Use BaseAudioProcessor format tracking in SilenceSkippingAudioProcessor

The class currently tracks the input format itself, updating it too
early in onConfigure() instead of onFlush(). This causes issues when
the format changes and the new values are applied to the silence
skipping logic of the old format. The fix is to use the base class
input format handling instead.

Issue: androidx/media#1352
PiperOrigin-RevId: 633232368
This commit is contained in:
tonihei 2024-05-13 09:15:09 -07:00 committed by Copybara-Service
parent 04ab71a1d4
commit 1a5cf6718b
4 changed files with 40 additions and 8 deletions

View File

@ -51,6 +51,10 @@
in `DefaultAudioSink` prior to calling `AudioTrack.stop()` so that
`AudioTrack.StreamEventCallback#onPresentationEnded` correctly
identifies when all pending data has been played.
* Fix bug in `SilenceSkippingAudioProcessor` where transitions between
different audio formats (for example stereo to mono) can cause the
processor to throw an exception
([#1352](https://github.com/androidx/media/issues/1352)).
* Video:
* Text:
* Fix issue where subtitles starting before a seek position are skipped.

View File

@ -57,6 +57,7 @@ public abstract class BaseAudioProcessor implements AudioProcessor {
return isActive() ? pendingOutputAudioFormat : AudioFormat.NOT_SET;
}
@CallSuper
@Override
public boolean isActive() {
return pendingOutputAudioFormat != AudioFormat.NOT_SET;

View File

@ -146,7 +146,6 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
*/
private final long maxSilenceToKeepDurationUs;
private AudioFormat inputFormat;
private int bytesPerFrame;
private boolean enabled;
private @State int state;
@ -236,7 +235,6 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
this.maxSilenceToKeepDurationUs = maxSilenceToKeepDurationUs;
this.minVolumeToKeepPercentageWhenMuting = minVolumeToKeepPercentageWhenMuting;
this.silenceThresholdLevel = silenceThresholdLevel;
inputFormat = AudioFormat.NOT_SET;
maybeSilenceBuffer = Util.EMPTY_BYTE_ARRAY;
contiguousOutputBuffer = Util.EMPTY_BYTE_ARRAY;
}
@ -266,14 +264,15 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) {
throw new UnhandledAudioFormatException(inputAudioFormat);
}
this.inputFormat = inputAudioFormat;
bytesPerFrame = inputAudioFormat.channelCount * 2;
if (inputAudioFormat.sampleRate == Format.NO_VALUE) {
return AudioFormat.NOT_SET;
}
return inputAudioFormat;
}
@Override
public boolean isActive() {
return inputFormat.sampleRate != Format.NO_VALUE && enabled;
return super.isActive() && enabled;
}
@Override
@ -307,6 +306,7 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
@Override
public void onFlush() {
if (isActive()) {
bytesPerFrame = inputAudioFormat.channelCount * 2;
// Divide by 2 to allow the buffer to be split into two bytesPerFrame aligned parts.
int maybeSilenceBufferSize =
alignToBytePerFrameBoundary(durationUsToFrames(minimumSilenceDurationUs) / 2) * 2;
@ -325,7 +325,6 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
@Override
public void onReset() {
enabled = false;
inputFormat = AudioFormat.NOT_SET;
maybeSilenceBuffer = Util.EMPTY_BYTE_ARRAY;
contiguousOutputBuffer = Util.EMPTY_BYTE_ARRAY;
}
@ -711,7 +710,7 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
* Returns the number of input frames corresponding to {@code durationUs} microseconds of audio.
*/
private int durationUsToFrames(long durationUs) {
return (int) ((durationUs * inputFormat.sampleRate) / C.MICROS_PER_SECOND);
return (int) ((durationUs * inputAudioFormat.sampleRate) / C.MICROS_PER_SECOND);
}
/**

View File

@ -177,6 +177,35 @@ public final class SilenceSkippingAudioProcessorTest {
.isEqualTo(TEST_SIGNAL_FRAME_COUNT - totalOutputFrames);
}
@Test
public void
skipInAlternatingTestSignal_withEarlyConfigureForNextFormat_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);
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
new SilenceSkippingAudioProcessor();
silenceSkippingAudioProcessor.setEnabled(true);
silenceSkippingAudioProcessor.configure(AUDIO_FORMAT);
silenceSkippingAudioProcessor.flush();
// Early configure the next format without flushing yet (this format should be ignored).
silenceSkippingAudioProcessor.configure(
new AudioFormat(
/* sampleRate= */ 1000, /* channelCount= */ 1, /* encoding= */ C.ENCODING_PCM_16BIT));
long totalOutputFrames =
process(silenceSkippingAudioProcessor, inputBufferProvider, INPUT_BUFFER_SIZE);
// The output has 50000 frames of noise, plus 50 * 0.2 * 1000 padding (plus rounding errors).
assertThat(totalOutputFrames).isIn(Range.closed(60000L - 500L, 60000L + 500L));
assertThat(silenceSkippingAudioProcessor.getSkippedFrames())
.isEqualTo(TEST_SIGNAL_FRAME_COUNT - totalOutputFrames);
}
@Test
public void skipWithSmallerInputBufferSize_hasCorrectOutputAndSkippedFrameCounts()
throws Exception {
@ -291,7 +320,6 @@ public final class SilenceSkippingAudioProcessorTest {
InputBufferProvider inputBufferProvider,
int inputBufferSize) {
int bytesPerFrame = AUDIO_FORMAT.bytesPerFrame;
processor.flush();
long totalOutputFrames = 0;
while (inputBufferProvider.hasRemaining()) {
ByteBuffer inputBuffer = inputBufferProvider.getNextInputBuffer(inputBufferSize);