一、概述
webrtc接收端觸發發送NACK報文有兩處:
1、接收RTP報文,對序列號進行檢測,發現有丟包,立即觸發發送NACK報文。
2、定時檢查nack_list_隊列,發現丟包滿足申請重傳條件,立即觸發發送NACK報文。
二、函數實現
1、接收丟包觸發函數實現
NackModule::OnReceivedPacket
->NackModule::GetNackBatch
函數裏實現,該函數在整個調用棧的位置如下:
2、定時檢查觸發函數實現
PlatformThread::StartThread()
->PlatformThread::Run()
->ProcessThreadImpl::Process()
->NackModule::Process()
->NackModule::GetNackBatch
其中NackModule::Process是掛載在接收RTP報文線程的一個定時任務。在RtpVideoStreamReceiver::RtpVideoStreamReceiver函數實現掛載。
NackModule::Process函數的調度週期是kProcessIntervalMs(默認20ms)
3、核心函數
NackModule::AddPacketsToNack、NackModule::GetNackBatch是NACK核心函數。
NackModule::AddPacketsToNack
決定是否將該報文放入NACK隊列
void NackModule::AddPacketsToNack(uint16_t seq_num_start,
uint16_t seq_num_end) {
// Remove old packets.
auto it = nack_list_.lower_bound(seq_num_end - kMaxPacketAge);
nack_list_.erase(nack_list_.begin(), it);
// If the nack list is too large, remove packets from the nack list until
// the latest first packet of a keyframe. If the list is still too large,
// clear it and request a keyframe.
uint16_t num_new_nacks = ForwardDiff(seq_num_start, seq_num_end);
if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {
while (RemovePacketsUntilKeyFrame() &&
nack_list_.size() + num_new_nacks > kMaxNackPackets) {
}
if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {
nack_list_.clear();
keyframe_request_sender_->RequestKeyFrame();
return;
}
}
for (uint16_t seq_num = seq_num_start; seq_num != seq_num_end; ++seq_num) {
// Do not send nack for packets that are already recovered by FEC or RTX
if (recovered_list_.find(seq_num) != recovered_list_.end())
continue;
NackInfo nack_info(seq_num, seq_num + WaitNumberOfPackets(0.5),
clock_->TimeInMilliseconds());
RTC_DCHECK(nack_list_.find(seq_num) == nack_list_.end());
nack_list_[seq_num] = nack_info;
}
}
該函數的中心思想是:
1、nack_list的最大長度爲kMaxNackPackets,即本次發送的nack包至多可以對kMaxNackPackets個丟失的包進行重傳請求。如果丟失的包數量超過kMaxNackPackets,會循環清空nack_list中關鍵幀之前的包,直到其長度小於kMaxNackPackets。也就是說,放棄對關鍵幀首包之前的包的重傳請求,直接而快速的以關鍵幀首包之後的包號作爲重傳請求的開始。
2、nack_list中包號的距離不能超過kMaxPacketAge個包號。即nack_list中的包號始終保持 [cur_seq_num - kMaxPacketAge, cur_seq_num] 這樣的跨度,以保證nack請求列表中不會有太老舊的包號。
NackModule::GetNackBatch
決定是否發生NACK請求重傳該報文。兩種觸發方式都是調用這個函數決定是否發送NACK請求重傳。
std::vector<uint16_t> NackModule::GetNackBatch(NackFilterOptions options) {
bool consider_seq_num = options != kTimeOnly;
bool consider_timestamp = options != kSeqNumOnly;
Timestamp now = clock_->CurrentTime();
std::vector<uint16_t> nack_batch;
auto it = nack_list_.begin();
while (it != nack_list_.end()) {
TimeDelta resend_delay = TimeDelta::Millis(rtt_ms_);
if (backoff_settings_) {
resend_delay = std::max(resend_delay, backoff_settings_->min_retry_interval);
if (it->second.retries > 1) {
TimeDelta exponential_backoff =
std::min(TimeDelta::Millis(rtt_ms_), backoff_settings_->max_rtt) *
std::pow(backoff_settings_->base, it->second.retries - 1);
resend_delay = std::max(resend_delay, exponential_backoff);
}
}
bool delay_timed_out =
now.ms() - it->second.created_at_time >= send_nack_delay_ms_;
bool nack_on_rtt_passed =
now.ms() - it->second.sent_at_time >= resend_delay.ms();
bool nack_on_seq_num_passed =
it->second.sent_at_time == -1 &&
AheadOrAt(newest_seq_num_, it->second.send_at_seq_num);
if (delay_timed_out && ((consider_seq_num && nack_on_seq_num_passed) ||
(consider_timestamp && nack_on_rtt_passed))) {
nack_batch.emplace_back(it->second.seq_num);
++it->second.retries;
it->second.sent_at_time = now.ms();
if (it->second.retries >= kMaxNackRetries) {
it = nack_list_.erase(it);
} else {
++it;
}
continue;
}
++it;
}
return nack_batch;
}
該函數的中心思想是:
1、因爲報文有可能出現亂序抖動情況,不能說檢測出丟包就立即重傳,需要等待send_nack_delay_ms_,當等待時間大於send_nack_delay_ms_,申請重傳。
2、因爲NACK產生的延時主要在RTT環路延時上,所以再次重傳的時間一定要大於rtt_ms_,當兩次發送NACK重傳請求時間大於rtt_ms_時,纔會申請再次重傳。
3、視頻會議場景對實時性要求很高,當報文一直處於丟包狀態,不能持續申請重傳,最大重傳次數爲kMaxNackRetries,超過最大重傳次數,放棄該報文。不再重傳。