4、Camera设备打开:camera_device_t结构体、open_device流程、设备状态机管理、多摄像头支持

好,咱们接着聊。上一节我们把HAL模块加载进来了,但模块加载只是第一步。真正让相机跑起来,得靠打开设备。这就像你插上U盘,系统识别到了硬件,但你还得双击盘符才能看到里面的文件。今天我就带你走一遍这个「双击盘符」的过程。

4.1 camera_device_t:设备操作的「遥控器」

先说说这个结构体。camera_device_t,说白了就是HAL层暴露给上层的一个操作句柄。上层拿着它,就能指挥相机干活了。

typedef struct camera_device {
    hw_device_t common;
    camera_device_ops_t *ops;
    void *priv;
} camera_device_t;

这里三个字段,我一个个说:

  • common:继承自hw_device_t,包含了设备ID、版本号、模块句柄等基础信息。这是Android硬件抽象层的通用套路。
  • ops:操作函数表。上层调用的所有API,比如start_preview、take_picture,最终都映射到这里。
  • priv:私有数据指针。我习惯在这里挂一个自定义的上下文结构体,里面放设备状态、流信息、传感器句柄等。你想想看,如果不用priv,这些数据放哪?全局变量?那多摄像头就乱套了。

ops这个函数表,我列几个关键接口:

接口说明
set_preview_window设置预览窗口,通常是一个ANativeWindow
start_preview启动预览流
stop_preview停止预览
take_picture拍照,返回JPEG数据
set_callbacks注册回调,比如帧到达、错误通知
dump调试用,导出设备内部状态

个人经验:我在项目中遇到过,某个厂商的ops实现里,set_preview_window和start_preview的顺序搞反了。上层先调start_preview再调set_preview_window,结果预览黑屏。排查了两天才发现是HAL层没做容错。所以我的建议是:在start_preview里检查窗口是否已设置,没设置就缓存起来,等窗口来了再启动。

4.2 open_device流程:从模块到设备的「最后一公里」

模块加载完成后,上层会调用hw_module_t->methods->open()来打开设备。这个流程,我拆成几步来讲:

  1. 参数校验:检查传入的device_id是否合法。多摄像头场景下,这个id可能是"0"、"1"或者"back"、"front"这样的字符串。
  2. 分配camera_device_t:malloc一个camera_device_t结构体,初始化common字段,把ops指向你的实现函数表。
  3. 初始化硬件:打开传感器驱动、初始化ISP、分配内存池。这一步如果失败,记得回滚前面分配的资源。
  4. 状态机初始化:把设备状态设为CAMERA_DEVICE_STATE_CLOSED或OPENED,看你的设计。
  5. 返回设备句柄:把填好的camera_device_t通过参数返回给上层。
static int camera_device_open(const struct hw_module_t* module,
                               const char* id,
                               struct hw_device_t** device) {
    // 1. 参数校验
    if (!module || !id || !device) return -EINVAL;
    int camera_id = atoi(id);
    if (camera_id < 0 || camera_id >= MAX_CAMERAS) return -ENODEV;

    // 2. 分配设备结构体
    camera_device_t *dev = malloc(sizeof(camera_device_t));
    memset(dev, 0, sizeof(*dev));

    // 3. 初始化common
    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = 1;
    dev->common.module = (hw_module_t*)module;
    dev->common.close = camera_device_close;

    // 4. 挂载ops
    dev->ops = &g_camera_device_ops;

    // 5. 初始化私有数据
    dev->priv = init_camera_priv(camera_id);
    if (!dev->priv) {
        free(dev);
        return -ENOMEM;
    }

    // 6. 状态机初始化
    set_device_state(dev->priv, CAMERA_DEVICE_STATE_OPENED);

    *device = &dev->common;
    return 0;
}

注意:open_device里不要做耗时操作。我曾经见过一个实现,在open里做了传感器校准,耗时3秒,结果上层超时直接杀进程。正确的做法是:open只做轻量初始化,重活放到start_preview里做。

4.3 设备状态机管理:别让相机「精神分裂」

相机设备是有状态的。你不能在预览中再去open,也不能在拍照时突然close。状态机就是用来管这些的。

我常用的状态定义:

  • CLOSED:设备未打开,或者已关闭。
  • OPENED:设备已打开,但未开始预览。
  • PREVIEW_STARTED:预览已启动。
  • CAPTURE_IN_PROGRESS:正在拍照或录像。
  • ERROR:设备出错,需要重启。

状态转换规则:

CLOSED --open()--> OPENED
OPENED --start_preview()--> PREVIEW_STARTED
PREVIEW_STARTED --stop_preview()--> OPENED
OPENED --close()--> CLOSED
PREVIEW_STARTED --take_picture()--> CAPTURE_IN_PROGRESS
CAPTURE_IN_PROGRESS --capture_done()--> PREVIEW_STARTED
任何状态 --error()--> ERROR
ERROR --reset()--> CLOSED

避坑指南:我曾经在状态机里漏掉了ERROR到CLOSED的转换。结果设备出错后,上层调用close(),我检查状态不是OPENED也不是PREVIEW_STARTED,直接返回了错误。上层就再也关不掉这个设备了。嗯,后来我加了一个「任何状态都能close」的兜底逻辑。

实现上,我建议用枚举+switch-case,别用if-else堆。代码可读性差,还容易漏状态。

typedef enum {
    CAMERA_DEVICE_STATE_CLOSED,
    CAMERA_DEVICE_STATE_OPENED,
    CAMERA_DEVICE_STATE_PREVIEW_STARTED,
    CAMERA_DEVICE_STATE_CAPTURE_IN_PROGRESS,
    CAMERA_DEVICE_STATE_ERROR,
} camera_device_state_t;

static int check_state_transition(camera_device_state_t current,
                                   camera_device_state_t target) {
    switch (current) {
        case CAMERA_DEVICE_STATE_CLOSED:
            return (target == CAMERA_DEVICE_STATE_OPENED) ? 0 : -EPERM;
        case CAMERA_DEVICE_STATE_OPENED:
            return (target == CAMERA_DEVICE_STATE_PREVIEW_STARTED ||
                    target == CAMERA_DEVICE_STATE_CLOSED) ? 0 : -EPERM;
        // ... 其他状态
        default:
            return -EINVAL;
    }
}

4.4 多摄像头支持:一个模块,多个设备

现在的手机,哪个不是三摄四摄?多摄像头支持,是HAL层必须面对的问题。

核心思路其实很简单:一个camera_module_t对应多个camera_device_t。每个设备有自己的id、自己的状态机、自己的私有数据。

具体实现上,有几点要注意:

  • 设备ID管理:用数组或链表维护所有设备的信息。我习惯用静态数组,因为摄像头数量是固定的。
  • 资源隔离:每个设备有自己的内存池、ISP通道、传感器控制。别让设备A的预览影响了设备B的拍照。
  • 同步问题:多摄像头同时操作时,可能会有资源竞争。比如两个摄像头共用同一个I2C总线,就需要加锁。
  • 热插拔:USB摄像头场景下,设备可能动态插入拔出。这时候需要动态管理设备列表。
#define MAX_CAMERAS 4

static camera_device_t *g_cameras[MAX_CAMERAS];
static pthread_mutex_t g_camera_lock = PTHREAD_MUTEX_INITIALIZER;

int camera_device_open(const struct hw_module_t* module,
                        const char* id,
                        struct hw_device_t** device) {
    pthread_mutex_lock(&g_camera_lock);
    int camera_id = atoi(id);
    if (g_cameras[camera_id] != NULL) {
        // 设备已打开,返回错误
        pthread_mutex_unlock(&g_camera_lock);
        return -EBUSY;
    }
    // ... 分配和初始化
    g_cameras[camera_id] = dev;
    pthread_mutex_unlock(&g_camera_lock);
    return 0;
}

个人经验:我在做双摄项目时,发现两个摄像头同时start_preview,第二个总是失败。查了半天,原来是ISP只有一个DMA通道,被第一个占用了。解决方案是:在open时分配好资源,如果资源不够,直接返回失败,别等到start_preview才报错。

好了,这一章的内容就这些。camera_device_t是操作入口,open_device是初始化流程,状态机保证操作有序,多摄像头支持则是现代手机的刚需。下一章,我们会深入预览流程,看看数据是怎么从传感器一路跑到屏幕上的。