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 三个接口的协作流程
这三个接口的调用顺序是固定的:
- open() → 打开设备,分配资源
- initialize() → 注册回调,启动线程
- 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 是怎么被处理的。到时候再聊!