V4L2应用层分析

article/2025/9/29 17:09:05

V4L2应用层分析

  • 一、总述
  • 二、例子

一、总述

V4L2,即Video for Linux 2,是第二代为Linux打造的音频、视频驱动。相比第一代V4L,V4L2支持更多的设备,同时更加稳定。现今的视频设备,如USB摄像头,基本都支持V4L2,故现今无需再学习第一代的V4L。

首先要说明的是,V4L2是一个驱动,而不是链接库,故不要想着找什么动态、静态链接库文件(.so文件和.a文件)。驱动是存在于内核层的代码,用于管理底层硬件。应用层无法直接使用驱动中的变量和函数,只能通过一系列标准调用接口来使用驱动(实现原理为系统调用syscall),常用的接口函数有open、write、read、ioctl、close,他们的声明原型如下:

// 应用层接口
// fcntl.h
extern int open (const char *__file, int __oflag, ...) __nonnull ((1));// unistd.h
extern ssize_t write (int __fd, const void *__buf, size_t __n) __wur;
extern ssize_t read (int __fd, void *__buf, size_t __nbytes) __wur;
extern int close (int __fd);// sys/ioctl.h
extern int ioctl (int __fd, unsigned long int __request, ...) __THROW;

由于 V4L2中只使用了ioctl这一个控制接口函数,故这里只介绍ioctl函数:

参数:int __fd:文件描述符,是open函数的返回值。unsigned long int __request:可以为简单的012等数字,也可以为规范的编码值,用于告知驱动控制命令的类型。...:三个点表示编译器在编译时不要检查此参数的类型。一般为整型变量或指针。返回值:当ioctl操作成功时返回 0,否则返回负数。

对于一个复杂的驱动来说,应用层的write和read函数由于传入参数单一,已经无法满足复杂驱动的需要。在此种情况下,ioctl凭借他传入参数的多样性成为最常用的接口函数,其传入参数的多样性体现如下:

1.ioctl的第二个参数unsigned long int __request,通常代表传入的控制命令。不同值的__request,代表着不同含义的控制命令,例如:V4L2中VIDIOC_QUERYCAP代表查询设备的驱动支持情况,VIDIOC_S_FMT代表设置设备属性。

__request可以由驱动开发者自定义使用规则,然后应用层开发者按照此自定义规则传入__request(如简单的0代表读取属性,1代表写入属性)。

驱动开发者最好遵从Linux内核的规范,按照内核提供好的格式对命令进行编码,详见头文件asm-generic/ioctl.h,此头文件在内核层和应用层都有提供,可随时查阅。对于应用层开发者来说,只需要按照驱动开发者提供的头文件中的说明,使用相应的命令即可。

2.ioctl的第三个参数 ...,通常代表需要传入的控制数据。...意味着编译器不会检查此处变量类型,这就允许第三个参数可以为不同类型的变量,实现c++重载的效果,再结合它对应的内核层的声明(见下段代码),第三个参数可以是简单的整型数值,也可以是指针(地址),当传入的是指针时,驱动层函数得到的即为传入参数的地址。此参数也会参与到第二个参数__request的编码中。

long (*unlocked_ioctl) (struct file *fl, unsigned int cmd, unsigned long arg);

综上所述,V4L2通过ioctl,通过传入不同的控制命令__request和不同的控制数据(V4L2中为结构体的地址或指向结构体的指针),来实现对底层硬件的操作。这些控制命令及相应注释见头文件linux/videodev2.h

二、例子

V4L2应用层的程序流程如下(以USB摄像头为例):

1.打开USB摄像头的设备节点:int cameraFd = open("/dev/video*", O_RDWR | O_NONBLOCK),*的取值根据自己USB摄像头实际的节点号确定,打开方式中,阻塞和非阻塞根据自己需要确定。

2.查询当前USB摄像头的驱动支持情况。由于摄像头驱动开发者可能并没有按照V4L2驱动标准实现全部的驱动功能,而只实现了部分功能,所以需要查询当前摄像头的驱动支持情况,以确定后续通过怎样的方式驱动摄像头。

struct v4l2_capability cap;
ioctl(cameraFd, VIDIOC_QUERYCAP, &cap);

执行完上述ioctl函数后,驱动会根据功能的实现情况填充cap结构体变量的成员。注意,这是一个一般规律,即执行完某个ioctl函数后,驱动会为传入的结构体的成员赋值。

接下来,我们可以把所有成员的值都打印出来,以查看当前摄像头的驱动支持情况。

printf("Driver Name:%s\nCard Name:%s\nBus info:%s\nKernel Version:%u.%u.%u\nCapability is %#x\nDevice capability is %#x\n",cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0XFF, (cap.version>>8)&0XFF,cap.version&0XFF,cap.capabilities,cap.device_caps);

v4l2_capability 结构体定义和成员变量意义说明见头文件linux/videodev2.h,如下图所示:
在这里
我们需要重点关注的是成员capabilitiesdevice_caps。按照文档描述,这两个的差异在于capabilities是描述整个物理设备的驱动支持情况的,而device_caps是描述当前设备节点的情况的。从逻辑上来看,我们应该查询device_caps来查看驱动支持情况。不过我的摄像头实测这两个值的差异仅在于有无如下代码块中的最后一个宏定义的值:V4L2_CAP_DEVICE_CAPS,故两者都可以用来查询驱动支持情况。

capabilities的值以位掩码(BitMask)的形式来组织。以16进制数输出capabilities,将他与如下代码块中的宏定义相比较,可以得知驱动是否支持宏定义代表的功能。例如,对于USB摄像头,他显然是一个捕获设备,故capabilities的最低位一定为1。除此之外,我们需要重点关注的是V4L2_CAP_READWRITEV4L2_CAP_ASYNCIOV4L2_CAP_STREAMING,他们代表USB摄像头支持的3种将数据输出到用户空间的方法,分别是通过read函数将内核空间的数据复制到用户空间、通过用户指针得到内核空间的数据、通过将内核空间映射到用户空间来读取数据,其中第二个我还不了解,第一个由于涉及到复制数据,故读取速率很慢,相比之下第三种映射方式读取速度要快得多。这三种读取数据的方法会在后续步骤提到。

 /* Values for 'capabilities' field */#define V4L2_CAP_VIDEO_CAPTURE		0x00000001  /* Is a video capture device */#define V4L2_CAP_VIDEO_OUTPUT		0x00000002  /* Is a video output device */#define V4L2_CAP_VIDEO_OVERLAY		0x00000004  /* Can do video overlay */#define V4L2_CAP_VBI_CAPTURE		0x00000010  /* Is a raw VBI capture device */#define V4L2_CAP_VBI_OUTPUT		0x00000020  /* Is a raw VBI output device */#define V4L2_CAP_SLICED_VBI_CAPTURE	0x00000040  /* Is a sliced VBI capture device */#define V4L2_CAP_SLICED_VBI_OUTPUT	0x00000080  /* Is a sliced VBI output device */#define V4L2_CAP_RDS_CAPTURE		0x00000100  /* RDS data capture */#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY	0x00000200  /* Can do video output overlay */#define V4L2_CAP_HW_FREQ_SEEK		0x00000400  /* Can do hardware frequency seek  */#define V4L2_CAP_RDS_OUTPUT		0x00000800  /* Is an RDS encoder *//* Is a video capture device that supports multiplanar formats */#define V4L2_CAP_VIDEO_CAPTURE_MPLANE	0x00001000 /* Is a video output device that supports multiplanar formats */#define V4L2_CAP_VIDEO_OUTPUT_MPLANE	0x00002000 /* Is a video mem-to-mem device that supports multiplanar formats */#define V4L2_CAP_VIDEO_M2M_MPLANE	0x00004000 /* Is a video mem-to-mem device */#define V4L2_CAP_VIDEO_M2M		0x00008000#define V4L2_CAP_TUNER			0x00010000  /* has a tuner */#define V4L2_CAP_AUDIO			0x00020000  /* has audio support */#define V4L2_CAP_RADIO			0x00040000  /* is a radio device */#define V4L2_CAP_MODULATOR		0x00080000  /* has a modulator */#define V4L2_CAP_SDR_CAPTURE		0x00100000  /* Is a SDR capture device */#define V4L2_CAP_EXT_PIX_FORMAT		0x00200000  /* Supports the extended pixel format */#define V4L2_CAP_READWRITE              0x01000000  /* read/write systemcalls */#define V4L2_CAP_ASYNCIO                0x02000000  /* async I/O */#define V4L2_CAP_STREAMING              0x04000000  /* streaming I/O ioctls */#define V4L2_CAP_DEVICE_CAPS            0x80000000  /* sets device capabilities field */ 

3.获取USB摄像头输出图像的格式:

struct v4l2_fmtdesc fmtdesc;
fmtdesc.index=0; 
fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; 
printf("Support format:\n");
while(ioctl(cameraFd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
{printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);fmtdesc.index++;
}

同第二步,相关结构体和宏定义可在linux/videodev2.h中查看。我的USB摄像头输出格式为MJPEG和YUV 4:2:2(YUYV),选择不同的输出格式,会影响到后续对图像数据的处理。

4.获取并设置图像帧的具体属性(代码中称为format),包括帧的宽度和高度(即分辨率)、输出格式以及field,其中field取自枚举型v4l2_field,由于我的摄像头的field值为1即没有field ( V4L2_FIELD_NONE ),我并不理解这里field的作用。

//获取当前摄像头的具体属性
struct v4l2_format fmt; 
fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; 
ioctl(cameraFd, VIDIOC_G_FMT, &fmt);
printf("Current data format information:\n\twidth:%d\n\theight:%d\n\tformat:%#x ie. %c%c%c%c\n\tfield:%d\n", fmt.fmt.pix.width, fmt.fmt.pix.height, fmt.fmt.pix.pixelformat,fmt.fmt.pix.pixelformat&0xff, (fmt.fmt.pix.pixelformat>>8)&0xff, (fmt.fmt.pix.pixelformat>>16)&0xff, (fmt.fmt.pix.pixelformat>>24)&0xff, fmt.fmt.pix.field);

如果获得的属性已经是我们想要的结果,则可以省略设置属性这一步骤,否则设置属性:

//设置摄像头的具体属性
fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; 
fmt.fmt.pix.width = ...;
fmt.fmt.pix.heifht = ...;
fmt.fmt.pix.pixelformat = ...;//输出格式需是第三步中查到的格式之一,即首先要求摄像头支持此种格式//格式对应的具体值在头文件linux/videodev2.h中可以查到
fmt.fmt.pix.field = ...;
ioctl(cameraFd, VIDIOC_S_FMT, &fmt);

注意,如果设置的属性是摄像头不支持的,则fmt结构体变量的相应成员的值不会发生改变。

5.向内核层申请缓冲区:缓冲区的作用是保存摄像头采集到的图像,他会和后面步骤提到的输入队列和输出队列配合将一帧帧图像数据送给用户。

struct v4l2_requestbuffers rqstbuf = {.count  = 5,.memory = V4L2_MEMORY_MMAP,.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
};
ret = ioctl(cameraFd, VIDIOC_REQBUFS, &rqstbuf); 
if(ret < 0) {printf("buffer's actual count is:%d\n", rqstbuf.count);perror("request buffers fail");return -1;
}else {printf("requst buffers success!\nbuffer's actual count is:%d\n", rqstbuf.count);
}

其中,
count为缓冲区的个数,每个缓冲区保存一帧图像,因此缓冲区的大小由第4步中图像帧的宽高和输出格式共同决定。
memory为第2步中提到的读取数据的方法,设置的值必须要求摄像头支持。
type只有两种,即捕获还是输出,对于摄像头来说当然是捕获。

ioctl执行完毕后,程序可能由于内存不足等原因没有申请到指定数量的缓冲区,此时需验证下实际申请到的缓冲区数量。

缓冲区的编号由buffer.index进行维护。

6.将申请到的缓冲区映射到用户空间,并将所有缓冲区放入输入队列:

    struct v4l2_buffer buffer = {.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,.memory = V4L2_MEMORY_MMAP,.index = 0,};unsigned char *video_buffer_ptr[rqstbuf.count];//定义用户空间的一段内存,//内核空间的缓冲区将会映射到此段内存for(int i=0;i<rqstbuf.count;i++) {buffer.index = i;ret = ioctl(cameraFd, VIDIOC_QUERYBUF, &buffer);if(ret<0) {perror("query address of buff failed");return -1;}printf("length of buff NO.%d is %d\n", i+1, buffer.length);   video_buffer_ptr[i] = (unsigned char*) mmap(NULL, buffer.length, PROT_READ, MAP_SHARED, cameraFd, buffer.m.offset);if (video_buffer_ptr[i] == MAP_FAILED) {            perror("mmap() failed");return -1;     }     //把所有缓存放入输入队列ret = ioctl(cameraFd, VIDIOC_QBUF, &buffer);       if (ret<0) {           perror("ioctl(VIDIOC_QBUF) failed");return -1;     }}  

通过定义一个指针数组unsigned char *video_buffer_ptr[rqstbuf.count];,我们定义了用户空间的一段内存。mmap函数负责将内核空间的缓冲区映射到此段内存。

映射完成后,V4L2通过两个队列进行视频流的管理。这两个队列对用户不可见,一个是输入队列,另一个是输出队列。对于捕获设备,输入队列满时将会转变为输出队列。

调用函数ioctl(cameraFd, VIDIOC_QBUF, &buffer);可以将缓冲区加入输入队列队尾,调用函数ioctl(cameraFd, VIDIOC_DQBUF, &buffer); 从输出队列队首取出缓冲区,而缓冲区已被映射到用户空间,我们就可以从定义的指针指向的地址处开始读取图像数据了。

7.开启图像捕获:

int buffer_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(cameraFd, VIDIOC_STREAMON, &buffer_type);
if(ret<0) {perror("capture stream failed");return -1;
}else {printf("capture start\n");
}

8.从输出队列中取出缓冲区:

struct v4l2_buffer buf;
memset(&buf,0,sizeof(buf));
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;
buf.index=0;
do {ret = ioctl(cameraFd, VIDIOC_DQBUF, &buf);if(ret<0) {printf("\rwaiting for capturing...");}
}while(ret<0);
printf("\ncapture successed\n");

注意驱动将输入队列填满需要一定时间,在这之前取出缓冲区必然是会失败的,而由于我是用非阻塞模式打开的设备节点,故代码中使用了do while来不停地尝试将输出队列队头的缓冲区取出。

9.从用户空间读出图像数据并处理:

char* fileName = "/home/root/capture1.jpg";
FILE *fp;
fp = fopen(fileName, "w+");
for(long i=0;i<buf.length;i++) {int data = *(video_buffer_ptr[buf.index]+i);fputc(data, fp);
}
fclose(fp);
printf("saving file successed\n");
ioctl(cameraFd, VIDIOC_QBUF, &buf);//

本段代码所做的处理工作为将图像保存到本地。由于我选择的是以MJPEG格式输出的图像,故摄像头输出的数据是已经经过JPEG编码之后的数据,直接将数据写入.jpg文件即可,Windows和Ubuntu都支持JPEG解码,可以直接打开图片。

注意,每次读取数据后,需要将刚刚使用的缓冲区重新加入输入队列。这样形成一个循环,就可以获得视频流了。

10.完成收尾工作:取消映射、关闭图像捕获

ioctl(cameraFd, VIDIOC_STREAMOFF, &buffer_type);
for(int i=0;i<rqstbuf.count;i++) {munmap(video_buffer_ptr[i], buffer.length);
}  
return 0;

最后,说明一下图像及视频编解码问题:
jpg是电脑上常用的图片格式,采用JPEG编码,由于我们的USB摄像头支持MJPEG输出格式,所以可以直接输出JPEG编码后的数据。但是,V4L2并不具有视频的编码能力,故单凭V4L2是无法输出.avi等视频格式的。要想输出视频文件,我们还需要其他专业的编解码库,如ffmpeg。


http://chatgpt.dhexx.cn/article/lJpiv3LS.shtml

相关文章

v4l2数据结构分析

v4l2数据结构分析 文章目录 v4l2数据结构分析Video4Linux2设备v4l2_device媒体设备media_deviceVideo4Linux2子设备v4l2_subdevVideo4Linux2子设备的操作集v4l2_subdev_opsVideo4Linux2子设备的内部操作集v4l2_subdev_internal_opsVideo4Linux2控制处理器v4l2_ctrl_handlerVide…

摄像头V4L2编程应用开发

1.V4L2简介 Video for Linux two(Video4Linux2)简称V4L2&#xff0c;是V4L的改进版。V4L2是linux系统操作系统下一套用于采集图片、视频、和音视频数据的通用API接口&#xff0c;配合适当的视频采集设备和相应的驱动程序&#xff0c;可以实现图片、视频、音频等的采集。 在linu…

V4L2框架

前言 在分析v4l2之前最好具有的知识&#xff1a; 1.字符设备,因为v4l2是被枚举为字符设备。 2.内存分配和映射,比如相关数据结构的分配和buffer。 3.DMA&#xff0c;因为v4l2的数据传输用到了DMA。 4.I2C&#xff0c;因为很多传感器都是用的i2c接口。 5.文件系统的基本操作&am…

V4L2系列 之 V4L2驱动框架

目录 前言一、V4L2驱动框架概览1、应用层 -》中间层-》驱动层2、主要代码文件(Linux 4.19版本内核) 二、怎么写V4L2驱动1、如何写一个设备的驱动&#xff1f;2、Video设备主要结构体3、怎么写V4L2驱动 三、V4L2的调试工具1、v4l2-ctl2、dev_debug3、v4l2-compliance 前言 本篇文…

V4L2框架概述

原文链接 本文开启 linux 内核 V4L2 框架部分的学习之旅&#xff0c;本文仅先对 V4L2 的框架做一个综述性的概括介绍&#xff0c;然后接下来的文章中会对 V4L2 框架的各个子模块进行一个全面的介绍&#xff0c;包括每一部分的实现原理&#xff0c;如何使用&#xff0c;用在什么…

V4l2框架分析

Table of Contents 1.V4L2框架概述 1.1 v4l2设备应用层流程 1.2 内核V4L2模块 1.2.1 video_device 1.2.2 v4l2_subdev 1.2.3 videobuf2 2. video_device结构体 2.1 图像处理模块 2.2 video_device处理流程 2.2.1 video_device 结构体成员介绍: 3. video_buf2 3.1 …

V4L2

V4L2(video 4 linux 2) V4L2有一段历史了。大约在1998的秋天&#xff0c;它的光芒第一次出现在Bill Dirks 的眼中。经过长足的发展&#xff0c;它于2002年11 月&#xff0c;发布2.5.46 时&#xff0c;融入了内核主干之中。然而直到今天&#xff0c;仍有一部分内核驱不支持新的A…

V4L2简介

http://work-blog.readthedocs.org/en/latest/v4l2%20intro.html 第一章 V4L2简介 1.1、什么是v4l2 V4L2&#xff08;Video4Linux的缩写&#xff09;是Linux下关于视频采集相关设备的驱动框架&#xff0c;为驱动和应用程序提供了一套统一的接口规范。 V4L2支持的设备十分广泛&…

我们一起学linux之V4L2摄像头应用流程

一、概述 Video for Linuxtwo(Video4Linux2)简称V4L2&#xff0c;是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口&#xff0c;配合适当的视频采集设备和相应的驱动程序&#xff0c;可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频…

深入学习Linux摄像头(一)v4l2应用编程

深入学习Linux摄像头&#xff08;一&#xff09;v4l2应用编程 一、什么是v4l2 二、v4l2 API介绍 2.1 Querying Capabilities 2.2 Application Priority 2.3 Device Inputs and Outputs 2.4 Video Standards 2.5 Camera Control Reference 2.6 Image Format 2.7 Cropping, compo…

V4L2驱动框架详解

1. V4L2框架概述 1.1 v4l2设备应用层流程 1.2 内核V4L2模块 2 2. video_device 2.1图像处理模块 2.2 video注册流程 3. videobuf2 3.1 与video device的关系: 3.2 buffer类型 3.3 vb2_ops回调函数 3.4 mmap 流程 4. Subdev 4.1 概念 4.2 subdev注册流程 5. media fra…

html背景自动适应,css背景图片如何自适应?

css可以使用background-size属性设置背景图片自适应&#xff0c;为背景图片设置background-size:cover;样式即可使背景图片自适应。 css可以使用background-size属性设置背景图片自适应。background-size属性规定背景图像的尺寸。 background-size属性&#xff1a; 语法&#x…

css实现图片自适应容器的几种方式

css实现图片自适应容器 经常有这样一个场景,需要让图片自适应容器的大小。 1、img标签的方式 我们马上就能想到,把width、height 设置为100%啊。来看一哈效果。 <div class=div1><img src="./peiqi.png" alt=""> </div>.div1 {widt…

css 背景图片自适应属性整理

本篇博客主要记录一些使用 css 对背景图片自适应的操作整合 背景图片取消重复 background-image: url(image.jpg); background-repeat:no-repeat;修改前 修改后 背景图片固定&#xff08;不会随着内容滚动而改变位置&#xff09; background-image: url(image.jpg); back…

CSS实现图片自适应布局

CSS实现图片自适应布局 最轻松的写法 <div class"container">// 这里图片尺寸为440X440像素&#xff0c;<img src"./images/medium.jpg" alt"test"> </div><style>.container {width: 600px;height: 600px;border: 1p…

CSS图片自适应框架

CSS图片自适应框架 使用img来设置 <!DOCTYPE html> <html><style>body{margin: 0;padding: 10px;}#a_1{display: block;width: 100px;height: 50px;overflow: hidden;padding: 10px;border: dashed;margin: 10px;}#a_1img{width: 100%;height: 100%;}</s…

css之实现图片自适应

文章目录 一、position实现图片自适应二、padding补偿法三、图片以正方形显示 原图如下&#xff1a; 在开发中&#xff0c;想在固定的高度和宽度中显示不一样的图片时&#xff0c;就会出现压缩&#xff0c;导致图片最后显示不好看&#xff0c;以下提供几种方法进行调整&#xf…

css实现图片自适应缩放的两种方法

想要实现图片根据给定宽高来自适应缩放的时候&#xff0c;会想到什么属性&#xff1f;是object-fit: cover;吗&#xff1f; 来试一试&#xff01;为了展示效果&#xff0c;我特地选了对称的图片。下面两个img元素都有一个div包裹&#xff0c;div设置固定宽高&#xff0c;第一个…

3种CSS实现背景图片全屏铺满自适应的方式

来源 | https://www.fly63.com/ 一张清晰漂亮的背景图片能给网页加分不少&#xff0c;设计师也经常会给页面的背景使用大图&#xff0c;我们既不想图片因为不同分辨率图片变形&#xff0c;也不希望当在大屏的情况下&#xff0c;背景有一块露白&#xff0c;简而言之&#xff0c;…

css怎么设置背景图片自适应大小

在css中&#xff0c;可以利用“background-size”属性设置背景图片自适应大小&#xff0c;该属性用于设置背景图片的大小&#xff0c;只需要给背景图片元素添加“background-size:cover;”样式&#xff0c;即可使背景图片的大小自适应。 本教程操作环境&#xff1a;windows10系…