4、Camera 管线初始化:open()、initialize()、configure_streams() 流程解析

好,咱们今天聊聊 Camera HAL 最核心的三个初始化接口。说实话,我见过太多开发者在 open() 和 configure_streams() 之间栽跟头。我自己也踩过不少坑,今天就把这些经验掰开揉碎讲给你听。

4.1 open():打开设备的第一步

open() 是 HAL 层第一个被调用的接口。说白了,就是 Framework 告诉 HAL:「嘿,我要用相机了,你准备一下。」

这个接口的签名很简单:

int camera_device_open(const hw_module_t* module, 
                       const char* name,
                       hw_device_t** device);

但里面要做的事可不少。我个人习惯在 open() 里做三件事:

  • 分配设备结构体:给 camera_device_t 分配内存,初始化回调函数指针
  • 初始化底层硬件:比如打开 sensor 驱动、初始化 ISP 等
  • 设置默认参数:把一些静态能力先准备好

嗯,这里要注意一个细节。open() 不能做耗时操作。为什么?因为 Framework 调用 open() 时是同步的,超过 5 秒没返回,系统就会报 ANR。我在项目中遇到过,有个厂商在 open() 里做了 sensor 校准,结果每次开机都要等 8 秒……后来被 CTS 测试卡得死死的。

⚠️ 避坑指南
我曾经在 open() 里初始化了太多东西,导致 CTS 测试中 camera 打开时间超标。建议只做必要的内存分配和轻量级初始化,把耗时的 sensor 配置放到 initialize() 或第一次 configure_streams() 时再做。

4.2 initialize():建立与 Framework 的通信桥梁

initialize() 是 open() 之后紧接着调用的。它的核心任务是注册回调函数:

int initialize(const struct camera3_device* device,
               const camera3_callback_ops_t* callback_ops);

这个 callback_ops 是什么?说白了,就是 Framework 给你的一本「电话本」。你以后想通知 Framework 什么事,比如拍照完成、出错、buffer 准备好了,都得通过这里面的函数指针来打电话。

我个人觉得,initialize() 里最容易被忽略的是:

  • 保存 callback_ops 指针:一定要保存到全局变量,后面所有回调都要用它
  • 初始化状态机:把设备状态设为 INITIALIZED
  • 创建处理线程:比如 request 处理线程、buffer 管理线程

你想想看,如果这里忘了保存 callback_ops,后面拍照完成时你想通知 Framework,结果发现没电话本……那画面太美我不敢看。

💡 小技巧
我习惯在 initialize() 里创建一个「错误监控线程」。这个线程定期检查 sensor 状态,如果发现硬件异常,就通过 callback_ops->notify() 上报 CAMERA3_MSG_ERROR_DEVICE。这样能提前发现问题,而不是等到拍照失败才报错。

4.3 configure_streams():真正的重头戏

configure_streams() 是整个初始化流程中最复杂的一环。Framework 会告诉你:「我要这些 stream,你给我配好。」

int configure_streams(const struct camera3_device* device,
                      camera3_stream_configuration_t* stream_list);

stream_list 里包含了一组 stream,每个 stream 有:

参数 说明 常见坑点
width/height 分辨率 必须是 sensor 支持的尺寸
format 像素格式 比如 HAL_PIXEL_FORMAT_YCbCr_420_888
usage 使用场景 GRALLOC_USAGE_HW_CAMERA_WRITE 等
max_buffers 最大 buffer 数 不能超过硬件限制

这里有个关键点:configure_streams() 可能会被多次调用。比如用户切换分辨率、开启 HDR 模式时,Framework 都会重新调用它。所以你的代码必须支持「重新配置」。

我记得有一次,某个厂商在 configure_streams() 里直接释放了所有 buffer,然后重新分配。结果在切换分辨率时,因为 buffer 释放和分配之间有短暂的空窗期,导致预览画面闪了一下黑屏。后来我建议他们用「双缓冲切换」策略——先分配新 buffer,再释放旧 buffer,完美解决。

4.4 三个接口的协作流程

这三个接口的调用顺序是固定的:

  1. open() → 打开设备,分配资源
  2. initialize() → 注册回调,启动线程
  3. configure_streams() → 配置数据流,分配 buffer

之后才能开始正常的拍照流程(process_capture_request)。

你可能会问:为什么不能把三个合并成一个?嗯,这是 Google 的设计哲学——职责分离。每个接口只做一件事,方便测试和调试。比如 CTS 测试可以单独测试 open() 的耗时,而不受 configure_streams() 的影响。

🔑 核心要点
  • open() 要快,别做耗时操作
  • initialize() 要保存 callback_ops,这是通信的桥梁
  • configure_streams() 要支持多次调用,做好状态切换
  • 三个接口的调用顺序不能乱,否则 CTS 直接报错

4.5 实战经验总结

最后分享几个我踩过的坑:

  • open() 返回后设备未就绪:有些 HAL 在 open() 里只分配了内存,但 sensor 还没初始化。结果 Framework 紧接着调用 configure_streams() 时,sensor 还没准备好,直接报错。我建议在 open() 里至少保证 sensor 处于「可查询状态」。
  • configure_streams() 参数校验不严:Framework 传过来的 stream 配置可能不合法,比如分辨率超出 sensor 支持范围。一定要做校验,返回 -EINVAL,而不是默默接受然后崩溃。
  • 多次 configure_streams() 的内存泄漏:每次调用都要释放之前的资源,否则内存会越涨越高。我见过一个案例,切换 10 次分辨率后,内存涨了 200MB……

好了,这一章就到这里。下一章我们会深入 process_capture_request(),看看 request 是怎么被处理的。到时候再聊!