Linux ----V4L2采集图像基本流程

时间:2021-12-19 16:09:38

一般操作流程(视频设备):     

1.打开设备fd = open(FILE_VIDEO1, O_RDWR))

2.取得设备的capability 看看设备具有什么功能 比如是否具有视频输入 或者音频输入输出等 VIDIOC QUERYCAP struct v4l2 capabilit

3 选择视频输入 一个视频设备可以有多个视频输入 VIDIOC S INPUT struct v4l2 input

4 设置视频的制式和帧格式 制式包括PAL NTSC 帧的格式个包括宽度和高度等 VIDIOC S STD VIDIOC S FMT struct v4l2 std id struct v4l2 format 

5 向驱动申请帧缓冲 一般不超过5个 struct v4l2 requestbuffers 

6 将申请到的帧缓冲映射到用户空间 这样就可以直接操作采集到的帧了 而不必去复制 mmap 

7 将申请到的帧缓冲全部入队列 以便存放采集到的数据 VIDIOC QBUF struct v4l2 buffer 

8 开始视频的采集 VIDIOC STREAMON

9 出队列以取得已采集数据的帧缓冲 取得原始采集数据 VIDIOC DQBUF 

10 将缓冲重新入队列尾 这样可以循环采集 VIDIOC QBUF 

11 停止视频的采集 VIDIOC STREAMOFF 12 关闭视频设备 close fd ;

常用的结构体(参见/usr/include/linux/videodev2.h)

struct v4l2_requestbuffers reqbufs;//向驱动申请帧缓冲的请求,里面包含申请的个数
struct v4l2_capability cap;//这个设备的功能,比如是否是视频输入设备
struct v4l2_input input; //视频输入
struct v4l2_standard std;//视频的制式,比如PAL,NTSC
struct v4l2_format fmt;//帧的格式,比如宽度,高度等

struct v4l2_buffer buf;//代表驱动中的一帧
v4l2_std_id stdid;//视频制式,例如:V4L2_STD_PAL_B
struct v4l2_queryctrl query;//查询的控制

struct v4l2_control control;//具体控制的值


下面具体说明开发流程

打开视频设备

在V4L2中,视频设备被看做一个文件。使用open函数打开这个设备:

// 用非阻塞模式打开摄像头设备

int cameraFd;

cameraFd = open(“/dev/video0″, O_RDWR | O_NONBLOCK, 0);

// 如果用阻塞模式打开摄像头设备,上述代码变为:

//cameraFd = open(”/dev/video0″, O_RDWR, 0);

关于阻塞模式和非阻塞模式

应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。

设定属性及采集方式

打开视频设备后,可以设置该视频设备的属性,例如裁剪、缩放等。这一步是可选的。在Linux编程中,一般使用ioctl函数来对设备的I/O通道进行管理:

extern int ioctl (int __fd, unsigned long int __request, …) __THROW;

__fd:设备的ID,例如刚才用open函数打开视频通道后返回的cameraFd;

__request:具体的命令标志符。

在进行V4L2开发中,一般会用到以下的命令标志符:

  1. VIDIOC_REQBUFS:分配内存
  2. VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
  3. VIDIOC_QUERYCAP:查询驱动功能
  4. VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
  5. VIDIOC_S_FMT:设置当前驱动的频捕获格式
  6. VIDIOC_G_FMT:读取当前驱动的频捕获格式
  7. VIDIOC_TRY_FMT:验证当前驱动的显示格式
  8. VIDIOC_CROPCAP:查询驱动的修剪能力
  9. VIDIOC_S_CROP:设置视频信号的边框
  10. VIDIOC_G_CROP:读取视频信号的边框
  11. VIDIOC_QBUF:把数据从缓存中读取出来
  12. VIDIOC_DQBUF:把数据放回缓存队列
  13. VIDIOC_STREAMON:开始视频显示函数
  14. VIDIOC_STREAMOFF:结束视频显示函数
  15. VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。

这些IO调用,有些是必须的,有些是可选择的。

检查当前视频设备支持的标准

在亚洲,一般使用PAL(720X576)制式的摄像头,而欧洲一般使用NTSC(720X480),使用VIDIOC_QUERYSTD来检测:

v4l2_std_id std;

do {

ret = ioctl(fd, VIDIOC_QUERYSTD, &std);

} while (ret == -1 && errno == EAGAIN);

switch (std) {

case V4L2_STD_NTSC:

//……

case V4L2_STD_PAL:

//……

}

设置视频捕获格式

当检测完视频设备支持的标准后,还需要设定视频捕获格式:

struct v4l2_format    fmt;

memset ( &fmt, 0, sizeof(fmt) );

fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

fmt.fmt.pix.width = 720;

fmt.fmt.pix.height = 576;

fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;

fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {

return -1;

}

v4l2_format结构体定义如下:

struct v4l2_format

{

enum v4l2_buf_type type;    // 数据流类型,必须永远是//V4L2_BUF_TYPE_VIDEO_CAPTURE

union

{

struct v4l2_pix_format    pix;

struct v4l2_window        win;

struct v4l2_vbi_format    vbi;

__u8    raw_data[200];

} fmt;

};

struct v4l2_pix_format

{

__u32                   width;         // 宽,必须是16的倍数

__u32                   height;        // 高,必须是16的倍数

__u32                   pixelformat;   // 视频数据存储类型,例如是//YUV4:2:2还是RGB

enum v4l2_field         field;

__u32                   bytesperline;

__u32                   sizeimage;

enum v4l2_colorspace    colorspace;

__u32                   priv;

};

分配内存

接下来可以为视频捕获分配内存:

struct v4l2_requestbuffers  req;

if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {

return -1;

}

v4l2_requestbuffers定义如下:

struct v4l2_requestbuffers

{

__u32               count;  // 缓存数量,也就是说在缓存队列里保持多少张照片

enum v4l2_buf_type  type;   // 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE

enum v4l2_memory    memory; // V4L2_MEMORY_MMAP 或V4L2_MEMORY_USERPTR

__u32               reserved[2];

};

获取并记录缓存的物理空间

使用VIDIOC_REQBUFS,我们获取了req.count个缓存,下一步通过调用VIDIOC_QUERYBUF命令来获取这些缓存的地址,然后使用mmap函数转换成应用程序中的绝对地址,最后把这段缓存放入缓存队列:



typedef struct VideoBuffer {

void *start;

size_t  length;

} VideoBuffer;

 

VideoBuffer*          buffers = calloc( req.count, sizeof(*buffers) );

struct v4l2_buffer    buf;

 

for (numBufs = 0; numBufs < req.count; numBufs++) {

memset( &buf, 0, sizeof(buf) );

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory = V4L2_MEMORY_MMAP;

buf.index = numBufs;

// 读取缓存

if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {

return -1;

}

buffers[numBufs].length = buf.length;

// 转换成相对地址

buffers[numBufs].start = mmap(NULL, buf.length,

PROT_READ | PROT_WRITE,

MAP_SHARED,

fd, buf.m.offset);

 

if (buffers[numBufs].start == MAP_FAILED) {

return -1;

}

 

// 放入缓存队列

if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {

return -1;

}

}

关于视频采集方式

操作系统一般把系统使用的内存划分成用户空间和内核空间,分别由应用程序管理和操作系统管理。应用程序可以直接访问内存的地址,而内核空间存放的是 供内核访问的代码和数据,用户不能直接访问。v4l2捕获的数据,最初是存放在内核空间的,这意味着用户不能直接访问该段内存,必须通过某些手段来转换地 址。

一共有三种视频采集方式:使用read、write方式;内存映射方式和用户指针模式。

read、write方式:在用户空间和内核空间不断拷贝数据,占用了大量用户内存空间,效率不高。

内存映射方式:把设备里的内存映射到应用程序中的内存控件,直接处理设备内存,这是一种有效的方式。上面的mmap函数就是使用这种方式。

用户指针模式:内存片段由应用程序自己分配。这点需要在v4l2_requestbuffers里将memory字段设置成V4L2_MEMORY_USERPTR。

处理采集数据

V4L2有一个数据缓存,存放req.count数量的缓存数据。数据缓存采用FIFO的方式,当应用程序调用缓存数据时,缓存队列将最先采集到的 视频数据缓存送出,并重新采集一张视频数据。这个过程需要用到两个ioctl命令,VIDIOC_DQBUF和VIDIOC_QBUF:

struct v4l2_buffer buf;

memset(&buf,0,sizeof(buf));

buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory=V4L2_MEMORY_MMAP;

buf.index=0;

//读取缓存

if (ioctl(cameraFd, VIDIOC_DQBUF, &buf) == -1)

{

return -1;

}

//…………视频处理算法

//重新放入缓存队列

if (ioctl(cameraFd, VIDIOC_QBUF, &buf) == -1) {

return -1;

}

关闭视频设备

使用close函数关闭一个视频设备

close(cameraFd)

还需要使用munmap方法。

  
[cpp]  view plain  copy
  1. #include <errno.h>  
  2. #include <fcntl.h>  
  3. #include <linux/videodev2.h>  
  4. #include <stdint.h>  
  5. #include <stdio.h>  
  6. #include <string.h>  
  7. #include <sys/ioctl.h>  
  8. #include <sys/mman.h>  
  9. #include <unistd.h>  
  10. #include <opencv2/core/core.hpp>  
  11. #include <opencv2/highgui/highgui.hpp>  
  12. #include <iostream>  
  13.   
  14. uchar *buffer;                          //buffers 指针记录缓冲帧  
  15.   
  16. #define IMAGEWIDTH 640  
  17. #define IMAGEHEIGHT 480  
  18.   
  19. #define TRUE 1  
  20. #define FALSE 0  
  21.   
  22. #define FILE_VIDEO1 "/dev/video0"  
  23.   
  24. static int fd;                          //设备描述符  
  25. struct v4l2_streamparm setfps;          //结构体v4l2_streamparm来描述视频流的属性  
  26. struct v4l2_capability cap;             //取得设备的capability,看看设备具有什么功能,比如是否具有视频输入,或者音频输入输出等  
  27. struct v4l2_fmtdesc fmtdesc;            //枚举设备所支持的image format:  VIDIOC_ENUM_FMT  
  28. struct v4l2_format fmt,fmtack;          //子结构体struct v4l2_pix_format设置摄像头采集视频的宽高和类型:V4L2_PIX_FMT_YYUV V4L2_PIX_FMT_YUYV  
  29. struct v4l2_requestbuffers req;         //向驱动申请帧缓冲的请求,里面包含申请的个数  
  30. struct v4l2_buffer buf;                 //代表驱动中的一帧  
  31. enum   v4l2_buf_type type;              //帧类型  
  32.   
  33. int init_v4l2(void);                    //初始化  
  34. int v4l2_grab(void);                    //采集  
  35.   
  36.   
  37.   
  38. int main()  
  39. {  
  40.         printf("first~~\n");  
  41.         if(init_v4l2() == FALSE){      //打开摄像头  
  42.             printf("Init fail~~\n");  
  43.             exit(1);  
  44.         }  
  45.         printf("second~~\n");  
  46.         if(v4l2_grab() == FALSE){  
  47.             printf("grab fail~~\n");  
  48.             exit(2);  
  49.         }  
  50.         buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;           //Stream 或者Buffer的类型。此处肯定为V4L2_BUF_TYPE_VIDEO_CAPTURE  
  51.         buf.memory = V4L2_MEMORY_MMAP;                    //既然是Memory Mapping模式,则此处设置为:V4L2_MEMORY_MMAP  
  52.         printf("third~~\n");  
  53.         cvNamedWindow("one",CV_WINDOW_AUTOSIZE);  
  54.         IplImage* img;  
  55.         CvMat cvmat;  
  56.         int i = 100;  
  57.         double t;  
  58.         while(1){  
  59.                 t = (double)cvGetTickCount();                      //调用时钟测时间  
  60.                 ioctl(fd,VIDIOC_DQBUF,&buf);  
  61.                 buf.index = 0;  
  62.                 cvmat = cvMat(IMAGEHEIGHT,IMAGEWIDTH,CV_8UC3,(void*)buffer);//CV_8UC3  
  63.                 //t = (double)cvGetTickCount();  
  64.                 img = cvDecodeImage(&cvmat,1);  
  65.                 //t=(double)cvGetTickCount()-t;  
  66.                 //printf("used time is %gms\n",(t/(cvGetTickFrequency()*1000)));  
  67.                 if(!img)    printf("No img\n");  
  68.                 cvShowImage("one",img);  
  69.                 cvReleaseImage(&img);  
  70.                 ioctl(fd,VIDIOC_QBUF,&buf);                      //在 driver 内部管理着两个 buffer queues ,一个输入队列,一个输出队列。  
  71.                                                  //对于 capture device 来说,当输入队列中的 buffer 被塞满数据以后会自动变为输出队列,  
  72.                                                  //等待调用 VIDIOC_DQBUF 将数据进行处理以后重新调用 VIDIOC_QBUF 将 buffer 重新放进输入队列.  
  73.                 if((cvWaitKey(1)&255) == 27)    exit(0);  
  74.                 t=(double)cvGetTickCount()-t;  
  75.                 printf("used time is %gms\n",(t/(cvGetTickFrequency()*1000)));  
  76.         }  
  77.   
  78.         ioctl(fd,VIDIOC_STREAMOFF,&type);         // 停止视频采集命令,应用程序调用VIDIOC_ STREAMOFF停止视频采集命令后,视频设备驱动程序不在采集视频数据。  
  79.         return 0;  
  80. }  
  81.   
  82. int init_v4l2(void){  
  83.         if ((fd = open(FILE_VIDEO1, O_RDWR)) == -1){  
  84.             printf("Opening video device error\n");  
  85.             return FALSE;  
  86.         }  
  87.         if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1){               // 查询视频设备的功能  
  88.                 printf("unable Querying Capabilities\n");  
  89.                 return FALSE;  
  90.         }  
  91.         else  
  92. /* 
  93.         { 
  94.         printf( "Driver Caps:\n" 
  95.                 "  Driver: \"%s\"\n" 
  96.                 "  Card: \"%s\"\n" 
  97.                 "  Bus: \"%s\"\n" 
  98.                 "  Version: %d\n" 
  99.                 "  Capabilities: %x\n", 
  100.                 cap.driver, 
  101.                 cap.card, 
  102.                 cap.bus_info, 
  103.                 cap.version, 
  104.                 cap.capabilities); 
  105.  
  106.         } 
  107.  
  108.         if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE){ 
  109.             printf("Camera device %s: support capture\n",FILE_VIDEO1); 
  110.         } 
  111.         if((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING){ 
  112.             printf("Camera device %s: support streaming.\n",FILE_VIDEO1); 
  113.         } 
  114. */  
  115.         fmtdesc.index = 0;  
  116.         fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
  117.         printf("Support format: \n");  
  118.         while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc) != -1){        // 获取当前视频设备支持的视频格式  
  119.             printf("\t%d. %s\n",fmtdesc.index+1,fmtdesc.description);  
  120.             fmtdesc.index++;  
  121.         }  
  122.         //set fmt  
  123.         fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
  124.         fmt.fmt.pix.width = IMAGEWIDTH;                    
  125.         fmt.fmt.pix.height = IMAGEHEIGHT;  
  126.         fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; //*************************V4L2_PIX_FMT_YUYV****************可以选择  
  127.         fmt.fmt.pix.field = V4L2_FIELD_NONE;  
  128.   
  129.         if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1){    // 设置视频设备的视频数据格式,例如设置视频图像数据的长、宽,图像格式(JPEG、YUYV格式)  
  130.             printf("Setting Pixel Format error\n");  
  131.             return FALSE;  
  132.         }  
  133.         if(ioctl(fd,VIDIOC_G_FMT,&fmt) == -1){   //获取图像格式  
  134.             printf("Unable to get format\n");  
  135.             return FALSE;  
  136.         }  
  137. //        else  
  138.   
  139. /*        {  
  140.             printf("fmt.type:\t%d\n",fmt.type);         //可以输出图像的格式  
  141.             printf("pix.pixelformat:\t%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF,(fmt.fmt.pix.pixelformat >> 8) & 0xFF,\  
  142.                    (fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);  
  143.             printf("pix.height:\t%d\n",fmt.fmt.pix.height);  
  144.             printf("pix.field:\t%d\n",fmt.fmt.pix.field);  
  145.         }  
  146. */  
  147. /* 
  148.         setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
  149.         setfps.parm.capture.timeperframe.numerator = 100; 
  150.         setfps.parm.capture.timeperframe.denominator = 100; 
  151.         printf("init %s is OK\n",FILE_VIDEO1); 
  152. */  
  153.         return TRUE;  
  154. }  
  155.   
  156. int v4l2_grab(void){  
  157.     //struct v4l2_requestbuffers req = {0};  
  158.     //4  request for 4 buffers  
  159.     req.count = 1;  
  160.     req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
  161.     req.memory = V4L2_MEMORY_MMAP;  
  162.   
  163.     if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1)        //开启内存映射或用户指针I/O  
  164.     {  
  165.         printf("Requesting Buffer error\n");  
  166.         return FALSE;  
  167.     }  
  168.     //5 mmap for buffers  
  169.     buffer = (uchar*)malloc(req.count * sizeof(*buffer));  
  170.     if(!buffer){  
  171.         printf("Out of memory\n");  
  172.         return FALSE;  
  173.     }  
  174.     unsigned int n_buffers;  
  175.     for(n_buffers = 0;n_buffers < req.count; n_buffers++){  
  176.     //struct v4l2_buffer buf = {0};  
  177.     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
  178.     buf.memory = V4L2_MEMORY_MMAP;  
  179.     buf.index = n_buffers;  
  180.     if(ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1){ // 查询已经分配的V4L2的视频缓冲区的相关信息,包括视频缓冲区的使用状态、在内核空间的偏移地址、缓冲区长度等。  
  181.         printf("Querying Buffer error\n");  
  182.         return FALSE;  
  183.         }  
  184.   
  185.     buffer = (uchar*)mmap (NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);  
  186.   
  187.     if(buffer == MAP_FAILED){  
  188.         printf("buffer map error\n");  
  189.         return FALSE;  
  190.         }  
  191.     printf("Length: %d\nAddress: %p\n", buf.length, buffer);  
  192.     printf("Image Length: %d\n", buf.bytesused);  
  193.     }  
  194.     //6 queue  
  195.     for(n_buffers = 0;n_buffers <req.count;n_buffers++){  
  196.         buf.index = n_buffers;  
  197.         buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
  198.         buf.memory = V4L2_MEMORY_MMAP;  
  199.         if(ioctl(fd,VIDIOC_QBUF,&buf)){    // 投放一个空的视频缓冲区到视频缓冲区输入队列中   
  200.             printf("query buffer error\n");  
  201.             return FALSE;  
  202.         }  
  203.     }  
  204.     //7 starting  
  205.     type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
  206.     if(ioctl(fd,VIDIOC_STREAMON,&type) == -1){ //  
  207.         printf("stream on error\n");  
  208.         return FALSE;  
  209.     }  
  210.     return TRUE;  
  211. }  

可以设置采集图像的大小类型。

http://download.csdn.net/detail/yuyangyg/9780338  这里面有V4L2的一些资料