1 核心流程與交互關係表

發送端 NACK 實現的核心流程與交互關係如下表所示:

發送端核心操作

媒體接收端操作

1. 發送 RTP 報文,並將報文存入 packet_history_ 緩存隊列

-

2. 接收來自接收端的 RTCP NACK 報文

1. 檢測 RTP 報文丟失,生成併發送 RTCP NACK 報文

3. 觸發 RTPSender::OnReceivedNack 處理 NACK 反饋

-

4. 調用 RTPSender::ReSendPacket 重發丟失的 RTP 報文

2. 接收重發的 RTP 報文,完成丟包恢復

WebRTC系列分享 第三期 | WebRTC QoS方法之視頻發送端NACK實現-_#c++

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 準備重傳。

WebRTC系列分享 第三期 | WebRTC QoS方法之視頻發送端NACK實現-_#webrtc_02

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 保障實時媒體傳輸質量的關鍵機制,其核心邏輯可概括為以下三點:

  1. 報文緩存:通過 packet_history_ 隊列·34緩存可重傳的 RTP 報文,按序列號索引,支持快速查詢;同時根據媒體類型(視頻/音頻)配置合理的緩存長度,平衡內存佔用與重傳覆蓋率。
  2. NACK 處理:接收端通過 RTCP NACK 報文反饋丟包序列,發送端解析後提取丟包序列號,結合 RTT 信息控制重傳節奏,避免短時間內重複重傳。
  3. 優先級重傳:重傳報文通過 RtpPacketMediaType::kRetransmission 標記為高優先級,確保 Pacer 優先發送;推薦使用 RTX 專用通道重傳,避免影響正常媒體的丟包率統計和帶寬估計(GCC)。