帧速率

借助帧速率 API,应用可以告知 Android 平台其预期的帧 适用于以 Android 11(API 级别 30)或更高版本为目标平台的应用。 大多数设备一直以来都只支持一种显示屏刷新频率 通常为 60Hz,但现在发生了变化。许多设备现在支持 刷新率,例如 90Hz 或 120Hz。部分设备支持流畅刷新 开关,而其他开关则短暂显示黑屏,通常会持续一秒钟。

该 API 的主要目的是让应用能够更好地利用 支持的屏幕刷新率例如,播放 24Hz 视频的应用 调用 setFrameRate() 可能会导致设备更改显示屏 刷新率范围为 60Hz 到 120Hz这种新的刷新频率可让您 24Hz 视频播放时无抖动,无需像 在 60Hz 显示屏上播放相同视频所需的资源这样可以使用户 体验

基本用法

Android 提供了多种访问和控制 Surface 的方式, 多个版本的 setFrameRate() API。每个版本的 API 都采用 参数和工作原理都相同:

应用无需考虑实际支持的屏幕刷新率, 该值可调用 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 。这是因为 视频帧速率超过了更改 刷新率。

其他建议

对于常见情况,请遵循以下建议。

多个平台

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(),而非 preferredDisplayModeIdsetFrameRate() 函数更容易使用,因为应用不需要在 显示模式列表,以查找具有特定帧速率的模式。

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()

视频播放应用集成示例

若要在视频播放应用中集成刷新率开关,我们建议您按照以下步骤操作:

  1. 确定 changeFrameRateStrategy: <ph type="x-smartling-placeholder">
      </ph>
    1. 如果要播放长时间运行的视频(例如电影),请使用 MATCH_CONTENT_FRAMERATE_ALWAYS
    2. 如果要播放动作预告片等短视频,请使用 CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
  2. 如果 changeFrameRateStrategyCHANGE_FRAME_RATE_ONLY_IF_SEAMLESS ,转到第 4 步。
  3. 通过检查 以下两点都是真的: <ph type="x-smartling-placeholder">
      </ph>
    1. 当前的刷新频率不支持流畅的模式切换(让我们 视频的帧速率(我们称之为 V)。这将 如果 C 和 V 不同, Display.getMode().getAlternativeRefreshRates 不包含 V 的倍数。
    2. 用户已选择启用非无缝刷新率更改。您可以检测 方法是检查 DisplayManager.getMatchContentFrameRateUserPreference 返回 MATCH_CONTENT_FRAMERATE_ALWAYS
  4. 如果切换是无缝的,请执行以下操作: <ph type="x-smartling-placeholder">
      </ph>
    1. 调用 setFrameRate 并将 fpsFRAME_RATE_COMPATIBILITY_FIXED_SOURCE 和 和 changeFrameRateStrategy,其中 fps 是视频的帧速率。
    2. 开始播放视频
  5. 如果即将发生非无缝模式更改,请执行以下操作: <ph type="x-smartling-placeholder">
      </ph>
    1. 显示用户体验以通知用户。请注意,我们建议您采用 用户忽略此用户体验,并跳过第 5.d 步的额外延迟。这是 因为我们推荐的延迟时间超过了 切换时间更短。
    2. 调用 setFrameRate 并将 fpsFRAME_RATE_COMPATIBILITY_FIXED_SOURCE 和 和CHANGE_FRAME_RATE_ALWAYS、 其中 fps 是视频的帧速率。
    3. 等待 onDisplayChanged 回调。
    4. 等待 2 秒钟,让模式切换完成。
    5. 开始播放视频

只支持流畅切换的伪代码如下:

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();
}