diff options
Diffstat (limited to 'webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc')
-rw-r--r-- | webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc new file mode 100644 index 0000000000..d77447f1ea --- /dev/null +++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2015 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. + * + */ + +// Implementation of Network-Assisted Dynamic Adaptation's (NADA's) proposal. +// Version according to Draft Document (mentioned in references) +// http://tools.ietf.org/html/draft-zhu-rmcat-nada-06 +// From March 26, 2015. + +#include <math.h> +#include <algorithm> +#include <vector> + +#include "webrtc/base/common.h" +#include "webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h" +#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h" +#include "webrtc/modules/rtp_rtcp/interface/receive_statistics.h" + +namespace webrtc { +namespace testing { +namespace bwe { + +const int64_t NadaBweReceiver::kReceivingRateTimeWindowMs = 500; + +NadaBweReceiver::NadaBweReceiver(int flow_id) + : BweReceiver(flow_id, kReceivingRateTimeWindowMs), + clock_(0), + last_feedback_ms_(0), + recv_stats_(ReceiveStatistics::Create(&clock_)), + baseline_delay_ms_(10000), // Initialized as an upper bound. + delay_signal_ms_(0), + last_congestion_signal_ms_(0), + last_delays_index_(0), + exp_smoothed_delay_ms_(-1), + est_queuing_delay_signal_ms_(0) { +} + +NadaBweReceiver::~NadaBweReceiver() { +} + +void NadaBweReceiver::ReceivePacket(int64_t arrival_time_ms, + const MediaPacket& media_packet) { + const float kAlpha = 0.1f; // Used for exponential smoothing. + const int64_t kDelayLowThresholdMs = 50; // Referred as d_th. + const int64_t kDelayMaxThresholdMs = 400; // Referred as d_max. + + clock_.AdvanceTimeMilliseconds(arrival_time_ms - clock_.TimeInMilliseconds()); + recv_stats_->IncomingPacket(media_packet.header(), + media_packet.payload_size(), false); + // Refered as x_n. + int64_t delay_ms = arrival_time_ms - media_packet.sender_timestamp_ms(); + + // The min should be updated within the first 10 minutes. + if (clock_.TimeInMilliseconds() < 10 * 60 * 1000) { + baseline_delay_ms_ = std::min(baseline_delay_ms_, delay_ms); + } + + delay_signal_ms_ = delay_ms - baseline_delay_ms_; // Refered as d_n. + const int kMedian = ARRAY_SIZE(last_delays_ms_); + last_delays_ms_[(last_delays_index_++) % kMedian] = delay_signal_ms_; + int size = std::min(last_delays_index_, kMedian); + + int64_t median_filtered_delay_ms_ = MedianFilter(last_delays_ms_, size); + exp_smoothed_delay_ms_ = ExponentialSmoothingFilter( + median_filtered_delay_ms_, exp_smoothed_delay_ms_, kAlpha); + + if (exp_smoothed_delay_ms_ < kDelayLowThresholdMs) { + est_queuing_delay_signal_ms_ = exp_smoothed_delay_ms_; + } else if (exp_smoothed_delay_ms_ < kDelayMaxThresholdMs) { + est_queuing_delay_signal_ms_ = static_cast<int64_t>( + pow((static_cast<double>(kDelayMaxThresholdMs - + exp_smoothed_delay_ms_)) / + (kDelayMaxThresholdMs - kDelayLowThresholdMs), + 4.0) * + kDelayLowThresholdMs); + } else { + est_queuing_delay_signal_ms_ = 0; + } + + // Log received packet information. + BweReceiver::ReceivePacket(arrival_time_ms, media_packet); +} + +FeedbackPacket* NadaBweReceiver::GetFeedback(int64_t now_ms) { + const int64_t kPacketLossPenaltyMs = 1000; // Referred as d_L. + + if (now_ms - last_feedback_ms_ < 100) { + return NULL; + } + + float loss_fraction = RecentPacketLossRatio(); + + int64_t loss_signal_ms = + static_cast<int64_t>(loss_fraction * kPacketLossPenaltyMs + 0.5f); + int64_t congestion_signal_ms = est_queuing_delay_signal_ms_ + loss_signal_ms; + + float derivative = 0.0f; + if (last_feedback_ms_ > 0) { + derivative = (congestion_signal_ms - last_congestion_signal_ms_) / + static_cast<float>(now_ms - last_feedback_ms_); + } + last_feedback_ms_ = now_ms; + last_congestion_signal_ms_ = congestion_signal_ms; + + int64_t corrected_send_time_ms = 0L; + + if (!received_packets_.empty()) { + PacketIdentifierNode* latest = *(received_packets_.begin()); + corrected_send_time_ms = + latest->send_time_ms + now_ms - latest->arrival_time_ms; + } + + // Sends a tuple containing latest values of <d_hat_n, d_tilde_n, x_n, x'_n, + // R_r> and additional information. + return new NadaFeedback(flow_id_, now_ms * 1000, exp_smoothed_delay_ms_, + est_queuing_delay_signal_ms_, congestion_signal_ms, + derivative, RecentKbps(), corrected_send_time_ms); +} + +// If size is even, the median is the average of the two middlemost numbers. +int64_t NadaBweReceiver::MedianFilter(int64_t* last_delays_ms, int size) { + std::vector<int64_t> array_copy(last_delays_ms, last_delays_ms + size); + std::nth_element(array_copy.begin(), array_copy.begin() + size / 2, + array_copy.end()); + if (size % 2 == 1) { + // Typically, size = 5. For odd size values, right and left are equal. + return array_copy.at(size / 2); + } + int64_t right = array_copy.at(size / 2); + std::nth_element(array_copy.begin(), array_copy.begin() + (size - 1) / 2, + array_copy.end()); + int64_t left = array_copy.at((size - 1) / 2); + return (left + right + 1) / 2; +} + +int64_t NadaBweReceiver::ExponentialSmoothingFilter(int64_t new_value, + int64_t last_smoothed_value, + float alpha) { + if (last_smoothed_value < 0) { + return new_value; // Handling initial case. + } + return static_cast<int64_t>(alpha * new_value + + (1.0f - alpha) * last_smoothed_value + 0.5f); +} + +// Implementation according to Cisco's proposal by default. +NadaBweSender::NadaBweSender(int kbps, BitrateObserver* observer, Clock* clock) + : BweSender(kbps), // Referred as "Reference Rate" = R_n., + clock_(clock), + observer_(observer), + original_operating_mode_(true) { +} + +NadaBweSender::NadaBweSender(BitrateObserver* observer, Clock* clock) + : BweSender(kMinBitrateKbps), // Referred as "Reference Rate" = R_n. + clock_(clock), + observer_(observer), + original_operating_mode_(true) { +} + +NadaBweSender::~NadaBweSender() { +} + +int NadaBweSender::GetFeedbackIntervalMs() const { + return 100; +} + +void NadaBweSender::GiveFeedback(const FeedbackPacket& feedback) { + const NadaFeedback& fb = static_cast<const NadaFeedback&>(feedback); + + // Following parameters might be optimized. + const int64_t kQueuingDelayUpperBoundMs = 10; + const float kDerivativeUpperBound = 10.0f / min_feedback_delay_ms_; + // In the modified version, a higher kMinUpperBound allows a higher d_hat + // upper bound for calling AcceleratedRampUp. + const float kProportionalityDelayBits = 20.0f; + + int64_t now_ms = clock_->TimeInMilliseconds(); + float delta_s = now_ms - last_feedback_ms_; + last_feedback_ms_ = now_ms; + // Update delta_0. + min_feedback_delay_ms_ = + std::min(min_feedback_delay_ms_, static_cast<int64_t>(delta_s)); + + // Update RTT_0. + int64_t rtt_ms = now_ms - fb.latest_send_time_ms(); + min_round_trip_time_ms_ = std::min(min_round_trip_time_ms_, rtt_ms); + + // Independent limits for AcceleratedRampUp conditions variables: + // x_n, d_tilde and x'_n in the original implementation, plus + // d_hat and receiving_rate in the modified one. + // There should be no packet losses/marking, hence x_n == d_tilde. + if (original_operating_mode_) { + // Original if conditions and rate update. + if (fb.congestion_signal() == fb.est_queuing_delay_signal_ms() && + fb.est_queuing_delay_signal_ms() < kQueuingDelayUpperBoundMs && + fb.derivative() < kDerivativeUpperBound) { + AcceleratedRampUp(fb); + } else { + GradualRateUpdate(fb, delta_s, 1.0); + } + } else { + // Modified if conditions and rate update; new ramp down mode. + if (fb.congestion_signal() == fb.est_queuing_delay_signal_ms() && + fb.est_queuing_delay_signal_ms() < kQueuingDelayUpperBoundMs && + fb.exp_smoothed_delay_ms() < + kMinBitrateKbps / kProportionalityDelayBits && + fb.derivative() < kDerivativeUpperBound && + fb.receiving_rate() > kMinBitrateKbps) { + AcceleratedRampUp(fb); + } else if (fb.congestion_signal() > kMaxCongestionSignalMs || + fb.exp_smoothed_delay_ms() > kMaxCongestionSignalMs) { + AcceleratedRampDown(fb); + } else { + double bitrate_reference = + (2.0 * bitrate_kbps_) / (kMaxBitrateKbps + kMinBitrateKbps); + double smoothing_factor = pow(bitrate_reference, 0.75); + GradualRateUpdate(fb, delta_s, smoothing_factor); + } + } + + bitrate_kbps_ = std::min(bitrate_kbps_, kMaxBitrateKbps); + bitrate_kbps_ = std::max(bitrate_kbps_, kMinBitrateKbps); + + observer_->OnNetworkChanged(1000 * bitrate_kbps_, 0, rtt_ms); +} + +int64_t NadaBweSender::TimeUntilNextProcess() { + return 100; +} + +int NadaBweSender::Process() { + return 0; +} + +void NadaBweSender::AcceleratedRampUp(const NadaFeedback& fb) { + const int kMaxRampUpQueuingDelayMs = 50; // Referred as T_th. + const float kGamma0 = 0.5f; // Referred as gamma_0. + + float gamma = + std::min(kGamma0, static_cast<float>(kMaxRampUpQueuingDelayMs) / + (min_round_trip_time_ms_ + min_feedback_delay_ms_)); + + bitrate_kbps_ = static_cast<int>((1.0f + gamma) * fb.receiving_rate() + 0.5f); +} + +void NadaBweSender::AcceleratedRampDown(const NadaFeedback& fb) { + const float kGamma0 = 0.9f; + float gamma = 3.0f * kMaxCongestionSignalMs / + (fb.congestion_signal() + fb.exp_smoothed_delay_ms()); + gamma = std::min(gamma, kGamma0); + bitrate_kbps_ = gamma * fb.receiving_rate() + 0.5f; +} + +void NadaBweSender::GradualRateUpdate(const NadaFeedback& fb, + float delta_s, + double smoothing_factor) { + const float kTauOMs = 500.0f; // Referred as tau_o. + const float kEta = 2.0f; // Referred as eta. + const float kKappa = 1.0f; // Referred as kappa. + const float kReferenceDelayMs = 10.0f; // Referred as x_ref. + const float kPriorityWeight = 1.0f; // Referred as w. + + float x_hat = fb.congestion_signal() + kEta * kTauOMs * fb.derivative(); + + float kTheta = + kPriorityWeight * (kMaxBitrateKbps - kMinBitrateKbps) * kReferenceDelayMs; + + int original_increase = + static_cast<int>((kKappa * delta_s * + (kTheta - (bitrate_kbps_ - kMinBitrateKbps) * x_hat)) / + (kTauOMs * kTauOMs) + + 0.5f); + + bitrate_kbps_ = bitrate_kbps_ + smoothing_factor * original_increase; +} + +} // namespace bwe +} // namespace testing +} // namespace webrtc |