diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java index 07328633dc3..b15f91fd9fb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java @@ -108,7 +108,7 @@ public final void enable( state = STATE_ENABLED; onEnabled(joining, mayRenderStartOfStream); replaceStream(formats, stream, startPositionUs, offsetUs); - resetPosition(positionUs, joining); + resetPosition(startPositionUs, joining); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index c6a970785c0..3c094ac209d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -83,7 +83,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.List; /** An abstract renderer that uses {@link MediaCodec} to decode samples for rendering. */ @@ -298,7 +297,6 @@ private static String buildCustomDiagnosticInfo(int errorCode) { private final DecoderInputBuffer buffer; private final DecoderInputBuffer bypassSampleBuffer; private final BatchBuffer bypassBatchBuffer; - private final ArrayList decodeOnlyPresentationTimestamps; private final MediaCodec.BufferInfo outputBufferInfo; private final ArrayDeque pendingOutputStreamChanges; private final OggOpusAudioPacketizer oggOpusAudioPacketizer; @@ -399,7 +397,6 @@ public MediaCodecRenderer( buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); bypassSampleBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT); bypassBatchBuffer = new BatchBuffer(); - decodeOnlyPresentationTimestamps = new ArrayList<>(); outputBufferInfo = new MediaCodec.BufferInfo(); currentPlaybackSpeed = 1f; targetPlaybackSpeed = 1f; @@ -919,7 +916,6 @@ protected void resetCodecStateForFlush() { shouldSkipAdaptationWorkaroundOutputBuffer = false; isDecodeOnlyOutputBuffer = false; isLastOutputBuffer = false; - decodeOnlyPresentationTimestamps.clear(); largestQueuedPresentationTimeUs = C.TIME_UNSET; lastBufferInStreamPresentationTimeUs = C.TIME_UNSET; lastProcessedOutputBufferTimeUs = C.TIME_UNSET; @@ -1376,9 +1372,6 @@ private boolean feedInputBuffer() throws ExoPlaybackException { c2Mp3TimestampTracker.getLastOutputBufferPresentationTimeUs(inputFormat)); } - if (buffer.isDecodeOnly()) { - decodeOnlyPresentationTimestamps.add(presentationTimeUs); - } if (waitingForFirstSampleInFormat) { if (!pendingOutputStreamChanges.isEmpty()) { pendingOutputStreamChanges.peekLast().formatQueue.add(presentationTimeUs, inputFormat); @@ -1908,7 +1901,7 @@ private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs) && largestQueuedPresentationTimeUs != C.TIME_UNSET) { outputBufferInfo.presentationTimeUs = largestQueuedPresentationTimeUs; } - isDecodeOnlyOutputBuffer = isDecodeOnlyBuffer(outputBufferInfo.presentationTimeUs); + isDecodeOnlyOutputBuffer = outputBufferInfo.presentationTimeUs < getLastResetPositionUs(); isLastOutputBuffer = lastBufferInStreamPresentationTimeUs == outputBufferInfo.presentationTimeUs; updateOutputFormatForTime(outputBufferInfo.presentationTimeUs); @@ -2199,19 +2192,6 @@ private void reinitializeCodec() throws ExoPlaybackException { maybeInitCodecOrBypass(); } - private boolean isDecodeOnlyBuffer(long presentationTimeUs) { - // We avoid using decodeOnlyPresentationTimestamps.remove(presentationTimeUs) because it would - // box presentationTimeUs, creating a Long object that would need to be garbage collected. - int size = decodeOnlyPresentationTimestamps.size(); - for (int i = 0; i < size; i++) { - if (decodeOnlyPresentationTimestamps.get(i) == presentationTimeUs) { - decodeOnlyPresentationTimestamps.remove(i); - return true; - } - } - return false; - } - @RequiresApi(23) private void updateDrmSessionV23() throws ExoPlaybackException { CryptoConfig cryptoConfig = sourceDrmSession.getCryptoConfig(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecRendererTest.java index b07f09478f7..70657b97fb0 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecRendererTest.java @@ -19,6 +19,9 @@ import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.spy; @@ -323,6 +326,90 @@ public void render_withReplaceStream_triggersOutputCallbacksInCorrectOrder() thr inOrder.verify(renderer).onProcessedOutputBuffer(400); } + @Test + public void render_afterEnableWithStartPositionUs_skipsSamplesBeforeStartPositionUs() + throws Exception { + Format format = + new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AAC).setAverageBitrate(1000).build(); + FakeSampleStream fakeSampleStream = + createFakeSampleStream(format, /* sampleTimesUs...= */ 0, 100, 200, 300, 400, 500); + MediaCodecRenderer renderer = spy(new TestRenderer()); + renderer.init(/* index= */ 0, PlayerId.UNSET); + + renderer.enable( + RendererConfiguration.DEFAULT, + new Format[] {format}, + fakeSampleStream, + /* positionUs= */ 0, + /* joining= */ false, + /* mayRenderStartOfStream= */ true, + /* startPositionUs= */ 300, + /* offsetUs= */ 0); + renderer.start(); + renderer.setCurrentStreamFinal(); + long positionUs = 0; + while (!renderer.isEnded()) { + renderer.render(positionUs, SystemClock.elapsedRealtime()); + positionUs += 100; + } + + InOrder inOrder = inOrder(renderer); + verifyProcessOutputBufferDecodeOnly( + inOrder, renderer, /* presentationTimeUs= */ 0, /* isDecodeOnly= */ true); + verifyProcessOutputBufferDecodeOnly( + inOrder, renderer, /* presentationTimeUs= */ 100, /* isDecodeOnly= */ true); + verifyProcessOutputBufferDecodeOnly( + inOrder, renderer, /* presentationTimeUs= */ 200, /* isDecodeOnly= */ true); + verifyProcessOutputBufferDecodeOnly( + inOrder, renderer, /* presentationTimeUs= */ 300, /* isDecodeOnly= */ false); + verifyProcessOutputBufferDecodeOnly( + inOrder, renderer, /* presentationTimeUs= */ 400, /* isDecodeOnly= */ false); + verifyProcessOutputBufferDecodeOnly( + inOrder, renderer, /* presentationTimeUs= */ 500, /* isDecodeOnly= */ false); + } + + @Test + public void render_afterPositionReset_skipsSamplesBeforeStartPositionUs() throws Exception { + Format format = + new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AAC).setAverageBitrate(1000).build(); + FakeSampleStream fakeSampleStream = + createFakeSampleStream(format, /* sampleTimesUs...= */ 0, 100, 200, 300, 400, 500); + MediaCodecRenderer renderer = spy(new TestRenderer()); + renderer.init(/* index= */ 0, PlayerId.UNSET); + renderer.enable( + RendererConfiguration.DEFAULT, + new Format[] {format}, + fakeSampleStream, + /* positionUs= */ 0, + /* joining= */ false, + /* mayRenderStartOfStream= */ true, + /* startPositionUs= */ 400, + /* offsetUs= */ 0); + renderer.start(); + + renderer.resetPosition(/* positionUs= */ 200); + renderer.setCurrentStreamFinal(); + long positionUs = 0; + while (!renderer.isEnded()) { + renderer.render(positionUs, SystemClock.elapsedRealtime()); + positionUs += 100; + } + + InOrder inOrder = inOrder(renderer); + verifyProcessOutputBufferDecodeOnly( + inOrder, renderer, /* presentationTimeUs= */ 0, /* isDecodeOnly= */ true); + verifyProcessOutputBufferDecodeOnly( + inOrder, renderer, /* presentationTimeUs= */ 100, /* isDecodeOnly= */ true); + verifyProcessOutputBufferDecodeOnly( + inOrder, renderer, /* presentationTimeUs= */ 200, /* isDecodeOnly= */ false); + verifyProcessOutputBufferDecodeOnly( + inOrder, renderer, /* presentationTimeUs= */ 300, /* isDecodeOnly= */ false); + verifyProcessOutputBufferDecodeOnly( + inOrder, renderer, /* presentationTimeUs= */ 400, /* isDecodeOnly= */ false); + verifyProcessOutputBufferDecodeOnly( + inOrder, renderer, /* presentationTimeUs= */ 500, /* isDecodeOnly= */ false); + } + private FakeSampleStream createFakeSampleStream(Format format, long... sampleTimesUs) { ImmutableList.Builder sampleListBuilder = ImmutableList.builder(); @@ -429,4 +516,23 @@ protected DecoderReuseEvaluation canReuseCodec( /* discardReasons= */ 0); } } + + private static void verifyProcessOutputBufferDecodeOnly( + InOrder inOrder, MediaCodecRenderer renderer, long presentationTimeUs, boolean isDecodeOnly) + throws Exception { + inOrder + .verify(renderer) + .processOutputBuffer( + anyLong(), + anyLong(), + any(), + any(), + anyInt(), + anyInt(), + anyInt(), + eq(presentationTimeUs), + eq(isDecodeOnly), + anyBoolean(), + any()); + } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java index 6383e10ee4c..ec1ac30f3b4 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java @@ -254,7 +254,7 @@ public void render_withBufferLimitEqualToNumberOfSamples_rendersLastFrameAfterEn /* positionUs= */ 0, /* joining= */ false, /* mayRenderStartOfStream= */ true, - /* startPositionUs= */ 0, + /* startPositionUs= */ 30_000, /* offsetUs= */ 0); mediaCodecVideoRenderer.start();