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 给速率 :GCC(GoogCc)预估带宽 → 设置 pacing_rate(通常是预估带宽的 2--2.5 倍,留余量)。
  • 漏桶平滑PacingControllerpacing_rate 匀速发包,把突发拉平。
  • 优先级插队:音频 / 重传包优先发送,保证实时性。
  • 队列积压处理:队列过长时动态提速,避免延迟累积

3.2 例子:

视频帧编码 → 最终网络发送为例:

  1. 编码输出 视频编码器生成一帧大图像,拆分出 20 个 RTP 包,一次性批量输出(流量突发)。
  2. 包入队 调用 TaskQueuePacedSender::EnqueuePackets(),所有包进入 PrioritizedPacketQueue,标记为 kNormalPriority
  3. 定时触发 5ms 定时任务启动,进入 PacingController 执行负债折旧。
  4. 负债抵扣 时间流逝抵扣部分历史负债,负债低于上限,允许发包。
  5. 优先级取包 从视频队列依次取包,每发一个包就累加负债。
  6. 触顶停止 发送若干包后,media_debt_ 达到 max_debt_,本轮发包终止。
  7. 循环调度 下一个 5ms 周期到来,再次折旧负债、继续发送剩余包,直到队列清空。
  8. 空闲补包 媒体包发送完毕,链路空闲,发送低优先级 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;
}
相关推荐
换个昵称都难3 小时前
webrtc 音频混音介绍
音视频·webrtc
换个昵称都难19 小时前
webrtc QOS-RemoteBitrateEstimator接收端带宽估计(1)
webrtc
换个昵称都难20 小时前
webrtc QOS-RemoteBitrateEstimator接收端带宽估计-四个实例(2)
webrtc
都在酒里1 天前
【极致低延时】香橙派部署 MediaMTX 实现 WebRTC 推流,延时仅 500-800ms,比局域网 ffmpeg 拉流快近 10 倍!(附踩坑全记录)
linux·arm开发·ffmpeg·webrtc·orangepi·嵌入式软件
换个昵称都难1 天前
WebRTC QoS 实战:从原理到弱网优化
开发语言·php·webrtc
小哈机器人1 天前
Phantom Bridge:一个基于WebRTC的ROS2远程可视化与遥操作工具
机器人·webrtc·数据可视化
换个昵称都难2 天前
webrtc 视频传输Flexfec模块
音视频·webrtc
AndyHuang19762 天前
实战记录:如何在 Release 模式下成功调试 WebRTC 源码(解决断点失效问题)
webrtc
换个昵称都难2 天前
webrtc视频Ulpfec介绍
音视频·webrtc