流媒體傳輸協議之 RTMP


作者:逸殊
審核:泰一

簡介

RTMP 在可靠流式傳輸(TCP)的基礎上提供了雙向的消息多路復用服務,在通訊雙方之間傳輸與時間相關的並行流數據,如音頻,視頻和數據消息。協議實現方通常為不同的消息類型指定不同的優先級,這樣在網絡帶寬受限時能改變底層傳輸順序。

定義

  • 負載:包中所承載的數據。例如音頻或視頻數據。
  • 包:一個數據包由固定頭部和所承載的數據組成。一些底層協議可能需要定義數據包的封裝格式。
  • 端口:在一個計算機中用於區分不同目標的抽象定義。在 TCP/IP 協議中用一個小的正整數來表示端口。OSI 傳輸層的傳輸選擇器就相當於端口。
  • 傳輸地址:標識一個傳輸終端的網絡地址和端口的組合,例如 IP 地址和 TCP 端口的組合。
  • 消息流:允許消息傳播的邏輯通道。
  • 消息流 ID:每個消息都會有一個對應的 ID,用於標識其所在的消息流。
  • 塊:消息的一個片段。消息在傳輸之前會被分割成更小的片段,因為每一塊都很小,以至於可以給不同的塊指定各自的優先級,通過這種方式保證多個流中數據可以按照時間戳的順序傳輸。
  • 塊流:塊向某一確定方向傳播的邏輯通道。可以是客戶端到服務端,也可以是服務端到客戶端。
  • 塊流 ID:每個塊都會有一個對應的 ID,用於標識其所在的塊流。
  • 復用:將獨立的音頻 / 視頻數據整合為統一的音視頻流,可以使多個音視頻流同步傳輸。
  • 復用分離:復用的逆向過程。將合並的音視頻數據分離為原始的音頻和視頻數據。
  • 遠程過程調用:客戶端或服務端調用另一端的功能。
  • 元數據:媒體數據的描述信息。
  • 應用實例:服務器上可以和 Client 建立連接的應用。
  • 動作消息格式:一個可用於序列化 ActionScript 對象圖的緊湊的二進制格式。
  • 字節序:字節的順序,即多字節類型的數據在內存中的存放順序。TCP/IP 各層協議將字節序定義為大端字節序,因此 TCP/IP 協議中使用的字節序通常稱之為網絡字節序。
  • 大字節序:高位字節排放在內存的低地址,低位字節排放在內存的高地址。
  • 小字節序:低位字節排放在內存的低地址,高位字節排放在內存的高地址。

字節序,校准,時間格式

所有整數都是以網絡字節序來表示的。除非另行說明,本文中的所有數字都是十進制數。
在沒有特殊說明的情況下,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 塊流和 RTMP 協議組合可以適用於多種音視頻應用,從一對一或一對多直播到視頻會議都能很好的滿足。
當使用可靠傳輸協議(如 TCP)時,RTMP 塊流為所有消息提供了可靠的跨流端對端按時間戳順序發送的機制。RTMP 塊流不提供優先級控制,但是可以由上層協議提供這樣的優先級。例如:當某個客戶端網絡比較慢時,可能會選擇拋棄一些視頻消息來保證聲音消息能夠及時接收。
RTMP 塊流除自身內置的協議控制消息外,還為上層協議提供了用戶控制消息的機制。

消息格式

消息格式由上層協議定義,消息可以被分成多個塊以支持多路復用。消息應該包含分塊功能所需的所有字段,具體內容如下:

  • 時間戳(4-byte):消息的時間戳。
  • 長度(3-byte):消息有效負載的長度,如果消息頭不能被省略,則消息頭的長度也應該包含在長度中。
  • 類型 ID(1-byte):消息類型 ID。一些類型 ID 是為協議控制消息保留的,這些消息所表示的信息同時供 RTMP 塊流協議和上層協議使用。所有其他類型 ID 都用於上層協議,RTMP 塊流對這些 ID 做不透明處理。實際上,RTMP 塊流不需要用這些值來區分類型,所有消息都可以是相同的類型,應用也可以用本字段來區分同步軌道而不是區分類型。
  • 消息流 ID(4-byte):消息流 ID 可以是任意值。被復合到同一個塊流的消息流,依據消息流 ID 進行分離。另外,就相關的塊流而言,這個值是不透明的。這個字段使用小字節序。

握手

RTMP 連接以握手開始,它的握手過程可能和其他協議不同,這里的握手由 3 個固定大小的塊組成,而不是可變大小的塊加上固定大小的頭。

握手流程

握手由客戶端發送 C0 和 C1 塊開始。
客戶端必須等接收到 S1 之后才可以發送 C2。客戶端必須等接收到 S2 之后才可以發送其他數據。
服務器必須等接收到 C0 之后才可以發送 S0 和 S1,也可能接收到 C1 之后發送。服務器必須等接收到 C1 之后才可以發送 S2。服務器必須等接收到 C2 之后才可以發送其他數據。

C0 和 S0 格式

C0 和 S0 是單獨的一個字節,可以當做一個 8bit 的整數字段來對待。
1.png

以下是 C0 和 S0 包的字段解釋:

  • 版本號(8 位): 在 C0 包中,該字段表示客戶端請求的 RTMP 版本。在 S0 中,該字段表示服務器選擇的 RTMP 版本。本規范所定義的版本是 3。可選值中,0-2 是早期版本所用的,已被丟棄,4-31 保留在未來使用,32-255 不允許使用(為了區分其他以某一可見字符開始的文本協議)。如果服務器不能識別客戶端請求的版本,應該返回 3,客戶端可能選擇降級到版本 3,也可能放棄握手。

C1 和 S1 格式

C1 和 S1 包固定為 1536 字節,包含以下字段:
2.png

  • 時間戳(4 字節):該字段承載一個時間戳,該時間戳應該作為發送端點所有后續塊的時間戳起始時間。可以是 0,也可以是其他的任意值。為了同步多個塊流,端點可能會發送其他塊流的當前時間戳。
  • 零值(4 字節):該字段所有值都必須為 0。
  • 隨機數據(1528 字節):該字段可以是任意值。通過這個字段來區分自己和連接的另一方,所以此數據應該有充分的隨機性,但是沒必要使用加密安全的隨機值或動態值。

C2 和 S2 格式

C2 和 S2 包的長度也為 1536 字節,基本上是 S1 和 C1 的回傳,包含以下字段:
3.png

  • 時間戳(4 字節):該字段必須包含對端發來的時間戳(對 C2 來說是 S1, 對 S2 來說是 C1)。
  • 時間 2(4 字節):該字段必須包含先前發送的並被對端讀取的包的時間戳。(對 C2 來說是 C1,對 S2 來說是 S1)。
  • 隨機數據回顯(1528 字節):該字段必須包含對端發送過來的隨機數據字段值(對 C2 來說是 S1, 對 S2 來說是 C1)。任何一端都可以用時間戳和時間戳 2 兩個字段值和當前時間戳來快速的估算帶寬和延遲,但這樣可能並不實用。

握手流程示意圖

4.png

上圖提到的狀態的解釋如下:

  • Uninitialized:未初始化狀態。在該階段發送協議版本。客戶端在 C0 包中發送 RTMP 協議版本,如果服務器支持此版本,服務器將在響應中發送 S0 和 S1。如果不支持,服務器采用適當的行為作為響應,在 RTMP 規范中是終止連接。
  • Version Send:版本已發送狀態。在未初始化狀態之后客戶端和服務端都進入版本已發送狀態。客戶端等待接收 S1 包,服務端等待接收 C1 包。收到所等待的包后,客戶端發送 C2 包,服務端發送 S2 包。之后狀態進入發送確認狀態。
  • Ack Send:客戶端和服務端等待接收 S2 和 C2 包,收到后進入握手完成狀態。
  • Handshake Done:握手完成, 客戶端和服務端開始交換消息。

分塊

握手完成后,一個或多個塊流可能會復用同一個連接,每個塊流承載來自同一個消息流的同一類消息。每個塊都有一個唯一的塊流 ID,這些塊通過網絡進行傳輸。在傳輸過程中,必須一個塊發送完畢之后再發送下一個塊。在接收端,將所有塊根據塊中的塊流 ID 組裝成消息。
分塊將上層協議的大消息分割成小的消息,保證大的低優先級消息(比如視頻)不阻塞小的高優先級消息(比如音頻或控制消息)。
分塊還能降低消息發送的開銷,它在塊頭中包含了壓縮的原本需要在消息中所包含的信息。
塊大小是可配置的,這個可以通過一個設置塊大小控制消息進行設定修改。越大的塊 CPU 使用率越低,但是在低帶寬的情況下,大的寫入會阻塞其他內容的寫入。而小一些的塊不適合高比特率的流。

塊格式

每個塊由塊頭和數據組成,塊頭包含 3 部分:基本頭、消息頭和擴展時間戳。
5.png

  • 基本頭 (1-3 字節):塊流 ID 和塊類型,塊類型決定了之后消息頭的編碼格式。基本頭的長度取決於塊流 ID,當塊流 ID 越大時所需要的字節數越多。
  • 消息頭 (0,3,7 或 11 字節):所發送消息的描述信息。該部分的長度取決於基本頭中指定的塊類型。
  • 擴展時間戳 (0 或 4 字節):該部分只有在某些特殊情況下才會使用,是否使用取決於時間戳或時間戳增量是否超出了塊消息頭中相應字段的描述范圍。
  • 塊數據 (變長):塊承載的有效數據,最大長度為配置的塊大小。
基本頭

基本頭包含塊流 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 個字節來編碼:
6.png

64-319 范圍內的塊流 ID 用 2 個字節來編碼,塊流 ID 為計算所得,公式為:第二個字節值 + 64:
7.png

64-65599 范圍內的塊流 ID 用 3 個字節來編碼,塊流 ID 為計算所得,公式為:第三個字節值 * 255 + 第二個字節值 + 64
8.png

上述圖中各個部分的含義如下:

  • cs id (6 位):該字段表示完整的塊流 ID,取值在 2-63 之間。0,1 兩個值是保留值,用來表示基本頭是 2 字節還是 3 字節長度。
  • fmt:該字段表明了消息頭使用的格式。
  • cs id - 64 (8 位或 16 位):該字段表示塊流 ID,取值在 64-63399 之間。

64-319 范圍內的塊流 ID 可以用 2 字節來表示,也可以用 3 字節表示。

消息頭

消息頭共有 4 種不同的格式,根據基本頭中的 "fmt" 字段值來確定。協議實現方應該用最緊湊的格式來表示塊消息頭。

類型 0

0 類型的塊消息頭占 11 個字節長度,該類型必須用在一個塊流的開頭,和每當塊流時間戳回退的時候(例如視頻回退的操作)。
9.png

  • timestamp (3 字節):對於 0 類型的消息塊,消息的絕對時間戳在這里發送。 如果時間戳大於或等於 16777215 (0xFFFFFF),改字段值必須為 16777215,並且必須設置擴展時間戳來共同編碼 32 位的時間戳。否則該字段就是完整的時間戳。
  • message length (3 字節): 消息長度,類型 0 和類型 1 的塊包含此字段,表示消息的長度。要注意的是,通常消息長度與塊長度並不相同。塊長度除了最后一個塊之外,都與塊最大長度相同。
  • message type id (3 字節): 消息類型 id,類型 0 和類型 1 的塊包含此字段,表示消息的類型。
  • message stream id (4 字節): 消息流 ID,類型 0 的塊包含此字段,表示消息流 ID。消息流 ID 以小字節序存儲。通常,相同塊流中的消息屬於用一個消息流。雖然,不同的消息流復用相同的塊流會導致消息頭無法有效壓縮,但是當一個消息流已關閉,准備打開另外一個消息流時,就可以通過發送一個新的 0 類型塊來實現復用。
類型 1

1 類型的塊消息頭占用 7 個字節長度,不包含消息流 ID,該塊沿用上一個消息的消息流 ID。對於傳輸大小可變消息的流(如多數視頻格式),在發送第一個消息之后的每個消息都應該使用該類型格式。
10.png

  • timestamp delta (3 字節): 時間戳增量。類型 1 和類型 2 的塊包含此字段,表示前一個塊的 timestamp 字段和當前塊 timestamp 間的差值。 如果時間戳增量大於或等於 16777215 (0xFFFFFF),該字段必須為 16777215,並且必須設置擴展時間戳,來共同表示 32 位的時間戳增量,否則該字段值就是實際的時間戳增量。
類型 2

2 類型的塊消息頭占用 3 個字節長度,不包含消息流 ID 和消息長度,沿用上一個塊的消息流 ID 和消息長度。對於傳輸固定大小消息的流(如音頻和數據格式),在發送第一個消息之后的每一個消息都應該使用該類型格式。
12.png

類型 3

3 類型的塊沒有消息頭,消息流 ID、消息長度和時間戳增量,該類型的塊使用和上一個塊相同的頭數據。當一個消息被分割成塊時,除了第一個塊,其他塊都應該使用該類型。由相同大小、消息流 ID 和時間間隔的消息組成的流,在類型 2 的塊之后所有塊都應該使用該類型格式。如果第一個消息和第二消息之間的時間增量與第一個消息的時間戳相同,則 0 類型的塊之后可以馬上發送 3 類型的塊,而不必使用 2 類型的塊來注冊時間增量。如果類型 3 的塊跟在類型 0 的塊后面,那么 3 類型塊的時間戳增量與 0 類型塊的時間戳相同。

擴展時間戳

擴展時間戳用來輔助編碼超過 16777215 (0xFFFFFF) 的時間戳或時間戳增量,也就說消息頭無法用 24 位數字來表示時間戳或時間戳增量時,既 0 類型塊的時間戳字段或 1,2 類型的時間戳增量字段值為 16777215 (0xFFFFFF)。當最近的屬於相同塊流 ID 的 0 類型塊、1 類型塊或 2 類型塊有此字段時有此字段時,3 類型塊也應該有此字段。

示例

示例 1

這是一個簡單的音頻流消息,這是示例示范了信息冗余。
13.png

下圖展示該消息流以塊流形式發送。從 3 類型塊開始了數據傳輸優化,之后的塊只附加了一個字節。
14.png

示例 2

該示例展示了一個超過 128 字節長度的消息,消息被分割成了數個塊。
15.png

下圖是被分割成的塊:
16.png

第一個塊的頭信息指明了消息總大小為 307 字節。
注意這兩個示例,3 類型塊可以在兩種情況下使用。第一種情況是消息拆分成多個塊,另一種情況是新消息復用上一個消息的所有頭部內容。

協議控制消息

RTMP 塊流用消息類型 1,2,3,5 和 6 來作為協議控制消息,這些消息包含 RTMP 塊流協議所需要的信息。
這些協議控制消息必須用 0 作為消息流 ID (控制流 ID),並在 ID 為 2 的塊流中發送。協議控制消息收到后立即生效,它們的時間戳信息是被忽略的。

設置塊大小

協議控制消息類型 1:設置塊大小,用於通知另一端新的最大塊大小。
最大塊大小默認為 128 字節,客戶端或服務端可以修改此值,並用該消息通知另一端。例如,假設一個客戶端想要發送 131 字節的音頻數據,而最大塊大小為 128。在這種情況下,客戶端可以向服務端發送該消息,通知它最大塊大小被設置為了 131 字節。這樣客戶端只用一個塊就可以發送這些音頻數據。
最大塊大小不能小於 1 字節,通常應該不低於 128 字節。每個方向上的最大塊大小是獨立的。
17.png

  • 0 (1 位): 該位必須為 0.
  • chunk size (31 位): 該字段以字節形式保存新的最大塊大小,該值將用於后續的所有塊的發送,直到收到新的通知。該值可取值范圍為 1-2147483647 (0x7FFFFFFF),但是所有大於 1677215 (0xFFFFFF) 的值都是視作是 16777215,因為任何塊不可能比消息大,而消息長度不能大於 16777215 字節。

終止消息

協議控制消息類型 2:終止消息,通知正在等待消息后續塊的另一端,可以丟棄指定塊流接收到的數據,塊流 ID 為該消息的載荷。應用可能在關閉的時候發送該消息,用來表明后面的消息沒有必要繼續處理了。
18.png

  • chunk stream id (32 字節): 指定消息的塊流 ID。

確認消息

客戶端或服務器在收到數據總長和窗口大小相等時,通過它回復確認消息。在連接建立完成后,消息的發送方會通知接收方一個窗口的大小(指定一個長度),如果接收方收到指定長度的數據后沒有發送回復消息,發送方就不會再發送任何內容了。
19.png

  • sequence number (32 字節): 到當前時刻為止接收到的字節總數。

確認窗口大小

客戶端或服務端發送該消息來通知對端發送確認消息所使用的視窗大小,並等待接收端發送確認消息。接收端在接收到視窗大小后必須發送確認消息。
20.png

設置對方傳輸帶寬

客戶端或服務端發送該消息來限制對端的輸出帶寬。接收端收到消息后,可以直接使用消息中指定的窗口大小,而不需要等待收到確認消息之后。如果視窗大小與上一個視窗大小不同,則該消息的接收端應該向該消息的發送端發送新的窗口大小消息。這個消息和上一個消息都是調整窗口大小的,不同的地方是,這個消息是接收者請求發送者,讓它調整窗口大小,而上一個消息是發送者主動設置了窗口大小,通知數據接收者。
21.png

Limit Type(限制類型)有以下值:

  • 0 - Hard: 應該將輸出帶寬限制為指定視窗大小。
  • 1 - Soft: 應該將輸出帶寬限制為指定視窗大小和當前視窗大小中較小的值。
  • 2 - Dynamic: 如果上一個消息的限制類型為 Hard,則該消息同樣為 Hard,否則拋棄該消息。

RTMP 消息格式

雖然 RTMP 被設計成使用 RTMP 塊流傳輸,但是它也可以使用其他傳輸協議來發送消息,在這種情況下 RTMP 消息的格式如下所示。值得一提的是,RTMP 塊流協議和 RTMP 協議配合時,非常適合音視頻應用,包括單播、一對多實時直播、視頻點播和視頻會議等。

格式

服務端和客戶端通過在網絡上發送 RTMP 消息實現之間的交互,消息包括音頻、視頻、數據等。
RTMP 消息包含兩部分,消息頭和有效負載。

RTMP 消息頭

消息頭包含以下信息:

  • Message Type: 消息類型,占用 1 個字節。1-6 的消息類型 ID 是為協議控制消息保留的。
  • Length: 有效負載的字節數,占用 3 個字節。該字段是用大端序表示的。
  • Timestamp: 時間戳,占用 4 個字節,用大端序表示。
  • Message Stream Id: 消息流 ID,標識消息所使用的流,用大端序表示。
    22.png

消息有效負載

消息的另一部分就是有效負載,也是消息包含的實際數據,可以是音頻樣本或者壓縮的視頻數據。

用戶控制消息

RTMP 協議將消息類型 4 作為用戶控制消息 ID,這些消息包含 RTMP 流所需的必要信息。消息類型 1,2,3,5 和 6 由 RTMP 塊流協議使用。
用戶控制消息應該使用 ID 為 0 的消息流(控制流),並且通過 RTMP 塊流傳輸時使用 ID 為 2 的塊流。用戶控制消息收到后立即生效,它們的時間戳信息會被忽略。
客戶端或服務端通過發送該消息告知對方用戶控制事件。該消息攜帶事件類型和事件數據兩部分。
23.png

開頭的 2 個字節用於指定事件類型,緊跟着是事件數據。事件數據字段長度可變,但是如果用 RTMP 塊流傳輸,則消息總長度不能超過最大塊大小,以使消息可以使用一個單獨的塊進行傳輸。

RTMP 指令消息

各種類型的消息在客戶端和服務端之間進行交換,包括用於發送音頻數據的音頻消息,用於發送視頻數據的視頻消息,用於發送任意用戶數據的數據消息,共享對象消息和指令消息等。共享對象消息的主要用途是管理客戶端和相同服務器的共享數據。指令消息發送的是客戶端與服務端之間的 AMF 編碼指令,客戶端或服務端也可以通過指令消息來實現遠程過程調用(RPC)。

消息類型

客戶端和服務端通過在網絡上發送消息來實現交互,消息可以是任意類型,包括音頻消息、視頻消息、指令消息、共享對象消息、數據消息和用戶控制消息等。

指令消息

指令消息在客戶端和服務端之間傳遞 AMF 編碼的指令,消息類型 20 代表 AMF0 編碼,消息類型 17 代表 AMF3 編碼。發送這些消息來完成連接、創建流、發布、播放、暫停等操作。像狀態、結果這樣的指令消息,用於通知發送方請求的指令狀態。一條指令消息由指令名、事務 ID 和包含相關參數的指令對象組成。客戶端或服務端還可以通過指令消息來實現遠程過程調用 (RPC)。

數據消息

客戶端或服務端通過該消息來發送元數據或其他用戶數據。元數據包括數據 (音頻、視頻) 的創建時間、時長、主題等詳細信息。消息類型 18 代表 AMF0 編碼,消息類型 15 代表 AMF3 編碼。

共享對象消息

共享對象是在多個客戶端之間同步的 Flash 對象 (鍵值對集合)。消息類型 19 代表 AMF0 編碼,消息類型 16 代表 AMF3 編碼。每個消息都可以包含多個事件。
24.png

支持以下事件類型:

  • 創建(1):客戶端向服務端發送,請求創建指定名稱的共享對象。
  • 釋放(2):客戶端通知服務端,共享對象已在本地刪除。
  • 請求更新(3):客戶端請求修改共享對象的屬性值。
  • 更新(4):通知服務端向除自己外的其他客戶端發送共享數據消息,通知它們有屬性的值發生了變化。
  • 成功(5):“請求更新” 事件被接受后,服務端向發送請求的客戶端回復此事件。
  • 發送消息(6):客戶端向服務端發送此事件,來廣播一個消息。服務端收到此事件后向所有客戶端廣播一條消息,包括請求方客戶端。
  • 狀態(7):服務端發送此事件來通知客戶端錯誤信息。
  • 清除(8):服務端向客戶端發送此事件,通知客戶端清除一個共享對象。服務端在回復客戶端的 “創建” 事件時也會發送此事件。
  • 移除(9):服務端發送此事件,使客戶端刪除一個插槽。
  • 請求移除(10):客戶端刪除一個插槽時發送此事件。
  • 創建成功(11):當連接成功時服務端向客戶端發送此事件。

音頻消息

客戶端或服務端通過發送此消息來發送音頻數據給對方,消息類型 8 是為音頻消息預留的。

視頻消息

客戶端或服務端通過發送此消息來發送視頻數據給對方,消息類型 9 是為視頻消息預留的。

組合消息

組合消息,是一個消息包含多個子 RTMP 消息,子消息符合 RTMP 消息格式。消息類型 22 用於組合消息。
25.png

組合消息的消息流 ID 會覆蓋其中子消息的消息流 ID。
組合消息的時間戳和其中第一個子消息的時間戳的差值,是用來將所有子消息的時間戳重整為流時間的位移量。位移量會加到每一個子消息的時間戳上來換算出正常的流時間。第一個子消息的時間戳應該與組合消息的時間戳相同,所以位移量應該為 0。
Back Pointer (反向指針) 包含前一個消息的長度(包括消息頭),這樣符合 flv 文件格式,可用於進行后退操作。
使用組合消息有以下好處:

  • 塊流協議中,一個塊最多只能發送一個消息,這樣就使用組合消息,加大塊大小,從而降低發送的塊數量。
  • 子消息在內存中連續存放,這樣系統調用網絡發送數據的性能更高。

用戶控制消息事件

客戶端或服務器通過該消息發送用戶控制事件。
26.png

用戶控制消息支持以下事件:

  • 流開始(0):服務端發送該事件,用來通知客戶端一個流已經可以用來通訊了。默認情況下,該事件是在收到客戶端連接指令並成功處理后發送的第一個事件。事件的數據使用 4 個字節來表示可用的流的 ID。
  • 流結束(1):服務端發送該事件,用來通知客戶端其在流中請求的數據已經結束了。如果沒有額外的指令,將不會再發送任何數據,而客戶端會丟棄之后從該流接收到的消息。事件數據使用 4 個字節來表示回放完成的流的 ID。
  • 流枯竭(2):服務端發送該事件,用來通知客戶端流中已經沒有更多的數據了。如果服務端在一定時間后沒有探測到更多數據,它就可以通知所有訂閱該流的客戶端,流已經枯竭。事件數據用 4 個字節來表示枯竭的流的 ID。
  • 設置緩沖區大小(3):客戶端發送該事件,用來告知服務端用來緩存流中數據的緩沖區大小 (單位毫秒)。該事件在服務端開始處理流數據之前發送。事件數據中,前 4 個字節用來表示流 ID,之后的 4 個字節用來表示緩沖區大小(單位毫秒)。
  • 流已錄制(4):服務端發送該事件,用來通知客戶端指定流是一個錄制流。事件數據用 4 個字節表示錄制流的 ID。
  • ping 請求(5):服務端發送該事件,用來探測客戶端是否處於可達狀態。事件數據是一個 4 字節的時間戳,表示服務端分發該事件時的服務器本地時間。客戶端收到后用 ping 響應回復服務端。
  • ping 響應(6):客戶端用該事件回復服務端的 ping 請求,事件數據為收到的 ping 請求中攜帶的 4 字節的時間戳。

指令類型

客戶端和服務器交換 AMF 編碼的指令。發送端發送一條指令消息,其中包含了指令名稱、處理 ID、以及含有相關參數的指令對象。例如,連接指令消息包含了’app' 參數,以告知服務器客戶端希望連接的目標程序。接收端處理這條指令並回復含有同樣處理 ID 的響應。回復的字符串可能為_result、_error 或方法名。如 verifyClient 或 contactExternalServer.
_result 或_error 的指令字符代表一條響應,處理 ID 則表明回復是針對哪條指令的,這在 IMAP 或其他協議中是完全相同的。指令字符串中的方法名表明發送端希望運行接收端上的一個方法。
指令消息可分為如下兩類:

  • NetConnection:一個服務器和客戶端之間連接的高層表現對象。
  • NetStream:一個音頻流、視頻流及其他相關數據傳輸流,我們會發送如播放、暫停等指令來控制數據流動。

NetConnection 指令

NetConnection 管理着一個客戶端程序和服務器之間的雙向連接,除此之外,它還提供了對異步遠程方法調用的支持。
下列指令可通過 NetConnection 進行發送:

  • Connect
  • Call
  • Close
  • CreateStream
Connect

客戶端發送 connect 指令至服務器端以請求連接至某一服務器程序實例。
指令結構如下:
27.png

Connect 指令中會用到的鍵值對:
28.png

音頻編碼:
29.png

視頻編碼:
30.png

視頻功能:
31.png

對象編碼:
32.png

由服務器發送至客戶端的指令結構如下:
33.png

指令執行流程:
34.png

指令執行的消息流如下:

  • 客戶端發送 connect 指令至服務器以請求連接至服務器端程序實例。
  • 在收到連接指令后,服務器端發送協議消息 'Window Acknowledgement Size' 給客戶端。同時,服務器端還會連接 connect 指令中提到的應用。
  • 服務器端發送協議消息‘Set Peer Bandwidth’至客戶端。
  • 客戶端成功處理‘Set Peer Bandwidth’后發送協議消息‘Window Acknowledgement Size' 給服務器端。
  • 服務器端發送用戶控制消息(StreamBegin)協議消息給客戶端。
  • 服務器端發送指令消息以通知客戶端連接狀態(success/fail)。該指令中含有處理 ID (與 1 中收到相同),該消息同時還制定了部分屬性,如 Flash Media Server 版本(string)。除此之外,它還指定了連接響應相關的信息如 level (string),code (string),description (string),object-encoding (number) 等。
Call

NetConnection 對象的 call 方法用於遠程調用接收端上的程序。需要遠程調用的程序名稱通過一個參數傳遞給 call 指令。
發送指令結構如下:
35.png

響應指令結構如下:
36.png

CreateStream

客戶端發送該指令至服務器端以創建一條用於傳遞消息的邏輯通道,從而可以利用已創建的流通道發布音頻、視頻和元數據。
NetConnection 是默認的通訊通道,流 ID 為 0。協議和一些指令消息,包括 createStream,使用默認通訊通道。
客戶端發出的指令結構如下:
37.png

服務器發出的指令結構如下:
38.png

NetStream 指令

基於 NetConnection 的客戶端至服務器間連接,NetStream 定義了一條可以傳遞音頻流、視頻流以及消息流的通道。NetConnection 對象支持多個 NetStreams 以傳輸多個數據流。
客戶端可在 NetStream 中發送下列指令至服務器:

  • Play
  • Play2
  • DeleteStream
  • CloseStream
  • ReceiveAudio
  • ReceiveVideo
  • Publish
  • Seek
  • Pause

服務器端通過 “onStatus" 將 NetStream 的狀態更新至客戶端:
39.png

Play

客戶端發送該指令值以播放一個流。多次調用該指令也可創建一個播放清單。
如果你希望創建一個在不同 live 或 recorded 流間切換的動態播放清單,需要多次調用 play 並傳遞 false 以避免每次 reset。相反地,如果你希望立即播放某一指定流,傳遞 true 以清除等待播放隊列中的所有其他流。
客戶端發送的指令結構如下:
40.png

流程圖如下:
41.png

指令執行期間的消息流如下:

  • 客戶端在接收到來自服務器的 createStream 指令的成功結果后發送 play 指令。
  • 在接收到 play 指令后,服務器發送協議數據來設置塊大小。
  • 服務器發送一些另外一個協議數據 (用戶控制),在這個消息里包含事件 “StreamIsRecord” 和流 ID。這個消息的前 2 個字節是事件類型隨后的 4 字節是流 ID。
  • 服務器向客戶端發送另外一個協議消息 (用戶控制),這個消息指示了 “StreamBegin” 事件,表示流開始了。
  • 如果客戶端向服務器發送的 play 指令成功執行了,服務器會發送 onStatus 指令消息包含 NetStream.Play.Start 或 NetStream.Play.Reset。僅當客戶端發送的 play 指令中的設置了 reset 標志 NetStream.Play.Reset 才會被發送。如果播放的流不存在,服務器會在發送 onStatus 消息中包含 NetStream.Play.StreamNotFound。隨后,服務器就發送客戶端播放的音頻和視頻數據。
Play2

不同於 play 指令,play2 可以切換碼率而不改變播放內容的時間軸。服務器為客戶端可以在 play2 中請求的所有支持的碼率維護多個字段。
客戶端發送的指令結構如下:
42.png

該指令的消息流程如下圖:
43.png

DeleteStream

當 NetStream 對象將要被銷毀時,它發送該 deleteStream 指令。
客戶端發送的指令結構如下:
44.png

服務器不需要發送任何應答。

ReceiveAudio

NetStream 發送 ReceiveAudio 消息通知服務器是否發送或不發送音頻到客戶端。
客戶端發送的指令結構如下:
45.png

如果 receiveAudio 指令發送帶有 flase 的 bool flag,服務器不發送任何響應。如果這個標志被設置為 true,服務器應答 NetStream.Seek.Notify 和 NetStream.Play.Start 的狀態消息。

ReceiveVideo

NetStream 發送 ReceiveVideo 消息通知服務器是否發送或不發送視頻到客戶端。
客戶端發送的指令結構如下:
46.png

如果 receiveVideo 指令發送帶有 flase 的 bool flag,服務器不發送任何響應。如果這個標志被設置為 true,服務器應答 NetStream.Seek.Notify 和 NetStream.Play.Start 的狀態消息。

Publish

客戶端發送 publish 指令將已命名的流發布到服務器上。使用這個名稱,任何客戶端都可以播放此流,並接收已發布的音頻、視頻和數據消息。
客戶端發送的指令結構如下:
47.png

服務器應答 onStatus 指令,以標記發布的開始。

Seek

客戶端發送 seek 指令以定位媒體文件內或者播放列表的某個位置(以毫秒為單位)。
客戶端發送的指令結構如下:
48.png

當定位成功,服務器發送 NetStream.Seek.Notify 的狀態消息。失敗的時候,它返回一個_error 的消息。

Pause

客戶端發送 pause 指令以告訴服務器暫停或者開始播放。
客戶端發送的指令結構如下:
49.png

當流被暫停,服務器發送一個 NetStream.Pause.Notify 的狀態消息。當一個流變成未暫停狀態,NetStream.Unpause.Notify 被發送。失敗的時候,它返回一個_error 的消息。

消息交換例子

這里是一些樣例,以解釋使用 RTMP 的消息交換。

發布視頻

這個例子說明了一個發布者如何發布一個流並將視頻流推到服務器上。其他客戶端可以訂閱這個已發布的流,並播放視頻。
50.png

廣播一個共享對象消息

這個例子說明了在創建和更改共享對象時所交換的消息。它也說明了共享對象消息廣播的過程。
51.png

發布媒體流元數據

這個例子描述了發布元數據的消息交換。
52.png

參考內容

[1] RTMP 規范
[2] RTMP 協議規范翻譯工作
[3] RTMP 協議規范 1.0 中文版

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM