diff --git a/modules/congestion_controller/goog_cc/delay_based_bwe.cc b/modules/congestion_controller/goog_cc/delay_based_bwe.cc index b718913888..f60863cc48 100644 --- a/modules/congestion_controller/goog_cc/delay_based_bwe.cc +++ b/modules/congestion_controller/goog_cc/delay_based_bwe.cc @@ -158,9 +158,10 @@ void DelayBasedBwe::IncomingPacketFeedback(const PacketResult& packet_feedback, packet_feedback.sent_packet.size.bytes(), &ts_delta, &t_delta, &size_delta); double ts_delta_ms = (1000.0 * ts_delta) / (1 << kInterArrivalShift); - delay_detector_->Update(t_delta, ts_delta_ms, - packet_feedback.sent_packet.send_time.ms(), - packet_feedback.receive_time.ms(), calculated_deltas); + delay_detector_->Update( + t_delta, ts_delta_ms, packet_feedback.sent_packet.send_time.ms(), + packet_feedback.receive_time.ms(), + packet_feedback.sent_packet.size.bytes(), calculated_deltas); } DataRate DelayBasedBwe::TriggerOveruse(Timestamp at_time, diff --git a/modules/congestion_controller/goog_cc/delay_increase_detector_interface.h b/modules/congestion_controller/goog_cc/delay_increase_detector_interface.h index d1d7496b12..8fe3f669bb 100644 --- a/modules/congestion_controller/goog_cc/delay_increase_detector_interface.h +++ b/modules/congestion_controller/goog_cc/delay_increase_detector_interface.h @@ -28,6 +28,7 @@ class DelayIncreaseDetectorInterface { double send_delta_ms, int64_t send_time_ms, int64_t arrival_time_ms, + size_t packet_size, bool calculated_deltas) = 0; virtual BandwidthUsage State() const = 0; diff --git a/modules/congestion_controller/goog_cc/trendline_estimator.cc b/modules/congestion_controller/goog_cc/trendline_estimator.cc index 6053adafbc..d95ab0c858 100644 --- a/modules/congestion_controller/goog_cc/trendline_estimator.cc +++ b/modules/congestion_controller/goog_cc/trendline_estimator.cc @@ -19,11 +19,29 @@ #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "modules/remote_bitrate_estimator/test/bwe_test_logging.h" #include "rtc_base/checks.h" +#include "rtc_base/experiments/struct_parameters_parser.h" #include "rtc_base/logging.h" #include "rtc_base/numerics/safe_minmax.h" namespace webrtc { +constexpr char BweIgnoreSmallPacketsSettings::kKey[]; + +BweIgnoreSmallPacketsSettings::BweIgnoreSmallPacketsSettings( + const WebRtcKeyValueConfig* key_value_config) { + Parser()->Parse( + key_value_config->Lookup(BweIgnoreSmallPacketsSettings::kKey)); +} + +std::unique_ptr +BweIgnoreSmallPacketsSettings::Parser() { + return StructParametersParser::Create( + "smoothing_factor", &smoothing_factor, // + "min_fraction_large_packets", &min_fraction_large_packets, // + "large_packet_size", &large_packet_size, // + "ignored_size", &ignored_size); +} + namespace { // Parameters for linear least squares fit of regression line to noisy data. @@ -84,23 +102,14 @@ constexpr int kDeltaCounterMax = 1000; TrendlineEstimator::TrendlineEstimator( const WebRtcKeyValueConfig* key_value_config, NetworkStatePredictor* network_state_predictor) - : TrendlineEstimator( - key_value_config->Lookup(kBweWindowSizeInPacketsExperiment) - .find("Enabled") == 0 - ? ReadTrendlineFilterWindowSize(key_value_config) - : kDefaultTrendlineWindowSize, - kDefaultTrendlineSmoothingCoeff, - kDefaultTrendlineThresholdGain, - network_state_predictor) {} - -TrendlineEstimator::TrendlineEstimator( - size_t window_size, - double smoothing_coef, - double threshold_gain, - NetworkStatePredictor* network_state_predictor) - : window_size_(window_size), - smoothing_coef_(smoothing_coef), - threshold_gain_(threshold_gain), + : ignore_small_packets_(key_value_config), + fraction_large_packets_(0.5), + window_size_(key_value_config->Lookup(kBweWindowSizeInPacketsExperiment) + .find("Enabled") == 0 + ? ReadTrendlineFilterWindowSize(key_value_config) + : kDefaultTrendlineWindowSize), + smoothing_coef_(kDefaultTrendlineSmoothingCoeff), + threshold_gain_(kDefaultTrendlineThresholdGain), num_of_deltas_(0), first_arrival_time_ms_(-1), accumulated_delay_(0), @@ -120,51 +129,78 @@ TrendlineEstimator::TrendlineEstimator( network_state_predictor_(network_state_predictor) { RTC_LOG(LS_INFO) << "Using Trendline filter for delay change estimation with window size " - << window_size_; + << window_size_ << " and field trial " + << ignore_small_packets_.Parser()->Encode(); } TrendlineEstimator::~TrendlineEstimator() {} +void TrendlineEstimator::UpdateTrendline(double recv_delta_ms, + double send_delta_ms, + int64_t send_time_ms, + int64_t arrival_time_ms, + size_t packet_size) { + if (ignore_small_packets_.ignored_size > 0) { + // Process the packet if it is "large" or if all packets in the call are + // "small". The packet size may have a significant effect on the propagation + // delay, especially at low bandwidths. Variations in packet size will then + // show up as noise in the delay measurement. + // By default, we include all packets. + fraction_large_packets_ = + (1 - ignore_small_packets_.smoothing_factor) * fraction_large_packets_ + + ignore_small_packets_.smoothing_factor * + (packet_size >= ignore_small_packets_.large_packet_size); + if (packet_size <= ignore_small_packets_.ignored_size && + fraction_large_packets_ >= + ignore_small_packets_.min_fraction_large_packets) { + return; + } + } + + const double delta_ms = recv_delta_ms - send_delta_ms; + ++num_of_deltas_; + num_of_deltas_ = std::min(num_of_deltas_, kDeltaCounterMax); + if (first_arrival_time_ms_ == -1) + first_arrival_time_ms_ = arrival_time_ms; + + // Exponential backoff filter. + accumulated_delay_ += delta_ms; + BWE_TEST_LOGGING_PLOT(1, "accumulated_delay_ms", arrival_time_ms, + accumulated_delay_); + smoothed_delay_ = smoothing_coef_ * smoothed_delay_ + + (1 - smoothing_coef_) * accumulated_delay_; + BWE_TEST_LOGGING_PLOT(1, "smoothed_delay_ms", arrival_time_ms, + smoothed_delay_); + + // Simple linear regression. + delay_hist_.push_back(std::make_pair( + static_cast(arrival_time_ms - first_arrival_time_ms_), + smoothed_delay_)); + if (delay_hist_.size() > window_size_) + delay_hist_.pop_front(); + double trend = prev_trend_; + if (delay_hist_.size() == window_size_) { + // Update trend_ if it is possible to fit a line to the data. The delay + // trend can be seen as an estimate of (send_rate - capacity)/capacity. + // 0 < trend < 1 -> the delay increases, queues are filling up + // trend == 0 -> the delay does not change + // trend < 0 -> the delay decreases, queues are being emptied + trend = LinearFitSlope(delay_hist_).value_or(trend); + } + BWE_TEST_LOGGING_PLOT(1, "trendline_slope", arrival_time_ms, trend); + + Detect(trend, send_delta_ms, arrival_time_ms); +} + void TrendlineEstimator::Update(double recv_delta_ms, double send_delta_ms, int64_t send_time_ms, int64_t arrival_time_ms, + size_t packet_size, bool calculated_deltas) { if (calculated_deltas) { - const double delta_ms = recv_delta_ms - send_delta_ms; - ++num_of_deltas_; - num_of_deltas_ = std::min(num_of_deltas_, kDeltaCounterMax); - if (first_arrival_time_ms_ == -1) - first_arrival_time_ms_ = arrival_time_ms; - - // Exponential backoff filter. - accumulated_delay_ += delta_ms; - BWE_TEST_LOGGING_PLOT(1, "accumulated_delay_ms", arrival_time_ms, - accumulated_delay_); - smoothed_delay_ = smoothing_coef_ * smoothed_delay_ + - (1 - smoothing_coef_) * accumulated_delay_; - BWE_TEST_LOGGING_PLOT(1, "smoothed_delay_ms", arrival_time_ms, - smoothed_delay_); - - // Simple linear regression. - delay_hist_.push_back(std::make_pair( - static_cast(arrival_time_ms - first_arrival_time_ms_), - smoothed_delay_)); - if (delay_hist_.size() > window_size_) - delay_hist_.pop_front(); - double trend = prev_trend_; - if (delay_hist_.size() == window_size_) { - // Update trend_ if it is possible to fit a line to the data. The delay - // trend can be seen as an estimate of (send_rate - capacity)/capacity. - // 0 < trend < 1 -> the delay increases, queues are filling up - // trend == 0 -> the delay does not change - // trend < 0 -> the delay decreases, queues are being emptied - trend = LinearFitSlope(delay_hist_).value_or(trend); - } - - BWE_TEST_LOGGING_PLOT(1, "trendline_slope", arrival_time_ms, trend); - - Detect(trend, send_delta_ms, arrival_time_ms); + UpdateTrendline(recv_delta_ms, send_delta_ms, send_time_ms, arrival_time_ms, + packet_size); } if (network_state_predictor_) { hypothesis_predicted_ = network_state_predictor_->Update( diff --git a/modules/congestion_controller/goog_cc/trendline_estimator.h b/modules/congestion_controller/goog_cc/trendline_estimator.h index 4bd4c71501..c48fcf0cfa 100644 --- a/modules/congestion_controller/goog_cc/trendline_estimator.h +++ b/modules/congestion_controller/goog_cc/trendline_estimator.h @@ -14,6 +14,7 @@ #include #include +#include #include #include "api/network_state_predictor.h" @@ -21,24 +22,29 @@ #include "modules/congestion_controller/goog_cc/delay_increase_detector_interface.h" #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "rtc_base/constructor_magic.h" +#include "rtc_base/experiments/struct_parameters_parser.h" namespace webrtc { +struct BweIgnoreSmallPacketsSettings { + static constexpr char kKey[] = "WebRTC-BweIgnoreSmallPackets"; + + BweIgnoreSmallPacketsSettings() = default; + explicit BweIgnoreSmallPacketsSettings( + const WebRtcKeyValueConfig* key_value_config); + + double smoothing_factor = 0.1; + double min_fraction_large_packets = 1.0; + unsigned large_packet_size = 0; + unsigned ignored_size = 0; + + std::unique_ptr Parser(); +}; + class TrendlineEstimator : public DelayIncreaseDetectorInterface { public: TrendlineEstimator(const WebRtcKeyValueConfig* key_value_config, NetworkStatePredictor* network_state_predictor); - // |window_size| is the number of points required to compute a trend line. - // |smoothing_coef| controls how much we smooth out the delay before fitting - // the 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.|network_state_predictor| is used to - // bettter predict network state. - TrendlineEstimator(size_t window_size, - double smoothing_coef, - double threshold_gain, - NetworkStatePredictor* network_state_predictor); ~TrendlineEstimator() override; @@ -48,13 +54,16 @@ class TrendlineEstimator : public DelayIncreaseDetectorInterface { double send_delta_ms, int64_t send_time_ms, int64_t arrival_time_ms, + size_t packet_size, bool calculated_deltas) override; - BandwidthUsage State() const override; + void UpdateTrendline(double recv_delta_ms, + double send_delta_ms, + int64_t send_time_ms, + int64_t arrival_time_ms, + size_t packet_size); - protected: - // Used in unit tests. - double modified_trend() const { return prev_trend_ * threshold_gain_; } + BandwidthUsage State() const override; private: friend class GoogCcStatePrinter; @@ -63,6 +72,11 @@ class TrendlineEstimator : public DelayIncreaseDetectorInterface { void UpdateThreshold(double modified_offset, int64_t now_ms); + // Filtering out small packets. (Intention is to base the detection only + // on video packets even if we have TWCC sequence number for audio.) + BweIgnoreSmallPacketsSettings ignore_small_packets_; + double fraction_large_packets_; + // Parameters. const size_t window_size_; const double smoothing_coef_; diff --git a/modules/congestion_controller/goog_cc/trendline_estimator_unittest.cc b/modules/congestion_controller/goog_cc/trendline_estimator_unittest.cc index 8390910271..b0195abdf5 100644 --- a/modules/congestion_controller/goog_cc/trendline_estimator_unittest.cc +++ b/modules/congestion_controller/goog_cc/trendline_estimator_unittest.cc @@ -10,69 +10,142 @@ #include "modules/congestion_controller/goog_cc/trendline_estimator.h" +#include +#include +#include + +#include "api/transport/field_trial_based_config.h" #include "rtc_base/random.h" #include "test/gtest.h" namespace webrtc { - namespace { -constexpr size_t kWindowSize = 20; -constexpr double kSmoothing = 0.0; -constexpr double kGain = 1; -constexpr int64_t kAvgTimeBetweenPackets = 10; -constexpr size_t kPacketCount = 2 * kWindowSize + 1; -class TrendlineEstimatorForTest : public TrendlineEstimator { + +class PacketTimeGenerator { public: - using TrendlineEstimator::modified_trend; - using TrendlineEstimator::TrendlineEstimator; + PacketTimeGenerator(int64_t initial_clock, double time_between_packets) + : initial_clock_(initial_clock), + time_between_packets_(time_between_packets), + packets_(0) {} + int64_t operator()() { + return initial_clock_ + time_between_packets_ * packets_++; + } + + private: + const int64_t initial_clock_; + const double time_between_packets_; + size_t packets_; }; -void TestEstimator(double slope, double jitter_stddev, double tolerance) { - TrendlineEstimatorForTest estimator(kWindowSize, kSmoothing, kGain, nullptr); - Random random(0x1234567); - int64_t send_times[kPacketCount]; - int64_t recv_times[kPacketCount]; - int64_t send_start_time = random.Rand(1000000); - int64_t recv_start_time = random.Rand(1000000); - for (size_t i = 0; i < kPacketCount; ++i) { - send_times[i] = send_start_time + i * kAvgTimeBetweenPackets; - double latency = i * kAvgTimeBetweenPackets / (1 - slope); - double jitter = random.Gaussian(0, jitter_stddev); - recv_times[i] = recv_start_time + latency + jitter; + +class TrendlineEstimatorTest : public testing::Test { + public: + TrendlineEstimatorTest() + : send_times(kPacketCount), + recv_times(kPacketCount), + packet_sizes(kPacketCount), + config(), + estimator(&config, nullptr), + count(1) { + std::fill(packet_sizes.begin(), packet_sizes.end(), kPacketSizeBytes); } - for (size_t i = 1; i < kPacketCount; ++i) { - double recv_delta = recv_times[i] - recv_times[i - 1]; - double send_delta = send_times[i] - send_times[i - 1]; - estimator.Update(recv_delta, send_delta, 0, recv_times[i], true); - if (i < kWindowSize) - EXPECT_NEAR(estimator.modified_trend(), 0, 0.001); - else - EXPECT_NEAR(estimator.modified_trend(), slope, tolerance); + + void RunTestUntilStateChange() { + RTC_DCHECK_EQ(send_times.size(), kPacketCount); + RTC_DCHECK_EQ(recv_times.size(), kPacketCount); + RTC_DCHECK_EQ(packet_sizes.size(), kPacketCount); + RTC_DCHECK_GE(count, 1); + RTC_DCHECK_LT(count, kPacketCount); + + auto initial_state = estimator.State(); + for (; count < kPacketCount; count++) { + double recv_delta = recv_times[count] - recv_times[count - 1]; + double send_delta = send_times[count] - send_times[count - 1]; + estimator.Update(recv_delta, send_delta, send_times[count], + recv_times[count], packet_sizes[count], true); + if (estimator.State() != initial_state) { + return; + } + } } -} + + protected: + const size_t kPacketCount = 25; + const size_t kPacketSizeBytes = 1200; + std::vector send_times; + std::vector recv_times; + std::vector packet_sizes; + const FieldTrialBasedConfig config; + TrendlineEstimator estimator; + size_t count; +}; } // namespace -TEST(TrendlineEstimator, PerfectLineSlopeOneHalf) { - TestEstimator(0.5, 0, 0.001); +TEST_F(TrendlineEstimatorTest, Normal) { + PacketTimeGenerator send_time_generator(123456789 /*initial clock*/, + 20 /*20 ms between sent packets*/); + std::generate(send_times.begin(), send_times.end(), send_time_generator); + + PacketTimeGenerator recv_time_generator(987654321 /*initial clock*/, + 20 /*delivered at the same pace*/); + std::generate(recv_times.begin(), recv_times.end(), recv_time_generator); + + EXPECT_EQ(estimator.State(), BandwidthUsage::kBwNormal); + RunTestUntilStateChange(); + EXPECT_EQ(estimator.State(), BandwidthUsage::kBwNormal); + EXPECT_EQ(count, kPacketCount); // All packets processed } -TEST(TrendlineEstimator, PerfectLineSlopeMinusOne) { - TestEstimator(-1, 0, 0.001); +TEST_F(TrendlineEstimatorTest, Overusing) { + PacketTimeGenerator send_time_generator(123456789 /*initial clock*/, + 20 /*20 ms between sent packets*/); + std::generate(send_times.begin(), send_times.end(), send_time_generator); + + PacketTimeGenerator recv_time_generator(987654321 /*initial clock*/, + 1.1 * 20 /*10% slower delivery*/); + std::generate(recv_times.begin(), recv_times.end(), recv_time_generator); + + EXPECT_EQ(estimator.State(), BandwidthUsage::kBwNormal); + RunTestUntilStateChange(); + EXPECT_EQ(estimator.State(), BandwidthUsage::kBwOverusing); + RunTestUntilStateChange(); + EXPECT_EQ(estimator.State(), BandwidthUsage::kBwOverusing); + EXPECT_EQ(count, kPacketCount); // All packets processed } -TEST(TrendlineEstimator, PerfectLineSlopeZero) { - TestEstimator(0, 0, 0.001); +TEST_F(TrendlineEstimatorTest, Underusing) { + PacketTimeGenerator send_time_generator(123456789 /*initial clock*/, + 20 /*20 ms between sent packets*/); + std::generate(send_times.begin(), send_times.end(), send_time_generator); + + PacketTimeGenerator recv_time_generator(987654321 /*initial clock*/, + 0.85 * 20 /*15% faster delivery*/); + std::generate(recv_times.begin(), recv_times.end(), recv_time_generator); + + EXPECT_EQ(estimator.State(), BandwidthUsage::kBwNormal); + RunTestUntilStateChange(); + EXPECT_EQ(estimator.State(), BandwidthUsage::kBwUnderusing); + RunTestUntilStateChange(); + EXPECT_EQ(estimator.State(), BandwidthUsage::kBwUnderusing); + EXPECT_EQ(count, kPacketCount); // All packets processed } -TEST(TrendlineEstimator, JitteryLineSlopeOneHalf) { - TestEstimator(0.5, kAvgTimeBetweenPackets / 3.0, 0.01); -} +TEST_F(TrendlineEstimatorTest, IncludesSmallPacketsByDefault) { + PacketTimeGenerator send_time_generator(123456789 /*initial clock*/, + 20 /*20 ms between sent packets*/); + std::generate(send_times.begin(), send_times.end(), send_time_generator); -TEST(TrendlineEstimator, JitteryLineSlopeMinusOne) { - TestEstimator(-1, kAvgTimeBetweenPackets / 3.0, 0.075); -} + PacketTimeGenerator recv_time_generator(987654321 /*initial clock*/, + 1.1 * 20 /*10% slower delivery*/); + std::generate(recv_times.begin(), recv_times.end(), recv_time_generator); -TEST(TrendlineEstimator, JitteryLineSlopeZero) { - TestEstimator(0, kAvgTimeBetweenPackets / 3.0, 0.02); + std::fill(packet_sizes.begin(), packet_sizes.end(), 100); + + EXPECT_EQ(estimator.State(), BandwidthUsage::kBwNormal); + RunTestUntilStateChange(); + EXPECT_EQ(estimator.State(), BandwidthUsage::kBwOverusing); + RunTestUntilStateChange(); + EXPECT_EQ(estimator.State(), BandwidthUsage::kBwOverusing); + EXPECT_EQ(count, kPacketCount); // All packets processed } } // namespace webrtc