V4L2 的API

来源:互联网 发布:乐谱排版软件 编辑:程序博客网 时间:2024/06/06 19:56

这段时间在忙毕业设计,正好需要用到v4l2,就整理了下它的API,希望对大家有些帮助。

小弟不才,但希望能够把这篇文章作为一篇十分好用的v4l2基础教程,留给以后学习v4l2的朋友。能力有限,错误的地方还希望大家及时留言更正,我会第一时间更新。先感谢所有认识的和不认识的朋友的支持!

PS. 不建议从头来看这篇文章,打开它放到一边,用到某个结构体时就回来用ctrl+f 来找。这篇文章主要是介绍V4L2的控制命令和数据结构,关于V4L2的其他一些细节(如应用开发流程),将在下一篇文章中列出,本篇用到的例子可以在这里找到  http://blog.csdn.net/yinjingyu_bisheng/article/details/8947138 。

{

   跟boss鲁交流了下,像摄像头这样的设备一般是有自己独立的存储器件的---用于缓冲,采集到的数据也确实是先放到这里。但驱动是访问不到这里的。

   应用程序、驱动、内存还有设备的数据缓冲存储器的关系如图


}


一、VIDIOC_QUERYCAP  和  v4l2_capability
        1、命令功能:查询视频设备的功能(感觉这个功能是鸡肋)
        2、结构体:这个结构体由应用程序创建,然后传给驱动,由驱动完成填写

struct v4l2_capability {        __u8 driver[16];   /* 设备驱动的名字 */        __u8 card[32];    /* i.e. 设备的硬件描述信息 */        __u8 bus_info[32]; /* 总线信息 */        __u32   version;        /* 保存驱动的版本号 */        __u32 capabilities; /* 一个位掩码用来描述驱动能做的事情 */        __u32 reserved[4]; /*保留的。V4L2规则要求要求reserved被置为0*/};
        3、实例

struct v4l2_capability cap;ioctl (fd, VIDIOC_QUERYCAP, &cap);


二、VIDIOC_S_FMT  和  v4l2_format

        1、设置设备的视频捕获格式(告诉设备生产什么样的数据),当硬件正在工作或缓冲帧已经开辟,则返回失败。

//当设备作为捕获设备用时,该命令对应的驱动回调函数是:int (*vidioc_s_fmt_cap)(struct file *file, void *private_data,struct v4l2_format *f);

      2、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;                struct v4l2_sliced_vbi_format     sliced;                __u8                              raw_data[200];        } fmt;    };
             再把涉及到的相关结构体也贴出来

             a、v4l2_pix_format  :  描述一个像素的信息,很多字段都是顾名思义的,所以就不解释这个结构体了

struct v4l2_pix_format{__u32  width;__u32  height;__u32  pixelformat;enum   v4l2_field  field;__u32  bytesperline;__u32  sizeimage;enum   v4l2_colorspace;__u32  priv;}
        3、实例:

struct v4l2_format fmt;CLEAR (fmt);                                                //#define CLEAR(x) memset (&(x), 0, sizeof (x))fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;      //数据帧类型fmt.fmt.pix.width       = 640; fmt.fmt.pix.height      = 480;fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;                 //数据的存储类型,一个像素是用YUYV即4:2:2格式还是RGB的形式表示一个点fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;            //没看懂:一帧由交错的两个field的组成ioctl (fd, VIDIOC_S_FMT, &fmt);                             //正式设置设备驱动的帧捕获格式


三、VIDIOC_REQBUFS  和 v4l2_requestbuffers  

       1、VIDIOC_REQBUFS  :向驱动申请一组缓冲帧

            (1)底层驱动命令的实现:

int (*vidioc_reqbufs) (struct file *file, void *private_data,struct v4l2_requestbuffers *req);

           (2)再调用此控制命令前,需要由应用程序填写v4l2_requestbuffers  数据结构中的所有字段(除reserved的)。

           (3)该命令不会返回缓冲帧的地址,驱动要做的只是在缓冲区中开辟出req.count个缓冲帧,然后就放在那里,等待应用程序来查询这些帧的物理地址

           (4)返回值:0表示成功。-1表示失败。

       2、 v4l2_requestbuffers  :   申请设备缓冲帧的结构体,这个结构体需要先由应用程序设置好,然后作为VIDIOC_REQBUFS命令的参数向设备申请缓冲区

struct v4l2_requestbuffers    {        __u32                   count;        //申请的缓冲帧数目。在应用层序中可以通过设置count字段为0的方式,来释放掉所有已存在的缓冲帧。        enum v4l2_buf_type      type;         // 申请的缓冲帧类型,常用的就是:视频捕获->V4L2_BUF_TYPE_VIDEO_CAPTURE;视频输出->V4L2_BUF_TYPE_VIDEO_OUTPUT        enum v4l2_memory        memory;       //如果我们想要使用内存映射的缓冲帧(就是mmap),就把这个字段设置为:V4L2_MEMORY_MMAP        __u32                   reserved[2];    };

      3、实例:

struct v4l2_requestbuffers req;                        //req就相当于应用程序向驱动开出的条件,然后让驱动或设备来买单CLEAR (req);                                           //#define CLEAR(x) memset (&(x), 0, sizeof (x))req.count               = 4;req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;req.memory              = V4L2_MEMORY_MMAP;ioctl (fd, VIDIOC_REQBUFS, &req); //向驱动申请缓冲帧if (req.count < 2)    printf("Insufficient buffer memory\n");

四、VIDIOC_QUERYBUF 和 v4l2_buffer

         1、VIDOC_QUERYBUF命令

          (1)它将转换成驱动中的vidioc_querybuf() 方法,这个方法的核心作用就是对v4l2_buffer这个结构体的赋值。

int (*vidioc_querybuf)(struct file *file, void *private_data, struct v4l2_buffer *buf);

          (2)进入这个方法时,buf的type和index字段要先赋值好,(它们可以确定一个特定的缓冲区),其余的字段由此驱动函数来填充。(驱动会检查待查询的缓冲帧类型和上面VIDIOC_REQBUFS 命令申请开辟的缓冲帧类型是否匹配,匹配成功才会返回缓冲帧的物理地址

         2、v4l2_buffer结构体:就是我们常说的视频的“一个帧”,它在应用层和驱动中的存在形式就是这个结构体啦!(这个结构体只是描述帧的一些基本信息)

struct v4l2_buffer {                  __u32           index;                     //猜的:在应用程序中采集视频信息时往往会设置一个缓冲区,index就表示这一帧在缓冲区的序号    enum v4l2_buf_type      type;              //帧类型,常用的就是:V4L2_BUF_TYPE_VIDEO_CAPTURE (视频捕获设备的帧类型)和                                                                               //V4L2_BUF_TYPE_VIDEO_OUTPU(视频输出设备的帧类型)    __u32           bytesused;                 //一帧的实际大小,对于捕获类型的设备这个值由驱动来填;对于对外输出类型的设备,这个值要由应用程序来填    __u32           flags;                     //标签,比如==V4L2_BUF_FLAG_MAPPED 暗示缓冲区己映射到用户空间。它只应用于内存映射缓冲区。    enum v4l2_field     field;                 //不懂:很多图片的源会使数据交错(就是说输出数据时是先传输奇数行,然后再传输偶数行).                                                                       //真正的摄像头设备是不会做数据的交错的.V4L2 API 允许应用使用很多种方式交错字段.                                                                           //常用的值为V4L2_FIELD_NONE (字段不交错),V4l2_FIELD_TOP (只交错顶部字面),                                                                                 //或V4L2_FIELD_ANY (无所谓). 详情见这里    struct timeval      timestamp;             //事件戳。对于输入设备来说,代表帧捕获的时间.对输出设备来说,在没有到达时间戳所代表的时间前                                                                 //,驱动不可以把帧发送出去;时间戳值为0代表越快越好。                                                                                                      //驱动会把时间戳设为:帧的第一个字节,传送到设备的时间;    struct v4l2_timecode    timecode;          //用来存放时间编码,对于视频编辑类的应用是非常有用的    __u32           sequence;                  //在驱动中维护一个递增的计数,用来记录所有传输的帧的序号;                                                                                                 //每一帧传送时,它都会在sequence字段中存入现行序号      /* memory location */      enum v4l2_memory        memory;            //memory 字段表示的是缓冲是内存映射的还是用户空间的。    union {          __u32           offset;          unsigned long   userptr;      } m;                                      //对于内存映谢的缓冲区,m.offset 描述的是缓冲区的位置.官方定义是:                                                                                        //它描述的是“从设备存储器开发的缓冲区偏移”,但其实质却是一个 magic cookie,                                                                              //应用可以将其传给mmap(),以确定哪一个缓冲区被映射了。然而对于用户空间缓冲区                                                                               //而言,m.userptr是缓冲区的用户空间地址。    __u32           length;      __u32           input;                    //input 字段可以用来快速切换捕获设备的输入 – 当然,这还得设备支持帧与帧间的快速切换才行    __u32           reserved;                 //reserved字段应置0.};

       3、实例:

struct buffer {//用于保存帧的一个缓冲区        void *                  start;   //帧在内存中的首地址,地址是void * 类型的        size_t                  length;};struct buffer *         buffers         = NULL;static unsigned int     n_buffers       = 0;

//6、连续的从设备中获取req.count个缓冲帧的物理地址,并把它映射到用户空间for (n_buffers = 0; n_buffers < req.count; ++n_buffers) //n_buffers 内存帧缓冲区计数器,{   struct v4l2_buffer buf;   //缓冲帧, 在驱动和应用层上帧是以v4l2_buffer结构体形式存在的,v4l2_buffer 由应用程序创建,填充几个必要的字段,传给驱动;成功获取缓冲帧物理地址的前提是先要用VIDIOC_REQBUFS命令提前向设备申请创建缓冲帧空间,VIDIOC_QUERYBUF命令做的仅仅是把创建好的缓冲帧的物理地址通过v4l2_buffer结构体返回给应用程序   CLEAR (buf);      //同上   buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;  //这里又出现了上面的提到过的type,同样我们的值还是这个   buf.memory      = V4L2_MEMORY_MMAP;    //表示我们要把这段物理地址映射到用户空间   buf.index       = n_buffers;    // 指定当前帧在帧缓冲区(buffers数组)中属于第几帧   if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf)) //查询一个缓冲帧的物理地址,结果通过v4l2_buffer返回printf ("VIDIOC_QUERYBUF error\n");   buffers[n_buffers].length = buf.length;//通过mmap建立映射关系,mmap的返回值是映射区内存的起始地址   buffers[n_buffers].start =   mmap (NULL /* start anywhere ,第一个参数一般设成NULL,表示由内存帮我们找一个合法的内存起始地址,并作为函数返回值*/,   buf.length,  //设置要把文件中的多大区域映射到内存PROT_READ | PROT_WRITE /* 映射区域的保护方式*/,MAP_SHARED /* 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享*/,fd/*  设备描述符*/, buf.m.offset /*文件映射偏移量,这个值由驱动帮我们确定*/);//映射失败   if (MAP_FAILED == buffers[n_buffers].start)printf ("mmap failed\n");}//end of for

五、VIDIOC_QBUF 、VIDIOC_STREAMON、VIDIOC_DQBUF和v4l2_buffer 

      1、v4l2 会维护一个数据缓冲队列:

         (1)开始的时候,通过VIDIOC_QBUF 命令将所有申请的缓冲帧插入缓冲队列(等待记录数据在设备文件中的相关信息:eg. 数据的起始地址,数据大小等)

         (2)通过VIDIOC_STREAMON命令通知设备开始数据采集,待设备采集好数据后,通过数据交换把数据传送到内存中与设备对应的区域中(该区域就是设备文件在内存中的位置),v4l2把数据在设备文件中的信息写入v4l2_buffer 结构体中;

         (3)通过VIDIOC_DQBUF命令,让数据帧出队列。此时数据帧中已经记录好了数据在设备文件中的信息。在应用中我们通过此数据帧就可以访问到采集到的真实数据了。至此一轮采集终于结束了。


     2、这里的命令使用的结构体都是v4l2_buffer

     3、实例:

//7、把物理空间中的数据帧插入队列for (i = 0; i < n_buffers; ++i) {   struct v4l2_buffer buf;   CLEAR (buf);   buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;   buf.memory      = V4L2_MEMORY_MMAP;   buf.index       = i;   if (-1 == ioctl (fd, VIDIOC_QBUF, &buf))//申请到的缓冲进入列队printf ("VIDIOC_QBUF failed\n");}
//8、采集数据           type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (-1 == ioctl (fd, VIDIOC_STREAMON, &type)) //开始捕捉图像数据   printf ("VIDIOC_STREAMON failed\n");

//9、检测设备是否完成采集for (;;) //这一段涉及到异步IO{   fd_set fds;   struct timeval tv;   int r;   FD_ZERO (&fds);//将指定的文件描述符集清空   FD_SET (fd, &fds);//在文件描述符集合中增加一个新的文件描述符   /* Timeout. */   tv.tv_sec = 2;   tv.tv_usec = 0;   r = select (fd + 1, &fds, NULL, NULL, &tv);//判断是否可读(即摄像头是否准备好),tv是定时   if (-1 == r) {if (EINTR == errno) continue;printf ("select err\n");   }   if (0 == r) {fprintf (stderr, "select timeout\n");exit (EXIT_FAILURE);   }   //10、采集完毕,则从设备的输出队列中读取一帧   if (read_frame ())//如果可读(即摄像头已准备好),执行read_frame ()函数,读取数据帧,并跳出循环break;}
read_frame 函数:

static int read_frame (void){struct v4l2_buffer buf;unsigned int i;CLEAR (buf);buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;ioctl (fd, VIDIOC_DQBUF, &buf); //出列采集的帧缓冲assert (buf.index < n_buffers);   //如果表达式为真才能继续后面的语句printf ("buf.index dq is %d,\n",buf.index);fwrite(buffers[buf.index].start, buffers[buf.index].length, 1, file_fd); //将其写入文件中  ioctl (fd, VIDIOC_QBUF, &buf); //再将其入列return 1;}



*、鸡肋的命令和结构体(不看也行)

      


    5、v4l2_fmtdes  和  VIDIOC_ENUM_FMT 命令

    1、这个结构体的由来是这样的,现有的视频格式各种各样、种类繁多,而同时,同一设备只能支持其中的部分格式。驱动不应支持设备不懂的视频格式(在内核中实现视频格式的转换??!!相信用过格式工厂或其他格式转换工具的朋友都知道,这是一件相当费时费力的工作),所以驱动需要向应用层提供一个接口,供应用程序查询设备支持的视频格式。

 struct v4l2_fmtdesc    {        __u32                    index;             //从0开始的一个值,一个值对应一中视频格式        enum v4l2_buf_type       type;              //type字段描述的是数据流类型        __u32                    flags;             //这个字段只定义了一个值即V4L2_FMT_FLAG_COMPRESSED,它表示是一个压缩了的视频格式.        __u8                     description[32];   // description 是对这个格式的一种简短的字符串描述        __u32                    pixelformat;       //描述视频表现方式的fourcc编码        __u32                    reserved[4];    };
    2、命令:VIDIOC_ENUM_FMT 

          VIDIOC_ENUM_FMT 会根据v4l2_fmtdesc中的type字段解释成不同的回调函数,这里只介绍常用的,当type=V4L2_BUF_TYPE_VIDEO_CAPTURE 时对应的回调函数:

         (1)驱动内部转化成回调函数:int (*vidioc_enum_fmt_cap)(struct file *file, void *private_data,struct v4l2_fmtdesc *f);

              i、  这个回调函数会根据设备所支持的视频格式信息去填写从应用程序中传来的fmdesc结构体然后返回。

              ii、从应用程序传来的fmdesc结构体变量,需要实现由用户填好:index 和 type 这两个字段。

              index是一个从0开始的整型数字,每个数值都代表一种视频格式(应用程序可以用枚举的方式[index++] 去遍历设备支持的所有格式)。

              type字段描述的是数据流类型(估计看到这你跟笔者我一样又晕了),跟你说一个它的值就明白了:对于摄像头来说(视频捕获设备),它的数据流类型是V4L2_BUF_TYPE_VIDEO_CAPTURE                           

//例子:

struct v4l2_fmtdesc fmt;memset(&fmt, 0, sizeof(fmt));fmt.index = 0;fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;while ((ret = ioctl(dev, VIDIOC_ENUM_FMT, &fmt)) == 0) {    fmt.index++;    printf("{ pixelformat = ''%c%c%c%c'', description = ''%s'' }\n",fmt.pixelformat & 0xFF, (fmt.pixelformat >> 8) & 0xFF,(fmt.pixelformat >> 16) & 0xFF, (fmt.pixelformat >> 24) & 0xFF,fmt.description);}