3. V4L2 框架基础:Video for Linux 2 的核心数据结构与 ioctl 命令
V4L2(Video for Linux 2)是 Linux 内核中视频设备驱动的标准框架,也是 Camera HAL 与 Kernel 交互的核心接口。理解 V4L2 的核心数据结构与 ioctl 命令体系,是进行 HAL 与驱动对齐的前提。本章将深入剖析 V4L2 的设备模型、关键数据结构以及控制流中的 ioctl 命令。
3.1 V4L2 设备模型与核心数据结构
V4L2 将视频设备抽象为字符设备,通常位于 /dev/videoX。其核心数据结构围绕 struct video_device 展开,并通过 struct v4l2_device 进行顶层管理。
关键概念: V4L2 设备节点(
/dev/video0)是用户空间与内核驱动交互的唯一入口。每个节点对应一个 struct video_device 实例,内部包含文件操作函数集(v4l2_file_operations)和 ioctl 处理逻辑。
3.1.1 核心数据结构关系图
下图展示了 V4L2 框架中从顶层设备到底层 Buffer 的核心数据结构层级关系:
3.1.2 核心数据结构详解
| 数据结构 | 所在层级 | 核心作用 | 关键字段 |
|---|---|---|---|
struct v4l2_device |
顶层 | 管理整个视频设备实例,包含子设备列表 | subdevs 链表、dev 指针 |
struct video_device |
设备节点 | 注册到 VFS,提供 open/close/ioctl 接口 |
fops、v4l2_dev、queue |
struct v4l2_subdev |
子设备 | 表示 sensor、CSI、ISP 等硬件模块 | ops、ctrl_handler |
struct vb2_queue |
Buffer 管理 | 管理视频缓冲区的分配、入队、出队 | buf_struct_size、ops |
struct vb2_buffer |
Buffer 实例 | 代表一个具体的视频帧缓冲区 | planes、timestamp |
实战要点: 在 Camera HAL 开发中,最常打交道的结构体是
struct v4l2_buffer(用户空间版本)和 struct vb2_buffer(内核空间版本)。HAL 通过 VIDIOC_QBUF / VIDIOC_DQBUF 操作 Buffer 队列,驱动通过 vb2_buffer_done() 通知 Buffer 完成。
3.2 V4L2 ioctl 命令体系
V4L2 使用 ioctl 系统调用作为用户空间与内核驱动通信的主要方式。ioctl 命令按照功能分为多个类别,涵盖格式协商、Buffer 管理、流控制、控制参数等。
3.2.1 ioctl 命令分类总览
| 类别 | 典型 ioctl 命令 | 功能描述 | HAL 调用时机 |
|---|---|---|---|
| 查询能力 | VIDIOC_QUERYCAP |
查询设备能力(驱动名、总线信息、Capabilities) | 打开设备后立即调用 |
| 格式协商 | VIDIOC_ENUM_FMT / VIDIOC_S_FMT / VIDIOC_G_FMT |
枚举、设置、获取当前格式(像素格式、分辨率) | 配置 Stream 时调用 |
| Buffer 管理 | VIDIOC_REQBUFS / VIDIOC_QBUF / VIDIOC_DQBUF / VIDIOC_QUERYBUF |
请求缓冲区、入队、出队、查询缓冲区状态 | Stream 生命周期核心操作 |
| 流控制 | VIDIOC_STREAMON / VIDIOC_STREAMOFF |
启动/停止视频流 | 开始/停止预览或录制 |
| 控制参数 | VIDIOC_QUERYCTRL / VIDIOC_S_CTRL / VIDIOC_G_CTRL |
查询、设置、获取控制参数(曝光、增益、白平衡) | 3A 算法调节时调用 |
| 选择器 | VIDIOC_S_SELECTION / VIDIOC_G_SELECTION |
设置/获取裁剪、缩放区域 | 配置 ROI 或缩放时调用 |
3.2.2 典型 ioctl 调用流程
以下是一个典型的 Camera HAL 打开设备并配置流的 ioctl 调用序列:
// 1. 打开设备
fd = open("/dev/video0", O_RDWR);
// 2. 查询设备能力
struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
// 3. 枚举并设置格式
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
fmt.fmt.pix_mp.width = 1920;
fmt.fmt.pix_mp.height = 1080;
fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;
ioctl(fd, VIDIOC_S_FMT, &fmt);
// 4. 请求缓冲区
struct v4l2_requestbuffers req;
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
req.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &req);
// 5. 查询并映射缓冲区
for (i = 0; i < req.count; i++) {
struct v4l2_buffer buf;
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_QUERYBUF, &buf);
// mmap 映射
mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
}
// 6. 入队所有缓冲区
for (i = 0; i < req.count; i++) {
struct v4l2_buffer buf;
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_QBUF, &buf);
}
// 7. 启动流
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
ioctl(fd, VIDIOC_STREAMON, &type);
// 8. 循环出队处理帧
while (running) {
struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_DQBUF, &buf);
// 处理帧数据
process_frame(buf);
// 重新入队
ioctl(fd, VIDIOC_QBUF, &buf);
}
3.2.3 ioctl 调用流程图
下图展示了从 HAL 发起 ioctl 到驱动响应的完整调用链路:
3.3 Buffer 管理机制
V4L2 的 Buffer 管理是性能关键路径。HAL 与驱动通过 VIDIOC_REQBUFS 协商缓冲区数量与内存模型,通过 VIDIOC_QBUF / VIDIOC_DQBUF 实现生产者-消费者模式。
3.3.1 三种内存模型对比
| 内存模型 | 标志 | 数据流向 | 适用场景 | 性能特点 |
|---|---|---|---|---|
| MMAP | V4L2_MEMORY_MMAP |
驱动分配 → 用户映射 | 大多数 Camera 场景 | 零拷贝,性能最优 |
| USERPTR | V4L2_MEMORY_USERPTR |
用户分配 → 驱动使用 | 需要用户管理内存的场景 | 需要拷贝或特殊 DMA 支持 |
| DMABUF | V4L2_MEMORY_DMABUF |
用户导入 dma-buf → 驱动使用 | 跨设备共享(GPU/Display) | 零拷贝,支持跨设备共享 |
对齐陷阱: HAL 与驱动必须就内存模型达成一致。如果 HAL 使用
V4L2_MEMORY_MMAP 请求缓冲区,但驱动只支持 V4L2_MEMORY_DMABUF,则 VIDIOC_REQBUFS 会返回 EINVAL。这是 HAL 与 Kernel 接口对齐中最常见的错误之一。
3.3.2 Buffer 状态机
每个 V4L2 Buffer 在生命周期中经历以下状态转换:
3.4 格式协商与 Selection
格式协商是 HAL 与驱动对齐的关键环节。HAL 通过 VIDIOC_ENUM_FMT 枚举驱动支持的格式,通过 VIDIOC_S_FMT 设置最终格式。Selection 机制用于控制裁剪和缩放区域。
3.4.1 格式协商流程
- 枚举格式: 调用
VIDIOC_ENUM_FMT获取驱动支持的所有像素格式。 - 尝试格式: 调用
VIDIOC_TRY_FMT测试某个格式是否被接受,但不实际设置。 - 设置格式: 调用
VIDIOC_S_FMT设置最终格式,驱动可能调整参数(如对齐到硬件限制)。 - 获取格式: 调用
VIDIOC_G_FMT确认实际生效的格式。
对齐检查点: HAL 在
S_FMT 后必须调用 G_FMT 确认驱动实际设置的格式。驱动可能修改 width/height 为硬件对齐值(如 16 像素对齐),HAL 必须根据实际值分配 Buffer。
3.4.2 Selection 目标类型
| Selection 目标 | 常量 | 用途 |
|---|---|---|
| 裁剪区域 | V4L2_SEL_TGT_CROP |
从 sensor 原始画面中裁剪出感兴趣区域 |
| 裁剪边界 | V4L2_SEL_TGT_CROP_BOUNDS |
查询硬件支持的最大裁剪范围 |
| 缩放区域 | V4L2_SEL_TGT_COMPOSE |
将裁剪后的图像缩放到输出尺寸 |
| 缩放边界 | V4L2_SEL_TGT_COMPOSE_BOUNDS |
查询硬件支持的最大缩放范围 |
3.5 本章小结
- V4L2 核心数据结构包括
v4l2_device、video_device、v4l2_subdev和vb2_queue,它们构成了从设备管理到 Buffer 流转的完整链路。 - ioctl 命令体系覆盖查询能力、格式协商、Buffer 管理、流控制和控制参数五大类,是 HAL 与驱动交互的唯一通道。
- Buffer 管理采用生产者-消费者模式,通过
QBUF/DQBUF实现零拷贝数据流转,内存模型选择(MMAP/USERPTR/DMABUF)直接影响性能与兼容性。 - 格式协商与 Selection 机制确保 HAL 与驱动在图像尺寸、裁剪、缩放等参数上达成一致,是接口对齐的核心检查点。