HEXA開發日志目錄
上一篇 HEXA娛樂開發日志技術點003——下位機成功推流
顛覆
本篇的成果是,實現了基于raw data的推流。
前一篇是基于flv文件的推流,flv文件的內容是對raw data進行壓縮編碼的產物,而我們從mind SDK獲得的是raw data,是不能用RTMPDUMP直接推流的,所以才有了本篇內容,并對前一篇的內容進行了顛覆,這個顛覆要從下圖說起。
這張圖相對于之前的版本有些變化,左邊技術需求多了個壓縮編碼,右邊技術點中的RTMPDUMP被ffmpeg取代了,右邊這個變化就是本篇所指的顛覆了。
需求的變化
當初對RTMPDUMP不了解,不清楚它要的是壓縮編碼后的數據,所以沒考慮到壓縮編碼這個必要的數據轉換步驟。
技術點的顛覆
前一篇的部分成果被顛覆的原因是,我只知道ffmpeg可以壓縮編碼,但不知道它還可以直接推流,當我看到雷前輩的例子時,我驚呆了,因為里面沒有一點RTMPDUMP的影子,卻說它可以推流。
在驗證了雷前輩的例子之后,我當時認為,大概是ffmpeg使用了RTMPDUMP的庫吧,因為它們的git地址都出現了ffmpeg.org,既然它們是整整齊齊的一家人,那至少應該是相互有關聯的一群人做出來的吧。對于rtmp推流的功能不會單獨做出兩份相同功能的東西吧。因為我的系統里裝了RTMPDUMP的庫,所以還僥幸地認為多半ffmpeg和RTMPDUMP之間有依賴關系,直到我把skill中的RTMPDUMP庫刪除,并確認機器人身體里確實沒有了這個庫,還依舊能正常推流之后,我才放棄了僥幸。
這徹底宣告了,我對RTMPDUMP的相關研究,對于目標來說是無用功。
這就是積極前進的人生的常態吧,在對昨天的自己的嘲笑中前進,明知道今天的自己也會被明天的自己嘲笑,但若不做今天的無用功,也無法獲得明天嘲笑今天的資格。
干貨
回顧了昨天的無用功,再來看看今天又搞出了什么幺蛾子。
首先明確一下本篇目標,就是把從raw data到推流這條路打通,這里的raw data還不是從mind SDK獲得的,只是用官方的算法制造的。步驟還是經典三板斧,即研究、上位機demo、移植到下位機。
研究ffmpeg
其實算不上什么研究,主要是搞清楚上位機demo的步驟,在考慮如何入手之前,先列一下已知條件:
下面對已知逐一分析:
- 首先,先看一下我研究過的那個例子,這個例子只能編碼視頻,沒有音頻,我并不能拿來用。只是在這個例子的研究中,我知道了如何用新ffmpeg接口替換掉過時的接口。
- 然后,我確定了我需要參考的官方例子,這個例子把raw data壓成了flv文件,而前一篇正好是以flv文件作為輸入的,我當時想,一定可以在這兩者之間建立數據連接,而不必生成flv文件,這樣輸入是raw data,輸出是推流,本篇的目標就達到了。這時的產出是61dd400e0323bd242e5242ec01608c594373cde0。在這個修改中,我把這個例子的過時接口都換成了新的,并且編譯OK了,然后待用。
- 最后,我看到了雷前輩的例子,從這個例子可以看出,我根本不用考慮如何與RTMPDUMP建立數據連接,只要對例子改一下輸出方式,就可以直接推流啦。
至此,第一板斧砍完。
上位機demo
這個階段就三步,先驗證例子的有效性,再驗證改造的例子的推流功能的有效性,最后用GO語言調用測試。
第一步很順利,沒有單獨提交。
第二步,即C語言部分的提交是f51f070eeb2b3a0a7827519d58d6a3c27bb7ce23,可以看到修改很簡單,如果真去嫁接RTMPDUMP,工作量得比這個大得多得多得多。。。
第三步,加入GO語言部分的提交是cf0d3e304a5a824c6912a8e3b2b13975c990e8c8,其中的demo_ffmpeg.c是基于test.c修改的,主要是把生產raw data的函數改成了call back,這樣就可以在GO語言里實現raw data的生成,至少從C部分來看,整個流程就和最后實際的使用方式完全一樣了。而對于GO語言部分,后面再把生產raw data的方法改成從攝像頭和話筒獲取就行了,當然這是后話了。
至此,第二板斧砍完。
移植到下位機
移植也是之前的套路,先編譯C依賴庫,然后GO程序與mind SDK對接。
ffmpeg的交叉編譯
這里先感謝某位哥們兒的幫助,把他以一般Ubuntu為目標平臺的編譯方法提供給我做參考。
可惜的是,后面還是有東西要探索,最關鍵的就是交叉編譯所帶來的麻煩,最后的configure命令長這樣:
./configure --prefix="/go/src/skill/FFmpeg" \
--extra-cflags="-I/go/src/skill/FFmpeg/include" \
--extra-ldflags="-L/go/src/skill/FFmpeg/lib" \
--bindir="/go/src/skill/FFmpeg/bin" \
--enable-cross-compile \
--cross-prefix=arm-linux-gnueabihf- \
--cc=arm-linux-gnueabihf-gcc \
--disable-x86asm --arch=armv4 --target-os=linux \
--disable-static --enable-shared \
--disable-ffmpeg --disable-ffplay --disable-ffprobe
make
make install
- 帶有
cross
字樣的都與交叉編譯有關,還有cc指定了交叉編譯器 - 要生成動態庫,
--enable-shared
是要加的,我不需要靜態庫就加了--disable-static
-
-disable-ffmpeg -disable-ffprobe -disable-ffplay
是說不需要生成這些可執行程序,因為我只需要依賴庫
相對于對接mind SDK,編譯ffmpeg還是麻煩一點,以為它的編譯選項比較復雜,有些選項好不好用、該不該用,都變成了很隨緣的事情,這些選項也是我慢慢摸索出來的。
GO程序對接mind SDK
關于對接mind SDK,修改是ca44359aebc9d2729cd9213f6c126a9be1722c34,雖然文件多,但是并不復雜。
- 增加ffmpeg的頭文件(DanmuDriveMe/robot/deps/include下面多個文件)
- 增加ffmpeg的庫文件(DanmuDriveMe/robot/deps/lib下面多個文件)
- 刪除RTMPDUMP相關的文件,包括頭文件、庫文件和GO文件
- ffmpeg demo對接mind SDK,這些是核心修改,只有5個文件
邏輯和上位機demo大同小異,關于對接mind SDK不做詳細解釋了,只說一個GO與C混合編程的小坑,一個坑了我若干小時的小坑,具體看下面注釋吧。
func stream_start(url string) {
//這個cc是C語言部分的buffer
cc := C.CString(url)
//defer是說在即將退出這個函數時執行后面語句,即在退出stream_start函數之前會把cc free掉
defer C.free(unsafe.Pointer(cc))
if stream_control(true) {
log.Debug.Println("url:["+url+"]")
//setup是C函數,里面會使用cc
go C.setup(cc)
//go語句是非阻塞的,如果沒有這個sleep,stream_start函數就會很快返回,這意味著cc很快會free
// avoid to free cc too early
time.Sleep(time.Second)
}
}
要注意上面代碼中的2件事的順序,即cc
的釋放和使用的順序,我被坑的原因就是,cc
先被釋放了,然后才被使用,并且我還不知道怎么才能看到C部分的log,對C部分的debug手段很笨拙,所以花很長時間才懷疑到這。
cc
被釋放的原因是stream_start
函數很快返回了,那么基于defer
語句的機制,cc
就很快被釋放了,而go
語句是非阻塞的,從cc
傳入setup
到被使用,需要一段時間,所以函數最后要加一個sleep,保證在這段時間內,函數不會返回,cc
也就不會在使用前被釋放。
當然,還應該有其他解決方法,比如不在GO這邊釋放cc
,在C部分釋放也許也能行。
至此,第三板斧砍完。
總結
- 用ffmpeg替代了RTMPDUMP,一步到位地完成了壓縮編碼和推流
- 下位機推流還有點問題,可以在播放端看到,似乎是推流的速度不夠,出現了視頻網站那種放一會兒就要緩沖一下的現象,具體原因有待分析
- 下面計劃是對機器人采集的音視頻raw data進行推流,以及用OpenCV處理圖像
- 這些事情的草還沒發芽,包括彈幕命令集、機器人命名等