/* * Copyright (c) 2026 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "video/timing/simulator/rtcp_rtt_calculator.h" #include #include #include #include "absl/container/flat_hash_map.h" #include "api/sequence_checker.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "modules/rtp_rtcp/source/ntp_time_util.h" #include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h" #include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h" #include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" #include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" namespace webrtc::video_timing_simulator { RtcpRttCalculator::RtcpRttCalculator() = default; RtcpRttCalculator::~RtcpRttCalculator() { RTC_DCHECK_RUN_ON(&sequence_checker_); } void RtcpRttCalculator::OnOutgoingSenderReport(const rtcp::SenderReport& sr, Timestamp now) { RTC_DCHECK_RUN_ON(&sequence_checker_); RTC_DCHECK(now.IsFinite()); CleanOldReports(now); // https://www.rfc-editor.org/info/rfc3550/#section-6.4.1 uint32_t compact_ntp = CompactNtp(sr.ntp()); if (compact_ntp == 0) { return; } outgoing_srs_[{sr.sender_ssrc(), compact_ntp}] = SentReportValue{.sent_time = now}; } void RtcpRttCalculator::OnOutgoingExtendedReports( const rtcp::ExtendedReports& xr, Timestamp now) { RTC_DCHECK_RUN_ON(&sequence_checker_); RTC_DCHECK(now.IsFinite()); CleanOldReports(now); if (!xr.rrtr().has_value()) { return; } // https://www.rfc-editor.org/info/rfc3611/#section-4.4 uint32_t compact_ntp = CompactNtp(xr.rrtr()->ntp()); if (compact_ntp == 0) { return; } outgoing_xrs_[{xr.sender_ssrc(), compact_ntp}] = SentReportValue{.sent_time = now}; } std::vector RtcpRttCalculator::OnIncomingSenderReport( const rtcp::SenderReport& sr, Timestamp now) { RTC_DCHECK_RUN_ON(&sequence_checker_); RTC_DCHECK(now.IsFinite()); CleanOldReports(now); return ProcessReportBlocks(sr.report_blocks(), now); } std::vector RtcpRttCalculator::OnIncomingReceiverReport( const rtcp::ReceiverReport& rr, Timestamp now) { RTC_DCHECK_RUN_ON(&sequence_checker_); RTC_DCHECK(now.IsFinite()); CleanOldReports(now); return ProcessReportBlocks(rr.report_blocks(), now); } std::vector RtcpRttCalculator::OnIncomingExtendedReports( const rtcp::ExtendedReports& xr, Timestamp now) { RTC_DCHECK_RUN_ON(&sequence_checker_); RTC_DCHECK(now.IsFinite()); CleanOldReports(now); std::vector rtt_samples; rtt_samples.reserve(xr.dlrr().sub_blocks().size()); for (const auto& block : xr.dlrr().sub_blocks()) { uint32_t sender_ssrc = block.ssrc; uint32_t last_rr = block.last_rr; // (Quotes from https://www.rfc-editor.org/info/rfc3611/#section-4.5) // "If no such block has been received, the field is set to zero." if (last_rr == 0) { continue; } if (auto it = outgoing_xrs_.find({sender_ssrc, last_rr}); it != outgoing_xrs_.end()) { TimeDelta delay_since_last_rr = CompactNtpIntervalToTimeDelta(block.delay_since_last_rr); // "It calculates the total round-trip time A-LRR using the" // "last RR timestamp (LRR) field, and then subtracting this field to" // "leave the round-trip propagation delay as A-LRR-DLRR." TimeDelta rtt = now - it->second.sent_time - delay_since_last_rr; if (rtt <= TimeDelta::Zero()) { RTC_LOG(LS_INFO) << "Ignoring non-positive RTT: " << rtt.ms() << "ms (now: " << now.ms() << ", sent: " << it->second.sent_time.ms() << ", delay_since_last_rr: " << delay_since_last_rr.ms() << ")"; continue; } rtt_samples.push_back(rtt); } } return rtt_samples; } std::vector RtcpRttCalculator::ProcessReportBlocks( const std::vector& report_blocks, Timestamp now) { RTC_DCHECK_RUN_ON(&sequence_checker_); RTC_DCHECK(now.IsFinite()); std::vector rtt_samples; rtt_samples.reserve(report_blocks.size()); for (const auto& block : report_blocks) { uint32_t sender_ssrc = block.source_ssrc(); uint32_t last_sr = block.last_sr(); // (Quotes from https://www.rfc-editor.org/info/rfc3550/#section-6.4.1) if (last_sr == 0) { // "If no SR has been received yet, the field is set to zero." continue; } if (auto it = outgoing_srs_.find({sender_ssrc, last_sr}); it != outgoing_srs_.end()) { TimeDelta delay_since_last_sr = CompactNtpIntervalToTimeDelta(block.delay_since_last_sr()); // "It calculates the total round-trip time A-LSR using the" // "last SR timestamp (LSR) field, and then subtracting this field to" // "leave the round-trip propagation delay as (A - LSR - DLSR)." TimeDelta rtt = now - it->second.sent_time - delay_since_last_sr; if (rtt <= TimeDelta::Zero()) { RTC_LOG(LS_INFO) << "Ignoring non-positive RTT: " << rtt.ms() << "ms (now: " << now.ms() << ", sent: " << it->second.sent_time.ms() << ", delay_since_last_sr: " << delay_since_last_sr.ms() << ")"; continue; } rtt_samples.push_back(rtt); } } return rtt_samples; } void RtcpRttCalculator::CleanOldReports(Timestamp now) { RTC_DCHECK_RUN_ON(&sequence_checker_); RTC_DCHECK(now.IsFinite()); constexpr TimeDelta kCleanupTimeout = TimeDelta::Minutes(1); absl::erase_if(outgoing_srs_, [&](const auto& kv) { return now - kv.second.sent_time > kCleanupTimeout; }); absl::erase_if(outgoing_xrs_, [&](const auto& kv) { return now - kv.second.sent_time > kCleanupTimeout; }); } } // namespace webrtc::video_timing_simulator