Linux V4l2中videobuf2实现

1.Videobuf2简介

videobuf2作为v4l2驱动程序和用户之间的数据传输的桥梁,用来分配和处理视频缓冲区,实现IO系统调用,包括read()、poll()、以及mmap()。
实现流式IO相关v4l2 ioctl()调用,包括缓冲区分配、缓冲区入队出队以及数据流的打开、关闭等控制。

2.video buffer缓冲区的类型

  • vb2_dma_sg_memops(DMA scatter/gather memory):物理地址和虚拟地址都是离散的缓冲区,几乎所有的用户空间缓冲区都是这样的,使用这种缓冲区时,需要硬件支持scatter(分散)/gather(收集) dma操作;
  • vb2_vmalloc_memops(vmallo memory):物理地址分散虚拟地址连续的缓冲区:即vmalloc分配的缓冲区,这种情况不能使用dma操作,在video buffer中这种缓冲区类型为USERPTR;
  • vb2_dma_contig_memops(DMA contig memory):物理地址连续的缓冲区,在分段式系统上这种类型的缓冲区可能不可用,长时间运行可能导致大量的内存碎片,从而难以获取到连续的内存空间,但是简单的DMA控制器只能使用这种类型的缓冲区;

3.Videobuf2结构体关键属性

v4l2_buffer用于用户空间和内核驱动交换数据,vb2_buffer是缓存队列的基本单位。
当IO流开始时,帧以v4l2_buffer格式在应用层和驱动层之间进行传输,一帧数据的三个状态是:

  • 在用户空间:通过VIDIOC_DQBUF将缓冲区数据取出;
  • 在驱动输出队列中:当驱动将缓冲区填充后,转移到输出队列;
  • 在驱动输入队列中:用户通过VIDIOC_QBUF 把缓冲区交给驱动;

3.1vb2_buffer 结构体

struct vb2_buffer {
	struct vb2_queue	*vb2_queue; //buffer所在的队列
	unsigned int		index;  //buffer序列号 vb2_queue->bufs[index]
	unsigned int		type;  //buffer类型,用户设定的
	unsigned int		memory; //buffer内存 mmap、usrptr、dmabuf三者之一
	unsigned int		num_planes; //对应的plane数量
	u64			timestamp;
	struct media_request	*request;
	struct media_request_object	req_obj;

	enum vb2_buffer_state	state; //该buffer当前的状态
	unsigned int		synced:1;
	unsigned int		prepared:1;
	unsigned int		copied_timestamp:1;
	unsigned int		need_cache_sync_on_prepare:1;
	unsigned int		need_cache_sync_on_finish:1;

	struct vb2_plane	planes[VB2_MAX_PLANES];
	struct list_head	queued_entry; //用户空间调用VIDIOC_QBUF queue的buffer入口,vb2_core_qbuf()时插入,vb2_core_dqbuf()时移除
	struct list_head	done_entry;//等待出队到用户空间的buffer入口点,会放入vb2_queue->done_list链表中,vb2_buffer_done()执行插入动作,__vb2_get_done_vb()执行移除动作
    ...
};

vb2_buffer->queued_entry和vb2_buffer->done_entry分别在vb2_queue->queued_list和vb2_queue->done_list两个链表上插入/移出

3.2vb2_buffer_state 结构体

enum vb2_buffer_state {
	VB2_BUF_STATE_DEQUEUED,      vb2_buffer->queued_entry从 vb2_queue->queued_list上移出,缓存交给用户空间处理
	VB2_BUF_STATE_IN_REQUEST,  
	VB2_BUF_STATE_PREPARING,     videobuf2准备缓冲区
	VB2_BUF_STATE_QUEUED,      vb2-buffer加入到 vb2_queue->queued_list中 vb2_core_qbuf()函数设置,不在驱动中
	VB2_BUF_STATE_ACTIVE,      vb2-buffer正在被驱动填充处理
	VB2_BUF_STATE_DONE,      驱动填充vb2-buffer后,放入 vb2_queue->done_list中,等待用户取走,还没到用户空间
	VB2_BUF_STATE_ERROR,     出错,通知用户空间进行dequeue
};

3.3vb2_queue 结构体

struct vb2_queue {
	unsigned int	type;  //videobuf2类型,见枚举enum v4l2_buf_type
	unsigned int	io_modes; //IO模式,见枚举enum vb2_io_modes
    ...
	unsigned int	fileio_read_once:1; //读取第一个缓冲区后报告EOF
	unsigned int	fileio_write_immediately:1;  //write写入的数据都添加到缓冲队列中
	unsigned int	allow_zero_bytesused:1;  //允许将byteused = 0 传递给驱动程序
    ...
	struct mutex			*lock; //保护vb2_queue的互斥锁,如果设置为NULL,表示Videobuf2不使用这个锁
	void				*owner; //文件句柄属于的模块,即调用reqbuf的文件句柄

	const struct vb2_ops		*ops; //实现开关视频流等回调函数,管理队列中vb2 buffer
	const struct vb2_mem_ops	*mem_ops; //实现mmap等内存管理回调函数,操作vb2 buffer内存,具体方式有vb2_vmalloc_memops、vb2_dma_sg_memops、vb2_dma_contig_memops
	const struct vb2_buf_ops	*buf_ops; //主要用来操作vb2 buffer或者v4l2 buffer结构体

	void				*drv_priv; //驱动的私有数据
	u32				subsystem_flags;
	unsigned int			buf_struct_size; //驱动的缓冲结构体大小,若为0表示驱动不想定义自己缓冲结构,使用sizeof(struct vb2_buffer)
	u32				timestamp_flags;
	gfp_t				gfp_flags; //分配缓冲区时的内存标志,通常为0
	u32				min_buffers_needed; //需要的最小缓冲区数量

	struct device			*alloc_devs[VB2_MAX_PLANES];

	/* private: internal use only */
	struct mutex			mmap_lock; //保护缓冲区的分配,释放和映射
	unsigned int			memory;     //memroy的类型,从userspace传下来,见枚举enum v4l2_memory
	enum dma_data_direction 
	struct vb2_buffer		*bufs[VB2_MAX_FRAME]; //配的驱动缓冲区的地址
	unsigned int			num_buffers; //分配的缓冲区数据,从用户空间传入

	struct list_head		queued_list;  //从用户空间入队列的缓冲区链表
	unsigned int			queued_count;  //入队列的就绪态的缓冲区数量

	atomic_t			owned_by_drv_count; //属于驱动的缓冲区数量
	struct list_head		done_list; //在这个链表中的缓冲区已经填充了数据,可以出队列被用户空间使用
	spinlock_t			done_lock; //保护done_list链表的自旋锁
	wait_queue_head_t		done_wq; //等待缓冲区出队的等待队列

	unsigned int			streaming:1; //视频流的状态
	unsigned int			start_streaming_called:1; //stream on成功调用的标志
	unsigned int			error:1;
	unsigned int			waiting_for_buffers:1; //在poll函数中使用,以检查是否还在等待数据
    ...
}

3.4vb2_ops函数集的实现

struct vb2_ops {
	int (*queue_setup)(struct vb2_queue *q, unsigned int *num_buffers, unsigned int *num_planes,unsigned int sizes[], struct device *alloc_devs[]);

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

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

	int (*start_streaming)(struct vb2_queue *q, unsigned int count);
	void (*stop_streaming)(struct vb2_queue *q);

	void (*buf_queue)(struct vb2_buffer *vb);

	void (*buf_request_complete)(struct vb2_buffer *vb);
};

驱动中注册vb2 ops

static struct vb2_ops dmarx_vb2_ops = {
	.queue_setup = rkisp_queue_setup,
	.buf_queue = rkisp_buf_queue,
	.wait_prepare = vb2_ops_wait_prepare,
	.wait_finish = vb2_ops_wait_finish,
	.stop_streaming = dmarx_stop_streaming,
	.start_streaming = dmarx_start_streaming,
};
  • queue_setup:在分配缓冲区前queue_setup会被VIDIOC_REQBUFS和VIDIOC_CREATE_BUFS调用,此函数可以调用两次,如果分配的缓冲区无法分配用户空间指定的的缓冲区个数,则使用实际的缓冲区数并且再次调用,并将分配的buffer数量保存到变量num_buffers中,将buffer的plane数量保存到num_plane中,每一个plane的大小在其size数组中设置,alloc_ctxs数组保存每一个plane的特定数据,最后将num_buffer的值付给vb2_queue->num_buffer变量。
  • buf_queue:在应用层调用VIDIOC_STREAMON后,该函数启动dma传输。
  • wait_prepare和 wait_finish:wait_prepare
    释放互斥锁,调用qbuf时内核判断是否阻塞调用,若阻塞且数据没准备好内核调用wait_prepare释放锁并且进行休眠等待,直到数据准备好被唤醒,然后再调用wait_finish重新持有锁;
  • stop_streaming:调用之后视频流处于关闭状态,驱动需要关掉DMA或者等待DMA结束,调用b2_buffer_done来归还所有驱动持有的buffers(参数使用VB2_BUF_STATE_DONE或者VB2_BUF_STATE_ERR),可能需要用到vb2_wait_for_all_buffers来等待所有的buffer,该函数是用来等待所有的 buffer被归还给videobuf2。
  • start_streaming:调用后视频流进入开启状态,在调用之前驱动必须先调用buf_queue接收buffer,必须将buffer放到驱动自己维护的一个buffer队列。
  • buf_init:在mmap方式下分配完buffer之后会被调用一次或者在userptr情况下请求完buffer之后调用一次来对于buffer进行一些初始化操作,如果初始化失败会导致queue_setup失败,比较少用。

3.5vb2_mem_ops结构体

struct vb2_mem_ops是用来操作vb2 buffer内存的函数集合,buffer的类型为enum v4l2_memory, 具体实现方式有vb2_cma_sg_memops/vb2_dma_contig_memops/vb2_vmalloc_memops/vb2_dma_sg_memops
buffer的类型如下
(1)get_userptr 和 put_userptr 函数用于处理 USERPTR类型的buffer;
(2)alloc、put、num_users 和 mmap 函数用于处理 MMAP类型的buffer;
(3)alloc、put、num_users 和 vaddr 函数用于处理 read/write访问类型的buffer;
(4)attach_dmabuf、detach_dmabuf、map_dmabuf 和 unmap_dmabuf 函数用于处理DMABUF类型的buffer;

struct vb2_mem_ops {
	void		*(*alloc)(struct device *dev, unsigned long attrs,unsigned long size,enum dma_data_direction dma_dir,
				  gfp_t gfp_flags); //分配保存图像的buffer,设置buffer大小,并返回分配的图像buffer地址
	void		(*put)(void *buf_priv);
	struct dma_buf *(*get_dmabuf)(void *buf_priv, unsigned long flags); //获取DMA缓冲区fd

	void		*(*get_userptr)(struct device *dev, unsigned long vaddr,unsigned long size,
						enum dma_data_direction dma_dir);
	void		(*put_userptr)(void *buf_priv);

    void		(*prepare)(void *buf_priv); //内核获取buffer用做缓存同步,用户空间每次将buffer入队列时会调用
   void		(*finish)(void *buf_priv); //内核将buffer出队
   void		*(*attach_dmabuf)(struct device *dev, struct dma_buf *dbuf,unsigned long size, enum dma_data_direction dma_dir); 
				//在V4L2_MEMORY_DMABUF下使用,实现DMA硬件对dma-buf访问
	void		(*detach_dmabuf)(void *buf_priv); //通知缓冲区的exporter目前的DMABUF不再使用
	int		(*map_dmabuf)(void *buf_priv); //映射分配的dma地址
	void		(*unmap_dmabuf)(void *buf_priv);//释放分配的dma地址
	
   void		*(*vaddr)(void *buf_priv); //返回缓冲区dma映射的虚拟地址
   void		*(*cookie)(void *buf_priv); //返回缓冲区的dma_addr
   
  unsigned int	(*num_users)(void *buf_priv);//atomic_read(&buf->refcount),在reqbuf调用,返回1为video2buf调用
  int		(*mmap)(void *buf_priv, struct vm_area_struct *vma); //建立用户空间与内核空间的映射关系
  };

例如vb2_cma_sg_memops的初始化

const struct vb2_mem_ops vb2_cma_sg_memops = {
	.alloc		= vb2_cma_sg_alloc,
	.put		= vb2_cma_sg_put,
	.get_userptr	= vb2_cma_sg_get_userptr,
	.put_userptr	= vb2_cma_sg_put_userptr,
	.prepare	= vb2_cma_sg_prepare,
	.finish		= vb2_cma_sg_finish,
	.vaddr		= vb2_cma_sg_vaddr,
	.mmap		= vb2_cma_ssg_mmap,
	.num_users	= vb2_cma_sg_num_users,
	.get_dmabuf	= vb2_cma_sg_get_dmabuf,
	.map_dmabuf	= vb2_cma_sg_map_dmabuf,
	.unmap_dmabuf	= vb2_cma_sg_unmap_dmabuf,
	.attach_dmabuf	= vb2_cma_sg_attach_dmabuf,
	.detach_dmabuf	= vb2_cma_sg_detach_dmabuf,
	.cookie		= vb2_cma_sg_cookie,
};
EXPORT_SYMBOL_GPL(vb2_cma_sg_memops);

4.vb2 buffer驱动开发者注意事项

(1)必须正确实现 vb2_ops 中的回调函数
(2)填充完缓冲区后需要调用 vb2_buffer_done()
(3)需要处理内存映射和用户指针两种模式
(4)需要考虑多平面视频格式的支持

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值