做者:逸殊
審覈:泰一git
RTMP 在可靠流式傳輸(TCP)的基礎上提供了雙向的消息多路複用服務,在通信雙方之間傳輸與時間相關的並行流數據,如音頻,視頻和數據消息。協議實現方一般爲不一樣的消息類型指定不一樣的優先級,這樣在網絡帶寬受限時能改變底層傳輸順序。算法
全部整數都是以網絡字節序來表示的。除非另行說明,本文中的全部數字都是十進制數。
在沒有特殊說明的狀況下,RTMP 中的數據都是字節對齊的。若是有填充的話,填充字節應該用 0。
RTMP 中的時間戳是用一個整數來表示的,表明相對於一個起始時間的毫秒數。一般每一個流的時間戳都從 0 開始,但這不是必須的,只要通信雙方使用統一的起始時間就能夠了。要注意的是,跨流的時間同步(不一樣主機之間)須要額外的機制來實現。
因爲時間戳的長度只有 32 位,因此只能在 50 天內循環(49 天 17 小時 2 分鐘 47.296 秒)。而流是能夠不斷運行的,可能多年纔會結束。因此 RTMP 應用在處理時間戳是應該使用連續的數字算法,而且應該支持迴環處理。例如:一個應用能夠假設全部相鄰的時間戳間隔不超過 2^31-1 毫秒,在此基礎上,10000 在 4000000000 以後,3000000000 在 4000000000 以前。
時間戳增量也是以毫秒爲單位的無符號整數。時間戳增量可能會是 24 位長度也多是 32 位長度。緩存
塊流爲上層流媒體協議提供複用和分包的功能。RTMP 塊流是爲配合 RTMP 協議而設計,但它可使用在任何發送消息流的協議中。每一個消息包含時間戳和負載類型信息。RTMP 塊流和 RTMP 協議組合能夠適用於多種音視頻應用,從一對一或一對多直播到視頻會議都能很好的知足。
當使用可靠傳輸協議(如 TCP)時,RTMP 塊流爲全部消息提供了可靠的跨流端對端按時間戳順序發送的機制。RTMP 塊流不提供優先級控制,可是能夠由上層協議提供這樣的優先級。例如:當某個客戶端網絡比較慢時,可能會選擇拋棄一些視頻消息來保證聲音消息可以及時接收。
RTMP 塊流除自身內置的協議控制消息外,還爲上層協議提供了用戶控制消息的機制。安全
消息格式由上層協議定義,消息能夠被分紅多個塊以支持多路複用。消息應該包含分塊功能所需的全部字段,具體內容以下:服務器
RTMP 鏈接以握手開始,它的握手過程可能和其餘協議不一樣,這裏的握手由 3 個固定大小的塊組成,而不是可變大小的塊加上固定大小的頭。網絡
握手由客戶端發送 C0 和 C1 塊開始。
客戶端必須等接收到 S1 以後才能夠發送 C2。客戶端必須等接收到 S2 以後才能夠發送其餘數據。
服務器必須等接收到 C0 以後才能夠發送 S0 和 S1,也可能接收到 C1 以後發送。服務器必須等接收到 C1 以後才能夠發送 S2。服務器必須等接收到 C2 以後才能夠發送其餘數據。app
C0 和 S0 是單獨的一個字節,能夠當作一個 8bit 的整數字段來對待。
異步
如下是 C0 和 S0 包的字段解釋:ide
C1 和 S1 包固定爲 1536 字節,包含如下字段:
性能
C2 和 S2 包的長度也爲 1536 字節,基本上是 S1 和 C1 的回傳,包含如下字段:
上圖提到的狀態的解釋以下:
握手完成後,一個或多個塊流可能會複用同一個鏈接,每一個塊流承載來自同一個消息流的同一類消息。每一個塊都有一個惟一的塊流 ID,這些塊經過網絡進行傳輸。在傳輸過程當中,必須一個塊發送完畢以後再發送下一個塊。在接收端,將全部塊根據塊中的塊流 ID 組裝成消息。
分塊將上層協議的大消息分割成小的消息,保證大的低優先級消息(好比視頻)不阻塞小的高優先級消息(好比音頻或控制消息)。
分塊還能下降消息發送的開銷,它在塊頭中包含了壓縮的本來須要在消息中所包含的信息。
塊大小是可配置的,這個能夠經過一個設置塊大小控制消息進行設定修改。越大的塊 CPU 使用率越低,可是在低帶寬的狀況下,大的寫入會阻塞其餘內容的寫入。而小一些的塊不適合高比特率的流。
每一個塊由塊頭和數據組成,塊頭包含 3 部分:基本頭、消息頭和擴展時間戳。
基本頭包含塊流 ID 和塊類型(在下圖中用 fmt 字段表示),塊類型決定了消息頭的編碼格式,基本頭長度多是 1,2 或 3 字節,這取決於塊流 ID 的長度。
協議實現方應該用可以用最短表示法來表示塊流 ID。
RTMP 最多支持 65597 個流,ID 在 3-65599 範圍內,0,1,2 爲保留值。若是 2~7 位表明的值爲 0 表示塊基本頭佔 2 個字節,而且塊流 ID 範圍在 64-319 之間(第二個字節 + 64),若是 2~7 位表明的值爲 1 表示塊基本頭佔 3 個字節,而且 ID 範圍在 64-65599 之間(第三個字節 * 256 + 第二個字節 + 64),當 ID 在 3-63 之間時直接使用 2~7 位的值來表示流 ID。
2-63 範圍內的塊流 ID 用 1 個字節來編碼:
64-319 範圍內的塊流 ID 用 2 個字節來編碼,塊流 ID 爲計算所得,公式爲:第二個字節值 + 64:
64-65599 範圍內的塊流 ID 用 3 個字節來編碼,塊流 ID 爲計算所得,公式爲:第三個字節值 * 255 + 第二個字節值 + 64
上述圖中各個部分的含義以下:
64-319 範圍內的塊流 ID 能夠用 2 字節來表示,也能夠用 3 字節表示。
消息頭共有 4 種不一樣的格式,根據基本頭中的 "fmt" 字段值來肯定。協議實現方應該用最緊湊的格式來表示塊消息頭。
0 類型的塊消息頭佔 11 個字節長度,該類型必須用在一個塊流的開頭,和每當塊流時間戳回退的時候(例如視頻回退的操做)。
1 類型的塊消息頭佔用 7 個字節長度,不包含消息流 ID,該塊沿用上一個消息的消息流 ID。對於傳輸大小可變消息的流(如多數視頻格式),在發送第一個消息以後的每一個消息都應該使用該類型格式。
2 類型的塊消息頭佔用 3 個字節長度,不包含消息流 ID 和消息長度,沿用上一個塊的消息流 ID 和消息長度。對於傳輸固定大小消息的流(如音頻和數據格式),在發送第一個消息以後的每個消息都應該使用該類型格式。
3 類型的塊沒有消息頭,消息流 ID、消息長度和時間戳增量,該類型的塊使用和上一個塊相同的頭數據。當一個消息被分割成塊時,除了第一個塊,其餘塊都應該使用該類型。由相同大小、消息流 ID 和時間間隔的消息組成的流,在類型 2 的塊以後全部塊都應該使用該類型格式。若是第一個消息和第二消息之間的時間增量與第一個消息的時間戳相同,則 0 類型的塊以後能夠立刻發送 3 類型的塊,而沒必要使用 2 類型的塊來註冊時間增量。若是類型 3 的塊跟在類型 0 的塊後面,那麼 3 類型塊的時間戳增量與 0 類型塊的時間戳相同。
擴展時間戳用來輔助編碼超過 16777215 (0xFFFFFF) 的時間戳或時間戳增量,也就說消息頭沒法用 24 位數字來表示時間戳或時間戳增量時,既 0 類型塊的時間戳字段或 1,2 類型的時間戳增量字段值爲 16777215 (0xFFFFFF)。當最近的屬於相同塊流 ID 的 0 類型塊、1 類型塊或 2 類型塊有此字段時有此字段時,3 類型塊也應該有此字段。
這是一個簡單的音頻流消息,這是示例示範了信息冗餘。
下圖展現該消息流以塊流形式發送。從 3 類型塊開始了數據傳輸優化,以後的塊只附加了一個字節。
該示例展現了一個超過 128 字節長度的消息,消息被分割成了數個塊。
下圖是被分割成的塊:
第一個塊的頭信息指明瞭消息總大小爲 307 字節。
注意這兩個示例,3 類型塊能夠在兩種狀況下使用。第一種狀況是消息拆分紅多個塊,另外一種狀況是新消息複用上一個消息的全部頭部內容。
RTMP 塊流用消息類型 1,2,3,5 和 6 來做爲協議控制消息,這些消息包含 RTMP 塊流協議所須要的信息。
這些協議控制消息必須用 0 做爲消息流 ID (控制流 ID),並在 ID 爲 2 的塊流中發送。協議控制消息收到後當即生效,它們的時間戳信息是被忽略的。
協議控制消息類型 1:設置塊大小,用於通知另外一端新的最大塊大小。
最大塊大小默認爲 128 字節,客戶端或服務端能夠修改此值,並用該消息通知另外一端。例如,假設一個客戶端想要發送 131 字節的音頻數據,而最大塊大小爲 128。在這種狀況下,客戶端能夠向服務端發送該消息,通知它最大塊大小被設置爲了 131 字節。這樣客戶端只用一個塊就能夠發送這些音頻數據。
最大塊大小不能小於 1 字節,一般應該不低於 128 字節。每一個方向上的最大塊大小是獨立的。
協議控制消息類型 2:終止消息,通知正在等待消息後續塊的另外一端,能夠丟棄指定塊流接收到的數據,塊流 ID 爲該消息的載荷。應用可能在關閉的時候發送該消息,用來代表後面的消息沒有必要繼續處理了。
客戶端或服務器在收到數據總長和窗口大小相等時,經過它回覆確認消息。在鏈接創建完成後,消息的發送方會通知接收方一個窗口的大小(指定一個長度),若是接收方收到指定長度的數據後沒有發送回覆消息,發送方就不會再發送任何內容了。
客戶端或服務端發送該消息來通知對端發送確認消息所使用的視窗大小,並等待接收端發送確認消息。接收端在接收到視窗大小後必須發送確認消息。
客戶端或服務端發送該消息來限制對端的輸出帶寬。接收端收到消息後,能夠直接使用消息中指定的窗口大小,而不須要等待收到確認消息以後。若是視窗大小與上一個視窗大小不一樣,則該消息的接收端應該向該消息的發送端發送新的窗口大小消息。這個消息和上一個消息都是調整窗口大小的,不一樣的地方是,這個消息是接收者請求發送者,讓它調整窗口大小,而上一個消息是發送者主動設置了窗口大小,通知數據接收者。
Limit Type(限制類型)有如下值:
雖然 RTMP 被設計成使用 RTMP 塊流傳輸,可是它也可使用其餘傳輸協議來發送消息,在這種狀況下 RTMP 消息的格式以下所示。值得一提的是,RTMP 塊流協議和 RTMP 協議配合時,很是適合音視頻應用,包括單播、一對多實時直播、視頻點播和視頻會議等。
服務端和客戶端經過在網絡上發送 RTMP 消息實現之間的交互,消息包括音頻、視頻、數據等。
RTMP 消息包含兩部分,消息頭和有效負載。
消息頭包含如下信息:
消息的另外一部分就是有效負載,也是消息包含的實際數據,能夠是音頻樣本或者壓縮的視頻數據。
RTMP 協議將消息類型 4 做爲用戶控制消息 ID,這些消息包含 RTMP 流所需的必要信息。消息類型 1,2,3,5 和 6 由 RTMP 塊流協議使用。
用戶控制消息應該使用 ID 爲 0 的消息流(控制流),而且經過 RTMP 塊流傳輸時使用 ID 爲 2 的塊流。用戶控制消息收到後當即生效,它們的時間戳信息會被忽略。
客戶端或服務端經過發送該消息告知對方用戶控制事件。該消息攜帶事件類型和事件數據兩部分。
開頭的 2 個字節用於指定事件類型,緊跟着是事件數據。事件數據字段長度可變,可是若是用 RTMP 塊流傳輸,則消息總長度不能超過最大塊大小,以使消息可使用一個單獨的塊進行傳輸。
各類類型的消息在客戶端和服務端之間進行交換,包括用於發送音頻數據的音頻消息,用於發送視頻數據的視頻消息,用於發送任意用戶數據的數據消息,共享對象消息和指令消息等。共享對象消息的主要用途是管理客戶端和相同服務器的共享數據。指令消息發送的是客戶端與服務端之間的 AMF 編碼指令,客戶端或服務端也能夠經過指令消息來實現遠程過程調用(RPC)。
客戶端和服務端經過在網絡上發送消息來實現交互,消息能夠是任意類型,包括音頻消息、視頻消息、指令消息、共享對象消息、數據消息和用戶控制消息等。
指令消息在客戶端和服務端之間傳遞 AMF 編碼的指令,消息類型 20 表明 AMF0 編碼,消息類型 17 表明 AMF3 編碼。發送這些消息來完成鏈接、建立流、發佈、播放、暫停等操做。像狀態、結果這樣的指令消息,用於通知發送方請求的指令狀態。一條指令消息由指令名、事務 ID 和包含相關參數的指令對象組成。客戶端或服務端還能夠經過指令消息來實現遠程過程調用 (RPC)。
客戶端或服務端經過該消息來發送元數據或其餘用戶數據。元數據包括數據 (音頻、視頻) 的建立時間、時長、主題等詳細信息。消息類型 18 表明 AMF0 編碼,消息類型 15 表明 AMF3 編碼。
共享對象是在多個客戶端之間同步的 Flash 對象 (鍵值對集合)。消息類型 19 表明 AMF0 編碼,消息類型 16 表明 AMF3 編碼。每一個消息均可以包含多個事件。
支持如下事件類型:
客戶端或服務端經過發送此消息來發送音頻數據給對方,消息類型 8 是爲音頻消息預留的。
客戶端或服務端經過發送此消息來發送視頻數據給對方,消息類型 9 是爲視頻消息預留的。
組合消息,是一個消息包含多個子 RTMP 消息,子消息符合 RTMP 消息格式。消息類型 22 用於組合消息。
組合消息的消息流 ID 會覆蓋其中子消息的消息流 ID。
組合消息的時間戳和其中第一個子消息的時間戳的差值,是用來將全部子消息的時間戳重整爲流時間的位移量。位移量會加到每個子消息的時間戳上來換算出正常的流時間。第一個子消息的時間戳應該與組合消息的時間戳相同,因此位移量應該爲 0。
Back Pointer (反向指針) 包含前一個消息的長度(包括消息頭),這樣符合 flv 文件格式,可用於進行後退操做。
使用組合消息有如下好處:
客戶端或服務器經過該消息發送用戶控制事件。
用戶控制消息支持如下事件:
客戶端和服務器交換 AMF 編碼的指令。發送端發送一條指令消息,其中包含了指令名稱、處理 ID、以及含有相關參數的指令對象。例如,鏈接指令消息包含了’app' 參數,以告知服務器客戶端但願鏈接的目標程序。接收端處理這條指令並回復含有一樣處理 ID 的響應。回覆的字符串可能爲_result、_error 或方法名。如 verifyClient 或 contactExternalServer.
_result 或_error 的指令字符表明一條響應,處理 ID 則代表回覆是針對哪條指令的,這在 IMAP 或其餘協議中是徹底相同的。指令字符串中的方法名代表發送端但願運行接收端上的一個方法。
指令消息可分爲以下兩類:
NetConnection 管理着一個客戶端程序和服務器之間的雙向鏈接,除此以外,它還提供了對異步遠程方法調用的支持。
下列指令可經過 NetConnection 進行發送:
客戶端發送 connect 指令至服務器端以請求鏈接至某一服務器程序實例。
指令結構以下:
Connect 指令中會用到的鍵值對:
音頻編碼:
視頻編碼:
視頻功能:
對象編碼:
由服務器發送至客戶端的指令結構以下:
指令執行流程:
指令執行的消息流以下:
NetConnection 對象的 call 方法用於遠程調用接收端上的程序。須要遠程調用的程序名稱經過一個參數傳遞給 call 指令。
發送指令結構以下:
響應指令結構以下:
客戶端發送該指令至服務器端以建立一條用於傳遞消息的邏輯通道,從而能夠利用已建立的流通道發佈音頻、視頻和元數據。
NetConnection 是默認的通信通道,流 ID 爲 0。協議和一些指令消息,包括 createStream,使用默認通信通道。
客戶端發出的指令結構以下:
服務器發出的指令結構以下:
基於 NetConnection 的客戶端至服務器間鏈接,NetStream 定義了一條能夠傳遞音頻流、視頻流以及消息流的通道。NetConnection 對象支持多個 NetStreams 以傳輸多個數據流。
客戶端可在 NetStream 中發送下列指令至服務器:
服務器端經過 「onStatus" 將 NetStream 的狀態更新至客戶端:
客戶端發送該指令值以播放一個流。屢次調用該指令也可建立一個播放清單。
若是你但願建立一個在不一樣 live 或 recorded 流間切換的動態播放清單,須要屢次調用 play 並傳遞 false 以免每次 reset。相反地,若是你但願當即播放某一指定流,傳遞 true 以清除等待播放隊列中的全部其餘流。
客戶端發送的指令結構以下:
流程圖以下:
指令執行期間的消息流以下:
不一樣於 play 指令,play2 能夠切換碼率而不改變播放內容的時間軸。服務器爲客戶端能夠在 play2 中請求的全部支持的碼率維護多個字段。
客戶端發送的指令結構以下:
該指令的消息流程以下圖:
當 NetStream 對象將要被銷燬時,它發送該 deleteStream 指令。
客戶端發送的指令結構以下:
服務器不須要發送任何應答。
NetStream 發送 ReceiveAudio 消息通知服務器是否發送或不發送音頻到客戶端。
客戶端發送的指令結構以下:
若是 receiveAudio 指令發送帶有 flase 的 bool flag,服務器不發送任何響應。若是這個標誌被設置爲 true,服務器應答 NetStream.Seek.Notify 和 NetStream.Play.Start 的狀態消息。
NetStream 發送 ReceiveVideo 消息通知服務器是否發送或不發送視頻到客戶端。
客戶端發送的指令結構以下:
若是 receiveVideo 指令發送帶有 flase 的 bool flag,服務器不發送任何響應。若是這個標誌被設置爲 true,服務器應答 NetStream.Seek.Notify 和 NetStream.Play.Start 的狀態消息。
客戶端發送 publish 指令將已命名的流發佈到服務器上。使用這個名稱,任何客戶端均可以播放此流,並接收已發佈的音頻、視頻和數據消息。
客戶端發送的指令結構以下:
服務器應答 onStatus 指令,以標記發佈的開始。
客戶端發送 seek 指令以定位媒體文件內或者播放列表的某個位置(以毫秒爲單位)。
客戶端發送的指令結構以下:
當定位成功,服務器發送 NetStream.Seek.Notify 的狀態消息。失敗的時候,它返回一個_error 的消息。
客戶端發送 pause 指令以告訴服務器暫停或者開始播放。
客戶端發送的指令結構以下:
當流被暫停,服務器發送一個 NetStream.Pause.Notify 的狀態消息。當一個流變成未暫停狀態,NetStream.Unpause.Notify 被髮送。失敗的時候,它返回一個_error 的消息。
這裏是一些樣例,以解釋使用 RTMP 的消息交換。
這個例子說明了一個發佈者如何發佈一個流並將視頻流推到服務器上。其餘客戶端能夠訂閱這個已發佈的流,並播放視頻。
這個例子說明了在建立和更改共享對象時所交換的消息。它也說明了共享對象消息廣播的過程。
這個例子描述了發佈元數據的消息交換。
[1] RTMP 規範
[2] RTMP 協議規範翻譯工做
[3] RTMP 協議規範 1.0 中文版
「視頻雲技術」你最值得關注的音視頻技術公衆號,每週推送來自阿里雲一線的實踐技術文章,在這裏與音視頻領域一流工程師交流切磋。