| Index: webrtc/modules/remote_bitrate_estimator/test/estimators/bbr.cc
|
| diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/bbr.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/bbr.cc
|
| index 11979bf4468d1b2291fe92233dcd6573fdfad3f8..a45700b19e9528d15303960928e244e3f29dfdde 100644
|
| --- a/webrtc/modules/remote_bitrate_estimator/test/estimators/bbr.cc
|
| +++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/bbr.cc
|
| @@ -12,6 +12,7 @@
|
| #include "webrtc/modules/remote_bitrate_estimator/test/estimators/bbr.h"
|
|
|
| #include <stdlib.h>
|
| +#include <algorithm>
|
|
|
| #include "webrtc/modules/remote_bitrate_estimator/test/estimators/congestion_window.h"
|
| #include "webrtc/modules/remote_bitrate_estimator/test/estimators/max_bandwidth_filter.h"
|
| @@ -21,7 +22,7 @@ namespace webrtc {
|
| namespace testing {
|
| namespace bwe {
|
| namespace {
|
| -const int kFeedbackIntervalsMs = 3;
|
| +const int kFeedbackIntervalsMs = 5;
|
| // BBR uses this value to double sending rate each round trip. Design document
|
| // suggests using this value.
|
| const float kHighGain = 2.885f;
|
| @@ -35,7 +36,7 @@ const int kMaxRoundsWithoutGrowth = 3;
|
| // Pacing gain values for Probe Bandwidth mode.
|
| const float kPacingGain[] = {1.25, 0.75, 1, 1, 1, 1, 1, 1};
|
| const size_t kGainCycleLength = sizeof(kPacingGain) / sizeof(kPacingGain[0]);
|
| -// The least amount of rounds PROBE_RTT mode should last.
|
| +// Least number of rounds PROBE_RTT should last.
|
| const int kProbeRttDurationRounds = 1;
|
| // The least amount of milliseconds PROBE_RTT mode should last.
|
| const int kProbeRttDurationMs = 200;
|
| @@ -46,9 +47,20 @@ const float kTargetCongestionWindowGain = 1;
|
| // work, it is nice to have some extra room in congestion window for full link
|
| // utilization. Value chosen by observations on different tests.
|
| const float kCruisingCongestionWindowGain = 1.5f;
|
| -// Expiration time for min_rtt sample, which is set to 10 seconds according to
|
| -// BBR design doc.
|
| -const int64_t kMinRttFilterSizeMs = 10000;
|
| +// Pacing gain specific for Recovery mode. Chosen by experiments in simulation
|
| +// tool.
|
| +const float kRecoveryPacingGain = 0.5f;
|
| +// Congestion window gain specific for Recovery mode. Chosen by experiments in
|
| +// simulation tool.
|
| +const float kRecoveryCongestionWindowGain = 1.5f;
|
| +// Number of rounds over which average RTT is stored for Recovery mode.
|
| +const size_t kPastRttsFilterSize = 1;
|
| +// Threshold to assume average RTT has increased for a round. Chosen by
|
| +// experiments in simulation tool.
|
| +const float kRttIncreaseThreshold = 3;
|
| +// Threshold to assume average RTT has decreased for a round. Chosen by
|
| +// experiments in simulation tool.
|
| +const float kRttDecreaseThreshold = 1.5f;
|
| } // namespace
|
|
|
| BbrBweSender::BbrBweSender(Clock* clock)
|
| @@ -60,15 +72,32 @@ BbrBweSender::BbrBweSender(Clock* clock)
|
| congestion_window_(new CongestionWindow()),
|
| rand_(new Random(time(NULL))),
|
| round_count_(0),
|
| - last_packet_sent_(0),
|
| round_trip_end_(0),
|
| full_bandwidth_reached_(false),
|
| cycle_start_time_ms_(0),
|
| cycle_index_(0),
|
| - prior_in_flight_(0),
|
| + bytes_acked_(0),
|
| probe_rtt_start_time_ms_(0),
|
| - minimum_congestion_window_start_time_ms_(),
|
| - minimum_congestion_window_start_round_(0) {
|
| + minimum_congestion_window_start_time_ms_(0),
|
| + minimum_congestion_window_start_round_(0),
|
| + bytes_sent_(0),
|
| + last_packet_sent_sequence_number_(0),
|
| + last_packet_acked_sequence_number_(0),
|
| + last_packet_ack_time_(0),
|
| + last_packet_send_time_(0),
|
| + pacing_rate_bps_(0),
|
| + last_packet_send_time_during_high_gain_ms_(-1),
|
| + data_sent_before_high_gain_started_bytes_(-1),
|
| + data_sent_before_high_gain_ended_bytes_(-1),
|
| + first_packet_ack_time_during_high_gain_ms_(-1),
|
| + last_packet_ack_time_during_high_gain_ms_(-1),
|
| + data_acked_before_high_gain_started_bytes_(-1),
|
| + data_acked_before_high_gain_ended_bytes_(-1),
|
| + first_packet_seq_num_during_high_gain_(-1),
|
| + last_packet_seq_num_during_high_gain_(-1),
|
| + high_gain_over_(false),
|
| + packet_stats_(),
|
| + past_rtts_() {
|
| // Initially enter Startup mode.
|
| EnterStartup();
|
| }
|
| @@ -79,26 +108,101 @@ int BbrBweSender::GetFeedbackIntervalMs() const {
|
| return kFeedbackIntervalsMs;
|
| }
|
|
|
| +void BbrBweSender::CalculatePacingRate() {
|
| + pacing_rate_bps_ =
|
| + max_bandwidth_filter_->max_bandwidth_estimate_bps() * pacing_gain_;
|
| +}
|
| +
|
| +void BbrBweSender::HandleLoss(uint64_t last_acked_packet,
|
| + uint64_t recently_acked_packet) {
|
| + // Logic specific to wrapping sequence numbers.
|
| + if (!last_acked_packet)
|
| + return;
|
| + for (uint16_t i = last_acked_packet + 1;
|
| + AheadOrAt<uint16_t>(recently_acked_packet - 1, i); i++) {
|
| + congestion_window_->AckReceived(packet_stats_[i].payload_size_bytes);
|
| + }
|
| +}
|
| +
|
| +void BbrBweSender::AddToPastRtts(int64_t rtt_sample_ms) {
|
| + uint64_t last_round = 0;
|
| + if (!past_rtts_.empty())
|
| + last_round = past_rtts_.back().round;
|
| +
|
| + // Try to add the sample to the last round.
|
| + if (last_round == round_count_ && !past_rtts_.empty()) {
|
| + past_rtts_.back().sum_of_rtts_ms += rtt_sample_ms;
|
| + past_rtts_.back().num_samples++;
|
| + } else {
|
| + // If the sample belongs to a new round, keep number of rounds in the window
|
| + // equal to |kPastRttsFilterSize|.
|
| + if (past_rtts_.size() == kPastRttsFilterSize)
|
| + past_rtts_.pop_front();
|
| + past_rtts_.push_back(
|
| + BbrBweSender::AverageRtt(rtt_sample_ms, 1, round_count_));
|
| + }
|
| +}
|
| +
|
| void BbrBweSender::GiveFeedback(const FeedbackPacket& feedback) {
|
| + int64_t now_ms = clock_->TimeInMilliseconds();
|
| + last_packet_ack_time_ = now_ms;
|
| const BbrBweFeedback& fb = static_cast<const BbrBweFeedback&>(feedback);
|
| // feedback_vector holds values of acknowledged packets' sequence numbers.
|
| const std::vector<uint64_t>& feedback_vector = fb.packet_feedback_vector();
|
| - // Check if new round started for the connection. Round is the period of time
|
| - // from sending packet to its acknowledgement.
|
| + // Go through all the packets acked, update variables/containers accordingly.
|
| + for (uint16_t sequence_number : feedback_vector) {
|
| + // Completing packet information with a recently received ack.
|
| + PacketStats* packet = &packet_stats_[sequence_number];
|
| + bytes_acked_ += packet->payload_size_bytes;
|
| + packet->data_sent_bytes = bytes_sent_;
|
| + packet->last_sent_packet_send_time_ms = last_packet_send_time_;
|
| + packet->data_acked_bytes = bytes_acked_;
|
| + packet->ack_time_ms = now_ms;
|
| + // Logic specific to applying "bucket" to high gain, in order to have
|
| + // quicker ramp-up. We check if we started receiving acks for the packets
|
| + // sent during high gain phase.
|
| + if (packet->sequence_number == first_packet_seq_num_during_high_gain_) {
|
| + first_packet_ack_time_during_high_gain_ms_ = now_ms;
|
| + // Substracting half of the packet's size to avoid overestimation.
|
| + data_acked_before_high_gain_started_bytes_ =
|
| + bytes_acked_ - packet->payload_size_bytes / 2;
|
| + }
|
| + // If the last packet of high gain phase has been acked, high gain phase is
|
| + // over.
|
| + if (packet->sequence_number == last_packet_seq_num_during_high_gain_) {
|
| + last_packet_ack_time_during_high_gain_ms_ = now_ms;
|
| + data_acked_before_high_gain_ended_bytes_ =
|
| + bytes_acked_ - packet->payload_size_bytes / 2;
|
| + high_gain_over_ = true;
|
| + }
|
| + // Notify pacer that an ack was received, to adjust data inflight.
|
| + // TODO(gnish): Add implementation for BitrateObserver class, to notify
|
| + // pacer about incoming acks.
|
| + congestion_window_->AckReceived(packet->payload_size_bytes);
|
| + HandleLoss(last_packet_acked_sequence_number_, packet->sequence_number);
|
| + last_packet_acked_sequence_number_ = packet->sequence_number;
|
| + // Logic for wrapping sequence numbers. If round started with packet number
|
| + // x, it can never end on y, if x > y. That could happen when sequence
|
| + // numbers are wrapped after some point.
|
| + if (packet->sequence_number == 0)
|
| + round_trip_end_ = 0;
|
| + }
|
| + // Check if new round started for the connection.
|
| bool new_round_started = false;
|
| if (!feedback_vector.empty()) {
|
| - uint64_t last_acked_packet = *feedback_vector.rbegin();
|
| - if (last_acked_packet > round_trip_end_) {
|
| + if (last_packet_acked_sequence_number_ > round_trip_end_) {
|
| new_round_started = true;
|
| round_count_++;
|
| - round_trip_end_ = last_packet_sent_;
|
| + round_trip_end_ = last_packet_sent_sequence_number_;
|
| }
|
| }
|
| + bool min_rtt_expired = false;
|
| + min_rtt_expired =
|
| + UpdateBandwidthAndMinRtt(now_ms, feedback_vector, bytes_acked_);
|
| if (new_round_started && !full_bandwidth_reached_) {
|
| full_bandwidth_reached_ = max_bandwidth_filter_->FullBandwidthReached(
|
| kStartupGrowthTarget, kMaxRoundsWithoutGrowth);
|
| }
|
| - int now_ms = clock_->TimeInMilliseconds();
|
| switch (mode_) {
|
| break;
|
| case STARTUP:
|
| @@ -111,12 +215,25 @@ void BbrBweSender::GiveFeedback(const FeedbackPacket& feedback) {
|
| TryUpdatingCyclePhase(now_ms);
|
| break;
|
| case PROBE_RTT:
|
| - TryExitingProbeRtt(now_ms, 0);
|
| + TryExitingProbeRtt(now_ms, round_count_);
|
| + break;
|
| + case RECOVERY:
|
| + TryExitingRecovery(new_round_started);
|
| break;
|
| }
|
| TryEnteringProbeRtt(now_ms);
|
| - // TODO(gnish): implement functions updating congestion window and pacing rate
|
| - // controllers.
|
| + TryEnteringRecovery(new_round_started); // Comment this line to disable
|
| + // entering Recovery mode.
|
| + for (uint64_t f : feedback_vector)
|
| + AddToPastRtts(packet_stats_[f].ack_time_ms - packet_stats_[f].send_time_ms);
|
| + CalculatePacingRate();
|
| + // Make sure we don't get stuck when pacing_rate is 0, because of simulation
|
| + // tool specifics.
|
| + if (!pacing_rate_bps_)
|
| + pacing_rate_bps_ = 100;
|
| + BWE_TEST_LOGGING_PLOT(1, "SendRate", now_ms, pacing_rate_bps_ / 1000);
|
| + // TODO(gnish): Add implementation for BitrateObserver class to update pacing
|
| + // rate for the pacer and the encoder.
|
| }
|
|
|
| size_t BbrBweSender::TargetCongestionWindow(float gain) {
|
| @@ -127,8 +244,82 @@ size_t BbrBweSender::TargetCongestionWindow(float gain) {
|
| return target_congestion_window;
|
| }
|
|
|
| -bool BbrBweSender::UpdateBandwidthAndMinRtt() {
|
| - return false;
|
| +rtc::Optional<int64_t> BbrBweSender::CalculateBandwidthSample(
|
| + size_t data_sent_bytes,
|
| + int64_t send_time_delta_ms,
|
| + size_t data_acked_bytes,
|
| + int64_t ack_time_delta_ms) {
|
| + rtc::Optional<int64_t> bandwidth_sample;
|
| + if (send_time_delta_ms > 0)
|
| + *bandwidth_sample = data_sent_bytes * 8000 / send_time_delta_ms;
|
| + rtc::Optional<int64_t> ack_rate;
|
| + if (ack_time_delta_ms > 0)
|
| + *ack_rate = data_acked_bytes * 8000 / ack_time_delta_ms;
|
| + // If send rate couldn't be calculated automaticaly set |bandwidth_sample| to
|
| + // ack_rate.
|
| + if (!bandwidth_sample)
|
| + bandwidth_sample = ack_rate;
|
| + if (bandwidth_sample && ack_rate)
|
| + *bandwidth_sample = std::min(*bandwidth_sample, *ack_rate);
|
| + return bandwidth_sample;
|
| +}
|
| +
|
| +void BbrBweSender::AddSampleForHighGain() {
|
| + if (!high_gain_over_)
|
| + return;
|
| + high_gain_over_ = false;
|
| + // Calculate data sent/acked and time elapsed only for packets sent during
|
| + // high gain phase.
|
| + size_t data_sent_bytes = data_sent_before_high_gain_ended_bytes_ -
|
| + data_sent_before_high_gain_started_bytes_;
|
| + int64_t send_time_delta_ms = last_packet_send_time_during_high_gain_ms_ -
|
| + *first_packet_send_time_during_high_gain_ms_;
|
| + size_t data_acked_bytes = data_acked_before_high_gain_ended_bytes_ -
|
| + data_acked_before_high_gain_started_bytes_;
|
| + int64_t ack_time_delta_ms = last_packet_ack_time_during_high_gain_ms_ -
|
| + first_packet_ack_time_during_high_gain_ms_;
|
| + rtc::Optional<int64_t> bandwidth_sample = CalculateBandwidthSample(
|
| + data_sent_bytes, send_time_delta_ms, data_acked_bytes, ack_time_delta_ms);
|
| + if (bandwidth_sample)
|
| + max_bandwidth_filter_->AddBandwidthSample(*bandwidth_sample, round_count_);
|
| + first_packet_send_time_during_high_gain_ms_.reset();
|
| +}
|
| +
|
| +bool BbrBweSender::UpdateBandwidthAndMinRtt(
|
| + int64_t now_ms,
|
| + const std::vector<uint64_t>& feedback_vector,
|
| + int64_t bytes_acked) {
|
| + rtc::Optional<int64_t> min_rtt_sample_ms;
|
| + for (uint64_t f : feedback_vector) {
|
| + PacketStats packet = packet_stats_[f];
|
| + size_t data_sent_bytes =
|
| + packet.data_sent_bytes - packet.data_sent_before_last_sent_packet_bytes;
|
| + int64_t send_time_delta_ms =
|
| + packet.last_sent_packet_send_time_ms - packet.send_time_ms;
|
| + size_t data_acked_bytes = packet.data_acked_bytes -
|
| + packet.data_acked_before_last_acked_packet_bytes;
|
| + int64_t ack_time_delta_ms =
|
| + packet.ack_time_ms - packet.last_acked_packet_ack_time_ms;
|
| + rtc::Optional<int64_t> bandwidth_sample =
|
| + CalculateBandwidthSample(data_sent_bytes, send_time_delta_ms,
|
| + data_acked_bytes, ack_time_delta_ms);
|
| + if (bandwidth_sample)
|
| + max_bandwidth_filter_->AddBandwidthSample(*bandwidth_sample,
|
| + round_count_);
|
| + AddSampleForHighGain(); // Comment to disable bucket for high gain.
|
| + if (!min_rtt_sample_ms)
|
| + *min_rtt_sample_ms = packet.ack_time_ms - packet.send_time_ms;
|
| + else
|
| + *min_rtt_sample_ms = std::min(*min_rtt_sample_ms,
|
| + packet.ack_time_ms - packet.send_time_ms);
|
| + BWE_TEST_LOGGING_PLOT(1, "MinRtt", now_ms,
|
| + packet.ack_time_ms - packet.send_time_ms);
|
| + }
|
| + if (!min_rtt_sample_ms)
|
| + return false;
|
| + min_rtt_filter_->AddRttSample(*min_rtt_sample_ms, now_ms);
|
| + bool min_rtt_expired = min_rtt_filter_->MinRttExpired(now_ms);
|
| + return min_rtt_expired;
|
| }
|
|
|
| void BbrBweSender::EnterStartup() {
|
| @@ -174,12 +365,13 @@ void BbrBweSender::TryUpdatingCyclePhase(int64_t now_ms) {
|
| // If BBR was probing and it couldn't increase data inflight sufficiently in
|
| // one min_rtt time, continue probing. BBR design doc isn't clear about this,
|
| // but condition helps in quicker ramp-up and performs better.
|
| - if (pacing_gain_ > 1.0 &&
|
| - prior_in_flight_ < TargetCongestionWindow(pacing_gain_))
|
| + if (pacing_gain_ > 1.0 && congestion_window_->data_inflight() <
|
| + TargetCongestionWindow(pacing_gain_))
|
| advance_cycle_phase = false;
|
| // If BBR has already drained queues there is no point in continuing draining
|
| // phase.
|
| - if (pacing_gain_ < 1.0 && prior_in_flight_ <= TargetCongestionWindow(1))
|
| + if (pacing_gain_ < 1.0 &&
|
| + congestion_window_->data_inflight() <= TargetCongestionWindow(1))
|
| advance_cycle_phase = true;
|
| if (advance_cycle_phase) {
|
| cycle_index_++;
|
| @@ -190,8 +382,7 @@ void BbrBweSender::TryUpdatingCyclePhase(int64_t now_ms) {
|
| }
|
|
|
| void BbrBweSender::TryEnteringProbeRtt(int64_t now_ms) {
|
| - if (min_rtt_filter_->min_rtt_expired(now_ms, kMinRttFilterSizeMs) &&
|
| - mode_ != PROBE_RTT) {
|
| + if (min_rtt_filter_->MinRttExpired(now_ms) && mode_ != PROBE_RTT) {
|
| mode_ = PROBE_RTT;
|
| pacing_gain_ = 1;
|
| probe_rtt_start_time_ms_ = now_ms;
|
| @@ -199,9 +390,10 @@ void BbrBweSender::TryEnteringProbeRtt(int64_t now_ms) {
|
| }
|
| }
|
|
|
| -// minimum_congestion_window_start_time_'s value is set to the first moment when
|
| -// data inflight was less then kMinimumCongestionWindowBytes, we should make
|
| -// sure that BBR has been in PROBE_RTT mode for at least one round or 200ms.
|
| +// |minimum_congestion_window_start_time_|'s value is set to the first moment
|
| +// when data inflight was less then
|
| +// |CongestionWindow::kMinimumCongestionWindowBytes|, we should make sure that
|
| +// BBR has been in PROBE_RTT mode for at least one round or 200ms.
|
| void BbrBweSender::TryExitingProbeRtt(int64_t now_ms, int64_t round) {
|
| if (!minimum_congestion_window_start_time_ms_) {
|
| if (congestion_window_->data_inflight() <=
|
| @@ -218,13 +410,74 @@ void BbrBweSender::TryExitingProbeRtt(int64_t now_ms, int64_t round) {
|
| }
|
| }
|
|
|
| +void BbrBweSender::TryEnteringRecovery(bool new_round_started) {
|
| + // If we are already in Recovery don't try to enter.
|
| + if (mode_ == RECOVERY || !new_round_started || !full_bandwidth_reached_)
|
| + return;
|
| + uint64_t increased_rtt_round_counter = 0;
|
| + // If average RTT for past |kPastRttsFilterSize| rounds has been more than
|
| + // some multiplier of min_rtt_ms enter Recovery.
|
| + for (BbrBweSender::AverageRtt i : past_rtts_) {
|
| + if (i.sum_of_rtts_ms / (int64_t)i.num_samples >=
|
| + *min_rtt_filter_->min_rtt_ms() * kRttIncreaseThreshold)
|
| + increased_rtt_round_counter++;
|
| + }
|
| + if (increased_rtt_round_counter < kPastRttsFilterSize)
|
| + return;
|
| + mode_ = RECOVERY;
|
| + pacing_gain_ = kRecoveryPacingGain;
|
| + congestion_window_gain_ = kRecoveryCongestionWindowGain;
|
| +}
|
| +
|
| +void BbrBweSender::TryExitingRecovery(bool new_round_started) {
|
| + if (mode_ != RECOVERY || !new_round_started || !full_bandwidth_reached_)
|
| + return;
|
| + // If average RTT for the past round has decreased sufficiently exit Recovery.
|
| + if (!past_rtts_.empty()) {
|
| + BbrBweSender::AverageRtt last_round_sample = past_rtts_.back();
|
| + if (last_round_sample.sum_of_rtts_ms / last_round_sample.num_samples <=
|
| + *min_rtt_filter_->min_rtt_ms() * kRttDecreaseThreshold) {
|
| + EnterProbeBw(clock_->TimeInMilliseconds());
|
| + }
|
| + }
|
| +}
|
| +
|
| int64_t BbrBweSender::TimeUntilNextProcess() {
|
| - return 100;
|
| + return 50;
|
| }
|
|
|
| void BbrBweSender::OnPacketsSent(const Packets& packets) {
|
| - last_packet_sent_ =
|
| - static_cast<const MediaPacket*>(packets.back())->sequence_number();
|
| + for (Packet* packet : packets) {
|
| + if (packet->GetPacketType() == Packet::kMedia) {
|
| + MediaPacket* media_packet = static_cast<MediaPacket*>(packet);
|
| + bytes_sent_ += media_packet->payload_size();
|
| + PacketStats packet_stats = PacketStats(
|
| + media_packet->sequence_number(), 0,
|
| + media_packet->sender_timestamp_ms(), 0, last_packet_ack_time_,
|
| + media_packet->payload_size(), 0, bytes_sent_, 0, bytes_acked_);
|
| + packet_stats_[media_packet->sequence_number()] = packet_stats;
|
| + last_packet_send_time_ = media_packet->sender_timestamp_ms();
|
| + last_packet_sent_sequence_number_ = media_packet->sequence_number();
|
| + // If this is the first packet sent for high gain phase, save data for it.
|
| + if (!first_packet_send_time_during_high_gain_ms_ && pacing_gain_ > 1) {
|
| + *first_packet_send_time_during_high_gain_ms_ = last_packet_send_time_;
|
| + data_sent_before_high_gain_started_bytes_ =
|
| + bytes_sent_ - media_packet->payload_size() / 2;
|
| + first_packet_seq_num_during_high_gain_ =
|
| + media_packet->sequence_number();
|
| + }
|
| + // This condition ensures that |last_packet_seq_num_during_high_gain_|
|
| + // will contain a sequence number of the last packet sent during high gain
|
| + // phase.
|
| + if (pacing_gain_ > 1) {
|
| + last_packet_send_time_during_high_gain_ms_ = last_packet_send_time_;
|
| + data_sent_before_high_gain_ended_bytes_ =
|
| + bytes_sent_ - media_packet->payload_size() / 2;
|
| + last_packet_seq_num_during_high_gain_ = media_packet->sequence_number();
|
| + }
|
| + congestion_window_->PacketSent(media_packet->payload_size());
|
| + }
|
| + }
|
| }
|
|
|
| void BbrBweSender::Process() {}
|
|
|