在开发项目的过程中遇到了处理摄像头接收数据的需求,故而记录下,便于之后回忆.
V4L2是什么大家搜搜就有,简单来说V4L2 是专门为 linux 设备设计的一套视频框架。我们可以借助于这个框架来实现摄像头数据的接收。
我们可以把整个V4L2接收数据的代码分为以下几个部分:
①打开摄像头设备文件 ②查询摄像头相应的信息 ③设置摄像头的格式 ④(循环)接收数据 ⑤结束
①打开摄像头设备文件:
fb = open("/dev/video0",O_RDWR);//读写方式打开摄像头设备文件
if(fb == -1) //返回值-1为直白
{
perror("open video error!"); //输出失败的原因
exit(-1);
}
②查询摄像头相应的信息
查询摄像头的信息就需要用ioctl()函数来进行交互,具体的命令在/usr/include/linux/videodev2.h 中,如下图所示。
这里有很多命令,有查询的也有设置的,需要什么用什么,这里我只据几个简单的例子。(ioctl函数的第三个参数类型使用相应命令后面的类型即可)
VIDIOC_QUERYCAP命令是判断这个设备是否和内核驱动相兼容,如果兼容就会返回相应的信息:
ret = ioctl(fb,VIDIOC_QUERYCAP,&v4_cap);
if(ret < 0)
{
perror("get camera capbility error!");
}
printf("camera driver:%s,card:%s,bus_info:%s\n",v4_cap.driver,v4_cap.card,v4_cap.bus_info);
VIDIOC_ENUM_FMT命令用来获取设备传输支持的视频格式。
v4_fmat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//设置type,此时选择video捕捉
while(1)
{
v4_fmat.index = i++;//因为可能支持很多格式,所以用index来获取每一个格式
ret = ioctl(fb,VIDIOC_ENUM_FMT,&v4_fmat);
if(ret < 0)
{
perror("get camera's format error!");
break;
}
printf("index :%d\t",v4_fmat.index);
printf("camera description:%s\n",(unsigned char *)v4_fmat.description);
}
VIDIOC_G_FMT命令用来查询摄像头视频的默认格式(可以修改,但是不一定成功),比如视频帧的宽、高和格式。
v4_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//设置type,此时选择video捕捉
ret = ioctl(fb,VIDIOC_G_FMT,&v4_format);
if(ret < 0)
{
perror("query format error!");
}
printf("collect format is width:%d,height:%d\n",v4_format.fmt.pix.width,v4_format.fmt.pix.height);
注:此处的pixelformat是__u32类型的
③设置摄像头的格式
如果我们不想按照默认格式来接收,就需要更改摄像头的格式,用VIDIOC_S_FMT命令来设置设备格式,但是不一定成功(设备本身不支持),所以一般设置之后就会再用VIDIOC_G_FMT命令来查看是否设置成功。
v4_set.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4_set.fmt.pix.width = 640;
v4_set.fmt.pix.height = 480;
v4_set.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV
ret = ioctl(fb,VIDIOC_S_FMT,&v4_set);
if(ret < 0)
{
perror("set format error!");
}
pixelformat 的具体格式还是在videodev2.h文件中,如下图
④(循环)接收数据
接下来就是具体的接收摄像头的数据,一般是循环接收数据,此处之展示接收一帧数据。
接收数据步骤:向驱动申请缓冲区,建立内存映射,开始采集
VIDIOC_REQBUFS命令用来申请缓冲区和设置映射方式
v4_rebuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4_rebuf.memory = V4L2_MEMORY_MMAP;//映射方式
v4_rebuf.count = count;//申请count个缓冲区
ret = ioctl(fb,VIDIOC_REQBUFS,&v4_rebuf);
if(ret < 0)
{
perror("malloc buffer error!");
}
之后就是建立内存映射,因为一个一个字节读写使得系统调用的很频繁,故而一般使用mmap映射的方式来回去数据。
v4_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for(i = 0;i < count; i++)
{
v4_buf.index = i;
ret = ioctl(fb,VIDIOC_QUERYBUF,&v4_buf);
if(ret < 0)
{
perror("set memory mmap error!");
}
mmapbuf[i] = (unsigned char*)mmap(NULL,v4_buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,fb,v4_buf.m.offset);
mmapbuf_size[i] = v4_buf.length;
printf("mmap:%d\t",i);
ret = ioctl(fb,VIDIOC_QBUF,&v4_buf);//告诉内核这一次使用完成
if(ret < 0)
{
perror("tell kernel fail in mmap!");
}
}
切记在VIDIOC_QUERYBUF之后一定要VIDIOC_QBUF告诉内核使用完成!
具体的采集用如下三个命令,一般是循环VIDIOC_DQBUF和VIDIOC_QBUF命令来不停的获取试试设备的数据。
VIDIOC_STREAMON(开始采集)
VIDIOC_DQBUF(提取数据)
VIDIOC_QBUF(告诉内核这一次使用完成)
注:VIDIOC_DQBUF之获取到数据的index和length,具体的数据在之前映射好的内存地址那里。
⑤结束
VIDIOC_STREAMOFF(停止采集)
mmap之后要munmap,
munmap(mmapbuf[i],mmapbuf_size[i]);//第一个参数是地址,第二个参数是长度。
close(fb);//关闭打开的设备文件
具体代码放在了:这里点击