V4L2 框架(一):理清 V4L2 的分层与数据流
V4L2 的接口看起来是散的几十个 ioctl每个都配一个形状不同的结构体命名还很相似。但实际上它们都挂在同一条数据通路上各自只负责一个环节。这个系列按这条通路讲一遍从应用层的调用一直讲到硬件。一、V4L2 是什么V4L2Video for Linux 2是 Linux 内核为视频类设备定义的统一接口。它并不是凭空出现的。早期的 V4L 只覆盖了当时的模拟采集卡和摄像头接口设计上留了不少历史包袱。V4L2 从 2.5 开发版内核开始引入重新设计了设备抽象、格式描述和缓冲区机制。到 2.6.38 前后内核里的 V4L1 兼容层被彻底移除V4L2 成为唯一接口一直沿用至今。视频类设备的范围比通常理解的宽类别典型设备采集captureUSB 摄像头、HDMI 采集卡、MIPI 摄像头模组输出output视频输出接口、虚拟显示设备编解码M2M硬件 H.264 / H.265 编码器、JPEG 编解码器元数据 / 调节ISP 参数、调谐器、Radio这些设备的共同点是都在搬运画面数据。V4L2 就是为这件事统一了接口。设备节点的几种类型应用访问 V4L2 设备靠的是/dev下的字符设备节点。它们不止一种节点用途/dev/videoN主视频节点采集、输出、编解码都走这里/dev/v4l-subdevN子设备节点可以直接用 ioctl 读写 sensor、ISP 的寄存器级参数/dev/mediaNMedia Controller 节点用于查看和配置 pipeline 拓扑/dev/vbiNVertical Blanking Interval模拟视频消隐期数据字幕、时间码/dev/radioN收音机设备日常开发打交道最多的是/dev/videoN。但如果你要调 sensor 的曝光、增益或者排查为什么取不到帧往往会用到/dev/mediaN和/dev/v4l-subdevN这两个节点后面会单独讲。要注意的是一个硬件设备不一定只对应一个节点。比如一个 ISP可能同时注册出/dev/video0采集和/dev/video1M2M 处理两者的能力完全不同。所以拿到一个节点编号之后第一件事永远是先查它的能力而不是假设它是干什么的。二、它解决什么问题在没有 V4L2 的年代每个摄像头厂商各写一套驱动各自定义自己的 ioctl。应用换一个设备就要重写一遍。V4L2 做的是定标准标准化对象具体表现设备接口所有设备统一暴露为/dev/videoN用同一套 ioctl 操作数据格式分辨率、像素格式、缓冲区用统一的结构体描述缓冲机制内核提供通用的缓冲区管理框架 videobuf2驱动模型驱动只实现一组回调通用逻辑由框架承担如果从设计角度看得更抽象一点V4L2 实际上提供了六种基础抽象理解这六种抽象基本就掌握了 V4L2 的骨架抽象对应的机制解决的问题设备/dev/videoN ioctl应用如何找到并操作设备格式v4l2_format fourcc如何描述一帧图像的尺寸和排布缓冲videobuf2 队列内核与用户之间如何移交画面内存控制v4l2_ctrl_handler如何查询和设置参数亮度、曝光、帧率事件v4l2_event设备状态变化如何通知应用器件v4l2_subdev一条通路上有多个器件时如何分别配置后面每一篇本质上都是在展开其中的一种或几种抽象。三、从光子到内存这是本系列最重要的一张图后面所有代码都挂在这条线上。光子 ↓ 光学镜头 Sensor 感光 → 输出 RAW Bayer 数据 ↓ MIPI CSI-2 差分串行总线 SoC 的 CSI 接收控制器 ↓ ISP图像信号处理去马赛克 / 白平衡 / 降噪 → 转成 YUV ↓ DMA 直接搬进内存CPU 不参与 内存里的一块 buffer也就是一帧图像 ↓ mmap 映射 应用通过指针读到这一帧每一段在做什么Sensor把光信号转成电信号输出的是 RAW Bayer 数据——每个像素只有 R、G、B 三个通道中的一个还不算一张彩色照片。Sensor 本身不参与数据搬运它的作用是感光和输出。CSI-2 接收控制器Sensor 到 SoC 之间走的是 MIPI CSI-2一种高速差分串行总线。CSI 控制器负责把串行数据解出来是 SoC 侧的接收端。ISP图像信号处理。RAW 数据要经过一串处理才能变成人眼可看的画面——去马赛克把 Bayer 单通道数据插值成完整 RGB、白平衡、降噪、Gamma 校正、色彩矩阵。处理后通常输出 YUV 格式。DMA把 ISP 的输出直接写进内存。这一段是整个通路里唯一涉及落地的环节也是 CPU 唯一不需要参与的地方。控制面与数据面上面这条是数据面的通路全程由硬件自动完成。但硬件不是自己知道该怎么干的它需要被配置。数据面Sensor → CSI → ISP → DMA → 内存 硬件自动CPU 不参与 控制面CPU → I2C 配置 Sensor 配置 sensor 寄存器 CPU → 寄存器配置 CSI / ISP / DMA CPU → ioctl 接收应用下发的参数把这两条路径分开是理解 pipeline 的关键。你在用户空间调一个S_FMT走的是控制面它影响的是硬件后面怎么搬数据而画面数据本身永远走数据面从不上经过 ioctl。这个区分在后面会反复用到。对于应用开发者来说/dev/videoN就是这条 pipeline 的出口——你不需要关心前面几段各自怎么工作只需要知道最后有一帧帧的数据会出现在内存里。四、分层结构V4L2 不是一个单一模块是叠起来的几层。┌───────────────────────────────────────────────┐ │ ① 应用层 │ │ v4l2-ctl / GStreamer / 你自己的程序 │ └───────────────────┬───────────────────────────┘ │ ioctl / mmap / poll ┌───────────────────▼───────────────────────────┐ │ ② V4L2 core │ │ 注册设备节点、分发 ioctl │ ├───────────────────────────────────────────────┤ │ ③ videobuf2vb2缓冲区层 │ │ 队列管理、三种内存模型、buffer 状态机 │ ├───────────────────────────────────────────────┤ │ ④ 驱动层 │ │ v4l2_subdevsensor / ISP │ │ 平台驱动CSI / DMA对外是 /dev/videoN │ └───────────────────┬───────────────────────────┘ │ ┌───────────────────▼───────────────────────────┐ │ ⑤ 硬件Sensor → MIPI CSI → ISP → DMA → 内存 │ └───────────────────────────────────────────────┘应用层你的程序或者 v4l2-ctl 这类工具。它和内核之间只有三种交互ioctl、mmap、poll。ioctl 下发命令和参数mmap 把内核里的缓冲区映射到本进程poll 用于等待帧就绪。V4L2 core负责把设备注册成/dev/videoN并在收到 ioctl 时分发到对应的处理者。它本身不实现业务逻辑只做分发。具体来说core 通过video_device结构体描述一个节点通过v4l2_ioctl_ops这张函数指针表把命令路由到实现方。有些命令由 core 自己处理比如QUERYCAP有些转给 vb2有些转给驱动。videobuf2通常简称 vb2缓冲区管理框架。这是 V4L2 最核心的机制也是本系列的重点。它把管理 buffer这件事从各个驱动里抽出来统一实现驱动只需要回答框架几个问题要几块 buffer、每块多大、什么时候启动流。在 vb2 出现之前每家驱动都要自己写一遍这套逻辑重复且容易出错。驱动层分两类角色。一类是子设备v4l2_subdev代表链路上的一个独立器件比如 sensor、ISP它们不负责取流只提供配置能力通过v4l2_subdev_ops暴露一组回调。另一类是平台驱动真正对外表现为/dev/videoN负责启动流和搬运数据。硬件层不涉及代码但决定设备的规格上限比如最高支持 4K 还是 2K。各层的关键结构体读内核源码时遇到的第一个障碍就是结构体太多。先建立一张对照表后面再看具体的代码会轻松很多所属层结构体作用corevideo_device一个/dev/videoN节点的描述corev4l2_ioctl_opsioctl 分发表函数指针集合vb2vb2_queue一条缓冲区队列vb2vb2_buffer队列里的一块 buffervb2vb2_ops驱动需要实现的回调集合驱动v4l2_subdev一个子器件的描述驱动v4l2_subdev_ops子设备的回调集合Media Controllermedia_entity/media_pad/media_link拓扑中的实体、端口和连线五、一次完整操作的流程速览在进入具体命令之前先看一遍用户空间操作 V4L2 设备的完整流程。这个顺序基本固定后面所有代码都是它的展开步骤命令做什么1open打开/dev/videoN得到文件描述符2VIDIOC_QUERYCAP查询设备能力确认它能干什么3VIDIOC_ENUM_FMT枚举设备支持的像素格式4VIDIOC_S_FMT协商并设置格式5VIDIOC_REQBUFS申请 N 块缓冲区6VIDIOC_QUERYBUFmmap查询缓冲区位置并映射到本进程7VIDIOC_QBUFVIDIOC_STREAMON缓冲区入队启动数据流8pollVIDIOC_DQBUF/VIDIOC_QBUF循环取帧、处理、归还循环往复第 1 到 4 步是配置阶段只涉及参数协商不产生任何图像数据。第 5 到 8 步是运行阶段从这里开始才会有真正的帧产生。这个划分很重要配置阶段可以反复调整代价很低一旦STREAMON硬件就按配置好的参数连续工作此时再想改格式就必须先停流。六、术语与边界以下术语后面每篇都会出现先建立印象。术语含义设备节点/dev/video0所有操作的句柄ioctl与 V4L2 交互的主要方式命令形式统一为VIDIOC_xxxfourcc四个 ASCII 字符打包成的 32 位整数用于标识像素格式如YUYV、NV12buffer存放一帧图像的内存由内核分配应用通过 mmap 映射访问pipeline一条完整的数据通路如 Sensor → CSI → ISP → 内存子设备pipeline 上的单个器件彼此通过 pad 相连M2MMemory-to-Memory输入输出都是内存的编解码类设备单平面 / 多平面一帧图像占用一个还是多个内存平面YUV 的 Y、UV 分量可以分开存放另外有几个职责边界需要提前划清否则后面容易混淆事项实际归属器件之间如何连接、格式如何逐级传递Media Controller (MC)内存如何在设备之间共享DMA-BUF画面如何显示DRM / KMS简单说Media Controller 管拓扑V4L2 管数据搬运。七、系列安排与环境准备整个系列共 7 篇分三段先用起来再理解原理最后进入进阶内容。篇号内容难度1大图景V4L2 到底是什么入门2打开设备与格式协商入门3缓冲与取流主链路入门4V4L2 core 与 videobuf2进阶5v4l2_subdev 与 Media Controller进阶6控制框架与事件机制进阶7M2M、电源管理、调试与排错进阶建议先看第 2、3 篇把用户空间这一层跑通能写出一个完整的采集程序并且清楚每一步在做什么。有这个基础再看第 4 篇之后的内核部分会容易很多。直接读 vb2 源码难度不小。环境准备# 安装 V4L2 用户空间工具集sudoaptinstallv4l-utils# 查看设备ls/dev/video*后续调试会经常用到这几个命令v4l2-ctl--all# 打印设备全部信息v4l2-ctl --list-formats# 列出支持的像素格式v4l2-ctl --list-devices# 列出系统里所有 V4L2 设备没有摄像头的话不必额外购买硬件。内核自带一个虚拟视频测试驱动vivid可以同时模拟采集、输出、M2M 和多平面设备sudomodprobe vividn_devs1ls/dev/video*它会创建一组/dev/videoN支持多种格式和分辨率本系列后面的代码用它都能跑通。下一篇这一篇给出了整体结构还没有代码。下一篇进入配置阶段也就是流程里的第 1 到 4 步。主要讲四件事open打开设备节点以及O_RDWR、O_NONBLOCK这两个标志会影响到后面的哪些行为VIDIOC_QUERYCAP查询设备能力其中capabilities字段该怎么读VIDIOC_ENUM_FMT与VIDIOC_ENUM_FRAMESIZES枚举设备支持的像素格式和分辨率VIDIOC_G_FMT与VIDIOC_S_FMT读取和设置格式这四个命令看起来各管一摊实际上共用同一套操作模式。下一篇会先把这套模式讲清楚再逐个展开每一步在做什么、结构体里哪些字段要填、哪些字段是驱动回填的。文末会给出一个完整可编译运行的程序把配置阶段整条走一遍。下一篇《V4L2 框架讲透二打开设备与格式协商》