如何写UVC驱动

一、根据应用了解video驱动

一、通过分析应用程序的调用情况来了解驱动

在这里插入图片描述

xawtv是一个video的应用程序,能够驱动真实的摄像头与vivi。

在每个函数中都调用了大量的ioctrl函数,我们通过分析,将一些可有可无的ioctrl去掉,这样驱动就能更加简单,最终得到最简的驱动程序。(既只含有必要的ioctrl)。

以下为ioctl(区分必要与不必要,在v4l2_ioctl_ops中)
1.必要
.vidioc_querycap = cx25821_vidioc_querycap,

作用:cx25821_vidioc_querycap

const u32 cap_input = V4L2_CAP_VIDEO_CAPTURE |V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
const u32 cap_output = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE;
cap->capabilities = cap_input | cap_output | V4L2_CAP_DEVICE_CAPS;

获取设备的属性(如:是否是摄像头,是否支持读写,等)

2.不必要

.vidioc_enum_input = cx25821_vidioc_enum_input,
.vidioc_g_input = cx25821_vidioc_g_input,
.vidioc_s_input = cx25821_vidioc_s_input,

作用:

用于选择输入源,一个video设备可以配置多个输入源

3.不必要(对于数字摄像头)

.vidioc_g_std = cx25821_vidioc_g_std,
.vidioc_s_std = cx25821_vidioc_s_std,

以及video_device中的

.tvnorms = CX25821_NORMS,

都可以去掉。其中.tvnorms服务于g_std于s_std。

4.必要

.vidioc_enum_fmt_vid_cap = cx25821_vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = cx25821_vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = cx25821_vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,

用于列举,获取,测试,设置,摄像头所支持的格式。

5.必要

.vidioc_reqbufs       = vb2_ioctl_reqbufs,
//.vidioc_prepare_buf   = vb2_ioctl_prepare_buf,
//.vidioc_create_bufs   = vb2_ioctl_create_bufs,
.vidioc_querybuf      = vb2_ioctl_querybuf,
.vidioc_qbuf          = vb2_ioctl_qbuf,
.vidioc_dqbuf         = vb2_ioctl_dqbuf,

缓存区操作。

6.不必要

int (*vidioc_queryctrl)        (struct file *file, void *fh,
	struct v4l2_queryctrl *a);
int (*vidioc_g_ctrl)           (struct file *file, void *fh,
	struct v4l2_control *a);
int (*vidioc_s_ctrl)           (struct file *file, void *fh,
	struct v4l2_control *a);

查询,获得,设置属性(如亮度,对比度)。

7.必要

.vidioc_streamon      = vb2_ioctl_streamon,
.vidioc_streamoff     = vb2_ioctl_streamoff,

启动,关闭,摄像头。


二、继续分析获取数据流程

在这里插入图片描述

1.应用程序调用ioctl的VIDIOC_REQBUFS来分配缓冲区(头部),最终是调用到v4l2_ioctl_ops中的vidioc_reqbufs

int vb2_ioctl_reqbufs(struct file *file, void *priv,
			  struct v4l2_requestbuffers *p)
{
	struct video_device *vdev = video_devdata(file);
	int res = __verify_memory_type(vdev->queue, p->memory, p->type);

	if (res)
		return res;
	if (vb2_queue_is_busy(vdev, file))
		return -EBUSY;
	res = __reqbufs(vdev->queue, p);//利用到了,vdev中的queue与p
	/* If count == 0, then the owner has released all buffers and he
	   is no longer owner of the queue. Otherwise we have a new owner. */
	if (res == 0)
		vdev->queue->owner = p->count ? file->private_data : NULL;
	return res;
}

2.查询buf

int vb2_ioctl_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
	struct video_device *vdev = video_devdata(file);

	/* No need to call vb2_queue_is_busy(), anyone can query buffers. */
	return vb2_querybuf(vdev->queue, p);
}
int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
{
	struct vb2_buffer *vb;
	int ret;

	if (b->type != q->type) {
		dprintk(1, "wrong buffer type\n");
		return -EINVAL;
	}

	if (b->index >= q->num_buffers) {
		dprintk(1, "buffer index out of range\n");
		return -EINVAL;
	}
	vb = q->bufs[b->index];
	ret = __verify_planes_array(vb, b);
	if (!ret)
		__fill_v4l2_buffer(vb, b);
	return ret;
}

query之后得到缓冲区的大小与一些信息,之后调用mmap才真正分配缓冲区内存。

3.将缓冲区入队

vb2_ioctl_qbuf
	->list_add_tail(&vb->queued_entry, &q->queued_list);

4.streamon启动

5.select,当有数据来临是上报给应用(在队列的第一个节点上操作)

file_operations的poss调用到v4l2_file_operations中的poll

6.当有数据来临时,取出队列

.vidioc_dqbuf         = vb2_ioctl_dqbuf,

三、如何写摄像头驱动程序

经过实验发现v4l2层完全没用,去掉也可以运行!!!

原因:

在Linux内核中,v4l2_device层和video_device层是一起工作的,v4l2_device层为video_device层提供了一些控制、配置等服务。但是并不是所有的摄像头驱动都需要使用v4l2_device层这个软件抽象层,有些摄像头驱动本身就比较简单,只需要少量的控制和配置服务,在这种情况下,只需要依靠video_device层就能够满足其基本的功能需求。因此,对于这些摄像头驱动,可以去掉v4l2_device层而直接使用video_device层来实现设备的运行。
流程:

1.分配

struct video_device *video_device_alloc(void)

2.设置

主要设置:
	1.	.fops = &video_fops,
	
		static const struct v4l2_file_operations video_fops = {
        .owner = THIS_MODULE,
        .open = v4l2_fh_open,
        .release        = vb2_fop_release,
        .read           = vb2_fop_read,
        .poll		= vb2_fop_poll,
        .unlocked_ioctl = video_ioctl2,
        .mmap           = vb2_fop_mmap,
    };
    
	2.	.ioctl_ops = &video_ioctl_ops,(主要有11个)
	
	static const struct v4l2_ioctl_ops video_ioctl_ops = {
	.vidioc_querycap = cx25821_vidioc_querycap,
	.vidioc_enum_fmt_vid_cap = cx25821_vidioc_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap = cx25821_vidioc_g_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap = cx25821_vidioc_try_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
	.vidioc_reqbufs       = vb2_ioctl_reqbufs,
    .vidioc_querybuf      = vb2_ioctl_querybuf,
    .vidioc_qbuf          = vb2_ioctl_qbuf,
    .vidioc_dqbuf         = vb2_ioctl_dqbuf,
    .vidioc_streamon      = vb2_ioctl_streamon,
	.vidioc_streamoff     = vb2_ioctl_streamoff,		
	.....
	};

3.注册video_device

static inline int __must_check video_register_device(struct video_device *vdev,int type, int nr)

二、分析UVC源码:usb video class

一、自己如何写一个usb驱动程序(UVC:usb video class)

在这里插入图片描述

把前面的摄像头驱动中的东西全部放到usb_driver的probe中去。

uvc所在目录(4.1.15内核):\drivers\media\usb\uvc

1. uvc_driver.c

1.首先看入口函数

static int __init uvc_init(void)
{
	int ret;

	uvc_debugfs_init();

	ret = usb_register(&uvc_driver.driver);
	if (ret < 0) {
		uvc_debugfs_cleanup();
		return ret;
	}

	printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
	return 0;
}
struct uvc_driver uvc_driver = {
	.driver = {
		.name		= "uvcvideo",
		.probe		= uvc_probe,//指向probe,里面有注册video
		.disconnect	= uvc_disconnect,
		.suspend	= uvc_suspend,
		.resume		= uvc_resume,
		.reset_resume	= uvc_reset_resume,
		.id_table	= uvc_ids,//支持的usb设备list
		.supports_autosuspend = 1,
	},
};

uvc_probe:

里面做的主要是分配,设置,注册video_device

UVC协议/硬件:通过下载官方手册

在这里插入图片描述

其中Control Interface是用于控制接口的,而Streaming是用于数据传输的!

在uvc中有两个概念:

1.unit:用于内部操作(比如PU就是process unit可以用于设置亮度,饱和度,等)

2.terminal:用于内外连接


有了uvc硬件相关的知识我们就可以分析:uvc从应用到驱动的调用过程了!
const struct v4l2_file_operations uvc_fops = {
	.owner		= THIS_MODULE,
	.open		= uvc_v4l2_open,//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
};
uvc_probe
    ->uvc_register_video
        ->vdev->fops = &uvc_fops;

uvc_v4l2_open:

static int uvc_v4l2_open(struct file *file)
{
	struct uvc_streaming *stream;
	struct uvc_fh *handle;
	int ret = 0;

	uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_open\n");
	stream = video_drvdata(file);

	if (stream->dev->state & UVC_DEV_DISCONNECTED)
		return -ENODEV;

	ret = usb_autopm_get_interface(stream->dev->intf);
	if (ret < 0)
		return ret;

	/* Create the device handle. */
	handle = kzalloc(sizeof *handle, GFP_KERNEL);
	if (handle == NULL) {
		usb_autopm_put_interface(stream->dev->intf);
		return -ENOMEM;
	}

	mutex_lock(&stream->dev->lock);
	if (stream->dev->users == 0) {
		ret = uvc_status_start(stream->dev, GFP_KERNEL);
		if (ret < 0) {
			mutex_unlock(&stream->dev->lock);
			usb_autopm_put_interface(stream->dev->intf);
			kfree(handle);
			return ret;
		}
	}

	stream->dev->users++;
	mutex_unlock(&stream->dev->lock);

	v4l2_fh_init(&handle->vfh, &stream->vdev);
	v4l2_fh_add(&handle->vfh);
	handle->chain = stream->chain;
	handle->stream = stream;
	handle->state = UVC_HANDLE_PASSIVE;
	file->private_data = handle;

	return 0;
}

video_ioctl2:

  1. vidioc_querycap
	if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)//这里的stream->type就是上面的VS接口
		cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
	else
		cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;

这里的type应该是在usb摄像头被枚举时,分析其描述符时被设置的。

2 .vidioc_enum_fmt_vid_cap

static int uvc_ioctl_enum_fmt(struct uvc_streaming *stream,
			      struct v4l2_fmtdesc *fmt)
{
	.....
	if (fmt->type != stream->type || fmt->index >= stream->nformats)//说明VS中既有type也有format
		return -EINVAL;
	.....

	format = &stream->format[fmt->index];//这个数组应该也是设备被枚举时分析描述符时被设置的
	.....
}
  1. vidioc_g_fmt_vid_cap
	format = stream->cur_format;//usb摄像头支持多种格式,每种格式有可分为多个分辨率(format:格式,frame:分辨率)
	frame = stream->cur_frame;
	.....
	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;

4…vidioc_try_fmt_vid_cap

	/* Check if the hardware supports the requested format, use the default
	 * format otherwise.
	 *///查看硬件是否支持用户要求的格式,复制使用默认格式
	for (i = 0; i < stream->nformats; ++i) {
		format = &stream->format[i];//这个数组是设备在枚举的时候,分析描述符的时候被设置的
		if (format->fcc == fmt->fmt.pix.pixelformat)
			break;
	}

	if (i == stream->nformats) {
		format = stream->def_format;
		fmt->fmt.pix.pixelformat = format->fcc;
	}
	/* Find the closest image size. The distance between image sizes is
	 * the size in pixels of the non-overlapping regions between the
	 * requested size and the frame-specified size.
	 *///使用最接近的分辨率去尝试硬件
		rw = fmt->fmt.pix.width;
	rh = fmt->fmt.pix.height;
	maxd = (unsigned int)-1;

	for (i = 0; i < format->nframes; ++i) {
		__u16 w = format->frame[i].wWidth;
		__u16 h = format->frame[i].wHeight;

		d = min(w, rw) * min(h, rh);
		d = w*h + rw*rh - 2*d;
		if (d < maxd) {
			maxd = d;
			frame = &format->frame[i];
		}

		if (maxd == 0)
			break;
	}
  1. .vidioc_s_fmt_vid_cap 只是保存起来,并没有真正发给usb摄像头
	//先try_format
	ret = uvc_v4l2_try_format(stream, fmt, &probe, &format, &frame);
	//再设置(先存起,当真正要用到时才发给硬件)
	stream->ctrl = probe;
	stream->cur_format = format;
	stream->cur_frame = frame;

6.vidioc_reqbufs

​ ->uvc_request_buffers(&stream->queue, rb);

​ ->vb2_reqbufs(&queue->queue, rb);

7.vidioc_querybuf

​ ->uvc_query_buffer(&stream->queue, buf);

8…mmap = uvc_v4l2_mmap,(分配内存并映射到用户空间)

​ ->uvc_queue_mmap(&stream->queue, vma);

8.vidioc_qbuf(映射完得到内存之后入队列)

​ ->uvc_queue_buffer(&stream->queue, buf);

list_add_tail(&vb->queued_entry, &q->queued_list);

9…vidioc_streamon(前面的设置都是保存在变量中,没有发送给usb摄像头,这里先发送前面设置的所有数据,然后启动usb摄像头传输)

​ ->uvc_queue_streamon(&stream->queue, type);

​ ->vb2_start_streaming

​ ->vb2_buffer_done

​ ->vb2_buffer_done

​ -> wake_up(&q->done_wq);

		for (i = 0; i < q->num_buffers; ++i) {//当有数据到来就调用wake_up唤醒poll
			vb = q->bufs[i];
			if (vb->state == VB2_BUF_STATE_ACTIVE)
				vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED);
		}

9…poll = uvc_v4l2_poll(调用poll查看有没有数据到来)

​ ->uvc_queue_poll(&stream->queue, file, wait);

10.vidioc_dqbuf(有数据取出队列)

  1. .vidioc_streamoff(关闭传输)

以上都是涉及VS的过程(数据传输)而没有VC(数据控制)

我们以分析控制亮度为例:(同样是ioctl只不过是s_ctl/g_ctl)

 uvc_query_ctrl(dev/*哪一个设备*/, UVC_SET_CUR, ctrl->entity->id/*哪一个unit/terminal*/,
				dev->intfnum/*哪一个接口VS/VC*/, ctrl->info.selector,
				uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
				ctrl->info.size);

二:总结

1.uvc中有VS与VC
2.vc用于控制属性(如亮度),可以通过以下函数来设置
 uvc_query_ctrl(dev/*哪一个设备*/, UVC_SET_CUR, ctrl->entity->id/*哪一个unit/terminal*/,
				dev->intfnum/*哪一个接口VS/VC*/, ctrl->info.selector,
				uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
				ctrl->info.size);//类似函数去设置
3.vs用于控制数据传输,一个vc支持多个format而一个format有支持多个frame(分辨率)可以通过以下函数设置
	ret = __uvc_query_ctrl(stream->dev, UVC_SET_CUR, 0, stream->intfnum,
		probe ? UVC_VS_PROBE_CONTROL : UVC_VS_COMMIT_CONTROL, data,
		size, uvc_timeout_param);
4.我们在设置format时,只是从已有的数据来的,而这些数据设备被枚举时分析描述符时被设置的来的
uvc驱动重点:

在这里插入图片描述


三、从零写UVC之分析描述符

1. bind1

分析usb_driver中的id_table
static struct usb_device_id myuvc_ids[] = {
	/* Generic USB Video Class */
	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },//VC
    /*{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 2, 0) },//VS通过vc可以找到vs*/
	{},
};

主要分析USB_INTERFACE_INFO:

#define USB_INTERFACE_INFO(cl, sc, pr) 
	.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, 
	.bInterfaceClass = USB_CLASS_VIDEO,  
	.bInterfaceSubClass = 1, 
	.bInterfaceProtocol = 0 

解开宏定义:

bInterfaceClass = USB_CLASS_VIDEO(0x0e)
bInterfaceSubClass = 1
bInterfaceProtocol = 0 

什么意思查看uvc规格书:

在这里插入图片描述

注意:VS接口属于VC接口,所以只配VC就行,不过要配VS也行。


2. bind2 (参考lsusb打印设备描述符)

接上摄像头之后,可以根据lsusb -v -d 0x厂家id; 得到摄像头的一些描述符!

在这里插入图片描述

	struct usb_device *dev = interface_to_usbdev(intf);//通过intf获取usb_device
	struct usb_device_descriptor *descriptor = &dev->descriptor;//一个usb设备只有一个usb设备描述符
		printf("Device Descriptor:\n"
	       "  bLength             %5u\n"
	       "  bDescriptorType     %5u\n"
	       "  bcdUSB              %2x.%02x\n"
	       "  bDeviceClass        %5u\n"
	       "  bDeviceSubClass     %5u\n"
	       "  bDeviceProtocol     %5u\n"
	       "  bMaxPacketSize0     %5u\n"
	       "  idVendor           0x%04x\n"
	       "  idProduct          0x%04x\n"
	       "  bcdDevice           %2x.%02x\n"
	       "  iManufacturer       %5u\n"
	       "  iProduct            %5u\n"
	       "  iSerial             %5u\n"
	       "  bNumConfigurations  %5u\n",
	       descriptor->bLength, descriptor->bDescriptorType,
	       descriptor->bcdUSB >> 8, descriptor->bcdUSB & 0xff,
	       descriptor->bDeviceClass,
	       descriptor->bDeviceSubClass,
	       descriptor->bDeviceProtocol,
	       descriptor->bMaxPacketSize0,
	       descriptor->idVendor, vendor, descriptor->idProduct, product,
	       descriptor->bcdDevice >> 8, descriptor->bcdDevice & 0xff,
	       descriptor->iManufacturer,
	       descriptor->iProduct,
	       descriptor->iSerialNumber,
	       descriptor->bNumConfigurations);

3. bind3 (打印配置描述符)一个usb设备对于一个设备描述符,一个设备描述符对应多个配置描述符

	struct usb_host_config *host_config;//一个数组
	struct usb_config_descriptor *config;//配置描述符
		for(;i < descriptor->bNumConfigurations; ++i){
		  	host_config = &dev->config[i];
			config = &host_config->desc;
			printk("  Configuration Descriptor: %d\n"
			       "    bLength             %5u\n"
			       "    bDescriptorType     %5u\n"
			       "    wTotalLength       0x%04x\n"
			       "    bNumInterfaces      %5u\n"
			       "    bConfigurationValue %5u\n"
			       "    iConfiguration      %5u\n"
			       "    bmAttributes         0x%02x\n",
			       i,
			       config->bLength, config->bDescriptorType,
			       le16_to_cpu(config->wTotalLength),
			       config->bNumInterfaces, config->bConfigurationValue,
			       config->iConfiguration,
			       config->bmAttributes);

4.bind4 打印IAD信息(其中最重要的是告诉usb摄像头first interface与一共有多少个interface)

在这里插入图片描述

	struct usb_interface_assoc_descriptor *assoc_desc;
		for(;i < descriptor->bNumConfigurations; ++i){
					assoc_desc = host_config->intf_assoc[0];//是一个指针数组,不需要取地址
			printf("    Interface Association:\n"
			       "      bLength             %5u\n"
			       "      bDescriptorType     %5u\n"
			       "      bFirstInterface     %5u\n"
			       "      bInterfaceCount     %5u\n"
			       "      bFunctionClass      %5u\n"
			       "      bFunctionSubClass   %5u\n"
			       "      bFunctionProtocol   %5u\n"
			       "      iFunction           %5u\n",
				   assoc_desc->bLength,
				   assoc_desc->bDescriptorType,
				   assoc_desc->bFirstInterface,
				   assoc_desc->bInterfaceCount,
				   assoc_desc->bFunctionClass,
				   assoc_desc->bFunctionSubClass,
				   assoc_desc->bFunctionProtocol,
				   assoc_desc->iFunction);
	}

在这里插入图片描述


5. bind5(打印接口描述符下的设置描述符)同样一个配置描述符下可以有多个接口描述符,而一共接口描述符下也有多个设置描述符。

注意:接口直接对应probe是否能够被调用,也就是是不是在id_table中!

	struct usb_interface_descriptor *intfac_desc;//接口描述符
	for(i = 0; i < descriptor->bNumConfigurations; ++i){
			for(j = 0; j < intf->num_altsetting; ++j){
					intfac_desc = &intf->altsetting[j].desc;/*接口描述符设置,而不是接口描述符,一个接口描述符可以有多个设置*/
					printk("    Interface Descriptor altsetting: %d\n"
					       "      bLength             %5u\n"
					       "      bDescriptorType     %5u\n"
					       "      bInterfaceNumber    %5u\n"
					       "      bAlternateSetting   %5u\n"
					       "      bNumEndpoints       %5u\n"
					       "      bInterfaceClass     %5u\n"
					       "      bInterfaceSubClass  %5u\n"
					       "      bInterfaceProtocol  %5u\n"
					       "      iInterface          %5u\n",
					       j,
					       intfac_desc->bLength, intfac_desc->bDescriptorType, intfac_desc->bInterfaceNumber,
					       intfac_desc->bAlternateSetting, intfac_desc->bNumEndpoints, intfac_desc->bInterfaceClass,				       intfac_desc->bInterfaceSubClass, intfac_desc->bInterfaceProtocol,
					       intfac_desc->iInterface);
			}
	}

6. bind6 打印vs/vc自定义的描述符

7. bind7 打印端点描述符

	struct usb_endpoint_descriptor *endpoint_desc;//端点描述符
		for(i = 0; i < descriptor->bNumConfigurations; ++i){
					for(j = 0; j < intf->num_altsetting; ++j){
									    /*打印端点描述符*/
					for(m = 0; m < intfac_desc->bNumEndpoints; ++m){
						endpoint_desc = &intf->altsetting[j].endpoint[m].desc;
							printf("      Endpoint Descriptor:\n"
							       "        bLength             %5u\n"
							       "        bDescriptorType     %5u\n"
							       "        bEndpointAddress     0x%02x  EP %u %s\n"
							       "        bmAttributes        %5u\n"
							       "          Transfer Type            \n"
							       "          Synch Type               \n"
							       "          Usage Type               \n"
							       "        wMaxPacketSize      bytes\n"
							       "        bInterval           %5u\n",
							       endpoint_desc->bLength,
							       endpoint_desc->bDescriptorType,
							       endpoint_desc->bEndpointAddress,
							       endpoint_desc->bEndpointAddress & 0x0f,
							       (endpoint_desc->bEndpointAddress & 0x80) ? "IN" : "OUT",
							       endpoint_desc->bmAttributes,
							       endpoint_desc->bInterval);
					}
			}
			
	}

四、从零写UVC之实现数据传输

开始写代码:参考

在这里插入图片描述

1.实现框架

static struct video_device *myuvc_dev;

int myuvc_open(struct file *filp)
{
	return 0;
}
int myuvc_close(struct file *file)
{
	return 0;
}
unsigned int myuvc_poll(struct file *file, poll_table *wait)
{
	return 0;
}
long video_ioctl2(struct file *file,unsigned int cmd, unsigned long arg)
{
	return 0;
}
int myuvc_mmap(struct file *file, struct vm_area_struct *vma)
{
	return 0;
}


static const struct v4l2_file_operations myuvc_fops = {
	.owner = THIS_MODULE,
	.open = myuvc_open,
	.release        = myuvc_close,
	.poll		= myuvc_poll,
	.unlocked_ioctl = video_ioctl2,
	.mmap           = myuvc_mmap,
};

static int myuvc_vidioc_querycap(struct file *file, void *priv,struct v4l2_capability *cap)
{
	return 0;
}
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,struct v4l2_fmtdesc *f)
{
	return 0;
}

static int myuvc_vidioc_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
	return 0;
}
static int myuvc_vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
	return 0;
}
static int myuvc_vidioc_s_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f)
{
}
int myuvc_ioctl_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p)
{
}
int myuvc_ioctl_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{

}
int myuvc_ioctl_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
}

int myuvc_ioctl_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
}

int myuvc_ioctl_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
}

int myuvc_ioctl_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
}

static const struct v4l2_ioctl_ops myuvc_ioctl_ops = {
	.vidioc_querycap = myuvc_vidioc_querycap,
	.vidioc_enum_fmt_vid_cap = myuvc_vidioc_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap = myuvc_vidioc_g_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap = myuvc_vidioc_try_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap = myuvc_vidioc_s_fmt_vid_cap,
	.vidioc_reqbufs       = myuvc_ioctl_reqbufs,
    .vidioc_querybuf      = myuvc_ioctl_querybuf,
    .vidioc_qbuf          = myuvc_ioctl_qbuf,
    .vidioc_dqbuf         = myuvc_ioctl_dqbuf,
    .vidioc_streamon      = myuvc_ioctl_streamon,
	.vidioc_streamoff     = myuvc_ioctl_streamoff,
};

void myuvc_release(struct video_device *vdev)//不写这个函数,video_device注册可能出现问题
{	
}
static int myuvc_probe(struct usb_interface *intf/*对于一个usb来说可能有多个intface,一个intface表示一个功能上的逻辑设备*/,const struct usb_device_id *id)
{
    .....
    /* 分配一个video_device结构体*/
    myuvc_dev = video_device_alloc();

    /* 设置结构体*/
    myuvc_dev->fops = &myuvc_fops;
    myuvc_dev->ioctl_ops = &myuvc_ioctl_ops;
    myuvc_dev->release = myuvc_release;//不写这个函数,video_device注册可能出现问题

    /*注册结构体*/
    video_register_device(myuvc_dev, VFL_TYPE_GRABBER, -1);//写-1为自动分配一个设备号
   	.....
}

2. 填充函数

一、v4l2_ioctl_ops

摄像头选型:

在这里插入图片描述

1. myuvc_vidioc_querycap 查询摄像头的能力
static int myuvc_vidioc_querycap(struct file *file, void *priv,struct v4l2_capability *cap)
{
	memset(cap, 0, sizeof *cap);
	strcpy(cap->driver, "myuvc");
	strcpy(cap->card, "myuvc");
	cap->capabilities = V4L2_CAP_STREAMING |V4L2_CAP_VIDEO_CAPTURE;//说明是视频捕捉设备,切不是使用read/write而是使用ioctl/mmap来读取数据

	return 0;
}
2. myuvc_vidioc_enum_fmt_vid_cap摄像头支持哪种格式(我们假设只支持一种格式,可以通过接上usb摄像头通过lsusb来查看)
//参考uvc的fmts,关于该结构体的填充还是需要lsusb根据实际情况来填充,摄像头支持几种格式就填几种格式
static struct myuvc_format_desc myuvc_fmts[] = {
	{
		.name		= "YUV 4:2:2 (YUYV)",
		.guid		= UVC_GUID_FORMAT_YUY2,
		.fcc		= V4L2_PIX_FMT_YUYV,
	},
	{
		.name		= "YUV 4:2:2 (YUYV)",
		.guid		= UVC_GUID_FORMAT_YUY2_ISIGHT,
		.fcc		= V4L2_PIX_FMT_YUYV,
	},
};


static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,struct v4l2_fmtdesc *f)
{
	if (unlikely(f->index >= ARRAY_SIZE(myuvc_fmts)))
		return -EINVAL;

	strcpy(f->description, myuvc_fmts[f->index].name, sizeof(f->description));
	f->pixelformat = myuvc_fmts[f->index].fcc;

	return 0;
}
3. myuvc_vidioc_g_fmt_vid_cap 返回当前使用的格式
static struct v4l2_format myuvc_format;

static int myuvc_vidioc_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
	memcpy(f, &myuvc_format, sizeof(myuvc_format));
	return 0;
}
4. myuvc_vidioc_try_fmt_vid_cap 测试程序是否支持某种格式,并强制设置分辨率以及帧数
struct frame_desc {
	int width;
	int height;
};
//自定义分辨率,通过查看描述符获得
static struct frame_desc myuvc_frame[] = {{640,480},{352,288},{320,240},{176,144},{160,120}};
static int frame_idx = 1;//先定死分辨率
static int bBitsPerPixel = 16;//可以通过lsusb查找"bBitsPerPixel"获得,表示一个橡塑占多少字节

//尝试各种格式,并强制设置分辨率以及帧数
static int myuvc_vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
	if (f && f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)//如果f不支持捕获就直接返回
		return -EINVAL;

	/*人工查看描述符,确定支持哪几种分辨率 通过lsusb*/
	f->fmt.pix.width = myuvc_frame[frame_idx].width;
	f->fmt.pix.height = myuvc_frame[frame_idx].height;

	f->fmt.pix.bytesperline = (f->fmt.pix.width * bBitsPerPixel) >> 3;//每行占多少字节 = 每行多少像素x一个像素多少字节
	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;//一帧多少字节 = 每行多少字节 * 多少行
	return 0;
}
5. myuvc_vidioc_s_fmt_vid_cap 设置格式
//设置格式
static int myuvc_vidioc_s_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f)
{
	//先使用try_fmt尝试设置,并强制设置其分辨率
	myuvc_vidioc_try_fmt_vid_cap(file, NULL, f);

	//在将设置好的format赋值到我们自己定义的全局变量中去
	memcpy(&myuvc_format, f, sizeof(myuvc_format));

	return 0;
}

6.剩下的buf与steamon/off

通过查看其它厂家编写的uvc驱动发现:他们一些v4l2_ioctl_ops是直接调用videobuf2_core.c中提供的

	.vidioc_reqbufs       = vb2_ioctl_reqbufs,
	.vidioc_prepare_buf   = vb2_ioctl_prepare_buf,
	.vidioc_create_bufs   = vb2_ioctl_create_bufs,
	.vidioc_querybuf      = vb2_ioctl_querybuf,
	.vidioc_qbuf          = vb2_ioctl_qbuf,
	.vidioc_dqbuf         = vb2_ioctl_dqbuf,
	.vidioc_streamon      = vb2_ioctl_streamon,
	.vidioc_streamoff     = vb2_ioctl_streamoff,

通过提问cht得到:

在Linux内核的uvc驱动中,如果需要自己编写驱动程序,则可以使用 v4l2-ioctl 框架处理ioctl命令。对于视频数据流应用程序来说,使用ioctl,它能够控制设备并请求或提供内存缓冲区地址给设备。

对于处理 v4l2_ioctl_ops 对象中的成员函数指针,我们可以选择是否直接使用videobuf2_core.c中定义的。一般而言,在开发过程中有两种选项:

直接使用videobuf2_core.c中定义的函数指针:可以在驱动模块中使用 vb2_* 系列函数来进行内存分配、管理和映射等操作。这些函数都由videobuf2_core.c文件导出,并且已经为大多数常见的操作类型实现了对应的函数指针。使用这些函数指针可以使驱动程序的编写过程更加简单,不必为每个操作自己创建独立的函数指针或进行繁琐的内存管理操作。

自定义功能类似的函数指针:如果驱动程序需要进行特定的内存分配操作或者需要自定义缓冲区管理逻辑,那么可以在当前驱动模块中定义功能类似的函数指针,然后在v4l2_ioctl_ops对象中填充这些函数指针即可。自定义函数指针可以实现更灵活的内存管理和缓冲区处理策略,并且可以根据具体需求自行进行优化和改进。

总之,在编写驱动程序过程中使用哪种方式取决于开发者对驱动程序需求的具体情况。如果当前驱动主要使用常见的操作类型,则建议直接使用 videobuf2_core.c 中函数指针;如果需要特殊的缓冲区管理策略或者需要在内存分配/映射时进行定制化操作,则可以选择自定义并填充功能类似的函数指针。

所以我们直接尝试使用内核通过的试试。

事实证明:uvc源码其实也是调用videobuf2_core.c中定义的ioctl

比如:.vidioc_querybuf = uvc_ioctl_querybuf,

uvc_ioctl_querybuf:

		->uvc_query_buffer

				->vb2_querybuf

二、v4l2_file_operations

通过查看其它厂家的驱动发现,里面的.poll, .mmap, .unlocked_ioctl都是使用videobuf2_core.c内核中定义的,我们也使用。

	.poll     = vb2_fop_poll,
	.mmap	  = vb2_fop_mmap,
	.unlocked_ioctl	  = video_ioctl2,

事实证明:uvc驱动中的mmap与poll也最终调用到videobuf2_core.c中定义的函数。

总结:

整个fops函数的填充我们自己写的有:

v4l2_ioctl_ops:
	.vidioc_querycap = myuvc_vidioc_querycap,
	.vidioc_enum_fmt_vid_cap = myuvc_vidioc_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap = myuvc_vidioc_g_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap = myuvc_vidioc_try_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap = myuvc_vidioc_s_fmt_vid_cap,
v4l2_file_operations:
	.open = myuvc_open,
	.release        = myuvc_close,

五、修改驱动使支持自制摄像头

在这里插入图片描述

一、修改内核UVC使在虚拟机上支持

这是厂家提供手册,根据手册修改的uvc驱动,没什么可说的。

二、修改上一节我们自己写的uvc驱动,在虚拟机上支持

1. 修改全局变量

在我们自己写的uvc驱动中,唯一不通用的部分就是前面根据lsusb查出的参数所定义的一些变量。所以我们大致只需要根据摄像头lsusb的数据重新需改这些参数就行。

//自定义分辨率,通过查看描述符获得
static struct frame_desc myuvc_frame[] = {{640,480},{352,288},{320,240},{176,144},{160,120}};
static int frame_idx = 1;//先定死分辨率
static int bBitsPerPixel = 16;//可以通过lsusb查找"bBitsPerPixel"获得,表示一个橡塑占多少字节

如何使用lsusb:

在这里插入图片描述

0x1b3b为厂家id。

2.修改ioctl与fops
	.vidioc_querycap = myuvc_vidioc_querycap,
	.vidioc_enum_fmt_vid_cap = myuvc_vidioc_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap = myuvc_vidioc_g_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap = myuvc_vidioc_try_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap = myuvc_vidioc_s_fmt_vid_cap,
	.vidioc_reqbufs       = myuvc_ioctl_reqbufs,
    .vidioc_querybuf      = myuvc_ioctl_querybuf,
    .vidioc_qbuf          = myuvc_ioctl_qbuf,
    .vidioc_dqbuf         = myuvc_ioctl_dqbuf,
    .vidioc_streamon      = myuvc_ioctl_streamon,
	.vidioc_streamoff     = myuvc_ioctl_streamoff,

1.vidioc_querycap 一般不需要改

2.myuvc_vidioc_enum_fmt_vid_cap

//参考uvc的fmts,关于该结构体的填充还是需要lsusb根据实际情况来填充,摄像头支持几种格式就填几种格式
static struct myuvc_format_desc myuvc_fmts[] = {
	{
		.name		= "YUV 4:2:2 (YUYV)",
		.guid		= UVC_GUID_FORMAT_YUY2,
		.fcc		= V4L2_PIX_FMT_YUYV,
	},
	{
		.name		= "YUV 4:2:2 (YUYV)",
		.guid		= UVC_GUID_FORMAT_YUY2_ISIGHT,
		.fcc		= V4L2_PIX_FMT_YUYV,
	},
};
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,struct v4l2_fmtdesc *f)
{
	if (unlikely(f->index >= ARRAY_SIZE(myuvc_fmts)))
		return -EINVAL;

	strcpy(f->description, myuvc_fmts[f->index].name, sizeof(f->description));
	f->pixelformat = myuvc_fmts[f->index].fcc;
	f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    
	return 0;
}

要修改myuvc_fmts结构体中的格式:

首先提供lsusb查看支持哪些格式,如何到对应的头文件中去

3.vidioc_g_fmt_vid_cap 不用改

4.vidioc_try_fmt_vid_cap

//尝试各种格式,并强制设置分辨率以及帧数
static int myuvc_vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
	if (f && f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)//如果f不支持捕获就直接返回
		return -EINVAL;
	if(f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)//如果不支持V4L2_PIX_FMT_YUYV这种格式
		return -EINVAL;
    
	/*人工查看描述符,确定支持哪几种分辨率 通过lsusb*/
	f->fmt.pix.width = myuvc_frame[frame_idx].width;
	f->fmt.pix.height = myuvc_frame[frame_idx].height;

	f->fmt.pix.bytesperline = (f->fmt.pix.width * bBitsPerPixel) >> 3;//每行占多少字节 = 每行多少像素x一个像素多少字节
	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;//一帧多少字节 = 每行多少字节 * 多少行
    
    //f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;//可以提供lsusb查看
	return 0;
}

改V4L2_PIX_FMT_YUYV为对应的格式。

注意:如果支持的是压缩格式,那么bBitsPerPixel全局变量需要设置为0.

而这时,一帧数据有多大呢?可以提供lsusb查看!

在这里插入图片描述

colorspace的设置:查看lsusb

在这里插入图片描述

5.vidioc_s_fmt_vid_cap 不需要改

对于我们直接使用videobuf2_core.c中的函数,这里的修改其实就可以提供提供的厂家手册来修改了。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android UVC即Android系统上的USB视频类(UVC)设备驱动程序。UVC设备是指插入电脑、手机等设备的摄像头、麦克风、扬声器等外界音频/视频设备,通常通过USB口连接。UVC设备在传输音频视频等数据的过程中,不需要安装驱动程序,只需要插上即可自动识别。此外,Android UVC驱动程序的出现,也使得Android设备可以兼容更多不同品牌的外部音频视频设备,如微型相机、USB摄像头、红外摄像头等。 Android UVC开发者来说也是很有帮助的。例如,应用程序可以直接访问摄像头的输出,而无需使用专用SDK;还可以通过USB设备连接到外部音频设备,并利用该设备处理应用程序的音频数据;甚至Android UVC还可以为开发者提供将设备作为HDMI输出器或虚拟USB主机的选择,以便在开发特定类型的产品时进行高级调试。 在使用Android UVC的同时,需要考虑的问题也不少。由于UVC设备的独特性,需要特定的USB接口支持才能正常工作。Android UVC还存在锁定、崩溃和数据质量等问题,因此需要谨慎使用。总之,Android UVC驱动程序丰富了Android系统设备与外部媒体设备的互联互通,以及应用程序在Android设备上的开发效率。 ### 回答2: Android UVC是指在Android系统上支持USB Video Class(UVC)设备的功能。UVC是一种视频设备接口标准,可以将外部USB摄像头、微型视频摄像机、望远镜和显微镜等设备与计算机连接,通过USB接口实现与计算机的数据传输。 在Android系统上使用UVC设备可以让用户轻松地进行视频录制、视频会议、远程监控和医疗诊断等应用。UVC设备通过USB接口连接到Android设备上,可以无需安装额外的驱动程序或软件,直接在Android设备上进行视频采集和处理。通过Android UVC功能,消费者可以更方便地使用外部视频设备与Android设备集成,增强设备的多媒体能力,扩展使用场景。 此外,一些Android设备已经支持内置UVC设备,如GoPro等相机,用户可以通过USB接口直接将视频数据导入到Android设备中,实现视频编辑和分享。Android UVC的普及和应用,将进一步加强Android设备的视频处理能力,为用户带来更加便捷和高效的视频交互体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值