USB摄像头驱动--UVC驱动的深入理解与编写

1.驱动调用流程

对于字符设备的驱动,其实linux已经给我们了一个封装好的框架,首先分配设置一个结构体,然后根据成员名字依次填充其中的名字,类型,probe函数等,然后在驱动的入口函数(一般是xxx_xxx_init函数)进行注册(注册到一个链表)。

APP操作read、write、open等接口,linux内核根据APP传入的参数(这里的参数传递一般是copy_to_kernel),在字符设备的链表中根据id_table比对,找到相应的结构体,调用它的probe函数,在probe函数中进行自己的实际数据分析(例如视频:在其中进行v4l2设备的注册以及填充)。

对于ioctl操作,APP调用ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);其中iFD是文件打开获得句柄,VIDIOC_QUERYCAP是预先定义的宏(即传入需要操作ioctl集中的哪一个函数,tV4l2Cap(struct v4l2_capability tV4l2Cap)一个传入的容器,用于函数的返回参数),linux会去调用fops中的ioctl处理函数即(video_ioctl2),在video_ioctl2函数中将用户参数复制给内核,然后调用实际的ioctl处理函数__video_do_ioctl,info = &v4l2_ioctls[_IOC_NR(cmd)]去调用ioctl函数集中相应的VIDIOC_QUERYCAP处理函数。

long video_ioctl2(struct file *file,
	       unsigned int cmd, unsigned long arg)
{
	return video_usercopy(file, cmd, arg, __video_do_ioctl);
}

1.1分析内核摄像头驱动

对于linux内核4.13.0中,UVC驱动在drivers/media/usb/uvc/文件夹里,下面对uvc_driver.c进行分析。

1.2构造一个usb_driver结构体

该结构体相当于告诉内核我这是一个USB设备,需要进行USB设备的相关函数调用。(UVC:usb video class)这里进行usb的设置。

struct uvc_driver {
	struct usb_driver driver;
};
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,
	},
};

.id_table 表示该USB驱动支持哪些设备

1.3设置结构体

设置相当于将其中的函数定一下来,一开始可以写作空函数,以便判定框架是否正确。在probe函数中进行这个结构体的具体内容进行设置,包括分配video_device结构体,设置结构体,注册video_device结构体,fops结构体中需要进行ioctrl的具体操作进行设置。

uvc_probe
    kzalloc //分配video_device
        uvc_register_chains  
            uvc_register_terms  
                uvc_register_video
                    vdev->v4l2_dev = &dev->vdev; //设置video_device
                    vdev->fops = &uvc_fops; 
                    vdev->ioctl_ops = &uvc_ioctl_ops;
                    vdev->release = uvc_release;
                    video_register_device //注册video_device

1.4注册结构体

uvc_init
    usb_register

在probe()函数中,进行video的相关设置,其实就是给video外面套上一个usb的壳子,具体的核心还是fops以及ioctl_ops。

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

		myuvc_vdev->fops    = &myuvc_fops;/*文件操作结构体,*/
  
        myuvc_vdev->ioctl_ops = &myuvc_ioctl_ops;/*ioctl函数集*/
        
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_vidioc_reqbufs,
        .vidioc_querybuf      = myuvc_vidioc_querybuf,
        .vidioc_qbuf          = myuvc_vidioc_qbuf,
        .vidioc_dqbuf         = myuvc_vidioc_dqbuf,
        

		/*查询/获得/设置属性*/
		.vidioc_queryctrl = myuvc_ioctl_queryctrl,
		.vidioc_g_ctrl = myuvc_ioctl_g_ctrl,
		.vidioc_s_ctrl = myuvc_ioctl_s_ctrl,
        // 启动/停止
        .vidioc_streamon      = myuvc_vidioc_streamon,
        .vidioc_streamoff     = myuvc_vidioc_streamoff,   
};

最重要的就是ioctl2,它使用video_usercopy()获得用户空间传进来的参数,调用__video_do_ioctl()在v4l2_ioctls[]数组里找到对应的uvc_ioctl_ops。
v4l2_ioctls[]数组定义是在内核中v4l2_ioctl.c中定义:
static struct v4l2_ioctl_info v4l2_ioctls[]

2.编写UVC驱动

流程:当插上USB至主机时,就会产生两个接口(VC和VS)(一般驱动是只有VC接口:原因是可以通过VC接口寻找到VS接口,进行设置);然后获取USB描述符并分析,从而设置摄像头(分辨率、格式等);然后分配缓冲区,启动摄像头;最后就是讲USB摄像头采集的数据放入之前申请的缓存区以便APP的使用。
编写中主要部分如下:
1.注册一个USB驱动(在probe函数中进行video驱动的注册)
2.数据格式的分析(获得USB的自定义格式)、设置格式
3.缓冲区的申请(在mmap函数中申请出来,在ioctl_reqbufs函数进行区域大小的定义)
4.属性相关的设置(亮度等)
5.设置URB格式以便USB数据的传输以及分析
6.启动、停止
7.mmap函数,poll函数

2.1各子函数的编写

2.1.1注册Usb驱动

在实体驱动外面套一个USB驱动的壳,原因在于需要通过USB传输数据(内核先访问到的是USB设备,然后才是video设备)

/*1.分配一个usb_driver结构体*/
/*2.设置*/
/*id_table表示支持哪些设备*/
/*probe表示接上支持的设备时,probe函数就会被调用*/
/*disconnect表示拔除设备后会调用该函数进行销毁的工作*/
static struct usb_driver myuvc_driver = {
	.name = "myuvc",
	.probe = myuvc_probe,
	.disconnect = myuvc_disconnect,
	.id_table = myuvc_ids,
};
/*myuvc_ids里面可以填充某个厂家特定的设备,也可以填充通用的设备*/
/*内核比对id成功一次调用一次probe函数,拔除设备时一样会比对id调用myuvc_disconnect函数*/
/*这是是两个通用设备,所以会调用两次*/
static struct usb_device_id myuvc_ids[] = {
	/* Generic USB Video Class */
	/*这个设备属于USB_CLASS_VIDEO类,子类是1(VIDEOControl视频控制接口),协议是0*/
	/*流接口属于控制接口,可以通过控制接口找到流接口*/
	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },	/*VdieoControl Interface*/
	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 2, 0) },	/*VdieoStreaming Interface*/
	{}
};

USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0)是接口宏参数
bInterfaceClass(接口类)-cl,bInterfaceSubClass(接口子类)–sc,bInterfaceProtocol(接口类协议)-- pr。

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

其中接口类在inclued/uapi/linux/usb/Ch9.h中定义

/*
 * Device and/or Interface Class codes
 * as found in bDeviceClass or bInterfaceClass
 * and defined by www.usb.org documents
 */
#define USB_CLASS_PER_INTERFACE		0	/* for DeviceClass */
#define USB_CLASS_AUDIO			1
#define USB_CLASS_COMM			2
#define USB_CLASS_HID			3
#define USB_CLASS_PHYSICAL		5
#define USB_CLASS_STILL_IMAGE		6
#define USB_CLASS_PRINTER		7
#define USB_CLASS_MASS_STORAGE		8
#define USB_CLASS_HUB			9
#define USB_CLASS_CDC_DATA		0x0a
#define USB_CLASS_CSCID			0x0b	/* chip+ smart card */
#define USB_CLASS_CONTENT_SEC		0x0d	/* content security */
#define USB_CLASS_VIDEO			0x0e
#define USB_CLASS_WIRELESS_CONTROLLER	0xe0
#define USB_CLASS_MISC			0xef
#define USB_CLASS_APP_SPEC		0xfe
#define USB_CLASS_VENDOR_SPEC		0xff

接口子类相当于在id_table[]中的一个编号

注册

static int myuvc_init(void)
{
	/*注册*/
	usb_register(&myuvc_driver);
	return 0;
}

2.1.2注册video驱动

在linux比对APP传入的参数后,匹配成功会自动调用usb驱动中的probe函数

static struct v4l2_device v4l2_dev;
static int myuvc_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	static int cnt = 0;
	int ret;
	struct usb_device *dev = interface_to_usbdev(intf);
	//struct usb_device_descriptor *descriptor = &dev->descriptor;

	//usb_device_id会使probe()调用两次,然而创建video_device只需要一次
	myuvc_udev = dev;

	printk("myuvc_probe : cnt = %d\n", cnt++);
	if (cnt == 1)//获取编号
    {
        myuvc_control_intf = intf->cur_altsetting->desc.bInterfaceNumber;
    }
    else if (cnt == 2)
    {
        myuvc_streaming_intf = intf->cur_altsetting->desc.bInterfaceNumber;
    }
	//在第二次进入probe进行设备的注册  这里可以第一次也可以第二次
    if (cnt == 2)
    {
         /* 1. 分配一个video_device结构体 */
        myuvc_vdev = video_device_alloc();
		if (NULL == myuvc_vdev)
        {
            printk("Faile to alloc video device (%d)\n", __LINE__);
            return -ENOMEM;
        }
        /* 2. 设置 */
        myuvc_vdev->release = myuvc_release;
        myuvc_vdev->v4l2_dev = &v4l2_dev;
        myuvc_vdev->fops    = &myuvc_fops;
        myuvc_vdev->ioctl_ops = &myuvc_ioctl_ops;
        
        /*3.注册*/
        ret = video_register_device(myuvc_vdev, VFL_TYPE_GRABBER, -1);
        if (ret < 0)
        {
            printk("Faile to video_register_device. ret =  %d \n", ret);
            return ret;
        }
        else
            printk("video_register_device ok.\n");
    }
    
    
	return 0;
}

相应的disconnect函数也会被调用两次,只做一次释放操作

static void myuvc_disconnect(struct usb_interface *intf)
{
	static int cnt;
	printk("myuvc_disconnect : cnt = %d\n", cnt++);
	
	/*同样的在第二次进行设备的销毁*/
    if (cnt == 2)
    {
        
        /*1.销毁*/
        video_unregister_device(myuvc_vdev);
        video_device_release(myuvc_vdev);
    }
}

2.2数据格式的设置

之前video_device设备填充的函数在这里进行一一实现与填充。
myuvc_vdev->fops = &myuvc_fops;
myuvc_vdev->ioctl_ops = &myuvc_ioctl_ops;

2.2.1 file_operations结构体的设置

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

myuvc_open:

/*A1*/
static int myuvc_open(struct file *file)
{
	return 0;
}

myuvc_close:

/*A18 关闭*/
static int myuvc_close(struct file *file)
{
	myuvc_vidioc_streamoff(NULL,NULL,0);
	return 0;
}

mmap函数与poll函数由于需要bufferr的操作,所以在ioctl函数申请buffer之后才才能进行。

video_ioctl2:
这里一个操作函数,在内核中搜索这个名字名字可以发现其内容就是内容复制的函数

long video_ioctl2(struct file *file,
	       unsigned int cmd, unsigned long arg)
{
	return video_usercopy(file, cmd, arg, __video_do_ioctl);
}

实际根据cmd的不同在 __video_do_ioctl调用不同的ioctl处理函数,这些函数是事先在一个函数结构体中定义好的函数集,cmd不同,从而调用不同的函数

/*在内核中函数名字可能改变,但是成员名字不会变*/
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_vidioc_reqbufs,
        .vidioc_querybuf      = myuvc_vidioc_querybuf,
        .vidioc_qbuf          = myuvc_vidioc_qbuf,
        .vidioc_dqbuf         = myuvc_vidioc_dqbuf,
        

		/*查询/获得/设置属性*/
		.vidioc_queryctrl = myuvc_ioctl_queryctrl,
		.vidioc_g_ctrl = myuvc_ioctl_g_ctrl,
		.vidioc_s_ctrl = myuvc_ioctl_s_ctrl,
        // 启动/停止
        .vidioc_streamon      = myuvc_vidioc_streamon,
        .vidioc_streamoff     = myuvc_vidioc_streamoff,   
};

myuvc_vidioc_querycap:

/*A2  参考的是UVC_V4L2.c*/
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->version = 1;
	/*V4L2_CAP_VIDEO_CAPTURE:表示视频捕获设备
	 *V4L2_CAP_STREAM:表示通过ioctrl读写数据不是通过read/write;
	 * */
	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
	return 0;
}

cap->driver:命名
cap->card:命名
cap->version:版本号(以上这些都是根据驱动编写者根据规范所定义,可以任意写或套用内核规范)
cap->capabilities:制定这个驱动支持的功能


myuvc_vidioc_enum_fmt_vid_cap:

static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
					struct v4l2_fmtdesc *f)
{
	/* 人工查看描述符可知我们用的摄像头只支持1种格式 :VS_FORMAT_MJPEG*/
	if (f->index >= 1)
		return -EINVAL;

    /* 支持什么格式呢?
     * 查看VideoStreaming Interface的描述符,
     * 得到GUID为"59 55 59 32 00 00 10 00 80 00 00 aa 00 38 9b 71"
     */
	strcpy(f->description, "MJPEG");
	f->pixelformat = V4L2_PIX_FMT_MJPEG;    
    //f->flags       = ;
	f->type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	return 0;
	
}

在内核中会传入描述符的参数(struct v4l2_fmtdesc *f),所以不用人工查看。
其中V4L2_PIX_FMT_MJPEG在videodevice2.h中定义:

/* compressed formats */
#define V4L2_PIX_FMT_MJPEG    v4l2_fourcc('M', 'J', 'P', 'G') /* Motion-JPEG   */
#define V4L2_PIX_FMT_JPEG     v4l2_fourcc('J', 'P', 'E', 'G') /* JFIF JPEG     */
#define V4L2_PIX_FMT_DV       v4l2_fourcc('d', 'v', 's', 'd') /* 1394          */
#define V4L2_PIX_FMT_MPEG     v4l2_fourcc('M', 'P', 'E', 'G') /* MPEG-1/2/4 Multiplexed */
#define V4L2_PIX_FMT_H264     v4l2_fourcc('H', '2', '6', '4') /* H264 with start codes */
#define V4L2_PIX_FMT_H264_NO_SC v4l2_fourcc('A', 'V', 'C', '1') /* H264 without start codes */
#define V4L2_PIX_FMT_H264_MVC v4l2_fourcc('M', '2', '6', '4') /* H264 MVC */
#define V4L2_PIX_FMT_H263     v4l2_fourcc('H', '2', '6', '3') /* H263          */
#define V4L2_PIX_FMT_MPEG1    v4l2_fourcc('M', 'P', 'G', '1') /* MPEG-1 ES     */
#define V4L2_PIX_FMT_MPEG2    v4l2_fourcc('M', 'P', 'G', '2') /* MPEG-2 ES     */
#define V4L2_PIX_FMT_MPEG4    v4l2_fourcc('M', 'P', 'G', '4') /* MPEG-4 part 2 ES */
#define V4L2_PIX_FMT_XVID     v4l2_fourcc('X', 'V', 'I', 'D') /* Xvid           */
#define V4L2_PIX_FMT_VC1_ANNEX_G v4l2_fourcc('V', 'C', '1', 'G') /* SMPTE 421M Annex G compliant stream */
#define V4L2_PIX_FMT_VC1_ANNEX_L v4l2_fourcc('V', 'C', '1', 'L') /* SMPTE 421M Annex L compliant stream */
#define V4L2_PIX_FMT_VP8      v4l2_fourcc('V', 'P', '8', '0') /* VP8 */
#define V4L2_PIX_FMT_VP9      v4l2_fourcc('V', 'P', '9', '0') /* VP9 */

其中V4L2_BUF_TYPE_VIDEO_CAPTURE是宏定义:

enum v4l2_buf_type {
	V4L2_BUF_TYPE_VIDEO_CAPTURE        = 1,
	V4L2_BUF_TYPE_VIDEO_OUTPUT         = 2,
	V4L2_BUF_TYPE_VIDEO_OVERLAY        = 3,
	V4L2_BUF_TYPE_VBI_CAPTURE          = 4,
	V4L2_BUF_TYPE_VBI_OUTPUT           = 5,
	V4L2_BUF_TYPE_SLICED_VBI_CAPTURE   = 6,
	V4L2_BUF_TYPE_SLICED_VBI_OUTPUT    = 7,
	V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,
	V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,
	V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE  = 10,
	V4L2_BUF_TYPE_SDR_CAPTURE          = 11,
	V4L2_BUF_TYPE_SDR_OUTPUT           = 12,
	V4L2_BUF_TYPE_META_CAPTURE         = 13,
	/* Deprecated, do not use */
	V4L2_BUF_TYPE_PRIVATE              = 0x80,
};

myuvc_vidioc_g_fmt_vid_cap:

/*A4 返回当前所使用的格式 */
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;
}

myuvc_vidioc_try_fmt_vid_cap:

/* A5 测试驱动程序是否支持某种格式, 强制设置该格式 
 * 参考: uvc_v4l2_try_format
 *       myvivi_vidioc_try_fmt_vid_cap
 * */
static int myuvc_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
			struct v4l2_format *f)
{
	
	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
    {
        return -EINVAL;
    }

    if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG)
        return -EINVAL;
    
    /* 调整format的width, height, 
     * 计算bytesperline, sizeimage
     */

    /* 人工查看描述符, 确定支持哪几种分辨率 */
    f->fmt.pix.width  = frames[frame_idx].width;
    f->fmt.pix.height = frames[frame_idx].height;
    
	f->fmt.pix.bytesperline = (f->fmt.pix.width * bBitsPerPixel) >> 3;
	f->fmt.pix.sizeimage = dwMaxVideoFrameSize;
    
    f->fmt.pix.field      = V4L2_FIELD_NONE;
	f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
	f->fmt.pix.priv       = 0;		/* private data, depends on pixelformat */

	return 0;
}

先判断传入的v4l2_format结构体里的type和pixelformat是不是正确的格式。
再设置v4l2_pix_format结构体的width(宽)、height(高)和filed(数据扫描方式:不交错)。
以及sizeimage(每帧图像大小),这里的值大小的确定是通过probe()里打印的dwMaxVideoFrameSize值,这里每帧的理论大小是widthheight=320240=76800小于dwMaxVideoFrameSize=77312,估计最大每帧图像还会包含其它数据。
大多数网络摄像头的colorspace(颜色空间)都是V4L2_COLORSPACE_SRGB。
priv(私有数据)由pixelformat决定。
这里的所有设置的值,理论上都来自对USB设备描述符的解析,这里简化了代码解析的过程,直接赋值,实际开发中为了适配多个摄像头,应该读取后解析。


myuvc_vidioc_s_fmt_vid_cap:

/*A6  参考 myvivi_vidioc_s_fmt_vid_cap*/
static int myuvc_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
					struct v4l2_format *f)
{
	int ret = myuvc_vidioc_try_fmt_vid_cap(file, NULL, f);
	if (ret < 0)
		return ret;

    memcpy(&myuvc_format, f, sizeof(myuvc_format));
	return 0;
}

设置格式既是将之前在try中的格式进行copy一份。

2.3缓冲区的相关操作

首先是申请缓冲区vidioc_reqbufs(),应用层ioctl调用此函数,让其分配若干个buf,应用层后面将从这些buf读取视频数据。
驱动先从传入的v4l2_requestbuffers结构体获得count(buf数量),每个buf的大小是前面my_uvc_format的sizeimage(每帧图像大小),且长度页对齐。

PAGE_ALIGN
PAGE_ALIGN在内核里作用是将数据以4K页大小上界对齐。
举个例子:
假如传入的数据大小是4000字节,那么结果得到4096字节;
假如传入的数据大小是4096字节,那么结果得到4096字节;
假如传入的数据大小是5000字节,那么结果得到8192字节;
源码:
#define PAGE_SIZE 4096
#define PAGE_MASK (~(PAGE_SIZE-1))
#define PAGE_ALIGN(x) ((x + PAGE_SIZE - 1) & PAGE_MASK)
实质:PAGE_ALIGN(x) = ((x + 4095) & (~4095))


myuvc_vidioc_reqbufs:

/*A7 APP调用该ioctrl让驱动程序分配若干个缓存,APP将从这些缓存中读到视频数据
 *   参考uvcg_alloc_buffers
 * */
static int myuvc_vidioc_reqbufs(struct file *file, void *priv,
			  struct v4l2_requestbuffers *p)
{
	int nbuffers = p->count;
	int bufsize = PAGE_ALIGN(myuvc_format.fmt.pix.sizeimage);
	unsigned int i;
	void *mem = NULL;
	int ret;

	/*如果已经有BUF则先free掉*/
	if((ret=myuvc_free_buffers())<0)
	{
		goto done;
	}
	/* Bail out if no buffers should be allocated. */
    if (nbuffers == 0)
	{
		goto done;
	}
    /* Decrement the number of buffers until allocation succeeds. */
    for (; nbuffers > 0; --nbuffers) 
	{
        mem = vmalloc_32(nbuffers * bufsize);
        if (mem != NULL)
		{
			break;
		}
    }
	/*沒有成功则返回*/
	if (mem == NULL) 
	{
        ret = -ENOMEM;
        goto done;
    }
	/* 这些缓存是一次性作为一个整体来分配的 */
    memset(&myuvc_queue, 0, sizeof(myuvc_queue));

	/*初始化两个队列头*/
	INIT_LIST_HEAD(&myuvc_queue.mainqueue);
	INIT_LIST_HEAD(&myuvc_queue.irqqueue);

	/*将一整块区域人工分开,分成几个区域*/
	for (i = 0; i < nbuffers; ++i) {
       myuvc_queue.buffer[i].buf.index = i;		/*索引*/
        myuvc_queue.buffer[i].buf.m.offset = i * bufsize;	/*偏移*/
        myuvc_queue.buffer[i].buf.length = myuvc_format.fmt.pix.sizeimage;	/*原始大小*/
        myuvc_queue.buffer[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	/*视频捕获设备*/
        myuvc_queue.buffer[i].buf.sequence = 0;				/*序列技术*/
        myuvc_queue.buffer[i].buf.field = V4L2_FIELD_NONE;	/*扫描方式*/
        myuvc_queue.buffer[i].buf.memory = V4L2_MEMORY_MMAP;/*内存类型*/
        myuvc_queue.buffer[i].buf.flags = 0;				/*标志*/
        myuvc_queue.buffer[i].state     = VIDEOBUF_IDLE;	/*状态*/
        init_waitqueue_head(&myuvc_queue.buffer[i].wait);	/*初始化等待队列*/
    }
	myuvc_queue.mem = mem;
    myuvc_queue.count = nbuffers;
    myuvc_queue.buf_size = bufsize;
    ret = nbuffers;
done:
    return ret;
}

判断my_uvc_queue结构体里的mem(内存地址)是否为空,非空的话说明原来已经分配了buf,需要先释放内存、清空my_uvc_queue。
如果传入的nbuffers 是0,表示不需要分配,直接返回。
分配buf:这里是将所有buf作为一个整体进行分配,然后将信息记录到一个buf结构体中,大小是nbuffers *bufsize,分配成功返回buf的数量,分配失败,减少nbuffers 数量再次进行分配。
然后分配两个队列,将分配的buf放入队列中,mainqueue用于应用层APP读取数据,irqqueue用于驱动产生数据放进去。(就是将buf一次排列出来,每个buf有两个指针,一个用于应用层读数据,一个用于驱动产生数据)。
再依次设置每个buf的v4l2_buffer结构体的index(索引)、m.offset(偏移)、length(大小)、type(类型)、sequence(序列计数)、field(扫描方式)、memory(内存类型)、flags(标志),再设置my_uvc_buffer的state(状态)和初始化等待队列wait。
在这里插入图片描述


myuvc_vidioc_querybuf:

/*A8 查询缓存状态,比如地址信息(APP可以用mmap进行映射)*/
static int myuvc_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{
	int ret = 0;

	if(v4l2_buf->index >= myuvc_queue.count)
	{
		ret = -EINVAL;
		goto done;
	}
	memcpy(v4l2_buf, &myuvc_queue.buffer[v4l2_buf->index].buf, sizeof(*v4l2_buf));

	/*更新flags*/
	if (myuvc_queue.buffer[v4l2_buf->index].vma_use_count)
		v4l2_buf->flags |= V4L2_BUF_FLAG_MAPPED;


	switch (myuvc_queue.buffer[v4l2_buf->index].state) {
    	case VIDEOBUF_ERROR:
    	case VIDEOBUF_DONE:
    		v4l2_buf->flags |= V4L2_BUF_FLAG_DONE;
    		break;
    	case VIDEOBUF_QUEUED:
    	case VIDEOBUF_ACTIVE:
    		v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED;
    		break;
    	case VIDEOBUF_IDLE:
    	default:
    		break;
	}
done:    
	return ret;

}

先判定传入的v4l2_buf.index 是否超出生成的索引(即队列),然后将之前申请buf时创建的myuvc_queue复制给v4l2_buf(注意判定buf是否已经被使用过(mmap过),如果使用过需要将flag标志位更新,表示已经使用过)


/A10 把缓冲区放入队列,底层的硬件操作函数将会把数据放入这个队列的缓存/
static int myuvc_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{
struct myuvc_buffer *buf;

/* 0. APP传入的v4l2_buf可能有问题, 要做判断 */
if (v4l2_buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
    v4l2_buf->memory != V4L2_MEMORY_MMAP) {
	return -EINVAL;
}

if (v4l2_buf->index >= myuvc_queue.count) {
	return -EINVAL;
}

buf = &myuvc_queue.buffer[v4l2_buf->index];

if (buf->state != VIDEOBUF_IDLE) {
	return -EINVAL;
}

/* 1. 修改状态 */
buf->state = VIDEOBUF_QUEUED;
buf->buf.bytesused = 0;

/* 2. 放入2个队列 */
/* 队列1: 供APP使用 
 * 当缓冲区没有数据时,放入mainqueue队列
 * 当缓冲区有数据时, APP从mainqueue队列中取出
 */
list_add_tail(&buf->stream, &myuvc_queue.mainqueue);

/* 队列2: 供产生数据的函数使用
 * 当采集到数据时,从irqqueue队列中取出第1个缓冲区,存入数据
 */
list_add_tail(&buf->irq, &myuvc_queue.irqqueue);

return 0;

}
APP传入的参数可能与驱动不一致,所以需要先进行类型以及存储类型的判定,节点是否超过最大数量,myuvc_queue的myuvc_buffer状态是否处于空闲。然后修改myuvc_queue的myuvc_buffer的状态为处于队列中VIDEOBUF_QUEUED,初始化v4l2_buffer中的bytesused(缓冲区中数据的大小)为0。
队列mainqueue:供应用层使用,当队列中缓冲区有数据时, 应用层从mainqueue队列中取出数据;
队列irqqueue:供产生数据的函数使用,当采集到数据时,从irqqueue队列中取出首个缓冲区,存入数据;


myuvc_vidioc_dqbuf:

static int myuvc_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{
	/* APP发现数据就绪后, 从mainqueue里取出这个buffer */

    struct myuvc_buffer *buf;
    int ret = 0;

	if (list_empty(&myuvc_queue.mainqueue)) {
		ret = -EINVAL;
		goto done;
	}
    
	/*根据stream节点在mainqueue取出第一个myuvc_buffer*/
	buf = list_first_entry(&myuvc_queue.mainqueue, struct myuvc_buffer, stream);

	switch (buf->state) {
	case VIDEOBUF_ERROR:
		ret = -EIO;
	case VIDEOBUF_DONE:
		buf->state = VIDEOBUF_IDLE;
		break;

	case VIDEOBUF_IDLE:
	case VIDEOBUF_QUEUED:
	case VIDEOBUF_ACTIVE:
	default:
		ret = -EINVAL;
		goto done;
	}

	list_del(&buf->stream);
	memcpy(v4l2_buf, &buf->buf, sizeof *v4l2_buf);

done:
	return ret;

}

这里是应用层想得到数据,因此是从mainqueue队列获取。
首先判断mainqueue是否是空队列,然后以myuvc_queu.mainqueue作为头节点,搜索myuvc_buffer结构体中的stream,得到队列中第一个myuvc_buffer的地址。
再把myuvc_buffer的state(状态)改为VIDEOBUF_IDLE(空闲)。、
再将该节点从队列删除,最后返回v4l2_buf。队列变化如下:
在这里插入图片描述
初始状态,队列mainqueue和队列irqqueue串连起了传进来的buf。
产生数据的时候,buf[0]装入数据,且断开与队列irqqueue的连接,此时buf[1]是队列irqqueue的第一个节点。
取出数据的时候,buf[0]取出数据,且断开与队列mainqueue的连接,此时buf[1]是队列mainqueue的第一个节点。
待数据处理完成,buf[0]将被再次放入队列,此时在队列尾部。
周而复始完成放入、取出队列。

2.4属性相关设置(亮度、对比度、饱和度等)

2.4.1亮度的设置

接下来进行摄像头属性的设置,从UVC硬件模型可知、VC interface控制摄像头的硬件属性(由P控制U:Processing Unit),在UVC 1.5 Class specification.pdf(70页)中找到Processing Unit Descriptor,其中的bmControls表示摄像头支持的属性
在这里插入图片描述
在这里插入图片描述
在代码中,UVC规范定义的属性在uvc_ctrl.c里的一个uvc_control_info结构体类型的vc_ctrls数组里。


	.entity		= UVC_GUID_UVC_PROCESSING,   //属于哪个entity(比如PU)
	.selector	= UVC_PU_BRIGHTNESS_CONTROL, //用于亮度
	.index		= 0,                         //对应Processing Unit Descriptor的bmControls[0]
	.size		= 2,                         //数据长度为2字节
	.flags		= UVC_CTRL_FLAG_SET_CUR      //支持SET_CUR、GET_RANGE(GET_CUR、GET_MIN、GET_MAX)等
			| UVC_CTRL_FLAG_GET_RANGE
			| UVC_CTRL_FLAG_RESTORE,
},
{
	.entity		= UVC_GUID_UVC_PROCESSING,
	.selector	= UVC_PU_CONTRAST_CONTROL,
	.index		= 1,
	.size		= 2,
	.flags		= UVC_CTRL_FLAG_SET_CUR
			| UVC_CTRL_FLAG_GET_RANGE
			| UVC_CTRL_FLAG_RESTORE,
},

此外,uvc_control_mapping结构体类型的uvc_ctrl_mappings数组更加细致地描述属性。

{
	.id	    	= V4L2_CID_BRIGHTNESS,       //应用层根据ID来找到对应属性
	.name		= "Brightness",              //名字
	.entity		= UVC_GUID_UVC_PROCESSING,   //属于哪了个entity(比如PU)
	.selector	= UVC_PU_BRIGHTNESS_CONTROL, //用于亮度控制
	.size		= 16,                        //数据占多少位
	.offset		= 0,                         //从哪位开始
	.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,    //属性类别(整数)
	.data_type	= UVC_CTRL_DATA_TYPE_SIGNED, //数据类型(有符号整数)
},
{
	.id		    = V4L2_CID_CONTRAST,
	.name		= "Contrast",
	.entity		= UVC_GUID_UVC_PROCESSING,
	.selector	= UVC_PU_CONTRAST_CONTROL,
	.size		= 16,
	.offset		= 0,
	.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
	.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
},

属性控制的准备工作如下:
1.获取摄像头的设备描述符,根据PU的描述符的bmControls,得知它支持哪些属性;
2.从uvc_ctrls数组中根据entity和index找到对应属性,得知其支持的操作(SET_CUR、GET_CUR等);
3.从uvc_ctrl_mappings数组中根据ID找到对应属性,得知其更加详细信息(整数等);

首先是查询属性vidioc_queryctrl(),应用层传入一个v4l2_queryctrl结构体,驱动设置其参数返回。
需要设置的参数有id(ID)、type(类型)、name(名字)、flags(标志)、minimum(最小值)、maximum(最大值)、step(步长)、default_value(典型值),其中前面几个是根据前面的准备工作得知的值,直接赋值,后面的几个需要使用usb_control_msg()函数向摄像头发起USB传输,获取对应值。

usb_control_msg
内核原型是:
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
__u8 requesttype, __u16 value, __u16 index, void *data,
__u16 size, int timeout)
功能:发送一个简单的控制消息到指定的端点,并等待消息完成或超时
参数解析:

  • dev:指向控制消息所发送的目标USB设备(usb_device)的指针; <这里是在probe()里获取的myuvc_udev>

  • pipe:控制消息所发送的目标USB设备的特定端点,调用usb_sndctrlpipe(把指定USB设备的指定端点设置为一个控制OUT端点)或usb_rcvctrlpipe(把指定USB设备的指定端点设置为一个控制IN端点)来创建的; <这里把my_uvc_udev设置为接收端点>

  • request:控制消息的USB请求值; <这里分别是需要的GET_MIN、GET_MAX、GET_RES、GET_DEF>

  • requesttype:控制消息的USB请求类型值;这里为USB_TYPE_CLASS(1<<5)、usb_recip_interface(1<<0)、usb_dir_in(1<<7)>
    D7:数据的传输方向:0表示从主机到设备;1表示从设备到主机;
    D6~5:命令的类型:0表示标准命令;1表示类命令;2表示厂商提供的命令;3保留;
    D4~0:接收对象:0表示设备; 1表示接口;2表示端点;3表示其他;   value:控制消息的USB消息值; <这里是PU亮度控制>

  • index:控制消息的USB消息索引值;<这里是PU对应的ID和控制接口>

  • data:指向要发送/接收的数据的指针; <这里是接收数据>

  • size:data参数所指缓冲区的大小;<这里是两字节,bControlSize=2>

  • timeout:以msecs为单位,期望等待的超时时间,如果为0,该函数将一直等待消息结束以的时间;<这里是5s> 返回值:

  • 成功返回接收/发送的字节数,否则返回负的错误值

myuvc_ioctl_queryctr:这个函数的实际目的就是确定亮度的最大值,最小值,步长,默认值,进行四次USB传输即可。

/*uvc_query_ctrl下的usb_control_msg是usb传输函数,
 *设置pipe以及type是接收还是发送,
 *即可实现读取以及写入相关的设置
 */
/*__uvc_ctrl_add_mapping里面对mapping结构体get函数赋值myuvc_get_le_valu*/
int myuvc_ioctl_queryctrl(struct file *file, void *fh, struct v4l2_queryctrl *ctrl)
{
	__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
	unsigned int pipe;
    int ret;
    u8 data[2];

	/*比对id是否一致*/
	if (ctrl->id != V4L2_CID_BRIGHTNESS)
        return -EINVAL;
	
	memset(ctrl, 0, sizeof *ctrl);
	ctrl->id = V4L2_CID_BRIGHTNESS;
	ctrl->type = V4L2_CTRL_TYPE_INTEGER;
	strcpy(ctrl->name, "MYUVC_BRIGHTNESS");
	ctrl->flags = 0;


	/*uvc_ctrl_populate_cache里面*/
	pipe = usb_rcvctrlpipe(myuvc_udev, 0);/*端点0*/
	type |=  USB_DIR_IN;

	/* 发起USB传输确定这些值 
	 *__uvc_ctrl_add_mapping里面对mapping结构体get函数赋值myuvc_get_le_value*/
	ret = usb_control_msg(myuvc_udev, pipe, GET_MIN, type, PU_BRIGHTNESS_CONTROL << 8,
			ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000); 
	if (ret != 2)
        return -EIO;
	ctrl->minimum = myuvc_get_le_value(data);

	ret = usb_control_msg(myuvc_udev, pipe, GET_MAX, type, PU_BRIGHTNESS_CONTROL << 8,
			ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000); 
	if (ret != 2)
        return -EIO;
	ctrl->maximum = myuvc_get_le_value(data);

	ret = usb_control_msg(myuvc_udev, pipe, GET_RES, type, PU_BRIGHTNESS_CONTROL << 8,
			ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000); 
	if (ret != 2)
        return -EIO;
	ctrl->step = myuvc_get_le_value(data);

	ret = usb_control_msg(myuvc_udev, pipe, GET_DEF, type, PU_BRIGHTNESS_CONTROL << 8,
			ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000); 
	if (ret != 2)
        return -EIO;
	ctrl->default_value = myuvc_get_le_value(data);

	printk("Brightness: min =%d, max = %d, step = %d, default = %d\n", ctrl->minimum, ctrl->maximum, ctrl->step, ctrl->default_value);
	return 0;
}

myuvc_ioctl_g_ctrl::获得亮度的当前值

int myuvc_ioctl_g_ctrl(struct file *file, void *fh, struct v4l2_control *ctrl)
{
	__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
	unsigned int pipe;
    int ret;
    u8 data[2];

	if (ctrl->id != V4L2_CID_BRIGHTNESS)
        return -EINVAL;
	
	pipe = usb_rcvctrlpipe(myuvc_udev, 0);
	type |= USB_DIR_IN;

	ret = usb_control_msg(myuvc_udev, pipe, GET_CUR, type, PU_BRIGHTNESS_CONTROL << 8,
			ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
    if (ret != 2)
        return -EIO;
	ctrl->value = myuvc_get_le_value(data);	/* Note signedness */
    
    return 0;
}

myuvc_get_le_value:将data(包含偏移位和大小)转为32位的整数

/* Extract the bit string specified by mapping->offset and mapping->size
 * from the little-endian data stored at 'data' and return the result as
 * a signed 32bit integer. Sign extension will be performed if the mapping
 * references a signed data type.
 */
static __s32 myuvc_get_le_value(const __u8 *data)
{
	int bits = 16;
	int offset = 0;
	__s32 value = 0;
	__u8 mask;

	data += offset / 8;
	offset &= 7;
	mask = ((1LL << bits) - 1) << offset;

	for (; bits > 0; data++) {
		__u8 byte = *data & mask;
		value |= offset > 0 ? (byte >> offset) : (byte << (-offset));
		bits -= 8 - (offset > 0 ? offset : 0);
		offset -= 8;
		mask = (1 << bits) - 1;
	}

	/* Sign-extend the value if needed. */
	value |= -(value & (1 << (16 - 1)));

	return value;
}

myuvc_ioctl_s_ctrl:设置亮度大小,进行一次USB传输即可(注意pipe和type数输出!)

/*参考uvc_ioctl_s_ctrl  uvc_ctrl_set  uvc_ctrl_commit*/
int myuvc_ioctl_s_ctrl(struct file *file, void *fh, struct v4l2_control *ctrl)
{
	__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
    unsigned int pipe;
    int ret;
    u8 data[2];
    
    if (ctrl->id != V4L2_CID_BRIGHTNESS)
        return -EINVAL;

	myuvc_set_le_value(ctrl->value, data);

	pipe = usb_sndctrlpipe(myuvc_udev, 0);
	type |= USB_DIR_OUT;

	ret = usb_control_msg(myuvc_udev, pipe, SET_CUR, type, PU_BRIGHTNESS_CONTROL << 8,
			ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
    if (ret != 2)
        return -EIO;
	return 0;
}
/* Set the bit string specified by mapping->offset and mapping->size
 * in the little-endian data stored at 'data' to the value 'value'.
 */
static void myuvc_set_le_value(__s32 value, __u8 *data)
{
	int bits = 16;
	int offset = 0;
	__u8 mask;

	data += offset / 8;
	offset &= 7;

	for (; bits > 0; data++) {
		mask = ((1LL << bits) - 1) << offset;
		*data = (*data & ~mask) | ((value << offset) & mask);
		value >>= offset ? offset : 8;
		bits -= 8 - offset;
		offset = 0;
	}
}

2.4.2设置URB

USB Request Block(URB)是Linux内核中,USB驱动实现的一个数据结构,用于组织每一次的USB设备驱动的数据传输请求。
也就是说,将USB传输相关信息放到URB这个结构体中,发送给USB核心,USB核心解析该结构体,从而进行所需数据/控制相关操作。

所需的操作大致有三步:
1.分配usb_buffers,作为数据的缓冲区;
2.分配URB;
3.设置URB;

  • 为什么要usb_buffer?
    从这个角度想:前面的my_uvc_buffer作为内核与用于空间的buf进行交互,这里的urb_buffer作为内核与USB设备的buf进行交互,最后类似urb_buffer 赋值给myuvc_buffer,就实现了USB设备的数据传到用户层了。

首先,USB每次传输的数据大小,是可变的,根据外部设备的能力决定,比如外部设备支持一次传输100、200或800字节数据,每次传输称为Packet(包);
其次,USB每次需要传输的数据,很可能大于前面的最大包(800字节),因此每次传输的数据,将会被分割成N个包来传输。
因此,用URB来记录一次完整传输的信息,包括每次传多少,传几次,传的目标位置等。

psize = my_uvc_wMaxPacketSize; //实时传输端点一次能传输的最大字节数;lsusb: wMaxPacketSize 0x0320 1x800 bytes;
size  = my_uvc_params.dwMaxVideoFrameSize; //一帧数据的最大长度
npackets = DIV_ROUND_UP(size, psize); //传多少次(向上取整)

psize就是每次传输的数据大小,通过USB摄像头的设备描述符wMaxPacketSize(最大每包大小)可以得知。
size就是每帧图像的大小,前面在my_uvc_params已经设置过了,是通过在probe()的打印dwMaxPayloadTransferSize得知的;
npackets就是size/psize再向上取整,得到需要传多少次。
最后还要size = psize * npackets更新一下向上取整后的新大小。

这个分配MYUVC_URBS_NUM个(一个就行)urb_buffer和urb。
urb_buffer通过usb_alloc_coherent()函数分配,大小为前面的调整后的size,得到指向buf的指针和DMA地址。
urb通过myuvc_uninit_urbs函数分配,数量为npackets,得到指向该urb的指针。

对应的,如果分配失败,相应的调用usb_free_coherent()和usb_free_urb()释放空间,并相应的清空指针和重置myuvc_q.urb_size。

然后就是设置URB:

urb->dev:指向目标设备的指针;<这里是USB摄像头my_uvc_udev>
urb->pipe:与目标设置的管道;<这里使用usb_rcvisocpipe()创建等时(ISO:Isochronous)管道,参数是对应VS的端点地址>
urb->transfer_flags:传输标志;<URB_ISO_ASAP(开始调度)和URB_NO_TRANSFER_DMA_MAP(使用DMA对应的buf)>
urb->interval:传输间隔;<来自USB描述符的bInterval=1>
urb->transfer_buffer:要传输的buf;<前面得到的my_uvc_q.urb_buffer[i]指针>
urb->transfer_dma:buf对应的dma物理地址;<前面得到的my_uvc_q.urb_dma[i]地址>
urb->complete:收完数据后的中断处理函数;<后面再编写>
urb->number_of_packets:该URB要传输多少个包;<前面计算的npackets>
urb->transfer_buffer_length:总共的数据长度;<前面计算的size>
urb->iso_frame_desc[j].offset:每个包的偏移位置;<j * psize就对应每个包的偏移>
urb->iso_frame_desc[j].length:每个包的大小;<前面得到的psize>
关于URB数据结构的参考博客。

/* 参考: uvc_get_video_ctrl 
 (ret = uvc_get_video_ctrl(video, probe, 1, GET_CUR)) 
 static int uvc_get_video_ctrl(struct uvc_video_device *video,
     struct uvc_streaming_control *ctrl, int probe, __u8 query)
 */
static int myuvc_get_streaming_params(struct myuvc_streaming_control *ctrl)
{
	__u8 *data;
	__u16 size;
	int ret;
	__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
	unsigned int pipe;

	size = uvc_version >= 0x0110 ? 34 : 26;
	data = kmalloc(size, GFP_KERNEL);
	if (data == NULL)
		return -ENOMEM;
   
	pipe = (GET_CUR & 0x80) ? usb_rcvctrlpipe(myuvc_udev, 0)
			      : usb_sndctrlpipe(myuvc_udev, 0);
	type |= (GET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;

	ret = usb_control_msg(myuvc_udev, pipe, GET_CUR, type, VS_PROBE_CONTROL << 8,
			0 << 8 | myuvc_streaming_intf, data, size, 5000);

    if (ret < 0)
        goto done;

	ctrl->bmHint = le16_to_cpup((__le16 *)&data[0]);
	ctrl->bFormatIndex = data[2];
	ctrl->bFrameIndex = data[3];
	ctrl->dwFrameInterval = le32_to_cpup((__le32 *)&data[4]);
	ctrl->wKeyFrameRate = le16_to_cpup((__le16 *)&data[8]);
	ctrl->wPFrameRate = le16_to_cpup((__le16 *)&data[10]);
	ctrl->wCompQuality = le16_to_cpup((__le16 *)&data[12]);
	ctrl->wCompWindowSize = le16_to_cpup((__le16 *)&data[14]);
	ctrl->wDelay = le16_to_cpup((__le16 *)&data[16]);
	ctrl->dwMaxVideoFrameSize = get_unaligned_le32(&data[18]);
	ctrl->dwMaxPayloadTransferSize = get_unaligned_le32(&data[22]);

	if (size == 34) {
		ctrl->dwClockFrequency = get_unaligned_le32(&data[26]);
		ctrl->bmFramingInfo = data[30];
		ctrl->bPreferedVersion = data[31];
		ctrl->bMinVersion = data[32];
		ctrl->bMaxVersion = data[33];
	} else {
		//ctrl->dwClockFrequency = video->dev->clock_frequency;
		ctrl->bmFramingInfo = 0;
		ctrl->bPreferedVersion = 0;
		ctrl->bMinVersion = 0;
		ctrl->bMaxVersion = 0;
	}

done:
    kfree(data);
    
    return (ret < 0) ? ret : 0;
}

/* 参考: uvc_v4l2_try_format ∕uvc_probe_video 
 *       uvc_set_video_ctrl(video, probe, 1)
 */
static int myuvc_try_streaming_params(struct myuvc_streaming_control *ctrl)
{
    __u8 *data;
    __u16 size;
    int ret;
	__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
	unsigned int pipe;
    
	memset(ctrl, 0, sizeof *ctrl);
    
	ctrl->bmHint = 1;	/* dwFrameInterval */
	ctrl->bFormatIndex = 1;
	ctrl->bFrameIndex  = frame_idx + 1;
	ctrl->dwFrameInterval = 333333;


    size = uvc_version >= 0x0110 ? 34 : 26;
    data = kzalloc(size, GFP_KERNEL);
    if (data == NULL)
        return -ENOMEM;

    *(__le16 *)&data[0] = cpu_to_le16(ctrl->bmHint);
    data[2] = ctrl->bFormatIndex;
    data[3] = ctrl->bFrameIndex;
    *(__le32 *)&data[4] = cpu_to_le32(ctrl->dwFrameInterval);
    *(__le16 *)&data[8] = cpu_to_le16(ctrl->wKeyFrameRate);
    *(__le16 *)&data[10] = cpu_to_le16(ctrl->wPFrameRate);
    *(__le16 *)&data[12] = cpu_to_le16(ctrl->wCompQuality);
    *(__le16 *)&data[14] = cpu_to_le16(ctrl->wCompWindowSize);
    *(__le16 *)&data[16] = cpu_to_le16(ctrl->wDelay);
    put_unaligned_le32(ctrl->dwMaxVideoFrameSize, &data[18]);
    put_unaligned_le32(ctrl->dwMaxPayloadTransferSize, &data[22]);

    if (size == 34) {
        put_unaligned_le32(ctrl->dwClockFrequency, &data[26]);
        data[30] = ctrl->bmFramingInfo;
        data[31] = ctrl->bPreferedVersion;
        data[32] = ctrl->bMinVersion;
        data[33] = ctrl->bMaxVersion;
    }

    pipe = (SET_CUR & 0x80) ? usb_rcvctrlpipe(myuvc_udev, 0)
                  : usb_sndctrlpipe(myuvc_udev, 0);
    type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;

    ret = usb_control_msg(myuvc_udev, pipe, SET_CUR, type, VS_PROBE_CONTROL << 8,
            0 << 8 | myuvc_streaming_intf, data, size, 5000);

    kfree(data);
    
    return (ret < 0) ? ret : 0;
    
}



static void myuvc_uninit_urbs(void)
{
	int i;
	for(i=0;i<MYUVC_URBS;++i)
	{
		if(myuvc_queue.urb_buffer[i])
		{

			usb_free_coherent(myuvc_udev, myuvc_queue.urb_size, myuvc_queue.urb_buffer[i], myuvc_queue.urb_dma[i]);
            myuvc_queue.urb_buffer[i] = NULL;
		}
		if (myuvc_queue.urb[i])
        {
            usb_free_urb(myuvc_queue.urb[i]);
            myuvc_queue.urb[i] = NULL;
        }
	}
	myuvc_queue.urb_size = 0;
}

/*参考:uvc_init_video_isoc*/
static int myuvc_alloc_init_urbs(void)
{
	u16 psize;
	u32 size;
	int npackets;
	int i;
    int j;
	struct urb *urb;

	psize = wMaxPacketSize;/*实时传输端点一次能传输的最大字节数*/
	size = myuvc_params.dwMaxVideoFrameSize;/*一帧数据的最大长度*/
	npackets = DIV_ROUND_UP(size, psize);	/*size/psize向上取整*/
	if(npackets>32)
	{
		npackets = 32;
	}
	size = myuvc_queue.urb_size = psize * npackets;
	for(i=0;i<MYUVC_URBS;i++)
	{
		/*1.分配URB buffers*/
		myuvc_queue.urb_buffer[i] = usb_alloc_coherent(myuvc_udev,size,GFP_KERNEL | __GFP_NOWARN,&myuvc_queue.urb_dma[i]);
		/*2.分配URB结构体*/
		myuvc_queue.urb[i] = usb_alloc_urb(npackets,GFP_KERNEL);
		if(!myuvc_queue.urb_buffer[i] || !myuvc_queue.urb[i])
		{
			myuvc_uninit_urbs();
            return -ENOMEM;
		}
	}
	
	/*3设置URB*/
	for(i = 0; i < MYUVC_URBS; ++i)
	{
		urb = myuvc_queue.urb[i];
        
        urb->dev = myuvc_udev;
        urb->context = NULL;
        urb->pipe = usb_rcvisocpipe(myuvc_udev,myuvc_bEndpointAddress);
        urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
        urb->interval = 1;
        urb->transfer_buffer = myuvc_queue.urb_buffer[i];
        urb->transfer_dma = myuvc_queue.urb_dma[i];
        urb->complete = myuvc_video_complete;	/*当一个URB来的时候回产生中断,myuvc_video_complete是处理函数*/
        urb->number_of_packets = npackets;
        urb->transfer_buffer_length = size;
		for (j = 0; j < npackets; ++j) {
            urb->iso_frame_desc[j].offset = j * psize;
            urb->iso_frame_desc[j].length = psize;
        }
	}
	return 0;
}

现在我们就设置好了URB,包含了目标设备USB摄像头和urb_buffer等信息,只要把这个URB传给USB核心,USB核心就会解析URB,与指定的USB设备传输数据,数据将被放在urb_buffer里,接收到USB设备传来的数据包时,将产生一个中断,执行中断处理函数myuvc_video_complete。
中断函数里会依次处理每个包,将包的数据放到myuvc_q.irqqueue队列首个节点所指的buf,当多个包的数据量足够一帧时,就唤醒休眠的应用层,应用层就会得到数据,最后中断程序再发送URB,再次进入中断,依次循环。

下面就是实现myuvc_video_complete,在里面首先判断之前URB传输的结果:
只有urb->status = 0才表示传输成功,否则都直接返回。

	switch (urb->status) {
	case 0:
		break;

	default:
		printk("Non-zero status (%d) in video "
			"completion handler.\n", urb->status);
		return;
	}

然后判定队列是否为空,取出首个buf,后面将从URB得到的数据放在这个buf里:

/* 从irqqueue队列中取出第1个缓冲区 */
	if (!list_empty(&myuvc_queue.irqqueue))
	{
		buf = list_first_entry(&myuvc_queue.irqqueue, struct myuvc_buffer, irq);
	}
	else
	{
		buf = NULL;
	}

之后便是对每个URB的子包进行处理:

1.判断状态urb->iso_frame_desc[i].status小于0,跳过处理该子包;
2.计算数据源(来自URB)、长度、目的地址(放到队列提取的buf);
3.判断该包数据是否有效,其中data[0]包含头部长度,data[1]包含错误状态;
4.使用摄像头厂家提供的特殊处理,完成对fid的操作;<fid介绍见下面>
5.如果buf=NULL,表示之前irqqueue队列没有空间了,没必要后续操作了;
6.判断buf->state是不是VIDEOBUF_ACTIVE(正在接收数据)状态,即是不是第一次开始接收数据,是的话改为VIDEOBUF_ACTIVE;
7.让last_fid = fid,表示要开始接收本帧数据;
8.传输的数据长度为:子包去除头部信息后的数据长度与buf剩余空间的 最小值;
9.将URB子包复制到buf中;
10.引用厂家代码,对buf数据进行某些处理;
11.当子包数据长度大于该buf剩下空间、得到标志UVC_STREAM_EOF且收到数据不为空时,表明一帧数据传完,修改buf状态VIDEOBUF_DONE;
12.从irqqueue队列删除该节点;唤醒应用层读取mainqueue队列的数据,即本帧数据;修改mem偏移和date_len,取出下一个buf;

以上就是对每个子包的操作,主要包含了子包状态的判断、对是否完成一帧传输的判断、复制子包数据到buf、厂家特殊处理、再次从队列获取buf。

讲一下fid(frame id)。
我们看到的连续视频,可以分成若干个1s的视频,再把每个1s的视频分成30份,每一份就是一张图片,称之为帧(frame)。
这个帧的数据,是由URB传输中的若干个pack组成的,在URB传输中,产生一连续的pack,我们如何知道其中的某几个pack属于某一帧的呢?
摄像头厂家的解决方案是,为每个pack也编号,属于同一帧的几个连续pack编号相同,这就实现了在pack上出现0、1交替时,就表示该帧传输完了,开始传输下一帧。
在这里插入图片描述
在中断函数的最后,还要再次提交URB,这样才能再次进入中断,拷贝数据,如此反复。(这里的中断产生是在每次URB包数据传输完后产生)

/* 参考: uvc_video_complete / uvc_video_decode_isoc */
/*将数据放入队列一遍APP使用*/
static void myuvc_video_complete(struct urb *urb)
{
	// 要修改影像資料,必須先宣告一個特別型態的指標變數,才能正確存取記憶體中的資料
    unsigned char *point_mem;
    static unsigned char *mem_temp = NULL;

    // 初始化暫存用的記憶體位置
    static unsigned int nArrayTemp_Size = 1000;

	u8 *src;
    u8 *dest;
	int ret, i;
    int len;
    int maxlen;
    int nbytes;
    struct myuvc_buffer *buf;
	static int fid;


	u8* mem;
	int data_len;

	switch (urb->status) {
	case 0:
		break;

	default:
		printk("Non-zero status (%d) in video "
			"completion handler.\n", urb->status);
		return;
	}

	 /* 从irqqueue队列中取出第1个缓冲区 */
	if (!list_empty(&myuvc_queue.irqqueue))
	{
		buf = list_first_entry(&myuvc_queue.irqqueue, struct myuvc_buffer, irq);
	}
	else
	{
		buf = NULL;
	}
	
	for (i = 0; i < urb->number_of_packets; ++i)
	{
		if (urb->iso_frame_desc[i].status < 0)
		{
			printk("USB isochronous frame "
				"lost (%d).\n", urb->iso_frame_desc[i].status);
			continue;
		}
		src  = urb->transfer_buffer + urb->iso_frame_desc[i].offset;

		len = urb->iso_frame_desc[i].actual_length;

		/* 判断数据是否有效 */
		/* URB数据含义:
			* data[0] : 头部长度
			* data[1] : 错误状态
			*/
		if(len<2 || src[0]<2 || src[0]>len)
		{
			continue;
		}

		/* Skip payloads marked with the error bit ("error frames"). */
		if (src[1] & UVC_STREAM_ERR) {
			printk("Dropping payload (error bit set).\n");
			continue;
		}
		/* ip2970/ip2977 */
		if (myuvc_udev->descriptor.idVendor == 0x1B3B)
		{
			if ( len >= 16 ) // have data in buffer
			{
				// 資料必須從data[12]開始判斷,是因為前面的資料是封包專用
				if ( (src[12]==0xFF && src[13]==0xD8 && src[14]==0xFF) ||
					(src[12]==0xD8 && src[13]==0xFF && src[14]==0xC4)) 
				{
					if(last_fid)
						fid &= ~UVC_STREAM_FID;
					else
						fid |= UVC_STREAM_FID;
				}
			}
		}
		else
		{
			fid = src[1] & UVC_STREAM_FID;
		}

		/* Store the payload FID bit and return immediately when the buffer is
		* NULL.
		*/
		if (buf == NULL) {
			last_fid = fid;
			continue;
		}


		/*根据FID判断当前帧数据是否结束*/
		/*VIDEOBUF_ACTIVE表示“正在接收数据”*/
		/*!= VIDEOBUF_ACTIVE表示之前还未接收数据*/
		if (buf->state != VIDEOBUF_ACTIVE) 
		{
			if (fid == last_fid) {
				/*刚开始接收数据,FID应该是一个新的值,不应该是原来的值last_fid*/
				continue;
			}
			/* TODO: Handle PTS and SCR. */
			/*表示开始接收第一个数据*/
			buf->state = VIDEOBUF_ACTIVE;
		}

		/*fid != last_fid表示开始新一帧*/
		if (fid != last_fid && buf->buf.bytesused != 0) 
		{
			buf->state = VIDEOBUF_DONE;

			//4.13内核是这样的buf->state = UVC_BUF_STATE_READY;
			/*从队列中删除,唤醒进程*/
			list_del(&buf->irq);
			wake_up(&buf->wait);

			mem = myuvc_queue.mem + buf->buf.m.offset;
			data_len = buf->buf.bytesused;
			/*取出下一个buf*/
			if (!list_empty(&myuvc_queue.irqqueue))
			{
				buf = list_first_entry(&myuvc_queue.irqqueue, struct myuvc_buffer, irq);
			}
			else
			{
				buf = NULL;
			}
			continue;
		}
		last_fid = fid;
		dest = myuvc_queue.mem + buf->buf.m.offset + buf->buf.bytesused;

		/* 除去头部后的数据长度 */
		len -= src[0];

		/* 缓冲区最多还能存多少数据 */
		maxlen = buf->buf.length - buf->buf.bytesused;
		nbytes = min(len, maxlen);

		/* 复制数据 */
		memcpy(dest, src + src[0], nbytes);
		buf->buf.bytesused += nbytes;

		/* ip2970/ip2977 */
		if (myuvc_udev->descriptor.idVendor == 0x1B3B)
		{
			if(mem_temp == NULL) {
				mem_temp = kmalloc(nArrayTemp_Size, GFP_KERNEL);
			}
			else if(nArrayTemp_Size <= nbytes){ // 當收到的資料長度大於上一次的資料長度,則重新分配所需的空間+
				kfree(mem_temp);
				nArrayTemp_Size += 500;
				mem_temp = kmalloc(nArrayTemp_Size, GFP_KERNEL);
			}
			memset(mem_temp, 0x00, nArrayTemp_Size);
			
			// 指向資料儲存的記憶體位置
			point_mem = (unsigned char *)dest;
			if( *(point_mem) == 0xD8 && *(point_mem + 1) == 0xFF && *(point_mem + 2) == 0xC4){
				memcpy( mem_temp + 1, point_mem, nbytes);
				mem_temp[0] = 0xFF;
				memcpy(point_mem, mem_temp, nbytes + 1);
			}
		}

		/*判断一帧数据是否已经全部接收到*/
		/*如果数据已经超过容量*/
		if(len>maxlen)
		{
			buf->state = VIDEOBUF_DONE;
		}
		/* Mark the buffer as done if the EOF marker is set. */
		if (src[1] & UVC_STREAM_EOF && buf->buf.bytesused != 0) 
		{
			printk("Frame complete (EOF found).\n");
			if (len == 0)
				printk("EOF in empty payload.\n");
			buf->state = VIDEOBUF_DONE;
		}
		/*
		*当接收完一帧数据,
		*从irqqueue中删除这个缓冲区
		*唤醒等待数据的线程*/
		if (buf->state == VIDEOBUF_DONE ||
			buf->state == VIDEOBUF_ERROR)
		{
			list_del(&buf->irq);
			wake_up(&buf->wait);

			mem = myuvc_queue.mem + buf->buf.m.offset;
			data_len = buf->buf.bytesused;
			//printk("wake_up %d : %s %d, start data: %02x %02x, end data: %02x %02x", wake_cnt++, __FUNCTION__, __LINE__, mem[0], mem[1], mem[data_len - 2], mem[data_len - 1]);
			
			/* 取出下一个buf */
			if (!list_empty(&myuvc_queue.irqqueue))
			{
				buf = list_first_entry(&myuvc_queue.irqqueue, struct myuvc_buffer, irq);
			}
			else
			{
				buf = NULL;
			}
		}
		
	}

	/*再次提交URB*/
	if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
		printk("Failed to resubmit video URB (%d).\n", ret);
	}
}

2.5启动/停止

应用层调用ioctl()传入的参数为VIDIOC_STREAMON时,就会调用vidioc_streamon()启动摄像头传输数据。
在vidioc_streamon()中主要完成三件事情:

  1. 设置USB摄像头参数;(比如使用何种视频数据格式、何种分辨率)
  2. 分配设置URB;(调用前面的my_uvc_alloc_init_urbs()函数)
  3. 提交URB,等待中断;

一般的摄像头,会支持多种格式,比如MJPEG、H264等,也会支持多种分辨率。
因此需要在开始传输前,通过USB设置摄像头,让其后面返回正确的数据。

假如我们直接设置,可能摄像头不支持我们设置的格式,后面对应的解析数据可能会出现错误。因此我们先尝试传入设置参数,摄像头接收后会保存起来,并根据自身情况做一些修正,再将该设置读取出来,再进行真正的设置。
这里我们定义一个myuvc_streaming_control结构体,用于保存这个设置过程中的参数。
首先是尝试设置参数,根据摄像头版本,对应的分配一个data空间,用于等会保存参数进行USB传输。

size = uvc_version >= 0x0110 ? 34 : 26;
    data = kzalloc(size, GFP_KERNEL);
    if (data == NULL)
        return -ENOMEM;

然后清空传入的myuvc_streaming_control结构体,设置相应参数,最后参考内核UVC驱动使用cpu_to_le16()将my_uvc_streaming_control赋值给data。

memset(ctrl, 0, sizeof *ctrl);
    
ctrl->bmHint = 1;	    //保持dwFrameInterval不变    
ctrl->bFormatIndex = 1; //支持格式数量
ctrl->bFrameIndex  = frame_idx + 1; //使用第二种分辨率:640x480(1),320x240(2),160x120(3)
ctrl->dwFrameInterval = 333333;   //lsusb: dwFrameInterval(0)  333333 每秒30帧

最后调用usb_control_msg()将data传给摄像头,这里的usb_control_msg()在前面的亮度控制详细介绍了每个参数的含义,当时使用的是VC接口,这里使用VS接口。
这里不是真正的设置,所以传入的参数是VS_PROBE_CONTROL。

 pipe = (SET_CUR & 0x80) ? usb_rcvctrlpipe(myuvc_udev, 0)
                  : usb_sndctrlpipe(myuvc_udev, 0);
    type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;

    ret = usb_control_msg(myuvc_udev, pipe, SET_CUR, type, VS_PROBE_CONTROL << 8,
            0 << 8 | myuvc_streaming_intf, data, size, 5000);

    kfree(data);

尝试设置USB之后,再讲修正后的参数读出来保存起来myuvc_streaming_control。

/* 参考: uvc_get_video_ctrl 
 (ret = uvc_get_video_ctrl(video, probe, 1, GET_CUR)) 
 static int uvc_get_video_ctrl(struct uvc_video_device *video,
     struct uvc_streaming_control *ctrl, int probe, __u8 query)
 */
static int myuvc_get_streaming_params(struct myuvc_streaming_control *ctrl)
{
	__u8 *data;
	__u16 size;
	int ret;
	__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
	unsigned int pipe;

	size = uvc_version >= 0x0110 ? 34 : 26;
	data = kmalloc(size, GFP_KERNEL);
	if (data == NULL)
		return -ENOMEM;
   
	pipe = (GET_CUR & 0x80) ? usb_rcvctrlpipe(myuvc_udev, 0)
			      : usb_sndctrlpipe(myuvc_udev, 0);
	type |= (GET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;

	ret = usb_control_msg(myuvc_udev, pipe, GET_CUR, type, VS_PROBE_CONTROL << 8,
			0 << 8 | myuvc_streaming_intf, data, size, 5000);

    if (ret < 0)
        goto done;

	ctrl->bmHint = le16_to_cpup((__le16 *)&data[0]);
	ctrl->bFormatIndex = data[2];
	ctrl->bFrameIndex = data[3];
	ctrl->dwFrameInterval = le32_to_cpup((__le32 *)&data[4]);
	ctrl->wKeyFrameRate = le16_to_cpup((__le16 *)&data[8]);
	ctrl->wPFrameRate = le16_to_cpup((__le16 *)&data[10]);
	ctrl->wCompQuality = le16_to_cpup((__le16 *)&data[12]);
	ctrl->wCompWindowSize = le16_to_cpup((__le16 *)&data[14]);
	ctrl->wDelay = le16_to_cpup((__le16 *)&data[16]);
	ctrl->dwMaxVideoFrameSize = get_unaligned_le32(&data[18]);
	ctrl->dwMaxPayloadTransferSize = get_unaligned_le32(&data[22]);

	if (size == 34) {
		ctrl->dwClockFrequency = get_unaligned_le32(&data[26]);
		ctrl->bmFramingInfo = data[30];
		ctrl->bPreferedVersion = data[31];
		ctrl->bMinVersion = data[32];
		ctrl->bMaxVersion = data[33];
	} else {
		//ctrl->dwClockFrequency = video->dev->clock_frequency;
		ctrl->bmFramingInfo = 0;
		ctrl->bPreferedVersion = 0;
		ctrl->bMinVersion = 0;
		ctrl->bMaxVersion = 0;
	}

done:
    kfree(data);
    
    return (ret < 0) ? ret : 0;
}

最后再将新的参数设置给摄像头,这样就能保证现在设置的参数对摄像头是有效的。
这里是真正的设置,所以传入的参数是VS_COMMIT_CONTROL。

/* 参考: uvc_v4l2_try_format ∕uvc_probe_video 
 *       uvc_set_video_ctrl(video, probe, 1)
 */
static int myuvc_set_streaming_params(struct myuvc_streaming_control *ctrl)
{
    __u8 *data;
    __u16 size;
    int ret;
	__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
	unsigned int pipe;
    
    size = uvc_version >= 0x0110 ? 34 : 26;
    data = kzalloc(size, GFP_KERNEL);
    if (data == NULL)
        return -ENOMEM;

    *(__le16 *)&data[0] = cpu_to_le16(ctrl->bmHint);
    data[2] = ctrl->bFormatIndex;
    data[3] = ctrl->bFrameIndex;
    *(__le32 *)&data[4] = cpu_to_le32(ctrl->dwFrameInterval);
    *(__le16 *)&data[8] = cpu_to_le16(ctrl->wKeyFrameRate);
    *(__le16 *)&data[10] = cpu_to_le16(ctrl->wPFrameRate);
    *(__le16 *)&data[12] = cpu_to_le16(ctrl->wCompQuality);
    *(__le16 *)&data[14] = cpu_to_le16(ctrl->wCompWindowSize);
    *(__le16 *)&data[16] = cpu_to_le16(ctrl->wDelay);
    put_unaligned_le32(ctrl->dwMaxVideoFrameSize, &data[18]);
    put_unaligned_le32(ctrl->dwMaxPayloadTransferSize, &data[22]);

    if (size == 34) {
        put_unaligned_le32(ctrl->dwClockFrequency, &data[26]);
        data[30] = ctrl->bmFramingInfo;
        data[31] = ctrl->bPreferedVersion;
        data[32] = ctrl->bMinVersion;
        data[33] = ctrl->bMaxVersion;
    }

    pipe = (SET_CUR & 0x80) ? usb_rcvctrlpipe(myuvc_udev, 0)
                  : usb_sndctrlpipe(myuvc_udev, 0);
    type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;

    ret = usb_control_msg(myuvc_udev, pipe, SET_CUR, type, VS_COMMIT_CONTROL << 8,
            0 << 8 | myuvc_streaming_intf, data, size, 5000);

    kfree(data);
    
    return (ret < 0) ? ret : 0;
    
}

设置VideoStreaming Interface所使用的setting
指定bAlternateSetting,bAlternateSetting用于在同一个接口中的多个描述符中进行切换。
也就是说,USB摄像头提供多种Interface Descriptor(接口),每个接口的支持一种wMaxPacketSize(带宽,一次传输提供的数据量)、dwMaxPayloadTransferSize(每帧最大数据,实测等于分辨率加512)。
当摄像头分辨率变化时,相应所需的接口也会变化,比如分辨率变大,要选择带宽更大的接口。
bAlternateSetting就相当于是接口的索引,因此不同分辨率,应该选择对应的接口。(我的摄像头是lsusb中看到时setting 5)
在print函数中得知MaxPacketSize是800

static void myuvc_print_streaming_params(struct myuvc_streaming_control *ctrl)
{
    printk("video params:\n");
    printk("bmHint                   = %d\n", ctrl->bmHint);
    printk("bFormatIndex             = %d\n", ctrl->bFormatIndex);
    printk("bFrameIndex              = %d\n", ctrl->bFrameIndex);
    printk("dwFrameInterval          = %d\n", ctrl->dwFrameInterval);
    printk("wKeyFrameRate            = %d\n", ctrl->wKeyFrameRate);
    printk("wPFrameRate              = %d\n", ctrl->wPFrameRate);
    printk("wCompQuality             = %d\n", ctrl->wCompQuality);
    printk("wCompWindowSize          = %d\n", ctrl->wCompWindowSize);
    printk("wDelay                   = %d\n", ctrl->wDelay);
    printk("dwMaxVideoFrameSize      = %d\n", ctrl->dwMaxVideoFrameSize);
    printk("dwMaxPayloadTransferSize = %d\n", ctrl->dwMaxPayloadTransferSize);
    printk("dwClockFrequency         = %d\n", ctrl->dwClockFrequency);
    printk("bmFramingInfo            = %d\n", ctrl->bmFramingInfo);
    printk("bPreferedVersion         = %d\n", ctrl->bPreferedVersion);
    printk("bMinVersion              = %d\n", ctrl->bMinVersion);
    printk("bMinVersion              = %d\n", ctrl->bMinVersion);
}
  Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       5
      bNumEndpoints           1
      bInterfaceClass        14 Video
      bInterfaceSubClass      2 Video Streaming
      bInterfaceProtocol      0
      iInterface              0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            5
          Transfer Type            Isochronous
          Synch Type               Asynchronous
          Usage Type               Data
        wMaxPacketSize     0x0320  1x 800 bytes

设置好了摄像头的format(格式)、frame(分辨率)等,就可以分配设置URB,准备和USB摄像头传输数据了。

/*2.分配设置URB*/
	ret = myuvc_alloc_init_urbs();
	if(ret)
	{
		printk("myuvc_alloc_init_urbs err : ret = %d\n", ret);
	}

分配完成后,就提交给USB核心,等待中断来临,读取摄像头发来的数据。

/*3.提交URB以接收数据*/
	for(i=0;i<MYUVC_URBS;i++)
	{
		if((ret = usb_submit_urb(myuvc_queue.urb[i],GFP_KERNEL))<0)
		{
			printk("Failed to submit URB %u (%d).\n", i, ret);
			myuvc_uninit_urbs();
			return ret;
		}
	}

停止数据采集myuvc_vidioc_streamoff()主要完成:

  1. 取消URB传输
  2. 释放URB_buf和URB
  3. 设置接口为0,让其处于休眠状态;
/*A17 停止*/
/*就是kill以及free之前提交的URB*/
static int myuvc_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type t)
{
	struct urb *urb;
	unsigned int i;
	/* 1. kill URB */
	for (i = 0; i < MYUVC_URBS; ++i) {
		if ((urb = myuvc_queue.urb[i]) == NULL)
			continue;
		usb_kill_urb(urb);
	}

	/* 2. free URB */
    myuvc_uninit_urbs();

	/*3设置VideoStreaming Interface为setting 0  使用后需要还原之前的设置*/
	usb_set_interface(myuvc_udev, myuvc_streaming_intf, 0);
    return 0;
}

2.6mmap和poll函数

首先是mmap(),前面提到应用层调用vidioc_queryctrl()时,会让驱动程序分配若干个buf,也就是my_uvc_q.buf[N];
现在我们需要做的就是把buf映射到用户空间,以后用户空间操作映射后的空间,就间接的操作了内核的my_uvc_q.buf[N]。

根据传入的vma->vm_pgoff偏移,对应找到my_uvc_q.buf,如果没找到或者大小不对,就退出。
如果找到了对应偏移的my_uvc_q.buf,就可以根据该buf的起始地址和偏移得到物理地址addr;
再将物理地址传入vmalloc_to_page()函数得到page结构体,再使用vm_insert_page()函数将page结构体和传入的vma虚拟地址绑定,以PAGE_SIZE大小分割总的size。
最后在使用计数加1,后面vidioc_querybuf()查询缓存状态时,用于更新标志。

static void myuvc_vm_open(struct vm_area_struct *vma)
{
	struct myuvc_buffer *buffer = vma->vm_private_data;
	buffer->vma_use_count++;
}

static void myuvc_vm_close(struct vm_area_struct *vma)
{
	struct myuvc_buffer *buffer = vma->vm_private_data;
	buffer->vma_use_count--;
}

static struct vm_operations_struct myuvc_vm_ops = {
	.open		= myuvc_vm_open,
	.close		= myuvc_vm_close,
};
/*A9 把缓存映射到APP的空间,以后APP就可以直接操作这块缓存*/
/*将驱动申请的缓存映射到应用空间*/
static int myuvc_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct myuvc_buffer *buffer;
    struct page *page;
    unsigned long addr, start, size;
    unsigned int i;
    int ret = 0;

    start = vma->vm_start;
    size = vma->vm_end - vma->vm_start;

	/*应用程序调用mmap函数时,传入offset参数
	 *根据这个offset找出制定的缓冲区*/
	for (i = 0; i < myuvc_queue.count; ++i) {
        buffer = &myuvc_queue.buffer[i];
        if ((buffer->buf.m.offset >> PAGE_SHIFT) == vma->vm_pgoff)
            break;
    }

    if (i == myuvc_queue.count || size != myuvc_queue.buf_size) {
        ret = -EINVAL;
        goto done;
    }

    /*
     * VM_IO marks the area as being an mmaped region for I/O to a
     * device. It also prevents the region from being core dumped.
     */
    vma->vm_flags |= VM_IO;

	/*根据虚拟地址找到缓冲区对应的page结构体*/
	addr = (unsigned long)myuvc_queue.mem + buffer->buf.m.offset;
	while (size > 0) {
        page = vmalloc_to_page((void *)addr);

        /* 把page和APP传入的虚拟地址挂构 */
        if ((ret = vm_insert_page(vma, start, page)) < 0)
            goto done;

        start += PAGE_SIZE;
        addr += PAGE_SIZE;
        size -= PAGE_SIZE;
    }

    vma->vm_ops = &myuvc_vm_ops;
    vma->vm_private_data = buffer;
    myuvc_vm_open(vma);

done:
    return ret;

}

poll:poll()函数,用来确定buf是否准备就绪,即含有数据。
应用层调用poll()时,会尝试从my_uvc_q.mainqueue队列取出首个缓冲区,得到其buf->wait,然后调用poll_wait()以wait为标志,进入休眠。等待中断myuvc_video_complete里的wake_up(),再唤醒。根据buf->state返回对应的mask,对应的应用程序就读取数据。

/*A12  app调用POLL/SELECT来确定缓存是否就绪(有数据)*/
static unsigned int myuvc_poll(struct file *file, struct poll_table_struct *wait)
{
	
	struct myuvc_buffer *buf;
	unsigned int mask = 0;
    
    /* 从mainqueuq中取出第1个缓冲区 */

    /*判断它的状态, 如果未就绪, 休眠 */
	if (list_empty(&myuvc_queue.mainqueue)) {
        mask |= POLLERR;
        goto done;
    }
    /*以stream为节点从队列中去myuvc_buffers*/
    buf = list_first_entry(&myuvc_queue.mainqueue, struct myuvc_buffer, stream);

    poll_wait(file, &buf->wait, wait);
    if (buf->state == VIDEOBUF_DONE ||
        buf->state == VIDEOBUF_ERROR)
        mask |= POLLIN | POLLRDNORM;
    
done:
    return mask;
}

至此,所有程序分析以及编写完毕

3.总结分析

框架图:在这里插入图片描述
基本概念:

  1. 应用层有5个操作函数,其中ioctl下至少有11基本的操作函数(除开设置属性的几个函数);
  2. USB摄像头有且只有一个VC借口用于控制,但是有多个VS接口用于数据传输(这里也说明一个摄像头有多重分辨率,支持多种格式)
  3. 11个基本操作函数包括:
		// 表示它是一个摄像头设备
        .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_vidioc_reqbufs,
        .vidioc_querybuf      = myuvc_vidioc_querybuf,
        .vidioc_qbuf          = myuvc_vidioc_qbuf,
        .vidioc_dqbuf         = myuvc_vidioc_dqbuf,
        
        // 启动/停止
        .vidioc_streamon      = myuvc_vidioc_streamon,
        .vidioc_streamoff     = myuvc_vidioc_streamoff, 
  1. 数据buf操作:
      a.根据应用层参数生成指定个数的v4l2_buffer,这些buf又同时在两个队列上:mianquque和irqquque;
      b.摄像头产生的数据通过VS接口和USB核心的URB,放入irqquque队列的首buf,并将该buf从该队列删除;
      c.应用层取出mianquque队列的首buf,得到数据,并将该buf从该队列删除,此时该buf同时不在两个队列上,将被重新放在尾部;

  2. 摄像头格式的操作:使用interface_to_usbdev()得到对应接口的USB设备描述符,描述符包含摄像头的各种特性信息,保存在v4l2_format结构体中;

  3. 摄像头属性的操作:使用·usb_control_msg()通过VC接口设置相关属性;

有了上面的基本概念,现在开始调用vidioc_streamon()启动传输:
1.设置USB摄像头对应带宽接口等;
2.分配usb_buffers和urb,设置urb;
3.上报urb,USB核心解析urb,向指定接口(摄像头VS接口)接收数据(放在usb_buffers);
4.urb传输完成后产生中断,中断里取出irqquque队列首buf,将usb_buffers数据放入,并唤醒休眠的poll;
5.poll唤醒,vidioc_dqbuf()从mianquque队列取出首buf,返回给应用层,完成了摄像头数据到应用层的传输。

特别注意:写这个摄像头并不是为了让我们能直接用自己的驱动代替内核提供的UVC驱动,主要是为了理解调用过程以及处理方式。除了需要添加自己特有摄像头数据外,建议使用内核所提供的的UVC驱动。
参考文章:
韦东山第三期项目视频_摄像头(http://www.100ask.org/index.html)

  • 18
    点赞
  • 103
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Zynq USB摄像头驱动是指在Xilinx公司的Zynq系列芯片上驱动USB摄像头的软件程序。Zynq系列芯片是一种结合了ARM处理器和可编程逻辑部分(FPGA)的SoC(System on Chip)芯片,具备处理器的高性能和FPGA的灵活性。 为了实现Zynq USB摄像头驱动,首先需要了解所使用的摄像头的型号和通信协议。不同型号的摄像头可能采用不同的通信协议,如UVC (USB Video Class)或者是厂商自定义的协议。 在驱动开发过程中,需要使用Linux操作系统的内核源码,并根据摄像头的通信协议进行相应的驱动程序开发。开发过程中可能需要编写各种函数,如初始化函数、帧捕获函数、图像处理函数等。初始化函数主要负责USB摄像头的硬件初始化和系统资源的分配;帧捕获函数负责从USB摄像头中获取图像帧数据;图像处理函数负责对获取到的图像帧数据进行处理、分析和渲染等操作。 在驱动编写完成后,需要将其编译成适配于Zynq系列芯片的可执行文件,并将其加载到Zynq芯片上执行。在加载和运行过程中可能需要进行设备树(DTS)的配置,以确保操作系统能够正确地识别和使用USB摄像头驱动。 总结起来,Zynq USB摄像头驱动的开发过程主要包括摄像头通信协议了解、驱动程序编写、编译和加载等步骤。通过这些步骤,可以使Zynq系列芯片能够与USB摄像头实现通信,并获取到摄像头的图像数据,为后续图像处理和分析等应用提供基础支持。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值