diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f2305f89ec..d8d143e447 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -12,6 +12,11 @@ * Allow constant power upmixing/downmixing in DefaultAudioMixer. * Add support for float PCM to `ChannelMappingAudioProcessor`. * Video: + * Add experimental `ExoPlayer` API to include the + `MediaCodec.BUFFER_FLAG_DECODE_ONLY` flag when queuing decode-only input + buffers. This flag will signal the decoder to skip the decode-only + buffers thereby resulting in faster seeking. Enable it with + `DefaultRenderersFactory.experimentalSetEnableMediaCodecBufferDecodeOnlyFlag`. * Text: * Metadata: * Image: diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java index af3bc5d0b4..37ceab5772 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java @@ -24,10 +24,12 @@ import android.os.Handler; import android.os.Looper; import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.media3.common.C; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Log; import androidx.media3.common.util.UnstableApi; +import androidx.media3.common.util.Util; import androidx.media3.exoplayer.audio.AudioRendererEventListener; import androidx.media3.exoplayer.audio.AudioSink; import androidx.media3.exoplayer.audio.DefaultAudioSink; @@ -111,6 +113,7 @@ public class DefaultRenderersFactory implements RenderersFactory { private boolean enableMediaCodecVideoRendererPrewarming; private boolean parseAv1SampleDependencies; private long lateThresholdToDropDecoderInputUs; + private boolean enableMediaCodecBufferDecodeOnlyFlag; /** * @param context A {@link Context}. @@ -298,6 +301,26 @@ public class DefaultRenderersFactory implements RenderersFactory { return this; } + /** + * Sets whether the {@link MediaCodec#BUFFER_FLAG_DECODE_ONLY} flag will be included when queuing + * decode-only input buffers to the decoder. + * + *

If {@code false}, then only if the decoder is set up in tunneling mode will the decode-only + * input buffers be queued with the {@link MediaCodec#BUFFER_FLAG_DECODE_ONLY} flag. The default + * value is {@code false}. + * + *

Requires API 34. + * + *

This method is experimental and will be renamed or removed in a future release. + */ + @RequiresApi(34) + @CanIgnoreReturnValue + public DefaultRenderersFactory experimentalSetEnableMediaCodecBufferDecodeOnlyFlag( + boolean enableMediaCodecBufferDecodeOnlyFlag) { + this.enableMediaCodecBufferDecodeOnlyFlag = enableMediaCodecBufferDecodeOnlyFlag; + return this; + } + /** * Sets the maximum duration for which video renderers can attempt to seamlessly join an ongoing * playback. @@ -406,7 +429,7 @@ public class DefaultRenderersFactory implements RenderersFactory { VideoRendererEventListener eventListener, long allowedVideoJoiningTimeMs, ArrayList out) { - MediaCodecVideoRenderer videoRenderer = + MediaCodecVideoRenderer.Builder videoRendererBuilder = new MediaCodecVideoRenderer.Builder(context) .setCodecAdapterFactory(getCodecAdapterFactory()) .setMediaCodecSelector(mediaCodecSelector) @@ -416,9 +439,13 @@ public class DefaultRenderersFactory implements RenderersFactory { .setEventListener(eventListener) .setMaxDroppedFramesToNotify(MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY) .experimentalSetParseAv1SampleDependencies(parseAv1SampleDependencies) - .experimentalSetLateThresholdToDropDecoderInputUs(lateThresholdToDropDecoderInputUs) - .build(); - out.add(videoRenderer); + .experimentalSetLateThresholdToDropDecoderInputUs(lateThresholdToDropDecoderInputUs); + if (Util.SDK_INT >= 34) { + videoRendererBuilder = + videoRendererBuilder.experimentalSetEnableMediaCodecBufferDecodeOnlyFlag( + enableMediaCodecBufferDecodeOnlyFlag); + } + out.add(videoRendererBuilder.build()); if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { return; @@ -851,17 +878,23 @@ public class DefaultRenderersFactory implements RenderersFactory { long allowedVideoJoiningTimeMs) { if (enableMediaCodecVideoRendererPrewarming && renderer.getClass() == MediaCodecVideoRenderer.class) { - return new MediaCodecVideoRenderer.Builder(context) - .setCodecAdapterFactory(getCodecAdapterFactory()) - .setMediaCodecSelector(mediaCodecSelector) - .setAllowedJoiningTimeMs(allowedVideoJoiningTimeMs) - .setEnableDecoderFallback(enableDecoderFallback) - .setEventHandler(eventHandler) - .setEventListener(eventListener) - .setMaxDroppedFramesToNotify(MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY) - .experimentalSetParseAv1SampleDependencies(parseAv1SampleDependencies) - .experimentalSetLateThresholdToDropDecoderInputUs(lateThresholdToDropDecoderInputUs) - .build(); + MediaCodecVideoRenderer.Builder builder = + new MediaCodecVideoRenderer.Builder(context) + .setCodecAdapterFactory(getCodecAdapterFactory()) + .setMediaCodecSelector(mediaCodecSelector) + .setAllowedJoiningTimeMs(allowedVideoJoiningTimeMs) + .setEnableDecoderFallback(enableDecoderFallback) + .setEventHandler(eventHandler) + .setEventListener(eventListener) + .setMaxDroppedFramesToNotify(MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY) + .experimentalSetParseAv1SampleDependencies(parseAv1SampleDependencies) + .experimentalSetLateThresholdToDropDecoderInputUs(lateThresholdToDropDecoderInputUs); + if (Util.SDK_INT >= 34) { + builder = + builder.experimentalSetEnableMediaCodecBufferDecodeOnlyFlag( + enableMediaCodecBufferDecodeOnlyFlag); + } + return builder.build(); } return null; } 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 d5b11ccd18..0f742cd4a5 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 @@ -183,6 +183,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer private final long minEarlyUsToDropDecoderInput; private final PriorityQueue droppedDecoderInputBufferTimestamps; + private final boolean enableMediaCodecBufferDecodeOnlyFlag; private @MonotonicNonNull CodecMaxValues codecMaxValues; private boolean codecNeedsSetOutputSurfaceWorkaround; @@ -233,6 +234,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer @Nullable private VideoSink videoSink; private boolean parseAv1SampleDependencies; private long lateThresholdToDropDecoderInputUs; + private boolean enableMediaCodecBufferDecodeOnlyFlag; /** * Creates a new builder. @@ -373,6 +375,26 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer return this; } + /** + * Sets whether the {@link MediaCodec#BUFFER_FLAG_DECODE_ONLY} flag will be included when + * queuing decode-only input buffers to the decoder. + * + *

If {@code false}, then only if the decoder is set up in tunneling mode will decode-only + * input buffers be queued with the {@link MediaCodec#BUFFER_FLAG_DECODE_ONLY} flag. The default + * value is {@code false}. + * + *

Requires API 34. + * + *

This method is experimental and will be renamed or removed in a future release. + */ + @RequiresApi(34) + @CanIgnoreReturnValue + public Builder experimentalSetEnableMediaCodecBufferDecodeOnlyFlag( + boolean enableMediaCodecBufferDecodeOnlyFlag) { + this.enableMediaCodecBufferDecodeOnlyFlag = enableMediaCodecBufferDecodeOnlyFlag; + return this; + } + /** * Builds the {@link MediaCodecVideoRenderer}. Must only be called once per Builder instance. * @@ -597,6 +619,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer builder.lateThresholdToDropDecoderInputUs != C.TIME_UNSET ? -builder.lateThresholdToDropDecoderInputUs : C.TIME_UNSET; + enableMediaCodecBufferDecodeOnlyFlag = builder.enableMediaCodecBufferDecodeOnlyFlag; } // FrameTimingEvaluator methods @@ -1479,11 +1502,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer @Override protected int getCodecBufferFlags(DecoderInputBuffer buffer) { - if (Util.SDK_INT >= 34 && tunneling && isBufferBeforeStartTime(buffer)) { + if (Util.SDK_INT >= 34 + && (enableMediaCodecBufferDecodeOnlyFlag || tunneling) + && isBufferBeforeStartTime(buffer)) { // The buffer likely needs to be dropped because its timestamp is less than the start time. - // We can't decide to do this after decoding because we won't get the buffer back from the - // codec in tunneling mode. This may not work perfectly, e.g. when the codec is doing frame - // rate conversion, but it's still better than not dropping the buffers at all. + // If tunneling, we can't decide to do this after decoding because we won't get the buffer + // back from the codec in tunneling mode. This may not work perfectly, e.g. when the codec is + // doing frame rate conversion, but it's still better than not dropping the buffers at all. return MediaCodec.BUFFER_FLAG_DECODE_ONLY; } return 0;