V4L2介绍
Linux摄像头(v4l2应用)——在LCD上实时显示摄像头采集JPEG数据 - 知乎 (zhihu.com)
FFmpeg解码USB摄像头MJPEG输出 - 相对维度 - 博客园 (cnblogs.com)
对于视频相关参考:
开启内核支持
Linxu内核——内核添加V4L2_v4l2安装-CSDN博客
Device Driver
Multimedia support
选中Cameras and video grabber
返回
Media core support
选中vid4Linux options
返回
Device Drivers --->
Multimedia support --->
<*> Multimedia support [Enable this option]
[*] Cameras/video grabbers support
[*] Media USB Adapters --->
<*> V4L USB devices [Enable this option if you have USB V4L devices]
[*] Media PCI Adapters --->
<*> V4L PCI devices [Enable this option if you have PCI V4L devices]
<*> V4L platform devices [Enable this option if you have platform V4L devices]
<*> V4L mem2mem devices [Enable this option for memory-to-memory V4L devices]
[*] V4L2 sub-device userspace API
[*] V4L2 sub-device internal API
[*] Enable advanced debug functionality on V4L2 devices2.1 V4L2简介
Video For Linux Two(Video4Linux2)简称为V4L2,是linux内核中自带的采集图片、视频、音频的一套API接口,它的特点如下:。
1)是linux内核自带的,所以可以在类linux系统上直接使用;
2)主流的视频设备几乎都适配了这套框架;
3)开发人员可以很方便的使用这一套api操作不同的视频设备,免去了学习视频设备驱动的成本。
2.2 V4L2接口查询相机功能
相机接入系统后,开发者最关心的是这台相机是否有想要的视频功能、音频功能、无线功能等等。那么这时除了看说明书问厂商以外,开发者还能够从V4L2接口中获得相机的功能信息。
1)打开系统下可以访问的摄像头,使用文件IO函数open()打开设备:
int fd = open("/dev/video0", O_RDWR);
2)获取摄像头属性值,利用标准IO函数ioctl()获取摄像头属性结构体:
struct v4l2_capability cap = {0}; //存储设备信息的结构体 v4l2_capability.
ioctl(fd, VIDIOC_QUERYCAP, &cap); //fd:打开设备返回的文件句柄.
3)打印结构体成员,也就是设备功能属性值:
printf("cap.driver = %s \n",cap.driver); //驱动名,通常是uvcvideo
printf("cap.card = %s \n",cap.card); // Device名,厂商定制
printf("cap.bus_info = %s \n",cap.bus_info); //总线信息
printf("cap.version = %d \n",cap.version); //内核版本
printf("cap.capabilities = %x \n",cap.capabilities); //所有能力集
printf("cap.device_caps = %x \n",cap.device_caps); //开放能力集
printf("cap.reserved = %x \n",cap.reserved); //保留
注意: 这里的宏VIDIOC_QUERYCAP以及结构体v4l2_capability全部来源于videodev2.h这个头文件,也就是V4L2接口的一部分。
2.3 相机的能力集capabilities
结构体v4l2_capability描述的是相机设备的功能。其中最重要的两个成员就是cap.capabilities和cap.device_caps,它们描述的是相机拥有的能力和人为干预的能力,通过追踪头文件定义可获得详细描述如下:
/* Values for 'capabilities' field */
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 //是视频捕获设备.
#define V4L2_CAP_VIDEO_OUTPUT 0x00000002 //是视频输出设备.
#define V4L2_CAP_VIDEO_OVERLAY 0x00000004 //可以做视频叠加.
#define V4L2_CAP_VBI_CAPTURE 0x00000010 //原始VBI捕获设备.
#define V4L2_CAP_VBI_OUTPUT 0x00000020 //原始VBI输出设备.
#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 //切片VBI捕获设备.
#define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 //切片VBI输出设备.
#define V4L2_CAP_HW_FREQ_SEEK 0x00000400 //硬件帧率控制.
#define V4L2_CAP_TUNER 0x00010000 //支持调谐器
#define V4L2_CAP_AUDIO 0x00020000 //支持音频
#define V4L2_CAP_RADIO 0x00040000 //支持无线
#define V4L2_CAP_STREAMING 0x04000000 //支持视频流
注意:能力集可以快速判断目前的相机设备是否支持后续操控,比如有的相机只有拍照没有视频功能,有的相机能拍照的同时记录音频等等,所以检查相机能力是必要的。
2.4 获取相机支持的格式信息
已知相机具备拍照能力V4L2_CAP_VIDEO_CAPTURE,那么拍出来的照片支持何种格式的呢?这时就需再次使用标准IO函数ioctl()来查询相机的格式信息:
struct v4l2_fmtdesc fmt = {
.index = 0, //第0种支持的格式.
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE, //拍照模式下.
}
while((ret = ioctl(fd, VIDIOC_ENUM_FMT, &fmt)) == 0){
printf("{pixelformat = %c%c%c%c},description = '%s'\n",
fmt.pixelformat & 0xff,
(fmt.pixelformat >> 8)&0xff,
(fmt.pixelformat >> 16) & 0xff,
(fmt.pixelformat >> 24)&0xff,
fmt.description);
fmt.index ++ ;
}
上面一段代码的输出结果是跟硬件相关的,测试发现输出结果是这样的:
注意:实际相机拍照时支持的照片格式往往不止一种,所以上面用循环遍历出所有可能的格式。
问题:
现在知道了相机能拍照V4L2_CAP_VIDEO_CAPTURE、相机拍照能输出多种格式的图像,那么如何获得一张指定格式的图像呢?
V4L2视频采集开发流程
mjpg-streamer:https://github.com/jacksonliam/mjpg-streamer
video2lcd:这是百问网编写的APP,它可以在LCD上直接显示摄像头的图像
可以参考这些文件:
mjpg-streamer\mjpg-streamer-experimental\plugins\input_control\input_uvc.c
video2lcd\video\v4l2.c
Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版。V4L2支持三种方式来采集图像:
内存映射方式(mmap)
直接读取方式(read)
用户指针
内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;用户指针使用较少,如有兴趣可自行研究。
数据采集流程
缓冲区 Buffer
摄像头采集数据时,是一帧又一帧地连续采集。所以需要申请若干个buffer,驱动程序把数据放入buffer,APP从buffer得到数据。这些buffer可以使用链表来管理。
那么这个buffer应该几帧?
一般设置2帧或3帧
其它的设置会出现的问题:
1帧,应用程序在读取这个帧,占用了整个缓冲,那么USB摄像头就无法写入新帧,导致丢失帧。
太多帧数,新的帧在写入缓冲区后,需要等待较长时间才能被应用程序读取,帧的处理和编码耗时会增加,会增加延时。
假设同样过了50ms,应用程序显示的是50ms前采集的帧,就会感觉实时性不强
帧数少的话,应用程序显示的是20ms前采集的帧,反正比帧数过多的实时性高。
少:丢帧
多:延迟
v4l2Cap这个摄像头有哪些能力

摄像头采集数据时,是一帧又一帧地连续采集。所以需要申请若干个buffer,驱动程序把数据放入buffer,APP从buffer得到数据。这些buffer可以使用链表来管理。
驱动程序周而复始地做如下事情:
从硬件采集到数据
把"空闲链表"取出buffer,把数据存入buffer
把含有数据的buffer放入"完成链表"
APP也会周而复始地做如下事情:
监测"完成链表",等待它含有buffer
从"完成链表"中取出buffer
处理数据
把buffer放入"空闲链表"
4、设置恰当的分辨率
open:打开设备节点/dev/videoX
ioctl VIDIOC_QUERYCAP:Query Capbility,查询能力,比如
确认它是否是"捕获设备",因为有些节点是输出设备
确认它是否支持mmap操作,还是仅支持read/write操作
ioctl VIDIOC_ENUM_FMT:枚举它支持的格式
ioctl VIDIOC_S_FMT:在上面枚举出来的格式里,选择一个来设置格式
ioctl VIDIOC_REQBUFS:申请buffer,APP可以申请很多个buffer,但是驱动程序不一定能申请到
ioctl VIDIOC_QUERYBUF和mmap:查询buffer信息、映射
如果申请到了N个buffer,这个ioctl就应该执行N次
执行mmap后,APP就可以直接读写这些buffer
ioctl VIDIOC_QBUF:把buffer放入"空闲链表"
如果申请到了N个buffer,这个ioctl就应该执行N次
ioctl VIDIOC_STREAMON:启动摄像头
这里是一个循环:使用poll/select监测buffer,然后从"完成链表"中取出buffer,处理后再放入"空闲链表"
poll/select
ioctl VIDIOC_DQBUF:从"完成链表"中取出buffer
处理:前面使用mmap映射了每个buffer的地址,处理时就可以直接使用地址来访问buffer
ioclt VIDIOC_QBUF:把buffer放入"空闲链表"
ioctl VIDIOC_STREAMOFF:停止摄像头
缓冲区
struct v4l2_buffer {
__u32 index; // 缓冲区索引
__u32 type; // 缓冲区类型,例如 V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 bytesused; // 缓冲区中已使用的数据字节数
__u32 flags; // 缓冲区标志
__u32 field; // 图像场类型
struct timeval timestamp; // 时间戳
struct v4l2_timecode timecode; // 时间码
__u32 sequence; // 序列号
__u32 memory; // 内存映射类型,例如 V4L2_MEMORY_MMAP
union {
__u32 offset; // 对于内存映射:缓冲区的偏移量
unsigned long userptr; // 对于用户指针:缓冲区的指针
__s32 fd; // 对于 DMA 缓冲区:文件描述符
} m;
__u32 length; // 缓冲区的长度
__u32 reserved2;
__u32 reserved;
};检测设备是否可用
v4l2-ctl --all -d /dev/video0列出支持的格式和分辨率
v4l2-ctl --list-formats-ext -d /dev/video0Linux摄像头(v4l2应用)——获取摄像头一帧图像 - 知乎 (zhihu.com)
ubuntu虚拟机中V4L2在VIDIOC_DQBUF处阻塞解决方法

设置摄像头输出格式
v4l2-ctl --set-fmt-video=pixelformat=YUYV使用ffplay测试是否可以使用
ls -l /dev/video0视频设备控制
我们可以调整摄像头的参数,达到我们想要的输出效果:
对于视频流本身:
设置格式:比如V4L2_PIX_FMT_YUYV、V4L2_PIX_FMT_MJPEG、V4L2_PIX_FMT_RGB565
设置分辨率:1024*768等
对于控制部分:
调节亮度
调节对比度
调节色度
判断摄像托有没有捕获的能力,即能否采集视频,而不是只能拍照。
获取当前的视频设备配置
struct v4l2_format currentFormat;
memset(¤tFormat, 0, sizeof(struct v4l2_format));
// 设置条件,根据“视频捕获”条件去获取
currentFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(vd->fd, VIDIOC_G_FMT, ¤tFormat);视频设备配置
struct v4l2_format {
__u32 type; // 缓冲区类型
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */
__u8 raw_data[200]; /* 用户自定义数据 */
} fmt;
};帧格式
struct v4l2_pix_format {
__u32 width; // 图像的宽度(像素)
__u32 height; // 图像的高度(像素)
__u32 pixelformat; // 像素格式(FourCC格式)
__u32 field; // 视频场类型(enum v4l2_field)
__u32 bytesperline; // 每行的字节数(行跨度),用于填充,未使用时为0
__u32 sizeimage; // 图像大小(字节)
__u32 colorspace; // 色彩空间类型(enum v4l2_colorspace)
__u32 priv; // 私有数据,依赖于像素格式
__u32 flags; // 格式标志(V4L2_PIX_FMT_FLAG_*)
__u32 ycbcr_enc; // YCbCr编码(enum v4l2_ycbcr_encoding)
__u32 quantization; // 量化(enum v4l2_quantization)
__u32 xfer_func; // 传输函数(enum v4l2_xfer_func)
};应用程序开发
参考mjpg-streamer和video2lcd,总结了摄像头的使用流程,如下:
1、打开设备
int fd;
fd = open("/dev/video0", O_RDWR);
if (fd < 0)
{
printf("can not open video device\n");
return -1;
}查询当前输入源
// 读到的value从0开始, 0表示第1个input源
int value;
ioctl(h->fd,VIDIOC_G_INPUT,&value);设置输入源
// 0表示第1个input源
int value = 0;
ioctl(h->fd,VIDIOC_S_INPUT,&value)2、查询摄像头有哪些能力
加入,我们想分辨率19201080,但是摄像头最大1080720,此时不会错,
3、获取摄像头支持的格式
// 内核中格式结构体
struct v4l2_fmtdesc {
__u32 index; // 格式编号
__u32 type; // 缓冲区的数据类型
__u32 flags; // 标志位
__u8 description[32]; // 描述信息
__u32 pixelformat; // 像素格式
__u32 reserved[4]; // 保留字段
};读取数据
关闭摄像头
Linux摄像头(v4l2应用)——获取摄像头一帧图像 - 知乎 (zhihu.com)
USB摄像头——v4l2打开设备、获取设备支持的格式 - 阿风小子 - 博客园 (cnblogs.com)
示例程序