一、RTMP 協(xié)議簡介
RTMP(Real-Time Messaging Protocol),譯為:實時消息傳輸協(xié)議,它是由 Adobe 公司提出的一種基于 TCP 的應(yīng)用層協(xié)議,用來解決多媒體數(shù)據(jù)傳輸流的多路復用(Multiplexing)和分包(Packetizing)的問題。RTMP 在兩個對等的通信端之間通過可靠的傳輸協(xié)議提供雙向的消息多路服務(wù),用來傳輸帶有時間信息的并行的視頻、音頻和數(shù)據(jù)。通常的協(xié)議的實現(xiàn)會給不同類型的消息賦予不同的優(yōu)先級,當傳輸能力受到限制時它會影響消息下層流發(fā)送的隊列順序。由于協(xié)議設(shè)計對低延時、音視頻同步等能力的良好支持,RTMP 是實時直播場景,尤其是在推流上行鏈路中,最常用的傳輸協(xié)議之一。
二、塊流(RTMP Chunk Stream)
RTMP Chunk Stream 為配合 RTMP 協(xié)議而設(shè)計,主要為 RTMP 提供復用和分包的功能。每個消息包含時間戳和負載類型信息。RTMP Chunk Stream 為配合 RTMP 協(xié)議而設(shè)計, 它也可以服務(wù)于任何發(fā)送消息流的協(xié)議。 每個消息包含時間戳和負載類型信息。RTMP Chunk Stream 除自身內(nèi)置的協(xié)議控制消息外, 還為上層協(xié)議提供了用戶控制消息的機制。RTMP Chunk Stream 和 RTMP 適合各種音視頻應(yīng)用, 不管是一對一還是一對多場景都能很好的滿足。 RTMP Chunk Stream 可以理解為是對傳輸 RTMP Chunk 的流的邏輯上的抽象,客戶端和服務(wù)器之間有關(guān) RTMP 的信息都在這個流上通信。
三、消息(RTMP Message)
1、RTMP 消息格式(RTMP Message Format)
RTMP 消息是 RTMP 協(xié)議中基本的數(shù)據(jù)單元。服務(wù)端和客戶端通過在網(wǎng)絡(luò)上發(fā)送消息實現(xiàn)之間的交互,消息包括但不限于音頻、視頻、數(shù)據(jù)。這里的消息是指滿足該協(xié)議格式的、可以切分成 Chunk 發(fā)送的消息。RTMP 消息包含兩部分:消息頭(Message Header)和有效數(shù)據(jù)(Message Body)。消息頭(Message Header)包含以下信息:
Message Type:消息類型,占用 1 個字節(jié)。1~6 的消息類型 ID 是為協(xié)議控制消息保留的。
Length:有效負載的字節(jié)數(shù),即音視頻等信息的數(shù)據(jù)長度。占用 3 個字節(jié),采用大端存儲(big-endian)模式。
Timestamp:時間戳,占用 4 個字節(jié),采用大端存儲模式。
Message Stream ID:消息流 ID,標識消息所使用的流,采用大端存儲模式。

2、分塊(Chunking)
RTMP 在收發(fā)數(shù)據(jù)時并不是以 Message 為單位的,而是把 Message 拆分成 Message Chunk 發(fā)送,被拆分的每個 Message Chunk 都有一個唯一的 ID, 最后在接收端會按照 Message Chunk ID 將 Message Chunk 重新組裝成 Message。在傳輸時必須在一個 Message Chunk 發(fā)送完成之后才能開始發(fā)送下一個。把數(shù)據(jù)量較大的 Message 拆分成較小的 Message Chunk,在傳輸時可以避免優(yōu)先級低的消息持續(xù)發(fā)送阻塞優(yōu)先級高的數(shù)據(jù),比如 Message 中的數(shù)據(jù)會包括視頻幀、音頻幀和控制信息,如果持續(xù)發(fā)送音頻數(shù)據(jù)或者控制數(shù)據(jù)的話可能就會造成視頻幀的阻塞,然后就會造成看視頻時最煩人的卡頓現(xiàn)象。同時對于數(shù)據(jù)量較小的 Message,可以通過對 Chunk Header 的字段來壓縮信息,從而減少信息的傳輸量。
Message Chunk 的默認大小是 128 字節(jié),在傳輸過程中,通過 Set Chunk Size 可以設(shè)置 Message Chunk 數(shù)據(jù)量的最大值,在發(fā)送端和接受端會各自維護一個 Chunk Size,可以分別設(shè)置這個值來改變自己這一方發(fā)送的 Chunk 的最大大小。大一點的 Message Chunk 減少了計算每個 Message Chunk 的時間從而減少了 CPU 的占用率,但是它會占用更多的時間在發(fā)送上,尤其是在低帶寬的網(wǎng)絡(luò)情況下,很可能會阻塞后面更重要信息的傳輸。小一點的 Message Chunk 可以減少這種阻塞問題,但小的 Message Chunk 會引入過多額外的信息(Chunk 中的 Header),少量多次的傳輸也可能會造成發(fā)送的間斷導致不能充分利用高帶寬的優(yōu)勢,因此并不適合在高比特率的流中傳輸。在實際發(fā)送時應(yīng)對要發(fā)送的數(shù)據(jù)用不同的 Chunk Size 去嘗試,通過抓包分析等手段得出合適的 Message Chunk 大小,并且在傳輸過程中可以根據(jù)當前的帶寬信息和實際信息的大小動態(tài)調(diào)整 Message Chunk 的大小,從而盡量提高 CPU 的利用率并減少信息的阻塞機率。
3、RTMP 消息塊格式(RTMP Message Chunk Format)
Message Chunk 由塊頭(Chunk Header)和數(shù)據(jù)(Chunk Data)組成,Chunk Header包含 3 部分:基本頭(Basic Header)、消息頭(Message Header)和擴展時間戳(Extended Timestap)。

3.1、基本頭(Basic Header)
該部分編碼Chunk Stream ID(流通道 ID,簡稱 CSID) 和 Chunk Type(下圖中fmt字段)?;绢^的長度是 1~3 字節(jié),采用小端存儲模式,其長度取決于CSID,CSID是一個變長字段,用來唯一標識一個特定的流通道。Chunk Type 決定了消息頭(Message Header)的編碼格式,長度固定占 2 位(bit)。在足夠存儲這兩個字段的前提下應(yīng)該用盡可能少的字節(jié)來表示,這樣能夠減少由于引入 Header 增加的數(shù)據(jù)量。
RTMP 最多支持 65597 個流,CSID在3~65599范圍內(nèi)。CSID的 0,1,2為保留值。0表示塊基本頭為 2 個字節(jié),并且CSID范圍在64~319之間(第二個字節(jié) + 64);1表示塊基本頭為 3 個字節(jié),并且 ID 范圍在64~65599之間(第三個字節(jié) * 256 + 第二個字節(jié) + 64);取值在 3~63 之間的 ID 表示完整的CSID。值2是為低版本協(xié)議保留的,用于協(xié)議控制消息和命令,第0~5位(不重要的)表示CSID。
當Basic Header長度為 1 個字節(jié)時,CSID占 6 位(6 位表示的范圍為0~63),由于CSID的 0,1,2 為保留值,因此用戶可以定義的CSID范圍為3~63,此時的 CSID是完整的,不需要計算得出:

當Basic Header長度為 2 個字節(jié)時,CSID占 14 位,此時 RTMP 將與 Chunk Type 所在字節(jié)的其他位都置為 0,剩下的 1 個字節(jié)來表示CSID - 64,這樣共有 8 位(8 位表示的范圍為 0~255)來存儲CSID,CSID計算公式:CSID = 第二個字節(jié)的值 + 64,因此計算得到CSID范圍是64~319:

當Basic Header長度為 3 個字節(jié)時,CSID占 22 位,此時 RTMP 將與 Chunk Type 所在字節(jié)的其他位都置為 1,剩下的 16 位表示CSID - 64,這樣共有 16 位(16 位表示的范圍為0~65535)來存儲CSID,因此計算得到 CSID 范圍是64~65599。CSID計算公式為:第三個字節(jié)值*255 + 第二個字節(jié)值 + 64.。 64~319 范圍內(nèi)的SCID可以用 2 字節(jié)長度來編碼,也可以用 3 字節(jié)長度來編碼。但實際實現(xiàn)時還是應(yīng)該秉著最少字節(jié)的原則使用 2 個字節(jié)的表示方式來表示范圍為64~319的CSID:

3.2、消息頭(Message Header)
Message Header 共有 4 種不同的格式,Message Header的格式和長度取決于 Basic Header 中的 Chunk Type(即fmt字段值)。協(xié)議實現(xiàn)方應(yīng)該用最緊湊的格式來表示塊消息頭。該部分編碼所發(fā)送消息的描述信息(無論是整個消息還是一部分)。該部分的長度可能是 0 字節(jié)、3 字節(jié)、7 字節(jié)或者 11 字節(jié),其長度取決于基本頭中指定的 Chunk Type。
3.2.1、塊類型 0(Type 0)
0 類型的塊消息頭占 11 個字節(jié)長度。在 Chunk Stream 發(fā)送第一個 Chunk 時和Message Header中的時間戳后退(即當前 Chunk 的時間戳小于上一個 Chunk 的時間戳,回退播放時會出現(xiàn)這種情況)時必須使用0 類型的 Chunk。

timestamp(3 字節(jié)):0 類型的 Chunk 的絕對時間戳。 如果時間戳大于等于16777215(0xFFFFFF),該字段的值必須為16777215,即 3 個字節(jié)全部置為 1,此時實際的 timestamp 會轉(zhuǎn)存到 Chunk 的Extended Timestamp字段中,接受端在判斷 timestamp 字段 24 個位都為 1 時就會去Extended Timestamp中解析實際的時間戳。
message length(3 字節(jié)): 消息長度, 0 類型和 1 類型的 Chunk 包含此字段,表示消息長度,即當前 Chunk 所屬消息的數(shù)據(jù)總長度,而非當前 Chunk 的數(shù)據(jù)長度。除了最后一個 Chunk,其他 Chunk 的數(shù)據(jù)長度大小都等于 Chunk Size 大小。
message type id(1 字節(jié)):消息類型 ID(數(shù)據(jù)的類型),如 8 代表音頻數(shù)據(jù)、9 代表視頻數(shù)據(jù)。
message stream id(4 字節(jié)):消息流 ID,表示該 Chunk 所在流的 ID,采用小端存儲模式。通常,相同 Chunk Stream 中的消息屬于用一個 Message Stream。雖然不同的 Message Stream 復用相同的 Chunk Stream 會導致Message Header無法有效壓縮,但是當一個 Message Stream 已關(guān)閉,才打開另外一個 Message Stream,就可以通過發(fā)送一個新的0 類型 Chunk 來實現(xiàn)復用。
3.2.2、塊類型 1(Type 1)
1 類型的塊消息頭占用 7 個字節(jié)長度,不包含message stream id,表示和上一個 Chunk 的message stream id 是相同的。對于傳輸大小可變消息的流(如多數(shù)視頻格式),在發(fā)送第一個消息之后的每個消息,第一個塊都應(yīng)該使用該類型格式。

timestamp delta(3字節(jié)): 時間戳增量。 1 類型和2 類型的 Chunk 包含此字段,存儲的是當前 Chunk 的timestamp和上一個 Chunk 的timestamp差值。當它的值超過 3 個字節(jié)所能表示的最大值時,三個字節(jié)都置為 1,實際的時間戳差值就會轉(zhuǎn)存到Extended Timestamp字段中,接受端在判斷timestamp delta字段 24 個位都為 1 時就會去Extended timestamp中解析實際的與上次時間戳的差值。
message length(3 字節(jié)): 同0 類型的 message length。
message type id(1 字節(jié)):同0 類型的 message type id。
3.2.3、塊類型 2(Type 2)
2 類型的塊消息頭占用 3 個字節(jié)長度,僅包timestamp delta(同1 類型的 message delta),表示當前 Chunk 和上一次發(fā)送的 Chunk 的 message length、 message type id和message stream id都相同。對于傳輸固定大小消息的流(如音頻和數(shù)據(jù)格式),在發(fā)送第一個消息之后的每一個消息,第一個塊都應(yīng)該使用該類型格式。

3.2.4、塊類型 3(Type 3)
3 類型的 Chunk 占用 0 字節(jié),也就是沒有消息頭,表示當前 Chunk 的 Message Header 和上一個 Chunk 的 Message Header 是相同的。
而當它跟在1 類型或者2 類型的 Chunk 后面時,表示和上一個 Chunk 的timestamp delta是相同的。比如第一個0 類型 Chunk 的timestamp=1000;第二個2 類型 Chunk 的timestamp delta= 20,則時間戳為1000 + 20 = 1020;第三個3 類型 Chunk 的timestamp delta= 20,則時間戳為 1020 + 20 = 1040;

下圖中展示了上圖中消息流分割成的塊:

當它跟在0 類型的 Chunk 后面時,表示和上一個 Chunk 的時間戳是相同的。這種情況出現(xiàn)在一個 Message 被拆分成了多個 Chunk 時,當前 Chunk 和上一個 Chunk 同屬于一個 Message 的情況下。如下圖:

下圖是被分割成的塊:

4、擴展時間戳(Extended Timestamp)
前面提到在0 類型 Chunk 的Message Header中會有時間戳timestamp,以及1 類型和2 類型 Chunk 的Message Header中有時間戳差timestamp delta,當timestamp或者timestamp delta大于 3 個字節(jié)所能表示的最大數(shù)值16777215(0xFFFFFF)時,才會用到這個字段,否則這個字段值為 0。擴展時間戳占 4 個字節(jié),能表示的最大數(shù)值就是4294967295(0xFFFFFFFF)。當Extended Timestamp啟用時,timestamp或者timestamp delta要全置為1,表示應(yīng)該去擴展時間戳字段來提取真正的時間戳或者時間戳差。擴展時間戳存儲的是完整值,而不是減去時間戳或者時間戳差的值。
三、協(xié)議控制消息(Protocol Control Messages)
RTMP Chunk Stream 用Message Type為1,2,3,5和6的 Message 來作為協(xié)議控制消息,這些 Message 包含 RTMP Chunk Stream Protocol 所需要的信息,所以協(xié)議控制消息是在 RTMP Chunk Stream Protocol 層的消息。在 Chunk Stream 中發(fā)送時,Message Stream ID = 0,CSID = 2。協(xié)議控制消息收到后立即生效,它們的時間戳信息是被忽略的。
1、設(shè)置塊大?。⊿et Chunk Size,Message Type = 1)
用于設(shè)置Chunk Data的最大長度 ,Chunk Size 默認是 128 字節(jié)(不能小于1字節(jié),通常應(yīng)該不低于 128 字節(jié))。通信過程中可以通過發(fā)送該消息來設(shè)置Chunk Data的大小,客戶端或服務(wù)端均可以修改此值。例如,假設(shè)一個客戶端想要發(fā)送 158 字節(jié)的音頻數(shù)據(jù),而最大塊大小為 128 字節(jié)。在這種情況下,客戶端可以向服務(wù)端發(fā)送該消息,通知它最大塊大小被設(shè)置為了 158 字節(jié),這樣客戶端只用一個塊就可以發(fā)送這些音頻數(shù)據(jù),否則需要要將該消息拆分為Chunk Data分別為128 字節(jié)和 30 字節(jié)的 2 個 Chunk 發(fā)送。以下為 Set Chunk Size 協(xié)議控制消息的Chunk Data:

0:該位必須為0;
chunk size(31位): 該字段以字節(jié)形式保存新的最大塊大小,該值將用于后續(xù)的所有塊的發(fā)送,知道收到新的通知。該值可取值范圍為1~2147483647(0x7FFFFFFF),由于Chunk size 不能大于 Message 的長度,所以超過 Message 的長度1677215(0xFFFFFF)的值是用不上的。
2、中止消息(Abort Message,Message Type = 2)
當一個 Message 被劃分為多個 Chunk,接受端只接收到了部分 Chunk 時,發(fā)送該控制消息表示發(fā)送端不再傳輸同 Message 的 Chunk 了,接受端接收到這個消息后要丟棄已收到不完整的Chunk。該控制消息的Chunk Data中只有一個CSID字段(32 字節(jié)),表示丟棄所有已接收到的塊流 ID 為CSID的 Chunk。

3、確認消息(Acknowledgement,Message Type = 3)
當客戶端或服務(wù)端收到對端的消息大小等于窗口大小(Window Size)的消息后要回饋一個 ACK 給發(fā)送端告知對端可以繼續(xù)發(fā)送數(shù)據(jù)。窗口大小就是指在收到接受端返回的 ACK 前可以發(fā)送的最大字節(jié)數(shù),返回的 ACK 中會帶有從發(fā)送上一個 ACK 后接收到的總字節(jié)數(shù)。

4、窗口大小確認(Window Acknowledgement Size,Message Type = 5)
客戶端或服務(wù)端發(fā)送該消息來告知對端在兩個 ACK 之間所使用的窗口大小。發(fā)送端發(fā)送窗口大小的數(shù)據(jù)后等待接收端發(fā)送 ACK。接收端在上一個 ACK 發(fā)送之后,接收到窗口大小的數(shù)據(jù)后必須發(fā)送 ACK,如果之前沒有發(fā)送過 ACK 就從會話開始之后算起接收到窗口大小的數(shù)據(jù)后發(fā)送 ACK。

5、設(shè)置對等帶寬(Set Peer Bandwidth,Message Type = 6)
客戶端或服務(wù)端發(fā)送該消息用來限制對端的輸出帶寬。對端收到該消息后通過將已發(fā)送但未收到的消息大小限制為該消息中的Acknowledgement Window size來實現(xiàn)限制發(fā)送帶寬。如果該消息中的Acknowledgement Window size與上一次發(fā)送給發(fā)送端的不同的話發(fā)送端要回饋一個 Window Acknowledgement Size 消息。

Limit Type(限制類型)有以下選項:
0 - Hard:消息接收端應(yīng)該將輸出帶寬限制為該消息中Acknowledgement Window size指定的大小;
1 - Soft:消息接收端應(yīng)該將輸出帶寬限制為該消息中Acknowledgement Window size指定的大小或者當前窗口大小,限制為二者中的最小值。
2 - Dynamic:如果上次的 Set Peer Bandwidth 消息中的Limit Type為0 - Hard,本次也按0 - Hard處理,否則忽略本消息。
四、RTMP 消息類型(Types of Messages)
1、命令消息(Command Message,Message Type = 20,Message Type = 17)
用于客戶端和服務(wù)器間傳遞在對端執(zhí)行某些操作的命令消息,比如發(fā)送這些消息來完成連接、創(chuàng)建流、發(fā)布、播放、暫停等操作,以及通知發(fā)送者命令請求狀態(tài)和結(jié)果等等。當消息使用AMF0編碼時,Message Type = 20,使用AMF3編碼時 Message Type = 17。
命令消息中包含Command Name(命令名稱)、transaction ID(命令標識)、command object(相關(guān)參數(shù))。接受端收到命令消息后,會返回發(fā)送端以下三種消息中的一種:_result響應(yīng)消息表示接受該命令,對端可以繼續(xù)往下執(zhí)行流程;_error響應(yīng)消息代表拒絕該命令要執(zhí)行的操作;method name響應(yīng)消息代表要在命令消息的發(fā)送端執(zhí)行的函數(shù)名稱。響應(yīng)消息都要帶有當前收到的命令消息中的 transaction ID 來表示接收端本次回應(yīng)的是哪個命令消息。
1.1、連接層命令(NetConnection Commands)
連接層命令用于管理客戶端和服務(wù)端之間的鏈接狀態(tài)。同時也提供了異步遠程方法調(diào)用(RPC)在對端執(zhí)行某方法,以下是常見的連接層的命令:
1.1.1、connect
用于客戶端向服務(wù)器發(fā)送鏈接請求??蛻舳税l(fā)送到服務(wù)端的消息結(jié)構(gòu)如下:
Command Name:命令名稱,當前命令設(shè)置為 "connect";
Transaction ID:對于連接請求該字段恒為 1;
Command Object:命令參數(shù),用鍵值對集合表示(可參考官方文檔 7.2.1.1 章節(jié));
Optional User:用戶自定義的額外信息;

執(zhí)行 connect 命令時的消息流順序:
1、客戶端向服務(wù)端發(fā)送 connect 命令消息請求建立連接;
2、服務(wù)端收到 connect 命令消息后向客戶端發(fā)送 Window Acknowledgement Size(窗口大小確認)消息,并且服務(wù)端連接 connect 命令消息提及的應(yīng)用;
3、服務(wù)端向客戶端發(fā)送 Set Peer Bandwidth(設(shè)置對等帶寬)消息;
4、客戶端收到 Set Peer Bandwidth 消息后回饋 Window Acknowledgement Size 消息給服務(wù)端;
5、服務(wù)端向客戶端發(fā)送 StreamBegin 消息;
6、服務(wù)端向客戶端發(fā)送transaction ID = 1的回應(yīng)消息給客戶端,服務(wù)端消息的回應(yīng)有兩種:_result表示接受連接,_error表示連接失敗。
1.1.2、call
用于遠程在對端執(zhí)行某函數(shù)(即 RPC:遠程進程調(diào)用)。消息的結(jié)構(gòu)如下:
Procedure Name:要調(diào)用的遠程進程名稱;
Transaction ID:如果想要對端發(fā)送響應(yīng)消息,需要設(shè)置為非 0 值,否則置為 0;
Command Object:命令參數(shù);
Optional Arguments:用戶自定義參數(shù);
如果消息的Transaction ID不為 0 的話,對端需要對該命令做出響應(yīng),響應(yīng)的消息結(jié)構(gòu)如下:
Command Name:命令名稱;
Transaction ID:接收到的命令消息中的Transaction ID;
Command Object:命令參數(shù);
Response:調(diào)用方法的響應(yīng);
1.1.3、createStream
客戶端發(fā)給服務(wù)端此命令消息創(chuàng)建傳輸消息的通道,從而可以在這個流中傳輸音頻、視頻或者元數(shù)據(jù)等,傳輸信息單元為 Chunk。
客戶端發(fā)給服務(wù)端消息的結(jié)構(gòu):
Command Name:命令名稱,當前命令設(shè)置為 "createStream";
Transaction ID:消息標識;
Command Object:命令參數(shù);
服務(wù)端回饋給客戶端消息結(jié)構(gòu):
Command Name:命令名稱,當前命令設(shè)置為 "createStream";
Transaction ID:與客戶端發(fā)送到服務(wù)端消息的Transaction ID相同;
Command Object:命令參數(shù);
Stream ID:返回 Stream ID 或者錯誤信息對象。
1.2、流連接層命令(NetStream Commands)
NetStream 建立在 NetConnection 之上,用于定義傳輸音頻和視頻等信息的通道。在傳輸層協(xié)議之上只能連接一個 NetConnection,但一個 NetConnection 可以建立多個 NetStream 來建立不同的流通道傳輸數(shù)據(jù)。服務(wù)端收到命令后會通過 onStatus 命令來響應(yīng)客戶端,表示當前 NetStream 的狀態(tài)。onStatus 命令消息結(jié)構(gòu)如下:
2、數(shù)據(jù)消息(Data Message,Message Type = 18,Message Type = 15)
傳遞一些 MetaData(元數(shù)據(jù),比如主題、創(chuàng)建時間和時長等等)或者用戶自定義的一些消息。當消息使用AMF0編碼時,Message Type = 18,使用AMF3編碼時 Message Type = 15。
3、共享消息(Shared Object Message,Message Type = 19,Message Type = 16)
表示一個 Flash 類型的對象(由鍵值對的集合組成),用于多客戶端,多實例進行同步時使用。每條消息可包含多個事件。當消息使用AMF0編碼時,Message Type = 19,使用AMF3編碼時 Message Type = 16。
4、音頻消息(Audio Message,Message Type = 8)
客戶端或服務(wù)端通過發(fā)送此消息來發(fā)送音頻數(shù)據(jù)給對方。
5、視頻消息(Video Message,Message Type = 9)
客戶端或服務(wù)端通過發(fā)送此消息來發(fā)送視頻數(shù)據(jù)給對方。
6、組合消息(Aggregate Message,Message Type = 22)
一個組合消息消息包含多個子 RTMP 消息。

7、用戶控制消息(User Control Messages,Message Type = 4)
RTMP 協(xié)議將Message Type為4的消息作為了用戶控制消息,在 Chunk Stream 中發(fā)送時,Message Stream ID = 0,CSID = 2。接收端收到用戶控制消息后立即生效,用戶控制消息的時間戳信息是被忽略的。和前面提到的協(xié)議控制消息不同,用戶控制消息是在 RTMP Protocol 層的,而不是在 RTMP Chunk Stream Protocol 層的。
客戶端或服務(wù)端通過發(fā)送該消息告知對端用戶控制事件。該消息攜帶事件類型和事件數(shù)據(jù)兩部分。開頭的 2 個字節(jié)用于指定事件類型,后面緊跟著是事件數(shù)據(jù)。事件數(shù)據(jù)字段長度可變,使用 RTMP Chunk Stream 傳輸時,最大塊大小要足夠大,以便可以使用一個單獨的 Chunk 進行傳輸用戶控制消息。

用戶控制消息支持以下事件:
0(流開始):服務(wù)端發(fā)送該事件,用來通知客戶端一個流已經(jīng)可以用來傳輸消息了。默認情況下,該事件是在收到客戶端連接指令并成功處理后發(fā)送的第一個事件。該事件的Event Data使用 4 個字節(jié)來表示可用的message stream id。
1(流結(jié)束):服務(wù)端發(fā)送該事件,用來通知客戶端其在流中請求的回放數(shù)據(jù)已經(jīng)結(jié)束了。如果沒有額外的指令,將不會再發(fā)送任何數(shù)據(jù),而客戶端會丟棄之后從該流接收到的消息。該事件的Event Data使用 4 個字節(jié)來表示回放完成的message stream id。
2(流枯竭):服務(wù)端發(fā)送該事件,用來通知客戶端流中沒有更多數(shù)據(jù)了。如果服務(wù)端在一定時間后沒有探測到更多數(shù)據(jù),它就可以通知所有訂閱該流的客戶端,流已經(jīng)枯竭。該事件的Event Data使用 4 個字節(jié)來表示枯竭的message stream id。
3(設(shè)置緩沖區(qū)大小):客戶端發(fā)送該事件,用來告知服務(wù)端緩沖區(qū)大?。▎挝缓撩耄?。該事件在服務(wù)端開始處理流數(shù)據(jù)之前發(fā)送。Event Data中,前 4 個字節(jié)表示message stream id,后 4 字節(jié)表示緩沖區(qū)大小(單位毫秒)。
4(流已錄制):服務(wù)端發(fā)送該事件,用來通知客戶端該流是一個錄制流。事件數(shù)據(jù)用4個字節(jié)表示錄制的message stream id。
6(ping請求):服務(wù)端發(fā)送該事件,用來探測客戶端是否處于可達狀態(tài)。Event Data里攜帶的是一個 4 字節(jié)長度的時間戳,表示服務(wù)端分發(fā)該事件時的服務(wù)器本地時間??蛻舳耸盏胶笥?ping 請求后應(yīng)該回復服務(wù)端。
7(ping響應(yīng)):客戶端用該事件響應(yīng)服務(wù)端的 ping 請求,Event Data為收到的 ping 請求中攜帶的時間戳,長度 4 字節(jié)。
五、握手(Handshake)
RTMP 的連接開始于握手。握手內(nèi)容不同于協(xié)議的其它部分,它包含三個固定大小的塊,而不是帶頭信息的變長塊。
客戶端(發(fā)起連接的端點)和服務(wù)器各自發(fā)送相同的三個塊。為了演示,這三個塊客戶端發(fā)送的被記做 C0,C1,C2,服務(wù)發(fā)送的被記做 S0,S1,S2。
1、握手順序
RTMP 協(xié)議本身并沒有規(guī)定這 6 個消息的具體傳輸順序,但 RTMP 協(xié)議的實現(xiàn)者需要保證這幾點:
客戶端發(fā)送 C0 和 C1 塊開始握手。
客戶端必須等接收到 S1 后才能發(fā)送 C2。
客戶端必須等接收到 S2 后才能發(fā)送其它數(shù)據(jù)。
服務(wù)器必須等接收到 C0 才能發(fā)送 S0 和 S1,也可以等接到 C1 一起之后。
服務(wù)器必須等到 C1 才能發(fā)送 S2。
服務(wù)器必須等到 C2 才能發(fā)送其它數(shù)據(jù)。
2、C0 和 S0 格式
C0 和 S0 包只有八個位,可以看成一個 8 位的整數(shù)。

以下是CO和S0包的字段解釋:
版本號(8 位):在 C0 包中,該字段表示客戶端請求的 RTMP 版本。在 S0 中,該字段表示服務(wù)器選擇的 RTMP 版本。本規(guī)范所定義的版本是 3??蛇x值中,0-2 是早期版本所用的,已被丟棄;4-31 保留在未來使用;32-255 不允許使用(為了區(qū)分其他以某一可見字符開始的文本協(xié)議)。如果服務(wù)器不能識別客戶端請求的版本, 應(yīng)該返回 3,客戶端可能選擇降級到版本3,也可能放棄握手。
3、C1 和 S1 格式

C1 和 S1 包的長度固定為1536字節(jié), 包含以下字段:
時間戳(4 字節(jié)):該字段承載一個時間戳,該時間戳應(yīng)該作為發(fā)送端點所有后續(xù)塊的時間戳起始時間??梢允?0,也可以是其他的任意值。為了同步多個塊流,端點可能會發(fā)送其他塊流時間戳的當前值。
零值(4 字節(jié)):該字段所有值都必須為 0。
隨機數(shù)據(jù)(1528 字節(jié)):該字段可以是任意值。因為每個端點都需要區(qū)分自己和其他端點初始化的握手響應(yīng),所以此數(shù)據(jù)應(yīng)該有充分的隨機性,但是沒必要使用加密安全的隨機值或動態(tài)值。
4、C2 和 S2 格式

C2 和 S2 包的長度固定為 1536 字節(jié),基本上分別是 S1 和 C1 的回復, 包含以下字段:
時間(4字節(jié)): 這個字段必須包含了一個時間戳,它是由對端發(fā)送過來。對于 C2 來說是 S1,對于 S2 來說是 C1;
時間2(4字節(jié)):這個字段必須包含前一個對端發(fā)送過來的并被讀取的包(S1 或 C1);
隨機數(shù)(1528字節(jié)):這個字段必須包含對端發(fā)送過來的隨機數(shù)對 S1 來說是 C2,對于 S2 來說是 C1。兩端都可以時間字段和時間 2 字段結(jié)合當前的時間來快速寬帶和(或)連接的延遲,但這個方法不太可能很有用處。
5、握手流程示意圖

下面是握手圖示中提到的各個階段具體內(nèi)容:
未初始化(Uninitialized):協(xié)議的版本發(fā)送出去的這個階段。客戶端與服務(wù)器都是未初始化態(tài)。客戶端在 C0 中發(fā)送協(xié)議版本。如果服務(wù)器支持這個版本,它將回應(yīng) S0 和 S1。如果不支持,服務(wù)器將會采取適當措施的回應(yīng)。在 RTMP 中,這個措施是終止連接;
版本已發(fā)送(Version Sent):客戶端和服務(wù)器在未初始化態(tài)后是版本已發(fā)送態(tài)。客戶端等待 S1,服務(wù)器在等待 C1。在接收到響應(yīng)包后,客戶端發(fā)送 C2,服務(wù)器發(fā)送 S2。狀態(tài)就變成了確認已發(fā)送;
確認已發(fā)送(Ack Send):客戶端和服務(wù)器分別在等 S2 和 C2;
握手完成(Handshake Done):客戶端與服務(wù)器可以交換消息了。