private void onMoovContainerAtomRead(ContainerAtom moov) { List<Atom.LeafAtom> moovChildren = moov.leafChildren; int moovChildrenSize = moovChildren.size(); DrmInitData.Mapped drmInitData = null; for (int i = 0; i < moovChildrenSize; i++) { LeafAtom child = moovChildren.get(i); if (child.type == Atom.TYPE_pssh) { if (drmInitData == null) { drmInitData = new DrmInitData.Mapped(MimeTypes.VIDEO_MP4); } byte[] psshData = child.data.data; drmInitData.put(PsshAtomUtil.parseUuid(psshData), psshData); } } if (drmInitData != null) { extractorOutput.drmInitData(drmInitData); } ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex); extendsDefaults = parseTrex(mvex.getLeafAtomOfType(Atom.TYPE_trex).data); track = AtomParsers.parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak), moov.getLeafAtomOfType(Atom.TYPE_mvhd)); Assertions.checkState(track != null); trackOutput.format(track.mediaFormat); }
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 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)); 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; }
/** * Processes the atom payload. If {@link #atomData} is null and the size is at or above the * threshold {@link #RELOAD_MINIMUM_SEEK_DISTANCE}, {@code true} is returned and the caller should * restart loading at the position in {@code positionHolder}. Otherwise, the atom is read/skipped. */ private boolean readAtomPayload(ExtractorInput input, PositionHolder positionHolder) throws IOException, InterruptedException { long atomPayloadSize = atomSize - atomHeaderBytesRead; long atomEndPosition = input.getPosition() + atomPayloadSize; boolean seekRequired = false; if (atomData != null) { input.readFully(atomData.data, atomHeaderBytesRead, (int) atomPayloadSize); if (!containerAtoms.isEmpty()) { containerAtoms.peek().add(new Atom.LeafAtom(atomType, atomData)); } } else { // We don't need the data. Skip or seek, depending on how large the atom is. if (atomPayloadSize < RELOAD_MINIMUM_SEEK_DISTANCE) { input.skipFully((int) atomPayloadSize); } else { positionHolder.position = input.getPosition() + atomPayloadSize; seekRequired = true; } } 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; return false; } else if (!containerAtoms.isEmpty()) { containerAtoms.peek().add(containerAtom); } } enterReadingAtomHeaderState(); return seekRequired; }
/** Updates the stored track metadata to reflect the contents of the specified moov atom. */ private void processMoovAtom(ContainerAtom moov) { List<Mp4Track> tracks = new ArrayList<>(); long earliestSampleOffset = Long.MAX_VALUE; 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)); 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); if (trackSampleTable.sampleCount == 0) { continue; } Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, extractorOutput.track(i)); mp4Track.trackOutput.format(track.mediaFormat); tracks.add(mp4Track); long firstSampleOffset = trackSampleTable.offsets[0]; if (firstSampleOffset < earliestSampleOffset) { earliestSampleOffset = firstSampleOffset; } } this.tracks = tracks.toArray(new Mp4Track[0]); extractorOutput.endTracks(); extractorOutput.seekMap(this); }
private void onContainerAtomRead(ContainerAtom container) { 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); } }
/** * Parses a traf atom (defined in 14496-12). */ private static void parseTraf(Track track, DefaultSampleValues extendsDefaults, ContainerAtom traf, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) { LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt); long decodeTime = tfdtAtom == null ? 0 : parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data); LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd); DefaultSampleValues fragmentHeader = parseTfhd(extendsDefaults, tfhd.data); out.sampleDescriptionIndex = fragmentHeader.sampleDescriptionIndex; LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun); parseTrun(track, fragmentHeader, decodeTime, workaroundFlags, trun.data, out); LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz); if (saiz != null) { TrackEncryptionBox trackEncryptionBox = track.sampleDescriptionEncryptionBoxes[fragmentHeader.sampleDescriptionIndex]; parseSaiz(trackEncryptionBox, saiz.data, out); } LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc); if (senc != null) { parseSenc(senc.data, out); } int childrenSize = traf.leafChildren.size(); for (int i = 0; i < childrenSize; i++) { LeafAtom atom = traf.leafChildren.get(i); if (atom.type == Atom.TYPE_uuid) { parseUuid(atom.data, out, extendedTypeScratch); } } }
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 static void parseMoof(ContainerAtom moof, SparseArray<TrackBundle> trackBundleArray, 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); if (child.type == Atom.TYPE_traf) { parseTraf(child, trackBundleArray, flags, extendedTypeScratch); } } }
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 (atomType == Atom.TYPE_mdat) { if (!haveOutputSeekMap) { extractorOutput.seekMap(SeekMap.UNSEEKABLE); haveOutputSeekMap = true; } if (fragmentRun.sampleEncryptionDataNeedsFill) { parserState = STATE_READING_ENCRYPTION_DATA; } else { parserState = STATE_READING_SAMPLE_START; } return true; } if (shouldParseAtom(atomType)) { if (shouldParseContainerAtom(atomType)) { long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE; containerAtoms.add(new ContainerAtom(atomType, endPosition)); enterReadingAtomHeaderState(); } else { // 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 { // We don't support skipping of atoms that have lengths greater than Integer.MAX_VALUE. Assertions.checkState(atomSize <= Integer.MAX_VALUE); atomData = null; parserState = STATE_READING_ATOM_PAYLOAD; } return true; }
private void onMoofContainerAtomRead(ContainerAtom moof) { fragmentRun.reset(); parseMoof(track, extendsDefaults, moof, fragmentRun, workaroundFlags, extendedTypeScratch); sampleIndex = 0; }
private static void parseMoof(Track track, DefaultSampleValues extendsDefaults, ContainerAtom moof, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) { parseTraf(track, extendsDefaults, moof.getContainerAtomOfType(Atom.TYPE_traf), out, workaroundFlags, extendedTypeScratch); }
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 { List<Mp4Track> tracks = new ArrayList<>(); long earliestSampleOffset = Long.MAX_VALUE; GaplessInfo gaplessInfo = null; Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); if (udta != null) { gaplessInfo = AtomParsers.parseUdta(udta, isQuickTime); } 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), -1, 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); 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; MediaFormat mediaFormat = track.mediaFormat.copyWithMaxInputSize(maxInputSize); if (gaplessInfo != null) { mediaFormat = mediaFormat.copyWithGaplessInfo(gaplessInfo.encoderDelay, gaplessInfo.encoderPadding); } mp4Track.trackOutput.format(mediaFormat); tracks.add(mp4Track); long firstSampleOffset = trackSampleTable.offsets[0]; if (firstSampleOffset < earliestSampleOffset) { earliestSampleOffset = firstSampleOffset; } } this.tracks = tracks.toArray(new Mp4Track[0]); 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.auxiliaryDataPosition = atomPosition; fragment.dataPosition = atomPosition; } } if (atomType == Atom.TYPE_mdat) { currentTrackBundle = null; endOfMdatPosition = atomPosition + atomSize; if (!haveOutputSeekMap) { extractorOutput.seekMap(SeekMap.UNSEEKABLE); 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; }
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException { parseMoof(moof, trackBundles, flags, extendedTypeScratch); }
/** * Parses a traf atom (defined in 14496-12). */ private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray, 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); 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); } LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun); parseTrun(trackBundle, decodeTime, flags, trun.data); 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 childrenSize = traf.leafChildren.size(); for (int i = 0; i < childrenSize; i++) { LeafAtom atom = traf.leafChildren.get(i); if (atom.type == Atom.TYPE_uuid) { parseUuid(atom.data, fragment, extendedTypeScratch); } } }