RTMP協議學習——Message與Chunk解讀

HolyZion發表於2023-12-02

前言

之前透過對抓包資料的學習和分析,對RTMP協議有了一個整體的認知,大致瞭解了RTMP從建立連線到播放影片的流程,文章請看《RTMP協議學習——從握手到播放》。但是對於RTMP訊息傳輸的載體還沒有過多的分析。本文將會就RTMP的傳輸資料方面,對RTMP協議進行進一步的研究和學習。

Message

Message是RTMP協議中的基本資料單元。訊息可以包含音訊,影片,控制訊息,以及其它資料。

Message的格式

RTMP的訊息由兩部分構成,分別是header和payload。

  • Message Type (訊息型別):一個位元組的欄位來表示訊息型別。型別 ID 1 - 6 被保留用於協議控制訊息。

  • Length (長度):三個位元組的欄位來表示有效負載的位元組數。以大端格式儲存。

  • Timestamp:四個位元組的欄位包含了當前訊息的 timestamp。四個位元組也以大端格式儲存。以毫秒為單位,時間戳用於同步音影片播放以及計算延遲。

  • Message Stream Id (訊息流 ID):三個位元組的欄位以指示出當前訊息的流。這三個位元組以大端格式儲存。

Payload

payload中是這個訊息所包含的實際內容,一般是音訊或者影片等資訊。

Chunk

訊息是透過訊息塊(Chunk)進行分塊傳輸的。Chunk 是 RTMP 傳輸的基本單位,用於將訊息拆分成小塊進行傳輸。建立的每個塊都有一個唯一 ID 對其進行關聯,這個 ID 叫做 chunk stream ID。
將Message拆成Chunk進行傳輸有以下幾個原因:

  • 流控制: 拆分成 Chunk 可以更好地進行流控制。透過將訊息拆分成小塊,可以根據網路狀況和接收方的處理能力來調整 Chunk 的大小。這有助於防止網路擁塞和提高整體效能。

  • 實時傳輸: RTMP 主要應用在實時傳輸場景,如音影片直播。拆分成 Chunk 允許更快地將部分訊息傳輸到接收方,從而降低整體的延遲。

  • 靈活性: 拆分成 Chunk 提供了更大的靈活性,允許在傳輸中插入其他訊息或進行其他控制。這對於複雜的實時通訊協議來說是非常重要的。

  • 錯誤恢復: 如果發生傳輸錯誤,僅僅需要重新傳輸丟失或損壞的 Chunk,而不需要重新傳輸整個訊息。這提高了系統的容錯性。

  • 封裝協議的設計: RTMP 使用 Chunk 進行訊息的封裝,這種設計可以更好地適應不同的應用場景和網路環境。

Chunk的格式

每個chunk由header和chunk data組成,如下圖所示:

Chunk Header

Basic Header

Basic Header對塊流 ID 和塊型別進行編碼。
其中Basic Header的第一個位元組的前兩位為塊型別(fmt)。塊型別一共有四種,決定了Message Header的型別選擇。
塊基本頭欄位可能會有 1,2 或者 3 個位元組,取決於chunk stream ID,對應關係如下表:

塊流 ID 位元組數 格式
2 - 63 1
64 - 319 2
64 - 65599 3
Message Header

塊訊息頭有四種不同的格式,由Basic Header中的塊型別進行選擇。

型別 位元組數 格式
0 11
1 7
2 3
3 0
  • Type0
    • 用於塊流的開始和時間戳後退的情況下。
    • timestamp最大為16777215,即2²⁴-1。
  • Type1
    • 不包含message stream id
    • 這一塊使用前一塊一樣的流 ID。可變長度訊息的流 (例如,一些影片格式) 應該在第一塊之後使用這一格式表示之後的每個新訊息。
  • Type2
    • 不包含stream ID和message length。
    • 具有和前一塊相同的流 ID 和訊息長度。具有不變長度的訊息 (例如,一些音訊和資料格式) 應該在第一塊之後使用這一格式表示之後的每個新訊息。
  • Type3
    • 沒有訊息頭。流 ID、訊息長度以及 timestamp delta 等欄位都不存在。
    • 使用前面塊一樣的塊流 ID。
    • 當單一一個訊息被分割為多塊時,除了第一塊的其他塊都應該使用這種型別。
Extended Timestamp

用於對大於 16777215的 timestamp 或者 timestamp delta 進行編碼。可以透過設定型別 0 塊的 timestamp 欄位、型別 1 或者 2 塊的 timestamp delta 欄位 16777215 (0xFFFFFF) 來啟用這一欄位。當最近的具有同一塊流的型別 0、1 或 2 塊指示擴充套件extended timestamp 欄位出現時,這一欄位才會在型別為 3 的塊中出現。
這樣設計的目的是在滿足大多數情況下使用更緊湊的資料表示,而只在必要時才使用額外的位元組。

Chunk Data

當前塊的有效負載,相當於定義的最大塊大小。

Message型別

命令訊息 (20, 17)

命令訊息在客戶端和伺服器之間攜帶AMF編碼的命令。這些訊息的AMF0編碼的訊息型別值為20,AMF3編碼的訊息型別值為17。傳送這些訊息來執行一些操作,如連線、建立流、釋出、播放、暫停對等點。命令訊息,如線上狀態,結果等。用於通知發件人所請求的命令的狀態。命令訊息由命令名稱、事務ID和包含相關引數的命令物件組成。客戶端或伺服器可以透過使用命令訊息向對等點進行通訊的流來請求遠端過程呼叫(RPC)。

資料訊息 (18, 15)

客戶端或伺服器傳送此訊息以傳送後設資料或任何使用者資料到對等資料。後設資料包括有關資料的詳細資訊(音訊、影片等)。比如創作時間,持續時間,主題等等。AMF0的訊息型別值為18,AMF3的訊息型別值為15。

共享物件訊息 (19, 16)

共享物件是一個Flash物件(名稱值對的集合),它在多個客戶端、例項等之間進行同步。AMF0的訊息型別19和AMF3的訊息型別16為共享物件事件保留。每個訊息都可以包含多個事件。

支援以下事件型別:

事件 描述
Use (=1) 客戶端傳送此事件以通知伺服器關於已命名共享物件的建立
Release (=2) 當在客戶端刪除共享物件時,客戶端將此事件傳送給伺服器。
Request Change (=3) 客戶端傳送此事件以請求更改與共享物件的命名引數關聯的值
Change (=4) 伺服器傳送此事件以通知所有客戶端(發出請求的客戶端除外)指定引數值的更改。
Success (=5) 如果請求被接受,伺服器將傳送此事件給請求客戶機作為RequestChange事件的響應。
SendMessage (=6) 客戶端將此事件傳送到伺服器以廣播訊息。在接收到此事件時,伺服器向所有客戶機廣播一條訊息,包括髮送方。
Status (=7) 伺服器傳送此事件以通知客戶端有關錯誤情況。
Clear (=8) 伺服器向客戶端傳送此事件以清除共享物件。伺服器還傳送此事件以響應客戶端在連線時傳送的Use事件。
Remove (=9) 伺服器傳送此事件以讓客戶端刪除插槽。
Requese Remove (=10) 客戶端傳送此事件以使客戶端刪除槽。
Use Success (=11) 在連線成功時,伺服器將此事件傳送給客戶端。

音訊訊息 (8)

客戶端或伺服器傳送此訊息向音訊資料傳送到對等點。訊息型別值8將保留給音訊訊息。

影片訊息 (9)

客戶端或伺服器傳送此訊息以向對等點傳送影片資料。訊息型別值9將保留給影片訊息。

聚合訊息 (22)

聚合訊息是包含一系列RTMP子訊息。訊息型別22用於聚合訊息。


聚合訊息的訊息流ID將覆蓋聚合內部的子訊息的訊息流ID。聚合訊息和第一個子訊息的時間戳之間的差異是用於將子訊息的時間戳重新規格化到流時間尺度的偏移量。該偏移量被新增到每個子訊息的時間戳中,以到達規範化的流時間。第一個子訊息的時間戳應該與聚合訊息的時間戳相同,因此偏移量應該為零。返回的指標包含前一條訊息的大小,包括它的報頭。它被包括來匹配FLV檔案的格式,並用於向後查詢。
使用聚合訊息有幾個效能好處:

  • 塊流最多可以在一個塊中傳送一條完整的訊息。因此,增加塊的大小並使用聚合訊息可以減少傳送的塊的數量。
  • 子訊息可以連續地儲存在記憶體中。當進行系統呼叫以在網路上傳送資料時,其效率更高

使用者控制訊息事件

客戶端或伺服器傳送此訊息以通知對等點有關使用者控制事件。
支援以下使用者控制事件型別:

事件 描述
Stream Begin (=0) 伺服器傳送此事件以通知客戶端流已變得可用並可用於通訊。 預設情況下,成功從客戶端接收到應用程式連線命令後,此事件在 ID 0 上傳送。 事件資料是 4 位元組,表示開始執行的流的流 ID。
Stream EOF (=1) 伺服器傳送此事件以通知客戶端該流上的資料播放已按請求結束。 如果不發出附加命令,則不會傳送更多資料。 客戶端丟棄為流接收的訊息。 事件資料的 4 個位元組表示播放已結束的流的 ID。
StreamDry (=2) 伺服器傳送此事件以通知客戶端流上沒有更多資料。 如果伺服器在一段時間內沒有檢測到任何訊息,它可以通知訂閱的客戶端流已幹。 4個位元組的事件資料代表幹流的流ID。
SetBuffer Length (=3) 客戶端傳送此事件以通知伺服器用於緩衝流中傳入的任何資料的緩衝區大小(以毫秒為單位)。此事件在伺服器開始處理流之前傳送。 事件資料的前 4 個位元組表示流 ID,接下來的 4 個位元組表示緩衝區長度(以毫秒為單位)。
StreamIs Recorded (=4) 伺服器傳送此事件以通知客戶端該流是錄製流。 4 位元組事件資料代表錄製流的流 ID。
PingRequest (=6) 伺服器傳送此事件來測試客戶端是否可達。 事件資料是一個 4 位元組時間戳,表示伺服器傳送命令時的本地伺服器時間。客戶端在收到 MsgPingRequest 後以 PingResponse 進行響應。
PingResponse (=7) 客戶端將此事件傳送到伺服器以響應 ping 請求。 事件資料是 4 位元組時間戳,隨 Ping Request 請求一起接收。

總結

在 RTMP(Real-Time Messaging Protocol)中,訊息(message)和分塊(chunk)是兩個重要的概念。

  • 訊息(Message): RTMP 的通訊單元被稱為訊息。訊息包含了一些後設資料以及實際的音影片資料。這些訊息可以分為音訊訊息、影片訊息、命令訊息等,具體的訊息型別由訊息頭中的訊息型別欄位指定。
  • 分塊(Chunk): RTMP 使用分塊的方式將訊息拆分成更小的資料塊。分塊主要是為了支援實時性,將大的訊息拆分成小的塊,可以更快地傳輸,響應時間更短。每個分塊都有自己的訊息頭,其中包含了塊的長度、訊息型別等資訊。在一個 RTMP 連線中,可以有多個訊息流,每個訊息流有自己的 ID。
    分塊的基本結構如下:
    • Basic Header(基本頭): 用於指示分塊的格式和長度。
    • Message Header(訊息頭): 包含訊息的時間戳、訊息長度等資訊。
    • Extended Timestamp(擴充套件時間戳): 當訊息頭中的時間戳欄位無法滿足表示實際時間戳時,使用擴充套件時間戳欄位。
      這樣的設計使得 RTMP 能夠在不同的網路條件下,提供較好的實時性和流暢性。

總的來說,訊息是 RTMP 通訊的邏輯單元,而分塊是為了提高實時性而採取的物理拆分手段。


參考連結

https://rtmp.veriskope.com/pdf/rtmp_specification_1.0.pdf

相關文章