作者:逸殊
稽核:泰一
介紹
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 資訊。
Sender & Receiver 報告
RTP 使用 Sender 報告(SR)和 Receiver 報告(RR)來回報資料的接收品質,如果是媒體資料的發送者那就會發送 SR,否則發送 RR。這兩類封包是通過頭部的封包類型識别碼來做區分的。SR 相對于 RR 來說多了 20byte 的 Sender 相關資訊,除此之外其他内容都是一樣的。
SR 封包
SR 封包包含三個部分,第一個部分是頭部,有 8 BYTE,各個字段的含義如下:
- version (V): 2 bits,RTP 協定版本。
- padding (P): 1 bit,是否包含填充,最後一個填充位元組辨別了總共需要忽略多少個填充位元組(包括自己)。Padding 可能會被一些加密算法使用,因為有些加密算法需要定長的資料塊。在複合包中,隻有最後一個 RTCP 包需要添加填充。
- reception report count (RC): 5 bits,有多少個接收報告。可以為 0。
- packet type (PT): 8 bits,200 表示 SR 封包。
- length: 16 bits,封包長度(按 32-bit 字統計),包含頭部和填充位元組。
- SSRC: 32 bits,身份定位符。
第二部分是發送者資訊,包含 20 BYTE 的資料,總結了這個發送的的傳輸統計,各個字段的含義如下:
- NTP timestamp: 64 bits,Wallclock time,用于計算 RTT。
- RTP timestamp: 32 bits,RTP 時間戳,基于 NTP 的某一随機偏移量。用于媒體資料内同步。
- sender's packet count: 32 bits,這個 SSRC 總共發送了多少包。
- sender's octet count: 32 bits,這個 SSRC 總共發送了多少 BYTE 的資料。
第三部分可能什麼都沒有,也可能有多個接收報告,這取決的上次報告以後收到了多少個 Sender 的資料。每個報告塊統計了一個 SSRC 的包數。具體内容如下:
- SSRC_n (source identifier): 32 bits,這個資訊塊對應的 SSRC。
- fraction lost: 8 bits,上次 SR 或 RR 發送後到目前為止的丢包率。
- cumulative number of packets lost: 24 bits,整體過程的丢包總數。
- extended highest sequence number received: 32 bits,低 16-bit 是收到的最新的 RTP 封包序列号,高 16-bit 是序列号循環的次數。
- interarrival jitter: 32 bits,RTP 資料封包抵達時間的抖動。如果 Si 代表 i 包中包含的 RTP 時間戳,Ri 代表 i 包被接收時的 RTP 時間戳,那兩個包 i 和 j 的到達時間抖動算法如下::
。我們在計算這個抖動時,要結合每個包的抖動,來計算一個平均值,計算平均值的方案如下:D(i,j) = (Rj - Ri) - (Sj - Si) = (Rj - Sj) - (Ri - Si)
J(i) = J(i-1) * (15 / 16) + (|D(i-1,i)|)/16
- last SR timestamp (LSR): 32 bits,該 SSRC 最後一個 RTCP 封包(SR)中帶的 NTP 時間。
- delay since last SR (DLSR): 32 bits,從該 SSSR 最後一個 RTCP 封包(SR)被收到以來經過的時間。
資料的發送者可以通過目前時間 A,接收到 RR 部分中的 LSR 和 DLSR 來計算 RTT,計算示意圖如下:
RR 封包
接收報告的格式和發送封包格式一樣,隻不過它在頭部中用 201 表示這是一個 RR 封包。此外 RR 封包中不含有上述 SR 封包中的第二部分。如果 RR 封包是空的那麼需要在頭部标明 RC=0。
發送 / 接收封包的拓展
一些預設可能根據自己的需求,要在接收報告和發送報告中附加一些資訊。那麼這些附加内容應該在 SR 或者 RR 的結尾之後。如果這些内容隻有發送者相關,那麼 RR 中就不包含這些資訊。
分析發送報告和接收報告
這些接收品質的報告資訊可能不光隻有發送者要使用,接收者或者第三方監控器也會使用。發送者可能根據接收品質調整自己的傳輸政策。接收者可以根據這個資訊來确定自己遇到的問題是本地網絡的問題還是整個 Session 的問題。網絡的管理者可以根據這些資訊來評估整個網絡環境的情況。
SDES 封包
SDES 是一個三級結構,它包含一個頭和 0 個或多個資料塊,每一個資料塊對應了一個 SSRC 或 CSRC,它又由多個描述字段組成。頭部的資訊如下:
- version (V),padding (P),length: 和上面一樣。
- packet type (PT): 8 bits,202 表示 SDES 類型。
- source count (SC): 5 bits,SSRC/CSRC 塊的數量。
每一個塊中都包含多個描述内容,這些描述内容都是 32-bit 對齊的,其中前 8-bit 描述了類型,接着 8-bit 描述了資訊長度(不包含前 16-bit),然後資訊内容。注意資訊部分不能超過 255 BYTE,這和前面的很多工作類似是為了限制 RTCP 的帶寬。
描述的文本内容是 UTF-8 編碼的。如果要使用多位元組的編碼,需要在醒目的地方表示用的什麼的編碼。各個描述部分是沒有中間分隔的,是以要用空位元組來填充以達到對齊的效果。注意這裡的填充和 RTCP 頭部的 P 不是一個概念。末端節點發送的 SDES 包含他自己的資料源辨別。而 Mixer 發送的 SDES 包含多個 CSRC,如果 CSRC 的數量超過了 31 個,會拆分成多個 SDES 封包。SDES 的所有類型會在後面一一介紹。其中隻有 CNAME 是強制要有的。可能有一些類型的的描述隻有部分預設才會使用。但是這些内容都是在一個共通的地方來記載,以防止不同的預設使用的描述類型發生沖突。如果要注冊新的類型,需要通過 IANA 注冊。
CNAME:權威的末端節點身份辨別
CNAME 有如下特征:
- 因為 SSRC 在許多意外情況下會重新生成,是以 CNAME 被用來綁定舊的 SSRC 和新的 SSRC,來保持資料源的連續。
- 和 SSRC 一樣,CNAME 也需要保證唯一性(同一個 Session 中)。
- 為了讓同一個參與者的多個 SSRC 綁定在一起,我們需要 CNAME 是固定的。
- 為了讓第三方監控用起來友善,CNAME 應該即友善程式使用,也要設計成可讀的,可以根據它确認來源。
是以 CNAME 應該通過算法來生成而不是手動生成。為了滿足如上需要,一般來說是按照如下的格式來描述 CNAME:
- "user@host" eg: "[email protected]" or "doe@2201:056D::112E:144A:1E24".
- "host", 如果是單使用者系統,擷取不到 user 時隻使用 host。eg: "sleepy.example.com","192.0.2.89" or "2201:056D::112E:144A:1E24".
有些人可能會發現,如果上述的 host 使用的是子網位址的話,就沒辦法保證整個 Session 的唯一性了,通常這類沒有直接 IP 的使用者是通過一個 RTP 級别的 Translator 來通路公共網絡。這個 Translator 會處理從私有位址到公網位址的轉換工作。
NAME:使用者名
這個是描述資料源的真實名字,eg:"John Doe, Bit Recycler"。整個 Session 過程中希望這個值不變。全 Session 不需要唯一。
EMAIL:電子郵箱位址
電子郵箱位址,eg: "[email protected]"。整個 Session 過程中希望這個值不變。
PHONE:電話号碼
電話号碼需要以國際通路碼開頭,eg: "+1 908 555 1212"。
LOC:使用者地理位址
視應用不同,詳細程度會各不相同。
TOOL:應用名或工具名
帶版本号的應用名,可以用來 DEBUG。
NOTE:提醒 / 狀态
用來發送暫時性的消息描述目前狀态。eg: "on the phone, can't talk"。
PRIV:自定義拓展
上層應用自定義的格式。一般都是用過一個字首描述消息類型,然後後面跟着消息正文。
BYE 封包
BYE 封包表示一個或多個流媒體源不再活躍。
- version (V),padding (P),length: 同上。
- packet type (PT): 8 bits,203 表示 BYE 封包。
- source count (SC): 5 bits,退出 Session 的 SSRC 的數量。
如果 BYE 封包被 Mixer 收到了,Mixer 應該啥都不改動,就發給下一節點。如果 Mixer 關閉了,它要發送一個包含它管理的所有 SSRC 的 BYE 封包。BYE 封包中可能也會跟着帶一些離開原因的描述。這些描述和 SDES 中帶的描述類似,需要 32-bit,用空位元組填補空缺。
APP:應用定義的 RTCP 封包
APP 封包一般用于實驗性的功能和開發。如果識别到了不認識 NAME 那麼上層應用一般都會忽略它。如果開發或者測試功能穩定了,一般是要通過 IANA 注冊一個新的 RTCP 封包類型。
- subtype: 5 bits,APP 封包子類型,一般是上層應用定義。
- packet type (PT): 8 bits,204 表示 APP 類型的 RTCP 封包。
- name: 4 octets 一般是應用名,防止 subtype 沖突。
- application-dependent data: variable length 和上層應用相關的内容,需要 32-bit 對齊。
RTP Translator & Mixer
作為末端節點的補充,RTP 引入了 Translator 和 Mixer 的概念,它們是 RTP 層的中間件。雖然這多少增加了協定的複雜度,但是對音視訊通話應用來說它們還是很關鍵的,因為它們能解決防火牆問題和低帶寬連接配接的問題。
描述
一個 RTP Translator/Mixer 連接配接至少兩個傳輸層的使用者組。通常來說,這裡提到的使用者組是公共網絡的概念,傳輸層協定會為其生成一個多點傳播位址(ip:port)。網絡層協定,像是 IPv4 和 IPv6 對 RTP 協定來說是隐藏的。一個系統可能會有多個 Translator 和 Mixer(多個 Session),它們中的每一個都可以看作是一個使用者組的邏輯分割。為了避免建立在建立 Translator 和 Mixer 造成了網絡包循環,必須遵循下列規則:
- 每個通過連接配接 Translator 和 Mixer 而加入 Session 的使用者組,要麼需要網絡層隔離,要麼最少互相知道這些參數(protocol,address,port)中的一個。
- 由上一個規則推廣的話,各個使用者組絕對不能同時連接配接多個 Translator 或者 Mixer,除非有某種機制能保證他們之間資料被阻斷。
Translator:在不改變 RTP 封包 SSRC 的條件下,向後傳播該封包,正因為如此,封包的接收者才能識别到 Translator 轉發後的封包到底是來自哪個人。有些 Translator 可能直接轉發封包,不做任何改動,也有可能改變資料編碼,payload 類型和時間戳。
如果多個資料封包被重新編碼并合并到一起的話,Translator 必須為這類封包指定一個組新的序列号。這樣,輸入封包的丢失就會導緻輸出封包的斷層。資料的接收者一般是不知道 Translator 的存在的,除非通過 payload 類型的不同或者傳輸層封包的源位址來判斷。
Mixer:從一個或多個資料源那裡接收資料,随後可能會改變資料的格式,然後将這些資料合并,并傳遞給下家。因為多個資料源的時序并不一定是同步的,是以 Mixer 需要整合各個資料源的時序關系,并将其映射到自己的一套時序上,是以 Mixer 也是一個 SSRC,所有通過 Mixer 的封包必須打上該 Mixer 的 SSRC。
為了表示這些資料的原始資料源,一般會通過 CSRC 清單來記錄。有些 Mixer 可能自己也是一個原始資料源,是以他自己的 SSRC 也會出現在 CSRC 清單中。有些應用可能不希望 Mixer 的 SSRC 出現在 CSRC 中,但是這樣可能就無法發現循環網絡包。
上圖是一個 Mixers 和 Translators 連接配接的例子。[] 代表末端節點,() 代表 Mixer,<> 代表 Translator,"M1:48 (1, 17)" 表示 Mixer1 的封包,48 是 Mixer1 的 SSRC,括号裡的 1,17 是 CSRC,它合并了 E1:17 和 E2:1 這兩個節點的資料。
Translator 處理 RTCP
除了要轉發資料包,進行資料包的更改,Translator 和 Mixer 也要發送 RTCP 封包。在很多情況下,它會将收到的末端節點的 RTCP 封包合并到複合包中。當再次收到這些包時或者自己的 RTCP 周期到時,它會将複合包發送出去。
有的 Translator 可能對收到的 RTCP 封包不做任何改動,隻是簡單的轉發這個包。如果這個 Translator 改變了封包資料的 payload,它必須對 SR 或者 RR 做相關的改動。通常來說,Translator 不能将多個資料源的 SR 和 RR 合并,因為這樣會導緻 RTT 的計算出現問題(RTT 根據 LSR 和 DLSR 計算)。
- SR 中的發送者資訊: Translator 不會建立自己的發送者資訊,它會将收到 SR 傳給下家。其中 SSRC 不會發生任何改動,但是發送者資訊有必要的話一定要做适當的改動。如果 Translator 改變了資料編碼,那 "byte count" 字段就要更改。如果他将多個資料封包合并,那它需要修改 "sender's packet count" 字段。如果它改變了時間頻率,那就需要修改 "RTP timestamp"。
- SR/RR 中的接收者資訊:SSRC 不會發生任何改動,如果 Translator 改變了序列号,那就需要修改 "extended last sequence number",在某些極端情況下,它可能完全沒有接收回報,或者根據接收到的 SR/RR 來建構自己的接收報告。一般情況下 Translator 是不需要自己的 SSRC 的,但是如果是為了表示自己的資料接收情況,它可能也會生成自己的 SSRC,并将這些 RTCP 封包發送過所有的連接配接者。
- SDES:一般 Translator 收到 SDES 後會什麼都不改就發給下家,但是也有可能為了節約帶寬篩掉 CNAME 之外的資訊的,如果 Translator 要發送自己的 RR 資訊,那它一定要發送一個自己的 SDES 給所有連接配接者。
- BYE:無改動轉發,如果 Translator 有自己的 SSRC 也要發送自己的 BYE。
- APP:無改動轉發。
Mixer 處理 RTCP
因為 Mixer 會生成自己的資料流,是以他不會轉發經過他的 SR 和 RR 而是為連接配接雙方發送自己的 SR 和 RR 封包。
- SR 的發送者資訊:Mixer 不轉發資料來源的發送資訊。它會生成自己的發送者資訊并把它發送給下家。
- SR/RR 中的接收者資訊:Mixer 會生成自己的接收資訊,然後發送給所有資料來源,它絕對不能做接收報告的轉發工作,或者把自己的接收資訊發給錯誤的對象。
- SDES:Mixers 通常會不做任何改動就轉發 SDES 資訊,但是也有可能為了節約帶寬過濾除了 CNAME 之外的其他資訊。Mixer 必須發送自己的 SDES 封包。通常,Mixer 會将多個收到的 SDES 打包一起發送。
- BYE:Mixer 必須轉發 BYE 封包。如果 Mixer 要退出時,它會将所有資料來源的 SSRC 放進 BYE 封包,也包括自己的 SSRC。
- APP:視上層應用。
瀑布型 Mixer
一個 RTP Session 可能包含多個 Mixer 和 Translator,就像上圖一樣。如果 Mixer 是瀑布型的,就像 M2 和 M3,一個 Mixer 收到的資料可能是已經合并過的,它有自己的 CSRC 清單。那麼第二個 Mixer 需要将之前的 CSRC 和自己接收的所有 SSRC 合并。就像圖中 M3 的輸出是 M3:89 (64,45)。
SSRC 的配置設定和使用
前面已經說過 SSRC 是一個随機的 32-bit 數,它需要在整個 Session 内保證唯一性。是以同一個網絡下的參與者在剛加入 Session 時使用不同的 SSRC 至關重要。
我們不能簡單的用本地的網絡位址,因為可能不唯一。也不能不考慮初始狀态而簡單地調一個随機數函數。
碰撞的可能性
因為 SSRC 是随機選擇的,這就可能多個資料源選用了相同的 SSRC。如果大家是同時加入 Session 的話,這個碰撞的幾率就更高。如果 SSRC 的數量是 N,L 是 SSRC 的資料長度(這裡是 32),那麼碰撞的可能性是
1 - exp(-N2 / 2(L+1))
,當 N=1000 時,碰撞率大概是 10**-4。通常來說,實際的碰撞率會比上述的最壞情況要低。通常一個新節點加入時,其他節點已經有了自己的唯一 SSRC,這時候碰撞的機率隻是生成的新 SSRC 在這些現有 SSRC 之中的可能性。這時候碰撞率是 是
N/2**L
。當 N=1000 時,碰撞率大約是 2*10**-7。因為新加入的節點會先接收一段時間的封包然後才發送自己的第一個封包,是以在它生成 SSRC 時可以避開已知的 SSRC,這也有效的降低了碰撞的幾率。
碰撞的解決方案和循環的發現
通常來說 SSRC 碰撞的可能性很小,所有的 RTP 實作必須有發現沖突的機制,并在發現沖突時作出适當的處理。如果資料源發現了任何一個别的資料源和自己使用同一個 SSRC,它必須用原來的 SSRC 發送一個 BYE 封包,然後選用一個新的 SSRC。如果一個資料的接收者發現了多個資料源的 SSRC 碰撞了(通過傳輸位址或者 CNAME),那麼它會隻接收其中一個人的封包,丢棄另一個人的所有封包。因為整個 Session 中的 SSRC 是唯一的,是以它也可以被用來發現環型封包。環形封包會導緻資料的重複以及控制資訊的重複。
- Translator 可能會錯誤地将封包發送回該封包來的地方。
- 兩個 Translator 錯誤地同時啟動,它們兩個都會轉發同樣的資料。
- Mixer 可能會錯誤地将合并封包發送回這些封包來的地方。
一個資料源可能發現自己的或者别人的封包被循環發送了。無論是封包循環還是 SSRC 的碰撞都會導緻同一個現象,即 SSRC 相同但是傳輸位址不同的封包。是以,如果資料源改變了自己的傳輸位址,那它就需要同時改變自己的 SSRC 來避免被檢測成環形封包。有一個需要注意的内容是,如果一個 Translator 再重新開機的過程中改變了自己的傳輸位址,那麼這個 Translator 轉發的所有資料都會被檢測成環。這類情況的解決方案一般有如下兩個:
- 重新開機的時候不改變傳輸位址。
- 接收者的逾時機制。
如果循環或者碰撞發生在離 Translator 和 Mixer 很遠的地方,我們就不能通過傳輸位址來發現。但是我們仍然可以通過 CNAME 的不同來發現 SSRC 碰撞。
為了解決上述問題,RTP 的實作必須包含一個類似如下的算法。這個算法不包括多個資料源 SSRC 碰撞的情況,這類情況通常下都是先用原來的 SSRC 發送一個 BYE 然後重新選擇一個新的 SSRC。
這個算法需要維護一個 SSRC 和傳輸位址的映射關系。因為 RTP 的資料和 RTCP 傳輸使用的是兩個不同的端口,是以一個 SSRC 對應的是兩個傳輸位址。
每次收到 RTP 封包和 RTCP 封包都會将其 SSRC 和 CSRC 在上述的表中進行比對。如果發現了傳輸位址對不上的情況,我們就可以說發現了一個循環或者碰撞。對于 RTCP 資料來說,可能每個資料塊都有自己獨立的 SSRC,比如 SDES 資料,對于這種情況就需要分别比對。如果沒有在表中找到這個 SSRC 或者 CSRC,就需要新添加一項。當收到 BYE 封包時,需要先比對這個 BYE 的傳輸位址,如果傳輸位址比對上了,就将這一項從表中删除。或者基于逾時機制,将逾時的資料從表中移除。
為了追蹤自己的資料封包循環情況,必須維護另一個清單,這個表存儲沖突封包的傳輸位址和收到該封包的時間。如果超過 10 個 RTCP 周期都沒有收到這個傳輸位址的沖突封包,就将該項從表中删除。
下面的算法還假設參與者自己的 SSRC 和狀态都包含在 SSRC 表中,它會先比對自己的 SSRC。
if (SSRC or CSRC identifier is not found in the source
identifier table) {
create a new entry storing the data or control source
transport address, the SSRC or CSRC and other state;
}
/* Identifier is found in the table */
else if (table entry was created on receipt of a control packet
and this is the first data packet or vice versa) {
store the source transport address from this packet;
}
else if (source transport address from the packet does not match
the one saved in the table entry for this identifier) {
/* An identifier collision or a loop is indicated */
if (source identifier is not the participant's own) {
/* OPTIONAL error counter step */
if (source identifier is from an RTCP SDES chunk
containing a CNAME item that differs from the CNAME
in the table entry) {
count a third-party collision;
} else {
count a third-party loop;
}
abort processing of data packet or control element;
/* MAY choose a different policy to keep new source */
}
/* A collision or loop of the participant's own packets */
else if (source transport address is found in the list of
conflicting data or control source transport
addresses) {
/* OPTIONAL error counter step */
if (source identifier is not from an RTCP SDES chunk
containing a CNAME item or CNAME is the
participant's own) {
count occurrence of own traffic looped;
}
mark current time in conflicting address list entry;
abort processing of data packet or control element;
}
/* New collision, change SSRC identifier */
else {
log occurrence of a collision;
create a new entry in the conflicting data or control
source transport address list and mark current time;
send an RTCP BYE packet with the old SSRC identifier;
choose a new SSRC identifier;
create a new entry in the source identifier table with
the old SSRC plus the source transport address from
the data or control packet being processed;
}
}
層級編碼
對于不同 Session 的層級編碼傳輸,一般都是所有層都使用同一個 SSRC,如果其中某一層發現了 SSRC 沖突,那麼隻改變這一層的 SSRC,而且他層的 SSRC 不做改變。
安全
下層協定可能會提供 RTP 應用所需要的所有安全服務,包括認證,資料完整性,資料保密性。這些服務在 IP 協定中都有解決方案。因為 Audio 和 Video 初始化過程中需要資料加密,而這時候 IP 協定這一層的安全服務還沒有提供。是以,RTP 需要實作一個 RTP 專用的保密服務。這個保密服務是非常輕量級的,而且保密部分的服務向後相容,以後可以随時進行更換。或者,某些預設會提供這部分加密服務,比如 SRTP(Secure Real-time Transport Protocol),SRTP 是基于 Advanced Encryption Standard (AES) 提供了一個比 RTP 預設加密服務更強大的實作。
保密性
保密性是指我們的封包隻希望一些特定的接收者可以解碼成明文,而其他人隻能得到無用的資訊,保密性是通過加密編碼來提供的。
當需要為 RTP 和 RTCP 封包提供加密服務時,所有傳輸的内容都會在下層封包那裡進行加密。對于 RTCP 來說,需要一個 32-bit 的随機數作為字首。而 RTP 封包不需要字首,取而代之的是随機序列号和時間戳偏移。因為随機部分很少,是以可以說這是一個非常弱的
初始向量。此外,SSRC 也可被破解者修改,這是這個加密方案的另一個薄弱的環節。
對于 RTCP 來說,可能會将一個複合包分成兩批,第一批加密,後一批明文發送。例如,SDES 部分的資訊可能加密,而接收報告部分不加密就發送出去,因為隻有這樣那些第三方監控器才能在不知道密鑰的情況下統計網絡狀況。如下圖所示,SDES 資訊必須跟在一個空的 RR 後,并且要有一個随機字首。
RTP 協定使用的 Data Encryption Standard (DES) 算法,使用 cipher block chaining (CBC) 模式,這需要資料填充到 64-bit 對齊。密碼算法使用零作為初始向量,因為 RTCP 封包中已經有一個随機字首了。
RTP 之是以選擇這個預設協定是因為它用起來很容易,但是因為 DES 太容易破解了。是以推薦預設中使用更健壯的加密算法來替換這個預設方案,例如 Triple-DES。這些算法普遍需要一個随機初始化塊,RTCP 使用了 32-bit 的随機數作為字首,RTP 使用了時間戳和序列号的随機偏移,可是相鄰的 RTP 封包之間的随機性就很差。需要注意的是,無論是 RTCP 還是 RTP,它們的随機性都有限。加密型更好的應用,需要考慮更多的保密措施。例如 SRTP 配置檔案,就基于 AES 來加密,它的加密方案就更完備,選擇這個預設來使用 RTP 就挺不錯的。
前面提到過也可以用 IP 級的加密方案或者 RTP 級的加密,一些預設可能會定義别的 payload 類型來加密。這種方案,可能隻加密 payload 部分而頭部分使用明文,因為隻有 payload 部分才是應用真正需要的内容。這可能對硬體裝置來說非常有用,它既處了解密過程,又處了解碼過程。
身份認證和消息完整性
RTP 協定這一層沒有身份認證和消息完整性服務,因為有些上層服務可能沒有認證就能使用。而消息完整性服務依賴下層協定來實作。
RTP 下的網絡層和傳輸層協定
RTP 需要下層協定提供多路複用機制。對于 UDP 這類應用,推薦 RTP 應該使用一個偶數端口傳輸資料,和它相關的 RTCP 流應該是用高一位的奇數端口。在單點傳播模式下,每個參與者都需要一對端口來傳輸 RTP 和 RTCP 封包。兩個參與者可能使用相同的端口。絕對不能以接收到的封包網絡位址直接作為目标位址發送封包。
建議層編碼模式是,使用相鄰的端口,是以對于層 N 來說,資料端口是 P+2N,控制端口是 P+2N+1。對于 IP 多點傳播來說,可能不會得到相鄰的多點傳播位址。
RTP 資料封包沒有描述封包長度的資訊。是以 RTP 封包依賴下層協定提供長度辨別。是以一個 RTP 封包的最大長度由下層協定限制。
如果 RTP 封包使用的下層協定是流傳輸協定的話,必須定義一套資料幀分割機制。
參考
[1]
rfc3550閱讀作者的更多文章,關注作者個人公衆号:
貝貝貓技術分享作者的個人部落格:
https://www.beikejiedeliulangmao.top/「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。