From 4481b3567e6851c2352d58dc54f7efb2432f598f Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 27 Sep 2024 10:04:11 -0700 Subject: [PATCH] Add workaround for codecs not propagating EOS signal. If a codec received the EOS signal and already returned the last output buffer, we should expect the output EOS very quickly. If it doesn't arrive within 100ms, we can proceed to end the stream manually without waiting any further to prevent cases where the codec is completely stuck otherwise. PiperOrigin-RevId: 679633116 --- RELEASENOTES.md | 2 ++ .../exoplayer/mediacodec/MediaCodecRenderer.java | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index df6e763e39..e281a7d987 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -67,6 +67,8 @@ * Add workaround for a device issue on Galaxy Tab S7 FE that causes 60fps secure H264 streams to be marked as unsupported ([#1619](https://github.com/androidx/media/issues/1619)). + * Add workaround for codecs that get stuck after the last sample without + returning an end-of-stream signal. * Text: * Metadata: * Assign the `C.TRACK_TYPE_METADATA` type to tracks containing icy or diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java index 330e47adde..64887aecc3 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java @@ -367,6 +367,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private boolean codecNeedsAdaptationWorkaroundBuffer; private boolean shouldSkipAdaptationWorkaroundOutputBuffer; private boolean codecNeedsEosPropagation; + private long lastOutputBufferProcessedRealtimeMs; private boolean codecRegisteredOnBufferAvailableListener; private long codecHotswapDeadlineMs; private int inputIndex; @@ -447,6 +448,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { largestQueuedPresentationTimeUs = C.TIME_UNSET; lastBufferInStreamPresentationTimeUs = C.TIME_UNSET; lastProcessedOutputBufferTimeUs = C.TIME_UNSET; + lastOutputBufferProcessedRealtimeMs = C.TIME_UNSET; codecDrainState = DRAIN_STATE_NONE; codecDrainAction = DRAIN_ACTION_NONE; decoderCounters = new DecoderCounters(); @@ -973,6 +975,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { resetOutputBuffer(); codecHotswapDeadlineMs = C.TIME_UNSET; codecReceivedEos = false; + lastOutputBufferProcessedRealtimeMs = C.TIME_UNSET; codecReceivedBuffers = false; codecNeedsAdaptationWorkaroundBuffer = false; shouldSkipAdaptationWorkaroundOutputBuffer = false; @@ -1997,6 +2000,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { && (inputStreamEnded || codecDrainState == DRAIN_STATE_WAIT_END_OF_STREAM)) { processEndOfStream(); } + if (lastOutputBufferProcessedRealtimeMs != C.TIME_UNSET + && lastOutputBufferProcessedRealtimeMs + 100 < getClock().currentTimeMillis()) { + // We processed the last output buffer more than 100ms ago without + // receiving an EOS buffer. This is likely a misbehaving codec, so + // process the end of stream manually. See b/359634542. + processEndOfStream(); + } return false; } @@ -2071,6 +2081,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (processedOutputBuffer) { onProcessedOutputBuffer(outputBufferInfo.presentationTimeUs); boolean isEndOfStream = (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; + if (!isEndOfStream && codecReceivedEos && isLastOutputBuffer) { + lastOutputBufferProcessedRealtimeMs = getClock().currentTimeMillis(); + } resetOutputBuffer(); if (!isEndOfStream) { return true;