第4章:V4L2框架入门:Video for Linux 2,设备节点与ioctl
好,咱们今天聊聊V4L2。说实话,这个框架在Linux多媒体子系统里,地位就跟文件系统里的VFS差不多。你写Camera驱动,绕不开它。
V4L2的全称是Video for Linux 2,是第二代视频处理框架。第一代V4L太老了,基本没人用了。V4L2从Linux 2.5内核开始引入,一直用到现在,生命力极强。
我个人习惯把V4L2理解成「摄像头领域的统一接口规范」。不管你是USB摄像头、MIPI CSI摄像头,还是PCIe采集卡,到了应用层,操作方式都一样——打开设备节点,发ioctl命令,拿数据。
4.1 设备节点:用户态看到的「摄像头」
你在Linux系统里插上一个摄像头,会看到什么?
ls -l /dev/video*
crw-rw---- 1 root video 81, 0 Jan 1 00:00 /dev/video0
crw-rw---- 1 root video 81, 1 Jan 1 00:00 /dev/video1
对,就是/dev/videoX。主设备号81,次设备号从0开始。
这里有个坑,我刚开始做驱动时踩过——不是每个video节点都能采集图像。有些节点是metadata节点,有些是输出节点。怎么区分?看功能。
| 设备节点类型 | 主功能 | 典型用途 |
|---|---|---|
| /dev/videoX | 视频采集/输出 | 摄像头数据流、显示输出 |
| /dev/vbiX | VBI数据 | 电视卡图文信息 |
| /dev/radioX | 收音机 | FM调频 |
| /dev/mediaX | Media Controller | 拓扑管理、路由配置 |
嗯,这里要注意:一个物理摄像头可能对应多个video节点。比如有些SoC的ISP会分出raw节点和processed节点。你选错了,拿到的数据就不对。
4.2 ioctl:驱动与应用的「对话协议」
设备节点只是个入口。真正干活的是ioctl。你可以把ioctl理解成「驱动暴露给应用层的API集合」。
V4L2定义了大量的ioctl命令,我挑几个最常用的说说:
- VIDIOC_QUERYCAP:查询设备能力。你总得知道这摄像头能干啥吧?
- VIDIOC_ENUM_FMT:枚举支持的格式。RGB?YUV?MJPEG?
- VIDIOC_S_FMT:设置格式。分辨率、像素格式、帧率。
- VIDIOC_REQBUFS:申请缓冲区。这是数据流的起点。
- VIDIOC_QBUF / VIDIOC_DQBUF:入队/出队缓冲区。数据流转起来了。
- VIDIOC_STREAMON / STREAMOFF:启动/停止数据流。
你想想看,整个Camera数据通路,说白了就是围绕这几个ioctl在转。
4.3 一个典型的ioctl调用流程
我写个最简单的示例,帮你理解这个流程:
#include <sys/ioctl.h>
#include <linux/videodev2.h>
int fd = open("/dev/video0", O_RDWR);
if (fd < 0) {
perror("open");
return -1;
}
// 1. 查询能力
struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
printf("Driver: %s\n", cap.driver);
// 2. 设置格式
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
ioctl(fd, VIDIOC_S_FMT, &fmt);
// 3. 申请缓冲区
struct v4l2_requestbuffers req;
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &req);
// 4. 启动流
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type);
// 5. 循环采集
for (int i = 0; i < 100; i++) {
struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_DQBUF, &buf); // 取出填满数据的buffer
// 处理数据...
ioctl(fd, VIDIOC_QBUF, &buf); // 放回去继续用
}
// 6. 停止
ioctl(fd, VIDIOC_STREAMOFF, &type);
close(fd);
这个流程,我在项目里写过不下几十遍。说白了就是三板斧:查能力→设格式→循环取数据。
4.4 缓冲区管理:MMAP vs USERPTR vs DMABUF
V4L2支持三种缓冲区管理方式。我分别说说:
- MMAP(内存映射):驱动分配内存,应用通过mmap映射到用户空间。最简单,最常用。适合大多数场景。
- USERPTR(用户指针):应用自己分配内存,把指针传给驱动。适合需要自己管理内存的场景,比如用GPU内存。
- DMABUF(DMA缓冲区):通过dma-buf机制共享缓冲区。零拷贝,性能最好。适合ISP、GPU、Display之间的数据流转。
我个人建议:新手先用MMAP。等你对整个数据流熟悉了,再考虑DMABUF。为什么?因为DMABUF涉及到的同步、生命周期管理,坑比较多。
4.5 驱动侧:ioctl的实现
从驱动角度看,V4L2框架帮你做了大部分脏活。你只需要实现几个回调函数:
static const struct v4l2_ioctl_ops my_ioctl_ops = {
.vidioc_querycap = my_querycap,
.vidioc_enum_fmt_vid_cap = my_enum_fmt,
.vidioc_s_fmt_vid_cap = my_s_fmt,
.vidioc_reqbufs = my_reqbufs,
.vidioc_qbuf = my_qbuf,
.vidioc_dqbuf = my_dqbuf,
.vidioc_streamon = my_streamon,
.vidioc_streamoff = my_streamoff,
};
然后注册到video_device结构体里。框架会自动帮你处理ioctl的分发、参数校验、权限检查。
嗯,这里有个细节:V4L2框架会帮你做很多默认处理。比如你不实现ENUM_FMT,框架会返回错误。但如果你不实现S_FMT,框架会用默认值。这有时候会掩盖问题——我见过一个驱动,S_FMT实现有bug,但应用层一直用默认格式,所以没发现。直到换了分辨率,才暴露出来。
4.6 调试利器:v4l2-ctl和media-ctl
最后,我强烈建议你学会用这两个工具:
- v4l2-ctl:操作V4L2设备。可以查询能力、设置格式、抓图。
- media-ctl:操作Media Controller拓扑。可以查看pipeline、设置路由。
举个例子,调试时我经常这么干:
# 查看设备信息
v4l2-ctl -d /dev/video0 --all
# 抓一帧数据
v4l2-ctl -d /dev/video0 --set-fmt-video=width=640,height=480,pixelformat=YUYV --stream-mmap --stream-to=frame.raw --stream-count=1
# 查看media拓扑
media-ctl -d /dev/media0 --print-topology
这些命令,能帮你快速定位问题。是驱动没配置对?还是应用层参数错了?一测便知。
下一章,咱们深入聊聊V4L2的缓冲区管理机制。这个搞明白了,数据通路就通了一半。