RTMP詳解

weixin_34054866發表於2018-03-08

RTMP協議是Real Time Message Protocol(實時資訊傳輸協議)的縮寫,它是由Adobe公司提出的一種應用層的協議,用來解決多媒體資料傳輸流的多路複用(Multiplexing)和分包(packetizing)的問題。隨著VR技術的發展,視訊直播等領域逐漸活躍起來,RTMP作為業內廣泛使用的協議也重新被相關開發者重視起來。正好最近在從事這方面的工作,在此記錄下自己對RTMP的理解,文章內容多翻譯自英文版RTMP文件,按照本人的理解重新整理,希望可以幫助想要了解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的話握手順序會是這樣:

5433968-81e99073011c1b48..png

理論上來講只要滿足以上條件,如何安排6個Message的順序都是可以的,但實際實現中為了在保證握手的身份驗證功能的基礎上儘量減少通訊的次數,一般的傳送順序是這樣的,這一點可以通過wireshark抓ffmpeg推流包進行驗證:

|client|Server |

|---C0+C1—->|

|<--S0+S1+S2– |

|---C2----> |

3. RTMP Chunk Stream

Chunk Stream是對傳輸RTMP Chunk的流的邏輯上的抽象,客戶端和伺服器之間有關RTMP的資訊都在這個流上通訊。這個流上的操作也是我們關注RTMP協議的重點。

3.1 Message(訊息)

這裡的Message是指滿足該協議格式的、可以切分成Chunk傳送的訊息,訊息包含的欄位如下:

Timestamp(時間戳):訊息的時間戳(但不一定是當前時間,後面會介紹),4個位元組

Length(長度):是指Message Payload(訊息負載)即音視訊等資訊的資料的長度,3個位元組

TypeId(型別Id):訊息的型別Id,1個位元組

Message Stream ID(訊息的流ID):每個訊息的唯一標識,劃分成Chunk和還原Chunk為Message的時候都是根據這個ID來辨識是否是同一個訊息的Chunk的,4個位元組,並且以小端格式儲存

3.2 Chunking(Message分塊)

RTMP在收發資料的時候並不是以Message為單位的,而是把Message拆分成Chunk傳送,而且必須在一個Chunk傳送完成之後才能開始傳送下一個Chunk。每個Chunk中帶有MessageID代表屬於哪個Message,接受端也會按照這個id來將chunk組裝成Message。

為什麼RTMP要將Message拆分成不同的Chunk呢?通過拆分,資料量較大的Message可以被拆分成較小的“Message”,這樣就可以避免優先順序低的訊息持續傳送阻塞優先順序高的資料,比如在視訊的傳輸過程中,會包括視訊幀,音訊幀和RTMP控制資訊,如果持續傳送音訊資料或者控制資料的話可能就會造成視訊幀的阻塞,然後就會造成看視訊時最煩人的卡頓現象。同時對於資料量較小的Message,可以通過對Chunk Header的欄位來壓縮資訊,從而減少資訊的傳輸量。(具體的壓縮方式會在後面介紹)

Chunk的預設大小是128位元組,在傳輸過程中,通過一個叫做Set Chunk Size的控制資訊可以設定Chunk資料量的最大值,在傳送端和接受端會各自維護一個Chunk Size,可以分別設定這個值來改變自己這一方傳送的Chunk的最大大小。大一點的Chunk減少了計算每個chunk的時間從而減少了CPU的佔用率,但是它會佔用更多的時間在傳送上,尤其是在低頻寬的網路情況下,很可能會阻塞後面更重要資訊的傳輸。小一點的Chunk可以減少這種阻塞問題,但小的Chunk會引入過多額外的資訊(Chunk中的Header),少量多次的傳輸也可能會造成傳送的間斷導致不能充分利用高頻寬的優勢,因此並不適合在高位元率的流中傳輸。在實際傳送時應對要傳送的資料用不同的Chunk Size去嘗試,通過抓包分析等手段得出合適的Chunk大小,並且在傳輸過程中可以根據當前的頻寬資訊和實際資訊的大小動態調整Chunk的大小,從而儘量提高CPU的利用率並減少資訊的阻塞機率。

3.3 Chunk Format(塊格式)

5433968-9d2371dbab08a24c..png

3.3.1 Basic Header(基本的頭資訊):

包含了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]。

5433968-0c5550307e27f5da..png

當Basic Header為2個位元組時,CSID佔14位,此時協議將與chunk type所在位元組的其他位都置為0,剩下的一個位元組來表示CSID-64,這樣共有8個二進位制位來儲存CSID,8位可以表示[0,255]共256個數,因此這種情況下CSID在[64,319],其中319=255+64。

5433968-2319f6405ea8f275..png

當Basic Header為3個位元組時,CSID佔22位,此時協議將[2,8]位元組置為1,餘下的16個位元組表示CSID-64,這樣共有16個位來儲存CSID,16位可以表示[0,65535]共65536個數,因此這種情況下CSID在[64,65599],其中65599=65535+64,需要注意的是,Basic Header是採用小端儲存的方式,越往後的位元組數量級越高,因此通過這3個位元組每一位的值來計算CSID時,應該是:<第三個位元組的值>x256+<第二個位元組的值>+64

5433968-7a7c1a4a522f92f8..png

可以看到2個位元組和3個位元組的Basic Header所能表示的CSID是有交集的[64,319],但實際實現時還是應該秉著最少位元組的原則使用2個位元組的表示方式來表示[64,319]的CSID。

3.3.2 Message Header(訊息的頭資訊)

包含了要傳送的實際資訊(可能是完整的,也可能是一部分)的描述資訊。Message Header的格式和長度取決於Basic Header的chunk type,共有4種不同的格式,由上面所提到的Basic Header中的fmt欄位控制。其中第一種格式可以表示其他三種表示的所有資料,但由於其他三種格式是基於對之前chunk的差量化的表示,因此可以更簡潔地表示相同的資料,實際使用的時候還是應該採用儘量少的位元組表示相同意義的資料。以下按照位元組數從多到少的順序分別介紹這4種格式的Message Header。

Type=0:

5433968-0ebebbd87f136a82..png

type=0時Message Header佔用11個位元組,其他三種能表示的資料它都能表示,但在chunk stream的開始的第一個chunk和頭資訊中的時間戳後退(即值與上一個chunk相比減小,通常在回退播放的時候會出現這種情況)的時候必須採用這種格式。

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:

5433968-6dabd6f8076941df..png

type=1時Message Header佔用7個位元組,省去了表示msg stream id的4個位元組,表示此chunk和上一次發的chunk所在的流相同,如果在傳送端只和對端有一個流連結的時候可以儘量去採取這種格式。

timestamp delta:佔用3個位元組,注意這裡和type=0時不同,儲存的是和上一個chunk的時間差。類似上面提到的timestamp,當它的值超過3個位元組所能表示的最大值時,三個位元組都置為1,實際的時間戳差值就會轉存到Extended Timestamp欄位中,接受端在判斷timestamp delta欄位24個位都為1時就會去Extended timestamp中解析時機的與上次時間戳的差值。

Type = 2:

5433968-707f0960e452d235..png

type=2時Message Header佔用3個位元組,相對於type=1格式又省去了表示訊息長度的3個位元組和表示訊息型別的1個位元組,表示此chunk和上一次傳送的chunk所在的流、訊息的長度和訊息的型別都相同。餘下的這三個位元組表示timestamp delta,使用同type=1

Type = 3

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

3.3.3 Extended Timestamp(擴充套件時間戳)

上面我們提到在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

5433968-eb2f361f0ebb6a73..png

首先包含第一個Message的chunk的Chunk Type為0,因為它沒有前面可參考的chunk,timestamp為1000,表示時間戳。type為0的header佔用11個位元組,假定chunkstreamId為3<127,因此Basic Header佔用1個位元組,再加上Data的32個位元組,因此第一個chunk共44=11+1+32個位元組。

第二個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如下:

5433968-bffb646f7336c148..png

3.3.6 chunk表示例2

5433968-73f497bb740cb770..png

注意到Data的Length=307>128,因此這個Message要切分成幾個chunk傳送,第一個chunk的Type=0,Timestamp=1000,承擔128個位元組的Data,因此共佔用140=11+1+128個位元組。

第二個chunk也要傳送128個位元組,其他欄位也同第一個chunk,因此採用Chunk Type=3,此時時間戳也為1000,共佔用129=1+128個位元組。

第三個chunk要傳送的Data的長度為307-128-128=51個位元組,還是採用Type=3,共佔用1+51=52個位元組。

最後實際傳送的chunk如下:

5433968-7fd928f40b231e6f..png

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:

5433968-e0c076246efc8242..png

其中第一位必須為0,chunk Size佔31個位,最大可代表2147483647=0x7FFFFFFF=231-1,但實際上所有大於16777215=0xFFFFFF的值都用不上,因為chunk size不能大於Message的長度,表示Message的長度欄位是用3個位元組表示的,最大隻能為0xFFFFFF。

Abort Message(Message Type ID=2):當一個Message被切分為多個chunk,接受端只接收到了部分chunk時,傳送該控制訊息表示傳送端不再傳輸同Message的chunk,接受端接收到這個訊息後要丟棄這些不完整的chunk。Data資料中只需要一個CSID,表示丟棄該CSID的所有已接收到的chunk。


5433968-492c7c2269d7c140.png

Acknowledgement(Message Type ID=3):當收到對端的訊息大小等於視窗大小(Window Size)時接受端要回饋一個ACK給傳送端告知對方可以繼續傳送資料。視窗大小就是指收到接受端返回的ACK前最多可以傳送的位元組數量,返回的ACK中會帶有從傳送上一個ACK後接收到的位元組數。

5433968-bc9ed52349026049..png

Window Acknowledgement Size(Message Type ID=5):傳送端在接收到接受端返回的兩個ACK間最多可以傳送的位元組數。

5433968-6a9a449959b66a17..png

Set Peer Bandwidth(Message Type ID=6):限制對端的輸出頻寬。接受端接收到該訊息後會通過設定訊息中的Window ACK Size來限制已傳送但未接受到反饋的訊息的大小來限制傳送端的傳送頻寬。如果訊息中的Window ACK Size與上一次傳送給傳送端的size不同的話要回饋一個Window Acknowledgement Size的控制訊息。

5433968-931445b30c14808e..png

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:用於客戶端向伺服器傳送連線請求,訊息的結構如下:

欄位型別說明

Command Name(命令名字)String命令的名字,如”connect”

Transaction ID(事務ID)Number恆為1

Command Object(命令包含的引數物件)Object鍵值對集合表示的命令引數

Optional User Arguments(額外的使用者引數)Object使用者自定義的額外資訊

第三個欄位中的Command Object中會涉及到很多鍵值對,這裡不再一一列出,使用時可以參考協議的官方文件。

訊息的回應有兩種,_result表示接受連線,_error表示連線失敗

4.1.1.2 Call:用於在對端執行某函式,即常說的RPC:遠端程式呼叫,訊息的結構如下:

欄位型別說明

Procedure Name(程式名)String要呼叫的程式名稱

Transaction IDNumber|如果想要對端響應的話置為非0值,否則置為0

Command ObjectObject命令引數

Optional ArguentsObject使用者自定義引數

如果訊息中的TransactionID不為0的話,對端需要對該命令做出響應,響應的訊息結構如下:

欄位型別說明

Command Name(命令名)String命令的名稱

TransactionIDNumber上面接收到的命令訊息中的TransactionID

Command ObjectObject命令引數

Optional ArgumentsObject使用者自定義引數

4.1.1.3 Create Stream:建立傳遞具體資訊的通道,從而可以在這個流中傳遞具體資訊,傳輸資訊單元為Chunk。

欄位型別說明

Command Name(命令名)String“createStream”

TransactionIDNumber上面接收到的命令訊息中的TransactionID

Command ObjectObject命令引數

Optional ArgumentsObject使用者自定義引數

4.1.2 NetStream Commands(流連線上的命令)

Netstream建立在NetConnection之上,通過NetConnection的createStream命令建立,用於傳輸具體的音訊、視訊等資訊。在傳輸層協議之上只能連線一個NetConnection,但一個NetConnection可以建立多個NetStream來建立不同的流通道傳輸資料。

以下會列出一些常用的NetStream Commands,服務端收到命令後會通過onStatus的命令來響應客戶端,表示當前NetStream的狀態。

onStatus命令的訊息結構如下:

欄位型別說明

Command NameString“onStatus”

TransactionIDNumber恆為0

Command ObjectNULL對onSatus命令來說不需要這個欄位

Info ObjectObjectAMF型別的Object,至少包含以下三個屬性:1,“level”,String型別,可以為“warning”、”status”、”error”中的一種;2,”code”,String型別,代表具體狀態的關鍵字,比如”NetStream.Play.Start”表示開始播流;3,”description”,String型別,代表對當前狀態的描述,提供對當前狀態可讀性更好的解釋,除了這三種必要資訊,使用者還可以自己增加自定義的鍵值對

4.1.2.1 play(播放):由客戶端向伺服器發起請求從伺服器端接受資料(如果傳輸的資訊是視訊的話就是請求開始播流),可以多次呼叫,這樣本地就會形成一組資料流的接收者。注意其中有一個reset欄位,表示是覆蓋之前的播流(設為true)還是重新開始一路播放(設為false)。

play命令的結構如下:

欄位型別說明

命令名String“play”

事務IDNumber恆為0

命令引數物件Null不需要此欄位,設為空

流名稱String要播放的流的名稱

開始位置Number可選引數,表示從何時開始播流,以秒為單位。預設為-2,代表選取對應該流名稱的直播流,即當前正在推送的流開始播放,如果對應該名稱的直播流不存在,就選取該名稱的流的錄播版本,如果這也沒有,當前播流端要等待直到對端開始該名稱的流的直播。如果傳值-1,那麼只會選取直播流進行播放,即使有錄播流也不會播放;如果傳值或者正數,就代表從該流的該時間點開始播放,如果流不存在的話就會自動播放播放列表中的下一個流

週期Number可選引數,表示回退的最小間隔單位,以秒為單位計數。預設值為-1,代表直到直播流不再可用或者錄播流停止後才能回退播放;如果傳值為0,代表從當前幀開始播放

重置Boolean可選引數,true代表清除之前的流,重新開始一路播放,false代表保留原來的流,向本地的播放列表中再新增一條播放流

4.1.2.2 play2(播放):和上面的play命令不同的是,play2命令可以將當前正在播放的流切換到同樣資料但不同位元率的流上,伺服器端會維護多種位元率的檔案來供客戶端使用play2命令來切換。

欄位型別說明

Command NameString“play2”

TransactionIDNumber恆為0

Command ObjectNULL,對onSatus命令來說不需要這個欄位

parametersObjectAMF編碼的Flash物件,包括了一些用於描述flash.net.NetstreamPlayOptions ActionScript obejct的引數

4.1.2.3 deleteStream(刪除流):用於客戶端告知伺服器端本地的某個流物件已被刪除,不需要再傳輸此路流。

欄位型別說明

Command NameString“deleteStream”

TransactionIDNumber恆為0

Command ObjectNULL,對onSatus命令來說不需要這個欄位

Stream ID(流ID)Number本地已刪除,不再需要伺服器傳輸的流的ID

4.1.2.4 receiveAudio(接收音訊):通知伺服器端該客戶端是否要傳送音訊

receiveAudio命令結構如下:

欄位型別說明

Command NameString“receiveAudio”

TransactionIDNumber恆為0

Command ObjectNULL對onSatus命令來說不需要這個欄位

Bool FlagBooleantrue表示傳送音訊,如果該值為false,伺服器端不做響應,如果為true的話,伺服器端就會準備接受音訊資料,會向客戶端回覆NetStream.Seek.Notify和NetStream.Play.Start的Onstatus命令告知客戶端當前流的狀態

4.1.2.5 receiveVideo(接收視訊):通知伺服器端該客戶端是否要傳送視訊

receiveVideo命令結構如下:

欄位型別說明

Command NameString“receiveVideo”

TransactionIDNumber恆為0

Command ObjectNULL對onSatus命令來說不需要這個欄位

Bool FlagBooleantrue表示傳送視訊,如果該值為false,伺服器端不做響應,如果為true的話,伺服器端就會準備接受視訊資料,會向客戶端回覆NetStream.Seek.Notify和NetStream.Play.Start的Onstatus命令告知客戶端當前流的狀態

4.1.2.6 publish(推送資料):由客戶端向伺服器發起請求推流到伺服器。

publish命令結構如下:

欄位型別說明

Command NameString“publish”

TransactionIDNumber恆為0

Command ObjectNULL,對onSatus命令來說不需要這個欄位

Publishing Name(推流的名稱)String流名稱|

Publishing Type(推流型別)String“live”、”record”、”append”中的一種。live表示該推流檔案不會在伺服器端儲存;record表示該推流的檔案會在伺服器應用程式下的子目錄下儲存以便後續播放,如果檔案已經存在的話刪除原來所有的內容重新寫入;append也會將推流資料儲存在伺服器端,如果檔案不存在的話就會建立一個新檔案寫入,如果對應該流的檔案已經存在的話儲存原來的資料,在檔案末尾接著寫入

4.1.2.7 seek(定位流的位置):定位到視訊或音訊的某個位置,以毫秒為單位。

seek命令的結構如下:

欄位型別說明

Command NameString“seek”

TransactionIDNumber恆為0

Command ObjectNULL,對onSatus命令來說不需要這個欄位

milliSecondsNumber定位到該檔案的xx毫秒處|

4.1.2.8 pause(暫停):客戶端告知服務端停止或恢復播放。

pause命令的結構如下:

欄位型別說明

Command NameString“pause”

TransactionIDNumber恆為0

Command ObjectNULL,對onSatus命令來說不需要這個欄位

Pause/Unpause FlagBooleantrue表示暫停,false表示恢復

milliSecondsNumber暫停或者恢復的時間,以毫秒為單位|

如果Pause為true即表示客戶端請求暫停的話,服務端暫停對應的流會返回NetStream.Pause.Notify的onStatus命令來告知客戶端當前流處於暫停的狀態,當Pause為false時,服務端會返回NetStream.Unpause.Notify的命令來告知客戶端當前流恢復。如果服務端對該命令響應失敗,返回_error資訊。

5. 代表流程

5.1 推流流程

5433968-d96fead8823aa19d..png

5.2 播流流程

5433968-f9bff0c9f33b483a..png

6. 新手建議

如果讀者仔細讀完了上面講的RTMP協議,想必會覺得RTMP協議非常繁瑣,事實也確實是這樣,RTMP協議中充斥著很多冗餘的欄位,比如三次握手中的時間戳的校對,還有一些特殊的命令,如FCPublish、UnFCPublish等,但在實際實現中為了保證更大相容性通常還是要處理這些看似多餘的命令。加上Adobe對RTMP協議的實現細節有些並沒有按照協議來或者協議中沒有寫清楚自己搞了一套實現,其他應用開發時還要相容Adobe錯誤的實現,從而使的RTMP也一直為開發者所詬病。但不管怎樣,RTMP確實提供了一種能夠全面並且實現簡單的協議來保證流資訊的傳輸,這方面暫時還沒有一種更完善更簡潔的協議能夠取代它在視訊流開發中的地位。

新人一開始接觸RTMP的時候肯定會覺得頭大,這也是RTMP協議不簡潔的後果。建議讀者在學習時先過一遍協議理解大概的概念和流程,然後對照wireshark抓的包,和協議進行比對,這樣將理論和實踐結合,應該會理解的更快一點。

相關文章