rtmp 协议详解
1. handshake
1.1 概述
rtmp 连接从握手开始。它包含三个固定大小的块。客户端发送的三个块命名为 C0,C1,C2;服务端发送的三个块命名为
S0,S1,S2。
握手序列:
- 客户端通过发送 C0 和 C1 消息来启动握手过程。客户端必须接收到 S1 消息,然后发送 C2 消息。客户端必须接收到
S2 消息,然后发送其他数据。 - 服务端必须接收到 C0 或者 C1 消息,然后发送 S0 和 S1 消息。服务端必须接收到 C2 消息,然后发送其他数据。
握手示意图
.
+-------------+ +-------------+
| 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
| | |
Pictorial Representation of Handshake
[译]握手示意图
1.2 complex handshake
1.2.1 C0 和 S0 格式
C0 和 S0 包由一个字节组成,下面是 C0/S0 包内的字段:
.
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
| version |
+-+-+-+-+-+-+-+-+
C0 and S0 bits
- version(1 byte):RTMP 的版本,一般为 3。
1.2.2 C1 和 S1 格式
C1和S1包含两部分数据:key和digest,分别为如下:
.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| version (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| key (764 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| digest (764 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
C1 and S1 bits
key 和 digest 的顺序是不确定的,也有可能是:(nginx-rtmp中是如下的顺序):
.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| version (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| digest (764 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| key (764 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
C1 and S1 bits
764 bytes key 结构:
- random-data: (offset) bytes
- key-data: 128 bytes
- random-data: (764 – offset – 128 – 4) bytes
- offset: 4 bytes
764 bytes digest 结构:
- offset: 4 bytes
- random-data: (offset) bytes
- digest-data: 32 bytes
- random-data: (764 – 4 – offset – 32) bytes
1.2.3 C2 和 S2 格式
.
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random-data (1504 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| digest-data (32 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
C2 and S2 bits
hanshake:S0 + S1 + S2
1.3 simple handshake
1.3.1 C0 和 S0 格式
C0 和 S0 包由一个字节组成,下面是 C0/S0 包内的字段:
.
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
| version |
+-+-+-+-+-+-+-+-+
C0 and S0 bits
- version(1 byte):版本。在 C0 包内,这个字段代表客户端请求的 RTMP 版本号。在 S0 包内,这个字段代表服务端选
择的 RTMP 版本号。当前使用的版本是 3。版本 0-2 用在早期的产品中,如今已经弃用;版本 4-31 被预留用于后续产
品;版本 32-255 (为了区分 RTMP 协议和文本协议,文本协议通常是可以打印字符)不允许使用。如果服务器无法识别
客户端的版本号,应该回复版本 3,。客户端可以选择降低到版本 3,或者终止握手过程。
1.3.2 C1 和 S1 格式
C1 和 S1 包长度为 1536 字节,包含以下字段:
.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| zero (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random bytes |
| (cont) |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
C1 and S1 bits
- time(4 bytes):本字段包含一个时间戳,客户端应该使用此字段来标识所有流块的时刻。时间戳取值可以为零或其他
任意值。为了同步多个块流,客户端可能希望多个块流使用相同的时间戳。 - zero(4 bytes):本字段必须为零。
- random (1528 bytes):本字段可以包含任意数据。由于握手的双方需要区分另一端,此字段填充的数据必须足够随机
(以防止与其他握手端混淆)。不过没有必要为此使用加密数据或动态数据。
1.3.3 C2 和 S2 格式
C2 和 S2 包长度为 1536 字节,作为 C1 和 S1 的回应,包含以下字段:
.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time2 (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random echo |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random echo |
| (cont) |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
C2 and S2 bits
- time(4 bytes):本字段必须包含对端发送的时间戳。
- time2(4 bytes):本字段必须包含时间戳,取值为接收对端发送过来的握手包的时刻。
- random(1528 bytes):本字段必须包含对端发送过来的随机数据。握手的双方可以使用时间 1 和时间 2 字段来估算
网络连接的带宽和/或延迟,但是不一定有用。
2. 组块
2.1 块格式
.
+--------------+----------------+--------------------+--------------+
| Basic Header | Message Header | Extended Timestamp | Chunk Data |
+--------------+----------------+--------------------+--------------+
| |
|<------------------- Chunk Header ----------------->|
Chunk Format
- 块的基本头(1-3字节):这个字段包含块流ID和块类型。块类型决定了编码过的消息头的格式。这个字段是一个变
长字段,长度取决于块流ID。 - 消息头(0,3,7,11字节):这个字段包含被发送的消息信息(无论是全部,还是部分)。字段长度由块头中的
块类型来决定。 - 扩展时间戳(0,4字节):这个字段是否存在取决于块消息头中编码的时间戳。
- 块数据(可变大小):当前块的有效数据,上限为配置的最大块大小。
2.2 Basic Header
包含 chunk stream ID(流通道id)和chunk type(即fmt),chunk stream id 一般被简写为CSID,用来唯一标识一个
特定的流通道,chunk type决定了后面Message Header的格式。Basic Header的长度可能是 1,2,或 3 个字节,
其中 chunk type 的长度是固定的(占2位,单位是bit),Basic Header 的长度取决于 CSID 的大小,在足够存储这两
个字段的前提下最好用尽量少的字节从而减少由于引入Header增加的数据量。
RTMP协议支持用户自定义 [3,65599] 之间的 CSID,0, 1, 2 由协议保留表示特殊信息。0 代表 Basic Header 总共要
占用 2 个字节,CSID 在 [64,319] 之间; 1 代表占用 3 个字节,CSID 在 [64,65599] 之间; 2 代表该 chunk 是控制
信息和一些命令信息。
2.2.1 Basic Header:1 byte
.
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt| cs id |
+-+-+-+-+-+-+-+-+
2.2.2 Basic Header: 2 byte , csid == 0
CSID占14bit,此时协议将于chunk type所在字节的其他bit都置为0,剩下的一个字节表示CSID – 64,这样共有8个bit
来存储 CSID,8 bit 可以表示 [0,255] 个数,因此这种情况下 CSID 在 [64,319],其中 319 = 255 + 64。
.
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt| 0 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2.2.3 Basic Header: 3 bytes , csid == 1
CSID占22bit,此时协议将第一个字节的[2,8]bit置1,余下的16个bit表示CSID – 64,这样共有16个bit来存储CSID,
16bit可以表示[0,65535]共 65536 个数,因此这种情况下 CSID 在 [64,65599],其中65599=65535+64,需要注意的是,
Basic Header是采用小端存储的方式,越往后的字节数量级越高,因此通过3个字节的每一个bit的值来计算CSID时,
应该是: <第三个字节的值> * 256 + <第二个字节的值> + 64.
.
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2.3 Message Header
包含了要发送的实际信息(可能是完整的,也可能是一部分)的描述信息。Message Header的格式和长度取决于Basic Header
的chunk type,即fmt,共有四种不同的格式。其中第一种格式可以表示其他三种表示的所有数据,但由于其他三种格式是基
于对之前chunk的差量化的表示,因此可以更简洁地表示相同的数据,实际使用的时候还是应该采用尽量少的字节表示相同意
义的数据。下面按字节从多到少的顺序分别介绍这四种格式的 Message Header。
Message Header 四种消息头格式。
一、Chunk Type(fmt) = 0: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 (coutinue) |message type id| msg stream id |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| msg stream id |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type=0时Message Header占用11个字节,其他三种能表示的数据它都能表示,但在chunk stream 的开始第一个chunk和头信息
中的时间戳后退(即值与上一个chunk相比减小,通常在回退播放的时候会出现这种情况)的时候必须采用这种格式。
- timestamp(时间戳):占用3个字节,因此它最多能表示到16777215=0xFFFFFF=2^24-1,当它
的值超过这个最大值时,这三个字节都置为1,这样实际的timestamp会转存到 Extended
Timestamp 字段中,接收端在判断timestamp字段24个位都为1时就会去Extended Timestamp
中解析实际的时间戳。 - message length(消息数据长度):占用3个字节,表示实际发送的消息的数据如音频帧、视频
帧等数据的长度,单位是字节。注意这里是Message的长度,也就是chunk属于的Message的总长
度,而不是chunk本身data的长度。 - message type id(消息的类型id):1个字节,表示实际发送的数据的类型,如8代表音频数据,
9代表视频数据。 - message stream id(消息的流id):4个字节,表示该chunk所在的流的ID,和Basic Header
的CSID一样,它采用小端存储方式。
二、Chunk Type(fmt) = 1: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 (coutinue) |message type id|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type为1时占用7个字节,省去了表示message stream id的4个字节,表示此chunk和上一次发的 chunk 所在的流相同,如果在
发送端和对端有一个流链接的时候可以尽量采取这种格式。
- timestamp delta:3 bytes,这里和type=0时不同,存储的是和上一个chunk的时间差。类似
上面提到的timestamp,当它的值超过3个字节所能表示的最大值时,三个字节都置为1,实际
的时间戳差值就会转存到Extended Timestamp字段中,接收端在判断timestamp delta字段24
个bit都为1时就会去Extended Timestamp 中解析实际的与上次时间戳的差值。 - 其他字段与上面的解释相同.
三、Chunk Type(fmt) = 2: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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type 为 2 时占用 3 个字节,相对于 type = 1 格式又省去了表示消息长度的3个字节和表示消息类型的1个字节,表示此 chunk
和上一次发送的 chunk 所在的流、消息的长度和消息的类型都相同。余下的这三个字节表示 timestamp delta,使用同type=1。
四、Chunk Type(fmt) = 3: 0 byte
type=3时,为0字节,表示这个chunk的Message Header和上一个是完全相同的。当它跟在type=0的chunk后面时,表示和前一
个 chunk 的时间戳都是相同。什么时候连时间戳都是相同呢?就是一个 Message 拆分成多个 chunk,这个 chunk 和上
一个 chunk 同属于一个 Message。而当它跟在 type = 1或 type = 2 的chunk后面时的chunk后面时,表示和前一个 chunk
的时间戳的差是相同的。比如第一个 chunk 的 type = 0,timestamp = 100,第二个 chunk 的 type = 2,
timestamp delta = 20,表示时间戳为 100 + 20 = 120,第三个 chunk 的 type = 3,表示 timestamp delta = 20,
时间戳为 120 + 20 = 140。
2.4 Extended Timestamp(扩展时间戳)
在 chunk 中会有时间戳 timestamp 和时间戳差 timestamp delta,并且它们不会同时存在,只有这两者之一大于3字节能表示的
最大数值 0xFFFFFF = 16777215 时,才会用这个字段来表示真正的时间戳,否则这个字段为 0。扩展时间戳占 4 个字节,
能表示的最大数值就是 0xFFFFFFFF = 4294967295。当扩展时间戳启用时,timestamp字段或者timestamp delta要全置为1,
而不是减去时间戳或者时间戳差的值。
2.5 chunk 示例
2.5.1 chunk 示例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 |
+---------+-----------------+-----------------+-------+---------+
Sample audio messages to be made into chunks
- 分析第一个 chunk:
- 首先包含第一个 Message 的 chunk 的 chunk type 为 0,因为它前面没有可参考的 chunk,timestamp 为 1000,表示时间戳。
- type 为 0 的 header 占用 11 个字节,假定 chunk stream id 为 3 < 127,因此 basic header 占用 1 个字节;
- 再加上 data 的 32 字节,因此第一个 chunk 共 44 字节 = 11 + 1 + 32 个字节。
- 分析第二个 chunk:
- 第二个 chunk 和第一个 chunk 的 cs id 和 chunk type id,以及 data 的长度都相同,因此采用 类型 2;
- 可知 timestamp delta = 1020 – 1000 = 20;
- 因此第二个 chunk 占用 36 = 3(message header) + 1(basic header) + 32
- 分析第三个 chunk:
- 第三个 chunk 和第二个 chunk 的 cs id ,chunk type id,以及 data 的长度和时间戳的差值都相同,因此采用 类型 3,省去全部的 Message Header 的信息;
- 因此占用 33 = 1 + 32
- 分析第四个 chunk:
- 第四个 chunk 和第三个 chunk 情况相同,也占用 33 = 1 + 32 个字节。
最后实际发送的chunk如下面表格所示,该表格展示了由此音频流产生的块信息。从第 3 条信息开始,数据传输达到最大优
化。每条消息的头部只增加了 1 字节长度。
.
+--------+---------+-----+------------+------- ---+------------+
| | 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) | | |
+--------+---------+-----+------------+-----------+------------+
Format of each of the chunks of audio messages
2.5.2 chunk 示例2
本示例展示了一条长消息,由于消息的长度超过了块的最大长度(128字节),此消息在传输时将被分割成若干个块。
.
+-----------+-------------------+-----------------+-----------------+
| | Message Stream ID | Message Type ID | Time | Length |
+-----------+-------------------+-----------------+-----------------+
| Msg # 1 | 12346 | 9 (video) | 1000 | 307 |
+-----------+-------------------+-----------------+-----------------+
Sample Message to be broken to chunks
由表格知 data 的长度 307 > 128,因此这个 Message 要分割成几个 chunk 发送:
- 第一个 chunk:type = 0,timestamp = 1000,承担 128 个字节的 data,因此共占用 140 = 11 + 1 + 128 个字节。
- 第二个 chunk:同样要发送 128 字节,其他字段(即Message Header 中的几个字段)都与第一个相同,因此采用 类型 3,共 129 = 1 + 128 字节。
- 第三个 chunk:要发送的 data 的长度为 307 – 128 – 128 = 51 字节,还是采用 类型 3,共 1 + 51 = 52 字节。
下面是消息分割后产生的块:
.
+-------+------+-----+-------------+-----------+------------+
| |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) | | |
+-------+------+-----+-------------+-----------+------------+
Format of each of the chunks
第一个块的头数据显示了消息的长度为 307 字节。
在这两个示例中,类型为 3 的块有两种使用方式。第一种是说明消息的继续。第二种是说明新消息的头信息可以由前面已经存
在的消息推到出来。
3. 协议控制消息
RTMP 块流使用消息类型 ID 1、2、3、5、6 作为控制消息。这些消息包含了必要的 RTMP 块流协议信息。
这些协议控制消息必须使用 0 作为消息流ID(作为已知的控制流ID),同时使用 2 作为块流ID。协议控制消息接收立即生效;
解析时,时间戳字段被忽略。
3.1 设置块大小 (1)
协议控制消息(1),设置块大小,被用来通知对方新的最大的块大小。
默认最大的块大小为 128 字节,客户端和服务器可以使用此消息来修改默认的块大小。例如,假设客户端想要发送的音频数据
大小为131 字节,而块大小为 128 字节。在这种情况下,客户端可以通知服务器新的块大小为 131 字节,然后就可以使用一
个块来发送完整的音频数据了。
最大的块大小至少为 128 字节,块至少携带 1 个字节的内容。通信的每一个方向(例如从客户端到服务器)拥有独立的块大小
设置。
.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0| chunk size (31 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Payload for the ‘Set Chunk Size’ protocol message
- 0:当前比特位必须为零。
- chunk size(31 bits): This field holds the new maximum chunk size, in bytes, which will be used for all of the sender\’s
subsequent chunks until further notice. Valid sizes are 1 to 2147483647(0x7FFFFFFF) inclusive; however, all sizes
greater than 16777215(0xFFFFFF) are equivalent since no chunk is larger than onemessage, and no message is larger than
16777215 bytes. - 块大小(31比特):本字段标识了新的最大块大小,以字节为单位,发送端之后将使用此值作为最大的块大小。本字段的
有效值为 1 – 2147483647(0x7FFFFFFF),由于消息的最大长度为 16777215(0xFFFFFF),而一个块最多只能携带一条消
息,因此本字段的实际有效值为 1~16777215(0xFFFFFF)。
send chunk size
3.2 中断消息 (2)
协议控制消息(2),中断消息,用来通知通信的对方,如果正在等待一条消息的部分块(已经接收了一部分),那么可以丢弃
之前已经接收到的块。通信的一方将接收到块流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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| chunk stream id (32 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Payload for the ‘Abort Message’ protocol message
- 块流ID(32比特):本字段包含了块流ID,用来标识哪个块流ID的消息将被丢弃。
3.3 应答(3)
客户端和服务器在接收到与接收窗口大小相等的数据后,必须发送应答消息给对方。窗口大小的定义为发送方在接收到接收方
的任何应答前,可以发送的最大数据量。本消息包含了序列号,序列号为截至目前接收到的数据总和,以字节为单位。
.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sequence number (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Payload for the ‘Acknowledgement’ protocol message
- 序列号(32比特):本字段包含了截止目前接收到的数据总和,以字节为单位。
3.4 应答窗口大小(5)
客户端和服务器发送这个消息来通知对方应答窗口的大小。发送方在发送了等于窗口大小的数据之后,等待接收对方的应答消
息(在接收到应答之前停止发送数据)。接收方必须发送应答消息,在会话开始时,或从上一次发送应答之后接收到了等于窗
口大小的数据。
.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgement Window size (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Payload for the ‘Window Acknowledgement Size’ protocol message
send ack_size
3.5 设置流带宽(6)
客户端和服务器发送此消息来说明对方的出口带宽限制。接收方以此来限制自己的出口带宽,即限制未被应答的消息数据大
小。接收到此消息的一方,如果窗口大小与上次发送的不一致,应该回复应答窗口大小的消息。
.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgement Window size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Limit Type |
+-+-+-+-+-+-+-+-+
Payload for the ‘Set Peer Bandwidth’ protocol message
限制类型的取值为下面之一:
- 硬限制(0):应该限制出口带宽为指明的窗口大小。
- 软限制(1):应该限制出口带宽为指明的窗口大小,或已经生效的小一点的窗口大小。
- 动态限制(2):如果上一次为硬限制,此消息被视为硬限制,否则忽略此消息。
send bandwidth
4. Command Message (17 或 20)
Command Message(命令消息,Message Type ID = 17 或 20):表示在客户端和服务器间传递的在对端执行某些操作的命令
消息,connect 表示连接对端,对端如果同意连接的话就会记录发送端信息并返回连接成功消息,publish 表示开始向对方
推流,接收端接收到命令后准备好接收对端发送的流信息。当信息使用 AMF0 编码时,Message Type ID = 20,AMF3 编码
时 为 17。
服务器和客户端之间使用 AMF 编码的命令消息交互。 一些命令消息被用来发送操作指令,比如 connect,createStream,
public,play,pause。另外一些命令消息被用来通知发送方请求命令的状态,比如 onstatus,result 等。一条命令消息
包括命令对称、交互 ID、包含相关参数的命令对象。服务器和客户端通过在创建的流中远程调用的方式,使用命令消息来
进行交互。
服务器发送给客户端的命令结构如下:
.
+--------------+----------+----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Command Name | String | _result or _error; indicates whether |
| | | the response is result or error. |
+--------------+----------+----------------------------------------+
| Transaction | Number | Transaction ID is 1 for connect |
| ID | | responses |
| | | |
+--------------+----------+----------------------------------------+
| Properties | Object | Name-value pairs that describe the |
| | | properties(fmsver etc.) of the |
| | | connection. |
+--------------+----------+----------------------------------------+
| Information | Object | Name-value pairs that describe the |
| | | response from|the server. ’code’, |
| | | ’level’, ’description’ are names of few|
| | | among such information. |
+--------------+----------+----------------------------------------+
命令执行过程中的消息流如下:
- 客户端发送连接命令给服务器,获得与服务器连接的实例。
- 服务器在接收到连接命令后,发送应答窗口大小的消息给客户端。同时与连接命令中接到的应用建立连接。
- 服务器发送设置流带宽消息给客户端。
- 客户端在接收并处理了设置流带宽的消息后,发送应答窗口大小的消息给服务器。
- 服务器接着发送开始流的用户控制消息给客户端。
- 服务器发送 result 命令消息给客户端,通知连接状态是成功或失败。命令消息中包含了事务ID。消息中还包含了像 FMS
版本之类的属性,以及级别,编码,描述,对象编码等信息。
+--------------+ +-------------+
| Client | | | Server |
+------+-------+ | +------+------+
| Handshaking done |
| | |
| | |
| | |
| | |
|----------- Command Message(connect) ------->|
| |
|<------- Window Acknowledgement Size --------|
| |
|<----------- Set Peer Bandwidth -------------|
| |
|-------- Window Acknowledgement Size ------->|
| |
|<------ User Control Message(StreamBegin) ---|
| |
|<------------ Command Message ---------------|
| (_result- connect response) |
| |
Message flow in the connect command
4.1 命令类型
客户端和服务器通过 AMF 编码的数据交换命令。发送者发送包含命令名称,事务ID,包含相关参数的命令对象的消息。例如,
通过连接命令中包含的 APP 参数来告诉服务器连接的对方是哪个客户端。接收方处理命令消息,并使用相同的事务ID应答。
应答字符串为 _result 或 _error 或方法名,例如 verifyClient 或 contactExternalServer。事务 ID 标明了应答指向的
命令。事务ID相当于 IMAP 协议或其他协议中的标签。命令字符串中的方法名,表明了发送端想要在接收端执行的方法。
下面的类对象被用来发送各种命令:
- NetConnection:服务器和客户端之间进行网络连接的一种高级表示形式。
- NetStream:代表了发送音频流,视频流,或其他相关数据的频道。当然还有一些像播放,暂停之类的命令,用来控制数
据流。
4.2 网络连接命令
网络连接管理着客户端和服务器之间的双向连接。另外,它也支持异步远程命令调用。
网络连接允许使用以下的命令:
- 连接 connect
- 调用 call
- 停止 close
- 创建流 createStream
4.2.1 connect: 连接
客户端发送连接命令给服务器,来获取一个和服务器通信的实例。客户端发送给服务器的命令结构如下:
.
+----------------+---------+---------------------------------------+
| Field Name | Type | Description |
+--------------- +---------+---------------------------------------+
| Command Name | String | Name of the command. Set to "connect".|
+----------------+---------+---------------------------------------+
| Transaction ID | Number | Always set to 1. |
+----------------+---------+---------------------------------------+
| Command Object | Object | Command information object which has |
| | | the name-value pairs. |
+----------------+---------+---------------------------------------+
| Optional User | Object | Any optional information |
| Arguments | | |
+----------------+---------+---------------------------------------+
下面是连接命令的命令对象里包含的键值对的说明:
.
+-----------+--------+-----------------------------+----------------+
| Property | Type | Description | Example Value |
+-----------+--------+-----------------------------+----------------+
| app | String | The Server application name | testapp |
| | | the client is connected to. | |
+-----------+--------+-----------------------------+----------------+
| flashver | String | Flash Player version. It is | FMSc/1.0 |
| | | the same string as returned | |
| | | by the ApplicationScript | |
| | | getversion () function. | |
+-----------+--------+-----------------------------+----------------+
| swfUrl | String | URL of the source SWF file | file://C:/ |
| | | making the connection. | FlvPlayer.swf |
+-----------+--------+-----------------------------+----------------+
| tcUrl | String | URL of the Server. | rtmp://local |
| | | It has the following format.| host:1935/test |
| | | protocol://servername:port/ | app/instance1 |
| | | appName/appInstance | |
+-----------+--------+-----------------------------+----------------+
| fpad | Boolean| True if proxy is being used.| true or false |
+-----------+--------+-----------------------------+----------------+
|audioCodecs| Number | Indicates what audio codecs | SUPPORT_SND |
| | | the client supports. | _MP3 |
+-----------+--------+-----------------------------+----------------+
|videoCodecs| Number | Indicates what video codecs | SUPPORT_VID |
| | | are supported. | _SORENSON |
+-----------+--------+-----------------------------+----------------+
|videoFunct-| Number | Indicates what special video| SUPPORT_VID |
|ion | | functions are supported. | _CLIENT_SEEK |
+-----------+--------+-----------------------------+----------------+
| pageUrl | String | URL of the web page from | http:// |
| | | where the SWF file was | somehost/ |
| | | loaded. | sample.html |
+-----------+--------+-----------------------------+----------------+
| object | Number | AMF encoding method. | AMF3 |
| Encoding | | | |
+-----------+--------+-----------------------------+----------------+
音频编码属性的可选值:
- 原始 PCM,ADPCM,MP3,NellyMoser(5,8,11,16,22,44kHz),AAC,Speex。
.
+----------------------+----------------------------+--------------+
| Codec Flag | Usage | Value |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_NONE | Raw sound, no compression | 0x0001 |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_ADPCM | ADPCM compression | 0x0002 |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_MP3 | mp3 compression | 0x0004 |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_INTEL | Not used | 0x0008 |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_UNUSED | Not used | 0x0010 |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_NELLY8 | NellyMoser at 8-kHz | 0x0020 |
| | compression | |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_NELLY | NellyMoser compression | 0x0040 |
| | (5, 11, 22, and 44 kHz) | |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_G711A | G711A sound compression | 0x0080 |
| | (Flash Media Server only) | |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_G711U | G711U sound compression | 0x0100 |
| | (Flash Media Server only) | |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_NELLY16 | NellyMouser at 16-kHz | 0x0200 |
| | compression | |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_AAC | Advanced audio coding | 0x0400 |
| | (AAC) codec | |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_SPEEX | Speex Audio | 0x0800 |
+----------------------+----------------------------+--------------+
| SUPPORT_SND_ALL | All RTMP-supported audio | 0x0FFF |
| | codecs | |
+----------------------+----------------------------+--------------+
视频编码属性的可选值:
- Sorenson,V1,On2,V2,H264.
.
+----------------------+----------------------------+--------------+
| Codec Flag | Usage | Value |
+----------------------+----------------------------+--------------+
| SUPPORT_VID_UNUSED | Obsolete value | 0x0001 |
+----------------------+----------------------------+--------------+
| SUPPORT_VID_JPEG | Obsolete value | 0x0002 |
+----------------------+----------------------------+--------------+
| SUPPORT_VID_SORENSON | Sorenson Flash video | 0x0004 |
+----------------------+----------------------------+--------------+
| SUPPORT_VID_HOMEBREW | V1 screen sharing | 0x0008 |
+----------------------+----------------------------+--------------+
| SUPPORT_VID_VP6 (On2)| On2 video (Flash 8+) | 0x0010 |
+----------------------+----------------------------+--------------+
| SUPPORT_VID_VP6ALPHA | On2 video with alpha | 0x0020 |
| (On2 with alpha | channel | |
| channel) | | |
+----------------------+----------------------------+--------------+
| SUPPORT_VID_HOMEBREWV| Screen sharing version 2 | 0x0040 |
| (screensharing v2) | (Flash 8+) | |
+----------------------+----------------------------+--------------+
| SUPPORT_VID_H264 | H264 video | 0x0080 |
+----------------------+----------------------------+--------------+
| SUPPORT_VID_ALL | All RTMP-supported video | 0x00FF |
| | codecs | |
+----------------------+----------------------------+--------------+
视频函数属性的可选值:
.
+----------------------+----------------------------+--------------+
| Function Flag | Usage | Value |
+----------------------+----------------------------+--------------+
| SUPPORT_VID_CLIENT | Indicates that the client | 1 |
| _SEEK | can perform frame-accurate | |
| | seeks. | |
+----------------------+----------------------------+--------------+
对象编码属性的可选值:
.
+----------------------+----------------------------+--------------+
| Encoding Type | Usage | Value |
+----------------------+----------------------------+--------------+
| AMF0 | AMF0 object encoding | 0 |
| | supported by Flash 6 and | |
| | later | |
+----------------------+----------------------------+--------------+
| AMF3 | AMF3 encoding from | 3 |
| | Flash 9 (AS3) | |
+----------------------+----------------------------+--------------+
示例
C -> S: 服务器接收客户端 connect 命令消息
S -> C: 服务器响应客户端 connect 成功消息
4.2.2 call: 调用
网络连接对象中包含的 call 方法,会在接收端执行远程过程调用(RPC)。被调用的 RPC 方法名作为 call 方法的参数传输。
从发送端到接收端的命令结构如下:
.
+--------------+----------+----------------------------------------+
|Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Procedure | String | Name of the remote procedure that is |
| Name | | called. |
+--------------+----------+----------------------------------------+
| Transaction | Number | If a response is expected we give a |
| | | transaction Id. Else we pass a value of|
| ID | | 0 |
+--------------+----------+----------------------------------------+
| Command | Object | If there exists any command info this |
| Object | | is set, else this is set to null type. |
+--------------+----------+----------------------------------------+
| Optional | Object | Any optional arguments to be provided |
| Arguments | | |
+--------------+----------+----------------------------------------+
应答的命令结构如下:
.
+--------------+----------+----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Command Name | String | Name of the command. |
| | | |
+--------------+----------+----------------------------------------+
| Transaction | Number | ID of the command, to which the |
| ID | | response belongs.
+--------------+----------+----------------------------------------+
| Command | Object | If there exists any command info this |
| Object | | is set, else this is set to null type. |
+--------------+----------+----------------------------------------+
| Response | Object | Response from the method that was |
| | | called. |
+------------------------------------------------------------------+
4.2.3 createStream: 创建流
客户端通过发送此消息给服务器来创建一个用于消息交互的逻辑通道。音频,视频,和元数据都是通过 createStream 命令创建
的流通道发布出去的。
NetConnection 是默认的交互通道,流 ID 为0. 协议和一部分命令消息,包含 createStream,都是使用默认的交互通道发布
的。
从客户端发送给服务器的命令结构如下:
.
+--------------+----------+----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Command Name | String | Name of the command. Set to |
| | | "createStream". |
+--------------+----------+----------------------------------------+
| Transaction | Number | Transaction ID of the command. |
| ID | | |
+--------------+----------+----------------------------------------+
| Command | Object | If there exists any command info this |
| Object | | is set, else this is set to null type. |
+--------------+----------+----------------------------------------+
从服务器发送给客户端的命令结构:
.
+--------------+----------+----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Command Name | String | _result or _error; indicates whether |
| | | the response is result or error. |
+--------------+----------+----------------------------------------+
| Transaction | Number | ID of the command that response belongs|
| ID | | to. |
+--------------+----------+----------------------------------------+
| Command | Object | If there exists any command info this |
| Object | | is set, else this is set to null type. |
+--------------+----------+----------------------------------------+
| Stream | Number | The return value is either a stream ID |
| ID | | or an error information object. |
+--------------+----------+----------------------------------------+
示例
C -> S: 服务器接收客户端 createStream 命令消息
C -> S: 服务器响应客户端 createStream 成功消息
4.3 网络流命令
网络流定义了通过网络连接把音频,视频和数据消息流在客户端和服务器之间进行交换的通道。一个网络连接对象可以有多个
网络流,进而支持多个数据流。
客户端可以通过网络流发送到服务器的命令如下:
- 播放play
- 播放2 play2
- 删除流 deleteStream
- 关闭流 closeStream
- 接收音频 receiveAudio
- 接收视频 receiveVideo
- 发布 publish
- 定位 seek
- 暂停 pause
服务器通过发送 onStatus 命令给客户端来通知网络流状态的更新。
.
+--------------+----------+----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Command Name | String | The command name "onStatus". |
+--------------+----------+----------------------------------------+
| Transaction | Number | Transaction ID set to 0. |
| ID | | |
+--------------+----------+----------------------------------------+
| Command | Null | There is no command object for |
| Object | | onStatus messages. |
+--------------+----------+----------------------------------------+
| Info Object | Object | An AMF object having at least the |
| | | following three properties: "level" |
| | | (String): the level for this message, |
| | | one of "warning", "status", or "error";|
| | | "code" (String): the message code, for |
| | | example "NetStream.Play.Start"; and |
| | | "description" (String): a human- |
| | | readable description of the message. |
| | | The Info object MAY contain other |
| | | properties as appropriate to the code. |
+--------------+----------+----------------------------------------+
Format of NetStream status message commands.
4.3.1 play: 播放
客户端发送此命令来通知服务器开始播放流。多次使用此命令可以创建一个播放列表。如果想要创建一个动态播放列表来在不
同的直播或点播流之间切换,可以通过多次调用播放命令,同时将 Reset 字段设置为 false。相反,如果想要立即播放指定
的流,先清理掉之前的播放队列,再调用播放命令,同时将 Reset 字段设置为 true。
从客户端发送给服务器的命令结构如下:
.
+--------------+----------+-----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+-----------------------------------------+
| Command Name | String | Name of the command. Set to "play". |
+--------------+----------+-----------------------------------------+
| Transaction | Number | Transaction ID set to 0. |
| ID | | |
+--------------+----------+-----------------------------------------+
| Command | Null | Command information does not exist. |
| Object | | Set to null type. |
+--------------+----------+-----------------------------------------+
| Stream Name | String | Name of the stream to play. |
| | | To play video (FLV) files, specify the |
| | | name of the stream without a file |
| | | extension (for example, "sample"). To |
| | | play back MP3 or ID3 tags, you must |
| | | precede the stream name with mp3: |
| | | (for example, "mp3:sample". To play |
| | | H.264/AAC files, you must precede the |
| | | stream name with mp4: and specify the |
| | | file extension. For example, to play the|
| | | file sample.m4v,specify "mp4:sample.m4v"|
| | | |
+--------------+----------+-----------------------------------------+
| Start | Number | An optional parameter that specifies |
| | | the start time in seconds. The default |
| | | value is -2, which means the subscriber |
| | | first tries to play the live stream |
| | | specified in the Stream Name field. If a|
| | | live stream of that name is not found,it|
| | | plays the recorded stream of the same |
| | | name. If there is no recorded stream |
| | | with that name, the subscriber waits for|
| | | a new live stream with that name and |
| | | plays it when available. If you pass -1 |
| | | in the Start field, only the live stream|
| | | specified in the Stream Name field is |
| | | played. If you pass 0 or a positive |
| | | number in the Start field, a recorded |
| | | stream specified in the Stream Name |
| | | field is played beginning from the time |
| | | specified in the Start field. If no |
| | | recorded stream is found, the next item |
| | | in the playlist is played. |
+--------------+----------+-----------------------------------------+
| Duration | Number | An optional parameter that specifies the|
| | | duration of playback in seconds. The |
| | | default value is -1. The -1 value means |
| | | a live stream is played until it is no |
| | | longer available or a recorded stream is|
| | | played until it ends. If you pass 0, it |
| | | plays the single frame since the time |
| | | specified in the Start field from the |
| | | beginning of a recorded stream. It is |
| | | assumed that the value specified in |
| | | the Start field is equal to or greater |
| | | than 0. If you pass a positive number, |
| | | it plays a live stream for |
| | | the time period specified in the |
| | | Duration field. After that it becomes |
| | | available or plays a recorded stream |
| | | for the time specified in the Duration |
| | | field. (If a stream ends before the |
| | | time specified in the Duration field, |
| | | playback ends when the stream ends.) |
| | | If you pass a negative number other |
| | | than -1 in the Duration field, it |
| | | interprets the value as if it were -1. |
+--------------+----------+-----------------------------------------+
| Reset | Boolean | An optional Boolean value or number |
| | | that specifies whether to flush any |
| | | previous playlist. |
+--------------+----------+-----------------------------------------+
play 播放命令执行流程:
.
+-------------+ +----------+
| Play Client | | | Server |
+------+------+ | +-----+----+
| Handshaking and Application |
| connect done |
| | |
| | |
| | |
| | |
---+---- |------Command Message(createStream) ----->|
Create| | |
Stream| | |
---+---- |<---------- Command Message --------------|
| (_result- createStream response) |
| |
---+---- |------ Command Message (play) ----------->|
| | |
| |<------------ SetChunkSize --------------|
| | |
| |<---- User Control (StreamIsRecorded) ----|
Play | | |
| |<---- User Control (StreamBegin) ---------|
| | |
| |<--Command Message(onStatus-play reset) --|
| | |
| |<--Command Message(onStatus-play start) --|
| | |
| |<-------------Audio Message---------------|
| | |
| |<-------------Video Message---------------|
| | | |
|
Keep receiving audio and video stream till finishes
Message flow in the play command
命令执行过程中的消息流如下:
- 当客户端接收到服务器返回的 createStream 成功的消息时,开始发送播放命令。
- 服务器接收到播放命令后,发送设置块大小的消息。
- 服务器发送一条用户控制消息,消息内包含了 StreamlsRecorded 事件和流ID。事件类型位于消息的前 2 个字节,流 ID
位于消息的最后 4 个字节。 - 服务器发送一条用户控制消息,消息内包含了 StreamBegin 事件,用于通知客户端开始播放流。
- 如果客户端已经成功发送了播放命令,那么服务器发送两条 onStatus 命令给客户端,命令的内容为 NetStream.Play.Start
和 NetStream.Play.Reset。服务器只有在客户端发送了设置有重置标签的播放命令后,才能发送 NetStream.Play.Reset
命令。如果服务器找不到客户端请求播放的流,那么发送 NetStream.Play.StreamNotFound 命令给客户端。 - 之后,服务器发送音频和视频数据给客户端。
4.3.2 play2: 播放2
与播放命令的不同之处在于,播放 2 命令可以在不修改播放内容时间线的前提下切换到一个不同码率的流。服务器包含了多个
不同码率的流文件用于支持客户端的播放 2 请求。
从客户端发送给服务器的命令结构如下:
.
The command structure from the client to the server is as follows:
+--------------+----------+----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Command Name | String | Name of the command, set to "play2". |
+--------------+----------+----------------------------------------+
| Transaction | Number | Transaction ID set to 0. |
| ID | | |
+--------------+----------+----------------------------------------+
| Command | Null | Command information does not exist. |
| Object | | Set to null type. |
+--------------+----------+----------------------------------------+
| Parameters | Object | An AMF encoded object whose properties |
| | | are the public properties described |
| | | for the flash.net.NetStreamPlayOptions |
| | | ActionScript object. |
+--------------+----------+----------------------------------------+
有关 NetStreamPlayOptions 对象的公开属性的说明详见 AS3 语言的文档。
此命令的消息流如下所示:
.
+--------------+ +-------------+
| Play2 Client | | | Server |
+--------+-----+ | +------+------+
| Handshaking and Application |
| connect done |
| | |
| | |
| | |
| | |
---+---- |---- Command Message(createStream) --->|
Create | | |
Stream | | |
---+---- |<---- Command Message (_result) -------|
| |
---+---- |------ Command Message (play) -------->|
| | |
| |<------------ SetChunkSize ------------|
| | |
| |<--- UserControl (StreamIsRecorded)----|
Play | | |
| |<------- UserControl (StreamBegin)-----|
| | |
| |<--Command Message(onStatus-playstart)-|
| | |
| |<---------- Audio Message -------------|
| | |
| |<---------- Video Message -------------|
| | |
| |
---+---- |-------- Command Message(play2) ------>|
| | |
| |<------- Audio Message (new rate) -----|
Play2 | | |
| |<------- Video Message (new rate) -----|
| | | |
| | | |
| Keep receiving audio and video stream till finishes
|
Message flow in the play2 command
4.3.3 deleteStream: 删除流
如果需要销毁网络流对象,可以通过网络流发送删除流消息给服务器。
客户端发送给服务器的命令结构如下:
.
The command structure from the client to the server is as follows:
+--------------+----------+----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Command Name | String | Name of the command, set to |
| | | "deleteStream". |
+--------------+----------+----------------------------------------+
| Transaction | Number | Transaction ID set to 0. |
| ID | | |
+--------------+----------+----------------------------------------+
| Command | Null | Command information object does not |
| Object | | exist. Set to null type. |
+--------------+----------+----------------------------------------+
| Stream ID | Number | The ID of the stream that is destroyed |
| | | on the server. |
+--------------+----------+----------------------------------------+
服务器接收到此消息后,不做任何回复。
4.3.4 receiveAudio: 接收音频
网络流发送此消息通知服务器,是否要发送音频数据给客户端。
.
The command structure from the client to the server is as follows:
+--------------+----------+----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Command Name | String | Name of the command, set to |
| | | "receiveAudio". |
+--------------+----------+----------------------------------------+
| Transaction | Number | Transaction ID set to 0. |
| ID | | |
+--------------+----------+----------------------------------------+
| Command | Null | Command information object does not |
| Object | | exist. Set to null type. |
+--------------+----------+----------------------------------------+
| Bool Flag | Boolean | true or false to indicate whether to |
| | | receive audio or not. |
+--------------+----------+----------------------------------------+
如果服务器接收到带有 fasle 标签的消息后,不做任何回复。如果接收到带有 true 标签的消息,服务器回复带有
NetStream.Seek.Notify 和 NetStream.Play.Start 的消息给客户端。
4.3.5 receiveVideo: 接收视频
网络流发送此消息通知服务器,是否要发送视频数据给客户端。
客户端发送给服务器的命令结构如下:
.
+--------------+----------+----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Command Name | String | Name of the command, set to |
| | | "receiveVideo". |
+--------------+----------+----------------------------------------+
| Transaction | Number | Transaction ID set to 0. |
| ID | | |
+--------------+----------+----------------------------------------+
| Command | Null | Command information object does not |
| Object | | exist. Set to null type. |
+--------------+----------+----------------------------------------+
| Bool Flag | Boolean | true or false to indicate whether to |
| | | receive video or not. |
+--------------+----------+----------------------------------------+
如果服务器接收到带有 false 标签的消息后,不做任何回复。如果接收到带有 true 标签的消息,服务器回复带有
NetStream.Seek.Notify 和 NetStream.Play.Start 的消息给客户端。
4.3.6 publish: 发布
客户端发送此消息,用来发布一个有名字的流到服务器。其他客户端可以使用此流名来播放流,接收发布的音频,视频,
以及其他数据消息。
客户端发送给服务器的命令结构如下:
.
+--------------+----------+----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Command Name | String | Name of the command, set to "publish". |
+--------------+----------+----------------------------------------+
| Transaction | Number | Transaction ID set to 0. |
| ID | | |
+--------------+----------+----------------------------------------+
| Command | Null | Command information object does not |
| Object | | exist. Set to null type. |
+--------------+----------+----------------------------------------+
| Publishing | String | Name with which the stream is |
| Name | | published. |
+--------------+----------+----------------------------------------+
| Publishing | String | Type of publishing. Set to "live", |
| Type | | "record", or "append". |
| | | record: The stream is published and the|
| | | data is recorded to a new file.The file|
| | | is stored on the server in a |
| | | subdirectory within the directory that |
| | | contains the server application. If the|
| | | file already exists, it is overwritten.|
| | | append: The stream is published and the|
| | | data is appended to a file. If no file |
| | | is found, it is created. |
| | | live: Live data is published without |
| | | recording it in a file. |
+--------------+----------+----------------------------------------+
服务器接收到此消息后,回复 onStatus 命令来标记发布的开始。
示例1:发布录制的视频
此示例阐述了发布者如果发布视频流到服务器。其他客户端可以订阅并播放此视频流。
.
+--------------------+ +-----------+
| Publisher Client | | | Server |
+----------+---------+ | +-----+-----+
| Handshaking Done |
| | |
| | |
---+---- |----- Command Message(connect) ----->|
| | |
| |<----- Window Acknowledge Size ------|
Connect | | |
| |<-------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) ------>|
| | |
| |<------User Control(StreamBegin)-----|
| | |
| |-----Data Message (Metadata)-------->|
| | |
Publishing| |------------ Audio Data ------------>|
Content | | |
| |------------ SetChunkSize ---------->|
| | |
| |<----------Command Message ----------|
| | (_result- publish result) |
| | |
| |------------- Video Data ----------->|
| | | |
| | | |
| Until the stream is complete |
| | |
Message flow in publishing a video stream
示例2:从录制的流发布元数据
本示例展示了发布元数据的消息交换过程。
.
+------------------+ +---------+
| Publisher Client | | | FMS |
+---------+--------+ | +----+----+
| Handshaking and Application |
| connect done |
| | |
| | |
---+--- |---Command Messsage(createStream) -->|
Create | | |
Stream | | |
---+--- |<---------Command Message------------|
| (_result - command response) |
| |
---+--- |---- Command Message(publish) ------>|
Publishing | | |
metadata | |<------ UserControl(StreamBegin)-----|
from file | | |
| |-----Data Message (Metadata) ------->|
| |
Publishing metadata
4.3.7 seek: 定位
客户端发送此消息来定位多媒体文件或播放列表的偏移(以毫秒为单位)。
客户端发送给服务器的命令结构如下:
.
+--------------+----------+----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Command Name | String | Name of the command, set to "seek". |
+--------------+----------+----------------------------------------+
| Transaction | Number | Transaction ID set to 0. |
| ID | | |
+--------------+----------+----------------------------------------+
| Command | Null | There is no command information object |
| Object | | for this command. Set to null type. |
+--------------+----------+----------------------------------------+
| milliSeconds | Number | Number of milliseconds to seek into |
| | | the playlist. |
+--------------+----------+----------------------------------------+
当定位完成后,服务器回复 NetStream.Seek.Notify 状态消息给客户端。如果定位失败,将回复 _error 消息。
4.3.8 pause: 暂停
客户端发送此消息来通知服务器暂停或开始播放。
客户端发送给服务器的命令结构如下:
.
+--------------+----------+----------------------------------------+
| Field Name | Type | Description |
+--------------+----------+----------------------------------------+
| Command Name | String | Name of the command, set to "pause". |
+--------------+----------+----------------------------------------+
| Transaction | Number | There is no transaction ID for this |
| ID | | command. Set to 0. |
+--------------+----------+----------------------------------------+
| Command | Null | Command information object does not |
| Object | | exist. Set to null type. |
+--------------+----------+----------------------------------------+
|Pause/Unpause | Boolean | true or false, to indicate pausing or |
| Flag | | resuming play |
+--------------+----------+----------------------------------------+
| milliSeconds | Number | Number of milliseconds at which the |
| | | the stream is paused or play resumed. |
| | | This is the current stream time at the |
| | | Client when stream was paused. When the|
| | | playback is resumed, the server will |
| | | only send messages with timestamps |
| | | greater than this value. |
+--------------+----------+----------------------------------------+
当流暂停成功,服务器发送 NetStream.Pause.Notify 状态消息给客户端,如果流未暂停,服务器发送
NetStream.Unpause.Notify 状态消息给客户端。如果暂停失败,则发送 _error 消息。
5. Data Message ( 15 或 18)
Data Message(数据消息,Message Type ID = 15 或 18):传递一些元数据(Metadata,比如视频名,分辨率等等)或者用
户自定义的一些消息。当信息使用 AMF0 编码时,Message Type ID = 18,AMF3 则为15.
元数据包含了(音视频)数据的细节信息,像流的创建时间,时间点,主题等等.
6. Shared Object Message (16 或 19)
Shared Object Message(共享消息,Message Type ID = 16 或 19):表示一个 Flash 类型的对象,由键值对的集合组成,用
于多客户端,多实例时使用。AMF0 时 Message Type ID 为 19,AMF3 为 16。每个消息可以包含多个事件。
共享消息格式:
.
+------+------+-------+-----+-----+------+-----+ +-----+------+-----+
|Header|Shared|Current|Flags|Event|Event |Event|.|Event|Event |Event|
| |Object|Version| |Type |data |data |.|Type |data |data |
| |Name | | | |length| |.| |length| |
+------+------+-------+-----+-----+------+-----+ +-----+------+-----+
| |
|<- - - - - - - - - - - - - - - - - - - - - - - - - - - - - >|
| AMF Shared Object Message body |
The shared object message format
下面是共享消息支持的事件类型:
.
+---------------+--------------------------------------------------+
| Event | Description |
+---------------+--------------------------------------------------+
| Use(=1) | The client sends this event to inform the server |
| | about the creation of a named shared object. |
+---------------+--------------------------------------------------+
| Release(=2) | The client sends this event to the server when |
| | the shared object is deleted on the client side. |
+---------------+--------------------------------------------------+
| Request Change| The client sends this event to request that the |
| (=3) | change the value associated with a named |
| | parameter of the shared object. |
+---------------+--------------------------------------------------+
| Change (=4) | The server sends this event to notify all |
| | clients, except the client originating the |
| | request, of a change in the value of a named |
| | parameter. |
+---------------+--------------------------------------------------+
| Success (=5) | The server sends this event to the requesting |
| | client in response to RequestChange event if the |
| | request is accepted. |
+---------------+--------------------------------------------------+
| SendMessage | The client sends this event to the server to |
| (=6) | broadcast a message. On receiving this event, |
| | the server broadcasts a message to all the |
| | clients, including the sender. |
+---------------+--------------------------------------------------+
| Status (=7) | The server sends this event to notify clients |
| | about error conditions. |
+---------------+--------------------------------------------------+
| Clear (=8) | The server sends this event to the client to |
| | clear a shared object. The server also sends |
| | this event in response to Use event that the |
| | client sends on connect. |
+---------------+--------------------------------------------------+
| Remove (=9) | The server sends this event to have the client |
| | delete a slot. |
+---------------+--------------------------------------------------+
| Request Remove| The client sends this event to have the client |
| (=10) | delete a slot. |
+---------------+--------------------------------------------------+
| Use Success | The server sends this event to the client on a |
| (=11) | successful connection. |
+---------------+--------------------------------------------------+
示例:广播共享对象消息
此示例阐述了流创建过程中的消息交换,和共享对象的变换过程。同时还展示了共享对象消息广播的处理过程。
.
+----------+ +----------+
| Client | | | Server |
+-----+----+ | +-----+----+
| Handshaking and Application |
| connect done |
| | |
| | |
| | |
| | |
Create and ---+---- |---- Shared Object Event(Use)---->|
connect | | |
Shared Object | | |
---+---- |<---- Shared Object Event---------|
| (UseSuccess,Clear) |
| |
---+---- |------ Shared Object Event ------>|
Shared object | | (RequestChange) |
Set Property | | |
---+---- |<------ Shared Object Event ------|
| (Success) |
| |
---+---- |------- Shared Object Event ----->|
Shared object| | (SendMessage) |
Message | | |
Broadcast ---+---- |<------- Shared Object Event -----|
| (SendMessage) |
| |
| |
Shared object message broadcast
7. Audio Message (8)
Audio Message(音频信息,Message Type ID = 8):音频数据
8. Video Message (9)
Video Message(视频信息,Message Type ID = 9):视频数据
9. Aggregate Message (22)
Aggregate Message(聚集信息,Message Type ID = 22):多个 RTMP 子消息的集合。
集合消息由消息头和消息内容组成。
消息内容由子消息组成,子消息由消息头,消息数据,回放指针组成。
.
+---------+-------------------------+
| Header | Aggregate Message body |
+---------+-------------------------+
The Aggregate Message format
+--------+-------+---------+--------+-------+---------+ - - - -
|Header 0|Message|Back |Header 1|Message|Back |
| |Data 0 |Pointer 0| |Data 1 |Pointer 1|
+--------+-------+---------+--------+-------+---------+ - - - -
The Aggregate Message body format
集合消息的消息流 ID 覆盖此消息内的子消息流的ID。
集合消息和第一个子消息的时间戳之间的偏移量,用来将子消息的时间戳处理为流的时间刻度。每个子消息的时间戳可以通过
添加偏移量来处理为正常的流时间。第一个子消息的时间戳应该和集合消息的时间戳相同,因此偏移量应该为零。
方向指针包含了以前的消息(包含头信息)的大小。集合消息包含此字段,一是为了适配 FLV 文件格式,二是为了回放定位。
使用集合消息有如下几种优势:
- 块流在一个块内至多可以携带一条完整的消息。使用集合消息之后,不仅可以增加块大小,同时还减少了发送的块数
量; - 集合消息的子消息可以连续的存储在内存中。当系统调用网络发送数据时更高效。
10. User Control Message Events (4)
User Control Message Events(用户控制消息,Message Type ID = 4):告知对方执行该信息中包含的用户控制事件,比如
Stream Begin 事件告知对方流信息开始传输。和前面提到的协议控制信息(Protocol Control Message)不同,用户控制消息
是在 RTMP 协议层的,而不是在 RTMP chunk 流协议层,这个很容易弄混。该信息在 chunk 流中发送时,Message Stream ID
= 0,Chunk Strean id = 2,Message type id = 4.
用户控制消息应该使用 0 作为消息流 ID,当通过 RTMP 块流发送此消息时,块流 ID 为 2. RTMP 流中的用户控制消息在接收
时立即生效,消息中的时间戳被忽略。
客户端或服务器发送此消息用来通知对方用户控制事件。此消息包含事件类型和事件数据。
.
+------------------------------+------------------------
| Event Type (16 bits) | Event Data
+------------------------------+-------------------------
Payload for the ‘User Control’ protocol message
用户控制消息的前 2 个字节数据用来标识事件类型。事件类型后面是事件数据。事件数据字段是可变的。由于此消息是通过
RTMP 块流层发送的,块大小的最大值应该满足在一个块里包含此消息。
用户控制事件支持如下类型:
+---------------+--------------------------------------------------+
| Event | Description |
+---------------+--------------------------------------------------+
|Stream Begin | The server sends this event to notify the client |
| (=0) | that a stream has become functional and can be |
| | used for communication. By default, this event |
| | is sent on ID 0 after the application connect |
| | command is successfully received from the |
| | client. The event data is 4-byte and represents |
| | the stream ID of the stream that became |
| | functional. |
+---------------+--------------------------------------------------+
| Stream EOF | The server sends this event to notify the client |
| (=1) | that the playback of data is over as requested |
| | on this stream. No more data is sent without |
| | issuing additional commands. The client discards |
| | the messages received for the stream. The |
| | 4 bytes of event data represent the ID of the |
| | stream on which playback has ended. |
+---------------+--------------------------------------------------+
| StreamDry | The server sends this event to notify the client |
| (=2) | that there is no more data on the stream. If the |
| | server does not detect any message for a time |
| | period, it can notify the subscribed clients |
| | that the stream is dry. The 4 bytes of event |
| | data represent the stream ID of the dry stream. |
+---------------+--------------------------------------------------+
| SetBuffer | The client sends this event to inform the server |
| Length (=3) | of the buffer size (in milliseconds) that is |
| | used to buffer any data coming over a stream. |
| | This event is sent before the server starts |
| | processing the stream. The first 4 bytes of the |
| | event data represent the stream ID and the next |
| | 4 bytes represent the buffer length, in |
| | milliseconds. |
+---------------+--------------------------------------------------+
| StreamIs | The server sends this event to notify the client |
| Recorded (=4) | that the stream is a recorded stream. The |
| | 4 bytes event data represent the stream ID of |
| | the recorded stream. |
+---------------+--------------------------------------------------+
| PingRequest | The server sends this event to test whether the |
| (=6) | client is reachable. Event data is a 4-byte |
| | timestamp, representing the local server time |
| | when the server dispatched the command. The |
| | client responds with PingResponse on receiving |
| | MsgPingRequest. |
+---------------+--------------------------------------------------+
| PingResponse | The client sends this event to the server in |
| (=7) | response to the ping request. The event data is |
| | a 4-byte timestamp, which was received with the |
| | PingRequest request. |
+---------------+--------------------------------------------------+