一、什么是V4L2
vl42是video for Linux 2的缩写,是一套Linux内核视频设备的驱动框架,该驱动框架为应用层提供一套统一的操作接口(一系列的ioctl)
假如要进行视频数据采集,大体的步骤如图左侧所示:
1. 打开设备
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main(void)
- {
- //1.打开设备
- int fd = open("/dev/video0", O_RDWR);
- if(fd < 0)
- {
- perror("打开设备失败");
- return -1;
- }
- //9.关闭设备
- close(fd);
- return 0;
- }
2. 获取支持格式
获取摄像头格式VIDIOC_ENUM_FMT--对应存储格式的结构体struct v4l2_fmtdesc
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
- #include <linux/videodev2.h>
- int main(void)
- {
- //1.打开设备
- int fd = open("/dev/video0", O_RDWR);
- if(fd < 0)
- {
- perror("打开设备失败");
- return -1;
- }
- //2.获取摄像头支持的格式ioctl(文件描述符, 命令, 与命令对应的结构体)
- struct v4l2_fmtdesc v4fmt;
- v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- int i=0;
- while(1)
- {
- v4fmt.index = i++;
- int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);
- if(ret < 0)
- {
- perror("获取失败");
- break;
- }
- printf("index=%d\n", v4fmt.index);
- printf("flags=%d\n", v4fmt.flags);
- printf("description=%s\n", v4fmt.description);
- unsigned char *p = (unsigned char *)&v4fmt.pixelformat;
- printf("pixelformat=%c%c%c%c\n", p[0],p[1],p[2],p[3]);
- printf("reserved=%d\n", v4fmt.reserved[0]);
- }
- //9.关闭设备
- close(fd);
- return 0;
- }
3.配置摄像头采集格式
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
- #include <linux/videodev2.h>
- #include <string.h>
-
- int main(void)
- {
- //1.打开设备
- int fd = open("/dev/video0", O_RDWR);
- if(fd < 0)
- {
- perror("打开设备失败");
- return -1;
- }
-
- //3.设置采集格式
- struct v4l2_format vfmt;
- vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//摄像头采集
- vfmt.fmt.pix.width = 640;//设置宽(不能任意)
- vfmt.fmt.pix.height = 480;//设置高
- vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//设置视频采集格式
- int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);
- if(ret < 0)
- {
- perror("设置格式失败");
- }
-
- memset(&vfmt, 0, sizeof(vfmt));
- vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- ret = ioctl(fd, VIDIOC_G_FMT, &vfmt);
- if(ret < 0)
- {
- perror("获取格式失败");
- }
-
- if(vfmt.fmt.pix.width == 640 && vfmt.fmt.pix.height == 480 &&
- vfmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV)
- {
- printf("设置成功\n");
- }else
- {
- printf("设置失败\n");
- }
-
- //9.关闭设备
- close(fd);
- return 0;
- }
4.申请内核缓冲区队列
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
- #include <linux/videodev2.h>
- #include <string.h>
-
- int main(void)
- {
- //1.打开设备
- int fd = open("/dev/video0", O_RDWR);
- if(fd < 0)
- {
- perror("打开设备失败");
- return -1;
- }
- //3.设置采集格式
- struct v4l2_format vfmt;
- vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//摄像头采集
- vfmt.fmt.pix.width = 640;//设置宽(不能任意)
- vfmt.fmt.pix.height = 480;//设置高
- vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//设置视频采集格式
- int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);
- if(ret < 0)
- {
- perror("设置格式失败");
- }
- //4.申请内核空间
- struct v4l2_requestbuffers reqbuffer;
- reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- reqbuffer.count = 4; //申请4个缓冲区
- reqbuffer.memory = V4L2_MEMORY_MMAP ;//映射方式
- ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
- if(ret < 0)
- {
- perror("申请队列空间失败");
- }
- //9.关闭设备
- close(fd);
- return 0;
- }
5.把内核的缓冲区队列映射到用户空间
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
- #include <linux/videodev2.h>
- #include <string.h>
- #include <sys/mman.h>
-
- int main(void)
- {
- //1.打开设备
- int fd = open("/dev/video0", O_RDWR);
- if(fd < 0)
- {
- perror("打开设备失败");
- return -1;
- }
-
- //3.设置采集格式
- struct v4l2_format vfmt;
- vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//摄像头采集
- vfmt.fmt.pix.width = 640;//设置宽(不能任意)
- vfmt.fmt.pix.height = 480;//设置高
- vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//设置视频采集格式
- int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);
- if(ret < 0)
- {
- perror("设置格式失败");
- }
-
- //4.申请内核空间
- struct v4l2_requestbuffers reqbuffer;
- reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- reqbuffer.count = 4; //申请4个缓冲区
- reqbuffer.memory = V4L2_MEMORY_MMAP ;//映射方式
- ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
- if(ret < 0)
- {
- perror("申请队列空间失败");
- }
-
- //5.映射
- unsigned char *mptr[4];//保存映射后用户空间的首地址
- unsigned int size[4];
- struct v4l2_buffer mapbuffer;
- //初始化type, index
- mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- for(int i=0; i<4; i++)
- {
- mapbuffer.index = i;
- ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);//从内核空间中查询一个空间做映射
- if(ret < 0)
- {
- perror("查询内核空间队列失败");
- }
- mptr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE,
- MAP_SHARED, fd, mapbuffer.m.offset);
- size[i]=mapbuffer.length;
-
- //通知使用完毕--‘放回去’
- ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
- if(ret < 0)
- {
- perror("放回失败");
- }
- }
- //9.关闭设备
- close(fd);
- return 0;
- }
6.开始采集
VIDIOC_STREAMON(开始采集写数据到队列中)
VIDIOC_DQBUF(告诉内核我要某一个数据,内核不可以修改)
VIDIOC_QBUF(告诉内核我已经使用完毕)
VIDIOC_STREAMOFF(停止采集-不在向队列中写数据)
- //6.开始采集
- int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- ret = ioctl(fd, VIDIOC_STREAMON, &type);
- if(ret < 0)
- {
- perror("开启失败");
- }
7.采集数据,
- //从队列中提取一帧数据
- struct v4l2_buffer readbuffer;
- readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
- if(ret < 0)
- {
- perror("提取数据失败");
- }
-
- FILE *file=fopen("my.jpg", "w+");
- //mptr[readbuffer.index]
- fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);
- fclose(file);
-
- //通知内核已经使用完毕
- ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
- if(ret < 0)
- {
- perror("放回队列失败");
- }
8. 停止采集
ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
9.释放映射
for(int i=0; i<4; i++) munmap(mptr[i], size[i]);
10.关闭设备
close(fd);