本系列文章將整理各個流媒體傳輸協議,包括 RTP/RTCP,RTMP,但願經過深刻梳理協議的設計細節,可以給流媒體領域的開發者帶來必定的啓發。html
做者:逸殊
審覈:泰一算法
接上篇:《 流媒體傳輸協議之 RTP(上篇)》安全
RTP 使用 Sender 報告(SR)和 Receiver 報告(RR)來反饋數據的接收質量,若是是媒體數據的發送者那就會發送 SR,不然發送 RR。這兩類報文是經過頭部的報文類型識別碼來作區分的。SR 相對於 RR 來講多了 20byte 的 Sender 相關信息,除此以外其餘內容都是同樣的。網絡
SR 報文包含三個部分,第一個部分是頭部,有 8 BYTE,各個字段的含義以下:app
第二部分是發送者信息,包含 20 BYTE 的數據,總結了這個發送的的傳輸統計,各個字段的含義以下:ide
第三部分可能什麼都沒有,也可能有多個接收報告,這取決的上次報告之後收到了多少個 Sender 的數據。每一個報告塊統計了一個 ***C 的包數。具體內容以下:函數
數據的發送者能夠經過當前時間 A,接收到 RR 部分中的 LSR 和 DLSR 來計算 RTT,計算示意圖以下:
工具
接收報告的格式和發送報文格式同樣,只不過它在頭部中用 201 表示這是一個 RR 報文。此外 RR 報文中不含有上述 SR 報文中的第二部分。若是 RR 報文是空的那麼須要在頭部標明 RC=0。oop
發送 / 接收報文的拓展
一些預設可能根據本身的需求,要在接收報告和發送報告中附加一些信息。那麼這些附加內容應該在 SR 或者 RR 的結尾以後。若是這些內容只有發送者相關,那麼 RR 中就不包含這些信息。測試
分析發送報告和接收報告
這些接收質量的報告信息可能不光只有發送者要使用,接收者或者第三方監控器也會使用。發送者可能根據接收質量調整本身的傳輸策略。接收者能夠根據這個信息來肯定本身遇到的問題是本地網絡的問題仍是整個 Session 的問題。網絡的管理者能夠根據這些信息來評估整個網絡環境的狀況。
SDES 是一個三級結構,它包含一個頭和 0 個或多個數據塊,每個數據塊對應了一個 ***C 或 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 應該經過算法來生成而不是手動生成。爲了知足如上須要,通常來講是按照以下的格式來描述 CNAME:
有些人可能會發現,若是上述的 host 使用的是子網地址的話,就沒辦法保證整個 Session 的惟一性了,一般這類沒有直接 IP 的使用者是經過一個 RTP 級別的 Translator 來訪問公共網絡。這個 Translator 會處理從私有地址到公網地址的轉換工做。
這個是描述數據源的真實名字,eg:"John Doe, Bit Recycler"。整個 Session 過程當中但願這個值不變。全 Session 不須要惟一。
電子郵箱地址,eg: "John.Doe@example.com"。整個 Session 過程當中但願這個值不變。
電話號碼須要以國際訪問碼開頭,eg: "+1 908 555 1212"。
視應用不一樣,詳細程度會各不相同。
帶版本號的應用名,能夠用來 DEBUG。
用來發送暫時性的消息描述當前狀態。eg: "on the phone, can't talk"。
上層應用自定義的格式。通常都是用過一個前綴描述消息類型,而後後面跟着消息正文。
BYE 報文表示一個或多個流媒體源再也不活躍。
若是 BYE 報文被 Mixer 收到了,Mixer 應該啥都不改動,就發給下一節點。若是 Mixer 關閉了,它要發送一個包含它管理的全部 ***C 的 BYE 報文。BYE 報文中可能也會跟着帶一些離開緣由的描述。這些描述和 SDES 中帶的描述相似,須要 32-bit,用空字節填補空缺。
APP 報文通常用於實驗性的功能和開發。若是識別到了不認識 NAME 那麼上層應用通常都會忽略它。若是開發或者測試功能穩定了,通常是要經過 IANA 註冊一個新的 RTCP 報文類型。
做爲末端節點的補充,RTP 引入了 Translator 和 Mixer 的概念,它們是 RTP 層的中間件。雖然這多少增長了協議的複雜度,可是對音視頻通話應用來講它們仍是很關鍵的,由於它們能解決防火牆問題和低帶寬鏈接的問題。
一個 RTP Translator/Mixer 鏈接至少兩個傳輸層的用戶組。一般來講,這裏提到的用戶組是公共網絡的概念,傳輸層協議會爲其生成一個組播地址(ip:port)。網絡層協議,像是 IPv4 和 IPv6 對 RTP 協議來講是隱藏的。一個系統可能會有多個 Translator 和 Mixer(多個 Session),它們中的每個均可以看做是一個用戶組的邏輯分割。
爲了不建立在建立 Translator 和 Mixer 形成了網絡包循環,必須遵循下列規則:
Translator:在不改變 RTP 報文 ***C 的條件下,向後傳播該報文,正由於如此,報文的接收者才能識別到 Translator 轉發後的報文究竟是來自哪一個人。有些 Translator 可能直接轉發報文,不作任何改動,也有可能改變數據編碼,payload 類型和時間戳。
若是多個數據報文被從新編碼併合併到一塊兒的話,Translator 必須爲這類報文指定一個組新的序列號。這樣,輸入報文的丟失就會致使輸出報文的斷層。數據的接收者通常是不知道 Translator 的存在的,除非經過 payload 類型的不一樣或者傳輸層報文的源地址來判斷。
Mixer:從一個或多個數據源那裏接收數據,隨後可能會改變數據的格式,而後將這些數據合併,並傳遞給下家。由於多個數據源的時序並不必定是同步的,因此 Mixer 須要整合各個數據源的時序關係,並將其映射到本身的一套時序上,因此 Mixer 也是一個 ***C,全部經過 Mixer 的報文必須打上該 Mixer 的 ***C。
爲了表示這些數據的原始數據源,通常會經過 CSRC 列表來記錄。有些 Mixer 可能本身也是一個原始數據源,因此他本身的 ***C 也會出如今 CSRC 列表中。有些應用可能不但願 Mixer 的 ***C 出如今 CSRC 中,可是這樣可能就沒法發現循環網絡包。
上圖是一個 Mixers 和 Translators 鏈接的例子。[] 表明末端節點,() 表明 Mixer,<> 表明 Translator,"M1:48 (1, 17)" 表示 Mixer1 的報文,48 是 Mixer1 的 ***C,括號裏的 1,17 是 CSRC,它合併了 E1:17 和 E2:1 這兩個節點的數據。
除了要轉發數據包,進行數據包的更改,Translator 和 Mixer 也要發送 RTCP 報文。在不少狀況下,它會將收到的末端節點的 RTCP 報文合併到複合包中。當再次收到這些包時或者本身的 RTCP 週期到時,它會將複合包發送出去。
有的 Translator 可能對收到的 RTCP 報文不作任何改動,只是簡單的轉發這個包。若是這個 Translator 改變了報文數據的 payload,它必須對 SR 或者 RR 作相關的改動。一般來講,Translator 不能將多個數據源的 SR 和 RR 合併,由於這樣會致使 RTT 的計算出現問題(RTT 根據 LSR 和 DLSR 計算)。
由於 Mixer 會生成本身的數據流,因此他不會轉發通過他的 SR 和 RR 而是爲鏈接雙方發送本身的 SR 和 RR 報文。
一個 RTP Session 可能包含多個 Mixer 和 Translator,就像上圖同樣。若是 Mixer 是瀑布型的,就像 M2 和 M3,一個 Mixer 收到的數據多是已經合併過的,它有本身的 CSRC 列表。那麼第二個 Mixer 須要將以前的 CSRC 和本身接收的全部 ***C 合併。就像圖中 M3 的輸出是 M3:89 (64,45)。
前面已經說過 ***C 是一個隨機的 32-bit 數,它須要在整個 Session 內保證惟一性。因此同一個網絡下的參與者在剛加入 Session 時使用不一樣的 ***C 相當重要。
咱們不能簡單的用本地的網絡地址,由於可能不惟一。也不能不考慮初始狀態而簡單地調一個隨機數函數。
由於 ***C 是隨機選擇的,這就可能多個數據源選用了相同的 ***C。若是你們是同時加入 Session 的話,這個碰撞的概率就更高。若是 ***C 的數量是 N,L 是 ***C 的數據長度(這裏是 32),那麼碰撞的可能性是 1 - exp(-N2 / 2(L+1))
,當 N=1000 時,碰撞率大概是 10**-4。
一般來講,實際的碰撞率會比上述的最壞狀況要低。一般一個新節點加入時,其餘節點已經有了本身的惟一 ***C,這時候碰撞的機率只是生成的新 ***C 在這些現有 ***C 之中的可能性。這時候碰撞率是 N/2**L
。當 N=1000 時,碰撞率大約是 2*10**-7。
由於新加入的節點會先接收一段時間的報文而後才發送本身的第一個報文,因此在它生成 ***C 時能夠避開已知的 ***C,這也有效的下降了碰撞的概率。
一般來講 ***C 碰撞的可能性很小,全部的 RTP 實現必須有發現衝突的機制,並在發現衝突時做出適當的處理。若是數據源發現了任何一個別的數據源和本身使用同一個 ***C,它必須用原來的 ***C 發送一個 BYE 報文,而後選用一個新的 ***C。若是一個數據的接收者發現了多個數據源的 ***C 碰撞了(經過傳輸地址或者 CNAME),那麼它會只接收其中一我的的報文,丟棄另外一我的的全部報文。
由於整個 Session 中的 ***C 是惟一的,因此它也能夠被用來發現環型報文。環形報文會致使數據的重複以及控制信息的重複。
一個數據源可能發現本身的或者別人的報文被循環發送了。不管是報文循環仍是 ***C 的碰撞都會致使同一個現象,即 ***C 相同可是傳輸地址不一樣的報文。所以,若是數據源改變了本身的傳輸地址,那它就須要同時改變本身的 ***C 來避免被檢測成環形報文。有一個須要注意的內容是,若是一個 Translator 再重啓的過程當中改變了本身的傳輸地址,那麼這個 Translator 轉發的全部數據都會被檢測成環。這類狀況的解決方案通常有以下兩個:
若是循環或者碰撞發生在離 Translator 和 Mixer 很遠的地方,咱們就不能經過傳輸地址來發現。可是咱們仍然能夠經過 CNAME 的不一樣來發現 ***C 碰撞。
爲了解決上述問題,RTP 的實現必須包含一個相似以下的算法。這個算法不包括多個數據源 ***C 碰撞的狀況,這類狀況一般下都是先用原來的 ***C 發送一個 BYE 而後從新選擇一個新的 ***C。
這個算法須要維護一個 ***C 和傳輸地址的映射關係。由於 RTP 的數據和 RTCP 傳輸使用的是兩個不一樣的端口,因此一個 ***C 對應的是兩個傳輸地址。
每次收到 RTP 報文和 RTCP 報文都會將其 ***C 和 CSRC 在上述的表中進行比對。若是發現了傳輸地址對不上的狀況,咱們就能夠說發現了一個循環或者碰撞。對於 RTCP 數據來講,可能每一個數據塊都有本身獨立的 ***C,好比 SDES 數據,對於這種狀況就須要分別比對。若是沒有在表中找到這個 ***C 或者 CSRC,就須要新添加一項。當收到 BYE 報文時,須要先比對這個 BYE 的傳輸地址,若是傳輸地址匹配上了,就將這一項從表中刪除。或者基於超時機制,將超時的數據從表中移除。
爲了追蹤本身的數據報文循環狀況,必須維護另外一個列表,這個表存儲衝突報文的傳輸地址和收到該報文的時間。若是超過 10 個 RTCP 週期都沒有收到這個傳輸地址的衝突報文,就將該項從表中刪除。
下面的算法還假設參與者本身的 ***C 和狀態都包含在 ***C 表中,它會先比對本身的 ***C。
if (***C or CSRC identifier is not found in the source identifier table) { create a new entry storing the data or control source transport address, the ***C 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 ***C 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 ***C identifier; choose a new ***C identifier; create a new entry in the source identifier table with the old ***C plus the source transport address from the data or control packet being processed; } }
對於不一樣 Session 的層級編碼傳輸,通常都是全部層都使用同一個 ***C,若是其中某一層發現了 ***C 衝突,那麼只改變這一層的 ***C,並且他層的 ***C 不作改變。
下層協議可能會提供 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 報文不須要前綴,取而代之的是隨機序列號和時間戳偏移。由於隨機部分不多,因此能夠說這是一個很是弱的初始向量。此外,***C 也可被破解者修改,這是這個加密方案的另外一個薄弱的環節。
對於 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 須要下層協議提供多路複用機制。對於 UDP 這類應用,推薦 RTP 應該使用一個偶數端口傳輸數據,和它相關的 RTCP 流應該是用高一位的奇數端口。在單播模式下,每一個參與者都須要一對端口來傳輸 RTP 和 RTCP 報文。兩個參與者可能使用相同的端口。絕對不能以接收到的報文網絡地址直接做爲目標地址發送報文。
建議層編碼模式是,使用相鄰的端口,所以對於層 N 來講,數據端口是 P+2N,控制端口是 P+2N+1。對於 IP 組播來講,可能不會獲得相鄰的組播地址。
RTP 數據報文沒有描述報文長度的信息。因此 RTP 報文依賴下層協議提供長度標識。因此一個 RTP 報文的最大長度由下層協議限制。
若是 RTP 報文使用的下層協議是流傳輸協議的話,必須定義一套數據幀分割機制。
[1] rfc3550
閱讀做者的更多文章,關注做者我的公衆號:貝貝貓技術分享
做者的我的博客:https://www.beikejiedeliulangmao.top/
「視頻雲技術」你最值得關注的音視頻技術公衆號,每週推送來自阿里雲一線的實踐技術文章,在這裏與音視頻領域一流工程師交流切磋。