From 60794d893f0362c321e3028254ed84b6ee2e1cf8 Mon Sep 17 00:00:00 2001 From: Steve Mayhew Date: Fri, 6 Sep 2019 12:15:27 -0700 Subject: [PATCH 1/2] Fix so video renderer correctly reports ready in tunneling mode. In tunneled playback mode the video renderer is potentially 'ready' with output at anytime there are buffers queued to the codec. This fix add the more specific boolean `hasOuputReady()` and uses this in the `isReady()` check. This changes fixes issue #6366 The call to dequeueOutputBuffer() simply returns TRY_AGAIN always in tunneled mode (this comment is correct: https://github.com/google/ExoPlayer/issues/1688#issuecomment-273970792 so the super.hasOutputReady() is always false in tunneling mode, even though the codec may have output ready. --- .../mediacodec/MediaCodecRenderer.java | 16 +++++++++++++++- .../video/MediaCodecVideoRenderer.java | 12 ++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index bb772a70255..8ea810c46c5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -34,6 +34,7 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; +import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.decoder.CryptoInfo; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; @@ -1144,6 +1145,19 @@ private boolean hasOutputBuffer() { return outputIndex >= 0; } + /** + * Check if the renderer has one or more output frames queued to render. For non-tunneled + * mode MediaCodecRenderer's this is true if the codec has returned a frame ready to render + * (that is {@see #hasOutputBuffer() is true}. + * + * This factors into the ready {@link Renderer#isReady()} decision + * + * @return + */ + protected boolean hasOutputReady() { + return hasOutputBuffer(); + } + private void resetInputBuffer() { inputIndex = C.INDEX_UNSET; buffer.data = null; @@ -1528,7 +1542,7 @@ public boolean isReady() { return inputFormat != null && !waitingForKeys && (isSourceReady() - || hasOutputBuffer() + || hasOutputReady() || (codecHotswapDeadlineMs != C.TIME_UNSET && SystemClock.elapsedRealtime() < codecHotswapDeadlineMs)); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 794bc5f7e4e..093261dbdee 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -400,6 +400,18 @@ public boolean isReady() { } } + /** + * Override, to handle tunneling. In the tunneled case we must assume there is + * output ready to render whenever we have queued any sample buffers to the codec that + * it has not reported as rendered. + * + * @return + */ + @Override + protected boolean hasOutputReady() { + return tunneling && buffersInCodecCount > 0 || super.hasOutputReady(); + } + @Override protected void onStarted() { super.onStarted(); From 59fd033569ecdb7a4acd5f47f6bb0da71f6f4436 Mon Sep 17 00:00:00 2001 From: dpolyakova Date: Wed, 25 Mar 2020 23:17:17 -0700 Subject: [PATCH 2/2] Improved video stall detection by using queued FIFO length as indicator. This allows player to go into buffering state before video playback starts freezing. --- .../video/MediaCodecVideoRenderer.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 093261dbdee..1616c868861 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -145,6 +145,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { /* package */ @Nullable OnFrameRenderedListenerV23 tunnelingOnFrameRenderedListener; @Nullable private VideoFrameMetadataListener frameMetadataListener; + private long lastInputTimeUs; + private long lastOutputTimeUs; /** * @param context A context. * @param mediaCodecSelector A decoder selector. @@ -231,6 +233,8 @@ public MediaCodecVideoRenderer( frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(this.context); eventDispatcher = new EventDispatcher(eventHandler, eventListener); deviceNeedsNoPostProcessWorkaround = deviceNeedsNoPostProcessWorkaround(); + lastInputTimeUs = C.TIME_UNSET; + lastOutputTimeUs = C.TIME_UNSET; joiningDeadlineMs = C.TIME_UNSET; currentWidth = Format.NO_VALUE; currentHeight = Format.NO_VALUE; @@ -370,6 +374,8 @@ protected void onPositionReset(long positionUs, boolean joining) throws ExoPlayb clearRenderedFirstFrame(); initialPositionUs = C.TIME_UNSET; consecutiveDroppedFrameCount = 0; + lastInputTimeUs = C.TIME_UNSET; + lastOutputTimeUs = C.TIME_UNSET; if (joining) { setJoiningDeadlineMs(); } else { @@ -409,7 +415,15 @@ public boolean isReady() { */ @Override protected boolean hasOutputReady() { - return tunneling && buffersInCodecCount > 0 || super.hasOutputReady(); + boolean fifoReady = true; + if (lastOutputTimeUs != C.TIME_UNSET) { + long fifoLengthUs = lastInputTimeUs - lastOutputTimeUs; + + // make sure there is at least 1/3s of video available in decoder FIFO, + // otherwise decoder may start stalling + fifoReady = fifoLengthUs > 333333; + } + return tunneling && fifoReady || super.hasOutputReady(); } @Override @@ -858,6 +872,7 @@ private void notifyFrameMetadataListener( /** Called when a buffer was processed in tunneling mode. */ protected void onProcessedTunneledBuffer(long presentationTimeUs) { + lastOutputTimeUs = presentationTimeUs; @Nullable Format format = updateOutputFormatForTime(presentationTimeUs); if (format != null) { processOutputFormat(getCodec(), format.width, format.height);