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 给速率 :GCC(
GoogCc)预估带宽 → 设置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 关系
- GCC(modules/congestion_controller) :负责带宽估计 + 拥塞决策 ,输出
target_bitrate。 - Pacer(modules/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(可在代码中调整)。 - 队列长度阈值:超过阈值触发 "紧急发送",防止延迟过大。
六、应用初探
cpp
#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_cast<int64_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::queue<RtpPacket> high_q_;
std::queue<RtpPacket> retrans_q_;
std::queue<RtpPacket> normal_q_;
std::queue<RtpPacket> 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_cast<int64_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_cast<int64_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;
}