借助帧速率 API,应用可以告知 Android 平台其预期的帧 适用于以 Android 11(API 级别 30)或更高版本为目标平台的应用。 大多数设备一直以来都只支持一种显示屏刷新频率 通常为 60Hz,但现在发生了变化。许多设备现在支持 刷新率,例如 90Hz 或 120Hz。部分设备支持流畅刷新 开关,而其他开关则短暂显示黑屏,通常会持续一秒钟。
该 API 的主要目的是让应用能够更好地利用
支持的屏幕刷新率例如,播放 24Hz 视频的应用
调用 setFrameRate()
可能会导致设备更改显示屏
刷新率范围为 60Hz 到 120Hz这种新的刷新频率可让您
24Hz 视频播放时无抖动,无需像
在 60Hz 显示屏上播放相同视频所需的资源这样可以使用户
体验
基本用法
Android 提供了多种访问和控制 Surface 的方式,
多个版本的 setFrameRate()
API。每个版本的 API 都采用
参数和工作原理都相同:
Surface.setFrameRate()
SurfaceControl.Transaction.setFrameRate()
ANativeWindow_setFrameRate()
ASurfaceTransaction_setFrameRate()
应用无需考虑实际支持的屏幕刷新率,
该值可调用
Display.getSupportedModes()
、
以便安全地调用 setFrameRate()
。例如,即使设备
支持 60Hz,请使用您的应用偏好的帧速率调用 setFrameRate()
。
如果设备的帧速率与应用的帧速率不匹配
当前显示刷新率。
查看调用 setFrameRate()
是否会导致显示刷新发生变化
率,通过调用
DisplayManager.registerDisplayListener()
或 AChoreographer_registerRefreshRateCallback()
。
调用 setFrameRate()
时,最好传入确切的帧速率,
而不是四舍五入为整数。例如,呈现以
29.97Hz,传入 29.97,而不是四舍五入到 30。
对于视频应用,应设置传递给 setFrameRate()
的兼容性参数
向 Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
提供额外的提示,
应用将使用下拉菜单来适应不匹配的 Android 平台
显示屏刷新率(这会导致抖动)。
在某些情况下,视频 surface 会停止提交帧,但会保留
在屏幕上持续显示一段时间。常见情况包括:
到达视频末尾或用户暂停播放时。在这些情况下
调用 setFrameRate()
并将帧速率参数设为 0,以清除 Surface 的
帧速率设置还原为默认值。正在清除帧速率设置
就不需要这样操作了
隐藏,因为用户切换到其他应用。清除帧速率
设置。
非无缝帧速率切换
在某些设备上,刷新频率切换可能会出现视觉中断,例如
显示一两秒钟的时间这通常发生在机顶盒、电视面板
以及类似设备默认情况下,Android 框架不会切换模式
当Surface.setFrameRate()
以避免此类视觉中断。
有些用户更喜欢在视频开始播放时就有视觉冲击力, 到这里就结束了这样,显示屏的刷新率 降低视频帧速率,并避免帧速率转换失真,例如 3:2 用于播放影片的下拉抖动。
因此,如果同时在两台设备上 用户和应用:
- 用户:要选择启用这项功能,用户可以启用匹配内容帧速率用户 设置。
- 应用:如要选择启用,应用需要允许通过
CHANGE_FRAME_RATE_ALWAYS
进入setFrameRate()
。
我们建议您始终使用 CHANGE_FRAME_RATE_ALWAYS
。这是因为
视频帧速率超过了更改
刷新率。
其他建议
对于常见情况,请遵循以下建议。
多个平台
Android 平台经过精心设计,能够正确处理存在以下情况的情形:
具有不同帧速率设置的多个 Surface当您的应用有多个
具有不同帧速率的 surface,使用正确的setFrameRate()
帧速率即使设备在
一次,使用分屏或画中画模式,每个应用可以安全地调用
setFrameRate()
。
平台不会更改应用的帧速率
即使设备支持应用在调用
setFrameRate()
,则在某些情况下设备无法切换到
刷新率例如,优先级较高的途径可能具有
或者设备可能处于省电模式(设置
限制显示屏刷新频率以节省电量)。应用必须
在设备未将显示刷新频率切换为
应用的帧速率设置,即使设备在正常情况下切换也是如此
情况。
由应用决定在显示刷新频率时如何响应
与应用帧速率不符。对于视频,帧速率会固定为
源视频,并且需要使用下拉菜单来显示视频内容。答
游戏可能会改为选择尝试以显示屏刷新频率运行
保持其首选帧速率应用不应更改其值
传递给 setFrameRate()
。它应保持设置状态
应用的首选帧速率,无论应用如何处理
平台不会根据应用的请求进行调整。这样一来,如果设备
更改条件以允许使用其他显示屏刷新频率,
平台具备正确的信息,可以切换到应用的首选帧
。
当应用无法或无法以显示屏刷新频率运行时, 应使用 平台设置呈现时间戳的机制:
使用这些时间戳会阻止平台呈现应用帧 过早,这样会导致不必要的抖动。正确使用帧 显示时间戳有点棘手。如需了解游戏,请参阅我们的 帧同步指南 详细了解如何避免抖动,并考虑使用 Android Frame Pacing 库。
在某些情况下,平台可能会切换到应用的帧速率倍数
在 setFrameRate()
中指定。例如,应用可能会调用 setFrameRate()
60Hz,设备可能会切换到 120Hz。其中一个原因可能是
另一个应用的 Surface 的帧速率设置为 24Hz。在
在这种情况下,以 120Hz 运行显示屏将同时允许 60Hz 表面和
24Hz surface,无需下拉即可运行。
当显示屏以应用帧速率的倍数运行时,应用 应为每个帧指定呈现时间戳,以避免不必要的 抖动。对于游戏,Android Frame Pacing 库 设置帧呈现时间戳
setFrameRate() 与 preferredDisplayModeId
WindowManager.LayoutParams.preferredDisplayModeId
是应用向平台指明其帧速率的另一种方式。部分
应用只想更改显示刷新率
显示模式设置,例如显示分辨率。一般来说,使用
setFrameRate()
,而非 preferredDisplayModeId
。setFrameRate()
函数更容易使用,因为应用不需要在
显示模式列表,以查找具有特定帧速率的模式。
setFrameRate()
可让平台有更多机会选择兼容的
当有多个表面以
不同的帧速率例如,设想一个有两个应用
在 Pixel 4 的分屏模式下运行,其中一个应用在播放 24Hz 的视频
另一个是向用户显示可滚动列表。Pixel 4 支持两种
显示屏刷新率:60Hz 和 90Hz。通过使用 preferredDisplayModeId
API,
系统强制视频界面选择 60Hz 或 90Hz。通过调用
setFrameRate()
和 24Hz,视频 Surface 为平台提供更多
与源视频帧速率相关的信息,以便平台
选择 90Hz 作为显示屏刷新率,
场景。
不过,在某些情况下,应使用 preferredDisplayModeId
而不是 setFrameRate()
,例如:
- 如果应用想要更改分辨率或其他显示模式设置,
请使用
preferredDisplayModeId
。 - 平台只有在收到对
setFrameRate()
(如果模式开关是轻量级且不太可能) 可被用户察觉到如果应用想要切换显示刷新 即使需要频繁切换模式(例如,在 Android TV 上 设备),请使用preferredDisplayModeId
。 - 应用无法处理以多个应用帧的倍数运行的显示屏
这要求在每一帧上设置呈现时间戳
请使用
preferredDisplayModeId
。
setFrameRate() 与 preferredRefreshRate
WindowManager.LayoutParams#preferredRefreshRate
在应用窗口中设置首选帧速率,并且该速率适用
窗口内的所有表面应用应指定其首选提供商
不考虑设备支持的刷新频率,类似于
setFrameRate()
,以便调度程序更好地了解应用的预期
帧速率
对于使用 setFrameRate()
的 Surface,会忽略 preferredRefreshRate
。在
一般使用 setFrameRate()
(如果可能)。
PreferredRefreshRate 与 preferredDisplayModeId
如果应用只想更改首选刷新率,最好使用
preferredRefreshRate
,而不是 preferredDisplayModeId
。
避免过于频繁地调用 setFrameRate()
虽然 setFrameRate()
调用在性能方面开销并不高,
应用应避免每一帧或每帧多次调用 setFrameRate()
。调用 setFrameRate()
可能会导致
显示刷新率,这可能会导致过渡期间丢失帧。
您应提前确定正确的帧速率,
setFrameRate()
1 次。
游戏或其他非视频应用的使用情况
虽然视频是 setFrameRate()
API 的主要用例,
用于其他应用例如,一款游戏不应高于
60Hz(为了减少耗电量并实现更长的播放会话)可以调用
Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
。在本课中,
因此,默认情况下以 90Hz 运行的设备
会改为以 60Hz 运行
这样就可以避免在播放过程中发生抖动
游戏的运行频率为 60Hz,显示屏的运行频率为 90Hz。
使用了 FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
仅适用于视频应用。对于
非视频使用情况,请使用 FRAME_RATE_COMPATIBILITY_DEFAULT
。
选择帧速率更改策略
- 在显示长视频(如
电影,调用
setFrameRate(
fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS)
其中 fps 是指视频的帧速率。 - 我们强烈建议您不要让应用使用
CHANGE_FRAME_RATE_ALWAYS
调用setFrameRate()
。
视频播放应用集成示例
若要在视频播放应用中集成刷新率开关,我们建议您按照以下步骤操作:
- 确定
changeFrameRateStrategy
: <ph type="x-smartling-placeholder">- </ph>
- 如果要播放长时间运行的视频(例如电影),请使用
MATCH_CONTENT_FRAMERATE_ALWAYS
- 如果要播放动作预告片等短视频,请使用
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
- 如果要播放长时间运行的视频(例如电影),请使用
- 如果
changeFrameRateStrategy
为CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
,转到第 4 步。 - 通过检查
以下两点都是真的:
<ph type="x-smartling-placeholder">
- </ph>
- 当前的刷新频率不支持流畅的模式切换(让我们
视频的帧速率(我们称之为 V)。这将
如果 C 和 V 不同,
Display.getMode().getAlternativeRefreshRates
不包含 V 的倍数。 - 用户已选择启用非无缝刷新率更改。您可以检测
方法是检查
DisplayManager.getMatchContentFrameRateUserPreference
返回MATCH_CONTENT_FRAMERATE_ALWAYS
- 当前的刷新频率不支持流畅的模式切换(让我们
视频的帧速率(我们称之为 V)。这将
如果 C 和 V 不同,
- 如果切换是无缝的,请执行以下操作:
<ph type="x-smartling-placeholder">
- </ph>
- 调用
setFrameRate
并将fps
、FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
和 和changeFrameRateStrategy
,其中fps
是视频的帧速率。 - 开始播放视频
- 调用
- 如果即将发生非无缝模式更改,请执行以下操作:
<ph type="x-smartling-placeholder">
- </ph>
- 显示用户体验以通知用户。请注意,我们建议您采用 用户忽略此用户体验,并跳过第 5.d 步的额外延迟。这是 因为我们推荐的延迟时间超过了 切换时间更短。
- 调用
setFrameRate
并将fps
、FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
和 和CHANGE_FRAME_RATE_ALWAYS
、 其中fps
是视频的帧速率。 - 等待
onDisplayChanged
回调。 - 等待 2 秒钟,让模式切换完成。
- 开始播放视频
只支持流畅切换的伪代码如下:
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
transaction.setFrameRate(surfaceControl,
contentFrameRate,
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
transaction.apply();
beginPlayback();
用于支持无缝切换和非无缝切换的伪代码(如上所述)如下:
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
if (isSeamlessSwitch(contentFrameRate)) {
transaction.setFrameRate(surfaceControl,
contentFrameRate,
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
transaction.apply();
beginPlayback();
} else if (displayManager.getMatchContentFrameRateUserPreference()
== MATCH_CONTENT_FRAMERATE_ALWAYS) {
showRefreshRateSwitchUI();
sleep(shortDelaySoUserSeesUi);
displayManager.registerDisplayListener(…);
transaction.setFrameRate(surfaceControl,
contentFrameRate,
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
CHANGE_FRAME_RATE_ALWAYS);
transaction.apply();
waitForOnDisplayChanged();
sleep(twoSeconds);
hideRefreshRateSwitchUI();
beginPlayback();
}