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); }
/** * 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; 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)); } return Pair.create(initializationData, nalUnitLengthFieldLength); } catch (ArrayIndexOutOfBoundsException e) { throw new ParserException("Error parsing AVC 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"); } }
@Override public void seek() { seiReader.seek(); NalUnitUtil.clearPrefixFlags(prefixFlags); sps.reset(); pps.reset(); sei.reset(); if (ifrParserBuffer != null) { ifrParserBuffer.reset(); } foundFirstSample = false; totalBytesWritten = 0; }
private void feedNalUnitTargetEnd(long pesTimeUs, int discardPadding) { sps.endNalUnit(discardPadding); pps.endNalUnit(discardPadding); if (sei.endNalUnit(discardPadding)) { int unescapedLength = NalUnitUtil.unescapeStream(sei.nalData, sei.nalLength); seiWrapper.reset(sei.nalData, unescapedLength); seiWrapper.setPosition(4); // NAL prefix and nal_unit() header. seiReader.consume(seiWrapper, pesTimeUs, true); } }
@Override public void seek() { seiReader.seek(); NalUnitUtil.clearPrefixFlags(prefixFlags); vps.reset(); sps.reset(); pps.reset(); prefixSei.reset(); suffixSei.reset(); foundFirstSample = false; totalBytesWritten = 0; }
public Mp4Extractor() { atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); containerAtoms = new Stack<>(); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); enterReadingAtomHeaderState(); }
/** * @param workaroundFlags Flags to allow parsing of faulty streams. * {@link #WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME} is currently the only flag defined. */ public FragmentedMp4Extractor(int workaroundFlags) { this.workaroundFlags = workaroundFlags; atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); encryptionSignalByte = new ParsableByteArray(1); extendedTypeScratch = new byte[16]; containerAtoms = new Stack<>(); fragmentRun = new TrackFragment(); enterReadingAtomHeaderState(); }
WebmExtractor(EbmlReader reader) { this.reader = reader; this.reader.init(new InnerEbmlReaderOutput()); varintReader = new VarintReader(); scratch = new ParsableByteArray(4); vorbisNumPageSamples = new ParsableByteArray(ByteBuffer.allocate(4).putInt(-1).array()); seekEntryIdBytes = new ParsableByteArray(4); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); sampleStrippedBytes = new ParsableByteArray(); }
@Override public void seek() { NalUnitUtil.clearPrefixFlags(prefixFlags); csdBuffer.reset(); pesPtsUsAvailable = false; foundFirstFrameInGroup = false; totalBytesWritten = 0; }
@Override public void seek() { NalUnitUtil.clearPrefixFlags(prefixFlags); sps.reset(); pps.reset(); sei.reset(); sampleReader.reset(); totalBytesWritten = 0; }
@Override public void seek() { NalUnitUtil.clearPrefixFlags(prefixFlags); vps.reset(); sps.reset(); pps.reset(); prefixSei.reset(); suffixSei.reset(); sampleReader.reset(); totalBytesWritten = 0; }
private static AvcCData 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<>(); float pixelWidthAspectRatio = 1; 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)); } 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)); pixelWidthAspectRatio = NalUnitUtil.parseSpsNalUnit(spsDataBitArray).pixelWidthAspectRatio; } return new AvcCData(initializationData, nalUnitLengthFieldLength, pixelWidthAspectRatio); }
/** * @param flags Flags to allow parsing of faulty streams. * @param sideloadedTrack Sideloaded track information, in the case that the extractor * will not receive a moov box in the input data. */ public FragmentedMp4Extractor(int flags, Track sideloadedTrack) { this.sideloadedTrack = sideloadedTrack; this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0); atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); encryptionSignalByte = new ParsableByteArray(1); extendedTypeScratch = new byte[16]; containerAtoms = new Stack<>(); trackBundles = new SparseArray<>(); enterReadingAtomHeaderState(); }
/** * 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); }
WebmExtractor(EbmlReader reader) { this.reader = reader; this.reader.init(new InnerEbmlReaderOutput()); varintReader = new VarintReader(); tracks = new SparseArray<>(); scratch = new ParsableByteArray(4); vorbisNumPageSamples = new ParsableByteArray(ByteBuffer.allocate(4).putInt(-1).array()); seekEntryIdBytes = new ParsableByteArray(4); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); sampleStrippedBytes = new ParsableByteArray(); subripSample = new ParsableByteArray(); }
@Override public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) { while (data.bytesLeft() > 0) { int offset = data.getPosition(); int limit = data.limit(); byte[] dataArray = data.data; // Append the data to the buffer. totalBytesWritten += data.bytesLeft(); output.sampleData(data, data.bytesLeft()); // Scan the appended data, processing NAL units as they are encountered while (offset < limit) { int nextNalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags); if (nextNalUnitOffset < limit) { // We've seen the start of a NAL unit. // This is the length to the start of the unit. It may be negative if the NAL unit // actually started in previously consumed data. int lengthToNalUnit = nextNalUnitOffset - offset; if (lengthToNalUnit > 0) { feedNalUnitTargetBuffersData(dataArray, offset, nextNalUnitOffset); } int nalUnitType = NalUnitUtil.getNalUnitType(dataArray, nextNalUnitOffset); int bytesWrittenPastNalUnit = limit - nextNalUnitOffset; switch (nalUnitType) { case NAL_UNIT_TYPE_IDR: isKeyframe = true; break; case NAL_UNIT_TYPE_AUD: if (foundFirstSample) { if (ifrParserBuffer != null && ifrParserBuffer.isCompleted()) { int sliceType = ifrParserBuffer.getSliceType(); isKeyframe |= (sliceType == FRAME_TYPE_I || sliceType == FRAME_TYPE_ALL_I); ifrParserBuffer.reset(); } if (isKeyframe && !hasOutputFormat && sps.isCompleted() && pps.isCompleted()) { parseMediaFormat(sps, pps); } int flags = isKeyframe ? C.SAMPLE_FLAG_SYNC : 0; int size = (int) (totalBytesWritten - samplePosition) - bytesWrittenPastNalUnit; output.sampleMetadata(sampleTimeUs, flags, size, bytesWrittenPastNalUnit, null); } foundFirstSample = true; samplePosition = totalBytesWritten - bytesWrittenPastNalUnit; sampleTimeUs = pesTimeUs; isKeyframe = false; break; } // If the length to the start of the unit is negative then we wrote too many bytes to the // NAL buffers. Discard the excess bytes when notifying that the unit has ended. feedNalUnitTargetEnd(pesTimeUs, lengthToNalUnit < 0 ? -lengthToNalUnit : 0); // Notify the start of the next NAL unit. feedNalUnitTargetBuffersStart(nalUnitType); // Continue scanning the data. offset = nextNalUnitOffset + 3; } else { feedNalUnitTargetBuffersData(dataArray, offset, limit); offset = limit; } } } }
@Override public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) { while (data.bytesLeft() > 0) { int offset = data.getPosition(); int limit = data.limit(); byte[] dataArray = data.data; // Append the data to the buffer. totalBytesWritten += data.bytesLeft(); output.sampleData(data, data.bytesLeft()); // Scan the appended data, processing NAL units as they are encountered while (offset < limit) { int nextNalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags); if (nextNalUnitOffset < limit) { // We've seen the start of a NAL unit. // This is the length to the start of the unit. It may be negative if the NAL unit // actually started in previously consumed data. int lengthToNalUnit = nextNalUnitOffset - offset; if (lengthToNalUnit > 0) { feedNalUnitTargetBuffersData(dataArray, offset, nextNalUnitOffset); } int nalUnitType = NalUnitUtil.getH265NalUnitType(dataArray, nextNalUnitOffset); int bytesWrittenPastNalUnit = limit - nextNalUnitOffset; if (isFirstSliceSegmentInPic(dataArray, nextNalUnitOffset)) { if (foundFirstSample) { if (isKeyframe && !hasOutputFormat && vps.isCompleted() && sps.isCompleted() && pps.isCompleted()) { parseMediaFormat(vps, sps, pps); } int flags = isKeyframe ? C.SAMPLE_FLAG_SYNC : 0; int size = (int) (totalBytesWritten - samplePosition) - bytesWrittenPastNalUnit; output.sampleMetadata(sampleTimeUs, flags, size, bytesWrittenPastNalUnit, null); } foundFirstSample = true; samplePosition = totalBytesWritten - bytesWrittenPastNalUnit; sampleTimeUs = pesTimeUs; isKeyframe = isRandomAccessPoint(nalUnitType); } // If the length to the start of the unit is negative then we wrote too many bytes to the // NAL buffers. Discard the excess bytes when notifying that the unit has ended. feedNalUnitTargetEnd(pesTimeUs, lengthToNalUnit < 0 ? -lengthToNalUnit : 0); // Notify the start of the next NAL unit. feedNalUnitTargetBuffersStart(nalUnitType); // Continue scanning the data. offset = nextNalUnitOffset + 3; } else { feedNalUnitTargetBuffersData(dataArray, offset, limit); offset = limit; } } } }
@Override public void consume(ParsableByteArray data) { while (data.bytesLeft() > 0) { int offset = data.getPosition(); int limit = data.limit(); byte[] dataArray = data.data; // Append the data to the buffer. totalBytesWritten += data.bytesLeft(); output.sampleData(data, data.bytesLeft()); int searchOffset = offset; while (true) { int startCodeOffset = NalUnitUtil.findNalUnit(dataArray, searchOffset, limit, prefixFlags); if (startCodeOffset == limit) { // We've scanned to the end of the data without finding another start code. if (!hasOutputFormat) { csdBuffer.onData(dataArray, offset, limit); } return; } // We've found a start code with the following value. int startCodeValue = data.data[startCodeOffset + 3] & 0xFF; if (!hasOutputFormat) { // This is the number of bytes from the current offset to the start of the next start // code. It may be negative if the start code started in the previously consumed data. int lengthToStartCode = startCodeOffset - offset; if (lengthToStartCode > 0) { csdBuffer.onData(dataArray, offset, startCodeOffset); } // This is the number of bytes belonging to the next start code that have already been // passed to csdDataTargetBuffer. int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0; if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) { // The csd data is complete, so we can parse and output the media format. Pair<MediaFormat, Long> result = parseCsdBuffer(csdBuffer); output.format(result.first); frameDurationUs = result.second; hasOutputFormat = true; } } if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) { int bytesWrittenPastStartCode = limit - startCodeOffset; if (foundFirstFrameInGroup) { int flags = isKeyframe ? C.SAMPLE_FLAG_SYNC : 0; int size = (int) (totalBytesWritten - framePosition) - bytesWrittenPastStartCode; output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null); isKeyframe = false; } if (startCodeValue == START_GROUP) { foundFirstFrameInGroup = false; isKeyframe = true; } else /* startCode == START_PICTURE */ { frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs); framePosition = totalBytesWritten - bytesWrittenPastStartCode; pesPtsUsAvailable = false; foundFirstFrameInGroup = true; } } offset = startCodeOffset; searchOffset = offset + 3; } } }
@Override public void consume(ParsableByteArray data) { while (data.bytesLeft() > 0) { int offset = data.getPosition(); int limit = data.limit(); byte[] dataArray = data.data; // Append the data to the buffer. totalBytesWritten += data.bytesLeft(); output.sampleData(data, data.bytesLeft()); // Scan the appended data, processing NAL units as they are encountered while (true) { int nalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags); if (nalUnitOffset == limit) { // We've scanned to the end of the data without finding the start of another NAL unit. nalUnitData(dataArray, offset, limit); return; } // We've seen the start of a NAL unit of the following type. int nalUnitType = NalUnitUtil.getNalUnitType(dataArray, nalUnitOffset); // This is the number of bytes from the current offset to the start of the next NAL unit. // It may be negative if the NAL unit started in the previously consumed data. int lengthToNalUnit = nalUnitOffset - offset; if (lengthToNalUnit > 0) { nalUnitData(dataArray, offset, nalUnitOffset); } int bytesWrittenPastPosition = limit - nalUnitOffset; long absolutePosition = totalBytesWritten - bytesWrittenPastPosition; // Indicate the end of the previous NAL unit. If the length to the start of the next unit // is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes // when notifying that the unit has ended. endNalUnit(absolutePosition, bytesWrittenPastPosition, lengthToNalUnit < 0 ? -lengthToNalUnit : 0, pesTimeUs); // Indicate the start of the next NAL unit. startNalUnit(absolutePosition, nalUnitType, pesTimeUs); // Continue scanning the data. offset = nalUnitOffset + 3; } } }
private static ParsableBitArray unescape(NalUnitTargetBuffer buffer) { int length = NalUnitUtil.unescapeStream(buffer.nalData, buffer.nalLength); ParsableBitArray bitArray = new ParsableBitArray(buffer.nalData, length); bitArray.skipBits(32); // NAL header return bitArray; }
public void putSps(NalUnitUtil.SpsData spsData) { sps.append(spsData.seqParameterSetId, spsData); }
public void putPps(NalUnitUtil.PpsData ppsData) { pps.append(ppsData.picParameterSetId, ppsData); }
@Override public void consume(ParsableByteArray data) { while (data.bytesLeft() > 0) { int offset = data.getPosition(); int limit = data.limit(); byte[] dataArray = data.data; // Append the data to the buffer. totalBytesWritten += data.bytesLeft(); output.sampleData(data, data.bytesLeft()); // Scan the appended data, processing NAL units as they are encountered while (offset < limit) { int nalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags); if (nalUnitOffset == limit) { // We've scanned to the end of the data without finding the start of another NAL unit. nalUnitData(dataArray, offset, limit); return; } // We've seen the start of a NAL unit of the following type. int nalUnitType = NalUnitUtil.getH265NalUnitType(dataArray, nalUnitOffset); // This is the number of bytes from the current offset to the start of the next NAL unit. // It may be negative if the NAL unit started in the previously consumed data. int lengthToNalUnit = nalUnitOffset - offset; if (lengthToNalUnit > 0) { nalUnitData(dataArray, offset, nalUnitOffset); } int bytesWrittenPastPosition = limit - nalUnitOffset; long absolutePosition = totalBytesWritten - bytesWrittenPastPosition; // Indicate the end of the previous NAL unit. If the length to the start of the next unit // is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes // when notifying that the unit has ended. endNalUnit(absolutePosition, bytesWrittenPastPosition, lengthToNalUnit < 0 ? -lengthToNalUnit : 0, pesTimeUs); // Indicate the start of the next NAL unit. startNalUnit(absolutePosition, bytesWrittenPastPosition, nalUnitType, pesTimeUs); // Continue scanning the data. offset = nalUnitOffset + 3; } } }
/** * @param output A {@link TrackOutput} to which samples should be written. */ public VideoTagPayloadReader(TrackOutput output) { super(output); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); }
/** * Returns whether the NAL unit in {@code data} starting at {@code offset} contains the first * slice in a picture. * * @param data The data to read. * @param offset The start offset of a NAL unit. Must lie between {@code -3} (inclusive) and * {@code data.length - 3} (exclusive). * @return Whether the NAL unit contains the first slice in a picture. */ public static boolean isFirstSliceSegmentInPic(byte[] data, int offset) { int nalUnitType = NalUnitUtil.getH265NalUnitType(data, offset); // Check the flag in NAL units that contain a slice_segment_layer_rbsp RBSP. if ((nalUnitType <= RASL_R) || (nalUnitType >= BLA_W_LP && nalUnitType <= CRA_NUT)) { return (data[offset + 5] & 0x80) != 0; } return false; }