Linux 深入分析V4l2框架4

Linux 深入分析V4l2框架4(基于Linux6.6)---videobuf2介绍

一、概述

Videobuf2 是 Linux 内核中的一个框架,旨在管理视频缓冲区的分配和传输,特别是在 V4L2 驱动程序中。它作为 V4L2 驱动模型的底层组成部分,负责缓存和缓冲区管理,确保视频数据的有效传输和处理。

在 V4L2 驱动中,视频数据通常是通过缓冲区(buffer)进行管理的,尤其是视频捕获和视频回放过程中,视频数据需要被存储、传输和处理。videobuf2 提供了一个通用的缓冲区管理机制,允许驱动程序高效地分配和处理这些缓冲区。

1.1、videobuf2 的关键特性

  • 缓冲区分配: videobuf2 负责为视频数据流分配内存缓冲区。它支持不同类型的内存(如用户空间内存、DMA 内存等)来存储视频帧数据。

  • 缓冲区映射: 它允许用户空间和内核空间之间共享缓冲区数据。缓冲区可以映射到用户空间,以便应用程序直接访问视频数据。

  • 内存管理: videobuf2 提供了对缓冲区的生命周期管理。缓冲区会在合适的时候被分配、映射、填充数据并最终释放。

  • 异步 I/O: 支持异步 I/O 操作,缓冲区可以在没有阻塞的情况下进行读取或写入,提高性能。

  • 支持多种类型的缓冲区: 包括请求缓冲区(用于捕获数据)、输出缓冲区(用于播放或回放数据)以及队列(管理多个缓冲区)。

1.2、videobuf2 的工作流程

  1. 缓冲区分配: 驱动程序通过 videobuf2 API 分配一个或多个缓冲区。分配的缓冲区可以是物理内存或用户空间内存。

  2. 缓冲区映射: 如果缓冲区需要与用户空间共享,videobuf2 会将缓冲区映射到用户空间,允许用户程序访问数据。

  3. 缓冲区填充和传输: 驱动程序将视频帧数据填充到缓冲区中,用户程序可以从缓冲区中读取数据,或者将数据写入到缓冲区中,驱动程序再将其传输到硬件设备。

  4. 缓冲区释放: 一旦缓冲区不再需要,videobuf2 会负责释放它们的内存。

1.3、videobuf2 的作用

videobuf2 用于连接 V4L2 驱动层与用户空间层,提供数据交流的通道,它可以分配并管理视频帧数据。videobuf 层实现了很多 ioctl 函数,包括 buffer 分配、入队、出队和数据流控制。

  • 为什么要有videobuf2?
  1. 扩展性不足,尤其是嵌入式多媒体设备
    a. 难以添加新的内存处理函数以及自定义的内存分配函数,比如现在 videobuf2 里面已有的三种-「dma-contig、sg-dma、vmalloc」;
    b. 对 cache 以及 IOMMU 的支持非常弱;
    c. 不够灵活。只有一个通用的函数来处理 cache、sg-list 创建等等事情,这些应该分为不同的抽象方法类进行差异化管理;
  2. 无用的成员,代码,变量(这个我就没有深入了解了)
  • videobuf2要解决的问题
    a. 更正 V4L2 的 API,修复 videobuf1 的问题与缺点「上面提到的问题」
    b. 把队列管理与内存管理完全分离,采用分层的设计
    c. 更多专门的驱动回调,不同类型驱动特定的,同时又满足可定制化的拆分,不必要的不使用,必要的按需使用
    d. 支持新的V4L2 API扩展,比如多 planar 视频 buffer,比如 YUV 就是多 planer 的格式,它们的 Y、U、V 数据可能不在同一个连续的地址段内

二、数据类型

2.1、数据类型简介

不是所有的视频设备都使用同一种类型的 buffer,事实上,buffer 类型可以概括为三种:

  • buffer 在虚拟地址以及物理地址上面都是分散的。几乎所有的用户空间 buffer 都是这种类型,在内核空间中,这种类型的 buffer 并不总是能够满足需要,因为这要求硬件可以进行分散的 DMA 操作。
  • 物理地址分散,虚拟地址连续。也就是通过 vmalloc 分配的 buffer,换句话说很难使用 DMA 来操作这些buffer。
  • 物理地址连续。在分段式系统上面分配这种类型的 buffer 是不可靠的(分段式-也即页式系统上面有可能在长时间运行之后内存出现大量碎片,从而导致连续的内存空间很难获得,所以说不可靠),但是简单 DMA 控制器只能够适用于这种类型的 buffer。也有分段式的 DMA 操作,不过那种需要用到 IOMMU 来实现。

这三种类型的 buffer 对应的相关操作函数分别对应于 videobuf2-dma-sg.c,videobuf2-vmalloc.c,videobuf2-dma-contig.c

2.2、结构体、回调函数、初始化

根据buffer的不同,,需要用到的内核头文件也不同:

<media/videobuf-dma-sg.h>		/* 物理地址分散 */
<media/videobuf-vmalloc.h>		/* vmalloc() 分配的buffer */
<media/videobuf-dma-contig.h>	/* 物理地址连续 */

需要实现以下几个回调函数来管理buffer:

include/media/videobuf2-core.h 

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);
};
  • queue_setup
    该回调函数在两个地方会被调用:
  1. 在真正的 memory 分配之前,由 VIDIOC_REQBUFS 和 VIDIOC_CREATE_BUFS 两个 ioctl 调用,一般情况下我们会使用 VIDIOC_REQBUFS 这个 ioctl,后者不常使用。
  2. 如果不能够分配 num_buffers 指定的原始数量的的 buffer,那么该函数就会再次被调用来尽最大努力去分配 buffer 并返回实际被分配的 buffer 数量,返回值依然存到 num_buffers,返回值这部分应该有驱动编写者来完成。
  3. num_planes 里面返回申请的 buffer 的 planes 的数量,比如对于 YUV420SP 格式的数据来说,它的 planes 就是 2「具体为什么是 2 就需要自己去网上找找 YUV 格式的解析啦,本文重点不在这里」。
  4. 每个 plane 的大小被存放在该回调函数的 sizes 数组成员里面,可选的,alloc_ctxs 数组可以存放每一个 plane 的特定数据,不过这种应用场景暂时没有遇到过,alloc_ctxs 没用过。
  5. 需要返回的值:驱动需要返回实际分配的 buffer 数量,存放在 num_buffers 中。返回 plane 数量,存放在 num_planes 中。每个 plane 的大小按照顺序存放在 sizes[] 中,每个 plane 的分配者特定数据存放在 alloc_ctxs[] 「之前的版本是在 alloc_devs[]」中,这一步是可选的,因为并不总是用得到。
  • wait_prepare:释放所有在 ioctl 操作函数执行时被持有的锁;该回调在 ioctl 需要等待一个新的 buffer 到达的时候被调用;需要在阻塞访问的时候避免死锁。
  • wait_finish:请求所有的在上面 wait_prepare 回调锁释放的锁;需要在睡眠等待新的 buffer 到来之后继续运行。

上面两个回调比较通用的做法是:将 wait_prepare 赋值为 vb2_ops_wait_prepare,将 wait_finish 赋值为 vb2_ops_wait_finish,这两个是 videobuf2 为我们实现的两个回调,直接使用即可。上面两个函数在 vb2_internal_dqbuf 内部会被使用到,通常情况下会在用户 DQBUF 的时候使用,内核里面会判断是否是阻塞的情况,如果是阻塞调用并且没有准备好的数据,内核就会调用 wait_prepare 释放锁并进行休眠等待,直到有数据到达被唤醒之后才调用 wait_finish 重新持有锁。

  • buf_init:在 MMAP 方式下,分配完 buffer 或者 USERPTR 情况下请求完 buffer 之后被调用一次,(一个 buffer 调用一次)。如果该 ioctl 返回失败,将会导致 queue_setup 执行失败。该函数的主要目的是让我们在 buffer 申请完毕之后对 buffer 做一些初始化工作,但是实际上好像并不是经常用到。在该函数里面可以获取到 buffer 的 type,memory 类型,index,planesnum 等

  • buf_prepare:每次 buffer 重新入队「就是在用户调用 QBUF 的时候」以及 VIDIOC_PREPARE_BUF 操作的时候被调用,驱动可以做一些硬件操作「通常数据都是由硬件产生的」之前的初始化工作。如果该回调返回失败,那么 buffer 将不会执行 queue 动作。一般在这里需要设置 plane 的 payload,也就是每个 plane 的使用内存长度,单位为 Byte,可以使用 vb2_set_plane_payload 来帮助完成「在 omap3isp 例程当中该回调函数里面获取 buffer 的 dma 地址,然后赋值给 buffer->dma 成员」。

  • buf_finish:每次 buffer 被取出的时候被调用,并且是在 buffer 到达用户空间之前,所以驱动可以访问/修改 buffer 的内容。buffer 的状态可以是:
    VB2_BUF_STATE_DONE 或者 VB2_BUF_STATE_ERROR:这两种状态只在 streaming 的时候才会出现,前者是在驱动把 buffer 传递给 videobuf2 的时候被置位,此时还没有被传递给用户空间,后者与前者类似,也会最终被传递给用户,但是表明对 buffer 的操作最终以失败告终
    VB2_BUF_STATE_PARPARED:videobuf2 准备好了 buffer,并且驱动持有 buffer。注意,驱动与 videobuf2 看作两个使用者
    VB2_BUF_STATE_DEQUEUED:默认状态,表明 buffer 处于被用户使用的状态

  • buf_cleanup:buffer 被释放之前调用一次,每个 buffer 仅有一次,驱动可以在这里面做一些额外的操作。

  • start_streaming:进入 streaming 状态时被调用一次,一般情况下,驱动在该回调函数执行之前,通过 buf_queue 回调来接收 buffer,这里驱动需要把 buffer 放到驱动自己维护的一个 buffer 队列里面。count 参数存放已经被 queue 的 buffer 数量,驱动可以由此获取它。如果发生硬件错误,驱动可以通过该回调返回一个错误,此时所有的buffer都会被归还给 videobuf2(调用 vb2_buffer_done(VB2_BUF_STATE_QUEUED))。如果需要设置开始 start_streaming 需要的 buffer 最小数量,比如驱动只有在满足 buffer 数量大于等于 3 的时候才能够开启数据流,那么就可以在该函数被调用之前设置 vb2_queue->min_buffers_needed 成员为自己想要的值,此时该回调函数会在满足最小 buffer 数量之后才被调用。

  • stop_streaming:在 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:用来传递 vb(vb2_buffer 结构体) 给驱动,驱动可以在这里开启硬件操作(DMA 等等)。驱动填充 buffer 完毕之后需要调用 vb2_buffer_done 归还 buffer,该函数总是在 VIDIOC_STREAMON 操作之后调用。但是也有可能在 VIDIOC_STREAMON 之前被调用,比如用户空间中 querybuf 之后的 queue 操作。videobuf2 允许自定义一个 buffer 结构体而不是直接使用 vb2_buffer,但是自定义的结构体的第一个成员必须是 vb2_v4l2_buffer 结构体,该结构体的大小需要在 vb2_queue 的 buf_struct_size 成员中指定。

每一个申请到的 buffer 都会被传递给 buf_prepare,该回调函数需要设置 buffer 的 size、width、height 与 field 成员,如果 buffer 的 state 成员为 VIDEOBUF_NEEDS_INIT,驱动需要传递 buffer 给以下函数:

/* 该函数调用通常会为buffer分配空间,最后buf_prepare()需要设置buffer的state为VIDEOBUF_PREPARED。 */
int videobuf_iolock(struct videobuf_queue* q,
    struct videobuf_buffer *vb, struct v4l2_framebuffer *fbuf);

当一个 buffer 需要进行队列操作时,它会被传递给 buf_queue,该回调函数需要将 buffer 放到驱动自行维护的一个 buffer 链表中,并且设置 buffer 状态为 VIDEOBUF_QUEUED(这个状态位不需要驱动自己去设置,交给 videobuf2 就好了)。该函数会保持 spinlock(videobuf_queue 里面的 irqlock,新的 videobuf 不会保持锁,需要自己申请并在 buf_queue 里面使用),使用 list_add_tail 来对 buffer 进行入队操作。

三、vb2_queue

include/media/videobuf2-core.h

struct vb2_queue {
	unsigned int			type;
	unsigned int			io_modes;
	struct device			*dev;
	unsigned long			dma_attrs;
	unsigned int			bidirectional:1;
	unsigned int			fileio_read_once:1;
	unsigned int			fileio_write_immediately:1;
	unsigned int			allow_zero_bytesused:1;
	unsigned int		   quirk_poll_must_check_waiting_for_buffers:1;
	unsigned int			supports_requests:1;
	unsigned int			requires_requests:1;
	unsigned int			uses_qbuf:1;
	unsigned int			uses_requests:1;
	unsigned int			allow_cache_hints:1;
	unsigned int			non_coherent_mem:1;

	struct mutex			*lock;
	void				*owner;

	const struct vb2_ops		*ops;
	const struct vb2_mem_ops	*mem_ops;
	const struct vb2_buf_ops	*buf_ops;

	void				*drv_priv;
	u32				subsystem_flags;
	unsigned int			buf_struct_size;
	u32				timestamp_flags;
	gfp_t				gfp_flags;
	u32				min_buffers_needed;

	struct device			*alloc_devs[VB2_MAX_PLANES];

	/* private: internal use only */
	struct mutex			mmap_lock;
	unsigned int			memory;
	enum dma_data_direction		dma_dir;
	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;
	wait_queue_head_t		done_wq;

	unsigned int			streaming:1;
	unsigned int			start_streaming_called:1;
	unsigned int			error:1;
	unsigned int			waiting_for_buffers:1;
	unsigned int			waiting_in_dqbuf:1;
	unsigned int			is_multiplanar:1;
	unsigned int			is_output:1;
	unsigned int			copy_timestamp:1;
	unsigned int			last_buffer_dequeued:1;

	struct vb2_fileio_data		*fileio;
	struct vb2_threadio_data	*threadio;

	char				name[32];

#ifdef CONFIG_VIDEO_ADV_DEBUG
	/*
	 * Counters for how often these queue-related ops are
	 * called. Used to check for unbalanced ops.
	 */
	u32				cnt_queue_setup;
	u32				cnt_wait_prepare;
	u32				cnt_wait_finish;
	u32				cnt_start_streaming;
	u32				cnt_stop_streaming;
#endif
};

该结构体可以当做是 videobuf2 的一个整体的抽象,要想使用 videobuf2 这个模块,就去初始化一个 vb2_queue 结构体吧,它会帮助我们建立起一个基于 videobuf2 的数据流管理模块的。

  • vb2_queue 的初始化
    vb2_queue 需要设置以下 field:
  1. typev4l2_buf_type 枚举类型,表明设备的 buffer 类型
  2. io_modesvb2_io_modes 枚举类型,表明驱动支持哪一种 streaming,实际应用中 VB2_MMAP 类型的居多
  3. drv_priv:一般指向驱动特定的结构体,也就是驱动自定义维护的结构体
  4. ops:指向 vb2_ops,也就是上面描述的的那一大坨
  5. mem_ops:根据 buffer 类型「物理连续、vmalloc、物理分散的 sg-dma」选择 vb2_dma_contig_memopsvb2_dma_sg_memops,vb2_vmalloc_memops 三种之一,与我来说,最常用的就是第一个啦,当然也可以自己动手丰衣足食,不过常规使用那三个就能够满足了
  6. timestamp_flagsV4L2_BUF_FLAG_TIMESTAMP_MASK 或者 V4L2_BUF_FLAG_TSTAMP_SRC_MASK 类型的 flag 之一,两种 flag 可以是或的关系同时存在,表明时间戳的类型,一般使用 V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC 即可,表明是递增类型的,它是在内核的 monotonic 时钟时间轴上面生成的时间戳
  7. lock:可以与 video_device 共用一个 lock,更推荐使用独立的 lock,独立的 lock 一定程度上可以减少锁竞争
  8. buf_struct_size:自定义的 buffer 结构体大小,自定义的 buffer 结构体必须将 vb2_v4l2_buffer 成员放在第一个。可以在 vb2 的 queue 回调中通过 container_of 来获取自定义的buffer结构体数据。

上面操作之后要记得使用 vb2_queue_init 对 vb2_queue 进一步初始化,里面主要是一大堆的 WARN_ON 来对设置进行检查,获取 v4l2_buf_ops 这个 vb2_buf_ops 类型的 buffer 操作函数,并且初始化一些锁与一个等待队列,该等待队列主要是用来等待 buffer 变为可 dequeue 状态。

四、用户空间的操作

 用用户空间 stream 操作 ioctl 调用流程:

通常用户空间的操作顺序如下:

  • VIDIOC_S_PARAM::参数为 struct v4l2_streamparm,主要设置帧率,type 与 capturemode/outputmode 等。内核的 ioctl 需要保存 capturemode(mode需自定义)帧率等数据,可以保存到自定义的结构体里面。也可以在内核里面通过 v4l2_subdev_call 来调用
  • VIDIOC_S_FMT::参数为 struct v4l2_format,主要设置长宽,数据格式等。内核的 ioctl 需要保存长宽,像素格式,所在的 field 等。必要时需要调用相关的子设备的结构体进行子设备的格式设置。
  • VIDIOC_REQBUFS::参数为 struct v4l2_requestbuffers,设置 count,type 与 memory 请求数据,在 queue_setup 回调函数里面需要设置一些东西。参见上面关于 queue_setup 的描述。
  • VIDIOC_QUERYBUF::参数为 struct v4l2_buffer,需设置 index,type,memory,length(plane length) 等参数
  • VIDIOC_QBUF,该操作与上一个不断循环,直到获取所有的 buf 并将 buf 入队到内核驱动的的 buf 管理列表中
  • VIDIOC_STREAMON::参数为 enum v4l2_buf_type,开启指定类型的 stream。
  • select 等待 buf 可读。在内核驱动里面需要获取 plane 的地址,vb2_plane_vaddr,在数据填充完毕之后需要调用 vb2_buffer_done 来标注该 buffer 已经成功填充完毕,以便 v4l2 把数据放入 done_list,以待用户空间进行读取。
  • VIDIOC_DQBUF,该操作与上一个操作不断循环,直到停止数据采集
  • VIDIOC_STREAMOFF,结束数据采集工作。

下图展示了用户空间到内核驱动的 ioctl 调用顺序与调用过程:

五、 /dev/video 节点与 videobuf2 联系起来

首先 video_device 需要有自己的 open,release,unlocked_ioctl 等等,一个常见的初始化操作如下。

drivers/media/platform/ti/omap3isp/ispvideo.c

static struct v4l2_file_operations isp_video_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = video_ioctl2,
	.open = isp_video_open,
	.release = isp_video_release,
	.poll = isp_video_poll,
	.mmap = isp_video_mmap,
};

其中那几个函数几乎都是与 videobuf2 是挂钩的,如果去看下内核里面的代码就可以了然了,其中实用频率最高的就是 .unlocked_ioctl = video_ioctl2, 这个回调函数了,这个回调函数是用户空间通过 /dev/videoX 节点通往 videobuf2 的不二路径,所以干脆直接就把这个成员赋值为 video_ioctl2 了,这个是 videobuf2 为我们提供的操作函数,那就拿起来并使用它吧。从用户空间的角度看,只有一个 ioctl 的入口,来到内核之后,里面还有一大坨分门别类的 ioctl 回调函数,截取如下:

drivers/media/platform/ti/omap3isp/ispvideo.c 

static const struct v4l2_ioctl_ops isp_video_ioctl_ops = {
	.vidioc_querycap		= isp_video_querycap,
	.vidioc_g_fmt_vid_cap		= isp_video_get_format,
	.vidioc_s_fmt_vid_cap		= isp_video_set_format,
	.vidioc_try_fmt_vid_cap		= isp_video_try_format,
	.vidioc_g_fmt_vid_out		= isp_video_get_format,
	.vidioc_s_fmt_vid_out		= isp_video_set_format,
	.vidioc_try_fmt_vid_out		= isp_video_try_format,
	.vidioc_cropcap			= isp_video_cropcap,
	.vidioc_g_crop			= isp_video_get_crop,
	.vidioc_s_crop			= isp_video_set_crop,
	.vidioc_g_parm			= isp_video_get_param,
	.vidioc_s_parm			= isp_video_set_param,
	.vidioc_reqbufs			= isp_video_reqbufs,
	.vidioc_querybuf		= isp_video_querybuf,
	.vidioc_qbuf			= isp_video_qbuf,
	.vidioc_dqbuf			= isp_video_dqbuf,
	.vidioc_streamon		= isp_video_streamon,
	.vidioc_streamoff		= isp_video_streamoff,
	.vidioc_enum_input		= isp_video_enum_input,
	.vidioc_g_input			= isp_video_g_input,
	.vidioc_s_input			= isp_video_s_input,
};

有了上面两个,只需要在初始化 video_device 的时候做以下的操作,此时便可以把整个串联起来了:

video->video.fops = &isp_video_fops;               /*  重点 */
video->video.vfl_type = VFL_TYPE_GRABBER;
video->video.release = video_device_release_empty; /*  重点 */
video->video.ioctl_ops = &isp_video_ioctl_ops;     /*  重点 */
video->pipe.stream_state = ISP_PIPELINE_STREAM_STOPPED;

这里就把 /dev/videoX,video_device,vb2_queue,videobuf2 这几个全部贯通起来了。

流程:首先用户 open 一个 /dev/videoX,获取其句柄,同时触发内核的 open 函数内部对 videobuf2 的 vb2_queue 进行初始化;然后进行一系列的 ioctl 操作,入口是 isp_video_fops->unlocked_ioctl 成员,再往后会细分为 isp_video_ioctl_ops 里面的一个个回调,这一个个回调与 vb2 众多的 ops 深度结合起来共同完成了数据流的管理工作。

六、应用举例

1. 视频捕捉应用(摄像头捕获)

在摄像头捕获应用中,videobuf2 主要用于管理从摄像头获取的视频帧的缓冲区。摄像头设备通过 V4L2 接口将图像数据传输到内存缓冲区,videobuf2 负责缓冲区的分配、填充、释放等操作。

应用流程:

  1. 初始化 V4L2 设备: 打开 V4L2 设备并设置设备参数(分辨率、像素格式等)。
  2. 分配缓冲区: 使用 videobuf2 分配视频缓冲区,这些缓冲区将用于存储捕获的视频帧。
  3. 映射缓冲区: 将缓冲区映射到用户空间,以便应用程序能够访问捕获的视频数据。
  4. 捕获数据: 从摄像头设备获取视频数据并填充到缓冲区中。
  5. 处理数据: 用户空间应用程序可以读取缓冲区中的视频数据进行处理。
  6. 释放缓冲区: 捕获结束后,释放缓冲区资源。

代码示例:

#include <linux/videodev2.h>
#include <linux/videobuf2-v4l2.h>
#include <linux/videobuf2-core.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>

int main() {
    int fd = open("/dev/video0", O_RDWR);
    if (fd < 0) {
        perror("Failed to open video device");
        return -1;
    }

    // 设置视频格式
    struct v4l2_format fmt;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 640;
    fmt.fmt.pix.height = 480;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
        perror("Failed to set format");
        close(fd);
        return -1;
    }

    // 分配缓冲区
    struct v4l2_requestbuffers req;
    req.count = 4;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
        perror("Failed to request buffers");
        close(fd);
        return -1;
    }

    // 映射缓冲区
    struct v4l2_buffer buf;
    for (int i = 0; i < req.count; i++) {
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
            perror("Failed to query buffer");
            close(fd);
            return -1;
        }

        void *buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        if (buffer == MAP_FAILED) {
            perror("Failed to map buffer");
            close(fd);
            return -1;
        }
        // 在这里可以开始捕获数据并处理
    }

    // 捕获数据
    for (int i = 0; i < req.count; i++) {
        buf.index = i;
        if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
            perror("Failed to queue buffer");
            close(fd);
            return -1;
        }
    }

    // 开始捕获
    if (ioctl(fd, VIDIOC_STREAMON, &buf.type) < 0) {
        perror("Failed to start stream");
        close(fd);
        return -1;
    }

    // 释放缓冲区
    for (int i = 0; i < req.count; i++) {
        buf.index = i;
        if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
            perror("Failed to dequeue buffer");
            close(fd);
            return -1;
        }
        // 处理捕获的数据
    }

    // 关闭设备
    close(fd);
    return 0;
}

关键点:

  • VIDIOC_S_FMT:设置视频捕捉的格式。
  • VIDIOC_REQBUFS:请求缓冲区。
  • VIDIOC_QUERYBUF:查询缓冲区信息。
  • VIDIOC_QBUF:将缓冲区添加到设备队列中。
  • VIDIOC_STREAMON:启动视频流捕获。

2. 视频回放应用(播放视频流)

在视频回放应用中,videobuf2 负责管理视频输出缓冲区,将处理过的视频数据传输到显示设备或其他输出设备。

应用流程:

  1. 初始化设备: 打开 V4L2 输出设备。
  2. 分配缓冲区: 为输出视频分配缓冲区。
  3. 填充缓冲区: 将视频帧数据填充到输出缓冲区。
  4. 传输数据: 将缓冲区中的数据传输到设备进行播放。
  5. 释放缓冲区: 释放不再需要的缓冲区。

3. 视频处理应用(编解码)

在视频编解码应用中,videobuf2 用于管理编解码过程中的缓冲区,确保视频数据的高效传输。例如,在 H.264 编解码的过程中,输入和输出的视频帧数据需要通过缓冲区进行高效地管理。

应用流程:

  1. 分配缓冲区: 使用 videobuf2 分配输入和输出缓冲区。
  2. 填充输入缓冲区: 将原始视频数据填充到输入缓冲区中。
  3. 启动编解码操作: 触发编解码操作,处理视频帧。
  4. 获取输出数据: 编解码后的数据存储在输出缓冲区中。

4. 视频流转发(流媒体应用)

videobuf2 也可以用于视频流转发的应用,像 RTSP、RTMP 或 WebRTC 等视频流协议。它能够确保视频帧高效传输,避免内存溢出或丢帧现象。

应用流程:

  1. 分配缓冲区: 为每个视频帧分配缓冲区。
  2. 网络传输: 将缓冲区中的数据发送到网络目标。
  3. 接收和解码: 在目标设备接收视频流并进行解码和播放。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值