webrtc源码走读(七)核心引擎层——Qos模块

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 会议场景 优先保证基础画质清晰
相关推荐
qdprobot3 分钟前
开源的在线串口调试助手支持macOS苹果电脑Windows系统Linux 浏览器webSerial
linux·运维·服务器·人工智能·mixly·小智ai·webserial
梵尔纳多5 分钟前
绘制一个三角形
c++·图形渲染·opengl
饭九钦vlog24 分钟前
dns形式的floodddos命令
linux·运维·服务器
gaize121331 分钟前
服务器异常的处理方法
服务器
Anthony_23139 分钟前
MySql常用SQL命令
服务器·数据库·sql·mysql·http·oracle·udp
汉克老师1 小时前
GESP2025年12月认证C++六级真题与解析(单选题8-15)
c++·算法·二叉树·动态规划·哈夫曼编码·gesp6级·gesp六级
树码小子1 小时前
网络原理(13):TCP协议十大核心机制 -- 确认应答 & 超时重传
服务器·网络·tcp/ip
王火火(DDoS CC防护)1 小时前
什么是域名解析?如何进行域名解析?
服务器·域名·域名解析
谢平康1 小时前
通过nfs方式做目录限额方法
linux·服务器·网络
manjianghong861 小时前
制作高质量AI视频需要哪些步骤
人工智能·音视频·ai视频·ai应用