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(); }
public MpegAudioReader(TrackOutput output) { super(output); state = STATE_FINDING_HEADER; // The first byte of an MPEG Audio frame header is always 0xFF. headerScratch = new ParsableByteArray(4); headerScratch.data[0] = (byte) 0xFF; header = new MpegAudioHeader(); }
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(); }
/** * Drains as much buffered data as possible up to {@code length} bytes to {@code trackOutput}. * * @param trackOutput Track output to populate with up to {@code length} bytes of sample data. * @param length Number of bytes to try to read from the buffer. * @return The number of buffered bytes written. */ public int drainToOutput(TrackOutput trackOutput, int length) { if (length == 0) { return 0; } buffer.setPosition(readPosition); int bytesToDrain = Math.min(writePosition - readPosition, length); trackOutput.sampleData(buffer, bytesToDrain); readPosition += bytesToDrain; return bytesToDrain; }
/** * Outputs up to {@code length} bytes of sample data to {@code output}, consisting of either * {@link #sampleStrippedBytes} or data read from {@code input}. */ private int readToOutput(ExtractorInput input, TrackOutput output, int length) throws IOException, InterruptedException { int bytesRead; int strippedBytesLeft = sampleStrippedBytes.bytesLeft(); if (strippedBytesLeft > 0) { bytesRead = Math.min(length, strippedBytesLeft); output.sampleData(sampleStrippedBytes, bytesRead); } else { bytesRead = output.sampleData(input, length, false); } sampleBytesRead += bytesRead; sampleBytesWritten += bytesRead; return bytesRead; }
private TrackOutput buildTrackOutput(long subsampleOffsetUs) { TrackOutput trackOutput = output.track(0); trackOutput.format(MediaFormat.createTextFormat("id", MimeTypes.TEXT_VTT, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, "en", subsampleOffsetUs)); output.endTracks(); return trackOutput; }
/** * @param output A {@link TrackOutput} to which H.264 samples should be written. * @param seiReader A reader for EIA-608 samples in SEI NAL units. * @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as * synchronization samples (key-frames). * @param detectAccessUnits Whether to split the input stream into access units (samples) based on * slice headers. Pass {@code false} if the stream contains access unit delimiters (AUDs). */ public H264Reader(TrackOutput output, SeiReader seiReader, boolean allowNonIdrKeyframes, boolean detectAccessUnits) { super(output); this.seiReader = seiReader; prefixFlags = new boolean[3]; sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits); 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(); }
public SampleReader(TrackOutput output, boolean allowNonIdrKeyframes, boolean detectAccessUnits) { this.output = output; this.allowNonIdrKeyframes = allowNonIdrKeyframes; this.detectAccessUnits = detectAccessUnits; sps = new SparseArray<>(); pps = new SparseArray<>(); previousSliceHeader = new SliceHeaderData(); sliceHeader = new SliceHeaderData(); scratch = new ParsableBitArray(); buffer = new byte[DEFAULT_BUFFER_SIZE]; reset(); }
/** * Constructs a new reader for DTS elementary streams. * * @param output Track output for extracted samples. */ public DtsReader(TrackOutput output) { super(output); headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]); headerScratchBytes.data[0] = (byte) ((SYNC_VALUE >> 24) & 0xFF); headerScratchBytes.data[1] = (byte) ((SYNC_VALUE >> 16) & 0xFF); headerScratchBytes.data[2] = (byte) ((SYNC_VALUE >> 8) & 0xFF); headerScratchBytes.data[3] = (byte) (SYNC_VALUE & 0xFF); state = STATE_FINDING_SYNC; }
/** * Constructs a new reader for (E-)AC-3 elementary streams. * * @param output Track output for extracted samples. * @param isEac3 Whether the stream is E-AC-3 (ETSI TS 102 366 Annex E). Specify {@code false} to * parse sample headers as AC-3. */ public Ac3Reader(TrackOutput output, boolean isEac3) { super(output); this.isEac3 = isEac3; headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]); headerScratchBytes = new ParsableByteArray(headerScratchBits.data); state = STATE_FINDING_SYNC; }
/** * @param output A {@link TrackOutput} to which H.265 samples should be written. * @param seiReader A reader for EIA-608 samples in SEI NAL units. */ 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); sampleReader = new SampleReader(output); seiWrapper = new ParsableByteArray(); }
/** * @param output A {@link TrackOutput} to which AAC samples should be written. * @param id3Output A {@link TrackOutput} to which ID3 samples should be written. */ public AdtsReader(TrackOutput output, TrackOutput id3Output) { super(output); this.id3Output = id3Output; id3Output.format(MediaFormat.createId3Format()); adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]); id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE)); setFindingSampleState(); }
/** * Sets the state to STATE_READING_SAMPLE. * * @param outputToUse TrackOutput object to write the sample to * @param currentSampleDuration Duration of the sample to be read * @param priorReadBytes Size of prior read bytes * @param sampleSize Size of the sample */ private void setReadingSampleState(TrackOutput outputToUse, long currentSampleDuration, int priorReadBytes, int sampleSize) { state = STATE_READING_SAMPLE; bytesRead = priorReadBytes; this.currentOutput = outputToUse; this.currentSampleDuration = currentSampleDuration; this.sampleSize = sampleSize; }
/** * 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; }
@Override public TrackOutput track(int id) { DefaultTrackOutput sampleQueue = new DefaultTrackOutput(allocator); sampleQueues.put(id, sampleQueue); return sampleQueue; }
public Ac3Reader(TrackOutput output) { super(output); headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]); headerScratchBytes = new ParsableByteArray(headerScratchBits.data); state = STATE_FINDING_SYNC; }
public SeiReader(TrackOutput output) { super(output); output.format(MediaFormat.createTextFormat(MimeTypes.APPLICATION_EIA608)); }
public AdtsReader(TrackOutput output) { super(output); adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]); state = STATE_FINDING_SYNC; }
/** * @param output A {@link TrackOutput} to which samples should be written. */ protected ElementaryStreamReader(TrackOutput output) { this.output = output; }
public Id3Reader(TrackOutput output) { super(output); output.format(MediaFormat.createTextFormat(MimeTypes.APPLICATION_ID3)); }
public Mp4Track(Track track, TrackSampleTable sampleTable, TrackOutput trackOutput) { this.track = track; this.sampleTable = sampleTable; this.trackOutput = trackOutput; }
private void outputSampleMetadata(TrackOutput trackOutput, long timeUs) { trackOutput.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, blockEncryptionKeyId); sampleRead = true; resetSample(); }
private void writeSampleData(ExtractorInput input, TrackOutput output, TrackFormat format, int size) throws IOException, InterruptedException { if (!sampleEncodingHandled) { if (format.hasContentEncryption) { // If the sample is encrypted, read its encryption signal byte and set the IV size. // Clear the encrypted flag. blockFlags &= ~C.SAMPLE_FLAG_ENCRYPTED; input.readFully(scratch.data, 0, 1); sampleBytesRead++; if ((scratch.data[0] & 0x80) == 0x80) { throw new ParserException("Extension bit is set in signal byte"); } if ((scratch.data[0] & 0x01) == 0x01) { scratch.data[0] = (byte) ENCRYPTION_IV_SIZE; scratch.setPosition(0); output.sampleData(scratch, 1); sampleBytesWritten++; blockFlags |= C.SAMPLE_FLAG_ENCRYPTED; } } else if (format.sampleStrippedBytes != null) { // If the sample has header stripping, prepare to read/output the stripped bytes first. sampleStrippedBytes.reset(format.sampleStrippedBytes, format.sampleStrippedBytes.length); } sampleEncodingHandled = true; } size += sampleStrippedBytes.limit(); if (CODEC_ID_H264.equals(format.codecId) || CODEC_ID_H265.equals(format.codecId)) { // TODO: Deduplicate with Mp4Extractor. // Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case // they're only 1 or 2 bytes long. byte[] nalLengthData = nalLength.data; nalLengthData[0] = 0; nalLengthData[1] = 0; nalLengthData[2] = 0; int nalUnitLengthFieldLength = format.nalUnitLengthFieldLength; int nalUnitLengthFieldLengthDiff = 4 - format.nalUnitLengthFieldLength; // NAL units are length delimited, but the decoder requires start code delimited units. // Loop until we've written the sample to the track output, replacing length delimiters with // start codes as we encounter them. while (sampleBytesRead < size) { if (sampleCurrentNalBytesRemaining == 0) { // Read the NAL length so that we know where we find the next one. readToTarget(input, nalLengthData, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength); nalLength.setPosition(0); sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt(); // Write a start code for the current NAL unit. nalStartCode.setPosition(0); output.sampleData(nalStartCode, 4); sampleBytesWritten += 4; } else { // Write the payload of the NAL unit. sampleCurrentNalBytesRemaining -= readToOutput(input, output, sampleCurrentNalBytesRemaining); } } } else { while (sampleBytesRead < size) { readToOutput(input, output, size - sampleBytesRead); } } if (CODEC_ID_VORBIS.equals(format.codecId)) { // Vorbis decoder in android MediaCodec [1] expects the last 4 bytes of the sample to be the // number of samples in the current page. This definition holds good only for Ogg and // irrelevant for WebM. So we always set this to -1 (the decoder will ignore this value if we // set it to -1). The android platform media extractor [2] does the same. // [1] https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp#314 // [2] https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/NuMediaExtractor.cpp#474 vorbisNumPageSamples.setPosition(0); output.sampleData(vorbisNumPageSamples, 4); sampleBytesWritten += 4; } }
@Override public TrackOutput track(int id) { Assertions.checkState(!seenTrack); seenTrack = true; return this; }
private void processSample() throws ParserException { ParsableByteArray webvttData = new ParsableByteArray(sampleData); // Validate the first line of the header. WebvttParserUtil.validateWebvttHeaderLine(webvttData); // Defaults to use if the header doesn't contain an X-TIMESTAMP-MAP header. long vttTimestampUs = 0; long tsTimestampUs = 0; // Parse the remainder of the header looking for X-TIMESTAMP-MAP. String line; while (!TextUtils.isEmpty(line = webvttData.readLine())) { if (line.startsWith("X-TIMESTAMP-MAP")) { Matcher localTimestampMatcher = LOCAL_TIMESTAMP.matcher(line); if (!localTimestampMatcher.find()) { throw new ParserException("X-TIMESTAMP-MAP doesn't contain local timestamp: " + line); } Matcher mediaTimestampMatcher = MEDIA_TIMESTAMP.matcher(line); if (!mediaTimestampMatcher.find()) { throw new ParserException("X-TIMESTAMP-MAP doesn't contain media timestamp: " + line); } vttTimestampUs = WebvttParserUtil.parseTimestampUs(localTimestampMatcher.group(1)); tsTimestampUs = PtsTimestampAdjuster.ptsToUs( Long.parseLong(mediaTimestampMatcher.group(1))); } } // Find the first cue header and parse the start time. Matcher cueHeaderMatcher = WebvttCueParser.findNextCueHeader(webvttData); if (cueHeaderMatcher == null) { // No cues found. Don't output a sample, but still output a corresponding track. buildTrackOutput(0); return; } long firstCueTimeUs = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1)); long sampleTimeUs = ptsTimestampAdjuster.adjustTimestamp( PtsTimestampAdjuster.usToPts(firstCueTimeUs + tsTimestampUs - vttTimestampUs)); long subsampleOffsetUs = sampleTimeUs - firstCueTimeUs; // Output the track. TrackOutput trackOutput = buildTrackOutput(subsampleOffsetUs); // Output the sample. sampleDataWrapper.reset(sampleData, sampleSize); trackOutput.sampleData(sampleDataWrapper, sampleSize); trackOutput.sampleMetadata(sampleTimeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); }
public H262Reader(TrackOutput output) { super(output); prefixFlags = new boolean[4]; csdBuffer = new CsdBuffer(128); }
public SeiReader(TrackOutput output) { this.output = output; output.format(MediaFormat.createTextFormat(null, MimeTypes.APPLICATION_EIA608, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, null)); }
public SampleReader(TrackOutput output) { this.output = output; }
public Id3Reader(TrackOutput output) { super(output); output.format(MediaFormat.createId3Format()); id3Header = new ParsableByteArray(ID3_HEADER_SIZE); }
@Override public void init(ExtractorOutput output) { TrackOutput trackOutput = output.track(0); output.endTracks(); streamReader.init(output, trackOutput); }
void init(ExtractorOutput output, TrackOutput trackOutput) { this.extractorOutput = output; this.trackOutput = trackOutput; }