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

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

- 作者：David（道雾轩）
- 专栏：音视频与 WebRTC 详解（https://daiw.org/manual/webrtc.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/webrtc/rtp-sender
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

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

[模块中枢](https://daiw.org/manual/webrtc/rtp-rtcp-module)把媒体交给发送侧。发送一个 [RTP 包](https://daiw.org/manual/webrtc/rtp-packet)看似简单，实则要做好几件事：分配[序号](https://daiw.org/manual/webrtc/rtp-packet)、加[头扩展](https://daiw.org/manual/webrtc/rtp-header-extensions)、经 pacing [平滑发出](https://daiw.org/manual/webrtc/source-map)、还要**留一份底**以备[重传](https://daiw.org/manual/webrtc/nack-rtx)。这一章拆开发送路径。

## 分工：打包 vs 发出

发送侧分两段（`modules/rtp_rtcp/source/`）：

- **`RTPSender`** —— **打包**：把媒体数据组装成 [RTP 包](https://daiw.org/manual/webrtc/rtp-packet)，分配 [SSRC、序号、时间戳](https://daiw.org/manual/webrtc/rtp-packet)、加[头扩展](https://daiw.org/manual/webrtc/rtp-header-extensions)。产出 [`RtpPacketToSend`](https://daiw.org/manual/webrtc/rtp-packet)。
- **`RtpSenderEgress`** —— **发出**：把打好的包真正送上网络（经 [SRTP 加密](https://daiw.org/manual/webrtc/srtp-encryption)）。`rtp_sender_egress.h` 里的 `SendPacket` → `SendPacketToNetwork`。

为什么拆两段？因为中间还夹着一个关键环节——**pacing（平滑发包）**（[第 5 部分](https://daiw.org/manual/webrtc/source-map)）。`RTPSender` 打好的包不会立即发，而是交给 [pacer](https://daiw.org/manual/webrtc/source-map) **按拥塞控制算出的节奏**慢慢吐出，再由 `RtpSenderEgress` 送走。打包和发出解耦，pacing 才好插在中间。

## 留底：RtpPacketHistory

`RtpSenderEgress` 有个关键的合作者——**`RtpPacketHistory`**（`modules/rtp_rtcp/source/rtp_packet_history.h`）。看 `rtp_sender_egress.h` 的成员：

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

<Callout type="info">
  **发出去的包，要在 `RtpPacketHistory` 里留一份底——因为它随时可能被要求重发。** [UDP 会丢包](https://daiw.org/manual/webrtc/what-is-webrtc)，对端一旦发现某个[序号](https://daiw.org/manual/webrtc/rtp-packet)缺了，会发 [NACK](https://daiw.org/manual/webrtc/nack-rtx) 请求重传。可「重传」的前提是**你还留着那个包**——发完就扔，就没法重传了。所以 `RtpPacketHistory` 保留**最近发送的一批包**（有容量 / 时长上限，不可能全留），NACK 来了就从历史里取出对应包[重发](https://daiw.org/manual/webrtc/nack-rtx)。

  留底还有第二个用途：**[带宽探测](https://daiw.org/manual/webrtc/payload-types)**。[拥塞控制](https://daiw.org/manual/webrtc/source-map)要试探「现在能不能发更快」，可以从历史里取几个包**重发当探测**（[RTX 探测](https://daiw.org/manual/webrtc/payload-types)，`g3doc/rtp.md`）——反正重发旧包既能探测带宽，万一那包真丢了还兼做冗余，一举两得。所以 `RtpPacketHistory` 同时服务**重传**和**探测**两件事。
</Callout>

## 序号：发送序号 vs 原始序号

一个细节值得点明（[上一章](https://daiw.org/manual/webrtc/rtp-packet)提过）：`RtpPacketToSend` 的**传输序号**可能和它的原始 `SequenceNumber()` 不同。为什么？因为[重传](https://daiw.org/manual/webrtc/nack-rtx)常走 **[RTX](https://daiw.org/manual/webrtc/nack-rtx)** 独立流，RTX 有自己的[序号空间](https://daiw.org/manual/webrtc/nack-rtx)。所以一个包在原始流里是序号 100，重传时在 RTX 流里可能是序号 5——`RtpSenderEgress` 要正确管理这两套序号，收端才能对上。这种细节，正是[实时传输工程](https://daiw.org/manual/webrtc/rtp-packet)的琐碎与严谨所在。

## 发送路径全景

```mermaid
flowchart TD
    ENC["编码媒体"] --> PACK["RTPSender:打包<br/>(分配序号 / 时间戳 / 头扩展)"]
    PACK --> PACE["Pacer:按拥塞控制节奏平滑(第 5 部分)"]
    PACE --> EG["RtpSenderEgress:SendPacket"]
    EG -->|"留一份底"| HIST["RtpPacketHistory<br/>(供重传 + 探测)"]
    EG --> SRTP["SRTP 加密 → ICE 发出"]
    NACK["对端 NACK 请求重传"] -->|"从历史取包"| HIST
    HIST -->|"重发"| EG
```

## 小结与下一章

发送路径分两段：**`RTPSender` 打包**（分配[序号 / 时间戳 / 头扩展](https://daiw.org/manual/webrtc/rtp-packet)）、**`RtpSenderEgress` 发出**（`SendPacket`，经 [SRTP](https://daiw.org/manual/webrtc/srtp-encryption)），中间夹着 [pacing 平滑](https://daiw.org/manual/webrtc/source-map)（故解耦）。关键是 **`RtpPacketHistory` 留底**——保留最近发送的包，服务两件事：[NACK 重传](https://daiw.org/manual/webrtc/nack-rtx)（丢了能重发）和[带宽探测](https://daiw.org/manual/webrtc/payload-types)（重发旧包当探测）。[RTX](https://daiw.org/manual/webrtc/nack-rtx) 重传用独立序号空间，故传输序号可能异于原始序号。

发送这条线讲完，该看**反馈**了——对端怎么通过 RTCP 告诉你「收到了多少、丢了多少、网络怎么样」。下一章进入 RTCP。👉 [RTCP 报告：SR 与 RR](https://daiw.org/manual/webrtc/rtcp-reports)。

## 参考资料

- [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）」。
