3、打开相机设备:camera_device_open函数的调用栈与参数解析

好,咱们接着往下走。上一章我们把 HAL 的加载流程捋了一遍,知道了 HAL 模块是怎么被找到、怎么被打开的。但说实话,那只是「前戏」——真正的重头戏,是今天要讲的 camera_device_open 函数。

这个函数,说白了就是 HAL 层和底层硬件之间的「握手仪式」。你想想看,上层应用说「我要打开相机」,Framework 一路把请求传下来,最终到了 HAL 层,HAL 层得真正去初始化硬件、分配内存、建立数据通路。而这一切的起点,就是 camera_device_open

3.1 调用栈:从 Framework 到 HAL 的最后一公里

我们先看看这个函数是怎么被调到的。我个人习惯在分析这类问题时,先画一条调用链出来,这样心里有数。

// 调用栈(从上到下)
CameraService::connect()          // Framework 层
  → CameraClient::initialize()    // CameraService 内部
    → CameraHardwareInterface::open()  // HAL 接口封装
      → hw_module_t->methods->open()  // 标准 HAL 调用
        → camera_device_open()         // 你的 HAL 实现

嗯,这里要注意:hw_module_t->methods->open() 是 Android HAL 架构里定义好的标准接口。所有 HAL 模块都必须实现这个函数指针。而 camera_device_open,就是我们自己写的具体实现。

我在项目中遇到过一个问题:有个同事把 open 函数指针赋值错了,结果 Framework 调用时直接 crash。排查了半天才发现是函数签名不匹配。所以这里我建议你,写 HAL 时一定要仔细核对函数指针的类型定义。

3.2 函数签名:参数里藏着什么秘密?

好,我们直接看函数原型。这是 Android 官方定义的标准接口:

int camera_device_open(const struct hw_module_t* module,
                       const char* id,
                       struct hw_device_t** device);

三个参数,每个都有讲究。我们一个一个来看。

3.2.1 参数一:module

这个 module 指针指向的是我们之前加载的 HAL 模块结构体。它里面包含了模块的版本号、作者信息、以及最重要的——methods 函数表。

你可能会问:「为什么打开设备还要传模块指针?」

原因很简单:HAL 层需要知道当前是哪个模块在请求打开设备。同一个 SoC 上可能有多个摄像头 HAL 实现(比如主摄和广角用不同的驱动),通过 module 指针可以区分。

我记得有一次调试时,发现 module 指针传进来的是 NULL,结果一访问 module->methods 就段错误。后来查出来是上层在加载 HAL 时出了问题。所以,一定要做空指针检查

3.2.2 参数二:id

id 参数是个字符串,用来标识你要打开哪个摄像头。在 Android 系统中,通常 "0" 代表后置主摄,"1" 代表前置摄像头。

但这里有个坑:id 不一定只是数字字符串。有些厂商会自定义 ID,比如 "back_main""front_wide" 之类的。所以解析 id 时,我建议你用字符串比较,而不是简单的 atoi() 转换。

警告: 不要假设 id 一定是数字。我在一个项目中就遇到过厂商把 id 定义为 "rear_telephoto",结果用 atoi() 解析得到 0,导致打开了错误的摄像头。

3.2.3 参数三:device

这个参数是输出参数。函数执行成功后,*device 会指向一个初始化好的 hw_device_t 结构体。Framework 后续的所有操作(预览、拍照、参数设置)都是通过这个 device 指针来完成的。

说白了,device 就是 HAL 层和 Framework 之间的「通信管道」。管道建好了,后面才能传数据。

3.3 标准实现模板:照着写就对了

好,理论说完了,我们来看一个标准的实现模板。这是我个人比较喜欢的一种写法,清晰、健壮:

int camera_device_open(const struct hw_module_t* module,
                       const char* id,
                       struct hw_device_t** device)
{
    int rv = 0;
    camera_device_t* camera_dev = NULL;
    camera_hal_t* hal_priv = NULL;

    // 1. 参数检查
    if (module == NULL || id == NULL || device == NULL) {
        ALOGE("Invalid parameters");
        return -EINVAL;
    }

    // 2. 解析摄像头 ID
    int camera_id = atoi(id);
    if (camera_id < 0 || camera_id >= CAMERA_NUM) {
        ALOGE("Invalid camera id: %s", id);
        return -ENODEV;
    }

    // 3. 分配设备结构体内存
    camera_dev = (camera_device_t*)malloc(sizeof(camera_device_t));
    if (camera_dev == NULL) {
        ALOGE("Failed to allocate camera device");
        return -ENOMEM;
    }
    memset(camera_dev, 0, sizeof(camera_device_t));

    // 4. 分配私有数据内存
    hal_priv = (camera_hal_t*)malloc(sizeof(camera_hal_t));
    if (hal_priv == NULL) {
        ALOGE("Failed to allocate HAL private data");
        free(camera_dev);
        return -ENOMEM;
    }
    memset(hal_priv, 0, sizeof(camera_hal_t));

    // 5. 初始化设备结构体
    camera_dev->common.tag     = HARDWARE_DEVICE_TAG;
    camera_dev->common.version = CAMERA_DEVICE_API_VERSION_1_0;
    camera_dev->common.module  = (hw_module_t*)module;
    camera_dev->common.close   = camera_device_close;

    // 6. 设置操作函数表
    camera_dev->ops = &camera_ops;

    // 7. 初始化硬件(关键步骤)
    hal_priv->fd = open("/dev/video" CAMERA_DEV_NODE, O_RDWR);
    if (hal_priv->fd < 0) {
        ALOGE("Failed to open camera device node");
        free(hal_priv);
        free(camera_dev);
        return -ENODEV;
    }

    // 8. 建立关联
    hal_priv->camera_id = camera_id;
    camera_dev->priv = hal_priv;

    // 9. 输出结果
    *device = &camera_dev->common;

    ALOGI("Camera device opened successfully, id=%d", camera_id);
    return 0;
}
关键点: 第 7 步的硬件初始化是整个函数的核心。打开设备节点、初始化传感器、分配 DMA 缓冲区,这些操作都建议放在这里。我曾经见过有人把这些操作分散到各个地方,结果调试时根本搞不清初始化顺序。

3.4 避坑指南:我踩过的那些坑

做 HAL 开发这么多年,我在 camera_device_open 这个函数上栽过不少跟头。分享几个典型的:

  • 内存泄漏:分配了 camera_device_t 和私有数据,但中间某个步骤失败了,忘记释放之前分配的内存。我建议你用一个 goto 统一错误处理,或者像我上面那样,在每次失败时手动释放。
  • 设备节点权限:曾经有个项目,open("/dev/video0") 总是返回权限错误。查了半天,发现是 SELinux 策略没配好。嗯,这个坑在 CTS 测试时特别容易暴露。
  • 多线程并发:如果两个线程同时调用 camera_device_open 打开同一个摄像头,会发生什么?我建议你加个互斥锁,或者在函数入口处检查设备是否已经被打开。
小技巧:camera_device_open 函数里加一些调试日志,把关键参数和返回值都打印出来。这样后续排查问题时,直接看 logcat 就能定位问题。我个人习惯在函数入口和出口各打一条日志,中间的关键步骤也打上。

3.5 总结

好了,这一章的内容就到这里。我们梳理了 camera_device_open 的调用栈,解析了三个参数的含义,还看了一个标准的实现模板。

下一章,我们会深入 camera_device_open 内部,看看硬件初始化到底做了什么——从打开设备节点到配置传感器,每一步都有讲究。

记住一句话:HAL 层的代码,90% 的问题都出在初始化阶段。把 camera_device_open 写好了,后面的路就好走了。