熊貓TV直播H5播放器架構探索
本文來自熊貓TV音視頻技術專家姜雨晴在LiveVideoStackCon 2017上的分享,並有LiveVideoStack整理成文。當下,打造一款播放器已經有比較好的開源實現,但熊貓TV為什麼還要自研一款H5播放器呢?為了保證業務持續擴展能力,需要對播放器做解耦。同時,在播放器上線初期還遇到了音畫不同步、故障定位、客戶端性能不足等問題。
文 / 姜雨晴
整理 / LiveVideoStack
大家知道HTML5播放器曾被廣泛運用於視頻點播,而今天我想與大家分享的是運用在直播領域的HTML5播放器。現在熊貓已不再使用FLVJS作為播放器了,所以今天與大家探討一下直播HTML5播放器的技術難點與架構探索。
我來自熊貓直播,從去年的7月份加入熊貓並在 11月中旬開始開發播放器,主要致力於HTML5播放器的研製開發。
接下來我將從以下幾個方面介紹HTML5播放器的相關內容:
1. HTML5播放器產生背景
首先讓我們來看看HTML5播放的產生背景,
通過最近的一些新聞大家可以看到,包括谷歌的Chrome還有Adobe這樣的公司都在強調其產品不再專註Flash轉而更關注HTML5。在這樣一個後Flash時代,我們必須要有自己的新技術來支撐視頻播放,尤其是視頻直播的需求。
作為熊貓直播最重要的用戶之一,熊貓直播的老闆王思聰之前提出H5播放器的開發需求,那麼H5播放器具有哪些優勢呢?
(1)高效性
第一點是高效性。我們需要明確Video標籤為瀏覽器帶來的是什麼?其實是在背後把H264的Codec打進了瀏覽器,無需內嵌應用而是利用瀏覽器Codec進行視頻解碼。
(2)兼容性
第二點是兼容性。之前我們遇見了很多非同尋常的案例與需求,包括將HTML5播放器技術運用於電視直播或遊戲主機,這其實是反映了H5解決方案的良好兼容性。這種兼容性體現在一次開發後可以在多個不同平台應用,降低開發成本。
(3)瀏覽器新技術
第三點是快速接入瀏覽器新技術。例如大家或多或少聽說過的流媒體加密的瀏覽器新介面Encrypted Media Extensions,還有 WebRTC、VP9、AV1、H.265等新技術,通過使用HTML5可以將這些新技術快速接入瀏覽器中。例如最新版本的Chrome瀏覽器便打入了H.265的Codec。相對於Flash播放器, HTML5可更便捷快速地引入新技術。
當然,HTML5播放器的開發過程並不是一帆風順的。
2. 直播領域H5播放器的問題
我們之前從未嘗試過將H5播放器技術運用於視頻直播領域,因此在開發初期我們遇到了很多棘手的問題。2016年12月份上線的第一版便出現音畫不同步、碼率過高、播放器崩潰、瀏覽器崩潰、延遲高等問題。
我們團隊曾經將這些問題集中並研究解決方案,下面我將會選其中幾個比較具有代表性的問題進行詳細闡述。
2.1 音畫不同步
音畫不同步的問題困擾了許久,很多開發者問到相關的問題,下面就是我們對於問題的定位與解決思路。
初期我們在觀察來自內核的視頻時會發現主播口型與聲音無法準確同步,延遲可達到兩三秒。這對用戶而言是一場糟糕的體驗,那麼究竟為什麼會出現音畫不同步的問題呢?
1) 問題定位
我們發現,戶外直播是發生音畫不同步問題最為頻繁的版區。第一個原因是戶外主播手機性能及網路問題導致上行數據掉幀頻發;第二個原因是音頻和視頻的掉幀時間長度存在差異;第三個原因是播放端音視頻實際播放時長不一致導致音畫不同步。
上圖為問題示意圖。灰色框為視頻幀組成的視頻流,紅色框為音頻幀組成的音頻流,理想狀態下的視頻流與音頻流應當是長度一致。其中虛線框表示幀片丟失的狀態,例如現在視頻流丟了3片,音頻流丟了1片,此時實際傳輸的音視頻為上圖,但實際播放的音視頻為下圖:
但看著一小段音視頻流,兩三幀的差異似乎不是特別明顯;一旦累計時間過長,視頻流與音頻流之間的時間差異越來越大,音畫不同步的現象也就會越來越明顯。相信現在使用FLVJS做視頻直播的朋友也都會遇到這樣一個問題:音畫不同步的現象隨時間的增長越來越顯著,那麼如何改進技術消除這個問題呢?
2) 解決方案
上圖是影視動畫後期製作時使用Au將配音員配音人聲與視頻畫面做對接的處理過程。當出現音畫不同步的現象時最常用的處理方案是調整軌道相對位置,再添加特效使得音畫自然同步。
視頻直播中出現音畫不同步時可以運用類似方法進行處理,我們稱為抽幀處理。當然抽幀後需要進行音頻補幀處理。
在這裡大家一定會有疑問,後期補進去的音頻幀並不是原生的,那麼應該補進去什麼幀呢?為了讓大家比較清晰地理解這個問題,也我們使用配音中的原理進行解釋。
演員配音時,因為演員說每個字時發聲的頻率不同,聲音聽上去也會不同。如果每個字的不同頻率切換得比較平滑便不會出現「嘶啦」的聲音也就是「過電」現象;但如果是補一個空白幀,便會出現這樣的現象,此時人耳會聽到短暫的電流雜音,體驗很不好;尤其是當直播頻繁掉幀時用戶會感覺到明顯的電流雜音。
所以我們取前一幀進行音頻補幀,較好避免了過電現象的發生。
3)改進效果
通過上述播放器對軌與補幀處理可以在掉幀頻繁時明顯降低音畫不同步帶來的對直播視頻觀看的影響。
2.2 碼率問題
1) 問題定位
相信大家無論是使用Flash還是在H5播放器都曾遇見正在播放時突然彈框顯示「頁面已崩潰」的問題。這是為什麼?因為瀏覽器會限制網頁佔用運行內存。普通的無音視頻流的網頁,除非代碼出現嚴重的Bug否則不會佔用過高的運行內存;但如果網頁中有播放器的運行便很容易使瀏覽器處於一個高內存佔用運行狀態,一旦達到運行內存上限便會使得網頁崩潰。
上圖是藍光直播上線第一天的反饋情況,可以看到大家反饋的信息,無論是選手毛孔還是主播妝容都是纖毫畢現。
上圖是6000kbps的高清的直播,可以清晰看到主播面部的細節。對熊貓來說,高清直播是一座里程碑,也是我們產品的一個賣點。我們不可能用3000kbps的冒充藍光線路,所以在這種大型活動熊貓基本上都維持在一個6000到8000kbps推流碼率下的高清直播。
而對於普通主播而言高碼率採集同樣重要。上圖是根據某天下午幾個FPS主播們的直播房間統計出來的結果,可以看到很多主播都將碼率採樣推到6000以上,對此主播們也是樂此不疲,這是為什麼?
這是我自己喜歡的幾位主播平時的推流規律。其中有一個最高需要推到一萬四的碼率,這樣一個高碼率對熊貓來講可以說是非常普遍的。我們需要保證頁面不崩潰的同時維持這樣一個高碼率的推流,可以說難度不小。
這是FPS遊戲《絕地求生》的直播畫面。可以看到遊戲中對手距離非常遠,有的時候在畫面中就是一個小黑點。像這樣的FPS遊戲一旦推至很高的碼率便會大大降低用戶體驗。因為會帶來明顯的卡頓,包括主播也對這一點心知肚明。一般情況下如果出現卡頓的問題主播會給出「換線路板」、「調清晰度」等提示語。但無論如何我們需要支持主播的高碼率直播需求,那麼如何解決?
2) 解決方案
如果你打開熊貓HTML5播放器並右鍵點擊打開監控,會看到顯示「正在清洗能量槽」,很多人問我什麼是正在清洗能量槽?其實是正在清理緩存的意思。這個功能的實現其實只需要幾行代碼,但背後會遇到了什麼問題呢?
a.什麼時候清洗
做前端的同學應該知道這個Setinterval。當Setinterval或新的GOP準備好時會觸發清洗能量槽的功能。
b.一次清洗多少
先說Setinterval和新GOP。 Setinterval解決方式有優點與缺點,其問題在於此定時器在頁面掛起的狀態下並非按照設置的時間運行,而只是把這一段代碼推至站並等待運行;此時如果超過時間而又在休眠狀態便失去作用。而新GOP會過於頻繁, 干擾系統正常運行,因此最後我們選擇Setinterval解決方式。那麼關於清理多少,我們暫時是確定10秒以前的全部清洗。
c.容易洗出什麼問題
BufferUpdating是MSE的Buffer的一個狀態。在新的GOP準備好時這是一個寫操作,此時一定會存在這樣一個無法清理的狀態,這也是我們沒有用新GOP的原因。
3) 改進效果
下面來看一下我們內存控制的效果,這是我們新版內核與老版內核的對比,請注意內存的變化。
在同樣的測試環境下,上面的標籤頁是我們使用老版內核得出的佔用內存值為285736k,下面的標籤頁是我們使用新版內核得出的佔用內存值為75632k,大概是老闆內核內存佔用的1/4。
2.3 累計延時問題
CDN的同事應該知道累計延時也是一個困擾大家很久的問題。上圖是我自己直播間的一個界面,左半圖右側是老版內核的,左側是新版內核,右半圖是我在新版內核網站刷新出的的一個狀態,最左邊的和最右邊我都是已經放置了一段比較長的時間。先對比來看時間戳,老版內核頁面與剛刷新完的頁面相比存在大概4分鐘的延遲,這4分鐘的延遲可以說為觀影體驗帶來的影響是毀滅性的。
1) 問題定位
延遲問題與碼率有關。當下行網速小於平均碼率時便會出現這種視頻卡頓的現象。瀏覽器的Video標籤是針對點播設計的,出現卡頓後一定是從卡頓點開始繼續播放,這種小規模無法被輕易感知的卡頓累計多了便會造成明顯的延遲,那我們該如何處理呢?
2) 解決方案
這一部分是我們寫的一個重新拉流,處理方法為網路抖動。如果使用網路抖動而後面網路又平滑了該怎麼辦?此時需要看最後一幀是否滿足需求,如果不滿足就重新拉流並重新計算起始時間;然後將始終時間和當天時間作差,得出實際播出的時間以及實際消耗的時間,便是累計延時的時長。若大於一定閾值便會觸發重新拉流的操作,當然這個閥值可根據應用環境進行修改,這裡我設定的是15秒。
3) 改進效果
以上是在弱網環境下的測試結果。大家可以看到如果在放置比較久的情況下會產生一定的累計延遲,大概為3秒。但這種體驗已經比之前好很多了,可以基本保證同步。
3. 熊貓HTML5播放器內核架構
3.1 明確問題
在整個開發過程中我們遇到了以下的一些問題使得我們將內核進行重新架構。
1) 不同業務
不同業務對播放器內核的需求是不一樣的。雖然這是個外層問題,但當我們再去剖析時會發現,其實針對不同需求的不同業務下所需要的內核也不太一樣。這個時候該怎麼辦呢?當然不可能將所有的業務都寫在內核里,一個業務對應一個內核會帶來龐大的開發體量。
2) 新技術接入
大家可以看到熊貓之前有十個多月處於Bata階段。為什麼我們一直沒有發布正式版?因為我們想在播放器當中接入一些新技術。而每次新技術的接入就需要改變包中代碼,可想而知其有多麼不穩定。
3) 團隊新人加入
我們團隊會遇到的一個很正常的問題就是當有新人加入該怎麼辦?新人一開始不熟悉開發過程,在開發過程中有時對內核造成不必要的影響。
3.2 構架特徵
我們對於新版內核的要求就是——「高度解耦」、「模塊化」、「易擴展」,也就是下面我們重構的架構。
1) Modules層
大家可以看到在Modules層是由Loader模塊、Demuxer模塊、Remuxer模塊與Build Packages模塊組成,每個模塊都有一些自己的插件。講到這裡,大家可能會想到一些以前的庫,包括HLSJS、FLAVJS等都大概有這樣的一個架構,那麼我們在Mccree Core層做了哪些工作?
2) Mccree Core層
首先我們設置了一個消息通道Message Channal,其作用是當有模塊要完成某些任務時會通知給下一個模塊,然後會把數據給到緩衝區。
這個消息通道採用廣播模式,任何一個模塊在得到對應的消息時會觸發對應功能。
3) 底層
底層的數據結構分為Loader Buffer、Tracks與Remuxed Buffer,分別用來放置原始的流數據、Demuxer後的數據與Demuxer前的數據,並提供給MICE。其中MICE是一個插件,其他的幾個部分是我們的核心模塊。可能大家剛開始看到這個構架有些複雜,接下來我會向大家介紹這些模塊是如何工作的。
3.3 模塊、插件與封裝
註冊、調用、銷毀的流程會經常在工程化中被用到。那麼在我們的Mccree Core中模塊是如何被接入的?
首先初始化模塊,接下來進行模塊調用;這一步比較簡單的是調用標準介面也就是Loader載入數據;最後在我不用的時候進行銷毀。需要注意的是這裡的Unload也是一個標準介面, Unload是promise,如果有人想比著這個東西去改FLVJS,可以把改掉,因為這個是個promise,泛指是個promise,其他的也都必須做成一個promise才能兼容這樣的一個介面。
這是我們一個具體的數據傳輸方式。首先是向緩存中填充數據,再通過消息通道通知下一個模塊獲取數據;之後會給出獲取數據的長度,否則下一塊模塊無法確定獲取數據量;接下來收到這些消息後下一模塊從緩存中提取數據。大家都知道FLV的視頻Header等於13位,就是以上的一段代碼,大家可以在開源庫上看到這段代碼,我就不再贅述了。
3.4 工程管理
這個較為複雜的流程本身會有一些工程管理上的麻煩:
1) 工程體積大,模塊多
解決方案:多包管理系統
我們現在使用Lerna package管理系統,我想做前端的同學應該了解這是Babel的多包管理工具。
2) 參與開發復性工作多
解決方案:完整的開發套件
當你第一次布局這套環境時會發現需要一個個裝所有的庫,還要做Lerna Bootstrap的工作,參與開發復性工作多,如何改進?我們可以做一套完整的開發套件,將包括自動檢測在內的全部工作做好。
3) 模塊質量保證
解決方案:完整的測試用例、文檔支持。
我們要求需要有一個完整的測試用例與文檔支持,即使是上一個模塊我們也會做A/B Test和軟切換。保證其模塊的質量。
4. 技術創新與展望
關於這一點我想與大家分享一個簡單的例子:P2P技術想必大家並不陌生。
上圖是我們實際中接入一位合作方P2P的代碼。如果需要我在外層去控制使用P2P該如何解決?
我們在P2PLoader層先寫了一些如剛才提到的Loade還有URLsource這樣的標準介面,再寫了這一套代碼;之後把P2P完整接入到我們的HTML5播放器。
我們花了半天工時寫了一個模塊與幾行控制代碼就可以將這樣一個P2P技術完整接入到播放器中。
4.1 WebVR
WebVR想必大家都了解一些。但現在距離產品化還有一段探索的路,故而一直沒有推向市場。只需封裝一個WebVR介面,也就是去做一個插件就可很快取代我們現有的純MSE,很快就可以上線。
4.2 服務端應用接入
這應該是前端的同學比較熟悉的NodeJS。由於現在的框架包括大部分的模塊和瀏覽器是不相關的,而唯一和瀏覽器相關的是部分Loader與基於瀏覽器的MSE。我們可以使用Node服務端運行提供音視頻服務,將來需要去自建CDN時可以很輕易地將我們現在所做的包括解決音畫不同步在內的一切優化都轉接到一個邊緣節點上。
Q&A
Q1.1:播放器剛啟動時默認使用大碼率還是小碼率?
A:大碼率
Q1.2:如果用戶的網路環境比較差怎麼辦?
A:關於這一點我們有一個降級的解決方案。熊貓直播可切換三個清晰度,但默認是超清;用戶上傳多少碼率,我就可以拉多少碼率。比如說有P2P推一個8000kbps的碼率,其用戶可在超清鏈路上拉到8000,如果出現卡頓可切換到高清或更低的碼流。
Q1.3:播放器是否會推薦給用戶合適的碼流?
A:這是一個業務層面需要解決的問題。我們在Loader里集成了一個實時監控的插件監控實時傳輸速率。基於保證沉浸且連續的用戶體驗與業務方的需求,我們不會默認在直播中向用戶彈出推薦合適碼流的提示框。
Q1.4:一般碼流切換時播放器會緩存多長時間?
A:這個問題與我們的首屏優化有一定關係的,我預測今天會有很多人講首屏優化。因為直播視頻里是沒有B幀的,不存在向後預測的幀,只存在向前預測的幀。我們進行首屏優化時,如果是在GOP比較長的情況下會在到下一個I幀前開始播放。我們只會給I幀緩存並且直接開始播放以實現秒開的效果,此時用戶會看到直播畫面閃一下。
當然在這個過程中需要切換碼率, MOOV的Header需要改變,所以必須要清空之前MSE上所有的數據。
Q2:這些視頻插件在Chrome、Safari、IE等平台上如何實現適配?
A:其實大部分國內的瀏覽器廠商使用的都是谷歌的Chromium內核解決方案,除此之外還有火狐、蘋果的Safari、微軟的Edge。Chrome與火狐已經支持了這些插件,而為什麼我最後說Safari與Edge?因為這個問題的解決很大程度上取決於瀏覽器的市場覆蓋率。但是這兩個瀏覽器在Fetch Loader上存在問題,我們只能去載入HLS流。如果我的Remuxer不變,MSE控制插件也不變的情況下降級兼容HLS,只需要換一個Loader一個Master就可以解決。
Q3:關於解決音視頻不同步問題的修正碼插件,是集成在原生播放器中嗎?
A:在Remaster中,暫時還沒有提取出來。 FLV流拉過來時會給出一個PTS差值。當被檢測到時我們就改動時間或重新輸出數據包。
HTML5原生播放器支持MP4、WebM,不支持FLV,PC端也不支持HLS。我們會將數據進行拆包和分包再傳輸給瀏覽器以實現格式支持。
Q4:客戶端會緩存多少?追趕策略是什麼?
A:首先說一下幾個不同拉流方式的差異:Fetch方式拉流時,因為是長鏈接所以是挨著拉。如果出現網路抖動,保持在比較卡的狀態下拉流會和伺服器端產生很大差距;但如果是網路抖動,後面的數據密度大,可與伺服器保持一個相似的狀態。這兩種不同追幀方式,如果只是抖動,最後拉流多少就是多少。我們會監測實際播放時長和理論播放時長的差值,根據差值找最新的GOP里的I幀。如果有就不用重新拉流,如果沒有則需要重新拉流。
Q4.1:可能緩存一個GOP?
A:有可能,如果說伺服器本身緩存了三個GOP,我們會緩存三個。
Q5:移動端的相關問題解決方案有什麼?
A:移動端我們暫時使用HLS拉流的方式,這一點策略與我們的業務相關。對我們而言移動端本身只是用來分享,沒有必要使用這麼高的碼。我們直接用的HLS流,不需要拆分包以提高移動端效率。
Q5.1:大概介紹一下碼流監控的埋點與監控的思路。
A:我們會監控一些參數,例如某個Buffer不夠用了,此時就開始埋這個卡頓點,開始計時到重新播放的狀態;此時會統計時間與卡頓次數並上報給我們自己的數據中心。其實在CDN會看到一個主播推流的上行狀態,我們還會監控下行網路速度等。通過這些埋點我們可以看出到底是哪個環節出現問題,防患於未然。
Q6:補幀的策略是怎麼樣的?
A:以視頻幀為基準。根據視頻幀的位置計算音頻幀的位置,如果這幀出現缺失我們就補幀。
Q6.1:補前一幀與後一幀的區別?
A:根據不同場景選擇最優化的方案,從代碼修改簡便的角度我們會優先選擇補前一幀。
Q7:國外有一種DASH的解決方案,但是國內CDN廠商對DASH的支持不太積極,為何不做相關的適配工作?
A:我們盡量去推動,但在時間成本上無法保證。技術過渡期是有必要存在這種技術的。
Q8:熊貓HTML5播放器是否參考flv.js?能否對比一下二者優劣?
A:我們之前有調研過他的東西,但最後未使用。原因一是開發包臃腫,很多東西對我們來說是沒有必要的。為了防止日後維護上的混亂我們重構了架構。原因二是維護風險過大,跟不上我們的業務節奏。
LiveVideoStackCon 2018講師招募


TAG:LiveVideoStack |