use of org.checkerframework.checker.nullness.qual.RequiresNonNull in project ExoPlayer by google.
the class MatroskaExtractor method writeSampleData.
/**
* Writes data for a single sample to the track output.
*
* @param input The input from which to read sample data.
* @param track The track to output the sample to.
* @param size The size of the sample data on the input side.
* @return The final size of the written sample.
* @throws IOException If an error occurs reading from the input.
*/
@RequiresNonNull("#2.output")
private int writeSampleData(ExtractorInput input, Track track, int size) throws IOException {
if (CODEC_ID_SUBRIP.equals(track.codecId)) {
writeSubtitleSampleData(input, SUBRIP_PREFIX, size);
return finishWriteSampleData();
} else if (CODEC_ID_ASS.equals(track.codecId)) {
writeSubtitleSampleData(input, SSA_PREFIX, size);
return finishWriteSampleData();
} else if (CODEC_ID_VTT.equals(track.codecId)) {
writeSubtitleSampleData(input, VTT_PREFIX, size);
return finishWriteSampleData();
}
TrackOutput output = track.output;
if (!sampleEncodingHandled) {
if (track.hasContentEncryption) {
// If the sample is encrypted, read its encryption signal byte and set the IV size.
// Clear the encrypted flag.
blockFlags &= ~C.BUFFER_FLAG_ENCRYPTED;
if (!sampleSignalByteRead) {
input.readFully(scratch.getData(), 0, 1);
sampleBytesRead++;
if ((scratch.getData()[0] & 0x80) == 0x80) {
throw ParserException.createForMalformedContainer("Extension bit is set in signal byte", /* cause= */
null);
}
sampleSignalByte = scratch.getData()[0];
sampleSignalByteRead = true;
}
boolean isEncrypted = (sampleSignalByte & 0x01) == 0x01;
if (isEncrypted) {
boolean hasSubsampleEncryption = (sampleSignalByte & 0x02) == 0x02;
blockFlags |= C.BUFFER_FLAG_ENCRYPTED;
if (!sampleInitializationVectorRead) {
input.readFully(encryptionInitializationVector.getData(), 0, ENCRYPTION_IV_SIZE);
sampleBytesRead += ENCRYPTION_IV_SIZE;
sampleInitializationVectorRead = true;
// Write the signal byte, containing the IV size and the subsample encryption flag.
scratch.getData()[0] = (byte) (ENCRYPTION_IV_SIZE | (hasSubsampleEncryption ? 0x80 : 0x00));
scratch.setPosition(0);
output.sampleData(scratch, 1, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION);
sampleBytesWritten++;
// Write the IV.
encryptionInitializationVector.setPosition(0);
output.sampleData(encryptionInitializationVector, ENCRYPTION_IV_SIZE, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION);
sampleBytesWritten += ENCRYPTION_IV_SIZE;
}
if (hasSubsampleEncryption) {
if (!samplePartitionCountRead) {
input.readFully(scratch.getData(), 0, 1);
sampleBytesRead++;
scratch.setPosition(0);
samplePartitionCount = scratch.readUnsignedByte();
samplePartitionCountRead = true;
}
int samplePartitionDataSize = samplePartitionCount * 4;
scratch.reset(samplePartitionDataSize);
input.readFully(scratch.getData(), 0, samplePartitionDataSize);
sampleBytesRead += samplePartitionDataSize;
short subsampleCount = (short) (1 + (samplePartitionCount / 2));
int subsampleDataSize = 2 + 6 * subsampleCount;
if (encryptionSubsampleDataBuffer == null || encryptionSubsampleDataBuffer.capacity() < subsampleDataSize) {
encryptionSubsampleDataBuffer = ByteBuffer.allocate(subsampleDataSize);
}
encryptionSubsampleDataBuffer.position(0);
encryptionSubsampleDataBuffer.putShort(subsampleCount);
// Loop through the partition offsets and write out the data in the way ExoPlayer
// wants it (ISO 23001-7 Part 7):
// 2 bytes - sub sample count.
// for each sub sample:
// 2 bytes - clear data size.
// 4 bytes - encrypted data size.
int partitionOffset = 0;
for (int i = 0; i < samplePartitionCount; i++) {
int previousPartitionOffset = partitionOffset;
partitionOffset = scratch.readUnsignedIntToInt();
if ((i % 2) == 0) {
encryptionSubsampleDataBuffer.putShort((short) (partitionOffset - previousPartitionOffset));
} else {
encryptionSubsampleDataBuffer.putInt(partitionOffset - previousPartitionOffset);
}
}
int finalPartitionSize = size - sampleBytesRead - partitionOffset;
if ((samplePartitionCount % 2) == 1) {
encryptionSubsampleDataBuffer.putInt(finalPartitionSize);
} else {
encryptionSubsampleDataBuffer.putShort((short) finalPartitionSize);
encryptionSubsampleDataBuffer.putInt(0);
}
encryptionSubsampleData.reset(encryptionSubsampleDataBuffer.array(), subsampleDataSize);
output.sampleData(encryptionSubsampleData, subsampleDataSize, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION);
sampleBytesWritten += subsampleDataSize;
}
}
} else if (track.sampleStrippedBytes != null) {
// If the sample has header stripping, prepare to read/output the stripped bytes first.
sampleStrippedBytes.reset(track.sampleStrippedBytes, track.sampleStrippedBytes.length);
}
if (track.maxBlockAdditionId > 0) {
blockFlags |= C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA;
blockAdditionalData.reset(/* limit= */
0);
// If there is supplemental data, the structure of the sample data is:
// sample size (4 bytes) || sample data || supplemental data
scratch.reset(/* limit= */
4);
scratch.getData()[0] = (byte) ((size >> 24) & 0xFF);
scratch.getData()[1] = (byte) ((size >> 16) & 0xFF);
scratch.getData()[2] = (byte) ((size >> 8) & 0xFF);
scratch.getData()[3] = (byte) (size & 0xFF);
output.sampleData(scratch, 4, TrackOutput.SAMPLE_DATA_PART_SUPPLEMENTAL);
sampleBytesWritten += 4;
}
sampleEncodingHandled = true;
}
size += sampleStrippedBytes.limit();
if (CODEC_ID_H264.equals(track.codecId) || CODEC_ID_H265.equals(track.codecId)) {
// TODO: Deduplicate with Mp4Extractor.
// Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case
// they're only 1 or 2 bytes long.
byte[] nalLengthData = nalLength.getData();
nalLengthData[0] = 0;
nalLengthData[1] = 0;
nalLengthData[2] = 0;
int nalUnitLengthFieldLength = track.nalUnitLengthFieldLength;
int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength;
// start codes as we encounter them.
while (sampleBytesRead < size) {
if (sampleCurrentNalBytesRemaining == 0) {
// Read the NAL length so that we know where we find the next one.
writeToTarget(input, nalLengthData, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength);
sampleBytesRead += nalUnitLengthFieldLength;
nalLength.setPosition(0);
sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt();
// Write a start code for the current NAL unit.
nalStartCode.setPosition(0);
output.sampleData(nalStartCode, 4);
sampleBytesWritten += 4;
} else {
// Write the payload of the NAL unit.
int bytesWritten = writeToOutput(input, output, sampleCurrentNalBytesRemaining);
sampleBytesRead += bytesWritten;
sampleBytesWritten += bytesWritten;
sampleCurrentNalBytesRemaining -= bytesWritten;
}
}
} else {
if (track.trueHdSampleRechunker != null) {
checkState(sampleStrippedBytes.limit() == 0);
track.trueHdSampleRechunker.startSample(input);
}
while (sampleBytesRead < size) {
int bytesWritten = writeToOutput(input, output, size - sampleBytesRead);
sampleBytesRead += bytesWritten;
sampleBytesWritten += bytesWritten;
}
}
if (CODEC_ID_VORBIS.equals(track.codecId)) {
// Vorbis decoder in android MediaCodec [1] expects the last 4 bytes of the sample to be the
// number of samples in the current page. This definition holds good only for Ogg and
// irrelevant for Matroska. So we always set this to -1 (the decoder will ignore this value if
// we set it to -1). The android platform media extractor [2] does the same.
// [1]
// https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp#314
// [2]
// https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/NuMediaExtractor.cpp#474
vorbisNumPageSamples.setPosition(0);
output.sampleData(vorbisNumPageSamples, 4);
sampleBytesWritten += 4;
}
return finishWriteSampleData();
}
use of org.checkerframework.checker.nullness.qual.RequiresNonNull in project ExoPlayer by google.
the class MediaMetricsListener method maybeUpdateTimelineMetadata.
@RequiresNonNull("metricsBuilder")
private void maybeUpdateTimelineMetadata(Timeline timeline, @Nullable MediaSource.MediaPeriodId mediaPeriodId) {
PlaybackMetrics.Builder metricsBuilder = this.metricsBuilder;
if (mediaPeriodId == null) {
return;
}
int periodIndex = timeline.getIndexOfPeriod(mediaPeriodId.periodUid);
if (periodIndex == C.INDEX_UNSET) {
return;
}
timeline.getPeriod(periodIndex, period);
timeline.getWindow(period.windowIndex, window);
metricsBuilder.setStreamType(getStreamType(window.mediaItem));
if (window.durationUs != C.TIME_UNSET && !window.isPlaceholder && !window.isDynamic && !window.isLive()) {
metricsBuilder.setMediaDurationMillis(window.getDurationMs());
}
metricsBuilder.setPlaybackType(window.isLive() ? PlaybackMetrics.PLAYBACK_TYPE_LIVE : PlaybackMetrics.PLAYBACK_TYPE_VOD);
reportedEventsForCurrentSession = true;
}
use of org.checkerframework.checker.nullness.qual.RequiresNonNull in project ExoPlayer by google.
the class HlsMediaChunk method prepareExtraction.
@RequiresNonNull("output")
@EnsuresNonNull("extractor")
private DefaultExtractorInput prepareExtraction(DataSource dataSource, DataSpec dataSpec, boolean initializeTimestampAdjuster) throws IOException {
long bytesToRead = dataSource.open(dataSpec);
if (initializeTimestampAdjuster) {
try {
timestampAdjuster.sharedInitializeOrWait(isMasterTimestampSource, startTimeUs);
} catch (InterruptedException e) {
throw new InterruptedIOException();
}
}
DefaultExtractorInput extractorInput = new DefaultExtractorInput(dataSource, dataSpec.position, bytesToRead);
if (extractor == null) {
long id3Timestamp = peekId3PrivTimestamp(extractorInput);
extractorInput.resetPeekPosition();
extractor = previousExtractor != null ? previousExtractor.recreate() : extractorFactory.createExtractor(dataSpec.uri, trackFormat, muxedCaptionFormats, timestampAdjuster, dataSource.getResponseHeaders(), extractorInput, playerId);
if (extractor.isPackedAudioExtractor()) {
output.setSampleOffsetUs(id3Timestamp != C.TIME_UNSET ? timestampAdjuster.adjustTsTimestamp(id3Timestamp) : startTimeUs);
} else {
// In case the container format changes mid-stream to non-packed-audio, we need to reset
// the timestamp offset.
output.setSampleOffsetUs(/* sampleOffsetUs= */
0L);
}
output.onNewExtractor();
extractor.init(output);
}
output.setDrmInitData(drmInitData);
return extractorInput;
}
use of org.checkerframework.checker.nullness.qual.RequiresNonNull in project ExoPlayer by google.
the class HlsMediaChunk method feedDataToExtractor.
/**
* Attempts to feed the given {@code dataSpec} to {@code this.extractor}. Whenever the operation
* concludes (because of a thrown exception or because the operation finishes), the number of fed
* bytes is written to {@code nextLoadPosition}.
*/
@RequiresNonNull("output")
private void feedDataToExtractor(DataSource dataSource, DataSpec dataSpec, boolean dataIsEncrypted, boolean initializeTimestampAdjuster) throws IOException {
// If we previously fed part of this chunk to the extractor, we need to skip it this time. For
// encrypted content we need to skip the data by reading it through the source, so as to ensure
// correct decryption of the remainder of the chunk. For clear content, we can request the
// remainder of the chunk directly.
DataSpec loadDataSpec;
boolean skipLoadedBytes;
if (dataIsEncrypted) {
loadDataSpec = dataSpec;
skipLoadedBytes = nextLoadPosition != 0;
} else {
loadDataSpec = dataSpec.subrange(nextLoadPosition);
skipLoadedBytes = false;
}
try {
ExtractorInput input = prepareExtraction(dataSource, loadDataSpec, initializeTimestampAdjuster);
if (skipLoadedBytes) {
input.skipFully(nextLoadPosition);
}
try {
while (!loadCanceled && extractor.read(input)) {
}
} catch (EOFException e) {
if ((trackFormat.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) {
// See onTruncatedSegmentParsed's javadoc for more info on why we are swallowing the EOF
// exception for trick play tracks.
extractor.onTruncatedSegmentParsed();
} else {
throw e;
}
} finally {
nextLoadPosition = (int) (input.getPosition() - dataSpec.position);
}
} finally {
DataSourceUtil.closeQuietly(dataSource);
}
}
use of org.checkerframework.checker.nullness.qual.RequiresNonNull in project ExoPlayer by google.
the class WebvttExtractor method processSample.
@RequiresNonNull("output")
private void processSample() throws ParserException {
ParsableByteArray webvttData = new ParsableByteArray(sampleData);
// Validate the first line of the header.
WebvttParserUtil.validateWebvttHeaderLine(webvttData);
// Defaults to use if the header doesn't contain an X-TIMESTAMP-MAP header.
long vttTimestampUs = 0;
long tsTimestampUs = 0;
// Parse the remainder of the header looking for X-TIMESTAMP-MAP.
for (String line = webvttData.readLine(); !TextUtils.isEmpty(line); line = webvttData.readLine()) {
if (line.startsWith("X-TIMESTAMP-MAP")) {
Matcher localTimestampMatcher = LOCAL_TIMESTAMP.matcher(line);
if (!localTimestampMatcher.find()) {
throw ParserException.createForMalformedContainer("X-TIMESTAMP-MAP doesn't contain local timestamp: " + line, /* cause= */
null);
}
Matcher mediaTimestampMatcher = MEDIA_TIMESTAMP.matcher(line);
if (!mediaTimestampMatcher.find()) {
throw ParserException.createForMalformedContainer("X-TIMESTAMP-MAP doesn't contain media timestamp: " + line, /* cause= */
null);
}
vttTimestampUs = WebvttParserUtil.parseTimestampUs(Assertions.checkNotNull(localTimestampMatcher.group(1)));
tsTimestampUs = TimestampAdjuster.ptsToUs(Long.parseLong(Assertions.checkNotNull(mediaTimestampMatcher.group(1))));
}
}
// Find the first cue header and parse the start time.
Matcher cueHeaderMatcher = WebvttParserUtil.findNextCueHeader(webvttData);
if (cueHeaderMatcher == null) {
// No cues found. Don't output a sample, but still output a corresponding track.
buildTrackOutput(0);
return;
}
long firstCueTimeUs = WebvttParserUtil.parseTimestampUs(Assertions.checkNotNull(cueHeaderMatcher.group(1)));
long sampleTimeUs = timestampAdjuster.adjustTsTimestamp(TimestampAdjuster.usToWrappedPts(firstCueTimeUs + tsTimestampUs - vttTimestampUs));
long subsampleOffsetUs = sampleTimeUs - firstCueTimeUs;
// Output the track.
TrackOutput trackOutput = buildTrackOutput(subsampleOffsetUs);
// Output the sample.
sampleDataWrapper.reset(sampleData, sampleSize);
trackOutput.sampleData(sampleDataWrapper, sampleSize);
trackOutput.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null);
}
Aggregations