diff --git a/webrtc/call/rampup_tests.cc b/webrtc/call/rampup_tests.cc index 6c3cce6010..0a2a7384d8 100644 --- a/webrtc/call/rampup_tests.cc +++ b/webrtc/call/rampup_tests.cc @@ -417,9 +417,8 @@ RampUpDownUpTester::RampUpDownUpTester(size_t num_video_streams, rtx, red, report_perf_stats), - link_rates_({4 * GetExpectedHighBitrate() / (3 * 1000), - kLowBandwidthLimitBps / 1000, - 4 * GetExpectedHighBitrate() / (3 * 1000), 0}), + link_rates_({GetHighLinkCapacity(), kLowBandwidthLimitBps / 1000, + GetHighLinkCapacity(), 0}), test_state_(kFirstRampup), next_state_(kTransitionToNextState), state_start_ms_(clock_->TimeInMilliseconds()), @@ -489,6 +488,10 @@ int RampUpDownUpTester::GetExpectedHighBitrate() const { return expected_bitrate_bps; } +int RampUpDownUpTester::GetHighLinkCapacity() const { + return 4 * GetExpectedHighBitrate() / (3 * 1000); +} + size_t RampUpDownUpTester::GetFecBytes() const { size_t flex_fec_bytes = 0; if (num_flexfec_streams_ > 0) { diff --git a/webrtc/examples/objc/AppRTCMobile/ios/ARDAppDelegate.m b/webrtc/examples/objc/AppRTCMobile/ios/ARDAppDelegate.m index 07c83a0826..c26f0529df 100644 --- a/webrtc/examples/objc/AppRTCMobile/ios/ARDAppDelegate.m +++ b/webrtc/examples/objc/AppRTCMobile/ios/ARDAppDelegate.m @@ -26,6 +26,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSDictionary *fieldTrials = @{ + kRTCFieldTrialImprovedBitrateEstimateKey: kRTCFieldTrialEnabledValue, kRTCFieldTrialH264HighProfileKey: kRTCFieldTrialEnabledValue, }; RTCInitFieldTrialDictionary(fieldTrials); diff --git a/webrtc/modules/congestion_controller/delay_based_bwe.cc b/webrtc/modules/congestion_controller/delay_based_bwe.cc index 042d7fa74a..e8d9d21050 100644 --- a/webrtc/modules/congestion_controller/delay_based_bwe.cc +++ b/webrtc/modules/congestion_controller/delay_based_bwe.cc @@ -42,12 +42,86 @@ constexpr int kInitialRateWindowMs = 500; constexpr int kRateWindowMs = 150; // Parameters for linear least squares fit of regression line to noisy data. -constexpr size_t kDefaultTrendlineWindowSize = 20; +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; + constexpr int kMaxConsecutiveFailedLookups = 5; +const char kBitrateEstimateExperiment[] = "WebRTC-ImprovedBitrateEstimate"; +const char kBweTrendlineFilterExperiment[] = "WebRTC-BweTrendlineFilter"; +const char kBweMedianSlopeFilterExperiment[] = "WebRTC-BweMedianSlopeFilter"; + +bool BitrateEstimateExperimentIsEnabled() { + return webrtc::field_trial::IsEnabled(kBitrateEstimateExperiment); +} + +bool TrendlineFilterExperimentIsEnabled() { + std::string experiment_string = + webrtc::field_trial::FindFullName(kBweTrendlineFilterExperiment); + // The experiment is enabled iff the field trial string begins with "Enabled". + return experiment_string.find("Enabled") == 0; +} + +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_size, smoothing_coef, threshold_gain); + if (parsed_values == 3) { + 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."; + return true; + } + LOG(LS_WARNING) << "Failed to parse parameters for BweTrendlineFilter " + "experiment from field trial string. Using default."; + *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; +} class PacketFeedbackComparator { public: @@ -79,9 +153,19 @@ DelayBasedBwe::BitrateEstimator::BitrateEstimator() current_win_ms_(0), prev_time_ms_(-1), bitrate_estimate_(-1.0f), - bitrate_estimate_var_(50.0f) {} + bitrate_estimate_var_(50.0f), + old_estimator_(kBitrateWindowMs, 8000), + in_experiment_(BitrateEstimateExperimentIsEnabled()) {} void DelayBasedBwe::BitrateEstimator::Update(int64_t now_ms, int bytes) { + if (!in_experiment_) { + old_estimator_.Update(bytes, now_ms); + rtc::Optional rate = old_estimator_.Rate(now_ms); + bitrate_estimate_ = -1.0f; + if (rate) + bitrate_estimate_ = *rate / 1000.0f; + return; + } int rate_window_ms = kRateWindowMs; // We use a larger window at the beginning to get a more stable sample that // we can use to initialize the estimate. @@ -147,9 +231,12 @@ rtc::Optional DelayBasedBwe::BitrateEstimator::bitrate_bps() const { } DelayBasedBwe::DelayBasedBwe(RtcEventLog* event_log, const Clock* clock) - : event_log_(event_log), + : in_trendline_experiment_(TrendlineFilterExperimentIsEnabled()), + in_median_slope_experiment_(MedianSlopeFilterExperimentIsEnabled()), + event_log_(event_log), clock_(clock), inter_arrival_(), + kalman_estimator_(), trendline_estimator_(), detector_(), receiver_incoming_bitrate_(), @@ -161,10 +248,29 @@ DelayBasedBwe::DelayBasedBwe(RtcEventLog* event_log, const Clock* clock) trendline_smoothing_coeff_(kDefaultTrendlineSmoothingCoeff), trendline_threshold_gain_(kDefaultTrendlineThresholdGain), probing_interval_estimator_(&rate_control_), + median_slope_window_size_(kDefaultMedianSlopeWindowSize), + median_slope_threshold_gain_(kDefaultMedianSlopeThresholdGain), consecutive_delayed_feedbacks_(0), last_logged_bitrate_(0), last_logged_state_(kBwNormal) { - LOG(LS_INFO) << "Using Trendline filter for delay change estimation."; + if (in_trendline_experiment_) { + ReadTrendlineFilterExperimentParameters(&trendline_window_size_, + &trendline_smoothing_coeff_, + &trendline_threshold_gain_); + LOG(LS_INFO) << "Trendline filter experiment enabled with parameters " + << trendline_window_size_ << ',' << trendline_smoothing_coeff_ + << ',' << trendline_threshold_gain_; + } + if (in_median_slope_experiment_) { + ReadMedianSlopeFilterExperimentParameters(&median_slope_window_size_, + &median_slope_threshold_gain_); + LOG(LS_INFO) << "Median-slope filter experiment enabled with parameters " + << median_slope_window_size_ << ',' + << median_slope_threshold_gain_; + } + if (!in_trendline_experiment_ && !in_median_slope_experiment_) { + LOG(LS_INFO) << "No overuse experiment enabled. Using Kalman filter."; + } network_thread_.DetachFromThread(); } @@ -237,9 +343,12 @@ DelayBasedBwe::Result DelayBasedBwe::IncomingPacketFeedback( inter_arrival_.reset( new InterArrival((kTimestampGroupLengthMs << kInterArrivalShift) / 1000, kTimestampToMs, true)); + kalman_estimator_.reset(new OveruseEstimator(OverUseDetectorOptions())); 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; @@ -261,11 +370,26 @@ DelayBasedBwe::Result DelayBasedBwe::IncomingPacketFeedback( now_ms, packet_feedback.payload_size, &ts_delta, &t_delta, &size_delta)) { double ts_delta_ms = (1000.0 * ts_delta) / (1 << kInterArrivalShift); - trendline_estimator_->Update(t_delta, ts_delta_ms, - packet_feedback.arrival_time_ms); - detector_.Detect(trendline_estimator_->trendline_slope(), ts_delta_ms, - trendline_estimator_->num_of_deltas(), - packet_feedback.arrival_time_ms); + if (in_trendline_experiment_) { + trendline_estimator_->Update(t_delta, ts_delta_ms, + packet_feedback.arrival_time_ms); + detector_.Detect(trendline_estimator_->trendline_slope(), ts_delta_ms, + trendline_estimator_->num_of_deltas(), + packet_feedback.arrival_time_ms); + } else if (in_median_slope_experiment_) { + median_slope_estimator_->Update(t_delta, ts_delta_ms, + packet_feedback.arrival_time_ms); + detector_.Detect(median_slope_estimator_->trendline_slope(), ts_delta_ms, + median_slope_estimator_->num_of_deltas(), + packet_feedback.arrival_time_ms); + } else { + kalman_estimator_->Update(t_delta, ts_delta_ms, size_delta, + detector_.State(), + packet_feedback.arrival_time_ms); + detector_.Detect(kalman_estimator_->offset(), ts_delta_ms, + kalman_estimator_->num_of_deltas(), + packet_feedback.arrival_time_ms); + } } int probing_bps = 0; diff --git a/webrtc/modules/congestion_controller/delay_based_bwe.h b/webrtc/modules/congestion_controller/delay_based_bwe.h index b43f92074e..5ae60f7d21 100644 --- a/webrtc/modules/congestion_controller/delay_based_bwe.h +++ b/webrtc/modules/congestion_controller/delay_based_bwe.h @@ -17,6 +17,7 @@ #include "webrtc/base/checks.h" #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" @@ -76,6 +77,8 @@ class DelayBasedBwe { int64_t prev_time_ms_; float bitrate_estimate_; float bitrate_estimate_var_; + RateStatistics old_estimator_; + const bool in_experiment_; }; Result IncomingPacketFeedback(const PacketFeedback& packet_feedback); @@ -86,12 +89,16 @@ 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_; RtcEventLog* const event_log_; const 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_; @@ -103,6 +110,8 @@ class DelayBasedBwe { double trendline_smoothing_coeff_; double trendline_threshold_gain_; ProbingIntervalEstimator probing_interval_estimator_; + size_t median_slope_window_size_; + double median_slope_threshold_gain_; int consecutive_delayed_feedbacks_; uint32_t last_logged_bitrate_; BandwidthUsage last_logged_state_; diff --git a/webrtc/modules/congestion_controller/delay_based_bwe_unittest.cc b/webrtc/modules/congestion_controller/delay_based_bwe_unittest.cc index 44407b37e9..559179e518 100644 --- a/webrtc/modules/congestion_controller/delay_based_bwe_unittest.cc +++ b/webrtc/modules/congestion_controller/delay_based_bwe_unittest.cc @@ -119,7 +119,7 @@ TEST_F(DelayBasedBweTest, ProbeDetectionSlowerArrivalHighBitrate) { TEST_F(DelayBasedBweTest, GetProbingInterval) { int64_t default_interval_ms = bitrate_estimator_->GetProbingIntervalMs(); EXPECT_GT(default_interval_ms, 0); - CapacityDropTestHelper(1, true, 333, 0); + CapacityDropTestHelper(1, true, 567, 0); int64_t interval_ms = bitrate_estimator_->GetProbingIntervalMs(); EXPECT_GT(interval_ms, 0); EXPECT_NE(interval_ms, default_interval_ms); @@ -132,25 +132,27 @@ TEST_F(DelayBasedBweTest, InitialBehavior) { TEST_F(DelayBasedBweTest, RateIncreaseReordering) { RateIncreaseReorderingTestHelper(674840); } + TEST_F(DelayBasedBweTest, RateIncreaseRtpTimestamps) { - RateIncreaseRtpTimestampsTestHelper(1288); + RateIncreaseRtpTimestampsTestHelper(1240); } TEST_F(DelayBasedBweTest, CapacityDropOneStream) { - CapacityDropTestHelper(1, false, 333, 0); + CapacityDropTestHelper(1, false, 567, 0); } TEST_F(DelayBasedBweTest, CapacityDropPosOffsetChange) { - CapacityDropTestHelper(1, false, 867, 30000); + CapacityDropTestHelper(1, false, 200, 30000); } TEST_F(DelayBasedBweTest, CapacityDropNegOffsetChange) { - CapacityDropTestHelper(1, false, 867, -30000); + CapacityDropTestHelper(1, false, 733, -30000); } TEST_F(DelayBasedBweTest, CapacityDropOneStreamWrap) { - CapacityDropTestHelper(1, true, 333, 0); + CapacityDropTestHelper(1, true, 567, 0); } + TEST_F(DelayBasedBweTest, TestTimestampGrouping) { TestTimestampGroupingTestHelper(); } @@ -170,4 +172,97 @@ TEST_F(DelayBasedBweTest, TestLongTimeoutAndWrap) { TestWrappingHelper(10 * 64); } +class DelayBasedBweExperimentTest : public DelayBasedBweTest { + public: + DelayBasedBweExperimentTest() + : override_field_trials_("WebRTC-ImprovedBitrateEstimate/Enabled/") { + bitrate_estimator_.reset(new DelayBasedBwe(nullptr, &clock_)); + } + + private: + test::ScopedFieldTrials override_field_trials_; +}; + +TEST_F(DelayBasedBweExperimentTest, RateIncreaseRtpTimestamps) { + RateIncreaseRtpTimestampsTestHelper(1288); +} + +TEST_F(DelayBasedBweExperimentTest, CapacityDropOneStream) { + CapacityDropTestHelper(1, false, 333, 0); +} + +TEST_F(DelayBasedBweExperimentTest, CapacityDropPosOffsetChange) { + CapacityDropTestHelper(1, false, 300, 30000); +} + +TEST_F(DelayBasedBweExperimentTest, CapacityDropNegOffsetChange) { + CapacityDropTestHelper(1, false, 300, -30000); +} + +TEST_F(DelayBasedBweExperimentTest, CapacityDropOneStreamWrap) { + CapacityDropTestHelper(1, true, 333, 0); +} + +class DelayBasedBweTrendlineExperimentTest : public DelayBasedBweTest { + public: + DelayBasedBweTrendlineExperimentTest() + : override_field_trials_("WebRTC-BweTrendlineFilter/Enabled-15,0.9,4/") { + bitrate_estimator_.reset(new DelayBasedBwe(nullptr, &clock_)); + } + + private: + test::ScopedFieldTrials override_field_trials_; +}; + +TEST_F(DelayBasedBweTrendlineExperimentTest, RateIncreaseRtpTimestamps) { + RateIncreaseRtpTimestampsTestHelper(1240); +} + +TEST_F(DelayBasedBweTrendlineExperimentTest, CapacityDropOneStream) { + CapacityDropTestHelper(1, false, 600, 0); +} + +TEST_F(DelayBasedBweTrendlineExperimentTest, CapacityDropPosOffsetChange) { + CapacityDropTestHelper(1, false, 600, 30000); +} + +TEST_F(DelayBasedBweTrendlineExperimentTest, CapacityDropNegOffsetChange) { + CapacityDropTestHelper(1, false, 1267, -30000); +} + +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(nullptr, &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/delay_based_bwe_unittest_helper.cc b/webrtc/modules/congestion_controller/delay_based_bwe_unittest_helper.cc index e7a98c350c..c87439e5c7 100644 --- a/webrtc/modules/congestion_controller/delay_based_bwe_unittest_helper.cc +++ b/webrtc/modules/congestion_controller/delay_based_bwe_unittest_helper.cc @@ -411,7 +411,7 @@ void DelayBasedBweTest::CapacityDropTestHelper( uint32_t bitrate_bps = SteadyStateRun( kDefaultSsrc, steady_state_time * kFramerate, kStartBitrate, kMinExpectedBitrate, kMaxExpectedBitrate, kInitialCapacityBps); - EXPECT_NEAR(kInitialCapacityBps, bitrate_bps, 180000u); + EXPECT_NEAR(kInitialCapacityBps, bitrate_bps, 130000u); bitrate_observer_.Reset(); // Add an offset to make sure the BWE can handle it. @@ -492,7 +492,7 @@ void DelayBasedBweTest::TestWrappingHelper(int silence_time_s) { clock_.AdvanceTimeMilliseconds(silence_time_s * 1000); send_time_ms += silence_time_s * 1000; - for (size_t i = 0; i < 23; ++i) { + for (size_t i = 0; i < 22; ++i) { IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, sequence_number++, 1000); clock_.AdvanceTimeMilliseconds(2 * kFrameIntervalMs); diff --git a/webrtc/sdk/objc/Framework/Classes/RTCFieldTrials.mm b/webrtc/sdk/objc/Framework/Classes/RTCFieldTrials.mm index 25d651f6e5..5e1ee0afc1 100644 --- a/webrtc/sdk/objc/Framework/Classes/RTCFieldTrials.mm +++ b/webrtc/sdk/objc/Framework/Classes/RTCFieldTrials.mm @@ -20,6 +20,9 @@ NSString * const kRTCFieldTrialAudioSendSideBweKey = @"WebRTC-Audio-SendSideBwe" NSString * const kRTCFieldTrialSendSideBweWithOverheadKey = @"WebRTC-SendSideBwe-WithOverhead"; NSString * const kRTCFieldTrialFlexFec03AdvertisedKey = @"WebRTC-FlexFEC-03-Advertised"; NSString * const kRTCFieldTrialFlexFec03Key = @"WebRTC-FlexFEC-03"; +NSString * const kRTCFieldTrialImprovedBitrateEstimateKey = @"WebRTC-ImprovedBitrateEstimate"; +NSString * const kRTCFieldTrialMedianSlopeFilterKey = @"WebRTC-BweMedianSlopeFilter"; +NSString * const kRTCFieldTrialTrendlineFilterKey = @"WebRTC-BweTrendlineFilter"; NSString * const kRTCFieldTrialH264HighProfileKey = @"WebRTC-H264HighProfile"; NSString * const kRTCFieldTrialEnabledValue = @"Enabled"; diff --git a/webrtc/sdk/objc/Framework/Headers/WebRTC/RTCFieldTrials.h b/webrtc/sdk/objc/Framework/Headers/WebRTC/RTCFieldTrials.h index 85430b78e7..a9d68066b5 100644 --- a/webrtc/sdk/objc/Framework/Headers/WebRTC/RTCFieldTrials.h +++ b/webrtc/sdk/objc/Framework/Headers/WebRTC/RTCFieldTrials.h @@ -17,11 +17,23 @@ RTC_EXTERN NSString * const kRTCFieldTrialAudioSendSideBweKey; RTC_EXTERN NSString * const kRTCFieldTrialSendSideBweWithOverheadKey; RTC_EXTERN NSString * const kRTCFieldTrialFlexFec03AdvertisedKey; RTC_EXTERN NSString * const kRTCFieldTrialFlexFec03Key; +RTC_EXTERN NSString * const kRTCFieldTrialImprovedBitrateEstimateKey; RTC_EXTERN NSString * const kRTCFieldTrialH264HighProfileKey; /** The valid value for field trials above. */ RTC_EXTERN NSString * const kRTCFieldTrialEnabledValue; +/** Use a string returned by RTCFieldTrialMedianSlopeFilterValue as the value. */ +RTC_EXTERN NSString * const kRTCFieldTrialMedianSlopeFilterKey; +RTC_EXTERN NSString *RTCFieldTrialMedianSlopeFilterValue( + size_t windowSize, double thresholdGain); + +/** Use a string returned by RTCFieldTrialTrendlineFilterValue as the value. */ +RTC_EXTERN NSString * const kRTCFieldTrialTrendlineFilterKey; +/** Returns a valid value for kRTCFieldTrialTrendlineFilterKey. */ +RTC_EXTERN NSString *RTCFieldTrialTrendlineFilterValue( + size_t windowSize, double smoothingCoeff, double thresholdGain); + /** Initialize field trials using a dictionary mapping field trial keys to their values. See above * for valid keys and values. * Must be called before any other call into WebRTC. See: