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);
相关推荐
云飞云共享云桌面2 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
Peter_chq2 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
一坨阿亮3 小时前
Linux 使用中的问题
linux·运维
dsywws4 小时前
Linux学习笔记之vim入门
linux·笔记·学习
幺零九零零5 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
小林熬夜学编程6 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
程思扬7 小时前
为什么Uptime+Kuma本地部署与远程使用是网站监控新选择?
linux·服务器·网络·经验分享·后端·网络协议·1024程序员节
sun0077007 小时前
拷贝 cp -rdp 和 cp -a
linux·运维·服务器
wowocpp7 小时前
ubuntu 22.04 server 安装 anaconda3
linux·运维·ubuntu
乡村农夫7 小时前
cuda 环境搭建
linux