Android中可变帧率VRR

本文详细介绍了Android Q及S系统中如何实现可变帧率(VRR)支持,包括应用设置帧率、系统设置帧率、黑名单与白名单管理以及低电量模式下的帧率调整。关键流程涉及Activity、Settings和SurfaceFlinger的交互,通过WindowManager.LayoutParams API来设定帧率,并介绍了黑名单和白名单的实现方式,以及如何通过ADB命令和系统设置动态调整帧率。
摘要由CSDN通过智能技术生成

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

帧率设置方式

android 提供了几种方式设置fps:activity 设置帧率, setting 设置帧率, app黑名单方式限制高帧率, surfaceflinger debug接口设置帧率, 另外也考虑到了低电等情况。

这几种方式优先级由高到低如下: 

1.如果用SurfaceFlinger debug接口设置了帧率, 那么后续其他设置方式都不会起作用。

2. 如果进入低电量模式, 系统帧率范围为0-60fps, 后面的帧率设置不起作用。

3. 如果settings中设置了帧率, 那么settings中的帧率限定了系统fps的最大和最小值。

4. app设置自身的帧率, 该帧率如果超出setting 帧率设置区间范围, 不起作用。

5. 高帧率黑名单,该名单包含了不能跑在高帧率的package name, 在4没有设置的情况下, 会检测app是否在黑名单中,如果4中设置app 帧率, 则不再查看黑名单。

帧率设置API

app 设置帧率:

activity可根据自身特点, 设定适合自己的帧率, 该帧率设定的影响范围为本activity在主屏幕作为focus app显示期间。 

api: 

  • 读取系统支持帧率api: 

Display.Mode.getRefreshRate();

  • 设置帧率api:

WindowManager.LayoutParams.preferredDisplayModeId (android Q )

WindowManager.LayoutParams.preferredMinDisplayRefreshRate(android S新增)

WindowManager.LayoutParams.preferredMaxDisplayRefreshRate(android S新增)

Activity示例代码:

public class MainActivity extends AppCompatActivity { 

//读取系统支持的Display.mode:

private Display.Mode[] getDisplayModes() {
 Display primaryDisplay = getDisplay();
 Display.Mode[] modes = primaryDisplay.getSupportedModes();
 return modes;//返回该display支持的所有mode的数组, activity可从中选择自己需要的mode. 
}

//app 设置帧率示例代码:

private void setMode(Activity activity, Display.Mode mode) {
    Window window = activity.getWindow();
 WindowManager.LayoutParams params = window.getAttributes();
 params.preferredDisplayModeId = mode.getModeId();
 window.setAttributes(params); //通过该函数通知wms layout变化。 
}

}

相关流程如下: 

  • app侧调用流程:

params.preferredDisplayModeId = mode.getModeId(); -->
    window.setAttributes(params); -->
        getWindowManager().updateViewLayout(decor, params);

  • 进而触发, WMS 调用(流程1.): 

relayoutWindow
    performSurfacePlacementNoTrace // RootWindowContainer
        applySurfaceChangesTransaction //RootWindowContainer.java
            applySurfaceChangesTransaction //DisplayContent.java 
                setDisplayProperties  //
                    setDisplayPropertiesInternal    
                        mDisplayModeDirector.getAppRequestObserver().setAppRequestedMode  
                            setAppRequestedMode(int displayId, int modeId) (int displayId 3 , int modeId  1)
                                setAppRequestedModeLocked  //设置display相应参数

  • 下一个vsync(由底向上):


  setAllowedDisplayConfigs //SurfaceControl.java   SurfaceControl设置相应的display config 到surfaceflinger
  at com.android.server.display.LogicalDisplay.configureDisplayLocked(LogicalDisplay.java:359)
  at com.android.server.display.DisplayManagerService.configureDisplayLocked(DisplayManagerService.java:1406)
  at com.android.server.display.DisplayManagerService.performTraversalLocked(DisplayManagerService.java:1209)
  at com.android.server.display.DisplayManagerService.performTraversalInternal(DisplayManagerService.java:520)
  - locked <0x50e3> (a com.android.server.display.DisplayManagerService$SyncRoot)
  at com.android.server.display.DisplayManagerService$LocalService.performTraversal(DisplayManagerService.java:2462)
  at com.android.server.wm.RootWindowContainer.applySurfaceChangesTransaction(RootWindowContainer.java:838)
  at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:610)
  at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:567)
  at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:159)
  at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:105)
  at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:95)

    

  • 再下一个surface flinger vsync:


surfaceflinger::handleMessageInvalidate handleMessage具体设置到hwc

app帧率黑白名单

       特殊app, 是否可以跑在系统帧率下的问题, 可以通过黑白名单解决。 目前系统支持黑名单, 白名单需要framework作特别定制。

黑名单方式

目前android 中支持帧率黑名单设置, 即不能以高帧率跑的app名单, 名单中的app 跑在最低帧率, 其他app跑在默认帧率。  两种方式可以创建该名单。 创建黑名单方式如下:

  1. 通过system resource: 添加item 在core/res/res/values/config.xml 文件的config_highRefreshRateBlacklist 下, 示例:

    <string-array name="config_highRefreshRateBlacklist">

        <item>"com.xxx.xxxxxxxxx"</item>                          

    </string-array>                                                                  

2. 添加相应名单到settings.config, 其中属性名为namespace+"/"+ name形式,值为包名list, 包名以逗号分割 ,见附后示例。

其中:

namespace :DeviceConfig.NAMESPACE_DISPLAY_MANAGER。

name: DisplayManager.DeviceConfig.KEY_HIGH_REFRESH_RATE_BLACKLIST。

写该属性需要有权限: Manifest.permission.WRITE_DEVICE_CONFIG

代码示例如下:

Settings.Config.putString(contentResolver, namespece+"/"+ name,  "com.xxx.xxxxx", true);

该settings数据存储位置: /data/system/user/0/settings_config.xml, 示例内容:

  <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<settings version="183">
  <setting id="22" name="display_manager/high_refresh_rate_blacklist" value="com.xxx.xxxxxxx, com.ssss.sssssssss" package="android" defaultValue="" defaultSysSet="true" />
 </settings>

无论是app自己请求帧率还是设立黑名单的方式, 在WMS都会通过调用RefreshRatePolicy.getPreferredModeId()函数获取到上面两种的设置。 在WMS刷新屏幕时,对于每个display, 按照Z order 从上向下的顺序, 依次调用RefreshRatePolicy.getPreferredModeId,去获取该窗口适合的display mode id (preferredModeID), getPreferredModeId首先检查app自身是否有要求的fps(即app api中介绍的设置params.preferredDisplayModeId), 如果有, preferredModeID 按app 自身要求给出, 如果没有再继续检查黑名单中是否有该app, 如果有的话, 按设定的系统最低帧率作为preferredModeID, 否则认为该window对于FPS没有特殊要求。 如果该display中多个窗口需要考虑preferredModeID, 则这些窗口中以Z order中最上层的window作为当前display的FPS, 然后按照app 设置帧率中提到的流程“WMS 调用(流程1.)“ 向displaymanagerservide 设置display mode ID .

例如display 0 上有以下窗口, z order 顺序上到下, 则最终按80设置该窗口的app 帧率:

    1. Window A   // 无特殊FPS 要求
    2. Window B   // app 设置FPS 80
    3. Window C //黑名单设置FPS60

调用getPreferredModeId()的流程如下(由底向上):

at com.android.server.wm.RefreshRatePolicy.getPreferredModeId(RefreshRatePolicy.java:70)
at com.android.server.wm.DisplayContent.lambda$new$8$DisplayContent(DisplayContent.java:821)
at com.android.server.wm.-$$Lambda$DisplayContent$qxt4izS31fb0LF2uo_OF9DMa7gc.accept(lambda:-1)
at com.android.server.wm.WindowContainer$ForAllWindowsConsumerWrapper.apply(WindowContainer.java:1172)
at com.android.server.wm.WindowContainer$ForAllWindowsConsumerWrapper.apply(WindowContainer.java:1162)
at com.android.server.wm.WindowState.applyInOrderWithImeWindows(WindowState.java:4269)
at com.android.server.wm.WindowState.forAllWindows(WindowState.java:4168)
at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:871)
at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:871)
at com.android.server.wm.DisplayContent.forAllWindows(DisplayContent.java:2153)
at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:888)
at com.android.server.wm.DisplayContent.applySurfaceChangesTransaction(DisplayContent.java:3765)
at com.android.server.wm.RootWindowContainer.applySurfaceChangesTransaction(RootWindowContainer.java:833)
at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:610)
at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:567)
at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:159)
at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:105)
at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:95)
at com.android.server.wm.WindowSurfacePlacer.lambda$new$0$WindowSurfacePlacer(WindowSurfacePlacer.java:62)
- locked <0x5186> (a com.android.server.wm.WindowManagerGlobalLock)
at com.android.server.wm.-$$Lambda$WindowSurfacePlacer$4Hbamt-LFcbu8AoZBoOZN_LveKQ.run(lambda:-1)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.os.HandlerThread.run(HandlerThread.java:67)
at com.android.server.ServiceThread.run(ServiceThread.java:44)

白名单方式

指明哪些app可以跑在最高帧率, 其他app默认为最低帧率。 该种方式可以使用以上类似方式设定。接口需要定义,建议为以下:

  • 通过system resource: resource name: config_highRefreshRateWhitelist。 
  • 添加相应名单到settings.config: 

namespace 同黑名单:DeviceConfig.NAMESPACE_DISPLAY_MANAGER。

name: DisplayManager.DeviceConfig.KEY_HIGH_REFRESH_RATE_WHITELIST ="high_refresh_rate_whitelist";

settings中设置帧率

settings 中设定帧率为全局帧率,限定了系统的最高帧率和最低帧率, 系统默认在该帧率区间运行, app请求的帧率不能突破settings限定。 修改如下setting值,会修改系统的fps, DisplayModeDirector.java 中主动监听下面settings值的变化,然后通过surfacecontrol 设置到surfaceflinger, 相应流程后面介绍。 

low power mode 的帧率范围为(0-60fps), 具有最高优先级。优先级由高到低: low power mode->settings -> app request.

private final Uri mPeakRefreshRateSetting =
Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
private final Uri mMinRefreshRateSetting =
Settings.System.getUriFor(Settings.System.MIN_REFRESH_RATE);
private final Uri mLowPowerModeSetting =
Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE);//low power mode

private final Uri mMatchContentFrameRateSetting =
Settings.Secure.getUriFor(Settings.Secure.MATCH_CONTENT_FRAME_RATE); // android S 新增测试api, 暂不做说明。 

adb命令修改settings帧率

adb shell settings put system peak_refresh_rate 90

通过SurfaceFlinger transacation 1035设定fps

使用该方法后, surfaceflinger 认为进入了debug mode, 不在接受上述两种方法设定的fps

adb : adb shell service call SurfaceFlinger 1035 i32 X //X 为surfaceflinger中displaymode ID, 通常为Display.Mode.getModeId 减1.  X 设为负值, 可以解除debug mode

帧率切换frameworks层实现

从上面的API 介绍中可以看到, 无论从APP、黑名单、还是Settings,  最后的设置都会调用到DisplayManagerService ,这一段的实现和代码逻辑可以参看Android 10 (Android Q)中的屏幕刷新率(display refresh rate)切换方法和策略_刷新率_方法_display_Android - 1024问

FPS 默认值设定

启动设备时, 硬件以最高帧率初始化, 后续在displaymanagerservice ready 之后, 该逻辑见SettingsObserver.updateRefreshRateSettingLocked@ frameworks/base/services/core/java/com/android/server/display/DisplayModeDirector.java

a. 如果settings中Settings.System.PEAK_REFRESH_RATE 和 Settings.System.MIN_REFRESH_RATE(FPS 最高值, 最低值), 则以该区间设置FPS。 如果settings中没有设置最高值peak_refresh_rate,则以系统默认值作为最高值。 

b. 系统默认值, 首先从资源文件中读取默认FPS,  该资源ID:config_defaultPeakRefreshRate@values/config.xml。 如果Settings.Config 中设置了“

DeviceConfig.NAMESPACE_DISPLAY_MANAGER/DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT”, 则以该值作为系统默认值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值