流媒体直播之五UVC摄像头的V4L2采集

Author: CaoHu
E-Mail: hnu_xiaohu@163.com Version:0.1 Date: 2018-01-29
Description: My level is limited, if there are some weaknesses, welcome criticism. If the content of the blog is involved in infringement, please contact my mailbox, it will be deleted immediately, welcome to exchange, learn from each other!


目标:
使用V4L2提供API,完成摄像头视频采集。
准备工作:
1.USB摄像头1个(这里使用的罗技C270,UVC摄像头);
2.编译环境(PC+Ubuntu14.04);
3.了解协议大概情况,有的linux低内核版本不支持UVC摄像头(即V4L2 API),关于摄像头内核驱动的内容这里就不深入讲解,感兴趣的可以自己研究,自己编写内核然后可以用加载 .ko模块的方式或者直接编译进内核使用。【驱动可以编译进内核,也可以通过模块的方式加载,UVC摄像头需要UVC driver,在linux-2.6.38版本加入内核再次之前没有加入】
框架理解:
Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。
在Linux下,所有外设都被看成一种特殊的文件,成为“设备文件”,可以象访问普通文件一样对其进行读写。一般来说,采用V4L2驱动的摄像头设备文件是/dev/video0。V4L2支持两种方式来采集图像:(1)内存映射方式(mmap)和(2)直接读取方式(read)。V4L2在include/linux/videodev.h文件中定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2的能力可在Linux内核编译阶段配置,默认情况下都有此开发接口。 而摄像头所用的主要是capature了,视频的捕捉,具体linux的调用可以参考下图。
这里写图片描述

其实其他的都比较简单,就是通过ioctl这个接口去设置一些参数。最主要的就是buf管理。他有一个或者多个输入队列和输出队列。
启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。
应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。
最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集,如图所示。

这里写图片描述

每一个帧缓冲区都有一个对应的状态标志变量,其中每一个比特代表一个状态

   V4L2_BUF_FLAG_UNMAPPED 0B0000
  V4L2_BUF_FLAG_MAPPED 0B0001
  V4L2_BUF_FLAG_ENQUEUED 0B0010
  V4L2_BUF_FLAG_DONE 0B0100

缓冲区的状态转化如图所示。
这里写图片描述

下面的程序注释的很好,就拿来参考下:
V4L2 编程
1. 定义
V4L2(Video ForLinux Two) 是内核提供给应用程序访问音、视频驱动的统一接口。
2. 工作流程:
打开设备-> 检查和设置设备属性->设置帧格式-> 设置一种输入输出方法(缓冲区管理)-> 循环获取数据-> 关闭设备。
3. 设备的打开和关闭:

#include<fcntl.h>
int open(constchar *device_name, int flags);

#include <unistd.h>
int close(intfd);

例:

int fd=open(“/dev/video0”,O_RDWR);// 打开设备  
close(fd);// 关闭设备 

注意:V4L2 的相关定义包含在头文件

int ioctl(intfd, int request, struct v4l2_capability *argp);  

相关结构体:

structv4l2_capability  
{  
__u8 driver[16];     // 驱动名字  
__u8 card[32];       // 设备名字  
__u8bus_info[32]; // 设备在系统中的位置  
__u32 version;       // 驱动版本号  
__u32capabilities;  // 设备支持的操作  
__u32reserved[4]; // 保留字段  
};  

capabilities 常用值:

V4L2_CAP_VIDEO_CAPTURE    // 是否支持图像获取

例:显示设备信息

structv4l2_capability cap;  
ioctl(fd,VIDIOC_QUERYCAP,&cap);  
printf(“DriverName:%s/nCard Name:%s/nBus info:%s/nDriverVersion:%u.%u.%u/n”,cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0XFF,(cap.version>>8)&0XFF,cap.version&OXFF);  
  1. 帧格式:
VIDIOC_ENUM_FMT// 显示所有支持的格式  
int ioctl(intfd, int request, struct v4l2_fmtdesc *argp);  
structv4l2_fmtdesc  
{  
__u32 index;   // 要查询的格式序号,应用程序设置  
enumv4l2_buf_type type;     // 帧类型,应用程序设置  
__u32 flags;    // 是否为压缩格式  
__u8       description[32];      // 格式名称  
__u32pixelformat; // 格式  
__u32reserved[4]; // 保留  
};  

例:显示所有支持的格式

structv4l2_fmtdesc fmtdesc;  
fmtdesc.index=0;  
fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;  
printf("Supportformat:/n");  
while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)  
{  
printf("/t%d.%s/n",fmtdesc.index+1,fmtdesc.description);  
fmtdesc.index++;  
}  

// 查看或设置当前格式
VIDIOC_G_FMT,VIDIOC_S_FMT
// 检查是否支持某种格式

VIDIOC_TRY_FMT  
int ioctl(intfd, int request, struct v4l2_format *argp);  
structv4l2_format  
{  
enumv4l2_buf_type type;// 帧类型,应用程序设置  
union fmt  
{  
structv4l2_pix_format pix;// 视频设备使用  
structv4l2_window win;  
structv4l2_vbi_format vbi;  
structv4l2_sliced_vbi_format sliced;  
__u8raw_data[200];  
};  
};  

structv4l2_pix_format  
{  
__u32 width;  // 帧宽,单位像素  
__u32 height;  // 帧高,单位像素  
__u32pixelformat; // 帧格式  
enum v4l2_fieldfield;  
__u32bytesperline;  
__u32 sizeimage;  
enumv4l2_colorspace colorspace;  
__u32 priv;  
};  

例:显示当前帧的相关信息

structv4l2_format fmt;  
fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;  
ioctl(fd,VIDIOC_G_FMT,&fmt);  
printf(“Currentdata format information:  
/n/twidth:%d/n/theight:%d/n”,fmt.fmt.width,fmt.fmt.height);  
structv4l2_fmtdesc fmtdesc;  
fmtdesc.index=0;  
fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;  
while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)  
{  
if(fmtdesc.pixelformat& fmt.fmt.pixelformat)  
{  
printf(“/tformat:%s/n”,fmtdesc.description);  
break;  
}  
fmtdesc.index++;  
}  

例:检查是否支持某种帧格式

structv4l2_format fmt;  
fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;  
fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_RGB32;  
if(ioctl(fd,VIDIOC_TRY_FMT,&fmt)==-1)  
if(errno==EINVAL)  
printf(“notsupport format RGB32!/n”);  
  1. 图像的缩放
VIDIOC_CROPCAP  
int ioctl(int fd,int request, struct v4l2_cropcap *argp);  
structv4l2_cropcap  
{  
enumv4l2_buf_type type;// 应用程序设置  
struct v4l2_rectbounds;//     最大边界  
struct v4l2_rectdefrect;// 默认值  
structv4l2_fract pixelaspect;  
};  

// 设置缩放

VIDIOC_G_CROP,VIDIOC_S_CROP  
int ioctl(intfd, int request, struct v4l2_crop *argp);  
int ioctl(intfd, int request, const struct v4l2_crop *argp);  
struct v4l2_crop  
{  
enumv4l2_buf_type type;// 应用程序设置  
struct v4l2_rectc;  
}  
  1. 申请和管理缓冲区,应用程序和设备有三种交换数据的方法,直接read/write ,内存映射(memorymapping) ,用户指针。这里只讨论 memorymapping.
// 向设备申请缓冲区

VIDIOC_REQBUFS  
int ioctl(intfd, int request, struct v4l2_requestbuffers *argp);  
structv4l2_requestbuffers  
{  
__u32 count;  // 缓冲区内缓冲帧的数目  
enumv4l2_buf_type type;     // 缓冲帧数据格式  
enum v4l2_memorymemory;       // 区别是内存映射还是用户指针方式  
__u32 reserved[2];  
};  

enum v4l2_memoy{V4L2_MEMORY_MMAP,V4L2_MEMORY_USERPTR};  
//count,type,memory都要应用程序设置  

例:申请一个拥有四个缓冲帧的缓冲区

structv4l2_requestbuffers req;  
req.count=4;  
req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;  
req.memory=V4L2_MEMORY_MMAP;  
ioctl(fd,VIDIOC_REQBUFS,&req);  

获取缓冲帧的地址,长度:
VIDIOC_QUERYBUF
int ioctl(intfd, int request, struct v4l2_buffer *argp);

structv4l2_buffer  
{  
__u32 index;   //buffer 序号  
enumv4l2_buf_type type;     //buffer 类型  
__u32 byteused;     //buffer 中已使用的字节数  
__u32 flags;    // 区分是MMAP 还是USERPTR  
enum v4l2_fieldfield;  
struct timevaltimestamp;// 获取第一个字节时的系统时间  
structv4l2_timecode timecode;  
__u32 sequence;// 队列中的序号  
enum v4l2_memorymemory;//IO 方式,被应用程序设置  
union m  
{  
__u32 offset;// 缓冲帧地址,只对MMAP 有效  
unsigned longuserptr;  
};  
__u32 length;// 缓冲帧长度  
__u32 input;  
__u32 reserved;  
};  

MMAP ,定义一个结构体来映射每个缓冲帧。

Struct buffer  
{  
void* start;  
unsigned intlength;  
}*buffers;  

#include<sys/mman.h>
void *mmap(void*addr, size_t length, int prot, int flags, int fd, off_t offset);
//addr 映射起始地址,一般为NULL ,让内核自动选择
//length 被映射内存块的长度
//prot 标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE
//flags 确定此内存映射能否被其他进程共享,MAP_SHARED,MAP_PRIVATE
//fd,offset, 确定被映射的内存地址
返回成功映射后的地址,不成功返回MAP_FAILED ((void*)-1);

int munmap(void*addr, size_t length);// 断开映射
//addr 为映射后的地址,length 为映射后的内存长度

例:将四个已申请到的缓冲帧映射到应用程序,用buffers 指针记录。

buffers =(buffer*)calloc (req.count, sizeof (*buffers));  
if (!buffers) {  
fprintf (stderr,"Out of memory/n");  
exit(EXIT_FAILURE);  
}  

// 映射

for (unsignedint n_buffers = 0; n_buffers < req.count; ++n_buffers) {  
struct v4l2_bufferbuf;  
memset(&buf,0,sizeof(buf));  
buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;  
buf.memory =V4L2_MEMORY_MMAP;  
buf.index =n_buffers;  
// 查询序号为n_buffers 的缓冲区,得到其起始物理地址和大小  
if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf))  
exit(-1);  
buffers[n_buffers].length= buf.length;  
// 映射内存  
buffers[n_buffers].start=mmap (NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset);  
if (MAP_FAILED== buffers[n_buffers].start)  
exit(-1);  
}  
  1. 缓冲区处理好之后,就可以开始获取数据了
// 启动/ 停止数据流  
VIDIOC_STREAMON,VIDIOC_STREAMOFF  
int ioctl(intfd, int request, const int *argp);  
//argp 为流类型指针,如V4L2_BUF_TYPE_VIDEO_CAPTURE.  
在开始之前,还应当把缓冲帧放入缓冲队列:  
VIDIOC_QBUF// 把帧放入队列  
VIDIOC_DQBUF// 从队列中取出帧  
int ioctl(intfd, int request, struct v4l2_buffer *argp);  

例:把四个缓冲帧放入队列,并启动数据流

unsigned int i;  
enum v4l2_buf_typetype;  
// 将缓冲帧放入队列  
for (i = 0; i< 4; ++i)  
{  
structv4l2_buffer buf;  
buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;  
buf.memory =V4L2_MEMORY_MMAP;  
buf.index = i;  
ioctl (fd,VIDIOC_QBUF, &buf);  
}  
type =V4L2_BUF_TYPE_VIDEO_CAPTURE;  
ioctl (fd,VIDIOC_STREAMON, &type);  

例:获取一帧并处理

structv4l2_buffer buf;  
CLEAR (buf);  
buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;  
buf.memory =V4L2_MEMORY_MMAP;  
// 从缓冲区取出一个缓冲帧  
ioctl (fd,VIDIOC_DQBUF, &buf);  
// 图像处理  
process_image(buffers[buf.index].start);  
// 将取出的缓冲帧放回缓冲区  
ioctl (fd, VIDIOC_QBUF,&buf);  

至于驱动的实现,可以参考内核中,我是用usb摄像头的,所以,其实现都是好的。主要就是应用程序的实现了。驱动都哦在uvc目录下面,这个待理解。

本人水平有限,如有不足之处,欢迎留言指正。。。
http://www.cnblogs.com/emouse/archive/2013/03/04/2943243.html这篇博客讲的也很好】
http://blog.csdn.net/eastmoon502136/article/details/8190262/此笔记博客地址】

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 首先需要安装v4l2uvc驱动,在终端中输入以下命令: ``` sudo apt-get install v4l-utils uvcdynctrl ``` 2. 然后使用v4l2-ctl命令查看摄像头设备的信息,例如: ``` v4l2-ctl --list-devices ``` 3. 接下来,使用v4l2-ctl命令设置摄像头的参数,例如分辨率、帧率等。 ``` v4l2-ctl --set-fmt-video=width=640,height=480,pixelformat=YUYV --set-parm=30 ``` 4. 接着,使用OpenCV库来进行视频采集和显示,代码示例如下: ``` #include <opencv2/opencv.hpp> #include <iostream> #include <stdio.h> using namespace cv; using namespace std; int main() { VideoCapture cap(0); //打开摄像头 if(!cap.isOpened()) //检查摄像头是否成功打开 { cout << "Error opening camera" << endl; return -1; } int fps = 30; //设置帧率 cap.set(CAP_PROP_FRAME_WIDTH, 640); //设置分辨率 cap.set(CAP_PROP_FRAME_HEIGHT, 480); cap.set(CAP_PROP_FPS, fps); namedWindow("Video", WINDOW_NORMAL); //创建窗口 VideoWriter writer("output.avi", CV_FOURCC('M', 'J', 'P', 'G'), fps, Size(640, 480)); //创建视频写入器 while(true) { Mat frame; cap >> frame; //采集一帧图像 imshow("Video", frame); //显示图像 writer.write(frame); //将图像写入视频文件 char c = waitKey(1); if(c == 27) //按下ESC键退出循环 { break; } } cap.release(); //释放摄像头 writer.release(); //释放视频写入器 destroyAllWindows(); //关闭窗口 return 0; } ``` 5. 最后编译并运行代码,即可进行视频采集和显示,并将采集到的视频保存成output.avi文件。 ``` g++ -o main main.cpp `pkg-config opencv --cflags --libs` ./main ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值