V4l2框架-Videobuf2数据结构简单分析

Videobuf2是Linux内核V4L2框架的一部分,它作为视频设备驱动和用户空间交互的桥梁,处理视频帧缓冲区的分配和管理。该框架支持多种缓冲区类型,包括物理分散虚拟连续、物理地址连续等,适应不同的DMA操作需求。文章详细介绍了Videobuf2中的数据结构,如vb2_queue、vb2_buffer,以及关键回调函数如queue_setup、buf_prepare、buf_queue等。此外,还涉及到了内存管理操作,如内存映射、用户指针和DMA缓冲区的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.简介

Videobuf2作为V4L2驱动程序和用户空间之间的数据传输桥梁,用于分配和处理视频帧缓冲区,实现许多标准的 POSIX I/O 系统调用,包括 read()、poll() 以及mmap()。实现大量与流式 I/O 相关V4L2 ioctl() 调用,包括缓冲区分配、缓冲区入队列和缓冲区出队列以及流控制。

2.缓冲区的类型

并非所有的视频设备都使用相同类型的缓冲区类型,实际上,至少有三种常见的缓冲区类型

  • 物理地址和虚拟地址空间都分散的缓冲区,实际上几乎所有的用户空间缓冲区都是这种类型,在某些情况下,使用这种方式分配的物理地址和虚拟地址的空间缓冲区是有意义的,使用这种缓冲区通常需要硬件可以执行scatter(分散)/gather(收集) DMA 操作
  • 物理地址分散虚拟地址连续的缓冲区:即vmalloc分配的缓冲区。这些缓冲区不适用于DMA操作,在DMA不可用,但是虚拟地址连续的情况下使用这种缓冲区十分有利
  • 物理地址连续的缓冲区,在分段式系统上这种类型的缓冲区可能不可用,长时间运行可能导致大量的内存碎片,从而难以获取到连续的内存空间,但是简单的DMA控制器只能使用这种类型的缓冲区

3.数据结构,回调和初始化

3.1 结构体变量

在分析回调函数和方法之前,我们需要对Videobuf2所使用的结构体变量有一个大致的理解,在Videobuf2中vb2_queue数据结构表示缓冲区队列,用来管理所有的缓冲区,每一帧的图像使用struct vb2_buffer描述,图像信息保证在v4l2_buffer中

3.1.1 vb2_queue
struct vb2_queue {
	enum v4l2_buf_type		type;					/*videobuf2类型,见枚举enum v4l2_buf_type*/
	unsigned int			io_modes;				/*支持的IO模式,见枚举enum vb2_io_modes*/
	unsigned			fileio_read_once:1;			/*读取第一个缓冲区后报告EOF*/
	unsigned			fileio_write_immediately:1;	/*write写入的数据都添加到缓冲队列中*/
	unsigned			allow_zero_bytesused:1;/*允许将byteused = 0 传递给驱动程序*/

	struct mutex			*lock;	/*保护vb2_queue的互斥锁,如果设置为NULL,表示Videobuf2不使用这个锁*/
	struct v4l2_fh			*owner;	/*文件句柄属于的模块,即调用reqbuf的文件句柄*/

	const struct vb2_ops		*ops;	/*实现开关视频流等回调函数*/
	const struct vb2_mem_ops	*mem_ops;/*实现mmap等内存管理回调函数*/
	void				*drv_priv;		 /*驱动的私有数据*/
	unsigned int			buf_struct_size;/*驱动的缓冲结构体大小,若为0表示驱动不想定义自己缓冲结构,使用sizeof(struct vb2_buffer)*/
	u32				timestamp_flags;		/*时间戳标志,作用是????,暂时不清楚*/
	gfp_t				gfp_flags;		    /*分配缓冲区时的内存标志,通常为0*/
	u32				min_buffers_needed;     /*需要的最小缓冲区数量*/

/* private: internal use only */
	struct mutex			mmap_lock;		/*保护缓冲区的分配,释放和映射*/
	enum v4l2_memory		memory;			/*memroy的类型,从userspace传下来,见枚举enum v4l2_memory*/
	struct vb2_buffer		*bufs[VIDEO_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;/*等待缓冲区出队的等待队列*/

	void				*alloc_ctx[VIDEO_MAX_PLANES];/*每一个plane特定memory类型/分配器内容*/
	unsigned int			plane_sizes[VIDEO_MAX_PLANES];/*表示某一个平面的大小*/

	unsigned int			streaming:1;/*视频流的状态*/
	unsigned int			start_streaming_called:1;/*stream on成功调用的标志*/
	unsigned int			error:1;//表示queue时发生错误
	unsigned int			waiting_for_buffers:1;//在poll函数中使用,以检查是否还在等待数据
。。。。。。。
3.1.2 enum v4l2_buf_type type

对于摄像头而言取值为V4L2_BUF_TYPE_VIDEO_CAPTURE,表示视频捕获设备

3.1.3 io_modes

缓冲区的IO模型使用io_modes来6表示,取值为enum vb2_io_modes,

enum vb2_io_modes {
        VB2_MMAP        = BIT(0),
        VB2_USERPTR     = BIT(1),
        VB2_READ        = BIT(2),
        VB2_WRITE       = BIT(3),
        VB2_DMABUF      = BIT(4),
};
  • VB2_MMAP

内存映射方式,表示将缓冲区buffer映射到用户空间,应用程序可以直接获取到图像缓冲区

  • VB2_USERPTR

用户指针方式,用户空间负责分配内存,并将分配的内存地址给内核空间,用户空间通过内存地址直接获取驱动的buffer

  • VB2_READ和VB2_WRITE
    传统的读写方式,效率较低,适合获取静态图像

  • VB2_DMABUF

DMABUF框架提供了在多设备间共享缓存的通用方法,支持DMABUF的设备驱动可以将一个DMA缓存以文件句柄的方式输出到用户空间(输出
者规则),以文件句柄的方式从用户空间获取一个DMA缓存,这个文件句柄是之前其他或相同的设备所输出的(引入者规则),或都是。
V4L2缓存以DMABUF文件句柄方式进行DMABUF输出

3.1.4 vb2_ops
struct vb2_ops {
	int (*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt,
			   unsigned int *num_buffers, unsigned int *num_planes,
			   unsigned int sizes[], void *alloc_ctxs[]);

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

	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);
};
  1. queue_setup

在分配缓冲区前queue_setup会被VIDIOC_REQBUFSVIDIOC_CREATE_BUFS调用,此函数可以调用两次,如果分配的缓冲区无法分配用户空间指定的的缓冲区个数,则使用实际的缓冲区数并且再次调用,并将分配的buffer数量保存到变量num_buffers中,将buffer的plane数量保存到num_plane中,每一个plane的大小在其size数组中设置,alloc_ctxs数组保存每一个plane的特定数据,最后将num_buffer的值付给vb2_queue->num_buffer变量

  1. wait_preparewait_finish

wait_prepare在驱动中有默认实现,为vb2_ops_wait_prepare,用来释放互斥锁,wait_finish对应vb2_ops_wait_finish,在用户空间调用VIDIOC_QBUF时被调用,如果应用程序以阻塞的方式获取图像数据,并且数据没有准备好的情况,内核会调用wait_finish释放互斥锁并且进行休眠等待,直到有图像数据准备好时被唤醒,再使用wait_finish重新持有互斥锁

  1. buf_init

在mmap方式下分配完buffer之后会被调用一次或者在userptr情况下请求完buffer之后调用一次来对于buffer进行一些初始化操作,如果初始化失败会导致queue_setup失败,比较少用

  1. buf_prepare

缓冲区每一次入队列或者使用VIDIOC_PREPARE_BUF的ioctl命令时调用,如果此回调返回失败,那么缓冲区将不会执行 queue 动作

  1. buf_finish

缓冲区每次出队到用户空间都需要调用,驱动可以访问或修改缓冲区

  1. buf_cleanup

调用后缓冲区被释放,驱动可以做一些清理工作

  1. start_streaming

调用后视频流进入开启状态,在调用之前驱动必须先调用buf_queue接收buffer,必须将buffer放到驱动自己维护的一个buffer队列

  1. stop_streaming

调用之后视频流处于关闭状态,驱动需要关掉 DMA 或者等待 DMA 结束,调用 vb2_buffer_done 来归还所有驱动持有的 buffers(参数使用 VB2_BUF_STATE_DONE 或者 VB2_BUF_STATE_ERR),可能需要用到 vb2_wait_for_all_buffers 来等待所有的 buffer,该函数是用来等待所有的 buffer 被归还给 videobuf2 的

  1. buf_queue

改函数通常在应用程序操作VIDIOC_STREAMON命令之后被调用,在该函数中会启动DMA传输

3.1.5 struct vb2_mem_ops
struct vb2_mem_ops {

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

	/*获取用户空间指针指向的内存,在memory类型为V4L2_MEMORY_USERPTR中使用*/
	void		*(*get_userptr)(void *alloc_ctx, unsigned long vaddr,
					unsigned long size,
					enum dma_data_direction dma_dir);
	/*释放vb2_dc_buf*/
	void		(*put_userptr)(void *buf_priv);

	/*用做缓存同步,用户空间每次将buffer入队列时会调用*/
	void		(*prepare)(void *buf_priv);

	/*内核空间每一次将buffer出队列时会被调用*/
	void		(*finish)(void *buf_priv);

	/*在V4L2_MEMORY_DMABUF下使用,实现 DMA 硬件对 dma-buf 的访问等*/
	void		*(*attach_dmabuf)(void *alloc_ctx, struct dma_buf *dbuf,
					  unsigned long size,
					  enum dma_data_direction dma_dir);
	/*通知缓冲区的exporter目前的DMABUF不再使用*/
	void		(*detach_dmabuf)(void *buf_priv);
	/*分配dma地址等*/
	int		(*map_dmabuf)(void *buf_priv);
	/*释放分配的dma地址*/
	void		(*unmap_dmabuf)(void *buf_priv);

	/*返回缓冲区dma映射的虚拟地址*/
	void		*(*vaddr)(void *buf_priv);
	/*返回缓冲区的dma_addr*/
	void		*(*cookie)(void *buf_priv);

	/*atomic_read(&buf->refcount),在reqbuf调用,返回1为video2buf调用*/
	unsigned int	(*num_users)(void *buf_priv);

	/*建立用户空间到内核空间的虚拟地址映射关系*/
	int		(*mmap)(void *buf_priv, struct vm_area_struct *vma);
};

   enum dma_data_direction {   // DMA数据传输方向
        DMA_BIDIRECTIONAL = 0,  // 双向
        DMA_TO_DEVICE = 1,      // 数据从CPU发送到设备(如write系统调用)
        DMA_FROM_DEVICE = 2,    // 数据从设备发送到CPU
        DMA_NONE = 3, //数据可双向移动
    }; 

alloc:分配保存图像的buffer,设置buffer大小,并返回分配的图像buffer地址

put:释放分配的buffer

get_dmabuf:获取DMA缓冲区fd

get_userptr:获取用户空间指针指向的内存,在memory类型为V4L2_MEMORY_USERPTR中使用

put_userptr :释放vb2_dc_buf,在V4L2_MEMORY_USERPTR中使用

prepare :用做缓存同步,用户空间每次将buffer入队列时会调用

finish :内核空间每一次将buffer出队列时会被调用

attach_dmabuf :在V4L2_MEMORY_DMABUF下使用,实现 DMA 硬件对 dma-buf 的访问等

detach_dmabuf:通知缓冲区的exporter目前的DMABUF不再使用

map_dmabuf :释放分配的dma地址

vaddr :返回缓冲区dma映射的虚拟地址

cookie:返回缓冲区的dma_addr

num_users :atomic_read(&buf->refcount),在reqbuf调用,返回1为video2buf调用

mmap :建立用户空间到内核空间的虚拟地址映射关系

注意点:

  1. alloc、put、num_users、vaddr函数用于处理read/write访问类型的buffer
  2. alloc、put、num_users、mmap函数用于处理MMAP类型的buffer
  3. get_userptr、put_userptr函数用于处理USERPTR类型的buffer
  4. attach_dmabuf、detach_dmabuf、map_dmabuf、unmap_dmabuf函数用于处理DMABUF类型的buffer
3.1.6 vb2_buffer,v4l2_buffer,v4l2_plane,vb2_plane

在videobuf2中,每一帧的图像使用struct vb2_buffer描述,每一帧图像数据的信息保证在v4l2_buffer中

[include\media\videobuf2-core.h]
struct vb2_buffer {
	/*v4l2_buffer用来保存图像信息*/
	struct v4l2_buffer	v4l2_buf;
	/*每一个v4l2_plane都包含了各自的,m.offset和length。当使用多平面API时,
	 *每个缓存冲的每个平面都需要分别映射,
	 *所以调用mmap()的次数就等于缓存数乘以每个缓存内的平面数量
	*/
	struct v4l2_plane	v4l2_planes[VIDEO_MAX_PLANES];

	/*b2_buffer所属的vb2_queue*/
	struct vb2_queue	*vb2_queue;

	/*该buffer有多少个planes,由用户空间设置*/
	unsigned int		num_planes;

/* Private: internal use only */
	/*buffer当前状态*/
	enum vb2_buffer_state	state;

	/* queued buffer链表,保存所有从用户空间入队列buffers*/
	struct list_head	queued_entry;

	/*done buffer链表,保存所有从内核空间出队列buffers*/
	struct list_head	done_entry;

	/*私有plane信息,驱动不可用修改*/
	struct vb2_plane	planes[VIDEO_MAX_PLANES];

};

[include\uapi\linux\videodev2.h]
struct v4l2_buffer {
	//buffer的编号,由用户空间传入
	__u32			index;
	//buffer的类型,在enum v4l2_buf_type定义
	__u32			type;
	//对single_plane表示图像缓冲区的大小
	//对于mutli_plane设置为0,不适用
	__u32			bytesused;
	//buffer的标志位
	__u32			flags;
	__u32			field;
	struct timeval		timestamp;
	struct v4l2_timecode	timecode;
   //帧的序列号
	__u32			sequence;

	/* memory location */
	//在enum v4l2_memory枚举定义
	__u32			memory;
	union {
		//用于V4L2_MEMORY_MMAP,表示需要mmap的数据对于数据开始位置的偏移
		__u32           offset;
		//用于V4L2_MEMORY_USERPTR,用户空间指向的buffer大小
		unsigned long   userptr;
		//对于mutli_plane而言,为用户空间关联的plane数组
		struct v4l2_plane *planes;
		//用于V4L2_MEMORY_DMABUF,用户空间用于关联buffer的文件描述符
		__s32		fd;
	} m;

     //size in bytes of the buffer (NOT its payload) for single-plane
    //buffers (when type != *_MPLANE); number of elements in the
    //planes array for multi-plane buffers
	__u32			length;

	//保留位
	__u32			reserved2;
	__u32			reserved;
};

[include\media\videobuf2-core.h]
struct v4l2_plane {
	//number of bytes occupied by data in the plane (payload),图像数据大小
	__u32			bytesused;
	//size of this plane (NOT the payload) in bytes
	//plane的大小,并非图像数据数据大小
	__u32			length;
	union {
		//用于V4L2_MEMORY_MMAP,表示需要mmap的数据对于数据开始位置的偏移
		__u32		mem_offset;
		//用于V4L2_MEMORY_USERPTR,用户空间指向的buffer大小
		unsigned long	userptr;
		//用于V4L2_MEMORY_DMABUF,用户空间用于关联buffer的文件描述符
		__s32		fd;
	} m;
	//offset in the plane to the start of data
	//平面中相对于数据开始位置的偏移
	__u32			data_offset;
	__u32			reserved[11];
};

struct vb2_plane {
	//存放一帧图片数据(针对MMAP类型)
	void			*mem_priv;
	//存放一帧图像数据(针对于DMABUF)
	struct dma_buf		*dbuf;
	//针对于DMABUF
	unsigned int		dbuf_mapped;
};

3.1.7 vb2_buffer_state

表示当前buffer的状态

 [include/media/videobuf2-core.h]
    enum vb2_buffer_state {  
        VB2_BUF_STATE_DEQUEUED,  // 缓冲区出队,处于用户空间的控制下,在reqbuf时设置
        VB2_BUF_STATE_PREPARING, // videobuf2准备缓冲区
        VB2_BUF_STATE_PREPARED,  // 缓冲区已准备好
        VB2_BUF_STATE_QUEUED,    // 缓冲区入队,处于videobuf2中,不处于驱动中
        VB2_BUF_STATE_ACTIVE,    // 缓冲区位于驱动中
        VB2_BUF_STATE_DONE,      // 缓冲区从驱动返回到videobuf2,但还没出队到用户空间
        VB2_BUF_STATE_ERROR,     // 出错,dequeued到用户空间出错会设置此状态
    };

参考:

Linux内核4.14版本——DMA Engine框架分析(2)_功能介绍及解接口分析(slave client driver)_风雨兼程8023的博客-优快云博客_dma_async_issue_pending

LINUX-DMA-子系统_ZHIK的博客-优快云博客_dma_sync_sg_for_device

Linux V4L2子系统-videobuf2框架分析(三)_业余程序员plus的博客-优快云博客

【Linux开发】IO streaming DMA buffer importing_Zhang_P_Y的博客-优快云博客)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值