diff --git a/modules/congestion_controller/rtp/BUILD.gn b/modules/congestion_controller/rtp/BUILD.gn new file mode 100644 index 0000000000..a75d5d43f1 --- /dev/null +++ b/modules/congestion_controller/rtp/BUILD.gn @@ -0,0 +1,276 @@ +# Copyright (c) 2014 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. + +import("../../../webrtc.gni") + +config("bwe_test_logging") { + if (rtc_enable_bwe_test_logging) { + defines = [ "BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=1" ] + } else { + defines = [ "BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=0" ] + } +} + +rtc_static_library("congestion_controller") { + visibility = [ "*" ] + configs += [ ":bwe_test_logging" ] + sources = [ + "include/receive_side_congestion_controller.h", + "include/send_side_congestion_controller.h", + "pacer_controller.cc", + "pacer_controller.h", + "receive_side_congestion_controller.cc", + "send_side_congestion_controller.cc", + ] + + # TODO(jschuh): Bug 1348: fix this warning. + configs += [ "//build/config/compiler:no_size_t_to_int_warning" ] + + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + + deps = [ + ":goog_cc", + ":transport_feedback", + "../..:module_api", + "../../..:webrtc_common", + "../../../rtc_base:checks", + "../../../rtc_base:rate_limiter", + "../../../rtc_base:rtc_task_queue_api", + "../../../rtc_base:sequenced_task_checker", + "../../../system_wrappers", + "../../../system_wrappers:field_trial_api", + "../../../system_wrappers:metrics_api", + "../../../system_wrappers:runtime_enabled_features_api", + "../../bitrate_controller", + "../../pacing", + "../../remote_bitrate_estimator", + "../../rtp_rtcp:rtp_rtcp_format", + "network_control", + ] + + if (!build_with_mozilla) { + deps += [ "../../../rtc_base:rtc_base" ] + } +} + +rtc_static_library("transport_feedback") { + visibility = [ "*" ] + sources = [ + "send_time_history.cc", + "send_time_history.h", + "transport_feedback_adapter.cc", + "transport_feedback_adapter.h", + ] + + deps = [ + "../..:module_api", + "../../../rtc_base:checks", + "../../../rtc_base:rtc_base_approved", + "../../../system_wrappers", + "../../rtp_rtcp:rtp_rtcp_format", + ] +} + +rtc_static_library("goog_cc") { + configs += [ ":bwe_test_logging" ] + sources = [ + "alr_detector.cc", + "alr_detector.h", + "goog_cc_network_control.cc", + "goog_cc_network_control.h", + "include/goog_cc_factory.h", + "probe_controller.cc", + "probe_controller.h", + ] + + # TODO(jschuh): Bug 1348: fix this warning. + configs += [ "//build/config/compiler:no_size_t_to_int_warning" ] + + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + + deps = [ + ":delay_based_bwe", + ":estimators", + "../..:module_api", + "../../..:webrtc_common", + "../../../:typedefs", + "../../../api:optional", + "../../../logging:rtc_event_log_api", + "../../../logging:rtc_event_pacing", + "../../../rtc_base:checks", + "../../../rtc_base:rtc_base_approved", + "../../../rtc_base/experiments:alr_experiment", + "../../../system_wrappers", + "../../../system_wrappers:field_trial_api", + "../../../system_wrappers:metrics_api", + "../../bitrate_controller", + "../../pacing", + "../../remote_bitrate_estimator", + "../../rtp_rtcp:rtp_rtcp_format", + "network_control", + ] +} + +rtc_source_set("estimators") { + configs += [ ":bwe_test_logging" ] + sources = [ + "acknowledged_bitrate_estimator.cc", + "acknowledged_bitrate_estimator.h", + "bitrate_estimator.cc", + "bitrate_estimator.h", + "delay_increase_detector_interface.h", + "median_slope_estimator.cc", + "median_slope_estimator.h", + "probe_bitrate_estimator.cc", + "probe_bitrate_estimator.h", + "trendline_estimator.cc", + "trendline_estimator.h", + ] + + # TODO(jschuh): Bug 1348: fix this warning. + configs += [ "//build/config/compiler:no_size_t_to_int_warning" ] + + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + + deps = [ + "../../../api:optional", + "../../../logging:rtc_event_bwe", + "../../../logging:rtc_event_log_api", + "../../../rtc_base:checks", + "../../../rtc_base:rtc_base_approved", + "../../../rtc_base:rtc_numerics", + "../../../system_wrappers:field_trial_api", + "../../../system_wrappers:metrics_api", + "../../remote_bitrate_estimator", + "../../rtp_rtcp:rtp_rtcp_format", + ] +} + +rtc_source_set("delay_based_bwe") { + configs += [ ":bwe_test_logging" ] + sources = [ + "delay_based_bwe.cc", + "delay_based_bwe.h", + ] + deps = [ + ":estimators", + "../../../:typedefs", + "../../../logging:rtc_event_bwe", + "../../../logging:rtc_event_log_api", + "../../../rtc_base:checks", + "../../../rtc_base:rtc_base_approved", + "../../../system_wrappers:field_trial_api", + "../../../system_wrappers:metrics_api", + "../../pacing", + "../../remote_bitrate_estimator", + ] + + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } +} + +if (rtc_include_tests) { + rtc_source_set("congestion_controller_unittests") { + testonly = true + + sources = [ + "congestion_controller_unittests_helper.cc", + "congestion_controller_unittests_helper.h", + "receive_side_congestion_controller_unittest.cc", + "send_side_congestion_controller_unittest.cc", + "send_time_history_unittest.cc", + "transport_feedback_adapter_unittest.cc", + ] + deps = [ + ":congestion_controller", + ":goog_cc_unittests", + ":mock_congestion_controller", + ":transport_feedback", + "../../../logging:mocks", + "../../../rtc_base:checks", + "../../../rtc_base:rtc_base", + "../../../rtc_base:rtc_base_approved", + "../../../rtc_base:rtc_base_tests_utils", + "../../../system_wrappers", + "../../../test:field_trial", + "../../../test:test_support", + "../../bitrate_controller:mocks", + "../../pacing:mock_paced_sender", + "../../pacing:pacing", + "../../remote_bitrate_estimator:remote_bitrate_estimator", + "../../rtp_rtcp:rtp_rtcp_format", + "network_control", + "//testing/gmock", + ] + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + } + + rtc_source_set("goog_cc_unittests") { + testonly = true + + sources = [ + "acknowledged_bitrate_estimator_unittest.cc", + "alr_detector_unittest.cc", + "delay_based_bwe_unittest.cc", + "delay_based_bwe_unittest_helper.cc", + "delay_based_bwe_unittest_helper.h", + "median_slope_estimator_unittest.cc", + "probe_bitrate_estimator_unittest.cc", + "probe_controller_unittest.cc", + "trendline_estimator_unittest.cc", + ] + deps = [ + ":delay_based_bwe", + ":estimators", + ":goog_cc", + "../../../rtc_base:checks", + "../../../rtc_base:rtc_base_approved", + "../../../rtc_base:rtc_base_tests_utils", + "../../../rtc_base/experiments:alr_experiment", + "../../../system_wrappers", + "../../../test:field_trial", + "../../../test:test_support", + "../../pacing", + "../../remote_bitrate_estimator", + "../../rtp_rtcp:rtp_rtcp_format", + "network_control", + "network_control:network_control_unittests", + "//testing/gmock", + ] + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + } + + rtc_source_set("mock_congestion_controller") { + testonly = true + sources = [ + "include/mock/mock_congestion_observer.h", + "include/mock/mock_send_side_congestion_controller.h", + ] + deps = [ + ":congestion_controller", + "../../../test:test_support", + ] + } +} diff --git a/modules/congestion_controller/rtp/acknowledged_bitrate_estimator.cc b/modules/congestion_controller/rtp/acknowledged_bitrate_estimator.cc new file mode 100644 index 0000000000..8858b6eed6 --- /dev/null +++ b/modules/congestion_controller/rtp/acknowledged_bitrate_estimator.cc @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2017 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 "modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h" + +#include + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "rtc_base/ptr_util.h" + +namespace webrtc { + +namespace { +bool IsInSendTimeHistory(const PacketFeedback& packet) { + return packet.send_time_ms != PacketFeedback::kNoSendTime; +} +} // namespace + +AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator() + : AcknowledgedBitrateEstimator(rtc::MakeUnique()) {} + +AcknowledgedBitrateEstimator::~AcknowledgedBitrateEstimator() {} + +AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator( + std::unique_ptr bitrate_estimator) + : bitrate_estimator_(std::move(bitrate_estimator)) {} + +void AcknowledgedBitrateEstimator::IncomingPacketFeedbackVector( + const std::vector& packet_feedback_vector) { + RTC_DCHECK(std::is_sorted(packet_feedback_vector.begin(), + packet_feedback_vector.end(), + PacketFeedbackComparator())); + for (const auto& packet : packet_feedback_vector) { + if (IsInSendTimeHistory(packet)) { + MaybeExpectFastRateChange(packet.send_time_ms); + bitrate_estimator_->Update(packet.arrival_time_ms, + rtc::dchecked_cast(packet.payload_size)); + } + } +} + +rtc::Optional AcknowledgedBitrateEstimator::bitrate_bps() const { + return bitrate_estimator_->bitrate_bps(); +} + +void AcknowledgedBitrateEstimator::SetAlrEndedTimeMs( + int64_t alr_ended_time_ms) { + alr_ended_time_ms_.emplace(alr_ended_time_ms); +} + +void AcknowledgedBitrateEstimator::MaybeExpectFastRateChange( + int64_t packet_send_time_ms) { + if (alr_ended_time_ms_ && packet_send_time_ms > *alr_ended_time_ms_) { + bitrate_estimator_->ExpectFastRateChange(); + alr_ended_time_ms_.reset(); + } +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h b/modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h new file mode 100644 index 0000000000..2d57ec82fb --- /dev/null +++ b/modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017 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 MODULES_CONGESTION_CONTROLLER_RTP_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_ + +#include +#include + +#include "api/optional.h" +#include "modules/congestion_controller/rtp/bitrate_estimator.h" + +namespace webrtc { + +struct PacketFeedback; + +class AcknowledgedBitrateEstimator { + public: + explicit AcknowledgedBitrateEstimator( + std::unique_ptr bitrate_estimator); + + AcknowledgedBitrateEstimator(); + ~AcknowledgedBitrateEstimator(); + + void IncomingPacketFeedbackVector( + const std::vector& packet_feedback_vector); + rtc::Optional bitrate_bps() const; + void SetAlrEndedTimeMs(int64_t alr_ended_time_ms); + + private: + void MaybeExpectFastRateChange(int64_t packet_arrival_time_ms); + rtc::Optional alr_ended_time_ms_; + std::unique_ptr bitrate_estimator_; +}; + +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_ diff --git a/modules/congestion_controller/rtp/acknowledged_bitrate_estimator_unittest.cc b/modules/congestion_controller/rtp/acknowledged_bitrate_estimator_unittest.cc new file mode 100644 index 0000000000..c7720a85ca --- /dev/null +++ b/modules/congestion_controller/rtp/acknowledged_bitrate_estimator_unittest.cc @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2017 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 "modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h" + +#include + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "rtc_base/fakeclock.h" +#include "rtc_base/ptr_util.h" +#include "test/gmock.h" +#include "test/gtest.h" + +using testing::_; +using testing::NiceMock; +using testing::InSequence; +using testing::Return; + +namespace webrtc { + +namespace { + +constexpr int64_t kFirstArrivalTimeMs = 10; +constexpr int64_t kFirstSendTimeMs = 10; +constexpr uint16_t kSequenceNumber = 1; +constexpr size_t kPayloadSize = 10; + +class MockBitrateEstimator : public BitrateEstimator { + public: + MOCK_METHOD2(Update, void(int64_t now_ms, int bytes)); + MOCK_CONST_METHOD0(bitrate_bps, rtc::Optional()); + MOCK_METHOD0(ExpectFastRateChange, void()); +}; + +struct AcknowledgedBitrateEstimatorTestStates { + std::unique_ptr acknowledged_bitrate_estimator; + MockBitrateEstimator* mock_bitrate_estimator; +}; + +AcknowledgedBitrateEstimatorTestStates CreateTestStates() { + AcknowledgedBitrateEstimatorTestStates states; + auto mock_bitrate_estimator = rtc::MakeUnique(); + states.mock_bitrate_estimator = mock_bitrate_estimator.get(); + states.acknowledged_bitrate_estimator = + rtc::MakeUnique( + std::move(mock_bitrate_estimator)); + return states; +} + +std::vector CreateFeedbackVector() { + std::vector packet_feedback_vector; + const PacedPacketInfo pacing_info; + packet_feedback_vector.push_back( + PacketFeedback(kFirstArrivalTimeMs, kFirstSendTimeMs, kSequenceNumber, + kPayloadSize, pacing_info)); + packet_feedback_vector.push_back( + PacketFeedback(kFirstArrivalTimeMs + 10, kFirstSendTimeMs + 10, + kSequenceNumber, kPayloadSize + 10, pacing_info)); + return packet_feedback_vector; +} + +} // anonymous namespace + +TEST(TestAcknowledgedBitrateEstimator, DontAddPacketsWhichAreNotInSendHistory) { + auto states = CreateTestStates(); + std::vector packet_feedback_vector; + packet_feedback_vector.push_back( + PacketFeedback(kFirstArrivalTimeMs, kSequenceNumber)); + EXPECT_CALL(*states.mock_bitrate_estimator, Update(_, _)).Times(0); + states.acknowledged_bitrate_estimator->IncomingPacketFeedbackVector( + packet_feedback_vector); +} + +TEST(TestAcknowledgedBitrateEstimator, UpdateBandwith) { + auto states = CreateTestStates(); + auto packet_feedback_vector = CreateFeedbackVector(); + { + InSequence dummy; + EXPECT_CALL( + *states.mock_bitrate_estimator, + Update(packet_feedback_vector[0].arrival_time_ms, + static_cast(packet_feedback_vector[0].payload_size))) + .Times(1); + EXPECT_CALL( + *states.mock_bitrate_estimator, + Update(packet_feedback_vector[1].arrival_time_ms, + static_cast(packet_feedback_vector[1].payload_size))) + .Times(1); + } + states.acknowledged_bitrate_estimator->IncomingPacketFeedbackVector( + packet_feedback_vector); +} + +TEST(TestAcknowledgedBitrateEstimator, ExpectFastRateChangeWhenLeftAlr) { + auto states = CreateTestStates(); + auto packet_feedback_vector = CreateFeedbackVector(); + { + InSequence dummy; + EXPECT_CALL( + *states.mock_bitrate_estimator, + Update(packet_feedback_vector[0].arrival_time_ms, + static_cast(packet_feedback_vector[0].payload_size))) + .Times(1); + EXPECT_CALL(*states.mock_bitrate_estimator, ExpectFastRateChange()) + .Times(1); + EXPECT_CALL( + *states.mock_bitrate_estimator, + Update(packet_feedback_vector[1].arrival_time_ms, + static_cast(packet_feedback_vector[1].payload_size))) + .Times(1); + } + states.acknowledged_bitrate_estimator->SetAlrEndedTimeMs(kFirstArrivalTimeMs + + 1); + states.acknowledged_bitrate_estimator->IncomingPacketFeedbackVector( + packet_feedback_vector); +} + +TEST(TestAcknowledgedBitrateEstimator, ReturnBitrate) { + auto states = CreateTestStates(); + rtc::Optional return_value(42); + EXPECT_CALL(*states.mock_bitrate_estimator, bitrate_bps()) + .Times(1) + .WillOnce(Return(return_value)); + EXPECT_EQ(return_value, states.acknowledged_bitrate_estimator->bitrate_bps()); +} + +} // namespace webrtc*/ diff --git a/modules/congestion_controller/rtp/alr_detector.cc b/modules/congestion_controller/rtp/alr_detector.cc new file mode 100644 index 0000000000..2a36d551f3 --- /dev/null +++ b/modules/congestion_controller/rtp/alr_detector.cc @@ -0,0 +1,95 @@ +/* + * 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 "modules/congestion_controller/rtp/alr_detector.h" + +#include +#include +#include + +#include "logging/rtc_event_log/events/rtc_event_alr_state.h" +#include "logging/rtc_event_log/rtc_event_log.h" +#include "rtc_base/checks.h" +#include "rtc_base/experiments/alr_experiment.h" +#include "rtc_base/format_macros.h" +#include "rtc_base/logging.h" +#include "rtc_base/ptr_util.h" +#include "rtc_base/timeutils.h" +#include "system_wrappers/include/field_trial.h" + +namespace webrtc { +AlrDetector::AlrDetector() : AlrDetector(nullptr) {} + +AlrDetector::AlrDetector(RtcEventLog* event_log) + : bandwidth_usage_percent_(kDefaultAlrBandwidthUsagePercent), + alr_start_budget_level_percent_(kDefaultAlrStartBudgetLevelPercent), + alr_stop_budget_level_percent_(kDefaultAlrStopBudgetLevelPercent), + alr_budget_(0, true), + event_log_(event_log) { + RTC_CHECK(AlrExperimentSettings::MaxOneFieldTrialEnabled()); + rtc::Optional experiment_settings = + AlrExperimentSettings::CreateFromFieldTrial( + AlrExperimentSettings::kScreenshareProbingBweExperimentName); + if (!experiment_settings) { + experiment_settings = AlrExperimentSettings::CreateFromFieldTrial( + AlrExperimentSettings::kStrictPacingAndProbingExperimentName); + } + if (experiment_settings) { + alr_stop_budget_level_percent_ = + experiment_settings->alr_stop_budget_level_percent; + alr_start_budget_level_percent_ = + experiment_settings->alr_start_budget_level_percent; + bandwidth_usage_percent_ = experiment_settings->alr_bandwidth_usage_percent; + } +} + +AlrDetector::~AlrDetector() {} + +void AlrDetector::OnBytesSent(size_t bytes_sent, int64_t send_time_ms) { + if (!last_send_time_ms_.has_value()) { + last_send_time_ms_ = send_time_ms; + // Since the duration for sending the bytes is unknwon, return without + // updating alr state. + return; + } + int64_t delta_time_ms = send_time_ms - *last_send_time_ms_; + last_send_time_ms_ = send_time_ms; + + alr_budget_.UseBudget(bytes_sent); + alr_budget_.IncreaseBudget(delta_time_ms); + bool state_changed = false; + if (alr_budget_.budget_level_percent() > alr_start_budget_level_percent_ && + !alr_started_time_ms_) { + alr_started_time_ms_.emplace(rtc::TimeMillis()); + state_changed = true; + } else if (alr_budget_.budget_level_percent() < + alr_stop_budget_level_percent_ && + alr_started_time_ms_) { + state_changed = true; + alr_started_time_ms_.reset(); + } + if (event_log_ && state_changed) { + event_log_->Log( + rtc::MakeUnique(alr_started_time_ms_.has_value())); + } +} + +void AlrDetector::SetEstimatedBitrate(int bitrate_bps) { + RTC_DCHECK(bitrate_bps); + const auto target_rate_kbps = static_cast(bitrate_bps) * + bandwidth_usage_percent_ / (1000 * 100); + alr_budget_.set_target_rate_kbps(rtc::dchecked_cast(target_rate_kbps)); +} + +rtc::Optional AlrDetector::GetApplicationLimitedRegionStartTime() + const { + return alr_started_time_ms_; +} +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/alr_detector.h b/modules/congestion_controller/rtp/alr_detector.h new file mode 100644 index 0000000000..dc609280ca --- /dev/null +++ b/modules/congestion_controller/rtp/alr_detector.h @@ -0,0 +1,74 @@ +/* + * 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 MODULES_CONGESTION_CONTROLLER_RTP_ALR_DETECTOR_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_ALR_DETECTOR_H_ + +#include "api/optional.h" +#include "common_types.h" // NOLINT(build/include) +#include "modules/pacing/interval_budget.h" +#include "modules/pacing/paced_sender.h" +#include "rtc_base/rate_statistics.h" +#include "typedefs.h" // NOLINT(build/include) + +namespace webrtc { + +class RtcEventLog; + +// Application limited region detector is a class that utilizes signals of +// elapsed time and bytes sent to estimate whether network traffic is +// currently limited by the application's ability to generate traffic. +// +// AlrDetector provides a signal that can be utilized to adjust +// estimate bandwidth. +// Note: This class is not thread-safe. +class AlrDetector { + public: + AlrDetector(); + explicit AlrDetector(RtcEventLog* event_log); + ~AlrDetector(); + + void OnBytesSent(size_t bytes_sent, int64_t send_time_ms); + + // Set current estimated bandwidth. + void SetEstimatedBitrate(int bitrate_bps); + + // Returns time in milliseconds when the current application-limited region + // started or empty result if the sender is currently not application-limited. + rtc::Optional GetApplicationLimitedRegionStartTime() const; + + // Sent traffic percentage as a function of network capacity used to determine + // application-limited region. ALR region start when bandwidth usage drops + // below kAlrStartUsagePercent and ends when it raises above + // kAlrEndUsagePercent. NOTE: This is intentionally conservative at the moment + // until BW adjustments of application limited region is fine tuned. + static constexpr int kDefaultAlrBandwidthUsagePercent = 65; + static constexpr int kDefaultAlrStartBudgetLevelPercent = 80; + static constexpr int kDefaultAlrStopBudgetLevelPercent = 50; + + void UpdateBudgetWithElapsedTime(int64_t delta_time_ms); + void UpdateBudgetWithBytesSent(size_t bytes_sent); + + private: + int bandwidth_usage_percent_; + int alr_start_budget_level_percent_; + int alr_stop_budget_level_percent_; + + rtc::Optional last_send_time_ms_; + + IntervalBudget alr_budget_; + rtc::Optional alr_started_time_ms_; + + RtcEventLog* event_log_; +}; + +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_ALR_DETECTOR_H_ diff --git a/modules/congestion_controller/rtp/alr_detector_unittest.cc b/modules/congestion_controller/rtp/alr_detector_unittest.cc new file mode 100644 index 0000000000..82ad4d6252 --- /dev/null +++ b/modules/congestion_controller/rtp/alr_detector_unittest.cc @@ -0,0 +1,175 @@ +/* + * 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 "modules/congestion_controller/rtp/alr_detector.h" + +#include "rtc_base/experiments/alr_experiment.h" +#include "test/field_trial.h" +#include "test/gtest.h" + +namespace { + +constexpr int kEstimatedBitrateBps = 300000; + +} // namespace + +namespace webrtc { + +namespace { +class SimulateOutgoingTrafficIn { + public: + explicit SimulateOutgoingTrafficIn(AlrDetector* alr_detector, + int64_t* timestamp_ms) + : alr_detector_(alr_detector), timestamp_ms_(timestamp_ms) { + RTC_CHECK(alr_detector_); + } + + SimulateOutgoingTrafficIn& ForTimeMs(int time_ms) { + interval_ms_ = time_ms; + ProduceTraffic(); + return *this; + } + + SimulateOutgoingTrafficIn& AtPercentOfEstimatedBitrate(int usage_percentage) { + usage_percentage_.emplace(usage_percentage); + ProduceTraffic(); + return *this; + } + + private: + void ProduceTraffic() { + if (!interval_ms_ || !usage_percentage_) + return; + const int kTimeStepMs = 10; + for (int t = 0; t < *interval_ms_; t += kTimeStepMs) { + *timestamp_ms_ += kTimeStepMs; + alr_detector_->OnBytesSent(kEstimatedBitrateBps * *usage_percentage_ * + kTimeStepMs / (8 * 100 * 1000), + *timestamp_ms_); + } + int remainder_ms = *interval_ms_ % kTimeStepMs; + if (remainder_ms > 0) { + *timestamp_ms_ += kTimeStepMs; + alr_detector_->OnBytesSent(kEstimatedBitrateBps * *usage_percentage_ * + remainder_ms / (8 * 100 * 1000), + *timestamp_ms_); + } + } + AlrDetector* const alr_detector_; + int64_t* timestamp_ms_; + rtc::Optional interval_ms_; + rtc::Optional usage_percentage_; +}; +} // namespace + +class AlrDetectorTest : public testing::Test { + public: + void SetUp() override { + alr_detector_.SetEstimatedBitrate(kEstimatedBitrateBps); + } + + protected: + AlrDetector alr_detector_; + int64_t timestamp_ms_ = 1000; +}; + +TEST_F(AlrDetectorTest, AlrDetection) { + // Start in non-ALR state. + EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime()); + + // Stay in non-ALR state when usage is close to 100%. + SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_) + .ForTimeMs(1000) + .AtPercentOfEstimatedBitrate(90); + EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime()); + + // Verify that we ALR starts when bitrate drops below 20%. + SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_) + .ForTimeMs(1500) + .AtPercentOfEstimatedBitrate(20); + EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime()); + + // Verify that ALR ends when usage is above 65%. + SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_) + .ForTimeMs(4000) + .AtPercentOfEstimatedBitrate(100); + EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime()); +} + +TEST_F(AlrDetectorTest, ShortSpike) { + // Start in non-ALR state. + EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime()); + + // Verify that we ALR starts when bitrate drops below 20%. + SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_) + .ForTimeMs(1000) + .AtPercentOfEstimatedBitrate(20); + EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime()); + + // Verify that we stay in ALR region even after a short bitrate spike. + SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_) + .ForTimeMs(100) + .AtPercentOfEstimatedBitrate(150); + EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime()); + + // ALR ends when usage is above 65%. + SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_) + .ForTimeMs(3000) + .AtPercentOfEstimatedBitrate(100); + EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime()); +} + +TEST_F(AlrDetectorTest, BandwidthEstimateChanges) { + // Start in non-ALR state. + EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime()); + + // ALR starts when bitrate drops below 20%. + SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_) + .ForTimeMs(1000) + .AtPercentOfEstimatedBitrate(20); + EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime()); + + // When bandwidth estimate drops the detector should stay in ALR mode and quit + // it shortly afterwards as the sender continues sending the same amount of + // traffic. This is necessary to ensure that ProbeController can still react + // to the BWE drop by initiating a new probe. + alr_detector_.SetEstimatedBitrate(kEstimatedBitrateBps / 5); + EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime()); + SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_) + .ForTimeMs(1000) + .AtPercentOfEstimatedBitrate(50); + EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime()); +} + +TEST_F(AlrDetectorTest, ParseControlFieldTrial) { + webrtc::test::ScopedFieldTrials field_trial( + "WebRTC-ProbingScreenshareBwe/Control/"); + rtc::Optional parsed_params = + AlrExperimentSettings::CreateFromFieldTrial( + "WebRTC-ProbingScreenshareBwe"); + EXPECT_FALSE(static_cast(parsed_params)); +} + +TEST_F(AlrDetectorTest, ParseActiveFieldTrial) { + webrtc::test::ScopedFieldTrials field_trial( + "WebRTC-ProbingScreenshareBwe/1.1,2875,85,20,-20,1/"); + rtc::Optional parsed_params = + AlrExperimentSettings::CreateFromFieldTrial( + "WebRTC-ProbingScreenshareBwe"); + ASSERT_TRUE(static_cast(parsed_params)); + EXPECT_EQ(1.1f, parsed_params->pacing_factor); + EXPECT_EQ(2875, parsed_params->max_paced_queue_time); + EXPECT_EQ(85, parsed_params->alr_bandwidth_usage_percent); + EXPECT_EQ(20, parsed_params->alr_start_budget_level_percent); + EXPECT_EQ(-20, parsed_params->alr_stop_budget_level_percent); + EXPECT_EQ(1, parsed_params->group_id); +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/bitrate_estimator.cc b/modules/congestion_controller/rtp/bitrate_estimator.cc new file mode 100644 index 0000000000..e682e64fde --- /dev/null +++ b/modules/congestion_controller/rtp/bitrate_estimator.cc @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2017 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 "modules/congestion_controller/rtp/bitrate_estimator.h" + +#include + +#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" + +namespace webrtc { + +namespace { +constexpr int kInitialRateWindowMs = 500; +constexpr int kRateWindowMs = 150; +} // namespace + +BitrateEstimator::BitrateEstimator() + : sum_(0), + current_win_ms_(0), + prev_time_ms_(-1), + bitrate_estimate_(-1.0f), + bitrate_estimate_var_(50.0f) {} + +BitrateEstimator::~BitrateEstimator() = default; + +void BitrateEstimator::Update(int64_t now_ms, int bytes) { + 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. + if (bitrate_estimate_ < 0.f) + rate_window_ms = kInitialRateWindowMs; + float bitrate_sample = UpdateWindow(now_ms, bytes, rate_window_ms); + if (bitrate_sample < 0.0f) + return; + if (bitrate_estimate_ < 0.0f) { + // This is the very first sample we get. Use it to initialize the estimate. + bitrate_estimate_ = bitrate_sample; + return; + } + // Define the sample uncertainty as a function of how far away it is from the + // current estimate. + float sample_uncertainty = + 10.0f * std::abs(bitrate_estimate_ - bitrate_sample) / bitrate_estimate_; + float sample_var = sample_uncertainty * sample_uncertainty; + // Update a bayesian estimate of the rate, weighting it lower if the sample + // uncertainty is large. + // The bitrate estimate uncertainty is increased with each update to model + // that the bitrate changes over time. + float pred_bitrate_estimate_var = bitrate_estimate_var_ + 5.f; + bitrate_estimate_ = (sample_var * bitrate_estimate_ + + pred_bitrate_estimate_var * bitrate_sample) / + (sample_var + pred_bitrate_estimate_var); + bitrate_estimate_var_ = sample_var * pred_bitrate_estimate_var / + (sample_var + pred_bitrate_estimate_var); + BWE_TEST_LOGGING_PLOT(1, "acknowledged_bitrate", now_ms, + bitrate_estimate_ * 1000); +} + +float BitrateEstimator::UpdateWindow(int64_t now_ms, + int bytes, + int rate_window_ms) { + // Reset if time moves backwards. + if (now_ms < prev_time_ms_) { + prev_time_ms_ = -1; + sum_ = 0; + current_win_ms_ = 0; + } + if (prev_time_ms_ >= 0) { + current_win_ms_ += now_ms - prev_time_ms_; + // Reset if nothing has been received for more than a full window. + if (now_ms - prev_time_ms_ > rate_window_ms) { + sum_ = 0; + current_win_ms_ %= rate_window_ms; + } + } + prev_time_ms_ = now_ms; + float bitrate_sample = -1.0f; + if (current_win_ms_ >= rate_window_ms) { + bitrate_sample = 8.0f * sum_ / static_cast(rate_window_ms); + current_win_ms_ -= rate_window_ms; + sum_ = 0; + } + sum_ += bytes; + return bitrate_sample; +} + +rtc::Optional BitrateEstimator::bitrate_bps() const { + if (bitrate_estimate_ < 0.f) + return rtc::nullopt; + return bitrate_estimate_ * 1000; +} + +void BitrateEstimator::ExpectFastRateChange() { + // By setting the bitrate-estimate variance to a higher value we allow the + // bitrate to change fast for the next few samples. + bitrate_estimate_var_ += 200; +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/bitrate_estimator.h b/modules/congestion_controller/rtp/bitrate_estimator.h new file mode 100644 index 0000000000..17ad8772e2 --- /dev/null +++ b/modules/congestion_controller/rtp/bitrate_estimator.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 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 MODULES_CONGESTION_CONTROLLER_RTP_BITRATE_ESTIMATOR_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_BITRATE_ESTIMATOR_H_ + +#include + +#include "api/optional.h" + +namespace webrtc { + +// Computes a bayesian estimate of the throughput given acks containing +// the arrival time and payload size. Samples which are far from the current +// estimate or are based on few packets are given a smaller weight, as they +// are considered to be more likely to have been caused by, e.g., delay spikes +// unrelated to congestion. +class BitrateEstimator { + public: + BitrateEstimator(); + virtual ~BitrateEstimator(); + virtual void Update(int64_t now_ms, int bytes); + + virtual rtc::Optional bitrate_bps() const; + + virtual void ExpectFastRateChange(); + + private: + float UpdateWindow(int64_t now_ms, int bytes, int rate_window_ms); + int sum_; + int64_t current_win_ms_; + int64_t prev_time_ms_; + float bitrate_estimate_; + float bitrate_estimate_var_; +}; + +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_BITRATE_ESTIMATOR_H_ diff --git a/modules/congestion_controller/rtp/congestion_controller_unittests_helper.cc b/modules/congestion_controller/rtp/congestion_controller_unittests_helper.cc new file mode 100644 index 0000000000..f2b4467dd7 --- /dev/null +++ b/modules/congestion_controller/rtp/congestion_controller_unittests_helper.cc @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017 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 "modules/congestion_controller/rtp/congestion_controller_unittests_helper.h" + +#include "rtc_base/checks.h" +#include "test/gtest.h" + +namespace webrtc { +void ComparePacketFeedbackVectors(const std::vector& truth, + const std::vector& input) { + ASSERT_EQ(truth.size(), input.size()); + size_t len = truth.size(); + // truth contains the input data for the test, and input is what will be + // sent to the bandwidth estimator. truth.arrival_tims_ms is used to + // populate the transport feedback messages. As these times may be changed + // (because of resolution limits in the packets, and because of the time + // base adjustment performed by the TransportFeedbackAdapter at the first + // packet, the truth[x].arrival_time and input[x].arrival_time may not be + // equal. However, the difference must be the same for all x. + int64_t arrival_time_delta = + truth[0].arrival_time_ms - input[0].arrival_time_ms; + for (size_t i = 0; i < len; ++i) { + RTC_CHECK(truth[i].arrival_time_ms != PacketFeedback::kNotReceived); + if (input[i].arrival_time_ms != PacketFeedback::kNotReceived) { + EXPECT_EQ(truth[i].arrival_time_ms, + input[i].arrival_time_ms + arrival_time_delta); + } + EXPECT_EQ(truth[i].send_time_ms, input[i].send_time_ms); + EXPECT_EQ(truth[i].sequence_number, input[i].sequence_number); + EXPECT_EQ(truth[i].payload_size, input[i].payload_size); + EXPECT_EQ(truth[i].pacing_info, input[i].pacing_info); + } +} +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/congestion_controller_unittests_helper.h b/modules/congestion_controller/rtp/congestion_controller_unittests_helper.h new file mode 100644 index 0000000000..98dfb3ddce --- /dev/null +++ b/modules/congestion_controller/rtp/congestion_controller_unittests_helper.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2017 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 MODULES_CONGESTION_CONTROLLER_RTP_CONGESTION_CONTROLLER_UNITTESTS_HELPER_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_CONGESTION_CONTROLLER_UNITTESTS_HELPER_H_ + +#include + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" + +namespace webrtc { +void ComparePacketFeedbackVectors(const std::vector& truth, + const std::vector& input); +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_CONGESTION_CONTROLLER_UNITTESTS_HELPER_H_ diff --git a/modules/congestion_controller/rtp/delay_based_bwe.cc b/modules/congestion_controller/rtp/delay_based_bwe.cc new file mode 100644 index 0000000000..71567f7be3 --- /dev/null +++ b/modules/congestion_controller/rtp/delay_based_bwe.cc @@ -0,0 +1,329 @@ +/* + * 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 "modules/congestion_controller/rtp/delay_based_bwe.h" + +#include +#include +#include +#include + +#include "logging/rtc_event_log/events/rtc_event_bwe_update_delay_based.h" +#include "logging/rtc_event_log/rtc_event_log.h" +#include "modules/congestion_controller/rtp/trendline_estimator.h" +#include "modules/pacing/paced_sender.h" +#include "modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" +#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h" +#include "rtc_base/checks.h" +#include "rtc_base/constructormagic.h" +#include "rtc_base/logging.h" +#include "rtc_base/ptr_util.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/field_trial.h" +#include "system_wrappers/include/metrics.h" +#include "typedefs.h" // NOLINT(build/include) + +namespace { +constexpr int kTimestampGroupLengthMs = 5; +constexpr int kAbsSendTimeFraction = 18; +constexpr int kAbsSendTimeInterArrivalUpshift = 8; +constexpr int kInterArrivalShift = + kAbsSendTimeFraction + kAbsSendTimeInterArrivalUpshift; +constexpr double kTimestampToMs = + 1000.0 / static_cast(1 << kInterArrivalShift); +// This ssrc is used to fulfill the current API but will be removed +// after the API has been changed. +constexpr uint32_t kFixedSsrc = 0; + +// Parameters for linear least squares fit of regression line to noisy data. +constexpr size_t kDefaultTrendlineWindowSize = 20; +constexpr double kDefaultTrendlineSmoothingCoeff = 0.9; +constexpr double kDefaultTrendlineThresholdGain = 4.0; + +constexpr int kMaxConsecutiveFailedLookups = 5; + +const char kBweWindowSizeInPacketsExperiment[] = + "WebRTC-BweWindowSizeInPackets"; + +size_t ReadTrendlineFilterWindowSize() { + std::string experiment_string = + webrtc::field_trial::FindFullName(kBweWindowSizeInPacketsExperiment); + size_t window_size; + int parsed_values = + sscanf(experiment_string.c_str(), "Enabled-%zu", &window_size); + if (parsed_values == 1) { + if (window_size > 1) + return window_size; + RTC_LOG(WARNING) << "Window size must be greater than 1."; + } + RTC_LOG(LS_WARNING) << "Failed to parse parameters for BweTrendlineFilter " + "experiment from field trial string. Using default."; + return kDefaultTrendlineWindowSize; +} +} // namespace + +namespace webrtc { + +DelayBasedBwe::Result::Result() + : updated(false), + probe(false), + target_bitrate_bps(0), + recovered_from_overuse(false) {} + +DelayBasedBwe::Result::Result(bool probe, uint32_t target_bitrate_bps) + : updated(true), + probe(probe), + target_bitrate_bps(target_bitrate_bps), + recovered_from_overuse(false) {} + +DelayBasedBwe::Result::~Result() {} + +DelayBasedBwe::DelayBasedBwe(RtcEventLog* event_log) + : event_log_(event_log), + inter_arrival_(), + delay_detector_(), + last_seen_packet_ms_(-1), + uma_recorded_(false), + probe_bitrate_estimator_(event_log), + trendline_window_size_( + webrtc::field_trial::IsEnabled(kBweWindowSizeInPacketsExperiment) + ? ReadTrendlineFilterWindowSize() + : kDefaultTrendlineWindowSize), + trendline_smoothing_coeff_(kDefaultTrendlineSmoothingCoeff), + trendline_threshold_gain_(kDefaultTrendlineThresholdGain), + consecutive_delayed_feedbacks_(0), + prev_bitrate_(0), + prev_state_(BandwidthUsage::kBwNormal) { + RTC_LOG(LS_INFO) + << "Using Trendline filter for delay change estimation with window size " + << trendline_window_size_; + delay_detector_.reset(new TrendlineEstimator(trendline_window_size_, + trendline_smoothing_coeff_, + trendline_threshold_gain_)); +} + +DelayBasedBwe::~DelayBasedBwe() {} + +DelayBasedBwe::Result DelayBasedBwe::IncomingPacketFeedbackVector( + const std::vector& packet_feedback_vector, + rtc::Optional acked_bitrate_bps, + int64_t at_time_ms) { + RTC_DCHECK(std::is_sorted(packet_feedback_vector.begin(), + packet_feedback_vector.end(), + PacketFeedbackComparator())); + RTC_DCHECK_RUNS_SERIALIZED(&network_race_); + + // TOOD(holmer): An empty feedback vector here likely means that + // all acks were too late and that the send time history had + // timed out. We should reduce the rate when this occurs. + if (packet_feedback_vector.empty()) { + RTC_LOG(LS_WARNING) << "Very late feedback received."; + return DelayBasedBwe::Result(); + } + + if (!uma_recorded_) { + RTC_HISTOGRAM_ENUMERATION(kBweTypeHistogram, + BweNames::kSendSideTransportSeqNum, + BweNames::kBweNamesMax); + uma_recorded_ = true; + } + bool delayed_feedback = true; + bool recovered_from_overuse = false; + BandwidthUsage prev_detector_state = delay_detector_->State(); + for (const auto& packet_feedback : packet_feedback_vector) { + if (packet_feedback.send_time_ms < 0) + continue; + delayed_feedback = false; + IncomingPacketFeedback(packet_feedback, at_time_ms); + if (prev_detector_state == BandwidthUsage::kBwUnderusing && + delay_detector_->State() == BandwidthUsage::kBwNormal) { + recovered_from_overuse = true; + } + prev_detector_state = delay_detector_->State(); + } + + if (delayed_feedback) { + ++consecutive_delayed_feedbacks_; + if (consecutive_delayed_feedbacks_ >= kMaxConsecutiveFailedLookups) { + consecutive_delayed_feedbacks_ = 0; + return OnLongFeedbackDelay(packet_feedback_vector.back().arrival_time_ms); + } + } else { + consecutive_delayed_feedbacks_ = 0; + return MaybeUpdateEstimate(acked_bitrate_bps, recovered_from_overuse, + at_time_ms); + } + return Result(); +} + +DelayBasedBwe::Result DelayBasedBwe::OnLongFeedbackDelay( + int64_t arrival_time_ms) { + // Estimate should always be valid since a start bitrate always is set in the + // Call constructor. An alternative would be to return an empty Result here, + // or to estimate the throughput based on the feedback we received. + RTC_DCHECK(rate_control_.ValidEstimate()); + rate_control_.SetEstimate(rate_control_.LatestEstimate() / 2, + arrival_time_ms); + Result result; + result.updated = true; + result.probe = false; + result.target_bitrate_bps = rate_control_.LatestEstimate(); + RTC_LOG(LS_WARNING) << "Long feedback delay detected, reducing BWE to " + << result.target_bitrate_bps; + return result; +} + +void DelayBasedBwe::IncomingPacketFeedback( + const PacketFeedback& packet_feedback, + int64_t at_time_ms) { + int64_t now_ms = at_time_ms; + // Reset if the stream has timed out. + if (last_seen_packet_ms_ == -1 || + now_ms - last_seen_packet_ms_ > kStreamTimeOutMs) { + inter_arrival_.reset( + new InterArrival((kTimestampGroupLengthMs << kInterArrivalShift) / 1000, + kTimestampToMs, true)); + delay_detector_.reset(new TrendlineEstimator(trendline_window_size_, + trendline_smoothing_coeff_, + trendline_threshold_gain_)); + } + last_seen_packet_ms_ = now_ms; + + uint32_t send_time_24bits = + static_cast( + ((static_cast(packet_feedback.send_time_ms) + << kAbsSendTimeFraction) + + 500) / + 1000) & + 0x00FFFFFF; + // Shift up send time to use the full 32 bits that inter_arrival works with, + // so wrapping works properly. + uint32_t timestamp = send_time_24bits << kAbsSendTimeInterArrivalUpshift; + + uint32_t ts_delta = 0; + int64_t t_delta = 0; + int size_delta = 0; + if (inter_arrival_->ComputeDeltas(timestamp, packet_feedback.arrival_time_ms, + now_ms, packet_feedback.payload_size, + &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.arrival_time_ms); + } + if (packet_feedback.pacing_info.probe_cluster_id != + PacedPacketInfo::kNotAProbe) { + probe_bitrate_estimator_.HandleProbeAndEstimateBitrate(packet_feedback); + } +} + +DelayBasedBwe::Result DelayBasedBwe::MaybeUpdateEstimate( + rtc::Optional acked_bitrate_bps, + bool recovered_from_overuse, + int64_t at_time_ms) { + Result result; + int64_t now_ms = at_time_ms; + + rtc::Optional probe_bitrate_bps = + probe_bitrate_estimator_.FetchAndResetLastEstimatedBitrateBps(); + // Currently overusing the bandwidth. + if (delay_detector_->State() == BandwidthUsage::kBwOverusing) { + if (acked_bitrate_bps && + rate_control_.TimeToReduceFurther(now_ms, *acked_bitrate_bps)) { + result.updated = + UpdateEstimate(now_ms, acked_bitrate_bps, &result.target_bitrate_bps); + } else if (!acked_bitrate_bps && rate_control_.ValidEstimate() && + rate_control_.TimeToReduceFurther( + now_ms, rate_control_.LatestEstimate() / 2 - 1)) { + // Overusing before we have a measured acknowledged bitrate. We check + // TimeToReduceFurther (with a fake acknowledged bitrate) to avoid + // reducing too often. + // TODO(tschumim): Improve this and/or the acknowledged bitrate estimator + // so that we (almost) always have a bitrate estimate. + rate_control_.SetEstimate(rate_control_.LatestEstimate() / 2, now_ms); + result.updated = true; + result.probe = false; + result.target_bitrate_bps = rate_control_.LatestEstimate(); + } + } else { + if (probe_bitrate_bps) { + result.probe = true; + result.updated = true; + result.target_bitrate_bps = *probe_bitrate_bps; + rate_control_.SetEstimate(*probe_bitrate_bps, now_ms); + } else { + result.updated = + UpdateEstimate(now_ms, acked_bitrate_bps, &result.target_bitrate_bps); + result.recovered_from_overuse = recovered_from_overuse; + } + } + BandwidthUsage detector_state = delay_detector_->State(); + if ((result.updated && prev_bitrate_ != result.target_bitrate_bps) || + detector_state != prev_state_) { + uint32_t bitrate_bps = + result.updated ? result.target_bitrate_bps : prev_bitrate_; + + BWE_TEST_LOGGING_PLOT(1, "target_bitrate_bps", now_ms, bitrate_bps); + + if (event_log_) { + event_log_->Log(rtc::MakeUnique( + bitrate_bps, detector_state)); + } + + prev_bitrate_ = bitrate_bps; + prev_state_ = detector_state; + } + return result; +} + +bool DelayBasedBwe::UpdateEstimate(int64_t now_ms, + rtc::Optional acked_bitrate_bps, + uint32_t* target_bitrate_bps) { + // TODO(terelius): RateControlInput::noise_var is deprecated and will be + // removed. In the meantime, we set it to zero. + const RateControlInput input(delay_detector_->State(), acked_bitrate_bps, 0); + *target_bitrate_bps = rate_control_.Update(&input, now_ms); + return rate_control_.ValidEstimate(); +} + +void DelayBasedBwe::OnRttUpdate(int64_t avg_rtt_ms) { + rate_control_.SetRtt(avg_rtt_ms); +} + +bool DelayBasedBwe::LatestEstimate(std::vector* ssrcs, + uint32_t* bitrate_bps) const { + // Currently accessed from both the process thread (see + // ModuleRtpRtcpImpl::Process()) and the configuration thread (see + // Call::GetStats()). Should in the future only be accessed from a single + // thread. + RTC_DCHECK(ssrcs); + RTC_DCHECK(bitrate_bps); + if (!rate_control_.ValidEstimate()) + return false; + + *ssrcs = {kFixedSsrc}; + *bitrate_bps = rate_control_.LatestEstimate(); + return true; +} + +void DelayBasedBwe::SetStartBitrate(int start_bitrate_bps) { + RTC_LOG(LS_WARNING) << "BWE Setting start bitrate to: " << start_bitrate_bps; + rate_control_.SetStartBitrate(start_bitrate_bps); +} + +void DelayBasedBwe::SetMinBitrate(int min_bitrate_bps) { + // Called from both the configuration thread and the network thread. Shouldn't + // be called from the network thread in the future. + rate_control_.SetMinBitrate(min_bitrate_bps); +} + +int64_t DelayBasedBwe::GetExpectedBwePeriodMs() const { + return rate_control_.GetExpectedBandwidthPeriodMs(); +} +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/delay_based_bwe.h b/modules/congestion_controller/rtp/delay_based_bwe.h new file mode 100644 index 0000000000..1e8c0685f1 --- /dev/null +++ b/modules/congestion_controller/rtp/delay_based_bwe.h @@ -0,0 +1,92 @@ +/* + * 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 MODULES_CONGESTION_CONTROLLER_RTP_DELAY_BASED_BWE_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_DELAY_BASED_BWE_H_ + +#include +#include +#include + +#include "modules/congestion_controller/rtp/delay_increase_detector_interface.h" +#include "modules/congestion_controller/rtp/probe_bitrate_estimator.h" +#include "modules/remote_bitrate_estimator/aimd_rate_control.h" +#include "modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" +#include "modules/remote_bitrate_estimator/inter_arrival.h" +#include "rtc_base/checks.h" +#include "rtc_base/constructormagic.h" +#include "rtc_base/race_checker.h" + +namespace webrtc { + +class RtcEventLog; + +class DelayBasedBwe { + public: + static const int64_t kStreamTimeOutMs = 2000; + + struct Result { + Result(); + Result(bool probe, uint32_t target_bitrate_bps); + ~Result(); + bool updated; + bool probe; + uint32_t target_bitrate_bps; + bool recovered_from_overuse; + }; + + explicit DelayBasedBwe(RtcEventLog* event_log); + virtual ~DelayBasedBwe(); + + Result IncomingPacketFeedbackVector( + const std::vector& packet_feedback_vector, + rtc::Optional acked_bitrate_bps, + int64_t at_time_ms); + void OnRttUpdate(int64_t avg_rtt_ms); + bool LatestEstimate(std::vector* ssrcs, + uint32_t* bitrate_bps) const; + void SetStartBitrate(int start_bitrate_bps); + void SetMinBitrate(int min_bitrate_bps); + int64_t GetExpectedBwePeriodMs() const; + + private: + void IncomingPacketFeedback(const PacketFeedback& packet_feedback, + int64_t at_time_ms); + Result OnLongFeedbackDelay(int64_t arrival_time_ms); + Result MaybeUpdateEstimate(rtc::Optional acked_bitrate_bps, + bool request_probe, + int64_t at_time_ms); + // Updates the current remote rate estimate and returns true if a valid + // estimate exists. + bool UpdateEstimate(int64_t now_ms, + rtc::Optional acked_bitrate_bps, + uint32_t* target_bitrate_bps); + + rtc::RaceChecker network_race_; + RtcEventLog* const event_log_; + std::unique_ptr inter_arrival_; + std::unique_ptr delay_detector_; + int64_t last_seen_packet_ms_; + bool uma_recorded_; + AimdRateControl rate_control_; + ProbeBitrateEstimator probe_bitrate_estimator_; + size_t trendline_window_size_; + double trendline_smoothing_coeff_; + double trendline_threshold_gain_; + int consecutive_delayed_feedbacks_; + uint32_t prev_bitrate_; + BandwidthUsage prev_state_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(DelayBasedBwe); +}; + +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_DELAY_BASED_BWE_H_ diff --git a/modules/congestion_controller/rtp/delay_based_bwe_unittest.cc b/modules/congestion_controller/rtp/delay_based_bwe_unittest.cc new file mode 100644 index 0000000000..b30ad4ccf8 --- /dev/null +++ b/modules/congestion_controller/rtp/delay_based_bwe_unittest.cc @@ -0,0 +1,237 @@ +/* + * 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 "modules/congestion_controller/rtp/delay_based_bwe.h" +#include "modules/congestion_controller/rtp/delay_based_bwe_unittest_helper.h" +#include "modules/pacing/paced_sender.h" +#include "rtc_base/constructormagic.h" +#include "system_wrappers/include/clock.h" +#include "test/field_trial.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { +constexpr int kNumProbesCluster0 = 5; +constexpr int kNumProbesCluster1 = 8; +const PacedPacketInfo kPacingInfo0(0, kNumProbesCluster0, 2000); +const PacedPacketInfo kPacingInfo1(1, kNumProbesCluster1, 4000); +constexpr float kTargetUtilizationFraction = 0.95f; +constexpr int64_t kDummyTimestamp = 1000; +} // namespace + +TEST_F(DelayBasedBweTest, NoCrashEmptyFeedback) { + std::vector packet_feedback_vector; + bitrate_estimator_->IncomingPacketFeedbackVector( + packet_feedback_vector, rtc::nullopt, kDummyTimestamp); +} + +TEST_F(DelayBasedBweTest, NoCrashOnlyLostFeedback) { + std::vector packet_feedback_vector; + packet_feedback_vector.push_back(PacketFeedback(PacketFeedback::kNotReceived, + PacketFeedback::kNoSendTime, + 0, 1500, PacedPacketInfo())); + packet_feedback_vector.push_back(PacketFeedback(PacketFeedback::kNotReceived, + PacketFeedback::kNoSendTime, + 1, 1500, PacedPacketInfo())); + bitrate_estimator_->IncomingPacketFeedbackVector( + packet_feedback_vector, rtc::nullopt, kDummyTimestamp); +} + +TEST_F(DelayBasedBweTest, ProbeDetection) { + int64_t now_ms = clock_.TimeInMilliseconds(); + uint16_t seq_num = 0; + + // First burst sent at 8 * 1000 / 10 = 800 kbps. + for (int i = 0; i < kNumProbesCluster0; ++i) { + clock_.AdvanceTimeMilliseconds(10); + now_ms = clock_.TimeInMilliseconds(); + IncomingFeedback(now_ms, now_ms, seq_num++, 1000, kPacingInfo0); + } + EXPECT_TRUE(bitrate_observer_.updated()); + + // Second burst sent at 8 * 1000 / 5 = 1600 kbps. + for (int i = 0; i < kNumProbesCluster1; ++i) { + clock_.AdvanceTimeMilliseconds(5); + now_ms = clock_.TimeInMilliseconds(); + IncomingFeedback(now_ms, now_ms, seq_num++, 1000, kPacingInfo1); + } + + EXPECT_TRUE(bitrate_observer_.updated()); + EXPECT_GT(bitrate_observer_.latest_bitrate(), 1500000u); +} + +TEST_F(DelayBasedBweTest, ProbeDetectionNonPacedPackets) { + int64_t now_ms = clock_.TimeInMilliseconds(); + uint16_t seq_num = 0; + // First burst sent at 8 * 1000 / 10 = 800 kbps, but with every other packet + // not being paced which could mess things up. + for (int i = 0; i < kNumProbesCluster0; ++i) { + clock_.AdvanceTimeMilliseconds(5); + now_ms = clock_.TimeInMilliseconds(); + IncomingFeedback(now_ms, now_ms, seq_num++, 1000, kPacingInfo0); + // Non-paced packet, arriving 5 ms after. + clock_.AdvanceTimeMilliseconds(5); + IncomingFeedback(now_ms, now_ms, seq_num++, 100, PacedPacketInfo()); + } + + EXPECT_TRUE(bitrate_observer_.updated()); + EXPECT_GT(bitrate_observer_.latest_bitrate(), 800000u); +} + +TEST_F(DelayBasedBweTest, ProbeDetectionFasterArrival) { + int64_t now_ms = clock_.TimeInMilliseconds(); + uint16_t seq_num = 0; + // First burst sent at 8 * 1000 / 10 = 800 kbps. + // Arriving at 8 * 1000 / 5 = 1600 kbps. + int64_t send_time_ms = 0; + for (int i = 0; i < kNumProbesCluster0; ++i) { + clock_.AdvanceTimeMilliseconds(1); + send_time_ms += 10; + now_ms = clock_.TimeInMilliseconds(); + IncomingFeedback(now_ms, send_time_ms, seq_num++, 1000, kPacingInfo0); + } + + EXPECT_FALSE(bitrate_observer_.updated()); +} + +TEST_F(DelayBasedBweTest, ProbeDetectionSlowerArrival) { + int64_t now_ms = clock_.TimeInMilliseconds(); + uint16_t seq_num = 0; + // First burst sent at 8 * 1000 / 5 = 1600 kbps. + // Arriving at 8 * 1000 / 7 = 1142 kbps. + // Since the receive rate is significantly below the send rate, we expect to + // use 95% of the estimated capacity. + int64_t send_time_ms = 0; + for (int i = 0; i < kNumProbesCluster1; ++i) { + clock_.AdvanceTimeMilliseconds(7); + send_time_ms += 5; + now_ms = clock_.TimeInMilliseconds(); + IncomingFeedback(now_ms, send_time_ms, seq_num++, 1000, kPacingInfo1); + } + + EXPECT_TRUE(bitrate_observer_.updated()); + EXPECT_NEAR(bitrate_observer_.latest_bitrate(), + kTargetUtilizationFraction * 1140000u, 10000u); +} + +TEST_F(DelayBasedBweTest, ProbeDetectionSlowerArrivalHighBitrate) { + int64_t now_ms = clock_.TimeInMilliseconds(); + uint16_t seq_num = 0; + // Burst sent at 8 * 1000 / 1 = 8000 kbps. + // Arriving at 8 * 1000 / 2 = 4000 kbps. + // Since the receive rate is significantly below the send rate, we expect to + // use 95% of the estimated capacity. + int64_t send_time_ms = 0; + for (int i = 0; i < kNumProbesCluster1; ++i) { + clock_.AdvanceTimeMilliseconds(2); + send_time_ms += 1; + now_ms = clock_.TimeInMilliseconds(); + IncomingFeedback(now_ms, send_time_ms, seq_num++, 1000, kPacingInfo1); + } + + EXPECT_TRUE(bitrate_observer_.updated()); + EXPECT_NEAR(bitrate_observer_.latest_bitrate(), + kTargetUtilizationFraction * 4000000u, 10000u); +} + +TEST_F(DelayBasedBweTest, GetExpectedBwePeriodMs) { + int64_t default_interval_ms = bitrate_estimator_->GetExpectedBwePeriodMs(); + EXPECT_GT(default_interval_ms, 0); + CapacityDropTestHelper(1, true, 333, 0); + int64_t interval_ms = bitrate_estimator_->GetExpectedBwePeriodMs(); + EXPECT_GT(interval_ms, 0); + EXPECT_NE(interval_ms, default_interval_ms); +} + +TEST_F(DelayBasedBweTest, InitialBehavior) { + InitialBehaviorTestHelper(730000); +} + +TEST_F(DelayBasedBweTest, RateIncreaseReordering) { + RateIncreaseReorderingTestHelper(730000); +} +TEST_F(DelayBasedBweTest, RateIncreaseRtpTimestamps) { + RateIncreaseRtpTimestampsTestHelper(627); +} + +TEST_F(DelayBasedBweTest, CapacityDropOneStream) { + CapacityDropTestHelper(1, false, 300, 0); +} + +TEST_F(DelayBasedBweTest, CapacityDropPosOffsetChange) { + CapacityDropTestHelper(1, false, 867, 30000); +} + +TEST_F(DelayBasedBweTest, CapacityDropNegOffsetChange) { + CapacityDropTestHelper(1, false, 933, -30000); +} + +TEST_F(DelayBasedBweTest, CapacityDropOneStreamWrap) { + CapacityDropTestHelper(1, true, 333, 0); +} + +TEST_F(DelayBasedBweTest, TestTimestampGrouping) { + TestTimestampGroupingTestHelper(); +} + +TEST_F(DelayBasedBweTest, TestShortTimeoutAndWrap) { + // Simulate a client leaving and rejoining the call after 35 seconds. This + // will make abs send time wrap, so if streams aren't timed out properly + // the next 30 seconds of packets will be out of order. + TestWrappingHelper(35); +} + +TEST_F(DelayBasedBweTest, TestLongTimeoutAndWrap) { + // Simulate a client leaving and rejoining the call after some multiple of + // 64 seconds later. This will cause a zero difference in abs send times due + // to the wrap, but a big difference in arrival time, if streams aren't + // properly timed out. + TestWrappingHelper(10 * 64); +} + +TEST_F(DelayBasedBweTest, TestInitialOveruse) { + const uint32_t kStartBitrate = 300e3; + const uint32_t kInitialCapacityBps = 200e3; + const uint32_t kDummySsrc = 0; + // High FPS to ensure that we send a lot of packets in a short time. + const int kFps = 90; + + stream_generator_->AddStream(new test::RtpStream(kFps, kStartBitrate)); + stream_generator_->set_capacity_bps(kInitialCapacityBps); + + // Needed to initialize the AimdRateControl. + bitrate_estimator_->SetStartBitrate(kStartBitrate); + + // Produce 30 frames (in 1/3 second) and give them to the estimator. + uint32_t bitrate_bps = kStartBitrate; + bool seen_overuse = false; + for (int i = 0; i < 30; ++i) { + bool overuse = GenerateAndProcessFrame(kDummySsrc, bitrate_bps); + // The purpose of this test is to ensure that we back down even if we don't + // have any acknowledged bitrate estimate yet. Hence, if the test works + // as expected, we should not have a measured bitrate yet. + EXPECT_FALSE(acknowledged_bitrate_estimator_->bitrate_bps().has_value()); + if (overuse) { + EXPECT_TRUE(bitrate_observer_.updated()); + EXPECT_NEAR(bitrate_observer_.latest_bitrate(), kStartBitrate / 2, 15000); + bitrate_bps = bitrate_observer_.latest_bitrate(); + seen_overuse = true; + break; + } else if (bitrate_observer_.updated()) { + bitrate_bps = bitrate_observer_.latest_bitrate(); + bitrate_observer_.Reset(); + } + } + EXPECT_TRUE(seen_overuse); + EXPECT_NEAR(bitrate_observer_.latest_bitrate(), kStartBitrate / 2, 15000); +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/delay_based_bwe_unittest_helper.cc b/modules/congestion_controller/rtp/delay_based_bwe_unittest_helper.cc new file mode 100644 index 0000000000..7128371012 --- /dev/null +++ b/modules/congestion_controller/rtp/delay_based_bwe_unittest_helper.cc @@ -0,0 +1,515 @@ +/* + * 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 "modules/congestion_controller/rtp/delay_based_bwe_unittest_helper.h" + +#include +#include +#include + +#include "modules/congestion_controller/rtp/delay_based_bwe.h" +#include "rtc_base/checks.h" +#include "rtc_base/ptr_util.h" + +namespace webrtc { + +constexpr size_t kMtu = 1200; +constexpr uint32_t kAcceptedBitrateErrorBps = 50000; + +// Number of packets needed before we have a valid estimate. +constexpr int kNumInitialPackets = 2; + +constexpr int kInitialProbingPackets = 5; + +namespace test { + +void TestBitrateObserver::OnReceiveBitrateChanged( + const std::vector& ssrcs, + uint32_t bitrate) { + latest_bitrate_ = bitrate; + updated_ = true; +} + +RtpStream::RtpStream(int fps, int bitrate_bps) + : fps_(fps), + bitrate_bps_(bitrate_bps), + next_rtp_time_(0), + sequence_number_(0) { + RTC_CHECK_GT(fps_, 0); +} + +// Generates a new frame for this stream. If called too soon after the +// previous frame, no frame will be generated. The frame is split into +// packets. +int64_t RtpStream::GenerateFrame(int64_t time_now_us, + std::vector* packets) { + if (time_now_us < next_rtp_time_) { + return next_rtp_time_; + } + RTC_CHECK(packets != NULL); + size_t bits_per_frame = (bitrate_bps_ + fps_ / 2) / fps_; + size_t n_packets = + std::max((bits_per_frame + 4 * kMtu) / (8 * kMtu), 1u); + size_t payload_size = (bits_per_frame + 4 * n_packets) / (8 * n_packets); + for (size_t i = 0; i < n_packets; ++i) { + PacketFeedback packet(-1, sequence_number_++); + packet.send_time_ms = (time_now_us + kSendSideOffsetUs) / 1000; + packet.payload_size = payload_size; + packets->push_back(packet); + } + next_rtp_time_ = time_now_us + (1000000 + fps_ / 2) / fps_; + return next_rtp_time_; +} + +// The send-side time when the next frame can be generated. +int64_t RtpStream::next_rtp_time() const { + return next_rtp_time_; +} + +void RtpStream::set_bitrate_bps(int bitrate_bps) { + ASSERT_GE(bitrate_bps, 0); + bitrate_bps_ = bitrate_bps; +} + +int RtpStream::bitrate_bps() const { + return bitrate_bps_; +} + +bool RtpStream::Compare(const std::unique_ptr& lhs, + const std::unique_ptr& rhs) { + return lhs->next_rtp_time_ < rhs->next_rtp_time_; +} + +StreamGenerator::StreamGenerator(int capacity, int64_t time_now) + : capacity_(capacity), prev_arrival_time_us_(time_now) {} + +// Add a new stream. +void StreamGenerator::AddStream(RtpStream* stream) { + streams_.push_back(std::unique_ptr(stream)); +} + +// Set the link capacity. +void StreamGenerator::set_capacity_bps(int capacity_bps) { + ASSERT_GT(capacity_bps, 0); + capacity_ = capacity_bps; +} + +// Divides |bitrate_bps| among all streams. The allocated bitrate per stream +// is decided by the current allocation ratios. +void StreamGenerator::SetBitrateBps(int bitrate_bps) { + ASSERT_GE(streams_.size(), 0u); + int total_bitrate_before = 0; + for (const auto& stream : streams_) { + total_bitrate_before += stream->bitrate_bps(); + } + int64_t bitrate_before = 0; + int total_bitrate_after = 0; + for (const auto& stream : streams_) { + bitrate_before += stream->bitrate_bps(); + int64_t bitrate_after = + (bitrate_before * bitrate_bps + total_bitrate_before / 2) / + total_bitrate_before; + stream->set_bitrate_bps(bitrate_after - total_bitrate_after); + total_bitrate_after += stream->bitrate_bps(); + } + ASSERT_EQ(bitrate_before, total_bitrate_before); + EXPECT_EQ(total_bitrate_after, bitrate_bps); +} + +// TODO(holmer): Break out the channel simulation part from this class to make +// it possible to simulate different types of channels. +int64_t StreamGenerator::GenerateFrame(std::vector* packets, + int64_t time_now_us) { + RTC_CHECK(packets != NULL); + RTC_CHECK(packets->empty()); + RTC_CHECK_GT(capacity_, 0); + auto it = + std::min_element(streams_.begin(), streams_.end(), RtpStream::Compare); + (*it)->GenerateFrame(time_now_us, packets); + int i = 0; + for (PacketFeedback& packet : *packets) { + int capacity_bpus = capacity_ / 1000; + int64_t required_network_time_us = + (8 * 1000 * packet.payload_size + capacity_bpus / 2) / capacity_bpus; + prev_arrival_time_us_ = + std::max(time_now_us + required_network_time_us, + prev_arrival_time_us_ + required_network_time_us); + packet.arrival_time_ms = prev_arrival_time_us_ / 1000; + ++i; + } + it = std::min_element(streams_.begin(), streams_.end(), RtpStream::Compare); + return std::max((*it)->next_rtp_time(), time_now_us); +} +} // namespace test + +DelayBasedBweTest::DelayBasedBweTest() + : clock_(100000000), + acknowledged_bitrate_estimator_( + rtc::MakeUnique()), + bitrate_estimator_(new DelayBasedBwe(nullptr)), + stream_generator_(new test::StreamGenerator(1e6, // Capacity. + clock_.TimeInMicroseconds())), + arrival_time_offset_ms_(0), + first_update_(true) {} + +DelayBasedBweTest::~DelayBasedBweTest() {} + +void DelayBasedBweTest::AddDefaultStream() { + stream_generator_->AddStream(new test::RtpStream(30, 3e5)); +} + +const uint32_t DelayBasedBweTest::kDefaultSsrc = 0; + +void DelayBasedBweTest::IncomingFeedback(int64_t arrival_time_ms, + int64_t send_time_ms, + uint16_t sequence_number, + size_t payload_size) { + IncomingFeedback(arrival_time_ms, send_time_ms, sequence_number, payload_size, + PacedPacketInfo()); +} + +void DelayBasedBweTest::IncomingFeedback(int64_t arrival_time_ms, + int64_t send_time_ms, + uint16_t sequence_number, + size_t payload_size, + const PacedPacketInfo& pacing_info) { + RTC_CHECK_GE(arrival_time_ms + arrival_time_offset_ms_, 0); + PacketFeedback packet(arrival_time_ms + arrival_time_offset_ms_, send_time_ms, + sequence_number, payload_size, pacing_info); + std::vector packets; + packets.push_back(packet); + acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(packets); + DelayBasedBwe::Result result = + bitrate_estimator_->IncomingPacketFeedbackVector( + packets, acknowledged_bitrate_estimator_->bitrate_bps(), + clock_.TimeInMilliseconds()); + const uint32_t kDummySsrc = 0; + if (result.updated) { + bitrate_observer_.OnReceiveBitrateChanged({kDummySsrc}, + result.target_bitrate_bps); + } +} + +// Generates a frame of packets belonging to a stream at a given bitrate and +// with a given ssrc. The stream is pushed through a very simple simulated +// network, and is then given to the receive-side bandwidth estimator. +// Returns true if an over-use was seen, false otherwise. +// The StreamGenerator::updated() should be used to check for any changes in +// target bitrate after the call to this function. +bool DelayBasedBweTest::GenerateAndProcessFrame(uint32_t ssrc, + uint32_t bitrate_bps) { + stream_generator_->SetBitrateBps(bitrate_bps); + std::vector packets; + int64_t next_time_us = + stream_generator_->GenerateFrame(&packets, clock_.TimeInMicroseconds()); + if (packets.empty()) + return false; + + bool overuse = false; + bitrate_observer_.Reset(); + clock_.AdvanceTimeMicroseconds(1000 * packets.back().arrival_time_ms - + clock_.TimeInMicroseconds()); + for (auto& packet : packets) { + RTC_CHECK_GE(packet.arrival_time_ms + arrival_time_offset_ms_, 0); + packet.arrival_time_ms += arrival_time_offset_ms_; + } + + acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(packets); + DelayBasedBwe::Result result = + bitrate_estimator_->IncomingPacketFeedbackVector( + packets, acknowledged_bitrate_estimator_->bitrate_bps(), + clock_.TimeInMilliseconds()); + const uint32_t kDummySsrc = 0; + if (result.updated) { + bitrate_observer_.OnReceiveBitrateChanged({kDummySsrc}, + result.target_bitrate_bps); + if (!first_update_ && result.target_bitrate_bps < bitrate_bps) + overuse = true; + first_update_ = false; + } + + clock_.AdvanceTimeMicroseconds(next_time_us - clock_.TimeInMicroseconds()); + return overuse; +} + +// Run the bandwidth estimator with a stream of |number_of_frames| frames, or +// until it reaches |target_bitrate|. +// Can for instance be used to run the estimator for some time to get it +// into a steady state. +uint32_t DelayBasedBweTest::SteadyStateRun(uint32_t ssrc, + int max_number_of_frames, + uint32_t start_bitrate, + uint32_t min_bitrate, + uint32_t max_bitrate, + uint32_t target_bitrate) { + uint32_t bitrate_bps = start_bitrate; + bool bitrate_update_seen = false; + // Produce |number_of_frames| frames and give them to the estimator. + for (int i = 0; i < max_number_of_frames; ++i) { + bool overuse = GenerateAndProcessFrame(ssrc, bitrate_bps); + if (overuse) { + EXPECT_LT(bitrate_observer_.latest_bitrate(), max_bitrate); + EXPECT_GT(bitrate_observer_.latest_bitrate(), min_bitrate); + bitrate_bps = bitrate_observer_.latest_bitrate(); + bitrate_update_seen = true; + } else if (bitrate_observer_.updated()) { + bitrate_bps = bitrate_observer_.latest_bitrate(); + bitrate_observer_.Reset(); + } + if (bitrate_update_seen && bitrate_bps > target_bitrate) { + break; + } + } + EXPECT_TRUE(bitrate_update_seen); + return bitrate_bps; +} + +void DelayBasedBweTest::InitialBehaviorTestHelper( + uint32_t expected_converge_bitrate) { + const int kFramerate = 50; // 50 fps to avoid rounding errors. + const int kFrameIntervalMs = 1000 / kFramerate; + const PacedPacketInfo kPacingInfo(0, 5, 5000); + uint32_t bitrate_bps = 0; + int64_t send_time_ms = 0; + uint16_t sequence_number = 0; + std::vector ssrcs; + EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps)); + EXPECT_EQ(0u, ssrcs.size()); + clock_.AdvanceTimeMilliseconds(1000); + EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps)); + EXPECT_FALSE(bitrate_observer_.updated()); + bitrate_observer_.Reset(); + clock_.AdvanceTimeMilliseconds(1000); + // Inserting packets for 5 seconds to get a valid estimate. + for (int i = 0; i < 5 * kFramerate + 1 + kNumInitialPackets; ++i) { + // NOTE!!! If the following line is moved under the if case then this test + // wont work on windows realease bots. + PacedPacketInfo pacing_info = + i < kInitialProbingPackets ? kPacingInfo : PacedPacketInfo(); + + if (i == kNumInitialPackets) { + EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps)); + EXPECT_EQ(0u, ssrcs.size()); + EXPECT_FALSE(bitrate_observer_.updated()); + bitrate_observer_.Reset(); + } + IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, + sequence_number++, kMtu, pacing_info); + clock_.AdvanceTimeMilliseconds(1000 / kFramerate); + send_time_ms += kFrameIntervalMs; + } + EXPECT_TRUE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps)); + ASSERT_EQ(1u, ssrcs.size()); + EXPECT_EQ(kDefaultSsrc, ssrcs.front()); + EXPECT_NEAR(expected_converge_bitrate, bitrate_bps, kAcceptedBitrateErrorBps); + EXPECT_TRUE(bitrate_observer_.updated()); + bitrate_observer_.Reset(); + EXPECT_EQ(bitrate_observer_.latest_bitrate(), bitrate_bps); +} + +void DelayBasedBweTest::RateIncreaseReorderingTestHelper( + uint32_t expected_bitrate_bps) { + const int kFramerate = 50; // 50 fps to avoid rounding errors. + const int kFrameIntervalMs = 1000 / kFramerate; + const PacedPacketInfo kPacingInfo(0, 5, 5000); + int64_t send_time_ms = 0; + uint16_t sequence_number = 0; + // Inserting packets for five seconds to get a valid estimate. + for (int i = 0; i < 5 * kFramerate + 1 + kNumInitialPackets; ++i) { + // NOTE!!! If the following line is moved under the if case then this test + // wont work on windows realease bots. + PacedPacketInfo pacing_info = + i < kInitialProbingPackets ? kPacingInfo : PacedPacketInfo(); + + // TODO(sprang): Remove this hack once the single stream estimator is gone, + // as it doesn't do anything in Process(). + if (i == kNumInitialPackets) { + // Process after we have enough frames to get a valid input rate estimate. + + EXPECT_FALSE(bitrate_observer_.updated()); // No valid estimate. + } + IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, + sequence_number++, kMtu, pacing_info); + clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); + send_time_ms += kFrameIntervalMs; + } + EXPECT_TRUE(bitrate_observer_.updated()); + EXPECT_NEAR(expected_bitrate_bps, bitrate_observer_.latest_bitrate(), + kAcceptedBitrateErrorBps); + for (int i = 0; i < 10; ++i) { + clock_.AdvanceTimeMilliseconds(2 * kFrameIntervalMs); + send_time_ms += 2 * kFrameIntervalMs; + IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, + sequence_number + 2, 1000); + IncomingFeedback(clock_.TimeInMilliseconds(), + send_time_ms - kFrameIntervalMs, sequence_number + 1, + 1000); + sequence_number += 2; + } + EXPECT_TRUE(bitrate_observer_.updated()); + EXPECT_NEAR(expected_bitrate_bps, bitrate_observer_.latest_bitrate(), + kAcceptedBitrateErrorBps); +} + +// Make sure we initially increase the bitrate as expected. +void DelayBasedBweTest::RateIncreaseRtpTimestampsTestHelper( + int expected_iterations) { + // This threshold corresponds approximately to increasing linearly with + // bitrate(i) = 1.04 * bitrate(i-1) + 1000 + // until bitrate(i) > 500000, with bitrate(1) ~= 30000. + uint32_t bitrate_bps = 30000; + int iterations = 0; + AddDefaultStream(); + // Feed the estimator with a stream of packets and verify that it reaches + // 500 kbps at the expected time. + while (bitrate_bps < 5e5) { + bool overuse = GenerateAndProcessFrame(kDefaultSsrc, bitrate_bps); + if (overuse) { + EXPECT_GT(bitrate_observer_.latest_bitrate(), bitrate_bps); + bitrate_bps = bitrate_observer_.latest_bitrate(); + bitrate_observer_.Reset(); + } else if (bitrate_observer_.updated()) { + bitrate_bps = bitrate_observer_.latest_bitrate(); + bitrate_observer_.Reset(); + } + ++iterations; + } + ASSERT_EQ(expected_iterations, iterations); +} + +void DelayBasedBweTest::CapacityDropTestHelper( + int number_of_streams, + bool wrap_time_stamp, + uint32_t expected_bitrate_drop_delta, + int64_t receiver_clock_offset_change_ms) { + const int kFramerate = 30; + const int kStartBitrate = 900e3; + const int kMinExpectedBitrate = 800e3; + const int kMaxExpectedBitrate = 1100e3; + const uint32_t kInitialCapacityBps = 1000e3; + const uint32_t kReducedCapacityBps = 500e3; + + int steady_state_time = 0; + if (number_of_streams <= 1) { + steady_state_time = 10; + AddDefaultStream(); + } else { + steady_state_time = 10 * number_of_streams; + int bitrate_sum = 0; + int kBitrateDenom = number_of_streams * (number_of_streams - 1); + for (int i = 0; i < number_of_streams; i++) { + // First stream gets half available bitrate, while the rest share the + // remaining half i.e.: 1/2 = Sum[n/(N*(N-1))] for n=1..N-1 (rounded up) + int bitrate = kStartBitrate / 2; + if (i > 0) { + bitrate = (kStartBitrate * i + kBitrateDenom / 2) / kBitrateDenom; + } + stream_generator_->AddStream(new test::RtpStream(kFramerate, bitrate)); + bitrate_sum += bitrate; + } + ASSERT_EQ(bitrate_sum, kStartBitrate); + } + + // Run in steady state to make the estimator converge. + stream_generator_->set_capacity_bps(kInitialCapacityBps); + uint32_t bitrate_bps = SteadyStateRun( + kDefaultSsrc, steady_state_time * kFramerate, kStartBitrate, + kMinExpectedBitrate, kMaxExpectedBitrate, kInitialCapacityBps); + EXPECT_NEAR(kInitialCapacityBps, bitrate_bps, 180000u); + bitrate_observer_.Reset(); + + // Add an offset to make sure the BWE can handle it. + arrival_time_offset_ms_ += receiver_clock_offset_change_ms; + + // Reduce the capacity and verify the decrease time. + stream_generator_->set_capacity_bps(kReducedCapacityBps); + int64_t overuse_start_time = clock_.TimeInMilliseconds(); + int64_t bitrate_drop_time = -1; + for (int i = 0; i < 100 * number_of_streams; ++i) { + GenerateAndProcessFrame(kDefaultSsrc, bitrate_bps); + if (bitrate_drop_time == -1 && + bitrate_observer_.latest_bitrate() <= kReducedCapacityBps) { + bitrate_drop_time = clock_.TimeInMilliseconds(); + } + if (bitrate_observer_.updated()) + bitrate_bps = bitrate_observer_.latest_bitrate(); + } + + EXPECT_NEAR(expected_bitrate_drop_delta, + bitrate_drop_time - overuse_start_time, 33); +} + +void DelayBasedBweTest::TestTimestampGroupingTestHelper() { + const int kFramerate = 50; // 50 fps to avoid rounding errors. + const int kFrameIntervalMs = 1000 / kFramerate; + int64_t send_time_ms = 0; + uint16_t sequence_number = 0; + // Initial set of frames to increase the bitrate. 6 seconds to have enough + // time for the first estimate to be generated and for Process() to be called. + for (int i = 0; i <= 6 * kFramerate; ++i) { + IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, + sequence_number++, 1000); + + clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); + send_time_ms += kFrameIntervalMs; + } + EXPECT_TRUE(bitrate_observer_.updated()); + EXPECT_GE(bitrate_observer_.latest_bitrate(), 400000u); + + // Insert batches of frames which were sent very close in time. Also simulate + // capacity over-use to see that we back off correctly. + const int kTimestampGroupLength = 15; + for (int i = 0; i < 100; ++i) { + for (int j = 0; j < kTimestampGroupLength; ++j) { + // Insert |kTimestampGroupLength| frames with just 1 timestamp ticks in + // between. Should be treated as part of the same group by the estimator. + IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, + sequence_number++, 100); + clock_.AdvanceTimeMilliseconds(kFrameIntervalMs / kTimestampGroupLength); + send_time_ms += 1; + } + // Increase time until next batch to simulate over-use. + clock_.AdvanceTimeMilliseconds(10); + send_time_ms += kFrameIntervalMs - kTimestampGroupLength; + } + EXPECT_TRUE(bitrate_observer_.updated()); + // Should have reduced the estimate. + EXPECT_LT(bitrate_observer_.latest_bitrate(), 400000u); +} + +void DelayBasedBweTest::TestWrappingHelper(int silence_time_s) { + const int kFramerate = 100; + const int kFrameIntervalMs = 1000 / kFramerate; + int64_t send_time_ms = 0; + uint16_t sequence_number = 0; + + for (size_t i = 0; i < 3000; ++i) { + IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, + sequence_number++, 1000); + clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); + send_time_ms += kFrameIntervalMs; + } + uint32_t bitrate_before = 0; + std::vector ssrcs; + bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_before); + + clock_.AdvanceTimeMilliseconds(silence_time_s * 1000); + send_time_ms += silence_time_s * 1000; + + for (size_t i = 0; i < 24; ++i) { + IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, + sequence_number++, 1000); + clock_.AdvanceTimeMilliseconds(2 * kFrameIntervalMs); + send_time_ms += kFrameIntervalMs; + } + uint32_t bitrate_after = 0; + bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_after); + EXPECT_LT(bitrate_after, bitrate_before); +} +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/delay_based_bwe_unittest_helper.h b/modules/congestion_controller/rtp/delay_based_bwe_unittest_helper.h new file mode 100644 index 0000000000..19fbb78e88 --- /dev/null +++ b/modules/congestion_controller/rtp/delay_based_bwe_unittest_helper.h @@ -0,0 +1,178 @@ +/* + * 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 MODULES_CONGESTION_CONTROLLER_RTP_DELAY_BASED_BWE_UNITTEST_HELPER_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_DELAY_BASED_BWE_UNITTEST_HELPER_H_ + +#include +#include +#include +#include +#include + +#include "modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h" +#include "modules/congestion_controller/rtp/delay_based_bwe.h" +#include "modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" +#include "rtc_base/constructormagic.h" +#include "system_wrappers/include/clock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace test { + +class TestBitrateObserver : public RemoteBitrateObserver { + public: + TestBitrateObserver() : updated_(false), latest_bitrate_(0) {} + virtual ~TestBitrateObserver() {} + + void OnReceiveBitrateChanged(const std::vector& ssrcs, + uint32_t bitrate) override; + + void Reset() { updated_ = false; } + + bool updated() const { return updated_; } + + uint32_t latest_bitrate() const { return latest_bitrate_; } + + private: + bool updated_; + uint32_t latest_bitrate_; +}; + +class RtpStream { + public: + enum { kSendSideOffsetUs = 1000000 }; + + RtpStream(int fps, int bitrate_bps); + + // Generates a new frame for this stream. If called too soon after the + // previous frame, no frame will be generated. The frame is split into + // packets. + int64_t GenerateFrame(int64_t time_now_us, + std::vector* packets); + + // The send-side time when the next frame can be generated. + int64_t next_rtp_time() const; + + void set_bitrate_bps(int bitrate_bps); + + int bitrate_bps() const; + + static bool Compare(const std::unique_ptr& lhs, + const std::unique_ptr& rhs); + + private: + int fps_; + int bitrate_bps_; + int64_t next_rtp_time_; + uint16_t sequence_number_; + + RTC_DISALLOW_COPY_AND_ASSIGN(RtpStream); +}; + +class StreamGenerator { + public: + StreamGenerator(int capacity, int64_t time_now); + + // Add a new stream. + void AddStream(RtpStream* stream); + + // Set the link capacity. + void set_capacity_bps(int capacity_bps); + + // Divides |bitrate_bps| among all streams. The allocated bitrate per stream + // is decided by the initial allocation ratios. + void SetBitrateBps(int bitrate_bps); + + // Set the RTP timestamp offset for the stream identified by |ssrc|. + void set_rtp_timestamp_offset(uint32_t ssrc, uint32_t offset); + + // TODO(holmer): Break out the channel simulation part from this class to make + // it possible to simulate different types of channels. + int64_t GenerateFrame(std::vector* packets, + int64_t time_now_us); + + private: + // Capacity of the simulated channel in bits per second. + int capacity_; + // The time when the last packet arrived. + int64_t prev_arrival_time_us_; + // All streams being transmitted on this simulated channel. + std::vector> streams_; + + RTC_DISALLOW_COPY_AND_ASSIGN(StreamGenerator); +}; +} // namespace test + +class DelayBasedBweTest : public ::testing::Test { + public: + DelayBasedBweTest(); + virtual ~DelayBasedBweTest(); + + protected: + void AddDefaultStream(); + + // Helpers to insert a single packet into the delay-based BWE. + void IncomingFeedback(int64_t arrival_time_ms, + int64_t send_time_ms, + uint16_t sequence_number, + size_t payload_size); + void IncomingFeedback(int64_t arrival_time_ms, + int64_t send_time_ms, + uint16_t sequence_number, + size_t payload_size, + const PacedPacketInfo& pacing_info); + + // Generates a frame of packets belonging to a stream at a given bitrate and + // with a given ssrc. The stream is pushed through a very simple simulated + // network, and is then given to the receive-side bandwidth estimator. + // Returns true if an over-use was seen, false otherwise. + // The StreamGenerator::updated() should be used to check for any changes in + // target bitrate after the call to this function. + bool GenerateAndProcessFrame(uint32_t ssrc, uint32_t bitrate_bps); + + // Run the bandwidth estimator with a stream of |number_of_frames| frames, or + // until it reaches |target_bitrate|. + // Can for instance be used to run the estimator for some time to get it + // into a steady state. + uint32_t SteadyStateRun(uint32_t ssrc, + int number_of_frames, + uint32_t start_bitrate, + uint32_t min_bitrate, + uint32_t max_bitrate, + uint32_t target_bitrate); + + void TestTimestampGroupingTestHelper(); + + void TestWrappingHelper(int silence_time_s); + + void InitialBehaviorTestHelper(uint32_t expected_converge_bitrate); + void RateIncreaseReorderingTestHelper(uint32_t expected_bitrate); + void RateIncreaseRtpTimestampsTestHelper(int expected_iterations); + void CapacityDropTestHelper(int number_of_streams, + bool wrap_time_stamp, + uint32_t expected_bitrate_drop_delta, + int64_t receiver_clock_offset_change_ms); + + static const uint32_t kDefaultSsrc; + + SimulatedClock clock_; // Time at the receiver. + test::TestBitrateObserver bitrate_observer_; + std::unique_ptr acknowledged_bitrate_estimator_; + std::unique_ptr bitrate_estimator_; + std::unique_ptr stream_generator_; + int64_t arrival_time_offset_ms_; + bool first_update_; + + RTC_DISALLOW_COPY_AND_ASSIGN(DelayBasedBweTest); +}; +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_DELAY_BASED_BWE_UNITTEST_HELPER_H_ diff --git a/modules/congestion_controller/rtp/delay_increase_detector_interface.h b/modules/congestion_controller/rtp/delay_increase_detector_interface.h new file mode 100644 index 0000000000..4df7148206 --- /dev/null +++ b/modules/congestion_controller/rtp/delay_increase_detector_interface.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_DELAY_INCREASE_DETECTOR_INTERFACE_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_DELAY_INCREASE_DETECTOR_INTERFACE_H_ + +#include + +#include "modules/remote_bitrate_estimator/include/bwe_defines.h" +#include "rtc_base/constructormagic.h" + +namespace webrtc { + +class DelayIncreaseDetectorInterface { + public: + DelayIncreaseDetectorInterface() {} + virtual ~DelayIncreaseDetectorInterface() {} + + // Update the detector with a new sample. The deltas should represent deltas + // between timestamp groups as defined by the InterArrival class. + virtual void Update(double recv_delta_ms, + double send_delta_ms, + int64_t arrival_time_ms) = 0; + + virtual BandwidthUsage State() const = 0; + + RTC_DISALLOW_COPY_AND_ASSIGN(DelayIncreaseDetectorInterface); +}; +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_DELAY_INCREASE_DETECTOR_INTERFACE_H_ diff --git a/modules/congestion_controller/rtp/goog_cc_network_control.cc b/modules/congestion_controller/rtp/goog_cc_network_control.cc new file mode 100644 index 0000000000..203c3d3246 --- /dev/null +++ b/modules/congestion_controller/rtp/goog_cc_network_control.cc @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2018 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 "modules/congestion_controller/rtp/goog_cc_network_control.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h" +#include "modules/congestion_controller/rtp/alr_detector.h" +#include "modules/congestion_controller/rtp/include/goog_cc_factory.h" +#include "modules/congestion_controller/rtp/probe_controller.h" +#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/format_macros.h" +#include "rtc_base/logging.h" +#include "rtc_base/ptr_util.h" +#include "rtc_base/timeutils.h" +#include "system_wrappers/include/field_trial.h" + +namespace webrtc { +namespace { + +const char kCwndExperiment[] = "WebRTC-CwndExperiment"; +const int64_t kDefaultAcceptedQueueMs = 250; + +// Pacing-rate relative to our target send rate. +// Multiplicative factor that is applied to the target bitrate to calculate +// the number of bytes that can be transmitted per interval. +// Increasing this factor will result in lower delays in cases of bitrate +// overshoots from the encoder. +const float kDefaultPaceMultiplier = 2.5f; + +bool CwndExperimentEnabled() { + std::string experiment_string = + webrtc::field_trial::FindFullName(kCwndExperiment); + // The experiment is enabled iff the field trial string begins with "Enabled". + return experiment_string.find("Enabled") == 0; +} + +bool ReadCwndExperimentParameter(int64_t* accepted_queue_ms) { + RTC_DCHECK(accepted_queue_ms); + std::string experiment_string = + webrtc::field_trial::FindFullName(kCwndExperiment); + int parsed_values = + sscanf(experiment_string.c_str(), "Enabled-%" PRId64, accepted_queue_ms); + if (parsed_values == 1) { + RTC_CHECK_GE(*accepted_queue_ms, 0) + << "Accepted must be greater than or equal to 0."; + return true; + } + return false; +} + +// Makes sure that the bitrate and the min, max values are in valid range. +static void ClampBitrates(int64_t* bitrate_bps, + int64_t* min_bitrate_bps, + int64_t* max_bitrate_bps) { + // TODO(holmer): We should make sure the default bitrates are set to 10 kbps, + // and that we don't try to set the min bitrate to 0 from any applications. + // The congestion controller should allow a min bitrate of 0. + if (*min_bitrate_bps < congestion_controller::GetMinBitrateBps()) + *min_bitrate_bps = congestion_controller::GetMinBitrateBps(); + if (*max_bitrate_bps > 0) + *max_bitrate_bps = std::max(*min_bitrate_bps, *max_bitrate_bps); + if (*bitrate_bps > 0) + *bitrate_bps = std::max(*min_bitrate_bps, *bitrate_bps); +} + +std::vector ReceivedPacketsFeedbackAsRtp( + const TransportPacketsFeedback report) { + std::vector packet_feedback_vector; + for (auto& fb : report.PacketsWithFeedback()) { + if (fb.receive_time.IsFinite()) { + PacketFeedback pf(fb.receive_time.ms(), 0); + pf.creation_time_ms = report.feedback_time.ms(); + if (fb.sent_packet.has_value()) { + pf.payload_size = fb.sent_packet->size.bytes(); + pf.pacing_info = fb.sent_packet->pacing_info; + pf.send_time_ms = fb.sent_packet->send_time.ms(); + } else { + pf.send_time_ms = PacketFeedback::kNoSendTime; + } + packet_feedback_vector.push_back(pf); + } + } + return packet_feedback_vector; +} + +} // namespace + +GoogCcNetworkControllerFactory::GoogCcNetworkControllerFactory( + RtcEventLog* event_log) + : event_log_(event_log) {} + +NetworkControllerInterface::uptr GoogCcNetworkControllerFactory::Create( + NetworkControllerObserver* observer) { + return rtc::MakeUnique(event_log_, observer); +} + +TimeDelta GoogCcNetworkControllerFactory::GetProcessInterval() const { + const int64_t kUpdateIntervalMs = 25; + return TimeDelta::ms(kUpdateIntervalMs); +} + +GoogCcNetworkController::GoogCcNetworkController( + RtcEventLog* event_log, + NetworkControllerObserver* observer) + : event_log_(event_log), + observer_(observer), + probe_controller_(new ProbeController(observer_)), + bandwidth_estimation_( + rtc::MakeUnique(event_log_)), + alr_detector_(rtc::MakeUnique()), + delay_based_bwe_(new DelayBasedBwe(event_log_)), + acknowledged_bitrate_estimator_( + rtc::MakeUnique()), + pacing_factor_(kDefaultPaceMultiplier), + min_pacing_rate_(DataRate::Zero()), + max_padding_rate_(DataRate::Zero()), + in_cwnd_experiment_(CwndExperimentEnabled()), + accepted_queue_ms_(kDefaultAcceptedQueueMs) { + delay_based_bwe_->SetMinBitrate(congestion_controller::GetMinBitrateBps()); + if (in_cwnd_experiment_ && + !ReadCwndExperimentParameter(&accepted_queue_ms_)) { + RTC_LOG(LS_WARNING) << "Failed to parse parameters for CwndExperiment " + "from field trial string. Experiment disabled."; + in_cwnd_experiment_ = false; + } +} + +GoogCcNetworkController::~GoogCcNetworkController() {} + +void GoogCcNetworkController::OnNetworkAvailability(NetworkAvailability msg) { + probe_controller_->OnNetworkAvailability(msg); +} + +void GoogCcNetworkController::OnNetworkRouteChange(NetworkRouteChange msg) { + int64_t min_bitrate_bps = msg.constraints.min_data_rate.bps(); + int64_t max_bitrate_bps = -1; + int64_t start_bitrate_bps = -1; + + if (msg.constraints.max_data_rate.IsFinite()) + max_bitrate_bps = msg.constraints.max_data_rate.bps(); + if (msg.constraints.starting_rate.IsFinite()) + start_bitrate_bps = msg.constraints.starting_rate.bps(); + + ClampBitrates(&start_bitrate_bps, &min_bitrate_bps, &max_bitrate_bps); + + bandwidth_estimation_ = + rtc::MakeUnique(event_log_); + bandwidth_estimation_->SetBitrates(start_bitrate_bps, min_bitrate_bps, + max_bitrate_bps); + delay_based_bwe_.reset(new DelayBasedBwe(event_log_)); + acknowledged_bitrate_estimator_.reset(new AcknowledgedBitrateEstimator()); + delay_based_bwe_->SetStartBitrate(start_bitrate_bps); + delay_based_bwe_->SetMinBitrate(min_bitrate_bps); + + probe_controller_->Reset(msg.at_time.ms()); + probe_controller_->SetBitrates(min_bitrate_bps, start_bitrate_bps, + max_bitrate_bps, msg.at_time.ms()); + + MaybeTriggerOnNetworkChanged(msg.at_time); +} + +void GoogCcNetworkController::OnProcessInterval(ProcessInterval msg) { + bandwidth_estimation_->UpdateEstimate(msg.at_time.ms()); + rtc::Optional start_time_ms = + alr_detector_->GetApplicationLimitedRegionStartTime(); + probe_controller_->SetAlrStartTimeMs(start_time_ms); + probe_controller_->Process(msg.at_time.ms()); + MaybeTriggerOnNetworkChanged(msg.at_time); +} + +void GoogCcNetworkController::OnRemoteBitrateReport(RemoteBitrateReport msg) { + bandwidth_estimation_->UpdateReceiverEstimate(msg.receive_time.ms(), + msg.bandwidth.bps()); + BWE_TEST_LOGGING_PLOT(1, "REMB_kbps", msg.receive_time.ms(), + msg.bandwidth.bps() / 1000); +} + +void GoogCcNetworkController::OnRoundTripTimeUpdate(RoundTripTimeUpdate msg) { + if (msg.smoothed) { + delay_based_bwe_->OnRttUpdate(msg.round_trip_time.ms()); + } else { + bandwidth_estimation_->UpdateRtt(msg.round_trip_time.ms(), + msg.receive_time.ms()); + } +} + +void GoogCcNetworkController::OnSentPacket(SentPacket sent_packet) { + alr_detector_->OnBytesSent(sent_packet.size.bytes(), + sent_packet.send_time.ms()); +} + +void GoogCcNetworkController::OnStreamsConfig(StreamsConfig msg) { + probe_controller_->EnablePeriodicAlrProbing(msg.requests_alr_probing); + + bool pacing_changed = false; + if (msg.pacing_factor && *msg.pacing_factor != pacing_factor_) { + pacing_factor_ = *msg.pacing_factor; + pacing_changed = true; + } + if (msg.min_pacing_rate && *msg.min_pacing_rate != min_pacing_rate_) { + min_pacing_rate_ = *msg.min_pacing_rate; + pacing_changed = true; + } + if (msg.max_padding_rate && *msg.max_padding_rate != max_padding_rate_) { + max_padding_rate_ = *msg.max_padding_rate; + pacing_changed = true; + } + if (pacing_changed) + UpdatePacingRates(msg.at_time); +} + +void GoogCcNetworkController::OnTargetRateConstraints( + TargetRateConstraints constraints) { + int64_t min_bitrate_bps = constraints.min_data_rate.bps(); + int64_t max_bitrate_bps = -1; + int64_t start_bitrate_bps = -1; + + if (constraints.max_data_rate.IsFinite()) + max_bitrate_bps = constraints.max_data_rate.bps(); + if (constraints.starting_rate.IsFinite()) + start_bitrate_bps = constraints.starting_rate.bps(); + + ClampBitrates(&start_bitrate_bps, &min_bitrate_bps, &max_bitrate_bps); + + probe_controller_->SetBitrates(min_bitrate_bps, start_bitrate_bps, + max_bitrate_bps, constraints.at_time.ms()); + + bandwidth_estimation_->SetBitrates(start_bitrate_bps, min_bitrate_bps, + max_bitrate_bps); + if (start_bitrate_bps > 0) + delay_based_bwe_->SetStartBitrate(start_bitrate_bps); + delay_based_bwe_->SetMinBitrate(min_bitrate_bps); + + MaybeTriggerOnNetworkChanged(constraints.at_time); +} + +void GoogCcNetworkController::OnTransportLossReport(TransportLossReport msg) { + int64_t total_packets_delta = + msg.packets_received_delta + msg.packets_lost_delta; + bandwidth_estimation_->UpdatePacketsLost( + msg.packets_lost_delta, total_packets_delta, msg.receive_time.ms()); +} + +void GoogCcNetworkController::OnTransportPacketsFeedback( + TransportPacketsFeedback report) { + int64_t feedback_rtt = -1; + for (const auto& packet_feedback : report.PacketsWithFeedback()) { + if (packet_feedback.sent_packet.has_value() && + packet_feedback.receive_time.IsFinite()) { + int64_t rtt = report.feedback_time.ms() - + packet_feedback.sent_packet->send_time.ms(); + // max() is used to account for feedback being delayed by the + // receiver. + feedback_rtt = std::max(rtt, feedback_rtt); + } + } + if (feedback_rtt > -1) { + feedback_rtts_.push_back(feedback_rtt); + const size_t kFeedbackRttWindow = 32; + if (feedback_rtts_.size() > kFeedbackRttWindow) + feedback_rtts_.pop_front(); + min_feedback_rtt_ms_.emplace( + *std::min_element(feedback_rtts_.begin(), feedback_rtts_.end())); + } + + std::vector received_feedback_vector = + ReceivedPacketsFeedbackAsRtp(report); + + rtc::Optional alr_start_time = + alr_detector_->GetApplicationLimitedRegionStartTime(); + + if (previously_in_alr && !alr_start_time.has_value()) { + int64_t now_ms = report.feedback_time.ms(); + acknowledged_bitrate_estimator_->SetAlrEndedTimeMs(now_ms); + probe_controller_->SetAlrEndedTimeMs(now_ms); + } + previously_in_alr = alr_start_time.has_value(); + acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector( + received_feedback_vector); + DelayBasedBwe::Result result; + result = delay_based_bwe_->IncomingPacketFeedbackVector( + received_feedback_vector, acknowledged_bitrate_estimator_->bitrate_bps(), + report.feedback_time.ms()); + if (result.updated) { + if (result.probe) { + bandwidth_estimation_->SetSendBitrate(result.target_bitrate_bps); + } + // Since SetSendBitrate now resets the delay-based estimate, we have to call + // UpdateDelayBasedEstimate after SetSendBitrate. + bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time.ms(), + result.target_bitrate_bps); + // Update the estimate in the ProbeController, in case we want to probe. + MaybeTriggerOnNetworkChanged(report.feedback_time); + } + if (result.recovered_from_overuse) { + probe_controller_->SetAlrStartTimeMs(alr_start_time); + probe_controller_->RequestProbe(report.feedback_time.ms()); + } + MaybeUpdateCongestionWindow(); +} + +void GoogCcNetworkController::MaybeUpdateCongestionWindow() { + if (!in_cwnd_experiment_) + return; + // No valid RTT. Could be because send-side BWE isn't used, in which case + // we don't try to limit the outstanding packets. + if (!min_feedback_rtt_ms_) + return; + if (!last_estimate_.has_value()) + return; + const DataSize kMinCwnd = DataSize::bytes(2 * 1500); + TimeDelta time_window = + TimeDelta::ms(*min_feedback_rtt_ms_ + accepted_queue_ms_); + DataSize data_window = last_estimate_->bandwidth * time_window; + CongestionWindow msg; + msg.enabled = true; + msg.data_window = std::max(kMinCwnd, data_window); + observer_->OnCongestionWindow(msg); + RTC_LOG(LS_INFO) << "Feedback rtt: " << *min_feedback_rtt_ms_ + << " Bitrate: " << last_estimate_->bandwidth.bps(); +} + +void GoogCcNetworkController::MaybeTriggerOnNetworkChanged(Timestamp at_time) { + int32_t estimated_bitrate_bps; + uint8_t fraction_loss; + int64_t rtt_ms; + + bool estimate_changed = GetNetworkParameters( + &estimated_bitrate_bps, &fraction_loss, &rtt_ms, at_time); + if (estimate_changed) { + TimeDelta bwe_period = + TimeDelta::ms(delay_based_bwe_->GetExpectedBwePeriodMs()); + + NetworkEstimate new_estimate; + new_estimate.at_time = at_time; + new_estimate.round_trip_time = TimeDelta::ms(rtt_ms); + new_estimate.bandwidth = DataRate::bps(estimated_bitrate_bps); + new_estimate.loss_rate_ratio = fraction_loss / 255.0f; + new_estimate.bwe_period = bwe_period; + new_estimate.changed = true; + last_estimate_ = new_estimate; + OnNetworkEstimate(new_estimate); + } +} + +bool GoogCcNetworkController::GetNetworkParameters( + int32_t* estimated_bitrate_bps, + uint8_t* fraction_loss, + int64_t* rtt_ms, + Timestamp at_time) { + bandwidth_estimation_->CurrentEstimate(estimated_bitrate_bps, fraction_loss, + rtt_ms); + *estimated_bitrate_bps = std::max( + *estimated_bitrate_bps, bandwidth_estimation_->GetMinBitrate()); + + bool estimate_changed = false; + if ((*estimated_bitrate_bps != last_estimated_bitrate_bps_) || + (*fraction_loss != last_estimated_fraction_loss_) || + (*rtt_ms != last_estimated_rtt_ms_)) { + last_estimated_bitrate_bps_ = *estimated_bitrate_bps; + last_estimated_fraction_loss_ = *fraction_loss; + last_estimated_rtt_ms_ = *rtt_ms; + estimate_changed = true; + } + + BWE_TEST_LOGGING_PLOT(1, "fraction_loss_%", at_time.ms(), + (*fraction_loss * 100) / 256); + BWE_TEST_LOGGING_PLOT(1, "rtt_ms", at_time.ms(), *rtt_ms); + BWE_TEST_LOGGING_PLOT(1, "Target_bitrate_kbps", at_time.ms(), + *estimated_bitrate_bps / 1000); + + return estimate_changed; +} + +void GoogCcNetworkController::OnNetworkEstimate(NetworkEstimate estimate) { + if (!estimate.changed) + return; + + UpdatePacingRates(estimate.at_time); + alr_detector_->SetEstimatedBitrate(estimate.bandwidth.bps()); + probe_controller_->SetEstimatedBitrate(estimate.bandwidth.bps(), + estimate.at_time.ms()); + + TargetTransferRate target_rate; + target_rate.at_time = estimate.at_time; + // Set the target rate to the full estimated bandwidth since the estimation + // for legacy reasons includes target rate constraints. + target_rate.target_rate = estimate.bandwidth; + target_rate.network_estimate = estimate; + observer_->OnTargetTransferRate(target_rate); +} + +void GoogCcNetworkController::UpdatePacingRates(Timestamp at_time) { + if (!last_estimate_) + return; + DataRate pacing_rate = + std::max(min_pacing_rate_, last_estimate_->bandwidth) * pacing_factor_; + DataRate padding_rate = + std::min(max_padding_rate_, last_estimate_->bandwidth); + PacerConfig msg; + msg.at_time = at_time; + msg.time_window = TimeDelta::s(1); + msg.data_window = pacing_rate * msg.time_window; + msg.pad_window = padding_rate * msg.time_window; + observer_->OnPacerConfig(msg); +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/goog_cc_network_control.h b/modules/congestion_controller/rtp/goog_cc_network_control.h new file mode 100644 index 0000000000..80d534f78c --- /dev/null +++ b/modules/congestion_controller/rtp/goog_cc_network_control.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_GOOG_CC_NETWORK_CONTROL_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_GOOG_CC_NETWORK_CONTROL_H_ + +#include +#include +#include +#include + +#include "api/optional.h" +#include "logging/rtc_event_log/rtc_event_log.h" +#include "modules/bitrate_controller/send_side_bandwidth_estimation.h" +#include "modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h" +#include "modules/congestion_controller/rtp/alr_detector.h" +#include "modules/congestion_controller/rtp/delay_based_bwe.h" +#include "modules/congestion_controller/rtp/network_control/include/network_control.h" +#include "modules/congestion_controller/rtp/probe_controller.h" +#include "rtc_base/constructormagic.h" + +namespace webrtc { + +class GoogCcNetworkController : public NetworkControllerInterface { + public: + GoogCcNetworkController(RtcEventLog* event_log, + NetworkControllerObserver* observer); + ~GoogCcNetworkController() override; + + // NetworkControllerInterface + void OnNetworkAvailability(NetworkAvailability msg) override; + void OnNetworkRouteChange(NetworkRouteChange msg) override; + void OnProcessInterval(ProcessInterval msg) override; + void OnRemoteBitrateReport(RemoteBitrateReport msg) override; + void OnRoundTripTimeUpdate(RoundTripTimeUpdate msg) override; + void OnSentPacket(SentPacket msg) override; + void OnStreamsConfig(StreamsConfig msg) override; + void OnTargetRateConstraints(TargetRateConstraints msg) override; + void OnTransportLossReport(TransportLossReport msg) override; + void OnTransportPacketsFeedback(TransportPacketsFeedback msg) override; + + private: + void MaybeUpdateCongestionWindow(); + void MaybeTriggerOnNetworkChanged(Timestamp at_time); + bool GetNetworkParameters(int32_t* estimated_bitrate_bps, + uint8_t* fraction_loss, + int64_t* rtt_ms, + Timestamp at_time); + void OnNetworkEstimate(NetworkEstimate msg); + void UpdatePacingRates(Timestamp at_time); + + RtcEventLog* const event_log_; + NetworkControllerObserver* const observer_; + + const std::unique_ptr probe_controller_; + + std::unique_ptr bandwidth_estimation_; + std::unique_ptr alr_detector_; + std::unique_ptr delay_based_bwe_; + std::unique_ptr acknowledged_bitrate_estimator_; + + std::deque feedback_rtts_; + rtc::Optional min_feedback_rtt_ms_; + + rtc::Optional last_estimate_; + rtc::Optional last_target_rate_; + + int32_t last_estimated_bitrate_bps_ = 0; + uint8_t last_estimated_fraction_loss_ = 0; + int64_t last_estimated_rtt_ms_ = 0; + + double pacing_factor_; + DataRate min_pacing_rate_; + DataRate max_padding_rate_; + + bool in_cwnd_experiment_; + int64_t accepted_queue_ms_; + bool previously_in_alr = false; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(GoogCcNetworkController); +}; + +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_GOOG_CC_NETWORK_CONTROL_H_ diff --git a/modules/congestion_controller/rtp/include/goog_cc_factory.h b/modules/congestion_controller/rtp/include/goog_cc_factory.h new file mode 100644 index 0000000000..0e8107ba47 --- /dev/null +++ b/modules/congestion_controller/rtp/include/goog_cc_factory.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_INCLUDE_GOOG_CC_FACTORY_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_INCLUDE_GOOG_CC_FACTORY_H_ +#include "modules/congestion_controller/rtp/network_control/include/network_control.h" + +namespace webrtc { +class Clock; +class RtcEventLog; + +class GoogCcNetworkControllerFactory + : public NetworkControllerFactoryInterface { + public: + explicit GoogCcNetworkControllerFactory(RtcEventLog*); + NetworkControllerInterface::uptr Create( + NetworkControllerObserver* observer) override; + TimeDelta GetProcessInterval() const override; + + private: + RtcEventLog* const event_log_; +}; +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_INCLUDE_GOOG_CC_FACTORY_H_ diff --git a/modules/congestion_controller/rtp/include/send_side_congestion_controller.h b/modules/congestion_controller/rtp/include/send_side_congestion_controller.h new file mode 100644 index 0000000000..a941fd2d15 --- /dev/null +++ b/modules/congestion_controller/rtp/include/send_side_congestion_controller.h @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2012 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 MODULES_CONGESTION_CONTROLLER_RTP_INCLUDE_SEND_SIDE_CONGESTION_CONTROLLER_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_INCLUDE_SEND_SIDE_CONGESTION_CONTROLLER_H_ + +#include +#include +#include +#include +#include + +#include "common_types.h" // NOLINT(build/include) +#include "modules/congestion_controller/rtp/network_control/include/network_control.h" +#include "modules/congestion_controller/rtp/network_control/include/network_types.h" +#include "modules/congestion_controller/rtp/pacer_controller.h" +#include "modules/congestion_controller/rtp/transport_feedback_adapter.h" +#include "modules/include/module.h" +#include "modules/include/module_common_types.h" +#include "modules/pacing/paced_sender.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "rtc_base/constructormagic.h" +#include "rtc_base/criticalsection.h" +#include "rtc_base/networkroute.h" +#include "rtc_base/race_checker.h" +#include "rtc_base/task_queue.h" + +namespace rtc { +struct SentPacket; +} + +namespace webrtc { + +class Clock; +class RateLimiter; +class RtcEventLog; + +namespace send_side_cc_internal { +// This is used to observe the network controller state and route calls to +// the proper handler. It also keeps cached values for safe asynchronous use. +// This makes sure that things running on the worker queue can't access state +// in SendSideCongestionController, which would risk causing data race on +// destruction unless members are properly ordered. +class ControlHandler; +} // namespace send_side_cc_internal + +class SendSideCongestionController : public CallStatsObserver, + public Module, + public TransportFeedbackObserver, + public RtcpBandwidthObserver { + public: + // Observer class for bitrate changes announced due to change in bandwidth + // estimate or due to that the send pacer is full. Fraction loss and rtt is + // also part of this callback to allow the observer to optimize its settings + // for different types of network environments. The bitrate does not include + // packet headers and is measured in bits per second. + class Observer { + public: + virtual void OnNetworkChanged(uint32_t bitrate_bps, + uint8_t fraction_loss, // 0 - 255. + int64_t rtt_ms, + int64_t probing_interval_ms) = 0; + + protected: + virtual ~Observer() {} + }; + SendSideCongestionController(const Clock* clock, + Observer* observer, + RtcEventLog* event_log, + PacedSender* pacer); + ~SendSideCongestionController() override; + + void RegisterPacketFeedbackObserver(PacketFeedbackObserver* observer); + void DeRegisterPacketFeedbackObserver(PacketFeedbackObserver* observer); + + // Currently, there can be at most one observer. + // TODO(nisse): The RegisterNetworkObserver method is needed because we first + // construct this object (as part of RtpTransportControllerSend), then pass a + // reference to Call, which then registers itself as the observer. We should + // try to break this circular chain of references, and make the observer a + // construction time constant. + void RegisterNetworkObserver(Observer* observer); + void DeRegisterNetworkObserver(Observer* observer); + + virtual void SetBweBitrates(int min_bitrate_bps, + int start_bitrate_bps, + int max_bitrate_bps); + // Resets the BWE state. Note the first argument is the bitrate_bps. + virtual void OnNetworkRouteChanged(const rtc::NetworkRoute& network_route, + int bitrate_bps, + int min_bitrate_bps, + int max_bitrate_bps); + virtual void SignalNetworkState(NetworkState state); + virtual void SetTransportOverhead(size_t transport_overhead_bytes_per_packet); + + virtual RtcpBandwidthObserver* GetBandwidthObserver(); + + virtual bool AvailableBandwidth(uint32_t* bandwidth) const; + virtual int64_t GetPacerQueuingDelayMs() const; + virtual int64_t GetFirstPacketTimeMs() const; + + virtual TransportFeedbackObserver* GetTransportFeedbackObserver(); + + RateLimiter* GetRetransmissionRateLimiter(); + void EnablePeriodicAlrProbing(bool enable); + + virtual void OnSentPacket(const rtc::SentPacket& sent_packet); + + // Implements RtcpBandwidthObserver + void OnReceivedEstimatedBitrate(uint32_t bitrate) override; + void OnReceivedRtcpReceiverReport(const ReportBlockList& report_blocks, + int64_t rtt, + int64_t now_ms) override; + // Implements CallStatsObserver. + void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override; + + // Implements Module. + int64_t TimeUntilNextProcess() override; + void Process() override; + + // Implements TransportFeedbackObserver. + void AddPacket(uint32_t ssrc, + uint16_t sequence_number, + size_t length, + const PacedPacketInfo& pacing_info) override; + void OnTransportFeedback(const rtcp::TransportFeedback& feedback) override; + std::vector GetTransportFeedbackVector() const override; + + // Sets the minimum send bitrate and maximum padding bitrate requested by send + // streams. + // |min_send_bitrate_bps| might be higher that the estimated available network + // bitrate and if so, the pacer will send with |min_send_bitrate_bps|. + // |max_padding_bitrate_bps| might be higher than the estimate available + // network bitrate and if so, the pacer will send padding packets to reach + // the min of the estimated available bitrate and |max_padding_bitrate_bps|. + void SetSendBitrateLimits(int64_t min_send_bitrate_bps, + int64_t max_padding_bitrate_bps); + void SetPacingFactor(float pacing_factor); + + protected: + // Waits long enough that any outstanding tasks should be finished. + void WaitOnTasks(); + + private: + SendSideCongestionController( + const Clock* clock, + RtcEventLog* event_log, + PacedSender* pacer, + NetworkControllerFactoryInterface::uptr controller_factory); + + void UpdateStreamsConfig(); + void WaitOnTask(std::function closure); + void MaybeUpdateOutstandingData(); + void OnReceivedRtcpReceiverReportBlocks(const ReportBlockList& report_blocks, + int64_t now_ms); + + const Clock* const clock_; + PacedSender* const pacer_; + TransportFeedbackAdapter transport_feedback_adapter_; + + const std::unique_ptr pacer_controller_; + const std::unique_ptr control_handler; + const std::unique_ptr controller_; + + TimeDelta process_interval_; + int64_t last_process_update_ms_ = 0; + + std::map last_report_blocks_; + Timestamp last_report_block_time_; + + StreamsConfig streams_config_; + const bool send_side_bwe_with_overhead_; + std::atomic transport_overhead_bytes_per_packet_; + std::atomic network_available_; + + rtc::RaceChecker worker_race_; + + // Note that moving ownership of the task queue makes it neccessary to make + // sure that there is no outstanding tasks on it using destructed objects. + // This is currently guranteed by using explicit reset in the destructor of + // this class. It is declared last to indicate that it's lifetime is shorter + // than all other members. + std::unique_ptr task_queue_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(SendSideCongestionController); +}; +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_INCLUDE_SEND_SIDE_CONGESTION_CONTROLLER_H_ diff --git a/modules/congestion_controller/rtp/median_slope_estimator.cc b/modules/congestion_controller/rtp/median_slope_estimator.cc new file mode 100644 index 0000000000..31b429ab9d --- /dev/null +++ b/modules/congestion_controller/rtp/median_slope_estimator.cc @@ -0,0 +1,93 @@ +/* + * 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 "modules/congestion_controller/rtp/median_slope_estimator.h" + +#include +#include + +#include "modules/remote_bitrate_estimator/include/bwe_defines.h" +#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h" +#include "rtc_base/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() {} + +MedianSlopeEstimator::DelayInfo::DelayInfo(int64_t time, + double delay, + size_t slope_count) + : time(time), delay(delay) { + slopes.reserve(slope_count); +} + +MedianSlopeEstimator::DelayInfo::~DelayInfo() = default; + +void MedianSlopeEstimator::Update(double recv_delta_ms, + double send_delta_ms, + int64_t arrival_time_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", arrival_time_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 (arrival_time_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) / + static_cast(arrival_time_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(arrival_time_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", arrival_time_ms, trendline_); +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/median_slope_estimator.h b/modules/congestion_controller/rtp/median_slope_estimator.h new file mode 100644 index 0000000000..f7f2570dab --- /dev/null +++ b/modules/congestion_controller/rtp/median_slope_estimator.h @@ -0,0 +1,72 @@ +/* + * 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 MODULES_CONGESTION_CONTROLLER_RTP_MEDIAN_SLOPE_ESTIMATOR_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_MEDIAN_SLOPE_ESTIMATOR_H_ + +#include +#include + +#include +#include + +#include "rtc_base/constructormagic.h" +#include "rtc_base/numerics/percentile_filter.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, + int64_t arrival_time_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(int64_t time, double delay, size_t slope_count); + ~DelayInfo(); + int64_t 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::deque delay_hist_; + PercentileFilter median_filter_; + double trendline_; + + RTC_DISALLOW_COPY_AND_ASSIGN(MedianSlopeEstimator); +}; +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_MEDIAN_SLOPE_ESTIMATOR_H_ diff --git a/modules/congestion_controller/rtp/median_slope_estimator_unittest.cc b/modules/congestion_controller/rtp/median_slope_estimator_unittest.cc new file mode 100644 index 0000000000..2e243879ae --- /dev/null +++ b/modules/congestion_controller/rtp/median_slope_estimator_unittest.cc @@ -0,0 +1,72 @@ +/* + * 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 "modules/congestion_controller/rtp/median_slope_estimator.h" +#include "rtc_base/random.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { +constexpr size_t kWindowSize = 20; +constexpr double kGain = 1; +constexpr int64_t kAvgTimeBetweenPackets = 10; +constexpr size_t kPacketCount = 2 * kWindowSize + 1; + +void TestEstimator(double slope, double jitter_stddev, double tolerance) { + MedianSlopeEstimator estimator(kWindowSize, kGain); + 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; + } + 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, recv_times[i]); + if (i < kWindowSize) + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001); + else + EXPECT_NEAR(estimator.trendline_slope(), slope, tolerance); + } +} +} // namespace + +TEST(MedianSlopeEstimator, PerfectLineSlopeOneHalf) { + TestEstimator(0.5, 0, 0.001); +} + +TEST(MedianSlopeEstimator, PerfectLineSlopeMinusOne) { + TestEstimator(-1, 0, 0.001); +} + +TEST(MedianSlopeEstimator, PerfectLineSlopeZero) { + TestEstimator(0, 0, 0.001); +} + +TEST(MedianSlopeEstimator, JitteryLineSlopeOneHalf) { + TestEstimator(0.5, kAvgTimeBetweenPackets / 3.0, 0.01); +} + +TEST(MedianSlopeEstimator, JitteryLineSlopeMinusOne) { + TestEstimator(-1, kAvgTimeBetweenPackets / 3.0, 0.05); +} + +TEST(MedianSlopeEstimator, JitteryLineSlopeZero) { + TestEstimator(0, kAvgTimeBetweenPackets / 3.0, 0.02); +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/network_control/BUILD.gn b/modules/congestion_controller/rtp/network_control/BUILD.gn new file mode 100644 index 0000000000..ccaec9bf42 --- /dev/null +++ b/modules/congestion_controller/rtp/network_control/BUILD.gn @@ -0,0 +1,40 @@ +# Copyright (c) 2018 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. + +import("../../../../webrtc.gni") + +rtc_static_library("network_control") { + sources = [ + "include/network_control.h", + "include/network_types.h", + "include/network_units.h", + "network_types.cc", + "network_units.cc", + ] + + deps = [ + "../../../:module_api", + "../../../../api:optional", + "../../../../rtc_base:checks", + "../../../../rtc_base:rtc_base_approved", + ] +} + +if (rtc_include_tests) { + rtc_source_set("network_control_unittests") { + testonly = true + sources = [ + "network_units_unittest.cc", + ] + + deps = [ + ":network_control", + "../../../../test:test_support", + ] + } +} diff --git a/modules/congestion_controller/rtp/network_control/include/network_control.h b/modules/congestion_controller/rtp/network_control/include/network_control.h new file mode 100644 index 0000000000..0fba1a2804 --- /dev/null +++ b/modules/congestion_controller/rtp/network_control/include/network_control.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_CONTROL_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_CONTROL_H_ +#include +#include + +#include "modules/congestion_controller/rtp/network_control/include/network_types.h" +#include "modules/congestion_controller/rtp/network_control/include/network_units.h" + +namespace webrtc { + +// NetworkControllerObserver is an interface implemented by observers of network +// controllers. It contains declarations of the possible configuration messages +// that can be sent from a network controller implementation. +class NetworkControllerObserver { + public: + // Called when congestion window configutation is changed. + virtual void OnCongestionWindow(CongestionWindow) = 0; + // Called when pacer configuration has changed. + virtual void OnPacerConfig(PacerConfig) = 0; + // Called to indicate that a new probe should be sent. + virtual void OnProbeClusterConfig(ProbeClusterConfig) = 0; + // Called to indicate target transfer rate as well as giving information about + // the current estimate of network parameters. + virtual void OnTargetTransferRate(TargetTransferRate) = 0; + + protected: + virtual ~NetworkControllerObserver() = default; +}; + +// NetworkControllerInterface is implemented by network controllers. A network +// controller is a class that uses information about network state and traffic +// to estimate network parameters such as round trip time and bandwidth. Network +// controllers does not guarantee thread safety, the interface must be used in a +// non-concurrent fashion. +class NetworkControllerInterface { + public: + using uptr = std::unique_ptr; + virtual ~NetworkControllerInterface() = default; + + // Called when network availabilty changes. + virtual void OnNetworkAvailability(NetworkAvailability) = 0; + // Called when the receiving or sending endpoint changes address. + virtual void OnNetworkRouteChange(NetworkRouteChange) = 0; + // Called periodically with a periodicy as specified by + // NetworkControllerFactoryInterface::GetProcessInterval. + virtual void OnProcessInterval(ProcessInterval) = 0; + // Called when remotely calculated bitrate is received. + virtual void OnRemoteBitrateReport(RemoteBitrateReport) = 0; + // Called round trip time has been calculated by protocol specific mechanisms. + virtual void OnRoundTripTimeUpdate(RoundTripTimeUpdate) = 0; + // Called when a packet is sent on the network. + virtual void OnSentPacket(SentPacket) = 0; + // Called when the stream specific configuration has been updated. + virtual void OnStreamsConfig(StreamsConfig) = 0; + // Called when target transfer rate constraints has been changed. + virtual void OnTargetRateConstraints(TargetRateConstraints) = 0; + // Called when a protocol specific calculation of packet loss has been made. + virtual void OnTransportLossReport(TransportLossReport) = 0; + // Called with per packet feedback regarding receive time. + virtual void OnTransportPacketsFeedback(TransportPacketsFeedback) = 0; +}; + +// NetworkControllerFactoryInterface is an interface for creating a network +// controller. +class NetworkControllerFactoryInterface { + public: + using uptr = std::unique_ptr; + // Used to create a new network controller, requires an observer to be + // provided to handle callbacks. + virtual NetworkControllerInterface::uptr Create( + NetworkControllerObserver* observer) = 0; + // Returns the interval by which the network controller expects + // OnProcessInterval calls. + virtual TimeDelta GetProcessInterval() const = 0; + virtual ~NetworkControllerFactoryInterface() = default; +}; +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_CONTROL_H_ diff --git a/modules/congestion_controller/rtp/network_control/include/network_types.h b/modules/congestion_controller/rtp/network_control/include/network_types.h new file mode 100644 index 0000000000..e2fd2983b6 --- /dev/null +++ b/modules/congestion_controller/rtp/network_control/include/network_types.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_TYPES_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_TYPES_H_ +#include +#include +#include +#include "modules/congestion_controller/rtp/network_control/include/network_units.h" +#include "modules/include/module_common_types.h" +#include "rtc_base/constructormagic.h" + +namespace webrtc { + +// Configuration + +// Use StreamsConfig for information about streams that is required for specific +// adjustments to the algorithms in network controllers. Especially useful +// for experiments. +struct StreamsConfig { + StreamsConfig(); + StreamsConfig(const StreamsConfig&); + ~StreamsConfig(); + Timestamp at_time; + bool requests_alr_probing = false; + rtc::Optional pacing_factor; + rtc::Optional min_pacing_rate; + rtc::Optional max_padding_rate; +}; + +struct TargetRateConstraints { + Timestamp at_time; + DataRate starting_rate; + DataRate min_data_rate; + DataRate max_data_rate; +}; + +// Send side information + +struct NetworkAvailability { + Timestamp at_time; + bool network_available = false; +}; + +struct NetworkRouteChange { + Timestamp at_time; + // The TargetRateConstraints are set here so they can be changed synchronously + // when network route changes. + TargetRateConstraints constraints; +}; + +struct SentPacket { + Timestamp send_time; + DataSize size; + PacedPacketInfo pacing_info; +}; + +struct PacerQueueUpdate { + TimeDelta expected_queue_time; +}; + +// Transport level feedback + +struct RemoteBitrateReport { + Timestamp receive_time; + DataRate bandwidth; +}; + +struct RoundTripTimeUpdate { + Timestamp receive_time; + TimeDelta round_trip_time; + bool smoothed = false; +}; + +struct TransportLossReport { + Timestamp receive_time; + Timestamp start_time; + Timestamp end_time; + uint64_t packets_lost_delta = 0; + uint64_t packets_received_delta = 0; +}; + +struct OutstandingData { + DataSize in_flight_data; +}; + +// Packet level feedback + +struct PacketResult { + PacketResult(); + PacketResult(const PacketResult&); + ~PacketResult(); + + rtc::Optional sent_packet; + Timestamp receive_time; +}; + +struct TransportPacketsFeedback { + TransportPacketsFeedback(); + TransportPacketsFeedback(const TransportPacketsFeedback& other); + ~TransportPacketsFeedback(); + + Timestamp feedback_time; + DataSize data_in_flight; + DataSize prior_in_flight; + std::vector packet_feedbacks; + + std::vector ReceivedWithSendInfo() const; + std::vector LostWithSendInfo() const; + std::vector PacketsWithFeedback() const; +}; + +// Network estimation + +struct NetworkEstimate { + Timestamp at_time; + DataRate bandwidth; + TimeDelta round_trip_time; + TimeDelta bwe_period; + + float loss_rate_ratio = 0; + bool changed = true; +}; + +// Network control +struct CongestionWindow { + bool enabled = true; + DataSize data_window; +}; + +struct PacerConfig { + Timestamp at_time; + // Pacer should send at most data_window data over time_window duration. + DataSize data_window; + TimeDelta time_window; + // Pacer should send at least pad_window data over time_window duration. + DataSize pad_window; + DataRate data_rate() const { return data_window / time_window; } +}; + +struct ProbeClusterConfig { + Timestamp at_time; + DataRate target_data_rate; + TimeDelta target_duration; + uint32_t target_probe_count; +}; + +struct TargetTransferRate { + Timestamp at_time; + DataRate target_rate; + // The estimate on which the target rate is based on. + NetworkEstimate network_estimate; +}; + +// Process control +struct ProcessInterval { + Timestamp at_time; +}; + +::std::ostream& operator<<(::std::ostream& os, + const ProbeClusterConfig& config); +::std::ostream& operator<<(::std::ostream& os, const PacerConfig& config); +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_TYPES_H_ diff --git a/modules/congestion_controller/rtp/network_control/include/network_units.h b/modules/congestion_controller/rtp/network_control/include/network_units.h new file mode 100644 index 0000000000..3d7b620a53 --- /dev/null +++ b/modules/congestion_controller/rtp/network_control/include/network_units.h @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_UNITS_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_UNITS_H_ +#include +#include +#include +#include "rtc_base/checks.h" + +namespace webrtc { +namespace units_internal { +inline int64_t DivideAndRound(int64_t numerator, int64_t denominators) { + if (numerator >= 0) { + return (numerator + (denominators / 2)) / denominators; + } else { + return (numerator + (denominators / 2)) / denominators - 1; + } +} +} // namespace units_internal + +// TimeDelta represents the difference between two timestamps. Connomly this can +// be a duration. However since two Timestamps are not guaranteed to have the +// same epoch (they might come from different computers, making exact +// synchronisation infeasible), the duration covered by a TimeDelta can be +// undefined. To simplify usage, it can be constructed and converted to +// different units, specifically seconds (s), milliseconds (ms) and +// microseconds (us). +class TimeDelta { + public: + static const TimeDelta kPlusInfinity; + static const TimeDelta kMinusInfinity; + static const TimeDelta kNotInitialized; + static const TimeDelta kZero; + TimeDelta() : TimeDelta(kNotInitialized) {} + static TimeDelta Zero() { return kZero; } + static TimeDelta Infinity() { return kPlusInfinity; } + static TimeDelta seconds(int64_t seconds) { return TimeDelta::s(seconds); } + static TimeDelta s(int64_t seconds) { + return TimeDelta::us(seconds * 1000000); + } + static TimeDelta ms(int64_t milliseconds) { + return TimeDelta::us(milliseconds * 1000); + } + static TimeDelta us(int64_t microseconds) { + // Infinities only allowed via use of explicit constants. + RTC_DCHECK(microseconds > std::numeric_limits::min()); + RTC_DCHECK(microseconds < std::numeric_limits::max()); + return TimeDelta(microseconds); + } + int64_t s() const { return units_internal::DivideAndRound(us(), 1000000); } + int64_t ms() const { return units_internal::DivideAndRound(us(), 1000); } + int64_t us() const { + RTC_DCHECK(IsFinite()); + return microseconds_; + } + TimeDelta Abs() const { return TimeDelta::us(std::abs(us())); } + bool IsZero() const { return microseconds_ == 0; } + bool IsFinite() const { return IsInitialized() && !IsInfinite(); } + bool IsInitialized() const { + return microseconds_ != kNotInitialized.microseconds_; + } + bool IsInfinite() const { + return *this == kPlusInfinity || *this == kMinusInfinity; + } + TimeDelta operator+(const TimeDelta& other) const { + return TimeDelta::us(us() + other.us()); + } + TimeDelta operator-(const TimeDelta& other) const { + return TimeDelta::us(us() - other.us()); + } + TimeDelta operator*(double scalar) const { + return TimeDelta::us(us() * scalar); + } + TimeDelta operator*(int64_t scalar) const { + return TimeDelta::us(us() * scalar); + } + TimeDelta operator*(int32_t scalar) const { + return TimeDelta::us(us() * scalar); + } + bool operator==(const TimeDelta& other) const { + return microseconds_ == other.microseconds_; + } + bool operator!=(const TimeDelta& other) const { + return microseconds_ != other.microseconds_; + } + bool operator<=(const TimeDelta& other) const { + return microseconds_ <= other.microseconds_; + } + bool operator>=(const TimeDelta& other) const { + return microseconds_ >= other.microseconds_; + } + bool operator>(const TimeDelta& other) const { + return microseconds_ > other.microseconds_; + } + bool operator<(const TimeDelta& other) const { + return microseconds_ < other.microseconds_; + } + + private: + explicit TimeDelta(int64_t us) : microseconds_(us) {} + int64_t microseconds_; +}; +inline TimeDelta operator*(const double& scalar, const TimeDelta& delta) { + return delta * scalar; +} +inline TimeDelta operator*(const int64_t& scalar, const TimeDelta& delta) { + return delta * scalar; +} +inline TimeDelta operator*(const int32_t& scalar, const TimeDelta& delta) { + return delta * scalar; +} + +// Timestamp represents the time that has passed since some unspecified epoch. +// The epoch is assumed to be before any represented timestamps, this means that +// negative values are not valid. The most notable feature is that the +// difference of of two Timestamps results in a TimeDelta. +class Timestamp { + public: + static const Timestamp kPlusInfinity; + static const Timestamp kNotInitialized; + Timestamp() : Timestamp(kNotInitialized) {} + static Timestamp Infinity() { return kPlusInfinity; } + static Timestamp s(int64_t seconds) { return Timestamp(seconds * 1000000); } + static Timestamp ms(int64_t millis) { return Timestamp(millis * 1000); } + static Timestamp us(int64_t micros) { return Timestamp(micros); } + int64_t s() const { return units_internal::DivideAndRound(us(), 1000000); } + int64_t ms() const { return units_internal::DivideAndRound(us(), 1000); } + int64_t us() const { + RTC_DCHECK(IsFinite()); + return microseconds_; + } + bool IsInfinite() const { + return microseconds_ == kPlusInfinity.microseconds_; + } + bool IsInitialized() const { + return microseconds_ != kNotInitialized.microseconds_; + } + bool IsFinite() const { return IsInitialized() && !IsInfinite(); } + TimeDelta operator-(const Timestamp& other) const { + return TimeDelta::us(us() - other.us()); + } + Timestamp operator-(const TimeDelta& delta) const { + return Timestamp::us(us() - delta.us()); + } + Timestamp operator+(const TimeDelta& delta) const { + return Timestamp::us(us() + delta.us()); + } + bool operator==(const Timestamp& other) const { + return microseconds_ == other.microseconds_; + } + bool operator!=(const Timestamp& other) const { + return microseconds_ != other.microseconds_; + } + bool operator<=(const Timestamp& other) const { return us() <= other.us(); } + bool operator>=(const Timestamp& other) const { return us() >= other.us(); } + bool operator>(const Timestamp& other) const { return us() > other.us(); } + bool operator<(const Timestamp& other) const { return us() < other.us(); } + + private: + explicit Timestamp(int64_t us) : microseconds_(us) {} + int64_t microseconds_; +}; + +// DataSize is a class represeting a count of bytes. Note that while it can be +// initialized by a number of bits, it does not guarantee that the resolution is +// kept and the internal storage is in bytes. The number of bits will be +// truncated to fit. +class DataSize { + public: + static const DataSize kZero; + static const DataSize kPlusInfinity; + static const DataSize kNotInitialized; + DataSize() : DataSize(kNotInitialized) {} + static DataSize Zero() { return kZero; } + static DataSize Infinity() { return kPlusInfinity; } + static DataSize bytes(int64_t bytes) { return DataSize(bytes); } + static DataSize bits(int64_t bits) { return DataSize(bits / 8); } + int64_t bytes() const { + RTC_DCHECK(IsFinite()); + return bytes_; + } + int64_t kilobytes() const { + return units_internal::DivideAndRound(bytes(), 1000); + } + int64_t bits() const { return bytes() * 8; } + int64_t kilobits() const { + return units_internal::DivideAndRound(bits(), 1000); + } + bool IsZero() const { return bytes_ == 0; } + bool IsInfinite() const { return bytes_ == kPlusInfinity.bytes_; } + bool IsInitialized() const { return bytes_ != kNotInitialized.bytes_; } + bool IsFinite() const { return IsInitialized() && !IsInfinite(); } + DataSize operator-(const DataSize& other) const { + return DataSize::bytes(bytes() - other.bytes()); + } + DataSize operator+(const DataSize& other) const { + return DataSize::bytes(bytes() + other.bytes()); + } + DataSize operator*(double scalar) const { + return DataSize::bytes(bytes() * scalar); + } + DataSize operator*(int64_t scalar) const { + return DataSize::bytes(bytes() * scalar); + } + DataSize operator*(int32_t scalar) const { + return DataSize::bytes(bytes() * scalar); + } + DataSize operator/(int64_t scalar) const { + return DataSize::bytes(bytes() / scalar); + } + DataSize& operator-=(const DataSize& other) { + bytes_ -= other.bytes(); + return *this; + } + DataSize& operator+=(const DataSize& other) { + bytes_ += other.bytes(); + return *this; + } + bool operator==(const DataSize& other) const { + return bytes_ == other.bytes_; + } + bool operator!=(const DataSize& other) const { + return bytes_ != other.bytes_; + } + bool operator<=(const DataSize& other) const { + return bytes_ <= other.bytes_; + } + bool operator>=(const DataSize& other) const { + return bytes_ >= other.bytes_; + } + bool operator>(const DataSize& other) const { return bytes_ > other.bytes_; } + bool operator<(const DataSize& other) const { return bytes_ < other.bytes_; } + + private: + explicit DataSize(int64_t bytes) : bytes_(bytes) {} + int64_t bytes_; +}; +inline DataSize operator*(const double& scalar, const DataSize& size) { + return size * scalar; +} +inline DataSize operator*(const int64_t& scalar, const DataSize& size) { + return size * scalar; +} +inline DataSize operator*(const int32_t& scalar, const DataSize& size) { + return size * scalar; +} + +// DataRate is a class that represents a given data rate. This can be used to +// represent bandwidth, encoding bitrate, etc. The internal storage is currently +// bits per second (bps) since this makes it easier to intepret the raw value +// when debugging. The promised precision, however is only that it will +// represent bytes per second accurately. Any implementation depending on bps +// resolution should document this by changing this comment. +class DataRate { + public: + static const DataRate kZero; + static const DataRate kPlusInfinity; + static const DataRate kNotInitialized; + DataRate() : DataRate(kNotInitialized) {} + static DataRate Zero() { return kZero; } + static DataRate Infinity() { return kPlusInfinity; } + static DataRate bytes_per_second(int64_t bytes_per_sec) { + return DataRate(bytes_per_sec * 8); + } + static DataRate bits_per_second(int64_t bits_per_sec) { + return DataRate(bits_per_sec); + } + static DataRate bps(int64_t bits_per_sec) { + return DataRate::bits_per_second(bits_per_sec); + } + static DataRate kbps(int64_t kilobits_per_sec) { + return DataRate::bits_per_second(kilobits_per_sec * 1000); + } + int64_t bits_per_second() const { + RTC_DCHECK(IsFinite()); + return bits_per_sec_; + } + int64_t bytes_per_second() const { return bits_per_second() / 8; } + int64_t bps() const { return bits_per_second(); } + int64_t kbps() const { return units_internal::DivideAndRound(bps(), 1000); } + bool IsZero() const { return bits_per_sec_ == 0; } + bool IsInfinite() const { + return bits_per_sec_ == kPlusInfinity.bits_per_sec_; + } + bool IsInitialized() const { + return bits_per_sec_ != kNotInitialized.bits_per_sec_; + } + bool IsFinite() const { return IsInitialized() && !IsInfinite(); } + DataRate operator*(double scalar) const { + return DataRate::bytes_per_second(bytes_per_second() * scalar); + } + DataRate operator*(int64_t scalar) const { + return DataRate::bytes_per_second(bytes_per_second() * scalar); + } + DataRate operator*(int32_t scalar) const { + return DataRate::bytes_per_second(bytes_per_second() * scalar); + } + bool operator==(const DataRate& other) const { + return bits_per_sec_ == other.bits_per_sec_; + } + bool operator!=(const DataRate& other) const { + return bits_per_sec_ != other.bits_per_sec_; + } + bool operator<=(const DataRate& other) const { + return bits_per_sec_ <= other.bits_per_sec_; + } + bool operator>=(const DataRate& other) const { + return bits_per_sec_ >= other.bits_per_sec_; + } + bool operator>(const DataRate& other) const { + return bits_per_sec_ > other.bits_per_sec_; + } + bool operator<(const DataRate& other) const { + return bits_per_sec_ < other.bits_per_sec_; + } + + private: + // Bits per second used internally to simplify debugging by making the value + // more recognizable. + explicit DataRate(int64_t bits_per_second) : bits_per_sec_(bits_per_second) {} + int64_t bits_per_sec_; +}; +inline DataRate operator*(const double& scalar, const DataRate& rate) { + return rate * scalar; +} +inline DataRate operator*(const int64_t& scalar, const DataRate& rate) { + return rate * scalar; +} +inline DataRate operator*(const int32_t& scalar, const DataRate& rate) { + return rate * scalar; +} + +DataRate operator/(const DataSize& size, const TimeDelta& duration); +TimeDelta operator/(const DataSize& size, const DataRate& rate); +DataSize operator*(const DataRate& rate, const TimeDelta& duration); +DataSize operator*(const TimeDelta& duration, const DataRate& rate); + +::std::ostream& operator<<(::std::ostream& os, const DataRate& datarate); +::std::ostream& operator<<(::std::ostream& os, const DataSize& datasize); +::std::ostream& operator<<(::std::ostream& os, const Timestamp& timestamp); +::std::ostream& operator<<(::std::ostream& os, const TimeDelta& delta); + +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_UNITS_H_ diff --git a/modules/congestion_controller/rtp/network_control/network_types.cc b/modules/congestion_controller/rtp/network_control/network_types.cc new file mode 100644 index 0000000000..3a2984c79f --- /dev/null +++ b/modules/congestion_controller/rtp/network_control/network_types.cc @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018 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 "modules/congestion_controller/rtp/network_control/include/network_types.h" + +namespace webrtc { + +StreamsConfig::StreamsConfig() = default; +StreamsConfig::StreamsConfig(const StreamsConfig&) = default; +StreamsConfig::~StreamsConfig() = default; + +PacketResult::PacketResult() = default; +PacketResult::PacketResult(const PacketResult& other) = default; +PacketResult::~PacketResult() = default; + +TransportPacketsFeedback::TransportPacketsFeedback() = default; +TransportPacketsFeedback::TransportPacketsFeedback( + const TransportPacketsFeedback& other) = default; +TransportPacketsFeedback::~TransportPacketsFeedback() = default; + +std::vector TransportPacketsFeedback::ReceivedWithSendInfo() + const { + std::vector res; + for (const PacketResult& fb : packet_feedbacks) { + if (fb.receive_time.IsFinite() && fb.sent_packet.has_value()) { + res.push_back(fb); + } + } + return res; +} + +std::vector TransportPacketsFeedback::LostWithSendInfo() const { + std::vector res; + for (const PacketResult& fb : packet_feedbacks) { + if (fb.receive_time.IsInfinite() && fb.sent_packet.has_value()) { + res.push_back(fb); + } + } + return res; +} + +std::vector TransportPacketsFeedback::PacketsWithFeedback() + const { + return packet_feedbacks; +} + +::std::ostream& operator<<(::std::ostream& os, + const ProbeClusterConfig& config) { + return os << "ProbeClusterConfig(...)"; +} + +::std::ostream& operator<<(::std::ostream& os, const PacerConfig& config) { + return os << "PacerConfig(...)"; +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/network_control/network_units.cc b/modules/congestion_controller/rtp/network_control/network_units.cc new file mode 100644 index 0000000000..29c620ebb6 --- /dev/null +++ b/modules/congestion_controller/rtp/network_control/network_units.cc @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018 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 "modules/congestion_controller/rtp/network_control/include/network_units.h" + +namespace webrtc { +namespace { +int64_t kPlusInfinityVal = std::numeric_limits::max(); +int64_t kMinusInfinityVal = std::numeric_limits::min(); +int64_t kSignedNotInitializedVal = kMinusInfinityVal + 1; +int64_t kNotInitializedVal = -1; +} // namespace +const TimeDelta TimeDelta::kZero = TimeDelta(0); +const TimeDelta TimeDelta::kMinusInfinity = TimeDelta(kMinusInfinityVal); +const TimeDelta TimeDelta::kPlusInfinity = TimeDelta(kPlusInfinityVal); +const TimeDelta TimeDelta::kNotInitialized = + TimeDelta(kSignedNotInitializedVal); + +const Timestamp Timestamp::kPlusInfinity = Timestamp(kPlusInfinityVal); +const Timestamp Timestamp::kNotInitialized = Timestamp(kNotInitializedVal); + +const DataRate DataRate::kZero = DataRate(0); +const DataRate DataRate::kPlusInfinity = DataRate(kPlusInfinityVal); +const DataRate DataRate::kNotInitialized = DataRate(kNotInitializedVal); + +const DataSize DataSize::kZero = DataSize(0); +const DataSize DataSize::kPlusInfinity = DataSize(kPlusInfinityVal); +const DataSize DataSize::kNotInitialized = DataSize(kNotInitializedVal); + +DataRate operator/(const DataSize& size, const TimeDelta& duration) { + RTC_DCHECK(size.bytes() < std::numeric_limits::max() / 1000000) + << "size is too large, size: " << size.bytes() << " is not less than " + << std::numeric_limits::max() / 1000000; + auto bytes_per_sec = size.bytes() * 1000000 / duration.us(); + return DataRate::bytes_per_second(bytes_per_sec); +} + +TimeDelta operator/(const DataSize& size, const DataRate& rate) { + RTC_DCHECK(size.bytes() < std::numeric_limits::max() / 1000000) + << "size is too large, size: " << size.bytes() << " is not less than " + << std::numeric_limits::max() / 1000000; + auto microseconds = size.bytes() * 1000000 / rate.bytes_per_second(); + return TimeDelta::us(microseconds); +} + +DataSize operator*(const DataRate& rate, const TimeDelta& duration) { + auto micro_bytes = rate.bytes_per_second() * duration.us(); + auto bytes = units_internal::DivideAndRound(micro_bytes, 1000000); + return DataSize::bytes(bytes); +} + +DataSize operator*(const TimeDelta& duration, const DataRate& rate) { + return rate * duration; +} + +::std::ostream& operator<<(::std::ostream& os, const DataRate& value) { + if (value == DataRate::kPlusInfinity) { + return os << "inf bps"; + } else if (value == DataRate::kNotInitialized) { + return os << "? bps"; + } else { + return os << value.bps() << " bps"; + } +} +::std::ostream& operator<<(::std::ostream& os, const DataSize& value) { + if (value == DataSize::kPlusInfinity) { + return os << "inf bytes"; + } else if (value == DataSize::kNotInitialized) { + return os << "? bytes"; + } else { + return os << value.bytes() << " bytes"; + } +} +::std::ostream& operator<<(::std::ostream& os, const Timestamp& value) { + if (value == Timestamp::kPlusInfinity) { + return os << "inf ms"; + } else if (value == Timestamp::kNotInitialized) { + return os << "? ms"; + } else { + return os << value.ms() << " ms"; + } +} +::std::ostream& operator<<(::std::ostream& os, const TimeDelta& value) { + if (value == TimeDelta::kPlusInfinity) { + return os << "+inf ms"; + } else if (value == TimeDelta::kMinusInfinity) { + return os << "-inf ms"; + } else if (value == TimeDelta::kNotInitialized) { + return os << "? ms"; + } else { + return os << value.ms() << " ms"; + } +} +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/network_control/network_units_unittest.cc b/modules/congestion_controller/rtp/network_control/network_units_unittest.cc new file mode 100644 index 0000000000..7044d20a26 --- /dev/null +++ b/modules/congestion_controller/rtp/network_control/network_units_unittest.cc @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2018 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 "modules/congestion_controller/rtp/network_control/include/network_units.h" +#include "test/gtest.h" + +namespace webrtc { +namespace test { + +TEST(TimeDeltaTest, GetBackSameValues) { + const int64_t kValue = 499; + for (int sign = -1; sign <= 1; ++sign) { + int64_t value = kValue * sign; + EXPECT_EQ(TimeDelta::ms(value).ms(), value); + EXPECT_EQ(TimeDelta::us(value).us(), value); + EXPECT_EQ(TimeDelta::s(value).s(), value); + EXPECT_EQ(TimeDelta::seconds(value).s(), value); + } + EXPECT_EQ(TimeDelta::Zero().us(), 0); +} + +TEST(TimeDeltaTest, GetDifferentPrefix) { + const int64_t kValue = 3000000; + EXPECT_EQ(TimeDelta::us(kValue).s(), kValue / 1000000); + EXPECT_EQ(TimeDelta::ms(kValue).s(), kValue / 1000); + EXPECT_EQ(TimeDelta::us(kValue).ms(), kValue / 1000); + + EXPECT_EQ(TimeDelta::ms(kValue).us(), kValue * 1000); + EXPECT_EQ(TimeDelta::s(kValue).ms(), kValue * 1000); + EXPECT_EQ(TimeDelta::s(kValue).us(), kValue * 1000000); +} + +TEST(TimeDeltaTest, IdentityChecks) { + const int64_t kValue = 3000; + EXPECT_TRUE(TimeDelta::Zero().IsZero()); + EXPECT_FALSE(TimeDelta::ms(kValue).IsZero()); + + EXPECT_TRUE(TimeDelta::Infinity().IsInfinite()); + EXPECT_TRUE(TimeDelta::kPlusInfinity.IsInfinite()); + EXPECT_TRUE(TimeDelta::kMinusInfinity.IsInfinite()); + EXPECT_FALSE(TimeDelta::Zero().IsInfinite()); + EXPECT_FALSE(TimeDelta::ms(-kValue).IsInfinite()); + EXPECT_FALSE(TimeDelta::ms(kValue).IsInfinite()); + + EXPECT_FALSE(TimeDelta::Infinity().IsFinite()); + EXPECT_FALSE(TimeDelta::kPlusInfinity.IsFinite()); + EXPECT_FALSE(TimeDelta::kMinusInfinity.IsFinite()); + EXPECT_TRUE(TimeDelta::ms(-kValue).IsFinite()); + EXPECT_TRUE(TimeDelta::ms(kValue).IsFinite()); + EXPECT_TRUE(TimeDelta::Zero().IsFinite()); +} + +TEST(TimeDeltaTest, ComparisonOperators) { + const int64_t kSmall = 450; + const int64_t kLarge = 451; + const TimeDelta small = TimeDelta::ms(kSmall); + const TimeDelta large = TimeDelta::ms(kLarge); + + EXPECT_EQ(TimeDelta::Zero(), TimeDelta::Zero()); + EXPECT_EQ(TimeDelta::Infinity(), TimeDelta::Infinity()); + EXPECT_EQ(small, TimeDelta::ms(kSmall)); + EXPECT_LE(small, TimeDelta::ms(kSmall)); + EXPECT_GE(small, TimeDelta::ms(kSmall)); + EXPECT_NE(small, TimeDelta::ms(kLarge)); + EXPECT_LE(small, TimeDelta::ms(kLarge)); + EXPECT_LT(small, TimeDelta::ms(kLarge)); + EXPECT_GE(large, TimeDelta::ms(kSmall)); + EXPECT_GT(large, TimeDelta::ms(kSmall)); + EXPECT_LT(TimeDelta::kZero, small); + EXPECT_GT(TimeDelta::kZero, TimeDelta::ms(-kSmall)); + EXPECT_GT(TimeDelta::kZero, TimeDelta::ms(-kSmall)); + + EXPECT_GT(TimeDelta::kPlusInfinity, large); + EXPECT_LT(TimeDelta::kMinusInfinity, TimeDelta::kZero); +} + +TEST(TimeDeltaTest, MathOperations) { + const int64_t kValueA = 267; + const int64_t kValueB = 450; + const TimeDelta delta_a = TimeDelta::ms(kValueA); + const TimeDelta delta_b = TimeDelta::ms(kValueB); + EXPECT_EQ((delta_a + delta_b).ms(), kValueA + kValueB); + EXPECT_EQ((delta_a - delta_b).ms(), kValueA - kValueB); + + const int32_t kInt32Value = 123; + const double kFloatValue = 123.0; + EXPECT_EQ((TimeDelta::us(kValueA) * kValueB).us(), kValueA * kValueB); + EXPECT_EQ((TimeDelta::us(kValueA) * kInt32Value).us(), kValueA * kInt32Value); + EXPECT_EQ((TimeDelta::us(kValueA) * kFloatValue).us(), kValueA * kFloatValue); + + EXPECT_EQ(TimeDelta::us(-kValueA).Abs().us(), kValueA); + EXPECT_EQ(TimeDelta::us(kValueA).Abs().us(), kValueA); +} + +TEST(TimestampTest, GetBackSameValues) { + const int64_t kValue = 499; + EXPECT_EQ(Timestamp::ms(kValue).ms(), kValue); + EXPECT_EQ(Timestamp::us(kValue).us(), kValue); + EXPECT_EQ(Timestamp::s(kValue).s(), kValue); +} + +TEST(TimestampTest, GetDifferentPrefix) { + const int64_t kValue = 3000000; + EXPECT_EQ(Timestamp::us(kValue).s(), kValue / 1000000); + EXPECT_EQ(Timestamp::ms(kValue).s(), kValue / 1000); + EXPECT_EQ(Timestamp::us(kValue).ms(), kValue / 1000); + + EXPECT_EQ(Timestamp::ms(kValue).us(), kValue * 1000); + EXPECT_EQ(Timestamp::s(kValue).ms(), kValue * 1000); + EXPECT_EQ(Timestamp::s(kValue).us(), kValue * 1000000); +} + +TEST(TimestampTest, IdentityChecks) { + const int64_t kValue = 3000; + + EXPECT_TRUE(Timestamp::Infinity().IsInfinite()); + EXPECT_FALSE(Timestamp::ms(kValue).IsInfinite()); + + EXPECT_FALSE(Timestamp::kNotInitialized.IsFinite()); + EXPECT_FALSE(Timestamp::Infinity().IsFinite()); + EXPECT_TRUE(Timestamp::ms(kValue).IsFinite()); +} + +TEST(TimestampTest, ComparisonOperators) { + const int64_t kSmall = 450; + const int64_t kLarge = 451; + + EXPECT_EQ(Timestamp::Infinity(), Timestamp::Infinity()); + EXPECT_EQ(Timestamp::ms(kSmall), Timestamp::ms(kSmall)); + EXPECT_LE(Timestamp::ms(kSmall), Timestamp::ms(kSmall)); + EXPECT_GE(Timestamp::ms(kSmall), Timestamp::ms(kSmall)); + EXPECT_NE(Timestamp::ms(kSmall), Timestamp::ms(kLarge)); + EXPECT_LE(Timestamp::ms(kSmall), Timestamp::ms(kLarge)); + EXPECT_LT(Timestamp::ms(kSmall), Timestamp::ms(kLarge)); + EXPECT_GE(Timestamp::ms(kLarge), Timestamp::ms(kSmall)); + EXPECT_GT(Timestamp::ms(kLarge), Timestamp::ms(kSmall)); +} + +TEST(UnitConversionTest, TimestampAndTimeDeltaMath) { + const int64_t kValueA = 267; + const int64_t kValueB = 450; + const Timestamp time_a = Timestamp::ms(kValueA); + const Timestamp time_b = Timestamp::ms(kValueB); + const TimeDelta delta_a = TimeDelta::ms(kValueA); + + EXPECT_EQ((time_a - time_b), TimeDelta::ms(kValueA - kValueB)); + EXPECT_EQ((time_b - delta_a), Timestamp::ms(kValueB - kValueA)); + EXPECT_EQ((time_b + delta_a), Timestamp::ms(kValueB + kValueA)); +} + +TEST(DataSizeTest, GetBackSameValues) { + const int64_t kValue = 123 * 8; + EXPECT_EQ(DataSize::bytes(kValue).bytes(), kValue); + EXPECT_EQ(DataSize::bits(kValue).bits(), kValue); +} + +TEST(DataSizeTest, GetDifferentPrefix) { + const int64_t kValue = 123 * 8000; + EXPECT_EQ(DataSize::bytes(kValue).bits(), kValue * 8); + EXPECT_EQ(DataSize::bits(kValue).bytes(), kValue / 8); + EXPECT_EQ(DataSize::bits(kValue).kilobits(), kValue / 1000); + EXPECT_EQ(DataSize::bytes(kValue).kilobytes(), kValue / 1000); +} + +TEST(DataSizeTest, IdentityChecks) { + const int64_t kValue = 3000; + EXPECT_TRUE(DataSize::Zero().IsZero()); + EXPECT_FALSE(DataSize::bytes(kValue).IsZero()); + + EXPECT_TRUE(DataSize::Infinity().IsInfinite()); + EXPECT_TRUE(DataSize::kPlusInfinity.IsInfinite()); + EXPECT_FALSE(DataSize::Zero().IsInfinite()); + EXPECT_FALSE(DataSize::bytes(kValue).IsInfinite()); + + EXPECT_FALSE(DataSize::Infinity().IsFinite()); + EXPECT_FALSE(DataSize::kPlusInfinity.IsFinite()); + EXPECT_TRUE(DataSize::bytes(kValue).IsFinite()); + EXPECT_TRUE(DataSize::Zero().IsFinite()); +} + +TEST(DataSizeTest, ComparisonOperators) { + const int64_t kSmall = 450; + const int64_t kLarge = 451; + const DataSize small = DataSize::bytes(kSmall); + const DataSize large = DataSize::bytes(kLarge); + + EXPECT_EQ(DataSize::Zero(), DataSize::Zero()); + EXPECT_EQ(DataSize::Infinity(), DataSize::Infinity()); + EXPECT_EQ(small, small); + EXPECT_LE(small, small); + EXPECT_GE(small, small); + EXPECT_NE(small, large); + EXPECT_LE(small, large); + EXPECT_LT(small, large); + EXPECT_GE(large, small); + EXPECT_GT(large, small); + EXPECT_LT(DataSize::kZero, small); + + EXPECT_GT(DataSize::kPlusInfinity, large); +} + +TEST(DataSizeTest, MathOperations) { + const int64_t kValueA = 450; + const int64_t kValueB = 267; + const DataSize size_a = DataSize::bytes(kValueA); + const DataSize size_b = DataSize::bytes(kValueB); + EXPECT_EQ((size_a + size_b).bytes(), kValueA + kValueB); + EXPECT_EQ((size_a - size_b).bytes(), kValueA - kValueB); + + const int32_t kInt32Value = 123; + const double kFloatValue = 123.0; + EXPECT_EQ((size_a * kValueB).bytes(), kValueA * kValueB); + EXPECT_EQ((size_a * kInt32Value).bytes(), kValueA * kInt32Value); + EXPECT_EQ((size_a * kFloatValue).bytes(), kValueA * kFloatValue); + + EXPECT_EQ((size_a / 10).bytes(), kValueA / 10); + + DataSize mutable_size = DataSize::bytes(kValueA); + mutable_size += size_b; + EXPECT_EQ(mutable_size.bytes(), kValueA + kValueB); + mutable_size -= size_a; + EXPECT_EQ(mutable_size.bytes(), kValueB); +} + +TEST(DataRateTest, GetBackSameValues) { + const int64_t kValue = 123 * 8; + EXPECT_EQ(DataRate::bytes_per_second(kValue).bytes_per_second(), kValue); + EXPECT_EQ(DataRate::bits_per_second(kValue).bits_per_second(), kValue); + EXPECT_EQ(DataRate::bps(kValue).bps(), kValue); + EXPECT_EQ(DataRate::kbps(kValue).kbps(), kValue); +} + +TEST(DataRateTest, GetDifferentPrefix) { + const int64_t kValue = 123 * 8000; + EXPECT_EQ(DataRate::bytes_per_second(kValue).bps(), kValue * 8); + EXPECT_EQ(DataRate::bits_per_second(kValue).bytes_per_second(), kValue / 8); + EXPECT_EQ(DataRate::bps(kValue).kbps(), kValue / 1000); +} + +TEST(DataRateTest, IdentityChecks) { + const int64_t kValue = 3000; + EXPECT_TRUE(DataRate::Zero().IsZero()); + EXPECT_FALSE(DataRate::bytes_per_second(kValue).IsZero()); + + EXPECT_TRUE(DataRate::Infinity().IsInfinite()); + EXPECT_TRUE(DataRate::kPlusInfinity.IsInfinite()); + EXPECT_FALSE(DataRate::Zero().IsInfinite()); + EXPECT_FALSE(DataRate::bytes_per_second(kValue).IsInfinite()); + + EXPECT_FALSE(DataRate::Infinity().IsFinite()); + EXPECT_FALSE(DataRate::kPlusInfinity.IsFinite()); + EXPECT_TRUE(DataRate::bytes_per_second(kValue).IsFinite()); + EXPECT_TRUE(DataRate::Zero().IsFinite()); +} + +TEST(DataRateTest, ComparisonOperators) { + const int64_t kSmall = 450; + const int64_t kLarge = 451; + const DataRate small = DataRate::bytes_per_second(kSmall); + const DataRate large = DataRate::bytes_per_second(kLarge); + + EXPECT_EQ(DataRate::Zero(), DataRate::Zero()); + EXPECT_EQ(DataRate::Infinity(), DataRate::Infinity()); + EXPECT_EQ(small, small); + EXPECT_LE(small, small); + EXPECT_GE(small, small); + EXPECT_NE(small, large); + EXPECT_LE(small, large); + EXPECT_LT(small, large); + EXPECT_GE(large, small); + EXPECT_GT(large, small); + EXPECT_LT(DataRate::kZero, small); + EXPECT_GT(DataRate::kPlusInfinity, large); +} + +TEST(DataRateTest, MathOperations) { + const int64_t kValueA = 450; + const int64_t kValueB = 267; + const DataRate size_a = DataRate::bytes_per_second(kValueA); + const int32_t kInt32Value = 123; + const double kFloatValue = 123.0; + EXPECT_EQ((size_a * kValueB).bytes_per_second(), kValueA * kValueB); + EXPECT_EQ((size_a * kInt32Value).bytes_per_second(), kValueA * kInt32Value); + EXPECT_EQ((size_a * kFloatValue).bytes_per_second(), kValueA * kFloatValue); +} + +TEST(UnitConversionTest, DataRateAndDataSizeAndTimeDelta) { + const int64_t kValueA = 5; + const int64_t kValueB = 450; + const int64_t kValueC = 45000; + const TimeDelta delta_a = TimeDelta::seconds(kValueA); + const DataRate rate_b = DataRate::bytes_per_second(kValueB); + const DataSize size_c = DataSize::bytes(kValueC); + EXPECT_EQ((delta_a * rate_b).bytes(), kValueA * kValueB); + EXPECT_EQ((size_c / delta_a).bytes_per_second(), kValueC / kValueA); + EXPECT_EQ((size_c / rate_b).s(), kValueC / kValueB); +} + +} // namespace test +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/pacer_controller.cc b/modules/congestion_controller/rtp/pacer_controller.cc new file mode 100644 index 0000000000..69d47b60ef --- /dev/null +++ b/modules/congestion_controller/rtp/pacer_controller.cc @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018 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 "modules/congestion_controller/rtp/pacer_controller.h" + +#include "modules/congestion_controller/rtp/network_control/include/network_units.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +PacerController::PacerController(PacedSender* pacer) : pacer_(pacer) { + sequenced_checker_.Detach(); +} + +PacerController::~PacerController() = default; + +void PacerController::OnCongestionWindow(CongestionWindow congestion_window) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + if (congestion_window.enabled) { + congestion_window_ = congestion_window; + } else { + congestion_window_ = rtc::nullopt; + congested_ = false; + UpdatePacerState(); + } +} + +void PacerController::OnNetworkAvailability(NetworkAvailability msg) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + network_available_ = msg.network_available; + congested_ = false; + UpdatePacerState(); +} + +void PacerController::OnNetworkRouteChange(NetworkRouteChange) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + congested_ = false; + UpdatePacerState(); +} + +void PacerController::OnPacerConfig(PacerConfig msg) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + DataRate pacing_rate = msg.data_window / msg.time_window; + DataRate padding_rate = msg.pad_window / msg.time_window; + pacer_->SetPacingRates(pacing_rate.bps(), padding_rate.bps()); +} + +void PacerController::OnProbeClusterConfig(ProbeClusterConfig config) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + int64_t bitrate_bps = config.target_data_rate.bps(); + pacer_->CreateProbeCluster(bitrate_bps); +} + +void PacerController::OnOutstandingData(OutstandingData msg) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + if (congestion_window_.has_value()) { + congested_ = msg.in_flight_data > congestion_window_->data_window; + } + UpdatePacerState(); +} + +void PacerController::UpdatePacerState() { + bool pause = congested_ || !network_available_; + SetPacerState(pause); +} + +void PacerController::SetPacerState(bool paused) { + if (paused && !pacer_paused_) + pacer_->Pause(); + else if (!paused && pacer_paused_) + pacer_->Resume(); + pacer_paused_ = paused; +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/pacer_controller.h b/modules/congestion_controller/rtp/pacer_controller.h new file mode 100644 index 0000000000..68d0bc03e3 --- /dev/null +++ b/modules/congestion_controller/rtp/pacer_controller.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_PACER_CONTROLLER_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_PACER_CONTROLLER_H_ + +#include + +#include "modules/congestion_controller/rtp/network_control/include/network_types.h" +#include "modules/pacing/paced_sender.h" +#include "rtc_base/sequenced_task_checker.h" + +namespace webrtc { +class Clock; + +// Wrapper class to control pacer using task queues. Note that this class is +// only designed to be used from a single task queue and has no built in +// concurrency safety. +// TODO(srte): Integrate this interface directly into PacedSender. +class PacerController { + public: + explicit PacerController(PacedSender* pacer); + ~PacerController(); + void OnCongestionWindow(CongestionWindow msg); + void OnNetworkAvailability(NetworkAvailability msg); + void OnNetworkRouteChange(NetworkRouteChange msg); + void OnOutstandingData(OutstandingData msg); + void OnPacerConfig(PacerConfig msg); + void OnProbeClusterConfig(ProbeClusterConfig msg); + + private: + void UpdatePacerState(); + void SetPacerState(bool paused); + PacedSender* const pacer_; + + rtc::Optional current_pacer_config_; + rtc::Optional congestion_window_; + bool congested_ = false; + bool pacer_paused_ = false; + bool network_available_ = true; + + rtc::SequencedTaskChecker sequenced_checker_; + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(PacerController); +}; +} // namespace webrtc +#endif // MODULES_CONGESTION_CONTROLLER_RTP_PACER_CONTROLLER_H_ diff --git a/modules/congestion_controller/rtp/probe_bitrate_estimator.cc b/modules/congestion_controller/rtp/probe_bitrate_estimator.cc new file mode 100644 index 0000000000..a7bfa41ca7 --- /dev/null +++ b/modules/congestion_controller/rtp/probe_bitrate_estimator.cc @@ -0,0 +1,189 @@ +/* + * 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 "modules/congestion_controller/rtp/probe_bitrate_estimator.h" + +#include + +#include "logging/rtc_event_log/events/rtc_event_probe_result_failure.h" +#include "logging/rtc_event_log/events/rtc_event_probe_result_success.h" +#include "logging/rtc_event_log/rtc_event_log.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "rtc_base/ptr_util.h" + +namespace { +// The minumum number of probes we need to receive feedback about in percent +// in order to have a valid estimate. +constexpr int kMinReceivedProbesPercent = 80; + +// The minumum number of bytes we need to receive feedback about in percent +// in order to have a valid estimate. +constexpr int kMinReceivedBytesPercent = 80; + +// The maximum |receive rate| / |send rate| ratio for a valid estimate. +constexpr float kMaxValidRatio = 2.0f; + +// The minimum |receive rate| / |send rate| ratio assuming that the link is +// not saturated, i.e. we assume that we will receive at least +// kMinRatioForUnsaturatedLink * |send rate| if |send rate| is less than the +// link capacity. +constexpr float kMinRatioForUnsaturatedLink = 0.9f; + +// The target utilization of the link. If we know true link capacity +// we'd like to send at 95% of that rate. +constexpr float kTargetUtilizationFraction = 0.95f; + +// The maximum time period over which the cluster history is retained. +// This is also the maximum time period beyond which a probing burst is not +// expected to last. +constexpr int kMaxClusterHistoryMs = 1000; + +// The maximum time interval between first and the last probe on a cluster +// on the sender side as well as the receive side. +constexpr int kMaxProbeIntervalMs = 1000; +} // namespace + +namespace webrtc { + +ProbeBitrateEstimator::ProbeBitrateEstimator(RtcEventLog* event_log) + : event_log_(event_log) {} + +ProbeBitrateEstimator::~ProbeBitrateEstimator() = default; + +int ProbeBitrateEstimator::HandleProbeAndEstimateBitrate( + const PacketFeedback& packet_feedback) { + int cluster_id = packet_feedback.pacing_info.probe_cluster_id; + RTC_DCHECK_NE(cluster_id, PacedPacketInfo::kNotAProbe); + + EraseOldClusters(packet_feedback.arrival_time_ms - kMaxClusterHistoryMs); + + int payload_size_bits = + rtc::dchecked_cast(packet_feedback.payload_size * 8); + AggregatedCluster* cluster = &clusters_[cluster_id]; + + if (packet_feedback.send_time_ms < cluster->first_send_ms) { + cluster->first_send_ms = packet_feedback.send_time_ms; + } + if (packet_feedback.send_time_ms > cluster->last_send_ms) { + cluster->last_send_ms = packet_feedback.send_time_ms; + cluster->size_last_send = payload_size_bits; + } + if (packet_feedback.arrival_time_ms < cluster->first_receive_ms) { + cluster->first_receive_ms = packet_feedback.arrival_time_ms; + cluster->size_first_receive = payload_size_bits; + } + if (packet_feedback.arrival_time_ms > cluster->last_receive_ms) { + cluster->last_receive_ms = packet_feedback.arrival_time_ms; + } + cluster->size_total += payload_size_bits; + cluster->num_probes += 1; + + RTC_DCHECK_GT(packet_feedback.pacing_info.probe_cluster_min_probes, 0); + RTC_DCHECK_GT(packet_feedback.pacing_info.probe_cluster_min_bytes, 0); + + int min_probes = packet_feedback.pacing_info.probe_cluster_min_probes * + kMinReceivedProbesPercent / 100; + int min_bytes = packet_feedback.pacing_info.probe_cluster_min_bytes * + kMinReceivedBytesPercent / 100; + if (cluster->num_probes < min_probes || cluster->size_total < min_bytes * 8) + return -1; + + float send_interval_ms = cluster->last_send_ms - cluster->first_send_ms; + float receive_interval_ms = + cluster->last_receive_ms - cluster->first_receive_ms; + + if (send_interval_ms <= 0 || send_interval_ms > kMaxProbeIntervalMs || + receive_interval_ms <= 0 || receive_interval_ms > kMaxProbeIntervalMs) { + RTC_LOG(LS_INFO) << "Probing unsuccessful, invalid send/receive interval" + << " [cluster id: " << cluster_id + << "] [send interval: " << send_interval_ms << " ms]" + << " [receive interval: " << receive_interval_ms << " ms]"; + if (event_log_) { + event_log_->Log(rtc::MakeUnique( + cluster_id, ProbeFailureReason::kInvalidSendReceiveInterval)); + } + return -1; + } + // Since the |send_interval_ms| does not include the time it takes to actually + // send the last packet the size of the last sent packet should not be + // included when calculating the send bitrate. + RTC_DCHECK_GT(cluster->size_total, cluster->size_last_send); + float send_size = cluster->size_total - cluster->size_last_send; + float send_bps = send_size / send_interval_ms * 1000; + + // Since the |receive_interval_ms| does not include the time it takes to + // actually receive the first packet the size of the first received packet + // should not be included when calculating the receive bitrate. + RTC_DCHECK_GT(cluster->size_total, cluster->size_first_receive); + float receive_size = cluster->size_total - cluster->size_first_receive; + float receive_bps = receive_size / receive_interval_ms * 1000; + + float ratio = receive_bps / send_bps; + if (ratio > kMaxValidRatio) { + RTC_LOG(LS_INFO) << "Probing unsuccessful, receive/send ratio too high" + << " [cluster id: " << cluster_id + << "] [send: " << send_size << " bytes / " + << send_interval_ms << " ms = " << send_bps / 1000 + << " kb/s]" + << " [receive: " << receive_size << " bytes / " + << receive_interval_ms << " ms = " << receive_bps / 1000 + << " kb/s]" + << " [ratio: " << receive_bps / 1000 << " / " + << send_bps / 1000 << " = " << ratio + << " > kMaxValidRatio (" << kMaxValidRatio << ")]"; + if (event_log_) { + event_log_->Log(rtc::MakeUnique( + cluster_id, ProbeFailureReason::kInvalidSendReceiveRatio)); + } + return -1; + } + RTC_LOG(LS_INFO) << "Probing successful" + << " [cluster id: " << cluster_id << "] [send: " << send_size + << " bytes / " << send_interval_ms + << " ms = " << send_bps / 1000 << " kb/s]" + << " [receive: " << receive_size << " bytes / " + << receive_interval_ms << " ms = " << receive_bps / 1000 + << " kb/s]"; + + float res = std::min(send_bps, receive_bps); + // If we're receiving at significantly lower bitrate than we were sending at, + // it suggests that we've found the true capacity of the link. In this case, + // set the target bitrate slightly lower to not immediately overuse. + if (receive_bps < kMinRatioForUnsaturatedLink * send_bps) { + RTC_DCHECK_GT(send_bps, receive_bps); + res = kTargetUtilizationFraction * receive_bps; + } + if (event_log_) { + event_log_->Log( + rtc::MakeUnique(cluster_id, res)); + } + estimated_bitrate_bps_ = res; + return *estimated_bitrate_bps_; +} + +rtc::Optional +ProbeBitrateEstimator::FetchAndResetLastEstimatedBitrateBps() { + rtc::Optional estimated_bitrate_bps = estimated_bitrate_bps_; + estimated_bitrate_bps_.reset(); + return estimated_bitrate_bps; +} + +void ProbeBitrateEstimator::EraseOldClusters(int64_t timestamp_ms) { + for (auto it = clusters_.begin(); it != clusters_.end();) { + if (it->second.last_receive_ms < timestamp_ms) { + it = clusters_.erase(it); + } else { + ++it; + } + } +} +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/probe_bitrate_estimator.h b/modules/congestion_controller/rtp/probe_bitrate_estimator.h new file mode 100644 index 0000000000..481cac637e --- /dev/null +++ b/modules/congestion_controller/rtp/probe_bitrate_estimator.h @@ -0,0 +1,55 @@ +/* + * 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 MODULES_CONGESTION_CONTROLLER_RTP_PROBE_BITRATE_ESTIMATOR_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_PROBE_BITRATE_ESTIMATOR_H_ + +#include +#include + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" + +namespace webrtc { +class RtcEventLog; + +class ProbeBitrateEstimator { + public: + explicit ProbeBitrateEstimator(RtcEventLog* event_log); + ~ProbeBitrateEstimator(); + + // Should be called for every probe packet we receive feedback about. + // Returns the estimated bitrate if the probe completes a valid cluster. + int HandleProbeAndEstimateBitrate(const PacketFeedback& packet_feedback); + + rtc::Optional FetchAndResetLastEstimatedBitrateBps(); + + private: + struct AggregatedCluster { + int num_probes = 0; + int64_t first_send_ms = std::numeric_limits::max(); + int64_t last_send_ms = 0; + int64_t first_receive_ms = std::numeric_limits::max(); + int64_t last_receive_ms = 0; + int size_last_send = 0; + int size_first_receive = 0; + int size_total = 0; + }; + + // Erases old cluster data that was seen before |timestamp_ms|. + void EraseOldClusters(int64_t timestamp_ms); + + std::map clusters_; + RtcEventLog* const event_log_; + rtc::Optional estimated_bitrate_bps_; +}; + +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_PROBE_BITRATE_ESTIMATOR_H_ diff --git a/modules/congestion_controller/rtp/probe_bitrate_estimator_unittest.cc b/modules/congestion_controller/rtp/probe_bitrate_estimator_unittest.cc new file mode 100644 index 0000000000..66645aa134 --- /dev/null +++ b/modules/congestion_controller/rtp/probe_bitrate_estimator_unittest.cc @@ -0,0 +1,221 @@ +/* + * 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 "modules/congestion_controller/rtp/probe_bitrate_estimator.h" + +#include +#include + +#include "modules/remote_bitrate_estimator/aimd_rate_control.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { +constexpr int kInvalidBitrate = -1; +constexpr int kDefaultMinProbes = 5; +constexpr int kDefaultMinBytes = 5000; +constexpr float kTargetUtilizationFraction = 0.95f; +} // anonymous namespace + +class TestProbeBitrateEstimator : public ::testing::Test { + public: + TestProbeBitrateEstimator() : probe_bitrate_estimator_(nullptr) {} + + // TODO(philipel): Use PacedPacketInfo when ProbeBitrateEstimator is rewritten + // to use that information. + void AddPacketFeedback(int probe_cluster_id, + size_t size_bytes, + int64_t send_time_ms, + int64_t arrival_time_ms, + int min_probes = kDefaultMinProbes, + int min_bytes = kDefaultMinBytes) { + PacedPacketInfo pacing_info(probe_cluster_id, min_probes, min_bytes); + PacketFeedback packet_feedback(arrival_time_ms, send_time_ms, 0, size_bytes, + pacing_info); + measured_bps_ = + probe_bitrate_estimator_.HandleProbeAndEstimateBitrate(packet_feedback); + } + + protected: + int measured_bps_ = kInvalidBitrate; + ProbeBitrateEstimator probe_bitrate_estimator_; +}; + +TEST_F(TestProbeBitrateEstimator, OneCluster) { + AddPacketFeedback(0, 1000, 0, 10); + AddPacketFeedback(0, 1000, 10, 20); + AddPacketFeedback(0, 1000, 20, 30); + AddPacketFeedback(0, 1000, 30, 40); + + EXPECT_NEAR(measured_bps_, 800000, 10); +} + +TEST_F(TestProbeBitrateEstimator, OneClusterTooFewProbes) { + AddPacketFeedback(0, 2000, 0, 10); + AddPacketFeedback(0, 2000, 10, 20); + AddPacketFeedback(0, 2000, 20, 30); + + EXPECT_EQ(kInvalidBitrate, measured_bps_); +} + +TEST_F(TestProbeBitrateEstimator, OneClusterTooFewBytes) { + const int kMinBytes = 6000; + AddPacketFeedback(0, 800, 0, 10, kDefaultMinProbes, kMinBytes); + AddPacketFeedback(0, 800, 10, 20, kDefaultMinProbes, kMinBytes); + AddPacketFeedback(0, 800, 20, 30, kDefaultMinProbes, kMinBytes); + AddPacketFeedback(0, 800, 30, 40, kDefaultMinProbes, kMinBytes); + AddPacketFeedback(0, 800, 40, 50, kDefaultMinProbes, kMinBytes); + + EXPECT_EQ(kInvalidBitrate, measured_bps_); +} + +TEST_F(TestProbeBitrateEstimator, SmallCluster) { + const int kMinBytes = 1000; + AddPacketFeedback(0, 150, 0, 10, kDefaultMinProbes, kMinBytes); + AddPacketFeedback(0, 150, 10, 20, kDefaultMinProbes, kMinBytes); + AddPacketFeedback(0, 150, 20, 30, kDefaultMinProbes, kMinBytes); + AddPacketFeedback(0, 150, 30, 40, kDefaultMinProbes, kMinBytes); + AddPacketFeedback(0, 150, 40, 50, kDefaultMinProbes, kMinBytes); + AddPacketFeedback(0, 150, 50, 60, kDefaultMinProbes, kMinBytes); + EXPECT_NEAR(measured_bps_, 120000, 10); +} + +TEST_F(TestProbeBitrateEstimator, LargeCluster) { + const int kMinProbes = 30; + const int kMinBytes = 312500; + + int64_t send_time = 0; + int64_t receive_time = 5; + for (int i = 0; i < 25; ++i) { + AddPacketFeedback(0, 12500, send_time, receive_time, kMinProbes, kMinBytes); + ++send_time; + ++receive_time; + } + EXPECT_NEAR(measured_bps_, 100000000, 10); +} + +TEST_F(TestProbeBitrateEstimator, FastReceive) { + AddPacketFeedback(0, 1000, 0, 15); + AddPacketFeedback(0, 1000, 10, 30); + AddPacketFeedback(0, 1000, 20, 35); + AddPacketFeedback(0, 1000, 30, 40); + + EXPECT_NEAR(measured_bps_, 800000, 10); +} + +TEST_F(TestProbeBitrateEstimator, TooFastReceive) { + AddPacketFeedback(0, 1000, 0, 19); + AddPacketFeedback(0, 1000, 10, 22); + AddPacketFeedback(0, 1000, 20, 25); + AddPacketFeedback(0, 1000, 40, 27); + + EXPECT_EQ(measured_bps_, kInvalidBitrate); +} + +TEST_F(TestProbeBitrateEstimator, SlowReceive) { + AddPacketFeedback(0, 1000, 0, 10); + AddPacketFeedback(0, 1000, 10, 40); + AddPacketFeedback(0, 1000, 20, 70); + AddPacketFeedback(0, 1000, 30, 85); + // Expected send rate = 800 kbps, expected receive rate = 320 kbps. + + EXPECT_NEAR(measured_bps_, kTargetUtilizationFraction * 320000, 10); +} + +TEST_F(TestProbeBitrateEstimator, BurstReceive) { + AddPacketFeedback(0, 1000, 0, 50); + AddPacketFeedback(0, 1000, 10, 50); + AddPacketFeedback(0, 1000, 20, 50); + AddPacketFeedback(0, 1000, 40, 50); + + EXPECT_EQ(measured_bps_, kInvalidBitrate); +} + +TEST_F(TestProbeBitrateEstimator, MultipleClusters) { + AddPacketFeedback(0, 1000, 0, 10); + AddPacketFeedback(0, 1000, 10, 20); + AddPacketFeedback(0, 1000, 20, 30); + AddPacketFeedback(0, 1000, 40, 60); + // Expected send rate = 600 kbps, expected receive rate = 480 kbps. + EXPECT_NEAR(measured_bps_, kTargetUtilizationFraction * 480000, 10); + + AddPacketFeedback(0, 1000, 50, 60); + // Expected send rate = 640 kbps, expected receive rate = 640 kbps. + EXPECT_NEAR(measured_bps_, 640000, 10); + + AddPacketFeedback(1, 1000, 60, 70); + AddPacketFeedback(1, 1000, 65, 77); + AddPacketFeedback(1, 1000, 70, 84); + AddPacketFeedback(1, 1000, 75, 90); + // Expected send rate = 1600 kbps, expected receive rate = 1200 kbps. + + EXPECT_NEAR(measured_bps_, kTargetUtilizationFraction * 1200000, 10); +} + +TEST_F(TestProbeBitrateEstimator, IgnoreOldClusters) { + AddPacketFeedback(0, 1000, 0, 10); + AddPacketFeedback(0, 1000, 10, 20); + AddPacketFeedback(0, 1000, 20, 30); + + AddPacketFeedback(1, 1000, 60, 70); + AddPacketFeedback(1, 1000, 65, 77); + AddPacketFeedback(1, 1000, 70, 84); + AddPacketFeedback(1, 1000, 75, 90); + // Expected send rate = 1600 kbps, expected receive rate = 1200 kbps. + + EXPECT_NEAR(measured_bps_, kTargetUtilizationFraction * 1200000, 10); + + // Coming in 6s later + AddPacketFeedback(0, 1000, 40 + 6000, 60 + 6000); + + EXPECT_EQ(measured_bps_, kInvalidBitrate); +} + +TEST_F(TestProbeBitrateEstimator, IgnoreSizeLastSendPacket) { + AddPacketFeedback(0, 1000, 0, 10); + AddPacketFeedback(0, 1000, 10, 20); + AddPacketFeedback(0, 1000, 20, 30); + AddPacketFeedback(0, 1000, 30, 40); + AddPacketFeedback(0, 1500, 40, 50); + // Expected send rate = 800 kbps, expected receive rate = 900 kbps. + + EXPECT_NEAR(measured_bps_, 800000, 10); +} + +TEST_F(TestProbeBitrateEstimator, IgnoreSizeFirstReceivePacket) { + AddPacketFeedback(0, 1500, 0, 10); + AddPacketFeedback(0, 1000, 10, 20); + AddPacketFeedback(0, 1000, 20, 30); + AddPacketFeedback(0, 1000, 30, 40); + // Expected send rate = 933 kbps, expected receive rate = 800 kbps. + + EXPECT_NEAR(measured_bps_, kTargetUtilizationFraction * 800000, 10); +} + +TEST_F(TestProbeBitrateEstimator, NoLastEstimatedBitrateBps) { + EXPECT_FALSE(probe_bitrate_estimator_.FetchAndResetLastEstimatedBitrateBps()); +} + +TEST_F(TestProbeBitrateEstimator, FetchLastEstimatedBitrateBps) { + AddPacketFeedback(0, 1000, 0, 10); + AddPacketFeedback(0, 1000, 10, 20); + AddPacketFeedback(0, 1000, 20, 30); + AddPacketFeedback(0, 1000, 30, 40); + + auto estimated_bitrate_bps = + probe_bitrate_estimator_.FetchAndResetLastEstimatedBitrateBps(); + EXPECT_TRUE(estimated_bitrate_bps); + EXPECT_NEAR(*estimated_bitrate_bps, 800000, 10); + EXPECT_FALSE(probe_bitrate_estimator_.FetchAndResetLastEstimatedBitrateBps()); +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/probe_controller.cc b/modules/congestion_controller/rtp/probe_controller.cc new file mode 100644 index 0000000000..6bac270829 --- /dev/null +++ b/modules/congestion_controller/rtp/probe_controller.cc @@ -0,0 +1,308 @@ +/* + * 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 "modules/congestion_controller/rtp/probe_controller.h" + +#include +#include + +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "system_wrappers/include/field_trial.h" +#include "system_wrappers/include/metrics.h" + +namespace webrtc { + +namespace { +// The minimum number probing packets used. +constexpr int kMinProbePacketsSent = 5; + +// The minimum probing duration in ms. +constexpr int kMinProbeDurationMs = 15; + +// Maximum waiting time from the time of initiating probing to getting +// the measured results back. +constexpr int64_t kMaxWaitingTimeForProbingResultMs = 1000; + +// Value of |min_bitrate_to_probe_further_bps_| that indicates +// further probing is disabled. +constexpr int kExponentialProbingDisabled = 0; + +// Default probing bitrate limit. Applied only when the application didn't +// specify max bitrate. +constexpr int64_t kDefaultMaxProbingBitrateBps = 5000000; + +// Interval between probes when ALR periodic probing is enabled. +constexpr int64_t kAlrPeriodicProbingIntervalMs = 5000; + +// Minimum probe bitrate percentage to probe further for repeated probes, +// relative to the previous probe. For example, if 1Mbps probe results in +// 80kbps, then we'll probe again at 1.6Mbps. In that case second probe won't be +// sent if we get 600kbps from the first one. +constexpr int kRepeatedProbeMinPercentage = 70; + +// If the bitrate drops to a factor |kBitrateDropThreshold| or lower +// and we recover within |kBitrateDropTimeoutMs|, then we'll send +// a probe at a fraction |kProbeFractionAfterDrop| of the original bitrate. +constexpr double kBitrateDropThreshold = 0.66; +constexpr int kBitrateDropTimeoutMs = 5000; +constexpr double kProbeFractionAfterDrop = 0.85; + +// Timeout for probing after leaving ALR. If the bitrate drops significantly, +// (as determined by the delay based estimator) and we leave ALR, then we will +// send a probe if we recover within |kLeftAlrTimeoutMs| ms. +constexpr int kAlrEndedTimeoutMs = 3000; + +// The expected uncertainty of probe result (as a fraction of the target probe +// This is a limit on how often probing can be done when there is a BW +// drop detected in ALR. +constexpr int64_t kMinTimeBetweenAlrProbesMs = 5000; + +// bitrate). Used to avoid probing if the probe bitrate is close to our current +// estimate. +constexpr double kProbeUncertainty = 0.05; + +// Use probing to recover faster after large bitrate estimate drops. +constexpr char kBweRapidRecoveryExperiment[] = + "WebRTC-BweRapidRecoveryExperiment"; + +} // namespace + +ProbeController::ProbeController(NetworkControllerObserver* observer) + : observer_(observer), enable_periodic_alr_probing_(false) { + Reset(0); + in_rapid_recovery_experiment_ = webrtc::field_trial::FindFullName( + kBweRapidRecoveryExperiment) == "Enabled"; +} + +ProbeController::~ProbeController() {} + +void ProbeController::SetBitrates(int64_t min_bitrate_bps, + int64_t start_bitrate_bps, + int64_t max_bitrate_bps, + int64_t at_time_ms) { + if (start_bitrate_bps > 0) { + start_bitrate_bps_ = start_bitrate_bps; + estimated_bitrate_bps_ = start_bitrate_bps; + } else if (start_bitrate_bps_ == 0) { + start_bitrate_bps_ = min_bitrate_bps; + } + + // The reason we use the variable |old_max_bitrate_pbs| is because we + // need to set |max_bitrate_bps_| before we call InitiateProbing. + int64_t old_max_bitrate_bps = max_bitrate_bps_; + max_bitrate_bps_ = max_bitrate_bps; + + switch (state_) { + case State::kInit: + if (network_available_) + InitiateExponentialProbing(at_time_ms); + break; + + case State::kWaitingForProbingResult: + break; + + case State::kProbingComplete: + // If the new max bitrate is higher than the old max bitrate and the + // estimate is lower than the new max bitrate then initiate probing. + if (estimated_bitrate_bps_ != 0 && + old_max_bitrate_bps < max_bitrate_bps_ && + estimated_bitrate_bps_ < max_bitrate_bps_) { + // The assumption is that if we jump more than 20% in the bandwidth + // estimate or if the bandwidth estimate is within 90% of the new + // max bitrate then the probing attempt was successful. + mid_call_probing_succcess_threshold_ = + std::min(estimated_bitrate_bps_ * 1.2, max_bitrate_bps_ * 0.9); + mid_call_probing_waiting_for_result_ = true; + mid_call_probing_bitrate_bps_ = max_bitrate_bps_; + + RTC_HISTOGRAM_COUNTS_10000("WebRTC.BWE.MidCallProbing.Initiated", + max_bitrate_bps_ / 1000); + + InitiateProbing(at_time_ms, {max_bitrate_bps}, false); + } + break; + } +} + +void ProbeController::OnNetworkAvailability(NetworkAvailability msg) { + network_available_ = msg.network_available; + if (network_available_ && state_ == State::kInit && start_bitrate_bps_ > 0) + InitiateExponentialProbing(msg.at_time.ms()); +} + +void ProbeController::InitiateExponentialProbing(int64_t at_time_ms) { + RTC_DCHECK(network_available_); + RTC_DCHECK(state_ == State::kInit); + RTC_DCHECK_GT(start_bitrate_bps_, 0); + + // When probing at 1.8 Mbps ( 6x 300), this represents a threshold of + // 1.2 Mbps to continue probing. + InitiateProbing(at_time_ms, {3 * start_bitrate_bps_, 6 * start_bitrate_bps_}, + true); +} + +void ProbeController::SetEstimatedBitrate(int64_t bitrate_bps, + int64_t at_time_ms) { + int64_t now_ms = at_time_ms; + + if (mid_call_probing_waiting_for_result_ && + bitrate_bps >= mid_call_probing_succcess_threshold_) { + RTC_HISTOGRAM_COUNTS_10000("WebRTC.BWE.MidCallProbing.Success", + mid_call_probing_bitrate_bps_ / 1000); + RTC_HISTOGRAM_COUNTS_10000("WebRTC.BWE.MidCallProbing.ProbedKbps", + bitrate_bps / 1000); + mid_call_probing_waiting_for_result_ = false; + } + + if (state_ == State::kWaitingForProbingResult) { + // Continue probing if probing results indicate channel has greater + // capacity. + RTC_LOG(LS_INFO) << "Measured bitrate: " << bitrate_bps + << " Minimum to probe further: " + << min_bitrate_to_probe_further_bps_; + + if (min_bitrate_to_probe_further_bps_ != kExponentialProbingDisabled && + bitrate_bps > min_bitrate_to_probe_further_bps_) { + // Double the probing bitrate. + InitiateProbing(now_ms, {2 * bitrate_bps}, true); + } + } + + if (bitrate_bps < kBitrateDropThreshold * estimated_bitrate_bps_) { + time_of_last_large_drop_ms_ = now_ms; + bitrate_before_last_large_drop_bps_ = estimated_bitrate_bps_; + } + + estimated_bitrate_bps_ = bitrate_bps; +} + +void ProbeController::EnablePeriodicAlrProbing(bool enable) { + enable_periodic_alr_probing_ = enable; +} + +void ProbeController::SetAlrStartTimeMs( + rtc::Optional alr_start_time_ms) { + alr_start_time_ms_ = alr_start_time_ms; +} +void ProbeController::SetAlrEndedTimeMs(int64_t alr_end_time_ms) { + alr_end_time_ms_.emplace(alr_end_time_ms); +} + +void ProbeController::RequestProbe(int64_t at_time_ms) { + // Called once we have returned to normal state after a large drop in + // estimated bandwidth. The current response is to initiate a single probe + // session (if not already probing) at the previous bitrate. + // + // If the probe session fails, the assumption is that this drop was a + // real one from a competing flow or a network change. + bool in_alr = alr_start_time_ms_.has_value(); + bool alr_ended_recently = + (alr_end_time_ms_.has_value() && + at_time_ms - alr_end_time_ms_.value() < kAlrEndedTimeoutMs); + if (in_alr || alr_ended_recently || in_rapid_recovery_experiment_) { + if (state_ == State::kProbingComplete) { + uint32_t suggested_probe_bps = + kProbeFractionAfterDrop * bitrate_before_last_large_drop_bps_; + uint32_t min_expected_probe_result_bps = + (1 - kProbeUncertainty) * suggested_probe_bps; + int64_t time_since_drop_ms = at_time_ms - time_of_last_large_drop_ms_; + int64_t time_since_probe_ms = at_time_ms - last_bwe_drop_probing_time_ms_; + if (min_expected_probe_result_bps > estimated_bitrate_bps_ && + time_since_drop_ms < kBitrateDropTimeoutMs && + time_since_probe_ms > kMinTimeBetweenAlrProbesMs) { + RTC_LOG(LS_INFO) << "Detected big bandwidth drop, start probing."; + // Track how often we probe in response to bandwidth drop in ALR. + RTC_HISTOGRAM_COUNTS_10000( + "WebRTC.BWE.BweDropProbingIntervalInS", + (at_time_ms - last_bwe_drop_probing_time_ms_) / 1000); + InitiateProbing(at_time_ms, {suggested_probe_bps}, false); + last_bwe_drop_probing_time_ms_ = at_time_ms; + } + } + } +} + +void ProbeController::Reset(int64_t at_time_ms) { + network_available_ = true; + state_ = State::kInit; + min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled; + time_last_probing_initiated_ms_ = 0; + estimated_bitrate_bps_ = 0; + start_bitrate_bps_ = 0; + max_bitrate_bps_ = 0; + int64_t now_ms = at_time_ms; + last_bwe_drop_probing_time_ms_ = now_ms; + alr_end_time_ms_.reset(); + mid_call_probing_waiting_for_result_ = false; + time_of_last_large_drop_ms_ = now_ms; + bitrate_before_last_large_drop_bps_ = 0; +} + +void ProbeController::Process(int64_t at_time_ms) { + int64_t now_ms = at_time_ms; + + if (now_ms - time_last_probing_initiated_ms_ > + kMaxWaitingTimeForProbingResultMs) { + mid_call_probing_waiting_for_result_ = false; + + if (state_ == State::kWaitingForProbingResult) { + RTC_LOG(LS_INFO) << "kWaitingForProbingResult: timeout"; + state_ = State::kProbingComplete; + min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled; + } + } + + if (state_ != State::kProbingComplete || !enable_periodic_alr_probing_) + return; + + // Probe bandwidth periodically when in ALR state. + if (alr_start_time_ms_ && estimated_bitrate_bps_ > 0) { + int64_t next_probe_time_ms = + std::max(*alr_start_time_ms_, time_last_probing_initiated_ms_) + + kAlrPeriodicProbingIntervalMs; + if (now_ms >= next_probe_time_ms) { + InitiateProbing(now_ms, {estimated_bitrate_bps_ * 2}, true); + } + } +} + +void ProbeController::InitiateProbing( + int64_t now_ms, + std::initializer_list bitrates_to_probe, + bool probe_further) { + for (int64_t bitrate : bitrates_to_probe) { + RTC_DCHECK_GT(bitrate, 0); + int64_t max_probe_bitrate_bps = + max_bitrate_bps_ > 0 ? max_bitrate_bps_ : kDefaultMaxProbingBitrateBps; + if (bitrate > max_probe_bitrate_bps) { + bitrate = max_probe_bitrate_bps; + probe_further = false; + } + + ProbeClusterConfig config; + config.at_time = Timestamp::ms(now_ms); + config.target_data_rate = DataRate::bps(rtc::dchecked_cast(bitrate)); + config.target_duration = TimeDelta::ms(kMinProbeDurationMs); + config.target_probe_count = kMinProbePacketsSent; + observer_->OnProbeClusterConfig(config); + } + time_last_probing_initiated_ms_ = now_ms; + if (probe_further) { + state_ = State::kWaitingForProbingResult; + min_bitrate_to_probe_further_bps_ = + (*(bitrates_to_probe.end() - 1)) * kRepeatedProbeMinPercentage / 100; + } else { + state_ = State::kProbingComplete; + min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled; + } +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/probe_controller.h b/modules/congestion_controller/rtp/probe_controller.h new file mode 100644 index 0000000000..2becd3025e --- /dev/null +++ b/modules/congestion_controller/rtp/probe_controller.h @@ -0,0 +1,97 @@ +/* + * 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 MODULES_CONGESTION_CONTROLLER_RTP_PROBE_CONTROLLER_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_PROBE_CONTROLLER_H_ + +#include + +#include + +#include "api/optional.h" +#include "modules/congestion_controller/rtp/network_control/include/network_control.h" + +namespace webrtc { + +class Clock; + +// This class controls initiation of probing to estimate initial channel +// capacity. There is also support for probing during a session when max +// bitrate is adjusted by an application. +class ProbeController { + public: + explicit ProbeController(NetworkControllerObserver* observer); + ~ProbeController(); + + void SetBitrates(int64_t min_bitrate_bps, + int64_t start_bitrate_bps, + int64_t max_bitrate_bps, + int64_t at_time_ms); + + void OnNetworkAvailability(NetworkAvailability msg); + + void SetEstimatedBitrate(int64_t bitrate_bps, int64_t at_time_ms); + + void EnablePeriodicAlrProbing(bool enable); + + void SetAlrStartTimeMs(rtc::Optional alr_start_time); + void SetAlrEndedTimeMs(int64_t alr_end_time); + + void RequestProbe(int64_t at_time_ms); + + // Resets the ProbeController to a state equivalent to as if it was just + // created EXCEPT for |enable_periodic_alr_probing_|. + void Reset(int64_t at_time_ms); + + void Process(int64_t at_time_ms); + + private: + enum class State { + // Initial state where no probing has been triggered yet. + kInit, + // Waiting for probing results to continue further probing. + kWaitingForProbingResult, + // Probing is complete. + kProbingComplete, + }; + + void InitiateExponentialProbing(int64_t at_time_ms); + void InitiateProbing(int64_t now_ms, + std::initializer_list bitrates_to_probe, + bool probe_further); + + NetworkControllerObserver* const observer_; + + bool network_available_; + State state_; + int64_t min_bitrate_to_probe_further_bps_; + int64_t time_last_probing_initiated_ms_; + int64_t estimated_bitrate_bps_; + int64_t start_bitrate_bps_; + int64_t max_bitrate_bps_; + int64_t last_bwe_drop_probing_time_ms_; + rtc::Optional alr_start_time_ms_; + rtc::Optional alr_end_time_ms_; + bool enable_periodic_alr_probing_; + int64_t time_of_last_large_drop_ms_; + int64_t bitrate_before_last_large_drop_bps_; + + bool in_rapid_recovery_experiment_; + // For WebRTC.BWE.MidCallProbing.* metric. + bool mid_call_probing_waiting_for_result_; + int64_t mid_call_probing_bitrate_bps_; + int64_t mid_call_probing_succcess_threshold_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ProbeController); +}; + +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_PROBE_CONTROLLER_H_ diff --git a/modules/congestion_controller/rtp/probe_controller_unittest.cc b/modules/congestion_controller/rtp/probe_controller_unittest.cc new file mode 100644 index 0000000000..4634ea8fae --- /dev/null +++ b/modules/congestion_controller/rtp/probe_controller_unittest.cc @@ -0,0 +1,292 @@ +/* + * 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 + +#include "modules/congestion_controller/rtp/network_control/include/network_types.h" +#include "modules/congestion_controller/rtp/probe_controller.h" +#include "rtc_base/logging.h" +#include "system_wrappers/include/clock.h" +#include "test/gmock.h" +#include "test/gtest.h" + +using testing::_; +using testing::AtLeast; +using testing::Field; +using testing::Matcher; +using testing::NiceMock; +using testing::Return; + +using webrtc::ProbeClusterConfig; + +namespace webrtc { +namespace test { + +namespace { + +constexpr int kMinBitrateBps = 100; +constexpr int kStartBitrateBps = 300; +constexpr int kMaxBitrateBps = 10000; + +constexpr int kExponentialProbingTimeoutMs = 5000; + +constexpr int kAlrProbeInterval = 5000; +constexpr int kAlrEndedTimeoutMs = 3000; +constexpr int kBitrateDropTimeoutMs = 5000; + +inline Matcher DataRateEqBps(int bps) { + return Field(&ProbeClusterConfig::target_data_rate, DataRate::bps(bps)); +} +class MockNetworkControllerObserver : public NetworkControllerObserver { + public: + MOCK_METHOD1(OnCongestionWindow, void(CongestionWindow)); + MOCK_METHOD1(OnPacerConfig, void(PacerConfig)); + MOCK_METHOD1(OnProbeClusterConfig, void(ProbeClusterConfig)); + MOCK_METHOD1(OnTargetTransferRate, void(TargetTransferRate)); +}; +} // namespace + +class ProbeControllerTest : public ::testing::Test { + protected: + ProbeControllerTest() : clock_(100000000L) { + probe_controller_.reset(new ProbeController(&cluster_handler_)); + } + ~ProbeControllerTest() override {} + + void SetNetworkAvailable(bool available) { + NetworkAvailability msg; + msg.at_time = Timestamp::ms(NowMs()); + msg.network_available = available; + probe_controller_->OnNetworkAvailability(msg); + } + + int64_t NowMs() { return clock_.TimeInMilliseconds(); } + + SimulatedClock clock_; + NiceMock cluster_handler_; + std::unique_ptr probe_controller_; +}; + +TEST_F(ProbeControllerTest, InitiatesProbingAtStart) { + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(AtLeast(2)); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); +} + +TEST_F(ProbeControllerTest, ProbeOnlyWhenNetworkIsUp) { + SetNetworkAvailable(false); + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); + + testing::Mock::VerifyAndClearExpectations(&cluster_handler_); + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(AtLeast(2)); + SetNetworkAvailable(true); +} + +TEST_F(ProbeControllerTest, InitiatesProbingOnMaxBitrateIncrease) { + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(AtLeast(2)); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); + // Long enough to time out exponential probing. + clock_.AdvanceTimeMilliseconds(kExponentialProbingTimeoutMs); + probe_controller_->SetEstimatedBitrate(kStartBitrateBps, NowMs()); + probe_controller_->Process(NowMs()); + + EXPECT_CALL(cluster_handler_, + OnProbeClusterConfig(DataRateEqBps(kMaxBitrateBps + 100))); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps + 100, NowMs()); +} + +TEST_F(ProbeControllerTest, InitiatesProbingOnMaxBitrateIncreaseAtMaxBitrate) { + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(AtLeast(2)); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); + // Long enough to time out exponential probing. + clock_.AdvanceTimeMilliseconds(kExponentialProbingTimeoutMs); + probe_controller_->SetEstimatedBitrate(kStartBitrateBps, NowMs()); + probe_controller_->Process(NowMs()); + + probe_controller_->SetEstimatedBitrate(kMaxBitrateBps, NowMs()); + EXPECT_CALL(cluster_handler_, + OnProbeClusterConfig(DataRateEqBps(kMaxBitrateBps + 100))); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps + 100, NowMs()); +} + +TEST_F(ProbeControllerTest, TestExponentialProbing) { + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); + + // Repeated probe should only be sent when estimated bitrate climbs above + // 0.7 * 6 * kStartBitrateBps = 1260. + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0); + probe_controller_->SetEstimatedBitrate(1000, NowMs()); + testing::Mock::VerifyAndClearExpectations(&cluster_handler_); + + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(DataRateEqBps(2 * 1800))); + probe_controller_->SetEstimatedBitrate(1800, NowMs()); +} + +TEST_F(ProbeControllerTest, TestExponentialProbingTimeout) { + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); + + // Advance far enough to cause a time out in waiting for probing result. + clock_.AdvanceTimeMilliseconds(kExponentialProbingTimeoutMs); + probe_controller_->Process(NowMs()); + + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0); + probe_controller_->SetEstimatedBitrate(1800, NowMs()); +} + +TEST_F(ProbeControllerTest, RequestProbeInAlr) { + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(2); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); + probe_controller_->SetEstimatedBitrate(500, NowMs()); + testing::Mock::VerifyAndClearExpectations(&cluster_handler_); + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(DataRateEqBps(0.85 * 500))) + .Times(1); + probe_controller_->SetAlrStartTimeMs(clock_.TimeInMilliseconds()); + clock_.AdvanceTimeMilliseconds(kAlrProbeInterval + 1); + probe_controller_->Process(NowMs()); + probe_controller_->SetEstimatedBitrate(250, NowMs()); + probe_controller_->RequestProbe(NowMs()); +} + +TEST_F(ProbeControllerTest, RequestProbeWhenAlrEndedRecently) { + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(2); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); + probe_controller_->SetEstimatedBitrate(500, NowMs()); + testing::Mock::VerifyAndClearExpectations(&cluster_handler_); + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(DataRateEqBps(0.85 * 500))) + .Times(1); + probe_controller_->SetAlrStartTimeMs(rtc::nullopt); + clock_.AdvanceTimeMilliseconds(kAlrProbeInterval + 1); + probe_controller_->Process(NowMs()); + probe_controller_->SetEstimatedBitrate(250, NowMs()); + probe_controller_->SetAlrEndedTimeMs(clock_.TimeInMilliseconds()); + clock_.AdvanceTimeMilliseconds(kAlrEndedTimeoutMs - 1); + probe_controller_->RequestProbe(NowMs()); +} + +TEST_F(ProbeControllerTest, RequestProbeWhenAlrNotEndedRecently) { + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(2); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); + probe_controller_->SetEstimatedBitrate(500, NowMs()); + testing::Mock::VerifyAndClearExpectations(&cluster_handler_); + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0); + probe_controller_->SetAlrStartTimeMs(rtc::nullopt); + clock_.AdvanceTimeMilliseconds(kAlrProbeInterval + 1); + probe_controller_->Process(NowMs()); + probe_controller_->SetEstimatedBitrate(250, NowMs()); + probe_controller_->SetAlrEndedTimeMs(clock_.TimeInMilliseconds()); + clock_.AdvanceTimeMilliseconds(kAlrEndedTimeoutMs + 1); + probe_controller_->RequestProbe(NowMs()); +} + +TEST_F(ProbeControllerTest, RequestProbeWhenBweDropNotRecent) { + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(2); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); + probe_controller_->SetEstimatedBitrate(500, NowMs()); + testing::Mock::VerifyAndClearExpectations(&cluster_handler_); + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0); + probe_controller_->SetAlrStartTimeMs(clock_.TimeInMilliseconds()); + clock_.AdvanceTimeMilliseconds(kAlrProbeInterval + 1); + probe_controller_->Process(NowMs()); + probe_controller_->SetEstimatedBitrate(250, NowMs()); + clock_.AdvanceTimeMilliseconds(kBitrateDropTimeoutMs + 1); + probe_controller_->RequestProbe(NowMs()); +} + +TEST_F(ProbeControllerTest, PeriodicProbing) { + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(2); + probe_controller_->EnablePeriodicAlrProbing(true); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); + probe_controller_->SetEstimatedBitrate(500, NowMs()); + testing::Mock::VerifyAndClearExpectations(&cluster_handler_); + + int64_t start_time = clock_.TimeInMilliseconds(); + + // Expect the controller to send a new probe after 5s has passed. + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(DataRateEqBps(1000))) + .Times(1); + probe_controller_->SetAlrStartTimeMs(start_time); + clock_.AdvanceTimeMilliseconds(5000); + probe_controller_->Process(NowMs()); + probe_controller_->SetEstimatedBitrate(500, NowMs()); + testing::Mock::VerifyAndClearExpectations(&cluster_handler_); + + // The following probe should be sent at 10s into ALR. + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0); + probe_controller_->SetAlrStartTimeMs(start_time); + clock_.AdvanceTimeMilliseconds(4000); + probe_controller_->Process(NowMs()); + probe_controller_->SetEstimatedBitrate(500, NowMs()); + testing::Mock::VerifyAndClearExpectations(&cluster_handler_); + + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(1); + probe_controller_->SetAlrStartTimeMs(start_time); + clock_.AdvanceTimeMilliseconds(1000); + probe_controller_->Process(NowMs()); + probe_controller_->SetEstimatedBitrate(500, NowMs()); + testing::Mock::VerifyAndClearExpectations(&cluster_handler_); +} + +TEST_F(ProbeControllerTest, PeriodicProbingAfterReset) { + NiceMock local_handler; + probe_controller_.reset(new ProbeController(&local_handler)); + int64_t alr_start_time = clock_.TimeInMilliseconds(); + + probe_controller_->SetAlrStartTimeMs(alr_start_time); + EXPECT_CALL(local_handler, OnProbeClusterConfig(_)).Times(2); + probe_controller_->EnablePeriodicAlrProbing(true); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); + probe_controller_->Reset(NowMs()); + + clock_.AdvanceTimeMilliseconds(10000); + probe_controller_->Process(NowMs()); + + EXPECT_CALL(local_handler, OnProbeClusterConfig(_)).Times(2); + probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps, + kMaxBitrateBps, NowMs()); + + // Make sure we use |kStartBitrateBps| as the estimated bitrate + // until SetEstimatedBitrate is called with an updated estimate. + clock_.AdvanceTimeMilliseconds(10000); + EXPECT_CALL(local_handler, + OnProbeClusterConfig(DataRateEqBps(kStartBitrateBps * 2))); + probe_controller_->Process(NowMs()); +} + +TEST_F(ProbeControllerTest, TestExponentialProbingOverflow) { + const int64_t kMbpsMultiplier = 1000000; + probe_controller_->SetBitrates(kMinBitrateBps, 10 * kMbpsMultiplier, + 100 * kMbpsMultiplier, NowMs()); + + // Verify that probe bitrate is capped at the specified max bitrate. + EXPECT_CALL(cluster_handler_, + OnProbeClusterConfig(DataRateEqBps(100 * kMbpsMultiplier))); + probe_controller_->SetEstimatedBitrate(60 * kMbpsMultiplier, NowMs()); + testing::Mock::VerifyAndClearExpectations(&cluster_handler_); + + // Verify that repeated probes aren't sent. + EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0); + probe_controller_->SetEstimatedBitrate(100 * kMbpsMultiplier, NowMs()); +} + +} // namespace test +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/send_side_congestion_controller.cc b/modules/congestion_controller/rtp/send_side_congestion_controller.cc new file mode 100644 index 0000000000..7606722efe --- /dev/null +++ b/modules/congestion_controller/rtp/send_side_congestion_controller.cc @@ -0,0 +1,661 @@ +/* + * Copyright (c) 2012 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 "modules/congestion_controller/rtp/include/send_side_congestion_controller.h" + +#include +#include +#include +#include +#include "modules/congestion_controller/rtp/include/goog_cc_factory.h" +#include "modules/congestion_controller/rtp/network_control/include/network_types.h" +#include "modules/congestion_controller/rtp/network_control/include/network_units.h" +#include "modules/remote_bitrate_estimator/include/bwe_defines.h" +#include "rtc_base/checks.h" +#include "rtc_base/format_macros.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "rtc_base/numerics/safe_minmax.h" +#include "rtc_base/ptr_util.h" +#include "rtc_base/rate_limiter.h" +#include "rtc_base/sequenced_task_checker.h" +#include "rtc_base/socket.h" +#include "rtc_base/timeutils.h" +#include "system_wrappers/include/field_trial.h" +#include "system_wrappers/include/runtime_enabled_features.h" + +using rtc::MakeUnique; + +namespace webrtc { +namespace { + +static const int64_t kRetransmitWindowSizeMs = 500; + +const char kPacerPushbackExperiment[] = "WebRTC-PacerPushbackExperiment"; + +bool IsPacerPushbackExperimentEnabled() { + return webrtc::field_trial::IsEnabled(kPacerPushbackExperiment) || + (!webrtc::field_trial::IsDisabled(kPacerPushbackExperiment) && + webrtc::runtime_enabled_features::IsFeatureEnabled( + webrtc::runtime_enabled_features::kDualStreamModeFeatureName)); +} + +NetworkControllerFactoryInterface::uptr ControllerFactory( + RtcEventLog* event_log) { + return rtc::MakeUnique(event_log); +} + +void SortPacketFeedbackVector(std::vector* input) { + std::sort(input->begin(), input->end(), PacketFeedbackComparator()); +} + +PacketResult NetworkPacketFeedbackFromRtpPacketFeedback( + const webrtc::PacketFeedback& pf) { + PacketResult feedback; + if (pf.arrival_time_ms == webrtc::PacketFeedback::kNotReceived) + feedback.receive_time = Timestamp::Infinity(); + else + feedback.receive_time = Timestamp::ms(pf.arrival_time_ms); + if (pf.send_time_ms != webrtc::PacketFeedback::kNoSendTime) { + feedback.sent_packet = SentPacket(); + feedback.sent_packet->send_time = Timestamp::ms(pf.send_time_ms); + feedback.sent_packet->size = DataSize::bytes(pf.payload_size); + feedback.sent_packet->pacing_info = pf.pacing_info; + } + return feedback; +} + +std::vector PacketResultsFromRtpFeedbackVector( + const std::vector& feedback_vector) { + RTC_DCHECK(std::is_sorted(feedback_vector.begin(), feedback_vector.end(), + PacketFeedbackComparator())); + + std::vector packet_feedbacks; + packet_feedbacks.reserve(feedback_vector.size()); + for (const PacketFeedback& rtp_feedback : feedback_vector) { + auto feedback = NetworkPacketFeedbackFromRtpPacketFeedback(rtp_feedback); + packet_feedbacks.push_back(feedback); + } + return packet_feedbacks; +} + +TargetRateConstraints ConvertConstraints(int min_bitrate_bps, + int max_bitrate_bps, + int start_bitrate_bps, + const Clock* clock) { + TargetRateConstraints msg; + msg.at_time = Timestamp::ms(clock->TimeInMilliseconds()); + msg.min_data_rate = + min_bitrate_bps >= 0 ? DataRate::bps(min_bitrate_bps) : DataRate::Zero(); + msg.starting_rate = start_bitrate_bps > 0 ? DataRate::bps(start_bitrate_bps) + : DataRate::kNotInitialized; + msg.max_data_rate = max_bitrate_bps > 0 ? DataRate::bps(max_bitrate_bps) + : DataRate::Infinity(); + return msg; +} +} // namespace + +namespace send_side_cc_internal { +class ControlHandler : public NetworkControllerObserver { + public: + ControlHandler(PacerController* pacer_controller, const Clock* clock); + + void OnCongestionWindow(CongestionWindow window) override; + void OnPacerConfig(PacerConfig config) override; + void OnProbeClusterConfig(ProbeClusterConfig config) override; + void OnTargetTransferRate(TargetTransferRate target_rate) override; + + void OnNetworkAvailability(NetworkAvailability msg); + void OnPacerQueueUpdate(PacerQueueUpdate msg); + + void RegisterNetworkObserver( + SendSideCongestionController::Observer* observer); + void DeRegisterNetworkObserver( + SendSideCongestionController::Observer* observer); + + rtc::Optional last_transfer_rate(); + bool pacer_configured(); + RateLimiter* retransmission_rate_limiter(); + + private: + void OnNetworkInvalidation(); + bool GetNetworkParameters(int32_t* estimated_bitrate_bps, + uint8_t* fraction_loss, + int64_t* rtt_ms); + bool IsSendQueueFull() const; + bool HasNetworkParametersToReportChanged(int64_t bitrate_bps, + uint8_t fraction_loss, + int64_t rtt); + PacerController* pacer_controller_; + RateLimiter retransmission_rate_limiter_; + + rtc::CriticalSection state_lock_; + rtc::Optional last_target_rate_ + RTC_GUARDED_BY(state_lock_); + bool pacer_configured_ RTC_GUARDED_BY(state_lock_) = false; + + SendSideCongestionController::Observer* observer_ = nullptr; + rtc::Optional current_target_rate_msg_; + bool network_available_ = true; + int64_t last_reported_target_bitrate_bps_ = 0; + uint8_t last_reported_fraction_loss_ = 0; + int64_t last_reported_rtt_ms_ = 0; + const bool pacer_pushback_experiment_ = false; + int64_t pacer_expected_queue_ms_ = 0; + float encoding_rate_ratio_ = 1.0; + + rtc::SequencedTaskChecker sequenced_checker_; + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ControlHandler); +}; + +ControlHandler::ControlHandler(PacerController* pacer_controller, + const Clock* clock) + : pacer_controller_(pacer_controller), + retransmission_rate_limiter_(clock, kRetransmitWindowSizeMs), + pacer_pushback_experiment_(IsPacerPushbackExperimentEnabled()) { + sequenced_checker_.Detach(); +} + +void ControlHandler::OnCongestionWindow(CongestionWindow window) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + pacer_controller_->OnCongestionWindow(window); +} + +void ControlHandler::OnPacerConfig(PacerConfig config) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + pacer_controller_->OnPacerConfig(config); + rtc::CritScope cs(&state_lock_); + pacer_configured_ = true; +} + +void ControlHandler::OnProbeClusterConfig(ProbeClusterConfig config) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + pacer_controller_->OnProbeClusterConfig(config); +} + +void ControlHandler::OnTargetTransferRate(TargetTransferRate target_rate) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + retransmission_rate_limiter_.SetMaxRate( + target_rate.network_estimate.bandwidth.bps()); + + current_target_rate_msg_ = target_rate; + OnNetworkInvalidation(); + rtc::CritScope cs(&state_lock_); + last_target_rate_ = target_rate; +} + +void ControlHandler::OnNetworkAvailability(NetworkAvailability msg) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + network_available_ = msg.network_available; + OnNetworkInvalidation(); +} + +void ControlHandler::OnPacerQueueUpdate(PacerQueueUpdate msg) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + pacer_expected_queue_ms_ = msg.expected_queue_time.ms(); + OnNetworkInvalidation(); +} + +void ControlHandler::RegisterNetworkObserver( + SendSideCongestionController::Observer* observer) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + RTC_DCHECK(observer_ == nullptr); + observer_ = observer; +} + +void ControlHandler::DeRegisterNetworkObserver( + SendSideCongestionController::Observer* observer) { + RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_); + RTC_DCHECK_EQ(observer_, observer); + observer_ = nullptr; +} + +void ControlHandler::OnNetworkInvalidation() { + if (!current_target_rate_msg_.has_value()) + return; + + uint32_t target_bitrate_bps = current_target_rate_msg_->target_rate.bps(); + int64_t rtt_ms = + current_target_rate_msg_->network_estimate.round_trip_time.ms(); + float loss_rate_ratio = + current_target_rate_msg_->network_estimate.loss_rate_ratio; + + int loss_ratio_255 = loss_rate_ratio * 255; + uint8_t fraction_loss = + rtc::dchecked_cast(rtc::SafeClamp(loss_ratio_255, 0, 255)); + + int64_t probing_interval_ms = + current_target_rate_msg_->network_estimate.bwe_period.ms(); + + if (!network_available_) { + target_bitrate_bps = 0; + } else if (!pacer_pushback_experiment_) { + target_bitrate_bps = IsSendQueueFull() ? 0 : target_bitrate_bps; + } else { + int64_t queue_length_ms = pacer_expected_queue_ms_; + + if (queue_length_ms == 0) { + encoding_rate_ratio_ = 1.0; + } else if (queue_length_ms > 50) { + float encoding_ratio = 1.0 - queue_length_ms / 1000.0; + encoding_rate_ratio_ = std::min(encoding_rate_ratio_, encoding_ratio); + encoding_rate_ratio_ = std::max(encoding_rate_ratio_, 0.0f); + } + + target_bitrate_bps *= encoding_rate_ratio_; + target_bitrate_bps = target_bitrate_bps < 50000 ? 0 : target_bitrate_bps; + } + if (HasNetworkParametersToReportChanged(target_bitrate_bps, fraction_loss, + rtt_ms)) { + if (observer_) { + observer_->OnNetworkChanged(target_bitrate_bps, fraction_loss, rtt_ms, + probing_interval_ms); + } + } +} +bool ControlHandler::HasNetworkParametersToReportChanged( + int64_t target_bitrate_bps, + uint8_t fraction_loss, + int64_t rtt_ms) { + bool changed = last_reported_target_bitrate_bps_ != target_bitrate_bps || + (target_bitrate_bps > 0 && + (last_reported_fraction_loss_ != fraction_loss || + last_reported_rtt_ms_ != rtt_ms)); + if (changed && + (last_reported_target_bitrate_bps_ == 0 || target_bitrate_bps == 0)) { + RTC_LOG(LS_INFO) << "Bitrate estimate state changed, BWE: " + << target_bitrate_bps << " bps."; + } + last_reported_target_bitrate_bps_ = target_bitrate_bps; + last_reported_fraction_loss_ = fraction_loss; + last_reported_rtt_ms_ = rtt_ms; + return changed; +} + +bool ControlHandler::IsSendQueueFull() const { + return pacer_expected_queue_ms_ > PacedSender::kMaxQueueLengthMs; +} + +rtc::Optional ControlHandler::last_transfer_rate() { + rtc::CritScope cs(&state_lock_); + return last_target_rate_; +} + +bool ControlHandler::pacer_configured() { + rtc::CritScope cs(&state_lock_); + return pacer_configured_; +} + +RateLimiter* ControlHandler::retransmission_rate_limiter() { + return &retransmission_rate_limiter_; +} +} // namespace send_side_cc_internal + +SendSideCongestionController::SendSideCongestionController( + const Clock* clock, + Observer* observer, + RtcEventLog* event_log, + PacedSender* pacer) + : SendSideCongestionController(clock, + event_log, + pacer, + ControllerFactory(event_log)) { + if (observer != nullptr) + RegisterNetworkObserver(observer); +} + +SendSideCongestionController::SendSideCongestionController( + const Clock* clock, + RtcEventLog* event_log, + PacedSender* pacer, + NetworkControllerFactoryInterface::uptr controller_factory) + : clock_(clock), + pacer_(pacer), + transport_feedback_adapter_(clock_), + pacer_controller_(MakeUnique(pacer_)), + control_handler(MakeUnique( + pacer_controller_.get(), + clock_)), + controller_(controller_factory->Create(control_handler.get())), + process_interval_(controller_factory->GetProcessInterval()), + send_side_bwe_with_overhead_( + webrtc::field_trial::IsEnabled("WebRTC-SendSideBwe-WithOverhead")), + transport_overhead_bytes_per_packet_(0), + network_available_(true), + task_queue_(MakeUnique("SendSideCCQueue")) {} + +SendSideCongestionController::~SendSideCongestionController() { + // Must be destructed before any objects used by calls on the task queue. + task_queue_.reset(); +} + +void SendSideCongestionController::RegisterPacketFeedbackObserver( + PacketFeedbackObserver* observer) { + transport_feedback_adapter_.RegisterPacketFeedbackObserver(observer); +} + +void SendSideCongestionController::DeRegisterPacketFeedbackObserver( + PacketFeedbackObserver* observer) { + transport_feedback_adapter_.DeRegisterPacketFeedbackObserver(observer); +} + +void SendSideCongestionController::RegisterNetworkObserver(Observer* observer) { + WaitOnTask([this, observer]() { + control_handler->RegisterNetworkObserver(observer); + }); +} + +void SendSideCongestionController::DeRegisterNetworkObserver( + Observer* observer) { + WaitOnTask([this, observer]() { + control_handler->DeRegisterNetworkObserver(observer); + }); +} + +void SendSideCongestionController::SetBweBitrates(int min_bitrate_bps, + int start_bitrate_bps, + int max_bitrate_bps) { + TargetRateConstraints msg = ConvertConstraints( + min_bitrate_bps, max_bitrate_bps, start_bitrate_bps, clock_); + WaitOnTask([this, msg]() { controller_->OnTargetRateConstraints(msg); }); +} + +// TODO(holmer): Split this up and use SetBweBitrates in combination with +// OnNetworkRouteChanged. +void SendSideCongestionController::OnNetworkRouteChanged( + const rtc::NetworkRoute& network_route, + int start_bitrate_bps, + int min_bitrate_bps, + int max_bitrate_bps) { + transport_feedback_adapter_.SetNetworkIds(network_route.local_network_id, + network_route.remote_network_id); + + NetworkRouteChange msg; + msg.at_time = Timestamp::ms(clock_->TimeInMilliseconds()); + msg.constraints = ConvertConstraints(min_bitrate_bps, max_bitrate_bps, + start_bitrate_bps, clock_); + WaitOnTask([this, msg]() { + controller_->OnNetworkRouteChange(msg); + pacer_controller_->OnNetworkRouteChange(msg); + }); +} + +bool SendSideCongestionController::AvailableBandwidth( + uint32_t* bandwidth) const { + // TODO(srte): Remove this interface and push information about bandwidth + // estimation to users of this class, thereby reducing synchronous calls. + if (control_handler->last_transfer_rate().has_value()) { + *bandwidth = + control_handler->last_transfer_rate()->network_estimate.bandwidth.bps(); + return true; + } + return false; +} + +RtcpBandwidthObserver* SendSideCongestionController::GetBandwidthObserver() { + return this; +} + +RateLimiter* SendSideCongestionController::GetRetransmissionRateLimiter() { + return control_handler->retransmission_rate_limiter(); +} + +void SendSideCongestionController::EnablePeriodicAlrProbing(bool enable) { + WaitOnTask([this, enable]() { + streams_config_.requests_alr_probing = enable; + UpdateStreamsConfig(); + }); +} + +void SendSideCongestionController::UpdateStreamsConfig() { + RTC_DCHECK(task_queue_->IsCurrent()); + streams_config_.at_time = Timestamp::ms(clock_->TimeInMilliseconds()); + controller_->OnStreamsConfig(streams_config_); +} + +int64_t SendSideCongestionController::GetPacerQueuingDelayMs() const { + // TODO(srte): This should be made less synchronous. Now it grabs a lock in + // the pacer just for stats usage. Some kind of push interface might make + // sense. + return network_available_ ? pacer_->QueueInMs() : 0; +} + +int64_t SendSideCongestionController::GetFirstPacketTimeMs() const { + return pacer_->FirstSentPacketTimeMs(); +} + +TransportFeedbackObserver* +SendSideCongestionController::GetTransportFeedbackObserver() { + return this; +} + +void SendSideCongestionController::SignalNetworkState(NetworkState state) { + RTC_LOG(LS_INFO) << "SignalNetworkState " + << (state == kNetworkUp ? "Up" : "Down"); + NetworkAvailability msg; + msg.at_time = Timestamp::ms(clock_->TimeInMilliseconds()); + msg.network_available = state == kNetworkUp; + network_available_ = msg.network_available; + WaitOnTask([this, msg]() { + controller_->OnNetworkAvailability(msg); + pacer_controller_->OnNetworkAvailability(msg); + control_handler->OnNetworkAvailability(msg); + }); +} + +void SendSideCongestionController::SetTransportOverhead( + size_t transport_overhead_bytes_per_packet) { + transport_overhead_bytes_per_packet_ = transport_overhead_bytes_per_packet; +} + +void SendSideCongestionController::OnSentPacket( + const rtc::SentPacket& sent_packet) { + // We're not interested in packets without an id, which may be stun packets, + // etc, sent on the same transport. + if (sent_packet.packet_id == -1) + return; + transport_feedback_adapter_.OnSentPacket(sent_packet.packet_id, + sent_packet.send_time_ms); + MaybeUpdateOutstandingData(); + auto packet = transport_feedback_adapter_.GetPacket(sent_packet.packet_id); + if (packet.has_value()) { + SentPacket msg; + msg.size = DataSize::bytes(packet->payload_size); + msg.send_time = Timestamp::ms(packet->send_time_ms); + task_queue_->PostTask([this, msg]() { controller_->OnSentPacket(msg); }); + } +} + +void SendSideCongestionController::OnRttUpdate(int64_t avg_rtt_ms, + int64_t max_rtt_ms) { + int64_t now_ms = clock_->TimeInMilliseconds(); + RoundTripTimeUpdate report; + report.receive_time = Timestamp::ms(now_ms); + report.round_trip_time = TimeDelta::ms(avg_rtt_ms); + report.smoothed = true; + task_queue_->PostTask( + [this, report]() { controller_->OnRoundTripTimeUpdate(report); }); +} + +int64_t SendSideCongestionController::TimeUntilNextProcess() { + const int kMaxProcessInterval = 60 * 1000; + if (process_interval_.IsInfinite()) + return kMaxProcessInterval; + int64_t next_process_ms = last_process_update_ms_ + process_interval_.ms(); + int64_t time_until_next_process = + next_process_ms - clock_->TimeInMilliseconds(); + return std::max(time_until_next_process, 0); +} + +void SendSideCongestionController::Process() { + int64_t now_ms = clock_->TimeInMilliseconds(); + last_process_update_ms_ = now_ms; + { + ProcessInterval msg; + msg.at_time = Timestamp::ms(now_ms); + task_queue_->PostTask( + [this, msg]() { controller_->OnProcessInterval(msg); }); + } + if (control_handler->pacer_configured()) { + PacerQueueUpdate msg; + msg.expected_queue_time = TimeDelta::ms(pacer_->ExpectedQueueTimeMs()); + task_queue_->PostTask( + [this, msg]() { control_handler->OnPacerQueueUpdate(msg); }); + } +} + +void SendSideCongestionController::AddPacket( + uint32_t ssrc, + uint16_t sequence_number, + size_t length, + const PacedPacketInfo& pacing_info) { + if (send_side_bwe_with_overhead_) { + length += transport_overhead_bytes_per_packet_; + } + transport_feedback_adapter_.AddPacket(ssrc, sequence_number, length, + pacing_info); +} + +void SendSideCongestionController::OnTransportFeedback( + const rtcp::TransportFeedback& feedback) { + RTC_DCHECK_RUNS_SERIALIZED(&worker_race_); + int64_t feedback_time_ms = clock_->TimeInMilliseconds(); + + DataSize prior_in_flight = + DataSize::bytes(transport_feedback_adapter_.GetOutstandingBytes()); + transport_feedback_adapter_.OnTransportFeedback(feedback); + MaybeUpdateOutstandingData(); + + std::vector feedback_vector = + transport_feedback_adapter_.GetTransportFeedbackVector(); + SortPacketFeedbackVector(&feedback_vector); + + if (!feedback_vector.empty()) { + TransportPacketsFeedback msg; + msg.packet_feedbacks = PacketResultsFromRtpFeedbackVector(feedback_vector); + msg.feedback_time = Timestamp::ms(feedback_time_ms); + msg.prior_in_flight = prior_in_flight; + msg.data_in_flight = + DataSize::bytes(transport_feedback_adapter_.GetOutstandingBytes()); + task_queue_->PostTask( + [this, msg]() { controller_->OnTransportPacketsFeedback(msg); }); + } +} + +void SendSideCongestionController::MaybeUpdateOutstandingData() { + OutstandingData msg; + msg.in_flight_data = + DataSize::bytes(transport_feedback_adapter_.GetOutstandingBytes()); + task_queue_->PostTask( + [this, msg]() { pacer_controller_->OnOutstandingData(msg); }); +} + +std::vector +SendSideCongestionController::GetTransportFeedbackVector() const { + RTC_DCHECK_RUNS_SERIALIZED(&worker_race_); + return transport_feedback_adapter_.GetTransportFeedbackVector(); +} + +void SendSideCongestionController::WaitOnTasks() { + rtc::Event event(false, false); + task_queue_->PostTask([&event]() { event.Set(); }); + event.Wait(rtc::Event::kForever); +} + +void SendSideCongestionController::WaitOnTask(std::function closure) { + rtc::Event done(false, false); + task_queue_->PostTask(rtc::NewClosure(closure, [&done] { done.Set(); })); + done.Wait(rtc::Event::kForever); +} + +void SendSideCongestionController::SetSendBitrateLimits( + int64_t min_send_bitrate_bps, + int64_t max_padding_bitrate_bps) { + WaitOnTask([this, min_send_bitrate_bps, max_padding_bitrate_bps]() { + streams_config_.min_pacing_rate = DataRate::bps(min_send_bitrate_bps); + streams_config_.max_padding_rate = DataRate::bps(max_padding_bitrate_bps); + UpdateStreamsConfig(); + }); +} + +void SendSideCongestionController::SetPacingFactor(float pacing_factor) { + WaitOnTask([this, pacing_factor]() { + streams_config_.pacing_factor = pacing_factor; + UpdateStreamsConfig(); + }); +} + +void SendSideCongestionController::OnReceivedEstimatedBitrate( + uint32_t bitrate) { + RemoteBitrateReport msg; + msg.receive_time = Timestamp::ms(clock_->TimeInMilliseconds()); + msg.bandwidth = DataRate::bps(bitrate); + task_queue_->PostTask( + [this, msg]() { controller_->OnRemoteBitrateReport(msg); }); +} + +void SendSideCongestionController::OnReceivedRtcpReceiverReport( + const webrtc::ReportBlockList& report_blocks, + int64_t rtt_ms, + int64_t now_ms) { + OnReceivedRtcpReceiverReportBlocks(report_blocks, now_ms); + + RoundTripTimeUpdate report; + report.receive_time = Timestamp::ms(now_ms); + report.round_trip_time = TimeDelta::ms(rtt_ms); + report.smoothed = false; + task_queue_->PostTask( + [this, report]() { controller_->OnRoundTripTimeUpdate(report); }); +} + +void SendSideCongestionController::OnReceivedRtcpReceiverReportBlocks( + const ReportBlockList& report_blocks, + int64_t now_ms) { + if (report_blocks.empty()) + return; + + int total_packets_lost_delta = 0; + int total_packets_delta = 0; + + // Compute the packet loss from all report blocks. + for (const RTCPReportBlock& report_block : report_blocks) { + auto it = last_report_blocks_.find(report_block.source_ssrc); + if (it != last_report_blocks_.end()) { + auto number_of_packets = report_block.extended_highest_sequence_number - + it->second.extended_highest_sequence_number; + total_packets_delta += number_of_packets; + auto lost_delta = report_block.packets_lost - it->second.packets_lost; + total_packets_lost_delta += lost_delta; + } + last_report_blocks_[report_block.source_ssrc] = report_block; + } + // Can only compute delta if there has been previous blocks to compare to. If + // not, total_packets_delta will be unchanged and there's nothing more to do. + if (!total_packets_delta) + return; + int packets_received_delta = total_packets_delta - total_packets_lost_delta; + // To detect lost packets, at least one packet has to be received. This check + // is needed to avoid bandwith detection update in + // VideoSendStreamTest.SuspendBelowMinBitrate + + if (packets_received_delta < 1) + return; + Timestamp now = Timestamp::ms(now_ms); + TransportLossReport msg; + msg.packets_lost_delta = total_packets_lost_delta; + msg.packets_received_delta = packets_received_delta; + msg.receive_time = now; + msg.start_time = last_report_block_time_; + msg.end_time = now; + task_queue_->PostTask( + [this, msg]() { controller_->OnTransportLossReport(msg); }); + last_report_block_time_ = now; +} +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/send_side_congestion_controller_unittest.cc b/modules/congestion_controller/rtp/send_side_congestion_controller_unittest.cc new file mode 100644 index 0000000000..896cd76b5d --- /dev/null +++ b/modules/congestion_controller/rtp/send_side_congestion_controller_unittest.cc @@ -0,0 +1,518 @@ +/* + * 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 "modules/congestion_controller/rtp/include/send_side_congestion_controller.h" +#include "logging/rtc_event_log/mock/mock_rtc_event_log.h" +#include "modules/congestion_controller/rtp/congestion_controller_unittests_helper.h" +#include "modules/congestion_controller/rtp/include/mock/mock_congestion_observer.h" +#include "modules/pacing/mock/mock_paced_sender.h" +#include "modules/pacing/packet_router.h" +#include "modules/remote_bitrate_estimator/include/bwe_defines.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h" +#include "rtc_base/socket.h" +#include "system_wrappers/include/clock.h" +#include "test/field_trial.h" +#include "test/gmock.h" +#include "test/gtest.h" + +using testing::_; +using testing::AtLeast; +using testing::Ge; +using testing::NiceMock; +using testing::Return; +using testing::SaveArg; +using testing::StrictMock; + +namespace webrtc { +namespace test { + +namespace { +const webrtc::PacedPacketInfo kPacingInfo0(0, 5, 2000); +const webrtc::PacedPacketInfo kPacingInfo1(1, 8, 4000); + +const uint32_t kInitialBitrateBps = 60000; +const float kDefaultPacingRate = 2.5f; + +class SendSideCongestionControllerForTest + : public SendSideCongestionController { + public: + SendSideCongestionControllerForTest(const Clock* clock, + Observer* observer, + RtcEventLog* event_log, + PacedSender* pacer) + : SendSideCongestionController(clock, observer, event_log, pacer) {} + ~SendSideCongestionControllerForTest() {} + void Process() override { + SendSideCongestionController::Process(); + SendSideCongestionController::WaitOnTasks(); + } +}; +} // namespace + + +class SendSideCongestionControllerTest : public ::testing::Test { + protected: + SendSideCongestionControllerTest() + : clock_(123456), target_bitrate_observer_(this) {} + ~SendSideCongestionControllerTest() override {} + + void SetUp() override { + pacer_.reset(new NiceMock()); + controller_.reset(new SendSideCongestionControllerForTest( + &clock_, &observer_, &event_log_, pacer_.get())); + bandwidth_observer_ = controller_->GetBandwidthObserver(); + + // Set the initial bitrate estimate and expect the |observer| and |pacer_| + // to be updated. + EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps, _, _, _)); + EXPECT_CALL(*pacer_, + SetPacingRates(kInitialBitrateBps * kDefaultPacingRate, _)); + EXPECT_CALL(*pacer_, CreateProbeCluster(kInitialBitrateBps * 3)); + EXPECT_CALL(*pacer_, CreateProbeCluster(kInitialBitrateBps * 5)); + controller_->SetBweBitrates(0, kInitialBitrateBps, 5 * kInitialBitrateBps); + } + + // Custom setup - use an observer that tracks the target bitrate, without + // prescribing on which iterations it must change (like a mock would). + void TargetBitrateTrackingSetup() { + bandwidth_observer_ = nullptr; + pacer_.reset(new NiceMock()); + controller_.reset(new SendSideCongestionControllerForTest( + &clock_, &target_bitrate_observer_, &event_log_, pacer_.get())); + controller_->SetBweBitrates(0, kInitialBitrateBps, 5 * kInitialBitrateBps); + } + + void OnSentPacket(const PacketFeedback& packet_feedback) { + constexpr uint32_t ssrc = 0; + controller_->AddPacket(ssrc, packet_feedback.sequence_number, + packet_feedback.payload_size, + packet_feedback.pacing_info); + controller_->OnSentPacket(rtc::SentPacket(packet_feedback.sequence_number, + packet_feedback.send_time_ms)); + } + + // Allows us to track the target bitrate, without prescribing the exact + // iterations when this would hapen, like a mock would. + class TargetBitrateObserver : public SendSideCongestionController::Observer { + public: + explicit TargetBitrateObserver(SendSideCongestionControllerTest* owner) + : owner_(owner) {} + ~TargetBitrateObserver() override = default; + void OnNetworkChanged(uint32_t bitrate_bps, + uint8_t fraction_loss, // 0 - 255. + int64_t rtt_ms, + int64_t probing_interval_ms) override { + owner_->target_bitrate_bps_ = bitrate_bps; + } + + private: + SendSideCongestionControllerTest* owner_; + }; + + void PacketTransmissionAndFeedbackBlock(uint16_t* seq_num, + int64_t runtime_ms, + int64_t delay) { + int64_t delay_buildup = 0; + int64_t start_time_ms = clock_.TimeInMilliseconds(); + while (clock_.TimeInMilliseconds() - start_time_ms < runtime_ms) { + constexpr size_t kPayloadSize = 1000; + PacketFeedback packet(clock_.TimeInMilliseconds() + delay_buildup, + clock_.TimeInMilliseconds(), *seq_num, kPayloadSize, + PacedPacketInfo()); + delay_buildup += delay; // Delay has to increase, or it's just RTT. + OnSentPacket(packet); + // Create expected feedback and send into adapter. + std::unique_ptr feedback( + new rtcp::TransportFeedback()); + feedback->SetBase(packet.sequence_number, packet.arrival_time_ms * 1000); + EXPECT_TRUE(feedback->AddReceivedPacket(packet.sequence_number, + packet.arrival_time_ms * 1000)); + rtc::Buffer raw_packet = feedback->Build(); + feedback = rtcp::TransportFeedback::ParseFrom(raw_packet.data(), + raw_packet.size()); + EXPECT_TRUE(feedback.get() != nullptr); + controller_->OnTransportFeedback(*feedback.get()); + clock_.AdvanceTimeMilliseconds(50); + controller_->Process(); + ++(*seq_num); + } + } + + SimulatedClock clock_; + StrictMock observer_; + TargetBitrateObserver target_bitrate_observer_; + NiceMock event_log_; + RtcpBandwidthObserver* bandwidth_observer_; + PacketRouter packet_router_; + std::unique_ptr> pacer_; + std::unique_ptr controller_; + + rtc::Optional target_bitrate_bps_; +}; + +TEST_F(SendSideCongestionControllerTest, OnNetworkChanged) { + // Test no change. + clock_.AdvanceTimeMilliseconds(25); + controller_->Process(); + + EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps * 2, _, _, _)); + EXPECT_CALL(*pacer_, + SetPacingRates(kInitialBitrateBps * 2 * kDefaultPacingRate, _)); + bandwidth_observer_->OnReceivedEstimatedBitrate(kInitialBitrateBps * 2); + clock_.AdvanceTimeMilliseconds(25); + controller_->Process(); + + EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps, _, _, _)); + EXPECT_CALL(*pacer_, + SetPacingRates(kInitialBitrateBps * kDefaultPacingRate, _)); + bandwidth_observer_->OnReceivedEstimatedBitrate(kInitialBitrateBps); + clock_.AdvanceTimeMilliseconds(25); + controller_->Process(); +} + +TEST_F(SendSideCongestionControllerTest, OnSendQueueFull) { + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()) + .WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs + 1)); + + EXPECT_CALL(observer_, OnNetworkChanged(0, _, _, _)); + controller_->Process(); + + // Let the pacer not be full next time the controller checks. + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()) + .WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs - 1)); + + EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps, _, _, _)); + controller_->Process(); +} + +TEST_F(SendSideCongestionControllerTest, OnSendQueueFullAndEstimateChange) { + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()) + .WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs + 1)); + EXPECT_CALL(observer_, OnNetworkChanged(0, _, _, _)); + controller_->Process(); + + // Receive new estimate but let the queue still be full. + bandwidth_observer_->OnReceivedEstimatedBitrate(kInitialBitrateBps * 2); + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()) + .WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs + 1)); + // The send pacer should get the new estimate though. + EXPECT_CALL(*pacer_, + SetPacingRates(kInitialBitrateBps * 2 * kDefaultPacingRate, _)); + clock_.AdvanceTimeMilliseconds(25); + controller_->Process(); + + // Let the pacer not be full next time the controller checks. + // |OnNetworkChanged| should be called with the new estimate. + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()) + .WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs - 1)); + EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps * 2, _, _, _)); + clock_.AdvanceTimeMilliseconds(25); + controller_->Process(); +} + +TEST_F(SendSideCongestionControllerTest, SignalNetworkState) { + EXPECT_CALL(observer_, OnNetworkChanged(0, _, _, _)); + controller_->SignalNetworkState(kNetworkDown); + + EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps, _, _, _)); + controller_->SignalNetworkState(kNetworkUp); + + EXPECT_CALL(observer_, OnNetworkChanged(0, _, _, _)); + controller_->SignalNetworkState(kNetworkDown); +} + +TEST_F(SendSideCongestionControllerTest, OnNetworkRouteChanged) { + int new_bitrate = 200000; + testing::Mock::VerifyAndClearExpectations(pacer_.get()); + EXPECT_CALL(observer_, OnNetworkChanged(new_bitrate, _, _, _)); + EXPECT_CALL(*pacer_, SetPacingRates(new_bitrate * kDefaultPacingRate, _)); + rtc::NetworkRoute route; + route.local_network_id = 1; + controller_->OnNetworkRouteChanged(route, new_bitrate, -1, -1); + + // If the bitrate is reset to -1, the new starting bitrate will be + // the minimum default bitrate kMinBitrateBps. + EXPECT_CALL( + observer_, + OnNetworkChanged(congestion_controller::GetMinBitrateBps(), _, _, _)); + EXPECT_CALL( + *pacer_, + SetPacingRates( + congestion_controller::GetMinBitrateBps() * kDefaultPacingRate, _)); + route.local_network_id = 2; + controller_->OnNetworkRouteChanged(route, -1, -1, -1); +} + +TEST_F(SendSideCongestionControllerTest, OldFeedback) { + int new_bitrate = 200000; + testing::Mock::VerifyAndClearExpectations(pacer_.get()); + EXPECT_CALL(observer_, OnNetworkChanged(new_bitrate, _, _, _)); + EXPECT_CALL(*pacer_, SetPacingRates(new_bitrate * kDefaultPacingRate, _)); + + // Send a few packets on the first network route. + std::vector packets; + packets.push_back(PacketFeedback(0, 0, 0, 1500, kPacingInfo0)); + packets.push_back(PacketFeedback(10, 10, 1, 1500, kPacingInfo0)); + packets.push_back(PacketFeedback(20, 20, 2, 1500, kPacingInfo0)); + packets.push_back(PacketFeedback(30, 30, 3, 1500, kPacingInfo1)); + packets.push_back(PacketFeedback(40, 40, 4, 1500, kPacingInfo1)); + + for (const PacketFeedback& packet : packets) + OnSentPacket(packet); + + // Change route and then insert a number of feedback packets. + rtc::NetworkRoute route; + route.local_network_id = 1; + controller_->OnNetworkRouteChanged(route, new_bitrate, -1, -1); + + for (const PacketFeedback& packet : packets) { + rtcp::TransportFeedback feedback; + feedback.SetBase(packet.sequence_number, packet.arrival_time_ms * 1000); + + EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number, + packet.arrival_time_ms * 1000)); + feedback.Build(); + controller_->OnTransportFeedback(feedback); + } + + // If the bitrate is reset to -1, the new starting bitrate will be + // the minimum default bitrate kMinBitrateBps. + EXPECT_CALL( + observer_, + OnNetworkChanged(congestion_controller::GetMinBitrateBps(), _, _, _)); + EXPECT_CALL( + *pacer_, + SetPacingRates( + congestion_controller::GetMinBitrateBps() * kDefaultPacingRate, _)); + route.local_network_id = 2; + controller_->OnNetworkRouteChanged(route, -1, -1, -1); +} + +TEST_F(SendSideCongestionControllerTest, + SignalNetworkStateAndQueueIsFullAndEstimateChange) { + // Send queue is full. + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()) + .WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs + 1)); + EXPECT_CALL(observer_, OnNetworkChanged(0, _, _, _)); + controller_->Process(); + + // Queue is full and network is down. Expect no bitrate change. + controller_->SignalNetworkState(kNetworkDown); + controller_->Process(); + + // Queue is full but network is up. Expect no bitrate change. + controller_->SignalNetworkState(kNetworkUp); + controller_->Process(); + + // Receive new estimate but let the queue still be full. + EXPECT_CALL(*pacer_, + SetPacingRates(kInitialBitrateBps * 2 * kDefaultPacingRate, _)); + bandwidth_observer_->OnReceivedEstimatedBitrate(kInitialBitrateBps * 2); + clock_.AdvanceTimeMilliseconds(25); + controller_->Process(); + + // Let the pacer not be full next time the controller checks. + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()) + .WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs - 1)); + EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps * 2, _, _, _)); + controller_->Process(); +} + +TEST_F(SendSideCongestionControllerTest, GetPacerQueuingDelayMs) { + EXPECT_CALL(observer_, OnNetworkChanged(_, _, _, _)).Times(AtLeast(1)); + + const int64_t kQueueTimeMs = 123; + EXPECT_CALL(*pacer_, QueueInMs()).WillRepeatedly(Return(kQueueTimeMs)); + EXPECT_EQ(kQueueTimeMs, controller_->GetPacerQueuingDelayMs()); + + // Expect zero pacer delay when network is down. + controller_->SignalNetworkState(kNetworkDown); + EXPECT_EQ(0, controller_->GetPacerQueuingDelayMs()); + + // Network is up, pacer delay should be reported. + controller_->SignalNetworkState(kNetworkUp); + EXPECT_EQ(kQueueTimeMs, controller_->GetPacerQueuingDelayMs()); +} + +TEST_F(SendSideCongestionControllerTest, GetProbingInterval) { + clock_.AdvanceTimeMilliseconds(25); + controller_->Process(); + + EXPECT_CALL(observer_, OnNetworkChanged(_, _, _, testing::Ne(0))); + EXPECT_CALL(*pacer_, SetPacingRates(_, _)); + bandwidth_observer_->OnReceivedEstimatedBitrate(kInitialBitrateBps * 2); + clock_.AdvanceTimeMilliseconds(25); + controller_->Process(); +} + +TEST_F(SendSideCongestionControllerTest, ProbeOnRouteChange) { + testing::Mock::VerifyAndClearExpectations(pacer_.get()); + EXPECT_CALL(*pacer_, CreateProbeCluster(kInitialBitrateBps * 6)); + EXPECT_CALL(*pacer_, CreateProbeCluster(kInitialBitrateBps * 12)); + EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps * 2, _, _, _)); + rtc::NetworkRoute route; + route.local_network_id = 1; + controller_->OnNetworkRouteChanged(route, 2 * kInitialBitrateBps, 0, + 20 * kInitialBitrateBps); +} + +// Estimated bitrate reduced when the feedbacks arrive with such a long delay, +// that the send-time-history no longer holds the feedbacked packets. +TEST_F(SendSideCongestionControllerTest, LongFeedbackDelays) { + TargetBitrateTrackingSetup(); + + const int64_t kFeedbackTimeoutMs = 60001; + const int kMaxConsecutiveFailedLookups = 5; + for (int i = 0; i < kMaxConsecutiveFailedLookups; ++i) { + std::vector packets; + packets.push_back( + PacketFeedback(i * 100, 2 * i * 100, 0, 1500, kPacingInfo0)); + packets.push_back( + PacketFeedback(i * 100 + 10, 2 * i * 100 + 10, 1, 1500, kPacingInfo0)); + packets.push_back( + PacketFeedback(i * 100 + 20, 2 * i * 100 + 20, 2, 1500, kPacingInfo0)); + packets.push_back( + PacketFeedback(i * 100 + 30, 2 * i * 100 + 30, 3, 1500, kPacingInfo1)); + packets.push_back( + PacketFeedback(i * 100 + 40, 2 * i * 100 + 40, 4, 1500, kPacingInfo1)); + + for (const PacketFeedback& packet : packets) + OnSentPacket(packet); + + rtcp::TransportFeedback feedback; + feedback.SetBase(packets[0].sequence_number, + packets[0].arrival_time_ms * 1000); + + for (const PacketFeedback& packet : packets) { + EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number, + packet.arrival_time_ms * 1000)); + } + + feedback.Build(); + + clock_.AdvanceTimeMilliseconds(kFeedbackTimeoutMs); + PacketFeedback later_packet(kFeedbackTimeoutMs + i * 100 + 40, + kFeedbackTimeoutMs + i * 200 + 40, 5, 1500, + kPacingInfo1); + OnSentPacket(later_packet); + + controller_->OnTransportFeedback(feedback); + + // Check that packets have timed out. + for (PacketFeedback& packet : packets) { + packet.send_time_ms = PacketFeedback::kNoSendTime; + packet.payload_size = 0; + packet.pacing_info = PacedPacketInfo(); + } + ComparePacketFeedbackVectors(packets, + controller_->GetTransportFeedbackVector()); + } + + controller_->Process(); + + EXPECT_EQ(kInitialBitrateBps / 2, target_bitrate_bps_); + + // Test with feedback that isn't late enough to time out. + { + std::vector packets; + packets.push_back(PacketFeedback(100, 200, 0, 1500, kPacingInfo0)); + packets.push_back(PacketFeedback(110, 210, 1, 1500, kPacingInfo0)); + packets.push_back(PacketFeedback(120, 220, 2, 1500, kPacingInfo0)); + packets.push_back(PacketFeedback(130, 230, 3, 1500, kPacingInfo1)); + packets.push_back(PacketFeedback(140, 240, 4, 1500, kPacingInfo1)); + + for (const PacketFeedback& packet : packets) + OnSentPacket(packet); + + rtcp::TransportFeedback feedback; + feedback.SetBase(packets[0].sequence_number, + packets[0].arrival_time_ms * 1000); + + for (const PacketFeedback& packet : packets) { + EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number, + packet.arrival_time_ms * 1000)); + } + + feedback.Build(); + + clock_.AdvanceTimeMilliseconds(kFeedbackTimeoutMs - 1); + PacketFeedback later_packet(kFeedbackTimeoutMs + 140, + kFeedbackTimeoutMs + 240, 5, 1500, + kPacingInfo1); + OnSentPacket(later_packet); + + controller_->OnTransportFeedback(feedback); + ComparePacketFeedbackVectors(packets, + controller_->GetTransportFeedbackVector()); + } +} + +// Bandwidth estimation is updated when feedbacks are received. +// Feedbacks which show an increasing delay cause the estimation to be reduced. +TEST_F(SendSideCongestionControllerTest, UpdatesDelayBasedEstimate) { + TargetBitrateTrackingSetup(); + + const int64_t kRunTimeMs = 6000; + uint16_t seq_num = 0; + + // The test must run and insert packets/feedback long enough that the + // BWE computes a valid estimate. This is first done in an environment which + // simulates no bandwidth limitation, and therefore not built-up delay. + PacketTransmissionAndFeedbackBlock(&seq_num, kRunTimeMs, 0); + ASSERT_TRUE(target_bitrate_bps_); + + // Repeat, but this time with a building delay, and make sure that the + // estimation is adjusted downwards. + uint32_t bitrate_before_delay = *target_bitrate_bps_; + PacketTransmissionAndFeedbackBlock(&seq_num, kRunTimeMs, 50); + EXPECT_LT(*target_bitrate_bps_, bitrate_before_delay); +} + +TEST_F(SendSideCongestionControllerTest, PacerQueueEncodeRatePushback) { + ScopedFieldTrials pushback_field_trial( + "WebRTC-PacerPushbackExperiment/Enabled/"); + SetUp(); + + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()).WillOnce(Return(0)); + controller_->Process(); + + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()).WillOnce(Return(100)); + EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps * 0.9, _, _, _)); + controller_->Process(); + + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()).WillOnce(Return(50)); + controller_->Process(); + + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()).WillOnce(Return(0)); + EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps, _, _, _)); + controller_->Process(); + + const uint32_t kMinAdjustedBps = 50000; + int expected_queue_threshold = + 1000 - kMinAdjustedBps * 1000.0 / kInitialBitrateBps; + + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()) + .WillOnce(Return(expected_queue_threshold)); + EXPECT_CALL(observer_, OnNetworkChanged(Ge(kMinAdjustedBps), _, _, _)); + controller_->Process(); + + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()) + .WillOnce(Return(expected_queue_threshold + 1)); + EXPECT_CALL(observer_, OnNetworkChanged(0, _, _, _)); + controller_->Process(); + + EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()).WillOnce(Return(0)); + EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps, _, _, _)); + controller_->Process(); +} + +} // namespace test +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/send_time_history.cc b/modules/congestion_controller/rtp/send_time_history.cc new file mode 100644 index 0000000000..74b9c66a10 --- /dev/null +++ b/modules/congestion_controller/rtp/send_time_history.cc @@ -0,0 +1,103 @@ +/* + * 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. + */ + +#include "modules/congestion_controller/rtp/send_time_history.h" + +#include +#include + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "rtc_base/checks.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +SendTimeHistory::SendTimeHistory(const Clock* clock, + int64_t packet_age_limit_ms) + : clock_(clock), packet_age_limit_ms_(packet_age_limit_ms) {} + +SendTimeHistory::~SendTimeHistory() {} + +void SendTimeHistory::AddAndRemoveOld(const PacketFeedback& packet) { + int64_t now_ms = clock_->TimeInMilliseconds(); + // Remove old. + while (!history_.empty() && + now_ms - history_.begin()->second.creation_time_ms > + packet_age_limit_ms_) { + // TODO(sprang): Warn if erasing (too many) old items? + history_.erase(history_.begin()); + } + + // Add new. + int64_t unwrapped_seq_num = seq_num_unwrapper_.Unwrap(packet.sequence_number); + history_.insert(std::make_pair(unwrapped_seq_num, packet)); +} + +bool SendTimeHistory::OnSentPacket(uint16_t sequence_number, + int64_t send_time_ms) { + int64_t unwrapped_seq_num = seq_num_unwrapper_.Unwrap(sequence_number); + auto it = history_.find(unwrapped_seq_num); + if (it == history_.end()) + return false; + it->second.send_time_ms = send_time_ms; + return true; +} + +rtc::Optional SendTimeHistory::GetPacket( + uint16_t sequence_number) const { + int64_t unwrapped_seq_num = + seq_num_unwrapper_.UnwrapWithoutUpdate(sequence_number); + rtc::Optional optional_feedback; + auto it = history_.find(unwrapped_seq_num); + if (it != history_.end()) + optional_feedback.emplace(it->second); + return optional_feedback; +} + +bool SendTimeHistory::GetFeedback(PacketFeedback* packet_feedback, + bool remove) { + RTC_DCHECK(packet_feedback); + int64_t unwrapped_seq_num = + seq_num_unwrapper_.Unwrap(packet_feedback->sequence_number); + latest_acked_seq_num_.emplace( + std::max(unwrapped_seq_num, latest_acked_seq_num_.value_or(0))); + RTC_DCHECK_GE(*latest_acked_seq_num_, 0); + auto it = history_.find(unwrapped_seq_num); + if (it == history_.end()) + return false; + + // Save arrival_time not to overwrite it. + int64_t arrival_time_ms = packet_feedback->arrival_time_ms; + *packet_feedback = it->second; + packet_feedback->arrival_time_ms = arrival_time_ms; + + if (remove) + history_.erase(it); + return true; +} + +size_t SendTimeHistory::GetOutstandingBytes(uint16_t local_net_id, + uint16_t remote_net_id) const { + size_t outstanding_bytes = 0; + auto unacked_it = history_.begin(); + if (latest_acked_seq_num_) { + unacked_it = history_.lower_bound(*latest_acked_seq_num_); + } + for (; unacked_it != history_.end(); ++unacked_it) { + if (unacked_it->second.local_net_id == local_net_id && + unacked_it->second.remote_net_id == remote_net_id && + unacked_it->second.send_time_ms >= 0) { + outstanding_bytes += unacked_it->second.payload_size; + } + } + return outstanding_bytes; +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/send_time_history.h b/modules/congestion_controller/rtp/send_time_history.h new file mode 100644 index 0000000000..d1a2a65603 --- /dev/null +++ b/modules/congestion_controller/rtp/send_time_history.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef MODULES_CONGESTION_CONTROLLER_RTP_SEND_TIME_HISTORY_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_SEND_TIME_HISTORY_H_ + +#include + +#include "modules/include/module_common_types.h" +#include "rtc_base/basictypes.h" +#include "rtc_base/constructormagic.h" + +namespace webrtc { +class Clock; +struct PacketFeedback; + +class SendTimeHistory { + public: + SendTimeHistory(const Clock* clock, int64_t packet_age_limit_ms); + ~SendTimeHistory(); + + // Cleanup old entries, then add new packet info with provided parameters. + void AddAndRemoveOld(const PacketFeedback& packet); + + // Updates packet info identified by |sequence_number| with |send_time_ms|. + // Return false if not found. + bool OnSentPacket(uint16_t sequence_number, int64_t send_time_ms); + + // Retrieves packet info identified by |sequence_number|. + rtc::Optional GetPacket(uint16_t sequence_number) const; + + // Look up PacketFeedback for a sent packet, based on the sequence number, and + // populate all fields except for arrival_time. The packet parameter must + // thus be non-null and have the sequence_number field set. + bool GetFeedback(PacketFeedback* packet_feedback, bool remove); + + size_t GetOutstandingBytes(uint16_t local_net_id, + uint16_t remote_net_id) const; + + private: + const Clock* const clock_; + const int64_t packet_age_limit_ms_; + SequenceNumberUnwrapper seq_num_unwrapper_; + std::map history_; + rtc::Optional latest_acked_seq_num_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(SendTimeHistory); +}; + +} // namespace webrtc +#endif // MODULES_CONGESTION_CONTROLLER_RTP_SEND_TIME_HISTORY_H_ diff --git a/modules/congestion_controller/rtp/send_time_history_unittest.cc b/modules/congestion_controller/rtp/send_time_history_unittest.cc new file mode 100644 index 0000000000..3e852ba631 --- /dev/null +++ b/modules/congestion_controller/rtp/send_time_history_unittest.cc @@ -0,0 +1,256 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#include "modules/congestion_controller/rtp/send_time_history.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "system_wrappers/include/clock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace test { + +static const int kDefaultHistoryLengthMs = 1000; + +class SendTimeHistoryTest : public ::testing::Test { + protected: + SendTimeHistoryTest() + : clock_(0), history_(&clock_, kDefaultHistoryLengthMs) {} + ~SendTimeHistoryTest() {} + + virtual void SetUp() {} + + virtual void TearDown() {} + + void AddPacketWithSendTime(uint16_t sequence_number, + size_t length, + int64_t send_time_ms, + const PacedPacketInfo& pacing_info) { + PacketFeedback packet(clock_.TimeInMilliseconds(), sequence_number, length, + 0, 0, pacing_info); + history_.AddAndRemoveOld(packet); + history_.OnSentPacket(sequence_number, send_time_ms); + } + + webrtc::SimulatedClock clock_; + SendTimeHistory history_; +}; + +TEST_F(SendTimeHistoryTest, SaveAndRestoreNetworkId) { + const PacedPacketInfo kPacingInfo(0, 5, 1200); + uint16_t sequence_number = 0; + int64_t now_ms = clock_.TimeInMilliseconds(); + for (int i = 1; i < 5; ++i) { + PacketFeedback packet(now_ms, sequence_number, 1000, i, i - 1, kPacingInfo); + history_.AddAndRemoveOld(packet); + history_.OnSentPacket(sequence_number, now_ms); + PacketFeedback restored(now_ms, sequence_number); + EXPECT_TRUE(history_.GetFeedback(&restored, sequence_number++)); + EXPECT_EQ(packet.local_net_id, restored.local_net_id); + EXPECT_EQ(packet.remote_net_id, restored.remote_net_id); + } +} + +TEST_F(SendTimeHistoryTest, AddRemoveOne) { + const uint16_t kSeqNo = 10; + // TODO(philipel): Fix PacedPacketInfo constructor? + const PacedPacketInfo kPacingInfo(0, 5, 1200); + const PacketFeedback kSentPacket(0, 1, kSeqNo, 1, kPacingInfo); + AddPacketWithSendTime(kSeqNo, 1, 1, kPacingInfo); + + PacketFeedback received_packet(0, 0, kSeqNo, 0, kPacingInfo); + EXPECT_TRUE(history_.GetFeedback(&received_packet, false)); + EXPECT_EQ(kSentPacket, received_packet); + + PacketFeedback received_packet2(0, 0, kSeqNo, 0, kPacingInfo); + EXPECT_TRUE(history_.GetFeedback(&received_packet2, true)); + EXPECT_EQ(kSentPacket, received_packet2); + + PacketFeedback received_packet3(0, 0, kSeqNo, 0, kPacingInfo); + EXPECT_FALSE(history_.GetFeedback(&received_packet3, true)); +} + +TEST_F(SendTimeHistoryTest, GetPacketReturnsSentPacket) { + const uint16_t kSeqNo = 10; + const PacedPacketInfo kPacingInfo(0, 5, 1200); + const PacketFeedback kSentPacket(0, -1, 1, kSeqNo, 123, 0, 0, kPacingInfo); + AddPacketWithSendTime(kSeqNo, 123, 1, kPacingInfo); + auto sent_packet = history_.GetPacket(kSeqNo); + EXPECT_EQ(kSentPacket, *sent_packet); +} + +TEST_F(SendTimeHistoryTest, GetPacketEmptyForRemovedPacket) { + const uint16_t kSeqNo = 10; + const PacedPacketInfo kPacingInfo(0, 5, 1200); + AddPacketWithSendTime(kSeqNo, 123, 1, kPacingInfo); + auto sent_packet = history_.GetPacket(kSeqNo); + PacketFeedback received_packet(0, 0, kSeqNo, 0, kPacingInfo); + EXPECT_TRUE(history_.GetFeedback(&received_packet, true)); + sent_packet = history_.GetPacket(kSeqNo); + EXPECT_FALSE(sent_packet.has_value()); +} + +TEST_F(SendTimeHistoryTest, PopulatesExpectedFields) { + const uint16_t kSeqNo = 10; + const int64_t kSendTime = 1000; + const int64_t kReceiveTime = 2000; + const size_t kPayloadSize = 42; + const PacedPacketInfo kPacingInfo(3, 10, 1212); + + AddPacketWithSendTime(kSeqNo, kPayloadSize, kSendTime, kPacingInfo); + + PacketFeedback packet_feedback(kReceiveTime, kSeqNo); + EXPECT_TRUE(history_.GetFeedback(&packet_feedback, true)); + EXPECT_EQ(kReceiveTime, packet_feedback.arrival_time_ms); + EXPECT_EQ(kSendTime, packet_feedback.send_time_ms); + EXPECT_EQ(kSeqNo, packet_feedback.sequence_number); + EXPECT_EQ(kPayloadSize, packet_feedback.payload_size); + EXPECT_EQ(kPacingInfo, packet_feedback.pacing_info); +} + +TEST_F(SendTimeHistoryTest, AddThenRemoveOutOfOrder) { + std::vector sent_packets; + std::vector received_packets; + const size_t num_items = 100; + const size_t kPacketSize = 400; + const size_t kTransmissionTime = 1234; + const PacedPacketInfo kPacingInfo(1, 2, 200); + for (size_t i = 0; i < num_items; ++i) { + sent_packets.push_back(PacketFeedback(0, static_cast(i), + static_cast(i), kPacketSize, + kPacingInfo)); + received_packets.push_back(PacketFeedback( + static_cast(i) + kTransmissionTime, 0, + static_cast(i), kPacketSize, PacedPacketInfo())); + } + for (size_t i = 0; i < num_items; ++i) { + PacketFeedback packet = sent_packets[i]; + packet.arrival_time_ms = PacketFeedback::kNotReceived; + packet.send_time_ms = PacketFeedback::kNoSendTime; + history_.AddAndRemoveOld(packet); + } + for (size_t i = 0; i < num_items; ++i) + history_.OnSentPacket(sent_packets[i].sequence_number, + sent_packets[i].send_time_ms); + std::shuffle(received_packets.begin(), received_packets.end(), + std::mt19937(std::random_device()())); + for (size_t i = 0; i < num_items; ++i) { + PacketFeedback packet = received_packets[i]; + EXPECT_TRUE(history_.GetFeedback(&packet, false)); + PacketFeedback sent_packet = sent_packets[packet.sequence_number]; + sent_packet.arrival_time_ms = packet.arrival_time_ms; + EXPECT_EQ(sent_packet, packet); + EXPECT_TRUE(history_.GetFeedback(&packet, true)); + } + for (PacketFeedback packet : sent_packets) + EXPECT_FALSE(history_.GetFeedback(&packet, false)); +} + +TEST_F(SendTimeHistoryTest, HistorySize) { + const int kItems = kDefaultHistoryLengthMs / 100; + for (int i = 0; i < kItems; ++i) { + clock_.AdvanceTimeMilliseconds(100); + AddPacketWithSendTime(i, 0, i * 100, PacedPacketInfo()); + } + for (int i = 0; i < kItems; ++i) { + PacketFeedback packet(0, 0, static_cast(i), 0, PacedPacketInfo()); + EXPECT_TRUE(history_.GetFeedback(&packet, false)); + EXPECT_EQ(i * 100, packet.send_time_ms); + } + clock_.AdvanceTimeMilliseconds(101); + AddPacketWithSendTime(kItems, 0, kItems * 101, PacedPacketInfo()); + PacketFeedback packet(0, 0, 0, 0, PacedPacketInfo()); + EXPECT_FALSE(history_.GetFeedback(&packet, false)); + for (int i = 1; i < (kItems + 1); ++i) { + PacketFeedback packet2(0, 0, static_cast(i), 0, + PacedPacketInfo()); + EXPECT_TRUE(history_.GetFeedback(&packet2, false)); + int64_t expected_time_ms = (i == kItems) ? i * 101 : i * 100; + EXPECT_EQ(expected_time_ms, packet2.send_time_ms); + } +} + +TEST_F(SendTimeHistoryTest, HistorySizeWithWraparound) { + const uint16_t kMaxSeqNo = std::numeric_limits::max(); + AddPacketWithSendTime(kMaxSeqNo - 2, 0, 0, PacedPacketInfo()); + + clock_.AdvanceTimeMilliseconds(100); + AddPacketWithSendTime(kMaxSeqNo - 1, 1, 100, PacedPacketInfo()); + + clock_.AdvanceTimeMilliseconds(100); + AddPacketWithSendTime(kMaxSeqNo, 0, 200, PacedPacketInfo()); + + clock_.AdvanceTimeMilliseconds(kDefaultHistoryLengthMs - 200 + 1); + AddPacketWithSendTime(0, 0, kDefaultHistoryLengthMs, PacedPacketInfo()); + + PacketFeedback packet(0, static_cast(kMaxSeqNo - 2)); + EXPECT_FALSE(history_.GetFeedback(&packet, false)); + PacketFeedback packet2(0, static_cast(kMaxSeqNo - 1)); + EXPECT_TRUE(history_.GetFeedback(&packet2, false)); + PacketFeedback packet3(0, static_cast(kMaxSeqNo)); + EXPECT_TRUE(history_.GetFeedback(&packet3, false)); + PacketFeedback packet4(0, 0); + EXPECT_TRUE(history_.GetFeedback(&packet4, false)); + + // Create a gap (kMaxSeqNo - 1) -> 0. + PacketFeedback packet5(0, kMaxSeqNo); + EXPECT_TRUE(history_.GetFeedback(&packet5, true)); + + clock_.AdvanceTimeMilliseconds(100); + AddPacketWithSendTime(1, 0, 1100, PacedPacketInfo()); + + PacketFeedback packet6(0, static_cast(kMaxSeqNo - 2)); + EXPECT_FALSE(history_.GetFeedback(&packet6, false)); + PacketFeedback packet7(0, static_cast(kMaxSeqNo - 1)); + EXPECT_FALSE(history_.GetFeedback(&packet7, false)); + PacketFeedback packet8(0, kMaxSeqNo); + EXPECT_FALSE(history_.GetFeedback(&packet8, false)); + PacketFeedback packet9(0, 0); + EXPECT_TRUE(history_.GetFeedback(&packet9, false)); + PacketFeedback packet10(0, 1); + EXPECT_TRUE(history_.GetFeedback(&packet10, false)); +} + +TEST_F(SendTimeHistoryTest, InterlievedGetAndRemove) { + const uint16_t kSeqNo = 1; + const int64_t kTimestamp = 2; + const PacedPacketInfo kPacingInfo1(1, 1, 100); + const PacedPacketInfo kPacingInfo2(2, 2, 200); + const PacedPacketInfo kPacingInfo3(3, 3, 300); + PacketFeedback packets[3] = { + {0, kTimestamp, kSeqNo, 0, kPacingInfo1}, + {0, kTimestamp + 1, kSeqNo + 1, 0, kPacingInfo2}, + {0, kTimestamp + 2, kSeqNo + 2, 0, kPacingInfo3}}; + + AddPacketWithSendTime(packets[0].sequence_number, packets[0].payload_size, + packets[0].send_time_ms, packets[0].pacing_info); + AddPacketWithSendTime(packets[1].sequence_number, packets[1].payload_size, + packets[1].send_time_ms, packets[1].pacing_info); + PacketFeedback packet(0, 0, packets[0].sequence_number, 0, PacedPacketInfo()); + EXPECT_TRUE(history_.GetFeedback(&packet, true)); + EXPECT_EQ(packets[0], packet); + + AddPacketWithSendTime(packets[2].sequence_number, packets[2].payload_size, + packets[2].send_time_ms, packets[2].pacing_info); + + PacketFeedback packet2(0, 0, packets[1].sequence_number, 0, kPacingInfo1); + EXPECT_TRUE(history_.GetFeedback(&packet2, true)); + EXPECT_EQ(packets[1], packet2); + + PacketFeedback packet3(0, 0, packets[2].sequence_number, 0, kPacingInfo2); + EXPECT_TRUE(history_.GetFeedback(&packet3, true)); + EXPECT_EQ(packets[2], packet3); +} +} // namespace test +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/transport_feedback_adapter.cc b/modules/congestion_controller/rtp/transport_feedback_adapter.cc new file mode 100644 index 0000000000..5537c32e8a --- /dev/null +++ b/modules/congestion_controller/rtp/transport_feedback_adapter.cc @@ -0,0 +1,191 @@ +/* + * 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. + */ + +#include "modules/congestion_controller/rtp/transport_feedback_adapter.h" + +#include + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/mod_ops.h" + +namespace webrtc { + +const int64_t kNoTimestamp = -1; +const int64_t kSendTimeHistoryWindowMs = 60000; +const int64_t kBaseTimestampScaleFactor = + rtcp::TransportFeedback::kDeltaScaleFactor * (1 << 8); +const int64_t kBaseTimestampRangeSizeUs = kBaseTimestampScaleFactor * (1 << 24); + +TransportFeedbackAdapter::TransportFeedbackAdapter(const Clock* clock) + : send_time_history_(clock, kSendTimeHistoryWindowMs), + clock_(clock), + current_offset_ms_(kNoTimestamp), + last_timestamp_us_(kNoTimestamp), + local_net_id_(0), + remote_net_id_(0) {} + +TransportFeedbackAdapter::~TransportFeedbackAdapter() { + RTC_DCHECK(observers_.empty()); +} + +void TransportFeedbackAdapter::RegisterPacketFeedbackObserver( + PacketFeedbackObserver* observer) { + rtc::CritScope cs(&observers_lock_); + RTC_DCHECK(observer); + RTC_DCHECK(std::find(observers_.begin(), observers_.end(), observer) == + observers_.end()); + observers_.push_back(observer); +} + +void TransportFeedbackAdapter::DeRegisterPacketFeedbackObserver( + PacketFeedbackObserver* observer) { + rtc::CritScope cs(&observers_lock_); + RTC_DCHECK(observer); + const auto it = std::find(observers_.begin(), observers_.end(), observer); + RTC_DCHECK(it != observers_.end()); + observers_.erase(it); +} + +void TransportFeedbackAdapter::AddPacket(uint32_t ssrc, + uint16_t sequence_number, + size_t length, + const PacedPacketInfo& pacing_info) { + { + rtc::CritScope cs(&lock_); + const int64_t creation_time_ms = clock_->TimeInMilliseconds(); + send_time_history_.AddAndRemoveOld( + PacketFeedback(creation_time_ms, sequence_number, length, local_net_id_, + remote_net_id_, pacing_info)); + } + + { + rtc::CritScope cs(&observers_lock_); + for (auto* observer : observers_) { + observer->OnPacketAdded(ssrc, sequence_number); + } + } +} + +void TransportFeedbackAdapter::OnSentPacket(uint16_t sequence_number, + int64_t send_time_ms) { + rtc::CritScope cs(&lock_); + send_time_history_.OnSentPacket(sequence_number, send_time_ms); +} + +rtc::Optional TransportFeedbackAdapter::GetPacket( + uint16_t sequence_number) const { + rtc::CritScope cs(&lock_); + return send_time_history_.GetPacket(sequence_number); +} + +void TransportFeedbackAdapter::SetNetworkIds(uint16_t local_id, + uint16_t remote_id) { + rtc::CritScope cs(&lock_); + local_net_id_ = local_id; + remote_net_id_ = remote_id; +} + +std::vector TransportFeedbackAdapter::GetPacketFeedbackVector( + const rtcp::TransportFeedback& feedback) { + int64_t timestamp_us = feedback.GetBaseTimeUs(); + int64_t now_ms = clock_->TimeInMilliseconds(); + // Add timestamp deltas to a local time base selected on first packet arrival. + // This won't be the true time base, but makes it easier to manually inspect + // time stamps. + if (last_timestamp_us_ == kNoTimestamp) { + current_offset_ms_ = now_ms; + } else { + int64_t delta = timestamp_us - last_timestamp_us_; + + // Detect and compensate for wrap-arounds in base time. + if (std::abs(delta - kBaseTimestampRangeSizeUs) < std::abs(delta)) { + delta -= kBaseTimestampRangeSizeUs; // Wrap backwards. + } else if (std::abs(delta + kBaseTimestampRangeSizeUs) < std::abs(delta)) { + delta += kBaseTimestampRangeSizeUs; // Wrap forwards. + } + + current_offset_ms_ += delta / 1000; + } + last_timestamp_us_ = timestamp_us; + + std::vector packet_feedback_vector; + if (feedback.GetPacketStatusCount() == 0) { + RTC_LOG(LS_INFO) << "Empty transport feedback packet received."; + return packet_feedback_vector; + } + packet_feedback_vector.reserve(feedback.GetPacketStatusCount()); + { + rtc::CritScope cs(&lock_); + size_t failed_lookups = 0; + int64_t offset_us = 0; + int64_t timestamp_ms = 0; + uint16_t seq_num = feedback.GetBaseSequence(); + for (const auto& packet : feedback.GetReceivedPackets()) { + // Insert into the vector those unreceived packets which precede this + // iteration's received packet. + for (; seq_num != packet.sequence_number(); ++seq_num) { + PacketFeedback packet_feedback(PacketFeedback::kNotReceived, seq_num); + // Note: Element not removed from history because it might be reported + // as received by another feedback. + if (!send_time_history_.GetFeedback(&packet_feedback, false)) + ++failed_lookups; + if (packet_feedback.local_net_id == local_net_id_ && + packet_feedback.remote_net_id == remote_net_id_) { + packet_feedback_vector.push_back(packet_feedback); + } + } + + // Handle this iteration's received packet. + offset_us += packet.delta_us(); + timestamp_ms = current_offset_ms_ + (offset_us / 1000); + PacketFeedback packet_feedback(timestamp_ms, packet.sequence_number()); + if (!send_time_history_.GetFeedback(&packet_feedback, true)) + ++failed_lookups; + if (packet_feedback.local_net_id == local_net_id_ && + packet_feedback.remote_net_id == remote_net_id_) { + packet_feedback_vector.push_back(packet_feedback); + } + + ++seq_num; + } + + if (failed_lookups > 0) { + RTC_LOG(LS_WARNING) << "Failed to lookup send time for " << failed_lookups + << " packet" << (failed_lookups > 1 ? "s" : "") + << ". Send time history too small?"; + } + } + return packet_feedback_vector; +} + +void TransportFeedbackAdapter::OnTransportFeedback( + const rtcp::TransportFeedback& feedback) { + last_packet_feedback_vector_ = GetPacketFeedbackVector(feedback); + { + rtc::CritScope cs(&observers_lock_); + for (auto* observer : observers_) { + observer->OnPacketFeedbackVector(last_packet_feedback_vector_); + } + } +} + +std::vector +TransportFeedbackAdapter::GetTransportFeedbackVector() const { + return last_packet_feedback_vector_; +} + +size_t TransportFeedbackAdapter::GetOutstandingBytes() const { + rtc::CritScope cs(&lock_); + return send_time_history_.GetOutstandingBytes(local_net_id_, remote_net_id_); +} +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/transport_feedback_adapter.h b/modules/congestion_controller/rtp/transport_feedback_adapter.h new file mode 100644 index 0000000000..abb9abe4d2 --- /dev/null +++ b/modules/congestion_controller/rtp/transport_feedback_adapter.h @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#ifndef MODULES_CONGESTION_CONTROLLER_RTP_TRANSPORT_FEEDBACK_ADAPTER_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_TRANSPORT_FEEDBACK_ADAPTER_H_ + +#include +#include + +#include "modules/congestion_controller/rtp/send_time_history.h" +#include "rtc_base/criticalsection.h" +#include "rtc_base/thread_annotations.h" +#include "rtc_base/thread_checker.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +class PacketFeedbackObserver; + +namespace rtcp { +class TransportFeedback; +} // namespace rtcp + +class TransportFeedbackAdapter { + public: + explicit TransportFeedbackAdapter(const Clock* clock); + virtual ~TransportFeedbackAdapter(); + + void RegisterPacketFeedbackObserver(PacketFeedbackObserver* observer); + void DeRegisterPacketFeedbackObserver(PacketFeedbackObserver* observer); + + void AddPacket(uint32_t ssrc, + uint16_t sequence_number, + size_t length, + const PacedPacketInfo& pacing_info); + void OnSentPacket(uint16_t sequence_number, int64_t send_time_ms); + + // TODO(holmer): This method should return DelayBasedBwe::Result so that we + // can get rid of the dependency on BitrateController. Requires changes + // to the CongestionController interface. + void OnTransportFeedback(const rtcp::TransportFeedback& feedback); + std::vector GetTransportFeedbackVector() const; + rtc::Optional GetPacket(uint16_t sequence_number) const; + + void SetTransportOverhead(int transport_overhead_bytes_per_packet); + + void SetNetworkIds(uint16_t local_id, uint16_t remote_id); + + size_t GetOutstandingBytes() const; + + private: + std::vector GetPacketFeedbackVector( + const rtcp::TransportFeedback& feedback); + + rtc::CriticalSection lock_; + SendTimeHistory send_time_history_ RTC_GUARDED_BY(&lock_); + const Clock* const clock_; + int64_t current_offset_ms_; + int64_t last_timestamp_us_; + std::vector last_packet_feedback_vector_; + uint16_t local_net_id_ RTC_GUARDED_BY(&lock_); + uint16_t remote_net_id_ RTC_GUARDED_BY(&lock_); + + rtc::CriticalSection observers_lock_; + std::vector observers_ + RTC_GUARDED_BY(&observers_lock_); +}; + +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_TRANSPORT_FEEDBACK_ADAPTER_H_ diff --git a/modules/congestion_controller/rtp/transport_feedback_adapter_unittest.cc b/modules/congestion_controller/rtp/transport_feedback_adapter_unittest.cc new file mode 100644 index 0000000000..f115c8c545 --- /dev/null +++ b/modules/congestion_controller/rtp/transport_feedback_adapter_unittest.cc @@ -0,0 +1,391 @@ +/* + * 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. + */ + +#include +#include +#include + +#include "modules/bitrate_controller/include/mock/mock_bitrate_controller.h" +#include "modules/congestion_controller/rtp/congestion_controller_unittests_helper.h" +#include "modules/congestion_controller/rtp/transport_feedback_adapter.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h" +#include "rtc_base/checks.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "system_wrappers/include/clock.h" +#include "test/gmock.h" +#include "test/gtest.h" + +using ::testing::_; +using ::testing::Invoke; + +namespace webrtc { + +namespace { +const PacedPacketInfo kPacingInfo0(0, 5, 2000); +const PacedPacketInfo kPacingInfo1(1, 8, 4000); +const PacedPacketInfo kPacingInfo2(2, 14, 7000); +const PacedPacketInfo kPacingInfo3(3, 20, 10000); +const PacedPacketInfo kPacingInfo4(4, 22, 10000); +} + +namespace test { + +class MockPacketFeedbackObserver : public webrtc::PacketFeedbackObserver { + public: + MOCK_METHOD2(OnPacketAdded, void(uint32_t ssrc, uint16_t seq_num)); + MOCK_METHOD1(OnPacketFeedbackVector, + void(const std::vector& packet_feedback_vector)); +}; + +class TransportFeedbackAdapterTest : public ::testing::Test { + public: + TransportFeedbackAdapterTest() : clock_(0) {} + + virtual ~TransportFeedbackAdapterTest() {} + + virtual void SetUp() { + adapter_.reset(new TransportFeedbackAdapter(&clock_)); + } + + virtual void TearDown() { adapter_.reset(); } + + protected: + void OnReceivedEstimatedBitrate(uint32_t bitrate) {} + + void OnReceivedRtcpReceiverReport(const ReportBlockList& report_blocks, + int64_t rtt, + int64_t now_ms) {} + + void OnSentPacket(const PacketFeedback& packet_feedback) { + adapter_->AddPacket(kSsrc, packet_feedback.sequence_number, + packet_feedback.payload_size, + packet_feedback.pacing_info); + adapter_->OnSentPacket(packet_feedback.sequence_number, + packet_feedback.send_time_ms); + } + + static constexpr uint32_t kSsrc = 8492; + + SimulatedClock clock_; + std::unique_ptr adapter_; +}; + +TEST_F(TransportFeedbackAdapterTest, ObserverSanity) { + MockPacketFeedbackObserver mock; + adapter_->RegisterPacketFeedbackObserver(&mock); + + const std::vector packets = { + PacketFeedback(100, 200, 0, 1000, kPacingInfo0), + PacketFeedback(110, 210, 1, 2000, kPacingInfo0), + PacketFeedback(120, 220, 2, 3000, kPacingInfo0) + }; + + rtcp::TransportFeedback feedback; + feedback.SetBase(packets[0].sequence_number, + packets[0].arrival_time_ms * 1000); + + for (const PacketFeedback& packet : packets) { + EXPECT_CALL(mock, OnPacketAdded(kSsrc, packet.sequence_number)).Times(1); + OnSentPacket(packet); + EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number, + packet.arrival_time_ms * 1000)); + } + + EXPECT_CALL(mock, OnPacketFeedbackVector(_)).Times(1); + adapter_->OnTransportFeedback(feedback); + + adapter_->DeRegisterPacketFeedbackObserver(&mock); + + // After deregistration, the observer no longers gets indications. + EXPECT_CALL(mock, OnPacketAdded(_, _)).Times(0); + const PacketFeedback new_packet(130, 230, 3, 4000, kPacingInfo0); + OnSentPacket(new_packet); + + rtcp::TransportFeedback second_feedback; + second_feedback.SetBase(new_packet.sequence_number, + new_packet.arrival_time_ms * 1000); + EXPECT_TRUE(feedback.AddReceivedPacket(new_packet.sequence_number, + new_packet.arrival_time_ms * 1000)); + EXPECT_CALL(mock, OnPacketFeedbackVector(_)).Times(0); + adapter_->OnTransportFeedback(second_feedback); +} + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +TEST_F(TransportFeedbackAdapterTest, ObserverDoubleRegistrationDeathTest) { + MockPacketFeedbackObserver mock; + adapter_->RegisterPacketFeedbackObserver(&mock); + EXPECT_DEATH(adapter_->RegisterPacketFeedbackObserver(&mock), ""); + adapter_->DeRegisterPacketFeedbackObserver(&mock); +} + +TEST_F(TransportFeedbackAdapterTest, ObserverMissingDeRegistrationDeathTest) { + MockPacketFeedbackObserver mock; + adapter_->RegisterPacketFeedbackObserver(&mock); + EXPECT_DEATH(adapter_.reset(), ""); + adapter_->DeRegisterPacketFeedbackObserver(&mock); +} +#endif + +TEST_F(TransportFeedbackAdapterTest, AdaptsFeedbackAndPopulatesSendTimes) { + std::vector packets; + packets.push_back(PacketFeedback(100, 200, 0, 1500, kPacingInfo0)); + packets.push_back(PacketFeedback(110, 210, 1, 1500, kPacingInfo0)); + packets.push_back(PacketFeedback(120, 220, 2, 1500, kPacingInfo0)); + packets.push_back(PacketFeedback(130, 230, 3, 1500, kPacingInfo1)); + packets.push_back(PacketFeedback(140, 240, 4, 1500, kPacingInfo1)); + + for (const PacketFeedback& packet : packets) + OnSentPacket(packet); + + rtcp::TransportFeedback feedback; + feedback.SetBase(packets[0].sequence_number, + packets[0].arrival_time_ms * 1000); + + for (const PacketFeedback& packet : packets) { + EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number, + packet.arrival_time_ms * 1000)); + } + + feedback.Build(); + + adapter_->OnTransportFeedback(feedback); + ComparePacketFeedbackVectors(packets, adapter_->GetTransportFeedbackVector()); +} + +TEST_F(TransportFeedbackAdapterTest, FeedbackVectorReportsUnreceived) { + std::vector sent_packets = { + PacketFeedback(100, 220, 0, 1500, kPacingInfo0), + PacketFeedback(110, 210, 1, 1500, kPacingInfo0), + PacketFeedback(120, 220, 2, 1500, kPacingInfo0), + PacketFeedback(130, 230, 3, 1500, kPacingInfo0), + PacketFeedback(140, 240, 4, 1500, kPacingInfo0), + PacketFeedback(150, 250, 5, 1500, kPacingInfo0), + PacketFeedback(160, 260, 6, 1500, kPacingInfo0) + }; + + for (const PacketFeedback& packet : sent_packets) + OnSentPacket(packet); + + // Note: Important to include the last packet, as only unreceived packets in + // between received packets can be inferred. + std::vector received_packets = { + sent_packets[0], sent_packets[2], sent_packets[6] + }; + + rtcp::TransportFeedback feedback; + feedback.SetBase(received_packets[0].sequence_number, + received_packets[0].arrival_time_ms * 1000); + + for (const PacketFeedback& packet : received_packets) { + EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number, + packet.arrival_time_ms * 1000)); + } + + feedback.Build(); + + adapter_->OnTransportFeedback(feedback); + ComparePacketFeedbackVectors(sent_packets, + adapter_->GetTransportFeedbackVector()); +} + +TEST_F(TransportFeedbackAdapterTest, HandlesDroppedPackets) { + std::vector packets; + packets.push_back(PacketFeedback(100, 200, 0, 1500, kPacingInfo0)); + packets.push_back(PacketFeedback(110, 210, 1, 1500, kPacingInfo1)); + packets.push_back(PacketFeedback(120, 220, 2, 1500, kPacingInfo2)); + packets.push_back(PacketFeedback(130, 230, 3, 1500, kPacingInfo3)); + packets.push_back(PacketFeedback(140, 240, 4, 1500, kPacingInfo4)); + + const uint16_t kSendSideDropBefore = 1; + const uint16_t kReceiveSideDropAfter = 3; + + for (const PacketFeedback& packet : packets) { + if (packet.sequence_number >= kSendSideDropBefore) + OnSentPacket(packet); + } + + rtcp::TransportFeedback feedback; + feedback.SetBase(packets[0].sequence_number, + packets[0].arrival_time_ms * 1000); + + for (const PacketFeedback& packet : packets) { + if (packet.sequence_number <= kReceiveSideDropAfter) { + EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number, + packet.arrival_time_ms * 1000)); + } + } + + feedback.Build(); + + std::vector expected_packets( + packets.begin(), packets.begin() + kReceiveSideDropAfter + 1); + // Packets that have timed out on the send-side have lost the + // information stored on the send-side. + for (size_t i = 0; i < kSendSideDropBefore; ++i) { + expected_packets[i].send_time_ms = -1; + expected_packets[i].payload_size = 0; + expected_packets[i].pacing_info = PacedPacketInfo(); + } + + adapter_->OnTransportFeedback(feedback); + ComparePacketFeedbackVectors(expected_packets, + adapter_->GetTransportFeedbackVector()); +} + +TEST_F(TransportFeedbackAdapterTest, SendTimeWrapsBothWays) { + int64_t kHighArrivalTimeMs = rtcp::TransportFeedback::kDeltaScaleFactor * + static_cast(1 << 8) * + static_cast((1 << 23) - 1) / 1000; + std::vector packets; + packets.push_back( + PacketFeedback(kHighArrivalTimeMs - 64, 200, 0, 1500, PacedPacketInfo())); + packets.push_back( + PacketFeedback(kHighArrivalTimeMs + 64, 210, 1, 1500, PacedPacketInfo())); + packets.push_back( + PacketFeedback(kHighArrivalTimeMs, 220, 2, 1500, PacedPacketInfo())); + + for (const PacketFeedback& packet : packets) + OnSentPacket(packet); + + for (size_t i = 0; i < packets.size(); ++i) { + std::unique_ptr feedback( + new rtcp::TransportFeedback()); + feedback->SetBase(packets[i].sequence_number, + packets[i].arrival_time_ms * 1000); + + EXPECT_TRUE(feedback->AddReceivedPacket(packets[i].sequence_number, + packets[i].arrival_time_ms * 1000)); + + rtc::Buffer raw_packet = feedback->Build(); + feedback = rtcp::TransportFeedback::ParseFrom(raw_packet.data(), + raw_packet.size()); + + std::vector expected_packets; + expected_packets.push_back(packets[i]); + + adapter_->OnTransportFeedback(*feedback.get()); + ComparePacketFeedbackVectors(expected_packets, + adapter_->GetTransportFeedbackVector()); + } +} + +TEST_F(TransportFeedbackAdapterTest, HandlesArrivalReordering) { + std::vector packets; + packets.push_back(PacketFeedback(120, 200, 0, 1500, kPacingInfo0)); + packets.push_back(PacketFeedback(110, 210, 1, 1500, kPacingInfo0)); + packets.push_back(PacketFeedback(100, 220, 2, 1500, kPacingInfo0)); + + for (const PacketFeedback& packet : packets) + OnSentPacket(packet); + + rtcp::TransportFeedback feedback; + feedback.SetBase(packets[0].sequence_number, + packets[0].arrival_time_ms * 1000); + + for (const PacketFeedback& packet : packets) { + EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number, + packet.arrival_time_ms * 1000)); + } + + feedback.Build(); + + // Adapter keeps the packets ordered by sequence number (which is itself + // assigned by the order of transmission). Reordering by some other criteria, + // eg. arrival time, is up to the observers. + adapter_->OnTransportFeedback(feedback); + ComparePacketFeedbackVectors(packets, adapter_->GetTransportFeedbackVector()); +} + +TEST_F(TransportFeedbackAdapterTest, TimestampDeltas) { + std::vector sent_packets; + const int64_t kSmallDeltaUs = + rtcp::TransportFeedback::kDeltaScaleFactor * ((1 << 8) - 1); + const int64_t kLargePositiveDeltaUs = + rtcp::TransportFeedback::kDeltaScaleFactor * + std::numeric_limits::max(); + const int64_t kLargeNegativeDeltaUs = + rtcp::TransportFeedback::kDeltaScaleFactor * + std::numeric_limits::min(); + + PacketFeedback packet_feedback(100, 200, 0, 1500, true, 0, 0, + PacedPacketInfo()); + sent_packets.push_back(packet_feedback); + + packet_feedback.send_time_ms += kSmallDeltaUs / 1000; + packet_feedback.arrival_time_ms += kSmallDeltaUs / 1000; + ++packet_feedback.sequence_number; + sent_packets.push_back(packet_feedback); + + packet_feedback.send_time_ms += kLargePositiveDeltaUs / 1000; + packet_feedback.arrival_time_ms += kLargePositiveDeltaUs / 1000; + ++packet_feedback.sequence_number; + sent_packets.push_back(packet_feedback); + + packet_feedback.send_time_ms += kLargeNegativeDeltaUs / 1000; + packet_feedback.arrival_time_ms += kLargeNegativeDeltaUs / 1000; + ++packet_feedback.sequence_number; + sent_packets.push_back(packet_feedback); + + // Too large, delta - will need two feedback messages. + packet_feedback.send_time_ms += (kLargePositiveDeltaUs + 1000) / 1000; + packet_feedback.arrival_time_ms += (kLargePositiveDeltaUs + 1000) / 1000; + ++packet_feedback.sequence_number; + + // Packets will be added to send history. + for (const PacketFeedback& packet : sent_packets) + OnSentPacket(packet); + OnSentPacket(packet_feedback); + + // Create expected feedback and send into adapter. + std::unique_ptr feedback( + new rtcp::TransportFeedback()); + feedback->SetBase(sent_packets[0].sequence_number, + sent_packets[0].arrival_time_ms * 1000); + + for (const PacketFeedback& packet : sent_packets) { + EXPECT_TRUE(feedback->AddReceivedPacket(packet.sequence_number, + packet.arrival_time_ms * 1000)); + } + EXPECT_FALSE(feedback->AddReceivedPacket( + packet_feedback.sequence_number, packet_feedback.arrival_time_ms * 1000)); + + rtc::Buffer raw_packet = feedback->Build(); + feedback = + rtcp::TransportFeedback::ParseFrom(raw_packet.data(), raw_packet.size()); + + std::vector received_feedback; + + EXPECT_TRUE(feedback.get() != nullptr); + adapter_->OnTransportFeedback(*feedback.get()); + ComparePacketFeedbackVectors(sent_packets, + adapter_->GetTransportFeedbackVector()); + + // Create a new feedback message and add the trailing item. + feedback.reset(new rtcp::TransportFeedback()); + feedback->SetBase(packet_feedback.sequence_number, + packet_feedback.arrival_time_ms * 1000); + EXPECT_TRUE(feedback->AddReceivedPacket( + packet_feedback.sequence_number, packet_feedback.arrival_time_ms * 1000)); + raw_packet = feedback->Build(); + feedback = + rtcp::TransportFeedback::ParseFrom(raw_packet.data(), raw_packet.size()); + + EXPECT_TRUE(feedback.get() != nullptr); + adapter_->OnTransportFeedback(*feedback.get()); + { + std::vector expected_packets; + expected_packets.push_back(packet_feedback); + ComparePacketFeedbackVectors(expected_packets, + adapter_->GetTransportFeedbackVector()); + } +} +} // namespace test +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/trendline_estimator.cc b/modules/congestion_controller/rtp/trendline_estimator.cc new file mode 100644 index 0000000000..1d27b2aa42 --- /dev/null +++ b/modules/congestion_controller/rtp/trendline_estimator.cc @@ -0,0 +1,183 @@ +/* + * 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 "modules/congestion_controller/rtp/trendline_estimator.h" + +#include + +#include + +#include "api/optional.h" +#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h" +#include "rtc_base/checks.h" +#include "rtc_base/numerics/safe_minmax.h" + +namespace webrtc { + +namespace { +rtc::Optional LinearFitSlope( + const std::deque>& points) { + RTC_DCHECK(points.size() >= 2); + // Compute the "center of mass". + double sum_x = 0; + double sum_y = 0; + for (const auto& point : points) { + sum_x += point.first; + sum_y += point.second; + } + double x_avg = sum_x / points.size(); + double y_avg = sum_y / points.size(); + // Compute the slope k = \sum (x_i-x_avg)(y_i-y_avg) / \sum (x_i-x_avg)^2 + double numerator = 0; + double denominator = 0; + for (const auto& point : points) { + numerator += (point.first - x_avg) * (point.second - y_avg); + denominator += (point.first - x_avg) * (point.first - x_avg); + } + if (denominator == 0) + return rtc::nullopt; + return numerator / denominator; +} + +constexpr double kMaxAdaptOffsetMs = 15.0; +constexpr double kOverUsingTimeThreshold = 10; +constexpr int kMinNumDeltas = 60; + +} // namespace + +enum { kDeltaCounterMax = 1000 }; + +TrendlineEstimator::TrendlineEstimator(size_t window_size, + double smoothing_coef, + double threshold_gain) + : window_size_(window_size), + smoothing_coef_(smoothing_coef), + threshold_gain_(threshold_gain), + num_of_deltas_(0), + first_arrival_time_ms_(-1), + accumulated_delay_(0), + smoothed_delay_(0), + delay_hist_(), + trendline_(0), + k_up_(0.0087), + k_down_(0.039), + overusing_time_threshold_(kOverUsingTimeThreshold), + threshold_(12.5), + last_update_ms_(-1), + prev_offset_(0.0), + time_over_using_(-1), + overuse_counter_(0), + hypothesis_(BandwidthUsage::kBwNormal) {} + +TrendlineEstimator::~TrendlineEstimator() {} + +void TrendlineEstimator::Update(double recv_delta_ms, + double send_delta_ms, + int64_t arrival_time_ms) { + const double delta_ms = recv_delta_ms - send_delta_ms; + ++num_of_deltas_; + if (num_of_deltas_ > kDeltaCounterMax) + 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(); + if (delay_hist_.size() == window_size_) { + // Only update trendline_ if it is possible to fit a line to the data. + trendline_ = LinearFitSlope(delay_hist_).value_or(trendline_); + } + + BWE_TEST_LOGGING_PLOT(1, "trendline_slope", arrival_time_ms, trendline_); + + Detect(trendline_slope(), send_delta_ms, num_of_deltas(), arrival_time_ms); +} + +BandwidthUsage TrendlineEstimator::State() const { + return hypothesis_; +} + +void TrendlineEstimator::Detect(double offset, + double ts_delta, + int num_of_deltas, + int64_t now_ms) { + if (num_of_deltas < 2) { + hypothesis_ = BandwidthUsage::kBwNormal; + return; + } + const double T = std::min(num_of_deltas, kMinNumDeltas) * offset; + BWE_TEST_LOGGING_PLOT(1, "T", now_ms, T); + BWE_TEST_LOGGING_PLOT(1, "threshold", now_ms, threshold_); + if (T > threshold_) { + if (time_over_using_ == -1) { + // Initialize the timer. Assume that we've been + // over-using half of the time since the previous + // sample. + time_over_using_ = ts_delta / 2; + } else { + // Increment timer + time_over_using_ += ts_delta; + } + overuse_counter_++; + if (time_over_using_ > overusing_time_threshold_ && overuse_counter_ > 1) { + if (offset >= prev_offset_) { + time_over_using_ = 0; + overuse_counter_ = 0; + hypothesis_ = BandwidthUsage::kBwOverusing; + } + } + } else if (T < -threshold_) { + time_over_using_ = -1; + overuse_counter_ = 0; + hypothesis_ = BandwidthUsage::kBwUnderusing; + } else { + time_over_using_ = -1; + overuse_counter_ = 0; + hypothesis_ = BandwidthUsage::kBwNormal; + } + prev_offset_ = offset; + + UpdateThreshold(T, now_ms); +} + +void TrendlineEstimator::UpdateThreshold(double modified_offset, + int64_t now_ms) { + if (last_update_ms_ == -1) + last_update_ms_ = now_ms; + + if (fabs(modified_offset) > threshold_ + kMaxAdaptOffsetMs) { + // Avoid adapting the threshold to big latency spikes, caused e.g., + // by a sudden capacity drop. + last_update_ms_ = now_ms; + return; + } + + const double k = fabs(modified_offset) < threshold_ ? k_down_ : k_up_; + const int64_t kMaxTimeDeltaMs = 100; + int64_t time_delta_ms = std::min(now_ms - last_update_ms_, kMaxTimeDeltaMs); + threshold_ += k * (fabs(modified_offset) - threshold_) * time_delta_ms; + threshold_ = rtc::SafeClamp(threshold_, 6.f, 600.f); + last_update_ms_ = now_ms; +} + +} // namespace webrtc diff --git a/modules/congestion_controller/rtp/trendline_estimator.h b/modules/congestion_controller/rtp/trendline_estimator.h new file mode 100644 index 0000000000..17befadb6c --- /dev/null +++ b/modules/congestion_controller/rtp/trendline_estimator.h @@ -0,0 +1,92 @@ +/* + * 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 MODULES_CONGESTION_CONTROLLER_RTP_TRENDLINE_ESTIMATOR_H_ +#define MODULES_CONGESTION_CONTROLLER_RTP_TRENDLINE_ESTIMATOR_H_ + +#include +#include + +#include +#include + +#include "modules/congestion_controller/rtp/delay_increase_detector_interface.h" +#include "rtc_base/constructormagic.h" + +namespace webrtc { + +class TrendlineEstimator : public DelayIncreaseDetectorInterface { + public: + // |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. + TrendlineEstimator(size_t window_size, + double smoothing_coef, + double threshold_gain); + + ~TrendlineEstimator() override; + + // 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, + int64_t arrival_time_ms) override; + + BandwidthUsage State() const override; + + // 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: + void Detect(double offset, + double ts_delta, + int num_of_deltas, + int64_t now_ms); + + void UpdateThreshold(double modified_offset, int64_t now_ms); + + // Parameters. + const size_t window_size_; + const double smoothing_coef_; + const double threshold_gain_; + // Used by the existing threshold. + unsigned int num_of_deltas_; + // Keep the arrival times small by using the change from the first packet. + int64_t first_arrival_time_ms_; + // Exponential backoff filtering. + double accumulated_delay_; + double smoothed_delay_; + // Linear least squares regression. + std::deque> delay_hist_; + double trendline_; + + const double k_up_; + const double k_down_; + double overusing_time_threshold_; + double threshold_; + int64_t last_update_ms_; + double prev_offset_; + double time_over_using_; + int overuse_counter_; + BandwidthUsage hypothesis_; + + RTC_DISALLOW_COPY_AND_ASSIGN(TrendlineEstimator); +}; +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_RTP_TRENDLINE_ESTIMATOR_H_ diff --git a/modules/congestion_controller/rtp/trendline_estimator_unittest.cc b/modules/congestion_controller/rtp/trendline_estimator_unittest.cc new file mode 100644 index 0000000000..6a765caf52 --- /dev/null +++ b/modules/congestion_controller/rtp/trendline_estimator_unittest.cc @@ -0,0 +1,73 @@ +/* + * 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 "modules/congestion_controller/rtp/trendline_estimator.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; + +void TestEstimator(double slope, double jitter_stddev, double tolerance) { + TrendlineEstimator estimator(kWindowSize, kSmoothing, kGain); + 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; + } + 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, recv_times[i]); + if (i < kWindowSize) + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001); + else + EXPECT_NEAR(estimator.trendline_slope(), slope, tolerance); + } +} +} // namespace + +TEST(TrendlineEstimator, PerfectLineSlopeOneHalf) { + TestEstimator(0.5, 0, 0.001); +} + +TEST(TrendlineEstimator, PerfectLineSlopeMinusOne) { + TestEstimator(-1, 0, 0.001); +} + +TEST(TrendlineEstimator, PerfectLineSlopeZero) { + TestEstimator(0, 0, 0.001); +} + +TEST(TrendlineEstimator, JitteryLineSlopeOneHalf) { + TestEstimator(0.5, kAvgTimeBetweenPackets / 3.0, 0.01); +} + +TEST(TrendlineEstimator, JitteryLineSlopeMinusOne) { + TestEstimator(-1, kAvgTimeBetweenPackets / 3.0, 0.075); +} + +TEST(TrendlineEstimator, JitteryLineSlopeZero) { + TestEstimator(0, kAvgTimeBetweenPackets / 3.0, 0.02); +} + +} // namespace webrtc