Skip to content

Commit 2fe92bf

Browse files
ychaparovcopybara-github
authored andcommitted
Use the AV1 sample dependency parser in MCVR
Parsing AV1 bitstreams allows us to identify frames that are not used as reference, and improve seeking or frame dropping behavior. The AV1 bitstream format is relatively quick to parse PiperOrigin-RevId: 723462680
1 parent bcce7b5 commit 2fe92bf

File tree

7 files changed

+386
-9
lines changed

7 files changed

+386
-9
lines changed

RELEASENOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
protected so that applications can override to block usage of
2020
placeholder surfaces
2121
([#1905](https://github.com/androidx/media/pull/1905)).
22+
* Add experimental `ExoPlayer` AV1 sample dependency parsing to speed up
23+
seeking. Enable it with the new
24+
`DefaultRenderersFactory.experimentalSetParseAv1SampleDependencies` API.
2225
* Text:
2326
* Metadata:
2427
* Image:

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import androidx.annotation.IntDef;
2626
import androidx.annotation.Nullable;
2727
import androidx.media3.common.C;
28+
import androidx.media3.common.MimeTypes;
2829
import androidx.media3.common.util.Log;
2930
import androidx.media3.common.util.UnstableApi;
3031
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
@@ -108,6 +109,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
108109
private boolean enableFloatOutput;
109110
private boolean enableAudioTrackPlaybackParams;
110111
private boolean enableMediaCodecVideoRendererPrewarming;
112+
private boolean parseAv1SampleDependencies;
111113

112114
/**
113115
* @param context A {@link Context}.
@@ -276,6 +278,24 @@ public final DefaultRenderersFactory experimentalSetEnableMediaCodecVideoRendere
276278
return this;
277279
}
278280

281+
/**
282+
* Sets whether {@link MimeTypes#VIDEO_AV1} bitstream parsing for sample dependency information is
283+
* enabled. Knowing which input frames are not depended on can speed up seeking and reduce dropped
284+
* frames.
285+
*
286+
*

Defaults to {@code false}.

287+
*
288+
*

This method is experimental and will be renamed or removed in a future release.

289+
*
290+
* @param parseAv1SampleDependencies Whether bitstream parsing is enabled.
291+
*/
292+
@CanIgnoreReturnValue
293+
public final DefaultRenderersFactory experimentalSetParseAv1SampleDependencies(
294+
boolean parseAv1SampleDependencies) {
295+
this.parseAv1SampleDependencies = parseAv1SampleDependencies;
296+
return this;
297+
}
298+
279299
/**
280300
* Sets the maximum duration for which video renderers can attempt to seamlessly join an ongoing
281301
* playback.
@@ -375,6 +395,7 @@ protected void buildVideoRenderers(
375395
.setEventHandler(eventHandler)
376396
.setEventListener(eventListener)
377397
.setMaxDroppedFramesToNotify(MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)
398+
.experimentalSetParseAv1SampleDependencies(parseAv1SampleDependencies)
378399
.build();
379400
out.add(videoRenderer);
380401

@@ -778,6 +799,7 @@ protected Renderer buildSecondaryVideoRenderer(
778799
.setEventHandler(eventHandler)
779800
.setEventListener(eventListener)
780801
.setMaxDroppedFramesToNotify(MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)
802+
.experimentalSetParseAv1SampleDependencies(parseAv1SampleDependencies)
781803
.build();
782804
}
783805
return null;

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
157157
private final boolean deviceNeedsNoPostProcessWorkaround;
158158
private final VideoFrameReleaseControl videoFrameReleaseControl;
159159
private final VideoFrameReleaseControl.FrameReleaseInfo videoFrameReleaseInfo;
160+
@Nullable private final Av1SampleDependencyParser av1SampleDependencyParser;
160161

161162
private @MonotonicNonNull CodecMaxValues codecMaxValues;
162163
private boolean codecNeedsSetOutputSurfaceWorkaround;
@@ -202,6 +203,7 @@ public static final class Builder {
202203
private int maxDroppedFramesToNotify;
203204
private float assumedMinimumCodecOperatingRate;
204205
@Nullable private VideoSink videoSink;
206+
private boolean parseAv1SampleDependencies;
205207

206208
/**
207209
* Creates a new builder.
@@ -310,6 +312,21 @@ public Builder setVideoSink(@Nullable VideoSink videoSink) {
310312
return this;
311313
}
312314

315+
/**
316+
* Sets whether {@link MimeTypes#VIDEO_AV1} bitstream parsing for sample dependency information
317+
* is enabled. Knowing which input frames are not depended on can speed up seeking and reduce
318+
* dropped frames.
319+
*
320+
*

Defaults to {@code false}.

321+
*
322+
*

This method is experimental and will be renamed or removed in a future release.

323+
*/
324+
@CanIgnoreReturnValue
325+
public Builder experimentalSetParseAv1SampleDependencies(boolean parseAv1SampleDependencies) {
326+
this.parseAv1SampleDependencies = parseAv1SampleDependencies;
327+
return this;
328+
}
329+
313330
/**
314331
* Builds the {@link MediaCodecVideoRenderer}. Must only be called once per Builder instance.
315332
*
@@ -527,6 +544,8 @@ protected MediaCodecVideoRenderer(Builder builder) {
527544
rendererPriority = C.PRIORITY_PLAYBACK;
528545
startPositionUs = C.TIME_UNSET;
529546
periodDurationUs = C.TIME_UNSET;
547+
av1SampleDependencyParser =
548+
builder.parseAv1SampleDependencies ? new Av1SampleDependencyParser() : null;
530549
}
531550

532551
// FrameTimingEvaluator methods
@@ -1397,10 +1416,6 @@ protected int getCodecBufferFlags(DecoderInputBuffer buffer) {
13971416

13981417
@Override
13991418
protected boolean shouldSkipDecoderInputBuffer(DecoderInputBuffer buffer) {
1400-
if (!buffer.notDependedOn()) {
1401-
// Buffer is depended on. Do not skip.
1402-
return false;
1403-
}
14041419
if (isBufferProbablyLastSample(buffer)) {
14051420
// Make sure to decode and render the last frame.
14061421
return false;
@@ -1411,7 +1426,23 @@ protected boolean shouldSkipDecoderInputBuffer(DecoderInputBuffer buffer) {
14111426
return false;
14121427
}
14131428
// Skip buffers without sample dependencies that won't be rendered.
1414-
return isBufferBeforeStartTime(buffer);
1429+
if (!isBufferBeforeStartTime(buffer)) {
1430+
return false;
1431+
}
1432+
if (buffer.notDependedOn()) {
1433+
return true;
1434+
}
1435+
if (av1SampleDependencyParser != null
1436+
&& checkNotNull(getCodecInfo()).mimeType.equals(MimeTypes.VIDEO_AV1)
1437+
&& buffer.data != null) {
1438+
ByteBuffer readOnlySample = buffer.data.asReadOnlyBuffer();
1439+
readOnlySample.flip();
1440+
int sampleLimitAfterSkippingNonReferenceFrames =
1441+
av1SampleDependencyParser.sampleLimitAfterSkippingNonReferenceFrame(readOnlySample);
1442+
// TODO: b/391108133 - support skipping parts of AV1 input buffers.
1443+
return sampleLimitAfterSkippingNonReferenceFrames == readOnlySample.position();
1444+
}
1445+
return false;
14151446
}
14161447

14171448
private boolean isBufferProbablyLastSample(DecoderInputBuffer buffer) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (C) 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package androidx.media3.exoplayer.e2etest;
17+
18+
import android.content.Context;
19+
import android.graphics.SurfaceTexture;
20+
import android.view.Surface;
21+
import androidx.media3.common.MediaItem;
22+
import androidx.media3.common.MediaItem.ClippingConfiguration;
23+
import androidx.media3.common.Player;
24+
import androidx.media3.exoplayer.ExoPlayer;
25+
import androidx.media3.test.utils.CapturingRenderersFactory;
26+
import androidx.media3.test.utils.DumpFileAsserts;
27+
import androidx.media3.test.utils.FakeClock;
28+
import androidx.media3.test.utils.robolectric.PlaybackOutput;
29+
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
30+
import androidx.media3.test.utils.robolectric.TestPlayerRunHelper;
31+
import androidx.test.core.app.ApplicationProvider;
32+
import androidx.test.ext.junit.runners.AndroidJUnit4;
33+
import org.junit.Rule;
34+
import org.junit.Test;
35+
import org.junit.runner.RunWith;
36+
37+
/** End-to-end playback tests using AV1 sample skipping. */
38+
@RunWith(AndroidJUnit4.class)
39+
public class ParseAv1SampleDependenciesPlaybackTest {
40+
41+
private static final String TEST_MP4_URI = "asset:///media/mp4/sample_with_av1c.mp4";
42+
43+
@Rule
44+
public ShadowMediaCodecConfig mediaCodecConfig =
45+
ShadowMediaCodecConfig.forAllSupportedMimeTypes();
46+
47+
@Test
48+
public void playback_withClippedMediaItem_skipNonReferenceInputSamples() throws Exception {
49+
Context applicationContext = ApplicationProvider.getApplicationContext();
50+
CapturingRenderersFactory renderersFactory = new CapturingRenderersFactory(applicationContext);
51+
renderersFactory.experimentalSetParseAv1SampleDependencies(true);
52+
ExoPlayer player =
53+
new ExoPlayer.Builder(applicationContext, renderersFactory)
54+
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
55+
.build();
56+
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
57+
player.setVideoSurface(surface);
58+
PlaybackOutput playbackOutput = PlaybackOutput.register(player, renderersFactory);
59+
player.setMediaItem(
60+
new MediaItem.Builder()
61+
.setUri(TEST_MP4_URI)
62+
.setClippingConfiguration(
63+
new ClippingConfiguration.Builder().setStartPositionMs(200).build())
64+
.build());
65+
66+
player.prepare();
67+
player.play();
68+
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
69+
player.release();
70+
surface.release();
71+
72+
DumpFileAsserts.assertOutput(
73+
applicationContext,
74+
playbackOutput,
75+
/* dumpFile= */ "playbackdumps/av1SampleDependencies/clippedMediaItem.dump");
76+
}
77+
}

0 commit comments

Comments
 (0)