| // 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 |