Commit bfee449e by olly Committed by Oliver Woodman

Fix FMP4 playback duration and absent tfdt handling.

- Parse duration from mehd box for FMP4.
- Handle absent tfdt boxes by accumulating decode time
  from one fragment to the next.

Issue: #1529
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=122512416
parent 9e65693e
...@@ -82,6 +82,7 @@ import java.util.List; ...@@ -82,6 +82,7 @@ import java.util.List;
public static final int TYPE_moof = Util.getIntegerCodeForString("moof"); public static final int TYPE_moof = Util.getIntegerCodeForString("moof");
public static final int TYPE_traf = Util.getIntegerCodeForString("traf"); public static final int TYPE_traf = Util.getIntegerCodeForString("traf");
public static final int TYPE_mvex = Util.getIntegerCodeForString("mvex"); 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_tkhd = Util.getIntegerCodeForString("tkhd");
public static final int TYPE_edts = Util.getIntegerCodeForString("edts"); public static final int TYPE_edts = Util.getIntegerCodeForString("edts");
public static final int TYPE_elst = Util.getIntegerCodeForString("elst"); public static final int TYPE_elst = Util.getIntegerCodeForString("elst");
......
...@@ -50,11 +50,13 @@ import java.util.List; ...@@ -50,11 +50,13 @@ import java.util.List;
* *
* @param trak Atom to parse. * @param trak Atom to parse.
* @param mvhd Movie header atom, used to get the timescale. * @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 drmInitData {@link DrmInitData} to be included in the format.
* @param isQuickTime True for QuickTime media. False otherwise. * @param isQuickTime True for QuickTime media. False otherwise.
* @return A {@link Track} instance, or {@code null} if the track's type isn't supported. * @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) { DrmInitData drmInitData, boolean isQuickTime) {
Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data); int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data);
...@@ -63,7 +65,9 @@ import java.util.List; ...@@ -63,7 +65,9 @@ import java.util.List;
} }
TkhdData tkhdData = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data); 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 movieTimescale = parseMvhd(mvhd.data);
long durationUs; long durationUs;
if (duration == -1) { if (duration == -1) {
...@@ -490,6 +494,11 @@ import java.util.List; ...@@ -490,6 +494,11 @@ import java.util.List;
duration = -1; duration = -1;
} else { } else {
duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong(); 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); tkhd.skipBytes(16);
......
...@@ -166,6 +166,10 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -166,6 +166,10 @@ public final class FragmentedMp4Extractor implements Extractor {
@Override @Override
public void seek() { public void seek() {
int trackCount = trackBundles.size();
for (int i = 0; i < trackCount; i++) {
trackBundles.valueAt(i).reset();
}
containerAtoms.clear(); containerAtoms.clear();
enterReadingAtomHeaderState(); enterReadingAtomHeaderState();
} }
...@@ -340,12 +344,15 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -340,12 +344,15 @@ public final class FragmentedMp4Extractor implements Extractor {
// Read declaration of track fragments in the Moov box. // Read declaration of track fragments in the Moov box.
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex); ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
SparseArray<DefaultSampleValues> defaultSampleValuesArray = new SparseArray<>(); SparseArray<DefaultSampleValues> defaultSampleValuesArray = new SparseArray<>();
long duration = -1;
int mvexChildrenSize = mvex.leafChildren.size(); int mvexChildrenSize = mvex.leafChildren.size();
for (int i = 0; i < mvexChildrenSize; i++) { for (int i = 0; i < mvexChildrenSize; i++) {
Atom.LeafAtom atom = mvex.leafChildren.get(i); Atom.LeafAtom atom = mvex.leafChildren.get(i);
if (atom.type == Atom.TYPE_trex) { if (atom.type == Atom.TYPE_trex) {
Pair<Integer, DefaultSampleValues> trexData = parseTrex(atom.data); Pair<Integer, DefaultSampleValues> trexData = parseTrex(atom.data);
defaultSampleValuesArray.put(trexData.first, trexData.second); 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 { ...@@ -355,7 +362,7 @@ public final class FragmentedMp4Extractor implements Extractor {
for (int i = 0; i < moovContainerChildrenSize; i++) { for (int i = 0; i < moovContainerChildrenSize; i++) {
Atom.ContainerAtom atom = moov.containerChildren.get(i); Atom.ContainerAtom atom = moov.containerChildren.get(i);
if (atom.type == Atom.TYPE_trak) { 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); drmInitData, false);
if (track != null) { if (track != null) {
tracks.put(track.id, track); tracks.put(track.id, track);
...@@ -402,6 +409,16 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -402,6 +409,16 @@ public final class FragmentedMp4Extractor implements Extractor {
defaultSampleDuration, defaultSampleSize, defaultSampleFlags)); 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<TrackBundle> trackBundleArray, private static void parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray,
int flags, byte[] extendedTypeScratch) throws ParserException { int flags, byte[] extendedTypeScratch) throws ParserException {
int moofContainerChildrenSize = moof.containerChildren.size(); int moofContainerChildrenSize = moof.containerChildren.size();
...@@ -427,15 +444,13 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -427,15 +444,13 @@ public final class FragmentedMp4Extractor implements Extractor {
if (trackBundle == null) { if (trackBundle == null) {
return; return;
} }
TrackFragment fragment = trackBundle.fragment; TrackFragment fragment = trackBundle.fragment;
trackBundle.currentSampleIndex = 0; long decodeTime = fragment.nextFragmentDecodeTime;
fragment.reset(); trackBundle.reset();
LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt); LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
long decodeTime; if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) {
if (tfdtAtom == null || (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) != 0) {
decodeTime = 0;
} else {
decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data); decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
} }
...@@ -661,6 +676,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -661,6 +676,7 @@ public final class FragmentedMp4Extractor implements Extractor {
&& (!workaroundEveryVideoFrameIsSyncFrame || i == 0); && (!workaroundEveryVideoFrameIsSyncFrame || i == 0);
cumulativeTime += sampleDuration; cumulativeTime += sampleDuration;
} }
fragment.nextFragmentDecodeTime = cumulativeTime;
} }
private static void parseUuid(ParsableByteArray uuid, TrackFragment out, private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
...@@ -961,7 +977,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -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_tfhd || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_trex
|| atom == Atom.TYPE_trun || atom == Atom.TYPE_pssh || atom == Atom.TYPE_saiz || 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_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}. */ /** Returns whether the extractor should parse a container atom with type {@code atom}. */
...@@ -992,6 +1008,10 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -992,6 +1008,10 @@ public final class FragmentedMp4Extractor implements Extractor {
this.track = Assertions.checkNotNull(track); this.track = Assertions.checkNotNull(track);
this.defaultSampleValues = Assertions.checkNotNull(defaultSampleValues); this.defaultSampleValues = Assertions.checkNotNull(defaultSampleValues);
output.format(track.format); output.format(track.format);
reset();
}
public void reset() {
fragment.reset(); fragment.reset();
currentSampleIndex = 0; currentSampleIndex = 0;
} }
......
...@@ -310,7 +310,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -310,7 +310,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
continue; 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); isQuickTime);
if (track == null) { if (track == null) {
continue; continue;
......
...@@ -80,15 +80,20 @@ import java.io.IOException; ...@@ -80,15 +80,20 @@ import java.io.IOException;
* Whether {@link #sampleEncryptionData} needs populating with the actual encryption data. * Whether {@link #sampleEncryptionData} needs populating with the actual encryption data.
*/ */
public boolean sampleEncryptionDataNeedsFill; public boolean sampleEncryptionDataNeedsFill;
/**
* The absolute decode time of the start of the next fragment.
*/
public long nextFragmentDecodeTime;
/** /**
* Resets the fragment. * Resets the fragment.
* <p> * <p>
* The {@link #length} is set to 0, and both {@link #definesEncryptionData} and * {@link #length} and {@link #nextFragmentDecodeTime} are set to 0, and both
* {@link #sampleEncryptionDataNeedsFill} is set to false. * {@link #definesEncryptionData} and {@link #sampleEncryptionDataNeedsFill} is set to false.
*/ */
public void reset() { public void reset() {
length = 0; length = 0;
nextFragmentDecodeTime = 0;
definesEncryptionData = false; definesEncryptionData = false;
sampleEncryptionDataNeedsFill = false; sampleEncryptionDataNeedsFill = false;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment