第2章:Stream配置核心流程——从open()到configure_streams()的调用链路
好,咱们直接进入正题。这一章我带你走一遍Stream配置的完整流程。说白了,就是从HAL层打开Camera设备,到最终把Stream配置下去的这条调用链。我在项目中见过不少新手,上来就对着configure_streams()猛写,结果前面open()没处理好,后面全崩了。
2.1 调用链路全景图
先给你个整体印象。这条链路大概是这样的:
CameraService --> HAL open() --> initialize() --> configure_streams()
↑ ↑ ↑
Framework层 HAL设备打开 Stream配置下发
嗯,看着简单吧?但每个环节都有坑。我一个个拆开讲。
2.2 open()——设备打开的"第一道门"
Framework调用camera_module_t->common.open(),传入设备ID和hw_device_t指针。HAL层要在这里做几件事:
- 分配camera_device结构体内存
- 初始化内部状态机
- 填充ops函数指针表
- 打开底层驱动节点(比如V4L2设备)
关键点:open()里不要做耗时操作。我见过有人在这里做Sensor初始化,结果超时被Framework杀掉。驱动打开可以,但Sensor流控放到后面。
代码示例:
int my_camera_open(const struct hw_module_t* module,
const char* id,
struct hw_device_t** device) {
// 分配结构体
my_camera_device_t* dev = malloc(sizeof(*dev));
memset(dev, 0, sizeof(*dev));
// 填充ops
dev->device.common.tag = HARDWARE_DEVICE_TAG;
dev->device.common.version = CAMERA_DEVICE_API_VERSION_3_4;
dev->device.ops = &my_camera_device_ops;
// 打开驱动节点
dev->fd = open("/dev/video0", O_RDWR);
if (dev->fd < 0) {
// 我曾经在这里吃过亏——忘记释放已分配的内存
free(dev);
return -ENODEV;
}
*device = &dev->device.common;
return 0;
}
注意:open()返回后,Framework会立即调用initialize()。如果你在open()里开了驱动但没配好,initialize()里可能会出问题。我建议open()只做最基础的事情。
2.3 initialize()——状态机的"初始化"
Framework调用camera3_device_ops->initialize(),传入callback函数指针。这里HAL要做:
- 保存callback指针(用于上报事件)
- 初始化Stream相关的内部数据结构
- 设置初始的Camera特性(比如AE、AF的默认参数)
我个人习惯在这里把Stream的buffer管理池先建好。虽然还没配置具体的Stream,但内存池的框架可以先搭起来。你想想看,等到configure_streams()再建,时间上可能来不及。
2.4 configure_streams()——真正的"重头戏"
这是整个流程的核心。Framework会传进来一个camera3_stream_configuration_t结构体,里面包含了所有要配置的Stream信息。
结构体长这样:
typedef struct camera3_stream_configuration {
uint32_t num_streams; // Stream数量
camera3_stream_t** streams; // Stream指针数组
uint32_t operation_mode; // 操作模式
const camera3_stream_buffer_set_t* buffer_handles; // 可选
} camera3_stream_configuration_t;
HAL层要做的:
- 验证每个Stream的参数是否合法
- 检查所有Stream组合是否支持
- 为每个Stream分配buffer(如果使用内部allocator)
- 配置底层硬件(Sensor、ISP、Video Encoder等)
- 返回0表示成功,或者返回错误码
我的经验:在configure_streams()里,一定要做"组合检查"。比如你支持1080p预览+480p拍照,但不支持4K预览+1080p拍照。这些限制要在这里返回-ENOSYS或-EINVAL。我曾经因为没检查组合,导致上层配置了不支持的组合,结果底层直接挂掉。
2.5 Stream配置参数详解
每个Stream由camera3_stream_t描述。关键参数如下:
| 参数名 | 类型 | 说明 | 我的建议 |
|---|---|---|---|
| stream_type | int | CAMERA3_STREAM_OUTPUT / INPUT / BIDIRECTIONAL | 大部分场景用OUTPUT |
| width / height | uint32_t | 图像分辨率 | 注意对齐到16或32 |
| format | int | 像素格式(HAL_PIXEL_FORMAT_xxx) | YUV420最通用 |
| usage | uint32_t | GRALLOC_USAGE_xxx 标志位 | GPU/Display/HWVideo不同 |
| max_buffers | uint32_t | HAL建议的buffer数量 | 一般3-4个够用 |
| priv | void* | HAL私有数据 | 可以用来存Stream上下文 |
这里有个容易忽略的点——usage字段。Framework会根据Stream的用途设置不同的usage flag。比如预览Stream会带GRALLOC_USAGE_HW_COMPOSER,拍照Stream可能带GRALLOC_USAGE_SW_READ_OFTEN。你分配buffer时要根据usage选择合适的heap。
避坑指南:我曾经遇到一个case,Framework传进来的format是HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED。这个format的意思是"你HAL自己看着办"。我当时没处理,直接返回了-ENOSYS。后来才知道,这种情况下HAL要选择一个最合适的实际格式,并在stream->format里写回去。
2.6 调用链中的常见问题
我总结几个实际项目中踩过的坑:
- open()重复调用:Framework可能多次open/close同一个设备。HAL要做好引用计数。
- configure_streams()被多次调用:比如切换分辨率时。HAL要能正确处理"重新配置"而不是"追加配置"。
- Stream数量限制:有些硬件只支持2个Stream同时工作。configure_streams()里要检查num_streams。
- Buffer Handle传递:如果Framework传了buffer_handles,HAL要使用这些外部buffer,而不是自己分配。
嗯,这一章的内容差不多就这些。核心就是记住:open()做基础初始化,initialize()搭框架,configure_streams()做真正的配置。每一步都有它的职责,别越界。
下一章我会讲Stream的buffer管理——怎么分配、怎么流转、怎么释放。那才是真正考验HAL功底的地方。