webrtc代码走读(四)-QOS-NACK实现-发送端

1 核心流程与交互关系表

发送端 NACK 实现的核心流程与交互关系如下表所示:

发送端核心操作 媒体接收端操作
1. 发送 RTP 报文,并将报文存入 packet_history_ 缓存队列 -
2. 接收来自接收端的 RTCP NACK 报文 1. 检测 RTP 报文丢失,生成并发送 RTCP NACK 报文
3. 触发 RTPSender::OnReceivedNack 处理 NACK 反馈 -
4. 调用 RTPSender::ReSendPacket 重发丢失的 RTP 报文 2. 接收重发的 RTP 报文,完成丢包恢复

2、核心函数走读

发送端 NACK 实现分为三大核心流程:RTP 报文缓存RTCP NACK 处理RTP 报文重发,以下将逐一拆解每个流程的关键函数与源码细节。

2.1 流程1:发送 RTP 报文并缓存到 packet_history_

发送端通过 Pacer( pacing 控制器)发送 RTP 报文时,会将媒体报文(需支持重传)存入 packet_history_ 队列,为后续重发提供数据来源。同时,需通过 SetStorePacketsStatus 配置队列长度,确保缓存能覆盖合理的重传窗口。

2.1.1 关键函数调用链

plaintext 复制代码
ProcessThreadImpl::Process  // 线程调度入口,触发 Pacer 处理
-> PacedSender::Process     // Pacing 发送器主逻辑
-> PacingController::ProcessPackets  // 控制报文发送节奏
-> PacketRouter::SendPacket  // 路由报文到对应模块
-> ModuleRtpRtcpImpl2::TrySendPacket  // RTP/RTCP 模块发送预处理
-> RtpSenderEgress::SendPacket  // 最终发送 RTP 报文,并触发缓存

2.1.2 核心函数:RtpSenderEgress::SendPacket(报文发送与缓存触发)

cpp 复制代码
// 函数功能:发送 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(报文缓存实现)

cpp 复制代码
// 函数功能:将 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 函数中初始化

    cpp 复制代码
    // 创建 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 函数中初始化

    cpp 复制代码
    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 关键函数调用链

plaintext 复制代码
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 报文)

cpp 复制代码
// 函数功能:解析 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 信息转发)

cpp 复制代码
// 函数功能:将解析后的 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(重传触发入口)

cpp 复制代码
// 函数功能:处理 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(报文重发实现)

该函数是重传逻辑的核心,包含 缓存校验重传通道选择优先级配置 三大关键逻辑。

cpp 复制代码
// 函数功能:重发指定序列号的 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 与状态校验)

cpp 复制代码
// 函数功能:从缓存中提取报文,校验重传条件(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 映射优先级。

cpp 复制代码
// 函数功能:根据报文类型获取发送优先级(数值越小,优先级越高)
// 参数说明:
//   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 需配置以下三个核心参数:

cpp 复制代码
// 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)。

相关推荐
qq_310658513 小时前
webrtc源码走读(一)-QOS-NACK-概述
网络·webrtc
长欢喜一场5 小时前
高精度算法全解析:从原理到实现
c++·1024程序员节
m0_748233645 小时前
单调栈详解【C/C++】
c语言·c++·算法·1024程序员节
Source.Liu6 小时前
【CMakeLists.txt】QtSvg 头文件包含配置详解
c++·qt·librecad
郝学胜-神的一滴6 小时前
Linux中的`fork`函数详解:深入解析
linux·服务器·开发语言·c++·算法
penguin_bark6 小时前
C++调用MySQL数据库完整教程
数据库·c++·mysql
让我们一起加油好吗7 小时前
【数论】欧拉定理 && 扩展欧拉定理
c++·算法·数论·1024程序员节·欧拉定理·欧拉降幂·扩展欧拉定理
Yupureki7 小时前
从零开始的C++学习生活 14:map/set的使用和封装
c语言·数据结构·c++·学习·visual studio·1024程序员节
一匹电信狗7 小时前
【LeetCode_876_2.02】快慢指针在链表中的简单应用
c语言·数据结构·c++·算法·leetcode·链表·stl