4. 网络传输基础

RTMP,全称 Real-Time Messaging Protocol,是 Adobe 搞出来的。说白了,它就是直播推流和拉流的老祖宗。虽然现在 WebRTC 和 SRT 很火,但 RTMP 在传统直播链路里依然坚挺。我个人习惯,只要涉及 CDN 推流,第一反应还是 RTMP。

这一章,我们把它拆开揉碎。从握手开始,到消息格式,再到 Chunk 流,最后上手推拉流。嗯,都是实战里必须啃下的硬骨头。

4.1 RTMP 握手过程

RTMP 握手是客户端和服务器建立连接的第一步。它不像 HTTP 那样一问一答,而是双方互发三个固定大小的数据块。

握手分为三个阶段:

  • C0/S0:1 个字节,表示 RTMP 版本。目前主流是版本 3。
  • C1/S1:1536 个字节,包含时间戳和随机数据。
  • C2/S2:1536 个字节,是对对方 S1/C1 的回应。

握手流程其实很简单:

  1. 客户端发 C0 + C1。
  2. 服务器收到后,发 S0 + S1。
  3. 客户端收到 S1,发 C2。
  4. 服务器收到 C1,发 S2。
  5. 双方都收到 C2/S2,握手完成。
避坑指南: 我曾经在对接一个老旧摄像头时,发现它发的 C0 版本号是 0。结果服务器直接断开连接。后来我加了个版本协商逻辑,才搞定。所以,遇到握手失败,先抓包看 C0 版本号。

握手完成后,就可以开始传输真正的音视频数据了。但别急,还有消息格式要搞懂。

4.2 消息格式

RTMP 的消息分为两种:协议控制消息和用户控制消息。协议控制消息用于管理连接,比如设置 Chunk 大小、确认窗口大小等。用户控制消息才是真正的音视频数据。

消息头格式如下:

字段 长度 说明
Message Type 1 字节 消息类型 ID,比如 0x08 是音频,0x09 是视频
Payload Length 3 字节 消息体长度
Timestamp 4 字节 时间戳,单位毫秒
Stream ID 3 字节 流 ID,用于区分不同的流

消息体就是实际的音视频数据。但 RTMP 不会直接发送这么大的消息,它会切成小块,这就是 Chunk 流。

4.3 Chunk 流

Chunk 流是 RTMP 的精髓。它把大消息切成小块,交错发送。这样,即使网络不好,也能保证低延迟。

Chunk 的基本格式:

  • Basic Header:1-3 字节,包含 Chunk Stream ID 和 Chunk Type。
  • Message Header:0、3、7、11 字节,取决于 Chunk Type。
  • Extended Timestamp:0 或 4 字节,当时间戳超过 0xFFFFFF 时使用。
  • Chunk Data:实际数据,长度由 Chunk Size 决定。

Chunk Type 有四种:

Type Header 长度 说明
Type 0 11 字节 完整消息头,用于新消息或时间戳变化
Type 1 7 字节 不包含 Stream ID,用于同一流
Type 2 3 字节 只包含时间戳增量,用于连续 Chunk
Type 3 0 字节 无头,完全复用上一个 Chunk 的头信息
核心要点: Type 3 是最高效的。它只发数据,头信息全靠复用。我在做低延迟推流时,会把 Chunk Size 设大,然后大量使用 Type 3,减少头部开销。

Chunk 流的工作流程:

  1. 发送方把消息切成 Chunk。
  2. 每个 Chunk 带上 Basic Header 和 Message Header。
  3. 接收方根据 Chunk Stream ID 重组消息。
  4. 如果 Chunk 丢失,接收方会等待重传或丢弃。

你想想看,如果一条视频消息有 10KB,切成 128 字节的 Chunk,那就有 80 个 Chunk。这些 Chunk 可以和其他音频、控制消息交错发送。这就是 RTMP 能保持低延迟的原因。

4.4 推流与拉流实战

理论讲完了,我们上手实战。我会用 FFmpeg 和 rtmpdump 来演示。

4.4.1 推流

推流就是把本地音视频数据发送到 RTMP 服务器。我用 FFmpeg 推流:

ffmpeg -re -i input.mp4 -c:v libx264 -c:a aac -f flv rtmp://your-server/live/stream-key

参数说明:

  • -re:以原始帧率读取,模拟实时推流。
  • -i input.mp4:输入文件。
  • -c:v libx264:视频编码为 H.264。
  • -c:a aac:音频编码为 AAC。
  • -f flv:输出格式为 FLV,RTMP 底层用 FLV 封装。
  • rtmp://your-server/live/stream-key:推流地址。
注意: 推流地址的 stream-key 是服务器分配的,不要泄露。我曾经在调试时把 key 写死在代码里,结果被同事抓包看到了,后来改了。

4.4.2 拉流

拉流就是从 RTMP 服务器接收音视频数据。我用 rtmpdump 拉流:

rtmpdump -r rtmp://your-server/live/stream-key -o output.flv

或者用 FFmpeg 拉流并转码:

ffmpeg -i rtmp://your-server/live/stream-key -c copy output.mp4

拉流时,客户端会先发送连接请求,然后创建流,最后接收音视频数据。整个过程就是握手、消息、Chunk 流的逆过程。

4.4.3 实战中的坑

我在项目中遇到过几个常见问题:

  • 握手超时:服务器防火墙没开 1935 端口。检查网络策略。
  • 推流卡顿:Chunk Size 太小,头部开销大。调大 Chunk Size 到 4096 或 8192。
  • 拉流花屏:视频编码参数不对,比如 B 帧太多。推流时加 -bf 0 禁用 B 帧。
  • 音画不同步:时间戳计算错误。检查推流端的 PTS 和 DTS。
我的习惯: 每次推流前,我都会用 Wireshark 抓包,看握手和 Chunk 流是否正常。抓包时过滤 rtmp 或 tcp.port == 1935,一目了然。

4.5 知识体系图

下面这张图展示了 RTMP 协议的核心逻辑:从握手到消息,再到 Chunk 流,最后到推拉流。

RTMP 协议核心逻辑图 RTMP 协议核心逻辑 握手阶段 消息格式 Chunk 流 推流实战 拉流实战 说明: 1. 握手阶段:C0/S0 + C1/S1 + C2/S2,共 3 次交互。 2. 消息格式:包含消息类型、长度、时间戳、流 ID。 3. Chunk 流:将大消息切成小块,交错发送,降低延迟。

这张图把 RTMP 的核心链路串起来了。你从握手开始,经过消息格式,再到 Chunk 流,最后落地到推拉流实战。每一步都环环相扣。

嗯,RTMP 协议就讲到这里。记住,握手是基础,消息是载体,Chunk 流是效率,实战是检验。下次遇到 RTMP 问题,先抓包看握手,再查 Chunk 流,基本能定位 80% 的问题。