diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 68baa0da96..e11d2b0619 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -29,9 +29,9 @@ * Add `DataSpec.Builder` and deprecate most `DataSpec` constructors. * Add `DataSpec.customData` to allow applications to pass custom data through `DataSource` chains. - * Move the handling of encoded buffers in audio passthrough from - `AudioSink.handleBuffer` to `AudioSink.handleEncodedBuffer` to allow - passing multiple encoded frames in one buffer. + * Add a sample count parameter to `MediaCodecRenderer.processOutputBuffer` + and `AudioSink.handleBuffer` to allow batching multiple encoded frames + in one buffer. * Text: * Parse `` and `` tags in WebVTT subtitles (rendering is coming later). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java index 4f25fa1ecf..77b6877153 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.audio; import android.media.AudioTrack; -import androidx.annotation.IntRange; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -29,18 +28,19 @@ import java.nio.ByteBuffer; *

Before starting playback, specify the input audio format by calling {@link #configure(int, * int, int, int, int[], int, int)}. * - *

Call {@link #handleBuffer(ByteBuffer, long)} to write data, and {@link #handleDiscontinuity()} - * when the data being fed is discontinuous. Call {@link #play()} to start playing the written data. + *

Call {@link #handleBuffer(ByteBuffer, long, int)} to write data, and {@link + * #handleDiscontinuity()} when the data being fed is discontinuous. Call {@link #play()} to start + * playing the written data. * *

Call {@link #configure(int, int, int, int, int[], int, int)} whenever the input format * changes. The sink will be reinitialized on the next call to {@link #handleBuffer(ByteBuffer, - * long)}. + * long, int)}. * *

Call {@link #flush()} to prepare the sink to receive audio data from a new playback position. * *

Call {@link #playToEndOfStream()} repeatedly to play out all data when no more input buffers - * will be provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #flush()}. - * Call {@link #reset()} when the instance is no longer required. + * will be provided via {@link #handleBuffer(ByteBuffer, long, int)} until the next {@link + * #flush()}. Call {@link #reset()} when the instance is no longer required. * *

The implementation may be backed by a platform {@link AudioTrack}. In this case, {@link * #setAudioSessionId(int)}, {@link #setAudioAttributes(AudioAttributes)}, {@link @@ -223,7 +223,7 @@ public interface AudioSink { void handleDiscontinuity(); /** - * Attempts to process PCM data from a {@link ByteBuffer}, starting from its current position and + * Attempts to process data from a {@link ByteBuffer}, starting from its current position and * ending at its limit (exclusive). The position of the {@link ByteBuffer} is advanced by the * number of bytes that were handled. {@link Listener#onPositionDiscontinuity()} will be called if * {@code presentationTimeUs} is discontinuous with the last buffer handled since the last reset. @@ -233,39 +233,18 @@ public interface AudioSink { * except in the case of an intervening call to {@link #flush()} (or to {@link #configure(int, * int, int, int, int[], int, int)} that causes the sink to be flushed). * - *

For encoded data (eg in passthrough), use {@link #handleEncodedBuffer(ByteBuffer, long, - * int)}. - * * @param buffer The buffer containing audio data. * @param presentationTimeUs The presentation timestamp of the buffer in microseconds. + * @param encodedAccessUnitCount The number of encoded access units in the buffer, or 1 if the + * buffer contains PCM audio. This allows batching multiple encoded access units in one + * buffer. * @return Whether the buffer was handled fully. * @throws InitializationException If an error occurs initializing the sink. * @throws WriteException If an error occurs writing the audio data. */ - boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs) + boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs, int encodedAccessUnitCount) throws InitializationException, WriteException; - /** - * Attempts to process data from a {@link ByteBuffer}, starting from its current position and - * ending at its limit (exclusive). - * - *

This method is the same as {@link #handleBuffer(ByteBuffer, long)} for encoded (non PCM) - * audio. The only difference is that it requires to pass the number of audio encoded access - * unites (frames) in the buffer. This is used in passthrough mode. - * - * @param buffer The buffer containing audio data. - * @param presentationTimeUs The presentation timestamp of the buffer in microseconds. - * @param accessUnitCount The number of encoded access units in the buffer. - * @return Whether the buffer was handled fully. - * @throws InitializationException If an error occurs initializing the sink. - * @throws WriteException If an error occurs writing the audio data. - */ - default boolean handleEncodedBuffer( - ByteBuffer buffer, long presentationTimeUs, @IntRange(from = 1) int accessUnitCount) - throws InitializationException, WriteException { - throw new UnsupportedOperationException(); - } - /** * Processes any remaining data. {@link #isEnded()} will return {@code true} when no data remains. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 362b30ddbf..3441764447 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -559,22 +559,8 @@ public final class DefaultAudioSink implements AudioSink { } @Override - public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs) - throws InitializationException, WriteException { - Assertions.checkArgument(configuration.isInputPcm); - return handleBufferInternal(buffer, presentationTimeUs, /* encodedAccessUnitCount= */ 0); - } - - @Override - public boolean handleEncodedBuffer( - ByteBuffer buffer, long presentationTimeUs, int accessUnitCount) - throws InitializationException, WriteException { - Assertions.checkArgument(!configuration.isInputPcm); - return handleBufferInternal(buffer, presentationTimeUs, accessUnitCount); - } - @SuppressWarnings("ReferenceEquality") - private boolean handleBufferInternal( + public boolean handleBuffer( ByteBuffer buffer, long presentationTimeUs, int encodedAccessUnitCount) throws InitializationException, WriteException { Assertions.checkArgument(inputBuffer == null || buffer == inputBuffer); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java index ab1710ca58..5b06d1cea4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java @@ -74,16 +74,10 @@ public class ForwardingAudioSink implements AudioSink { } @Override - public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs) + public boolean handleBuffer( + ByteBuffer buffer, long presentationTimeUs, int encodedAccessUnitCount) throws InitializationException, WriteException { - return sink.handleBuffer(buffer, presentationTimeUs); - } - - @Override - public boolean handleEncodedBuffer( - ByteBuffer buffer, long presentationTimeUs, int accessUnitCount) - throws InitializationException, WriteException { - return sink.handleEncodedBuffer(buffer, presentationTimeUs, accessUnitCount); + return sink.handleBuffer(buffer, presentationTimeUs, encodedAccessUnitCount); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 1b64c18a17..c4b4bc0af9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -88,8 +88,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media private boolean allowFirstBufferPositionDiscontinuity; private boolean allowPositionDiscontinuity; - @C.Encoding private int audioSinkEncoding; - /** * @param context A context. * @param mediaCodecSelector A decoder selector. @@ -436,7 +434,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media // TODO(internal: b/145658993) Use outputFormat instead. throw createRendererException(e, inputFormat); } - audioSinkEncoding = encoding; } /** @@ -632,12 +629,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media boolean fullyConsumed; try { - if (Util.isEncodingLinearPcm(audioSinkEncoding)) { - fullyConsumed = audioSink.handleBuffer(buffer, bufferPresentationTimeUs); - } else { - fullyConsumed = - audioSink.handleEncodedBuffer(buffer, bufferPresentationTimeUs, sampleCount); - } + fullyConsumed = audioSink.handleBuffer(buffer, bufferPresentationTimeUs, sampleCount); } catch (AudioSink.InitializationException | AudioSink.WriteException e) { // TODO(internal: b/145658993) Use outputFormat instead. throw createRendererException(e, inputFormat); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index 0137d6a5b6..41e2401f98 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -358,7 +358,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements audioTrackNeedsConfigure = false; } - if (audioSink.handleBuffer(outputBuffer.data, outputBuffer.timeUs)) { + if (audioSink.handleBuffer( + outputBuffer.data, outputBuffer.timeUs, /* encodedAccessUnitCount= */ 1)) { decoderCounters.renderedOutputBufferCount++; outputBuffer.release(); outputBuffer = null; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java index 7982163ee8..c110a3f6cc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java @@ -38,7 +38,7 @@ import org.robolectric.annotation.Config; * data (i.e., the {@link android.media.AudioTrack#write} methods just return 0), so these tests are * currently limited to verifying behavior that doesn't rely on consuming data, and the position * will stay at its initial value. For example, we can't verify {@link - * AudioSink#handleBuffer(ByteBuffer, long)} handling a complete buffer, or queueing audio then + * AudioSink#handleBuffer(ByteBuffer, long, int)} handling a complete buffer, or queueing audio then * draining to the end of the stream. This could be worked around by having a test-only mode where * {@link DefaultAudioSink} automatically treats audio as consumed. */ @@ -77,12 +77,14 @@ public final class DefaultAudioSinkTest { @Test public void handlesBufferAfterReset() throws Exception { configureDefaultAudioSink(CHANNEL_COUNT_STEREO); - defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0); + defaultAudioSink.handleBuffer( + createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); // After reset and re-configure we can successfully queue more input. defaultAudioSink.reset(); configureDefaultAudioSink(CHANNEL_COUNT_STEREO); - defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0); + defaultAudioSink.handleBuffer( + createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); } @Test @@ -90,24 +92,28 @@ public final class DefaultAudioSinkTest { PlaybackParameters playbackParameters = new PlaybackParameters(1.5f); defaultAudioSink.setPlaybackParameters(playbackParameters); configureDefaultAudioSink(CHANNEL_COUNT_STEREO); - defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0); + defaultAudioSink.handleBuffer( + createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); // After reset and re-configure we can successfully queue more input. defaultAudioSink.reset(); configureDefaultAudioSink(CHANNEL_COUNT_STEREO); - defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0); + defaultAudioSink.handleBuffer( + createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); assertThat(defaultAudioSink.getPlaybackParameters()).isEqualTo(playbackParameters); } @Test public void handlesBufferAfterReset_withFormatChange() throws Exception { configureDefaultAudioSink(CHANNEL_COUNT_STEREO); - defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0); + defaultAudioSink.handleBuffer( + createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); // After reset and re-configure we can successfully queue more input. defaultAudioSink.reset(); configureDefaultAudioSink(CHANNEL_COUNT_MONO); - defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0); + defaultAudioSink.handleBuffer( + createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); } @Test @@ -115,12 +121,14 @@ public final class DefaultAudioSinkTest { PlaybackParameters playbackParameters = new PlaybackParameters(1.5f); defaultAudioSink.setPlaybackParameters(playbackParameters); configureDefaultAudioSink(CHANNEL_COUNT_STEREO); - defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0); + defaultAudioSink.handleBuffer( + createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); // After reset and re-configure we can successfully queue more input. defaultAudioSink.reset(); configureDefaultAudioSink(CHANNEL_COUNT_MONO); - defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0); + defaultAudioSink.handleBuffer( + createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); assertThat(defaultAudioSink.getPlaybackParameters()).isEqualTo(playbackParameters); } @@ -130,7 +138,8 @@ public final class DefaultAudioSinkTest { CHANNEL_COUNT_STEREO, /* trimStartFrames= */ TRIM_100_MS_FRAME_COUNT, /* trimEndFrames= */ 0); - defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0); + defaultAudioSink.handleBuffer( + createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); assertThat(arrayAudioBufferSink.output) .hasLength( @@ -145,7 +154,8 @@ public final class DefaultAudioSinkTest { CHANNEL_COUNT_STEREO, /* trimStartFrames= */ 0, /* trimEndFrames= */ TRIM_10_MS_FRAME_COUNT); - defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0); + defaultAudioSink.handleBuffer( + createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); assertThat(arrayAudioBufferSink.output) .hasLength( @@ -160,7 +170,8 @@ public final class DefaultAudioSinkTest { CHANNEL_COUNT_STEREO, /* trimStartFrames= */ TRIM_100_MS_FRAME_COUNT, /* trimEndFrames= */ TRIM_10_MS_FRAME_COUNT); - defaultAudioSink.handleBuffer(createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0); + defaultAudioSink.handleBuffer( + createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); assertThat(arrayAudioBufferSink.output) .hasLength( @@ -173,14 +184,18 @@ public final class DefaultAudioSinkTest { public void getCurrentPosition_returnsPositionFromFirstBuffer() throws Exception { configureDefaultAudioSink(CHANNEL_COUNT_STEREO); defaultAudioSink.handleBuffer( - createDefaultSilenceBuffer(), /* presentationTimeUs= */ 5 * C.MICROS_PER_SECOND); + createDefaultSilenceBuffer(), + /* presentationTimeUs= */ 5 * C.MICROS_PER_SECOND, + /* encodedAccessUnitCount= */ 1); assertThat(defaultAudioSink.getCurrentPositionUs(/* sourceEnded= */ false)) .isEqualTo(5 * C.MICROS_PER_SECOND); defaultAudioSink.reset(); configureDefaultAudioSink(CHANNEL_COUNT_STEREO); defaultAudioSink.handleBuffer( - createDefaultSilenceBuffer(), /* presentationTimeUs= */ 8 * C.MICROS_PER_SECOND); + createDefaultSilenceBuffer(), + /* presentationTimeUs= */ 8 * C.MICROS_PER_SECOND, + /* encodedAccessUnitCount= */ 1); assertThat(defaultAudioSink.getCurrentPositionUs(/* sourceEnded= */ false)) .isEqualTo(8 * C.MICROS_PER_SECOND); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingAudioSink.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingAudioSink.java index a2710c21ab..963d940d0d 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingAudioSink.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingAudioSink.java @@ -80,26 +80,10 @@ public final class CapturingAudioSink extends ForwardingAudioSink implements Dum } @Override - public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs) - throws InitializationException, WriteException { - interceptBuffer(buffer, presentationTimeUs); - boolean fullyConsumed = super.handleBuffer(buffer, presentationTimeUs); - updateCurrentBuffer(fullyConsumed); - return fullyConsumed; - } - - @Override - public boolean handleEncodedBuffer( - ByteBuffer buffer, long presentationTimeUs, int accessUnitCount) - throws InitializationException, WriteException { - interceptBuffer(buffer, presentationTimeUs); - boolean fullyConsumed = super.handleEncodedBuffer(buffer, presentationTimeUs, accessUnitCount); - updateCurrentBuffer(fullyConsumed); - return fullyConsumed; - } - @SuppressWarnings("ReferenceEquality") - private void interceptBuffer(ByteBuffer buffer, long presentationTimeUs) { + public boolean handleBuffer( + ByteBuffer buffer, long presentationTimeUs, int encodedAccessUnitCount) + throws InitializationException, WriteException { // handleBuffer is called repeatedly with the same buffer until it's been fully consumed by the // sink. We only want to dump each buffer once, and we need to do so before the sink being // forwarded to has a chance to modify its position. @@ -107,13 +91,13 @@ public final class CapturingAudioSink extends ForwardingAudioSink implements Dum interceptedData.add(new DumpableBuffer(buffer, presentationTimeUs)); currentBuffer = buffer; } - } - - private void updateCurrentBuffer(boolean fullyConsumed) { + boolean fullyConsumed = super.handleBuffer(buffer, presentationTimeUs, encodedAccessUnitCount); if (fullyConsumed) { currentBuffer = null; } + return fullyConsumed; } + @Override public void flush() { currentBuffer = null;