private static TxxxFrame parseTxxxFrame(ParsableByteArray id3Data, int frameSize) throws UnsupportedEncodingException { int encoding = id3Data.readUnsignedByte(); String charset = getCharsetName(encoding); byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); int descriptionEndIndex = indexOfEos(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); int valueStartIndex = descriptionEndIndex + delimiterLength(encoding); int valueEndIndex = indexOfEos(data, valueStartIndex, encoding); String value = new String(data, valueStartIndex, valueEndIndex - valueStartIndex, charset); return new TxxxFrame(description, value); }
@Override public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, ExtractorOutput output) { // Skip pointer. if (payloadUnitStartIndicator) { int pointerField = data.readUnsignedByte(); data.skipBytes(pointerField); } data.readBytes(patScratch, 3); patScratch.skipBits(12); // table_id (8), section_syntax_indicator (1), '0' (1), reserved (2) int sectionLength = patScratch.readBits(12); // transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1), // section_number (8), last_section_number (8) data.skipBytes(5); int programCount = (sectionLength - 9) / 4; for (int i = 0; i < programCount; i++) { data.readBytes(patScratch, 4); patScratch.skipBits(19); // program_number (16), reserved (3) int pid = patScratch.readBits(13); tsPayloadReaders.put(pid, new PmtReader()); } // Skip CRC_32. }
/** * Parses the edts atom (defined in 14496-12 subsection 8.6.5). * * @param edtsAtom edts (edit box) atom to parse. * @return Pair of edit list durations and edit list media times, or a pair of nulls if they are * not present. */ private static Pair<long[], long[]> parseEdts(Atom.ContainerAtom edtsAtom) { Atom.LeafAtom elst; if (edtsAtom == null || (elst = edtsAtom.getLeafAtomOfType(Atom.TYPE_elst)) == null) { return Pair.create(null, null); } ParsableByteArray elstData = elst.data; elstData.setPosition(Atom.HEADER_SIZE); int fullAtom = elstData.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); int entryCount = elstData.readUnsignedIntToInt(); long[] editListDurations = new long[entryCount]; long[] editListMediaTimes = new long[entryCount]; for (int i = 0; i < entryCount; i++) { editListDurations[i] = version == 1 ? elstData.readUnsignedLongToLong() : elstData.readUnsignedInt(); editListMediaTimes[i] = version == 1 ? elstData.readLong() : elstData.readInt(); int mediaRateInteger = elstData.readShort(); if (mediaRateInteger != 1) { // The extractor does not handle dwell edits (mediaRateInteger == 0). throw new IllegalArgumentException("Unsupported media rate."); } elstData.skipBytes(2); } return Pair.create(editListDurations, editListMediaTimes); }
/** * Locates the next sync word, advancing the position to the byte that immediately follows it. * If a sync word was not located, the position is advanced to the limit. * * @param pesBuffer The buffer whose position should be advanced. * @return True if a sync word position was found. False otherwise. */ private boolean skipToNextSync(ParsableByteArray pesBuffer) { while (pesBuffer.bytesLeft() > 0) { if (!lastByteWas0B) { lastByteWas0B = pesBuffer.readUnsignedByte() == 0x0B; continue; } int secondByte = pesBuffer.readUnsignedByte(); if (secondByte == 0x77) { lastByteWas0B = false; return true; } else { lastByteWas0B = secondByte == 0x0B; } } return false; }
/** * Locates the next sync word, advancing the position to the byte that immediately follows it. * If a sync word was not located, the position is advanced to the limit. * * @param pesBuffer The buffer whose position should be advanced. * @return True if a sync word position was found. False otherwise. */ private boolean skipToNextSync(ParsableByteArray pesBuffer) { byte[] adtsData = pesBuffer.data; int startOffset = pesBuffer.getPosition(); int endOffset = pesBuffer.limit(); for (int i = startOffset; i < endOffset; i++) { boolean byteIsFF = (adtsData[i] & 0xFF) == 0xFF; boolean found = lastByteWasFF && !byteIsFF && (adtsData[i] & 0xF0) == 0xF0; lastByteWasFF = byteIsFF; if (found) { hasCrc = (adtsData[i] & 0x1) == 0; pesBuffer.setPosition(i + 1); // Reset lastByteWasFF for next time. lastByteWasFF = false; return true; } } pesBuffer.setPosition(endOffset); return false; }
private static GaplessInfo parseMetaAtom(ParsableByteArray data) { data.skipBytes(Atom.FULL_HEADER_SIZE); ParsableByteArray ilst = new ParsableByteArray(); while (data.bytesLeft() >= Atom.HEADER_SIZE) { int payloadSize = data.readInt() - Atom.HEADER_SIZE; int atomType = data.readInt(); if (atomType == Atom.TYPE_ilst) { ilst.reset(data.data, data.getPosition() + payloadSize); ilst.setPosition(data.getPosition()); GaplessInfo gaplessInfo = parseIlst(ilst); if (gaplessInfo != null) { return gaplessInfo; } } data.skipBytes(payloadSize); } return null; }
public void testReadIdHeader() throws Exception { byte[] data = TestData.getIdentificationHeaderData(); ParsableByteArray headerData = new ParsableByteArray(data, data.length); VorbisUtil.VorbisIdHeader vorbisIdHeader = VorbisUtil.readVorbisIdentificationHeader(headerData); assertEquals(22050, vorbisIdHeader.sampleRate); assertEquals(0, vorbisIdHeader.version); assertTrue(vorbisIdHeader.framingFlag); assertEquals(2, vorbisIdHeader.channels); assertEquals(512, vorbisIdHeader.blockSize0); assertEquals(1024, vorbisIdHeader.blockSize1); assertEquals(-1, vorbisIdHeader.bitrateMax); assertEquals(-1, vorbisIdHeader.bitrateMin); assertEquals(66666, vorbisIdHeader.bitrateNominal); assertEquals(66666, vorbisIdHeader.getApproximateBitrate()); }
private static Pair<List<byte[]>, Integer> parseAvcCFromParent(ParsableByteArray parent, int position) { parent.setPosition(position + Atom.HEADER_SIZE + 4); // Start of the AVCDecoderConfigurationRecord (defined in 14496-15) int nalUnitLengthFieldLength = (parent.readUnsignedByte() & 0x3) + 1; if (nalUnitLengthFieldLength == 3) { throw new IllegalStateException(); } List<byte[]> initializationData = new ArrayList<>(); // TODO: We should try and parse these using CodecSpecificDataUtil.parseSpsNalUnit, and // expose the AVC profile and level somewhere useful; Most likely in MediaFormat. int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F; for (int j = 0; j < numSequenceParameterSets; j++) { initializationData.add(NalUnitUtil.parseChildNalUnit(parent)); } int numPictureParameterSets = parent.readUnsignedByte(); for (int j = 0; j < numPictureParameterSets; j++) { initializationData.add(NalUnitUtil.parseChildNalUnit(parent)); } return Pair.create(initializationData, nalUnitLengthFieldLength); }
private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position, int size) { int childPosition = position + Atom.HEADER_SIZE; TrackEncryptionBox trackEncryptionBox = null; while (childPosition - position < size) { parent.setPosition(childPosition); int childAtomSize = parent.readInt(); int childAtomType = parent.readInt(); if (childAtomType == Atom.TYPE_frma) { parent.readInt(); // dataFormat. } else if (childAtomType == Atom.TYPE_schm) { parent.skipBytes(4); parent.readInt(); // schemeType. Expect cenc parent.readInt(); // schemeVersion. Expect 0x00010000 } else if (childAtomType == Atom.TYPE_schi) { trackEncryptionBox = parseSchiFromParent(parent, childPosition, childAtomSize); } childPosition += childAtomSize; } return trackEncryptionBox; }
private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) { 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 IllegalStateException("Overriding TrackEncryptionBox parameters is unsupported"); } boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0; int sampleCount = senc.readUnsignedIntToInt(); if (sampleCount != out.length) { throw new IllegalStateException("Length mismatch: " + sampleCount + ", " + out.length); } Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption); out.initEncryptionData(senc.bytesLeft()); out.fillEncryptionData(senc); }
public void consume(long pesTimeUs, ParsableByteArray seiBuffer) { int b; while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) { // Parse payload type. int payloadType = 0; do { b = seiBuffer.readUnsignedByte(); payloadType += b; } while (b == 0xFF); // Parse payload size. int payloadSize = 0; do { b = seiBuffer.readUnsignedByte(); payloadSize += b; } while (b == 0xFF); // Process the payload. if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) { output.sampleData(seiBuffer, payloadSize); output.sampleMetadata(pesTimeUs, C.SAMPLE_FLAG_SYNC, payloadSize, 0, null); } else { seiBuffer.skipBytes(payloadSize); } } }
private int appendSampleEncryptionData(ParsableByteArray sampleEncryptionData) { TrackEncryptionBox encryptionBox = track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex]; int vectorSize = encryptionBox.initializationVectorSize; boolean subsampleEncryption = fragmentRun.sampleHasSubsampleEncryptionTable[sampleIndex]; // Write the signal byte, containing the vector size and the subsample encryption flag. encryptionSignalByte.data[0] = (byte) (vectorSize | (subsampleEncryption ? 0x80 : 0)); encryptionSignalByte.setPosition(0); trackOutput.sampleData(encryptionSignalByte, 1); // Write the vector. trackOutput.sampleData(sampleEncryptionData, vectorSize); // If we don't have subsample encryption data, we're done. if (!subsampleEncryption) { return 1 + vectorSize; } // Write the subsample encryption data. int subsampleCount = sampleEncryptionData.readUnsignedShort(); sampleEncryptionData.skipBytes(-2); int subsampleDataLength = 2 + 6 * subsampleCount; trackOutput.sampleData(sampleEncryptionData, subsampleDataLength); return 1 + vectorSize + subsampleDataLength; }
/** * Parses a udta atom. * * @param udtaAtom The udta (user data) atom to parse. * @param isQuickTime True for QuickTime media. False otherwise. * @return Gapless playback information stored in the user data, or {@code null} if not present. */ public static GaplessInfo parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime) { if (isQuickTime) { // Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and // parse one. return null; } ParsableByteArray udtaData = udtaAtom.data; udtaData.setPosition(Atom.HEADER_SIZE); while (udtaData.bytesLeft() >= Atom.HEADER_SIZE) { int atomSize = udtaData.readInt(); int atomType = udtaData.readInt(); if (atomType == Atom.TYPE_meta) { udtaData.setPosition(udtaData.getPosition() - Atom.HEADER_SIZE); udtaData.setLimit(udtaData.getPosition() + atomSize); return parseMetaAtom(udtaData); } else { udtaData.skipBytes(atomSize - Atom.HEADER_SIZE); } } return null; }
/** * Parses an MS/ACM codec private, returning whether it indicates PCM audio. * * @return True if the codec private indicates PCM audio. False otherwise. * @throws ParserException If a parsing error occurs. */ private static boolean parseMsAcmCodecPrivate(ParsableByteArray buffer) throws ParserException { try { int formatTag = buffer.readLittleEndianUnsignedShort(); if (formatTag == WAVE_FORMAT_PCM) { return true; } else if (formatTag == WAVE_FORMAT_EXTENSIBLE) { buffer.setPosition(WAVE_FORMAT_SIZE + 6); // unionSamples(2), channelMask(4) return buffer.readLong() == WAVE_SUBFORMAT_PCM.getMostSignificantBits() && buffer.readLong() == WAVE_SUBFORMAT_PCM.getLeastSignificantBits(); } else { return false; } } catch (ArrayIndexOutOfBoundsException e) { throw new ParserException("Error parsing MS/ACM codec private"); } }
/** * Builds initialization data for a {@link MediaFormat} from H.264 (AVC) codec private data. * * @return The initialization data for the {@link MediaFormat}. * @throws ParserException If the initialization data could not be built. */ private static Pair<List<byte[]>, Integer> parseAvcCodecPrivate(ParsableByteArray buffer) throws ParserException { try { // TODO: Deduplicate with AtomParsers.parseAvcCFromParent. buffer.setPosition(4); int nalUnitLengthFieldLength = (buffer.readUnsignedByte() & 0x03) + 1; if (nalUnitLengthFieldLength == 3) { throw new ParserException(); } List<byte[]> initializationData = new ArrayList<>(); int numSequenceParameterSets = buffer.readUnsignedByte() & 0x1F; for (int i = 0; i < numSequenceParameterSets; i++) { initializationData.add(NalUnitUtil.parseChildNalUnit(buffer)); } int numPictureParameterSets = buffer.readUnsignedByte(); for (int j = 0; j < numPictureParameterSets; j++) { initializationData.add(NalUnitUtil.parseChildNalUnit(buffer)); } return Pair.create(initializationData, nalUnitLengthFieldLength); } catch (ArrayIndexOutOfBoundsException e) { throw new ParserException("Error parsing AVC codec private"); } }
/** * Parses a saio atom (defined in 14496-12). * * @param saio The saio atom to parse. * @param out The {@link TrackFragment} 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 Object readAmfData(ParsableByteArray data, int type) { switch (type) { case AMF_TYPE_NUMBER: return readAmfDouble(data); case AMF_TYPE_BOOLEAN: return readAmfBoolean(data); case AMF_TYPE_STRING: return readAmfString(data); case AMF_TYPE_OBJECT: return readAmfObject(data); case AMF_TYPE_ECMA_ARRAY: return readAmfEcmaArray(data); case AMF_TYPE_STRICT_ARRAY: return readAmfStrictArray(data); case AMF_TYPE_DATE: return readAmfDate(data); default: return null; } }
/** * @param allocator An {@link Allocator} from which allocations for sample data can be obtained. */ public RollingSampleBuffer(Allocator allocator) { this.allocator = allocator; allocationLength = allocator.getIndividualAllocationLength(); infoQueue = new InfoQueue(); dataQueue = new LinkedBlockingDeque<>(); extrasHolder = new SampleExtrasHolder(); scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE); lastAllocationOffset = allocationLength; }
/** * Parses an ID3 header. * * @param id3Buffer A {@link ParsableByteArray} from which data should be read. * @return The size of ID3 frames in bytes, excluding the header and footer. * @throws ParserException If ID3 file identifier != "ID3". */ private static int parseId3Header(ParsableByteArray id3Buffer) throws ParserException { int id1 = id3Buffer.readUnsignedByte(); int id2 = id3Buffer.readUnsignedByte(); int id3 = id3Buffer.readUnsignedByte(); if (id1 != 'I' || id2 != 'D' || id3 != '3') { throw new ParserException(String.format(Locale.US, "Unexpected ID3 file identifier, expected \"ID3\", actual \"%c%c%c\".", id1, id2, id3)); } id3Buffer.skipBytes(2); // Skip version. int flags = id3Buffer.readUnsignedByte(); int id3Size = id3Buffer.readSynchSafeInt(); // Check if extended header presents. if ((flags & 0x2) != 0) { int extendedHeaderSize = id3Buffer.readSynchSafeInt(); if (extendedHeaderSize > 4) { id3Buffer.skipBytes(extendedHeaderSize - 4); } id3Size -= extendedHeaderSize; } // Check if footer presents. if ((flags & 0x8) != 0) { id3Size -= 10; } return id3Size; }
/** * Appends data to the rolling buffer. * * @param buffer A buffer containing the data to append. * @param length The length of the data to append. */ public void appendData(ParsableByteArray buffer, int length) { while (length > 0) { int thisAppendLength = prepareForAppend(length); buffer.readBytes(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset), thisAppendLength); lastAllocationOffset += thisAppendLength; totalBytesWritten += thisAppendLength; length -= thisAppendLength; } }
public H264Reader(TrackOutput output, SeiReader seiReader, boolean idrKeyframesOnly) { super(output); this.seiReader = seiReader; prefixFlags = new boolean[3]; ifrParserBuffer = (idrKeyframesOnly) ? null : new IfrParserBuffer(); sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128); pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128); sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128); seiWrapper = new ParsableByteArray(); }
@Override public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) { if (startOfPacket) { timeUs = pesTimeUs; } while (data.bytesLeft() > 0) { switch (state) { case STATE_FINDING_SYNC: if (skipToNextSync(data)) { state = STATE_READING_HEADER; headerScratchBytes.data[0] = 0x0B; headerScratchBytes.data[1] = 0x77; bytesRead = 2; } break; case STATE_READING_HEADER: if (continueRead(data, headerScratchBytes.data, HEADER_SIZE)) { parseHeader(); headerScratchBytes.setPosition(0); output.sampleData(headerScratchBytes, HEADER_SIZE); state = STATE_READING_SAMPLE; } break; case STATE_READING_SAMPLE: int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); output.sampleData(data, bytesToRead); bytesRead += bytesToRead; if (bytesRead == sampleSize) { output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); timeUs += frameDurationUs; state = STATE_FINDING_SYNC; } break; } } }
/** * Parses the next valid WebVTT cue in a parsable array, including timestamps, settings and text. * * @param webvttData Parsable WebVTT file data. * @param builder Builder for WebVTT Cues. * @return True if a valid Cue was found, false otherwise. */ /* package */ boolean parseNextValidCue(ParsableByteArray webvttData, WebvttCue.Builder builder) { Matcher cueHeaderMatcher; while ((cueHeaderMatcher = findNextCueHeader(webvttData)) != null) { if (parseCue(cueHeaderMatcher, webvttData, builder, textBuilder)) { return true; } } return false; }
/** * Returns a {@link XingSeeker} for seeking in the stream, if required information is present. * Returns {@code null} if not. On returning, {@code frame}'s position is not specified so the * caller should reset it. * * @param mpegAudioHeader The MPEG audio header associated with the frame. * @param frame The data in this audio frame, with its position set to immediately after the * 'Xing' or 'Info' tag. * @param position The position (byte offset) of the start of this frame in the stream. * @param inputLength The length of the stream in bytes. * @return A {@link XingSeeker} for seeking in the stream, or {@code null} if the required * information is not present. */ public static XingSeeker create(MpegAudioHeader mpegAudioHeader, ParsableByteArray frame, long position, long inputLength) { int samplesPerFrame = mpegAudioHeader.samplesPerFrame; int sampleRate = mpegAudioHeader.sampleRate; long firstFramePosition = position + mpegAudioHeader.frameSize; int flags = frame.readInt(); int frameCount; if ((flags & 0x01) != 0x01 || (frameCount = frame.readUnsignedIntToInt()) == 0) { // If the frame count is missing/invalid, the header can't be used to determine the duration. return null; } long durationUs = Util.scaleLargeTimestamp(frameCount, samplesPerFrame * C.MICROS_PER_SECOND, sampleRate); if ((flags & 0x06) != 0x06) { // If the size in bytes or table of contents is missing, the stream is not seekable. return new XingSeeker(firstFramePosition, durationUs, inputLength); } long sizeBytes = frame.readUnsignedIntToInt(); frame.skipBytes(1); long[] tableOfContents = new long[99]; for (int i = 0; i < 99; i++) { tableOfContents[i] = frame.readUnsignedByte(); } // TODO: Handle encoder delay and padding in 3 bytes offset by xingBase + 213 bytes: // delay = (frame.readUnsignedByte() << 4) + (frame.readUnsignedByte() >> 4); // padding = ((frame.readUnsignedByte() & 0x0F) << 8) + frame.readUnsignedByte(); return new XingSeeker(firstFramePosition, durationUs, inputLength, tableOfContents, sizeBytes, mpegAudioHeader.frameSize); }
/** * Reads the rest of the sample */ private void readSample(ParsableByteArray data) { int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); currentOutput.sampleData(data, bytesToRead); bytesRead += bytesToRead; if (bytesRead == sampleSize) { currentOutput.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); timeUs += currentSampleDuration; setFindingSampleState(); } }
private static void parseUuid(ParsableByteArray uuid, TrackFragment out, byte[] extendedTypeScratch) throws ParserException { uuid.setPosition(Atom.HEADER_SIZE); uuid.readBytes(extendedTypeScratch, 0, 16); // Currently this parser only supports Microsoft's PIFF SampleEncryptionBox. if (!Arrays.equals(extendedTypeScratch, PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE)) { return; } // Except for the extended type, this box is identical to a SENC box. See "Portable encoding of // audio-video objects: The Protected Interoperable File Format (PIFF), John A. Bocharov et al, // Section 5.3.2.1." parseSenc(uuid, 16, out); }
/** * Attempts to read the remainder of the frame. * <p> * If a frame is read in full then true is returned. The frame will have been output, and the * position of the source will have been advanced to the byte that immediately follows the end of * the frame. * <p> * If a frame is not read in full then the position of the source will have been advanced to the * limit, and the method should be called again with the next source to continue the read. * * @param source The source from which to read. */ private void readFrameRemainder(ParsableByteArray source) { int bytesToRead = Math.min(source.bytesLeft(), frameSize - frameBytesRead); output.sampleData(source, bytesToRead); frameBytesRead += bytesToRead; if (frameBytesRead < frameSize) { // We haven't read the whole of the frame yet. return; } output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, frameSize, 0, null); timeUs += frameDurationUs; frameBytesRead = 0; state = STATE_FINDING_HEADER; }
public H265Reader(TrackOutput output, SeiReader seiReader) { super(output); this.seiReader = seiReader; prefixFlags = new boolean[3]; vps = new NalUnitTargetBuffer(VPS_NUT, 128); sps = new NalUnitTargetBuffer(SPS_NUT, 128); pps = new NalUnitTargetBuffer(PPS_NUT, 128); prefixSei = new NalUnitTargetBuffer(PREFIX_SEI_NUT, 128); suffixSei = new NalUnitTargetBuffer(SUFFIX_SEI_NUT, 128); seiWrapper = new ParsableByteArray(); }
@Override public void setUp() throws Exception { MpegAudioHeader xingFrameHeader = new MpegAudioHeader(); MpegAudioHeader.populateHeader(XING_FRAME_HEADER_DATA, xingFrameHeader); seeker = XingSeeker.create(xingFrameHeader, new ParsableByteArray(XING_FRAME_PAYLOAD), XING_FRAME_POSITION, C.UNKNOWN_TIME_US); seekerWithInputLength = XingSeeker.create(xingFrameHeader, new ParsableByteArray(XING_FRAME_PAYLOAD), XING_FRAME_POSITION, INPUT_LENGTH); xingFrameSize = xingFrameHeader.frameSize; }
@Override public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) { if (startOfPacket) { timeUs = pesTimeUs; } while (data.bytesLeft() > 0) { switch (state) { case STATE_FINDING_SYNC: if (skipToNextSync(data)) { bytesRead = 0; state = STATE_READING_HEADER; } break; case STATE_READING_HEADER: int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE; if (continueRead(data, adtsScratch.data, targetLength)) { parseHeader(); bytesRead = 0; state = STATE_READING_SAMPLE; } break; case STATE_READING_SAMPLE: int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); output.sampleData(data, bytesToRead); bytesRead += bytesToRead; if (bytesRead == sampleSize) { output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); timeUs += frameDurationUs; bytesRead = 0; state = STATE_FINDING_SYNC; } break; } } }
/** * Appends the corresponding encryption data to the {@link TrackOutput} contained in the given * {@link TrackBundle}. * * @param trackBundle The {@link TrackBundle} that contains the {@link Track} for which the * Sample encryption data must be output. * @return The number of written bytes. */ private int appendSampleEncryptionData(TrackBundle trackBundle) { TrackFragment trackFragment = trackBundle.fragment; ParsableByteArray sampleEncryptionData = trackFragment.sampleEncryptionData; int sampleDescriptionIndex = trackFragment.header.sampleDescriptionIndex; TrackEncryptionBox encryptionBox = trackFragment.trackEncryptionBox != null ? trackFragment.trackEncryptionBox : trackBundle.track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex]; int vectorSize = encryptionBox.initializationVectorSize; boolean subsampleEncryption = trackFragment .sampleHasSubsampleEncryptionTable[trackBundle.currentSampleIndex]; // Write the signal byte, containing the vector size and the subsample encryption flag. encryptionSignalByte.data[0] = (byte) (vectorSize | (subsampleEncryption ? 0x80 : 0)); encryptionSignalByte.setPosition(0); TrackOutput output = trackBundle.output; output.sampleData(encryptionSignalByte, 1); // Write the vector. output.sampleData(sampleEncryptionData, vectorSize); // If we don't have subsample encryption data, we're done. if (!subsampleEncryption) { return 1 + vectorSize; } // Write the subsample encryption data. int subsampleCount = sampleEncryptionData.readUnsignedShort(); sampleEncryptionData.skipBytes(-2); int subsampleDataLength = 2 + 6 * subsampleCount; output.sampleData(sampleEncryptionData, subsampleDataLength); return 1 + vectorSize + subsampleDataLength; }
/** * Parses a mvhd atom (defined in 14496-12), returning the timescale for the movie. * * @param mvhd Contents of the mvhd atom to be parsed. * @return Timescale for the movie. */ private static long parseMvhd(ParsableByteArray mvhd) { mvhd.setPosition(Atom.HEADER_SIZE); int fullAtom = mvhd.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); mvhd.skipBytes(version == 0 ? 8 : 16); return mvhd.readUnsignedInt(); }
/** * Parses an mdhd atom (defined in 14496-12). * * @param mdhd The mdhd atom to parse. * @return The media timescale, defined as the number of time units that pass in one second. */ private static long parseMdhd(ParsableByteArray mdhd) { mdhd.setPosition(Atom.HEADER_SIZE); int fullAtom = mdhd.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); mdhd.skipBytes(version == 0 ? 8 : 16); return mdhd.readUnsignedInt(); }
private static StsdDataHolder parseStsd(ParsableByteArray stsd, long durationUs) { stsd.setPosition(Atom.FULL_HEADER_SIZE); int numberOfEntries = stsd.readInt(); StsdDataHolder holder = new StsdDataHolder(numberOfEntries); for (int i = 0; i < numberOfEntries; i++) { int childStartPosition = stsd.getPosition(); int childAtomSize = stsd.readInt(); Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); int childAtomType = stsd.readInt(); if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3 || childAtomType == Atom.TYPE_encv || childAtomType == Atom.TYPE_mp4v || childAtomType == Atom.TYPE_hvc1 || childAtomType == Atom.TYPE_hev1 || childAtomType == Atom.TYPE_s263) { parseVideoSampleEntry(stsd, childStartPosition, childAtomSize, durationUs, holder, i); } else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca || childAtomType == Atom.TYPE_ac_3) { parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, durationUs, holder, i); } else if (childAtomType == Atom.TYPE_TTML) { holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TTML, durationUs); } else if (childAtomType == Atom.TYPE_tx3g) { holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TX3G, durationUs); } stsd.setPosition(childStartPosition + childAtomSize); } return holder; }
private static TextInformationFrame parseTextInformationFrame(ParsableByteArray id3Data, int frameSize, String id) throws UnsupportedEncodingException { int encoding = id3Data.readUnsignedByte(); String charset = getCharsetName(encoding); byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); int descriptionEndIndex = indexOfEos(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); return new TextInformationFrame(id, description); }
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); }
/** * Builds initialization data for a {@link MediaFormat} from H.264 (AVC) codec private data. * * @return The AvcSequenceHeader data needed to initialize the video codec. * @throws ParserException If the initialization data could not be built. */ private AvcSequenceHeaderData parseAvcCodecPrivate(ParsableByteArray buffer) throws ParserException { // TODO: Deduplicate with AtomParsers.parseAvcCFromParent. buffer.setPosition(4); int nalUnitLengthFieldLength = (buffer.readUnsignedByte() & 0x03) + 1; Assertions.checkState(nalUnitLengthFieldLength != 3); List<byte[]> initializationData = new ArrayList<>(); int numSequenceParameterSets = buffer.readUnsignedByte() & 0x1F; for (int i = 0; i < numSequenceParameterSets; i++) { initializationData.add(NalUnitUtil.parseChildNalUnit(buffer)); } int numPictureParameterSets = buffer.readUnsignedByte(); for (int j = 0; j < numPictureParameterSets; j++) { initializationData.add(NalUnitUtil.parseChildNalUnit(buffer)); } float pixelWidthAspectRatio = 1; int width = MediaFormat.NO_VALUE; int height = MediaFormat.NO_VALUE; if (numSequenceParameterSets > 0) { // Parse the first sequence parameter set to obtain pixelWidthAspectRatio. ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0)); // Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte). spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1)); NalUnitUtil.SpsData sps = NalUnitUtil.parseSpsNalUnit(spsDataBitArray); width = sps.width; height = sps.height; pixelWidthAspectRatio = sps.pixelWidthAspectRatio; } return new AvcSequenceHeaderData(initializationData, nalUnitLengthFieldLength, width, height, pixelWidthAspectRatio); }
/** * 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); }
@Override public void consume(ParsableByteArray data) { while (data.bytesLeft() > 0) { switch (state) { case STATE_FINDING_SYNC: if (skipToNextSync(data)) { bytesRead = SYNC_VALUE_SIZE; state = STATE_READING_HEADER; } break; case STATE_READING_HEADER: if (continueRead(data, headerScratchBytes.data, HEADER_SIZE)) { parseHeader(); headerScratchBytes.setPosition(0); output.sampleData(headerScratchBytes, HEADER_SIZE); state = STATE_READING_SAMPLE; } break; case STATE_READING_SAMPLE: int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); output.sampleData(data, bytesToRead); bytesRead += bytesToRead; if (bytesRead == sampleSize) { output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); timeUs += sampleDurationUs; state = STATE_FINDING_SYNC; } break; } } }
/** * 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(); }