应用可通过 Frame Rate API 将其预期帧速率告知 Android 平台,该 API 可在以 Android 11(API 级别 30)或更高版本为目标平台的应用中使用。大多数设备历来都只支持一种显示屏刷新频率,通常为 60Hz,但这一直在变化。现在,许多设备都支持其他刷新频率,如 90Hz 或 120Hz。部分设备支持流畅刷新 开关,而其他开关则短暂显示黑屏,通常会持续一秒钟。
该 API 的主要目的是让应用能够更好地利用
支持的屏幕刷新率例如,如果应用播放 24Hz 视频并调用 setFrameRate()
,则可能会导致设备将显示刷新率从 60Hz 更改为 120Hz。这种新刷新率支持流畅无拖影地播放 24Hz 视频,而无需像在 60Hz 显示屏上播放相同视频时那样进行 3:2 拉伸。这样可以使用户
体验。
基本用法
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 会停止提交帧,但会保留
在屏幕上持续显示一段时间。常见情况包括:
到达视频末尾或用户暂停播放时。在这些情况下,请将帧速率参数设置为 0 并调用 setFrameRate()
,以将 Surface 的帧速率设置清除回默认值。在销毁 Surface 或 Surface 因用户切换到其他应用而隐藏时,无需像这样清除帧速率设置。仅当 Surface 保持可见但未使用时,才应清除帧速率设置。
非流畅帧速率切换
在某些设备上,刷新频率切换可能会出现视觉中断,例如
显示一两秒钟的时间这通常发生在机顶盒、电视面板
以及类似设备默认情况下,Android 框架在调用 Surface.setFrameRate()
API 时不会切换模式,以避免此类视觉中断。
有些用户更喜欢在视频开始播放时就有视觉冲击力, 到这里就结束了这样,显示屏的刷新率就可以与视频帧速率保持一致,并避免电影播放时出现 3:2 拉伸抖动等帧速率转换伪影。
因此,如果同时在两台设备上 用户和应用:
- 用户:要选择启用这项功能,用户可以启用匹配内容帧速率用户 设置。
- 应用:如要选择启用,应用需要允许通过
CHANGE_FRAME_RATE_ALWAYS
进入setFrameRate()
。
我们建议您始终使用 CHANGE_FRAME_RATE_ALWAYS
。这是因为
视频帧速率超过了更改
刷新率。
其他建议
请遵循以下建议来处理常见场景。
多个 Surface
Android 平台旨在正确处理存在多个帧速率设置的 Surface 的情况。如果您的应用有多个帧速率不同的 Surface,请针对每个 Surface 使用正确的帧速率调用 setFrameRate()
。即使设备使用分屏或画中画模式同时运行多个应用,每个应用也可以安全地调用 setFrameRate()
来获取自己的 Surface。
平台不会更改应用的帧速率
即使设备支持应用在调用
setFrameRate()
,则在某些情况下设备无法切换到
刷新率例如,优先级较高的 Surface 可能具有不同的帧速率设置,或者设备可能处于省电模式(对显示刷新率设置限制以节省电量)。应用必须
在设备未将显示刷新频率切换为
应用的帧速率设置,即使设备在正常情况下切换也是如此
情况。
当显示屏刷新率与应用帧速率不匹配时,应用可以自行决定如何响应。对于视频,帧速率会固定为
源视频,并且需要使用下拉菜单来显示视频内容。答
游戏可能会改为选择尝试以显示屏刷新频率运行
保持其首选帧速率应用不应更改其值
传递给 setFrameRate()
。它应保持设置状态
应用的首选帧速率,无论应用如何处理
平台不会根据应用的请求进行调整。这样一来,如果设备条件发生变化,允许使用其他显示刷新率,平台就会拥有正确的信息来切换到应用的首选帧速率。
当应用无法或无法以显示屏刷新频率运行时, 应使用 平台设置呈现时间戳的机制:
使用这些时间戳会阻止平台呈现应用帧 过早,这样会导致不必要的抖动。正确使用帧呈现时间戳有点棘手。如需了解游戏,请参阅我们的 帧同步指南 详细了解如何避免抖动,并考虑使用 Android Frame Pacing 库。
在某些情况下,平台可能会切换到应用的帧速率倍数
在 setFrameRate()
中指定。例如,应用可能会调用 setFrameRate()
60Hz,设备可能会切换到 120Hz。其中一个原因可能是
另一个应用的 Surface 的帧速率设置为 24Hz。在这种情况下,以 120Hz 运行显示屏将允许 60Hz 界面和 24Hz 界面同时运行,而无需拉下。
当显示屏以应用帧速率的倍数运行时,应用 应为每个帧指定呈现时间戳,以避免不必要的 抖动。对于游戏,Android Frame Pacing 库 设置帧呈现时间戳
setFrameRate() 与 preferredDisplayModeId
WindowManager.LayoutParams.preferredDisplayModeId
是应用向平台指明其帧速率的另一种方式。有些应用只想更改显示刷新率,而不是更改其他显示模式设置(例如显示分辨率)。一般来说,使用
setFrameRate()
,而非 preferredDisplayModeId
。setFrameRate()
函数更易于使用,因为应用无需搜索显示模式列表即可找到具有特定帧速率的模式。
在多个 Surface 以不同帧速率运行的情况下,setFrameRate()
可让平台有更多机会选择兼容的帧速率。例如,设想一个有两个应用
在 Pixel 4 的分屏模式下运行,其中一个应用在播放 24Hz 的视频
另一个是向用户显示可滚动列表。Pixel 4 支持两种
显示屏刷新率:60Hz 和 90Hz。使用 preferredDisplayModeId
API 时,视频 Surface 会被强制选择 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()
一次。
游戏或其他非视频应用的使用情况
虽然视频是 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
:- 如果要播放长时间运行的视频(例如电影),请使用
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
是视频的帧速率。 - 开始播放视频
- 调用
- 如果即将发生非流畅模式更改,请执行以下操作:
- 显示用户体验以通知用户。请注意,我们建议您采用 用户关闭此用户体验并跳过第 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();
}