1 /*
2 * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 *
10 */
11
12 // Implementation of Network-Assisted Dynamic Adaptation's (NADA's) proposal.
13 // Version according to Draft Document (mentioned in references)
14 // http://tools.ietf.org/html/draft-zhu-rmcat-nada-06
15 // From March 26, 2015.
16
17 #include <math.h>
18 #include <algorithm>
19 #include <vector>
20
21 #include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
22 #include "modules/remote_bitrate_estimator/test/estimators/nada.h"
23 #include "modules/rtp_rtcp/include/receive_statistics.h"
24 #include "rtc_base/arraysize.h"
25
26 namespace webrtc {
27 namespace testing {
28 namespace bwe {
29
30 namespace {
31 // Used as an upper bound for calling AcceleratedRampDown.
32 const float kMaxCongestionSignalMs =
33 40.0f + NadaBweSender::kMinNadaBitrateKbps / 15;
34 } // namespace
35
36 const int NadaBweSender::kMinNadaBitrateKbps = 50;
37 const int64_t NadaBweReceiver::kReceivingRateTimeWindowMs = 500;
38
NadaBweReceiver(int flow_id)39 NadaBweReceiver::NadaBweReceiver(int flow_id)
40 : BweReceiver(flow_id, kReceivingRateTimeWindowMs),
41 clock_(0),
42 last_feedback_ms_(0),
43 recv_stats_(ReceiveStatistics::Create(&clock_)),
44 baseline_delay_ms_(10000), // Initialized as an upper bound.
45 delay_signal_ms_(0),
46 last_congestion_signal_ms_(0),
47 last_delays_index_(0),
48 exp_smoothed_delay_ms_(-1),
49 est_queuing_delay_signal_ms_(0) {
50 }
51
~NadaBweReceiver()52 NadaBweReceiver::~NadaBweReceiver() {
53 }
54
ReceivePacket(int64_t arrival_time_ms,const MediaPacket & media_packet)55 void NadaBweReceiver::ReceivePacket(int64_t arrival_time_ms,
56 const MediaPacket& media_packet) {
57 const float kAlpha = 0.1f; // Used for exponential smoothing.
58 const int64_t kDelayLowThresholdMs = 50; // Referred as d_th.
59 const int64_t kDelayMaxThresholdMs = 400; // Referred as d_max.
60
61 clock_.AdvanceTimeMilliseconds(arrival_time_ms - clock_.TimeInMilliseconds());
62 recv_stats_->IncomingPacket(media_packet.header(),
63 media_packet.payload_size(), false);
64 // Refered as x_n.
65 int64_t delay_ms = arrival_time_ms - media_packet.sender_timestamp_ms();
66
67 // The min should be updated within the first 10 minutes.
68 if (clock_.TimeInMilliseconds() < 10 * 60 * 1000) {
69 baseline_delay_ms_ = std::min(baseline_delay_ms_, delay_ms);
70 }
71
72 delay_signal_ms_ = delay_ms - baseline_delay_ms_; // Refered as d_n.
73 const int kMedian = arraysize(last_delays_ms_);
74 last_delays_ms_[(last_delays_index_++) % kMedian] = delay_signal_ms_;
75 int size = std::min(last_delays_index_, kMedian);
76
77 int64_t median_filtered_delay_ms_ = MedianFilter(last_delays_ms_, size);
78 exp_smoothed_delay_ms_ = ExponentialSmoothingFilter(
79 median_filtered_delay_ms_, exp_smoothed_delay_ms_, kAlpha);
80
81 if (exp_smoothed_delay_ms_ < kDelayLowThresholdMs) {
82 est_queuing_delay_signal_ms_ = exp_smoothed_delay_ms_;
83 } else if (exp_smoothed_delay_ms_ < kDelayMaxThresholdMs) {
84 est_queuing_delay_signal_ms_ = static_cast<int64_t>(
85 pow((static_cast<double>(kDelayMaxThresholdMs -
86 exp_smoothed_delay_ms_)) /
87 (kDelayMaxThresholdMs - kDelayLowThresholdMs),
88 4.0) *
89 kDelayLowThresholdMs);
90 } else {
91 est_queuing_delay_signal_ms_ = 0;
92 }
93
94 // Log received packet information.
95 BweReceiver::ReceivePacket(arrival_time_ms, media_packet);
96 }
97
GetFeedback(int64_t now_ms)98 FeedbackPacket* NadaBweReceiver::GetFeedback(int64_t now_ms) {
99 const int64_t kPacketLossPenaltyMs = 1000; // Referred as d_L.
100
101 if (now_ms - last_feedback_ms_ < 100) {
102 return NULL;
103 }
104
105 float loss_fraction = RecentPacketLossRatio();
106
107 int64_t loss_signal_ms =
108 static_cast<int64_t>(loss_fraction * kPacketLossPenaltyMs + 0.5f);
109 int64_t congestion_signal_ms = est_queuing_delay_signal_ms_ + loss_signal_ms;
110
111 float derivative = 0.0f;
112 if (last_feedback_ms_ > 0) {
113 derivative = (congestion_signal_ms - last_congestion_signal_ms_) /
114 static_cast<float>(now_ms - last_feedback_ms_);
115 }
116 last_feedback_ms_ = now_ms;
117 last_congestion_signal_ms_ = congestion_signal_ms;
118
119 int64_t corrected_send_time_ms = 0L;
120
121 if (!received_packets_.empty()) {
122 PacketIdentifierNode* latest = *(received_packets_.begin());
123 corrected_send_time_ms =
124 latest->send_time_ms + now_ms - latest->arrival_time_ms;
125 }
126
127 // Sends a tuple containing latest values of <d_hat_n, d_tilde_n, x_n, x'_n,
128 // R_r> and additional information.
129 return new NadaFeedback(flow_id_, now_ms * 1000, exp_smoothed_delay_ms_,
130 est_queuing_delay_signal_ms_, congestion_signal_ms,
131 derivative, RecentKbps(), corrected_send_time_ms);
132 }
133
134 // If size is even, the median is the average of the two middlemost numbers.
MedianFilter(int64_t * last_delays_ms,int size)135 int64_t NadaBweReceiver::MedianFilter(int64_t* last_delays_ms, int size) {
136 std::vector<int64_t> array_copy(last_delays_ms, last_delays_ms + size);
137 std::nth_element(array_copy.begin(), array_copy.begin() + size / 2,
138 array_copy.end());
139 if (size % 2 == 1) {
140 // Typically, size = 5. For odd size values, right and left are equal.
141 return array_copy.at(size / 2);
142 }
143 int64_t right = array_copy.at(size / 2);
144 std::nth_element(array_copy.begin(), array_copy.begin() + (size - 1) / 2,
145 array_copy.end());
146 int64_t left = array_copy.at((size - 1) / 2);
147 return (left + right + 1) / 2;
148 }
149
ExponentialSmoothingFilter(int64_t new_value,int64_t last_smoothed_value,float alpha)150 int64_t NadaBweReceiver::ExponentialSmoothingFilter(int64_t new_value,
151 int64_t last_smoothed_value,
152 float alpha) {
153 if (last_smoothed_value < 0) {
154 return new_value; // Handling initial case.
155 }
156 return static_cast<int64_t>(alpha * new_value +
157 (1.0f - alpha) * last_smoothed_value + 0.5f);
158 }
159
160 // Implementation according to Cisco's proposal by default.
NadaBweSender(int kbps,BitrateObserver * observer,Clock * clock)161 NadaBweSender::NadaBweSender(int kbps, BitrateObserver* observer, Clock* clock)
162 : BweSender(kbps), // Referred as "Reference Rate" = R_n.,
163 clock_(clock),
164 observer_(observer),
165 original_operating_mode_(true) {
166 }
167
NadaBweSender(BitrateObserver * observer,Clock * clock)168 NadaBweSender::NadaBweSender(BitrateObserver* observer, Clock* clock)
169 : BweSender(kMinNadaBitrateKbps), // Referred as "Reference Rate" = R_n.
170 clock_(clock),
171 observer_(observer),
172 original_operating_mode_(true) {}
173
~NadaBweSender()174 NadaBweSender::~NadaBweSender() {
175 }
176
GetFeedbackIntervalMs() const177 int NadaBweSender::GetFeedbackIntervalMs() const {
178 return 100;
179 }
180
GiveFeedback(const FeedbackPacket & feedback)181 void NadaBweSender::GiveFeedback(const FeedbackPacket& feedback) {
182 const NadaFeedback& fb = static_cast<const NadaFeedback&>(feedback);
183
184 // Following parameters might be optimized.
185 const int64_t kQueuingDelayUpperBoundMs = 10;
186 const float kDerivativeUpperBound =
187 10.0f / std::max<int64_t>(1, min_feedback_delay_ms_);
188 // In the modified version, a higher kMinUpperBound allows a higher d_hat
189 // upper bound for calling AcceleratedRampUp.
190 const float kProportionalityDelayBits = 20.0f;
191
192 int64_t now_ms = clock_->TimeInMilliseconds();
193 float delta_s = now_ms - last_feedback_ms_;
194 last_feedback_ms_ = now_ms;
195 // Update delta_0.
196 min_feedback_delay_ms_ =
197 std::min(min_feedback_delay_ms_, static_cast<int64_t>(delta_s));
198
199 // Update RTT_0.
200 int64_t rtt_ms = now_ms - fb.latest_send_time_ms();
201 min_round_trip_time_ms_ = std::min(min_round_trip_time_ms_, rtt_ms);
202
203 // Independent limits for AcceleratedRampUp conditions variables:
204 // x_n, d_tilde and x'_n in the original implementation, plus
205 // d_hat and receiving_rate in the modified one.
206 // There should be no packet losses/marking, hence x_n == d_tilde.
207 if (original_operating_mode_) {
208 // Original if conditions and rate update.
209 if (fb.congestion_signal() == fb.est_queuing_delay_signal_ms() &&
210 fb.est_queuing_delay_signal_ms() < kQueuingDelayUpperBoundMs &&
211 fb.derivative() < kDerivativeUpperBound) {
212 AcceleratedRampUp(fb);
213 } else {
214 GradualRateUpdate(fb, delta_s, 1.0);
215 }
216 } else {
217 // Modified if conditions and rate update; new ramp down mode.
218 if (fb.congestion_signal() == fb.est_queuing_delay_signal_ms() &&
219 fb.est_queuing_delay_signal_ms() < kQueuingDelayUpperBoundMs &&
220 fb.exp_smoothed_delay_ms() <
221 kMinNadaBitrateKbps / kProportionalityDelayBits &&
222 fb.derivative() < kDerivativeUpperBound &&
223 fb.receiving_rate() > kMinNadaBitrateKbps) {
224 AcceleratedRampUp(fb);
225 } else if (fb.congestion_signal() > kMaxCongestionSignalMs ||
226 fb.exp_smoothed_delay_ms() > kMaxCongestionSignalMs) {
227 AcceleratedRampDown(fb);
228 } else {
229 double bitrate_reference =
230 (2.0 * bitrate_kbps_) / (kMaxBitrateKbps + kMinNadaBitrateKbps);
231 double smoothing_factor = pow(bitrate_reference, 0.75);
232 GradualRateUpdate(fb, delta_s, smoothing_factor);
233 }
234 }
235
236 bitrate_kbps_ = std::min(bitrate_kbps_, kMaxBitrateKbps);
237 bitrate_kbps_ = std::max(bitrate_kbps_, kMinNadaBitrateKbps);
238
239 observer_->OnNetworkChanged(1000 * bitrate_kbps_, 0, rtt_ms);
240 }
241
TimeUntilNextProcess()242 int64_t NadaBweSender::TimeUntilNextProcess() {
243 return 100;
244 }
245
Process()246 void NadaBweSender::Process() {
247 }
248
AcceleratedRampUp(const NadaFeedback & fb)249 void NadaBweSender::AcceleratedRampUp(const NadaFeedback& fb) {
250 const int kMaxRampUpQueuingDelayMs = 50; // Referred as T_th.
251 const float kGamma0 = 0.5f; // Referred as gamma_0.
252
253 float gamma =
254 std::min(kGamma0, static_cast<float>(kMaxRampUpQueuingDelayMs) /
255 (min_round_trip_time_ms_ + min_feedback_delay_ms_));
256
257 bitrate_kbps_ = static_cast<int>((1.0f + gamma) * fb.receiving_rate() + 0.5f);
258 }
259
AcceleratedRampDown(const NadaFeedback & fb)260 void NadaBweSender::AcceleratedRampDown(const NadaFeedback& fb) {
261 const float kGamma0 = 0.9f;
262 float gamma = 3.0f * kMaxCongestionSignalMs /
263 (fb.congestion_signal() + fb.exp_smoothed_delay_ms());
264 gamma = std::min(gamma, kGamma0);
265 bitrate_kbps_ = gamma * fb.receiving_rate() + 0.5f;
266 }
267
GradualRateUpdate(const NadaFeedback & fb,float delta_s,double smoothing_factor)268 void NadaBweSender::GradualRateUpdate(const NadaFeedback& fb,
269 float delta_s,
270 double smoothing_factor) {
271 const float kTauOMs = 500.0f; // Referred as tau_o.
272 const float kEta = 2.0f; // Referred as eta.
273 const float kKappa = 1.0f; // Referred as kappa.
274 const float kReferenceDelayMs = 10.0f; // Referred as x_ref.
275 const float kPriorityWeight = 1.0f; // Referred as w.
276
277 float x_hat = fb.congestion_signal() + kEta * kTauOMs * fb.derivative();
278
279 float kTheta = kPriorityWeight * (kMaxBitrateKbps - kMinNadaBitrateKbps) *
280 kReferenceDelayMs;
281
282 int original_increase = static_cast<int>(
283 (kKappa * delta_s *
284 (kTheta - (bitrate_kbps_ - kMinNadaBitrateKbps) * x_hat)) /
285 (kTauOMs * kTauOMs) +
286 0.5f);
287
288 bitrate_kbps_ = bitrate_kbps_ + smoothing_factor * original_increase;
289 }
290
291 } // namespace bwe
292 } // namespace testing
293 } // namespace webrtc
294