4. 打开 Camera 设备:open() 接口调用链、设备节点权限检查、常见打开失败原因

Camera HAL 层打开设备是上层应用与底层硬件交互的“第一道关卡”。open() 调用一旦失败,整个相机链路将无法启动。本章深入剖析 open() 的完整调用链、设备节点权限检查机制,并系统梳理最常见的打开失败原因及排查方法。

4.1 open() 接口调用链全景

从 Framework 到 Kernel 驱动,open() 调用链通常跨越 4 个层级。理解每一层的职责是定位问题的前提。

关键路径: CameraService → HAL Module → HAL Adapter/Device → V4L2 / 私有驱动 → Kernel Device Node
Camera open() 调用链分层架构 Camera open() 调用链分层架构 Framework (CameraService) 调用 HAL 的 open() 接口 HAL Module (camera_module_t) 匹配 camera_id,调用 open() 回调 HAL Adapter / Device (camera3_device_t) 初始化流、管道、Sensor 配置 Kernel Driver (V4L2 / 私有驱动) 打开设备节点 /dev/video*,初始化硬件 每一层都可能抛出错误,排查时需逐层确认日志

4.2 设备节点权限检查

Android 系统通过 udev / SELinux / 文件系统权限 三重机制控制 Camera 设备节点的访问。权限不足是 open() 失败的最常见原因之一。

4.2.1 设备节点路径与权限模型

设备类型 典型节点路径 所需权限 所属组
V4L2 Camera /dev/video0 ~ /dev/video63 crw-rw---- (0660) camera / video
ISP 设备 /dev/isp、/dev/media0 crw-rw---- (0660) camera
MIPI CSI 控制 /dev/v4l-subdev* crw-rw---- (0660) camera
私有 Camera 设备 /dev/camera* crw-rw---- (0660) system / camera

4.2.2 权限排查命令

# 检查设备节点权限和所属组
ls -l /dev/video*

# 检查当前进程的 user/group
ps -A | grep cameraserver
# 或
ps -A | grep mediaserver

# 检查 SELinux 上下文
ls -Z /dev/video0

# 临时测试:以 root 权限打开设备(仅调试用)
su root
cat /dev/video0 > /dev/null  # 检查是否能打开
⚠️ 常见陷阱: cameraserver 进程必须属于 camera 组。如果 /dev/video0 的组是 video 而非 camera,且 cameraserver 不在 video 组中,则 open() 会返回 EACCES (Permission denied)。

4.3 常见打开失败原因及排查

根据实际工程经验,open() 失败可归纳为以下 5 大类。下表列出了每种失败的表现、根因和快速定位方法。

失败类别 错误码 典型日志关键字 根因 快速排查
权限不足 EACCES (13) Permission denied 进程无设备节点读写权限;SELinux 策略阻止 检查 ls -l /dev/video* 和 dmesg | grep avc
节点被占用 EBUSY (16) Device or resource busy 其他进程已打开该设备;驱动未释放 lsof /dev/video0 或 fuser -v /dev/video0
驱动未加载 ENOENT (2) No such file or directory 设备节点不存在;驱动模块未 insmod ls /dev/video*;lsmod | grep camera
硬件初始化失败 EIO (5) I/O error / init failed Sensor 上电失败;MIPI 时钟不稳;I2C 通信异常 检查 dmesg 中驱动 probe 日志
HAL 层逻辑错误 EINVAL (22) Invalid argument camera_id 越界;HAL 版本不匹配;参数结构体错误 检查 HAL 层 logcat 中 open_locked 调用参数

4.4 节点被占用的深度排查

“节点被占用”在双摄、多摄、录像+预览同时使用的场景中尤为常见。以下是一套标准排查流程:

  1. 确认占用进程: 使用 lsof /dev/video0 或 fuser -v /dev/video0 查看哪个 PID 持有该节点。
  2. 检查进程状态: ps -p <PID> -o pid,comm,state,确认进程是否处于 D (不可中断睡眠) 或 Z (僵尸) 状态。
  3. 检查驱动引用计数: cat /sys/kernel/debug/v4l2/<dev>/refcount (部分内核支持)。
  4. 强制释放(调试用): kill -9 <PID> 后重新尝试打开。
💡 工程经验: 在 HAL 层实现 open() 时,建议加入超时重试机制。例如,遇到 EBUSY 时等待 100ms 重试,最多重试 3 次。这能有效应对驱动初始化时序导致的短暂占用。

4.5 驱动未加载的根因分析

设备节点 /dev/video* 不存在,通常意味着内核驱动未成功加载或设备树 (DT) 未正确匹配。排查步骤如下:

  • 检查驱动模块: lsmod | grep -E "camera|video|v4l",确认驱动是否已插入。
  • 检查设备树匹配: dmesg | grep -i "camera\|sensor\|imx\|ov",查看 probe 是否成功。
  • 检查 I2C 探测: i2cdetect -y <bus>,确认 Sensor 在 I2C 总线上是否响应。
  • 检查电源/时钟: cat /sys/kernel/debug/regulator/<name>/status,确认 LDO 是否使能。
驱动未加载排查决策树 驱动未加载排查决策树 /dev/video* 不存在 lsmod | grep camera dmesg | grep -i "probe\|error\|camera" i2cdetect -y <bus> 确认 Sensor I2C 地址 检查电源 LDO / 时钟 / GPIO 复位时序

4.6 实战案例:权限问题导致 open() 返回 -13

现象: 第三方 App 调用 Camera2 API 打开后置摄像头时,logcat 报错:

E/CameraService: Camera HAL module open failed for camera 0
E/CameraService: connectHelper: Could not open camera 0: Permission denied (-13)

排查过程:

  1. 检查设备节点权限:ls -l /dev/video0 显示 crw------- 1 root root,组权限缺失。
  2. 检查 cameraserver 进程组:ps -A | grep cameraserver 确认进程 UID 为 cameraserver,但不在 camera 组。
  3. 检查 SELinux:dmesg | grep avc 发现 avc: denied { read write } for pid=... comm="cameraserver" name="video0"。

解决方案:

  • 临时修复: chmod 0666 /dev/video0 (仅调试,不安全)。
  • 正确修复: 修改 ueventd.rc 或 init.rc,确保 /dev/video0 权限为 0660,组为 camera。
  • SELinux 修复: 在 cameraserver.te 中添加 allow cameraserver video_device:chr_file rw_file_perms;。
✅ 验证方法: 修复后重启 cameraserver:killall cameraserver,再次打开 Camera,logcat 应显示 Camera HAL module opened successfully。