UVC 设备框架在 Linux 4.15 内核的演变

1. 概述

发现之前的uvc框架和现在的还是有一些差别的(比如从videobuf 过渡到videobuf2),写个blog记录一下,方便以后查询,我的内核版本:Linux 4.15

  • UVC(USB Video Class)设备框架是建立在V4L2(Video4Linux version 2)子系统之上的。UVC框架主要负责管理通过USB接口连接的视频捕获设备,如网络摄像头。

2. 流程分析

  • 打开设备文件 : 应用程序通过文件I/O打开设备时,内核创建一个uvc_fh 文件句柄实例,将其关联到特定的视频流
  • 缓冲区队列操作:uvc_fh通过它的视频流uvc_streaming 结构体间接操作vb2_queue ,以执行如缓冲区排队(qbuf),和缓冲区准备(reqbufs)等操作
  • 数据流传输: 视频数据通过 vb2_buffer 结构体在用户空间和UVC硬件之间传输

3. 主要的内核结构体

可以看出当前版本下的UVC驱动和V4L2之间的交互非常密切。V4L2是Linux内核中用于处理视频捕获和输出设备的一个标准API框架,而UVC驱动则是V4L2框架下用于支持符合USB视频类规范的摄像头和其他视频设备的一个具体实现

3.1 UVC驱动和V4L2之间主要的交互方式:

  1. 设备注册和初始化
    UVC驱动在系统启动或USB视频设备插入时,会被初始化并注册为V4L2设备。这一过程包括设置设备的V4L2能力、支持的格式、控制操作等,确保UVC设备能够通过V4L2接口与用户空间应用程序交互。
  2. 控制查询和设置
    UVC驱动实现了一系列V4L2控制类(control class)接口,允许用户空间应用程序查询和设置视频设备的参数,如亮度、对比度、饱和度等。这些控制操作通过UVC驱动转换为USB传输,与硬件设备交互。
  3. 缓冲区管理
    UVC驱动使用V4L2提供的videobuf2 API来管理视频帧的缓冲区。这包括缓冲区的分配、队列管理、数据传输等。Videobuf2作为V4L2的一部分,提供了一个高效的机制来处理视频数据的缓冲和流转。
  4. 数据流控制
    用户空间应用程序可以通过V4L2接口来启动和停止视频流。UVC驱动响应这些请求,通过USB接口与硬件设备进行交互,控制视频数据的捕获和传输。
  5. 事件处理
    UVC驱动能够处理来自硬件的事件,比如状态变化、错误报告等,并通过V4L2框架将这些事件上报给用户空间应用程序,使得应用程序能够对特定的硬件事件做出响应。
  6. 格式协商
    在视频捕获或输出过程中,UVC驱动和用户空间应用程序会通过V4L2接口进行格式协商,确定视频数据的格式、分辨率、帧率等参数。UVC驱动根据这些协商结果配置USB视频设备,以满足应用程序的需求。

4. UVC_Driver 驱动入口出口函数

c 复制代码
static int __init uvc_init(void)
{
...
	ret = usb_register(&uvc_driver.driver);
	return 0;
...
}

static void __exit uvc_cleanup(void)
{
	usb_deregister(&uvc_driver.driver);
}
module_init(uvc_init);
module_exit(uvc_cleanup);

接下里就是关于uvc_driver 结构体

c 复制代码
struct uvc_driver uvc_driver = {
	.driver = {
		.name		= "uvcvideo",
		.probe		= uvc_probe,
		.disconnect	= uvc_disconnect,
		.suspend	= uvc_suspend,
		.resume		= uvc_resume,
		.reset_resume	= uvc_reset_resume,
		.id_table	= uvc_ids,
		.supports_autosuspend = 1,
	},
};

4.1 uvc_probe

这里主要是分配 设置 设置结构体,以及一些其他的操作

4.1.1 分配 uvc_device

c 复制代码
if ((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL)

	INIT_LIST_HEAD(&dev->entities); 
	INIT_LIST_HEAD(&dev->chains);  
	INIT_LIST_HEAD(&dev->streams); 
  • entities: 指的也就是摄像头、输入终端(ITT)、输出终端(OTT)、处理单元(PU)和扩展单元(XU)等。每个实体代表设备的一个功能部分,可能是视频捕获的源头(例如摄像头),或是对视频数据进行处理的模块(例如编码器)。
  • chains: 视频链是由一系列实体(entities)连接而成的路径,从视频捕获的源头开始,经过一系列处理,最终到达输出。每个视频链代表了一种特定的视频流处理流程
  • streams :视频流可以理解为通过一个特定视频链(chains)实现的数据流,包含实际的视频帧数据

解析设备接口描述符 并将信息填充到 uvc_device *dev; 以便后面使用

c 复制代码
uvc_parse_control(dev)

4.1.2 分配 uvc_device注册 video 设备节点

  1. 注册uvc设备到V4L2框架下, 将V4L2设备结构体 v4l2_device 与内核结构体device 关联起来使其成为一个可以由用户空间访问和控制的设备
c 复制代码
if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
		goto error;
  1. 初始化uvc设备的控制项
c 复制代码
if (uvc_ctrl_init_device(dev) < 0)
		goto error;
/* 
uvc_ctrl_init_device 主要就是
1.. 遍历所有与设备管理的实体(比如处理单元PU,控制单元CU等),并且为每
个实体分配了一个 struct uvc_control *ctrl 的结构体
3. 对于每个支持的控制项,函数设置控制的索引,并调用
 uvc_ctrl_init_ctrl 来进一步初始化控制项。
*/
  1. 扫描设备实体(entities) 并注册成 视频链 (chains)
c 复制代码
if (uvc_scan_device(dev) < 0)
		goto error;
/* 1.遍历所有实体 从输出端OT 反向扫描
   2.为没用分配到的实体分配视频链结构体
   3.初始化视频链:
	4.设置默认标志:
*/
  1. 为识别到的视频链注册到video_device 设备节点
c 复制代码
uvc_register_chains(dev)	
	uvc_register_terms(dev, chain);
		uvc_register_video(dev, stream);//根据传输终端的 ID 查找视频流并将其注册为设备节点 dev/video x 

接下来分析一下uvc_register_video 这里面的函数

a. 首先对uvc_queue 进行初始化, 设置它的队列的类型,以及是否丢帧行为 (uvc_no_drop_param),根据内核结构体那张图,我们还需要将其初始化到vb2_queue 里面去

c 复制代码
	ret = uvc_queue_init(&stream->queue, stream->type, !uvc_no_drop_param);
	if (ret)
		return ret;
/*
ret = vb2_queue_init(&queue->queue);
	if (ret)
		return ret;
*/

可以看出在UVC驱动中,主要是使用videobuf2来管理视频流的数据传输,主要包括以下几个步骤:

  • 初始化队列:UVC驱动在初始化视频流时,会创建并初始化一个vb2_queue结构体实例。这个过程包括指定队列操作的回调函数和设置缓冲区的类型(捕获或输出)。

  • 缓冲区操作:UVC驱动会使用videobuf2提供的API来执行缓冲区的排队(queue)和出队(dequeue)操作

  • 数据处理:当缓冲区准备好数据后(例如,捕获了一帧视频数据),videobuf2框架会通知UVC驱动,驱动随后可以处理这些数据,比如将其传输给用户空间。

  • 与用户空间的交互:videobuf2还处理与用户空间应用程序的交互,包括应用程序对缓冲区的请求、查询和映射操作。

b .初始化视频流 :确保视频流在开始传输数据之前已经正确配置,包括分辨率、帧率等参数

c 复制代码
ret = uvc_video_init(stream);
	if (ret < 0) {
		uvc_printk(KERN_ERR, "Failed to initialize the device "
			"(%d).\n", ret);
		return ret;
	}

c. 注册视频设备

c 复制代码
	/*设置相关的参数*/
	vdev->v4l2_dev = &dev->vdev;
	vdev->fops = &uvc_fops;
	vdev->ioctl_ops = &uvc_ioctl_ops;
	vdev->release = uvc_release;
	vdev->prio = &stream->chain->prio;
	if (stream->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
		vdev->vfl_dir = VFL_DIR_TX;
	strlcpy(vdev->name, dev->name, sizeof vdev->name);

//设置视频设备的私有数据,确保在设备操作中可以访问到关联的 uvc_streaming 结构
	video_set_drvdata(vdev, stream);
	ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
	if (ret < 0) {
		return ret;
	}

c. 更新视频流和链的能力

c 复制代码
	if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
		stream->chain->caps |= V4L2_CAP_VIDEO_CAPTURE;
	else
		stream->chain->caps |= V4L2_CAP_VIDEO_OUTPUT;

	atomic_inc(&dev->nstreams); /*更新活跃视频流的计数*/
  1. 将数据指针保存在数据接口中
c 复制代码
usb_set_intfdata(intf, dev);
  1. 初始化中断 URB。
c 复制代码
if ((ret = uvc_status_init(dev)) < 0) {
		uvc_printk(KERN_INFO, "Unable to initialize the status "
			"endpoint (%d), status interrupt will not be "
			"supported.\n", ret);
	}

5. UVC驱动的调用过程详解

UVC驱动通过一系列文件操作和IO控制操作(ioctl)来管理视频捕获和控制流程

5.1 核心操作集合

uvc驱动的调用过程主要是涉及之前注册时候设置的

c 复制代码
    vdev->fops = &uvc_fops; //文件操作
	vdev->ioctl_ops = &uvc_ioctl_ops; //ioctl操作

文件操作(uvc_fops)

c 复制代码
const struct v4l2_file_operations uvc_fops = {
	.owner		= THIS_MODULE,
	.open		= uvc_v4l2_open,
	.release	= uvc_v4l2_release,
	.unlocked_ioctl	= video_ioctl2,
#ifdef CONFIG_COMPAT
	.compat_ioctl32	= uvc_v4l2_compat_ioctl32,
#endif
	.read		= uvc_v4l2_read,
	.mmap		= uvc_v4l2_mmap,
	.poll		= uvc_v4l2_poll,
#ifndef CONFIG_MMU
	.get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};

IOCTL操作(uvc_ioctl_ops)

c 复制代码
const struct v4l2_ioctl_ops uvc_ioctl_ops = {
	/*最基本的几个参数*/
	.vidioc_querycap = uvc_ioctl_querycap,
	.vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,

	.vidioc_reqbufs = uvc_ioctl_reqbufs,
	.vidioc_querybuf = uvc_ioctl_querybuf,
	.vidioc_qbuf = uvc_ioctl_qbuf,
	.vidioc_dqbuf = uvc_ioctl_dqbuf,

	
	

	.vidioc_streamon = uvc_ioctl_streamon,
	/* uvc_ioctl_streamon
	 *1.向USB摄像头设置参数 比如使用那个format 使用这个format下的那个frame(分辨率)
	 *1.1 根据一个uvc_streaming_control 设置数据包,可以手工设置也可以读出
	 *1.2 调用usb_control_msg 发出数据包
	 
	 *2.分配设置URB
	 *3.提交URB以接受数据
	*/
	.vidioc_streamoff = uvc_ioctl_streamoff,

	.vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out,
	.vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out,
	.vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out,
	.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,
	.vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out,
	.vidioc_create_bufs = uvc_ioctl_create_bufs,

	.vidioc_enum_input = uvc_ioctl_enum_input,
	.vidioc_g_input = uvc_ioctl_g_input,
	.vidioc_s_input = uvc_ioctl_s_input,

	/*查询 查询额外属性 获得 设置属性*/
	.vidioc_queryctrl = uvc_ioctl_queryctrl,
	.vidioc_query_ext_ctrl = uvc_ioctl_query_ext_ctrl,
	.vidioc_g_ctrl = uvc_ioctl_g_ctrl,
	.vidioc_s_ctrl = uvc_ioctl_s_ctrl,
	
	.vidioc_g_ext_ctrls = uvc_ioctl_g_ext_ctrls,
	.vidioc_s_ext_ctrls = uvc_ioctl_s_ext_ctrls,
	.vidioc_try_ext_ctrls = uvc_ioctl_try_ext_ctrls,
	.vidioc_querymenu = uvc_ioctl_querymenu,
	.vidioc_g_selection = uvc_ioctl_g_selection,

	/*设置帧间隔*/
	.vidioc_g_parm = uvc_ioctl _g_parm,
	.vidioc_s_parm = uvc_ioctl_s_parm,
	/*枚举支持的分辨率 帧间隔参数*/
	.vidioc_enum_framesizes = uvc_ioctl_enum_framesizes,
	.vidioc_enum_frameintervals = uvc_ioctl_enum_frameintervals,

	.vidioc_subscribe_event = uvc_ioctl_subscribe_event,
	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
	.vidioc_default = uvc_ioctl_default,
};

5.2 主要的调用过程

5.2.1 open

内核创建一个uvc_fh 文件句柄实例,将其关联到特定的视频流,以及初始化V4L2文件句柄

c 复制代码
static int uvc_v4l2_open(struct file *file)

5.2.2 VIDIOC_QUERYCAP 查询设备的功能

c 复制代码
static int uvc_ioctl_querycap(struct file *file, void *fh,
			      struct v4l2_capability *cap)
{
...
	usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));
	cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING
			  | chain->caps;
	if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
		cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
	else
		cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;

	return 0;
}

5.2.3 VIDIOC_ENUM_FMT 枚举设备支持的格式

枚举设备支持的格式 ,比如MJPEG或H264等 填充到v4l2_fmtdesc *fmt

c 复制代码
static int uvc_ioctl_enum_fmt(struct uvc_streaming *stream,
			      struct v4l2_fmtdesc *fmt)
{
	struct uvc_format *format;
	enum v4l2_buf_type type = fmt->type;
	__u32 index = fmt->index;

	if (fmt->type != stream->type || fmt->index >= stream->nformats)
		return -EINVAL;

	memset(fmt, 0, sizeof(*fmt)); //将fmt结构内存清0 
	fmt->index = index;
	fmt->type = type;

	format = &stream->format[fmt->index];
	fmt->flags = 0;
	if (format->flags & UVC_FMT_FLAG_COMPRESSED)
		fmt->flags |= V4L2_FMT_FLAG_COMPRESSED;
	strlcpy(fmt->description, format->name, sizeof(fmt->description));
	fmt->description[sizeof(fmt->description) - 1] = 0;
	fmt->pixelformat = format->fcc;
	return 0;
}

5.2.4 VIDIOC_G_FMT 得到当前的支持的格式 比如分辨率等

c 复制代码
int uvc_ioctl_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
--------->	uvc_v4l2_get_format(stream, fmt); /*跳转到v4l2框架下的函数来进行*/
c 复制代码
static int uvc_v4l2_get_format(struct uvc_streaming *stream,
	struct v4l2_format *fmt)
{
...
	fmt->fmt.pix.pixelformat = format->fcc;
	fmt->fmt.pix.width = frame->wWidth;
	fmt->fmt.pix.height = frame->wHeight;
	fmt->fmt.pix.field = V4L2_FIELD_NONE; //逐行扫描
	fmt->fmt.pix.bytesperline = format->bpp * frame->wWidth / 8;
	fmt->fmt.pix.sizeimage = stream->ctrl.dwMaxVideoFrameSize;
	fmt->fmt.pix.colorspace = format->colorspace;//色彩空间
	fmt->fmt.pix.priv = 0;
	return ret;
...
}

5.2.5 VIDIOC_S_FMT 更新UVC视频流的当前格式和帧大小 以匹配应用程序的请求配置

c 复制代码
static int uvc_ioctl_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
-------> uvc_v4l2_set_format(stream, fmt);
c 复制代码
/*
struct uvc_streaming *stream: 视频流对象的指针。
struct v4l2_format *fmt: 应用程序请求的视频格式信息。
*/
static int uvc_v4l2_set_format(struct uvc_streaming *stream,struct v4l2_format *fmt)
{
	/*尝试匹配和调整应用请求的格式*/
	ret = uvc_v4l2_try_format(stream, fmt, &probe, &format, &frame);

	
	mutex_lock(&stream->mutex);
	
	if (uvc_queue_allocated(&stream->queue)) {
		/*如果视频流帧缓冲区已经分配,此时不能修改格式*/
		ret = -EBUSY;
	}
}

5.2.6 VIDIOC_TRY_FMT 检查格式是否匹配

c 复制代码
static int uvc_ioctl_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
-------> uvc_v4l2_try_format(stream, fmt, &probe, NULL, NULL);

5.2.3 VIDIOC_REQBUF

c 复制代码
static int uvc_ioctl_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *rb)
-------> uvc_request_buffers(&stream->queue, rb);
--------->vb2_reqbufs(&queue->queue, rb); //跳转到video_buf2
-----------> __reqbufs(q, req)
--------------> __vb2_queue_alloc(q, req->memory, num_buffers, num_planes)
c 复制代码
/*
struct vb2_queue *q: 指向视频缓冲区vb2队列对象的指针。
struct v4l2_requestbuffers *req: 包含了请求分配缓冲区的信息。
*/

int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
	/*验证请求的内存类型和视频流的类型是否匹配*/
	int ret = __verify_memory_type(q, req->memory, req->type);
	return ret ? ret : __reqbufs(q, req);
}
5.2.3.1 视频缓冲区的主要类型有
c 复制代码
enum v4l2_memory {
	V4L2_MEMORY_MMAP             = 1, /*内存映射到用户空间*/
	V4L2_MEMORY_USERPTR          = 2,/*用户指针*/
	V4L2_MEMORY_OVERLAY          = 3,/*覆盖方式 不需要额外分配内存 视频数据直接覆盖到另外一个图像上*/
	V4L2_MEMORY_DMABUF           = 4,/*DMA缓存方式 无需经过cpu*/
};
5.2.3.2 涉及的主要结构体关系
5.2.3.3 枚举视频缓冲区的状态有:
c 复制代码
enum vb2_buffer_state {
	VB2_BUF_STATE_DEQUEUED,  // 未入队
	VB2_BUF_STATE_PREPARING,// 正在准备buffer
	VB2_BUF_STATE_PREPARED, //准备好了
	VB2_BUF_STATE_QUEUED, //入队
	VB2_BUF_STATE_ACTIVE, //激活状态 正在传输数据或者其他操作
	VB2_BUF_STATE_DONE,  //完成任务 等待进一步处理
	VB2_BUF_STATE_ERROR, //错误
};
5.2.3.4 __reqbufs() 启动流式传输

应从驱动程序的 vidioc_reqbufs ioctl 处理程序中调用。

该函数

  1. 验证从用户空间传递的流参数、
  2. 设置队列、
  3. 与驱动程序协商 buffer 数量和每个 buffer 的平面数
  4. 根据商定的参数分配内部缓冲区结构(struct vb2_buffer)。
  5. 对于 MMAP 内存类型,使用队列初始化过程中提供的内存处理/分配例程分配实际视频内存

如果 req->count 为 0,则释放所有内存。

如果队列之前已被分配(通过之前的 vb2_reqbufs)调用

且队列不忙,内存将被重新分配。

该函数的返回值可直接从 从驱动程序中的 vidioc_reqbufs 处理程序直接返回。

c 复制代码
static int __reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
	...

	/* Finally, allocate buffers and video memory */
	allocated_buffers = __vb2_queue_alloc(q, req->memory, num_buffers, num_planes);
	if (allocated_buffers == 0) {
		dprintk(1, "memory allocation failed\n");
		return -ENOMEM;
	}
}
5.2.3.4 __vb2_queue_alloc -----分配videobuf buffer 结构体
c 复制代码
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;
	/*1.为每个buffer 分配结构体*/
	for (buffer = 0; buffer < num_buffers; ++buffer) 
	{
		vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
		if (!vb) {
			dprintk(1, "memory alloc for buffer struct failed\n");
			break;}

		
		/*如果支持多平面缓存区 对于多平面格式,每个平面存储视频帧的一部分(例如YUV格式的Y、U、V分量)*/
		if (V4L2_TYPE_IS_MULTIPLANAR(q->type))
			vb->v4l2_buf.length = num_planes;
		/*设置buffer 参数*/
		vb->state = VB2_BUF_STATE_DEQUEUED; //未入队
		vb->vb2_queue = q;
		vb->num_planes = num_planes;
		vb->v4l2_buf.index = q->num_buffers + buffer;
		vb->v4l2_buf.type = q->type;
		vb->v4l2_buf.memory = memory;

		/* MMAP 类型的缓存区,需要调用__vb2_buf_mem_alloc再次分配*/
		if (memory == V4L2_MEMORY_MMAP)
		 {
			ret = __vb2_buf_mem_alloc(vb);
			if (ret) {
				dprintk(1, "failed allocating memory for "
						"buffer %d\n", buffer);
				kfree(vb);
				break;}
			/*缓冲区初始化回调*/
			ret = call_vb_qop(vb, buf_init, vb);
			if (ret) {
			/*初始化失败 释放相关资源跳出循环*/
				dprintk(1, "buffer %d %p initialization"
					" failed\n", buffer, vb);
				__vb2_buf_mem_free(vb);
				kfree(vb);
				break;
			}
		}
		/*更新信息*/
		q->bufs[q->num_buffers + buffer] = vb;
	}

	__setup_lengths(q, buffer);
	if (memory == V4L2_MEMORY_MMAP)
		__setup_offsets(q, buffer);
	dprintk(1, "allocated %d buffers, %d plane(s) each\n",
			buffer, num_planes);
	return buffer;
}

接下来在来看看 __vb2_buf_mem_alloc 这个函数 ,它主要是为给定的 vb2_buffer 分配足够的内存,以存储视频帧数据,主要就是为缓冲区的每个平面分配内存。

c 复制代码
/* 
struct vb2_buffer *vb 是指向一个视频缓冲区的指针。代表了视频数据的一个容器,可以是单平面的(整个帧在一个连续的内存块中)或多平面的(帧的不同部分,比如YUV分量,分布在不同的内存块中)。
*/
static int __vb2_buf_mem_alloc(struct vb2_buffer *vb)
{
	struct vb2_queue *q = vb->vb2_queue;
	/*确定DMA方向
	 * DMA_TO_DEVICE  从内存到设备  输出队列
	 * DMA_FROM_DEVICE  输入队列
	 */
	enum dma_data_direction dma_dir =
		V4L2_TYPE_IS_OUTPUT(q->type) ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
	void *mem_priv;
	int plane;
	
	/*遍历所有平面*/
	for (plane = 0; plane < vb->num_planes; ++plane) {
		unsigned long size = PAGE_ALIGN(q->plane_sizes[plane]);/*对平面大小进行对其*/
		/*使用缓冲区提供的内存分配器call_ptr_memop*/
		mem_priv = call_ptr_memop(vb, alloc, q->alloc_ctx[plane],
				      size, dma_dir, q->gfp_flags);
		if (IS_ERR_OR_NULL(mem_priv))
			goto free;

		/*设置平面属性*/
		vb->planes[plane].mem_priv = mem_priv;
		vb->v4l2_planes[plane].length = q->plane_sizes[plane];
	}

	return 0;
free:
	/* Free already allocated memory if one of the allocations failed */
	for (; plane > 0; --plane) {
		call_void_memop(vb, put, vb->planes[plane - 1].mem_priv);
		vb->planes[plane - 1].mem_priv = NULL;
	}

	return -ENOMEM;
}

5.2.4 VIDIOC_QUERYBUF 获取buffer信息 以及根据用户设置buffer

c 复制代码
static int uvc_ioctl_querybuf(struct file *file, void *fh, struct v4l2_buffer *buf)
-------> uvc_query_buffer(&stream->queue, buf)
--------->vb2_querybuf(&queue->queue, buf);
c 复制代码
int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
{
	struct vb2_buffer *vb;
	int ret;
	vb = q->bufs[b->index];/*获得buffer结构体指针*/
	
	ret = __verify_planes_array(vb, b);/*验证*/
	if (!ret)
		__fill_v4l2_buffer(vb, b);/*根据用户需求填充buffer信息*/
	return ret;
}
EXPORT_SYMBOL(vb2_querybuf);

5.2.5 VIDIOC_DBUF 获取buffer信息 以及根据用户设置buffer

c 复制代码
static int uvc_ioctl_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
------->uvc_queue_buffer(&stream->queue, buf);
--------->vb2_qbuf(&queue->queue, buf);
-------------> vb2_internal_qbuf(q, b);
相关推荐
半桔8 分钟前
【Linux手册】从接口到管理:Linux文件系统的核心操作指南
android·java·linux·开发语言·面试·系统架构
禁默16 分钟前
Linux Vim 编辑器详解:从入门到进阶(含图示+插件推荐)
linux·vim·excel
许白掰1 小时前
Linux入门篇学习——Linux 工具之 make 工具和 makefile 文件
linux·运维·服务器·前端·学习·编辑器
longze_75 小时前
Ubuntu连接不上网络问题(Network is unreachable)
linux·服务器·ubuntu
Dirschs6 小时前
【Ubuntu22.04安装ROS Noetic】
linux·ubuntu·ros
qianshanxue116 小时前
ubuntu 操作记录
linux
AmosTian8 小时前
【系统与工具】Linux——Linux简介、安装、简单使用
linux·运维·服务器
这我可不懂11 小时前
Python 项目快速部署到 Linux 服务器基础教程
linux·服务器·python
车车不吃香菇12 小时前
java idea 本地debug linux服务
java·linux·intellij-idea
tan77º12 小时前
【Linux网络编程】Socket - TCP
linux·网络·c++·tcp/ip