zoukankan      html  css  js  c++  java
  • 24、vb2_buffer和videobuf_buffer比较分析

    看韦东山视频第三期摄像头驱动中构造了自己的vivi驱动,但是使用的videoBuf结构体,新的版本用的是vb2_buffer结构,我机器上(ubuntu12.04)使用的内核是linux3.2,看了看改动还是挺大的,自己看代码自己理解了下:

            首先是韦东山老师总结的摄像头驱动的架构如下
     
    摄像头驱动程序必需的11个ioctl:
        // 表示它是一个摄像头设备
     .vidioc_querycap      = vidioc_querycap,
        /* 用于列举、获得、测试、设置摄像头的数据的格式 */
     .vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
     .vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
     .vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
     .vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
        /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
     .vidioc_reqbufs       = vidioc_reqbufs,
     .vidioc_querybuf      = vidioc_querybuf,
     .vidioc_qbuf          = vidioc_qbuf,
     .vidioc_dqbuf         = vidioc_dqbuf,
     // 启动/停止
     .vidioc_streamon      = vidioc_streamon,
     .vidioc_streamoff     = vidioc_streamoff,
         
    分析数据的获取过程:
    1. 请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS          // 请求系统分配缓冲区
    2. 查询映射缓冲区:  ioctl(4, VIDIOC_QUERYBUF         // 查询所分配的缓冲区 
    3. 把缓冲区放入队列: ioctl(4, VIDIOC_QBUF             // 把缓冲区放入队列         
    4. 启动摄像头  ioctl(4, VIDIOC_STREAMON 
    5. 用select查询是否有数据  
    6. 有数据后从队列里取出缓冲区  ioctl(4, VIDIOC_DQBUF
    7. 应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据    就去读对应的地址(该地址来自前面的mmap) 
     
    对于架构都是一样的,有关于VB2_buffer 的主要是数据的获取过程,根据韦东山老师的视频中分析步骤分析如下:
    1,首先是创建并初始化一个vb2_queue结构体 ,
            static   struct vb2_queue    Myvivi_vb2_queue;
     
            struct vb2_queue  *q = &Myvivi_vb2_queue;
             q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 类型
             q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;  // 该队列支持的模式
             q->drv_priv = dev;  // 自定义模式
             q->buf_struct_size = sizeof(struct vivi_buffer);  // 将vb2_buffer结构体封装到我们自己的buffer中,此为我们自己的buffer的size
             q->ops = &vivi_video_qops;   
             q->mem_ops = &vb2_vmalloc_memops;     // 
            vb2_queue_init(q);
        其中vivi_video_qops 是 队列的操作函数,以后的REQBUFS 等请求会调用到此中的函数,其结构如下
    static struct vb2_ops vivi_video_qops = {
     .queue_setup = queue_setup,   // 必须有,vb2_queue_init中会判断
     .buf_init = buffer_init,
     .buf_prepare = buffer_prepare,
     .buf_finish = buffer_finish,
     .buf_cleanup = buffer_cleanup,
     .buf_queue = buffer_queue,  // 必须有,vb2_queue_init中会判断
     .start_streaming = start_streaming,
     .stop_streaming = stop_streaming,
     .wait_prepare = vivi_unlock,
     .wait_finish = vivi_lock,
    };
          其中vb2_vmalloc_memops用于有关此队列中mem分配问题,其中的函数一般不需要我们自己写,使用默认
    const struct vb2_mem_ops vb2_vmalloc_memops = {
         .alloc = vb2_vmalloc_alloc,
         .put = vb2_vmalloc_put,
         .vaddr = vb2_vmalloc_vaddr,
         .mmap = vb2_vmalloc_mmap,
         .num_users = vb2_vmalloc_num_users,
    };
     
    2,初始化完成后,调用VIDIOC_REQBUFS  请求系统分配缓冲区,该函数的调用过程如下
        vb2_reqbufs  ===》
                q->ops->queue_setup此函数允许我们的驱动函数自定义分配空间的大小
                 __vb2_queue_alloc  分配vivi_buffer结构体的空间(缓存区头部信息), 如果使用的是V4L2_MEMORY_MMAP类型则 调用==>__vb2_buf_mem_alloc  ==> q->mem_ops->alloc  即vb2_vmalloc_alloc 分配空间,将分配的空间指向vb2_buffer->planes[0].mem_priv ,该指针保存着分配到的空间,该指针指向vb2_vmalloc_buf结构体
    韦东山老师视频中讲的video_buf中,讲到在这不真正的分配空间,但是在vb2中此时却已经分配了空间,这是我的理解
     
    3,查询映射缓冲区 VIDIOC_QUERYBUF , 返回实际上分配到的buffer,
          查询分配好的缓存区,返回v4l2_buffer结构,设置vb->state 
     
    4,使用mmap
        vb2_mmap ==》q->mem_ops->mmap   即  vb2_vmalloc_mmap  用于映射,将上面分配好的vb2_buffer->planes[0].mem_priv指向的空间重映射到mmap参数中的用户空间
     
    5,把缓冲区放入队列: VIDIOC_QBUF     
            vb2_qbuf    将 list_add_tail(&vb->queued_entry, &q->queued_list);    将vb2_buffer 放入队列q的queued_list中
            设置vb->state = VB2_BUF_STATE_PREPARED;
     
    6,  启动摄像头   VIDIOC_STREAMON 
        vb2_streamon 
            q->streaming = 1;   
    // 如果 q->queued_list 中部位空,即有qbuf没有被处理 调用__enqueue_in_driver ()
     
    7, 用select查询是否有数据  会调用poll函数   
           vb2_poll   等待 q->done_list 中有数据,   
     
    8, 怎样往 q->done_list 中添加数据呢 ?
    每次调用qbuf 和 vidioc_streamon 时候都会查询,如果这两个条件都成立,则调用q->ops->buf_queue 将 核心中的vb2_buffer调如我们写的驱动中,放入一个列表, 在vivi中 周期性的调用函数向这个列表中的vb缓冲区中添加数据 即 向vb2_buffer->planes中添加数据 ,然后后调用  
    vb2_buffer_done(&vb, VB2_BUF_STATE_DONE);  ==》 list_add_tail(&vb->done_entry, &q->done_list);  将vivi驱动中的vb2 放入 q->done_list中 ,然后设置vb->state = VB2_BUF_STATE_DONE;
    最后wake_up(&q->done_wq);  唤醒poll中休眠的进程。
     
    9,调用VIDIOC_DQBUF, 从队列里取出缓冲区
            vb2_dqbuf  ==> __vb2_get_done_vb  将q->done_list 中的vb2_buffer中提出来,然后 将vb2_buffer中的v4l2_buffer信息返回,并将其从q->done_list 中删除
     
    10,应用程序将数据取出来(mmap的空间)
     
    总结:    
    结构体如下 
    struct vb2_buffer {
     struct v4l2_buffer v4l2_buf;    // 里面有该vb2中数据的信息
     struct v4l2_plane v4l2_planes[VIDEO_MAX_PLANES];
     
     struct vb2_queue *vb2_queue;
     
     unsigned int num_planes;
     
    /* Private: internal use only */
     enum vb2_buffer_state state;
     
     struct list_head queued_entry;
     struct list_head done_entry;
     
     struct vb2_plane planes[VIDEO_MAX_PLANES];   // 存放实际数据的结构
    };
    struct vb2_queue {
     enum v4l2_buf_type type;
     unsigned int io_modes;
     unsigned int io_flags;
     
     const struct vb2_ops *ops;
     const struct vb2_mem_ops *mem_ops;
     void *drv_priv;
     unsigned int buf_struct_size;
     
    /* private: internal use only */
     enum v4l2_memory memory;
     struct vb2_buffer *bufs[VIDEO_MAX_FRAME];
     unsigned int num_buffers;
     
     struct list_head queued_list;
     
     atomic_t queued_count;
     struct list_head done_list;
     spinlock_t done_lock;
     wait_queue_head_t done_wq;
     
     void *alloc_ctx[VIDEO_MAX_PLANES];
     unsigned int plane_sizes[VIDEO_MAX_PLANES];
     
     unsigned int streaming:1;
     
     struct vb2_fileio_data *fileio;
    };
    在vb2中细化了锁,并且将核心部分封装,使我们更容易使用。
    与vb中有冲突的地方如下
    1,结构中红色部分都为自己的数据,一般对于我们的驱动程序来说不要使用,因此使用vb2_buffer时候不能(也不需要)直接指定vb->state 的内容。 比如说通知数据完成只需要调用 vb2_buffer_done(&vb2, VB2_BUF_STATE_DONE); 即可,
    2,对于vb2_buffer,没用供我们使用的list,因此如果要将vb可以放入list head,需要我们自己添加list ,例如
    struct vivi_buffer {
         /* common v4l buffer stuff -- must be first */
         struct vb2_buffer vb;
         struct list_head list;
    };
    使用vb2,总结如下 
        1,调用vb2_queue_init 初始化队列  q 。
        2,调用reqbuf 时候会根据请求(v4l2_requestbuffers)分配vb2结构,并且加入到q->buf中
        3,调用querybuf时候,根据信息(v4l2_buffer)返回q->buf中对应的vb2_buffer的信息(v4l2_buffer)
        4,mmap上面信息对应的 vb空间到用户空间
        5,调用qbuf 时,将对应的vb2_buffer ( vivi_bufer->list )添加到  q->queued_list 队列中
        6,使用select 调用poll 休眠等待 q->done_list 有数据
        7, 调用qbuf 和 vidioc_streamon 时候都会查询,如果这两个条件都成立,则调用q->ops->buf_queue 将 核心中的vb2_buffer调如我们写的驱动中,放入一个列表,然后等待(上面的poll过程休眠)我们的驱动程序将数据放入该vb2_buffer
        8, 数据存放完成后 调用vb2_buffer_done函数,即将上面有数据的vb2_buffer放入q->done_list中,然后唤醒上面poll休眠的进程
        9, poll唤醒后会调用dqbuf将q->done_list 中的vb2_buffer提出来后,将此vb2的信息(v4l2_buffer)返回
        10, 应用程序得到buffer信息后,就去对应的mmap后的用户空间中读数据。
     
     
    以上为这次根据韦东山视频步骤对vivi程序的分析,如有错误,忘指出
  • 相关阅读:
    下载安装安卓开发工具
    斐波那契数列
    各位相加
    求连续最大子序列和
    反转字符串中的单词
    统计位数为偶数的数字
    Express框架的整体感知
    node.js当中的http模块与url模块的简单介绍
    formidable处理提交的表单或文件的简单介绍
    30分钟,学会经典小游戏编程!
  • 原文地址:https://www.cnblogs.com/liusiluandzhangkun/p/8727526.html
Copyright © 2011-2022 走看看