/** Parses a sidx atom (defined in 14496-12). */ private static ChunkIndex parseSidx(ParsableByteArray atom, long inputPosition) throws ParserException { atom.setPosition(Atom.HEADER_SIZE); int fullAtom = atom.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); atom.skipBytes(4); long timescale = atom.readUnsignedInt(); long earliestPresentationTime; long offset = inputPosition; if (version == 0) { earliestPresentationTime = atom.readUnsignedInt(); offset += atom.readUnsignedInt(); } else { earliestPresentationTime = atom.readUnsignedLongToLong(); offset += atom.readUnsignedLongToLong(); } atom.skipBytes(2); int referenceCount = atom.readUnsignedShort(); int[] sizes = new int[referenceCount]; long[] offsets = new long[referenceCount]; long[] durationsUs = new long[referenceCount]; long[] timesUs = new long[referenceCount]; long time = earliestPresentationTime; long timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale); for (int i = 0; i < referenceCount; i++) { int firstInt = atom.readInt(); int type = 0x80000000 & firstInt; if (type != 0) { throw new ParserException("Unhandled indirect reference"); } long referenceDuration = atom.readUnsignedInt(); sizes[i] = 0x7fffffff & firstInt; offsets[i] = offset; // Calculate time and duration values such that any rounding errors are consistent. i.e. That // timesUs[i] + durationsUs[i] == timesUs[i + 1]. timesUs[i] = timeUs; time += referenceDuration; timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale); durationsUs[i] = timeUs - timesUs[i]; atom.skipBytes(4); offset += sizes[i]; } return new ChunkIndex(sizes, offsets, durationsUs, timesUs); }
private static void parseSaiz( TrackEncryptionBox encryptionBox, ParsableByteArray saiz, TrackFragment out) throws ParserException { int vectorSize = encryptionBox.initializationVectorSize; saiz.setPosition(Atom.HEADER_SIZE); int fullAtom = saiz.readInt(); int flags = Atom.parseFullAtomFlags(fullAtom); if ((flags & 0x01) == 1) { saiz.skipBytes(8); } int defaultSampleInfoSize = saiz.readUnsignedByte(); int sampleCount = saiz.readUnsignedIntToInt(); if (sampleCount != out.length) { throw new ParserException("Length mismatch: " + sampleCount + ", " + out.length); } int totalSize = 0; if (defaultSampleInfoSize == 0) { boolean[] sampleHasSubsampleEncryptionTable = out.sampleHasSubsampleEncryptionTable; for (int i = 0; i < sampleCount; i++) { int sampleInfoSize = saiz.readUnsignedByte(); totalSize += sampleInfoSize; sampleHasSubsampleEncryptionTable[i] = sampleInfoSize > vectorSize; } } else { boolean subsampleEncryption = defaultSampleInfoSize > vectorSize; totalSize += defaultSampleInfoSize * sampleCount; Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption); } out.initEncryptionData(totalSize); }
/** Parses a trex atom (defined in 14496-12). */ private static DefaultSampleValues parseTrex(ParsableByteArray trex) { trex.setPosition(Atom.FULL_HEADER_SIZE + 4); int defaultSampleDescriptionIndex = trex.readUnsignedIntToInt() - 1; int defaultSampleDuration = trex.readUnsignedIntToInt(); int defaultSampleSize = trex.readUnsignedIntToInt(); int defaultSampleFlags = trex.readInt(); return new DefaultSampleValues( defaultSampleDescriptionIndex, defaultSampleDuration, defaultSampleSize, defaultSampleFlags); }
/** * 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) throws ParserException { 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 ParserException("Unexpected saio entry count: " + entryCount); } int version = Atom.parseFullAtomVersion(fullAtom); out.auxiliaryDataPosition += version == 0 ? saio.readUnsignedInt() : saio.readUnsignedLongToLong(); }
private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) throws ParserException { senc.setPosition(Atom.HEADER_SIZE + offset); int fullAtom = senc.readInt(); int flags = Atom.parseFullAtomFlags(fullAtom); if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) { // TODO: Implement this. throw new ParserException("Overriding TrackEncryptionBox parameters is unsupported."); } boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0; int sampleCount = senc.readUnsignedIntToInt(); if (sampleCount != out.length) { throw new ParserException("Length mismatch: " + sampleCount + ", " + out.length); } Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption); out.initEncryptionData(senc.bytesLeft()); out.fillEncryptionData(senc); }
/** * Parses a tfhd atom (defined in 14496-12). * * @param extendsDefaults Default sample values from the trex atom. * @param tfhd The tfhd atom to parse. * @param out The track fragment to populate with data from the tfhd atom. */ 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) { long baseDataPosition = tfhd.readUnsignedLongToLong(); out.dataPosition = baseDataPosition; out.auxiliaryDataPosition = baseDataPosition; } int defaultSampleDescriptionIndex = ((flags & 0x02 /* default_sample_description_index_present */) != 0) ? tfhd.readUnsignedIntToInt() - 1 : extendsDefaults.sampleDescriptionIndex; int defaultSampleDuration = ((flags & 0x08 /* default_sample_duration_present */) != 0) ? tfhd.readUnsignedIntToInt() : extendsDefaults.duration; int defaultSampleSize = ((flags & 0x10 /* default_sample_size_present */) != 0) ? tfhd.readUnsignedIntToInt() : extendsDefaults.size; int defaultSampleFlags = ((flags & 0x20 /* default_sample_flags_present */) != 0) ? tfhd.readUnsignedIntToInt() : extendsDefaults.flags; out.header = new DefaultSampleValues( defaultSampleDescriptionIndex, defaultSampleDuration, defaultSampleSize, defaultSampleFlags); }
/** * Parses a trun atom (defined in 14496-12). * * @param track The corresponding track. * @param defaultSampleValues Default sample values. * @param decodeTime The decode time. * @param trun The trun atom to parse. * @param out The {@TrackFragment} into which parsed data should be placed. */ private static void parseTrun( Track track, DefaultSampleValues defaultSampleValues, long decodeTime, int workaroundFlags, ParsableByteArray trun, TrackFragment out) { trun.setPosition(Atom.HEADER_SIZE); int fullAtom = trun.readInt(); int flags = Atom.parseFullAtomFlags(fullAtom); int sampleCount = trun.readUnsignedIntToInt(); if ((flags & 0x01 /* data_offset_present */) != 0) { out.dataPosition += trun.readInt(); } boolean firstSampleFlagsPresent = (flags & 0x04 /* first_sample_flags_present */) != 0; int firstSampleFlags = defaultSampleValues.flags; if (firstSampleFlagsPresent) { firstSampleFlags = trun.readUnsignedIntToInt(); } boolean sampleDurationsPresent = (flags & 0x100 /* sample_duration_present */) != 0; boolean sampleSizesPresent = (flags & 0x200 /* sample_size_present */) != 0; boolean sampleFlagsPresent = (flags & 0x400 /* sample_flags_present */) != 0; boolean sampleCompositionTimeOffsetsPresent = (flags & 0x800 /* sample_composition_time_offsets_present */) != 0; out.initTables(sampleCount); int[] sampleSizeTable = out.sampleSizeTable; int[] sampleCompositionTimeOffsetTable = out.sampleCompositionTimeOffsetTable; long[] sampleDecodingTimeTable = out.sampleDecodingTimeTable; boolean[] sampleIsSyncFrameTable = out.sampleIsSyncFrameTable; long timescale = track.timescale; long cumulativeTime = decodeTime; boolean workaroundEveryVideoFrameIsSyncFrame = track.type == Track.TYPE_vide && (workaroundFlags & WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0; for (int i = 0; i < sampleCount; i++) { // Use trun values if present, otherwise tfhd, otherwise trex. int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt() : defaultSampleValues.duration; int sampleSize = sampleSizesPresent ? trun.readUnsignedIntToInt() : defaultSampleValues.size; int sampleFlags = (i == 0 && firstSampleFlagsPresent) ? firstSampleFlags : sampleFlagsPresent ? trun.readInt() : defaultSampleValues.flags; if (sampleCompositionTimeOffsetsPresent) { // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in // version 0 trun boxes, however a significant number of streams violate the spec and use // signed integers instead. It's safe to always parse sample offsets as signed integers // here, because unsigned integers will still be parsed correctly (unless their top bit is // set, which is never true in practice because sample offsets are always small). int sampleOffset = trun.readInt(); sampleCompositionTimeOffsetTable[i] = (int) ((sampleOffset * 1000) / timescale); } else { sampleCompositionTimeOffsetTable[i] = 0; } sampleDecodingTimeTable[i] = Util.scaleLargeTimestamp(cumulativeTime, 1000, timescale); sampleSizeTable[i] = sampleSize; sampleIsSyncFrameTable[i] = ((sampleFlags >> 16) & 0x1) == 0 && (!workaroundEveryVideoFrameIsSyncFrame || i == 0); cumulativeTime += sampleDuration; } }
/** * Parses a tfdt atom (defined in 14496-12). * * @return baseMediaDecodeTime The sum of the decode durations of all earlier samples in the * media, expressed in the media's timescale. */ private static long parseTfdt(ParsableByteArray tfdt) { tfdt.setPosition(Atom.HEADER_SIZE); int fullAtom = tfdt.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt(); }
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. fragmentRun.auxiliaryDataPosition = atomPosition; fragmentRun.dataPosition = atomPosition; } if (atomType == Atom.TYPE_mdat) { endOfMdatPosition = atomPosition + atomSize; 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 (shouldParseContainerAtom(atomType)) { long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE; 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. checkState(atomHeaderBytesRead == Atom.HEADER_SIZE); 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. checkState(atomSize <= Integer.MAX_VALUE); atomData = null; parserState = STATE_READING_ATOM_PAYLOAD; } return true; }