Commit d02e1df4 by Oliver Woodman

Clean up VideoFrameReleaseTimeHelper

parent eb54da59
...@@ -18,15 +18,16 @@ package com.google.android.exoplayer2.video; ...@@ -18,15 +18,16 @@ package com.google.android.exoplayer2.video;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.Message; import android.os.Message;
import android.support.annotation.Nullable;
import android.view.Choreographer; import android.view.Choreographer;
import android.view.Choreographer.FrameCallback; import android.view.Choreographer.FrameCallback;
import android.view.Display; import android.view.Display;
import android.view.WindowManager; import android.view.WindowManager;
import com.google.android.exoplayer2.C; 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. * Makes a best effort to adjust frame release timestamps for a smoother visual result.
...@@ -34,20 +35,18 @@ import com.google.android.exoplayer2.C; ...@@ -34,20 +35,18 @@ import com.google.android.exoplayer2.C;
@TargetApi(16) @TargetApi(16)
public final class VideoFrameReleaseTimeHelper { 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 CHOREOGRAPHER_SAMPLE_DELAY_MILLIS = 500;
private static final long MAX_ALLOWED_DRIFT_NS = 20000000; private static final long MAX_ALLOWED_DRIFT_NS = 20000000;
private static final long VSYNC_OFFSET_PERCENTAGE = 80; private static final long VSYNC_OFFSET_PERCENTAGE = 80;
private static final int MIN_FRAMES_FOR_ADJUSTMENT = 6; private static final int MIN_FRAMES_FOR_ADJUSTMENT = 6;
private Context context = null; private final WindowManager windowManager;
private final DefaultDisplayListener defaultDisplayListener;
private final VSyncSampler vsyncSampler; private final VSyncSampler vsyncSampler;
private final boolean useDefaultDisplayVsync; private final DefaultDisplayListener displayListener;
private long vsyncDurationNs = -1; // Value unused.
private long vsyncOffsetNs = -1; // Value unused. private long vsyncDurationNs;
private long vsyncOffsetNs;
private long lastFramePresentationTimeUs; private long lastFramePresentationTimeUs;
private long adjustedLastFrameTimeNs; private long adjustedLastFrameTimeNs;
...@@ -63,10 +62,7 @@ public final class VideoFrameReleaseTimeHelper { ...@@ -63,10 +62,7 @@ public final class VideoFrameReleaseTimeHelper {
* the default display's vsync signal. * the default display's vsync signal.
*/ */
public VideoFrameReleaseTimeHelper() { public VideoFrameReleaseTimeHelper() {
defaultDisplayListener = null; this(null);
useDefaultDisplayVsync = false;
vsyncSampler = null;
context = null;
} }
/** /**
...@@ -75,46 +71,48 @@ public final class VideoFrameReleaseTimeHelper { ...@@ -75,46 +71,48 @@ public final class VideoFrameReleaseTimeHelper {
* *
* @param context A context from which information about the default display can be retrieved. * @param context A context from which information about the default display can be retrieved.
*/ */
public VideoFrameReleaseTimeHelper(Context context) { public VideoFrameReleaseTimeHelper(@Nullable Context context) {
this.context = context.getApplicationContext(); windowManager = context == null ? null
defaultDisplayListener = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ? : (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
new DefaultDisplayListener(context) : null; if (windowManager != null) {
useDefaultDisplayVsync = true; displayListener = Util.SDK_INT >= 17 ? maybeBuildDefaultDisplayListenerV17(context) : null;
vsyncSampler = VSyncSampler.getInstance(); 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() { public void enable() {
haveSync = false; haveSync = false;
if (useDefaultDisplayVsync) { if (windowManager != null) {
vsyncSampler.addObserver(); vsyncSampler.addObserver();
setSync(getDefaultDisplayRefreshRate(context)); if (displayListener != null) {
if (defaultDisplayListener != null) displayListener.register();
defaultDisplayListener.register(); }
updateDefaultDisplayRefreshRateParams();
} }
} }
/** /**
* Disables the helper. * Disables the helper. Must be called from the playback thread.
*/ */
public void disable() { public void disable() {
if (useDefaultDisplayVsync) { if (windowManager != null) {
if (displayListener != null) {
displayListener.unregister();
}
vsyncSampler.removeObserver(); 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 framePresentationTimeUs The frame's presentation time, in microseconds.
* @param unadjustedReleaseTimeNs The frame's unadjusted release time, in nanoseconds and in * @param unadjustedReleaseTimeNs The frame's unadjusted release time, in nanoseconds and in
...@@ -167,25 +165,39 @@ public final class VideoFrameReleaseTimeHelper { ...@@ -167,25 +165,39 @@ public final class VideoFrameReleaseTimeHelper {
syncUnadjustedReleaseTimeNs = unadjustedReleaseTimeNs; syncUnadjustedReleaseTimeNs = unadjustedReleaseTimeNs;
frameCount = 0; frameCount = 0;
haveSync = true; haveSync = true;
onSynced();
} }
lastFramePresentationTimeUs = framePresentationTimeUs; lastFramePresentationTimeUs = framePresentationTimeUs;
pendingAdjustedFrameTimeNs = adjustedFrameTimeNs; 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; return adjustedReleaseTimeNs;
} }
// Find the timestamp of the closest vsync. This is the vsync that we're targeting. // Find the timestamp of the closest vsync. This is the vsync that we're targeting.
long snappedTimeNs = closestVsync(adjustedReleaseTimeNs, long snappedTimeNs = closestVsync(adjustedReleaseTimeNs, sampledVsyncTimeNs, vsyncDurationNs);
vsyncSampler.sampledVsyncTimeNs, vsyncDurationNs);
// Apply an offset so that we release before the target vsync, but after the previous one. // Apply an offset so that we release before the target vsync, but after the previous one.
return snappedTimeNs - vsyncOffsetNs; return snappedTimeNs - vsyncOffsetNs;
} }
protected void onSynced() { @TargetApi(17)
// Do nothing. 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) { private boolean isDriftTooLarge(long frameTimeNs, long releaseTimeNs) {
...@@ -211,10 +223,40 @@ public final class VideoFrameReleaseTimeHelper { ...@@ -211,10 +223,40 @@ public final class VideoFrameReleaseTimeHelper {
return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs; return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs;
} }
private static double getDefaultDisplayRefreshRate(Context context) { @TargetApi(17)
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); private final class DefaultDisplayListener implements DisplayManager.DisplayListener {
return manager != null && manager.getDefaultDisplay() != null ?
manager.getDefaultDisplay().getRefreshRate() : DISPLAY_REFRESH_RATE_UNKNOWN; 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 { ...@@ -242,6 +284,7 @@ public final class VideoFrameReleaseTimeHelper {
} }
private VSyncSampler() { private VSyncSampler() {
sampledVsyncTimeNs = C.TIME_UNSET;
choreographerOwnerThread = new HandlerThread("ChoreographerOwner:Handler"); choreographerOwnerThread = new HandlerThread("ChoreographerOwner:Handler");
choreographerOwnerThread.start(); choreographerOwnerThread.start();
handler = new Handler(choreographerOwnerThread.getLooper(), this); handler = new Handler(choreographerOwnerThread.getLooper(), this);
...@@ -306,48 +349,7 @@ public final class VideoFrameReleaseTimeHelper { ...@@ -306,48 +349,7 @@ public final class VideoFrameReleaseTimeHelper {
observerCount--; observerCount--;
if (observerCount == 0) { if (observerCount == 0) {
choreographer.removeFrameCallback(this); choreographer.removeFrameCallback(this);
sampledVsyncTimeNs = 0; sampledVsyncTimeNs = C.TIME_UNSET;
}
}
}
@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);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment