Source code
Revision control
Copy as Markdown
Other Tools
/*
* 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 <cstdint>
#include <optional>
#include <vector>
#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);
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;
}
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<TimeDelta> 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<TimeDelta> 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<TimeDelta> RtcpRttCalculator::OnIncomingExtendedReports(
const rtcp::ExtendedReports& xr,
Timestamp now) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_DCHECK(now.IsFinite());
CleanOldReports(now);
std::vector<TimeDelta> 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;
// "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<TimeDelta> RtcpRttCalculator::ProcessReportBlocks(
const std::vector<rtcp::ReportBlock>& report_blocks,
Timestamp now) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_DCHECK(now.IsFinite());
std::vector<TimeDelta> 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();
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