Rtmp Chunk詳解

一、簡述

RTMP協議中基本的數據單元稱爲消息(Message)。當RTMP協議在互聯網中傳輸數據的時候,消息會被拆分成更小的單元,稱爲消息塊(Chunk)。

每個塊必須完整的發送後才能發送下一個塊。接收端根據快流ID把塊組裝成完整的小消息。塊允許把更高層協議的大消息分割成更小的消息分片,例如爲了防止低優先級的大消息(如視頻消息)阻礙高優先級的小消息(如音頻和控制消息)。

二、Rtmp Chunk Header

   +--------------+----------------+--------------------+--------------+
   | Basic Header | Message Header | Extended Timestamp |  Chunk Data  |
   +--------------+----------------+--------------------+--------------+
   |<------------------- Chunk Header ----------------->|
                               Chunk Format

Chunk Header 分爲4部分,

2.1基本頭:Base Header(1-3字節)

RTMP 協議最多支持 65597 個流(chunk),CS ID(塊流ID) 範圍爲: 3 ~ 65599。ID 0、1、2 被保留。

基本頭分爲三種情況由Base Header第一個字節後六個bits決定

  • (後六個bits==0):基本頭長度爲2字節第二個字節表示塊流ID
  • (後六個bits==1):基本頭長度爲3字節,第二三個字節表示快流ID
  • (1<後六個bits<=64):基本頭長度爲1字節,後6bits表示塊流ID

1).第一個字節後六bits爲0的基本頭結構如下: 

 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |fmt|     0     |   cs id - 64  |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 Chunk basic header 2

 Basic Header 爲 3字節的情況 (第一個字節後 6 位爲 000001)

塊流ID從64到65599。塊流ID= 第三個byte*256+(第二個byte+64)

2). 第一個字節後六bits爲1的基本頭結構如下: 

 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt|     1     |        cs id - 64             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
           Chunk basic header 3

3).第一個字節後六bits爲cs id的基本頭結構如下:

 

 0 1 2 3 4 5 6 7
 +-+-+-+-+-+-+-+-+
 |fmt|   cs id   |
 +-+-+-+-+-+-+-+-+
 Chunk basic header 1

Basic Header 爲 1字節的情況 (第一個字節後 6 位爲 cs id)

塊流ID從2到63

CSID 保留值

0、1、2 被保留, 3 ~ 8 基本都是固定用途,所以 9 ~ 65599 才用於自定義 csid,但一般我們用不到。

  • 0 表示 Basic Header 總共要佔用 2 個字節
  • 1 表示 Basic Header 總共要佔用 3 個字節
  • 2 代表該 chunk 是控制信息和一些命令信息
  • 3 代表該 chunk 是客戶端發出的 AMF0 命令以及服務端對該命令的應答
  • 4 代表該 chunk 是客戶端發出的音頻數據,用於 publish
  • 5 代表該 chunk 是服務端發出的 AMF0 命令和數據
  • 6 代表該 chunk 是服務端發出的音頻數據,用於 play;或客戶端發出的視頻數據,用於 publish
  • 7 代表該 chunk 是服務端發出的視頻數據,用於 play
  • 8 代表該 chunk 是客戶端發出的 AMF0 命令,專用來發送: getStreamLength, play, publish

2.2.消息頭:Message Header(0-11字節)

2.2.1.消息頭結構有4種類型

  • 類型0:fmt == 0:Message Header 長度爲 11
  • 類型1:fmt == 1:Message Header 長度爲 7
  • 類型2:fmt == 2:Message Header 長度爲 3
  • 類型3:fmt == 3:Message Header 長度爲 0

1).類型0

timestamp(4bytes)+message length(3bytes)+message type id(1byte)+message stream id(4bytes)

 0               1               2               3
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                   timestamp                   |message length |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |     message length (cont)     |message type id| msg stream id |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |           message stream id (cont)            |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       Chunk Message Header - Type 0

注意: 此時 timestamp 爲絕對時間戳

2).類型1

timestamp(4bytes)+message length(3bytes)+message type id(1byte)

 0               1               2               3
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |              timestamp delta                  |message length |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |     message length (cont)     |message type id| 
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 Chunk Message Header - Type 1

fmt爲1的情況下省略了message stream id,這個id與上一個chunk message相同

注意: 此時 timestamp 爲相對時間戳

3).類型2

timestamp(4bytes)

 0               1               2               3
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |              timestamp delta                  |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 Chunk Message Header - Type 3

fmt爲2的情況下省略了message length、message type id和message stream id,省略的這個與上一個chunk message 相同

該類型的消息是固定長度與固定類型的消息,也就是說每次發的消息大小與格式相同,例如音頻流格式,在第一個新的 chunk 以後使用該類型。

注意: 此時 timestamp 爲相對時間戳

3).類型3

fmt爲3的情況下Message Header全部省略了,跟上一個chunk Message完全相同

類型3使用的情況大多是因爲消息太大比如視頻消息,一個chunk中放不下,要分chunk發送,第一個chunk後的消息使用該類型,比如關鍵幀,動不動就上萬字節的數據,而且時間戳類型都是固定的,所以使用該類型發送數據。

2.2.2.時間戳

只有在類型0的情況下時間戳是絕對時間戳其他類型的時間戳都是相對時間戳(時間戳增量)。從librtmp與srs中觀察分析得到服務器與客戶端推流的時間戳是相同的(本人都實現了,所以認爲相同,如有其它情況請大神告知)

在此說下在rtmp中時間戳怎麼打:

一般情況下這個時間戳都是從絕對時間戳開始也就是從0開始,音視頻都有自己的時間戳(這一點很重要),音視頻採集過來的時候會自帶採集時間戳,僞代碼如下:

private int GetVideoTimestamp(MediaFrame mediaFrame)
{
    if (視頻時間戳== 0)
    {
        _timestampVideo = mediaFrame.採集時間戳;
        return 0;
    }
    else
    {
        return mediaFrame.採集時間戳 - 視頻時間戳;
    } 
}

2.2.3.消息長度

消息長度指的是Header以後的所有字節數

2.2.4.消息類型

  • 1 設置塊大小
  • 2 中斷消息,丟棄舊數據
  • 3 確認
  • 4 用戶控制消息
  • 5 設置確認窗口大小
  • 6 設置流帶寬
  • 7 音頻數據
  • 9 視頻數據
  • 15(0x0f). AMF3 數據
  • 16(0x10) AMF3 共享對象事件
  • 17(0x11) AMF3 命令
  • 18(0x12) AMF0 數據
  • 19(0x13) AMF0 共享對象事件
  • 20(0x14) AMF0 命令,Invoke 方法調用
  • 22(0x16) 聚合消息, H.264, 類似 FLV 文件存儲格式,每個音視頻包作爲一個 Tag, 許多的 Tag 組成了這個 AMFType=0x16 的數據類型

2.2.5.消息ID

這個ID是四個字節的小端數據,這個ID默認置1即:0x01 0x00 0x00 0x00

2.3擴展時間戳:Extended Timestamp(0-4字節)

類型爲1或2的塊裏,本字段代表當前塊和上一個塊的時間戳之差。如果時間間隔大於等於16777215(0xFFFFFF),此字段的取值必須爲16777215,並且與擴展時間戳一起組成32比特的完整時間戳。如果時間戳小於16777215,那麼此字段代表了完整的時間戳。

三、示例

3.1音頻示例

一下是普通的音頻幀發送方式,音頻消息長度比較小,一般不用分包,直接全包發送即可,但是如下方式發送會導致部分消息的冗餘。

 +---------+-----------------+-----------------+-----------------+
 |         |Message Stream ID| Message Type ID | Time  | Length  |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 1 |    12345        |         8       | 0     |   512   |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 2 |    12345        |         8       | 32    |   512   |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 3 |    12345        |         8       | 64    |   512   |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 4 |    12345        |         8       | 96    |   512   |
 +---------+-----------------+-----------------+-------+---------+

如下方式解決了Chunk的消息冗餘。消息按類型0、類型2、類型3、類型3... 的方式發送,第二包因爲不確定時間戳增量因此用類型二來確定,從第三包以後因爲時間戳增量也是相同的就可以用類型三來發送。

 +---------+-----------------+-----------------+-----------------+
 |         |Message Stream ID| Message Type ID | Time  | Length  |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 1 |    12345        |         8       | 0     |   512   |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 2 |    12345        |         8       | 32    |   512   |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 3 |    12345        |         8       | 64    |   512   |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 4 |    12345        |         8       | 96    |   512   |
 +---------+-----------------+-----------------+-------+---------+

注意:本人在實現服務器的過程中,發現從各個其他組發來的流的時間戳是各式各樣的,尤其音頻的時間戳更是奇葩的厲害,根本不會出現使用類型3的情況,乾脆就直接用類型1來傳輸音頻,再者音視頻都是相間交錯發的。

3.2視頻示例

視頻消息由於太長即使你把Chunk Size設的再大也不行,一般Chunk Size都設成4096(0x1000)。

 +-----------+-------------------+-----------------+-----------------+
 |           | Message Stream ID | Message Type ID | Time  | Length  |
 +-----------+-------------------+-----------------+-----------------+
 | Msg # 1   |       12346       |    9 (video)    |   0   | 20000   |
 +-----------+-------------------+-----------------+-----------------+

下面是分割後的Chunk。消息按類型0、類型3、類型3、類型3... 的方式發送,因爲整個幀的時間戳都是一個時間戳。

 +-------+------+-----+-------------+-----------+------------+
 |       |Chunk |Chunk|Header       |No. of     |Total No. of|
 |       |Stream| Type|Data         |Bytes after| bytes in   |
 |       | ID   |     |             | Header    | the chunk  |
 +-------+------+-----+-------------+-----------+------------+
 |Chunk#1|  4   |  0  | delta: 0    |  4096     | 4096+12    |
 |       |      |     | length:9192 |           |            |
 |       |      |     | type: 9,    |           |            |
 |       |      |     | stream ID:  |           |            |
 |       |      |     | 12346       |           |            |
 |       |      |     | (11 bytes)  |           |            |
 +-------+------+-----+-------------+-----------+------------+
 |Chunk#2|  4   |  3  | none        | 4096      | 4096+1     |
 |       |      |     | (0 bytes)   |           |            |
 +-------+------+-----+-------------+-----------+------------+
 |Chunk#3|  4   |  3  | none        | 1000      | 1000+1     |
 |       |      |     | (0 bytes)   |           |            |
 +-------+------+-----+-------------+-----------+------------+

注意:在類型0的的Chunk中一定要設定整個消息的長度,在本實例中的長度爲9192(4096+4096+1000)這個別算錯了。這裏的視頻幀是按RTMP發送視頻幀格式拼裝完後再整體進行Chunk 分塊發送的。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章