相机 HAL3 缓冲区管理 API

Android 10 引入了可选的相机 HAL3 缓冲区管理 API,它使您能够实现缓冲区管理逻辑,以便在相机 HAL 实现中达成不同的内存和捕获延迟折衷权衡。

相机 HAL 需要 N 个请求(其中 N 等于管道深度)在其管道中排队,但通常不需要同时使用所有 N 组输出缓冲区。

例如,在 HAL 管道中排队的请求可能有 8 个,但 HAL 只需使用管道最后阶段的 2 个请求的输出缓冲区。在搭载 Android 9 及更低版本的设备上,当请求在 HAL 中排队时,相机框架会分配缓冲区,以便 HAL 中有 6 组未使用的缓冲区。在 Android 10 中,相机 HAL3 缓冲区管理 API 允许分离输出缓冲区,从而释放 6 组缓冲区的空间。对于高端设备,这可能会节省数百兆的内存,而对于低内存设备,这也非常有用。

图 1 显示了搭载 Android 9 及更低版本的设备的相机 HAL 接口示意图。图 2 显示了已实现相机 HAL3 缓冲区管理 API 的 Android 10 中的相机 HAL 接口。

Android 9 或更低版本中的缓冲区管理

图 1. Android 9 及更低版本中的相机 HAL 接口

Android Q 中的缓冲区管理

图 2. 使用缓冲区管理 API 的 Android 10 中的相机 HAL 接口

实现缓冲区管理 API

要实现缓冲区管理 API,相机 HAL 必须执行以下操作:

  • 实现 HIDL ICameraDevice@3.5
  • 将摄像头特征密钥 android.info.supportedBufferManagementVersion 设置为 HIDL_DEVICE_3_5

相机 HAL 使用 ICameraDeviceCallback.hal 中的 requestStreamBuffersreturnStreamBuffers 方法请求并返回缓冲区。该 HAL 还必须实现 ICameraDeviceSession.hal 中的 signalStreamFlush 方法来指示相机 HAL 返回缓冲区。

requestStreamBuffers

使用 requestStreamBuffers 方法从摄像头传输框架请求缓冲区。使用相机 HAL3 缓冲区管理 API 时,摄像头传输框架中的捕获请求不会包含输出缓冲区,也就是说,StreamBuffer 中的 bufferId 字段为 0。因此,相机 HAL 必须使用 requestStreamBuffers 从摄像头传输框架请求缓冲区。

通过 requestStreamBuffers 方法,调用程序可以在一次调用中从多个输出流请求多个缓冲区,从而减少 HIDL IPC 调用次数。不过,如果同时请求的缓冲区增加,调用所需时间会增加,这可能会导致从请求到结果的总延迟时间增加。此外,由于 requestStreamBuffers 调用已在摄像头服务中序列化,因此建议相机 HAL 使用专用的高优先级线程请求缓冲区。

如果缓冲区请求失败,相机 HAL 必须能够正确处理非严重错误。下表介绍了缓冲区请求失败的常见原因以及相机 HAL 应如何处理这些失败。

  • 应用与输出流断开连接:这是非严重错误。相机 HAL 应针对任何以已断开连接的输出流为目标的捕获请求发送 ERROR_REQUEST,并做好正常处理后续请求的准备。
  • 超时:如果应用在忙于执行密集型处理操作,同时保留某些缓冲区,则可能会发生这种情况。相机 HAL 应针对因超时错误而无法完成的捕获请求发送 ERROR_REQUEST,并做好正常处理后续请求的准备。
  • 摄像头传输框架正在准备新的输出流配置:相机 HAL 应一直等到下一次 configureStreams 调用完成,然后再重新调用 requestStreamBuffers
  • 相机 HAL 已达到其缓冲区限制maxBuffers 字段):相机 HAL 应一直等到返回输出流的至少一个缓冲区,然后再重新调用 requestStreamBuffers

returnStreamBuffers

使用 returnStreamBuffers 方法将额外的缓冲区返回到摄像头传输框架。相机 HAL 通常会通过 processCaptureResult 方法将缓冲区返回到摄像头传输框架,但是它只能考虑已发送至相机 HAL 的捕获请求。通过 requestStreamBuffers 方法,相机 HAL 实现保留的缓冲区数可能会超过摄像头传输框架所请求的缓冲区数。这时应使用 returnStreamBuffers 方法。如果 HAL 实现保留的缓冲区数从未超过请求的缓冲区数,则相机 HAL 实现无需调用 returnStreamBuffers 方法。

signalStreamFlush

signalStreamFlush 方法由摄像头传输框架调用,用于通知相机 HAL 返回当前可用的所有缓冲区。通常在以下情况下调用此方法:摄像头传输框架即将调用 configureStreams 且必须排空相机捕获管道。与 returnStreamBuffers 方法类似,如果相机 HAL 实现保留的缓冲区数不超过请求的缓冲区数,则可以不实现此方法。

摄像头传输框架调用 signalStreamFlush 后,该框架会停止向相机 HAL 发送新的捕获请求,直到所有缓冲区均已返回到摄像头传输框架为止。返回所有缓冲区时,requestStreamBuffers 方法调用会失败,并且摄像头传输框架会在整洁状态下继续工作。然后摄像头传输框架会调用 configureStreamsprocessCaptureRequest 方法。如果摄像头传输框架调用 configureStreams 方法,相机 HAL 可以在 configureStreams 调用成功返回后重新开始请求缓冲区。如果摄像头传输框架调用 processCaptureRequest 方法,相机 HAL 可在 processCaptureRequest 调用期间开始请求缓冲区。

signalStreamFlush 方法和 flush 方法的语义有所不同。调用 flush 方法时,HAL 会终止待处理的捕获请求并显示 ERROR_REQUEST,以尽快排空管道。调用 signalStreamFlush 方法时,HAL 必须正常完成所有待处理的捕获请求,并将所有缓冲区返回到摄像头传输框架。

signalStreamFlush 方法与其他方法之间的另一个差异在于 signalStreamFlush 是单向 HIDL 方法,也就是说,摄像头传输框架可能会先调用其他屏蔽 API,然后 HAL 再接收 signalStreamFlush 调用。这意味着,signalStreamFlush 方法和其他方法(特别是 configureStreams 方法)可能会以不同的顺序(异于其在摄像头传输框架中的调用顺序)到达相机 HAL。为解决此异步问题,streamConfigCounter 字段已添加到 StreamConfiguration 并作为参数添加到 signalStreamFlush 方法。相机 HAL 实现应使用 streamConfigCounter 参数来确定 signalStreamFlush 调用到达时间是否晚于其相应的 configureStreams 调用时间。如需查看示例,请参见图 3。

处理延迟到达的调用

图 3. 相机 HAL 应如何检测和处理延迟到达的 signalStreamFlush 调用

实现缓冲区管理 API 时发生的行为变更

使用缓冲区管理 API 实现缓冲区管理逻辑时,请考虑相机和相机 HAL 实现可能会发生的行为变更:

  • 捕获请求更快且更频繁地到达相机 HAL:如果不使用缓冲区管理 API,摄像头传输框架会针对每个捕获请求请求输出缓冲区,然后再向相机 HAL 发送捕获请求。如果使用缓冲区管理 API,则摄像头传输框架不再需要等待缓冲区请求完成,因此可以更早向相机 HAL 发送捕获请求。

    此外,如果不使用缓冲区管理 API,当捕获请求的其中一个输出流达到 HAL 一次可容纳的最大缓冲区数(此值由相机 HAL 在 configureStreams 调用的返回值中的 HalStream::maxBuffers 字段中指定)时,摄像头传输框架会停止发送捕获请求。如果使用缓冲区管理 API,此限制行为不再存在,而且如果 HAL 中排队的捕获请求过多,相机 HAL 实现不得接受 processCaptureRequest 调用。

  • requestStreamBuffers 调用延迟变化显著requestStreamBuffers 调用所需时间超过平均时间的原因有很多。例如:

    • 对于新建输出流的前几个缓冲区,调用所需时间可能更长,因为设备需要分配内存。
    • 预期延迟时间会按每次调用时请求的缓冲区数的比例增加。
    • 该应用正在保留缓冲区并忙于处理操作。这可能会导致缓冲区请求因缓冲区不足或 CPU 繁忙而降速或超时。

缓冲区管理策略

缓冲区管理 API 支持实现各种类型的缓冲区管理策略。以下是一些示例:

  • 向后兼容:调用 processCaptureRequest 期间,HAL 会针对捕获请求请求缓冲区。此策略不会节省任何内存,但可用作缓冲区管理 API 的第一个实现,几乎不需要更改现有相机 HAL 的代码。
  • 最大限度地节省内存:相机 HAL 仅在需要填充缓冲区之前立即请求输出缓冲区。此策略可最大限度地节省内存。此策略的潜在弊端是,当完成缓冲区请求所需时间过长时,相机管道会出现更多卡顿。
  • 已缓存:相机 HAL 会缓存少许缓冲区,以降低缓冲区请求速度偶尔较慢时受到影响的可能性。

相机 HAL 可针对特定使用情形采用不同的策略,例如,对于使用大量内存的使用情形采用“最大限度地节省内存”策略,对于其他使用情形则采用“向后兼容”策略。

外部相机 HAL 中的实现示例

外部相机 HAL 是在 Android 9 中引入的,可在 hardware/interfaces/camera/device/3.5/ 上的源代码树中找到。在 Android 10 中,此外部相机 HAL 已进行更新,其中新增了 ExternalCameraDeviceSession.cpp,这是缓冲区管理 API 的一种实现。此外部相机 HAL 使用几百行 C++ 代码实现缓冲区管理策略中介绍的“最大限度地节省内存”策略。