☰
V4L2视频采集流程复习笔记
2026/10/22 2:29:07 网站建设 项目流程

【Linux V4L2】视频采集流程全景笔记(ioctl 命令、mmap、缓冲区轮转、DVP vs MIPI)

本文整理自课堂板书,主题是Linux 视频采集子系统 V4L2(Video for Linux 2):从 OV5640 摄像头怎么接到 SoC,到应用程序用九步 ioctl 流程拿到一帧图像,再到 mmap 零拷贝和缓冲区轮转的设计思想。适合复习摄像头驱动/应用开发,也可作为 V4L2 入门路线图。

项目说明
摄像头模组OV5640(500W 像素,支持 DVP / MIPI 两种接口)
平台IMX6ULL(CSI 接口),Linux 4.1.15
涉及层次应用层 ioctl / 内核 V4L2 框架 / 传感器驱动
核心头文件linux/videodev2.h(所有结构体与命令宏都在这里)

目录

  • 一、V4L2 是什么:先看清整条数据通路
  • 二、采集九步流程总览
  • 三、逐步拆解(含结构体)
  • 四、缓冲区轮转:为什么是 4 块 buffer
  • 五、mmap 内存映射详解
  • 六、完整示例代码
  • 七、像素格式怎么选:YUV / RGB / MJPEG
  • 八、调试与 FAQ
  • 九、复习清单

一、V4L2 是什么:先看清整条数据通路

V4L2 是内核的视频采集框架:向上给应用提供统一的/dev/videoN接口(open/read/ioctl),向下管理各种摄像头驱动。先看硬件通路,再谈软件流程。

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=images_v4l2%2Ffig1_camera_hw.png&pos_id=img-BkEHYoXj-1792511975105

关键认知:摄像头和 SoC 之间是"两条路"——

  1. 控制路(慢):I2C/SCCB 总线,用来读写传感器的寄存器(配置分辨率、增益、曝光等),数据量小
  2. 数据路(快):图像像素流,走 DVP 并行接口或 MIPI CSI 串行接口,由 SoC 的 CSI 控制器接进内存

1.1 DVP vs MIPI CSI 对比

对比项DVP(并行)MIPI CSI-2(串行)
信号线PCLK + HREF/VSYNC + D0~D7(多根)CLK± / D0± / D1±(差分对,线少)
抗干扰一般强(差分传输)
速率低(几十 MHz)高(Gbps 级)
典型场景低分辨率、老平台、学习板高分辨率、手机/产品
IMX6ULLCSI 控制器支持部分型号支持

记忆点:MIPI = 差分 = 高速;DVP 引脚多、速率低但简单直观,教学板常用。

模组上还有一根MCLK:SoC 提供给传感器的基准时钟,没有它传感器不工作——排查"摄像头点不亮"时先量它。

1.2 软件框架的三块基石

组成说明
videodev2.hV4L2 的"字典":所有数据结构(v4l2_format、v4l2_buffer…)和命令宏(VIDIOC_xxx)都在这个头文件里,复习时对着它看最有效
/dev/videoN每个采集设备一个节点(/dev/video0…),应用层所有操作都围绕它
ioctl万能接口V4L2没有独立的 read/write 风格 API,一切操作(查询、设置格式、申请缓冲、启停流)都通过ioctl(fd, VIDIOC_XXX, &arg)完成,这是它和我们之前写的字符设备驱动最大的差异

二、采集九步流程总览

步骤ioctl 命令作用关键结构体
1open("/dev/video0", O_RDWR)打开设备节点—
2VIDIOC_QUERYCAP查询设备能力(是否支持采集流)v4l2_capability
3VIDIOC_S_FMT(G_FMT/ENUM_FMT)设置/读取/枚举图像格式(宽高+像素格式)v4l2_format
4VIDIOC_REQBUFS向内核申请 N 块缓冲区(MMAP 方式)v4l2_requestbuffers
5VIDIOC_QUERYBUF+mmap逐块查询缓冲区信息,映射到用户空间v4l2_buffer
6VIDIOC_QBUF把所有空 buffer入队交给驱动v4l2_buffer
7VIDIOC_STREAMON启动采集流,传感器数据开始填充 bufferv4l2_buf_type
8VIDIOC_DQBUF→ 处理 →VIDIOC_QBUF循环:出队一帧 → 处理 → 还回队列v4l2_buffer
9VIDIOC_STREAMOFF停止采集,释放资源(munmap/close)—

一句话框架:前面 1~7 步是"布置现场",第 8 步是"循环干活",第 9 步"收拾走人"。所有命令都以VIDIOC_开头,正是Video IO Control的缩写。


三、逐步拆解(含结构体)

3.1 打开设备 + 查询能力

#include<linux/videodev2.h>intfd=open("/dev/video0",O_RDWR);structv4l2_capabilitycap;ioctl(fd,VIDIOC_QUERYCAP,&cap);printf("driver=%s card=%s\n",cap.driver,cap.card);/* 必做的合理性检查:是否支持视频采集 */if(!(cap.capabilities&V4L2_CAP_VIDEO_CAPTURE))fprintf(stderr,"不是采集设备\n");

3.2 设置图像格式(VIDIOC_S_FMT)

structv4l2_formatfmt={0};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);
v4l2_pix_format常用字段含义
width / height分辨率
pixelformat像素格式四字符码(V4L2_PIX_FMT_YUYV、V4L2_PIX_FMT_MJPEG、V4L2_PIX_FMT_RGB565…)
bytesperline一行字节数(内核按格式算好回填)
sizeimage一帧总字节数(申请 buffer 大小的依据)

想知道传感器支持哪些格式,用VIDIOC_ENUM_FMT枚举一遍即可。

3.3 申请缓冲区(VIDIOC_REQBUFS)

structv4l2_requestbuffersreq={0};req.count=4;/* 一般 3~4 块,见第四节 */req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;req.memory=V4L2_MEMORY_MMAP;/* 内核分配,应用映射 */ioctl(fd,VIDIOC_REQBUFS,&req);

3.4 查询并映射(VIDIOC_QUERYBUF + mmap)

structbuffer{void*start;size_tlength;}bufs[4];for(inti=0;i<4;i++){structv4l2_bufferbuf={0};buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory=V4L2_MEMORY_MMAP;buf.index=i;/* 第 i 块 */ioctl(fd,VIDIOC_QUERYBUF,&buf);/* 查询长度和偏移 */bufs[i].length=buf.length;bufs[i].start=mmap(NULL,buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,buf.m.offset);}

3.5 入队 + 启动流(VIDIOC_QBUF / STREAMON)

for(inti=0;i<4;i++){structv4l2_bufferbuf={0};buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory=V4L2_MEMORY_MMAP;buf.index=i;ioctl(fd,VIDIOC_QBUF,&buf);/* 全部交给驱动 */}enumv4l2_buf_typetype=V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(fd,VIDIOC_STREAMON,&type);/* 开始采集 */

3.6 采集循环(DQBUF → 处理 → QBUF)

for(;;){structv4l2_bufferbuf={0};buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory=V4L2_MEMORY_MMAP;ioctl(fd,VIDIOC_DQBUF,&buf);/* 阻塞直到一帧就绪 *//* buf.index 指明是哪块 buffer,数据就在 bufs[buf.index].start */process(bufs[buf.index].start,buf.bytesused);ioctl(fd,VIDIOC_QBUF,&buf);/* 处理完立刻还回去 */}

最容易忘的一步:处理完必须QBUF还回队列。还回去的 buffer 会被驱动继续用来装下一帧——不还,几帧之后队列就空了,DQBUF永久阻塞。


四、缓冲区轮转:为什么是 4 块 buffer

V4L2 的流式采集本质是一个生产者-消费者环形队列:

  • 生产者:CSI 控制器/驱动,把传感器数据 DMA 写进"已入队"的空 buffer
  • 消费者:应用程序,DQBUF取走满 buffer 处理,处理完QBUF归还
  • 为什么申请 3~4 块:驱动写 buffer A 的同时,应用还在处理 buffer B,读写并行,一帧都不丢;只有 1 块的话,处理期间新数据无处可写,只能丢帧

板书里的记忆图:入队 → buffer1 第一帧图像;出队处理 → buffer2 第二帧图像……四块 buffer 轮流值班,循环往复。

v4l2_buffer出队后最常用的字段:

字段含义
index是哪块 buffer(用它去自己的bufs[]数组取数据指针)
bytesused这一帧实际有效字节数(处理时别超过它)
flags状态标志(V4L2_BUF_FLAG_DONE表示已填好数据)
timestamp帧时间戳

五、mmap 内存映射详解

为什么要 mmap:普通read()要把数据从内核拷到用户缓冲区,一帧 640×480 YUV 就是 600KB,30fps 下每秒白拷 18MB。mmap 把内核 buffer 直接映射进应用的虚拟地址空间,两边看到的是同一块物理内存,零拷贝。

函数原型:

void*mmap(void*addr,size_tlength,intprot,intflags,intfd,off_toffset);
参数传什么说明
addrNULL映射起始地址,一般交由内核自动选择
lengthbuf.length映射长度(QUERYBUF 查回来的)
protPROT_READ | PROT_WRITE映射区保护方式(读/写)
flagsMAP_SHARED共享映射(写回可见);MAP_PRIVATE为私有写时复制,采集场景不用
fd设备描述符要映射的文件/设备
offsetbuf.m.offset被映射对象内的偏移量,V4L2 里来自 QUERYBUF
  • 返回值:成功返回映射区首地址(应用直接拿它当图像数据用),失败返回MAP_FAILED(即(void *)-1,注意不是 NULL)
  • 用完记得munmap(p, length)逐块解除映射

六、完整示例代码

把九步串起来,就是一段可以直接编译跑的采集骨架:

#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<unistd.h>#include<string.h>#include<sys/ioctl.h>#include<sys/mman.h>#include<linux/videodev2.h>#defineBUF_CNT4#defineDEV"/dev/video0"structbuffer{void*start;size_tlength;};staticstructbufferbufs[BUF_CNT];staticvoidxioctl(intfd,unsignedlongcmd,void*arg){if(ioctl(fd,cmd,arg)<0){perror("ioctl");exit(1);}}intmain(void){/* 1. 打开设备 */intfd=open(DEV,O_RDWR);/* 2. 查询能力 */structv4l2_capabilitycap;xioctl(fd,VIDIOC_QUERYCAP,&cap);printf("driver=%s card=%s\n",cap.driver,cap.card);/* 3. 设置格式 */structv4l2_formatfmt={0};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;xioctl(fd,VIDIOC_S_FMT,&fmt);printf("sizeimage=%u\n",fmt.fmt.pix.sizeimage);/* 4. 申请缓冲区 */structv4l2_requestbuffersreq={0};req.count=BUF_CNT;req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;req.memory=V4L2_MEMORY_MMAP;xioctl(fd,VIDIOC_REQBUFS,&req);/* 5+6. 查询、映射、全部入队 */for(inti=0;i<BUF_CNT;i++){structv4l2_bufferb={0};b.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;b.memory=V4L2_MEMORY_MMAP;b.index=i;xioctl(fd,VIDIOC_QUERYBUF,&b);bufs[i].length=b.length;bufs[i].start=mmap(NULL,b.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,b.m.offset);if(bufs[i].start==MAP_FAILED){perror("mmap");return1;}xioctl(fd,VIDIOC_QBUF,&b);}/* 7. 启动采集 */enumv4l2_buf_typetype=V4L2_BUF_TYPE_VIDEO_CAPTURE;xioctl(fd,VIDIOC_STREAMON,&type);/* 8. 采集循环:DQBUF -> 处理 -> QBUF */for(intn=0;n<100;n++){structv4l2_bufferb={0};b.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;b.memory=V4L2_MEMORY_MMAP;xioctl(fd,VIDIOC_DQBUF,&b);printf("frame %d: index=%u bytes=%u\n",n,b.index,b.bytesused);/* process(bufs[b.index].start, b.bytesused); 显示/编码/保存 */xioctl(fd,VIDIOC_QBUF,&b);/* 必须还回去 */}/* 9. 停止并释放 */xioctl(fd,VIDIOC_STREAMOFF,&type);for(inti=0;i<BUF_CNT;i++)munmap(bufs[i].start,bufs[i].length);close(fd);return0;}

编译运行:

arm-linux-gnueabihf-gcc capture.c-ocapture ./capture

七、像素格式怎么选:YUV / RGB / MJPEG

格式四字符码大小(一帧 640×480)特点适用
YUYV(YUV422)V4L2_PIX_FMT_YUYV~600 KB传感器原生输出,每像素 2 字节要自己做图像处理/算法
RGB565 / RGB888V4L2_PIX_FMT_RGB565等~600 KB / ~900 KB可直接刷到 LCD framebuffer直接显示
MJPEGV4L2_PIX_FMT_MJPEG几十 KB(压缩)体积小,省带宽,需解码网络传输、高帧率预览
  • 板书结论:一般选 YUV(传感器原生、信息全),需要显示再转 RGB888
  • YUV422 交错存储格式Y0 U0 Y1 V1:两个 Y 共享一组 UV,人眼对亮度(Y)敏感、对色度(UV)不敏感,所以色度可以减半采样

八、调试与 FAQ

现象可能原因排查
/dev/video0不存在驱动没编/没加载,或摄像头没接`dmesg
VIDIOC_QUERYCAP失败打开的不是 V4L2 设备v4l2-ctl --all看设备信息
S_FMT后宽高变了传感器不支持该分辨率,内核自动就近调整以G_FMT读回来的为准
DQBUF一直阻塞没入队足够的 buffer / 没 STREAMON / 数据线没接好检查第 6、7 步顺序;量 MCLK 和 PCLK
图像全绿/全灰像素格式与实际不符(如 YUYV 当 RGB 显示)用ENUM_FMT确认格式;YUV 转 RGB 再显示
图像撕裂处理太久还 buffer 不及时把处理挪到另一线程;增加 buffer 数
花屏/错位bytesperline没按 stride 处理一行不是 width×2 就可能有对齐 padding
没有摄像头想学流程内核自带vivid虚拟摄像头驱动加载 vivid 后同样有/dev/video0,流程完全一致

无驱动的快速验证工具:

v4l2-ctl --list-devices# 列出采集设备v4l2-ctl-d/dev/video0--all# 查看能力/格式v4l2-ctl --list-formats-ext-d/dev/video0# 枚举支持的格式和分辨率dmesg|tail# 驱动加载日志

九、复习清单

  • 能画出摄像头到 SoC 的两条路:控制走 I2C/SCCB,数据走 DVP/MIPI,并解释 MCLK 的作用
  • 能对比 DVP 和 MIPI CSI(线数、差分、速率、场景)
  • 能说出 V4L2 的三大基石:videodev2.h、/dev/videoN、ioctl 万能接口
  • 能默写采集九步:open → QUERYCAP → S_FMT → REQBUFS → QUERYBUF+mmap → QBUF → STREAMON → 循环(DQBUF→处理→QBUF) → STREAMOFF
  • 能解释为什么申请 3~4 块 buffer(读写并行不丢帧)
  • 能默写 mmap 六个参数及返回值,说清"零拷贝"省在哪
  • 知道v4l2_buffer的index / bytesused怎么用
  • 知道 YUYV 的存储格式和 YUV→RGB 为什么要转
  • 知道忘写 QBUF 会发生什么(队列耗尽、DQBUF 永久阻塞)
  • 会用v4l2-ctl在写代码前验证设备和格式

写在最后:V4L2 的九步流程看似命令很多,其实只有三件事——谈好格式(S_FMT)、摆好缓冲区(REQBUFS/mmap/QBUF)、开始流水线(STREAMON + 循环)。把它和之前写的字符设备驱动对照着看:ioctl 就是file_operations.unlocked_ioctl的具体应用,buffer 队列就是内核与应用之间的生产者-消费者模型——新框架只是老知识的重新组合。

标签:LinuxV4L2摄像头驱动开发嵌入式IMX6ULL

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询