4. 打开 Camera 设备:open() 接口调用链、设备节点权限检查、常见打开失败原因
Camera HAL 层打开设备是上层应用与底层硬件交互的“第一道关卡”。open() 调用一旦失败,整个相机链路将无法启动。本章深入剖析 open() 的完整调用链、设备节点权限检查机制,并系统梳理最常见的打开失败原因及排查方法。
4.1 open() 接口调用链全景
从 Framework 到 Kernel 驱动,open() 调用链通常跨越 4 个层级。理解每一层的职责是定位问题的前提。
关键路径: CameraService → HAL Module → HAL Adapter/Device → V4L2 / 私有驱动 → Kernel Device Node
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 节点被占用的深度排查
“节点被占用”在双摄、多摄、录像+预览同时使用的场景中尤为常见。以下是一套标准排查流程:
- 确认占用进程: 使用
lsof /dev/video0或fuser -v /dev/video0查看哪个 PID 持有该节点。 - 检查进程状态:
ps -p <PID> -o pid,comm,state,确认进程是否处于D(不可中断睡眠) 或Z(僵尸) 状态。 - 检查驱动引用计数:
cat /sys/kernel/debug/v4l2/<dev>/refcount(部分内核支持)。 - 强制释放(调试用):
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 是否使能。
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)
排查过程:
- 检查设备节点权限:
ls -l /dev/video0显示crw------- 1 root root,组权限缺失。 - 检查 cameraserver 进程组:
ps -A | grep cameraserver确认进程 UID 为cameraserver,但不在camera组。 - 检查 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。