Camera3 HAL 初始化流程:从 open() 到 Stream 配置协商
大家好,我是负责 Camera HAL 架构的老工程师。今天我们来聊聊 Camera3 HAL 的初始化流程。这部分内容,说白了就是整个 Camera 框架的「地基」。地基没打好,上层再花哨的功能也跑不起来。
我记得刚接手瑞芯微平台时,第一次看 HAL 初始化代码,差点被绕晕。后来慢慢理清了脉络,才发现其实就三个核心阶段:open() 调用链、initialize() 初始化、Stream 配置协商。今天我就把这套流程掰开揉碎了讲给你听。
一、open() 调用链:Camera 设备的「开门」过程
当上层 CameraService 调用 camera_module_t->common.open() 时,HAL 层就开始干活了。这个 open() 函数,说白了就是给 Camera 设备「开门」。
我习惯把 open() 调用链分成三步:
- 设备查找:根据传入的 camera_id,找到对应的物理 Camera 设备
- 资源分配:分配 hw_device_t 结构体,填充 camera3_device_t 的函数指针
- 硬件初始化:打开 sensor、ISP 等硬件驱动节点
核心要点:open() 返回的 camera3_device_t 结构体,包含了后续所有操作的函数指针。比如 initialize()、configure_streams()、process_capture_request() 等。这个结构体就是 CameraService 和 HAL 之间的「通信协议」。
来看一段典型的 open() 实现代码:
static int camera_device_open(const struct hw_module_t* module,
const char* name,
struct hw_device_t** device) {
// 1. 根据 camera_id 找到设备信息
int camera_id = atoi(name);
rk_camera_device_t* rk_dev = get_rk_camera_device(camera_id);
// 2. 分配 camera3_device_t 结构体
camera3_device_t* cam_dev = malloc(sizeof(camera3_device_t));
memset(cam_dev, 0, sizeof(*cam_dev));
// 3. 填充函数指针
cam_dev->ops = &camera3_device_ops;
cam_dev->priv = rk_dev;
// 4. 打开硬件驱动
rk_dev->sensor_fd = open_sensor(camera_id);
rk_dev->isp_fd = open_isp(camera_id);
*device = (struct hw_device_t*)cam_dev;
return 0;
}
嗯,这里要注意:open() 阶段不要做太重的初始化工作。我见过有些同事在 open() 里就把 ISP 全部配好了,结果发现上层根本不需要那么高的分辨率,白白浪费了时间。open() 只做「开门」的事,具体「装修」留给 initialize() 去做。
二、initialize() 中做了什么:真正的「装修」阶段
open() 之后,CameraService 会调用 camera3_device_t->ops->initialize()。这个函数才是真正干活的。
initialize() 的核心任务就两个:
- 注册回调:把 CameraService 的 callback 接口传给 HAL
- 初始化内部状态机:准备好处理后续的 Stream 配置和 Capture 请求
我个人习惯在 initialize() 里做以下几件事:
| 初始化项 | 说明 | 避坑指南 |
|---|---|---|
| callback 注册 | 保存 CameraService 传进来的 camera3_callback_ops_t | 一定要做深拷贝,防止野指针 |
| Buffer 管理器初始化 | 创建 gralloc 分配器,准备 Buffer 池 | 我曾经在这里忘了初始化,导致后面 allocate 一直失败 |
| Pipeline 状态机 | 初始化各个 pipeline 的状态:IDLE、CONFIGURED、ACTIVE | 状态机一定要有超时保护,防止死锁 |
| Sensor 能力查询 | 读取 sensor 的 supported resolution、fps 范围等 | 建议缓存起来,不要每次都去读 I2C |
我的经验:initialize() 里还有一个容易被忽略的点——线程池初始化。Camera3 HAL 是异步架构,需要多个线程来处理 request、返回 result。我建议在 initialize() 里就把线程池建好,避免后续动态创建线程带来的延迟。
三、Stream 配置协商过程:Camera 的「谈判桌」
initialize() 完成后,CameraService 会调用 configure_streams()。这是整个初始化流程中最复杂、也最容易出问题的地方。
你想想看,上层想要 4K 预览 + 1080p 录像 + 2M 拍照,但 sensor 可能只支持 4K 和 1080p,ISP 可能对某些分辨率有限制。这时候就需要「谈判」——也就是 Stream 配置协商。
协商过程大致如下:
- 上层提需求:CameraService 传进来一个 camera3_stream_configuration_t,里面包含所有 Stream 的配置
- HAL 检查可行性:HAL 检查 sensor、ISP、MIPI 带宽等资源是否满足
- HAL 修改配置:如果某些 Stream 不满足,HAL 可以修改 stream->width/height/format
- 返回结果:返回 0 表示接受,返回 -EINVAL 表示无法满足
注意:HAL 只能修改 Stream 的 width、height、format 和 usage 字段。不能修改 stream->stream_type(CAMERA3_STREAM_OUTPUT / INPUT)和 stream->max_buffers。这是 Camera3 协议规定的。
我曾经在一个项目里遇到过一个坑:上层要求 4 个 Stream,但我们的 ISP 只能同时处理 3 个。当时我直接在 configure_streams() 里返回 -EINVAL,结果上层直接崩溃了。后来我查了文档才发现,正确的做法是:能支持几个就配置几个,不支持的 Stream 通过修改 width=0 来告诉上层「我不支持」。
来看一个典型的协商逻辑:
static int configure_streams(
const struct camera3_device *device,
camera3_stream_configuration_t *stream_list) {
rk_camera_device_t* rk_dev = (rk_camera_device_t*)device->priv;
// 1. 检查 Stream 数量
if (stream_list->num_streams > MAX_STREAMS) {
// 修改不支持的 Stream 为 0
for (int i = MAX_STREAMS; i < stream_list->num_streams; i++) {
stream_list->streams[i]->width = 0;
stream_list->streams[i]->height = 0;
}
return 0;
}
// 2. 逐个检查 Stream 配置
for (int i = 0; i < stream_list->num_streams; i++) {
camera3_stream_t* stream = stream_list->streams[i];
// 检查分辨率是否支持
if (!is_resolution_supported(stream->width, stream->height)) {
// 协商:找一个最接近的支持分辨率
find_nearest_supported_resolution(
&stream->width, &stream->height);
}
// 检查 format 是否支持
if (!is_format_supported(stream->format)) {
stream->format = HAL_PIXEL_FORMAT_YCbCr_420_888;
}
}
// 3. 配置 ISP pipeline
rk_dev->isp_config = build_isp_config(stream_list);
return 0;
}
嗯,这里有个细节:协商的顺序很重要。我建议先检查资源限制(带宽、ISP 通道数),再检查单个 Stream 的配置。因为资源限制是硬约束,改不了;而分辨率、格式这些是可以协商的。
四、核心流程图:初始化全貌
说了这么多,我画了一张流程图,帮你把整个初始化流程串起来:
这张图把整个流程分成了三个阶段。你仔细看,每个阶段的输出都是下一个阶段的输入。open() 产出了设备句柄,initialize() 初始化了内部状态,configure_streams() 最终确定了数据通路。
我的建议:调试初始化问题时,按照这个流程图逐级排查。先确认 open() 是否成功,再检查 initialize() 的 callback 是否注册正确,最后看 configure_streams() 的协商结果是否符合预期。我曾经用这个方法,半小时就定位了一个「预览黑屏」的问题——原来是 configure_streams() 里忘了配置 ISP 的 scaler 模块。
好了,Camera3 HAL 的初始化流程就讲到这里。这部分内容虽然看起来繁琐,但只要你理解了「open() 开门 → initialize() 装修 → configure_streams() 谈判」这个主线,后面再学 request 处理、buffer 管理就会轻松很多。
可可精品资料库