3. HAL 模块初始化流程:HAL Module 加载、open 函数实现、Camera 设备枚举与初始化
好,咱们今天聊聊 HAL 层最开头的那几步。说白了,就是系统怎么找到你的 HAL 库,怎么把摄像头设备“叫醒”,然后怎么把一个个 Camera 设备枚举出来。这部分代码我调过不下十次,每次出问题都挺头疼的——因为一旦初始化挂了,后面啥都跑不起来。
3.1 HAL Module 加载:系统怎么找到你的 so 库?
Android 的 Camera HAL 遵循标准的 HAL 架构。系统通过 hw_get_module() 来加载你编译好的 camera.xxx.so。这个函数会根据硬件 ID 去 /vendor/lib/hw/ 或 /system/lib/hw/ 下找对应的库。
我个人习惯在 HAL_MODULE_INFO_SYM 这个结构体里把信息填全。你想想看,系统加载时第一个找的就是这个符号。如果这里填错了,加载直接失败。
核心结构体:
struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = CAMERA_HARDWARE_MODULE_ID,
.name = "Camera HAL Module",
.author = "Your Company",
.methods = &hal_module_methods,
};
这里有个坑——.id 必须写成 CAMERA_HARDWARE_MODULE_ID,也就是 "camera"。我曾经见过有人手抖写成了 "camra",结果系统死活加载不上,查了半天才发现是拼写错误。
3.2 open 函数实现:真正的设备初始化入口
hw_module_methods_t 里最重要的就是 open 函数。当 Camera Service 调用 camera_device_open() 时,HAL 层需要返回一个 camera_device_t 结构体。
嗯,这里要注意。open 函数不仅仅是“打开设备”,它要完成的事情很多:
- 分配
camera_device_t内存 - 填充操作函数表(ops 结构体)
- 初始化底层硬件(比如 I2C、MIPI 接口)
- 设置默认的流配置
我建议在 open 函数里加个超时机制。为什么?因为有些 Sensor 上电初始化需要时间,如果硬件没准备好就返回,上层调用时会直接崩掉。
我的经验:在 open 函数里加一个 200ms 的重试逻辑。如果 Sensor 第一次读 ID 失败,等 50ms 再试一次。很多硬件问题其实只是上电时序没对齐。
static int camera_device_open(const struct hw_module_t* module,
const char* id,
struct hw_device_t** device) {
// 1. 参数检查
if (module == NULL || id == NULL || device == NULL) {
ALOGE("Invalid parameters");
return -EINVAL;
}
// 2. 分配设备结构体
camera_device_t* cam_dev = (camera_device_t*)malloc(sizeof(camera_device_t));
memset(cam_dev, 0, sizeof(camera_device_t));
// 3. 填充 ops 函数表
cam_dev->ops = &camera_device_ops;
// 4. 初始化底层硬件(带重试)
int ret = init_sensor_hardware(atoi(id));
if (ret != 0) {
ALOGE("Failed to init sensor %s", id);
free(cam_dev);
return -ENODEV;
}
// 5. 返回设备句柄
*device = (hw_device_t*)cam_dev;
return 0;
}
3.3 Camera 设备枚举:get_number_of_cameras 的秘密
系统通过 get_number_of_cameras() 知道你有几个摄像头。这个函数看似简单,但我在项目中遇到过一个问题——如果返回的数量和实际不匹配,上层会直接报错。
说白了,这个函数返回的是你 HAL 层支持的摄像头总数。包括后置、前置、深度摄像头等等。我建议在实现时用一个静态数组来维护摄像头信息列表。
| 摄像头 ID | 朝向 | 分辨率 | 是否支持 ZSL |
|---|---|---|---|
| 0 | BACK | 4096x3072 | 是 |
| 1 | FRONT | 1920x1080 | 否 |
| 2 | BACK | 1280x720 | 是 |
枚举时,get_camera_info() 会填充 camera_info_t 结构体。这里有个容易忽略的点——facing 字段和 orientation 字段必须填对。否则预览画面方向会不对。
警告:千万不要在 get_camera_info() 里做耗时操作。这个函数会被频繁调用,如果每次去读 Sensor 的 EEPROM,性能会非常差。我建议在 HAL 模块加载时就把所有摄像头信息缓存起来。
3.4 初始化流程图:从加载到就绪
下面这张图是我自己画的,把整个初始化流程串起来了。你跟着箭头走一遍,基本就能理解 HAL 层启动的全貌。
3.5 设备枚举的细节:get_camera_info 实现要点
枚举过程中,get_camera_info() 会被每个摄像头调用一次。我见过有些实现里在这个函数里动态去读 Sensor 的寄存器,结果导致 Camera 启动特别慢。
我的做法是:在 HAL 模块加载时,一次性把所有摄像头的静态信息读出来,存到一个全局数组里。get_camera_info() 只需要查表返回即可。
static camera_info_t g_camera_info[MAX_CAMERAS];
static int get_camera_info(int camera_id, struct camera_info* info) {
if (camera_id < 0 || camera_id >= MAX_CAMERAS) {
return -EINVAL;
}
// 直接从缓存中拷贝,不做任何硬件操作
memcpy(info, &g_camera_info[camera_id], sizeof(camera_info_t));
return 0;
}
避坑指南:我曾经在 get_camera_info() 里加了日志打印,结果发现每次打开 Camera 应用,这个函数被调用了十几次。日志刷屏不说,还拖慢了启动速度。所以——生产代码里,这个函数越轻量越好。
3.6 初始化失败的处理策略
初始化不可能永远成功。硬件没接好、I2C 通信失败、电源不稳定,都可能导致初始化失败。我建议在 HAL 层做好这几件事:
- 返回明确的错误码:不要所有失败都返回
-1,用-ENODEV、-EIO、-ENOMEM区分不同场景 - 记录关键日志:初始化失败时,把失败原因、寄存器值、重试次数都打出来
- 提供降级方案:如果主 Sensor 初始化失败,可以考虑切换到备用的低分辨率模式
嗯,这里要特别说一下。有些厂商的 HAL 实现里,如果某个摄像头初始化失败,会直接让整个 HAL 模块加载失败。这其实不太合理。我建议让其他正常的摄像头继续工作,只标记失败的摄像头不可用。
好了,这部分内容就到这里。初始化流程是整个 HAL 层的基石,这块搞扎实了,后面的调试会顺畅很多。
可可精品资料库