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
This commit is contained in:
olly 2016-05-17 04:33:06 -07:00 committed by Oliver Woodman
parent 9e65693e91
commit bfee449ed8
5 changed files with 48 additions and 13 deletions

View file

@ -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");

View file

@ -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);

View file

@ -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<DefaultSampleValues> 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<Integer, DefaultSampleValues> 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<TrackBundle> 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;
}

View file

@ -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;

View file

@ -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.
* <p>
* 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;
}