从Linux设备上的USB摄像机捕获视频

背景


前段时间,我很想从著名的“坦克大战”中“改进”一辆坦克,增加了像我是坦克手一样的比赛能力。阅读了有关哈布雷的几篇文章后,这个想法就出现了(例如,在这里:geektimes.ru/post/257528),在其中我发现了如何使用小型WiFi路由器和USB摄像头来完成此操作。解决方案看起来非常简单:路由器使用特殊固件进行刷新,将摄像机连接到其上,储罐由本地遥控器控制,并且可以在浏览器中查看视频。快速组装好原型后,我发现视频的录制质量令人作呕。它是320x440x30或640x480x30。开启1280x720模式时,充其量是一部带有伪像的撕裂视频,最糟糕的是根本没有。1920x1080模式原则上不起作用。这让我非常不高兴,因为在PC上,相机支持高达1920x1080x30的模式,并且具有硬件MJPG压缩。我的直觉表明,实现还远远不够完美。

目标


  1. 具有FullHD(1920X1080)或HD(1280x720)分辨率和正常帧速率的视频(以便可以播放)。
  2. 我计划将玩具送给孩子,所以我需要自动启动并支持连接/断开相机的连接。

总的来说,我想要这样的东西:



局限性


我不会寻找一个始终适用于任何地方的解决方案。以下限制非常适合我:

  1. 好的wifi信号。
  2. 连接数量有限,只有一个客户端时优先考虑。
  3. 相机支持MJPG模式。

硬件和软件


  1. Logitech B910高清摄像机(http://www.logitech.com/zh-cn/product/b910-hd-webcam)。
  2. 路由器TP-LINK TL-MR3020。这个孩子有以下硬件:CPU MIPS 24K 400MHz,RAM 32 MiB,Flash 4 MiB,以太网100 Mbit,USB 2.0(http://wiki.openwrt.org/en/toh/tp-link/tl-mr3020)。
  3. . OR-WRT (http://roboforum.ru/wiki/OR-WRT), OpenWRT (http://openwrt.org/, 12.07 15.05).
  4. . , .
  5. “ ”.


通常,这是一个非常弱的配置,尤其是当您回想起YUV420格式的1920X1080大小的帧占用4 MiB(每像素2个字节)时。我被鼓励使用该相机支持硬件MJPG压缩。实验表明,压缩的FullHD帧通常小于500 KiB。所以我决定继续研究。事实证明,要捕获视频并通过HTTP进行流传输,使用了mjpg-streamer(http://sourceforge.net/projects/mjpg-streamer/)。对他的代码的分析表明,他使用1个流来捕获视频+为每个客户端使用一个单独的流。这不是单核系统的最佳解决方案,因为它需要线程同步和每个线程的堆栈内存。他还复制了捕获的帧。通常,mjpg-streamer成为嫌疑犯#1。

有趣的发现


研究mjpg-streamer时,我发现Linux上的视频捕获是使用v4l2库完成的,并且使用缓冲区队列来捕获它。在调试mjpg-streamer中的这些缓冲区的初始化时,我发现即使对于MJPG模式,它们的大小也非常大,并且意外地与未压缩帧的大小一致。因此,我开始怀疑我是否必须进入负责支持摄像机的UVC驱动程序代码。

驱动程序代码分析和首次成功


通过研究代码,我得出的结论是,缓冲区的大小由相机询问,而我的相机返回了未压缩帧的大小。从相机开发人员的角度来看,这可能是最安全的解决方案。但这也不是最佳的。我决定针对我的情况,可以使用实验性最小压缩率来调整所需的缓冲区大小。我选择k = 5 有了这个值,我的利润约为20%。

一个小题外话。
, JPG. . , .

UVC驱动程序代码已准备就绪,可以添加各种“特殊”解决方案,并且我很容易找到了调整缓冲区大小的地方(函数uvc_fixup_video_ctrl())。此外,驱动程序支持一组怪癖,可让您支持与UVC标准有各种偏差的相机。通常,驱动程序的开发人员已尽力支持动物园的相机。

通过添加缓冲区大小校正,我在1280x720模式甚至1920x1080模式下都可以稳定运行。万岁!问题解决了一半!

寻找新的冒险


我对最初的成功感到有些满意,我记得mjpg-streamer远非完美。当然,您可以做一些简单的事情,不像mjpg-streamer那样通用,但是更适合我的情况。因此,我决定制作uvc2http。

在mjpg-streamer中,我不喜欢使用多个流和复制缓冲区。这就决定了解决方案的体系结构:1个流,没有副本。使用非阻塞IO,这非常简单:完成捕获帧并将其发送到客户端,而无需复制。有一个小问题:当我们从缓冲区发送数据时,我们无法将缓冲区返回到队列。并且当缓冲区不在队列中时,驱动程序无法将新帧放入队列中。但是,如果队列大小> 1,则有可能。缓冲区的数量确定可以保证服务的最大连接数。也就是说,如果我想保证支持1个客户端,那么3个缓冲区就足够了(驱动程序写入一个缓冲区,数据从第二个缓冲区发送,第三个缓冲区有库存,以避免在尝试获取新帧时与驱动程序争夺缓冲区)。

Uvc2http


Uvc2http由两个组件组成:UvcGrabber和HttpStreamer。第一个负责从队列接收缓冲区(帧),并将其返回到队列。第二个负责通过HTTP为客户端提供服务。还有一些链接这些组件的代码。可以在源代码中找到详细信息。

意外的问题


一切都很棒:该应用程序正常工作,并且以1280x720的分辨率产生了20+帧/秒的速度。我对代码进行了外观上的更改。在另一批更改之后,我测量了帧速率。结果令人沮丧-少于15帧。我急忙寻找导致退化的原因。我可能花了2个小时,在此期间,每次测量的频率都会降低到7帧/秒的值。关于路由器由于长时间工作而导致的性能下降,我想到了不同的想法。这是难以理解的。在某个时候,我关闭了流传输,看到只有一个捕获(没有流传输)给出了相同的7帧。我什至开始怀疑相机有问题。一般来说,有些废话。当时是晚上,相机打开了窗户,露出灰色的东西。为了改变阴暗的图像,我打开了房间内的相机。瞧!帧速率提高到15,我了解了一切。相机会自动调整曝光时间,并且在某个时候变得比给定频率下的帧持续时间更长。在这两个小时内,发生了以下事情:首先,天色渐渐黑了(是晚上),然后我把照相机照进了光亮的房间。将相机对准枝形吊灯,我得到了20+帧/秒的速度。万岁。


  1. . 1-1.5 .
  2. . , , qv4l2, . : - . .
  3. . USB , ( ) ( ). USB ( ).
  4. 路由器的内存和磁盘空间很少。因此,我拒绝了OR-WRT并编译了我的OpenWRT映像,从中删除了所有多余的内容。

结果


以下是比较mjpg-streamer和uvc2http的结果的图板。简而言之,内存消耗显着增加,而帧速率和CPU利用率则获得小幅增长。
1280x7201920x1080
VSZ,KB,1个客户端VSZ,KB,2个客户端CPU,%,1个客户端CPU,%,2个客户端FPS,f / s,1个客户端FPS,f / s,2个客户端VSZ,KB,1个客户端VSZ,KB,2个客户端CPU,%,1个客户端CPU,%,2个客户端FPS,f / s,1个客户端FPS,f / s,2个客户端
Mjpg流媒体1686019040264317.6十五254562581228五十13.810
uvc2http3960396026432219.675767576284315.512.2

当然还有我和孩子们一起制作的视频:



产生的坦克的照片(结果像是一辆吉普赛车):



使用


资料在这里要在PC Linux上使用,只需要编译即可(前提是您不想修补UVC驱动程序)。该实用程序以标准方式使用CMake构建。如果需要在OpenWRT中使用它,则需要采取其他步骤:

  1. 将OpenWrt-15.05目录的内容复制到OpenWRT存储库的根目录。这些文件仅适用于OpenWRT 15.05。他们描述了OpenWRT的新软件包和UVC驱动程序的补丁。
  2. , quirk UVC_QUIRK_COMPRESSION_RATE uvc_driver.c. UVC. , wiki.openwrt.org/doc/devel/patches. uvc_ids. :

    /* Logitech B910 HD Webcam */
    	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
    				| USB_DEVICE_ID_MATCH_INT_INFO,
    	  .idVendor		= 0x046d,
    	  .idProduct		= 0x0823,
    	  .bInterfaceClass	= USB_CLASS_VIDEO,
    	  .bInterfaceSubClass	= 1,
    	  .bInterfaceProtocol	= 0,
    	  .driver_info		= UVC_QUIRK_RESTORE_CTRLS_ON_INIT
    				| UVC_QUIRK_COMPRESSION_RATE }, // Enable buffer correction for compressed modes
    

  3. OpenWRT (http://wiki.openwrt.org/doc/howto/build). uvc2http Multimedia.
  4. uvc2http ( ) . , .
  5. 在设备上安装软件包/更新系统

下一步是什么


该解决方案包括两部分:一个驱动程序补丁和另一个流算法。驱动程序补丁可能包含在新版本的Linux内核中,但这是一个有争议的决定,因为它基于最小压缩率的假设。在我看来,该实用程序非常适合在较弱的系统(玩具,家庭视频监视系统)上使用,并且可以通过添加通过参数指定摄像机设置的功能来对其稍作改进。

由于在CPU负载和通道宽度上有余量(我很容易从连接十个客户端的路由器上收到50+ MBit),因此可以改进流算法。您还可以添加声音支持。

Source: https://habr.com/ru/post/zh-CN386273/


All Articles