流媒体之RTMP——RTMP协议分析

文章目录

    • 一:Message
      • 1.1 Protocol Control Messages
        • 1.1.1 Set Chunk Size(1)
        • 1.1.2 Abort Message(2)
        • 1.1.3 Acknowledgement(3)
        • 1.1.4 Window Acknowledgement Size(5)
        • 1.1.5 Set Peer Bandwidth(6)
      • 1.2 Command Messages
        • 1.2.1 User Control Messages (4)
        • 1.2.2 Command Message (20, 17)
        • 1.2.3 Data Message (18, 15)
        • 1.2.4 Shared Object Message (19, 16)
        • 1.2.5 Audio Message (8)
        • 1.2.6 Video Message (9)
        • 1.2.7 Aggregate Message (22)
    • 二:Chunk
      • 2.1 Basic Header
      • 2.2 Message Header
        • 2.2.1 Type0:11 bytes
        • 2.2.2 Type1:7 bytes
        • 2.2.3 Type2: 3 bytes
        • 2.2.4 Type3:0 bytes
      • 2.3 Extended Timestamp
      • 2.4 Example
        • 2.4.1 Audio Messages
        • 2.4.2 Split Messages
    • 三:Handshake
      • 3.1 C0和S0格式
      • 3.2 C1和S1格式
      • 3.3 C2和S2格式
    • 四:Stream和Chunk的理解
      • 4.1 谈谈Stream ID
      • 4.2 谈谈Stream、Message和Chunk关系
    • 五:拆包和组包的完整示例
      • 5.1 无优化
      • 5.2 有优化

作者:一步(Reser)
日期:2019.10.11

一:Message

Message为应用层的抽象,实际发送是将message 拆分为chunk发送。

 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Message Type  |                Payload length                 |
|   (1 byte)    |                 (3 bytes)                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Timestamp                               |
|                       (4 bytes)                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                Stream ID                      |
|                (3 bytes)                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • Message Type:包类型,根据功能划分;
  • Stream ID:Message通道,推流时请求建立后由服务器返回。

1.1 Protocol Control Messages

1.1.1 Set Chunk Size(1)

默认大小为128 bytes。

 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|                     chunk size (31 bits)                    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

1.1.2 Abort Message(2)

 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       chunk stream id (32 bits)               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

1.1.3 Acknowledgement(3)

 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        sequence number (4 bytes)              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

1.1.4 Window Acknowledgement Size(5)

 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   Acknowledgement Window size (4 bytes)       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

1.1.5 Set Peer Bandwidth(6)

 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   Acknowledgement Window size                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Limit Type   |
+-+-+-+-+-+-+-+-+

1.2 Command Messages

1.2.1 User Control Messages (4)

message stream ID=0

chunk stream ID=2

+------------------------------+-------------------------
|     Event Type (16 bits)     | Event Data
+------------------------------+-------------------------

1.2.2 Command Message (20, 17)

1.2.3 Data Message (18, 15)

1.2.4 Shared Object Message (19, 16)

1.2.5 Audio Message (8)

1.2.6 Video Message (9)

1.2.7 Aggregate Message (22)



二:Chunk

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

2.1 Basic Header

IDs: 2~63
 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt|   cs id   |
+-+-+-+-+-+-+-+-+

IDs: 64~319 (the second byte + 64)
 0               1
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt|     0     |   cs id - 64  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

IDs: 64~65599 (((the third byte)*256 + (the second byte) + 64))
 0               1               2
 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             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • fmt - message header的四种类型之一;
  • cs id - 表示范围 3~65599;
  • 64~319 可以使用2字节或3字节的头部表示。

2.2 Message Header

只有Type0的时间戳为绝对时间,其他类型的时间戳都为相对时间!

2.2.1 Type0:11 bytes

 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)            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  

2.2.2 Type1:7 bytes

 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|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

2.2.3 Type2: 3 bytes

 0               1               2
 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                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

2.2.4 Type3:0 bytes

++
||
++

timestamp >= 0xFFFFFF 时意味着使用扩展时间戳。

2.3 Extended Timestamp

当Message Header的时间戳为 0xFFFFFF 时使用扩展时间戳。4 bytes表示timestamp或timestamp delta。

 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      extended timestamp                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

2.4 Example

2.4.1 Audio Messages

例如有以下音频帧(Messages)需要发送:

+---------+-----------------+-----------------+-----------------+
|         |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    |
+---------+-----------------+-----------------+-------+---------+

因为都是音频帧,所以走的Stream ID和Message Type ID相同。每帧的时间间隔为20ms,每帧长度相等。使用Chunk类型优化,最终最优的发送方案为:

+--------+---------+-----+------------+------- ---+------------+
|        | Chunk   |Chunk|Header Data |No.of Bytes|Total No.of |
|        |Stream ID|Type |            |  After    |Bytes in the|
|        |         |     |            |Header     |Chunk       |
+--------+---------+-----+------------+-----------+------------+
|Chunk#1 |    3    |  0  | delta: 1000|   32      |    44      |
|        |         |     | length: 32,|           |            |
|        |         |     | type: 8,   |           |            |
|        |         |     | stream ID: |           |            |
|        |         |     | 12345 (11  |           |            |
|        |         |     | bytes)     |           |            |
+--------+---------+-----+------------+-----------+------------+
|Chunk#2 |    3    |  2  | 20 (3      |   32      |    36      |
|        |         |     | bytes)     |           |            |
+--------+---------+-----+----+-------+-----------+------------+
|Chunk#3 |    3    |  3  | none (0    |   32      |    33      |
|        |         |     | bytes)     |           |            |
+--------+---------+-----+------------+-----------+------------+
|Chunk#4 |    3    |  3  | none (0    |   32      |    33      |
|        |         |     | bytes)     |           |            |
+--------+---------+-----+------------+-----------+------------+
  • 第一帧:因为没有上一帧,所以Chunk无法优化,使用Chunk Type1;
  • 第二帧:和第一帧相比消息类型、数据长度等都相同,只有时间戳不同,所以可以采用Chunk Type2对帧优化,头部只需timestamp delta字段;
  • 第三帧和第四帧:与上面帧相比,不单消息通道类型、数据长度等都相同,时间戳差值也都相同,为20,因此使用Chunk Type3,头部无需任何信息。

可见使用Chunk Type的优化还是很有效果的。

2.4.2 Split Messages

当Chunk Size=128时,如果Message长度过长就需要分割为多个Chunk。如有以下Messages:

+-----------+-------------------+-----------------+-----------------+
|           | Message Stream ID | Message TYpe ID | Time  | Length  |
+-----------+-------------------+-----------------+-----------------+
| Msg # 1   |       12346       |    9 (video)    | 1000  |   307   |
+-----------+-------------------+-----------------+-----------------+

Message的长度为307 > 128,因此需要对Message进行分割:

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

307 = 128 + 128 + 51。

可见当Chunk Type3的上一帧为Chunk Type2或者Chunk Type1时表示timestamp delta相同进行的优化,而如果上一帧为Chunk Type0则表示同一帧的分割。(此处可继续优化)



三:Handshake

         +-------------+                           +-------------+
         |    Client   |       TCP/IP Network      |    Server   |
         +-------------+            |              +-------------+
               |                    |                     |
         Uninitialized              |               Uninitialized
               |          C0        |                     |
               |------------------->|         C0          |
               |                    |-------------------->|
               |          C1        |                     |
               |------------------->|         S0          |
               |                    |<--------------------|
               |                    |         S1          |
          Version sent              |<--------------------|
               |          S0        |                     |
               |<-------------------|                     |
               |          S1        |                     |
               |<-------------------|                Version sent
               |                    |         C1          |
               |                    |-------------------->|
               |          C2        |                     |
               |------------------->|         S2          |
               |                    |<--------------------|
            Ack sent                |                  Ack Sent
               |          S2        |                     |
               |<-------------------|                     |
               |                    |         C2          |
               |                    |-------------------->|
          Handshake Done            |               Handshake Done
               |                    |                     |

其实整个握手过程涉及两条握手线路,一条针对客户端,一条针对服务端。

客户端:

C1    ->    
      <-     S2            

服务端:

C0    ->     
      <-     S0/S1
C2    ->

3.1 C0和S0格式

 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|   version     |
+-+-+-+-+-+-+-+-+

C0中version表示客户端请求的版本,S0的version表示server选择的版本。

0~2:废弃

3:当前使用版本

4~31:保留

32~255:禁止使用

3.2 C1和S1格式

1536 bytes。

 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        time (4 bytes)                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        zero (4 bytes)/FMS Version             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        random bytes                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         random bytes                          |
|                            (cont)                             |
|                             ....                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

3.3 C2和S2格式

1536 bytes,基本上是对C1和S1的回复。

 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        time (4 bytes)                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       time2 (4 bytes)                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        random echo                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         random echo                           |
|                            (cont)                             |
|                             ....                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


四:Stream和Chunk的理解

Message Stream为创建的一条数据通道,可能有以下三种:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       视频/音频                                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       只含视频                                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       只含音频                                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Message Stream由 Message Stream ID 唯一标识。创建Message Stream通道之后就可以在通道中发送Message,Message的类型由Message Stream Type 标识。RTMP模块在底层将对Message分割,分割的Chunk是真正发送的格式。

  • Chunk Type优化 - 同一个Message分割内部或者连续发送的Message之间;

  • Chunk ID - 同一个Message分割成的Chunks的 Chunk ID 相同;不同Message可以选择使用相同的Chunk ID。官方文档对ChunkID的使用描述很模糊,很大程度上会产生误导。其实,只要保证同一Message分割的Chunks的Chunk ID相同就可以了,这样服务器就可正确组包;此外,还要保证特定的Message使用特定的Chunk ID

    常用的Chunk ID有:

    • 2: Protocol Control Messages (1,2,3,5,6) & User Control Messages Event (4),Ping 和ByteRead通道

    • 3: Invoke通道,这个通道适用的消息很多,比较灵活, connect, create_stream, release_stream, delete_stream, fcpublish, fcunpublish, publish, play, pause, seek, send_get_stream_length, 以及script脚本数据

    • 4:Audio和Vidio通道

    • 5 、6 、7:服务器保留,经观察FMS2用这些Channel也用来发送音频或视频数据。

    // https://github.com/ossrs/srs/blob/master/trunk/src/kernel/srs_kernel_flv.hpp
    /**
     * the chunk stream id used for some under-layer message,
     * for example, the PC(protocol control) message.
     */
    #define RTMP_CID_ProtocolControl                0x02
    /**
     * the AMF0/AMF3 command message, invoke method and return the result, over NetConnection.
     * generally use 0x03.
     */
    #define RTMP_CID_OverConnection                 0x03
    /**
     * the AMF0/AMF3 command message, invoke method and return the result, over NetConnection,
     * the midst state(we guess).
     * rarely used, e.g. onStatus(NetStream.Play.Reset).
     */
    #define RTMP_CID_OverConnection2                0x04
    /**
     * the stream message(amf0/amf3), over NetStream.
     * generally use 0x05.
     */
    #define RTMP_CID_OverStream                     0x05
    /**
     * the stream message(amf0/amf3), over NetStream, the midst state(we guess).
     * rarely used, e.g. play("mp4:mystram.f4v")
     */
    #define RTMP_CID_OverStream2                    0x08
    /**
     * the stream message(video), over NetStream
     * generally use 0x06.
     */
    #define RTMP_CID_Video                          0x06
    /**
     * the stream message(audio), over NetStream.
     * generally use 0x07.
     */
    #define RTMP_CID_Audio                          0x07
    

4.1 谈谈Stream ID

这节很重要!

Message Header中的 Message Stream ID 到底是什么?相信研究过RTMP协议的人十有八九都想搞清楚,但翻来覆去好像都没有说明白的。

网上流行的两种说法:

message stream id的字节序是小端序,这个字段是为了解复用而设计的,RTMP文档上说的相当的模糊。

message stream ID可以使任意值,不同的消息流复用成相同的chunk stream,基于它们的ID能够解复用。于chunk stream 是相关的,这个字段是一个不透明的值没有整明白什么意思,我的理解就是用来标识和服务器连接的flash端的序号。

这种说法乍看相当合理,说Stream ID为客户端标识,所有的C/S命令处理直接通过鉴别这个标识就可以了,无需再关注什么IP之类。但测试发现,多个客户端时候,服务器返回的 Message Stream ID 都相同,等于1。可见这个并不是针对不同客户端的标识,Adobe的人确实不按常规出牌。

另外说法:

StreamID占用RTMP包头的最后4个字节,是一个big-endian的int型数据。我们x86 计算机内存中数据存放都是小尾数模式:little-endian,而网络数据流一般都是大尾数模式:big-endian。 StreamID是音视频流的唯一ID, 一路流如果既有音频包又有视频包,那么这路流音频包的StreamID和他视频包的StreamID相同,但ChannelID不同。
ChannelID 和StreamID之间的计算公式:StreamID=(ChannelID-4)/5+1  参考red5。如果这个封包既不是音频包,也不是视频包,那么他的StreamID=0。例如当音视频包ChannelID为234时StreamID都为1 当音视频包ChannelID为9的时候StreamID为2

简言之,非音视频的StreamID=0;音视频的StreamID唯一。另外, S t r e a m I D = ( C h a n n e l I D − 4 ) / 5 + 1 StreamID=(ChannelID-4)/5+1 StreamID=(ChannelID4)/5+1 ,这个可能是什么?下文说明。

不妨再扒扒官方文档 rtmp_specification_1.0.pdf :

7.2. Types of Commands
...
The following class objects are used to send various commands:

NetConnection An object that is a higher-level representation of
connection between the server and the client.
    
NetStream An object that represents the channel over which audio
streams, video streams and other related data are sent. We also
send commands like play , pause etc. which control the flow of the
data.

这里好像说了两个Object:NetConnection 是C/S连接的高层控制协议;NetStream 是媒体数据传输和媒体数据内容控制协议。继续:

7.2.1. NetConnection Commands
The NetConnection manages a two-way connection between a client
application and the server. In addition, it provides support for
asynchronous remote method calls.
    
The following commands can be sent on the NetConnection :
o connect
o call
o close
o createStream

顺便看下 createStream

7.2.1.3. createStream
The client sends this command to the server to create a logical
channel for message communication The publishing of audio, video, and
metadata is carried out over stream channel created using the
createStream command.
    
NetConnection is the default communication channel, which has a
stream ID 0. Protocol and a few command messages, including
createStream, use the default communication channel.

再看:

7.2.2. NetStream Commands
The NetStream defines the channel through which the streaming audio,
video, and data messages can flow over the NetConnection that
connects the client to the server. A NetConnection object can
support multiple NetStreams for multiple data streams.
    
The following commands can be sent on the NetStream by the client to
the server:
o play
o play2
o deleteStream
o closeStream
o receiveAudio
o receiveVideo
o publish
o seek
o pause

到此基本清晰:

  • RTMP的命令由两类Object管理:NetConnectionNetStream
  • NetConnection 负责客户端和服务端连接的高层管理,且具有默认的 S t r e a m I D = 0 StreamID=0 StreamID=0
  • NetStream 负责音视频流数据的发送接收,以及流内容控制的一些命令;一个RTMP连接可以创建多个 NetStream 负责传输音视频,每个通道自然也就会有自己的唯一StreamID;
  • 简言之,NetConnectionNetStream 是一对多关系;但由于绝大多数情况下并不需要多个 NetStream 作为音视频载体,因此通常为:NetConnection: StreamID=0NetStream: StreamID=1 ;这也就解释了为了服务器返回的 StreamID 通常为1了。

另外,librtmp源码中 RTMP_SendCtrl 注释里有这样一段描述:

/*
from http://jira.red5.org/confluence/display/docs/Ping:

Ping is the most mysterious message in RTMP and till now we haven't fully interpreted it yet. In summary, Ping message is used as a special command that are exchanged between client and server. This page aims to document all known Ping messages. Expect the list to grow.

The type of Ping packet is 0x4 and contains two mandatory parameters and two optional parameters. The first parameter is the type of Ping and in short integer. The second parameter is the target of the ping. As Ping is always sent in Channel 2 (control channel) and the target object in RTMP header is always 0 which means the Connection object, it's necessary to put an extra parameter to indicate the exact target object the Ping is sent to. The second parameter takes this responsibility. The value has the same meaning as the target object field in RTMP header. (The second value could also be used as other purposes, like RTT Ping/Pong. It is used as the timestamp.) The third and fourth parameters are optional and could be looked upon as the parameter of the Ping packet. Below is an unexhausted list of Ping messages.

    * type 0: Clear the stream. No third and fourth parameters. The second parameter could be 0. After the connection is established, a Ping 0,0 will be sent from server to client. The message will also be sent to client on the start of Play and in response of a Seek or Pause/Resume request. This Ping tells client to re-calibrate the clock with the timestamp of the next packet server sends.
    * type 1: Tell the stream to clear the playing buffer.
    * type 3: Buffer time of the client. The third parameter is the buffer time in millisecond.
    * type 4: Reset a stream. Used together with type 0 in the case of VOD. Often sent before type 0.
    * type 6: Ping the client from server. The second parameter is the current time.
    * type 7: Pong reply from client. The second parameter is the time the server sent with his ping request.
    * type 26: SWFVerification request
    * type 27: SWFVerification response
*/
int
RTMP_SendCtrl(RTMP *r, short nType, unsigned int nObject, unsigned int nTime)
{
    ...
}

这里提到 nObject 参数和RTMP头部的StreamID字段意义相同,都是指明额外的发送目标;而通常 nObject 的赋值正是Server返回的StreamID。

S t r e a m I D = ( C h a n n e l I D − 4 ) / 5 + 1 StreamID=(ChannelID-4)/5+1 StreamID=(ChannelID4)/5+1 ,这个可能是什么?当有多个 NetStream 时,为了方便Chunk的分包管理,对ChunkID和StreamID进行了分组对应,但具体实现也并不可知,也无需关注。

文档中粗列了NetConnection和NetStream的适用命令,但还很多命令并没有完全列出;此外,librtmp在命令发送时也并没有严格按照文档规范去填充 StreamID (m_nInfoField2字段),常常为0,但并没有影响,不知如果有多路NetStream会不会出状况。

4.2 谈谈Stream、Message和Chunk关系

这节也很重要!

Stream是什么由上节已经比较清楚,其实就是C/S数据交互的逻辑通道,由StreamID标识。而Message是针对包的逻辑概念,发送的一帧视频,一帧音频,一个控制命令,其实都是Message,由Message Type标识。但这些Message并不是直接发送,而是在底层按照RTMP协议规范拆成了一个一个Chunk发送,这些Chunk在传输时会被决定走哪个Channel(虚拟通道),会被赋予Chunk ID(也可以认为是Channel ID),这个ChunkID按照Message类型赋予就可以了。



五:拆包和组包的完整示例

当ChunkSize=128时,考虑在Stream ID=12345的通道上传输长度为1024的四帧音频数据(音频的Type ID=8):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fkkZ5Bdj-1570787194095)(./raw.jpg)]

5.1 无优化

当无忧化时,四帧采用相同的发送方式:

流媒体之RTMP——RTMP协议分析_第1张图片

5.2 有优化

第一帧发送:
流媒体之RTMP——RTMP协议分析_第2张图片

第二帧发送:
流媒体之RTMP——RTMP协议分析_第3张图片

第三帧和第四帧发送:
流媒体之RTMP——RTMP协议分析_第4张图片



你可能感兴趣的:(Media,Stream)