1. RTP协议基础:RTP/RTCP协议栈概述、RTP在流媒体传输中的角色、RTP与UDP的关系
各位同学好,我是老周。做流媒体通信这行十几年了,调试过的RTP流估计能绕地球好几圈。今天咱们开始第一课,先把地基打牢。
很多人一上来就扎进参数堆里,结果越调越乱。为什么?因为你不清楚RTP在整个传输链路里到底扮演什么角色。说白了,RTP就是流媒体的“快递员”——它不保证包裹一定送到,但它负责把包裹打包好、贴上标签、按顺序发出去。
1.1 RTP/RTCP协议栈:一对孪生兄弟
RTP全称是实时传输协议,RTCP是RTP控制协议。这两个协议总是成对出现,就像快递员和物流跟踪系统——一个负责送货,一个负责告诉你货到哪了。
核心要点:RTP负责媒体数据的实际传输,RTCP负责传输质量的监控和反馈。两者缺一不可。
我刚开始做视频通话项目时,只关注了RTP的发送,完全没管RTCP。结果呢?画面卡成幻灯片,我还以为是带宽不够。后来才发现,没有RTCP的反馈,发送端根本不知道网络状况,只能盲目发包。嗯,这个坑我替你们踩过了。
来看一下RTP/RTCP在协议栈中的位置:
你看,RTP/RTCP夹在应用层和UDP之间。编码器产出的H.264或AAC数据,先交给RTP打包,再塞进UDP报文里,最后通过IP网络发出去。
1.2 RTP在流媒体传输中的角色
RTP的核心职责就三件事:打包、排序、时间同步。你想想看,视频通话时,每一帧画面被切成多个包发送。如果没有RTP的序列号,接收端怎么知道哪个包先到、哪个包后到?
我给你们列个表,看看RTP头里到底藏了哪些关键信息:
| 字段 | 长度(位) | 作用 | 我的经验 |
|---|---|---|---|
| 序列号 | 16 | 包顺序标识,检测丢包 | 调试时我最先看这个字段 |
| 时间戳 | 32 | 媒体采样时间,用于播放同步 | 时间戳跳变异常,八成是编码器问题 |
| SSRC | 32 | 同步源标识,区分不同媒体流 | SSRC冲突会导致流混乱 |
| CSRC | 0~96 | 贡献源列表,用于混音场景 | 会议场景才用得到 |
| PT(负载类型) | 7 | 标识媒体编码格式 | PT值对不上,解码器直接罢工 |
调试小技巧:抓包时先看序列号是否连续。如果序列号跳了,说明有丢包;如果序列号乱序,说明网络抖动大。这两个问题,处理方式完全不同。
我记得有一次排查视频卡顿问题,抓包看了半天,序列号连续、时间戳也正常,就是画面卡。后来才发现,是接收端的jitter buffer设置太大,导致延迟过高。你看,RTP本身没问题,但上下游配合不好,照样出故障。
1.3 RTP与UDP的关系:为什么非要用UDP?
这个问题我面试时经常问。很多人回答“因为UDP快”,但快不是唯一原因。你想想看,视频通话能容忍偶尔丢几个包,但能容忍等TCP重传吗?不能。延迟超过200毫秒,人就觉得卡了。
RTP选择UDP,核心原因就两个:
- 低延迟:UDP没有三次握手,没有拥塞控制,发出去就完事。这对实时流媒体至关重要。
- 无阻塞:TCP如果丢包,会阻塞后续所有数据。RTP + UDP丢一个包,其他包照常到达,接收端自己处理丢包补偿。
但UDP也有个致命问题——它不保证送达。所以RTP才需要RTCP来反馈丢包率、抖动、往返时延等信息。说白了,RTP负责“怎么发”,RTCP负责“发得怎么样”。
注意:千万不要在公网直接用裸UDP传输RTP。很多企业防火墙会拦截UDP流量。我建议用SRTP加密,或者通过TURN服务器中转。否则,你的流可能根本出不了内网。
来看一下RTP/UDP的封装关系:
从这张图你能看到,RTP头只有12字节固定部分,加上UDP头8字节,IP头20字节,总共40字节的开销。如果你的媒体负载只有几十字节,那效率就很低了。我一般建议每个RTP包至少带几百字节的负载,否则带宽都浪费在头上了。
1.4 一个简单的RTP发送示例
光说不练假把式。我给你们看一段伪代码,演示RTP包的基本构造过程。实际项目中我会用成熟的库,但理解底层原理很重要:
// RTP发送伪代码示例
function sendRTPPacket(mediaData, payloadType, sequenceNumber, timestamp, ssrc) {
// 1. 构造RTP头(12字节)
rtpHeader = new byte[12];
rtpHeader[0] = 0x80; // V=2, P=0, X=0, CC=0
rtpHeader[1] = payloadType; // 负载类型,如 96 表示H.264
rtpHeader[2] = (sequenceNumber >> 8) & 0xFF;
rtpHeader[3] = sequenceNumber & 0xFF;
rtpHeader[4] = (timestamp >> 24) & 0xFF;
rtpHeader[5] = (timestamp >> 16) & 0xFF;
rtpHeader[6] = (timestamp >> 8) & 0xFF;
rtpHeader[7] = timestamp & 0xFF;
rtpHeader[8] = (ssrc >> 24) & 0xFF;
rtpHeader[9] = (ssrc >> 16) & 0xFF;
rtpHeader[10] = (ssrc >> 8) & 0xFF;
rtpHeader[11] = ssrc & 0xFF;
// 2. 组合RTP包 = 头 + 负载
rtpPacket = rtpHeader + mediaData;
// 3. 通过UDP socket发送
udpSocket.send(rtpPacket, destinationAddress, destinationPort);
}
这段代码虽然简单,但包含了RTP发送的核心逻辑。实际项目中,你还要处理分片(比如H.264的NAL单元大于MTU时)、时间戳生成、序列号管理等。这些我们后面章节会逐一展开。
好了,第一课就到这里。记住一句话:RTP是流媒体的骨架,RTCP是神经系统,UDP是血管。这三者配合好了,你的流媒体系统才能跑得稳。下一课我们深入RTP头的每个字段,看看调试时到底该盯哪些位。