From d02e1df4b42edaaaae171c0677730bec677fdc58 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 27 Oct 2017 19:22:59 +0100 Subject: [PATCH] Clean up VideoFrameReleaseTimeHelper --- .../video/VideoFrameReleaseTimeHelper.java | 174 +++++++++--------- 1 file changed, 88 insertions(+), 86 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java index c5ca212a36..9036b19a75 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java @@ -18,15 +18,16 @@ package com.google.android.exoplayer2.video; import android.annotation.TargetApi; import android.content.Context; import android.hardware.display.DisplayManager; -import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; +import android.support.annotation.Nullable; import android.view.Choreographer; import android.view.Choreographer.FrameCallback; import android.view.Display; import android.view.WindowManager; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.Util; /** * Makes a best effort to adjust frame release timestamps for a smoother visual result. @@ -34,20 +35,18 @@ import com.google.android.exoplayer2.C; @TargetApi(16) public final class VideoFrameReleaseTimeHelper { - private static final double DISPLAY_REFRESH_RATE_UNKNOWN = -1; private static final long CHOREOGRAPHER_SAMPLE_DELAY_MILLIS = 500; private static final long MAX_ALLOWED_DRIFT_NS = 20000000; private static final long VSYNC_OFFSET_PERCENTAGE = 80; private static final int MIN_FRAMES_FOR_ADJUSTMENT = 6; - private Context context = null; - - private final DefaultDisplayListener defaultDisplayListener; + private final WindowManager windowManager; private final VSyncSampler vsyncSampler; - private final boolean useDefaultDisplayVsync; - private long vsyncDurationNs = -1; // Value unused. - private long vsyncOffsetNs = -1; // Value unused. + private final DefaultDisplayListener displayListener; + + private long vsyncDurationNs; + private long vsyncOffsetNs; private long lastFramePresentationTimeUs; private long adjustedLastFrameTimeNs; @@ -63,10 +62,7 @@ public final class VideoFrameReleaseTimeHelper { * the default display's vsync signal. */ public VideoFrameReleaseTimeHelper() { - defaultDisplayListener = null; - useDefaultDisplayVsync = false; - vsyncSampler = null; - context = null; + this(null); } /** @@ -75,46 +71,48 @@ public final class VideoFrameReleaseTimeHelper { * * @param context A context from which information about the default display can be retrieved. */ - public VideoFrameReleaseTimeHelper(Context context) { - this.context = context.getApplicationContext(); - defaultDisplayListener = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ? - new DefaultDisplayListener(context) : null; - useDefaultDisplayVsync = true; - vsyncSampler = VSyncSampler.getInstance(); + public VideoFrameReleaseTimeHelper(@Nullable Context context) { + windowManager = context == null ? null + : (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + if (windowManager != null) { + displayListener = Util.SDK_INT >= 17 ? maybeBuildDefaultDisplayListenerV17(context) : null; + vsyncSampler = VSyncSampler.getInstance(); + } else { + displayListener = null; + vsyncSampler = null; + } + vsyncDurationNs = C.TIME_UNSET; + vsyncOffsetNs = C.TIME_UNSET; } - /** - * Enables the helper. + * Enables the helper. Must be called from the playback thread. */ public void enable() { haveSync = false; - if (useDefaultDisplayVsync) { + if (windowManager != null) { vsyncSampler.addObserver(); - setSync(getDefaultDisplayRefreshRate(context)); - if (defaultDisplayListener != null) - defaultDisplayListener.register(); + if (displayListener != null) { + displayListener.register(); + } + updateDefaultDisplayRefreshRateParams(); } } /** - * Disables the helper. + * Disables the helper. Must be called from the playback thread. */ public void disable() { - if (useDefaultDisplayVsync) { + if (windowManager != null) { + if (displayListener != null) { + displayListener.unregister(); + } vsyncSampler.removeObserver(); - if (defaultDisplayListener != null) - defaultDisplayListener.unregister(); } } - private void setSync(double defaultDisplayRefreshRate) { - vsyncDurationNs = (long) (C.NANOS_PER_SECOND / defaultDisplayRefreshRate); - vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100; - } - /** - * Adjusts a frame release timestamp. + * Adjusts a frame release timestamp. Must be called from the playback thread. * * @param framePresentationTimeUs The frame's presentation time, in microseconds. * @param unadjustedReleaseTimeNs The frame's unadjusted release time, in nanoseconds and in @@ -167,25 +165,39 @@ public final class VideoFrameReleaseTimeHelper { syncUnadjustedReleaseTimeNs = unadjustedReleaseTimeNs; frameCount = 0; haveSync = true; - onSynced(); } lastFramePresentationTimeUs = framePresentationTimeUs; pendingAdjustedFrameTimeNs = adjustedFrameTimeNs; - if (vsyncSampler == null || vsyncSampler.sampledVsyncTimeNs == 0) { + if (vsyncSampler == null || vsyncDurationNs == C.TIME_UNSET) { + return adjustedReleaseTimeNs; + } + long sampledVsyncTimeNs = vsyncSampler.sampledVsyncTimeNs; + if (sampledVsyncTimeNs == C.TIME_UNSET) { return adjustedReleaseTimeNs; } // Find the timestamp of the closest vsync. This is the vsync that we're targeting. - long snappedTimeNs = closestVsync(adjustedReleaseTimeNs, - vsyncSampler.sampledVsyncTimeNs, vsyncDurationNs); + long snappedTimeNs = closestVsync(adjustedReleaseTimeNs, sampledVsyncTimeNs, vsyncDurationNs); // Apply an offset so that we release before the target vsync, but after the previous one. return snappedTimeNs - vsyncOffsetNs; } - protected void onSynced() { - // Do nothing. + @TargetApi(17) + private DefaultDisplayListener maybeBuildDefaultDisplayListenerV17(Context context) { + DisplayManager manager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + return manager == null ? null : new DefaultDisplayListener(manager); + } + + private void updateDefaultDisplayRefreshRateParams() { + // Note: If we fail to update the parameters, we leave them set to their previous values. + Display defaultDisplay = windowManager.getDefaultDisplay(); + if (defaultDisplay != null) { + double defaultDisplayRefreshRate = defaultDisplay.getRefreshRate(); + vsyncDurationNs = (long) (C.NANOS_PER_SECOND / defaultDisplayRefreshRate); + vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100; + } } private boolean isDriftTooLarge(long frameTimeNs, long releaseTimeNs) { @@ -211,10 +223,40 @@ public final class VideoFrameReleaseTimeHelper { return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs; } - private static double getDefaultDisplayRefreshRate(Context context) { - WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - return manager != null && manager.getDefaultDisplay() != null ? - manager.getDefaultDisplay().getRefreshRate() : DISPLAY_REFRESH_RATE_UNKNOWN; + @TargetApi(17) + private final class DefaultDisplayListener implements DisplayManager.DisplayListener { + + private final DisplayManager displayManager; + + public DefaultDisplayListener(DisplayManager displayManager) { + this.displayManager = displayManager; + } + + public void register() { + displayManager.registerDisplayListener(this, null); + } + + public void unregister() { + displayManager.unregisterDisplayListener(this); + } + + @Override + public void onDisplayAdded(int displayId) { + // Do nothing. + } + + @Override + public void onDisplayRemoved(int displayId) { + // Do nothing. + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == Display.DEFAULT_DISPLAY) { + updateDefaultDisplayRefreshRateParams(); + } + } + } /** @@ -242,6 +284,7 @@ public final class VideoFrameReleaseTimeHelper { } private VSyncSampler() { + sampledVsyncTimeNs = C.TIME_UNSET; choreographerOwnerThread = new HandlerThread("ChoreographerOwner:Handler"); choreographerOwnerThread.start(); handler = new Handler(choreographerOwnerThread.getLooper(), this); @@ -306,48 +349,7 @@ public final class VideoFrameReleaseTimeHelper { observerCount--; if (observerCount == 0) { choreographer.removeFrameCallback(this); - sampledVsyncTimeNs = 0; - } - } - - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - private class DefaultDisplayListener implements DisplayManager.DisplayListener { - - private final Context context; - private final DisplayManager displayManager; - - DefaultDisplayListener(Context context) { - this.context = context; - displayManager = context != null ? - (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE) : null; - } - - @Override - public void onDisplayAdded(int displayId) { - } - - @Override - public void onDisplayRemoved(int displayId) { - } - - @Override - public void onDisplayChanged(int displayId) { - if (displayId == Display.DEFAULT_DISPLAY) { - setSync(getDefaultDisplayRefreshRate(context)); - } - } - - public void register() { - if (displayManager != null && context != null) { // context is used on callback - displayManager.registerDisplayListener(this, null); - } - } - - public void unregister() { - if (displayManager != null) { - displayManager.unregisterDisplayListener(this); + sampledVsyncTimeNs = C.TIME_UNSET; } }