/* * Copyright (c) 2024 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/rate_utilization_tracker.h" #include namespace webrtc { RateUtilizationTracker::RateUtilizationTracker( size_t max_num_encoded_data_points, TimeDelta max_duration) : max_data_points_(max_num_encoded_data_points), max_duration_(max_duration), current_rate_(DataRate::Zero()) { RTC_CHECK_GE(max_num_encoded_data_points, 0); RTC_CHECK_GT(max_duration, TimeDelta::Zero()); } void RateUtilizationTracker::OnDataRateChanged(DataRate rate, Timestamp time) { current_rate_ = rate; if (data_points_.empty()) { // First entry should be contain first produced data, so just return after // setting `current_rate_`. return; } else { RateUsageUpdate& last_data_point = data_points_.back(); RTC_CHECK_GE(time, last_data_point.time); if (last_data_point.time == time) { last_data_point.target_rate = rate; } else { data_points_.push_back({.time = time, .target_rate = rate, .produced_data = DataSize::Zero()}); } } CullOldData(time); } void RateUtilizationTracker::OnDataProduced(DataSize size, Timestamp time) { if (data_points_.empty()) { data_points_.push_back( {.time = time, .target_rate = current_rate_, .produced_data = size}); } else { RateUsageUpdate& last_data_point = data_points_.back(); RTC_CHECK_GE(time, last_data_point.time); if (last_data_point.time == time) { last_data_point.produced_data += size; } else { data_points_.push_back( {.time = time, .target_rate = current_rate_, .produced_data = size}); } } CullOldData(time); } std::optional RateUtilizationTracker::GetRateUtilizationFactor( Timestamp time) const { if (data_points_.empty()) { return std::nullopt; } RTC_CHECK_GE(time, data_points_.back().time); DataSize allocated_send_data_size = DataSize::Zero(); DataSize total_produced_data = DataSize::Zero(); // Keep track of the last time data was produced - how much it was and how // much rate budget has been allocated since then. DataSize data_allocated_for_last_data = DataSize::Zero(); DataSize size_of_last_data = DataSize::Zero(); RTC_DCHECK(!data_points_.front().produced_data.IsZero()); for (size_t i = 0; i < data_points_.size(); ++i) { const RateUsageUpdate& update = data_points_[i]; total_produced_data += update.produced_data; DataSize allocated_since_previous_data_point = i == 0 ? DataSize::Zero() : (update.time - data_points_[i - 1].time) * data_points_[i - 1].target_rate; allocated_send_data_size += allocated_since_previous_data_point; if (update.produced_data.IsZero()) { // Just a rate update past the last seen produced data. data_allocated_for_last_data = std::min(size_of_last_data, data_allocated_for_last_data + allocated_since_previous_data_point); } else { // A newer data point with produced data, reset accumulator for rate // allocated past the last data point. size_of_last_data = update.produced_data; data_allocated_for_last_data = DataSize::Zero(); } } if (allocated_send_data_size.IsZero() && current_rate_.IsZero()) { // No allocated rate across all of the data points, ignore. return std::nullopt; } // Calculate the rate past the very last data point until the polling time. const RateUsageUpdate& last_update = data_points_.back(); DataSize allocated_since_last_data_point = (time - last_update.time) * last_update.target_rate; // If the last produced data packet is larger than the accumulated rate // allocation window since then, use that data point size instead (minus any // data rate accumulated in rate updates after that data point was produced). allocated_send_data_size += std::max(allocated_since_last_data_point, size_of_last_data - data_allocated_for_last_data); return total_produced_data.bytes() / allocated_send_data_size.bytes(); } void RateUtilizationTracker::CullOldData(Timestamp time) { // Remove data points that are either too old, exceed the limit of number of // data points - and make sure the first entry in the list contains actual // data produced since we calculate send usage since that time. // We don't allow negative times so always start window at absolute time >= 0. const Timestamp oldest_included_time = time.ms() > max_duration_.ms() ? time - max_duration_ : Timestamp::Zero(); while (!data_points_.empty() && (data_points_.front().time < oldest_included_time || data_points_.size() > max_data_points_ || data_points_.front().produced_data.IsZero())) { data_points_.pop_front(); } } } // namespace webrtc