From c622483f798ebc6e82fef8893d3848845de0ef24 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 3 Jun 2016 10:39:39 -0700 Subject: [PATCH] Enable seamless rejoing for Vp9 extension. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=123982330 --- .../exoplayer/ext/vp9/VpxPlaybackTest.java | 2 +- .../ext/vp9/LibvpxVideoTrackRenderer.java | 53 ++++++++++++++----- .../android/exoplayer/SimpleExoPlayer.java | 29 ++++++---- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java index 068bfec351..35af59c3f8 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java @@ -88,7 +88,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase { @Override public void run() { Looper.prepare(); - LibvpxVideoTrackRenderer videoRenderer = new LibvpxVideoTrackRenderer(true); + LibvpxVideoTrackRenderer videoRenderer = new LibvpxVideoTrackRenderer(true, 0); DefaultTrackSelector trackSelector = new DefaultTrackSelector( new DefaultTrackSelectionPolicy(), null); player = ExoPlayerFactory.newInstance(new TrackRenderer[] {videoRenderer}, trackSelector); diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer/ext/vp9/LibvpxVideoTrackRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer/ext/vp9/LibvpxVideoTrackRenderer.java index 8f95e1f432..4e17ca3c2d 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer/ext/vp9/LibvpxVideoTrackRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer/ext/vp9/LibvpxVideoTrackRenderer.java @@ -58,8 +58,9 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer { public final CodecCounters codecCounters = new CodecCounters(); private final boolean scaleToFit; - private final EventDispatcher eventDispatcher; + private final long allowedJoiningTimeMs; private final int maxDroppedFrameCountToNotify; + private final EventDispatcher eventDispatcher; private final FormatHolder formatHolder; private Format format; @@ -71,6 +72,7 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer { private Bitmap bitmap; private boolean drawnToSurface; private boolean renderedFirstFrame; + private long joiningDeadlineMs; private Surface surface; private VpxOutputBufferRenderer outputBufferRenderer; private int outputMode; @@ -85,26 +87,31 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer { private int consecutiveDroppedFrameCount; /** - * @param scaleToFit Boolean that indicates if video frames should be scaled to fit when - * rendering. + * @param scaleToFit Whether video frames should be scaled to fit when rendering. + * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer + * can attempt to seamlessly join an ongoing playback. */ - public LibvpxVideoTrackRenderer(boolean scaleToFit) { - this(scaleToFit, null, null, 0); + public LibvpxVideoTrackRenderer(boolean scaleToFit, long allowedJoiningTimeMs) { + this(scaleToFit, allowedJoiningTimeMs, null, null, 0); } /** - * @param scaleToFit Boolean that indicates if video frames should be scaled to fit when - * rendering. + * @param scaleToFit Whether video frames should be scaled to fit when rendering. + * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer + * can attempt to seamlessly join an ongoing playback. * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. * @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between * invocations of {@link VideoTrackRendererEventListener#onDroppedFrames(int, long)}. */ - public LibvpxVideoTrackRenderer(boolean scaleToFit, Handler eventHandler, - VideoTrackRendererEventListener eventListener, int maxDroppedFrameCountToNotify) { + public LibvpxVideoTrackRenderer(boolean scaleToFit, long allowedJoiningTimeMs, + Handler eventHandler, VideoTrackRendererEventListener eventListener, + int maxDroppedFrameCountToNotify) { this.scaleToFit = scaleToFit; + this.allowedJoiningTimeMs = allowedJoiningTimeMs; this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify; + joiningDeadlineMs = -1; previousWidth = -1; previousHeight = -1; formatHolder = new FormatHolder(); @@ -202,10 +209,11 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer { return false; } - // Drop frame only if we have the next frame and that's also late, otherwise render whatever we - // have. - if (nextOutputBuffer != null && nextOutputBuffer.timestampUs < positionUs) { - // Drop frame if we are too late. + // Drop the frame if we're joining and are more than 30ms late, or if we have the next frame + // and that's also late. Else we'll render what we have. + if ((joiningDeadlineMs != -1 && outputBuffer.timestampUs < positionUs - 30000) + || (nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream() + && nextOutputBuffer.timestampUs < positionUs)) { codecCounters.droppedOutputBufferCount++; droppedFrameCount++; consecutiveDroppedFrameCount++; @@ -322,7 +330,21 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer { @Override protected boolean isReady() { - return format != null && (isSourceReady() || outputBuffer != null) && renderedFirstFrame; + if (format != null && (isSourceReady() || outputBuffer != null) && renderedFirstFrame) { + // Ready. If we were joining then we've now joined, so clear the joining deadline. + joiningDeadlineMs = -1; + return true; + } else if (joiningDeadlineMs == -1) { + // Not joining. + return false; + } else if (SystemClock.elapsedRealtime() < joiningDeadlineMs) { + // Joining and still within the joining deadline. + return true; + } else { + // The joining deadline has been exceeded. Give up and clear the deadline. + joiningDeadlineMs = -1; + return false; + } } @Override @@ -340,6 +362,8 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer { if (decoder != null) { flushDecoder(); } + joiningDeadlineMs = joining && allowedJoiningTimeMs > 0 + ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : -1; } @Override @@ -350,6 +374,7 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer { @Override protected void onStopped() { + joiningDeadlineMs = -1; maybeNotifyDroppedFrameCount(); } diff --git a/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java b/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java index d62030315f..2bc336df7e 100644 --- a/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java @@ -84,6 +84,8 @@ public final class SimpleExoPlayer implements ExoPlayer { } private static final String TAG = "SimpleExoPlayer"; + private static final long ALLOWED_VIDEO_JOINING_TIME_MS = 5000; + private static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50; private final ExoPlayer player; private final BandwidthMeter bandwidthMeter; @@ -381,8 +383,9 @@ public final class SimpleExoPlayer implements ExoPlayer { private void buildRenderers(Context context, DrmSessionManager drmSessionManager, ArrayList renderersList) { MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, - MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, - drmSessionManager, false, mainHandler, componentListener, 50); + MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, + ALLOWED_VIDEO_JOINING_TIME_MS, drmSessionManager, false, mainHandler, componentListener, + MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); renderersList.add(videoRenderer); TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(MediaCodecSelector.DEFAULT, @@ -405,13 +408,15 @@ public final class SimpleExoPlayer implements ExoPlayer { try { Class clazz = Class.forName("com.google.android.exoplayer.ext.vp9.LibvpxVideoTrackRenderer"); - Constructor constructor = clazz.getConstructor(boolean.class, Handler.class, + Constructor constructor = clazz.getConstructor(boolean.class, long.class, Handler.class, VideoTrackRendererEventListener.class, int.class); - renderersList.add((TrackRenderer) constructor.newInstance(true, mainHandler, - componentListener, 50)); + renderersList.add((TrackRenderer) constructor.newInstance(true, ALLOWED_VIDEO_JOINING_TIME_MS, + mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); Log.i(TAG, "Loaded LibvpxVideoTrackRenderer."); - } catch (Exception e) { + } catch (ClassNotFoundException e) { // Expected if the app was built without the extension. + } catch (Exception e) { + throw new RuntimeException(e); } try { @@ -421,8 +426,10 @@ public final class SimpleExoPlayer implements ExoPlayer { AudioTrackRendererEventListener.class); renderersList.add((TrackRenderer) constructor.newInstance(mainHandler, componentListener)); Log.i(TAG, "Loaded LibopusAudioTrackRenderer."); - } catch (Exception e) { + } catch (ClassNotFoundException e) { // Expected if the app was built without the extension. + } catch (Exception e) { + throw new RuntimeException(e); } try { @@ -432,8 +439,10 @@ public final class SimpleExoPlayer implements ExoPlayer { AudioTrackRendererEventListener.class); renderersList.add((TrackRenderer) constructor.newInstance(mainHandler, componentListener)); Log.i(TAG, "Loaded LibflacAudioTrackRenderer."); - } catch (Exception e) { + } catch (ClassNotFoundException e) { // Expected if the app was built without the extension. + } catch (Exception e) { + throw new RuntimeException(e); } try { @@ -443,8 +452,10 @@ public final class SimpleExoPlayer implements ExoPlayer { AudioTrackRendererEventListener.class); renderersList.add((TrackRenderer) constructor.newInstance(mainHandler, componentListener)); Log.i(TAG, "Loaded FfmpegAudioTrackRenderer."); - } catch (Exception e) { + } catch (ClassNotFoundException e) { // Expected if the app was built without the extension. + } catch (Exception e) { + throw new RuntimeException(e); } }