Make the base values of SilenceSkippingAudioProcessor configurable.
Issue:#6705 PiperOrigin-RevId: 310907118
This commit is contained in:
parent
5f6a489bd1
commit
ad1dffcae8
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
### Next release ###
|
### Next release ###
|
||||||
|
|
||||||
|
* Enable the configuration of `SilenceSkippingAudioProcessor`
|
||||||
|
([#6705](https://github.com/google/ExoPlayer/issues/6705)).
|
||||||
* Add `SilenceMediaSource.Factory` to support tags
|
* Add `SilenceMediaSource.Factory` to support tags
|
||||||
([PR #7245](https://github.com/google/ExoPlayer/pull/7245)).
|
([PR #7245](https://github.com/google/ExoPlayer/pull/7245)).
|
||||||
* Avoid throwing an exception while parsing fragmented MP4 default sample
|
* Avoid throwing an exception while parsing fragmented MP4 default sample
|
||||||
|
@ -120,9 +120,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];
|
||||||
@ -132,8 +143,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;
|
||||||
}
|
}
|
||||||
|
@ -32,17 +32,20 @@ import java.nio.ByteOrder;
|
|||||||
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
|
||||||
@ -60,8 +63,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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,8 +88,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;
|
||||||
}
|
}
|
||||||
@ -158,11 +186,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];
|
||||||
}
|
}
|
||||||
@ -320,7 +348,7 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
|
|||||||
Assertions.checkArgument(buffer.order() == ByteOrder.LITTLE_ENDIAN);
|
Assertions.checkArgument(buffer.order() == ByteOrder.LITTLE_ENDIAN);
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
@ -336,7 +364,7 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
|
|||||||
Assertions.checkArgument(buffer.order() == ByteOrder.LITTLE_ENDIAN);
|
Assertions.checkArgument(buffer.order() == ByteOrder.LITTLE_ENDIAN);
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
@ -204,7 +204,34 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSkipThenFlush_resetsSkippedFrameCount() throws Exception {
|
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 {
|
||||||
// Given a signal that alternates between silence and noise.
|
// Given a signal that alternates between silence and noise.
|
||||||
InputBufferProvider inputBufferProvider =
|
InputBufferProvider inputBufferProvider =
|
||||||
getInputBufferProviderForAlternatingSilenceAndNoise(
|
getInputBufferProviderForAlternatingSilenceAndNoise(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user