RTMP協(xié)議詳解

RTMP協(xié)議是Real Time Message Protocol(實(shí)時(shí)信息傳輸協(xié)議)的縮寫(xiě),它是由Adobe公司提出的一種應(yīng)用層的協(xié)議,用來(lái)解決多媒體數(shù)據(jù)傳輸流的多路復(fù)用(Multiplexing)和分包(packetizing)的問(wèn)題。隨著VR技術(shù)的發(fā)展,視頻直播等領(lǐng)域逐漸活躍起來(lái),RTMP作為業(yè)內(nèi)廣泛使用的協(xié)議也重新被相關(guān)開(kāi)發(fā)者重視起來(lái)。正好最近在從事這方面的工作,在此記錄下自己對(duì)RTMP的理解,文章內(nèi)容多翻譯自英文版RTMP文檔,按照本人的理解重新整理,希望可以幫助想要了解RTMP協(xié)議的朋友,也方面自己日后查閱。

1. 總體介紹:

RTMP協(xié)議是應(yīng)用層協(xié)議,是要靠底層可靠的傳輸層協(xié)議(通常是TCP)來(lái)保證信息傳輸?shù)目煽啃缘?。在基于傳輸層協(xié)議的鏈接建立完成后,RTMP協(xié)議也要客戶(hù)端和服務(wù)器通過(guò)“握手”來(lái)建立基于傳輸層鏈接之上的RTMP Connection鏈接,在Connection鏈接上會(huì)傳輸一些控制信息,如SetChunkSize,SetACKWindowSize。其中CreateStream命令會(huì)創(chuàng)建一個(gè)Stream鏈接,用于傳輸具體的音視頻數(shù)據(jù)和控制這些信息傳輸?shù)拿钚畔?。RTMP協(xié)議傳輸時(shí)會(huì)對(duì)數(shù)據(jù)做自己的格式化,這種格式的消息我們稱(chēng)之為RTMP Message,而實(shí)際傳輸?shù)臅r(shí)候?yàn)榱烁玫貙?shí)現(xiàn)多路復(fù)用、分包和信息的公平性,發(fā)送端會(huì)把Message劃分為帶有Message ID的Chunk,每個(gè)Chunk可能是一個(gè)單獨(dú)的Message,也可能是Message的一部分,在接受端會(huì)根據(jù)chunk中包含的data的長(zhǎng)度,message id和message的長(zhǎng)度把chunk還原成完整的Message,從而實(shí)現(xiàn)信息的收發(fā)。

2. 握手

要建立一個(gè)有效的RTMP Connection鏈接,首先要“握手”:客戶(hù)端要向服務(wù)器發(fā)送C0,C1,C2(按序)三個(gè)chunk,服務(wù)器向客戶(hù)端發(fā)送S0,S1,S2(按序)三個(gè)chunk,然后才能進(jìn)行有效的信息傳輸。RTMP協(xié)議本身并沒(méi)有規(guī)定這6個(gè)Message的具體傳輸順序,但RTMP協(xié)議的實(shí)現(xiàn)者需要保證這幾點(diǎn):

  • 客戶(hù)端要等收到S1之后才能發(fā)送C2

  • 客戶(hù)端要等收到S2之后才能發(fā)送其他信息(控制信息和真實(shí)音視頻等數(shù)據(jù))

  • 服務(wù)端要等到收到C0之后發(fā)送S1

  • 服務(wù)端必須等到收到C1之后才能發(fā)送S2

  • 服務(wù)端必須等到收到C2之后才能發(fā)送其他信息(控制信息和真實(shí)音視頻等數(shù)據(jù))

如果每次發(fā)送一個(gè)握手chunk的話(huà)握手順序會(huì)是這樣:

image

理論上來(lái)講只要滿(mǎn)足以上條件,如何安排6個(gè)Message的順序都是可以的,但實(shí)際實(shí)現(xiàn)中為了在保證握手的身份驗(yàn)證功能的基礎(chǔ)上盡量減少通信的次數(shù),一般的發(fā)送順序是這樣的,這一點(diǎn)可以通過(guò)wireshark抓ffmpeg推流包進(jìn)行驗(yàn)證:

|c(diǎn)lient|Server |

|---C0+C1—->|

|<--S0+S1+S2– |

|---C2----> |

3. RTMP Chunk Stream

Chunk Stream是對(duì)傳輸RTMP Chunk的流的邏輯上的抽象,客戶(hù)端和服務(wù)器之間有關(guān)RTMP的信息都在這個(gè)流上通信。這個(gè)流上的操作也是我們關(guān)注RTMP協(xié)議的重點(diǎn)。

3.1 Message(消息)

這里的Message是指滿(mǎn)足該協(xié)議格式的、可以切分成Chunk發(fā)送的消息,消息包含的字段如下:

Timestamp(時(shí)間戳):消息的時(shí)間戳(但不一定是當(dāng)前時(shí)間,后面會(huì)介紹),4個(gè)字節(jié)

Length(長(zhǎng)度):是指Message Payload(消息負(fù)載)即音視頻等信息的數(shù)據(jù)的長(zhǎng)度,3個(gè)字節(jié)

TypeId(類(lèi)型Id):消息的類(lèi)型Id,1個(gè)字節(jié)

Message Stream ID(消息的流ID):每個(gè)消息的唯一標(biāo)識(shí),劃分成Chunk和還原Chunk為Message的時(shí)候都是根據(jù)這個(gè)ID來(lái)辨識(shí)是否是同一個(gè)消息的Chunk的,4個(gè)字節(jié),并且以小端格式存儲(chǔ)

3.2 Chunking(Message分塊)

RTMP在收發(fā)數(shù)據(jù)的時(shí)候并不是以Message為單位的,而是把Message拆分成Chunk發(fā)送,而且必須在一個(gè)Chunk發(fā)送完成之后才能開(kāi)始發(fā)送下一個(gè)Chunk。每個(gè)Chunk中帶有MessageID代表屬于哪個(gè)Message,接受端也會(huì)按照這個(gè)id來(lái)將chunk組裝成Message。

為什么RTMP要將Message拆分成不同的Chunk呢?通過(guò)拆分,數(shù)據(jù)量較大的Message可以被拆分成較小的“Message”,這樣就可以避免優(yōu)先級(jí)低的消息持續(xù)發(fā)送阻塞優(yōu)先級(jí)高的數(shù)據(jù),比如在視頻的傳輸過(guò)程中,會(huì)包括視頻幀,音頻幀和RTMP控制信息,如果持續(xù)發(fā)送音頻數(shù)據(jù)或者控制數(shù)據(jù)的話(huà)可能就會(huì)造成視頻幀的阻塞,然后就會(huì)造成看視頻時(shí)最煩人的卡頓現(xiàn)象。同時(shí)對(duì)于數(shù)據(jù)量較小的Message,可以通過(guò)對(duì)Chunk Header的字段來(lái)壓縮信息,從而減少信息的傳輸量。(具體的壓縮方式會(huì)在后面介紹)

Chunk的默認(rèn)大小是128字節(jié),在傳輸過(guò)程中,通過(guò)一個(gè)叫做Set Chunk Size的控制信息可以設(shè)置Chunk數(shù)據(jù)量的最大值,在發(fā)送端和接受端會(huì)各自維護(hù)一個(gè)Chunk Size,可以分別設(shè)置這個(gè)值來(lái)改變自己這一方發(fā)送的Chunk的最大大小。大一點(diǎn)的Chunk減少了計(jì)算每個(gè)chunk的時(shí)間從而減少了CPU的占用率,但是它會(huì)占用更多的時(shí)間在發(fā)送上,尤其是在低帶寬的網(wǎng)絡(luò)情況下,很可能會(huì)阻塞后面更重要信息的傳輸。小一點(diǎn)的Chunk可以減少這種阻塞問(wèn)題,但小的Chunk會(huì)引入過(guò)多額外的信息(Chunk中的Header),少量多次的傳輸也可能會(huì)造成發(fā)送的間斷導(dǎo)致不能充分利用高帶寬的優(yōu)勢(shì),因此并不適合在高比特率的流中傳輸。在實(shí)際發(fā)送時(shí)應(yīng)對(duì)要發(fā)送的數(shù)據(jù)用不同的Chunk Size去嘗試,通過(guò)抓包分析等手段得出合適的Chunk大小,并且在傳輸過(guò)程中可以根據(jù)當(dāng)前的帶寬信息和實(shí)際信息的大小動(dòng)態(tài)調(diào)整Chunk的大小,從而盡量提高CPU的利用率并減少信息的阻塞機(jī)率。

3.3 Chunk Format(塊格式)

image

3.3.1 Basic Header(基本的頭信息):

包含了chunk stream ID(流通道Id)和chunk type(chunk的類(lèi)型),chunk stream id一般被簡(jiǎn)寫(xiě)為CSID,用來(lái)唯一標(biāo)識(shí)一個(gè)特定的流通道,chunk type決定了后面Message Header的格式。Basic Header的長(zhǎng)度可能是1,2,或3個(gè)字節(jié),其中chunk type的長(zhǎng)度是固定的(占2位,注意單位是位,bit),Basic Header的長(zhǎng)度取決于CSID的大小,在足夠存儲(chǔ)這兩個(gè)字段的前提下最好用盡量少的字節(jié)從而減少由于引入Header增加的數(shù)據(jù)量。

RTMP協(xié)議支持用戶(hù)自定義[3,65599]之間的CSID,0,1,2由協(xié)議保留表示特殊信息。0代表Basic Header總共要占用2個(gè)字節(jié),CSID在[64,319]之間,1代表占用3個(gè)字節(jié),CSID在[64,65599]之間,2代表該chunk是控制信息和一些命令信息,后面會(huì)有詳細(xì)的介紹。

chunk type的長(zhǎng)度固定為2位,因此CSID的長(zhǎng)度是(6=8-2)、(14=16-2)、(22=24-2)中的一個(gè)。

當(dāng)Basic Header為1個(gè)字節(jié)時(shí),CSID占6位,6位最多可以表示64個(gè)數(shù),因此這種情況下CSID在[0,63]之間,其中用戶(hù)可自定義的范圍為[3,63]。

image

當(dāng)Basic Header為2個(gè)字節(jié)時(shí),CSID占14位,此時(shí)協(xié)議將與chunk type所在字節(jié)的其他位都置為0,剩下的一個(gè)字節(jié)來(lái)表示CSID-64,這樣共有8個(gè)二進(jìn)制位來(lái)存儲(chǔ)CSID,8位可以表示[0,255]共256個(gè)數(shù),因此這種情況下CSID在[64,319],其中319=255+64。

image

當(dāng)Basic Header為3個(gè)字節(jié)時(shí),CSID占22位,此時(shí)協(xié)議將[2,8]字節(jié)置為1,余下的16個(gè)字節(jié)表示CSID-64,這樣共有16個(gè)位來(lái)存儲(chǔ)CSID,16位可以表示[0,65535]共65536個(gè)數(shù),因此這種情況下CSID在[64,65599],其中65599=65535+64,需要注意的是,Basic Header是采用小端存儲(chǔ)的方式,越往后的字節(jié)數(shù)量級(jí)越高,因此通過(guò)這3個(gè)字節(jié)每一位的值來(lái)計(jì)算CSID時(shí),應(yīng)該是:<第三個(gè)字節(jié)的值>x256+<第二個(gè)字節(jié)的值>+64

image

可以看到2個(gè)字節(jié)和3個(gè)字節(jié)的Basic Header所能表示的CSID是有交集的[64,319],但實(shí)際實(shí)現(xiàn)時(shí)還是應(yīng)該秉著最少字節(jié)的原則使用2個(gè)字節(jié)的表示方式來(lái)表示[64,319]的CSID。

3.3.2 Message Header(消息的頭信息)

包含了要發(fā)送的實(shí)際信息(可能是完整的,也可能是一部分)的描述信息。Message Header的格式和長(zhǎng)度取決于Basic Header的chunk type,共有4種不同的格式,由上面所提到的Basic Header中的fmt字段控制。其中第一種格式可以表示其他三種表示的所有數(shù)據(jù),但由于其他三種格式是基于對(duì)之前chunk的差量化的表示,因此可以更簡(jiǎn)潔地表示相同的數(shù)據(jù),實(shí)際使用的時(shí)候還是應(yīng)該采用盡量少的字節(jié)表示相同意義的數(shù)據(jù)。以下按照字節(jié)數(shù)從多到少的順序分別介紹這4種格式的Message Header。

Type=0:

image

type=0時(shí)Message Header占用11個(gè)字節(jié),其他三種能表示的數(shù)據(jù)它都能表示,但在chunk stream的開(kāi)始的第一個(gè)chunk和頭信息中的時(shí)間戳后退(即值與上一個(gè)chunk相比減小,通常在回退播放的時(shí)候會(huì)出現(xiàn)這種情況)的時(shí)候必須采用這種格式。

timestamp(時(shí)間戳):占用3個(gè)字節(jié),因此它最多能表示到16777215=0xFFFFFF=2

24-1, 當(dāng)它的值超過(guò)這個(gè)最大值時(shí),這三個(gè)字節(jié)都置為1,這樣實(shí)際的timestamp會(huì)轉(zhuǎn)存到Extended Timestamp字段中,接受端在判斷timestamp字段24個(gè)位都為1時(shí)就會(huì)去Extended timestamp中解析實(shí)際的時(shí)間戳。

message length(消息數(shù)據(jù)的長(zhǎng)度):占用3個(gè)字節(jié),表示實(shí)際發(fā)送的消息的數(shù)據(jù)如音頻幀、視頻幀等數(shù)據(jù)的長(zhǎng)度,單位是字節(jié)。注意這里是Message的長(zhǎng)度,也就是chunk屬于的Message的總數(shù)據(jù)長(zhǎng)度,而不是chunk本身Data的數(shù)據(jù)的長(zhǎng)度。

message type id(消息的類(lèi)型id):占用1個(gè)字節(jié),表示實(shí)際發(fā)送的數(shù)據(jù)的類(lèi)型,如8代表音頻數(shù)據(jù)、9代表視頻數(shù)據(jù)。

msg stream id(消息的流id):占用4個(gè)字節(jié),表示該chunk所在的流的ID,和Basic Header的CSID一樣,它采用小端存儲(chǔ)的方式,

Type = 1:

image

type=1時(shí)Message Header占用7個(gè)字節(jié),省去了表示msg stream id的4個(gè)字節(jié),表示此chunk和上一次發(fā)的chunk所在的流相同,如果在發(fā)送端只和對(duì)端有一個(gè)流鏈接的時(shí)候可以盡量去采取這種格式。

timestamp delta:占用3個(gè)字節(jié),注意這里和type=0時(shí)不同,存儲(chǔ)的是和上一個(gè)chunk的時(shí)間差。類(lèi)似上面提到的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í)機(jī)的與上次時(shí)間戳的差值。

Type = 2:

image

type=2時(shí)Message Header占用3個(gè)字節(jié),相對(duì)于type=1格式又省去了表示消息長(zhǎng)度的3個(gè)字節(jié)和表示消息類(lèi)型的1個(gè)字節(jié),表示此chunk和上一次發(fā)送的chunk所在的流、消息的長(zhǎng)度和消息的類(lèi)型都相同。余下的這三個(gè)字節(jié)表示timestamp delta,使用同type=1

Type = 3

0字節(jié)?。?!好吧,它表示這個(gè)chunk的Message Header和上一個(gè)是完全相同的,自然就不用再傳輸一遍了。當(dāng)它跟在Type=0的chunk后面時(shí),表示和前一個(gè)chunk的時(shí)間戳都是相同的。什么時(shí)候連時(shí)間戳都相同呢?就是一個(gè)Message拆分成了多個(gè)chunk,這個(gè)chunk和上一個(gè)chunk同屬于一個(gè)Message。而當(dāng)它跟在Type=1或者Type=2的chunk后面時(shí),表示和前一個(gè)chunk的時(shí)間戳的差是相同的。比如第一個(gè)chunk的Type=0,timestamp=100,第二個(gè)chunk的Type=2,timestamp delta=20,表示時(shí)間戳為100+20=120,第三個(gè)chunk的Type=3,表示timestamp delta=20,時(shí)間戳為120+20=140

3.3.3 Extended Timestamp(擴(kuò)展時(shí)間戳)

上面我們提到在chunk中會(huì)有時(shí)間戳timestamp和時(shí)間戳差timestamp delta,并且它們不會(huì)同時(shí)存在,只有這兩者之一大于3個(gè)字節(jié)能表示的最大數(shù)值0xFFFFFF=16777215時(shí),才會(huì)用這個(gè)字段來(lái)表示真正的時(shí)間戳,否則這個(gè)字段為0。擴(kuò)展時(shí)間戳占4個(gè)字節(jié),能表示的最大數(shù)值就是0xFFFFFFFF=4294967295。當(dāng)擴(kuò)展時(shí)間戳啟用時(shí),timestamp字段或者timestamp delta要全置為1,表示應(yīng)該去擴(kuò)展時(shí)間戳字段來(lái)提取真正的時(shí)間戳或者時(shí)間戳差。注意擴(kuò)展時(shí)間戳存儲(chǔ)的是完整值,而不是減去時(shí)間戳或者時(shí)間戳差的值。

3.3.4 Chunk Data(塊數(shù)據(jù))

用戶(hù)層面上真正想要發(fā)送的與協(xié)議無(wú)關(guān)的數(shù)據(jù),長(zhǎng)度在(0,chunkSize]之間。

3.3.5 chunk表示例1

image

首先包含第一個(gè)Message的chunk的Chunk Type為0,因?yàn)樗鼪](méi)有前面可參考的chunk,timestamp為1000,表示時(shí)間戳。type為0的header占用11個(gè)字節(jié),假定chunkstreamId為3<127,因此Basic Header占用1個(gè)字節(jié),再加上Data的32個(gè)字節(jié),因此第一個(gè)chunk共44=11+1+32個(gè)字節(jié)。

第二個(gè)chunk和第一個(gè)chunk的CSID,TypeId,Data的長(zhǎng)度都相同,因此采用Chunk Type=2,timestamp delta=1020-1000=20,因此第二個(gè)chunk占用36=3+1+32個(gè)字節(jié)。

第三個(gè)chunk和第二個(gè)chunk的CSID,TypeId,Data的長(zhǎng)度和時(shí)間戳差都相同,因此采用Chunk Type=3省去全部Message Header的信息,占用33=1+32個(gè)字節(jié)。

第四個(gè)chunk和第三個(gè)chunk情況相同,也占用33=1+32個(gè)字節(jié)。

最后實(shí)際發(fā)送的chunk如下:

image

3.3.6 chunk表示例2

image

注意到Data的Length=307>128,因此這個(gè)Message要切分成幾個(gè)chunk發(fā)送,第一個(gè)chunk的Type=0,Timestamp=1000,承擔(dān)128個(gè)字節(jié)的Data,因此共占用140=11+1+128個(gè)字節(jié)。

第二個(gè)chunk也要發(fā)送128個(gè)字節(jié),其他字段也同第一個(gè)chunk,因此采用Chunk Type=3,此時(shí)時(shí)間戳也為1000,共占用129=1+128個(gè)字節(jié)。

第三個(gè)chunk要發(fā)送的Data的長(zhǎng)度為307-128-128=51個(gè)字節(jié),還是采用Type=3,共占用1+51=52個(gè)字節(jié)。

最后實(shí)際發(fā)送的chunk如下:

image

3.4 協(xié)議控制消息(Protocol Control Message)

在RTMP的chunk流會(huì)用一些特殊的值來(lái)代表協(xié)議的控制消息,它們的Message Stream ID必須為0(代表控制流信息),CSID必須為2,Message Type ID可以為1,2,3,5,6,具體代表的消息會(huì)在下面依次說(shuō)明。控制消息的接受端會(huì)忽略掉chunk中的時(shí)間戳,收到后立即生效。

Set Chunk Size(Message Type ID=1):設(shè)置chunk中Data字段所能承載的最大字節(jié)數(shù),默認(rèn)為128B,通信過(guò)程中可以通過(guò)發(fā)送該消息來(lái)設(shè)置chunk Size的大?。ú坏眯∮?28B),而且通信雙方會(huì)各自維護(hù)一個(gè)chunkSize,兩端的chunkSize是獨(dú)立的。比如當(dāng)A想向B發(fā)送一個(gè)200B的Message,但默認(rèn)的chunkSize是128B,因此就要將該消息拆分為Data分別為128B和72B的兩個(gè)chunk發(fā)送,如果此時(shí)先發(fā)送一個(gè)設(shè)置chunkSize為256B的消息,再發(fā)送Data為200B的chunk,本地不再劃分Message,B接受到Set Chunk Size的協(xié)議控制消息時(shí)會(huì)調(diào)整的接受的chunk的Data的大小,也不用再將兩個(gè)chunk組成為一個(gè)Message。

以下為代表Set Chunk Size消息的chunk的Data:

image

其中第一位必須為0,chunk Size占31個(gè)位,最大可代表2147483647=0x7FFFFFFF=231-1,但實(shí)際上所有大于16777215=0xFFFFFF的值都用不上,因?yàn)閏hunk size不能大于Message的長(zhǎng)度,表示Message的長(zhǎng)度字段是用3個(gè)字節(jié)表示的,最大只能為0xFFFFFF。

Abort Message(Message Type ID=2):當(dāng)一個(gè)Message被切分為多個(gè)chunk,接受端只接收到了部分chunk時(shí),發(fā)送該控制消息表示發(fā)送端不再傳輸同Message的chunk,接受端接收到這個(gè)消息后要丟棄這些不完整的chunk。Data數(shù)據(jù)中只需要一個(gè)CSID,表示丟棄該CSID的所有已接收到的chunk。

image

Acknowledgement(Message Type ID=3):當(dāng)收到對(duì)端的消息大小等于窗口大?。╓indow Size)時(shí)接受端要回饋一個(gè)ACK給發(fā)送端告知對(duì)方可以繼續(xù)發(fā)送數(shù)據(jù)。窗口大小就是指收到接受端返回的ACK前最多可以發(fā)送的字節(jié)數(shù)量,返回的ACK中會(huì)帶有從發(fā)送上一個(gè)ACK后接收到的字節(jié)數(shù)。

image

Window Acknowledgement Size(Message Type ID=5):發(fā)送端在接收到接受端返回的兩個(gè)ACK間最多可以發(fā)送的字節(jié)數(shù)。

image

Set Peer Bandwidth(Message Type ID=6):限制對(duì)端的輸出帶寬。接受端接收到該消息后會(huì)通過(guò)設(shè)置消息中的Window ACK Size來(lái)限制已發(fā)送但未接受到反饋的消息的大小來(lái)限制發(fā)送端的發(fā)送帶寬。如果消息中的Window ACK Size與上一次發(fā)送給發(fā)送端的size不同的話(huà)要回饋一個(gè)Window Acknowledgement Size的控制消息。

image

Hard(Limit Type=0):接受端應(yīng)該將Window Ack Size設(shè)置為消息中的值

Soft(Limit Type=1):接受端可以講Window Ack Size設(shè)為消息中的值,也可以保存原來(lái)的值(前提是原來(lái)的Size小與該控制消息中的Window Ack Size)

Dynamic(Limit Type=2):如果上次的Set Peer Bandwidth消息中的Limit Type為0,本次也按Hard處理,否則忽略本消息,不去設(shè)置Window Ack Size。

4. 不同類(lèi)型的RTMP Message

Command Message(命令消息,Message Type ID=17或20):表示在客戶(hù)端盒服務(wù)器間傳遞的在對(duì)端執(zhí)行某些操作的命令消息,如connect表示連接對(duì)端,對(duì)端如果同意連接的話(huà)會(huì)記錄發(fā)送端信息并返回連接成功消息,publish表示開(kāi)始向?qū)Ψ酵屏?,接受端接到命令后?zhǔn)備好接受對(duì)端發(fā)送的流信息,后面會(huì)對(duì)比較常見(jiàn)的Command Message具體介紹。當(dāng)信息使用AMF0編碼時(shí),Message Type ID=20,AMF3編碼時(shí)Message Type ID=17.

Data Message(數(shù)據(jù)消息,Message Type ID=15或18):傳遞一些元數(shù)據(jù)(MetaData,比如視頻名,分辨率等等)或者用戶(hù)自定義的一些消息。當(dāng)信息使用AMF0編碼時(shí),Message Type ID=18,AMF3編碼時(shí)Message Type ID=15.

Shared Object Message(共享消息,Message Type ID=16或19):表示一個(gè)Flash類(lèi)型的對(duì)象,由鍵值對(duì)的集合組成,用于多客戶(hù)端,多實(shí)例時(shí)使用。當(dāng)信息使用AMF0編碼時(shí),Message Type ID=19,AMF3編碼時(shí)Message Type ID=16.

Audio Message(音頻信息,Message Type ID=8):音頻數(shù)據(jù)。

Video Message(視頻信息,Message Type ID=9):視頻數(shù)據(jù)。

Aggregate Message (聚集信息,Message Type ID=22):多個(gè)RTMP子消息的集合

User Control Message Events(用戶(hù)控制消息,Message Type ID=4):告知對(duì)方執(zhí)行該信息中包含的用戶(hù)控制事件,比如Stream Begin事件告知對(duì)方流信息開(kāi)始傳輸。和前面提到的協(xié)議控制信息(Protocol Control Message)不同,這是在RTMP協(xié)議層的,而不是在RTMP chunk流協(xié)議層的,這個(gè)很容易弄混。該信息在chunk流中發(fā)送時(shí),Message Stream ID=0,Chunk Stream Id=2,Message Type Id=4。

———下面對(duì)以上7種信息具體介紹———-

4.1 Command Message(命令消息,Message Type ID=17或20)

發(fā)送端發(fā)送時(shí)會(huì)帶有命令的名字,如connect,TransactionID表示此次命令的標(biāo)識(shí),Command Object表示相關(guān)參數(shù)。接受端收到命令后,會(huì)返回以下三種消息中的一種:_result 消息表示接受該命令,對(duì)端可以繼續(xù)往下執(zhí)行流程,_error消息代表拒絕該命令要執(zhí)行的操作,method name消息代表要在之前命令的發(fā)送端執(zhí)行的函數(shù)名稱(chēng)。這三種回應(yīng)的消息都要帶有收到的命令消息中的TransactionId來(lái)表示本次的回應(yīng)作用于哪個(gè)命令。

可以認(rèn)為發(fā)送命令消息的對(duì)象有兩種,一種是NetConnection,表示雙端的上層連接,一種是NetStream,表示流信息的傳輸通道,控制流信息的狀態(tài),如Play播放流,Pause暫停。

4.1.1 NetConnection Commands(連接層的命令)

用來(lái)管理雙端之間的連接狀態(tài),同時(shí)也提供了異步遠(yuǎn)程方法調(diào)用(RPC)在對(duì)端執(zhí)行某方法,以下是常見(jiàn)的連接層的命令:

4.1.1.1 connect:用于客戶(hù)端向服務(wù)器發(fā)送連接請(qǐng)求,消息的結(jié)構(gòu)如下:

字段類(lèi)型說(shuō)明

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

Transaction ID(事務(wù)ID)Number恒為1

Command Object(命令包含的參數(shù)對(duì)象)Object鍵值對(duì)集合表示的命令參數(shù)

Optional User Arguments(額外的用戶(hù)參數(shù))Object用戶(hù)自定義的額外信息

第三個(gè)字段中的Command Object中會(huì)涉及到很多鍵值對(duì),這里不再一一列出,使用時(shí)可以參考協(xié)議的官方文檔。

消息的回應(yīng)有兩種,_result表示接受連接,_error表示連接失敗

4.1.1.2 Call:用于在對(duì)端執(zhí)行某函數(shù),即常說(shuō)的RPC:遠(yuǎn)程進(jìn)程調(diào)用,消息的結(jié)構(gòu)如下:

字段類(lèi)型說(shuō)明

Procedure Name(進(jìn)程名)String要調(diào)用的進(jìn)程名稱(chēng)

Transaction IDNumber|如果想要對(duì)端響應(yīng)的話(huà)置為非0值,否則置為0

Command ObjectObject命令參數(shù)

Optional ArguentsObject用戶(hù)自定義參數(shù)

如果消息中的TransactionID不為0的話(huà),對(duì)端需要對(duì)該命令做出響應(yīng),響應(yīng)的消息結(jié)構(gòu)如下:

字段類(lèi)型說(shuō)明

Command Name(命令名)String命令的名稱(chēng)

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

Command ObjectObject命令參數(shù)

Optional ArgumentsObject用戶(hù)自定義參數(shù)

4.1.1.3 Create Stream:創(chuàng)建傳遞具體信息的通道,從而可以在這個(gè)流中傳遞具體信息,傳輸信息單元為Chunk。

字段類(lèi)型說(shuō)明

Command Name(命令名)String“createStream”

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

Command ObjectObject命令參數(shù)

Optional ArgumentsObject用戶(hù)自定義參數(shù)

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

Netstream建立在NetConnection之上,通過(guò)NetConnection的createStream命令創(chuàng)建,用于傳輸具體的音頻、視頻等信息。在傳輸層協(xié)議之上只能連接一個(gè)NetConnection,但一個(gè)NetConnection可以建立多個(gè)NetStream來(lái)建立不同的流通道傳輸數(shù)據(jù)。

以下會(huì)列出一些常用的NetStream Commands,服務(wù)端收到命令后會(huì)通過(guò)onStatus的命令來(lái)響應(yīng)客戶(hù)端,表示當(dāng)前NetStream的狀態(tài)。

onStatus命令的消息結(jié)構(gòu)如下:

字段類(lèi)型說(shuō)明

Command NameString“onStatus”

TransactionIDNumber恒為0

Command ObjectNULL對(duì)onSatus命令來(lái)說(shuō)不需要這個(gè)字段

Info ObjectObjectAMF類(lèi)型的Object,至少包含以下三個(gè)屬性:1,“l(fā)evel”,String類(lèi)型,可以為“warning”、”status”、”error”中的一種;2,”code”,String類(lèi)型,代表具體狀態(tài)的關(guān)鍵字,比如”NetStream.Play.Start”表示開(kāi)始播流;3,”description”,String類(lèi)型,代表對(duì)當(dāng)前狀態(tài)的描述,提供對(duì)當(dāng)前狀態(tài)可讀性更好的解釋?zhuān)诉@三種必要信息,用戶(hù)還可以自己增加自定義的鍵值對(duì)

4.1.2.1 play(播放):由客戶(hù)端向服務(wù)器發(fā)起請(qǐng)求從服務(wù)器端接受數(shù)據(jù)(如果傳輸?shù)男畔⑹且曨l的話(huà)就是請(qǐng)求開(kāi)始播流),可以多次調(diào)用,這樣本地就會(huì)形成一組數(shù)據(jù)流的接收者。注意其中有一個(gè)reset字段,表示是覆蓋之前的播流(設(shè)為true)還是重新開(kāi)始一路播放(設(shè)為false)。

play命令的結(jié)構(gòu)如下:

字段類(lèi)型說(shuō)明

命令名String“play”

事務(wù)IDNumber恒為0

命令參數(shù)對(duì)象Null不需要此字段,設(shè)為空

流名稱(chēng)String要播放的流的名稱(chēng)

開(kāi)始位置Number可選參數(shù),表示從何時(shí)開(kāi)始播流,以秒為單位。默認(rèn)為-2,代表選取對(duì)應(yīng)該流名稱(chēng)的直播流,即當(dāng)前正在推送的流開(kāi)始播放,如果對(duì)應(yīng)該名稱(chēng)的直播流不存在,就選取該名稱(chēng)的流的錄播版本,如果這也沒(méi)有,當(dāng)前播流端要等待直到對(duì)端開(kāi)始該名稱(chēng)的流的直播。如果傳值-1,那么只會(huì)選取直播流進(jìn)行播放,即使有錄播流也不會(huì)播放;如果傳值或者正數(shù),就代表從該流的該時(shí)間點(diǎn)開(kāi)始播放,如果流不存在的話(huà)就會(huì)自動(dòng)播放播放列表中的下一個(gè)流

周期Number可選參數(shù),表示回退的最小間隔單位,以秒為單位計(jì)數(shù)。默認(rèn)值為-1,代表直到直播流不再可用或者錄播流停止后才能回退播放;如果傳值為0,代表從當(dāng)前幀開(kāi)始播放

重置Boolean可選參數(shù),true代表清除之前的流,重新開(kāi)始一路播放,false代表保留原來(lái)的流,向本地的播放列表中再添加一條播放流

4.1.2.2 play2(播放):和上面的play命令不同的是,play2命令可以將當(dāng)前正在播放的流切換到同樣數(shù)據(jù)但不同比特率的流上,服務(wù)器端會(huì)維護(hù)多種比特率的文件來(lái)供客戶(hù)端使用play2命令來(lái)切換。

字段類(lèi)型說(shuō)明

Command NameString“play2”

TransactionIDNumber恒為0

Command ObjectNULL,對(duì)onSatus命令來(lái)說(shuō)不需要這個(gè)字段

parametersObjectAMF編碼的Flash對(duì)象,包括了一些用于描述flash.net.NetstreamPlayOptions ActionScript obejct的參數(shù)

4.1.2.3 deleteStream(刪除流):用于客戶(hù)端告知服務(wù)器端本地的某個(gè)流對(duì)象已被刪除,不需要再傳輸此路流。

字段類(lèi)型說(shuō)明

Command NameString“deleteStream”

TransactionIDNumber恒為0

Command ObjectNULL,對(duì)onSatus命令來(lái)說(shuō)不需要這個(gè)字段

Stream ID(流ID)Number本地已刪除,不再需要服務(wù)器傳輸?shù)牧鞯腎D

4.1.2.4 receiveAudio(接收音頻):通知服務(wù)器端該客戶(hù)端是否要發(fā)送音頻

receiveAudio命令結(jié)構(gòu)如下:

字段類(lèi)型說(shuō)明

Command NameString“receiveAudio”

TransactionIDNumber恒為0

Command ObjectNULL對(duì)onSatus命令來(lái)說(shuō)不需要這個(gè)字段

Bool FlagBooleantrue表示發(fā)送音頻,如果該值為false,服務(wù)器端不做響應(yīng),如果為true的話(huà),服務(wù)器端就會(huì)準(zhǔn)備接受音頻數(shù)據(jù),會(huì)向客戶(hù)端回復(fù)NetStream.Seek.Notify和NetStream.Play.Start的Onstatus命令告知客戶(hù)端當(dāng)前流的狀態(tài)

4.1.2.5 receiveVideo(接收視頻):通知服務(wù)器端該客戶(hù)端是否要發(fā)送視頻

receiveVideo命令結(jié)構(gòu)如下:

字段類(lèi)型說(shuō)明

Command NameString“receiveVideo”

TransactionIDNumber恒為0

Command ObjectNULL對(duì)onSatus命令來(lái)說(shuō)不需要這個(gè)字段

Bool FlagBooleantrue表示發(fā)送視頻,如果該值為false,服務(wù)器端不做響應(yīng),如果為true的話(huà),服務(wù)器端就會(huì)準(zhǔn)備接受視頻數(shù)據(jù),會(huì)向客戶(hù)端回復(fù)NetStream.Seek.Notify和NetStream.Play.Start的Onstatus命令告知客戶(hù)端當(dāng)前流的狀態(tài)

4.1.2.6 publish(推送數(shù)據(jù)):由客戶(hù)端向服務(wù)器發(fā)起請(qǐng)求推流到服務(wù)器。

publish命令結(jié)構(gòu)如下:

字段類(lèi)型說(shuō)明

Command NameString“publish”

TransactionIDNumber恒為0

Command ObjectNULL,對(duì)onSatus命令來(lái)說(shuō)不需要這個(gè)字段

Publishing Name(推流的名稱(chēng))String流名稱(chēng)|

Publishing Type(推流類(lèi)型)String“l(fā)ive”、”record”、”append”中的一種。live表示該推流文件不會(huì)在服務(wù)器端存儲(chǔ);record表示該推流的文件會(huì)在服務(wù)器應(yīng)用程序下的子目錄下保存以便后續(xù)播放,如果文件已經(jīng)存在的話(huà)刪除原來(lái)所有的內(nèi)容重新寫(xiě)入;append也會(huì)將推流數(shù)據(jù)保存在服務(wù)器端,如果文件不存在的話(huà)就會(huì)建立一個(gè)新文件寫(xiě)入,如果對(duì)應(yīng)該流的文件已經(jīng)存在的話(huà)保存原來(lái)的數(shù)據(jù),在文件末尾接著寫(xiě)入

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

seek命令的結(jié)構(gòu)如下:

字段類(lèi)型說(shuō)明

Command NameString“seek”

TransactionIDNumber恒為0

Command ObjectNULL,對(duì)onSatus命令來(lái)說(shuō)不需要這個(gè)字段

milliSecondsNumber定位到該文件的xx毫秒處|

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

pause命令的結(jié)構(gòu)如下:

字段類(lèi)型說(shuō)明

Command NameString“pause”

TransactionIDNumber恒為0

Command ObjectNULL,對(duì)onSatus命令來(lái)說(shuō)不需要這個(gè)字段

Pause/Unpause FlagBooleantrue表示暫停,false表示恢復(fù)

milliSecondsNumber暫?;蛘呋謴?fù)的時(shí)間,以毫秒為單位|

如果Pause為true即表示客戶(hù)端請(qǐng)求暫停的話(huà),服務(wù)端暫停對(duì)應(yīng)的流會(huì)返回NetStream.Pause.Notify的onStatus命令來(lái)告知客戶(hù)端當(dāng)前流處于暫停的狀態(tài),當(dāng)Pause為false時(shí),服務(wù)端會(huì)返回NetStream.Unpause.Notify的命令來(lái)告知客戶(hù)端當(dāng)前流恢復(fù)。如果服務(wù)端對(duì)該命令響應(yīng)失敗,返回_error信息。

5. 代表流程

5.1 推流流程

image

5.2 播流流程

image

6. 新手建議

如果讀者仔細(xì)讀完了上面講的RTMP協(xié)議,想必會(huì)覺(jué)得RTMP協(xié)議非常繁瑣,事實(shí)也確實(shí)是這樣,RTMP協(xié)議中充斥著很多冗余的字段,比如三次握手中的時(shí)間戳的校對(duì),還有一些特殊的命令,如FCPublish、UnFCPublish等,但在實(shí)際實(shí)現(xiàn)中為了保證更大兼容性通常還是要處理這些看似多余的命令。加上Adobe對(duì)RTMP協(xié)議的實(shí)現(xiàn)細(xì)節(jié)有些并沒(méi)有按照協(xié)議來(lái)或者協(xié)議中沒(méi)有寫(xiě)清楚自己搞了一套實(shí)現(xiàn),其他應(yīng)用開(kāi)發(fā)時(shí)還要兼容Adobe錯(cuò)誤的實(shí)現(xiàn),從而使的RTMP也一直為開(kāi)發(fā)者所詬病。但不管怎樣,RTMP確實(shí)提供了一種能夠全面并且實(shí)現(xiàn)簡(jiǎn)單的協(xié)議來(lái)保證流信息的傳輸,這方面暫時(shí)還沒(méi)有一種更完善更簡(jiǎn)潔的協(xié)議能夠取代它在視頻流開(kāi)發(fā)中的地位。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • RTMP協(xié)議是Real Time Message Protocol(實(shí)時(shí)信息傳輸協(xié)議)的縮寫(xiě),它是由Adobe公司...
    iOS小肖閱讀 3,662評(píng)論 0 4
  • 實(shí)時(shí)消息協(xié)議---流的分塊 版權(quán)聲明: 版權(quán)(c)2009 Adobe系統(tǒng)有限公司。全權(quán)所有。 摘要: 本備忘錄描...
    一個(gè)人zy閱讀 2,078評(píng)論 0 9
  • 版本記錄 前言 大家都知道很多視頻應(yīng)用的app中都是使用RTMP格式的協(xié)議,這個(gè)是國(guó)際上共同使用的協(xié)議,我自己雖然...
    刀客傳奇閱讀 12,644評(píng)論 5 15
  • 個(gè)人翻譯,轉(zhuǎn)載請(qǐng)注明出處,謝謝! Adobe's Real Time Messaging Protocol 摘要 ...
    SniperPan閱讀 2,906評(píng)論 1 17
  • RTSP(Real Time Streaming Protocol) RTSP協(xié)議,這應(yīng)該是實(shí)時(shí)性最好的了,如果要...
    吸霾少年閱讀 36,868評(píng)論 0 13

友情鏈接更多精彩內(nèi)容