@Override public int readData(int track, long positionUs, MediaFormatHolder formatHolder, SampleHolder sampleHolder) { Assertions.checkState(mPrepared); Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED); if (mPendingDiscontinuities.get(track)) { return NOTHING_READ; } if (mTrackStates.get(track) != TRACK_STATE_FORMAT_SENT) { mSampleExtractor.getTrackMediaFormat(track, formatHolder); mTrackStates.set(track, TRACK_STATE_FORMAT_SENT); return FORMAT_READ; } mPendingSeekPositionUs = C.UNKNOWN_TIME_US; return mSampleExtractor.readSample(track, sampleHolder); }
public void testParsesValidMp4File() throws Exception { TestUtil.consumeTestData(extractor, getTestInputData(true /* includeStss */, false /* mp4vFormat */)); // The seek map is correct. assertSeekMap(extractorOutput.seekMap, true); // The video and audio formats are set correctly. assertEquals(2, extractorOutput.trackOutputs.size()); MediaFormat videoFormat = extractorOutput.trackOutputs.get(0).format; MediaFormat audioFormat = extractorOutput.trackOutputs.get(1).format; assertEquals(MimeTypes.VIDEO_H264, videoFormat.mimeType); assertEquals(VIDEO_WIDTH, videoFormat.width); assertEquals(VIDEO_HEIGHT, videoFormat.height); assertEquals(MimeTypes.AUDIO_AAC, audioFormat.mimeType); // The timestamps and sizes are set correctly. FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0); videoTrackOutput.assertSampleCount(SAMPLE_TIMESTAMPS.length); for (int i = 0; i < SAMPLE_TIMESTAMPS.length; i++) { byte[] sampleData = getOutputSampleData(i, true); int sampleFlags = SAMPLE_IS_SYNC[i] ? C.SAMPLE_FLAG_SYNC : 0; long sampleTimestampUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]); videoTrackOutput.assertSample(i, sampleData, sampleTimestampUs, sampleFlags, null); } }
/** * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at * index {@code offset}. * <p> * This method blocks until at least one byte of data can be read, the end of the opened range is * detected, or an exception is thrown. * * @param buffer The buffer into which the read data should be stored. * @param offset The start offset into {@code buffer} at which data should be written. * @param readLength The maximum number of bytes to read. * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the end of the opened * range is reached. * @throws IOException If an error occurs reading from the source. */ private int readInternal(byte[] buffer, int offset, int readLength) throws IOException { readLength = bytesToRead == C.LENGTH_UNBOUNDED ? readLength : (int) Math.min(readLength, bytesToRead - bytesRead); if (readLength == 0) { // We've read all of the requested data. return C.RESULT_END_OF_INPUT; } int read = inputStream.read(buffer, offset, readLength); if (read == -1) { if (bytesToRead != C.LENGTH_UNBOUNDED && bytesToRead != bytesRead) { // The server closed the connection having not sent sufficient data. throw new EOFException(); } return C.RESULT_END_OF_INPUT; } bytesRead += read; if (listener != null) { listener.onBytesTransferred(read); } return read; }
/** * Appends data to the rolling buffer. * * @param dataSource The source from which to read. * @param length The maximum length of the read. * @param allowEndOfInput True if encountering the end of the input having appended 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. * @return The number of bytes appended, or {@link C#RESULT_END_OF_INPUT} if the input has ended. * @throws IOException If an error occurs reading from the source. */ public int appendData(DataSource dataSource, int length, boolean allowEndOfInput) throws IOException { length = prepareForAppend(length); int bytesAppended = dataSource.read(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset), length); if (bytesAppended == C.RESULT_END_OF_INPUT) { if (allowEndOfInput) { return C.RESULT_END_OF_INPUT; } throw new EOFException(); } lastAllocationOffset += bytesAppended; totalBytesWritten += bytesAppended; return bytesAppended; }
/** * Appends data to the rolling buffer. * * @param input The source from which to read. * @param length The maximum length of the read. * @param allowEndOfInput True if encountering the end of the input having appended 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. * @return The number of bytes appended, or {@link C#RESULT_END_OF_INPUT} if the input has ended. * @throws IOException If an error occurs reading from the source. * @throws InterruptedException If the thread has been interrupted. */ public int appendData(ExtractorInput input, int length, boolean allowEndOfInput) throws IOException, InterruptedException { length = prepareForAppend(length); int bytesAppended = input.read(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset), length); if (bytesAppended == C.RESULT_END_OF_INPUT) { if (allowEndOfInput) { return C.RESULT_END_OF_INPUT; } throw new EOFException(); } lastAllocationOffset += bytesAppended; totalBytesWritten += bytesAppended; return bytesAppended; }
public StreamElement(String baseUri, String chunkTemplate, int type, String subType, long timescale, String name, int qualityLevels, int maxWidth, int maxHeight, int displayWidth, int displayHeight, String language, TrackElement[] tracks, List<Long> chunkStartTimes, long lastChunkDuration) { this.baseUri = baseUri; this.chunkTemplate = chunkTemplate; this.type = type; this.subType = subType; this.timescale = timescale; this.name = name; this.qualityLevels = qualityLevels; this.maxWidth = maxWidth; this.maxHeight = maxHeight; this.displayWidth = displayWidth; this.displayHeight = displayHeight; this.language = language; this.tracks = tracks; this.chunkCount = chunkStartTimes.size(); this.chunkStartTimes = chunkStartTimes; lastChunkDurationUs = Util.scaleLargeTimestamp(lastChunkDuration, C.MICROS_PER_SECOND, timescale); chunkStartTimesUs = Util.scaleLargeTimestamps(chunkStartTimes, C.MICROS_PER_SECOND, timescale); }
@Override public void consume(ParsableByteArray seiBuffer, long pesTimeUs, boolean startOfPacket) { int b; while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) { // Parse payload type. int payloadType = 0; do { b = seiBuffer.readUnsignedByte(); payloadType += b; } while (b == 0xFF); // Parse payload size. int payloadSize = 0; do { b = seiBuffer.readUnsignedByte(); payloadSize += b; } while (b == 0xFF); // Process the payload. We only support EIA-608 payloads currently. if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) { output.sampleData(seiBuffer, payloadSize); output.sampleMetadata(pesTimeUs, C.SAMPLE_FLAG_SYNC, payloadSize, 0, null); } else { seiBuffer.skipBytes(payloadSize); } } }
@Override public int read(byte[] target, int offset, int length) throws IOException, InterruptedException { if (Thread.interrupted()) { throw new InterruptedException(); } int peekBytes = Math.min(peekBufferLength, length); System.arraycopy(peekBuffer, 0, target, offset, peekBytes); offset += peekBytes; length -= peekBytes; int bytesRead = length != 0 ? dataSource.read(target, offset, length) : 0; if (bytesRead == C.RESULT_END_OF_INPUT) { return C.RESULT_END_OF_INPUT; } updatePeekBuffer(peekBytes); bytesRead += peekBytes; position += bytesRead; return bytesRead; }
@Override public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput) throws IOException, InterruptedException { int peekBytes = Math.min(peekBufferLength, length); System.arraycopy(peekBuffer, 0, target, offset, peekBytes); offset += peekBytes; int remaining = length - peekBytes; while (remaining > 0) { if (Thread.interrupted()) { throw new InterruptedException(); } int bytesRead = dataSource.read(target, offset, remaining); if (bytesRead == C.RESULT_END_OF_INPUT) { if (allowEndOfInput && remaining == length) { return false; } throw new EOFException(); } offset += bytesRead; remaining -= bytesRead; } updatePeekBuffer(peekBytes); position += length; return true; }
@Override public void skipFully(int length) throws IOException, InterruptedException { int peekBytes = Math.min(peekBufferLength, length); int remaining = length - peekBytes; while (remaining > 0) { if (Thread.interrupted()) { throw new InterruptedException(); } int bytesRead = dataSource.read(SCRATCH_SPACE, 0, Math.min(SCRATCH_SPACE.length, remaining)); if (bytesRead == C.RESULT_END_OF_INPUT) { throw new EOFException(); } remaining -= bytesRead; } updatePeekBuffer(peekBytes); position += length; }
@Override public long open(DataSpec dataSpec) throws FileDataSourceException { try { uriString = dataSpec.uri.toString(); file = new RandomAccessFile(dataSpec.uri.getPath(), "r"); file.seek(dataSpec.position); bytesRemaining = dataSpec.length == C.LENGTH_UNBOUNDED ? file.length() - dataSpec.position : dataSpec.length; if (bytesRemaining < 0) { throw new EOFException(); } } catch (IOException e) { throw new FileDataSourceException(e); } opened = true; if (listener != null) { listener.onTransferStart(); } return bytesRemaining; }
@Override public void fixedTrack(MediaPresentationDescription manifest, int periodIndex, int adaptationSetIndex, int representationIndex) { List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex); Format representationFormat = adaptationSet.representations.get(representationIndex).format; String mediaMimeType = getMediaMimeType(representationFormat); if (mediaMimeType == null) { Log.w(TAG, "Skipped track " + representationFormat.id + " (unknown media mime type)"); return; } MediaFormat trackFormat = getTrackFormat(adaptationSet.type, representationFormat, mediaMimeType, manifest.dynamic ? C.UNKNOWN_TIME_US : manifest.duration * 1000); if (trackFormat == null) { Log.w(TAG, "Skipped track " + representationFormat.id + " (unknown media format)"); return; } tracks.add(new ExposedTrack(trackFormat, adaptationSetIndex, representationFormat)); }
public void consume(long pesTimeUs, ParsableByteArray seiBuffer) { int b; while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) { // Parse payload type. int payloadType = 0; do { b = seiBuffer.readUnsignedByte(); payloadType += b; } while (b == 0xFF); // Parse payload size. int payloadSize = 0; do { b = seiBuffer.readUnsignedByte(); payloadSize += b; } while (b == 0xFF); // Process the payload. if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) { output.sampleData(seiBuffer, payloadSize); output.sampleMetadata(pesTimeUs, C.SAMPLE_FLAG_SYNC, payloadSize, 0, null); } else { seiBuffer.skipBytes(payloadSize); } } }
@Override public int read(byte[] buffer, int offset, int max) throws IOException { try { int bytesRead = currentDataSource.read(buffer, offset, max); if (bytesRead >= 0) { if (currentDataSource == cacheReadDataSource) { totalCachedBytesRead += bytesRead; } readPosition += bytesRead; if (bytesRemaining != C.LENGTH_UNBOUNDED) { bytesRemaining -= bytesRead; } } else { closeCurrentSource(); if (bytesRemaining > 0 && bytesRemaining != C.LENGTH_UNBOUNDED) { openNextSource(); return read(buffer, offset, max); } } return bytesRead; } catch (IOException e) { handleBeforeThrow(e); throw e; } }
/** * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at * index {@code offset}. * <p> * This method blocks until at least one byte of data can be read, the end of the opened range is * detected, or an exception is thrown. * * @param buffer The buffer into which the read data should be stored. * @param offset The start offset into {@code buffer} at which data should be written. * @param readLength The maximum number of bytes to read. * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the end of the opened * range is reached. * @throws IOException If an error occurs reading from the source. */ private int readInternal(byte[] buffer, int offset, int readLength) throws IOException { readLength = bytesToRead == C.LENGTH_UNBOUNDED ? readLength : (int) Math.min(readLength, bytesToRead - bytesRead); if (readLength == 0) { // We've read all of the requested data. return C.RESULT_END_OF_INPUT; } int read = responseByteStream.read(buffer, offset, readLength); if (read == -1) { if (bytesToRead != C.LENGTH_UNBOUNDED && bytesToRead != bytesRead) { // The server closed the connection having not sent sufficient data. throw new EOFException(); } return C.RESULT_END_OF_INPUT; } bytesRead += read; if (listener != null) { listener.onBytesTransferred(read); } return read; }
/** * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at index * {@code offset}. * * <p>This method blocks until at least one byte of data can be read, the end of the opened range * is detected, or an exception is thrown. * * @param buffer The buffer into which the read data should be stored. * @param offset The start offset into {@code buffer} at which data should be written. * @param readLength The maximum number of bytes to read. * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the end of the opened * range is reached. * @throws IOException If an error occurs reading from the source. */ private int readInternal(byte[] buffer, int offset, int readLength) throws IOException { readLength = bytesToRead == C.LENGTH_UNBOUNDED ? readLength : (int) Math.min(readLength, bytesToRead - bytesRead); if (readLength == 0) { // We've read all of the requested data. return C.RESULT_END_OF_INPUT; } int read = responseByteStream.read(buffer, offset, readLength); if (read == -1) { if (bytesToRead != C.LENGTH_UNBOUNDED && bytesToRead != bytesRead) { // The server closed the connection having not sent sufficient data. throw new EOFException(); } return C.RESULT_END_OF_INPUT; } bytesRead += read; if (listener != null) { listener.onBytesTransferred(read); } return read; }
@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_UNBOUNDED ? 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_UNBOUNDED || 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; }
/** * Establishes a connection. */ private Request makeRequest(DataSpec dataSpec) { long position = dataSpec.position; long length = dataSpec.length; boolean allowGzip = (dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) != 0; HttpUrl url = HttpUrl.parse(dataSpec.uri.toString()); Request.Builder builder = new Request.Builder().url(url); if (cacheControl != null) { builder.cacheControl(cacheControl); } synchronized (requestProperties) { for (Map.Entry<String, String> property : requestProperties.entrySet()) { builder.addHeader(property.getKey(), property.getValue()); } } if (!(position == 0 && length == C.LENGTH_UNBOUNDED)) { String rangeRequest = "bytes=" + position + "-"; if (length != C.LENGTH_UNBOUNDED) { rangeRequest += (position + length - 1); } builder.addHeader("Range", rangeRequest); } builder.addHeader("User-Agent", userAgent); if (!allowGzip) { builder.addHeader("Accept-Encoding", "identity"); } if (dataSpec.postBody != null) { builder.post(RequestBody.create(null, dataSpec.postBody)); } return builder.build(); }
private void assertSample(int index, byte[] expectedMedia, long timeUs, boolean keyframe, boolean invisible, byte[] encryptionKey, FakeTrackOutput output) { if (encryptionKey != null) { expectedMedia = TestUtil.joinByteArrays( new byte[] {(byte) StreamBuilder.TEST_INITIALIZATION_VECTOR.length}, StreamBuilder.TEST_INITIALIZATION_VECTOR, expectedMedia); } int flags = 0; flags |= keyframe ? C.SAMPLE_FLAG_SYNC : 0; flags |= invisible ? C.SAMPLE_FLAG_DECODE_ONLY : 0; flags |= encryptionKey != null ? C.SAMPLE_FLAG_ENCRYPTED : 0; output.assertSample(index, expectedMedia, timeUs, flags, encryptionKey); }
private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) { Uri mediaPlaylistUri = UriUtil.resolveToUri(baseUri, variants[variantIndex].url); DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null, DataSpec.FLAG_ALLOW_GZIP); return new MediaPlaylistChunk(dataSource, dataSpec, scratchSpace, playlistParser, variantIndex, mediaPlaylistUri.toString()); }
/** * Attempts to locate the keyframe before the specified time, if it's present in the buffer. * * @param timeUs The seek time. * @return The offset of the keyframe's data if the keyframe was present. -1 otherwise. */ public synchronized long skipToKeyframeBefore(long timeUs) { if (queueSize == 0 || timeUs < timesUs[relativeReadIndex]) { return -1; } int lastWriteIndex = (relativeWriteIndex == 0 ? capacity : relativeWriteIndex) - 1; long lastTimeUs = timesUs[lastWriteIndex]; if (timeUs > lastTimeUs) { return -1; } // TODO: This can be optimized further using binary search, although the fact that the array // is cyclic means we'd need to implement the binary search ourselves. int sampleCount = 0; int sampleCountToKeyframe = -1; int searchIndex = relativeReadIndex; while (searchIndex != relativeWriteIndex) { if (timesUs[searchIndex] > timeUs) { // We've gone too far. break; } else if ((flags[searchIndex] & C.SAMPLE_FLAG_SYNC) != 0) { // We've found a keyframe, and we're still before the seek position. sampleCountToKeyframe = sampleCount; } searchIndex = (searchIndex + 1) % capacity; sampleCount++; } if (sampleCountToKeyframe == -1) { return -1; } queueSize -= sampleCountToKeyframe; relativeReadIndex = (relativeReadIndex + sampleCountToKeyframe) % capacity; absoluteReadIndex += sampleCountToKeyframe; return offsets[relativeReadIndex]; }
/** * Converts a sample bit depth to a corresponding PCM encoding constant. * * @param bitDepth The bit depth. Supported values are 8, 16, 24 and 32. * @return The corresponding encoding. One of {@link C#ENCODING_PCM_8BIT}, * {@link C#ENCODING_PCM_16BIT}, {@link C#ENCODING_PCM_24BIT} and * {@link C#ENCODING_PCM_32BIT}. If the bit depth is unsupported then * {@link C#ENCODING_INVALID} is returned. */ public static int getPcmEncoding(int bitDepth) { switch (bitDepth) { case 8: return C.ENCODING_PCM_8BIT; case 16: return C.ENCODING_PCM_16BIT; case 24: return C.ENCODING_PCM_24BIT; case 32: return C.ENCODING_PCM_32BIT; default: return C.ENCODING_INVALID; } }
@Override public void load() throws IOException, InterruptedException { int result = Extractor.RESULT_CONTINUE; while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { ExtractorInput input = null; try { long position = positionHolder.position; long length = dataSource.open(new DataSpec(uri, position, C.LENGTH_UNBOUNDED, null)); if (length != C.LENGTH_UNBOUNDED) { length += position; } input = new DefaultExtractorInput(dataSource, position, length); Extractor extractor = extractorHolder.selectExtractor(input); if (pendingExtractorSeek) { extractor.seek(); pendingExtractorSeek = false; } while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { allocator.blockWhileTotalBytesAllocatedExceeds(requestedBufferSize); result = extractor.read(input, positionHolder); // TODO: Implement throttling to stop us from buffering data too often. } } finally { if (result == Extractor.RESULT_SEEK) { result = Extractor.RESULT_CONTINUE; } else if (input != null) { positionHolder.position = input.getPosition(); } dataSource.close(); } } }
public void testSkipFullyWithFailingDataSource() throws IOException, InterruptedException { FakeDataSource testDataSource = buildFailingDataSource(); DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNBOUNDED); try { input.skipFully(TEST_DATA.length); fail(); } catch (IOException e) { // Expected. } // The position should not have advanced. assertEquals(0, input.getPosition()); }
/** * Attempts to read the remainder of the frame. * <p> * If a frame is read in full then true is returned. The frame will have been output, and the * position of the source will have been advanced to the byte that immediately follows the end of * the frame. * <p> * If a frame is not read in full then the position of the source will have been advanced to the * limit, and the method should be called again with the next source to continue the read. * * @param source The source from which to read. */ private void readFrameRemainder(ParsableByteArray source) { int bytesToRead = Math.min(source.bytesLeft(), frameSize - frameBytesRead); output.sampleData(source, bytesToRead); frameBytesRead += bytesToRead; if (frameBytesRead < frameSize) { // We haven't read the whole of the frame yet. return; } output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, frameSize, 0, null); timeUs += frameDurationUs; frameBytesRead = 0; state = STATE_FINDING_HEADER; }
@Override public void consume(ParsableByteArray data) { while (data.bytesLeft() > 0) { switch (state) { case STATE_FINDING_SYNC: if (skipToNextSync(data)) { bytesRead = SYNC_VALUE_SIZE; state = STATE_READING_HEADER; } break; case STATE_READING_HEADER: if (continueRead(data, headerScratchBytes.data, HEADER_SIZE)) { parseHeader(); headerScratchBytes.setPosition(0); output.sampleData(headerScratchBytes, HEADER_SIZE); state = STATE_READING_SAMPLE; } break; case STATE_READING_SAMPLE: int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); output.sampleData(data, bytesToRead); bytesRead += bytesToRead; if (bytesRead == sampleSize) { output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); timeUs += sampleDurationUs; state = STATE_FINDING_SYNC; } break; } } }
@Override public long open(DataSpec dataSpec) throws IOException { Assertions.checkState(!opened); // DataSpec requires a matching close call even if open fails. opened = true; // If the source knows that the request is unsatisfiable then fail. if (dataSpec.position >= totalLength) { throw new IOException("Unsatisfiable position"); } else if (dataSpec.length != C.LENGTH_UNBOUNDED && dataSpec.position + dataSpec.length >= totalLength) { throw new IOException("Unsatisfiable range"); } // Scan through the segments, configuring them for the current read. boolean findingCurrentSegmentIndex = true; currentSegmentIndex = 0; int scannedLength = 0; for (Segment segment : segments) { segment.bytesRead = (int) Math.min(Math.max(0, dataSpec.position - scannedLength), segment.length); scannedLength += segment.length; findingCurrentSegmentIndex &= segment.isErrorSegment() ? segment.exceptionCleared : segment.bytesRead == segment.length; if (findingCurrentSegmentIndex) { currentSegmentIndex++; } } // Configure bytesRemaining, and return. if (dataSpec.length == C.LENGTH_UNBOUNDED) { bytesRemaining = totalLength - dataSpec.position; return simulateUnknownLength ? C.LENGTH_UNBOUNDED : bytesRemaining; } else { bytesRemaining = dataSpec.length; return bytesRemaining; } }
/** * 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 long open(DataSpec dataSpec) throws ContentDataSourceException { try { uriString = dataSpec.uri.toString(); AssetFileDescriptor assetFd = resolver.openAssetFileDescriptor(dataSpec.uri, "r"); inputStream = new FileInputStream(assetFd.getFileDescriptor()); long skipped = inputStream.skip(dataSpec.position); if (skipped < dataSpec.position) { // We expect the skip to be satisfied in full. If it isn't then we're probably trying to // skip beyond the end of the data. throw new EOFException(); } if (dataSpec.length != C.LENGTH_UNBOUNDED) { bytesRemaining = dataSpec.length; } else { bytesRemaining = inputStream.available(); if (bytesRemaining == 0) { // FileInputStream.available() returns 0 if the remaining length cannot be determined, or // if it's greater than Integer.MAX_VALUE. We don't know the true length in either case, // so treat as unbounded. bytesRemaining = C.LENGTH_UNBOUNDED; } } } catch (IOException e) { throw new ContentDataSourceException(e); } opened = true; if (listener != null) { listener.onTransferStart(); } return bytesRemaining; }
/** * Returns the sample index of the closest synchronization sample at or before the given * timestamp, if one is available. * * @param timeUs Timestamp adjacent to which to find a synchronization sample. * @return Index of the synchronization sample, or {@link #NO_SAMPLE} if none. */ public int getIndexOfEarlierOrEqualSynchronizationSample(long timeUs) { int startIndex = Util.binarySearchFloor(timestampsUs, timeUs, true, false); for (int i = startIndex; i >= 0; i--) { if (timestampsUs[i] <= timeUs && (flags[i] & C.SAMPLE_FLAG_SYNC) != 0) { return i; } } return NO_SAMPLE; }
/** * Parses a trak atom (defined in 14496-12). * * @param trak Atom to parse. * @param mvhd Movie header atom, used to get the timescale. * @return A {@link Track} instance, or {@code null} if the track's type isn't supported. */ public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd) { Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data); if (trackType != Track.TYPE_AUDIO && trackType != Track.TYPE_VIDEO && trackType != Track.TYPE_TEXT && trackType != Track.TYPE_SUBTITLE) { return null; } Pair<Integer, Long> header = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data); int id = header.first; long duration = header.second; long movieTimescale = parseMvhd(mvhd.data); long durationUs; if (duration == -1) { durationUs = C.UNKNOWN_TIME_US; } else { durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, movieTimescale); } Atom.ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf) .getContainerAtomOfType(Atom.TYPE_stbl); long mediaTimescale = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); StsdDataHolder stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, durationUs); return stsdData.mediaFormat == null ? null : new Track(id, trackType, mediaTimescale, durationUs, stsdData.mediaFormat, stsdData.trackEncryptionBoxes, stsdData.nalUnitLengthFieldLength); }
/** * @see DashSegmentIndex#getTimeUs(int) */ public final long getSegmentTimeUs(int sequenceNumber) { long unscaledSegmentTime; if (segmentTimeline != null) { unscaledSegmentTime = segmentTimeline.get(sequenceNumber - startNumber).startTime - presentationTimeOffset; } else { unscaledSegmentTime = (sequenceNumber - startNumber) * duration; } return Util.scaleLargeTimestamp(unscaledSegmentTime, C.MICROS_PER_SECOND, timescale); }
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException { if (sampleBytesRemaining == 0) { if (!maybeResynchronize(extractorInput)) { return RESULT_END_OF_INPUT; } if (basisTimeUs == -1) { basisTimeUs = seeker.getTimeUs(extractorInput.getPosition()); if (forcedFirstSampleTimestampUs != -1) { long embeddedFirstSampleTimestampUs = seeker.getTimeUs(0); basisTimeUs += forcedFirstSampleTimestampUs - embeddedFirstSampleTimestampUs; } } sampleBytesRemaining = synchronizedHeader.frameSize; } int bytesAppended = trackOutput.sampleData(extractorInput, sampleBytesRemaining, true); if (bytesAppended == C.RESULT_END_OF_INPUT) { return RESULT_END_OF_INPUT; } sampleBytesRemaining -= bytesAppended; if (sampleBytesRemaining > 0) { return RESULT_CONTINUE; } long timeUs = basisTimeUs + (samplesRead * C.MICROS_PER_SECOND / synchronizedHeader.sampleRate); trackOutput.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, synchronizedHeader.frameSize, 0, null); samplesRead += synchronizedHeader.samplesPerFrame; sampleBytesRemaining = 0; return RESULT_CONTINUE; }
public void testReadVarintExceedsMaximumAllowedLength() throws IOException, InterruptedException { VarintReader reader = new VarintReader(); ExtractorInput input = new FakeExtractorInput.Builder() .setData(DATA_8_BYTE_0) .setSimulateUnknownLength(true) .build(); long result = reader.readUnsignedVarint(input, false, true, 4); assertEquals(C.RESULT_MAX_LENGTH_EXCEEDED, result); }
@Override public long getPosition(long timeUs) { float percent = timeUs * 100f / durationUs; float fx; if (percent <= 0f) { fx = 0f; } else if (percent >= 100f) { fx = 256f; } else { int a = (int) percent; float fa, fb; if (a == 0) { fa = 0f; } else { fa = tableOfContents[a - 1]; } if (a < 99) { fb = tableOfContents[a]; } else { fb = 256f; } fx = fa + (fb - fa) * (percent - a); } long position = (long) ((1f / 256) * fx * sizeBytes) + firstFramePosition; return inputLength != C.LENGTH_UNBOUNDED ? Math.min(position, inputLength - 1) : position; }
@Override public long getPosition(long timeUs) { if (!isSeekable()) { return firstFramePosition; } float percent = timeUs * 100f / durationUs; float fx; if (percent <= 0f) { fx = 0f; } else if (percent >= 100f) { fx = 256f; } else { int a = (int) percent; float fa, fb; if (a == 0) { fa = 0f; } else { fa = tableOfContents[a - 1]; } if (a < 99) { fb = tableOfContents[a]; } else { fb = 256f; } fx = fa + (fb - fa) * (percent - a); } long position = Math.round((1.0 / 256) * fx * sizeBytes) + firstFramePosition; long maximumPosition = inputLength != C.LENGTH_UNBOUNDED ? inputLength - 1 : firstFramePosition - headerSize + sizeBytes - 1; return Math.min(position, maximumPosition); }
/** * Given a {@link DataSpec} and a number of bytes already loaded, returns a {@link DataSpec} * that represents the remainder of the data. * * @param dataSpec The original {@link DataSpec}. * @param bytesLoaded The number of bytes already loaded. * @return A {@link DataSpec} that represents the remainder of the data. */ public static DataSpec getRemainderDataSpec(DataSpec dataSpec, int bytesLoaded) { if (bytesLoaded == 0) { return dataSpec; } else { long remainingLength = dataSpec.length == C.LENGTH_UNBOUNDED ? C.LENGTH_UNBOUNDED : dataSpec.length - bytesLoaded; return new DataSpec(dataSpec.uri, dataSpec.position + bytesLoaded, remainingLength, dataSpec.key, dataSpec.flags); } }