blob: 30f4b0773188ceaf1f80596984a028caab4a6bc1 [file] [log] [blame]
// Copyright 2018 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/android/shared/video_frame_release_time_adjuster.h"
namespace starboard {
namespace android {
namespace shared {
VideoFrameReleaseTimeHelper::VideoFrameReleaseTimeHelper() {
this(DISPLAY_REFRESH_RATE_UNKNOWN);
}
VideoFrameReleaseTimeHelper::VideoFrameReleaseTimeHelper(Context context) {
this(getDefaultDisplayRefreshRate(context));
}
VideoFrameReleaseTimeHelper::VideoFrameReleaseTimeHelper(
double defaultDisplayRefreshRate) {
useDefaultDisplayVsync =
defaultDisplayRefreshRate != DISPLAY_REFRESH_RATE_UNKNOWN;
if (useDefaultDisplayVsync) {
vsyncSampler = VSyncSampler.getInstance();
vsyncDurationNs = (long)(NANOS_PER_SECOND / defaultDisplayRefreshRate);
vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100;
} else {
vsyncSampler = null;
vsyncDurationNs = -1; // Value unused.
vsyncOffsetNs = -1; // Value unused.
}
}
void VideoFrameReleaseTimeHelper::enable() {
haveSync = false;
if (useDefaultDisplayVsync) {
vsyncSampler.addObserver();
}
}
void VideoFrameReleaseTimeHelper::disable() {
if (useDefaultDisplayVsync) {
vsyncSampler.removeObserver();
}
}
long VideoFrameReleaseTimeHelper::adjustReleaseTime(
long framePresentationTimeUs,
long unadjustedReleaseTimeNs) {
long framePresentationTimeNs = framePresentationTimeUs * 1000;
// Until we know better, the adjustment will be a no-op.
long adjustedFrameTimeNs = framePresentationTimeNs;
long adjustedReleaseTimeNs = unadjustedReleaseTimeNs;
if (haveSync) {
// See if we've advanced to the next frame.
if (framePresentationTimeUs != lastFramePresentationTimeUs) {
frameCount++;
adjustedLastFrameTimeNs = pendingAdjustedFrameTimeNs;
}
if (frameCount >= MIN_FRAMES_FOR_ADJUSTMENT) {
// We're synced and have waited the required number of frames to apply an
// adjustment. 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 themselves (which
// often only have millisecond granularity).
long averageFrameDurationNs =
(framePresentationTimeNs - syncFramePresentationTimeNs) / frameCount;
// Project the adjusted frame time forward using the average.
long candidateAdjustedFrameTimeNs =
adjustedLastFrameTimeNs + averageFrameDurationNs;
if (isDriftTooLarge(candidateAdjustedFrameTimeNs,
unadjustedReleaseTimeNs)) {
haveSync = false;
} else {
adjustedFrameTimeNs = candidateAdjustedFrameTimeNs;
adjustedReleaseTimeNs = syncUnadjustedReleaseTimeNs +
adjustedFrameTimeNs -
syncFramePresentationTimeNs;
}
} else {
// We're synced but haven't waited the required number of frames to apply
// an adjustment. Check drift anyway.
if (isDriftTooLarge(framePresentationTimeNs, unadjustedReleaseTimeNs)) {
haveSync = false;
}
}
}
// If we need to sync, do so now.
if (!haveSync) {
syncFramePresentationTimeNs = framePresentationTimeNs;
syncUnadjustedReleaseTimeNs = unadjustedReleaseTimeNs;
frameCount = 0;
haveSync = true;
onSynced();
}
lastFramePresentationTimeUs = framePresentationTimeUs;
pendingAdjustedFrameTimeNs = adjustedFrameTimeNs;
if (vsyncSampler == null || vsyncSampler.sampledVsyncTimeNs == 0) {
return adjustedReleaseTimeNs;
}
// Find the timestamp of the closest vsync. This is the vsync that we're
// targeting.
long snappedTimeNs = closestVsync(
adjustedReleaseTimeNs, vsyncSampler.sampledVsyncTimeNs, vsyncDurationNs);
// Apply an offset so that we release before the target vsync, but after the
// previous one.
return snappedTimeNs - vsyncOffsetNs;
}
void VideoFrameReleaseTimeHelper::onSynced() {
// Do nothing.
}
boolean VideoFrameReleaseTimeHelper::isDriftTooLarge(long frameTimeNs,
long releaseTimeNs) {
long elapsedFrameTimeNs = frameTimeNs - syncFramePresentationTimeNs;
long elapsedReleaseTimeNs = releaseTimeNs - syncUnadjustedReleaseTimeNs;
return Math.abs(elapsedReleaseTimeNs - elapsedFrameTimeNs) >
MAX_ALLOWED_DRIFT_NS;
}
static long VideoFrameReleaseTimeHelper::closestVsync(long releaseTime,
long sampledVsyncTime,
long vsyncDuration) {
long vsyncCount = (releaseTime - sampledVsyncTime) / vsyncDuration;
long snappedTimeNs = sampledVsyncTime + (vsyncDuration * vsyncCount);
long snappedBeforeNs;
long snappedAfterNs;
if (releaseTime <= snappedTimeNs) {
snappedBeforeNs = snappedTimeNs - vsyncDuration;
snappedAfterNs = snappedTimeNs;
} else {
snappedBeforeNs = snappedTimeNs;
snappedAfterNs = snappedTimeNs + vsyncDuration;
}
long snappedAfterDiff = snappedAfterNs - releaseTime;
long snappedBeforeDiff = releaseTime - snappedBeforeNs;
return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs
: snappedBeforeNs;
}
static double VideoFrameReleaseTimeHelper::getDefaultDisplayRefreshRate(
Context context) {
WindowManager manager =
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
return manager.getDefaultDisplay() != null
? manager.getDefaultDisplay().getRefreshRate()
: DISPLAY_REFRESH_RATE_UNKNOWN;
}
} // namespace shared
} // namespace android
} // namespace starboard