Skip to content

Commit 227a4d7

Browse files
ychaparovcopybara-github
authored andcommitted
AV1 Frame Header parsing
PiperOrigin-RevId: 719253811
1 parent 1772050 commit 227a4d7

File tree

2 files changed

+134
-3
lines changed

2 files changed

+134
-3
lines changed

libraries/container/src/main/java/androidx/media3/container/ObuParser.java

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package androidx.media3.container;
1717

1818
import static androidx.media3.common.util.Assertions.checkArgument;
19+
import static java.lang.Math.min;
1920

2021
import androidx.annotation.Nullable;
2122
import androidx.media3.common.util.ParsableBitArray;
@@ -136,9 +137,10 @@ public static final class SequenceHeader {
136137
public final int orderHintBits;
137138

138139
/**
139-
* Returns a {@link SequenceHeader} parsed from the input {@link #OBU_SEQUENCE_HEADER}.
140+
* Returns a {@link SequenceHeader} parsed from the input OBU, or {@code null} if the AV1
141+
* bitstream is not yet supported.
140142
*
141-
*

Returns {@code null} if the AV1 bitstream is not yet supported.

143+
* @param obu The input OBU with type {@link #OBU_SEQUENCE_HEADER}.
142144
*/
143145
@Nullable
144146
public static SequenceHeader parse(Obu obu) {
@@ -153,7 +155,7 @@ public static SequenceHeader parse(Obu obu) {
153155
private SequenceHeader(Obu obu) throws NotYetImplementedException {
154156
checkArgument(obu.type == OBU_SEQUENCE_HEADER);
155157
byte[] data = new byte[obu.payload.remaining()];
156-
// Do not modify obu.payload as we read.
158+
// Do not modify obu.payload while reading it.
157159
obu.payload.asReadOnlyBuffer().get(data);
158160
ParsableBitArray obuData = new ParsableBitArray(data);
159161
obuData.skipBits(4); // seq_profile and still_picture
@@ -252,6 +254,94 @@ private static void skipUvlc(ParsableBitArray parsableBitArray) {
252254
}
253255
}
254256

257+
/** An AV1 Frame Header. */
258+
public static final class FrameHeader {
259+
private static final int PROBE_BYTES = 4;
260+
261+
private static final int FRAME_TYPE_KEY_FRAME = 0;
262+
private static final int FRAME_TYPE_INTRA_ONLY_FRAME = 2;
263+
private static final int FRAME_TYPE_SWITCH_FRAME = 3;
264+
265+
private final boolean isDependedOn;
266+
267+
/** Returns whether the frame header is depended on by subsequent frames. */
268+
public boolean isDependedOn() {
269+
return isDependedOn;
270+
}
271+
272+
/**
273+
* Returns a {@link FrameHeader} parsed from the input OBU, or {@code null} if the AV1 bitstream
274+
* is not yet supported.
275+
*
276+
* @param sequenceHeader The most recent sequence header before the frame header.
277+
* @param obu The input OBU with type {@link #OBU_FRAME} or {@link #OBU_FRAME_HEADER}.
278+
*/
279+
@Nullable
280+
public static FrameHeader parse(SequenceHeader sequenceHeader, Obu obu) {
281+
try {
282+
return new FrameHeader(sequenceHeader, obu);
283+
} catch (NotYetImplementedException ignored) {
284+
return null;
285+
}
286+
}
287+
288+
private FrameHeader(SequenceHeader sequenceHeader, Obu obu) throws NotYetImplementedException {
289+
checkArgument(obu.type == OBU_FRAME || obu.type == OBU_FRAME_HEADER);
290+
byte[] bytes = new byte[min(PROBE_BYTES, obu.payload.remaining())];
291+
// Do not modify obu.payload while reading it.
292+
obu.payload.asReadOnlyBuffer().get(bytes);
293+
ParsableBitArray obuData = new ParsableBitArray(bytes);
294+
throwWhenFeatureRequired(sequenceHeader.reducedStillPictureHeader);
295+
boolean showExistingFrame = obuData.readBit();
296+
if (showExistingFrame) {
297+
// TODO: b/391108133 - Treat showExistingFrame as depended on. The picture was already
298+
// decoded and the player may not save a lot of resources by rendering. Check if this
299+
// assumption is correct!
300+
isDependedOn = true;
301+
return;
302+
}
303+
int frameType = obuData.readBits(2);
304+
boolean showFrame = obuData.readBit();
305+
throwWhenFeatureRequired(sequenceHeader.decoderModelInfoPresentFlag);
306+
if (!showFrame) {
307+
// show_frame equal to 0 specifies that this frame should not be immediately output.
308+
// If a frame is output later, then it is depended on.
309+
isDependedOn = true;
310+
return;
311+
}
312+
boolean errorResilientMode;
313+
if (frameType == FRAME_TYPE_SWITCH_FRAME || (frameType == FRAME_TYPE_KEY_FRAME)) {
314+
errorResilientMode = true;
315+
} else {
316+
errorResilientMode = obuData.readBit();
317+
}
318+
obuData.skipBit(); // disable_cdf_update
319+
throwWhenFeatureRequired(!sequenceHeader.seqForceScreenContentTools);
320+
boolean allowScreenContentTools = obuData.readBit();
321+
if (allowScreenContentTools) {
322+
throwWhenFeatureRequired(!sequenceHeader.seqForceIntegerMv);
323+
obuData.skipBit(); // force_integer_mv
324+
}
325+
throwWhenFeatureRequired(sequenceHeader.frameIdNumbersPresentFlag);
326+
if (frameType != FRAME_TYPE_SWITCH_FRAME) {
327+
obuData.skipBit(); // frame_size_override_flag
328+
}
329+
obuData.skipBits(sequenceHeader.orderHintBits); // order_hint
330+
if (frameType != FRAME_TYPE_INTRA_ONLY_FRAME
331+
&& frameType != FRAME_TYPE_KEY_FRAME
332+
&& !errorResilientMode) {
333+
obuData.skipBits(3); // primary_ref_frame
334+
}
335+
int refreshFrameFlags;
336+
if (frameType == FRAME_TYPE_SWITCH_FRAME || (frameType == FRAME_TYPE_KEY_FRAME)) {
337+
refreshFrameFlags = (1 << 8) - 1;
338+
} else {
339+
refreshFrameFlags = obuData.readBits(8);
340+
}
341+
isDependedOn = refreshFrameFlags != 0;
342+
}
343+
}
344+
255345
/** Full AV1 bitstream parsing is not yet implemented. */
256346
private static void throwWhenFeatureRequired(boolean expression)
257347
throws NotYetImplementedException {

libraries/container/src/test/java/androidx/media3/container/ObuParserTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public class ObuParserTest {
4444
private static final ByteBuffer DELIMITER_AND_HEADER_AND_PADDING_WITH_EXTENSION_AND_MISSING_SIZE =
4545
ByteBuffer.wrap(createByteArray(0x16, 0x00, 0x00, 0x1A, 0x01, 0xC8, 0x78, 0xFF, 0xFF, 0xFF));
4646

47+
private static final ByteBuffer NON_REFERENCE_FRAME =
48+
ByteBuffer.wrap(
49+
createByteArray(
50+
0x32, 0x1A, 0x30, 0xC0, 0x00, 0x1D, 0x66, 0x68, 0x46, 0xC9, 0x38, 0x00, 0x60, 0x10,
51+
0x20, 0x80, 0x20, 0x00, 0x00, 0x01, 0x8B, 0x7A, 0x87, 0xF9, 0xAA, 0x2D, 0x0F, 0x2C));
52+
4753
@Test
4854
public void split_sequenceHeaderAndFrame_parsesCorrectTypesAndSizes() {
4955
List<ObuParser.Obu> obuList = ObuParser.split(SEQUENCE_HEADER_AND_FRAME);
@@ -82,4 +88,39 @@ public void sequenceHeader_parses() {
8288
assertThat(sequenceHeader.seqForceIntegerMv).isTrue();
8389
assertThat(sequenceHeader.orderHintBits).isEqualTo(7);
8490
}
91+
92+
@Test
93+
public void parseFrameHeader_fromFrame_returnsIsDependedOn() {
94+
List<ObuParser.Obu> obuList = ObuParser.split(SEQUENCE_HEADER_AND_FRAME);
95+
ObuParser.Obu sequenceHeaderObu = obuList.get(0);
96+
ObuParser.SequenceHeader sequenceHeader = ObuParser.SequenceHeader.parse(sequenceHeaderObu);
97+
ObuParser.Obu frameObu = obuList.get(1);
98+
99+
ObuParser.FrameHeader frameHeader = ObuParser.FrameHeader.parse(sequenceHeader, frameObu);
100+
101+
assertThat(frameHeader.isDependedOn()).isTrue();
102+
}
103+
104+
@Test
105+
public void parseFrameHeader_fromFrameHeader_returnsIsDependedOn() {
106+
ObuParser.Obu sequenceHeaderObu = ObuParser.split(SEQUENCE_HEADER_AND_FRAME).get(0);
107+
ObuParser.SequenceHeader sequenceHeader = ObuParser.SequenceHeader.parse(sequenceHeaderObu);
108+
ObuParser.Obu frameHeaderObu =
109+
ObuParser.split(DELIMITER_AND_HEADER_AND_PADDING_WITH_EXTENSION_AND_MISSING_SIZE).get(1);
110+
111+
ObuParser.FrameHeader frameHeader = ObuParser.FrameHeader.parse(sequenceHeader, frameHeaderObu);
112+
113+
assertThat(frameHeader.isDependedOn()).isTrue();
114+
}
115+
116+
@Test
117+
public void parseFrameHeader_fromNonReferenceFrame_returnsNotDependedOn() {
118+
ObuParser.Obu sequenceHeaderObu = ObuParser.split(SEQUENCE_HEADER_AND_FRAME).get(0);
119+
ObuParser.SequenceHeader sequenceHeader = ObuParser.SequenceHeader.parse(sequenceHeaderObu);
120+
ObuParser.Obu frameObu = ObuParser.split(NON_REFERENCE_FRAME).get(0);
121+
122+
ObuParser.FrameHeader frameHeader = ObuParser.FrameHeader.parse(sequenceHeader, frameObu);
123+
124+
assertThat(frameHeader.isDependedOn()).isFalse();
125+
}
85126
}

0 commit comments

Comments
 (0)