NO IMAGE

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

直播與短視訊相繼爆發,也促使眾多企業紛紛加入其中,對於許多傳統企業和中小企業而言音視訊開發成為了最大難點,而視訊雲客戶端SDK也就無疑成為了不錯的選擇。本文是全民快樂研發高階總監展曉凱在LiveVideoStackCon 2017上分享的整理,主要從架構設計、模組的拆分實現、跨平臺視訊處理系統和推流系統的構建幾部分著重介紹。

演講 / 展曉凱

整理 / LiveVideoStack

我是來自全民快樂的展曉凱,曾就職於淘寶開發機票搜尋,在唱吧上線之初加入,經歷了唱吧從上線到擁有4億使用者的整個過程,在此期間負責唱吧音視訊的開發,其中涉及多個產品線,包括唱吧、唱吧直播間、火星等產品。目前在全民快樂負責直播產品線業務,主要面向海外市場。

在唱吧和全民快樂多年的音視訊技術積累,展曉凱也在近期發售了業內第一本音視訊移動端開發書籍《音視訊開發進階指南——基於Android和iOS平臺的實踐》。(文末有彩蛋)

本次分享將從以下幾部分來介紹視訊雲客戶端SDK的設計與實現:音視訊領域的發展,SDK的核心應用場景,視訊錄製器和視訊播放器模組的拆分,跨平臺視訊處理系統和推流系統的構建,以及未來的機遇與挑戰。

視訊雲客戶端SDK發展和核心場景

音視訊架構與開發的演進歷經很長時間,大致可以分為以下幾個過程:最開始是廣電領域,也就是給電視臺提供直播以及轉碼等服務;後來擴充套件到了PC端的音視訊領域;而近幾年則是在移動端音視訊領域發展比較火熱。

對每一個視訊雲廠商,除了提供持續、穩定、高可用的線上服務外,它其實也提供了客戶端的SDK,以方便客戶在不瞭解音視訊細節的條件下,也可以快速構建自己的APP,這樣也可以更加關注與自身所在垂直領域相關的業務。

那麼SDK的核心場景有哪些?為了方便講解,我們把SDK核心場景分為錄播場景和直播場景:對於錄播場景,主播端或者內容貢獻者需要錄製一個視訊,後期對視訊和音訊頻新增特效,比如主題、貼紙、混音、BGM等等,最終把視訊上傳到伺服器,觀眾端則需要使用播放器播放以及社互動動即可;而對於直播場景同樣包含這兩個角色,主播端需要將內容進行實時直播,並針對於觀眾的一些行為完成實時互動,觀眾端則需要使用定製的播放器觀看,這個場景下的播放器並非使用系統提供的播放器即可,必須加以定製化。

針對於錄播和直播兩個場景,他們的共同特點都包含視訊錄製器和視訊播放器;區別則主要體現在是否具有實時互動性;他們需要在各自場景下做一些特殊的配置,比如對於直播來說推流的穩定性和拉流的秒開,對於錄播則是後期視訊處理和上傳。

視訊錄製器的架構設計

  • 模組拆分

視訊錄製器分為三部分:輸入、處理和輸出。輸入就是通過攝像頭和麥克風這類採集裝置去做音訊和畫面的採集。處理則是針對採集到的畫面和聲音進行處理,比如大家熟知的美顏、回聲抑制、混響等等。最終輸出會分為幾部分:首先是預覽,比如用手機錄製視訊時,在螢幕上會有預覽畫面;第二部分是編碼,在安卓平臺採用硬體編碼 軟體編碼,而iOS平臺的相容性較好,所以只採用硬體編碼就可以達到要求;最後將音視訊資料封裝成一個容器——FLV或MP4,再進行IO輸出,IO輸出有可能是磁碟——錄播場景,也有可能向流媒體伺服器推流——直播場景。

  • 音訊架構設計

0?wx_fmt=png

上圖是音訊架構圖,由於Processor比較複雜,因此在裡面沒有做體現。從圖中可以看到,音訊架構分為Input、Output、佇列和Consumer幾部分,架構圖上下部分分別是安卓平臺和iOS平臺實現的結構。

使用者在K歌過程中需要混入伴奏音樂,對於安卓平臺而言,需要有一個MP3的Decoder,它可以通過MAD、Lame或者FFmpeg等開源庫來實現,最終通過AudioTrack 的API或者OpenSL ES的API來播放,同時我們把播放PCM資料放到PCM佇列中。而在採集過程,我們一般使用Audio Recoder或OpenSL ES來採集人聲,採集到的人聲也會放在一個PCM佇列中。在一般架構設計中,佇列一般承擔生產者和消費者中間解耦的角色,因此可以看到Input和Output就是上面兩個佇列的生產者,而Consumer執行緒中的Encoder就是消費者——從佇列中取出PCM資料進行編碼。

對於iOS平臺,我們使用的AUGraph,它底層使用的是AudioUnit,其中RemoteIO型別的AudioUnit可以採集人聲,AudioFilePlayer型別的AudioUnit可以播放伴奏。然後通過Mixer型別的AudioUnit將人聲和伴奏混合之後入隊,後面Consumer執行緒中的Encoder從佇列中取出PCM資料進行編碼。

  • 視訊架構設計

0?wx_fmt=png

視訊部分的結構設計相對會簡單一些。安卓平臺通過Camera採集視訊,在Output中首先是通過EGL Display來回顯預覽介面,其次編碼則是採用MediaCodec硬體編碼和Libx264軟體編碼相結合的實現方式(由於安卓平臺硬體編碼有可能出現相容性問題)。

而在iOS平臺則會更簡單,直接使用Camera採集,然後通過GLImageView來進行渲染——GLImageView的實現方式是繼承自UIView,在LayerClass中返回CAEAGLLayer,然後構造出OpenGL環境用來渲染紋理,最終再用VideoToolbox進行編碼輸出。編碼後的資料會放到H.264佇列中,那麼這裡的生產者就是編碼器,消費者實際上是Consumer模組,它把H.264佇列中資料Mux後再進行IO操作(輸出到磁碟成為mp4檔案或者輸出到流媒體伺服器)。

視訊播放器架構設計

  • 模組拆分

視訊播放器的模組拆分和視訊錄製器非常相似,同樣分為輸入、處理和輸出三部分。首先是IO輸入——本地磁碟或遠端拉流,拿到碼流後需要進行解封裝(Demux)過程,也就是封裝(Mux)的逆過程,它會把FLV中音訊軌、視訊軌以及字幕軌拆解出來,然後進行解碼過程,一般採用採用硬體 軟體解碼的方案。

視訊播放器中中間處理過程使用的並不算很多,音訊處理上可以做一些混音或者EQ處理,畫面處理則是畫質增強,如自動對比度、去塊濾波器等,當然播放器處理中非常重要的一環就是音視訊同步,目前一般有三種模式:音訊向視訊同步、視訊向音訊同步以及統一向外部時鐘同步。我們一般會選擇視訊向音訊同步,這主要是由於兩方面的原因:一方面是由人的生理特性決定的,相比於畫面,人對聲音的感受會更加強烈;另一方面音訊PCM是線性的,我們可以信賴播放器也是線性的播放,因此在用視訊幀向音訊幀同步時,我們允許對視訊幀進行丟幀或重複幀渲染。最後,輸出則主要包含音訊渲染和視訊渲染兩部分。

  • 執行流程

0?wx_fmt=png

對一個多媒體檔案,視訊播放器會對其進行Demux和Decode處理,當解碼器解碼出一幀視訊後給到佇列,這時如果是軟體解碼則一般解碼出來的是YUV格式,然後放入到記憶體佇列中;如果是硬體解碼則一般是視訊記憶體中的紋理ID,會放到迴圈視訊記憶體佇列中。解碼出音訊的PCM資料也會入隊。

對於這兩個佇列來說也同樣存在生產者和消費者,解碼器就是生產者,右邊的Output則是消費者。這裡值得一提的是,可以通過設定兩個遊標值來做佇列的控制——minSize和maxSize,當佇列中的音訊大小到達minSize時,消費者則會開始工作,而當音訊大小到達maxSize時,解碼執行緒就要暫停工作(wait住),當消費者消費了佇列中的內容後,佇列中音訊大小小於maxSize的時候,會讓解碼執行緒繼續工作(發出Singal指令)。而消費者的工作流程為:從音訊佇列中取出一幀音訊幀給音訊播放模組進行播放,然後會通過AVSync音視訊同步模組取出一幀對應的視訊幀給視訊播放模組進行播放。當生產者、消費者周而復始的運轉起來,整個播放器也就執行起來了。

  • 音視訊同步策略

前面提到我們音視訊同步策略是採取視訊向音訊同步,也就是說假設我們在播放音訊第一幀時,對應的第一幀視訊沒有過來,而此時馬上要播放音訊第二幀,那麼我們就會選擇放棄第一幀視訊,繼續播放第二幀從而保證使用者感受到音視訊是同步的;那麼假設當沒有播放第三幀音訊時已經接收到對應的視訊幀時,則會將視訊幀返回,直到對應音訊播放的時候再取出對應的視訊幀。

那麼對於普通開發者而言,想要實現播放器每一個細節其實是非常複雜的,尤其對於一些創業公司或者對於音視訊積累比較薄弱的公司來說,所以直接接入CDN廠商提供的SDK是不錯的選擇,這樣可以儘快實現自身業務邏輯,而伴隨著業務的發展,後期可以針對特殊需求基於SDK進行二次開發。

從個人經驗來講,我認為SDK中技術含量較高的主要有兩點:跨平臺的視訊處理系統和跨平臺的推流系統構建,接下來我會做重點介紹。

跨平臺的視訊處理系統

跨平臺的視訊處理系統實際可以說是跨平臺的圖片濾鏡系統,它所應用的場景主要有實現美顏、瘦臉這種單幀圖片的處理,也有如雨天、老照片等主題效果,以及貼紙效果這幾種。為了達到效果,我們通過OpenGL ES來實現,如果用軟體(CPU中計算)做視訊處理是非常消耗效能的,尤其在移動端無法接受。因此它的輸入是紋理ID和時間戳,時間戳主要用於主題和貼紙場景的處理。輸出則是處理完畢的紋理ID。

  • GPUImage

0?wx_fmt=png

這裡特別介紹下GPUImage框架(以iOS平臺作為講解),它的整個流程分為Input、Processor和Output。首先通過GPUImageVideoCamera採集畫面;然後轉化為紋理ID就可以通過模糊、混合、邊緣檢測、飽和度等一系列處理進行優化;最終Output中使用GPUImageView把處理完的視訊幀渲染到螢幕上,而對於錄製則提供了GPUImageMovieWriter,它可以將紋理ID硬體編碼到本地檔案。除了視訊錄製過程,它對視訊播放器和離線處理場景提供了GPUImageMovie作為Input的實現。

  • 跨平臺的視訊處理系統構建

對於搭建跨平臺的視訊處理系統,我們需要搭建兩個客戶端的OpenGL環境,安卓平臺使用EGL來提供上下文環境與視窗管理,iOS使用EAGL來提供上下文環境與視窗管理,然後我們抽象出統一介面服務於兩個平臺。

0?wx_fmt=png

這是結構圖,左邊是根據平臺搭建的環境——Platform OpenGL Environment,右邊是視訊處理系統—VideoEffectProcessor。整個過程為:首先通過Camera或者Decoder採集或者解碼出視訊幀紋理,將紋理ID交給VideoEffectProcessor完成視訊處理功能,而這裡面可能需要很多支援,比如整合一些第三方庫解析XML、解析Json、libpng等等,同時我們也要暴露一些可以動態新增和刪除Filter的功能。當處理完成後會輸出一個Output TexId做渲染,最終呈現到介面上,或者給到Encoder做離線儲存。

跨平臺的推流系統

我們先來看跨平臺推流系統的應用場景,首先無論網路是否抖動都要維持互動的實時性,其次要保證正常直播的流暢性,並能根據網路條件的好壞來決定清晰度,最後要有統計資料來幫助產品運營做策略優化,比如提升位元速率、解析度等等。針對這三點場景分析,如何從技術角度實現?首先在弱網下做出丟幀,第二是位元速率自適應,第三為了保證主播端持續直播,需要做到自動斷線重連。

那為什麼要做跨平臺的推流系統?這主要考慮到開發成本和效率的問題,從開發策略制定和測試的角度來看都可以節省一部分成本,而且一套程式碼在後期維護中也有很多好處。那麼跨平臺推流系統應該如何實現?我們使用FFmpeg將AAC和H.264封裝成FLV格式,然後使用RTMP協議推到流媒體伺服器上就可以。

  • 弱網丟幀

當檢測到H.264或AAC佇列的大小超過一定域值時,我們要做丟幀處理,因為此時可能會導致現在的資料很長時間發不出去,從而互動的實時性就無法得到保證。當我們需要進行丟幀處理時,對於視訊幀要明確丟棄的是否為I幀或P幀;對於音訊幀則有多種策略,可以簡單丟棄與視訊丟幀相同時間長度的音訊幀。

  • 位元速率自適應


對於位元速率自適應,我們需要檢測上行網路頻寬的情況,準確的說是上行網路到推流的流媒體伺服器節點的情況。當需要降低位元速率,我們要把現在編碼佇列中高位元速率的視訊幀丟掉,並讓編碼器強制產生關鍵幀,以保證最新的視訊以低位元速率推到伺服器上完成整場直播的互動性。改變編碼器的輸出位元速率,對於libx264來說,需要在它的客戶端程式碼中改變vbv buffer size,並Reconfig X264編碼器才可以;而對於FFmpeg的API則是需要改變rc buffer size,並且需要ffmpeg 2.8版本以上才能支援;對於MediaCodec和VideoToolbox則使用各個平臺硬體編碼設定。

0?wx_fmt=png

這張圖是通過當前傳送的位元速率調整實際編碼器產生的視訊位元速率,這裡調整的不僅僅是位元速率,同時也包括幀率。當幀率較低時,單純提升位元速率也無法達到視訊質量提升的效果,因此兩者會一起做調整。

  • 鏈路選擇與自動重連策略

0?wx_fmt=png

在鏈路選擇方面,尤其在某一些特殊場景下,DNS解析不一定能找到最佳鏈路,我們可以選擇直接接入CDN提供的介面,在主播推流前向CDN廠商請求一個最優節點,而不依賴Local DNS去解析IP地址;對於主播端,也可以POST一個500KB的flv檔案,在多個推流節點測試網路鏈路情況,從中選擇最優鏈路。再者推流一段時間後,網路鏈路有可能會出現擁塞的情況,IDC機房節點也有可能出現問題,因此SDK底層需要有自動重連機制來保證重新分配更優的鏈路和CDN節點,從而保證主播持續推流不受影響。

  • 資料收集

最後是資料收集,資料收集涉及到後期調優、評判鏈路節點等等,因此非常重要,而這也是用定製播放器的原因。基本統計的點包括連線時長、釋出時長、丟幀比例、平均速率、設定速率和位元速率自適應的變化曲線等等。

以上是本次分享的全部內容。

酷炫短視訊開發進階&新書抽獎

此外,我們還特別邀請了展曉凱在下週二線上與我們一同分享酷炫短視訊開發的設計架構、實現思路以及研發過程中的經驗。

在參與直播互動的小夥伴中,將抽出10位贈送展老師的新書《音視訊開發進階指南——基於Android和iOS平臺的實踐》,同時我們也會面向參與直播的小夥伴開放購書優惠通道。

掃描下方圖中二維碼,加入直播群。

0?wx_fmt=jpeg