From f69f1fbc98e5705b8ccf3559e58c9eebb67e8b96 Mon Sep 17 00:00:00 2001 From: Cesar Magalhaes Date: Sat, 30 May 2015 17:49:18 +0200 Subject: [PATCH] Testing and improving NADA algorithm. A modified operation mode was added, holding: --- Stricter conditions for AcceleratedRampUp. --- Smoother GradualRateUpdate adjustments. --- New AcceleratedRampDown update mode. This mode reduces significantly the delay for bitrates around its minimum bound. Several NADA unittests and a few simulations were added. Fixed LinkedSet bug. Fixed IsNewerSequenceNumber/IsNewerTimestamp bug. BUG=4550 R=stefan@webrtc.org, tommi@webrtc.org Review URL: https://webrtc-codereview.appspot.com/54399004 Cr-Commit-Position: refs/heads/master@{#9340} --- .../neteq/packet_buffer_unittest.cc | 9 +- .../modules/interface/module_common_types.h | 45 +- .../modules/module_common_types_unittest.cc | 21 + .../bwe_simulations.cc | 44 +- .../test/estimators/nada.cc | 187 ++++-- .../test/estimators/nada.h | 52 +- .../test/estimators/nada_unittest.cc | 596 ++++++++++++++++-- 7 files changed, 825 insertions(+), 129 deletions(-) diff --git a/webrtc/modules/audio_coding/neteq/packet_buffer_unittest.cc b/webrtc/modules/audio_coding/neteq/packet_buffer_unittest.cc index dc8b68c32..61a8ee121 100644 --- a/webrtc/modules/audio_coding/neteq/packet_buffer_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/packet_buffer_unittest.cc @@ -531,9 +531,14 @@ void TestIsObsoleteTimestamp(uint32_t limit_timestamp) { // 1 sample ahead is not old. EXPECT_FALSE(PacketBuffer::IsObsoleteTimestamp( limit_timestamp + 1, limit_timestamp, kZeroHorizon)); - // 2^31 samples ahead is not old. + // If |t1-t2|=2^31 and t1>t2, t2 is older than t1 but not the opposite. + uint32_t other_timestamp = limit_timestamp + (1 << 31); + uint32_t lowest_timestamp = std::min(limit_timestamp, other_timestamp); + uint32_t highest_timestamp = std::max(limit_timestamp, other_timestamp); + EXPECT_TRUE(PacketBuffer::IsObsoleteTimestamp( + lowest_timestamp, highest_timestamp, kZeroHorizon)); EXPECT_FALSE(PacketBuffer::IsObsoleteTimestamp( - limit_timestamp + (1 << 31), limit_timestamp, kZeroHorizon)); + highest_timestamp, lowest_timestamp, kZeroHorizon)); // Fixed horizon at 10 samples. static const uint32_t kHorizon = 10; diff --git a/webrtc/modules/interface/module_common_types.h b/webrtc/modules/interface/module_common_types.h index 94492b4ab..0b0e063a6 100644 --- a/webrtc/modules/interface/module_common_types.h +++ b/webrtc/modules/interface/module_common_types.h @@ -509,6 +509,18 @@ inline AudioFrame& AudioFrame::Append(const AudioFrame& rhs) { return *this; } +namespace { +inline int16_t ClampToInt16(int32_t input) { + if (input < -0x00008000) { + return -0x8000; + } else if (input > 0x00007FFF) { + return 0x7FFF; + } else { + return static_cast(input); + } +} +} + inline AudioFrame& AudioFrame::operator+=(const AudioFrame& rhs) { // Sanity check assert((num_channels_ > 0) && (num_channels_ < 3)); @@ -541,15 +553,9 @@ inline AudioFrame& AudioFrame::operator+=(const AudioFrame& rhs) { } else { // IMPROVEMENT this can be done very fast in assembly for (int i = 0; i < samples_per_channel_ * num_channels_; i++) { - int32_t wrapGuard = + int32_t wrap_guard = static_cast(data_[i]) + static_cast(rhs.data_[i]); - if (wrapGuard < -32768) { - data_[i] = -32768; - } else if (wrapGuard > 32767) { - data_[i] = 32767; - } else { - data_[i] = (int16_t)wrapGuard; - } + data_[i] = ClampToInt16(wrap_guard); } } energy_ = 0xffffffff; @@ -572,15 +578,9 @@ inline AudioFrame& AudioFrame::operator-=(const AudioFrame& rhs) { speech_type_ = kUndefined; for (int i = 0; i < samples_per_channel_ * num_channels_; i++) { - int32_t wrapGuard = + int32_t wrap_guard = static_cast(data_[i]) - static_cast(rhs.data_[i]); - if (wrapGuard < -32768) { - data_[i] = -32768; - } else if (wrapGuard > 32767) { - data_[i] = 32767; - } else { - data_[i] = (int16_t)wrapGuard; - } + data_[i] = ClampToInt16(wrap_guard); } energy_ = 0xffffffff; return *this; @@ -588,11 +588,24 @@ inline AudioFrame& AudioFrame::operator-=(const AudioFrame& rhs) { inline bool IsNewerSequenceNumber(uint16_t sequence_number, uint16_t prev_sequence_number) { + // Distinguish between elements that are exactly 0x8000 apart. + // If s1>s2 and |s1-s2| = 0x8000: IsNewer(s1,s2)=true, IsNewer(s2,s1)=false + // rather than having IsNewer(s1,s2) = IsNewer(s2,s1) = false. + if (static_cast(sequence_number - prev_sequence_number) == 0x8000) { + return sequence_number > prev_sequence_number; + } return sequence_number != prev_sequence_number && static_cast(sequence_number - prev_sequence_number) < 0x8000; } inline bool IsNewerTimestamp(uint32_t timestamp, uint32_t prev_timestamp) { + // Distinguish between elements that are exactly 0x80000000 apart. + // If t1>t2 and |t1-t2| = 0x80000000: IsNewer(t1,t2)=true, + // IsNewer(t2,t1)=false + // rather than having IsNewer(t1,t2) = IsNewer(t2,t1) = false. + if (static_cast(timestamp - prev_timestamp) == 0x80000000) { + return timestamp > prev_timestamp; + } return timestamp != prev_timestamp && static_cast(timestamp - prev_timestamp) < 0x80000000; } diff --git a/webrtc/modules/module_common_types_unittest.cc b/webrtc/modules/module_common_types_unittest.cc index ee23bf020..3e7f59416 100644 --- a/webrtc/modules/module_common_types_unittest.cc +++ b/webrtc/modules/module_common_types_unittest.cc @@ -38,6 +38,11 @@ TEST(IsNewerSequenceNumber, BackwardWrap) { EXPECT_FALSE(IsNewerSequenceNumber(0xFF00, 0x00FF)); } +TEST(IsNewerSequenceNumber, HalfWayApart) { + EXPECT_TRUE(IsNewerSequenceNumber(0x8000, 0x0000)); + EXPECT_FALSE(IsNewerSequenceNumber(0x0000, 0x8000)); +} + TEST(IsNewerTimestamp, Equal) { EXPECT_FALSE(IsNewerTimestamp(0x00000001, 0x000000001)); } @@ -62,6 +67,11 @@ TEST(IsNewerTimestamp, BackwardWrap) { EXPECT_FALSE(IsNewerTimestamp(0xFFFF0000, 0x0000FFFF)); } +TEST(IsNewerTimestamp, HalfWayApart) { + EXPECT_TRUE(IsNewerTimestamp(0x80000000, 0x00000000)); + EXPECT_FALSE(IsNewerTimestamp(0x00000000, 0x80000000)); +} + TEST(LatestSequenceNumber, NoWrap) { EXPECT_EQ(0xFFFFu, LatestSequenceNumber(0xFFFF, 0xFFFE)); EXPECT_EQ(0x0001u, LatestSequenceNumber(0x0001, 0x0000)); @@ -101,4 +111,15 @@ TEST(LatestTimestamp, Wrap) { EXPECT_EQ(0x0000FFFFu, LatestTimestamp(0xFFFFFFFF, 0x0000FFFF)); EXPECT_EQ(0x0000FFFFu, LatestTimestamp(0xFFFF0000, 0x0000FFFF)); } + +TEST(ClampToInt16, TestCases) { + EXPECT_EQ(0x0000, ClampToInt16(0x00000000)); + EXPECT_EQ(0x0001, ClampToInt16(0x00000001)); + EXPECT_EQ(0x7FFF, ClampToInt16(0x00007FFF)); + EXPECT_EQ(0x7FFF, ClampToInt16(0x7FFFFFFF)); + EXPECT_EQ(-0x0001, ClampToInt16(-0x00000001)); + EXPECT_EQ(-0x8000, ClampToInt16(-0x8000)); + EXPECT_EQ(-0x8000, ClampToInt16(-0x7FFFFFFF)); +} + } // namespace webrtc diff --git a/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc b/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc index d51931727..09d23fae6 100644 --- a/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc +++ b/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc @@ -183,6 +183,44 @@ TEST_P(BweSimulation, GoogleWifiTrace3Mbps) { RunFor(300 * 1000); } +TEST_P(BweSimulation, LinearIncreasingCapacity) { + PeriodicKeyFrameSource source(0, 30, 300, 0, 0, 1000000); + PacedVideoSender sender(&uplink_, &source, GetParam()); + ChokeFilter filter(&uplink_, 0); + RateCounterFilter counter(&uplink_, 0, "receiver_input"); + PacketReceiver receiver(&uplink_, 0, GetParam(), true, true); + filter.SetMaxDelay(500); + const int kStartingCapacityKbps = 150; + const int kEndingCapacityKbps = 1500; + const int kStepKbps = 5; + const int kStepTimeMs = 1000; + + for (int i = kStartingCapacityKbps; i <= kEndingCapacityKbps; + i += kStepKbps) { + filter.SetCapacity(i); + RunFor(kStepTimeMs); + } +} + +TEST_P(BweSimulation, LinearDecreasingCapacity) { + PeriodicKeyFrameSource source(0, 30, 300, 0, 0, 1000000); + PacedVideoSender sender(&uplink_, &source, GetParam()); + ChokeFilter filter(&uplink_, 0); + RateCounterFilter counter(&uplink_, 0, "receiver_input"); + PacketReceiver receiver(&uplink_, 0, GetParam(), true, true); + filter.SetMaxDelay(500); + const int kStartingCapacityKbps = 1500; + const int kEndingCapacityKbps = 150; + const int kStepKbps = -5; + const int kStepTimeMs = 1000; + + for (int i = kStartingCapacityKbps; i >= kEndingCapacityKbps; + i += kStepKbps) { + filter.SetCapacity(i); + RunFor(kStepTimeMs); + } +} + TEST_P(BweSimulation, PacerGoogleWifiTrace3Mbps) { PeriodicKeyFrameSource source(0, 30, 300, 0, 0, 1000); PacedVideoSender sender(&uplink_, &source, GetParam()); @@ -234,11 +272,6 @@ TEST_P(BweSimulation, PacedSelfFairnessTest) { RunFairnessTest(GetParam(), 4, 0, 1000, 3000, 50); } -TEST_P(BweSimulation, PacedTcpFairnessTest) { - srand(Clock::GetRealTimeClock()->TimeInMicroseconds()); - RunFairnessTest(GetParam(), 4, 0, 1000, 3000, 500); -} - TEST_P(BweSimulation, PacedSelfFairness1000msTest) { srand(Clock::GetRealTimeClock()->TimeInMicroseconds()); RunFairnessTest(GetParam(), 4, 0, 1000, 3000, 1000); @@ -258,6 +291,7 @@ TEST_P(BweSimulation, TcpFairness1000msTest) { srand(Clock::GetRealTimeClock()->TimeInMicroseconds()); RunFairnessTest(GetParam(), 1, 1, 1000, 2000, 1000); } + #endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE } // namespace bwe } // namespace testing diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc index eb9381f32..4c4638b61 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc @@ -20,6 +20,7 @@ #include #include "webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h" +#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h" #include "webrtc/modules/rtp_rtcp/interface/receive_statistics.h" namespace webrtc { @@ -27,6 +28,10 @@ namespace testing { namespace bwe { const int NadaBweReceiver::kMedian; +const int64_t NadaBweReceiver::kPacketLossTimeWindowMs; +const int64_t NadaBweReceiver::kReceivingRateTimeWindowMs; +const int NadaBweSender::kMinRefRateKbps; +const int NadaBweSender::kMaxRefRateKbps; NadaBweReceiver::NadaBweReceiver(int flow_id) : BweReceiver(flow_id), @@ -46,7 +51,7 @@ NadaBweReceiver::~NadaBweReceiver() { void NadaBweReceiver::ReceivePacket(int64_t arrival_time_ms, const MediaPacket& media_packet) { - const float kAlpha = 0.9f; // Used for exponential smoothing. + const float kAlpha = 0.1f; // Used for exponential smoothing. const int64_t kDelayLowThresholdMs = 50; // Referred as d_th. const int64_t kDelayMaxThresholdMs = 400; // Referred as d_max. @@ -70,8 +75,11 @@ void NadaBweReceiver::ReceivePacket(int64_t arrival_time_ms, est_queuing_delay_signal_ms_ = exp_smoothed_delay_ms_; } else if (exp_smoothed_delay_ms_ < kDelayMaxThresholdMs) { est_queuing_delay_signal_ms_ = static_cast( - pow(0.001 * (kDelayMaxThresholdMs - exp_smoothed_delay_ms_), 4.0) / - pow(0.001 * (kDelayMaxThresholdMs - kDelayLowThresholdMs), 4.0)); + pow((static_cast(kDelayMaxThresholdMs - + exp_smoothed_delay_ms_)) / + (kDelayMaxThresholdMs - kDelayLowThresholdMs), + 4.0) * + kDelayLowThresholdMs); } else { est_queuing_delay_signal_ms_ = 0; } @@ -88,8 +96,10 @@ FeedbackPacket* NadaBweReceiver::GetFeedback(int64_t now_ms) { return NULL; } - int64_t loss_signal_ms = static_cast( - RecentPacketLossRatio() * kPacketLossPenaltyMs + 0.5f); + float loss_fraction = RecentPacketLossRatio(); + + int64_t loss_signal_ms = + static_cast(loss_fraction * kPacketLossPenaltyMs + 0.5f); int64_t congestion_signal_ms = est_queuing_delay_signal_ms_ + loss_signal_ms; float derivative = 0.0f; @@ -102,7 +112,7 @@ FeedbackPacket* NadaBweReceiver::GetFeedback(int64_t now_ms) { PacketIdentifierNode* latest = *(received_packets_->begin()); int64_t corrected_send_time_ms = - latest->send_time_ms_ + now_ms - latest->arrival_time_ms_; + latest->send_time_ms + now_ms - latest->arrival_time_ms; // Sends a tuple containing latest values of and additional information. @@ -138,7 +148,6 @@ float NadaBweReceiver::GlobalPacketLossRatio() { // E.g.: for a timewindow covering 5 packets of the following arrival sequence // {10 7 9 5 6} 8 3 2 4 1, the output will be 1/6 (#8 is considered as missing). float NadaBweReceiver::RecentPacketLossRatio() { - const int64_t kRecentTimeWindowMs = 500; if (received_packets_->empty()) { return 0.0f; @@ -148,16 +157,16 @@ float NadaBweReceiver::RecentPacketLossRatio() { PacketNodeIt node_it = received_packets_->begin(); // Latest. // Lowest timestamp limit, oldest one that should be checked. - int64_t time_limit_ms = (*node_it)->arrival_time_ms_ - kRecentTimeWindowMs; + int64_t time_limit_ms = (*node_it)->arrival_time_ms - kPacketLossTimeWindowMs; // Oldest and newest values found within the given time window. - uint16_t oldest_seq_nb = (*node_it)->sequence_number_; + uint16_t oldest_seq_nb = (*node_it)->sequence_number; uint16_t newest_seq_nb = oldest_seq_nb; while (node_it != received_packets_->end()) { - if ((*node_it)->arrival_time_ms_ < time_limit_ms) { + if ((*node_it)->arrival_time_ms < time_limit_ms) { break; } - uint16_t seq_nb = (*node_it)->sequence_number_; + uint16_t seq_nb = (*node_it)->sequence_number; if (IsNewerSequenceNumber(seq_nb, newest_seq_nb)) { newest_seq_nb = seq_nb; } @@ -167,7 +176,6 @@ float NadaBweReceiver::RecentPacketLossRatio() { ++node_it; ++number_packets_received; } - // Interval width between oldest and newest sequence number. // There was an overflow if newest_seq_nb < oldest_seq_nb. int gap = static_cast(newest_seq_nb - oldest_seq_nb + 1); @@ -175,26 +183,58 @@ float NadaBweReceiver::RecentPacketLossRatio() { return static_cast(gap - number_packets_received) / gap; } +// For a given time window, compute the receiving speed rate in kbps. +// As described below, three cases are considered depending on the number of +// packets received. size_t NadaBweReceiver::RecentReceivingRate() { - const int64_t kRecentTimeWindowMs = 500; + // If the receiver didn't receive any packet, return 0. if (received_packets_->empty()) { return 0.0f; } - size_t totalSize = 0; - int64_t time_limit_ms = clock_.TimeInMilliseconds() - kRecentTimeWindowMs; + size_t total_size = 0; + int number_packets = 0; + PacketNodeIt node_it = received_packets_->begin(); + + int64_t last_time_ms = (*node_it)->arrival_time_ms; + int64_t start_time_ms = last_time_ms; PacketNodeIt end = received_packets_->end(); - while (node_it != end && (*node_it)->arrival_time_ms_ > time_limit_ms) { - totalSize += (*node_it)->payload_size_; + // Stops after including the first packet out of the timeWindow. + // Ameliorates results when there are wide gaps between packets. + // E.g. Large packets : p1(0ms), p2(3000ms). + while (node_it != end) { + total_size += (*node_it)->payload_size; + last_time_ms = (*node_it)->arrival_time_ms; + ++number_packets; + if ((*node_it)->arrival_time_ms < + start_time_ms - kReceivingRateTimeWindowMs) { + break; + } ++node_it; } - return static_cast((1000 * totalSize) / kRecentTimeWindowMs); + int64_t corrected_time_ms; + // If the receiver received a single packet, return its size*8/timeWindow. + if (number_packets == 1) { + corrected_time_ms = kReceivingRateTimeWindowMs; + } else { + // If the receiver received multiple packets, use as time interval the gap + // between first and last packet falling in the timeWindow corrected by the + // factor number_packets/(number_packets-1). + // E.g: Let timeWindow = 500ms, payload_size = 500bytes, number_packets=2, + // packets received at t1(0ms) and t2(499 or 501ms). This prevent the + // function from returning ~2*8, sending instead a more likely ~1*8 kbps. + corrected_time_ms = (number_packets * (start_time_ms - last_time_ms)) / + (number_packets - 1); + } + + // Converting from bytes/ms to kbits/s. + return static_cast(8 * total_size / corrected_time_ms); } int64_t NadaBweReceiver::MedianFilter(int64_t* last_delays_ms, int size) { - // Typically size = 5. + // Typically, size = 5. std::vector array_copy(last_delays_ms, last_delays_ms + size); std::nth_element(array_copy.begin(), array_copy.begin() + size / 2, array_copy.end()); @@ -211,12 +251,19 @@ int64_t NadaBweReceiver::ExponentialSmoothingFilter(int64_t new_value, (1.0f - alpha) * last_smoothed_value + 0.5f); } +// Implementation according to Cisco's proposal by default. NadaBweSender::NadaBweSender(int kbps, BitrateObserver* observer, Clock* clock) - : clock_(clock), observer_(observer), bitrate_kbps_(kbps) { + : clock_(clock), + observer_(observer), + bitrate_kbps_(kbps), + original_operating_mode_(true) { } NadaBweSender::NadaBweSender(BitrateObserver* observer, Clock* clock) - : clock_(clock), observer_(observer), bitrate_kbps_(kMinRefRateKbps) { + : clock_(clock), + observer_(observer), + bitrate_kbps_(kMinRefRateKbps), + original_operating_mode_(true) { } NadaBweSender::~NadaBweSender() { @@ -228,11 +275,13 @@ int NadaBweSender::GetFeedbackIntervalMs() const { void NadaBweSender::GiveFeedback(const FeedbackPacket& feedback) { const NadaFeedback& fb = static_cast(feedback); + // Following parameters might be optimized. const int64_t kQueuingDelayUpperBoundMs = 10; - const float kDerivativeUpperBound = 10.0f * min_feedback_delay_ms_; - - const int kMaxRefRateKbps = 1500; // Referred as R_max. + const float kDerivativeUpperBound = 10.0f / min_feedback_delay_ms_; + // In the modified version, a higher kMinUpperBound allows a higher d_hat + // upper bound for calling AcceleratedRampUp. + const float kProportionalityDelayBits = 20.0f; int64_t now_ms = clock_->TimeInMilliseconds(); float delta_s = now_ms - last_feedback_ms_; @@ -242,18 +291,46 @@ void NadaBweSender::GiveFeedback(const FeedbackPacket& feedback) { std::min(min_feedback_delay_ms_, static_cast(delta_s)); // Update RTT_0. - int64_t rtt = now_ms - fb.latest_send_time_ms(); - min_round_trip_time_ms_ = std::min(min_round_trip_time_ms_, rtt); + int64_t rtt_ms = now_ms - fb.latest_send_time_ms(); + min_round_trip_time_ms_ = std::min(min_round_trip_time_ms_, rtt_ms); - // Independent limits for those variables. + // Independent limits for AcceleratedRampUp conditions variables: + // x_n, d_tilde and x'_n in the original implementation, plus + // d_hat and receiving_rate in the modified one. // There should be no packet losses/marking, hence x_n == d_tilde. - if (fb.congestion_signal() == fb.est_queuing_delay_signal_ms() && - fb.est_queuing_delay_signal_ms() < kQueuingDelayUpperBoundMs && - fb.derivative() < kDerivativeUpperBound) { - AcceleratedRampUp(fb, kMaxRefRateKbps); + if (original_operating_mode_) { + // Original if conditions and rate update. + if (fb.congestion_signal() == fb.est_queuing_delay_signal_ms() && + fb.est_queuing_delay_signal_ms() < kQueuingDelayUpperBoundMs && + fb.derivative() < kDerivativeUpperBound) { + AcceleratedRampUp(fb); + } else { + GradualRateUpdate(fb, delta_s, 1.0); + } } else { - GradualRateUpdate(fb, kMaxRefRateKbps, delta_s); + // Modified if conditions and rate update; new ramp down mode. + if (fb.congestion_signal() == fb.est_queuing_delay_signal_ms() && + fb.est_queuing_delay_signal_ms() < kQueuingDelayUpperBoundMs && + fb.exp_smoothed_delay_ms() < + kMinRefRateKbps / kProportionalityDelayBits && + fb.derivative() < kDerivativeUpperBound && + fb.receiving_rate() > kMinRefRateKbps) { + AcceleratedRampUp(fb); + } else if (fb.congestion_signal() > kMaxCongestionSignalMs || + fb.exp_smoothed_delay_ms() > kMaxCongestionSignalMs) { + AcceleratedRampDown(fb); + } else { + double bitrate_reference = + (2.0 * bitrate_kbps_) / (kMaxRefRateKbps + kMinRefRateKbps); + double smoothing_factor = pow(bitrate_reference, 0.75); + GradualRateUpdate(fb, delta_s, smoothing_factor); + } } + + bitrate_kbps_ = std::min(bitrate_kbps_, kMaxRefRateKbps); + bitrate_kbps_ = std::max(bitrate_kbps_, kMinRefRateKbps); + + observer_->OnNetworkChanged(1000 * bitrate_kbps_, 0, rtt_ms); } int64_t NadaBweSender::TimeUntilNextProcess() { @@ -264,46 +341,46 @@ int NadaBweSender::Process() { return 0; } -void NadaBweSender::AcceleratedRampUp(const NadaFeedback& fb, - const int kMaxRefRateKbps) { +void NadaBweSender::AcceleratedRampUp(const NadaFeedback& fb) { const int kMaxRampUpQueuingDelayMs = 50; // Referred as T_th. const float kGamma0 = 0.5f; // Referred as gamma_0. float gamma = std::min(kGamma0, static_cast(kMaxRampUpQueuingDelayMs) / (min_round_trip_time_ms_ + min_feedback_delay_ms_)); - bitrate_kbps_ = static_cast((1.0f + gamma) * fb.receiving_rate() + 0.5f); - bitrate_kbps_ = std::min(bitrate_kbps_, kMaxRefRateKbps); - bitrate_kbps_ = std::max(bitrate_kbps_, kMinRefRateKbps); + bitrate_kbps_ = static_cast((1.0f + gamma) * fb.receiving_rate() + 0.5f); +} + +void NadaBweSender::AcceleratedRampDown(const NadaFeedback& fb) { + const float kGamma0 = 0.9f; + float gamma = 3.0f * kMaxCongestionSignalMs / + (fb.congestion_signal() + fb.exp_smoothed_delay_ms()); + gamma = std::min(gamma, kGamma0); + bitrate_kbps_ = gamma * fb.receiving_rate() + 0.5f; } void NadaBweSender::GradualRateUpdate(const NadaFeedback& fb, - const int kMaxRefRateKbps, - const float delta_s) { + float delta_s, + double smoothing_factor) { const float kTauOMs = 500.0f; // Referred as tau_o. const float kEta = 2.0f; // Referred as eta. const float kKappa = 1.0f; // Referred as kappa. const float kReferenceDelayMs = 10.0f; // Referred as x_ref. + const float kPriorityWeight = 1.0f; // Referred as w. - float kPriorityWeight = static_cast(fb.exp_smoothed_delay_ms()) / - kReferenceDelayMs; // Referred as w. + float x_hat = fb.congestion_signal() + kEta * kTauOMs * fb.derivative(); float kTheta = kPriorityWeight * (kMaxRefRateKbps - kMinRefRateKbps) * kReferenceDelayMs; - float x_hat = fb.congestion_signal() + kEta * kTauOMs * fb.derivative(); - bitrate_kbps_ = - bitrate_kbps_ + + int original_increase = static_cast((kKappa * delta_s * (kTheta - (bitrate_kbps_ - kMinRefRateKbps) * x_hat)) / (kTauOMs * kTauOMs) + 0.5f); - bitrate_kbps_ = std::min(bitrate_kbps_, kMaxRefRateKbps); - bitrate_kbps_ = std::max(bitrate_kbps_, kMinRefRateKbps); - - observer_->OnNetworkChanged(1000 * bitrate_kbps_, 0, 0); + bitrate_kbps_ = bitrate_kbps_ + smoothing_factor * original_increase; } void LinkedSet::Insert(uint16_t sequence_number, @@ -312,9 +389,13 @@ void LinkedSet::Insert(uint16_t sequence_number, size_t payload_size) { std::map::iterator it = map_.find(sequence_number); if (it != map_.end()) { - if (it->second != list_.begin()) { - list_.erase(it->second); - list_.push_front(*(it->second)); + PacketNodeIt node_it = it->second; + PacketIdentifierNode* node = *node_it; + node->arrival_time_ms = arrival_time_ms; + if (node_it != list_.begin()) { + list_.erase(node_it); + list_.push_front(node); + map_[sequence_number] = list_.begin(); } } else { if (size() == capacity_) { @@ -325,12 +406,12 @@ void LinkedSet::Insert(uint16_t sequence_number, } } void LinkedSet::RemoveTail() { - map_.erase(list_.back()->sequence_number_); + map_.erase(list_.back()->sequence_number); list_.pop_back(); } void LinkedSet::UpdateHead(PacketIdentifierNode* new_head) { list_.push_front(new_head); - map_[new_head->sequence_number_] = list_.begin(); + map_[new_head->sequence_number] = list_.begin(); } } // namespace bwe diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h index 097f74176..99e6c6a17 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h +++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h @@ -38,15 +38,15 @@ struct PacketIdentifierNode { int64_t send_time_ms, int64_t arrival_time_ms, size_t payload_size) - : sequence_number_(sequence_number), - send_time_ms_(send_time_ms), - arrival_time_ms_(arrival_time_ms), - payload_size_(payload_size) {} + : sequence_number(sequence_number), + send_time_ms(send_time_ms), + arrival_time_ms(arrival_time_ms), + payload_size(payload_size) {} - uint16_t sequence_number_; - int64_t send_time_ms_; - int64_t arrival_time_ms_; - size_t payload_size_; + uint16_t sequence_number; + int64_t send_time_ms; + int64_t arrival_time_ms; + size_t payload_size; }; typedef std::list::iterator PacketNodeIt; @@ -106,6 +106,14 @@ class NadaBweReceiver : public BweReceiver { int64_t last_smoothed_value, float alpha); + // With the assumption that packet loss is lower than 97%, the max gap + // between elements in the set is lower than 0x8000, hence we have a + // total order in the set. For (x,y,z) subset of the LinkedSet, + // (x<=y and y<=z) ==> x<=z so the set can be sorted. + static const int kSetCapacity = 1000; + static const int64_t kPacketLossTimeWindowMs = 500; + static const int64_t kReceivingRateTimeWindowMs = 500; + private: SimulatedClock clock_; int64_t last_feedback_ms_; @@ -116,8 +124,8 @@ class NadaBweReceiver : public BweReceiver { int last_delays_index_; int64_t exp_smoothed_delay_ms_; // Referred as d_hat_n. int64_t est_queuing_delay_signal_ms_; // Referred as d_tilde_n. + // Deals with packets sent more than once. - static const int kSetCapacity = 10000; // Lower than 0xFFFF / 2. LinkedSet* received_packets_ = new LinkedSet(kSetCapacity); static const int kMedian = 5; // Used for k-points Median Filter. int64_t last_delays_ms_[kMedian]; // Used for Median Filter. @@ -135,22 +143,36 @@ class NadaBweSender : public BweSender { void OnPacketsSent(const Packets& packets) override {} int64_t TimeUntilNextProcess() override; int Process() override; - void AcceleratedRampUp(const NadaFeedback& fb, const int kMaxRefRateKbps); + void AcceleratedRampUp(const NadaFeedback& fb); + void AcceleratedRampDown(const NadaFeedback& fb); void GradualRateUpdate(const NadaFeedback& fb, - const int kMaxRefRateKbps, - const float delta_s); + float delta_s, + double smoothing_factor); + + int bitrate_kbps() const { return bitrate_kbps_; } + void set_bitrate_kbps(int bitrate_kbps) { bitrate_kbps_ = bitrate_kbps; } + bool original_operating_mode() const { return original_operating_mode_; } + void set_original_operating_mode(bool original_operating_mode) { + original_operating_mode_ = original_operating_mode; + } + int64_t NowMs() const { return clock_->TimeInMilliseconds(); } + + static const int kMinRefRateKbps = 150; // Referred as R_min. + static const int kMaxRefRateKbps = 1500; // Referred as R_max. private: Clock* const clock_; BitrateObserver* const observer_; + // Used as an upper bound for calling AcceleratedRampDown. + const float kMaxCongestionSignalMs = 40.0f + kMinRefRateKbps / 15; // Referred as R_min, default initialization for bitrate R_n. - const int kMinRefRateKbps = 150; int bitrate_kbps_; // Referred as "Reference Rate" = R_n. int64_t last_feedback_ms_ = 0; // Referred as delta_0, initialized as an upper bound. - int64_t min_feedback_delay_ms_ = 5000; + int64_t min_feedback_delay_ms_ = 200; // Referred as RTT_0, initialized as an upper bound. - int64_t min_round_trip_time_ms_ = 500; + int64_t min_round_trip_time_ms_ = 100; + bool original_operating_mode_; DISALLOW_IMPLICIT_CONSTRUCTORS(NadaBweSender); }; diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada_unittest.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada_unittest.cc index 6bf4f8e69..300c800f5 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada_unittest.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada_unittest.cc @@ -8,12 +8,14 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include "webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h" + #include #include +#include "webrtc/base/common.h" #include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h" #include "webrtc/modules/remote_bitrate_estimator/test/packet.h" -#include "webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h" #include "testing/gtest/include/gtest/gtest.h" #include "webrtc/base/constructormagic.h" #include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h" @@ -23,67 +25,585 @@ namespace webrtc { namespace testing { namespace bwe { -class MedianFilterTest : public ::testing::Test { +class FilterTest : public ::testing::Test { public: - void FilterFromConstantArray() { - for (int i = 0; i < kSize; v[i++] = 200) { - } - for (int i = 0; i < kSize; ++i) { + void MedianFilterConstantArray() { + std::fill_n(raw_signal_, kNumElements, kSignalValue); + for (int i = 0; i < kNumElements; ++i) { int size = std::min(5, i + 1); - m_filtered[i] = NadaBweReceiver::MedianFilter(v + i + 1 - size, size); + median_filtered_[i] = + NadaBweReceiver::MedianFilter(&raw_signal_[i + 1 - size], size); } } - void FilterFromIntermittentNoiseArray() { + void MedianFilterIntermittentNoise() { const int kValue = 500; const int kNoise = 100; - for (int i = 0; i < kSize; i++) { - v[i] = kValue + kNoise * (i % 10 == 9 ? 1 : 0); + for (int i = 0; i < kNumElements; ++i) { + raw_signal_[i] = kValue + kNoise * (i % 10 == 9 ? 1 : 0); } - for (int i = 0; i < kSize; ++i) { + for (int i = 0; i < kNumElements; ++i) { int size = std::min(5, i + 1); - m_filtered[i] = NadaBweReceiver::MedianFilter(v + i + 1 - size, size); - EXPECT_EQ(m_filtered[i], kValue); + median_filtered_[i] = + NadaBweReceiver::MedianFilter(&raw_signal_[i + 1 - size], size); + EXPECT_EQ(median_filtered_[i], kValue); } } - protected: - static const int kSize = 1000; - int64_t v[kSize]; - int64_t m_filtered[kSize]; -}; - -class ExponentialSmoothingFilterTest : public ::testing::Test { - public: - void FilterFromConstantArray() { - for (int i = 0; i < kSize; v[i++] = 200) { - } + void ExponentialSmoothingFilter(const int64_t raw_signal_[], + int num_elements, + int64_t exp_smoothed[]) { exp_smoothed[0] = - NadaBweReceiver::ExponentialSmoothingFilter(v[0], -1, kAlpha); - - for (int i = 1; i < kSize; ++i) { + NadaBweReceiver::ExponentialSmoothingFilter(raw_signal_[0], -1, kAlpha); + for (int i = 1; i < num_elements; ++i) { exp_smoothed[i] = NadaBweReceiver::ExponentialSmoothingFilter( - v[i], exp_smoothed[i - 1], kAlpha); + raw_signal_[i], exp_smoothed[i - 1], kAlpha); } } + void ExponentialSmoothingConstantArray(int64_t exp_smoothed[]) { + std::fill_n(raw_signal_, kNumElements, kSignalValue); + ExponentialSmoothingFilter(raw_signal_, kNumElements, exp_smoothed); + } + protected: - static const int kSize = 1000; - const float kAlpha = 0.8f; - int64_t v[kSize]; - int64_t exp_smoothed[kSize]; + static const int kNumElements = 1000; + static const int64_t kSignalValue; + static const float kAlpha; + int64_t raw_signal_[kNumElements]; + int64_t median_filtered_[kNumElements]; }; -TEST_F(MedianFilterTest, ConstantArray) { - FilterFromConstantArray(); - for (int i = 0; i < kSize; ++i) { - EXPECT_TRUE(m_filtered[i] == v[i]); +const int64_t FilterTest::kSignalValue = 200; +const float FilterTest::kAlpha = 0.1f; + +class TestBitrateObserver : public BitrateObserver { + public: + TestBitrateObserver() + : last_bitrate_(0), last_fraction_loss_(0), last_rtt_(0) {} + + virtual void OnNetworkChanged(uint32_t bitrate, + uint8_t fraction_loss, + int64_t rtt) { + last_bitrate_ = bitrate; + last_fraction_loss_ = fraction_loss; + last_rtt_ = rtt; + } + uint32_t last_bitrate_; + uint8_t last_fraction_loss_; + int64_t last_rtt_; +}; + +class NadaSenderSideTest : public ::testing::Test { + public: + NadaSenderSideTest() + : observer_(), + simulated_clock_(0), + nada_sender_(&observer_, &simulated_clock_) {} + ~NadaSenderSideTest() {} + + private: + TestBitrateObserver observer_; + SimulatedClock simulated_clock_; + + protected: + NadaBweSender nada_sender_; +}; + +class NadaReceiverSideTest : public ::testing::Test { + protected: + NadaReceiverSideTest() : nada_receiver_(kFlowId) {} + ~NadaReceiverSideTest() {} + + const int kFlowId = 0; + NadaBweReceiver nada_receiver_; +}; + +class NadaFbGenerator { + public: + NadaFbGenerator(); + + static NadaFeedback NotCongestedFb(size_t receiving_rate, + int64_t ref_signal_ms, + int64_t send_time_ms) { + int64_t exp_smoothed_delay_ms = ref_signal_ms; + int64_t est_queuing_delay_signal_ms = ref_signal_ms; + int64_t congestion_signal_ms = ref_signal_ms; + float derivative = 0.0f; + return NadaFeedback(kFlowId, kNowMs, exp_smoothed_delay_ms, + est_queuing_delay_signal_ms, congestion_signal_ms, + derivative, receiving_rate, send_time_ms); + } + + static NadaFeedback CongestedFb(size_t receiving_rate, int64_t send_time_ms) { + int64_t exp_smoothed_delay_ms = 1000; + int64_t est_queuing_delay_signal_ms = 800; + int64_t congestion_signal_ms = 1000; + float derivative = 1.0f; + return NadaFeedback(kFlowId, kNowMs, exp_smoothed_delay_ms, + est_queuing_delay_signal_ms, congestion_signal_ms, + derivative, receiving_rate, send_time_ms); + } + + static NadaFeedback ExtremelyCongestedFb(size_t receiving_rate, + int64_t send_time_ms) { + int64_t exp_smoothed_delay_ms = 100000; + int64_t est_queuing_delay_signal_ms = 0; + int64_t congestion_signal_ms = 100000; + float derivative = 10000.0f; + return NadaFeedback(kFlowId, kNowMs, exp_smoothed_delay_ms, + est_queuing_delay_signal_ms, congestion_signal_ms, + derivative, receiving_rate, send_time_ms); + } + + private: + // Arbitrary values, won't change these test results. + static const int kFlowId = 2; + static const int64_t kNowMs = 1000; +}; + +// Verify if AcceleratedRampUp is called and that bitrate increases. +TEST_F(NadaSenderSideTest, AcceleratedRampUp) { + const int64_t kRefSignalMs = 3; + const int64_t kOneWayDelayMs = 50; + int original_bitrate = 2 * NadaBweSender::kMinRefRateKbps; + size_t receiving_rate = static_cast(original_bitrate); + int64_t send_time_ms = nada_sender_.NowMs() - kOneWayDelayMs; + + NadaFeedback not_congested_fb = NadaFbGenerator::NotCongestedFb( + receiving_rate, kRefSignalMs, send_time_ms); + + nada_sender_.set_original_operating_mode(true); + nada_sender_.set_bitrate_kbps(original_bitrate); + + // Trigger AcceleratedRampUp mode. + nada_sender_.GiveFeedback(not_congested_fb); + int bitrate_1_kbps = nada_sender_.bitrate_kbps(); + EXPECT_GT(bitrate_1_kbps, original_bitrate); + // Updates the bitrate according to the receiving rate and other constant + // parameters. + nada_sender_.AcceleratedRampUp(not_congested_fb); + EXPECT_EQ(nada_sender_.bitrate_kbps(), bitrate_1_kbps); + + nada_sender_.set_original_operating_mode(false); + nada_sender_.set_bitrate_kbps(original_bitrate); + // Trigger AcceleratedRampUp mode. + nada_sender_.GiveFeedback(not_congested_fb); + bitrate_1_kbps = nada_sender_.bitrate_kbps(); + EXPECT_GT(bitrate_1_kbps, original_bitrate); + nada_sender_.AcceleratedRampUp(not_congested_fb); + EXPECT_EQ(nada_sender_.bitrate_kbps(), bitrate_1_kbps); +} + +// Verify if AcceleratedRampDown is called and if bitrate decreases. +TEST_F(NadaSenderSideTest, AcceleratedRampDown) { + const int64_t kOneWayDelayMs = 50; + int original_bitrate = 3 * NadaBweSender::kMinRefRateKbps; + size_t receiving_rate = static_cast(original_bitrate); + int64_t send_time_ms = nada_sender_.NowMs() - kOneWayDelayMs; + + NadaFeedback congested_fb = + NadaFbGenerator::CongestedFb(receiving_rate, send_time_ms); + + nada_sender_.set_original_operating_mode(false); + nada_sender_.set_bitrate_kbps(original_bitrate); + nada_sender_.GiveFeedback(congested_fb); // Trigger AcceleratedRampDown mode. + int bitrate_1_kbps = nada_sender_.bitrate_kbps(); + EXPECT_LE(bitrate_1_kbps, original_bitrate * 0.9f + 0.5f); + EXPECT_LT(bitrate_1_kbps, original_bitrate); + + // Updates the bitrate according to the receiving rate and other constant + // parameters. + nada_sender_.AcceleratedRampDown(congested_fb); + int bitrate_2_kbps = + std::max(nada_sender_.bitrate_kbps(), NadaBweSender::kMinRefRateKbps); + EXPECT_EQ(bitrate_2_kbps, bitrate_1_kbps); +} + +TEST_F(NadaSenderSideTest, GradualRateUpdate) { + const int64_t kDeltaSMs = 20; + const int64_t kRefSignalMs = 20; + const int64_t kOneWayDelayMs = 50; + int original_bitrate = 2 * NadaBweSender::kMinRefRateKbps; + size_t receiving_rate = static_cast(original_bitrate); + int64_t send_time_ms = nada_sender_.NowMs() - kOneWayDelayMs; + + NadaFeedback congested_fb = + NadaFbGenerator::CongestedFb(receiving_rate, send_time_ms); + NadaFeedback not_congested_fb = NadaFbGenerator::NotCongestedFb( + original_bitrate, kRefSignalMs, send_time_ms); + + nada_sender_.set_bitrate_kbps(original_bitrate); + double smoothing_factor = 0.0; + nada_sender_.GradualRateUpdate(congested_fb, kDeltaSMs, smoothing_factor); + EXPECT_EQ(nada_sender_.bitrate_kbps(), original_bitrate); + + smoothing_factor = 1.0; + nada_sender_.GradualRateUpdate(congested_fb, kDeltaSMs, smoothing_factor); + EXPECT_LT(nada_sender_.bitrate_kbps(), original_bitrate); + + nada_sender_.set_bitrate_kbps(original_bitrate); + nada_sender_.GradualRateUpdate(not_congested_fb, kDeltaSMs, smoothing_factor); + EXPECT_GT(nada_sender_.bitrate_kbps(), original_bitrate); +} + +// Sending bitrate should decrease and reach its Min bound. +TEST_F(NadaSenderSideTest, VeryLowBandwith) { + const int64_t kOneWayDelayMs = 50; + const int kMin = NadaBweSender::kMinRefRateKbps; + size_t receiving_rate = static_cast(kMin); + int64_t send_time_ms = nada_sender_.NowMs() - kOneWayDelayMs; + + NadaFeedback extremely_congested_fb = + NadaFbGenerator::ExtremelyCongestedFb(receiving_rate, send_time_ms); + NadaFeedback congested_fb = + NadaFbGenerator::CongestedFb(receiving_rate, send_time_ms); + + nada_sender_.set_bitrate_kbps(5 * kMin); + nada_sender_.set_original_operating_mode(true); + for (int i = 0; i < 100; ++i) { + // Trigger GradualRateUpdate mode. + nada_sender_.GiveFeedback(extremely_congested_fb); + } + // The original implementation doesn't allow the bitrate to stay at kMin, + // even if the congestion signal is very high. + EXPECT_GE(nada_sender_.bitrate_kbps(), kMin); + + nada_sender_.set_original_operating_mode(false); + nada_sender_.set_bitrate_kbps(5 * kMin); + + for (int i = 0; i < 100; ++i) { + int previous_bitrate = nada_sender_.bitrate_kbps(); + // Trigger AcceleratedRampDown mode. + nada_sender_.GiveFeedback(congested_fb); + EXPECT_LE(nada_sender_.bitrate_kbps(), previous_bitrate); + } + EXPECT_EQ(nada_sender_.bitrate_kbps(), kMin); +} + +// Sending bitrate should increase and reach its Max bound. +TEST_F(NadaSenderSideTest, VeryHighBandwith) { + const int64_t kOneWayDelayMs = 50; + const int kMax = NadaBweSender::kMaxRefRateKbps; + const size_t kRecentReceivingRate = static_cast(kMax); + const int64_t kRefSignalMs = 5; + int64_t send_time_ms = nada_sender_.NowMs() - kOneWayDelayMs; + + NadaFeedback not_congested_fb = NadaFbGenerator::NotCongestedFb( + kRecentReceivingRate, kRefSignalMs, send_time_ms); + + nada_sender_.set_original_operating_mode(true); + for (int i = 0; i < 100; ++i) { + int previous_bitrate = nada_sender_.bitrate_kbps(); + nada_sender_.GiveFeedback(not_congested_fb); + EXPECT_GE(nada_sender_.bitrate_kbps(), previous_bitrate); + } + EXPECT_EQ(nada_sender_.bitrate_kbps(), kMax); + + nada_sender_.set_original_operating_mode(false); + nada_sender_.set_bitrate_kbps(NadaBweSender::kMinRefRateKbps); + + for (int i = 0; i < 100; ++i) { + int previous_bitrate = nada_sender_.bitrate_kbps(); + nada_sender_.GiveFeedback(not_congested_fb); + EXPECT_GE(nada_sender_.bitrate_kbps(), previous_bitrate); + } + EXPECT_EQ(nada_sender_.bitrate_kbps(), kMax); +} + +TEST_F(NadaReceiverSideTest, ReceivingRateNoPackets) { + EXPECT_EQ(nada_receiver_.RecentReceivingRate(), static_cast(0)); +} + +TEST_F(NadaReceiverSideTest, ReceivingRateSinglePacket) { + const size_t kPayloadSizeBytes = 500 * 1000; + const int64_t kSendTimeUs = 300 * 1000; + const int64_t kArrivalTimeMs = kSendTimeUs / 1000 + 100; + const uint16_t kSequenceNumber = 1; + const int64_t kTimeWindowMs = NadaBweReceiver::kReceivingRateTimeWindowMs; + + const MediaPacket media_packet(kFlowId, kSendTimeUs, kPayloadSizeBytes, + kSequenceNumber); + nada_receiver_.ReceivePacket(kArrivalTimeMs, media_packet); + + const size_t kReceivingRateKbps = 8 * kPayloadSizeBytes / kTimeWindowMs; + + EXPECT_EQ(nada_receiver_.RecentReceivingRate(), kReceivingRateKbps); +} + +TEST_F(NadaReceiverSideTest, ReceivingRateLargePackets) { + const size_t kPayloadSizeBytes = 3000 * 1000; + const int64_t kTimeGapMs = 3000; // Between each packet. + const int64_t kOneWayDelayMs = 1000; + + for (int i = 1; i < 5; ++i) { + int64_t send_time_us = i * kTimeGapMs * 1000; + int64_t arrival_time_ms = send_time_us / 1000 + kOneWayDelayMs; + uint16_t sequence_number = i; + const MediaPacket media_packet(kFlowId, send_time_us, kPayloadSizeBytes, + sequence_number); + nada_receiver_.ReceivePacket(arrival_time_ms, media_packet); + } + + const size_t kReceivingRateKbps = 8 * kPayloadSizeBytes / kTimeGapMs; + EXPECT_EQ(nada_receiver_.RecentReceivingRate(), kReceivingRateKbps); +} + +TEST_F(NadaReceiverSideTest, ReceivingRateSmallPackets) { + const size_t kPayloadSizeBytes = 100 * 1000; + const int64_t kTimeGapMs = 50; // Between each packet. + const int64_t kOneWayDelayMs = 50; + + for (int i = 1; i < 50; ++i) { + int64_t send_time_us = i * kTimeGapMs * 1000; + int64_t arrival_time_ms = send_time_us / 1000 + kOneWayDelayMs; + uint16_t sequence_number = i; + const MediaPacket media_packet(kFlowId, send_time_us, kPayloadSizeBytes, + sequence_number); + nada_receiver_.ReceivePacket(arrival_time_ms, media_packet); + } + + const size_t kReceivingRateKbps = 8 * kPayloadSizeBytes / kTimeGapMs; + EXPECT_EQ(nada_receiver_.RecentReceivingRate(), kReceivingRateKbps); +} + +TEST_F(NadaReceiverSideTest, ReceivingRateIntermittentPackets) { + const size_t kPayloadSizeBytes = 100 * 1000; + const int64_t kTimeGapMs = 50; // Between each packet. + const int64_t kFirstSendTimeMs = 0; + const int64_t kOneWayDelayMs = 50; + + // Gap between first and other packets + const MediaPacket media_packet(kFlowId, kFirstSendTimeMs, kPayloadSizeBytes, + 1); + nada_receiver_.ReceivePacket(kFirstSendTimeMs + kOneWayDelayMs, media_packet); + + const int64_t kDelayAfterFirstPacketMs = 1000; + const int kNumPackets = 5; // Small enough so that all packets are covered. + EXPECT_LT((kNumPackets - 2) * kTimeGapMs, + NadaBweReceiver::kReceivingRateTimeWindowMs); + const int64_t kTimeWindowMs = + kDelayAfterFirstPacketMs + (kNumPackets - 2) * kTimeGapMs; + + for (int i = 2; i <= kNumPackets; ++i) { + int64_t send_time_us = + ((i - 2) * kTimeGapMs + kFirstSendTimeMs + kDelayAfterFirstPacketMs) * + 1000; + int64_t arrival_time_ms = send_time_us / 1000 + kOneWayDelayMs; + uint16_t sequence_number = i; + const MediaPacket media_packet(kFlowId, send_time_us, kPayloadSizeBytes, + sequence_number); + nada_receiver_.ReceivePacket(arrival_time_ms, media_packet); + } + + const size_t kTotalReceivedKb = 8 * kNumPackets * kPayloadSizeBytes; + const int64_t kCorrectedTimeWindowMs = + (kTimeWindowMs * kNumPackets) / (kNumPackets - 1); + EXPECT_EQ(nada_receiver_.RecentReceivingRate(), + kTotalReceivedKb / kCorrectedTimeWindowMs); +} + +TEST_F(NadaReceiverSideTest, ReceivingRateDuplicatedPackets) { + const size_t kPayloadSizeBytes = 500 * 1000; + const int64_t kSendTimeUs = 300 * 1000; + const int64_t kArrivalTimeMs = kSendTimeUs / 1000 + 100; + const uint16_t kSequenceNumber = 1; + const int64_t kTimeWindowMs = NadaBweReceiver::kReceivingRateTimeWindowMs; + + // Insert the same packet twice. + for (int i = 0; i < 2; ++i) { + const MediaPacket media_packet(kFlowId, kSendTimeUs + 50 * i, + kPayloadSizeBytes, kSequenceNumber); + nada_receiver_.ReceivePacket(kArrivalTimeMs + 50 * i, media_packet); + } + // Should be counted only once. + const size_t kReceivingRateKbps = 8 * kPayloadSizeBytes / kTimeWindowMs; + + EXPECT_EQ(nada_receiver_.RecentReceivingRate(), kReceivingRateKbps); +} + +TEST_F(NadaReceiverSideTest, PacketLossNoPackets) { + EXPECT_EQ(nada_receiver_.RecentPacketLossRatio(), 0.0f); +} + +TEST_F(NadaReceiverSideTest, PacketLossSinglePacket) { + const MediaPacket media_packet(kFlowId, 0, 0, 0); + nada_receiver_.ReceivePacket(0, media_packet); + EXPECT_EQ(nada_receiver_.RecentPacketLossRatio(), 0.0f); +} + +TEST_F(NadaReceiverSideTest, PacketLossContiguousPackets) { + const int64_t kTimeWindowMs = NadaBweReceiver::kPacketLossTimeWindowMs; + const int kSetCapacity = NadaBweReceiver::kSetCapacity; + + for (int i = 0; i < 10; ++i) { + uint16_t sequence_number = static_cast(i); + // Sequence_number and flow_id are the only members that matter here. + const MediaPacket media_packet(kFlowId, 0, 0, sequence_number); + // Arrival time = 0, all packets will be considered. + nada_receiver_.ReceivePacket(0, media_packet); + } + EXPECT_EQ(nada_receiver_.RecentPacketLossRatio(), 0.0f); + + for (int i = 30; i > 20; i--) { + uint16_t sequence_number = static_cast(i); + // Sequence_number and flow_id are the only members that matter here. + const MediaPacket media_packet(kFlowId, 0, 0, sequence_number); + // Only the packets sent in this for loop will be considered. + nada_receiver_.ReceivePacket(2 * kTimeWindowMs, media_packet); + } + EXPECT_EQ(nada_receiver_.RecentPacketLossRatio(), 0.0f); + + // Should handle uint16_t overflow. + for (int i = 0xFFFF - 10; i < 0xFFFF + 10; ++i) { + uint16_t sequence_number = static_cast(i); + const MediaPacket media_packet(kFlowId, 0, 0, sequence_number); + // Only the packets sent in this for loop will be considered. + nada_receiver_.ReceivePacket(4 * kTimeWindowMs, media_packet); + } + EXPECT_EQ(nada_receiver_.RecentPacketLossRatio(), 0.0f); + + // Should handle set overflow. + for (int i = 0; i < kSetCapacity * 1.5; ++i) { + uint16_t sequence_number = static_cast(i); + const MediaPacket media_packet(kFlowId, 0, 0, sequence_number); + // Only the packets sent in this for loop will be considered. + nada_receiver_.ReceivePacket(6 * kTimeWindowMs, media_packet); + } + EXPECT_EQ(nada_receiver_.RecentPacketLossRatio(), 0.0f); +} + +// Should handle duplicates. +TEST_F(NadaReceiverSideTest, PacketLossDuplicatedPackets) { + const int64_t kTimeWindowMs = NadaBweReceiver::kPacketLossTimeWindowMs; + + for (int i = 0; i < 10; ++i) { + const MediaPacket media_packet(kFlowId, 0, 0, 0); + // Arrival time = 0, all packets will be considered. + nada_receiver_.ReceivePacket(0, media_packet); + } + EXPECT_EQ(nada_receiver_.RecentPacketLossRatio(), 0.0f); + + // Missing the element 5. + const uint16_t kSequenceNumbers[] = {1, 2, 3, 4, 6, 7, 8}; + const int kNumPackets = ARRAY_SIZE(kSequenceNumbers); + + // Insert each sequence number twice. + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < kNumPackets; j++) { + const MediaPacket media_packet(kFlowId, 0, 0, kSequenceNumbers[j]); + // Only the packets sent in this for loop will be considered. + nada_receiver_.ReceivePacket(2 * kTimeWindowMs, media_packet); + } + } + + EXPECT_NEAR(nada_receiver_.RecentPacketLossRatio(), 1.0f / (kNumPackets + 1), + 0.1f / (kNumPackets + 1)); +} + +TEST_F(NadaReceiverSideTest, PacketLossLakingPackets) { + const int kSetCapacity = NadaBweReceiver::kSetCapacity; + EXPECT_LT(kSetCapacity, 0xFFFF); + + // Missing every other packet. + for (int i = 0; i < kSetCapacity; ++i) { + if ((i & 1) == 0) { // Only even sequence numbers. + uint16_t sequence_number = static_cast(i); + const MediaPacket media_packet(kFlowId, 0, 0, sequence_number); + // Arrival time = 0, all packets will be considered. + nada_receiver_.ReceivePacket(0, media_packet); + } + } + EXPECT_NEAR(nada_receiver_.RecentPacketLossRatio(), 0.5f, 0.01f); +} + +TEST_F(NadaReceiverSideTest, PacketLossLakingFewPackets) { + const int kSetCapacity = NadaBweReceiver::kSetCapacity; + EXPECT_LT(kSetCapacity, 0xFFFF); + + const int kPeriod = 100; + // Missing one for each kPeriod packets. + for (int i = 0; i < kSetCapacity; ++i) { + if ((i % kPeriod) != 0) { + uint16_t sequence_number = static_cast(i); + const MediaPacket media_packet(kFlowId, 0, 0, sequence_number); + // Arrival time = 0, all packets will be considered. + nada_receiver_.ReceivePacket(0, media_packet); + } + } + EXPECT_NEAR(nada_receiver_.RecentPacketLossRatio(), 1.0f / kPeriod, + 0.1f / kPeriod); +} + +// Packet's sequence numbers greatly apart, expect high loss. +TEST_F(NadaReceiverSideTest, PacketLossWideGap) { + const int64_t kTimeWindowMs = NadaBweReceiver::kPacketLossTimeWindowMs; + + const MediaPacket media_packet1(0, 0, 0, 1); + const MediaPacket media_packet2(0, 0, 0, 1000); + // Only these two packets will be considered. + nada_receiver_.ReceivePacket(0, media_packet1); + nada_receiver_.ReceivePacket(0, media_packet2); + EXPECT_NEAR(nada_receiver_.RecentPacketLossRatio(), 0.998f, 0.0001f); + + const MediaPacket media_packet3(0, 0, 0, 0); + const MediaPacket media_packet4(0, 0, 0, 0x8000); + // Only these two packets will be considered. + nada_receiver_.ReceivePacket(2 * kTimeWindowMs, media_packet3); + nada_receiver_.ReceivePacket(2 * kTimeWindowMs, media_packet4); + EXPECT_NEAR(nada_receiver_.RecentPacketLossRatio(), 0.99994f, 0.00001f); +} + +// Packets arriving unordered should not be counted as losted. +TEST_F(NadaReceiverSideTest, PacketLossUnorderedPackets) { + const int kNumPackets = NadaBweReceiver::kSetCapacity / 2; + std::vector sequence_numbers; + + for (int i = 0; i < kNumPackets; ++i) { + sequence_numbers.push_back(static_cast(i + 1)); + } + + random_shuffle(sequence_numbers.begin(), sequence_numbers.end()); + + for (int i = 0; i < kNumPackets; ++i) { + const MediaPacket media_packet(kFlowId, 0, 0, sequence_numbers[i]); + // Arrival time = 0, all packets will be considered. + nada_receiver_.ReceivePacket(0, media_packet); + } + + EXPECT_EQ(nada_receiver_.RecentPacketLossRatio(), 0.0f); +} + +TEST_F(FilterTest, MedianConstantArray) { + MedianFilterConstantArray(); + for (int i = 0; i < kNumElements; ++i) { + EXPECT_EQ(median_filtered_[i], raw_signal_[i]); } } -TEST_F(MedianFilterTest, IntermittentNoise) { - FilterFromIntermittentNoiseArray(); +TEST_F(FilterTest, MedianIntermittentNoise) { + MedianFilterIntermittentNoise(); +} + +TEST_F(FilterTest, ExponentialSmoothingConstantArray) { + int64_t exp_smoothed[kNumElements]; + ExponentialSmoothingConstantArray(exp_smoothed); + for (int i = 0; i < kNumElements; ++i) { + EXPECT_EQ(exp_smoothed[i], kSignalValue); + } +} + +TEST_F(FilterTest, ExponentialSmoothingInitialPertubation) { + const int64_t kSignal[] = {90000, 0, 0, 0, 0, 0}; + const int kNumElements = ARRAY_SIZE(kSignal); + int64_t exp_smoothed[kNumElements]; + ExponentialSmoothingFilter(kSignal, kNumElements, exp_smoothed); + for (int i = 1; i < kNumElements; ++i) { + EXPECT_EQ( + exp_smoothed[i], + static_cast(exp_smoothed[i - 1] * (1.0f - kAlpha) + 0.5f)); + } } } // namespace bwe