diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator.gypi b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator.gypi index 40c1a62db..dd929f2bf 100644 --- a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator.gypi +++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator.gypi @@ -82,6 +82,7 @@ 'test/packet.h', 'test/estimators/nada.cc', 'test/estimators/nada.h', + 'test/estimators/nada_unittest.cc', 'test/estimators/remb.cc', 'test/estimators/remb.h', 'test/estimators/send_side.cc', diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc index f9df06fdb..4beb34d33 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc @@ -178,9 +178,10 @@ void MediaPacket::SetAbsSendTimeMs(int64_t abs_send_time_ms) { RembFeedback::RembFeedback(int flow_id, int64_t send_time_us, + int64_t last_send_time_ms, uint32_t estimated_bps, RTCPReportBlock report_block) - : FeedbackPacket(flow_id, send_time_us), + : FeedbackPacket(flow_id, send_time_us, last_send_time_ms), estimated_bps_(estimated_bps), report_block_(report_block) { } @@ -188,8 +189,9 @@ RembFeedback::RembFeedback(int flow_id, SendSideBweFeedback::SendSideBweFeedback( int flow_id, int64_t send_time_us, + int64_t last_send_time_ms, const std::vector& packet_feedback_vector) - : FeedbackPacket(flow_id, send_time_us), + : FeedbackPacket(flow_id, send_time_us, last_send_time_ms), packet_feedback_vector_(packet_feedback_vector) { } diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc index 9ed29b532..dd7bbfcfe 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc @@ -14,11 +14,10 @@ #include "testing/gtest/include/gtest/gtest.h" #include "webrtc/base/constructormagic.h" +#include "webrtc/modules/remote_bitrate_estimator/test/packet.h" #include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h" #include "webrtc/test/testsupport/fileutils.h" -using std::vector; - namespace webrtc { namespace testing { namespace bwe { @@ -947,7 +946,7 @@ TEST(BweTestFramework_VideoSenderTest, FeedbackIneffective) { TestVideoSender(&sender, 9998, 1000, 500, 1025000); // Make sure feedback has no effect on a regular video sender. - RembFeedback* feedback = new RembFeedback(0, 0, 512000, RTCPReportBlock()); + RembFeedback* feedback = new RembFeedback(0, 0, 0, 512000, RTCPReportBlock()); Packets packets; packets.push_back(feedback); sender.RunFor(0, &packets); @@ -962,7 +961,7 @@ TEST(BweTestFramework_AdaptiveVideoSenderTest, FeedbackChangesBitrate) { TestVideoSender(&sender, 9998, 1000, 500, 1025000); // Make sure we can reduce the bitrate. - RembFeedback* feedback = new RembFeedback(0, 0, 512000, RTCPReportBlock()); + RembFeedback* feedback = new RembFeedback(0, 0, 0, 512000, RTCPReportBlock()); Packets packets; packets.push_back(feedback); sender.RunFor(0, &packets); @@ -971,7 +970,7 @@ TEST(BweTestFramework_AdaptiveVideoSenderTest, FeedbackChangesBitrate) { // Increase the bitrate to the initial bitrate and verify that the output is // the same. - feedback = new RembFeedback(0, 0, 820000, RTCPReportBlock()); + feedback = new RembFeedback(0, 0, 0, 820000, RTCPReportBlock()); packets.push_back(feedback); sender.RunFor(10000, &packets); EXPECT_EQ(820000u, source.bits_per_second()); @@ -987,7 +986,7 @@ TEST(BweTestFramework_AdaptiveVideoSenderTest, Paced_FeedbackChangesBitrate) { TestVideoSender(&sender, 9998, 1000, 500, 1025000); // Make sure we can reduce the bitrate. - RembFeedback* feedback = new RembFeedback(0, 1, 512000, RTCPReportBlock()); + RembFeedback* feedback = new RembFeedback(0, 1, 0, 512000, RTCPReportBlock()); Packets packets; packets.push_back(feedback); sender.RunFor(10000, &packets); @@ -996,7 +995,7 @@ TEST(BweTestFramework_AdaptiveVideoSenderTest, Paced_FeedbackChangesBitrate) { // Increase the bitrate to the initial bitrate and verify that the output is // the same. - feedback = new RembFeedback(0, 0, 820000, RTCPReportBlock()); + feedback = new RembFeedback(0, 0, 0, 820000, RTCPReportBlock()); packets.push_back(feedback); sender.RunFor(10000, &packets); EXPECT_EQ(820000u, source.bits_per_second()); diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc index 32f772c02..eb9381f32 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc @@ -6,18 +6,28 @@ * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. + * */ +// Implementation of Network-Assisted Dynamic Adaptation's (NADA's) proposal. +// Version according to Draft Document (mentioned in references) +// http://tools.ietf.org/html/draft-zhu-rmcat-nada-06 +// From March 26, 2015. + +#include #include +#include +#include #include "webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h" - #include "webrtc/modules/rtp_rtcp/interface/receive_statistics.h" namespace webrtc { namespace testing { namespace bwe { +const int NadaBweReceiver::kMedian; + NadaBweReceiver::NadaBweReceiver(int flow_id) : BweReceiver(flow_id), clock_(0), @@ -25,7 +35,10 @@ NadaBweReceiver::NadaBweReceiver(int flow_id) recv_stats_(ReceiveStatistics::Create(&clock_)), baseline_delay_ms_(0), delay_signal_ms_(0), - last_congestion_signal_ms_(0) { + last_congestion_signal_ms_(0), + last_delays_index_(0), + exp_smoothed_delay_ms_(-1), + est_queuing_delay_signal_ms_(0) { } NadaBweReceiver::~NadaBweReceiver() { @@ -33,34 +46,51 @@ NadaBweReceiver::~NadaBweReceiver() { void NadaBweReceiver::ReceivePacket(int64_t arrival_time_ms, const MediaPacket& media_packet) { + const float kAlpha = 0.9f; // Used for exponential smoothing. + const int64_t kDelayLowThresholdMs = 50; // Referred as d_th. + const int64_t kDelayMaxThresholdMs = 400; // Referred as d_max. + clock_.AdvanceTimeMilliseconds(arrival_time_ms - clock_.TimeInMilliseconds()); recv_stats_->IncomingPacket(media_packet.header(), media_packet.payload_size(), false); - int64_t delay_ms = arrival_time_ms - media_packet.creation_time_us() / 1000; - // TODO(holmer): The min should time out after 10 minutes. - if (delay_ms < baseline_delay_ms_) { - baseline_delay_ms_ = delay_ms; + int64_t delay_ms = arrival_time_ms - + media_packet.creation_time_us() / 1000; // Refered as x_n. + // The min should be updated within the first 10 minutes. + if (clock_.TimeInMilliseconds() < 10 * 60 * 1000) { + baseline_delay_ms_ = std::min(baseline_delay_ms_, delay_ms); } - delay_signal_ms_ = delay_ms - baseline_delay_ms_; + delay_signal_ms_ = delay_ms - baseline_delay_ms_; // Refered as d_n. + last_delays_ms_[(last_delays_index_++) % kMedian] = delay_signal_ms_; + int size = std::min(last_delays_index_, kMedian); + int64_t median_filtered_delay_ms_ = MedianFilter(last_delays_ms_, size); + exp_smoothed_delay_ms_ = ExponentialSmoothingFilter( + median_filtered_delay_ms_, exp_smoothed_delay_ms_, kAlpha); + + if (exp_smoothed_delay_ms_ < kDelayLowThresholdMs) { + 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)); + } else { + est_queuing_delay_signal_ms_ = 0; + } + + received_packets_->Insert(media_packet.sequence_number(), + media_packet.send_time_ms(), arrival_time_ms, + media_packet.payload_size()); } FeedbackPacket* NadaBweReceiver::GetFeedback(int64_t now_ms) { - if (now_ms - last_feedback_ms_ < 100) - return NULL; + const int64_t kPacketLossPenaltyMs = 1000; // Referred as d_L. - StatisticianMap statisticians = recv_stats_->GetActiveStatisticians(); - int64_t loss_signal_ms = 0.0f; - if (!statisticians.empty()) { - RtcpStatistics stats; - if (!statisticians.begin()->second->GetStatistics(&stats, true)) { - const float kLossSignalWeight = 1000.0f; - loss_signal_ms = - (kLossSignalWeight * static_cast(stats.fraction_lost) + 127) / - 255; - } + if (now_ms - last_feedback_ms_ < 100) { + return NULL; } - int64_t congestion_signal_ms = delay_signal_ms_ + loss_signal_ms; + int64_t loss_signal_ms = static_cast( + RecentPacketLossRatio() * kPacketLossPenaltyMs + 0.5f); + int64_t congestion_signal_ms = est_queuing_delay_signal_ms_ + loss_signal_ms; float derivative = 0.0f; if (last_feedback_ms_ > 0) { @@ -69,14 +99,124 @@ FeedbackPacket* NadaBweReceiver::GetFeedback(int64_t now_ms) { } last_feedback_ms_ = now_ms; last_congestion_signal_ms_ = congestion_signal_ms; - return new NadaFeedback(flow_id_, now_ms, congestion_signal_ms, derivative); + + PacketIdentifierNode* latest = *(received_packets_->begin()); + int64_t corrected_send_time_ms = + latest->send_time_ms_ + now_ms - latest->arrival_time_ms_; + + // Sends a tuple containing latest values of and additional information. + return new NadaFeedback(flow_id_, now_ms, exp_smoothed_delay_ms_, + est_queuing_delay_signal_ms_, congestion_signal_ms, + derivative, RecentReceivingRate(), + corrected_send_time_ms); +} + +float NadaBweReceiver::GlobalPacketLossRatio() { + if (received_packets_->empty()) { + return 0.0f; + } + // Possibly there are packets missing. + const uint16_t kMaxGap = 1.5 * kSetCapacity; + uint16_t min = received_packets_->find_min(); + uint16_t max = received_packets_->find_max(); + + int gap; + if (max - min < kMaxGap) { + gap = max - min + 1; + } else { // There was an overflow. + max = received_packets_->upper_bound(kMaxGap); + min = received_packets_->lower_bound(0xFFFF - kMaxGap); + gap = max + (0xFFFF - min) + 2; + } + return static_cast(received_packets_->size()) / gap; +} + +// Go through a fixed time window of most recent packets received and +// counts packets missing to obtain the packet loss ratio. If an unordered +// packet falls out of the timewindow it will be counted as missing. +// 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; + } + int number_packets_received = 0; + + 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; + // Oldest and newest values found within the given time window. + 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) { + break; + } + uint16_t seq_nb = (*node_it)->sequence_number_; + if (IsNewerSequenceNumber(seq_nb, newest_seq_nb)) { + newest_seq_nb = seq_nb; + } + if (IsNewerSequenceNumber(oldest_seq_nb, seq_nb)) { + oldest_seq_nb = seq_nb; + } + ++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); + + return static_cast(gap - number_packets_received) / gap; +} + +size_t NadaBweReceiver::RecentReceivingRate() { + const int64_t kRecentTimeWindowMs = 500; + if (received_packets_->empty()) { + return 0.0f; + } + size_t totalSize = 0; + int64_t time_limit_ms = clock_.TimeInMilliseconds() - kRecentTimeWindowMs; + PacketNodeIt node_it = received_packets_->begin(); + PacketNodeIt end = received_packets_->end(); + + while (node_it != end && (*node_it)->arrival_time_ms_ > time_limit_ms) { + totalSize += (*node_it)->payload_size_; + ++node_it; + } + + return static_cast((1000 * totalSize) / kRecentTimeWindowMs); +} + +int64_t NadaBweReceiver::MedianFilter(int64_t* last_delays_ms, int size) { + // 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()); + return array_copy.at(size / 2); +} + +int64_t NadaBweReceiver::ExponentialSmoothingFilter(int64_t new_value, + int64_t last_smoothed_value, + float alpha) { + if (last_smoothed_value < 0) { + return new_value; // Handling initial case. + } + return static_cast(alpha * new_value + + (1.0f - alpha) * last_smoothed_value + 0.5f); } NadaBweSender::NadaBweSender(int kbps, BitrateObserver* observer, Clock* clock) - : clock_(clock), - observer_(observer), - bitrate_kbps_(kbps), - last_feedback_ms_(0) { + : clock_(clock), observer_(observer), bitrate_kbps_(kbps) { +} + +NadaBweSender::NadaBweSender(BitrateObserver* observer, Clock* clock) + : clock_(clock), observer_(observer), bitrate_kbps_(kMinRefRateKbps) { } NadaBweSender::~NadaBweSender() { @@ -88,31 +228,32 @@ 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_; - // TODO(holmer): Implement special start-up behavior. - - const float kEta = 2.0f; - const float kTaoO = 500.0f; - float x_hat = fb.congestion_signal() + kEta * kTaoO * fb.derivative(); + const int kMaxRefRateKbps = 1500; // Referred as R_max. int64_t now_ms = clock_->TimeInMilliseconds(); float delta_s = now_ms - last_feedback_ms_; last_feedback_ms_ = now_ms; + // Update delta_0. + min_feedback_delay_ms_ = + std::min(min_feedback_delay_ms_, static_cast(delta_s)); - const float kPriorityWeight = 1.0f; - const float kReferenceDelayS = 10.0f; - float kTheta = - kPriorityWeight * (kMaxBitrateKbps - kMinBitrateKbps) * kReferenceDelayS; + // 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); - const float kKappa = 1.0f; - bitrate_kbps_ = bitrate_kbps_ + - kKappa * delta_s / (kTaoO * kTaoO) * - (kTheta - (bitrate_kbps_ - kMinBitrateKbps) * x_hat) + - 0.5f; - bitrate_kbps_ = std::min(bitrate_kbps_, kMaxBitrateKbps); - bitrate_kbps_ = std::max(bitrate_kbps_, kMinBitrateKbps); - - observer_->OnNetworkChanged(1000 * bitrate_kbps_, 0, 0); + // Independent limits for those variables. + // 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); + } else { + GradualRateUpdate(fb, kMaxRefRateKbps, delta_s); + } } int64_t NadaBweSender::TimeUntilNextProcess() { @@ -123,6 +264,75 @@ int NadaBweSender::Process() { return 0; } +void NadaBweSender::AcceleratedRampUp(const NadaFeedback& fb, + const int kMaxRefRateKbps) { + 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); +} + +void NadaBweSender::GradualRateUpdate(const NadaFeedback& fb, + const int kMaxRefRateKbps, + const float delta_s) { + 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. + + float kPriorityWeight = static_cast(fb.exp_smoothed_delay_ms()) / + kReferenceDelayMs; // Referred as w. + + float kTheta = + kPriorityWeight * (kMaxRefRateKbps - kMinRefRateKbps) * kReferenceDelayMs; + float x_hat = fb.congestion_signal() + kEta * kTauOMs * fb.derivative(); + + bitrate_kbps_ = + bitrate_kbps_ + + 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); +} + +void LinkedSet::Insert(uint16_t sequence_number, + int64_t send_time_ms, + int64_t arrival_time_ms, + 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)); + } + } else { + if (size() == capacity_) { + RemoveTail(); + } + UpdateHead(new PacketIdentifierNode(sequence_number, send_time_ms, + arrival_time_ms, payload_size)); + } +} +void LinkedSet::RemoveTail() { + 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(); +} + } // namespace bwe } // namespace testing } // namespace webrtc diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h index 14cc8a273..097f74176 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h +++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h @@ -6,12 +6,23 @@ * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. - */ + * +*/ + +// Implementation of Network-Assisted Dynamic Adaptation's (NADA's) proposal +// Version according to Draft Document (mentioned in references) +// http://tools.ietf.org/html/draft-zhu-rmcat-nada-06 +// From March 26, 2015. #ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_NADA_H_ #define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_NADA_H_ +#include +#include + +#include "webrtc/modules/interface/module_common_types.h" #include "webrtc/modules/remote_bitrate_estimator/test/bwe.h" +#include "webrtc/voice_engine/channel.h" namespace webrtc { @@ -20,6 +31,65 @@ class ReceiveStatistics; namespace testing { namespace bwe { +// Holds only essential information about packets to be saved for +// further use, e.g. for calculating packet loss and receiving rate. +struct PacketIdentifierNode { + PacketIdentifierNode(uint16_t sequence_number, + 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) {} + + uint16_t sequence_number_; + int64_t send_time_ms_; + int64_t arrival_time_ms_; + size_t payload_size_; +}; + +typedef std::list::iterator PacketNodeIt; + +// FIFO implementation for a limited capacity set. +// Used for keeping the latest arrived packets while avoiding duplicates. +// Allows efficient insertion, deletion and search. +class LinkedSet { + public: + explicit LinkedSet(size_t capacity) : capacity_(capacity) {} + + // If the arriving packet (identified by its sequence number) is already + // in the LinkedSet, move its Node to the head of the list. Else, create + // a PacketIdentifierNode n_ and then UpdateHead(n_), calling RemoveTail() + // if the LinkedSet reached its maximum capacity. + void Insert(uint16_t sequence_number, + int64_t send_time_ms, + int64_t arrival_time_ms, + size_t payload_size); + + PacketNodeIt begin() { return list_.begin(); } + PacketNodeIt end() { return list_.end(); } + bool empty() { return list_.empty(); } + size_t size() { return list_.size(); } + // Gets the latest arrived sequence number. + uint16_t find_max() { return map_.rbegin()->first; } + // Gets the first arrived sequence number still saved in the LinkedSet. + uint16_t find_min() { return map_.begin()->first; } + // Gets the lowest saved sequence number that is >= than the input key. + uint16_t lower_bound(uint16_t key) { return map_.lower_bound(key)->first; } + // Gets the highest saved sequence number that is <= than the input key. + uint16_t upper_bound(uint16_t key) { return map_.upper_bound(key)->first; } + + private: + // Pop oldest element from the back of the list and remove it from the map. + void RemoveTail(); + // Add new element to the front of the list and insert it in the map. + void UpdateHead(PacketIdentifierNode* new_head); + size_t capacity_; + std::map map_; + std::list list_; +}; + class NadaBweReceiver : public BweReceiver { public: explicit NadaBweReceiver(int flow_id); @@ -28,32 +98,59 @@ class NadaBweReceiver : public BweReceiver { void ReceivePacket(int64_t arrival_time_ms, const MediaPacket& media_packet) override; FeedbackPacket* GetFeedback(int64_t now_ms) override; + float GlobalPacketLossRatio(); + float RecentPacketLossRatio(); + size_t RecentReceivingRate(); + static int64_t MedianFilter(int64_t* v, int size); + static int64_t ExponentialSmoothingFilter(int64_t new_value, + int64_t last_smoothed_value, + float alpha); private: SimulatedClock clock_; int64_t last_feedback_ms_; rtc::scoped_ptr recv_stats_; - int64_t baseline_delay_ms_; - int64_t delay_signal_ms_; + int64_t baseline_delay_ms_; // Referred as d_f. + int64_t delay_signal_ms_; // Referred as d_n. int64_t last_congestion_signal_ms_; + 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. }; class NadaBweSender : public BweSender { public: NadaBweSender(int kbps, BitrateObserver* observer, Clock* clock); + NadaBweSender(BitrateObserver* observer, Clock* clock); virtual ~NadaBweSender(); int GetFeedbackIntervalMs() const override; + // Updates the min_feedback_delay_ms_ and the min_round_trip_time_ms_. void GiveFeedback(const FeedbackPacket& feedback) override; void OnPacketsSent(const Packets& packets) override {} int64_t TimeUntilNextProcess() override; int Process() override; + void AcceleratedRampUp(const NadaFeedback& fb, const int kMaxRefRateKbps); + void GradualRateUpdate(const NadaFeedback& fb, + const int kMaxRefRateKbps, + const float delta_s); private: Clock* const clock_; BitrateObserver* const observer_; - int bitrate_kbps_; - int64_t last_feedback_ms_; + // 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; + // Referred as RTT_0, initialized as an upper bound. + int64_t min_round_trip_time_ms_ = 500; 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 new file mode 100644 index 000000000..6bf4f8e69 --- /dev/null +++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada_unittest.cc @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "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" +#include "webrtc/test/testsupport/fileutils.h" + +namespace webrtc { +namespace testing { +namespace bwe { + +class MedianFilterTest : public ::testing::Test { + public: + void FilterFromConstantArray() { + for (int i = 0; i < kSize; v[i++] = 200) { + } + for (int i = 0; i < kSize; ++i) { + int size = std::min(5, i + 1); + m_filtered[i] = NadaBweReceiver::MedianFilter(v + i + 1 - size, size); + } + } + + void FilterFromIntermittentNoiseArray() { + 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 < kSize; ++i) { + int size = std::min(5, i + 1); + m_filtered[i] = NadaBweReceiver::MedianFilter(v + i + 1 - size, size); + EXPECT_EQ(m_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) { + } + exp_smoothed[0] = + NadaBweReceiver::ExponentialSmoothingFilter(v[0], -1, kAlpha); + + for (int i = 1; i < kSize; ++i) { + exp_smoothed[i] = NadaBweReceiver::ExponentialSmoothingFilter( + v[i], exp_smoothed[i - 1], kAlpha); + } + } + + protected: + static const int kSize = 1000; + const float kAlpha = 0.8f; + int64_t v[kSize]; + int64_t exp_smoothed[kSize]; +}; + +TEST_F(MedianFilterTest, ConstantArray) { + FilterFromConstantArray(); + for (int i = 0; i < kSize; ++i) { + EXPECT_TRUE(m_filtered[i] == v[i]); + } +} + +TEST_F(MedianFilterTest, IntermittentNoise) { + FilterFromIntermittentNoiseArray(); +} + +} // namespace bwe +} // namespace testing +} // namespace webrtc diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.cc index a61b5239d..d1ba46a14 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.cc @@ -111,8 +111,10 @@ FeedbackPacket* RembReceiver::GetFeedback(int64_t now_ms) { if (!statisticians.empty()) { report_block = BuildReportBlock(statisticians.begin()->second); } - feedback = - new RembFeedback(flow_id_, now_ms * 1000, estimated_bps, report_block); + + feedback = new RembFeedback(flow_id_, now_ms * 1000, last_feedback_ms_, + estimated_bps, report_block); + last_feedback_ms_ = now_ms; double estimated_kbps = static_cast(estimated_bps) / 1000.0; RTC_UNUSED(estimated_kbps); diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h b/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h index 753f15259..93279e660 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h +++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h @@ -70,6 +70,7 @@ class RembReceiver : public BweReceiver, public RemoteBitrateObserver { SimulatedClock clock_; rtc::scoped_ptr recv_stats_; int64_t latest_estimate_bps_; + int64_t last_feedback_ms_; rtc::scoped_ptr estimator_; DISALLOW_IMPLICIT_CONSTRUCTORS(RembReceiver); diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.cc index b33534b5d..1e2352e00 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.cc @@ -126,9 +126,10 @@ void SendSideBweReceiver::ReceivePacket(int64_t arrival_time_ms, FeedbackPacket* SendSideBweReceiver::GetFeedback(int64_t now_ms) { if (now_ms - last_feedback_ms_ < 100) return NULL; + int64_t latest_send_time_ms = last_feedback_ms_; last_feedback_ms_ = now_ms; - FeedbackPacket* fb = - new SendSideBweFeedback(flow_id_, now_ms * 1000, packet_feedback_vector_); + FeedbackPacket* fb = new SendSideBweFeedback( + flow_id_, now_ms * 1000, latest_send_time_ms, packet_feedback_vector_); packet_feedback_vector_.clear(); return fb; } diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.cc index 1312fe36d..6b125564e 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.cc @@ -35,10 +35,9 @@ void TcpBweReceiver::ReceivePacket(int64_t arrival_time_ms, } FeedbackPacket* TcpBweReceiver::GetFeedback(int64_t now_ms) { - // if (now_ms - last_feedback_ms_ < 100) - // return NULL; + FeedbackPacket* fb = + new TcpFeedback(flow_id_, now_ms * 1000, last_feedback_ms_, acks_); last_feedback_ms_ = now_ms; - FeedbackPacket* fb = new TcpFeedback(flow_id_, now_ms * 1000, acks_); acks_.clear(); return fb; } diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet.h b/webrtc/modules/remote_bitrate_estimator/test/packet.h index f4e9c12a1..647e357f9 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/packet.h +++ b/webrtc/modules/remote_bitrate_estimator/test/packet.h @@ -39,12 +39,17 @@ class Packet { virtual int64_t send_time_us() const { return send_time_us_; } virtual size_t payload_size() const { return payload_size_; } virtual Packet::Type GetPacketType() const = 0; + void set_sender_timestamp_us(int64_t sender_timestamp_us) { + sender_timestamp_us_ = sender_timestamp_us; + } + int64_t sender_timestamp_us() const { return sender_timestamp_us_; } - private: + protected: int flow_id_; int64_t creation_time_us_; // Time when the packet was created. int64_t send_time_us_; // Time the packet left last processor touching it. - size_t payload_size_; // Size of the (non-existent, simulated) payload. + int64_t sender_timestamp_us_; // Time the packet left the Sender. + size_t payload_size_; // Size of the (non-existent, simulated) payload. }; class MediaPacket : public Packet { @@ -69,6 +74,8 @@ class MediaPacket : public Packet { void SetAbsSendTimeMs(int64_t abs_send_time_ms); const RTPHeader& header() const { return header_; } virtual Packet::Type GetPacketType() const { return kMedia; } + uint16_t sequence_number() const { return header_.sequenceNumber; } + int64_t send_time_ms() const { return send_time_us_ / 1000; } private: static const int kAbsSendTimeFraction = 18; @@ -81,17 +88,25 @@ class MediaPacket : public Packet { class FeedbackPacket : public Packet { public: - FeedbackPacket(int flow_id, int64_t send_time_us) - : Packet(flow_id, send_time_us, 0) {} + FeedbackPacket(int flow_id, + int64_t this_send_time_us, + int64_t latest_send_time_ms) + : Packet(flow_id, this_send_time_us, 0), + latest_send_time_ms_(latest_send_time_ms) {} virtual ~FeedbackPacket() {} virtual Packet::Type GetPacketType() const { return kFeedback; } + int64_t latest_send_time_ms() const { return latest_send_time_ms_; } + + private: + int64_t latest_send_time_ms_; // Time stamp for the latest sent packet. }; class RembFeedback : public FeedbackPacket { public: RembFeedback(int flow_id, int64_t send_time_us, + int64_t latest_send_time_ms, uint32_t estimated_bps, RTCPReportBlock report_block); virtual ~RembFeedback() {} @@ -109,6 +124,7 @@ class SendSideBweFeedback : public FeedbackPacket { typedef std::map ArrivalTimesMap; SendSideBweFeedback(int flow_id, int64_t send_time_us, + int64_t latest_send_time_ms, const std::vector& packet_feedback_vector); virtual ~SendSideBweFeedback() {} @@ -123,28 +139,45 @@ class SendSideBweFeedback : public FeedbackPacket { class NadaFeedback : public FeedbackPacket { public: NadaFeedback(int flow_id, - int64_t send_time_us, + int64_t this_send_time_us, + int64_t exp_smoothed_delay_ms, + int64_t est_queuing_delay_signal_ms, int64_t congestion_signal, - float derivative) - : FeedbackPacket(flow_id, send_time_us), + float derivative, + float receiving_rate, + int64_t latest_send_time_ms) + : FeedbackPacket(flow_id, this_send_time_us, latest_send_time_ms), + exp_smoothed_delay_ms_(exp_smoothed_delay_ms), + est_queuing_delay_signal_ms_(est_queuing_delay_signal_ms), congestion_signal_(congestion_signal), - derivative_(derivative) {} + derivative_(derivative), + receiving_rate_(receiving_rate) {} virtual ~NadaFeedback() {} + int64_t exp_smoothed_delay_ms() const { return exp_smoothed_delay_ms_; } + int64_t est_queuing_delay_signal_ms() const { + return est_queuing_delay_signal_ms_; + } int64_t congestion_signal() const { return congestion_signal_; } float derivative() const { return derivative_; } + float receiving_rate() const { return receiving_rate_; } private: - int64_t congestion_signal_; - float derivative_; + int64_t exp_smoothed_delay_ms_; // Referred as d_hat_n. + int64_t est_queuing_delay_signal_ms_; // Referred as d_tilde_n. + int64_t congestion_signal_; // Referred as x_n. + float derivative_; // Referred as x'_n. + float receiving_rate_; // Referred as R_r. }; class TcpFeedback : public FeedbackPacket { public: TcpFeedback(int flow_id, int64_t send_time_us, + int64_t latest_send_time_ms, const std::vector& acked_packets) - : FeedbackPacket(flow_id, send_time_us), acked_packets_(acked_packets) {} + : FeedbackPacket(flow_id, send_time_us, latest_send_time_ms), + acked_packets_(acked_packets) {} virtual ~TcpFeedback() {} const std::vector& acked_packets() const { return acked_packets_; } @@ -156,6 +189,7 @@ class TcpFeedback : public FeedbackPacket { typedef std::list Packets; typedef std::list::iterator PacketsIt; typedef std::list::const_iterator PacketsConstIt; + } // namespace bwe } // namespace testing } // namespace webrtc diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet_sender.cc b/webrtc/modules/remote_bitrate_estimator/test/packet_sender.cc index 480f185ca..a94b4e75c 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/packet_sender.cc +++ b/webrtc/modules/remote_bitrate_estimator/test/packet_sender.cc @@ -40,14 +40,16 @@ std::list GetFeedbackPackets(Packets* in_out, return fb_packets; } +void PacketSender::SetSenderTimestamps(Packets* in_out) { + for (auto it = in_out->begin(); it != in_out->end(); ++it) { + (*it)->set_sender_timestamp_us(clock_.TimeInMilliseconds() * 1000); + } +} + VideoSender::VideoSender(PacketProcessorListener* listener, VideoSource* source, BandwidthEstimatorType estimator_type) : PacketSender(listener, source->flow_id()), - // For Packet::send_time_us() to be comparable with timestamps from - // clock_, the clock of the VideoSender and the Source must be aligned. - // We assume that both start at time 0. - clock_(0), source_(source), bwe_(CreateBweSender(estimator_type, source_->bits_per_second() / 1000, @@ -60,10 +62,10 @@ VideoSender::~VideoSender() { } void VideoSender::RunFor(int64_t time_ms, Packets* in_out) { - int64_t now_ms = clock_.TimeInMilliseconds(); - std::list feedbacks = - GetFeedbackPackets(in_out, now_ms + time_ms, source_->flow_id()); + std::list feedbacks = GetFeedbackPackets( + in_out, clock_.TimeInMilliseconds() + time_ms, source_->flow_id()); ProcessFeedbackAndGeneratePackets(time_ms, &feedbacks, in_out); + SetSenderTimestamps(in_out); } void VideoSender::ProcessFeedbackAndGeneratePackets( @@ -190,6 +192,7 @@ void PacedVideoSender::RunFor(int64_t time_ms, Packets* in_out) { } } while (clock_.TimeInMilliseconds() < end_time_ms); QueuePackets(in_out, end_time_ms * 1000); + SetSenderTimestamps(in_out); } int64_t PacedVideoSender::TimeUntilNextProcess( @@ -267,14 +270,15 @@ void PacedVideoSender::OnNetworkChanged(uint32_t target_bitrate_bps, } void TcpSender::RunFor(int64_t time_ms, Packets* in_out) { - if (now_ms_ + time_ms < offset_ms_) { - now_ms_ += time_ms; + if (clock_.TimeInMilliseconds() + time_ms < offset_ms_) { + clock_.AdvanceTimeMilliseconds(time_ms); return; } BWE_TEST_LOGGING_CONTEXT("Sender"); BWE_TEST_LOGGING_CONTEXT(*flow_ids().begin()); - std::list feedbacks = - GetFeedbackPackets(in_out, now_ms_ + time_ms, *flow_ids().begin()); + + std::list feedbacks = GetFeedbackPackets( + in_out, clock_.TimeInMilliseconds() + time_ms, *flow_ids().begin()); // The number of packets which are sent in during time_ms depends on the // number of packets in_flight_ and the max number of packets in flight // (cwnd_). Therefore SendPackets() isn't directly dependent on time_ms. @@ -283,8 +287,16 @@ void TcpSender::RunFor(int64_t time_ms, Packets* in_out) { SendPackets(in_out); } + for (auto it = in_flight_.begin(); it != in_flight_.end();) { + if (it->time_ms < clock_.TimeInMilliseconds() - 1000) + in_flight_.erase(it++); + else + ++it; + } + SendPackets(in_out); - now_ms_ += time_ms; + clock_.AdvanceTimeMilliseconds(time_ms); + SetSenderTimestamps(in_out); } void TcpSender::SendPackets(Packets* in_out) { @@ -298,6 +310,7 @@ void TcpSender::SendPackets(Packets* in_out) { Packets generated = GeneratePackets(packets_to_send); for (Packet* packet : generated) in_flight_.insert(InFlight(*static_cast(packet))); + in_out->merge(generated, DereferencingComparator); } } @@ -312,7 +325,7 @@ void TcpSender::UpdateCongestionControl(const FeedbackPacket* fb) { expected - static_cast(tcp_fb->acked_packets().size()); for (uint16_t ack_seq_num : tcp_fb->acked_packets()) - in_flight_.erase(InFlight(ack_seq_num, now_ms_)); + in_flight_.erase(InFlight(ack_seq_num, clock_.TimeInMilliseconds())); if (missing > 0) { HandleLoss(); @@ -329,7 +342,7 @@ void TcpSender::UpdateCongestionControl(const FeedbackPacket* fb) { int TcpSender::TriggerTimeouts() { int timed_out = 0; for (auto it = in_flight_.begin(); it != in_flight_.end();) { - if (it->time_ms < now_ms_ - 1000) { + if (it->time_ms < clock_.TimeInMilliseconds() - 1000) { in_flight_.erase(it++); ++timed_out; } else { @@ -347,7 +360,8 @@ void TcpSender::HandleLoss() { Packets TcpSender::GeneratePackets(size_t num_packets) { Packets generated; for (size_t i = 0; i < num_packets; ++i) { - generated.push_back(new MediaPacket(*flow_ids().begin(), 1000 * now_ms_, + generated.push_back(new MediaPacket(*flow_ids().begin(), + 1000 * clock_.TimeInMilliseconds(), 1200, next_sequence_number_++)); } return generated; diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet_sender.h b/webrtc/modules/remote_bitrate_estimator/test/packet_sender.h index 82a6229ea..367d09932 100644 --- a/webrtc/modules/remote_bitrate_estimator/test/packet_sender.h +++ b/webrtc/modules/remote_bitrate_estimator/test/packet_sender.h @@ -28,7 +28,11 @@ namespace bwe { class PacketSender : public PacketProcessor { public: PacketSender(PacketProcessorListener* listener, int flow_id) - : PacketProcessor(listener, flow_id, kSender) {} + : PacketProcessor(listener, flow_id, kSender), + // For Packet::send_time_us() to be comparable with timestamps from + // clock_, the clock of the PacketSender and the Source must be aligned. + // We assume that both start at time 0. + clock_(0) {} virtual ~PacketSender() {} // Call GiveFeedback() with the returned interval in milliseconds, provided // there is a new estimate available. @@ -36,6 +40,10 @@ class PacketSender : public PacketProcessor { // output of the estimators is sampled and therefore the baseline files may // have to be regenerated. virtual int GetFeedbackIntervalMs() const = 0; + void SetSenderTimestamps(Packets* in_out); + + protected: + SimulatedClock clock_; }; class VideoSender : public PacketSender, public BitrateObserver { @@ -60,7 +68,6 @@ class VideoSender : public PacketSender, public BitrateObserver { std::list* feedbacks, Packets* generated); - SimulatedClock clock_; VideoSource* source_; rtc::scoped_ptr bwe_; int64_t start_of_run_ms_; @@ -107,7 +114,6 @@ class TcpSender : public PacketSender { public: TcpSender(PacketProcessorListener* listener, int flow_id, int64_t offset_ms) : PacketSender(listener, flow_id), - now_ms_(0), cwnd_(10), ssthresh_(std::numeric_limits::max()), ack_received_(false), @@ -146,7 +152,6 @@ class TcpSender : public PacketSender { void HandleLoss(); Packets GeneratePackets(size_t num_packets); - int64_t now_ms_; float cwnd_; int ssthresh_; std::set in_flight_;