RTMP: Real Time Messaging Protocol,是流媒体传输协议,在直播业务中最常用于推流。
由 Adobe 开发,设计的目的:
这个协议其实不复杂。以前做过直播业务,我不想忘记他,所以在这里记录下我学习的内容。
在了解 RTMP 的协议内容之前,需要先了解一些名词定义:还可以参考和这位好心人的翻译。看不懂也没事,可以反复回来看。
不过,为了了解 RTMP,一定要知道的就是消息:消息是 RTMP 协议发送的最基本单元,每个消息包含一个或多个音视频帧(要么都是音频、要么都是视频)。消息由块(Chuck)组成。
这一节就是讲啥是块流。大概的结构如下图
块是从消息切分出来的,所以无论什么消息,都一定要包含以下的四个字段。
RTMP 的客户端和服务端握完手,就说明连接建立了:
以上提到了握手块,他们的具体内容是啥呢?
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
| version |
+-+-+-+-+-+-+-+-+
C0 and S0 bits
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| zero (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random bytes |
| (cont) |
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time2 (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random echo |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random echo |
| (cont) |
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
上面那个握手图里,还有一种 ack 消息,用于流量控制。这个 ack 消息会通知对方接收了多少数据(以字节为单位)。服务器和客户端通过 ack 消息确认已接收到的数据,好调整窗口大小和数据发送速率。
RTMP 协议将一个完整的消息分为多个较小的数据块(Chunks),每个块都有自己的时间戳、序列号和 Chunk Stream ID。这样可以有效地在网络上进行传输和管理,特别是对于大型消息或实时数据流。
块的结构是这样的:块头(基本头+消息头+扩展时间戳)+数据
+--------------+----------------+--------------------+--------------+
| Basic Header | Message Header | Extended Timestamp | Chunk Data |
+--------------+----------------+--------------------+--------------+
| |
|<------------------- Chunk Header ----------------->|
一个块最大 128 个字节(默认的,用 set chuck size,在 Section 5.4.1 会讲,也可以设),如果超过这个长度就分块。
比如这个例子,是一个音频流,这个结构就是一个消息。
+---------+-----------------+-----------------+-----------------+
| |Message Stream ID| Message TYpe ID | Time | Length |
+---------+-----------------+-----------------+-------+---------+
| Msg # 1 | 12345 | 8 | 1000 | 32 |
+---------+-----------------+-----------------+-------+---------+
| Msg # 2 | 12345 | 8 | 1020 | 32 |
+---------+-----------------+-----------------+-------+---------+
| Msg # 3 | 12345 | 8 | 1040 | 32 |
+---------+-----------------+-----------------+-------+---------+
| Msg # 4 | 12345 | 8 | 1060 | 32 |
+---------+-----------------+-----------------+-------+---------+
对这个音频流的Msg #3分块之后:这里面的 Chunk Stream ID+Chunk Type 就是基本头,Header Data 就是 Message Header
+--------+---------+-----+------------+-----------+------------+
| | Chunk |Chunk|Header Data |No.of Bytes|Total No.of |
| |Stream ID|Type | | After |Bytes in the|
| | | | |Header |Chunk |
+--------+---------+-----+------------+-----------+------------+
|Chunk#1 | 3 | 0 | delta: 1000| 32 | 44 |
| | | | length: 32,| | |
| | | | type: 8, | | |
| | | | stream ID: | | |
| | | | 12345 (11 | | |
| | | | bytes) | | |
+--------+---------+-----+------------+-----------+------------+
|Chunk#2 | 3 | 2 | 20 (3 | 32 | 36 |
| | | | bytes) | | |
+--------+---------+-----+------------+-----------+------------+
|Chunk#3 | 3 | 3 | none (0 | 32 | 33 |
| | | | bytes) | | |
+--------+---------+-----+------------+-----------+------------+
|Chunk#4 | 3 | 3 | none (0 | 32 | 33 |
| | | | bytes) | | |
+--------+---------+-----+------------+-----------+------------+
这是一个视频流的消息:
+-----------+-------------------+-----------------+-----------------+
| | Message Stream ID | Message TYpe ID | Time | Length |
+-----------+-------------------+-----------------+-----------------+
| Msg # 1 | 12346 | 9 (video) | 1000 | 307 |
+-----------+-------------------+-----------------+-----------------+
这是组成这个Msg #1的块:
+-------+------+-----+-------------+-----------+------------+
| |Chunk |Chunk|Header |No. of |Total No. of|
| |Stream| Type|Data |Bytes after| bytes in |
| | ID | | | Header | the chunk |
+-------+------+-----+-------------+-----------+------------+
|Chunk#1| 4 | 0 | delta: 1000 | 128 | 140 |
| | | | length: 307 | | |
| | | | type: 9, | | |
| | | | stream ID: | | |
| | | | 12346 (11 | | |
| | | | bytes) | | |
+-------+------+-----+-------------+-----------+------------+
|Chunk#2| 4 | 3 | none (0 | 128 | 129 |
| | | | bytes) | | |
+-------+------+-----+-------------+-----------+------------+
|Chunk#3| 4 | 3 | none (0 | 51 | 52 |
| | | | bytes) | | |
+-------+------+-----+-------------+-----------+------------+
我看到这里其实有点晕了。总结一下:Chunk 的 Message Stream ID 与 Message 的 Message Stream ID 来自协议的分层,各自层次的 id 即标识,或者说各自层次的消息归类。
RTMP 块流,使用 message type IDs: 1, 2, 3, 5, 6 用于协议控制类型的消息。
(协议第 6 节)
上一节详细讲了块流,这个东西发明的就是为了低带宽传输的。现在这节讲下消息。
格式:消息头+消息负载(数据)
消息头:其实前面 5.1 提到过了
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Message Type | Payload length |
| (1 byte) | (3 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp |
| (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Stream ID |
| (3 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
消息负载:其实就是音频帧、视频帧。比如一个音频帧,多个音频帧,或者一个视频帧,或者多个视频帧。后面讲负载的类型。
前面提到了,message type ID 4 代表用户控制消息。对于所有的用户控制消息,他的 message stream ID 要用 z0,chuck stream ID 要用 2。
对于用户控制消息,他的负载是这样的。这个 Event 在 Section 7.1.7 讲。
+------------------------------+-------------------------
| Event Type (16 bits) | Event Data
+------------------------------+-------------------------
这里提到了不同的消息,以及客户端和服务端交互的消息。、
(协议 7.1 节)
这个前面提到一点,这里详细说明下消息的类型:
是前面 6.2 提到的:User Control Message Events
Stream Begin (=0)
Stream EOF (=1)
StreamDry (=2)
SetBuffer Length (=3)
StreamIs Recorded (=4)
PingRequest (=6)
PingResponse (=7)
客户端和服务端的交互消息通过命令消息完成,使用 AMF 编码(后面会讲)。
发送端会发送一个命令消息,这个消息包括命令名(command name),交互 ID(transaction ID)还有包含相关参数的命令对象(command object)。
命令大体有两种:
NetConnection: 与客户端服务端连接相关的命令。
NetStream: 音视频流在信道传播的时候涉及到的一些命令。
协议对这几种命令有非常详细的描述,这里列一个例子,connect 命令作用的位置和过程:
+--------------+ +-------------+
| Client | | | Server |
+------+-------+ | +------+------+
| Handshaking done |
| | |
| | |
| | |
| | |
|----------- Command Message(connect) ------->|
| |
|<------- Window Acknowledgement Size --------|
| |
|<----------- Set Peer Bandwidth -------------|
| |
|-------- Window Acknowledgement Size ------->|
| |
|<------ User Control Message(StreamBegin) ---|
| |
|<------------ Command Message ---------------|
| (_result- connect response) |
| |
(用 onStatus 事件标记,发送 NetStream 的状态信息)
这里是一个play 命令的例子
+-------------+ +------------+
| Play Client | | | Server |
+------+------+ | +------+-----+
| Handshaking and Application |
| connect done |
| | |
| | |
| | |
| | |
---+---- |----- Command Message(createStream) ----->|
Create| | |
Stream| | |
---+---- |<---------- Command Message --------------|
| (_result- createStream response) |
| |
---+---- |------ Command Message (play) ----------->|
| | |
| |<------------- SetChunkSize --------------|
| | |
| |<---- User Control (StreamIsRecorded) ----|
Play | | |
| |<---- UserControl (StreamBegin) ----------|
| | |
| |<- Command Message(onStatus-play reset) --|
| | |
| |<- Command Message(onStatus-play start) --|
| | |
| |<------------ Audio Message --------------|
| | |
| |<------------ Video Message --------------|
| | | |
|
Keep receiving audio and video stream till finishes
(协议 7.3)
协议这里提供了很多消息交换过程的例子。
前面在命令消息中提到了 AMF,这个全称是 Action Message Format。指令消息在客户端和服务端之间传递通过 AMF 编码的指令,消息类型 20 代表 AMF0 编码,消息类型 17 代表 AMF3 编码:
假设有一个帧的大小为 300 字节,按照默认 128 一个块,需要分成 3 个块来传输:
第一个块:
第二个块:
第三个块:
这个其实协议中也举例了,音频流、视频流的分块。
大概是这样的:multiplexer 是复用器,其实就是拆包合并成码流,demultiplexer 是解复用器,就是逆过程
RTMP 协议规定,播放一个流媒体有两个前提步骤:第一步,建立一个网络连接(NetConnection);第二步,建立一个网络流(NetStream)。其中,网络连接代表服务器端应用程序和客户端之间基础的连通关系。网络流代表了发送多媒体数据的通道。服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。
播放一个 RTMP 协议的流媒体需要经过以下几个步骤:握手,建立连接,建立流,播放。RTMP 连接都是以握手作为开始的。建立连接阶段用于建立客户端与服务器之间的“网络连接”;建立流阶段用于建立客户端与服务器之间的“网络流”;播放阶段用于传输视音频数据。
比如,在 wireshark 抓包,执行 rtmp 推流,比如筛选 rtmpt 协议。这里面提到的 RTMPT 是 RTMP 扩展协议。
先看第一个包:
fmt 为 00,cs id 为 3,body size: 91,TypeID 0x14=20,是控制协议。
前面提到 RTMPT,这是个延伸协议。延伸协议包括:
这个是防止大量推流攻击服务器,以及盗链(比如,我很邪恶,我不是直播商,但是我就想偷直播商或者别人的直播流)。
IP 白名单:
RTMP 鉴权密钥:
用户名和密码:
IP 白名单推流:
nginx
Copy code
rtmp {
server {
listen 1935;
application live {
allow 192.168.1.0/24; # 允许该子网的 IP 推流
deny all; # 拒绝其他所有 IP
}
}
}
推流鉴权密钥:
nginx
Copy code
rtmp {
server {
listen 1935;
application live {
push rtmp://backup-server;
# 可选: 指定推流密钥
# push rtmp://backup-server?key=your_key;
}
}
}
示例配置(Nginx RTMP)
URL 鉴权示例:
rtmp {
server {
listen 1935;
application live {
# Example configuration for token-based access
on_publish http://localhost:8080/auth;
on_play http://localhost:8080/auth;
}
}
}
生成:以毫秒为单位,从连接开始时的零时间起算。
更新:客户端和服务器在生成 RTMP 消息时会为每个消息分配一个时间戳。时间戳值是相对的,基于消息的实际播放时间或发送时间,应该是单调递增的。
计算:时间戳基于消息的播放时间进行计算。RTMP 协议允许消息的时间戳与实际播放时间相匹配,以便正确地同步音频和视频数据。时间戳的增量通常基于媒体数据的传输速率或播放进度。
块传输:每个块都有一个时间戳,这个时间戳可以用来区分和同步块中的数据。即使数据块大小不同,时间戳会帮助保持数据的顺序和时间同步。
同步:时间戳确保音视频同步。
RTMP 的时间戳是单调递增的,时间戳到之后,时间戳再从 0 开始。
一句话总结:TCP 可靠
可靠性:TCP 保证数据包的正确到达,处理丢包、重传和数据完整性,确保流媒体内容准确无误。
顺序性:TCP 确保数据包按照发送顺序到达接收端,避免了数据顺序错乱的问题。
流控制:TCP 具有流控制机制,可以避免数据传输过快导致的接收端缓冲区溢出。
也就是说,RTMP 不需要像那些基于 UDP 的协议(比如 RTP)那样去重点处理丢包冗余重传的过程,收到了就是收到了。另外还有 RTMFP 这种基于 UDP 的协议。
RTP 常用于实时传输,但因为 UDP 不保证传输可靠性,容易丢包,且需要额外的机制来处理重传和同步,增加了复杂性。他这个设计的场景就不是为了直播的。另外,RTMP(以及 HLS)非常可靠,RTP 虽然延时很低但是毕竟用 UDP,不保证 QOS,webrtc 的平台兼容不如 RTMP 和 HLS。所以还是用这两种多。
技术演变:RTMP 逐渐被更现代的协议取代,比如 HLS 和 DASH,这些协议更适应移动设备和 HTTP 基础设施。
设备兼容:现代浏览器和设备对 HLS 支持更广泛,而对 RTMP 的原生支持较少。
网络穿透:HLS 基于 HTTP,能够更好地穿透防火墙和代理,而 RTMP 由于使用了专有端口,可能会遇到阻碍。
因为 RTMP 通常使用 TCP 的 1935 端口,而许多防火墙默认阻止或限制不常见的端口和协议。此外,RTMP 的流量可以很难被传统防火墙检测和管理,因此可能会被阻挡以提高安全性。
推流端也可能受防火墙影响,但推流通常在发送端设置了相应的防火墙规则来允许流量通过。总体来说,两端都可能面临防火墙的阻碍,但播放端的问题更为常见。
因为推流端面对的是服务器,服务器可以同意设置规则,但是播放端各有各的规则。
以下几种协议或技术可以用来实现 RTMP 的消息传输:
WebSocket:将 RTMP 流封装在 WebSocket 连接中。WebSocket 允许在客户端和服务器之间建立持久的双向通信通道。
HTTP:将 RTMP 流转换成 HTTP 流媒体传输,例如通过 HTTP-FLV(Flash Video)或 HLS(HTTP Live Streaming)。这也是很多直播选择的方式。
RTSP(Real-Time Streaming Protocol):将流从 RTMP 转发到 RTSP 客户端。这个我没怎么听说过,不太了解。
UDP:这个也是需要转换。
QUIC:Google 开发的传输协议,基于 UDP,目的是减少延迟和提高传输速度。低延时直播会用到这种协议。
这个握手是用于建立和维护流媒体传输的会话,就是虽然 TCP 握手完事了,但是 RTMP 自己还是需要一个 session 的管理机制,以防例如以下的 case 出现:
所以不是所有的情况都允许你发数据。
RTMP 的特点:
RTMP 协议的基本数据单元是消息(Message),传输的过程中消息会被拆分为更小的消息块(Chunk)单元。通常一个消息(message)包含一个或多个完整的音、视频帧。最后将分割后的消息块通过 TCP 协议传输,接收端再反解,将消息块恢复成流媒体数据。
优点:
缺点:
补充:什么是有状态协议?
ANS:有状态协议就是就通信双方要记住双方,并且共享一些信息。而无状态协议的通信每次都是独立的,与你上一次的通信没什么关系。不记录上下文