第三阶段应用层——2.4 视频监控—从0写USB摄像头驱动(2)-实现数据传输(初步)

视频监控—从0写USB摄像头驱动(2)-实现数据传输(初步)

  • 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
  • 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
  • 参考资料:USB_Video_Example 1.5、UVC 1.5 Class specification
  • 开发环境:Linux-4.13.0-41内核(虚拟机)、arm-linux-gcc-4.3.2工具链
  • 源码仓库:https://gitee.com/d_1254436976/Embedded-Linux-Phase-3


一、前言

通过【2.1 视频监控—V4L2框架的简单分析】【2.3 视频监控—uvc驱动框架分析】两篇博文的介绍我们可以知道,对于一个USB摄像头驱动,编写步骤主要如下:

  1. 构造结构体:usb_driver结构体

  2. 设置结构体:设置其成员变量
    .name = xxx,
    .disconnect = xxx_disconnet,
    .id_table = xxx,
    .probe = xxx_probe,

    xxx_probe()中进行
      2.1. 分配video_device结构体 = video_device_alloc
      2.2. 设置video_device结构体
          .fops
          .ioctl_ops (里面需要设置11项)
         如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops
       2.3. 注册video_device结构体: video_register_device

  3. 注册结构体usb_register()

在实际的工作和应用中,尽量使用内核带有的驱动程序,对于没有适配的驱动程序才自己开发,这个usb驱动程序只是学习的时候使用。


二、程序编写


1、 初步框架

1.1 构造usb_driver结构体

struct usb_driver myuvc_driver = {
	.name		= "myuvcvideo",
	.probe		= myuvc_probe,
	.disconnect	= myuvc_disconnect,
	.id_table	= myuvc_ids,
};

1.2 设置usb_driver结构体

/*!
 * 所支持usb设备类的接口
 */
static struct usb_device_id myuvc_ids[] = {
	/* Generic USB Video Class */
	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },	 /**< VideoControl interface */
	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 2, 0) }, /**< VideoStreaming interface */
	{}
};

static int myuvc_probe(struct usb_interface *intf,
		     const struct usb_device_id *id)
{	
	static int s_cnt = 0;
	
	printk("myuvc_probe : cnt = %d\n", s_cnt++);

	/*!
	 * myuvc_probe()调用第二次后执行
	 */
    if (s_cnt == 2) {
		/* 1、分配一个video_device结构体 */
		s_myuvc_vdev = video_device_alloc();

		/* 2、设置 */
		/* 注册过程需要用到release,必须设置 */
		s_myuvc_vdev->release     = myuvc_release;

		s_myuvc_vdev->fops        = &myuvc_fops;

		s_myuvc_vdev->ioctl_ops	  = &myuvc_ioctl_ops;

		/* 3、注册结构体 
		 * -1 - 自动分配次设备号
		 */
		video_register_device(s_myuvc_vdev, VFL_TYPE_GRABBER, -1);
	}
	
    return 0;
}

static void myuvc_disconnect(struct usb_interface *intf)
{
	static int s_cnt = 0;

	printk("myuvc_disconnect : cnt = %d\n", s_cnt++);
	
	/*!
	 * myuvc_disconnect()调用第二次后执行
	 */
	if (s_cnt == 2) {
		/* 注销结构体 */
		video_unregister_device(s_myuvc_vdev);
		
		/* 释放结构体 */
		video_device_release(s_myuvc_vdev);		
	}
}

1.3 注册usb_driver结构体

static int myuvc_init(void)
{
	int result;
	
	result = usb_register(&myuvc_driver);
	if (result < 0)
		printk("USB register error!\n");
	return result;
}

1.4 完整框架

/*******************************************************************************
 * Copyleft (c) 2021 Kcode
 *
 * @file    myuvc.c
 * @brief   实现USB摄像头的数据传输(框架)
 * @author  K
 * @version 0.0.1
 * @date    2021-07-21
 * @license MulanPSL-1.0
 *
 * 文件修改历史:
 * <时间>         | <版本>      | <作者>    | <描述>
 * 2021-07-21   | v0.0.1    | Kcode   | 实现USB摄像头的数据传输(框架)
 * -----------------------------------------------------------------------------
 ******************************************************************************/

#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/videodev2.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
#include <asm/atomic.h>
#include <asm/unaligned.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-common.h>

static struct video_device *s_myuvc_vdev;	/**< video */

/*!
 * 所支持usb设备类的接口
 */
static struct usb_device_id myuvc_ids[] = {
	/* Generic USB Video Class */
	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },	 /**< VideoControl interface */
	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 2, 0) }, /**< VideoStreaming interface */
	{}
};

/*!
 * Step1  - 打开myuvc_fops设备文件
 */
static int myuvc_open(struct file *file)
{
	return 0;
}

/*!
 * 关闭myuvc_fops设备文件
 */
static int myuvc_close(struct file *file)
{
	return 0;
}

/*!
 * Step2 - 查询是否为USB摄像头设备
 */
static int myuvc_vidioc_querycap(struct file *file, void  *priv,
					struct v4l2_capability *cap)
{
	return 0;
}

/*!
 * Step3 - 列举USB摄像头设备所支持的格式format
 */
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, 
							void *priv, struct v4l2_fmtdesc *f)
{
	return 0;
}

/*!
 * Step4 - 返回当前所使用的格式
 */
static int myuvc_vidioc_g_fmt_vid_cap(struct file *file, 
							void *priv, struct v4l2_format *f)
{
	return (0);
}

/*!
 * Step5 - 测试驱动程序是否支持某种格式
 */
static int myuvc_vidioc_try_fmt_vid_cap(struct file *file,
							void *priv, struct v4l2_format *f)
{
	return 0;
}

/*!
 * Step6 - 设置所支持的格式
 */
static int myuvc_vidioc_s_fmt_vid_cap(struct file *file,
							void *priv, struct v4l2_format *f)
{	
	return 0;
}

/*!
 * Step7 - 为该设备申请若干个缓冲区,分配头部信息
 */
static int myuvc_vidioc_reqbufs(struct file *file,
						void *priv, struct v4l2_requestbuffers *p)
{
	return 0;
}

/*!
 * Step8 - 查询缓冲区的信息,如大小、偏移地址等
 * 		得到信息后,APP可mmap进行地址映射,分配真正的存储数据的缓冲区
 */
static int myuvc_vidioc_querybuf(struct file *file, 
								void *priv, struct v4l2_buffer *p)
{
	return 0;
}

/*!
 * Step9 - APPmmap进行地址映射可直接操作这块内存,分配真正的存储数据的缓冲区
 */
static int myuvc_mmap(struct file *file, struct vm_area_struct *vma)
{
	return 0;
}

/*!
 * Step10 - 把申请的缓冲区放入队列,底层的硬件操作函数将会把数据放入队列
 */
static int myuvc_vidioc_qbuf(struct file *file, 
								void *priv, struct v4l2_buffer *p)
{
	return 0;
}

/*!
 * Step11 - 启动数据传输
 */
static int myuvc_vidioc_streamon(struct file *file, 
									void *priv, enum v4l2_buf_type i)
{
	return 0;
}

/*!
 * Step12 - APP调用poll/select确定缓存是否有数据
 */
static unsigned int myuvc_poll(struct file *file, 
									struct poll_table_struct *wait)
{
	return 0;
}


/*!
 * Step13 - APP通过poll/select确定缓冲区有数据后,从队列中取出并删除缓冲区
 */
static int myuvc_vidioc_dqbuf(struct file *file,
								void *priv, struct v4l2_buffer *p)
{
	return 0;
}

/*!
 * Step14 - APP已经mmap映射缓存,可直接读数据
 * Step15 - 再次调用myuvc_vidioc_dqbuf(),把缓存尾插法放入队列
 * Step16 - 在其调用myuvc_poll()
 */

/*!
 * Step17 - 不使用时,停止摄像头数据传输
 */
static int myuvc_vidioc_streamoff(struct file *file, 
									void *priv, enum v4l2_buf_type i)
{
	return 0;
}

/*!
 * 所支持的ioclt函数
 */
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_streamon      = myuvc_vidioc_streamon,
	.vidioc_streamoff     = myuvc_vidioc_streamoff,   
};

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

static void myuvc_release(struct video_device *vdev)
{}

static int myuvc_probe(struct usb_interface *intf,
		     const struct usb_device_id *id)
{	
	static int s_cnt = 0;
	
	printk("myuvc_probe : cnt = %d\n", s_cnt++);

	/*!
	 * myuvc_probe()调用第二次后执行
	 */
    if (s_cnt == 2) {
		/* 1、分配一个video_device结构体 */
		s_myuvc_vdev = video_device_alloc();

		/* 2、设置 */
		/* 注册过程需要用到release,必须设置 */
		s_myuvc_vdev->release     = myuvc_release;

		s_myuvc_vdev->fops        = &myuvc_fops;

		s_myuvc_vdev->ioctl_ops	  = &myuvc_ioctl_ops;

		/* 3、注册结构体 
		 * -1 - 自动分配次设备号
		 */
		video_register_device(s_myuvc_vdev, VFL_TYPE_GRABBER, -1);
	}
	
    return 0;
}
			 
static void myuvc_disconnect(struct usb_interface *intf)
{
	static int s_cnt = 0;

	printk("myuvc_disconnect : cnt = %d\n", s_cnt++);
	
	/*!
	 * myuvc_disconnect()调用第二次后执行
	 */
	if (s_cnt == 2) {
		/* 注销结构体 */
		video_unregister_device(s_myuvc_vdev);
		
		/* 释放结构体 */
		video_device_release(s_myuvc_vdev);		
	}
}

struct usb_driver myuvc_driver = {
	.name		= "myuvcvideo",
	.probe		= myuvc_probe,
	.disconnect	= myuvc_disconnect,
	.id_table	= myuvc_ids,
};

static int myuvc_init(void)
{
	int result;
	
	result = usb_register(&myuvc_driver);
	if (result < 0)
		printk("USB register error!\n");
	return result;
}

static void myuvc_cleanup(void)
{	
	usb_deregister(&myuvc_driver);
}

module_init(myuvc_init);
module_exit(myuvc_cleanup);

MODULE_LICENSE("GPL");

2、实现初步数据传输

2.1 程序的整体调用顺序

  1. 在这个程序中,注册了usb_driver结构体,把驱动加载到内核中
  2. 插入USB摄像头,内核自动从该usb_driver结构体的.id_table中找到是否支持这个接口
  3. 支持该设备接口后调用usb_driver结构体的.probe函数
    3.1. 分配、设置、注册video_device结构体
    3.2. 调用自定义的.vidioc_streamon的ioclt函数:测试、设置、打印USB摄像头的参数

通过打印出来的USB摄像头参数来调试程序

2.2 缓冲区的分配与运作问题

  • 存储缓冲区信息的结构体
/*!
 * 缓冲区的信息
 */
typedef struct myuvc_buffer {
    int state;                      /**< 状态位 */
    int vma_use_count;              /**< 是否已经被mmap */
	struct v4l2_buffer buf;         /**< 存储每个缓冲区的查询信息 */
    wait_queue_head_t wait;         /**< APP读取某个缓冲区,如果无数据,在此休眠 */
    struct list_head stream;        /**< mainqueue队列结点,供APP消费用 */
    struct list_head irq;           /**< irqqueue队列结点,供底层驱动生成数据用*/
}MYUVC_BUFFER_S; 

/*!
 * 存储分配的整块缓冲区
 */
typedef struct mvuvc_video_queue {
    int count;                          /**< 分配缓冲区个数 */
    int buf_size;                       /**< 每个缓冲区(页对齐)大小 */
	void *mem;                          /**< 存储分配的内存 */
	MYUVC_BUFFER_S buffer[32];          /**< 存储每个缓冲区的信息 */
    struct list_head mainqueue;         /**< mainqueue队列头结点,供APP消费用 */
    struct list_head irqqueue;          /**< irqqueue队列头结点,供底层驱动生成用*/
}MYUVC_VIDEO_QUEUE_S;
  1. .vidioc_reqbufs函数中分配的是整个大的缓存区MYUVC_VIDEO_QUEUE_S.mem:缓冲区的数量MYUVC_VIDEO_QUEUE_S.count x 每个缓冲区头部MYUVC_VIDEO_QUEUE_S.buffer[ ]

  2. 每个缓冲区都需要进入两个队列mainqueue队列(供APP消费用)和irqqueue队列(供底层驱动生成用),其实际操作如下:
    在这里插入图片描述

    扫描二维码关注公众号,回复: 11533182 查看本文章
  • 函数实现
/*!
 * @brief  Step7 - 为该设备申请若干个缓冲区,分配头部信息
 * @return   正数:返回成功分配内存的大小,负数:分配失败
 */
static int myuvc_vidioc_reqbufs(struct file *file,
						void *priv, struct v4l2_requestbuffers *p)
{
	int buf_num   = p->count ;
	int buf_size_unalign = s_myuvc_format.fmt.pix.sizeimage;
	int buf_size_align    = PAGE_ALIGN(s_myuvc_format.fmt.pix.sizeimage);
	unsigned int i;
	void *mem = NULL;
	int ret;

	if (buf_num > UVC_MAX_VIDEO_BUFFERS)
		buf_num = UVC_MAX_VIDEO_BUFFERS;

	/* 释放之前分配的缓存 */
	if ((ret = myuvc_free_buffers()) < 0)
		goto done;

	/* 如果不分配缓冲区,则退出 */
	if (buf_num == 0)
		goto done;

	/* 减少缓冲区的数量,直到分配成功 */
	for (; buf_num > 0; --buf_num) {
		mem = vmalloc_32(buf_num * buf_size_align);
		if (mem != NULL)
			break;
	}

	if (mem == NULL) {
		ret = -ENOMEM;
		goto done;
	}

	memset(&s_myuvc_queue, 0, sizeof(s_myuvc_queue));
	
	/*!
	 * 初始化mainqueue和irqqueue队列
     */
    INIT_LIST_HEAD(&s_myuvc_queue.mainqueue);
    INIT_LIST_HEAD(&s_myuvc_queue.irqqueue);
    
    /*!
	 * 缓存是一次性分配一个大的整体
	 * 需分别设置每个缓存的信息
	 */
	for (i = 0; i < buf_num; ++i) {
		s_myuvc_queue.buffer[i].buf.index     = i;
		s_myuvc_queue.buffer[i].buf.m.offset = i * buf_size_align;
		s_myuvc_queue.buffer[i].buf.length    = buf_size_unalign;
		s_myuvc_queue.buffer[i].buf.type      = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		s_myuvc_queue.buffer[i].buf.sequence = 0;
		s_myuvc_queue.buffer[i].buf.field     = V4L2_FIELD_NONE;
		s_myuvc_queue.buffer[i].buf.memory    = V4L2_MEMORY_MMAP;
		s_myuvc_queue.buffer[i].buf.flags     = 0;

        /* 空闲状态 */
		s_myuvc_queue.buffer[i].state         = VIDEOBUF_IDLE;

        /* 初始化队列 */
		init_waitqueue_head(&s_myuvc_queue.buffer[i].wait);
	}

	s_myuvc_queue.mem = mem;
	s_myuvc_queue.count = buf_num;
	s_myuvc_queue.buf_size = buf_size_align;
	ret = buf_num;

done:
	return ret;
}

/*!
 * @brief  Step10 - 把申请的缓冲区放入队列,底层的硬件操作函数将会把数据放入队列
 */
static int myuvc_vidioc_qbuf(struct file *file, 
								void *priv, struct v4l2_buffer *v4l2_buf)
{
    struct myuvc_buffer *buf = &s_myuvc_queue.buffer[v4l2_buf->index];
    
    /*!
     * 修改状态:处于队列状态,且缓冲区数据为空
     */
    buf->state = VIDEOBUF_QUEUED;
    v4l2_buf->bytesused = 0;

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

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

	return 0;
}

2.3 USB摄像头控制信息的测试、取出、设置

对于一个usb设备,需要调用usb_control_msg():允许一个驱动发送和结束USB控制信息

  1. 测试该设备是否支持指定的参数:这些参数已经在【2.4 视频监控—从0写USB摄像头驱动(1)-描述符的分析与打印】已获得
    1.1 需要先设置好参数,
    1.2 调用usb_control_msg()发送这个设置好的数据包
/*!
 * @brief  发送数据包参数,测试设备是否支持
 * @return 0:成功,负数:错误
 */
static int myuvc_try_streaming_params(MYUVC_STREAMING_CONTROL_S *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  = s_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(s_myuvc_udev, 0)
                  : usb_sndctrlpipe(s_myuvc_udev, 0);
    type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;

    /*!
     * 后发送数据
     */
    ret = usb_control_msg(s_myuvc_udev, pipe, SET_CUR, type, VS_PROBE_CONTROL << 8,
            0 << 8 | s_myuvc_streaming_intf, data, size, 5000);

    kfree(data);
    
    return (ret < 0) ? ret : 0;
}
  1. 取出设备支持的参数: 由于已经测试了设备所支持的参数,现在需要获得测试中成功的参数
    2.1 调用usb_control_msg()获得数据包
    2.2 根据数据包设置参数
/*!
 * @brief  根据数据包的数据获得当前设备的参数
 * @return 0:成功,负数:错误
 */
static int myuvc_get_streaming_params(MYUVC_STREAMING_CONTROL_S *ctrl)
{
    __u8 *data;
    __u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
	__u16 size;
	int ret = 0;
    unsigned int pipe;  /**< 端点 */

    /*!
     * 根据uvc设备版本设置数据宽度
     */
	size = uvc_version >= 0x0110 ? 34 : 26;
	data = kmalloc(size, GFP_KERNEL);
	if (data == NULL)
		return -ENOMEM;

    /* 确定端点 */
    pipe = (GET_CUR & 0x80) ? usb_rcvctrlpipe(s_myuvc_udev, 0)
                  : usb_sndctrlpipe(s_myuvc_udev, 0);

    /* 确定类型 */
    type |= (GET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;

    ret = usb_control_msg(s_myuvc_udev, pipe, GET_CUR, type, VS_PROBE_CONTROL << 8,
            0 << 8 | s_myuvc_streaming_intf, data, size, 500);
    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;
}
  1. 设置设备支持的参数:上述步骤已经获得了设备所支持的参数,现在要把参数设置进设备
    3.1 根据上述获得的参数设置数据包
    3.2 调用usb_control_msg(),发送数据包
/*!
 * @brief  设置数据包参数,测试设备是否支持
 * @return 0:成功,负数:错误
 */
static int myuvc_set_streaming_params(MYUVC_STREAMING_CONTROL_S *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(s_myuvc_udev, 0)
                  : usb_sndctrlpipe(s_myuvc_udev, 0);
    type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;

    /*!
     * 后发送数据
     */
    ret = usb_control_msg(s_myuvc_udev, pipe, SET_CUR, type, VS_COMMIT_CONTROL << 8,
            0 << 8 | s_myuvc_streaming_intf, data, size, 5000);

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

2.4 USB摄像头视频流参数设置

由于这些参数已经在【2.4 视频监控—从0写USB摄像头驱动(1)-描述符的分析与打印】已获得直接计算好对应参数,调用usb_set_interface()函数即可

    /*!
     * 手工确定:
     * bandwidth = s_myuvc_params.dwMaxPayloadTransferSize = 1024
     * 观察lsusb -v -d 0x1e4e:的结果:
     *                wMaxPacketSize     0x0400  1x 1024 bytes
     * bAlternateSetting       8
     */
    usb_set_interface(s_myuvc_udev, s_myuvc_streaming_intf, s_myuvc_streaming_setting);

3、完整函数

/*******************************************************************************
 * Copyleft (c) 2021 Kcode
 *
 * @file    myuvc.c
 * @brief   实现USB摄像头的数据传输(设置参数)
 * @author  K
 * @version 0.0.1
 * @date    2021-07-22
 * @license MulanPSL-1.0
 *
 * 文件修改历史:
 * <时间>       | <版本>    | <作者>  | <描述>
 * 2021-07-22   | v0.0.1    | Kcode   | 实现USB摄像头的数据传输(设置参数)
 * -----------------------------------------------------------------------------
 ******************************************************************************/

#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/videodev2.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
#include <asm/atomic.h>
#include <asm/unaligned.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-common.h>
#include <media/videobuf-core.h>

#include "uvcvideo.h"

#define MYDRIVER_VERSION_NUMBER     1		/**< myuvc版本 */

/*!
 * 分辨率描述
 */
typedef struct frame_desc {
	int width;		/**< x分辨率 */
	int height;		/**< y分辨率 */
} FRAME_DESC_S;

/*!
 * uvc数据流控制结构体
 */
typedef struct myuvc_streaming_control {
	__u16 bmHint;
	__u8  bFormatIndex;
	__u8  bFrameIndex;
	__u32 dwFrameInterval;
	__u16 wKeyFrameRate;
	__u16 wPFrameRate;
	__u16 wCompQuality;
	__u16 wCompWindowSize;
	__u16 wDelay;
	__u32 dwMaxVideoFrameSize;
	__u32 dwMaxPayloadTransferSize;
	__u32 dwClockFrequency;
	__u8  bmFramingInfo;
	__u8  bPreferedVersion;
	__u8  bMinVersion;
	__u8  bMaxVersion;
}MYUVC_STREAMING_CONTROL_S;

/*!
 * 缓冲区的信息
 */
typedef struct myuvc_buffer {
    int state;                      /**< 状态位 */
    int vma_use_count;              /**< 是否已经被mmap */
	struct v4l2_buffer buf;         /**< 存储每个缓冲区的查询信息 */
    wait_queue_head_t wait;         /**< APP读取某个缓冲区,如果无数据,在此休眠 */
    struct list_head stream;        /**< mainqueue队列结点,供APP消费用 */
    struct list_head irq;           /**< irqqueue队列结点,供底层驱动生成用*/
}MYUVC_BUFFER_S; 

/*!
 * 存储分配的整块缓冲区
 */
typedef struct mvuvc_video_queue {
    int count;                          /**< 分配缓冲区个数 */
    int buf_size;                       /**< 每个缓冲区(页对齐)大小 */
	void *mem;                          /**< 存储分配的内存 */
	MYUVC_BUFFER_S buffer[32];          /**< 存储每个缓冲区的信息 */
    struct list_head mainqueue;         /**< mainqueue队列头结点,供APP消费用 */
    struct list_head irqqueue;          /**< irqqueue队列头结点,供底层驱动生成用*/
}MYUVC_VIDEO_QUEUE_S;

static int uvc_version = 0x100;             /**< 手工查看知道 */
static int s_myuvc_streaming_intf;
static int s_myuvc_streaming_setting = 8;
static int s_myuvc_control_intf;
static struct video_device *s_myuvc_vdev;
static struct v4l2_format s_myuvc_format;	/**< USB摄像头的format */
static struct usb_device *s_myuvc_udev;
static MYUVC_VIDEO_QUEUE_S s_myuvc_queue;	/**< 存放分配的一整块缓冲区 */
static MYUVC_STREAMING_CONTROL_S s_myuvc_params;    /**< uvc数据流解析 */

static int s_frame_idx   = 1;
static int s_pixel_bits  = 16;				/**< USB摄像头像素位 */
static int s_frame_index = 1;				/**< 指定分辨率数组下标 */

static FRAME_DESC_S s_frame_arr[] = {
	{640, 480},
	{352, 288},
	{320, 240},
	{176, 144},
	{160, 120},
};	/**< 该USB摄像头所有支持的分辨率 */

/*!
 * 所支持usb设备类的接口
 */
static struct usb_device_id myuvc_ids[] = {
	/* Generic USB Video Class */
	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },	/**< VideoControl interface */
	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 2, 0) },	/**< VideoStreaming interface */
	{}
};

/*!
 * @brief  Step1  - 打开myuvc_fops设备文件
 */
static int myuvc_open(struct file *file)
{
	return 0;
}

/*!
 * 关闭myuvc_fops设备文件
 */
static int myuvc_close(struct file *file)
{
	return 0;
}

/*!
 * @brief  Step2 - 查询是否为USB摄像头设备
 */
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 = MYDRIVER_VERSION_NUMBER;

	/*!
	 * V4L2_CAP_VIDEO_CAPTURE - 设备为视频捕捉设备
	 * V4L2_CAP_STREAMING     - 使用ioctl来读视频数据
	 */
	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;

	return 0;
}

/*!
 * @brief  Step3 - 列举USB摄像头设备所支持的格式format
 */
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, 
							void *priv, struct v4l2_fmtdesc *f)
{
	/*!
	 * 当前USB摄像头只支持一种格式format(命令查看描述符信息可知)
	 */
	if (f->index >= 1)
		return -EINVAL;

	/*!
	 * 格式:VS_FORMAT_UNCOMPRESSED(不压缩原始数据)
	 * GUID:59 55 59 32 00 00 10 00 80 00 00 aa 00 38 9b 71 10
	 * 参考:uvc_fmts[]得到 最终格式宏定义V4L2_PIX_FMT_YUYV
	 */
	strcpy(f->description, "4:2:2, packed, YUYV");
	f->pixelformat = V4L2_PIX_FMT_YUYV;

	return 0;
}

/*!
 * @brief  Step4 - 返回当前所使用的格式
 */
static int myuvc_vidioc_g_fmt_vid_cap(struct file *file, 
							void *priv, struct v4l2_format *f)
{
	memcpy(f, &s_myuvc_format, sizeof(s_myuvc_format));
	return 0;
}

/*!
 * @brief  Step5 - 测试驱动程序是否支持某种格式,强制设定格式
 */
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) || \
		(f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV))
		return -EINVAL;

	/*!
	 * 手工确定分辨率、像素位、图片大小信息
	 */
	f->fmt.pix.width  = s_frame_arr[s_frame_index].width;
	f->fmt.pix.height = s_frame_arr[s_frame_index].height;

	f->fmt.pix.bytesperline = (f->fmt.pix.width * s_pixel_bits) >> 3;
	f->fmt.pix.sizeimage    = f->fmt.pix.height * f->fmt.pix.width;
	
	return 0;
}

/*!
 * @brief  Step6 - 设置所支持的格式
 */
static int myuvc_vidioc_s_fmt_vid_cap(struct file *file,
							void *priv, struct v4l2_format *f)
{
	int ret;

	/*!
	 * 测试是否支持该格式(强制设置格式)
	 */
	ret = myuvc_vidioc_try_fmt_vid_cap(file, NULL, f);	
	if (ret < 0)
		return ret;
	
	memcpy(&s_myuvc_format, f, sizeof(s_myuvc_format));

	return 0;
}

/*!
 * @brief  释放分配的缓冲区
 */
int myuvc_free_buffers(void)
{
    kfree(s_myuvc_queue.mem);
    memset(&s_myuvc_queue, 0, sizeof(s_myuvc_queue));
	return 0;
}

/*!
 * @brief  Step7 - 为该设备申请若干个缓冲区,分配头部信息
 * @return   正数:返回成功分配内存的大小,负数:分配失败
 */
static int myuvc_vidioc_reqbufs(struct file *file,
						void *priv, struct v4l2_requestbuffers *p)
{
	int buf_num   = p->count ;
	int buf_size_unalign = s_myuvc_format.fmt.pix.sizeimage;
	int buf_size_align    = PAGE_ALIGN(s_myuvc_format.fmt.pix.sizeimage);
	unsigned int i;
	void *mem = NULL;
	int ret;

	if (buf_num > UVC_MAX_VIDEO_BUFFERS)
		buf_num = UVC_MAX_VIDEO_BUFFERS;

	/* 释放之前分配的缓存 */
	if ((ret = myuvc_free_buffers()) < 0)
		goto done;

	/* 如果不分配缓冲区,则退出 */
	if (buf_num == 0)
		goto done;

	/* 减少缓冲区的数量,直到分配成功 */
	for (; buf_num > 0; --buf_num) {
		mem = vmalloc_32(buf_num * buf_size_align);
		if (mem != NULL)
			break;
	}

	if (mem == NULL) {
		ret = -ENOMEM;
		goto done;
	}

	memset(&s_myuvc_queue, 0, sizeof(s_myuvc_queue));
	
	/*!
	 * 初始化mainqueue和irqqueue队列
     */
    INIT_LIST_HEAD(&s_myuvc_queue.mainqueue);
    INIT_LIST_HEAD(&s_myuvc_queue.irqqueue);
    
    /*!
	 * 缓存是一次性分配一个大的整体
	 * 需分别设置每个缓存的信息
	 */
	for (i = 0; i < buf_num; ++i) {
		s_myuvc_queue.buffer[i].buf.index     = i;
		s_myuvc_queue.buffer[i].buf.m.offset = i * buf_size_align;
		s_myuvc_queue.buffer[i].buf.length    = buf_size_unalign;
		s_myuvc_queue.buffer[i].buf.type      = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		s_myuvc_queue.buffer[i].buf.sequence = 0;
		s_myuvc_queue.buffer[i].buf.field     = V4L2_FIELD_NONE;
		s_myuvc_queue.buffer[i].buf.memory    = V4L2_MEMORY_MMAP;
		s_myuvc_queue.buffer[i].buf.flags     = 0;

        /* 空闲状态 */
		s_myuvc_queue.buffer[i].state         = VIDEOBUF_IDLE;

        /* 初始化队列 */
		init_waitqueue_head(&s_myuvc_queue.buffer[i].wait);
	}

	s_myuvc_queue.mem = mem;
	s_myuvc_queue.count = buf_num;
	s_myuvc_queue.buf_size = buf_size_align;
	ret = buf_num;

done:
	return ret;
}

/*!
 * @brief  Step8 - 查询指定缓冲区的信息,如大小、偏移地址等
 *          得到信息后,APP可mmap进行地址映射,分配真正的存储数据的缓冲区
 * @return  0:成功,负数:失败
 */
static int myuvc_vidioc_querybuf(struct file *file, 
								void *priv, struct v4l2_buffer *v4l2_buf)
{
    int ret = 0;

    if (v4l2_buf->index >= s_myuvc_queue.count) {
        ret = -EINVAL;
        goto done;
    }

    /* 拷贝该缓冲区的状态信息 */
    memcpy(v4l2_buf, &s_myuvc_queue.buffer[v4l2_buf->index].buf,
                sizeof(*v4l2_buf));

    /*!
     * 若已经该缓冲区已被mmap,则更新状态
     */
    if (s_myuvc_queue.buffer[v4l2_buf->index].vma_use_count)
        v4l2_buf->flags |= V4L2_BUF_FLAG_MAPPED;

    /*!
     * 更新状态
     */
	switch (s_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;
}

/*!
 * @brief  Step9 - APPmmap进行地址映射可直接操作这块内存,分配真正的存储数据的缓冲区
 * @return 0:成功
 */
static int myuvc_mmap(struct file *file, struct vm_area_struct *vma)
{
    return 0;
}

/*!
 * @brief  Step10 - 把申请的缓冲区放入队列,底层的硬件操作函数将会把数据放入队列
 */
static int myuvc_vidioc_qbuf(struct file *file, 
								void *priv, struct v4l2_buffer *v4l2_buf)
{
    struct myuvc_buffer *buf = &s_myuvc_queue.buffer[v4l2_buf->index];
    
    /*!
     * 修改状态:处于队列状态,且缓冲区数据为空
     */
    buf->state = VIDEOBUF_QUEUED;
    v4l2_buf->bytesused = 0;

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

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

	return 0;
}

/*!
 * 打印设置参数
 */
static void myuvc_print_streaming_params(MYUVC_STREAMING_CONTROL_S *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);
}

/*!
 * @brief  根据数据包的数据获得当前设备的参数
 * @return 0:成功,负数:错误
 */
static int myuvc_get_streaming_params(MYUVC_STREAMING_CONTROL_S *ctrl)
{
    __u8 *data;
    __u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
	__u16 size;
	int ret = 0;
    unsigned int pipe;  /**< 端点 */

    /*!
     * 根据uvc设备版本设置数据宽度
     */
	size = uvc_version >= 0x0110 ? 34 : 26;
	data = kmalloc(size, GFP_KERNEL);
	if (data == NULL)
		return -ENOMEM;

    /* 确定端点 */
    pipe = (GET_CUR & 0x80) ? usb_rcvctrlpipe(s_myuvc_udev, 0)
                  : usb_sndctrlpipe(s_myuvc_udev, 0);

    /* 确定类型 */
    type |= (GET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;

    ret = usb_control_msg(s_myuvc_udev, pipe, GET_CUR, type, VS_PROBE_CONTROL << 8,
            0 << 8 | s_myuvc_streaming_intf, data, size, 500);
    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;
}

/*!
 * @brief  发送数据包参数,测试设备是否支持
 * @return 0:成功,负数:错误
 */
static int myuvc_try_streaming_params(MYUVC_STREAMING_CONTROL_S *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  = s_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(s_myuvc_udev, 0)
                  : usb_sndctrlpipe(s_myuvc_udev, 0);
    type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;

    /*!
     * 后发送数据
     */
    ret = usb_control_msg(s_myuvc_udev, pipe, SET_CUR, type, VS_PROBE_CONTROL << 8,
            0 << 8 | s_myuvc_streaming_intf, data, size, 5000);

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

/*!
 * @brief  设置数据包参数,测试设备是否支持
 * @return 0:成功,负数:错误
 */
static int myuvc_set_streaming_params(MYUVC_STREAMING_CONTROL_S *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(s_myuvc_udev, 0)
                  : usb_sndctrlpipe(s_myuvc_udev, 0);
    type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;

    /*!
     * 后发送数据
     */
    ret = usb_control_msg(s_myuvc_udev, pipe, SET_CUR, type, VS_COMMIT_CONTROL << 8,
            0 << 8 | s_myuvc_streaming_intf, data, size, 5000);

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


/*!
 * @brief  Step11 - 启动数据传输
 */
static int myuvc_vidioc_streamon(struct file *file, 
									void *priv, enum v4l2_buf_type i)
{
    int ret = 0;
    
    /*!
     * 1. 向USB摄像头设置参数: 比如使用哪个format, 使用这个format下的哪个frame(分辨率) 
     * 参考: uvc_set_video_ctrl / uvc_get_video_ctrl
     * 1.1 根据一个结构体uvc_streaming_control设置数据包: 可以手工设置,也可以读出后再修改
     * 1.2 调用usb_control_msg发出数据包
     */
     
    /* a.测试参数 */
    ret = myuvc_try_streaming_params(&s_myuvc_params);
    if (ret < 0)
        printk("myuvc_try_streaming_params ret : %d\n", ret);

    /* b.取出参数 */
    ret = myuvc_get_streaming_params(&s_myuvc_params);
    if (ret < 0)
        printk("myuvc_get_streaming_params ret : %d\n", ret);

    /* c.设置参数 */
    ret = myuvc_set_streaming_params(&s_myuvc_params);
    if (ret < 0)
        printk("myuvc_set_streaming_params ret : %d\n", ret);
    
    myuvc_print_streaming_params(&s_myuvc_params);

    /*!
     * d. 设置VideoStreaming Interface所使用的setting
     * d.1 从myuvc_params确定带宽
     * d.2 根据setting的endpoint能传输的wMaxPacketSize
     *     找到能满足该带宽的setting
     */
    /*!
     * 手工确定:
     * bandwidth = s_myuvc_params.dwMaxPayloadTransferSize = 1024
     * 观察lsusb -v -d 0x1e4e:的结果:
     *                wMaxPacketSize     0x0400  1x 1024 bytes
     * bAlternateSetting       8
     */
    usb_set_interface(s_myuvc_udev, s_myuvc_streaming_intf, s_myuvc_streaming_setting);
    
    /* 2. 分配设置URB */

    /* 3. 提交URB以接收数据 */

	return 0;
}

/*!
 * @brief  Step12 - APP调用poll/select确定缓存是否有数据
 */
static unsigned int myuvc_poll(struct file *file, 
									struct poll_table_struct *wait)
{
	return 0;
}


/*!
 * @brief  Step13 - APP通过poll/select确定缓冲区有数据后,从队列中取出并删除缓冲区
 */
static int myuvc_vidioc_dqbuf(struct file *file,
								void *priv, struct v4l2_buffer *v4l2_buf)
{
    struct myuvc_buffer *buf = &s_myuvc_queue.buffer[v4l2_buf->index];

    /* APP发现数据后,从mianqueue中取出buffer */
    list_del(&buf->stream);
    
	return 0;
}

/*!
 * @brief  Step14 - APP已经mmap映射缓存,可直接读数据
 *         Step15 - 再次调用myuvc_vidioc_dqbuf(),把缓存尾插法放入队列
 *         Step16 - 在其调用myuvc_poll()
 */

/*!
 * @brief  Step17 - 不使用时,停止摄像头数据传输
 */
static int myuvc_vidioc_streamoff(struct file *file, 
									void *priv, enum v4l2_buf_type i)
{
	return 0;
}

/*!
 * 所支持的ioclt函数
 */
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_streamon      = myuvc_vidioc_streamon,
	.vidioc_streamoff     = myuvc_vidioc_streamoff,   
};

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

static void myuvc_release(struct video_device *vdev)
{}

static int myuvc_probe(struct usb_interface *intf,
		     const struct usb_device_id *id)
{	
	static int s_cnt = 0;
    struct usb_device *dev = interface_to_usbdev(intf);
    
    s_myuvc_udev = dev;
	printk("myuvc_probe : cnt = %d\n", s_cnt++);

    if (s_cnt == 1) {
        s_myuvc_control_intf = intf->cur_altsetting->desc.bInterfaceNumber;
    } else if (s_cnt == 2) {
        s_myuvc_streaming_intf = intf->cur_altsetting->desc.bInterfaceNumber;
    }

	/*!
	 * myuvc_probe()调用第二次后执行
	 */
    if (s_cnt == 2) {
		/* 1、分配一个video_device结构体 */
		s_myuvc_vdev = video_device_alloc();

		/* 2、设置 */
		/* 注册过程需要用到release,必须设置 */
		s_myuvc_vdev->release     = myuvc_release;

		s_myuvc_vdev->fops        = &myuvc_fops;

		s_myuvc_vdev->ioctl_ops	  = &myuvc_ioctl_ops;

		/* 3、注册结构体 
		 * -1 - 自动分配次设备号
		 */
	    printk("1\n");
		video_register_device(s_myuvc_vdev, VFL_TYPE_GRABBER, -1);
        printk("1\n");

        myuvc_vidioc_streamon(NULL, NULL, 0);
	}
	
    return 0;
}
			 
static void myuvc_disconnect(struct usb_interface *intf)
{
	static int s_cnt = 0;

	printk("myuvc_disconnect : cnt = %d\n", s_cnt++);
	
	/*!
	 * myuvc_disconnect()调用第二次后执行
	 */
	if (s_cnt == 2) {
		/* 注销结构体 */
		video_unregister_device(s_myuvc_vdev);
		
		/* 释放结构体 */
		video_device_release(s_myuvc_vdev);		
	}
}

struct usb_driver myuvc_driver = {
	.name		= "myuvcvideo",
	.probe		= myuvc_probe,
	.disconnect	= myuvc_disconnect,
	.id_table	= myuvc_ids,
};

static int myuvc_init(void)
{
	int result;

	result = usb_register(&myuvc_driver);
	if (result)
		printk("USB register error!\n");
	return result;
}

static void myuvc_cleanup(void)
{	
	usb_deregister(&myuvc_driver);
}

module_init(myuvc_init);
module_exit(myuvc_cleanup);
MODULE_LICENSE("GPL")

三、编译与运行

  1. 执行make,生成驱动文件并插入USB摄像头之后
  2. 卸载原先虚拟机的摄像头驱动sudo rmmod uvcvideo,装载新驱动sudo insmod myuvc.ko
  3. 后执行dmesg查看输出信息。

可以看到,输出的参数信息与设置参数信息一致。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42813232/article/details/107536912