V4L2 videobuffer2的介绍,数据流分析

1. videobuffer2的介绍
一. vb2涉及的数据结构:
struct v4l2_buffer {                                            //供用户态使用
    __u32            index;
    __u32            type;
    __u32            bytesused;
    __u32            flags;
    __u32            field;
    struct timeval        timestamp;
    struct v4l2_timecode    timecode;
    __u32            sequence;

    /* memory location */
    __u32            memory;
    union {
        __u32           offset;                    //MMAP方式
        unsigned long   userptr;                //userptr
        struct v4l2_plane *planes;
        __s32        fd;                            //dma
    } m;
    __u32            length;
    __u32            reserved2;
    __u32            reserved;
};

struct v4l2_requestbuffers {                                    //由用户指定
    __u32            count;                                        //buffer的个数
    __u32            type;        /* enum v4l2_buf_type */        //vb2的类型 (不是很重要,与申请的内存无关)
    __u32            memory;        /* enum v4l2_memory */            //3种类型 VB2_MEMORY_MMAP,VB2_MEMORY_MMAP,VB2_MEMORY_DMABUF
    __u32            reserved[2];
};

struct vb2_queue {                                                //由内核态维护
    unsigned int            type;
    unsigned int            io_modes;
    const struct vb2_ops        *ops;                            //vb2_queue的接口操作,比如buf_prepare
    const struct vb2_mem_ops    *mem_ops;                        //内存申请方面,针对MMAP类型模式
    const struct vb2_buf_ops    *buf_ops;                        //vb2_queue的接口操作
    void                *drv_priv;
    unsigned int            buf_struct_size;
    u32                timestamp_flags;
    gfp_t                gfp_flags;
    u32                min_buffers_needed;                            //指定至少需要的buffer数,用户层申请的buffer块数不得小于此值
    /* private: internal use only */
    struct mutex            mmap_lock;
    unsigned int            memory;                                //用户指定的v4l2_memory(3种类型)
    struct vb2_buffer        *bufs[VB2_MAX_FRAME];                //存放vb2_buffer数据,格式是用户申请的count数目
    unsigned int            num_buffers;                        //实际申请到的buffer数目,防止用户态恶意申请非常大的buffer块数
    struct list_head        queued_list;                        //用户态申请的buffer块放在此列表内
    unsigned int            queued_count;
    //以下dqbuf取数据的标志有关
    struct list_head        done_list;
    spinlock_t            done_lock;
    wait_queue_head_t        done_wq;                            //dqbuf取数据时会检测这个变量
};

struct vb2_buffer {                                                //由内核态维护
    struct vb2_queue    *vb2_queue;
    unsigned int        index;
    unsigned int        type;
    unsigned int        memory;
    unsigned int        num_planes;
    struct vb2_plane    planes[VB2_MAX_PLANES];                    //length:记录一幅图像的大小
    u64            timestamp;

    /* private: internal use only
     *
     * state:        current buffer state; do not change
     * queued_entry:    entry on the queued buffers list, which holds
     *            all buffers queued from userspace
     * done_entry:        entry on the list that stores all buffers ready
     *            to be dequeued to userspace
     */
    enum vb2_buffer_state    state;

    struct list_head    queued_entry;
    struct list_head    done_entry;
}

struct vb2_plane {
    void            *mem_priv;                                    //存放具体的一幅图片的大小(针对MMAP类型模式)
    struct dma_buf        *dbuf;                                    //(针对DMA类型模式)
    unsigned int        dbuf_mapped;
    unsigned int        bytesused;
    unsigned int        length;                                    //记录一幅图像的大小
    unsigned int        min_length;                                //记录一幅图像的最小的大小
    union {
        unsigned int    offset;
        unsigned long    userptr;                                //(针对USERPTR类型模式)
        int        fd;
    } m;
    unsigned int        data_offset;
};


二. 常用的vb2接口介绍
2.1 以下3个回调函数数组在vb2接口使用时被调用。其中vb2_ops是用户根据项目需求自己去实现,其余2个是vb2架构平台自带的实现
static const struct vb2_buf_ops v4l2_buf_ops = {
    .verify_planes_array    = __verify_planes_array_core,
    .fill_user_buffer    = __fill_v4l2_buffer,                    //将内核态的一些信息告知用户态。交流的结构体就是struct v4l2_buffer
    .fill_vb2_buffer    = __fill_vb2_buffer,                    //VIDIOC_QBUF时会被调用
    .copy_timestamp        = __copy_timestamp,
};

static void __fill_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *pb)                        //将内核态的一些信息告知用户态。交流的结构体就是struct v4l2_buffer    
static int __fill_vb2_buffer(struct vb2_buffer *vb, const void *pb, struct vb2_plane *planes)    //通过用户态v4l2_buffer去完成vb2_buffer的填充。针对USERPTR,USERDMA模式
    if (b->memory == VB2_MEMORY_USERPTR) {
        planes[0].m.userptr = b->m.userptr;
        planes[0].length = b->length;
    }
    if (b->memory == VB2_MEMORY_DMABUF) {
        planes[0].m.fd = b->m.fd;
        planes[0].length = b->length;
    }

//以下是队列的相关操作函数,在使用vb2的标准接口时会调用到以下接口。由于接口的具体实现内容由项目实际情况决定,故作为开放的接口。
const struct vb2_ops v4l2_model_qops = {
    .queue_setup = v4l2_model_qops_queue_setup,                //VIDIOC_REQBUF时会被调用

    /* do nothing */
    .wait_prepare = v4l2_model_qops_wait_prepare,            //VIDIOC_DQBUF时会被调用
    .wait_finish = v4l2_model_qops_wait_finish,                //VIDIOC_DQBUF时会被调用
    .buf_init = v4l2_model_qops_buf_init,                    //VIDIOC_REQBUF时会被调用

    .buf_prepare = v4l2_model_qops_buf_prepare,                //VIDIOC_QBUF时会被调用
    .buf_cleanup = v4l2_model_qops_buf_cleanup,                //与buf_prepare相对应
    .buf_finish = v4l2_model_qops_buf_finish,                //VIDIOC_DQBUF时会被调用
    .start_streaming = v4l2_model_qops_start_streaming,
    .stop_streaming = v4l2_model_qops_stop_streaming,
    .buf_queue = v4l2_model_qops_buf_queue,
};

支持3种方式:(用于具体的内存申请)
V4L2_MODEL_BUF_TYPE_VMALLOC    (分配的内存在物理地址上是不连续的,虚拟地址连续)
    q->mem_ops = &vb2_vmalloc_memops;            //申请的物理地址不连续. 内部vmalloc, 返回值 struct vb2_vmalloc_buf *buf;
V4L2_MODEL_BUF_TYPE_DMA_CONT (分配的内存在物理地址上是连续的)
    q->mem_ops = &vb2_dma_contig_memops;        //申请的物理地址连续. 内部 dma_alloc_from_coherent(), 返回值 struct vb2_dc_buf *buf;
V4L2_MODEL_BUF_TYPE_DMA_SG (需要硬件支持iommu功能,申请的地址物理地址上不连续,虚拟地址也不连续)
    q->mem_ops = &vb2_dma_sg_memops;            //iommu相关,运行申请的物理地址不连续.

2.2 初始化队列
int vb2_queue_init(struct vb2_queue *q)
    q->buf_ops = &v4l2_buf_ops;
    vb2_core_queue_init(q);            //无实际作用,用于判断
此初始化函数全部由vb2架构平台来实现

2.3 申请buffer块.(涉及的ioctl操作时VIDIOC_REQBUF)
.vidioc_reqbufs    =    vb2_ioctl_reqbufs,
int vb2_ioctl_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p)
    vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory, unsigned int *count)    //队列,申请的内存类型,申请的内存个数
        *count == 0时,清空目前队列所有的buffer数据
        *count != 0时, queue_setup指定,一幅图片的大小以及num_planes,一般num_planes=1
        allocated_buffers = __vb2_queue_alloc(q, memory, num_buffers(用户态设置的大小), num_planes(1), plane_sizes(一幅图片的大小));    //具体的申请空间
            //内部调用alloc() 和 buf_init()函数,涉及的结构体是struct vb2_buffer以及struct vb2_plane
        

2.4 QBUF,将用户申请的buffer块压入队列。(涉及的ioctl操作时VIDIOC_QBUF)
.vidioc_qbuf =    vb2_ioctl_qbuf,
int vb2_ioctl_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
    vb2_core_qbuf(q, b->index(用户态申请的buffer块索引), b(v4l2_buffer));
        vb = q->bufs[index];                            //取出vb2_buffer
        list_add_tail(&vb->queued_entry, &q->queued_list);
        q->queued_count++;
        __buf_prepare(vb, pb);
            switch (q->memory) {
            case VB2_MEMORY_MMAP:
                ret = __qbuf_mmap(vb, pb);
                    fill_vb2_buffer()
                    buf_prepare()
                break;
            case VB2_MEMORY_USERPTR:
                ret = __qbuf_userptr(vb, pb);
                    fill_vb2_buffer()
                    get_userptr()                        //获取用户态申请到的内存空间
                    buf_init()
                    buf_prepare()
                break;
            case VB2_MEMORY_DMABUF:
                ret = __qbuf_dmabuf(vb, pb);
                break;

2.4 DQBUF,从队列取出申请的buffer。(涉及的ioctl操作时VIDIOC_DQBUF)
.vidioc_dqbuf         = vb2_ioctl_dqbuf,
int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)            //从队列取出数据,类似于读数据,涉及是否阻塞的问题
    vb2_core_dqbuf(q, NULL, b, nonblocking);
        __vb2_get_done_vb(q, &vb, pb, nonblocking);                                    //获取已经完成操作的队列数据
            __vb2_wait_for_done_vb(q, nonblocking);                                    //等待done_list上有数据
                wait_prepare()
                wait_event_interruptible(q->done_wq);
                wait_finish()
            *vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);    //从done_list上取出数据(何时往done_list上放数据,见以下2.5)
        buf_finish()
        fill_user_buffer()
        list_del(&vb->queued_entry);
        q->queued_count--;
        __vb2_dqbuf(vb);
            unmap_dmabuf

2.5 此函数可以唤醒VIDIOC_DQBUF中等待数据部分(因为若打开方式设置为阻塞,用户态在调用VIDIOC_DQBUF时将一直等待,直到内核态驱动调用vb2_buffer_done函数)
void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state)            //往done_list上放数据
    finish()
    list_add_tail(&vb->done_entry, &q->done_list);
    wake_up(&q->done_wq);                //唤醒done_wq队列。dqbuf里wait_event_interruptible(q->done_wq);所以dqbuf阻塞状态时会一直阻塞,直到调用vb2_buffer_done()

2.6 查询用户申请的buffer块(struct v4l2_buffer是用户态与内核态下进行数据交流的数据结构。如果使用USERPTR方式,则v4l2_buffer里会保存用户态下申请的数据首地址以及大小)
.vidioc_querybuf =    vb2_ioctl_querybuf,
int vb2_ioctl_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
    int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
        vb = q->bufs[b->index];                //根据index取出vb2_buffer
        vb2_core_querybuf(q, b->index, b);
            fill_user_buffer();

            
3. 用户态与内核态的数据交流
struct vb2_plane结构体存储的是真正申请的内存的地址以及长度.有3种方式:
a. USERPTR:此种方式由用户态去申请空间(比如用vmalloc,申请的大小可能有限).在VIDIOC_QBUF时会将用户态信息v4l2_buffer转换为内核态下vb2_buffer信息
b. DMA的方式没有仔细研究,确定的是也是由用户态去申请空间
c. MMAP方式: 此方式由内核态去申请,申请方式在内核态下又分为3种,上面有介绍过

问:那么这部分数据在内核态下怎么走?
答:举例以MMAP方式申请,V4L2_MODEL_BUF_TYPE_DMA_CONT类型申请到的一块连续的物理地址值和对应的一个虚拟地址值。
buf_descs[0].addr = vb2_dma_contig_plane_dma_addr(vb, i);    //得到物理地址.这个值很有可能写到具体的寄存器中
buf_descs[0].size = vb2_plane_size(vb, i);                    //得到大小(通常一幅图片大小)
以下2行是针对V4L2_MODEL_BUF_TYPE_SG_CONT类型
buf_descs[j].addr = sg_dma_address(sg);
buf_descs[j].size = sg_dma_len(sg);
所以一旦dma采集到数据,则对应的MMAP方式申请的空间也就有了数据。然后调用video_buf_done()函数将数据放到done_list链表,并执行wakeup()函数来唤醒dqbuf()函数取出数据。此时用户态调用VIDIOC_DQBUF时就可以采集到一帧的视频数据了。

总结:
使用MMAP的方式在内核态下申请数据(又分3种申请方式),申请的数据可以是在物理地址上连续,从而使用DMA。但是USERPTR方式,由于用户态下只能malloc,所以申请的数据基本上不能用于dma传输。

 

用户指定buffer的个数,具体由内核去分配,分配方式也有用户态指定。内核态的多个buffer以链表的形式进行组织。

 

你可能感兴趣的:(V4L2 videobuffer2的介绍,数据流分析)