2、预览流程总览:从App到HAL的预览请求链路全景图

好,咱们正式开始聊预览流程。

说实话,我刚开始接触Camera HAL层时,最头疼的就是搞不清一条预览请求到底是怎么从App一路跑到HAL的。你想想看,用户点一下「预览」按钮,背后要经过多少层?Framework、Service、Provider、HAL……每一层都有自己的职责,稍不留神就掉坑里了。

这一节,我带你从高空俯瞰整条链路。先看全景,再拆细节。

2.1 整条链路长什么样?

一条预览请求,从App到HAL,大致经过这么几个节点:

  1. App层:调用 CameraManager.openCamera() 打开相机,然后调用 CameraDevice.createCaptureSession() 创建会话,最后调用 CameraCaptureSession.setRepeatingRequest() 发起预览请求。
  2. Framework层:App的请求被封装成 CaptureRequest,通过Binder跨进程发送到 CameraService
  3. CameraService:这是承上启下的关键节点。它把请求交给 CameraProvider,同时管理着底层的 CameraDevice 状态。
  4. CameraProvider:HAL的入口。它把Framework的请求翻译成HAL能理解的格式,然后调用HAL接口。
  5. HAL层:最终执行者。HAL收到请求后,配置ISP、Sensor,开始输出帧数据。

嗯,说白了就是一条「App → Framework → Service → Provider → HAL」的链条。每一层都在做一件事:把上一层的请求「翻译」成下一层能听懂的语言。

我个人习惯:调试预览问题时,先确认请求到底卡在了哪一层。比如App层报错,那多半是权限或配置问题;如果HAL层没收到请求,那就要查Binder通信或Service的状态了。

2.2 关键数据结构:CaptureRequest 的旅行

预览请求的核心,其实就是 CaptureRequest 这个对象。它从App一路传到HAL,沿途被不断「拆包」和「重组」。

我画个简化的流程图给你看:

App (CaptureRequest)
  ↓ Binder
CameraService (CaptureRequest)
  ↓ 内部转换
CameraProvider (CaptureRequest)
  ↓ HAL接口调用
HAL (camera3_capture_request_t)

注意看,到了HAL层,数据结构变成了 camera3_capture_request_t。这是HAL定义的C结构体,和Framework的Java对象完全不同。

为什么会这样?因为跨进程传输需要序列化,而HAL层为了性能,直接用C结构体操作内存。我在项目中遇到过一个问题:App设置了AE补偿,但HAL收到的值始终是0。查了半天,发现是Provider层在转换时漏掉了这个字段。嗯,这种坑很常见。

2.3 预览请求的「三要素」

一个预览请求,说白了就三样东西:

要素 说明 常见坑点
输出目标(Surface) 预览画面要显示到哪里?通常是SurfaceView或TextureView Surface格式不匹配,比如HAL要求NV12,App给了YV12
控制参数(Settings) AE、AWB、AF等参数,决定画面效果 参数未初始化,导致HAL使用默认值,画面偏色
模板类型(Template) 告诉HAL这是预览、拍照还是录像 模板类型传错,HAL可能用错ISP配置

你想想看,这三样东西只要有一个不对,预览就可能黑屏、卡顿或者画面异常。我曾经调试过一个案子:预览画面一直闪烁,最后发现是Surface的BufferQueue深度设置太小,导致丢帧。嗯,这种问题在HAL层很难定位,因为错误信息往往只出现在Framework层。

2.4 预览请求的「生命周期」

一条预览请求从发出到结束,大致经历这几个阶段:

  1. 请求提交:App调用 setRepeatingRequest(),请求进入Framework的请求队列。
  2. 请求分发:CameraService把请求分发给CameraProvider,Provider再调用HAL的 process_capture_request()
  3. 硬件执行:HAL配置Sensor和ISP,开始曝光、读出、处理,最终输出一帧图像。
  4. 结果回调:HAL通过 process_capture_result() 把结果返回给Framework,Framework再回调给App。

这里有个关键点:预览是「重复请求」,所以第1步到第4步会不断循环。HAL每处理完一帧,就会收到下一个请求。如果HAL处理速度跟不上Sensor的帧率,就会出现丢帧。

避坑指南:我曾经遇到一个项目,预览帧率只有15fps,但Sensor明明支持30fps。查到最后,发现是HAL的 process_capture_request() 里做了太多同步操作,导致处理时间过长。解决办法是把一些初始化操作移到 configure_streams() 里,提前准备好。

2.5 预览流程的「关键节点」

整条链路中,有几个节点特别容易出问题,我列出来给你参考:

  • Binder通信:App和Service之间的跨进程调用,如果Binder缓冲区满了,请求会阻塞。我建议在App层用异步方式提交请求,避免卡UI线程。
  • Stream配置:创建CaptureSession时,App会指定输出Stream的格式和尺寸。如果HAL不支持这个配置,就会报错。嗯,这个坑我踩过好几次,后来学乖了,先查 CameraCharacteristics 确认支持的配置。
  • HAL的process_capture_request:这是HAL层的核心函数。它必须尽快返回,不能阻塞。如果HAL需要做耗时操作,比如3A计算,应该另开线程处理。
  • 结果回调:HAL返回结果时,必须保证帧序号和请求序号一致。否则Framework会认为丢帧,触发异常处理。

2.6 小结:记住这张全景图

好了,预览流程的全景图就讲到这里。你记住一句话就行:

预览请求从App出发,经过Framework、Service、Provider,最终到达HAL。每一层都在做「翻译」和「调度」的工作。

下一节,我会带你逐行分析 setRepeatingRequest() 的代码,看看Framework层到底是怎么处理这个请求的。到时候你会发现,很多细节其实藏在Binder通信和请求队列里。

嗯,今天就到这儿。有问题随时问我。