1、Qos整体架构与设计哲学
WebRTC 的 QoS(服务质量)机制是一套端到端闭环反馈系统,运行在应用层,不依赖网络设备。其核心目标是:
- 保障实时性:音视频通信对延迟极其敏感(语音 >100ms 就会影响可懂度)
- 最大化可用带宽利用率:在不造成拥塞的前提下尽可能提高画质/音质
- 容忍丢包与抖动:互联网环境无法保证可靠传输,必须具备容错能力
整个系统通过接收端反馈 → 发送端决策 → 媒体调整的闭环,动态适应网络变化。
为什么必须闭环?
因为网络状态瞬息万变(如从 WiFi 切到 4G),只有基于接收端真实反馈的决策才可靠。预测模型(如仅靠丢包率判断拥塞)在真实网络中极易误判。
2、QoS 模块与其他组件的交互关系
接收端
发送端
原始帧
分层编码
RTP包
带宽估计
层码率建议
重传包
RTP包
解码帧
丢包列表
NACK RTCP
到达时间
抖动值
Transport-CC RTCP
RTCP反馈
Video Encoder
SVC Controller
Pacer
Network
BWE
NACK Resender
Jitter Buffer
Video Decoder
Packet Loss Detector
NACK Generator
Transport Feedback
BWE
Jitter Estimator
关键交互说明:
| 交互方向 | 触发条件 | 数据内容 | 作用 |
|---|---|---|---|
| 接收端 → 发送端 | 每 50~100ms | Transport Feedback(包到达时间) | BWE 计算可用带宽 |
| 接收端 → 发送端 | 检测到丢包 | NACK RTCP(丢失序列号) | 请求重传关键视频包 |
| BWE → Pacer | 带宽更新 | 目标比特率(bps) | 控制发送节奏,避免突发 |
| BWE → SVC Controller | 带宽变化 | 各层最大码率 | 动态开启/关闭增强层 |
| Jitter Buffer → BWE | 抖动过大 | 网络波动信号 | 辅助 BWE 判断拥塞 |
3、核心机制详解
3.1. NACK:丢包重传(仅视频)
作用
当接收端发现 RTP 包丢失时,主动请求发送端重传。适用于低 RTT(<300ms)且丢包率中等(5%~15%) 的场景。
工作流程与时序图
接收端 网络 发送端 接收端 网络 发送端 包 发送 RTP 包 收到 检测到 发送 NACK RTCP (seq=101) 从历史缓存查找 重传 RTP 包 收到重传包 完整帧组装 → 解码
源码(接收端生成 NACK)
cpp
// 文件: modules/rtp_rtcp/source/rtcp_receiver.cc
void RtcpReceiver::OnReceivedRtcpReportBlock(
const RtcpReportBlock& report_block,
uint32_t ssrc) {
uint16_t last_seq = report_block.last_sequence_number;
std::vector<uint16_t> nack_list;
// 回溯最多 2000 毫秒内的包(默认窗口)
for (uint16_t seq = last_seq - 1;
seq != last_seq && nack_list.size() < kMaxNackListSize;
--seq) {
if (packet_receiver_->IsPacketLost(seq)) {
nack_list.push_back(seq);
}
// 防止无限循环(序列号回绕)
if (nack_list.size() >= 2000) break;
}
if (!nack_list.empty()) {
rtp_rtcp_->SendNack(ssrc, nack_list); // 发送 NACK RTCP
}
}
源码(发送端重传)
cpp
// 文件: modules/rtp_rtcp/source/rtp_packet_history.cc
bool RtpPacketHistory::GetPacket(uint16_t sequence_number,
RtpPacketToSend* packet) {
// 在历史缓存中查找对应序列号的包
for (const auto& item : packet_history_) {
if (item.packet.sequence_number() == sequence_number) {
*packet = item.packet;
return true;
}
}
return false; // 包已过期(超过 2000ms),无法重传
}
3.2. RTT:往返延迟测量
作用
RTT 是判断是否启用 NACK 的关键阈值(通常 <100ms 才有效),也用于 Jitter Buffer 的目标延迟计算。
工作流程
Receiver Sender Receiver Sender 发送 Sender Report (SR),含 LSR 时间戳 记录 LSR,并等待一段时间(DLSR) 发送 Receiver Report (RR),含 DLSR 计算 RTT = 当前时间 - LSR - DLSR
源码(RTT 计算)
cpp
// 文件: modules/rtp_rtcp/source/rtcp_receiver.cc
void RtcpReceiver::OnRtcpReceived(const RtcpPacket* packet) {
if (packet->type() == kRtcpRr) {
const RtcpRrPacket* rr = static_cast<const RtcpRrPacket*>(packet);
// 提取 LSR(Sender Report 时间戳)和 DLSR(Delay since LSR)
uint32_t lsr = rr->lsr();
uint32_t dlsr = rr->dlsr();
// 转换为毫秒(高16位是秒,低16位是小数)
int64_t lsr_ms = (lsr >> 16) * 1000;
int64_t dlsr_ms = (dlsr >> 16) * 1000;
// RTT = 当前时间 - LSR - DLSR
int64_t rtt_ms = clock_->TimeInMilliseconds() - lsr_ms - dlsr_ms;
// 指数加权平均平滑(α=0.1)
if (rtt_ms > 0) {
estimated_rtt_ms_ = 0.1 * rtt_ms + 0.9 * estimated_rtt_ms_;
}
}
}
3.3. FEC:前向纠错(仅视频)
作用
通过冗余包恢复丢失数据,适用于高丢包率(>15%)或高 RTT(>300ms) 场景,避免 NACK 延迟过高。
与 NACK 的协同策略
| 网络条件 | 策略 | 原因 |
|---|---|---|
| 丢包率 < 5%,RTT < 100ms | 仅 NACK | 带宽效率最高 |
| 丢包率 5%15%,RTT 100300ms | NACK + 低冗余 FEC | 混合容错 |
| 丢包率 > 15% 或 RTT > 300ms | 仅 FEC | 避免 NACK 延迟 |
源码(FEC 生成)
cpp
// 文件: modules/rtp_rtcp/source/fec_generator.cc
void FecGenerator::GenerateFecPackets(
const std::vector<RtpPacketToSend>& packets,
std::vector<RtpPacketToSend>* fec_packets) {
if (packets.empty()) return;
// 计算 FEC 包数量(例如冗余度 20%)
size_t num_fec = (packets.size() * fec_rate_ + 99) / 100;
for (size_t i = 0; i < num_fec; ++i) {
RtpPacketToSend fec;
fec.SetPayloadType(kFlexfecPayloadType);
fec.SetSequenceNumber(packets.back().sequence_number() + i + 1);
// 简化版:对所有包 payload 异或生成 FEC
std::vector<uint8_t> fec_payload;
for (const auto& pkt : packets) {
const uint8_t* p = pkt.payload();
size_t sz = pkt.payload_size();
if (fec_payload.empty()) {
fec_payload.assign(p, p + sz);
} else {
for (size_t j = 0; j < std::min(fec_payload.size(), sz); ++j) {
fec_payload[j] ^= p[j];
}
}
}
fec.SetPayload(fec_payload.data(), fec_payload.size());
fec_packets->push_back(std::move(fec));
}
}
3.4. BWE:带宽估计(Transport-CC)
作用
实时估算可用上行带宽,为 Pacer 和 SVC 提供决策依据。
工作流程与时序图
SVC Pacer Receiver Network Sender SVC Pacer Receiver Network Sender 发送 RTP 包(带 TCC 序列号) RTP 包(可能延迟/乱序) 记录每个包的到达时间 Transport Feedback RTCP(含序列号+到达时间) 计算排队延迟 → 更新带宽估计 设置新目标比特率 调整各层码率
源码(BWE 更新)
cpp
// 文件: modules/congestion_controller/goog_cc/goog_cc_bandwidth_estimator.cc
void GoogCcBandwidthEstimator::OnTransportFeedback(
const TransportFeedback& feedback) {
for (const auto& packet : feedback.packets) {
int64_t delay_ms = packet.arrival_time_ms - packet.send_time_ms;
// 卡尔曼滤波器平滑延迟
double predicted = kalman_filter_.Predict();
double residual = delay_ms - predicted;
kalman_filter_.Update(residual);
// 简化带宽计算(实际使用 AIMD 算法)
if (delay_ms > 0) {
int bitrate = (packet.size_bytes * 8000) / delay_ms;
current_estimate_ = bitrate;
}
}
// 通知 Pacer 和 SVC
observer_->OnBandwidthEstimate(current_estimate_);
}
3.5. SVC:可伸缩视频编码
作用
将视频分为基础层(BL)和增强层(EL),根据带宽动态调整质量。
层级结构示例(VP9 S1T3 模式)
| 层级 | 分辨率 | 码率占比 | 作用 |
|---|---|---|---|
| BL | 320×180 | 30% | 必需层,保证基本可用 |
| EL1 | 640×360 | 40% | 中等质量 |
| EL2 | 1280×720 | 30% | 高清质量 |
源码(SVC 控制)
cpp
// 文件: modules/video_coding/svc/scalable_video_controller.cc
void ScalableVideoController::OnBandwidthEstimate(int bitrate_bps) {
const float weights[3] = {0.3f, 0.4f, 0.3f}; // BL, EL1, EL2
int layer_bitrates[3] = {0};
for (int i = 0; i < num_layers_; ++i) {
layer_bitrates[i] = static_cast<int>(bitrate_bps * weights[i]);
// 不超过该层上限
if (layer_bitrates[i] > max_bitrate_[i]) {
layer_bitrates[i] = max_bitrate_[i];
}
}
encoder_->SetLayerBitrates(layer_bitrates); // 通知编码器
}
3.6. Pacer:发送调度器
作用
将 BWE 限制的总带宽均匀分配到时间片,避免突发流量导致拥塞。
工作流程
BWE: 1000kbps
Pacer
每5ms发送 625 字节
网络
源码(Pacer 调度)
cpp
// 文件: modules/pacing/paced_sender.cc
void PacedSender::Process() {
int target_bps = congestion_controller_->GetTargetBitrate();
// 5ms 时间片应发送的字节数
int bytes_to_send = (target_bps * 5) / (1000 * 8);
int sent = 0;
while (sent < bytes_to_send && !packet_queue_.empty()) {
RtpPacketToSend pkt = packet_queue_.Pop();
network_->SendPacket(pkt);
sent += pkt.size();
}
// 5ms 后再次调度
task_queue_.PostDelayedTask([this]() { Process(); }, 5);
}
4、全链路工作流程示例(网络恶化场景)
场景:用户从 WiFi 切换到 4G,丢包率从 2% 升至 15%,RTT 从 50ms 升至 250ms。
JitterBuffer BWE Receiver Network Pacer SVC控制器 编码器 应用层 JitterBuffer BWE Receiver Network Pacer SVC控制器 编码器 应用层 用户切换到4G,网络恶化 输入 1080p 视频帧 编码为 BL+EL1+EL2 三层 RTP 包(1000kbps) 均匀发送 正常接收(丢包率2%) 丢包率升至15%,RTT=250ms 检测丢包 → 生成 NACK Transport Feedback(延迟增大) 带宽估计降至 800kbps 建议降低码率 新目标速率 800kbps 关闭 EL2,保留 BL+EL1 降速至 800kbps 发送 NACK 请求 重传丢失包 发送 FEC 冗余包 恢复完整帧 组装帧 → 解码播放
5、不同场景关键参数调整
| 参数 | 默认值 | 可调整的值(仅举例) | 适用场景 | 说明 |
|---|---|---|---|---|
kNackWindowMs |
2000ms | 1500ms | 移动网络 | 减少内存占用,避免重传过期包 |
kBandwidthEstimationWindowMs |
1000ms | 500ms | 高抖动网络 | 提升 BWE 响应速度 |
| FEC 冗余度 | 动态 | 15%(固定) | 4G/弱网 | 稳定抗丢包 |
| SVC 层权重 | BL:0.3, EL1:0.4, EL2:0.3 | BL:0.4, EL1:0.4, EL2:0.2 | 会议场景 | 优先保证基础画质清晰 |