diff --git a/modules/congestion_controller/goog_cc/BUILD.gn b/modules/congestion_controller/goog_cc/BUILD.gn index 851122ec5c..00cd2d55c1 100644 --- a/modules/congestion_controller/goog_cc/BUILD.gn +++ b/modules/congestion_controller/goog_cc/BUILD.gn @@ -129,6 +129,8 @@ rtc_library("estimators") { "../../../api/rtc_event_log", "../../../api/transport:network_control", "../../../api/units:data_rate", + "../../../api/units:data_size", + "../../../api/units:time_delta", "../../../api/units:timestamp", "../../../logging:rtc_event_bwe", "../../../rtc_base:checks", @@ -357,6 +359,7 @@ if (rtc_include_tests) { "../../pacing", "//testing/gmock", ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings:strings" ] } } } diff --git a/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.cc b/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.cc index 283c9a8a41..c043353a7a 100644 --- a/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.cc +++ b/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.cc @@ -12,6 +12,7 @@ #include +#include "api/units/time_delta.h" #include "modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h" #include "modules/congestion_controller/goog_cc/robust_throughput_estimator.h" #include "rtc_base/logging.h" @@ -24,22 +25,36 @@ RobustThroughputEstimatorSettings::RobustThroughputEstimatorSettings( const FieldTrialsView* key_value_config) { Parser()->Parse( key_value_config->Lookup(RobustThroughputEstimatorSettings::kKey)); - if (min_packets < 10 || kMaxPackets < min_packets) { - RTC_LOG(LS_WARNING) << "Window size must be between 10 and " << kMaxPackets - << " packets"; - min_packets = 20; + if (window_packets < 10 || 1000 < window_packets) { + RTC_LOG(LS_WARNING) << "Window size must be between 10 and 1000 packets"; + window_packets = 20; } - if (initial_packets < 10 || kMaxPackets < initial_packets) { - RTC_LOG(LS_WARNING) << "Initial size must be between 10 and " << kMaxPackets - << " packets"; - initial_packets = 20; + if (max_window_packets < 10 || 1000 < max_window_packets) { + RTC_LOG(LS_WARNING) + << "Max window size must be between 10 and 1000 packets"; + max_window_packets = 500; } - initial_packets = std::min(initial_packets, min_packets); - if (window_duration < TimeDelta::Millis(100) || - TimeDelta::Millis(2000) < window_duration) { - RTC_LOG(LS_WARNING) << "Window duration must be between 100 and 2000 ms"; - window_duration = TimeDelta::Millis(500); + max_window_packets = std::max(max_window_packets, window_packets); + + if (required_packets < 10 || 1000 < required_packets) { + RTC_LOG(LS_WARNING) << "Required number of initial packets must be between " + "10 and 1000 packets"; + required_packets = 10; } + required_packets = std::min(required_packets, window_packets); + + if (min_window_duration < TimeDelta::Millis(100) || + TimeDelta::Millis(3000) < min_window_duration) { + RTC_LOG(LS_WARNING) << "Window duration must be between 100 and 3000 ms"; + min_window_duration = TimeDelta::Millis(750); + } + if (max_window_duration < TimeDelta::Seconds(1) || + TimeDelta::Seconds(15) < max_window_duration) { + RTC_LOG(LS_WARNING) << "Max window duration must be between 1 and 15 s"; + max_window_duration = TimeDelta::Seconds(5); + } + min_window_duration = std::min(min_window_duration, max_window_duration); + if (unacked_weight < 0.0 || 1.0 < unacked_weight) { RTC_LOG(LS_WARNING) << "Weight for prior unacked size must be between 0 and 1."; @@ -49,14 +64,14 @@ RobustThroughputEstimatorSettings::RobustThroughputEstimatorSettings( std::unique_ptr RobustThroughputEstimatorSettings::Parser() { - return StructParametersParser::Create("enabled", &enabled, // - "reduce_bias", &reduce_bias, // - "assume_shared_link", // - &assume_shared_link, // - "min_packets", &min_packets, // - "window_duration", &window_duration, // - "initial_packets", &initial_packets, // - "unacked_weight", &unacked_weight); + return StructParametersParser::Create( + "enabled", &enabled, // + "window_packets", &window_packets, // + "max_window_packets", &max_window_packets, // + "window_duration", &min_window_duration, // + "max_window_duration", &max_window_duration, // + "required_packets", &required_packets, // + "unacked_weight", &unacked_weight); } AcknowledgedBitrateEstimatorInterface:: diff --git a/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.h b/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.h index 6dce69b72b..515af1efc9 100644 --- a/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.h +++ b/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.h @@ -11,6 +11,8 @@ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_ACKNOWLEDGED_BITRATE_ESTIMATOR_INTERFACE_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_ACKNOWLEDGED_BITRATE_ESTIMATOR_INTERFACE_H_ +#include + #include #include @@ -18,13 +20,14 @@ #include "api/field_trials_view.h" #include "api/transport/network_types.h" #include "api/units/data_rate.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" #include "rtc_base/experiments/struct_parameters_parser.h" namespace webrtc { struct RobustThroughputEstimatorSettings { static constexpr char kKey[] = "WebRTC-Bwe-RobustThroughputEstimatorSettings"; - static constexpr size_t kMaxPackets = 500; RobustThroughputEstimatorSettings() = delete; explicit RobustThroughputEstimatorSettings( @@ -32,30 +35,32 @@ struct RobustThroughputEstimatorSettings { bool enabled = false; // Set to true to use RobustThroughputEstimator. - // The estimator handles delay spikes by removing the largest receive time - // gap, but this introduces some bias that may lead to overestimation when - // there isn't any delay spike. If `reduce_bias` is true, we instead replace - // the largest receive time gap by the second largest. This reduces the bias - // at the cost of not completely removing the genuine delay spikes. - bool reduce_bias = true; + // The estimator keeps the smallest window containing at least + // `window_packets` and at least the packets received during the last + // `min_window_duration` milliseconds. + // (This means that it may store more than `window_packets` at high bitrates, + // and a longer duration than `min_window_duration` at low bitrates.) + // However, if will never store more than kMaxPackets (for performance + // reasons), and never longer than max_window_duration (to avoid very old + // packets influencing the estimate for example when sending is paused). + unsigned window_packets = 20; + unsigned max_window_packets = 500; + TimeDelta min_window_duration = TimeDelta::Seconds(1); + TimeDelta max_window_duration = TimeDelta::Seconds(5); - // If `assume_shared_link` is false, we ignore the size of the first packet - // when computing the receive rate. Otherwise, we remove half of the first - // and last packet's sizes. - bool assume_shared_link = false; - - // The estimator window keeps at least `min_packets` packets and up to - // kMaxPackets received during the last `window_duration`. - unsigned min_packets = 20; - TimeDelta window_duration = TimeDelta::Millis(500); - - // The estimator window requires at least `initial_packets` packets received - // over at least `initial_duration`. - unsigned initial_packets = 20; + // The estimator window requires at least `required_packets` packets + // to produce an estimate. + unsigned required_packets = 10; + // If audio packets aren't included in allocation (i.e. the + // estimated available bandwidth is divided only among the video + // streams), then `unacked_weight` should be set to 0. // If audio packets are included in allocation, but not in bandwidth - // estimation and the sent audio packets get double counted, - // then it might be useful to reduce the weight to 0.5. + // estimation (i.e. they don't have transport-wide sequence numbers, + // but we nevertheless divide the estimated available bandwidth among + // both audio and video streams), then `unacked_weight` should be set to 1. + // If all packets have transport-wide sequence numbers, then the value + // of `unacked_weight` doesn't matter. double unacked_weight = 1.0; std::unique_ptr Parser(); diff --git a/modules/congestion_controller/goog_cc/robust_throughput_estimator.cc b/modules/congestion_controller/goog_cc/robust_throughput_estimator.cc index 1169e9f6bb..93909afab6 100644 --- a/modules/congestion_controller/goog_cc/robust_throughput_estimator.cc +++ b/modules/congestion_controller/goog_cc/robust_throughput_estimator.cc @@ -15,24 +15,55 @@ #include #include +#include "api/units/data_rate.h" +#include "api/units/data_size.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" #include "rtc_base/checks.h" namespace webrtc { RobustThroughputEstimator::RobustThroughputEstimator( const RobustThroughputEstimatorSettings& settings) - : settings_(settings) { + : settings_(settings), + latest_discarded_send_time_(Timestamp::MinusInfinity()) { RTC_DCHECK(settings.enabled); } RobustThroughputEstimator::~RobustThroughputEstimator() {} +bool RobustThroughputEstimator::FirstPacketOutsideWindow() { + if (window_.empty()) + return false; + if (window_.size() > settings_.max_window_packets) + return true; + TimeDelta current_window_duration = + window_.back().receive_time - window_.front().receive_time; + if (current_window_duration > settings_.max_window_duration) + return true; + if (window_.size() > settings_.window_packets && + current_window_duration > settings_.min_window_duration) { + return true; + } + return false; +} + void RobustThroughputEstimator::IncomingPacketFeedbackVector( const std::vector& packet_feedback_vector) { RTC_DCHECK(std::is_sorted(packet_feedback_vector.begin(), packet_feedback_vector.end(), PacketResult::ReceiveTimeOrder())); for (const auto& packet : packet_feedback_vector) { + // Ignore packets without valid send or receive times. + // (This should not happen in production since lost packets are filtered + // out before passing the feedback vector to the throughput estimator. + // However, explicitly handling this case makes the estimator more robust + // and avoids a hard-to-detect bad state.) + if (packet.receive_time.IsInfinite() || + packet.sent_packet.send_time.IsInfinite()) { + continue; + } + // Insert the new packet. window_.push_back(packet); window_.back().sent_packet.prior_unacked_data = @@ -45,24 +76,24 @@ void RobustThroughputEstimator::IncomingPacketFeedbackVector( i > 0 && window_[i].receive_time < window_[i - 1].receive_time; i--) { std::swap(window_[i], window_[i - 1]); } - // Remove old packets. - while (window_.size() > settings_.kMaxPackets || - (window_.size() > settings_.min_packets && - packet.receive_time - window_.front().receive_time > - settings_.window_duration)) { - window_.pop_front(); - } + } + + // Remove old packets. + while (FirstPacketOutsideWindow()) { + latest_discarded_send_time_ = std::max( + latest_discarded_send_time_, window_.front().sent_packet.send_time); + window_.pop_front(); } } absl::optional RobustThroughputEstimator::bitrate() const { - if (window_.size() < settings_.initial_packets) + if (window_.empty() || window_.size() < settings_.required_packets) return absl::nullopt; TimeDelta largest_recv_gap(TimeDelta::Millis(0)); TimeDelta second_largest_recv_gap(TimeDelta::Millis(0)); for (size_t i = 1; i < window_.size(); i++) { - // Find receive time gaps + // Find receive time gaps. TimeDelta gap = window_[i].receive_time - window_[i - 1].receive_time; if (gap > largest_recv_gap) { second_largest_recv_gap = largest_recv_gap; @@ -72,63 +103,86 @@ absl::optional RobustThroughputEstimator::bitrate() const { } } - Timestamp min_send_time = window_[0].sent_packet.send_time; - Timestamp max_send_time = window_[0].sent_packet.send_time; - Timestamp min_recv_time = window_[0].receive_time; - Timestamp max_recv_time = window_[0].receive_time; - DataSize data_size = DataSize::Bytes(0); + Timestamp first_send_time = Timestamp::PlusInfinity(); + Timestamp last_send_time = Timestamp::MinusInfinity(); + Timestamp first_recv_time = Timestamp::PlusInfinity(); + Timestamp last_recv_time = Timestamp::MinusInfinity(); + DataSize recv_size = DataSize::Bytes(0); + DataSize send_size = DataSize::Bytes(0); + DataSize first_recv_size = DataSize::Bytes(0); + DataSize last_send_size = DataSize::Bytes(0); + size_t num_sent_packets_in_window = 0; for (const auto& packet : window_) { - min_send_time = std::min(min_send_time, packet.sent_packet.send_time); - max_send_time = std::max(max_send_time, packet.sent_packet.send_time); - min_recv_time = std::min(min_recv_time, packet.receive_time); - max_recv_time = std::max(max_recv_time, packet.receive_time); - data_size += packet.sent_packet.size; - data_size += packet.sent_packet.prior_unacked_data; + if (packet.receive_time < first_recv_time) { + first_recv_time = packet.receive_time; + first_recv_size = + packet.sent_packet.size + packet.sent_packet.prior_unacked_data; + } + last_recv_time = std::max(last_recv_time, packet.receive_time); + recv_size += packet.sent_packet.size; + recv_size += packet.sent_packet.prior_unacked_data; + + if (packet.sent_packet.send_time < latest_discarded_send_time_) { + // If we have dropped packets from the window that were sent after + // this packet, then this packet was reordered. Ignore it from + // the send rate computation (since the send time may be very far + // in the past, leading to underestimation of the send rate.) + // However, ignoring packets creates a risk that we end up without + // any packets left to compute a send rate. + continue; + } + if (packet.sent_packet.send_time > last_send_time) { + last_send_time = packet.sent_packet.send_time; + last_send_size = + packet.sent_packet.size + packet.sent_packet.prior_unacked_data; + } + first_send_time = std::min(first_send_time, packet.sent_packet.send_time); + + send_size += packet.sent_packet.size; + send_size += packet.sent_packet.prior_unacked_data; + ++num_sent_packets_in_window; } // Suppose a packet of size S is sent every T milliseconds. // A window of N packets would contain N*S bytes, but the time difference // between the first and the last packet would only be (N-1)*T. Thus, we - // need to remove one packet. - DataSize recv_size = data_size; - DataSize send_size = data_size; - if (settings_.assume_shared_link) { - // Depending on how the bottleneck queue is implemented, a large packet - // may delay sending of sebsequent packets, so the delay between packets - // i and i+1 depends on the size of both packets. In this case we minimize - // the maximum error by removing half of both the first and last packet - // size. - DataSize first_last_average_size = - (window_.front().sent_packet.size + - window_.front().sent_packet.prior_unacked_data + - window_.back().sent_packet.size + - window_.back().sent_packet.prior_unacked_data) / - 2; - recv_size -= first_last_average_size; - send_size -= first_last_average_size; - } else { - // In the simpler case where the delay between packets i and i+1 only - // depends on the size of packet i+1, the first packet doesn't give us - // any information. Analogously, we assume that the start send time - // for the last packet doesn't depend on the size of the packet. - recv_size -= (window_.front().sent_packet.size + - window_.front().sent_packet.prior_unacked_data); - send_size -= (window_.back().sent_packet.size + - window_.back().sent_packet.prior_unacked_data); - } + // need to remove the size of one packet to get the correct rate of S/T. + // Which packet to remove (if the packets have varying sizes), + // depends on the network model. + // Suppose that 2 packets with sizes s1 and s2, are received at times t1 + // and t2, respectively. If the packets were transmitted back to back over + // a bottleneck with rate capacity r, then we'd expect t2 = t1 + r * s2. + // Thus, r = (t2-t1) / s2, so the size of the first packet doesn't affect + // the difference between t1 and t2. + // Analoguously, if the first packet is sent at time t1 and the sender + // paces the packets at rate r, then the second packet can be sent at time + // t2 = t1 + r * s1. Thus, the send rate estimate r = (t2-t1) / s1 doesn't + // depend on the size of the last packet. + recv_size -= first_recv_size; + send_size -= last_send_size; - // Remove the largest gap by replacing it by the second largest gap - // or the average gap. - TimeDelta send_duration = max_send_time - min_send_time; - TimeDelta recv_duration = (max_recv_time - min_recv_time) - largest_recv_gap; - if (settings_.reduce_bias) { - recv_duration += second_largest_recv_gap; - } else { - recv_duration += recv_duration / (window_.size() - 2); - } - - send_duration = std::max(send_duration, TimeDelta::Millis(1)); + // Remove the largest gap by replacing it by the second largest gap. + // This is to ensure that spurious "delay spikes" (i.e. when the + // network stops transmitting packets for a short period, followed + // by a burst of delayed packets), don't cause the estimate to drop. + // This could cause an overestimation, which we guard against by + // never returning an estimate above the send rate. + RTC_DCHECK(first_recv_time.IsFinite()); + RTC_DCHECK(last_recv_time.IsFinite()); + TimeDelta recv_duration = (last_recv_time - first_recv_time) - + largest_recv_gap + second_largest_recv_gap; recv_duration = std::max(recv_duration, TimeDelta::Millis(1)); + + if (num_sent_packets_in_window < settings_.required_packets) { + // Too few send times to calculate a reliable send rate. + return recv_size / recv_duration; + } + + RTC_DCHECK(first_send_time.IsFinite()); + RTC_DCHECK(last_send_time.IsFinite()); + TimeDelta send_duration = last_send_time - first_send_time; + send_duration = std::max(send_duration, TimeDelta::Millis(1)); + return std::min(send_size / send_duration, recv_size / recv_duration); } diff --git a/modules/congestion_controller/goog_cc/robust_throughput_estimator.h b/modules/congestion_controller/goog_cc/robust_throughput_estimator.h index b67b49fcfb..9d89856496 100644 --- a/modules/congestion_controller/goog_cc/robust_throughput_estimator.h +++ b/modules/congestion_controller/goog_cc/robust_throughput_estimator.h @@ -12,13 +12,12 @@ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_ROBUST_THROUGHPUT_ESTIMATOR_H_ #include -#include #include #include "absl/types/optional.h" -#include "api/field_trials_view.h" #include "api/transport/network_types.h" #include "api/units/data_rate.h" +#include "api/units/timestamp.h" #include "modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.h" namespace webrtc { @@ -39,8 +38,11 @@ class RobustThroughputEstimator : public AcknowledgedBitrateEstimatorInterface { void SetAlrEndedTime(Timestamp /*alr_ended_time*/) override {} private: + bool FirstPacketOutsideWindow(); + const RobustThroughputEstimatorSettings settings_; std::deque window_; + Timestamp latest_discarded_send_time_ = Timestamp::MinusInfinity(); }; } // namespace webrtc diff --git a/modules/congestion_controller/goog_cc/robust_throughput_estimator_unittest.cc b/modules/congestion_controller/goog_cc/robust_throughput_estimator_unittest.cc index d2e01d362c..95ac525640 100644 --- a/modules/congestion_controller/goog_cc/robust_throughput_estimator_unittest.cc +++ b/modules/congestion_controller/goog_cc/robust_throughput_estimator_unittest.cc @@ -10,158 +10,418 @@ #include "modules/congestion_controller/goog_cc/robust_throughput_estimator.h" -#include "api/transport/field_trial_based_config.h" -#include "test/field_trial.h" +#include +#include + +#include +#include + +#include "absl/strings/string_view.h" +#include "api/units/data_size.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "test/explicit_key_value_config.h" #include "test/gtest.h" namespace webrtc { -namespace { -std::vector CreateFeedbackVector(size_t number_of_packets, - DataSize packet_size, - TimeDelta send_increment, - TimeDelta recv_increment, - Timestamp* send_clock, - Timestamp* recv_clock, - uint16_t* sequence_number) { - std::vector packet_feedback_vector(number_of_packets); - for (size_t i = 0; i < number_of_packets; i++) { - packet_feedback_vector[i].receive_time = *recv_clock; - packet_feedback_vector[i].sent_packet.send_time = *send_clock; - packet_feedback_vector[i].sent_packet.sequence_number = *sequence_number; - packet_feedback_vector[i].sent_packet.size = packet_size; - *send_clock += send_increment; - *recv_clock += recv_increment; - *sequence_number += 1; - } - return packet_feedback_vector; -} -} // anonymous namespace -TEST(RobustThroughputEstimatorTest, SteadyRate) { - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Bwe-RobustThroughputEstimatorSettings/" - "enabled:true,assume_shared_link:false,reduce_bias:true,min_packets:10," - "window_duration:100ms/"); - FieldTrialBasedConfig field_trial_config; - RobustThroughputEstimatorSettings settings(&field_trial_config); - RobustThroughputEstimator throughput_estimator(settings); - DataSize packet_size(DataSize::Bytes(1000)); - Timestamp send_clock(Timestamp::Millis(100000)); - Timestamp recv_clock(Timestamp::Millis(10000)); - TimeDelta send_increment(TimeDelta::Millis(10)); - TimeDelta recv_increment(TimeDelta::Millis(10)); - uint16_t sequence_number = 100; +RobustThroughputEstimatorSettings CreateRobustThroughputEstimatorSettings( + absl::string_view field_trial_string) { + test::ExplicitKeyValueConfig trials(field_trial_string); + RobustThroughputEstimatorSettings settings(&trials); + return settings; +} + +class FeedbackGenerator { + public: + std::vector CreateFeedbackVector(size_t number_of_packets, + DataSize packet_size, + DataRate send_rate, + DataRate recv_rate) { + std::vector packet_feedback_vector(number_of_packets); + for (size_t i = 0; i < number_of_packets; i++) { + packet_feedback_vector[i].sent_packet.send_time = send_clock_; + packet_feedback_vector[i].sent_packet.sequence_number = sequence_number_; + packet_feedback_vector[i].sent_packet.size = packet_size; + send_clock_ += packet_size / send_rate; + recv_clock_ += packet_size / recv_rate; + sequence_number_ += 1; + packet_feedback_vector[i].receive_time = recv_clock_; + } + return packet_feedback_vector; + } + + Timestamp CurrentReceiveClock() { return recv_clock_; } + + void AdvanceReceiveClock(TimeDelta delta) { recv_clock_ += delta; } + + void AdvanceSendClock(TimeDelta delta) { send_clock_ += delta; } + + private: + Timestamp send_clock_ = Timestamp::Millis(100000); + Timestamp recv_clock_ = Timestamp::Millis(10000); + uint16_t sequence_number_ = 100; +}; + +TEST(RobustThroughputEstimatorTest, InitialEstimate) { + FeedbackGenerator feedback_generator; + RobustThroughputEstimator throughput_estimator( + CreateRobustThroughputEstimatorSettings( + "WebRTC-Bwe-RobustThroughputEstimatorSettings/" + "enabled:true/")); + DataRate send_rate(DataRate::BytesPerSec(100000)); + DataRate recv_rate(DataRate::BytesPerSec(100000)); + + // No estimate until the estimator has enough data. std::vector packet_feedback = - CreateFeedbackVector(9, packet_size, send_increment, recv_increment, - &send_clock, &recv_clock, &sequence_number); + feedback_generator.CreateFeedbackVector(9, DataSize::Bytes(1000), + send_rate, recv_rate); throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); EXPECT_FALSE(throughput_estimator.bitrate().has_value()); - packet_feedback = - CreateFeedbackVector(11, packet_size, send_increment, recv_increment, - &send_clock, &recv_clock, &sequence_number); + // Estimate once `required_packets` packets have been received. + packet_feedback = feedback_generator.CreateFeedbackVector( + 1, DataSize::Bytes(1000), send_rate, recv_rate); throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); auto throughput = throughput_estimator.bitrate(); - EXPECT_TRUE(throughput.has_value()); - EXPECT_NEAR(throughput.value().bytes_per_sec(), 100 * 1000.0, - 0.05 * 100 * 1000.0); // Allow 5% error + EXPECT_EQ(throughput, send_rate); + + // Estimate remains stable when send and receive rates are stable. + packet_feedback = feedback_generator.CreateFeedbackVector( + 15, DataSize::Bytes(1000), send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + throughput = throughput_estimator.bitrate(); + EXPECT_EQ(throughput, send_rate); } -TEST(RobustThroughputEstimatorTest, DelaySpike) { - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Bwe-RobustThroughputEstimatorSettings/" - "enabled:true,assume_shared_link:false,reduce_bias:true,min_packets:10," - "window_duration:100ms/"); - FieldTrialBasedConfig field_trial_config; - RobustThroughputEstimatorSettings settings(&field_trial_config); - RobustThroughputEstimator throughput_estimator(settings); - DataSize packet_size(DataSize::Bytes(1000)); - Timestamp send_clock(Timestamp::Millis(100000)); - Timestamp recv_clock(Timestamp::Millis(10000)); - TimeDelta send_increment(TimeDelta::Millis(10)); - TimeDelta recv_increment(TimeDelta::Millis(10)); - uint16_t sequence_number = 100; - std::vector packet_feedback = - CreateFeedbackVector(20, packet_size, send_increment, recv_increment, - &send_clock, &recv_clock, &sequence_number); - throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); - auto throughput = throughput_estimator.bitrate(); - EXPECT_TRUE(throughput.has_value()); - EXPECT_NEAR(throughput.value().bytes_per_sec(), 100 * 1000.0, - 0.05 * 100 * 1000.0); // Allow 5% error +TEST(RobustThroughputEstimatorTest, EstimateAdapts) { + FeedbackGenerator feedback_generator; + RobustThroughputEstimator throughput_estimator( + CreateRobustThroughputEstimatorSettings( + "WebRTC-Bwe-RobustThroughputEstimatorSettings/" + "enabled:true/")); - // Delay spike - recv_clock += TimeDelta::Millis(40); + // 1 second, 800kbps, estimate is stable. + DataRate send_rate(DataRate::BytesPerSec(100000)); + DataRate recv_rate(DataRate::BytesPerSec(100000)); + for (int i = 0; i < 10; ++i) { + std::vector packet_feedback = + feedback_generator.CreateFeedbackVector(10, DataSize::Bytes(1000), + send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + auto throughput = throughput_estimator.bitrate(); + EXPECT_EQ(throughput, send_rate); + } - // Faster delivery after the gap - recv_increment = TimeDelta::Millis(2); - packet_feedback = - CreateFeedbackVector(5, packet_size, send_increment, recv_increment, - &send_clock, &recv_clock, &sequence_number); - throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); - throughput = throughput_estimator.bitrate(); - EXPECT_TRUE(throughput.has_value()); - EXPECT_NEAR(throughput.value().bytes_per_sec(), 100 * 1000.0, - 0.05 * 100 * 1000.0); // Allow 5% error + // 1 second, 1600kbps, estimate increases + send_rate = DataRate::BytesPerSec(200000); + recv_rate = DataRate::BytesPerSec(200000); + for (int i = 0; i < 20; ++i) { + std::vector packet_feedback = + feedback_generator.CreateFeedbackVector(10, DataSize::Bytes(1000), + send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + auto throughput = throughput_estimator.bitrate(); + ASSERT_TRUE(throughput.has_value()); + EXPECT_GE(throughput.value(), DataRate::BytesPerSec(100000)); + EXPECT_LE(throughput.value(), send_rate); + } - // Delivery at normal rate. This will be capped by the send rate. - recv_increment = TimeDelta::Millis(10); - packet_feedback = - CreateFeedbackVector(5, packet_size, send_increment, recv_increment, - &send_clock, &recv_clock, &sequence_number); - throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); - throughput = throughput_estimator.bitrate(); - EXPECT_TRUE(throughput.has_value()); - EXPECT_NEAR(throughput.value().bytes_per_sec(), 100 * 1000.0, - 0.05 * 100 * 1000.0); // Allow 5% error + // 1 second, 1600kbps, estimate is stable + for (int i = 0; i < 20; ++i) { + std::vector packet_feedback = + feedback_generator.CreateFeedbackVector(10, DataSize::Bytes(1000), + send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + auto throughput = throughput_estimator.bitrate(); + EXPECT_EQ(throughput, send_rate); + } + + // 1 second, 400kbps, estimate decreases + send_rate = DataRate::BytesPerSec(50000); + recv_rate = DataRate::BytesPerSec(50000); + for (int i = 0; i < 5; ++i) { + std::vector packet_feedback = + feedback_generator.CreateFeedbackVector(10, DataSize::Bytes(1000), + send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + auto throughput = throughput_estimator.bitrate(); + ASSERT_TRUE(throughput.has_value()); + EXPECT_LE(throughput.value(), DataRate::BytesPerSec(200000)); + EXPECT_GE(throughput.value(), send_rate); + } + + // 1 second, 400kbps, estimate is stable + send_rate = DataRate::BytesPerSec(50000); + recv_rate = DataRate::BytesPerSec(50000); + for (int i = 0; i < 5; ++i) { + std::vector packet_feedback = + feedback_generator.CreateFeedbackVector(10, DataSize::Bytes(1000), + send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + auto throughput = throughput_estimator.bitrate(); + EXPECT_EQ(throughput, send_rate); + } } TEST(RobustThroughputEstimatorTest, CappedByReceiveRate) { - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Bwe-RobustThroughputEstimatorSettings/" - "enabled:true,assume_shared_link:false,reduce_bias:true,min_packets:10," - "window_duration:100ms/"); - FieldTrialBasedConfig field_trial_config; - RobustThroughputEstimatorSettings settings(&field_trial_config); - RobustThroughputEstimator throughput_estimator(settings); - DataSize packet_size(DataSize::Bytes(1000)); - Timestamp send_clock(Timestamp::Millis(100000)); - Timestamp recv_clock(Timestamp::Millis(10000)); - TimeDelta send_increment(TimeDelta::Millis(10)); - TimeDelta recv_increment(TimeDelta::Millis(40)); - uint16_t sequence_number = 100; + FeedbackGenerator feedback_generator; + RobustThroughputEstimator throughput_estimator( + CreateRobustThroughputEstimatorSettings( + "WebRTC-Bwe-RobustThroughputEstimatorSettings/" + "enabled:true/")); + DataRate send_rate(DataRate::BytesPerSec(100000)); + DataRate recv_rate(DataRate::BytesPerSec(25000)); + std::vector packet_feedback = - CreateFeedbackVector(20, packet_size, send_increment, recv_increment, - &send_clock, &recv_clock, &sequence_number); + feedback_generator.CreateFeedbackVector(20, DataSize::Bytes(1000), + send_rate, recv_rate); throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); auto throughput = throughput_estimator.bitrate(); - EXPECT_TRUE(throughput.has_value()); - EXPECT_NEAR(throughput.value().bytes_per_sec(), 25 * 1000.0, - 0.05 * 25 * 1000.0); // Allow 5% error + ASSERT_TRUE(throughput.has_value()); + EXPECT_NEAR(throughput.value().bytes_per_sec(), + recv_rate.bytes_per_sec(), + 0.05 * recv_rate.bytes_per_sec()); // Allow 5% error } TEST(RobustThroughputEstimatorTest, CappedBySendRate) { - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Bwe-RobustThroughputEstimatorSettings/" - "enabled:true,assume_shared_link:false,reduce_bias:true,min_packets:10," - "window_duration:100ms/"); - FieldTrialBasedConfig field_trial_config; - RobustThroughputEstimatorSettings settings(&field_trial_config); - RobustThroughputEstimator throughput_estimator(settings); - DataSize packet_size(DataSize::Bytes(1000)); - Timestamp send_clock(Timestamp::Millis(100000)); - Timestamp recv_clock(Timestamp::Millis(10000)); - TimeDelta send_increment(TimeDelta::Millis(20)); - TimeDelta recv_increment(TimeDelta::Millis(10)); - uint16_t sequence_number = 100; + FeedbackGenerator feedback_generator; + RobustThroughputEstimator throughput_estimator( + CreateRobustThroughputEstimatorSettings( + "WebRTC-Bwe-RobustThroughputEstimatorSettings/" + "enabled:true/")); + DataRate send_rate(DataRate::BytesPerSec(50000)); + DataRate recv_rate(DataRate::BytesPerSec(100000)); + std::vector packet_feedback = - CreateFeedbackVector(20, packet_size, send_increment, recv_increment, - &send_clock, &recv_clock, &sequence_number); + feedback_generator.CreateFeedbackVector(20, DataSize::Bytes(1000), + send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + auto throughput = throughput_estimator.bitrate(); + ASSERT_TRUE(throughput.has_value()); + EXPECT_NEAR(throughput.value().bytes_per_sec(), + send_rate.bytes_per_sec(), + 0.05 * send_rate.bytes_per_sec()); // Allow 5% error +} + +TEST(RobustThroughputEstimatorTest, DelaySpike) { + FeedbackGenerator feedback_generator; + // This test uses a 500ms window to amplify the effect + // of a delay spike. + RobustThroughputEstimator throughput_estimator( + CreateRobustThroughputEstimatorSettings( + "WebRTC-Bwe-RobustThroughputEstimatorSettings/" + "enabled:true,window_duration:500ms/")); + DataRate send_rate(DataRate::BytesPerSec(100000)); + DataRate recv_rate(DataRate::BytesPerSec(100000)); + + std::vector packet_feedback = + feedback_generator.CreateFeedbackVector(20, DataSize::Bytes(1000), + send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + auto throughput = throughput_estimator.bitrate(); + EXPECT_EQ(throughput, send_rate); + + // Delay spike. 25 packets sent, but none received. + feedback_generator.AdvanceReceiveClock(TimeDelta::Millis(250)); + + // Deliver all of the packets during the next 50 ms. (During this time, + // we'll have sent an additional 5 packets, so we need to receive 30 + // packets at 1000 bytes each in 50 ms, i.e. 600000 bytes per second). + recv_rate = DataRate::BytesPerSec(600000); + // Estimate should not drop. + for (int i = 0; i < 30; ++i) { + packet_feedback = feedback_generator.CreateFeedbackVector( + 1, DataSize::Bytes(1000), send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + throughput = throughput_estimator.bitrate(); + ASSERT_TRUE(throughput.has_value()); + EXPECT_NEAR(throughput.value().bytes_per_sec(), + send_rate.bytes_per_sec(), + 0.05 * send_rate.bytes_per_sec()); // Allow 5% error + } + + // Delivery at normal rate. When the packets received before the gap + // has left the estimator's window, the receive rate will be high, but the + // estimate should be capped by the send rate. + recv_rate = DataRate::BytesPerSec(100000); + for (int i = 0; i < 20; ++i) { + packet_feedback = feedback_generator.CreateFeedbackVector( + 5, DataSize::Bytes(1000), send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + throughput = throughput_estimator.bitrate(); + ASSERT_TRUE(throughput.has_value()); + EXPECT_NEAR(throughput.value().bytes_per_sec(), + send_rate.bytes_per_sec(), + 0.05 * send_rate.bytes_per_sec()); // Allow 5% error + } +} + +TEST(RobustThroughputEstimatorTest, HighLoss) { + FeedbackGenerator feedback_generator; + RobustThroughputEstimator throughput_estimator( + CreateRobustThroughputEstimatorSettings( + "WebRTC-Bwe-RobustThroughputEstimatorSettings/" + "enabled:true/")); + DataRate send_rate(DataRate::BytesPerSec(100000)); + DataRate recv_rate(DataRate::BytesPerSec(100000)); + + std::vector packet_feedback = + feedback_generator.CreateFeedbackVector(20, DataSize::Bytes(1000), + send_rate, recv_rate); + + // 50% loss + for (size_t i = 0; i < packet_feedback.size(); i++) { + if (i % 2 == 1) { + packet_feedback[i].receive_time = Timestamp::PlusInfinity(); + } + } + + std::sort(packet_feedback.begin(), packet_feedback.end(), + PacketResult::ReceiveTimeOrder()); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + auto throughput = throughput_estimator.bitrate(); + ASSERT_TRUE(throughput.has_value()); + EXPECT_NEAR(throughput.value().bytes_per_sec(), + send_rate.bytes_per_sec() / 2, + 0.05 * send_rate.bytes_per_sec() / 2); // Allow 5% error +} + +TEST(RobustThroughputEstimatorTest, ReorderedFeedback) { + FeedbackGenerator feedback_generator; + RobustThroughputEstimator throughput_estimator( + CreateRobustThroughputEstimatorSettings( + "WebRTC-Bwe-RobustThroughputEstimatorSettings/" + "enabled:true/")); + DataRate send_rate(DataRate::BytesPerSec(100000)); + DataRate recv_rate(DataRate::BytesPerSec(100000)); + + std::vector packet_feedback = + feedback_generator.CreateFeedbackVector(20, DataSize::Bytes(1000), + send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + auto throughput = throughput_estimator.bitrate(); + EXPECT_EQ(throughput, send_rate); + + std::vector delayed_feedback = + feedback_generator.CreateFeedbackVector(10, DataSize::Bytes(1000), + send_rate, recv_rate); + packet_feedback = feedback_generator.CreateFeedbackVector( + 10, DataSize::Bytes(1000), send_rate, recv_rate); + + // Since we're missing some feedback, it's expected that the + // estimate will drop. + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + throughput = throughput_estimator.bitrate(); + ASSERT_TRUE(throughput.has_value()); + EXPECT_LT(throughput.value(), send_rate); + + // But it should completely recover as soon as we get the feedback. + throughput_estimator.IncomingPacketFeedbackVector(delayed_feedback); + throughput = throughput_estimator.bitrate(); + EXPECT_EQ(throughput, send_rate); + + // It should then remain stable (as if the feedbacks weren't reordered.) + for (int i = 0; i < 10; ++i) { + packet_feedback = feedback_generator.CreateFeedbackVector( + 15, DataSize::Bytes(1000), send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + throughput = throughput_estimator.bitrate(); + EXPECT_EQ(throughput, send_rate); + } +} + +TEST(RobustThroughputEstimatorTest, DeepReordering) { + FeedbackGenerator feedback_generator; + // This test uses a 500ms window to amplify the + // effect of reordering. + RobustThroughputEstimator throughput_estimator( + CreateRobustThroughputEstimatorSettings( + "WebRTC-Bwe-RobustThroughputEstimatorSettings/" + "enabled:true,window_duration:500ms/")); + DataRate send_rate(DataRate::BytesPerSec(100000)); + DataRate recv_rate(DataRate::BytesPerSec(100000)); + + std::vector delayed_packets = + feedback_generator.CreateFeedbackVector(1, DataSize::Bytes(1000), + send_rate, recv_rate); + + for (int i = 0; i < 10; i++) { + std::vector packet_feedback = + feedback_generator.CreateFeedbackVector(10, DataSize::Bytes(1000), + send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + auto throughput = throughput_estimator.bitrate(); + EXPECT_EQ(throughput, send_rate); + } + + // Delayed packet arrives ~1 second after it should have. + // Since the window is 500 ms, the delayed packet was sent ~500 + // ms before the second oldest packet. However, the send rate + // should not drop. + delayed_packets.front().receive_time = + feedback_generator.CurrentReceiveClock(); + throughput_estimator.IncomingPacketFeedbackVector(delayed_packets); + auto throughput = throughput_estimator.bitrate(); + ASSERT_TRUE(throughput.has_value()); + EXPECT_NEAR(throughput.value().bytes_per_sec(), + send_rate.bytes_per_sec(), + 0.05 * send_rate.bytes_per_sec()); // Allow 5% error + + // Thoughput should stay stable. + for (int i = 0; i < 10; i++) { + std::vector packet_feedback = + feedback_generator.CreateFeedbackVector(10, DataSize::Bytes(1000), + send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + auto throughput = throughput_estimator.bitrate(); + ASSERT_TRUE(throughput.has_value()); + EXPECT_NEAR(throughput.value().bytes_per_sec(), + send_rate.bytes_per_sec(), + 0.05 * send_rate.bytes_per_sec()); // Allow 5% error + } +} + +TEST(RobustThroughputEstimatorTest, StreamPausedAndResumed) { + FeedbackGenerator feedback_generator; + RobustThroughputEstimator throughput_estimator( + CreateRobustThroughputEstimatorSettings( + "WebRTC-Bwe-RobustThroughputEstimatorSettings/" + "enabled:true/")); + DataRate send_rate(DataRate::BytesPerSec(100000)); + DataRate recv_rate(DataRate::BytesPerSec(100000)); + + std::vector packet_feedback = + feedback_generator.CreateFeedbackVector(20, DataSize::Bytes(1000), + send_rate, recv_rate); throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); auto throughput = throughput_estimator.bitrate(); EXPECT_TRUE(throughput.has_value()); - EXPECT_NEAR(throughput.value().bytes_per_sec(), 50 * 1000.0, - 0.05 * 50 * 1000.0); // Allow 5% error + double expected_bytes_per_sec = 100 * 1000.0; + EXPECT_NEAR(throughput.value().bytes_per_sec(), + expected_bytes_per_sec, + 0.05 * expected_bytes_per_sec); // Allow 5% error + + // No packets sent or feedback received for 60s. + feedback_generator.AdvanceSendClock(TimeDelta::Seconds(60)); + feedback_generator.AdvanceReceiveClock(TimeDelta::Seconds(60)); + + // Resume sending packets at the same rate as before. The estimate + // will initially be invalid, due to lack of recent data. + packet_feedback = feedback_generator.CreateFeedbackVector( + 5, DataSize::Bytes(1000), send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + throughput = throughput_estimator.bitrate(); + EXPECT_FALSE(throughput.has_value()); + + // But be back to the normal level once we have enough data. + for (int i = 0; i < 4; ++i) { + packet_feedback = feedback_generator.CreateFeedbackVector( + 5, DataSize::Bytes(1000), send_rate, recv_rate); + throughput_estimator.IncomingPacketFeedbackVector(packet_feedback); + throughput = throughput_estimator.bitrate(); + EXPECT_EQ(throughput, send_rate); + } } -} // namespace webrtc*/ +} // namespace webrtc diff --git a/rtc_tools/rtc_event_log_visualizer/analyzer.cc b/rtc_tools/rtc_event_log_visualizer/analyzer.cc index bdbb43882f..f94adc28f6 100644 --- a/rtc_tools/rtc_event_log_visualizer/analyzer.cc +++ b/rtc_tools/rtc_event_log_visualizer/analyzer.cc @@ -1242,11 +1242,11 @@ void EventLogAnalyzer::CreateSendSideBweSimulationGraph(Plot* plot) { return std::numeric_limits::max(); }; - RateStatistics acked_bitrate(750, 8000); + RateStatistics raw_acked_bitrate(750, 8000); test::ExplicitKeyValueConfig throughput_config( "WebRTC-Bwe-RobustThroughputEstimatorSettings/" - "enabled:true,reduce_bias:true,assume_shared_link:false,initial_packets:" - "10,min_packets:25,window_duration:750ms,unacked_weight:0.5/"); + "enabled:true,required_packets:10," + "window_packets:25,window_duration:1000ms,unacked_weight:1.0/"); std::unique_ptr robust_throughput_estimator( AcknowledgedBitrateEstimatorInterface::Create(&throughput_config)); @@ -1305,7 +1305,6 @@ void EventLogAnalyzer::CreateSendSideBweSimulationGraph(Plot* plot) { auto feedback_msg = transport_feedback.ProcessTransportFeedback( rtcp_iterator->transport_feedback, Timestamp::Millis(clock.TimeInMilliseconds())); - absl::optional bitrate_bps; if (feedback_msg) { observer.Update(goog_cc->OnTransportPacketsFeedback(*feedback_msg)); std::vector feedback = @@ -1315,24 +1314,30 @@ void EventLogAnalyzer::CreateSendSideBweSimulationGraph(Plot* plot) { feedback); robust_throughput_estimator->IncomingPacketFeedbackVector(feedback); for (const PacketResult& packet : feedback) { - acked_bitrate.Update(packet.sent_packet.size.bytes(), - packet.receive_time.ms()); + raw_acked_bitrate.Update(packet.sent_packet.size.bytes(), + packet.receive_time.ms()); + } + absl::optional raw_bitrate_bps = + raw_acked_bitrate.Rate(feedback.back().receive_time.ms()); + float x = config_.GetCallTimeSec(clock.CurrentTime()); + if (raw_bitrate_bps) { + float y = raw_bitrate_bps.value() / 1000; + acked_time_series.points.emplace_back(x, y); + } + absl::optional robust_estimate = + robust_throughput_estimator->bitrate(); + if (robust_estimate) { + float y = robust_estimate.value().kbps(); + robust_time_series.points.emplace_back(x, y); + } + absl::optional acked_estimate = + acknowledged_bitrate_estimator->bitrate(); + if (acked_estimate) { + float y = acked_estimate.value().kbps(); + acked_estimate_time_series.points.emplace_back(x, y); } - bitrate_bps = acked_bitrate.Rate(feedback.back().receive_time.ms()); } } - - float x = config_.GetCallTimeSec(clock.CurrentTime()); - float y = bitrate_bps.value_or(0) / 1000; - acked_time_series.points.emplace_back(x, y); - y = robust_throughput_estimator->bitrate() - .value_or(DataRate::Zero()) - .kbps(); - robust_time_series.points.emplace_back(x, y); - y = acknowledged_bitrate_estimator->bitrate() - .value_or(DataRate::Zero()) - .kbps(); - acked_estimate_time_series.points.emplace_back(x, y); ++rtcp_iterator; } if (clock.TimeInMicroseconds() >= NextProcessTime()) {