4. Camera HAL 初始化流程:HAL Module 加载、camera_info 填充、open() 与 close() 接口实现

Camera HAL 的初始化是上层 Camera Service 与底层硬件驱动之间的第一个关键握手阶段。本章将深入剖析 HAL Module 的加载机制、camera_info 静态信息的填充策略,以及 open() 与 close() 接口的标准实现模式。理解这一流程是后续进行多摄像头适配、HAL 层性能调优的基础。

4.1 HAL Module 加载机制

在 Android 系统中,Camera Service 通过 HAL_MODULE_INFO_SYM 符号找到 HAL 库的入口。该符号是一个 hw_module_t 结构体,其中包含了 HAL 的版本号、作者信息以及最重要的 open 函数指针。

核心要点: HAL 库必须导出 HAL_MODULE_INFO_SYM 符号,否则 Camera Service 无法加载该库。加载失败时,logcat 中会出现 load: module=xxx failed 的错误信息。

加载流程分为以下步骤:

  1. 动态库加载: Camera Service 通过 dlopen() 打开 camera.xxx.so 库文件。
  2. 符号查找: 通过 dlsym() 获取 HAL_MODULE_INFO_SYM 符号地址。
  3. Module 结构验证: 检查 hw_module_t.common.module_api_version 是否匹配当前系统版本。
  4. 调用 open(): 通过 module->methods->open() 创建 HAL 设备实例。
字段 类型 说明
tag uint32_t 必须设置为 HARDWARE_MODULE_TAG
module_api_version uint16_t HAL 模块 API 版本,如 HAL_MODULE_API_VERSION(1, 0)
hal_api_version uint16_t HAL 层 API 版本,通常与 module_api_version 一致
id const char* 必须为 HARDWARE_CAMERA_HAL
name const char* HAL 模块名称,如 "Camera HAL v3.4"
methods hw_module_methods_t* 包含 open 函数指针
移植提示: 在嵌入式平台上,如果使用 V4L2 驱动,HAL 库的加载路径通常为 /vendor/lib/hw/camera.xxx.so。确保 Android.bp 或 Android.mk 中正确指定了 relative_install_path 为 hw。

4.2 camera_info 静态信息填充

camera_info 是 HAL 层向 Camera Service 报告摄像头能力的关键数据结构。它包含设备 ID、朝向、安装角度、是否支持闪光灯等静态属性。该信息在 get_camera_info() 回调中填充,通常在 HAL 初始化阶段完成。

以下是一个典型的 camera_info 填充示例:

int get_camera_info(int camera_id, struct camera_info *info) {
    if (camera_id < 0 || camera_id >= CAMERA_NUM) {
        ALOGE("Invalid camera id: %d", camera_id);
        return -EINVAL;
    }

    // 填充设备 ID
    info->device_id = camera_id;

    // 设置摄像头朝向:前置或后置
    if (camera_id == 0) {
        info->facing = CAMERA_FACING_BACK;
        info->orientation = 90;   // 后置摄像头旋转角度
    } else {
        info->facing = CAMERA_FACING_FRONT;
        info->orientation = 270;  // 前置摄像头旋转角度
    }

    // 资源成本:0-100,数值越大表示资源消耗越高
    info->resource_cost = 50;

    // 冲突设备:如果同时打开该设备会冲突的设备 ID 列表
    info->conflicting_devices = NULL;
    info->conflicting_devices_length = 0;

    return 0;
}
常见错误: orientation 值设置错误会导致预览画面旋转方向异常。对于后置摄像头,通常为 90 度或 270 度;前置摄像头通常为 270 度或 90 度,具体取决于硬件安装角度。务必参考硬件规格书确认。

4.3 open() 与 close() 接口实现

open() 是 HAL 层最核心的接口,负责初始化硬件设备、分配资源、创建数据通路。close() 则负责释放资源、关闭设备。这两个接口必须成对实现,且要保证线程安全。

4.3.1 open() 接口实现要点

  • 参数验证: 检查 hw_module_t 和 hw_device_t 指针是否有效。
  • 设备打开: 通过 V4L2 或平台私有驱动接口打开摄像头设备节点。
  • 内存分配: 为 Gralloc Buffer、Metadata 等分配内存池。
  • 初始化流配置: 设置默认的流格式和分辨率。
  • 注册回调: 将 camera3_device_ops 函数表注册到设备结构体中。
static int camera_device_open(const struct hw_module_t *module,
                              const char *name,
                              struct hw_device_t **device) {
    if (module == NULL || name == NULL || device == NULL) {
        ALOGE("Invalid parameters");
        return -EINVAL;
    }

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

    // 分配设备上下文
    camera3_device_t *camera_dev = (camera3_device_t *)
        malloc(sizeof(camera3_device_t));
    if (camera_dev == NULL) {
        ALOGE("Failed to allocate camera device");
        return -ENOMEM;
    }

    memset(camera_dev, 0, sizeof(camera3_device_t));

    // 填充 hw_device_t 公共字段
    camera_dev->common.tag = HARDWARE_DEVICE_TAG;
    camera_dev->common.version = CAMERA_DEVICE_API_VERSION_3_4;
    camera_dev->common.module = (hw_module_t *)module;
    camera_dev->common.close = camera_device_close;

    // 注册 camera3_device_ops
    camera_dev->ops = &camera3_ops;

    // 打开 V4L2 设备节点
    char dev_node[32];
    snprintf(dev_node, sizeof(dev_node), "/dev/video%d", camera_id);
    int fd = open(dev_node, O_RDWR);
    if (fd < 0) {
        ALOGE("Failed to open %s: %s", dev_node, strerror(errno));
        free(camera_dev);
        return -ENODEV;
    }

    // 保存私有数据
    camera_dev->priv = (void *)create_private_context(fd, camera_id);

    *device = &camera_dev->common;
    ALOGI("Camera device %d opened successfully", camera_id);
    return 0;
}

4.3.2 close() 接口实现要点

  • 停止所有流: 确保所有正在运行的流已经停止,释放未处理的请求。
  • 关闭设备节点: 关闭 V4L2 或平台驱动的文件描述符。
  • 释放内存: 释放 Metadata、Buffer 池、私有上下文等资源。
  • 置空指针: 将设备结构体指针置空,防止野指针。
static int camera_device_close(struct hw_device_t *device) {
    if (device == NULL) {
        ALOGE("Device is NULL");
        return -EINVAL;
    }

    camera3_device_t *camera_dev = (camera3_device_t *)device;
    private_context_t *priv = (private_context_t *)camera_dev->priv;

    if (priv != NULL) {
        // 停止所有正在运行的流
        stop_all_streams(priv);

        // 关闭 V4L2 设备节点
        if (priv->fd >= 0) {
            close(priv->fd);
            priv->fd = -1;
        }

        // 释放 Metadata 和 Buffer 池
        free_metadata_pool(priv);
        free_buffer_pool(priv);

        // 释放私有上下文
        free(priv);
        camera_dev->priv = NULL;
    }

    free(camera_dev);
    ALOGI("Camera device closed successfully");
    return 0;
}
工程经验: 在嵌入式平台上,close() 接口中务必添加超时机制。如果某个流正在等待硬件完成,而硬件出现异常,close() 可能会被阻塞。建议设置 2-3 秒的超时,超时后强制关闭设备节点。

4.4 初始化流程总览

下图展示了从 HAL Module 加载到设备打开的整体流程:

Camera HAL 初始化流程总览 Camera Service dlopen("camera.xxx.so") dlsym → HAL_MODULE_INFO_SYM module->methods->open() open("/dev/videoX") 初始化完成 说明:每个步骤失败时需返回对应错误码,并释放已分配的资源,避免内存泄漏

4.5 常见问题与排查方法

问题现象 可能原因 排查方法
HAL 库加载失败 库路径错误或符号未导出 检查 dlopen 返回的 dlerror 信息;确认 HAL_MODULE_INFO_SYM 已正确声明
open() 返回 -ENODEV 设备节点不存在或权限不足 检查 /dev/video* 节点是否存在;确认 SELinux 策略允许访问
camera_info 朝向错误 orientation 值设置与硬件安装角度不匹配 通过 v4l2-ctl --all 查看驱动报告的旋转信息;对照硬件规格书修正
close() 卡死 流未停止或硬件未响应 在 close 中添加超时机制;检查驱动是否有未完成的 DMA 传输
安全提示: 在多摄像头系统中,open() 和 close() 可能被多个线程同时调用。必须使用互斥锁(如 pthread_mutex_t)保护共享资源,防止竞态条件导致设备状态不一致。

4.6 本章小结

Camera HAL 初始化流程是嵌入式平台摄像头适配的基石。开发者需要掌握:

  • HAL Module 加载: 确保 HAL_MODULE_INFO_SYM 正确导出,库文件路径符合系统规范。
  • camera_info 填充: 准确设置朝向、旋转角度等静态信息,避免上层应用出现方向错误。
  • open() 与 close() 实现: 严格遵循资源分配与释放的对称原则,添加超时和错误处理机制。