热插拔处理

在 Android TV 机顶盒 (STB) 和 OTT 服务 (OTT) 设备等具有外接显示器(通过 HDMI 或 DisplayPort 连接)的设备上,显示模式和受支持的 HDR 类型等显示功能会动态变化。而 HDMI 热插拔信号正是引发此变化的原因。例如,当用户从一个显示器切换到另一个显示器,或在没有连接显示器的情况下启动设备时,便会出现这种情况。为处理热插拔和显示功能的动态变化,Android 12 及更高版本中包含对框架的一些更改。

本文将介绍如何在混合渲染器 HAL 实现中处理显示器热插拔和显示功能的变化。此外,本页还将讨论如何管理关联的帧缓冲区并防止在这些情况下出现竞态条件。

更新显示功能

本部分将介绍 Android 框架如何处理由混合渲染器 HAL 触发的显示功能变化。

OEM 只有先实现混合渲染器 HAL,以使其能使用 onHotplug(display, connection=CONNECTED) 通知框架对显示功能所做的任何更改,Android 才能正确处理显示功能的变化。实现混合渲染器 HAL 后,Android 会按如下方式处理显示功能发生的更改:

  1. 检测到显示功能有所变化时,框架会收到 onHotplug(display, connection=CONNECTED) 通知。
  2. 收到通知后,框架会删除原有的显示状态,根据 HAL 中的新功能,使用以下方法重新创建显示状态:getActiveConfiggetDisplayConfigsgetDisplayAttributegetColorModesgetHdrCapabilitiesgetDisplayCapabilities
  3. 框架重新创建好新显示状态后,会向正在监听此类事件的应用发送 onDisplayChanged 回调。

后续发生 onHotplug(display, connection=CONNECTED) 事件时,框架会重新分配帧缓冲区。请参阅客户端帧缓冲区管理,详细了解如何正确管理帧缓冲区内存,以避免在分配新的帧缓冲区期间造成故障。

处理常见的连接场景

本部分将介绍当主屏幕与设备连接和断开连接时,如何在实现中正确处理各种连接场景。

尽管 Android 框架专为移动设备而打造,但不提供对已断开连接的主屏幕的内置支持。相反,如果主屏幕已断开物理连接,则 HAL 在与框架交互时,必须将主屏幕替换为占位符屏幕。

机顶盒和电视加密狗(配备可以断开连接的外接显示器)可能会出现以下场景。为实现对这些场景的支持,请使用下表中的信息:

场景 使用
启动时尚未连接任何显示器
  • onHotplug(display, connection=CONNECTED) 信号从混合渲染器 HAL 发送到框架。
  • 将混合渲染器 HAL 中物理屏幕的状态替换为占位符屏幕的状态。
已物理连接到主屏幕
主屏幕已断开物理连接
  • 再次将 onHotplug(display, connection=CONNECTED) 事件从混合渲染器 HAL 发送到框架。
  • 将混合渲染器 HAL 中物理显示器的状态替换为占位符屏幕的状态。占位符屏幕应只有一个单一显示模式,以便框架向应用发送 onDisplayChanged 回调(因为受支持的模式组已更改)。 该单一显示模式必须与物理显示器断开连接之前的最后一个活动模式相符,以免应用收到配置更改事件

非 HDMI 连接注意事项

Android TV 仅支持以下分辨率:

  • 720x1280
  • 1080x1920
  • 2160x3840
  • 4320x7680

如果机顶盒或电视加密狗尝试通过 CVBS 连接显示采用不支持的分辨率(例如 480i)的内容,系统会向用户显示错误消息。

如果机顶盒或电视加密狗同时具有 HDMI 连接和非 HDMI 连接,则 HDMI 连接为主屏幕,而非 HDMI 连接处于非活动状态。因此,如果在非 HDMI 连接仍处于连接状态时断开 HDMI 连接,系统会向 SurfaceFlinger 发送事件,并且必须通过 getDisplayAttribute 和其他 iComposerClient API(例如 getHdrCapabilities)反映非 HDMI 的显示功能。

使用序列配置 ID 以防出现竞态条件

如果混合渲染器 HAL 更新受支持的显示配置,与框架调用 setActiveConfigsetActiveConfigWithConstraints 同时发生,就会出现竞态条件。解决方案是实现混合渲染器 HAL,以使用序列 ID 并避免出现此问题。

本部分先介绍什么情况下可能会出现竞态条件,接着详细介绍如何实现混合渲染器 HAL,以便使用序列 ID 来防止再出现类似情况。

如果未将新序列 ID 分配给新显示配置,进而导致出现竞态条件,请考虑采用以下事件序列:

  1. 受支持的显示配置 ID 包括:

    • id=1,1080x1920 60 Hz
    • id=2,1080x1920 50 Hz
  2. 框架调用 setActiveConfig(display, config=1)

  3. 同时,混合渲染器 HAL 处理显示配置的更改,并将其内部状态更新为一组新的显示配置,如下所示:

    • id=1,2160x3840 60 Hz
    • id=2,2160x3840 50 Hz
    • id=3,1080x1920 60 Hz
    • id=4,1080x1920 50 Hz
  4. 混合渲染器 HAL 向框架发送 onHotplug 事件,以通知受支持的模式组已更改。

  5. 混合渲染器 HAL 收到 setActiveConfig(display, config=1)(在第 2 步中生成)。

  6. HAL 将其解读为:框架请求将配置更改为 2160x3840 60 Hz,实际上所需的配置为 1080x1920 60 Hz。

由于对所需配置更改的解读有误,使用非序列 ID 分配的进程到此为止。

配置混合渲染器 HAL 以使用序列 ID

为避免出现此类竞态条件,OEM 必须按如下方式实现混合渲染器 HAL:

  • 混合渲染器 HAL 在更新受支持的显示配置时,会将新的序列 ID 分配给新的显示配置。
  • 当框架调用包含无效配置 ID 的 setActiveConfigsetActiveConfigWithConstraints 时,混合渲染器 HAL 会忽略该调用。

这些步骤的目的是防止出现竞态条件,如以下讨论中所示。

如果已将新序列 ID 分配给新显示配置,请考虑采用以下事件序列:

  1. 受支持的显示配置 ID 包括:

    • id=1,1080x1920 60 Hz
    • id=2,1080x1920 50 Hz
  2. 框架调用 setActiveConfig(display, config=1)

  3. 处理显示配置的更改时,系统会从下一个未使用的整数开始,依序分配下一组配置 ID,如下所示:

    • id=3,2160x3840 60 Hz

    • id=4,2160x3840 50 Hz

    • id=5,1080x1920 60 Hz

    • id=6,1080x1920 50 Hz

  4. 混合渲染器 HAL 向框架发送 onHotplug 事件,以通知受支持的模式组已更改。

  5. 混合渲染器 HAL 收到 setActiveConfig(display, config=1)(在第 2 步中生成)。

  6. 由于该 ID 不再有效,因此混合渲染器 HAL 会忽略该调用。

  7. 框架接收并处理第 4 步中生成的 onHotplug 事件。它会使用 getDisplayConfigsgetDisplayAttribute 函数调用混合渲染器 HAL。借助这些函数,框架可以识别新 ID (5) 即为所需的分辨率 (1080x1920) 和刷新率 (60 Hz)。

  8. 框架再次发送 setActiveConfig 事件,其中更新后的 ID 为 5。

  9. 混合渲染器 HAL 收到 setActiveConfig(display, config=5)(在第 5 步中生成)。

  10. HAL 将其正确解读为:框架请求将配置更改为 1080x1920 60 Hz。

如上例所示,使用序列 ID 分配的进程可确保防止出现竞态条件,同时更新并应用正确的显示配置更改。