网络环境摸底:带宽探测、丢包率与RTT测量、NAT类型检测
做WebRTC推流,第一件事不是调编码参数,也不是改拥塞算法。
而是先搞清楚你的网络到底什么德行。
我见过太多团队,花了两周优化编码器,结果卡顿是因为上行带宽只有500kbps,码率却设了2Mbps。说白了,方向错了。
这一章,我们就来给网络做个全面体检。三个核心指标:带宽、丢包与RTT、NAT类型。摸清了底牌,后面的优化才有依据。
核心观点:网络环境摸底是推流优化的前提。跳过这一步,所有优化都是盲人摸象。
2.1 带宽探测:你到底能跑多快?
带宽不是一成不变的。Wi-Fi信号波动、小区共享带宽、运营商限速,都会让你的实际可用带宽忽高忽低。
WebRTC内部有带宽估计模块,但它的初始值往往偏保守。我个人习惯在建立连接前,先主动做一次带宽探测。
2.1.1 探测方法
- 基于RTT的探测:通过发送递增大小的数据包,观察RTT的突变点。当RTT突然增大,说明网络开始排队,接近带宽上限。
- 基于吞吐量的探测:直接发送大块数据,测量单位时间内的接收量。简单粗暴,但容易对现有网络造成冲击。
- WebRTC内置的GCC(Google Congestion Control):基于延迟和丢包的混合估计。我建议用它做实时调整,但不要依赖它做初始探测。
我的经验:在项目初期,我会写一个简单的带宽探测工具,用UDP发送递增数据包,持续3-5秒。这样能快速摸清网络的“天花板”。
2.1.2 带宽探测代码示例
下面是一个简化的带宽探测逻辑,使用Node.js实现。实际生产环境需要更完善的超时和重试机制。
const dgram = require('dgram');
const client = dgram.createSocket('udp4');
const SERVER_PORT = 5000;
const SERVER_HOST = '192.168.1.100';
const PACKET_SIZE = 1400; // 接近MTU
const INTERVAL = 10; // 毫秒
let sentBytes = 0;
let startTime = Date.now();
function sendProbe() {
const buf = Buffer.alloc(PACKET_SIZE);
buf.writeUInt32BE(sentBytes, 0); // 序列号
client.send(buf, SERVER_PORT, SERVER_HOST, (err) => {
if (!err) {
sentBytes += PACKET_SIZE;
if (Date.now() - startTime < 5000) { // 持续5秒
setTimeout(sendProbe, INTERVAL);
} else {
const elapsed = (Date.now() - startTime) / 1000;
const bandwidth = (sentBytes * 8) / elapsed / 1000; // kbps
console.log(`Estimated bandwidth: ${bandwidth.toFixed(2)} kbps`);
client.close();
}
}
});
}
sendProbe();
2.1.3 带宽探测结果解读
| 探测结果 | 可能原因 | 建议操作 |
|---|---|---|
| 带宽稳定 > 2Mbps | 网络良好 | 可尝试720p推流 |
| 带宽在 500kbps - 2Mbps | 中等网络 | 建议使用自适应码率 |
| 带宽 < 500kbps | 网络较差 | 考虑降分辨率或使用SVC |
| 带宽波动剧烈 | 无线干扰或共享带宽 | 启用带宽估计平滑算法 |
注意:带宽探测本身会占用网络资源。不要在推流过程中频繁探测,建议在连接建立前或推流空闲时进行。
2.2 丢包率与RTT测量:网络质量的晴雨表
带宽够用不代表不卡顿。丢包和延迟才是卡顿的元凶。
丢包率直接决定视频能否连续播放。RTT则影响交互的实时性。这两个指标,必须实时监控。
2.2.1 测量方法
WebRTC的RTCP Receiver Report(RR)和Sender Report(SR)已经包含了丢包率和RTT信息。我们可以直接从中提取。
但如果你需要更精细的控制,可以自己实现探测包机制。
// 伪代码:从RTCP中提取丢包率和RTT
function parseRTCP(packet) {
// 假设packet是RTCP RR报文
const fractionLost = packet.readUInt8(8); // 丢包率,单位256分之一
const cumulativeLost = packet.readUInt24(9); // 累计丢包数
const lastSR = packet.readUInt32(16); // 上次SR的NTP时间戳
const delaySinceLastSR = packet.readUInt32(20); // 距上次SR的延迟
const lossRate = fractionLost / 256.0;
const rtt = delaySinceLastSR - lastSR; // 简化计算
return { lossRate, rtt };
}
2.2.2 指标阈值参考
| 指标 | 优秀 | 良好 | 较差 | 不可用 |
|---|---|---|---|---|
| 丢包率 | < 0.5% | 0.5% - 2% | 2% - 5% | > 5% |
| RTT | < 50ms | 50ms - 150ms | 150ms - 300ms | > 300ms |
避坑指南:我曾经遇到一个案例,丢包率只有1%,但视频依然卡顿。后来发现是RTT高达400ms,导致NACK重传包迟迟不到。所以,丢包率和RTT要一起看,不能只看一个。
2.3 NAT类型检测:你的节点能直连吗?
WebRTC的P2P连接依赖ICE(Interactive Connectivity Establishment)协议。ICE能否成功,很大程度上取决于NAT类型。
如果双方都是对称NAT,那STUN和TURN都救不了,只能走TURN中继。中继意味着额外的延迟和带宽成本。
2.3.1 常见NAT类型
- 完全锥形NAT:最友好,任何外部主机都能通过映射地址访问内部主机。
- 限制锥形NAT:只有内部主机曾经通信过的外部主机才能访问。
- 端口限制锥形NAT:在限制锥形基础上,还限制了端口。
- 对称NAT:最严格,每次通信都使用不同的映射地址和端口。P2P几乎不可能。
2.3.2 检测方法
经典的RFC 3489方法,需要三个STUN服务器。但实际项目中,我更喜欢用Google的libjingle中的NAT检测逻辑,它更稳定。
检测流程如下:
- 向STUN服务器发送请求,获取公网IP和端口。
- 向另一个IP的STUN服务器发送请求,看公网IP和端口是否变化。
- 让STUN服务器从不同IP和端口回包,看是否能收到。
根据结果,可以判断NAT类型。
关键点:对称NAT是P2P的天敌。如果检测到对称NAT,建议直接启用TURN中继,不要浪费时间做P2P尝试。
2.4 本章知识体系
下面这张图,把网络环境摸底的三个维度串起来了。你可以把它当作排查卡顿的起点。
2.5 实战:整合三个指标
光测出来没用,得会用。我通常的做法是,在推流SDK初始化时,依次执行带宽探测、丢包率基线测量和NAT类型检测。然后根据结果,动态调整推流参数。
举个例子:
- 如果带宽 > 2Mbps,丢包率 < 1%,NAT为锥形,直接走P2P推720p。
- 如果带宽 < 1Mbps,丢包率 > 3%,NAT为对称,走TURN中继,降分辨率到360p,开启FEC。
- 如果带宽波动大,启用自适应码率,让GCC接管。
注意:网络环境是动态变化的。推流过程中,要定期(比如每30秒)重新评估带宽和丢包率,但不能频繁做NAT检测。NAT类型在会话期间基本不变。
嗯,到这里,网络环境摸底就讲完了。你想想看,有了这些数据,你还会盲目地调编码参数吗?