Pacing 平滑发包 · 别一股脑猛发

编码器一帧可能出一大批包,若一股脑发出去会造成瞬时拥塞、干扰带宽估计。Pacing 把包按目标码率的节奏平滑吐出。这一章读 pacing/pacing_controller.h 的 EnqueuePacket/ProcessPackets,讲清 pacer 的队列、发包节奏,以及它为什么是拥塞控制不可或缺的一环。

作者 David更新于 第 48 篇(共 112 篇)

Pacing 平滑发包 · 别一股脑猛发

拥塞控制估出了目标码率,但这只是「平均该发多快」。还有个问题:怎么把包吐出去? 如果编码器出一帧就把它的一大批包一股脑全发出,哪怕平均码率没超,瞬时也会形成一个突发(burst),造成短暂拥塞、还干扰带宽估计。解决它的,是 pacing(平滑发包)——modules/pacing/ 的 PacingController。

问题:突发有害

一帧视频可能被拆成几十个 RTP 包,如果编码器一出帧就把它们全塞进网络,就是一个瞬时突发。 突发的害处:

  • 瞬时超过链路容量 —— 哪怕平均码率合规,这一瞬间的速率远超链路,包在瓶颈处排队,增加延迟、甚至丢包。
  • 干扰带宽估计 —— 突发让包的到达时间乱成一团,延迟趋势、探测测量都被污染,估不准。

想象水管:你要平稳的水流,而不是一桶一桶地猛倒。pacing 就是把“一桶水”变成“平稳的细流”——把一帧的包,按目标码率的节奏,均匀地在时间上摊开发出。

PacingController:排队 + 按节奏吐

PacingController(modules/pacing/pacing_controller.h)的接口就两个核心动作:

// modules/pacing/pacing_controller.h(节选)
void EnqueuePacket(std::unique_ptr<RtpPacketToSend> packet);  // 包入队
void ProcessPackets();                                         // 到点就吐一批出去

pacer 是一个“按节奏放行”的队列:打包好的包先 EnqueuePacket 进队,不立即发;ProcessPackets 被周期性调用,每次按当前的 pacing 速率放行恰当数量的包出去。 于是无论编码器怎么“一阵一阵”地出帧,发到网络上的包流都是平滑均匀的。pacing 速率通常略高于目标码率(留点余量让队列不至于无限堆积),由拥塞控制设定。

pacer 还兼顾优先级:重传包、音频包(对延迟敏感)可以优先放行,普通视频包次之。它也是探测包的发射器——ProbeController 要发探测,就是通过 pacer 按探测速率吐出一串包。所以 pacer 站在发送路径的咽喉:所有出网的包都从它这儿按节奏走,它是拥塞控制把“决策”变成“实际发包行为”的执行点。

现代实现是 TaskQueuePacedSender(pacing/task_queue_paced_sender.h)——把 pacer 跑在一个 TaskQueue 上,用任务调度驱动 ProcessPackets 的周期调用,契合 WebRTC 的线程模型。

在发送路径的位置

把 pacer 放进发送路径看,它夹在「打包」和「发出」之间:

图表加载中…

编码器的突发进 pacer 的队列,出来的是平稳的包流——pacer 把“内容生产的节奏”和“网络发送的节奏”解耦了。这也回答了发送路径那章为什么「打包」和「发出」要分两段:中间正是留给 pacer。

小结与下一章

Pacing 把突发的发包变平滑:编码器一帧的一批包若一股脑发出会造成瞬时拥塞、干扰估计。PacingController(EnqueuePacket 入队、ProcessPackets 按节奏放行)把包均匀摊开发出,速率由拥塞控制设定(略高于目标码率),兼顾优先级、兼发探测。现代用 TaskQueuePacedSender(跑在 TaskQueue 上)。它站在发送咽喉,是拥塞控制决策的执行点。

pacing 控制「按节奏发」,但还有一个更硬的约束——在途未确认的数据量不能太多。这是拥塞窗口的活。下一章。👉 拥塞窗口。

参考资料

  • [1] libwebrtc 源码.modules/pacing/pacing_controller.h、task_queue_paced_sender.h(EnqueuePacket / ProcessPackets)。
  • [2] libwebrtc 源码.modules/pacing/(pacer 与探测发射)。
  • [3] 关联拥塞控制总览——pacer 是其执行端。

本页目录