Skip to content

Commit e8fdab3

Browse files
icbakerandrewlewis
authored andcommitted
Allow missing full_range_flag in colr box with type=nclx
Test file produced with: $ MP4Box -add "sample.mp4#video:colr=nclc,1,1,1" -new sample_18byte_nclx_colr.mp4 And then manually changing the `nclc` bytes to `nclx`. This produces an 18-byte `colr` box with type `nclx`. The bitstream of this file does not contain HDR content, so the file itself is invalid for playback with a real decoder, but adding the box is enough to test the extractor change in this commit. (aside: MP4Box will let you pass `nclx`, but it requires 4 parameters, i.e. it requires the full_range_flag to be set, resulting in a valid 19-byte colr box) #minor-release Issue: #9332 PiperOrigin-RevId: 405842520
1 parent 031f26b commit e8fdab3

File tree

8 files changed

+759
-3
lines changed

8 files changed

+759
-3
lines changed

libraries/extractor/src/main/java/androidx/media3/extractor/mp4/AtomParsers.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,14 +1198,19 @@ private static void parseVideoSampleEntry(
11981198
}
11991199
} else if (childAtomType == Atom.TYPE_colr) {
12001200
int colorType = parent.readInt();
1201-
boolean isNclx = colorType == TYPE_nclx;
1202-
if (isNclx || colorType == TYPE_nclc) {
1201+
if (colorType == TYPE_nclx || colorType == TYPE_nclc) {
12031202
// For more info on syntax, see Section 8.5.2.2 in ISO/IEC 14496-12:2012(E) and
12041203
// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html.
12051204
int colorPrimaries = parent.readUnsignedShort();
12061205
int transferCharacteristics = parent.readUnsignedShort();
12071206
parent.skipBytes(2); // matrix_coefficients.
1208-
boolean fullRangeFlag = isNclx && (parent.readUnsignedByte() & 0b10000000) != 0;
1207+
1208+
// Only try and read full_range_flag if the box is long enough. It should be present in
1209+
// all colr boxes with type=nclx (Section 8.5.2.2 in ISO/IEC 14496-12:2012(E)) but some
1210+
// device cameras record videos with type=nclx without this final flag (and therefore
1211+
// size=18): https://github.com/google/ExoPlayer/issues/9332
1212+
boolean fullRangeFlag =
1213+
childAtomSize == 19 && (parent.readUnsignedByte() & 0b10000000) != 0;
12091214
colorInfo =
12101215
new ColorInfo(
12111216
ColorInfo.isoColorPrimariesToColorSpace(colorPrimaries),

libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,17 @@ public void mp4SampleWithColorInfo() throws Exception {
103103
Mp4Extractor::new, "media/mp4/sample_with_color_info.mp4", simulationConfig);
104104
}
105105

106+
/**
107+
* Test case for https://github.com/google/ExoPlayer/issues/9332. The file contains a colr box
108+
* with size=18 and type=nclx. This is not valid according to the spec (size must be 19), but
109+
* files like this exist in the wild.
110+
*/
111+
@Test
112+
public void mp4Sample18ByteNclxColr() throws Exception {
113+
ExtractorAsserts.assertBehavior(
114+
Mp4Extractor::new, "media/mp4/sample_18byte_nclx_colr.mp4", simulationConfig);
115+
}
116+
106117
@Test
107118
public void mp4SampleWithDolbyTrueHDTrack() throws Exception {
108119
ExtractorAsserts.assertBehavior(
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
seekMap:
2+
isSeekable = true
3+
duration = 1001000
4+
getPosition(0) = [[timeUs=0, position=1160]]
5+
getPosition(1) = [[timeUs=0, position=1160]]
6+
getPosition(500500) = [[timeUs=0, position=1160]]
7+
getPosition(1001000) = [[timeUs=0, position=1160]]
8+
numberOfTracks = 1
9+
track 0:
10+
total output bytes = 89876
11+
sample count = 30
12+
format 0:
13+
id = 1
14+
sampleMimeType = video/avc
15+
codecs = avc1.64001F
16+
maxInputSize = 36722
17+
width = 1080
18+
height = 720
19+
frameRate = 29.970028
20+
colorInfo:
21+
colorSpace = 1
22+
colorRange = 2
23+
colorTransfer = 3
24+
hdrStaticInfo = length 0, hash 0
25+
initializationData:
26+
data = length 29, hash 4746B5D9
27+
data = length 10, hash 7A0D0F2B
28+
sample 0:
29+
time = 0
30+
flags = 1
31+
data = length 36692, hash D216076E
32+
sample 1:
33+
time = 66733
34+
flags = 0
35+
data = length 5312, hash D45D3CA0
36+
sample 2:
37+
time = 33366
38+
flags = 0
39+
data = length 599, hash 1BE7812D
40+
sample 3:
41+
time = 200200
42+
flags = 0
43+
data = length 7735, hash 4490F110
44+
sample 4:
45+
time = 133466
46+
flags = 0
47+
data = length 987, hash 560B5036
48+
sample 5:
49+
time = 100100
50+
flags = 0
51+
data = length 673, hash ED7CD8C7
52+
sample 6:
53+
time = 166833
54+
flags = 0
55+
data = length 523, hash 3020DF50
56+
sample 7:
57+
time = 333666
58+
flags = 0
59+
data = length 6061, hash 736C72B2
60+
sample 8:
61+
time = 266933
62+
flags = 0
63+
data = length 992, hash FE132F23
64+
sample 9:
65+
time = 233566
66+
flags = 0
67+
data = length 623, hash 5B2C1816
68+
sample 10:
69+
time = 300300
70+
flags = 0
71+
data = length 421, hash 742E69C1
72+
sample 11:
73+
time = 433766
74+
flags = 0
75+
data = length 4899, hash F72F86A1
76+
sample 12:
77+
time = 400400
78+
flags = 0
79+
data = length 568, hash 519A8E50
80+
sample 13:
81+
time = 367033
82+
flags = 0
83+
data = length 620, hash 3990AA39
84+
sample 14:
85+
time = 567233
86+
flags = 0
87+
data = length 5450, hash F06EC4AA
88+
sample 15:
89+
time = 500500
90+
flags = 0
91+
data = length 1051, hash 92DFA63A
92+
sample 16:
93+
time = 467133
94+
flags = 0
95+
data = length 874, hash 69587FB4
96+
sample 17:
97+
time = 533866
98+
flags = 0
99+
data = length 781, hash 36BE495B
100+
sample 18:
101+
time = 700700
102+
flags = 0
103+
data = length 4725, hash AC0C8CD3
104+
sample 19:
105+
time = 633966
106+
flags = 0
107+
data = length 1022, hash 5D8BFF34
108+
sample 20:
109+
time = 600600
110+
flags = 0
111+
data = length 790, hash 99413A99
112+
sample 21:
113+
time = 667333
114+
flags = 0
115+
data = length 610, hash 5E129290
116+
sample 22:
117+
time = 834166
118+
flags = 0
119+
data = length 2751, hash 769974CB
120+
sample 23:
121+
time = 767433
122+
flags = 0
123+
data = length 745, hash B78A477A
124+
sample 24:
125+
time = 734066
126+
flags = 0
127+
data = length 621, hash CF741E7A
128+
sample 25:
129+
time = 800800
130+
flags = 0
131+
data = length 505, hash 1DB4894E
132+
sample 26:
133+
time = 967633
134+
flags = 0
135+
data = length 1268, hash C15348DC
136+
sample 27:
137+
time = 900900
138+
flags = 0
139+
data = length 880, hash C2DE85D0
140+
sample 28:
141+
time = 867533
142+
flags = 0
143+
data = length 530, hash C98BC6A8
144+
sample 29:
145+
time = 934266
146+
flags = 536870912
147+
data = length 568, hash 4FE5C8EA
148+
tracksEnded = true
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
seekMap:
2+
isSeekable = true
3+
duration = 1001000
4+
getPosition(0) = [[timeUs=0, position=1160]]
5+
getPosition(1) = [[timeUs=0, position=1160]]
6+
getPosition(500500) = [[timeUs=0, position=1160]]
7+
getPosition(1001000) = [[timeUs=0, position=1160]]
8+
numberOfTracks = 1
9+
track 0:
10+
total output bytes = 89876
11+
sample count = 30
12+
format 0:
13+
id = 1
14+
sampleMimeType = video/avc
15+
codecs = avc1.64001F
16+
maxInputSize = 36722
17+
width = 1080
18+
height = 720
19+
frameRate = 29.970028
20+
colorInfo:
21+
colorSpace = 1
22+
colorRange = 2
23+
colorTransfer = 3
24+
hdrStaticInfo = length 0, hash 0
25+
initializationData:
26+
data = length 29, hash 4746B5D9
27+
data = length 10, hash 7A0D0F2B
28+
sample 0:
29+
time = 0
30+
flags = 1
31+
data = length 36692, hash D216076E
32+
sample 1:
33+
time = 66733
34+
flags = 0
35+
data = length 5312, hash D45D3CA0
36+
sample 2:
37+
time = 33366
38+
flags = 0
39+
data = length 599, hash 1BE7812D
40+
sample 3:
41+
time = 200200
42+
flags = 0
43+
data = length 7735, hash 4490F110
44+
sample 4:
45+
time = 133466
46+
flags = 0
47+
data = length 987, hash 560B5036
48+
sample 5:
49+
time = 100100
50+
flags = 0
51+
data = length 673, hash ED7CD8C7
52+
sample 6:
53+
time = 166833
54+
flags = 0
55+
data = length 523, hash 3020DF50
56+
sample 7:
57+
time = 333666
58+
flags = 0
59+
data = length 6061, hash 736C72B2
60+
sample 8:
61+
time = 266933
62+
flags = 0
63+
data = length 992, hash FE132F23
64+
sample 9:
65+
time = 233566
66+
flags = 0
67+
data = length 623, hash 5B2C1816
68+
sample 10:
69+
time = 300300
70+
flags = 0
71+
data = length 421, hash 742E69C1
72+
sample 11:
73+
time = 433766
74+
flags = 0
75+
data = length 4899, hash F72F86A1
76+
sample 12:
77+
time = 400400
78+
flags = 0
79+
data = length 568, hash 519A8E50
80+
sample 13:
81+
time = 367033
82+
flags = 0
83+
data = length 620, hash 3990AA39
84+
sample 14:
85+
time = 567233
86+
flags = 0
87+
data = length 5450, hash F06EC4AA
88+
sample 15:
89+
time = 500500
90+
flags = 0
91+
data = length 1051, hash 92DFA63A
92+
sample 16:
93+
time = 467133
94+
flags = 0
95+
data = length 874, hash 69587FB4
96+
sample 17:
97+
time = 533866
98+
flags = 0
99+
data = length 781, hash 36BE495B
100+
sample 18:
101+
time = 700700
102+
flags = 0
103+
data = length 4725, hash AC0C8CD3
104+
sample 19:
105+
time = 633966
106+
flags = 0
107+
data = length 1022, hash 5D8BFF34
108+
sample 20:
109+
time = 600600
110+
flags = 0
111+
data = length 790, hash 99413A99
112+
sample 21:
113+
time = 667333
114+
flags = 0
115+
data = length 610, hash 5E129290
116+
sample 22:
117+
time = 834166
118+
flags = 0
119+
data = length 2751, hash 769974CB
120+
sample 23:
121+
time = 767433
122+
flags = 0
123+
data = length 745, hash B78A477A
124+
sample 24:
125+
time = 734066
126+
flags = 0
127+
data = length 621, hash CF741E7A
128+
sample 25:
129+
time = 800800
130+
flags = 0
131+
data = length 505, hash 1DB4894E
132+
sample 26:
133+
time = 967633
134+
flags = 0
135+
data = length 1268, hash C15348DC
136+
sample 27:
137+
time = 900900
138+
flags = 0
139+
data = length 880, hash C2DE85D0
140+
sample 28:
141+
time = 867533
142+
flags = 0
143+
data = length 530, hash C98BC6A8
144+
sample 29:
145+
time = 934266
146+
flags = 536870912
147+
data = length 568, hash 4FE5C8EA
148+
tracksEnded = true

0 commit comments

Comments
 (0)