作者:逸殊
稽核:泰一
簡介
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 的整數字段來對待。

以下是 C0 和 S0 包的字段解釋:
- 版本号(8 位): 在 C0 包中,該字段表示用戶端請求的 RTMP 版本。在 S0 中,該字段表示伺服器選擇的 RTMP 版本。本規範所定義的版本是 3。可選值中,0-2 是早期版本所用的,已被丢棄,4-31 保留在未來使用,32-255 不允許使用(為了區分其他以某一可見字元開始的文本協定)。如果伺服器不能識别用戶端請求的版本,應該傳回 3,用戶端可能選擇降級到版本 3,也可能放棄握手。
C1 和 S1 格式
C1 和 S1 包固定為 1536 位元組,包含以下字段:
- 時間戳(4 位元組):該字段承載一個時間戳,該時間戳應該作為發送端點所有後續塊的時間戳起始時間。可以是 0,也可以是其他的任意值。為了同步多個塊流,端點可能會發送其他塊流的目前時間戳。
- 零值(4 位元組):該字段所有值都必須為 0。
- 随機資料(1528 位元組):該字段可以是任意值。通過這個字段來區分自己和連接配接的另一方,是以此資料應該有充分的随機性,但是沒必要使用加密安全的随機值或動态值。
C2 和 S2 格式
C2 和 S2 包的長度也為 1536 位元組,基本上是 S1 和 C1 的回傳,包含以下字段:
- 時間戳(4 位元組):該字段必須包含對端發來的時間戳(對 C2 來說是 S1, 對 S2 來說是 C1)。
- 時間 2(4 位元組):該字段必須包含先前發送的并被對端讀取的包的時間戳。(對 C2 來說是 C1,對 S2 來說是 S1)。
- 随機資料回顯(1528 位元組):該字段必須包含對端發送過來的随機資料字段值(對 C2 來說是 S1, 對 S2 來說是 C1)。任何一端都可以用時間戳和時間戳 2 兩個字段值和目前時間戳來快速的估算帶寬和延遲,但這樣可能并不實用。
握手流程示意圖
上圖提到的狀态的解釋如下:
- Uninitialized:未初始化狀态。在該階段發送協定版本。用戶端在 C0 包中發送 RTMP 協定版本,如果伺服器支援此版本,伺服器将在響應中發送 S0 和 S1。如果不支援,伺服器采用适當的行為作為響應,在 RTMP 規範中是終止連接配接。
- Version Send:版本已發送狀态。在未初始化狀态之後用戶端和服務端都進入版本已發送狀态。用戶端等待接收 S1 包,服務端等待接收 C1 包。收到所等待的包後,用戶端發送 C2 包,服務端發送 S2 包。之後狀态進入發送确認狀态。
- Ack Send:用戶端和服務端等待接收 S2 和 C2 包,收到後進入握手完成狀态。
- Handshake Done:握手完成, 用戶端和服務端開始交換消息。
分塊
握手完成後,一個或多個塊流可能會複用同一個連接配接,每個塊流承載來自同一個消息流的同一類消息。每個塊都有一個唯一的塊流 ID,這些塊通過網絡進行傳輸。在傳輸過程中,必須一個塊發送完畢之後再發送下一個塊。在接收端,将所有塊根據塊中的塊流 ID 組裝成消息。
分塊将上層協定的大消息分割成小的消息,保證大的低優先級消息(比如視訊)不阻塞小的高優先級消息(比如音頻或控制消息)。
分塊還能降低消息發送的開銷,它在塊頭中包含了壓縮的原本需要在消息中所包含的資訊。
塊大小是可配置的,這個可以通過一個設定塊大小控制消息進行設定修改。越大的塊 CPU 使用率越低,但是在低帶寬的情況下,大的寫入會阻塞其他内容的寫入。而小一些的塊不适合高比特率的流。
塊格式
每個塊由塊頭和資料組成,塊頭包含 3 部分:基本頭、消息頭和擴充時間戳。
- 基本頭 (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 個位元組來編碼:
64-319 範圍内的塊流 ID 用 2 個位元組來編碼,塊流 ID 為計算所得,公式為:第二個位元組值 + 64:
64-65599 範圍内的塊流 ID 用 3 個位元組來編碼,塊流 ID 為計算所得,公式為:第三個位元組值 * 255 + 第二個位元組值 + 64
上述圖中各個部分的含義如下:
- 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 個位元組長度,該類型必須用在一個塊流的開頭,和每當塊流時間戳回退的時候(例如視訊回退的操作)。
- 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。對于傳輸大小可變消息的流(如多數視訊格式),在發送第一個消息之後的每個消息都應該使用該類型格式。
- timestamp delta (3 位元組): 時間戳增量。類型 1 和類型 2 的塊包含此字段,表示前一個塊的 timestamp 字段和目前塊 timestamp 間的內插補點。 如果時間戳增量大于或等于 16777215 (0xFFFFFF),該字段必須為 16777215,并且必須設定擴充時間戳,來共同表示 32 位的時間戳增量,否則該字段值就是實際的時間戳增量。
類型 2
2 類型的塊消息頭占用 3 個位元組長度,不包含消息流 ID 和消息長度,沿用上一個塊的消息流 ID 和消息長度。對于傳輸固定大小消息的流(如音頻和資料格式),在發送第一個消息之後的每一個消息都應該使用該類型格式。
類型 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
這是一個簡單的音頻流消息,這是示例示範了資訊備援。
下圖展示該消息流以塊流形式發送。從 3 類型塊開始了資料傳輸優化,之後的塊隻附加了一個位元組。
示例 2
該示例展示了一個超過 128 位元組長度的消息,消息被分割成了數個塊。
下圖是被分割成的塊:
第一個塊的頭資訊指明了消息總大小為 307 位元組。
注意這兩個示例,3 類型塊可以在兩種情況下使用。第一種情況是消息拆分成多個塊,另一種情況是新消息複用上一個消息的所有頭部内容。
協定控制消息
RTMP 塊流用消息類型 1,2,3,5 和 6 來作為協定控制消息,這些消息包含 RTMP 塊流協定所需要的資訊。
這些協定控制消息必須用 0 作為消息流 ID (控制流 ID),并在 ID 為 2 的塊流中發送。協定控制消息收到後立即生效,它們的時間戳資訊是被忽略的。
設定塊大小
協定控制消息類型 1:設定塊大小,用于通知另一端新的最大塊大小。
最大塊大小預設為 128 位元組,用戶端或服務端可以修改此值,并用該消息通知另一端。例如,假設一個用戶端想要發送 131 位元組的音頻資料,而最大塊大小為 128。在這種情況下,用戶端可以向服務端發送該消息,通知它最大塊大小被設定為了 131 位元組。這樣用戶端隻用一個塊就可以發送這些音頻資料。
最大塊大小不能小于 1 位元組,通常應該不低于 128 位元組。每個方向上的最大塊大小是獨立的。
- 0 (1 位): 該位必須為 0.
- chunk size (31 位): 該字段以位元組形式儲存新的最大塊大小,該值将用于後續的所有塊的發送,直到收到新的通知。該值可取值範圍為 1-2147483647 (0x7FFFFFFF),但是所有大于 1677215 (0xFFFFFF) 的值都是視作是 16777215,因為任何塊不可能比消息大,而消息長度不能大于 16777215 位元組。
終止消息
協定控制消息類型 2:終止消息,通知正在等待消息後續塊的另一端,可以丢棄指定塊流接收到的資料,塊流 ID 為該消息的載荷。應用可能在關閉的時候發送該消息,用來表明後面的消息沒有必要繼續處理了。
- chunk stream id (32 位元組): 指定消息的塊流 ID。
确認消息
用戶端或伺服器在收到資料總長和視窗大小相等時,通過它回複确認消息。在連接配接建立完成後,消息的發送方會通知接收方一個視窗的大小(指定一個長度),如果接收方收到指定長度的資料後沒有發送回複消息,發送方就不會再發送任何内容了。
- sequence number (32 位元組): 到目前時刻為止接收到的位元組總數。
确認視窗大小
用戶端或服務端發送該消息來通知對端發送确認消息所使用的視窗大小,并等待接收端發送确認消息。接收端在接收到視窗大小後必須發送确認消息。
設定對方傳輸帶寬
用戶端或服務端發送該消息來限制對端的輸出帶寬。接收端收到消息後,可以直接使用消息中指定的視窗大小,而不需要等待收到确認消息之後。如果視窗大小與上一個視窗大小不同,則該消息的接收端應該向該消息的發送端發送新的視窗大小消息。這個消息和上一個消息都是調整視窗大小的,不同的地方是,這個消息是接收者請求發送者,讓它調整視窗大小,而上一個消息是發送者主動設定了視窗大小,通知資料接收者。
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,辨別消息所使用的流,用大端序表示。
消息有效負載
消息的另一部分就是有效負載,也是消息包含的實際資料,可以是音頻樣本或者壓縮的視訊資料。
使用者控制消息
RTMP 協定将消息類型 4 作為使用者控制消息 ID,這些消息包含 RTMP 流所需的必要資訊。消息類型 1,2,3,5 和 6 由 RTMP 塊流協定使用。
使用者控制消息應該使用 ID 為 0 的消息流(控制流),并且通過 RTMP 塊流傳輸時使用 ID 為 2 的塊流。使用者控制消息收到後立即生效,它們的時間戳資訊會被忽略。
用戶端或服務端通過發送該消息告知對方使用者控制事件。該消息攜帶事件類型和事件資料兩部分。
開頭的 2 個位元組用于指定事件類型,緊跟着是事件資料。事件資料字段長度可變,但是如果用 RTMP 塊流傳輸,則消息總長度不能超過最大塊大小,以使消息可以使用一個單獨的塊進行傳輸。
RTMP 指令消息
各種類型的消息在用戶端和服務端之間進行交換,包括用于發送音頻資料的音頻消息,用于發送視訊資料的視訊消息,用于發送任意使用者資料的資料消息,共享對象消息和指令消息等。共享對象消息的主要用途是管理用戶端和相同伺服器的共享資料。指令消息發送的是用戶端與服務端之間的 AMF 編碼指令,用戶端或服務端也可以通過指令消息來實作遠端過程調用(RPC)。
消息類型
用戶端和服務端通過在網絡上發送消息來實作互動,消息可以是任意類型,包括音頻消息、視訊消息、指令消息、共享對象消息、資料消息和使用者控制消息等。
指令消息
指令消息在用戶端和服務端之間傳遞 AMF 編碼的指令,消息類型 20 代表 AMF0 編碼,消息類型 17 代表 AMF3 編碼。發送這些消息來完成連接配接、建立流、釋出、播放、暫停等操作。像狀态、結果這樣的指令消息,用于通知發送方請求的指令狀态。一條指令消息由指令名、事務 ID 和包含相關參數的指令對象組成。用戶端或服務端還可以通過指令消息來實作遠端過程調用 (RPC)。
資料消息
用戶端或服務端通過該消息來發送中繼資料或其他使用者資料。中繼資料包括資料 (音頻、視訊) 的建立時間、時長、主題等詳細資訊。消息類型 18 代表 AMF0 編碼,消息類型 15 代表 AMF3 編碼。
共享對象消息
共享對象是在多個用戶端之間同步的 Flash 對象 (鍵值對集合)。消息類型 19 代表 AMF0 編碼,消息類型 16 代表 AMF3 編碼。每個消息都可以包含多個事件。
支援以下事件類型:
- 建立(1):用戶端向服務端發送,請求建立指定名稱的共享對象。
- 釋放(2):用戶端通知服務端,共享對象已在本地删除。
- 請求更新(3):用戶端請求修改共享對象的屬性值。
- 更新(4):通知服務端向除自己外的其他用戶端發送共享資料消息,通知它們有屬性的值發生了變化。
- 成功(5):“請求更新” 事件被接受後,服務端向發送請求的用戶端回複此事件。
- 發送消息(6):用戶端向服務端發送此事件,來廣播一個消息。服務端收到此事件後向所有用戶端廣播一條消息,包括請求方用戶端。
- 狀态(7):服務端發送此事件來通知用戶端錯誤資訊。
- 清除(8):服務端向用戶端發送此事件,通知用戶端清除一個共享對象。服務端在回複用戶端的 “建立” 事件時也會發送此事件。
- 移除(9):服務端發送此事件,使用戶端删除一個插槽。
- 請求移除(10):用戶端删除一個插槽時發送此事件。
- 建立成功(11):當連接配接成功時服務端向用戶端發送此事件。
音頻消息
用戶端或服務端通過發送此消息來發送音頻資料給對方,消息類型 8 是為音頻消息預留的。
視訊消息
用戶端或服務端通過發送此消息來發送視訊資料給對方,消息類型 9 是為視訊消息預留的。
組合消息
組合消息,是一個消息包含多個子 RTMP 消息,子消息符合 RTMP 消息格式。消息類型 22 用于組合消息。
組合消息的消息流 ID 會覆寫其中子消息的消息流 ID。
組合消息的時間戳和其中第一個子消息的時間戳的內插補點,是用來将所有子消息的時間戳重整為流時間的位移量。位移量會加到每一個子消息的時間戳上來換算出正常的流時間。第一個子消息的時間戳應該與組合消息的時間戳相同,是以位移量應該為 0。
Back Pointer (反向指針) 包含前一個消息的長度(包括消息頭),這樣符合 flv 檔案格式,可用于進行後退操作。
使用組合消息有以下好處:
- 塊流協定中,一個塊最多隻能發送一個消息,這樣就使用組合消息,加大塊大小,進而降低發送的塊數量。
- 子消息在記憶體中連續存放,這樣系統調用網絡發送資料的性能更高。
使用者控制消息事件
用戶端或伺服器通過該消息發送使用者控制事件。
使用者控制消息支援以下事件:
- 流開始(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 指令中會用到的鍵值對:
音頻編碼:
視訊編碼:
視訊功能:
對象編碼:
由伺服器發送至用戶端的指令結構如下:
指令執行流程:
指令執行的消息流如下:
- 用戶端發送 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) 等。
NetConnection 對象的 call 方法用于遠端調用接收端上的程式。需要遠端調用的程式名稱通過一個參數傳遞給 call 指令。
發送指令結構如下:
響應指令結構如下:
用戶端發送該指令至伺服器端以建立一條用于傳遞消息的邏輯通道,進而可以利用已建立的流通道釋出音頻、視訊和中繼資料。
NetConnection 是預設的通訊通道,流 ID 為 0。協定和一些指令消息,包括 createStream,使用預設通訊通道。
用戶端發出的指令結構如下:
伺服器發出的指令結構如下:
NetStream 指令
基于 NetConnection 的用戶端至伺服器間連接配接,NetStream 定義了一條可以傳遞音頻流、視訊流以及消息流的通道。NetConnection 對象支援多個 NetStreams 以傳輸多個資料流。
用戶端可在 NetStream 中發送下列指令至伺服器:
- Play
- Play2
- DeleteStream
- CloseStream
- ReceiveAudio
- ReceiveVideo
- Publish
- Seek
- Pause
伺服器端通過 “onStatus" 将 NetStream 的狀态更新至用戶端:
用戶端發送該指令值以播放一個流。多次調用該指令也可建立一個播放清單。
如果你希望建立一個在不同 live 或 recorded 流間切換的動态播放清單,需要多次調用 play 并傳遞 false 以避免每次 reset。相反地,如果你希望立即播放某一指定流,傳遞 true 以清除等待播放隊列中的所有其他流。
用戶端發送的指令結構如下:
流程圖如下:
指令執行期間的消息流如下:
- 用戶端在接收到來自伺服器的 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。随後,伺服器就發送用戶端播放的音頻和視訊資料。
不同于 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 中文版「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。