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 的核心数据结构层级关系:

V4L2 核心数据结构层级关系 struct v4l2_device struct video_device struct v4l2_subdev struct vb2_queue sensor / csi / isp 驱动 struct vb2_buffer 硬件寄存器 / I2C / SPI DMA / 内存 / 页表 数据结构关系说明 v4l2_device 管理整个视频设备 video_device 提供用户空间接口

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 到驱动响应的完整调用链路:

V4L2 ioctl 调用链路 Camera HAL (用户空间) ioctl(fd, VIDIOC_xxx, &arg) VFS / 系统调用层 V4L2 核心 (video_ioctl2) 驱动 v4l2_ioctl_ops 回调 硬件寄存器 / DMA 操作 关键路径说明 HAL 调用 ioctl → VFS 分发 → V4L2 核心解析命令 → 驱动回调执行 → 硬件操作

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 在生命周期中经历以下状态转换:

V4L2 Buffer 状态机 DEQUEUED QBUF QUEUED STREAMON IN_DRIVER done DONE DQBUF → DEQUEUED → QBUF → QUEUED(循环) Buffer 生命周期说明 HAL 通过 QBUF 将 Buffer 交给驱动 → 驱动填充数据后标记 DONE HAL 通过 DQBUF 取回已填充的 Buffer → 处理完后再次 QBUF

3.4 格式协商与 Selection

格式协商是 HAL 与驱动对齐的关键环节。HAL 通过 VIDIOC_ENUM_FMT 枚举驱动支持的格式,通过 VIDIOC_S_FMT 设置最终格式。Selection 机制用于控制裁剪和缩放区域。

3.4.1 格式协商流程

  1. 枚举格式: 调用 VIDIOC_ENUM_FMT 获取驱动支持的所有像素格式。
  2. 尝试格式: 调用 VIDIOC_TRY_FMT 测试某个格式是否被接受,但不实际设置。
  3. 设置格式: 调用 VIDIOC_S_FMT 设置最终格式,驱动可能调整参数(如对齐到硬件限制)。
  4. 获取格式: 调用 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 与驱动在图像尺寸、裁剪、缩放等参数上达成一致,是接口对齐的核心检查点。