private void maybeLoadInitData() throws IOException, InterruptedException { if (previousExtractor == extractor || initLoadCompleted || initDataSpec == null) { // According to spec, for packed audio, initDataSpec is expected to be null. return; } DataSpec initSegmentDataSpec = Util.getRemainderDataSpec(initDataSpec, initSegmentBytesLoaded); try { ExtractorInput input = new DefaultExtractorInput(initDataSource, initSegmentDataSpec.absoluteStreamPosition, initDataSource.open(initSegmentDataSpec)); try { int result = Extractor.RESULT_CONTINUE; while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { result = extractor.read(input, null); } } finally { initSegmentBytesLoaded = (int) (input.getPosition() - initDataSpec.absoluteStreamPosition); } } finally { Util.closeQuietly(dataSource); } initLoadCompleted = true; }
@Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { int currentFileSize = (int) input.getLength(); // Increase the size of sampleData if necessary. if (sampleSize == sampleData.length) { sampleData = Arrays.copyOf(sampleData, (currentFileSize != C.LENGTH_UNSET ? currentFileSize : sampleData.length) * 3 / 2); } // Consume to the input. int bytesRead = input.read(sampleData, sampleSize, sampleData.length - sampleSize); if (bytesRead != C.RESULT_END_OF_INPUT) { sampleSize += bytesRead; if (currentFileSize == C.LENGTH_UNSET || sampleSize != currentFileSize) { return Extractor.RESULT_CONTINUE; } } // We've reached the end of the input, which corresponds to the end of the current file. processSample(); return Extractor.RESULT_END_OF_INPUT; }
@Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { int bytesRead = input.read(packetBuffer.data, 0, MAX_PACKET_SIZE); if (bytesRead == C.RESULT_END_OF_INPUT) { return RESULT_END_OF_INPUT; } // Feed whatever data we have to the reader, regardless of whether the read finished or not. packetBuffer.setPosition(0); packetBuffer.setLimit(bytesRead); if (!startedPacket) { // Pass data to the reader as though it's contained within a single infinitely long packet. reader.packetStarted(firstSampleTimestampUs, true); startedPacket = true; } // TODO: Make it possible for reader to consume the dataSource directly, so that it becomes // unnecessary to copy the data through packetBuffer. reader.consume(packetBuffer); return RESULT_CONTINUE; }
@Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { int bytesRead = input.read(sampleData.data, 0, MAX_SYNC_FRAME_SIZE); if (bytesRead == C.RESULT_END_OF_INPUT) { return RESULT_END_OF_INPUT; } // Feed whatever data we have to the reader, regardless of whether the read finished or not. sampleData.setPosition(0); sampleData.setLimit(bytesRead); if (!startedPacket) { // Pass data to the reader as though it's contained within a single infinitely long packet. reader.packetStarted(firstSampleTimestampUs, true); startedPacket = true; } // TODO: Make it possible for the reader to consume the dataSource directly, so that it becomes // unnecessary to copy the data through packetBuffer. reader.consume(sampleData); return RESULT_CONTINUE; }
@Override public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { byte[] buffer = tsPacketBuffer.data; input.peekFully(buffer, 0, BUFFER_SIZE); for (int j = 0; j < TS_PACKET_SIZE; j++) { for (int i = 0; true; i++) { if (i == BUFFER_PACKET_COUNT) { input.skipFully(j); return true; } if (buffer[j + i * TS_PACKET_SIZE] != TS_SYNC_BYTE) { break; } } } return false; }
@Override public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { try { OggPageHeader header = new OggPageHeader(); if (!header.populate(input, true) || (header.type & 0x02) != 0x02) { return false; } int length = Math.min(header.bodySize, MAX_VERIFICATION_BYTES); ParsableByteArray scratch = new ParsableByteArray(length); input.peekFully(scratch.data, 0, length); if (FlacReader.verifyBitstreamType(resetPosition(scratch))) { streamReader = new FlacReader(); } else if (VorbisReader.verifyBitstreamType(resetPosition(scratch))) { streamReader = new VorbisReader(); } else if (OpusReader.verifyBitstreamType(resetPosition(scratch))) { streamReader = new OpusReader(); } else { return false; } return true; } catch (ParserException e) { return false; } }
/** * @see Extractor#read(ExtractorInput, PositionHolder) */ final int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { switch (state) { case STATE_READ_HEADERS: return readHeaders(input); case STATE_SKIP_HEADERS: input.skipFully((int) payloadStartPosition); state = STATE_READ_PAYLOAD; return Extractor.RESULT_CONTINUE; case STATE_READ_PAYLOAD: return readPayload(input, seekPosition); default: // Never happens. throw new IllegalStateException(); } }
@Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { while (true) { switch (parserState) { case STATE_READING_ATOM_HEADER: if (!readAtomHeader(input)) { return RESULT_END_OF_INPUT; } break; case STATE_READING_ATOM_PAYLOAD: if (readAtomPayload(input, seekPosition)) { return RESULT_SEEK; } break; case STATE_READING_SAMPLE: return readSample(input, seekPosition); default: throw new IllegalStateException(); } } }
/** * Processes the atom payload. If {@link #atomData} is null and the size is at or above the * threshold {@link #RELOAD_MINIMUM_SEEK_DISTANCE}, {@code true} is returned and the caller should * restart loading at the position in {@code positionHolder}. Otherwise, the atom is read/skipped. */ private boolean readAtomPayload(ExtractorInput input, PositionHolder positionHolder) throws IOException, InterruptedException { long atomPayloadSize = atomSize - atomHeaderBytesRead; long atomEndPosition = input.getPosition() + atomPayloadSize; boolean seekRequired = false; if (atomData != null) { input.readFully(atomData.data, atomHeaderBytesRead, (int) atomPayloadSize); if (atomType == Atom.TYPE_ftyp) { isQuickTime = processFtypAtom(atomData); } else if (!containerAtoms.isEmpty()) { containerAtoms.peek().add(new Atom.LeafAtom(atomType, atomData)); } } else { // We don't need the data. Skip or seek, depending on how large the atom is. if (atomPayloadSize < RELOAD_MINIMUM_SEEK_DISTANCE) { input.skipFully((int) atomPayloadSize); } else { positionHolder.position = input.getPosition() + atomPayloadSize; seekRequired = true; } } processAtomEnded(atomEndPosition); return seekRequired && parserState != STATE_READING_SAMPLE; }
@Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { while (true) { switch (parserState) { case STATE_READING_ATOM_HEADER: if (!readAtomHeader(input)) { return Extractor.RESULT_END_OF_INPUT; } break; case STATE_READING_ATOM_PAYLOAD: readAtomPayload(input); break; case STATE_READING_ENCRYPTION_DATA: readEncryptionData(input); break; default: if (readSample(input)) { return RESULT_CONTINUE; } } } }
private void readEncryptionData(ExtractorInput input) throws IOException, InterruptedException { TrackBundle nextTrackBundle = null; long nextDataOffset = Long.MAX_VALUE; int trackBundlesSize = trackBundles.size(); for (int i = 0; i < trackBundlesSize; i++) { TrackFragment trackFragment = trackBundles.valueAt(i).fragment; if (trackFragment.sampleEncryptionDataNeedsFill && trackFragment.auxiliaryDataPosition < nextDataOffset) { nextDataOffset = trackFragment.auxiliaryDataPosition; nextTrackBundle = trackBundles.valueAt(i); } } if (nextTrackBundle == null) { parserState = STATE_READING_SAMPLE_START; return; } int bytesToSkip = (int) (nextDataOffset - input.getPosition()); if (bytesToSkip < 0) { throw new ParserException("Offset to encryption data was negative."); } input.skipFully(bytesToSkip); nextTrackBundle.fragment.fillEncryptionData(input); }
/** * Does a byte by byte search to try and find the next level 1 element. This method is called if * some invalid data is encountered in the parser. * * @param input The {@link ExtractorInput} from which data has to be read. * @return id of the next level 1 element that has been found. * @throws EOFException If the end of input was encountered when searching for the next level 1 * element. * @throws IOException If an error occurs reading from the input. * @throws InterruptedException If the thread is interrupted. */ private long maybeResyncToNextLevel1Element(ExtractorInput input) throws IOException, InterruptedException { input.resetPeekPosition(); while (true) { input.peekFully(scratch, 0, MAX_ID_BYTES); int varintLength = VarintReader.parseUnsignedVarintLength(scratch[0]); if (varintLength != C.LENGTH_UNSET && varintLength <= MAX_ID_BYTES) { int potentialId = (int) VarintReader.assembleVarint(scratch, varintLength, false); if (output.isLevel1Element(potentialId)) { input.skipFully(varintLength); return potentialId; } } input.skipFully(1); } }
/** * Peeks a variable-length unsigned EBML integer from the input. */ private long readUint(ExtractorInput input) throws IOException, InterruptedException { input.peekFully(scratch.data, 0, 1); int value = scratch.data[0] & 0xFF; if (value == 0) { return Long.MIN_VALUE; } int mask = 0x80; int length = 0; while ((value & mask) == 0) { mask >>= 1; length++; } value &= ~mask; input.peekFully(scratch.data, 1, length); for (int i = 0; i < length; i++) { value <<= 8; value += scratch.data[i + 1] & 0xFF; } peekLength += length + 1; return value; }
@Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { while (true) { switch (parserState) { case STATE_READING_FLV_HEADER: if (!readFlvHeader(input)) { return RESULT_END_OF_INPUT; } break; case STATE_SKIPPING_TO_TAG_HEADER: skipToTagHeader(input); break; case STATE_READING_TAG_HEADER: if (!readTagHeader(input)) { return RESULT_END_OF_INPUT; } break; case STATE_READING_TAG_DATA: if (readTagData(input)) { return RESULT_CONTINUE; } break; } } }
/** * Reads a tag header from the provided {@link ExtractorInput}. * * @param input The {@link ExtractorInput} from which to read. * @return True if tag header was read successfully. Otherwise, false. * @throws IOException If an error occurred reading or parsing data from the source. * @throws InterruptedException If the thread was interrupted. */ private boolean readTagHeader(ExtractorInput input) throws IOException, InterruptedException { if (!input.readFully(tagHeaderBuffer.data, 0, FLV_TAG_HEADER_SIZE, true)) { // We've reached the end of the stream. return false; } tagHeaderBuffer.setPosition(0); tagType = tagHeaderBuffer.readUnsignedByte(); tagDataSize = tagHeaderBuffer.readUnsignedInt24(); tagTimestampUs = tagHeaderBuffer.readUnsignedInt24(); tagTimestampUs = ((tagHeaderBuffer.readUnsignedByte() << 24) | tagTimestampUs) * 1000L; tagHeaderBuffer.skipBytes(3); // streamId parserState = STATE_READING_TAG_DATA; return true; }
/** * Reads the body of a tag from the provided {@link ExtractorInput}. * * @param input The {@link ExtractorInput} from which to read. * @return True if the data was consumed by a reader. False if it was skipped. * @throws IOException If an error occurred reading or parsing data from the source. * @throws InterruptedException If the thread was interrupted. */ private boolean readTagData(ExtractorInput input) throws IOException, InterruptedException { boolean wasConsumed = true; if (tagType == TAG_TYPE_AUDIO && audioReader != null) { audioReader.consume(prepareTagData(input), tagTimestampUs); } else if (tagType == TAG_TYPE_VIDEO && videoReader != null) { videoReader.consume(prepareTagData(input), tagTimestampUs); } else if (tagType == TAG_TYPE_SCRIPT_DATA && metadataReader != null) { metadataReader.consume(prepareTagData(input), tagTimestampUs); } else { input.skipFully(tagDataSize); wasConsumed = false; } bytesToNextTagHeader = 4; // There's a 4 byte previous tag size before the next header. parserState = STATE_SKIPPING_TO_TAG_HEADER; return wasConsumed; }
private boolean parseTimestampAndSampleCount(ExtractorInput input) throws IOException, InterruptedException { dataScratch.reset(); if (version == 0) { if (!input.readFully(dataScratch.data, 0, TIMESTAMP_SIZE_V0 + 1, true)) { return false; } // version 0 timestamps are 45kHz, so we need to convert them into us timestampUs = dataScratch.readUnsignedInt() * 1000 / 45; } else if (version == 1) { if (!input.readFully(dataScratch.data, 0, TIMESTAMP_SIZE_V1 + 1, true)) { return false; } timestampUs = dataScratch.readLong(); } else { throw new ParserException("Unsupported version number: " + version); } remainingSampleCount = dataScratch.readUnsignedByte(); sampleBytesWritten = 0; return true; }
@Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { if (synchronizedHeaderData == 0) { try { synchronize(input, false); } catch (EOFException e) { return RESULT_END_OF_INPUT; } } if (seeker == null) { seeker = setupSeeker(input); extractorOutput.seekMap(seeker); trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType, null, Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels, synchronizedHeader.sampleRate, Format.NO_VALUE, gaplessInfoHolder.encoderDelay, gaplessInfoHolder.encoderPadding, null, null, 0, null, (flags & FLAG_DISABLE_ID3_METADATA) != 0 ? null : metadata)); } return readSample(input); }
@SuppressWarnings("NonAtomicVolatileUpdate") @Override public void load() throws IOException, InterruptedException { DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); try { // Create and open the input. ExtractorInput input = new DefaultExtractorInput(dataSource, loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); if (bytesLoaded == 0) { extractorWrapper.init(null); } // Load and decode the initialization data. try { Extractor extractor = extractorWrapper.extractor; int result = Extractor.RESULT_CONTINUE; while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { result = extractor.read(input, null); } Assertions.checkState(result != Extractor.RESULT_SEEK); } finally { bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); } } finally { Util.closeQuietly(dataSource); } }
private static void assertEvents(ExtractorInput input, List<String> expectedEvents) throws IOException, InterruptedException { DefaultEbmlReader reader = new DefaultEbmlReader(); TestOutput output = new TestOutput(); reader.init(output); // We expect the number of successful reads to equal the number of expected events. for (int i = 0; i < expectedEvents.size(); i++) { assertTrue(reader.read(input)); } // The next read should be unsuccessful. assertFalse(reader.read(input)); // Check that we really did get to the end of input. assertFalse(input.readFully(new byte[1], 0, 1, true)); assertEquals(expectedEvents.size(), output.events.size()); for (int i = 0; i < expectedEvents.size(); i++) { assertEquals(expectedEvents.get(i), output.events.get(i)); } }
@SuppressWarnings("NonAtomicVolatileUpdate") @Override public void load() throws IOException, InterruptedException { DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); try { // Create and open the input. ExtractorInput input = new DefaultExtractorInput(dataSource, loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); if (bytesLoaded == 0) { // Set the target to ourselves. extractorWrapper.init(this, this); } // Load and decode the initialization data. try { int result = Extractor.RESULT_CONTINUE; while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { result = extractorWrapper.read(input); } } finally { bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); } } finally { dataSource.close(); } }