转自:https://blog.csdn.net/paul_liao/article/details/8986999

The videobuf2 API

Author:CJOK

Contact:cjok.liao#gmail.com

SinaWeibo:@廖野cjok

原文地址:http://lwn.net/Articles/447435/

Video4linux2驱动主要负责从sensor(通常是通过DMA)上获取视频数据然后把这些视频帧传输到用户空间,大量数据的传输是性能要考虑。出于此目的,V4L2定义了复杂的API去处理流数据。V4L2子系统为实现这些API也加入了不少复杂的代码,当然大部分的代码沿用了V4L。为了使驱动工程师更方便实现视频驱动代码,videobuf子系统提供了一组用于管理流IO buffer的接口。

Linux kernel在不断的更新和完善,videobuf在2009已经被videobuf2取代,相比videobuf,videobuf2更加完善,更加实用。本文用于介绍videobuf2。

为什么出现videobuf2?虽然videobuf工作的很好,但是在一些方面并不是那么完善,各种不同的API相当依赖buffer的类型,也没有提供相关的接口用于视频缓冲区管理。Videobuf2提供了一组统一的API接口,允许驱动自己有更多的配置。

Buffers

像原来的的videobuf一样,videobuf2也实现了三种buffer类型。Vmalloc buffer这类buffer由vmalloc()分配,因此在内核空间虚拟地址上是连续的,drivers working with these buffers almost invariably end up copyingthe data once as it passes through the system;contiguous DMA buffers在物理内存上连续,通常硬件设备需要在连续物理地址空间上执行DMA操作;S/G DMA buffers在物理内存上是不连续的,如果硬件上支持scatter/gather DMA,驱动可以使用这种方式。

驱动程序使用不同的类型buffer,需要包含以下对应的头文件:

#include <media/videobuf2-vmalloc.h>

#include <media/videobuf2-dma-contig.h>

#include <media/videobuf2-dma-sg.h>

和videobuf相比,videobuf2有一个好处就是能够是一个驱动程序同时支持多种类型的buffer,上面的头文件不会相互冲突,而且videobuf2提供的这三种buffer类型的操作接口非常类似。

结构体vb2_buffer代表一个视频缓冲区,此结构体定义在<media/videobuf2-core.h>。通常驱动程序都想保存每个缓冲区特有的信息,因此,驱动程序会自己定义一个缓冲区结构体,然后把vb2_buffer嵌入在其中。可是,videobuf2作者没有读过Neil Brown’s object-oriented design patterns in the kernel(内核中的面向对象设计模型),没有提供分配这些结构体的函数。这就意味着驱动程序需要把自定义结构体的大小通知videobuf2子系统,而且vb2_buffer实例必须放在自定义结构体的第一个成员域。

一个视频驱动程序需要实现一组用于管理buffer的回调函数并注册到videobuf2子系统,在原来的videobuf中也有类似的回调函数。这五个函数如下:

    int (*buf_init)(struct vb2_buffer *vb);
    int (*buf_prepare)(struct vb2_buffer *vb);
    void (*buf_queue)(struct vb2_buffer *vb);
    int (*buf_finish)(struct vb2_buffer *vb);
    void (*buf_cleanup)(struct vb2_buffer *vb);

videobuf2会调用buf_init()对每个分配好的新buffer执行一些必要的额外初始化,如果buffer_init返回错误码将会中断缓冲队列的设置;

buf_prepare()在用户空间入队这些buffer时被调用(比如,响应VIDIOC_QBUF操作),在这些buffer被流I/O使用之前完成一些必要的准备工作。调用Buf_queue()把buffers传递给驱动程序,开始流I/O操作。

译者注:buf_queue是响应VIDIOC_STREAMON操作,开始流IO操作,然后会往buffers中填充视频数据。

Buf_finish()在buffers传回到用户空间前被调用,你可能需要对这个回调函数考虑的问题是,驱动程序已经知道buffer有一帧新的数据传递到用户空间,事实上,还需要告诉videobuf2。一个可能的答案是完成流I/O数据到buffer的操作通常是在中断上下文处理的,而buf_finish()会在进程上下文调用。

译者注:buf_finish()在每个buffer出队传递到用户空间前被调用,通常在用户空间访问buffers前做一些必要操作。

Buf_cleanup()在buffer释放前被调用,用来做一些清理工作。

Other videobuf2 callbacks

在原来的videobuf版本中,驱动程序只提供上述的回调函数来管理缓冲区,videobuf2新增了一些回调函数用来完善对缓冲区的操作。

    int (*queue_setup)(struct vb2_queue *q, unsigned int *num_buffers,
                                                unsigned int *num_planes, unsigned long sizes[],
                                                void *alloc_ctxs[]);

queue_setup()在用户空间执行ioctl:VIDIOC_REQBUFS操作时被调用,作用是用来建立缓冲队列。结构体vb2_queue用来描述缓冲队列,num_buffers参数是应用程序申请的缓冲数。Num_planes是每个buffer中的视频层数目。Size数组包含了每个视频层的大小(bytes)。

Alloc_ctxs指针数组包含了每个视频层的“分配上下文”,当前还只被contiguous DMA模式使用到。使用contiguous DMA的驱动程序需要调用获取/注销上下文信息:

void *vb2_dma_contig_init_ctx(struct device *dev)

voidvb2_dma_contig_cleanup_ctx(void *alloc_ctx)

下面两个回调函数用于开始和停止获取视频数据:

    int (*start_streaming)(struct vb2_queue *q);
    int (*stop_streaming)(struct vb2_queue *q);

start_streaming()用来响应ioctl:VIDIOC_STREAMON操作开始抓取视频数据,如果驱动程序实现了read()方法也会调用到start_streaming()。Stop_streaming()用来响应ioctl:VIDIOC_STREAMOFF操作停止抓取视频数据,等待DMA停止后函数才会返回。这里值得注意的是,调用stop_streaming()后,videobuf2子系统会回收掉所有传递到驱动程序的buffers,此时驱动程序不能访问这些buffers。

Locking

最后两个回调函数是用于保护视频设备访问——锁机制:

l  调用驱动程序访问视频设备时需要获取到锁;

l  Videobuf2子系统调用到驱动的回调函数前应该先获取到设备访问锁。比如,用户空间获取视频数据会导致start_streaming()调用,需要通过videobuf2子系统调用到相关的设备驱动程序,所以调用start_streaming()时,将要持有设备锁。

在这样的情况下,有一个需要考虑的问题:用户空间调用ioctl:VIDIOC_DQBUF从内核获取缓冲区的数据,当缓冲区不可用时,进程可能会阻塞。

void (*wait_prepare)(struct vb2_queue *q);
    void (*wait_finish)(struct vb2_queue *q);

在进行VIDIOC_DQBUF操作阻塞等待一个buffer数据之前调用wait_prepare()去释放设备锁,一旦退出阻塞,会调用wait_finish()获取设备锁。可能这两个回调函数用release_lock()和reacquire_lock()名字更好。

Queue setup

上面介绍了vb2_ops,了解了视频驱动程序如何注册到videobuf2子系统。实现需要构建vb2_ops,并实现其中的回调函数:

static const struct vb2_ops my_special_callbacks = {
                                         .queue_setup = my_special_queue_setup,
                                         /* ... */
    };

然后,建立一个videobuf2队列(通常是在设备注册或者设备open的时候),驱动需要分配一个vb2_queue结构体:

    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;
                    /* Lots of private stuff omitted */
    };

这个结构体需要实现上面的成员,type代表buffer类型,通常是V4L2_BUF_TYPE_VIDEO_CAPTURE,io_modes是一个标志位,用来描述以何种方式来访问buffer:

l  VB2_MMAP:缓冲区由内核空间分配,通过mmap映射缓冲区到用户空间。通常vmalloc和contiguous DMA buffers来分配此方式的缓冲区;

l  VB2_USERPTR:缓冲区由用户空间分配,通常需要设备支持集散IO才可以分配用户空间缓冲区。有趣的是,videobuf2支持分配连续的缓冲区在用户空间,但是需要运用一些特别的机制,比如android的pmem驱动。在用户空间分配连续大量的页面是不支持的。

l  VB2_READ,VB2_WRITE:用户空间缓冲区提供read()和write()方式访问视频设备。

Mem_ops成员是缓冲区操作函数集合,提供给上层videobuf2子系统操作缓冲区,内核已经实现三组供我们使用: vb2_vmalloc_memops,vb2_dma_contig_memops和 vb2_dma_sg_memops,如果这三组都不符合,驱动工程师可以自己实现一组。在撰写本文时,内核主线还有没有提供内存操作的驱动程序。

最后,drv_priv是驱动私有的指针(通常指向device结构),buf_struct_size用于表示buffer结构体的大小。Vb2_queue结构体实现后,调用以下函数进行初始化:

intvb2_queue_init(struct vb2_queue *q)

在设备关闭时调用 vb2_queue_release()进行销毁。

Device operations

到现在我们已经分析了videobuf2底层的基础设施,接下来看下videobuf2子系统是怎么把用户空间的操作和视频设备驱动关联起来。通常第一步驱动程序需要实现大量的ioctl()调用接口,大部分的函数接口只是简单的获取设备锁,然后直接调用videobuf2实现的ops。下面我们来看这些ops:

    int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b);
    int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req);
    int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b);
    int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, 
                                           bool nonblocking);
    int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type);
    int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type);
    int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma);
    unsigned int vb2_poll(struct vb2_queue *q, struct file *file, 
                                                               poll_table *wait);
    size_t vb2_read(struct vb2_queue *q, char __user *data, size_t count,
                                             loff_t *ppos, int nonblock);
    size_t vb2_write(struct vb2_queue *q, char __user *data, size_t count,
                                              loff_t *ppos, int nonblock);

这些函数接口屏蔽了底层大量缓冲区管理的细节,使代码十分简洁,提供了统一的接口给上层调用。

最后还有一点工作需要做:获取数据放到缓冲区里。对于vmalloc buffers,通常是通过memcpy()这样的操作来完成,但是videobuf2提供了一个有用函数:

void *vb2_plane_vaddr(struct vb2_buffer*vb, unsigned int plane_no)

返回在缓冲区中给定的内核虚拟地址。

Contiguous DMA驱动需要获取处理

    dma_addr_t vb2_dma_contig_plane_paddr(struct vb2_buffer *vb, 
                                          unsigned int plane_no);
对于scatter/gather DMA驱动,提供的接口略显复杂:
struct vb2_dma_sg_desc {
                    unsigned long                                   size;
                    unsigned int                                      num_pages;
                    struct scatterlist         *sglist;
};
static inline struct vb2_dma_sg_desc *vb2_dma_sg_plane_desc(
                                         struct vb2_buffer *vb, unsigned int plane_no)

驱动程序从videobuf2获取到能用于设备的scatterlist。

对于这三种情况,缓冲区将会填充满需要传递到用户空间的帧数据。Vb2_buffer结构体中内嵌了一个v4l2_buf,通常包含图像大小、序列号、时间帧等信息。最后调用以下函数:

void vb2_buffer_done(struct vb2_buffer *vb,enum vb2_buffer_state state)

state参数:

VB2_BUF_STATE_DONE,通知videobuf2子系统有一个buffer处理完成;

VB2_BUF_STATE_ERROR,通知videobuf2子系统有一个buffer处理时出现ERROR。

本文大致介绍总结videobuf2 API,如果想了解完整的接口函数,可以参考内核源码。内核提供一个虚拟视频驱动实例供参考(vivi.c)。

The videobuf2 API【转】的更多相关文章

  1. 深入理解linux内核v4l2框架之videobuf2【转】

    转自:https://blog.csdn.net/ramon1892/article/details/8444193 Videobuf2框架 1. 什么是videobuf2框架? 它是一个针对多媒体设 ...

  2. 干货来袭-整套完整安全的API接口解决方案

    在各种手机APP泛滥的现在,背后都有同样泛滥的API接口在支撑,其中鱼龙混杂,直接裸奔的WEB API大量存在,安全性令人堪优 在以前WEB API概念没有很普及的时候,都采用自已定义的接口和结构,对 ...

  3. 12306官方火车票Api接口

    2017,现在已进入春运期间,真的是一票难求,深有体会.各种购票抢票软件应运而生,也有购买加速包提高抢票几率,可以理解为变相的黄牛.对于技术人员,虽然写一个抢票软件还是比较难的,但是还是简单看看123 ...

  4. 几个有趣的WEB设备API(二)

    浏览器和设备之间还有很多有趣的接口, 1.屏幕朝向接口 浏览器有两种方法来监听屏幕朝向,看是横屏还是竖屏. (1)使用css媒体查询的方法 /* 竖屏 */ @media screen and (or ...

  5. html5 canvas常用api总结(三)--图像变换API

    canvas的图像变换api,可以帮助我们更加方便的绘画出一些酷炫的效果,也可以用来制作动画.接下来将总结一下canvas的变换方法,文末有一个例子来更加深刻的了解和利用这几个api. 1.画布旋转a ...

  6. JavaScript 对数据处理的5个API

    JavaScript对数据处理包括向上取整.向下取整.四舍五入.固定精度和固定长度5种方式,分别对应ceil,floor,round,toFixed,toPrecision等5个API,本文将对这5个 ...

  7. ES5对Array增强的9个API

    为了更方便的对Array进行操作,ES5规范在Array的原型上新增了9个方法,分别是forEach.filter.map.reduce.reduceRight.some.every.indexOf ...

  8. javascript的api设计原则

    前言 本篇博文来自一次公司内部的前端分享,从多个方面讨论了在设计接口时遵循的原则,总共包含了七个大块.系卤煮自己总结的一些经验和教训.本篇博文同时也参考了其他一些文章,相关地址会在后面贴出来.很难做到 ...

  9. 一百元的智能家居——Asp.Net Mvc Api+讯飞语音+Android+Arduino

    大半夜的,先说些废话提提神 如今智能家居已经不再停留在概念阶段,高大上的科技公司都已经推出了自己的部分或全套的智能家居解决方案,不过就目前的现状而言,大多还停留在展厅阶段,还没有广泛的推广起来,有人说 ...

随机推荐

  1. 【UOJ#275】组合数问题(卢卡斯定理,动态规划)

    [UOJ#275]组合数问题(卢卡斯定理,动态规划) 题面 UOJ 题解 数据范围很大,并且涉及的是求值,没法用矩阵乘法考虑. 发现\(k\)的限制是,\(k\)是一个质数,那么在大组合数模小质数的情 ...

  2. 【ATcoder s8pc_3 F】 寿司

    http://s8pc-3.contest.atcoder.jp/tasks/s8pc_3_f (题目链接) 题意 有一个长度为$N$的数列$A$,初始为$0$.$Q$次操作,每次两个参数$x,y$. ...

  3. 写入与读取第三方的 cookie - P3P: CP="CAO PSA OUR"

    应用的场景是这样: 在 a.com 页面显示一个 来自b.com的一张图片 a.com/test.html 的内容: <img src=b.com/a.jpg> 但需求是,当用户访问 b. ...

  4. 使用React.cloneElement()给子组件传值

    React提供了一个克隆组件的API: React.cloneElement( element, [props], [...child] ) 可以利用该方法,给子组件传值,使用如下: class Pa ...

  5. javascript高级程序设计第二章知识点提炼

    这是我整理的javascript高级程序设计第二章的脑图,内容也是非常浅显与简单.希望您看了我的博客能够给我一些意见或者建议.

  6. Apache的ProxyPass简单使用

    转: Apache的ProxyPass简单使用 置顶 2017年08月14日 18:54:33 师太,老衲把持不住了 阅读数:11164   http://mtnt2008.iteye.com/blo ...

  7. 编译Uboot——错误记录

    我使用的是ZLG的EasyARM i.MX280A的开发板.官方提供的编译器时arm-fsl-linux-gnueabihf(gcc 4.4.4).自己尝试使用arm-linaro-linux-gnu ...

  8. 对于nginx配置文件中的fastcgi_param的配置错误

    在centos中搭建LNMP的时候,遇到了一个问题 在浏览器中访问.php文件的时候,nginx不能正常解析,页面返回404,后来百度了一下,发现了问题 在nginx的配置文件nginx.conf中的 ...

  9. 函数和常用模块【day06】:pickle模块(十二)

    本节内容 1.dumps序列化和loads反序列化 2.dump序列化和load反序列化 3.序列函数 1.dumps序列化和loads反序列化 dumps()序列化 1 2 3 4 5 6 7 8 ...

  10. GsonWithoutObject 没有对象(脱离对象) 直接提取【转】

    GsonWithoutObject 没有对象(脱离对象) 直接提取 ... gson json GsonWithoutObject 脱离对象, 直接提取 package temp; import to ...