USB设备驱动开发之扩展(利用USB虚拟总线驱动模拟USB摄像头)

                                         fanxiushu 2016-10-08 转载或引用,请注明原始作者

做这个事情写这篇文章之前,压根没朝模拟USB摄像头这方面去想过。

直到CSDN上一位朋友提出问题,才想到还有这么一个玩意。
因此花了4,5天时间,利用自己之前开发的USB驱动,实现了一个虚拟USB摄像头的实例代码。
稍后会公布到CSDN上。

记得最早的一篇文章也是介绍虚拟摄像头驱动的开发的,只是当时采用的是windows的流内核实现的,
windows实现视频驱动(包括摄像头,声卡等等多媒体相关的)本身就是采用流内核作为微端口框架来实现的。
因此很容易想到使用AVStream流内核框架来实现虚拟摄像头,
事实上,USB接口的摄像头,在经过底层的USB总线层,上升到驱动的功能层,
依然需要利用AVStream微端口实现摄像头的功能。

而USB接口的摄像头,如果它的USB接口传输的协议是符合UVC(USB Video Class)标准的,
则windows会自动加载自己的usbvideo驱动,而无需再额外开发驱动,这就是大家所说的是免驱的。
不光是USB摄像头,因为USB的通用性和普遍性,很多很多的USB接口设备,包括USB键盘,U盘, USB鼠标,USB声卡等等,
这些USB设备,他们的USB通讯协议都符合某些通用的标准的,因此当把这些设备插入电脑,系统都会加载通用的驱动,
而无需再额外开发自己的驱动程序。
这就是所谓的免驱,其实并不是不要驱动,而是这些驱动已经作为系统内核一部分集成进去了。

开发USB虚拟总线驱动的目的正如第一章开头所述,只是为了把真实的USB接口的硬件设备“延长”到其他机器,
http://blog.csdn.net/fanxiushu/article/details/51420096(USB设备驱动开发之远程访问USB设备(一USB设备数据采集端))
对于浩繁的各种通用性以USB协议为基础的协议,不可能全部研究,也没这个精力。

当然,为了演示如何调用先前开发的USB驱动代码的接口,
以及完善在摄像头这块开发中的一些空缺,
而且稍微看了下UVC协议部分,USBLyzer抓包分析了下UVC协议,相对来说并不复杂,
因此花了几天时间完成这么一个功能,希望对正在做这些方面的朋友有些帮助,
有兴趣的朋友可利用我的USB驱动作为基础,开发模拟出一些其他符合通用协议”设备“, 比如模拟USB声卡,USB鼠标等。
对于不熟悉windows驱动的朋友也可以使用,只要熟悉相关的USB协议,全部都能在应用层实现。
相关代码请到CSDN下载。

首先研究UVC协议,查看官方的UVC文档,全是英文的,可是英文对我不太友好,
中文版的挺少见,估计是既熟悉英语,又精通相关技术的人较少,因此没翻译的。
(不过有时看翻译的比看原文还累,还不如干脆看原文)。
于是是通过 USBLyzer抓包软件,抓包分析了一个普通USB摄像头的通讯数据包,
看它的交互无非就是设备,配置,接口等描述符,然后就是 CLASS信息,最后就是大量的视频传输的 ISO Transfer。
这样把通讯过程分成3个部分。
一,模拟USB描述符数据,
二,模拟CLASS信息数据,
三,ISO同步传输实现视频数据的传输。

如果只看官方的UVC文档,你将摸不着门,还好有现成的,随便拿个摄像头,用USBLyzer或者类似的USB抓包软件抓包分析,
就知道传输了些什么,然后再结合UVC文档,看看每个包对应字段的解释,这就叫实践出真知。
而且还有个最大的代码级别的帮手,那就是开源的linux内核代码。
这些通用协议,linux内核都有对应的实现代码,在这里对我们来说尤其有用的就是linux对UVC数据包的各种结构声明,
具体位置在 include\uapi\linux\usb\video.h中,(linux3.8源代码),直接copy过来使用即可。

在填充USB描述符的时候,主要是三个,一个是设备描述符,一个是字符串描述符,一个是配置描述符。
前两个没什么好说的,最麻烦的是第三个:配置描述符。
但是,一般来说,只要随便仿照一个通用摄像头的配置数据填充虚拟数据即可,至少我是这么干的。
配置描述符中必须有一个 control interface和至少一个 stream interface。但是一般就配置两个接口就可以了,
一个 控制接口,一个视频流接口,至于endpoint,为了简单,把控制接口的interrupt端口去掉了,
只留下视频流接口的一个iso端口,用于传输视频数据。
配置描述符的数据结构有点多,具体都填写了哪些,可查看CSDN上的源代码,其实都是仿照某个真实摄像头的数据填充的。

接下来的就是CLASS信息,CLASS信息其实对应的windows的USB请求的 URB_FUNCTION_CLASS_INTERFACE 命令,
UVC采用这个命令来发送各种命令给摄像头硬件控制接口或者视频流接口,达到控制摄像头行为的目的。
UVC描述得这类命令包括
GET_CUR,GET_MIN,GET_MAX,GET_RES,GET_LEN,GET_INFO,GET_DEF, SET_CUR等
有些命令是发给控制接口,有些命令是发给视频接口的,有些是两个接口都发送。
这些命令如何详细使用,实话说我也不大清楚,有兴趣的同学可仔细研究UVC文档。
不过为了简单,就只实现stream interface 的四个命令就足够让摄像头运行了。
包括 GET_MIN, GET_MAX, GET_CUR, SET_CUR。
因此代码中只实现了这四个命令,若你对UVC协议非常熟悉,或者遇到某些软件需要其他命令支持,可进行修改。

最后就是视频数据的传输了,
模拟的配置描述符中,只配置了一个端口,而且类型是ISO同步传输,
虽然UVC文档说可以使用同步传输或者批量传输来作为摄像头的数据传输通道,
但是我所见到摄像头的清一色的都是 ISO同步传输的,所以这里采用同步传输。
UVC规定每个视频数据都得有个12或者2个字节的头来描述这个视频数据包信息,
代码采用的是2个字节的头来描述数据包,
接着就是如何界定数据包的范围了,一开始我的理解是一个 URB_FUNCTION_ISOCH_TRANSFER 请求就是一个数据包。
结果总是得不到视频数据,
后来仔细研究了linux代码中相关部分的实现,原来是 URB_FUNCTION_ISOCH_TRANSFER 传输的每个 PACKET才是一个包,
每个PACKET都得加个2个字节或者12个字节的视频头。

如此之后,摄像头就能工作了。
总得来说,只要具备了前面的虚拟USB总线驱动基础,后边的UVC相关的东西并不复杂,就是稍微繁琐点。

发布到CSDN上的代码,提供的模拟摄像头接口如下:

struct frame_t
{
    char* buffer;
    int   length;
    int   width;
    int   height;
    int   delay_msec; ///停留时间
    ////
    void* param;  ///
};

typedef int (*FRAME_CALLBACK)(frame_t* frame);

struct uvc_vcam_t
{
    int pid;
    int vid;
    const char* manu_fact;
    const char* product;
    FRAME_CALLBACK  frame_callback; ///
    void* param;
};

//// function
void* vcam_create(uvc_vcam_t* uvc);

void vcam_destroy(void* handle);

非常简单,只需要在安装USB虚拟总线驱动之后,就可正常运行。
代码接口只有一个 frame_callback回调函数,用来在获取每个视频帧的时候,填写每帧的视频数据(采用YUY2格式),
就能让这个虚拟摄像头工作起来。

代码作为例子,并没提供实际的数据源,只是在main.cpp代码中简单的动态模拟了一段文字大小不断变化。
你若有实际需要,可自行扩展功能,
但是慎重申明,请勿使用本代码作为基础开发出具有欺骗性功能的摄像头从事欺诈活动。

CSDN上代码地址:
http://download.csdn.net/detail/fanxiushu/9648010

在win10下的安装和运行效果图:
图片

猜你喜欢

转载自blog.csdn.net/fanxiushu/article/details/52761644