HEXA開發(fā)日志目錄
上一篇 HEXA娛樂開發(fā)日志技術(shù)點(diǎn)002——下位機(jī)成功獲取彈幕
成果
目前,我手中的HEXA機(jī)器人可以用RTMP協(xié)議推流flv文件了,即用flv作為視頻源做直播,并且此功能已在奧點(diǎn)云和B站試驗(yàn)成功。如果要推實(shí)時視頻,還需要一些后續(xù)工作,這次只說flv文件推流的事。
源碼版本812171d9eb56768c18d525b6e3c61b1ae7c315ee
目前的控制端網(wǎng)頁長這樣,在推流測試的地址中填入rtmp服務(wù)器地址后,點(diǎn)擊“推”即可啟動demo,并開始推流。
這是目前這個skill的目錄結(jié)構(gòu),本次主要添加了一個rtmp.go和deps目錄下的一堆依賴文件。
DanmuDriveMe
├── manifest.json
├── remote
│ └── index.html==>控制端網(wǎng)頁
├── robot
│ ├── assets
│ │ └── test.flv==>用于測試的flv文件
│ ├── deps
│ │ ├── include==>C文件,包括librtmp庫頭文件和我對librtmp庫的封裝
│ │ │ ├── amf.h
│ │ │ ├── log.h
│ │ │ ├── rtmp.h
│ │ │ └── rtmp_sample_api.c==>我對librtmp庫的簡單封裝
│ │ └── lib==>依賴庫,包括librtmp所依賴的openssl、zlib庫
│ │ ├── libcrypto.so -> libcrypto.so.1.0.0
│ │ ├── libcrypto.so.1.0.0
│ │ ├── librtmp.so -> librtmp.so.1
│ │ ├── librtmp.so.1
│ │ ├── libssl.so -> libssl.so.1.0.0
│ │ ├── libssl.so.1.0.0
│ │ ├── libz.so -> libz.so.1
│ │ ├── libz.so.1 -> libz.so.1.2.11
│ │ └── libz.so.1.2.11
│ └── src==>go代碼
│ ├── Danmu.go==>彈幕相關(guān)
│ ├── DanmuDriveMe.go
│ └── rtmp.go==>推流相關(guān)
從文件角度,目前的依賴或調(diào)用關(guān)系大概是這樣
index.html ==> DanmuDriveMe.go ==> rtmp.go ==> rtmp_sample_api.c ==> librtmp.so ==> libssl.so+libcrypto.so+libz.so
How To
在做之前,先理清一下關(guān)于步驟的邏輯。
- 要先在電腦上跑通
推流要使用開源C工程rtmpdump中的librtmp庫,在不熟悉它的情況下,最好先在電腦上demo成功,同時在此過程中了解rtmpdump的使用。 - 要封裝librtmp庫
封裝是為了方便給Go程序調(diào)用,如果不封裝的話,就要在Go程序中使用很多復(fù)雜的C數(shù)據(jù)類型,這會帶來很多麻煩,因此要對librtmp庫進(jìn)行簡單封裝以保持與Go對接的C接口和數(shù)據(jù)類型都足夠簡單。 - 用Go程序測試這個封裝
這點(diǎn)不必多說,前面的簡單封裝就是為了方便Go程序調(diào)用的。 - 本次最終目標(biāo)——下位機(jī)推流
這里有個大前提,即這是一個嵌入式平臺,所以不管搞什么,都要針對平臺來做,所有C庫,都必須交叉編譯才能用。包括librtmp庫和它所依賴的庫,統(tǒng)統(tǒng)要交叉編譯。
前3步的工作體現(xiàn)在我的rtmpdump工程源碼的以下文件中
- demo.c(要看這個文件的前一個版本,此版本已經(jīng)使用封裝的接口了)是第1步的成果,參考的是雷霄驊,張暉的例子源碼
- rtmp_sample_api.c和rtmp_sample_api.h是第2步的工作成果
- demo.go是第3步的工作成果,使用Go程序調(diào)用了第2步的封裝接口
在上位機(jī)demo rtmpdump
這項(xiàng)內(nèi)容在網(wǎng)上資料很多,這里只講一些值的一說的點(diǎn)。
- 雷霄驊前輩引用的是librtmp/rtmp_sys.h,至少在我使用的rtmpdump版本中,這不是一個public頭文件,使用自己編的庫還好,要是使用安裝到系統(tǒng)的librtmp庫,會找不到這個頭文件。
- librtmp提供了兩個推流API,它們分別是RTMP_Write和RTMP_SendPacket,雷前輩的demo中都有展示,我選擇了RTMP_Write的方式,因?yàn)槲业膮⒖家罁?jù)是OBS Studio的方式,參考一個成熟的開源應(yīng)用會靠譜一點(diǎn),并且如果出了什么問題相當(dāng)于有個官方可以參考,可以借助OBS開發(fā)者們的智慧,這也是開源軟件的魅力。
- 編譯問題要仔細(xì)讀rtmpdump的README,針對自己平臺指定make參數(shù),不要上來就一個make。
上位機(jī)封裝與Go程序
如果熟悉了librtmp庫的使用,封裝就很簡單,這里就不多說了,只說一些稍微值得一提的點(diǎn)吧。
- 關(guān)于CGO
據(jù)我所知,Go語言調(diào)用C至少有兩個工具可用,它們分別是CGO和SWIG,SWIG是mind SDK官方例子中OpenCVSkill中的opencv采用的封裝方法,具體不太了解。
我用的是CGO,用法也很簡單,和gcc基本編譯的那些知識很容易對應(yīng)上。下面是一個典型的Go代碼文件結(jié)構(gòu),如果經(jīng)常用gcc的話,應(yīng)該很容易理解下面注釋的意思。
package <包名>
/*
#cgo LDFLAGS: ld參數(shù),比如-L...和-l...之類的,和Makefile常見的寫法一樣的
#cgo CFLAGS: gcc參數(shù),比如-I...之類的,也合Makefile常見寫法一樣的
//此處寫C代碼完全是C語法
void c_fun(){}
*///此處和下一句import "C"之間不可以有空行
import "C"
import(
<其他包>
...
)
func go_func_name() {
C.c_fun() //調(diào)用C函數(shù)
}
- 數(shù)據(jù)類型轉(zhuǎn)換
網(wǎng)上有很多對Go和C基本數(shù)據(jù)類型轉(zhuǎn)換的文章,我不想展開討論。我這里就做個記錄,下面這段[]byte轉(zhuǎn)char *的代碼寫得讓我不舒服,希望將來可以優(yōu)化。
//buf 是[]byte類型
cc := C.CString(string(buf))
//rtmp_sample_add_data第一個參數(shù)要char *類型
C.rtmp_sample_add_data(cc, C.int(datalength)+15)
C.free(unsafe.Pointer(cc))
下位機(jī)推流
有了上位機(jī)的demo之后,下位機(jī)代碼就很好寫了,所以這個步驟的難點(diǎn)不在于代碼,而在于如何交叉編譯動態(tài)鏈接庫并正確引用他們。
如何建立交叉編譯環(huán)境?
如果想效仿官方做法,一個方法是讀mind SDK的源碼,我這么做得到了以下認(rèn)知:
- 官方使用了Docker
這當(dāng)然是廢話,因?yàn)橐婚_始就要求開發(fā)者安裝了Docker,關(guān)鍵是怎么使用。我對它不是很了解,我只知道它可以幫我建立類似虛擬機(jī)的環(huán)境。當(dāng)然,在自己平臺上建立一個交叉編譯環(huán)境也不是不可能,只是相當(dāng)麻煩而已。
官方的mind SDK的mind x
命令實(shí)際上就是運(yùn)行了一個Docker容器(類似虛擬機(jī)),并在這個容器環(huán)境里執(zhí)行mind x
后面跟的命令。如果可以用mind x搞定很多事,但是也有一些不便之處。總之,經(jīng)過研究,我誤入歧途地得到了構(gòu)造并進(jìn)入一個官方Docker容器的命令,即
docker run -it -v <主機(jī)絕對路徑>:/go/src/skill vincross/mindcli bash
上述命令中-v
后面跟的分別是主機(jī)(電腦)和容器(類似虛擬機(jī))的路徑,-v
實(shí)際上類似于mount命令,這樣一來,在容器中,我們可以直接對主機(jī)文件進(jìn)行操作;vincross/mindcli
大概是容器的類型,這里指定使用官方容器類型;bash
應(yīng)該是在容器中運(yùn)行的程序,意思應(yīng)該是要啟動bash shell。 - 交叉編譯器
我繞了些彎路,終于在mind-sdk/xcompile/Dockerfile中看到了這一句ENV CROSS arm-linux-gnueabihf
,雖然我不了解Dockerfile的機(jī)制,但是猜得出這句話的意思,然后就好辦了,在Docker容器中安裝它就行了,安裝命令是
apt-get install gcc-arm-linux-gnueabihf
要交叉編譯啥?
當(dāng)然是rtmpdump啦,先來個make SYS=posix
嘗嘗,立刻發(fā)現(xiàn)<openssl/ssl.h>
找不到,搞定openssl之后,再來,會發(fā)現(xiàn)<zlib.h>
找不到。如果先讀了README就明白了,rtmpdump依賴openssl和zlib庫,所以我得把rtmpdump
、openssl
和zlib
都交叉編譯成動態(tài)庫。
zlib編譯
下載源碼,然后執(zhí)行
CC=arm-linux-gnueabihf-gcc ./configure --prefix=<安裝目錄的絕對路徑>
make
make install
openssl編譯
下載源碼,下載后要注意兩點(diǎn)
- 選擇版本,很可能由于API變動,最新版不和rtmpdump不能匹配,所以要預(yù)先知道版本,切換到那個版本的tag上再編譯。一個確定版本的方法是,自己的上位機(jī)openssl版本或者Docker容器中的openssl版本,因?yàn)樵谇懊娴牡谝粋€步驟中,已經(jīng)證明了系統(tǒng)中的openssl是可以和rtmpdump匹配的,用它準(zhǔn)沒錯。
- 如果build了錯誤版本,再build rtmpdump時發(fā)現(xiàn)一些奇怪的問題,要換openssl版本的話,一定要先刪除.gitignore之后再
git clean -fd
,因?yàn)橛行﹊gnore的文件是會影響編譯的。
我在這吃了一些虧,最后發(fā)現(xiàn)了openssl API變動的修改,原本公開的結(jié)構(gòu)體不再公開了,那么rtmpdump中使用這個結(jié)構(gòu)體的地方就編不過了。
確定好版本后,編譯命令如下:
AR="arm-linux-gnueabihf-ar" RANLIB=arm-linux-gnueabihf-ranlib CC=arm-linux-gnueabihf-gcc /usr/bin/perl ./Configure shared linux-armv4 --prefix=<安裝目錄的絕對路徑>
make
make install
rtmpdump編譯
下載源碼,完整的make命令我先不給出來,因?yàn)橐米约壕幍膐penssl和zlib庫,makefile和make命令改得有點(diǎn)亂,一是目前處于demo階段,二是預(yù)計(jì)很長時間內(nèi),我沒有必要再交叉編譯librtmp庫,所以這里偷個懶,只是強(qiáng)調(diào)一下,認(rèn)真讀README,都能搞定的!
導(dǎo)入庫到skill
這里看一下rtmp.go中開頭部分對CGO的配置,再結(jié)合庫文件和C文件的位置就明白了。
一個特別的地方是#include "../deps/include/rtmp_sample_api.c"
這一句,這里直接棄用了.h文件,也只是偷個懶,因?yàn)樵谇懊婊靵y的研究中,只引用頭文件的話,好像會找不到.c文件,索性就直接引用.c文件了,也可能只是個誤會,不過我最近感冒,暫時懶得研究了。
總結(jié)和計(jì)劃
- 上次說的“調(diào)通OpenCV,來驗(yàn)證C/C++庫的調(diào)用”太簡單了,沒什么好講的
- 完成了rtmp推流的上位機(jī)和下位機(jī)的demo
- 交叉編譯和Go調(diào)用C的套路基本清楚了
- 官方framework中,可以分別獲得視頻的YCbCr raw data和音頻的raw data,這樣的數(shù)據(jù)不能直接交給librtmp庫推流,需要經(jīng)過壓縮編碼,所以下一步要研究的是如何用ffmpeg的庫將raw data壓縮成flv格式的包,并交給librtmp庫推流
- 還有些事情可以開始種草了,包括彈幕命令集、機(jī)器人命名等