版權(quán)聲明:本文為衛(wèi)偉學(xué)習(xí)總結(jié)文章,轉(zhuǎn)載請(qǐng)注明出處!
1.handshake
1.1.概述
rtmp連接從握手開始。它包含三個(gè)固定大小的塊僧诚。客戶端發(fā)送的三個(gè)塊命名為c0,c1,c2;服務(wù)端發(fā)送的三個(gè)塊命名為S0,S1,S2蝗碎。
握手序列:
- 客戶端通過發(fā)送c0和c1消息來啟動(dòng)握手過程湖笨。客戶端必須收到S1消息蹦骑,然后發(fā)送C2消息慈省。客戶端必須接收到S2消息眠菇,然后發(fā)送其他數(shù)據(jù)边败。
- 服務(wù)端必須接收到C0或者C1消息袱衷,然后發(fā)送S0和S1消息。服務(wù)端必須接收到C2消息笑窜,然后發(fā)送其他數(shù)據(jù)致燥。
握手示意圖
1.2.complex handshake
1.2.1 C0和S0格式
C0和S0包由一個(gè)字節(jié)組成,下面是C0/S0包內(nèi)的字段:- version(1 byte) : RTMP的版本排截,一般為3
1.2.2 C1和S1格式
C1和S1包含兩部分?jǐn)?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)搬葬,這個(gè)字段代表客戶端請(qǐng)求的RTMP版本號(hào)。在S0包內(nèi)艳悔,這個(gè)字段代表服務(wù)端選擇的RTMP版本號(hào)。當(dāng)前使用的版本是3女仰。在版本0-2用在早期的產(chǎn)品中猜年,如今已經(jīng)棄用;版本4-31被預(yù)留用于后續(xù)產(chǎn)品疾忍;版本32-255(為了區(qū)分RTMAP協(xié)議和文本協(xié)議乔外,文本協(xié)議通常是可以打印字符)不允許使用。如果服務(wù)端無(wú)法識(shí)別客戶端的版本號(hào)一罩,應(yīng)該回復(fù)版本3杨幼。客戶端可以選擇降低到版本3聂渊,或者終止握手過程差购。
1.3.2 C1和S1格式
C1和S1包長(zhǎng)度為1536字節(jié),包含以下字段:- time(4 bytes) : 本字段包含一個(gè)時(shí)間戳汉嗽,客戶端應(yīng)該使用此字段來標(biāo)識(shí)所有流塊的時(shí)刻欲逃。時(shí)間戳取值可以為零或者任意值。為了同步多個(gè)塊流饼暑,客戶端可能希望多個(gè)塊流使用相同的時(shí)間戳稳析。
- zero (4 bytes): 本字段必須為零。
- random (1528 bytes):本字段可以包含任意數(shù)據(jù)弓叛。由于握手的雙方需要區(qū)分另一端彰居,此字段填充的數(shù)據(jù)必須足夠隨機(jī)(以防止與其他握手端混淆)。不過沒有必要為此使用加密數(shù)據(jù)或動(dòng)態(tài)數(shù)據(jù)撰筷。
1.3.3 C2和S2格式
C2 和 S2 包長(zhǎng)度為 1536 字節(jié)陈惰,作為 C1 和 S1 的回應(yīng),包含以下字段:- time(4 bytes):本字段必須包含對(duì)端發(fā)送的時(shí)間戳闭专。
- time2(4 bytes):本字段必須包含時(shí)間戳奴潘,取值為接收對(duì)端發(fā)送過來的握手包的時(shí)刻旧烧。
- random(1528 bytes):本字段必須包含對(duì)端發(fā)送過來的隨機(jī)數(shù)據(jù)。握手的雙方可以使用時(shí)間 1 和時(shí)間 2 字段來估算網(wǎng)絡(luò)連接的帶寬和/或延遲画髓,但是不一定有用掘剪。
2.組塊
2.1塊格式
- 塊的基本頭(1-3字節(jié)): 這個(gè)字段包含塊流ID和塊類型。塊的類型決定了編碼過的消息頭的格式奈虾。這個(gè)字段是一個(gè)變長(zhǎng)字段夺谁,長(zhǎng)度取決于塊流ID。
- 消息頭(0肉微,3匾鸥,7,11字節(jié)):這個(gè)字段包含被發(fā)送的消息信息(無(wú)論是全部碉纳,還是部分)勿负。字段長(zhǎng)度由塊頭中的塊類型來決定。
- 擴(kuò)展時(shí)間戳(0劳曹,4字節(jié)): 這個(gè)字段是否存在取決于塊消息頭中編碼的時(shí)間戳奴愉。
- 塊數(shù)據(jù)(可變大小):當(dāng)前塊的有效數(shù)據(jù)铁孵,上限為配置的最大塊大小锭硼。
2.2 Basic Header
包含chunk stream ID(流通道id)和chunk type(即fmt), chunk stream id 一般被簡(jiǎn)寫為CSID,用來唯一標(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é)議支持用戶自定義[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 是控制信息和一些命令信息播赁。
2.2.1 Basic Header: 1byte
2.2.2 Basic Header: 2 byte, csid == 0
CSID占14bit,此時(shí)協(xié)議將于chunk type所在字節(jié)的其他bit都置為0吼渡,剩下的一個(gè)字節(jié)表示CSID - 64容为,這樣共有8個(gè)bit來存儲(chǔ) CSID,8 bit 可以表示 [0,255] 個(gè)數(shù),因此這種情況下 CSID 在 [64,319]坎背,其中 319 = 255 + 64替劈。2.2.3 Basic Header: 3 bytes, csid == 1
CSID占22bit,此時(shí)協(xié)議將第一個(gè)字節(jié)的[2,8]bit置1得滤,余下的16個(gè)bit表示CSID - 64陨献,這樣共有16個(gè)bit來存儲(chǔ)CSID,16bit可以表示[0,65535]共 65536 個(gè)數(shù)懂更,因此這種情況下 CSID 在 [64,65599]岗仑,其中65599=65535+64鄙陡,需要注意的是钝腺,Basic Header是采用小端存儲(chǔ)的方式啦辐,越往后的字節(jié)數(shù)量級(jí)越高,因此通過3個(gè)字節(jié)的每一個(gè)bit的值來計(jì)算CSID時(shí)慷暂,應(yīng)該是: <第三個(gè)字節(jié)的值> * 256 + <第二個(gè)字節(jié)的值> + 64.2.3 Message Header
包含了要發(fā)送的實(shí)際消息(可能是完整的聘殖,也可能是一部分)的描述消息。Message Header的格式和長(zhǎng)度取決于Basic Header的chunk type行瑞,即fmt就斤,共有四種不同的格式。其中一種格式可以表示其他三種表示的所有數(shù)據(jù)蘑辑,但由于其他三種格式是基于對(duì)之前chunk的差量化的表示,因此可以更簡(jiǎn)潔地表示相同的數(shù)據(jù)坠宴,實(shí)際使用的時(shí)候還是應(yīng)該采用盡量少的字節(jié)表示相同意義的數(shù)據(jù)洋魂。下面按字節(jié)從多到少的順序分別介紹這四種格式的 Message Header。
Message Header四種消息頭格式
一喜鼓、Chunk Type(fmt)=0:11bytes
type=0時(shí)Message Header占用11個(gè)字節(jié)副砍,其他三種能表示的數(shù)據(jù)它都能表示,但Chunk stream的開始第一個(gè)chunk和頭信息中時(shí)間戳后退(即值與上一個(gè)chunk相比減少庄岖,通常在回退播放的時(shí)候會(huì)出現(xiàn)這種情況)的時(shí)候必須采用這種格式豁翎。
- timestamp(時(shí)間戳):占用3個(gè)字節(jié),因此它最多能表示16777215=0xFFFFFF=2^24-1隅忿,當(dāng)它的值超過這個(gè)最大值時(shí)心剥,這三個(gè)字節(jié)都置為1,這樣實(shí)際的timestamp會(huì)轉(zhuǎn)存到 ExtendedTimestamp 字段中背桐,接收端在判斷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)度畦娄,單位時(shí)字節(jié)。注意這里時(shí)Message的長(zhǎng)度,也就是chunk屬于Message的總長(zhǎng)度熙卡,而不是chunk本事data的長(zhǎng)度杖刷。
- message type id(消息的類型id): 1個(gè)字節(jié),表示實(shí)際發(fā)送的數(shù)據(jù)的類型驳癌,如8代表音頻數(shù)據(jù)滑燃,9代表視頻數(shù)據(jù)。
- message stream id(消息的流id): 4個(gè)字節(jié)喂柒,表示該chunk所在的流的ID不瓶,和Basic Header的CSID一樣,采用小端存儲(chǔ)方式灾杰。
二蚊丐、Chunk Type(fmt)=1:7bytes
type為1時(shí)占用7個(gè)字節(jié),省去了表示message stream id的4個(gè)字節(jié)艳吠,表示此chunk和上一次發(fā)的chunk所在的流相同麦备,如果在發(fā)送端和對(duì)端有一個(gè)流鏈接的時(shí)候可以盡量采用這種格式。
- timestamp delta:3 bytes昭娩,這里和type=0時(shí)不同凛篙,存儲(chǔ)的是和上一個(gè)chunk的時(shí)間差。類似上面提到的timestamp栏渺,當(dāng)它的值超過3個(gè)字節(jié)所能表示的最大值時(shí)呛梆,三個(gè)字節(jié)都置為1,實(shí)際的時(shí)間戳差值就會(huì)轉(zhuǎn)存到Extended Timestamp字段中磕诊,接收端在判斷timestamp delta字段24個(gè)bit都為1時(shí)就會(huì)去Extended Timestamp 中解析實(shí)際的與上次時(shí)間戳的差值填物。
- 其他字段與上面的解釋相同。
三霎终、Chunk Type(fmt)=2: 3 bytes
type 為 2 時(shí)占用 3 個(gè)字節(jié)滞磺,相對(duì)于 type = 1 格式又省去了表示消息長(zhǎng)度的3個(gè)字節(jié)和表示消息類型的1個(gè)字節(jié),表示此 chunk和上一次發(fā)送的 chunk 所在的流莱褒、消息的長(zhǎng)度和消息的類型都相同击困。余下的這三個(gè)字節(jié)表示 timestamp delta,使用同type=1广凸。
四阅茶、Chunk Type(fmt)=3: 0byte
type=3時(shí),為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í)的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。
2.4 Extended Timestamp(擴(kuò)展時(shí)間戳)
在 chunk 中會(huì)有時(shí)間戳 timestamp 和時(shí)間戳差 timestamp delta爷狈,并且它們不會(huì)同時(shí)存在植影,只有這兩者之一大于3字節(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,而不是減去時(shí)間戳或者時(shí)間戳差的值妈倔。
2.5 chunk 示例
2.5.1 chunk 示例1
本示例展示了一個(gè)音頻消息流博投。流中包含有冗余信息。- 分析第一個(gè)chunk:
-1 首先包含第一個(gè)Message的chunk的chunk type為0盯蝴,因?yàn)樗懊鏇]有可參考的chunk贬堵,timestamp為1000,表示時(shí)間戳结洼。
-2 type為0的header占用11個(gè)字節(jié),假定chunk stream id為3 < 127叉跛,因此basic header占用1個(gè)字節(jié);
-3 再加上data的32字節(jié)松忍,因此第一個(gè)chunk共44個(gè)字節(jié)=11+1+32個(gè)字節(jié)。 - 分析第二個(gè)chunk:
-1. 第二個(gè)chunk與第一個(gè)chunk的cs id和chunk type id筷厘,以及data的長(zhǎng)度都相同鸣峭,因此采用類型2;
-2. 可知timestamp delta = 1020 -1000 = 20;
-3. 因此第二個(gè)chunk占用36 = 3 (message header) + 1(basic header) +32 - 分析第三個(gè)chunk:
-1. 第三個(gè) chunk 和第二個(gè) chunk 的 cs id ,chunk type id酥艳,以及 data 的長(zhǎng)度和時(shí)間戳的差值都相同摊溶,因此采用 類型 3,省去全部的 Message Header 的信息充石;
-2. 因此占用 33 = 1 + 32 - 分析第四個(gè)chunk:
-1.第四個(gè) chunk 和第三個(gè) chunk 情況相同莫换,也占用 33 = 1 + 32 個(gè)字節(jié)。
最后實(shí)際發(fā)送的chunk如下面表格所示,該表格展示了由此音頻流產(chǎn)生的塊信息拉岁。從第 3 條信息開始坷剧,數(shù)據(jù)傳輸達(dá)到最大優(yōu)化。每條消息的頭部只增加了 1 字節(jié)長(zhǎng)度喊暖。