流媒體傳輸協議之 RTP (上篇)

阿里雲視訊雲發表於2021-02-09

本系列文章將整理各個流媒體傳輸協議,包括 RTP/RTCP,RTMP,希望通過深入梳理協議的設計細節,能夠給流媒體領域的開發者帶來一定的啟發。

作者:逸殊
稽核:泰一

介紹

RTP,即 real-time transport protocol(實時傳輸協議),為實時傳輸互動的音訊和視訊提供了端到端傳輸服務。其中包括載荷的型別確認,序列編碼,時間戳和傳輸監控功能。一般應用都是基於 UDP 協議,來使用 RTP 的多路技術以及驗和服務。然而,RTP 還可以與其它適合的協議並用,如果底層網路支援多路分發,RTP 還可以將資料傳輸給多個目標。

需要注意的是 RTP 不提供任何機制以保證資料的實時性和 QOS (quality-of-service),而是依賴底層的服務來提供這些功能,RTP 既不保證傳輸的可靠性也不保證無序傳輸,同時也不假定底層網路是可信任的和有序的。接收端可以利用 RTP 中的序列號排序收到的報文。

RTP 與 RTCP

  • 實時傳輸協議 (RTP),傳輸具有實時特性的資料

  • RTP 控制協議 (RTCP),監控 QOS 和傳遞會話中參與者的資訊。它沒有明確的成員控制功能和 Session 建立過程,但這些對一個相對寬鬆的 Session 控制來說已經足夠了,它沒有必要包含一個應用的所有控制功能。

RTP 代表了一種新型協議,它遵循 Application level framing 和 Integrated layer processing。即 RTP 可以比較容易的擴充以傳遞某些特定需要的內容,而且可以比較容易地整合進某個應用,而不是作為一個獨立的補充層。RTP 協議被故意地設計成不完整的協議框架。

RTP 的使用場景

下面的例子描述了 RTP 的部分特性,選擇的例子是用來闡明基於 RTP 的應用的基本操作,而不是說 RTP 僅能用於此類應用。

簡單的多播音訊會議

一個小組要通過網路開一個音訊會議,他們用了 IP 多播服務。基於某種分配機制,小組得到了一個多播組地址和一對埠,其中一個埠是用來傳輸音訊資料的,另一個是用來傳輸 RTCP 報文的。這個組播地址和埠發給了所有與會者。如果想要引入一些安全策略,可以對資料包文和控制報文加密,然後把加密時用到的金鑰分發給與會者。

這個音訊會議軟體,可能會一直髮送時長為 20ms 的音訊資料包。每個實際音訊資料包,都以 RTP 頭資料開始,然後再以 UDP 協議封裝併傳送。RTP 包的頭部標識了該包的資料型別,以便訊息傳送器來改變資料的編碼。例如,針對低頻寬的與會者進行一些調節,或者對網路擁堵作出反應。

像 UDP 這類包型別的網路,偶爾會丟包,亂序,延遲不定長時間。為了解決這類意外情況,RTP 包中包含了時間資訊和序列號,這樣接收者就可以通過它們重排資料包的時序。在這個例子中,我們就可以按順序地播放每個 20ms 的音訊資料。在會議中對每個資料來源的 RTP 報文時序重排都是獨立進行的。接收者也可以通過序列號來確定丟失了多少報文。

因為這個小組開會期間,會有一些人加入或退出這個網路會議,所以我們需要知道具體是誰加入了會議,以及他們有沒有正常地接收到音訊資料。出於這個目的,每個網路會議的客戶端都會週期性的通過 RTCP 埠報告使用者的名字以及自己接收資料的情況,如果有人接收資料不正常,可能就需要對應的改變編碼。而且,除了使用者的名字之外,還會有一些別的資訊,用來控制頻寬限制。當有人從視訊會議中退出時,還需要傳送一個 RTCP BYE 報文。

音訊和視訊會議

如果這個會議既要傳輸音訊又要傳輸視訊的話,它們會以獨立的 RTP Session 傳輸。也就是說,負責音訊傳輸的部分和負責視訊傳輸的部分會通過不同的組播地址(和埠對)分別傳輸各自的 RTP 報文和 RTCP 報文。在 RTP 協議這一層,音訊和視訊 Session 並沒有被組合到一起。我們期望與會者用同一個名字來建立音訊和視訊 Session,這樣這兩個 Session 就能聯絡起來了。

RTP 協議之所以這樣設計,一個原因是某些與會者可以選擇只接收某一種型別的資料(只接收 Audio)。即便 Audio 資料和 Video 資料是獨立分發的,但是我們仍然可以通過參考 RTCP 協議中時間資訊來同步播放它們。

Mixers & Translators

到目前為止,我們都是假設所有的與會者想要接收同一格式的媒體資料。但是這顯然不太合適,考慮一下,可能某些與會者網速相對較慢,而其他人網速卻比較快。對於這種情況,我們不應該強迫所有人都用低頻寬並降低音訊編碼的質量,而是使用 RTP 級別的中繼節點(Mixer)來給周圍低頻寬使用者分發低頻寬消耗的資料。

這個 Mixer 將接收到的不同與會者的音訊資料同步,並將它們耦合到一個單一流中,然後將這個流用低頻寬消耗的編碼方案進行壓縮,最後傳送給那些低頻寬的與會者。Mixer 可以在 RTP 頭部寫一些特殊內容,來表明該 Mixer 包具體耦合了哪些與會者,這樣,接收到該 Mixer 包的人就能確定當前說話的人是誰了。

此外,有些與會者可能處於應用級防火牆的後面,無法僅通過 IP 組播訪問。這種情況下 Mixer 就沒有什麼意義了,他們需要另一類 RTP 級別的中繼(Translator)。我們需要兩個 Translator,安裝在防火牆的兩面,外面的 Translator 將收到的所有組播報文,通過一個安全連線傳輸給防火牆裡面的 Translator。然後,防火牆裡的 Translator 再將這些報文分發給內網的與會者。

層編碼

多媒體應用可以根據接收者的能力或者網路擁堵的情況調整傳輸速率。許多實現將位元速率控制的責任放在了傳送端。這和組播模式不太相容,因為各個不同的資料接收者會有不同的頻寬情況,這就會產生木桶效應,即頻寬最差的接收者會拖垮整個會議的通訊質量。

因此,頻寬自適應的工作應該放到接收者這裡,傳送者需要拆分出面向不同頻寬與會者的媒體流(500K,2M,5M),它們分別對應了不同的組播地址,資料的接收者根據自己的頻寬情況,選擇加入適合的組播。

定義

  • RTP payload:RTP 包中傳輸的資料,比如音訊取樣資料或者壓縮過的視訊資料。
  • RTP packet:由定長 RTP 頭部,資料來源者的列表,RTP payload 組成的資料包。一些下層協議可能會自己定義 RTP 的封裝格式。一般來說,一個下層協議包只包含一個 RTP 包,但是也有可能多個 RTP 包被合併到一起。
  • RTCP packet:RTP 控制報文,由定長的 RTC 頭部開始,之後會跟著一些結構化的元素,它們在 RTCP 發揮不同功能時,會有不同的結構。通常多個 RTCP 包會被合在一起,通過一個下層協議包一起傳送。
  • Port:傳輸層協議中用來區分某一主機下不同應用的抽象。RTP 協議依賴更底層網路提供埠機制,繼而提供多播的 RTP 和 RTCP 報文。
  • Transport address:網路地址和埠的組合,用來定位傳輸層的節點。
  • RTC media type:一個 RTP Session 中所用到的所有 payload 型別的合集。
  • Multimedia Session:視訊會議組中同時工作的一組 RTP Session。例如,視訊會議中的 Audio Session 和 Video Session。
  • RTP Session:一組參與者利用 RTP 來通訊的組合。一個參與者可以同時加入到多個 RTP Session 中。在 Multimedia Session 中,除非特意將多媒體編碼進同一資料流,否則,每個資料流會通過不同的 RTP Session 傳輸。與會者通過 Transport address 來區分不同的 RTP Session。同一 RTP Session 的不同與會者會共享同一個 Transport address,也可能每個與會者都有自己的 Transport address。在單播的情況時,一個與會者可能用同一對埠(RTP&RTCP)來接收所有其他與會者的資料,也可能對不同的與會者採用不同的埠對(RTP&RTCP)。
  • Synchronization source (SSRC):RTP 報文流的一個 Source,由 RTP 頭中定義的 32-bit 的 SSRC identifier 來標識,這樣做是為了不依賴網路地址。同一個 SSRC 中傳送的所有包都具有同一時序和序列號間隔,因此接收者可以通過 SSRC 將收到的資料包分組並排序。一個訊號源(麥克風,攝像頭,Mixer)的報文流會有由一個 SSRC 的傳送器傳送。一個 SSRC 可能會隨著時間的變化,改變其資料格式,例如音訊編碼。SSRC 的身份識別碼都是隨機生成的,但是必須保證整個 RTP Session 中該身份識別碼不會重複,這些工作是通過 RTCP 來完成的。如果一個與會者在一個 RTP Session 中傳送不同的媒體資料流,那麼每個流的 SSRC 必須不同。
  • Contributing source (CSRC):RTP Mixer 所混合的所有資料對應的 SSRC 的列表。Mixer 會將一個 SSRC 列表寫入 RTP 頭中,該列表包含了這個混合報文中包含的所有來源 SSRC。
  • End system:一個生成 RTP payload 和消費收到的 RTP payload 的應用。一個 End system 可以扮演一個或者多個 SSRC 角色,但是通常是一個。
  • Mixer:一箇中介系統,它接收一個或多個 Source 的資料,隨後它可能會改變這些資料的格式,並將它們合併為一個新的 RTP packet。因為,多個輸入源的時序通常來說都不一致,所以 Mixer 通常會同步不同源的時間,並生成一個自己的時序來處理合並資料流。所有從 Mixer 輸出的資料包都會標記上該 Mixer 的 SSRC。
  • Translator:一箇中介系統,它會轉發 RTP packet 但是不改變其原本的 SSRC。
  • Monitor:一個在 RTP Session 中接收 RTCP 報文的應用,它會總結資料被接收的報告,併為當前分發系統評估 QOS,診斷錯誤,長期統計。Monitor 可以整合進會議應用中,也可以是獨立的第三方應用,只接收 RTCP 報文,但是什麼都不傳送。
  • Non-RTP means:為了讓 RTP 提供可用服務而加入的協議或者機制。特別是在多媒體會議中,需要一種控制協議來分發組播地址和加密金鑰,協調加密演算法,定義 RTP payload 格式和 RTP payload 型別的動態對映。

位元組序,資料對齊,時間格式

所有的整數字段都使用網路位元組序(大端序),除了特別宣告,數字常量由十進位制表示。

所有頭部資料都會根據其資料的原始長度進行對齊,比如,16-bit 的資料會對齊到偶數偏移,32-bit 的資料會對齊到可被 4 整除的偏移。此外,用 0 來作為填充位元組。

Wallclock time(絕對日期和時間)是用網路時間協議(NTP)的時間格式來表示,即從 1900 年一月一日 0 點到現在的秒數。NTP 的時間戳使用了 64-bit 的無符號固定小數點的形式表示,其中頭 32-bit 用來表示整數部分,後 32-bit 用來表示小數部分。RTP 的時間格式採用了 NTP 的簡化版,他只用了 NTP 的 64-bit 資料的中間 32-bit,即前 16-bit 表示整數,後 16-bit 表示小數。

NTP 時間戳到 2036 年就會迴圈回 0,但是因為 RTP 只會使用不同 NTP 時間的差值,所以這不會有什麼影響。只要一對時間戳都在同一個迴圈週期裡,直接用模組化的架構相減或者比較就可以,NTP 的迴圈問題就不重要了。

RTP 資料傳輸協議

RTP 的定長頭欄位

RTP 頭的格式如下:

上圖中前 96-bit 的資料是每個 RTP 包都有的部分,CSRC 部分只有 Mixer 傳送的報文才會有。這些欄位的意義如下:

  • Version(V):2 bits,RTP 版本號,現在用的是 2。(第一個 RTP 草案用的 1)
  • Padding(P):1 bit,如果設定了該欄位,報文的末尾會包含一個或多個填充位元組,這些填充位元組不是 payload 的內容。最後一個填充位元組標識了總共需要忽略多少個填充位元組(包括自己)。Padding 可能會被一些加密演算法使用,因為有些加密演算法需要定長的資料塊。Padding 也可能被一些更下層的協議使用,用來一次傳送多個 RTP 包。
  • Extension(X):1 bit,如果設定了該欄位,那麼頭資料後跟著一個擴充資料。
  • CSRC count(CC):4 bits,CSRC 列表的長度。
  • Marker(M):1 bit,Marker 會在預設中進行定義(預設和 RTP 的關係可以參考 rfc3551,我的理解是預設是對 RTP 的補充,以達到某一類實際使用場景的需要),在報文流中用它來劃分每一幀的邊界。預設中可能會定義附加的 marker,或者移除 Marker 來擴充 payload type 欄位的長度。
  • Payload type(PT): 7bits,該欄位定義 RTP payload 的格式和他在預設中的意義。上層應用可能會定義一個(靜態的型別碼 <->payload 格式)對映關係。也可以用 RTP 協議外的方式來動態地定義 payload 型別。在一個 RTP Session 中 payload 型別可能會改變,但是不應該用 payload 型別來區分不同的媒體流,正如之前所說,不同的媒體流應該通過不同 Session 分別傳輸。
  • Sequence number:16 bits,每傳送一個 RTP 包該序列號 + 1,RTP 包的接收者可以通過它來確定丟包情況並且利用它來重排包的順序。這個欄位的初始值應該是隨機的,這會讓 known-plaintext 更加困難。
  • Timestamp:32 bits,時間戳反映了 RTP 資料包生成第一塊資料時的時刻。這個時間戳必須恆定地線性增長,因為它會被用來同步資料包和計算網路抖動,此外這個時鐘解決方案必須有足夠的精度,像是一個視訊幀只有一個時鐘嘀嗒這樣是肯定不夠的。如果 RTP 包是週期性的生成的話,通常會使用取樣時鐘而不是系統時鐘,例如音訊傳輸中每個 RTP 報文包含 20ms 的音訊資料,那麼相鄰的下一個 RTP 報文的時間戳就是增加 20ms 而不是獲取系統時間。和序列號一樣時間戳的初始值也應該是隨機的,而且如果多個 RTP 包是一次性生成的,那它們就會有相同的時間戳。不同媒體流的時間戳可能以不同的步幅增長,它們通常都是獨立的,具有隨機的偏移。這些時間戳雖然足以重建單一媒體流的時序,但是直接比較多個媒體流的時間戳是沒辦法進行同步的。每一時間戳都會和參考時鐘(wallclock)組成時間對,而且需要同步的不同流會共用同一個參考時鐘,通過對比不同流的時間對,就能計算出不同流的時間戳偏移量。這個時間對並不是和每個 RTP 包一同傳送,而是通過 RTCP 協議,以一個相對較低的頻率進行共享。
  • SSRC:32 bits,該欄位用來確定資料的傳送源。這個身份標識應該隨機生成,並且要保證同一個 RTP Session 中沒有重複的 SSRC。雖然 SSRC 衝突的概率很小,但是每個 RTP 客戶端都應該時刻警惕,如果發現衝突就要去解決。
  • CSRC list:0 ~ 15 items, 32 bits each,CSRC list 表示對該 payload 資料做出貢獻的所有 SSRC。這個欄位包含的 SSRC 數量由 CC 欄位定義。如果有超過 15 個 SSRC,只有 15 個可以被記錄。

RTP Session 多路複用

在 RTP 中,多路複用由目標傳輸地址(address:port)提供,不同的 RTP Session 有不同的傳輸地址。

獨立的音訊和視訊流不應該包含在同一個 RTP Session 中,也不應該通過 payload 型別和 SSRC 來區分不同的流。如果用同一個 SSRC 傳送了不同的資料流,會引入如下問題:

  1. 假設 2 個音訊流共享了一個 RTP Session,並且用了同一個 SSRC,如果其中一個要改變編碼,這就導致了 payload 型別的改變,但是協議中沒有提供方法來讓接收者知道具體是哪個音訊流改變了編碼。

  2. 一個 SSRC 只有一個對應的時序和序列號,如果多個流有不同的時鐘週期的話,就需要不同的時序。而且還不能用序列號來確認是哪個流丟包了。

  3. RTCP 傳送者報告和接收者報告只描述了時序和序列號而不包含 payload 型別資料。

  4. Mixer 無法將不相容的兩個流合併。

  5. 如果一個 RTP Session 中包含了多個媒體流後就會失去如下優勢:

  • 使用不同的網路路徑或者分配網路資源
  • 只接收某一種媒體資料(網路較差時只接收 audio)
  • 接收方對不同的媒體型別做不同的處理

不同的流使用不同的 SSRC 但是仍然用同一個 RTP Session 傳送確實可以解決前三個問題,但是仍然無法解決後兩個問題。

預設可能對 RTP 頭的改動

現有的這些 RTP 報文頭對一般應用來說已經足夠了。如果有需要,頭欄位可以根據預設進行一些修改,但仍要保證檢測和統計功能的正常使用。

RTP 頭擴充

RTP 提供了一個擴充機制,讓上層應用可以將自定義的資訊儲存在 RTP 報文頭。如果上層應用收到了無法識別的頭部擴充資料,它們會忽略它。

值得一提的是,這個頭部擴充是有一些限制的。如果附加資訊只對某些 payload 格式才有意義,那麼最好還是別把這些資訊放到頭部擴充中,而是放到 payload 部分。


如果 RTP Header 中的 X 位設定為 1,那麼 Header 後必須跟著一個不定長度的擴充塊,緊跟著 CSRC list(如果有的話)。擴充部分的頭部包含一個 16-bit 的資料來描述擴充塊包含多少個 32-bit 字(不包括擴充部分的頭部)。因為 RTP 頭部後面只能連線一個擴充塊,考慮到有些應用可能會有多種型別的擴充塊,所以擴充塊的頭 16-bit 留給開發者去自定義一些引數。

RTP 控制協議

同一個 Session 所有參與者會週期性地傳送控制報文,RTP 控制協議就是通過這種方式進行的,和 RTP 資料的傳播一樣採用了組播的機制。下層協議必須提供資料包和控制報文的多路複用功能,例如使用獨立的 UDP 埠分別傳輸資料和控制報文。RTCP 協議具有如下四大功能:

  1. 最主要的功能是反饋資料分發的質量。這也是 RTP 作為一個傳輸協議來說最關鍵的功能,而且它和流量控制,擁塞控制息息相關。反饋資訊可能會直接影響自適應編碼的控制。傳送反饋報告給所有的參與者可以讓它們評估遇到的資料分發問題是個人問題還是全域性問題。通過 IP 組播這樣的分發機制,像網路提供商這樣的機構即便不加入到這個 RTP Session 中也能收到反饋資訊,它們會扮演一個第三方監測者的角色去確認資料分發問題。這個反饋的功能無論是 RTCP 的傳送者還是接收者都會進行報告。

  2. RTCP 還會給每個 RTP source 帶一個不變的傳輸層身份識別符(CNAME),因為 SSRC 可能會中途改變(程式重啟),所以接收者需要這個 CNAME 來持續追蹤每個與會者。而且,接收者可以通過 CNAME 來將同一個與會者的所有資料流聯絡在一起,比如同步音訊和視訊。單個媒體內部的資料同步也需要 NTP 和 RTP 時間戳,這些資料都在資料傳送者傳送的 RTCP 報文中。

  3. 因為前兩個功能需要所有的與會者都傳送 RTCP 報文,所以需要適當的控制報文傳送的頻率以保證 RTP 協議可以在大量客戶端一同加入時也能正常工作。通過每個參與者都廣播控制報文的方式,每個人都能獨立地計算出參與者的總數。

  4. 還有一個可有可無的功能,RTCP 可以用來共享小量的 Session 控制資訊,例如辨認參與者的身份。通常來說,該功能會被那些管理比較鬆散的 Session 使用。RTCP 可以作為一個方便的與其他參與者溝通的通道,但是你也別期望 RTCP 可以滿足一個應用的所有傳輸控制需求,這類需求往往是通過一個更高層的 Session 控制協議來滿足。

這四個功能中,前三個應該會被所有應用場景使用(IP 組播機制下)。RTP 應用的設計者應該避免自己的應用只能工作在單播模式,RTP 應用應該設計成可擴充的,要考慮大量使用者併發時的情況。此外,RTCP 的傳輸應該根據傳送者和接收者角色的不同而分別進行控制,例如一些單項連線,接收者的反饋資訊就發不出來。

提醒:像是指定源組播路由(SSM),只有一個人可以傳送資料,其他接收者不能用組播來和其他人直接通訊。對於這種情況,建議完全關閉接收者的原始 RTCP 功能,然後為這個 SSM 設定一個 RTCP 的介面卡,來接收所有的反饋。

RTCP 包格式

RTCP 定義了許多包型別來傳輸不同的控制資訊:

  • SR:傳送者報告,傳送者資料傳送和接收的統計。
  • RR:接收者報告,只接收資料的節點的接收統計。
  • SDES:Source 描述,包括 CNAME。
  • BYE:表示退出。
  • APP:上層應用自定義。

每個 RTCP 包都有一個和 RTP 類似的固定格式的頭,後面跟著長度不定的結構化資料,在不同 RTCP 型別時,這些結構化資料各不一樣,但是它們必須都要 32-bit 對齊。RTCP 的頭部是定長的,而且在頭部有一個欄位來描述這個 RTCP 資料的長度,因此 RTCP 可以被複合成一組一同傳送,還不需要任何分隔符來分割出單個的 RTCP 包。下層協議可能會根據自己的情況決定將多少個 RTCP 報文複合在一起組成一個複合包。

複合包中的每個獨立的 RTCP 報文都是無序的,而且可能會被隨意複合。為了讓協議的功能正常運作,會有如下限制:

  • 接收統計(SR|RR)的傳送頻率需要達到頻寬的最大限制,因此每個週期傳送的 RTCP 複合包都需要包含一個這類報文。
  • 一個新來的接收者需要儘可能快地得到資料來源的 CNAME,因為它要用 CNAME 來確定每個資料來源分別對應哪個人,並將資料來源聯絡在一起進行同步,所以每個 RTCP 複合包必須包含 SDES CNAME(除非這個複合包被拆成兩半一半加密,一般明文,這部分後面會介紹)。
  • 複合包中包型別的數量需要限制,這可以減少其他發錯的包或者不相關的包被識別成 RTCP 包的可能性,還能增加第一個字中固定位元的數量。

因此,一個複合包中至少需要含有 2 種型別的 RTCP 報文,它的格式如下:

  • Encryption prefix:當且僅當這個複合包需要加密的時,那複合包在頭部插入一個隨機的 32-bit 數。如果加密演算法需要填充資料的話,需要填充到複合包中的最後一個 RTCP 包後。
  • SS 或 RR:複合包中第一個 RTCP 包必須是一個報告報文,這可以加速報文頭部資料的校驗。即便沒有 RTP 資料的傳送和接收也要有一個報告報文,這種情況下必須傳送一個空的 RR 報文,並且即便是這個複合包中的其他 RTCP 報文是 BYE 也要這麼做。
  • Additional RRs:如果接收的 RTP 資料來自超過 31 個不同的源,前 31 個接收報告會寫進 SR 或者 RR 報文中,多出來的接收報告應該緊跟著預設的報告報文(SR 或 RR)。
  • SDES:SDES 包必須包含 CNAME,每個複合包必須包含一個 SDES 包。如果上層應用有需要,也可以加入一些別的 SDES 報文,這視頻寬限制而定。
  • BYE 或 APP:其他 RTCP 包型別(包括協議中還未定義的),可能以任意順序跟在 SDES 後面,但是希望 BYE 包寫在最後面(BYE 包需要和 SSRC/CSRC 一同傳送)。

一個單獨的 RTP 參與者應該在一個報告週期中只傳送一個複合 RTCP 包,該週期每個參與者應該視頻寬情況來估算,除非一個複合包被拆分加密。如果資料傳送者的數量太多,以至於除了增加 MTU 這個方法之外,沒辦法將所有 RR 報文塞進一個複合包時,那麼一次只會將部分 RR 資料塞進這個複合包,其他的資料就不傳送了。當然,為了讓所有源的接收情況都得到報告,會在多個週期內以環的形式迴圈共享所有源的接收情況。

為了減少資料包的開銷,一般建議 Translator 和 Mixer 無論何時都能將多個源的 RTCP 報文複合成一個複合包。下圖展示的就是一個 Mixer 生成的複合包的例子:

如果一個複合包的長度超過了下層網路協議的 MTU 的話,這個複合包會被拆分成多個更小的複合包分別傳送。這不會對 RTCP 的頻寬估計產生任何影響,因為即便 Mixer 的複合包被拆分成了多個更小的複合包,但是這個些更小的複合包也要滿足 "每個複合包都要包含 SS 或 RR" 這一條件,所以每個更小的複合包至少也對應了一個參與者,這樣 Mixer 生成的複合包就和它收到的 RTCP 包數量基本匹配,甚至更少。

如果某一客戶端收到了它無法解析的 RTCP 型別的包,那它應該忽略這個包。附加的 RTCP 包型別會通過 IANA 進行註冊。

RTCP 傳輸週期

RTP 的設計理念是它要能根據 Session 參與者的人數增加而進行自適應處理。例如,音訊會議中同一時刻說話的一般也就那麼一兩個人(這就從內部限制了音訊資料的傳輸),那麼可以認為組播資料分發所用到的頻寬資源和與會人數無關。控制資訊的傳送和音訊資料的傳輸不同,每個人都會不停的傳送 RTCP 報文,如果每個參與者的接收報告以同一個週期傳送的話,RTCP 報文傳輸所消耗的資源會隨著與會人數的增加而線性增加。因此,當與會人數增加時,RTCP 報文的傳送間隔應該相應的動態地增大。

對每個 Session 來說,會有一個總的頻寬限制(Session bandwidth),它會被分配給每個獨立的與會者。整個網路的頻寬可能會有所保留,並從網路層面強制限制 Session 的頻寬。如果網路的頻寬沒有保留的話,也可能會有一些別的限制,不過這些都跟網路環境有關,總之最後會得出一個靠譜的 Session 最大頻寬。Session 頻寬可能會通過實際會消耗的網路資源進行評估,或者中途根據 Session 的剩餘可用頻寬來變化。

這些都和媒體資料的編碼無關,但是會根據頻寬的限制來選擇具體使用哪種編碼。通常來說,會預估 Session 中有多少參與者會同時傳送資料,然後根據同時傳送這類資料大概需要多少頻寬這種方式來評估 Session 的頻寬。在音訊會議中,通常來說就是一個音訊傳送者所需要的頻寬(一般同一時間只會有一個人說話)。對於分層編碼這種情況,每一層都在一個獨立的 RTP Session 中,這些 Session 都有自己獨立的頻寬限制。

在 RTP Session 中應該有一個管理應用來調整 Session 頻寬,但是那些音訊會議應用可能會基於 Session 中選用的編碼格式,假設只有一個傳送者傳送資料,給自己設定一個預設的頻寬限制。這個音訊會議應用可能也會受到組播網路(或其他因素)的頻寬限制。同一個 Session 的所有參與者必須使用統一的 Session 頻寬限制,因為只有這樣大家才是以一個相同的頻率傳送 RTCP 包。

Session 頻寬評估過程需要考慮到下層的傳輸層和網路層是否有一些資源保留機制。而且上層應用也需要知道 RTP 下層使用了什麼協議,但是不需要知道資料鏈路層及以下的協議,因為從資料鏈路層開始資料包的頭就各不相同了。

控制報文的傳輸應該只使用 Session 頻寬中很小的一部分,這樣媒體資料的傳輸才會不受影響。建議 RTCP 傳輸使用 Session 頻寬的 5%,媒體資料傳送者至少要佔用 1/4 的 RTCP 頻寬,因為這樣做的話,新加進來的人可以更快的收到媒體資料傳送者的 CNAME。在某些預設中,如果傳送者的數量超過 1/4 可能會完全關閉接收報告,雖然 RTP 協議標準並不推薦這樣做,但是那些只有單向鏈路的系統或者不需要接收者反饋的系統一般是這麼做的。

RTCP 報文的傳輸間隔一般都會稍微長一點,這樣,當參與者的數量陡增時,報文的數量就不會超過頻寬限制太多。當一個應用啟動時,它應該等一段時間(一般是最小 RTCP 報文間隔的一半)再傳送第一個 RTCP 報文,這樣這可讓傳送間隔的計算更快的收斂。推薦 RTCP 報文傳送的最小間隔是 5 秒。

RTP 的上層應用可能會使用更短的 RTCP 傳送間隔,但是也會遵循如下原則:

  • 對於組播形式的 Session,只有資料傳送者會使用更短的 RTCP 傳送間隔。
  • 對於單播形式的 Session,無論是傳送者還是接收者都有可能使用更短的 RTCP 間隔,並且它們傳送初始 RTCP 前可能不會等待一段時間。
  • 所有的 Session 都應該根據最小 RTCP 傳送間隔來確定參與者的超時時間。
  • 推薦的最小 RTCP 傳送間隔時間使用 "360 kb/Session 頻寬(kb/s)" 這種方式計算。這樣當 Session 頻寬大於 72kb/s 時,RTCP 傳送間隔會小於 5 秒。

此外,為了讓 RTCP 能在大型 Session 中正常執行,現有的演算法還具有如下特點:

  • RTCP 報文傳送間隔隨著 Session 參與者的人數增加而線性地降低。
  • RTCP 發包間隔通常會隨機縮放 0.5~1.5 倍,這樣做是為了避免大量的參與者同時傳送 RTCP 報文。
  • RTCP 複合包中包含的控制報文資料會根據收發包情況動態變化。
  • 因為 RTCP 報文間隔是根據已知的 Session 參與者情況計算的,所以當有新的人要加入到 Session 時,可能會錯估整個 Session 的規模,而是用了較短的 RTCP 間隔,尤其是當大批量的人一齊加入 Session 時這種現象更加明顯。所以,可能會有一個 "傳送時機重整" 演算法,它實現了一個簡單的撤回機制,可以在 Session 規模持續增長時,適當的撤回一些 RTCP 報文。
  • 當有人通過傳送 BYE 報文或者因為超時退出 Session 時,RTCP 的傳送間隔應該縮短。
  • BYE 報文和其他 RTCP 報文相比,有一些特殊的地方。當有人想要退出,併傳送 BYE 報文時,它可以在下一個傳送週期到來之前就傳送。當然,如果一大批人同時退出時,也會受到前面提到的 RTCP 報文撤回機制的影響。

維護 Session 成員的數量

我們已經知道了,計算 RTCP 傳送間隔是需要清楚整個 Session 中成員數量的,當一個新的節點被監聽到時,它就會被加入到 Session 總數中,並且大家要把它加入到一個 SSRC(CSRC)身份識別表中然後持續追蹤。大家只有收到這個新節點的多個資料包,或者收到他的 SDES 包(CNAME)時才覺得這個新節點是靠譜的。當某個節點發了一個 BYE 之後,它的資訊可能就會被大家刪了,但是考慮到可能有丟包或者網路擁堵的情況,所以大家會先把它標記為 "收到 BYE",然後等一段時間,如果還沒收到它的別的報文,這時候才會把它刪了。

如果一個節點超過一個 RTCP 週期都沒收到另一個節點的報文,它可能就會將其標記為不活躍,或者刪了它,這就需要丟包的情況儘可能別發生。但是不丟包是不可能的,所以大家一般會將 RTCP 傳輸間隔乘以一個係數(大於 1 的數)作為超時時間。

對於那些參與者很超級多的 Session,可能沒法去維護一個 SSRC 表來儲存所有參與者的資訊。通常大家都會簡化這個 SSRC 表,但是需要注意的是無論怎麼簡化這個表都不能低估了參與者的總數,可以允許高估參與者總數。

RTCP 報文的收發規則

首先,無論是組播還是多個節點的單播都必須遵循前面提到的 RTCP 間隔。為了正常完成 RTCP 報文的收發操作,Session 中的每個參與者都會維護如下資訊:

  • TP:最後 RTCP 報文的傳送時間;
  • TC:當前時間;
  • TN:下一個要傳送報文的時間點;
  • P-Members:計算上一個 TN 時參考的 Session 成員總數;
  • Members:當前的 Session 成員總是;
  • Senders:資料傳送者總數;
  • RTCP_BW:RTCP 的目標頻寬;
  • WE_Sent:從倒數第二個 RTCP 報文傳送後,到現在為止,是否傳送過資料;
  • AVG_RTCP_Size:平均 RTCP 複合包大小,包括傳輸層和網路層的頭;
  • initial:是否一個 RTCP 報文都沒發過。

計算 RTCP 傳送間隔

為了讓 RTP 協議具有可伸縮性,RTCP 的傳送間隔需要隨著 Session 總人數的變化而適當的縮放。結合上述的部分狀態,我們按如下方式計算 RTCP 報文間隔:

  1. 如果媒體流傳送者的數量小於總人數的 25% 時,這個間隔和當前節點是否是媒體流傳送者有關(通過 WE_Sent 判斷)。如果是媒體流傳送者,計算公式為 Senders * AVG_RTCP_Size / (25% * RTCP_BW),如果是媒體流的接收者,計算公式為:(Members - Senders)* AVG_RTCP_Size / (75% * RTCP_BW)。當媒體流傳送者的數量超過 25% 時,傳送者和接收者會被同等對待,即它們的 RTCP 週期公式為:Members * AVG_RTCP_Size / RTCP_BW。

  2. 如果某個參與者一個 RTCP 包都還沒傳送,最小傳送間隔間隔(Tmin)為 2.5 秒,否則為 5 秒。

  3. 決定的傳送間隔(Td)會是第一步計算的值和 Tmin 中較大的那個。

  4. 發包時會在 Td 的基礎上隨機縮放 0.5~1.5 倍。

  5. 最終這個間隔還要除以 e-3/2=1.21828,這是為了彌補因為 "傳送時機重整" 演算法帶來的影響(因為這個演算法會導致最終 RTCP 使用的實際頻寬比預計使用的頻寬低)。

初始化

當一個人剛加入到 Session 中時,tp=0,tc=0,senders=0,p-members=0,members=1,we_sent=false,rtcp_bw = 5% * Session 頻寬,initial=true,avg_rtcp_size 被設定為之後會傳送的首個 RTCP 包的大小,然後計算髮送間隔 T 時,會根據上述初始狀態進行計算,並以此作為參考傳送第一個包,最後將自己的 SSRC 加入到成員列表中。

接收 RTP 和 Non-BYE RTCP 包

當 RTP 或者 RTCP 包被另一個人(A)接收到了,如果對 A 來說這個包的 SSRC 他沒見過,那麼他就會將其加入到 SSRC 表中,並更新 Session 總人數(Members)。對每個 CSRC 也會做同樣的操作。

如果收到了一個 RTP 報文,並且其對應的 SSRC 沒在傳送者 SSRC 表中,那他就會把它加進傳送者 SSRC 表中,並更新傳送者的總數(Senders)。

當每個複合 RTCP 報文被接收到時,平均 RTCP 報文大小(AVG_RTCP_Size)的狀態就會更新,更新公式為:AVG_RTCP_Size = (1 / 16) * last_rtcp_package_size + (15 / 16) * previous_avg_rtcp_size。

接收 RTCP BYE 報文

如果接收到了 RTCP BYE 報文,會在成員列表中確認一下,如果有對應的 SSRC 項,就會把它移除並更新成員總數(Members)。同時也會在傳送者 SSRC 表中做類似的操作,如果找到了就刪除它並更新傳送者總數(Senders)。

此外,為了讓 RTCP 的傳輸率跟隨 Session 中人數的變化而動態變化,如下演算法會在收到 BYE 報文時執行:

  1. TN 按照如下公式更新:TN = TC + (Members / P-Members) * (TN - TC) 。

  2. TP 按照如下公式更新:TP = TC - (Members / P-Members) * (TC - TP) 。

  3. 下一個 RTCP 報文按照新的 TN 指示傳送(比原來發的更早了)。

  4. 將 P-Members 設定成 Members 的值。

這個演算法沒有考慮到一個意外情況,那就是當一大波人(並不是所有人)同時退出 Session 時,會導致 RTCP 的週期降到一個非常小的值,這樣可能出現錯誤的 Timeout 判斷,最終它會導致整個 Session 的總人數降到 0。但是,這種情況一般來說很少發生,所以大家都覺得問題不是很大。

SSRC 的超時

我們需要偶爾確認一下是不是太久沒收到某個與會者的報文了,一般來說每個 RTCP 週期內都必須確認。如果發現了超時,就需要將這個 SSRC 從成員列表(Members & Senders)中移除,並更新當前人數。

Member 表:一般超過 5 個傳送週期(不考慮隨機縮放因素)未收到某人的訊息,會被確定為超時。

Sender 表:一般是 2 個傳送週期。

如果某個成員被確定為超時,上一步介紹的演算法就操作起來了。

傳送倒數計時

我們已經知道,每個 RTCP 都是週期性的傳送的,當傳送完一個 RTCP 報文時,就會根據 TN 新建一個倒數計時,每次當倒數計時歸零時就會重複如下操作:

  1. 計算傳輸週期 T,引入隨機縮放因素。

  2. 如果 TP + T <= TC,立即傳送一個 RTCP 報文,並將 TP 設定為 TC,TN 設定為 TC + T,下一個倒數計時會在 TN 時刻歸零。如果 TP + T> TC,就不傳送了 RTCP 報文,計算 TN = TC + T 後,然後重設一個定時器在 TN 歸零。

  3. P-Members 設定為 Members。

如果傳送了 RTCP 報文,initial 會被設定為 FALSE,AVG_RTCP_Size 會按如下方式更新:
AVG_RTCP_Size = (1 / 16) * last_rtcp_package_size + (15 / 16) * previous_avg_rtcp_size。

傳送 BYE 報文

當某個人想要退出 Session 時,他就會發一個 BYE 報文給其他人。為了防止一大幫人同時退出 Session 時出現 BYE 報文井噴的情況,所以當 Session 人數超過 50 時,會按下述方式操作:

  1. 當一個參與者想要離開時,TP 會設定成 TC,Members 和 P-Members 會設定成 1,initial 設定成 1,we_send 設定成 false,senders 設定成 0,avg_rtcp_size 設定成複合 BYE 報文的大小。然後計算 RTCP 傳送間隔 T,下個 BYE 報文會在 TN = TC + T 後傳送。

  2. 每當這個要離開的人收到了別人的 BYE 報文時,Members 就會增加 1,無論這個人是否在成員列表中。Members 的數量只有收到 BYE 報文時才增加,其他報文都不管。同樣,avg_rtcp_size 也只管收到的 BYE 報文的大小。Senders 數量也不變。

  3. 對了 BYE 報文來說,除了狀態值的維護套路變了,傳送邏輯和前面提到的都一樣。通過上述方案,即可以讓 BYE 報文正確地傳送,還能控制整體頻寬。最差的情況下,也只會導致 RTCP 報文傳輸佔用 10% 的 Session 總頻寬。

有些參與者可能不想按照上述的方式傳送 BYE 報文,他們可能什麼也不發就離開了。這類情況會被 Timeout 機制 hold 住。

如果一個參與者要離開時,Session 的總人數小於 50,他可能會直接傳送一個 BYE 報文,也可能按照上述方案來進行。

此外還有一個無論如何都要遵循的規則,如果一個參與者一個 RTP 報文或者 RTCP 報文都沒傳送過的話,那他離開 Session 時絕對不能傳送 BYE 報文。

更新 WE_Sent

當某個參與者最近傳送過一個 RTP 後,他就會將 WE_Sent 置為 true 並將自己加入到 Senders 表中,否則如果超過兩個 RTCP 傳送週期的時間內都沒傳送過 RTP 報文,那他就會將自己從 Sender 表中移除,並將 WE_Sent 置為 false。

SDES 類報文的頻寬分配

SDES 報文中除了必須要有的 CNAME 之外,還有一些別的資訊,比如 NAME(個人名稱),EMAIL(email 地址)等。上層應用也可以自定義的一些報文型別,但是要小心別付加了太多的自定義資訊以至於拖慢了整個 RTCP 協議的運轉。建議這些附加內容的頻寬佔用不要超過整個 RTCP 協議頻寬的 20%。此外,也不要覺得每個上層應用都會包含所有的 SDES 內容。上層應用要根據實際使用的情況給這些內容分配一定的頻寬,一般來說他們會通過控制傳送間隔來控制這部分的頻寬。

比如,一個應用的 SDES 可能只包含 CNAME,NAME,和 EMAIL,其中 NAME 可能就會比 EMAIL 分配更多的頻寬。因為 NAME 會一直顯示出來,而 EMAIL 可能只在點選檢視的時候才顯示。在每個 RTCP 傳送週期裡,SDES 中都會包含 CNAME。如果假設 RTCP 週期是 5 秒的話,可能每 15 秒 SDES 才會附帶一個除 CNAME 以外的資訊,以 2 分鐘為例,其中 7 次附帶的是 NAME 資訊,1 次附帶的是 EMAIL 資訊。

「視訊雲技術」你最值得關注的音視訊技術公眾號,每週推送來自阿里雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。

相關文章