1 核心流程與交互關係表
發送端 NACK 實現的核心流程與交互關係如下表所示:
|
發送端核心操作
|
媒體接收端操作
|
|
1. 發送 RTP 報文,並將報文存入 |
-
|
|
2. 接收來自接收端的 RTCP NACK 報文
|
1. 檢測 RTP 報文丟失,生成併發送 RTCP NACK 報文
|
|
3. 觸發 |
-
|
|
4. 調用 |
2. 接收重發的 RTP 報文,完成丟包恢復
|
2、核心函數走讀
發送端 NACK 實現分為三大核心流程:RTP 報文緩存、RTCP NACK 處理、RTP 報文重發,以下將逐一拆解每個流程的關鍵函數與源碼細節。
2.1 流程1:發送 RTP 報文並緩存到 packet_history_
發送端通過 Pacer( pacing 控制器)發送 RTP 報文時,會將媒體報文(需支持重傳)存入 packet_history_ 隊列,為後續重發提供數據來源。同時,需通過 SetStorePacketsStatus 配置隊列長度,確保緩存能覆蓋合理的重傳窗口。
2.1.1 關鍵函數調用鏈
ProcessThreadImpl::Process // 線程調度入口,觸發 Pacer 處理
-> PacedSender::Process // Pacing 發送器主邏輯
-> PacingController::ProcessPackets // 控制報文發送節奏
-> PacketRouter::SendPacket // 路由報文到對應模塊
-> ModuleRtpRtcpImpl2::TrySendPacket // RTP/RTCP 模塊發送預處理
-> RtpSenderEgress::SendPacket // 最終發送 RTP 報文,並觸發緩存
2.1.2 核心函數:RtpSenderEgress::SendPacket(報文發送與緩存觸發)
// 函數功能:發送 RTP 報文到網絡,並將可重傳的媒體報文存入 packet_history_ 緩存
// 參數説明:
// *packet: 待發送的 RTP 報文對象
// options: 發送選項(如 QoS 優先級、是否允許重傳等)
// pacing_info: Pacing 相關信息(如發送時間、比特率等)
// 返回值:bool - 報文是否成功發送到網絡
const bool send_success = SendPacketToNetwork(*packet, options, pacing_info);
// 關鍵邏輯:無論發送是否成功,均處理報文緩存(確保重傳時能找到報文)
// 條件1:is_media - 是否為媒體報文(非 RTCP、非 Padding 等)
// 條件2:packet->allow_retransmission() - 報文是否允許重傳(由發送端配置決定)
if (is_media && packet->allow_retransmission()) {
// 將報文存入緩存,記錄當前時間(用於後續 RTT 校驗)
packet_history_->PutRtpPacket(
std::make_unique<RtpPacketToSend>(*packet), // 複製 RTP 報文
now_ms // 當前時間戳(毫秒),標記報文首次發送時間
);
}
// 處理重傳報文的狀態更新:若當前報文是重傳報文,標記原報文為“已發送”
else if (packet->retransmitted_sequence_number()) {
packet_history_->MarkPacketAsSent(*packet->retransmitted_sequence_number());
}
2.1.3 核心函數:RtpPacketHistory::PutRtpPacket(報文緩存實現)
// 函數功能:將 RTP 報文存入緩存隊列,以序列號(SequenceNumber)為索引,支持重傳時快速查詢
// 參數説明:
// packet: 待緩存的 RTP 報文(智能指針,確保內存安全)
// send_time_ms: 報文發送時間戳(可選,首次發送時傳入)
void RtpPacketHistory::PutRtpPacket(
std::unique_ptr<RtpPacketToSend> packet,
absl::optional<int64_t> send_time_ms
) {
RTC_DCHECK(packet); // WebRTC 斷言:確保 packet 非空(調試用)
MutexLock lock(&lock_); // 加鎖,保證多線程下緩存操作線程安全
int64_t now_ms = clock_->TimeInMilliseconds(); // 獲取當前系統時間
// 若緩存模式為“禁用”,直接返回(不緩存任何報文)
if (mode_ == StorageMode::kDisabled) {
return;
}
// 斷言:確保當前報文允許重傳(與 RtpSenderEgress::SendPacket 中的條件一致)
RTC_DCHECK(packet->allow_retransmission());
// 清理過期報文:刪除緩存中超過最大緩存時間/數量的報文,避免內存泄漏
CullOldPackets(now_ms);
// 1. 獲取當前報文的序列號,計算其在緩存隊列中的索引
const uint16_t rtp_seq_no = packet->SequenceNumber(); // RTP 報文序列號(16位)
int packet_index = GetPacketIndex(rtp_seq_no); // 根據序列號計算索引(隊列位置)
// 2. 處理重複報文:若該序列號的報文已存在,刪除舊報文(避免狀態不一致)
if (packet_index >= 0 &&
static_cast<size_t>(packet_index) < packet_history_.size() &&
packet_history_[packet_index].packet_ != nullptr) {
RTC_LOG(LS_WARNING) << "Duplicate packet inserted: " << rtp_seq_no; // 打印警告日誌
RemovePacket(packet_index); // 刪除舊報文
packet_index = GetPacketIndex(rtp_seq_no); // 重新計算索引(舊報文刪除後索引可能變化)
}
// 3. 擴展緩存隊列:若索引小於0(報文序列號小於隊列中所有報文),在隊列頭部插入空元素
for (; packet_index < 0; ++packet_index) {
packet_history_.emplace_front(nullptr, absl::nullopt, 0);
}
// 4. 擴展緩存隊列:若索引超過隊列長度(報文序列號大於隊列中所有報文),在隊列尾部插入空元素
while (static_cast<int>(packet_history_.size()) <= packet_index) {
packet_history_.emplace_back(nullptr, absl::nullopt, 0);
}
// 5. 斷言:確保索引合法(防止越界訪問)
RTC_DCHECK_GE(packet_index, 0); // 索引 >= 0
RTC_DCHECK_LT(packet_index, packet_history_.size()); // 索引 < 隊列長度
RTC_DCHECK(packet_history_[packet_index].packet_ == nullptr); // 目標位置為空(無重複)
// 6. 存入緩存:創建 StoredPacket 對象,保存報文、發送時間和插入順序
packet_history_[packet_index] = StoredPacket(
std::move(packet), // 轉移報文所有權到緩存
send_time_ms, // 發送時間戳
packets_inserted_++ // 插入計數器(用於排序或清理優先級)
);
// 7. (可選)Padding 優先級處理:若啓用 Padding 優先級,將報文加入優先級集合
if (enable_padding_prio_) {
// 若優先級集合超過最大長度,刪除最後一個元素(LRU 策略)
if (padding_priority_.size() >= kMaxPaddingHistory - 1) {
padding_priority_.erase(std::prev(padding_priority_.end()));
}
// 將當前緩存的報文加入優先級集合
auto prio_it = padding_priority_.insert(&packet_history_[packet_index]);
RTC_DCHECK(prio_it.second) << "Failed to insert packet into prio set."; // 斷言插入成功
}
}
2.1.4 緩存隊列配置:SetStorePacketsStatus
packet_history_ 的緩存長度需根據媒體類型(視頻/音頻)分別配置,確保能覆蓋典型的 RTT 窗口(避免重傳時報文已被清理)。
- 視頻緩存配置:在
CreateRtpStreamSenders函數中初始化
// 創建 ModuleRtpRtcpImpl2 實例(RTP/RTCP 核心模塊)
std::unique_ptr<ModuleRtpRtcpImpl2> rtp_rtcp(
ModuleRtpRtcpImpl2::Create(configuration)
);
rtp_rtcp->SetSendingStatus(false); // 初始關閉發送狀態
rtp_rtcp->SetSendingMediaStatus(false); // 初始關閉媒體發送狀態
rtp_rtcp->SetRTCPStatus(RtcpMode::kCompound); // 啓用複合 RTCP 模式(支持 NACK)
// 啓用緩存,設置最小緩存長度(kMinSendSidePacketHistorySize 為預定義常量,通常為 1000+)
rtp_rtcp->SetStorePacketsStatus(true, kMinSendSidePacketHistorySize);
- 音頻緩存配置:在
ChannelSend::RegisterSenderCongestionControlObjects函數中初始化
void ChannelSend::RegisterSenderCongestionControlObjects(
RtpTransportControllerSendInterface* transport,
RtcpBandwidthObserver* bandwidth_observer
) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_); // 斷言在工作線程執行
RtpPacketSender* rtp_packet_pacer = transport->packet_sender(); // 獲取 Pacer
PacketRouter* packet_router = transport->packet_router(); // 獲取報文路由
RTC_DCHECK(rtp_packet_pacer);
RTC_DCHECK(packet_router);
RTC_DCHECK(!packet_router_);
rtcp_observer_->SetBandwidthObserver(bandwidth_observer); // 設置帶寬觀察者
rtp_packet_pacer_proxy_->SetPacketPacer(rtp_packet_pacer); // 綁定 Pacer 代理
// 啓用音頻緩存,固定設置緩存長度為 600(音頻報文小,緩存更多以應對高丟包)
rtp_rtcp_->SetStorePacketsStatus(true, 600);
constexpr bool remb_candidate = false; // 不作為 REMB(帶寬估計)候選
packet_router->AddSendRtpModule(rtp_rtcp_.get(), remb_candidate); // 註冊 RTP 模塊到路由
packet_router_ = packet_router;
}
2.2 流程2:處理接收端的 RTCP NACK 報文
接收端檢測到 RTP 丟包後,會發送 RTCP NACK 報文(攜帶丟包序列號列表)。發送端通過 RTCP 接收模塊解析該報文,提取丟包序列,並傳遞給 RTPSender 準備重傳。
2.2.1 關鍵函數調用鏈
RTCPReceiver::IncomingPacket // 接收 RTCP 報文(從網絡層獲取)
-> RTCPReceiver::ParseCompoundPacket // 解析複合 RTCP 報文(NACK 屬於複合報文的一部分)
-> RTCPReceiver::TriggerCallbacksFromRtcpPacket // 觸發 RTCP 報文回調(分發到對應處理器)
-> RTCPReceiver::HandleNack // 專門處理 NACK 報文,提取丟包序列號
-> ModuleRtpRtcpImpl::OnReceivedNack // 轉發 NACK 信息到 RTPSender
-> RTPSender::OnReceivedNack // 最終處理 NACK,準備重傳
2.2.2 核心函數:RTCPReceiver::HandleNack(解析 NACK 報文)
// 函數功能:解析 RTCP NACK 報文,提取丟包序列號,存入 packet_information 供後續處理
// 參數説明:
// rtcp_block: RTCP 報文頭部(包含 NACK 報文的類型、長度等信息)
// packet_information: 輸出參數,存儲 NACK 相關信息(丟包序列、報文類型標記等)
void RTCPReceiver::HandleNack(
const CommonHeader& rtcp_block,
PacketInformation* packet_information
) {
rtcp::Nack nack; // NACK 報文解析對象
// 第一步:解析 RTCP 報文塊,若解析失敗(格式錯誤),跳過該報文並計數
if (!nack.Parse(rtcp_block)) {
++num_skipped_packets_; // 統計跳過的無效報文數
return;
}
// 第二步:校驗 NACK 報文的目標 SSRC 是否匹配當前發送端的媒體 SSRC
// receiver_only_: 若當前是純接收端(不發送媒體),忽略 NACK
// main_ssrc_: 當前發送端的媒體 SSRC(NACK 報文中的 media_ssrc 需與之匹配)
if (receiver_only_ || main_ssrc_ != nack.media_ssrc()) {
return; // 不是發給當前發送端的 NACK,忽略
}
// 第三步:提取 NACK 報文中的丟包序列號,存入 packet_information
// nack.packet_ids(): 返回丟包序列號列表(std::vector<uint16_t>)
packet_information->nack_sequence_numbers.insert(
packet_information->nack_sequence_numbers.end(),
nack.packet_ids().begin(),
nack.packet_ids().end()
);
// 第四步:更新 NACK 統計信息(用於監控和調試)
for (uint16_t packet_id : nack.packet_ids()) {
nack_stats_.ReportRequest(packet_id); // 記錄每個丟包序列號的請求次數
}
// 第五步:標記 packet_information 的報文類型為“NACK”,供後續回調識別
if (!nack.packet_ids().empty()) {
packet_information->packet_type_flags |= kRtcpNack; // 置位 NACK 標記
++packet_type_counter_.nack_packets; // 統計接收的 NACK 報文總數
packet_type_counter_.nack_requests = nack_stats_.requests(); // 統計總 NACK 請求數
packet_type_counter_.unique_nack_requests = nack_stats_.unique_requests(); // 統計唯一丟包數
}
}
2.2.3 核心函數:ModuleRtpRtcpImpl::OnReceivedNack(NACK 信息轉發)
// 函數功能:將解析後的 NACK 丟包序列和 RTT 信息轉發給 RTPSender,觸發重傳準備
// 參數説明:
// nack_sequence_numbers: 丟包序列號列表(從 NACK 報文中提取)
void ModuleRtpRtcpImpl::OnReceivedNack(
const std::vector<uint16_t>& nack_sequence_numbers
) {
// 檢查 RTPSender 是否存在(若未初始化,忽略 NACK)
if (!rtp_sender_) {
return;
}
// 檢查緩存是否啓用且丟包列表非空(無緩存則無法重傳,空列表無需處理)
if (!StorePackets() || nack_sequence_numbers.empty()) {
return;
}
// 計算 RTT(往返時間):用於後續重傳頻率控制(避免短時間內重複重傳)
int64_t rtt = rtt_ms(); // 優先從 RtcpRttStats 獲取 RTT(若已統計)
if (rtt == 0) { // 若 RTT 未統計,從 RTCPReceiver 中獲取遠程 SSRC 的 RTT
rtcp_receiver_.RTT(
rtcp_receiver_.RemoteSSRC(), // 接收端的 SSRC
NULL, // 忽略發送端到接收端的延遲
&rtt, // 輸出 RTT(毫秒)
NULL, // 忽略抖動
NULL // 忽略延遲偏差
);
}
// 將 NACK 丟包序列和 RTT 傳遞給 RTPSender,觸發重傳邏輯
rtp_sender_->packet_generator.OnReceivedNack(nack_sequence_numbers, rtt);
}
2.3 流程3:重發 NACK 反饋的 RTP 報文
RTPSender 接收 NACK 丟包序列後,從 packet_history_ 中提取對應報文,校驗重傳條件(如 RTT 間隔、是否已在重傳隊列等),並通過 Pacer 以高優先級重發報文。
2.3.1 核心函數:RTPSender::OnReceivedNack(重傳觸發入口)
// 函數功能:處理 NACK 丟包序列,逐個觸發報文重傳
// 參數説明:
// nack_sequence_numbers: 丟包序列號列表
// avg_rtt: 平均 RTT(用於重傳頻率控制)
void RTPSender::OnReceivedNack(
const std::vector<uint16_t>& nack_sequence_numbers,
const int32_t avg_rtt
) {
// 設置 RTT 到緩存:緩存中用於校驗重傳間隔(避免短時間內重複重傳)
// 5 + avg_rtt:增加 5ms 偏移,應對網絡抖動
packet_history_->SetRtt(5 + avg_rtt);
// 遍歷丟包序列號列表,逐個嘗試重傳
for (uint16_t seq_no : nack_sequence_numbers) {
// 調用 ReSendPacket 重傳當前序列號的報文,返回重發的字節數
const int32_t bytes_sent = ReSendPacket(seq_no);
// 若重傳失敗(bytes_sent < 0),放棄後續所有丟包的重傳(避免連鎖失敗)
if (bytes_sent < 0) {
RTC_LOG(LS_WARNING) << "Failed resending RTP packet " << seq_no
<< ", Discard rest of packets.";
break;
}
}
}
2.3.2 核心函數:RTPSender::ReSendPacket(報文重發實現)
該函數是重傳邏輯的核心,包含 緩存校驗、重傳通道選擇、優先級配置 三大關鍵邏輯。
// 函數功能:重發指定序列號的 RTP 報文,處理重傳通道、優先級和速率限制
// 參數説明:
// packet_id: 待重傳報文的序列號
// 返回值:int32_t - 重發的字節數(<0 表示重傳失敗)
int32_t RTPSender::ReSendPacket(uint16_t packet_id) {
// 第一步:查詢報文在緩存中的狀態(是否存在、是否已在重傳隊列)
absl::optional<RtpPacketHistory::PacketState> stored_packet =
packet_history_->GetPacketState(packet_id);
// 若報文不存在或已在重傳隊列,返回 0(無需處理)
if (!stored_packet || stored_packet->pending_transmission) {
return 0;
}
// 第二步:獲取報文大小(用於速率限制和統計)
const int32_t packet_size = static_cast<int32_t>(stored_packet->packet_size);
// 第三步:判斷是否使用 RTX 通道重傳(RTX 是專門的重傳通道)
// RtxStatus() & kRtxRetransmitted:檢查 RTX 重傳模式是否啓用
const bool rtx = (RtxStatus() & kRtxRetransmitted) > 0;
// 第四步:從緩存中提取報文並標記為“待重傳”(防止重複重傳)
std::unique_ptr<RtpPacketToSend> packet =
packet_history_->GetPacketAndMarkAsPending(
packet_id,
// 匿名函數:處理報文封裝(RTX 或普通通道)
[&](const RtpPacketToSend& stored_packet) -> std::unique_ptr<RtpPacketToSend> {
// 子步驟1:速率限制校驗(避免重傳佔用過多帶寬)
if (retransmission_rate_limiter_ &&
!retransmission_rate_limiter_->TryUseRate(packet_size)) {
// 若速率超限,返回空指針(重傳失敗)
return nullptr;
}
// 子步驟2:選擇重傳通道並封裝報文
std::unique_ptr<RtpPacketToSend> retransmit_packet;
if (rtx) {
// 方案1:使用 RTX 通道重傳(推薦)
// BuildRtxPacket:將原報文封裝為 RTX 格式(攜帶原序列號和 SSRC)
retransmit_packet = BuildRtxPacket(stored_packet);
} else {
// 方案2:與普通媒體報文混傳(不推薦,會影響丟包率統計)
retransmit_packet = std::make_unique<RtpPacketToSend>(stored_packet);
}
// 子步驟3:標記重傳報文的原序列號(供接收端識別)
if (retransmit_packet) {
retransmit_packet->set_retransmitted_sequence_number(
stored_packet.SequenceNumber()
);
}
return retransmit_packet;
}
);
// 若提取報文失敗(如速率超限、報文已過期),返回 -1(重傳失敗)
if (!packet) {
return -1;
}
// 第五步:配置重傳報文的屬性
packet->set_packet_type(RtpPacketMediaType::kRetransmission); // 標記為“重傳報文”(用於優先級)
packet->set_fec_protect_packet(false); // 重傳報文無需再做 FEC 保護(原報文已做)
// 第六步:將重傳報文加入 Pacer 隊列(按高優先級發送)
std::vector<std::unique_ptr<RtpPacketToSend>> packets;
packets.emplace_back(std::move(packet)); // 轉移報文所有權到隊列
paced_sender_->EnqueuePackets(std::move(packets)); // 加入 Pacer 發送隊列
// 返回重發的字節數(成功)
return packet_size;
}
2.3.3 重傳關鍵校驗:RtpPacketHistory::GetPacketAndMarkAsPending(RTT 與狀態校驗)
// 函數功能:從緩存中提取報文,校驗重傳條件(RTT 間隔、是否已在重傳),並標記狀態
// 參數説明:
// sequence_number: 待提取報文的序列號
// encapsulate: 封裝函數(用於 RTX 或普通通道處理)
// 返回值:std::unique_ptr<RtpPacketToSend> - 提取的報文(空指針表示失敗)
std::unique_ptr<RtpPacketToSend> RtpPacketHistory::GetPacketAndMarkAsPending(
uint16_t sequence_number,
rtc::FunctionView<std::unique_ptr<RtpPacketToSend>(const RtpPacketToSend&)> encapsulate
) {
MutexLock lock(&lock_); // 線程安全鎖
// 條件1:緩存禁用,返回空
if (mode_ == StorageMode::kDisabled) {
return nullptr;
}
// 條件2:獲取緩存中的報文,不存在則返回空
StoredPacket* packet = GetStoredPacket(sequence_number);
if (packet == nullptr) {
return nullptr;
}
// 條件3:報文已在重傳隊列(pending_transmission_ 為 true),返回空(避免重複重傳)
if (packet->pending_transmission_) {
return nullptr;
}
// 條件4:RTT 校驗(避免短時間內重複重傳,減輕網絡負擔)
int64_t now_ms = clock_->TimeInMilliseconds();
if (!VerifyRtt(*packet, now_ms)) {
// 校驗失敗:距離上次重傳時間小於 RTT,返回空
return nullptr;
}
// 封裝報文(RTX 或普通通道)
std::unique_ptr<RtpPacketToSend> encapsulated_packet = encapsulate(*packet->packet_);
// 若封裝成功,標記報文為“待重傳”
if (encapsulated_packet) {
packet->pending_transmission_ = true;
}
return encapsulated_packet;
}
// 輔助函數:RTT 校驗邏輯
bool RtpPacketHistory::VerifyRtt(
const RtpPacketHistory::StoredPacket& packet,
int64_t now_ms
) const {
// 僅對已發送過的重傳報文進行校驗(首次重傳無需校驗)
if (packet.send_time_ms_ && // 報文有發送時間戳
packet.times_retransmitted() > 0 && // 已重傳過至少一次
now_ms < *packet.send_time_ms_ + rtt_ms_) { // 當前時間 - 上次發送時間 < RTT
// 校驗失敗:短時間內重複重傳,可能報文仍在網絡中
return false;
}
return true;
}
2.3.4 重傳優先級配置:GetPriorityForType(確保重傳報文優先發送)
重傳報文需按高優先級發送,以減少延遲。WebRTC 通過 RtpPacketMediaType 定義報文類型,再通過 GetPriorityForType 映射優先級。
// 函數功能:根據報文類型獲取發送優先級(數值越小,優先級越高)
// 參數説明:
// type: 報文類型(音頻、視頻、重傳、FEC、Padding 等)
// 返回值:int - 優先級數值
int GetPriorityForType(RtpPacketMediaType type) {
switch (type) {
case RtpPacketMediaType::kAudio:
// 音頻優先級最高(實時性要求最高)
return kFirstPriority + 1;
case RtpPacketMediaType::kRetransmission:
// 重傳報文優先級次之(需儘快恢復丟包,避免卡頓)
return kFirstPriority + 2;
case RtpPacketMediaType::kVideo:
// 普通視頻報文優先級中等
return kFirstPriority + 3;
case RtpPacketMediaType::kForwardErrorCorrection:
// FEC(前向糾錯)報文優先級低於普通視頻
return kFirstPriority + 3;
case RtpPacketMediaType::kPadding:
// Padding(填充)報文優先級最低(無實際數據,僅用於帶寬探測)
return kFirstPriority + 4;
default:
RTC_CHECK_NOTREACHED(); // 斷言:無其他類型
}
}
// Pacer 隊列中使用優先級:EnqueuePacket 時傳入優先級,確保高優先級報文先發送
void PacingController::EnqueuePacket(std::unique_ptr<RtpPacketToSend> packet) {
RTC_DCHECK(pacing_bitrate_ > DataRate::Zero())
<< "SetPacingRate must be called before InsertPacket."; // 斷言 Pacing 速率已配置
// 先獲取報文優先級,再存入隊列(避免報文移動後無法訪問類型)
const int priority = GetPriorityForType(*packet->packet_type());
// 按優先級加入內部隊列(Pacer 會優先發送低數值優先級的報文)
EnqueuePacketInternal(std::move(packet), priority);
}
2.3.5 RTX 通道配置(推薦)
RTX(Retransmission)是專門的重傳通道,與普通媒體通道分離,可避免重傳報文影響正常媒體的丟包率統計。啓用 RTX 需配置以下三個核心參數:
// 1. 配置 NACK 緩存歷史時間(確保重傳時報文未被清理)
rtp_config_.nack.rtp_history_ms = kNackHistoryMs; // kNackHistoryMs 通常為 1000ms(1秒)
// 2. 配置 RTX 通道的 payload type(與普通媒體的 payload type 區分)
rtp_config_.rtx.payload_type = payload_type; // 如 96(需與接收端協商一致)
// 3. 配置 RTX 通道的 SSRC(與普通媒體的 SSRC 區分,避免混淆)
rtp_config_.rtx.ssrcs.push_back(rtx_ssrc); // 如 0x12345678(需唯一)
3、核心總結
發送端 NACK 是 WebRTC 保障實時媒體傳輸質量的關鍵機制,其核心邏輯可概括為以下三點:
- 報文緩存:通過
packet_history_隊列·34緩存可重傳的 RTP 報文,按序列號索引,支持快速查詢;同時根據媒體類型(視頻/音頻)配置合理的緩存長度,平衡內存佔用與重傳覆蓋率。 - NACK 處理:接收端通過 RTCP NACK 報文反饋丟包序列,發送端解析後提取丟包序列號,結合 RTT 信息控制重傳節奏,避免短時間內重複重傳。
- 優先級重傳:重傳報文通過
RtpPacketMediaType::kRetransmission標記為高優先級,確保 Pacer 優先發送;推薦使用 RTX 專用通道重傳,避免影響正常媒體的丟包率統計和帶寬估計(GCC)。