Commit bcb9f828 by Oliver Woodman

Enable SmoothFrameTimeHelper by default.

Context:
- Currently, playback is significantly more juddery with it disabled,
  particularly on AndroidTV.
- We should be able to do the "best" job of this internally, so injection
  doesn't buy anything useful. If someone has a better implementation for
  adjusting the frame release, they should improve the core library.
parent 9b4e9723
...@@ -219,8 +219,8 @@ public class DashRendererBuilder implements RendererBuilder { ...@@ -219,8 +219,8 @@ public class DashRendererBuilder implements RendererBuilder {
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
DemoPlayer.TYPE_VIDEO); DemoPlayer.TYPE_VIDEO);
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource,
drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, true,
mainHandler, player, 50); mainHandler, player, 50);
// Build the audio renderer. // Build the audio renderer.
......
...@@ -61,8 +61,8 @@ public class ExtractorRendererBuilder implements RendererBuilder { ...@@ -61,8 +61,8 @@ public class ExtractorRendererBuilder implements RendererBuilder {
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator, ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator,
BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE); BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, player.getMainHandler(), sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, player.getMainHandler(),
player, 50); player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context)); null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context));
......
...@@ -148,8 +148,8 @@ public class HlsRendererBuilder implements RendererBuilder { ...@@ -148,8 +148,8 @@ public class HlsRendererBuilder implements RendererBuilder {
variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE); variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl, HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO); BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50); sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context)); null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context));
MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>( MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>(
......
...@@ -163,9 +163,9 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder { ...@@ -163,9 +163,9 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder {
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
DemoPlayer.TYPE_VIDEO); DemoPlayer.TYPE_VIDEO);
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource,
drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, true, mainHandler,
mainHandler, player, 50); player, 50);
// Build the audio renderer. // Build the audio renderer.
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
......
...@@ -15,17 +15,17 @@ ...@@ -15,17 +15,17 @@
*/ */
package com.google.android.exoplayer; package com.google.android.exoplayer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer.FrameReleaseTimeHelper;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context;
import android.view.Choreographer; import android.view.Choreographer;
import android.view.Choreographer.FrameCallback; import android.view.Choreographer.FrameCallback;
import android.view.WindowManager;
/** /**
* 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.
*/ */
@TargetApi(16) @TargetApi(16)
public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelper, FrameCallback { public final class VideoFrameReleaseTimeHelper implements FrameCallback {
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;
...@@ -33,32 +33,45 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -33,32 +33,45 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
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 final boolean usePrimaryDisplayVsync; private final boolean useDefaultDisplayVsync;
private final long vsyncDurationNs; private final long vsyncDurationNs;
private final long vsyncOffsetNs; private final long vsyncOffsetNs;
private Choreographer choreographer; private Choreographer choreographer;
private long sampledVsyncTimeNs; private long sampledVsyncTimeNs;
private long lastUnadjustedFrameTimeUs; private long lastFramePresentationTimeUs;
private long adjustedLastFrameTimeNs; private long adjustedLastFrameTimeNs;
private long pendingAdjustedFrameTimeNs; private long pendingAdjustedFrameTimeNs;
private boolean haveSync; private boolean haveSync;
private long syncReleaseTimeNs; private long syncUnadjustedReleaseTimeNs;
private long syncFrameTimeNs; private long syncFramePresentationTimeNs;
private int frameCount; private long frameCount;
/**
* Constructs an instance that smoothes frame release but does not snap release to the default
* display's vsync signal.
*/
public VideoFrameReleaseTimeHelper() {
this(-1, false);
}
/** /**
* @param primaryDisplayRefreshRate The refresh rate of the default display. * Constructs an instance that smoothes frame release and snaps release to the default display's
* @param usePrimaryDisplayVsync Whether to snap to the primary display vsync. May not be * vsync signal.
* suitable when rendering to secondary displays. *
* @param context A context from which information about the default display can be retrieved.
*/ */
public SmoothFrameReleaseTimeHelper( public VideoFrameReleaseTimeHelper(Context context) {
float primaryDisplayRefreshRate, boolean usePrimaryDisplayVsync) { this(getDefaultDisplayRefreshRate(context), true);
this.usePrimaryDisplayVsync = usePrimaryDisplayVsync; }
if (usePrimaryDisplayVsync) {
vsyncDurationNs = (long) (1000000000d / primaryDisplayRefreshRate); private VideoFrameReleaseTimeHelper(float defaultDisplayRefreshRate,
boolean useDefaultDisplayVsync) {
this.useDefaultDisplayVsync = useDefaultDisplayVsync;
if (useDefaultDisplayVsync) {
vsyncDurationNs = (long) (1000000000d / defaultDisplayRefreshRate);
vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100; vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100;
} else { } else {
vsyncDurationNs = -1; vsyncDurationNs = -1;
...@@ -66,19 +79,23 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -66,19 +79,23 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
} }
} }
@Override /**
* Enables the helper.
*/
public void enable() { public void enable() {
haveSync = false; haveSync = false;
if (usePrimaryDisplayVsync) { if (useDefaultDisplayVsync) {
sampledVsyncTimeNs = 0; sampledVsyncTimeNs = 0;
choreographer = Choreographer.getInstance(); choreographer = Choreographer.getInstance();
choreographer.postFrameCallback(this); choreographer.postFrameCallback(this);
} }
} }
@Override /**
* Disables the helper.
*/
public void disable() { public void disable() {
if (usePrimaryDisplayVsync) { if (useDefaultDisplayVsync) {
choreographer.removeFrameCallback(this); choreographer.removeFrameCallback(this);
choreographer = null; choreographer = null;
} }
...@@ -90,17 +107,25 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -90,17 +107,25 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
choreographer.postFrameCallbackDelayed(this, CHOREOGRAPHER_SAMPLE_DELAY_MILLIS); choreographer.postFrameCallbackDelayed(this, CHOREOGRAPHER_SAMPLE_DELAY_MILLIS);
} }
@Override /**
public long adjustReleaseTime(long unadjustedFrameTimeUs, long unadjustedReleaseTimeNs) { * Called to make a fine-grained adjustment to a frame release time.
long unadjustedFrameTimeNs = unadjustedFrameTimeUs * 1000; *
* @param framePresentationTimeUs The frame's media presentation time, in microseconds.
* @param unadjustedReleaseTimeNs The frame's unadjusted release time, in nanoseconds and in
* the same time base as {@link System#nanoTime()}.
* @return An adjusted release time for the frame, in nanoseconds and in the same time base as
* {@link System#nanoTime()}.
*/
public long adjustReleaseTime(long framePresentationTimeUs, long unadjustedReleaseTimeNs) {
long framePresentationTimeNs = framePresentationTimeUs * 1000;
// Until we know better, the adjustment will be a no-op. // Until we know better, the adjustment will be a no-op.
long adjustedFrameTimeNs = unadjustedFrameTimeNs; long adjustedFrameTimeNs = framePresentationTimeNs;
long adjustedReleaseTimeNs = unadjustedReleaseTimeNs; long adjustedReleaseTimeNs = unadjustedReleaseTimeNs;
if (haveSync) { if (haveSync) {
// See if we've advanced to the next frame. // See if we've advanced to the next frame.
if (unadjustedFrameTimeUs != lastUnadjustedFrameTimeUs) { if (framePresentationTimeUs != lastFramePresentationTimeUs) {
frameCount++; frameCount++;
adjustedLastFrameTimeNs = pendingAdjustedFrameTimeNs; adjustedLastFrameTimeNs = pendingAdjustedFrameTimeNs;
} }
...@@ -109,20 +134,22 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -109,20 +134,22 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
// Calculate the average frame time across all the frames we've seen since the last sync. // Calculate the average frame time across all the frames we've seen since the last sync.
// This will typically give us a frame rate at a finer granularity than the frame times // This will typically give us a frame rate at a finer granularity than the frame times
// themselves (which often only have millisecond granularity). // themselves (which often only have millisecond granularity).
long averageFrameTimeNs = (unadjustedFrameTimeNs - syncFrameTimeNs) / frameCount; long averageFrameDurationNs = (framePresentationTimeNs - syncFramePresentationTimeNs)
/ frameCount;
// Project the adjusted frame time forward using the average. // Project the adjusted frame time forward using the average.
long candidateAdjustedFrameTimeNs = adjustedLastFrameTimeNs + averageFrameTimeNs; long candidateAdjustedFrameTimeNs = adjustedLastFrameTimeNs + averageFrameDurationNs;
if (isDriftTooLarge(candidateAdjustedFrameTimeNs, unadjustedReleaseTimeNs)) { if (isDriftTooLarge(candidateAdjustedFrameTimeNs, unadjustedReleaseTimeNs)) {
haveSync = false; haveSync = false;
} else { } else {
adjustedFrameTimeNs = candidateAdjustedFrameTimeNs; adjustedFrameTimeNs = candidateAdjustedFrameTimeNs;
adjustedReleaseTimeNs = syncReleaseTimeNs + adjustedFrameTimeNs - syncFrameTimeNs; adjustedReleaseTimeNs = syncUnadjustedReleaseTimeNs + adjustedFrameTimeNs
- syncFramePresentationTimeNs;
} }
} else { } else {
// We're synced but haven't waited the required number of frames to apply an adjustment. // We're synced but haven't waited the required number of frames to apply an adjustment.
// Check drift anyway. // Check drift anyway.
if (isDriftTooLarge(unadjustedFrameTimeNs, unadjustedReleaseTimeNs)) { if (isDriftTooLarge(framePresentationTimeNs, unadjustedReleaseTimeNs)) {
haveSync = false; haveSync = false;
} }
} }
...@@ -130,14 +157,14 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -130,14 +157,14 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
// If we need to sync, do so now. // If we need to sync, do so now.
if (!haveSync) { if (!haveSync) {
syncFrameTimeNs = unadjustedFrameTimeNs; syncFramePresentationTimeNs = framePresentationTimeNs;
syncReleaseTimeNs = unadjustedReleaseTimeNs; syncUnadjustedReleaseTimeNs = unadjustedReleaseTimeNs;
frameCount = 0; frameCount = 0;
haveSync = true; haveSync = true;
onSynced(); onSynced();
} }
lastUnadjustedFrameTimeUs = unadjustedFrameTimeUs; lastFramePresentationTimeUs = framePresentationTimeUs;
pendingAdjustedFrameTimeNs = adjustedFrameTimeNs; pendingAdjustedFrameTimeNs = adjustedFrameTimeNs;
if (sampledVsyncTimeNs == 0) { if (sampledVsyncTimeNs == 0) {
...@@ -155,8 +182,8 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -155,8 +182,8 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
} }
private boolean isDriftTooLarge(long frameTimeNs, long releaseTimeNs) { private boolean isDriftTooLarge(long frameTimeNs, long releaseTimeNs) {
long elapsedFrameTimeNs = frameTimeNs - syncFrameTimeNs; long elapsedFrameTimeNs = frameTimeNs - syncFramePresentationTimeNs;
long elapsedReleaseTimeNs = releaseTimeNs - syncReleaseTimeNs; long elapsedReleaseTimeNs = releaseTimeNs - syncUnadjustedReleaseTimeNs;
return Math.abs(elapsedReleaseTimeNs - elapsedFrameTimeNs) > MAX_ALLOWED_DRIFT_NS; return Math.abs(elapsedReleaseTimeNs - elapsedFrameTimeNs) > MAX_ALLOWED_DRIFT_NS;
} }
...@@ -177,4 +204,9 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -177,4 +204,9 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs; return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs;
} }
private static float getDefaultDisplayRefreshRate(Context context) {
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
return manager.getDefaultDisplay().getRefreshRate();
}
} }
...@@ -217,7 +217,7 @@ public final class H264DashTest extends ActivityInstrumentationTestCase2<HostAct ...@@ -217,7 +217,7 @@ public final class H264DashTest extends ActivityInstrumentationTestCase2<HostAct
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, handler, logger, VIDEO_EVENT_ID, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, handler, logger, VIDEO_EVENT_ID,
MIN_LOADABLE_RETRY_COUNT); MIN_LOADABLE_RETRY_COUNT);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer( MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(host,
videoSampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, handler, logger, 50); videoSampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, handler, logger, 50);
videoCounters = videoRenderer.codecCounters; videoCounters = videoRenderer.codecCounters;
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface); player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
......
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