2. WebRTC中的RTP通道架构:PeerConnection与RtpTransceiver、RtpSender与RtpReceiver设计、SDP中的RTP参数协商、RTP扩展头(RTP Header Extension)机制
聊到WebRTC的RTP通道,很多人第一反应就是PeerConnection。没错,它是整个音视频通信的入口。但真正干活的是谁?是藏在里面的RtpTransceiver、RtpSender和RtpReceiver。我刚开始接触这部分时,也以为PeerConnection直接管理媒体流,后来才发现——它只是个调度中心。
一句话总结:PeerConnection是外壳,RtpTransceiver是通道,RtpSender/RtpReceiver是收发器。SDP负责谈好参数,RTP扩展头则是在报头里塞额外信息。
2.1 PeerConnection与RtpTransceiver:一对多的关系
PeerConnection可以理解为一个会议室的墙。墙上可以开多个窗口,每个窗口就是一个RtpTransceiver。每个Transceiver负责一路媒体——要么是音频,要么是视频。
我记得有个项目,客户要求同时传输三路视频流(主摄像头、文档摄像头、屏幕共享)。一开始他们创建了三个PeerConnection,结果ICE连接数爆炸,NAT穿透成功率直线下降。我建议改用单个PeerConnection加三个RtpTransceiver,问题立刻解决。
为什么?因为PeerConnection只维护一条ICE通道,而Transceiver共享这条通道。你想想看,如果每个流都单独建连接,光STUN/TURN的握手开销就够受的。
| 组件 | 职责 | 数量关系 |
|---|---|---|
| PeerConnection | ICE管理、DTLS加密、SDP协商入口 | 1个连接 |
| RtpTransceiver | 管理一对Sender+Receiver,绑定mid | N个(每路媒体1个) |
| RtpSender | 编码、打包、发送RTP包 | 每个Transceiver最多1个 |
| RtpReceiver | 接收、解包、送入解码器 | 每个Transceiver最多1个 |
2.2 RtpSender与RtpReceiver:收发分离的设计哲学
WebRTC把发送和接收拆成两个独立对象,这设计其实很聪明。发送端只管把MediaStreamTrack编码成RTP包,接收端只管把RTP包还原成帧。两者通过Transceiver关联,但生命周期可以不同。
举个例子:你可以先创建一个只接收的Transceiver(direction设为"recvonly"),这时候RtpSender是空的。等需要发送时,再通过transceiver.sender.replaceTrack()把轨道挂上去。这种灵活性在实际项目中太重要了。
// 创建只接收的Transceiver
const transceiver = pc.addTransceiver('video', {
direction: 'recvonly'
});
// 后续需要发送时,替换轨道
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
transceiver.sender.replaceTrack(stream.getVideoTracks()[0]);
transceiver.direction = 'sendrecv';
个人经验:我曾经在调试一个多方会议时,发现某路视频一直黑屏。查了半天,原来是replaceTrack调用后忘了更新direction。接收端还在等数据,发送端却以为自己是recvonly。这种坑,踩过一次就记住了。
2.3 SDP中的RTP参数协商:一场精心设计的谈判
SDP协商说白了就是两端坐下来谈:你用啥编码?码率多少?包多大?这些参数都写在SDP的媒体行里。
一个典型的视频SDP片段长这样:
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98
a=rtpmap:96 H264/90000
a=rtpmap:97 VP8/90000
a=rtpmap:98 VP9/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42e01f
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
这里面有几个关键点:
- m=行:声明媒体类型、端口、传输协议、payload类型列表
- rtpmap:把payload类型映射到具体编码格式和时钟频率
- fmtp:编码参数,比如H264的packetization-mode
- rtcp-fb:反馈机制,NACK和PLI是WebRTC的标配
- extmap:RTP扩展头的ID映射
协商过程遵循Offer/Answer模型。发起方发Offer,接收方回Answer。Answer里只能从Offer提供的参数里选,不能自己发明新参数。这规则保证了双方能达成一致。
避坑指南:我曾经遇到一个兼容性问题——Chrome和Firefox协商H264时,Firefox的Answer里把profile-level-id改成了自己的值。Chrome收到后直接拒绝,认为参数不匹配。后来发现是Firefox的SDP实现有个bug,升级版本后解决。所以,永远不要假设对方的SDP实现完全符合规范。
2.4 RTP扩展头(RTP Header Extension)机制
RTP标准头只有12字节,能带的信息有限。实际场景中我们需要传递更多元数据——比如音频音量、视频旋转角度、绝对发送时间。这些信息放哪?RTP扩展头就是干这个的。
扩展头的设计遵循RFC 8285。它采用TLV(Type-Length-Value)结构,但做了优化:
- 每个扩展项通过SDP中的extmap分配一个本地ID(1-14)
- 传输时只带ID和值,省去了URI的冗余
- 支持单字节和双字节两种头格式
WebRTC中常用的扩展头:
| URI | 用途 | 长度 |
|---|---|---|
urn:ietf:params:rtp-hdrext:ssrc-audio-level |
音频音量等级 | 1字节 |
http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time |
绝对发送时间(用于带宽估算) | 3字节 |
urn:3gpp:video-orientation |
视频旋转角度 | 1字节 |
http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 |
传输级拥塞控制序列号 | 2字节 |
为什么要有绝对发送时间?说白了,接收端需要知道这个包是什么时候从发送端出来的。如果网络有抖动,接收端可以根据这个时间戳做播放缓冲调整。我在做跨国会议优化时,发现加上abs-send-time后,带宽估算的准确性提升了至少30%。
实现细节:扩展头在RTP包中的位置紧跟在固定头之后,CSRC列表之前。WebRTC的实现里,扩展头默认是开启的,但你可以通过SDP的a=extmap-allow-mixed来控制是否允许混合使用单字节和双字节格式。我个人建议统一用双字节格式,兼容性更好。
2.5 整体架构图
下面这张图展示了从PeerConnection到RTP包的完整链路。我习惯用这种分层视角来理解整个架构——每一层各司其职,互不干扰。
从图里可以清楚看到:PeerConnection在最上层,负责连接管理;中间是Transceiver层,每个Transceiver绑定一个mid;再往下是Sender和Receiver,它们直接操作RTP包;最底层是网络传输。扩展头则嵌入在RTP包内部,不影响分层结构。
2.6 实际部署中的几个要点
说了这么多理论,最后分享几个我在实际部署中总结的经验:
- Transceiver数量控制:虽然理论上可以创建很多Transceiver,但每个都会增加SDP大小和协商时间。我一般控制在5个以内,超过这个数建议考虑Simulcast或SVC。
- 扩展头顺序:SDP中extmap的顺序会影响RTP包中扩展头的排列。接收端解析时按ID查找,但发送端按顺序打包。如果你有多个扩展头,把变化频率高的放前面(比如audio-level),可以减少解析开销。
- mid的重要性:每个Transceiver的mid必须唯一,且在Offer/Answer中保持一致。我曾经遇到一个bug,Answer里把mid重新排序了,导致接收端把音频流当视频流解码——结果就是刺耳的噪声。
- direction切换:动态切换Transceiver的direction时,记得先停掉对应的Track,再改direction。否则可能出现发送端还在发数据,接收端已经关闭的情况,造成RTP包丢失。
核心要点:WebRTC的RTP通道架构是分层且灵活的。PeerConnection提供连接骨架,Transceiver提供媒体通道,Sender/Receiver负责实际收发,SDP协商确保双方参数一致,扩展头则在不破坏标准的前提下扩展了RTP的能力。理解这层关系,你就能在复杂的音视频场景中游刃有余。