webrtc pacing 平滑发包模块
pacing是 WebRTC 的平滑发包Pacer模块核心作用是把编码器突发的大流量 “削峰填谷”按预估带宽匀速发送避免网络瞬时拥塞与丢包。下面从目录结构、核心类、原理、优先级、与 GCC 关系、关键配置几方面讲清楚。一、目录结构modules/pacing/ ├── bitrate_prober.h/cc # 带宽探测主动发尖刺测带宽 ├── interval_budget.h/cc # 漏桶预算/负债计算 ├── pacing_controller.h/cc # 核心发包节奏控制 ├── paced_sender.h # 对外接口旧 ├── task_queue_paced_sender.h/cc # 封装器任务队列驱动发包 ├── prioritized_packet_queue.h/cc # 优先级队列音频重传视频padding └── round_robin_packet_queue.h/cc # 同优先级轮询二、核心类与职责2.1 TaskQueuePacedSender对外入口所有发包先进这里。内部持有PacingController用任务队列驱动周期性发包默认 5ms 一次。接口EnqueuePackets()入队、SetPacingRate()设置目标码率、SetCongested()拥塞标记。2.2 PacingController核心实现漏桶Leaky Bucket 负债机制。维护media_debt_已发数据 “负债”随时间自动减少漏水。核心逻辑时间推进 → 减少负债有包要发 → 检查负债是否允许允许则发送增加负债否则排队等下一轮2.3 PrioritizedPacketQueue优先级调度音频最高 重传 视频 FEC Padding最低。同优先级按入队时间 FIFO多流同优先级轮询RR。2.4 BitRateProber带宽探测主动发短时间高速包尖刺探测可用带宽上限。探测结果给 GCC用于调整pacing_rate三、工作原理3.1 简单工作原理介绍编码器输出I 帧 / 大帧一次性产生几十甚至上百个 RTP 包突发。入队 Pacer所有包进入PrioritizedPacketQueue排队。GCC 给速率GCCGoogCc预估带宽 → 设置pacing_rate通常是预估带宽的 2–2.5 倍留余量。漏桶平滑PacingController按pacing_rate匀速发包把突发拉平。优先级插队音频 / 重传包优先发送保证实时性。队列积压处理队列过长时动态提速避免延迟累积3.2 例子以视频帧编码 → 最终网络发送为例编码输出视频编码器生成一帧大图像拆分出 20 个 RTP 包一次性批量输出流量突发。包入队调用TaskQueuePacedSender::EnqueuePackets()所有包进入PrioritizedPacketQueue标记为kNormalPriority。定时触发5ms 定时任务启动进入PacingController执行负债折旧。负债抵扣时间流逝抵扣部分历史负债负债低于上限允许发包。优先级取包从视频队列依次取包每发一个包就累加负债。触顶停止发送若干包后media_debt_达到max_debt_本轮发包终止。循环调度下一个 5ms 周期到来再次折旧负债、继续发送剩余包直到队列清空。空闲补包媒体包发送完毕链路空闲发送低优先级 Padding 包填充带宽。四、 与GCC 关系GCCmodules/congestion_controller负责带宽估计 拥塞决策输出target_bitrate。Pacermodules/pacing负责流量整形 平滑发送接收pacing_rate由target_bitrate放大而来。关系GCC 是 “大脑”Pacer 是 “手脚”—— 大脑决定发多快手脚按节奏平稳发。五优先级和关键配置与参数5.1 优先级音频包kHighPriority最高优先发送保证通话流畅。重传包kRetransmissionPriority丢包重传次高减少卡顿。视频包kNormalPriority普通视频数据。FEC 包kNormalPriority前向纠错和视频同优先级。Padding 包kLowPriority填充空包用于保活 / 探测最低。5.2 关键配置与参数默认发包间隔5ms周期模式新版支持kDynamic动态间隔基于负债。Pacing 速率放大系数pacing_rate target_bitrate × 2.5可在代码中调整。队列长度阈值超过阈值触发 “紧急发送”防止延迟过大。六、应用初探#include iostream #include queue #include vector #include cstdint #include algorithm // 1. 优先级枚举 (对齐 WebRTC 源码) enum class PacketPriority { kHighPriority, // 音频 最高 kRetransmission, // 重传 kNormalPriority, // 视频 / FEC kLowPriority // Padding 填充包 最低 }; // RTP 数据包结构体 struct RtpPacket { PacketPriority priority; size_t size_bytes; RtpPacket(PacketPriority p, size_t s) : priority(p), size_bytes(s) {} }; // 2. IntervalBudget 漏桶预算/负债计算类 // 复刻 WebRTC: modules/pacing/interval_budget.h class IntervalBudget { public: explicit IntervalBudget(int64_t initial_budget 0) : budget_(initial_budget) {} // 时间流逝增加可用预算 (负债折旧) void IncreaseBudget(int64_t delta_ms, uint32_t pacing_bps) { // bps - bytes/ms: bps / 8 / 1000 int64_t bytes_per_ms static_castint64_t(pacing_bps) / 8 / 1000; budget_ delta_ms * bytes_per_ms; } // 消耗预算 (发送包增加负债) void Consume(int64_t bytes) { budget_ - bytes; } int64_t budget() const { return budget_; } bool IsOverBudget() const { return budget_ 0; } private: int64_t budget_ 0; // 剩余预算 (负数 负债) }; // 3. 优先级包队列 class PrioritizedPacketQueue { public: void Enqueue(RtpPacket pkt) { switch (pkt.priority) { case PacketPriority::kHighPriority: high_q_.push(std::move(pkt)); break; case PacketPriority::kRetransmission: retrans_q_.push(std::move(pkt)); break; case PacketPriority::kNormalPriority: normal_q_.push(std::move(pkt)); break; case PacketPriority::kLowPriority: low_q_.push(std::move(pkt)); break; } } // 按优先级顺序取出一个包 bool GetNext(RtpPacket out_pkt) { if (!high_q_.empty()) { out_pkt std::move(high_q_.front()); high_q_.pop(); return true; } if (!retrans_q_.empty()) { out_pkt std::move(retrans_q_.front()); retrans_q_.pop(); return true; } if (!normal_q_.empty()) { out_pkt std::move(normal_q_.front()); normal_q_.pop(); return true; } if (!low_q_.empty()) { out_pkt std::move(low_q_.front()); low_q_.pop(); return true; } return false; } bool Empty() const { return high_q_.empty() retrans_q_.empty() normal_q_.empty() low_q_.empty(); } private: std::queueRtpPacket high_q_; std::queueRtpPacket retrans_q_; std::queueRtpPacket normal_q_; std::queueRtpPacket low_q_; }; // 4. PacingController 核心控制器 // 复刻 WebRTC PacingController 漏桶负债逻辑 class PacingController { public: PacingController() default; void SetPacingRate(uint32_t bps) { pacing_bps_ bps; // max_debt: 允许最大突发时长 10ms (WebRTC 默认值) max_burst_bytes_ static_castint64_t(bps) * 10 / 8 / 1000; } // 每轮调度时间推进 折旧负债 void AdvanceTime(int64_t delta_ms) { budget_.IncreaseBudget(delta_ms, pacing_bps_); // 负债不能超过最大突发限制 if (budget_.budget() -max_burst_bytes_) { budget_.Consume(budget_.budget() max_burst_bytes_); } } // 尝试发送一个包返回是否发送成功 bool TrySendPacket(const RtpPacket pkt) { // 预算不足(负债超限)禁止发送 if (budget_.budget() 0) { return false; } budget_.Consume(static_castint64_t(pkt.size_bytes)); return true; } int64_t CurrentDebt() const { return -budget_.budget(); } private: IntervalBudget budget_; uint32_t pacing_bps_ 0; int64_t max_burst_bytes_ 0; // 最大允许突发字节(对应max_debt) }; // 5. 外层 PacedSender (入口 定时调度) class TaskQueuePacedSender { public: TaskQueuePacedSender() default; void SetPacingRate(uint32_t bps) { controller_.SetPacingRate(bps); std::cout [Pacer] 设置发包速率: bps / 1000 kbps\n; } void EnqueuePacket(RtpPacket pkt) { queue_.Enqueue(std::move(pkt)); } // 模拟 5ms 定时调度 (WebRTC 默认调度周期) void RunPacingRound(int64_t interval_ms 5) { std::cout \n 调度周期 interval_ms ms ; // 1. 时间推进折旧负债 controller_.AdvanceTime(interval_ms); std::cout 当前负债: controller_.CurrentDebt() bytes\n; // 2. 循环取包、尝试发送 RtpPacket pkt(PacketPriority::kNormalPriority, 0); while (queue_.GetNext(pkt)) { if (controller_.TrySendPacket(pkt)) { std::cout 发送包 | 优先级: (int)pkt.priority | 大小: pkt.size_bytes bytes | 累计负债: controller_.CurrentDebt() bytes\n; } else { // 负债超限本轮停止发包包重新入队 queue_.Enqueue(std::move(pkt)); std::cout 负债超限本轮停止发包\n; break; } } } bool QueueEmpty() const { return queue_.Empty(); } private: PrioritizedPacketQueue queue_; PacingController controller_; }; // 6. 主函数完整流程演示 int main() { std::cout WebRTC Pacer 模拟演示 (漏桶优先级队列)\n\n; TaskQueuePacedSender pacer; // 1. 设置 Pacing 速率2000 kbps (对齐 GCC 输出的 pacing_rate) const uint32_t pacing_rate 2000 * 1000; pacer.SetPacingRate(pacing_rate); // 2. 模拟编码器突发生成一批包 (I帧突发流量) std::cout \n[入队] 模拟视频I帧突发批量加入20个视频包 2个音频包\n; // 先加入视频包(普通优先级) for (int i 0; i 20; i) { pacer.EnqueuePacket(RtpPacket(PacketPriority::kNormalPriority, 1200)); } // 后加入音频包(高优先级会插队优先发送) for (int i 0; i 2; i) { pacer.EnqueuePacket(RtpPacket(PacketPriority::kHighPriority, 200)); } // 3. 模拟多轮 5ms 定时调度直到队列清空 int round 0; while (!pacer.QueueEmpty()) { round; pacer.RunPacingRound(5); } std::cout \n 所有包发送完成 std::endl; return 0; }