From 55f696e11d6aa3fef22539d2eb998bb19bd0eeb5 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 27 Sep 2017 03:14:20 -0700 Subject: [PATCH] Fix spurious failures due to late decoding. By default, if a codec is instantiated during an ongoing playback, ExoPlayer will render the first frame that it receives (so that there's "something other than black" drawn to the surface). This frame is the key-frame before the current playback position, and may be as much as 5 seconds behind the current position. ExoPlayer then drops subsequent frames that are late until it's caught up to the current position again. For GTS tests that are counting dropped frames, this is not desirable behavior, since it will cause spurious test failures in cases where DummySurface is not supported. This change overrides the default behavior so that the player instead skips (rather than drops) frames until it's caught up to the current playback position, and only then renders the first frame. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=170175944 --- .../testutil/DebugRenderersFactory.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java index af7c1a3e2a..b63afd3984 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java @@ -17,6 +17,8 @@ package com.google.android.exoplayer2.testutil; import android.annotation.TargetApi; import android.content.Context; +import android.media.MediaCodec; +import android.media.MediaCrypto; import android.os.Handler; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlaybackException; @@ -25,9 +27,12 @@ import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; +import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; +import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoRendererEventListener; +import java.nio.ByteBuffer; import java.util.ArrayList; /** @@ -66,6 +71,7 @@ public class DebugRenderersFactory extends DefaultRenderersFactory { private int queueSize; private int bufferCount; private int minimumInsertIndex; + private boolean skipToPositionBeforeRenderingFirstFrame; public DebugMediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector, long allowedJoiningTimeMs, DrmSessionManager drmSessionManager, @@ -75,10 +81,23 @@ public class DebugRenderersFactory extends DefaultRenderersFactory { eventHandler, eventListener, maxDroppedFrameCountToNotify); } + @Override + protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format, + MediaCrypto crypto) throws DecoderQueryException { + // If the codec is being initialized whilst the renderer is started, default behavior is to + // render the first frame (i.e. the keyframe before the current position), then drop frames up + // to the current playback position. For test runs that place a maximum limit on the number of + // dropped frames allowed, this is not desired behavior. Hence we skip (rather than drop) + // frames up to the current playback position [Internal: b/66494991]. + skipToPositionBeforeRenderingFirstFrame = getState() == Renderer.STATE_STARTED; + super.configureCodec(codecInfo, codec, format, crypto); + } + @Override protected void releaseCodec() { super.releaseCodec(); clearTimestamps(); + skipToPositionBeforeRenderingFirstFrame = false; } @Override @@ -102,6 +121,34 @@ public class DebugRenderersFactory extends DefaultRenderersFactory { maybeShiftTimestampsList(); } + @Override + protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, + ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, + boolean shouldSkip) { + if (skipToPositionBeforeRenderingFirstFrame && bufferPresentationTimeUs < positionUs) { + // After the codec has been initialized, don't render the first frame until we've caught up + // to the playback position. Else test runs on devices that do not support dummy surface + // will drop frames between rendering the first one and catching up [Internal: b/66494991]. + shouldSkip = true; + } + return super.processOutputBuffer(positionUs, elapsedRealtimeUs, codec, buffer, bufferIndex, + bufferFlags, bufferPresentationTimeUs, shouldSkip); + } + + @Override + protected void renderOutputBuffer(MediaCodec codec, int index, long presentationTimeUs) { + skipToPositionBeforeRenderingFirstFrame = false; + super.renderOutputBuffer(codec, index, presentationTimeUs); + } + + @TargetApi(21) + @Override + protected void renderOutputBufferV21(MediaCodec codec, int index, long presentationTimeUs, + long releaseTimeNs) { + skipToPositionBeforeRenderingFirstFrame = false; + super.renderOutputBufferV21(codec, index, presentationTimeUs, releaseTimeNs); + } + @Override protected void onProcessedOutputBuffer(long presentationTimeUs) { super.onProcessedOutputBuffer(presentationTimeUs);