Search in sources :

Example 36 with FormatHolder

use of com.google.android.exoplayer2.FormatHolder in project ExoPlayer by google.

the class MediaCodecRenderer method onInputFormatChanged.

/**
 * Called when a new {@link Format} is read from the upstream {@link MediaPeriod}.
 *
 * @param formatHolder A {@link FormatHolder} that holds the new {@link Format}.
 * @throws ExoPlaybackException If an error occurs re-initializing the {@link MediaCodec}.
 * @return The result of the evaluation to determine whether the existing decoder instance can be
 *     reused for the new format, or {@code null} if the renderer did not have a decoder.
 */
@CallSuper
@Nullable
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
    waitingForFirstSampleInFormat = true;
    Format newFormat = checkNotNull(formatHolder.format);
    if (newFormat.sampleMimeType == null) {
        throw createRendererException(new IllegalArgumentException(), newFormat, PlaybackException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED);
    }
    setSourceDrmSession(formatHolder.drmSession);
    inputFormat = newFormat;
    if (bypassEnabled) {
        bypassDrainAndReinitialize = true;
        // Need to drain batch buffer first.
        return null;
    }
    if (codec == null) {
        availableCodecInfos = null;
        maybeInitCodecOrBypass();
        return null;
    }
    // We have an existing codec that we may need to reconfigure, re-initialize, or release to
    // switch to bypass. If the existing codec instance is kept then its operating rate and DRM
    // session may need to be updated.
    // Copy the current codec and codecInfo to local variables so they remain accessible if the
    // member variables are updated during the logic below.
    MediaCodecAdapter codec = this.codec;
    MediaCodecInfo codecInfo = this.codecInfo;
    Format oldFormat = codecInputFormat;
    if (drmNeedsCodecReinitialization(codecInfo, newFormat, codecDrmSession, sourceDrmSession)) {
        drainAndReinitializeCodec();
        return new DecoderReuseEvaluation(codecInfo.name, oldFormat, newFormat, REUSE_RESULT_NO, DISCARD_REASON_DRM_SESSION_CHANGED);
    }
    boolean drainAndUpdateCodecDrmSession = sourceDrmSession != codecDrmSession;
    Assertions.checkState(!drainAndUpdateCodecDrmSession || Util.SDK_INT >= 23);
    DecoderReuseEvaluation evaluation = canReuseCodec(codecInfo, oldFormat, newFormat);
    @DecoderDiscardReasons int overridingDiscardReasons = 0;
    switch(evaluation.result) {
        case REUSE_RESULT_NO:
            drainAndReinitializeCodec();
            break;
        case REUSE_RESULT_YES_WITH_FLUSH:
            if (!updateCodecOperatingRate(newFormat)) {
                overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
            } else {
                codecInputFormat = newFormat;
                if (drainAndUpdateCodecDrmSession) {
                    if (!drainAndUpdateCodecDrmSessionV23()) {
                        overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
                    }
                } else if (!drainAndFlushCodec()) {
                    overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
                }
            }
            break;
        case REUSE_RESULT_YES_WITH_RECONFIGURATION:
            if (!updateCodecOperatingRate(newFormat)) {
                overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
            } else {
                codecReconfigured = true;
                codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
                codecNeedsAdaptationWorkaroundBuffer = codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS || (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION && newFormat.width == oldFormat.width && newFormat.height == oldFormat.height);
                codecInputFormat = newFormat;
                if (drainAndUpdateCodecDrmSession && !drainAndUpdateCodecDrmSessionV23()) {
                    overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
                }
            }
            break;
        case REUSE_RESULT_YES_WITHOUT_RECONFIGURATION:
            if (!updateCodecOperatingRate(newFormat)) {
                overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
            } else {
                codecInputFormat = newFormat;
                if (drainAndUpdateCodecDrmSession && !drainAndUpdateCodecDrmSessionV23()) {
                    overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
                }
            }
            break;
        default:
            // Never happens.
            throw new IllegalStateException();
    }
    if (evaluation.result != REUSE_RESULT_NO && (this.codec != codec || codecDrainAction == DRAIN_ACTION_REINITIALIZE)) {
        // The reasons are indicated by overridingDiscardReasons.
        return new DecoderReuseEvaluation(codecInfo.name, oldFormat, newFormat, REUSE_RESULT_NO, overridingDiscardReasons);
    }
    return evaluation;
}
Also used : Format(com.google.android.exoplayer2.Format) MediaFormat(android.media.MediaFormat) DecoderReuseEvaluation(com.google.android.exoplayer2.decoder.DecoderReuseEvaluation) DecoderDiscardReasons(com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons) CallSuper(androidx.annotation.CallSuper) Nullable(androidx.annotation.Nullable)

Example 37 with FormatHolder

use of com.google.android.exoplayer2.FormatHolder in project ExoPlayer by google.

the class MediaCodecRenderer method feedInputBuffer.

/**
 * @return Whether it may be possible to feed more input data.
 * @throws ExoPlaybackException If an error occurs feeding the input buffer.
 */
private boolean feedInputBuffer() throws ExoPlaybackException {
    if (codec == null || codecDrainState == DRAIN_STATE_WAIT_END_OF_STREAM || inputStreamEnded) {
        return false;
    }
    if (codecDrainState == DRAIN_STATE_NONE && shouldReinitCodec()) {
        drainAndReinitializeCodec();
    }
    if (inputIndex < 0) {
        inputIndex = codec.dequeueInputBufferIndex();
        if (inputIndex < 0) {
            return false;
        }
        buffer.data = codec.getInputBuffer(inputIndex);
        buffer.clear();
    }
    if (codecDrainState == DRAIN_STATE_SIGNAL_END_OF_STREAM) {
        // that it outputs any remaining buffers before we release it.
        if (codecNeedsEosPropagation) {
        // Do nothing.
        } else {
            codecReceivedEos = true;
            codec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            resetInputBuffer();
        }
        codecDrainState = DRAIN_STATE_WAIT_END_OF_STREAM;
        return false;
    }
    if (codecNeedsAdaptationWorkaroundBuffer) {
        codecNeedsAdaptationWorkaroundBuffer = false;
        buffer.data.put(ADAPTATION_WORKAROUND_BUFFER);
        codec.queueInputBuffer(inputIndex, 0, ADAPTATION_WORKAROUND_BUFFER.length, 0, 0);
        resetInputBuffer();
        codecReceivedBuffers = true;
        return true;
    }
    // the start of the buffer that also contains the first frame in the new format.
    if (codecReconfigurationState == RECONFIGURATION_STATE_WRITE_PENDING) {
        for (int i = 0; i < codecInputFormat.initializationData.size(); i++) {
            byte[] data = codecInputFormat.initializationData.get(i);
            buffer.data.put(data);
        }
        codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING;
    }
    int adaptiveReconfigurationBytes = buffer.data.position();
    FormatHolder formatHolder = getFormatHolder();
    @SampleStream.ReadDataResult int result;
    try {
        result = readSource(formatHolder, buffer, /* readFlags= */
        0);
    } catch (InsufficientCapacityException e) {
        onCodecError(e);
        // Skip the sample that's too large by reading it without its data. Then flush the codec so
        // that rendering will resume from the next key frame.
        readSourceOmittingSampleData(/* readFlags= */
        0);
        flushCodec();
        return true;
    }
    if (hasReadStreamToEnd()) {
        // Notify output queue of the last buffer's timestamp.
        lastBufferInStreamPresentationTimeUs = largestQueuedPresentationTimeUs;
    }
    if (result == C.RESULT_NOTHING_READ) {
        return false;
    }
    if (result == C.RESULT_FORMAT_READ) {
        if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) {
            // We received two formats in a row. Clear the current buffer of any reconfiguration data
            // associated with the first format.
            buffer.clear();
            codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
        }
        onInputFormatChanged(formatHolder);
        return true;
    }
    // We've read a buffer.
    if (buffer.isEndOfStream()) {
        if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) {
            // We received a new format immediately before the end of the stream. We need to clear
            // the corresponding reconfiguration data from the current buffer, but re-write it into
            // a subsequent buffer if there are any (for example, if the user seeks backwards).
            buffer.clear();
            codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
        }
        inputStreamEnded = true;
        if (!codecReceivedBuffers) {
            processEndOfStream();
            return false;
        }
        try {
            if (codecNeedsEosPropagation) {
            // Do nothing.
            } else {
                codecReceivedEos = true;
                codec.queueInputBuffer(inputIndex, /* offset= */
                0, /* size= */
                0, /* presentationTimeUs= */
                0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                resetInputBuffer();
            }
        } catch (CryptoException e) {
            throw createRendererException(e, inputFormat, Util.getErrorCodeForMediaDrmErrorCode(e.getErrorCode()));
        }
        return false;
    }
    // sample that's too large to be held in one of the decoder's input buffers.
    if (!codecReceivedBuffers && !buffer.isKeyFrame()) {
        buffer.clear();
        if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) {
            // The buffer we just cleared contained reconfiguration data. We need to re-write this data
            // into a subsequent buffer (if there is one).
            codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
        }
        return true;
    }
    boolean bufferEncrypted = buffer.isEncrypted();
    if (bufferEncrypted) {
        buffer.cryptoInfo.increaseClearDataFirstSubSampleBy(adaptiveReconfigurationBytes);
    }
    if (codecNeedsDiscardToSpsWorkaround && !bufferEncrypted) {
        NalUnitUtil.discardToSps(buffer.data);
        if (buffer.data.position() == 0) {
            return true;
        }
        codecNeedsDiscardToSpsWorkaround = false;
    }
    long presentationTimeUs = buffer.timeUs;
    if (c2Mp3TimestampTracker != null) {
        presentationTimeUs = c2Mp3TimestampTracker.updateAndGetPresentationTimeUs(inputFormat, buffer);
        // When draining the C2 MP3 decoder it produces an extra non-empty buffer with a timestamp
        // after all queued input buffer timestamps (unlike other decoders, which generally propagate
        // the input timestamps to output buffers 1:1). To detect the end of the stream when this
        // buffer is dequeued we override the largest queued timestamp accordingly.
        largestQueuedPresentationTimeUs = max(largestQueuedPresentationTimeUs, c2Mp3TimestampTracker.getLastOutputBufferPresentationTimeUs(inputFormat));
    }
    if (buffer.isDecodeOnly()) {
        decodeOnlyPresentationTimestamps.add(presentationTimeUs);
    }
    if (waitingForFirstSampleInFormat) {
        formatQueue.add(presentationTimeUs, inputFormat);
        waitingForFirstSampleInFormat = false;
    }
    largestQueuedPresentationTimeUs = max(largestQueuedPresentationTimeUs, presentationTimeUs);
    buffer.flip();
    if (buffer.hasSupplementalData()) {
        handleInputBufferSupplementalData(buffer);
    }
    onQueueInputBuffer(buffer);
    try {
        if (bufferEncrypted) {
            codec.queueSecureInputBuffer(inputIndex, /* offset= */
            0, buffer.cryptoInfo, presentationTimeUs, /* flags= */
            0);
        } else {
            codec.queueInputBuffer(inputIndex, /* offset= */
            0, buffer.data.limit(), presentationTimeUs, /* flags= */
            0);
        }
    } catch (CryptoException e) {
        throw createRendererException(e, inputFormat, Util.getErrorCodeForMediaDrmErrorCode(e.getErrorCode()));
    }
    resetInputBuffer();
    codecReceivedBuffers = true;
    codecReconfigurationState = RECONFIGURATION_STATE_NONE;
    decoderCounters.queuedInputBufferCount++;
    return true;
}
Also used : FormatHolder(com.google.android.exoplayer2.FormatHolder) ReadDataResult(com.google.android.exoplayer2.source.SampleStream.ReadDataResult) InsufficientCapacityException(com.google.android.exoplayer2.decoder.DecoderInputBuffer.InsufficientCapacityException) MediaCryptoException(android.media.MediaCryptoException) CryptoException(android.media.MediaCodec.CryptoException)

Example 38 with FormatHolder

use of com.google.android.exoplayer2.FormatHolder in project ExoPlayer by google.

the class MediaCodecRenderer method bypassRead.

private void bypassRead() throws ExoPlaybackException {
    checkState(!inputStreamEnded);
    FormatHolder formatHolder = getFormatHolder();
    bypassSampleBuffer.clear();
    while (true) {
        bypassSampleBuffer.clear();
        @ReadDataResult int result = readSource(formatHolder, bypassSampleBuffer, /* readFlags= */
        0);
        switch(result) {
            case C.RESULT_FORMAT_READ:
                onInputFormatChanged(formatHolder);
                return;
            case C.RESULT_NOTHING_READ:
                return;
            case C.RESULT_BUFFER_READ:
                if (bypassSampleBuffer.isEndOfStream()) {
                    inputStreamEnded = true;
                    return;
                }
                if (waitingForFirstSampleInFormat) {
                    // This is the first buffer in a new format, the output format must be updated.
                    outputFormat = checkNotNull(inputFormat);
                    onOutputFormatChanged(outputFormat, /* mediaFormat= */
                    null);
                    waitingForFirstSampleInFormat = false;
                }
                // Try to append the buffer to the batch buffer.
                bypassSampleBuffer.flip();
                if (!bypassBatchBuffer.append(bypassSampleBuffer)) {
                    bypassSampleBufferPending = true;
                    return;
                }
                break;
            default:
                throw new IllegalStateException();
        }
    }
}
Also used : ReadDataResult(com.google.android.exoplayer2.source.SampleStream.ReadDataResult) FormatHolder(com.google.android.exoplayer2.FormatHolder)

Example 39 with FormatHolder

use of com.google.android.exoplayer2.FormatHolder in project ExoPlayer by google.

the class MetadataRenderer method readMetadata.

private void readMetadata() {
    if (!inputStreamEnded && pendingMetadata == null) {
        buffer.clear();
        FormatHolder formatHolder = getFormatHolder();
        @ReadDataResult int result = readSource(formatHolder, buffer, /* readFlags= */
        0);
        if (result == C.RESULT_BUFFER_READ) {
            if (buffer.isEndOfStream()) {
                inputStreamEnded = true;
            } else {
                buffer.subsampleOffsetUs = subsampleOffsetUs;
                buffer.flip();
                @Nullable Metadata metadata = castNonNull(decoder).decode(buffer);
                if (metadata != null) {
                    List<Metadata.Entry> entries = new ArrayList<>(metadata.length());
                    decodeWrappedMetadata(metadata, entries);
                    if (!entries.isEmpty()) {
                        Metadata expandedMetadata = new Metadata(entries);
                        pendingMetadata = expandedMetadata;
                        pendingMetadataTimestampUs = buffer.timeUs;
                    }
                }
            }
        } else if (result == C.RESULT_FORMAT_READ) {
            subsampleOffsetUs = Assertions.checkNotNull(formatHolder.format).subsampleOffsetUs;
        }
    }
}
Also used : ReadDataResult(com.google.android.exoplayer2.source.SampleStream.ReadDataResult) FormatHolder(com.google.android.exoplayer2.FormatHolder) ArrayList(java.util.ArrayList) Nullable(androidx.annotation.Nullable)

Aggregations

FormatHolder (com.google.android.exoplayer2.FormatHolder)16 Test (org.junit.Test)13 ReadDataResult (com.google.android.exoplayer2.source.SampleStream.ReadDataResult)11 Format (com.google.android.exoplayer2.Format)10 EventStream (com.google.android.exoplayer2.source.dash.manifest.EventStream)9 EventMessage (com.google.android.exoplayer2.metadata.emsg.EventMessage)8 Nullable (androidx.annotation.Nullable)7 DecoderReuseEvaluation (com.google.android.exoplayer2.decoder.DecoderReuseEvaluation)5 DecoderInputBuffer (com.google.android.exoplayer2.decoder.DecoderInputBuffer)4 DrmSession (com.google.android.exoplayer2.drm.DrmSession)4 CallSuper (androidx.annotation.CallSuper)2 DecoderException (com.google.android.exoplayer2.decoder.DecoderException)2 ExoTrackSelection (com.google.android.exoplayer2.trackselection.ExoTrackSelection)2 FixedTrackSelection (com.google.android.exoplayer2.trackselection.FixedTrackSelection)2 Before (org.junit.Before)2 CryptoException (android.media.MediaCodec.CryptoException)1 MediaCryptoException (android.media.MediaCryptoException)1 MediaFormat (android.media.MediaFormat)1 InsufficientCapacityException (com.google.android.exoplayer2.decoder.DecoderInputBuffer.InsufficientCapacityException)1 DecoderDiscardReasons (com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons)1