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

article/2025/9/29 17:15:02

一、概述
Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。

在这里插入图片描述
二、编程思路
在Linux下,所有外设都被看成一种特殊的文件,成为“设备文件”,可以象访问普通文件一样对其进行读写。一般来说,采用V4L2驱动的摄像头设备文件是/dev/video0。V4L2支持两种方式来采集图像:内存映射方式(mmap)和直接读取方式(read)。V4L2在include/linux/videodev.h文件中定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2的能力可在Linux内核编译阶段配置,默认情况下都有此开发接口。

而摄像头所用的主要是capature了,视频的捕捉,具体linux的调用可以参考下图。

在这里插入图片描述
三、应用程序通过V4L2进行视频采集的原理
V4L2支持内存映射方式(mmap)和直接读取方式(read)来采集数据,前者一般用于连续视频数据的采集,后者常用于静态图片数据的采集,本文重点讨论内存映射方式的视频采集。
应用程序通过V4L2接口采集视频数据分为五个步骤:

打开视频设备文件,进程视频采集的参数初始化,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式。
申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据。
将申请到的帧缓冲区在视频采集输入队列排队,,并启动视频采集
驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据
停止视频采集

具体的程序实现流程可以参考下面的流程图:

在这里插入图片描述

其实其他的都比较简单,就是通过ioctl这个接口去设置一些参数。最主要的就是buf管理。他有一个或者多个输入队列和输出队列。

启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。

应用程序从视频采集输出队列中取出含有视频塑化剂的帧缓冲区,处理帧缓冲区中的视频数据,如压缩或存储。

最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集,如图所示:

在这里插入图片描述

 每一个帧缓冲区都有一个对应的状态标志变量,其中每一个比特代表一个状态。
 

V4L2_BUF_FLAG_UNMAPPED   	0B0000
V4L2_BUF_FLAG_MAPPED 		0B0001
V4L2_BUF_FLAG_ENQUEUED 	    0B0010
V4L2_BUF_FLAG_DONE 			0B0100

缓冲区的状态转化如图所示。
在这里插入图片描述

四、核心命令字和结构体(参见/usr/include/linux/videodev2.h)

一、VIDIOC_ENUM_FMT
含义:枚举出当前摄像头(驱动)所支持的所有数据格式

具体用法如下:

ioctl(fd,VIDIOC_ENUM_FMT,struct v4l2_fmtdesc *argp);

通过迭代结构体struct v4l2_fmtdesc 中的index 成员,来枚举罗列支持的所有格式,该结构体的详细信息如下:

struct v4l2_fmtdesc
{
__u32 index; // 数据格式的索引
__u32 type; // 一般设置为 V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 flags;
__u8 description[32];
__u32 pixelformat;
__u32 reserved[4];
};

其中 type跟v4l2_format中的type设置要一致。在成功调用ioctl之后,description将保存对当前获取的数据格式的描述。

二、VIDIOC_G_FMT / VIDIOC_S_FMT / VIDIOC_TRY_FMT
含义:
1.获取当前摄像头驱动数据格式
2.设置摄像头驱动数据格式
3.尝试设置格式

具体用法:
 

ioctl(fd,VIDIOC_G_FMT ,struct v4l2_format     *argp)
ioctl(fd,VIDIOC_S_FMT ,struct v4l2_format      *argp)
ioctl(fd,VIDIOC_TRY_FMT,struct v4l2_format  *argp)

涉及数据结构:

struct v4l2_format
{
__u32 type;
union
{struct v4l2_pix_format pix;struct v4l2_pix_format_mplane pix_mp;struct v4l2_window win;struct v4l2_vbi_format vbi;struct v4l2_sliced_vbi_format sliced;__u8 raw_data[200];
} fmt;
}

V4l2_format中的fmt是一个union,其中哪个成员有效取决于type的取值,一般较常用的是取类型type为 V4L2_BUF_TYPE_VIDEO_CAPTURE,此时pix生效。该成员的详细内部细节如下:

struct v4l2_pix_format
{__u32 width;__u32 height;__u32 pixelformat;__u32 field;__u32 bytesperline;__u32 sizeimage;__u32 colorspace;__u32 priv;
};

该结构体中的成员 pixelformat 代表视频输入驱动所使用的像素格式,常见的有V4L2_PIX_FMT_JPEG、V4L2_PIX_FMT_YUV、V4L2_PIX_FMT_MJPG等。而成员field代表视频帧传输的方式,选择 V4L2_FIELD_INTERLACED 为交错式。

三、VIDIOC_REQBUFS
含义:向内核申请视频缓存
具体用法如下:
 

ioctl(fd,VIDIOC_REQBUFS,v4l2_requestbuffers *argp)

该命令字所申请的缓存就是如下图所示的内核中处理视频数据的队列缓存,这些缓存的具体配置参数用如下结构体来指定:

struct v4l2_requestbuffers
{__u32 count; // 申请缓存总个数__u32 type; // 与 struct v4l2_format 中的 type 一致__u32 memory;__u32 reserved[2];
};

其中 memory 的取值为 V4L2_MEMORY_MMAP V4L2_MEMORY_USERPTR,取决于,当该字段被设置为 V4L2_MEMORY_MMAP 时,count 字段才有效。

在这里插入图片描述

四、VIDIOC_QUERYBUF
含义:内核成功分配了缓存后,取得这些缓存的具体参数
具体用法如下:

ioctl(fd, VIDIOC_QUERYBUF, v4l2_buffer *argp);

 之所以需要取得这些缓存的具体参数的一个目的是,这些缓存都是处在内核空间的,我们并不能直接操作他们,因此需要将他们通过mmap映射到用法空间,这就要求必须知道他们的大小、偏移等信息。这些信息统一被储存到如下结构体中:

struct v4l2_buffer
{__u32 index; // 内核缓存索引号,由用户指定,范围是[0 ~ count-1]__u32 type; // 与 v4l2_format 中的 type 一致__u32 bytesused;__u32 flags;__u32 field;struct timeval timestamp;struct v4l2_timecode timecode;__u32 sequence;__u32 memory; // 与 v4l2_requestbuffers 中的 memory 一致union{__u32 offset; // 缓存相对于设备内存的偏移unsigned long userptr;struct v4l2_plane *planes;__s32 fd;} m;__u32 length; // 缓存大小__u32 reserved2;__u32 reserved;};

五、VIDIOC_QBUF / VIDIOC_DQBUF
含义:
1.使一个空的(视频输入时)或者一个满的(视频输出时)缓存入队
2.使一个满的(视频输入时)或者一个空的(视频输出时)缓存出队

具体用法如下:

ioctl(fd, VIDIOC_QBUF, v4l2_buffer *argp);
ioctl(fd, VIDIOC_DQBUF, v4l2_buffer *argp);

内核缓存的入队和出队

这两个命令字是捕捉视频帧最常用的动作,通过 v4l2_buffer 中 index 字段,将指定缓存出队或者入队,这里需要澄清的几个要点是:
1、在尚未开启摄像头取像之前,需要将空的缓存一一入队
2、针对视频输入,出队的时候如果缓存没有数据,那么出队将阻塞
3、虽然内核对这些内存的定义时“队列”,但实际上不按顺序“插队”也是可以的,但一般不那么做

六、VIDIOC_STREAMON / VIDIOC_STREAMOFF
含义:
1、开启I/O流
2、关闭I/O流

具体用法如下:
 

ioctl(fd, VIDIOC_STREAMON, const int *argp);
ioctl(fd, VIDIOC_STREAMOFF, const int *argp);

不管 I/O 方式被设定为内存映射(MMAP)方式还是用户指针(USERPTR)方式,都可以使用 VIDIOC_STREAMON 和 VIDIOC_STREAMOFF 来启停 I/O 流。事实上,在使用 ioctl 调用 VIDIOC_STREAMON 之前,物理硬件将暂时被禁用且没有缓存被填充数据。

VIDIOC_STREAMOFF 除了终止进程的 DMA 操作(如果有的话)之外,还将解锁用户指针指向的物理内存,队列中的所有缓存都将被移除,这意味着如果是视频输入,那么那些没来得及读取的视频帧将被丢弃,如果是视频输出,那么那写没来及传输的视频帧也同样会被丢弃。

五、V4L2 API及数据结构
V4L2是V4L的升级版本,为linux下视频设备程序提供了一套接口规范。包括一套数据结构和底层V4L2驱动接口。

1.常用的结构体在内核目录include/linux/videodev2.h中定义
 

 struct v4l2_requestbuffers		//申请帧缓冲,对应命令VIDIOC_REQBUFS 
struct v4l2_capability			//视频设备的功能,对应命令VIDIOC_QUERYCAP 
struct v4l2_input				//视频输入信息,对应命令VIDIOC_ENUMINPUT
struct v4l2_standard			//视频的制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD 
struct v4l2_format				//帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
struct v4l2_buffer				//驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF 
struct v4l2_crop				//视频信号矩形边框
struct v4l2_std_id				//视频制式

2、常用的IOCTL接口命令也在include/linux/videodev2.h中定义

VIDIOC_REQBUFS					//分配内存  
VIDIOC_QUERYBUF					//把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址 
VIDIOC_QUERYCAP					//查询驱动功能 
VIDIOC_ENUM_FMT 				//获取当前驱动支持的视频格式 
VIDIOC_S_FMT					//设置当前驱动的频捕获格式 
VIDIOC_G_FMT 					//读取当前驱动的频捕获格式 
VIDIOC_TRY_FMT 					//验证当前驱动的显示格式 
VIDIOC_CROPCAP 					//查询驱动的修剪能力 
VIDIOC_S_CROP 					//设置视频信号的矩形边框 
VIDIOC_G_CROP 					//读取视频信号的矩形边框
VIDIOC_QBUF 					//把数据从缓存中读取出来 
VIDIOC_DQBUF 					//把数据放回缓存队列 
VIDIOC_STREAMON 				//开始视频显示函数 
VIDIOC_STREAMOFF 				//结束视频显示函数 
VIDIOC_QUERYSTD 				//检查当前视频设备支持的标准,例如PAL或NTSC。

3、操作流程
V4L2提供了很多访问接口,你可以根据具体需要选择操作方法。需要注意的是,很少有驱动完全实现了所有的接口功能。所以在使用时需要参考驱动源码,或仔细阅读驱动提供者的使用说明。下面列举出一种操作的流程,供参考。

(1)打开设备文件

int fd = open(Devicename,mode);
Devicename:/dev/video0、/dev/video1 ……
Mode:O_RDWR | O_NONBLOCK

如果使用非阻塞模式调用视频设备,则当没有可用的视频数据时,不会阻塞,而立刻返回。

(2)获取摄像头设备的基本参数

 struct v4l2_capability cap;bzero(&cap, sizeof(cap));if(ioctl(camfd, VIDIOC_QUERYCAP, &cap) == -1){printf("获取摄像头基本信息失败: %s\n", strerror(errno));exit(0);}printf("驱动:%s\n", cap.driver);printf("显卡:%s\n", cap.card);printf("总线:%s\n", cap.bus_info);printf("版本:%d\n", cap.version);if((cap.capabilities&V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE){printf("该设备为视频采集设备\n");}if((cap.capabilities&V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING){printf("该设备支持流IO操作\n\n");}

(3)获取摄像头格式信息(固定)

struct v4l2_fmtdesc fmtdesc;fmtdesc.index = 0;fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;int ret;printf("像素格式: \n");while((ret=ioctl(camfd, VIDIOC_ENUM_FMT, &fmtdesc)) == 0){printf("[%d]", fmtdesc.index);sprintf(formats[fmtdesc.index]+0, "%c", (fmtdesc.pixelformat>>8*0)&0xFF);sprintf(formats[fmtdesc.index]+1, "%c", (fmtdesc.pixelformat>>8*1)&0xFF);sprintf(formats[fmtdesc.index]+2, "%c", (fmtdesc.pixelformat>>8*2)&0xFF);sprintf(formats[fmtdesc.index]+3, "%c", (fmtdesc.pixelformat>>8*3)&0xFF);printf("\"%s\"", formats[fmtdesc.index]);printf("(详细描述: %s)\n", fmtdesc.description);fmtdesc.index++;}

(4)获取摄像头格式信息(可调)

 struct v4l2_format  fmt;bzero(&fmt, sizeof(fmt));fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if(ioctl(camfd, VIDIOC_G_FMT, &fmt) == -1){printf("获取摄像头格式信息失败: %s\n", strerror(errno));exit(0);}printf("分辨率: %d×%d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);printf("像素格式: ");switch(fmt.fmt.pix.pixelformat){case V4L2_PIX_FMT_MJPEG:printf("V4L2_PIX_FMT_MJPEG\n");break;case V4L2_PIX_FMT_JPEG:printf("V4L2_PIX_FMT_JPEG\n");break;case V4L2_PIX_FMT_MPEG:printf("V4L2_PIX_FMT_MPEG\n");break;case V4L2_PIX_FMT_MPEG1:printf("V4L2_PIX_FMT_MPEG1\n");break;case V4L2_PIX_FMT_MPEG2:printf("V4L2_PIX_FMT_MPEG2\n");break;case V4L2_PIX_FMT_MPEG4:printf("V4L2_PIX_FMT_MPEG4\n");break;case V4L2_PIX_FMT_H264:printf("V4L2_PIX_FMT_H264\n");break;case V4L2_PIX_FMT_XVID:printf("V4L2_PIX_FMT_XVID\n");break;case V4L2_PIX_FMT_RGB24:printf("V4L2_PIX_FMT_RGB24\n");break;case V4L2_PIX_FMT_BGR24:printf("V4L2_PIX_FMT_BGR24\n");break;case V4L2_PIX_FMT_YUYV:printf("V4L2_PIX_FMT_YUYV\n");break;case V4L2_PIX_FMT_YYUV:printf("V4L2_PIX_FMT_YYUV\n");break;case V4L2_PIX_FMT_YVYU:printf("V4L2_PIX_FMT_YVYU\n");break;case V4L2_PIX_FMT_YUV444:printf("V4L2_PIX_FMT_YUV444\n");break;case V4L2_PIX_FMT_YUV410:printf("V4L2_PIX_FMT_YUV410\n");break;case V4L2_PIX_FMT_YUV420:printf("V4L2_PIX_FMT_YUV420\n");break;case V4L2_PIX_FMT_YVU420:printf("V4L2_PIX_FMT_YVU420\n");break;case V4L2_PIX_FMT_YUV422P:printf("V4L2_PIX_FMT_YUV422P\n");break;default:printf("未知\n");}

(5)配置摄像头像素格式

 struct v4l2_format *tmp = calloc(1, sizeof(*tmp));bzero(tmp, sizeof(*tmp));tmp->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;tmp->fmt.pix.width  = 800;tmp->fmt.pix.height = 448;printf("\n请选择要配置的像素格式:");int n; scanf("%d", &n);if(!strcmp(formats[n], "JPEG")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;else if(!strcmp(formats[n], "MJPG")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;else if(!strcmp(formats[n], "MPEG")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;else if(!strcmp(formats[n], "YUYV")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;else if(!strcmp(formats[n], "YVYU")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_YVYU;else if(!strcmp(formats[n], "H264")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_H264;else{printf("对不起,所选格式无法配置.\n");exit(0);}	tmp->fmt.pix.field = V4L2_FIELD_INTERLACED;if(ioctl(camfd, VIDIOC_S_FMT, tmp) == -1){printf("ioctl() VIDIOC_S_FMT 失败了: %s\n", strerror(errno));}

(6)向驱动申请帧缓存

int nbuf = 3;struct v4l2_requestbuffers reqbuf;bzero(&reqbuf, sizeof (reqbuf));reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbuf.memory = V4L2_MEMORY_MMAP;reqbuf.count = nbuf;// 使用该参数reqbuf来申请缓存ioctl(camfd, VIDIOC_REQBUFS, &reqbuf);
}

v4l2_requestbuffers结构中定义了缓存的数量,驱动会据此申请对应数量的视频缓存。多个缓存可以用于建立FIFO,来提高视频采集的效率。

(7)获取每个缓存的信息,并mmap到用户空间

 // 根据刚刚设置的reqbuf.count的值,来定义相应数量的struct v4l2_buffer// 每一个struct v4l2_buffer对应内核摄像头驱动中的一个缓存struct v4l2_buffer buffer[nbuf];int length[nbuf];uint8_t *start[nbuf];for(int i=0; i<nbuf; i++){bzero(&buffer[i], sizeof(buffer[i]));buffer[i].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buffer[i].memory = V4L2_MEMORY_MMAP;buffer[i].index = i;ioctl(camfd, VIDIOC_QUERYBUF, &buffer[i]);length[i] = buffer[i].length;start[i] = mmap(NULL, buffer[i].length,	PROT_READ | PROT_WRITE,MAP_SHARED,	camfd, buffer[i].m.offset);ioctl(camfd , VIDIOC_QBUF, &buffer[i]);}

在这里插入图片描述

(8)启动摄像头数据采集

enum v4l2_buf_type vtype= V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(camfd, VIDIOC_STREAMON, &vtype);

 (9)取出FIFO缓存中已经采样的帧缓存

 struct v4l2_buffer v4lbuf;bzero(&v4lbuf, sizeof(v4lbuf));v4lbuf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4lbuf.memory= V4L2_MEMORY_MMAP;// 从队列中取出填满数据的缓存v4lbuf.index = i%nbuf;ioctl(camfd , VIDIOC_DQBUF, &v4lbuf);display(start[i%nbuf]);    //显示取出数据的缓存

根据返回的buf.index找到对应的mmap映射好的缓存,取出视频数据。

(10)将刚刚处理完的缓冲重新入队列尾,这样可以循环采集

// 将已经读取过数据的缓存块重新置入队列中v4lbuf.index = i%nbuf;ioctl(camfd , VIDIOC_QBUF, &v4lbuf);}

(11)停止视频的采集

int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);

(12)关闭视频设备

close(fd);

六、YUV格式转RGB

点击查看详细的信息

七、摄像头快速编程

caminfo.c

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>#include <linux/fb.h>
#include <linux/videodev2.h>
#include <linux/input.h>
#include <sys/ioctl.h>#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <setjmp.h>char formats[5][16] = {0};
struct v4l2_fmtdesc fmtdesc;
struct v4l2_format  fmt;
struct v4l2_capability cap;// 获取摄像头格式信息(固定)
void get_caminfo(int camfd)
{fmtdesc.index = 0;fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;int ret;printf("像素格式: \n");while((ret=ioctl(camfd, VIDIOC_ENUM_FMT, &fmtdesc)) == 0){printf("[%d]", fmtdesc.index);sprintf(formats[fmtdesc.index]+0, "%c", (fmtdesc.pixelformat>>8*0)&0xFF);sprintf(formats[fmtdesc.index]+1, "%c", (fmtdesc.pixelformat>>8*1)&0xFF);sprintf(formats[fmtdesc.index]+2, "%c", (fmtdesc.pixelformat>>8*2)&0xFF);sprintf(formats[fmtdesc.index]+3, "%c", (fmtdesc.pixelformat>>8*3)&0xFF);printf("\"%s\"", formats[fmtdesc.index]);printf("(详细描述: %s)\n", fmtdesc.description);fmtdesc.index++;}
}// 获取摄像头格式信息(可调)
void get_camfmt(int camfd)
{bzero(&fmt, sizeof(fmt));fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if(ioctl(camfd, VIDIOC_G_FMT, &fmt) == -1){printf("获取摄像头格式信息失败: %s\n", strerror(errno));exit(0);}printf("分辨率: %d×%d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);printf("像素格式: ");switch(fmt.fmt.pix.pixelformat){case V4L2_PIX_FMT_MJPEG:printf("V4L2_PIX_FMT_MJPEG\n");break;case V4L2_PIX_FMT_JPEG:printf("V4L2_PIX_FMT_JPEG\n");break;case V4L2_PIX_FMT_MPEG:printf("V4L2_PIX_FMT_MPEG\n");break;case V4L2_PIX_FMT_MPEG1:printf("V4L2_PIX_FMT_MPEG1\n");break;case V4L2_PIX_FMT_MPEG2:printf("V4L2_PIX_FMT_MPEG2\n");break;case V4L2_PIX_FMT_MPEG4:printf("V4L2_PIX_FMT_MPEG4\n");break;case V4L2_PIX_FMT_H264:printf("V4L2_PIX_FMT_H264\n");break;case V4L2_PIX_FMT_XVID:printf("V4L2_PIX_FMT_XVID\n");break;case V4L2_PIX_FMT_RGB24:printf("V4L2_PIX_FMT_RGB24\n");break;case V4L2_PIX_FMT_BGR24:printf("V4L2_PIX_FMT_BGR24\n");break;case V4L2_PIX_FMT_YUYV:printf("V4L2_PIX_FMT_YUYV\n");break;case V4L2_PIX_FMT_YYUV:printf("V4L2_PIX_FMT_YYUV\n");break;case V4L2_PIX_FMT_YVYU:printf("V4L2_PIX_FMT_YVYU\n");break;case V4L2_PIX_FMT_YUV444:printf("V4L2_PIX_FMT_YUV444\n");break;case V4L2_PIX_FMT_YUV410:printf("V4L2_PIX_FMT_YUV410\n");break;case V4L2_PIX_FMT_YUV420:printf("V4L2_PIX_FMT_YUV420\n");break;case V4L2_PIX_FMT_YVU420:printf("V4L2_PIX_FMT_YVU420\n");break;case V4L2_PIX_FMT_YUV422P:printf("V4L2_PIX_FMT_YUV422P\n");break;default:printf("未知\n");}
}// 获取摄像头设备的基本参数
void get_camcap(int camfd)
{bzero(&cap, sizeof(cap));if(ioctl(camfd, VIDIOC_QUERYCAP, &cap) == -1){printf("获取摄像头基本信息失败: %s\n", strerror(errno));exit(0);}printf("驱动:%s\n", cap.driver);printf("显卡:%s\n", cap.card);printf("总线:%s\n", cap.bus_info);printf("版本:%d\n", cap.version);if((cap.capabilities&V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE){printf("该设备为视频采集设备\n");}if((cap.capabilities&V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING){printf("该设备支持流IO操作\n\n");}
}// 配置摄像头像素格式
void set_camfmt(int camfd)
{struct v4l2_format *tmp = calloc(1, sizeof(*tmp));bzero(tmp, sizeof(*tmp));tmp->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;tmp->fmt.pix.width  = 800;tmp->fmt.pix.height = 448;printf("\n请选择要配置的像素格式:");int n; scanf("%d", &n);if(!strcmp(formats[n], "JPEG")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;else if(!strcmp(formats[n], "MJPG")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;else if(!strcmp(formats[n], "MPEG")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;else if(!strcmp(formats[n], "YUYV")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;else if(!strcmp(formats[n], "YVYU")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_YVYU;else if(!strcmp(formats[n], "H264")) tmp->fmt.pix.pixelformat = V4L2_PIX_FMT_H264;else{printf("对不起,所选格式无法配置.\n");exit(0);}	tmp->fmt.pix.field = V4L2_FIELD_INTERLACED;if(ioctl(camfd, VIDIOC_S_FMT, tmp) == -1){printf("ioctl() VIDIOC_S_FMT 失败了: %s\n", strerror(errno));}
}

common.h

///#ifndef __COMMON_H
#define __COMMON_H#include <stdio.h>
#include <stdint.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>#include <linux/fb.h>
#include <linux/videodev2.h>
#include <linux/input.h>
#include <sys/ioctl.h>#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <setjmp.h>
#include <pthread.h>
#include <semaphore.h>extern char formats[5][16];
extern struct v4l2_fmtdesc fmtdesc;
extern struct v4l2_format  fmt;
extern struct v4l2_capabilities cap;// 获取摄像头格式信息(固定)
void get_caminfo(int camfd);// 获取/设置摄像头格式信息(可调)
void get_camfmt(int camfd);
void set_camfmt(int camfd, char *pixfmt);// 获取摄像头设备的基本参数
void get_camcap(int camfd);#define MIN(a, b) \({ \typeof(a) _a = a; \typeof(b) _b = b; \(void)(&_a==&_b); \_a < _b ? _a : _b;\})#endif

main.c

#include "common.h"#define SCREENSIZE 800*480*4#define MIN(a, b) \({ \typeof(a) _a = a; \typeof(b) _b = b; \(void)(&_a==&_b); \_a < _b ? _a : _b; \})int redoffset  ;
int greenoffset;
int blueoffset ;int lcd;
struct fb_var_screeninfo lcdinfo;
uint8_t *fb;int SCREEN_W, SCREEN_H;
int CAMERA_W, CAMERA_H;int R[256][256];
int G[256][256][256];
int B[256][256];sem_t s;void *convert(void *arg)
{/*******************************R = Y + 1.042*(V-128);G = Y - 0.344*(U-128)-0.714*(V-128);B = Y + 1.772*(U-128);*******************************/pthread_detach(pthread_self());for(int i=0; i<256; i++){for(int j=0; j<256; j++){R[i][j] = i + 1.042*(j-128);R[i][j] = R[i][j]>255 ? 255 : R[i][j];R[i][j] = R[i][j]<0   ? 0   : R[i][j];B[i][j] = i + 1.772*(j-128);B[i][j] = B[i][j]>255 ? 255 : B[i][j];B[i][j] = B[i][j]<0   ? 0   : B[i][j];for(int k=0; k<256; k++){G[i][j][k] = i - 0.344*(j-128)-0.714*(k-128);G[i][j][k] = G[i][j][k]>255 ? 255 : G[i][j][k];G[i][j][k] = G[i][j][k]<0   ? 0   : G[i][j][k];}}}sem_post(&s);
}void display(uint8_t *yuv)
{static uint32_t shown = 0;int R0, G0, B0;int R1, G1, B1;uint8_t Y0, U;uint8_t Y1, V;int w = MIN(SCREEN_W, CAMERA_W);int h = MIN(SCREEN_H, CAMERA_H);// 画显存之前,先把LCD移动到不可见区域lcdinfo.xoffset = 0;lcdinfo.yoffset = 480 * ((shown+1)%2);ioctl(lcd, FBIOPAN_DISPLAY, &lcdinfo);uint8_t *fbtmp = fb;fbtmp += (shown%2) * SCREENSIZE;int yuv_offset, lcd_offset;for(int y=0; y<h; y++){for(int x=0; x<w; x+=2){yuv_offset = ( CAMERA_W*y + x ) * 2;lcd_offset = ( SCREEN_W*y + x ) * 4;Y0 = *(yuv + yuv_offset + 0);U  = *(yuv + yuv_offset + 1);Y1 = *(yuv + yuv_offset + 2);V  = *(yuv + yuv_offset + 3);*(fbtmp + lcd_offset + redoffset  +0) = R[Y0][V];*(fbtmp + lcd_offset + greenoffset+0) = G[Y0][U][V];*(fbtmp + lcd_offset + blueoffset +0) = B[Y0][U];*(fbtmp + lcd_offset + redoffset  +4) = R[Y1][V];*(fbtmp + lcd_offset + greenoffset+4) = G[Y1][U][V];*(fbtmp + lcd_offset + blueoffset +4) = B[Y1][U];}}shown++;
}void usage(int argc, char *argv[])
{if(argc != 2){printf("Usage: %s </dev/videoX>\n", argv[0]);exit(0);}
}int main(int argc, char *argv[])
{usage(argc, argv);sem_init(&s, 0, 0);// 打开LCD设备lcd = open("/dev/fb0", O_RDWR);if(lcd == -1){perror("open \"/dev/fb0\" failed");exit(0);}// 获取LCD显示器的设备参数ioctl(lcd, FBIOGET_VSCREENINFO, &lcdinfo);SCREEN_W = lcdinfo.xres;SCREEN_H = lcdinfo.yres;fb = mmap(NULL, lcdinfo.xres* lcdinfo.yres_virtual* lcdinfo.bits_per_pixel/8,PROT_READ | PROT_WRITE, MAP_SHARED, lcd, 0);if(fb == MAP_FAILED){perror("mmap failed");exit(0);}// 清屏bzero(fb, 2 * lcdinfo.xres * lcdinfo.yres * 4);// 获取RGB偏移量redoffset  = lcdinfo.red.offset/8;greenoffset= lcdinfo.green.offset/8;blueoffset = lcdinfo.blue.offset/8;lcdinfo.xoffset = 0;lcdinfo.yoffset = 0;ioctl(lcd, FBIOPAN_DISPLAY, &lcdinfo);// ************************************************** //// 准备好YUV-RGB映射表pthread_t tid;pthread_create(&tid, NULL, convert, NULL);// 打开摄像头设备文件int camfd = open(argv[1],O_RDWR);if(camfd == -1){printf("open %s faield: %s\n", argv[1], strerror(errno));exit(0);}printf("\n摄像头的基本参数:\n");get_camcap(camfd);get_camfmt(camfd);get_caminfo(camfd);// 配置摄像头的采集格式set_camfmt(camfd, "YUYV");get_camfmt(camfd);CAMERA_W = fmt.fmt.pix.width;CAMERA_H = fmt.fmt.pix.height;// 设置即将要申请的摄像头缓存的参数int nbuf = 3;struct v4l2_requestbuffers reqbuf;bzero(&reqbuf, sizeof (reqbuf));reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbuf.memory = V4L2_MEMORY_MMAP;reqbuf.count = nbuf;// 使用该参数reqbuf来申请缓存ioctl(camfd, VIDIOC_REQBUFS, &reqbuf);// 根据刚刚设置的reqbuf.count的值,来定义相应数量的struct v4l2_buffer// 每一个struct v4l2_buffer对应内核摄像头驱动中的一个缓存struct v4l2_buffer buffer[nbuf];int length[nbuf];uint8_t *start[nbuf];for(int i=0; i<nbuf; i++){bzero(&buffer[i], sizeof(buffer[i]));buffer[i].type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buffer[i].memory = V4L2_MEMORY_MMAP;buffer[i].index = i;ioctl(camfd, VIDIOC_QUERYBUF, &buffer[i]);length[i] = buffer[i].length;start[i] = mmap(NULL, buffer[i].length,	PROT_READ | PROT_WRITE,MAP_SHARED,	camfd, buffer[i].m.offset);ioctl(camfd , VIDIOC_QBUF, &buffer[i]);}// 启动摄像头数据采集enum v4l2_buf_type vtype= V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(camfd, VIDIOC_STREAMON, &vtype);struct v4l2_buffer v4lbuf;bzero(&v4lbuf, sizeof(v4lbuf));v4lbuf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4lbuf.memory= V4L2_MEMORY_MMAP;// 开始抓取摄像头数据并在屏幕播放视频sem_wait(&s);int i=0;while(1){// 从队列中取出填满数据的缓存v4lbuf.index = i%nbuf;ioctl(camfd , VIDIOC_DQBUF, &v4lbuf);display(start[i%nbuf]);// 将已经读取过数据的缓存块重新置入队列中v4lbuf.index = i%nbuf;ioctl(camfd , VIDIOC_QBUF, &v4lbuf);i++;}return 0;
}

通用Makefile


CROSS_COMPILE ?=arm-none-linux-gnueabi-
AS		= $(CROSS_COMPILE)as
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nmSTRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdumpexport AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMPCFLAGS := -Wall -O2 -g
CFLAGS += LDFLAGS := -lpthreadexport CFLAGS LDFLAGSTOPDIR := $(shell pwd)
export TOPDIRTARGET := testobj-y += main.o
obj-y += caminfo.oall : start_recursive_build $(TARGET)@echo $(TARGET) has been built!start_recursive_build:make -C ./ -f $(TOPDIR)/Makefile.build$(TARGET) : built-in.o$(CC) -o $(TARGET) built-in.o $(LDFLAGS)clean:rm -f $(shell find -name "*.o")rm -f $(TARGET)distclean:rm -f $(shell find -name "*.o")rm -f $(shell find -name "*.d")rm -f $(TARGET)

八、实验效果

[root@GEC6818 /mnt/v4l2]#./test  /dev/video7摄像头的基本参数:
驱动:uvcvideo
显卡:USB2.0 PC CAMERA
总线:usb-nxp-ehci-1.3
版本:197671
该设备为视频采集设备
该设备支持流IO操作分辨率: 640×480
像素格式: V4L2_PIX_FMT_YUYV
像素格式:
[0]"YUYV"(详细描述: YUV 4:2:2 (YUYV))请选择要配置的像素格式:0
分辨率: 640×480
像素格式: V4L2_PIX_FMT_YUYV

在这里插入图片描述


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

相关文章

深入学习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系…

CSS——图片自适应宽高

宽度拉伸&#xff1a;把width设置成100%&#xff0c;height设置auto 高度拉伸&#xff1a;把height设置成100%&#xff0c;width设置auto ​ <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv&…

使用CSS进行图片自适应的两个方法

1.object-fit 属性 设置好图片的宽高&#xff0c;然后设置object-fit属性为contain就是常见的图片自适应效果。 img {width: 400px;height: 400px;object-fit: contain;} object-fit: fill|contain|cover|scale-down|none|initial|inherit; 2.background 我们把图片作为背景…

css实现一个图片自适应,图片不会变形。

第一种&#xff0c;图片填充满容器 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" conten…

常见分布的概率分布及期望方差

文章转载&#xff1a;https://www.jianshu.com/p/c05bafb52877f 参考链接&#xff1a;https://blog.csdn.net/sodacoco/article/details/89041910

卡方分布(Chi-Square Distribution):

定义&#xff1a;如果我们的随机变量是标准正态分布&#xff08;详见以前博客的高斯分布&#xff09;&#xff0c;那么多个随机变量的平方和服从的分布即为卡方分布。 XY12Y22⋯Yn2 其中&#xff0c;Y1,Y2,⋯,Yn均为服从标准正态分布的随机变量&#xff0c;那么XX服从卡方分布&…

自由度为n的卡方分布χ²(n)的期望等于n、方差等于2n的证明

自由度为n的卡方分布χ&#xff08;n&#xff09;的期望等于n、方差等于2n的证明 出自&#xff1a;http://blog.sina.com.cn/s/blog_4cb6ee6c0102xh17.html posted on 2019-06-09 20:31 蔡军帅_ACM 阅读( ...) 评论( ...) 编辑 收藏

常见分布的期望与方差

离散型随机变量的期望&#xff1a;连续型随机变量的期望&#xff1a;方差公式&#xff1a;DX E(X) - (EX)

线性回归和卡方分布与方差分析

**1、线性回归 例子 import numpy as np import pylab def compute_error(b,m,data):totalError 0#Two ways to implement this#first way# for i in range(0,len(data)):# x data[i,0]# y data[i,1]## totalError (y-(m*xb))**2#second wayx data[:,0]y da…

数据分析之卡方检验

1、卡方检验定义 卡方检验&#xff0c;是用途非常广的一种假设检验方法&#xff0c;它在分类资料统计推断中的应用&#xff0c;包括两个率或两个构成比比较的卡方检验&#xff1b;多个率或多个构成比比较的卡方检验以及分类资料的相关分析等。 是一种非参数检验方法。它的原假设…