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 时间戳映射:一张图看懂
我画了一张图,把整个映射关系串起来。你看完应该就明白了。
流程其实很简单:
- 推流端定期发送 RTCP SR 报文,里面同时携带 NTP 时间戳和当前 RTP 时间戳。
- 视频流和音频流各自独立发送 SR 报文,但 NTP 时间戳来自同一个系统时钟。
- 接收端收到 SR 后,建立 NTP 到 RTP 的映射关系。
- 播放时,接收端根据当前 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 报文把两者绑在一起。音视频同步的秘密,说白了就这么简单。