mediasoup RTT 使用场景
背景知识
往返时延(round-trip time,RTT) 是网络请求从起点到目的地然后再回到起点所花费的时长(不包括接收端的处理时间)。RTT是分析网络性能的一个重要指标,我们通常使用RTT来诊断网络连接的速度,可靠性以及拥塞程度。
webrtc 中目前有两种方式计算RTT,具体的详细内容参考WebRTC研究:统计参数之往返时延
本文基于mediasoup 代码分析RTT在webrtc中具体的使用场景
使用场景
发送端RTP包重传次数控制
通过Sender Report(SR)与Receiver Report(RR)携带的信息,计算得出RTT。
RTT 用在控制发送端RTP包的重传次数,当 mediasoup(RtpStreamSend)发送端收到NACK 包,如果该序列号是第一次重传,会立马重传RTP包;但如果不是第一次重传,只有在当前时间减去上次重传的发送时间大于RTT的时候,才会再次重传RTP包,避免过多的重传流量。
具体代码
void RtpStreamSend::FillRetransmissionContainer(uint16_t seq, uint16_t bitmask)
{
MS_TRACE();
// Ensure the container's first element is 0.
RetransmissionContainer[0] = nullptr;
// If NACK is not supported, exit.
if (!this->params.useNack)
{
MS_WARN_TAG(rtx, "NACK not supported");
return;
}
// Look for each requested packet.
uint64_t nowMs = DepLibUV::GetTimeMs();
uint16_t rtt = (this->rtt != 0u ? this->rtt : DefaultRtt);
uint16_t currentSeq = seq;
bool requested{ true };
size_t containerIdx{ 0 };
// Variables for debugging.
uint16_t origBitmask = bitmask;
uint16_t sentBitmask{ 0b0000000000000000 };
bool isFirstPacket{ true };
bool firstPacketSent{ false };
uint8_t bitmaskCounter{ 0 };
bool tooOldPacketFound{ false };
while (requested || bitmask != 0)
{
bool sent = false;
if (requested)
{
auto* storageItem = this->buffer[currentSeq];
RTC::RtpPacket* packet{ nullptr };
uint32_t diffMs;
// Calculate the elapsed time between the max timestampt seen and the
// requested packet's timestampt (in ms).
if (storageItem)
{
packet = storageItem->packet;
uint32_t diffTs = this->maxPacketTs - packet->GetTimestamp();
diffMs = diffTs * 1000 / this->params.clockRate;
}
// Packet not found.
if (!storageItem)
{
// Do nothing.
}
// Don't resend the packet if older than MaxRetransmissionDelay ms.
else if (diffMs > MaxRetransmissionDelay)
{
if (!tooOldPacketFound)
{
MS_WARN_TAG(
rtx,
"ignoring retransmission for too old packet "
"[seq:%" PRIu16 ", max age:%" PRIu32 "ms, packet age:%" PRIu32 "ms]",
packet->GetSequenceNumber(),
MaxRetransmissionDelay,
diffMs);
tooOldPacketFound = true;
}
}
// Don't resent the packet if it was resent in the last RTT ms.
// clang-format off
else if (
storageItem->resentAtMs != 0u &&
nowMs - storageItem->resentAtMs <= static_cast<uint64_t>(rtt)
)
// clang-format on
{
MS_DEBUG_TAG(
rtx,
"ignoring retransmission for a packet already resent in the last RTT ms "
"[seq:%" PRIu16 ", rtt:%" PRIu32 "]",
packet->GetSequenceNumber(),
rtt);
}
// Stored packet is valid for retransmission. Resend it.
else
{
// If we use RTX and the packet has not yet been resent, encode it now.
if (HasRtx())
{
// Increment RTX seq.
++this->rtxSeq;
if (!storageItem->rtxEncoded)
{
packet->RtxEncode(this->params.rtxPayloadType, this->params.rtxSsrc, this->rtxSeq);
storageItem->rtxEncoded = true;
}
else
{
packet->SetSequenceNumber(this->rtxSeq);
}
}
// Save when this packet was resent.
storageItem->resentAtMs = nowMs;
// Increase the number of times this packet was sent.
storageItem->sentTimes++;
// Store the storage item in the container and then increment its index.
RetransmissionContainer[containerIdx++] = storageItem;
sent = true;
if (isFirstPacket)
firstPacketSent = true;
}
}
requested = (bitmask & 1) != 0;
bitmask >>= 1;
++currentSeq;
if (!isFirstPacket)
{
sentBitmask |= (sent ? 1 : 0) << bitmaskCounter;
++bitmaskCounter;
}
else
{
isFirstPacket = false;
}
}
// If not all the requested packets was sent, log it.
if (!firstPacketSent || origBitmask != sentBitmask)
{
MS_WARN_DEV(
"could not resend all packets [seq:%" PRIu16
", first:%s, "
"bitmask:" MS_UINT16_TO_BINARY_PATTERN ", sent bitmask:" MS_UINT16_TO_BINARY_PATTERN "]",
seq,
firstPacketSent ? "yes" : "no",
MS_UINT16_TO_BINARY(origBitmask),
MS_UINT16_TO_BINARY(sentBitmask));
}
else
{
MS_DEBUG_DEV(
"all packets resent [seq:%" PRIu16 ", bitmask:" MS_UINT16_TO_BINARY_PATTERN "]",
seq,
MS_UINT16_TO_BINARY(origBitmask));
}
// Set the next container element to null.
RetransmissionContainer[containerIdx] = nullptr;
}
接收端NACK发送次数控制
通过RTCP Extended ReportsRTCP(XR)携带的信息(DLRR)计算得出RTT。
RTT 用在控制接收端NACK发送次数控制。当 mediasoup(RtpStreamRecv)接收端收到RTP数据包,会去检测是否有包丢失,如果有包丢失,第一次重传会立即进行,并且设置了一个40毫秒的定时器,定期检测重传队列,如果当前时间减去上次重传时间超过RTT时,会再重新进行发送NACK,但最大重传次数是10次,超过10次会取消该序列号的重传,避免过多的重传流量。
具体代码
std::vector<uint16_t> NackGenerator::GetNackBatch(NackFilter filter)
{
MS_TRACE();
uint64_t nowMs = DepLibUV::GetTimeMs();
std::vector<uint16_t> nackBatch;
auto it = this->nackList.begin();
while (it != this->nackList.end())
{
NackInfo& nackInfo = it->second;
uint16_t seq = nackInfo.seq;
// clang-format off
if (
filter == NackFilter::SEQ &&
nackInfo.sentAtMs == 0 &&
(
nackInfo.sendAtSeq == this->lastSeq ||
SeqManager<uint16_t>::IsSeqHigherThan(this->lastSeq, nackInfo.sendAtSeq)
)
)
// clang-format on
{
nackBatch.emplace_back(seq);
nackInfo.retries++;
nackInfo.sentAtMs = nowMs;
if (nackInfo.retries >= MaxNackRetries)
{
MS_WARN_TAG(
rtx,
"sequence number removed from the NACK list due to max retries [filter:seq, seq:%" PRIu16
"]",
seq);
it = this->nackList.erase(it);
}
else
{
++it;
}
continue;
}
if (filter == NackFilter::TIME && nowMs - nackInfo.sentAtMs >= this->rtt)
{
nackBatch.emplace_back(seq);
nackInfo.retries++;
nackInfo.sentAtMs = nowMs;
if (nackInfo.retries >= MaxNackRetries)
{
MS_WARN_TAG(
rtx,
"sequence number removed from the NACK list due to max retries [filter:time, seq:%" PRIu16
"]",
seq);
it = this->nackList.erase(it);
}
else
{
++it;
}
continue;
}
++it;
}
return nackBatch;
}