RtpSender 与发送路径 · 打包、发出、留底

发送一个 RTP 包不只是「发出去」——还要分配序号、经 pacing 平滑、并把包留一份底以备重传。这一章读 modules/rtp_rtcp/source 的 RtpSenderEgress 与 RtpPacketHistory:发送路径的分工,以及「发过的包留一份」为什么是 NACK 重传和带宽探测的基础。

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

RtpSender 与发送路径 · 打包、发出、留底

模块中枢把媒体交给发送侧。发送一个 RTP 包看似简单,实则要做好几件事:分配序号、加头扩展、经 pacing 平滑发出、还要留一份底以备重传。这一章拆开发送路径。

分工:打包 vs 发出

发送侧分两段(modules/rtp_rtcp/source/):

为什么拆两段?因为中间还夹着一个关键环节——pacing(平滑发包)(第 5 部分)。RTPSender 打好的包不会立即发,而是交给 pacer 按拥塞控制算出的节奏慢慢吐出,再由 RtpSenderEgress 送走。打包和发出解耦,pacing 才好插在中间。

留底:RtpPacketHistory

RtpSenderEgress 有个关键的合作者——RtpPacketHistory(modules/rtp_rtcp/source/rtp_packet_history.h)。看 rtp_sender_egress.h 的成员:

// modules/rtp_rtcp/source/rtp_sender_egress.h(节选)
void SendPacket(std::unique_ptr<RtpPacketToSend> packet, ...);
RtpPacketHistory* const packet_history_ RTC_GUARDED_BY(worker_queue_);

发出去的包,要在 RtpPacketHistory 里留一份底——因为它随时可能被要求重发。 UDP 会丢包,对端一旦发现某个序号缺了,会发 NACK 请求重传。可「重传」的前提是你还留着那个包——发完就扔,就没法重传了。所以 RtpPacketHistory 保留最近发送的一批包(有容量 / 时长上限,不可能全留),NACK 来了就从历史里取出对应包重发。

留底还有第二个用途:带宽探测。拥塞控制要试探「现在能不能发更快」,可以从历史里取几个包重发当探测(RTX 探测,g3doc/rtp.md)——反正重发旧包既能探测带宽,万一那包真丢了还兼做冗余,一举两得。所以 RtpPacketHistory 同时服务重传和探测两件事。

序号:发送序号 vs 原始序号

一个细节值得点明(上一章提过):RtpPacketToSend 的传输序号可能和它的原始 SequenceNumber() 不同。为什么?因为重传常走 RTX 独立流,RTX 有自己的序号空间。所以一个包在原始流里是序号 100,重传时在 RTX 流里可能是序号 5——RtpSenderEgress 要正确管理这两套序号,收端才能对上。这种细节,正是实时传输工程的琐碎与严谨所在。

发送路径全景

图表加载中…

小结与下一章

发送路径分两段:RTPSender 打包(分配序号 / 时间戳 / 头扩展)、RtpSenderEgress 发出(SendPacket,经 SRTP),中间夹着 pacing 平滑(故解耦)。关键是 RtpPacketHistory 留底——保留最近发送的包,服务两件事:NACK 重传(丢了能重发)和带宽探测(重发旧包当探测)。RTX 重传用独立序号空间,故传输序号可能异于原始序号。

发送这条线讲完,该看反馈了——对端怎么通过 RTCP 告诉你「收到了多少、丢了多少、网络怎么样」。下一章进入 RTCP。👉 RTCP 报告:SR 与 RR。

参考资料

  • [1] libwebrtc 源码.modules/rtp_rtcp/source/rtp_sender_egress.h、rtp_sender.h、rtp_packet_history.h。
  • [2] libwebrtc 源码.g3doc/rtp.md(RTX 探测复用历史包)。
  • [3] IETF RFC 3550「RTP」、RFC 4588「RTP Retransmission (RTX)」。

本页目录