版權(quán)聲明:本文為衛(wèi)偉學習總結(jié)文章,轉(zhuǎn)載請注明出處!
1.handshake
1.1.概述
rtmp連接從握手開始。它包含三個固定大小的塊。客戶端發(fā)送的三個塊命名為c0,c1,c2;服務端發(fā)送的三個塊命名為S0,S1,S2。
握手序列:
- 客戶端通過發(fā)送c0和c1消息來啟動握手過程。客戶端必須收到S1消息,然后發(fā)送C2消息。客戶端必須接收到S2消息,然后發(fā)送其他數(shù)據(jù)。
- 服務端必須接收到C0或者C1消息,然后發(fā)送S0和S1消息。服務端必須接收到C2消息,然后發(fā)送其他數(shù)據(jù)。
握手示意圖
1.2.complex handshake
1.2.1 C0和S0格式
C0和S0包由一個字節(jié)組成,下面是C0/S0包內(nèi)的字段:- version(1 byte) : RTMP的版本,一般為3
1.2.2 C1和S1格式
C1和S1包含兩部分數(shù)據(jù): key和digest,分別為如下:764 bytes key 結(jié)構(gòu):
random-data: (offset) bytes
- key-data: 128 bytes
- random-data: (764 - offset - 128 - 4) bytes
- offset: 4 bytes
764 bytes digest 結(jié)構(gòu):
- offset: 4 bytes
- random-data: (offset) bytes
- digest-data: 32 bytes
- random-data: (764 - 4 - offset - 32) bytes
1.2.3 C2和S2格式
1.3 simple handshake
1.3.1 C0和S0格式
version(1byte):版本。在C0包內(nèi),這個字段代表客戶端請求的RTMP版本號。在S0包內(nèi),這個字段代表服務端選擇的RTMP版本號。當前使用的版本是3。在版本0-2用在早期的產(chǎn)品中,如今已經(jīng)棄用;版本4-31被預留用于后續(xù)產(chǎn)品;版本32-255(為了區(qū)分RTMAP協(xié)議和文本協(xié)議,文本協(xié)議通常是可以打印字符)不允許使用。如果服務端無法識別客戶端的版本號,應該回復版本3。客戶端可以選擇降低到版本3,或者終止握手過程。
1.3.2 C1和S1格式
C1和S1包長度為1536字節(jié),包含以下字段:- time(4 bytes) : 本字段包含一個時間戳,客戶端應該使用此字段來標識所有流塊的時刻。時間戳取值可以為零或者任意值。為了同步多個塊流,客戶端可能希望多個塊流使用相同的時間戳。
- zero (4 bytes): 本字段必須為零。
- random (1528 bytes):本字段可以包含任意數(shù)據(jù)。由于握手的雙方需要區(qū)分另一端,此字段填充的數(shù)據(jù)必須足夠隨機(以防止與其他握手端混淆)。不過沒有必要為此使用加密數(shù)據(jù)或動態(tài)數(shù)據(jù)。
1.3.3 C2和S2格式
C2 和 S2 包長度為 1536 字節(jié),作為 C1 和 S1 的回應,包含以下字段:- time(4 bytes):本字段必須包含對端發(fā)送的時間戳。
- time2(4 bytes):本字段必須包含時間戳,取值為接收對端發(fā)送過來的握手包的時刻。
- random(1528 bytes):本字段必須包含對端發(fā)送過來的隨機數(shù)據(jù)。握手的雙方可以使用時間 1 和時間 2 字段來估算網(wǎng)絡連接的帶寬和/或延遲,但是不一定有用。
2.組塊
2.1塊格式
- 塊的基本頭(1-3字節(jié)): 這個字段包含塊流ID和塊類型。塊的類型決定了編碼過的消息頭的格式。這個字段是一個變長字段,長度取決于塊流ID。
- 消息頭(0,3,7,11字節(jié)):這個字段包含被發(fā)送的消息信息(無論是全部,還是部分)。字段長度由塊頭中的塊類型來決定。
- 擴展時間戳(0,4字節(jié)): 這個字段是否存在取決于塊消息頭中編碼的時間戳。
- 塊數(shù)據(jù)(可變大小):當前塊的有效數(shù)據(jù),上限為配置的最大塊大小。
2.2 Basic Header
包含chunk stream ID(流通道id)和chunk type(即fmt), chunk stream id 一般被簡寫為CSID,用來唯一標識一個特定的流通道,chunk type決定了后面的Message Header的格式。Basic Header的長度可能是1,2,或者3個字節(jié),其中chunk type的長度是固定的(占2位,單位是bit),Basic Header的長度取決于CSID的大小,在足夠存儲這兩個字段的前提下最好使用最少的字節(jié)從而減少由于引入Header增加的數(shù)據(jù)量。
RTMP協(xié)議支持用戶自定義[3,65599] 之間的 CSID,0, 1, 2 由協(xié)議保留表示特殊信息。0 代表 Basic Header 總共要占用 2 個字節(jié),CSID 在 [64,319] 之間; 1 代表占用 3 個字節(jié),CSID 在 [64,65599] 之間; 2 代表該 chunk 是控制信息和一些命令信息。
2.2.1 Basic Header: 1byte
2.2.2 Basic Header: 2 byte, csid == 0
CSID占14bit,此時協(xié)議將于chunk type所在字節(jié)的其他bit都置為0,剩下的一個字節(jié)表示CSID - 64,這樣共有8個bit來存儲 CSID,8 bit 可以表示 [0,255] 個數(shù),因此這種情況下 CSID 在 [64,319],其中 319 = 255 + 64。2.2.3 Basic Header: 3 bytes, csid == 1
CSID占22bit,此時協(xié)議將第一個字節(jié)的[2,8]bit置1,余下的16個bit表示CSID - 64,這樣共有16個bit來存儲CSID,16bit可以表示[0,65535]共 65536 個數(shù),因此這種情況下 CSID 在 [64,65599],其中65599=65535+64,需要注意的是,Basic Header是采用小端存儲的方式,越往后的字節(jié)數(shù)量級越高,因此通過3個字節(jié)的每一個bit的值來計算CSID時,應該是: <第三個字節(jié)的值> * 256 + <第二個字節(jié)的值> + 64.2.3 Message Header
包含了要發(fā)送的實際消息(可能是完整的,也可能是一部分)的描述消息。Message Header的格式和長度取決于Basic Header的chunk type,即fmt,共有四種不同的格式。其中一種格式可以表示其他三種表示的所有數(shù)據(jù),但由于其他三種格式是基于對之前chunk的差量化的表示,因此可以更簡潔地表示相同的數(shù)據(jù),實際使用的時候還是應該采用盡量少的字節(jié)表示相同意義的數(shù)據(jù)。下面按字節(jié)從多到少的順序分別介紹這四種格式的 Message Header。
Message Header四種消息頭格式
一、Chunk Type(fmt)=0:11bytes
type=0時Message Header占用11個字節(jié),其他三種能表示的數(shù)據(jù)它都能表示,但Chunk stream的開始第一個chunk和頭信息中時間戳后退(即值與上一個chunk相比減少,通常在回退播放的時候會出現(xiàn)這種情況)的時候必須采用這種格式。
- timestamp(時間戳):占用3個字節(jié),因此它最多能表示16777215=0xFFFFFF=2^24-1,當它的值超過這個最大值時,這三個字節(jié)都置為1,這樣實際的timestamp會轉(zhuǎn)存到 ExtendedTimestamp 字段中,接收端在判斷timestamp字段24個位都為1時就會去Extended Timestamp中解析實際的時間戳。
- message length(消息數(shù)據(jù)長度):占用3個字節(jié),表示實際發(fā)送的消息的數(shù)據(jù)如音頻、視頻幀等數(shù)據(jù)的長度,單位時字節(jié)。注意這里時Message的長度,也就是chunk屬于Message的總長度,而不是chunk本事data的長度。
- message type id(消息的類型id): 1個字節(jié),表示實際發(fā)送的數(shù)據(jù)的類型,如8代表音頻數(shù)據(jù),9代表視頻數(shù)據(jù)。
- message stream id(消息的流id): 4個字節(jié),表示該chunk所在的流的ID,和Basic Header的CSID一樣,采用小端存儲方式。
二、Chunk Type(fmt)=1:7bytes
type為1時占用7個字節(jié),省去了表示message stream id的4個字節(jié),表示此chunk和上一次發(fā)的chunk所在的流相同,如果在發(fā)送端和對端有一個流鏈接的時候可以盡量采用這種格式。
- timestamp delta:3 bytes,這里和type=0時不同,存儲的是和上一個chunk的時間差。類似上面提到的timestamp,當它的值超過3個字節(jié)所能表示的最大值時,三個字節(jié)都置為1,實際的時間戳差值就會轉(zhuǎn)存到Extended Timestamp字段中,接收端在判斷timestamp delta字段24個bit都為1時就會去Extended Timestamp 中解析實際的與上次時間戳的差值。
- 其他字段與上面的解釋相同。
三、Chunk Type(fmt)=2: 3 bytes
type 為 2 時占用 3 個字節(jié),相對于 type = 1 格式又省去了表示消息長度的3個字節(jié)和表示消息類型的1個字節(jié),表示此 chunk和上一次發(fā)送的 chunk 所在的流、消息的長度和消息的類型都相同。余下的這三個字節(jié)表示 timestamp delta,使用同type=1。
四、Chunk Type(fmt)=3: 0byte
type=3時,為0字節(jié),表示這個chunk的Message Header和上一個是完全相同的。當它跟在type=0的chunk后面時,表示和前一
個 chunk 的時間戳都是相同。什么時候連時間戳都是相同呢?就是一個 Message 拆分成多個 chunk,這個 chunk 和上一個 chunk 同屬于一個 Message。而當它跟在 type = 1或 type = 2 的chunk后面時的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。
2.4 Extended Timestamp(擴展時間戳)
在 chunk 中會有時間戳 timestamp 和時間戳差 timestamp delta,并且它們不會同時存在,只有這兩者之一大于3字節(jié)能表示的最大數(shù)值 0xFFFFFF = 16777215 時,才會用這個字段來表示真正的時間戳,否則這個字段為 0。擴展時間戳占 4 個字節(jié),
能表示的最大數(shù)值就是 0xFFFFFFFF = 4294967295。當擴展時間戳啟用時,timestamp字段或者timestamp delta要全置為1,而不是減去時間戳或者時間戳差的值。
2.5 chunk 示例
2.5.1 chunk 示例1
本示例展示了一個音頻消息流。流中包含有冗余信息。- 分析第一個chunk:
-1 首先包含第一個Message的chunk的chunk type為0,因為它前面沒有可參考的chunk,timestamp為1000,表示時間戳。
-2 type為0的header占用11個字節(jié),假定chunk stream id為3 < 127,因此basic header占用1個字節(jié);
-3 再加上data的32字節(jié),因此第一個chunk共44個字節(jié)=11+1+32個字節(jié)。 - 分析第二個chunk:
-1. 第二個chunk與第一個chunk的cs id和chunk type id,以及data的長度都相同,因此采用類型2;
-2. 可知timestamp delta = 1020 -1000 = 20;
-3. 因此第二個chunk占用36 = 3 (message header) + 1(basic header) +32 - 分析第三個chunk:
-1. 第三個 chunk 和第二個 chunk 的 cs id ,chunk type id,以及 data 的長度和時間戳的差值都相同,因此采用 類型 3,省去全部的 Message Header 的信息;
-2. 因此占用 33 = 1 + 32 - 分析第四個chunk:
-1.第四個 chunk 和第三個 chunk 情況相同,也占用 33 = 1 + 32 個字節(jié)。
最后實際發(fā)送的chunk如下面表格所示,該表格展示了由此音頻流產(chǎn)生的塊信息。從第 3 條信息開始,數(shù)據(jù)傳輸達到最大優(yōu)化。每條消息的頭部只增加了 1 字節(jié)長度。