| /* |
| * 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 <algorithm> |
| |
| 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<double> 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<double>() / 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 |