Categories
程式開發

下一代音視頻實時傳輸 SDK 的架構設計


我是來自聲網的SDK資深架構師,負責整個前端API。聲網在全球部署了軟件定義的實時網 SD-RTN™,它為開發者提供了實時音視頻專用網絡服務。之前有一位演講人說 API 很重要。確實是這樣的。

我會從這 4 個方面簡要介紹一下我們的架構經驗:

1.RTC 場景現在面臨的問題和挑戰;
2.重點介紹一下架構和API的設計和思想;
3.如何對架構上進行重構或代碼改進,從而更好地控制媒體和網絡;
4.為了 SDK 的低延遲、高性能、高並發,我們做了哪些探索。

考慮到大家對 RTC 領域不是太了解,我先簡單介紹一下。其實它是一個很傳統的實時音視頻場景,現在最主流的技術是由谷歌提供 WebRTC,利用它,你可以通過瀏覽器與另一個人進行實時音視頻的通話。聲網也參考了一些 WebRTC 的設計,從最開始的一對一通話,然後到一對一多通話,到現在一個頻道可以支持上百萬的用戶,其中也有很多技術挑戰。

問題與挑戰

首先,從場景角度講,我們會遇到的問題和挑戰有哪些呢?

  • 傳統的 RTC 場景:現在我們可以看到很多場景,例如說 4K 高清視頻,如果傳統的SDK不做改善的話,傳輸一個 4K 視頻,對它的內存、CPU等各方面都會帶來極大的挑戰。
  • 娛樂社交和在線教育:現在不光需要打開 Web 瀏覽器、攝像頭,還需要打開本地的播放器,傳輸本地播放器的內容。
  • 雲遊戲加速:現在很多廠商還在開發雲遊戲,遊戲運行於服務端,數據以音視頻、指令等形式傳輸至手機,手機僅僅負責渲染,其中最大的挑戰就是延時,如果從服務端到手機的傳輸延時超過200ms 的話,遊戲體驗會變得很差,這就需要一個類似於聲網的實時碼流加速傳輸網絡。
  • SIP/PSTN:SIP傳統的網絡電話,在全球有大量的業務需求,通過網絡的流量來達到整個 RTC 的效果。
  • WebRTC 加速:如果在中國和美國之前通過公網 P2P 溝通,卻缺少一個底層網絡網和SDK的介入的話,其實是很難工作的。一個沒有任何 QoS(服務質量)保障的連接,通話會很糟。

這些都是我們在 RTC 領域會遇到的場景,而 WebRTC 一類的開源引擎是遠不能達到我們對場景的技術要求的,需要一個具備網絡傳輸、音視頻編解碼等能力的 SDK 來實現。

面對這樣的場景需求,SDK 需要具備哪些特性呢?

首先是合理的架構設計,它有兩個特點:第一點是媒體和網絡是獨立控制的。因為在類似 PSTN、雲遊戲加速傳輸的場景中,它的媒體數據是由自己處理的,僅需要我們提供網絡傳輸加速的能力。但像 4K 音視頻的實時傳輸,從採集、編碼、渲染到傳輸,都需要 SDK 來完成。所以對於不同場景,SDK 就需要提供不同層次和不同模塊的接口。

第二是面向對象的 API 設計。關於 WebRTC 有個小故事,P2P 連接的協商過程是通過 SDP 協議做的,而整個能力協商的過程通過交換 offer 和 answer 就可以快速握手。最初這種設計認為協商過於復雜,一般的工程師搞不懂,所以並沒有開放接口讓開發者控制SDP相關內容。微軟在進入 RTC 領域後,基於 WebRTC 貢獻了 ORTC 項目,它 API 設計則是面向對象的。他們曾經有過這樣一個看法,如果可以開放更多面向底層、面向對象的 API,開發者可以根據自己的場景需要來搭建。這也是面向對象 API 設計的重要性。

現在很多提供 API 的公司都強調一點,叫做易用性,十幾行代碼就可以讓你實現某個功能。因為以前開發者的能力普遍還沒有那麼強,也不清楚 RTC 場景是怎樣的,所以我們通過這種簡單的方式,讓任何一個小白開發者都可以輕鬆做出一個 App。隨著這些年的發展,場景變得越來越複雜,開發者的能力也越來越強,我們完全可以提供面向對象的 API,讓開發者自己通過它們構建自己想要的場景。

除了合理的架構設計,還要支持豐富的媒體傳輸能力,具備低延時、高性能、高並發的特性等。這些我稍後會詳細分析。

架構與API設計

下一代音視頻實時傳輸 SDK 的架構設計 1

先說一下傳輸 SDK 的分層。如上圖,SDK 的分層最底下是網絡層。最早之前的一些網絡傳輸都是基於TCP 的,TCP 和UDP 之間的區別,我就不說了,但是對於媒體的實時傳輸來講,在有網絡丟包時,TCP 的延時會非常大,完全不能滿足實時互動的要求,所以最核心的是說媒體其實是不需要,就是在網絡上丟包的情況下,TCP現在幾乎所有的媒體實時傳輸都是基於UDP 實現的,包括比較新的QUIC協議,底層也是基於UDP的。

Transport(UDP)上面是擁塞控制與網絡連接控制,這是 RTC 領域最重要的一個技術環節和算法模塊。目的是要在比較複雜錯綜的網絡環境下,實現更靈活的網絡控制。

然後是 Media stream 層,它類似於一個 RTP 的協議,更多是面向媒體流,這一層有時間戳和一些標準的協議。

再上面就是 Media Engine。 Media Engine有兩層,一層是編解碼器,一層是輸出編碼後的數據,比如 VP8、VP9,也包括一些傳統的編碼碼率。

再往上是 Frame YUV/PCM。 WebRTC 一般只能傳YUV和PCM的數據。這裡講一個小的故事,很多中國的開發者會把 WebRTC 當成一個 SDK 用,其實 WebRTC 根本算不上是一個 SDK,它僅僅是一個 Media Engine。 Media Engine 和 SDK 最主要的差別是什麼呢? Media Engine僅僅是提供了一個功能,比如說像谷歌自己也有 RTC 的功能,它僅僅是把 WebRTC 的代碼當成一個功能模塊來使用,Chromium 才是一個真正的 SDK。

說完網絡與對象的簡單分層,我們來一起看一下對象的建模。

下一代音視頻實時傳輸 SDK 的架構設計 2

我們去分析一個業務場景,或者是去設計一個 API,最重要是要了解你控制的對像是什麼。首先,我們一般的輸入源有攝像頭、屏幕共享、錄音設備,以及文件或客戶自定義數據,對於這些對象,我們通過Audio Source 和Video Source 作為管理,既可以管理YUV/PCM 這種原始採集數據,也可以管理類似H264/VP8 這種編碼後數據。這些數據源可以產生媒體流,對於媒體流對象,我們用 Video Track 或者 Audio Track 來管理,對於本地發布流和遠端訂閱的流,用 local 和 remote 作為區分。而最重要的模塊自然就是網絡,我們抽象為一個叫 RTC Connection 的對象,負責網絡連接到我們的 SD-RTN™ 上。每一個 Connection 都有且只有一個 local user 負責媒體流的發布和訂閱。除此以外,video 和 audio 的處理模塊也都對象化處理,如 video filter、audio filter、audio device manager 等。把媒體流發佈到這個 Connection 上,你可以進行遠端的通話了。

在這裡我們可以看到面向對象 API 的一些優點。你可以在其中創建多個對象,對應這個圖來講就是可以創建多個 Local Video Track,能同時有幾個或幾千個 RTC Connection,可以同時與多人建立連接,或者創建更多頻道。

從我們的理解來講,API 的設計還有一個非常重要的地方。很多初級開發者都會覺得 API 僅僅是把 SDK 的功能體現給使用者。而在我們看來,好的 API 設計“能自己講故事”。當別人看過你 30%的 API 之後,就能知道你整個架構和設計理念是什麼,它能成為架構師與開發者對話的一個渠道。如果發送編碼數據和發送原始數據 是完全兩套API的style,就會給開發者帶來困惑。所以在 API 的設計之中,架構要做的不僅僅是展現功能,還將你的API 設計理念通過 API 傳達給使用者。

下一代音視頻實時傳輸 SDK 的架構設計 3

舉一個例子。我們怎麼實現與遠端用戶的通話。首先你要創建一個 Connection,你作為一個 Local User 想要發布流就需要一個 Local Track,這時候你需要調用 Publish Track 把 Local Track 發送到 Connection 上,這樣遠端的用戶就能看到你了。同樣的,你也可以去訂閱遠端用戶(Remote Users)的流,他的 Remote Track 會通過 Connection 發送到 Local Users 這一端。這就是一個完整的“故事”。在聽完這個“故事”之後,如果有一天你想傳輸你的攝像頭數據,對你來講,它仍然是一個 Track,只是 Source 不同了。只有會講“故事”的 API,才能讓用戶理解如何去靈活使用。

另外,還有很重要的一點,就是不要創造新的名詞,應該符合全球定義的標準。我們在定義 API 的時候,就會大量地翻閱一些國際標準,比如 W3C 的,這些都是符合開發者認知體系的。

媒體和網絡控制

接下來,我們講講架構設計裡面的一些具體實現。

我不知道大家是否聽過 SOLID 法則。在講它之前,我們要講講為什麼說 WebRTC 只是一個功能模塊。當你去玩一些開源項目,谷歌提供的能力也好,WebRTC 的開源代碼也罷,你可能會發現它的適用場景非常單一,它只是適合 P2P 或者跟一些服務器打交道。

作為一個 SDK,要講功能開放給開發者,就必須要實現一個 Pipeline。從最簡單的 Pipeline 來講,有 5 個 SOLID 法則:

  • 單一責任法則。假如你有一個100 人的團隊,每個團隊都有自己的任務,有做降噪的,有做視頻編碼的,好的架構是讓這些人只需要專注於自身的功能模塊的實現,代碼如何寫,算法如何改進,而不需要去考慮其它模塊中的業務。
  • 開閉法則。當你需要開發一個新功能的時候,不需要去修改之前的代碼,這是好的架構。
  • 模塊可替換。作為一個好的 SDK 架構,SDK 中的任何接口和模塊都是可以被無縫替換的。
  • 接口隔離。用戶可以清楚找到控制對像或者接口,而不需要理解很多不感興趣的接口。
  • 最後,依賴反轉是特別重要的一點。任何API 都需要面向接口編程,這樣一來,用戶就不需要去理解模塊內部是如何實現的,只需要看接口就行了。

下一代音視頻實時傳輸 SDK 的架構設計 4

我們的 Pipeline 如上圖所示。綠色的是接收端,中間通過 Agora SD-RTN™進行傳輸。我們會將一些算法、引擎等用 Pipeline 的方式進行組織。基於 SOLID 法則,我們面向各種場景的應用,代碼會變得越來越快、越來越方便,算法專家也不用去了解其他模塊,只專注於手上的工作。

下一代音視頻實時傳輸 SDK 的架構設計 5

舉個例子,我們有一個叫做 Media Player Kit 的組件,它支持本地媒體播放和多流互動(詳見我們此前的文章),如上圖是它的架構。 Media Player 可以支持本地媒體播放,也可以將本地視頻流發送到遠端。如果你還記得“API需要講統一的故事“,就能想像到,Media Player 是一個媒體數據源,可以提供video track 和audio track,如果將這些track 加上renderer,就可以本地播放,如果把這個track 發佈到RTC Connection 就可以和遠端用戶共享了。

Pipeline 就像一個管道一樣,一般來說 Pipeline 都是單向的,從管道的入口到出口,但其實Pipeline 裡最核心的一些控制是通過負向反饋來做的,這也是控制理論經典的話題。

下一代音視頻實時傳輸 SDK 的架構設計 6

在RTC 領域裡,有一個很核心的Pipeline 叫“帶寬估計”,它可以實時監控當前網絡是否有擁塞,當發現有擁塞的之後,會立即反饋預估的帶寬值到Video Quality Controller 模塊,動態調整碼流、幀率,以保證音視頻流的實時體驗。如上圖所示,Video Quality Controller模塊同時還會監聽 CPU 狀態,因為低端手機,遇到較高幀率、分辨率的視頻會容易遇到 CPU 的性能瓶頸,從而出現卡頓。 Video Quality Controller 模塊會基於收到的帶寬估計和CPU 狀態信息來動態改變編碼碼率,比如你現在發送的是2M 的碼流,但是遇到了網絡擁塞,那麼就會降低一些畫質,改為發送1M 的碼流,能保證通話是流暢的。

在架構中,策略層和功能層是要嚴格區分的。從上圖來講,實線的部分就是數據通道,它提供了視頻的採集、編碼、傳輸功能,而下方的模塊則是策略層,負責根據網絡及設備情況來反饋給功能模塊,調整其中的碼率、幀率這樣的參數。

低延時、高性能、高並發

除此弱網對抗的算法等常規方法以外,我們還可以在開發工具層面來進一步優化網絡延時。就好像萊特兄弟造飛機一樣。他們做的最重要的一項設計就是風洞。這飛機真正試飛前就可以進行充分的測試。我們也一樣,在此方面也花費了很多精力。我們做了配套的性能調查工具、系統工具,比如perf性能瓶頸的查找,熱點代碼的定位等,以此來做到 SDK 的白盒化。我們通過這些工具來不斷優化SDK 的各項指標,包括延時、弱網對抗、內存優化、CPU 優化等。

以分段延時為例,如果以光的速度來計算,從中國到美國直線傳輸大概需要 30ms。我們聲網在全球的平均延時可以達到 76ms。下圖是一個傳輸的分段延時示意圖。我們通過工具來對每段延時生成清晰的報表。這些監測數據讓我們能有針對性地優化不同的模塊。

下一代音視頻實時傳輸 SDK 的架構設計 7

同時,我們還要對弱網對抗算法進行不斷的驗證和優化。我們會模擬丟包、模擬延遲,我們在算法上會關注碼率跟踪速度、帶寬預估準確度。如下圖所示,紅線是我們的預估值,黑線是驗證的數值,兩者越接近,說明碼率控制得越好。

下一代音視頻實時傳輸 SDK 的架構設計 8

在高性能方面,我們提出了內存池和線程池的概念。

我們需要根據系統內存情況,自動調整內存池的大小,不同大小的空閒隊列需要自動進行負載均衡,同時要有效地減少 malloc/free 調用次數、頁錯誤數量。確保 SDK 在低內存環境中的可用性。

在某些服務器推流的場景下,高並發可以極大的降低用戶的服務器使用成本。如果每一路通話或者推流都需要一個進程實例的話,在並發情況下,CPU 會消耗在線程切換上。在我們的 SDK 中,我們可以通過線程的方式多開實例,可以極大地降低線程梳理,從而提高並發量。我們也進行了一些測試,業界其他產品在相同機器環境下,並發路只有 600 路,而我們聲網的最大並發數可以到達 3400 路。

下一代音視頻實時傳輸 SDK 的架構設計 9

在我們的SDK中,線程是通過統一的線程池管理的,這種做法既讓研發功能模塊中,降低並發編程模型的複雜度,有可以讓我們的線程數目受控,比如如果模塊或者功能團隊需要新的線程,需要提出申請,SDK通過注入的方式,將線程給予模塊使用。這對於 SDK 的性能改善會很有幫助。

作者介紹

章真,聲網 Agora SDK 資深架構師。

本文轉載自公眾號聲網Agora(ID:shengwang-agora)。

原文鏈接

https://mp.weixin.qq.com/s?__biz=MzIwNzA1OTA2OQ==&mid=2657211475&idx=1&sn=032a5f7f487a2c768ccad33790c40d88&chksm=8c8d5541bbfadc573b31a57e7ff7fb4f698a41e47603ecd9708f2c8b4362dad3d1dc18552ba7&scene=27#wechat_redirect