一、什么是V4L2
vl42是video for Linux 2的缩写,是一套Linux内核视频设备的驱动框架,该驱动框架为应用层提供一套统一的操作接口(一系列的ioctl)
假如要进行视频数据采集,大体的步骤如图左侧所示:
- 打开设备文件/dev/videoX;
- 根据打开的设备,查询设备能力集;
- 设置视频数据的格式、参数等;
- 分配buffer,这个buffer可以是用户态分配的,也可以是从内核中获取的;
- 开始视频流采集工作;
- 将buffer enqueue到v4l2框架,底层负责将视频数据填充后,应用层再将buffer dequeue以便获取数据,然后再将buffer enqueue,如此循环往复;
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);