一、說明
近兩年來直播行業(yè)越來越火,各個直播平臺加一起差不多300多家。有些直播平臺做秀場、綜娛類的直播(來瘋直播),有的做游戲直播(熊貓直播),有的做體育賽事的直播(樂視直播),分類也各種各樣。下面一張圖很好地反映了國內(nèi)直播平臺的大致分類。
本人有幸參與到了來瘋Android手機(jī)直播的研發(fā),本著技術(shù)分享的精神,現(xiàn)在寫一系列的文章來介紹安卓手機(jī)直播,一方面希望能幫助大家了解Android手機(jī)直播相關(guān)的技術(shù),另一方面也當(dāng)作是自己工作一段時間的總結(jié)。
注:1、這系列文章只涉及Android手機(jī)直播相關(guān)知識;2、整個項(xiàng)目已經(jīng)開源(開源地址)
二、總體
如果讓大家馬上去開發(fā)一款A(yù)ndroid直播應(yīng)用,大家可能感覺到無從下手,因此從總體上了解整個手機(jī)直播過程是十分重要的。
手機(jī)直播需要實(shí)現(xiàn)的無非是將手機(jī)采集到的視頻和音頻經(jīng)過處理后以一定的格式發(fā)送到服務(wù)器端,整個過程如下所示:
三、采集
采集主要包括兩個方面:視頻采集和音頻采集。視頻通過攝像頭進(jìn)行采集,這里面涉及到攝像頭的相關(guān)操作以及攝像頭的參數(shù)設(shè)置,由于各個手機(jī)廠商的攝像頭存在差異,因此這方面有一些坑,之后的講攝像頭的文章中會一一講述。音頻通過麥克風(fēng)進(jìn)行采集,不同手機(jī)的麥克風(fēng)對音頻采樣率的支持不同,而且有時候?yàn)榱酥С诌B麥功能需要對音頻進(jìn)行回聲消除。
視頻采集技術(shù)要點(diǎn):
- 檢測攝像頭是否可以使用;
- 攝像頭采集到的圖像是橫向的,需要對采集到的圖像進(jìn)行一定的旋轉(zhuǎn)后再進(jìn)行顯示;
- 攝像頭采集時有一系列的圖像大小可以選擇,當(dāng)采集的圖像大小和手機(jī)屏幕大小比例不一致時,需要進(jìn)行特殊處理;
- Android手機(jī)攝像頭有一系列的狀態(tài),需要在正確的狀態(tài)下才能對攝像頭進(jìn)行相應(yīng)的操作;
- Android手機(jī)攝像頭的很多參數(shù)存在兼容性問題,需要較好地處理這些兼容性的問題。
音頻采集技術(shù)要點(diǎn):
- 檢測麥克風(fēng)是否可以使用;
- 需要檢測手機(jī)對某個音頻采樣率的支持;
- 在一些情況下需要對音頻進(jìn)行回聲消除處理;
- 音頻采集時設(shè)置正確的緩沖區(qū)大小。
注:之后會有專門的文章對采集方面進(jìn)行講述
四、處理
視頻處理
美顏現(xiàn)在幾乎是一個手機(jī)直播軟件的標(biāo)配,經(jīng)過美顏后主播顏值更高,對粉絲也就更有吸引力,也有一些安卓直播應(yīng)用可以對主播進(jìn)行人臉識別,然后添加好玩的動畫特效,有些時候我們也需要對視頻添加水印。
其實(shí)對視頻進(jìn)行美顏和添加特效都是通過OpenGL進(jìn)行處理的。Android中有GLSurfaceView,這個類似于SurfaceView,不過可以利用Renderer對其進(jìn)行渲染。通過OpenGL可以生成紋理,通過紋理的Id可以生成SurfaceTexture,而SurfaceTexture可以交給Camera,最后通過紋理就將攝像頭預(yù)覽畫面和OpenGL建立了聯(lián)系,從而可以通過OpenGL進(jìn)行一系列的操作。
美顏的整個過程無非是根據(jù)Camera預(yù)覽的紋理通過OpenGL中FBO技術(shù)生成一個新的紋理,然后在Renderer中的onDrawFrame()使用新的紋理進(jìn)行繪制。添加水印也就是先將一張圖片轉(zhuǎn)換為紋理,然后利用OpenGL進(jìn)行繪制。添加動態(tài)掛件特效則比較復(fù)雜,先要根據(jù)當(dāng)前的預(yù)覽圖片進(jìn)行算法分析識別人臉部相應(yīng)部位,然后在各個相應(yīng)部位上繪制相應(yīng)的圖像,整個過程的實(shí)現(xiàn)有一定的難度。
下圖是整個美顏過程的流程圖:
下面的圖片很好地展示了美顏和動畫效果。
注:之后會有專門的文章講述OpenGL以及整個過程的實(shí)現(xiàn)
音頻處理
在一些情況下,主播需要添加一些額外的聲音以增加直播氣氛,比如:鼓掌聲等等。一種處理方式是讓附加的聲音直接播放出來,這樣麥克風(fēng)會采集到然后一起錄制,但是這樣的處理在主播戴上耳機(jī)或者需要對聲音進(jìn)行回聲消除處理的情況下就不能起到作用。由于我們項(xiàng)目中也未加入相應(yīng)功能,暫時未有相關(guān)經(jīng)驗(yàn)進(jìn)行分享,之后我們可能會加上這個功能,到時候再和大家分享。
五、編碼
通過攝像頭和麥克風(fēng)我們可以采集到相應(yīng)的視音頻數(shù)據(jù),但是這些是固定格式的原始數(shù)據(jù),一般來說攝像頭采集到的是一幀一幀畫面,而麥克風(fēng)采集的是PCM音頻數(shù)據(jù)。如果直接將這些數(shù)據(jù)進(jìn)行發(fā)送,這樣往往會數(shù)據(jù)量很大,造成很大的帶寬浪費(fèi),因此在發(fā)送前往往需要對視音頻進(jìn)行編碼。
視頻編碼
1、預(yù)測編碼
眾所周知,一幅圖像由許多個所謂像素的點(diǎn)組成,大量的統(tǒng)計表明,同一幅圖像中像素之間具有較強(qiáng)的相關(guān)性,兩個像素之間的距離越短,則其相關(guān)性越強(qiáng),通俗地講,即兩個像素的值越接近。于是,人們可利用這種像素間的相關(guān)性進(jìn)行壓縮編碼,這種壓縮方式稱為幀內(nèi)預(yù)測編碼。不僅如此,鄰近幀之間的相關(guān)性一般比幀內(nèi)像素間的相關(guān)性更強(qiáng),壓縮比也更大。由此可見,利用像素之間(幀內(nèi))的相關(guān)性和幀間的相關(guān)性,即找到相應(yīng)的參考像素或參考幀作為預(yù)測值,可以實(shí)現(xiàn)視頻壓縮編碼。
2、變換編碼
大量統(tǒng)計表明,視頻信號中包含著能量上占大部分的直流和低頻成分,即圖像的平坦部分,也有少量的高頻成分,即圖像的細(xì)節(jié)。因此,可以用另一種方法進(jìn)行視頻編碼,將圖像經(jīng)過某種數(shù)學(xué)變換后,得到變換域中的圖像(如圖所示),其中 u,v 分別是空間頻率坐標(biāo)。
3、基于波形的編碼
基于波形的編碼采用了把預(yù)測編碼和變換編碼組合起來的基于塊的混合編碼方法。為了減少編碼的復(fù)雜性,使視頻編碼操作易于執(zhí)行,采用混合編碼方法時,首先把一幅圖像分成固定大小的塊,例如塊 8×8(即每塊 8 行,每行 8 個像素)、塊 16×16(每塊 16 行,每行 16 個像素)等等,然后對塊進(jìn)行壓縮編碼處理。
自 1989 年 ITU-T 發(fā)布第一個數(shù)字視頻編碼標(biāo)準(zhǔn)——H.261 以來,已陸續(xù)發(fā)布了 H.263 等視頻編碼標(biāo)準(zhǔn)及 H.320、H.323 等多媒體終端標(biāo)準(zhǔn)。ISO 下屬的運(yùn)動圖像專家組(MPEG)定義了 MPEG-1、MPEG-2、MPEG-4 等娛樂和數(shù)字電視壓縮編碼國際標(biāo)準(zhǔn)。
2003 年 3 月份,ITU-T 頒布了 H.264 視頻編碼標(biāo)準(zhǔn)。它不僅使視頻壓縮比較以往標(biāo)準(zhǔn)有明顯提高,而且具有良好的網(wǎng)絡(luò)親和性,特別是對 IP 互聯(lián)網(wǎng)、無線移動網(wǎng)等易誤碼、易阻塞、QoS 不易保證的網(wǎng)絡(luò)視頻傳輸性能有明顯的改善。 所有這些視頻編碼都采用了基于塊的混合編碼法,都屬于基于波形的編碼。
4、基于內(nèi)容的編碼
還有一種基于內(nèi)容的編碼技術(shù),這時先把視頻幀分成對應(yīng)于不同物體的區(qū)域,然后對其編碼。具體說來,即對不同物體的形狀、運(yùn)動和紋理進(jìn)行編碼。在最簡單情況下,利用二維輪廓描述物體的形狀,利用運(yùn)動矢量描述其運(yùn)動狀態(tài),而紋理則用顏色的波形進(jìn)行描述。
當(dāng)視頻序列中的物體種類已知時,可采用基于知識或基于模型的編碼。例如,對人的臉部,已開發(fā)了一些預(yù)定義的線框?qū)δ樀奶卣鬟M(jìn)行編碼,這時編碼效率很高,只需少數(shù)比特就能描述其特征。對于人臉的表情(如生氣、高興等),可能的行為可用語義編碼,由于物體可能的行為數(shù)目非常小,可獲得非常高的編碼效率。
MPEG-4 采用的編碼方法就既基于塊的混合編碼,又有基于內(nèi)容的編碼方法。
5、軟編與硬編
在Android平臺上實(shí)現(xiàn)視頻的編碼有兩種實(shí)現(xiàn)方式,一種是軟編,一種是硬編。軟編的話,往往是依托于cpu,利用cpu的計算能力去進(jìn)行編碼。比如我們可以下載x264編碼庫,寫好相關(guān)的jni接口,然后傳入相應(yīng)的圖像數(shù)據(jù)。經(jīng)過x264庫的處理以后就將原始的圖像轉(zhuǎn)換成為h264格式的視頻。
硬編則是采用Android自身提供的MediaCodec,使用MediaCodec需要傳入相應(yīng)的數(shù)據(jù),這些數(shù)據(jù)可以是yuv的圖像信息,也可以是一個Surface,一般推薦使用Surface,這樣的話效率更高。Surface直接使用本地視頻數(shù)據(jù)緩存,而沒有映射或復(fù)制它們到ByteBuffers;因此,這種方式會更加高效。在使用Surface的時候,通常不能直接訪問原始視頻數(shù)據(jù),但是可以使用ImageReader類來訪問不可靠的解碼后(或原始)的視頻幀。這可能仍然比使用ByteBuffers更加高效,因?yàn)橐恍┍镜鼐彺婵梢员挥成涞?direct ByteBuffers。當(dāng)使用ByteBuffer模式,可以利用Image類和getInput/OutputImage(int)方法來訪問到原始視頻數(shù)據(jù)幀。
注:之后的文章會具體講述如何進(jìn)行視頻編碼
音頻編碼
Android中利用AudioRecord可以錄制聲音,錄制出來的聲音是PCM聲音。想要將聲音用計算機(jī)語言表述,則必須將聲音進(jìn)行數(shù)字化。將聲音數(shù)字化,最常見的方式是透過脈沖編碼調(diào)制PCM(Pulse Code Modulation) 。聲音經(jīng)過麥克風(fēng),轉(zhuǎn)換成一連串電壓變化的信號。要將這樣的信號轉(zhuǎn)為 PCM 格式的方法,是使用三個參數(shù)來表示聲音,它們是:聲道數(shù)、采樣位數(shù)和采樣頻率。
1、采樣頻率
即取樣頻率,指每秒鐘取得聲音樣本的次數(shù)。采樣頻率越高,聲音的質(zhì)量也就越好,聲音的還原也就越真實(shí),但同時它占的資源比較多。由于人耳的分辨率很有限,太高的頻率并不能分辨出來。在16位聲卡中有22KHz、44KHz等幾級,其中,22KHz相當(dāng)于普通FM廣播的音質(zhì),44KHz已相當(dāng)于CD音質(zhì)了,目前的常用采樣頻率都不超過48KHz。
2、采樣位數(shù)
即采樣值或取樣值(就是將采樣樣本幅度量化)。它是用來衡量聲音波動變化的一個參數(shù),也可以說是聲卡的分辨率。它的數(shù)值越大,分辨率也就越高,所發(fā)出聲音的能力越強(qiáng)。
在計算機(jī)中采樣位數(shù)一般有8位和16位之分,但有一點(diǎn)請大家注意,8位不是說把縱坐標(biāo)分成8份,而是分成2的8次方即256份; 同理16位是把縱坐標(biāo)分成2的16次方65536份。
3、聲道數(shù)
很好理解,有單聲道和立體聲之分,單聲道的聲音只能使用一個喇叭發(fā)聲(有的也處理成兩個喇叭輸出同一個聲道的聲音),立體聲的pcm可以使兩個喇叭都發(fā)聲(一般左右聲道有分工) ,更能感受到空間效果。
那么,現(xiàn)在我們就可以得到pcm文件所占容量的公式:
存儲量=(采樣頻率 ?? 采樣位數(shù) ?? 聲道 ?? 時間)? 8 (單位:字節(jié)數(shù))
如果音頻全部用PCM的格式進(jìn)行傳輸,則占用帶寬比較大,因此在傳輸之前需要對音頻進(jìn)行編碼。
現(xiàn)在已經(jīng)有一些廣泛使用的聲音格式,如:wav、MIDI、MP3、WMA、AAC、Ogg等等。相比于pcm格式而言,這些格式對聲音數(shù)據(jù)進(jìn)行了壓縮處理,可以降低傳輸帶寬。
對音頻進(jìn)行編碼也可以分為軟編和硬編兩種。軟編則下載相應(yīng)的編碼庫,寫好相應(yīng)的jni,然后傳入數(shù)據(jù)進(jìn)行編碼。硬編則是使用Android自身提供的MediaCodec。
注:之后的文章會具體講述如何進(jìn)行音頻編碼
六、打包
視音頻在傳輸過程中需要定義相應(yīng)的格式,這樣傳輸?shù)綄Χ说臅r候才能正確地被解析出來。
1、HTTP-FLV
Web 2.0時代,要說什么類型網(wǎng)站最火,自然是以國外的Youtube,國內(nèi)的優(yōu)酷、土豆網(wǎng)站了。這類網(wǎng)站提供的視頻內(nèi)容可謂各有千秋,但它們無一例外的都使用了Flash作為視頻播放載體,支撐這些視頻網(wǎng)站的技術(shù)基礎(chǔ)就是——Flash 視頻(FLV) 。FLV 是一種全新的流媒體視頻格式,它利用了網(wǎng)頁上廣泛使用的Flash Player 平臺,將視頻整合到Flash動畫中。也就是說,網(wǎng)站的訪問者只要能看Flash動畫,自然也能看FLV格式視頻,而無需再額外安裝其它視頻插件,F(xiàn)LV視頻的使用給視頻傳播帶來了極大便利。
HTTP-FLV即將音視頻數(shù)據(jù)封裝成FLV,然后通過HTTP協(xié)議傳輸給客戶端。而作為上傳端只需要將FLV格式的視音頻傳輸?shù)椒?wù)器端即可。
一般來說FLV格式的視音頻,里面視頻一般使用h264格式,而音頻一般使用AAC-LC格式。
FLV格式是先傳輸FLV頭信息,然后傳輸帶有視音頻參數(shù)的元數(shù)據(jù)(Metadata),然后傳輸視音頻的參數(shù)信息,然后傳輸視音頻數(shù)據(jù)。
注:之后的文章會詳細(xì)講述FLV
2、RTMP
RTMP是Real Time Messaging Protocol(實(shí)時消息傳輸協(xié)議)的首字母縮寫。該協(xié)議基于TCP,是一個協(xié)議簇,包括RTMP基本協(xié)議及RTMPT/RTMPS/RTMPE等多種變種。RTMP是一種設(shè)計用來進(jìn)行實(shí)時數(shù)據(jù)通信的網(wǎng)絡(luò)協(xié)議,主要用來在Flash/AIR平臺和支持RTMP協(xié)議的流媒體/交互服務(wù)器之間進(jìn)行音視頻和數(shù)據(jù)通信。
RTMP協(xié)議是Adobe公司推出的實(shí)時傳輸協(xié)議,主要用于基于flv格式的音視頻流的實(shí)時傳輸。得到編碼后的視音頻數(shù)據(jù)后,先要進(jìn)行FLV包裝,然后封包成rtmp格式,然后進(jìn)行傳輸。
使用RTMP格式進(jìn)行傳輸,需要先連接服務(wù)器,然后創(chuàng)建流,然后發(fā)布流,然后傳輸相應(yīng)的視音頻數(shù)據(jù)。整個發(fā)送是用消息來定義的,rtmp定義了各種形式的消息,而為了消息能夠很好地發(fā)送,又對消息進(jìn)行了分塊處理,整個協(xié)議較為復(fù)雜。
注:之后的文章會詳細(xì)講述RTMP
還有其他幾種形式的協(xié)議,比如RTP等等,大致原理差不多,也就不一一進(jìn)行說明講述了。
七、差網(wǎng)絡(luò)處理
好的網(wǎng)絡(luò)下視音頻能夠得到及時的發(fā)送,不會造成視音頻數(shù)據(jù)在本地的堆積,直播效果流暢,延時較小。而在壞的網(wǎng)絡(luò)環(huán)境下,視音頻數(shù)據(jù)發(fā)送不出去,則需要我們對視音頻數(shù)據(jù)進(jìn)行處理。差網(wǎng)絡(luò)環(huán)境下對視音頻數(shù)據(jù)一般有四種處理方式:緩存區(qū)設(shè)計、網(wǎng)絡(luò)檢測、丟幀處理、降碼率處理。
1、緩沖區(qū)設(shè)計
視音頻數(shù)據(jù)傳入緩沖區(qū),發(fā)送者從緩沖區(qū)獲取數(shù)據(jù)進(jìn)行發(fā)送,這樣就形成了一個異步的生產(chǎn)者消費(fèi)者模式。生產(chǎn)者只需要將采集、編碼后的視音頻數(shù)據(jù)推送到緩沖區(qū),而消費(fèi)者則負(fù)責(zé)從這個緩沖區(qū)里面取出數(shù)據(jù)發(fā)送。
上圖中只顯示了視頻幀,顯然里面也有相應(yīng)的音頻幀。要構(gòu)建異步的生產(chǎn)者消費(fèi)者模式,java已經(jīng)提供了很好的類,由于之后還需要進(jìn)行丟幀、插入、取出等處理,顯然LinkedBlockingQueue是個很不錯的選擇。
2、網(wǎng)絡(luò)檢測
差網(wǎng)絡(luò)處理過程中一個重要的過程是網(wǎng)絡(luò)檢測,當(dāng)網(wǎng)絡(luò)變差的時候能夠快速地檢測出來,然后進(jìn)行相應(yīng)的處理,這樣對網(wǎng)絡(luò)反應(yīng)就比較靈敏,效果就會好很多。
我們這邊通過實(shí)時計算每秒輸入緩沖區(qū)的數(shù)據(jù)和發(fā)送出去數(shù)據(jù),如果發(fā)送出去的數(shù)據(jù)小于輸入緩沖區(qū)的數(shù)據(jù),那么說明網(wǎng)絡(luò)帶寬不行,這時候緩沖區(qū)的數(shù)據(jù)會持續(xù)增多,這時候就要啟動相應(yīng)的機(jī)制。
3、丟幀處理
當(dāng)檢測到網(wǎng)絡(luò)變差的時候,丟幀是一個很好的應(yīng)對機(jī)制。視頻經(jīng)過編碼后有關(guān)鍵幀和非關(guān)鍵幀,關(guān)鍵幀也就是一副完整的圖片,而非關(guān)鍵幀描述圖像的相對變化。
丟幀策略多鐘多樣,可以自行定義,一個需要注意的地方是:如果要丟棄P幀(非關(guān)鍵幀),那么需要丟棄兩個關(guān)鍵幀之間的所有非關(guān)鍵幀,不然的話會出現(xiàn)馬賽克。對于丟幀策略的設(shè)計因需求而異,可以自行進(jìn)行設(shè)計。
4、降碼率
在Android中,如果使用了硬編進(jìn)行編碼,在差網(wǎng)絡(luò)環(huán)境下,我們可以實(shí)時改變硬編的碼率,從而使直播更為流暢。當(dāng)檢測到網(wǎng)絡(luò)環(huán)境較差的時候,在丟幀的同時,我們也可以降低視音頻的碼率。在Android sdk版本大于等于19的時候,可以通過傳遞參數(shù)給MediaCodec,從而改變硬編編碼器出來數(shù)據(jù)的碼率。
Bundle bitrate = new Bundle();bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bps * 1024);
mMediaCodec.setParameters(bitrate);
八、發(fā)送
經(jīng)過各種處理,最后需要將數(shù)據(jù)發(fā)送出去,這一步較為簡單。無論是HTTP-FLV,還是RTMP,我們這邊都是使用TCP建立連接的。直播開始之前需要通過Socket連接服務(wù)器,驗(yàn)證是否能連接服務(wù)器,連接之后便使用這個Socket向服務(wù)器發(fā)送數(shù)據(jù),數(shù)據(jù)發(fā)送完畢后關(guān)閉Socket。
九、鏈接
Android手機(jī)直播(一)總覽
Android手機(jī)直播(二)攝像機(jī)
Android手機(jī)直播(三)聲音采集
Android手機(jī)直播(四)Android Media API