精华内容
下载资源
问答
  • 从调试数据分析USB通信协议——UVC摄像头【UVC类设备】(五)  前面关于USB协议的一些基础学习得也不少了,由简入深,趁热打铁,接下来,我们就来分析一下我们的主题,UVC摄像头了。有了上面一圈的了解,设备描述符...

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

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

     

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


    【官方文档\USB_Video_Class_1_1_090711\USBVideo Class 1_1\USB_Video_Class_1.1.pdf,49页,以下的CS_INTERFACE文档中有提到为0x24.

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

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

    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_090711\USB Video Class1_1\USB_Video_Class_1.1.pdf,接下来的流格式输出,以上文档推荐我们参考另一份文档【官方文档\USB_Video_Class_1_1_090711\USB Video Class1_1\USB_Video_Payload_Uncompressed_1.1.pdf】,有如下内容:

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

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

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

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

    然后这些请求码的取值情况呢,见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_090711\USB Video Class1_1\USB_Video_Class_1.1.pdf102页开始的内容UVC关于流接口请求的定义跟之前的控制接口请求的定义是区分开来的,如下:

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

    关于Video嗅探和提交这块的内容有些多,读者可以自行翻阅【官方文档\USB_Video_Class_1_1_090711\USB Video Class1_1\USB_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.pdf259页,有如下内容,这里我们选择的是接口1setting值为4setting,即如下异步等时传输端点。

    再往后,视频数据流就正式开启传输了。涉及数据流传输,小编我这里建议读者通读一下【官方文档\USB_Video_Class_1_1_090711\USB Video Class1_1\USB_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_090711\USB Video Class1_1\USB_Video_Payload_Uncompressed_1.1.pdf】第二页的Stream Header来分析,发现可以看出一些门道。因为我们发现第一个字节是0x0c,而这一行的有效数据刚好是12个字节。

    这里推荐我们去【官方文档\USB_Video_Class_1_1_090711\USB Video Class1_1\USB_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模块驱动的架构,小编我这里不展开来讲了,只给各位看看下面几张图,这里参考了【UVC\uvc摄像头代码解析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













    展开全文
  • UVC camera 封装

    2020-01-10 16:50:35
    借用该,在接入UVC 摄像头设备时可以很方便的进行使用,只需要在对应的Activity中,先初始化该,绑定一个SurfaceView,便于展示摄像视频数据,再在onStart生命周期回调中调用onStart方法、onStop生命周期回调中...

    下述代码是关于UVC camera处理的封装类,具体依赖的aar请参考https://github.com/saki4510t/UVCCamera

    借用该类,在接入UVC 摄像头设备时可以很方便的进行使用,只需要在对应的Activity中,先初始化该类,绑定一个SurfaceView,便于展示摄像视频数据,再在onStart生命周期回调中调用onStart方法、onStop生命周期回调中调用onStop方法、在onDestroy生命周期回调中调用release即可。

    public class UVCCameraManager {
    
        private static final String TAG = UVCCameraManager.class.getSimpleName();
        
        private final USBMonitor mUSBMonitor;
        private UVCCamera mUVCCamera;
        private SurfaceView mUVCCameraView;
        private Surface mPreviewSurface;
        private final Object mSync = new Object();
        private boolean isActive, isPreview;
        private final HandlerThreadHandler mWorkerHandler;
        private final Handler mUIHandler = new Handler(Looper.getMainLooper());
        private long mWorkerThreadID;
    
        public UVCCameraManager(@NonNull Context context, @NonNull SurfaceView surfaceView) {
            mUSBMonitor = new USBMonitor(context, mConnectListener);
            mWorkerHandler = HandlerThreadHandler.createHandler(TAG);
            mWorkerThreadID = mWorkerHandler.getLooper().getThread().getId();
            mUVCCameraView = surfaceView;
            mUVCCameraView.getHolder().addCallback(mSurfaceViewCallback);
        }
    
        public void onStart() {
            L.d(TAG, "onStart:");
            synchronized (mSync) {
                mUSBMonitor.register();
            }
        }
    
        public void onStop() {
            L.d(TAG, "onStop:");
            synchronized (mSync) {
                mUSBMonitor.unregister();
            }
        }
    
        public void release() {
            synchronized (mSync) {
                isActive = isPreview = false;
                if (mUVCCamera != null) {
                    mUVCCamera.destroy();
                    mUVCCamera = null;
                }
                mUSBMonitor.destroy();
            }
            try {
                mWorkerHandler.getLooper().quit();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        private synchronized void queueEvent(final Runnable task, final long delayMillis) {
            if (task == null) return;
            try {
                mWorkerHandler.removeCallbacks(task);
                if (delayMillis > 0) {
                    mWorkerHandler.postDelayed(task, delayMillis);
                } else if (mWorkerThreadID == Thread.currentThread().getId()) {
                    task.run();
                } else {
                    mWorkerHandler.post(task);
                }
            } catch (final Exception e) {
                // ignore
            }
        }
    
        private final USBMonitor.OnDeviceConnectListener mConnectListener = new USBMonitor.OnDeviceConnectListener() {
    
            @Override
            public void onAttach(UsbDevice usbDevice) {
                L.d(TAG, "onAttach");
                if (usbDevice != null) {
                    mUSBMonitor.requestPermission(usbDevice);
                } else {
                    L.d(TAG, "usbDevice is null");
                }
            }
    
            @Override
            public void onDettach(UsbDevice usbDevice) {
                L.d(TAG, "onDettach");
            }
    
            @Override
            public void onConnect(UsbDevice usbDevice, USBMonitor.UsbControlBlock ctrlBlock, boolean createNew) {
                L.d(TAG, "onConnect:");
                synchronized (mSync) {
                    if (mUVCCamera != null) {
                        mUVCCamera.destroy();
                    }
                    isActive = isPreview = false;
                }
                queueEvent(() -> {
                    synchronized (mSync) {
                        final UVCCamera camera = new UVCCamera();
                        camera.open(ctrlBlock);
                        L.d(TAG, "supportedSize:" + camera.getSupportedSize());
                        try {
                            camera.setPreviewSize(UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT, UVCCamera.FRAME_FORMAT_MJPEG);
                        } catch (IllegalArgumentException e) {
                            try {
                                // fallback to YUV mode
                                camera.setPreviewSize(UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT, UVCCamera.DEFAULT_PREVIEW_MODE);
                            } catch (IllegalArgumentException ex) {
                                camera.destroy();
                                return;
                            }
                        }
                        mPreviewSurface = mUVCCameraView.getHolder().getSurface();
                        if (mPreviewSurface != null) {
                            isActive = true;
                            camera.setPreviewDisplay(mPreviewSurface);
                            //第一次在这里会黑屏???
                            //isPreview = camera.startPreview()>=0;
                            //L.d(TAG,"startPreview:"+isPreview);
    
                        }
                        synchronized (mSync) {
                            mUVCCamera = camera;
                        }
                    }
                }, 0);
                mUIHandler.post(() -> {
                    mUVCCameraView.setVisibility(View.VISIBLE);
                    mUVCCameraView.bringToFront();
                });
            }
    
            @Override
            public void onDisconnect(UsbDevice usbDevice, USBMonitor.UsbControlBlock usbControlBlock) {
                L.d(TAG, "onDisconnect:");
                // XXX you should check whether the comming device equal to camera device that currently using
                queueEvent(() -> {
                    synchronized (mSync) {
                        if (mUVCCamera != null) {
                            mUVCCamera.close();
                            if (mPreviewSurface != null) {
                                mPreviewSurface.release();
                                mPreviewSurface = null;
                            }
                            isActive = isPreview = false;
                        }
                    }
                }, 0);
                mUIHandler.post(() -> mUVCCameraView.setVisibility(View.GONE));
            }
    
            @Override
            public void onCancel(UsbDevice usbDevice) {
                L.d(TAG, "onCancel:");
            }
        };
    
        private final SurfaceHolder.Callback mSurfaceViewCallback = new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(final SurfaceHolder holder) {
                L.d(TAG, "surfaceCreated:");
            }
    
            @Override
            public void surfaceChanged(final SurfaceHolder holder, final int format, final int width, final int height) {
                if ((width == 0) || (height == 0)) return;
                L.d(TAG, "surfaceChanged:");
                mPreviewSurface = holder.getSurface();
                synchronized (mSync) {
                    if (isActive && !isPreview && (mUVCCamera != null)) {
                        mUVCCamera.setPreviewDisplay(mPreviewSurface);
                        mUVCCamera.startPreview();
                        isPreview = true;
                        L.d(TAG, "startPreview:" + isPreview);
                    } else {
                        L.d(TAG, "isActive:" + isActive + ",isPreview:" + isPreview + ",mUVCCamera:" + mUVCCamera);
                    }
                }
            }
    
            @Override
            public void surfaceDestroyed(final SurfaceHolder holder) {
                L.d(TAG, "surfaceDestroyed:");
                synchronized (mSync) {
                    if (mUVCCamera != null) {
                        mUVCCamera.stopPreview();
                    }
                    isPreview = false;
                }
                mPreviewSurface = null;
            }
        };
    }

     

    展开全文
  • 为什么不用QT自带的摄像头而自己提升一个摄像头呢? 因为现在市场上大部分用的都是UVC摄像头, 因为UVC摄像头 价格低,使用方便;免驱动安装;硬件接线少 USB接口通用性强。UVC是一种摄像头协议,USB摄像头基本都是...

    为什么不用QT自带的摄像头类而自己提升一个摄像头类呢?

    因为现在市场上大部分用的都是UVC摄像头, 因为UVC摄像头 价格低,使用方便;免驱动安装;硬件接线少
    USB接口通用性强。UVC是一种摄像头协议,USB摄像头基本都是UVC协议的 很多商家都说摄像头”免驱”,并不是真正免驱动,只不过是系统自带这个UVC驱动而已,所以它们敢说”免驱”. 因为UVC摄像头使用非常非常非常广泛,所以很多系统都集成了UVC驱动。

    提升需要哪些资料支撑

    此次我们使用的是usbcamera库来操作摄像头:
    • usbcamera库非常适合操作uvc摄像头
    • usbcamera库需要ffmpeg的支持
    • usbcamera的比qt自带的QCamera类更方便
    usbcanmera库和ffmpeg资源包可以去官网下载,不想下载的小伙伴可以私信我博客找我获取也可。

    arm环境ffmpeg库交叉编译

    首先我们先拿到ffmpeg的压缩包。
    ffmpeg在这里插入图片描述
    然后上传到ubuntu,这个是 ffmpeg的源码,我们要把它编译成库。
    首先解压压缩包:

    tar -xf ffmpeg-3.4.5.tar
    

    解压之后我们要进入到解压文件中进行编译成库:

    cd ffmpeg-3.4.5/
    

    然后打开上面截图里面的文档里面有编译方法:
    在这里插入图片描述
    一、第(1)步编译源码,但是需要根据我们的自己的编译器将配置源码的命令进行修改,需要修改的地方我圈出来:
    在这里插入图片描述
    比如我自己的编译器是“ arm-cortexa9-linux-gnueabihf- ”则编译源码的命令为:

    ./configure --cross-prefix=arm-cortexa9-linux-gnueabihf- --enable-cross-compile --target-
    os=linux --cc=arm-cortexa9-linux-gnueabihf-gcc --arch=arm --prefix=host --enable-shared --
    disable-static  --disable-doc --disable-x86asm --enable-ffplay
    

    记住一定是根据自己的编译器名字进行修改。
    二、然后就是按照编译步骤进行第(2)步 make,make是编译的意思。在这里可能会比较久,大概15分钟,电脑配置高可能会快一点。
    三、编译完成之后就进行第(3)步,make install,make install就是执行 install 这个目标,也是就我们常说的安装。

    usbcamera库的使用

    usbcamera库的大概使用方法:
    ① 提升QWidget成USBCameraWidget
    ② 设置摄像头节点,采样速率
    ③ 打开摄像头
    就这么简单。
    但是
    一、首先我们要获取usbcame库,不想下载的小伙伴可以私信我获取。
    在这里插入图片描述
    二、将库放到我们要使用摄像头的QT工程里面去并解压。

    tar -xf
    

    三、解压完成后我们进入到usbcamer文件夹就要注意了,

    ls
    

    就会看到有两个文件夹分别是 include和lib 如图:
    在这里插入图片描述
    (1)拷贝ffmpeg 下的 include到usbcamera下的include。
    此时的include里面是没有东西的,我们可以进去看一下
    在这里插入图片描述
    include里面是用来放头文文件的,所以我们要把之前ffmpeg编译生的include下文件拷贝过来;
    路径在ffmpeg/host下,如果不是很明白请看图操作(前面两个cp只是tab查看路径,最后那个才是真正的copy):
    在这里插入图片描述
    (2)拷贝ffmpeg 下的lib到usbcamera下的lib。
    操作几乎和拷贝include头文件一致,直接上图看具体步骤:
    在这里插入图片描述
    四、把usbcamera库添加到我们的工程。
    首先我先给你们看一下usbcamera下的pri文件,我们只要把 ffmpeg.pri 加到我们的工程即可。
    在这里插入图片描述
    如何加pri?QT进入到 .pro 文件下输入代码:

    include($$PWD/usbcamera/ffmpeg.pri)
    

    添加了pri终于来到了我们要提升的环节,具体步骤如下:
    首先右键点击要提升的Qwidget,点击提升为:
    在这里插入图片描述
    然后我们要找到我们要提升的类名复制下来,看到没他也是继承Qwidget父类而来:
    在这里插入图片描述
    然后复制到提升窗口那里的提升类的名称框里,但是这时会自动生成头文件,注意这是一个坑:
    在这里插入图片描述
    我们要去看他的头文件是否真的就是这个名称,机智的我发现果然不是,现在我想到放逐大帝的一句话:拐的一批!

    在这里插入图片描述
    所以正确的提升应该是下面这样的:
    在这里插入图片描述
    五、这时候就可以去设置采样频率以及摄像头节点啦。
    我们只要在构造函数里输入这两句神奇的代码就搞定了:

    ui->camear->setCameraName("/dev/video15");
    ui->camear->setInterval(80);
    

    注意:你的usbcamera窗口类名可能不是camera根据自己窗口类名修改,然后你的摄像头驱动节点也要自己查看修改即 “/dev/video15”。
    六、然后就是在你想打开或者关闭的地方打开或者关闭啦。

     ui->camear->open();//打开
     ui->camear->open();//关闭
    

    七、还可以有截图拍照功能哦。
    原理我就不多讲了,直接上代码:

     connect(ui->camera,SIGNAL(receiveImage(QImage)),this,SLOT(slotCremasend(QImage)));  //链接信号槽
    
    void formcamera::slotCremasend(QImage image)
    {
        cramestatus = 1;
        pixmap = QPixmap::fromImage(image);
    
    }
    
     QPixmap pix;
        if(cramestatus == 0){
    
              qDebug("camera is not start");
              return ;
        }
    
        pix = pixmap;
    
        QString filename = QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zz");
    
        filename += ".jpg";
    
        QString path = QFileDialog::getExistingDirectory(this,tr("选择一个文件夹"), "/", 
       		QFileDialog::ShowDirsOnly|QFileDialog::DontResolveSymlinks);//和上面是一句,这是形参
    
        qDebug()<< "path=" << path +"/" +filename;
    
        if(pix.save(path +"/" +filename) == false){
    
            qDebug("pic save error");
        }
    

    好了,整体的uvc摄像头的使用就到这里啦,感谢观看,如果有什么疑问或者错误欢迎私聊小编改正,如果觉得有帮助可以点赞收藏,需要资源也可以私聊小编下载哦。谢谢啦!

    展开全文
  • 我正在尝试为符合UVC标准的USB相机编写UVC扩展单元驱动程序。我从哪里开始?替换了过程中指定的所有GUID。我使用的是罗技Pro 9000 USB摄像头。使用UVCView工具获得UVC扩展guid并分配给PROPSETID_VIDCAP_EXTENSION_...

    我正在尝试为符合UVC标准的USB相机编写UVC扩展单元驱动程序。

    我从哪里开始?

    替换了过程中指定的所有GUID。我使用的是罗技Pro 9000 USB摄像头。使用UVCView工具获得UVC扩展guid并分配给PROPSETID_VIDCAP_EXTENSION_UNIT。

    编写示例应用程序以访问此扩展单元。

    IExtensionUnit * g_pIXCcontrol;

    hr = CoCreateInstance(__uuidof(IExtensionUnit), NULL, CLSCTX_INPROC_SERVER,

    IID_IExtensionUnit, (void **)&g_pIXCcontrol);

    我面临什么问题?

    CoCreateInstance返回未注册的类HRESULT:0x80040154(REGDB_E_CLASSNOTREG)。

    我已经通过命令提示符使用regsvr32命令注册了uvcplgn.ax,这反过来又会在成功注册时抛出消息。但即使在注册之后,CoCreateInstance也会抛出类未注册的错误。

    这可能是什么原因? MSDN示例是否错过了正确注册扩展单元的内容?

    提前致谢

    展开全文
  • 1.STM32实现UVC类设备 (1)STM32F103视频传输; (2)免驱动; (3)STM32F103 USB; (4)模拟摄像头数据; (5)开源代码,拿到即用。 2.演示界面 3.代码获取 关注【一个早起的程序员】微信公众号。 ...
  • When the brightness is adjusted in the video pleayer, the corresponding callback code is shown in the UVCHandleProcessingUnitRqts function in uvc.c file. In that function, you can see ...
  • UVC 扩展单元的示例应用程序Sample application for UVC extension units09/08/2020本文内容本主题包含可用于支持扩展单元的示例应用程序代码。This topic contains sample application code that you can use to ...
  • UVC设备

    千次阅读 2018-05-31 18:11:23
    本文对USB的功能协议USB Video Class(UVC)的具体设计进行介绍,但不会介绍USB基础协议,所以需要对USB基础协议有一定的了解,包括USB四大描述符以及四种传输方式。分析usb协议的重点在于描述符,这里将以实际...
  • UVC调试

    2021-03-20 16:18:16
    Subtype为0x01,表示为VC_HEADER,UVC协议版本号为0x0100,即1.00。VC特征描述符信息返回的总字节数为0x004E,即78。时钟频率为0x00e4e1c0,即15Mhz,VS接口数为1,第一个VS接口的接口序号为1. Subtype为0x03,...
  • Linux UVC driver(uvc) 该驱动适用于符合USB视频(USB Video Class)规范的摄像头设备,它包括V4L2内核设备驱动和用户空间工具补丁。只要符合这标准,则不同厂商的USB camera设备,不需要特定的driver就能在Linux...
  • UVC协议学习2--UVC请求格式分析

    千次阅读 2018-06-30 14:58:42
    前言 大致了解一下UVC请求格式,贴出来已做备忘。一、UVC请求格式1、设置请求格式2、获取请求格式二、字段释义1、bmRequestType字段...请求命令(UVC规范中的请求属于请求命令)10 = 用户定义的请求命令11 = ...
  • Linux UVC driver

    千次阅读 2013-11-06 23:39:49
     Linux UVC driver(uvc) 该驱动适用于符合USB视频(USB Video Class)规范的摄像头设备,它包括V4L2内核设备驱动和用户空间工具补丁。大多数大容量存储器设备(如优盘)都遵循USB规范,因而仅用一个单一驱动就可以...
  • UVC协议学习1--初步了解标准协议规范(UVC1.5为例)

    万次阅读 多人点赞 2018-06-28 15:37:36
    前言 UVC是属于USB行业规范中的USB设备规范,用于USB接口的视频设备的一个统一的数据交换规范。其中UVC官方协议文档 UVC 1.5 Class specification是对整个UVC协议的框架做了规范,USB_Video_Example则规定了描述...
  • 该压缩包其中包括两个uvc官方文档,包括uvc1.1 和 uvc 1.5,都为usb video class开发官方文档,有需要的可以进行学习和理解。
  • UVC设备枚举简述

    2021-03-23 10:33:07
    UVC设备枚举简述 定义 usb设备按工作模式可分为: host(主机) device(从机) 连接过程: 建立物理连接 ...建立物理连接:host和...UVC设备,即USB视频设备,工作于device工作模式。 枚举 1 Get_Device_Descri
  • uvc和v4l2简介

    2020-07-23 14:47:17
    UVC全称为USB Video Class,即:USB视频,是一种为USB视频捕获设备定义的协议标准。 UVC是一个开放的标准,拥有维护良好的驱动,它属于内核代码的一部分。插入摄像头后就可以工作,而无须编译或安装额外的驱动。 ...
  • 安卓系统采用v4l2接口打开YUYV和MJPEG摄像头,支持热插拔。 v4l2接口支持两种摄像头数据格式“V4L2_PIX_FMT_YUYV”和“V4L2_PIX_FMT_MJPEG”,网上大部分示例给出的是采用“V4L2_PIX_FMT_YUYV”格式的, ...

空空如也

空空如也

1 2 3 4 5 6
收藏数 119
精华内容 47
关键字:

uvc类