diff --git a/webrtc/base/analytics/percentile_filter.h b/webrtc/base/analytics/percentile_filter.h index b3c8f8d177..7cfba5ad00 100644 --- a/webrtc/base/analytics/percentile_filter.h +++ b/webrtc/base/analytics/percentile_filter.h @@ -33,9 +33,10 @@ class PercentileFilter { // the size of the container. void Insert(const T& value); - // Remove one observation. The complexity of this operation is logarithmic in - // the size of the container. - void Erase(const T& value); + // Remove one observation or return false if |value| doesn't exist in the + // container. The complexity of this operation is logarithmic in the size of + // the container. + bool Erase(const T& value); // Get the percentile value. The complexity of this operation is constant. T GetPercentileValue() const; @@ -76,11 +77,11 @@ void PercentileFilter::Insert(const T& value) { } template -void PercentileFilter::Erase(const T& value) { +bool PercentileFilter::Erase(const T& value) { typename std::multiset::const_iterator it = set_.lower_bound(value); // Ignore erase operation if the element is not present in the current set. if (it == set_.end() || *it != value) - return; + return false; if (it == percentile_it_) { // If same iterator, update to the following element. Index is not // affected. @@ -92,6 +93,7 @@ void PercentileFilter::Erase(const T& value) { --percentile_index_; } UpdatePercentileIterator(); + return true; } template diff --git a/webrtc/base/analytics/percentile_filter_unittest.cc b/webrtc/base/analytics/percentile_filter_unittest.cc index 98168fd27b..02b7d091cb 100644 --- a/webrtc/base/analytics/percentile_filter_unittest.cc +++ b/webrtc/base/analytics/percentile_filter_unittest.cc @@ -84,15 +84,18 @@ TEST(PercentileFilterTest, MedianFilterUnsigned) { TEST_P(PercentileFilterTest, EmptyFilter) { EXPECT_EQ(0, filter_.GetPercentileValue()); filter_.Insert(3); - filter_.Erase(3); + bool success = filter_.Erase(3); + EXPECT_TRUE(success); EXPECT_EQ(0, filter_.GetPercentileValue()); } TEST_P(PercentileFilterTest, EraseNonExistingElement) { - filter_.Erase(3); + bool success = filter_.Erase(3); + EXPECT_FALSE(success); EXPECT_EQ(0, filter_.GetPercentileValue()); filter_.Insert(4); - filter_.Erase(3); + success = filter_.Erase(3); + EXPECT_FALSE(success); EXPECT_EQ(4, filter_.GetPercentileValue()); } diff --git a/webrtc/modules/BUILD.gn b/webrtc/modules/BUILD.gn index 4abd193343..ca519e7c04 100644 --- a/webrtc/modules/BUILD.gn +++ b/webrtc/modules/BUILD.gn @@ -394,6 +394,7 @@ if (rtc_include_tests) { "congestion_controller/delay_based_bwe_unittest.cc", "congestion_controller/delay_based_bwe_unittest_helper.cc", "congestion_controller/delay_based_bwe_unittest_helper.h", + "congestion_controller/median_slope_estimator_unittest.cc", "congestion_controller/probe_bitrate_estimator_unittest.cc", "congestion_controller/probe_controller_unittest.cc", "congestion_controller/probing_interval_estimator_unittest.cc", diff --git a/webrtc/modules/congestion_controller/BUILD.gn b/webrtc/modules/congestion_controller/BUILD.gn index 09b33881c8..d2dc680adb 100644 --- a/webrtc/modules/congestion_controller/BUILD.gn +++ b/webrtc/modules/congestion_controller/BUILD.gn @@ -14,6 +14,8 @@ rtc_static_library("congestion_controller") { "delay_based_bwe.cc", "delay_based_bwe.h", "include/congestion_controller.h", + "median_slope_estimator.cc", + "median_slope_estimator.h", "probe_bitrate_estimator.cc", "probe_bitrate_estimator.h", "probe_controller.cc", @@ -41,6 +43,7 @@ rtc_static_library("congestion_controller") { } deps = [ + "../../base:rtc_analytics", "../bitrate_controller", "../pacing", ] diff --git a/webrtc/modules/congestion_controller/delay_based_bwe.cc b/webrtc/modules/congestion_controller/delay_based_bwe.cc index 04dadc489c..bb63f353da 100644 --- a/webrtc/modules/congestion_controller/delay_based_bwe.cc +++ b/webrtc/modules/congestion_controller/delay_based_bwe.cc @@ -21,6 +21,7 @@ #include "webrtc/modules/congestion_controller/include/congestion_controller.h" #include "webrtc/modules/pacing/paced_sender.h" #include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" +#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h" #include "webrtc/system_wrappers/include/field_trial.h" #include "webrtc/system_wrappers/include/metrics.h" #include "webrtc/typedefs.h" @@ -39,12 +40,18 @@ constexpr uint32_t kFixedSsrc = 0; constexpr int kInitialRateWindowMs = 500; constexpr int kRateWindowMs = 150; +// Parameters for linear least squares fit of regression line to noisy data. constexpr size_t kDefaultTrendlineWindowSize = 15; constexpr double kDefaultTrendlineSmoothingCoeff = 0.9; constexpr double kDefaultTrendlineThresholdGain = 4.0; +// Parameters for Theil-Sen robust fitting of line to noisy data. +constexpr size_t kDefaultMedianSlopeWindowSize = 20; +constexpr double kDefaultMedianSlopeThresholdGain = 4.0; + const char kBitrateEstimateExperiment[] = "WebRTC-ImprovedBitrateEstimate"; const char kBweTrendlineFilterExperiment[] = "WebRTC-BweTrendlineFilter"; +const char kBweMedianSlopeFilterExperiment[] = "WebRTC-BweMedianSlopeFilter"; bool BitrateEstimateExperimentIsEnabled() { return webrtc::field_trial::FindFullName(kBitrateEstimateExperiment) == @@ -58,16 +65,27 @@ bool TrendlineFilterExperimentIsEnabled() { return experiment_string.find("Enabled") == 0; } -bool ReadTrendlineFilterExperimentParameters(size_t* window_points, +bool MedianSlopeFilterExperimentIsEnabled() { + std::string experiment_string = + webrtc::field_trial::FindFullName(kBweMedianSlopeFilterExperiment); + // The experiment is enabled iff the field trial string begins with "Enabled". + return experiment_string.find("Enabled") == 0; +} + +bool ReadTrendlineFilterExperimentParameters(size_t* window_size, double* smoothing_coef, double* threshold_gain) { RTC_DCHECK(TrendlineFilterExperimentIsEnabled()); + RTC_DCHECK(!MedianSlopeFilterExperimentIsEnabled()); + RTC_DCHECK(window_size != nullptr); + RTC_DCHECK(smoothing_coef != nullptr); + RTC_DCHECK(threshold_gain != nullptr); std::string experiment_string = webrtc::field_trial::FindFullName(kBweTrendlineFilterExperiment); int parsed_values = sscanf(experiment_string.c_str(), "Enabled-%zu,%lf,%lf", - window_points, smoothing_coef, threshold_gain); + window_size, smoothing_coef, threshold_gain); if (parsed_values == 3) { - RTC_CHECK_GT(*window_points, 1) << "Need at least 2 points to fit a line."; + RTC_CHECK_GT(*window_size, 1) << "Need at least 2 points to fit a line."; RTC_CHECK(0 <= *smoothing_coef && *smoothing_coef <= 1) << "Coefficient needs to be between 0 and 1 for weighted average."; RTC_CHECK_GT(*threshold_gain, 0) << "Threshold gain needs to be positive."; @@ -75,12 +93,33 @@ bool ReadTrendlineFilterExperimentParameters(size_t* window_points, } LOG(LS_WARNING) << "Failed to parse parameters for BweTrendlineFilter " "experiment from field trial string. Using default."; - *window_points = kDefaultTrendlineWindowSize; + *window_size = kDefaultTrendlineWindowSize; *smoothing_coef = kDefaultTrendlineSmoothingCoeff; *threshold_gain = kDefaultTrendlineThresholdGain; return false; } +bool ReadMedianSlopeFilterExperimentParameters(size_t* window_size, + double* threshold_gain) { + RTC_DCHECK(!TrendlineFilterExperimentIsEnabled()); + RTC_DCHECK(MedianSlopeFilterExperimentIsEnabled()); + RTC_DCHECK(window_size != nullptr); + RTC_DCHECK(threshold_gain != nullptr); + std::string experiment_string = + webrtc::field_trial::FindFullName(kBweMedianSlopeFilterExperiment); + int parsed_values = sscanf(experiment_string.c_str(), "Enabled-%zu,%lf", + window_size, threshold_gain); + if (parsed_values == 2) { + RTC_CHECK_GT(*window_size, 1) << "Need at least 2 points to fit a line."; + RTC_CHECK_GT(*threshold_gain, 0) << "Threshold gain needs to be positive."; + return true; + } + LOG(LS_WARNING) << "Failed to parse parameters for BweMedianSlopeFilter " + "experiment from field trial string. Using default."; + *window_size = kDefaultMedianSlopeWindowSize; + *threshold_gain = kDefaultMedianSlopeThresholdGain; + return false; +} } // namespace namespace webrtc { @@ -168,7 +207,9 @@ rtc::Optional DelayBasedBwe::BitrateEstimator::bitrate_bps() const { } DelayBasedBwe::DelayBasedBwe(Clock* clock) - : clock_(clock), + : in_trendline_experiment_(TrendlineFilterExperimentIsEnabled()), + in_median_slope_experiment_(MedianSlopeFilterExperimentIsEnabled()), + clock_(clock), inter_arrival_(), kalman_estimator_(), trendline_estimator_(), @@ -180,13 +221,19 @@ DelayBasedBwe::DelayBasedBwe(Clock* clock) trendline_window_size_(kDefaultTrendlineWindowSize), trendline_smoothing_coeff_(kDefaultTrendlineSmoothingCoeff), trendline_threshold_gain_(kDefaultTrendlineThresholdGain), - in_trendline_experiment_(TrendlineFilterExperimentIsEnabled()), - probing_interval_estimator_(&rate_control_) { + probing_interval_estimator_(&rate_control_), + median_slope_window_size_(kDefaultMedianSlopeWindowSize), + median_slope_threshold_gain_(kDefaultMedianSlopeThresholdGain) { if (in_trendline_experiment_) { ReadTrendlineFilterExperimentParameters(&trendline_window_size_, &trendline_smoothing_coeff_, &trendline_threshold_gain_); } + if (in_median_slope_experiment_) { + ReadMedianSlopeFilterExperimentParameters(&trendline_window_size_, + &trendline_threshold_gain_); + } + network_thread_.DetachFromThread(); } @@ -224,6 +271,8 @@ DelayBasedBwe::Result DelayBasedBwe::IncomingPacketInfo( trendline_estimator_.reset(new TrendlineEstimator( trendline_window_size_, trendline_smoothing_coeff_, trendline_threshold_gain_)); + median_slope_estimator_.reset(new MedianSlopeEstimator( + median_slope_window_size_, median_slope_threshold_gain_)); } last_seen_packet_ms_ = now_ms; @@ -249,7 +298,12 @@ DelayBasedBwe::Result DelayBasedBwe::IncomingPacketInfo( detector_.Detect(trendline_estimator_->trendline_slope(), ts_delta_ms, trendline_estimator_->num_of_deltas(), info.arrival_time_ms); - + } else if (in_median_slope_experiment_) { + median_slope_estimator_->Update(t_delta, ts_delta_ms, + info.arrival_time_ms); + detector_.Detect(median_slope_estimator_->trendline_slope(), ts_delta_ms, + median_slope_estimator_->num_of_deltas(), + info.arrival_time_ms); } else { kalman_estimator_->Update(t_delta, ts_delta_ms, size_delta, detector_.State(), info.arrival_time_ms); @@ -288,8 +342,11 @@ DelayBasedBwe::Result DelayBasedBwe::IncomingPacketInfo( UpdateEstimate(info.arrival_time_ms, now_ms, acked_bitrate_bps, &result.target_bitrate_bps); } - if (result.updated) + if (result.updated) { last_update_ms_ = now_ms; + BWE_TEST_LOGGING_PLOT(1, "target_bitrate_bps", now_ms, + result.target_bitrate_bps); + } return result; } diff --git a/webrtc/modules/congestion_controller/delay_based_bwe.h b/webrtc/modules/congestion_controller/delay_based_bwe.h index c5a599f334..6e7a6f8d63 100644 --- a/webrtc/modules/congestion_controller/delay_based_bwe.h +++ b/webrtc/modules/congestion_controller/delay_based_bwe.h @@ -19,6 +19,7 @@ #include "webrtc/base/constructormagic.h" #include "webrtc/base/rate_statistics.h" #include "webrtc/base/thread_checker.h" +#include "webrtc/modules/congestion_controller/median_slope_estimator.h" #include "webrtc/modules/congestion_controller/probe_bitrate_estimator.h" #include "webrtc/modules/congestion_controller/probing_interval_estimator.h" #include "webrtc/modules/congestion_controller/trendline_estimator.h" @@ -84,12 +85,15 @@ class DelayBasedBwe { int64_t now_ms, rtc::Optional acked_bitrate_bps, uint32_t* target_bitrate_bps); + const bool in_trendline_experiment_; + const bool in_median_slope_experiment_; rtc::ThreadChecker network_thread_; Clock* const clock_; std::unique_ptr inter_arrival_; std::unique_ptr kalman_estimator_; std::unique_ptr trendline_estimator_; + std::unique_ptr median_slope_estimator_; OveruseDetector detector_; BitrateEstimator receiver_incoming_bitrate_; int64_t last_update_ms_; @@ -100,8 +104,9 @@ class DelayBasedBwe { size_t trendline_window_size_; double trendline_smoothing_coeff_; double trendline_threshold_gain_; - const bool in_trendline_experiment_; ProbingIntervalEstimator probing_interval_estimator_; + size_t median_slope_window_size_; + double median_slope_threshold_gain_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(DelayBasedBwe); }; diff --git a/webrtc/modules/congestion_controller/delay_based_bwe_unittest.cc b/webrtc/modules/congestion_controller/delay_based_bwe_unittest.cc index c242bf4f27..d4b3a72a3f 100644 --- a/webrtc/modules/congestion_controller/delay_based_bwe_unittest.cc +++ b/webrtc/modules/congestion_controller/delay_based_bwe_unittest.cc @@ -175,13 +175,11 @@ TEST_F(DelayBasedBweTest, TestLongTimeoutAndWrap) { class DelayBasedBweExperimentTest : public DelayBasedBweTest { public: DelayBasedBweExperimentTest() - : override_field_trials_("WebRTC-ImprovedBitrateEstimate/Enabled/") {} - - protected: - void SetUp() override { + : override_field_trials_("WebRTC-ImprovedBitrateEstimate/Enabled/") { bitrate_estimator_.reset(new DelayBasedBwe(&clock_)); } + private: test::ScopedFieldTrials override_field_trials_; }; @@ -208,13 +206,11 @@ TEST_F(DelayBasedBweExperimentTest, CapacityDropOneStreamWrap) { class DelayBasedBweTrendlineExperimentTest : public DelayBasedBweTest { public: DelayBasedBweTrendlineExperimentTest() - : override_field_trials_("WebRTC-BweTrendlineFilter/Enabled-15,0.9,4/") {} - - protected: - void SetUp() override { + : override_field_trials_("WebRTC-BweTrendlineFilter/Enabled-15,0.9,4/") { bitrate_estimator_.reset(new DelayBasedBwe(&clock_)); } + private: test::ScopedFieldTrials override_field_trials_; }; @@ -238,4 +234,35 @@ TEST_F(DelayBasedBweTrendlineExperimentTest, CapacityDropOneStreamWrap) { CapacityDropTestHelper(1, true, 600, 0); } +class DelayBasedBweMedianSlopeExperimentTest : public DelayBasedBweTest { + public: + DelayBasedBweMedianSlopeExperimentTest() + : override_field_trials_("WebRTC-BweMedianSlopeFilter/Enabled-20,4/") { + bitrate_estimator_.reset(new DelayBasedBwe(&clock_)); + } + + private: + test::ScopedFieldTrials override_field_trials_; +}; + +TEST_F(DelayBasedBweMedianSlopeExperimentTest, RateIncreaseRtpTimestamps) { + RateIncreaseRtpTimestampsTestHelper(1240); +} + +TEST_F(DelayBasedBweMedianSlopeExperimentTest, CapacityDropOneStream) { + CapacityDropTestHelper(1, false, 600, 0); +} + +TEST_F(DelayBasedBweMedianSlopeExperimentTest, CapacityDropPosOffsetChange) { + CapacityDropTestHelper(1, false, 600, 30000); +} + +TEST_F(DelayBasedBweMedianSlopeExperimentTest, CapacityDropNegOffsetChange) { + CapacityDropTestHelper(1, false, 1267, -30000); +} + +TEST_F(DelayBasedBweMedianSlopeExperimentTest, CapacityDropOneStreamWrap) { + CapacityDropTestHelper(1, true, 600, 0); +} + } // namespace webrtc diff --git a/webrtc/modules/congestion_controller/median_slope_estimator.cc b/webrtc/modules/congestion_controller/median_slope_estimator.cc new file mode 100644 index 0000000000..488831e173 --- /dev/null +++ b/webrtc/modules/congestion_controller/median_slope_estimator.cc @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016 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 "webrtc/modules/congestion_controller/median_slope_estimator.h" + +#include +#include + +#include "webrtc/base/logging.h" +#include "webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h" +#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h" + +namespace webrtc { + +constexpr unsigned int kDeltaCounterMax = 1000; + +MedianSlopeEstimator::MedianSlopeEstimator(size_t window_size, + double threshold_gain) + : window_size_(window_size), + threshold_gain_(threshold_gain), + num_of_deltas_(0), + accumulated_delay_(0), + delay_hist_(), + median_filter_(0.5), + trendline_(0) {} + +MedianSlopeEstimator::~MedianSlopeEstimator() {} + +void MedianSlopeEstimator::Update(double recv_delta_ms, + double send_delta_ms, + double now_ms) { + const double delta_ms = recv_delta_ms - send_delta_ms; + ++num_of_deltas_; + if (num_of_deltas_ > kDeltaCounterMax) { + num_of_deltas_ = kDeltaCounterMax; + } + + accumulated_delay_ += delta_ms; + BWE_TEST_LOGGING_PLOT(1, "accumulated_delay_ms", now_ms, accumulated_delay_); + + // If the window is full, remove the |window_size_| - 1 slopes that belong to + // the oldest point. + if (delay_hist_.size() == window_size_) { + for (double slope : delay_hist_.front().slopes) { + const bool success = median_filter_.Erase(slope); + RTC_CHECK(success); + } + delay_hist_.pop_front(); + } + // Add |window_size_| - 1 new slopes. + for (auto& old_delay : delay_hist_) { + if (now_ms - old_delay.time != 0) { + // The C99 standard explicitly states that casts and assignments must + // perform the associated conversions. This means that |slope| will be + // a 64-bit double even if the division is computed using, e.g., 80-bit + // extended precision. I believe this also holds in C++ even though the + // C++11 standard isn't as explicit. Furthermore, there are good reasons + // to believe that compilers couldn't perform optimizations that break + // this assumption even if they wanted to. + double slope = + (accumulated_delay_ - old_delay.delay) / (now_ms - old_delay.time); + median_filter_.Insert(slope); + // We want to avoid issues with different rounding mode / precision + // which we might get if we recomputed the slope when we remove it. + old_delay.slopes.push_back(slope); + } + } + delay_hist_.emplace_back(now_ms, accumulated_delay_, window_size_ - 1); + // Recompute the median slope. + if (delay_hist_.size() == window_size_) + trendline_ = median_filter_.GetPercentileValue(); + + BWE_TEST_LOGGING_PLOT(1, "trendline_slope", now_ms, trendline_); +} + +} // namespace webrtc diff --git a/webrtc/modules/congestion_controller/median_slope_estimator.h b/webrtc/modules/congestion_controller/median_slope_estimator.h new file mode 100644 index 0000000000..26d7f6144d --- /dev/null +++ b/webrtc/modules/congestion_controller/median_slope_estimator.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016 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. + */ +#ifndef WEBRTC_MODULES_CONGESTION_CONTROLLER_MEDIAN_SLOPE_ESTIMATOR_H_ +#define WEBRTC_MODULES_CONGESTION_CONTROLLER_MEDIAN_SLOPE_ESTIMATOR_H_ + +#include +#include +#include + +#include "webrtc/base/analytics/percentile_filter.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/common_types.h" + +namespace webrtc { + +class MedianSlopeEstimator { + public: + // |window_size| is the number of points required to compute a trend line. + // |threshold_gain| is used to scale the trendline slope for comparison to + // the old threshold. Once the old estimator has been removed (or the + // thresholds been merged into the estimators), we can just set the + // threshold instead of setting a gain. + MedianSlopeEstimator(size_t window_size, double threshold_gain); + ~MedianSlopeEstimator(); + + // Update the estimator with a new sample. The deltas should represent deltas + // between timestamp groups as defined by the InterArrival class. + void Update(double recv_delta_ms, double send_delta_ms, double now_ms); + + // Returns the estimated trend k multiplied by some gain. + // 0 < k < 1 -> the delay increases, queues are filling up + // k == 0 -> the delay does not change + // k < 0 -> the delay decreases, queues are being emptied + double trendline_slope() const { return trendline_ * threshold_gain_; } + + // Returns the number of deltas which the current estimator state is based on. + unsigned int num_of_deltas() const { return num_of_deltas_; } + + private: + struct DelayInfo { + DelayInfo(double time, double delay, size_t slope_count) + : time(time), delay(delay) { + slopes.reserve(slope_count); + } + double time; + double delay; + std::vector slopes; + }; + // Parameters. + const size_t window_size_; + const double threshold_gain_; + // Used by the existing threshold. + unsigned int num_of_deltas_; + // Theil-Sen robust line fitting + double accumulated_delay_; + std::list delay_hist_; + PercentileFilter median_filter_; + double trendline_; + + RTC_DISALLOW_COPY_AND_ASSIGN(MedianSlopeEstimator); +}; +} // namespace webrtc + +#endif // WEBRTC_MODULES_CONGESTION_CONTROLLER_MEDIAN_SLOPE_ESTIMATOR_H_ diff --git a/webrtc/modules/congestion_controller/median_slope_estimator_unittest.cc b/webrtc/modules/congestion_controller/median_slope_estimator_unittest.cc new file mode 100644 index 0000000000..ef942f143d --- /dev/null +++ b/webrtc/modules/congestion_controller/median_slope_estimator_unittest.cc @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2016 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 "webrtc/test/gtest.h" +#include "webrtc/base/random.h" +#include "webrtc/modules/congestion_controller/median_slope_estimator.h" + +namespace webrtc { + +namespace { +constexpr size_t kWindowSize = 20; +constexpr double kGain = 1; +constexpr int64_t kAvgTimeBetweenPackets = 10; +} // namespace + +TEST(MedianSlopeEstimator, PerfectLineSlopeOneHalf) { + MedianSlopeEstimator estimator(kWindowSize, kGain); + Random rand(0x1234567); + double now_ms = rand.Rand() * 10000; + for (size_t i = 1; i < 2 * kWindowSize; i++) { + double send_delta = rand.Rand() * 2 * kAvgTimeBetweenPackets; + double recv_delta = 2 * send_delta; + now_ms += recv_delta; + estimator.Update(recv_delta, send_delta, now_ms); + if (i < kWindowSize) + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001); + else + EXPECT_NEAR(estimator.trendline_slope(), 0.5, 0.001); + } +} + +TEST(MedianSlopeEstimator, PerfectLineSlopeMinusOne) { + MedianSlopeEstimator estimator(kWindowSize, kGain); + Random rand(0x1234567); + double now_ms = rand.Rand() * 10000; + for (size_t i = 1; i < 2 * kWindowSize; i++) { + double send_delta = rand.Rand() * 2 * kAvgTimeBetweenPackets; + double recv_delta = 0.5 * send_delta; + now_ms += recv_delta; + estimator.Update(recv_delta, send_delta, now_ms); + if (i < kWindowSize) + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001); + else + EXPECT_NEAR(estimator.trendline_slope(), -1, 0.001); + } +} + +TEST(MedianSlopeEstimator, PerfectLineSlopeZero) { + MedianSlopeEstimator estimator(kWindowSize, kGain); + Random rand(0x1234567); + double now_ms = rand.Rand() * 10000; + for (size_t i = 1; i < 2 * kWindowSize; i++) { + double send_delta = rand.Rand() * 2 * kAvgTimeBetweenPackets; + double recv_delta = send_delta; + now_ms += recv_delta; + estimator.Update(recv_delta, send_delta, now_ms); + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001); + } +} + +TEST(MedianSlopeEstimator, JitteryLineSlopeOneHalf) { + MedianSlopeEstimator estimator(kWindowSize, kGain); + Random rand(0x1234567); + double now_ms = rand.Rand() * 10000; + for (size_t i = 1; i < 2 * kWindowSize; i++) { + double send_delta = rand.Rand() * 2 * kAvgTimeBetweenPackets; + double recv_delta = 2 * send_delta + rand.Gaussian(0, send_delta / 3); + now_ms += recv_delta; + estimator.Update(recv_delta, send_delta, now_ms); + if (i < kWindowSize) + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001); + else + EXPECT_NEAR(estimator.trendline_slope(), 0.5, 0.1); + } +} + +TEST(MedianSlopeEstimator, JitteryLineSlopeMinusOne) { + MedianSlopeEstimator estimator(kWindowSize, kGain); + Random rand(0x1234567); + double now_ms = rand.Rand() * 10000; + for (size_t i = 1; i < 2 * kWindowSize; i++) { + double send_delta = rand.Rand() * 2 * kAvgTimeBetweenPackets; + double recv_delta = 0.5 * send_delta + rand.Gaussian(0, send_delta / 20); + now_ms += recv_delta; + estimator.Update(recv_delta, send_delta, now_ms); + if (i < kWindowSize) + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001); + else + EXPECT_NEAR(estimator.trendline_slope(), -1, 0.1); + } +} + +TEST(MedianSlopeEstimator, JitteryLineSlopeZero) { + MedianSlopeEstimator estimator(kWindowSize, kGain); + Random rand(0x1234567); + double now_ms = rand.Rand() * 10000; + for (size_t i = 1; i < 2 * kWindowSize; i++) { + double send_delta = rand.Rand() * 2 * kAvgTimeBetweenPackets; + double recv_delta = send_delta + rand.Gaussian(0, send_delta / 5); + now_ms += recv_delta; + estimator.Update(recv_delta, send_delta, now_ms); + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.1); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py b/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py index 5fdac51d09..40f1778598 100755 --- a/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py +++ b/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py @@ -145,8 +145,11 @@ def main(): "Time (s)", "Delay (ms)") trendline_state.addSubplot(["trendline_slope"], "Time (s)", "Slope") + target_bitrate = Figure("TargetBitrate") + target_bitrate.addSubplot(['target_bitrate_bps'], "Time (s)", "Bitrate (bps)") + # Select which figures to plot here. - figures = [receiver, detector_state, trendline_state] + figures = [receiver, detector_state, trendline_state, target_bitrate] # Add samples to the figures. for line in sys.stdin: