作者:張凌柱(以繩)
“本文為《優酷播放黑科技》系列文章第二篇,第一篇文章可點選優酷播放黑科技 | 自由視角技術體驗最佳化實踐進行檢視。歡迎點選左上角【阿里巴巴移動技術】關注我們,點關注不迷路 ~”
直播內容區別於點播,因其實時性、強互動性的特點吸引著使用者,在觀看的同時還可以同步進行點贊、評論、打賞等互動行為。此類互動主要集中在使用者的社交行為,而內容型的互動也隨著點播中的“互動劇集”的出現而映入大眾的眼簾,使用者可以在劇情進展的關鍵節點選擇角色執行不同的行為繼而進入到不同的分支劇情中。同樣直播方向上的內容型互動也在被不停的探索和應用中,其中一個例子就是同時進行多個視角的直播,允許使用者切換選擇某個特定的視角觀看直播內容。尤其在中大型的直播活動中(例如街舞、歐冠等),粉絲向比較明顯(明星、球星),如果能提供多個觀看視角或者提供某個聚焦視角供切換選擇,在使用者體驗上無疑能有很大的提升。
基於此,經過研究與探索,最終基於WebRTC落地實現了低延遲高畫質晰度的多視角直播能力,並在《這!就是街舞》的年度總決賽正式上線應用。效果影片如下:
影片可點選檢視:優酷播放黑科技 | 基於WebRTC實現的直播"雲多視角"技術解析
方案選型
能否實現低延遲切換、高畫質晰度、全機型覆蓋的使用者體驗是最終決定採用哪種技術方案的重要衡量標準。業內常用的應用層協議包括:
- HLS(含LHLS)
- RTMP
- RTP(嚴格來說RTP介於應用層和傳輸層之間)
其中,RTMP因其優缺點(低延遲但易被防火牆攔截)被確定用於直播推流,本文暫不多作介紹,下面我們主要針對拉流過程進行分析。根據端上拉流個數可以將實現方案分為:
- 單路流偽多視角直播:基於HLS協議,端上同一時間僅拉取一路流,切視角需要重新切流地址起播;
- 多路流多視角直播:基於HLS協議,端上同時拉取多路流進行解碼渲染,切視角不需要切流地址起播;
- 單路流多視角直播:基於RTP協議,端上同一時間僅拉取一路流,切視角不需要重新切流地址起播。
橫向對比
對處於劣勢的特性作標紅處理後得到的表格:
方案 |
協議 |
同時預覽 |
無縫切換 |
位元速率 |
端效能負擔 |
增量成本 |
單路流偽多視角直播 |
HLS |
否 |
否 |
普通 |
普通 |
無 |
多路流多視角直播 |
HLS |
是 |
是 |
高 |
高 |
CDN |
單路流多視角直播 |
RTP |
是 |
是 |
普通 |
普通 |
邊緣服務與 流量頻寬 |
透過對比,我們最終決定採用基於RTP的單路流多視角直播,即邊緣方案。
WebRTC 概述
WebRTC,名稱源自網頁即時通訊(英語:Web Real-Time Communication)的縮寫,是一個支援網頁瀏覽器進行實時語音對話或影片對話的 API。它於 2011 年 6 月 1 日開源並在 Google、Mozilla、Opera 支援下被納入全球資訊網聯盟的 W3C 推薦標準。WebRTC預設使用UDP協議(實際上使用的是RTP/RTCP協議)進行音影片資料的傳輸,但是也可以透過TCP傳輸。目前主要應用於視訊會議和連麥中。
Voice Engine 音訊引擎負責音訊採集和傳輸,具有降噪、回聲消除等功能。Video Engine影片引擎負責網路抖動最佳化,網際網路傳輸編解碼最佳化。音影片引擎之上是 一套 C++ API。
ICE、STUN、TURN 用於內網穿透, 解決了獲取與繫結外網對映地址的問題。DTLS (UDP 版的 TLS)用於對傳輸內容進行加密 。RTP/SRTP主要用來傳輸對實時性要求比較高的音影片資料,RTCP/SRTCP用來控制RTP傳輸質量。SCTP用於自定義應用資料的傳輸。
系統設計
多視角直播整體鏈路涉及流生產、域名排程服務、邊緣服務、合流服務、直播播控服務等多個環節,整體結構框架如下:
- 流生產:演播現場攝像機採集流上傳到直播中心,直播中心對流進行編碼及最佳化處理;
- 合流服務:多路輸入流透過時間戳進行音影片對齊後,按照模版輸出多路混合流;
- 邊緣服務:預快取合流服務多路輸出流, 對輸出流進行編碼, 透過RTP連線傳輸給端側;
- 域名排程服務:進行IP、埠配置,供端側與邊緣服務通訊;
- 播控服務:提供合流服務請求引數配置、及多視角業務能力配置等播放控制服務。
詳細設計
端側為了儘可能的複用播控主鏈路,減少對主播放鏈路的侵入, 增加了多視角播放器。多視角播放器與其他播放器保持相同的呼叫介面,在播控處根據業務型別決定是否建立多視角播放器。在業務層增加多視角外掛,與其他業務解耦,可以很方便的掛載與移除。
端側結構設計如下:
核心流程
使用者進入多視角模式主要起播流程如下:
目前支援3種展示形態,分別為mix模式、cover模式及source模式, 下圖為具體形態及切換過程。
邊端指令
端側與邊緣結點主要互動指令,透過RTP協議進行資料傳輸。通用頭部傳輸格式如下,PT=96 代表H264媒體資料。
- 建連指令:阻塞式指令,端側與邊緣結點建立RTP連線,需要等待邊緣結點返回響應,如果在一定時間內無返回,則認為建連失敗;
- 斷連指令:非阻塞式指令,端側與邊緣結點斷開連線,無需等待邊緣結點返回;
- 播放指令:非阻塞式指令,端側下發播放指令,需要傳遞播放流ID及OSS流配置資訊,供邊緣結點查詢到正確流;
- 切流指令:非阻塞式指令,端側下發切換視角指令,為了與原視角保持同步,需要傳遞原視角幀時間戳到邊緣服務。
專案落地
播放能力調整
- 音訊取樣率調整
WebRTC目前預設不支援48K音訊取樣率,而當前大型直播的取樣率較高,如果不經過調整下發到端上會導致音訊解碼模組的崩潰;
- AAC音訊解碼能力擴充套件
WebRTC裡音訊編碼預設用的是Opus,但當前直播現狀大部分都是AAC格式,所以需要客戶端在offer SDP 中新增AAC編碼資訊並實現AAC解碼器,邊緣服務配合下發RTP打包資料;
- H.264編碼支援
為了儘可能地降低延時,WebRTC影片編碼採用的是VP8和VP9,需要遷移至更通用的H.264編碼;
- 在傳輸方式上,WebRTC使用P2P方式來進行媒體中轉,它只是解決端到端的問題,對於大型PGC、OGC的直播內容來說明顯不適合,因此網路傳輸層並沒有打洞邏輯,利用RTP連線傳輸流媒體資料,RTCP進行傳輸質量控制。
接入已有播放體系
為了儘可能小的減少對主播放器影響,與主播放器保持了一致的資料請求流程、一樣的播放能力介面、一致的資料埋點時機等:
- 多視角播放器:封裝WebRTC, 擴展出多視角播放器, 專用於多視角影片播放;
- 播控邏輯調整:調整直播旁路資料獲取流程,將合路服務、AMDC等服務返回資料合併到資料流中;
- 播放能力&統計調整:保持原有播放能力及回撥介面, 透過擴充套件介面引數來予以區分;
- 擴充套件錯誤碼:基於主播放器錯誤規則, 擴展出多視角播放器錯誤碼。
端側問題解決
最終需要實現下圖所示的主播放視窗附帶幾個子播放視窗同時渲染:
右側可滑動列表採用UITableView(RecyclerView)實現,為每一個子視窗新增渲染例項GLKView(被RTCEAGLVideoView包裹管理),透過在合適的時機建立、移除、更新子視窗的渲染frame實現一路流多個視角同步直播的效果。
期間主要解決了切視角閃爍的體驗問題以及記憶體洩漏導致的渲染黑屏穩定性問題。這裡作簡要介紹。
1播放閃爍
【問題描述】假如一共有N個視角,那麼我們的實現方案則共有N*3(參見系統設計中的3種展示形態)路流,每次點選視角切換,實際的操作流程是播放器發出RTP切流指令,邊緣節點換流ID併發送Sei回傳資訊,播放器接收到SEI資訊後進行各個視窗的裁剪與更新操作。這就存在一個問題,切視角成功後窗口更新需要一定的時間,會導致實際接收到了新流的幀資料最開始的一幀或幾幀採用的裁剪方式依舊是舊流的模板,所以使用者會看到一瞬間的視覺殘留。
【解決方案】在播放核心收到切流指令之後,設定短時間的丟幀視窗期,待收到Sei資訊表明切流成功後恢復渲染,這期間因為沒有新的幀資料所以使用者看到的是靜態幀,這樣可以遮蔽導致子視窗內容錯亂的渲染資料,上層UI在此期間也看不到殘留幀閃爍了,使用者體感上基本是平滑切換的。
2記憶體洩漏
【問題描述】當用戶不停地切換視角,列表重新整理,cell重新建立的過程中,記憶體佔用不停增長,達到上限後導致黑屏視窗的出現。
【解決方案】記憶體洩漏的原因在於cell重新建立過程中,核心中的OpenGL 渲染例項發生了批次新的建立,而舊的例項又未能及時銷燬。第一步明確過上層業務程式碼對UIView物件透過removeFromSuperview有釋放操作,而lldb列印引用計數後仍舊不為0,那麼問題就出在了核心的例項管理上。__bridge_retained代表OC轉CF物件後,記憶體管理需要手動釋放,因此切視角時需要呼叫相關Filter實現C++層面的銷燬與釋放。解決後的記憶體表現:
服務併發最佳化
最佳化前版本曾經支援CUBA相關賽事直播,但是存在不少問題:成本高、切換延時高,且不具備支援大規模併發能力。本次最佳化目標是能夠支援大型活動直播,大幅降低成本,同時保證體驗。
1編碼前置
下圖為最佳化前鏈路,可以看到,每個客戶端需要重新佈局和編碼,這兩個操作耗費大量資源,即使是用T4 GPU也只能支援20路同時觀看。
經過分析可以發現,在多視角的應用場景下使用者觀看的組合是有限的,在街舞模式下,觀看視角為3*N,N為原始採集視角,那麼我們如果把這3N個流預先生產,然後在邊緣服務直接複製編碼後的H.264資料,則可以把CPU的消耗大大降低,同時承載的使用者數就是提高至少一個數量級。
圖中合流服務只需生產一次,生產的內容就可以為所有的邊緣服務節點所共享。
但是這種簡單的切流有個問題,無法做到幀級的對齊,換句話說,就是使用者在選擇不同視角時會發現新切的視角跟之前的視角時間上不連續。為了解決這個問題,我們請阿里雲導播的同學在合流時以絕對時間戳為依據,進行對齊,同時將這個時間戳資訊透傳給邊緣服務。
解決的PTS對齊的問題,我們仍然無法做到真正的幀級對齊,因為還有GOP對齊的問題,熟悉H,264編碼的同學都知道,影片的編碼解碼都是以GOP為單位的,GOP的長度可能有1s到10s甚至更長,在直播中典型應用長度為2s。如果使用者切換時,恰好在一個新的GOP的開始,則簡單切流即可實現,但是我們不能要求使用者在哪些時間能切,哪些時間不能切,使用者要的是想切就切。我們的解決方式如果使用者切換時沒有可以正好切換的GOP,邊緣服務就生產一個。
透過上圖,我們可以看到,當用戶從視角1切換到視角2時,我們在切換點產生一個新的GOP,這樣推流到客戶端後,端上可以無縫解碼渲染到新的視角。
透過上面的步驟,我們大大減少了編碼消耗,但是為了能夠快速響應使用者切視角,我們必須把所有源視角的原始畫面Frame(YUV420)準備好,當需要產生新的GOP時,可以直接使用。但是需求總是變化的,4個視角時,我們可以對4*3=12路流都進行預先解碼,當業務方要9*3=27路流時,同時解碼27路1080p影片資料使得整個32核機器不堪重負,更何況以後還可能要求更多路源流。
2按需解碼
使用者想要更多的視角,我們需要滿足。那麼之前全部預先解碼的方式必須改變,所以我們實現了按需解碼,也就是隻有真的需要解碼時,才去為它準備那路流的Frame(YUV420)。這裡最大的問題就是實時性的問題,因為當用戶切換時,畫面可能處於一個GOP的任意位置,而我們只能從GOP的開始幀進行解碼。不過透過多次最佳化,做到了呈現給使用者的延遲不會被感知的效果。
3客戶端動態快取
做過音影片的同學都應該對卡頓率很熟悉,年年治理總是不夠理想,多視角直播則面臨更麻煩的問題,因為如果降低卡頓率,最基本的做法是增大播放快取,但是我們又希望使用者能夠迅速看到新的視角,就要求快取不能太高。所以我們引入了動態快取,簡單來講就是當用戶切換時,我們採用極低水位快取,保證切換體驗,而當用戶不切換時,快取會慢慢恢復到一定水位,保證一定程度對抗網路抖動。
總結與展望
多視角能力在街舞總決賽正式上線,作為優酷獨創新玩法,收到了來自於微博、朋友圈等廣大使用者的諸多正向反饋。後續優酷計劃將透過互動最佳化,時延最佳化等方式,進一步提升使用者體驗。
關注【阿里巴巴移動技術】微信公眾號,每週 3 篇移動技術實踐&乾貨給你思考!