V4L2 即 Video4Linux2,它是 Linux 内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处。
V4L2 支持三类设备:视频输入输出设备、 VBI 设备和 radio 设备,分别会在/dev 目录下产生 videoX、 radioX 和 vbiX 设备节点。我们常见的视频输入设备主要是摄像头。
Kernal
是否支持USB
摄像头1:摄像头连接上后 查看/dev/ 下有无video设备节点
2:判断USB摄像头是否为是否属于UVC规范
dmesg 命令查看是否有uvcvideo,并查看USB摄像头他的 VID:PID 是05a3:9320; 或者通过命令 lsusb查看摄像头的设备号(Vendor ID)和产品号(Product ID)
3.得到VID:PID后通过lsusb -d 05a3:9320 -v | grep "14 Video"查找是否有视频类接口信息
如果该摄像头兼容UVC,则会输出类似信息,若无以上信息,则是non-UVC设备
因为Linux下,USB
协议除了电气协议和标准,还有很多Class。 这些Class就是为了支持和定义某一类设备接口和交互数据格式。只要符合这类标准,则不同厂商的USB
设备,不需要特定的driver就能在Linux下使用。
例如:USB Input class,则使所有输入设备都可以直接使用。还有类似Audio Class, Pring Class,Mass Storage Class, video class等。
其中Video Class 就是我们常说的UVC(USB Video Class). 只要USB Camera符合UVC标准。理论上在Kernel Linux 就可以正常使用。
至此说明我们的摄像头设备可以在PC端上进行编程使用。我们一般说的免驱摄像头就是因为内核已经包含了驱动摄像头的类所以我们不需要再安装驱动
若内核并没有把这个驱动包含进来,所以现在为了能在板子上运行,有两种方法
1.重新配置内核,把 UVC 编进内核,并测试是否可以用
2.自己从零写这个驱动。
1.打开设备
int fd = open("/dev/video0",O_RDWR);
if(fd<0){
perror("open failed");
return -1;
}
else {
printf("open success!");
}
close(fd);
2.获取设备的支持
struct v4l2_fmtdesc fmt;
int index = 0;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while(1){
fmt.index = index++;
ret = ioctl(fd,VIDIOC_ENUM_FMT,&fmt);
if(ret<0){
perror("require fmt failed");
break;
}
else {
printf("index=%d\n",fmt.index);
printf("description=%s\n",fmt.description);
unsigned char *p =(unsigned char *) &fmt.pixelformat;
printf("pixelformat=%c%c%c%c\n",p[0],p[1],p[2],p[3]);
}
}
3.配置摄像头采集格式
struct v4l2_format format;
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = 640;
format.fmt.pix.height = 480;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
ret = ioctl(fd,VIDIOC_S_FMT,&format);
if(ret<0){
perror("set format failed");
return -1;
}
else {
printf("set format success\n");
}
4.申请内核缓存区
struct v4l2_requestbuffers reqbufs;
reqbufs.count = 4;
reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbufs.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd,VIDIOC_REQBUFS,&reqbufs);
if(ret<0){
perror("require memory failed");
return -1;
}else{
printf("require memory success\n");
}
5.查看申请的内核缓存区并做映射
struct v4l2_buffer bufs;
unsigned char *ptr[4];
bufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int j = 0;
for(j = 0;j<reqbufs.count;j++){
bufs.index = j;
ret = ioctl(fd,VIDIOC_QUERYBUF,&bufs);
if(ret<0){
perror("VIDIOC_QUERYBUF failed");
return -1;
}
else {
printf("VIDIOC_QUERYBUF success\n");
}
ptr[bufs.index]=(unsigned char *) mmap(NULL,bufs.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,bufs.m.offset);
printf("ptr:%p\n",ptr[bufs.index]);
ret = ioctl(fd,VIDIOC_QBUF,&bufs);
if(ret<0){
perror("error");
return -1;
}
}
6.开始采集
VIDIOC_STREAMON(开始采集写数据到队列)
VIDIOC_DQBUF(告诉内核我要取某个数据,将取的缓存放入读队列)
VIDIOC_QBUF(告诉内核已经使用完毕,将缓存放入写队列)
VIDIOC_STREAMOFF(关闭采集)
//------------------------start collect data------------------------------
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd,VIDIOC_STREAMON,&type);
if(ret<0){
perror("VIDIOC_STREAMON error");
return -1;
}else {
printf("VIDIOC_STREAMON success\n");
}
//-----------------get one frame data from quene---------------------------
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd,VIDIOC_DQBUF,&readbuffer);
if(ret<0){
perror("VIDIOC_DQBUF error");
return -1;
}
FILE *file = fopen("my.jpg","w+");
fwrite(ptr[readbuffer.index],readbuffer.length,1,file);
fclose(file);
//---------notify core using buffer over-----------------
ret = ioctl(fd,VIDIOC_QBUF,&readbuffer);
if(ret<0){
perror("collect data failed");
return -1;
}
//---------------------stop collect data--------------------------------
ret = ioctl(fd,VIDIOC_STREAMOFF,&type);
完整程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <string.h>
int main()
{
int ret = -1;
//---------------------------打开设备-------------------------------
int fd = open("/dev/video0",O_RDWR);
if(fd<0){
perror("open failed");
return -1;
}
else {
printf("open success!\n");
}
//----------------------设置采集格式---------------------------------
struct v4l2_format format;
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = 960;
format.fmt.pix.height = 960;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_GREY;
ret = ioctl(fd,VIDIOC_S_FMT,&format);
if(ret<0){
perror("set format failed");
return -1;
}
else {
printf("set format success\n");
}
//------------------向内核缓冲区申请内存-------------------------------------
struct v4l2_requestbuffers reqbufs;
reqbufs.count = 4;
reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbufs.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd,VIDIOC_REQBUFS,&reqbufs);
if(ret<0){
perror("require memory failed");
return -1;
}else{
printf("require memory success\n");
}
//------------------------将内核中的内存映射到用户空间--------------------------------
struct v4l2_buffer bufs;
unsigned char *ptr[4];
bufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int j = 0;
for(j = 0;j<reqbufs.count;j++){
bufs.index = j;
ret = ioctl(fd,VIDIOC_QUERYBUF,&bufs);
if(ret<0){
perror("VIDIOC_QUERYBUF failed");
return -1;
}
else {
printf("VIDIOC_QUERYBUF success\n");
}
ptr[bufs.index]=(unsigned char *) mmap(NULL,bufs.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,bufs.m.offset);
printf("ptr:%p\n",ptr[bufs.index]);
ret = ioctl(fd,VIDIOC_QBUF,&bufs);
if(ret<0){
perror("error");
return -1;
}
}
//------------------------开启视频流------------------------------
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd,VIDIOC_STREAMON,&type);
if(ret<0){
perror("VIDIOC_STREAMON error");
return -1;
}else {
printf("VIDIOC_STREAMON success\n");
}
//-----------------从缓存队列中取出一组数据---------------------------
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd,VIDIOC_DQBUF,&readbuffer);
if(ret<0){
perror("VIDIOC_DQBUF error");
return -1;
}
printf("index:%d,length:%d\n",readbuffer.index,readbuffer.length);
FILE *file = fopen("my.gif","w+");
fwrite(ptr[readbuffer.index],readbuffer.length,1,file);
fclose(file);
//---------通知内核缓冲区使用完毕-----------------
ret = ioctl(fd,VIDIOC_QBUF,&readbuffer);
if(ret<0){
perror("collect data failed");
return -1;
}
//---------------------停止采集--------------------------------
ret = ioctl(fd,VIDIOC_STREAMOFF,&type);
//-------------------释放映射------------------------------
//close device
close(fd);
return 0;
}