diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java index e0b74bb2f9..21b5356367 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java @@ -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) diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java index 0df0f40e08..996eab4578 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java @@ -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}. */ diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java index 7cb7badedd..c128495707 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java @@ -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. */