1. webrtc淺析
webrtc的前世今生、編譯方法、行業應用、最佳實踐等技術與產業類的文章在網上卷帙浩繁,重復的內容我不再贅述。對我來講,webrtc的概念可以有三個角度去解釋:
(1).一個W3C和IETF制定的標准,約定了Web間實時音視頻通信機制,通過該標准可開發基於瀏覽器的、無插件的web多媒體應用(一般是js),該標准僅設定了點對點無中心的實時會話場景,沒有強制約束信令協議與內容,沒有要求有媒體處理的中心服務器,主要目標是形成開發者與瀏覽器廠商良好的生態環境,並積極向HTML5靠攏爭取被納入進去;
(2).一群音視頻算法和網絡適應性算法,這些算法囊括了視頻會議幾乎所有的核心技術,包括音視頻的采集、編解碼、網絡傳輸、播放顯示、安全等功能,還提供了操作系統系統調用跨平台封裝的實現,包含Windows,Linux,Mac,Android,iOS;
(3).一個開源工程,核心由c++實現,可通過修改、封裝、提取代碼等方式實現一套視頻會議系統,客戶端可實現為Web js、App或Windows應用程序等多種形式,服務端可實現包括業務外的所有服務,包括媒體服務、信令服務、穿牆服務、中繼服務等等,這些服務稍微調整后可輕易支持分布式部署、容器部署、雲部署等。
對webrtc的理解與使用,我認為有三個境界:
(1).能搭建一個簡易的視頻會議系統,其中客戶端部分可以這樣做: Windows端Mac端Linux(x86)端在自帶的peerconnection client或libjingle改一下(取決於信令是http還是sip家族信令);Android/iOS端在apprtc或licodeAndroidClient及Licode-ErizoClientIOS改一下;web端用webrtc 自帶js api實現一下。服務端部分可以這樣做:信令服務器在apprtc的collider改一下;穿牆服務器用自帶的stun server,turn server部署一下;中繼服務器在自帶的relay server改一下;媒體服務器在kurentos、licode、jitsi、Intel Collaboration Suite for WebRTC或janus改一下;如果需要和傳統的SIP體系互通則在webrtc2sip改一下;如果需要關注實時通信過程中的延時、丟包、接通率、掉線率等質量問題,可以部署callstats來進行專業監測;把所有服務都再部署於雲平台的多個虛擬主機上或阿里雲的容器雲服務,完成以上操作就搭建起初級規模的雲上視頻會議系統原型了。
(2).能提取、理解並使用webrtc的算法模塊,即或將算法模塊融入到自己的代碼中,或將自己的算法添加至webrtc里作為開源貢獻或自己產品的差異性優勢。值得提取的算法模塊包括音視頻編解碼與處理框架(vp8、vp9、voice engine、video engine),音視頻采集呈現封裝、音視頻預處理(NetEq、NS、AEC、Video Jitter Buffer)、網絡適應性(GCC算法、ARQ、FEC)、安全性(Dtls、Srtp/Srtcp)等,自己可添加的有視頻編碼模塊(x265、nvenc、intel qsv、xavs2、以及其他定制化的網絡傳輸策略)。
(3).能夠結合基於rtmp-cdn/p2p等直播技術,構建既支持交互互動,又支持海量圍觀,可商用、能運營、易擴展、全兼容的音視頻PaaS服務平台,完成一個多媒體通信的終極產品。
Licode是達到第一境界技術層面所需要了解的開源工程,它從webrtc代碼中提取出了SFU/MCU媒體服務所用到的音視頻、媒體傳輸、信令的功能代碼,結合libnice與libav實現了ICE與媒體轉發功能,並封裝成了可被調用的js API,此外還用js實現了包含全局管理服務、數據庫服務、業務邏輯服務與信令媒體的調用服務的業務代碼。Licode實現了webrtc開源工程沒直接實現的中心側的媒體服務功能,並提供了簡單的業務模型與分布式部署方式,從而得到了多媒體通信工程師的廣泛關注。但是Licode的相關文檔與資料非常少,故本文是在我個人觀察運行調試代碼后總結所寫的,不正確之處希望在留言中能夠得以指正。
2. Licode系統分析
2.1 系統架構
Licode的系統架構如下,有客戶端和服務端,客戶端包括ErizoClient和NuveClient,分別用來進行信令與音視頻交互和業務操作,類似於終端和會控集成在一起;服務端包括Nuve、ErizoController、ErizoAgent和MessageBus,分別用來實現業務服務與全局管理服務、信令服務、媒體服務和服務通信,其中ErizoController類似於MC、ErizoAgent類似於MP。
圖1 系統架構圖
2.2 客戶端與服務端交互流程
客戶端與服務端的交互流程如下圖所示,其中客戶端代碼集中在N.API.js與Client.js上,服務端代碼集中在nuve.js與erizoController.js上。
圖2 信令交互圖
3. 系統組成與模塊划分
3.1 系統組成
本系統組成僅指服務端的系統組成,包括Nuve業務管理服務,ErizoController信令服務,ErizoAgent媒體服務,ErizoJS媒體轉發實體。個人覺得代碼在文件命名與文件組織結構上比較亂,初學者不易理解,如erizo_controller文件夾包括了除nuve以及webrtc的js api之外的所有js代碼,不僅僅是erizoController,此外webrtc核心部分命名為erizo,也讓人與整個系統相混淆。系統組成如下圖所示,一個Nuve管理多個ErizoController(以下簡稱EC),一個EC管理多個ErizoAgent(以下簡稱EA),一個EA管理多個ErizoJS(以下簡稱EJ),一個EJ就是一個SFU,封裝了C++實現的webrtc、libav與libnice。
圖3 服務端系統組成圖
3.2 Nuve業務管理服務
圖4 Nuve業務管理服務
圖5 Nuve各模塊用途及對應文件
此外,這篇文章Licode(二):Nuve源碼分析 - CSDN博客 對Nuve做了更詳細的分析。
3.3 ErizoController信令服務
圖6 ErizoController信令服務
圖7 ErizoController各模塊用途及對應文件
3.4 ErizoAgent媒體服務
圖8 ErizoAgent媒體服務
圖9 ErizoAgent各模塊用途及對應文件
3.5 ErizoJS媒體轉發實體
圖10 ErizoJS媒體轉發實體
圖11 ErizoJS各模塊用途及對應文件
3.6 webrtc
由於webrtc工程代碼本身非常龐大,編譯工具也很獨特,Licode並未全部引入,僅僅抽取了webrtc的部分代碼,如下圖所示
圖12 webrtc各組件用途及對應目錄
由此可見,所抽取的代碼沒有webrtc自帶的音視頻處理框架(voice engine/video engine/media engine),這是由於SFC只在RTP層進行轉發並不解碼,只需要check一下媒體流屬性(如I幀頭)即可。抽取代碼的亮點在於將webrtc強大的抵抗網絡時延和丟包的網絡適應性算法和協議都提煉出來了,可以供廣大研究視頻傳輸的網絡適應方向的開發者們單獨學習、實驗並快速集成到自己非webrtc-based的產品中來。
webrtc的網絡適應性手段包括丟包重傳(ARQ)、前向糾錯(FEC)和擁塞控制(GCC),其中自動碼率(ARC)在GCC中。對網絡適應性有興趣的朋友可對照代碼看如下幾篇博文:
(1). 丟包重傳 (ARQ)
RFC4585 RTP/AVPF 傳輸層反饋;
RFC4588 4585補充 重傳包格式;
RFC5104 4585補充 負載層反饋;
RFC5124 RTP/SAVPF
RFC5450 4585補充 Jitter報告
(2). 前向糾錯 (FEC)
RFC2198 WebRTC中的前向糾錯編碼 - Red Packet
RFC5109 WebRTC 的前向糾錯編碼 - XOR FEC
(3). 帶寬檢測 (BWE)
傳統的基於RTCP RR報文中丟包數來進行帶寬檢測的算法目前被認為是最low的了,因為屬於事后處理;先進的算法是需要事前感知的(突發情況除外),假定帶寬是逐漸變窄的,根據信號估計理論可以在網絡鏈路發生丟包以前就監測到網絡擁塞,
GCC算法與函數調用 WebRTC基於GCC的擁塞控制(上) - 算法分析 WebRTC基於GCC的擁塞控制(下) - 實現分析
帶寬估計 WebRTC之帶寬控制部分學習(1) ------基本demo的介紹
接收緩沖區延遲估計 WebRTC視頻接收緩沖區基於KalmanFilter的延遲模型
3.7 libav、libnice、libsrtp
libav是從ffmpeg分離出的開發者發布的開源工程,與早期ffmpeg有很大相似性,對於僅調用api的開發者來說就簡單看成是代碼量更小、第三方依賴庫更少、編譯更簡單的輕量級ffmpeg吧。這個庫在Licode編譯過程中有處問題,Licode使用版本較舊的libav的接口,其中avcodec_alloc_frame和avcodec_free_frame都已經遺棄,改為av_frame_alloc/av_frame_free才可編譯通過。
libnice庫基於ICE協議實現的網絡層庫,Licode使用libnice庫來實現端到端的ICE連接和數據流發送接收,以及candidates(候選地址)和SDP(媒體描述文件)的相互交換。
libsrtp庫主要是是用來加密rtp/rtcp的。
4. 關鍵技術
4.1 網絡收發流水線架構技術
網絡收發或一個SFU實現,並非簡單的從一個源地址收到若干個數據包再復制多份發送給多個目的地址,正如3.6所描述,除了需要堆棧式的ICE地址轉換、DTLS/SRTP的解密,還需要流水線式的RTP/RTCP的丟包檢測與重傳、FEC處理、帶寬檢測與估計、包緩沖區調整以及平滑發送等若干個復雜步驟,這個流程在Licode代碼中是通過Pipeline-Handler這樣的架構實現的,由於webrtc僅提供底層算法並未提供SFU架構,所以我認為這種架構是Licode最重要的關鍵技術,可供未來想成為軟件架構師的開發者參考學習。日后我也再詳細分析這個架構,看看是否能抽象出一個新的設計模式來。
整個網絡收發的層級模型圖如下圖所示,其中每一層右側標注出了關鍵類。
圖13 網絡收發層級模型
Pipeline的類圖如下圖
圖14 Pipeline相關類圖
對Pipeline來說,需要全局管理、長時處理或非逐個包處理的流程,實現對象叫Service,包括Handler的管理、RTCP的計算與處理、當前狀態的處理、網絡質量的管理以及包緩沖的管理都是Service;而需要每次逐包處理的流程,實現對象叫Handler,包括19項Handler,其代碼都位於erizo\src\erizo\rtp目錄下
//新增全局處理的Service pipeline_->addService(handler_manager_); pipeline_->addService(rtcp_processor_); pipeline_->addService(stats_); pipeline_->addService(quality_manager_); pipeline_->addService(packet_buffer_); //新增單次處理的Handler pipeline_->addFront(std::make_shared<RtcpProcessorHandler>()); pipeline_->addFront(std::make_shared<FecReceiverHandler>()); pipeline_->addFront(std::make_shared<LayerBitrateCalculationHandler>()); pipeline_->addFront(std::make_shared<QualityFilterHandler>()); pipeline_->addFront(std::make_shared<IncomingStatsHandler>()); pipeline_->addFront(std::make_shared<RtpTrackMuteHandler>()); pipeline_->addFront(std::make_shared<RtpSlideShowHandler>()); pipeline_->addFront(std::make_shared<RtpPaddingGeneratorHandler>()); pipeline_->addFront(std::make_shared<PliPacerHandler>()); pipeline_->addFront(std::make_shared<BandwidthEstimationHandler>()); pipeline_->addFront(std::make_shared<RtpPaddingRemovalHandler>()); pipeline_->addFront(std::make_shared<RtcpFeedbackGenerationHandler>()); pipeline_->addFront(std::make_shared<RtpRetransmissionHandler>()); pipeline_->addFront(std::make_shared<SRPacketHandler>()); pipeline_->addFront(std::make_shared<SenderBandwidthEstimationHandler>()); pipeline_->addFront(std::make_shared<LayerDetectorHandler>()); pipeline_->addFront(std::make_shared<OutgoingStatsHandler>()); pipeline_->addFront(std::make_shared<PacketCodecParser>()); pipeline_->addFront(std::make_shared<PacketWriter>(this));
4.2 分布式保活技術
Licode的系統設計如3.1所述,Nuve管理多個EC,EC管理多個EA,EA啟動多個EJ進程;在資源上,Nuve負責EC的負載均攤,EC負責EA和EJ的負載均攤(畢竟EJ在EA內,EA除了監控OS性能並未太多其他工作),故Nuve需要EC分布式保活,EC需要EA分布式保活。Licode實現比較簡單,代碼可參考CloudHandler.js與ecCloudHandler.js。
例如Nuve每啟動一個EC立刻分配id,並加入到ECList中,並在DB中寫入id作為key,且在該key對應的value上進行累加,此后Nuve的秒級循環中不斷check這個value並繼續累加,如果超過閾值,則認為該EC失效,將此EC從list中刪除。Nuve同時提供keepalive接口,EC通過RPC調用此接口不斷對數據庫中本id的value清零,從而實現了分布式保活。EC與EA間方法一樣。
4.3 資源管理技術
這里定義的資源有三個含義:設備資源(設備簡單分為發布設備、接收設備、收發設備)、內容資源(某地的實時視頻或已錄制視頻)以及server處理能力余量(例如還能轉發的路數,還能容納的新成員)。
Licode的publish-subscribe模型將信令交互、內容管理(源站管理)、媒體分發三者分開,從而可實現多維度的資源管理。
Client 1 發的第一個信令僅起到了一個資源注冊的作用,即room中存在Client 1的id了;
Client 1 發的第二個信令表明該id可以發布新內容了,內容為Stream 1;
Client 2 同理,room記錄了Client 2的id與Stream 2;
Client 1發的第三個信令表明對Client 2的Stream 2內容感興趣開始訂閱了,於是啟動了SFU過程;
Client 3加入room后,可以發布資源(Stream3),可以訂閱資源(Stream1、Stream2),也可以什么都不做,因為任何在該room中注冊的設備都可以pubilsh內容資源或subscirbe已publish的內容資源。完全解耦帶來的好處是內容資源同設備資源一樣也可以輕易管理了,在啟動SFU過程中,server處理能力余量也能得到較好的管理。
H.323是緊耦合的,信令交互后必須開啟媒體交互(電路交換的思維,信道容量有限,申請資源趕緊用,用完趕緊釋放),對內容管理過於依賴於會議邏輯的實現,為了實現更靈活的互聽互看,其邏輯會堆砌的很復雜。
然而好的方法也不是萬能的,publish-subscribe的完全解耦雖帶來極大的可擴展性,但對於某些傳統業務更復雜了。比如MCU兩個終端互看,就需要啟動兩個erizoJS,互相訂閱發布,根據負載均攤的算法,可能兩個erizoJS都不在一個erizoAgent里(不在同一台物理機或虛擬機上)。MCU輪詢模式也稍復雜,輪詢者需要不斷的subscribe room中的發布者,發布者也要不斷的去remove、add,切換速度比接收所有流+I Frame Check的傳統方式要慢的多。
5. 總結
本文先介紹了自己對webrtc的概念理解與使用參考,接下來從系統架構、交互流程、系統組成與模塊划分幾個角度對Licode進行概要設計級別的分析,最后對自己覺得Licode比較有特色的三大技術,即網絡收發流水線架構技術、分布式保活技術與資源管理技術進行了淺層次的解釋。文中表述內容可能會有不准確之處,希望能與讀者們交流並及時改正。