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..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 { @@ -400,6 +406,26 @@ 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() { + 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 protected void onStarted() { super.onStarted(); @@ -846,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);