2. V4L2框架基础:V4L2设备节点、ioctl命令集、videobuf2核心数据结构
V4L2(Video for Linux 2)是 Linux 内核中视频设备驱动的标准框架,也是 Camera HAL 与底层硬件交互的核心桥梁。理解 V4L2 的设备节点模型、ioctl 命令体系以及 videobuf2 缓冲区管理机制,是进行 Camera HAL Buffer 管理调试的前提。
2.1 V4L2 设备节点模型
在 Linux 系统中,V4L2 设备以字符设备的形式暴露给用户空间,设备节点通常位于 /dev 目录下。Camera HAL 通过打开这些节点来与驱动进行通信。
| 设备节点 | 主设备号 | 次设备号范围 | 典型用途 |
|---|---|---|---|
/dev/video0 |
81 | 0-63 | 主摄像头视频采集、M2M(Memory-to-Memory)设备 |
/dev/video1 |
81 | 64-127 | 副摄像头、深度传感器 |
/dev/media0 |
81 | 128-191 | Media Controller 拓扑管理 |
/dev/v4l-subdev0 |
81 | 192-255 | 子设备(Sensor、ISP、CSI 接收器)控制 |
调试提示: 在调试 Camera HAL 时,首先通过
ls -l /dev/video* 确认设备节点是否存在,并使用 v4l2-ctl --list-devices 查看设备拓扑关系,这是定位驱动加载失败或节点权限问题的第一步。
2.2 V4L2 ioctl 命令集
V4L2 通过 ioctl() 系统调用实现用户空间与内核驱动的交互。ioctl 命令按照功能分为查询、设置、流控制、缓冲区管理四大类。Camera HAL 中最常用的 ioctl 命令如下:
| 分类 | ioctl 命令 | 功能描述 | HAL 调用时机 |
|---|---|---|---|
| 查询 | VIDIOC_QUERYCAP |
查询设备能力(驱动名、总线信息、Capabilities 标志) | 打开设备后立即调用 |
VIDIOC_ENUM_FMT |
枚举支持的像素格式(YUYV、NV12、MJPEG 等) | 初始化格式协商阶段 | |
VIDIOC_ENUM_FRAMESIZES |
枚举支持的帧尺寸(分辨率列表) | 配置流参数前 | |
| 设置 | VIDIOC_S_FMT |
设置数据格式(宽、高、像素格式、bytesperline) | 配置流参数时 |
VIDIOC_S_PARM |
设置帧率参数(timeperframe) | 配置帧率时 | |
| 缓冲区管理 | VIDIOC_REQBUFS |
请求分配缓冲区(指定 count 和 memory 类型) | 启动流前 |
VIDIOC_QBUF |
将缓冲区入队(放入驱动待处理队列) | 填充空 buffer 或 re-queue | |
VIDIOC_DQBUF |
从驱动出队已填充数据的缓冲区 | 获取已采集帧 | |
| 流控制 | VIDIOC_STREAMON |
启动视频流(通知硬件开始采集) | 所有 buffer 入队后 |
VIDIOC_STREAMOFF |
停止视频流(停止采集并清空队列) | 停止预览/录像时 |
关键流程: Camera HAL 启动流的典型 ioctl 序列为:
VIDIOC_S_FMT → VIDIOC_REQBUFS → VIDIOC_QBUF(多次) → VIDIOC_STREAMON → 循环 VIDIOC_DQBUF / VIDIOC_QBUF → VIDIOC_STREAMOFF。
2.3 videobuf2 核心数据结构
videobuf2(vb2)是 V4L2 框架中负责缓冲区管理的核心模块。它抽象了不同内存类型(MMAP、USERPTR、DMA_BUF)的缓冲区分配、映射、入队出队和同步操作。理解 vb2 的数据结构是分析 Buffer 问题的关键。
2.3.1 核心结构体关系
2.3.2 缓冲区状态机
videobuf2 使用严格的状态机管理每个 buffer 的生命周期。理解状态转换对于调试 buffer 卡死、丢帧问题至关重要。
| 状态 | 枚举值 | 含义 | 典型触发操作 |
|---|---|---|---|
| DEQUEUED | VB2_BUF_STATE_DEQUEUED |
缓冲区在用户空间,未被驱动使用 | DQBUF 返回后 |
| PREPARED | VB2_BUF_STATE_PREPARED |
缓冲区已准备,等待入队 | QBUF 调用后、硬件处理前 |
| QUEUED | VB2_BUF_STATE_QUEUED |
缓冲区在驱动队列中,等待硬件处理 | QBUF 完成、硬件未开始 |
| ACTIVE | VB2_BUF_STATE_ACTIVE |
硬件正在使用该缓冲区进行 DMA | 硬件开始采集 |
| DONE | VB2_BUF_STATE_DONE |
硬件完成填充,等待用户空间出队 | 硬件中断、DMA 完成 |
| ERROR | VB2_BUF_STATE_ERROR |
硬件处理出错,buffer 无效 | 硬件错误、超时 |
调试陷阱: 如果 HAL 调用 DQBUF 时返回
EIO,通常表示 buffer 状态进入了 ERROR。此时应检查驱动中 vb2_buffer_done() 调用时传递的状态参数是否为 VB2_BUF_STATE_ERROR,以及硬件是否产生了 DMA 错误中断。
2.3.3 内存类型与操作回调
videobuf2 支持三种内存模型,每种模型对应不同的分配和映射策略。驱动通过 vb2_queue 中的 io_modes 字段声明支持的模式。
| 内存类型 | ioctl 标志 | 分配方式 | HAL 侧操作 | 典型场景 |
|---|---|---|---|---|
| MMAP | V4L2_MEMORY_MMAP |
内核分配物理连续/非连续内存,用户空间通过 mmap 映射 | 调用 mmap() 获取虚拟地址 |
简单预览、拍照 |
| USERPTR | V4L2_MEMORY_USERPTR |
用户空间分配内存,将用户地址传递给驱动 | 分配 malloc 或 dma_buf 映射地址 |
GPU/Display 共享内存 |
| DMA_BUF | V4L2_MEMORY_DMABUF |
通过 dma-buf 文件描述符传递,驱动导入 exporter 的内存 | 获取 dma-buf fd 并传递给驱动 | Android HAL 标准路径 |
Android HAL 实践: 在 Android Camera HAL3 中,buffer 管理使用 DMA_BUF 模式。HAL 从 Gralloc 获取 dma-buf fd,通过
VIDIOC_QBUF 的 m.fd 字段传递给 V4L2 驱动。驱动通过 dma_buf_get() 获取 sg_table 进行 DMA 操作。这是性能最优、内存拷贝最少的路径。
2.3.4 驱动侧回调函数接口
驱动开发者需要实现 vb2_ops 结构体中的回调函数,videobuf2 核心会在合适的时机调用这些回调:
struct vb2_ops {
int (*queue_setup)(struct vb2_queue *q,
unsigned int *nbuffers,
unsigned int *nplanes,
unsigned int sizes[],
struct device *alloc_devs[]);
void (*wait_prepare)(struct vb2_queue *q);
void (*wait_finish)(struct vb2_queue *q);
int (*buf_prepare)(struct vb2_buffer *vb);
void (*buf_queue)(struct vb2_buffer *vb);
int (*start_streaming)(struct vb2_queue *q, unsigned int count);
void (*stop_streaming)(struct vb2_queue *q);
};
| 回调函数 | 调用时机 | 驱动职责 | 常见调试问题 |
|---|---|---|---|
queue_setup |
REQBUFS 时 | 确认 buffer 数量和大小,分配 plane 数 | 返回的 sizes 与硬件需求不匹配 |
buf_prepare |
QBUF 时(入队前) | 验证 buffer 参数,准备 DMA 映射 | dma_map_sg 失败导致 -ENOMEM |
buf_queue |
QBUF 完成时 | 将 buffer 加入硬件处理队列 | 未正确添加到链表导致 buffer 丢失 |
start_streaming |
STREAMON 时 | 启动硬件采集,使能 DMA | 硬件未就绪导致 streamon 失败 |
stop_streaming |
STREAMOFF 时 | 停止硬件,返回所有 buffer 到 DONE 状态 | 未正确调用 vb2_buffer_done 导致 buffer 泄漏 |
调试技巧: 当遇到
VIDIOC_REQBUFS 返回 EBUSY 时,检查驱动中 queue_setup 回调是否在流未停止时被重复调用。在 stop_streaming 中务必调用 vb2_buffer_done(vb, VB2_BUF_STATE_ERROR) 将未处理的 buffer 全部归还,否则会导致后续 REQBUFS 失败。