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 进程调用,其内部通常执行以下操作:
- 获取 camera_device 结构体并初始化互斥锁;
- 打开底层驱动节点(如
/dev/video*或 vendor 专用节点); - 初始化 ISP/传感器电源、时钟;
- 创建并启动预览/捕获相关线程;
- 返回
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 秒内 |
| 同一设备被并发 open | HAL 层未加互斥保护 | 在 open 入口增加 pthread_mutex_trylock,失败立即返回 CAMERA_IN_USE |
验证方法与判定标准
编译验证:将上述代码放入 HAL 源码目录,使用 Android 编译系统执行 mmm hardware/sample_camera,确认无编译错误。
运行时验证:
- 在 HAL 层
sample_device_open()入口和出口添加ALOGI日志,记录时间戳; - 使用
adb shell dumpsys media.camera查看设备状态; - 编写测试应用调用
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 层增加打开耗时统计日志,便于线上问题回溯。