@Override public void onChunkLoadCompleted(Chunk chunk) { if (chunk instanceof InitializationChunk) { InitializationChunk initializationChunk = (InitializationChunk) chunk; RepresentationHolder representationHolder = representationHolders[trackSelection.indexOf(initializationChunk.trackFormat)]; // The null check avoids overwriting an index obtained from the manifest with one obtained // from the stream. If the manifest defines an index then the stream shouldn't, but in cases // where it does we should ignore it. if (representationHolder.segmentIndex == null) { SeekMap seekMap = representationHolder.extractorWrapper.getSeekMap(); if (seekMap != null) { representationHolder.segmentIndex = new DashWrappingSegmentIndex((ChunkIndex) seekMap); } } } }
@Override public void onChunkLoadCompleted(Chunk chunk) { if (chunk instanceof InitializationChunk) { InitializationChunk initializationChunk = (InitializationChunk) chunk; RepresentationHolder representationHolder = representationHolders[trackSelection.indexOf(initializationChunk.trackFormat)]; Format sampleFormat = initializationChunk.getSampleFormat(); if (sampleFormat != null) { representationHolder.setSampleFormat(sampleFormat); } // The null check avoids overwriting an index obtained from the manifest with one obtained // from the stream. If the manifest defines an index then the stream shouldn't, but in cases // where it does we should ignore it. if (representationHolder.segmentIndex == null) { SeekMap seekMap = initializationChunk.getSeekMap(); if (seekMap != null) { representationHolder.segmentIndex = new DashWrappingSegmentIndex((ChunkIndex) seekMap, initializationChunk.dataSpec.uri.toString()); } } } }
/** * 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) { ensureReadyForMediaOutput(); audioReader.consume(prepareTagData(input), mediaTagTimestampOffsetUs + tagTimestampUs); } else if (tagType == TAG_TYPE_VIDEO && videoReader != null) { ensureReadyForMediaOutput(); videoReader.consume(prepareTagData(input), mediaTagTimestampOffsetUs + tagTimestampUs); } else if (tagType == TAG_TYPE_SCRIPT_DATA && !outputSeekMap) { metadataReader.consume(prepareTagData(input), tagTimestampUs); long durationUs = metadataReader.getDurationUs(); if (durationUs != C.TIME_UNSET) { extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); outputSeekMap = true; } } else { input.skipFully(tagDataSize); wasConsumed = false; } bytesToNextTagHeader = 4; // There's a 4 byte previous tag size before the next header. state = STATE_SKIPPING_TO_TAG_HEADER; return wasConsumed; }
/** * 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; }
@Override public void init(ExtractorOutput output) { reader = new AdtsReader(true); reader.createTracks(output, new TrackIdGenerator(0, 1)); output.endTracks(); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); }
@Override public void init(ExtractorOutput output) { reader = new Ac3Reader(); // TODO: Add support for embedded ID3. reader.createTracks(output, new TrackIdGenerator(0, 1)); output.endTracks(); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); }
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; }
/** * Builds a {@link SeekMap} from the recently gathered Cues information. * * @return The built {@link SeekMap}. The returned {@link SeekMap} may be unseekable if cues * information was missing or incomplete. */ private SeekMap buildSeekMap() { if (segmentContentPosition == C.POSITION_UNSET || durationUs == C.TIME_UNSET || cueTimesUs == null || cueTimesUs.size() == 0 || cueClusterPositions == null || cueClusterPositions.size() != cueTimesUs.size()) { // Cues information is missing or incomplete. cueTimesUs = null; cueClusterPositions = null; return new SeekMap.Unseekable(durationUs); } int cuePointsSize = cueTimesUs.size(); int[] sizes = new int[cuePointsSize]; long[] offsets = new long[cuePointsSize]; long[] durationsUs = new long[cuePointsSize]; long[] timesUs = new long[cuePointsSize]; for (int i = 0; i < cuePointsSize; i++) { timesUs[i] = cueTimesUs.get(i); offsets[i] = segmentContentPosition + cueClusterPositions.get(i); } for (int i = 0; i < cuePointsSize - 1; i++) { sizes[i] = (int) (offsets[i + 1] - offsets[i]); durationsUs[i] = timesUs[i + 1] - timesUs[i]; } sizes[cuePointsSize - 1] = (int) (segmentContentPosition + segmentContentSize - offsets[cuePointsSize - 1]); durationsUs[cuePointsSize - 1] = durationUs - timesUs[cuePointsSize - 1]; cueTimesUs = null; cueClusterPositions = null; return new ChunkIndex(sizes, offsets, durationsUs, timesUs); }
@Override public void init(ExtractorOutput output) { output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); trackOutput = output.track(0, C.TRACK_TYPE_TEXT); output.endTracks(); trackOutput.format(format); }
@Override public void init(ExtractorOutput output) { reader = new AdtsReader(true); reader.init(output, new TrackIdGenerator(0, 1)); output.endTracks(); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); }
@Override public void init(ExtractorOutput output) { reader = new Ac3Reader(); // TODO: Add support for embedded ID3. reader.init(output, new TrackIdGenerator(0, 1)); output.endTracks(); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); }
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 void init(ExtractorOutput output) { this.extractorOutput = output; extractorOutput.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); trackOutput = extractorOutput.track(0); extractorOutput.endTracks(); trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null)); }
private void ensureReadyForMediaOutput() { if (!outputSeekMap) { extractorOutput.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); outputSeekMap = true; } if (mediaTagTimestampOffsetUs == C.TIME_UNSET) { mediaTagTimestampOffsetUs = metadataReader.getDurationUs() == C.TIME_UNSET ? -tagTimestampUs : 0; } }
@Override public void init(ExtractorOutput output) { this.output = output; output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); }
@Override public SeekMap createSeekMap() { return new SeekMap.Unseekable(C.TIME_UNSET); }
@Override public SeekMap createSeekMap() { return this; }
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(); } if (atomSize < atomHeaderBytesRead) { throw new ParserException("Atom size less than header length (unsupported)."); } long atomPosition = input.getPosition() - atomHeaderBytesRead; if (atomType == Atom.TYPE_moof) { // The data positions may be updated when parsing the tfhd/trun. int trackCount = trackBundles.size(); for (int i = 0; i < trackCount; i++) { TrackFragment fragment = trackBundles.valueAt(i).fragment; fragment.atomPosition = atomPosition; fragment.auxiliaryDataPosition = atomPosition; fragment.dataPosition = atomPosition; } } if (atomType == Atom.TYPE_mdat) { currentTrackBundle = null; endOfMdatPosition = atomPosition + atomSize; if (!haveOutputSeekMap) { extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); haveOutputSeekMap = true; } parserState = STATE_READING_ENCRYPTION_DATA; return true; } if (shouldParseContainerAtom(atomType)) { long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE; containerAtoms.add(new ContainerAtom(atomType, endPosition)); if (atomSize == atomHeaderBytesRead) { processAtomEnded(endPosition); } else { // Start reading the first child atom. enterReadingAtomHeaderState(); } } else if (shouldParseLeafAtom(atomType)) { if (atomHeaderBytesRead != Atom.HEADER_SIZE) { throw new ParserException("Leaf atom defines extended atom size (unsupported)."); } if (atomSize > Integer.MAX_VALUE) { throw new ParserException("Leaf atom with length > 2147483647 (unsupported)."); } atomData = new ParsableByteArray((int) atomSize); System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.HEADER_SIZE); parserState = STATE_READING_ATOM_PAYLOAD; } else { if (atomSize > Integer.MAX_VALUE) { throw new ParserException("Skipping atom with length > 2147483647 (unsupported)."); } atomData = null; parserState = STATE_READING_ATOM_PAYLOAD; } return true; }
void startMasterElement(int id, long contentPosition, long contentSize) throws ParserException { switch (id) { case ID_SEGMENT: if (segmentContentPosition != C.POSITION_UNSET && segmentContentPosition != contentPosition) { throw new ParserException("Multiple Segment elements not supported"); } segmentContentPosition = contentPosition; segmentContentSize = contentSize; break; case ID_SEEK: seekEntryId = UNSET_ENTRY_ID; seekEntryPosition = C.POSITION_UNSET; break; case ID_CUES: cueTimesUs = new LongArray(); cueClusterPositions = new LongArray(); break; case ID_CUE_POINT: seenClusterPositionForCurrentCuePoint = false; break; case ID_CLUSTER: if (!sentSeekMap) { // We need to build cues before parsing the cluster. if (seekForCuesEnabled && cuesContentPosition != C.POSITION_UNSET) { // We know where the Cues element is located. Seek to request it. seekForCues = true; } else { // We don't know where the Cues element is located. It's most likely omitted. Allow // playback, but disable seeking. extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); sentSeekMap = true; } } break; case ID_BLOCK_GROUP: sampleSeenReferenceBlock = false; break; case ID_CONTENT_ENCODING: // TODO: check and fail if more than one content encoding is present. break; case ID_CONTENT_ENCRYPTION: currentTrack.hasContentEncryption = true; break; case ID_TRACK_ENTRY: currentTrack = new Track(); break; case ID_MASTERING_METADATA: currentTrack.hasColorInfo = true; break; default: break; } }
/** * Returns the {@link SeekMap} most recently output by the extractor, or null. */ public SeekMap getSeekMap() { return seekMap; }
void startMasterElement(int id, long contentPosition, long contentSize) throws ParserException { switch (id) { case ID_SEGMENT: if (segmentContentPosition != C.POSITION_UNSET && segmentContentPosition != contentPosition) { throw new ParserException("Multiple Segment elements not supported"); } segmentContentPosition = contentPosition; segmentContentSize = contentSize; break; case ID_SEEK: seekEntryId = UNSET_ENTRY_ID; seekEntryPosition = C.POSITION_UNSET; break; case ID_CUES: cueTimesUs = new LongArray(); cueClusterPositions = new LongArray(); break; case ID_CUE_POINT: seenClusterPositionForCurrentCuePoint = false; break; case ID_CLUSTER: if (!sentSeekMap) { // We need to build cues before parsing the cluster. if (cuesContentPosition != C.POSITION_UNSET) { // We know where the Cues element is located. Seek to request it. seekForCues = true; } else { // We don't know where the Cues element is located. It's most likely omitted. Allow // playback, but disable seeking. extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); sentSeekMap = true; } } break; case ID_BLOCK_GROUP: sampleSeenReferenceBlock = false; break; case ID_CONTENT_ENCODING: // TODO: check and fail if more than one content encoding is present. break; case ID_CONTENT_ENCRYPTION: currentTrack.hasContentEncryption = true; break; case ID_TRACK_ENTRY: currentTrack = new Track(); break; default: break; } }