Add support for multiple truns in a single traf
This kind of mp4 files has been seen in Hls+fMp4 samples. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=130094845
This commit is contained in:
parent
d04fde1a53
commit
fc5df6312d
@ -436,6 +436,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
int moofContainerChildrenSize = moof.containerChildren.size();
|
int moofContainerChildrenSize = moof.containerChildren.size();
|
||||||
for (int i = 0; i < moofContainerChildrenSize; i++) {
|
for (int i = 0; i < moofContainerChildrenSize; i++) {
|
||||||
Atom.ContainerAtom child = moof.containerChildren.get(i);
|
Atom.ContainerAtom child = moof.containerChildren.get(i);
|
||||||
|
// TODO: Support multiple traf boxes per track in a single moof.
|
||||||
if (child.type == Atom.TYPE_traf) {
|
if (child.type == Atom.TYPE_traf) {
|
||||||
parseTraf(child, trackBundleArray, flags, extendedTypeScratch);
|
parseTraf(child, trackBundleArray, flags, extendedTypeScratch);
|
||||||
}
|
}
|
||||||
@ -447,10 +448,6 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
*/
|
*/
|
||||||
private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
|
private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
|
||||||
int flags, byte[] extendedTypeScratch) throws ParserException {
|
int flags, byte[] extendedTypeScratch) throws ParserException {
|
||||||
if (traf.getChildAtomOfTypeCount(Atom.TYPE_trun) != 1) {
|
|
||||||
throw new ParserException("Trun count in traf != 1 (unsupported).");
|
|
||||||
}
|
|
||||||
|
|
||||||
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
|
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
|
||||||
TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags);
|
TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags);
|
||||||
if (trackBundle == null) {
|
if (trackBundle == null) {
|
||||||
@ -466,8 +463,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
|
decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
|
||||||
}
|
}
|
||||||
|
|
||||||
LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun);
|
parseTruns(traf, trackBundle, decodeTime, flags);
|
||||||
parseTrun(trackBundle, decodeTime, flags, trun.data);
|
|
||||||
|
|
||||||
LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
|
LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
|
||||||
if (saiz != null) {
|
if (saiz != null) {
|
||||||
@ -492,8 +488,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
parseSgpd(sbgp.data, sgpd.data, fragment);
|
parseSgpd(sbgp.data, sgpd.data, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
int childrenSize = traf.leafChildren.size();
|
int leafChildrenSize = traf.leafChildren.size();
|
||||||
for (int i = 0; i < childrenSize; i++) {
|
for (int i = 0; i < leafChildrenSize; i++) {
|
||||||
LeafAtom atom = traf.leafChildren.get(i);
|
LeafAtom atom = traf.leafChildren.get(i);
|
||||||
if (atom.type == Atom.TYPE_uuid) {
|
if (atom.type == Atom.TYPE_uuid) {
|
||||||
parseUuid(atom.data, fragment, extendedTypeScratch);
|
parseUuid(atom.data, fragment, extendedTypeScratch);
|
||||||
@ -501,6 +497,40 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime,
|
||||||
|
int flags) {
|
||||||
|
int trunCount = 0;
|
||||||
|
int totalSampleCount = 0;
|
||||||
|
List<LeafAtom> leafChildren = traf.leafChildren;
|
||||||
|
int leafChildrenSize = leafChildren.size();
|
||||||
|
for (int i = 0; i < leafChildrenSize; i++) {
|
||||||
|
LeafAtom atom = leafChildren.get(i);
|
||||||
|
if (atom.type == Atom.TYPE_trun) {
|
||||||
|
ParsableByteArray trunData = atom.data;
|
||||||
|
trunData.setPosition(Atom.FULL_HEADER_SIZE);
|
||||||
|
int trunSampleCount = trunData.readUnsignedIntToInt();
|
||||||
|
if (trunSampleCount > 0) {
|
||||||
|
totalSampleCount += trunSampleCount;
|
||||||
|
trunCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trackBundle.currentTrackRunIndex = 0;
|
||||||
|
trackBundle.currentSampleInTrackRun = 0;
|
||||||
|
trackBundle.currentSampleIndex = 0;
|
||||||
|
trackBundle.fragment.initTables(trunCount, totalSampleCount);
|
||||||
|
|
||||||
|
int trunIndex = 0;
|
||||||
|
int trunStartPosition = 0;
|
||||||
|
for (int i = 0; i < leafChildrenSize; i++) {
|
||||||
|
LeafAtom trun = leafChildren.get(i);
|
||||||
|
if (trun.type == Atom.TYPE_trun) {
|
||||||
|
trunStartPosition = parseTrun(trackBundle, trunIndex++, decodeTime, flags, trun.data,
|
||||||
|
trunStartPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz,
|
private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz,
|
||||||
TrackFragment out) throws ParserException {
|
TrackFragment out) throws ParserException {
|
||||||
int vectorSize = encryptionBox.initializationVectorSize;
|
int vectorSize = encryptionBox.initializationVectorSize;
|
||||||
@ -513,8 +543,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
int defaultSampleInfoSize = saiz.readUnsignedByte();
|
int defaultSampleInfoSize = saiz.readUnsignedByte();
|
||||||
|
|
||||||
int sampleCount = saiz.readUnsignedIntToInt();
|
int sampleCount = saiz.readUnsignedIntToInt();
|
||||||
if (sampleCount != out.length) {
|
if (sampleCount != out.sampleCount) {
|
||||||
throw new ParserException("Length mismatch: " + sampleCount + ", " + out.length);
|
throw new ParserException("Length mismatch: " + sampleCount + ", " + out.sampleCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
int totalSize = 0;
|
int totalSize = 0;
|
||||||
@ -617,12 +647,14 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
*
|
*
|
||||||
* @param trackBundle The {@link TrackBundle} that contains the {@link TrackFragment} into
|
* @param trackBundle The {@link TrackBundle} that contains the {@link TrackFragment} into
|
||||||
* which parsed data should be placed.
|
* which parsed data should be placed.
|
||||||
|
* @param index Index of the track run in the fragment.
|
||||||
* @param decodeTime The decode time of the first sample in the fragment run.
|
* @param decodeTime The decode time of the first sample in the fragment run.
|
||||||
* @param flags Flags to allow any required workaround to be executed.
|
* @param flags Flags to allow any required workaround to be executed.
|
||||||
* @param trun The trun atom to decode.
|
* @param trun The trun atom to decode.
|
||||||
|
* @return The starting position of samples for the next run.
|
||||||
*/
|
*/
|
||||||
private static void parseTrun(TrackBundle trackBundle, long decodeTime, int flags,
|
private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime, int flags,
|
||||||
ParsableByteArray trun) {
|
ParsableByteArray trun, int trackRunStart) {
|
||||||
trun.setPosition(Atom.HEADER_SIZE);
|
trun.setPosition(Atom.HEADER_SIZE);
|
||||||
int fullAtom = trun.readInt();
|
int fullAtom = trun.readInt();
|
||||||
int atomFlags = Atom.parseFullAtomFlags(fullAtom);
|
int atomFlags = Atom.parseFullAtomFlags(fullAtom);
|
||||||
@ -631,9 +663,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
TrackFragment fragment = trackBundle.fragment;
|
TrackFragment fragment = trackBundle.fragment;
|
||||||
DefaultSampleValues defaultSampleValues = fragment.header;
|
DefaultSampleValues defaultSampleValues = fragment.header;
|
||||||
|
|
||||||
int sampleCount = trun.readUnsignedIntToInt();
|
fragment.trunLength[index] = trun.readUnsignedIntToInt();
|
||||||
if ((atomFlags & 0x01 /* data_offset_present */) != 0) {
|
if ((atomFlags & 0x01 /* data_offset_present */) != 0) {
|
||||||
fragment.dataPosition += trun.readInt();
|
fragment.trunDataPosition[index] = fragment.dataPosition + trun.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean firstSampleFlagsPresent = (atomFlags & 0x04 /* first_sample_flags_present */) != 0;
|
boolean firstSampleFlagsPresent = (atomFlags & 0x04 /* first_sample_flags_present */) != 0;
|
||||||
@ -659,17 +691,18 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
edtsOffset = Util.scaleLargeTimestamp(track.editListMediaTimes[0], 1000, track.timescale);
|
edtsOffset = Util.scaleLargeTimestamp(track.editListMediaTimes[0], 1000, track.timescale);
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment.initTables(sampleCount);
|
|
||||||
int[] sampleSizeTable = fragment.sampleSizeTable;
|
int[] sampleSizeTable = fragment.sampleSizeTable;
|
||||||
int[] sampleCompositionTimeOffsetTable = fragment.sampleCompositionTimeOffsetTable;
|
int[] sampleCompositionTimeOffsetTable = fragment.sampleCompositionTimeOffsetTable;
|
||||||
long[] sampleDecodingTimeTable = fragment.sampleDecodingTimeTable;
|
long[] sampleDecodingTimeTable = fragment.sampleDecodingTimeTable;
|
||||||
boolean[] sampleIsSyncFrameTable = fragment.sampleIsSyncFrameTable;
|
boolean[] sampleIsSyncFrameTable = fragment.sampleIsSyncFrameTable;
|
||||||
|
|
||||||
long timescale = track.timescale;
|
|
||||||
long cumulativeTime = decodeTime;
|
|
||||||
boolean workaroundEveryVideoFrameIsSyncFrame = track.type == C.TRACK_TYPE_VIDEO
|
boolean workaroundEveryVideoFrameIsSyncFrame = track.type == C.TRACK_TYPE_VIDEO
|
||||||
&& (flags & FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0;
|
&& (flags & FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0;
|
||||||
for (int i = 0; i < sampleCount; i++) {
|
|
||||||
|
int trackRunEnd = trackRunStart + fragment.trunLength[index];
|
||||||
|
long timescale = track.timescale;
|
||||||
|
long cumulativeTime = index > 0 ? fragment.nextFragmentDecodeTime : decodeTime;
|
||||||
|
for (int i = trackRunStart; i < trackRunEnd; i++) {
|
||||||
// Use trun values if present, otherwise tfhd, otherwise trex.
|
// Use trun values if present, otherwise tfhd, otherwise trex.
|
||||||
int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt()
|
int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt()
|
||||||
: defaultSampleValues.duration;
|
: defaultSampleValues.duration;
|
||||||
@ -695,6 +728,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
cumulativeTime += sampleDuration;
|
cumulativeTime += sampleDuration;
|
||||||
}
|
}
|
||||||
fragment.nextFragmentDecodeTime = cumulativeTime;
|
fragment.nextFragmentDecodeTime = cumulativeTime;
|
||||||
|
return trackRunEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
|
private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
|
||||||
@ -730,8 +764,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
|
|
||||||
boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0;
|
boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0;
|
||||||
int sampleCount = senc.readUnsignedIntToInt();
|
int sampleCount = senc.readUnsignedIntToInt();
|
||||||
if (sampleCount != out.length) {
|
if (sampleCount != out.sampleCount) {
|
||||||
throw new ParserException("Length mismatch: " + sampleCount + ", " + out.length);
|
throw new ParserException("Length mismatch: " + sampleCount + ", " + out.sampleCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
|
Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
|
||||||
@ -895,7 +929,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
long nextDataPosition = currentTrackBundle.fragment.dataPosition;
|
long nextDataPosition = currentTrackBundle.fragment
|
||||||
|
.trunDataPosition[currentTrackBundle.currentTrackRunIndex];
|
||||||
// We skip bytes preceding the next sample to read.
|
// We skip bytes preceding the next sample to read.
|
||||||
int bytesToSkip = (int) (nextDataPosition - input.getPosition());
|
int bytesToSkip = (int) (nextDataPosition - input.getPosition());
|
||||||
if (bytesToSkip < 0) {
|
if (bytesToSkip < 0) {
|
||||||
@ -973,7 +1008,11 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey);
|
output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey);
|
||||||
|
|
||||||
currentTrackBundle.currentSampleIndex++;
|
currentTrackBundle.currentSampleIndex++;
|
||||||
if (currentTrackBundle.currentSampleIndex == fragment.length) {
|
currentTrackBundle.currentSampleInTrackRun++;
|
||||||
|
if (currentTrackBundle.currentSampleInTrackRun
|
||||||
|
== fragment.trunLength[currentTrackBundle.currentTrackRunIndex]) {
|
||||||
|
currentTrackBundle.currentTrackRunIndex++;
|
||||||
|
currentTrackBundle.currentSampleInTrackRun = 0;
|
||||||
currentTrackBundle = null;
|
currentTrackBundle = null;
|
||||||
}
|
}
|
||||||
parserState = STATE_READING_SAMPLE_START;
|
parserState = STATE_READING_SAMPLE_START;
|
||||||
@ -991,10 +1030,10 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
int trackBundlesSize = trackBundles.size();
|
int trackBundlesSize = trackBundles.size();
|
||||||
for (int i = 0; i < trackBundlesSize; i++) {
|
for (int i = 0; i < trackBundlesSize; i++) {
|
||||||
TrackBundle trackBundle = trackBundles.valueAt(i);
|
TrackBundle trackBundle = trackBundles.valueAt(i);
|
||||||
if (trackBundle.currentSampleIndex == trackBundle.fragment.length) {
|
if (trackBundle.currentTrackRunIndex == trackBundle.fragment.trunCount) {
|
||||||
// This track fragment contains no more runs in the next mdat box.
|
// This track fragment contains no more runs in the next mdat box.
|
||||||
} else {
|
} else {
|
||||||
long trunOffset = trackBundle.fragment.dataPosition;
|
long trunOffset = trackBundle.fragment.trunDataPosition[trackBundle.currentTrackRunIndex];
|
||||||
if (trunOffset < nextTrackRunOffset) {
|
if (trunOffset < nextTrackRunOffset) {
|
||||||
nextTrackBundle = trackBundle;
|
nextTrackBundle = trackBundle;
|
||||||
nextTrackRunOffset = trunOffset;
|
nextTrackRunOffset = trunOffset;
|
||||||
@ -1071,6 +1110,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
public Track track;
|
public Track track;
|
||||||
public DefaultSampleValues defaultSampleValues;
|
public DefaultSampleValues defaultSampleValues;
|
||||||
public int currentSampleIndex;
|
public int currentSampleIndex;
|
||||||
|
public int currentSampleInTrackRun;
|
||||||
|
public int currentTrackRunIndex;
|
||||||
|
|
||||||
public TrackBundle(TrackOutput output) {
|
public TrackBundle(TrackOutput output) {
|
||||||
fragment = new TrackFragment();
|
fragment = new TrackFragment();
|
||||||
@ -1087,6 +1128,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
public void reset() {
|
public void reset() {
|
||||||
fragment.reset();
|
fragment.reset();
|
||||||
currentSampleIndex = 0;
|
currentSampleIndex = 0;
|
||||||
|
currentTrackRunIndex = 0;
|
||||||
|
currentSampleInTrackRun = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public DefaultSampleValues header;
|
public DefaultSampleValues header;
|
||||||
/**
|
/**
|
||||||
* The position (byte offset) of the start of sample data.
|
* The position (byte offset) of the start of data contained in the fragment.
|
||||||
*/
|
*/
|
||||||
public long dataPosition;
|
public long dataPosition;
|
||||||
/**
|
/**
|
||||||
@ -37,19 +37,31 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public long auxiliaryDataPosition;
|
public long auxiliaryDataPosition;
|
||||||
/**
|
/**
|
||||||
* The number of samples contained by the fragment.
|
* The number of track runs of the fragment.
|
||||||
*/
|
*/
|
||||||
public int length;
|
public int trunCount;
|
||||||
/**
|
/**
|
||||||
* The size of each sample in the run.
|
* The total number of samples in the fragment.
|
||||||
|
*/
|
||||||
|
public int sampleCount;
|
||||||
|
/**
|
||||||
|
* The position (byte offset) of the start of sample data of each track run in the fragment.
|
||||||
|
*/
|
||||||
|
public long[] trunDataPosition;
|
||||||
|
/**
|
||||||
|
* The number of samples contained by each track run in the fragment.
|
||||||
|
*/
|
||||||
|
public int[] trunLength;
|
||||||
|
/**
|
||||||
|
* The size of each sample in the fragment.
|
||||||
*/
|
*/
|
||||||
public int[] sampleSizeTable;
|
public int[] sampleSizeTable;
|
||||||
/**
|
/**
|
||||||
* The composition time offset of each sample in the run.
|
* The composition time offset of each sample in the fragment.
|
||||||
*/
|
*/
|
||||||
public int[] sampleCompositionTimeOffsetTable;
|
public int[] sampleCompositionTimeOffsetTable;
|
||||||
/**
|
/**
|
||||||
* The decoding time of each sample in the run.
|
* The decoding time of each sample in the fragment.
|
||||||
*/
|
*/
|
||||||
public long[] sampleDecodingTimeTable;
|
public long[] sampleDecodingTimeTable;
|
||||||
/**
|
/**
|
||||||
@ -91,12 +103,12 @@ import java.io.IOException;
|
|||||||
/**
|
/**
|
||||||
* Resets the fragment.
|
* Resets the fragment.
|
||||||
* <p>
|
* <p>
|
||||||
* {@link #length} and {@link #nextFragmentDecodeTime} are set to 0, and both
|
* {@link #sampleCount} and {@link #nextFragmentDecodeTime} are set to 0, and both
|
||||||
* {@link #definesEncryptionData} and {@link #sampleEncryptionDataNeedsFill} is set to false,
|
* {@link #definesEncryptionData} and {@link #sampleEncryptionDataNeedsFill} is set to false,
|
||||||
* and {@link #trackEncryptionBox} is set to null.
|
* and {@link #trackEncryptionBox} is set to null.
|
||||||
*/
|
*/
|
||||||
public void reset() {
|
public void reset() {
|
||||||
length = 0;
|
trunCount = 0;
|
||||||
nextFragmentDecodeTime = 0;
|
nextFragmentDecodeTime = 0;
|
||||||
definesEncryptionData = false;
|
definesEncryptionData = false;
|
||||||
sampleEncryptionDataNeedsFill = false;
|
sampleEncryptionDataNeedsFill = false;
|
||||||
@ -106,14 +118,19 @@ import java.io.IOException;
|
|||||||
/**
|
/**
|
||||||
* Configures the fragment for the specified number of samples.
|
* Configures the fragment for the specified number of samples.
|
||||||
* <p>
|
* <p>
|
||||||
* The {@link #length} of the fragment is set to the specified sample count, and the contained
|
* The {@link #sampleCount} of the fragment is set to the specified sample count, and the
|
||||||
* tables are resized if necessary such that they are at least this length.
|
* contained tables are resized if necessary such that they are at least this length.
|
||||||
*
|
*
|
||||||
* @param sampleCount The number of samples in the new run.
|
* @param sampleCount The number of samples in the new run.
|
||||||
*/
|
*/
|
||||||
public void initTables(int sampleCount) {
|
public void initTables(int trunCount, int sampleCount) {
|
||||||
length = sampleCount;
|
this.trunCount = trunCount;
|
||||||
if (sampleSizeTable == null || sampleSizeTable.length < length) {
|
this.sampleCount = sampleCount;
|
||||||
|
if (trunLength == null || trunLength.length < trunCount) {
|
||||||
|
trunDataPosition = new long[trunCount];
|
||||||
|
trunLength = new int[trunCount];
|
||||||
|
}
|
||||||
|
if (sampleSizeTable == null || sampleSizeTable.length < sampleCount) {
|
||||||
// Size the tables 25% larger than needed, so as to make future resize operations less
|
// Size the tables 25% larger than needed, so as to make future resize operations less
|
||||||
// likely. The choice of 25% is relatively arbitrary.
|
// likely. The choice of 25% is relatively arbitrary.
|
||||||
int tableSize = (sampleCount * 125) / 100;
|
int tableSize = (sampleCount * 125) / 100;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user