(转)rtmp协议简单解析以及用其发送h264的flv文件
Adobe公司太坑人了,官方文檔公布的信息根本就不全,如果只按照他上面的寫的話,是沒法用的。按照文檔上面的流程,server和client連接之后首先要進行握手,握手成功之后進行一些交互,其實就是交互一些信息以確認大家都是用的同一個協議,交互成功之后就開始傳數據了。
??????
首先說下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 - 時間戳
音視頻的播放同步是由時間戳來控制的,單位是毫秒。如果時間戳大于或等于16777215(16進制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的約定是當Chunk流ID為2,消息流ID為0的時候,被認為是控制消息。
?
?
具體傳輸的過程是這樣的,首先雙方先進行握手,握手過程官 方文檔上有說明,但是在flash10.1之后,adobe公司改了握手,文檔上那個握手不能用了,至少播放AVC和ACC不能用,這東西太坑人了,改了 又不說一聲,而且一個本來簡單的握手改的很是復雜,居然要依賴openssl加密,有必要嗎。網上找不到有關文章,我只有看rtmpserver開源項目 源碼來弄。
???????握手步驟沒有變,但內容完全不一樣,以前叫sample handshake,現在叫復雜握手(complex handshake)。
它的步驟是由三個固定大小的塊組成,而不是可變大小的塊加上頭。握手開始于客戶端發送C0,C1塊。在發送C2之前客戶端必須等待接收S1。在發送任何數據之前客戶端必須等待接收S2。服務端在發送S0和S1之前必須等待接收C0,也可以等待接收C1。服務端在發送S2之前必須等待接收C1。服務端在發送任何數據之前必須等待接收C2。
網上有一個人說出了complex handshake的方式,http://blog.csdn.net/winlinvip/article/details/7714493,以下是他的說明:
scheme1和scheme2這兩種方式,是包結構的調換。
?
key和digest的主要算法是:C1的key為128bytes隨機數。C1_32bytes_digest = HMACsha256(P1+P2, 1504, FPKey, 30)?,其中P1為digest之前的部分,P2為digest之后的部分,P1+P2是將這兩部分拷貝到新的數組,共1536-32長度。S1的key根據C1的key算出來,算法如下:
?
在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”)。對于回放MP3或ID3標簽,必須在流名前加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坑人的地方。
轉載于:https://www.cnblogs.com/zjoch/p/3286301.html
總結
以上是生活随笔為你收集整理的(转)rtmp协议简单解析以及用其发送h264的flv文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 扣图方法
- 下一篇: 【带流程眼镜的思考】消除“等待”就是提高