Skip to content

Commit 2bf62ec

Browse files
rohitjoinscopybara-github
authored andcommitted
Add support for extracting ITU-T T.35 (it35) timed metadata tracks
Update the MP4 parsing pipeline in `BoxParser` to natively recognize and expose ITU-T T.35 timed metadata tracks. The parsed tracks are assigned the `application/x-itut-t35` MIME type, allowing them to be safely exposed as `C.TRACK_TYPE_METADATA` standalone tracks. This extraction logic mirrors the `it35` muxing support and adheres to the accepted T.35 metadata sample entry spec. PiperOrigin-RevId: 917318574
1 parent 2f6dbcf commit 2bf62ec

12 files changed

Lines changed: 1255 additions & 4 deletions

File tree

RELEASENOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
* Fix an issue where `ExportResult.fileSizeBytes` may be over-reported.
5757
* Track selection:
5858
* Extractors:
59+
* MP4: Add support for extracting ITU-T T.35 (`it35`) timed metadata
60+
tracks.
5961
* MP4: Add support for extracting chapter metadata (both Nero and
6062
QuickTime formats). QuickTime chapters are preferred if both are
6163
present. The extracted chapter information is exposed as `Chapter`

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,9 @@ public abstract class Mp4Box {
461461
@SuppressWarnings("ConstantCaseForConstants")
462462
public static final int TYPE_mett = 0x6d657474;
463463

464+
@SuppressWarnings({"ConstantCaseForConstants", "IdentifierName"})
465+
public static final int TYPE_it35 = 0x69743335;
466+
464467
@SuppressWarnings("ConstantCaseForConstants")
465468
public static final int TYPE_alac = 0x616c6163;
466469

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1343,7 +1343,7 @@ private static StsdData parseStsd(
13431343
|| childAtomType == TYPE_text) {
13441344
parseTextSampleEntry(
13451345
stsd, childAtomType, childStartPosition, childAtomSize, tkhdData, language, out);
1346-
} else if (childAtomType == Mp4Box.TYPE_mett) {
1346+
} else if (childAtomType == Mp4Box.TYPE_mett || childAtomType == Mp4Box.TYPE_it35) {
13471347
parseMetaDataSampleEntry(stsd, childAtomType, childStartPosition, tkhdData.id, out);
13481348
} else if (childAtomType == Mp4Box.TYPE_camm) {
13491349
out.format =
@@ -1958,6 +1958,16 @@ private static void parseMetaDataSampleEntry(
19581958
if (mimeType != null) {
19591959
out.format = new Format.Builder().setId(trackId).setSampleMimeType(mimeType).build();
19601960
}
1961+
} else if (atomType == Mp4Box.TYPE_it35) {
1962+
int identifierLength = parent.readUnsignedByte();
1963+
byte[] identifier = new byte[identifierLength];
1964+
parent.readBytes(identifier, 0, identifierLength);
1965+
out.format =
1966+
new Format.Builder()
1967+
.setId(trackId)
1968+
.setSampleMimeType(MimeTypes.APPLICATION_ITUT_T35)
1969+
.setInitializationData(ImmutableList.of(identifier))
1970+
.build();
19611971
}
19621972
}
19631973

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,11 @@ public void mp4SampleWithNeroChaptersOnly() throws Exception {
348348
assertExtractorBehavior("media/mp4/sample_with_nero_chapters_only.mp4", /* peekLimit= */ 2152);
349349
}
350350

351+
@Test
352+
public void mp4SampleWithIt35Track() throws Exception {
353+
assertExtractorBehavior("media/mp4/sample_with_it35_track.mp4", /* peekLimit= */ 1386);
354+
}
355+
351356
private void assertExtractorBehavior(String file, int peekLimit) throws IOException {
352357
ExtractorAsserts.AssertionConfig.Builder assertionConfigBuilder =
353358
new ExtractorAsserts.AssertionConfig.Builder();

libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1094,7 +1094,6 @@ public void writeMp4File_withT35MetadataTrack_matchesExpected() throws Exception
10941094
FakeExtractorOutput fakeExtractorOutput =
10951095
TestUtil.extractAllSamplesFromFilePath(
10961096
new Mp4Extractor(new DefaultSubtitleParserFactory()), checkNotNull(outputFilePath));
1097-
// Dump file should have T.35 metadata track once extractor starts extracting.
10981097
DumpFileAsserts.assertOutput(
10991098
context,
11001099
fakeExtractorOutput,
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
seekMap:
2+
isSeekable = true
3+
duration = 1003000
4+
getPosition(0) = [[timeUs=0, position=48]]
5+
getPosition(1) = [[timeUs=0, position=48]]
6+
getPosition(501500) = [[timeUs=0, position=48]]
7+
getPosition(1003000) = [[timeUs=0, position=48]]
8+
numberOfTracks = 2
9+
track 0:
10+
total output bytes = 43683
11+
sample count = 30
12+
track duration = 1003000
13+
format 0:
14+
averageBitrate = 348493
15+
peakBitrate = 348493
16+
id = 1
17+
containerMimeType = video/mp4
18+
sampleMimeType = video/av01
19+
codecs = av01.0.00M.10
20+
maxInputSize = 18085
21+
width = 480
22+
height = 270
23+
frameRate = 29.92
24+
colorInfo:
25+
colorSpace = 6
26+
colorRange = 2
27+
colorTransfer = 6
28+
lumaBitdepth = 10
29+
chromaBitdepth = 10
30+
language = en
31+
metadata = entries=[TSSE: description=null: values=[Lavf62.12.100], Mp4Timestamp: creation time=0, modification time=0, timescale=1000, ThumbnailMetadata: presentationTimeUs=0]
32+
initializationData:
33+
data = length 20, hash 27B68A02
34+
sample 0:
35+
time = 0
36+
flags = 1
37+
data = length 18055, hash 4A4CEBD8
38+
sample 1:
39+
time = 33426
40+
flags = 0
41+
data = length 10390, hash 1A97B887
42+
sample 2:
43+
time = 66852
44+
flags = 0
45+
data = length 3, hash D5E0
46+
sample 3:
47+
time = 100278
48+
flags = 0
49+
data = length 111, hash F52A3D2C
50+
sample 4:
51+
time = 133704
52+
flags = 0
53+
data = length 3, hash D5D0
54+
sample 5:
55+
time = 167130
56+
flags = 0
57+
data = length 748, hash 7FA30B17
58+
sample 6:
59+
time = 200557
60+
flags = 0
61+
data = length 3, hash D610
62+
sample 7:
63+
time = 233983
64+
flags = 0
65+
data = length 94, hash D426B3A3
66+
sample 8:
67+
time = 267409
68+
flags = 0
69+
data = length 3, hash D5C0
70+
sample 9:
71+
time = 300835
72+
flags = 0
73+
data = length 1487, hash 1B89F2D3
74+
sample 10:
75+
time = 334261
76+
flags = 0
77+
data = length 3, hash D5D0
78+
sample 11:
79+
time = 367688
80+
flags = 0
81+
data = length 243, hash 83AC8EF9
82+
sample 12:
83+
time = 401114
84+
flags = 0
85+
data = length 3, hash D600
86+
sample 13:
87+
time = 434540
88+
flags = 0
89+
data = length 719, hash 3F9A2D2F
90+
sample 14:
91+
time = 467966
92+
flags = 0
93+
data = length 3, hash D5E0
94+
sample 15:
95+
time = 501392
96+
flags = 0
97+
data = length 398, hash 29898A8D
98+
sample 16:
99+
time = 534818
100+
flags = 0
101+
data = length 200, hash 18678644
102+
sample 17:
103+
time = 568245
104+
flags = 0
105+
data = length 3, hash D5B0
106+
sample 18:
107+
time = 601671
108+
flags = 0
109+
data = length 8443, hash 90C5F9E
110+
sample 19:
111+
time = 635097
112+
flags = 0
113+
data = length 3, hash D5C0
114+
sample 20:
115+
time = 668523
116+
flags = 0
117+
data = length 299, hash 3219B04
118+
sample 21:
119+
time = 701949
120+
flags = 0
121+
data = length 222, hash FBB32D8
122+
sample 22:
123+
time = 735376
124+
flags = 0
125+
data = length 3, hash D600
126+
sample 23:
127+
time = 768802
128+
flags = 0
129+
data = length 645, hash 3FF6F2DF
130+
sample 24:
131+
time = 802228
132+
flags = 0
133+
data = length 3, hash D5E0
134+
sample 25:
135+
time = 835654
136+
flags = 0
137+
data = length 251, hash 83CE17A6
138+
sample 26:
139+
time = 869080
140+
flags = 0
141+
data = length 253, hash 34714BEA
142+
sample 27:
143+
time = 902506
144+
flags = 0
145+
data = length 3, hash D610
146+
sample 28:
147+
time = 935933
148+
flags = 0
149+
data = length 482, hash 4D8E647E
150+
sample 29:
151+
time = 969359
152+
flags = 536870912
153+
data = length 607, hash 230AC585
154+
track 1:
155+
total output bytes = 7350
156+
sample count = 30
157+
track duration = 1003000
158+
format 0:
159+
averageBitrate = 58637
160+
id = 2
161+
containerMimeType = video/mp4
162+
sampleMimeType = application/x-itut-t35
163+
maxInputSize = 275
164+
metadata = entries=[TSSE: description=null: values=[Lavf62.12.100], Mp4Timestamp: creation time=0, modification time=0, timescale=1000]
165+
initializationData:
166+
data = length 5, hash FD925165
167+
sample 0:
168+
time = 0
169+
flags = 1
170+
data = length 245, hash 9BFA0B
171+
sample 1:
172+
time = 33426
173+
flags = 1
174+
data = length 245, hash EA2065CD
175+
sample 2:
176+
time = 66852
177+
flags = 1
178+
data = length 245, hash F3A0DA5C
179+
sample 3:
180+
time = 100278
181+
flags = 1
182+
data = length 245, hash CF1BDEB2
183+
sample 4:
184+
time = 133704
185+
flags = 1
186+
data = length 245, hash BA53650F
187+
sample 5:
188+
time = 167130
189+
flags = 1
190+
data = length 245, hash 88941B39
191+
sample 6:
192+
time = 200557
193+
flags = 1
194+
data = length 245, hash 302A69B6
195+
sample 7:
196+
time = 233983
197+
flags = 1
198+
data = length 245, hash 86A9B7CF
199+
sample 8:
200+
time = 267409
201+
flags = 1
202+
data = length 245, hash 6C8AF8DF
203+
sample 9:
204+
time = 300835
205+
flags = 1
206+
data = length 245, hash A8D264F
207+
sample 10:
208+
time = 334261
209+
flags = 1
210+
data = length 245, hash BF90E97B
211+
sample 11:
212+
time = 367688
213+
flags = 1
214+
data = length 245, hash 3A036FF7
215+
sample 12:
216+
time = 401114
217+
flags = 1
218+
data = length 245, hash 4C76613D
219+
sample 13:
220+
time = 434540
221+
flags = 1
222+
data = length 245, hash 9C103E0D
223+
sample 14:
224+
time = 467966
225+
flags = 1
226+
data = length 245, hash 546611FD
227+
sample 15:
228+
time = 501392
229+
flags = 1
230+
data = length 245, hash 2EEAD77C
231+
sample 16:
232+
time = 534818
233+
flags = 1
234+
data = length 245, hash 45EE18FD
235+
sample 17:
236+
time = 568245
237+
flags = 1
238+
data = length 245, hash 6C4A5B60
239+
sample 18:
240+
time = 601671
241+
flags = 1
242+
data = length 245, hash C55E7F4F
243+
sample 19:
244+
time = 635097
245+
flags = 1
246+
data = length 245, hash 8C43E61B
247+
sample 20:
248+
time = 668523
249+
flags = 1
250+
data = length 245, hash 88CB156F
251+
sample 21:
252+
time = 701949
253+
flags = 1
254+
data = length 245, hash 975E8975
255+
sample 22:
256+
time = 735376
257+
flags = 1
258+
data = length 245, hash D7DAF392
259+
sample 23:
260+
time = 768802
261+
flags = 1
262+
data = length 245, hash 72F8BC8A
263+
sample 24:
264+
time = 802228
265+
flags = 1
266+
data = length 245, hash 69F05BD3
267+
sample 25:
268+
time = 835654
269+
flags = 1
270+
data = length 245, hash E12DFF0E
271+
sample 26:
272+
time = 869080
273+
flags = 1
274+
data = length 245, hash 5DD18D72
275+
sample 27:
276+
time = 902506
277+
flags = 1
278+
data = length 245, hash 2B6AB923
279+
sample 28:
280+
time = 935933
281+
flags = 1
282+
data = length 245, hash 68205A3F
283+
sample 29:
284+
time = 969359
285+
flags = 536870913
286+
data = length 245, hash 7D03F36A
287+
tracksEnded = true

0 commit comments

Comments
 (0)