iOS音視頻直播--RTMP協(xié)議解析

概述

本文篇幅較長愿待,主要介紹RTMP協(xié)議格式信令過程靴患、FLV tag三個部分仍侥,文檔結(jié)構(gòu)參考rtmp_specification_1.0,最后通過wireshark抓取項目推流過程介紹其中細節(jié)鸳君。
筆者項目背景是iOS推流端访圃,所以研究的側(cè)重點也在RTMP推流過程,后續(xù)涉及到拉流端再繼續(xù)完善相嵌,如有錯誤感謝指出。
手打不易况脆,感謝支持饭宾,轉(zhuǎn)載請注明出處。

1. 簡介

Adobe的實時消息傳遞協(xié)議(RTMP)在可靠的流傳輸協(xié)議(如TCP)之上提供雙向消息多路服務(wù)格了,使得通信的兩端傳遞并行的音視頻流看铆、數(shù)據(jù)流以及相關(guān)的時間信息。通過抽象出的chunk流盛末,發(fā)送細粒度的數(shù)據(jù)包弹惦,并且可以為不同類型的信息分配不同的流通道,以便在傳輸能力受限時(如網(wǎng)絡(luò)不佳)優(yōu)先傳輸高優(yōu)先級數(shù)據(jù)以減少網(wǎng)絡(luò)延時悄但。

2. 專有名詞

  • Message stream:數(shù)據(jù)流中的邏輯通道棠隐。
  • Message stream ID:數(shù)據(jù)流中每一條message都有對應(yīng)的msid來標識邏輯通道。
    默認0檐嚣,推流端在createStream的_result回調(diào)時更新此id助泽,之后發(fā)送消息使用新的msid。
  • Chunk:消息分塊嚎京,消息真正在網(wǎng)絡(luò)傳輸前會根據(jù)chunk size拆分成更小的數(shù)據(jù)包嗡贺,chunk是RTMP協(xié)議層最基本的數(shù)據(jù)包,也是發(fā)送給下層協(xié)議的最小單位鞍帝,音視頻數(shù)據(jù)的chunks需保證時間戳遞增(回退播放除外)诫睬。
  • Chunk stream:chunk流的邏輯通道。
  • Chunk stream ID:csid用來標識對應(yīng)的chunk流帕涌。
    通成惴玻控制流csid為2续徽,命令流為3澳盐,開發(fā)中發(fā)現(xiàn)音視頻流csid可自定義挺邀,如音頻流4漾脂,視頻流6瞎疼。
  • Action Message Format(AMF):用來序列化ActionScript對象圖的二進制格式(功能類似json梭稚,相比json占用更少的字節(jié))撇眯,兩個版本AMF0和AMF3谱仪。

3. 字節(jié)序蓄髓,對齊及時間格式

除非特殊說明会涎,字節(jié)序采用大端格式裹匙。
默認字節(jié)對齊,如一個16-bit的字段填充數(shù)據(jù)時不足16bit需要用0補齊末秃。
時間戳采用毫秒級無符號整型數(shù)概页。

4. Handshake

建立RTMP連接首先需要握手,由三個定長的chunk組成练慕。

4.1 順序要求

  • client必須收到s1才能發(fā)送c2惰匙;
  • client必須收到s2才能發(fā)送其他數(shù)據(jù),如connect等連接操作铃将;
  • server必須收到c0才能發(fā)送s0和s1项鬼;
  • server必須收到c1才能發(fā)送s2;
  • server必須收到c2才能發(fā)送其他數(shù)據(jù)劲阎;

如下圖所示

實際開發(fā)中绘盟,為了盡量減少通信的次數(shù),發(fā)送順序可以優(yōu)化成三步:

4.2 c0 and s0 Format

c0和s0都是8-bit的整型字段悯仙,如下圖

Version(8 bit):該字段分別表示client/server支持的version龄毡,通常為3。

4.3 c1 and s1 Format

c1和s1都是1536字節(jié)锡垄,如下圖

Time(4 bytes):時間戳沦零,通常為0,是之后發(fā)送chunk流的參考時間货岭;
Zero(4 bytes):恒為0蠢终;
Random data(1528 bytes):隨機數(shù);

4.4 c2 and s2 Format

c2和s2同樣都是1536字節(jié)茴她,如下圖

Time(4 bytes):對于c2寻拂,填入對端發(fā)來的s1包里帶的時間戳,s2同理丈牢;
Time2(4 bytes):對于c2祭钉,填入讀取到s1包時刻的時間戳,s2同理己沛,開發(fā)中發(fā)現(xiàn)可填0慌核;
Random echo(1528 bytes):對于c2距境,填入對端發(fā)來的s1包里帶的隨機數(shù),s2同理垮卓;
至此垫桂,握手階段完成,可以發(fā)送message了粟按。

5. RTMP Chunk Stream

Chunk Stream是對傳輸RTMP Chunk的流的抽象诬滩,可以對Control、Audio灭将、Video等多路流同時傳輸疼鸟,并在邏輯上區(qū)別開來。

5.1 Message Format

一個可以拆分成chunk發(fā)送的message庙曙,應(yīng)該包含以下必要的字段:
Timestamp(3 bytes):message的時間戳空镜;
Length(3 bytes):message的payload負載的長度,而非message的長度捌朴,對于control類型的消息是命令的長度吴攒,對于audio和video類型的消息是FLV audio/video tag的長度;
Type Id(1 byte):消息類型砂蔽,預留的包含control類消息的各個細分命令洼怔,audio,video等察皇,也可自定義id供RTMP協(xié)議之上的業(yè)務(wù)協(xié)議使用;
Message Stream ID(4 bytes):消息的唯一標識泽台,簡稱msid什荣,多路消息流復用相同的chunk流時根據(jù)這個id解復用,以便區(qū)分是否是同一個消息的chunk怀酷,小端格式存儲稻爬;

5.2 Chunking

RTMP發(fā)送的message需要根據(jù)chunk size拆分成chunk發(fā)送,而且必須在上一個chunk發(fā)送完成后才能發(fā)送下一個chunk蜕依,所以chunk是數(shù)據(jù)封包及發(fā)送的最小單位桅锄。每個chunk中帶有(或間接找到)msid,接收端會按照這個id將chunk組裝成message样眠。
這么做的好處是避免一些數(shù)據(jù)量大但優(yōu)先級較低的消息(如video)阻塞數(shù)據(jù)量小但優(yōu)先級較高的消息(如audio和control)友瘤,這樣可以一定程度避免直播場景中因網(wǎng)絡(luò)波動造成的卡頓現(xiàn)象。
并且chunk header的長度是變長的檐束,根據(jù)場景可以選擇type0-type3類型壓縮header辫秧,從而減少分包造成的數(shù)據(jù)浪費。
chunk size默認128字節(jié)被丧,在傳輸過程中盟戏,可通過Set Chunk Size 消息通知對端chunk拆分的最大值绪妹。更大的chunk size可減少cpu占用,但同時寫入時間較長容易在低帶寬網(wǎng)絡(luò)時阻塞后面更重要的message柿究。更小的chunk size可以減少阻塞問題邮旷,但是會引入更多額外的信息(chunk中的header),小量多次的傳輸也會造成發(fā)送的間斷導致不能充分利用高帶寬網(wǎng)絡(luò)的優(yōu)勢蝇摸。所以實際傳輸中要根據(jù)不同的網(wǎng)絡(luò)帶寬動態(tài)調(diào)整chunk size的大小婶肩,以在cpu利用率和網(wǎng)絡(luò)帶寬之間做出最佳權(quán)衡。

5.2.1 Chunk Format

每一個chunk由header和data組成探入,如下圖

Basic Header(1-3 bytes):該字段包含chunk type和chunk stream id(簡稱csid)狡孔,其中fmt決定了chunk的類型及message header的長度,占2 bit蜂嗽,而Basic header的長度取決于csid的數(shù)值大小苗膝,最少占1 byte;
Message Header(0植旧,3辱揭,7 or 11 bytes):長度取決于Basic Header中的chunk type,有Type 0病附,1问窃,2,3類型的header完沪,注意message length字段指的是message body的長度(不包含header)域庇,而且如果被拆分成chunk,此字段填充拆分前message的body長度覆积,而不是chunk的長度听皿;
Extended Timestamp(0 or 4 bytes):當message header中的timestamp或timestamp delta大于0xFFFFFF時啟用這個字段,存儲直接或者間接時間戳宽档,注意存儲直接時間戳時要寫入真實的時間戳尉姨;
Chunk Data(variable size):chunk的負載數(shù)據(jù),最大為chunk size吗冤;

5.2.1.1 Chunk Basic Header

fmt(2 bits):該字段即是上文提到的chunk type又厉,取值[0, 3],表示四種類型的Chunk Message Header椎瘟;
csid:RTMP支持用戶自定義[3, 65599]區(qū)間的csid覆致,因為0、1肺蔚、2為協(xié)議預留篷朵,預留的id可推算出Basic header長度,小端存儲
0表示Basic Header總共占2個字節(jié)声旺,csid在[64, 319]之間笔链;
1表示占3個字節(jié),csid在[64, 65599]之間腮猖;
2表示該chunk是控制信息鉴扫,后面會講到Protocol Control Message和User Control Message的csid必須為2。
而在[2, 63]區(qū)間的csid可以使用1字節(jié)的Basic Header澈缺,csid直接讀出即可坪创。

在[64, 319]區(qū)間的csid可使用2字節(jié)的header,實際的csid通過 the second byte + 64 計算得出姐赡。

在[64, 65599]區(qū)間的csid可使用3字節(jié)的header莱预,其中csid占用2個字節(jié),這就涉及到字節(jié)序项滑,注意此時是小端存儲依沮,實際的csid通過 (the third byte)*256 + the second byte + 64 計算得出。

盡管可以隨意自定義csid枪狂,但盡量使用較小的id以減小Header的長度危喉。

5.2.1.2 Chunk Message Header

5.2.1.2.1 Type 0

Type 0的Chunk Message Header共11字節(jié),在chunk stream開始的第一個chunk和時間戳有回退(如回退播放)的情況下必須使用Type 0州疾。

timestamp(3 bytes):直接時間戳(區(qū)別于后續(xù)提到的間接時間戳 ts delta)辜限,如果時間戳大于等于16777215(二進制0xFFFFFF),該字段必須等于16777215严蓖,然后轉(zhuǎn)存到4字節(jié)的Extended Timstamp字段中薄嫡;
message length(3 bytes):注意這里是Message的長度,即拆分成chunk之前Message的payload負載的長度颗胡,而不是chunk里data的長度毫深;
message type id(1 byte):消息類型,如8代表audio數(shù)據(jù)杭措,9代表video费什,其他值代表細分的control消息钾恢,后面會詳細講到手素;
message stream id(4 bytes):通常相同的chunk stream中的所有消息都來自相同的message stream,雖然可以將不同的message streams傳輸?shù)较嗤腸hunk stream中瘩蚪,但這就抵消了Chunk Message Header頭壓縮的好處泉懦。所以開發(fā)直播推流時,audio和video的csid不一樣疹瘦,但msid一般都一樣崩哩;

5.2.1.2.2 Type 1

共7字節(jié),相比Type 0,省略msid字段邓嘹,和前一個chunk共用msid酣栈。

timestamp delta(3 bytes):間接時間戳,表示和上一個chunk的時間戳差值汹押,可用上一個的timestamp+delta計算得出矿筝。

5.2.1.2.3 Type 2

共3字節(jié),省略msid和message length棚贾,和前一個chunk共用這倆字段窖维,在發(fā)送定長的數(shù)據(jù)時,在第一個chunk之后使用這個類型妙痹。

5.2.1.2.4 Type 3

Type 3類型的chunk沒有Message header铸史。
當它跟在Type 0的chunk后面時,表示和前一個chunk的時間戳是相同的怯伊,也就是一個Message拆分成多個chunk時琳轿,后一個chunk和前一個chunk同屬一個Message自然也就可以不用傳Message header。
當它跟在Type 1或者Type2的chunk后面時震贵,表示和前一個時間戳的差相同利赋。如第一個chunk是Type 0,timestamp = 0猩系,第二個chunk是Type 2媚送,timestamp delta = 20,表示時間戳為0+20=20寇甸,第三個chunk是Type 3塘偎,則timestamp delta = 20,表示時間戳為20+20=40拿霉;

5.2.2 Examples

5.2.2.1 Example 1

下圖展示了常見的audio消息流

分析:

  1. 第一個Message的chunk的chunk type為0吟秩,因為前面沒有可參考的chunk,此時chunk的長度為Basic Header(1 byte)+ Message Header(11 bytes)+ Payload(32 bytes)= 44 bytes绽淘;
  2. 第二個Message的chunk可與前一個共用length涵防、type id和msid,但是不可省略timestamp delta字段沪铭,所以chunk type為2壮池,同理可得chunk長度為36 bytes;
  3. 第三個Message的chunk可以共用前一個的timestamp delta字段杀怠,所以chunk type為0椰憋,chunk長度為33 bytes;
    詳見下圖

5.2.2.2 Example 2

下圖展示了如何將過長的message以128字節(jié)的chunk size拆分成多個chunks

分析:

  1. Payload length為307 > 128字節(jié)赔退,所以需要將Message拆分成多個chunks橙依,首個chunk使用Type 0证舟;
  2. 之后的chunk由于是同一個Message拆分而來,以上字段都可共享窗骑,所以直接使用Type 3女责;
  3. 需要注意的是,第一個chunk的length需要傳入整個message的負載長度即307创译;

以上兩個例子可以看出鲤竹,Type 3類型的chunk可以用在兩個場景:

  1. 單個較大Message拆分的非首個chunk;
  2. 某個Message可以與之前的Message共享以上字段昔榴,其第一個chunk也可為Type 3辛藻;
    開發(fā)中場景1比較常見,比如視頻幀大于chunk size(默認128 bytes)時互订,使用Type 3可有效減少chunk header長度吱肌。

5.3 Protocol Control Messages(協(xié)議控制消息)

在RTMP的chunk流中使用特殊的type id如1,2仰禽,3氮墨,5,6吐葵,代表協(xié)議控制消息规揪,這類控制消息msid必須為0,csid必須為2温峭,并且時間戳為0猛铅,接收端收到立即生效。

5.3.1 Set Chunk Size(Message Type ID = 1)

上文提到凤藏,rtmp消息需要以chunk size為單位封裝成chunk包發(fā)送奸忽,因此接收端需要根據(jù)chunk size才能正確解包,所以雙端都要記錄對端的封包單位chunk size揖庄,默認128 bytes栗菜。
通信過程中可發(fā)送此消息通知對端更新其記錄的本端的chunk size。比如client想發(fā)送131 bytes的音頻數(shù)據(jù)(此時chunk size為128 bytes蹄梢,不更新chunk size的話需要拆成兩個chunk)疙筹,此時client可通知對端,這邊client的chunk size更新為131 bytes禁炒,之后發(fā)送一個data為131 bytes的chunk即可而咆,server端收到Set Chunk Size之后更新chunk size即可正確解析之后到來的chunk。
雙端的chunk size各自獨立維護齐苛,可以不同翘盖。

如上圖桂塞,payload的第一個bit恒為0凹蜂。
chunk size(31 bits):可表示[1, 0x7FFFFFFF]區(qū)間,但是由于chunk size要小于Message的length,而Message length字段用3個字節(jié)存儲玛痊,最大值為0xFFFFFF汰瘫,所以實際可取值區(qū)間為[1, 0xFFFFFF]。

5.3.2 Abort Message(2)

發(fā)送數(shù)據(jù)過程中擂煞,發(fā)送端可發(fā)送Abort消息通知接收端丟棄當前未接收完的Message及忽略之后的消息混弥,接收端根據(jù)Abort消息中的csid可丟棄對應(yīng)chunk流中之后的所有數(shù)據(jù)。比如在發(fā)送端需要關(guān)閉時对省,發(fā)送此消息通知對端之后的數(shù)據(jù)可以不用處理了蝗拿。

chunk stream ID(32 bits):payload中就一個字段csid,表示可忽略的chunk流蒿涎。

5.3.3 Acknowledge(3)

當收到對端消息字節(jié)數(shù)等于window size時哀托,接收端要回復一個ack告知對端可以繼續(xù)發(fā)送數(shù)據(jù)。當然這個窗口大小也可以理解為接收端read buffer的大小劳秋,意為接收端在發(fā)送ack之前最多可以處理的數(shù)據(jù)大小仓手。

sequence number(32 bits):接收端自handshake起接收到的總的數(shù)據(jù)量,單位字節(jié)玻淑。

5.3.4 Window Acknowledge Size(5)

對應(yīng)于上文提到的ack嗽冒,發(fā)送端可以發(fā)送此消息通知對端更新窗口大小,一般在音視頻數(shù)據(jù)之前發(fā)送补履。
不同于chunk size添坊,window size通常比較大,以容納更多的數(shù)據(jù)在緩沖區(qū)中箫锤,并且雙端的window size共同維護帅腌,保持相同

5.3.5 Set Peer Bandwidth(6)

限制對端的輸出帶寬麻汰,接收端收到該消息后速客,通過限制已發(fā)送但尚未收到ack的數(shù)據(jù)大小,來限制輸出帶寬五鲫。如果帶寬與上次發(fā)送給發(fā)送端的window size不同的話溺职,接收端需要重新發(fā)送Window Acknowledge Size消息。

limit type有以下取值:
0 - Hard:接收端立即將window size更新為window ack size位喂。
1 - Soft:接收端可以更新window size浪耘,也可以保留原值,但是原值要小于window ack size塑崖。
2 - Dynamic:如果上次收到的Set Peer Bandwidth消息的limit type是Hard七冲,這次也按Hard處理,否則可忽略此消息规婆。

6. RTMP Command Messages

上文說到的Protocol Control Message(協(xié)議控制消息)是在RTMP Chunk Stream Protocol(RTMP Chunk流協(xié)議層)澜躺,總的來說是對chunk流的管理蝉稳。本章介紹在RTMP Stream layer(流協(xié)議層)的命令消息。

6.1 Types of Messages

6.1.1 Command Message(Message Type ID = 20掘鄙,17)

命令消息耘戚,攜帶由AMF編碼格式的命令,AMF0的type id是20操漠,AMF3是17收津。例如connect翅帜,createStream奢米,publish,play晦闰,pause等命令嚣鄙,對端會回復如_result部服,_error等狀態(tài),具體什么格式以及如何將命令和回調(diào)一一對應(yīng)后面會詳細介紹拗慨。命令消息中包含command name廓八、transaction ID和params。

6.1.2 Data Message(18赵抢,15)

數(shù)據(jù)消息剧蹂,傳遞音視頻元數(shù)據(jù)和其他用戶數(shù)據(jù),AMF0的type id是18烦却,AMF3是15宠叼。
這個是很有必要的,比如直播推流發(fā)送實際音視頻數(shù)據(jù)之前其爵,必須要先發(fā)送@setDataFrame命令冒冬,發(fā)送視頻分辨率、幀率碼率摩渺、音頻采樣率简烤、音視頻編碼方式等元數(shù)據(jù),以便接收端收到后續(xù)數(shù)據(jù)可以正確解析摇幻。

6.1.3 Share Object Message(19横侦,16)

共享消息,多個客戶端需要共享一些鍵值對時使用绰姻,AMF0是19枉侧,AMF3是16。

6.1.4 Audio Message(8)音頻消息

6.1.5 Video Message(9)視頻消息

6.1.6 Aggregate Message(22)

聚合消息狂芋,可以將多個子消息聚合到一個message中從而減少發(fā)送的chunk榨馁。而且子消息在內(nèi)存中是連續(xù)存儲的,在進行系統(tǒng)調(diào)用時效率更高帜矾。

6.1.7 User Control Message Events(4)

用戶控制消息翼虫,client和server端可以發(fā)送該消息通知對端執(zhí)行用戶控制事件屑柔。此類消息msid必須為0,csid必須為2蛙讥。負載格式(即RTMP Chunk Data)如下圖


Event Type(16 bits):指定事件類型,共7種灭衷,如下圖次慢;
Event Data(變長):因為底層還要包裝成chunk,所以為了不被拆分成多個chunk翔曲,需要保證Event Type+Event Data <= chunk size迫像;

其中常見的有
Set Buffer Length:在server端處理數(shù)據(jù)流之前,client通知server每毫秒接收的流的大型椤闻妓;
Ping Request & Response:推流過程中server如果長時間收不到數(shù)據(jù),會發(fā)送ping詢問client是否可達掠械,client狀態(tài)正常時需要回復該消息表明網(wǎng)絡(luò)可達由缆;

6.2 Type of Commands

命令類型的消息有很多種,如上文所說都需要AMF編碼猾蒂,包含命令名稱均唉、事務(wù)ID和相關(guān)參數(shù)。如client端發(fā)送connect命令時需要包含要連接的應(yīng)用名稱作為參數(shù)肚菠,然后server端回復消息時帶上收到的transaction ID對應(yīng)此條消息舔箭。回復命令有_result蚊逢,_error层扶,或者其他如verifyClient,contactExternalServer的方法名烙荷。命令類型的csid通常為3镜会。
命令分兩大類,NetConnection和NetStream终抽。

6.2.1 NetConnection Commands

連接命令稚叹,維護兩端的連接狀態(tài),也可以實現(xiàn)RPC遠程進程調(diào)用拿诸,接收端的回調(diào)為_result和_error扒袖,分別表成功和失敗,有四個命令:

  • connect
  • call
  • close
  • createStream

6.2.1.1 connect

client發(fā)送connect命令請求連接對應(yīng)的應(yīng)用亩码,格式如下

Command Object用到的鍵值對如下

audioCodecs取值如下

videoCodecs如下

videoFunction如下

編碼類型如下

server端回復的消息結(jié)構(gòu)如下

connect大致流程如下

  1. client發(fā)送連接命令季率,要求連接server的應(yīng)用實例;
  2. server啟動對應(yīng)的應(yīng)用描沟,并發(fā)送Window Acknowledge Size告知client設(shè)置window size飒泻;
  3. server發(fā)送Set Peer Bandwidth限制client默認輸出帶寬鞭光,這個值一般等于上一步的window size惰许;
  4. client收到Set Peer Bandwidth后史辙,同樣發(fā)送Window Acknowledge Size消息,一般等于之前收到的window size晦毙;
  5. server發(fā)送User Control Message(StreamBegin),表示準備接收message stream耙蔑;
  6. server回復步驟1 _result或者_error告知client連接結(jié)果须揣,并且包含相同的transcation ID(即1)返敬,同時參數(shù)攜帶如fmsVer表示flash server版本,capabilities表示可承載的連接數(shù)量凛澎,以及l(fā)evel,code最铁,description等狀態(tài)描述冷尉;

6.2.1.2 Call

Remote Procedure calls遠程進程調(diào)用,也可理解為遠程方法調(diào)用雾棺,格式如下

響應(yīng)格式如下

6.2.1.3 createStream

client通過發(fā)送此命令創(chuàng)建發(fā)布音視頻的邏輯通道放刨,即message stream channel。之前提到msid默認為0麻昼,client收到createStream的回調(diào)之后倍谜,解析其中的msid并更新本地尔崔,自此之后發(fā)送數(shù)據(jù)的msid都為此id洗搂。client發(fā)送的格式如下

server回復的格式如下

6.2.2 NetStream Commands

流命令宇攻,對音視頻流的狀態(tài)管理逞刷,如FCPublish/publish發(fā)布流仑最,play播放流等词身,接收端的回調(diào)為onStatus损敷,有如下命令:

  • play
  • play2
  • deleteStream
  • closeStream
  • receiveAudio
  • receiveVideo
  • publish
  • seek
  • pause

onStatus回調(diào)的格式大致一致,如下圖

6.2.2.1 play

拉流端向server端請求播放音視頻流诱桂,可以多次調(diào)用以請求多個視頻流,其中resset字段設(shè)置true用來播放和切換多路流,設(shè)置false會清除其他流僅播放當前請求的流辞槐。結(jié)構(gòu)如下

play流程大致如下

  1. client接收到createStream _result之后,發(fā)送play命令請求播放視頻流;
  2. server接收到play命令后舱殿,發(fā)送Set Chunk Size,后續(xù)發(fā)送的chunk包以此大小拆分枝恋;
  3. server緊接著發(fā)送User Control Message,如StreamIsRecorded和StreamBegin十电,通知client當前流的狀態(tài)信息台盯;
  4. server發(fā)送onStatus消息回復對應(yīng)的play命令,如果流存在則回復NetStream.Play.Start蒿叠,不存在則回復NetStream.Play.StreamNotFound,如果play命令設(shè)置了reset施绎,server還會回復NetStream.Play.Reset鬓照。
  5. 之后server開始發(fā)送要播放的音視頻數(shù)據(jù)拒秘。

6.2.2.2 play 2

和上文play命令都是請求播放音視頻流躺酒,區(qū)別在于server會維護多中比特率的視頻流,client可以調(diào)用play2命令切換。
結(jié)構(gòu)如下裸违,其中NetStreamPlayOptions使用ActionScript 3格式。

play2流程類似play雀久,如下

6.2.2.3 deleteStream

對應(yīng)于createStream,客戶端在結(jié)束推流時發(fā)送deleteStream命令巡蘸,server不需要回復此消息,結(jié)構(gòu)如下圖,其中Stream ID是要關(guān)閉的msid碰纬。

6.2.2.4 receiveAudio

client通知server是否要接收音頻數(shù)據(jù),結(jié)構(gòu)如下

其中,如果flag為false,則服務(wù)端不需要回復道媚。如果為true,server會回復NetStream.Seek.Notify和NetStream.Play.Start消息羡宙。

6.2.2.5 receiveVideo

client通知server是否要接收視頻數(shù)據(jù)钞馁,結(jié)構(gòu)如下

其中,如果flag為false,則服務(wù)端不需要回復绩鸣。如果為true,server會回復NetStream.Seek.Notify和NetStream.Play.Start消息。

6.2.2.6 publish

上文說到垒手,推流時client發(fā)送connect連接到server對應(yīng)的應(yīng)用,收到回調(diào)后發(fā)送createStream創(chuàng)建發(fā)布的msid唆迁,收到回調(diào)后就需要發(fā)送publish指定接下來推送的流名稱鳞溉,收到回調(diào)后就可以推音視頻流了看政,拉流端就可以連接server拉取到對應(yīng)名稱的音視頻流。結(jié)構(gòu)如下

publish過程如下

6.2.2.7 seek

定位到視頻的某個時間點,以毫秒為單位同衣,server返回NetStream.Seek.Notify表成功,_error表失敗,結(jié)構(gòu)如下

6.2.2.8 pause

client發(fā)送pause通知server暫停或恢復播放金蜀,server回復NetStream.Pause.Notify表暫停成功,NetStream.Unpause.Notify表恢復成功,_error表暫停失敗二庵,結(jié)構(gòu)如下

7. RTMP Video Payload

經(jīng)過上文提到的handshake哟绊,connect攀涵,publish/play等一系列流程,接下來需要收發(fā)真正的音視頻數(shù)據(jù)怒详,這些數(shù)據(jù)放在message的payload里橱野,拆分成chunk后即放在data字段。
以flv格式的視頻為例,將編碼后的音視頻幀以FLV tag的AUDIODATA和VIDEODATA字段的格式存儲到chunk的data中。

7.1.1 AUDIODATA

SoundFormat(4 bits):音頻幀格式,一般為AAC楷兽;
SoundRate(2 bits):采樣率,對于AAC取值恒為3;
SoundSize(1 bit):采樣精度,對于壓縮格式恒為1顽染;
SoundType(1 bit):聲道數(shù)左腔,對于AAC恒為1振亮;
SoundData(變長):音頻幀數(shù)據(jù),對于AAC取值為AACAUDIODATA;
對于AAC格式星瘾,SoundRate和SoundType取值恒定并不意味著采樣率和聲道數(shù)恒定盒齿,而是因為播放端可以通過解析AAC碼流中的AudioSpecificConfig獲得肮柜,所以忽略了這兩個字段。

7.1.2 AACAUDIODATA

AAC碼流需要傳輸兩種類型的幀
AudioSpecificConfig:其中包含編碼細分類別、采樣率痴晦、聲道數(shù)三個解碼必要的參數(shù)露乏;
raw frame data:AAC編碼器輸出的序列幀箱锐;

7.1.3 AudioSpecificConfig

object type(5 bits):編碼細分類別,如1表AAC_Main臊恋,2表AAC_LC(低復雜度)衣洁;
frequency index(4 bits):采樣率,如3表48000Hz抖仅,4表44100Hz坊夫;
channel configuration(4 bits):聲道配置岸售,如1表單聲道践樱,2表雙聲道;
以上三字段5+4+4=13 bits凸丸,不足2字節(jié)的bit需要補位0以便字節(jié)對齊拷邢,如下

7.2.1 VIDEODATA

FrameType(4 bits):幀類型,如1表關(guān)鍵幀屎慢,2表非關(guān)鍵幀即PB幀瞭稼;
CodecID(4 bits):編碼格式,如7表avcC格式包裝的H264序列幀腻惠;
這里說明下H264是編碼格式环肘,其輸出的是可以在網(wǎng)絡(luò)傳輸?shù)腘AL Unit,由于NALU長度不一集灌,如果不加以封裝則無法正確拆分多個NALU悔雹,主流包裝格式有avcC和AnnexB兩種;
VideoData(變長):視頻幀數(shù)據(jù)欣喧,對于H264取值為AVCVIDEOPACKET腌零;

7.2.2 AVCVIDEOPACKET

其中
AVCPacketType(8 bits):常用如0表包含sps/pps等解碼必要數(shù)據(jù);1表編碼器輸出的序列幀唆阿;
CompositionTime(3 bytes):單位毫秒益涧,對于序列幀表示延遲解碼間隔時間,因為B幀有解碼延遲驯鳖,若不存在B幀此值一般為0闲询;
Data(變長):AVCDecoderConfigurationRecord或者avcC格式封裝的H264序列幀;
對于傳輸NALU的情況浅辙,不同編碼器的輸出格式不同扭弧,如iOS的VideoToolBox輸出avcC格式,Android的MediaCodec輸出AnnexB格式摔握,在RTMP場景下必要時需要做格式轉(zhuǎn)換寄狼;

7.2.3 AVCDecoderConfigurationRecord

下面通過wireshark抓取的RTMP包分析

圖中高亮部分即是AVCVIDEOPACKET,00表示Data字段存儲的是AVCDecoderConfigurationRecord氨淌,其中
configurationVersion=01泊愧,版本號恒為1;
AVCProfileIndication=42盛正,等于sps[1]删咱,表baseline,H264編碼檔次有baseline/main/high豪筝,其中baseline效率最高適用于實時通信痰滋;
profile_compatibility=e0,等于sps[2]续崖;
AVCLevelIndication=1f敲街,等于sps[3],表auto严望;
lengthSizeMinusOne=ff多艇,NALU單元頭長度-1,即(lengthSizeMinusOne&0x03)+1=4字節(jié)像吻;
numOfSequenceParameterSets=e1峻黍,sps個數(shù),一般為1拨匆,(numOfSequenceParameterSets&0x1f)=1姆涩;
sequenceParameterSetLength=00 13,sps長度惭每;
sequenceParameterSetNALUnit=27 42 e0 1f a9 18 3c 11 d8 0b 50 20 20 23 0a d7 bd f0 10骨饿,sps unit,可以看出AVCProfileIndication台腥,profile_compatibility宏赘,AVCLevelIndication分別對應(yīng)sps的1,2览爵,3字節(jié)置鼻;
numOfPictureParameterSets=01,pps個數(shù)蜓竹,一般為1箕母;
pictureParameterSetLength=00 04,pps長度俱济;
pictureParameterSetNALUnit=28 de 09 88嘶是,pps unit;
sps和pps一般都可通過API直接獲得蛛碌,其內(nèi)部字段的意義屬于編碼器范疇聂喇,讀者有興趣可以自行深究:)

8. 附錄

8.1 RTMP Messages

RTMP消息種類較多且繁瑣,理解起來較為困難,筆者總結(jié)下圖結(jié)構(gòu)幫助理解希太。

8.2 wireshark分析推流過程

上圖是推流項目中實際的交互流程克饶,雖然與文檔相比少了部分命令,基本也涵蓋了主體流程誊辉,并且適用于多個平臺的rtmp server矾湃,下面講下流程細節(jié):

8.2.1 Handshake

簡化版的握手流程,相對簡單不再贅述堕澄;

8.2.2 connect:c to s

下圖高亮部分是rtmp header邀跃,由chunk basic header+chunk message header組成。
format:0表type 0 chunk蛙紫,因為這是第一個chunk拍屑;
csid:Protocol Control Message和User Control Message為2,Command Messages通常為3坑傅,所以這里是3僵驰;
msid:上文說到createStream會創(chuàng)建消息流通道對應(yīng)msid,所以一般在createStream之后msid才會有值裁蚁;
transactionId:恒為1矢渊,之后會有_result與之對應(yīng);

8.2.3 Window Acknowledge Size:s2c

server通知client其緩沖窗口大小5000000字節(jié)枉证。
csid:該消息屬Protocol Control Message矮男,所以為2;

8.2.4 Set Peer Bandwidth/Set Chunk Size/_result('NetConnection.Connect.Success'):s2c

可以看到三條消息的csid與上文說的規(guī)則一致室谚,其中_result的transactionId是1毡鉴,與connect對應(yīng)。

8.2.5 Window Acknowledgement Size:c2s

client每收到window size大小的數(shù)據(jù)之后秒赤,都要返回ack猪瞬,message body包含當前收到的字節(jié)數(shù)。

8.2.6 Set Chunk Size:c2s

client通知server其發(fā)送的chunk分包大小入篮,自此client發(fā)的chunk以8192拆分陈瘦,server發(fā)的chunk以4096拆分,雙端各自維護chunk size且可以不同潮售。

8.2.7 createStream:c2s

client發(fā)送的第二個command message痊项,transactionId為2。

8.2.8 _result() of createStream:s2c

自此雙端都明確創(chuàng)建了一個msid為1的邏輯通道酥诽,此后發(fā)送的消息msid都要為1鞍泉。
這里由于底層TCP是全雙工通信,可能導致之后緊接著發(fā)送的msid來不及更新為1肮帐,如FCPublish咖驮,這種情況也是允許的。

8.2.9 FCPublish:c2s

FCPublish沒在官方文檔中,可以看到與publish消息類似托修,猜測是為了版本兼容忘巧,讀者了解的話麻煩告知:)。

8.2.10 onFCPublish:s2c

8.2.11 publish:c2s

此消息更新到了msid诀黍,自此之后的消息msid都為1袋坑。

8.2.12 onStatus('NetStream.Publish.Start'):s2c

publish屬于NetStream Command Message仗处,上文提到此類消息回調(diào)為onStatus且transactionId為0眯勾,client收到此消息后不能通過transactionId對應(yīng)上,可通過其中的code對應(yīng)婆誓。
自此發(fā)布流成功吃环,接下來開始發(fā)送音視頻相關(guān)數(shù)據(jù)。

8.2.13 @setDataFrame:c2s

發(fā)送音視頻原數(shù)據(jù)洋幻,這些信息可在拉流端共享郁轻。

8.2.14 Audio Data:c2s

csid:音頻和視頻數(shù)據(jù)采用不同的chunk通道,即使用不同的csid文留,并且此csid可自定義好唯;
format:當前是此chunk通道的首個chunk,所以使用type 0 chunk燥翅;
注意Audio data第一個字節(jié)是00骑篙,說明此chunk里包含的是AAC sequence header。

format:此chunk通道的非首個chunk森书,可使用type 1 chunk靶端;
注意Audio data第一個字節(jié)是01,說明此chunk里包含的是音頻序列幀凛膏。

8.2.15 Video Data:c2s

format:當前是此chunk通道的首個chunk杨名,所以使用type 0 chunk;
注意Video data第一個字節(jié)是00猖毫,說明此chunk里包含的是AVC sequence header台谍。

format:此chunk通道的非首個chunk,可使用type 1 chunk吁断;
注意Video data第一個字節(jié)是01趁蕊,說明此chunk里包含的是NAL Unit。
視頻幀大于chunk size時會拆分成多個chunks胯府,為減少數(shù)據(jù)量chunks中非首個chunk要使用Type 3類型介衔,但是實測wireshark并不會顯示Type 3的chunk,猜測內(nèi)部實現(xiàn)把Type 3又整合成了Type 1骂因,讀者抓包時不必疑惑炎咖。

8.2.16 FCUnpublish:c2s

8.2.17 onFCUnpublish:s2c

8.2.18 closeStream:c2s

8.2.19 onStatus('NetStream.Unpublish.Success'):s2c

參考資料

1. RTMP
rtmp_specification_1.0.pdf
2. Action Message Format
AMF 0
AMF 3
3. FLV
video_file_format_spec_v10.pdf
4. AAC
Understanding_AAC
AudioSpecificConfig
5. H.264
H.264
AVC

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子乘盼,更是在濱河造成了極大的恐慌升熊,老刑警劉巖合住,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晶通,死亡現(xiàn)場離奇詭異,居然都是意外死亡檩互,警方通過查閱死者的電腦和手機粹胯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門蓖柔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人风纠,你說我怎么就攤上這事况鸣。” “怎么了竹观?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵镐捧,是天一觀的道長。 經(jīng)常有香客問我臭增,道長懂酱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任誊抛,我火速辦了婚禮列牺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芍锚。我一直安慰自己昔园,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布并炮。 她就那樣靜靜地躺著默刚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逃魄。 梳的紋絲不亂的頭發(fā)上荤西,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音伍俘,去河邊找鬼邪锌。 笑死,一個胖子當著我的面吹牛癌瘾,可吹牛的內(nèi)容都是我干的觅丰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼妨退,長吁一口氣:“原來是場噩夢啊……” “哼妇萄!你這毒婦竟也來了蜕企?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤冠句,失蹤者是張志新(化名)和其女友劉穎轻掩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體懦底,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡唇牧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了聚唐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丐重。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拱层,靈堂內(nèi)的尸體忽然破棺而出弥臼,到底是詐尸還是另有隱情,我是刑警寧澤根灯,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站掺栅,受9級特大地震影響烙肺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜氧卧,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一桃笙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沙绝,春花似錦搏明、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至粗悯,卻和暖如春虚循,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背样傍。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工横缔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衫哥。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓茎刚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親撤逢。 傳聞我的和親對象是個殘疾皇子膛锭,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容