private void readEncryptionData(ExtractorInput input) throws IOException, InterruptedException { int bytesToSkip = (int) (fragmentRun.auxiliaryDataPosition - input.getPosition()); checkState(bytesToSkip >= 0, "Offset to encryption data was negative."); input.skipFully(bytesToSkip); fragmentRun.fillEncryptionData(input); parserState = STATE_READING_SAMPLE_START; }
/** * Reads an EBML variable-length integer (varint) from an {@link ExtractorInput} such that reading * can be resumed later if an error occurs having read only some of it. * * <p>If an value is successfully read, then the reader will automatically reset itself ready to * read another value. * * <p>If an {@link IOException} or {@link InterruptedException} is throw, the read can be resumed * later by calling this method again, passing an {@link ExtractorInput} providing data starting * where the previous one left off. * * @param input The {@link ExtractorInput} from which the integer should be read. * @param allowEndOfInput True if encountering the end of the input having read no data is * allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it * should be considered an error, causing an {@link EOFException} to be thrown. * @param removeLengthMask Removes the variable-length integer length mask from the value. * @param maximumAllowedLength Maximum allowed length of the variable integer to be read. * @return The read value, or {@link C#RESULT_END_OF_INPUT} if {@code allowEndOfStream} is true * and the end of the input was encountered, or {@link C#RESULT_MAX_LENGTH_EXCEEDED} if the * length of the varint exceeded maximumAllowedLength. * @throws IOException If an error occurs reading from the input. * @throws InterruptedException If the thread is interrupted. */ public long readUnsignedVarint( ExtractorInput input, boolean allowEndOfInput, boolean removeLengthMask, int maximumAllowedLength) throws IOException, InterruptedException { if (state == STATE_BEGIN_READING) { // Read the first byte to establish the length. if (!input.readFully(scratch, 0, 1, allowEndOfInput)) { return C.RESULT_END_OF_INPUT; } int firstByte = scratch[0] & 0xFF; length = parseUnsignedVarintLength(firstByte); if (length == -1) { throw new IllegalStateException("No valid varint length mask found"); } state = STATE_READ_CONTENTS; } if (length > maximumAllowedLength) { state = STATE_BEGIN_READING; return C.RESULT_MAX_LENGTH_EXCEEDED; } if (length != 1) { // Read the remaining bytes. input.readFully(scratch, 1, length - 1); } state = STATE_BEGIN_READING; return assembleVarint(scratch, length, removeLengthMask); }
@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; }
private void readAtomPayload(ExtractorInput input) throws IOException, InterruptedException { int atomPayloadSize = (int) atomSize - atomHeaderBytesRead; if (atomData != null) { input.readFully(atomData.data, Atom.HEADER_SIZE, atomPayloadSize); onLeafAtomRead(new LeafAtom(atomType, atomData), input.getPosition()); } else { input.skipFully(atomPayloadSize); } long currentPosition = input.getPosition(); while (!containerAtoms.isEmpty() && containerAtoms.peek().endPosition == currentPosition) { onContainerAtomRead(containerAtoms.pop()); } enterReadingAtomHeaderState(); }
@Override public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { try { OggReader.PageHeader header = new OggReader.PageHeader(); OggReader.populatePageHeader(input, header, scratch, true); if ((header.type & 0x02) != 0x02) { throw new ParserException("expected page to be first page of a logical stream"); } input.resetPeekPosition(); } catch (ParserException e) { Log.e(TAG, e.getMessage()); return false; } return true; }
/** * 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; }
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; }