🎬 第四章 RTMP流媒体协议
📄 一句话概括

RTMP 是跑在 TCP 上的直播协议,用 1935 端口,延迟一到三秒。 它比 HLS 快一个数量级,秘密不在传输层,而在「不攒够一个切片就发」。

📝 本节和前面几节的关系

第三章讲的 RTSP 是「遥控器搬数据」的老路子。从这一章起换成一个更工业的选择:直播行业的事实标准 RTMP。 4.2 拆握手与命令,4.4 拆块流,这一节先定性。

一、它跟 RTSP 的根本区别

两个协议都能推能拉、都能低延迟,但设计目标完全不同。 ① RTSP 是「点播的延伸」:它假设有一个文件、有一个时间轴,你可以随意暂停与拖动。 ② RTMP 是「推流的工作台」:它假设你有一个采集端在不停往外吐,重点是「怎么把这一堆帧稳定地送出去」。

这个差别带来一个直接后果:RTMP 把音视频的封装格式也定死了。 它的消息体就是 FLV 的 tag 数据体,所以「RTMP 封 FLV」其实是「共用同一套负载格式」,封装层与协议层是一起设计的。

RTSP 反过来是「只管命令、不管封装」,SDP 里说什么格式就发什么格式。 灵活,但也意味着每个环节都要自己谈一遍。

二、一个端口搞定所有事

RTMP 只用一条 TCP 连接、一个端口 1935。 命令、音频、视频、控制信息全在这一条连接里跑,靠「块流 ID」区分。

这跟 RTSP 的「一个固定端口 + 一堆临时 UDP 端口」正好相反。 好处是穿防火墙极容易——只要放行 1935 就行,不像 RTSP 还要放一段 UDP 区间。 代价是「大家都挤在一条 TCP 上」: TCP 保证有序,所以只要一个包丢了、在等重传,后面所有流都得排队。 这就是队头阻塞,也是 RTMP 延迟下不去的根本原因。

那为什么不换 UDP? 因为 RTMP 定型在 2002 年前后,那时的网络与企业环境里「能穿过去」比「延迟低 200 毫秒」重要得多。 这个取舍一直留到今天。

三、延迟到底花在哪

一拍脑袋以为「用 TCP 就慢」,其实不是。 把一条 RTMP 链路的延迟拆开,大头往往不在协议本身: ① 编码器缓冲:为了压码率稳定,编码器常攒几帧再出,200~500 毫秒。 ② 等关键帧:播放端必须从 IDR 开始,最坏要等一个 GOP,1~4 秒,这是最大的一块。 ③ 网络传输:同城通常 20~50 毫秒,跨省 50~100 毫秒。 ④ 播放器缓冲:攒几帧再播,200~500 毫秒。

四段加起来才是你看到的「三秒」。 其中协议本身贡献的只有百分之几——所以「换成低延迟协议」往往只能省下最后一小段,先把 GOP 与缓冲调小效果更明显。

四、RTMP 现在还在用吗

【⚠️ 2026 现状】Flash 已经在 2020 年底彻底停用,但 RTMP 协议本身活得很好。

原因是它被「降级使用」了: ① 推流端几乎全是 RTMP:OBS、FFmpeg、各种采集卡软件默认就推 RTMP。 平台收流的第一跳仍然是 1935。 ② 播放端基本不用 RTMP 了:浏览器不支持(要 Flash),所以拉流换成了 HTTP-FLV、HLS、WebRTC。 ③ 服务端之间常用更低成本的转发:RTMP 或 SRT。

所以今天讲 RTMP,讲的多半是「推流这一段」。 你打开 OBS 填的那个 rtmp:// 地址,走的就是这一章的全部内容。 而观众那边看到的是 HTTP-FLV 或 HLS——那是第五、六章的事。

五、动手 Demo:一个端口、一条连接、一堆消息

先把三个协议摆在一起对比,再真跑一次「握手包」的递交。 握手只是确认「对方懂 RTMP」,不做认证。

import socket, struct, os, threading

srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("127.0.0.1", 0))          # 真实环境是 1935
srv.listen(4)
port = srv.getsockname()[1]
got = {}

def serve():
    c, _ = srv.accept()
    data = b""
    while len(data) < 1537:            # C0 1 字节 + C1 1536 字节
        d = c.recv(4096)
        if not d:
            break
        data += d
    got["in"] = len(data)
    got["c0"] = data[0]
    # S0 + S1:版本号 + 时间戳 4 + 零 4 + 随机 1528
    c.sendall(struct.pack("!B", 3) + b"\x00" * 8 + os.urandom(1528))
    c.close()

threading.Thread(target=serve, daemon=True).start()

c = socket.create_connection(("127.0.0.1", port), 3.0)
c1 = struct.pack("!I", 0) + b"\x00" * 4 + os.urandom(1528)
c.sendall(struct.pack("!B", 3) + c1)

s0s1 = b""
while len(s0s1) < 1537:
    d = c.recv(4096)
    if not d:
        break
    s0s1 += d
c.close()
srv.close()

print("客户端递出 %d 字节,C0 版本号 = %d" % (got["in"], got["c0"]))
print("服务器回了 %d 字节,S0 版本号 = %d" % (len(s0s1), s0s1[0]))
print("完整握手六个包共 3073 字节")

实跑输出:

--------------------------------------------------------------------
RTMP 简介——一个端口、一条连接、一堆消息
--------------------------------------------------------------------
**① 先把三个协议摆在一起看**

  协议     传输        端口        典型延迟
  ---------------------------------------------
  RTMP     TCP         1935        1~3 秒
  RTSP     TCP+UDP     554         0.5~2 秒
  HLS      HTTP        80/443     6~30 秒

  **RTMP 的定位很特别:它用 TCP(可靠、会重传),但延迟又比 HLS 低一个数量级。**
  **秘密在「不做缓冲」:收到就往上送,不像 HLS 要攒够一个切片才发。**

**② 真跑一遍:连上 1935 风格的端口,递出握手包**

  方向              包     字节     内容
  ---------------------------------------------------------------
  客户端→服务器     C0     1        版本号 3
  客户端→服务器     C1     1536     时间戳 0 + 零 + 1528 随机
  服务器→客户端     S0     1        版本号 3
  服务器→客户端     S1     1536     时间戳 + 零 + 随机

  **实测客户端递了 1537 字节,服务器回了 1537 字节。**
  **完整握手是六个包,一共 3073 字节**(C0+C1+C2 与 S0+S1+S2 各半)。

**③ 为什么是 1536 这个数**

  **1536 = 4(时间戳)+ 4(必须为零)+ 1528(随机)。**
  **凑成 1536 是为了让 C1 正好等于「一个 1500 字节的 MTU 再加一点」,旧实现一次写完不用拆包。**
  **这个数从 Flash 时代定下,一直没改。**

  **握手的作用只有一个:双向确认「对方懂 RTMP、且版本兼容」。**
  **它不做认证、不交换参数——认证在后面的 connect 命令里。**

C0 只有 1 字节,就是版本号,现代实现固定写 3。 C1 是 1536 字节:时间戳 4 + 必须为零的 4 字节 + 1528 字节随机。 完整握手六个包、3073 字节——但它只确认「双方懂这套协议」,认证在后面的 connect 里。

30 秒实验

  1. 把 C0 的版本号从 3 改成 2,在真服务器上试一次——多数实现会直接断开,这就是「连上就断」的典型。
  2. 把 C1 的随机段改成 1528 个 0,再看握手能不能过——能过,但服务器端统计里会记录「弱随机」,属于隐患。
  3. 用 telnet 127.0.0.1 1935 试连一次,确认端口是通的——这是排除「网络问题」最快的一步。

常见误区

  1. 「RTMP 是 Flash 的协议,Flash 死了它就没了」——推流端几乎全是 RTMP,OBS 至今默认就推它。
  2. 「RTMP 延迟低是因为用了 UDP」——恰恰相反,它跑在 TCP 上,延迟主要来自等关键帧与缓冲。
  3. 「RTMP 也封 FLV,所以它俩是一回事」——协议层与封装层是两件事,只是共用了 tag 数据体这个负载格式。
自测三问(点开对答案) 1. RTMP 用哪个端口、跑在什么传输层上? —— **1935,TCP。**所有数据走同一条连接。 2. RTMP 相比 RTSP 在穿防火墙上有什么优势? —— **只用一个固定端口**,不像 RTSP 还要放一段 UDP 端口区间。 3. RTMP 的延迟大头在哪? —— **等关键帧与编码器、播放器的缓冲。**协议本身贡献很小。

相关笔记