RTMP 協議
一、概述
<font size=3 color=666666>RTMP協議是Real Time Message Protocol(實時信息傳輸協議)的縮寫,它是由Adobe公司提出的一種應用層的協議,用來解決多媒體數據傳輸流的多路復用(Multiplexing)和分包(packetizing)的問題。</font>
握手、消息塊概念
握手的目的是為了確認對端RTMP的Version和確認對端能互相通信。
消息塊就是消息的載體,是RTMP協議最重要的載體,這個載體是有一定格式的,如果把Client和Server端當作鐵路的兩個站點,
那這個消息塊就是火車,它負責運輸貨物。正如火車有火車頭、車廂一樣,消息塊也有基本頭,消息頭和消息負載。RTMP協議當中,
除了握手協議,其他的數據都是以消息塊的方式發送的,發送一個消息時,當塊大小比需要發送的消息的字節數更大時,
一個消息塊就相當于一個消息,否則消息需要分成多個消息塊。
二、握手
<font size=3 color=666666>要建立一個有效的RTMP Connection鏈接,首先要“握手”:客戶端要向服務器發送C0,C1,C2(按序)三個chunk,服務器向客戶端發送S0,S1,S2(按序)三個chunk,然后才能進行有效的信息傳輸。RTMP協議本身并沒有規定這6個Message的具體傳輸順序,但RTMP協議的實現者需要保證這幾點如下:</font>
客戶端要等收到S1之后才能發送C2
客戶端要等收到S2之后才能發送其他信息(控制信息和真實音視頻等數據)
服務端要等到收到C0之后發送S1
服務端必須等到收到C1之后才能發送S2
服務端必須等到收到C2之后才能發送其他信息(控制信息和真實音視頻等數據)
握手流程
握手是一切的開始,Client和Server兩個站點之前要運輸貨物,首先得先互相通知對方,確認鐵軌是否符合火車運行,是否暢通無阻,是否能準確的運輸貨物到對端。
實際的流程大概是這樣的:
- Client發送帶有1byte的C0和固定長度為1536byte的C1。
- Server發送S0S1S2給Client。
- Client發送C2。
+-+-+-+-+-+-+ +-+-+-+-+-+-+
| Client | | Server |
+-+-+-+-+-+-+ +-+-+-+-+-+-+
|--------- C0C1 -------->|
|<------- S0S1S2 -------|
|---------- C2 --------->|
- C0和S0的格式(1 byte)
+-+-+-+-+-+-+-+-+
| version |
+-+-+-+-+-+-+-+-+
- C1和S1的格式
+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+
| zero (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+
| random bytes |
+-+-+-+-+-+-+-+-+-+-+
|random bytes(cont) |
| .... |
+-+-+-+-+-+-+-+-+-+-+
time: 4 字節
本字段包含時間戳。該時間戳應該是發送這個數據塊的端點的后續塊的時間起始點。
可以是 0 ,或其他的任何值。為了同步多個流,端點可能發送其塊流的當前值。
zero: 4 字節
本字段必須是全零。
random bytes: 1528 字節。
本字段可以包含任何值。因為每個端點必須用自己初始化的握手和對端初始化的
- C2和S2的格式
+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+
| time2(4 bytes) |
+-+-+-+-+-+-+-+-+-+-+
| random bytes |
+-+-+-+-+-+-+-+-+-+-+
|random bytes(cont) |
| .... |
+-+-+-+-+-+-+-+-+-+-+
time: 4 字節
本字段必須包含對等段發送的時間(對C2來說是S1 ,對S2來說是C1)。
time2: 4 字節
本字段必須包含先前發送的并被對端讀取的包的時間戳。
random bytes: 1528 字節
本字段必須包含對端發送的隨機數據字段(對C2來說是S1 ,對S2來說是C1)。
三、消息塊 Chunck Block
<font size=3 color=666666>RTMP在收發數據的時候并不是以Message為單位的,而是把Message拆分成Chunk發送,而且必須在一個Chunk發送完成之后才能開始發送下一個Chunk。每個Chunk中帶有MessageID代表屬于哪個Message,接受端也會按照這個id來將chunk組裝成Message。</font>
為什么RTMP要將Message拆分成不同的Chunk呢?通過拆分,數據量較大的Message可以被拆分成較小的“Message”,這樣就可以避免優先級低的消息持續發送阻塞優先級高的數據,比如在視頻的傳輸過程中,會包括視頻幀,音頻幀和RTMP控制信息,如果持續發送音頻數據或者控制數據的話可能就會造成視頻幀的阻塞,然后就會造成看視頻時最煩人的卡頓現象。同時對于數據量較小的Message,可以通過對Chunk Header的字段來壓縮信息,從而減少信息的傳輸量。
每個Chunk都由 <font size=3>Chunk Header + Chunk Data</font> 組成
+-------+ +--------------+----------------+
| Chunk | = | Chunk Header | Chunk Data |
+-------+ +--------------+----------------+
3.1. Chunk Header 由 Basic Header + Message Header + ExtendedTimestamp(不一定存在)組成
+--------------+ +-------------+----------------+-------------------+
| Chunk Header | = | Basic header| Message Header |Extended Timestamp |
+--------------+ +-------------+----------------+-------------------+
3.1.1. Basic Header(基本的頭信息)
(1~3 byte)
+-+-+-+-+-+-+-+-+
|fmt| cs id |
+-+-+-+-+-+-+-+-+
chuck stream = cs
fmt: 表示塊類型,決定了Chunk Msg Header的格式,它占第一個字節的0~1bit,
csid:表示塊流id 占2~7bit屬于csid。
①csid在64~319的范圍內時,
csidTS=0,csid =(第二個字節的值)+64
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt|0 0 0 0 0 0|the second byte|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
②csid在64~65599的范圍內時(①與②存在交集,原則上交集部分選擇①),
csidTS=0x3f,bit位全為1時,csid=(第二個字節的值×256)+(第三個字節的值)+64
0 1 2
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt|1 1 1 1 1 1|the second byte| the third byte|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
③csid在3~63的范圍內時,
csidTS=1~0x3e,即6位bit非全0也非全1時,csid=csidTS
0
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt|0<csid<0x3f|
+-+-+-+-+-+-+-+-+
以下是上述 chuck stream id 類型非全部:
typedef NS_ENUM(NSUInteger, RTMPChunckStreamID)
{
RTMPChunckStreamID_PRO_CONTROL = 0x2, // 協議控制塊流ID
RTMPChunckStreamID_COMMAND = 0x3, // 控制塊流ID
RTMPChunckStreamID_MEDIA = 0x4, // 音視頻塊流ID
};
注意:這里第一個字節的2~7bit的值暫時稱之為csidTS
由此可見:Basic Header的長度范圍為1~3 byte,具體多少byte是csidTS決定的,csid的值范圍3~65599,0~2作為保留。
3.1.2. Chuck Message Header(塊消息的消息頭信息)
(0、3、7、11 byte)
包含了要發送的實際信息(可能是完整的,也可能是一部分)的描述信息。Message Header的格式和長度取決于Basic Header的chunk type,共有4種不同的格式,由上面所提到的Basic Header中的fmt字段控制。
其中第一種格式可以表示其他三種表示的所有數據,但由于其他三種格式是基于對之前chunk的差量化的表示,因此可以更簡潔地表示相同的數據,實際使用的時候還是應該采用盡量少的字節表示相同意義的數據。以下按照字節數從多到少的順序分別介紹這4種格式的Chuck Message Header
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp | message length:
+-------------------------------+---------------+---------------+
: |message type id| :
+-------------------------------+---------------+---------------+
: message stream id |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
timestamp(時間戳): 占 3 byte 最大表示16777215=0xFFFFFF=2^24-1,超出這個值,這3個字節置為1,
將實際數據轉存到Extended Timestamp字段中。
message length(時間戳): 占 3 byte 表示實際發送的消息的數據如音頻幀、視頻幀等數據的長度,單位是字節。
注意這里是Message的長度,也就是chunk屬于的Message的總數據長度,而不是chunk本身Data的數據的長度。
message type id(消息的類型id): 占 1 byte 表示實際發送的數據的類型,如8代表音頻數據、9代表視頻數據。
message stream id(消息的流id): 占 4byte 表示該chunk所在的流的ID,和Basic Header的CSID一樣,它采用小端存儲的方式。
①fmt=0:長度11 byte,其他三種能表示的數據它都能表示
在一個塊流的開始和時間戳返回的時候必須有這種塊
Chuck Message Header = timestamp + message length + message type id + message stream id
②fmt=1:長度為7 byte,與fmt=0時,相比,該類型少了Message Stream Id,
具有可變大小消息的流,在第一個消息之后的每個消息的第一個塊應該使用這個格式
Chuck Message Header = timestamp + message length + message type id
③fmt=2:長度3 byte,不包含Message Stream Id和Message Length 、Message type Id
具有固定大小消息的流,在第一個消息之后的每個消息的第一個塊應該使用這個格式
Chuck Message Header = timestamp
④fmt=3:長度為0 byte,
當一個消息被分成多個塊,除了第一塊以外,所有的塊都應使用這種類型
Chuck Message Header = 0 byte
以下是上述Basic Header中fmt值枚舉:
typedef NS_ENUM(NSUInteger, RTMPBHFmt)
{
RTMPBHFmt_FULL = 0x0,
RTMPBHFmt_NO_MSG_STREAM_ID = 0x1,
RTMPBHFmt_TIMESTAMP = 0x2, // 'Chuck Message Header' only timestamp
RTMPBHFmt_ONLY = 0x3, // 'Chunk Message Header' all no
};
以下是上述 message type id 類型非全部:
typedef NS_ENUM(NSUInteger, RTMPMessageTypeID)
{
RTMPMessageTypeID_CHUNK_SIZE = 0x1, //協議控制消息 ChunkData承載大小,進行分塊
RTMPMessageTypeID_ABORT = 0x2, //協議控制消息 消息分塊只收到部分時,發送此控制消息,發端不在
RTMPMessageTypeID_BYTES_READ = 0x3, //協議控制消息
RTMPMessageTypeID_PING = 0x4, //用戶控制消息 該消息在Chunk流中發送時,msg stream id = 0, chunck stream id = 2, message type id = 4
RTMPMessageTypeID_SERVER_WINDOW = 0x5, //協議控制消息
RTMPMessageTypeID_PEER_BW = 0x6, //協議控制消息
RTMPMessageTypeID_AUDIO = 0x8, //音頻消息
RTMPMessageTypeID_VIDEO = 0x9, //視頻消息
RTMPMessageTypeID_FLEX_STREAM = 0xF,
RTMPMessageTypeID_FLEX_OBJECT = 0x10,
RTMPMessageTypeID_FLEX_MESSAGE = 0x11,
RTMPMessageTypeID_NOTIFY = 0x12, //數據消息,傳遞一些元數據
RTMPMessageTypeID_SHARED_OBJ = 0x13, //
RTMPMessageTypeID_INVOKE = 0x14, //命令消息,客戶端與服務器之間執行命令如:connect、publish
RTMPMessageTypeID_METADATA = 0x16, //
};
<font size=4 color=ff0000>注意:</font>
message type id 發送音視頻數據的時候
如果包頭MessageTypeID為0x8或0x9,數據(chunk data)是flv的tag data(沒有tag header),flv格式封裝請見官網
也可以用新類型MessageTypeID為0x16,數據(chunk data)是一個完整flv的tag(tag header + tag data)
message stream id 采用<font size=4 color=000000>小端</font>存儲
RTMP都是<font size=4 color=000000>大端</font>模式,所以發送數據,包頭,交互消息都要填寫<font size=4 color=000000>大端</font>模式的,但是只有streamID是<font size=4 color=000000>小端</font>模式
<font size=4 color=000000>附:大小端的理解</font>
例子:
假設某段內存中存放以下這樣的數據
低地址 高地址
------------------------------------>
+--------+--------+--------+--------+
| 11 | 66 | 85 | 27 |
+--------+--------+--------+--------+
大端模式下值為 0x11668527,數據高位存放在低地址中,數據低位存放高地址中
小端模式下值為 0x27856611,數據高位存放在高地址中,數據低位存放低地址中
所以在對數據處理的時候需要注意當前是在什么模式下,一般32位環境是小端模式,64位環境是大端模式
3.1.3. ExtendedTimestamp(擴展時間)
(0、4 byte)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
只有當塊消息頭中的普通時間戳設置為 0xffffff 時,本字段才被傳送。
如果普通時間戳的值小于 0x00ffffff ,那么本字段一定不能出現。
如果塊消息頭中時間戳字段不出現本字段也一定不能出現。
類型 3 的塊一定不能含有本字段。
本字段在塊消息頭之后,塊數據之前。
3.2.Chunk Data
+-----------+
|Chunk Data |
+-----------+
Chunk Data的實例就是Message
3.2.1.消息-Message
Message = Message Header + Message Payload
Message Header = Message Type + Payload Length + Timestamp + Stream ID
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Message Type | Payload Length |
+---------------------------------------------------------------+
| Timestamp |
+-----------------------------------------------+---------------+
| Stream ID | :
+-----------------------------------------------+ - - - - - - - +
: Message Payload :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
: ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(1)Message Type(1 byte)以下簡寫為MT
消息類型很重要,它代表了這個消息是什么類型,當寫程序的時候需要根據不同的消息,做不同的處理
(2)Payload length(3 bytes)
表示負載的長度(big-endian 格式)
(3)Timestamp (4 bytes)
時間戳(big-endian 格式)
(4)Stream ID (3 bytes)
消息流ID(big-endian 格式)
(5)Message Payload
真實的數據
3.2.2.消息的分類
3.2.2.1協議控制消息
協議控制消息是用來與對端協調控制的
MT的范圍1~7
1~2 用于chunk協議
3~6 用于rtmp協議本身,協議控制消息必須要求
Message Stream ID=0 和 Chunk Stream ID=2
- <font size=3 color=#666666 >MT=1, Set Chunk Size 設置塊的大小,通知對端用使用新的塊大小,共4 bytes。默認大小是128字節</font>
+-------------+----------------+-------------------+----------------+
| Basic header|Chunk Msg Header|Extended Timestamp | Set chunk size |
+-------------+----------------+-------------------+----------------+
- <font size=3 color=#666666 >MT=2, Abort Message 取消消息,用于通知正在等待接收塊以完成消息的對等端,丟棄一個塊流中已經接收的部分并且取消對該消息的處理,共4 bytes。</font>
+-------------+----------------+-------------------+----------------+
| Basic header|Chunk Msg Header|Extended Timestamp | Chunk Stream ID|
+-------------+----------------+-------------------+----------------+
- <font size=3 color=#666666 >MT=3, Acknowledgement 確認消息,客戶端或服務端在接收到數量與窗口大小相等的字節后發送確認消息到對方。窗口大小是在沒有接收到接收者發送的確認消息之前發送的字節數的最大值。服務端在建立連接之后發送窗口大小。本消息指定序列號。序列號,是到當前時間為止已經接收到的字節數。
共4 bytes。</font>
+-------------+----------------+-------------------+----------------+
| Basic header|Chunk Msg Header|Extended Timestamp | Sequence Number|
+-------------+----------------+-------------------+----------------+
- <font size=3 color=#666666 >MT=4, User Control Message 用戶控制消息,客戶端或服務端發送本消息通知對方用戶的控制事件。本消息承載事件類型和事件數據。消息數據的頭兩個字節用于標識事件類型。事件類型之后是事件數據。事件數據字段是可變長的。</font>
+-------------+----------------+-------------------+-----------+-----------+
| Basic header|Chunk Msg Header|Extended Timestamp | Event Type|Event Datar|
+-------------+----------------+-------------------+-----------+-----------+
- <font size=3 color=#666666 >MT=5, Window Acknowledgement Size 確認窗口大小,客戶端或服務端發送本消息來通知對方發送確認消息的窗口大小,共4 bytes.</font>
+-------------+----------------+-------------------+----------------------------+
| Basic header|Chunk Msg Header|Extended Timestamp | Window Acknowledgement Size|
+-------------+----------------+-------------------+----------------------------+
- <font size=3 color=#666666 >MT=6, Set Peer Bandwidth 設置對等端帶寬,客戶端或服務端發送本消息更新對等端的輸出帶寬。發送者可以在限制類型字段(1 bytes)把消息標記為硬(0),軟(1),或者動態(2)。如果是硬限制對等端必須按提供的帶寬發送數據。如果是軟限制,對等端可以靈活決定帶寬,發送端可以限制帶寬?。如果是動態限制,帶寬既可以是硬限制也可以是軟限制。</font>
+-------------+----------------+-------------------+----------------------------+------------+
| Basic header|Chunk Msg Header|Extended Timestamp | Window Acknowledgement Size| Limit type |
+-------------+----------------+-------------------+----------------------------+------------+
Hard(Limit Type=0):接受端應該將Window Ack Size設置為消息中的值
Soft(Limit Type=1):接受端可以講Window Ack Size設為消息中的值,也可以保存原來的值(前提是原來的Size小與該控制消息中的Window Ack Size)
Dynamic(Limit Type=2):如果上次的Set Peer Bandwidth消息中的Limit Type為0,本次也按Hard處理,否則忽略本消息,不去設置Window Ack Size。
3.2.2.2.音頻數據消息
- <font size=3 color=#666666 >MT=8, Audio message, 客戶端或服務端發送本消息用于發送音頻數據。消息類型 8 ,保留為音頻消息</font>
3.2.2.3.視頻數據消息
- <font size=3 color=#666666 >MT=9, Video message, 客戶端或服務端使用本消息向對方發送視頻數據。消息類型值 9 ,保留為視頻消息。</font>
3.2.2.4.元數據消息
- <font size=3 color=#666666 >MT=15或18, Data message, 客戶端或服務端通過本消息向對方發送元數據和用戶數據。元數據包括數據的創建時間、時長、主題等細節。
消息類型為 18 的用 AMF0 編碼,消息類型為 15 的用AMF3 編碼。</font>
3.2.2.5.共享對象消息
- <font size=3 color=#666666 >MT=16或19, Shared object message, 共享對象是跨多個客戶端,實例同步的 FLASH 對象(名值對的集合)。</font>
3.2.2.6.命令消息
- <font size=3 color=#666666 >MT=17或20, Command message, 命令消息都是用AMF編碼的,AMF有兩種,為AMF0和AMF3。命令消息有命令名,傳輸ID,和命名對象組成。而命名對象是由一系列參數組成的。
</font>
命令消息的類型
3.2.2.6.1.NetConnection Commands(連接層的命令)
代表服務端和客戶端之間連接的更高層的對象。包含4個命令類型。
- connect:該命令是client先發送給server,意思是我要連接,能建立連接嗎?server返回含“_result”或者“_error”命令名, 返回“_result”,表示server能提供服務,client可以進行下一步。“_error”,很明顯server端不能提供服務。
消息結構如下:
字段 | 類型 | 類型 |
---|---|---|
CommandName(命令名字) | String | 命令的名字,如”connect” |
TransactionID(事務ID) | Number | 恒為1 |
CommandObject(命令包含的參數對象) | Object | 鍵值對集合表示的命令參數 |
OptionalUserArguments(額外的用戶參數) | Object | 用戶自定義的額外信息 |
- call:NetConnection 對象的調用方法在接收端運行遠程過程調用。遠程方法的名作為調用命令的參數。
消息的結構如下:
字段 | 類型 | 類型 |
---|---|---|
ProcedureName(進程名) | String | 要調用的進程名稱 |
TransactionID | Number | 如果想要對端響應的話置為非0值,否則置為0 |
CommandObject | Object | 命令參數 |
OptionalArguents | Object | 用戶自定義參數 |
如果消息中的TransactionID不為0的話,對端需要對該命令做出響應,響應的消息結構如下:
字段 | 類型 | 類型 |
---|---|---|
CommandName(命令名) | String | 命令的名稱 |
TransactionID | Number | 上面接收到的命令消息中的TransactionID |
CommandObject | Object | 命令參數 |
OptionalArguents | Object | 用戶自定義參數 |
- close:不知道為何協議里沒寫這個命令的內容,我猜應該是close connect。
- createStream:客戶端發送本命令到服務端創建一個消息通訊的邏輯通道。音頻,視頻和元數據的發布是由創建流命令建立的流通道承載的。
字段 | 類型 | 類型 |
---|---|---|
CommandName(命令名) | String | “createStream” |
TransactionID | Number | 上面接收到的命令消息中的TransactionID |
CommandObject | Object | 命令參數 |
OptionalArguents | Object | 用戶自定義參數 |
NetConnection 本身是默認的流通道,具有流ID 0。協議和一少部分命令消息,包括創建流,就使用默認的通訊通道。
3.2.2.6.2.NetStream Commands(流連接上的命令)
Netstream建立在NetConnection之上,通過NetConnection的createStream命令創建,用于傳輸具體的音頻、視頻等信息。
在傳輸層協議之上只能連接一個NetConnection,但一個NetConnection可以建立多個NetStream來建立不同的流通道傳輸數據。
以下會列出一些常用的NetStream Commands,服務端收到命令后會通過onStatus的命令來響應客戶端,表示當前NetStream的狀態。
onStatus命令的消息結構如下:
字段 | 類型 | 類型 |
---|---|---|
CommandName(命令名) | String | “onStatus” |
TransactionID | Number | 恒為0 |
CommandObject | NULL | 對onSatus命令來說不需要這個字段 |
InfoObject | Object | AMF類型的Object,至少包含以下三個屬性:1、“level”,String類型,可以為“warning”、”status”、”error”中的一種;2、”code”,String類型,代表具體狀態的關鍵字,比如”NetStream.Play.Start”表示開始播流;3、”description”,String類型,代表對當前狀態的描述,提供對當前狀態可讀性更好的解釋,除了這三種必要信息,用戶還可以自己增加自定義的鍵值對 |
-
play(播放)
+-------------+ +----------+ | Play Client | | | Server | +-------------+ | +----------+ | |Handshaking and Application| | | | connect done | | | | | ---+---- |---------Command Message(createStream) --------->| Create | | Stream | | ---+---- |<-------------- Command Message -----------------| | (_result- createStream response) | | | ---+---- |------------ Command Message (play) ------------>| play | | | |<---------------- SetChunkSize ------------------| | | | | |<----- User Control (StreamIsRecorded) ----------| | | | | |<-------- UserControl (StreamBegin) -------------| | | | | |<---- Command Message(onStatus-play reset) ------| | | | | |<---- Command Message(onStatus-play start) ------| | | | | |------------------ Audio Message---------------->| | | | | |------------------ Video Message---------------->| | | | | | Keep receiving audio and video stream till finishes
a. 客戶端從服務端接收到流創建成功消息,發送播放命令到服務端。 b. 接收到播放命令后,服務端發送協議消息設置塊大小。 c. 服務端發送另一個協議消息(用戶控制消息),并且在消息中指定事件” streamisrecorded” 和流 ID 。消息承載的頭 2 個字,為事件類型,后4 個字節為流 ID 。 d. 服務端發送事件” streambegin” 的協議消息(用戶控制),告知客戶端流 ID 。 e. 服務端發送響應狀態命令消息`NetStream.Play.Start`&`NetStream.Play.reset` , 如果客戶端發送的播放命令成功的話。只有當客戶端發送的播放命令設置了 `reset`命令的條件下,服務端才發送`NetStream.Play.reset`消息。如果要發送的流 沒有找的話,服務端發送`NetStream.Play.StreamNotFound`消息。在此之后服務端發送客戶端要播放的音頻和視頻數據。
play命令的結構如下:
字段 | 類型 | 類型 |
---|---|---|
CommandName(命令名) | String | “play” |
TransactionID | Number | 恒為0 |
CommandObject | NULL | 不需要此字段,設為空 |
StreamName | String | 要播放的流的名稱 |
開始位置 | Number | 可選參數,表示從何時開始播流,以秒為單位。默認為-2,代表選取對應該流名稱的直播流,即當前正在推送的流開始播放,如果對應該名稱的直播流不存在,就選取該名稱的流的錄播版本,如果這也沒有,當前播流端要等待直到對端開始該名稱的流的直播。如果傳值-1,那么只會選取直播流進行播放,即使有錄播流也不會播放;如果傳值或者正數,就代表從該流的該時間點開始播放,如果流不存在的話就會自動播放播放列表中的下一個流 |
周期 | Number | 可選參數,表示回退的最小間隔單位,以秒為單位計數。默認值為-1,代表直到直播流不再可用或者錄播流停止后才能回退播放;如果傳值為0,代表從當前幀開始播放 |
重置 | Boolean | 可選參數,true代表清除之前的流,重新開始一路播放,false代表保留原來的流,向本地的播放列表中再添加一條播放流 |
-
play2(播放)
和播放命令不同,play2命令可以切換到不同的碼率,而不用改變已經播放的內容的時間線。 服務端對播放 2 命令可以請求的多個碼率維護多個文件。
字段 | 類型 | 類型 |
---|---|---|
CommandName(命令名) | String | “play2” |
TransactionID | Number | 恒為0 |
CommandObject | NULL | 對onSatus命令來說不需要這個字段 |
parameters | Object | AMF編碼的Flash對象,包括了一些用于描述flash.net.NetstreamPlayOptions ActionScript obejct的參數 |
-
deleteStream(刪除流)
當 NetStream 對象銷毀的時候發送刪除流命令。
字段 | 類型 | 類型 |
---|---|---|
CommandName(命令名) | String | “deleteStream” |
TransactionID | Number | 恒為0 |
CommandObject | NULL | 對onSatus命令來說不需要這個字段 |
StreamID(流ID) | Number | 本地已刪除,不再需要服務器傳輸的流的ID |
closeStream
-
receiveAudio(接收音頻)
NetStream 對象發送接收音頻消息通知服務端發送還是不發送音頻到客戶端。
字段 | 類型 | 類型 |
---|---|---|
CommandName(命令名) | String | “receiveAudio” |
TransactionID | Number | 恒為0 |
CommandObject | NULL | 對onSatus命令來說不需要這個字段 |
BoolFlag | Boolean | true表示發送音頻,如果該值為false,服務器端不做響應,如果為true的話,服務器端就會準備接受音頻數據,會向客戶端回復NetStream.Seek.Notify和NetStream.Play.Start的Onstatus命令告知客戶端當前流的狀態 |
-
receiveVideo(接收視頻)
NetStream 對象發送 receiveVideo 消息通知服務端是否發送視頻到客戶端。
字段 | 類型 | 類型 |
---|---|---|
CommandName(命令名) | String | “receiveVideo” |
TransactionID | Number | 恒為0 |
CommandObject | NULL | 對onSatus命令來說不需要這個字段 |
BoolFlag | Boolean | true表示發送視頻,如果該值為false,服務器端不做響應,如果為true的話,服務器端就會準備接受視頻數據,會向客戶端回復NetStream.Seek.Notify和NetStream.Play.Start的Onstatus命令告知客戶端當前流的狀態 |
-
publish(推送數據)
由客戶端向服務器發起請求推流到服務器。+-------------+ +----------+ | Client | | | Server | +-------------+ | +----------+ | | Handshaking Done | | | | | | | | ---+---- |--------- Command Message(connect) --------->| | | | Connect |<---------- Window Acknowledge Size -------------| | | | | |<------------- Set Peer BandWidth ---------------| | | | | |----------- Window Acknowledge Size ------------>| | | | | |<--------- User Control(StreamBegin) ------------| | | | ---+---- |--------------- Command Message ---------------->| | (_result- connect response) | | | ---+---- |---------Command Message(createStream) --------->| Create | | Stream | | ---+---- |<-------------- Command Message -----------------| | (_result- createStream response) | | | ---+---- |--------- Command Message (publish) ------------>| | | | publish |<-------- UserControl (StreamBegin) -------------| | | | | |---------- Data Message (Metadata) ------------->| | | | | |------------------ Audio Message---------------->| | | | | |----------------- SetChunkSize ----------------->| | | | | |<--------------- Command Message ----------------| | | (_result- publish result) | | |------------------ Video Message---------------->| | | Until the stream is complete
客戶端發送一個發布命令,發布一個命名流到服務端。使用這個名字,任何客戶端可以播放該流并且接收音頻,視頻,和數據消息。
publish命令結構如下:
字段 | 類型 | 類型 |
---|---|---|
CommandName(命令名) | String | “publish” |
TransactionID | Number | 恒為0 |
CommandObject | NULL | 對onSatus命令來說不需要這個字段 |
PublishingName(推流的名稱) | String | 流名稱 |
PublishingType(推流類型) | String | “live”、”record”、”append”中的一種。live表示該推流文件不會在服務器端存儲;record表示該推流的文件會在服務器應用程序下的子目錄下保存以便后續播放,如果文件已經存在的話刪除原來所有的內容重新寫入;append也會將推流數據保存在服務器端,如果文件不存在的話就會建立一個新文件寫入,如果對應該流的文件已經存在的話保存原來的數據,在文件末尾接著寫入 |
-
seek(定位流的位置)
定位到視頻或音頻的某個位置,以毫秒為單位。客戶端發送搜尋命令在一個媒體文件中或播放列表中搜尋偏移。
seek命令的結構如下:
字段 | 類型 | 類型 |
---|---|---|
CommandName(命令名) | String | “seek” |
TransactionID | Number | 恒為0 |
CommandObject | NULL | 對onSatus命令來說不需要這個字段 |
milliSeconds | Number | 定位到該文件的xx毫秒處 |
-
pause(暫停)
客戶端告知服務端停止或恢復播放客戶端發送暫停命令告訴服務端暫停或開始一個命令。
pause命令的結構如下:
字段 | 類型 | 類型 |
---|---|---|
CommandName(命令名) | String | “pause” |
TransactionID | Number | 恒為0 |
CommandObject | NULL | 對onSatus命令來說不需要這個字段 |
Pause/Unpause Flag | Boolean | true表示暫停,false表示恢復 |
milliSeconds | Number | 暫停或者恢復的時間,以毫秒為單位 |
如果Pause為true即表示客戶端請求暫停的話,服務端暫停對應的流會返回NetStream.Pause.Notify的onStatus命令來告知客戶端當前流處于暫停的狀態,當Pause為false時,服務端會返回NetStream.Unpause.Notify的命令來告知客戶端當前流恢復。如果服務端對該命令響應失敗,返回_error信息。
3.2.2.7.聚合消息
- <font size=3 color=#666666 >MT=22, Aggregate message, 聚合消息是含有一個消息列表的一種消息。消息類型值 22 ,保留用于聚合消息。
</font>
+---------+-------------------------+
| Header | Aggregate Message body |
+---------+-------------------------+
聚合消息的格式
+--------+--------------+--------------+--------+-------------+---------------+ - - - -
|Header 0|Message Data 0|Back Pointer 0|Header 1|Message Data 1|Back Pointer 1|
+--------+--------------+--------------+--------+--------------+--------------+ - - - -
聚合消息的body
Back Pointer包含了前面消息的大小(包括Header的大小)。這個設置匹配了 flv 文件格式,可用于后向搜索。
3.2.3.接收命令消息反饋結果 ResponseCommand
通過塊消息攜帶的數據,拼接成消息內容,通過AMF解讀消息內容,略過不細講
#define RTMPConnectSuccess @"NetConnection.Connect.Success"
#define RTMPPublishStart @"NetStream.Publish.Start"
#define RTMPPublishBadName @"NetStream.Publish.BadName"
#define RTMPPlayStart @"NetStream.Play.Start"
#define RTMPPlayReset @"NetStream.Play.Reset"
#define RTMPPlayStreamNotFound @"NetStream.Play.StreamNotFound"
typedef enum : char {
RTMPResponseCommand_Result = 0x1, //_Result命令
RTMPResponseCommandOnBWDone = 0x2, //OnBWDone命令
RTMPResponseCommandOnFCPublish = 0x3, //OnFCPublish命令
RTMPResponseCommandOnStatus = 0x4, //OnStatus命令
RTMPResponseCommandOnFCUnpublish = 0x5, //OnFCUnpublish命令
RTMPResponseCommandOnMetaData = 0x6, //OnMetaData命令
RTMPResponseCommandUnkonwn = 0x7f,//未知類型
} RTMPResponseCommandType;
<font size=4 color=000000>附:FLV數據封裝簡單描述</font>
FLV 由 FLV Header + FLV Body 組成
-- FLV Body ------------------------------ ... ----------------------------
/ \
+---------+-------------+-------------+-------------+-- ... --+-------------+-------------+
| FLV | PreTag | Tag | PreTag | | Tag | PreTag |
| Hearder | Size | 1 | Size | | N | Size |
+---------+-------------+-------------+-------------+-- ... --+-------------+-------------+jiantou
|
┏—————————————————————————————┛
|
| FLV Header占9bytes,flv的類型、版本的信息,
| 組成:Signature(3bytes)+Version(1bytes)+Flags(1bytes)+DataOffset(4bytes)
| ①Signature:固定FLV三個字符作為標示,一般前3個字符為FLV是就認為是flv文件
| ②Version:表示FLV的版本號
| ③Flags:內容標示,第0位和第2位,分別表示video與audio存在的情況(1表示存在,0表示不存在)
| 如0x05(00000101)同時存在視頻、音頻
| ④DataOffset:表示FLV的Header長度。這里固定是9
|
| FLV Body: TagSize0 + Tag1 + Tag1 Size + Tag2 + Tag2 Size + ... + TagN + TagN Size
|
| PreTagSize占4bytes,值表示前一個Tag的長度
| Tag 由 Tag Header + Tag Data 組成,Tag分三種類型,video、audio、scripts(元數據)
|
| (某一個Tag)
┗——→ +------------+
| Tag Header |
+------------+
| Tag Data |
+------------+
+------------+------------+------------+------------+------------+
TagHeader | Tag type | Data Size | Time Stamp |ExtTimeStamp| Stream ID |
+------------+------------+------------+------------+------------+
+------------+------------+------------+-----------------------------------+
TagData_Video | Frame | AvcPk |Composition | AVC Pk Type == 0: SPS_PPS |
| Codec | Type | Time | AVC Pk Type == 1: nalu len + nalu |
+------------+------------+------------+-----------------------------------+
+------------+------------+-----------------------------------+
TagData_Audio | Format | AACPk | AAC Pk Type == 0: AAC Spec |
| SR,SZ,ST | Type | AAC Pk Type == 1: AAC RAW |
+------------+------------+-----------------------------------+
//AVC Pk Type
typedef NS_ENUM(NSUInteger, flv_video_h264_packettype)
{
flv_video_h264_packettype_seqHeader = 0,
flv_video_h264_packettype_nalu = 1,
flv_video_h264_packettype_endOfSeq = 2,
};
//AAC Pk Type
typedef NS_ENUM(NSUInteger, flv_audio_aac_packettype)
{
msp_flv_audio_aac_packettype_seqHeader = 0,
msp_flv_audio_aac_packettype_raw = 1,
};
flv要求發送音視頻之前先發送一個tag_scripts類型元數據,描述視頻或音頻的信息的數據,如寬度、高度、采樣、聲道、頻率、編碼等等
flv要求第一個tag_video必須是sps pps數據包 之后才能發送視頻編碼數據
flv要求第一個tag_audio必須是音頻配置數據包 之后才能發送音頻編碼數據
Tag Header占11bytes,存放當前Tag類型、TagData(數據區)的長度等信息
+------------+
| Tag Header |
+------------+
①Tag類型:1byte,8 = 音頻,9 = 視頻,18(0x12) = 腳本, 其他 = 保留
②數據區長度(tag data size):3bytes
③時間戳:3bytes,整數單位毫秒,腳本類型Tag為0
④時間戳擴展:1bytes
⑤StreamsID:3bytes 總是0
Tag Data 數據區,音頻數據(TagData_Audio)、視頻數據(TagData_Video)、腳本數據(TagData_Scripts)
+------------+
| Tag Data |
+------------+
TagData_Video
第一個byte視頻信息 = 幀類型(4bit) + 編碼ID(4bit)
后面數據,視頻格式為 AVC(H.264)的話,后面為4個字節信息,AVCPacketType(1byte)和 CompositionTime(3byte)
AVCPacketType == 1 則CompositionTime = Composition time offset
后面三個字節也是0,說明這個tag記錄的是AVCDecoderConfigurationRecord。包含sps和pps數據。
后面數據為:0x01+sps[1]+sps[2]+sps[3]+0xFF+0xE1+sps_size+sps+01+pps_size+pps
AVCPacketType != 1 則CompositionTime = 0
后面三個字節也是0,說明這個tag記錄的是AVCDecoderConfigurationRecord。包含sps和pps數據
后面數據為:0x01+sps[1]+sps[2]+sps[3]+0xFF+0xE1+sps_size+sps+01+pps_size+pps
TagData_Audio
第一個byte音頻信息 = 音頻格式(4bit) + 采樣率(2bit) + 采樣長度(1bit) + 音頻類型(1bit)
后面數據,如果音頻格式為AAC,后面跟1byte 0x00/0x01。
如果0x00 后面跟 audio config data 數據 需要作為第一個 audio tag 發送
如果0x01 后面跟 audio frame data 數據
TagData_Scripts
數據類型 +(數據長度)+ 數據,數據類型占1byte,數據長度根據數據類型
數據類型: 0 = Number type
1 = Boolean type
2 = String type
3 = Object type
4 = MovieClip type
5 = Null type
6 = Undefined type
7 = Reference type
8 = ECMA array type
10 = Strict array type
11 = Date type
12 = Long string type
如果為 String type ,那么數據長度占2bytes(Long string type 占 4bytes),后面就是字符串數據
舉個栗子:0x02(String 類型)+0x000a("onMetaData"長度) + "onMetaData"
如果為 Number type ,沒有數據長度,后面直接為8bytes的 Double 類型數據
如果為 Boolean type,沒有數據長度,后面直接為1byte的 Bool 類型數據
如果為 ECMA array type,數據長度占4bytes 值表示數組長度,后面 鍵是 String 類型的,開頭0x02被省略,
直接跟字符串長度,然后是字符串,在是值類型(根據上面來)
#pragma mark - video Tag Data
// 幀類型 占4bits 這里只用到了 key、inner
typedef NS_ENUM (NSUInteger, flv_video_frametype)
{
flv_video_frametype_key = 1,// keyframe (for AVC, a seekable frame)
flv_video_frametype_inner = 2,// inter frame (for AVC, a non-seekable frame)
//flv_video_frametype_inner_d = 3,// disposable inter frame (H.263 only)
//flv_video_frametype_key_g = 4,// generated keyframe (reserved for server use only)
//flv_video_frametype_vi_cf = 5,// video info/command frame
};
// 編碼(格式)ID 占4bits 只用到了H264
typedef NS_ENUM (NSUInteger, flv_video_codecid)
{
flv_video_codecid_JPEG = 1,// JPEG (currently unused)
flv_video_codecid_H263 = 2,// Sorenson H.263
//flv_video_codecid_ScreenVideo = 3,// Screen video
//flv_video_codecid_On2VP6 = 4,// On2 VP6
//flv_video_codecid_On2VP6_AC = 5,// On2 VP6 with alpha channel
//flv_video_codecid_ScreenVideo2 = 6,// Screen video version 2
flv_video_codecid_H264 = 7,// AVC
//flv_video_codecid_RealH263 = 8,
//flv_video_codecid_MPEG4 = 9,
};
#pragma mark - audio Tag Data
// 音頻編碼(音頻格式)ID 占4bits 只用到了AAC
typedef NS_ENUM (NSUInteger, flv_audio_codecid)
{
//flv_audio_codecid_PCM = 0,// Linear PCM, platform endian
//flv_audio_codecid_ADPCM = 1,// ADPCM
flv_audio_codecid_MP3 = 2,// MP3
//flv_audio_codecid_PCM_LE = 3,// Linear PCM, little endian
//flv_audio_codecid_N16 = 4,// Nellymoser 16-kHz mono
//flv_audio_codecid_N8 = 5,// Nellymoser 8-kHz mono
//flv_audio_codecid_N = 6,// Nellymoser
//flv_audio_codecid_PCM_ALAW = 7,// G.711 A-law logarithmic PCM
//flv_audio_codecid_PCM_MULAW = 8,// G.711 mu-law logarithmic PCM
//flv_audio_codecid_RESERVED = 9,// reserved
flv_audio_codecid_AAC = 10,// AAC
//flv_audio_codecid_SPEEX = 11,// Speex
//flv_audio_codecid_MP3_8 = 14,// MP3 8-Khz
//flv_audio_codecid_DSS = 15,// Device-specific sound
};
// soundSize 8bit/16bit 采樣長度 壓縮過的音頻都是16bit 占1bit
typedef NS_ENUM (NSUInteger, flv_audio_soundsize)
{
flv_audio_soundsize_8bit = 0,// snd8Bit
flv_audio_soundsize_16bit = 1,// snd16Bit
};
// sound rate 5.5 11 22 44 kHz 采樣率 對于AAC總是3
typedef NS_ENUM (NSUInteger, flv_audio_soundrate)
{
flv_audio_soundrate_5_5kHZ = 0,// 5.5-kHz
flv_audio_soundrate_11kHZ = 1,// 11-kHz
flv_audio_soundrate_22kHZ = 2,// 22-kHz
flv_audio_soundrate_44kHZ = 3,// 44-kHz
};
// sound type mono/stereo 對于AAC總是1 立體音
typedef NS_ENUM (NSUInteger, flv_audio_soundtype)
{
flv_audio_soundtype_mono = 0,// sndMono
flv_audio_soundtype_stereo = 1,// sndStereo
};