版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2017.09.04 |
前言
大家都知道很多視頻應用的app中都是使用RTMP格式的協議,這個是國際上共同使用的協議,我自己雖然做過了直播類型的app,但是從沒時間深入的了解這個協議的基礎,從這一篇開始讓我們逐步揭開RTMP協議的神秘面紗,從應用層逐步進入原理層和底層。
定義
以下內容部分來自百度百科
RTMP是Real Time Messaging Protocol
(實時消息傳輸協議)的首字母縮寫。該協議基于TCP,是一個協議族,包括RTMP基本協議及RTMPT/RTMPS/RTMPE等多種變種。RTMP是一種設計用來進行實時數據通信的網絡協議,主要用來在Flash/AIR平臺和支持RTMP協議的流媒體/交互服務器之間進行音視頻和數據通信。支持該協議的軟件包括Adobe Media Server/Ultrant Media Server/red5
等。
RTMP又是Routing Table Maintenance Protocol
(路由選擇表維護協議)的縮寫。 在 AppleTalk 協議組中,路由選擇表維護協議(RTMP,Routing Table Protocol
)是一種傳輸層協議,它在 AppleTalk 路由器中建立并維護路由選擇表。RTMP 基于路由選擇信息協議(RIP)。正如 RIP 一樣,RTMP 使用跳數作為路由計量標準。一個數據包從源 網絡發送到目標網絡,必須通過的路由器或其它中間介質節點數目的計算結果即為跳數。
下面我們看一下兩張原理圖理解一下。
協議概述
RTMP(Real Time Messaging Protocol)
實時消息傳送協議是Adobe Systems公司為Flash播放器和服務器之間音頻、視頻和數據傳輸 開發的開放協議。
它有多種變種:
-
RTMP
工作在TCP之上,默認使用端口1935; -
RTMPE
在RTMP的基礎上增加了加密功能; -
RTMPT
封裝在HTTP請求之上,可穿透防火墻; -
RTMPS
類似RTMPT,增加了TLS/SSL
的安全功能。
協議詳細介紹
RTMP
協議(Real Time Messaging Protocol)
是被Flash用于對象,視頻,音頻的傳輸。這個協議建立在TCP協議或者輪詢HTTP協議之上。
RTMP協議就像一個用來裝數據包的容器,這些數據既可以是AMF
格式的數據,也可以是FLV
中的視/音頻數據。
一個單一的連接可以通過不同的通道傳輸多路網絡流,這些通道中的包都是按照固定大小的包傳輸的。
網絡連接(Connection)
一個Actionscript
連接并播放一個流的簡單代碼:
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");
默認端口為1935
握手請求及應答
1. 握手過程
Client → Server
:向服務器發出握手請求.這不屬于協議包一部分,該握手請求第一個字節為(0×03),其后跟著1536個字節。盡管看上去這部分的內容對于RTMP協議來說并不是至關重要的,但也不可隨意對待。
Server → Client
:服務器向客戶端回應握手請求,這部分的數據仍然不屬于RTMP協議的部分。該回應的起始字節仍然為(0x03),但是后邊跟著兩個長度為1536個字節(一共為3072字節 )的包塊。第一個1536塊看上去似乎可以是任意內容,甚至好像可以是Null都沒有關系。第二個1536的代碼塊,是上一步客戶端向服務器端發送的握手請求的內容。
Client→Server
:把上一步服務器向客戶端回應的第二塊1536個字節的數據塊。
至此客戶端與服務器端的握手結束,下面將發送RTMP協議的包內容。
Client → Server
:向服務器發送連接包。
Server → Client
:服務器回應。
... .... 等等... ...
2. RTMP數據類型
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 數據類型
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包結構
RTMP包 包含一個固定長度的包頭和一個最長為128字節的包體,包頭可以是下面4種長度的任意一種:12, 8, 4, or 1 byte(s)
。
第一個字節的前兩個Bit很重要,它決定了包頭的長度,它可以用掩碼0xC0進行"與"計算。下面羅列了可能的包頭長度Bits Header Length
。
00 12 bytes
01 8 bytes
10 4 bytes
11 1 byte
其實RTMP
包結構就是使用了AMF
格式.
下面是一個關于客戶端向服務器端發送流的流程:
-
Client → Server
:發送一個創建流的請求 -
Server → Client
:返回一個表示流的索引號 -
Client → Server
:開始發送 -
Client → Server
:發送視音頻數據包(這些包在同一個頻道(channel)并用流的索引號來唯一標識)
4. RTMP Chunk Stream - RTMP塊流
Chunk Stream
是對傳輸RTMP Chunk
的流的邏輯上的抽象,客戶端和服務器之間有關RTMP的信息都在這個流上通信。這個流上的操作也是我們關注RTMP協議的重點
Message
Message
是指滿足該協議格式的、可以切分成Chunk
發送的消息,消息包含的字段如下所示。
-
Timestamp
(時間戳):消息的時間戳(但不一定是當前時間,后面會介紹),4個字節。 -
Length
(長度):是指Message Payload
(消息負載)即音視頻等信息的數據的長度,3個字節。 -
TypeId
(類型Id):消息的類型Id,1個字節。 -
Message Stream ID
(消息的流ID):每個消息的唯一標識,劃分成Chunk
和還原Chunk為Message
的時候都是根據這個ID來辨識是否是同一個消息的Chunk
的,4個字節,并且以小端格式存儲。
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的利用率并減少信息的阻塞機率。
Chunk Format - Chunk格式
下面就說一下快格式里面的組成。
- 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]。
下面看一下Basic Header不同字節時的字節示意圖。
需要注意的是,Basic Header是采用小端存儲的方式,越往后的字節數量級越高。可以看到2個字節和3個字節的Basic Header所能表示的CSID是有交集的[64,319]
,但實際實現時還是應該秉著最少字節的原則使用2個字節的表示方式來表示[64,319]
的CSID
。
- Message Header
包含了要發送的實際信息(可能是完整的,也可能是一部分)的描述信息。Message Header的格式和長度取決于Basic Header
的chunk type
,共有4種不同的格式,由上面所提到的Basic Header中的fmt字段控制。其中第一種格式可以表示其他三種表示的所有數據,但由于其他三種格式是基于對之前chunk的差量化的表示,因此可以更簡潔地表示相同的數據,實際使用的時候還是應該采用盡量少的字節表示相同意義的數據。以下按照字節數從多到少的順序分別介紹這4種格式的Message Header
。
(1) type=0時Message Header占用11個字節,其他三種能表示的數據它都能表示,但在chunk stream的開始的第一個chunk和頭信息中的時間戳后退(即值與上一個chunk相比減小,通常在回退播放的時候會出現這種情況)的時候必須采用這種格式。
(2) type=1時Message Header占用7個字節,省去了表示msg stream id的4個字節,表示此chunk和上一次發的chunk所在的流相同,如果在發送端只和對端有一個流鏈接的時候可以盡量去采取這種格式。
(3) type=2時Message Header占用3個字節,相對于type=1格式又省去了表示消息長度的3個字節和表示消息類型的1個字節,表示此chunk和上一次發送的chunk所在的流、消息的長度和消息的類型都相同。余下的這三個字節表示timestamp delta,使用同type=1 。
(4) 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。
- Extended Timestamp(擴展時間戳)
上面我們提到在chunk中會有時間戳timestamp和時間戳差timestamp delta,并且它們不會同時存在,只有這兩者之一大于3個字節能表示的最大數值0xFFFFFF=16777215時,才會用這個字段來表示真正的時間戳,否則這個字段為0。擴展時間戳占4個字節,能表示的最大數值就是0xFFFFFFFF=4294967295。當擴展時間戳啟用時,timestamp字段或者timestamp delta要全置為1,表示應該去擴展時間戳字段來提取真正的時間戳或者時間戳差。注意擴展時間戳存儲的是完整值,而不是減去時間戳或者時間戳差的值。
- Chunk Data(塊數據):
用戶層面上真正想要發送的與協議無關的數據,長度在[0,chunkSize]
之間。
協議控制消息(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是獨立的。Abort Message(Message Type ID=2)
:當一個Message被切分為多個chunk,接受端只接收到了部分chunk時,發送該控制消息表示發送端不再傳輸同Message的chunk,接受端接收到這個消息后要丟棄這些不完整的chunk。Data數據中只需要一個CSID,表示丟棄該CSID的所有已接收到的chunk。Acknowledgement(Message Type ID=3)
:當收到對端的消息大小等于窗口大小(Window Size)時接受端要回饋一個ACK給發送端告知對方可以繼續發送數據。窗口大小就是指收到接受端返回的ACK前最多可以發送的字節數量,返回的ACK中會帶有從發送上一個ACK后接收到的字節數。Window Acknowledgement Size(Message Type ID=5)
:發送端在接收到接受端返回的兩個ACK間最多可以發送的字節數。Set Peer Bandwidth(Message Type ID=6)
:限制對端的輸出帶寬。接受端接收到該消息后會通過設置消息中的Window ACK Size來限制已發送但未接受到反饋的消息的大小來限制發送端的發送帶寬。如果消息中的Window ACK Size與上一次發送給發送端的size不同的話要回饋一個Window Acknowledgement Size
的控制消息。
(1)Hard(Limit Type=0)
:接受端應該將Window Ack Size
設置為消息中的值
(2)Soft(Limit Type=1)
:接受端可以講Window Ack Size
設為消息中的值,也可以保存原來的值(前提是原來的Size小與該控制消息中的Window Ack Size
)
(3)Dynamic(Limit Type=2)
:如果上次的Set Peer Bandwidth
消息中的Limit Type為0,本次也按Hard處理,否則忽略本消息,不去設置Window Ack Size
。
5. 不同類型的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
。
6. 基于RTMP協議的推流和拉流播放過程
先看一下推流過程。
再看一下拉流過程
參考文章
后記
未完,待續~~~