2. 相机设备打开超时

问题现象与排查范围

相机应用调用 openCamera() 后长时间无响应,最终抛出 CAMERA_DEVICE_ERROR_CAMERA_IN_USE 或 CAMERA_DEVICE_ERROR_MAX_CAMERAS_IN_USE,Logcat 中出现 camera_device.open 回调超时(通常为 5 秒)。本案例聚焦 HAL 层 camera_device.open 入口,分析导致回调阻塞的典型原因并给出可落地的优化方案。

HAL 层 open 调用链分析

在 Android Camera HAL3 架构中,camera_module_t.open() 由 CameraProvider 进程调用,其内部通常执行以下操作:

  1. 获取 camera_device 结构体并初始化互斥锁;
  2. 打开底层驱动节点(如 /dev/video* 或 vendor 专用节点);
  3. 初始化 ISP/传感器电源、时钟;
  4. 创建并启动预览/捕获相关线程;
  5. 返回 camera_device_t 接口指针。

超时根因集中在驱动节点打开阻塞、电源/时钟等待超时、以及初始化流程中持锁等待外部服务三方面。下面给出一个最小可复现的 HAL 层 open 实现,并标注超时风险点与修复方式。

自洽实现:带超时保护的 camera_device.open

// camera_hal_sample.cpp
#include <cutils/log.h>
#include <hardware/camera_common.h>
#include <hardware/camera.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <time.h>

#define DEV_NODE "/dev/video0"
#define OPEN_TIMEOUT_SEC 3

typedef struct {
    camera_device_t base;
    int fd;
    pthread_mutex_t lock;
    int open_done;
} sample_camera_device_t;

// 带超时的驱动节点打开
static int open_driver_node(const char* path, int timeout_sec) {
    int fd = -1;
    struct timeval tv;
    fd_set wfds;
    int flags = O_RDWR | O_NONBLOCK;

    fd = open(path, flags);
    if (fd < 0) {
        // 驱动节点不存在或权限不足,直接返回错误
        ALOGE("open %s failed: %s", path, strerror(errno));
        return -1;
    }

    // 等待设备可写(表示驱动初始化完成)
    FD_ZERO(&wfds);
    FD_SET(fd, &wfds);
    tv.tv_sec = timeout_sec;
    tv.tv_usec = 0;

    int ret = select(fd + 1, NULL, &wfds, NULL, &tv);
    if (ret == 0) {
        ALOGE("open %s timeout after %d sec", path, timeout_sec);
        close(fd);
        return -1;
    } else if (ret < 0) {
        ALOGE("select error: %s", strerror(errno));
        close(fd);
        return -1;
    }

    // 恢复阻塞模式
    int flags_restore = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags_restore & ~O_NONBLOCK);
    return fd;
}

static int sample_device_open(const struct hw_module_t* module,
                              const char* id,
                              struct hw_device_t** device) {
    if (module == NULL || id == NULL || device == NULL) {
        return -EINVAL;
    }

    sample_camera_device_t* dev = (sample_camera_device_t*)calloc(1, sizeof(*dev));
    if (dev == NULL) {
        return -ENOMEM;
    }

    pthread_mutex_init(&dev->lock, NULL);
    dev->open_done = 0;

    // 关键修复点:驱动打开必须带超时,避免内核态阻塞导致 open 回调超时
    dev->fd = open_driver_node(DEV_NODE, OPEN_TIMEOUT_SEC);
    if (dev->fd < 0) {
        ALOGE("driver node open failed");
        pthread_mutex_destroy(&dev->lock);
        free(dev);
        return -ENODEV;
    }

    // 模拟耗时初始化(如 ISP 固件加载),实际项目中应检查每一步的耗时
    struct timespec req = {0, 200 * 1000 * 1000}; // 200ms
    nanosleep(&req, NULL);

    // 初始化 camera_device 接口
    dev->base.common.tag = HARDWARE_DEVICE_TAG;
    dev->base.common.version = CAMERA_DEVICE_API_VERSION_3_4;
    dev->base.common.module = (hw_module_t*)module;
    dev->base.common.close = NULL; // 实际需实现 close 回调

    // 此处省略 camera_device_ops_t 的函数指针赋值
    // 实际项目中必须实现 initialize、set_request 等核心回调

    *device = &dev->base.common;
    dev->open_done = 1;
    ALOGI("camera device opened, fd=%d", dev->fd);
    return 0;
}

// HAL module 定义(仅展示 open 入口)
static hw_module_methods_t v1_module_methods = {
    .open = sample_device_open
};

camera_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = CAMERA_MODULE_API_VERSION_2_4,
        .hal_api_version = HARDWARE_HAL_API_VERSION,
        .id = CAMERA_HARDWARE_MODULE_ID,
        .name = "Sample Camera HAL",
        .author = "CodeCase",
        .methods = &v1_module_methods,
        .dso = NULL,
        .reserved = {0},
    },
    .get_number_of_cameras = NULL, // 实际需实现
    .get_camera_info = NULL,       // 实际需实现
};

超时根因与修复对照

超时原因代码位置修复策略
驱动节点 open 阻塞(内核态等待)open_driver_node() 中 open() 调用使用 O_NONBLOCK + select() 设置超时,超时后主动返回错误
初始化流程中持锁等待外部服务HAL 中调用 vendor 服务或等待属性所有跨进程等待必须设置超时,禁止无限期阻塞
电源/时钟稳定等待时间过长驱动或 HAL 初始化序列将等待拆分为多次短查询,总超时控制在 3 秒内
同一设备被并发 openHAL 层未加互斥保护在 open 入口增加 pthread_mutex_trylock,失败立即返回 CAMERA_IN_USE

验证方法与判定标准

编译验证:将上述代码放入 HAL 源码目录,使用 Android 编译系统执行 mmm hardware/sample_camera,确认无编译错误。

运行时验证:

  1. 在 HAL 层 sample_device_open() 入口和出口添加 ALOGI 日志,记录时间戳;
  2. 使用 adb shell dumpsys media.camera 查看设备状态;
  3. 编写测试应用调用 openCamera(),观察 Logcat:
# 预期日志序列(示例,非实测)
01-01 12:00:01.123  1234  5678 I SampleCam: camera_device.open enter
01-01 12:00:01.456  1234  5678 I SampleCam: driver node opened, fd=21
01-01 12:00:01.657  1234  5678 I SampleCam: camera device opened, fd=21
01-01 12:00:01.658  1234  5678 I CameraProvider: camera_device.open callback success

判定标准:

  • 从 open 进入到回调返回总耗时小于 5 秒(系统超时阈值);
  • 当驱动节点无法访问时,HAL 在 3 秒内返回错误,而非无限阻塞;
  • 连续 100 次 open/close 循环无超时或崩溃。

故障注入测试:将 DEV_NODE 改为不存在的路径,确认 HAL 在 3 秒内返回 -ENODEV,上层收到 CAMERA_DEVICE_ERROR_CAMERA_IN_USE 或对应错误码,且进程无 ANR。

关键优化点总结

  • 所有可能阻塞的调用(open、ioctl、等待属性)必须设置超时上限;
  • open 流程中避免持锁调用耗时操作,初始化状态用原子变量标记;
  • 驱动节点打开失败时区分“不存在”和“忙”,分别返回 -ENODEV 和 -EBUSY,便于上层定位;
  • 在 HAL 层增加打开耗时统计日志,便于线上问题回溯。