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;
}
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;
}
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();
}
}
}
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;
}
}
}
Aggregations