private void processAtomEnded(long atomEndPosition) throws ParserException { while (!containerAtoms.isEmpty() && containerAtoms.peek().endPosition == atomEndPosition) { Atom.ContainerAtom containerAtom = containerAtoms.pop(); if (containerAtom.type == Atom.TYPE_moov) { // We've reached the end of the moov atom. Process it and prepare to read samples. processMoovAtom(containerAtom); containerAtoms.clear(); parserState = STATE_READING_SAMPLE; } else if (!containerAtoms.isEmpty()) { containerAtoms.peek().add(containerAtom); } } if (parserState != STATE_READING_SAMPLE) { enterReadingAtomHeaderState(); } }
private void onContainerAtomRead(ContainerAtom container) throws ParserException { if (container.type == Atom.TYPE_moov) { onMoovContainerAtomRead(container); } else if (container.type == Atom.TYPE_moof) { onMoofContainerAtomRead(container); } else if (!containerAtoms.isEmpty()) { containerAtoms.peek().add(container); } }
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException { parseMoof(moof, trackBundles, flags, extendedTypeScratch); DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren); if (drmInitData != null) { int trackCount = trackBundles.size(); for (int i = 0; i < trackCount; i++) { trackBundles.valueAt(i).updateDrmInitData(drmInitData); } } }
private static void parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray, @Flags int flags, byte[] extendedTypeScratch) throws ParserException { int moofContainerChildrenSize = moof.containerChildren.size(); for (int i = 0; i < moofContainerChildrenSize; 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) { parseTraf(child, trackBundleArray, flags, extendedTypeScratch); } } }
private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime, @Flags 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 void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException { parseMoof(moof, trackBundles, flags, extendedTypeScratch); // If drm init data is sideloaded, we ignore pssh boxes. DrmInitData drmInitData = sideloadedDrmInitData != null ? null : getDrmInitDataFromAtoms(moof.leafChildren); if (drmInitData != null) { int trackCount = trackBundles.size(); for (int i = 0; i < trackCount; i++) { trackBundles.valueAt(i).updateDrmInitData(drmInitData); } } }
private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException { if (atomHeaderBytesRead == 0) { // Read the standard length atom header. if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) { return false; } atomHeaderBytesRead = Atom.HEADER_SIZE; atomHeader.setPosition(0); atomSize = atomHeader.readUnsignedInt(); atomType = atomHeader.readInt(); } if (atomSize == Atom.LONG_SIZE_PREFIX) { // Read the extended atom size. int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining); atomHeaderBytesRead += headerBytesRemaining; atomSize = atomHeader.readUnsignedLongToLong(); } if (shouldParseContainerAtom(atomType)) { long endPosition = input.getPosition() + atomSize - atomHeaderBytesRead; containerAtoms.add(new ContainerAtom(atomType, endPosition)); if (atomSize == atomHeaderBytesRead) { processAtomEnded(endPosition); } else { // Start reading the first child atom. enterReadingAtomHeaderState(); } } else if (shouldParseLeafAtom(atomType)) { // We don't support parsing of leaf atoms that define extended atom sizes, or that have // lengths greater than Integer.MAX_VALUE. Assertions.checkState(atomHeaderBytesRead == Atom.HEADER_SIZE); Assertions.checkState(atomSize <= Integer.MAX_VALUE); atomData = new ParsableByteArray((int) atomSize); System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.HEADER_SIZE); parserState = STATE_READING_ATOM_PAYLOAD; } else { atomData = null; parserState = STATE_READING_ATOM_PAYLOAD; } return true; }
/** * Updates the stored track metadata to reflect the contents of the specified moov atom. */ private void processMoovAtom(ContainerAtom moov) throws ParserException { long durationUs = C.TIME_UNSET; List<Mp4Track> tracks = new ArrayList<>(); long earliestSampleOffset = Long.MAX_VALUE; Metadata metadata = null; GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder(); Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); if (udta != null) { metadata = AtomParsers.parseUdta(udta, isQuickTime); if (metadata != null) { gaplessInfoHolder.setFromMetadata(metadata); } } for (int i = 0; i < moov.containerChildren.size(); i++) { Atom.ContainerAtom atom = moov.containerChildren.get(i); if (atom.type != Atom.TYPE_trak) { continue; } Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), C.TIME_UNSET, null, isQuickTime); if (track == null) { continue; } Atom.ContainerAtom stblAtom = atom.getContainerAtomOfType(Atom.TYPE_mdia) .getContainerAtomOfType(Atom.TYPE_minf).getContainerAtomOfType(Atom.TYPE_stbl); TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom, gaplessInfoHolder); if (trackSampleTable.sampleCount == 0) { continue; } Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, extractorOutput.track(i, track.type)); // Each sample has up to three bytes of overhead for the start code that replaces its length. // Allow ten source samples per output sample, like the platform extractor. int maxInputSize = trackSampleTable.maximumSize + 3 * 10; Format format = track.format.copyWithMaxInputSize(maxInputSize); if (track.type == C.TRACK_TYPE_AUDIO) { if (gaplessInfoHolder.hasGaplessInfo()) { format = format.copyWithGaplessInfo(gaplessInfoHolder.encoderDelay, gaplessInfoHolder.encoderPadding); } if (metadata != null) { format = format.copyWithMetadata(metadata); } } mp4Track.trackOutput.format(format); durationUs = Math.max(durationUs, track.durationUs); tracks.add(mp4Track); long firstSampleOffset = trackSampleTable.offsets[0]; if (firstSampleOffset < earliestSampleOffset) { earliestSampleOffset = firstSampleOffset; } } this.durationUs = durationUs; this.tracks = tracks.toArray(new Mp4Track[tracks.size()]); extractorOutput.endTracks(); extractorOutput.seekMap(this); }
private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException { if (atomHeaderBytesRead == 0) { // Read the standard length atom header. if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) { return false; } atomHeaderBytesRead = Atom.HEADER_SIZE; atomHeader.setPosition(0); atomSize = atomHeader.readUnsignedInt(); atomType = atomHeader.readInt(); } if (atomSize == Atom.LONG_SIZE_PREFIX) { // Read the extended atom size. int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining); atomHeaderBytesRead += headerBytesRemaining; atomSize = atomHeader.readUnsignedLongToLong(); } if (atomSize < atomHeaderBytesRead) { throw new ParserException("Atom size less than header length (unsupported)."); } long atomPosition = input.getPosition() - atomHeaderBytesRead; if (atomType == Atom.TYPE_moof) { // The data positions may be updated when parsing the tfhd/trun. int trackCount = trackBundles.size(); for (int i = 0; i < trackCount; i++) { TrackFragment fragment = trackBundles.valueAt(i).fragment; fragment.atomPosition = atomPosition; fragment.auxiliaryDataPosition = atomPosition; fragment.dataPosition = atomPosition; } } if (atomType == Atom.TYPE_mdat) { currentTrackBundle = null; endOfMdatPosition = atomPosition + atomSize; if (!haveOutputSeekMap) { extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); haveOutputSeekMap = true; } parserState = STATE_READING_ENCRYPTION_DATA; return true; } if (shouldParseContainerAtom(atomType)) { long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE; containerAtoms.add(new ContainerAtom(atomType, endPosition)); if (atomSize == atomHeaderBytesRead) { processAtomEnded(endPosition); } else { // Start reading the first child atom. enterReadingAtomHeaderState(); } } else if (shouldParseLeafAtom(atomType)) { if (atomHeaderBytesRead != Atom.HEADER_SIZE) { throw new ParserException("Leaf atom defines extended atom size (unsupported)."); } if (atomSize > Integer.MAX_VALUE) { throw new ParserException("Leaf atom with length > 2147483647 (unsupported)."); } atomData = new ParsableByteArray((int) atomSize); System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.HEADER_SIZE); parserState = STATE_READING_ATOM_PAYLOAD; } else { if (atomSize > Integer.MAX_VALUE) { throw new ParserException("Skipping atom with length > 2147483647 (unsupported)."); } atomData = null; parserState = STATE_READING_ATOM_PAYLOAD; } return true; }
/** * Parses a traf atom (defined in 14496-12). */ private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray, @Flags int flags, byte[] extendedTypeScratch) throws ParserException { LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd); TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags); if (trackBundle == null) { return; } TrackFragment fragment = trackBundle.fragment; long decodeTime = fragment.nextFragmentDecodeTime; trackBundle.reset(); LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt); if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) { decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data); } parseTruns(traf, trackBundle, decodeTime, flags); LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz); if (saiz != null) { TrackEncryptionBox trackEncryptionBox = trackBundle.track .sampleDescriptionEncryptionBoxes[fragment.header.sampleDescriptionIndex]; parseSaiz(trackEncryptionBox, saiz.data, fragment); } LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio); if (saio != null) { parseSaio(saio.data, fragment); } LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc); if (senc != null) { parseSenc(senc.data, fragment); } LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp); LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd); if (sbgp != null && sgpd != null) { parseSgpd(sbgp.data, sgpd.data, fragment); } int leafChildrenSize = traf.leafChildren.size(); for (int i = 0; i < leafChildrenSize; i++) { LeafAtom atom = traf.leafChildren.get(i); if (atom.type == Atom.TYPE_uuid) { parseUuid(atom.data, fragment, extendedTypeScratch); } } }
/** * Updates the stored track metadata to reflect the contents of the specified moov atom. */ private void processMoovAtom(ContainerAtom moov) throws ParserException { long durationUs = C.TIME_UNSET; List<Mp4Track> tracks = new ArrayList<>(); long earliestSampleOffset = Long.MAX_VALUE; GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder(); Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); if (udta != null) { AtomParsers.parseUdta(udta, isQuickTime, gaplessInfoHolder); } for (int i = 0; i < moov.containerChildren.size(); i++) { Atom.ContainerAtom atom = moov.containerChildren.get(i); if (atom.type != Atom.TYPE_trak) { continue; } Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), C.TIME_UNSET, null, isQuickTime); if (track == null) { continue; } Atom.ContainerAtom stblAtom = atom.getContainerAtomOfType(Atom.TYPE_mdia) .getContainerAtomOfType(Atom.TYPE_minf).getContainerAtomOfType(Atom.TYPE_stbl); TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom, gaplessInfoHolder); if (trackSampleTable.sampleCount == 0) { continue; } Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, extractorOutput.track(i)); // Each sample has up to three bytes of overhead for the start code that replaces its length. // Allow ten source samples per output sample, like the platform extractor. int maxInputSize = trackSampleTable.maximumSize + 3 * 10; Format format = track.format.copyWithMaxInputSize(maxInputSize); if (track.type == C.TRACK_TYPE_AUDIO && gaplessInfoHolder.hasGaplessInfo()) { format = format.copyWithGaplessInfo(gaplessInfoHolder.encoderDelay, gaplessInfoHolder.encoderPadding); } mp4Track.trackOutput.format(format); durationUs = Math.max(durationUs, track.durationUs); tracks.add(mp4Track); long firstSampleOffset = trackSampleTable.offsets[0]; if (firstSampleOffset < earliestSampleOffset) { earliestSampleOffset = firstSampleOffset; } } this.durationUs = durationUs; this.tracks = tracks.toArray(new Mp4Track[tracks.size()]); extractorOutput.endTracks(); extractorOutput.seekMap(this); }
private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException { if (atomHeaderBytesRead == 0) { // Read the standard length atom header. if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) { return false; } atomHeaderBytesRead = Atom.HEADER_SIZE; atomHeader.setPosition(0); atomSize = atomHeader.readUnsignedInt(); atomType = atomHeader.readInt(); } if (atomSize == Atom.LONG_SIZE_PREFIX) { // Read the extended atom size. int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining); atomHeaderBytesRead += headerBytesRemaining; atomSize = atomHeader.readUnsignedLongToLong(); } long atomPosition = input.getPosition() - atomHeaderBytesRead; if (atomType == Atom.TYPE_moof) { // The data positions may be updated when parsing the tfhd/trun. int trackCount = trackBundles.size(); for (int i = 0; i < trackCount; i++) { TrackFragment fragment = trackBundles.valueAt(i).fragment; fragment.atomPosition = atomPosition; fragment.auxiliaryDataPosition = atomPosition; fragment.dataPosition = atomPosition; } } if (atomType == Atom.TYPE_mdat) { currentTrackBundle = null; endOfMdatPosition = atomPosition + atomSize; if (!haveOutputSeekMap) { extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); haveOutputSeekMap = true; } parserState = STATE_READING_ENCRYPTION_DATA; return true; } if (shouldParseContainerAtom(atomType)) { long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE; containerAtoms.add(new ContainerAtom(atomType, endPosition)); if (atomSize == atomHeaderBytesRead) { processAtomEnded(endPosition); } else { // Start reading the first child atom. enterReadingAtomHeaderState(); } } else if (shouldParseLeafAtom(atomType)) { if (atomHeaderBytesRead != Atom.HEADER_SIZE) { throw new ParserException("Leaf atom defines extended atom size (unsupported)."); } if (atomSize > Integer.MAX_VALUE) { throw new ParserException("Leaf atom with length > 2147483647 (unsupported)."); } atomData = new ParsableByteArray((int) atomSize); System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.HEADER_SIZE); parserState = STATE_READING_ATOM_PAYLOAD; } else { if (atomSize > Integer.MAX_VALUE) { throw new ParserException("Skipping atom with length > 2147483647 (unsupported)."); } atomData = null; parserState = STATE_READING_ATOM_PAYLOAD; } return true; }
/** * Updates the stored track metadata to reflect the contents of the specified moov atom. */ private void processMoovAtom(ContainerAtom moov) throws ParserException { long durationUs = C.TIME_UNSET; List<Mp4Track> tracks = new ArrayList<>(); long earliestSampleOffset = Long.MAX_VALUE; Metadata metadata = null; GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder(); Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); if (udta != null) { metadata = AtomParsers.parseUdta(udta, isQuickTime); if (metadata != null) { gaplessInfoHolder.setFromMetadata(metadata); } } for (int i = 0; i < moov.containerChildren.size(); i++) { Atom.ContainerAtom atom = moov.containerChildren.get(i); if (atom.type != Atom.TYPE_trak) { continue; } Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), C.TIME_UNSET, null, (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0, isQuickTime); if (track == null) { continue; } Atom.ContainerAtom stblAtom = atom.getContainerAtomOfType(Atom.TYPE_mdia) .getContainerAtomOfType(Atom.TYPE_minf).getContainerAtomOfType(Atom.TYPE_stbl); TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom, gaplessInfoHolder); if (trackSampleTable.sampleCount == 0) { continue; } Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, extractorOutput.track(i, track.type)); // Each sample has up to three bytes of overhead for the start code that replaces its length. // Allow ten source samples per output sample, like the platform extractor. int maxInputSize = trackSampleTable.maximumSize + 3 * 10; Format format = track.format.copyWithMaxInputSize(maxInputSize); if (track.type == C.TRACK_TYPE_AUDIO) { if (gaplessInfoHolder.hasGaplessInfo()) { format = format.copyWithGaplessInfo(gaplessInfoHolder.encoderDelay, gaplessInfoHolder.encoderPadding); } if (metadata != null) { format = format.copyWithMetadata(metadata); } } mp4Track.trackOutput.format(format); durationUs = Math.max(durationUs, track.durationUs); tracks.add(mp4Track); long firstSampleOffset = trackSampleTable.offsets[0]; if (firstSampleOffset < earliestSampleOffset) { earliestSampleOffset = firstSampleOffset; } } this.durationUs = durationUs; this.tracks = tracks.toArray(new Mp4Track[tracks.size()]); extractorOutput.endTracks(); extractorOutput.seekMap(this); }
/** * Parses a traf atom (defined in 14496-12). */ private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray, @Flags int flags, byte[] extendedTypeScratch) throws ParserException { LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd); TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags); if (trackBundle == null) { return; } TrackFragment fragment = trackBundle.fragment; long decodeTime = fragment.nextFragmentDecodeTime; trackBundle.reset(); LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt); if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) { decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data); } parseTruns(traf, trackBundle, decodeTime, flags); TrackEncryptionBox encryptionBox = trackBundle.track .getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex); LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz); if (saiz != null) { parseSaiz(encryptionBox, saiz.data, fragment); } LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio); if (saio != null) { parseSaio(saio.data, fragment); } LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc); if (senc != null) { parseSenc(senc.data, fragment); } LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp); LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd); if (sbgp != null && sgpd != null) { parseSgpd(sbgp.data, sgpd.data, encryptionBox != null ? encryptionBox.schemeType : null, fragment); } int leafChildrenSize = traf.leafChildren.size(); for (int i = 0; i < leafChildrenSize; i++) { LeafAtom atom = traf.leafChildren.get(i); if (atom.type == Atom.TYPE_uuid) { parseUuid(atom.data, fragment, extendedTypeScratch); } } }