Pacing 平滑发包 · 别一股脑猛发
编码器一帧可能出一大批包,若一股脑发出去会造成瞬时拥塞、干扰带宽估计。Pacing 把包按目标码率的节奏平滑吐出。这一章读 pacing/pacing_controller.h 的 EnqueuePacket/ProcessPackets,讲清 pacer 的队列、发包节奏,以及它为什么是拥塞控制不可或缺的一环。
Pacing 平滑发包 · 别一股脑猛发
拥塞控制估出了目标码率,但这只是「平均该发多快」。还有个问题:怎么把包吐出去? 如果编码器出一帧就把它的一大批包一股脑全发出,哪怕平均码率没超,瞬时也会形成一个突发(burst),造成短暂拥塞、还干扰带宽估计。解决它的,是 pacing(平滑发包)——modules/pacing/ 的 PacingController。
问题:突发有害
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 是其执行端。