4、Camera设备打开:camera_device_t结构体、open_device流程、设备状态机管理、多摄像头支持
好,咱们接着聊。上一节我们把HAL模块加载进来了,但模块加载只是第一步。真正让相机跑起来,得靠打开设备。这就像你插上U盘,系统识别到了硬件,但你还得双击盘符才能看到里面的文件。今天我就带你走一遍这个「双击盘符」的过程。
4.1 camera_device_t:设备操作的「遥控器」
先说说这个结构体。camera_device_t,说白了就是HAL层暴露给上层的一个操作句柄。上层拿着它,就能指挥相机干活了。
typedef struct camera_device {
hw_device_t common;
camera_device_ops_t *ops;
void *priv;
} camera_device_t;
这里三个字段,我一个个说:
- common:继承自hw_device_t,包含了设备ID、版本号、模块句柄等基础信息。这是Android硬件抽象层的通用套路。
- ops:操作函数表。上层调用的所有API,比如start_preview、take_picture,最终都映射到这里。
- priv:私有数据指针。我习惯在这里挂一个自定义的上下文结构体,里面放设备状态、流信息、传感器句柄等。你想想看,如果不用priv,这些数据放哪?全局变量?那多摄像头就乱套了。
ops这个函数表,我列几个关键接口:
| 接口 | 说明 |
|---|---|
| set_preview_window | 设置预览窗口,通常是一个ANativeWindow |
| start_preview | 启动预览流 |
| stop_preview | 停止预览 |
| take_picture | 拍照,返回JPEG数据 |
| set_callbacks | 注册回调,比如帧到达、错误通知 |
| dump | 调试用,导出设备内部状态 |
个人经验:我在项目中遇到过,某个厂商的ops实现里,set_preview_window和start_preview的顺序搞反了。上层先调start_preview再调set_preview_window,结果预览黑屏。排查了两天才发现是HAL层没做容错。所以我的建议是:在start_preview里检查窗口是否已设置,没设置就缓存起来,等窗口来了再启动。
4.2 open_device流程:从模块到设备的「最后一公里」
模块加载完成后,上层会调用hw_module_t->methods->open()来打开设备。这个流程,我拆成几步来讲:
- 参数校验:检查传入的device_id是否合法。多摄像头场景下,这个id可能是"0"、"1"或者"back"、"front"这样的字符串。
- 分配camera_device_t:malloc一个camera_device_t结构体,初始化common字段,把ops指向你的实现函数表。
- 初始化硬件:打开传感器驱动、初始化ISP、分配内存池。这一步如果失败,记得回滚前面分配的资源。
- 状态机初始化:把设备状态设为CAMERA_DEVICE_STATE_CLOSED或OPENED,看你的设计。
- 返回设备句柄:把填好的camera_device_t通过参数返回给上层。
static int camera_device_open(const struct hw_module_t* module,
const char* id,
struct hw_device_t** device) {
// 1. 参数校验
if (!module || !id || !device) return -EINVAL;
int camera_id = atoi(id);
if (camera_id < 0 || camera_id >= MAX_CAMERAS) return -ENODEV;
// 2. 分配设备结构体
camera_device_t *dev = malloc(sizeof(camera_device_t));
memset(dev, 0, sizeof(*dev));
// 3. 初始化common
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 1;
dev->common.module = (hw_module_t*)module;
dev->common.close = camera_device_close;
// 4. 挂载ops
dev->ops = &g_camera_device_ops;
// 5. 初始化私有数据
dev->priv = init_camera_priv(camera_id);
if (!dev->priv) {
free(dev);
return -ENOMEM;
}
// 6. 状态机初始化
set_device_state(dev->priv, CAMERA_DEVICE_STATE_OPENED);
*device = &dev->common;
return 0;
}
注意:open_device里不要做耗时操作。我曾经见过一个实现,在open里做了传感器校准,耗时3秒,结果上层超时直接杀进程。正确的做法是:open只做轻量初始化,重活放到start_preview里做。
4.3 设备状态机管理:别让相机「精神分裂」
相机设备是有状态的。你不能在预览中再去open,也不能在拍照时突然close。状态机就是用来管这些的。
我常用的状态定义:
- CLOSED:设备未打开,或者已关闭。
- OPENED:设备已打开,但未开始预览。
- PREVIEW_STARTED:预览已启动。
- CAPTURE_IN_PROGRESS:正在拍照或录像。
- ERROR:设备出错,需要重启。
状态转换规则:
CLOSED --open()--> OPENED
OPENED --start_preview()--> PREVIEW_STARTED
PREVIEW_STARTED --stop_preview()--> OPENED
OPENED --close()--> CLOSED
PREVIEW_STARTED --take_picture()--> CAPTURE_IN_PROGRESS
CAPTURE_IN_PROGRESS --capture_done()--> PREVIEW_STARTED
任何状态 --error()--> ERROR
ERROR --reset()--> CLOSED
避坑指南:我曾经在状态机里漏掉了ERROR到CLOSED的转换。结果设备出错后,上层调用close(),我检查状态不是OPENED也不是PREVIEW_STARTED,直接返回了错误。上层就再也关不掉这个设备了。嗯,后来我加了一个「任何状态都能close」的兜底逻辑。
实现上,我建议用枚举+switch-case,别用if-else堆。代码可读性差,还容易漏状态。
typedef enum {
CAMERA_DEVICE_STATE_CLOSED,
CAMERA_DEVICE_STATE_OPENED,
CAMERA_DEVICE_STATE_PREVIEW_STARTED,
CAMERA_DEVICE_STATE_CAPTURE_IN_PROGRESS,
CAMERA_DEVICE_STATE_ERROR,
} camera_device_state_t;
static int check_state_transition(camera_device_state_t current,
camera_device_state_t target) {
switch (current) {
case CAMERA_DEVICE_STATE_CLOSED:
return (target == CAMERA_DEVICE_STATE_OPENED) ? 0 : -EPERM;
case CAMERA_DEVICE_STATE_OPENED:
return (target == CAMERA_DEVICE_STATE_PREVIEW_STARTED ||
target == CAMERA_DEVICE_STATE_CLOSED) ? 0 : -EPERM;
// ... 其他状态
default:
return -EINVAL;
}
}
4.4 多摄像头支持:一个模块,多个设备
现在的手机,哪个不是三摄四摄?多摄像头支持,是HAL层必须面对的问题。
核心思路其实很简单:一个camera_module_t对应多个camera_device_t。每个设备有自己的id、自己的状态机、自己的私有数据。
具体实现上,有几点要注意:
- 设备ID管理:用数组或链表维护所有设备的信息。我习惯用静态数组,因为摄像头数量是固定的。
- 资源隔离:每个设备有自己的内存池、ISP通道、传感器控制。别让设备A的预览影响了设备B的拍照。
- 同步问题:多摄像头同时操作时,可能会有资源竞争。比如两个摄像头共用同一个I2C总线,就需要加锁。
- 热插拔:USB摄像头场景下,设备可能动态插入拔出。这时候需要动态管理设备列表。
#define MAX_CAMERAS 4
static camera_device_t *g_cameras[MAX_CAMERAS];
static pthread_mutex_t g_camera_lock = PTHREAD_MUTEX_INITIALIZER;
int camera_device_open(const struct hw_module_t* module,
const char* id,
struct hw_device_t** device) {
pthread_mutex_lock(&g_camera_lock);
int camera_id = atoi(id);
if (g_cameras[camera_id] != NULL) {
// 设备已打开,返回错误
pthread_mutex_unlock(&g_camera_lock);
return -EBUSY;
}
// ... 分配和初始化
g_cameras[camera_id] = dev;
pthread_mutex_unlock(&g_camera_lock);
return 0;
}
个人经验:我在做双摄项目时,发现两个摄像头同时start_preview,第二个总是失败。查了半天,原来是ISP只有一个DMA通道,被第一个占用了。解决方案是:在open时分配好资源,如果资源不够,直接返回失败,别等到start_preview才报错。
好了,这一章的内容就这些。camera_device_t是操作入口,open_device是初始化流程,状态机保证操作有序,多摄像头支持则是现代手机的刚需。下一章,我们会深入预览流程,看看数据是怎么从传感器一路跑到屏幕上的。