标题: 语音包的快递面单:RTP 作者: 爱卫生 封面: "[[封面1.png]]" 封面裁剪: false 原文地址: 打开评论: true 仅粉丝可评论: false 摘要: 你在微信上传一个压缩包,传到99%卡住了,等三秒,它续传接着走,最后文件完好无损。你不会介意,因为文件传输的信条是一个字节都不能错。
你在微信上传一个压缩包,传到99%卡住了,等三秒,它续传接着走,最后文件完好无损。你不会介意,因为文件传输的信条是一个字节都不能错。
但打电话不一样。对方说“今晚一起吃饭”,这句话拆成的语音包如果有一个晚了,等你收到时对话已经进行到下一句了,这个包放进来不是补全,是捣乱。实时语音的信条恰好相反:晚到的包等于丢了的包,甚至更糟。
这个反直觉的取舍,决定了语音传输技术的整个设计方向。我们熟悉的 TCP 为了“一个都不能丢”付出了三样代价,而这三样在实时语音场景里全是毒药:
| 传输方式 | 丢包怎么办 | 时延表现 | 顺序保证 | 适合语音吗 |
|---|---|---|---|---|
| TCP | 自动重传,等到为止 | 丢包时重传+拥塞退避,时延飙升 | 严格按序,后面的包排队等前面的 | 不适合 |
| 裸UDP | 不管 | 恒定、极低 | 不保证 | 能用,但接收端“看不懂” |
| UDP + RTP | 交给应用层决策 | 低且可预测 | 不强制,但提供重排依据 | 设计目标所在 |
TCP 的队头阻塞尤其致命:第 100 号包丢了,哪怕第 101 到 110 号包都到了,应用层也拿不到——它们必须等 100 号重传回来。对下载来说这是秩序,对通话来说这是灾难:你要么把 10 个包全部延迟,要么全部丢弃,而 TCP 替你选了前者。
于是工程师选了 UDP 做底座。但 UDP 裸用有个大问题:它只负责把一坨字节从 A 搬到 B,丢没丢、第几包、这包里的声音对应哪个时刻、这条流是谁发的,UDP 一概不知。接收端面对一堆到达顺序混乱的数据块,无从下手。
缺的是一张面单。
把每个语音包想象成一个快递包裹。UDP 负责把包裹扔上货车运走——它不承诺签收,但胜在快。而 RTP(Real-time Transport Protocol,实时传输协议)做的,就是在每个包裹上贴一张标准化的面单,写清楚:
这套机制由 IETF 在 RFC 3550 中定义(2003 年发布,取代早期的 RFC 1889),配套的 RFC 3551 规定了音视频场景下的具体用法。它跑在 UDP 之上,本身不提供任何可靠性和时延保证——它不护送包裹,只保证包裹到了之后,你能看懂、能排序、能算账。这是一次彻底的职责分工:网络负责尽力送达,协议负责事后对账,应用负责取舍。
RTP 头部最常见形态是 12 字节定长部分,加上若干可选块。下图是它的构成:
flowchart TD
H["RTP包头 12字节定长"] --> F1["版本与填充标志 1字节"]
H --> F2["标记位与载荷类型 1字节"]
H --> F3["序列号 2字节"]
H --> F4["时间戳 4字节"]
H --> F5["同步源SSRC 4字节"]
H --> F6["贡献源CSRC列表 可选"]
H --> P["载荷:AMR或EVS语音帧"]
逐个拆解,重点看每个字段的设计动机:
| 字段 | 位宽 | 作用 | 为什么这样设计 |
|---|---|---|---|
| 版本号 V | 2 | 当前恒为 2 | 留出升级空间,历史上已迭代过两版 |
| 填充 P | 1 | 标记末尾有无填充字节 | 某些加密算法要求整块对齐 |
| 扩展位 X | 1 | 是否有扩展头 | 为新功能留后门,不破坏基本结构 |
| CSRC 计数 CC | 4 | 后面跟几个贡献源 | 混音场景(如会议)列出原始发言者 |
| 标记 M | 1 | 事件边界提示 | 如标记一段静音后的第一帧,便于解码器快速对齐 |
| 载荷类型 PT | 7 | 声明载荷的编码格式 | 接收端据此选择解码器,通话中可动态切换 |
| 序列号 | 16 | 每发一包加一 | 丢包检测和乱序重排的“包号” |
| 时间戳 | 32 | 载荷首字节的采样时刻 | 还原声音的时间轴,与包号节奏解耦 |
| SSRC | 32 | 标识本条流的同步源 | 区分同会话内多路媒体流 |
有三处设计尤其值得咀嚼。
第一,序列号和时间戳为什么是两条独立的轴? 因为“第几个包”和“第几毫秒的声音”不是一回事。编码器一次可能把多帧语音打成一包(节省头开销),此时序列号加一,时间戳却跳过多个帧长;静音检测开启时,编码器进入不连续传输,长时间不发常规包,只偶尔发一个描述背景噪声的小帧——序列号按发包节奏走,时间戳按声音的时间轴走。接收端用序列号查丢包,用时间戳还原声波,两轴各司其职。
时间戳的步进由采样率决定,公式很朴素:
$$\Delta TS = f_s \times T_{frame}$$
以窄带 AMR 为例,采样率 8 kHz、帧长 20 ms,则每个语音帧对应的时间戳增量 = 8000 × 0.02 = 160。你在抓包时看到时间戳整齐地以 160 递增,就说明这是一路标准的 20 ms 窄带语音。
第二,初始值为什么必须随机? RFC 3550 明确要求序列号和时间戳的起始值随机选取,而不是从零开始。动机有二:一是安全——若序列号可预测,攻击者可以伪造包号正确的假语音流注入通话;二是加密场景下,已知的明文开头(如固定帧头)会降低攻击难度,随机化起始偏移能打乱这种对应关系。
第三,16 位序列号会不会用完? 会,而且很快。65536 个包号,按每 20 ms 一包计算:
$$65536 \times 20\,ms \approx 21.8\ \text{分钟}$$
约 22 分钟就回绕一圈。所以接收端判断丢包时不能简单比大小,必须按回绕感知的方式比较包号先后。这是实现 RTP 栈时最常见的错误来源之一,后文实战部分会再提。
把面单贴好之后,包裹是怎么走的?下图是一条端到端的媒体路径:
flowchart TD
A["说话:模拟声波"] --> B["编码:AMR或EVS压缩"]
B --> C["封装:贴上RTP面单"]
C --> D["加UDP与IP头发出去"]
D --> E["空口专用承载传输"]
E --> F["核心网用户面转发"]
F --> G["对端空口接收"]
G --> H["逐层剥掉封装头"]
H --> I["进抖动缓冲排队"]
I --> J["按时间戳解码还原"]
J --> K["喇叭播放"]
这里有两个现网关键点。
语音走的是“专车”。 VoLTE 语音媒体流被映射到专用承载上(4G 里的 QCI 1、5G 里的 5QI 1,都是保证比特率类型),空口会为它预留资源,这也是打电话时刷网页会变慢的原因之一——资源被语音优先占用了。RTP 本身不提供任何服务质量,它只是老实告诉网络“我是语音”,真正的优先级由底下的承载体系保证。理解这个分层,就不会犯“给 RTP 加参数提升通话质量”这类方向性错误。
面单比货物还重,怎么办? 算一笔账:IPv4 头 20 字节 + UDP 头 8 字节 + RTP 头 12 字节 = 40 字节封装开销。而一帧 12.2 kbps 的 AMR 语音只有约 31 字节。也就是说,快递费比货值还贵两成多——每 20 ms 就要为 40 字节的“行政成本”付出空口资源。这正是无线侧引入鲁棒头压缩(ROHC)的原因:基站和手机把 40 字节的重复头信息压缩到几个字节,只偶尔发送全量头来纠错。压缩的底气来自规律性——序列号、时间戳的递增规律太强了,接收端完全可以“猜”出下一包的头,只需定期校对。这与本文第三节的随机初始值并不矛盾:随机的是起点,可预测的是趋势。
VoLTE 通话还有一个架构层面的设计值得单独讲:信令面和媒体面从会话建立之初就是分家的。
flowchart TD
A["手机A"] -->|注册与呼叫信令| C["IMS核心网"]
B["手机B"] -->|注册与呼叫信令| C
A -->|RTP媒体流| S["媒体边界节点"]
S -->|RTP媒体流| B
C -->|下发协商好的媒体地址| S
为什么这样设计?想象一下如果媒体也要绕道中心机房:广州的手机 A 打给广州的手机 B,语音包却要先跑到部署在上海的呼叫控制节点再折回来,平白多出几十毫秒和一段公网路由。正确的设计是:集中式的信令网只负责“牵线”——完成注册、路由、编码协商,把双方约定的媒体地址和参数互相通知;一旦牵线成功,媒体流就走就近的媒体边界节点直达对端。信令像婚姻登记处,只在你结婚离婚时去;媒体像日常同居,在你们自己家进行。
这个分离带来的直接推论是:通话中如果你去抓包,会看到 SIP 信令流早就安静了,网络上川流不息的只有双向的 RTP 包。排查“能打通但没声音”的故障时,思路立刻清晰:信令面正常(能建立会话),问题必然在媒体面——承载没建起来、媒体地址不可达、或编解码协商不一致。
只有单向的面单还不够。快递系统要运转良好,还需要回执系统——收件方定期向寄件方报告“最近 100 件到了 98 件,平均延误多少”。这就是 RTCP(RTP Control Protocol),与 RTP 同生共死,定义于同一份 RFC 3550。
sequenceDiagram
participant S as 发送端手机A
participant R as 接收端手机B
S->>R: 语音RTP包流
R->>S: 接收者报告RR
Note over R: 统计丢包与抖动
S->>R: 发送者报告SR
Note over S: 据此评估链路质量
R->>S: 周期性回执 默认约5秒一次
RTCP 定义了五种常规报文,常用的就两三种:
| 类型 | 缩写 | 方向 | 内容 |
|---|---|---|---|
| 发送者报告 | SR | 媒体发送方→接收方 | 本端已发包数、字节数、时间戳,用于算往返时延 |
| 接收者报告 | RR | 媒体接收方→发送方 | 丢包率、累计丢包数、到达抖动 |
| 源描述 | SDES | 双方 | CNAME 等标识信息,用于多流关联与冲突检测 |
| 离会 | BYE | 双方 | 宣布退出会话 |
三个核心统计量的算法,就是面试和排障都绕不开的基本功。
丢包率:接收端在报告周期内统计“应收而未收”的包,用序列号间隙推断丢失,按比例上报:
$$FLR = \frac{L}{E + L}$$
其中 L 为本周期丢失数,E 为正常收到数。收到端每秒 50 包、周期 5 秒应收 250 包、丢 5 包,则上报丢包率 2%。
到达间隔抖动:理想情况下包每 20 ms 到一个,实际总会有波动。RTCP 用一个滑动平均来估计这种波动,且刻意做成不对称滤波——突发抖动要快速跟上,抖动消退则慢慢回落:
$$J_i = J_{i-1} + \frac{\lvert D_{i-1,i} \rvert - J_{i-1}}{16}$$
其中 D 是相邻两包“到达间隔与发送间隔之差”。分母 16 意味着大约 16 个包才能把估算值拉过去一半:对突发敏感,对毛刺钝感。这个值直接决定对端抖动缓冲该开多大——你之前学过的抖动缓冲原理,其输入依据正是这个数。
往返时延:借 SR 里的时间戳三件套计算:
$$RTT = A - LSR - DLSR$$
接收端在 RR 里附上“最后一次收到 SR 的时刻(LSR)”和“距那次 SR 过了多久(DLSR)”,发送端用本机当前时刻 A 一减,就得到整个回执环路的耗时。
至于发送频率,RFC 3550 给的基准是:RTCP 流量控制在会话带宽的 5% 左右,且单个报文间隔不短于约 5 秒。这个保守值是按大规模会议(成百上千人互相发报告)设计的;两人通话中这个间隔明显太疏,实际系统普遍会缩短周期或采用更快的反馈机制。这也解释了一个现网现象:你在通话里抓 RTCP 包,它稀稀拉拉,但每个都金贵——网络侧的丢包 KPI、链路质量评估,原料全在这里面。
抓包三件事。 用 Wireshark 抓一通 VoLTE 通话,过滤出 RTP 流后:一看序列号是否连续——跳号就是丢包,Wireshark 的 RTP Stream Analysis 能直接画出丢包率随时间曲线;二看时间戳增量是否恒定为 160(或宽带语音的 320)——增量突变往往意味着编码模式切换或丢包后补偿;三看 PT 载荷类型——通话中段 PT 变化说明发生了编码切换,比如从语音切到了视频通话。
三个高频陷阱。
一是序列号回绕误判。自己写的 RTP 处理逻辑用简单的数值比较判断先后,21.8 分钟后必出诡异 bug——通话进行到一半突然出现“大量乱序”。正确做法是按模 65536 的回绕感知比较。
二是SSRC 冲突。SSRC 是各方随机生成的,理论上会撞号。RFC 3550 要求端点检测到冲突后主动换号。你在抓包时看到一条流中途 SSRC 突变、序列号却接得上,多半是发生了冲突切换或发送端重启了媒体栈,不是丢了一条流。
三是NAT 老化。RTP over UDP 的会话在 NAT 设备上有老化时间,静音期长时间单向无包(不连续传输)可能让映射表项过期,声音突然单向中断几秒又恢复。工程上的对策是媒体保活或对称 RTP——端点收包往哪来,发包就往哪去。
一个常见误解:把 RTCP 丢包率高当成“RTP 出问题了”。RTCP 只是体温计,它如实报告链路状况;病灶通常在无线覆盖、承载资源或拥塞上。体温计不治病,但没体温计你连病了都不知道。
Q: RTP 和 SRTP 是什么关系? A: SRTP 在 RTP 基础上加了一层加密与完整性保护,报文格式兼容。WebRTC 体系用它保护浏览器间媒体。VoLTE 的语音加密主要在无线接入层完成,核心网段的媒体保护方式由部署方决定,标准允许多种实现,具体以各厂家官方文档为准。
Q: RTP 说的“实时”到底体现在哪?它又不能加速网络。 A: 体现在“放弃治愈”的智慧上。它不承诺任何时延,但把时延的知情权和决策权交给应用:序列号让你知道丢了什么,时间戳让你知道该放哪一秒,RTCP 让你量化链路多糟。播放端据此决定“这个包等到什么时候为止”。实时性不是被保证出来的,是被测量和取舍出来的。
Q: 有了 UDP 为什么还要 RTP?直接用 UDP 自定义格式不行吗? A: 自定义格式当然可行,很多私有系统就是这么干的。RTP 的价值在于把序列号、时间戳、源标识、载荷类型这些所有实时应用共同的需求标准化,让互通成为可能,并让几十年来积累的成熟工具链(抓包分析、媒体服务器、防火墙辨识)直接可用。
Q: RTCP 5 秒才发一次报告,发现丢包是不是太晚了? A: 对交互式通话确实偏疏。标准给出的是通用底线,实际系统普遍采用更短周期或快速反馈机制来适配实时场景;大规模会议场景则反过来要抑制报告风暴。频率本质上是在“反馈及时性”和“控制信道开销”之间做交易,没有免费的选择。
Q: 为什么不用能可靠传输的新协议(比如 QUIC)传语音? A: 语音不怕丢只怕等,而任何以可靠为目标的协议都会用重传换完整,时延随之波动。事实上 WebRTC 的媒体部分至今仍走 RTP over UDP,只有数据通道采用可靠协议。协议选型的第一问永远是:这个应用怕丢,还是怕等?
本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
![[知识星球二维码.png]]