3. HAL 模块初始化流程

好,咱们今天聊聊 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 层启动的全貌。

Camera HAL 初始化流程 1. hw_get_module() 2. HAL_MODULE_INFO_SYM 3. open() 函数调用 4. 初始化硬件 Sensor 5. 填充 camera_device_t 6. 返回设备句柄 关键说明 • 加载 so 库文件 • 匹配硬件 ID • 获取模块信息 • 找到 open 入口 • 分配设备内存 • 填充 ops 表 • I2C 通信初始化 • Sensor ID 读取 • 设置默认参数 • 注册回调函数 • 初始化流配置 • 准备预览/拍照 • 返回给 CameraService • 等待上层调用

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 层的基石,这块搞扎实了,后面的调试会顺畅很多。

可可精品资料库