vb2_buffer结构探究

本文详细解析了vb2_buffer在摄像头驱动程序中的关键作用,涉及buffer请求、映射、队列操作和数据处理流程。通过vb2_queue结构的初始化、VIDIOC_REQBUFS、VIDIOC_QUERYBUF等 ioctl 调用,展示了如何在V4L2框架下进行高效的数据获取。

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

vb2_buffer结构探究

vb2_buffer结构探究

摄像头驱动程序必需的11个ioctl:
// 表示它是一个摄像头设备
.vidioc_querycap = vidioc_querycap,
/* 用于列举、获得、测试、设置摄像头的数据的格式 /
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
/
缓冲区操作: 申请/查询/放入队列/取出队列 */
.vidioc_reqbufs = vidioc_reqbufs,
.vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,
// 启动/停止
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,

分析数据的获取过程:

  1. 请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
  2. 查询映射缓冲区: ioctl(4, VIDIOC_QUERYBUF // 查询所分配的缓冲区
  3. 把缓冲区放入队列: ioctl(4, VIDIOC_QBUF // 把缓冲区放入队列
  4. 启动摄像头 ioctl(4, VIDIOC_STREAMON
  5. 用select查询是否有数据
  6. 有数据后从队列里取出缓冲区 ioctl(4, VIDIOC_DQBUF
  7. 应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据 就去读对应的地址(该地址来自前面的mmap)

对于架构都是一样的,有关于VB2_buffer 的主要是数据的获取过程, 根据韦东山老师的视频中分析步骤分析如下:
1,首先是创建并初始化一个vb2_queue结构体 ,
static struct vb2_queue Myvivi_vb2_queue;

     struct vb2_queue  *q = &Myvivi_vb2_queue;
     q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 类型
     q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;  // 该队列支持的模式
     q->drv_priv = dev;  // 自定义模式
     q->buf_struct_size = sizeof(struct vivi_buffer);  // 将vb2_buffer结构体封装到我们自己的buffer中,此为我们自己的buffer的size
     q->ops = &vivi_video_qops;   
     q->mem_ops = &vb2_vmalloc_memops;     // 
    vb2_queue_init(q);
其中vivi_video_qops 是 队列的操作函数,以后的REQBUFS 等请求会调用到此中的函数,其结构如下

static struct vb2_ops vivi_video_qops = {
.queue_setup = queue_setup, // 必须有,vb2_queue_init中会判断
.buf_init = buffer_init,
.buf_prepare = buffer_prepare,
.buf_finish = buffer_finish,
.buf_cleanup = buffer_cleanup,
.buf_queue = buffer_queue, // 必须有,vb2_queue_init中会判断
.start_streaming = start_streaming,
.stop_streaming = stop_streaming,
.wait_prepare = vivi_unlock,
.wait_finish = vivi_lock,
};
其中vb2_vmalloc_memops用于有关此队列中mem分配问题,其中的函数一般不需要我们自己写,使用默认
const struct vb2_mem_ops vb2_vmalloc_memops = {
.alloc = vb2_vmalloc_alloc,
.put = vb2_vmalloc_put,
.vaddr = vb2_vmalloc_vaddr,
.mmap = vb2_vmalloc_mmap,
.num_users = vb2_vmalloc_num_users,
};

2,初始化完成后,调用 VIDIOC_REQBUFS 请求系统分配缓冲区,该函数的调用过程如下
vb2_reqbufs =
q->ops->queue_setup此函数允许我们的驱动函数自定义分配空间的大小
__vb2_queue_alloc 分配vivi_buffer结构体的空间(缓存区头部信息), 如果使用的是V4L2_MEMORY_MMAP类型则 调用
>__vb2_buf_mem_alloc ==> q->mem_ops->alloc 即 vb2_vmalloc_alloc 分配空间,将分配的空间指向vb2_buffer->planes[0].mem_priv ,该指针保存着分配到的空间,该指针指向vb2_vmalloc_buf结构体
韦东山老师视频中讲的video_buf中,讲到在这不真正的分配空间,但是在vb2中此时却已经分配了空间,这是我的理解

3,查询映射缓冲区 VIDIOC_QUERYBUF , 返回实际上分配到的buffer,
查询分配好的缓存区,返回v4l2_buffer结构,设置vb->state

4,使用mmap
vb2_mmap ==》q->mem_ops->mmap 即 vb2_vmalloc_mmap 用于映射,将上面分配好的vb2_buffer->planes[0].mem_priv指向的空间重映射到mmap参数中的用户空间

5,把缓冲区放入队列: VIDIOC_QBUF
vb2_qbuf 将 list_add_tail(&vb->queued_entry, &q->queued_list); 将vb2_buffer 放入队列q的queued_list中
设置vb->state = VB2_BUF_STATE_PREPARED;

6, 启动摄像头 VIDIOC_STREAMON
vb2_streamon
q->streaming = 1;
// 如果 q->queued_list 中部位空,即有qbuf没有被处理 调用__enqueue_in_driver ()

7, 用select查询是否有数据 会调用poll函数
vb2_poll 等待 q->done_list 中有数据,

8, 怎样往 q->done_list 中添加数据呢 ?
每次调用qbuf 和 vidioc_streamon 时候都会查询,如果这两个条件都成立,则调用q->ops->buf_queue 将 核心中的vb2_buffer调如我们写的驱动中,放入一个列表, 在vivi中 周期性的调用函数向这个列表中的vb缓冲区中添加数据 即 向vb2_buffer->planes中添加数据 ,然后后调用
vb2_buffer_done(&vb, VB2_BUF_STATE_DONE); ==》 list_add_tail(&vb->done_entry, &q->done_list); 将vivi驱动 中的vb2 放入 q->done_list中 ,然后设置vb->state = VB2_BUF_STATE_DONE;
最后wake_up(&q->done_wq); 唤醒poll中休眠的进程。

9,调用VIDIOC_DQBUF, 从队列里取出缓冲区
vb2_dqbuf ==> __vb2_get_done_vb 将q->done_list 中的vb2_buffer中提出来,然后 将vb2_buffer中的v4l2_buffer信息返回,并将其从q->done_list 中删除

10,应用程序将数据取出来(mmap的空间)

总结:
结构体如下
struct vb2_buffer {
struct v4l2_buffer v4l2_buf; // 里面有该vb2中数据的信息
struct v4l2_plane v4l2_planes[VIDEO_MAX_PLANES];

struct vb2_queue *vb2_queue;

unsigned int num_planes;

/* Private: internal use only */
enum vb2_buffer_state state;

struct list_head queued_entry;
struct list_head done_entry;

struct vb2_plane planes[VIDEO_MAX_PLANES]; // 存放实际数据的结构
};
struct vb2_queue {
enum v4l2_buf_type type;
unsigned int io_modes;
unsigned int io_flags;

const struct vb2_ops *ops;
const struct vb2_mem_ops *mem_ops;
void *drv_priv;
unsigned int buf_struct_size;

/* private: internal use only */
enum v4l2_memory memory;
struct vb2_buffer *bufs[VIDEO_MAX_FRAME];
unsigned int num_buffers;

struct list_head queued_list;

atomic_t queued_count;
struct list_head done_list;
spinlock_t done_lock;
wait_queue_head_t done_wq;

void *alloc_ctx[VIDEO_MAX_PLANES];
unsigned int plane_sizes[VIDEO_MAX_PLANES];

unsigned int streaming:1;

struct vb2_fileio_data fileio;
};
在vb2中细化了锁,并且将核心部分封装,使我们更容易使用。
与vb中有冲突的地方如下
1,结构中红色部分都为自己的数据,一般对于我们的驱动程序来说不要使用,因此使用vb2_buffer时候不能(也不需要)直接指定vb->state 的内容。 比如说通知数据完成只需要调用 vb2_buffer_done(&vb2, VB2_BUF_STATE_DONE); 即可,
2,对于vb2_buffer,没用供我们使用的list,因此如果要将vb可以放入list head,需要我们自己添加list ,例如
struct vivi_buffer {
/
common v4l buffer stuff – must be first */
struct vb2_buffer vb;
struct list_head list;
};
使用vb2,总结如下
1,调用vb2_queue_init 初始化队列 q 。
2,调用reqbuf 时候会根据请求(v4l2_requestbuffers)分配vb2结构,并且加入到q->buf中
3,调用querybuf时候,根据信息(v4l2_buffer)返回q->buf中对应的vb2_buffer的信息( v4l2_buffer)
4,mmap上面 信息对应的 vb空间到用户空间
5,调用qbuf 时,将对应的vb2_buffer ( vivi_bufer->list )添加到 q-> queued_list 队列中
6,使用select 调用poll 休眠等待 q->done_list 有数据
7, 调用qbuf 和 vidioc_streamon 时候都会查询,如果这两个条件都成立,则调用q->ops->buf_queue 将 核心中的vb2_buffer调如我们写的驱动中,放入一个列表,然后等待(上面的poll过程休眠)我们的驱动程序将数据放入该vb2_buffer
8, 数据存放完成后 调用vb2_buffer_done函数,即将上面有数据的vb2_buffer放入q->done_list中,然后唤醒上面poll休眠的进程
9, poll唤醒后会调用dqbuf将q->done_list 中的vb2_buffer提出来后,将此vb2的信息(v4l2_buffer)返回
10, 应用程序得到buffer信息后,就去对应的mmap后的用户空间中读数据。
————————————————
版权声明:本文为优快云博主「raceant」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/hnllei/article/details/45564125

reference

raceant vb2_buffer结构探究

### v4l2_buffervb2_buffer 的关系与区别 #### 关系 `v4l2_buffer` 是 V4L2(Video for Linux 2)框架中用于描述单个视频缓冲区的数据结构,而 `vb2_buffer` 则是 videobuf2 库中的核心数据结构之一。两者都服务于管理视频流的缓冲区操作,但在层次和功能上有显著差异。 在实际实现中,`v4l2_buffer` 提供的是用户空间接口,允许应用程序通过 ioctl 调用来控制缓冲区的行为。然而,在驱动程序内部,这些操作最终会映射到 `vb2_buffer` 上[^1]。因此可以认为,`v4l2_buffer` 是对外暴露给用户的抽象层,而 `vb2_buffer` 是底层具体实现的一部分。 #### 主要区别 | 特性 | `v4l2_buffer` | `vb2_buffer` | |---------------------|--------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| | **定义位置** | 定义于 `<linux/videodev2.h>` 中,作为用户空间 API 的一部分 | 定义于 videobuf2 内核模块中,主要用于内核空间的操作 | | **目标用途** | 面向用户空间的应用程序开发者,提供对缓冲区的基本访问和控制 | 面向设备驱动开发人员,负责缓冲区的具体管理和生命周期处理 | | **内存分配机制** | 不直接参与内存分配过程,而是依赖底层实现 | 支持多种内存分配方式,如 DMA contiguous (`vb2_dma_contig_memops`)、DMA scatter-gather (`vb2_dma_sg_memops`) 和 vmalloc (`vb2_vmalloc_memops`) [^2] | | **队列管理** | 用户空间仅能感知单一缓冲区的状态 | 结合 `vb2_queue` 使用,能够高效管理整个缓冲区队列 | | **复杂度** | 较低,主要是简单的状态查询和设置 | 较高,涉及复杂的缓冲区生命周期管理以及硬件交互 | #### 实现细节对比 - 在用户调用 `VIDIOC_QBUF` 或 `VIDIOC_DQBUF` 等 ioctls 时,V4L2 层面的工作实际上会被传递到底层的 videobuf2 模块来执行具体的缓冲区排队或释放逻辑[^1]。 - 当涉及到不同类型的内存分配策略时,`vb2_buffer` 可以灵活切换不同的 `struct vb2_mem_ops` 接口,从而适应各种场景需求[^2]。 ```c // 示例:初始化 vb2_queue 并关联 vb2_buffer static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers, unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[]) { q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; // 设置支持的 I/O 方式 q->drv_priv = ctx; // 私有上下文指针 q->buf_struct_size = sizeof(struct my_vb2_buffer); // 自定义扩展字段大小 q->ops = &my_qops; // 注册队列操作函数集 q->mem_ops = &vb2_dma_contig_memops; // 使用连续物理地址分配器 q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; return 0; } ``` 以上代码片段展示了如何配置一个基于 `vb2_queue` 的队列,并指定其使用的内存分配器为 `vb2_dma_contig_memops`。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值