第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层要做的:

  1. 验证每个Stream的参数是否合法
  2. 检查所有Stream组合是否支持
  3. 为每个Stream分配buffer(如果使用内部allocator)
  4. 配置底层硬件(Sensor、ISP、Video Encoder等)
  5. 返回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功底的地方。