深入理解linux内核v4l2框架之videobuf

Videobuf


下面来介绍以下videobuffer相关的一些东西。

V4L2核心api提供了一套标准的方法来处理视频缓冲,这些方法允许驱动实现read(),mmap(), overlay()等操作。同样也有方法支持DMAscatter/gather操作,并且支持vmallocbuffer(这个大多用在USB驱动上)

videobuf层功能是一种在v4l2驱动和用户空间当中的依附层,这话看起来有点绕,说白了就是提供一种功能框架,用来分配和管理视频缓冲区,它相对独立,却又被v4l2驱动使用。它有一组功能函数集用来实现许多标准的POSIX系统调用,包括read(),poll()mmap()等等,还有一组功能函数集用来实现流式(streaming)IOv4l2_ioctl调用,包括缓冲区的分配,入队和出队以及数据流控制等操作。使用videobuf需要驱动程序作者遵从一些强制的设计规则,但带来的好处是代码量的减少和v4l2框架API的一致。


缓冲类型

并不是所有的视频设备都使用相同的缓冲类型。实际上,有三种通用的类型:

被分散在物理和内核虚拟地址空间的缓冲,几乎所有的用户空间缓冲都是这种类型,


如果可能的话分配内核空间的缓冲也很有意义,但是不幸的是,这个通常需要那些支持离散聚合DMA操作的硬件设备。

物理上离散的但是虚拟地址是连续的,换句话说,就是用vmalloc分配的内核缓冲。这些缓冲很难用于DMA操作。

– 物理上连续的缓冲。

videobuf可以很好地处理这三种类型的缓冲,但是在此之前,驱动程序作者必须选择一种类型,并且以此类型为基础设计驱动。


数据结构,回调函数和初始化

根据选择的类型,包含不同的头文件,这些头文件在include/media/下面

<media/videobuf-dma-sg.h>

<media/videobuf-vmalloc.h>

<media/videobuf-dma-contig.h>

v4l2驱动需要包含一个videobuf_queue的实例用来管理缓冲队列,同时还要一个链表来维护这个队列,另外还要一个中断安全的spin_lock来保护队列的操作。

下一步就是要填充一个回调函数集来处理实际的缓冲区队列,这个函数集用videobuf_queue_ops来描述:

struct videobuf_queue_ops {

int *(buf_setup)(struct videobuf_queue*q, uint *count, uint *size);

int *(buf_prepare)(structvideobuf_queue *q, struct videobuf_buffer *vb,

enum v4l2_field field);

void *(buf_queue)(structvideobuf_queue*q,struct videobuf_buffer *vb);

void *(buf_release)(...);

}

buf_setupIO处理请求之前被调用。目的是告诉videobuf关于IO的信息。 count参数提供一个缓冲区个数的参考,驱动必须检查它的合理性,一个经验是大于等于2,小于等于32个。Size参数指定了每一帧数据的大小。

buf_prepare每一个缓冲(videobuf_buffer结构描述的)将被传递给该回调函数,用来配置缓冲的height,widthfileds。如果field参数被设置为VIDEOBUF_NEEDS_INIT,那么驱动将把vb传递给videobuf_iolock()这个函数。除此之外,该回调函数通常也将为vb分配内存,最后把vb状态置为VIDEOBUF_PREPARED

buf_queue当一个vb需要被放入IO请求队列时,调用该回调。它将把这个buffer放到可用的buffer链表当中去,然后把状态置为VIDEOBUF_QUEUED

buf_release当一个buffer不再使用的时候,调用该回调函数。驱动必须保证 buffer上没有活跃的IO请求,之后就可以将这个buffer传递给合适的 free函数,根据申请的buffer类型调用对应的释放函数:

scatter/gather类型的调用

videobuf_dma_unmap(structvideobuf_queue, videobuf_dmabuf)

videobuf_dma_free(videobuf_dmabuf)

vmalloc类型的调用

videobuf_vmalloc_free(videobuf_buffer)

contiguous类型的调用

videobuf_dma_contig_free(videobuf_queue,videobuf_buffer)

有一种方法可以保证buffer上没有IO请求,调用函数

videobuf_waiton(videobuf_buffer,non_blocking, intr)


文件操作(v4l2_file_operations)

到了这儿,很多工作也就做完了,剩下的事情就是将对videobuf的调用传递给具体的驱动实现了。首先就是打开操作,这个操作要先对videobuf_queue进行初始化,初始化取决于申请的buffer是什么类型,有如下三种初始化函数可供调用:

void videobuf_queue_sg_init(structvideobuf_queue *q,

struct videobuf_queue_ops *ops,

struct device *dev,

spinlock_t *irqlock,

enum v4l2_buf_type type,

enum v4l2_field_ field,

unsigned int msize,

void *priv

struct mutex *ext_lock)

void videobuf_queue_vmalloc_init(structvideobuf_queue *q,

struct videobuf_queue_ops *ops,

struct device *dev,

spinlock_t *irqlock,

enum v4l2_buf_type type,

enum v4l2_field field,

unsigned int mszie,

void *priv

struct mutex *ext_lock);

voidvideobuf_queue_dma_contig_init(struct videobuf_queue *q,

struct videobuf_queue_ops *ops,

struct device *dev,

spinlock_t *irqlock,

enum v4l2_buf_type type,

enum v4l2_field field,

unsigned int mszie,

void *priv

struct mutex *ext_lock);

以上三种初始化函数,有相同的参数,这些参数的从他们的名称就可以看出来锁代表的意义是什么。

这里着重说下v4l2_buf_type类型,

V4L2_BUF_TYPE_VIDEO_CAPTURE 指定buf的类型为capture,用于视频捕获设备

V4L2_BUF_TYPE_VIDEO_OUTPUT 指定buf的类型output,用于视频输出设备

V4L2_BUF_TYPE_VIDEO_OVERLAY 指定buf的类型为overlay,用于overlay设备

V4L2_BUF_TYPE_VBI_CAPTURE 用于vbi捕获设备

V4L2_BUF_TYPE_VBI_OUTPUT 用于vbi输出设备

V4L2_BUF_TYPE_SLICED_VBI_CAPTURE 用于切片vbi捕获设备

V4L2_BUF_TYPE_SLICED_VBI_OUTPUT 用于切片vbi输出设备

V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY 用于视频输出overlay设备

V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 用于多平面存储格式的视频捕获设备

V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE用于多平面存储格式的视频输出设备

v4l2_field指定videofield,也就是说interleaved或者progressive的,一般都是指定为V4L2_FIELD_NONE,用于逐行扫描的设备。


V4L2捕获设备驱动可以支持两种APIread()系统调用和更为复杂的流机制。一般的做法是两种都支持以确保所有的应用都可以使用该设备。videobuf框架使得这种驱动的编写变得更为简单。比如说要实现read()系统调用,那么驱动程序只需要调用

ssize_t videobuf_read_one(structvideobuf_queue *q,

char __user *data, size_t count,loff_t *ppos, int nonblocking)

ssize_t videobuf_read_streaming(structvideobuf_queue *q, char __user *data, size_t count, loff_t *ppos,int vbihack, int nonblocking)

这两个函数都是把帧数据读入到data当中,然后返回实际上读取的字节数。不同的是前者只读取一帧数据,而后者可以选择读取多帧。一个典型的应用read()系统调用必须开启捕获设备,然后返回之前停止该设备。


poll()系统调用通常由以下函数来实现:

unsigned intvideobuf_poll_stream(struct file *file, struct videobuf_queue *q,

poll_table *wait)

注意,实际最终使用的q是可用的第一个buffer


当内核空间缓冲的流IO请求完成后,驱动还必须支持mmap系统调用以使能用户空间可以访问data数据。在v4l2驱动中,通常很复杂的mmap的实现被简化了,只需要调用下面这个函数就可以了:

int videobuf_mmap_mapper(structvideobuf_queue *q,

struct vma_area_struct * vma)

剩下的事情就交给videobuf核心层来完成好了。


release函数需要调用两个单独的函数来完成:

void videobuf_stop(structvideobuf_queue *q);

int videobuf_mmap_free(structvideobuf_queue *q)

前者终止所有bufferIO操作。后者保证所有的bufferunmap掉,如果已经被unmap掉的话,这个buffer就会被传递给buf_release回调函数。如果buffer还没有被unmap,那么后者将返回一个错误代码。


Ioctl操作:

v4l2api涵盖了很长一组驱动回调函数来响应用户的ioctl操作,有很大一部分和流IO操作相关的都是直接调用到videobuf里面来。相关的函数如下:

int videobuf_reqbufs(structvideobuf_queue *q,

structv4l2_requestbuffers *req);

int videobuf_querybuf(structvideobuf_queue *q, struct v4l2_buffer *vb)

int videobuf_qbuf(strurctvideobuf_queue *q, struct v4l2_buffer *vb)

int videobuf_dqbuf(structvideobuf_queue *q, struct v4l2_buffer *vb)

int videobuf_streamon(structvideobuf_queue *q);

int videobuf_streamoff(structvideobuf_queue *q);


Buffer的分配

讲到这儿,我们讨论了很多关于buffer的话题,但是却没有提到他们是怎么分配的。Scatter/gather例子比较复杂,驱动程序可以完全让videobuf层去完成buffer的分配,在这种情况下,buffer将被分配为匿名用户空间页并且实际上将非常分散。如果应用程序使用用户空间的buffer的话,驱动也就不需要分配了,videobuf层将小心的调用get_user_pages()并且填充离散列表数组(scatterlistarray)

如果驱动程序要自己做内存分配,那么将在vidioc_reqbufs函数中进行,在调用了videobuf_reqbufs()之后,首先第一步就是要调用到

struct videobuf_dmabuf*videobuf_to_dma(struct videobuf_buffer *buf)

返回的videobuf_dmabuf包含了一对相关的域

struct scatterlist *sglist;

int sglen;

驱动必须分配合适大小的scatterlist数组,并且将分配的内存片和指针对应起来,sglen指定了scatterlist数组的大小。


驱动当中如果使用了vmalloc()来分配内存的话,就不用关心buffer的分配了,videobuf层将处理具体的细节,一些驱动程序使用了小技巧,就是在系统启动的时候就分配好了dma内存,以避免动态申请有的时候会申请不到的问题,但是对于此类的设计,videobuf层目前还不能很好的胜任。在3.0以上的内核当中,出现了一种新的框架videobuf2,已经解决了这个问题,我们会在后面详细介绍这个框架。


Filling缓冲区

videobuf层的最后一部分就是关于将帧数据传递到buffer中的实现。这一部分没有直接的回调函数,通常都是在设备的中断响应中来完成。对于所有类型的驱动,流程大概是这个样子的:

– 获取下一个buffer,并且确保有人正在等待这个buffer

– 得到一个内存指针,然后将视频数据放到那个地方

-- 标记buffer完成,并且唤醒等待的进程


第一步,buffer可以通过驱动管理的一个链表获得,这个链表由buf_queue回调函数填充,所以驱动最先要是链表初始化为空,并且如果链表当中的buffer没有一个进程在其上等待的话,是不能被移除或者填充的。

另外buffer在被mapdma之前,要把它的状态设置为VIDEOBUF_ACTIVE,这将保证在设备传输数据的时候videobuf层不去尝试任何操作。


第二步,得到一个内存指针,对于scatter/gather类型的内存来说,可以从scatterlist当中找到内存指针;对于vmalloc类型的来说调用

void * videobuf_to_vmalloc(structvideobuf_buffer *vb)

对于连续物理内存类型来说调用

dma_addr_tvideobuf_to_dma_contig(struct videobuf_buffer *buf)

第三步,就是设置videobuf_buffer中的大小,并且把buffer的状态设置为VIDEOBUF_DONE,然后在完成队列上调用wake_up().到此,buffer就真正的属于videobuf层了,驱动程序不用再去关心它如何被调度。


最后,一个很好的关于v4l2的例子就是drviers/media/video/vivi.c,它使用了vmalloc类型的videobuf,可以通过阅读这份例子来学习v4l2驱动的写法。

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

Videobuf2框架


1. 什么是videobuf2框架?

它是一个针对多媒体设备的v4l2兼容驱动框架,是用户空间应用程序和设备驱动的中间层。它为驱动提供更为底层的模块化的内存管理功能。

它能够使得驱动开发变得简单,减少代码量,帮助合理的连续的实现驱动当中的v4l2接口。

videobuf2的内存管理是完全模块化的,这就允许在不改变更高级别缓冲管理框架的情况下可以为设备和平台定制内存管理方法。

框架提供了一下三种:

  • 实现了v4l2_ioctl的流控和文件操作

  • 高级的视频缓冲,视频队列和状态管理

  • 视频缓冲内存分配和管理


2.为什么要新开发一种框架呢?

在当前的videobuf实现当中,有很多问题,在2010年举行的赫尔欣基峰会上重点提到了这么几个:

  • V4L2 API出现异常和错误的内存管理设计

  • 不能停止stream请求,缓冲在streamoff的时候被释放

  • VIDIOC_REQBUFS 不释放内存,也不能重新分配内存

  • 视频内存在mmapqbuf或者页错误的时候才分配

  • 每个缓冲都有一个等待队列

  • 扩展性不够强,尤其对于嵌入式多媒体设备来说支持不够

  • 很难加入定制的内存分配和管理机制

  • 不支持对缓存一致性和IOMMU设备

  • 不够灵活,只有一个包办一切的函数来处理内存锁定,缓存,sg-list的创建

  • 很多未使用的域,还有代码的重复,模糊晦涩的命名

很多驱动程序作者发布基于videobuf的基础组件.开发者也承认videobuf的功绩,也乐意使用它,但是由于灵活性不够现在不能这么做了。


3.重新设计的目的

  • 修正V4L2API的实现,修复videobuf的问题和缺陷

  • 分离缓冲队列管理和内存管理

  • 在内存的分配和管理上更加灵活,可以嵌入定制的机制

  • 更加有针对性的驱动回调函数,在不同的地方调用

  • 支持新的V4L2API扩展,例如多平面视频帧存储的支持

4. 驱动回调函数

对称的驱动回调函数设计:

  • buf_init 在内存被分配后或者一个新的USERPTR缓冲入队之后调用一次,比如用来锁定页,验证连续性,设置IOMMU映射等等。

  • buf_prepare每个QBUF都要调用,用来同步缓存,拷贝数据到buffer

  • buf_finish每个DQBUF调用,用来同步缓存,从buffer中取回数据等

  • buf_cleanup free/release内存的时候调用

其余的回调函数也有重新设计:

  • queue_negotiate现在合并了多平面的扩展;驱动返回所需要的缓冲数和每个缓冲的平面数。

  • plane_setup 驱动返回平面的尺寸大小

这两个调用取代了老的buf_setup

  • buf_queue 保留了原来的功能,将buffer放入请求队列。


5. 内存分配和处理

内存处理这块设计得更加个性化,使得内存分配可以定制,定制的函数放在一个叫做v4l2_alloc_ctx的结构体当中。它的目的是给videobuf提供操作函数,并且存放一些私有数据。私有数据可以被嵌入到更大的一些结构体当中。

Struct vb2_alloc_ctx {

const struct vb2_mem_ops *mem_ops;

}

struct vb2_foo_alloc_conf {

strucdt vb2_alloc_ctx alloc_ctx;

/* private data*/

}

更重要的是引入了一个buffer上下文结构的概念,在每次分配之后,分配器返回他们自己,定制的和每个buffer的结构。这个结构可以当作cookie传递给其他的内存处理方法。

存放在分配器上下文的内存操作可以被其他的分配器取代,详细的文档可以参考videobuf2-core.h

一个非常好的例子从三星galaxy S系列的android手机内核源码中的videbuf2-cma.c,可以看看这个例子。

1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值