1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "cast/streaming/bandwidth_estimator.h"
6
7 #include <algorithm>
8
9 #include "util/osp_logging.h"
10 #include "util/saturate_cast.h"
11
12 namespace openscreen {
13 namespace cast {
14
15 using openscreen::operator<<; // For std::chrono::duration logging.
16
17 namespace {
18
19 // Converts units from |bytes| per |time_window| number of Clock ticks into
20 // bits-per-second.
ToClampedBitsPerSecond(int32_t bytes,Clock::duration time_window)21 int ToClampedBitsPerSecond(int32_t bytes, Clock::duration time_window) {
22 OSP_DCHECK_GT(time_window, Clock::duration::zero());
23
24 // Divide |bytes| by |time_window| and scale the units to bits per second.
25 constexpr int64_t kBitsPerByte = 8;
26 constexpr int64_t kClockTicksPerSecond =
27 Clock::to_duration(std::chrono::seconds(1)).count();
28 const int64_t bits = bytes * kBitsPerByte;
29 const int64_t bits_per_second =
30 (bits * kClockTicksPerSecond) / time_window.count();
31 return saturate_cast<int>(bits_per_second);
32 }
33
34 } // namespace
35
BandwidthEstimator(int max_packets_per_timeslice,Clock::duration timeslice_duration,Clock::time_point start_time)36 BandwidthEstimator::BandwidthEstimator(int max_packets_per_timeslice,
37 Clock::duration timeslice_duration,
38 Clock::time_point start_time)
39 : max_packets_per_history_window_(max_packets_per_timeslice *
40 kNumTimeslices),
41 history_window_(timeslice_duration * kNumTimeslices),
42 burst_history_(timeslice_duration, start_time),
43 feedback_history_(timeslice_duration, start_time) {
44 OSP_DCHECK_GT(max_packets_per_timeslice, 0);
45 OSP_DCHECK_GT(timeslice_duration, Clock::duration::zero());
46 }
47
48 BandwidthEstimator::~BandwidthEstimator() = default;
49
OnBurstComplete(int num_packets_sent,Clock::time_point when)50 void BandwidthEstimator::OnBurstComplete(int num_packets_sent,
51 Clock::time_point when) {
52 OSP_DCHECK_GE(num_packets_sent, 0);
53 burst_history_.Accumulate(num_packets_sent, when);
54 }
55
OnRtcpReceived(Clock::time_point arrival_time,Clock::duration estimated_round_trip_time)56 void BandwidthEstimator::OnRtcpReceived(
57 Clock::time_point arrival_time,
58 Clock::duration estimated_round_trip_time) {
59 OSP_DCHECK_GE(estimated_round_trip_time, Clock::duration::zero());
60 // Move forward the feedback history tracking timeline to include the latest
61 // moment a packet could have left the Sender.
62 feedback_history_.AdvanceToIncludeTime(arrival_time -
63 estimated_round_trip_time);
64 }
65
OnPayloadReceived(int payload_bytes_acknowledged,Clock::time_point ack_arrival_time,Clock::duration estimated_round_trip_time)66 void BandwidthEstimator::OnPayloadReceived(
67 int payload_bytes_acknowledged,
68 Clock::time_point ack_arrival_time,
69 Clock::duration estimated_round_trip_time) {
70 OSP_DCHECK_GE(payload_bytes_acknowledged, 0);
71 OSP_DCHECK_GE(estimated_round_trip_time, Clock::duration::zero());
72 // Track the bytes in terms of when the last packet was sent.
73 feedback_history_.Accumulate(payload_bytes_acknowledged,
74 ack_arrival_time - estimated_round_trip_time);
75 }
76
ComputeNetworkBandwidth() const77 int BandwidthEstimator::ComputeNetworkBandwidth() const {
78 // Determine whether the |burst_history_| time window overlaps with the
79 // |feedback_history_| time window by at least half. The time windows don't
80 // have to overlap entirely because the calculations are averaging all the
81 // measurements (i.e., recent typical behavior). Though, they should overlap
82 // by "enough" so that the measurements correlate "enough."
83 const Clock::time_point overlap_begin =
84 std::max(burst_history_.begin_time(), feedback_history_.begin_time());
85 const Clock::time_point overlap_end =
86 std::min(burst_history_.end_time(), feedback_history_.end_time());
87 if ((overlap_end - overlap_begin) < (history_window_ / 2)) {
88 return 0;
89 }
90
91 const int32_t num_packets_transmitted = burst_history_.Sum();
92 if (num_packets_transmitted <= 0) {
93 // Cannot estimate because there have been no transmissions recently.
94 return 0;
95 }
96 const Clock::duration transmit_duration = history_window_ *
97 num_packets_transmitted /
98 max_packets_per_history_window_;
99 const int32_t num_bytes_received = feedback_history_.Sum();
100 return ToClampedBitsPerSecond(num_bytes_received, transmit_duration);
101 }
102
103 // static
104 constexpr int BandwidthEstimator::kNumTimeslices;
105
FlowTracker(Clock::duration timeslice_duration,Clock::time_point begin_time)106 BandwidthEstimator::FlowTracker::FlowTracker(Clock::duration timeslice_duration,
107 Clock::time_point begin_time)
108 : timeslice_duration_(timeslice_duration), begin_time_(begin_time) {}
109
110 BandwidthEstimator::FlowTracker::~FlowTracker() = default;
111
AdvanceToIncludeTime(Clock::time_point until)112 void BandwidthEstimator::FlowTracker::AdvanceToIncludeTime(
113 Clock::time_point until) {
114 if (until < end_time()) {
115 return; // Not advancing.
116 }
117
118 // Step forward in time, at timeslice granularity.
119 const int64_t num_periods = 1 + (until - end_time()) / timeslice_duration_;
120 begin_time_ += num_periods * timeslice_duration_;
121
122 // Shift the ring elements, discarding N oldest timeslices, and creating N new
123 // ones initialized to zero.
124 const int shift_count = std::min<int64_t>(num_periods, kNumTimeslices);
125 for (int i = 0; i < shift_count; ++i) {
126 history_ring_[tail_++] = 0;
127 }
128 }
129
Accumulate(int32_t amount,Clock::time_point when)130 void BandwidthEstimator::FlowTracker::Accumulate(int32_t amount,
131 Clock::time_point when) {
132 if (when < begin_time_) {
133 return; // Ignore a data point that is already too old.
134 }
135
136 AdvanceToIncludeTime(when);
137
138 // Because of the AdvanceToIncludeTime() call just made, the offset/index
139 // calculations here are guaranteed to point to a valid element in the
140 // |history_ring_|.
141 const int64_t offset_from_first = (when - begin_time_) / timeslice_duration_;
142 const index_mod_256_t ring_index = tail_ + offset_from_first;
143 int32_t& timeslice = history_ring_[ring_index];
144 timeslice = saturate_cast<int32_t>(int64_t{timeslice} + amount);
145 }
146
Sum() const147 int32_t BandwidthEstimator::FlowTracker::Sum() const {
148 int64_t result = 0;
149 for (int32_t amount : history_ring_) {
150 result += amount;
151 }
152 return saturate_cast<int32_t>(result);
153 }
154
155 } // namespace cast
156 } // namespace openscreen
157