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() 调用链分成三步:

  1. 设备查找:根据传入的 camera_id,找到对应的物理 Camera 设备
  2. 资源分配:分配 hw_device_t 结构体,填充 camera3_device_t 的函数指针
  3. 硬件初始化:打开 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 配置协商。

协商过程大致如下:

  1. 上层提需求:CameraService 传进来一个 camera3_stream_configuration_t,里面包含所有 Stream 的配置
  2. HAL 检查可行性:HAL 检查 sensor、ISP、MIPI 带宽等资源是否满足
  3. HAL 修改配置:如果某些 Stream 不满足,HAL 可以修改 stream->width/height/format
  4. 返回结果:返回 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 的配置。因为资源限制是硬约束,改不了;而分辨率、格式这些是可以协商的。

四、核心流程图:初始化全貌

说了这么多,我画了一张流程图,帮你把整个初始化流程串起来:

Camera3 HAL 初始化流程 阶段一:open() 调用链 camera_module->common.open() 查找设备 + 分配结构体 打开 sensor/ISP 驱动 输出:camera3_device_t(含 ops 函数指针) 阶段二:initialize() 初始化 注册 CameraService callback 初始化 Buffer 管理器 初始化状态机 输出:HAL 内部状态就绪,等待 Stream 配置 阶段三:Stream 配置协商 上层传入 Stream 列表 检查资源限制 协商分辨率/格式 输出:修改后的 Stream 配置 + ISP pipeline 配置 注意:只能修改 width/height/format/usage,不能修改 stream_type/max_buffers 三个阶段的输出逐级传递,最终完成 Camera 设备的完整初始化

这张图把整个流程分成了三个阶段。你仔细看,每个阶段的输出都是下一个阶段的输入。open() 产出了设备句柄,initialize() 初始化了内部状态,configure_streams() 最终确定了数据通路。

我的建议:调试初始化问题时,按照这个流程图逐级排查。先确认 open() 是否成功,再检查 initialize() 的 callback 是否注册正确,最后看 configure_streams() 的协商结果是否符合预期。我曾经用这个方法,半小时就定位了一个「预览黑屏」的问题——原来是 configure_streams() 里忘了配置 ISP 的 scaler 模块。

好了,Camera3 HAL 的初始化流程就讲到这里。这部分内容虽然看起来繁琐,但只要你理解了「open() 开门 → initialize() 装修 → configure_streams() 谈判」这个主线,后面再学 request 处理、buffer 管理就会轻松很多。

可可精品资料库