mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Clean up VideoFrameReleaseTimeHelper
This commit is contained in:
parent
eb54da596d
commit
d02e1df4b4
1 changed files with 88 additions and 86 deletions
|
|
@ -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;
|
||||||
@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 {
|
||||||
* 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 {
|
||||||
*
|
*
|
||||||
* @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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue