摘要
这个文章描述了Adobe的RTMP(全称:Real Time Messaging Protocol)协议,RTMP协议是一个建立在传输层协议(如:TCP)之上的,支持多路复用、分包、用来传输媒体流(像音频、视频、可交互内容)的应用层协议。
文档当前的状态
这个文章是2012年12月发布的一个版本,它最初是以PDF的方式进行发布。这个版本相对于2019年1月发布的html版本没有实质改变。仅仅更改了一些格式排版。
1. 介绍
RTMP协议提供了一个基于传输层协议的双向通信的服务。目的是在互相通信的双方,传输可以并行的数据流。这些数据流可以传输携带时间戳的视频、音频、消息数据。它的实现可以支持对不同类型的消息类型使用不同的优先级。当传输层能力受限时(如弱网)它可以按优先级顺序发送数据。
1.1. 术语
一些关键字 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” 这些关键字的解释是按 [RFC2119]来解释。
2. 贡献者
Rajesh Mallipeddi,以前在Adobe工作,是这个规范最开始的编辑者,他提供了最多的文字。
Mohit Srivastava,在Adobe工作,他编写了这个文档。
3. 定义
payload: 包含在Packet的数据,如音频样本数据、视频压缩数据 。具体的payload的格式和解释不在这个文档的范围。
packet: 一个数据包,包含了固定的头和 payload数据。
port: 传输层协议用来在同一主机上区分数据包的目的地。TCP/IP协议簇使用小的正数作为端口。
transport Address:网络地址+端口的组合用来标识传输层的一端,如一个Ip地址+TCP端口。数据包会从一个原 transport address 传输到目的 transport address。
message stream: 一个用来进行消息通信的逻辑渠道。
message stream id: 每一个消息都会关联一个 id ,这个id用来区分消息属于哪个流。
chunk: 消息的分片,消息在发送前会被分成小块。这些分片会按照时间序通过多个stream进行端到端的交付。
chunk stream: 一个用来进行通信的逻辑渠道,它允许多个同一方向的多个chunk在同一个chunk stream中。这个chunk stream 可以从客户端到服务端,相反也可以从服务端到客户端。
multiplexing : 使分离开的音频/视频数据进入同一个音视频数据流的过程。它使同时传送多个音视频数据成为了可能。
demultiplexing: multiplexing的逆过程。它将交错传输的音视频流拆分为单独的音频、视频数据。
remote procedure call (RPC): 一个允许客户端/服务端调用对端子程序的请求。
metadata: 对数据的描述。如一个电影的meta包含:标题、时长、创建日期等等。
application instance: 一个在服务端正在处理客户端请求的应用实例。
action message format (AMF): 一个简洁的用来序列化ActionScript 对象图的二进制格式。AFM有2个版本:AFM0 和AMF3。
4. 字节序、字节对齐、和时间格式。
所有的整型数据都是按网络字节序传输的。也就是众所周知的大端序。这个传输序的细节在网络协议:RFC0791中有详细描述。除非其他特殊说明,文档中提到的数字都是10进制。
除了其他特殊说明,所有在RTMP中传输的数据都是字节对齐的;举个例子:一个16-bit的属性可能偏移量会是奇数。如果有地方指出需要补位都应该补0。
RTMP中的时间戳都是一个以毫秒为单位的epoch时间戳。通常每个流开始的时间戳都会是0,但这不是必须的,只要双端约定好就可以。这意味着多个流之间(尤其在不同的主机中)需要使用一些RTMP之外的机制来进行同步。
因为时间戳是32位的整数,所有它最多表示49天,17小时,2分钟和47.296秒。因为流是连续的,有可能需要几年才能结束。RTMP程序需要保证连续的数字来处理时间戳,并且需要有回绕能力。比如流的时间戳已经超过了2^31 -1 毫秒,那么10000将出现在 4000000000后面,3000000000将在4000000000的前面。
相对时间戳也被指定单位为毫秒的无符号整型,是相对于上一个时间戳的差值。这个相对时间戳可能占24bit或32bit。
5. RTMP 分块
这一节将说明RTMP的分块流。它提供上层流媒体协议的复合(multipexing)和分包能力。
虽然 RTMP分块流被设计为和RTMP协议一起工作,但是它能处理任意协议的消息流。每个消息包含有时间戳和payload类型。RTMP分块流和RTMP协议一起适用于多种音视频应用,一对一、一对多、直播、点播服务、视频会议应用。
当使用可靠的传输层协议像TCP协议、RTMP分块流会保证数据是按时间戳顺序传送、以及通过多个strem,端到端交付所有消息。 RTMP分块流不提供优先级或类似的控制消息。但上层的协议可以提供处理优先级的能力。比如:一个直播视频服务也许会选择丢一些视频消息来确保音频信息被及时的接收。以及可以为每个消息都发送ACK来确保信息接收。
RTMP分块流包含了自己内部的控制协议消息,它也可以提供高层的协议嵌入自己的用户控制消息。
5.1. 消息格式
一个消息可以拆分为多个Chunk用来支持复合(multiplexing)。这个消息应该包含下列必须的属性。
timestamp: 消息的时间戳。这个字段可以传送4字节。
length: 消息playload的长度。如果消息头不能够被省略,它也应该包含在这个长度中。这个字段在chunk header中占3字节。
type id: 一系列的 type IDs 控制协议消息的保留字段。RTMP分块流会透传这个字段,这个字段可以被上层的协议读取。实际,RTMP分块流(RTMP Chunk Stream)也没有要求这个字段只能是一个字节。所有的消息也可以用同样的类型、或者应用层可以用它来区分不同的track。这个字段在chunk 头中占1个字节。
message stream id : 消息的stream id 可以为任意值,不同的消息流进入同一个chunk流会被根据stream id 解复用。除此之外,就RTMP分块流而言,他是一个透明值。这个值以小端格式占4个字节。
5.2. 握手
一个RTMP链接会以握手开始,这个握手不像其他协议一样,它由固定大小的3个chunk组成,而不是可变长度的chunk。
客户端和服务端都互相发送3个chunk。为了方便解释,我们把客户端发送的chunks称为C0,C1,C2。服务端发送的我们称为S0,S1,S2。
5.2.1 握手顺序
客户端会首先发送C0和C1。
客户端必须等到接收到S1后才会发送C2。之后必须等到收到S2之后才会发送其他数据。
服务端必须等到C0之后才能发送S0和S1,也许也会等到C1之后才发送。服务端必须等到接收到C1才会发送S2.之后必须等到接收到C2才会发送其他数据。
5.2.2 c0和s0的格式
C0和C1是一个字节。
Version(8 bit): 在C0中这个字段表示客户端请求的RTMP版本。在服务端这个字段表示服务端选择的客户端请求版本。这个字段在这个文档中被定义为3. 0-2 已经废弃了。4-31被保留为将来的实现。32-255是不允许的(它用来区分RTMP基于文本的协议,它将总是以可打印字符开始)。如果服务端不认识客户端请求的版本,它应该返回3。客户端也许会降级为3,或者放弃握手。
5.2.3 c1和s1的格式
c1和s1包长1536个字节,固定为下列字段:
time(4 bytes): 这个字段包含一个时间戳,后续发送的chunks都会基于这个时间。它也可以为0,或者其他任意值。为了同步多个分块流,端上可能会希望发送一个chunk stream的当前时间。
zero(4 bytes): 这个字段必须全为0s.
random data: 这个字段可以是任意的值。由于双端必须区分自身产生的数据和对端产生的数据,所有这个数据应该充分随机。但这里没有必要使用密码学安全级别的随机数、甚至可以不是动态产生的值。
5.2.4 c2和s2的格式
c2和s2长1536字节,它几乎是s1或c1的一个回应。它有下列固定的字段组成。
time( 4 bytes): 这个字段必须包含对端在S1(对于C2来讲是S1,对于S2来讲是C1)发送的时间戳。
time2(4 bytes): 这个字段必须包含读到的上一个对端发送的数据包(s1或c1)的时间戳。
random echo: 这个字段必须包含对端发送的随机数(对于C2来将就是S1,对于S2来讲就是C1)。双端可以根据time和time2字段估计出链接大概的带宽和延时。但是这个看起来不是很有用。
5.2.5. 握手流程图
下面将描述一下图中提到的几个状态:
uninitialized: 协议的版本在这个阶段已经发送了,双端还没有初始化。客户端发送的版本信息在C0中。如果服务端支持这个版本,它将回应S0和S1。如果不支持它将执行相应的动作,在RTMP中这个动作就是结束链接。
version sent: 这个状态在uninitialized之后,双端都已经发送完了版本信息。客户端正在等待S1,服务端正在等待C1。在等待的过程中客户端将发送C2,服务端也将发送S2。这个状态之后是 ack sent。
act sent: 客户端在等待S2,服务端在等待C2。
handshake done: 客户端和服务端都已经完成握手。
5.3. 分块
在握手结束之后,连接将复用一个或多个分块流。每个分块流将携带来自消息流的一种类型消息数据。每个分块都关联一个唯一的id,叫做分块流id(chunk stream ID)。这些分块通过网络传输。传输中每个分块必须完全发送出去之后才能发送下一个分块。在接收端这些分块会重新根据分块流ID重新组装在一起。
分块使得高层协议中的大消息块可以分成更小的消息。比如可以防止大的低优先级的消息(如视频)被更小的高优先级(如音频数据或控制消息)的消息阻塞。
分开也允许可以使小的消息以较小的开销发送出去。像chunk头中包含了一些被压缩的信息。否则这些信息将必须包含在消息body中。
分块的大小是可配的。可以使用Set Chunk Size 控制消息来设置(5.4.1会讲)。Chunk size 设置的大可以减少cpu开销。但也会在低带宽网络环境下带来延迟。Chunk size 设置的小不利益高比特率的数据流。Chunk Size 对于双端是分开设置的。
5.3.1 分块格式
每个分块都由header和data组成。header分为3个部分:
basic header: (1 到 3个 bytes) 这个字段编码了chunk stream id和 chunk类型。chunk类型决定了message header 的格式。这个字段的长度依赖 chunk stream id 的值。他是一个可变长度字段。
message header ( 0,3,7或11 bytes): 这个字段编码了关于要发送的消息内容的信息(无论是这个消息还是消息的一部分)。这个字段的长度取决于 chunk 类型。
extended timestamp(0或4字节):这个字段某些情况下可能是时间戳、也可能是时间差值。具体参考5.3.1.3。
chunk data ( variable size): chunk的payload,上限是配置的chunk size的最大值。
5.3.1.1 Chunk Basic Header
这个Chunk Basic Header 是由 chunk stream ID和chunk type组成。chunk type决定了消息头的格式。chunk basic header的大小可能为1,2,或3个字节。这个大小取决于 chunk stream ID。
实现中应该用最少的字节数来表示chunk stream ID。
这个协议支持65597个stream,id从3到65599。ID为0,1和2是保留的。值0表示用2个字节来代表id,范围从63到319(第二个字节的值+64)。值1代表用3个字节来代表id,范围从64到65599(第3个字节 * 256 + 第二个字节+64)。值3到63则就代表了完整的id。 chunk stream id 值为2的情况被保留,为了低层协议的控制消息和命令消息。
位0-5(从低位开始数)代表了chunk stream id。
1个字节:
chunk stream id 2-63被编码为1个字节。
2个字节:
chunk stream IDs 64-319可以被编码为2个字节。ID的计算方式为:第二个字节的值+64
3个字节:
chunk stream id 64-65599可以被编码为3个字节。ID的计算方式为 :第三个字节的值 * 256 + 第二个字节的值+64。
cs id (6 bits) 这个字段包含了chunk stream id,值从2-63。值0和1用来指示用2个或3个字节表示cs id。
fmt (2 bits) 这个字段可以取0-4用来代表不同的 消息头(chunk message header)。这个 chunk message header 将在下一节进行解释。
cs id -64 (8 或 16 bits): 这个字段包含了chunk stream id 减 64 。例如:ID 365 在cs id字段为 1,以及在 cs-id -64 这里占16bit值为301。
值为64-319的chunk stream id,可以用2个字节表示也可以用3个字节表示。
5.3.1.2 Chunk message header
这里有4个不同格式的 chunk message header,chunk basic header中的fmt用来指示不同的格式。
实现程序应尽可能的使chunk message header更紧凑。
5.3.1.2.1 TYPE 0
Type 0 chunk header 长11个字节。这个type必须被用在chunk stream的开始位置。
timestamp(3 bytes):对于这个type为0的chunk,这个是绝对时间戳,如果这个时间戳大于等于16777215(0xffffff),这个值必须为16777215,代表将启用拓展时间戳字段,那么这个扩展时间戳会占用4个字节来表示,否则它就是代表当前的时间戳。
5.3.1.2.2 TYPE 1
type 1的chunk header长7个字节,这个消息的stream id没有被包含。这个chunk将使用同一个chunk stream中的前一个chunk的stream id。大小不固定的消息(如许多视频格式)流应该在chunk stream中第一个chunk之后使用这个格式。
5.3.1.2.3 TYPE 2
type 2 chunk header长3个字节。这个消息的stream id和消息长度都没有被包含。这个chunk将使用同一个chunk stream中前一个chunk相同的长度和stream id 。大小固定的消息(如一些音频和一些数据格式)流应该在每个chunk stream 第一个chunk 之后,使用这种格式。
5.3.1.2.4 TYPE 3
type 3 chunk 没有消息头。这个消息stream id ,message length, timestamp detla字段都没有出现。这些字段的值将和同一个chunk stream中的前一个chunk 的取值一致。当一个消息被拆分为多个chunk时,除了第一个chunk,其余的chunk都应该使用这个类型,对应有个例子2(在5.3.2.2节)。但当一个流由多个一样大小、stream ID 相同、时间间隔(timestamp delata)也相同的组成时。这个type 3则应该出现在 type 2之后,有个例子1(在5.3.2.1节)。如果第一个chunk和第二个chunk之间的时间差值(timestamp delta) 和第一个chunk中的timestamp相同时,则也可以直接在type 0 chunk之后发送type 3 chunk 不再需要type 2。如果是这种情况时间差(timestamp delta)会认为和 type 0中的timestamp一致。
5.3.1.2.4 公共头字段
下面是对于chunk 消息头的每个字段的描述:
timestamp delta(3 bytes) :存在于type 1 和 type 2的chunk,它们是相对于前一个chunk的时间差值。如果这个差值大于等于16777215 ( 0xFFFFFF),则这个值必须为 16777215,代表必须启用扩展时间戳字段。启用后扩展字段将使用4个字节代表时间。
message length (3 bytes): 存在于type 0 和type 2的chunk,代表发送的消息长度。注意这里一般不等于chunk payload的长度。chunk payload的长度是除最后一个chunk后所有chunk中最大的chunk大小,以及最后一个chunk它表示的是剩余数据的长度(对于比较小的消息它是整个消息的长度)。
message type id (1 byte): 存在于 type 0 和 type 1 chunk。代表发送的消息的类型。
message stream id (4 bytes): 存在于type 0 chunk。是消息流的ID。消息流ID是小端格式。通常,同一个chunk stream中的chunk都来自于同一个消息流。如果将不同消息流的消息放入相同的chunk stream会降低头的压缩效果。当然如果一个消息流关闭了,后续的另一个打开的消息流来通过重新发送一个type-0 chunk 来复用这个chunk stream是合理的。
5.3.1.3 扩展时间戳
当时间大于16777215(0xffffff)时,扩展时间戳字段是用来编码时间戳、或时间差值的,因为这时3个字节不够存储。扩展时间戳字段占4个字节,它出现在 type 0 、type 1、type2 chunk中,当它们中的timestamp为1677725时。当最近的type 0,1或2 chunk指示出要出现扩展时间戳时,它也会出现在type 3的chunk中。
5.3.2 例子
5.3.2.1 例1
这个例子是一个简单的音频流信息。
下一个表格展示这个消息进入chunk stream后的情况。从消息3开始,数据传输是被优化的。每个消息将仅需要一个额外字节开销。
5.3.2.2 例2
这个例子是一个消息因为太长被拆分为了大小为128字节的chunk进行发送。
这里是被处理为chunk后的情况:
第一个chunk就指出了整个消息的长度为307字节。
从这2个例子中可以看出,type 3的chunk可以有2种使用方式。第一个例子支持的是连续的不同的消息。第二个例子指出的是一个消息被拆分出的多个chunk。
5.4. 协议控制消息
RTMP chunk stream 使用消息类型ID (message type IDs)为 1,2,3,5,和 6 来表示控制消息。这些信息包含RTMP chunk stream 需要的信息。
这些控制消息必须包含message stream id 并且为0,并且chunk stream id为2。控制消息在收到之后会立即生效。它们的时间戳时被忽略的。
5.4.1 Set chunk size
协议控制消息1,Set Chunk Size,用来通知对端设置chunk size的最大值。
这个chunk size的最大值默认为128字节,但是客户端和服务端都可以改变这个值。已经使用这个消息来更新。如:假如当前的chunk size 时128 客户想发送131字节的音频数据,这种情况下,客户端可以发这个消息告知服务端它将chun size设置为131字节,这样客户端之后就可以在一个chunk中发送整个音频数据。
这个chunk size最大值应该至少为128,并且必须大于1字节。这个chunk size最大值双端时相互独立的。
chunk size (31 bits): 这个字段代表了新的chunk size最大值,单位为字节。发送着后续的chunk都将使用这个最大值。知道下一个次更新。这个值的取值范围为1到2147483647(0x7fffffff),闭区间。但是,对于所有消息大小大于16777215的消息都是等价的,因为没有chunk的大小能大于这个对应的消息大小。没有消息的长度能超过16777215字节 (消息的长度用3个字节表示,所以不可能超过16777215)。
5.4.2. Abort Message
协议控制消息2,Abort Message,用来通知等待的对端结束消息,然后丢弃这个chunk stream接收到的部分消息。对端将在message的payload中得到这个chunk stream id。应用实例允许在结束的时候发送这个消息,用来指示不需要再接收数据。
chunk stream ID (32 bits):这个字段代表chunk stream ID,这个chunk stream ID 对应的消息将被丢弃。
5.4.3. Acknowledgement
客户端或服务端必须在接受到的数据达到window size之后给对端发送这个消息。这个window size 是一个不给对端发送acknowledgement的情况下能接受到对端消息字节数的最大值。这个消息是一个序列号,代表迄今为止收到对端的字节数。
sequence number (32 bits): 这个字段代表迄今为止接受到对端的字节数。
5.4.4. Window Acknowledgement Size
客户端或服务端发送这个消息给对端用来指示出window size。 发送端期望在发送 window size个字节后,对端会回应acknowledgement。对端必须在收到window size个字节后发送acknowledgement回应。
5.4.5. Set Peer Bandwidth
客户端或服务端发送用来限制对端输出的bandwidth。对端收到这个消息后,它的输出带宽将被限制,这个消息指出了它能发送的未被确认的数据的字节数大小。当对端收到这个消息如果这个消息的window size 不同于上次发送端发送的window size,则应该回应一个Window Acknowledgement Size消息。
Limit Type 是下列列出来的值之一:
0 - hard: 对端的输出bandwidth应该使用这个消息的 window size。
1 - soft: 对端如果已经有生效的输出bandwidth,则取它和这个消息种的window size的最小值,作为输出bandwidth。
2 - dynamic: 如果上一个消息的limit type是hard,这个消息的limit type也被认为是hard,否则忽略这个消息。
6. RTMP Message Formats
这一节指出在网络中使用低层传输层协议(如 RTMP Chunk stream)的RTMP消息格式。
尽管RTMP协议被设计为RTMP Chunk stream一起工作,但它可以使用任意的传输层协议进行发送。
RTMP Chunk stream 和RTMP一起可以适用于很多音视频应用,包括:一对一、一对多、直播、点播以及一些会议应用。
6.1. RTMP 消息格式
客户端和服务端通过从网络中发送RTMP消息进行通信。这些消息包含:音频、视频、数据以及其他消息。
RTMP消息包含2个部分:消息头和消息体(payload)。
6.1.1 消息头
消息头包含下列字段:
message type : 一个字节。用来表示消息类型。值 1 - 6被保留用作协议控制消息。
length: 3个字节用来表示payload的大小。大端格式存储。
timestamp: 4个字节,代表时间。大端格式存储。
message stream id : 3个字节,消息对应的流id。大端格式存储。
6.1.2. 消息payload
这个里面是负载的真实数据,如:他可能是音频样本数据或压缩后的视频数据。这个payload的格式不在本文档的讨论范围内。
6.2. 用户控制消息
RTMP使用message type ID 4来表示拥护控制消息。这些消息包含的信息被用作RTMP stream 层。控制消息ID: 1,2,3,5和6被用作RTMP Chunk stream 协议(5.4节)。
用户控制消息应该使用消息流ID为0,并且通过RTMP Chunk Stream发送,chunk stream id的流 ID为2。
客户端或服务端通过发送这个消息告知关于用户的控制事件。这个消息包含事件类型和时间数据。
前2个字节用来标记事件类型。事件数据(Event Data)的长度是变长的。当然,这里的消息必须通过RTMP chunk stream层传输。最大的chunk size设置的应该足够大, 需要能够容纳这里的数据只进一个chunk。
Event Type和它们对应数据格式将在7.1.7节介绍。
7. RTMP消息
这节描述客户端和服务端交互的不同的消息和命令类型。
这些类型包含:音频数据、视频数据、数据消息、共享对象消息、和命令消息。共享对象数据提供了一种通用的方式来管理多个客户端和服务端之间的分布式数据。命令消息携带AMF编码过的消息在双端通信。客户端或服务端可以通过stream通过命令消息实现RPC调用。
7.1. RTMP消息类型
客户端和服务端通过在网络中发送消息互相通信。这些信息可以是音频数据、视频数据、命令消息、共享对象消息、数据消息和用户控制消息。
7.1.1 命令消息(20,17)
命令消息可以携带AMF编码过后的命令。这些消息被设计为message type 为20的话代表AMF0编码,message type为17的话代表AMF3编码。这些消息执行的操作有:connect,createStream,publish,play,pause。命令消息像:onstatus,result等是用来表示发送者发送的命令状态。一个命令消息由命令名称、transation ID 和包含参数的命令对象组成。客户端或服务端可以通过命令消息来执行RPC调用。
更多细节,参考7.2节。
7.1.2. 数据消息(18,15)
客户端或服务端发送metadata或任意用户数据告诉对端。metadata包含数据(音频或视频)的详情信息,像创建时间、时长、主题等等。这些消息类型为18(AMF0) ,15 (AMF3)。
7.1.3. 共享对象消息(19,16)
一个共享对象是一个Flash对象(一个键值对集合),它们多个客户端、实例等等中同步共享。这个消息的类型为19(AMF0) ,16(AMF3)。每个消息可以包含多个事件。
支持下列事件类型:
Use: 客户端通过这个事件指示服务端创建并命名一个共享对象。
Release: 当这个共享对象从客户端这一侧删除时,通过这个消息告诉服务端。
Request Change: 客户度发送这个事件改变共享对象中参数。
Change: 服务端向除原始发起改变请求的客户端以外的客户端发送这个消息,表示这个参数值改变了。
Success: 用户回应客户端发起的RequestChange被接受。
Status: 服务端通知客户端错误的情况。
Clear: 服务端发送这个事件给客户端去清理共享对象。服务端也会向发起Use事件的那个客户端响应这个消息。
Remove: 服务端向客户端发送这个事件,要求客户端删除一个卡槽。
Request Remove: 客户端发送这个事件,要求客户端删除一个卡槽。
Use Success: 在连接成功时,服务端向客户端发送这个消息。
7.1.4. 音频消息 (8)
客户端或服务端发送通过这个消息发送音频数据给对端。
7.1.5. 视频消息(9)
客户端或服务端通过这个消息发送视频数据给对端。
7.1.6. 聚合消息(22)
聚合消息是一个可以嵌套一系列子消息的消息。message type 为 22 代表是一个聚合消息。
这个消息的stream id 会代替所有嵌套在内部的子消息的stream id。
对于时间戳来聚合消息会重组子消息中的时间戳,子消息中存储的时间为它的真实时间距聚合消息中时间的差值。对于第一个子消息它的时间戳值应该是0。
back pointer: 包含的是上一个消息的大小(包括它的header)。它和flv文件格式中的tag大小类似,可以用来回退。
使用聚合消息有一些性能优点:
- chunk stream 最多只能在一个chunk中发送一个完整的消息。因此增加chunk size 并使用聚合消息则可以减小法发送chunk的数量。
- 子消息可以存在在连续的内存中。这样再发送的时候比较高效。
7.1.7. 用户控制消息事件
客户端或服务端通过发送这个消息来通知关于用户控制信息。消息格式参考6.2节。
支持下列用户控制事件:
Stream Begin: 服务端发送这个事件告知客户端这个流已经变得可用以及可以用来通信了。默认,这个事件在应用connect成功之后发送,且消息的stream id为0。对应的event data 占4字节,代表可以使用的stream id。
Stream EOF: 服务端通过这个消息告诉客户端,请求媒体进度已经超过了这个流。客户端会丢失收到的这个流的数据。这个event data占4字节,代表已经结束的那个流的ID。
StreamDry :服务端通过这个消息告知客户端,这个流没有更多数据了。如果服务端在规定的时间内没有检测到有推送端的数据,则会向订阅客户端发送这个消息。这个event data占4字节,代表流ID。
SetBuffer Length: 客户端向服务端发送这个消息,告知这个流 buffer的大小(毫秒为单位),这个事件在服务端处理这个流前发送。event data中前4个字节代表流ID,接着的4个字节代表buffer的长度,单位为毫秒。
Streamls Recorded: 服务端向客户端发送这个消息告知这个流是个录制流。event data占4字节代表流ID。
PingRequest: 服务端发送这个消息给客户端用来检测客户端是否可达。event data是个4字节的时间戳,代表服务端发送命令的时间。
PingResponse: 客户端回应PingRequest,event data是个4字节的时间戳,代表接受到PingRequest的时间。
7.2. 命令消息
客户端和服务端可以交换AMF编码过的命令消息,发送端发送的命令由命令名称、事物ID(transation ID)和包含相关参数的命令对象。如:connect 命令包含"app"参数。服务端会用相同事物ID(transation ID)进行回应。回应的消息名称为:_result、_error, 或一个方法名。如verifyClient 或 contrack ExternalServer。
命令字符串_result 或_error 代表是一个响应。响应中的transation ID 代表了为被回应的那个命令。它和IMAP协议中的tag或许多其他协议是相似的。 如果是方法名,代表发送方想要尝试执行接收端的方法。
下面的类对象用来发送各种各样的命令:
NetConnection: 表示客户端与服务端连接之间的高层对象。
NetStream: 表示如音频流、视频流或其他流渠道对象。我们也发送像play,pause等控制数据流。
7.2.1 NetConnection 命令
NetConnnection管理客户端于服务端之间的双向链接。另外,他提供异步远程调用。
下面的命令可以在NetConnection上发送:
- connect
- call
- close
- createStream
7.2.1.1 connect
客户端发送connect命令请求服务端创建一个链接实例。
这个命令的结构如下:
Command Name: 命令的名称。“connect”
transaction ID : 总为1
command object : 命令的信息,是一些键值对。
Optinal User Arguments: 任意可选的信息。
下面是command object中的一些键值对信息:
app: 客户端链接服务端应用的名称。
flashver: Flash播放器的版本,他是ApplicationScript getversioin()函数的返回值,是个string。
swfUrl: SWF文件资源的url。
tcUrl: 服务端的url。格式为: protocol://servername:port/appName/appInstance
fpad: 如果用了代理则是true。
audioCodecs :指示支持的音频编码器。
videoCodecs: 指示支持的视频编码器。
videoFunction: 指示支持的特殊的视频功能。
pageUrl: web页面在哪里加载SWF文件的页面路径。
object Encoding: AMF编码的版本。
下面是audioCodecs 支持的编码器对应的code:
下面是videoCodes对应的编码器的code:
下面是videoFunction对应的属性
下面是object encoding的属性
服务端发送给客户端的命令结构如下:
command name: _result 或 _error;代表响应是结果还是错误。
transation ID: 对于connect来讲值为1。
properties: 用来描述链接信息的键值对。
information: 用来描述server的一些信息的键值对。
connect命令的交互时序图如下:
- 客户端发送connect命令给服务端,请求链接到服务端应用实例。
- 服务端接受到这个命令以后,服务端发送协议信息“Window Acknowledgement Size"给客户端。服务端也会链接connect命令中提到的应用实例。
- 服务端发送"Set Peer Bandwidth"给客户端。
- 客户端在处理完"Set Peer Bandwidth"后 发送“Window Acknowledgement Size"给服务端。
- 服务端发送用户控制消息中的其他消息(StreamBegin)给客户端。
- 服务端发送result命令消息指示客户端请求的状态(成功或失败)。命令中含有transation ID (connect消息总为1)。消息中也可以包含其他属性,如flash media server的版本,另外如level,code, description,object encoding 等。
7.2.1.2. Call
这个call方法可以在接收端执行远程调用(RPC)。 RPC的名称在命令的参数中。
发送端到接收端的,这个命令的结构如下:
procedure Name: RPC的名称。
transaction: 事物ID。
Command Object: 命令对象信息,如果不存在,则为空类型。
Optional Arguments: 可选参数。
这个命令的响应结构如下:
Command Name : 命令的名称。
Transaction ID: 事物ID。
Command Object: 命令对象信息,如果不存在则为空类型。
Response: RPC执行的返回结果。
7.2.1.3 createStream
客户端发送这个命令给服务端用来创建一个逻辑消息渠道(message stream ID),音频、视频和metadata使用这个命令创建的stream进行发布。
NetConnection是一个默认的通信渠道,它的ID是0。一些协议命令消息包括:createStream,使用这个默认的沟通渠道。
下面是客户端发给服务端的命令结构:
Command Name: 命令的名称。为“createStream"。
Transaction ID: 命令的事物ID。
Command Object: 如果不存在则设置为空类型。
下面是服务端回复客户端的命令结构:
Command Name: _result 或 _error;用来表示成功响应或失败响应。
Transaction ID : 对应请求的transaction ID。
Command Object: 如果不存在则为空类型。
Stream ID : 要么是个流ID,要么是个错误信息。
7.2.2 NetStream 命令
NetStream是一个在NetConnection传输音频、视频、消息数据的流。一个NetConnection对象可以包含多个NetStreams或多个 data stream。
下面是客户端向服务端发送的在NetStream中的命令:
- play
- play2
- deleteStream
- closeStream
- receiveAudio
- receiveVideo
- publish
- seek
- pause
服务端通过“onStatus"更新NetStream的状态,onStatus的命令结构如下:
Command Name: 命令的名称。为“onStatus"
Transaction ID: 事物ID,为0。
Command Object: 类型为Null。
Info Object: 一个AMF对象。最好有下面几个属性:“level”:代表这个消息的级别:可以取“warning",“status" 或“error"。 “code": 消息的code,如:“NetStream.Play.Start"; 以及“description",一个人可读的消息描述信息。这个Info Object也可以包含其他适当的消息。
7.2.2.1. play
客户端发送这个命令到服务端播放一个流。一个播放列表也可以创建通过这个命令创建多次。
如果你想创建一个动态的播放列表,可以在不同的直播或录播流直接切换。可以多次调用call,并且对reset字段传false。相反,如果你想立即播放指定的流,并清理其他已经入队的播放流,则reset传true。
客户端向服务端发送这个命令的结构为:
Command Name: 命令的名称,这个命令为“play"
Transaction ID :命令的事物ID,这个设置为0。
Command Object: 命令的信息,如果不存在,则设置为空类型。
Stream Name: 流的名称,播放flv文件,则指定为不包含文件扩展名的文件名(如:sample)。播放Mp3或ID3 tags的话,你必须在之前加上mp3:(如: example,“mp3:sample”)。播放H.264/AAC 文件,你必须在之前加上 mp4:并且指定文件扩展后缀。如:为了播放sample.m4v,需指定为“mp4:sample.m4v"。
Start: 一个可选参数,用于指定开始时间,单位秒。默认是 -2,代表订阅者尝试播放这个直播流,如果没找到的话则播放录播流,如果录播流也没找到的话,则等待这个直播流出现。如果传 -1,则只播放直播流。如果传0或一个正数,则播放一个录播流,并指定到对应的开始时间,如果录播流没找到,则播放播放列表的下一个流。
Duration: 一个可选参数,表示一个录像的时长。默认为-1,直播的话,播放到直到不可用,录播的话播放到结束。如果你传0,它播放录播开始的第一个帧。如果你传一个正数,它播放一个直播流 它会等指定的时间后等它变得有效,或播放一个录播流定位到指定的时间。
Reset: 可选参数,用来决定是否需要冲掉之前的播放列表。
这个流程图执行的命令是:
- 客户端在收到createStream的成功消息后发送play命令给服务端。
- 服务端收到play命令,服务端发送一个协议消息去设置chunk size。
- 服务端发送其他的协议消息(用户控制消息)“StreamIsRecorded" 并且stream ID会在这个消息中。这个消息event type 在开始的2个字节,streamID在最后的4字节。
- 服务端发送其他的协议消息(用户控制消息)“StreamBegin",给客户端指出流的开始。
- 如果客户端播放命令成功后,服务端发送onStatus命令消息,包含NetStream.Play.Start和NetStream.Play.Reset。NetStream.Play.Reset只能够在客户端发送了带reset标记的play后被服务端发送。如果对应的流没有被找到,服务端在onStatus中发送 NetStream.Play.StreamNotFound.
此后,服务端会发送音频数据和视频数据。
7.2.2.2 play2
不像play命令,play2命令可以切换在不变动播放时间的情况下,切换不同比特率的流。服务端必须维护多个不同比特率的文件。
这个命令客户端往服务端发送的结构如下:
Command Name: 命令的名称。“play2"
Transaction ID : 事物ID。
Command Object: 命令信息,如果不存在则为空类型。
Parameters: 一个AMF编码过的对象,他的属性是 ActionScript对象flash.net.NetStreamPlayOptions 中的publish属性。
这个NetStreamPlayOptions对象在ActionScript 3 语言中有描述。
下面是这个命令的时序图:
7.2.2.3. deleteStream
当NetStream对象被销毁时,NetStream发送deleteStream命令。
客户端向服务端发送的这个命令的结构如下:
Command Name: 命令名称,“deleteStream"。
Transaction ID :事务ID,0。
Commond Object: 命令信息,如果没有,设置为Null类型。
Stream ID: 销毁的stream ID。
服务端不会回应这个消息。
7.2.2.4 receiveAudio
NetStream发送这个消息给服务单指出是否应该发送音频数据给客户端。
客户端给服务端发送的命令消息结构如下:
前三个字段同上。
Bool Flag: true 或 false 代表是否应该接收音频。
如果bool flag设置的为 false,服务端不需要回应。如果flag设置的为true,服务端会响应 status 消息,消息中含有NetStream.Seek.Notify和NetStream.Play.Start。
7.2.2.5 receiveVideo
NetStream发送receiveVideo消息指示服务端是否应该发送视频给客户端。
客户端向服务端发送的这个命令消息结构为:
字段含义和receiveAudio类似。
如果bool flag设置的为 false,服务端不需要回应。如果flag设置的为true,服务端会响应 status 消息,消息中含有NetStream.Seek.Notify和NetStream.Play.Start。
7.2.2.6 publish
客户端发送publish命令给服务端,发布并命名一个流。任意客户端可以使用这个名称去播放和接收发布的音频、视频和数据消息。
这个命令客户端到服务端的结构为:
前3个字段含义,同上。
publishing Name: 发布的流名称。
publish Type: 可以设置为 “live”, “record”,“append” 。record: 这个流会被录制并在服务单会保存成一个新文件,在服务端实例对应的子目录下,如果对应的文件存在,则会覆盖。 append: 和record类似,不过如果对应的文件存在,则会追加在后面。 live: 直播数据,不录制。
服务端会使用onStatus响应,意味着发布流开始了。
7.2.2.7 seek
客户端发送seek命令,去定位到一个媒体文件或播放列表的进度。
这个命令从客户端到服务端的结构为:
前3个字段同上。
milliSeconds : 需要定位到的时间,单位:毫秒。
如果seek成功,服务单会发送status消息并包含:NetStream.Seek.Notify。 如果失败,则响应 _error消息。
7.2.2.8 pause
客户端发送pause命令给服务端去暂停或开始播放。
这个命令从客户端到服务端的结构为:
前3个字段同上。
Pause/UnPause flag: true 或 false ,代表暂停还是恢复。
milliSeconds: 暂停或恢复的位置。当客户端流暂停时,这个是当前流的时间。当播放恢复后,服务端将近发送大于这个时间的数据。
当流暂停时,服务端发送status消息,包含NetStream.Pause.Notify。当流恢复时,服务端发送status消息,包含NetStream.Unpause.Notify。如果失败,发送_error 消息。
7.3 消息交换例子
这里介绍一些RTMP消息交换的例子。
7.3.1. Publish Recorded Video
这个例子介绍了一个发布者如何发布一个流,并在之后将视频放进这个流中。其他客户端就可以订阅并播放这个视频。
7.3.2. 广播一个共享对象消息
这个例子介绍了一个共享的对象的创建、被改变。它也解释了一个共享对象被广播的过程。
7.3.3. 从一个录制的流中发布metadata
这个例子描述了发布metadata的消息交互过程。
引用
- [RFC0791] Postel, J., “Internet Protocol”, STD 5, RFC 791, September 1981.
- [RFC0793] Postel, J., “Transmission Control Protocol”, STD 7, RFC 793, September 1981.
- [RFC1982] Elz, R. and R. Bush, “Serial Number Arithmetic”, RFC 1982, August 1996.
- [RFC2119] Bradner, S., “Key words for use in RFCs to Indicate Requirement Levels”, BCP 14, RFC 2119, March 1997.
- [AS3] Adobe Systems, Inc., “ActionScript 3.0 Reference for the Adobe Flash Platform”, 2011, http://www.adobe.com/devnet/actionscript/documentation.html.
- [AMF0] Adobe Systems, Inc., “Action Message Format – AMF 0”, December 2007, http://opensource.adobe.com/wiki/download/attachments/1114283/amf0_spec_121207.pdf.
- [AMF3] Adobe Systems, Inc., “Action Message Format – AMF 3”, May 2008, http://opensource.adobe.com/wiki/download/attachments/1114283/amf3_spec_05_05_08.pdf.
原文
https://rtmp.veriskope.com/docs/spec/