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() 转换。
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;
}
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 写好了,后面的路就好走了。