@Override public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) { // Skip pointer. if (payloadUnitStartIndicator) { int pointerField = data.readUnsignedByte(); data.skip(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.skip(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. }
// @VisibleForTesting /* package */ VorbisSetup readSetupHeaders(ExtractorInput input, ParsableByteArray scratch) throws IOException, InterruptedException { if (vorbisIdHeader == null) { oggReader.readPacket(input, scratch); vorbisIdHeader = VorbisUtil.readVorbisIdentificationHeader(scratch); scratch.reset(); } if (commentHeader == null) { oggReader.readPacket(input, scratch); commentHeader = VorbisUtil.readVorbisCommentHeader(scratch); scratch.reset(); } oggReader.readPacket(input, scratch); // the third packet contains the setup header byte[] setupHeaderData = new byte[scratch.limit()]; // raw data of vorbis setup header has to be passed to decoder as CSD buffer #2 System.arraycopy(scratch.data, 0, setupHeaderData, 0, scratch.limit()); // partially decode setup header to get the modes Mode[] modes = VorbisUtil.readVorbisModes(scratch, vorbisIdHeader.channels); // we need the ilog of modes all the time when extracting, so we compute it once int iLogModes = VorbisUtil.iLog(modes.length - 1); scratch.reset(); return new VorbisSetup(vorbisIdHeader, commentHeader, setupHeaderData, modes, iLogModes); }
@Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { if (vorbisSetup == null) { vorbisSetup = readSetupHeaders(input, scratch); ArrayList<byte[]> codecInitialisationData = new ArrayList<>(); codecInitialisationData.clear(); codecInitialisationData.add(vorbisSetup.idHeader.data); codecInitialisationData.add(vorbisSetup.setupHeaderData); long duration = input.getLength() == C.LENGTH_UNBOUNDED ? C.UNKNOWN_TIME_US : input.getLength() * 8000000 / vorbisSetup.idHeader.getApproximateBitrate(); trackOutput.format( MediaFormat.createAudioFormat( null, MimeTypes.AUDIO_VORBIS, this.vorbisSetup.idHeader.bitrateNominal, OGG_MAX_SEGMENT_SIZE * 255, duration, this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate, codecInitialisationData, null)); } if (oggReader.readPacket(input, scratch)) { // if this is an audio packet... if ((scratch.data[0] & 0x01) != 1) { // ... we need to decode the block size int packetBlockSize = decodeBlockSize(scratch.data[0], vorbisSetup); // a packet contains samples produced from overlapping the previous and current frame data // (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-350001.3.2) int samplesInPacket = seenFirstAudioPacket ? (packetBlockSize + previousPacketBlockSize) / 4 : 0; // codec expects the number of samples appended to audio data appendNumberOfSamples(scratch, samplesInPacket); // calculate time and send audio data to codec long timeUs = elapsedSamples * C.MICROS_PER_SECOND / vorbisSetup.idHeader.sampleRate; trackOutput.sampleData(scratch, scratch.limit()); trackOutput.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, scratch.limit(), 0, null); // update state in members for next iteration seenFirstAudioPacket = true; elapsedSamples += samplesInPacket; previousPacketBlockSize = packetBlockSize; } scratch.reset(); return RESULT_CONTINUE; } return RESULT_END_OF_INPUT; }
// @VisibleForTesting /* package */ static void appendNumberOfSamples( ParsableByteArray buffer, long packetSampleCount) { buffer.setLimit(buffer.limit() + 4); // The vorbis decoder expects the number of samples in the packet // to be appended to the audio data as an int32 buffer.data[buffer.limit() - 4] = (byte) ((packetSampleCount) & 0xFF); buffer.data[buffer.limit() - 3] = (byte) ((packetSampleCount >>> 8) & 0xFF); buffer.data[buffer.limit() - 2] = (byte) ((packetSampleCount >>> 16) & 0xFF); buffer.data[buffer.limit() - 1] = (byte) ((packetSampleCount >>> 24) & 0xFF); }
/** * Continues a read from the provided {@code source} into a given {@code target}. It's assumed * that the data should be written into {@code target} starting from an offset of zero. * * @param source The source from which to read. * @param target The target into which data is to be read, or {@code null} to skip. * @param targetLength The target length of the read. * @return Whether the target length has been reached. */ private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); if (bytesToRead <= 0) { return true; } else if (target == null) { source.skip(bytesToRead); } else { source.readBytes(target, bytesRead, bytesToRead); } bytesRead += bytesToRead; return bytesRead == targetLength; }
/** 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); }
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); }
@Override public void seek() { oggReader.reset(); previousPacketBlockSize = -1; elapsedSamples = 0; seenFirstAudioPacket = false; scratch.reset(); }
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); }
@Override public void consume(ParsableByteArray data) { if (!writingSample) { return; } int bytesAvailable = data.bytesLeft(); if (sampleBytesRead < ID3_HEADER_SIZE) { // We're still reading the ID3 header. int headerBytesAvailable = Math.min(bytesAvailable, ID3_HEADER_SIZE - sampleBytesRead); System.arraycopy( data.data, data.getPosition(), id3Header.data, sampleBytesRead, headerBytesAvailable); if (sampleBytesRead + headerBytesAvailable == ID3_HEADER_SIZE) { // We've finished reading the ID3 header. Extract the sample size. id3Header.setPosition(6); // 'ID3' (3) + version (2) + flags (1) sampleSize = ID3_HEADER_SIZE + id3Header.readSynchSafeInt(); } } // Write data to the output. output.sampleData(data, bytesAvailable); sampleBytesRead += bytesAvailable; }
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); }
private int appendSampleEncryptionData(ParsableByteArray sampleEncryptionData) { int sampleDescriptionIndex = fragmentRun.header.sampleDescriptionIndex; TrackEncryptionBox encryptionBox = track.sampleDescriptionEncryptionBoxes[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; }
@Override public int read(DataSource dataSource) throws IOException { int bytesRead = dataSource.read(tsPacketBuffer.data, tsPacketBytesRead, TS_PACKET_SIZE - tsPacketBytesRead); if (bytesRead == -1) { return -1; } tsPacketBytesRead += bytesRead; if (tsPacketBytesRead < TS_PACKET_SIZE) { // We haven't read the whole packet yet. return bytesRead; } // Reset before reading the packet. tsPacketBytesRead = 0; tsPacketBuffer.setPosition(0); tsPacketBuffer.setLimit(TS_PACKET_SIZE); int syncByte = tsPacketBuffer.readUnsignedByte(); if (syncByte != TS_SYNC_BYTE) { return bytesRead; } tsPacketBuffer.readBytes(tsScratch, 3); tsScratch.skipBits(1); // transport_error_indicator boolean payloadUnitStartIndicator = tsScratch.readBit(); tsScratch.skipBits(1); // transport_priority int pid = tsScratch.readBits(13); tsScratch.skipBits(2); // transport_scrambling_control boolean adaptationFieldExists = tsScratch.readBit(); boolean payloadExists = tsScratch.readBit(); // Last 4 bits of scratch are skipped: continuity_counter // Skip the adaptation field. if (adaptationFieldExists) { int adaptationFieldLength = tsPacketBuffer.readUnsignedByte(); tsPacketBuffer.skip(adaptationFieldLength); } // Read the payload. if (payloadExists) { TsPayloadReader payloadReader = tsPayloadReaders.get(pid); if (payloadReader != null) { payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator); } } if (!prepared) { prepared = checkPrepared(); } return bytesRead; }
/** * 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 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(); }
@Override public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) { // Skip pointer. if (payloadUnitStartIndicator) { int pointerField = data.readUnsignedByte(); data.skip(pointerField); } data.readBytes(pmtScratch, 3); pmtScratch.skipBits(12); // table_id (8), section_syntax_indicator (1), '0' (1), reserved (2) int sectionLength = pmtScratch.readBits(12); // program_number (16), reserved (2), version_number (5), current_next_indicator (1), // section_number (8), last_section_number (8), reserved (3), PCR_PID (13) // Skip the rest of the PMT header. data.skip(7); data.readBytes(pmtScratch, 2); pmtScratch.skipBits(4); int programInfoLength = pmtScratch.readBits(12); // Skip the descriptors. data.skip(programInfoLength); int entriesSize = sectionLength - 9 /* Size of the rest of the fields before descriptors */ - programInfoLength - 4 /* CRC size */; while (entriesSize > 0) { data.readBytes(pmtScratch, 5); int streamType = pmtScratch.readBits(8); pmtScratch.skipBits(3); // reserved int elementaryPid = pmtScratch.readBits(13); pmtScratch.skipBits(4); // reserved int esInfoLength = pmtScratch.readBits(12); // Skip the descriptors. data.skip(esInfoLength); entriesSize -= esInfoLength + 5; if (sampleQueues.get(streamType) != null) { continue; } ElementaryStreamReader pesPayloadReader = null; switch (streamType) { case TS_STREAM_TYPE_AAC: pesPayloadReader = new AdtsReader(bufferPool); break; case TS_STREAM_TYPE_H264: SeiReader seiReader = new SeiReader(bufferPool); sampleQueues.put(TS_STREAM_TYPE_EIA608, seiReader); pesPayloadReader = new H264Reader(bufferPool, seiReader); break; case TS_STREAM_TYPE_ID3: pesPayloadReader = new Id3Reader(bufferPool); break; } if (pesPayloadReader != null) { sampleQueues.put(streamType, pesPayloadReader); tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader)); } } // Skip CRC_32. }
/** * Attempts to extract the next sample in the current mdat atom. * * <p>If there are no more samples in the current mdat atom then the parser state is transitioned * to {@link #STATE_READING_ATOM_HEADER} and {@code false} is returned. * * <p>It is possible for a sample to be extracted in part in the case that an exception is thrown. * In this case the method can be called again to extract the remainder of the sample. * * @param input The {@link ExtractorInput} from which to read data. * @return True if a sample was extracted. False otherwise. * @throws IOException If an error occurs reading from the input. * @throws InterruptedException If the thread is interrupted. */ private boolean readSample(ExtractorInput input) throws IOException, InterruptedException { if (sampleIndex == 0) { int bytesToSkip = (int) (fragmentRun.dataPosition - input.getPosition()); checkState(bytesToSkip >= 0, "Offset to sample data was negative."); input.skipFully(bytesToSkip); } if (sampleIndex >= fragmentRun.length) { int bytesToSkip = (int) (endOfMdatPosition - input.getPosition()); checkState(bytesToSkip >= 0, "Offset to end of mdat was negative."); input.skipFully(bytesToSkip); // We've run out of samples in the current mdat atom. enterReadingAtomHeaderState(); return false; } if (parserState == STATE_READING_SAMPLE_START) { sampleSize = fragmentRun.sampleSizeTable[sampleIndex]; if (fragmentRun.definesEncryptionData) { sampleBytesWritten = appendSampleEncryptionData(fragmentRun.sampleEncryptionData); sampleSize += sampleBytesWritten; } else { sampleBytesWritten = 0; } sampleCurrentNalBytesRemaining = 0; parserState = STATE_READING_SAMPLE_CONTINUE; } if (track.nalUnitLengthFieldLength != -1) { // 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 = track.nalUnitLengthFieldLength; int nalUnitLengthFieldLengthDiff = 4 - track.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 (sampleBytesWritten < sampleSize) { if (sampleCurrentNalBytesRemaining == 0) { // Read the NAL length so that we know where we find the next one. input.readFully(nalLength.data, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength); nalLength.setPosition(0); sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt(); // Write a start code for the current NAL unit. nalStartCode.setPosition(0); trackOutput.sampleData(nalStartCode, 4); sampleBytesWritten += 4; sampleSize += nalUnitLengthFieldLengthDiff; } else { // Write the payload of the NAL unit. int writtenBytes = trackOutput.sampleData(input, sampleCurrentNalBytesRemaining, false); sampleBytesWritten += writtenBytes; sampleCurrentNalBytesRemaining -= writtenBytes; } } } else { while (sampleBytesWritten < sampleSize) { int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false); sampleBytesWritten += writtenBytes; } } long sampleTimeUs = fragmentRun.getSamplePresentationTime(sampleIndex) * 1000L; int sampleFlags = (fragmentRun.definesEncryptionData ? C.SAMPLE_FLAG_ENCRYPTED : 0) | (fragmentRun.sampleIsSyncFrameTable[sampleIndex] ? C.SAMPLE_FLAG_SYNC : 0); int sampleDescriptionIndex = fragmentRun.header.sampleDescriptionIndex; byte[] encryptionKey = fragmentRun.definesEncryptionData ? track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId : null; trackOutput.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey); sampleIndex++; parserState = STATE_READING_SAMPLE_START; return true; }
/** 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); }
/** * 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(); }
@Override public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) { if (payloadUnitStartIndicator) { switch (state) { case STATE_FINDING_HEADER: case STATE_READING_HEADER: // Expected. break; case STATE_READING_HEADER_EXTENSION: Log.w(TAG, "Unexpected start indicator reading extended header"); break; case STATE_READING_BODY: // If payloadSize == -1 then the length of the previous packet was unspecified, and so // we only know that it's finished now that we've seen the start of the next one. This // is expected. If payloadSize != -1, then the length of the previous packet was known, // but we didn't receive that amount of data. This is not expected. if (payloadSize != -1) { Log.w(TAG, "Unexpected start indicator: expected " + payloadSize + " more bytes"); } // Either way, if the body was started, notify the reader that it has now finished. if (bodyStarted) { pesPayloadReader.packetFinished(); } break; } setState(STATE_READING_HEADER); } while (data.bytesLeft() > 0) { switch (state) { case STATE_FINDING_HEADER: data.skip(data.bytesLeft()); break; case STATE_READING_HEADER: if (continueRead(data, pesScratch.getData(), HEADER_SIZE)) { setState(parseHeader() ? STATE_READING_HEADER_EXTENSION : STATE_FINDING_HEADER); } break; case STATE_READING_HEADER_EXTENSION: int readLength = Math.min(MAX_HEADER_EXTENSION_SIZE, extendedHeaderLength); // Read as much of the extended header as we're interested in, and skip the rest. if (continueRead(data, pesScratch.getData(), readLength) && continueRead(data, null, extendedHeaderLength)) { parseHeaderExtension(); bodyStarted = false; setState(STATE_READING_BODY); } break; case STATE_READING_BODY: readLength = data.bytesLeft(); int padding = payloadSize == -1 ? 0 : readLength - payloadSize; if (padding > 0) { readLength -= padding; data.setLimit(data.getPosition() + readLength); } pesPayloadReader.consume(data, timeUs, !bodyStarted); bodyStarted = true; if (payloadSize != -1) { payloadSize -= readLength; if (payloadSize == 0) { pesPayloadReader.packetFinished(); setState(STATE_READING_HEADER); } } break; } } }
/** * 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; } }
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; }