1. 打开设备文件
1
2
3
4int fd; fd = open("/dev/video0",O_RDWR | O_NOBLOCK,0);
通常来说,驱动应该支持read/write和mmap两种传递帧数据的方式,但是较多情况下,采用的是mmap方式,如果应用通过read/write来和驱动进行数据交互,则在打开设备的时候设置为阻塞模式或者非阻塞模式,都可以,但是如果是应用是使用mmap来与驱动进行数据交互的话,则在打开设备时设置为阻塞模式,是没有意义的,因为驱动在数据交互的过程中,根本就不会检查这个flag是否被设置了。
2. 获取设备的capablity参数
通过系统调用VIDIOC_QUERYCAP来获取设备的capablity参数,如是设备支持视频采集还是视频输出,判断驱动是否支持read/write和mmap等等。
1
2
3struct v4l2_capability cap; int ret = ioctl(fd,VIDIOC_QUERYCAP,&cap);
3. 查询设备所支持的格式
通过VIDIOC_ENUM_FMT命令来枚举设备所支持的不同格式,实例代码如下。
1
2
3
4
5
6
7
8
9
10
11struct v4l2_fmtdesc fmt; int ret; bzero(&fmt,sizeof(fmt)); fmt.index = 0;//从0 开始枚举 fmt.type = v4l2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获设备 while(!ioctl(fd,VIDIOC_ENUM_FMT,&fmt)) { ...... fmt.index++; //枚举所有的格式 }
4.获取设备所支持的制式,通过命令VIDIOC_ENUMSTD来获取,实例代码如下:
1
2
3
4v4l2_std_id std; ioctl(fd,VIDIOC_ENUMSTD,&std); ......
5.设置视频格式
这一步是相当重要的一个步骤了,用于设置视频的捕获格式,如设置视频图像数据的长,宽,图像格式等等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18struct v4l2_format fmt; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = IMG_WIDTH; fmt.fmt.pix.height = IMG_HEIGHT; fmt.fmt.pix.pixeformat = V4L2_PIX_FMT_YUYV; fmt.fmt.pix.filed = V4L2_FIELD_INTERLACED; int ret; ret = ioctl(fd,VIDIOC_S_FMT,&fmt); bzero(&fmt,sizeof(fmt)); //设置之后,读取实际的视频格式 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if(!ioctl(fd,VIDIOC_G_GMT,&fmt) ){ printf(......); ...... }else{ ...... }
注意: 如果视频设备驱动不支持应用设定的图像格式,则视频驱动会根据具体的设备来重新修改参数fmt的值,所以,一般在应用程序中,在设置视频格式之后,要获取实际的视频格式。
6 .帧缓冲区申请与初始化
在前面也提到过,应用程序与驱动进行数据交互,主要有常用的read/write方式和流I/O方式,而流I/O方式有两种缓冲区:分别为内核空间缓存区,和用户空间缓存区,内核空间缓冲区需要使用mmap进行映射,而用户空间缓存区不需要进行mmap映射,但是会增加驱动的复杂性。这里就以常用的mmap方式为例进行介绍。实例代码如下。
1
2
3
4
5
6
7
8
9struct v4l2_requestbuffers reqbuf; bzero(&req,sizeof(reqbuf)); reqbuf.count = 3;// 设定申请缓存区的个数。 reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = V4L2_MEMORY_MMAP;//流I/O方式 int ret; ret = ioctl(fd,VIDIOC_REQBUFS,&reqbuf); ......
注意:申请缓存区的实际数目是由内核驱动层根据内存的使用情况来决定的,实际的数目可能会小于应用程序欲分配的数目。
7 查询缓存区参数,并且通过mmpap系统调用映射到进程的用户空间。
实例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22struct buffer{ void *start; size_t length; }; struct buffer *buffers; buffers = (struct buffer *)calloc(reqbuf.count,sizeof(*buffers)); int i ; for(i = 0; i < reqbuf.count;i++){ struct v4l2_buffer buf; bzero(&buf,sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memroy = V4L2_MEMORY_MMAP; buf.index = i; if(!ioctl(fd,VIDIOC_QUERYBUF,&buf)){ buffers[i].length = buf.length; buffers[i].start = mmap(NULL,buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,buf.m.offset); ...... } }
8. 将一个空的缓存区放到视频缓冲区输入队列中
其实质就是告诉内核驱动空闲缓冲区的序列号,以便内核驱动来维护内核空间的视频缓冲区,实例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15int i; for(i = 0;i< reqbuf.count;i++){ struct v4l2_buffer buf; memset(&buf,0,sizeof(struct v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if(!ioctl(fd,VIDIOC_QBUF,&buf)){ ..... }else{ ...... } }
9.开始采集
启动视频采集,应用程序通过VIDIOC_STREAMON启动视频采集过程。实例如下:
1
2
3
4enum v4l2_buf_type type; type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd,VIDIOC_STREAMON,&type);
10. 从视频输出队列中取出视频数据
在开始采集视频之后,通过select来监测数据的到来,当数据可读时,从驱动中的视频输出队列中取出视频数据。其实质就是通过ioctl来获取就绪缓存区的序列号,然后应用就可以操作对应buffers数组。
1
2
3
4
5
6
7struct v4l2_buffer buf; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if(!ioctl(fd,VIDIOC_DQBUF,&buf)){ ...... }
以上就是针对V4L2摄像头应用程序大体流程的一些概括,如有错误,敬请指正!
最后
以上就是善良龙猫最近收集整理的关于V4L2 摄像头应用程序编程的全部内容,更多相关V4L2内容请搜索靠谱客的其他文章。
发表评论 取消回复