RTMP协议详解及实例分析

1、简介

       RTMP协议是Real Time Message Protocol(实时信息传输协议)的缩写,它是由Adobe公司提出的一种应用层的协议,用来解决多媒体数据传输流的多路复用(Multiplexing)和分包(packetizing)的问题。实现通常对不同类型的消息分配不同的优先级,当运载能力有限时,这会影响等待流传输的消息的次序。

       RTMP协议是应用层协议,是要靠底层可靠的传输层协议(通常是TCP)来保证信息传输的可靠性的。在基于传输层协议的链接建立完成后,RTMP协议也要客户端和服务器通过“握手”来建立基于传输层链接之上的RTMP Connection链接,在Connection链接上会传输一些控制信息,如SetChunkSize,SetACKWindowSize。其中CreateStream命令会创建一个Stream链接,用于传输具体的音视频数据和控制这些信息传输的命令信息。RTMP协议传输时会对数据做自己的格式化,这种格式的消息我们称之为RTMP Message,而实际传输的时候为了更好地实现多路复用、分包和信息的公平性,发送端会把Message划分为带有Message ID的Chunk,每个Chunk可能是一个单独的Message,也可能是Message的一部分,在接收端会根据chunk中包含的data的长度,message id和message的长度把chunk还原成完整的Message,从而实现信息的收发。

 

2、定义

  • Payload (有效载荷)一个数据包或者其它传输单元中运载的基本必要数据,例如音频采样或者压缩的视频数据。
  • Packet (数据包)一个数据包由一个固定头和有效载荷数据构成。一些底层协议可能会要求对数据包定义封装。
  • Port (端口)"传输协议用以区分开指定一台主机的不同目的地的一个抽象。TCP/IP 使用小的正整数对端口进行标识。" OSI 传输层使用的运输选择器 (TSEL) 相当于端口。
  • Transport address (传输地址)用以识别传输层端点的网络地址和端口的组合,例如一个 IP 地址和一个 TCP 端口。数据包由一个源传输地址传送到一个目的传输地址。
  • Message stream (消息流)通信中消息流通的一个逻辑通道。
  • Message stream ID (消息流 ID)每个消息有一个关联的 ID,使用 ID 可以识别出流通中的消息流。
  • Chunk ()消息的一段。消息在网络发送之前被拆分成很多小的部分。块可以确保端到端交付所有消息有序 timestamp,即使有很多不同的流。
  • Chunk stream (块流)通信中允许块流向一个特定方向的逻辑通道。块流可以从客户端流向服务器,也可以从服务器流向客户端。
  • Chunk stream ID (块流 ID)每个块有一个关联的 ID,使用 ID 可以识别出流通中的块流。
  • Multiplexing (合成)将独立的音频/视频数据合成为一个连续的音频/视频流的加工,这样可以同时发送几个视频和音频。
  • DeMultiplexing (分解)Multiplexing 的逆向处理,将交叉的音频和视频数据还原成原始音频和视频数据的格式。
  • Remote Procedure Call (RPC 远程方法调用)允许客户端或服务器调用对端的一个子程序或者程序的请求。
  • Metadata (元数据)关于数据的一个描述。一个电影的 metadata 包括电影标题、持续时间、创建时间等等。
  • Application Instance (应用实例)服务器上应用的实例,客户端可以连接这个实例并发送连接请求。
  • Action Message Format (AMF 动作消息格式协议)一个用于序列化 ActionScript 对象图的紧凑的二进制格式。AMF 有两个版本:AMF 0 [AMF0] 和 AMF 3 [AMF3]。

 

3、字节序、对齐和时间格式

(1)字节序

        所有整数型属性以网络字节顺序传输,字节 0 代表第一个字节,零位是一个单词或字段最常用的有效位。字节序通常是大端排序。关于传输顺序的更多细节描述参考 IP 协议[RFC0791]。

(2)对齐

       除非另有规定,RTMP 中的所有数据都是字节对准的;例如,一个十六位的属性可能会在一个奇字节偏移上。填充后,填充字节应该有零值。

(3)时间格式

       RTMP 中的 Timestamps 以一个整数形式给出,表示一个未指明的时间点。典型地,每个流会以一个为 0 的 timestamp 起始,但这不是必须的,只要双端能够就时间点达成一致。注意这意味着任意不同流 (尤其是来自不同主机的) 的同步需要 RTMP 之外的机制。

  因为 timestamp 的长度为 32 位,每隔 49 天 17 小时 2 分钟和 47.296 秒就要重来一次。因为允许流连续传输,有可能要多年,RTMP 应用在处理 timestamp 时应该使用序列码算法 [RFC1982],并且能够处理无限循环。例如,一个应用假定所有相邻的 timestamp 都在 2^31 - 1 毫秒之内,因此 10000 在 4000000000 之后,而 3000000000 在 4000000000 之前。

  timestamp 也可以使用无符整数定义,相对于前面的 timestamp。timestamp 的长度可能会是 24 位或者 32 位。

 

4、分块

       RTMP中创建的每个块都有一个唯一 ID 对其进行关联,这个 ID 叫做 chunk stream ID (块流 ID)。这些块通过网络进行传输。传递时,每个块必须被完全发送才可以发送下一块。在接收端,这些块被根据块流 ID 被组装成消息

  分块允许上层协议将大的消息分解为更小的消息,例如,防止体积大的但优先级小的消息 (比如视频) 阻碍体积较小但优先级高的消息 (比如音频或者控制命令)。

  分块也让我们能够使用较小开销发送小消息,因为块头包含包含在消息内部的信息压缩提示。

  块的大小是可以配置的。它可以使用一个设置块大小的控制消息进行设置 。更大的块大小可以降低 CPU 开销,但在低带宽连接时因为它的大量的写入也会延迟其他内容的传递。更小的块不利于高比特率的流化。所以块的大小设置取决于具体情况。

(1)块格式

       每个块包含一个头和数据体。块头包含三个部分:

RTMP协议详解及实例分析_第1张图片

 

  • Basic Header (基本头,1 3 个字节)这个字段对块流 ID 和块类型进行编码。块类型决定了消息头的编码格式。(这一字段的) 长度完全取决于块流 ID,因为块流 ID 是一个可变长度的字段。RTMP 协议最多支持 65597 个流,流 ID 范围 3 - 65599。
  • Message Header (消息头,037,或者 11 个字节)这一字段对正在发送的消息 (不管是整个消息,还是只是一小部分) 的信息进行编码。这一字段的长度可以使用块头中定义的块类型进行决定。消息头有四种不同的格式,由块基本头中的 "fmt" 字段进行选择。

类型 0 11 个字节。这一类型必须用在块流的起始位置,和流 timestamp 重来的时候 (比如,重置)。

类型 1 7 个字节。不包含消息流 ID;这一块使用前一块一样的流 ID。可变长度消息的流 (例如,一些视频格式) 应该在第一块之后使用这一格式表示之后的每个新消息。

类型 2 3 个字节。既不包含流 ID 也不包含消息长度;这一块具有和前一块相同的流 ID 和消息长度。具有不变长度的消息 (例如,一些音频和数据格式) 应该在第一块之后使用这一格式表示之后的每个新消息。

类型 3 没有消息头。流 ID、消息长度以及 timestamp delta 等字段都不存在;这种类型的块使用前面块一样的块流 ID。当单一一个消息被分割为多块时,除了第一块的其他块都应该使用这种类型。

  • Extended Timestamp (扩展 timestamp0 4 字节)这一字段是否出现取决于块消息头中的 timestamp 或者 timestamp delta 字段。扩展 timestamp 字段主要用于对大于 16777215 (0xFFFFFF) 的 timestamp 或者 timestamp delta 进行编码;也就是,对于不适合于在 24 位的类型 0、1 和 2 的块里的 timestamp 和 timestamp delta 编码。
  • Chunk Data (有效大小)当前块的有效负载,相当于定义的最大块大小。

 

5、RTMP消息格式

       RTMP 协议设计使用 RTMP 块流,可以使用其他任意传输协议对消息进行发送。服务器端和客户端通过网络发送 RTMP 消息来进行彼此通信。消息可以包含音频、视频、数据,或者其他消息。RTMP 消息有两部分:头和它的有效载荷。

(1)消息头

 

 消息头包含以下:

RTMP协议详解及实例分析_第2张图片

       Timestamp(时间戳):对于一个类型 1 或者类型 2 的块,前一块的 timestamp 和当前块的 timestamp 的区别在这里发送。如果 delta 大于或者等于 16777215 (十六进制 0xFFFFFF),那么这一字段必须是为 16777215,表示具有扩展 timestamp 字段来对整个 32 位 delta 进行编码。否则的话,这一字段应该是为具体 delta。

       Length (长度)对于一个类型 0 或者类型 1 的块,消息长度在这里进行发送。注意这通常不同于块的有效载荷的长度。块的有效载荷代表所有的除了最后一块的最大块大小,以及剩余的 (也可能是小消息的整个长度) 最后一块。

       Message Type Id (消息类型)对于类型 0 或者类型 1 的块,消息的类型在这里发送。类型 ID 1 - 6 被保留用于协议控制消息。这些传播信息的消息由 RTMP 块流协议和上层协议共同处理。其他的所有类型 ID 可用于上层协议,它们被 RTMP 块流处理为不透明值。事实上,RTMP 块流中没有任何地方要把这些值当做类型使用;所有消息必须是同一类型,或者应用使用这一字段来区分同步跟踪,而不是类型。

       Message Stream Id (消息流 ID)对于一个类型为 0 的块,保存消息流 ID。消息流 ID 以小端格式保存。所有同一个块流下的消息都来自同一个消息流。当可以将不同的消息流组合进同一个块流时,这种方法比头压缩的做法要好。但是,当一个消息流被关闭而其他的随后另一个是打开着的,就没有理由将现有块流以发送一个新的类型 0 的块进行复用了。message stream (消息流) ID 可以使任意值。合并到同一个块流的不同的消息流是根据各自的消息流 ID 进行分解。除此之外,对 RTMP 块流而言,这是一个不透明的值。

(2)有效载荷

       消息的另一个部分就是有效负载,这是这个消息所包含的实际内容。例如,它可以是一些音频样本或者压缩的视频数据。

例:

RTMP协议详解及实例分析_第3张图片

 

6、RTMP消息类型

       服务器端和客户端交换的不同消息类型包括用于发送音频数据的音频消息、用于发送视频数据的视频消息、用于发送任意用户数据的数据消息、共享对象消息以及命令消息。共享对象消息提供了一个通用的方法来管理多用户和一台服务器之间的分布式数据。命令消息在客户端和服务器端传输 AMF 编码的命令。客户端或者服务器端可以通过使用命令消息和对端通信的流请求远程方法调用 (RPC)。

       服务器端和客户端通过在网络中发送消息来进行彼此通信。消息可以是任何类型,包含音频消息,视频消息,命令消息,共享对象消息,数据消息,以及用户控制消息。

(1)命令消息(20,17)

       命令消息在客户端和服务器端传递 AMF 编码的命令。这些消息被分配以消息类型值为 20 以进行 AMF0 编码,消息类型值为 17 以进行 AMF3 编码。这些消息发送以进行一些操作,比如,连接,创建流,发布,播放,对端暂停。命令消息,像 onstatus、result 等等,用于通知发送者请求的命令的状态。一个命令消息由命令名、事务 ID 和包含相关参数的命令对象组成。一个客户端或者一个服务器端可以通过和对端通信的流使用这些命令消息请求远程调用 (RPC)。

(2)数据消息 (18, 15)

       客户端或者服务器端通过发送这些消息以发送元数据或者任何用户数据到对端。元数据包括数据 (音频,视频等等) 的详细信息,比如创建时间,时长,主题等等。这些消息被分配以消息类型为 18 以进行 AMF0 编码和消息类型 15 以进行 AMF3 编码。

(3)共享对象消息 (19, 16)

       所谓共享对象其实是一个 Flash 对象 (一个名值对的集合),这个对象在多个不同客户端、应用实例中保持同步。消息类型 19 用于 AMF0 编码、16 用于 AMF3 编码都被为共享对象事件保留。每个消息可以包含有不同事件。

RTMP协议详解及实例分析_第4张图片

 

支持以下事件类型:

事件

描述

Use(=1)

客户端发送这一事件以通知服务器端一个已命名的共享对象已创建。

Release(=2)

当共享对象在客户端被删除时客户端发送这一事件到服务器端。

Request Change (=3)

客户端发送给服务器端这一事件以请求共享对象的已命名的参数所关联到的值的改变。

Change (=4)

服务器端发送这一事件已通知发起这一请求之外的所有客户端,一个已命名参数的值的改变。

Success (=5)

如果请求被接受,服务器端发送这一事件给请求的客户端,以作为 RequestChange 事件的响应。

SendMessage (=6)

客户端发送这一事件到服务器端以广播一条消息。一旦接收到这一事件,服务器端将会给所有的客户端广播这一消息,包括这一消息的发起者。

Status (=7)

服务器端发送这一事件以通知客户端异常情况。

Clear (=8)

服务器端发送这一消息到客户端以清理一个共享对象。服务器端也会对客户端发送的 Use 事件使用这一事件进行响应。

Remove (=9)

服务器端发送这一事件有客户端删除一个 slot

Request Remove (=10)

客户端发送这一事件有客户端删除一个 slot

Use Success (=11)

服务器端发送给客户端这一事件表示连接成功。

(4)音频消息 (8)

       客户端或者服务器端发送这一消息以发送音频数据到对端。消息类型 8 为音频消息保留。

(5)视频消息 (9)

       客户端或者服务器发送这一消息以发送视频数据到对端。消息类型 9 为视频消息保留。

(6)统计消息 (22)

       统计消息是一个单一的包含一系列的使用 6.1 节描述的 RTMP 子消息的消息。消息类型 22 用于统计消息。

RTMP协议详解及实例分析_第5张图片

 

  统计消息的消息流 ID 覆盖了统计中子消息的消息流 ID。

       统计消息里的 timestamp 和第一个子消息的 timestamp 的不同点在于子消息的 timestamp 被相对流时间标调整了偏移。每个子消息的 timestamp 被加入偏移以达到一个统一流时间。第一个子消息的 timestamp 应该和统计消息的 timestamp 一样,所以这个偏移量应该为 0。

  反向指针包含有前一个消息的大小 (包含前一个消息的头)。这样子匹配了 FLV 文件的格式,用于反向查找。

  使用统计消息具有以下性能优势:

  • 块流可以在一个块中以至多一个单一完整的消息发送。因此,增加块大小并使用统计消息减少了发送块的数量。
  • 子消息可以在内存中连续存储。在网络中系统调用发送这些数据时更高效。

(7)用户控制消息事件

       客户端或者服务器端发送这一消息来通知对端用户控制事件。

  支持以下用户控制事件类型:

事件

描述

Stream Begin (=0)

服务器发送这个事件来通知客户端一个流已就绪并可以用来通信。默认情况下,这一事件在成功接收到客户端的应用连接命令之后以 ID 0 发送。这一事件数据为 4 字节,代表了已就绪流的流 ID

Stream EOF (=1)

服务器端发送这一事件来通知客户端请求的流的回放数据已经结束。在发送额外的命令之前不再发送任何数据。客户端将丢弃接收到的这个流的消息。这一事件数据为 4 字节,代表了回放已结束的流的流 ID

StreamDry (=2)

服务器端发送这一事件来通知客户端当前流中已没有数据。当服务器端在一段时间内没有检测到任何消息,它可以通知相关客户端当前流已经没数据了。这一事件数据为 4 字节,代表了已没数据的流的流 ID

SetBuffer Length (=3)

客户端发送这一事件来通知服务器端用于缓存流中任何数据的缓存大小 (以毫秒为单位)。这一事件在服务器端开始处理流之前就发送。这一事件数据的前 4 个字节代表了流 ID 4 个字节代表了以毫秒为单位的缓存的长度。

StreamIs Recorded (=4)

服务器端发送这一事件来通知客户端当前流是一个录制流。这一事件数据为 4 字节,代表了录制流的流 ID

PingRequest (=6)

服务器端发送这一事件用于测试是否能够送达客户端。时间数据是为一个 4 字节的 timestamp,代表了服务器端发送这一命令时的服务器本地时间。客户端在接收到这一消息后会立即发送 PingResponse 回复。

PingResponse (=7)

客户端作为对 ping 请求的回复发送这一事件到服务器端。这一事件数据是为一个 4 字节的 timestamp,就是接收自 PingRequest 那个。

 

7、用户控制消息

       RTMP 使用消息类型 ID 4 表示用户控制消息。这些消息包含 RTMP 流传输层所使用的信息。RTMP 块流协议使用 ID 为 1、2、3、5 和 6。

  用户控制消息应该使用消息流 ID 0 (以被认为是控制流),并且以 RTMP 块流发送时以块流 ID 为 2。用户控制消息一旦被接收立马生效;它们的 timestamp 是被忽略的。

  客户端或者服务器端发送这个消息来通知对端用户操作事件。这一消息携带有事件类型和事件数据。

  消息数据的前两个字节用于指示事件类型。事件类型被事件数据紧随。事件数据字段的大小是可变的。但是,如果消息必须通过 RTMP 块流层传输时,最大块大小应该足够大以允许这些消息填充在一个单一块中。

 

8、协议控制消息

       RTMP 块流使用消息类型 ID 为 1、2、3、5 和 6 用于协议控制消息。这些消息包含有 RTMP 块流协议所需要的信息。这些协议控制消息必须使用消息流 ID 0 (作为已知控制流) 并以流 ID 为 2 的块发送。协议控制消息一旦被接收到就立即生效;协议控制消息的 timestamp 被忽略。

(1)设置块类型

       协议控制消息 1,设置块大小,以通知对端一个新的最大块大小。

  默认的最大块大小是为 128 字节,但是客户端或者服务器可以改变这个大小,并使用这一消息对对端进行更新。例如,假定一个客户端想要发送一个 131 字节的音频数据,当前块大小是默认的 128。在这种情况下,客户端可以发送这种消息到服务器以通知它块大小现在是 131 字节了。这样客户端就可以在单一块中发送整个音频数据了。

  最大块大小设置的话最少为 128 字节,包含内容最少要一个字节。最大块大小由每个方面 (服务器或者客户端) 自行维护。

 

RTMP协议详解及实例分析_第6张图片

  0:这个位必须为 0。

  chunk size (块大小,31 位):这一字段保存新的最大块大小值,以字节为单位,这将用于之后发送者发送的块,直到有更多 (关于最大块大小的) 通知。有效值为 1 到 2147483647 (0x7FFFFFFF,1 和 2147483647 都可取); 但是所有大于 16777215 (0xFFFFFF) 的大小值是等价的,因为没有一个块比一整个消息大,并且没有一个消息大于 16777215 字节。

(2)终止消息

       协议控制消息 2,终止消息,用于通知对端,如果对端在等待去完成一个消息的块的话,然后抛弃一个块流中已接受到的部分消息。对端接收到块流 ID 作为当前协议消息的有效负载。一些程序可能会在关闭的时候使用这个消息以指示不需要进一步对这个消息的处理了。

 

RTMP协议详解及实例分析_第7张图片

  chunk stream ID (块流 ID,32 位):这一字段保存块流 ID,该流的当前消息会被丢弃。

(3)确认

       客户端或者服务器在接收到等同于窗口大小的字节之后必须要发送给对端一个确认。窗口大小是指发送者在没有收到接收者确认之前发送的最大数量的字节。这个消息定义了序列号,也就是目前接收到的字节数。

 

RTMP协议详解及实例分析_第8张图片

  sequence number (序列号,32 位):这一字段保存有目前接收到的字节数。

(4)窗口确认大小

       客户端或者服务器端发送这条消息来通知对端发送和应答之间的窗口大小。发送者在发送完窗口大小字节之后期待对端的确认。接收端在上次确认发送后接收到的指示数值后,或者会话建立之后尚未发送确认,必须发送一个确认。

 

RTMP协议详解及实例分析_第9张图片

(5)设置对端带宽

       客户端或者服务器端发送这一消息来限制其对端的输出带宽。对端接收到这一消息后,将通过限制这一消息中窗口大小指出的已发送但未被答复的数据的数量以限制其输出带宽。接收到这一消息的对端应该回复一个窗口确认大小消息,如果这个窗口大小不同于其发送给 (设置对端带宽) 发送者的最后一条消息。

 

RTMP协议详解及实例分析_第10张图片

  限制类型取以下值之一:

  •   0 - Hard:对端应该限制其输出带宽到指示的窗口大小。
  •   1 - Soft:对端应该限制其输出带宽到知识的窗口大小,或者已经有限制在其作用的话就取两者之间的较小值。
  •   2 - Dynamic:如果先前的限制类型为 Hard,处理这个消息就好像它被标记为 Hard,否则的话忽略这个消息。

 

9、RTMP 命令消息类型

       客户端和服务器端交换 AMF 编码的命令。服务器端发送一个命令消息,这个命令消息由命令名、事务 ID 以及包含有相关参数的命令对象组成。例如,包含有 'app' 参数的连接命令,这个命令说明了客户端连接到的服务器端的应用名。接收者处理这一命令并回发一个同样事务 ID 的响应。回复字符串可以是 _result、_error 或者 一个方法名的任意一个,比如,verifyClient 或者 contactExternalServer。

  命令字符串 _result 或者 _error 是响应信号。事务 ID 指示出响应所指向的命令。这和 AMAP 和其他一些协议的标签一样。命令字符串中的方法名表示发送者试图执行接收者一端的一个方法。

  以下类的对象用于发送不同的命令:

  NetConnection 代表上层的服务器端和客户端之间连接的一个对象。

       NetStream 一个代表发送音频流、视频流和其他相关数据的通道的对象。当然,我们也会发送控制数据流的命令,诸如 play、pause 等等。

       命令执行时消息流动如下:

 

RTMP协议详解及实例分析_第11张图片

  • 客户端发送 connect 命令到服务器端以请求对服务器端应用实例的连接。
  • 收到 connect 命令后,服务器端发送协议消息 '窗口确认大小' 到客户端。服务器端也会连接到 connect 命令中提到的应用。
  • 服务器端发送协议消息 '设置对端带宽' 到客户端。
  • 在处理完协议消息 '设置对端带宽' 之后客户端发送协议消息 '窗口确认大小' 到服务器端。
  • 服务器端发送另一个用户控制消息 (StreamBegin) 类型的协议消息到客户端。
  • 服务器端发送结果命令消息告知客户端连接状态 (success/fail)。这一命令定义了事务 ID (常常为 connect 命令设置为 1)。这一消息也定义了一些属性,比如 FMS 服务器版本 (字符串)。此外,它还定义了其他连接关联到的信息,比如 level (字符串)、code (字符串)、description (字符串)、objectencoding (数字) 等等。

服务器端到客户端的命令的结构如下:

 

RTMP协议详解及实例分析_第12张图片

(1)NetConnection 命令

       NetConnection 管理着一个客户端应用和服务器端之间的双相连接。此外,它还提供远程方法的异步调用。

  NetConnection 可以发送以下命令:

  • connect
  • call
  • close
  • createStream

(a)connect 命令

       由客户端发送到服务器端的 connect 命令结构如下:

字段名

类型

描述

Command Name

字符串

命令的名字。设置给 "connect"

Transaction ID

数字

总是设置为 1

Command Object

对象

具有名值对的命令信息对象。

Optional User Arguments

对象

任意可选信息。

以下是为 connect 命令中使用的名值对对象的描述:

属性

类型

描述

范例

app

字符串

客户端连接到的服务器端应用的名字。

testapp

flashver

字符串

Flash Player 版本号。和ApplicationScript getversion() 方法返回的是同一个字符串。

FMSc/1.0

swfUrl

字符串

进行当前连接的 SWF 文件源地址。

file://C:/FlvPlayer.swf

tcUrl

字符串

服务器 URL。具有以下格式:protocol://servername:port/appName/appInstance

rtmp://localhost:1935/testapp/instance1

fpad

布尔

如果使用了代理就是 true

true 或者 false

audioCodecs

数字

表明客户端所支持的音频编码。

SUPPORT_SND_MP3

videoCodecs

数字

表明支持的视频编码。

SUPPORT_VID_SORENSON

videoFunction

数字

表明所支持的特殊视频方法。

SUPPORT_VID_CLIENT_SEEK

pageUrl

字符串

SWF 文件所加载的网页 URL

http://somehost/sample.html

objectEncoding

数字

AMF 编码方法。

AMF3

       audioCodecs 属性的标识值:

 

RTMP协议详解及实例分析_第13张图片

       videoCodecs 属性的标识值:

 

RTMP协议详解及实例分析_第14张图片

       videoFunction 属性的标识值:

 

RTMP协议详解及实例分析_第15张图片

       encoding 属性值:

 

RTMP协议详解及实例分析_第16张图片

(b)call 方法

       NetConnection 对象的 call 方法执行接收端远程方法的调用 (PRC)。被调用的 PRC 名字作为一个参数传给调用命令。

发送端发送给接收端的命令结构如下:

字段名

类型

描述

Procedure Name

字符串

调用的远程方法的名字。

Transaction ID

数字

如果期望回复我们要给一个事务 ID。否则我们传 0 值即可。

Command Object

对象

如果存在一些命令信息要设置这个对象,否则置空。

Optional Arguments

对象

任意要提供的可选参数。

回复的命令结构如下:

字段名

类型

描述

Command Name

字符串

命令的名字。

Transaction ID

数字

响应所属的命令的 ID

Command Object

对象

如果存在一些命令信息要设置这个对象,否则置空。

Response

对象

调用方法的回复。

(c)createStream 命令

       客户端发送这一命令到服务器端以为消息连接创建一个逻辑通道。音频、视频和元数据使用 createStream 命令创建的流通道传输。

       NetConnection 是默认的通信通道,流 ID 为 0。协议和一些命令消息,包括 createStream,使用默认的通信通道。

客户端发送给服务器端的命令结构如下:

字段名

类型

描述

Command Name

字符串

命令名。设置给 "createStream"

Transaction ID

数字

命令的事务 ID

Command Object

对象

如果存在一些命令信息要设置这个对象,否则置空。

服务器端发送给客户端的命令结构如下:

字段名

类型

描述

Command Name

字符串

_result 或者 _error;表明回复是一个结果还是错误。

Transaction ID

数字

响应所属的命令的 ID

Command Object

对象

如果存在一些命令信息要设置这个对象,否则置空。

Stream ID

数字

返回值要么是一个流 ID 要么是一个错误信息对象。

(2)NetStream 命令

       NetStream 定义了传输通道,通过这个通道,音频流、视频流以及数据消息流可以通过连接客户端到服务端的 NetConnection 传输。

以下命令可以由客户端使用 NetStream 往服务器端发送:

  • play
  • play2
  • deleteStream
  • closeStream
  • receiveAudio
  • receiveVideo
  • publish
  • seek
  • pause

服务器端使用 "onStatus" 命令向客户端发送 NetStream 状态:

字段名

类型

描述

Command Name

字符串

命令名 "onStatus"

Transaction ID

数字

事务 ID 设置为 0

Command Object

Null

onStatus 消息没有命令对象。

Info Object

对象

一个 AMF 对象至少要有以下三个属性。"level" (字符串):这一消息的等级,"warning""status""error" 中的某个值;"code" (字符串):消息码,例如 "NetStream.Play.Start""description" (字符串):关于这个消息人类可读描述。

(a)play 命令

       客户端发送这一命令到服务器端以播放流。也可以多次使用这一命令以创建一个播放列表。

       如果你想要创建一个动态的播放列表,可以在不同的直播流或者录制流之间进行切换播放的话,多次调用 play 方法,并在每次调用时传递重置为 false。相反的,如果你想要立即播放指定流,将其他等待播放的流清空,并为重置设为 true。

命令执行时的消息流动是为:

  • 当客户端从服务器端接收到 createStream 命令的结果是为 success 时,发送 play 命令。
  • 一旦接收到 play 命令,服务器端发送一个协议消息来设置块大小。
  • 服务器端发送另一个协议消息 (用户控制),这个消息中定义了 'StreamIsRecorded' 事件和流 ID。消息在前两个字节中保存事件类型,在后四个字节中保存流 ID。
  • 服务器端发送另一个协议消息 (用户控制),这一消息包含 'StreamBegin' 事件,来指示发送给客户端的流的起点。
  • 如果客户端发送的 play 命令成功,服务器端发送一个 onStatus 命令消息 NetStream.Play.Start & NetStream.Play.Reset。只有当客户端发送的 play 命令设置了 reset 时服务器端才会发送 NetStream.Play.Reset。如果要播放的流没有找到,服务器端发送 onStatus 消息 NetStream.Play.StreamNotFound。
  • 之后,服务器端发送视频和音频数据,客户端对其进行播放。

客户端发送到服务器端的命令结构如下:

字段名

类型

描述

Command Name

字符串

命令名。设为 "play"

Transaction ID

数字

事务 ID 设为 0

Command Object

Null

命令信息不存在。设为 null 类型。

Stream Name

字符串

要播放流的名字。要播放视频 (FLV) 文件,使用没有文件扩展名的名字对流名进行定义 (例如,"sample")。要重播 MP3 或者 ID3,你必须在流名前加上 mp3:例如,"mp3:sample"。要播放 H.264/AAC 文件,你必须在流名前加上 mp4:并指定文件扩展名。例如,要播放 sample.m4v 文件,定义 "mp4:sample.m4v"

Start

数字

一个可选的参数,以秒为单位定义开始时间。默认值为 -2,表示用户首先尝试播放流名字段中定义的直播流。如果那个名字的直播流没有找到,它将播放同名的录制流。如果没有那个名字的录制流,客户端将等待一个新的那个名字的直播流,并当其有效时进行播放。如果你在 Start 字段中传递 -1,那么就只播放流名中定义的那个名字的直播流。如果你在 Start 字段中传递 0 或一个整数,那么将从 Start 字段定义的时间开始播放流名中定义的那个录制流。如果没有找到录制流,那么将播放播放列表中的下一项。

Duration

数字

一个可选的参数,以秒为单位定义了回放的持续时间。默认值为 -1-1 值意味着一个直播流会一直播放直到它不再可用或者一个录制流一直播放直到结束。如果你传递 0 值,它将只播放单一一帧,因为播放时间已经在录制流的开始的 Start 字段指定了。假定定义在 Start 字段中的值大于或者等于 0。如果你传递一个正数,将播放 Duration 字段定义的一段直播流。之后,变为可播放状态,或者播放 Duration 字段定义的一段录制流。(如果流在 Duration 字段定义的时间段内结束,那么流结束时回放结束)。如果你在 Duration 字段中传递一个 -1 以外的负数的话,它将把你给的值当做 -1 处理。

Reset

布尔

一个可选的布尔值或者数字定义了是否对以前的播放列表进行 flush

(b)play2

       不同于 play 命令的是,play2 可以在不改变播放内容时间轴的情况下切换到不同的比特率。服务器端为客户端可以在 play2 中请求所有支持的码率维护了不同的字段。

       命令执行时的消息流动如下图所示:

 

RTMP协议详解及实例分析_第17张图片

客户端发送给服务器端的命令结构如下:

字段名

类型

描述

Command Name

字符串

命令名,设置为 "play2"

Transaction ID

数字

事务 ID 设置为 0

Command Object

Null

命令信息不存在,设置为 null 类型。

Parameters

对象

一个 AMF 编码的对象,该对象的属性是为公开的 flash.net.NetStreamPlayOptions ActionScript 对象所描述的属性。

       NetStreamPlayOptions 对象的公开属性在 ActionScript 3 语言指南中 [AS3] 有所描述。

(c)deleteStream 命令

       当 NetStream 对象消亡时 NetStream 发送 deleteStream 命令。

客户端发送给服务器端的命令结构如下:

字段名

类型

描述

Command Name

字符串

命令名,设置为 "deleteStream"

Transaction ID

数字

事务 ID 设置为 0

Command Object

Null

命令信息对象不存在,设为 null 类型。

Stream ID

数字

服务器端消亡的流 ID

服务器端不再发送任何回复。

(d)receiveAudio 命令

  NetStream 通过发送 receiveAudio 消息来通知服务器端是否发送音频到客户端。

客户端发送给服务器端的命令结构如下:

字段名

类型

描述

Command Name

字符串

命令名,设置为 "receiveAudio"

Transaction ID

数字

事务 ID 设置为 0

Command Object

Null

命令信息对象不存在,设置为 null 类型。

Bool Flag

布尔

true 或者 false 以表明是否接受音频。

       如果发送来的 receiveAudio 命令布尔字段被设为 false 时服务器端不发送任何回复。如果这一标识被设为 true,服务器端以状态消息 NetStream.Seek.Notify 和 NetStream.Play.Start 进行回复。

(e)receiveVideo 命令

  NetStream 通过发送 receiveVideo 消息来通知服务器端是否发送视频到客户端。

客户端发送给服务器端的命令结构如下:

字段名

类型

描述

Command Name

字符串

命令名,设置为 "receiveVideo"

Transaction ID 

数字

事务 ID 设置为 0

Command Object

Null

命令信息对象不存在,设置为 null 类型。

Bool Flag

布尔

true 或者 false 以表明是否接受视频。

       如果发送来的 receiveVideo 命令布尔字段被设为 false 时服务器端不发送任何回复。如果这一标识被设为 true,服务器端以状态消息 NetStream.Seek.Notify 和 NetStream.Play.Start 进行回复。

(f)publish 命令

  客户端发送给服务器端这一命令以发布一个已命名的流。使用这个名字,任意客户端都可以播放这个流,并接受发布的音频、视频以及数据消息。

客户端发送给服务器端的命令结构如下:

字段名

类型

描述

Command Name

字符串

命令名,设置为 "publish"

Transaction ID

数字

事务 ID 设置为 0

Command Object

Null

命令信息对象不存在,设置为 null 类型。

Publishing Name

字符串

发布的流的名字。

Publishing Type

字符串

发布类型。可以设置为 "live""record" 或者 "append"record:流被发布,数据被录制到一个新的文件。新文件被存储在服务器上包含服务应用目录的子路径。如果文件已存在,将重写。append:流被发布,数据被添加到一个文件。如果该文件没找着,将新建一个。live:直播数据只被发布,并不对其进行录制。

       服务器端回复 onStatus 命令以标注发布的起始位置。

(g)seek 命令

  客户端发送 seek 命令以查找一个多媒体文件或一个播放列表的偏移量 (以毫秒为单位)。

客户端发送到服务器端的命令结构如下:

字段名

类型

描述

Command Name

字符串

命令的名字,设为 "seek"

Transaction ID

数字

事务 ID 设为 0

Command Object

Null

没有命令信息对象,设置为 null 类型。

milliSeconds

数字

播放列表查找的毫秒数。

seek 命令执行成功时服务器会发送一个状态消息 NetStream.Seek.Notify。失败的话,服务器端返回一个 _error 消息。

(h)pause 命令

  客户端发送 pause 命令以告知服务器端是暂停还是开始播放。

客户端发送给服务器端的命令结构如下:

字段名

类型

描述

Command Name

字符串

命令名,设为 "pause"

Transaction ID

数字

没有这一命令的事务 ID,设为 0

Command Object

Null

命令信息对象不存在,设为 null 类型。

Pause/Unpause Flag

布尔

true 或者 false,来指示暂停或者重新播放。

milliSeconds

数字

流暂停或者重新开始所在的毫秒数。这个是客户端暂停的当前流时间。当回放已恢复时,服务器端值发送带有比这个值大的 timestamp 消息。

       当流暂停时,服务器端发送一个状态消息 NetStream.Pause.Notify。NetStream.Unpause.Notify 只有针对没有暂停的流进行发放。失败的话,服务器端返回一个 _error 消息。

 

10、播放RTMP的基本流程

       (1)在基于传输层协议的链接建立完成后,RTMP协议也要客户端和服务器通过“握手”来建立基于传输层链接之上的RTMP Connection链接。播放一个RTMP协议的流媒体需要经过以下几个基本步骤:握手,建立网络连接,建立网络流,播放。服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。

       (2)RTMP协议传输时会对数据做自己的格式化,这种格式的消息我们称之为RTMP Message,而实际传输的时候为了更好地实现多路复用、分包和信息的公平性,发送端会把Message划分为带有Message ID的Chunk,每个Chunk可能是一个单独的Message,也可能是Message的一部分,在接受端会根据chunk中包含的data的长度,message id和message的长度把chunk还原成完整的Message,从而实现信息的收发。

 

11、握手

       要建立一个有效的RTMP Connection链接,首先要“握手”:客户端要向服务器发送C0,C1,C2(按序)三个chunk,服务器向客户端发送S0,S1,S2(按序)三个chunk,然后才能进行有效的信息传输。RTMP协议本身并没有规定这6个Message的具体传输顺序。

(1)握手顺序

  • 客户端要等收到S1之后才能发送C2
  • 客户端要等收到S2之后才能发送其他信息(控制信息和真实音视频等数据)
  • 服务端要等到收到C0之后发送S1
  • 服务端必须等到收到C1之后才能发送S2
  • 服务端必须等到收到C2之后才能发送其他信息(控制信息和真实音视频等数据)

 

RTMP协议详解及实例分析_第18张图片

注:

  • Uninitialized (未初始化):协议的版本号在这个阶段被发送。客户端和服务器都是 uninitialized (未初始化) 状态。之后客户端在数据包 C0 中将协议版本号发出。如果服务器支持这个版本,它将在回应中发送 S0 和 S1。如果不支持呢,服务器会才去适当的行为进行响应。在 RTMP 协议中,这个行为就是终止连接。
  • Version Sent (版本已发送):在未初始化状态之后,客户端和服务器都进入 Version Sent (版本已发送) 状态。客户端会等待接收数据包 S1 而服务器在等待 C1。一旦拿到期待的包,客户端会发送数据包 C2 而服务器发送数据包 S2。(客户端和服务器各自的)状态随即变为 Ack Sent (确认已发送)。
  • Ack Sent (确认已发送):客户端和服务器分别等待 S2 和 C2。
  • Handshake Done (握手结束):客户端和服务器可以开始交换消息了。

另:

       理论上来讲只要满足以上条件,如何安排6个Message的顺序都是可以的,但实际实现中为了在保证握手的身份验证功能的基础上尽量减少通信的次数,一般的发送顺序是这样的:

 

例:

 

RTMP协议详解及实例分析_第19张图片

(2)chunk格式

(a)C0和S0的格式

       C0 和 S0 包都是一个单一的八位字节,以一个单独的八位整型域进行处理:

 

RTMP协议详解及实例分析_第20张图片

       版本号 (八位)在 C0 中,这一字段指示出客户端要求的 RTMP 版本号。在 S0 中,这一字段指示出服务器端选择的 RTMP 版本号。本文中规范的版本号为 3。0、1、2 三个值是由早期其他产品使用的,是废弃值;4 - 31 被保留为 RTMP 协议的未来实现版本使用;32 - 255 不允许使用 (以区分开 RTMP 和其他常以一个可打印字符开始的文本协议)。无法识别客户端所请求版本号的服务器应该以版本 3 响应,(收到响应的) 客户端可以选择降低到版本 3,或者放弃握手。

(b)C1和S1的格式

       C1 和 S1 数据包的长度都是 1536 字节,包含以下字段:

 

RTMP协议详解及实例分析_第21张图片

  • Time (四个字节)这个字段包含一个 timestamp,用于本终端发送的所有后续块的时间起点。这个值可以是 0,或者一些任意值。要同步多个块流,终端可以发送其他块流当前的 timestamp 的值。
  • Zero (四个字节)这个字段必须都是 0。
  • Random data (1528 个字节)这个字段可以包含任意值。终端需要区分出响应来自它发起的握手还是对端发起的握手,这个数据应该发送一些足够随机的数。这个不需要对随机数进行加密保护,也不需要动态值。

(c)C2和S2的格式

       C2 和 S2 数据包长度都是 1536 字节,基本就是 S1 和 C1 的副本 (分别),包含有以下字段:

 

RTMP协议详解及实例分析_第22张图片

Time (四个字节)这个字段必须包含终端在 S1 (给 C2) 或者 C1 (给 S2) 发的 timestamp。

Time2 (四个字节)这个字段必须包含终端先前发出数据包 (s1 或者 c1) timestamp。

Random echo (1528 个字节)这个字段必须包含终端发的 S1 (给 C2) 或者 S2 (给 C1) 的随机数。两端都可以一起使用 time 和 time2 字段再加当前 timestamp 以快速估算带宽和/或者连接延迟,但这不太可能是有多大用处。

 

12、建立网络连接

 

RTMP协议详解及实例分析_第23张图片

 

(1)连接到服务器

       客户端发送命令消息中的“连接”(connect)到服务器,请求与一个服务应用实例建立连接。

 

RTMP协议详解及实例分析_第24张图片

(a)RTMP Header:

       StreamID:每个消息的唯一标识,划分成Chunk和还原Chunk为Message的时候都是根据这个ID来辨识是否是同一个消息的Chunk的,这里面为0说明这个消息是初始的0消息。

       Chunk stream ID:message会拆分成多个chunk,同一个Chunk Stream ID必然属于同一个Message。

(b)RTMP Body

       此connect包中是一个OSI五层模型,最后一个是RTMP协议发送了connect链接消息,查看内容包含推流地址名,但是可以观察到还没有发流名,地址是有app名。

(2)设置chunk大小

       服务端给客户端发送chunk大小。

 

RTMP协议详解及实例分析_第25张图片

(3)服务端确认窗口大小

       服务器接收到连接命令消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到客户端,同时连接到连接命令中提到的应用程序。

 

RTMP协议详解及实例分析_第26张图片

(4)设置对端带宽

       客户端或者服务器端发送这一消息来限制其对端的输出带宽。对端接收到这一消息后,将通过限制这一消息中窗口大小指出的已发送但未被答复的数据的数量以限制其输出带宽。如果这个窗口大小不同于其发送给 (设置对端带宽) 发送者的最后一条消息,那么接收到这一消息的对端应该回复一个窗口确认大小消息。

 

RTMP协议详解及实例分析_第27张图片

(5)服务端返回连接结果

       服务端返回连接结果。

 

RTMP协议详解及实例分析_第28张图片

(6)服务端响应结果

       服务端向客户端发送的关于之前客户端向服务端请求的响应结果。

RTMP协议详解及实例分析_第29张图片

 

13、建立一个网络流

 

RTMP协议详解及实例分析_第30张图片

(1)客户端向服务端发送请求创建流

       客户端向服务端发送窗口大小,请求创建流,最后检查请求的响应结果。

 

RTMP协议详解及实例分析_第31张图片

(2)服务端收到请求后向客户端发送_result(),对创建流的消息进行响应。此时NetStream创建完成。

RTMP协议详解及实例分析_第32张图片

 

14、播放

RTMP协议详解及实例分析_第33张图片

(1)客户端发送命令消息中的播放(play)命令到服务端

RTMP协议详解及实例分析_第34张图片

(2)服务器收到播放命令后,发送设置大小(chunksize)协议消息。

(3)服务端发送用户控制消息中的“streambegin”,告知客户端流ID。

RTMP协议详解及实例分析_第35张图片

(4)播放命令成功的话,服务器发送命令消息中的“响应状态”NetStream.Play.reset & NetStream.Play.Start,告知客户端“播放”命令执行成功。

RTMP协议详解及实例分析_第36张图片

RTMP协议详解及实例分析_第37张图片

(5)服务端通过发送数据消息类型以发送元数据或者任何用户数据到对端。元数据包括数据 (音频,视频等等) 的详细信息,比如创建时间,时长,主题等等。

RTMP协议详解及实例分析_第38张图片

RTMP协议详解及实例分析_第39张图片

RTMP协议详解及实例分析_第40张图片

(6)在此之后服务器发送客户端要播放的音频和视频数据

RTMP协议详解及实例分析_第41张图片

RTMP协议详解及实例分析_第42张图片

其中视频type是(0x99),音频type是(0x88)。

 

15、public推流

RTMP协议详解及实例分析_第43张图片

       推流从握手开始和前面步骤一直,和播放的区别在于netstream改为publish。

 

 

参考:

Adobe官方文档:https://www.adobe.com/devnet/rtmp.html

https://my.oschina.net/shishuo365/blog/745489

你可能感兴趣的:(音视频流媒体开发,rtmp)