RTMP協議是Real Time Message Protocol(實時資訊傳輸協議)的縮寫,它是由Adobe公司提出的一種應用層的協議,用來解決多媒體資料傳輸流的多路複用(Multiplexing)和分包(packetizing)的問題。隨著VR技術的發展,視訊直播等領域逐漸活躍起來,RTMP作為業內廣泛使用的協議也重新被相關開發者重視起來。
1. 總體介紹
RTMP協議是應用層協議,是要靠底層可靠的傳輸層協議(通常是TCP)來保證資訊傳輸的可靠性的。在基於傳輸層協議的連結建立完成後,RTMP協議也要客戶端和伺服器通過“握手”來建立基於傳輸層連結之上的RTMP Connection連結,在Connection連結上會傳輸一些控制資訊,如SetChunkSize,SetACKWindowSize。其中CreateStream命令會建立一個Stream連結,用於傳輸具體的音視訊資料和控制這些資訊傳輸的命令資訊。RTMP協議傳輸時會對資料做自己的格式化,這種格式的訊息我們稱之為RTMP Message,而實際傳輸的時候為了更好地實現多路複用、分包和資訊的公平性,傳送端會把Message劃分為帶有Message ID的Chunk,每個Chunk可能是一個單獨的Message,也可能是Message的一部分,在接受端會根據chunk中包含的data的長度,message id和message的長度把chunk還原成完整的Message,從而實現資訊的收發。
2. 握手
要建立一個有效的RTMP Connection連結,首先要“握手”:客戶端要向伺服器傳送C0,C1,C2(按序)三個chunk,伺服器向客戶端傳送S0,S1,S2(按序)三個chunk,然後才能進行有效的資訊傳輸。RTMP協議本身並沒有規定這6個Message的具體傳輸順序,但RTMP協議的實現者需要保證這幾點:
-
客戶端要等收到S1之後才能傳送C2
-
客戶端要等收到S2之後才能傳送其他資訊(控制資訊和真實音視訊等資料)
-
服務端要等到收到C0之後傳送S1
-
服務端必須等到收到C1之後才能傳送S2
-
服務端必須等到收到C2之後才能傳送其他資訊(控制資訊和真實音視訊等資料)
如果每次傳送一個握手chunk的話握手順序會是這樣:
|client|Server |
|---C0+C1—->|
|<--S0+S1+S2– |
|---C2----> |
3. RTMP Chunk Stream
3.1 Message(訊息)
-
Timestamp(時間戳):訊息的時間戳(但不一定是當前時間,後面會介紹),4個位元組
-
Length(長度):是指Message Payload(訊息負載)即音視訊等資訊的資料的長度,3個位元組
-
TypeId(型別Id):訊息的型別Id,1個位元組
-
Message Stream ID(訊息的流ID):每個訊息的唯一標識,劃分成Chunk和還原Chunk為Message的時候都是根據這個ID來辨識是否是同一個訊息的Chunk的,4個位元組,並且以小端格式儲存
3.2 Chunking(Message分塊)
Chunk的預設大小是128位元組,在傳輸過程中,通過一個叫做Set Chunk Size的控制資訊可以設定Chunk資料量的最大值,在傳送端和接受端會各自維護一個Chunk Size,可以分別設定這個值來改變自己這一方傳送的Chunk的最大大小。大一點的Chunk減少了計算每個chunk的時間從而減少了CPU的佔用率,但是它會佔用更多的時間在傳送上,尤其是在低頻寬的網路情況下,很可能會阻塞後面更重要資訊的傳輸。
3.3 Chunk Format(塊格式)
包含了chunk stream ID(流通道Id)和chunk type(chunk的型別),chunk stream id一般被簡寫為CSID,用來唯一標識一個特定的流通道,chunk type決定了後面Message Header的格式。Basic Header的長度可能是1,2,或3個位元組,其中chunk type的長度是固定的(佔2位,注意單位是位,bit),Basic Header的長度取決於CSID的大小,在足夠儲存這兩個欄位的前提下最好用盡量少的位元組從而減少由於引入Header增加的資料量。
RTMP協議支援使用者自定義[3,65599]之間的CSID,0,1,2由協議保留表示特殊資訊。0代表Basic Header總共要佔用2個位元組,CSID在[64,319]之間,1代表佔用3個位元組,CSID在[64,65599]之間,2代表該chunk是控制資訊和一些命令資訊,後面會有詳細的介紹。
chunk type的長度固定為2位,因此CSID的長度是(6=8-2)、(14=16-2)、(22=24-2)中的一個。
當Basic Header為1個位元組時,CSID佔6位,6位最多可以表示64個數,因此這種情況下CSID在[0,63]之間,其中使用者可自定義的範圍為[3,63]。
3.3.2 Message Header(訊息的頭資訊)
包含了要傳送的實際資訊(可能是完整的,也可能是一部分)的描述資訊。Message Header的格式和長度取決於Basic Header的chunk type,共有4種不同的格式,由上面所提到的Basic Header中的fmt欄位控制。其中第一種格式可以表示其他三種表示的所有資料,但由於其他三種格式是基於對之前chunk的差量化的表示,因此可以更簡潔地表示相同的資料,實際使用的時候還是應該採用儘量少的位元組表示相同意義的資料。
Type=0:
-
timestamp(時間戳):佔用3個位元組,因此它最多能表示到16777215=0xFFFFFF=2
24-1, 當它的值超過這個最大值時,這三個位元組都置為1,這樣實際的timestamp會轉存到Extended Timestamp欄位中,接受端在判斷timestamp欄位24個位都為1時就會去Extended timestamp中解析實際的時間戳。 -
message length(訊息資料的長度):佔用3個位元組,表示實際傳送的訊息的資料如音訊幀、視訊幀等資料的長度,單位是位元組。注意這裡是Message的長度,也就是chunk屬於的Message的總資料長度,而不是chunk本身Data的資料的長度。
-
message type id(訊息的型別id):佔用1個位元組,表示實際傳送的資料的型別,如8代表音訊資料、9代表視訊資料。
-
msg stream id(訊息的流id):佔用4個位元組,表示該chunk所在的流的ID,和Basic Header的CSID一樣,它採用小端儲存的方式,
Type = 1:
timestamp delta:佔用3個位元組,注意這裡和type=0時不同,儲存的是和上一個chunk的時間差。類似上面提到的timestamp,當它的值超過3個位元組所能表示的最大值時,三個位元組都置為1,實際的時間戳差值就會轉存到Extended Timestamp欄位中,接受端在判斷timestamp delta欄位24個位都為1時就會去Extended timestamp中解析時機的與上次時間戳的差值。
0位元組!!!好吧,它表示這個chunk的Message Header和上一個是完全相同的,自然就不用再傳輸一遍了。當它跟在Type=0的chunk後面時,表示和前一個chunk的時間戳都是相同的。什麼時候連時間戳都相同呢?就是一個Message拆分成了多個chunk,這個chunk和上一個chunk同屬於一個Message。而當它跟在Type=1或者Type=2的chunk後面時,表示和前一個chunk的時間戳的差是相同的。比如第一個chunk的Type=0,timestamp=100,第二個chunk的Type=2,timestamp delta=20,表示時間戳為100+20=120,第三個chunk的Type=3,表示timestamp delta=20,時間戳為120+20=140
上面我們提到在chunk中會有時間戳timestamp和時間戳差timestamp delta,並且它們不會同時存在,只有這兩者之一大於3個位元組能表示的最大數值0xFFFFFF=16777215時,才會用這個欄位來表示真正的時間戳,否則這個欄位為0。擴充套件時間戳佔4個位元組,能表示的最大數值就是0xFFFFFFFF=4294967295。當擴充套件時間戳啟用時,timestamp欄位或者timestamp delta要全置為1,表示應該去擴充套件時間戳欄位來提取真正的時間戳或者時間戳差。注意擴充套件時間戳儲存的是完整值,而不是減去時間戳或者時間戳差的值。
3.3.4 Chunk Data(塊資料)
使用者層面上真正想要傳送的與協議無關的資料,長度在(0,chunkSize]之間。
3.3.5 chunk表示例1
第二個chunk和第一個chunk的CSID,TypeId,Data的長度都相同,因此採用Chunk Type=2,timestamp delta=1020-1000=20,因此第二個chunk佔用36=3+1+32個位元組。
第三個chunk和第二個chunk的CSID,TypeId,Data的長度和時間戳差都相同,因此採用Chunk Type=3省去全部Message Header的資訊,佔用33=1+32個位元組。
第四個chunk和第三個chunk情況相同,也佔用33=1+32個位元組。
最後實際傳送的chunk如下:
第二個chunk也要傳送128個位元組,其他欄位也同第一個chunk,因此採用Chunk Type=3,此時時間戳也為1000,共佔用129=1+128個位元組。
第三個chunk要傳送的Data的長度為307-128-128=51個位元組,還是採用Type=3,共佔用1+51=52個位元組。
最後實際傳送的chunk如下:
3.4 協議控制訊息(Protocol Control Message)
在RTMP的chunk流會用一些特殊的值來代表協議的控制訊息,它們的Message Stream ID必須為0(代表控制流資訊),CSID必須為2,Message Type ID可以為1,2,3,5,6,具體代表的訊息會在下面依次說明。控制訊息的接受端會忽略掉chunk中的時間戳,收到後立即生效。
-
Set Chunk Size(Message Type ID=1):設定chunk中Data欄位所能承載的最大位元組數,預設為128B,通訊過程中可以通過傳送該訊息來設定chunk Size的大小(不得小於128B),而且通訊雙方會各自維護一個chunkSize,兩端的chunkSize是獨立的。比如當A想向B傳送一個200B的Message,但預設的chunkSize是128B,因此就要將該訊息拆分為Data分別為128B和72B的兩個chunk傳送,如果此時先傳送一個設定chunkSize為256B的訊息,再傳送Data為200B的chunk,本地不再劃分Message,B接受到Set Chunk Size的協議控制訊息時會調整的接受的chunk的Data的大小,也不用再將兩個chunk組成為一個Message。
以下為代表Set Chunk Size訊息的chunk的Data:
-
Abort Message(Message Type ID=2):當一個Message被切分為多個chunk,接受端只接收到了部分chunk時,傳送該控制訊息表示傳送端不再傳輸同Message的chunk,接受端接收到這個訊息後要丟棄這些不完整的chunk。Data資料中只需要一個CSID,表示丟棄該CSID的所有已接收到的chunk。
-
Acknowledgement(Message Type ID=3):當收到對端的訊息大小等於視窗大小(Window Size)時接受端要回饋一個ACK給傳送端告知對方可以繼續傳送資料。視窗大小就是指收到接受端返回的ACK前最多可以傳送的位元組數量,返回的ACK中會帶有從傳送上一個ACK後接收到的位元組數。
-
Window Acknowledgement Size(Message Type ID=5):傳送端在接收到接受端返回的兩個ACK間最多可以傳送的位元組數。
-
Set Peer Bandwidth(Message Type ID=6):限制對端的輸出頻寬。接受端接收到該訊息後會通過設定訊息中的Window ACK Size來限制已傳送但未接受到反饋的訊息的大小來限制傳送端的傳送頻寬。如果訊息中的Window ACK Size與上一次傳送給傳送端的size不同的話要回饋一個Window Acknowledgement Size的控制訊息。
-
Hard(Limit Type=0):接受端應該將Window Ack Size設定為訊息中的值
-
Soft(Limit Type=1):接受端可以講Window Ack Size設為訊息中的值,也可以儲存原來的值(前提是原來的Size小與該控制訊息中的Window Ack Size)
-
Dynamic(Limit Type=2):如果上次的Set Peer Bandwidth訊息中的Limit Type為0,本次也按Hard處理,否則忽略本訊息,不去設定Window Ack Size。
4. 不同型別的RTMP Message
– Command Message(命令訊息,Message Type ID=17或20)
表示在客戶端盒伺服器間傳遞的在對端執行某些操作的命令訊息,如connect表示連線對端,對端如果同意連線的話會記錄傳送端資訊並返回連線成功訊息,publish表示開始向對方推流,接受端接到命令後準備好接受對端傳送的流資訊,後面會對比較常見的Command Message具體介紹。當資訊使用AMF0編碼時,Message Type ID=20,AMF3編碼時Message Type ID=17.
– Data Message(資料訊息,Message Type ID=15或18):傳遞一些後設資料(MetaData,比如視訊名,解析度等等)或者使用者自定義的一些訊息。當資訊使用AMF0編碼時,Message Type ID=18,AMF3編碼時Message Type ID=15.
– Shared Object Message(共享訊息,Message Type ID=16或19):表示一個Flash型別的物件,由鍵值對的集合組成,用於多客戶端,多例項時使用。當資訊使用AMF0編碼時,Message Type ID=19,AMF3編碼時Message Type ID=16.
– Audio Message(音訊資訊,Message Type ID=8):音訊資料。
– Video Message(視訊資訊,Message Type ID=9):視訊資料。
– Aggregate Message (聚集資訊,Message Type ID=22):多個RTMP子訊息的集合
– User Control Message Events(使用者控制訊息,Message Type ID=4):告知對方執行該資訊中包含的使用者控制事件,比如Stream Begin事件告知對方流資訊開始傳輸。和前面提到的協議控制資訊(Protocol Control Message)不同,這是在RTMP協議層的,而不是在RTMP chunk流協議層的,這個很容易弄混。該資訊在chunk流中傳送時,Message Stream ID=0,Chunk Stream Id=2,Message Type Id=4。
———下面對以上7種資訊具體介紹———-
4.1 Command Message(命令訊息,Message Type ID=17或20)
傳送端傳送時會帶有命令的名字,如connect,TransactionID表示此次命令的標識,Command Object表示相關引數。接受端收到命令後,會返回以下三種訊息中的一種:_result 訊息表示接受該命令,對端可以繼續往下執行流程,_error訊息代表拒絕該命令要執行的操作,method name訊息代表要在之前命令的傳送端執行的函式名稱。這三種回應的訊息都要帶有收到的命令訊息中的TransactionId來表示本次的回應作用於哪個命令。
可以認為傳送命令訊息的物件有兩種,一種是NetConnection,表示雙端的上層連線,一種是NetStream,表示流資訊的傳輸通道,控制流資訊的狀態,如Play播放流,Pause暫停。
4.1.1 NetConnection Commands(連線層的命令)
用來管理雙端之間的連線狀態,同時也提供了非同步遠端方法呼叫(RPC)在對端執行某方法,以下是常見的連線層的命令:
4.1.1.1 connect:用於客戶端向伺服器傳送連線請求,訊息的結構如下:
訊息的回應有兩種,_result表示接受連線,_error表示連線失敗
以下會列出一些常用的NetStream Commands,服務端收到命令後會通過onStatus的命令來響應客戶端,表示當前NetStream的狀態。
onStatus命令的訊息結構如下:
5. 代表流程
5.1 推流流程
5.2 播流流程
6. 新手建議
新人一開始接觸RTMP的時候肯定會覺得頭大,這也是RTMP協議不簡潔的後果。建議讀者在學習時先過一遍協議理解大概的概念和流程,然後對照wireshark抓的包,和協議進行比對,這樣將理論和實踐結合,應該會理解的更快一點。
——————— 本文來自 會敲程式碼的咩 的CSDN 部落格