赞
踩
V4L全称是Video for Linux,是Linux内核中标准的关于视频驱动程序,目前使用比较多的版本是Video for Linux 2,简称V4L2。它为Linux下的视频驱动提供了统一的接口,使得应用程序可以使用统一的API操作不同的视频设备。从内核空间到用户空间,主要的数据流和控制类均由V4L2驱动程序的框架来定义。 V4L2支持三类设备:视频输入输出设备、VBI设备和radio设备,分别会在/dev目录下产生video*、radio*和vbi设备节点。
在Linux中,摄像头方面的标准化程度比较高,这个标准就是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 设备驱动框架向应用层提供了一套统一、标准的接口规范,应用程序按照该接口规范来进行应用
编程。 多数采用ioctl来实现。
//阻塞模式
fd = open("/dev/video0", O_RDWR);
//非阻塞模式
fd = open("/dev/video0", O_RDWR | O_NOBLOCK);
//查询设备的属性
ioctl(int fd, VIDIOC_QUERYCAP, struct v4l2_capability *cap);
//查询设备的属性
ioctl(int fd, VIDIOC_QUERYCAP, struct v4l2_capability *cap);
/**
可以从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 */
οnclick=“mdcp.copyCode(event)” style=“position: unset;”>//枚举出摄像头支持的所有像素格式: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);
//设置设备参数
ioctl(int fd, VIDIOC_G_FMT, struct v4l2_format *fmt);
/**
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: 读取当前驱动的视频捕获格式;
ioctl(int fd, VIDIOC_S_PARM, struct v4l2_streamparm *streamparm);
//申请帧缓存
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);
取该缓冲区的数据,我们需要将其映射到用户空间中
//需要查询帧缓冲的信息
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);
ioctl(int fd, VIDIOC_QBUF, struct v4l2_buffer *buf);
ioctl(int fd, VIDIOC_STREAMON, int *type); //开启视频采集
ioctl(int fd, VIDIOC_STREAMOFF, int *type); //停止视频采集
ioctl(int fd, VIDIOC_DQBUF, struct v4l2_buffer *buf);
帧缓冲出队之后,接下来便可读取数据了,然后对数据进行处理, 比如对数据进行转化,如RGB888转RGB565,将转换后的数据刷到FrameBuffer上进行显示。处理完后再入队继续采集。
停止采集图像数据,首先使用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摄像头实现效果
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。