android R 可变帧率VRR

  • 前言
  • API简述
  • Graphics相关代码分析
    • 1.用户设置刷新率流程
    • 2.SurfaceFlinger设置刷新率
  • 使用API以及验证

前言

本文代码基于Android R r1版本。如果不是这个版本的代码,可能会有部分代码上的差异。

API简述

Surface.setFrameRate(float frameRate, int compatibility)
//frameworks/base/core/java/android/view/Surface.javaSurfaceControl.Transaction.setFrameRate(float frameRate, int compatibility)
//frameworks/base/core/java/android/view/SurfaceControl.javaANativeWindow_setFrameRate(float frameRate, int8_t compatibility)
//frameworks/native/libs/nativewindow/ANativeWindow.cppASurfaceTransaction_setFrameRate(float frameRate, int8_t compatibility)
//frameworks/base/native/android/surface_control.cpp

分别有两个参数:
参数frameRate表示要设置的帧率,设置0表示接受系统刷新率,设置其余的整数都可以,系统会默认适配一个正确的刷新率。
参数compatibility有两个值可选,分别是
FRAME_RATE_COMPATIBILITY_DEFAULT和
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE。
其中FRAME_RATE_COMPATIBILITY_DEFAULT被设置之后,不能保证设置的帧率一定生效,app要保证在未生效的刷新率下正常运行,同时这个参数只能被用在非视频场景下。FRAME_RATE_COMPATIBILITY_FIXED_SOURCE这个值只能在视频场景下被使用。

Graphics相关代码分析

1.用户设置刷新率流程

无论是SDK中的接口还是NDK中的接口最终都会调用到surfaceflinger中。

status_t SurfaceFlinger::setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,int8_t compatibility)

进入setFrameRate之后,首先是判断参数的有效性。这个判断就是验证frameRate的值是否大于0,是否为浮点数。然后再判断compatibility的值是否为两个枚举值。

bool ValidateFrameRate(float frameRate, int8_t compatibility, const char* inFunctionName) {const char* functionName = inFunctionName != nullptr ? inFunctionName : "call";int floatClassification = std::fpclassify(frameRate);if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) {ALOGE("%s failed - invalid frame rate %f", functionName, frameRate);return false;}if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE) {ALOGE("%s failed - invalid compatibility value %d", functionName, compatibility);return false;}return true;
}

接下来就是一个lamda表达式。

    static_cast<void>(schedule([=] {Mutex::Autolock lock(mStateLock);if (authenticateSurfaceTextureLocked(surface)) {sp<Layer> layer = (static_cast<MonitoredProducer*>(surface.get()))->getLayer();if (layer->setFrameRate(Layer::FrameRate(frameRate,Layer::FrameRate::convertCompatibility(compatibility)))) {setTransactionFlags(eTraversalNeeded);}} else {ALOGE("Attempt to set frame rate on an unrecognized IGraphicBufferProducer");return BAD_VALUE;}return NO_ERROR;}));

调用的surfaceflinger.cpp中的模板方法。

template <typename F, typename T>
inline std::future<T> SurfaceFlinger::schedule(F&& f) {auto [task, future] = makeTask(std::move(f));mEventQueue->postMessage(std::move(task));return std::move(future);
}

makeTask定义在MessageQueue.h中。

template <typename F>
class Task : public MessageHandler {template <typename G>friend auto makeTask(G&&);explicit Task(F&& f) : mTask(std::move(f)) {}void handleMessage(const Message&) override { mTask(); }using T = std::invoke_result_t<F>;std::packaged_task<T()> mTask;
};template <typename F>
inline auto makeTask(F&& f) {sp<Task<F>> task = new Task<F>(std::move(f));return std::make_pair(task, task->mTask.get_future());
}

setFrameRate中的lambda表达式被封装成MessageHander之后postMessage到MessageQueue中去。再Looper中pollOnce的时候被取出来执行。这个时候才会真正调用setFrameRate中的lambda表达式。再看这个表达式是怎么写的。authenticateSurfaceTextureLocked就是判断producer是否存在。然后在调用layer的setFrameRate。

bool Layer::setFrameRate(FrameRate frameRate) {if (!mFlinger->useFrameRateApi) {return false;}if (mCurrentState.frameRate == frameRate) {return false;}// Activate the layer in Scheduler's LayerHistorymFlinger->mScheduler->recordLayerHistory(this, systemTime(),LayerHistory::LayerUpdateType::SetFrameRate);mCurrentState.sequence++;mCurrentState.frameRate = frameRate;mCurrentState.modified = true;updateTreeHasFrameRateVote();setTransactionFlags(eTransactionNeeded);return true;
}

首先是调用scheduler的layerhistory,实现在LayerHistoryV2中。

void LayerHistoryV2::record(Layer* layer, nsecs_t presentTime, nsecs_t now,LayerUpdateType updateType) {std::lock_guard lock(mLock);const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),[layer](const auto& pair) { return pair.first == layer; });LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);const auto& info = it->second;info->setLastPresentTime(presentTime, now, updateType, mConfigChangePending);// Activate layer if inactive.if (const auto end = activeLayers().end(); it >= end) {std::iter_swap(it, end);mActiveLayersEnd++;}
}

根据layer从mLayerInfos中取出对应的LayerPair,LayerPair就是一个Layer和LayerInfo的pair。然后将参数中的各种属性设置到LayerInfoV2的setLastPresentTime中去。

void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now,LayerUpdateType updateType, bool pendingConfigChange) {lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));mLastUpdatedTime = std::max(lastPresentTime, now);switch (updateType) {case LayerUpdateType::AnimationTX:mLastAnimationTime = std::max(lastPresentTime, now);break;case LayerUpdateType::SetFrameRate:case LayerUpdateType::Buffer:FrameTimeData frameTime = {.presetTime = lastPresentTime,.queueTime = mLastUpdatedTime,.pendingConfigChange = pendingConfigChange};mFrameTimes.push_back(frameTime);if (mFrameTimes.size() > HISTORY_SIZE) {mFrameTimes.pop_front();}break;}
}

这个函数的作用就是记录图层的请求时间,如果now比最后一次请求的时间更大就把now当做最后一次请求的时间。
再往下也没有什么特殊的代码了,就是遍历图层,设置flag。到这里,设置refresh rate的代码就结束了。但是到这里并不表示设置refresh rate成功。refresh rate也不会是你设置多少就变成多少。

2.SurfaceFlinger设置刷新率

在sf中的onMessageInvalidate函数中,还会根据layer history决定最终设置怎么样的refresh rate。
这是在scheduler中的调用。

void Scheduler::chooseRefreshRateForContent() {if (!mLayerHistory) return;ATRACE_CALL();scheduler::LayerHistory::Summary summary = mLayerHistory->summarize(systemTime());HwcConfigIndexType newConfigId;{std::lock_guard<std::mutex> lock(mFeatureStateLock);if (mFeatures.contentRequirements == summary) {return;}mFeatures.contentRequirements = summary;mFeatures.contentDetectionV1 =!summary.empty() ? ContentDetectionState::On : ContentDetectionState::Off;scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;newConfigId = calculateRefreshRateConfigIndexType(&consideredSignals);if (mFeatures.configId == newConfigId) {// We don't need to change the config, but we might need to send an event// about a config change, since it was suppressed due to a previous idleConsideredif (!consideredSignals.idle) {dispatchCachedReportedConfig();}return;}mFeatures.configId = newConfigId;auto& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);mSchedulerCallback.changeRefreshRate(newRefreshRate,consideredSignals.idle ? ConfigEvent::None: ConfigEvent::Changed);}
}

上面setFrameRate的时候调用的setLastPresentTime在这边就用上了。这里先调用了mLayerHistory的summarize。看下summarize的实现。

LayerHistoryV2::Summary LayerHistoryV2::summarize(nsecs_t now) {LayerHistory::Summary summary;std::lock_guard lock(mLock);partitionLayers(now);for (const auto& [layer, info] : activeLayers()) {const auto strong = layer.promote();if (!strong) {continue;}const auto frameRateSelectionPriority = strong->getFrameRateSelectionPriority();const auto layerFocused = Layer::isLayerFocusedBasedOnPriority(frameRateSelectionPriority);ALOGV("%s has priority: %d %s focused", strong->getName().c_str(),frameRateSelectionPriority, layerFocused ? "" : "not");const auto [type, refreshRate] = info->getRefreshRate(now);// Skip NoVote layer as those don't have any requirementsif (type == LayerHistory::LayerVoteType::NoVote) {continue;}// Compute the layer's position on the screenconst Rect bounds = Rect(strong->getBounds());const ui::Transform transform = strong->getTransform();constexpr bool roundOutwards = true;Rect transformed = transform.transform(bounds, roundOutwards);const float layerArea = transformed.getWidth() * transformed.getHeight();float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;summary.push_back({strong->getName(), type, refreshRate, weight, layerFocused});if (CC_UNLIKELY(mTraceEnabled)) {trace(layer, *info, type, static_cast<int>(std::round(refreshRate)));}}return summary;
}

summarize中又会调用partitionLayers。首先获取一个阀值,这个阀值范围是从当前开始算往前推1200ms。然后while循环所有的active的layer,如果这个layer还存在并且在过去的1200ms内是active的再并且这个layer设置的rate不能为0再再并且layer的framerate type不能是NoVote再再再layer是可见的才会用这个layer设置的framerate去投票。不满足条件的lyaer info被设置忽略或者重置掉。之后再清理掉不需要投票的layer。

void LayerHistoryV2::partitionLayers(nsecs_t now) {const nsecs_t threshold = getActiveLayerThreshold(now);// Collect expired and inactive layers after active layers.size_t i = 0;while (i < mActiveLayersEnd) {auto& [weak, info] = mLayerInfos[i];if (const auto layer = weak.promote(); layer && isLayerActive(*layer, *info, threshold)) {i++;// Set layer vote if setconst auto frameRate = layer->getFrameRateForLayerTree();const auto voteType = [&]() {switch (frameRate.type) {case Layer::FrameRateCompatibility::Default:return LayerVoteType::ExplicitDefault;case Layer::FrameRateCompatibility::ExactOrMultiple:return LayerVoteType::ExplicitExactOrMultiple;case Layer::FrameRateCompatibility::NoVote:return LayerVoteType::NoVote;}}();if (frameRate.rate > 0 || voteType == LayerVoteType::NoVote) {const auto type = layer->isVisible() ? voteType : LayerVoteType::NoVote;info->setLayerVote(type, frameRate.rate);} else {info->resetLayerVote();}continue;}if (CC_UNLIKELY(mTraceEnabled)) {trace(weak, *info, LayerHistory::LayerVoteType::NoVote, 0);}info->onLayerInactive(now);std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);}// Collect expired layers after inactive layers.size_t end = mLayerInfos.size();while (i < end) {if (mLayerInfos[i].first.promote()) {i++;} else {std::swap(mLayerInfos[i], mLayerInfos[--end]);}}mLayerInfos.erase(mLayerInfos.begin() + static_cast<long>(end), mLayerInfos.end());
}

接下来再回到summarize函数。首先是调用getFrameRateSelectionPriority获取每个layer设置的refresh rate的优先级。代码比较简单就是从mDrawingState和parent中获取layer设置的优先级,从判断获取优先级的逻辑可以看出来,被设置的优先级想要起作用一定是在layer 送显之后。

int32_t Layer::getFrameRateSelectionPriority() const {// Check if layer has priority set.if (mDrawingState.frameRateSelectionPriority != PRIORITY_UNSET) {return mDrawingState.frameRateSelectionPriority;}// If not, search whether its parents have it set.sp<Layer> parent = getParent();if (parent != nullptr) {return parent->getFrameRateSelectionPriority();}return Layer::PRIORITY_UNSET;
}

既然看到了获取layer设置的优先级,那就看一下上层是如何设置frame rate优先级的。这里只看native层设置优先级。调用的地方是SurfaceComposerClient中。上层通过surfacecontrol来设置layer的属性,基本上全部都是调用SurfaceComposerClient接口来实现的,这里设置layer的frame rate优先级也不例外。

SurfaceComposerClient::Transaction&
SurfaceComposerClient::Transaction::setFrameRateSelectionPriority(const sp<SurfaceControl>& sc,int32_t priority) {layer_state_t* s = getLayerState(sc);if (!s) {mStatus = BAD_INDEX;return *this;}s->what |= layer_state_t::eFrameRateSelectionPriority;s->frameRateSelectionPriority = priority;registerSurfaceControlForCallback(sc);return *this;
}

然后就是通过sf的setClientStateLocked调用到layer的setFrameRateSelectionPriority。逻辑也很简单,和设置layer的各种属性一样。

bool Layer::setFrameRateSelectionPriority(int32_t priority) {if (mCurrentState.frameRateSelectionPriority == priority) return false;mCurrentState.frameRateSelectionPriority = priority;mCurrentState.sequence++;mCurrentState.modified = true;setTransactionFlags(eTransactionNeeded);return true;
}

优先级分为4种,0最高,-1最低。
最高的优先级就是有焦点的窗口并且要投票给某一个mode。
次一级的是有焦点但是没有要设置某一个mode,基本上大部分的优先级都是这种。
再次一等的优先级就是没有焦点的窗口但是又设置了mode。

    static constexpr int32_t PRIORITY_UNSET = -1;// Windows that are in focus and voted for the preferred mode IDstatic constexpr int32_t PRIORITY_FOCUSED_WITH_MODE = 0;// // Windows that are in focus, but have not requested a specific mode ID.static constexpr int32_t PRIORITY_FOCUSED_WITHOUT_MODE = 1;// Windows that are not in focus, but voted for a specific mode ID.static constexpr int32_t PRIORITY_NOT_FOCUSED_WITH_MODE = 2;

getRefreshRate就是获取每个layer info的投票类型和fps。代码逻辑很简答,这里就略过,有点特殊的就是isFrequent,这是判断图层更新的频率,如果有效记录少于90帧,有效帧的时间跨度小于1秒,都算是请求不够频繁,会被return 掉,不会被用作启发式计算的数据。最重要的就是calculateRefreshRateIfPossible。

std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));return {mLayerVote.type, mLayerVote.fps};}if (isAnimating(now)) {ALOGV("%s is animating", mName.c_str());mLastRefreshRate.animatingOrInfrequent = true;return {LayerHistory::LayerVoteType::Max, 0};}if (!isFrequent(now)) {ALOGV("%s is infrequent", mName.c_str());mLastRefreshRate.animatingOrInfrequent = true;return {LayerHistory::LayerVoteType::Min, 0};}// If the layer was previously tagged as animating or infrequent, we clear// the history as it is likely the layer just changed its behavior// and we should not look at stale dataif (mLastRefreshRate.animatingOrInfrequent) {clearHistory(now);}auto refreshRate = calculateRefreshRateIfPossible(now);if (refreshRate.has_value()) {ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};}ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());return {LayerHistory::LayerVoteType::Max, 0};
}

calculateRefreshRateIfPossible开始是先判断是否有足够的数据用来做启发式计算。满足条件才能继续往下走。然后就是根据保存的数据计算出帧率。

std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible(nsecs_t now) {static constexpr float MARGIN = 1.0f; // 1Hzif (!hasEnoughDataForHeuristic()) {ALOGV("Not enough data");return std::nullopt;}const auto averageFrameTime = calculateAverageFrameTime();if (averageFrameTime.has_value()) {const auto refreshRate = 1e9f / *averageFrameTime;const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);if (refreshRateConsistent) {const auto knownRefreshRate =sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);// To avoid oscillation, use the last calculated refresh rate if it is// close enoughif (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&mLastRefreshRate.reported != knownRefreshRate) {mLastRefreshRate.calculated = refreshRate;mLastRefreshRate.reported = knownRefreshRate;}ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),refreshRate, mLastRefreshRate.reported);} else {ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),refreshRate, mLastRefreshRate.reported);}}return mLastRefreshRate.reported == 0 ? std::nullopt: std::make_optional(mLastRefreshRate.reported);
}

再继续回去看这个函数summarize。获取完帧率之后就算根据图层的大小计算权重。计算方法也很简单就是用layer的大小/display的大小,这个mDisplayArea会在主屏被添加之后被设置,大小就是屏幕大小。然后就是把图层的名称,权重,刷新率等信息保存到一个vector中。接下来就是calculateRefreshRateConfigIndexType函数,这个函数判断各种会影响帧率的因素并且返回configid。在看具体代码之前先要看这两部分,一部分和mIdleTimer, mTouchTimer, mDisplayPowerTimer相关。

    // Timer that records time between requests for next vsync.std::optional<scheduler::OneShotTimer> mIdleTimer;// Timer used to monitor touch events.std::optional<scheduler::OneShotTimer> mTouchTimer;// Timer used to monitor display power mode.std::optional<scheduler::OneShotTimer> mDisplayPowerTimer;

初始化是在Scheduler的构造函数中。分别初始化了mIdleTimer, mTouchTimer, mDisplayPowerTimer。就拿mTouchTimer看一下。

const int setIdleTimerMs = property_get_int32("debug.sf.set_idle_timer_ms", 0);if (const auto millis = setIdleTimerMs ? setIdleTimerMs : set_idle_timer_ms(0); millis > 0) {const auto callback = mSupportKernelTimer ? &Scheduler::kernelIdleTimerCallback: &Scheduler::idleTimerCallback;mIdleTimer.emplace(std::chrono::milliseconds(millis),[this, callback] { std::invoke(callback, this, TimerState::Reset); },[this, callback] { std::invoke(callback, this, TimerState::Expired); });mIdleTimer->start();
}if (const int64_t millis = set_touch_timer_ms(0); millis > 0) {// Touch events are coming to SF every 100ms, so the timer needs to be higher than thatmTouchTimer.emplace(std::chrono::milliseconds(millis),[this] { touchTimerCallback(TimerState::Reset); },[this] { touchTimerCallback(TimerState::Expired); });mTouchTimer->start();
}if (const int64_t millis = set_display_power_timer_ms(0); millis > 0) {mDisplayPowerTimer.emplace(std::chrono::milliseconds(millis),[this] { displayPowerTimerCallback(TimerState::Reset); },[this] { displayPowerTimerCallback(TimerState::Expired); });mDisplayPowerTimer->start();
}

OneShotTimer的start内容很简单,就是设置状态以及创建线程。线程中会调用loop函数。

void OneShotTimer::start() {{std::lock_guard<std::mutex> lock(mMutex);mState = TimerState::RESET;}mThread = std::thread(&OneShotTimer::loop, this);
}

loop函数就是调度的核心了,每一次reset之后通过mResetCallback回调重置状态,WAITING的这个while等待超时,如果超时了就调用mTimeoutCallback回掉。

void OneShotTimer::loop() {while (true) {bool triggerReset = false;bool triggerTimeout = false;{std::lock_guard<std::mutex> lock(mMutex);if (mState == TimerState::STOPPED) {break;}if (mState == TimerState::IDLE) {mCondition.wait(mMutex);continue;}if (mState == TimerState::RESET) {triggerReset = true;}}if (triggerReset && mResetCallback) {mResetCallback();}{ // lock the mutex again. someone might have called stop meanwhilestd::lock_guard<std::mutex> lock(mMutex);if (mState == TimerState::STOPPED) {break;}auto triggerTime = std::chrono::steady_clock::now() + mInterval;mState = TimerState::WAITING;while (mState == TimerState::WAITING) {constexpr auto zero = std::chrono::steady_clock::duration::zero();auto waitTime = triggerTime - std::chrono::steady_clock::now();if (waitTime > zero) mCondition.wait_for(mMutex, waitTime);if (mState == TimerState::RESET) {triggerTime = std::chrono::steady_clock::now() + mInterval;mState = TimerState::WAITING;} else if (mState == TimerState::WAITING &&(triggerTime - std::chrono::steady_clock::now()) <= zero) {triggerTimeout = true;mState = TimerState::IDLE;}}}if (triggerTimeout && mTimeoutCallback) {mTimeoutCallback();}}
}

最终通过回调调用到getBestRefreshRate函数。这个函数就是最终给出refreshrate的地方。之前各种判断是否有touch,是否idle,还有权重,投票的图层数量多少等等都会在这里拿出来计算,最终计算出最适合当前场景应该的refresh rate。

const RefreshRate& RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals,GlobalSignals* outSignalsConsidered) const {ATRACE_CALL();ALOGV("getRefreshRateForContent %zu layers", layers.size());if (outSignalsConsidered) *outSignalsConsidered = {};const auto setTouchConsidered = [&] {if (outSignalsConsidered) {outSignalsConsidered->touch = true;}};const auto setIdleConsidered = [&] {if (outSignalsConsidered) {outSignalsConsidered->idle = true;}};std::lock_guard lock(mLock);int noVoteLayers = 0;int minVoteLayers = 0;int maxVoteLayers = 0;int explicitDefaultVoteLayers = 0;int explicitExactOrMultipleVoteLayers = 0;float maxExplicitWeight = 0;for (const auto& layer : layers) {if (layer.vote == LayerVoteType::NoVote) {noVoteLayers++;} else if (layer.vote == LayerVoteType::Min) {minVoteLayers++;} else if (layer.vote == LayerVoteType::Max) {maxVoteLayers++;} else if (layer.vote == LayerVoteType::ExplicitDefault) {explicitDefaultVoteLayers++;maxExplicitWeight = std::max(maxExplicitWeight, layer.weight);} else if (layer.vote == LayerVoteType::ExplicitExactOrMultiple) {explicitExactOrMultipleVoteLayers++;maxExplicitWeight = std::max(maxExplicitWeight, layer.weight);}}const bool hasExplicitVoteLayers =explicitDefaultVoteLayers > 0 || explicitExactOrMultipleVoteLayers > 0;// Consider the touch event if there are no Explicit* layers. Otherwise wait until after we've// selected a refresh rate to see if we should apply touch boost.if (globalSignals.touch && !hasExplicitVoteLayers) {ALOGV("TouchBoost - choose %s", getMaxRefreshRateByPolicyLocked().getName().c_str());setTouchConsidered();return getMaxRefreshRateByPolicyLocked();}// If the primary range consists of a single refresh rate then we can only// move out the of range if layers explicitly request a different refresh// rate.const Policy* policy = getCurrentPolicyLocked();const bool primaryRangeIsSingleRate = policy->primaryRange.min == policy->primaryRange.max;if (!globalSignals.touch && globalSignals.idle &&!(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {ALOGV("Idle - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());setIdleConsidered();return getMinRefreshRateByPolicyLocked();}if (layers.empty() || noVoteLayers == layers.size()) {return getMaxRefreshRateByPolicyLocked();}// Only if all layers want Min we should return Minif (noVoteLayers + minVoteLayers == layers.size()) {ALOGV("all layers Min - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());return getMinRefreshRateByPolicyLocked();}// Find the best refresh rate based on scorestd::vector<std::pair<const RefreshRate*, float>> scores;scores.reserve(mAppRequestRefreshRates.size());for (const auto refreshRate : mAppRequestRefreshRates) {scores.emplace_back(refreshRate, 0.0f);}for (const auto& layer : layers) {ALOGV("Calculating score for %s (%s, weight %.2f)", layer.name.c_str(),layerVoteTypeString(layer.vote).c_str(), layer.weight);if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min) {continue;}auto weight = layer.weight;for (auto i = 0u; i < scores.size(); i++) {bool inPrimaryRange =scores[i].first->inPolicy(policy->primaryRange.min, policy->primaryRange.max);if ((primaryRangeIsSingleRate || !inPrimaryRange) &&!(layer.focused && layer.vote == LayerVoteType::ExplicitDefault)) {// Only focused layers with ExplicitDefault frame rate settings are allowed to score// refresh rates outside the primary range.continue;}// If the layer wants Max, give higher score to the higher refresh rateif (layer.vote == LayerVoteType::Max) {const auto ratio = scores[i].first->fps / scores.back().first->fps;// use ratio^2 to get a lower score the more we get further from peakconst auto layerScore = ratio * ratio;ALOGV("%s (Max, weight %.2f) gives %s score of %.2f", layer.name.c_str(), weight,scores[i].first->name.c_str(), layerScore);scores[i].second += weight * layerScore;continue;}const auto displayPeriod = scores[i].first->hwcConfig->getVsyncPeriod();const auto layerPeriod = round<nsecs_t>(1e9f / layer.desiredRefreshRate);if (layer.vote == LayerVoteType::ExplicitDefault) {const auto layerScore = [&]() {// Find the actual rate the layer will render, assuming// that layerPeriod is the minimal time to render a frameauto actualLayerPeriod = displayPeriod;int multiplier = 1;while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) {multiplier++;actualLayerPeriod = displayPeriod * multiplier;}return std::min(1.0f,static_cast<float>(layerPeriod) /static_cast<float>(actualLayerPeriod));}();ALOGV("%s (ExplicitDefault, weight %.2f) %.2fHz gives %s score of %.2f",layer.name.c_str(), weight, 1e9f / layerPeriod, scores[i].first->name.c_str(),layerScore);scores[i].second += weight * layerScore;continue;}if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||layer.vote == LayerVoteType::Heuristic) {const auto layerScore = [&] {// Calculate how many display vsyncs we need to present a single frame for this// layerconst auto [displayFramesQuot, displayFramesRem] =getDisplayFrames(layerPeriod, displayPeriod);static constexpr size_t MAX_FRAMES_TO_FIT =10; // Stop calculating when score < 0.1if (displayFramesRem == 0) {// Layer desired refresh rate matches the display rate.return 1.0f;}if (displayFramesQuot == 0) {// Layer desired refresh rate is higher the display rate.return (static_cast<float>(layerPeriod) /static_cast<float>(displayPeriod)) *(1.0f / (MAX_FRAMES_TO_FIT + 1));}// Layer desired refresh rate is lower the display rate. Check how well it fits// the cadenceauto diff = std::abs(displayFramesRem - (displayPeriod - displayFramesRem));int iter = 2;while (diff > MARGIN_FOR_PERIOD_CALCULATION && iter < MAX_FRAMES_TO_FIT) {diff = diff - (displayPeriod - diff);iter++;}return 1.0f / iter;}();ALOGV("%s (%s, weight %.2f) %.2fHz gives %s score of %.2f", layer.name.c_str(),layerVoteTypeString(layer.vote).c_str(), weight, 1e9f / layerPeriod,scores[i].first->name.c_str(), layerScore);scores[i].second += weight * layerScore;continue;}}}// Now that we scored all the refresh rates we need to pick the one that got the highest score.// In case of a tie we will pick the higher refresh rate if any of the layers wanted Max,// or the lower otherwise.const RefreshRate* bestRefreshRate = maxVoteLayers > 0? getBestRefreshRate(scores.rbegin(), scores.rend()): getBestRefreshRate(scores.begin(), scores.end());if (primaryRangeIsSingleRate) {// If we never scored any layers, then choose the rate from the primary// range instead of picking a random score from the app range.if (std::all_of(scores.begin(), scores.end(),[](std::pair<const RefreshRate*, float> p) { return p.second == 0; })) {ALOGV("layers not scored - choose %s",getMaxRefreshRateByPolicyLocked().getName().c_str());return getMaxRefreshRateByPolicyLocked();} else {return *bestRefreshRate;}}// Consider the touch event if there are no ExplicitDefault layers. ExplicitDefault are mostly// interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit// vote we should not change it if we get a touch event. Only apply touch boost if it will// actually increase the refresh rate over the normal selection.const RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked();if (globalSignals.touch && explicitDefaultVoteLayers == 0 &&bestRefreshRate->fps < touchRefreshRate.fps) {setTouchConsidered();ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str());return touchRefreshRate;}return *bestRefreshRate;
}

使用API以及验证

上面那么多全部都是代码层面的分析,写两个app测试一下。
先分别写app调用setFrameRate和preferredDisplayModeId。看看是否真的像API中说的那样,preferredDisplayModeId设置之后setFrameRate设置的值就无效了。
我这边用来测试的机器,高刷是120hz的,所以判断里面获取到mode值判断是否大于61。因为这个getRefreshRate获取到的值可能是60.xxx并不一定是60。

public void btn_setpreferredDisplayModeId(View v) {Display display = this.getDisplay();Display.Mode[] modes = display.getSupportedModes();for (Display.Mode m : modes) {Log.e(TAG, "btn_setpreferredDisplayModeId: " + m.getRefreshRate());if (m.getRefreshRate() >= 61) {WindowManager.LayoutParams param = getWindow().getAttributes();param.preferredDisplayModeId = m.getModeId();getWindow().setAttributes(param);}}
}

这是调用setFrameRate的代码。

public void surfaceCreated(SurfaceHolder holder) {holder.getSurface().setFrameRate(60, FRAME_RATE_COMPATIBILITY_DEFAULT);
}

假设调用setFrameRate的APP叫做A,调用preferredDisplayModeId的APP叫做B。
我作的对比很简单,先运行B,但是不点击B中设置刷新率的按钮,然后将当前的window设置成multi-window或者pop-window,再运行A。
这个时候因为我点击了屏幕,所以刷新率会维持在120hz,查看当前的刷新率很简单,1是在开发者模式中“show refresh rate”,2是dumpsys SurfaceFlinger,从dump的信息中可以看到当前的刷新率是多少。
当一段时间不触碰屏幕之后,刷新率会降低到60hz。这就是A设置的刷新率。这时候我再点击B中的按钮,调用preferredDisplayModeId设置到120hz。这时候刷新率就会变成120hz,之后无论怎么操作,只要B的window还在(relaunch之后要重新点击button)刷新率会一直在120hz,就算你重新关闭打开A。
这是一个对比,两种设置刷新率方式的对比。另外还有一个对比,就是一个app设120hz,一个app设60fps,到底会怎么样呢?
经过测试发现,这和app的focus相关。假设设置60hz的app叫做app_60,另外一个叫做app_120,当焦点在app_60上的时候,启发式计算就会将刷新率设置为60,反之则会设置为120.原因在上面的代码分析中也写过了,和window的几种级别相关。
还有一些别的场景,我也不想去一一做对比了,有兴趣的可以自己测试。以上两种测试只是为了分析完代码之后写个APP印证一下。

android R Variable Refresh Rate 可变帧率 VRR相关推荐

  1. Android中可变帧率VRR

    Android 从Q开始实现对可变帧率(VRR:Variable Refresh Rate)的支持.本文以android Q 为基础介绍android VRR的实现, 与android S有部分差别, ...

  2. android动态修改refresh rate,Lenovo k860i 移植Android 4.4 cm11进度记录【下篇--实时更新中】...

    2014.8.24 k860i的cm11的移植在中断了近两三个月之后又开始继续了,进度记录的日志上一篇已经没什么写的了,就完结掉它吧,重新开一篇日志做下篇好了.最近的战况是,在scue同学的努力之下, ...

  3. Android 10 (Android Q)中的屏幕刷新率(display refresh rate)切换方法和策略

           本文禁止转载,如有需求,请联系作者. 1. 屏幕刷新率和应用的显示帧率 首先请区分好屏幕刷新率(Panel Refresh Rate)和应用的帧率(APP Frame Rate)两个概念 ...

  4. Android: R cannot be resolved to a variable

    Android开发过程中,碰到R cannot be resolved to a variable的报错信息,网上的解决办法汇总: 首先说下我自己的问题所在,尝试过网上的各种方法,均未奏效,最后发现是 ...

  5. 多屏幕刷新率(refresh rate)模式下的帧显示流水线

    给Android应用和游戏开发工程师的最新参考. 长时间以来,手机显示屏幕的刷新率一直是固定的60Hz. 从2019年开始,由于游戏等应用对响应速度的需求越来越高,越来越多的手机开始在高刷新率(90/ ...

  6. android r中的变量_R中的变量

    android r中的变量 Variables in R are the same as the notion of variables in any other programming langua ...

  7. Android 自带图标库 android.R.drawable

    在xml文件中调用. android:title="@string/secure_connect" android:orderInCategory="100" ...

  8. 视频编码中的RC(rate control)是什么?码率控制 CBR (Constant Bit Rate)、VBR (Variable Bit Rate)

    再看RXW源码的时候在视频编码结构体里有一个RC结构体,RC(rate control)到底是啥? 码率控制技术 RC (Rate Control) 是实际视频编码器中一个非常重要的技术模块. 根据实 ...

  9. Variable Assembly Language可变汇编语言

    Variable Assembly Language可变汇编语言 可变汇编语言(Variable Assembly Language, VAL)是一个设计给Unimation Inc.工业机器人用的电 ...

最新文章

  1. linux vi文本类常用命令
  2. Redis的两种消息模式
  3. 50佳设计独特的名片设计欣赏(上篇)
  4. Angular 应用的DevDependencies
  5. 利用ado.net和winform连接ms Server可以做什么?
  6. JavaScript算法(实例八)递归计算每个月的兔子总数【斐波那契数列】
  7. java 线程 主进程_java 多线程通用方法
  8. [2003] Can't connect to MySQL server on 'localhost' (13) 错误解决方法 【主要是linux下php连接不上mysq时】...
  9. ubuntu16.04 安装 wxPython方法
  10. linux下交叉编译ffmpeg,并加入H264编码支持
  11. 解析java文件_使用JDK的Parser来解析Java源代码
  12. zend studio php调试,Zend Studio中如何配置和使用xdebug断点调试工具?
  13. 问题解决之——未知usb设备设备描述符请求失败(Jlink驱动)
  14. JSP介绍及视频教程
  15. SXF 安全服务一面
  16. jbx添加加mysql驱动
  17. Web3.0:Skiff 去中心化隐私协同文档
  18. 关于STM8的程序下载问题:SWIM Error[30006]报错解决办法汇总
  19. 51单片机 普中V2 数字时钟 电子时钟 万年历 DS1302 LCD1602 AT24C02
  20. 计算机怎么复制公式,excel怎么复制公式 -电脑资料

热门文章

  1. 热像仪的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  2. react setState特性
  3. CSS初始化的两种常见方法
  4. root登录Linux图形界面
  5. GitLab删除项目的操作步骤
  6. java crc计算_JavaCRC校验原理
  7. 知识蒸馏论文翻译(7)—— Knowledge Distillation from Internal Representations(内部表征)
  8. Android 日期筛选器
  9. pc android系统下载,在PC上运行Android-x86 9.0-r1进行下载
  10. 计算机应用中最高语言,计算机应用基础考试题