如何利用VisionSeed+樹莓派,實現智能小車實時圖傳系統?

導語 | 所謂圖傳,就是把相機模組捕捉到的畫面,實時傳輸到另一個可接收該數據的設備上,並且在該設備上進行實時播放。本文將介紹如何基於 VisionSeed 和 Raspberry Pi(4B)搭建一套完整的圖傳系統,希望與大家一同交流。文章作者:毛江雲,騰訊優圖實驗室研發工程師。

http://mpvideo.qpic.cn/0bf2jibziaadfaagfy2wr5pvgswdsrfahfaa.f10002.mp4?dis_k=27248bd1e2107c7bcfb47d671741019f&dis_t=1600673227&vid=wxv_1507883883827150849

VisionSeed智能小車實際跑圈演示視頻

一、概念介紹

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

本文轉載自公衆號雲加社區(ID:QcloudCommunity)。

原文鏈接

如何利用VisionSeed+樹莓派,實現智能小車實時圖傳系統?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章