如何利用VisionSeed+树莓派,实现智能小车实时图传系统?

一、概念介绍



1. Raspberry Pi


树莓派[1]其实不用笔者过多介绍,这应该是做的最成功的开源硬件芯片,深受技术和数码爱好者们的拥护。下图摘自淘宝某店家的中文说明图,总之第四代比第三代功能强了很多,而且好多接口都与时俱进了。
如何利用VisionSeed+树莓派,实现智能小车实时图传系统?

2. VisionSeed


VisionSeed[2] 是腾讯优图推出的一款具备 AI 功能的摄像头模组,产品如下图所示。它的体型很小,有点类似 Raspberry Pi Zero,不过麻雀虽小五脏俱全。
右边是整块 VisionSeed 的核心模块,包括 2 个摄像头(一个 UVC 摄像头,一个红外摄像头),剩下一整块都是 AI 计算单元;左边是控制板块,主要是对外的接口,如串口、TypeC 接口。两块直接通过 FP C连接起来。

VisionSeed 模组可以搭载很多 CV 的 AI 能力,目前官方已经推出的有疲劳驾驶监测仪[3],笔者目前参加的智能小车就是正在孵化的另一个项目,期待越来越多的 AI 爱好者们参与进来,把 VisionSeed “玩出花”。


二、系统搭建



本文所介绍的是利用 VisionSeed 和 Raspberry Pi(4B) 搭建的一套基于 FFMPEG 编码+SRS+WIFI 协议+RTMP 协议+FFPLAY 解码播放的完整图传系统,该实时图传全过程示意图如下所示:

1. RTMP推流服务器
推流服务器怎么选择?其实推流服务器有很多种选型,具体该选择哪种比较好?笔者结合自身经验给出 nginx-rtmp 服务和 srs 服务的使用心得和实际对比:
如何利用VisionSeed+树莓派,实现智能小车实时图传系统?
补充说明一下延时这块:首先笔者给出的具体延迟时间并非真正服务器推流的延迟,而是端(VisionSeed 采集到视频)到端(播放设备播放视频)的延迟。
这中间涉及到的环节多且复杂:VisionSeed 的 UVC 视频采集,FFMPEG 编码、推流服务器推流、FFPLAY 解码、以及显示器显示。其中推流服务器推流还包括服务器内部 buffer 缓存、网络数据包拼接和组装、以及网络包的传输等。
2. FFMPEG编码和推流
那么,该怎么捕捉 VisionSeed 摄像头的视频呢
VisionSeed 上的摄像头是 UVC。UVC 全称 Usb Video Class,是一种标准的 USB 视频设备协议,也就是传说中的免驱摄像头。也就是说,这款摄像头可以通过 USB 即插即用,不需要安装驱动。
于是,我们用一根 TypeC 数据线把 VisionSeed 和树莓派连接在一起。如下图所示:
如何利用VisionSeed+树莓派,实现智能小车实时图传系统?
登录 Raspberry Pi,打开 Terminal,查看 UVC 的状态:$ lsusb。如果出现下图红框的部分,就说明 UVC 被系统识别了。
如何利用VisionSeed+树莓派,实现智能小车实时图传系统?
然后,查看 UVC 被挂在哪个节点上:$ ls /dev/video*:
如何利用VisionSeed+树莓派,实现智能小车实时图传系统?
也可以用 v4l2-ctl 进一步仔细查看 /dev/video* 的信息,如命令 v4l2-ctl --device=/dev/video0 --all 可以查看到摄像头的细节信息,命令 v4l2-ctl --list-devices 查看系统中所有的设备等。
如何利用VisionSeed+树莓派,实现智能小车实时图传系统?
”Linux 系统一切皆文件“,也就是说,系统是直接把 /dev/video0 当作文件来进行处理,也就是在 ffmpeg 的 -i 的参数。
那么,怎么用 FFMPEG 做编码呢
虽然本地可以直接用 ffplay 播放 UVC 的原始流,但是要走 RTMP 协议做图传的话,必须要使用编码流。因此必须在搭载 VisionSeed 的 Raspberry Pi 上做编码,然后推流出去。
FFMPEG CMD 功能强大:
  • -i :指定文件输入路径;

  • -an :指不处理音频数据;

  • -vcodec:指定视频 codec;

  • -f:指定视频输出格式。


下述命令就是把从 /dev/video0 获取到的原始视频流编码成 flv,并保存成本地文件 test.flv。


ffmpeg -i /dev/video0 -an -vcodec h264 -f flv rtmp://localhost:${port}/${api}

综上,FFMPEG 对 UVC 原始视频流做编码就完成了。
3. FFPLAY 收流
必须要确保客户端设备和推流端的 Raspberry Pi 处于同一个局域网下,互相可以联通。
笔者因为项目需要,客户端也选择了 Raspberry Pi +显示屏。但是实际上,选择手上的笔记本或者台式机就可以,只要确保安装了 FFMPEG(FFMPEG、FFPLAY和FFPROBE是打包的)。
那么,如何用 FFPLAY 实时显示 RTMP 视频呢
笔者原本以为播放端出不了什么问题,万万没想到 FFPLAY 的问题竟然无比的“坑”。首先,很多类似的网站教程提供的播放命令大多是:ffplay -i rtmp://${ip}:${port}/${api}。
这个命令存在相当大的延时,导致笔者最开始就走错了方向。ffplay 内部有 buffer,因此看到的播放画面其实是好几十秒之前的!
ffplay 播放时间长了会有累积延时,也就是越播放到后来,延时越大。并且画面时不时出现卡顿、有时候还会发现画面帧率不稳定,时快时慢。当推流端/服务端断开时,ffplay画面就卡住了,超过 2 min 也并不会退出。
这些问题该怎样解决呢?下文将会来详细讨论。


三、优化延时



按照以上的流程搭建好之后,就可以在客户端上看到 VisionSeed 的视频画面了。但是,延时巨大,主观感受至少有10s的延迟,所以还需要进一步做优化。
1.  FFMPEG 硬解码
细心的同学在使用 FFMPEG 做编码的时候,应该发现实际编码推流的帧率大约在 18 左右,运行到后来大概稳定在 10 左右,笔者这边的情况如下图所示:

但是,VisionSeed(关闭算法功能后)的原始视频帧率是 28 fps,分辨率是1280x720。由此可见 Raspberry Pi 4B 的 CPU 编码速率跟不上,必须要优化。
虽然这个 CMD 没有开启多线程,但是根据笔者经验来看,即使多线程开满了,也很难满足性能要求。
要知道 Raspberry Pi 4B 是有专用编解码模块的,官方号称性能是 1080p@30fps 的编码能力,而端侧开发就是要“榨干”每一个芯片模组的过程。
查了资料后了解到,Raspberry Pi 的硬解码支持 OPENMAX 标准[4],这是一种类似 vaapi 等多媒体硬件加速的统一接口,因此可以直接用 h264_max 来调用底层硬解码,然后再推送到推流服务器上,命令如下:


ffmpeg -i /dev/video0 -an -vcodec h264_omx -f flv rtmp://localhost:${port}/${api}

此外,对实时性要求再高一点的同学,不妨再多了解些 FFMPEG 的参数,参见 FFmpeg Formats Documentation[5] 和 H.264 Video Encoding Guide[6],笔者下面摘录一些跟效率相关的参数,大家可以选择使用(如果有更多未列出来的,欢迎大家留言补充):







### 延时相关- fflags nobuffer # 减少由于buffer带来的延时,能够做到即时处理。- fflags flush_packets # 马上把packets刷出来。(实际好像没有对降低延时带来作用)- analyzeduration ${整型值|时间} # 流分析时间,数值越长,得到的流信息越多、准确,但是延时上升,默认值是5秒。(对编码流会起作用,原始流应该没啥作用)- max_delay ${整型值|时间} # 设置(解)封装的最大延时。(对封装格式的编码流有作用,原始流应该没啥作用)- framerate ${整型值|时间} # 输入视频的码率,默认值是25。(建议可以用-re,这个是用输入视频的码率)

而笔者最后使用的命令如下:





ffmpeg -r 28 -fflags nobuffer -fflags flush_packets -i /dev/video0 -vf fps=fps=28 -an -vcodec h264_omx -preset slower -tune zerolatency -max_delay 10 -r 28 -video_size 1280x720 -g 50 -b:v 8192k -f flv "rtmp://{RTMPIP}:{RTMPPORT}/live/1"

2. 用 srs 推流服务器,开启优化参数
从最终结果上来看,替换了 srs 服务器之后,时延确实比用 nignx-rtmp 提升了 400 ms。
但是这到底是因为 srs 确实比 nginx-rtmp 优秀呢,还是因为笔者打开 nginx-rtmp 方式不正确,还有待讨论。对这块有了解的大佬们,欢迎留言告知,不胜感激!
修改 srs.conf 如下:










































listen              1935; # rtmp端口 !!可以修改为自己的端口号!!max_connections     1000;srs_log_tank        file;srs_log_file        ./objs/srs.log;http_api {    enabled         on;    listen          1985;}http_server {    enabled         on;    listen          80;    dir             ./objs/nginx/html;}stats {    network         0;    disk            sda sdb xvda xvdb;}vhost __defaultVhost__ {    #最小延迟打开,默认是打开的,该选项打开的时候,mr默认关闭。    min_latency     on;    #Merged-Read,针对RTMP协议,为了提高性能,SRS对于上行的read使用merged-read,即SRS在读写时一次读取N毫秒的数据    mr {        enabled     off;        #默认350ms,范围[300-2000]        #latency     350;    }    #Merged-Write,SRS永远使用Merged-Write,即一次发送N毫秒的包给客户端。这个算法可以将RTMP下行的效率提升5倍左右,范围[350-1800]    mw_latency      100;    #enabled         on;    #https://github.com/simple-rtmp-server/srs/wiki/v2_CN_LowLatency#gop-cache    gop_cache       off;    #配置直播队列的长度,服务器会将数据放在直播队列中,如果超过这个长度就清空到最后一个I帧    #https://github.com/simple-rtmp-server/srs/wiki/v2_CN_LowLatency#%E7%B4%AF%E7%A7%AF%E5%BB%B6%E8%BF%9F    queue_length    10;    #http_flv配置    http_remux {      enabled     on;      mount [vhost]/[app]/[stream].flv;      hstrs  on;    }}

配置修改之后的确实时性得到了很大的提升。
3. 优化 FFPLAY
上文出现的问题,在这里也为大家一一解答。

问题一:ffplay 内部有 buffer,因此看到播放的画面是好几十秒之前的。


解决方法:关闭 buffer!
参考 ffplay Documentation[7],参数 -fflags nobuffer(FFMPEG命令里面也有这个参数)是最关键的。笔者最后采用了:





ffplay -autoexit -fflags nobuffer -fflags flush_packets -flags low_delay -noframedrop -strict very -analyzeduration 600000 -i rtmp://192.168.1.1:2020/live/1

问题二:ffplay 播放时间长了会有累积延时,也就是越播放到后来,延时越大。并且画面时不时出现卡顿、有时候还会发现画面帧率不稳定,时快时慢。


解决方法:自从用了 srs,累积延时的问题就没有了。至于画面不稳定的问题,可能和网络有关,笔者后面也会提到怎么在树莓派上搭建无线 AP 来提供专有无线局域网。
替换到无线 AP 之后,画面卡顿的情况会好很多。但是经过长时间的观察,还是会有帧率不稳定的情况。

问题三:当推流端/服务端断开时,ffplay 画面就卡主了!超过 2 min 也并不会退出。


解决方法:这个其实就是 FFPLAY 的bug !其实,ffplay 提供了几个参数,一个是 -autoexit,但是它对 RTMP 还有 RTSP 都不起作用,当流断开或者网断开的时候, ffplay 还是卡住的。
要想解决就必须修改源码,重新编译 ffplay。修改办法可以参考文档[8] 。
另一个是 -timeout 参数,但是一旦加上它,ffplay 就跑不起来,具体原因参考文章[9]。
4. Raspberry Pi 上搭建无线 AP
怎么基于 Raspberry Pi 搭建无线 AP 是有官方教程的:How to use your Raspberry Pi as a wireless access point[10]。但是,官方教程是有坑的,下文将重点介绍哪些坑需要避开。
无线 AP,全称 Wireless Access Point,其实就是常说的 WIFI 热点,生活中的路由器也是一种无线 AP 设备。
在我们的环境中,可能会在没有无线网环境下,甚至在网络条件很糟糕的环境下。另外Raspberry Pi 4B 本身自带了无线网卡,因此不妨用它搭建无线 AP,客户端直接接入它的网络就可以接受它的流数据。
而且,从网络链路上来说,无线 AP 还能在原来基础上减少路由器转发的环节。网络拓扑图如下图所示,优化的链路环节是把蓝色虚线替换了红色虚线和实线。
如何利用VisionSeed+树莓派,实现智能小车实时图传系统?
接下来,跟着官方教程一步步走:
(1)升级 apt-get 工具
建议国内的小伙伴们替换一下源,笔者早就已经替换到了清华源,教程网上很多,推荐树莓派 3b 更换国内源[11] 。
/etc/apt/sources.list 修改为:




deb http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ stretch main contrib non-free rpideb-src http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ stretch main contrib non-free rpi

/etc/apt/sources.list.d/raspi.list修改为:




deb http://mirror.tuna.tsinghua.edu.cn/raspberrypi/ stretch main uideb-src http://mirror.tuna.tsinghua.edu.cn/raspberrypi/ stretch main ui

(2)安装 hostapd 和 dnsmasq
hostapd 就是对外提供热点的主要服务,dnsmasq 则是负责 dns 和 dhcp 作用的。如果操作完毕后,没有搜索到自己设置的热点 WIFI,大概率是 hostapd 的问题(原因:hostapd 没有工作,因此没有对外提供 AP);如果搜到了热点网络,但是一直连不上的话,大概率是 dnsmasq 的问题(原因:dnsmasq 没有工作,没办法为客户端分配 ip)。
Raspberry Pi 为自己分为静态 IP。笔者这里的设置如下:






interface wlan0static ip_address=192.168.1.1/24 # 官方给的是192.168.0.10/24,这个自己灵活配置,这个是这台树莓派在它提供出去的AP网络里面的ip地址denyinterfaces eth0denyinterfaces wlan0

(3)配置 DHCP
笔者这里的配置如下:




interface=wlan0  dhcp-range=192.168.1.6,192.168.1.12,255.255.255.0,24h # ip范围自定义就可以

(4)修改 hostapd 配置
这一个步骤是最容易出问题的步骤,而且官方提供的配置在笔者的环境中并不能起作用。笔者给出自己的配置,并在注释中说明为什么这么配置:




















interface=wlan0#bridge=br0    # 这个一定要去掉,因为笔者不需要做桥接(不需要外网)。country_code=CNhw_mode=a    # g-2.4GHZ;a-5GHZchannel=149    # 5G的信道,网上有的写36有的是0各种,推荐查看下面的信道示意图wmm_enabled=1macaddr_acl=0auth_algs=1ignore_broadcast_ssid=0wpa=2wpa_key_mgmt=WPA-PSKwpa_pairwise=TKIPrsn_pairwise=CCMPssid=${YOUR_NETWORK_NAME} # 对外暴露的wifi名称,请注意不要和已有网络重名wpa_passphrase=${YOUR_NETWORK_PSWD} # wifi的密码ieee80211n=1ieee80211d=1ieee80211ac=1


如何利用VisionSeed+树莓派,实现智能小车实时图传系统?
其实做到这一步之后,基本就达到目标了。一个可用的无线 AP 就搭建好了。客户端只需要连接到刚刚设置的网络,就可以和这台 Raspberry Pi 通信。
终于,笔者把端到端的延时从十几秒优化到 400 ms 左右(一把辛酸泪)!最后,如果您有更多的优化方案,欢迎留言与我讨论~


上一篇:ffmpeg源码分析1-概述


下一篇:CF1456D. Cakes for Clones