(轉)rtmp協議簡單解析以及用其發送h264的flv文件


Adobe公司太坑人了,官方文檔公布的信息根本就不全,如果只按照他上面的寫的話,是沒法用的。按照文檔上面的流程,serverclient連接之后首先要進行握手,握手成功之后進行一些交互,其實就是交互一些信息以確認大家都是用的同一個協議,交互成功之后就開始傳數據了。

      

首先說下rtmp協議包的格式。握手之后rtmp傳輸一個數據默認的長度是128bytes,這128bytes不包括包頭的長度,只是數據的長度,文檔上面沒有說明,很憋了我一段時間,數據超過這個長度之后就要分塊,超過128bytes的數據放到下一個塊中,以此類推。塊大小是可配置的,最大塊是65535字節,最小塊是128字節。塊越大CPU使用率越低,但是也導致大的寫入,在低帶寬下產生其他內容的延遲。

       Rtmp協議是包頭加包體(數據)組成,包頭可以是4種長度的任意一種:12, 8, 4,  1 byte(s)。包頭包含了head type、時間戳、amf size、amf type、streamID。

完整的RTMP包頭有12字節,由下面5個部分組成:

用途

大小(Byte)

含義

Head_Type

1

包頭

TIMER

3

時間戳

AMFSize

3

數據大小

AMFType

1

數據類型

StreamID

4

流ID

Head_Type占用RTMP包的第一個字節,這個字節里面記錄了包的類型和包的ChannelID。Head_Type字節的前兩個Bits決定了包頭的長度。

Head_Type的前兩個Bit和長度對應關系,header length是包頭的長度:

Bits

Header Length

00

12 bytes

01

8 bytes

10

4 bytes

11

1 byte

一個視頻流的第一個包必須是12bytes,也就是00,要告訴對方這個流的信息。

Head_Type的后面6個Bits記錄着ChannelID,其為一下內容:

02

Ping 和ByteRead通道

03

Invoke通道 我們的connect() publish()和自字寫的NetConnection.Call() 數據都是在這個通道的

04

Audio和Vidio通道

05 06 07

服務器保留,經觀察FMS2用這些Channel也用來發送音頻或視頻數據

比如傳一個視頻流,第一個塊的head type就是00 00 00 04.(0x04)。實測fms用4或者5在傳影視頻。官方文檔上面說head_type可能會不止一個byte,但實際情況用一個byte也夠了。

TiMMER - 時間戳

音視頻的播放同步是由時間戳來控制的,單位是毫秒。如果時間戳大於或等於1677721516進制0x00ffffff),該值必須為16777215,並且擴展時間戳必須出現,4bytes的擴展時間戳出現在包頭之后包體之前。這個時間戳一般去flv文件里面每個tag里面的時間戳。

AMFSize - 數據大小

AMFSize占三個字節,這個長度是AMF長度,其實就是本次數據的長度。

AMFType - 數據類型

AMFType是RTMP包里面的數據的類型,占用1個字節。例如音頻包的類型為8,視頻包的類型為9。下面列出的是常用的數據類型:

0×01

Chunk Size

changes the chunk size for packets

0×02

Unknown

 

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

 

0×08

Audio Data

packet containing audio

0×09

Video Data

packet containing video data

0x0A-0x0E

Unknown

 

0x0F

FLEX_STREAM_SEND

TYPE_FLEX_STREAM_SEND

0x10

FLEX_SHARED_OBJECT

TYPE_FLEX_SHARED_OBJECT

0x11

FLEX_MESSAGE 

TYPE_FLEX_MESSAGE 

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.

0×16

StreamData

這是FMS3出來后新增的數據類型,這種類型數據中包含AudioData和VideoData

StreamID - 流ID

占用RTMP包頭的最后4個字節,是一個big-endian的int型數據,每個消息所關聯的ID,用於區分其所在的消息流。

 

前面說包頭有幾種長度,第一個長度是12bytes,包含了全部的頭信息,第一個數據流也就是流的開始必須是這個長度。

第二種8bytes的包,沒有了streamID,發這種 包,對方就默認此streamID和上次相同,一個視頻數據在第一個流之后都可以使這種格式,比如一個1M的視頻,第一次發128bytes用 12bytes的包,之后每次發的數據都應該用8bytes的包。當然如果每次都用12bytes也沒有問題。

第三種為4bytes,只有head_type和時間戳,缺少的對方認為與之前的一樣,實際應用中很難出現。

第四中就只有head_type一個byte。如果一次數據超過了長度(默認是128bytes),就要分塊,第一個塊是12bytes或者8bytes的,之后的塊就是1byte。因為分的N塊,每一塊的信息都是一樣的,所以只需要告訴對方此次包的長度就行了。

例子:

例如有一個RTMP封包的數據0300 00 0000 01 021400 00 00 000200 0763 6F 6E 6E 65 63 74003F F0 00 00 00 00 00 0008 ,,,
數據依次解析的含義 
03表示12字節頭,
channelid=3
000000表示時間戳
 Timer=0
000102表示
AMFSize=18
14表示AMFType=Invoke 方法調用

 00 00 00 00 表示StreamID = 0
//到此,12字節RTMP頭結束下面的是AMF數據分析
 
02表示
String
0007表示String長度
7
63 6F 6E 6E 65 63 74 是String的Ascall值
"connect"
00表示
Double
3F F0 00 00 00 00 00 00 表示double的
0.0
08表示Map數據開始

官方文檔上面的例子:

1展示一個簡單的音頻消息流。這個例子顯示了信息的冗余。

         

 

Message Stream ID

Message Type ID

Time

Length

Msg # 1

  12345

  8 

1000

32

Msg # 2

  12345

  8 

1020

32

Msg # 3

  12345

  8 

1040

32

Msg # 4

  12345

  8 

1060

32

            

下表顯示了這個流產生的塊。從消息3開始,數據傳輸開始優化。在消息3之后,每個消息只有一個字節的開銷。

     

 

Chunk Stream ID

Chunk Type

Header Data

No.of Bytes After Header

Total No.of

Bytes in the Chunk

Chunk#1

3

0

delta: 1000

length: 32

type: 8

stream ID:1234

(11bytes)

32

44

Chunk#2

3

2

20 (3 bytes)

32

36

Chunk#3

3

3

none(0 bytes)

32

33

Chunk#4

3

3

none(0 bytes)

32

33

 

   演示一個消息由於太長,而被分割成128字節的塊。

 

Message Stream ID

Message TYpe ID

Time

Length

Msg # 1

12346

9 (video)

1000

307

 

下面是產生的塊。

 

Chunk Stream ID

Chunk Type

Header Data

No. of Bytes after Header

Total No. of bytes in  the chunk

Chunk#1

4

0

delta: 1000

length: 307

type: 9

streamID: 12346

(11 bytes)

128

140 

Chunk#2

4

3

none (0 bytes)

128  

129

Chunk#3

4

3

none (0 bytes)

51

52

 

數據傳輸的限定大小默認是128bytes,可以通過控制消息來改變,RTMP的約定是當ChunkID2,消息流ID0的時候,被認為是控制消息

 

 

具體傳輸的過程是這樣的,首先雙方先進行握手,握手過程官 方文檔上有說明,但是在flash10.1之后,adobe公司改了握手,文檔上那個握手不能用了,至少播放AVC和ACC不能用,這東西太坑人了,改了 又不說一聲,而且一個本來簡單的握手改的很是復雜,居然要依賴openssl加密,有必要嗎。網上找不到有關文章,我只有看rtmpserver開源項目 源碼來弄。

       握手步驟沒有變,但內容完全不一樣,以前叫sample handshake,現在叫復雜握手(complex handshake)。

它的步驟是由三個固定大小的塊組成,而不是可變大小的塊加上頭。握手開始於客戶端發送C0C1塊。在發送C2之前客戶端必須等待接收S1。在發送任何數據之前客戶端必須等待接收S2服務端在發送S0S1之前必須等待接收C0,也可以等待接收C1。服務端在發送S2之前必須等待接收C1。服務端在發送任何數據之前必須等待接收C2

網上有一個人說出了complex handshake的方式,http://blog.csdn.net/winlinvip/article/details/7714493,以下是他的說明:

scheme1scheme2這兩種方式,是包結構的調換。

 

keydigest的主要算法是:C1key128bytes隨機數。C1_32bytes_digest = HMACsha256(P1+P2, 1504, FPKey, 30) ,其中P1digest之前的部分,P2digest之后的部分,P1+P2是將這兩部分拷貝到新的數組,共1536-32長度。S1key根據C1key算出來,算法如下:

 

在Rtmpserver的sources\thelib\src\protocols\rtmp\inboundrtmpprotocol.cpp中有握手的代碼,看他的說明也馬馬虎虎,我也看源碼后才清楚具體過程。具體過程和他說的差不多,只是有些計算要用到openssl加密,所以要安裝openssl,我也是從rtmpserver上面扣出來的代碼然后自己改一下。

 

       握手之后就開始一些交互信令,這個如果按照文檔上的來,根本不行,文檔上省略的一些東西,坑人。

       我的交互過程是按照抓的FMS3.5的包來做的。握手之后首先client會發送一個connect消息過來。另外,RTMP交互的消息格式官方說明上有,還是比較清楚的。

字段名

類型

描述

命令名

字符串

命令名。設置為”connect”

傳輸ID

數字

總是設為1

命令對象

對象

含有名值對的命令信息對象

可選的用戶變量

對象

任何可選信息

   下面是在連接命令的命令對象中使用的名值對的描述:

屬性

類型

描述

示例值

App

字符串

客戶端要連接到的服務應用名

Testapp

Flashver

字符串

Flash播放器版本。和應用文檔中getversion()函數返回的字符串相同。

FMSc/1.0

SwfUrl

字符串

發起連接的swf文件的url

file://C:/ FlvPlayer.swf

TcUrl

字符串

服務url。有下列的格式。protocol://servername:port/appName/appInstance

rtmp://localhost::1935/testapp/instance1

fpad

布爾值

是否使用代理

true or false

audioCodecs

數字

指示客戶端支持的音頻編解碼器

SUPPORT_SND_MP3

videoCodecs

數字

指示支持的視頻編解碼器

SUPPORT_VID_SORENSON

pageUrl

字符串

SWF文件被加載的頁面的Url

http:// somehost/sample.html

objectEncoding

數字

AMF編碼方法

kAMF3

 

上圖中app的vod就是請求文件的路徑。

表示使用amf0編碼格式傳輸命令,如果是3就是表示用amf3。

 

之后server回復windows acknowledgement size-    set peer bandwidth- user control message(begin 0)- result(connect response)

User control message (用戶控制信息)的格式官方文檔上有,消息數據的頭兩個字節用於標識事件類型,0表示Stream Begin。事件類型之后是事件數據。事件數據字段是可變長的,此時的數據時00

 

在發送了windows acknowledgement size之后client會回一個windows adknowledgement size此時需要接收

然后server接收client發的一個create stream過來

 

接收到之后server返回一個_result

 

Server接收client發來的recv set buffer length - play commcand from

字段名

類型

描述

命令名

字符串

命令名,設為”play”

傳輸ID

數字

設置為0

命令對象

NULL

命令信息不存在,設為NULL

類型

流名

字符串

要播放的流名。對於播放一個FLV文件,則流名不帶文件后綴(例如,"sample”)。對於回放MP3ID3標簽,必須在流名前加MP3(例如:”MP3:Sample”)。對於播放H264/AAC文件,必須在流名前加MP4前綴,並且指定擴展名,例如播放sample.m4v,則指定”mp4:sample.m4v”

開始

數字

一個指定開始時間的可選參數。默認值是-2,意味着用戶首先嘗試播放流名中指定的直播流。如果流名字段中指定的直播流不存在,則播放同名的錄制流。如果本字段設置為-1,則只播放流名字段中指定的直播流。如果,本字段為0,或正值,則在本字段指定的時間,播放流名字段中指定的錄制流。如果指定的錄制流不存在,則播放播放列表中的下一項。

時長

數字

指定播放時長的可選字段。默認值是-1-1意味着播放一個直播流,直到沒有數據可以活到,或者播放一個錄制流知道結束。如果本字段位0,則播放一個錄制流中從start字段中指定的時間開始的單個幀。假設,start字段中指定的是大於或等於0的值。如果本字段為正值,則以本字段中的值為時間周期播放直播流。之后,則以時長字段中指定的時間播放錄制流?。(如果一個流在時長字段中指定的時間之前結束,則回放結束。)。如果傳遞一個非-1的負值,則把值當作-1

Reset

布爾值

指定是否刷新先前的播放列表的BOOL值或數值。

 

Client發的play命了中包含的請求的文件名,上圖是1(string ‘1’ 這個數據)。

 

然后server發送set chunk size - user control message(stream is recorded) - onstatus-play reset (AMF0 Command) - user control message(begin1) - onstatus-play start (AMF0 Command) - RtmpSampleAccess (AMF0 Data) 空audio數據 - onstatus-data start (AMF0 Data)

 

Set chunk size這里就是改默認128字節的消息,這里改成了4096,當然也可以不發次消息。注意,在改之前發送的消息如果超過了128bytes都要分塊發送的之后就可以每塊大小事4096了。

Begin這里,這個前2bytes是0,表示stream begin,和上面的begin是個類型,只是數據段是1,這個抓包有點問題,沒有顯示數據段而已,此begin 1之后,所有的包頭里面的streamID都是1了,之前的都是0

字段

類型

描述

命令名

字符串

命令名。如果播放成功,則命令名設為響應狀態。

描述

字符串

如果播放命令成功,則客戶端從服務端接收NetStream.Play.Start狀態響應消息。如果指定的流沒有找到,則接收到NetStream.Play.StreamNotFound

響應狀態消息。

 

這里的加載amf數據和解析amf數據(也就是交互的消息)可以用我寫的一個amf0的解析模塊來做。

 

然后就發送flv的元數據(metadata)了,如果沒有也可以不發這個。之后再發音視頻的配置信息項。最后就可以發送音視頻數據了。

發送音視頻數據的時候可以用以前的方式發:包頭的AMFType是8或者9,數據是flv的 tag data(沒有tag header)

 

也可以按照新的類型amf type為0x16,數據就是一個tag (header + data)來發。

 

其中的amf0數據請看這里

 

要注意下就是rtmp都是大端模式,所以發送數據,包頭、交互消息都要填成大端模式的,但是只有streamID是小端模式,這個也是文檔沒有說明,rtmp坑人的地方。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM