4. RTSP与RTP/RTCP协同:RTP数据包结构、RTCP控制报文、时间戳同步机制、NTP与RTP时间戳映射

RTSP 说白了就是个遥控器——它负责「开始」、「暂停」、「快进」这些控制指令。但真正扛着视频数据往前冲的,是 RTP。而 RTCP 呢,就像个监工,时不时回头看看数据传得怎么样、有没有丢包、要不要调整码率。

这三兄弟配合得好,你的推流才稳。我见过不少开发者把 RTSP 和 RTP 混为一谈,结果调试时抓瞎。今天咱们就把它们拆开,一个一个看清楚。

4.1 RTP 数据包结构:视频数据到底怎么装?

RTP(Real-time Transport Protocol)是真正承载音视频数据的协议。它跑在 UDP 上面,端口一般是偶数。为什么用 UDP?因为实时性要求高,丢几个包还能忍,但延迟大了可不行。

一个标准的 RTP 包长这样:

 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X|  CC   |M|     PT      |       sequence number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           timestamp                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           synchronization source (SSRC) identifier            |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|            contributing source (CSRC) identifiers             |
|                             ....                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    RTP payload (H.264/H.265/AAC...)           |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

我来拆开讲几个关键字段:

  • V(Version):版本号,目前固定为 2。没什么好说的。
  • P(Padding):填充位。如果置 1,包尾有填充字节。我几乎没见过实际场景用这个。
  • X(Extension):扩展位。有些厂商会在 RTP 头后面加自定义扩展头,比如加个时间戳精度之类的。但标准推流一般不用。
  • CC(CSRC Count):CSRC 数量。混流场景才用,单路推流时是 0。
  • M(Marker):标记位。对于视频,通常表示一帧的结束。比如 H.264 的 NAL 单元边界。
  • PT(Payload Type):负载类型。H.264 是 96,H.265 是 98,AAC 音频是 97。这些值可以动态协商。
  • Sequence Number:序列号。每发一个包就 +1。接收端靠它检测丢包和排序。
  • Timestamp:时间戳。这是 RTP 的核心,后面会重点讲。
  • SSRC:同步源标识。每个 RTP 流一个唯一 ID,用来区分不同的流。

关键点:RTP 本身不保证可靠传输,也不保证按序到达。序列号和时间戳是接收端自己用来排序和同步的。别指望 RTP 帮你做 QoS。

4.2 RTCP 控制报文:谁在背后盯着质量?

RTCP(RTP Control Protocol)和 RTP 共用一对 UDP 端口——RTP 用偶数,RTCP 用下一个奇数。比如 RTP 是 5000,RTCP 就是 5001。

RTCP 报文有好几种类型,但最常用的是这两种:

类型 名称 作用 发送频率
200 SR(Sender Report) 发送端报告:包含 NTP 时间戳、RTP 时间戳、发送包数、发送字节数 推流端定期发送,通常每 1-5 秒
201 RR(Receiver Report) 接收端报告:包含丢包率、累计丢包数、抖动、延迟 接收端收到 SR 后回复

SR 报文里有个东西特别重要——NTP 时间戳和RTP 时间戳的映射关系。这个映射是音视频同步的基础。

我的经验:我曾经调试过一个项目,视频和音频总是对不上。查了半天,发现是推流端 RTCP SR 报文里的 NTP 时间戳没更新,一直发同一个值。接收端拿这个映射去算同步,结果全乱套了。所以,RTCP 报文里的时间戳一定要实时更新,别偷懒。

4.3 时间戳同步机制:音视频怎么对上嘴?

这个问题其实很经典。视频流和音频流是两个独立的 RTP 流,各有各的 SSRC,各有各的时间戳。接收端怎么知道视频的第 100 帧对应音频的第 200 个采样点?

答案就在 RTCP SR 报文里。

SR 报文里同时包含了:

  • NTP 时间戳:绝对时间,单位是秒的 2^32 分之一。这个时间来自系统时钟,所有流共享同一个时间基准。
  • RTP 时间戳:相对时间,单位取决于采样率。视频通常是 90000 Hz,音频是 48000 Hz 或 44100 Hz。

接收端拿到这两个值,就能算出一个映射关系:

RTP 时间戳 = (NTP 时间戳 - 基准 NTP) × 采样率 + 基准 RTP 时间戳

说白了,NTP 是「墙上的钟」,RTP 是「计数器」。通过 RTCP SR 报文,接收端把两个计数器对齐,然后就能算出任意时刻对应的 RTP 时间戳。

注意:RTP 时间戳是 32 位无符号整数,会溢出。90000 Hz 采样率下,大约 13 小时溢出一次。实际推流中要考虑回绕处理,否则同步会出问题。

4.4 NTP 与 RTP 时间戳映射:一张图看懂

我画了一张图,把整个映射关系串起来。你看完应该就明白了。

NTP 与 RTP 时间戳映射关系 NTP 时间线(绝对时间,秒) NTP = 系统时钟,所有流共享同一个基准 RTCP SR 报文 包含 NTP 时间戳 + RTP 时间戳 视频 RTP 流 SSRC = 0x12345678 采样率 = 90000 Hz 音频 RTP 流 SSRC = 0x87654321 采样率 = 48000 Hz 接收端同步计算 RTP_ts = (NTP_ts - NTP_base) × 采样率 + RTP_base

流程其实很简单:

  1. 推流端定期发送 RTCP SR 报文,里面同时携带 NTP 时间戳和当前 RTP 时间戳。
  2. 视频流和音频流各自独立发送 SR 报文,但 NTP 时间戳来自同一个系统时钟。
  3. 接收端收到 SR 后,建立 NTP 到 RTP 的映射关系。
  4. 播放时,接收端根据当前 NTP 时间,算出对应的视频 RTP 时间戳和音频 RTP 时间戳,然后找到对应的数据帧进行播放。

核心结论:音视频同步靠的不是 RTP 时间戳本身,而是 RTCP SR 报文提供的 NTP ↔ RTP 映射。没有这个映射,两个独立的时间戳就是两本糊涂账。

4.5 实战中的坑与避坑指南

做 RTSP 推流这些年,我在时间戳同步上踩过不少坑。挑几个典型的说说:

  • 坑一:RTP 时间戳起始值不一致。视频流和音频流的 RTP 时间戳起始值不同,接收端如果只靠 RTP 时间戳做同步,永远对不上。解决办法:必须依赖 RTCP SR 里的 NTP 映射。
  • 坑二:RTCP SR 发送间隔太长。有些实现把 SR 间隔设成 10 秒甚至更长。接收端在这期间如果发生时钟漂移,同步就会越来越偏。我建议 SR 间隔不要超过 2 秒。
  • 坑三:NTP 时间戳精度不够。有些嵌入式设备用 gettimeofday() 获取时间,精度只有毫秒级。对于 90000 Hz 的采样率,毫秒级误差会导致 90 个时间单位的偏差。最好用 clock_gettime() 或者硬件时间戳。

我的建议:如果你在写推流端,记得在 RTCP SR 报文里同时更新 NTP 和 RTP 时间戳,并且保证它们对应的是同一个时刻。如果你在写接收端,收到 SR 后立即更新映射表,不要等到播放时才去算。

4.6 小结

RTP 负责运数据,RTCP 负责报质量,时间戳是它们之间的粘合剂。NTP 提供绝对时间基准,RTP 提供相对计数,通过 RTCP SR 报文把两者绑在一起。音视频同步的秘密,说白了就这么简单。