Videobuf
下面来介绍以下videobuffer相关的一些东西。
V4L2核心api提供了一套标准的方法来处理视频缓冲,这些方法允许驱动实现read(),mmap(), overlay()等操作。同样也有方法支持DMA的scatter/gather操作,并且支持vmallocbuffer(这个大多用在USB驱动上)。
videobuf层功能是一种在v4l2驱动和用户空间当中的依附层,这话看起来有点绕,说白了就是提供一种功能框架,用来分配和管理视频缓冲区,它相对独立,却又被v4l2驱动使用。它有一组功能函数集用来实现许多标准的POSIX系统调用,包括read(),poll()和mmap()等等,还有一组功能函数集用来实现流式(streaming)IO的v4l2_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_setup在IO处理请求之前被调用。目的是告诉videobuf关于IO的信息。 count参数提供一个缓冲区个数的参考,驱动必须检查它的合理性,一个经验是大于等于2,小于等于32个。Size参数指定了每一帧数据的大小。
buf_prepare每一个缓冲(videobuf_buffer结构描述的)将被传递给该回调函数,用来配置缓冲的height,width和fileds。如果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指定video的field,也就是说interleaved或者progressive的,一般都是指定为V4L2_FIELD_NONE,用于逐行扫描的设备。
V4L2捕获设备驱动可以支持两种API:read()系统调用和更为复杂的流机制。一般的做法是两种都支持以确保所有的应用都可以使用该设备。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)
前者终止所有buffer的IO操作。后者保证所有的buffer被unmap掉,如果已经被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在被map到dma之前,要把它的状态设置为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驱动的写法。