从调试数据分析USB通信协议——UVC摄像头【UVC类设备】(五)

发布于 2019-09-26 作者 风铃 91次 浏览 版块 前端

从调试数据分析USB通信协议——UVC摄像头【UVC类设备】(五)


       
前面关于USB协议的一些基础学习得也不少了,由简入深,趁热打铁,接下来,我们就来分析一下我们的主题,UVC摄像头了。有了上面一圈的了解,设备描述符这些东西,小编这里不想再去一行行分析了,小编这里只贴几张图,具体的分析,读者可以自己去看看了。


 




注:既然去查了,干脆就还是放在这里吧。小编我去查了一下下面的Miscellaneous,当然是查【官方文档USB_Video_Class_1_1_090711USB
Video
Class1_1USB_Video_Class_1.1.pdf
,47页,有如下这么一段。


【官方文档USB_Video_Class_1_1_090711USBVideo Class 1_1USB_Video_Class_1.1.pdf,49页,以下的CS_INTERFACE文档中有提到为0x24.

Subtype0x01,表示为VC_HEADER,UVC协议版本号为0x0100,即1.00VC类特征描述符信息返回的总字节数为0x006A,即106。时钟频率为0x00e4e1c0,即15MhzVS接口数为1,第一个VS接口的接口序号为1.

Subtype为0x03,表示为VC_OUTPUT_TERMINAL,终端标识号ID为0x05,终端类型为0x0101,即TT_STREAMING.该输出终端没有内部输入终端与之连接。该终端连接的单元或终端的ID为0x04,该终端没有字符描述。

VC扩展单元描述符:subtype0x06,即表示VC_EXTENSION_UNIT,单元ID0x03,供应商特征ID28f03370-6311-4a2e-ba2c-6890eb334016。该扩展单元支持的控制数为0x18,24,单元支持的输入脚有1个。该扩展单元第一个输入脚所连接的终端ID0x02bmControls的大小为3bmControls的内容为0xffffff,没有关于该扩展单元的字符描述。

VC扩展单元描述符:subtype0x06,即表示VC_EXTENSION_UNIT,单元ID0x04,供应商特征IDdddf7394-973e-4727-bed9-04ed6426dc67。该扩展单元支持的控制数为0x10,16,单元支持的输入脚有1个。该扩展单元第一个输入脚所连接的终端ID0x03bmControls的大小为2bmControls的内容为0x03ff,没有关于该扩展单元的字符描述。

Subtype0x02,表示为VC_INPUT_TERMINAL,终端标识号ID0x01,终端类型为0x0201,即ITT_CAMERA.该输出终端没有内部输入终端与之连接,该终端没有字符描述。

Subtype为0x05,表示为VC_PROCESSING_UNIT,单元ID0x02,与该终端连接的单元ID0x01,不支持数字乘法器,该单元支持的bmControls的大小为2,内容为0x37ff,可能摄像头使用老版本UVC1.0协议的原因,有些字段对不上,这里我们只看低16位就好。0x00表示没有字符串描述。

描述符类型为0x25,即CS_ENDPOINT,Subtype0x03,EP_INTERRUPT,该中断端点支持的最大发送结构大小为0x0040,即64字节。

描述符类型为0x24,即CS_INTERFACE,Subtype0x01,VS_INPUT_HEADER,视频载荷类型,即payload类型只有1种,VS描述符返回信息的总长度为0x0093,147字节。Isochronous[等时]bulk[批量]传输端点地址为0x01,方向为输入[host而言是输入]。不支持动态格式转换,该终端连接的终端ID0x05,不支持静态图片捕获和硬件触发,忽略静态图像捕获初始化,bmControls大小为1,内容为0.

前面的内容都是参考【官方文档USB_Video_Class_1_1_090711USB Video Class1_1USB_Video_Class_1.1.pdf】,接下来的流格式输出,以上文档推荐我们参考另一份文档【官方文档USB_Video_Class_1_1_090711USB Video Class1_1USB_Video_Payload_Uncompressed_1.1.pdf】,有如下内容:

这里CS_INTERFACE即是0x24,VS_FORMAT_UNCOMPRESSED即是0x04,格式描述符索引号为0x01,后跟的帧描述符有共有2个,流编码格式GUID号为32595559-0000-0010-8000-00AA00389b71,也就是如下所示的YUV2格式.像素位数为0x10,即16位,默认的帧索引为0x01,x/y维的透明度均为0,即不透明。Interlaceflags值取0,参照上面各位含义理解。拷贝权限为0,即无限制。

这里0x05即VS_FRAME_UNCOMPRESSED,帧描述符索引号为0x01,不支持静态图像和固定帧率,待解码图像的宽度为0x0280,即640,高度为0x01e0,即480,最小传输比特率为0x01770000,即24Kbit/s,最大传输速率为0x08CA0000,即144Kbit/s,需要的最大帧缓冲大小为0x00096000,即600Kbyte,默认帧间隔时间为0x00051615,即33.3ms,帧间隔类型个数为0x06,即支持6种离散帧间隔,第一种帧间隔时间为0x00051615,即33.3ms,第二种帧间隔时间为0x00061A80,即40ms,第三种帧间隔时间为0x0007A120,即50ms,第四种帧间隔时间为0x000A2C2A,即66.6ms,第五种帧间隔时间为0x000F4240,即100ms,第六种帧间隔时间为0x001E8480,即200ms,故综上对应的帧率分别为30fps,25fps,20fps,15fps,10fps,5fps。这里我们通过打开微软提供的免驱UVC点亮软件【所用分析软件AMCap.exe】,并依次点击菜单栏的Options->Video Capture pin... 有如下界面,通过点击帧率的上下选择键,可以看到以上这几种帧率,说明我们的分析是正确的,另外,这里显示的默认分辨率也与我们分析相符,至于输出格式YUV2也与前述相符。[这里我们显然没有采用连续帧间隔模式而是采用离散模式]。

这里0x05即VS_FRAME_UNCOMPRESSED,格式描述符索引号为0x02,其他与上面格式描述符1内的内容是相同的。

0x0D即为VS_COLORFORMAT,相信各位读者对此已毫无疑问,不然,可以自己去搜搜文档内部这个标识了。这里基色用的是默认的BT.709,即0x01Gamma校正也是选取的默认BT709,什么你不清楚什么是Gamma校正,请参考文档【USB经典博文理解伽马(Gamma.pdf。亮度和色彩转换系数矩阵采用的是默认值BT601


端点地址为0x01,采用异步等时输入[对于host来说是输入]。

我们在使用这颗摄像头的时候其实并没有用这种额外的速度配置,所以这里我就不重复讲之前展开讲过的概念了,读者可以自行去分析。

一轮分析完小编我觉得官网文档中有一部分还是有必要拉出来再看看,它有助于我们进一步理解上面的所谓单元和终端到底是什么东西,在【官方文档USB_Video_Class_1_5USB Video Class 1_5UVC1.5 Class specification.pdf】文档中这部分是这样介绍的,这里为什么选择UVC1.5版本的协议,因为它这部分更加图文并茂和生动。

其实关于以上这种抽象的模块单元的概念,小编我觉得跟微软在DriectShow中的做法也是很像的[小编我最初解码UVC摄像头用的就是DirectShow,先在GraphEdit中进行类似G语言的模块连接调试,然后在根据模块编写与之相应的C++代码进行图像捕获],把一个功能模块封装成一个具有输入输出引脚的对象,在很多软件中都是使用的这种方法,比如MatlabLabView,这很类似于G语言的概念。那么什么是终端呢,很明显终端就是一个数据流起始和终结的地方,那么这段数据流从源端出来,我们肯定要对其进行一些处理,或者压缩,或者编码,这时候就需要一些单元,如编码单元,或者进行一些扩展处理,如扩展单元,这样说你明白了么。接下来,小编我根据上面描述符的信息整理了一下,各单元之间的一个连接情况,整理出如下的连接表:

[Input Terminal](接口0.实体1)->[ProcessingUnit] (接口0.实体2)->[Extension Unit] (接口0.实体3)->[Extension Unit] (接口0.实体4)->[Output Terminal](接口0.实体5)

参考WireShark对整个配置描述符的解析,有如下信息:

接下来,小编我用一张在【USB_Video_Class_1_5USB Video Class 1_5UVC 1.5 Classspecification.pdf】中的描述符层级结构图,来对上面的内容总结一下,现在各位读者应该一目了然了吧。另外,关于以上USB摄像头插入时,小编我也用WireShark捕获了数据包,但是由于UVC摄像头不像U盘,还有文件系统等内容,这里直接几条指令实际就已经向主机报备了以上所有的信息,所以这里我就不带各位分析数据包前面关于设备和配置描述符等的内容了,这部分各位自己看看就好。

对于后半部分,小编我这里从控制接口类请求讲起吧,在讲控制接口请求之前,当然,我们也得先看看关于它的一些说明[77页],如下,首先我们看看请求的指令格式:[注意,控制接口和视频流接口的定义是在该文档不同位置的,是分开的]

格式呢就是上面这个格式啦,但是里面的请求码bRequest到底代表什么意思呢?在【USB_Video_Class_1.1.pdf】75页找到如下内容:

然后这些请求码的取值情况呢,见124页,如下:

然后我们看看第二个字段Control Selector[控制选择器],这里我们看到第124页。

发现小编我这个截图员当得是够可以的,咦,你也发现了不同类型的接口和单元控制器标识符怎么这么多重复的,不要紧张,后面我们不是还有一个接口和实体序号的索引么,通过它,我们就把不同的实体和接口区分开来了,这样问询所应得到的结果不就是唯一的了么。OK,了解了这么多,我们可以来分析UVC摄像头在插入后,后半部分发生的事情了

这里首先我们看到的是从host发送了一条GET_LEN的接口控制请求给端点0,要求其输入接口号为0,终端实体ID号为3的实体的2字节长度的信息,我们回过头去检查了一下,发现接口0的实体3是一个扩展单元,而在说明中第101页,关于扩展单元的wValue字段有如下一段描述:

我们回过头去查看扩展单元的描述符,共有3Controls,这里我们取的是0x01[这里取值范围似乎并不是在1~3范围,也成功了,小编这块也没太理解,不过这块实际作用也不大]。对于扩展单元3和扩展单元4,其内容的实际意义都是厂商定义的,这里我们直接跳过这部分继续分析后面的内容。然后,我们就看到了如下数据包。

很明显这部分是查询绝对曝光时间的,这里小编我就带各位分析这一个属性,其他的属性查询过程都是类似的,就靠读者们自己去了解了。这里首先我们使用了GET_INFO指令,查询对象是接口0的实体1[InputTerminal],所查询内容的长度为1个字节。由于是一个终端,因此这里我们对照上表Talbe A-12,发现这个指令即CT_EXPOSURE_TIME_ABSOLUTE_CONTROL。

参照如上GET_INFO的返回值定义,我们也可以分析出以下WireShark所显示的结果。

接下来,依次使用GET_MIN和GET_MAX指令查询该实体的曝光时间属性的最大最小值。

这里我们得到如下的返回信息,其最小值为0x00000001,最大值为0x00001388,即曝光时间最小为0.0001s,最大为0.5s.

紧接着,使用GET_RES[RESresolution的缩写]来查询该属性的分辨率值,这里我们曝光值的分辨率如下,就是0x00000001,即0.0001s.

最后使用GET_DEF[default]来获取该属性的默认值,这里我们默认曝光时间为0x0000009c,即0.0156s

接下来我们依次获取以下属性:

1.向接口0的实体1[Input Terminal]获取[Auto-Exposure Priority]属性。

2.向接口0的实体1[Input Terminal]获取[Iris (Absolute)]光圈属性

3.向接口0的实体1[Input Terminal]获取[Roll (Absolute)]属性

4.向接口0的实体2[Processing Unit]获取[Brightness]亮度属性

5.向接口0的实体2[ProcessingUnit]获取[Contrast]对比度属性

6.向接口0的实体2[ProcessingUnit]获取[Hue]色彩属性

7.向接口0的实体2[Processing Unit]获取[Saturation]饱和度属性

8.向接口0的实体2[ProcessingUnit]获取[Sharpness]锐化属性

9.向接口0的实体2[Processing Unit]获取[Gamma]属性

10.向接口0的实体2[Processing Unit]获取[White Balance Temperature]白平衡色温的开尔文温度属性

11.向接口0的实体2[ProcessingUnit]获取[Backlight Compensation]背光补偿属性

12.向接口0的实体2[Processing Unit]获取[Gain]属性

13.向接口0的实体2[Processing Unit]获取[Power Line Frequency]电力线频率属性

14.向接口0的实体2[ProcessingUnit]获取[White Balance Component]属性

至此,整个插入过程的数据流结束。


接下来我们继续分析UVC摄像头在点亮过程中的数据流,小编我通过【所用分析软件AMCap.exe】点亮摄像头时,使用WireShark捕获的数据包如下。有了上面的基础,对于以下分析的理解应该相对简单了不少了。

首先第一条指令,我们看到是从host发送到了端点0,且要求后续数据流方向为从设备输入host。关于它,我们联系之前的概念,可以知道,这里是一条GET_CUR命令,获取的是接口1.实体0的当前属性,而接口1是一个Video Streaming的接口,这与前面的控制接口又不同,因此这里我们参考【官方文档USB_Video_Class_1_1_090711USB Video Class1_1USB_Video_Class_1.1.pdf102页开始的内容UVC关于流接口请求的定义跟之前的控制接口请求的定义是区分开来的,如下:

这里我们回到前面查看Table-A15,可以看到关于流接口控制选择器的标识说明,0x01即VS_PROBE_CONTROL,获取信息长度为0x1a,即26字节。

关于Video嗅探和提交这块的内容有些多,读者可以自行翻阅【官方文档USB_Video_Class_1_1_090711USB Video Class1_1USB_Video_Class_1.1.pdf103页开始的内容。根据当中的内容,我们知道数据流参数的选择是基于一种共享形式的协商模型,共享的双方是

Host和视频流接口。当在嗅探[probe]过程中,一组流参数被成功接收,提交控制器[commit]就会从嗅探控制器中获取这组协商参数来配置硬件。如下图分别是同步传输协商成功和失败的流程图:

了解了嗅探提交协商的流程,我们来分析以下返回数据,前两个字节为bmHint定参设置,这里0x0000,表示没有定参,接下来0x01,表示使用流格式描述符中的第一种流格式,这里只有YUV2这一种流格式可供选取。0x01表示帧格式分辨率选取,这里选择第一种帧分辨率,0.0001s。帧间隔时间为0x000a2c2a,66.6ms15fpsIPB帧是H264视频流格式中里要用到的概念,关键帧,运动帧,小编我在做RTP摄像头项目的时候也有遇到过,这里我们是YUV2输出并没有用到这些内容,所以这里直接将这些字段取0了。wCompWindowSize值为0x001ewDelay值为0x0000,即内部视频流延迟时间为0.最大视频帧所占空间为0x00096000,即600KByte。单个载荷数据包能传输的最大字节数为0x00000640,即1600字节。

第二条指令,从host发送到端点0,要求后续数据流方向为从host输出到设备。这是一条SET_CUR命令,设置的是接口1.实体0的当前属性,即设置Video Streaming的接口,设置的内容共26字节长度,以上指令执行的顺序从这里看是符合我们上面提到的嗅探提交流程的。

这里这条返回指令让小编我很是奇怪,WireShark显示发送源是设备,发送目标却是host,但是端点值又是0x00,显示方向是输出。小编我这里暂时认为这条指令应该是host输出到设备的,跟之前的返回指令做对比,这里只是改变了帧率,这里帧间隔时间被修改为了0x00051615,即33.3ms30fps。单包最大字节数对于host来说是只读的,因此这里设置为0,并没有其他意义。难道这里是由于共享模型,才会呈现这样的数据流向?

接下来我们又一次去发送了GET_CUR命令,以检查我们关于帧率的设置,返回数据显示帧率设置成功了。但是单包最大传输字节数却不对了。因此,这一次嗅探提交可能失败了,但我们还继续尝试读取几次。

然后我们发送了一条GET_MAX[0x83]指令,依然发送给Video Streaming类型接口1.实体0以获取最大值属性,0x01VS_PROBE_CONTROL,获取信息长度为0x1a,即26字节。而获得的返回数据与上面的GET_CUR命令的返回数据是相同的,单包最大传输字节数依然有问题。

接着我们发送一条GET_MIN[0x82]指令给Video Streaming类型接口1.实体0以获取最小值属性,0x01VS_PROBE_CONTROL,获取信息长度为0x1a26字节。获得的返回数据与上面的GET_CURGET_MAX命令的返回数据都是一样的,单包最大传输字节数仍有问题。

再发送一条GET_CUR[0x81]指令给Video Streaming类型接口1.实体0以获取当前值属性,指令内容与返回信息均与之前GET_CUR相同,单包最大传输字节数还有问题,本次嗅探提交失败,重新再来。

接下来又通过SET_CUR[0x01]指令,把帧率设置回之前的0x000a2c2a,即66.6ms15fps。然后继续GET_CUR/GET_MAX/GET_MIN,这回不止帧率设置成功了,单包最大字节数,也恢复正常的1600字节了。然后我们再次执行SET_CUR/GET_CUR设置帧率为66.6ms,依然成功。接下来重点来了,注意我们之前的操作都是嗅探[VS_PROBE_CONTROL]操作,现在嗅探已经成功配置了一组参数,并完成了检测,都没有问题出现,也就是说该是提交这组参数的时候了。因此,就有了接下来的这条SET_CUR指令,这里其wValue,毫无疑问就是VS_COMMIT_CONTROL(0x0200)

然后根据之前的流程图,我们接下来要做的操作就是SET INTERFACE Request。这条指令是属于USB2.0协议的,因此,我们看到【官方文档usb官方协议文档usb_20.pdf】259页,有如下内容,这里我们选择的是接口1中setting值为4的setting,即如下异步等时传输端点。

再往后,视频数据流就正式开启传输了。涉及数据流传输,小编我这里建议读者通读一下【官方文档USB_Video_Class_1_1_090711USB Video Class1_1USB_Video_Payload_Uncompressed_1.1.pdf】文档,其一是它的内容并不多,其二是我们这里的UVC摄像头采用的正是非压缩的YUV2[YUV422]格式。

对于视频数据流这块,还好小编我手头就有UVC的摄像头,且小编之前又采用DirectShow做过驱动UVC摄像头的软件。因而具备了抓包对比分析的一些先决条件,二话不说,小编我立马动手制作了【所用分析软件CameraCtrlDev_全兼容抓取YUV2格式UVC协议包分析专用】软件,这个软件我做了什么呢?我用DirectShow驱动UVC摄像头点亮,并在获取到第一帧画面的时候停止摄像头捕获[虽然第一时间停止了捕获,但是这是在保存数据之后,因而还是耽搁了一些时间,所以还是多抓了一些内容进来,且因为是用于调试和学习USB,这个抓包软件,小编我也是随便改改,改的有一些乱也就无所谓了].

总而言之,接下来小编我利用自己做的软件抓取了一帧YUV2的原始数据,且同时打开了Bus Hound进行USB总线上的抓包[抓包的时候记得设置Bus Hound的数据包上限,小编我就给忘了,又造成一些误解,耽误一些时间,不开森],并在第一帧捕获完全的时候,即调用DirectShowAPI停止捕获。既然两段数据包都包含同一帧YUV2的原始数据,我们再来观察这款摄像头的UVC数据流就比较直观了。相关数据包小编我放在了【数据包USB摄像头单帧数据包对比分析】中,其中capturedata文件是小编我自制软件保存的YUV2的原始数据,单帧数据.txt文件是利用Bus Hound抓取的启动停止UVC捕获整个过程的USB数据包。usbraw数据文件是小编我通过UltralEditWinHex处理单帧数据.txt文件得到的Usb16进制数据包文件[处理方式参考前面FAT32部分]

接下来我们使用Beyond Compare软件对比【capturedata】和【usbraw】有如下截图:

这里对于【单帧数据.txt】中,USB通讯前面部分用于启动视频数据流的嗅探提交过程数据,小编我就不再分析了。且对于前面大量的空白数据包小编我这里也直接跳过。我们直接来看载荷有有效数据的这个数据包。这个数据包起始于29.1.0 这行记录,如下:

 19.1 ISOC   0c 8d 9c b5   3a 3d 30 66  61 3d e5 07 00 00 00 00  ....:=0fa=......        29.1.0

这里我们首先对照【官方文档USB_Video_Class_1_1_090711USB Video Class1_1USB_Video_Payload_Uncompressed_1.1.pdf】第二页的Stream Header来分析,发现可以看出一些门道。因为我们发现第一个字节是0x0c,而这一行的有效数据刚好是12个字节。

这里推荐我们去【官方文档USB_Video_Class_1_1_090711USB Video Class1_1USB_Video_Class_1.1.pdf】的2.4.3.3[31]查看详细的介绍,ok我们去查看到如下的内容:

对照以上介绍,我们可以分析的比较清楚了。0x8d10001101b,因此,EOH1,表示这段是Stream Header头的结束。ERR0,表示没有流错误,STI0表示该段数据不属于任一张静态图片。SCR1表示包含时钟源参考数据,PTS1表示包含时间戳信息。EOF0表示当前不是一帧画面的结束。FID1表示后跟数据尚不含任何有效数据,不属于任一帧数据,当我们开始接收有效帧数据时,该位将会清0,并在该帧没有结束有效数据包传输的过程中一直保持为0,直到该帧所有数据发完,数据包数据再次无效。

这里的[9c b5 3a3d]即时间戳信息,由于这个时间戳是针对帧的,因此对于上述FID0的保持段中,该时间戳内容都应该是保持不变的,因为它们都是隶属于同一帧画面的时间戳。

对于时钟源参考数据SCR这里我们没有用到,且与图像数据关系不大,故不做解释。

我们注意到前面再对比数据时,有效数据前面的Stream Header内容是[0C8C C4 49 61 3D 83 6D 61 3D E6 07],这里我们重点看看0x8c,这个字段,很明显,它与前述0x8d的区别在于FID位,而这个位恰是用来标识有效帧起始边界的,且他在后续有效数据段中,将一直保持0x8C不变。而[ C4 49 61 3D]则是该帧所对应的时间戳信息。

在后面该帧有效数据结束的位置,我们还找到了0x8e的标识,对比0x8c,这里变动的是EOF位,end of frame,关于它,这里相信小编也不用再解释什么了。

关于以上这段YUV2载荷数据的分析,小编我尚有一些存疑的地方,该处有待后续补充。

 

 

至此,关于USB协议的分析大致算是结束了。这里对USB摄像头传输协议我们也算有了一些了解。那么了解了这些我们可以来做什么呢?首先,在对linux模块驱动架构熟悉的情况下,我们可以来设计USB驱动程序,这里关于linux模块驱动的架构,小编我这里不展开来讲了,只给各位看看下面几张图,这里参考了【UVCuvc摄像头代码解析2 - tureno2011 - 博客园.pdf】这篇博文,显然这里在构建驱动模块时,跟小编一样,分析了一下设备配置接口端点的分配情况,然后分析了输出流中各终端单元的pad连接情况,并采用给结构体成员赋值的方式,向各结构体填充了对应的标识符数据,至于具体的解码,由于USB都已经是标准化的东西,这里小编也不从底层源代码级别去深挖了,标准化的东西自然是用标准库的,只要对象设置好了,传递给库,解码部分,是由库内部去实现的,且我们已经知道数据传递的格式了,要写出解码程序也不是什么难事了。而对于硬件部分,这部分则是直接由IC内部来实现的,如SN9C292A等。

最后,讲讲小编的资料来源,其实协议方面的资料,多是来自发布该协议的组织的官方网站的doc,如USB,如下:


这里各种USB设备类协议都被整合在了一起。其他资料一般来自CSDN等网站博客。


这里小编我将相关的资料资源下载链接放在下面:

1.圈圈教你玩usb,一本介绍usb不错的书:http://download.csdn.net/download/tanjiaqi2554/10049482

2.usb协议和文件系统用的一些分析软件:http://download.csdn.net/download/tanjiaqi2554/10049478
4.USBlyzer,一款不错的usb设备类分析软件:http://download.csdn.net/download/tanjiaqi2554/10049464
5.介绍伽马在摄像和显示中存在的原因和意义:http://download.csdn.net/download/tanjiaqi2554/10049457
6.FAT文件系统介绍文档和官方协议:http://download.csdn.net/download/tanjiaqi2554/10049444
7.使用wireshark和bushound抓取的usb设备数据包:http://download.csdn.net/download/tanjiaqi2554/10049454
收藏
暂无回复