版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2017.09.04 |
前言
大家都知道很多視頻應(yīng)用的app中都是使用RTMP格式的協(xié)議,這個(gè)是國際上共同使用的協(xié)議,我自己雖然做過了直播類型的app,但是從沒時(shí)間深入的了解這個(gè)協(xié)議的基礎(chǔ),從這一篇開始讓我們逐步揭開RTMP協(xié)議的神秘面紗,從應(yīng)用層逐步進(jìn)入原理層和底層。
定義
以下內(nèi)容部分來自百度百科
RTMP是Real Time Messaging Protocol
(實(shí)時(shí)消息傳輸協(xié)議)的首字母縮寫。該協(xié)議基于TCP,是一個(gè)協(xié)議族,包括RTMP基本協(xié)議及RTMPT/RTMPS/RTMPE等多種變種。RTMP是一種設(shè)計(jì)用來進(jìn)行實(shí)時(shí)數(shù)據(jù)通信的網(wǎng)絡(luò)協(xié)議,主要用來在Flash/AIR平臺(tái)和支持RTMP協(xié)議的流媒體/交互服務(wù)器之間進(jìn)行音視頻和數(shù)據(jù)通信。支持該協(xié)議的軟件包括Adobe Media Server/Ultrant Media Server/red5
等。
RTMP又是Routing Table Maintenance Protocol
(路由選擇表維護(hù)協(xié)議)的縮寫。 在 AppleTalk 協(xié)議組中,路由選擇表維護(hù)協(xié)議(RTMP,Routing Table Protocol
)是一種傳輸層協(xié)議,它在 AppleTalk 路由器中建立并維護(hù)路由選擇表。RTMP 基于路由選擇信息協(xié)議(RIP)。正如 RIP 一樣,RTMP 使用跳數(shù)作為路由計(jì)量標(biāo)準(zhǔn)。一個(gè)數(shù)據(jù)包從源 網(wǎng)絡(luò)發(fā)送到目標(biāo)網(wǎng)絡(luò),必須通過的路由器或其它中間介質(zhì)節(jié)點(diǎn)數(shù)目的計(jì)算結(jié)果即為跳數(shù)。
下面我們看一下兩張?jiān)韴D理解一下。
協(xié)議概述
RTMP(Real Time Messaging Protocol)
實(shí)時(shí)消息傳送協(xié)議是Adobe Systems公司為Flash播放器和服務(wù)器之間音頻、視頻和數(shù)據(jù)傳輸 開發(fā)的開放協(xié)議。
它有多種變種:
-
RTMP
工作在TCP之上,默認(rèn)使用端口1935; -
RTMPE
在RTMP的基礎(chǔ)上增加了加密功能; -
RTMPT
封裝在HTTP請(qǐng)求之上,可穿透防火墻; -
RTMPS
類似RTMPT,增加了TLS/SSL
的安全功能。
協(xié)議詳細(xì)介紹
RTMP
協(xié)議(Real Time Messaging Protocol)
是被Flash用于對(duì)象,視頻,音頻的傳輸。這個(gè)協(xié)議建立在TCP協(xié)議或者輪詢HTTP協(xié)議之上。
RTMP協(xié)議就像一個(gè)用來裝數(shù)據(jù)包的容器,這些數(shù)據(jù)既可以是AMF
格式的數(shù)據(jù),也可以是FLV
中的視/音頻數(shù)據(jù)。
一個(gè)單一的連接可以通過不同的通道傳輸多路網(wǎng)絡(luò)流,這些通道中的包都是按照固定大小的包傳輸?shù)摹?br>
網(wǎng)絡(luò)連接(Connection)
一個(gè)Actionscript
連接并播放一個(gè)流的簡單代碼:
var videoInstance:Video = your_video_instance;
var nc:NetConnection = new NetConnection();
var connected:Boolean = nc.connect("rtmp:/localhost/myapp");
var ns:NetStream = new NetStream(nc);
videoInstance.attachVideo(ns);
ns.play("flvName");
默認(rèn)端口為1935
握手請(qǐng)求及應(yīng)答
1. 握手過程
Client → Server
:向服務(wù)器發(fā)出握手請(qǐng)求.這不屬于協(xié)議包一部分,該握手請(qǐng)求第一個(gè)字節(jié)為(0×03),其后跟著1536個(gè)字節(jié)。盡管看上去這部分的內(nèi)容對(duì)于RTMP協(xié)議來說并不是至關(guān)重要的,但也不可隨意對(duì)待。
Server → Client
:服務(wù)器向客戶端回應(yīng)握手請(qǐng)求,這部分的數(shù)據(jù)仍然不屬于RTMP協(xié)議的部分。該回應(yīng)的起始字節(jié)仍然為(0x03),但是后邊跟著兩個(gè)長度為1536個(gè)字節(jié)(一共為3072字節(jié) )的包塊。第一個(gè)1536塊看上去似乎可以是任意內(nèi)容,甚至好像可以是Null都沒有關(guān)系。第二個(gè)1536的代碼塊,是上一步客戶端向服務(wù)器端發(fā)送的握手請(qǐng)求的內(nèi)容。
Client→Server
:把上一步服務(wù)器向客戶端回應(yīng)的第二塊1536個(gè)字節(jié)的數(shù)據(jù)塊。
至此客戶端與服務(wù)器端的握手結(jié)束,下面將發(fā)送RTMP協(xié)議的包內(nèi)容。
Client → Server
:向服務(wù)器發(fā)送連接包。
Server → Client
:服務(wù)器回應(yīng)。
... .... 等等... ...
2. RTMP數(shù)據(jù)類型
0×01 Chunk Size changes the chunk size for packets
0×02 Unknown anyone know this one?
0×03 Bytes Read send every x bytes read by both sides
0×04 Ping ping is a stream control message, has subtypes
0×05 Server BW the servers downstream bw
0×06 Client BW the clients upstream bw
0×07 Unknown anyone know this one?
0×08 Audio Data packet containing audio
0×09 Video Data packet containing video data
0x0A - 0×11 Unknown anyone know?
0×12 Notify an invoke which does not expect a reply
0×13 Shared Object has subtypes
0×14 Invoke like remoting call, used for stream actions too.
Shared Object 數(shù)據(jù)類型
0×01 Connect
0×02 Disconnect
0×03 Set Attribute
0×04 Update Data
0×05 Update Attribute
0×06 Send Message
0×07 Status
0×08 Clear Data
0×09 Delete Data
0x0A Delete Attribute
0x0B
Initial Data
3. RTMP包結(jié)構(gòu)
RTMP包 包含一個(gè)固定長度的包頭和一個(gè)最長為128字節(jié)的包體,包頭可以是下面4種長度的任意一種:12, 8, 4, or 1 byte(s)
。
第一個(gè)字節(jié)的前兩個(gè)Bit很重要,它決定了包頭的長度,它可以用掩碼0xC0進(jìn)行"與"計(jì)算。下面羅列了可能的包頭長度Bits Header Length
。
00 12 bytes
01 8 bytes
10 4 bytes
11 1 byte
其實(shí)RTMP
包結(jié)構(gòu)就是使用了AMF
格式.
下面是一個(gè)關(guān)于客戶端向服務(wù)器端發(fā)送流的流程:
-
Client → Server
:發(fā)送一個(gè)創(chuàng)建流的請(qǐng)求 -
Server → Client
:返回一個(gè)表示流的索引號(hào) -
Client → Server
:開始發(fā)送 -
Client → Server
:發(fā)送視音頻數(shù)據(jù)包(這些包在同一個(gè)頻道(channel)并用流的索引號(hào)來唯一標(biāo)識(shí))
4. RTMP Chunk Stream - RTMP塊流
Chunk Stream
是對(duì)傳輸RTMP Chunk
的流的邏輯上的抽象,客戶端和服務(wù)器之間有關(guān)RTMP的信息都在這個(gè)流上通信。這個(gè)流上的操作也是我們關(guān)注RTMP協(xié)議的重點(diǎn)
Message
Message
是指滿足該協(xié)議格式的、可以切分成Chunk
發(fā)送的消息,消息包含的字段如下所示。
-
Timestamp
(時(shí)間戳):消息的時(shí)間戳(但不一定是當(dāng)前時(shí)間,后面會(huì)介紹),4個(gè)字節(jié)。 -
Length
(長度):是指Message Payload
(消息負(fù)載)即音視頻等信息的數(shù)據(jù)的長度,3個(gè)字節(jié)。 -
TypeId
(類型Id):消息的類型Id,1個(gè)字節(jié)。 -
Message Stream ID
(消息的流ID):每個(gè)消息的唯一標(biāo)識(shí),劃分成Chunk
和還原Chunk為Message
的時(shí)候都是根據(jù)這個(gè)ID來辨識(shí)是否是同一個(gè)消息的Chunk
的,4個(gè)字節(jié),并且以小端格式存儲(chǔ)。
Chunking(Message分塊)
RTMP
在收發(fā)數(shù)據(jù)的時(shí)候并不是以Message
為單位的,而是把Message拆分成Chunk發(fā)送,而且必須在一個(gè)Chunk
發(fā)送完成之后才能開始發(fā)送下一個(gè)Chunk。每個(gè)Chunk中帶有MessageID代表屬于哪個(gè)Message,接受端也會(huì)按照這個(gè)id來將chunk組裝成Message。
為什么RTMP要將Message拆分成不同的Chunk呢?通過拆分,數(shù)據(jù)量較大的Message可以被拆分成較小的“Message”,這樣就可以避免優(yōu)先級(jí)低的消息持續(xù)發(fā)送阻塞優(yōu)先級(jí)高的數(shù)據(jù),比如在視頻的傳輸過程中,會(huì)包括視頻幀,音頻幀和RTMP控制信息,如果持續(xù)發(fā)送音頻數(shù)據(jù)或者控制數(shù)據(jù)的話可能就會(huì)造成視頻幀的阻塞,然后就會(huì)造成看視頻時(shí)最煩人的卡頓現(xiàn)象。同時(shí)對(duì)于數(shù)據(jù)量較小的Message,可以通過對(duì)Chunk Header的字段來壓縮信息,從而減少信息的傳輸量。
Chunk的默認(rèn)大小是128字節(jié),在傳輸過程中,通過一個(gè)叫做Set Chunk Size的控制信息可以設(shè)置Chunk數(shù)據(jù)量的最大值,在發(fā)送端和接受端會(huì)各自維護(hù)一個(gè)Chunk Size,可以分別設(shè)置這個(gè)值來改變自己這一方發(fā)送的Chunk的最大大小。大一點(diǎn)的Chunk減少了計(jì)算每個(gè)chunk的時(shí)間從而減少了CPU的占用率,但是它會(huì)占用更多的時(shí)間在發(fā)送上,尤其是在低帶寬的網(wǎng)絡(luò)情況下,很可能會(huì)阻塞后面更重要信息的傳輸。小一點(diǎn)的Chunk可以減少這種阻塞問題,但小的Chunk會(huì)引入過多額外的信息(Chunk中的Header),少量多次的傳輸也可能會(huì)造成發(fā)送的間斷導(dǎo)致不能充分利用高帶寬的優(yōu)勢,因此并不適合在高比特率的流中傳輸。在實(shí)際發(fā)送時(shí)應(yīng)對(duì)要發(fā)送的數(shù)據(jù)用不同的Chunk Size去嘗試,通過抓包分析等手段得出合適的Chunk大小,并且在傳輸過程中可以根據(jù)當(dāng)前的帶寬信息和實(shí)際信息的大小動(dòng)態(tài)調(diào)整Chunk的大小,從而盡量提高CPU的利用率并減少信息的阻塞機(jī)率。
Chunk Format - Chunk格式
下面就說一下快格式里面的組成。
- Basic Header:它是基本的頭信息。
包含了chunk stream ID
(流通道Id)和chunk type
(chunk的類型),chunk stream id
一般被簡寫為CSID
,用來唯一標(biāo)識(shí)一個(gè)特定的流通道,chunk type決定了后面Message Header的格式。Basic Header的長度可能是1,2,或3個(gè)字節(jié),其中chunk type的長度是固定的(占2位,注意單位是位,bit),Basic Header的長度取決于CSID的大小,在足夠存儲(chǔ)這兩個(gè)字段的前提下最好用盡量少的字節(jié)從而減少由于引入Header增加的數(shù)據(jù)量。
RTMP協(xié)議支持用戶自定義[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的長度固定為2位,因此CSID的長度是(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]之間,其中用戶可自定義的范圍為[3,63]。
下面看一下Basic Header不同字節(jié)時(shí)的字節(jié)示意圖。
需要注意的是,Basic Header是采用小端存儲(chǔ)的方式,越往后的字節(jié)數(shù)量級(jí)越高。可以看到2個(gè)字節(jié)和3個(gè)字節(jié)的Basic Header所能表示的CSID是有交集的[64,319]
,但實(shí)際實(shí)現(xiàn)時(shí)還是應(yīng)該秉著最少字節(jié)的原則使用2個(gè)字節(jié)的表示方式來表示[64,319]
的CSID
。
- Message Header
包含了要發(fā)送的實(shí)際信息(可能是完整的,也可能是一部分)的描述信息。Message Header的格式和長度取決于Basic Header
的chunk type
,共有4種不同的格式,由上面所提到的Basic Header中的fmt字段控制。其中第一種格式可以表示其他三種表示的所有數(shù)據(jù),但由于其他三種格式是基于對(duì)之前chunk的差量化的表示,因此可以更簡潔地表示相同的數(shù)據(jù),實(shí)際使用的時(shí)候還是應(yīng)該采用盡量少的字節(jié)表示相同意義的數(shù)據(jù)。以下按照字節(jié)數(shù)從多到少的順序分別介紹這4種格式的Message Header
。
(1) type=0時(shí)Message Header占用11個(gè)字節(jié),其他三種能表示的數(shù)據(jù)它都能表示,但在chunk stream的開始的第一個(gè)chunk和頭信息中的時(shí)間戳后退(即值與上一個(gè)chunk相比減小,通常在回退播放的時(shí)候會(huì)出現(xiàn)這種情況)的時(shí)候必須采用這種格式。
(2) type=1時(shí)Message Header占用7個(gè)字節(jié),省去了表示msg stream id的4個(gè)字節(jié),表示此chunk和上一次發(fā)的chunk所在的流相同,如果在發(fā)送端只和對(duì)端有一個(gè)流鏈接的時(shí)候可以盡量去采取這種格式。
(3) type=2時(shí)Message Header占用3個(gè)字節(jié),相對(duì)于type=1格式又省去了表示消息長度的3個(gè)字節(jié)和表示消息類型的1個(gè)字節(jié),表示此chunk和上一次發(fā)送的chunk所在的流、消息的長度和消息的類型都相同。余下的這三個(gè)字節(jié)表示timestamp delta,使用同type=1 。
(4) 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。
- Extended Timestamp(擴(kuò)展時(shí)間戳)
上面我們提到在chunk中會(huì)有時(shí)間戳timestamp和時(shí)間戳差timestamp delta,并且它們不會(huì)同時(shí)存在,只有這兩者之一大于3個(gè)字節(jié)能表示的最大數(shù)值0xFFFFFF=16777215時(shí),才會(huì)用這個(gè)字段來表示真正的時(shí)間戳,否則這個(gè)字段為0。擴(kuò)展時(shí)間戳占4個(gè)字節(jié),能表示的最大數(shù)值就是0xFFFFFFFF=4294967295。當(dāng)擴(kuò)展時(shí)間戳啟用時(shí),timestamp字段或者timestamp delta要全置為1,表示應(yīng)該去擴(kuò)展時(shí)間戳字段來提取真正的時(shí)間戳或者時(shí)間戳差。注意擴(kuò)展時(shí)間戳存儲(chǔ)的是完整值,而不是減去時(shí)間戳或者時(shí)間戳差的值。
- Chunk Data(塊數(shù)據(jù)):
用戶層面上真正想要發(fā)送的與協(xié)議無關(guān)的數(shù)據(jù),長度在[0,chunkSize]
之間。
協(xié)議控制消息(Protocol Control Message)
在RTMP的chunk流會(huì)用一些特殊的值來代表協(xié)議的控制消息,它們的Message Stream ID必須為0(代表控制流信息),CSID必須為2,Message Type ID可以為1,2,3,5,6,具體代表的消息會(huì)在下面依次說明。控制消息的接受端會(huì)忽略掉chunk中的時(shí)間戳,收到后立即生效。
Set Chunk Size(Message Type ID=1)
:設(shè)置chunk中Data字段所能承載的最大字節(jié)數(shù),默認(rèn)為128B,通信過程中可以通過發(fā)送該消息來設(shè)置chunk Size
的大小(不得小于128B),而且通信雙方會(huì)各自維護(hù)一個(gè)chunkSize,兩端的chunkSize是獨(dú)立的。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。Acknowledgement(Message Type ID=3)
:當(dāng)收到對(duì)端的消息大小等于窗口大小(Window Size)時(shí)接受端要回饋一個(gè)ACK給發(fā)送端告知對(duì)方可以繼續(xù)發(fā)送數(shù)據(jù)。窗口大小就是指收到接受端返回的ACK前最多可以發(fā)送的字節(jié)數(shù)量,返回的ACK中會(huì)帶有從發(fā)送上一個(gè)ACK后接收到的字節(jié)數(shù)。Window Acknowledgement Size(Message Type ID=5)
:發(fā)送端在接收到接受端返回的兩個(gè)ACK間最多可以發(fā)送的字節(jié)數(shù)。Set Peer Bandwidth(Message Type ID=6)
:限制對(duì)端的輸出帶寬。接受端接收到該消息后會(huì)通過設(shè)置消息中的Window ACK Size來限制已發(fā)送但未接受到反饋的消息的大小來限制發(fā)送端的發(fā)送帶寬。如果消息中的Window ACK Size與上一次發(fā)送給發(fā)送端的size不同的話要回饋一個(gè)Window Acknowledgement Size
的控制消息。
(1)Hard(Limit Type=0)
:接受端應(yīng)該將Window Ack Size
設(shè)置為消息中的值
(2)Soft(Limit Type=1)
:接受端可以講Window Ack Size
設(shè)為消息中的值,也可以保存原來的值(前提是原來的Size小與該控制消息中的Window Ack Size
)
(3)Dynamic(Limit Type=2)
:如果上次的Set Peer Bandwidth
消息中的Limit Type為0,本次也按Hard處理,否則忽略本消息,不去設(shè)置Window Ack Size
。
5. 不同類型的RTMP Message
-
Command Message
(命令消息,Message Type ID=17或20
):表示在客戶端和服務(wù)器間傳遞的在對(duì)端執(zhí)行某些操作的命令消息,如connect表示連接對(duì)端,對(duì)端如果同意連接的話會(huì)記錄發(fā)送端信息并返回連接成功消息,publish表示開始向?qū)Ψ酵屏鳎邮芏私拥矫詈鬁?zhǔn)備好接受對(duì)端發(fā)送的流信息,后面會(huì)對(duì)比較常見的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,比如視頻名,分辨率等等)或者用戶自定義的一些消息。當(dāng)信息使用AMF0編碼時(shí),Message Type ID=18,AMF3編碼時(shí)Message Type ID=15。 -
Shared Object Message
(共享消息,Message Type ID=16或19):表示一個(gè)Flash類型的對(duì)象,由鍵值對(duì)的集合組成,用于多客戶端,多實(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
(用戶控制消息,Message Type ID=4):告知對(duì)方執(zhí)行該信息中包含的用戶控制事件,比如Stream Begin事件告知對(duì)方流信息開始傳輸。和前面提到的協(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
。
6. 基于RTMP協(xié)議的推流和拉流播放過程
先看一下推流過程。
再看一下拉流過程
參考文章
后記
未完,待續(xù)~~~