V4l2应用框架-Videobuf2数据结构
Videobuf2作为V4L2驱动程序和用户空间之间的数据传输桥梁,用于分配和处理视频帧缓冲区,实现许多标准的POSIXI/O系统调用,包括read、poll以及mmap。实现大量与流式I/O相关V4L2ioctl调用,包括缓冲区分配、缓冲区入队列和缓冲区出队列以及流控制。
缓冲区的类型
并非所有的视频设备都使用相同类型的缓冲区类型,实际上,至少有三种常见的缓冲区类型
物理地址和虚拟地址空间都分散的缓冲区,实际上几乎所有的用户空间缓冲区都是这种类型,在某些情况下,使用这种方式分配的物理地址和虚拟地址的空间缓冲区是有意义的,使用这种缓冲区通常需要硬件可以执行scatter/gatherDMA操作物理地址分散虚拟地址连续的缓冲区:即vmalloc分配的缓冲区。这些缓冲区不适用于DMA操作,在DMA不可用,但是虚拟地址连续的情况下使用这种缓冲区十分有利物理地址连续的缓冲区,在分段式系统上这种类型的缓冲区可能不可用,长时间运行可能导致大量的内存碎片,从而难以获取到连续的内存空间,但是简单的DMA控制器只能使用这种类型的缓冲区
数据结构,回调和初始化
1结构体变量videobuf2的核心数据结构是一个缓冲区队列,用来管理所有的缓冲区。缓冲区队列用structvb2_queue描述,用来管理所有的缓冲区。缓冲区用structvb2_buffer描述,每一帧的像使用structvb2_buffer描述,像信息保证在v4l2_buffer中。structvb2_ops和structvb2_mem_ops结构体中的函数指针需要驱动实现。数据结构的关系如下所示:视频帧数据保存和管理的数据结构由structvb2_buffer描述。buf_struct_size定义了缓冲区的大小,需要驱动设置;num_buffers定义了缓冲区的数量,不能大于VIDEO_MAX_FRAME,不能小于min_buffers_needed。缓冲区的大小由buf_struct_size定义,驱动可以定义自己的缓冲区,同时设置buf_struct_size,若为0表示驱动不定义自己缓冲结构,则使用sizeof初始化buf_struct_size。缓冲区类型由enumv4l2_buf_type枚举定义,像采集使用V4L2_BUF_TYPE_VIDEO_CAPTURE类型。
————————————————————————————————————————————————————————structvb2_queue和structvb2_buffer数据结构的关系可用下描述:
所以动态分配的structvb2_buffer结构体保存到bufs[VIDEO_MAX_FRAME]数组中,由structvb2_queue统一管理。structvb2_buffer结构体的最大数量由VIDEO_MAX_FRAME宏定义;queued_list保存所有从用户空间入队的缓冲区;done_list保存准备出队到用户空间的缓冲区。
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函数中使用,以检查是否还在等待数据
[include/uapi/linux/videodev2.h]
// videobuf2类型枚举
enum v4l2_buf_type {
V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, // 图像采集
V4L2_BUF_TYPE_VIDEO_OUTPUT = 2, // 图像输出
V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
V4L2_BUF_TYPE_VBI_CAPTURE = 4,
V4L2_BUF_TYPE_VBI_OUTPUT = 5,
V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6,
V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7,
#if 1
V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8, /* Experimental */
#endif
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,
V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10,
V4L2_BUF_TYPE_SDR_CAPTURE = 11,
};
对于摄像头而言取值为V4L2_BUF_TYPE_VIDEO_CAPTURE,表示视频捕获设备
[include/media/videobuf2-core.h]
enum vb2_io_modes {
VB2_MMAP = (1 << 0), // 驱动程序支持mmap的流式API
VB2_USERPTR = (1 << 1), // 驱动程序支持用户指针模式的流式API
VB2_READ = (1 << 2), // 驱动程序支持read()方式访问
VB2_WRITE = (1 << 3), // 驱动程序支持write()方式访问
VB2_DMABUF = (1 << 4), // 驱动程序支持DMABUF的流式API
};
VB2_MMAP内存映射方式,表示将缓冲区buffer映射到用户空间,应用程序可以直接获取到像缓冲区VB2_USERPTR用户指针方式,用户空间负责分配内存,并将分配的内存地址给内核空间,用户空间通过内存地址直接获取驱动的bufferVB2_READ和VB2_WRITE传统的读写方式,效率较低,适合获取静态像VB2_DMABUFDMABUF框架提供了在多设备间共享缓存的通用方法,支持DMABUF的设备驱动可以将一个DMA缓存以文件句柄的方式输出到用户空间,以文件句柄的方式从用户空间获取一个DMA缓存,这个文件句柄是之前其他或相同的设备所输出的,或都是。V4L2缓存以DMABUF文件句柄方式进行DMABUF输出
驱动需要实现structvb2_ops中的函数,当然也可以实现一部分,也可以直接使用内核提供的函数。
// 驱动需要明确定义的函数,用于操作vb2_queue
[include/media/videobuf2-core.h]
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[]);
// 在调用ioctl等待新的缓冲区时释放所有锁,避免阻塞时产生死锁
void (*wait_prepare)(struct vb2_queue *q);
// 重新获取在前一个回调函数中释放的锁
void (*wait_finish)(struct vb2_queue *q);
// buffer初始化
int (*buf_init)(struct vb2_buffer *vb);
// buffer准备好
int (*buf_prepare)(struct vb2_buffer *vb);
// 缓冲区每次出队到用户空间都需要调用,驱动可以访问或修改缓冲区。
void (*buf_finish)(struct vb2_buffer *vb);
// 调用后缓冲区被释放,驱动可以做一些清理工作
void (*buf_cleanup)(struct vb2_buffer *vb);
// 调用后流进入开启状态,在调用之前驱动必须先调用buf_queue接收缓冲区,
int (*start_streaming)(struct vb2_queue *q, unsigned int count);
// 调用后流进入关闭状态,驱动需要停止DMA传输或等待工作完成和缓冲区全部入队。
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变量。orqueue_setup由ioctl命令VIDIOC_REQBUFS和VIDIOC_CREATE_BUFS调用,经常使用的是VIDIOC_REQBUFS,在实际分配内存之前调用一次,若分配的buffer数量不足num_buffers,则会再次调用。该函数需要将分配的buffer数量保存到num_buffers,将buffer的planes的数量保存到num_planes中,每个plans的大小存放在sizes数组中。planes和视频的像素格式有关,如YUV420SP格式的planes为alloc_ctxs数组保存每一个plane的特定数据。wait_prepare和wait_finishwait_prepare在驱动中有默认实现,为vb2_ops_wait_prepare,用来释放互斥锁,wait_finish对应vb2_ops_wait_finish,在用户空间调用VIDIOC_QBUF时被调用,如果应用程序以阻塞的方式获取像数据,并且数据没有准备好的情况,内核会调用wait_finish释放互斥锁并且进行休眠等待,直到有像数据准备好时被唤醒,再使用wait_finish重新持有互斥锁orwait_prepare和wait_finish函数内核提供了默认的实现,可以直接使用,分别对应函数vb2_ops_wait_prepare和vb2_ops_wait_finish。这两个函数实现很简单,vb2_ops_wait_prepare释放互斥锁vb2_ops_wait_finish获取互斥锁。用户调用ioctl并使用VIDIOC_QBUF命令时,内核会判断是否是阻塞调用,如果是阻塞调用并且没有准备好数据,内核此时会调用wait_prepare释放锁并进行休眠等待,直到数据准备好被唤醒,然后再调用wait_finish重新持有锁。buf_init在mmap方式下分配完buffer之后会被调用一次或者在userptr情况下请求完buffer之后调用一次来对于buffer进行一些初始化操作,如果初始化失败会导致queue_setup失败,比较少用orbuf_init在分配缓冲区之后调用或获取了新的USERPTR之后调用,驱动需要完成一些缓冲区初始化的工作,若初始化失败,则返回不为0的数,此时queue_setup将失败,一般用不到。buf_prepare缓冲区每一次入队列或者使用VIDIOC_PREPARE_BUF的ioctl命令时调用,如果此回调返回失败,那么缓冲区将不会执行queue动作orbuf_prepare缓冲区每次从用户空间入队都需要调用或被ioctl的VIDIOC_PREPARE_BUF命令调用,驱动需要执行一些初始化工作或获取、修改缓冲区,若驱动支持VIDIOC_CREATE_BUFS,则需要验证缓冲区的大小,若有错误发生,则缓冲区不会入队。buf_finish缓冲区每次出队到用户空间都需要调用,驱动可以访问或修改缓冲区buf_cleanup调用后缓冲区被释放,驱动可以做一些清理工作start_streamingstart_streaming调用后流进入开启状态,在调用之前驱动必须先调用buf_queue接收缓冲区。如果发生硬件错误,驱动可以通过该回调返回一个错误,此时所有的buffer都会被归还给videobuf如果需要设置start_streaming时buffer的最小数量,那么应该在调用该函数之前设置最少的buffer数量值vb2_queue->min_buffers_needed,只有buffer数量大于vb2_queue->min_buffers_needed时start_streaming才能被成功调用。or调用后视频流进入开启状态,在调用之前驱动必须先调用buf_queue接收buffer,必须将buffer放到驱动自己维护的一个buffer队列stop_streamingstop_streaming调用后流进入关闭状态,驱动需要停止DMA传输或等待工作完成和归还全部buffers。调用vb2_buffer_done来归还所有驱动持有的buffers,可使用VB2_BUF_STATE_DONE和VB2_BUF_STATE_ERR参数。若要等待完成,可使用vb2_wait_for_all_buffers。or调用之后视频流处于关闭状态,驱动需要关掉DMA或者等待DMA结束,调用vb2_buffer_done来归还所有驱动持有的buffers,可能需要用到vb2_wait_for_all_buffers来等待所有的buffer,该函数是用来等待所有的buffer被归还给videobuf2的buf_queue该函数通常在应用程序操作VIDIOC_STREAMON命令之后被调用,在该函数中会启动DMA传输
structvb2_buffer是保存视频数据和信息的核心结构体,每一帧的像都对应一个structvb2_buffer结构体每一帧像数据的信息保存在structv4l2_buffer结构中,如时间戳、编号、序列号等信息。应用使用ioctl的VIDIOC_REQBUFS命令请求缓冲区时,分配structvb2_buffer。structvb2_buffer的状态由enumvb2_buffer_state枚举定义描述。若是V4L2_MEMORY_MMAP类型,则会额外分配内存,像数据则保存在额外分配的内存中,额外分配的内存指针保存在planes[VIDEO_MAX_PLANES]数组中。
[includemediavideobuf2-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];
};
[includeuapilinuxvideodev2.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;
};
表示当前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到用户空间出错会设置此状态
};
[includemediavideobuf2-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;
};
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:释放分配的bufferget_dmabuf:获取DMA缓冲区fdget_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_addrnum_users:atomic_read,在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
使用方法分析
videobuf2的使用方法复杂,需要结合具体的驱动实例进行说明,这样比较好理解。下是imx6ull平台上,CSI控制器的videobuf2使用方法总结。应用可以通过调用open、close、ioctl、mmap、read系统调用访问Video设备,内核根据不同的系统调用采用相对应的方法访问videobuf下面从这些系统调用入手,分析内核中videobuf2的使用方法。
应用调用open打开Video设备,获取设备的描述符。内核中首先调用v4l2_open,然后调用驱动提供的mx6s_csi_open函数。缓冲区队列vb2_queue就是在mx6s_csi_open函数中完成初始化。缓冲区队列数据结构vb2_queue一般嵌入到其他结构体中,由驱动进行动态分配。首先必须设置缓冲区类型type、I/O模型io_modes、缓冲区操作函数集合ops、缓冲区内存管理函数集合mem_ops、时间戳类型timestamp_flags,其他成员可根据实际情况设置,最后调用vb2_queue_init完成缓冲区队列vb2_queue的初始化。
[include/media/videobuf2-core.h]
q-缓冲区队列数据结构struct vb2_queue指针
返回值-0成功,小于0失败
int vb2_queue_init(struct vb2_queue *q)
{
// 必须设置下面的成员,否则返回-EINVAL的错误
if (WARN_ON(!q) || WARN_ON(!q->ops) || WARN_ON(!q->mem_ops) ||
WARN_ON(!q->type) || WARN_ON(!q->io_modes) ||
WARN_ON(!q->ops->queue_setup) || WARN_ON(!q->ops->buf_queue) ||
WARN_ON(q->timestamp_flags & ~(V4L2_BUF_FLAG_TIMESTAMP_MASK |
V4L2_BUF_FLAG_TSTAMP_SRC_MASK)))
return -EINVAL;
// 驱动必须选择合适的时间戳,否则内核会发出警告
WARN_ON((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) ==
V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN);
INIT_LIST_HEAD(&q->queued_list); // 初始化queued_list链表节点
INIT_LIST_HEAD(&q->done_list); // 初始化done_list链表节点
spin_lock_init(&q->done_lock); // 初始化自旋锁
mutex_init(&q->mmap_lock); // 初始化互斥锁
init_waitqueue_head(&q->done_wq); // 初始化等待队列头
// 若缓冲区大小驱动没有设置,则内核使用默认值初始化
if (q->buf_struct_size == 0)
q->buf_struct_size = sizeof(struct vb2_buffer);
return 0;
}
使用VIDIOC_REQBUFS命令调用ioctl,最终会调用到vb2_reqbufs函数,内核使用vb2_reqbufs函数创建缓冲区。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2_queue指针
// req-申请缓冲区所需信息存放的结构体,应用需要设置里面的成员
// 返回值-0成功,小于0失败
int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req);
[include/uapi/linux/videodev2.h]
struct v4l2_requestbuffers {
__u32 count; // 申请缓冲区的数量,一帧图像对应一个缓冲区
__u32 type; // 缓冲区类型,摄像头为V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 memory; // 通常为V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR
__u32 reserved[2]; // 保留
};
vb2_reqbufs调用流程可总结如下:验证缓冲区的memorytype和buffertype是否正确。__verify_memory_type函数用于校验缓冲区类型和缓冲区内存类型。vb2_queue中的缓冲区类型type和v4l2_requestbuffers中的缓冲区类型type需一致。缓冲区内存类型必须是V4L2_MEMORY_MMAP、V4L2_MEMORY_USERPTR、V4L2_MEMORY_DMABUF其中之若是V4L2_MEMORY_MMAP类型,则q->io_modes必须设置为VB2_MMAP,mem_ops->alloc、q->mem_ops->put和q->mem_ops->mmap的函数指针也必须设置。若是V4L2_MEMORY_USERPTR类型,则q->io_modes必须设置为VB2_USERPTR,mem_ops->get_userptr和q->mem_ops->put_userptr的函数指针也必须设置。若是V4L2_MEMORY_DMABUF类型,则q->io_modes必须设置为VB2_DMABUF,mem_ops->attach_dmabuf、q->mem_ops->detach_dmabuf、q->mem_ops->map_dmabuf和q->mem_ops->unmap_dmabuf的函数指针也必须设置。判断缓冲区参数是否正确,若不正确,则需要做一些处理。申请的缓冲区数量为0或缓冲区队列中缓冲区数量不为0或申请的缓冲区内存类型和缓冲区队列中缓冲区内存类型不一致,则进入额外的处理逻辑。若内存类型为V4L2_MEMORY_MMAP且缓冲区正在使用,则直接返回错误。清理处于PREPARED或QUEUED状态的缓冲区并释放缓冲区内存。计算需要分配的缓冲区数量。调用驱动实现的函数queue_setup,驱动函数需要设置num_buffers、num_buffers、q->plane_sizes和q->alloc_ctx。调用__vb2_queue_alloc分配缓冲区内存。此时缓冲区的状态为VB2_BUF_STATE_DEQUEUED。后面详细说明该函数。若分配的缓冲区数量小于需要分配的数量,需要再次调用驱动提供的queue_setup函数。设置分配的缓冲区数量并向应用返回分配的缓冲区数量。
vb2_reqbufs
// 验证缓冲区的memory type和buffer type是否正确
->__verify_memory_type
->__reqbufs
// 申请的缓冲区数量为0或缓冲区队列中缓冲区数量不为0或
// 申请的缓冲区内存类型和缓冲区队列中缓冲区内存类型不一致,则需要则额外的处理
if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) {
mutex_lock(&q->mmap_lock);
// 内存类型为V4L2_MEMORY_MMAP且缓冲区正在使用,则直接返回错误
if (q->memory == V4L2_MEMORY_MMAP && __buffers_in_use(q)) {
mutex_unlock(&q->mmap_lock);
return -EBUSY;
}
// 若缓冲区处于PREPARED或QUEUED状态,则需要清理,
// 一般在申请了缓冲区,但没调用STREAMON,会出现这种情况
__vb2_queue_cancel(q);
// 释放已经分配内存的缓冲区
ret = __vb2_queue_free(q, q->num_buffers);
mutex_unlock(&q->mmap_lock);
}
// 缓冲区数量取应用申请的数量和最大数量中的较小值,VIDEO_MAX_FRAME定义为32
num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME);
// 缓冲区数量取num_buffers和需要最少的缓冲区数量的较大值
num_buffers = max_t(unsigned int, num_buffers, q->min_buffers_needed);
// 设置缓冲区队列中缓冲区的内存模型,内存模型应该和应用申请的模型保持一致
q->memory = req->memory;
// 回调用驱动提供的queue_setup函数,imx6ull提供的函数为mx6s_videobuf_setup
// 驱动函数需要设置num_buffers、num_buffers、q->plane_sizes和q->alloc_ctx
call_qop(...queue_setup...)
// 分配缓冲区内存
->__vb2_queue_alloc
// 若分配的缓冲区数量小于需要分配的数量,需要再次调用驱动提供的queue_setup函数
if (!ret && allocated_buffers < num_buffers) {
num_buffers = allocated_buffers;
// 若驱动能处理这种情况,则不会返回错误,若无法处理,则会返回错误
// mx6s_videobuf_setup不具备这种功能
ret = call_qop(q, queue_setup, q, NULL, &num_buffers,
&num_planes, q->plane_sizes, q->alloc_ctx);
if (!ret && allocated_buffers < num_buffers)
ret = -ENOMEM;
}
->mutex_lock(&q->mmap_lock); // 缓冲区共享成员访问需要同步
// 设置分配的缓冲区数量
q->num_buffers = allocated_buffers;
->mutex_unlock(&q->mmap_lock);
req->count = allocated_buffers // 向应用返回分配的缓冲区数量
queue_setup函数需要驱动提供。imx6ull提供的函数为mx6s_videobuf_setup,主要的作用是设置__reqbufs函数中的缓冲区数量num_buffers、plane的数量num_planes、plane的大小q->plane_sizes及q->alloc_ctx。上述设置的变量都是调用函数以指针的形式传入。
static int mx6s_videobuf_setup(struct vb2_queue *vq,
const struct v4l2_format *fmt,
unsigned int *count, unsigned int *num_planes,
unsigned int sizes[], void *alloc_ctxs[])
{
struct mx6s_csi_dev *csi_dev = vb2_get_drv_priv(vq);
alloc_ctxs[0] = csi_dev->alloc_ctx; // 设置alloc_ctxs
sizes[0] = csi_dev->pix.sizeimage; // 设置plane_sizes为图像的大小
if (0 == *count) // 如果传入的缓冲区数量为0,则设置为32
*count = 32;
// 如果num_planes为0且缓冲区占用的总内存超过了规定的最大值,则要重新计算缓冲区数量
// 最大值为64MB,MAX_VIDEO_MEM定义为64
if (!*num_planes && sizes[0] * *count > MAX_VIDEO_MEM * 1024 * 1024)
// 则缓冲区数量按最大内存计算
*count = (MAX_VIDEO_MEM * 1024 * 1024) / sizes[0];
*num_planes = 1; // 设置plane的数量为1
return 0;
}
缓冲区分配主要由__vb2_queue_alloc函数实现。分配num_buffers个缓冲区,即分配num_buffers个structvb2_buffer结构退。所有的缓冲区内存地址都保存到vb2_queue结构体中的bufs数组中。若是缓冲区内存是V4L2_MEMORY_MMAP类型,则还需要额外分配保存像的缓冲区,一个缓冲区分配num_planes个保存像的缓冲区。此缓冲区由vb2_dc_alloc分配。分配完后缓冲器的结构示意如下所示。V4L2_MEMORY_MMAP类型的缓冲区需要分配额外的内存空间用于存储像数据,如中绿框所属,首选分配一个管理的结构体structvb2_dc_buf,再分配真正存储像数据的缓冲区,存储像的缓冲区物理地址和虚拟地址一致,其虚拟地址保存到管理结构体的vaddr成员中,虚拟地址保存到管理结构体的dma_addr成员中,缓冲区大小保存到管理结构体的size成员中。
static int __vb2_queue_alloc(struct vb2_queue *q, enum v4l2_memory memory,
unsigned int num_buffers, unsigned int num_planes)
{
unsigned int buffer;
struct vb2_buffer *vb;
int ret;
// 总共分配num_buffers个缓冲区
for (buffer = 0; buffer < num_buffers; ++buffer) {
// 分配缓冲区内存,缓冲区大小为buf_struct_size,一般由驱动设置,
// imx6ull平台设置为sizeof(struct mx6s_buffer)
vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
// 如果是multiplanar buffers,则缓冲区长度length保存的是plane的数量
if (V4L2_TYPE_IS_MULTIPLANAR(q->type))
vb->v4l2_buf.length = num_planes;
vb->state = VB2_BUF_STATE_DEQUEUED; // 设置缓冲区状态
vb->vb2_queue = q; // 设置管理缓冲区的缓冲区队列
vb->num_planes = num_planes; // 设置plane数量
vb->v4l2_buf.index = q->num_buffers + buffer; // 设置缓冲区编号
vb->v4l2_buf.type = q->type; // 设置缓冲区类型
vb->v4l2_buf.memory = memory; // 设置缓冲区内存类型
// 对于V4L2_MEMORY_MMAP类型,则还需要分配额外的内存用于保存图像数据
// 然后映射到用户空间,用户可以直接读取额外内存中的数据
if (memory == V4L2_MEMORY_MMAP) {
// ******分配存储图像数据内存的函数*******
->__vb2_buf_mem_alloc(vb);
// 每一个缓冲区,分配num_planes块额外的内存
for (plane = 0; plane < vb->num_planes; ++plane) {
// 分配的内存大小按页对齐
unsigned long size = PAGE_ALIGN(q->plane_sizes[plane]);
// 调用驱动提供的alloc函数进行内存分配,
// imx6ull平台调用vb2_dc_alloc函数
mem_priv = call_ptr_memop(vb, alloc, q->alloc_ctx[plane],
size, dma_dir, q->gfp_flags);
// 将额外分配的内存保存到mem_priv成员中
vb->planes[plane].mem_priv = mem_priv;
// 保存长度
vb->v4l2_planes[plane].length = q->plane_sizes[plane];
}
// 调用驱动提供的buf_init函数进行初始化,imx6ull没有提供
->call_vb_qop(vb, buf_init, vb);
}
// 保存缓冲区地址
q->bufs[q->num_buffers + buffer] = vb;
}
// 设置所有缓冲区的每个plane的长度
->__setup_lengths(q, buffer);
vb->v4l2_planes[plane].length = q->plane_sizes[plane]
// MMAP类型还要设置偏移,每个buffer的每个plane偏移都不一样
if (memory == V4L2_MEMORY_MMAP)
->__setup_offsets(q, buffer);
return buffer;
}
vb2_dc_alloc由驱动提供,其分配的缓冲区虚拟地址和物理地址都连续,属于第三种类型的videobuf
[driversmediav4l2-corevideobuf2-dma-contig.c]
struct vb2_dc_buf {
struct device *dev;
void *vaddr; // 内存虚拟地址
unsigned long size; // 内存大小
dma_addr_t dma_addr; // 内存物理地址
enum dma_data_direction dma_dir; // DMA传输方向
struct sg_table *dma_sgt; // SG DMA相关
/* MMAP相关变量 */
struct vb2_vmarea_handler handler;
atomic_t refcount;
struct sg_table *sgt_base;
struct vm_area_struct *vma; // USERPTR相关变量
struct dma_buf_attachment *db_attach; // DMABUF相关变量
};
static void *vb2_dc_alloc(void *alloc_ctx, unsigned long size,
enum dma_data_direction dma_dir, gfp_t gfp_flags)
{
struct vb2_dc_conf *conf = alloc_ctx;
struct device *dev = conf->dev;
struct vb2_dc_buf *buf;
// 首先分配一个struct vb2_dc_buf结构体
buf = kzalloc(sizeof *buf, GFP_KERNEL);
// 然后分配存储图像数据的缓冲区,此缓冲区的物理地址和虚拟地址都连续
buf->vaddr = dma_alloc_coherent(dev, size, &buf->dma_addr, GFP_KERNEL | gfp_flags);
buf->dev = get_device(dev); // 设置父设备指针
buf->size = size; // 保存图像数据缓冲区的大小
buf->dma_dir = dma_dir; // 记录DMA传输方向
// 设置struct vb2_vmarea_handler结构体
buf->handler.refcount = &buf->refcount;
buf->handler.put = vb2_dc_put; // 回调函数
buf->handler.arg = buf; // 回调函数参数
atomic_inc(&buf->refcount); // 增加引用计数,引用计数为0时释放缓冲区
return buf; // 返回分配的vb2_dc_buf地址
}
使用VIDIOC_QUERYBUF命令调用ioctl,最终会调用到vb2_querybuf函数,内核使用vb2_querybuf函数将缓冲区信息拷贝到用户空间,主要有时间戳timestamp、标志flags、缓冲区长度length、缓冲区偏移offset等信息。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2_queue指针
// b-v4l2_buffer结构体指针,内核将缓冲区信息存放到里面
// 返回值-0成功,小于0失败
int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b);
使用VIDIOC_QBUF命令调用ioctl,最终会调用到vb2_qbuf函数,内核使用vb2_qbuf函数将读取完数据的空缓存返还给驱动的缓存队列。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2_queue指针
// b-v4l2_buffer结构体指针
// 返回值-0成功,小于0失败
int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b);
vb2_qbuf的主要工作如下:通过bufs数组获取对应编号的缓冲区地址。根据缓冲区的不同状态做不同的处理。VB2_BUF_STATE_DEQUEUED状态的缓冲区需要调用__buf_prepare函数执行一些准备工作。首先将缓冲区状态设置为VB2_BUF_STATE_PREPARING,V4L2_MEMORY_MMAP类型调用buf_prepare,imx6ull平台调用mx6s_videobuf_prepare,V4L2_MEMORY_USERPTR调用get_userptr,imx6ull平台调用vb2_dc_get_userptr。VB2_BUF_STATE_PREPARED状态的缓冲区无需准备工作。其他状态的缓冲区直接返回错误,不能进行VIDIOC_QBUF操作。将缓冲区挂到queued_list链表中。设置缓冲区状态为VB2_BUF_STATE_QUEUED。若VIDIOC_STREAMON已经被调用,说明流已经打开,则需要调用__enqueue_in_driver函数将缓冲区添加到驱动的队列中。
vb2_qbuf
->vb2_internal_qbuf
// 获取对应编号的缓冲区地址
vb = q->bufs[b->index];
// 检查缓冲区状态,只有VB2_BUF_STATE_DEQUEUED和
// VB2_BUF_STATE_PREPARED状态才能被加入到驱动的缓存队列
switch (vb->state) {
case VB2_BUF_STATE_DEQUEUED:
-> __buf_prepare(vb, b);
->__verify_length; // 验证数据长度
vb->state = VB2_BUF_STATE_PREPARING; // 设置缓冲区状态为PREPARING
vb->v4l2_buf.timestamp.tv_sec = 0; // 清空时间戳
vb->v4l2_buf.timestamp.tv_usec = 0;
vb->v4l2_buf.sequence = 0; // 设置序列号为0
->__qbuf_mmap(vb, b); // V4L2_MEMORY_MMAP类型调用
->__fill_vb2_buffer // 填充信息并校验
// 调用buf_prepare函数,imx6ull平台调用mx6s_videobuf_prepare函数
// 主要的作用设置payload和校验缓冲区虚拟地址是否正确
call_vb_qop(vb, buf_prepare, vb)
->__qbuf_userptr(vb, b); // V4L2_MEMORY_USERPTR类型调用
->__fill_vb2_buffer // 填充信息并校验
// 调用驱动提供的get_userptr回调函数,
// imx6ull平台调用vb2_dc_get_userptr回调函数
call_ptr_memop(...get_userptr...);
// 调用buf_prepare函数,imx6ull平台调用mx6s_videobuf_prepare函数
call_vb_qop(vb, buf_prepare, vb);
// dmabuf目前没有接触到,不讨论
->__qbuf_dmabuf(vb, b); // V4L2_MEMORY_DMABUF类型调用
break;
case VB2_BUF_STATE_PREPARED:
break;
// videobuf2正在准备缓冲区,不能被加入到驱动的缓存队列
// __buf_prepare函数内部会设置VB2_BUF_STATE_PREPARING状态
case VB2_BUF_STATE_PREPARING:
return -EINVAL;
default:
return -EINVAL;
}
// 将应用传入的缓冲区挂到queued_list链表中
list_add_tail(&vb->queued_entry, &q->queued_list)
q->queued_count++; // queued_list链表中的缓冲区数量加1
vb->state = VB2_BUF_STATE_QUEUED;
// 如果VIDIOC_STREAMON已经被调用,则需要将缓冲区添加到驱动的缓冲区队列中
if (q->start_streaming_called)
// 将缓冲区挂到imx6ull CSI设备驱动结构体的capture链表上
->__enqueue_in_driver(vb);
__fill_v4l2_buffer(vb, b); // 向用户空间填充信息
imx6ull平台buf_prepare的回调函数为mx6s_videobuf_prepare,主要的作用是设置payload,检查缓冲区虚拟地址是否存在和payload是否正确设置。
static int mx6s_videobuf_prepare(struct vb2_buffer *vb)
{
struct mx6s_csi_dev *csi_dev = vb2_get_drv_priv(vb->vb2_queue);
int ret = 0;
// 设置payload,payload为图像大小
vb2_set_plane_payload(vb, 0, csi_dev->pix.sizeimage);
// 缓冲区的有效字节数为图像大小
vb->v4l2_planes[plane_no].bytesused = size
// 检查缓冲区虚拟地址是否存在和payload是否正确设置
if (vb2_plane_vaddr(vb, 0) &&
vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) {
ret = -EINVAL;
goto out;
}
return 0;
out:
return ret;
}
// 获取plane的虚拟地址
vb2_plane_vaddr(vb, 0);
// 调用驱动提供的vaddr函数,imx6ull平台调用vb2_dc_vaddr
call_ptr_memop(vb, vaddr, vb->planes[plane_no].mem_priv)
->vb2_dc_vaddr
return buf->vaddr // 返回存储图像内存的虚拟地址
使用VIDIOC_STREAMON命令调用ioctl,最终会调用到vb2_streamon函数,内核使用vb2_streamon函数将开启视频流,对于像采集设备而言,则设备开始采集像,并将像数据保存到缓冲区中。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2_queue指针
// type-缓冲区类型
// 返回值-0成功,小于0失败
int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type);
vb2_streamon主要的工作如下:遍历queued_list链表,首先将缓冲区状态设置为VB2_BUF_STATE_ACTIVE,然后将入队的缓冲区都添加到驱动的队列中。imx6ull平台调用mx6s_videobuf_queue将缓冲区添加到capture链表中。调用start_streaming开始视频流,imx6ull平台调用mx6s_start_streaming函数使能设备,开始像采集。流开启标志设置streaming设置为
vb2_streamon
->vb2_internal_streamon
->vb2_start_streaming
// 将queued_list上的所有缓冲区添加到驱动中
list_for_each_entry(vb, &q->queued_list, queued_entry)
__enqueue_in_driver
vb->state = VB2_BUF_STATE_ACTIVE // 设置缓冲区状态
atomic_inc(&q->owned_by_drv_count) // 增加驱动使用缓冲区的计数
// 对每一缓冲区的plane调用prepare函数
all_void_memop(vb, prepare, vb->planes[plane].mem_priv)
->vb2_dc_prepare // imx6ull平台调用vb2_dc_prepare函数
// 同步缓冲区
->dma_sync_sg_for_device
// 调用buf_queue函数
call_void_vb_qop(vb, buf_queue, vb)
->mx6s_videobuf_queue
->spin_lock_irqsave // 加锁
// 将缓冲区挂到imx6ull CSI设备驱动结构体的capture链表上
list_add_tail(&buf->internal.queue, &csi_dev->capture)
->spin_unlock_irqrestore // 解锁
q->start_streaming_called = 1 // 设置VIDIOC_STREAMON已被调用标记
// 回调驱动中的start_streaming函数,视频设备开始工作
call_qop(q, start_streaming, q, atomic_read(&q->owned_by_drv_count))
->mx6s_start_streaming // imx6ull平台调用mx6s_start_streaming函数
->mx6s_csi_enable // 使能设备,开始图像采集
q->streaming = 1; // 设置流开启的标志
使用VIDIOC_DQBUF命令调用ioctl,最终会调用到vb2_dqbuf函数,内核使用vb2_dqbuf函数将填充满数据的缓存从驱动中返回给应用。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2_queue指针
// b-v4l2_buffer结构体指针
// nonblocking-阻塞标志,根据file->f_flags & O_NONBLOCK决定
// 返回值-0成功,小于0失败
int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking);
vb2_dqbuf主要的工作如下:检查缓冲区是否可用。像采集模式下,需要等待像数据填充到缓冲区中才能被使用。非阻塞且无缓冲区可用,则直接返回-EAGAIN错误。阻塞且无缓冲区可用,睡眠等待。缓冲区可用,则获取一个可用的缓冲区并将其从done_list链表中删除。将可用的缓冲区信息拷贝到用户空间。将可用的缓冲区从queued_list链表中删除。设置缓冲区状态为VB2_BUF_STATE_DEQUEUED。
vb2_dqbuf
->vb2_internal_dqbuf
// 等待缓冲器是否可用,缓冲区可用,返回0
// 非阻塞且无缓冲区可用,则直接返回-EAGAIN错误
// 阻塞且无缓冲区可用,睡眠等待
->__vb2_wait_for_done_vb
// 睡眠等待缓冲区
call_void_qop(q, wait_prepare, q)
->vb2_ops_wait_prepare // wait_prepare最终调用vb2_ops_wait_prepare函数
->vb2_ops_wait_prepare
->mutex_unlock(vq->lock); // 释放锁
->wait_event_interruptible // 睡眠等待缓冲区可用
// 缓冲区可用被唤醒,执行wait_finish函数,最终调用vb2_ops_wait_finish函数
call_void_qop(q, wait_finish, q)
->vb2_ops_wait_finish
->mutex_lock(vq->lock); // 获取锁
->spin_lock_irqsave(&q->done_lock, flags) // 获取自旋锁
// 获取done_list链表中一个可用的缓冲区
list_first_entry(&q->done_list, struct vb2_buffer, done_entry)
->__verify_planes_array // 确保取出的缓冲区所有planes可用
list_del(&(*vb)->done_entry) // 缓冲区所有planes可用,则将缓冲区从done_list链表移除
spin_unlock_irqrestore(&q->done_lock, flags) // 释放自旋锁
// 调用buf_finish函数,imx6ull平台没有实现
call_void_vb_qop(vb, buf_finish, vb)
->__fill_v4l2_buffer(vb, b) // 将出队缓冲区信息填充到用户空间
list_del(&vb->queued_entry); // 将缓冲区从queued_entry链表中删除
q->queued_count--; // 入队缓冲区减1
->__vb2_dqbuf
vb->state = VB2_BUF_STATE_DEQUEUED; // 设置缓冲器为VB2_BUF_STATE_DEQUEUED状态
使用VIDIOC_STREAMOFF命令调用ioctl,最终会调用到vb2_streamoff函数,内核使用vb2_streamoff函数关闭流。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2_queue指针
// type-缓冲区类型
// 返回值-0成功,小于0失败
int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type);
vb2_streamoff的主要工作如下:调用驱动提供的函数stop_streaming停止流,imx6ull平台调用mx6s_stop_streaming函数关闭视频采集设备。清空入队链表和清空完成链表。将所有缓冲区出队到用户空间并设置VB2_BUF_STATE_DEQUEUED状态。
vb2_streamoff
->vb2_internal_streamoff
// 取消缓冲区
->__vb2_queue_cancel
// 调用驱动提供的停止流的函数
call_void_qop(q, stop_streaming, q)
->mx6s_stop_streaming
->mx6s_csi_disable // 关闭视频采集设备
q->streaming = 0; // 清除流开启标志
q->start_streaming_called = 0; // 清除VIDIOC_STREAMON被调用标志
q->queued_count = 0; // 清除入队缓冲区计数
q->error = 0;
INIT_LIST_HEAD(&q->queued_list) // 清空入队链表
INIT_LIST_HEAD(&q->done_list) // 清空完成链表
->atomic_set(&q->owned_by_drv_count, 0) // 设置驱动引用计数为0
->wake_up_all(&q->done_wq) // 唤醒等待在缓冲区队列的线程
->__vb2_dqbuf(vb) // 将所有缓冲区出队到用户空间
vb->state = VB2_BUF_STATE_DEQUEUED
缓冲区状态变化
通过分析关于缓冲区的ioctl命令执行流程,可以总结出缓冲区的状态变化过程,如下所示。黄色表示缓冲区属于用户空间,绿色表示缓冲区属于videobuf青色表示缓冲区属于驱动。通过VIDIOC_REQBUFS命令申请缓冲区后,缓冲区的状态为VB2_BUF_STATE_DEQUEUED,缓冲区属于用户空间。通过VIDIOC_QBUF命令将缓冲区添加到内核空间,缓冲区的状态先变为VB2_BUF_STATE_PREPARING,然后再变为VB2_BUF_STATE_QUEUED,缓冲区属于videobuf若VIDIOC_STREAMON被调用,则start_streaming_called=则VIDIOC_QBUF还会把缓冲区添加到驱动缓冲区队列中,此时缓冲区属于驱动。通过VIDIOC_STREAMON命令开启流后,缓冲区被添加到驱动缓冲区队列中,缓冲区的状态为VB2_BUF_STATE_ACTIVE,此时缓冲区属于驱动。若DMA数据传输完成,则缓冲区中已被填充数据,则驱动调用vb2_buffer_done将缓冲区状态更改为VB2_BUF_STATE_DONE,此时缓冲区属于videobuf
缓冲区状态变化:
使用mmap系统调用映射Video设备,最终会调用到vb2_mmap,内核使用vb2_mmap函数将Video设备的缓冲映射到用户空间。要使用mmap,则缓冲区内存类型必须为V4L2_MEMORY_MMAP,虚拟内存区域必须是共享的VM_SHARED,像输出模式时虚拟内存区域必须是可写的VM_WRITE,像采集模式时虚拟内存区域必须是可读的VM_READ。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2_queue指针
// vma-虚拟内存区域管理结构体指针
// 返回值-0成功,小于0失败
int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma);
vb2_mmap的主要工作流程如下:获取缓冲区plane的偏移。检查映射的内存是否按页对齐,mmap要求内存按页对齐。调用驱动提供的内存映射函数mmap进行映射。imx6ull平台调用vb2_dc_mmap进行内存映射。
vb2_mmap
// 获取缓冲区plane的偏移
->__find_plane_by_offset
PAGE_ALIGN // mmap要求内存按页对齐
->mutex_lock(&q->mmap_lock) // 获取mmap_lock锁
// 调用驱动提供的mmap函数
call_memop(vb, mmap, vb->planes[plane].mem_priv, vma)
->vb2_dc_mmap // imx6ull平台调用vb2_dc_mmap进行内存映射
->dma_mmap_coherent // DMA内存一致性映射
// 设置虚拟内存不能扩大和core dump标志
vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
vma->vm_private_data = &buf->handler; // 设置私有数据
vma->vm_ops = &vb2_common_vm_ops; // 设置虚拟内存的操作函数
vma->vm_ops->open(vma); // 打开虚拟内存
->mutex_unlock(&q->mmap_lock); // 释放mmap_lock锁
使用read系统调用读取Video设备的数据,最终会调用到vb2_read,内核使用vb2_read将Video设备缓冲区中的数据拷贝到用户空间。使用read系统调用会将数据从内核空间拷贝用户空间,而Video设备产生的数据量很大,拷贝会严重影响性能,因此通常采用的是mmap或userptr的方式。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2_queue指针
// data-用户空间缓冲区指针
// count-读取数据的字节数
// ppos-文件偏移指针
// nonblocking-非阻塞标志
// 返回值-大于等于0成功读取的字节数,小于0失败
size_t vb2_read(struct vb2_queue *q, char __user *data, size_t count,
loff_t *ppos, int nonblocking);
使用close系统关闭Video设备,最终会调用到vb2_queue_release,内核使用vb2_queue_release关闭Video设备、清理缓冲区队列和释放缓冲区占用的内存。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2_queue指针
// 返回值-无
void vb2_queue_release(struct vb2_queue *q);
vb2_queue_release的主要工作如下:释放文件I/O模拟器占用的资源停止流,关闭Video设备,清空入队链表和清空完成链表,将所有缓冲区出队到用户空间并设置VB2_BUF_STATE_DEQUEUED状态。释放缓冲区内存。
void vb2_queue_release(struct vb2_queue *q)
{
__vb2_cleanup_fileio(q); // 释放文件I/O模拟器占用的资源
__vb2_queue_cancel(q); // 退出和停止流
mutex_lock(&q->mmap_lock);
// 释放缓冲区内存
__vb2_queue_free(q, q->num_buffers);
mutex_unlock(&q->mmap_lock);
}
文章为作者独立观点,不代表 股票程序化软件自动交易接口观点