RTMP规范(重新整理版)
Posted jaketseng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RTMP规范(重新整理版)相关的知识,希望对你有一定的参考价值。
本文是根据Adobe在2012年发布的Rtmp Specification 1.0进行再次翻译和整理而成。
本文翻译集合了网上多种翻译的版本,尽量整理得通顺点方便阅读。
在整理此文过程中,有一个感觉是之所以RTMP协议比较费解,正是因为这份文档的原文本身就是十分不顺畅,内容也比较混乱所致。
以下是正文:
1. 引言
本备忘录用来描述Adobe公司的实时消息协议(RTMP), RTMP是一种应用层协议, 被设计用来对基于底层传输协议(如TCP)的多媒体传输流(如音频、视频和交互数据)进行复用和分包。
3. 名词解释
Payload (有效负载):包含于一个数据包中的数据,例如音频采样或者压缩的视频数据。payload 的格式和解释,超出了本文档的范围。
Packet (数据包):一个数据包由一个固定头和有效负载数据构成。一些个底层协议可能会要求对数据包定义封装。
Port (端口):"传输协议用以区分开指定一台主机的不同目的地的一个抽象。TCP/IP 使用小的正整数对端口进行标识。" OSI 传输层使用的传输选择器相当于端口。
Transport address (传输地址):用于唯一标识一个传输终端的网络地址和端口的组合, 例如IP地址和TCP端口的组合。 数据包从源地址传输到目的地址。
Message stream (消息流):通信中消息流通的一个逻辑通道。
Message stream ID (消息流 ID):每个消息有一个关联的 ID,使用 ID 可以识别出流通中的消息流。
Chunk (块):消息的一个片段。 消息在网络上传输之前会被分成更小的片段,并交错存取。分块确保在多个流中安全的按照时间戳顺序端到端传输。
Chunk stream (块流):允许块向某一确定方向传播的逻辑通讯通道。 块流可以是客户端到服务端, 也可以是服务端到客户端。
Chunk stream ID (块流 ID):每个块有一个关联的 ID,用于标识其所在的块流。
Multiplexing (混合):将分开的音频/视频数据整合为统一的音视频流,以使多个音视频流可以同步传输的过程。
DeMultiplexing (分解):Multiplexing 的反向处理,将交错的音频和视频数据还原成原始音频和视频数据的格式。
Remote Procedure Call (RPC 远程方法调用):允许客户端或服务端调用另一端程序过程的请求。
Metadata (元数据):数据的描述信息。 一个影片的元数据包括名称、时长、创建时间等信息。
Application Instance (应用实例):服务器上允许客户端发起连接请求的应用程序实例。
Action Message Format (AMF 动作消息格式协议):用于序列化ActionScript对象图的一种紧凑二进制格式。 AMF有两个版本: AMF0和AMF3。
4. 字节顺序, 校准和时间格式
所有整数是以网络字节序来承载的,字节 0 代表第一个字节,零位是一个单词或字段最常用的有效位。这种字节序就是所谓的“big-endian"。 这种传输顺序的详细描述在IP协议[RFC0791]。除非另行说明,本文档中的所有数字都是十进制数。
在没有特殊说明的情况下, RTMP中的数据都是按字节对齐的; 例如: 一个16位的字段可能在奇数字节偏移。填充后,填充字节应该是零值。
RTMP中的时间戳是用一个整数来表示的,代表相对于一个未规定的起始时间的毫秒数。通常,每个流的时间戳都从0开始,但这不是必须的,只要通讯的双方用统一的起始时间就可以了。要注意的是,跨流的时间同步(特别是不同主机之间)需要额外的机制来实现。
由于时间戳的长度只有32位,所以只能在50天内循环(49天17小时2分钟47.296秒)。而流是可以不断运行的,可能多年才会结束。所以RTMP应用在处理时间戳时,应该使用序列码算法 [RFC1982],并且能够处理无限循环。例如:一个应用可以假设所有相邻的时间戳间隔不超过2^31-1毫秒, 那么 10000 在 4000000000 之后,而3000000000在4000000000之前。
时间戳增量(Timestamp deltas)也是以毫秒为单位的无符号整数。时间戳增量可以是24位长度也可以是32位长度。
5. RTMP块流(RTMP Chunk Stream)
本章节定义RTMP协议的块流, 它为上层多媒体流协议提供混合和分包的功能。
RTMP块流为配合RTMP协议而设计,它可以处理任何发送消息流的协议。每个消息包含 timestamp 和 payload 类型标识。RTMP 块流和 RTMP 一起适合各种音视频应用,从一对一和一对多直播到点播服务,到互动会议应用。
当使用可靠传输协议(如TCP)时, RTMP块流为所有消息提供了可靠的跨流端对端按时间戳的有序传输。RTMP块流不提供任何优先权或类似形式的控制消息,但是可以由上层协议提供这样的优先级。 例如:一个直播视频服务器可能会基于发送时间或者每个消息的确认时间丢弃一个传输缓慢的客户端的视频消息以确保及时获取其音频消息。
RTMP块流除自身内置的协议控制消息外,还为上层协议提供了用户控制消息的机制。
5.1 消息格式 Message Format
消息格式取决于上层协议,消息可以被分成多个块以支持混合。消息格式必须包含以下创建块所需的字段:
- 时间戳(Timestamp): 消息的时间戳,在块头信息占4个字节。
- 长度(Length): 消息有效负载的长度,如果消息头不能被省略,则消息头的长度也应该包含在长度中。在块头信息占3个字节
- 类型ID(Type Id): 一些类型 ID 保留给协议控制消息使用。控制消息可同时供RTMP块流协议和上层协议使用。而其他所有类型ID都是用于上层协议,RTMP块流会将这些ID类型信息看做未知的信息,因为实际上,RTMP块流不需要用这些值来区分类型。这些非控制信息都可以是相同的类型,上层应用可以用这些字段来区分同步轨道而不是区分类型。本字段在块头信息占1个字节
- 消息流ID(Message Stream ID): 消息流ID可以是任意值。被混合到同一个块流的消息流,会依据其消息流ID进行分解。另外, 就其他相关的块流而言,这个值是未知的。这个字段在块头信息中占4个字节,并且使用little-endian的字节序。
5.2 握手
一个 RTMP 连接以握手开始。RTMP 的握手不同于其他协议;RTMP 握手由三个固定长度的块组成,而不是像其他协议一样的带有报头的可变长度的块。
客户端 (发起连接请求的终端) 和服务器端各自发送相同的三块。便于演示,当发送自客户端时这些块被指定为 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 和 S0 是单独的一个8位字节,可看做一个单独的八位整型域来处理:
以下是 CO 和 S0 包的字段解释:
版本号(8位): 在C0包中,该字段表示客户端请求的RTMP版本。在S0中,该字段表示服务器选择的RTMP版本。本规范所定义的版本是3。其他值,0-2是早期版本所用的,已被丢弃;4-31保留在未来使用;32-255不允许使用(为了区分其他以某一可见字符开始的文本协议)。如果服务器不能识别客户端请求的版本,应该返回3,客户端可能选择降级到版本3,也可能放弃握手。
5.2.3 C1 和 S1 的格式
C1 和 S1 数据包的长度都是 1536 字节,包含以下字段:
时间戳(Time 4字节):这个字段是一个时间戳,用于本终端发送的所有后续块的时间起点。这个值可以是 0,或者任意值。要同步多个块流,终端可以发送其他块流当前的时间戳。
零值 (Zero 4字节):这个字段必须都是 0。
随机数据 (Random data 1528字节):这个字段可以是任意值。终端需要区分出响应来自它发起的握手还是其他方发起的握手,这个数据应该发送一些足够随机的数,但是没必要使用加密安全的随机值或动态值。
5.2.4 C2 和 S2 的格式
C2 和 S2 包的长度固定为1536字节,基本上分别就是 S1 和 C1 的回显(ECHO),包含以下字段:
- 时间戳(Time 4字节):该字段必须是对方发来的时间戳(对C2来说是S1,对S2来说是C1)。
- 时间2(Time2 4字节):该字段必须是前面对方发来的包里的时间戳(C1或S1)。
- 随机数据回显(Random echo 1528字节):该字段必须是对方发来的随机数据字段(对C2来说是S1,对S2来说是C1)。任何一端都可以用时间戳(Time)和时间戳2(Time2)两个字段值和当前时间戳来对带宽和延迟进行快速估算,当然这可能没多大用处。
5.2.5 握手流程示意图
下面是对图中提到的状态的解释:
- Uninitialized: 未初始化状态。在该阶段发送协议版本,客户端和服务端都未初始化。客户端在 C0 包中发送RTMP协议版本,如果服务器支持此版本,服务器将在响应中发送 S0 和 S1 ;如果不支持,服务器采用适当的行为作为响应,按RTMP规范是进行终止连接。
- Version Send: 版本已发送状态。在未初始化状态之后客户端和服务端都进入版本已发送状态。客户端等待接收 S1 包,服务端等待接收 C1 包。收到所等待的包后,客户端发送 C2 包,服务端发送 S2 包。之后状态进入发送确认状态。
- Ack Send: 客户端和服务端等待接收S2和C2包,收到后进入握手完成状态。
- Handshake Done: 握手完成,客户端和服务端开始交换消息。
5.3 分块 Chunking
握手完成后,链接里面混合了一个或多个块流,每个块流承载来自同一个消息流的同一类消息。每个块创建时都会关联着它所属块流的唯一ID(Chunk Stream Id),这些块通过网络进行传输。 在传输过程中,必须是一个块发送完毕之后再发送下一个块。在接收端,这些块会根据块流ID进行组装成消息。
分块可以将在上层协议的大的消息分解为更小的消息,比如说可以保证大的低优先级消息(比如视频)不会阻塞小的高优先级消息(比如音频或控制消息)。
分块还能降低消息发送的开销,它在块头中包含了关于压缩方面的信息,这些信息原本需要在消息中的。
块尺寸是可配置的,块尺寸配置可以通过设置块大小的控制消息进行设定 (参考 5.4.1)。设置大的块尺寸CPU使用率越低,但是在低带宽的情况下,但在低带宽连接时因为它的大量的写入也会延迟其他内容的传递;而设置更小的块不适合高比特率的流。所以块的尺寸设置取决于具体情况。
5.3.1 块格式 Chunk Format
块由块头和数据组成,块头包含3部分:基本头、消息头和扩展时间戳。
- Basic Header (基本头,1 到 3 个字节):该部分是块流ID和块类型,块类型决定了消息头的编码格式。该部分的长度取决于块流ID,块流ID是一个变长字段。
- Message Header (消息头,0,3,7,或者 11 个字节):该部分是所发送消息的描述信息(无论是整个消息还是一部分)。该部分的长度取决于基本头中指定的块类型。
- Extended Timestamp (扩展 timestamp,0 或 4 字节):该部分只有在某些特殊情况下才会使用,是否使用取决于块消息头中的时间戳或时间戳增量,详细信息请参阅5.3.1.3。
- Chunk Data(块数据,变长):块承载的有效数据(Payload),长度最大为配置的块大小。
5.3.1.1 块基本头 Chunk Basic Header
块基本头是块流ID和块类型(在下图中用fmt字段表示),块类型决定了消息头的编码格式,块基本头长度可能是1,2或3字节,这取决于块流ID。
协议实现的开发者应该用能够承载块流ID的最短表示法来表示块流ID。
RTMP最多支持65597个流,流ID 范围在 3 - 65599 内。ID 0,1,2为保留值。0 表示块基本头为2个字节,并且块流ID 范围在 64 - 319 之间(第二个字节 + 64);1 表示块基本头为3个字节,并且ID范围在 64 - 65599 之间(第三个字节*256 + 第二个字节 + 64);3 - 63 范围内的值表示整个流 ID。值 2 是为低版本协议保留的,用于协议控制消息和命令。
块基本头中的 0 - 5 位 (最低有效) 代表块流 ID。
块流ID在2 - 63 范围内的可以用1个字节来编码。
块流ID在64 - 319 范围内可以用2个字节来编码,块流ID为计算所得,公式为:第二个字节值 + 64。
块流ID在64 - 65599 范围内可以用3个字节来编码,块流ID为计算所得,公式为:第三个字节值*255 + 第二个字节值 + 64。
- fmt: 该字段表明了块消息头(Chunk Message Header)使用的4种格式的哪一种,每种块类型的消息头详情,在下一节描述。
- cs id(Chunk Stream ID 6位) 该字段是完整的块流ID,取值在 2 - 63 之间。0,1两个值用于表示这一字段是 2- 或者 3- 字节版本。
- cs id - 64(Chunk Stream ID 8位 或 16位)这一字段包含了块流 ID 减掉 64 后的值。例如,ID 365 等于在 cs id 中会是1 然后这里有 16 位 的 301 (cs id - 64)。也就是 301 + 64 = 365。
块流ID在 64 - 319 范围内的,可以用 2字节 或 3字节 的长度来进行编码。
5.3.1.2 块消息头(Chunk Message Header)
块消息头共有4种不同的格式,根据块基本头中的"fmt"字段值来选择。
协议实现开发者应该用最紧凑的格式来表示块消息头。
5.3.1.2.1 类型 0
类型0的块消息头长度是11个字节,类型0必须用在块流的开头位置,或者每次当块流的时间戳后退的时候(例如向后拖动的操作)。
- timestamp (3字节):对于类型0的消息块,当前消息的绝对时间戳在这里发送。如果时间戳大于或等于16777215(0xFFFFFF),该字段值必须为16777215,并且必须设置扩展时间戳来一起表示32位的时间戳。否则该字段就是完整的时间戳。
其他字段描述请参考5.3.1.2.5节
5.3.1.2.2 类型 1
类型1的块消息头长度是7个字节,不包含消息流ID,该块沿用上一个块的消息流ID。对于传输大小可变长度的流信息(如多数视频格式),应该在第一条信息之后,一直在每条新信息的第一块上面使用相同的类型1格式。
5.3.1.2.3 类型 2
类型2的块消息头长度是3个字节,不包含消息流ID和消息长度,沿用上一个块的消息流ID和消息长度。对于传输固定大小消息的流(如音频和数据格式),应该在第一条信息之后,一直在每条新信息的第一块上面使用相同的类型2格式。
5.3.1.2.4 类型 3
类型3的块没有消息头。消息流ID、消息长度和时间戳增量等字段都不存在,类型3的块都使用上一个块一样的块流ID。当一条消息被分成多个块时,除了第一个块,其他块都应该使用类型3。请参考5.3.2.2节的示例2。
如果组成的流是具有同样的大小、流 ID 和时间间隔的信息,应该是第一块是类型 2,而之后的所有块都使用类型3。请参考5.3.2.1节示例1。
如果第二条消息的时间增量与第一条消息的时间戳增量相同,则类型0的块之后可以马上发送类型3的块,而不必使用类型2的块来注册时间增量。如果类型3的块跟在类型0的块后面,那么类型3的块的时间戳增量与类型块0的时间戳增量相同。
5.3.1.2.5 公共头字段
描述块消息头的字段:
时间戳增量 (timestamp delta 3 字节) :类型1和类型2的块包含此字段,表示前一个块的timestamp字段和当前块timestamp间的差值。 如果时间戳增量大于或等于16777215(0xFFFFFF),该字段必须为16777215,并且必须设置扩展时间戳,来一起表示32位的时间戳增量,否则该字段值就是16777215。
消息长度 (message length 3 字节):类型0和类型1的块包含此字段,表示消息的长度。要注意的是,通常该长度与Payload (有效负载)的长度并不相同。块有效负载长度除了最后一个块,都与块最大长度相同;或者和最后块的剩余长度相同(对于小的信息可能是整个长度)。
消息类型id (message type id 3 字节):类型0和类型1的块包含此字段,表示消息的类型。
消息流ID (message stream id 4 字节):类型0的块包含此字段,表示消息流ID。消息流ID以小字节序存储。通常,相同块流中的消息属于同一个消息流。诚然,可以将不同的消息流放到相同的块流里面,这样会让压缩头信息更有效率,但是当其中一个消息流关闭后,另外一个消息流还继续开着,就没有理由将用第一个块流来发送一个新的类型 0 的块来维持另一个信息流了。
5.3.1.3 扩展时间戳(Extended Timestamp)
扩展时间戳用来辅助编码超过16777215(0xFFFFFF)的时间戳或时间戳增量。当类型0,1或2的块,无法用24位字段来表示时间戳或时间戳增量时就可以启用扩展时间戳,同时类型0块的时间戳字段或类型1,2的时间戳增量字段值应该设为16777215(0xFFFFFF)。当类型3块最近的属于相同块流ID的类型0块、类型1块或类型2块有此字段时,该类型3块也应该有此字段。
5.3.2 示例
5.3.2.1 示例1
这是一个简单的音频流消息,这是示例演示了如何避免信息冗余。
这里是多个信息,但它们都是相同的Messge Stream ID,所以在变成一堆trunk的时候,第一个trunk是完整的header信息(类型0),后面的信息都可以对header进行缩减(类型2,3)。
下图展示该消息流分成的块。从类型3块开始了数据传输优化,之后的块只多加了一个字节(33-32)。
5.3.2.2 示例2
该示例展示了一个超过128字节长度的消息,一条消息被分成了数个块。
下图是被分成的块
第一个块的头信息表示这一个消息总大小为307字节。(128+128+51 = payload长度)
注意这两个示例,类型3块可以在两种情况下使用。第一种情况是在多个相同payload长度trunk的时候,当前面已经有完整的header信息时,接下来的新trunk可以接着使用类型3trunk以达到优化的效果。另一种情况是,当同一条信息被拆分时,作为后续的trunk类型对前面trunk进行补充。
5.4 协议控制消息 Protocol Control Messages
RTMP块流使用类型ID 1,2,3,5和6来作为协议控制消息,这些数据包含RTMP块流协议所需要的内容。
这些信息都在Payload里面
这些协议控制消息必须用 0 作为消息流ID(Message Stream ID)以便标识为控制信息,并且会在快流ID 为2的块中传输。协议控制消息收到后立即生效,它们的时间戳timestamp信息是被忽略的。
5.4.1 设置块大小(1)Set Chunk Size (1)
协议控制消息 1,设置块大小 Set Chunk Size,用于通知另一端新的最大块的大小。
默认的最大块大小为128字节,客户端或服务端可以改变这个大小,并用本消息通知另一端。例如,假设一个客户端想要发送131字节的音频数据,而这时候最大块大小为128,那么客户端可以向服务端发送本消息,通知服务端最大块大小被设置为了131字节。这样客户端之后只要用一个块就可以发送这些音频数据。
最大块大小,通常应该不低于128字节,也不能小于1字节。客户端或服务端的最大块大小是各自维护的。
0:首位必须为0
chunk size(块大小 31位):该字段保存新的最大块大小,以字节为单位,该值将作用于后续的所有块的发送,直到收到新的通知。有效值为 1 到 2147483647 (0x7FFFFFFF,1 和 2147483647 都是可用的);但是所有大于 16777215 (0xFFFFFF)的大小值是等同于16777215,因为没有一个块比一整个消息大,也没有一个消息是大于 16777215 字节的。
5.4.2 终止消息(2)Abort Message(2)
协议控制消息 2,终止消息 Abort Message,通知正在等待消息后续块的另一端,可以丢弃指定块流ID前面接收到的数据。该消息有效负载payload为块流ID。一端的程序可能在关闭的时候发送该消息,用来表明后面的消息没有必要继续处理了。
chunk stream id(块流ID 32字节):该字段是可以丢弃当前消息的块流ID。
5.4.3 确认消息(3)Acknowledgement(3)ACK
客户端或服务器在收到等同于窗口大小的字节的数据后,必须向对端发送一个确认消息ACK。窗口大小是发送端发送的最大字节数,无论有没有收到接收端发送的确认消息。该消息表示为一个序列号格式,也就是到当前时间为止接收到的字节总数。
sequence number(序列号 32 位):到当前时间为止接收到的字节总数。
5.4.4 窗口大小确认信息(5)Window Acknowledgement Size(5)
客户端或服务端发送该消息来通知对方发送确认消息(ACK)所使用的窗口大小,并等待对方发送回确认消息(ACK)。对方(接收端)在接收到窗口大小确认信息后必须发送确认消息(ACK)。
5.4.5 设置对等带宽(6)Set Peer Bandwidth (6)
客户端或服务端发送该消息来限制对方的输出带宽。接收端收到消息后,通过将已发送但尚未被确认的数据总数限制为该消息指定的窗口大小,来实现限制输出带宽的目的。如果窗口大小与上一个窗口大小不同,则该消息的接收端应该向该消息的发送端发送窗口大小确认消息。
Limit Type(限制类型)有以下可选值:
- 0 - Hard: 消息接收端应该将输出带宽限制为指定窗口大小
- 1 - Soft: 消息接收端应该将输出带宽限制为指定窗口大小和当前窗口大小中较小的值
- 2 - Dynamic: 如果上一个消息的限制类型为Hard,则该消息同样为Hard,否则抛弃该消息。
6. RTMP消息格式 RTMP Message Formats
本章描述RTMP消息遵循底层协议(比如RTMP块流)在网络传输时的消息格式。
虽然RTMP被设计成使用RTMP块流传输,但是它也可以使用其他传输协议来发送消息。RTMP块流协议和RTMP协议配合时,非常适合音视频应用,包括一对一和一对多实时直播、视频点播和视频互动会议等。
6.1 RTMP消息格式 RTMP Message Format
服务端和客户端通过在网络上发送RTMP消息实现之间的交互,消息包括但不限于音频、视频、数据或其他信息。
RTMP消息有两部分,消息头和有效负载。
6.1.1 消息头 Message Header
消息头包含以下信息:
- Message Type:消息类型,1个字节。消息类型ID为 1 - 6 的是为协议控制消息保留的。
- Payload Length:有效负载的字节数(长度),3个字节。该字段是用大字节序(big-endian)表示的。
- Timestamp:时间戳,4个字节,用大字节序(big-endian)表示。
- Message Stream ID:消息流ID,标识消息所使用的流,用大字节序(big-endian)表示。
6.1.2 消息有效负载 Message Payload
消息的另一部分就是有效负载,也是消息包含的实际数据,比如说音频样本或者压缩的视频数据。有效负载的格式不再本文档的讨论范围之内。
6.2 用户控制消息(4)User Control Messages(4)
RTMP协议使用 消息类型 4 (message type ID)作为用户控制消息ID,这些消息包含RTMP流所需的必要信息。消息类型1,2,3,5 和 6 由 RTMP块流协议 (Chunk Stream protocol (Section 5.4))使用。
用户控制消息应该使用ID为 0 的消息流(message stream ID 0,以被看做为控制流),并且通过RTMP块流传输时使用ID为2(chunk stream ID)的块流。用户控制消息一旦被接收立即生效,它们的时间戳信息会被忽略。
客户端或服务端通过发送该消息告知对方用户操作事件。该消息携带事件类型和事件数据两部分。
开头的2个字节用于指定事件类型,紧跟着是事件数据。事件数据字段长度可变,但是如果用RTMP块流传输,则消息总长度不能超过最大块大小(the maximum chunk size (Section 5.4.1)),以使消息可以使用一个单独的块进行传输。
事件类型和对应的事件数据格式,在7.1.7章节详细介绍。
7. RTMP指令消息 RTMP Command Messages
本章描述客户端和服务端进行交互的各种类型的信息。
各种类型的消息在客户端和服务端之间进行传输,包括用于发送音频数据的音频消息,用于发送视频数据的视频消息,用于发送任意用户数据的数据消息,共享对象消息和指令消息等。共享对象消息可以管理分布在不同客户端和相同服务器的共享数据提供了规范途径。指令消息携带客户端与服务端之间的AMF编码指令,客户端或服务端也可以通过指令消息来实现远程过程调用(RPC)。
7.1 消息类型
客户端和服务端通过在网络上发送消息来实现交互,消息可以是任意类型,包括但不限于音频消息、视频消息、指令消息、共享对象消息、数据消息和用户控制消息。
7.1.1 命令消息(20, 17)Command Message
命令消息在客户端和服务器端传递 AMF 编码的命令。消息类型20代表AMF0编码,消息类型17代表AMF3编码。发送这些消息来完成连接、创建流、发布、播放、暂停等操作。像状态、结果这样的指令消息,用于通知发送方请求的命令的状态情况。一条命令消息由命令名、事务ID和包含相关参数的命令对象。客户端或服务端还可以通过命令消息来实现远程过程调用(RPC)。
7.1.2 数据消息(18, 15) Data Message
客户端或服务端通过该类型的消息来发送元数据或其他数据。元数据包括数据(音频、视频)的创建时间、时长、主题等详细信息。消息类型18代表AMF0编码,消息类型15代表AMF3编码。
7.1.3 共享对象消息(19,16) Shared Object Message
共享对象是一个作为传输数据的 Flash 对象(键值对集合)。消息类型19代表AMF0编码,消息类型16代表AMF3编码。每个消息都可以包含多个事件。
支持以下事件类型:
事件 | 中文 | 取值 | 描述 |
Use | 创建 | 1 | 客户端向服务端发送,通知指定名称的共享对象已经被创建。 |
Release | 释放 | 2 | 客户端通知服务端,共享对象已在本地删除 |
Request Change | 请求更新 | 3 | 客户端请求修改共享对象的属性值 |
Change | 更新 | 4 | 服务端向除请求发送方外的其他客户端发送,通知其有属性的值发生了变化。 |
Success | 成功 | 5 | “请求更新”(Request Change)事件被接受后,服务端向发送请求的客户端回复此事件 |
SendMessage | 发送消息 | 6 | 客户端向服务端发送此事件,要求服务端进行广播消息。服务端收到此事件后向所有客户端广播一条消息,包括请求方客户端 |
Status | 状态 | 7 | 服务端发送此事件来通知客户端错误信息 |
Clear | 清除 | 8 | 服务端向客户端发送此事件,通知客户端清除一个共享对象。服务端在回复客户端的“创建”(Use)事件时也可能发送此事件 |
Remove | 移除 | 9 | 服务端发送此事件,使客户端删除一个slot(键值对) |
Request Remove | 请求移除 | 10 | 客户端删除一个slot(键值对)时发送此事件 |
Use Success | 创建成功 | 11 | 当连接成功时服务端向客户端发送此事件 |
7.1.4 音频消息(8)Audio Message
客户端或服务端通过发送此消息来发送音频数据给对方,消息类型8是为音频消息预留的。
7.1.5 视频消息(9)Video Message
客户端或服务端通过发送此消息来发送视频数据给对方,消息类型9是为视频消息预留的。
7.1.6 组合消息(22)Aggregate Message
组合消息,一条组合消息包含多个子RTMP消息,子消息符合6.1章节所描述的消息格式。消息类型22用于组合消息。
组合消息的消息流ID会覆盖其中子消息的消息流ID。
组合消息的时间戳和其中第一个子消息的时间戳的差值,是用来将所有子消息的时间戳重整为流时间标尺的位移量。位移量会加到每一个子消息的时间戳上来换算出正常的流时间。所以,第一个子消息的时间戳应该与组合消息的时间戳相同,所以位移量应该为0。
Back Pointer(反向指针)包含前一个消息的长度(包括消息头),这样就能符合flv文件格式,并用于进行向前查找操作。
使用组合消息有以下好处:
- 块流协议中,一个块最多只能发送一个消息,而使用组合消息,加大块大小,从而减少发送的块的数量。
- 子消息在内存中连续存放,这样系统调用网络发送数据的性能更高。
7.1.7 用户控制消息事件 User Control Message Events
客户端或服务器通过该消息发送用户控制事件,用户控制消息格式请参考6.2章节(用户控制消息 User Control Messages)
用户控制消息支持以下事件:
事件 | 名称 | 取值 | 描述 |
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 | ping请求 | 6 | 服务端发送该事件,用来探测客户端是否处于可达状态。事件数据是一个4字节的时间戳,表示服务端分发该事件时的服务器本地时间。客户端收到后用ping响应(PingResponse)回复服务端。 |
PingResponse | ping响应 | 7 | 客户端用该事件回复服务端的ping请求,事件数据为收到的ping请求(PingRequest)中携带的4字节的时间戳。 |
7.2 命令类型 Types of Commands
客户端和服务器端交互的命令采用 AMF 编码。发送的命令消息由命令名、事务 ID 以及包含有各种参数的命令对象组成。例如,包含有 'app' 参数的连接命令,这个命令提供了客户端需要连接到的服务端应用名,作为参数。接收者处理这命令同时发回同样事务 ID 的作为响应。回复的字符串可以是 _result、_error 或者 任意方法名,比如,verifyClient 或者 contactExternalServer。
字符串 _result 或者 _error 都是回复时使用的命令信息。回复的命令里面包含了原命令的事务 ID。这和 AMAP 或者其他协议的处理方式一致。而发送者回复了方法名是试图在接收端执行此方法。
以下对象用于发送不同的命令:
NetConnection 代表上层的服务器端和客户端之间连接的对象。
NetStream 一个代表发送音频流、视频流和其他相关数据的通道的对象。当然,也可以发送控制数据流的命令,如 play、pause 等等。
7.2.1 NetConnection 命令
NetConnection 管理着客户端和服务端之间的相互连接。此外,它还提供远程方法的异步调用。
NetConnection 会承载以下这些命令:
- connect
- call
- close
- createStream
7.2.1.1 connect 命令
客户端发送 connect 命令到服务器端来发起一次网络链接。
connect 命令结构如下:
字段名 | 类型 | 描述 |
Command Name | 字符串 | 命令名。这里是设置为"connect"。 |
Transaction ID | 数字 | 总是设置为 1。 |
Command Object | 对象 | 键值对的命令信息 |
Optional User Arguments | 对象 | 其他可选信息。 |
以下是为 connect 命令中使用的键值对命令信息(Command Object)的描述:
属性名 | 类型 | 描述 | 示例值 |
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 | |
objectEncoding | 数字 | AMF 编码方法 | AMF3 |
audioCodecs 属性的标识值:
videoCodecs 属性的标识值:
videoFunction 属性的标识值:
对象编码属性(object encoding)属性值:
服务端发给客户端的命令结构如下:
connect 命令的信息流向:
这个过程中connect命令的信息流如下:
- 客户端发送 connect 命令到服务端,请求与服务端应用实例进行连接。
- 收到 connect 命令后,服务端发送协议消息 '窗口确认大小’(Window Acknowledgement Size)到客户端。服务端同时会链接上connect命令里面提到的应用示例。
- 服务端发送协议消息 '设置对端带宽’(Set Peer Bandwidth)到客户端。
- 客户端在处理完协议消息 '设置对端带宽’之后,会发送协议消息 '窗口确认大小' 到服务端。
- 服务端发送另一个用户控制消息 (StreamBegin) 到客户端。
- 服务端发送结果(_result)的命令消息告知客户端连接状态 (success/fail)。这个命令定义了事务 ID (在 connect 命令通常设置为 1)。这个命令还定义了其他的属性,比如 FMS (Flush Media Server)服务器版本 (字符串)。此外,它还定义了其他链接相关的信息,比如 level (字符串)、code (字符串)、description (字符串)、objectencoding (数字) 等等。
7.2.1.2 Call 方法
NetConnection 对象的 Call 方法可以执行接收端的远程方法调用 (PRC)。Call命令将要被调用的 PRC 名字作为传输的参数。
发送端到接收端的命令结构如下:
字段名 | 类型 | 描述 |
Procedure Name | 字符串 | 需要调用的远程方法的名字。 |
Transaction ID | 数字 | 如果发送端需要,可以设置一个事务 ID。否则传 0 值即可。 |
Command Object | 对象 | 如果需要命令相关信息可以设置此对象,否则为空值(null)。 |
Optional Arguments | 对象 | 任意需要的可选参数。 |
响应端回复的信息结构如下:
字段名 | 类型 | 描述 |
Command Name | 字符串 | 命令名 |
Transaction ID | 数字 | 响应所属的命令 ID |
Command Object | 对象 | 如果需要命令相关信息可以设置此对象,否则为空值(null) |
Response | 对象 | 调用方法的回复 |
7.2.1.3 createStream 命令
客户端发送createStream命令到服务端,来创建新的逻辑通道。使得音频、视频和元数据都可以用这个通道来传输。
NetConnection 是流 ID 为 0的默认通道,流 ID 为 0。传输协议和部分命令消息,包括 createStream命令,都会使用默认通道。
客户端发送到服务端的命令结构如下:
字段名 | 类型 | 描述 |
Command Name | 字符串 | 命令名。这里是 "createStream"。 |
Transaction ID | 数字 | 命令的事务 ID。 |
Command Object | 对象 | 如果需要命令相关信息可以设置此对象,否则为空值(null) |
服务端发给客户端的命令结构如下:
字段名 | 类型 | 描述 |
Command Name | 字符串 | _result 或者 _error;指明回复是一个结果还是错误 |
Transaction ID | 数字 | 响应所属的命令 ID |
Command Object | 对象 | 如果需要命令相关信息可以设置此对象,否则为空值(null) |
Stream ID | 数字 | 返回值要么是流 ID,要么是一个错误信息的对象 |
7.2.2 NetStream 命令
NetStream 定义了承载音频流、视频流以及数据消息流的channel,该channel可以在双端建立的NetConnection通道之上进行传输。一个NetConnection对象可以支持多个信息流的多个NetStream。
客户端发给服务端的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" (字符串):这个人类可以阅读的描述信息。 |
7.2.2.1 play 命令
客户端发送play命令到服务端来播放流。也可以多次使用play命令来创建一个播放列表。
如果想要创建一个动态的播放列表,让这个播放列表可以在不同的直播流或者录制流之间进行切换播放的话,多次调用 play 方法,调用时传 false 进行重置。相反的,如果想要立即播放指定流,并且其他等待播放的流清空,可以传 true 值。
客户端发给服务端的命令如下:
字段名 | 类型 | 描述 |
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。意味着直播流会一直播放直到它不再可用或者录制流一直播放到结束。 传递 0 值,它将只播放一帧(?),这是因为播放时间已经在录制流的开头的 Start 字段指定了,并且假定定义的值大于或者等于 0。 传递正数值,直播流将播放Duration定义的时长,之后直播间会是可用状态;录制流会直接播放Duration定义的时长。(如果流在 Duration 字段定义的时间段内结束,那么流结束时播放结束)。 传递-1 以外的负数,将当做 -1 处理。 |
Reset | 布尔 | 可选的布尔值或者数字,定义了是否对之前的播放列表进行清空。 |
play 命令执行的过程:
- 当客户端从服务端接收到 createStream 命令的结果是为 success 时,发送 play 命令。
- 接收到 play 命令后,服务端发送协议消息来设置块大小(Set Chunk Size)。
- 服务端同时发送另一个消息 (控制用户信息),这个消息中定义了 'StreamIsRecorded' 事件和流 ID。消息前面2字节是事件类型,随后4个字节中是流 ID。
- 服务端同时发送另一个消息 (控制用户信息),消息包含 'StreamBegin' 事件,来指示发送给客户端的流的起点。
- 如果 play 命令成功,服务端回复 onStatus 命令消息 NetStream.Play.Start 和 NetStream.Play.Reset。其中NetStream.Play.Reset只有当客户端发送的 play 命令里设置了 reset 时才会发送。如果要播放的流没有找到,服务端会发送 onStatus 消息 NetStream.Play.StreamNotFound。
之后,服务端返回视频和音频数据,客户端对其进行播放。
7.2.2.2 play2
不同于 play 命令的是,play2 可以在不改变播放时间轴的情况下切换到不同的比率的流。
客户端可以通过play2来播放服务端保存的各种比率的文件,只要这些比率都在支持的范围内。
客户端发给服务端的命令如下:
字段名 | 类型 | 描述 |
Command Name | 字符串 | 命令名 "play2" |
Transaction ID | 数字 | 事务 ID 设置为 0 |
Command Object | Null | 命令信息不存在,设置为 null 类型 |
Parameters | 对象 | 一个 AMF 编码的对象,该对象的属性是为Public 对应的是ActionScript 对象 flash.net.NetStreamPlayOptions 的属性。 |
NetStreamPlayOptions 对象的Public 属性在 ActionScript 3 语言指南中 [AS3] 有所描述。
play2 命令执行的过程:
7.2.2.3 deleteStream 命令
当 NetStream 对象销毁时, NetStream 会发送 deleteStream 命令。
字段名 | 类型 | 描述 |
Command Name | 字符串 | 命令名 "deleteStream" |
Transaction ID | 数字 | 事务 ID 设置为 0 |
Command Object | Null | 命令信息对象不存在,设为 null 类型 |
Stream ID | 数字 | 服务端销毁的流 ID。 |
服务端不再发送任何回复。
7.2.2.4 receiveAudio 命令
NetStream 通过发送 receiveAudio 消息来通知服务端是否需要发送音频给客户端。
字段名 | 类型 | 描述 |
Command Name | 字符串 | 命令名"receiveAudio" |
Transaction ID | 数字 | 事务 ID 设置为 0 |
Command Object | Null | 命令信息对象不存在,设置为 null 类型 |
Bool Flag | 布尔 | true 或者 false 以表明是否需要音频 |
如果receiveAudio 命令的Bool Flag设置为false,服务端不需要任何的回应。如果是true,服务端应当回复状态信息,包含NetStream.Seek.Notify 和 NetStream.Play.Start。
7.2.2.5 receiveVideo 命令
NetStream 通过发送 receiveVideo 消息来通知服务端是否需要发送视频给客户端。
字段名 | 类型 | 描述 |
Command Name | 字符串 | 命令名"receiveVideo" |
Transaction ID | 数字 | 事务 ID 设置为 0 |
Command Object | Null | 命令信息对象不存在,设置为 null 类型 |
Bool Flag | 布尔 | true 或者 false 以表明是否需要视频 |
如果receiveVideo 命令的Bool Flag设置为false,服务端不需要任何的回应。如果是true,服务端应当回复状态信息,包含NetStream.Seek.Notify 和 NetStream.Play.Start。
7.2.2.6 publish 命令
客户端发送publish命令来发布一个已命名的流。通过流名字,任意客户端都可以接收该流上已发布的音频、视频以及数据消息,进行播放。
字段名 | 类型 | 描述 |
Command Name | 字符串 | 命令名 "publish" |
Transaction ID | 数字 | 事务 ID 设置为 0 |
Command Object | Null | 命令信息对象不存在,设置为 null 类型 |
Publishing Name | 字符串 | 要发布的流名字。 |
Publishing Type | 字符串 | 发布类型。可以设置为 "live"、"record" 或者 "append”。 record:发布流的数据会被录制到成新的文件。文件存储在服务器应用目录的路径里。如果文件已存在,将被覆盖。 append:发布流的数据会被追加到原有录制文件中,如果文件不存在,则创建新的录制文件。 live:流只发布,不进行录制。 |
服务器端回复 onStatus 命令以标注发布的起始位置。
7.2.2.7 seek 命令
客户端发送 seek 命令以寻找(seek)一个媒体文件或一个播放列表的偏移量offset (offset,以毫秒为单位)。
字段名 | 类型 | 描述 |
Command Name | 字符串 | 命令名 "seek" |
Transaction ID | 数字 | 事务 ID 设为 0 |
Command Object | Null | 没有命令信息对象,设置为 null 类型 |
milliSeconds | 数字 | 寻址的毫秒 转使用BBB的device tree和cape(重新整理版)只要你想用BBB做哪怕一丁点涉及到硬件的东西,你就不可避免地要用到 cape和device tree的知识。所以尽管它们看起来很陌生而且有点复杂,但还是得学。其实用起来不难的。下面我只讲使用时必须会的内容,不深究其工作原理。文中基本没有 废话,请仔细阅读每个字,勿遗漏细节。
我们已经知道beagleboard官网上有一些官方的硬件外设,比如lcd显示屏之类的,他们管这些外设叫做cape。其实应该说只要是修改了芯 片引脚功能,或占用了空闲的引脚的东西,都可以叫做cape。比如之前我们提到的开启某些引脚的I2C功能,其实也是给设备添加了一个虚拟的 (virtual)cape。当我们想要使用一个cape的时候,需要做两件事:配置BBB引脚的功能,启动相应的驱动程序。而device tree基本就是用来干这两件事的。
下面我们就来依次认识device tree文件,修改dts文件,编译dts文件,加载device tree,验证是否加载成功。
一、认识device tree文件那么device tree具体是长什么样的呢?首先要知道它们有三种格式:一个方便人类阅读的源文件*.dts(device tree source),另两个是经过编译送给系统使用的文件*.dtb(device tree blob)和*.dtbo(device tree blob overlay)。
在BBB的/lib/firmware/目录下,你可以看到很多*.dts文件。我们随便打开一个(BB-UART1-00A0.dts)看看它们长什么样:
不要被dts文件复杂的外表吓到!dts文件确实有一定的编写规则,但是这不是我们要操心的。因为BBB已经提供了足够多现成的dts文件,我们要做的就是:1、看懂它们,2、学会复制和粘贴。 二、修改dts文件我们的最终目的是写出符合自己需求的dts文件,那么单单复制粘还是不够的,我们有时候需要修改里面那些属性的值。那么这些属性都是什么含义
呢?其实它们都是有据可循的:www.kernel.org/doc/Documentation/devicetree/bindings/ 。提示一
点,文档有点乱,请多用Ctrl+F。
前面说到dts里主要有两部分内容:修改BBB引脚功能和启动驱动程序。上面的网址里只告诉了驱动程序的属性,那么引脚功能该如何配置呢? 以前面给出的代码为例,这几行代码是用来配置引脚功能的:
如何查看BBB当前的引脚功能呢?
一般这句后面还会加上grep命令来显示特定的引脚功能,同样在《BBB引脚功能速查表》中第三列找到P9.24的地址是0x984,所以可以这样查找:
另外一个要注意的是每个dts里根节点下都有这两个属性:
三、编译dts文件实际上,dts和dtbo文件可以随时编译和反编译,即dts可以生成dtbo,dtbo也可以复原成dts(但是复原的dts里没有注释等无用的东西了)。编译和反编译使用的命令都是相同的:dtc(device tree compile)。 dts编译成dtbo:
四、加载dtbo文件加载之前,一定记住要把编译好的dtbo文件放到/lib/firmare/目录中,否则程序是找不到你的dtbo文件的。
Beaglebone Black中用一个叫做cape manager的软件管理所有的cape,不论它是实实在在的扩展板,还是虚拟的cape。这个软件的目录是 /sys /devices/bone_capemgr.8/(这里的数字也有可能是9,与启动顺序有关,你可以直接用*代替它)。这个目录内有一个叫做slots 的文件,这就是capemgr这个软件的对外接口。我们要加载某个cape的话,只需要向这个文件中写入dts文件里定义的名字(part-number 属性)即可:
(注:如果那个dtbo有多个版本,比如有00A0,00A1,00A2这3个版本,如果你只写 echo BB-UART1 > $SLOTS 的话,它会自动加载最新的版本。而且,必须保证从00A0开始每个版本都存在才可以成功加载,就是说,如果/lib/firmware/目录中只有 00A2这一个版本的话,加载会失败。但是,你可以通过 echo BB-UART1:00A2 > $SLOTS 像这样添加版本号来加载某个特定版本。) 使用命令:
BBB可以插入4个实体cape,它们只能插在0、1、2、3这四个slot里,这也是1、2、3号slot是空白的原因。后面的slot里都是虚拟cape,只要引脚不冲突,可以不限数量地添加。一旦你的dtbo文件使用的引脚与已加载的cape有冲突,就会提示:
至于卸载cape,假设我要卸载我的第8个cape,按照官方的说法,应当这样操作:
以上是关于RTMP规范(重新整理版)的主要内容,如果未能解决你的问题,请参考以下文章 视频流媒体推流平台RTMP协议是如何进行网络连接并推送视频流的? JavaCV音视频开发宝典:rtsp拉流转码方式转推到rtmp |