Search in sources :

Example 31 with RequiresNonNull

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();
}
Also used : TrackOutput(com.google.android.exoplayer2.extractor.TrackOutput) RequiresNonNull(org.checkerframework.checker.nullness.qual.RequiresNonNull)

Example 32 with RequiresNonNull

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;
}
Also used : PlaybackMetrics(android.media.metrics.PlaybackMetrics) SuppressLint(android.annotation.SuppressLint) RequiresNonNull(org.checkerframework.checker.nullness.qual.RequiresNonNull)

Example 33 with RequiresNonNull

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;
}
Also used : InterruptedIOException(java.io.InterruptedIOException) DefaultExtractorInput(com.google.android.exoplayer2.extractor.DefaultExtractorInput) EnsuresNonNull(org.checkerframework.checker.nullness.qual.EnsuresNonNull) RequiresNonNull(org.checkerframework.checker.nullness.qual.RequiresNonNull)

Example 34 with RequiresNonNull

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);
    }
}
Also used : ExtractorInput(com.google.android.exoplayer2.extractor.ExtractorInput) DefaultExtractorInput(com.google.android.exoplayer2.extractor.DefaultExtractorInput) EOFException(java.io.EOFException) DataSpec(com.google.android.exoplayer2.upstream.DataSpec) RequiresNonNull(org.checkerframework.checker.nullness.qual.RequiresNonNull)

Example 35 with RequiresNonNull

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);
}
Also used : ParsableByteArray(com.google.android.exoplayer2.util.ParsableByteArray) Matcher(java.util.regex.Matcher) TrackOutput(com.google.android.exoplayer2.extractor.TrackOutput) RequiresNonNull(org.checkerframework.checker.nullness.qual.RequiresNonNull)

Aggregations

RequiresNonNull (org.checkerframework.checker.nullness.qual.RequiresNonNull)43 Nullable (androidx.annotation.Nullable)12 Format (com.google.android.exoplayer2.Format)10 SuppressLint (android.annotation.SuppressLint)4 TrackOutput (com.google.android.exoplayer2.extractor.TrackOutput)3 MediaPeriodId (com.google.android.exoplayer2.source.MediaSource.MediaPeriodId)3 IOException (java.io.IOException)3 ByteBuffer (java.nio.ByteBuffer)3 Intent (android.content.Intent)2 Paint (android.graphics.Paint)2 Uri (android.net.Uri)2 Bundle (android.os.Bundle)2 TextPaint (android.text.TextPaint)2 DecoderInputBuffer (com.google.android.exoplayer2.decoder.DecoderInputBuffer)2 DefaultExtractorInput (com.google.android.exoplayer2.extractor.DefaultExtractorInput)2 Transformer (com.google.android.exoplayer2.transformer.Transformer)2 EnsuresNonNull (org.checkerframework.checker.nullness.qual.EnsuresNonNull)2 UnderlyingAST (org.checkerframework.dataflow.cfg.UnderlyingAST)2 SpecialBlock (org.checkerframework.dataflow.cfg.block.SpecialBlock)2 TypeSystemError (org.checkerframework.javacutil.TypeSystemError)2