从 Android10 开始,camera 系统加入了一个可选地 buffer 管理方式,可以在 Vendor HAL 这边灵活使用这个选项进行 buffer 管理,以此达到减少 buffer 使用峰值,改变 request 执行速度等优点。具体的来说就是对于 HAL request queue 中的每一个 request 来讲,并不是每一个 request 的每一个 buffer 都是被使用到的,有些 request 可能就没有用到 buffer。说到这里想起关于 buffer 使用量的几个问题,一个是 buffer 使用总量,一个是 buffer 使用峰值。

Buffer 使用总量顾名思义就是一个系统中最多可能用到的 buffer 数量,这个是系统设计之初就已经定好的,申请 buffer pool 的时候应该申请等量的 buffer 空间,不然就可能会造成处理错误。Buffer 使用峰值就是某一时刻整个系统同时在用的 buffer 最大值,这个值是小于等于 Buffer 使用总量这个值的。那我们要优化一个系统就需要同时减少两者的值,使用总量减少很能够理解,可以节省空间,但是峰值为什么要减少呢?如果某一时刻系统处理达到了峰值,那么就可能会阻塞接下来的处理请求,因为要等待空闲出来的 buffer 嘛,想象下如果我们是一个流式处理系统,如果总是一开始就全部申请设置好 buffer,那就会导致大量的 buffer 在大部分时间状况下没有用到,这浪费了很多的等待时间,所以也是为什么要减少峰值的原因。

对于 Google Android 的文档介绍,举了一个例子:假设说 HAL 可能有 8 个 request 正在队列里面等待处理,但是只在最后两个 request 才需要申请 buffer。在 Android 9 和 9 以下的版本,这 8 个 request 的 buffer 都是提前分配好的,所以这个时候有 6 个 request 的 buffer 都是没用使用的,Android 10 往后的版本就支持把 buffer 使用和 request 分离开,不用每次都提前绑定,而是用到了才进行绑定。这在高端平台可以节省很多 buffer 使用,并且对于低内存的平台也是很有帮助的,至于为什么会有帮助,就是上面说的峰值这块,减少峰值一定程度上也是可以减少最大值的。

下面两个图是从官网搞过来的:

可以看到第二个图里面的 buffer 是实际使用到的时候才进行配对处理的,这样可以节省很多 buffer 的使用,甚至对于每次申请不同类型 buffer 的 request 来说也可以针对 buffer 类型的不同来申请,因为同一个 request 里面的 buffer 是有一定可能并非全部都用得上的,有些用不上的就可以不去申请空间了。

实现 buffer management

对于 HAL 层来讲需要实现下面几个点:

  1. 实现 HIDL 接口,ICameraDevice@3.5,这部分其实是 Android HAL 层的部分,Vendor HAL 不用管这里先。
  2. android.info.supportedBufferManagementVersion 的值改为 HIDL_DEVICE_3_5。这个值在 /platform/hardware/interfaces/camera/metadata/3.4/types.hal 里面。

Vendor HAl 可以使用的接口有:requestStreamBuffers, returnStreamBuffers 两个分别用于请求、释放 buffer,另外还需要 signalStreamFlush 接口,这个接口是在 Vendor HAL 那边实现的,前面两个接口都是 Android HAL 实现好了给到 Vendor HAL 那边进行回调的,最后一个是 framework 往 Vendor HAL 调用的,用于返还全部 Vendor HAL 持有的 buffer,这个调用明显就是要在 configure_stream 或者 flush 前后进行的,用于一次性回收全部的 buffer。

requestStreamBuffers

这个是由 Vendor HAL 主动调用的,其函数实体是在 Android HAL 那边实现的,会在 device 初始化的时候作为 camera3_callback_ops_t 类型结构体成员传递 Vendor HAL,通常情况下这个需要在 process_capture_request 的时候进行调用,用以申请相关所需要的 buffer。如果设置了使用 Android HAL 的 buffer manager,在下发 request 的时候是不会带 buffer 的,这个时候必须得使用该接口去获取 buffer,否则会造成致命错误。

该函数可以一次请求多个 stream 类型的多个 buffer,该函数的参数主要包含了一对输入,一对输出,其中关键结构体如下所示:

struct camera3_buffer_request {    camera3_stream_t *stream;    uint32_t num_buffers_requested;}struct camera3_stream_buffer_ret {    camera3_stream_t *stream;    camera3_stream_buffer_req_status_t status;    uint32_t num_output_buffers;    camera3_stream_buffer_t *output_buffers;}
  1. 输入:需请求的 camera2_buffer_request 数量,以及存放该结构体信息的数组。
  2. 输出:返回的 camera3_stream_buffer_ret 数量,以及存放该结构体信息的数组。

输入输出基本上都是一个套路,就是首先区分 stream,不同的 stream buffer 归拢到一块,设置其数量,然后把不同 stream 再放在一个结构体里面排排队作为数组传递给 Android HAL。需要注意的是返回的信息里面有一个 camera3_stream_buffer_req_status_t 信息,这个是表明本次 buffer 申请成功与否的标志,大致上包含以下几个方面:

  1. OK:不用说看长相就是很成功了。
  2. FAILED_PARTIAL:这个就要 check 下每一个返回的 camera3_stream_buffer_ret 结构体了,说明是有的成功有的失败了,但是这个不是细化到每一个 camera3_stream_buffer_t 层级的,一个 stream 类型失败那就说明这个类型下面全部的 buffer 都不可用。
  3. FAILED_CONFIGURING:没有任何一个 buffer 返回,一个都没有。据注解是说这个时候可能 Android HAL 那边正在进行或者将要进行 configure_streams 操作了,Vendor HAL 需要等到最近一次 configure_streams 操作完成之后再去请求。
  4. FAILED_ILLEGAL_ARGUMENTS:是说明我们输入的参数不对,比如根本找不到输入的 stream 类型,这个时候也是一个 buffer 返回都没有的,我们请求的 stream 必须是 configure_streams 的时候传递下来的 stream,不能是其它的。
  5. FAILED_UNKNOW:不知名错误,或者每一个 stream 的错误类型都不一样,这种错误说明也是一个 buffer 都没回来,需要检查每一个 stream 对应的错误情况。

由于该函数请求在 camera service 那边是串行化的,也就是会 block 多个的请求,并且请求的 buffer 越多就越可能会导致更长的等待时间,综合来讲,需要 Vendor HAL 以及 Android HAL 那边使用同一个高优先级的线程进行该调用。对于输出参数而言,Vendor HAL 需要申请好预先设定的空间,然后交给 Android HAL 去进行数据填充。

该请求可能会出现一些非致命的问题,调用者也需要能够适当进行处理,下面的错误都是整个函数的返回值决定的,返回值类型是 camera3_buffer_request_status_t

  1. App disconnects from the output stream:这个是非致命的错误,如果出现该种情况,Vendor HAL 需要返回一个 ERROR_REQUEST 错误,只要该 request 包含有 disconnect stream,那就得返回一个这样的错误值。
  2. Timeout:这个可能是因为 APP 正忙着做别的事情,所以也要返回一个 ERROR_REQUEST 错误,但是这个并不影响后续的处理流程,只在当前 request 返回一个错误即可,后续的还按照正常的步骤进行处理。
  3. Camera framework is preparing a new stream configuration:这个就需要先等待 configure_streams 跑完之后再进行 buffer 申请。
  4. The camera HAL has reached its buffer limit (the maxBuffers field):说明 buffer 用量达到最大值了,这个时候应该等着,知道有新的 buffer 可用再去申请。关于怎么知道可用这个问题,一般我们返回 buffer 是用 process_capture_result 返回的,所以可以在每一个 process_capture_result 调用之后唤醒重新请求 buffer。

returnStreamBuffers

该接口可以返回 buffer 给到 framework,正常情况下来讲,都是通过 process_capture_result 函数来进行 buffer 返回的,但是有些时候 Vendor HAL 可能请求了多于实际用到的 buffer 数量,这个时候正常返回接口就没有办法返回全部的 buffer,只能通过该接口进行剩余 buffer 的返还操作。如果 Vendor HAL 在实现上就不会 hold 多余 buffer,那么本接口就可以忽略掉。

signalStreamFlush

这个有一点类似于 returnStreamBuffers,只不过本接口表明 framework 想要拿回所有的 buffer,此时一般都是在 configure_streams 前后,当然如果 Vendor HAL 也还是从不拿多余的 buffer,那么该接口也可以不用实现,就置为空就可以了。当 framework 调用该接口的时候,就不会再继续下发 request 了,并且等到所有的 buffer 返回到 framework 之后,Vendor HAL request buffer 也是会失败的。

本接口与 flush 有所区别,flush 是需要给所有 pending 的 request 返回一个 ERROR_REQUEST 的错误,而本接口是需要正常完成所有的 request 的。并且有一个很重要的一点,就是本接口它是相对独立的一个 HIDL 接口,调用的时候可能本接口先调用,但是 configure_streams 调用先被执行,如果没有任何同步装置的话,就会造成执行错乱继而引起程序崩溃,一个方法是 framework 在本接口的参数里面加入了一个 streamConfigCounter 参数,Vendor HAL 需要判断这个参数来看是否是在 configure_streams 调用之后的时间调用的,看下图示例:

行为变化

使用 buffer management API 实现 buffer 管理逻辑,需要考虑下以下的行为改变:

  • Capture request:framework 会更快的发 request 到 camera Vendor HAL 那边,如果没有 buffer management,framework 由于不需要绑定 buffer 到 request 那边,所以下发 request 的速度会更快。并且没有 buffer management 的话,framework 在达到 buffer 最大数量的时候会停止下发 request 到 Vendor HAL 那边,但是有了 buffer management,这个机制就不存在了,也就是说 framework 会持续不断地下发 request,当然 Vendor HAL 那边一定要在 request Queue 达到 Vendor HAL 的最大值的时候拒绝接受新的下发 request。
  • requestStreamBuffers 调用延迟:在以下几种情况下可能会发生调用被阻塞延迟:
    • 对于第一次创建 buffer 的 stream 来说,需要更长时间来分配 buffer 空间。
    • 申请越多的 buffer 就会导致越长的时间延迟。
    • APP 正在忙于其它事情,这个时候可能会造成 buffer request 速度变慢,并且有一定可能触发 timeout。

Buffer management 策略

Buffer management API 允许不同的策略实现,下面就是一些例子:

  • 后向兼容:Vendor HAL 在 process_capture_request 的时候就去申请 buffer,这样做并不会节省 buffer 使用,但是可以作为一个初步的实现,这部分只需要更改很少量的代码即可。
  • 最大化节省内存:只有在真正用到 buffer 时才去申请,这个要结合 Vendor HAL 内部 pipeline 的实现去整合,这部分是需要一个系统性的改动的,我立刻就能想到两种解决方向,一种是在 request 的时候就请求多个 buffer 作为缓冲池,然后内部 pipeline 在用到的时候去缓冲池里面取 buffer,这个重点是如何控制缓冲池 buffer 数量。第二种就是用到了的时候去 framework 取,这种需要一个贯穿 Vendor HAL 底层到 framework 的调用来实现才可以。
  • 缓存 buffer:Vendor HAL 可以 cache 一些 buffer 以避免用到的时候才去取用会导致的延迟问题(这里也就凸显了 signalStreamFlush 的作用)。

End

这个一定程度上来讲是个挺有用的 feature,应该是慢慢标准化并且支持进去,现在我看还是可选的一个选项,并且是有一部分示例代码,还不是标准化流程代码。说起 buffer 管理这玩意儿,我觉得对于视频流产品模块来讲,有 70% 以上的代码都是在实现 buffer 的流转、管理,掌握一个视频流模块框架,一部分程度上等同于掌握其 buffer 管理的设计与实现。


更多相关文章

  1. 浅析Android.jar文件中的包
  2. Android个层次调用流程概述
  3. Android中Intent中如何传递对象
  4. Android(安卓)反编译apk文件得到项目文件
  5. android访问NFC的SE
  6. Android核心分析 ----- Android电话系统之RIL-Java
  7. Android上实现MVP模式的途径
  8. Android(安卓)开发之Android(安卓)应用程序如何调用支付宝接口
  9. android之延时操作

随机推荐

  1. android 内嵌字体样式
  2. Android:实现一个带动画轮播效果的公告条
  3. Android之Handler有感(一)
  4. Android(安卓)studio配置NDK开发环境
  5. Android(安卓)Studio | 一个优雅的安卓开
  6. Android应用框架之数据库框架Room简介
  7. Android(安卓)手机玩转技巧
  8. Android独立交叉编译环境搭建
  9. Android(安卓)CTS Verifier Sensor Test
  10. 浅谈一下关于android碎片化的问题