# NACK 与 RTX 重传 · 丢包了就重发

> UDP 会丢包，WebRTC 用 NACK+重传补救：接收端发现序号缺失就发 NACK，发送端从历史重发。这一章讲清 NackTracker 怎么决定请求哪些包、RTX 为什么用独立的流承载重传，以及重传的适用边界——RTT 太大时重传就来不及了。

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

# NACK 与 RTX 重传 · 丢包了就重发

[UDP 会丢包](https://daiw.org/manual/webrtc/what-is-webrtc)，而视频对丢包尤其敏感（丢一个包可能[让好几帧解不出](https://daiw.org/manual/webrtc/source-map)）。WebRTC 对抗丢包的第一招，是最直接的：**发现丢了就重发**。这套机制叫 **NACK + 重传**，重传常走一条叫 **RTX** 的独立流。这一章拆开它。

## NACK：接收端喊「重发」

[序号](https://daiw.org/manual/webrtc/rtp-packet)让接收端能**发现丢包**——收到序号 100、102，那 101 就是丢了。接收端于是发一个 **[NACK](https://daiw.org/manual/webrtc/rtcp-feedback)**（Negative ACKnowledgement，否定确认）告诉发送端「101 我没收到，重发」。发送端从 [`RtpPacketHistory` 历史](https://daiw.org/manual/webrtc/rtp-sender)里取出 101 号包，重发。

决定「请求重传哪些包」的，是接收端的 **`NackTracker`**（`modules/rtp_rtcp/source/nack_tracker.cc` 一类）：

<Callout type="info">
  **`NackTracker` 不是「一发现缺号就立刻狂发 NACK」，它有策略。** 要点：

  - **判断是丢包还是乱序**：序号 101 没到，可能是**真丢了**，也可能只是**乱序**（101 比 102 晚到）。`NackTracker` 会**稍等**——给乱序一点到达时间，别把「晚到」误当「丢失」而无谓重传。
  - **控制重传请求的节奏**：同一个包别反复请求太频繁（重传本身也占带宽），也别请求太久（过了[播放时刻](https://daiw.org/manual/webrtc/source-map)再来就没用了）。
  - **考虑 [RTT](https://daiw.org/manual/webrtc/connection-and-checks)**：重传一来一回要一个 RTT，得判断「重传回来还赶不赶得上播放」。

  这些策略，是「重传有用」和「重传别添乱」之间的精细平衡——重传不是免费的，发多了反而加重拥塞。
</Callout>

## RTX：重传走独立的流

重传的包怎么发？最朴素的办法是**原样重发**（同序号同 SSRC）。但 WebRTC 更常用 **RTX**（RTP Retransmission,RFC 4588）——**让重传走一条独立的 RTP 流**：

<Callout type="info">
  **RTX 给重传包配了独立的 [SSRC、payload type 和序号空间](https://daiw.org/manual/webrtc/rtp-packet)——重传不再冒充原包，而是「另开一路」。** 一个原始流（SSRC=A），配一个 RTX 流（SSRC=B）专发它的重传。重传 101 号包时，在 RTX 流里它有自己的新序号（比如 RTX 序号 5），包体里再带上「我其实是原始流的 101 号」。为什么这么绕？

  - **不打乱原始流的序号连续性**：原始流的序号保持严格 +1，[丢包检测](https://daiw.org/manual/webrtc/rtp-packet)、[抖动计算](https://daiw.org/manual/webrtc/source-map)才不被重传包干扰。
  - **重传流可独立统计**：能清楚知道「重传占了多少带宽」，给[拥塞控制](https://daiw.org/manual/webrtc/source-map)参考。
  - **兼做[带宽探测](https://daiw.org/manual/webrtc/payload-types)**：`g3doc/rtp.md` 说的 [RTX 探测](https://daiw.org/manual/webrtc/payload-types)就复用这条流——重发个旧包既探带宽、又万一有用。

  代价是要[多协商一个 payload type](https://daiw.org/manual/webrtc/payload-types) 和 SSRC。这是[实时传输](https://daiw.org/manual/webrtc/rtp-packet)里「多一层间接、换取干净与灵活」的又一例。RTX 是否启用由 [SDP 协商](https://daiw.org/manual/webrtc/media-session)（`a=rtpmap` 里的 `rtx`）。
</Callout>

## 重传的边界：RTT 太大就来不及

重传不是万能的，它有个硬边界：

<Callout type="warn">
  **重传要一来一回一个 [RTT](https://daiw.org/manual/webrtc/connection-and-checks)，如果 RTT 大于「这段媒体还能等多久」，重传回来时媒体早该播了，纯属浪费。** 举例：视频缓冲区只能容忍 100ms 延迟，而 RTT 是 300ms——你发 NACK、对端重传、包回来，已经 300ms 过去，这帧的播放时刻早过了，重传的包只能丢弃。所以**重传适合低 RTT 场景**（同城、好网络）；高 RTT（跨国、卫星）下，重传越来越不划算，得靠**不需要往返的** [FEC 前向纠错](https://daiw.org/manual/webrtc/fec)来补。

  这引出实时传输一个核心权衡：**重传（NACK/RTX）省带宽但要往返、FEC 免往返但费带宽**。WebRTC 常**两者结合**——低 RTT 主要靠重传，高 RTT / 高丢包补以 FEC。下一章就讲 FEC。
</Callout>

## 小结与下一章

**NACK + 重传**是对抗[丢包](https://daiw.org/manual/webrtc/what-is-webrtc)的第一招：接收端靠[序号](https://daiw.org/manual/webrtc/rtp-packet)发现缺包、发 [NACK](https://daiw.org/manual/webrtc/rtcp-feedback)，发送端从[历史](https://daiw.org/manual/webrtc/rtp-sender)重发。`NackTracker` 有策略（区分丢包 / 乱序、控节奏、看 [RTT](https://daiw.org/manual/webrtc/connection-and-checks)）。重传常走 **RTX 独立流**（独立 SSRC/PT/序号），不打乱原始流、可独立统计、兼做[探测](https://daiw.org/manual/webrtc/payload-types)。重传的硬边界是 **RTT**——太大就来不及，得靠 FEC。

那个「不需要往返」的补丁，就是**前向纠错 FEC**。下一章看它怎么用冗余包在**丢包发生的当下**就恢复。👉 [FEC 前向纠错](https://daiw.org/manual/webrtc/fec)。

## 参考资料

- [1] libwebrtc 源码.`modules/rtp_rtcp/source/nack_tracker.h`、`rtp_rtcp/source/rtp_packet_history.h`（重传取包）。
- [2] libwebrtc 源码.`g3doc/rtp.md`（RTX 流与探测）。
- [3] IETF RFC 4585「NACK」、RFC 4588「RTP Retransmission （RTX）」。
