文章目录
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);
};
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变量
wait_prepare
和wait_finish
wait_prepare
在驱动中有默认实现,为vb2_ops_wait_prepare
,用来释放互斥锁,wait_finish
对应vb2_ops_wait_finish
,在用户空间调用VIDIOC_QBUF
时被调用,如果应用程序以阻塞的方式获取图像数据,并且数据没有准备好的情况,内核会调用wait_finish
释放互斥锁并且进行休眠等待,直到有图像数据准备好时被唤醒,再使用wait_finish
重新持有互斥锁
buf_init
在mmap方式下分配完buffer之后会被调用一次或者在userptr情况下请求完buffer之后调用一次来对于buffer进行一些初始化操作,如果初始化失败会导致queue_setup
失败,比较少用
buf_prepare
缓冲区每一次入队列或者使用VIDIOC_PREPARE_BUF
的ioctl命令时调用,如果此回调返回失败,那么缓冲区将不会执行 queue 动作
buf_finish
缓冲区每次出队到用户空间都需要调用,驱动可以访问或修改缓冲区
buf_cleanup
调用后缓冲区被释放,驱动可以做一些清理工作
start_streaming
调用后视频流进入开启状态,在调用之前驱动必须先调用buf_queue接收buffer,必须将buffer放到驱动自己维护的一个buffer队列
stop_streaming
调用之后视频流处于关闭状态,驱动需要关掉 DMA 或者等待 DMA 结束,调用 vb2_buffer_done
来归还所有驱动持有的 buffers(参数使用 VB2_BUF_STATE_DONE
或者 VB2_BUF_STATE_ERR
),可能需要用到 vb2_wait_for_all_buffers
来等待所有的 buffer,该函数是用来等待所有的 buffer 被归还给 videobuf2 的
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
:建立用户空间到内核空间的虚拟地址映射关系
注意点:
alloc、put、num_users、vaddr
函数用于处理read/write访问类型的bufferalloc、put、num_users、mmap
函数用于处理MMAP类型的bufferget_userptr、put_userptr
函数用于处理USERPTR类型的bufferattach_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-DMA-子系统_ZHIK的博客-优快云博客_dma_sync_sg_for_device
Linux V4L2子系统-videobuf2框架分析(三)_业余程序员plus的博客-优快云博客
【Linux开发】IO streaming DMA buffer importing_Zhang_P_Y的博客-优快云博客)