一反惕、RTMP 協(xié)議簡(jiǎn)介
RTMP(Real-Time Messaging Protocol),譯為:實(shí)時(shí)消息傳輸協(xié)議,它是由 Adobe 公司提出的一種基于 TCP 的應(yīng)用層協(xié)議,用來(lái)解決多媒體數(shù)據(jù)傳輸流的多路復(fù)用(Multiplexing)和分包(Packetizing)的問(wèn)題蕉扮。RTMP 在兩個(gè)對(duì)等的通信端之間通過(guò)可靠的傳輸協(xié)議提供雙向的消息多路服務(wù),用來(lái)傳輸帶有時(shí)間信息的并行的視頻颗圣、音頻和數(shù)據(jù)喳钟。通常的協(xié)議的實(shí)現(xiàn)會(huì)給不同類(lèi)型的消息賦予不同的優(yōu)先級(jí),當(dāng)傳輸能力受到限制時(shí)它會(huì)影響消息下層流發(fā)送的隊(duì)列順序在岂。由于協(xié)議設(shè)計(jì)對(duì)低延時(shí)奔则、音視頻同步等能力的良好支持,RTMP 是實(shí)時(shí)直播場(chǎng)景蔽午,尤其是在推流上行鏈路中易茬,最常用的傳輸協(xié)議之一。
二及老、塊流(RTMP Chunk Stream)
RTMP Chunk Stream 為配合 RTMP 協(xié)議而設(shè)計(jì)抽莱,主要為 RTMP 提供復(fù)用和分包的功能。每個(gè)消息包含時(shí)間戳和負(fù)載類(lèi)型信息骄恶。RTMP Chunk Stream 為配合 RTMP 協(xié)議而設(shè)計(jì)食铐, 它也可以服務(wù)于任何發(fā)送消息流的協(xié)議。 每個(gè)消息包含時(shí)間戳和負(fù)載類(lèi)型信息僧鲁。RTMP Chunk Stream 除自身內(nèi)置的協(xié)議控制消息外虐呻, 還為上層協(xié)議提供了用戶控制消息的機(jī)制象泵。RTMP Chunk Stream 和 RTMP 適合各種音視頻應(yīng)用, 不管是一對(duì)一還是一對(duì)多場(chǎng)景都能很好的滿足铃慷。 RTMP Chunk Stream 可以理解為是對(duì)傳輸 RTMP Chunk 的流的邏輯上的抽象单芜,客戶端和服務(wù)器之間有關(guān) RTMP 的信息都在這個(gè)流上通信蜕该。
三犁柜、消息(RTMP Message)
1、RTMP 消息格式(RTMP Message Format)
RTMP 消息是 RTMP 協(xié)議中基本的數(shù)據(jù)單元堂淡。服務(wù)端和客戶端通過(guò)在網(wǎng)絡(luò)上發(fā)送消息實(shí)現(xiàn)之間的交互馋缅,消息包括但不限于音頻、視頻绢淀、數(shù)據(jù)萤悴。這里的消息是指滿足該協(xié)議格式的、可以切分成 Chunk 發(fā)送的消息皆的。RTMP 消息包含兩部分:消息頭(Message Header
)和有效數(shù)據(jù)(Message Body
)覆履。消息頭(Message Header
)包含以下信息:
Message Type:消息類(lèi)型,占用 1 個(gè)字節(jié)费薄。1~6 的消息類(lèi)型 ID 是為協(xié)議控制消息保留的硝全。
Length:有效負(fù)載的字節(jié)數(shù),即音視頻等信息的數(shù)據(jù)長(zhǎng)度楞抡。占用 3 個(gè)字節(jié)伟众,采用大端存儲(chǔ)(big-endian)模式。
Timestamp:時(shí)間戳召廷,占用 4 個(gè)字節(jié)凳厢,采用大端存儲(chǔ)模式。
Message Stream ID:消息流 ID竞慢,標(biāo)識(shí)消息所使用的流先紫,采用大端存儲(chǔ)模式。
2筹煮、分塊(Chunking)
RTMP 在收發(fā)數(shù)據(jù)時(shí)并不是以 Message 為單位的泡孩,而是把 Message 拆分成 Message Chunk 發(fā)送,被拆分的每個(gè) Message Chunk 都有一個(gè)唯一的 ID寺谤, 最后在接收端會(huì)按照 Message Chunk ID 將 Message Chunk 重新組裝成 Message仑鸥。在傳輸時(shí)必須在一個(gè) Message Chunk 發(fā)送完成之后才能開(kāi)始發(fā)送下一個(gè)。把數(shù)據(jù)量較大的 Message 拆分成較小的 Message Chunk变屁,在傳輸時(shí)可以避免優(yōu)先級(jí)低的消息持續(xù)發(fā)送阻塞優(yōu)先級(jí)高的數(shù)據(jù)眼俊,比如 Message 中的數(shù)據(jù)會(huì)包括視頻幀、音頻幀和控制信息粟关,如果持續(xù)發(fā)送音頻數(shù)據(jù)或者控制數(shù)據(jù)的話可能就會(huì)造成視頻幀的阻塞疮胖,然后就會(huì)造成看視頻時(shí)最煩人的卡頓現(xiàn)象。同時(shí)對(duì)于數(shù)據(jù)量較小的 Message,可以通過(guò)對(duì) Chunk Header 的字段來(lái)壓縮信息澎灸,從而減少信息的傳輸量院塞。
Message Chunk 的默認(rèn)大小是 128 字節(jié),在傳輸過(guò)程中性昭,通過(guò) Set Chunk Size 可以設(shè)置 Message Chunk 數(shù)據(jù)量的最大值拦止,在發(fā)送端和接受端會(huì)各自維護(hù)一個(gè) Chunk Size,可以分別設(shè)置這個(gè)值來(lái)改變自己這一方發(fā)送的 Chunk 的最大大小糜颠。大一點(diǎn)的 Message Chunk 減少了計(jì)算每個(gè) Message Chunk 的時(shí)間從而減少了 CPU 的占用率汹族,但是它會(huì)占用更多的時(shí)間在發(fā)送上,尤其是在低帶寬的網(wǎng)絡(luò)情況下其兴,很可能會(huì)阻塞后面更重要信息的傳輸顶瞒。小一點(diǎn)的 Message Chunk 可以減少這種阻塞問(wèn)題,但小的 Message Chunk 會(huì)引入過(guò)多額外的信息(Chunk 中的 Header)元旬,少量多次的傳輸也可能會(huì)造成發(fā)送的間斷導(dǎo)致不能充分利用高帶寬的優(yōu)勢(shì)榴徐,因此并不適合在高比特率的流中傳輸。在實(shí)際發(fā)送時(shí)應(yīng)對(duì)要發(fā)送的數(shù)據(jù)用不同的 Chunk Size 去嘗試匀归,通過(guò)抓包分析等手段得出合適的 Message Chunk 大小坑资,并且在傳輸過(guò)程中可以根據(jù)當(dāng)前的帶寬信息和實(shí)際信息的大小動(dòng)態(tài)調(diào)整 Message Chunk 的大小,從而盡量提高 CPU 的利用率并減少信息的阻塞機(jī)率朋譬。
3盐茎、RTMP 消息塊格式(RTMP Message Chunk Format)
Message Chunk 由塊頭(Chunk Header
)和數(shù)據(jù)(Chunk Data
)組成,Chunk Header
包含 3 部分:基本頭(Basic Header
)徙赢、消息頭(Message Header
)和擴(kuò)展時(shí)間戳(Extended Timestap
)字柠。
3.1、基本頭(Basic Header)
該部分編碼Chunk Stream ID
(流通道 ID狡赐,簡(jiǎn)稱(chēng) CSID
) 和 Chunk Type(下圖中fmt
字段)窑业。基本頭的長(zhǎng)度是 1~3 字節(jié)枕屉,采用小端存儲(chǔ)模式常柄,其長(zhǎng)度取決于CSID
,CSID
是一個(gè)變長(zhǎng)字段搀擂,用來(lái)唯一標(biāo)識(shí)一個(gè)特定的流通道西潘。Chunk Type 決定了消息頭(Message Header
)的編碼格式,長(zhǎng)度固定占 2 位(bit)哨颂。在足夠存儲(chǔ)這兩個(gè)字段的前提下應(yīng)該用盡可能少的字節(jié)來(lái)表示喷市,這樣能夠減少由于引入 Header 增加的數(shù)據(jù)量。
RTMP 最多支持 65597 個(gè)流威恼,CSID
在3~65599
范圍內(nèi)品姓。CSID
的 0
寝并,1
,2
為保留值腹备。0
表示塊基本頭為 2 個(gè)字節(jié)衬潦,并且CSID
范圍在64~319
之間(第二個(gè)字節(jié) + 64
);1
表示塊基本頭為 3 個(gè)字節(jié)植酥,并且 ID 范圍在64~65599
之間(第三個(gè)字節(jié) * 256 + 第二個(gè)字節(jié) + 64
)镀岛;取值在 3~63 之間的 ID 表示完整的CSID
。值2
是為低版本協(xié)議保留的惧互,用于協(xié)議控制消息和命令哎媚,第0~5
位(不重要的)表示CSID
喇伯。
當(dāng)Basic Header
長(zhǎng)度為 1 個(gè)字節(jié)時(shí)喊儡,CSID
占 6 位(6 位表示的范圍為0~63
),由于CSID
的 0稻据,1艾猜,2 為保留值,因此用戶可以定義的CSID
范圍為3~63
捻悯,此時(shí)的 CSID
是完整的匆赃,不需要計(jì)算得出:
當(dāng)Basic Header
長(zhǎng)度為 2 個(gè)字節(jié)時(shí),CSID
占 14 位今缚,此時(shí) RTMP 將與 Chunk Type 所在字節(jié)的其他位都置為 0算柳,剩下的 1 個(gè)字節(jié)來(lái)表示CSID - 64
,這樣共有 8 位(8 位表示的范圍為 0~255
)來(lái)存儲(chǔ)CSID
姓言,CSID
計(jì)算公式:CSID = 第二個(gè)字節(jié)的值 + 64
瞬项,因此計(jì)算得到CSID
范圍是64~319
:
當(dāng)Basic Header
長(zhǎng)度為 3 個(gè)字節(jié)時(shí),CSID
占 22 位何荚,此時(shí) RTMP 將與 Chunk Type 所在字節(jié)的其他位都置為 1囱淋,剩下的 16 位表示CSID - 64
,這樣共有 16 位(16 位表示的范圍為0~65535
)來(lái)存儲(chǔ)CSID
餐塘,因此計(jì)算得到 CSID
范圍是64~65599
妥衣。CSID
計(jì)算公式為:第三個(gè)字節(jié)值*255 + 第二個(gè)字節(jié)值 + 64.
。 64~319 范圍內(nèi)的SCID
可以用 2 字節(jié)長(zhǎng)度來(lái)編碼戒傻,也可以用 3 字節(jié)長(zhǎng)度來(lái)編碼税手。但實(shí)際實(shí)現(xiàn)時(shí)還是應(yīng)該秉著最少字節(jié)的原則使用 2 個(gè)字節(jié)的表示方式來(lái)表示范圍為64~319
的CSID
:
3.2、消息頭(Message Header)
Message Header 共有 4 種不同的格式需纳,Message Header
的格式和長(zhǎng)度取決于 Basic Header 中的 Chunk Type(即fmt
字段值)芦倒。協(xié)議實(shí)現(xiàn)方應(yīng)該用最緊湊的格式來(lái)表示塊消息頭。該部分編碼所發(fā)送消息的描述信息(無(wú)論是整個(gè)消息還是一部分)候齿。該部分的長(zhǎng)度可能是 0 字節(jié)熙暴、3 字節(jié)闺属、7 字節(jié)或者 11 字節(jié),其長(zhǎng)度取決于基本頭中指定的 Chunk Type周霉。
3.2.1掂器、塊類(lèi)型 0(Type 0)
0 類(lèi)型
的塊消息頭占 11 個(gè)字節(jié)長(zhǎng)度。在 Chunk Stream 發(fā)送第一個(gè) Chunk 時(shí)和Message Header
中的時(shí)間戳后退(即當(dāng)前 Chunk 的時(shí)間戳小于上一個(gè) Chunk 的時(shí)間戳俱箱,回退播放時(shí)會(huì)出現(xiàn)這種情況)時(shí)必須使用0 類(lèi)型
的 Chunk国瓮。
timestamp(3 字節(jié)):0 類(lèi)型的 Chunk 的絕對(duì)時(shí)間戳。 如果時(shí)間戳大于等于16777215(0xFFFFFF)狞谱,該字段的值必須為16777215乃摹,即 3 個(gè)字節(jié)全部置為 1,此時(shí)實(shí)際的 timestamp 會(huì)轉(zhuǎn)存到 Chunk 的Extended Timestamp
字段中跟衅,接受端在判斷 timestamp 字段 24 個(gè)位都為 1 時(shí)就會(huì)去Extended Timestamp
中解析實(shí)際的時(shí)間戳孵睬。
message length(3 字節(jié)): 消息長(zhǎng)度, 0 類(lèi)型和 1 類(lèi)型的 Chunk 包含此字段伶跷,表示消息長(zhǎng)度掰读,即當(dāng)前 Chunk 所屬消息的數(shù)據(jù)總長(zhǎng)度,而非當(dāng)前 Chunk 的數(shù)據(jù)長(zhǎng)度叭莫。除了最后一個(gè) Chunk蹈集,其他 Chunk 的數(shù)據(jù)長(zhǎng)度大小都等于 Chunk Size 大小。
message type id(1 字節(jié)):消息類(lèi)型 ID(數(shù)據(jù)的類(lèi)型)雇初,如 8 代表音頻數(shù)據(jù)拢肆、9 代表視頻數(shù)據(jù)。
message stream id(4 字節(jié)):消息流 ID靖诗,表示該 Chunk 所在流的 ID郭怪,采用小端存儲(chǔ)模式。通常呻畸,相同 Chunk Stream 中的消息屬于用一個(gè) Message Stream移盆。雖然不同的 Message Stream 復(fù)用相同的 Chunk Stream 會(huì)導(dǎo)致Message Header
無(wú)法有效壓縮,但是當(dāng)一個(gè) Message Stream 已關(guān)閉伤为,才打開(kāi)另外一個(gè) Message Stream咒循,就可以通過(guò)發(fā)送一個(gè)新的0 類(lèi)型
Chunk 來(lái)實(shí)現(xiàn)復(fù)用。
3.2.2绞愚、塊類(lèi)型 1(Type 1)
1 類(lèi)型
的塊消息頭占用 7 個(gè)字節(jié)長(zhǎng)度叙甸,不包含message stream id
,表示和上一個(gè) Chunk 的message stream id
是相同的位衩。對(duì)于傳輸大小可變消息的流(如多數(shù)視頻格式)裆蒸,在發(fā)送第一個(gè)消息之后的每個(gè)消息,第一個(gè)塊都應(yīng)該使用該類(lèi)型格式糖驴。
timestamp delta(3字節(jié)): 時(shí)間戳增量僚祷。 1 類(lèi)型
和2 類(lèi)型
的 Chunk 包含此字段佛致,存儲(chǔ)的是當(dāng)前 Chunk 的timestamp
和上一個(gè) Chunk 的timestamp
差值。當(dāng)它的值超過(guò) 3 個(gè)字節(jié)所能表示的最大值時(shí)辙谜,三個(gè)字節(jié)都置為 1俺榆,實(shí)際的時(shí)間戳差值就會(huì)轉(zhuǎn)存到Extended Timestamp
字段中,接受端在判斷timestamp delta
字段 24 個(gè)位都為 1 時(shí)就會(huì)去Extended timestamp
中解析實(shí)際的與上次時(shí)間戳的差值装哆。
message length(3 字節(jié)): 同0 類(lèi)型
的 message length
罐脊。
message type id(1 字節(jié)):同0 類(lèi)型
的 message type id
。
3.2.3蜕琴、塊類(lèi)型 2(Type 2)
2 類(lèi)型
的塊消息頭占用 3 個(gè)字節(jié)長(zhǎng)度萍桌,僅包timestamp delta
(同1 類(lèi)型
的 message delta
),表示當(dāng)前 Chunk 和上一次發(fā)送的 Chunk 的 message length
凌简、 message type id
和message stream id
都相同上炎。對(duì)于傳輸固定大小消息的流(如音頻和數(shù)據(jù)格式),在發(fā)送第一個(gè)消息之后的每一個(gè)消息号醉,第一個(gè)塊都應(yīng)該使用該類(lèi)型格式反症。
3.2.4辛块、塊類(lèi)型 3(Type 3)
3 類(lèi)型
的 Chunk 占用 0 字節(jié)畔派,也就是沒(méi)有消息頭,表示當(dāng)前 Chunk 的 Message Header
和上一個(gè) Chunk 的 Message Header
是相同的润绵。
而當(dāng)它跟在1 類(lèi)型
或者2 類(lèi)型
的 Chunk 后面時(shí)线椰,表示和上一個(gè) Chunk 的timestamp delta
是相同的。比如第一個(gè)0 類(lèi)型
Chunk 的timestamp
=1000尘盼;第二個(gè)2 類(lèi)型
Chunk 的timestamp delta
= 20憨愉,則時(shí)間戳為1000 + 20 = 1020;第三個(gè)3 類(lèi)型
Chunk 的timestamp delta
= 20卿捎,則時(shí)間戳為 1020 + 20 = 1040配紫;
下圖中展示了上圖中消息流分割成的塊:
當(dāng)它跟在0 類(lèi)型
的 Chunk 后面時(shí),表示和上一個(gè) Chunk 的時(shí)間戳是相同的午阵。這種情況出現(xiàn)在一個(gè) Message 被拆分成了多個(gè) Chunk 時(shí)躺孝,當(dāng)前 Chunk 和上一個(gè) Chunk 同屬于一個(gè) Message 的情況下。如下圖:
下圖是被分割成的塊:
4底桂、擴(kuò)展時(shí)間戳(Extended Timestamp)
前面提到在0 類(lèi)型
Chunk 的Message Header
中會(huì)有時(shí)間戳timestamp
植袍,以及1 類(lèi)型
和2 類(lèi)型
Chunk 的Message Header
中有時(shí)間戳差timestamp delta
,當(dāng)timestamp
或者timestamp delta
大于 3 個(gè)字節(jié)所能表示的最大數(shù)值16777215
(0xFFFFFF
)時(shí)籽懦,才會(huì)用到這個(gè)字段于个,否則這個(gè)字段值為 0
。擴(kuò)展時(shí)間戳占 4 個(gè)字節(jié)暮顺,能表示的最大數(shù)值就是4294967295
(0xFFFFFFFF
)厅篓。當(dāng)Extended Timestamp
啟用時(shí)秀存,timestamp
或者timestamp delta
要全置為1
,表示應(yīng)該去擴(kuò)展時(shí)間戳字段來(lái)提取真正的時(shí)間戳或者時(shí)間戳差羽氮。擴(kuò)展時(shí)間戳存儲(chǔ)的是完整值应又,而不是減去時(shí)間戳或者時(shí)間戳差的值。
三乏苦、協(xié)議控制消息(Protocol Control Messages)
RTMP Chunk Stream 用Message Type
為1
株扛,2
,3
汇荐,5
和6
的 Message 來(lái)作為協(xié)議控制消息洞就,這些 Message 包含 RTMP Chunk Stream Protocol 所需要的信息,所以協(xié)議控制消息是在 RTMP Chunk Stream Protocol 層的消息掀淘。在 Chunk Stream 中發(fā)送時(shí)旬蟋,Message Stream ID = 0
,CSID = 2
革娄。協(xié)議控制消息收到后立即生效咪橙,它們的時(shí)間戳信息是被忽略的填抬。
1、設(shè)置塊大小(Set Chunk Size准颓,Message Type = 1)
用于設(shè)置Chunk Data
的最大長(zhǎng)度 寇甸,Chunk Size 默認(rèn)是 128 字節(jié)(不能小于1字節(jié)署尤,通常應(yīng)該不低于 128 字節(jié))蝶锋。通信過(guò)程中可以通過(guò)發(fā)送該消息來(lái)設(shè)置Chunk Data
的大小,客戶端或服務(wù)端均可以修改此值言秸。例如软能,假設(shè)一個(gè)客戶端想要發(fā)送 158 字節(jié)的音頻數(shù)據(jù),而最大塊大小為 128 字節(jié)举畸。在這種情況下查排,客戶端可以向服務(wù)端發(fā)送該消息,通知它最大塊大小被設(shè)置為了 158 字節(jié)抄沮,這樣客戶端只用一個(gè)塊就可以發(fā)送這些音頻數(shù)據(jù)跋核,否則需要要將該消息拆分為Chunk Data
分別為128 字節(jié)和 30 字節(jié)的 2 個(gè) Chunk 發(fā)送。以下為 Set Chunk Size 協(xié)議控制消息的Chunk Data
:
0:該位必須為0合是;
chunk size(31位): 該字段以字節(jié)形式保存新的最大塊大小了罪,該值將用于后續(xù)的所有塊的發(fā)送,知道收到新的通知聪全。該值可取值范圍為1~2147483647
(0x7FFFFFFF
)泊藕,由于Chunk size 不能大于 Message 的長(zhǎng)度,所以超過(guò) Message 的長(zhǎng)度1677215
(0xFFFFFF
)的值是用不上的。
2娃圆、中止消息(Abort Message玫锋,Message Type = 2)
當(dāng)一個(gè) Message 被劃分為多個(gè) Chunk,接受端只接收到了部分 Chunk 時(shí)讼呢,發(fā)送該控制消息表示發(fā)送端不再傳輸同 Message 的 Chunk 了撩鹿,接受端接收到這個(gè)消息后要丟棄已收到不完整的Chunk。該控制消息的Chunk Data
中只有一個(gè)CSID
字段(32 字節(jié))悦屏,表示丟棄所有已接收到的塊流 ID 為CSID
的 Chunk节沦。
3、確認(rèn)消息(Acknowledgement础爬,Message Type = 3)
當(dāng)客戶端或服務(wù)端收到對(duì)端的消息大小等于窗口大懈帷(Window Size)的消息后要回饋一個(gè) ACK 給發(fā)送端告知對(duì)端可以繼續(xù)發(fā)送數(shù)據(jù)。窗口大小就是指在收到接受端返回的 ACK 前可以發(fā)送的最大字節(jié)數(shù)看蚜,返回的 ACK 中會(huì)帶有從發(fā)送上一個(gè) ACK 后接收到的總字節(jié)數(shù)叫搁。
4、窗口大小確認(rèn)(Window Acknowledgement Size供炎,Message Type = 5)
客戶端或服務(wù)端發(fā)送該消息來(lái)告知對(duì)端在兩個(gè) ACK 之間所使用的窗口大小渴逻。發(fā)送端發(fā)送窗口大小的數(shù)據(jù)后等待接收端發(fā)送 ACK。接收端在上一個(gè) ACK 發(fā)送之后音诫,接收到窗口大小的數(shù)據(jù)后必須發(fā)送 ACK惨奕,如果之前沒(méi)有發(fā)送過(guò) ACK 就從會(huì)話開(kāi)始之后算起接收到窗口大小的數(shù)據(jù)后發(fā)送 ACK。
5纽竣、設(shè)置對(duì)等帶寬(Set Peer Bandwidth墓贿,Message Type = 6)
客戶端或服務(wù)端發(fā)送該消息用來(lái)限制對(duì)端的輸出帶寬。對(duì)端收到該消息后通過(guò)將已發(fā)送但未收到的消息大小限制為該消息中的Acknowledgement Window size
來(lái)實(shí)現(xiàn)限制發(fā)送帶寬蜓氨。如果該消息中的Acknowledgement Window size
與上一次發(fā)送給發(fā)送端的不同的話發(fā)送端要回饋一個(gè) Window Acknowledgement Size 消息。
Limit Type(限制類(lèi)型)有以下選項(xiàng):
0 - Hard:消息接收端應(yīng)該將輸出帶寬限制為該消息中Acknowledgement Window size
指定的大卸游啊穴吹;
1 - Soft:消息接收端應(yīng)該將輸出帶寬限制為該消息中Acknowledgement Window size
指定的大小或者當(dāng)前窗口大小,限制為二者中的最小值嗜侮。
2 - Dynamic:如果上次的 Set Peer Bandwidth 消息中的Limit Type
為0 - Hard
港令,本次也按0 - Hard
處理,否則忽略本消息锈颗。
四顷霹、RTMP 消息類(lèi)型(Types of Messages)
1、命令消息(Command Message击吱,Message Type = 20淋淀,Message Type = 17)
用于客戶端和服務(wù)器間傳遞在對(duì)端執(zhí)行某些操作的命令消息,比如發(fā)送這些消息來(lái)完成連接覆醇、創(chuàng)建流朵纷、發(fā)布炭臭、播放、暫停等操作袍辞,以及通知發(fā)送者命令請(qǐng)求狀態(tài)和結(jié)果等等鞋仍。當(dāng)消息使用AMF0
編碼時(shí),Message Type = 20搅吁,使用AMF3
編碼時(shí) Message Type = 17威创。
命令消息中包含Command Name(命令名稱(chēng))、transaction ID(命令標(biāo)識(shí))谎懦、command object(相關(guān)參數(shù))那婉。接受端收到命令消息后,會(huì)返回發(fā)送端以下三種消息中的一種:_result
響應(yīng)消息表示接受該命令党瓮,對(duì)端可以繼續(xù)往下執(zhí)行流程详炬;_error
響應(yīng)消息代表拒絕該命令要執(zhí)行的操作;method name
響應(yīng)消息代表要在命令消息的發(fā)送端執(zhí)行的函數(shù)名稱(chēng)寞奸。響應(yīng)消息都要帶有當(dāng)前收到的命令消息中的 transaction ID 來(lái)表示接收端本次回應(yīng)的是哪個(gè)命令消息呛谜。
1.1、連接層命令(NetConnection Commands)
連接層命令用于管理客戶端和服務(wù)端之間的鏈接狀態(tài)枪萄。同時(shí)也提供了異步遠(yuǎn)程方法調(diào)用(RPC)在對(duì)端執(zhí)行某方法隐岛,以下是常見(jiàn)的連接層的命令:
1.1.1、connect
用于客戶端向服務(wù)器發(fā)送鏈接請(qǐng)求瓷翻【郯迹客戶端發(fā)送到服務(wù)端的消息結(jié)構(gòu)如下:
Command Name:命令名稱(chēng),當(dāng)前命令設(shè)置為 "connect"齐帚;
Transaction ID:對(duì)于連接請(qǐng)求該字段恒為 1妒牙;
Command Object:命令參數(shù),用鍵值對(duì)集合表示(可參考官方文檔 7.2.1.1 章節(jié))对妄;
Optional User:用戶自定義的額外信息湘今;
執(zhí)行 connect 命令時(shí)的消息流順序:
1、客戶端向服務(wù)端發(fā)送 connect 命令消息請(qǐng)求建立連接剪菱;
2摩瞎、服務(wù)端收到 connect 命令消息后向客戶端發(fā)送 Window Acknowledgement Size(窗口大小確認(rèn))消息,并且服務(wù)端連接 connect 命令消息提及的應(yīng)用孝常;
3旗们、服務(wù)端向客戶端發(fā)送 Set Peer Bandwidth(設(shè)置對(duì)等帶寬)消息;
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
用于遠(yuǎn)程在對(duì)端執(zhí)行某函數(shù)(即 RPC:遠(yuǎn)程進(jìn)程調(diào)用)。消息的結(jié)構(gòu)如下:
Procedure Name:要調(diào)用的遠(yuǎn)程進(jìn)程名稱(chēng)锹锰;
Transaction ID:如果想要對(duì)端發(fā)送響應(yīng)消息芥炭,需要設(shè)置為非 0 值,否則置為 0恃慧;
Command Object:命令參數(shù)园蝠;
Optional Arguments:用戶自定義參數(shù);
如果消息的Transaction ID
不為 0 的話痢士,對(duì)端需要對(duì)該命令做出響應(yīng)彪薛,響應(yīng)的消息結(jié)構(gòu)如下:
Command Name:命令名稱(chēng);
Transaction ID:接收到的命令消息中的Transaction ID
怠蹂;
Command Object:命令參數(shù)善延;
Response:調(diào)用方法的響應(yīng);
1.1.3城侧、createStream
客戶端發(fā)給服務(wù)端此命令消息創(chuàng)建傳輸消息的通道易遣,從而可以在這個(gè)流中傳輸音頻、視頻或者元數(shù)據(jù)等嫌佑,傳輸信息單元為 Chunk豆茫。
客戶端發(fā)給服務(wù)端消息的結(jié)構(gòu):
Command Name:命令名稱(chēng),當(dāng)前命令設(shè)置為 "createStream"屋摇;
Transaction ID:消息標(biāo)識(shí)揩魂;
Command Object:命令參數(shù);
服務(wù)端回饋給客戶端消息結(jié)構(gòu):
Command Name:命令名稱(chēng)摊册,當(dāng)前命令設(shè)置為 "createStream"肤京;
Transaction ID:與客戶端發(fā)送到服務(wù)端消息的Transaction ID
相同;
Command Object:命令參數(shù)茅特;
Stream ID:返回 Stream ID 或者錯(cuò)誤信息對(duì)象。
1.2棋枕、流連接層命令(NetStream Commands)
NetStream 建立在 NetConnection 之上白修,用于定義傳輸音頻和視頻等信息的通道。在傳輸層協(xié)議之上只能連接一個(gè) NetConnection重斑,但一個(gè) NetConnection 可以建立多個(gè) NetStream 來(lái)建立不同的流通道傳輸數(shù)據(jù)兵睛。服務(wù)端收到命令后會(huì)通過(guò) onStatus 命令來(lái)響應(yīng)客戶端,表示當(dāng)前 NetStream 的狀態(tài)。onStatus 命令消息結(jié)構(gòu)如下:
2祖很、數(shù)據(jù)消息(Data Message笛丙,Message Type = 18,Message Type = 15)
傳遞一些 MetaData(元數(shù)據(jù)假颇,比如主題胚鸯、創(chuàng)建時(shí)間和時(shí)長(zhǎng)等等)或者用戶自定義的一些消息。當(dāng)消息使用AMF0
編碼時(shí)笨鸡,Message Type = 18姜钳,使用AMF3
編碼時(shí) Message Type = 15。
3形耗、共享消息(Shared Object Message哥桥,Message Type = 19,Message Type = 16)
表示一個(gè) Flash 類(lèi)型的對(duì)象(由鍵值對(duì)的集合組成)激涤,用于多客戶端拟糕,多實(shí)例進(jìn)行同步時(shí)使用。每條消息可包含多個(gè)事件倦踢。當(dāng)消息使用AMF0
編碼時(shí)送滞,Message Type = 19,使用AMF3
編碼時(shí) Message Type = 16硼一。
4累澡、音頻消息(Audio Message,Message Type = 8)
客戶端或服務(wù)端通過(guò)發(fā)送此消息來(lái)發(fā)送音頻數(shù)據(jù)給對(duì)方般贼。
5愧哟、視頻消息(Video Message,Message Type = 9)
客戶端或服務(wù)端通過(guò)發(fā)送此消息來(lái)發(fā)送視頻數(shù)據(jù)給對(duì)方哼蛆。
6蕊梧、組合消息(Aggregate Message,Message Type = 22)
一個(gè)組合消息消息包含多個(gè)子 RTMP 消息腮介。
7肥矢、用戶控制消息(User Control Messages,Message Type = 4)
RTMP 協(xié)議將Message Type
為4
的消息作為了用戶控制消息叠洗,在 Chunk Stream 中發(fā)送時(shí)甘改,Message Stream ID = 0
,CSID = 2
灭抑。接收端收到用戶控制消息后立即生效十艾,用戶控制消息的時(shí)間戳信息是被忽略的。和前面提到的協(xié)議控制消息不同腾节,用戶控制消息是在 RTMP Protocol 層的忘嫉,而不是在 RTMP Chunk Stream Protocol 層的荤牍。
客戶端或服務(wù)端通過(guò)發(fā)送該消息告知對(duì)端用戶控制事件。該消息攜帶事件類(lèi)型和事件數(shù)據(jù)兩部分庆冕。開(kāi)頭的 2 個(gè)字節(jié)用于指定事件類(lèi)型康吵,后面緊跟著是事件數(shù)據(jù)。事件數(shù)據(jù)字段長(zhǎng)度可變访递,使用 RTMP Chunk Stream 傳輸時(shí)晦嵌,最大塊大小要足夠大,以便可以使用一個(gè)單獨(dú)的 Chunk 進(jìn)行傳輸用戶控制消息力九。
用戶控制消息支持以下事件:
0(流開(kāi)始):服務(wù)端發(fā)送該事件耍铜,用來(lái)通知客戶端一個(gè)流已經(jīng)可以用來(lái)傳輸消息了。默認(rèn)情況下跌前,該事件是在收到客戶端連接指令并成功處理后發(fā)送的第一個(gè)事件棕兼。該事件的Event Data
使用 4 個(gè)字節(jié)來(lái)表示可用的message stream id
。
1(流結(jié)束):服務(wù)端發(fā)送該事件抵乓,用來(lái)通知客戶端其在流中請(qǐng)求的回放數(shù)據(jù)已經(jīng)結(jié)束了伴挚。如果沒(méi)有額外的指令,將不會(huì)再發(fā)送任何數(shù)據(jù)灾炭,而客戶端會(huì)丟棄之后從該流接收到的消息茎芋。該事件的Event Data
使用 4 個(gè)字節(jié)來(lái)表示回放完成的message stream id
。
2(流枯竭):服務(wù)端發(fā)送該事件蜈出,用來(lái)通知客戶端流中沒(méi)有更多數(shù)據(jù)了田弥。如果服務(wù)端在一定時(shí)間后沒(méi)有探測(cè)到更多數(shù)據(jù),它就可以通知所有訂閱該流的客戶端铡原,流已經(jīng)枯竭偷厦。該事件的Event Data
使用 4 個(gè)字節(jié)來(lái)表示枯竭的message stream id
。
3(設(shè)置緩沖區(qū)大醒嗫獭):客戶端發(fā)送該事件只泼,用來(lái)告知服務(wù)端緩沖區(qū)大小(單位毫秒)卵洗。該事件在服務(wù)端開(kāi)始處理流數(shù)據(jù)之前發(fā)送请唱。Event Data
中,前 4 個(gè)字節(jié)表示message stream id
过蹂,后 4 字節(jié)表示緩沖區(qū)大惺蟆(單位毫秒)。
4(流已錄制):服務(wù)端發(fā)送該事件酷勺,用來(lái)通知客戶端該流是一個(gè)錄制流孽惰。事件數(shù)據(jù)用4個(gè)字節(jié)表示錄制的message stream id
。
6(ping請(qǐng)求):服務(wù)端發(fā)送該事件鸥印,用來(lái)探測(cè)客戶端是否處于可達(dá)狀態(tài)勋功。Event Data
里攜帶的是一個(gè) 4 字節(jié)長(zhǎng)度的時(shí)間戳,表示服務(wù)端分發(fā)該事件時(shí)的服務(wù)器本地時(shí)間库说】裥客戶端收到后用 ping 請(qǐng)求后應(yīng)該回復(fù)服務(wù)端。
7(ping響應(yīng)):客戶端用該事件響應(yīng)服務(wù)端的 ping 請(qǐng)求潜的,Event Data
為收到的 ping 請(qǐng)求中攜帶的時(shí)間戳骚揍,長(zhǎng)度 4 字節(jié)。
五啰挪、握手(Handshake)
RTMP 的連接開(kāi)始于握手信不。握手內(nèi)容不同于協(xié)議的其它部分,它包含三個(gè)固定大小的塊亡呵,而不是帶頭信息的變長(zhǎng)塊抽活。
客戶端(發(fā)起連接的端點(diǎn))和服務(wù)器各自發(fā)送相同的三個(gè)塊。為了演示锰什,這三個(gè)塊客戶端發(fā)送的被記做 C0下硕,C1,C2汁胆,服務(wù)發(fā)送的被記做 S0梭姓,S1,S2嫩码。
1誉尖、握手順序
RTMP 協(xié)議本身并沒(méi)有規(guī)定這 6 個(gè)消息的具體傳輸順序,但 RTMP 協(xié)議的實(shí)現(xiàn)者需要保證這幾點(diǎn):
客戶端發(fā)送 C0 和 C1 塊開(kāi)始握手铸题。
客戶端必須等接收到 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 包只有八個(gè)位,可以看成一個(gè) 8 位的整數(shù)墙牌。
以下是CO和S0包的字段解釋?zhuān)?/p>
版本號(hào)(8 位):在 C0 包中涡驮,該字段表示客戶端請(qǐng)求的 RTMP 版本。在 S0 中喜滨,該字段表示服務(wù)器選擇的 RTMP 版本捉捅。本規(guī)范所定義的版本是 3∷浞纾可選值中棒口,0-2 是早期版本所用的寄月,已被丟棄;4-31 保留在未來(lái)使用无牵;32-255 不允許使用(為了區(qū)分其他以某一可見(jiàn)字符開(kāi)始的文本協(xié)議)漾肮。如果服務(wù)器不能識(shí)別客戶端請(qǐng)求的版本, 應(yīng)該返回 3茎毁,客戶端可能選擇降級(jí)到版本3克懊,也可能放棄握手。
3七蜘、C1 和 S1 格式
C1 和 S1 包的長(zhǎng)度固定為1536字節(jié)谭溉, 包含以下字段:
時(shí)間戳(4 字節(jié)):該字段承載一個(gè)時(shí)間戳,該時(shí)間戳應(yīng)該作為發(fā)送端點(diǎn)所有后續(xù)塊的時(shí)間戳起始時(shí)間橡卤“缒睿可以是 0,也可以是其他的任意值蒜魄。為了同步多個(gè)塊流扔亥,端點(diǎn)可能會(huì)發(fā)送其他塊流時(shí)間戳的當(dāng)前值。
零值(4 字節(jié)):該字段所有值都必須為 0谈为。
隨機(jī)數(shù)據(jù)(1528 字節(jié)):該字段可以是任意值旅挤。因?yàn)槊總€(gè)端點(diǎn)都需要區(qū)分自己和其他端點(diǎn)初始化的握手響應(yīng),所以此數(shù)據(jù)應(yīng)該有充分的隨機(jī)性伞鲫,但是沒(méi)必要使用加密安全的隨機(jī)值或動(dòng)態(tài)值粘茄。
4、C2 和 S2 格式
C2 和 S2 包的長(zhǎng)度固定為 1536 字節(jié)秕脓,基本上分別是 S1 和 C1 的回復(fù)柒瓣, 包含以下字段:
時(shí)間(4字節(jié)): 這個(gè)字段必須包含了一個(gè)時(shí)間戳,它是由對(duì)端發(fā)送過(guò)來(lái)吠架。對(duì)于 C2 來(lái)說(shuō)是 S1芙贫,對(duì)于 S2 來(lái)說(shuō)是 C1;
時(shí)間2(4字節(jié)):這個(gè)字段必須包含前一個(gè)對(duì)端發(fā)送過(guò)來(lái)的并被讀取的包(S1 或 C1)傍药;
隨機(jī)數(shù)(1528字節(jié)):這個(gè)字段必須包含對(duì)端發(fā)送過(guò)來(lái)的隨機(jī)數(shù)對(duì) S1 來(lái)說(shuō)是 C2磺平,對(duì)于 S2 來(lái)說(shuō)是 C1。兩端都可以時(shí)間字段和時(shí)間 2 字段結(jié)合當(dāng)前的時(shí)間來(lái)快速寬帶和(或)連接的延遲拐辽,但這個(gè)方法不太可能很有用處拣挪。
5、握手流程示意圖
下面是握手圖示中提到的各個(gè)階段具體內(nèi)容:
未初始化(Uninitialized):協(xié)議的版本發(fā)送出去的這個(gè)階段俱诸〔と埃客戶端與服務(wù)器都是未初始化態(tài)≌龃睿客戶端在 C0 中發(fā)送協(xié)議版本赶诊。如果服務(wù)器支持這個(gè)版本笼平,它將回應(yīng) S0 和 S1。如果不支持甫何,服務(wù)器將會(huì)采取適當(dāng)措施的回應(yīng)出吹。在 RTMP 中,這個(gè)措施是終止連接辙喂;
版本已發(fā)送(Version Sent):客戶端和服務(wù)器在未初始化態(tài)后是版本已發(fā)送態(tài)○椋客戶端等待 S1巍耗,服務(wù)器在等待 C1。在接收到響應(yīng)包后渐排,客戶端發(fā)送 C2炬太,服務(wù)器發(fā)送 S2。狀態(tài)就變成了確認(rèn)已發(fā)送驯耻;
確認(rèn)已發(fā)送(Ack Send):客戶端和服務(wù)器分別在等 S2 和 C2亲族;
握手完成(Handshake Done):客戶端與服務(wù)器可以交換消息了。