文章目录
前言
视频方面的研究,rtmp里一门必修课,为了方便查阅,也方便初学者入门,我将rtmp一些常规资料整理并共享。
RTMP协议文档
Adobe 官方公布的 RTMP 规范
https://www.adobe.com/devnet/rtmp.html
Adbode 公布的RTMP Specification 1.0 截图,如下:
官方公布的RTMP协议的翻译
Defonds的博客《Adobe 官方公布的 RTMP 规范 》对RTMP协议的翻译,截图,如下:
对RTMP的翻译并添加官方协议之外的补充
王纲博客《RTMP协议规范(转载)》,对官方协议翻译之外还做了补充,还添加了一些代码示例。建议对应官文英文版同步阅读。
维基百科关于 RTMP 的解释
Defonds的博文《实时消息传输协议 RTMP(Real Time Messaging Protocol)》,维基百科关于 RTMP 的解释,截图:
基于librtmp接收RTMP保存为FLV
雷霄骅博客《最简单的基于librtmp的示例:接收(RTMP保存为FLV)》,截图,如下:
RTMP协议
握手
协议是这样的:
- RTMP协议是TCP协议的上层协议,所以必须要先建立TCP连接。
- 客户端向服务器发送C0块(chunks),表示要和服务器握手,C0中包含版本号。
- 服务器收到C0后,检查C0中的版本是否支持,如果支持发送S0作为响应,否则应该终止连接。
- 客户端和服务器都分别等待C1和S1,等待版本确认。
- 客户端收到S1后发送C2,服务器收到C1后发送S2(确认发送,测试握手完成。)
实际是这样的(不是完全按协议来的,如果全按协议来,延迟就要大大增大了):
- RTMP协议是TCP协议的上层协议,所以必须要先建立TCP连接。
- 客户端发送的是C0+C1块,直接告诉服务器我发的版本我自己确认了。
- 服务器更狠,直接发送S0+S1+S2。
- 客户端收到后,发送C2,握手完成!
一个 RTMP 连接以握手开始。由三个固定大小的块组成,而不是可变大小的块加上头。 客户端(发起连接的一方)和服务端各自发送三个相同的块。这些块如果是客户端 发送的话记为 C0,C1 和 C2,如果是服务端发送的话记为 S0,S1 和 S2。
C0和S0消息格式
C0 和 S0 是单独的一个字节 。下面是 C0 和 S0 包的字段说明。
- 版本:8 位 。在 C0 中这个字段表示客户端要求的 RTMP 版本 。在 S0 中这个字段表示服务器选择的RTMP版本。本规范所定义的版本是 3;0-2 是早期产品所用的,已被丢弃; 4-31 保留在未来使用 ;32-255 不允许使用 (为了区分其他以某一字符开始的文本协议)。如果服务无法识别客户端请求的版本,应该返回 3 。客户端可以选择减到版本 3或选择取消握手。
C1和S1消息格式
C1 和 S1 消息有 1536 字节长,由下列字段组成。
-
时间:4 字节 ,本字段包含时间戳。该时间戳应该是发送这个数据块的端点的后续块的时间起始点。 可以是 0,或其他的任何值。为了同步多个流,端点可能发送其块流的当前值。
-
零:4 字节 ,本字段必须是全零。
-
随机数据:1528 字节,本字段可以包含任何值。因为每个端点必须用自己初始化的握手和对端初始化的握
手来区分身份,所以这个数据应有充分的随机性。但是并不需要加密安全的随机值,或者动态值。
C2和S2消息格式
C2 和 S2 消息有 1536 字节长。只是 S1 和 C1 的回复。本消息由下列字段组成。
-
时间:4 字节,本字段必须包含对等段发送的时间(对 C2 来说是 S1,对 S2 来说是 C1)。
-
时间 2:4 字节,本字段必须包含先前发送的并被对端读取的包的时间戳。
-
随机回复:1528 字节,本字段必须包含对端发送的随机数据字段(对 C2 来说是 S1,对 S2 来说是 C1)。
每个对等端可以用时间和时间 2 字段中的时间戳来快速地估计带宽和延迟。但这样做可能并不实用。
分块
握手之后,连接开始复用一个或多个块流。每个块流承载来自一个消息流的一类消息。每个被创建的块都关联到一个唯一的块流 ID。所有的块都通过网络传输。在传输过程中,必须一个块发送完之后再发送下一个块。在接收端,每个块都根据块 ID 被收集成消息。
分块使高层协议的大消息分割成小的消息,保证大的低优先级消息不阻塞小的高优先级消息。分块把原本应该消息中包含的信息压缩在块头中减少了小块消息发送的开销。
块大小是可配置的。这个可以在描述的块消息中完成。最大块是 65535 字 节,最小块是 128 字节。块越大 CPU 使用率越低,但是也导致大的写入,在低带宽下产生 其他内容的延迟。块大小对每个方向都保持独立。
块格式
块由头和数据组成。块头由三部分组成:
-
块基本头:1 到 3 字节 。本字段包含块流 ID 和块类型。块类型决定编码的消息头的格式。长度取决于块流ID。块流 ID 是可变长字段。
-
块消息头:0,3,7 或 11 字节。本字段编码要发送的消息的信息。本字段的长度,取决于块头中指定的块类型。
-
扩展时间戳:0 个或 4 字节。本字段必须在发送普通时间戳(普通时间戳是指块消息头中的时间戳)设置为
0xffffff 时发送,正常时间戳为其他值时都不应发送本值。当普通时间戳的值小于0xffffff时,本字段不用出现,而应当使用正常时间戳字段。
块基本头
块基本头编码块流 ID 和块类型(在下图中用 fmt 表示)。块类型决定编码消息头的格式。块基本头字段可能是 1,2 或 3 个字节。这取决于块流 ID。 一个实现应该用最少的数据来表示 ID。 本协议支持 65597 种流,ID 从 3-65599。ID 0、1、2 作为保留。
- 0,表示 ID 的范围是 64-319(第二个字节+64);
- 1,表示 ID 范围是 64-65599(第三个字节*256+第二个字节+64);
- 2,表示低层协议消息。没有其他的字节来表示流 ID。
- 3-63 表示完整的流 ID。没有其他的字节表示流 ID。
块流 ID 2-63 可以用 1 字节的字段表示 。0-5(不显著的)位表示块流 ID。
块流 ID 64-319 可以用 2-字节表示。ID 计算是(第二个字节+64)
块流 ID 64-65599 可以表示为 3 个字节 。ID 计算为第三个字节*255+第二个字节+64
-
Cs id:6 位,本字段表示范围在 2-63 的块流 ID。值 0 和 1 表示本字段的 2 或 3 字节版本。
-
Fmt:2 位,本字段标识块消息头的 4 种格式。每种流类型的块消息头在下一节中表示。
-
Cs id-64:8-16 位,本字段包含块流 ID 减去 64 的值。例如 365,应 在 cs id 中表示 1,而用这里的 1 6
位表示 301。
块流 ID 在 64-319 范围之内,可以用 2 个字节版本表示,也可以用 3 字节版本表示。
块消息头
有四种格式的块消息 ID,供块流基本头中的 fmt 字段选择。 一个实现应该使用最紧致的方式来表示块消息头。
类型0
0 类型的块长度为 11 字节。在一个块流的开始和时间戳返回的时候必须有这种块。
- 时间戳:3 字节
对于 0 类型的块。消息的绝对时间戳在这里发送。如果时间戳大于或等于 16777215(16 进制 0x00ffffff),该值必须为 16777215,并且扩展时间戳必须出现。 否则该值就是整个的时间戳。
类型1
类型 1 的块占 7 个字节长。消息流 ID 不包含在本块中。块的消息流 ID 与先前的块相同。具有可变大小消息的流,在第一个消息之后的每个消息的第一个块应该使用这个格式。
类型 2
类型2的块占3个字节。既不包含流 ID 也不包含消息长度。本块使用的流ID 和消息长度与先前的块相同。具有固定大小消息的流,在第一个消息之后的每个消息的第一个块应该使用这个格式。
类型 3
类型 3 的块没有头。流 ID,消息长度,时间戳都不出现。这种类型的块使用 与先前块相同的数据。当一个消息被分成多个块,除了第一块以外,所有的块都 应使用这种类型。示例可参考 6.2.2 节中的例 2 。由相同大小,流 ID,和时间间 隔的流在类型 2 的块之后应使用这种块。示例可参考 6.2.1 节中的例 1 。 如果第一个消息和第二个消息的时间增量与第一个消息的时间戳相同,那么 0 类型的块之后必须是 3 类型的块而,不需要类型 2 的块来注册时间增量。如果类 型 3 的块在类型 0 的块之后,那么类型 3 的时间戳增量与 0 类型的块的时间戳相 同。