/** * Asserts {@code extractor} throws {@code expectedThrowable} while consuming {@code sampleFile}. * * @param extractor The {@link Extractor} to be tested. * @param fileData Content of the input file. * @param expectedThrowable Expected {@link Throwable} class. * @param simulateIOErrors If true simulates IOErrors. * @param simulateUnknownLength If true simulates unknown input length. * @param simulatePartialReads If true simulates partial reads. * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. */ public static void assertThrows(Extractor extractor, byte[] fileData, Class<? extends Throwable> expectedThrowable, boolean simulateIOErrors, boolean simulateUnknownLength, boolean simulatePartialReads) throws IOException, InterruptedException { FakeExtractorInput input = new FakeExtractorInput.Builder().setData(fileData) .setSimulateIOErrors(simulateIOErrors) .setSimulateUnknownLength(simulateUnknownLength) .setSimulatePartialReads(simulatePartialReads).build(); try { consumeTestData(extractor, input, 0, true); throw new AssertionError(expectedThrowable.getSimpleName() + " expected but not thrown"); } catch (Throwable throwable) { if (expectedThrowable.equals(throwable.getClass())) { return; // Pass! } throw throwable; } }
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; }
/** * @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 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; } } } }
@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); } }
@Override public void load() throws IOException, InterruptedException { DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); try { ExtractorInput input = new DefaultExtractorInput(dataSource, loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); try { int result = Extractor.RESULT_CONTINUE; while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { result = extractor.read(input, null); } } finally { bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); } } finally { dataSource.close(); } }
@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(); } }
public void testIncompleteSample() throws Exception { Random random = new Random(0); byte[] fileData = TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts"); ByteArrayOutputStream out = new ByteArrayOutputStream(fileData.length * 2); writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1); out.write(fileData, 0, TS_PACKET_SIZE * 5); for (int i = TS_PACKET_SIZE * 5; i < fileData.length; i += TS_PACKET_SIZE) { writeJunkData(out, random.nextInt(TS_PACKET_SIZE)); out.write(fileData, i, TS_PACKET_SIZE); } out.write(TS_SYNC_BYTE); writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1); fileData = out.toByteArray(); TestUtil.assertOutput(new TestUtil.ExtractorFactory() { @Override public Extractor create() { return new TsExtractor(); } }, "ts/sample.ts", fileData, getInstrumentation()); }
public void testCustomPesReader() throws Exception { CustomEsReaderFactory factory = new CustomEsReaderFactory(); TsExtractor tsExtractor = new TsExtractor(new TimestampAdjuster(0), factory, false); FakeExtractorInput input = new FakeExtractorInput.Builder() .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts")) .setSimulateIOErrors(false) .setSimulateUnknownLength(false) .setSimulatePartialReads(false).build(); FakeExtractorOutput output = new FakeExtractorOutput(); tsExtractor.init(output); tsExtractor.seek(input.getPosition()); PositionHolder seekPositionHolder = new PositionHolder(); int readResult = Extractor.RESULT_CONTINUE; while (readResult != Extractor.RESULT_END_OF_INPUT) { readResult = tsExtractor.read(input, seekPositionHolder); } CustomEsReader reader = factory.reader; assertEquals(2, reader.packetsRead); TrackOutput trackOutput = reader.getTrackOutput(); assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */)); assertEquals( Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, "und", null, 0), ((FakeTrackOutput) trackOutput).format); }
private void maybeLoadInitData() throws IOException, InterruptedException { if (initLoadCompleted || initDataSpec == null) { // Note: The HLS spec forbids initialization segments for packed audio. return; } DataSpec initSegmentDataSpec = initDataSpec.subrange(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; }
@SuppressWarnings("NonAtomicVolatileUpdate") @Override public void load() throws IOException, InterruptedException { DataSpec loadDataSpec = dataSpec.subrange(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); } }
public void testIncompleteSample() throws Exception { Random random = new Random(0); byte[] fileData = TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts"); ByteArrayOutputStream out = new ByteArrayOutputStream(fileData.length * 2); writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1); out.write(fileData, 0, TS_PACKET_SIZE * 5); for (int i = TS_PACKET_SIZE * 5; i < fileData.length; i += TS_PACKET_SIZE) { writeJunkData(out, random.nextInt(TS_PACKET_SIZE)); out.write(fileData, i, TS_PACKET_SIZE); } out.write(TS_SYNC_BYTE); writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1); fileData = out.toByteArray(); ExtractorAsserts.assertOutput(new ExtractorFactory() { @Override public Extractor create() { return new TsExtractor(); } }, "ts/sample.ts", fileData, getInstrumentation()); }
public void testCustomPesReader() throws Exception { CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(true, false); TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), factory); FakeExtractorInput input = new FakeExtractorInput.Builder() .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts")) .setSimulateIOErrors(false) .setSimulateUnknownLength(false) .setSimulatePartialReads(false).build(); FakeExtractorOutput output = new FakeExtractorOutput(); tsExtractor.init(output); PositionHolder seekPositionHolder = new PositionHolder(); int readResult = Extractor.RESULT_CONTINUE; while (readResult != Extractor.RESULT_END_OF_INPUT) { readResult = tsExtractor.read(input, seekPositionHolder); } CustomEsReader reader = factory.esReader; assertEquals(2, reader.packetsRead); TrackOutput trackOutput = reader.getTrackOutput(); assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */)); assertEquals( Format.createTextSampleFormat("1/257", "mime", null, 0, 0, "und", null, 0), ((FakeTrackOutput) trackOutput).format); }
public void testCustomInitialSectionReader() throws Exception { CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(false, true); TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), factory); FakeExtractorInput input = new FakeExtractorInput.Builder() .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample_with_sdt.ts")) .setSimulateIOErrors(false) .setSimulateUnknownLength(false) .setSimulatePartialReads(false).build(); tsExtractor.init(new FakeExtractorOutput()); PositionHolder seekPositionHolder = new PositionHolder(); int readResult = Extractor.RESULT_CONTINUE; while (readResult != Extractor.RESULT_END_OF_INPUT) { readResult = tsExtractor.read(input, seekPositionHolder); } assertEquals(1, factory.sdtReader.consumedSdts); }
public static boolean sniffTestData(Extractor extractor, FakeExtractorInput input) throws IOException, InterruptedException { while (true) { try { return extractor.sniff(input); } catch (SimulatedIOException e) { // Ignore. } } }
public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input, long timeUs, boolean retryFromStartIfLive) throws IOException, InterruptedException { FakeExtractorOutput output = new FakeExtractorOutput(); extractor.init(output); consumeTestData(extractor, input, timeUs, output, retryFromStartIfLive); return output; }
private static void consumeTestData(Extractor extractor, FakeExtractorInput input, long timeUs, FakeExtractorOutput output, boolean retryFromStartIfLive) throws IOException, InterruptedException { extractor.seek(input.getPosition(), timeUs); PositionHolder seekPositionHolder = new PositionHolder(); int readResult = Extractor.RESULT_CONTINUE; while (readResult != Extractor.RESULT_END_OF_INPUT) { try { // Extractor.read should not read seekPositionHolder.position. Set it to a value that's // likely to cause test failure if a read does occur. seekPositionHolder.position = Long.MIN_VALUE; readResult = extractor.read(input, seekPositionHolder); if (readResult == Extractor.RESULT_SEEK) { long seekPosition = seekPositionHolder.position; Assertions.checkState(0 <= seekPosition && seekPosition <= Integer.MAX_VALUE); input.setPosition((int) seekPosition); } } catch (SimulatedIOException e) { if (!retryFromStartIfLive) { continue; } boolean isOnDemand = input.getLength() != C.LENGTH_UNSET || (output.seekMap != null && output.seekMap.getDurationUs() != C.TIME_UNSET); if (isOnDemand) { continue; } input.setPosition(0); for (int i = 0; i < output.numberOfTracks; i++) { output.trackOutputs.valueAt(i).clear(); } extractor.seek(0, 0); } } }
/** * Asserts that {@code extractor} consumes {@code sampleFile} successfully and its output equals * to a prerecorded output dump file with the name {@code sampleFile} + "{@value * #DUMP_EXTENSION}". If {@code simulateUnknownLength} is true and {@code sampleFile} + "{@value * #UNKNOWN_LENGTH_EXTENSION}" exists, it's preferred. * * @param extractor The {@link Extractor} to be tested. * @param sampleFile The path to the input sample. * @param fileData Content of the input file. * @param instrumentation To be used to load the sample file. * @param simulateIOErrors If true simulates IOErrors. * @param simulateUnknownLength If true simulates unknown input length. * @param simulatePartialReads If true simulates partial reads. * @return The {@link FakeExtractorOutput} used in the test. * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. */ public static FakeExtractorOutput assertOutput(Extractor extractor, String sampleFile, byte[] fileData, Instrumentation instrumentation, boolean simulateIOErrors, boolean simulateUnknownLength, boolean simulatePartialReads) throws IOException, InterruptedException { FakeExtractorInput input = new FakeExtractorInput.Builder().setData(fileData) .setSimulateIOErrors(simulateIOErrors) .setSimulateUnknownLength(simulateUnknownLength) .setSimulatePartialReads(simulatePartialReads).build(); Assert.assertTrue(sniffTestData(extractor, input)); input.resetPeekPosition(); FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, 0, true); if (simulateUnknownLength && assetExists(instrumentation, sampleFile + UNKNOWN_LENGTH_EXTENSION)) { extractorOutput.assertOutput(instrumentation, sampleFile + UNKNOWN_LENGTH_EXTENSION); } else { extractorOutput.assertOutput(instrumentation, sampleFile + ".0" + DUMP_EXTENSION); } SeekMap seekMap = extractorOutput.seekMap; if (seekMap.isSeekable()) { long durationUs = seekMap.getDurationUs(); for (int j = 0; j < 4; j++) { long timeUs = (durationUs * j) / 3; long position = seekMap.getPosition(timeUs); input.setPosition((int) position); for (int i = 0; i < extractorOutput.numberOfTracks; i++) { extractorOutput.trackOutputs.valueAt(i).clear(); } consumeTestData(extractor, input, timeUs, extractorOutput, false); extractorOutput.assertOutput(instrumentation, sampleFile + '.' + j + DUMP_EXTENSION); } } return extractorOutput; }
private int readHeaders(ExtractorInput input) throws IOException, InterruptedException { boolean readingHeaders = true; while (readingHeaders) { if (!oggPacket.populate(input)) { state = STATE_END_OF_INPUT; return Extractor.RESULT_END_OF_INPUT; } lengthOfReadPacket = input.getPosition() - payloadStartPosition; readingHeaders = readHeaders(oggPacket.getPayload(), payloadStartPosition, setupData); if (readingHeaders) { payloadStartPosition = input.getPosition(); } } sampleRate = setupData.format.sampleRate; if (!formatSet) { trackOutput.format(setupData.format); formatSet = true; } if (setupData.oggSeeker != null) { oggSeeker = setupData.oggSeeker; } else if (input.getLength() == C.LENGTH_UNSET) { oggSeeker = new UnseekableOggSeeker(); } else { OggPageHeader firstPayloadPageHeader = oggPacket.getPageHeader(); oggSeeker = new DefaultOggSeeker(payloadStartPosition, input.getLength(), this, firstPayloadPageHeader.headerSize + firstPayloadPageHeader.bodySize, firstPayloadPageHeader.granulePosition); } setupData = null; state = STATE_READ_PAYLOAD; // First payload packet. Trim the payload array of the ogg packet after headers have been read. oggPacket.trimPayload(); return Extractor.RESULT_CONTINUE; }
private int readPayload(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { long position = oggSeeker.read(input); if (position >= 0) { seekPosition.position = position; return Extractor.RESULT_SEEK; } else if (position < -1) { onSeekEnd(-(position + 2)); } if (!seekMapSet) { SeekMap seekMap = oggSeeker.createSeekMap(); extractorOutput.seekMap(seekMap); seekMapSet = true; } if (lengthOfReadPacket > 0 || oggPacket.populate(input)) { lengthOfReadPacket = 0; ParsableByteArray payload = oggPacket.getPayload(); long granulesInPacket = preparePayload(payload); if (granulesInPacket >= 0 && currentGranule + granulesInPacket >= targetGranule) { // calculate time and send payload data to codec long timeUs = convertGranuleToTime(currentGranule); trackOutput.sampleData(payload, payload.limit()); trackOutput.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, payload.limit(), 0, null); targetGranule = -1; } currentGranule += granulesInPacket; } else { state = STATE_END_OF_INPUT; return Extractor.RESULT_END_OF_INPUT; } return Extractor.RESULT_CONTINUE; }
@Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { sampleRead = false; boolean continueReading = true; while (continueReading && !sampleRead) { continueReading = reader.read(input); if (continueReading && maybeSeekForCues(seekPosition, input.getPosition())) { return Extractor.RESULT_SEEK; } } return continueReading ? Extractor.RESULT_CONTINUE : Extractor.RESULT_END_OF_INPUT; }
/** * @param uri The {@link Uri} of the media stream. * @param dataSource The data source to read the media. * @param extractors The extractors to use to read the data source. * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. * @param eventHandler A handler for events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. * @param sourceListener A listener to notify when the timeline has been loaded. * @param allocator An {@link Allocator} from which to obtain media buffer allocations. * @param customCacheKey A custom key that uniquely identifies the original stream. Used for cache * indexing. May be null. */ public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors, int minLoadableRetryCount, Handler eventHandler, ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener, Allocator allocator, String customCacheKey) { this.uri = uri; this.dataSource = dataSource; this.minLoadableRetryCount = minLoadableRetryCount; this.eventHandler = eventHandler; this.eventListener = eventListener; this.sourceListener = sourceListener; this.allocator = allocator; this.customCacheKey = customCacheKey; loader = new Loader("Loader:ExtractorMediaPeriod"); extractorHolder = new ExtractorHolder(extractors, this); loadCondition = new ConditionVariable(); maybeFinishPrepareRunnable = new Runnable() { @Override public void run() { maybeFinishPrepare(); } }; onContinueLoadingRequestedRunnable = new Runnable() { @Override public void run() { if (!released) { callback.onContinueLoadingRequested(ExtractorMediaPeriod.this); } } }; handler = new Handler(); pendingResetPositionUs = C.TIME_UNSET; sampleQueues = new SparseArray<>(); length = C.LENGTH_UNSET; }
@SuppressWarnings("NonAtomicVolatileUpdate") @Override public final 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) { // Configure the output and set it as the target for the extractor wrapper. BaseMediaChunkOutput output = getOutput(); output.setSampleOffsetUs(sampleOffsetUs); extractorWrapper.init(output); } // Load and decode the sample 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); } loadCompleted = true; }
private int readHeaders(ExtractorInput input) throws IOException, InterruptedException { boolean readingHeaders = true; while (readingHeaders) { if (!oggPacket.populate(input)) { state = STATE_END_OF_INPUT; return Extractor.RESULT_END_OF_INPUT; } lengthOfReadPacket = input.getPosition() - payloadStartPosition; readingHeaders = readHeaders(oggPacket.getPayload(), payloadStartPosition, setupData); if (readingHeaders) { payloadStartPosition = input.getPosition(); } } sampleRate = setupData.format.sampleRate; if (!formatSet) { trackOutput.format(setupData.format); formatSet = true; } if (setupData.oggSeeker != null) { oggSeeker = setupData.oggSeeker; } else if (input.getLength() == C.LENGTH_UNSET) { oggSeeker = new UnseekableOggSeeker(); } else { OggPageHeader firstPayloadPageHeader = oggPacket.getPageHeader(); oggSeeker = new DefaultOggSeeker(payloadStartPosition, input.getLength(), this, firstPayloadPageHeader.headerSize + firstPayloadPageHeader.bodySize, firstPayloadPageHeader.granulePosition); } setupData = null; state = STATE_READ_PAYLOAD; return Extractor.RESULT_CONTINUE; }