V4L2是Video for linux2的简称,为linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写,摄像头在/dev/video*下,如果只有一个视频设备,通常为/dev/video0。
v4L2是针对uvc免驱usb设备的编程框架 ,主要用于采集usb摄像头等。
(1)打开摄像头设备(/dev/video0 、/dev/video1 )。
(2)设置图像格式:VIDIOC_S_FMT(视频捕获格式、图像颜色数据格式、图像宽和高)。
(3)申请缓冲区:VIDIOC_REQBUFS(缓冲区数量、缓冲映射方式、视频捕获格式)。
(4)将缓冲区映射到进程空间:VIDIOC_QUERYBUF(要映射的缓冲区下标、缓冲映射方式、视频捕获格式)。
(5)将缓冲区添加到队列中:VIDIOC_QBUF(映射的缓冲区下标、缓冲映射方式、视频捕获格式)。
(6)开启摄像头采集:VIDIOC_STREAMON (视频捕获格式)。
(7)从采集队列中取出图像数据VIDIOC_DQBUF,进行图像渲染。
V4L2是Linux下标准视频驱动框架,相关头文件信息在include/linux/videodev2.h中。
V4L2 驱动对用户空间提供字符设备,主设备号为 81,对于视频设备,其次设备号为 0-63。除此之外,次设备号为 64-127 的 Radio 收音机设备,次设备号为 192-223 的是 Teletext 广播设备,次设备号为 224-255 的是 VBI视频消影设备。
V4L2 驱动的 Video 设备在用户空间通过各种 ioctl 调用进行控制,并且可以使用 mmap 进行内存映射。
设置视频捕获格式
#define VIDIOC_S_FMT _IOWR(‘V’, 5, struct v4l2_format)
向内核申请缓冲区
#define VIDIOC_REQBUFS _IOWR(‘V’, 8, struct v4l2_requestbuffers)
将缓冲区映射到进程空间
#define VIDIOC_QUERYBUF _IOWR(‘V’, 9, struct v4l2_buffer)
将缓冲区添加到采集队列
#define VIDIOC_QBUF _IOWR(‘V’, 15, struct v4l2_buffer)
从队列中获取图像数据
#define VIDIOC_DQBUF _IOWR(‘V’, 17, struct v4l2_buffer)
开启摄像头图像采集
#define VIDIOC_STREAMON _IOW(‘V’, 18, int)
struct v4l2_format {
__u32 type;/* 类型V4L2_BUF_TYPE_VIDEO_CAPTURE*/
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE视频捕获格式*/
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE /
struct v4l2_window win; / V4L2_BUF_TYPE_VIDEO_OVERLAY /
struct v4l2_vbi_format vbi; / V4L2_BUF_TYPE_VBI_CAPTURE /
struct v4l2_sliced_vbi_format sliced; / V4L2_BUF_TYPE_SLICED_VBI_CAPTURE /
__u8 raw_data[200]; / user-defined */
} fmt;
};
struct v4l2_pix_format {
__u32 width;//图像宽度
__u32 height;//图像高度
__u32 pixelformat;//图像数据格式
__u32 field; /*enum v4l2_field */
__u32 bytesperline; /*for padding, zero if unused */
__u32 sizeimage;
__u32 colorspace; /*enum v4l2_colorspace*/
__u32 priv; /*private data, depends on pixelformat */
};
//内存映射缓冲区
struct v4l2_requestbuffers {
__u32 count; //申请缓冲区个数
__u32 type; /* enum v4l2_buf_type 视频类型 /
__u32 memory; / enum v4l2_memory 映射方式*/
__u32 reserved[2];
};
//视频缓冲区信息
struct v4l2_buffer {
__u32 index;/数组下标/
__u32 type;/视频捕获格式/
__u32 bytesused;
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
__u32 memory;/映射格式/
union {
__u32 offset;/偏移量/
unsigned long userptr;
struct v4l2_plane *planes;
int fd;
} m;
__u32 length;/映射缓冲区大小/
__u32 input;
__u32 reserved;
};
关于YUV格式图像参考:https://blog.csdn.net/sway913/article/details/120602052
打开摄像头设备,设置视频捕获格式,设置图像格式为YUYV422。
/*摄像头初始化*/
int Camera_Init(void)
{
int i=0;
/*1.打开摄像头设备*/
int fd=open(VIDEO_DEV,2);
if(fd<0)return -1;//摄像头打开失败
/*2.设置摄像头捕获格式*/
struct v4l2_format v4l2fmt;
memset(&v4l2fmt,0,sizeof(v4l2fmt));//初始化结构体
v4l2fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获格式
v4l2fmt.fmt.pix.width=1920;//图像宽度
v4l2fmt.fmt.pix.height=1080;//图像高度
v4l2fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;//YUYV颜色编码格式
if(ioctl(fd,VIDIOC_S_FMT,&v4l2fmt))return -2;//设置格式失败
printf("采集图像大小:%d*%d\n",v4l2fmt.fmt.pix.width,v4l2fmt.fmt.pix.height);
imag_w=v4l2fmt.fmt.pix.width;
imag_h=v4l2fmt.fmt.pix.height;
/*3.申请缓冲区*/
struct v4l2_requestbuffers v4l2reqbuf;
memset(&v4l2reqbuf,0,sizeof(v4l2reqbuf));//初始化结构体
v4l2reqbuf.count=4;//申请的缓冲区个数
v4l2reqbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获格式
v4l2reqbuf.memory=V4L2_MEMORY_MMAP;//内存映射
if(ioctl(fd,VIDIOC_REQBUFS,&v4l2reqbuf))return -3;//申请缓冲区失败
printf("申请的缓冲区个数:%d\n",v4l2reqbuf.count);
/*4.将缓冲区映射到进程空间*/
struct v4l2_buffer v4l2buf;
memset(&v4l2buf,0,sizeof(v4l2buf));//初始化结构体
v4l2buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获格式
v4l2buf.memory=V4L2_MEMORY_MMAP;//内存映射
for(i=0;i<v4l2reqbuf.count;i++)
{
v4l2buf.index=i;/*缓冲区下标*/
if(ioctl(fd,VIDIOC_QUERYBUF,&v4l2buf))return -4;//申请缓冲区失败
video_buff[i]=mmap(NULL,v4l2buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,v4l2buf.m.offset);
printf("video_buff[%d]=%p\n",i,video_buff[i]);
}
/*5.将映射的空间添加到采集队列*/
memset(&v4l2buf,0,sizeof(v4l2buf));//初始化结构体
v4l2buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获格式
v4l2buf.memory=V4L2_MEMORY_MMAP;//内存映射
for(i=0;i<v4l2reqbuf.count;i++)
{
v4l2buf.index=i;/*缓冲区下标*/
if(ioctl(fd,VIDIOC_QBUF,&v4l2buf))return -5;//添加到采集队列失败
}
/*6.开启摄像头*/
int type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获格式
if(ioctl(fd,VIDIOC_STREAMON,&type))return -6;//启动摄像头失败
return fd;//成功返回摄像头文件描述符
}
循环采集图像数据,将一帧图像数据保存为BMP图片。
由于摄像头图像颜色格式为YUYV格式,BMP图片颜色格式为RGB,因此需要将YUYV转RGB再写入到文件中。
/*YUYV转RGB888*/
void yuv_to_rgb(unsigned char *yuv_buffer,unsigned char *rgb_buffer,int iWidth,int iHeight)
{
int x;
int z=0;
unsigned char *ptr = rgb_buffer;
unsigned char *yuyv= yuv_buffer;
for (x = 0; x < iWidth*iHeight; x++)
{
int r, g, b;
int y, u, v;
if (!z)
y = yuyv[0] << 8;
else
y = yuyv[2] << 8;
u = yuyv[1] - 128;
v = yuyv[3] - 128;
r = (y + (359 * v)) >> 8;
g = (y - (88 * u) - (183 * v)) >> 8;
b = (y + (454 * u)) >> 8;
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
if(z++)
{
z = 0;
yuyv += 4;
}
}
}
int main()
{
int fd=Camera_Init();
if(fd<0)
{
printf("初始化摄像头失败,res=%d\n",fd);
return 0;
}
printf("初始化摄像头成功\n");
struct v4l2_buffer v4l2buf;
memset(&v4l2buf,0,sizeof(v4l2buf));//初始化结构体
v4l2buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获格式
v4l2buf.memory=V4L2_MEMORY_MMAP;//内存映射
BMP_HEADER bmp_head;//bmp头数据
BMP_INFO bmp_info;//位图数据
memset(&bmp_head,0,sizeof(BMP_HEADER));
memset(&bmp_info,0,sizeof(BMP_INFO));
bmp_head.bfType='M'<<8|'B';//图片类型
bmp_head.bfSize=imag_w*imag_h*3+sizeof(BMP_HEADER)+sizeof(BMP_INFO);
bmp_head.bfOffBits=sizeof(BMP_INFO)+sizeof(BMP_HEADER);/*RGB颜色偏移量*/
bmp_info.biSize=sizeof(BMP_INFO);//当前结构体大小
bmp_info.biWidth=imag_w;//图片宽
bmp_info.biHeight=imag_h;//图片高
bmp_info.biPlanes=1;//固定为1
bmp_info.biBitCount=24;//24位真彩色
char *rgb=malloc(imag_w*imag_h*3);//存放rgb图像数据
int count=1;
char buff[20];
FILE *fp;
while(1)
{
/*从采集队列中取出图像数据*/
if(ioctl(fd,VIDIOC_DQBUF,&v4l2buf))break;//取数据失败
printf("v4l2buff[%d]=%p\n",v4l2buf.index,video_buff[v4l2buf.index]);
/*将头数据和位图数据写入到文件中*/
snprintf(buff,sizeof(buff),"./image/%d.bmp",count);//图片名字
fp=fopen(buff,"w+b");
if(fp==NULL)
{
printf("文件创建失败\n");
continue;
}
count++;
fwrite(&bmp_head,sizeof(BMP_HEADER),1,fp);//写头数据
fwrite(&bmp_info,sizeof(BMP_INFO),1,fp);//写头数据
/*将yuyv数据转换RGB888*/
yuv_to_rgb(video_buff[v4l2buf.index],rgb,imag_w,imag_h);
/*将颜色数据写入到文件中*/
fwrite(rgb,imag_w*imag_h*3,1,fp);//写头数据
fclose(fp);
/*将缓冲区添加回采集队列中*/
if(ioctl(fd,VIDIOC_QBUF,&v4l2buf))break;//添加到采集队列失败
}
close(fd);//关闭摄像头
}