webrtc中的rtp包重传机制分析

    技术2022-07-10  120

    webrtc中采用了两种手段来对抗网络丢包,一种是重传,另一种是fec。本文针对视频rtp包的重传实现进行分析,不涉及音频rtp包(猜测应与视频rtp包类似)。与rtp传输相关的模块源码位于webrtc目录下的src\modules\rtp_rtcp

    rtp包的打包。RtpPacketToSend类用来封装待发送的rtp包,可以通过set_allow_retransmission来设置此rtp包是否允许重传。rtp包的发送。RtpSenderEgress是rtp包的出口类,主要负责将rtp包发送到网络。在SendPacket函数中,调用SendPacketToNetwork将rtp包发送到网络,如果rtp包允许重传的话则将其存起来,相关代码如下: const bool send_success = SendPacketToNetwork(*packet, options, pacing_info); // Put packet in retransmission history or update pending status even if // actual sending fails. if (is_media && packet->allow_retransmission()) { packet_history_->PutRtpPacket(std::make_unique<RtpPacketToSend>(*packet), now_ms); } else if (packet->retransmitted_sequence_number()) { packet_history_->MarkPacketAsSent(*packet->retransmitted_sequence_number()); }

     

    rtp包的缓存。如果允许重传,那么已发送的rtp包需要进行缓存,webrtc使用RtpPacketHistory类来缓存rtp包。当发送端收到接收端的ack时,从RtpPacketHistory中踢除相关的包,相关代码如下:

    void ModuleRtpRtcpImpl::OnPacketsAcknowledged( rtc::ArrayView<const uint16_t> sequence_numbers) { RTC_DCHECK(rtp_sender_); rtp_sender_->packet_history.CullAcknowledgedPackets(sequence_numbers); }

     

    rtcp包的构建和丢包的判定。接收端通过rtcp包来反馈rtp包的接收情况,TransportFeedback类用于构建一个rtcp包,每收到一个rtp包都会调用其中的AddReceivedPacket函数来进行登记,在AddReceivedPacket函数内部会对丢包做出判断,代码和注释如下:

    bool TransportFeedback::AddReceivedPacket(uint16_t sequence_number, int64_t timestamp_us) { // Set delta to zero if timestamps are not included, this will simplify the // encoding process. int16_t delta = 0; if (include_timestamps_) { // Convert to ticks and round. int64_t delta_full = (timestamp_us - last_timestamp_us_) % kTimeWrapPeriodUs; if (delta_full > kTimeWrapPeriodUs / 2) delta_full -= kTimeWrapPeriodUs; delta_full += delta_full < 0 ? -(kDeltaScaleFactor / 2) : kDeltaScaleFactor / 2; delta_full /= kDeltaScaleFactor; delta = static_cast<int16_t>(delta_full); // If larger than 16bit signed, we can't represent it - need new fb packet. if (delta != delta_full) { RTC_LOG(LS_WARNING) << "Delta value too large ( >= 2^16 ticks )"; return false; } } uint16_t next_seq_no = base_seq_no_ + num_seq_no_; if (sequence_number != next_seq_no) { uint16_t last_seq_no = next_seq_no - 1; if (!IsNewerSequenceNumber(sequence_number, last_seq_no)) return false; // // sequence_number是当前的rtp包的序号,next_seq_no是接收端当前期望接收的rtp包序号, // 如果sequence_number 不等于next_seq_no,则认为序号next_seq_no到sequence_number之间的rtp包 都丢失了,并把这些序号的包标识为丢失。 // 语句all_packets_.emplace_back(next_seq_no)是构造一个ReceivedPacket对象并将其放入all_ackets_窗口中,ReceivedPacket的构造函数是重载的,当调用只有一个整形的构造函数时,表示此rtp包是丢失的。这些信息最终会被通过rtcp发送给rtp包的发送端 // for (; next_seq_no != sequence_number; ++next_seq_no) { if (!AddDeltaSize(0)) return false; if (include_lost_) all_packets_.emplace_back(next_seq_no); } } DeltaSize delta_size = (delta >= 0 && delta <= 0xff) ? 1 : 2; if (!AddDeltaSize(delta_size)) return false; received_packets_.emplace_back(sequence_number, delta); if (include_lost_) all_packets_.emplace_back(sequence_number, delta); last_timestamp_us_ += delta * kDeltaScaleFactor; if (include_timestamps_) { size_bytes_ += delta_size; } return true; }

     

    rtcp包的发送。ModuleProcessThread线程会大约50ms调用一次RemoteEstimatorProxy类的SendPeriodicFeedbacks函数来发送rtcp包,但是真正发送rtcp包的时间间隔会比50ms要小,原因是单个RtcpPacket包有大小限制,如果需要反馈的信息过长,那么会被分为多个片段,也就是多个rtcp包来发送,

    void RTCPSender::SendCombinedRtcpPacket( std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets) { size_t max_packet_size; uint32_t ssrc; { rtc::CritScope lock(&critical_section_rtcp_sender_); if (method_ == RtcpMode::kOff) { RTC_LOG(LS_WARNING) << "Can't send rtcp if it is disabled."; return; } max_packet_size = max_packet_size_; ssrc = ssrc_; } RTC_DCHECK_LE(max_packet_size, IP_PACKET_SIZE); auto callback = [&](rtc::ArrayView<const uint8_t> packet) { // // 如果rtcp_packets过长,这个lambda函数将会被多次调用。 // if (transport_->SendRtcp(packet.data(), packet.size())) { if (event_log_) event_log_->Log(std::make_unique<RtcEventRtcpPacketOutgoing>(packet)); } }; PacketSender sender(callback, max_packet_size); for (auto& rtcp_packet : rtcp_packets) { rtcp_packet->SetSenderSsrc(ssrc); sender.AppendPacket(*rtcp_packet); } sender.Send();

    其中的callback函数声明如下:

    // Callback used to signal that an RTCP packet is ready. Note that this may // not contain all data in this RtcpPacket; if a packet cannot fit in // max_length bytes, it will be fragmented and multiple calls to this // callback will be made. using PacketReadyCallback = rtc::FunctionView<void(rtc::ArrayView<const uint8_t> packet)>;

     

    重传。当发送端接收到rtcp时,将会回调到RtpVideoSender类中的OnPacketFeedbackVector函数,ReSendPacket函数是重发指定序号的rtp包,相关代码片段如下:

    for (const auto& kv : early_loss_detected_per_ssrc) { const uint32_t ssrc = kv.first; auto it = ssrc_to_rtp_module_.find(ssrc); RTC_DCHECK(it != ssrc_to_rtp_module_.end()); RTPSender* rtp_sender = it->second->RtpSender(); for (uint16_t sequence_number : kv.second) { rtp_sender->ReSendPacket(sequence_number); } } }

    往往测试分析webrtc代码都是在内网环境进行,而内网环境的网络状况都是相当好的,所以不会触发到ReSendPacket。为观察丢包的处理流程,可以通过一些限速工具来制造丢包的情况。

    Processed: 0.012, SQL: 12