Skip to the sample/auxiliary data offset in fragmented MP4 streams.
The sample data position is the sum of the data offset and the base data offset. The base data offset is either specified in the stream, or defaults to the first byte position in the moof box. (We only support one traf per moof currently, so the offset does not need to be assigned for later track fragments.) The data position can optionally be offset by a data position read from the trun. The auxiliary information offset is calculated in the same way, but using an offset read from the saio box. Issue: #837 Issue: #861
This commit is contained in:
parent
aa647745a2
commit
6d44ec560e
@ -92,6 +92,7 @@ import java.util.List;
|
||||
public static final int TYPE_enca = Util.getIntegerCodeForString("enca");
|
||||
public static final int TYPE_frma = Util.getIntegerCodeForString("frma");
|
||||
public static final int TYPE_saiz = Util.getIntegerCodeForString("saiz");
|
||||
public static final int TYPE_saio = Util.getIntegerCodeForString("saio");
|
||||
public static final int TYPE_uuid = Util.getIntegerCodeForString("uuid");
|
||||
public static final int TYPE_senc = Util.getIntegerCodeForString("senc");
|
||||
public static final int TYPE_pasp = Util.getIntegerCodeForString("pasp");
|
||||
@ -219,6 +220,31 @@ import java.util.List;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of leaf/container children of this atom with the given type.
|
||||
*
|
||||
* @param type The type of child atoms to count.
|
||||
* @return The total number of leaf/container children of this atom with the given type.
|
||||
*/
|
||||
public int getChildAtomOfTypeCount(int type) {
|
||||
int count = 0;
|
||||
int size = leafChildren.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
LeafAtom atom = leafChildren.get(i);
|
||||
if (atom.type == type) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
size = containerChildren.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
ContainerAtom atom = containerChildren.get(i);
|
||||
if (atom.type == type) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getAtomTypeString(type)
|
||||
|
@ -86,6 +86,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
private long atomSize;
|
||||
private int atomHeaderBytesRead;
|
||||
private ParsableByteArray atomData;
|
||||
private long endOfMdatPosition;
|
||||
|
||||
private int sampleIndex;
|
||||
private int sampleSize;
|
||||
@ -204,7 +205,15 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
atomSize = atomHeader.readUnsignedLongToLong();
|
||||
}
|
||||
|
||||
long atomPosition = input.getPosition() - atomHeaderBytesRead;
|
||||
if (atomType == Atom.TYPE_moof) {
|
||||
// The data positions may be updated when parsing the tfhd/trun.
|
||||
fragmentRun.auxiliaryDataPosition = atomPosition;
|
||||
fragmentRun.dataPosition = atomPosition;
|
||||
}
|
||||
|
||||
if (atomType == Atom.TYPE_mdat) {
|
||||
endOfMdatPosition = atomPosition + atomSize;
|
||||
if (!haveOutputSeekMap) {
|
||||
extractorOutput.seekMap(SeekMap.UNSEEKABLE);
|
||||
haveOutputSeekMap = true;
|
||||
@ -324,6 +333,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
|
||||
private static void parseMoof(Track track, DefaultSampleValues extendsDefaults,
|
||||
ContainerAtom moof, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) {
|
||||
// This extractor only supports one traf per moof.
|
||||
Assertions.checkArgument(1 == moof.getChildAtomOfTypeCount(Atom.TYPE_traf));
|
||||
parseTraf(track, extendsDefaults, moof.getContainerAtomOfType(Atom.TYPE_traf),
|
||||
out, workaroundFlags, extendedTypeScratch);
|
||||
}
|
||||
@ -333,6 +344,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
*/
|
||||
private static void parseTraf(Track track, DefaultSampleValues extendsDefaults,
|
||||
ContainerAtom traf, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) {
|
||||
// This extractor only supports one trun per traf.
|
||||
Assertions.checkArgument(1 == traf.getChildAtomOfTypeCount(Atom.TYPE_trun));
|
||||
LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
|
||||
long decodeTime;
|
||||
if (tfdtAtom == null || (workaroundFlags & WORKAROUND_IGNORE_TFDT_BOX) != 0) {
|
||||
@ -342,19 +355,23 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
}
|
||||
|
||||
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
|
||||
DefaultSampleValues fragmentHeader = parseTfhd(extendsDefaults, tfhd.data);
|
||||
out.sampleDescriptionIndex = fragmentHeader.sampleDescriptionIndex;
|
||||
parseTfhd(extendsDefaults, tfhd.data, out);
|
||||
|
||||
LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun);
|
||||
parseTrun(track, fragmentHeader, decodeTime, workaroundFlags, trun.data, out);
|
||||
parseTrun(track, out.header, decodeTime, workaroundFlags, trun.data, out);
|
||||
|
||||
LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
|
||||
if (saiz != null) {
|
||||
TrackEncryptionBox trackEncryptionBox =
|
||||
track.sampleDescriptionEncryptionBoxes[fragmentHeader.sampleDescriptionIndex];
|
||||
track.sampleDescriptionEncryptionBoxes[out.header.sampleDescriptionIndex];
|
||||
parseSaiz(trackEncryptionBox, saiz.data, out);
|
||||
}
|
||||
|
||||
LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio);
|
||||
if (saio != null) {
|
||||
parseSaio(saio.data, out);
|
||||
}
|
||||
|
||||
LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc);
|
||||
if (senc != null) {
|
||||
parseSenc(senc.data, out);
|
||||
@ -401,21 +418,49 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
out.initEncryptionData(totalSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a saio atom (defined in 14496-12).
|
||||
*
|
||||
* @param saio The saio atom to parse.
|
||||
* @param out The track fragment to populate with data from the saio atom.
|
||||
*/
|
||||
private static void parseSaio(ParsableByteArray saio, TrackFragment out) {
|
||||
saio.setPosition(Atom.HEADER_SIZE);
|
||||
int fullAtom = saio.readInt();
|
||||
int flags = Atom.parseFullAtomFlags(fullAtom);
|
||||
if ((flags & 0x01) == 1) {
|
||||
saio.skipBytes(8);
|
||||
}
|
||||
|
||||
int entryCount = saio.readUnsignedIntToInt();
|
||||
if (entryCount != 1) {
|
||||
// We only support one trun element currently, so always expect one entry.
|
||||
throw new IllegalStateException("Unexpected saio entry count: " + entryCount);
|
||||
}
|
||||
|
||||
int version = Atom.parseFullAtomVersion(fullAtom);
|
||||
out.auxiliaryDataPosition +=
|
||||
version == 0 ? saio.readUnsignedInt() : saio.readUnsignedLongToLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a tfhd atom (defined in 14496-12).
|
||||
*
|
||||
* @param extendsDefaults Default sample values from the trex atom.
|
||||
* @return The parsed default sample values.
|
||||
* @param tfhd The tfhd atom to parse.
|
||||
* @param out The track fragment to populate with data from the tfhd atom.
|
||||
*/
|
||||
private static DefaultSampleValues parseTfhd(DefaultSampleValues extendsDefaults,
|
||||
ParsableByteArray tfhd) {
|
||||
private static void parseTfhd(DefaultSampleValues extendsDefaults, ParsableByteArray tfhd,
|
||||
TrackFragment out) {
|
||||
tfhd.setPosition(Atom.HEADER_SIZE);
|
||||
int fullAtom = tfhd.readInt();
|
||||
int flags = Atom.parseFullAtomFlags(fullAtom);
|
||||
|
||||
tfhd.skipBytes(4); // trackId
|
||||
if ((flags & 0x01 /* base_data_offset_present */) != 0) {
|
||||
tfhd.skipBytes(8);
|
||||
long baseDataPosition = tfhd.readUnsignedLongToLong();
|
||||
out.dataPosition = baseDataPosition;
|
||||
out.auxiliaryDataPosition = baseDataPosition;
|
||||
}
|
||||
|
||||
int defaultSampleDescriptionIndex =
|
||||
@ -427,7 +472,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
? tfhd.readUnsignedIntToInt() : extendsDefaults.size;
|
||||
int defaultSampleFlags = ((flags & 0x20 /* default_sample_flags_present */) != 0)
|
||||
? tfhd.readUnsignedIntToInt() : extendsDefaults.flags;
|
||||
return new DefaultSampleValues(defaultSampleDescriptionIndex, defaultSampleDuration,
|
||||
out.header = new DefaultSampleValues(defaultSampleDescriptionIndex, defaultSampleDuration,
|
||||
defaultSampleSize, defaultSampleFlags);
|
||||
}
|
||||
|
||||
@ -461,7 +506,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
|
||||
int sampleCount = trun.readUnsignedIntToInt();
|
||||
if ((flags & 0x01 /* data_offset_present */) != 0) {
|
||||
trun.skipBytes(4);
|
||||
out.dataPosition += trun.readInt();
|
||||
}
|
||||
|
||||
boolean firstSampleFlagsPresent = (flags & 0x04 /* first_sample_flags_present */) != 0;
|
||||
@ -610,6 +655,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
}
|
||||
|
||||
private void readEncryptionData(ExtractorInput input) throws IOException, InterruptedException {
|
||||
int bytesToSkip = (int) (fragmentRun.auxiliaryDataPosition - input.getPosition());
|
||||
Assertions.checkState(bytesToSkip >= 0, "Offset to encryption data was negative.");
|
||||
input.skipFully(bytesToSkip);
|
||||
fragmentRun.fillEncryptionData(input);
|
||||
parserState = STATE_READING_SAMPLE_START;
|
||||
}
|
||||
@ -629,7 +677,16 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
* @throws InterruptedException If the thread is interrupted.
|
||||
*/
|
||||
private boolean readSample(ExtractorInput input) throws IOException, InterruptedException {
|
||||
if (sampleIndex == 0) {
|
||||
int bytesToSkip = (int) (fragmentRun.dataPosition - input.getPosition());
|
||||
Assertions.checkState(bytesToSkip >= 0, "Offset to sample data was negative.");
|
||||
input.skipFully(bytesToSkip);
|
||||
}
|
||||
|
||||
if (sampleIndex >= fragmentRun.length) {
|
||||
int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
|
||||
Assertions.checkState(bytesToSkip >= 0, "Offset to end of mdat was negative.");
|
||||
input.skipFully(bytesToSkip);
|
||||
// We've run out of samples in the current mdat atom.
|
||||
enterReadingAtomHeaderState();
|
||||
return false;
|
||||
@ -687,8 +744,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
long sampleTimeUs = fragmentRun.getSamplePresentationTime(sampleIndex) * 1000L;
|
||||
int sampleFlags = (fragmentRun.definesEncryptionData ? C.SAMPLE_FLAG_ENCRYPTED : 0)
|
||||
| (fragmentRun.sampleIsSyncFrameTable[sampleIndex] ? C.SAMPLE_FLAG_SYNC : 0);
|
||||
int sampleDescriptionIndex = fragmentRun.header.sampleDescriptionIndex;
|
||||
byte[] encryptionKey = fragmentRun.definesEncryptionData
|
||||
? track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex].keyId : null;
|
||||
? track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId : null;
|
||||
trackOutput.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey);
|
||||
|
||||
sampleIndex++;
|
||||
@ -697,8 +755,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
}
|
||||
|
||||
private int appendSampleEncryptionData(ParsableByteArray sampleEncryptionData) {
|
||||
int sampleDescriptionIndex = fragmentRun.header.sampleDescriptionIndex;
|
||||
TrackEncryptionBox encryptionBox =
|
||||
track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex];
|
||||
track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex];
|
||||
int vectorSize = encryptionBox.initializationVectorSize;
|
||||
boolean subsampleEncryption = fragmentRun.sampleHasSubsampleEncryptionTable[sampleIndex];
|
||||
|
||||
@ -730,8 +789,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
|| atom == Atom.TYPE_traf || atom == Atom.TYPE_trak || atom == Atom.TYPE_trex
|
||||
|| atom == Atom.TYPE_trun || atom == Atom.TYPE_mvex || atom == Atom.TYPE_mdia
|
||||
|| atom == Atom.TYPE_minf || atom == Atom.TYPE_stbl || atom == Atom.TYPE_pssh
|
||||
|| atom == Atom.TYPE_saiz || atom == Atom.TYPE_uuid || atom == Atom.TYPE_senc
|
||||
|| atom == Atom.TYPE_pasp || atom == Atom.TYPE_s263;
|
||||
|| atom == Atom.TYPE_saiz || atom == Atom.TYPE_saio || atom == Atom.TYPE_uuid
|
||||
|| atom == Atom.TYPE_senc || atom == Atom.TYPE_pasp || atom == Atom.TYPE_s263;
|
||||
}
|
||||
|
||||
/** Returns whether the extractor should parse a container atom with type {@code atom}. */
|
||||
|
@ -25,8 +25,18 @@ import java.io.IOException;
|
||||
*/
|
||||
/* package */ final class TrackFragment {
|
||||
|
||||
public int sampleDescriptionIndex;
|
||||
|
||||
/**
|
||||
* The default values for samples from the track fragment header.
|
||||
*/
|
||||
public DefaultSampleValues header;
|
||||
/**
|
||||
* The position (byte offset) of the start of sample data.
|
||||
*/
|
||||
public long dataPosition;
|
||||
/**
|
||||
* The position (byte offset) of the start of auxiliary data.
|
||||
*/
|
||||
public long auxiliaryDataPosition;
|
||||
/**
|
||||
* The number of samples contained by the fragment.
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user