RTMP(Real Time Messaging Protocol 实时消息传输协议)
RTMP是由Adobe公司提出的,在互联网TCP/IP五层体系结构中应用层,RTMP协议是基于TCP协议的,也就是说RTMP实际上是使用TCP作为传输协议。
TCP协议在处在传输层,是面向连接的协议,能够为数据的传输提供可靠保障,因此数据在网络上传输不会出现丢包的情况。
不过这种可靠的保障也会造成一些问题,也就是说前面的数据包没有交付到目的地,后面的数据也无法进行传输。幸运的是,目前的网络带宽基本上可以满足RTMP协议传输普通质量视频的要求。
RTMP传输的数据的基本单元为Message,但是实际上传输的最小单元是Chunk(消息块),因为RTMP协议为了提升传输速度,在传输数据的时候,会把Message拆分开来,形成更小的块,这些块就是Chunk。
Message 结构:
- Message Type:它是一个消息类型的ID,通过该ID接收方可以判断接收到的数据的类型,从而做相应的处理。
- Message Type ID在1-7的消息用于协议控制,这些消息一般是RTMP协议自身管理要使用的消息,用户一般情况下无需操作其中的数据。
- Message Type ID为8,9的消息分别用于传输音频和视频数据。
- Message Type ID为15-20的消息用于发送AMF编码的命令,负责用户与服务器之间的交互,比如播放,暂停等。
- Playload Length: 消息负载的长度,即音视频相关信息的的数据长度,4个字节
- TimeStamp:时间戳,3个字节。
- Stream ID:消息的唯一标识。拆分消息成Chunk时添加该ID,从而在还原时根据该ID识别Chunk属于哪个消息。
- Message Body:消息体,承载了音视频等信息。
Chunk 结构:
- Basic Header:基本的头部信息,在头部信息里面包含了chunk stream ID(流通道Id,用来标识指定的通道)和chunk type(chunk的类型)。
- Message Header:消息的头部信息,包含了要发送的实际信息(可能是完整的,也可能是一部分)的描述信息。Message Header的格式和长度取决于Basic Header的chunk type。
- Extended TimeStamp:扩展时间戳。
- Chunk Data:块数据。
RTMP在传输数据的时候,发送端会把需要传输的媒体数据封装成消息,然后把消息拆分成消息块,再一个一个进行传输。
接收端收到消息块后,根据Message Stream ID重新将消息块进行组装、组合成消息,再解除该消息的封装处理就可以还原出媒体数据。
由此可以看出,RTMP收发数据是以Chunk为单位,而不是以Message为单位。需要注意的是,RTMP发送Chunk必须是一个一个发送,后面的Chunk必须等前面的Chunk发送完成。
工作流程:
RTMP协议是应用层协议,是要靠底层可靠的传输层协议(通常是TCP)来保证信息传输的可靠性的。
在基于传输层协议的链接建立完成后,一个RTMP协议的流媒体推流需要经过以下几个步骤:
- 握手(RTMP连接都是以握手作为开始)
- 建立连接 (建立客户端与服务器之间的“网络连接”)
- 建立流 (建立客户端与服务器之间的“网络流”)
- 推流 (传输视音频数据)
1. 握手
在RTMP连接建立后,服务端与客户端需要通过3次交换报文完成握手,握手与其他的协议不同,是由三个静态大小的块,而不是可变大小的块组成的,客户端与服务器发送相同的三个chunk:
- 客户端发送C0,C1,C2
- 服务端发送S0,S1,S2
其数据格式如下:
C0、S0:
长度是一个字节,这个字段表示服务器选择的 RTMP 版本。
- RTMP1.0规范所定义的版本是 3;
- 0-2 是早期产品所用的,已被丢弃;
- 4-31保留在未来使用;
- 32-255 不允许使用(为了区分其他以某一字符开始的文本协议)。
如果服务无法识别客户端请求的版本,应该返回 3 。客户端可以选择减到版本 3 或选择取消握手。
C1、S1:
1536 字节长,由下列字段组成:
- 时间:4 字节 本字段包含时间戳。该时间戳应该是发送这个数据块的端点的后续块的时间起始点。可以是 0,* 或其他的 任何值。为了同步多个流,端点可能发送其块流的当前值。
- 零:4 字节 本字段必须是全零。
- 随机数据:1528 字节。 本字段可以包含任何值。 因为每个端点必须用自己初始化的握手和对端初始化的握手来区分身份,所以这个数据应有充分的随机性。但是并不需要加密安全的随机值,或动态值。
C2、S2:
1536 字节长。分别是对于S1 和 C1 的回复。本消息由下列字段组成。
- 时间:4 字节 本字段必须包含对等段发送的时间(对 C2 来说是 S1,对 S2 来说是 C1)。
- 时间2:4 字节 本字段必须包含先前发送的并被对端读取的包的时间戳。
- 随机回复:1528 字节本字段必须包含对端发送的随机数据字段(对 C2 来说是 S1,对 S2 来说是 C1)。每个对等端可以用时间和时间2字段中的时间戳来快速地估计带宽和延迟。 但这样做可能并不实用。
其发送规则如下:
- 客户端发送 C0,C1 块,握手开始。
- 客户端在发送 C2 之前客户端必须等待接收 S1 。
- 客户端在发送任何数据之前客户端必须等待接收 S2。
- 服务端在发送 S0 和 S1 之前必须等待接收 C0,也可以等待接收 C1。
- 服务端在发送 S2 之前必须等待接收 C1。
- 服务端在发送任何数据之前必须等待接收 C2。
RTMP握手的这个过程完成了两件事:
- 校验客户端和服务器端RTMP协议版本号
- 发了一堆数据,猜想应该是测试一下网络状况,看看有没有传错或者不能传的情况
2. 建立连接
- 客户端发送命令消息中的“连接”(connect)到服务器,请求与一个服务应用实例建立连接。
- 服务器接收到连接命令消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到客户端,同时连接到连接命令中提到的应用程序。
- 服务器发送设置带宽协议消息到客户端。
- 客户端处理设置带宽协议消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到服务器端。
- 服务器发送用户控制消息中的“流开始”(Stream Begin)消息到客户端。
- 服务器发送命令消息中的“结果”(_result),通知客户端连接的状态。
注意:
- 这里面的connect 命令消息,命令里面包含什么东西,协议中没有说,真实通信中要指定一些编解码的信息,这些信息是以AMF格式发送的, 其中audioCodecs和videoCodecs这两个指定音视频编码信息的不能少的。
- Window Acknowledgement Size 是设置接收端消息窗口大小,一般是2500000字节,即告诉客户端你在收到我设置的窗口大小的这么多数据之后给我返回一个ACK消息,告诉我你收到了这么多消息。在实际做推流的时候推流端要接收很少的服务器数据,远远到达不了窗口大小,所以基本不用考虑这点。而对于服务器返回的ACK消息一般也不做处理,我们默认服务器都已经收到了这么多消息。
- 服务器返回的_result命令类型消息的payload length一般不会大于128字节,但是在最新的nginx-rtmp中返回的消息长度会大于128字节,所以一定要做好收包,组包的工作。
3. 建立流
创建完网络连接之后就可以创建网络流了
- 客户端发送命令消息中 releaseStream 命令到服务器端
- 客户端发送命令消息中 FCPublish 命令到服务器端
- 客户端发送命令消息中的“创建流”(createStream)命令到服务器端。
- 服务器端接收到“创建流”命令后,发送命令消息中的“结果”(_result),通知客户端流的状态。
- 解析服务器返回的消息会得到一个stream ID, 这个ID也就是以后和服务器通信的 message stream ID, 一般返回的是1,不固定。
4. 推流
推流准备工作的最后一步是 Publish Stream,即向服务器发一个publish命令,这个命令的message stream ID 就是上面 create stream 之后服务器返回的stream ID,发完这个命令一般不用等待服务器返回的回应,直接下一步发送音视频数据。
有些rtmp库还会发setMetaData消息,这个消息可以发也可以不发,里面包含了一些音视频编码的信息。