1、Qos整体架构与设计哲学
WebRTC 的 QoS(服务质量)机制是一套端到端闭环反馈系统,运行在应用层,不依赖网络设备。其核心目标是:
- 保障实时性:音视频通信对延迟极其敏感(语音 >100ms 就会影响可懂度)
- 最大化可用带宽利用率:在不造成拥塞的前提下尽可能提高画质/音质
- 容忍丢包与抖动:互联网环境无法保证可靠传输,必须具备容错能力
整个系统通过接收端反馈 → 发送端决策 → 媒体调整的闭环,动态适应网络变化。
为什么必须闭环?
因为网络状态瞬息万变(如从 WiFi 切到 4G),只有基于接收端真实反馈的决策才可靠。预测模型(如仅靠丢包率判断拥塞)在真实网络中极易误判。
2、QoS 模块与其他组件的交互关系
#mermaid-svg-349ADd6jYuWJqO3z{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-349ADd6jYuWJqO3z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-349ADd6jYuWJqO3z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-349ADd6jYuWJqO3z .error-icon{fill:#552222;}#mermaid-svg-349ADd6jYuWJqO3z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-349ADd6jYuWJqO3z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-349ADd6jYuWJqO3z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-349ADd6jYuWJqO3z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-349ADd6jYuWJqO3z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-349ADd6jYuWJqO3z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-349ADd6jYuWJqO3z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-349ADd6jYuWJqO3z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-349ADd6jYuWJqO3z .marker.cross{stroke:#333333;}#mermaid-svg-349ADd6jYuWJqO3z svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-349ADd6jYuWJqO3z p{margin:0;}#mermaid-svg-349ADd6jYuWJqO3z .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-349ADd6jYuWJqO3z .cluster-label text{fill:#333;}#mermaid-svg-349ADd6jYuWJqO3z .cluster-label span{color:#333;}#mermaid-svg-349ADd6jYuWJqO3z .cluster-label span p{background-color:transparent;}#mermaid-svg-349ADd6jYuWJqO3z .label text,#mermaid-svg-349ADd6jYuWJqO3z span{fill:#333;color:#333;}#mermaid-svg-349ADd6jYuWJqO3z .node rect,#mermaid-svg-349ADd6jYuWJqO3z .node circle,#mermaid-svg-349ADd6jYuWJqO3z .node ellipse,#mermaid-svg-349ADd6jYuWJqO3z .node polygon,#mermaid-svg-349ADd6jYuWJqO3z .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-349ADd6jYuWJqO3z .rough-node .label text,#mermaid-svg-349ADd6jYuWJqO3z .node .label text,#mermaid-svg-349ADd6jYuWJqO3z .image-shape .label,#mermaid-svg-349ADd6jYuWJqO3z .icon-shape .label{text-anchor:middle;}#mermaid-svg-349ADd6jYuWJqO3z .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-349ADd6jYuWJqO3z .rough-node .label,#mermaid-svg-349ADd6jYuWJqO3z .node .label,#mermaid-svg-349ADd6jYuWJqO3z .image-shape .label,#mermaid-svg-349ADd6jYuWJqO3z .icon-shape .label{text-align:center;}#mermaid-svg-349ADd6jYuWJqO3z .node.clickable{cursor:pointer;}#mermaid-svg-349ADd6jYuWJqO3z .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-349ADd6jYuWJqO3z .arrowheadPath{fill:#333333;}#mermaid-svg-349ADd6jYuWJqO3z .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-349ADd6jYuWJqO3z .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-349ADd6jYuWJqO3z .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-349ADd6jYuWJqO3z .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-349ADd6jYuWJqO3z .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-349ADd6jYuWJqO3z .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-349ADd6jYuWJqO3z .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-349ADd6jYuWJqO3z .cluster text{fill:#333;}#mermaid-svg-349ADd6jYuWJqO3z .cluster span{color:#333;}#mermaid-svg-349ADd6jYuWJqO3z div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-349ADd6jYuWJqO3z .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-349ADd6jYuWJqO3z rect.text{fill:none;stroke-width:0;}#mermaid-svg-349ADd6jYuWJqO3z .icon-shape,#mermaid-svg-349ADd6jYuWJqO3z .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-349ADd6jYuWJqO3z .icon-shape p,#mermaid-svg-349ADd6jYuWJqO3z .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-349ADd6jYuWJqO3z .icon-shape rect,#mermaid-svg-349ADd6jYuWJqO3z .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-349ADd6jYuWJqO3z .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-349ADd6jYuWJqO3z .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-349ADd6jYuWJqO3z :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
接收端
发送端
原始帧
分层编码
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%) 的场景。
工作流程与时序图
接收端网络发送端接收端网络发送端#mermaid-svg-kPDX8JylfSJnsUMl{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kPDX8JylfSJnsUMl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kPDX8JylfSJnsUMl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kPDX8JylfSJnsUMl .error-icon{fill:#552222;}#mermaid-svg-kPDX8JylfSJnsUMl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kPDX8JylfSJnsUMl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kPDX8JylfSJnsUMl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kPDX8JylfSJnsUMl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kPDX8JylfSJnsUMl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kPDX8JylfSJnsUMl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kPDX8JylfSJnsUMl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kPDX8JylfSJnsUMl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kPDX8JylfSJnsUMl .marker.cross{stroke:#333333;}#mermaid-svg-kPDX8JylfSJnsUMl svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kPDX8JylfSJnsUMl p{margin:0;}#mermaid-svg-kPDX8JylfSJnsUMl .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kPDX8JylfSJnsUMl text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-kPDX8JylfSJnsUMl .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-kPDX8JylfSJnsUMl .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-kPDX8JylfSJnsUMl .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-kPDX8JylfSJnsUMl .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-kPDX8JylfSJnsUMl #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-kPDX8JylfSJnsUMl .sequenceNumber{fill:white;}#mermaid-svg-kPDX8JylfSJnsUMl #sequencenumber{fill:#333;}#mermaid-svg-kPDX8JylfSJnsUMl #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-kPDX8JylfSJnsUMl .messageText{fill:#333;stroke:none;}#mermaid-svg-kPDX8JylfSJnsUMl .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kPDX8JylfSJnsUMl .labelText,#mermaid-svg-kPDX8JylfSJnsUMl .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-kPDX8JylfSJnsUMl .loopText,#mermaid-svg-kPDX8JylfSJnsUMl .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-kPDX8JylfSJnsUMl .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-kPDX8JylfSJnsUMl .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-kPDX8JylfSJnsUMl .noteText,#mermaid-svg-kPDX8JylfSJnsUMl .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-kPDX8JylfSJnsUMl .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kPDX8JylfSJnsUMl .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kPDX8JylfSJnsUMl .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kPDX8JylfSJnsUMl .actorPopupMenu{position:absolute;}#mermaid-svg-kPDX8JylfSJnsUMl .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-kPDX8JylfSJnsUMl .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kPDX8JylfSJnsUMl .actor-man circle,#mermaid-svg-kPDX8JylfSJnsUMl line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-kPDX8JylfSJnsUMl :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}包发送 RTP 包收到检测到发送 NACK RTCP (seq=101)从历史缓存查找重传 RTP 包收到重传包完整帧组装 → 解码
源码(接收端生成 NACK)
// 文件: 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
}
}
源码(发送端重传)
// 文件: 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 的目标延迟计算。
工作流程
ReceiverSenderReceiverSender#mermaid-svg-UcFCmd26IvF3ljsp{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UcFCmd26IvF3ljsp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UcFCmd26IvF3ljsp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UcFCmd26IvF3ljsp .error-icon{fill:#552222;}#mermaid-svg-UcFCmd26IvF3ljsp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UcFCmd26IvF3ljsp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UcFCmd26IvF3ljsp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UcFCmd26IvF3ljsp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UcFCmd26IvF3ljsp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UcFCmd26IvF3ljsp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UcFCmd26IvF3ljsp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UcFCmd26IvF3ljsp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UcFCmd26IvF3ljsp .marker.cross{stroke:#333333;}#mermaid-svg-UcFCmd26IvF3ljsp svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UcFCmd26IvF3ljsp p{margin:0;}#mermaid-svg-UcFCmd26IvF3ljsp .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UcFCmd26IvF3ljsp text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-UcFCmd26IvF3ljsp .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-UcFCmd26IvF3ljsp .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-UcFCmd26IvF3ljsp .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-UcFCmd26IvF3ljsp .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-UcFCmd26IvF3ljsp #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-UcFCmd26IvF3ljsp .sequenceNumber{fill:white;}#mermaid-svg-UcFCmd26IvF3ljsp #sequencenumber{fill:#333;}#mermaid-svg-UcFCmd26IvF3ljsp #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-UcFCmd26IvF3ljsp .messageText{fill:#333;stroke:none;}#mermaid-svg-UcFCmd26IvF3ljsp .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UcFCmd26IvF3ljsp .labelText,#mermaid-svg-UcFCmd26IvF3ljsp .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-UcFCmd26IvF3ljsp .loopText,#mermaid-svg-UcFCmd26IvF3ljsp .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-UcFCmd26IvF3ljsp .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-UcFCmd26IvF3ljsp .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-UcFCmd26IvF3ljsp .noteText,#mermaid-svg-UcFCmd26IvF3ljsp .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-UcFCmd26IvF3ljsp .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UcFCmd26IvF3ljsp .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UcFCmd26IvF3ljsp .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UcFCmd26IvF3ljsp .actorPopupMenu{position:absolute;}#mermaid-svg-UcFCmd26IvF3ljsp .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-UcFCmd26IvF3ljsp .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UcFCmd26IvF3ljsp .actor-man circle,#mermaid-svg-UcFCmd26IvF3ljsp line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-UcFCmd26IvF3ljsp :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}发送 Sender Report (SR),含 LSR 时间戳记录 LSR,并等待一段时间(DLSR)发送 Receiver Report (RR),含 DLSR计算 RTT = 当前时间 – LSR – DLSR
源码(RTT 计算)
// 文件: 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 生成)
// 文件: 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 提供决策依据。
工作流程与时序图
SVCPacerReceiverNetworkSenderSVCPacerReceiverNetworkSender#mermaid-svg-89tklXiJcAuJHhtC{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-89tklXiJcAuJHhtC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-89tklXiJcAuJHhtC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-89tklXiJcAuJHhtC .error-icon{fill:#552222;}#mermaid-svg-89tklXiJcAuJHhtC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-89tklXiJcAuJHhtC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-89tklXiJcAuJHhtC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-89tklXiJcAuJHhtC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-89tklXiJcAuJHhtC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-89tklXiJcAuJHhtC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-89tklXiJcAuJHhtC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-89tklXiJcAuJHhtC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-89tklXiJcAuJHhtC .marker.cross{stroke:#333333;}#mermaid-svg-89tklXiJcAuJHhtC svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-89tklXiJcAuJHhtC p{margin:0;}#mermaid-svg-89tklXiJcAuJHhtC .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-89tklXiJcAuJHhtC text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-89tklXiJcAuJHhtC .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-89tklXiJcAuJHhtC .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-89tklXiJcAuJHhtC .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-89tklXiJcAuJHhtC .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-89tklXiJcAuJHhtC #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-89tklXiJcAuJHhtC .sequenceNumber{fill:white;}#mermaid-svg-89tklXiJcAuJHhtC #sequencenumber{fill:#333;}#mermaid-svg-89tklXiJcAuJHhtC #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-89tklXiJcAuJHhtC .messageText{fill:#333;stroke:none;}#mermaid-svg-89tklXiJcAuJHhtC .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-89tklXiJcAuJHhtC .labelText,#mermaid-svg-89tklXiJcAuJHhtC .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-89tklXiJcAuJHhtC .loopText,#mermaid-svg-89tklXiJcAuJHhtC .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-89tklXiJcAuJHhtC .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-89tklXiJcAuJHhtC .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-89tklXiJcAuJHhtC .noteText,#mermaid-svg-89tklXiJcAuJHhtC .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-89tklXiJcAuJHhtC .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-89tklXiJcAuJHhtC .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-89tklXiJcAuJHhtC .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-89tklXiJcAuJHhtC .actorPopupMenu{position:absolute;}#mermaid-svg-89tklXiJcAuJHhtC .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-89tklXiJcAuJHhtC .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-89tklXiJcAuJHhtC .actor-man circle,#mermaid-svg-89tklXiJcAuJHhtC line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-89tklXiJcAuJHhtC :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}发送 RTP 包(带 TCC 序列号)RTP 包(可能延迟/乱序)记录每个包的到达时间Transport Feedback RTCP(含序列号+到达时间)计算排队延迟 → 更新带宽估计设置新目标比特率调整各层码率
源码(BWE 更新)
// 文件: 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 控制)
// 文件: 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 限制的总带宽均匀分配到时间片,避免突发流量导致拥塞。
工作流程
#mermaid-svg-jyUGhOLj9Ck2CzDq{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jyUGhOLj9Ck2CzDq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jyUGhOLj9Ck2CzDq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jyUGhOLj9Ck2CzDq .error-icon{fill:#552222;}#mermaid-svg-jyUGhOLj9Ck2CzDq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jyUGhOLj9Ck2CzDq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jyUGhOLj9Ck2CzDq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jyUGhOLj9Ck2CzDq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jyUGhOLj9Ck2CzDq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jyUGhOLj9Ck2CzDq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jyUGhOLj9Ck2CzDq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jyUGhOLj9Ck2CzDq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jyUGhOLj9Ck2CzDq .marker.cross{stroke:#333333;}#mermaid-svg-jyUGhOLj9Ck2CzDq svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jyUGhOLj9Ck2CzDq p{margin:0;}#mermaid-svg-jyUGhOLj9Ck2CzDq .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-jyUGhOLj9Ck2CzDq .cluster-label text{fill:#333;}#mermaid-svg-jyUGhOLj9Ck2CzDq .cluster-label span{color:#333;}#mermaid-svg-jyUGhOLj9Ck2CzDq .cluster-label span p{background-color:transparent;}#mermaid-svg-jyUGhOLj9Ck2CzDq .label text,#mermaid-svg-jyUGhOLj9Ck2CzDq span{fill:#333;color:#333;}#mermaid-svg-jyUGhOLj9Ck2CzDq .node rect,#mermaid-svg-jyUGhOLj9Ck2CzDq .node circle,#mermaid-svg-jyUGhOLj9Ck2CzDq .node ellipse,#mermaid-svg-jyUGhOLj9Ck2CzDq .node polygon,#mermaid-svg-jyUGhOLj9Ck2CzDq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jyUGhOLj9Ck2CzDq .rough-node .label text,#mermaid-svg-jyUGhOLj9Ck2CzDq .node .label text,#mermaid-svg-jyUGhOLj9Ck2CzDq .image-shape .label,#mermaid-svg-jyUGhOLj9Ck2CzDq .icon-shape .label{text-anchor:middle;}#mermaid-svg-jyUGhOLj9Ck2CzDq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jyUGhOLj9Ck2CzDq .rough-node .label,#mermaid-svg-jyUGhOLj9Ck2CzDq .node .label,#mermaid-svg-jyUGhOLj9Ck2CzDq .image-shape .label,#mermaid-svg-jyUGhOLj9Ck2CzDq .icon-shape .label{text-align:center;}#mermaid-svg-jyUGhOLj9Ck2CzDq .node.clickable{cursor:pointer;}#mermaid-svg-jyUGhOLj9Ck2CzDq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jyUGhOLj9Ck2CzDq .arrowheadPath{fill:#333333;}#mermaid-svg-jyUGhOLj9Ck2CzDq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jyUGhOLj9Ck2CzDq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jyUGhOLj9Ck2CzDq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jyUGhOLj9Ck2CzDq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jyUGhOLj9Ck2CzDq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jyUGhOLj9Ck2CzDq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jyUGhOLj9Ck2CzDq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jyUGhOLj9Ck2CzDq .cluster text{fill:#333;}#mermaid-svg-jyUGhOLj9Ck2CzDq .cluster span{color:#333;}#mermaid-svg-jyUGhOLj9Ck2CzDq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jyUGhOLj9Ck2CzDq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jyUGhOLj9Ck2CzDq rect.text{fill:none;stroke-width:0;}#mermaid-svg-jyUGhOLj9Ck2CzDq .icon-shape,#mermaid-svg-jyUGhOLj9Ck2CzDq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jyUGhOLj9Ck2CzDq .icon-shape p,#mermaid-svg-jyUGhOLj9Ck2CzDq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jyUGhOLj9Ck2CzDq .icon-shape rect,#mermaid-svg-jyUGhOLj9Ck2CzDq .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jyUGhOLj9Ck2CzDq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jyUGhOLj9Ck2CzDq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jyUGhOLj9Ck2CzDq :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
BWE: 1000kbps
Pacer
每5ms发送 625 字节
网络
源码(Pacer 调度)
// 文件: 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。
JitterBufferBWEReceiverNetworkPacerSVC控制器编码器应用层JitterBufferBWEReceiverNetworkPacerSVC控制器编码器应用层#mermaid-svg-zvazmmoirSwEvfNq{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zvazmmoirSwEvfNq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zvazmmoirSwEvfNq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zvazmmoirSwEvfNq .error-icon{fill:#552222;}#mermaid-svg-zvazmmoirSwEvfNq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zvazmmoirSwEvfNq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zvazmmoirSwEvfNq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zvazmmoirSwEvfNq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zvazmmoirSwEvfNq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zvazmmoirSwEvfNq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zvazmmoirSwEvfNq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zvazmmoirSwEvfNq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zvazmmoirSwEvfNq .marker.cross{stroke:#333333;}#mermaid-svg-zvazmmoirSwEvfNq svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zvazmmoirSwEvfNq p{margin:0;}#mermaid-svg-zvazmmoirSwEvfNq .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-zvazmmoirSwEvfNq text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-zvazmmoirSwEvfNq .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-zvazmmoirSwEvfNq .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-zvazmmoirSwEvfNq .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-zvazmmoirSwEvfNq .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-zvazmmoirSwEvfNq #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-zvazmmoirSwEvfNq .sequenceNumber{fill:white;}#mermaid-svg-zvazmmoirSwEvfNq #sequencenumber{fill:#333;}#mermaid-svg-zvazmmoirSwEvfNq #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-zvazmmoirSwEvfNq .messageText{fill:#333;stroke:none;}#mermaid-svg-zvazmmoirSwEvfNq .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-zvazmmoirSwEvfNq .labelText,#mermaid-svg-zvazmmoirSwEvfNq .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-zvazmmoirSwEvfNq .loopText,#mermaid-svg-zvazmmoirSwEvfNq .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-zvazmmoirSwEvfNq .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-zvazmmoirSwEvfNq .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-zvazmmoirSwEvfNq .noteText,#mermaid-svg-zvazmmoirSwEvfNq .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-zvazmmoirSwEvfNq .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-zvazmmoirSwEvfNq .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-zvazmmoirSwEvfNq .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-zvazmmoirSwEvfNq .actorPopupMenu{position:absolute;}#mermaid-svg-zvazmmoirSwEvfNq .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-zvazmmoirSwEvfNq .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-zvazmmoirSwEvfNq .actor-man circle,#mermaid-svg-zvazmmoirSwEvfNq line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-zvazmmoirSwEvfNq :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}用户切换到4G,网络恶化输入 1080p 视频帧编码为 BL+EL1+EL2三层 RTP 包(1000kbps)均匀发送正常接收(丢包率2%)丢包率升至15%,RTT=250ms检测丢包 → 生成 NACKTransport 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 | 会议场景 | 优先保证基础画质清晰 |



