diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java index a8c9cb298e..5556e27e25 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java @@ -82,6 +82,7 @@ import java.util.List; public static final int TYPE_moof = Util.getIntegerCodeForString("moof"); public static final int TYPE_traf = Util.getIntegerCodeForString("traf"); public static final int TYPE_mvex = Util.getIntegerCodeForString("mvex"); + public static final int TYPE_mehd = Util.getIntegerCodeForString("mehd"); public static final int TYPE_tkhd = Util.getIntegerCodeForString("tkhd"); public static final int TYPE_edts = Util.getIntegerCodeForString("edts"); public static final int TYPE_elst = Util.getIntegerCodeForString("elst"); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java index 2fa8106eb3..68efddd96d 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java @@ -50,11 +50,13 @@ import java.util.List; * * @param trak Atom to parse. * @param mvhd Movie header atom, used to get the timescale. + * @param duration The duration in units of the timescale declared in the mvhd atom, or -1 if the + * duration should be parsed from the tkhd atom. * @param drmInitData {@link DrmInitData} to be included in the format. * @param isQuickTime True for QuickTime media. False otherwise. * @return A {@link Track} instance, or {@code null} if the track's type isn't supported. */ - public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, + public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long duration, DrmInitData drmInitData, boolean isQuickTime) { Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data); @@ -63,7 +65,9 @@ import java.util.List; } TkhdData tkhdData = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data); - long duration = tkhdData.duration; + if (duration == -1) { + duration = tkhdData.duration; + } long movieTimescale = parseMvhd(mvhd.data); long durationUs; if (duration == -1) { @@ -490,6 +494,11 @@ import java.util.List; duration = -1; } else { duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong(); + if (duration == 0) { + // 0 duration normally indicates that the file is fully fragmented (i.e. all of the media + // samples are in fragments). Treat as unknown. + duration = -1; + } } tkhd.skipBytes(16); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java index b71363a33b..55bbec5c60 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java @@ -166,6 +166,10 @@ public final class FragmentedMp4Extractor implements Extractor { @Override public void seek() { + int trackCount = trackBundles.size(); + for (int i = 0; i < trackCount; i++) { + trackBundles.valueAt(i).reset(); + } containerAtoms.clear(); enterReadingAtomHeaderState(); } @@ -340,12 +344,15 @@ public final class FragmentedMp4Extractor implements Extractor { // Read declaration of track fragments in the Moov box. ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex); SparseArray defaultSampleValuesArray = new SparseArray<>(); + long duration = -1; int mvexChildrenSize = mvex.leafChildren.size(); for (int i = 0; i < mvexChildrenSize; i++) { Atom.LeafAtom atom = mvex.leafChildren.get(i); if (atom.type == Atom.TYPE_trex) { Pair trexData = parseTrex(atom.data); defaultSampleValuesArray.put(trexData.first, trexData.second); + } else if (atom.type == Atom.TYPE_mehd) { + duration = parseMehd(atom.data); } } @@ -355,7 +362,7 @@ public final class FragmentedMp4Extractor implements Extractor { for (int i = 0; i < moovContainerChildrenSize; i++) { Atom.ContainerAtom atom = moov.containerChildren.get(i); if (atom.type == Atom.TYPE_trak) { - Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), + Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), duration, drmInitData, false); if (track != null) { tracks.put(track.id, track); @@ -402,6 +409,16 @@ public final class FragmentedMp4Extractor implements Extractor { defaultSampleDuration, defaultSampleSize, defaultSampleFlags)); } + /** + * Parses an mehd atom (defined in 14496-12). + */ + private static long parseMehd(ParsableByteArray mehd) { + mehd.setPosition(Atom.HEADER_SIZE); + int fullAtom = mehd.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + return version == 0 ? mehd.readUnsignedInt() : mehd.readUnsignedLongToLong(); + } + private static void parseMoof(ContainerAtom moof, SparseArray trackBundleArray, int flags, byte[] extendedTypeScratch) throws ParserException { int moofContainerChildrenSize = moof.containerChildren.size(); @@ -427,15 +444,13 @@ public final class FragmentedMp4Extractor implements Extractor { if (trackBundle == null) { return; } + TrackFragment fragment = trackBundle.fragment; - trackBundle.currentSampleIndex = 0; - fragment.reset(); + long decodeTime = fragment.nextFragmentDecodeTime; + trackBundle.reset(); LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt); - long decodeTime; - if (tfdtAtom == null || (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) != 0) { - decodeTime = 0; - } else { + if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) { decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data); } @@ -661,6 +676,7 @@ public final class FragmentedMp4Extractor implements Extractor { && (!workaroundEveryVideoFrameIsSyncFrame || i == 0); cumulativeTime += sampleDuration; } + fragment.nextFragmentDecodeTime = cumulativeTime; } private static void parseUuid(ParsableByteArray uuid, TrackFragment out, @@ -961,7 +977,7 @@ public final class FragmentedMp4Extractor implements Extractor { || atom == Atom.TYPE_tfhd || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_trex || atom == Atom.TYPE_trun || atom == Atom.TYPE_pssh || atom == Atom.TYPE_saiz || atom == Atom.TYPE_saio || atom == Atom.TYPE_senc || atom == Atom.TYPE_uuid - || atom == Atom.TYPE_elst; + || atom == Atom.TYPE_elst || atom == Atom.TYPE_mehd; } /** Returns whether the extractor should parse a container atom with type {@code atom}. */ @@ -992,6 +1008,10 @@ public final class FragmentedMp4Extractor implements Extractor { this.track = Assertions.checkNotNull(track); this.defaultSampleValues = Assertions.checkNotNull(defaultSampleValues); output.format(track.format); + reset(); + } + + public void reset() { fragment.reset(); currentSampleIndex = 0; } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java index 34b6c61cfb..03c2f18efb 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java @@ -310,7 +310,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { continue; } - Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), null, + Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), -1, null, isQuickTime); if (track == null) { continue; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java index c128495707..b8e203315a 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java @@ -80,15 +80,20 @@ import java.io.IOException; * Whether {@link #sampleEncryptionData} needs populating with the actual encryption data. */ public boolean sampleEncryptionDataNeedsFill; + /** + * The absolute decode time of the start of the next fragment. + */ + public long nextFragmentDecodeTime; /** * Resets the fragment. *

- * The {@link #length} is set to 0, and both {@link #definesEncryptionData} and - * {@link #sampleEncryptionDataNeedsFill} is set to false. + * {@link #length} and {@link #nextFragmentDecodeTime} are set to 0, and both + * {@link #definesEncryptionData} and {@link #sampleEncryptionDataNeedsFill} is set to false. */ public void reset() { length = 0; + nextFragmentDecodeTime = 0; definesEncryptionData = false; sampleEncryptionDataNeedsFill = false; }