Skip to content

Commit ecb0024

Browse files
rohitjoinscopybara-github
authored andcommitted
Improve frame rate calculation by using media duration from mdhd box
- Added logic to parse media duration from the `mdhd` box for accurate frame rate calculation. - Fallbacks to track duration from `tkhd` when `mdhd` contains invalid or missing data. - Avoids incorrect frame rate calculations in MP4 files with an edit list (`elst`) box. - Adds frame rate calculations for partially fragmented MP4 files. - Verified accuracy with tools like `mediainfo` and `ffprobe`. Issue: #1531 **Note**: The slight difference in frame rate values in dump files that aren’t MP4s with an edit list or fragmented MP4s isn’t due to differences in `tkhd` and `mdhd` duration values (which should be identical for non-edited or non-fragmented files). Rather, it’s because they are calculated using different timescales. The `mvhd` box defines a global movie timescale, which is used for the track's `tkhd` duration. Meanwhile, each track’s `mdhd` box defines its own timescale specific to its content type, which we now use for more accurate frame rate calculation. PiperOrigin-RevId: 676046744
1 parent 8799bf4 commit ecb0024

File tree

54 files changed

+98
-54
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+98
-54
lines changed

RELEASENOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
* Fix preroll sample handling for non-keyframe media start positions when
4141
processing edit lists in MP4 files
4242
([#1659](https://github.com/google/ExoPlayer/issues/1659)).
43+
* Improved frame rate calculation by using media duration from the `mdhd`
44+
box in `Mp4Extractor` and `FragmentedMp4Extractor`
45+
([#1531](https://github.com/androidx/media/issues/1531)).
4346
* DataSource:
4447
* Audio:
4548
* Video:

libraries/exoplayer_smoothstreaming/src/main/java/androidx/media3/exoplayer/smoothstreaming/DefaultSsChunkSource.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ public DefaultSsChunkSource(
203203
streamElement.timescale,
204204
C.TIME_UNSET,
205205
manifest.durationUs,
206+
/* mediaDurationUs= */ manifest.durationUs,
206207
format,
207208
Track.TRANSFORMATION_NONE,
208209
trackEncryptionBoxes,

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

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,7 @@ public static Track parseTrak(
351351
checkNotNull(mdia.getContainerBoxOfType(Mp4Box.TYPE_minf))
352352
.getContainerBoxOfType(Mp4Box.TYPE_stbl));
353353

354-
Pair<Long, String> mdhdData =
355-
parseMdhd(checkNotNull(mdia.getLeafBoxOfType(Mp4Box.TYPE_mdhd)).data);
354+
MdhdData mdhdData = parseMdhd(checkNotNull(mdia.getLeafBoxOfType(Mp4Box.TYPE_mdhd)).data);
356355
LeafBox stsd = stbl.getLeafBoxOfType(Mp4Box.TYPE_stsd);
357356
if (stsd == null) {
358357
throw ParserException.createForMalformedContainer(
@@ -363,7 +362,7 @@ public static Track parseTrak(
363362
stsd.data,
364363
tkhdData.id,
365364
tkhdData.rotationDegrees,
366-
mdhdData.second,
365+
mdhdData.language,
367366
drmInitData,
368367
isQuickTime);
369368
@Nullable long[] editListDurations = null;
@@ -383,9 +382,10 @@ public static Track parseTrak(
383382
: new Track(
384383
tkhdData.id,
385384
trackType,
386-
mdhdData.first,
385+
mdhdData.timescale,
387386
movieTimescale,
388387
durationUs,
388+
mdhdData.mediaDurationUs,
389389
stsdData.format,
390390
stsdData.requiredSampleTransformation,
391391
stsdData.trackEncryptionBoxes,
@@ -431,6 +431,12 @@ public static TrackSampleTable parseStbl(
431431
/* durationUs= */ 0);
432432
}
433433

434+
if (track.type == C.TRACK_TYPE_VIDEO && track.mediaDurationUs > 0) {
435+
float frameRate = sampleCount / (track.mediaDurationUs / 1000000f);
436+
Format format = track.format.buildUpon().setFrameRate(frameRate).build();
437+
track = track.copyWithFormat(format);
438+
}
439+
434440
// Entries are byte offsets of chunks.
435441
boolean chunkOffsetsAreLongs = false;
436442
@Nullable LeafBox chunkOffsetsAtom = stblBox.getLeafBoxOfType(Mp4Box.TYPE_stco);
@@ -927,23 +933,30 @@ private static int parseHdlr(ParsableByteArray hdlr) {
927933
* Parses an mdhd atom (defined in ISO/IEC 14496-12).
928934
*
929935
* @param mdhd The mdhd atom to decode.
930-
* @return A pair consisting of the media timescale defined as the number of time units that pass
931-
* in one second, and the language code.
936+
* @return An {@link MdhdData} object containing the parsed data.
932937
*/
933-
private static Pair<Long, String> parseMdhd(ParsableByteArray mdhd) {
938+
private static MdhdData parseMdhd(ParsableByteArray mdhd) {
934939
mdhd.setPosition(Mp4Box.HEADER_SIZE);
935940
int fullAtom = mdhd.readInt();
936941
int version = parseFullBoxVersion(fullAtom);
937942
mdhd.skipBytes(version == 0 ? 8 : 16);
938943
long timescale = mdhd.readUnsignedInt();
939-
mdhd.skipBytes(version == 0 ? 4 : 8);
944+
long mediaDuration = version == 0 ? mdhd.readUnsignedInt() : mdhd.readUnsignedLongToLong();
945+
long mediaDurationUs;
946+
if (mediaDuration == 0) {
947+
// 0 duration normally indicates that the file is fully fragmented (i.e. all of the media
948+
// samples are in fragments). Treat as unknown.
949+
mediaDurationUs = C.TIME_UNSET;
950+
} else {
951+
mediaDurationUs = Util.scaleLargeTimestamp(mediaDuration, C.MICROS_PER_SECOND, timescale);
952+
}
940953
int languageCode = mdhd.readUnsignedShort();
941954
String language =
942955
""
943956
+ (char) (((languageCode >> 10) & 0x1F) + 0x60)
944957
+ (char) (((languageCode >> 5) & 0x1F) + 0x60)
945958
+ (char) ((languageCode & 0x1F) + 0x60);
946-
return Pair.create(timescale, language);
959+
return new MdhdData(timescale, mediaDurationUs, language);
947960
}
948961

949962
/**
@@ -2408,6 +2421,19 @@ public EyesData(StriData striData) {
24082421
}
24092422
}
24102423

2424+
/** Data parsed from mdhd box. */
2425+
private static final class MdhdData {
2426+
private final long timescale;
2427+
private final long mediaDurationUs;
2428+
private final String language;
2429+
2430+
public MdhdData(long timescale, long mediaDurationUs, String language) {
2431+
this.timescale = timescale;
2432+
this.mediaDurationUs = mediaDurationUs;
2433+
this.language = language;
2434+
}
2435+
}
2436+
24112437
/** Data parsed from vexu box. */
24122438
/* package */ static final class VexuData {
24132439
@Nullable private final EyesData eyesData;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,9 @@ private void processMoovAtom(ContainerBox moov) throws ParserException {
738738
roleFlags |=
739739
firstVideoTrackIndex == C.INDEX_UNSET ? C.ROLE_FLAG_MAIN : C.ROLE_FLAG_ALTERNATE;
740740
}
741-
if (trackDurationUs > 0 && trackSampleTable.sampleCount > 0) {
741+
if (track.format.frameRate == Format.NO_VALUE
742+
&& trackDurationUs > 0
743+
&& trackSampleTable.sampleCount > 0) {
742744
float frameRate = trackSampleTable.sampleCount / (trackDurationUs / 1000000f);
743745
formatBuilder.setFrameRate(frameRate);
744746
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ public final class Track {
6464
/** The duration of the track in microseconds, or {@link C#TIME_UNSET} if unknown. */
6565
public final long durationUs;
6666

67+
/** The duration of the media in microseconds, or {@link C#TIME_UNSET} if unknown. */
68+
public final long mediaDurationUs;
69+
6770
/** The format. */
6871
public final Format format;
6972

@@ -93,6 +96,7 @@ public Track(
9396
long timescale,
9497
long movieTimescale,
9598
long durationUs,
99+
long mediaDurationUs,
96100
Format format,
97101
@Transformation int sampleTransformation,
98102
@Nullable TrackEncryptionBox[] sampleDescriptionEncryptionBoxes,
@@ -104,6 +108,7 @@ public Track(
104108
this.timescale = timescale;
105109
this.movieTimescale = movieTimescale;
106110
this.durationUs = durationUs;
111+
this.mediaDurationUs = mediaDurationUs;
107112
this.format = format;
108113
this.sampleTransformation = sampleTransformation;
109114
this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes;
@@ -133,6 +138,7 @@ public Track copyWithFormat(Format format) {
133138
timescale,
134139
movieTimescale,
135140
durationUs,
141+
mediaDurationUs,
136142
format,
137143
sampleTransformation,
138144
sampleDescriptionEncryptionBoxes,
@@ -148,6 +154,7 @@ public Track copyWithoutEditLists() {
148154
timescale,
149155
movieTimescale,
150156
durationUs,
157+
mediaDurationUs,
151158
format,
152159
sampleTransformation,
153160
sampleDescriptionEncryptionBoxes,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class FragmentedMp4ExtractorNoSniffingTest {
4545
/* timescale= */ 30_000,
4646
/* movieTimescale= */ 1000,
4747
/* durationUs= */ C.TIME_UNSET,
48+
/* mediaDurationUs= */ C.TIME_UNSET,
4849
new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).build(),
4950
/* sampleTransformation= */ Track.TRANSFORMATION_NONE,
5051
/* sampleDescriptionEncryptionBoxes= */ null,

libraries/test_data/src/test/assets/extractordumps/mp4/sample_android_slow_motion.mp4.0.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 0
1818
width = 1280
1919
height = 720
20-
frameRate = 13.31
20+
frameRate = 13.32
2121
colorInfo:
2222
colorSpace = 2
2323
colorRange = 1

libraries/test_data/src/test/assets/extractordumps/mp4/sample_android_slow_motion.mp4.1.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 0
1818
width = 1280
1919
height = 720
20-
frameRate = 13.31
20+
frameRate = 13.32
2121
colorInfo:
2222
colorSpace = 2
2323
colorRange = 1

libraries/test_data/src/test/assets/extractordumps/mp4/sample_android_slow_motion.mp4.2.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 0
1818
width = 1280
1919
height = 720
20-
frameRate = 13.31
20+
frameRate = 13.32
2121
colorInfo:
2222
colorSpace = 2
2323
colorRange = 1

libraries/test_data/src/test/assets/extractordumps/mp4/sample_android_slow_motion.mp4.3.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 0
1818
width = 1280
1919
height = 720
20-
frameRate = 13.31
20+
frameRate = 13.32
2121
colorInfo:
2222
colorSpace = 2
2323
colorRange = 1

libraries/test_data/src/test/assets/extractordumps/mp4/sample_android_slow_motion.mp4.reading_within_gop_sample_dependencies.0.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 0
1818
width = 1280
1919
height = 720
20-
frameRate = 13.31
20+
frameRate = 13.32
2121
colorInfo:
2222
colorSpace = 2
2323
colorRange = 1

libraries/test_data/src/test/assets/extractordumps/mp4/sample_android_slow_motion.mp4.reading_within_gop_sample_dependencies.1.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 0
1818
width = 1280
1919
height = 720
20-
frameRate = 13.31
20+
frameRate = 13.32
2121
colorInfo:
2222
colorSpace = 2
2323
colorRange = 1

libraries/test_data/src/test/assets/extractordumps/mp4/sample_android_slow_motion.mp4.reading_within_gop_sample_dependencies.2.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 0
1818
width = 1280
1919
height = 720
20-
frameRate = 13.31
20+
frameRate = 13.32
2121
colorInfo:
2222
colorSpace = 2
2323
colorRange = 1

libraries/test_data/src/test/assets/extractordumps/mp4/sample_android_slow_motion.mp4.reading_within_gop_sample_dependencies.3.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 0
1818
width = 1280
1919
height = 720
20-
frameRate = 13.31
20+
frameRate = 13.32
2121
colorInfo:
2222
colorSpace = 2
2323
colorRange = 1

libraries/test_data/src/test/assets/extractordumps/mp4/sample_android_slow_motion.mp4.reading_within_gop_sample_dependencies.unknown_length.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 0
1818
width = 1280
1919
height = 720
20-
frameRate = 13.31
20+
frameRate = 13.32
2121
colorInfo:
2222
colorSpace = 2
2323
colorRange = 1

libraries/test_data/src/test/assets/extractordumps/mp4/sample_android_slow_motion.mp4.unknown_length.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 0
1818
width = 1280
1919
height = 720
20-
frameRate = 13.31
20+
frameRate = 13.32
2121
colorInfo:
2222
colorSpace = 2
2323
colorRange = 1

libraries/test_data/src/test/assets/extractordumps/mp4/sample_edit_list.mp4.0.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 2
1818
width = 1920
1919
height = 1080
20-
frameRate = 32.57
20+
frameRate = 28.03
2121
rotationDegrees = 90
2222
colorInfo:
2323
colorSpace = 6

libraries/test_data/src/test/assets/extractordumps/mp4/sample_edit_list.mp4.1.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 2
1818
width = 1920
1919
height = 1080
20-
frameRate = 32.57
20+
frameRate = 28.03
2121
rotationDegrees = 90
2222
colorInfo:
2323
colorSpace = 6

libraries/test_data/src/test/assets/extractordumps/mp4/sample_edit_list.mp4.2.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 2
1818
width = 1920
1919
height = 1080
20-
frameRate = 32.57
20+
frameRate = 28.03
2121
rotationDegrees = 90
2222
colorInfo:
2323
colorSpace = 6

libraries/test_data/src/test/assets/extractordumps/mp4/sample_edit_list.mp4.3.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 2
1818
width = 1920
1919
height = 1080
20-
frameRate = 32.57
20+
frameRate = 28.03
2121
rotationDegrees = 90
2222
colorInfo:
2323
colorSpace = 6

libraries/test_data/src/test/assets/extractordumps/mp4/sample_edit_list.mp4.reading_within_gop_sample_dependencies.0.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 2
1818
width = 1920
1919
height = 1080
20-
frameRate = 32.57
20+
frameRate = 28.03
2121
rotationDegrees = 90
2222
colorInfo:
2323
colorSpace = 6

libraries/test_data/src/test/assets/extractordumps/mp4/sample_edit_list.mp4.reading_within_gop_sample_dependencies.1.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 2
1818
width = 1920
1919
height = 1080
20-
frameRate = 32.57
20+
frameRate = 28.03
2121
rotationDegrees = 90
2222
colorInfo:
2323
colorSpace = 6

libraries/test_data/src/test/assets/extractordumps/mp4/sample_edit_list.mp4.reading_within_gop_sample_dependencies.2.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 2
1818
width = 1920
1919
height = 1080
20-
frameRate = 32.57
20+
frameRate = 28.03
2121
rotationDegrees = 90
2222
colorInfo:
2323
colorSpace = 6

libraries/test_data/src/test/assets/extractordumps/mp4/sample_edit_list.mp4.reading_within_gop_sample_dependencies.3.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 2
1818
width = 1920
1919
height = 1080
20-
frameRate = 32.57
20+
frameRate = 28.03
2121
rotationDegrees = 90
2222
colorInfo:
2323
colorSpace = 6

libraries/test_data/src/test/assets/extractordumps/mp4/sample_edit_list.mp4.reading_within_gop_sample_dependencies.unknown_length.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 2
1818
width = 1920
1919
height = 1080
20-
frameRate = 32.57
20+
frameRate = 28.03
2121
rotationDegrees = 90
2222
colorInfo:
2323
colorSpace = 6

libraries/test_data/src/test/assets/extractordumps/mp4/sample_edit_list.mp4.unknown_length.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 2
1818
width = 1920
1919
height = 1080
20-
frameRate = 32.57
20+
frameRate = 28.03
2121
rotationDegrees = 90
2222
colorInfo:
2323
colorSpace = 6

libraries/test_data/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.0.dump

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ track 0:
1313
maxNumReorderSamples = 2
1414
width = 1080
1515
height = 720
16+
frameRate = 29.97
1617
colorInfo:
1718
lumaBitdepth = 8
1819
chromaBitdepth = 8

libraries/test_data/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.reading_within_gop_sample_dependencies.0.dump

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ track 0:
1313
maxNumReorderSamples = 2
1414
width = 1080
1515
height = 720
16+
frameRate = 29.97
1617
colorInfo:
1718
lumaBitdepth = 8
1819
chromaBitdepth = 8

libraries/test_data/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.reading_within_gop_sample_dependencies.unknown_length.dump

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ track 0:
1313
maxNumReorderSamples = 2
1414
width = 1080
1515
height = 720
16+
frameRate = 29.97
1617
colorInfo:
1718
lumaBitdepth = 8
1819
chromaBitdepth = 8

libraries/test_data/src/test/assets/extractordumps/mp4/sample_partially_fragmented.mp4.unknown_length.dump

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ track 0:
1313
maxNumReorderSamples = 2
1414
width = 1080
1515
height = 720
16+
frameRate = 29.97
1617
colorInfo:
1718
lumaBitdepth = 8
1819
chromaBitdepth = 8

libraries/test_data/src/test/assets/extractordumps/mp4/sample_with_color_info.mp4.0.dump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ track 0:
1717
maxNumReorderSamples = 2
1818
width = 1920
1919
height = 1080
20-
frameRate = 30.17
20+
frameRate = 30.00
2121
rotationDegrees = 90
2222
colorInfo:
2323
colorSpace = 6

0 commit comments

Comments
 (0)