本文是我在查阅众多资料程序后,调试成功自己的摄像头后写下的。
1.定义
V4L2(Video For Linux Two) 是内核提供给应用程序访问音、视频驱动的统一接口。也就是Linux系统下视频设备驱动好后,应用程序怎样调用相关的视频设备的编程接口。
2.工作步骤
打开设备-> 检查和设置设备属性-> 设置帧格式-> 设置一种输入输出方法-> 循环获取数据-> 关闭设备。
3.检查设备是否支持UVC协议
插上USB摄像头,命令终端输入:lsusb
然后输入:lsusb -d 0c45:6340 -v |grep "14 Video" // 0c45:6340设备ID见上图
输出如上则证明支持UVC协议
4.打开设备:
int fd = open(DEV_PATH,O_RDWR,0);//阻塞方式(活干不完不准回来)打开摄像头
if(fd<0)
{ printf("open device failed.\n"); }
5.查询和设置设备属性:VIDIOC_QUERYCAP
结构体:struct v4l2_capability
{
u8 driver[16]; // 驱动名字
u8 card[32]; // 设备名字
u8 bus_info[32]; // 设备在系统中的位置
u32 version; // 驱动版本号
u32 capabilities; // 设备支持的操作
u32 reserved[4]; // 保留字段
};//结构体 存放在/usr/include/linux/videodev2.h 路径下
代码段:
struct v4l2_capability cap;
if(ioctl(fd,VIDIOC_QUERYCAP,&cap)==-1)
{
printf("VIDIOC_QUERYCAP failed.\n");
}
printf("VIDIOC_QUERYCAP success.->DriverName:%s CardName:%s BusInfo:%s\n",cap.driver,cap.card,cap.bus_info);//查询驱动设备信息
6.显示并设置帧的格式
代码段:
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index=0;
fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("Support format:\n");
while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
{
printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
fmtdesc.index++;
}
struct v4l2_format fmt;
memset ( &fmt, 0, sizeof(fmt) );
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//这里没有设置像素大小,由设备决定
if (ioctl(fd,VIDIOC_S_FMT,&fmt) == -1)
{
printf("VIDIOC_S_FMT failed.\n");
return -1;
}
printf("VIDIOC_S_FMT sucess.\n");
if (ioctl(fd,VIDIOC_G_FMT,&fmt) == -1)
{
printf("VIDIOC_G_FMT failed.\n");
return -1;
}
printf("VIDIOC_G_FMT sucess.->fmt.fmt.width is %ld\nfmt.fmt.pix.height is %ld\n\
fmt.fmt.pix.colorspaceis%ld\n",fmt.fmt.pix.width,fmt.fmt.pix.height,fmt.fmt.pix.colorspace);
检查是否支持YUYV格式:
memset ( &fmt, 0, sizeof(fmt) );
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;
if (ioctl(fd,VIDIOC_TRY_FMT,&fmt) == -1)
{
printf("VIDIOC_TRY_FMT failed.\n");
return -1;
} printf"VIDIOC_TRY_FMTsucess.->fmt.fmt.widthis%ld\nfmt.fmt.pix.heightis%ld\n\fmt.fmt.pix.colorspaceis%ld\n",fmt.fmt.pix.width,fmt.fmt.pix.height,fmt.fmt.pix.colorspace);
7.申请和管理缓冲区
应用程序和设备有三种交换数据的方法,直接 read/write、内存映射(memory mapping)
和用户指针。这里本人采用内存映射(memory mapping)。
1.申请缓冲区
2.管理缓冲区
3.映射内存:内存映射MMAP 及定义一个结构体来映射每个缓冲帧。 相关结构体:
struct buffer{
void *start;
unsigned int length;
}*buffers;
4.缓冲放入队列
struct v4l2_requestbuffers req;
req.count = 2;//frame count.帧的个数
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if ( ioctl(fd,VIDIOC_REQBUFS,&req)==-1)
{
printf("VIDIOC_REQBUFS map failed.\n");
close(fd);
exit(-1);
}
printf("VIDIOC_REQBUFS map success.\n");
unsigned int n_buffers = 0;
buffers = calloc (req.count, sizeof(*buffers));
for(n_buffers = 0; n_buffers < req.count; ++n_buffers)
{
struct v4l2_buffer buf;
memset(&buf,0,sizeof(buf));
buf.index = n_buffers;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if(ioctl(fd,VIDIOC_QUERYBUF,&buf) == -1)
{
printf("VIDIOC_QUERYBUF failed.\n");
close(fd);
exit(-1);
}
printf("VIDIOC_QUERYBUF success.\n");
buffers[n_buffers].length = buf.length;
buffers [n_buffers].start=mmapNULL,buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,buf.m.offset);
if(MAP_FAILED == buffers[n_buffers].start)
{
printf("memory map failed.\n");
close(fd);
exit(-1);
}
printf("memory map success.\n");
if (ioctl(fd , VIDIOC_QBUF, &buf) ==-1)
{
printf("VIDIOC_QBUF failed.->n_buffers=%d\n", n_buffers);
return -1;
}
printf("VIDIOC_QBUF.->Frame buffer %d: address=0x%x, length=%d\n",n_buffers, (unsigned int)buffers[n_buffers].start, buffers[n_buffers].length);
}
8.使能设备输出数据流
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd,VIDIOC_STREAMON,&type) == -1)
{ printf("VIDIOC_STREAMON failed.\n");
return -1;
}
printf("VIDIOC_STREAMON success.\n");
9.处理数据获得.YUV文件
用YUVplayer打开即可
获得图像