V4L2框架基础:核心数据结构与Pipeline概念

做摄像头驱动开发,V4L2是绕不开的坎。我刚开始接触R-Car平台时,也被这套框架搞得晕头转向。说白了,V4L2就是Linux内核给视频设备定的一套标准接口。你只要按它的规矩来,上层应用就能用统一的open/ioctl去操作摄像头,不用管底层是MIPI还是USB。

这一章,我带你拆解V4L2最核心的几个概念。理解了它们,后面调试驱动时你才知道问题出在哪。

2.1 核心数据结构:video_device 与 v4l2_device

先看两个最重要的结构体。一个是代表“视频设备节点”的,一个是代表“整个硬件设备”的。

2.1.1 video_device

video_device 对应 /dev/videoX 这个设备节点。用户空间打开它,就能发ioctl、取数据。它的定义在 include/media/v4l2-dev.h 里。

struct video_device {
    const struct v4l2_file_operations *fops;
    struct v4l2_ioctl_ops *ioctl_ops;
    struct device dev;
    struct v4l2_device *v4l2_dev;
    unsigned long flags;
    int index;
    // ... 其他字段
};

我个人习惯重点关注这几个字段:

  • fops:文件操作函数集,open/release/read/write/poll 都在这里。
  • ioctl_ops:ioctl 操作函数集,后面会细讲。
  • v4l2_dev:指向父设备,也就是 v4l2_device。
  • index:设备编号,决定是 /dev/video0 还是 /dev/video1。
我的经验:注册 video_device 时,记得设置 v4l2_dev 指针。我见过有人忘了这步,结果上层应用打开设备时直接崩溃。嗯,调试了半天才发现。

2.1.2 v4l2_device

v4l2_device 代表一个完整的视频硬件设备。它可能包含多个 subdev(子设备),比如一个摄像头模组里有 sensor、ISP、串行器等。定义在 include/media/v4l2-device.h。

struct v4l2_device {
    struct device *dev;
    struct list_head subdevs;
    spinlock_t lock;
    char name[V4L2_DEVICE_NAME_SIZE];
    // ...
};

关键点:

  • subdevs:链表,挂载所有子设备。
  • dev:指向 platform_device 或 i2c_client 的 device 结构。
  • name:设备名,调试时很有用。
结构体 作用 对应用户空间
video_device 代表 /dev/videoX 节点 open/ioctl/mmap
v4l2_device 代表整个硬件设备 无直接对应

2.2 ioctl调用流程:从用户态到硬件

你写个应用程序调用 ioctl(fd, VIDIOC_S_FMT, &fmt),内核里到底发生了什么?我画个图你就明白了。

ioctl 调用流程 用户应用 sys_ioctl() v4l2_ioctl() video_device.ioctl_ops 具体实现函数 硬件操作 关键路径: 1. 应用调用 ioctl(fd, VIDIOC_S_FMT, ...) 2. 内核进入 sys_ioctl → v4l2_ioctl 3. v4l2_ioctl 根据 cmd 查找 ioctl_ops 中的对应函数 4. 调用驱动实现的函数,最终操作硬件寄存器

流程其实不复杂。关键是 v4l2_ioctl 这个核心分发函数。它会根据你传入的 cmd(比如 VIDIOC_S_FMT),去 video_device->ioctl_ops 里找到对应的回调函数。

避坑指南:我曾经在实现 VIDIOC_S_FMT 时,忘了检查用户传入的格式是否合法。结果上层传了个 1920x1080 的格式,但 sensor 只支持 1280x720。驱动直接写寄存器,sensor 就罢工了。后来我加了个格式校验表,才搞定。

2.3 subdev与pipeline概念

现代摄像头模组不是单一设备。一个典型的 R-Car 摄像头通路是这样的:

  • sensor:采集图像,输出 RAW 数据。
  • 串行器/解串器:把 MIPI CSI-2 信号转成并行。
  • ISP:图像信号处理,做去噪、白平衡等。
  • 视频采集控制器:把数据写入内存。

每个硬件模块都是一个 subdev(子设备)。它们通过 pipeline(管道)串联起来,数据才能从 sensor 流到内存。

2.3.1 subdev 结构

struct v4l2_subdev {
    struct v4l2_device *v4l2_dev;
    struct media_entity entity;
    const struct v4l2_subdev_ops *ops;
    struct v4l2_subdev_pad_config *config;
    char name[V4L2_SUBDEV_NAME_SIZE];
    // ...
};

每个 subdev 有自己的 pad(端口)。pad 分两种:

  • 源端(source pad):数据从这里出去。
  • 接收端(sink pad):数据从这里进来。

比如 sensor 只有一个 source pad,ISP 有一个 sink pad 和一个 source pad。

2.3.2 pipeline 的建立

pipeline 是通过 media controller 框架来建立的。你想想看,应用层要配置一条通路,得先知道 sensor 连到哪个 ISP,ISP 又连到哪个视频采集控制器。

在 R-Car 平台上,我通常这样建立 pipeline:

  1. 枚举所有 subdev,找到 sensor 和视频采集控制器。
  2. 通过 media controller 的 ioctl(MEDIA_IOC_ENUM_LINKS)查看连接关系。
  3. 调用 MEDIA_IOC_SETUP_LINK 建立连接。
  4. 最后通过 VIDIOC_SUBDEV_S_FMT 配置每个 subdev 的格式。
注意:pipeline 的格式必须一致。比如 sensor 输出 1920x1080,ISP 的输入和输出也必须是 1920x1080。我曾经遇到一个问题:sensor 输出 10-bit RAW,但 ISP 配置成了 8-bit,结果图像全是噪点。查了两天才发现是格式不匹配。

2.4 小结

这一章我们聊了 V4L2 的三个核心概念:

  • video_device:用户空间看到的 /dev/videoX。
  • v4l2_device:整个硬件设备的抽象。
  • subdev 与 pipeline:模块化硬件和它们之间的数据通路。