|  | /* | 
|  | *  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> | 
|  | #include <cstddef> | 
|  | #include <optional> | 
|  |  | 
|  | #include "api/units/data_rate.h" | 
|  | #include "api/units/data_size.h" | 
|  | #include "api/units/time_delta.h" | 
|  | #include "api/units/timestamp.h" | 
|  | #include "rtc_base/checks.h" | 
|  |  | 
|  | 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 |