From f02dc8e5280553a0508d6b12f45813fd9c9714c2 Mon Sep 17 00:00:00 2001 From: kimvde Date: Thu, 7 Mar 2024 06:30:04 -0800 Subject: [PATCH] Implement AudioGraphInput.flush PiperOrigin-RevId: 613563855 --- .../media3/transformer/AudioGraphInput.java | 24 ++- .../transformer/SilentAudioGenerator.java | 6 + .../transformer/AudioGraphInputTest.java | 144 ++++++++++++++++++ .../transformer/SilentAudioGeneratorTest.java | 27 ++++ 4 files changed, 200 insertions(+), 1 deletion(-) diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/AudioGraphInput.java b/libraries/transformer/src/main/java/androidx/media3/transformer/AudioGraphInput.java index 04dffd04ff..d9f8bd53fc 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/AudioGraphInput.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/AudioGraphInput.java @@ -183,13 +183,35 @@ import java.util.concurrent.atomic.AtomicReference; return true; } + /** + * Clears any pending input and output data. + * + *

Should only be called by the processing thread. + */ + public void flush() { + pendingMediaItemChange.set(null); + processedFirstMediaItemChange = true; + if (!availableInputBuffers.isEmpty()) { + // Clear first available buffer in case the caller wrote data in the input buffer without + // queueing it. + clearAndAddToAvailableBuffers(availableInputBuffers.remove()); + } + while (!pendingInputBuffers.isEmpty()) { + clearAndAddToAvailableBuffers(pendingInputBuffers.remove()); + } + silentAudioGenerator.flush(); + audioProcessingPipeline.flush(); + currentInputBufferBeingOutput = null; + receivedEndOfStreamFromInput = false; + queueEndOfStreamAfterSilence = false; + } + /** * Releases any underlying resources. * *

Should only be called by the processing thread. */ public void release() { - // TODO(b/303029174): Impl flush(), reset() & decide if a separate release() is still needed. audioProcessingPipeline.reset(); } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/SilentAudioGenerator.java b/libraries/transformer/src/main/java/androidx/media3/transformer/SilentAudioGenerator.java index fcd80f3052..de712da748 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/SilentAudioGenerator.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/SilentAudioGenerator.java @@ -73,4 +73,10 @@ import java.util.concurrent.atomic.AtomicLong; public boolean hasRemaining() { return internalBuffer.hasRemaining() || remainingBytesToOutput.get() > 0; } + + public void flush() { + remainingBytesToOutput.set(0); + internalBuffer.position(0); + internalBuffer.limit(0); + } } diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/AudioGraphInputTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/AudioGraphInputTest.java index 8d8249f574..12bf61f64e 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/AudioGraphInputTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/AudioGraphInputTest.java @@ -107,6 +107,47 @@ public class AudioGraphInputTest { assertThat(audioGraphInput.getOutputAudioFormat()).isEqualTo(STEREO_44100); } + @Test + public void getOutputAudioFormat_afterFlush_isSet() throws Exception { + AudioGraphInput audioGraphInput = + new AudioGraphInput( + /* requestedOutputAudioFormat= */ STEREO_44100, + /* editedMediaItem= */ FAKE_ITEM, + /* inputFormat= */ getPcmFormat(MONO_48000)); + + audioGraphInput.flush(); + + assertThat(audioGraphInput.getOutputAudioFormat()).isEqualTo(STEREO_44100); + } + + @Test + public void getInputBuffer_afterFlush_returnsEmptyBuffer() throws Exception { + AudioGraphInput audioGraphInput = + new AudioGraphInput( + /* requestedOutputAudioFormat= */ AudioFormat.NOT_SET, + /* editedMediaItem= */ FAKE_ITEM, + /* inputFormat= */ getPcmFormat(STEREO_44100)); + byte[] inputData = TestUtil.buildTestData(/* length= */ 100 * STEREO_44100.bytesPerFrame); + + audioGraphInput.onMediaItemChanged( + /* editedMediaItem= */ FAKE_ITEM, + /* durationUs= */ 1_000_000, + /* decodedFormat= */ getPcmFormat(STEREO_44100), + /* isLast= */ true); + + // Force the media item change to be processed. + checkState(!audioGraphInput.getOutput().hasRemaining()); + + // Fill input buffer. + DecoderInputBuffer inputBuffer = audioGraphInput.getInputBuffer(); + inputBuffer.ensureSpaceForWrite(inputData.length); + inputBuffer.data.put(inputData).flip(); + + audioGraphInput.flush(); + + assertThat(audioGraphInput.getInputBuffer().data.remaining()).isEqualTo(0); + } + @Test public void isEnded_whenInitialized_returnsFalse() throws Exception { AudioGraphInput audioGraphInput = @@ -143,6 +184,35 @@ public class AudioGraphInputTest { assertThat(audioGraphInput.isEnded()).isTrue(); } + @Test + public void isEnded_afterFlush_returnsFalse() throws Exception { + AudioGraphInput audioGraphInput = + new AudioGraphInput( + /* requestedOutputAudioFormat= */ AudioFormat.NOT_SET, + /* editedMediaItem= */ FAKE_ITEM, + /* inputFormat= */ getPcmFormat(MONO_44100)); + + audioGraphInput.onMediaItemChanged( + /* editedMediaItem= */ FAKE_ITEM, + /* durationUs= */ C.TIME_UNSET, + /* decodedFormat= */ getPcmFormat(MONO_44100), + /* isLast= */ false); + + // Force the media item change to be processed. + checkState(!audioGraphInput.getOutput().hasRemaining()); + + // Queue EOS. + audioGraphInput.getInputBuffer().setFlags(C.BUFFER_FLAG_END_OF_STREAM); + checkState(audioGraphInput.queueInputBuffer()); + + drainAudioGraphInputUntilEnded(audioGraphInput); + checkState(audioGraphInput.isEnded()); + + audioGraphInput.flush(); + + assertThat(audioGraphInput.isEnded()).isFalse(); + } + @Test public void getOutput_withoutMediaItemChange_returnsEmptyBuffer() throws Exception { AudioGraphInput audioGraphInput = @@ -224,6 +294,80 @@ public class AudioGraphInputTest { assertThat(bytesOutput).isEqualTo(expectedSampleCount * STEREO_44100.bytesPerFrame); } + @Test + public void getOutput_afterFlush_returnsEmptyBuffer() throws Exception { + AudioGraphInput audioGraphInput = + new AudioGraphInput( + /* requestedOutputAudioFormat= */ AudioFormat.NOT_SET, + /* editedMediaItem= */ FAKE_ITEM, + /* inputFormat= */ getPcmFormat(STEREO_44100)); + byte[] inputData = TestUtil.buildTestData(/* length= */ 100 * STEREO_44100.bytesPerFrame); + + audioGraphInput.onMediaItemChanged( + /* editedMediaItem= */ FAKE_ITEM, + /* durationUs= */ 1_000_000, + /* decodedFormat= */ getPcmFormat(STEREO_44100), + /* isLast= */ true); + + // Force the media item change to be processed. + checkState(!audioGraphInput.getOutput().hasRemaining()); + + // Queue inputData. + DecoderInputBuffer inputBuffer = audioGraphInput.getInputBuffer(); + inputBuffer.ensureSpaceForWrite(inputData.length); + inputBuffer.data.put(inputData).flip(); + checkState(audioGraphInput.queueInputBuffer()); + + audioGraphInput.flush(); + + // Queue EOS. + audioGraphInput.getInputBuffer().setFlags(C.BUFFER_FLAG_END_OF_STREAM); + checkState(audioGraphInput.queueInputBuffer()); + + List outputBytes = drainAudioGraphInputUntilEnded(audioGraphInput); + assertThat(outputBytes).isEmpty(); + } + + @Test + public void getOutput_afterFlushAndInput_returnsCorrectAmountOfBytes() throws Exception { + AudioGraphInput audioGraphInput = + new AudioGraphInput( + /* requestedOutputAudioFormat= */ AudioFormat.NOT_SET, + /* editedMediaItem= */ FAKE_ITEM, + /* inputFormat= */ getPcmFormat(STEREO_44100)); + byte[] inputData = TestUtil.buildTestData(/* length= */ 100 * STEREO_44100.bytesPerFrame); + + audioGraphInput.onMediaItemChanged( + /* editedMediaItem= */ FAKE_ITEM, + /* durationUs= */ 1_000_000, + /* decodedFormat= */ getPcmFormat(STEREO_44100), + /* isLast= */ true); + + // Force the media item change to be processed. + checkState(!audioGraphInput.getOutput().hasRemaining()); + + // Queue inputData. + DecoderInputBuffer inputBuffer = audioGraphInput.getInputBuffer(); + inputBuffer.ensureSpaceForWrite(inputData.length); + inputBuffer.data.put(inputData).flip(); + checkState(audioGraphInput.queueInputBuffer()); + + audioGraphInput.flush(); + + // Queue inputData. + inputBuffer = audioGraphInput.getInputBuffer(); + inputBuffer.ensureSpaceForWrite(inputData.length); + inputBuffer.data.put(inputData).flip(); + checkState(audioGraphInput.queueInputBuffer()); + + // Queue EOS. + audioGraphInput.getInputBuffer().setFlags(C.BUFFER_FLAG_END_OF_STREAM); + checkState(audioGraphInput.queueInputBuffer()); + + List outputBytes = drainAudioGraphInputUntilEnded(audioGraphInput); + assertThat(outputBytes).containsExactlyElementsIn(Bytes.asList(inputData)); + } + /** Drains the graph and returns the bytes output. */ private static List drainAudioGraphInputUntilEnded(AudioGraphInput audioGraphInput) throws Exception { diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/SilentAudioGeneratorTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/SilentAudioGeneratorTest.java index cc328e9007..b3bd112b14 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/SilentAudioGeneratorTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/SilentAudioGeneratorTest.java @@ -89,6 +89,33 @@ public class SilentAudioGeneratorTest { assertThat(generator.getBuffer().remaining()).isEqualTo(960); } + @Test + public void addSilence_afterFlush_producesCorrectNumberOfBytes() { + SilentAudioGenerator generator = + new SilentAudioGenerator( + new AudioFormat(/* sampleRate= */ 88_200, /* channelCount= */ 6, C.ENCODING_PCM_16BIT)); + + generator.addSilence(/* durationUs= */ 3_000_000); + generator.flush(); + generator.addSilence(/* durationUs= */ 1_500_000); + int bytesOutput = drainGenerator(generator); + + // 88_200 * 12 * 1.5s = 1_587_600 + assertThat(bytesOutput).isEqualTo(1_587_600); + } + + @Test + public void hasRemaining_afterFlush_isFalse() { + SilentAudioGenerator generator = + new SilentAudioGenerator( + new AudioFormat(/* sampleRate= */ 88_200, /* channelCount= */ 6, C.ENCODING_PCM_16BIT)); + + generator.addSilence(/* durationUs= */ 3_000_000); + generator.flush(); + + assertThat(generator.hasRemaining()).isFalse(); + } + /** Drains the generator and returns the number of bytes output. */ private static int drainGenerator(SilentAudioGenerator generator) { int bytesOutput = 0;