diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java index 4f9d6b5ef4..0890ec10ab 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java @@ -52,7 +52,6 @@ import androidx.media3.common.Effect; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.media3.common.PlaybackException; -import androidx.media3.common.Timeline; import androidx.media3.common.VideoSize; import androidx.media3.common.util.Log; import androidx.media3.common.util.MediaFormatUtil; @@ -135,16 +134,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer private static final int HEVC_MAX_INPUT_SIZE_THRESHOLD = 2 * 1024 * 1024; /** The earliest time threshold, in microseconds, after which a frame is considered late. */ - private static final long MIN_EARLY_US_LATE_THRESHOLD = -30_000L; + private static final long MIN_EARLY_US_LATE_THRESHOLD = -30_000; /** The earliest time threshold, in microseconds, after which a frame is considered very late. */ - private static final long MIN_EARLY_US_VERY_LATE_THRESHOLD = -500_000L; - - /** - * The offset from the {@link Timeline.Period} end duration in microseconds, after which input - * buffers will be treated as if they are last. - */ - private static final long OFFSET_FROM_PERIOD_END_TO_TREAT_AS_LAST_US = 100_000L; + private static final long MIN_EARLY_US_VERY_LATE_THRESHOLD = -500_000; private static boolean evaluatedDeviceNeedsSetOutputSurfaceWorkaround; private static boolean deviceNeedsSetOutputSurfaceWorkaround; @@ -186,8 +179,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer /* package */ @Nullable OnFrameRenderedListenerV23 tunnelingOnFrameRenderedListener; @Nullable private VideoFrameMetadataListener frameMetadataListener; private long startPositionUs; - @Nullable private MediaSource.MediaPeriodId mediaPeriodId; - private long periodDurationUs; private boolean videoSinkNeedsRegisterInputStream; /** @@ -427,7 +418,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer reportedVideoSize = null; rendererPriority = C.PRIORITY_PLAYBACK; startPositionUs = C.TIME_UNSET; - periodDurationUs = C.TIME_UNSET; } // FrameTimingEvaluator methods @@ -742,20 +732,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer if (this.startPositionUs == C.TIME_UNSET) { this.startPositionUs = startPositionUs; } - this.mediaPeriodId = mediaPeriodId; - updatePeriodDurationUs(); - } - - private void updatePeriodDurationUs() { - Timeline timeline = getTimeline(); - if (timeline.isEmpty() || mediaPeriodId == null) { - periodDurationUs = C.TIME_UNSET; - return; - } - periodDurationUs = - timeline - .getPeriodByUid(checkNotNull(mediaPeriodId).periodUid, new Timeline.Period()) - .getDurationUs(); } @Override @@ -837,8 +813,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer @Override protected void onDisabled() { reportedVideoSize = null; - mediaPeriodId = null; - periodDurationUs = C.TIME_UNSET; if (videoSink != null) { videoSink.onRendererDisabled(); } else { @@ -874,12 +848,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer } } - @Override - protected void onTimelineChanged(Timeline timeline) { - super.onTimelineChanged(timeline); - updatePeriodDurationUs(); - } - @Override public void handleMessage(@MessageType int messageType, @Nullable Object message) throws ExoPlaybackException { @@ -1264,12 +1232,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer @Override protected boolean shouldSkipDecoderInputBuffer(DecoderInputBuffer buffer) { - if (!buffer.notDependedOn()) { - // Buffer is depended on. Do not skip. - return false; - } - if (isBufferProbablyLastSample(buffer)) { - // Make sure to decode and render the last frame. + // TODO: b/351164714 - Do not apply this optimization for buffers with timestamp near + // the media duration. + if (hasReadStreamToEnd() || buffer.isLastSample()) { + // Last buffer is always decoded. return false; } if (buffer.isEncrypted()) { @@ -1278,21 +1244,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer return false; } // Skip buffers without sample dependencies that won't be rendered. - return isBufferBeforeStartTime(buffer); - } - - private boolean isBufferProbablyLastSample(DecoderInputBuffer buffer) { - if (hasReadStreamToEnd() || buffer.isLastSample()) { - return true; - } - // TODO: b/352276461 - improve buffer.isLastSample() logic. - // This is a temporary workaround: do not skip buffers close to the period end. - if (periodDurationUs == C.TIME_UNSET) { - // Duration unknown: probably last sample. - return true; - } - long presentationTimeUs = buffer.timeUs - getOutputStreamOffsetUs(); - return periodDurationUs - presentationTimeUs <= OFFSET_FROM_PERIOD_END_TO_TREAT_AS_LAST_US; + return isBufferBeforeStartTime(buffer) && buffer.notDependedOn(); } private boolean isBufferBeforeStartTime(DecoderInputBuffer buffer) { diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java index 11dd79dc68..12d8fee2ab 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java @@ -20,7 +20,6 @@ import static androidx.media3.common.util.Util.msToUs; import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.format; import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample; -import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -77,7 +76,6 @@ import androidx.media3.exoplayer.upstream.Allocator; import androidx.media3.exoplayer.upstream.DefaultAllocator; import androidx.media3.test.utils.FakeMediaPeriod; import androidx.media3.test.utils.FakeSampleStream; -import androidx.media3.test.utils.FakeTimeline; import androidx.media3.test.utils.robolectric.RobolectricUtil; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -391,7 +389,6 @@ public class MediaCodecVideoRendererTest { /* maxDroppedFramesToNotify= */ 1); mediaCodecVideoRenderer.init(/* index= */ 0, PlayerId.UNSET, Clock.DEFAULT); mediaCodecVideoRenderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, surface); - FakeTimeline fakeTimeline = new FakeTimeline(); mediaCodecVideoRenderer.enable( RendererConfiguration.DEFAULT, new Format[] {VIDEO_H264}, @@ -401,13 +398,12 @@ public class MediaCodecVideoRendererTest { /* mayRenderStartOfStream= */ true, /* startPositionUs= */ 30_000, /* offsetUs= */ 0, - new MediaSource.MediaPeriodId(fakeTimeline.getUidOfPeriod(0))); - mediaCodecVideoRenderer.setTimeline(fakeTimeline); + new MediaSource.MediaPeriodId(new Object())); mediaCodecVideoRenderer.start(); mediaCodecVideoRenderer.setCurrentStreamFinal(); mediaCodecVideoRenderer.render(0, SystemClock.elapsedRealtime() * 1000); - // Call to render has read all samples including the END_OF_STREAM_ITEM because the + // Call to render has reads all samples including the END_OF_STREAM_ITEM because the // previous sample is skipped before decoding. assertThat(mediaCodecVideoRenderer.hasReadStreamToEnd()).isTrue(); int posUs = 30_000; @@ -427,76 +423,6 @@ public class MediaCodecVideoRendererTest { assertThat(argumentDecoderCounters.getValue().renderedOutputBufferCount).isEqualTo(1); } - @Test - public void render_withoutSampleDependenciesAndShortDuration_skipsNoDecoderInputBuffers() - throws Exception { - ArgumentCaptor argumentDecoderCounters = - ArgumentCaptor.forClass(DecoderCounters.class); - FakeTimeline fakeTimeline = - new FakeTimeline( - new FakeTimeline.TimelineWindowDefinition( - /* isSeekable= */ true, /* isDynamic= */ false, /* durationUs= */ 30_000)); - FakeSampleStream fakeSampleStream = - new FakeSampleStream( - new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), - /* mediaSourceEventDispatcher= */ null, - DrmSessionManager.DRM_UNSUPPORTED, - new DrmSessionEventListener.EventDispatcher(), - /* initialFormat= */ VIDEO_H264, - ImmutableList.of( - oneByteSample( - /* timeUs= */ DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, - C.BUFFER_FLAG_KEY_FRAME), - oneByteSample( - /* timeUs= */ DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + 10_000, - C.BUFFER_FLAG_NOT_DEPENDED_ON), - oneByteSample( - /* timeUs= */ DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + 20_000, - C.BUFFER_FLAG_NOT_DEPENDED_ON), - END_OF_STREAM_ITEM)); - fakeSampleStream.writeData(/* startPositionUs= */ 0); - // Seek to time after samples. - fakeSampleStream.seekToUs(30_000, /* allowTimeBeyondBuffer= */ true); - mediaCodecVideoRenderer = - new MediaCodecVideoRenderer( - ApplicationProvider.getApplicationContext(), - new ForwardingSynchronousMediaCodecAdapterWithBufferLimit.Factory(/* bufferLimit= */ 5), - mediaCodecSelector, - /* allowedJoiningTimeMs= */ 0, - /* enableDecoderFallback= */ false, - /* eventHandler= */ new Handler(testMainLooper), - /* eventListener= */ eventListener, - /* maxDroppedFramesToNotify= */ 1); - mediaCodecVideoRenderer.init(/* index= */ 0, PlayerId.UNSET, Clock.DEFAULT); - mediaCodecVideoRenderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, surface); - mediaCodecVideoRenderer.enable( - RendererConfiguration.DEFAULT, - new Format[] {VIDEO_H264}, - fakeSampleStream, - /* positionUs= */ 0, - /* joining= */ false, - /* mayRenderStartOfStream= */ true, - /* startPositionUs= */ DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + 30_000, - /* offsetUs= */ 0, - new MediaSource.MediaPeriodId(fakeTimeline.getUidOfPeriod(0))); - mediaCodecVideoRenderer.setTimeline(fakeTimeline); - - mediaCodecVideoRenderer.start(); - mediaCodecVideoRenderer.setCurrentStreamFinal(); - mediaCodecVideoRenderer.render(0, SystemClock.elapsedRealtime() * 1000); - // Call to render has read all samples including the END_OF_STREAM_ITEM. - assertThat(mediaCodecVideoRenderer.hasReadStreamToEnd()).isTrue(); - int posUs = 30_000; - while (!mediaCodecVideoRenderer.isEnded()) { - mediaCodecVideoRenderer.render(posUs, SystemClock.elapsedRealtime() * 1000); - posUs += 40_000; - } - shadowOf(testMainLooper).idle(); - - verify(eventListener).onVideoEnabled(argumentDecoderCounters.capture()); - assertThat(argumentDecoderCounters.getValue().skippedInputBufferCount).isEqualTo(0); - } - @Test public void render_withClippingMediaPeriodAndBufferContainingLastAndClippingSamples_rendersLastFrame() @@ -1897,7 +1823,7 @@ public class MediaCodecVideoRendererTest { @Override public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) { int outputIndex = super.dequeueOutputBufferIndex(bufferInfo); - if (outputIndex >= 0) { + if (outputIndex > 0) { bufferCounter++; } return outputIndex; diff --git a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/e2etest/DashPlaybackTest.java b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/e2etest/DashPlaybackTest.java index f34d6aa896..2de304b61c 100644 --- a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/e2etest/DashPlaybackTest.java +++ b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/e2etest/DashPlaybackTest.java @@ -16,7 +16,6 @@ package androidx.media3.exoplayer.dash.e2etest; import static androidx.media3.common.util.Assertions.checkNotNull; -import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.graphics.SurfaceTexture; @@ -25,7 +24,6 @@ import androidx.media3.common.MediaItem; import androidx.media3.common.Player; import androidx.media3.datasource.DataSource; import androidx.media3.datasource.DefaultDataSource; -import androidx.media3.exoplayer.DecoderCounters; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.Renderer; import androidx.media3.exoplayer.RenderersFactory; @@ -404,40 +402,4 @@ public final class DashPlaybackTest { DumpFileAsserts.assertOutput( applicationContext, playbackOutput, "playbackdumps/dash/optimized_seek.dump"); } - - @Test - public void playVideo_usingWithinGopSampleDependencies_withSeekAfterEoS() throws Exception { - Context applicationContext = ApplicationProvider.getApplicationContext(); - CapturingRenderersFactory capturingRenderersFactory = - new CapturingRenderersFactory(applicationContext); - BundledChunkExtractor.Factory chunkExtractorFactory = - new BundledChunkExtractor.Factory().experimentalParseWithinGopSampleDependencies(true); - DataSource.Factory defaultDataSourceFactory = new DefaultDataSource.Factory(applicationContext); - DashMediaSource.Factory dashMediaSourceFactory = - new DashMediaSource.Factory( - /* chunkSourceFactory= */ new DefaultDashChunkSource.Factory( - chunkExtractorFactory, defaultDataSourceFactory, /* maxSegmentsPerLoad= */ 1), - /* manifestDataSourceFactory= */ defaultDataSourceFactory); - ExoPlayer player = - new ExoPlayer.Builder(applicationContext, capturingRenderersFactory) - .setMediaSourceFactory(dashMediaSourceFactory) - .setClock(new FakeClock(/* isAutoAdvancing= */ true)) - .build(); - Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1)); - player.setVideoSurface(surface); - - player.setMediaItem(MediaItem.fromUri("asset:///media/dash/standalone-webvtt/sample.mpd")); - player.seekTo(50_000L); - player.prepare(); - player.play(); - TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); - player.release(); - surface.release(); - - DecoderCounters decoderCounters = checkNotNull(player.getVideoDecoderCounters()); - assertThat(decoderCounters.skippedInputBufferCount).isEqualTo(13); - assertThat(decoderCounters.queuedInputBufferCount).isEqualTo(17); - // TODO: b/352276461 - The last frame might not be rendered. When the bug is fixed, - // assert on the full playback dump. - } }