mirror of
https://github.com/samsonjs/media.git
synced 2026-03-30 10:15:48 +00:00
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:
parent
9e65693e91
commit
bfee449ed8
5 changed files with 48 additions and 13 deletions
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue