【Linux应用】Linux--V4L2摄像头应用编程

Linux–V4L2应用编程

一、V4L2简介

​ V4L全称是Video for Linux,是Linux内核中标准的关于视频驱动程序,目前使用比较多的版本是Video for Linux 2,简称V4L2。它为Linux下的视频驱动提供了统一的接口,使得应用程序可以使用统一的API操作不同的视频设备。从内核空间到用户空间,主要的数据流和控制类均由V4L2驱动程序的框架来定义。 V4L2支持三类设备:视频输入输出设备VBI设备radio设备,分别会在/dev目录下产生video*、radio*和vbi设备节点。

​ 在Linux中,摄像头方面的标准化程度比较高,这个标准就是V4L2驱动程序,这也是业界比较公认的方式。

二、V4L2整体框架

在这里插入图片描述

三、V4L2视频采集过程

重要的一步是分配帧缓冲区,并将分配的帧缓冲区从内核空间映射到用户空间,然后将申请到的帧缓冲区在视频采集输入队列排队,剩下的就是等待视频数据的到来。 当启动视频采集后,驱动程序开始采集一帧图像数据,会把采集的图像数据放入视频采集输入队列的第一个帧缓冲区,一阵图像数据就算采集完成了。第一个帧缓冲区存满一帧图像数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出,应用程序取出图像数据可以对图像数据进行处理或存储操作,然后将帧该缓冲区放入视频采集输入队列的尾部。驱动程序接下来采集下一帧数据,放入第二个缓冲区,同样的帧缓冲区存满一帧数据后,驱动程序将该缓冲区移至视频采集输出队列,应用程序将该帧缓冲区的图像数据取出后又将该帧缓冲区放入视频输入队列尾部,这样循环往复就实现了循环采集。流程如下图所示:

在这里插入图片描述

四、V4L2应用层主要接口

​ V4L2应用层大多数用ioctl来实现对设备的IO操作。其中V4L2提供了很多ioctl宏来和应用层交互。主要定义在uapi/linux/videodev2.h文件中。 在应用程序代码中,需要包含头文件 linux/videodev2.h 。

在进行V4L2开发中,常用的命令标识符如下:
(1) VIDIOC_REQBUFS:  分配内存;
(2) VIDIOC_QUERYBUF: 把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址;
(3) VIDIOC_QUERYCAP: 查询驱动功能;
(4) VIDIOC_ENUM_FMT: 获取当前驱动支持的视频格式;
(5) VIDIOC_S_FMT:    设置当前驱动的视频捕获格式;
(6) VIDIOC_G_FMT:    读取当前驱动的视频捕获格式;
(7) VIDIOC_TRY_FMT:  验证当前驱动的显示格式;
(8) VIDIOC_CROPCAP:  查询驱动的修剪功能;
(9) VIDIOC_S_CROP:   设置视频信号的边框;
(10)VIDIOC_G_CROP:   读取视频信号的边框;
(11)VIDIOC_QBUF:     把数据从缓存中读取出来;
(12)VIDIOC_DQBUF:    把数据放回缓存队列;
(13)VIDIOC_STREAMON: 开始视频显示函数;
(14)VIDIOC_STREAMOFF:结束视频显示函数;
(15)VIDIOC_QUERYSTD: 检查当前视频设备支持的标准,例如PAL或NTSC;

在这里插入图片描述

五、V4L2应用编程流程

V4L2 设备驱动框架向应用层提供了一套统一、标准的接口规范,应用程序按照该接口规范来进行应用
编程。 多数采用ioctl来实现。
在这里插入图片描述

1、打开视频文件设备

  • 视频类设备对应的设备节点为/dev/videoX, X 为数字编号,通常从 0 开始 ,使用open打开节点, 应用程序能够使用阻塞模式非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。
//阻塞模式
fd = open("/dev/video0", O_RDWR);
//非阻塞模式
fd = open("/dev/video0", O_RDWR | O_NOBLOCK);

2、查询属性、功能

  • 查询设备的属性, 使用的指令为 VIDIOC_QUERYCAP,需要传入一个表示属性的结构体, 如下所示:
//查询设备的属性
ioctl(int fd, VIDIOC_QUERYCAP, struct v4l2_capability *cap);  


/**
  * struct v4l2_capability - Describes V4L2 device caps returned by VIDIOC_QUERYCAP
  *
  * @driver:	   name of the driver module (e.g. "bttv")
  * @card:	   name of the card (e.g. "Hauppauge WinTV")
  * @bus_info:	   name of the bus (e.g. "PCI:" + pci_name(pci_dev) )
  * @version:	   KERNEL_VERSION
  * @capabilities: capabilities of the physical device as a whole
  * @device_caps:  capabilities accessed via this particular device (node)
  * @reserved:	   reserved fields for future extensions
  */
struct v4l2_capability {
	__u8	driver[16];		/* 驱动名字 */
	__u8	card[32];		/* 设备名字 */	
	__u8	bus_info[32];	/* 总线名字 */
	__u32   version;		/* 版本信息 */	
	__u32	capabilities;	/* 功能 */
	__u32	device_caps;	/* 通过特定设备(节点)访问的能力 */
	__u32	reserved[3];	/* 保留 */	
};

可以从capabilities 成员获得很多信息,如是否是capture设备 ,使用内存映射还是直接读的方式获取图像数据。

/* 判断是否是视频采集设备 */
if (!(V4L2_CAP_VIDEO_CAPTURE & vcap.capabilities)) {
	fprintf(stderr, "Error: No capture video device!\n");
	return -1;
}


#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_READWRITE 0x01000000 /* read/write systemcalls */
#define V4L2_CAP_ASYNCIO 0x02000000 /* async I/O */
#define V4L2_CAP_STREAMING 0x04000000 /* streaming I/O ioctls */

3、设置设备参数

  • 在设置格式之前,要先枚举出所有的格式,看一看是否支持要设置的格式,然后再进一步设置 。
//枚举出摄像头支持的所有像素格式:VIDIOC_ENUM_FMT
ioctl(int fd, VIDIOC_ENUM_FMT, struct v4l2_fmtdesc *fmtdesc);
//枚举摄像头所支持的所有视频采集分辨率:VIDIOC_ENUM_FRAMESIZES
ioctl(int fd, VIDIOC_ENUM_FRAMESIZES, struct v4l2_frmsizeenum *frmsize);
  • 设置图像格式需要用到struct v4l2_format结构体,该结构体描述每帧图像的具体格式,包括帧类型以及图像的长、宽等信息。
//设置设备参数
ioctl(int fd, VIDIOC_G_FMT, struct v4l2_format *fmt);



/**
 * struct v4l2_format - stream data format
 * @type:	enum v4l2_buf_type; type of the data stream
 * @pix:	definition of an image format
 * @pix_mp:	definition of a multiplanar image format
 * @win:	definition of an overlaid image
 * @vbi:	raw VBI capture or output parameters
 * @sliced:	sliced VBI capture or output parameters
 * @raw_data:	placeholder for future extensions and custom formats
 */
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 */
		struct v4l2_meta_format		meta;    /* V4L2_BUF_TYPE_META_CAPTURE */
		__u8	raw_data[200];                   /* user-defined */
	} fmt;
};
  • 使用摄像头设备时,type设置成V4L2_BUF_TYPE_VIDEO_CAPTURE ,使用struct v4l2_pix_format来描述摄像头属性。
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 800;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565;
if (0 > ioctl(fd, VIDIOC_S_FMT, &fmt)) { //设置格式
	perror("ioctl error");
	return -1;
}



struct v4l2_pix_format {
	__u32			width;			//视频帧的宽度(单位:像素)
	__u32			height;			//视频帧的高度(单位:像素)
	__u32			pixelformat;	//像素格式	
	__u32			field;		/* enum v4l2_field */
	__u32			bytesperline;	/* for padding, zero if unused */
	__u32			sizeimage;
	__u32			colorspace;	/* enum v4l2_colorspace */
	__u32			priv;		/* private data, depends on pixelformat */
	__u32			flags;		/* format flags (V4L2_PIX_FMT_FLAG_*) */
	union {
		/* enum v4l2_ycbcr_encoding */
		__u32			ycbcr_enc;
		/* enum v4l2_hsv_encoding */
		__u32			hsv_enc;
	};
	__u32			quantization;	/* enum v4l2_quantization */
	__u32			xfer_func;	/* enum v4l2_xfer_func */
};

VIDIOC_S_FMT:    设置当前驱动的视频捕获格式;
VIDIOC_G_FMT:    读取当前驱动的视频捕获格式;
  • 设置帧率,需要传入struct v4l2_streamparm结构体
ioctl(int fd, VIDIOC_S_PARM, struct v4l2_streamparm *streamparm);

4、申请帧缓存

  • 读取摄像头数据的方式有两种,一种是 read 方式(对应设备功能返回的 V4L2_CAP_READWRITE );另一种则是 streaming 方式 (使用mmap,对应设备功能返回的 V4L2_CAP_STREAMING )
//申请帧缓存
ioctl(int fd, VIDIOC_REQBUFS, struct v4l2_requestbuffers *reqbuf);



struct v4l2_requestbuffers {
	__u32			count;		/* 帧缓存区的数量 */
	__u32			type;		/* enum v4l2_buf_type  缓冲帧数据格式*/
	__u32			memory;		/* enum v4l2_memory 是内存映射还是用户指针方式*/
	__u32			capabilities;
	__u32			reserved[1];
    
};

例子:

memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
tV4l2ReqBuffs.count = 3;
tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //缓冲帧数据格式
tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;			//内存映射的方式
iError = ioctl(Fd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
  • 使用 VIDIOC_REQBUFS 指令申请帧缓冲, 该缓冲区实质上是由内核所维护的,应用程序不能直接读

取该缓冲区的数据,我们需要将其映射到用户空间

//需要查询帧缓冲的信息
ioctl(int fd, VIDIOC_QUERYBUF, struct v4l2_buffer *buf);



//申请帧缓冲后、需要查询帧缓冲的信息用于mmap参数,调用 mmap()将帧缓冲映射到用户地址空间
iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);

ptVideoDevice->pucVideBuf[i] = mmap(0 /* start anywhere */ ,
        			  tV4l2Buf.length, PROT_READ, MAP_SHARED, iFd,
        			  tV4l2Buf.m.offset);

5、入队,开始采集

  • 将申请的缓冲帧依次放入缓冲帧输入队列,等待被图像采集设备依次填满
ioctl(int fd, VIDIOC_QBUF, struct v4l2_buffer *buf);
  • 开启采集
ioctl(int fd, VIDIOC_STREAMON, int *type); //开启视频采集
ioctl(int fd, VIDIOC_STREAMOFF, int *type); //停止视频采集

6、出队

  • 边采集边出队
ioctl(int fd, VIDIOC_DQBUF, struct v4l2_buffer *buf);

帧缓冲出队之后,接下来便可读取数据了,然后对数据进行处理, 比如对数据进行转化,如RGB888转RGB565,将转换后的数据刷到FrameBuffer上进行显示。处理完后再入队继续采集。

7、关闭采集

​ 停止采集图像数据,首先使用VIDIOC_STREAMOFF命令,关闭捕获图像数据。同时要注意取消内存映射和关闭句柄,防止不必要的内存泄漏。

enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (0 > ioctl(fd, VIDIOC_STREAMOFF, &type)) {
	perror("ioctl error");
	return -1;
}  

六、实现效果

使用USB摄像头实现效果
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

参考资料

  • 11
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ssq不是上上签

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值