在 mediasoup 中,NACK(Negative Acknowledgement,否定确认)对丢包恢复至关重要,核心原因在于它是当前 mediasoup 实现中唯一有效的主动丢包重传机制。由于 mediasoup 尚未实现前向纠错(FEC),当网络发生丢包时,接收端无法通过冗余数据自行恢复,必须依赖 NACK 机制请求发送端重传丢失的数据包,这是保障音视频流媒体传输质量的关键防线。
其重要性具体体现在以下两个层面:
1. 架构层面的核心地位
mediasoup 的 NACK 机制实现了与 WebRTC 标准的对接,构成了其丢包恢复能力的基石。发送端(RtpStreamSend)使用 RtpRetransmissionBuffer 缓存已发送的 RTP 包,接收端(RtpStreamRecv)通过 NackGenerator 检测丢包并生成 NACK 请求,触发发送端从缓存中取出对应包进行重传(通过 RTX 或原始 SSRC)。整个处理流程清晰,是保障数据可靠性的核心链路。
2. 机制设计的必要性
NACK 的触发与执行机制确保了恢复的及时性和有效性。
- 发送端缓存管理:发送端缓存受最大数量( 2500个包)和最大延迟(视频 2000ms,音频 1000ms)限制,确保有足够的数据可供重传,同时避免无限缓存。
- 接收端丢包检测与请求:NackGenerator 通过比对 RTP 序列号来识别丢包,并将丢失的包信息加入 nackList。触发重传请求的条件包括:
- 序列号触发:当收到序列号大于某个丢失包预设的 sendAtSeq 时,会触发对该丢失包的首次重传请求。
- 定时器触发:如果一个丢失包的重传请求发出后,超过一个 RTT(Round-Trip Time)时间仍未收到该包,则会再次发起请求。 请求本身也有延迟(默认 10ms)和最大重试次数限制,以避免无效的网络泛洪。
以下代码片段展示了 NackGenerator 中获取一批待重传包序列号的核心逻辑,体现了上述触发机制:
std::vector<uint16_t> NackGenerator::GetNackBatch(NackFilter filter)
{
const uint64_t nowMs = DepLibUV::GetTimeMs();
std::vector<uint16_t> nackBatch;
auto it = this->nackList.begin();
while (it != this->nackList.end()) {
NackInfo& nackInfo = it->second;
const uint16_t seq = nackInfo.seq;
// 条件1: 检查是否满足最小发送延迟(如10ms)
if (this->sendNackDelayMs > 0 && nowMs – nackInfo.createdAtMs < this->sendNackDelayMs) {
++it;
continue;
}
// 条件2: 序列号触发(首次发送)
if (filter == NackFilter::SEQ && nackInfo.sentAtMs == 0
&& (nackInfo.sendAtSeq == this->lastSeq ||
SeqManager<uint16_t>::IsSeqHigherThan(this->lastSeq, nackInfo.sendAtSeq))) {
nackBatch.emplace_back(seq);
nackInfo.retries++;
nackInfo.sentAtMs = nowMs;
// 达到最大重试次数则移除
if (nackInfo.retries >= MaxNackRetries) {
it = this->nackList.erase(it);
}
else {
++it;
}
continue;
}
// 条件3: 定时器触发(非首次发送,距离上次请求超过一个RTT)
if (filter == NackFilter::TIME && (nackInfo.sentAtMs == 0 ||
nowMs – nackInfo.sentAtMs >= (this->rtt > 0u ? this->rtt : DefaultRtt))) {
nackBatch.emplace_back(seq);
nackInfo.retries++;
nackInfo.sentAtMs = nowMs;
if (nackInfo.retries >= MaxNackRetries) {
it = this->nackList.erase(it);
}
else {
++it;
}
continue;
}
++it;
}
return nackBatch;
}
3. 与 WebRTC 实现的对比
与 WebRTC 的实现相比,mediasoup 的 NACK 机制进行了一定简化,这更凸显了其在当前架构下的不可或缺性:
| 触发序列号计算 | 直接将丢失包序列号设为触发点 (sendAtSeq = seq)。 | 基于乱序直方图统计,计算一个概率值(如50%)对应的偏移量作为触发点。 | 策略更为激进,倾向于立即请求重传,在无FEC的情况下有助于更快开始恢复流程。 |
| 恢复包列表作用 | recoveredList 仅存储 RTX 重传包,且逻辑上其必要性存疑。 | recovered_list_ 用于存储通过 FEC 或 RTX 恢复的包,这些包会影响乱序统计。 | 由于无FEC,此列表功能弱化,侧面说明丢包恢复几乎完全依赖 NACK+RTX。 |
| 发送端缓存清理 | 基于最大数量(` | ||
| 2500)和固定最大延迟(视频 | |||
| 2000ms/音频 | |||
| 1000ms`)。 | 基于数量、动态时长(max(1000ms, 3*RTT))及更严格的“终极”限制。 | mediasoup 策略更简单直接,在缺乏其他恢复手段时,确保合理时间窗口内的包可被重传。 |
综上所述,NACK 是 mediasoup 应对网络丢包的核心且主要的恢复机制。其简洁有效的设计,在缺乏 FEC 等辅助手段的情况下,承担了保障实时音视频数据传输可靠性的全部重担,因此对于 mediasoup 的丢包恢复能力至关重要。
参考来源
- 深入浅出mediasoup—NACK





