1 Video Class 基础概念
Usb协议中,除了通用的软硬件电气接口规范等,还包含了各种各样的Class协议,用来为不同的功能定义各自的标准接口和具体的总线上的数据交互格式和内容。这些Class协议的数量非常多,最常见的比如支持U盘功能的Mass Storage Class,以及通用的数据交换协议:CDC class。此外还包括Audio Class, Print Class等等。
理论上说,即使没有这些Class,通过专用驱动也能够实现各种各样的应用功能。但是,正如Mass Storage Class的使用,使得各个厂商生产的U盘都能通过操作系统自带的统一的驱动程序来使用,对U盘的普及使用起了极大的推动作用,制定其它这些Class也是为了同样的目的。
Video Class 协议的目的是给USB接口的视频设备提供一个统一的数据交换规范。最初版本是在2003年9月才添加到USB Class规范中的,1.1的版本更是在2005年才发布。相比之下,Mass Storage Class 早在1998年就发布了。支持Video Class协议的多媒体芯片也是在2005年才陆续发布。所以USB 视频设备目前的现状是,在设备一端,多数依旧还采用原先的各种包含通用USB功能的多媒体处理芯片,主机端需要安装专用的驱动程序,基本上各个产品之间不具备兼容性。甚至对于操作系统而言,也只有在XP的SP2以后,才包含了对通用的Video class协议的支持。所以即使是某些多媒体设备(比如Logitech最新的几款摄像头)包含了对Video Class的支持,在Win2000等操作系统上依然需要安装驱动程序。不过,应该说使用Video Class无疑会是一个趋势,在相应的多媒体芯片陆续投入市场后,支持Video Class的多媒体设备应该会在一两年内会迅速普及开来。
除了在硬件上通过相应的多媒体芯片支持Video Class的设备以外,对于包含了操作系统的智能手机,当然也可以在手机端通过驱动程序来实现对Video Class的支持,就好像原先支持任何一种专用的USB驱动一样。只不过数据交换的格式不是自己随意制订的,而是按照Video Class的规范来实现的。
由于目前支持Video Class的设备还很少,所以在Linux上还没有开源的Video Class的主机端驱动,设备端的Video Class驱动就更没有见到开源的代码了。本文在介绍USB Video Class架构的基础上,主要是探讨Linux操作系统下设备端Video Class驱动的实现。不过在其它平台下的实现思路应该也是类似的。
Usb协议中,除了通用的软硬件电气接口规范等,还包含了各种各样的Class协议,用来为不同的功能定义各自的标准接口和具体的总线上的数据交互格式和内容。这些Class协议的数量非常多,最常见的比如支持U盘功能的Mass Storage Class,以及通用的数据交换协议:CDC class。此外还包括Audio Class, Print Class等等。
理论上说,即使没有这些Class,通过专用驱动也能够实现各种各样的应用功能。但是,正如Mass Storage Class的使用,使得各个厂商生产的U盘都能通过操作系统自带的统一的驱动程序来使用,对U盘的普及使用起了极大的推动作用,制定其它这些Class也是为了同样的目的。
Video Class 协议的目的是给USB接口的视频设备提供一个统一的数据交换规范。最初版本是在2003年9月才添加到USB Class规范中的,1.1的版本更是在2005年才发布。相比之下,Mass Storage Class 早在1998年就发布了。支持Video Class协议的多媒体芯片也是在2005年才陆续发布。所以USB 视频设备目前的现状是,在设备一端,多数依旧还采用原先的各种包含通用USB功能的多媒体处理芯片,主机端需要安装专用的驱动程序,基本上各个产品之间不具备兼容性。甚至对于操作系统而言,也只有在XP的SP2以后,才包含了对通用的Video class协议的支持。所以即使是某些多媒体设备(比如Logitech最新的几款摄像头)包含了对Video Class的支持,在Win2000等操作系统上依然需要安装驱动程序。不过,应该说使用Video Class无疑会是一个趋势,在相应的多媒体芯片陆续投入市场后,支持Video Class的多媒体设备应该会在一两年内会迅速普及开来。
除了在硬件上通过相应的多媒体芯片支持Video Class的设备以外,对于包含了操作系统的智能手机,当然也可以在手机端通过驱动程序来实现对Video Class的支持,就好像原先支持任何一种专用的USB驱动一样。只不过数据交换的格式不是自己随意制订的,而是按照Video Class的规范来实现的。
由于目前支持Video Class的设备还很少,所以在Linux上还没有开源的Video Class的主机端驱动,设备端的Video Class驱动就更没有见到开源的代码了。本文在介绍USB Video Class架构的基础上,主要是探讨Linux操作系统下设备端Video Class驱动的实现。不过在其它平台下的实现思路应该也是类似的。
2 USB Video Class 协议结构
2.1 设备拓扑结构
在拓扑结构上Video Class 将视频设备抽象为几个主要的硬件功能模块:
2.1 设备拓扑结构
在拓扑结构上Video Class 将视频设备抽象为几个主要的硬件功能模块:
输入端点 Input Terminal
输出端点 Output Terminal
camera端点 Camera Terminal
选择单元 Selector Unit
处理单元 Processing Unit
拓展单元 Extension Unit
输出端点 Output Terminal
camera端点 Camera Terminal
选择单元 Selector Unit
处理单元 Processing Unit
拓展单元 Extension Unit
下图是一幅摘自USB_Video_Example 1.1.pdf (www.usb.org)的拓扑结构示例图:
图1 USB Video Camera Topology Example
从sensor和另一个复合视频设备得到的数据流由IT 和 CT输入,经SU选择送PU处理,再由OT绑定到指定的USB端点。最后由USB端点与主机交互将数据发送到host端。在实际设备中,可能没有其中的某些功能模块,也可能其中的几个模块都是由同一硬件来完成的。
2.2 协议层次结构
上图中,左半部的框架组成了Video Class中的控制接口界面,右半部的框架组成了视频流传输接口界面。这两部分构成了Video Class的主要协议框架。
2.2.1 Descriptor Layout
与Class相关的信息,当然是主机端通过向设备端获取描述符(Descriptor)来得到的, 下图摘自USB_Video_Class_1.1.pdf , 给出了一个Video Class协议描述符应用示例的Layout。
图2 Video Camera Descriptor Layout Example
可以看到,在Descriptor Layout中,在标准描述符里,除了Device Descriptor, Configuration Descriptor, Interface Descriptor, Endpoint Descriptor,String Descriptor以外,还有一个USB2.0 协议中后期才新加的IAD 即 Interface Association Descriptor,用来描述多个相关Interface之间的关系,在Video Class中,IAD用来描述VideoControl Interface和VideoStreaming Interface之间的关系。
图中深色的部分就是Video Class 协议相关的专用描述符(Class Specific Descriptor)了。主要就是对硬件图像采集和处理模块的物理拓扑结构和功能的描述,以及对视频传输格式(包括编码格式,码率等等视频图像相关参数)的描述。
通过从设备处获得这些描述符,主机可以得知视频设备端的结构及其所支持的功能。而控制这些功能模块,对数据源和数据流进行配置,则需要通过Request来完成。
2.3 Request
Request是由主机向设备端发起的功能请求,包括所有USB设备都需要支持的Standard Device Requests 和与Class相关的Class Specific Requests :
2.3.1 Standard Device Requests
下图列出了USB Spec中规定的标准Request
图3 Standard Device Requests
这其中,有一部分Request是由USB控制芯片在硬件一级就直接完成的应答工作,比如SET_ADDRESS,有些则需要由软件来做进一步的处理,比如Set_Configuration。软硬件的这种任务的划分还与具体的硬件芯片相关。因为这部分是标准协议相关,本文就不详述。
2.3.2 Class Specific Requests
Class Specific Requests的数据结构Layout与标准Request是一样的,只是内容不同而已。VideoClass的Class Specific Requests主要根据Interface分为两类,其下又根据具体功能模块做进一步的划分:
Ø VideoControl Requests
- Camera Terminal Control Requests
- Selector Unit Control Requests
- Processing Unit Control Requests
- Extension Unit Control Requests
Ø VideoStreaming Requests
- Interface Control Requests
这其中,Interface Control Requests因为是用来在主机和设备之间协商数据交互格式和分辨率,流量等信息的,所以一般来说是必须实现的。
而Camera Terminal Control Requests 和 Processing Unit Control Requests中的内容,则是目前常用的即时通讯软件如MSN / QQ 等在其视频控制界面上集成的控制参数。
其中,Camera Terminal Control Requests包含了对曝光时间,曝光模式,对焦,变焦,平移等sensor获取数据阶段时的参数控制。
而Processing Unit Control Requests中则包含了亮度,增益,色调,对比度,白平衡等等sensor在获取到图像数据后,图像处理阶段的相关参数。
不过实际上,以上两者的划分在硬件层次并不是绝对的。很多参数的控制在sensor硬件级别上是同一层次的。不过,将这些功能抽象成这两类,正如在硬件的拓扑结构上将功能模块抽象出来一样,有利于通用化的程序设计
3.1 驱动架构
3.1.1 平台及软件基础
本文讨论的是USB Video Class在Linux操作系统上的设备端实现。具体是在Omap平台上,基于USB Gadget的驱动架构来实现的。
USB Gadget驱动分为两层,底层是处理与USB控制芯片硬件相关的内容,对上层屏蔽了大部分硬件相关的设置,并处理了一部分标准Request和EP0相关的标准操作流程。上层则是Class相关的部分,官方的Gadget驱动中,已经包含了File Storage(U盘功能),RNDIS(USB网卡功能)等的支持。考虑到Video Class的数据交换过程与File Storage有很多相似的地方,所以本文在Video Class的实现中,在大的框架上仿照了File Storage驱动的架构。
3.1.2 基本框架和数据流程
在本文实现的Video Class驱动中,整体的框架基本上分为两大部分。
一部分是负责处理模块的初始化过程,并负责处理Usb总线上的Descriptor和Requests的交互过程。包括USB总线上的控制和查询包的接收,解释,分配和应答。
另一部分,是在初始化过程中启动的一个独立的内核线程。负责具体的控制指令的执行和图像数据的获取及传输工作。这其中的许多操作都有可能引起睡眠,或者需要对文件进行操作,因此必须有一个线程做为依托。
模块的流程基本上是这样的:
在init函数中向Gadget底层驱动注册VideoClass数据结构。所有的描述符都定义为全局结构变量,在模块初始化过程中,进一步完成对描述符的填充过程,启动独立的内核线程,并注册EP0的complete回调函数。
在启动的内核线程中打开并初始化camera设备。将camera设置为默认的参数配置。读取图像数据并将数据填充到BUF里而后提交Request到VideoStream Interface里的BULK IN端点中。而后睡眠等待由于数据传送完毕被唤醒或有(异常)Exception发生被唤醒。
如果是数据传送完毕,则继续读取,填充并发送图像数据,如果有异常发生,则转而处理异常。
另一方面,哪些情况会引发异常呢?主要是驱动程序与BUS总线交互信息的这部分模块中,如果发生主机端重新设置Configuration,改变USB设备配置,或者发生总线插拔等引起总线状态变化的时候,会产生一个相应的异常,等待内核线程被唤醒并处理这些异常。
此外,在处理Requests的时候,有时候需要与camera驱动模块交互控制信息,同样需要操作文件句柄,目前的做法是在ep0 request的回调函数(context)中启动一个bottom half task,在Bottom half中完成相应的控制。
从总体结构上说,这样做很难看,理想的话应该在上述独立的内核线程中统一处理与Camera模块相关的操作,但是目前的架构,要唤醒该线程,只有两个途径,一是数据传输完毕或被取消,二是有总线状态变化相关的异常发生。如果硬加一个异常用来处理Requests似乎也很难看,而且对架构需要有较大的调整。所以目前只好简化的采用了前面所说的方案。
然后关于什么时候打开设备,开始往USB总线上放置数据,目前的处理方式也不是非常理想。目前是在模块初始化后立即获取第一帧图像而后等待主机端读取,实际上,主机端可能并不马上安排图像数据的传输。但是如果在主机端需要的时候才去读取数据的话,因为sensor获取数据需要一段曝光时间再加上压缩数据所需的时间,不可能立刻响应,所以在时间上肯定不能满足开始一段的数据传输请求。也需要继续仔细分析在何时启动camera模块最为合适。
3.2 与Camera驱动和V4L2子系统的配合
在linux内核中,2002年12月起发布了V4L2 (Video For Linux Two ) 0.1版本的规范,V4l2试图为所有和视频相关的设备都提供统一的接口,这其中当然也就包括了Camera设备。
而USB Video Class这一部分内容恰恰与视频设备也是密切相关的,所以在某些平台产品的实现中,甚至是在VideoClass中直接包含了Camera的驱动程序。这样做对于单一产品来说,可以大大简化驱动的层次结构,应该说是处理Camera的最直接简洁的办法。
但是,在本文的实现中,考虑到Linux内核中合理的模块划分的原则,也是为了符合Gadget驱动的其他Class实现的一贯风格,所以还是尽量使用V4L2的接口来控制Camera模块。这样做也有利于代码的移植。减小不同功能模块之间的耦合性。
理想的方式自然是所有的与Camera相关的操作都通过V4L2接口来实现,不过目前的实现中还是有些例外,引发例外的主要因素是效率问题。
由于在USB总线上传输图像,受到总线速度的限制,特别是在非USB2.0接口的芯片中,所以势必要采用JPEG或MPEG编码对数据进行压缩。V4L2子系统的框架中,包含了对编码器的支持,但是,也许是笔者对V4L2子系统的学习还不够深入,其对编码器的支持应该是对硬件编码芯片的支持,即使如此,也没有见到相关的代码,而且,在实现中使用的手机主板上也没有硬件的编解码芯片,所以在实现中Camera驱动通过V4L2子系统对应用层提供的是原始的未经编码的数据流,由应用程序调用IJG ( Independent JPEG Group )JPEG库函数来实现jpeg的编解码工作。
所以如果通过V4L2的read接口来获取数据,势必只能得到原始的图像数据,而且在V4L2的实现中,通过Read方式获取的数据,需要通过多次内存拷贝才能到达调用者处。所以也会很大程度的影响图像处理的速度。
要对图像进行压缩,要不在用户空间调用IJG库,要不在内核中再实现一个JPEG压缩算法。按前者来实现的话,涉及到Video Class如何去启动一个用户程序来读取Camera数据并在压缩后再传送给内核,也不是完全没法实现,但是无疑是一个非常糟糕的实现办法。
后者的话,涉及到这个JPEG压缩算法应该在什么地方实现,以及由谁来调用的问题(Video Class 还是 V4L2)。考虑到在存在硬件编码芯片的情况下,该芯片的管理和使用应该会纳入V4L2子系统中,所以考虑到兼容性,目前实现的方式是将JPEG压缩算法作为一个独立的模块插入内核,由V4L2子系统调用相关函数对图像数据进行压缩,然后再在Camera驱动中Export一个额外的函数接口,USB Video Class通过该函数接口启动Camera图像的读取和压缩过程,以区别标准的V4L2子系统的数据处理流程。压缩后的图像数据直接写入通过指针参数传递进来的内存地址空间中,尽可能的减少内存拷贝的次数,加速图像的传递。
这样做带来的问题就是需要在V4L2架构中添加额外的接口,增加了USB Video Class和V4L2子系统之间的耦合性,不能完全将这两个模块隔离开来。应该还有更好的解决方案。
3.3 JPEG编码相关
Jpeg的编解码,在Linux操作系统中,基本上采用的都是IJG(www.ijg.org)的JPEG库函数Libjpeg,这是一个相当可靠稳定和高效的开源项目,支持JPEG标准(不包括JPEG2000)的绝大多数编码方式。如非无奈,确实没有必要另外再写一个编码程序。但是由于需要在内核中使用,所以只好自己再编一个了。
JPEG编码相关的代码除了IJG的源代码以外,在网上还可以搜索到若干,但是无疑IJG的代码是最完善的。其它我能搜到的代码,多多少少都有一些BUG,而且也只是实现了JPEG标准的最基本的功能(当然,对于Video Class的应用来说已经是足够了)。最重要的是,多数是用浮点数运算来实现的,撇开速度不说,在本文的实现中OMAP平台的CPU也不支持浮点数运算。。。所以,本文实现中,最终是参考了网上搜到的某个算法的流程(主要是IJG的架构太复杂,一来没有时间精力和能力进行完整的分析,二来也不适合在内核中使用如此复杂的架构),在快速离散余弦变化的整数算法上仿照了IJG库的算法两者综合起来完成的。最终的代码还有很多需要改进的地方,不过,对于VideoClass来说,应该勉强够用了。这其中的具体问题,打算在另外单独的文档中再说明。
3.4 操作系统相关
说操作系统相关,可能说大了一些,这里主要涉及的内容是在本文的实现中,在WIN2000和WINXP平台的MSN测试中,遇到的一些问题。
由于VideoClass的协议只是规定了数据传输的格式和内容,对具体实现中的一些细节并没有作硬性的规定,所以导致有些细节可能存在不兼容的实现方式。(当然,我想主要还是本文的实现,由于能力有限,没有充分考虑到各种情况下的容错性,如果驱动做得好应该可以避免出现问题)。所以在WIN2000和WINXP的MSN测试中,遇到了一些平台相关的问题,有些功能在2000下能正常工作在XP下存在Bug,有些却相反。有些已经解决,有些只是猜测了可能的原因,罗列如下:
3.4.1 视频窗口关闭再打开后,没有图像
开始是在XP的MSN上发现有这样的问题,2000下没有,分析BUS数据可以看到,XP在关闭视频窗口的时候,会执行一个Abort Pipe的操作,这个操作应该会中断BULK传输,但是在设备端,Gadget底层驱动接收不到这个事件(也有可能是Gadget底层驱动的BUG),所以在VideoClass中无从得知这个传输已经被取消了,这样睡眠在等待数据传送完毕或失败上的线程也就无法被唤醒,自然也就不会继续发送数据。造成主机端再度打开视频窗口时接收不到图像数据。而在2000下的MSN中,关闭视频窗口的动作系统不会发送这个Abort Pipe事件,所以也就没有问题。
考虑到每次打开视频窗口的时候,主机端都会设置Streaming Interface的图像分辨率,码率等参数。而这之后主机端才会读取图像数据,所以后来解决的办法是在主机端设置Streaming Interface的时候,将之前已经放入BULK IN传输节点的数据 Dequeue出来,这样会造成这个传输的失败,从而唤醒睡眠的线程。但是如果仅仅这样做,XP能够正常工作了,2000又显示不了图像了。分析认为由于部分数据丢失,所以造成第一帧图像的数据是不完整的,无法正常解压缩显示,但是XP下的MSN有较好的容错性,能够丢弃这一帧图像,继续读取之后的数据,而2000下的MSN容错能力较差,无法再正常解读后面的图像数据。所以最终的解决办法是在发现传输失败后,将当前这一帧的图像数据从头开始重新发送,这样在XP和2000下就都能正常工作了。
不知道这种解决方案是否仅仅是一种治标的方案,有待以后继续研究。
3.4.2 某些分辨率下图像无法正常显示
在Win2000中如果提供160*120分辨率的图像,图像非常容易就停止刷新了,而BUS上实际数据还是在发送的。而在160*112(两者都是16的整倍数)的分辨率的情况下,就几乎不会发生这种情况。如果说这有可能还是JPEG的压缩算法有点问题,那另外一种情况就一定是XP 和 2000的区别了:如果设备这端通过描述符和Streaming Interface申明只能支持160*120 或者 160*112 的分辨率,2000可以接受这种分辨率,而XP根本就不能接受,总线上的控制传输就停止了,在界面上则显示检测不到Camera或Camera正在被其它设备打开占用,只有在进一步提供更高的320*240的分辨率的情况下,XP才会承认Camera的存在!其它问题倒不大,就是在本文的实现平台上,受软件编码JPEG速度的限制,320*240的分辨率下,视频的帧频会低一些,影响图像的流畅性。
3.5 其它
3.5.1 特殊效果的控制
应该说,VideoClass的Control Request基本上涵盖了V4L2标准界面提供的大部分控制参数,但是,还是有一部分没有涵盖,至于特定驱动专有的控制就更无法体现了,尤其是在MSN等应用程序的界面上,更不可能提供这些参数的控制了。但是,我们还是可以想办法trick过这个问题。
比如手机上常见的图像效果的设定,虽然不是特别有意义,但是既然是很常见的,为什么不能把它也做到Web Cam中呢?所以,如果一定要做,我们可以利用MSN控制界面上的原有的控制界面,借用其中一两个控制参数来实现图像效果的设定。
本文的实现中选择采用色调来控制图像效果,因为实际上这个参数是很不常用的,甚至只能在XP的高级设定中找到,对于99.9%的用户我相信都不会去改变这个参数。而它的字面含义与我们实现的功能也不算一点关系都没有,毕竟有很多效果实际上就是改变一下图像的颜色(当然还有一部分例外了)。
类似的可以用一些我们认为常用的设置替换既有的参数。这样做的缺点就是控制参数的字面含义与实际功能不太吻合,优点当然就是可以提供给用户更多更常用的图像设置。比如设置一个黑白,素描之类的图像的效果,玩玩抽象派视频聊天。
图1 USB Video Camera Topology Example
从sensor和另一个复合视频设备得到的数据流由IT 和 CT输入,经SU选择送PU处理,再由OT绑定到指定的USB端点。最后由USB端点与主机交互将数据发送到host端。在实际设备中,可能没有其中的某些功能模块,也可能其中的几个模块都是由同一硬件来完成的。
2.2 协议层次结构
上图中,左半部的框架组成了Video Class中的控制接口界面,右半部的框架组成了视频流传输接口界面。这两部分构成了Video Class的主要协议框架。
2.2.1 Descriptor Layout
与Class相关的信息,当然是主机端通过向设备端获取描述符(Descriptor)来得到的, 下图摘自USB_Video_Class_1.1.pdf , 给出了一个Video Class协议描述符应用示例的Layout。
图2 Video Camera Descriptor Layout Example
可以看到,在Descriptor Layout中,在标准描述符里,除了Device Descriptor, Configuration Descriptor, Interface Descriptor, Endpoint Descriptor,String Descriptor以外,还有一个USB2.0 协议中后期才新加的IAD 即 Interface Association Descriptor,用来描述多个相关Interface之间的关系,在Video Class中,IAD用来描述VideoControl Interface和VideoStreaming Interface之间的关系。
图中深色的部分就是Video Class 协议相关的专用描述符(Class Specific Descriptor)了。主要就是对硬件图像采集和处理模块的物理拓扑结构和功能的描述,以及对视频传输格式(包括编码格式,码率等等视频图像相关参数)的描述。
通过从设备处获得这些描述符,主机可以得知视频设备端的结构及其所支持的功能。而控制这些功能模块,对数据源和数据流进行配置,则需要通过Request来完成。
2.3 Request
Request是由主机向设备端发起的功能请求,包括所有USB设备都需要支持的Standard Device Requests 和与Class相关的Class Specific Requests :
2.3.1 Standard Device Requests
下图列出了USB Spec中规定的标准Request
图3 Standard Device Requests
这其中,有一部分Request是由USB控制芯片在硬件一级就直接完成的应答工作,比如SET_ADDRESS,有些则需要由软件来做进一步的处理,比如Set_Configuration。软硬件的这种任务的划分还与具体的硬件芯片相关。因为这部分是标准协议相关,本文就不详述。
2.3.2 Class Specific Requests
Class Specific Requests的数据结构Layout与标准Request是一样的,只是内容不同而已。VideoClass的Class Specific Requests主要根据Interface分为两类,其下又根据具体功能模块做进一步的划分:
Ø VideoControl Requests
- Camera Terminal Control Requests
- Selector Unit Control Requests
- Processing Unit Control Requests
- Extension Unit Control Requests
Ø VideoStreaming Requests
- Interface Control Requests
这其中,Interface Control Requests因为是用来在主机和设备之间协商数据交互格式和分辨率,流量等信息的,所以一般来说是必须实现的。
而Camera Terminal Control Requests 和 Processing Unit Control Requests中的内容,则是目前常用的即时通讯软件如MSN / QQ 等在其视频控制界面上集成的控制参数。
其中,Camera Terminal Control Requests包含了对曝光时间,曝光模式,对焦,变焦,平移等sensor获取数据阶段时的参数控制。
而Processing Unit Control Requests中则包含了亮度,增益,色调,对比度,白平衡等等sensor在获取到图像数据后,图像处理阶段的相关参数。
不过实际上,以上两者的划分在硬件层次并不是绝对的。很多参数的控制在sensor硬件级别上是同一层次的。不过,将这些功能抽象成这两类,正如在硬件的拓扑结构上将功能模块抽象出来一样,有利于通用化的程序设计
3.1 驱动架构
3.1.1 平台及软件基础
本文讨论的是USB Video Class在Linux操作系统上的设备端实现。具体是在Omap平台上,基于USB Gadget的驱动架构来实现的。
USB Gadget驱动分为两层,底层是处理与USB控制芯片硬件相关的内容,对上层屏蔽了大部分硬件相关的设置,并处理了一部分标准Request和EP0相关的标准操作流程。上层则是Class相关的部分,官方的Gadget驱动中,已经包含了File Storage(U盘功能),RNDIS(USB网卡功能)等的支持。考虑到Video Class的数据交换过程与File Storage有很多相似的地方,所以本文在Video Class的实现中,在大的框架上仿照了File Storage驱动的架构。
3.1.2 基本框架和数据流程
在本文实现的Video Class驱动中,整体的框架基本上分为两大部分。
一部分是负责处理模块的初始化过程,并负责处理Usb总线上的Descriptor和Requests的交互过程。包括USB总线上的控制和查询包的接收,解释,分配和应答。
另一部分,是在初始化过程中启动的一个独立的内核线程。负责具体的控制指令的执行和图像数据的获取及传输工作。这其中的许多操作都有可能引起睡眠,或者需要对文件进行操作,因此必须有一个线程做为依托。
模块的流程基本上是这样的:
在init函数中向Gadget底层驱动注册VideoClass数据结构。所有的描述符都定义为全局结构变量,在模块初始化过程中,进一步完成对描述符的填充过程,启动独立的内核线程,并注册EP0的complete回调函数。
在启动的内核线程中打开并初始化camera设备。将camera设置为默认的参数配置。读取图像数据并将数据填充到BUF里而后提交Request到VideoStream Interface里的BULK IN端点中。而后睡眠等待由于数据传送完毕被唤醒或有(异常)Exception发生被唤醒。
如果是数据传送完毕,则继续读取,填充并发送图像数据,如果有异常发生,则转而处理异常。
另一方面,哪些情况会引发异常呢?主要是驱动程序与BUS总线交互信息的这部分模块中,如果发生主机端重新设置Configuration,改变USB设备配置,或者发生总线插拔等引起总线状态变化的时候,会产生一个相应的异常,等待内核线程被唤醒并处理这些异常。
此外,在处理Requests的时候,有时候需要与camera驱动模块交互控制信息,同样需要操作文件句柄,目前的做法是在ep0 request的回调函数(context)中启动一个bottom half task,在Bottom half中完成相应的控制。
从总体结构上说,这样做很难看,理想的话应该在上述独立的内核线程中统一处理与Camera模块相关的操作,但是目前的架构,要唤醒该线程,只有两个途径,一是数据传输完毕或被取消,二是有总线状态变化相关的异常发生。如果硬加一个异常用来处理Requests似乎也很难看,而且对架构需要有较大的调整。所以目前只好简化的采用了前面所说的方案。
然后关于什么时候打开设备,开始往USB总线上放置数据,目前的处理方式也不是非常理想。目前是在模块初始化后立即获取第一帧图像而后等待主机端读取,实际上,主机端可能并不马上安排图像数据的传输。但是如果在主机端需要的时候才去读取数据的话,因为sensor获取数据需要一段曝光时间再加上压缩数据所需的时间,不可能立刻响应,所以在时间上肯定不能满足开始一段的数据传输请求。也需要继续仔细分析在何时启动camera模块最为合适。
3.2 与Camera驱动和V4L2子系统的配合
在linux内核中,2002年12月起发布了V4L2 (Video For Linux Two ) 0.1版本的规范,V4l2试图为所有和视频相关的设备都提供统一的接口,这其中当然也就包括了Camera设备。
而USB Video Class这一部分内容恰恰与视频设备也是密切相关的,所以在某些平台产品的实现中,甚至是在VideoClass中直接包含了Camera的驱动程序。这样做对于单一产品来说,可以大大简化驱动的层次结构,应该说是处理Camera的最直接简洁的办法。
但是,在本文的实现中,考虑到Linux内核中合理的模块划分的原则,也是为了符合Gadget驱动的其他Class实现的一贯风格,所以还是尽量使用V4L2的接口来控制Camera模块。这样做也有利于代码的移植。减小不同功能模块之间的耦合性。
理想的方式自然是所有的与Camera相关的操作都通过V4L2接口来实现,不过目前的实现中还是有些例外,引发例外的主要因素是效率问题。
由于在USB总线上传输图像,受到总线速度的限制,特别是在非USB2.0接口的芯片中,所以势必要采用JPEG或MPEG编码对数据进行压缩。V4L2子系统的框架中,包含了对编码器的支持,但是,也许是笔者对V4L2子系统的学习还不够深入,其对编码器的支持应该是对硬件编码芯片的支持,即使如此,也没有见到相关的代码,而且,在实现中使用的手机主板上也没有硬件的编解码芯片,所以在实现中Camera驱动通过V4L2子系统对应用层提供的是原始的未经编码的数据流,由应用程序调用IJG ( Independent JPEG Group )JPEG库函数来实现jpeg的编解码工作。
所以如果通过V4L2的read接口来获取数据,势必只能得到原始的图像数据,而且在V4L2的实现中,通过Read方式获取的数据,需要通过多次内存拷贝才能到达调用者处。所以也会很大程度的影响图像处理的速度。
要对图像进行压缩,要不在用户空间调用IJG库,要不在内核中再实现一个JPEG压缩算法。按前者来实现的话,涉及到Video Class如何去启动一个用户程序来读取Camera数据并在压缩后再传送给内核,也不是完全没法实现,但是无疑是一个非常糟糕的实现办法。
后者的话,涉及到这个JPEG压缩算法应该在什么地方实现,以及由谁来调用的问题(Video Class 还是 V4L2)。考虑到在存在硬件编码芯片的情况下,该芯片的管理和使用应该会纳入V4L2子系统中,所以考虑到兼容性,目前实现的方式是将JPEG压缩算法作为一个独立的模块插入内核,由V4L2子系统调用相关函数对图像数据进行压缩,然后再在Camera驱动中Export一个额外的函数接口,USB Video Class通过该函数接口启动Camera图像的读取和压缩过程,以区别标准的V4L2子系统的数据处理流程。压缩后的图像数据直接写入通过指针参数传递进来的内存地址空间中,尽可能的减少内存拷贝的次数,加速图像的传递。
这样做带来的问题就是需要在V4L2架构中添加额外的接口,增加了USB Video Class和V4L2子系统之间的耦合性,不能完全将这两个模块隔离开来。应该还有更好的解决方案。
3.3 JPEG编码相关
Jpeg的编解码,在Linux操作系统中,基本上采用的都是IJG(www.ijg.org)的JPEG库函数Libjpeg,这是一个相当可靠稳定和高效的开源项目,支持JPEG标准(不包括JPEG2000)的绝大多数编码方式。如非无奈,确实没有必要另外再写一个编码程序。但是由于需要在内核中使用,所以只好自己再编一个了。
JPEG编码相关的代码除了IJG的源代码以外,在网上还可以搜索到若干,但是无疑IJG的代码是最完善的。其它我能搜到的代码,多多少少都有一些BUG,而且也只是实现了JPEG标准的最基本的功能(当然,对于Video Class的应用来说已经是足够了)。最重要的是,多数是用浮点数运算来实现的,撇开速度不说,在本文的实现中OMAP平台的CPU也不支持浮点数运算。。。所以,本文实现中,最终是参考了网上搜到的某个算法的流程(主要是IJG的架构太复杂,一来没有时间精力和能力进行完整的分析,二来也不适合在内核中使用如此复杂的架构),在快速离散余弦变化的整数算法上仿照了IJG库的算法两者综合起来完成的。最终的代码还有很多需要改进的地方,不过,对于VideoClass来说,应该勉强够用了。这其中的具体问题,打算在另外单独的文档中再说明。
3.4 操作系统相关
说操作系统相关,可能说大了一些,这里主要涉及的内容是在本文的实现中,在WIN2000和WINXP平台的MSN测试中,遇到的一些问题。
由于VideoClass的协议只是规定了数据传输的格式和内容,对具体实现中的一些细节并没有作硬性的规定,所以导致有些细节可能存在不兼容的实现方式。(当然,我想主要还是本文的实现,由于能力有限,没有充分考虑到各种情况下的容错性,如果驱动做得好应该可以避免出现问题)。所以在WIN2000和WINXP的MSN测试中,遇到了一些平台相关的问题,有些功能在2000下能正常工作在XP下存在Bug,有些却相反。有些已经解决,有些只是猜测了可能的原因,罗列如下:
3.4.1 视频窗口关闭再打开后,没有图像
开始是在XP的MSN上发现有这样的问题,2000下没有,分析BUS数据可以看到,XP在关闭视频窗口的时候,会执行一个Abort Pipe的操作,这个操作应该会中断BULK传输,但是在设备端,Gadget底层驱动接收不到这个事件(也有可能是Gadget底层驱动的BUG),所以在VideoClass中无从得知这个传输已经被取消了,这样睡眠在等待数据传送完毕或失败上的线程也就无法被唤醒,自然也就不会继续发送数据。造成主机端再度打开视频窗口时接收不到图像数据。而在2000下的MSN中,关闭视频窗口的动作系统不会发送这个Abort Pipe事件,所以也就没有问题。
考虑到每次打开视频窗口的时候,主机端都会设置Streaming Interface的图像分辨率,码率等参数。而这之后主机端才会读取图像数据,所以后来解决的办法是在主机端设置Streaming Interface的时候,将之前已经放入BULK IN传输节点的数据 Dequeue出来,这样会造成这个传输的失败,从而唤醒睡眠的线程。但是如果仅仅这样做,XP能够正常工作了,2000又显示不了图像了。分析认为由于部分数据丢失,所以造成第一帧图像的数据是不完整的,无法正常解压缩显示,但是XP下的MSN有较好的容错性,能够丢弃这一帧图像,继续读取之后的数据,而2000下的MSN容错能力较差,无法再正常解读后面的图像数据。所以最终的解决办法是在发现传输失败后,将当前这一帧的图像数据从头开始重新发送,这样在XP和2000下就都能正常工作了。
不知道这种解决方案是否仅仅是一种治标的方案,有待以后继续研究。
3.4.2 某些分辨率下图像无法正常显示
在Win2000中如果提供160*120分辨率的图像,图像非常容易就停止刷新了,而BUS上实际数据还是在发送的。而在160*112(两者都是16的整倍数)的分辨率的情况下,就几乎不会发生这种情况。如果说这有可能还是JPEG的压缩算法有点问题,那另外一种情况就一定是XP 和 2000的区别了:如果设备这端通过描述符和Streaming Interface申明只能支持160*120 或者 160*112 的分辨率,2000可以接受这种分辨率,而XP根本就不能接受,总线上的控制传输就停止了,在界面上则显示检测不到Camera或Camera正在被其它设备打开占用,只有在进一步提供更高的320*240的分辨率的情况下,XP才会承认Camera的存在!其它问题倒不大,就是在本文的实现平台上,受软件编码JPEG速度的限制,320*240的分辨率下,视频的帧频会低一些,影响图像的流畅性。
3.5 其它
3.5.1 特殊效果的控制
应该说,VideoClass的Control Request基本上涵盖了V4L2标准界面提供的大部分控制参数,但是,还是有一部分没有涵盖,至于特定驱动专有的控制就更无法体现了,尤其是在MSN等应用程序的界面上,更不可能提供这些参数的控制了。但是,我们还是可以想办法trick过这个问题。
比如手机上常见的图像效果的设定,虽然不是特别有意义,但是既然是很常见的,为什么不能把它也做到Web Cam中呢?所以,如果一定要做,我们可以利用MSN控制界面上的原有的控制界面,借用其中一两个控制参数来实现图像效果的设定。
本文的实现中选择采用色调来控制图像效果,因为实际上这个参数是很不常用的,甚至只能在XP的高级设定中找到,对于99.9%的用户我相信都不会去改变这个参数。而它的字面含义与我们实现的功能也不算一点关系都没有,毕竟有很多效果实际上就是改变一下图像的颜色(当然还有一部分例外了)。
类似的可以用一些我们认为常用的设置替换既有的参数。这样做的缺点就是控制参数的字面含义与实际功能不太吻合,优点当然就是可以提供给用户更多更常用的图像设置。比如设置一个黑白,素描之类的图像的效果,玩玩抽象派视频聊天。