如何利用VisionSeed+树莓派,实现智能小车实时图传系统?
导语 | 所谓图传,就是把相机模组捕捉到的画面,实时传输到另一个可接收该数据的设备上,并且在该设备上进行实时播放。本文将介绍如何基于 VisionSeed 和 Raspberry Pi(4B)搭建一套完整的图传系统,希望与大家一同交流。文章作者:毛江云,腾讯优图实验室研发工程师。
一、概念介绍
1. Raspberry Pi
树莓派 [1]其实不用笔者过多介绍,这应该是做的最成功的开源硬件芯片,深受技术和数码爱好者们的拥护。下图摘自淘宝某店家的中文说明图,总之第四代比第三代功能强了很多,而且好多接口都与时俱进了。
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 的 UVC 视频采集,FFMPEG 编码、推流服务器推流、FFPLAY 解码、以及显示器显示。其中推流服务器推流还包括服务器内部 buffer 缓存、网络数据包拼接和组装、以及网络包的传输等。
2. FFMPEG 编码和推流
那么,该 怎么捕捉 VisionSeed 摄像头的视频呢 ?
VisionSeed 上的摄像头是 UVC。 UVC 全称 Usb Video Class ,是一种标准的 USB 视频设备协议,也就是传说中的免驱摄像头。也就是说,这款摄像头可以通过 USB 即插即用,不需要安装驱动。
于是,我们用一根 TypeC 数据线把 VisionSeed 和树莓派连接在一起。如下图所示:
登录 Raspberry Pi,打开 Terminal,查看 UVC 的状态:$ lsusb。如果出现下图红框的部分,就说明 UVC 被系统识别了。
然后,查看 UVC 被挂在哪个节点上:$ ls /dev/video*:
也可以用 v4l2-ctl 进一步仔细查看 /dev/video* 的信息,如命令 v4l2-ctl --device=/dev/video0 --all 可以查看到摄像头的细节信息,命令 v4l2-ctl --list-devices 查看系统中所有的设备等。
”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 还能在原来基础上减少路由器转发的环节。网络拓扑图如下图所示,优化的链路环节是把蓝色虚线替换了红色虚线和实线
接下来,跟着官方教程一步步走:
(1)升级 apt-get 工具
建议国内的小伙伴们替换一下源,笔者早就已经替换到了清华源,教程网上很多,推荐树莓派 3b 更换国内源 [11] 。
/etc/apt/sources.list 修改为:
deb http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ stretch main contrib non-free rpi
deb-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 ui
deb-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 wlan0
static ip_address=192.168.1.1/24 # 官方给的是 192.168.0.10/24,这个自己灵活配置,这个是这台树莓派在它提供出去的 AP 网络里面的 ip 地址
denyinterfaces eth0
denyinterfaces 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=CN
hw_mode=a # g-2.4GHZ;a-5GHZ
channel=149 # 5G 的信道,网上有的写 36 有的是 0 各种,推荐查看下面的信道示意图
wmm_enabled=1
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
ssid=${YOUR_NETWORK_NAME} # 对外暴露的 wifi 名称,请注意不要和已有网络重名
wpa_passphrase=${YOUR_NETWORK_PSWD} # wifi 的密码
ieee80211n=1
ieee80211d=1
ieee80211ac=1
其实做到这一步之后,基本就达到目标了。一个可用的无线 AP 就搭建好了。客户端只需要连接到刚刚设置的网络,就可以和这台 Raspberry Pi 通信。
终于,笔者把端到端的延时从十几秒优化到 400 ms 左右(一把辛酸泪)!最后,如果您有更多的优化方案,欢迎留言与我讨论~
参考资料:
[1] Raspberry Pi:
https://www.raspberrypi.org/
[2] VisionSeed:
https://visionseed.youtu.qq.com/#/home
[3] 疲劳驾驶监测仪:
https://zhuanlan.zhihu.com/p/77190381
[4] OPENMAX 标准:
https://zh.wikipedia.org/wiki/OpenMAX
[5] FFmpeg Formats Documentation:
https://ffmpeg.org/ffmpeg-formats.html
[6] H.264 Video Encoding Guide:
https://trac.ffmpeg.org/wiki/Encode/H.264
[7] ffplay Documentation:
https://ffmpeg.org/ffplay-all.html
[8] -autoexit 参数修改参考:
http://ffmpeg.org/pipermail/ffmpeg-devel/2020-August/268749.html
[9] -timeout 参数修改参考:
https://www.jianshu.com/p/e75e3f1fb6b0
[10] How to use your Raspberry Pi as a wireless access point:
https://thepi.io/how-to-use-your-raspberry-pi-as-a-wireless-access-point/
[11] 树莓派 3b 更换国内源:
https://my.oschina.net/TimeCarving/blog/1622950
作者:毛江云
来源:微信公众号-云加社区
文章讲解的很详细 赞