Commit 8528129e by samrobinson Committed by Oliver Woodman

Make the base values of SilenceSkippingAudioProcessor configurable.

Issue:#6705
PiperOrigin-RevId: 310907118
parent 88223882
...@@ -84,6 +84,8 @@ ...@@ -84,6 +84,8 @@
([#7247](https://github.com/google/ExoPlayer/pull/7247)). ([#7247](https://github.com/google/ExoPlayer/pull/7247)).
* Replace `CacheDataSinkFactory` and `CacheDataSourceFactory` with * Replace `CacheDataSinkFactory` and `CacheDataSourceFactory` with
`CacheDataSink.Factory` and `CacheDataSource.Factory` respectively. `CacheDataSink.Factory` and `CacheDataSource.Factory` respectively.
* Enable the configuration of `SilenceSkippingAudioProcessor`
([#6705](https://github.com/google/ExoPlayer/issues/6705)).
* Video: Pass frame rate hint to `Surface.setFrameRate` on Android R devices. * Video: Pass frame rate hint to `Surface.setFrameRate` on Android R devices.
* Text: * Text:
* Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming * Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming
......
...@@ -131,9 +131,20 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -131,9 +131,20 @@ public final class DefaultAudioSink implements AudioSink {
/** /**
* Creates a new default chain of audio processors, with the user-defined {@code * Creates a new default chain of audio processors, with the user-defined {@code
* audioProcessors} applied before silence skipping and playback parameters. * audioProcessors} applied before silence skipping and speed adjustment processors.
*/ */
public DefaultAudioProcessorChain(AudioProcessor... audioProcessors) { public DefaultAudioProcessorChain(AudioProcessor... audioProcessors) {
this(audioProcessors, new SilenceSkippingAudioProcessor(), new SonicAudioProcessor());
}
/**
* Creates a new default chain of audio processors, with the user-defined {@code
* audioProcessors} applied before silence skipping and speed adjustment processors.
*/
public DefaultAudioProcessorChain(
AudioProcessor[] audioProcessors,
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor,
SonicAudioProcessor sonicAudioProcessor) {
// The passed-in type may be more specialized than AudioProcessor[], so allocate a new array // The passed-in type may be more specialized than AudioProcessor[], so allocate a new array
// rather than using Arrays.copyOf. // rather than using Arrays.copyOf.
this.audioProcessors = new AudioProcessor[audioProcessors.length + 2]; this.audioProcessors = new AudioProcessor[audioProcessors.length + 2];
...@@ -143,8 +154,8 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -143,8 +154,8 @@ public final class DefaultAudioSink implements AudioSink {
/* dest= */ this.audioProcessors, /* dest= */ this.audioProcessors,
/* destPos= */ 0, /* destPos= */ 0,
/* length= */ audioProcessors.length); /* length= */ audioProcessors.length);
silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor(); this.silenceSkippingAudioProcessor = silenceSkippingAudioProcessor;
sonicAudioProcessor = new SonicAudioProcessor(); this.sonicAudioProcessor = sonicAudioProcessor;
this.audioProcessors[audioProcessors.length] = silenceSkippingAudioProcessor; this.audioProcessors[audioProcessors.length] = silenceSkippingAudioProcessor;
this.audioProcessors[audioProcessors.length + 1] = sonicAudioProcessor; this.audioProcessors[audioProcessors.length + 1] = sonicAudioProcessor;
} }
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.audio; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.audio;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
...@@ -30,17 +31,20 @@ import java.nio.ByteBuffer; ...@@ -30,17 +31,20 @@ import java.nio.ByteBuffer;
public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor { public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
/** /**
* The minimum duration of audio that must be below {@link #SILENCE_THRESHOLD_LEVEL} to classify * The default value for {@link #SilenceSkippingAudioProcessor(long, long, short)
* that part of audio as silent, in microseconds. * minimumSilenceDurationUs}.
*/ */
private static final long MINIMUM_SILENCE_DURATION_US = 150_000; public static final long DEFAULT_MINIMUM_SILENCE_DURATION_US = 150_000;
/** /**
* The duration of silence by which to extend non-silent sections, in microseconds. The value must * The default value for {@link #SilenceSkippingAudioProcessor(long, long, short)
* not exceed {@link #MINIMUM_SILENCE_DURATION_US}. * paddingSilenceUs}.
*/ */
private static final long PADDING_SILENCE_US = 20_000; public static final long DEFAULT_PADDING_SILENCE_US = 20_000;
/** The absolute level below which an individual PCM sample is classified as silent. */ /**
private static final short SILENCE_THRESHOLD_LEVEL = 1024; * The default value for {@link #SilenceSkippingAudioProcessor(long, long, short)
* silenceThresholdLevel}.
*/
public static final short DEFAULT_SILENCE_THRESHOLD_LEVEL = 1024;
/** Trimming states. */ /** Trimming states. */
@Documented @Documented
...@@ -58,8 +62,10 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor { ...@@ -58,8 +62,10 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
/** State when the input is silent. */ /** State when the input is silent. */
private static final int STATE_SILENT = 2; private static final int STATE_SILENT = 2;
private final long minimumSilenceDurationUs;
private final long paddingSilenceUs;
private final short silenceThresholdLevel;
private int bytesPerFrame; private int bytesPerFrame;
private boolean enabled; private boolean enabled;
/** /**
...@@ -81,8 +87,31 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor { ...@@ -81,8 +87,31 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
private boolean hasOutputNoise; private boolean hasOutputNoise;
private long skippedFrames; private long skippedFrames;
/** Creates a new silence trimming audio processor. */ /** Creates a new silence skipping audio processor. */
public SilenceSkippingAudioProcessor() { public SilenceSkippingAudioProcessor() {
this(
DEFAULT_MINIMUM_SILENCE_DURATION_US,
DEFAULT_PADDING_SILENCE_US,
DEFAULT_SILENCE_THRESHOLD_LEVEL);
}
/**
* Creates a new silence skipping audio processor.
*
* @param minimumSilenceDurationUs The minimum duration of audio that must be below {@code
* silenceThresholdLevel} to classify that part of audio as silent, in microseconds.
* @param paddingSilenceUs The duration of silence by which to extend non-silent sections, in
* microseconds. The value must not exceed {@code minimumSilenceDurationUs}.
* @param silenceThresholdLevel The absolute level below which an individual PCM sample is
* classified as silent.
*/
public SilenceSkippingAudioProcessor(
long minimumSilenceDurationUs, long paddingSilenceUs, short silenceThresholdLevel) {
Assertions.checkArgument(paddingSilenceUs <= minimumSilenceDurationUs);
this.minimumSilenceDurationUs = minimumSilenceDurationUs;
this.paddingSilenceUs = paddingSilenceUs;
this.silenceThresholdLevel = silenceThresholdLevel;
maybeSilenceBuffer = Util.EMPTY_BYTE_ARRAY; maybeSilenceBuffer = Util.EMPTY_BYTE_ARRAY;
paddingBuffer = Util.EMPTY_BYTE_ARRAY; paddingBuffer = Util.EMPTY_BYTE_ARRAY;
} }
...@@ -156,11 +185,11 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor { ...@@ -156,11 +185,11 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
protected void onFlush() { protected void onFlush() {
if (enabled) { if (enabled) {
bytesPerFrame = inputAudioFormat.bytesPerFrame; bytesPerFrame = inputAudioFormat.bytesPerFrame;
int maybeSilenceBufferSize = durationUsToFrames(MINIMUM_SILENCE_DURATION_US) * bytesPerFrame; int maybeSilenceBufferSize = durationUsToFrames(minimumSilenceDurationUs) * bytesPerFrame;
if (maybeSilenceBuffer.length != maybeSilenceBufferSize) { if (maybeSilenceBuffer.length != maybeSilenceBufferSize) {
maybeSilenceBuffer = new byte[maybeSilenceBufferSize]; maybeSilenceBuffer = new byte[maybeSilenceBufferSize];
} }
paddingSize = durationUsToFrames(PADDING_SILENCE_US) * bytesPerFrame; paddingSize = durationUsToFrames(paddingSilenceUs) * bytesPerFrame;
if (paddingBuffer.length != paddingSize) { if (paddingBuffer.length != paddingSize) {
paddingBuffer = new byte[paddingSize]; paddingBuffer = new byte[paddingSize];
} }
...@@ -317,7 +346,7 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor { ...@@ -317,7 +346,7 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
private int findNoisePosition(ByteBuffer buffer) { private int findNoisePosition(ByteBuffer buffer) {
// The input is in ByteOrder.nativeOrder(), which is little endian on Android. // The input is in ByteOrder.nativeOrder(), which is little endian on Android.
for (int i = buffer.position(); i < buffer.limit(); i += 2) { for (int i = buffer.position(); i < buffer.limit(); i += 2) {
if (Math.abs(buffer.getShort(i)) > SILENCE_THRESHOLD_LEVEL) { if (Math.abs(buffer.getShort(i)) > silenceThresholdLevel) {
// Round to the start of the frame. // Round to the start of the frame.
return bytesPerFrame * (i / bytesPerFrame); return bytesPerFrame * (i / bytesPerFrame);
} }
...@@ -332,7 +361,7 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor { ...@@ -332,7 +361,7 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
private int findNoiseLimit(ByteBuffer buffer) { private int findNoiseLimit(ByteBuffer buffer) {
// The input is in ByteOrder.nativeOrder(), which is little endian on Android. // The input is in ByteOrder.nativeOrder(), which is little endian on Android.
for (int i = buffer.limit() - 2; i >= buffer.position(); i -= 2) { for (int i = buffer.limit() - 2; i >= buffer.position(); i -= 2) {
if (Math.abs(buffer.getShort(i)) > SILENCE_THRESHOLD_LEVEL) { if (Math.abs(buffer.getShort(i)) > silenceThresholdLevel) {
// Return the start of the next frame. // Return the start of the next frame.
return bytesPerFrame * (i / bytesPerFrame) + bytesPerFrame; return bytesPerFrame * (i / bytesPerFrame) + bytesPerFrame;
} }
......
...@@ -203,6 +203,33 @@ public final class SilenceSkippingAudioProcessorTest { ...@@ -203,6 +203,33 @@ public final class SilenceSkippingAudioProcessorTest {
} }
@Test @Test
public void customPaddingValue_hasCorrectOutputAndSkippedFrameCounts() throws Exception {
// Given a signal that alternates between silence and noise.
InputBufferProvider inputBufferProvider =
getInputBufferProviderForAlternatingSilenceAndNoise(
TEST_SIGNAL_SILENCE_DURATION_MS,
TEST_SIGNAL_NOISE_DURATION_MS,
TEST_SIGNAL_FRAME_COUNT);
// When processing the entire signal with a larger than normal padding silence.
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
new SilenceSkippingAudioProcessor(
SilenceSkippingAudioProcessor.DEFAULT_MINIMUM_SILENCE_DURATION_US,
/* paddingSilenceUs= */ 21_000,
SilenceSkippingAudioProcessor.DEFAULT_SILENCE_THRESHOLD_LEVEL);
silenceSkippingAudioProcessor.setEnabled(true);
silenceSkippingAudioProcessor.configure(AUDIO_FORMAT);
silenceSkippingAudioProcessor.flush();
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
long totalOutputFrames =
process(silenceSkippingAudioProcessor, inputBufferProvider, /* inputBufferSize= */ 120);
// The right number of frames are skipped/output.
assertThat(totalOutputFrames).isEqualTo(58379);
assertThat(silenceSkippingAudioProcessor.getSkippedFrames()).isEqualTo(41621);
}
@Test
public void skipThenFlush_resetsSkippedFrameCount() throws Exception { public void skipThenFlush_resetsSkippedFrameCount() throws Exception {
// Given a signal that alternates between silence and noise. // Given a signal that alternates between silence and noise.
InputBufferProvider inputBufferProvider = InputBufferProvider inputBufferProvider =
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment