一、V4L2简介
V4L2(video for linux 2的缩写)是Linux下关于采集图片、视频和音频数据相关设备的驱动框架,为驱动和应用程序提供了一套统一的接口规范。使应用层跟硬件层分离,硬件层的驱动操作都交给V4L2,应用层只需要调用V4L2的接口即可,如下图
二、QT通过V4L2接口采集视频的步骤
1、首先打开摄像头
2、配置设备(采集的频率,图像宽高, 图像格式(jpeg, yuv--422,420)),获取设备信息,确保配置成功
3、在内核空间申请缓冲区队列(2-4个),把申请好的缓冲区对象一一映射到用户空间
4、开始 / 结束采集
三、具体实现过程
1、打开摄像头,可通过open函数来打开摄像头
this->vfd = ::open(deviceName.c_str(), O_RDWR);
if(this->vfd < 0)
{
perror("open fail");
VideoException vexp("open fail");//创建异常对象
//抛异常
throw vexp;
}
2、配置设备 和 获取设备信息
在配置时,用到了linux下videodev2.h(路径为/usr/include/linux/videodev2.h)头文件中的V4l2_format 和 v4l2_pix_format 结构体。
V4l2_format
v4l2_pix_format
将上面的结构体信息写入设备即可,通过ioctl函数将属性写入设备,该函数的参数用到了一些宏定义 (videodev2.h中)
VIDIOC_S_FMT ---设置格式属性
VIDIOC_G_FMT ---获取格式属性
函数参数说明
int ioctl(文件描述符, 命令--上的宏, 参数--上宏定义里面的结构体对象)
配置属性和获取属性的实现的代码
truct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //使用第一个结构体
fmt.fmt.pix.width = 640;
vfmt.fmt.pix.height = 480;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//(设置视频输出格式,但是要摄像头支持)
//通过ioctl把属性写入设备
int ret = ioctl(this->vfd, VIDIOC_S_FMT, &vfmt);
//通过ioctl从设备获取属性
memset(&vfmt, 0, sizeof(vfmt)); //清空
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(this->vfd, VIDIOC_G_FMT, &vfmt)
3、申请缓冲区队列, 映射到用户空间
1、使用的结构体 (同样也在videodev2.h中)
申请缓冲区队列
映射 ---从队列中拿出一个空间做映射
2、使用到的宏定义
3、通过ioctl函数进行操作
申请队列长度
ioctl(文件描述符, VIDIOC_REQBUFS(宏定义), struct v4l2_requestbuffer 对象地址)
从队列拿出一个空间做映射
ioctl(文件描述符, VIDIOC_QUERYBUF(宏定义), struct v4l2_buffer 对象地址)
放回队列
ioctl(文件描述符, VIDIOC_QBUF(宏定义), struct v4l2_buffer 对象地址)
4、具体代码实现
一、申请缓冲区队列
//1申请缓冲区队列
struct v4l2_requestbuffers reqbuffer;
reqbuffer.count = this->count;//申请缓冲区队列长度
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.memory = V4L2_MEMORY_MMAP;
int ret = ioctl(this->vfd, VIDIOC_REQBUFS, &reqbuffer);
if(ret < 0)
{
VideoException vexp("req buffer fail");//创建异常对象
throw vexp;
}
二、映射缓冲区队列到用户空间
for(int i=0; i<this->count; i++)
{
struct VideoFrame frame;
struct v4l2_buffer mapbuffer;
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
mapbuffer.index = i;
mapbuffer.memory = V4L2_MEMORY_MMAP;
//从队列中拿到内核空间
ret = ioctl(this->vfd, VIDIOC_QUERYBUF, &mapbuffer);
if(ret < 0)
{
perror("query fail");
}
//映射
frame.length = mapbuffer.length;
frame.start = (char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, this->vfd, mapbuffer.m.offset);
//空间放回队列中(内核空间)
ret = ioctl(this->vfd, VIDIOC_QBUF, &mapbuffer);
//把frame添加到容器framebuffers
framebuffers.push_back(frame);
}
4、开始/结束采集数据
1、使用到的宏定义
2、通过ioctl函数进行操作
enum v4l2_buf_type type; //赋值为:V4L2_BUF_TYPE_VIDEO_CAPTURE
开始采集 ioctl(文件描述符, VIDIOC_STREAMON(宏定义), &type)
结束采集 ioctl(文件描述符, VIDIOC_STREAMOFF(宏定义), &type)
3、采集数据的步骤
1、从队列中拿出一个缓冲区
2、从映射用户空间中把缓冲区数据取走
3、把缓冲区放回队列
3、具体代码实现
一、开始采集
//开始采集
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret = ioctl(this->vfd, VIDIOC_STREAMON, &type);
if(ret < 0)
{
perror("start fail");
}
二、结束采集
//结束采集
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret = ioctl(this->vfd, VIDIOC_STREAMOFF, &type);
if(ret < 0)
{
perror("stop fail");
}
//释放映射
for(int i=0; i<this->framebuffers.size(); i++)
{
munmap(framebuffers.at(i).start, framebuffers.at(i).length);
}
三、采集数据
struct v4l2_buffer readbuf;
readbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
readbuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(this->vfd, VIDIOC_DQBUF, &readbuf)<0)//取一针数据
{
perror("read image fail");
exit(1);
}
printf("%ld\n", readbuf.length);
*length = readbuf.length;
memcpy(imageBuffer,framebuffers[readbuf.index].start, framebuffers[readbuf.index].length); //拷贝数据
//把用完的队列空间放回队列中重复使用
if(ioctl(vfd, VIDIOC_QBUF, &readbuf)<0)
{
perror("destroy fail");
exit(1);
}
5、测试采集数据是否成功
int main()
{
V4l2Api mV4l2("/dev/video0");
mV4l2.open();
char image[1000000];
int length=0;
while(1)
{
mV4l2.grapImage(image, &length);
FILE * file = fopen("./my.jpg", "w+");
fwrite(image,length, 1, file);
fclose(file);
break;
}
mV4l2.close();
return 0;
}
注意:我这里采集的视频输出格式为MJPEG,所以才能直接存储为.jpg格式的图片。