aboutsummaryrefslogtreecommitdiff
path: root/webrtc/modules/remote_bitrate_estimator/test
diff options
context:
space:
mode:
authorChih-hung Hsieh <chh@google.com>2015-12-01 17:07:48 +0000
committerandroid-build-merger <android-build-merger@google.com>2015-12-01 17:07:48 +0000
commita4acd9d6bc9b3b033d7d274316e75ee067df8d20 (patch)
tree672a185b294789cf991f385c3e395dd63bea9063 /webrtc/modules/remote_bitrate_estimator/test
parent3681b90ba4fe7a27232dd3e27897d5d7ed9d651c (diff)
parentfe8b4a657979b49e1701bd92f6d5814a99e0b2be (diff)
downloadwebrtc-a4acd9d6bc9b3b033d7d274316e75ee067df8d20.tar.gz
Merge changes I7bbf776e,I1b827825
am: fe8b4a6579 * commit 'fe8b4a657979b49e1701bd92f6d5814a99e0b2be': (7237 commits) WIP: Changes after merge commit 'cb3f9bd' Make the nonlinear beamformer steerable Utilize bitrate above codec max to protect video. Enable VP9 internal resize by default. Filter overlapping RTP header extensions. Make VCMEncodedFrameCallback const. MediaCodecVideoEncoder: Add number of quality resolution downscales to Encoded callback. Remove redudant encoder rate calls. Create isolate files for nonparallel tests. Register header extensions in RtpRtcpObserver to avoid log spam. Make an enum class out of NetEqDecoder, and hide the neteq_decoders_ table ACM: Move NACK functionality inside NetEq Fix chromium-style warnings in webrtc/sound/. Create a 'webrtc_nonparallel_tests' target. Update scalability structure data according to updates in the RTP payload profile. audio_coding: rename interface -> include Rewrote perform_action_on_all_files to be parallell. Update reference indices according to updates in the RTP payload profile. Disable P2PTransport...TestFailoverControlledSide on Memcheck pass clangcl compile options to ignore warnings in gflags.cc ...
Diffstat (limited to 'webrtc/modules/remote_bitrate_estimator/test')
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe.cc280
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe.h194
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test.cc983
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test.h195
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc168
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.h45
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.cc97
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h59
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc817
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h468
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc1051
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.cc250
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h323
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_unittest.cc393
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc287
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h109
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/nada_unittest.cc495
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/remb.cc164
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h83
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.cc151
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.h69
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.cc54
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.h38
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/metric_recorder.cc445
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h189
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/metric_recorder_unittest.cc108
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/packet.h205
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/packet_receiver.cc147
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h71
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/packet_sender.cc494
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/packet_sender.h194
-rwxr-xr-xwebrtc/modules/remote_bitrate_estimator/test/plot_bars.sh286
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py166
-rwxr-xr-xwebrtc/modules/remote_bitrate_estimator/test/plot_dynamics.sh87
34 files changed, 9165 insertions, 0 deletions
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe.cc
new file mode 100644
index 0000000000..c667b6864e
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe.cc
@@ -0,0 +1,280 @@
+/*
+ * 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 "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+
+#include <limits>
+
+#include "webrtc/base/common.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+// 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.
+const int kSetCapacity = 1000;
+
+BweReceiver::BweReceiver(int flow_id)
+ : flow_id_(flow_id),
+ received_packets_(kSetCapacity),
+ rate_counter_(),
+ loss_account_() {
+}
+
+BweReceiver::BweReceiver(int flow_id, int64_t window_size_ms)
+ : flow_id_(flow_id),
+ received_packets_(kSetCapacity),
+ rate_counter_(window_size_ms),
+ loss_account_() {
+}
+
+void BweReceiver::ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) {
+ if (received_packets_.size() == kSetCapacity) {
+ RelieveSetAndUpdateLoss();
+ }
+
+ received_packets_.Insert(media_packet.sequence_number(),
+ media_packet.send_time_ms(), arrival_time_ms,
+ media_packet.payload_size());
+
+ rate_counter_.UpdateRates(media_packet.send_time_ms() * 1000,
+ static_cast<uint32_t>(media_packet.payload_size()));
+}
+
+class NullBweSender : public BweSender {
+ public:
+ NullBweSender() {}
+ virtual ~NullBweSender() {}
+
+ int GetFeedbackIntervalMs() const override { return 1000; }
+ void GiveFeedback(const FeedbackPacket& feedback) override {}
+ void OnPacketsSent(const Packets& packets) override {}
+ int64_t TimeUntilNextProcess() override {
+ return std::numeric_limits<int64_t>::max();
+ }
+ int Process() override { return 0; }
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(NullBweSender);
+};
+
+int64_t GetAbsSendTimeInMs(uint32_t abs_send_time) {
+ const int kInterArrivalShift = 26;
+ const int kAbsSendTimeInterArrivalUpshift = 8;
+ const double kTimestampToMs =
+ 1000.0 / static_cast<double>(1 << kInterArrivalShift);
+ uint32_t timestamp = abs_send_time << kAbsSendTimeInterArrivalUpshift;
+ return static_cast<int64_t>(timestamp) * kTimestampToMs;
+}
+
+BweSender* CreateBweSender(BandwidthEstimatorType estimator,
+ int kbps,
+ BitrateObserver* observer,
+ Clock* clock) {
+ switch (estimator) {
+ case kRembEstimator:
+ return new RembBweSender(kbps, observer, clock);
+ case kFullSendSideEstimator:
+ return new FullBweSender(kbps, observer, clock);
+ case kNadaEstimator:
+ return new NadaBweSender(kbps, observer, clock);
+ case kTcpEstimator:
+ FALLTHROUGH();
+ case kNullEstimator:
+ return new NullBweSender();
+ }
+ assert(false);
+ return NULL;
+}
+
+BweReceiver* CreateBweReceiver(BandwidthEstimatorType type,
+ int flow_id,
+ bool plot) {
+ switch (type) {
+ case kRembEstimator:
+ return new RembReceiver(flow_id, plot);
+ case kFullSendSideEstimator:
+ return new SendSideBweReceiver(flow_id);
+ case kNadaEstimator:
+ return new NadaBweReceiver(flow_id);
+ case kTcpEstimator:
+ return new TcpBweReceiver(flow_id);
+ case kNullEstimator:
+ return new BweReceiver(flow_id);
+ }
+ assert(false);
+ return NULL;
+}
+
+// Take into account all LinkedSet content.
+void BweReceiver::UpdateLoss() {
+ loss_account_.Add(LinkedSetPacketLossRatio());
+}
+
+// Preserve 10% latest packets and update packet loss based on the oldest
+// 90%, that will be removed.
+void BweReceiver::RelieveSetAndUpdateLoss() {
+ // Compute Loss for the whole LinkedSet and updates loss_account_.
+ UpdateLoss();
+
+ size_t num_preserved_elements = received_packets_.size() / 10;
+ PacketNodeIt it = received_packets_.begin();
+ std::advance(it, num_preserved_elements);
+
+ while (it != received_packets_.end()) {
+ received_packets_.Erase(it++);
+ }
+
+ // Compute Loss for the preserved elements
+ loss_account_.Subtract(LinkedSetPacketLossRatio());
+}
+
+float BweReceiver::GlobalReceiverPacketLossRatio() {
+ UpdateLoss();
+ return loss_account_.LossRatio();
+}
+
+// This function considers at most kSetCapacity = 1000 packets.
+LossAccount BweReceiver::LinkedSetPacketLossRatio() {
+ if (received_packets_.empty()) {
+ return LossAccount();
+ }
+
+ uint16_t oldest_seq_num = received_packets_.OldestSeqNumber();
+ uint16_t newest_seq_num = received_packets_.NewestSeqNumber();
+
+ size_t set_total_packets =
+ static_cast<uint16_t>(newest_seq_num - oldest_seq_num + 1);
+
+ size_t set_received_packets = received_packets_.size();
+ size_t set_lost_packets = set_total_packets - set_received_packets;
+
+ return LossAccount(set_total_packets, set_lost_packets);
+}
+
+uint32_t BweReceiver::RecentKbps() const {
+ return (rate_counter_.bits_per_second() + 500) / 1000;
+}
+
+// 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 BweReceiver::RecentPacketLossRatio() {
+ 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 - kPacketLossTimeWindowMs;
+ // Oldest and newest values found within the given time window.
+ uint16_t oldest_seq_num = (*node_it)->sequence_number;
+ uint16_t newest_seq_num = oldest_seq_num;
+
+ while (node_it != received_packets_.end()) {
+ if ((*node_it)->arrival_time_ms < time_limit_ms) {
+ break;
+ }
+ uint16_t seq_num = (*node_it)->sequence_number;
+ if (IsNewerSequenceNumber(seq_num, newest_seq_num)) {
+ newest_seq_num = seq_num;
+ }
+ if (IsNewerSequenceNumber(oldest_seq_num, seq_num)) {
+ oldest_seq_num = seq_num;
+ }
+ ++node_it;
+ ++number_packets_received;
+ }
+ // Interval width between oldest and newest sequence number.
+ // There was an overflow if newest_seq_num < oldest_seq_num.
+ int gap = static_cast<uint16_t>(newest_seq_num - oldest_seq_num + 1);
+
+ return static_cast<float>(gap - number_packets_received) / gap;
+}
+
+LinkedSet::~LinkedSet() {
+ while (!empty())
+ RemoveTail();
+}
+
+void LinkedSet::Insert(uint16_t sequence_number,
+ int64_t send_time_ms,
+ int64_t arrival_time_ms,
+ size_t payload_size) {
+ auto it = map_.find(sequence_number);
+ if (it != map_.end()) {
+ 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_) {
+ RemoveTail();
+ }
+ UpdateHead(new PacketIdentifierNode(sequence_number, send_time_ms,
+ arrival_time_ms, payload_size));
+ }
+}
+
+void LinkedSet::Insert(PacketIdentifierNode packet_identifier) {
+ Insert(packet_identifier.sequence_number, packet_identifier.send_time_ms,
+ packet_identifier.arrival_time_ms, packet_identifier.payload_size);
+}
+
+void LinkedSet::RemoveTail() {
+ map_.erase(list_.back()->sequence_number);
+ delete list_.back();
+ list_.pop_back();
+}
+void LinkedSet::UpdateHead(PacketIdentifierNode* new_head) {
+ list_.push_front(new_head);
+ map_[new_head->sequence_number] = list_.begin();
+}
+
+void LinkedSet::Erase(PacketNodeIt node_it) {
+ map_.erase((*node_it)->sequence_number);
+ delete (*node_it);
+ list_.erase(node_it);
+}
+
+void LossAccount::Add(LossAccount rhs) {
+ num_total += rhs.num_total;
+ num_lost += rhs.num_lost;
+}
+void LossAccount::Subtract(LossAccount rhs) {
+ num_total -= rhs.num_total;
+ num_lost -= rhs.num_lost;
+}
+
+float LossAccount::LossRatio() {
+ if (num_total == 0)
+ return 0.0f;
+ return static_cast<float>(num_lost) / num_total;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe.h b/webrtc/modules/remote_bitrate_estimator/test/bwe.h
new file mode 100644
index 0000000000..ef9b3149d7
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_H_
+
+#include <sstream>
+
+#include "webrtc/test/testsupport/gtest_prod_util.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet.h"
+#include "webrtc/modules/bitrate_controller/include/bitrate_controller.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+// Overload map comparator.
+class SequenceNumberOlderThan {
+ public:
+ bool operator()(uint16_t seq_num_1, uint16_t seq_num_2) const {
+ return IsNewerSequenceNumber(seq_num_2, seq_num_1);
+ }
+};
+
+// Holds information for computing global packet loss.
+struct LossAccount {
+ LossAccount() : num_total(0), num_lost(0) {}
+ LossAccount(size_t num_total, size_t num_lost)
+ : num_total(num_total), num_lost(num_lost) {}
+ void Add(LossAccount rhs);
+ void Subtract(LossAccount rhs);
+ float LossRatio();
+ size_t num_total;
+ size_t num_lost;
+};
+
+// 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<PacketIdentifierNode*>::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(int capacity) : capacity_(capacity) {}
+ ~LinkedSet();
+
+ // 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);
+
+ void Insert(PacketIdentifierNode packet_identifier);
+
+ PacketNodeIt begin() { return list_.begin(); }
+ PacketNodeIt end() { return list_.end(); }
+
+ bool empty() const { return list_.empty(); }
+ size_t size() const { return list_.size(); }
+ size_t capacity() const { return capacity_; }
+
+ uint16_t OldestSeqNumber() const { return empty() ? 0 : map_.begin()->first; }
+ uint16_t NewestSeqNumber() const {
+ return empty() ? 0 : map_.rbegin()->first;
+ }
+
+ void Erase(PacketNodeIt node_it);
+
+ 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<uint16_t, PacketNodeIt, SequenceNumberOlderThan> map_;
+ std::list<PacketIdentifierNode*> list_;
+};
+
+const int kMinBitrateKbps = 50;
+const int kMaxBitrateKbps = 2500;
+
+class BweSender : public Module {
+ public:
+ BweSender() {}
+ explicit BweSender(int bitrate_kbps) : bitrate_kbps_(bitrate_kbps) {}
+ virtual ~BweSender() {}
+
+ virtual int GetFeedbackIntervalMs() const = 0;
+ virtual void GiveFeedback(const FeedbackPacket& feedback) = 0;
+ virtual void OnPacketsSent(const Packets& packets) = 0;
+
+ protected:
+ int bitrate_kbps_;
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(BweSender);
+};
+
+class BweReceiver {
+ public:
+ explicit BweReceiver(int flow_id);
+ BweReceiver(int flow_id, int64_t window_size_ms);
+
+ virtual ~BweReceiver() {}
+
+ virtual void ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet);
+ virtual FeedbackPacket* GetFeedback(int64_t now_ms) { return NULL; }
+
+ size_t GetSetCapacity() { return received_packets_.capacity(); }
+ double BitrateWindowS() const { return rate_counter_.BitrateWindowS(); }
+ uint32_t RecentKbps() const; // Receiving Rate.
+
+ // Computes packet loss during an entire simulation, up to 4 billion packets.
+ float GlobalReceiverPacketLossRatio(); // Plot histogram.
+ float RecentPacketLossRatio(); // Plot dynamics.
+
+ static const int64_t kPacketLossTimeWindowMs = 500;
+ static const int64_t kReceivingRateTimeWindowMs = 1000;
+
+ protected:
+ int flow_id_;
+ // Deals with packets sent more than once.
+ LinkedSet received_packets_;
+ // Used for calculating recent receiving rate.
+ RateCounter rate_counter_;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(BweReceiverTest, RecentKbps);
+ FRIEND_TEST_ALL_PREFIXES(BweReceiverTest, Loss);
+
+ void UpdateLoss();
+ void RelieveSetAndUpdateLoss();
+ // Packet loss for packets stored in the LinkedSet, up to 1000 packets.
+ // Used to update global loss account whenever the set is filled and cleared.
+ LossAccount LinkedSetPacketLossRatio();
+
+ // Used for calculating global packet loss ratio.
+ LossAccount loss_account_;
+};
+
+enum BandwidthEstimatorType {
+ kNullEstimator,
+ kNadaEstimator,
+ kRembEstimator,
+ kFullSendSideEstimator,
+ kTcpEstimator
+};
+
+const std::string bwe_names[] = {"Null", "NADA", "REMB", "GCC", "TCP"};
+
+int64_t GetAbsSendTimeInMs(uint32_t abs_send_time);
+
+BweSender* CreateBweSender(BandwidthEstimatorType estimator,
+ int kbps,
+ BitrateObserver* observer,
+ Clock* clock);
+
+BweReceiver* CreateBweReceiver(BandwidthEstimatorType type,
+ int flow_id,
+ bool plot);
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test.cc
new file mode 100644
index 0000000000..f837638474
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test.cc
@@ -0,0 +1,983 @@
+/*
+ * Copyright (c) 2013 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 "webrtc/modules/remote_bitrate_estimator/test/bwe_test.h"
+
+#include <sstream>
+
+#include "webrtc/base/common.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h"
+#include "webrtc/system_wrappers/include/clock.h"
+#include "webrtc/test/testsupport/perf_test.h"
+
+using std::string;
+using std::vector;
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+PacketProcessorRunner::PacketProcessorRunner(PacketProcessor* processor)
+ : processor_(processor) {
+}
+
+PacketProcessorRunner::~PacketProcessorRunner() {
+ for (Packet* packet : queue_)
+ delete packet;
+}
+
+bool PacketProcessorRunner::RunsProcessor(
+ const PacketProcessor* processor) const {
+ return processor == processor_;
+}
+
+void PacketProcessorRunner::RunFor(int64_t time_ms,
+ int64_t time_now_ms,
+ Packets* in_out) {
+ Packets to_process;
+ FindPacketsToProcess(processor_->flow_ids(), in_out, &to_process);
+ processor_->RunFor(time_ms, &to_process);
+ QueuePackets(&to_process, time_now_ms * 1000);
+ if (!to_process.empty()) {
+ processor_->Plot(to_process.back()->send_time_ms());
+ }
+ in_out->merge(to_process, DereferencingComparator<Packet>);
+}
+
+void PacketProcessorRunner::FindPacketsToProcess(const FlowIds& flow_ids,
+ Packets* in,
+ Packets* out) {
+ assert(out->empty());
+ for (Packets::iterator it = in->begin(); it != in->end();) {
+ // TODO(holmer): Further optimize this by looking for consecutive flow ids
+ // in the packet list and only doing the binary search + splice once for a
+ // sequence.
+ if (flow_ids.find((*it)->flow_id()) != flow_ids.end()) {
+ Packets::iterator next = it;
+ ++next;
+ out->splice(out->end(), *in, it);
+ it = next;
+ } else {
+ ++it;
+ }
+ }
+}
+
+void PacketProcessorRunner::QueuePackets(Packets* batch,
+ int64_t end_of_batch_time_us) {
+ queue_.merge(*batch, DereferencingComparator<Packet>);
+ if (queue_.empty()) {
+ return;
+ }
+ Packets::iterator it = queue_.begin();
+ for (; it != queue_.end(); ++it) {
+ if ((*it)->send_time_us() > end_of_batch_time_us) {
+ break;
+ }
+ }
+ Packets to_transfer;
+ to_transfer.splice(to_transfer.begin(), queue_, queue_.begin(), it);
+ batch->merge(to_transfer, DereferencingComparator<Packet>);
+}
+
+// Plot link capacity by default.
+BweTest::BweTest() : BweTest(true) {
+}
+
+BweTest::BweTest(bool plot_capacity)
+ : run_time_ms_(0),
+ time_now_ms_(-1),
+ simulation_interval_ms_(-1),
+ plot_total_available_capacity_(plot_capacity) {
+ links_.push_back(&uplink_);
+ links_.push_back(&downlink_);
+}
+
+BweTest::~BweTest() {
+ for (Packet* packet : packets_)
+ delete packet;
+}
+
+void BweTest::SetUp() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ string test_name =
+ string(test_info->test_case_name()) + "_" + string(test_info->name());
+ BWE_TEST_LOGGING_GLOBAL_CONTEXT(test_name);
+ BWE_TEST_LOGGING_GLOBAL_ENABLE(false);
+}
+
+void Link::AddPacketProcessor(PacketProcessor* processor,
+ ProcessorType processor_type) {
+ assert(processor);
+ switch (processor_type) {
+ case kSender:
+ senders_.push_back(static_cast<PacketSender*>(processor));
+ break;
+ case kReceiver:
+ receivers_.push_back(static_cast<PacketReceiver*>(processor));
+ break;
+ case kRegular:
+ break;
+ }
+ processors_.push_back(PacketProcessorRunner(processor));
+}
+
+void Link::RemovePacketProcessor(PacketProcessor* processor) {
+ for (vector<PacketProcessorRunner>::iterator it = processors_.begin();
+ it != processors_.end(); ++it) {
+ if (it->RunsProcessor(processor)) {
+ processors_.erase(it);
+ return;
+ }
+ }
+ assert(false);
+}
+
+// Ownership of the created packets is handed over to the caller.
+void Link::Run(int64_t run_for_ms, int64_t now_ms, Packets* packets) {
+ for (auto& processor : processors_) {
+ processor.RunFor(run_for_ms, now_ms, packets);
+ }
+}
+
+void BweTest::VerboseLogging(bool enable) {
+ BWE_TEST_LOGGING_GLOBAL_ENABLE(enable);
+}
+
+void BweTest::RunFor(int64_t time_ms) {
+ // Set simulation interval from first packet sender.
+ // TODO(holmer): Support different feedback intervals for different flows.
+ if (!uplink_.senders().empty()) {
+ simulation_interval_ms_ = uplink_.senders()[0]->GetFeedbackIntervalMs();
+ } else if (!downlink_.senders().empty()) {
+ simulation_interval_ms_ = downlink_.senders()[0]->GetFeedbackIntervalMs();
+ }
+ assert(simulation_interval_ms_ > 0);
+ if (time_now_ms_ == -1) {
+ time_now_ms_ = simulation_interval_ms_;
+ }
+ for (run_time_ms_ += time_ms;
+ time_now_ms_ <= run_time_ms_ - simulation_interval_ms_;
+ time_now_ms_ += simulation_interval_ms_) {
+ // Packets are first generated on the first link, passed through all the
+ // PacketProcessors and PacketReceivers. The PacketReceivers produces
+ // FeedbackPackets which are then processed by the next link, where they
+ // at some point will be consumed by a PacketSender.
+ for (Link* link : links_)
+ link->Run(simulation_interval_ms_, time_now_ms_, &packets_);
+ }
+}
+
+string BweTest::GetTestName() const {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ return string(test_info->name());
+}
+
+void BweTest::PrintResults(double max_throughput_kbps,
+ Stats<double> throughput_kbps,
+ int flow_id,
+ Stats<double> flow_delay_ms,
+ Stats<double> flow_throughput_kbps) {
+ std::map<int, Stats<double>> flow_delays_ms;
+ flow_delays_ms[flow_id] = flow_delay_ms;
+ std::map<int, Stats<double>> flow_throughputs_kbps;
+ flow_throughputs_kbps[flow_id] = flow_throughput_kbps;
+ PrintResults(max_throughput_kbps, throughput_kbps, flow_delays_ms,
+ flow_throughputs_kbps);
+}
+
+void BweTest::PrintResults(double max_throughput_kbps,
+ Stats<double> throughput_kbps,
+ std::map<int, Stats<double>> flow_delay_ms,
+ std::map<int, Stats<double>> flow_throughput_kbps) {
+ double utilization = throughput_kbps.GetMean() / max_throughput_kbps;
+ webrtc::test::PrintResult("BwePerformance", GetTestName(), "Utilization",
+ utilization * 100.0, "%", false);
+ std::stringstream ss;
+ ss << throughput_kbps.GetStdDev() / throughput_kbps.GetMean();
+ webrtc::test::PrintResult("BwePerformance", GetTestName(),
+ "Utilization var coeff", ss.str(), "", false);
+ for (auto& kv : flow_throughput_kbps) {
+ ss.str("");
+ ss << "Throughput flow " << kv.first;
+ webrtc::test::PrintResultMeanAndError("BwePerformance", GetTestName(),
+ ss.str(), kv.second.AsString(),
+ "kbps", false);
+ }
+ for (auto& kv : flow_delay_ms) {
+ ss.str("");
+ ss << "Delay flow " << kv.first;
+ webrtc::test::PrintResultMeanAndError("BwePerformance", GetTestName(),
+ ss.str(), kv.second.AsString(), "ms",
+ false);
+ }
+ double fairness_index = 1.0;
+ if (!flow_throughput_kbps.empty()) {
+ double squared_bitrate_sum = 0.0;
+ fairness_index = 0.0;
+ for (auto kv : flow_throughput_kbps) {
+ squared_bitrate_sum += kv.second.GetMean() * kv.second.GetMean();
+ fairness_index += kv.second.GetMean();
+ }
+ fairness_index *= fairness_index;
+ fairness_index /= flow_throughput_kbps.size() * squared_bitrate_sum;
+ }
+ webrtc::test::PrintResult("BwePerformance", GetTestName(), "Fairness",
+ fairness_index * 100, "%", false);
+}
+
+void BweTest::RunFairnessTest(BandwidthEstimatorType bwe_type,
+ size_t num_media_flows,
+ size_t num_tcp_flows,
+ int64_t run_time_seconds,
+ uint32_t capacity_kbps,
+ int64_t max_delay_ms,
+ int64_t rtt_ms,
+ int64_t max_jitter_ms,
+ const int64_t* offsets_ms) {
+ RunFairnessTest(bwe_type, num_media_flows, num_tcp_flows, run_time_seconds,
+ capacity_kbps, max_delay_ms, rtt_ms, max_jitter_ms,
+ offsets_ms, "Fairness_test", bwe_names[bwe_type]);
+}
+
+void BweTest::RunFairnessTest(BandwidthEstimatorType bwe_type,
+ size_t num_media_flows,
+ size_t num_tcp_flows,
+ int64_t run_time_seconds,
+ uint32_t capacity_kbps,
+ int64_t max_delay_ms,
+ int64_t rtt_ms,
+ int64_t max_jitter_ms,
+ const int64_t* offsets_ms,
+ const std::string& title,
+ const std::string& flow_name) {
+ std::set<int> all_flow_ids;
+ std::set<int> media_flow_ids;
+ std::set<int> tcp_flow_ids;
+ int next_flow_id = 0;
+ for (size_t i = 0; i < num_media_flows; ++i) {
+ media_flow_ids.insert(next_flow_id);
+ all_flow_ids.insert(next_flow_id);
+ ++next_flow_id;
+ }
+ for (size_t i = 0; i < num_tcp_flows; ++i) {
+ tcp_flow_ids.insert(next_flow_id);
+ all_flow_ids.insert(next_flow_id);
+ ++next_flow_id;
+ }
+
+ std::vector<VideoSource*> sources;
+ std::vector<PacketSender*> senders;
+ std::vector<MetricRecorder*> metric_recorders;
+
+ int64_t max_offset_ms = 0;
+
+ for (int media_flow : media_flow_ids) {
+ sources.push_back(new AdaptiveVideoSource(media_flow, 30, 300, 0,
+ offsets_ms[media_flow]));
+ senders.push_back(new PacedVideoSender(&uplink_, sources.back(), bwe_type));
+ max_offset_ms = std::max(max_offset_ms, offsets_ms[media_flow]);
+ }
+
+ for (int tcp_flow : tcp_flow_ids) {
+ senders.push_back(new TcpSender(&uplink_, tcp_flow, offsets_ms[tcp_flow]));
+ max_offset_ms = std::max(max_offset_ms, offsets_ms[tcp_flow]);
+ }
+
+ ChokeFilter choke(&uplink_, all_flow_ids);
+ choke.set_capacity_kbps(capacity_kbps);
+ choke.set_max_delay_ms(max_delay_ms);
+ LinkShare link_share(&choke);
+
+ int64_t one_way_delay_ms = rtt_ms / 2;
+ DelayFilter delay_uplink(&uplink_, all_flow_ids);
+ delay_uplink.SetOneWayDelayMs(one_way_delay_ms);
+
+ JitterFilter jitter(&uplink_, all_flow_ids);
+ jitter.SetMaxJitter(max_jitter_ms);
+
+ std::vector<RateCounterFilter*> rate_counters;
+ for (int flow : media_flow_ids) {
+ rate_counters.push_back(
+ new RateCounterFilter(&uplink_, flow, "Receiver", bwe_names[bwe_type]));
+ }
+ for (int flow : tcp_flow_ids) {
+ rate_counters.push_back(new RateCounterFilter(&uplink_, flow, "Receiver",
+ bwe_names[kTcpEstimator]));
+ }
+
+ RateCounterFilter total_utilization(
+ &uplink_, all_flow_ids, "total_utilization", "Total_link_utilization");
+
+ std::vector<PacketReceiver*> receivers;
+ // Delays is being plotted only for the first flow.
+ // To plot all of them, replace "i == 0" with "true" on new PacketReceiver().
+ for (int media_flow : media_flow_ids) {
+ metric_recorders.push_back(
+ new MetricRecorder(bwe_names[bwe_type], static_cast<int>(media_flow),
+ senders[media_flow], &link_share));
+ receivers.push_back(new PacketReceiver(&uplink_, media_flow, bwe_type,
+ media_flow == 0, false,
+ metric_recorders[media_flow]));
+ metric_recorders[media_flow]->set_plot_available_capacity(
+ media_flow == 0 && plot_total_available_capacity_);
+ metric_recorders[media_flow]->set_start_computing_metrics_ms(max_offset_ms);
+ }
+ // Delays is not being plotted only for TCP flows. To plot all of them,
+ // replace first "false" occurence with "true" on new PacketReceiver().
+ for (int tcp_flow : tcp_flow_ids) {
+ metric_recorders.push_back(
+ new MetricRecorder(bwe_names[kTcpEstimator], static_cast<int>(tcp_flow),
+ senders[tcp_flow], &link_share));
+ receivers.push_back(new PacketReceiver(&uplink_, tcp_flow, kTcpEstimator,
+ false, false,
+ metric_recorders[tcp_flow]));
+ metric_recorders[tcp_flow]->set_plot_available_capacity(
+ tcp_flow == 0 && plot_total_available_capacity_);
+ }
+
+ DelayFilter delay_downlink(&downlink_, all_flow_ids);
+ delay_downlink.SetOneWayDelayMs(one_way_delay_ms);
+
+ RunFor(run_time_seconds * 1000);
+
+ std::map<int, Stats<double>> flow_throughput_kbps;
+ for (RateCounterFilter* rate_counter : rate_counters) {
+ int flow_id = *rate_counter->flow_ids().begin();
+ flow_throughput_kbps[flow_id] = rate_counter->GetBitrateStats();
+ }
+
+ std::map<int, Stats<double>> flow_delay_ms;
+ for (PacketReceiver* receiver : receivers) {
+ int flow_id = *receiver->flow_ids().begin();
+ flow_delay_ms[flow_id] = receiver->GetDelayStats();
+ }
+
+ PrintResults(capacity_kbps, total_utilization.GetBitrateStats(),
+ flow_delay_ms, flow_throughput_kbps);
+
+ for (int i : all_flow_ids) {
+ metric_recorders[i]->PlotThroughputHistogram(
+ title, flow_name, static_cast<int>(num_media_flows), 0);
+
+ metric_recorders[i]->PlotLossHistogram(title, flow_name,
+ static_cast<int>(num_media_flows),
+ receivers[i]->GlobalPacketLoss());
+ }
+
+ // Pointless to show delay histogram for TCP flow.
+ for (int i : media_flow_ids) {
+ metric_recorders[i]->PlotDelayHistogram(title, bwe_names[bwe_type],
+ static_cast<int>(num_media_flows),
+ one_way_delay_ms);
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], one_way_delay_ms, i);
+ }
+
+ for (VideoSource* source : sources)
+ delete source;
+ for (PacketSender* sender : senders)
+ delete sender;
+ for (RateCounterFilter* rate_counter : rate_counters)
+ delete rate_counter;
+ for (PacketReceiver* receiver : receivers)
+ delete receiver;
+ for (MetricRecorder* recorder : metric_recorders)
+ delete recorder;
+}
+
+void BweTest::RunChoke(BandwidthEstimatorType bwe_type,
+ std::vector<int> capacities_kbps) {
+ int flow_id = bwe_type;
+ AdaptiveVideoSource source(flow_id, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, bwe_type);
+ ChokeFilter choke(&uplink_, flow_id);
+ LinkShare link_share(&choke);
+ MetricRecorder metric_recorder(bwe_names[bwe_type], flow_id, &sender,
+ &link_share);
+ PacketReceiver receiver(&uplink_, flow_id, bwe_type, true, false,
+ &metric_recorder);
+ metric_recorder.set_plot_available_capacity(plot_total_available_capacity_);
+
+ choke.set_max_delay_ms(500);
+ const int64_t kRunTimeMs = 60 * 1000;
+
+ std::stringstream title("Choke");
+ char delimiter = '_';
+
+ for (auto it = capacities_kbps.begin(); it != capacities_kbps.end(); ++it) {
+ choke.set_capacity_kbps(*it);
+ RunFor(kRunTimeMs);
+ title << delimiter << (*it);
+ delimiter = '-';
+ }
+
+ title << "_kbps,_" << (kRunTimeMs / 1000) << "s_each";
+ metric_recorder.PlotThroughputHistogram(title.str(), bwe_names[bwe_type], 1,
+ 0);
+ metric_recorder.PlotDelayHistogram(title.str(), bwe_names[bwe_type], 1, 0);
+ // receiver.PlotLossHistogram(title, bwe_names[bwe_type], 1);
+ // receiver.PlotObjectiveHistogram(title, bwe_names[bwe_type], 1);
+}
+
+// 5.1. Single Video and Audio media traffic, forward direction.
+void BweTest::RunVariableCapacity1SingleFlow(BandwidthEstimatorType bwe_type) {
+ const int kFlowId = 0; // Arbitrary value.
+ AdaptiveVideoSource source(kFlowId, 30, 300, 0, 0);
+ PacedVideoSender sender(&uplink_, &source, bwe_type);
+
+ DefaultEvaluationFilter up_filter(&uplink_, kFlowId);
+ LinkShare link_share(&(up_filter.choke));
+ MetricRecorder metric_recorder(bwe_names[bwe_type], kFlowId, &sender,
+ &link_share);
+
+ PacketReceiver receiver(&uplink_, kFlowId, bwe_type, true, true,
+ &metric_recorder);
+
+ metric_recorder.set_plot_available_capacity(plot_total_available_capacity_);
+
+ DelayFilter down_filter(&downlink_, kFlowId);
+ down_filter.SetOneWayDelayMs(kOneWayDelayMs);
+
+ // Test also with one way propagation delay = 100ms.
+ // up_filter.delay.SetOneWayDelayMs(100);
+ // down_filter.SetOneWayDelayMs(100);
+
+ up_filter.choke.set_capacity_kbps(1000);
+ RunFor(40 * 1000); // 0-40s.
+ up_filter.choke.set_capacity_kbps(2500);
+ RunFor(20 * 1000); // 40-60s.
+ up_filter.choke.set_capacity_kbps(600);
+ RunFor(20 * 1000); // 60-80s.
+ up_filter.choke.set_capacity_kbps(1000);
+ RunFor(20 * 1000); // 80-100s.
+
+ std::string title("5.1_Variable_capacity_single_flow");
+ metric_recorder.PlotThroughputHistogram(title, bwe_names[bwe_type], 1, 0);
+ metric_recorder.PlotDelayHistogram(title, bwe_names[bwe_type], 1,
+ kOneWayDelayMs);
+ metric_recorder.PlotLossHistogram(title, bwe_names[bwe_type], 1,
+ receiver.GlobalPacketLoss());
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], kOneWayDelayMs, kFlowId);
+}
+
+// 5.2. Two forward direction competing flows, variable capacity.
+void BweTest::RunVariableCapacity2MultipleFlows(BandwidthEstimatorType bwe_type,
+ size_t num_flows) {
+ std::vector<VideoSource*> sources;
+ std::vector<PacketSender*> senders;
+ std::vector<MetricRecorder*> metric_recorders;
+ std::vector<PacketReceiver*> receivers;
+
+ const int64_t kStartingApartMs = 0; // Flows initialized simultaneously.
+
+ for (size_t i = 0; i < num_flows; ++i) {
+ sources.push_back(new AdaptiveVideoSource(static_cast<int>(i), 30, 300, 0,
+ i * kStartingApartMs));
+ senders.push_back(new VideoSender(&uplink_, sources[i], bwe_type));
+ }
+
+ FlowIds flow_ids = CreateFlowIdRange(0, static_cast<int>(num_flows - 1));
+
+ DefaultEvaluationFilter up_filter(&uplink_, flow_ids);
+ LinkShare link_share(&(up_filter.choke));
+
+ RateCounterFilter total_utilization(&uplink_, flow_ids, "Total_utilization",
+ "Total_link_utilization");
+
+ // Delays is being plotted only for the first flow.
+ // To plot all of them, replace "i == 0" with "true" on new PacketReceiver().
+ for (size_t i = 0; i < num_flows; ++i) {
+ metric_recorders.push_back(new MetricRecorder(
+ bwe_names[bwe_type], static_cast<int>(i), senders[i], &link_share));
+
+ receivers.push_back(new PacketReceiver(&uplink_, static_cast<int>(i),
+ bwe_type, i == 0, false,
+ metric_recorders[i]));
+ metric_recorders[i]->set_plot_available_capacity(
+ i == 0 && plot_total_available_capacity_);
+ }
+
+ DelayFilter down_filter(&downlink_, flow_ids);
+ down_filter.SetOneWayDelayMs(kOneWayDelayMs);
+ // Test also with one way propagation delay = 100ms.
+ // up_filter.delay.SetOneWayDelayMs(100);
+ // down_filter.SetOneWayDelayMs(100);
+
+ up_filter.choke.set_capacity_kbps(4000);
+ RunFor(25 * 1000); // 0-25s.
+ up_filter.choke.set_capacity_kbps(2000);
+ RunFor(25 * 1000); // 25-50s.
+ up_filter.choke.set_capacity_kbps(3500);
+ RunFor(25 * 1000); // 50-75s.
+ up_filter.choke.set_capacity_kbps(1000);
+ RunFor(25 * 1000); // 75-100s.
+ up_filter.choke.set_capacity_kbps(2000);
+ RunFor(25 * 1000); // 100-125s.
+
+ std::string title("5.2_Variable_capacity_two_flows");
+ for (size_t i = 0; i < num_flows; ++i) {
+ metric_recorders[i]->PlotThroughputHistogram(title, bwe_names[bwe_type],
+ num_flows, 0);
+ metric_recorders[i]->PlotDelayHistogram(title, bwe_names[bwe_type],
+ num_flows, kOneWayDelayMs);
+ metric_recorders[i]->PlotLossHistogram(title, bwe_names[bwe_type],
+ num_flows,
+ receivers[i]->GlobalPacketLoss());
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], kOneWayDelayMs, i);
+ }
+
+ for (VideoSource* source : sources)
+ delete source;
+ for (PacketSender* sender : senders)
+ delete sender;
+ for (MetricRecorder* recorder : metric_recorders)
+ delete recorder;
+ for (PacketReceiver* receiver : receivers)
+ delete receiver;
+}
+
+// 5.3. Bi-directional RMCAT flows.
+void BweTest::RunBidirectionalFlow(BandwidthEstimatorType bwe_type) {
+ enum direction { kForward = 0, kBackward };
+ const size_t kNumFlows = 2;
+ rtc::scoped_ptr<AdaptiveVideoSource> sources[kNumFlows];
+ rtc::scoped_ptr<VideoSender> senders[kNumFlows];
+ rtc::scoped_ptr<MetricRecorder> metric_recorders[kNumFlows];
+ rtc::scoped_ptr<PacketReceiver> receivers[kNumFlows];
+
+ sources[kForward].reset(new AdaptiveVideoSource(kForward, 30, 300, 0, 0));
+ senders[kForward].reset(
+ new VideoSender(&uplink_, sources[kForward].get(), bwe_type));
+
+ sources[kBackward].reset(new AdaptiveVideoSource(kBackward, 30, 300, 0, 0));
+ senders[kBackward].reset(
+ new VideoSender(&downlink_, sources[kBackward].get(), bwe_type));
+
+ DefaultEvaluationFilter up_filter(&uplink_, kForward);
+ LinkShare up_link_share(&(up_filter.choke));
+
+ metric_recorders[kForward].reset(new MetricRecorder(
+ bwe_names[bwe_type], kForward, senders[kForward].get(), &up_link_share));
+ receivers[kForward].reset(
+ new PacketReceiver(&uplink_, kForward, bwe_type, true, false,
+ metric_recorders[kForward].get()));
+
+ metric_recorders[kForward].get()->set_plot_available_capacity(
+ plot_total_available_capacity_);
+
+ DefaultEvaluationFilter down_filter(&downlink_, kBackward);
+ LinkShare down_link_share(&(down_filter.choke));
+
+ metric_recorders[kBackward].reset(
+ new MetricRecorder(bwe_names[bwe_type], kBackward,
+ senders[kBackward].get(), &down_link_share));
+ receivers[kBackward].reset(
+ new PacketReceiver(&downlink_, kBackward, bwe_type, true, false,
+ metric_recorders[kBackward].get()));
+
+ metric_recorders[kBackward].get()->set_plot_available_capacity(
+ plot_total_available_capacity_);
+
+ // Test also with one way propagation delay = 100ms.
+ // up_filter.delay.SetOneWayDelayMs(100);
+ // down_filter.delay.SetOneWayDelayMs(100);
+
+ up_filter.choke.set_capacity_kbps(2000);
+ down_filter.choke.set_capacity_kbps(2000);
+ RunFor(20 * 1000); // 0-20s.
+
+ up_filter.choke.set_capacity_kbps(1000);
+ RunFor(15 * 1000); // 20-35s.
+
+ down_filter.choke.set_capacity_kbps(800);
+ RunFor(5 * 1000); // 35-40s.
+
+ up_filter.choke.set_capacity_kbps(500);
+ RunFor(20 * 1000); // 40-60s.
+
+ up_filter.choke.set_capacity_kbps(2000);
+ RunFor(10 * 1000); // 60-70s.
+
+ down_filter.choke.set_capacity_kbps(2000);
+ RunFor(30 * 1000); // 70-100s.
+
+ std::string title("5.3_Bidirectional_flows");
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ metric_recorders[i].get()->PlotThroughputHistogram(
+ title, bwe_names[bwe_type], kNumFlows, 0);
+ metric_recorders[i].get()->PlotDelayHistogram(title, bwe_names[bwe_type],
+ kNumFlows, kOneWayDelayMs);
+ metric_recorders[i].get()->PlotLossHistogram(
+ title, bwe_names[bwe_type], kNumFlows,
+ receivers[i].get()->GlobalPacketLoss());
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], kOneWayDelayMs, i);
+ }
+}
+
+// 5.4. Three forward direction competing flows, constant capacity.
+void BweTest::RunSelfFairness(BandwidthEstimatorType bwe_type) {
+ const int kNumRmcatFlows = 3;
+ const int kNumTcpFlows = 0;
+ const int64_t kRunTimeS = 120;
+ const int kLinkCapacity = 3500;
+
+ int64_t max_delay_ms = kMaxQueueingDelayMs;
+ int64_t rtt_ms = 2 * kOneWayDelayMs;
+
+ const int64_t kStartingApartMs = 20 * 1000;
+ int64_t offsets_ms[kNumRmcatFlows];
+ for (int i = 0; i < kNumRmcatFlows; ++i) {
+ offsets_ms[i] = kStartingApartMs * i;
+ }
+
+ // Test also with one way propagation delay = 100ms.
+ // rtt_ms = 2 * 100;
+ // Test also with bottleneck queue size = 20ms and 1000ms.
+ // max_delay_ms = 20;
+ // max_delay_ms = 1000;
+
+ std::string title("5.4_Self_fairness_test");
+
+ // Test also with one way propagation delay = 100ms.
+ RunFairnessTest(bwe_type, kNumRmcatFlows, kNumTcpFlows, kRunTimeS,
+ kLinkCapacity, max_delay_ms, rtt_ms, kMaxJitterMs, offsets_ms,
+ title, bwe_names[bwe_type]);
+}
+
+// 5.5. Five competing RMCAT flows under different RTTs.
+void BweTest::RunRoundTripTimeFairness(BandwidthEstimatorType bwe_type) {
+ const int kAllFlowIds[] = {0, 1, 2, 3, 4}; // Five RMCAT flows.
+ const int64_t kAllOneWayDelayMs[] = {10, 25, 50, 100, 150};
+ const size_t kNumFlows = ARRAY_SIZE(kAllFlowIds);
+ rtc::scoped_ptr<AdaptiveVideoSource> sources[kNumFlows];
+ rtc::scoped_ptr<VideoSender> senders[kNumFlows];
+ rtc::scoped_ptr<MetricRecorder> metric_recorders[kNumFlows];
+
+ // Flows initialized 10 seconds apart.
+ const int64_t kStartingApartMs = 10 * 1000;
+
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ sources[i].reset(new AdaptiveVideoSource(kAllFlowIds[i], 30, 300, 0,
+ i * kStartingApartMs));
+ senders[i].reset(new VideoSender(&uplink_, sources[i].get(), bwe_type));
+ }
+
+ ChokeFilter choke_filter(&uplink_, CreateFlowIds(kAllFlowIds, kNumFlows));
+ LinkShare link_share(&choke_filter);
+
+ JitterFilter jitter_filter(&uplink_, CreateFlowIds(kAllFlowIds, kNumFlows));
+
+ rtc::scoped_ptr<DelayFilter> up_delay_filters[kNumFlows];
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ up_delay_filters[i].reset(new DelayFilter(&uplink_, kAllFlowIds[i]));
+ }
+
+ RateCounterFilter total_utilization(
+ &uplink_, CreateFlowIds(kAllFlowIds, kNumFlows), "Total_utilization",
+ "Total_link_utilization");
+
+ // Delays is being plotted only for the first flow.
+ // To plot all of them, replace "i == 0" with "true" on new PacketReceiver().
+ rtc::scoped_ptr<PacketReceiver> receivers[kNumFlows];
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ metric_recorders[i].reset(
+ new MetricRecorder(bwe_names[bwe_type], static_cast<int>(i),
+ senders[i].get(), &link_share));
+
+ receivers[i].reset(new PacketReceiver(&uplink_, kAllFlowIds[i], bwe_type,
+ i == 0, false,
+ metric_recorders[i].get()));
+ metric_recorders[i].get()->set_start_computing_metrics_ms(kStartingApartMs *
+ (kNumFlows - 1));
+ metric_recorders[i].get()->set_plot_available_capacity(
+ i == 0 && plot_total_available_capacity_);
+ }
+
+ rtc::scoped_ptr<DelayFilter> down_delay_filters[kNumFlows];
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ down_delay_filters[i].reset(new DelayFilter(&downlink_, kAllFlowIds[i]));
+ }
+
+ jitter_filter.SetMaxJitter(kMaxJitterMs);
+ choke_filter.set_max_delay_ms(kMaxQueueingDelayMs);
+
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ up_delay_filters[i]->SetOneWayDelayMs(kAllOneWayDelayMs[i]);
+ down_delay_filters[i]->SetOneWayDelayMs(kAllOneWayDelayMs[i]);
+ }
+
+ choke_filter.set_capacity_kbps(3500);
+
+ RunFor(300 * 1000); // 0-300s.
+
+ std::string title("5.5_Round_Trip_Time_Fairness");
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ metric_recorders[i].get()->PlotThroughputHistogram(
+ title, bwe_names[bwe_type], kNumFlows, 0);
+ metric_recorders[i].get()->PlotDelayHistogram(title, bwe_names[bwe_type],
+ kNumFlows, kOneWayDelayMs);
+ metric_recorders[i].get()->PlotLossHistogram(
+ title, bwe_names[bwe_type], kNumFlows,
+ receivers[i].get()->GlobalPacketLoss());
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], kAllOneWayDelayMs[i],
+ i);
+ }
+}
+
+// 5.6. RMCAT Flow competing with a long TCP Flow.
+void BweTest::RunLongTcpFairness(BandwidthEstimatorType bwe_type) {
+ const size_t kNumRmcatFlows = 1;
+ const size_t kNumTcpFlows = 1;
+ const int64_t kRunTimeS = 120;
+ const int kCapacityKbps = 2000;
+ // Tcp starts at t = 0, media flow at t = 5s.
+ const int64_t kOffSetsMs[] = {5000, 0};
+
+ int64_t max_delay_ms = kMaxQueueingDelayMs;
+ int64_t rtt_ms = 2 * kOneWayDelayMs;
+
+ // Test also with one way propagation delay = 100ms.
+ // rtt_ms = 2 * 100;
+ // Test also with bottleneck queue size = 20ms and 1000ms.
+ // max_delay_ms = 20;
+ // max_delay_ms = 1000;
+
+ std::string title("5.6_Long_TCP_Fairness");
+ std::string flow_name(bwe_names[bwe_type] + 'x' + bwe_names[kTcpEstimator]);
+
+ RunFairnessTest(bwe_type, kNumRmcatFlows, kNumTcpFlows, kRunTimeS,
+ kCapacityKbps, max_delay_ms, rtt_ms, kMaxJitterMs, kOffSetsMs,
+ title, flow_name);
+}
+
+// 5.7. RMCAT Flows competing with multiple short TCP Flows.
+void BweTest::RunMultipleShortTcpFairness(
+ BandwidthEstimatorType bwe_type,
+ std::vector<int> tcp_file_sizes_bytes,
+ std::vector<int64_t> tcp_starting_times_ms) {
+ // Two RMCAT flows and ten TCP flows.
+ const int kAllRmcatFlowIds[] = {0, 1};
+ const int kAllTcpFlowIds[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+
+ assert(tcp_starting_times_ms.size() == tcp_file_sizes_bytes.size() &&
+ tcp_starting_times_ms.size() == ARRAY_SIZE(kAllTcpFlowIds));
+
+ const size_t kNumRmcatFlows = ARRAY_SIZE(kAllRmcatFlowIds);
+ const size_t kNumTotalFlows = kNumRmcatFlows + ARRAY_SIZE(kAllTcpFlowIds);
+
+ rtc::scoped_ptr<AdaptiveVideoSource> sources[kNumRmcatFlows];
+ rtc::scoped_ptr<PacketSender> senders[kNumTotalFlows];
+ rtc::scoped_ptr<MetricRecorder> metric_recorders[kNumTotalFlows];
+ rtc::scoped_ptr<PacketReceiver> receivers[kNumTotalFlows];
+
+ // RMCAT Flows are initialized simultaneosly at t=5 seconds.
+ const int64_t kRmcatStartingTimeMs = 5 * 1000;
+ for (size_t id : kAllRmcatFlowIds) {
+ sources[id].reset(new AdaptiveVideoSource(static_cast<int>(id), 30, 300, 0,
+ kRmcatStartingTimeMs));
+ senders[id].reset(new VideoSender(&uplink_, sources[id].get(), bwe_type));
+ }
+
+ for (size_t id : kAllTcpFlowIds) {
+ senders[id].reset(new TcpSender(&uplink_, static_cast<int>(id),
+ tcp_starting_times_ms[id - kNumRmcatFlows],
+ tcp_file_sizes_bytes[id - kNumRmcatFlows]));
+ }
+
+ FlowIds flow_ids = CreateFlowIdRange(0, static_cast<int>(kNumTotalFlows - 1));
+ DefaultEvaluationFilter up_filter(&uplink_, flow_ids);
+
+ LinkShare link_share(&(up_filter.choke));
+
+ RateCounterFilter total_utilization(&uplink_, flow_ids, "Total_utilization",
+ "Total_link_utilization");
+
+ // Delays is being plotted only for the first flow.
+ // To plot all of them, replace "i == 0" with "true" on new PacketReceiver().
+ for (size_t id : kAllRmcatFlowIds) {
+ metric_recorders[id].reset(
+ new MetricRecorder(bwe_names[bwe_type], static_cast<int>(id),
+ senders[id].get(), &link_share));
+ receivers[id].reset(new PacketReceiver(&uplink_, static_cast<int>(id),
+ bwe_type, id == 0, false,
+ metric_recorders[id].get()));
+ metric_recorders[id].get()->set_start_computing_metrics_ms(
+ kRmcatStartingTimeMs);
+ metric_recorders[id].get()->set_plot_available_capacity(
+ id == 0 && plot_total_available_capacity_);
+ }
+
+ // Delays is not being plotted only for TCP flows. To plot all of them,
+ // replace first "false" occurence with "true" on new PacketReceiver().
+ for (size_t id : kAllTcpFlowIds) {
+ metric_recorders[id].reset(
+ new MetricRecorder(bwe_names[kTcpEstimator], static_cast<int>(id),
+ senders[id].get(), &link_share));
+ receivers[id].reset(new PacketReceiver(&uplink_, static_cast<int>(id),
+ kTcpEstimator, false, false,
+ metric_recorders[id].get()));
+ metric_recorders[id].get()->set_plot_available_capacity(
+ id == 0 && plot_total_available_capacity_);
+ }
+
+ DelayFilter down_filter(&downlink_, flow_ids);
+ down_filter.SetOneWayDelayMs(kOneWayDelayMs);
+
+ // Test also with one way propagation delay = 100ms.
+ // up_filter.delay.SetOneWayDelayMs(100);
+ // down_filter.SetOneWayDelayms(100);
+
+ // Test also with bottleneck queue size = 20ms and 1000ms.
+ // up_filter.choke.set_max_delay_ms(20);
+ // up_filter.choke.set_max_delay_ms(1000);
+
+ // Test also with no Jitter:
+ // up_filter.jitter.SetMaxJitter(0);
+
+ up_filter.choke.set_capacity_kbps(2000);
+
+ RunFor(300 * 1000); // 0-300s.
+
+ std::string title("5.7_Multiple_short_TCP_flows");
+ for (size_t id : kAllRmcatFlowIds) {
+ metric_recorders[id].get()->PlotThroughputHistogram(
+ title, bwe_names[bwe_type], kNumRmcatFlows, 0);
+ metric_recorders[id].get()->PlotDelayHistogram(
+ title, bwe_names[bwe_type], kNumRmcatFlows, kOneWayDelayMs);
+ metric_recorders[id].get()->PlotLossHistogram(
+ title, bwe_names[bwe_type], kNumRmcatFlows,
+ receivers[id].get()->GlobalPacketLoss());
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], kOneWayDelayMs, id);
+ }
+}
+
+// 5.8. Three forward direction competing flows, constant capacity.
+// During the test, one of the flows is paused and later resumed.
+void BweTest::RunPauseResumeFlows(BandwidthEstimatorType bwe_type) {
+ const int kAllFlowIds[] = {0, 1, 2}; // Three RMCAT flows.
+ const size_t kNumFlows = ARRAY_SIZE(kAllFlowIds);
+
+ rtc::scoped_ptr<AdaptiveVideoSource> sources[kNumFlows];
+ rtc::scoped_ptr<VideoSender> senders[kNumFlows];
+ rtc::scoped_ptr<MetricRecorder> metric_recorders[kNumFlows];
+ rtc::scoped_ptr<PacketReceiver> receivers[kNumFlows];
+
+ // Flows initialized simultaneously.
+ const int64_t kStartingApartMs = 0;
+
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ sources[i].reset(new AdaptiveVideoSource(kAllFlowIds[i], 30, 300, 0,
+ i * kStartingApartMs));
+ senders[i].reset(new VideoSender(&uplink_, sources[i].get(), bwe_type));
+ }
+
+ DefaultEvaluationFilter filter(&uplink_,
+ CreateFlowIds(kAllFlowIds, kNumFlows));
+
+ LinkShare link_share(&(filter.choke));
+
+ RateCounterFilter total_utilization(
+ &uplink_, CreateFlowIds(kAllFlowIds, kNumFlows), "Total_utilization",
+ "Total_link_utilization");
+
+ // Delays is being plotted only for the first flow.
+ // To plot all of them, replace "i == 0" with "true" on new PacketReceiver().
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ metric_recorders[i].reset(
+ new MetricRecorder(bwe_names[bwe_type], static_cast<int>(i),
+ senders[i].get(), &link_share));
+ receivers[i].reset(new PacketReceiver(&uplink_, kAllFlowIds[i], bwe_type,
+ i == 0, false,
+ metric_recorders[i].get()));
+ metric_recorders[i].get()->set_start_computing_metrics_ms(kStartingApartMs *
+ (kNumFlows - 1));
+ metric_recorders[i].get()->set_plot_available_capacity(
+ i == 0 && plot_total_available_capacity_);
+ }
+
+ // Test also with one way propagation delay = 100ms.
+ // filter.delay.SetOneWayDelayMs(100);
+ filter.choke.set_capacity_kbps(3500);
+
+ RunFor(40 * 1000); // 0-40s.
+ senders[0].get()->Pause();
+ RunFor(20 * 1000); // 40-60s.
+ senders[0].get()->Resume(20 * 1000);
+ RunFor(60 * 1000); // 60-120s.
+
+ int64_t paused[] = {20 * 1000, 0, 0};
+
+ // First flow is being paused, hence having a different optimum.
+ const std::string optima_lines[] = {"1", "2", "2"};
+
+ std::string title("5.8_Pause_and_resume_media_flow");
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ metric_recorders[i].get()->PlotThroughputHistogram(
+ title, bwe_names[bwe_type], kNumFlows, paused[i], optima_lines[i]);
+ metric_recorders[i].get()->PlotDelayHistogram(title, bwe_names[bwe_type],
+ kNumFlows, kOneWayDelayMs);
+ metric_recorders[i].get()->PlotLossHistogram(
+ title, bwe_names[bwe_type], kNumFlows,
+ receivers[i].get()->GlobalPacketLoss());
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], kOneWayDelayMs, i);
+ }
+}
+
+// Following functions are used for randomizing TCP file size and
+// starting time, used on 5.7 RunMultipleShortTcpFairness.
+// They are pseudo-random generators, creating always the same
+// value sequence for a given Random seed.
+
+std::vector<int> BweTest::GetFileSizesBytes(int num_files) {
+ // File size chosen from uniform distribution between [100,1000] kB.
+ const int kMinKbytes = 100;
+ const int kMaxKbytes = 1000;
+
+ test::Random random(0x12345678);
+ std::vector<int> tcp_file_sizes_bytes;
+
+ while (num_files-- > 0) {
+ tcp_file_sizes_bytes.push_back(random.Rand(kMinKbytes, kMaxKbytes) * 1000);
+ }
+
+ return tcp_file_sizes_bytes;
+}
+
+std::vector<int64_t> BweTest::GetStartingTimesMs(int num_files) {
+ // OFF state behaves as an exp. distribution with mean = 10 seconds.
+ const float kMeanMs = 10000.0f;
+ test::Random random(0x12345678);
+
+ std::vector<int64_t> tcp_starting_times_ms;
+
+ // Two TCP Flows are initialized simultaneosly at t=0 seconds.
+ for (int i = 0; i < 2; ++i, --num_files) {
+ tcp_starting_times_ms.push_back(0);
+ }
+
+ // Other TCP Flows are initialized in an OFF state.
+ while (num_files-- > 0) {
+ tcp_starting_times_ms.push_back(
+ static_cast<int64_t>(random.Exponential(1.0f / kMeanMs)));
+ }
+
+ return tcp_starting_times_ms;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test.h
new file mode 100644
index 0000000000..5fb3252195
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_H_
+
+#include <map>
+#include <string>
+#include <vector>
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+
+namespace webrtc {
+
+namespace testing {
+namespace bwe {
+
+class BweReceiver;
+class PacketReceiver;
+class PacketSender;
+
+class PacketProcessorRunner {
+ public:
+ explicit PacketProcessorRunner(PacketProcessor* processor);
+ ~PacketProcessorRunner();
+
+ bool RunsProcessor(const PacketProcessor* processor) const;
+ void RunFor(int64_t time_ms, int64_t time_now_ms, Packets* in_out);
+
+ private:
+ void FindPacketsToProcess(const FlowIds& flow_ids, Packets* in, Packets* out);
+ void QueuePackets(Packets* batch, int64_t end_of_batch_time_us);
+
+ PacketProcessor* processor_;
+ Packets queue_;
+};
+
+class Link : public PacketProcessorListener {
+ public:
+ virtual ~Link() {}
+
+ virtual void AddPacketProcessor(PacketProcessor* processor,
+ ProcessorType type);
+ virtual void RemovePacketProcessor(PacketProcessor* processor);
+
+ void Run(int64_t run_for_ms, int64_t now_ms, Packets* packets);
+
+ const std::vector<PacketSender*>& senders() { return senders_; }
+ const std::vector<PacketProcessorRunner>& processors() { return processors_; }
+
+ private:
+ std::vector<PacketSender*> senders_;
+ std::vector<PacketReceiver*> receivers_;
+ std::vector<PacketProcessorRunner> processors_;
+};
+
+class BweTest {
+ public:
+ BweTest();
+ explicit BweTest(bool plot_capacity);
+ ~BweTest();
+
+ void RunChoke(BandwidthEstimatorType bwe_type,
+ std::vector<int> capacities_kbps);
+
+ void RunVariableCapacity1SingleFlow(BandwidthEstimatorType bwe_type);
+ void RunVariableCapacity2MultipleFlows(BandwidthEstimatorType bwe_type,
+ size_t num_flows);
+ void RunBidirectionalFlow(BandwidthEstimatorType bwe_type);
+ void RunSelfFairness(BandwidthEstimatorType bwe_type);
+ void RunRoundTripTimeFairness(BandwidthEstimatorType bwe_type);
+ void RunLongTcpFairness(BandwidthEstimatorType bwe_type);
+ void RunMultipleShortTcpFairness(BandwidthEstimatorType bwe_type,
+ std::vector<int> tcp_file_sizes_bytes,
+ std::vector<int64_t> tcp_starting_times_ms);
+ void RunPauseResumeFlows(BandwidthEstimatorType bwe_type);
+
+ void RunFairnessTest(BandwidthEstimatorType bwe_type,
+ size_t num_media_flows,
+ size_t num_tcp_flows,
+ int64_t run_time_seconds,
+ uint32_t capacity_kbps,
+ int64_t max_delay_ms,
+ int64_t rtt_ms,
+ int64_t max_jitter_ms,
+ const int64_t* offsets_ms);
+
+ void RunFairnessTest(BandwidthEstimatorType bwe_type,
+ size_t num_media_flows,
+ size_t num_tcp_flows,
+ int64_t run_time_seconds,
+ uint32_t capacity_kbps,
+ int64_t max_delay_ms,
+ int64_t rtt_ms,
+ int64_t max_jitter_ms,
+ const int64_t* offsets_ms,
+ const std::string& title,
+ const std::string& flow_name);
+
+ static std::vector<int> GetFileSizesBytes(int num_files);
+ static std::vector<int64_t> GetStartingTimesMs(int num_files);
+
+ protected:
+ void SetUp();
+
+ void VerboseLogging(bool enable);
+ void RunFor(int64_t time_ms);
+ std::string GetTestName() const;
+
+ void PrintResults(double max_throughput_kbps,
+ Stats<double> throughput_kbps,
+ int flow_id,
+ Stats<double> flow_delay_ms,
+ Stats<double> flow_throughput_kbps);
+
+ void PrintResults(double max_throughput_kbps,
+ Stats<double> throughput_kbps,
+ std::map<int, Stats<double>> flow_delay_ms,
+ std::map<int, Stats<double>> flow_throughput_kbps);
+
+ Link downlink_;
+ Link uplink_;
+
+ private:
+ void FindPacketsToProcess(const FlowIds& flow_ids, Packets* in,
+ Packets* out);
+ void GiveFeedbackToAffectedSenders(PacketReceiver* receiver);
+
+ int64_t run_time_ms_;
+ int64_t time_now_ms_;
+ int64_t simulation_interval_ms_;
+ std::vector<Link*> links_;
+ Packets packets_;
+ bool plot_total_available_capacity_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(BweTest);
+};
+
+// Default Evaluation parameters:
+// Link capacity: 4000ms;
+// Queueing delay capacity: 300ms.
+// One-Way propagation delay: 50ms.
+// Jitter model: Truncated gaussian.
+// Maximum end-to-end jitter: 30ms = 2*standard_deviation.
+// Bottleneck queue type: Drop tail.
+// Path loss ratio: 0%.
+
+const int kOneWayDelayMs = 50;
+const int kMaxQueueingDelayMs = 300;
+const int kMaxCapacityKbps = 4000;
+const int kMaxJitterMs = 15;
+
+struct DefaultEvaluationFilter {
+ DefaultEvaluationFilter(PacketProcessorListener* listener, int flow_id)
+ : choke(listener, flow_id),
+ delay(listener, flow_id),
+ jitter(listener, flow_id) {
+ SetDefaultParameters();
+ }
+
+ DefaultEvaluationFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : choke(listener, flow_ids),
+ delay(listener, flow_ids),
+ jitter(listener, flow_ids) {
+ SetDefaultParameters();
+ }
+
+ void SetDefaultParameters() {
+ delay.SetOneWayDelayMs(kOneWayDelayMs);
+ choke.set_max_delay_ms(kMaxQueueingDelayMs);
+ choke.set_capacity_kbps(kMaxCapacityKbps);
+ jitter.SetMaxJitter(kMaxJitterMs);
+ }
+
+ ChokeFilter choke;
+ DelayFilter delay;
+ JitterFilter jitter;
+};
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc
new file mode 100644
index 0000000000..d7abede707
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2013 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 "webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.h"
+
+#include <stdio.h>
+
+#include <algorithm>
+#include <vector>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "webrtc/test/testsupport/fileutils.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+// The format of BWE test baseline files is extremely simple:
+// 1. All read/written entities are 32-bit unsigned integers in network byte
+// order (Big Endian).
+// 2. Files beging with a 2 word header containing a magic marker and file
+// format version indicator. The Magic marker reads "BWE!" in a hex dump.
+// 3. Each estimate is logged as a pair of words: time in milliseconds and
+// estimated bit rate, in bits per second.
+const uint32_t kMagicMarker = 0x42574521;
+const uint32_t kFileVersion1 = 0x00000001;
+const char kResourceSubDir[] = "remote_bitrate_estimator";
+
+class BaseLineFileVerify : public BaseLineFileInterface {
+ public:
+ // If |allow_missing_file| is set, VerifyOrWrite() will return true even if
+ // the baseline file is missing. This is the default when verifying files, but
+ // not when updating (i.e. we always write it out if missing).
+ BaseLineFileVerify(const std::string& filepath, bool allow_missing_file)
+ : reader_(),
+ fail_to_read_response_(false) {
+ rtc::scoped_ptr<ResourceFileReader> reader;
+ reader.reset(ResourceFileReader::Create(filepath, "bin"));
+ if (!reader.get()) {
+ printf("WARNING: Missing baseline file for BWE test: %s.bin\n",
+ filepath.c_str());
+ fail_to_read_response_ = allow_missing_file;
+ } else {
+ uint32_t magic_marker = 0;
+ uint32_t file_version = 0;
+ if (reader->Read(&magic_marker) && magic_marker == kMagicMarker &&
+ reader->Read(&file_version) && file_version == kFileVersion1) {
+ reader_.swap(reader);
+ } else {
+ printf("WARNING: Bad baseline file header for BWE test: %s.bin\n",
+ filepath.c_str());
+ }
+ }
+ }
+ virtual ~BaseLineFileVerify() {}
+
+ virtual void Estimate(int64_t time_ms, uint32_t estimate_bps) {
+ if (reader_.get()) {
+ uint32_t read_ms = 0;
+ uint32_t read_bps = 0;
+ if (reader_->Read(&read_ms) && read_ms == time_ms &&
+ reader_->Read(&read_bps) && read_bps == estimate_bps) {
+ } else {
+ printf("ERROR: Baseline differs starting at: %d ms (%d vs %d)!\n",
+ static_cast<uint32_t>(time_ms), estimate_bps, read_bps);
+ reader_.reset(NULL);
+ }
+ }
+ }
+
+ virtual bool VerifyOrWrite() {
+ if (reader_.get()) {
+ if (reader_->IsAtEnd()) {
+ return true;
+ } else {
+ printf("ERROR: Baseline file contains more data!\n");
+ return false;
+ }
+ }
+ return fail_to_read_response_;
+ }
+
+ private:
+ rtc::scoped_ptr<ResourceFileReader> reader_;
+ bool fail_to_read_response_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BaseLineFileVerify);
+};
+
+class BaseLineFileUpdate : public BaseLineFileInterface {
+ public:
+ BaseLineFileUpdate(const std::string& filepath,
+ BaseLineFileInterface* verifier)
+ : verifier_(verifier),
+ output_content_(),
+ filepath_(filepath) {
+ output_content_.push_back(kMagicMarker);
+ output_content_.push_back(kFileVersion1);
+ }
+ virtual ~BaseLineFileUpdate() {}
+
+ virtual void Estimate(int64_t time_ms, uint32_t estimate_bps) {
+ verifier_->Estimate(time_ms, estimate_bps);
+ output_content_.push_back(static_cast<uint32_t>(time_ms));
+ output_content_.push_back(estimate_bps);
+ }
+
+ virtual bool VerifyOrWrite() {
+ if (!verifier_->VerifyOrWrite()) {
+ std::string dir_path = webrtc::test::OutputPath() + kResourceSubDir;
+ if (!webrtc::test::CreateDir(dir_path)) {
+ printf("WARNING: Cannot create output dir: %s\n", dir_path.c_str());
+ return false;
+ }
+ rtc::scoped_ptr<OutputFileWriter> writer;
+ writer.reset(OutputFileWriter::Create(filepath_, "bin"));
+ if (!writer.get()) {
+ printf("WARNING: Cannot create output file: %s.bin\n",
+ filepath_.c_str());
+ return false;
+ }
+ printf("NOTE: Writing baseline file for BWE test: %s.bin\n",
+ filepath_.c_str());
+ for (std::vector<uint32_t>::iterator it = output_content_.begin();
+ it != output_content_.end(); ++it) {
+ writer->Write(*it);
+ }
+ return true;
+ }
+ printf("NOTE: No change, not writing: %s\n", filepath_.c_str());
+ return true;
+ }
+
+ private:
+ rtc::scoped_ptr<BaseLineFileInterface> verifier_;
+ std::vector<uint32_t> output_content_;
+ std::string filepath_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BaseLineFileUpdate);
+};
+
+BaseLineFileInterface* BaseLineFileInterface::Create(
+ const std::string& filename, bool write_output_file) {
+ std::string filepath = filename;
+ std::replace(filepath.begin(), filepath.end(), '/', '_');
+ filepath = std::string(kResourceSubDir) + "/" + filepath;
+
+ rtc::scoped_ptr<BaseLineFileInterface> result;
+ result.reset(new BaseLineFileVerify(filepath, !write_output_file));
+ if (write_output_file) {
+ // Takes ownership of the |verifier| instance.
+ result.reset(new BaseLineFileUpdate(filepath, result.release()));
+ }
+ return result.release();
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.h
new file mode 100644
index 0000000000..64dfa85535
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_BASELINEFILE_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_BASELINEFILE_H_
+
+#include <string>
+#include "webrtc/modules/interface/module_common_types.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class BaseLineFileInterface {
+ public:
+ virtual ~BaseLineFileInterface() {}
+
+ // Compare, or log, one estimate against the baseline file.
+ virtual void Estimate(int64_t time_ms, uint32_t estimate_bps) = 0;
+
+ // Verify whether there are any differences between the logged estimates and
+ // those read from the baseline file. If updating the baseline file, write out
+ // new file if there were differences. Return true if logged estimates are
+ // identical, or if output file was updated successfully.
+ virtual bool VerifyOrWrite() = 0;
+
+ // Create an instance for either verifying estimates against a baseline file
+ // with name |filename|, living in the resources/ directory or, if the flag
+ // |write_updated_file| is set, write logged estimates to a file with the same
+ // name, living in the out/ directory.
+ static BaseLineFileInterface* Create(const std::string& filename,
+ bool write_updated_file);
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_BASELINEFILE_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.cc
new file mode 100644
index 0000000000..5744c9c320
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.cc
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2013 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 "webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h"
+
+#ifdef WIN32
+#include <Winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+#include <assert.h>
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "webrtc/test/testsupport/fileutils.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+ResourceFileReader::~ResourceFileReader() {
+ if (file_ != NULL) {
+ fclose(file_);
+ file_ = NULL;
+ }
+}
+
+bool ResourceFileReader::IsAtEnd() {
+ int32_t current_pos = ftell(file_);
+ fseek(file_, 0, SEEK_END);
+ int32_t end_pos = ftell(file_);
+ fseek(file_, current_pos, SEEK_SET);
+ return current_pos == end_pos;
+}
+
+bool ResourceFileReader::Read(uint32_t* out) {
+ assert(out);
+ uint32_t tmp = 0;
+ if (fread(&tmp, 1, sizeof(uint32_t), file_) != sizeof(uint32_t)) {
+ printf("Error reading!\n");
+ return false;
+ }
+ *out = ntohl(tmp);
+ return true;
+}
+
+ResourceFileReader* ResourceFileReader::Create(const std::string& filename,
+ const std::string& extension) {
+ std::string filepath = webrtc::test::ResourcePath(filename, extension);
+ FILE* file = fopen(filepath.c_str(), "rb");
+ if (file == NULL) {
+ BWE_TEST_LOGGING_CONTEXT("ResourceFileReader");
+ BWE_TEST_LOGGING_LOG1("Create", "Can't read file: %s", filepath.c_str());
+ return 0;
+ } else {
+ return new ResourceFileReader(file);
+ }
+}
+
+OutputFileWriter::~OutputFileWriter() {
+ if (file_ != NULL) {
+ fclose(file_);
+ file_ = NULL;
+ }
+}
+
+bool OutputFileWriter::Write(uint32_t value) {
+ uint32_t tmp = htonl(value);
+ if (fwrite(&tmp, 1, sizeof(uint32_t), file_) != sizeof(uint32_t)) {
+ return false;
+ }
+ return true;
+}
+
+OutputFileWriter* OutputFileWriter::Create(const std::string& filename,
+ const std::string& extension) {
+ std::string filepath = webrtc::test::OutputPath() + filename + "." +
+ extension;
+ FILE* file = fopen(filepath.c_str(), "wb");
+ if (file == NULL) {
+ BWE_TEST_LOGGING_CONTEXT("OutputFileWriter");
+ BWE_TEST_LOGGING_LOG1("Create", "Can't write file: %s", filepath.c_str());
+ return NULL;
+ } else {
+ return new OutputFileWriter(file);
+ }
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h
new file mode 100644
index 0000000000..2881eba424
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_FILEUTILS_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_FILEUTILS_H_
+
+#include <stdio.h>
+
+#include <string>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/modules/interface/module_common_types.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class ResourceFileReader {
+ public:
+ ~ResourceFileReader();
+
+ bool IsAtEnd();
+ bool Read(uint32_t* out);
+
+ static ResourceFileReader* Create(const std::string& filename,
+ const std::string& extension);
+
+ private:
+ explicit ResourceFileReader(FILE* file) : file_(file) {}
+ FILE* file_;
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ResourceFileReader);
+};
+
+class OutputFileWriter {
+ public:
+ ~OutputFileWriter();
+
+ bool Write(uint32_t value);
+
+ static OutputFileWriter* Create(const std::string& filename,
+ const std::string& extension);
+
+ private:
+ explicit OutputFileWriter(FILE* file) : file_(file) {}
+ FILE* file_;
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(OutputFileWriter);
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_FILEUTILS_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc
new file mode 100644
index 0000000000..4574d3d8a1
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc
@@ -0,0 +1,817 @@
+/*
+ * Copyright (c) 2013 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 "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+
+#include <stdio.h>
+
+#include <sstream>
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class DelayCapHelper {
+ public:
+ // Max delay = 0 stands for +infinite.
+ DelayCapHelper() : max_delay_us_(0), delay_stats_() {}
+
+ void set_max_delay_ms(int64_t max_delay_ms) {
+ BWE_TEST_LOGGING_ENABLE(false);
+ BWE_TEST_LOGGING_LOG1("Max Delay", "%d ms", static_cast<int>(max_delay_ms));
+ assert(max_delay_ms >= 0);
+ max_delay_us_ = max_delay_ms * 1000;
+ }
+
+ bool ShouldSendPacket(int64_t send_time_us, int64_t arrival_time_us) {
+ int64_t packet_delay_us = send_time_us - arrival_time_us;
+ delay_stats_.Push((std::min(packet_delay_us, max_delay_us_) + 500) / 1000);
+ return (max_delay_us_ == 0 || max_delay_us_ >= packet_delay_us);
+ }
+
+ const Stats<double>& delay_stats() const {
+ return delay_stats_;
+ }
+
+ private:
+ int64_t max_delay_us_;
+ Stats<double> delay_stats_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(DelayCapHelper);
+};
+
+const FlowIds CreateFlowIds(const int *flow_ids_array, size_t num_flow_ids) {
+ FlowIds flow_ids(&flow_ids_array[0], flow_ids_array + num_flow_ids);
+ return flow_ids;
+}
+
+const FlowIds CreateFlowIdRange(int initial_value, int last_value) {
+ int size = last_value - initial_value + 1;
+ assert(size > 0);
+ int* flow_ids_array = new int[size];
+ for (int i = initial_value; i <= last_value; ++i) {
+ flow_ids_array[i - initial_value] = i;
+ }
+ return CreateFlowIds(flow_ids_array, size);
+}
+
+void RateCounter::UpdateRates(int64_t send_time_us, uint32_t payload_size) {
+ ++recently_received_packets_;
+ recently_received_bytes_ += payload_size;
+ last_accumulated_us_ = send_time_us;
+ window_.push_back(std::make_pair(send_time_us, payload_size));
+ while (!window_.empty()) {
+ const TimeSizePair& packet = window_.front();
+ if (packet.first > (last_accumulated_us_ - window_size_us_)) {
+ break;
+ }
+ assert(recently_received_packets_ >= 1);
+ assert(recently_received_bytes_ >= packet.second);
+ --recently_received_packets_;
+ recently_received_bytes_ -= packet.second;
+ window_.pop_front();
+ }
+}
+
+uint32_t RateCounter::bits_per_second() const {
+ return (8 * recently_received_bytes_) / BitrateWindowS();
+}
+
+uint32_t RateCounter::packets_per_second() const {
+ return recently_received_packets_ / BitrateWindowS();
+}
+
+double RateCounter::BitrateWindowS() const {
+ return static_cast<double>(window_size_us_) / (1000 * 1000);
+}
+
+Packet::Packet()
+ : flow_id_(0),
+ creation_time_us_(-1),
+ send_time_us_(-1),
+ sender_timestamp_us_(-1),
+ payload_size_(0),
+ paced_(false) {
+}
+
+Packet::Packet(int flow_id, int64_t send_time_us, size_t payload_size)
+ : flow_id_(flow_id),
+ creation_time_us_(send_time_us),
+ send_time_us_(send_time_us),
+ sender_timestamp_us_(send_time_us),
+ payload_size_(payload_size),
+ paced_(false) {
+}
+
+Packet::~Packet() {
+}
+
+bool Packet::operator<(const Packet& rhs) const {
+ return send_time_us_ < rhs.send_time_us_;
+}
+
+void Packet::set_send_time_us(int64_t send_time_us) {
+ assert(send_time_us >= 0);
+ send_time_us_ = send_time_us;
+}
+
+MediaPacket::MediaPacket() {
+ memset(&header_, 0, sizeof(header_));
+}
+
+MediaPacket::MediaPacket(int flow_id,
+ int64_t send_time_us,
+ size_t payload_size,
+ uint16_t sequence_number)
+ : Packet(flow_id, send_time_us, payload_size) {
+ header_ = RTPHeader();
+ header_.sequenceNumber = sequence_number;
+}
+
+MediaPacket::MediaPacket(int flow_id,
+ int64_t send_time_us,
+ size_t payload_size,
+ const RTPHeader& header)
+ : Packet(flow_id, send_time_us, payload_size), header_(header) {
+}
+
+MediaPacket::MediaPacket(int64_t send_time_us, uint16_t sequence_number)
+ : Packet(0, send_time_us, 0) {
+ header_ = RTPHeader();
+ header_.sequenceNumber = sequence_number;
+}
+
+void MediaPacket::SetAbsSendTimeMs(int64_t abs_send_time_ms) {
+ header_.extension.hasAbsoluteSendTime = true;
+ header_.extension.absoluteSendTime = ((static_cast<int64_t>(abs_send_time_ms *
+ (1 << 18)) + 500) / 1000) & 0x00fffffful;
+}
+
+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, last_send_time_ms),
+ estimated_bps_(estimated_bps),
+ report_block_(report_block) {
+}
+
+SendSideBweFeedback::SendSideBweFeedback(
+ int flow_id,
+ int64_t send_time_us,
+ int64_t last_send_time_ms,
+ const std::vector<PacketInfo>& packet_feedback_vector)
+ : FeedbackPacket(flow_id, send_time_us, last_send_time_ms),
+ packet_feedback_vector_(packet_feedback_vector) {
+}
+
+bool IsTimeSorted(const Packets& packets) {
+ PacketsConstIt last_it = packets.begin();
+ for (PacketsConstIt it = last_it; it != packets.end(); ++it) {
+ if (it != last_it && **it < **last_it) {
+ return false;
+ }
+ last_it = it;
+ }
+ return true;
+}
+
+PacketProcessor::PacketProcessor(PacketProcessorListener* listener,
+ int flow_id,
+ ProcessorType type)
+ : listener_(listener), flow_ids_(&flow_id, &flow_id + 1) {
+ if (listener_) {
+ listener_->AddPacketProcessor(this, type);
+ }
+}
+
+PacketProcessor::PacketProcessor(PacketProcessorListener* listener,
+ const FlowIds& flow_ids,
+ ProcessorType type)
+ : listener_(listener), flow_ids_(flow_ids) {
+ if (listener_) {
+ listener_->AddPacketProcessor(this, type);
+ }
+}
+
+PacketProcessor::~PacketProcessor() {
+ if (listener_) {
+ listener_->RemovePacketProcessor(this);
+ }
+}
+
+uint32_t PacketProcessor::packets_per_second() const {
+ return rate_counter_.packets_per_second();
+}
+
+uint32_t PacketProcessor::bits_per_second() const {
+ return rate_counter_.bits_per_second();
+}
+
+RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener,
+ int flow_id,
+ const char* name,
+ const std::string& plot_name)
+ : PacketProcessor(listener, flow_id, kRegular),
+ packets_per_second_stats_(),
+ kbps_stats_(),
+ start_plotting_time_ms_(0),
+ plot_name_(plot_name) {
+ std::stringstream ss;
+ ss << name << "_" << flow_id;
+ name_ = ss.str();
+}
+
+RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids,
+ const char* name,
+ const std::string& plot_name)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ packets_per_second_stats_(),
+ kbps_stats_(),
+ start_plotting_time_ms_(0),
+ plot_name_(plot_name) {
+ std::stringstream ss;
+ ss << name;
+ char delimiter = '_';
+ for (int flow_id : flow_ids) {
+ ss << delimiter << flow_id;
+ delimiter = ',';
+ }
+ name_ = ss.str();
+}
+
+RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids,
+ const char* name,
+ int64_t start_plotting_time_ms,
+ const std::string& plot_name)
+ : RateCounterFilter(listener, flow_ids, name, plot_name) {
+ start_plotting_time_ms_ = start_plotting_time_ms;
+}
+
+RateCounterFilter::~RateCounterFilter() {
+ LogStats();
+}
+
+
+void RateCounterFilter::LogStats() {
+ BWE_TEST_LOGGING_CONTEXT("RateCounterFilter");
+ packets_per_second_stats_.Log("pps");
+ kbps_stats_.Log("kbps");
+}
+
+Stats<double> RateCounterFilter::GetBitrateStats() const {
+ return kbps_stats_;
+}
+
+void RateCounterFilter::Plot(int64_t timestamp_ms) {
+ uint32_t plot_kbps = 0;
+ if (timestamp_ms >= start_plotting_time_ms_) {
+ plot_kbps = rate_counter_.bits_per_second() / 1000.0;
+ }
+ BWE_TEST_LOGGING_CONTEXT(name_.c_str());
+ if (plot_name_.empty()) {
+ BWE_TEST_LOGGING_PLOT(0, "Throughput_kbps#1", timestamp_ms, plot_kbps);
+ } else {
+ BWE_TEST_LOGGING_PLOT_WITH_NAME(0, "Throughput_kbps#1", timestamp_ms,
+ plot_kbps, plot_name_);
+ }
+
+ RTC_UNUSED(plot_kbps);
+}
+
+void RateCounterFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) {
+ assert(in_out);
+ for (const Packet* packet : *in_out) {
+ rate_counter_.UpdateRates(packet->send_time_us(),
+ static_cast<int>(packet->payload_size()));
+ }
+ packets_per_second_stats_.Push(rate_counter_.packets_per_second());
+ kbps_stats_.Push(rate_counter_.bits_per_second() / 1000.0);
+}
+
+LossFilter::LossFilter(PacketProcessorListener* listener, int flow_id)
+ : PacketProcessor(listener, flow_id, kRegular),
+ random_(0x12345678),
+ loss_fraction_(0.0f) {
+}
+
+LossFilter::LossFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ random_(0x12345678),
+ loss_fraction_(0.0f) {
+}
+
+void LossFilter::SetLoss(float loss_percent) {
+ BWE_TEST_LOGGING_ENABLE(false);
+ BWE_TEST_LOGGING_LOG1("Loss", "%f%%", loss_percent);
+ assert(loss_percent >= 0.0f);
+ assert(loss_percent <= 100.0f);
+ loss_fraction_ = loss_percent * 0.01f;
+}
+
+void LossFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) {
+ assert(in_out);
+ for (PacketsIt it = in_out->begin(); it != in_out->end(); ) {
+ if (random_.Rand() < loss_fraction_) {
+ delete *it;
+ it = in_out->erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+const int64_t kDefaultOneWayDelayUs = 0;
+
+DelayFilter::DelayFilter(PacketProcessorListener* listener, int flow_id)
+ : PacketProcessor(listener, flow_id, kRegular),
+ one_way_delay_us_(kDefaultOneWayDelayUs),
+ last_send_time_us_(0) {
+}
+
+DelayFilter::DelayFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ one_way_delay_us_(kDefaultOneWayDelayUs),
+ last_send_time_us_(0) {
+}
+
+void DelayFilter::SetOneWayDelayMs(int64_t one_way_delay_ms) {
+ BWE_TEST_LOGGING_ENABLE(false);
+ BWE_TEST_LOGGING_LOG1("Delay", "%d ms", static_cast<int>(one_way_delay_ms));
+ assert(one_way_delay_ms >= 0);
+ one_way_delay_us_ = one_way_delay_ms * 1000;
+}
+
+void DelayFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) {
+ assert(in_out);
+ for (Packet* packet : *in_out) {
+ int64_t new_send_time_us = packet->send_time_us() + one_way_delay_us_;
+ last_send_time_us_ = std::max(last_send_time_us_, new_send_time_us);
+ packet->set_send_time_us(last_send_time_us_);
+ }
+}
+
+JitterFilter::JitterFilter(PacketProcessorListener* listener, int flow_id)
+ : PacketProcessor(listener, flow_id, kRegular),
+ random_(0x89674523),
+ stddev_jitter_us_(0),
+ last_send_time_us_(0),
+ reordering_(false) {
+}
+
+JitterFilter::JitterFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ random_(0x89674523),
+ stddev_jitter_us_(0),
+ last_send_time_us_(0),
+ reordering_(false) {
+}
+
+const int kN = 3; // Truncated N sigma gaussian.
+
+void JitterFilter::SetMaxJitter(int64_t max_jitter_ms) {
+ BWE_TEST_LOGGING_ENABLE(false);
+ BWE_TEST_LOGGING_LOG1("Max Jitter", "%d ms", static_cast<int>(max_jitter_ms));
+ assert(max_jitter_ms >= 0);
+ // Truncated gaussian, Max jitter = kN*sigma.
+ stddev_jitter_us_ = (max_jitter_ms * 1000 + kN / 2) / kN;
+}
+
+namespace {
+inline int64_t TruncatedNSigmaGaussian(test::Random* const random,
+ int64_t mean,
+ int64_t std_dev) {
+ int64_t gaussian_random = random->Gaussian(mean, std_dev);
+ return std::max(std::min(gaussian_random, kN * std_dev), -kN * std_dev);
+}
+}
+
+void JitterFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) {
+ assert(in_out);
+ for (Packet* packet : *in_out) {
+ int64_t jitter_us =
+ std::abs(TruncatedNSigmaGaussian(&random_, 0, stddev_jitter_us_));
+ int64_t new_send_time_us = packet->send_time_us() + jitter_us;
+
+ if (!reordering_) {
+ new_send_time_us = std::max(last_send_time_us_, new_send_time_us);
+ }
+
+ // Receiver timestamp cannot be lower than sender timestamp.
+ assert(new_send_time_us >= packet->sender_timestamp_us());
+
+ packet->set_send_time_us(new_send_time_us);
+ last_send_time_us_ = new_send_time_us;
+ }
+}
+
+// Computes the expected value for a right sided (abs) truncated gaussian.
+// Does not take into account possible reoerdering updates.
+int64_t JitterFilter::MeanUs() {
+ const double kPi = 3.1415926535897932;
+ double max_jitter_us = static_cast<double>(kN * stddev_jitter_us_);
+ double right_sided_mean_us =
+ static_cast<double>(stddev_jitter_us_) / sqrt(kPi / 2.0);
+ double truncated_mean_us =
+ right_sided_mean_us *
+ (1.0 - exp(-pow(static_cast<double>(kN), 2.0) / 2.0)) +
+ max_jitter_us * erfc(static_cast<double>(kN));
+ return static_cast<int64_t>(truncated_mean_us + 0.5);
+}
+
+ReorderFilter::ReorderFilter(PacketProcessorListener* listener, int flow_id)
+ : PacketProcessor(listener, flow_id, kRegular),
+ random_(0x27452389),
+ reorder_fraction_(0.0f) {
+}
+
+ReorderFilter::ReorderFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ random_(0x27452389),
+ reorder_fraction_(0.0f) {
+}
+
+void ReorderFilter::SetReorder(float reorder_percent) {
+ BWE_TEST_LOGGING_ENABLE(false);
+ BWE_TEST_LOGGING_LOG1("Reordering", "%f%%", reorder_percent);
+ assert(reorder_percent >= 0.0f);
+ assert(reorder_percent <= 100.0f);
+ reorder_fraction_ = reorder_percent * 0.01f;
+}
+
+void ReorderFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) {
+ assert(in_out);
+ if (in_out->size() >= 2) {
+ PacketsIt last_it = in_out->begin();
+ PacketsIt it = last_it;
+ while (++it != in_out->end()) {
+ if (random_.Rand() < reorder_fraction_) {
+ int64_t t1 = (*last_it)->send_time_us();
+ int64_t t2 = (*it)->send_time_us();
+ std::swap(*last_it, *it);
+ (*last_it)->set_send_time_us(t1);
+ (*it)->set_send_time_us(t2);
+ }
+ last_it = it;
+ }
+ }
+}
+
+const uint32_t kDefaultKbps = 1200;
+
+ChokeFilter::ChokeFilter(PacketProcessorListener* listener, int flow_id)
+ : PacketProcessor(listener, flow_id, kRegular),
+ capacity_kbps_(kDefaultKbps),
+ last_send_time_us_(0),
+ delay_cap_helper_(new DelayCapHelper()) {
+}
+
+ChokeFilter::ChokeFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ capacity_kbps_(kDefaultKbps),
+ last_send_time_us_(0),
+ delay_cap_helper_(new DelayCapHelper()) {
+}
+
+ChokeFilter::~ChokeFilter() {}
+
+void ChokeFilter::set_capacity_kbps(uint32_t kbps) {
+ BWE_TEST_LOGGING_ENABLE(false);
+ BWE_TEST_LOGGING_LOG1("BitrateChoke", "%d kbps", kbps);
+ capacity_kbps_ = kbps;
+}
+
+uint32_t ChokeFilter::capacity_kbps() {
+ return capacity_kbps_;
+}
+
+void ChokeFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) {
+ assert(in_out);
+ for (PacketsIt it = in_out->begin(); it != in_out->end(); ) {
+ int64_t earliest_send_time_us =
+ std::max(last_send_time_us_, (*it)->send_time_us());
+
+ int64_t new_send_time_us =
+ earliest_send_time_us +
+ ((*it)->payload_size() * 8 * 1000 + capacity_kbps_ / 2) /
+ capacity_kbps_;
+
+ if (delay_cap_helper_->ShouldSendPacket(new_send_time_us,
+ (*it)->send_time_us())) {
+ (*it)->set_send_time_us(new_send_time_us);
+ last_send_time_us_ = new_send_time_us;
+ ++it;
+ } else {
+ delete *it;
+ it = in_out->erase(it);
+ }
+ }
+}
+
+void ChokeFilter::set_max_delay_ms(int64_t max_delay_ms) {
+ delay_cap_helper_->set_max_delay_ms(max_delay_ms);
+}
+
+Stats<double> ChokeFilter::GetDelayStats() const {
+ return delay_cap_helper_->delay_stats();
+}
+
+TraceBasedDeliveryFilter::TraceBasedDeliveryFilter(
+ PacketProcessorListener* listener,
+ int flow_id)
+ : PacketProcessor(listener, flow_id, kRegular),
+ current_offset_us_(0),
+ delivery_times_us_(),
+ next_delivery_it_(),
+ local_time_us_(-1),
+ rate_counter_(new RateCounter),
+ name_(""),
+ delay_cap_helper_(new DelayCapHelper()),
+ packets_per_second_stats_(),
+ kbps_stats_() {
+}
+
+TraceBasedDeliveryFilter::TraceBasedDeliveryFilter(
+ PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ current_offset_us_(0),
+ delivery_times_us_(),
+ next_delivery_it_(),
+ local_time_us_(-1),
+ rate_counter_(new RateCounter),
+ name_(""),
+ delay_cap_helper_(new DelayCapHelper()),
+ packets_per_second_stats_(),
+ kbps_stats_() {
+}
+
+TraceBasedDeliveryFilter::TraceBasedDeliveryFilter(
+ PacketProcessorListener* listener,
+ int flow_id,
+ const char* name)
+ : PacketProcessor(listener, flow_id, kRegular),
+ current_offset_us_(0),
+ delivery_times_us_(),
+ next_delivery_it_(),
+ local_time_us_(-1),
+ rate_counter_(new RateCounter),
+ name_(name),
+ delay_cap_helper_(new DelayCapHelper()),
+ packets_per_second_stats_(),
+ kbps_stats_() {
+}
+
+TraceBasedDeliveryFilter::~TraceBasedDeliveryFilter() {
+}
+
+bool TraceBasedDeliveryFilter::Init(const std::string& filename) {
+ FILE* trace_file = fopen(filename.c_str(), "r");
+ if (!trace_file) {
+ return false;
+ }
+ int64_t first_timestamp = -1;
+ while(!feof(trace_file)) {
+ const size_t kMaxLineLength = 100;
+ char line[kMaxLineLength];
+ if (fgets(line, kMaxLineLength, trace_file)) {
+ std::string line_string(line);
+ std::istringstream buffer(line_string);
+ int64_t timestamp;
+ buffer >> timestamp;
+ timestamp /= 1000; // Convert to microseconds.
+ if (first_timestamp == -1)
+ first_timestamp = timestamp;
+ assert(delivery_times_us_.empty() ||
+ timestamp - first_timestamp - delivery_times_us_.back() >= 0);
+ delivery_times_us_.push_back(timestamp - first_timestamp);
+ }
+ }
+ assert(!delivery_times_us_.empty());
+ next_delivery_it_ = delivery_times_us_.begin();
+ fclose(trace_file);
+ return true;
+}
+
+void TraceBasedDeliveryFilter::Plot(int64_t timestamp_ms) {
+ BWE_TEST_LOGGING_CONTEXT(name_.c_str());
+ // This plots the max possible throughput of the trace-based delivery filter,
+ // which will be reached if a packet sent on every packet slot of the trace.
+ BWE_TEST_LOGGING_PLOT(0, "MaxThroughput_#1", timestamp_ms,
+ rate_counter_->bits_per_second() / 1000.0);
+}
+
+void TraceBasedDeliveryFilter::RunFor(int64_t time_ms, Packets* in_out) {
+ assert(in_out);
+ for (PacketsIt it = in_out->begin(); it != in_out->end();) {
+ while (local_time_us_ < (*it)->send_time_us()) {
+ ProceedToNextSlot();
+ }
+ // Drop any packets that have been queued for too long.
+ while (!delay_cap_helper_->ShouldSendPacket(local_time_us_,
+ (*it)->send_time_us())) {
+ delete *it;
+ it = in_out->erase(it);
+ if (it == in_out->end()) {
+ return;
+ }
+ }
+ if (local_time_us_ >= (*it)->send_time_us()) {
+ (*it)->set_send_time_us(local_time_us_);
+ ProceedToNextSlot();
+ }
+ ++it;
+ }
+ packets_per_second_stats_.Push(rate_counter_->packets_per_second());
+ kbps_stats_.Push(rate_counter_->bits_per_second() / 1000.0);
+}
+
+void TraceBasedDeliveryFilter::set_max_delay_ms(int64_t max_delay_ms) {
+ delay_cap_helper_->set_max_delay_ms(max_delay_ms);
+}
+
+Stats<double> TraceBasedDeliveryFilter::GetDelayStats() const {
+ return delay_cap_helper_->delay_stats();
+}
+
+Stats<double> TraceBasedDeliveryFilter::GetBitrateStats() const {
+ return kbps_stats_;
+}
+
+void TraceBasedDeliveryFilter::ProceedToNextSlot() {
+ if (*next_delivery_it_ <= local_time_us_) {
+ ++next_delivery_it_;
+ if (next_delivery_it_ == delivery_times_us_.end()) {
+ // When the trace wraps we allow two packets to be sent back-to-back.
+ for (int64_t& delivery_time_us : delivery_times_us_) {
+ delivery_time_us += local_time_us_ - current_offset_us_;
+ }
+ current_offset_us_ += local_time_us_ - current_offset_us_;
+ next_delivery_it_ = delivery_times_us_.begin();
+ }
+ }
+ local_time_us_ = *next_delivery_it_;
+ const int kPayloadSize = 1200;
+ rate_counter_->UpdateRates(local_time_us_, kPayloadSize);
+}
+
+VideoSource::VideoSource(int flow_id,
+ float fps,
+ uint32_t kbps,
+ uint32_t ssrc,
+ int64_t first_frame_offset_ms)
+ : kMaxPayloadSizeBytes(1200),
+ kTimestampBase(0xff80ff00ul),
+ frame_period_ms_(1000.0 / fps),
+ bits_per_second_(1000 * kbps),
+ frame_size_bytes_(bits_per_second_ / 8 / fps),
+ flow_id_(flow_id),
+ next_frame_ms_(first_frame_offset_ms),
+ next_frame_rand_ms_(0),
+ now_ms_(0),
+ prototype_header_() {
+ memset(&prototype_header_, 0, sizeof(prototype_header_));
+ prototype_header_.ssrc = ssrc;
+ prototype_header_.sequenceNumber = 0xf000u;
+}
+
+uint32_t VideoSource::NextFrameSize() {
+ return frame_size_bytes_;
+}
+
+int64_t VideoSource::GetTimeUntilNextFrameMs() const {
+ return next_frame_ms_ + next_frame_rand_ms_ - now_ms_;
+}
+
+uint32_t VideoSource::NextPacketSize(uint32_t frame_size,
+ uint32_t remaining_payload) {
+ return std::min(kMaxPayloadSizeBytes, remaining_payload);
+}
+
+void VideoSource::RunFor(int64_t time_ms, Packets* in_out) {
+ assert(in_out);
+
+ now_ms_ += time_ms;
+ Packets new_packets;
+
+ while (now_ms_ >= next_frame_ms_) {
+ const int64_t kRandAmplitude = 2;
+ // A variance picked uniformly from {-1, 0, 1} ms is added to the frame
+ // timestamp.
+ next_frame_rand_ms_ =
+ kRandAmplitude * static_cast<float>(rand()) / RAND_MAX -
+ kRandAmplitude / 2;
+
+ // Ensure frame will not have a negative timestamp.
+ int64_t next_frame_ms =
+ std::max<int64_t>(next_frame_ms_ + next_frame_rand_ms_, 0);
+
+ prototype_header_.timestamp =
+ kTimestampBase + static_cast<uint32_t>(next_frame_ms * 90.0);
+ prototype_header_.extension.transmissionTimeOffset = 0;
+
+ // Generate new packets for this frame, all with the same timestamp,
+ // but the payload size is capped, so if the whole frame doesn't fit in
+ // one packet, we will see a number of equally sized packets followed by
+ // one smaller at the tail.
+
+ int64_t send_time_us = next_frame_ms * 1000.0;
+
+ uint32_t frame_size = NextFrameSize();
+ uint32_t payload_size = frame_size;
+
+ while (payload_size > 0) {
+ ++prototype_header_.sequenceNumber;
+ uint32_t size = NextPacketSize(frame_size, payload_size);
+ MediaPacket* new_packet =
+ new MediaPacket(flow_id_, send_time_us, size, prototype_header_);
+ new_packets.push_back(new_packet);
+ new_packet->SetAbsSendTimeMs(next_frame_ms);
+ new_packet->set_sender_timestamp_us(send_time_us);
+ payload_size -= size;
+ }
+
+ next_frame_ms_ += frame_period_ms_;
+ }
+
+ in_out->merge(new_packets, DereferencingComparator<Packet>);
+}
+
+AdaptiveVideoSource::AdaptiveVideoSource(int flow_id,
+ float fps,
+ uint32_t kbps,
+ uint32_t ssrc,
+ int64_t first_frame_offset_ms)
+ : VideoSource(flow_id, fps, kbps, ssrc, first_frame_offset_ms) {
+}
+
+void AdaptiveVideoSource::SetBitrateBps(int bitrate_bps) {
+ bits_per_second_ = std::min(bitrate_bps, 2500000);
+ frame_size_bytes_ = (bits_per_second_ / 8 * frame_period_ms_ + 500) / 1000;
+}
+
+PeriodicKeyFrameSource::PeriodicKeyFrameSource(int flow_id,
+ float fps,
+ uint32_t kbps,
+ uint32_t ssrc,
+ int64_t first_frame_offset_ms,
+ int key_frame_interval)
+ : AdaptiveVideoSource(flow_id, fps, kbps, ssrc, first_frame_offset_ms),
+ key_frame_interval_(key_frame_interval),
+ frame_counter_(0),
+ compensation_bytes_(0),
+ compensation_per_frame_(0) {
+}
+
+uint32_t PeriodicKeyFrameSource::NextFrameSize() {
+ uint32_t payload_size = frame_size_bytes_;
+ if (frame_counter_ == 0) {
+ payload_size = kMaxPayloadSizeBytes * 12;
+ compensation_bytes_ = 4 * frame_size_bytes_;
+ compensation_per_frame_ = compensation_bytes_ / 30;
+ } else if (key_frame_interval_ > 0 &&
+ (frame_counter_ % key_frame_interval_ == 0)) {
+ payload_size *= 5;
+ compensation_bytes_ = payload_size - frame_size_bytes_;
+ compensation_per_frame_ = compensation_bytes_ / 30;
+ } else if (compensation_bytes_ > 0) {
+ if (compensation_per_frame_ > static_cast<int>(payload_size)) {
+ // Skip this frame.
+ compensation_bytes_ -= payload_size;
+ payload_size = 0;
+ } else {
+ payload_size -= compensation_per_frame_;
+ compensation_bytes_ -= compensation_per_frame_;
+ }
+ }
+ if (compensation_bytes_ < 0)
+ compensation_bytes_ = 0;
+ ++frame_counter_;
+ return payload_size;
+}
+
+uint32_t PeriodicKeyFrameSource::NextPacketSize(uint32_t frame_size,
+ uint32_t remaining_payload) {
+ uint32_t fragments =
+ (frame_size + (kMaxPayloadSizeBytes - 1)) / kMaxPayloadSizeBytes;
+ uint32_t avg_size = (frame_size + fragments - 1) / fragments;
+ return std::min(avg_size, remaining_payload);
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h
new file mode 100644
index 0000000000..6b24cf30a6
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_FRAMEWORK_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_FRAMEWORK_H_
+
+#include <assert.h>
+#include <math.h>
+
+#include <algorithm>
+#include <list>
+#include <numeric>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "webrtc/base/common.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/bitrate_controller/include/bitrate_controller.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/pacing/include/paced_sender.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h"
+#include "webrtc/system_wrappers/include/clock.h"
+#include "webrtc/test/random.h"
+
+namespace webrtc {
+
+class RtcpBandwidthObserver;
+
+namespace testing {
+namespace bwe {
+
+class DelayCapHelper;
+
+class RateCounter {
+ public:
+ RateCounter(int64_t window_size_ms)
+ : window_size_us_(1000 * window_size_ms),
+ recently_received_packets_(0),
+ recently_received_bytes_(0),
+ last_accumulated_us_(0),
+ window_() {}
+
+ RateCounter() : RateCounter(1000) {}
+
+ void UpdateRates(int64_t send_time_us, uint32_t payload_size);
+
+ int64_t window_size_ms() const { return (window_size_us_ + 500) / 1000; }
+ uint32_t packets_per_second() const;
+ uint32_t bits_per_second() const;
+
+ double BitrateWindowS() const;
+
+ private:
+ typedef std::pair<int64_t, uint32_t> TimeSizePair;
+
+ int64_t window_size_us_;
+ uint32_t recently_received_packets_;
+ uint32_t recently_received_bytes_;
+ int64_t last_accumulated_us_;
+ std::list<TimeSizePair> window_;
+};
+
+typedef std::set<int> FlowIds;
+const FlowIds CreateFlowIds(const int *flow_ids_array, size_t num_flow_ids);
+const FlowIds CreateFlowIdRange(int initial_value, int last_value);
+
+template <typename T>
+bool DereferencingComparator(const T* const& a, const T* const& b) {
+ assert(a != NULL);
+ assert(b != NULL);
+ return *a < *b;
+}
+
+template<typename T> class Stats {
+ public:
+ Stats()
+ : data_(),
+ last_mean_count_(0),
+ last_variance_count_(0),
+ last_minmax_count_(0),
+ mean_(0),
+ variance_(0),
+ min_(0),
+ max_(0) {
+ }
+
+ void Push(T data_point) {
+ data_.push_back(data_point);
+ }
+
+ T GetMean() {
+ if (last_mean_count_ != data_.size()) {
+ last_mean_count_ = data_.size();
+ mean_ = std::accumulate(data_.begin(), data_.end(), static_cast<T>(0));
+ assert(last_mean_count_ != 0);
+ mean_ /= static_cast<T>(last_mean_count_);
+ }
+ return mean_;
+ }
+ T GetVariance() {
+ if (last_variance_count_ != data_.size()) {
+ last_variance_count_ = data_.size();
+ T mean = GetMean();
+ variance_ = 0;
+ for (const auto& sample : data_) {
+ T diff = (sample - mean);
+ variance_ += diff * diff;
+ }
+ assert(last_variance_count_ != 0);
+ variance_ /= static_cast<T>(last_variance_count_);
+ }
+ return variance_;
+ }
+ T GetStdDev() {
+ return sqrt(static_cast<double>(GetVariance()));
+ }
+ T GetMin() {
+ RefreshMinMax();
+ return min_;
+ }
+ T GetMax() {
+ RefreshMinMax();
+ return max_;
+ }
+
+ std::string AsString() {
+ std::stringstream ss;
+ ss << (GetMean() >= 0 ? GetMean() : -1) << ", " <<
+ (GetStdDev() >= 0 ? GetStdDev() : -1);
+ return ss.str();
+ }
+
+ void Log(const std::string& units) {
+ BWE_TEST_LOGGING_LOG5("", "%f %s\t+/-%f\t[%f,%f]",
+ GetMean(), units.c_str(), GetStdDev(), GetMin(), GetMax());
+ }
+
+ private:
+ void RefreshMinMax() {
+ if (last_minmax_count_ != data_.size()) {
+ last_minmax_count_ = data_.size();
+ min_ = max_ = 0;
+ if (data_.empty()) {
+ return;
+ }
+ typename std::vector<T>::const_iterator it = data_.begin();
+ min_ = max_ = *it;
+ while (++it != data_.end()) {
+ min_ = std::min(min_, *it);
+ max_ = std::max(max_, *it);
+ }
+ }
+ }
+
+ std::vector<T> data_;
+ typename std::vector<T>::size_type last_mean_count_;
+ typename std::vector<T>::size_type last_variance_count_;
+ typename std::vector<T>::size_type last_minmax_count_;
+ T mean_;
+ T variance_;
+ T min_;
+ T max_;
+};
+
+bool IsTimeSorted(const Packets& packets);
+
+class PacketProcessor;
+
+enum ProcessorType { kSender, kReceiver, kRegular };
+
+class PacketProcessorListener {
+ public:
+ virtual ~PacketProcessorListener() {}
+
+ virtual void AddPacketProcessor(PacketProcessor* processor,
+ ProcessorType type) = 0;
+ virtual void RemovePacketProcessor(PacketProcessor* processor) = 0;
+};
+
+class PacketProcessor {
+ public:
+ PacketProcessor(PacketProcessorListener* listener,
+ int flow_id,
+ ProcessorType type);
+ PacketProcessor(PacketProcessorListener* listener,
+ const FlowIds& flow_ids,
+ ProcessorType type);
+ virtual ~PacketProcessor();
+
+ // Called after each simulation batch to allow the processor to plot any
+ // internal data.
+ virtual void Plot(int64_t timestamp_ms) {}
+
+ // Run simulation for |time_ms| milliseconds, consuming packets from, and
+ // producing packets into in_out. The outgoing packet list must be sorted on
+ // |send_time_us_|. The simulation time |time_ms| is optional to use.
+ virtual void RunFor(int64_t time_ms, Packets* in_out) = 0;
+
+ const FlowIds& flow_ids() const { return flow_ids_; }
+
+ uint32_t packets_per_second() const;
+ uint32_t bits_per_second() const;
+
+ protected:
+ RateCounter rate_counter_;
+
+ private:
+ PacketProcessorListener* listener_;
+ const FlowIds flow_ids_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(PacketProcessor);
+};
+
+class RateCounterFilter : public PacketProcessor {
+ public:
+ RateCounterFilter(PacketProcessorListener* listener,
+ int flow_id,
+ const char* name,
+ const std::string& plot_name);
+ RateCounterFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids,
+ const char* name,
+ const std::string& plot_name);
+ RateCounterFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids,
+ const char* name,
+ int64_t start_plotting_time_ms,
+ const std::string& plot_name);
+ virtual ~RateCounterFilter();
+
+ void LogStats();
+ Stats<double> GetBitrateStats() const;
+ virtual void Plot(int64_t timestamp_ms);
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ private:
+ Stats<double> packets_per_second_stats_;
+ Stats<double> kbps_stats_;
+ std::string name_;
+ int64_t start_plotting_time_ms_;
+ // Algorithm name if single flow, Total link utilization if all flows.
+ std::string plot_name_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RateCounterFilter);
+};
+
+class LossFilter : public PacketProcessor {
+ public:
+ LossFilter(PacketProcessorListener* listener, int flow_id);
+ LossFilter(PacketProcessorListener* listener, const FlowIds& flow_ids);
+ virtual ~LossFilter() {}
+
+ void SetLoss(float loss_percent);
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ private:
+ test::Random random_;
+ float loss_fraction_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(LossFilter);
+};
+
+class DelayFilter : public PacketProcessor {
+ public:
+ DelayFilter(PacketProcessorListener* listener, int flow_id);
+ DelayFilter(PacketProcessorListener* listener, const FlowIds& flow_ids);
+ virtual ~DelayFilter() {}
+
+ void SetOneWayDelayMs(int64_t one_way_delay_ms);
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ private:
+ int64_t one_way_delay_us_;
+ int64_t last_send_time_us_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(DelayFilter);
+};
+
+class JitterFilter : public PacketProcessor {
+ public:
+ JitterFilter(PacketProcessorListener* listener, int flow_id);
+ JitterFilter(PacketProcessorListener* listener, const FlowIds& flow_ids);
+ virtual ~JitterFilter() {}
+
+ void SetMaxJitter(int64_t stddev_jitter_ms);
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+ void set_reorderdering(bool reordering) { reordering_ = reordering; }
+ int64_t MeanUs();
+
+ private:
+ test::Random random_;
+ int64_t stddev_jitter_us_;
+ int64_t last_send_time_us_;
+ bool reordering_; // False by default.
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(JitterFilter);
+};
+
+// Reorders two consecutive packets with a probability of reorder_percent.
+class ReorderFilter : public PacketProcessor {
+ public:
+ ReorderFilter(PacketProcessorListener* listener, int flow_id);
+ ReorderFilter(PacketProcessorListener* listener, const FlowIds& flow_ids);
+ virtual ~ReorderFilter() {}
+
+ void SetReorder(float reorder_percent);
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ private:
+ test::Random random_;
+ float reorder_fraction_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ReorderFilter);
+};
+
+// Apply a bitrate choke with an infinite queue on the packet stream.
+class ChokeFilter : public PacketProcessor {
+ public:
+ ChokeFilter(PacketProcessorListener* listener, int flow_id);
+ ChokeFilter(PacketProcessorListener* listener, const FlowIds& flow_ids);
+ virtual ~ChokeFilter();
+
+ void set_capacity_kbps(uint32_t kbps);
+ void set_max_delay_ms(int64_t max_queueing_delay_ms);
+
+ uint32_t capacity_kbps();
+
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ Stats<double> GetDelayStats() const;
+
+ private:
+ uint32_t capacity_kbps_;
+ int64_t last_send_time_us_;
+ rtc::scoped_ptr<DelayCapHelper> delay_cap_helper_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ChokeFilter);
+};
+
+class TraceBasedDeliveryFilter : public PacketProcessor {
+ public:
+ TraceBasedDeliveryFilter(PacketProcessorListener* listener, int flow_id);
+ TraceBasedDeliveryFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids);
+ TraceBasedDeliveryFilter(PacketProcessorListener* listener,
+ int flow_id,
+ const char* name);
+ virtual ~TraceBasedDeliveryFilter();
+
+ // The file should contain nanosecond timestamps corresponding to the time
+ // when the network can accept another packet. The timestamps should be
+ // separated by new lines, e.g., "100000000\n125000000\n321000000\n..."
+ bool Init(const std::string& filename);
+ virtual void Plot(int64_t timestamp_ms);
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ void set_max_delay_ms(int64_t max_delay_ms);
+ Stats<double> GetDelayStats() const;
+ Stats<double> GetBitrateStats() const;
+
+ private:
+ void ProceedToNextSlot();
+
+ typedef std::vector<int64_t> TimeList;
+ int64_t current_offset_us_;
+ TimeList delivery_times_us_;
+ TimeList::const_iterator next_delivery_it_;
+ int64_t local_time_us_;
+ rtc::scoped_ptr<RateCounter> rate_counter_;
+ std::string name_;
+ rtc::scoped_ptr<DelayCapHelper> delay_cap_helper_;
+ Stats<double> packets_per_second_stats_;
+ Stats<double> kbps_stats_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(TraceBasedDeliveryFilter);
+};
+
+class VideoSource {
+ public:
+ VideoSource(int flow_id,
+ float fps,
+ uint32_t kbps,
+ uint32_t ssrc,
+ int64_t first_frame_offset_ms);
+ virtual ~VideoSource() {}
+
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ virtual int flow_id() const { return flow_id_; }
+ virtual void SetBitrateBps(int bitrate_bps) {}
+ uint32_t bits_per_second() const { return bits_per_second_; }
+ uint32_t max_payload_size_bytes() const { return kMaxPayloadSizeBytes; }
+ int64_t GetTimeUntilNextFrameMs() const;
+
+ protected:
+ virtual uint32_t NextFrameSize();
+ virtual uint32_t NextPacketSize(uint32_t frame_size,
+ uint32_t remaining_payload);
+
+ const uint32_t kMaxPayloadSizeBytes;
+ const uint32_t kTimestampBase;
+ const double frame_period_ms_;
+ uint32_t bits_per_second_;
+ uint32_t frame_size_bytes_;
+
+ private:
+ const int flow_id_;
+ int64_t next_frame_ms_;
+ int64_t next_frame_rand_ms_;
+ int64_t now_ms_;
+ RTPHeader prototype_header_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(VideoSource);
+};
+
+class AdaptiveVideoSource : public VideoSource {
+ public:
+ AdaptiveVideoSource(int flow_id,
+ float fps,
+ uint32_t kbps,
+ uint32_t ssrc,
+ int64_t first_frame_offset_ms);
+ virtual ~AdaptiveVideoSource() {}
+
+ void SetBitrateBps(int bitrate_bps) override;
+
+ private:
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AdaptiveVideoSource);
+};
+
+class PeriodicKeyFrameSource : public AdaptiveVideoSource {
+ public:
+ PeriodicKeyFrameSource(int flow_id,
+ float fps,
+ uint32_t kbps,
+ uint32_t ssrc,
+ int64_t first_frame_offset_ms,
+ int key_frame_interval);
+ virtual ~PeriodicKeyFrameSource() {}
+
+ protected:
+ uint32_t NextFrameSize() override;
+ uint32_t NextPacketSize(uint32_t frame_size,
+ uint32_t remaining_payload) override;
+
+ private:
+ int key_frame_interval_;
+ uint32_t frame_counter_;
+ int compensation_bytes_;
+ int compensation_per_frame_;
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(PeriodicKeyFrameSource);
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_FRAMEWORK_H_
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
new file mode 100644
index 0000000000..627260678b
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc
@@ -0,0 +1,1051 @@
+/*
+ * Copyright (c) 2013 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 "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+
+#include <numeric>
+
+#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"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+TEST(BweTestFramework_RandomTest, Gaussian) {
+ enum {
+ kN = 100000,
+ kBuckets = 100,
+ kMean = 49,
+ kStddev = 10
+ };
+
+ test::Random random(0x12345678);
+
+ int buckets[kBuckets] = {0};
+ for (int i = 0; i < kN; ++i) {
+ int index = random.Gaussian(kMean, kStddev);
+ if (index >= 0 && index < kBuckets) {
+ buckets[index]++;
+ }
+ }
+
+ const double kPi = 3.14159265358979323846;
+ const double kScale = kN / (kStddev * sqrt(2.0 * kPi));
+ const double kDiv = -2.0 * kStddev * kStddev;
+ double self_corr = 0.0;
+ double bucket_corr = 0.0;
+ for (int n = 0; n < kBuckets; ++n) {
+ double normal_dist = kScale * exp((n - kMean) * (n - kMean) / kDiv);
+ self_corr += normal_dist * normal_dist;
+ bucket_corr += normal_dist * buckets[n];
+ }
+ printf("Correlation: %f (random sample), %f (self), %f (quotient)\n",
+ bucket_corr, self_corr, bucket_corr / self_corr);
+ EXPECT_NEAR(1.0, bucket_corr / self_corr, 0.0004);
+}
+
+static bool IsSequenceNumberSorted(const Packets& packets) {
+ PacketsConstIt last_it = packets.begin();
+ for (PacketsConstIt it = last_it; it != packets.end(); ++it) {
+ const MediaPacket* packet = static_cast<const MediaPacket*>(*it);
+ const MediaPacket* last_packet = static_cast<const MediaPacket*>(*last_it);
+ if (IsNewerSequenceNumber(last_packet->header().sequenceNumber,
+ packet->header().sequenceNumber)) {
+ return false;
+ }
+ last_it = it;
+ }
+ return true;
+}
+
+TEST(BweTestFramework_PacketTest, IsTimeSorted) {
+ Packets packets;
+ // Insert some packets in order...
+ EXPECT_TRUE(IsTimeSorted(packets));
+
+ packets.push_back(new MediaPacket(100, 0));
+ EXPECT_TRUE(IsTimeSorted(packets));
+
+ packets.push_back(new MediaPacket(110, 0));
+ EXPECT_TRUE(IsTimeSorted(packets));
+
+ // ...and one out-of-order...
+ packets.push_back(new MediaPacket(100, 0));
+ EXPECT_FALSE(IsTimeSorted(packets));
+
+ // ...remove the out-of-order packet, insert another in-order packet.
+ delete packets.back();
+ packets.pop_back();
+ packets.push_back(new MediaPacket(120, 0));
+ EXPECT_TRUE(IsTimeSorted(packets));
+
+ for (auto* packet : packets)
+ delete packet;
+}
+
+TEST(BweTestFramework_PacketTest, IsSequenceNumberSorted) {
+ Packets packets;
+ // Insert some packets in order...
+ EXPECT_TRUE(IsSequenceNumberSorted(packets));
+
+ packets.push_back(new MediaPacket(0, 100));
+ EXPECT_TRUE(IsSequenceNumberSorted(packets));
+
+ packets.push_back(new MediaPacket(0, 110));
+ EXPECT_TRUE(IsSequenceNumberSorted(packets));
+
+ // ...and one out-of-order...
+ packets.push_back(new MediaPacket(0, 100));
+ EXPECT_FALSE(IsSequenceNumberSorted(packets));
+
+ // ...remove the out-of-order packet, insert another in-order packet.
+ delete packets.back();
+ packets.pop_back();
+ packets.push_back(new MediaPacket(0, 120));
+ EXPECT_TRUE(IsSequenceNumberSorted(packets));
+
+ for (auto* packet : packets)
+ delete packet;
+}
+
+TEST(BweTestFramework_StatsTest, Mean) {
+ Stats<int32_t> stats;
+ EXPECT_EQ(0, stats.GetMean());
+
+ stats.Push(1);
+ stats.Push(3);
+ EXPECT_EQ(2, stats.GetMean());
+
+ // Integer division rounds (1+3-3)/3 to 0.
+ stats.Push(-3);
+ EXPECT_EQ(0, stats.GetMean());
+}
+
+TEST(BweTestFramework_StatsTest, Variance) {
+ Stats<int32_t> stats;
+ EXPECT_EQ(0, stats.GetVariance());
+
+ // Mean is 2 ; ((1-2)*(1-2)+(3-2)*(3-2))/2 = (1+1)/2 = 1
+ stats.Push(1);
+ stats.Push(3);
+ EXPECT_EQ(1, stats.GetVariance());
+
+ // Integer division rounds 26/3 to 8
+ // Mean is 0 ; (1*1+3*3+(-4)*(-4))/3 = (1+9+16)/3 = 8
+ stats.Push(-4);
+ EXPECT_EQ(8, stats.GetVariance());
+}
+
+TEST(BweTestFramework_StatsTest, StdDev) {
+ Stats<int32_t> stats;
+ EXPECT_EQ(0, stats.GetStdDev());
+
+ // Variance is 1 ; sqrt(1) = 1
+ stats.Push(1);
+ stats.Push(3);
+ EXPECT_EQ(1, stats.GetStdDev());
+
+ // Variance is 8 ; sqrt(8) = 2 with integers.
+ stats.Push(-4);
+ EXPECT_EQ(2, stats.GetStdDev());
+}
+
+TEST(BweTestFramework_StatsTest, MinMax) {
+ Stats<int32_t> stats;
+ EXPECT_EQ(0, stats.GetMin());
+ EXPECT_EQ(0, stats.GetMax());
+
+ stats.Push(1);
+ EXPECT_EQ(1, stats.GetMin());
+ EXPECT_EQ(1, stats.GetMax());
+
+ stats.Push(3);
+ EXPECT_EQ(1, stats.GetMin());
+ EXPECT_EQ(3, stats.GetMax());
+
+ stats.Push(-4);
+ EXPECT_EQ(-4, stats.GetMin());
+ EXPECT_EQ(3, stats.GetMax());
+}
+
+class BweTestFramework_RateCounterFilterTest : public ::testing::Test {
+ public:
+ BweTestFramework_RateCounterFilterTest()
+ : filter_(NULL, 0, "", ""), now_ms_(0) {}
+ virtual ~BweTestFramework_RateCounterFilterTest() {}
+
+ protected:
+ void TestRateCounter(int64_t run_for_ms, uint32_t payload_bits,
+ uint32_t expected_pps, uint32_t expected_bps) {
+ Packets packets;
+ RTPHeader header;
+ // "Send" a packet every 10 ms.
+ for (int64_t i = 0; i < run_for_ms; i += 10, now_ms_ += 10) {
+ packets.push_back(
+ new MediaPacket(0, now_ms_ * 1000, payload_bits / 8, header));
+ }
+ filter_.RunFor(run_for_ms, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ EXPECT_EQ(expected_pps, filter_.packets_per_second());
+ EXPECT_EQ(expected_bps, filter_.bits_per_second());
+
+ for (auto* packet : packets)
+ delete packet;
+ }
+
+ private:
+ RateCounterFilter filter_;
+ int64_t now_ms_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(BweTestFramework_RateCounterFilterTest);
+};
+
+TEST_F(BweTestFramework_RateCounterFilterTest, Short) {
+ // 100ms, 100 bytes per packet, should result in 10 pps and 8 kbps. We're
+ // generating one packet every 10 ms ; 10 * 800 = 8k
+ TestRateCounter(100, 800, 10, 8000);
+}
+
+TEST_F(BweTestFramework_RateCounterFilterTest, Medium) {
+ // 100ms, like above.
+ TestRateCounter(100, 800, 10, 8000);
+ // 1000ms, 100 bpp, should result in 100 pps and 80 kbps. We're still
+ // generating packets every 10 ms.
+ TestRateCounter(900, 800, 100, 80000);
+}
+
+TEST_F(BweTestFramework_RateCounterFilterTest, Long) {
+ // 100ms, 1000ms, like above.
+ TestRateCounter(100, 800, 10, 8000);
+ TestRateCounter(900, 800, 100, 80000);
+ // 2000ms, should only see rate of last second, so 100 pps, and 40 kbps now.
+ TestRateCounter(1000, 400, 100, 40000);
+ // 2500ms, half a second with zero payload size. We should get same pps as
+ // before, but kbps should drop to half of previous rate.
+ TestRateCounter(500, 0, 100, 20000);
+ // Another half second with zero payload size. Now the kbps rate should drop
+ // to zero.
+ TestRateCounter(500, 0, 100, 0);
+ // Increate payload size again. 200 * 100 * 0.5 = 10 kbps.
+ TestRateCounter(500, 200, 100, 10000);
+}
+
+static void TestLossFilter(float loss_percent, bool zero_tolerance) {
+ LossFilter filter(NULL, 0);
+ filter.SetLoss(loss_percent);
+ Packets::size_type sent_packets = 0;
+ Packets::size_type remaining_packets = 0;
+
+ // No input should yield no output
+ {
+ Packets packets;
+ sent_packets += packets.size();
+ filter.RunFor(0, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+ remaining_packets += packets.size();
+ EXPECT_EQ(0u, sent_packets);
+ EXPECT_EQ(0u, remaining_packets);
+ for (auto* packet : packets)
+ delete packet;
+ }
+
+ // Generate and process 10000 packets in different batch sizes (some empty)
+ for (int i = 0; i < 2225; ++i) {
+ Packets packets;
+ for (int j = 0; j < i % 10; ++j)
+ packets.push_back(new MediaPacket(i, i));
+ sent_packets += packets.size();
+ filter.RunFor(0, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+ remaining_packets += packets.size();
+ for (auto* packet : packets)
+ delete packet;
+ }
+
+ float loss_fraction = 0.01f * (100.0f - loss_percent);
+ Packets::size_type expected_packets = loss_fraction * sent_packets;
+ if (zero_tolerance) {
+ EXPECT_EQ(expected_packets, remaining_packets);
+ } else {
+ // Require within 1% of expected
+ EXPECT_NEAR(expected_packets, remaining_packets, 100);
+ }
+}
+
+TEST(BweTestFramework_LossFilterTest, Loss0) {
+ // With 0% loss, the result should be exact (no loss).
+ TestLossFilter(0.0f, true);
+}
+
+TEST(BweTestFramework_LossFilterTest, Loss10) {
+ TestLossFilter(10.0f, false);
+}
+
+TEST(BweTestFramework_LossFilterTest, Loss50) {
+ TestLossFilter(50.0f, false);
+}
+
+TEST(BweTestFramework_LossFilterTest, Loss100) {
+ // With 100% loss, the result should be exact (no packets out).
+ TestLossFilter(100.0f, true);
+}
+
+class BweTestFramework_DelayFilterTest : public ::testing::Test {
+ public:
+ BweTestFramework_DelayFilterTest()
+ : filter_(NULL, 0), now_ms_(0), sequence_number_(0) {}
+ virtual ~BweTestFramework_DelayFilterTest() {
+ for (auto* packet : accumulated_packets_)
+ delete packet;
+ }
+
+ protected:
+ void TestDelayFilter(int64_t run_for_ms, uint32_t in_packets,
+ uint32_t out_packets) {
+ Packets packets;
+ for (uint32_t i = 0; i < in_packets; ++i) {
+ packets.push_back(new MediaPacket(
+ now_ms_ * 1000 + (sequence_number_ >> 4), sequence_number_));
+ sequence_number_++;
+ }
+ filter_.RunFor(run_for_ms, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+ for (PacketsConstIt it = packets.begin(); it != packets.end(); ++it) {
+ EXPECT_LE(now_ms_ * 1000, (*it)->send_time_us());
+ }
+ EXPECT_EQ(out_packets, packets.size());
+ accumulated_packets_.splice(accumulated_packets_.end(), packets);
+ now_ms_ += run_for_ms;
+ }
+
+ void TestDelayFilter(int64_t delay_ms) {
+ filter_.SetOneWayDelayMs(delay_ms);
+ TestDelayFilter(1, 0, 0); // No input should yield no output
+
+ // Single packet
+ TestDelayFilter(0, 1, 1);
+ TestDelayFilter(delay_ms, 0, 0);
+
+ for (int i = 0; i < delay_ms; ++i) {
+ filter_.SetOneWayDelayMs(i);
+ TestDelayFilter(1, 10, 10);
+ }
+ TestDelayFilter(0, 0, 0);
+ TestDelayFilter(delay_ms, 0, 0);
+
+ // Wait a little longer - should still see no output
+ TestDelayFilter(delay_ms, 0, 0);
+
+ for (int i = 1; i < delay_ms + 1; ++i) {
+ filter_.SetOneWayDelayMs(i);
+ TestDelayFilter(1, 5, 5);
+ }
+ TestDelayFilter(0, 0, 0);
+ filter_.SetOneWayDelayMs(2 * delay_ms);
+ TestDelayFilter(1, 0, 0);
+ TestDelayFilter(delay_ms, 13, 13);
+ TestDelayFilter(delay_ms, 0, 0);
+
+ // Wait a little longer - should still see no output
+ TestDelayFilter(delay_ms, 0, 0);
+
+ for (int i = 0; i < 2 * delay_ms; ++i) {
+ filter_.SetOneWayDelayMs(2 * delay_ms - i - 1);
+ TestDelayFilter(1, 5, 5);
+ }
+ TestDelayFilter(0, 0, 0);
+ filter_.SetOneWayDelayMs(0);
+ TestDelayFilter(0, 7, 7);
+
+ ASSERT_TRUE(IsTimeSorted(accumulated_packets_));
+ ASSERT_TRUE(IsSequenceNumberSorted(accumulated_packets_));
+ }
+
+ DelayFilter filter_;
+ Packets accumulated_packets_;
+
+ private:
+ int64_t now_ms_;
+ uint16_t sequence_number_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(BweTestFramework_DelayFilterTest);
+};
+
+TEST_F(BweTestFramework_DelayFilterTest, Delay0) {
+ TestDelayFilter(1, 0, 0); // No input should yield no output
+ TestDelayFilter(1, 10, 10); // Expect no delay (delay time is zero)
+ TestDelayFilter(1, 0, 0); // Check no packets are still in buffer
+ filter_.SetOneWayDelayMs(0);
+ TestDelayFilter(1, 5, 5); // Expect no delay (delay time is zero)
+ TestDelayFilter(1, 0, 0); // Check no packets are still in buffer
+}
+
+TEST_F(BweTestFramework_DelayFilterTest, Delay1) {
+ TestDelayFilter(1);
+}
+
+TEST_F(BweTestFramework_DelayFilterTest, Delay2) {
+ TestDelayFilter(2);
+}
+
+TEST_F(BweTestFramework_DelayFilterTest, Delay20) {
+ TestDelayFilter(20);
+}
+
+TEST_F(BweTestFramework_DelayFilterTest, Delay100) {
+ TestDelayFilter(100);
+}
+
+TEST_F(BweTestFramework_DelayFilterTest, JumpToZeroDelay) {
+ DelayFilter delay(NULL, 0);
+ Packets acc;
+ Packets packets;
+
+ // Delay a bunch of packets, accumulate them to the 'acc' list.
+ delay.SetOneWayDelayMs(100.0f);
+ for (uint32_t i = 0; i < 10; ++i) {
+ packets.push_back(new MediaPacket(i * 100, i));
+ }
+ delay.RunFor(1000, &packets);
+ acc.splice(acc.end(), packets);
+ ASSERT_TRUE(IsTimeSorted(acc));
+ ASSERT_TRUE(IsSequenceNumberSorted(acc));
+
+ // Drop delay to zero, send a few more packets through the delay, append them
+ // to the 'acc' list and verify that it is all sorted.
+ delay.SetOneWayDelayMs(0.0f);
+ for (uint32_t i = 10; i < 50; ++i) {
+ packets.push_back(new MediaPacket(i * 100, i));
+ }
+ delay.RunFor(1000, &packets);
+ acc.splice(acc.end(), packets);
+ ASSERT_TRUE(IsTimeSorted(acc));
+ ASSERT_TRUE(IsSequenceNumberSorted(acc));
+
+ for (auto* packet : acc)
+ delete packet;
+}
+
+TEST_F(BweTestFramework_DelayFilterTest, IncreasingDelay) {
+ // Gradually increase delay.
+ for (int i = 1; i < 50; i += 4) {
+ TestDelayFilter(i);
+ }
+ // Reach a steady state.
+ filter_.SetOneWayDelayMs(100);
+ TestDelayFilter(1, 20, 20);
+ TestDelayFilter(2, 0, 0);
+ TestDelayFilter(99, 20, 20);
+ // Drop delay back down to zero.
+ filter_.SetOneWayDelayMs(0);
+ TestDelayFilter(1, 100, 100);
+ TestDelayFilter(23010, 0, 0);
+ ASSERT_TRUE(IsTimeSorted(accumulated_packets_));
+ ASSERT_TRUE(IsSequenceNumberSorted(accumulated_packets_));
+}
+
+static void TestJitterFilter(int64_t max_jitter_ms) {
+ JitterFilter filter(NULL, 0);
+ filter.SetMaxJitter(max_jitter_ms);
+
+ int64_t now_ms = 0;
+ uint16_t sequence_number = 0;
+
+ // Generate packets, add jitter to them, accumulate the altered packets.
+ Packets original;
+ Packets jittered;
+ for (uint32_t i = 0; i < 1000; ++i) {
+ Packets packets;
+ for (uint32_t j = 0; j < i % 100; ++j) {
+ packets.push_back(new MediaPacket(now_ms * 1000, sequence_number));
+ original.push_back(new MediaPacket(now_ms * 1000, sequence_number));
+ ++sequence_number;
+ now_ms += 5 * max_jitter_ms;
+ }
+ filter.RunFor(max_jitter_ms, &packets);
+ jittered.splice(jittered.end(), packets);
+ }
+
+ // Jittered packets should still be in order.
+ ASSERT_TRUE(IsTimeSorted(original));
+ ASSERT_TRUE(IsTimeSorted(jittered));
+ ASSERT_TRUE(IsSequenceNumberSorted(original));
+ ASSERT_TRUE(IsSequenceNumberSorted(jittered));
+ EXPECT_EQ(original.size(), jittered.size());
+
+ // Make sure jittered and original packets are in same order. Collect time
+ // difference (jitter) in stats, then check that mean jitter is close to zero
+ // and standard deviation of jitter is what we set it to.
+ Stats<double> jitter_us;
+ int64_t max_jitter_obtained_us = 0;
+ for (PacketsIt it1 = original.begin(), it2 = jittered.begin();
+ it1 != original.end() && it2 != jittered.end(); ++it1, ++it2) {
+ const MediaPacket* packet1 = static_cast<const MediaPacket*>(*it1);
+ const MediaPacket* packet2 = static_cast<const MediaPacket*>(*it2);
+ EXPECT_EQ(packet1->header().sequenceNumber,
+ packet2->header().sequenceNumber);
+ max_jitter_obtained_us =
+ std::max(max_jitter_obtained_us,
+ packet2->send_time_us() - packet1->send_time_us());
+ jitter_us.Push(packet2->send_time_us() - packet1->send_time_us());
+ }
+ EXPECT_NEAR(filter.MeanUs(), jitter_us.GetMean(),
+ max_jitter_ms * 1000.0 * 0.01);
+ EXPECT_NEAR(max_jitter_ms * 1000.0, max_jitter_obtained_us,
+ max_jitter_ms * 1000.0 * 0.01);
+ for (auto* packet : original)
+ delete packet;
+ for (auto* packet : jittered)
+ delete packet;
+}
+
+TEST(BweTestFramework_JitterFilterTest, Jitter0) {
+ TestJitterFilter(0);
+}
+
+TEST(BweTestFramework_JitterFilterTest, Jitter1) {
+ TestJitterFilter(1);
+}
+
+TEST(BweTestFramework_JitterFilterTest, Jitter5) {
+ TestJitterFilter(5);
+}
+
+TEST(BweTestFramework_JitterFilterTest, Jitter10) {
+ TestJitterFilter(10);
+}
+
+TEST(BweTestFramework_JitterFilterTest, Jitter1031) {
+ TestJitterFilter(1031);
+}
+
+static void TestReorderFilter(uint16_t reorder_percent, uint16_t near_value) {
+ const uint16_t kPacketCount = 10000;
+
+ // Generate packets with 10 ms interval.
+ Packets packets;
+ int64_t now_ms = 0;
+ uint16_t sequence_number = 1;
+ for (uint16_t i = 0; i < kPacketCount; ++i, now_ms += 10) {
+ packets.push_back(new MediaPacket(now_ms * 1000, sequence_number++));
+ }
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+
+ // Reorder packets, verify that send times are still in order.
+ ReorderFilter filter(NULL, 0);
+ filter.SetReorder(reorder_percent);
+ filter.RunFor(now_ms, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+
+ // We measure the amount of reordering by summing the distance by which out-
+ // of-order packets have been moved in the stream.
+ uint16_t distance = 0;
+ uint16_t last_sequence_number = 0;
+ for (auto* packet : packets) {
+ const MediaPacket* media_packet = static_cast<const MediaPacket*>(packet);
+ uint16_t sequence_number = media_packet->header().sequenceNumber;
+ if (sequence_number < last_sequence_number) {
+ distance += last_sequence_number - sequence_number;
+ }
+ last_sequence_number = sequence_number;
+ }
+
+ // Because reordering is random, we allow a threshold when comparing. The
+ // maximum distance a packet can be moved is PacketCount - 1.
+ EXPECT_NEAR(
+ ((kPacketCount - 1) * reorder_percent) / 100, distance, near_value);
+
+ for (auto* packet : packets)
+ delete packet;
+}
+
+TEST(BweTestFramework_ReorderFilterTest, Reorder0) {
+ // For 0% reordering, no packets should have been moved, so result is exact.
+ TestReorderFilter(0, 0);
+}
+
+TEST(BweTestFramework_ReorderFilterTest, Reorder10) {
+ TestReorderFilter(10, 30);
+}
+
+TEST(BweTestFramework_ReorderFilterTest, Reorder20) {
+ TestReorderFilter(20, 20);
+}
+
+TEST(BweTestFramework_ReorderFilterTest, Reorder50) {
+ TestReorderFilter(50, 20);
+}
+
+TEST(BweTestFramework_ReorderFilterTest, Reorder70) {
+ TestReorderFilter(70, 20);
+}
+
+TEST(BweTestFramework_ReorderFilterTest, Reorder100) {
+ // Note that because the implementation works by optionally swapping two
+ // adjacent packets, when the likelihood of a swap is 1.0, a swap will always
+ // occur, so the stream will be in order except for the first packet, which
+ // has been moved to the end. Therefore we expect the result to be exact here.
+ TestReorderFilter(100.0, 0);
+}
+
+class BweTestFramework_ChokeFilterTest : public ::testing::Test {
+ public:
+ BweTestFramework_ChokeFilterTest()
+ : now_ms_(0),
+ sequence_number_(0),
+ output_packets_(),
+ send_times_us_() {
+ }
+ virtual ~BweTestFramework_ChokeFilterTest() {
+ for (auto* packet : output_packets_)
+ delete packet;
+ }
+
+ protected:
+ void TestChoke(PacketProcessor* filter,
+ int64_t run_for_ms,
+ uint32_t packets_to_generate,
+ size_t expected_kbit_transmitted) {
+ // Generate a bunch of packets, apply choke, verify output is ordered.
+ Packets packets;
+ RTPHeader header;
+ for (uint32_t i = 0; i < packets_to_generate; ++i) {
+ int64_t send_time_ms = now_ms_ + (i * run_for_ms) / packets_to_generate;
+ header.sequenceNumber = sequence_number_++;
+ // Payload is 1000 bits.
+ packets.push_back(new MediaPacket(0, send_time_ms * 1000, 125, header));
+ send_times_us_.push_back(send_time_ms * 1000);
+ }
+ ASSERT_TRUE(IsTimeSorted(packets));
+ filter->RunFor(run_for_ms, &packets);
+ now_ms_ += run_for_ms;
+ output_packets_.splice(output_packets_.end(), packets);
+ ASSERT_TRUE(IsTimeSorted(output_packets_));
+ ASSERT_TRUE(IsSequenceNumberSorted(output_packets_));
+
+ // Sum up the transmitted bytes up until the current time.
+ size_t bytes_transmitted = 0;
+ while (!output_packets_.empty()) {
+ const Packet* packet = output_packets_.front();
+ if (packet->send_time_us() > now_ms_ * 1000) {
+ break;
+ }
+ bytes_transmitted += packet->payload_size();
+ delete output_packets_.front();
+ output_packets_.pop_front();
+ }
+ EXPECT_EQ(expected_kbit_transmitted, (bytes_transmitted * 8 + 500) / 1000);
+ }
+
+ void CheckMaxDelay(int64_t max_delay_ms) {
+ for (const auto* packet : output_packets_) {
+ const MediaPacket* media_packet = static_cast<const MediaPacket*>(packet);
+ int64_t delay_us = media_packet->send_time_us() -
+ send_times_us_[media_packet->header().sequenceNumber];
+ EXPECT_GE(max_delay_ms * 1000, delay_us);
+ }
+ }
+
+ private:
+ int64_t now_ms_;
+ uint16_t sequence_number_;
+ Packets output_packets_;
+ std::vector<int64_t> send_times_us_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(BweTestFramework_ChokeFilterTest);
+};
+
+TEST_F(BweTestFramework_ChokeFilterTest, NoQueue) {
+ const int kCapacityKbps = 10;
+ const size_t kPacketSizeBytes = 125;
+ const int64_t kExpectedSendTimeUs =
+ (kPacketSizeBytes * 8 * 1000 + kCapacityKbps / 2) / kCapacityKbps;
+ uint16_t sequence_number = 0;
+ int64_t send_time_us = 0;
+ ChokeFilter filter(NULL, 0);
+ filter.set_capacity_kbps(10);
+ Packets packets;
+ RTPHeader header;
+ for (int i = 0; i < 2; ++i) {
+ header.sequenceNumber = sequence_number++;
+ // Payload is 1000 bits.
+ packets.push_back(
+ new MediaPacket(0, send_time_us, kPacketSizeBytes, header));
+ // Packets are sent far enough a part plus an extra millisecond so that they
+ // will never be in the choke queue at the same time.
+ send_time_us += kExpectedSendTimeUs + 1000;
+ }
+ ASSERT_TRUE(IsTimeSorted(packets));
+ filter.RunFor(2 * kExpectedSendTimeUs + 1000, &packets);
+ EXPECT_EQ(kExpectedSendTimeUs, packets.front()->send_time_us());
+ delete packets.front();
+ packets.pop_front();
+ EXPECT_EQ(2 * kExpectedSendTimeUs + 1000, packets.front()->send_time_us());
+ delete packets.front();
+ packets.pop_front();
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, Short) {
+ // 100ms, 100 packets, 10 kbps choke -> 1 kbit of data should have propagated.
+ // That is actually just a single packet, since each packet has 1000 bits of
+ // payload.
+ ChokeFilter filter(NULL, 0);
+ filter.set_capacity_kbps(10);
+ TestChoke(&filter, 100, 100, 1);
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, Medium) {
+ // 100ms, 10 packets, 10 kbps choke -> 1 packet through, or 1 kbit.
+ ChokeFilter filter(NULL, 0);
+ filter.set_capacity_kbps(10);
+ TestChoke(&filter, 100, 10, 1);
+ // 200ms, no new packets -> another packet through.
+ TestChoke(&filter, 100, 0, 1);
+ // 1000ms, no new packets -> 8 more packets.
+ TestChoke(&filter, 800, 0, 8);
+ // 2000ms, no new packets -> queue is empty so no output.
+ TestChoke(&filter, 1000, 0, 0);
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, Long) {
+ // 100ms, 100 packets in queue, 10 kbps choke -> 1 packet through, or 1 kbit.
+ ChokeFilter filter(NULL, 0);
+ filter.set_capacity_kbps(10);
+ TestChoke(&filter, 100, 100, 1);
+ // 200ms, no input, another packet through.
+ TestChoke(&filter, 100, 0, 1);
+ // 1000ms, no input, 8 packets through.
+ TestChoke(&filter, 800, 0, 8);
+ // 10000ms, no input, raise choke to 100 kbps. Remaining 90 packets in queue
+ // should be propagated, for a total of 90 kbps.
+ filter.set_capacity_kbps(100);
+ TestChoke(&filter, 9000, 0, 90);
+ // 10100ms, 20 more packets -> 10 packets or 10 kbit through.
+ TestChoke(&filter, 100, 20, 10);
+ // 10300ms, 10 more packets -> 20 packets out.
+ TestChoke(&filter, 200, 10, 20);
+ // 11300ms, no input, queue should be empty.
+ filter.set_capacity_kbps(10);
+ TestChoke(&filter, 1000, 0, 0);
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, MaxDelay) {
+ // 10 kbps choke, 500 ms delay cap
+ ChokeFilter filter(NULL, 0);
+ filter.set_capacity_kbps(10);
+ filter.set_max_delay_ms(500);
+ // 100ms, 100 packets in queue, 10 kbps choke -> 1 packet through, or 1 kbit.
+ TestChoke(&filter, 100, 100, 1);
+ CheckMaxDelay(500);
+ // 500ms, no input, 4 more packets through.
+ TestChoke(&filter, 400, 0, 4);
+ // 10000ms, no input, remaining packets should have been dropped.
+ TestChoke(&filter, 9500, 0, 0);
+
+ // 100 ms delay cap
+ filter.set_max_delay_ms(100);
+ // 10100ms, 50 more packets -> 1 packets or 1 kbit through.
+ TestChoke(&filter, 100, 50, 1);
+ CheckMaxDelay(100);
+ // 20000ms, no input, remaining packets in queue should have been dropped.
+ TestChoke(&filter, 9900, 0, 0);
+
+ // Reset delay cap (0 is no cap) and verify no packets are dropped.
+ filter.set_capacity_kbps(10);
+ filter.set_max_delay_ms(0);
+ TestChoke(&filter, 100, 100, 1);
+ TestChoke(&filter, 9900, 0, 99);
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, ShortTrace) {
+ // According to the input file 6 packets should be transmitted within
+ // 100 milliseconds.
+ TraceBasedDeliveryFilter filter(NULL, 0);
+ ASSERT_TRUE(filter.Init(test::ResourcePath("synthetic-trace", "rx")));
+ TestChoke(&filter, 100, 100, 6);
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, ShortTraceTwoWraps) {
+ // According to the input file 19 packets should be transmitted within
+ // 280 milliseconds (at the wrapping point two packets are sent back to back).
+ TraceBasedDeliveryFilter filter(NULL, 0);
+ ASSERT_TRUE(filter.Init(test::ResourcePath("synthetic-trace", "rx")));
+ TestChoke(&filter, 280, 100, 19);
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, ShortTraceMaxDelay) {
+ TraceBasedDeliveryFilter filter(NULL, 0);
+ filter.set_max_delay_ms(25);
+ ASSERT_TRUE(filter.Init(test::ResourcePath("synthetic-trace", "rx")));
+ // Uses all slots up to 110 ms. Several packets are being dropped.
+ TestChoke(&filter, 110, 20, 9);
+ CheckMaxDelay(25);
+ // Simulate enough time for the next slot (at 135 ms) to be used. This makes
+ // sure that a slot isn't missed between runs.
+ TestChoke(&filter, 25, 1, 1);
+}
+
+void TestVideoSender(VideoSender* sender,
+ int64_t run_for_ms,
+ uint32_t expected_packets,
+ uint32_t expected_payload_size,
+ size_t expected_total_payload_size) {
+ assert(sender);
+ Packets packets;
+ sender->RunFor(run_for_ms, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+ EXPECT_EQ(expected_packets, packets.size());
+
+ int64_t send_time_us = -1;
+ size_t total_payload_size = 0;
+ uint32_t absolute_send_time = 0;
+ uint32_t absolute_send_time_wraps = 0;
+ uint32_t rtp_timestamp = 0;
+ uint32_t rtp_timestamp_wraps = 0;
+
+ for (const auto* packet : packets) {
+ const MediaPacket* media_packet = static_cast<const MediaPacket*>(packet);
+ EXPECT_LE(send_time_us, media_packet->send_time_us());
+ send_time_us = media_packet->send_time_us();
+ if (sender->source()->max_payload_size_bytes() !=
+ media_packet->payload_size()) {
+ EXPECT_EQ(expected_payload_size, media_packet->payload_size());
+ }
+ total_payload_size += media_packet->payload_size();
+ if (absolute_send_time >
+ media_packet->header().extension.absoluteSendTime) {
+ absolute_send_time_wraps++;
+ }
+ absolute_send_time = media_packet->header().extension.absoluteSendTime;
+ if (rtp_timestamp > media_packet->header().timestamp) {
+ rtp_timestamp_wraps++;
+ }
+ rtp_timestamp = media_packet->header().timestamp;
+ }
+
+ EXPECT_EQ(expected_total_payload_size, total_payload_size);
+ EXPECT_GE(1u, absolute_send_time_wraps);
+ EXPECT_GE(1u, rtp_timestamp_wraps);
+
+ for (auto* packet : packets)
+ delete packet;
+}
+
+// Random {-1, 0, +1} ms was added to frame timestamps.
+
+TEST(BweTestFramework_VideoSenderTest, Fps1Kbps80_1s) {
+ // 1 fps, 80 kbps
+ VideoSource source(0, 1.0f, 80, 0x1234, 0);
+ VideoSender sender(NULL, &source, kNullEstimator);
+ EXPECT_EQ(80000u, source.bits_per_second());
+ // We're at 1 fps, so all packets should be generated on first call, giving 10
+ // packets of each 1000 bytes, total 10000 bytes.
+ TestVideoSender(&sender, 1, 9, 400, 10000);
+ // 998ms, should see no output here.
+ TestVideoSender(&sender, 997, 0, 0, 0);
+ // 1001ms, should get data for one more frame.
+ TestVideoSender(&sender, 3, 9, 400, 10000);
+ // 1998ms, should see no output here.
+ TestVideoSender(&sender, 997, 0, 0, 0);
+ // 2001ms, one more frame.
+ TestVideoSender(&sender, 3, 9, 400, 10000);
+ // 2998ms, should see nothing.
+ TestVideoSender(&sender, 997, 0, 0, 0);
+}
+
+TEST(BweTestFramework_VideoSenderTest, Fps1Kbps80_1s_Offset) {
+ // 1 fps, 80 kbps, offset 0.5 of a frame period, ==0.5s in this case.
+ VideoSource source(0, 1.0f, 80, 0x1234, 500);
+ VideoSender sender(NULL, &source, kNullEstimator);
+ EXPECT_EQ(80000u, source.bits_per_second());
+ // 498ms, no output.
+ TestVideoSender(&sender, 498, 0, 0, 0);
+ // 501ms, first frame (this is the offset we set), 10 packets of 1000 bytes.
+ TestVideoSender(&sender, 3, 9, 400, 10000);
+ // 1498ms, nothing.
+ TestVideoSender(&sender, 997, 0, 0, 0);
+ // 1501ms, second frame.
+ TestVideoSender(&sender, 3, 9, 400, 10000);
+ // 2498ms, nothing.
+ TestVideoSender(&sender, 997, 0, 0, 0);
+ // 2501ms, third frame.
+ TestVideoSender(&sender, 3, 9, 400, 10000);
+ // 3498ms, nothing.
+ TestVideoSender(&sender, 997, 0, 0, 0);
+}
+
+TEST(BweTestFramework_VideoSenderTest, Fps50Kpbs80_11s) {
+ // 50 fps, 80 kbps.
+ VideoSource source(0, 50.0f, 80, 0x1234, 0);
+ VideoSender sender(NULL, &source, kNullEstimator);
+ EXPECT_EQ(80000u, source.bits_per_second());
+ // 9981, should see 500 frames, 200 byte payloads, total 100000 bytes.
+ TestVideoSender(&sender, 9981, 500, 200, 100000);
+ // 9998ms, nothing.
+ TestVideoSender(&sender, 17, 0, 0, 0);
+ // 10001ms, 501st frame as a single packet.
+ TestVideoSender(&sender, 3, 1, 200, 200);
+ // 10981ms, 49 more frames.
+ TestVideoSender(&sender, 981, 49, 200, 9800);
+ // 10998ms, nothing.
+ TestVideoSender(&sender, 17, 0, 0, 0);
+}
+
+TEST(BweTestFramework_VideoSenderTest, Fps20Kpbs120_1s) {
+ // 20 fps, 120 kbps.
+ VideoSource source(0, 20.0f, 120, 0x1234, 0);
+ VideoSender sender(NULL, &source, kNullEstimator);
+ EXPECT_EQ(120000u, source.bits_per_second());
+ // 451ms, 10 frames with 750 byte payloads, total 7500 bytes.
+ TestVideoSender(&sender, 451, 10, 750, 7500);
+ // 498ms, nothing.
+ TestVideoSender(&sender, 47, 0, 0, 0);
+ // 501ms, one more frame.
+ TestVideoSender(&sender, 3, 1, 750, 750);
+ // 951ms, 9 more frames.
+ TestVideoSender(&sender, 450, 9, 750, 6750);
+ // 998ms, nothing.
+ TestVideoSender(&sender, 47, 0, 0, 0);
+}
+
+TEST(BweTestFramework_VideoSenderTest, Fps25Kbps820_20s) {
+ // 25 fps, 820 kbps.
+ VideoSource source(0, 25.0f, 820, 0x1234, 0);
+ VideoSender sender(NULL, &source, kNullEstimator);
+ EXPECT_EQ(820000u, source.bits_per_second());
+ // 9961ms, 250 frames. 820 kbps = 102500 bytes/s, so total should be 1025000.
+ // Each frame is 102500/25=4100 bytes, or 5 packets (4 @1000 bytes, 1 @100),
+ // so packet count should be 5*250=1250 and last packet of each frame has
+ // 100 bytes of payload.
+ TestVideoSender(&sender, 9961, 1000, 500, 1025000);
+ // 9998ms, nothing.
+ TestVideoSender(&sender, 37, 0, 0, 0);
+ // 19961ms, 250 more frames.
+ TestVideoSender(&sender, 9963, 1000, 500, 1025000);
+ // 19998ms, nothing.
+ TestVideoSender(&sender, 37, 0, 0, 0);
+ // 20001ms, one more frame, as described above (25fps == 40ms/frame).
+ TestVideoSender(&sender, 3, 4, 500, 4100);
+ // 20038ms, nothing.
+ TestVideoSender(&sender, 37, 0, 0, 0);
+}
+
+TEST(BweTestFramework_VideoSenderTest, TestAppendInOrder) {
+ // 1 fps, 80 kbps, 250ms offset.
+ VideoSource source1(0, 1.0f, 80, 0x1234, 250);
+ VideoSender sender1(NULL, &source1, kNullEstimator);
+ EXPECT_EQ(80000u, source1.bits_per_second());
+ Packets packets;
+ // Generate some packets, verify they are sorted.
+ sender1.RunFor(999, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+ EXPECT_EQ(9u, packets.size());
+ // Generate some more packets and verify they are appended to end of list.
+ sender1.RunFor(1000, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+ EXPECT_EQ(18u, packets.size());
+
+ // Another sender, 2 fps, 160 kbps, 150ms offset
+ VideoSource source2(0, 2.0f, 160, 0x2234, 150);
+ VideoSender sender2(NULL, &source2, kNullEstimator);
+ EXPECT_EQ(160000u, source2.bits_per_second());
+ // Generate some packets, verify that they are merged with the packets already
+ // on the list.
+ sender2.RunFor(999, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ EXPECT_EQ(36u, packets.size());
+ // Generate some more.
+ sender2.RunFor(1000, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ EXPECT_EQ(54u, packets.size());
+
+ for (auto* packet : packets)
+ delete packet;
+}
+
+TEST(BweTestFramework_VideoSenderTest, FeedbackIneffective) {
+ VideoSource source(0, 25.0f, 820, 0x1234, 0);
+ VideoSender sender(NULL, &source, kNullEstimator);
+
+ EXPECT_EQ(820000u, source.bits_per_second());
+ TestVideoSender(&sender, 9961, 1000, 500, 1025000);
+
+ // Make sure feedback has no effect on a regular video sender.
+ RembFeedback* feedback = new RembFeedback(0, 0, 0, 512000, RTCPReportBlock());
+ Packets packets;
+ packets.push_back(feedback);
+ sender.RunFor(0, &packets);
+ EXPECT_EQ(820000u, source.bits_per_second());
+ TestVideoSender(&sender, 10000, 1000, 500, 1025000);
+}
+
+TEST(BweTestFramework_AdaptiveVideoSenderTest, FeedbackChangesBitrate) {
+ AdaptiveVideoSource source(0, 25.0f, 820, 0x1234, 0);
+ VideoSender sender(NULL, &source, kRembEstimator);
+ EXPECT_EQ(820000u, source.bits_per_second());
+ TestVideoSender(&sender, 9961, 1000, 500, 1025000);
+
+ // Make sure we can reduce the bitrate.
+ RembFeedback* feedback = new RembFeedback(0, 0, 0, 512000, RTCPReportBlock());
+ Packets packets;
+ packets.push_back(feedback);
+ sender.RunFor(0, &packets);
+ EXPECT_EQ(512000u, source.bits_per_second());
+ TestVideoSender(&sender, 10000, 750, 160, 640000);
+
+ // Increase the bitrate to the initial bitrate and verify that the output is
+ // the same.
+ feedback = new RembFeedback(0, 0, 0, 820000, RTCPReportBlock());
+ packets.push_back(feedback);
+ sender.RunFor(10000, &packets);
+ EXPECT_EQ(820000u, source.bits_per_second());
+
+ for (auto* packet : packets)
+ delete packet;
+}
+
+TEST(BweTestFramework_AdaptiveVideoSenderTest, Paced_FeedbackChangesBitrate) {
+ AdaptiveVideoSource source(0, 25.0f, 820, 0x1234, 0);
+ PacedVideoSender sender(NULL, &source, kRembEstimator);
+ EXPECT_EQ(820000u, source.bits_per_second());
+ TestVideoSender(&sender, 9998, 1000, 500, 1025000);
+
+ // Make sure we can reduce the bitrate.
+ RembFeedback* feedback = new RembFeedback(0, 1, 0, 512000, RTCPReportBlock());
+ Packets packets;
+ packets.push_back(feedback);
+ sender.RunFor(10000, &packets);
+ ASSERT_EQ(512000u, source.bits_per_second());
+ TestVideoSender(&sender, 10000, 750, 160, 640000);
+
+ // Increase the bitrate to the initial bitrate and verify that the output is
+ // the same.
+ feedback = new RembFeedback(0, 0, 0, 820000, RTCPReportBlock());
+ packets.push_back(feedback);
+ sender.RunFor(10000, &packets);
+ EXPECT_EQ(820000u, source.bits_per_second());
+
+ for (auto* packet : packets)
+ delete packet;
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.cc
new file mode 100644
index 0000000000..dcc08d8dde
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.cc
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2013 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 "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+
+#if BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <algorithm>
+#include <sstream>
+
+#include "webrtc/system_wrappers/include/critical_section_wrapper.h"
+#include "webrtc/system_wrappers/include/thread_wrapper.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+Logging Logging::g_Logging;
+
+static std::string ToString(uint32_t v) {
+ std::stringstream ss;
+ ss << v;
+ return ss.str();
+}
+
+Logging::Context::Context(uint32_t name, int64_t timestamp_ms, bool enabled) {
+ Logging::GetInstance()->PushState(ToString(name), timestamp_ms, enabled);
+}
+
+Logging::Context::Context(const std::string& name, int64_t timestamp_ms,
+ bool enabled) {
+ Logging::GetInstance()->PushState(name, timestamp_ms, enabled);
+}
+
+Logging::Context::Context(const char* name, int64_t timestamp_ms,
+ bool enabled) {
+ Logging::GetInstance()->PushState(name, timestamp_ms, enabled);
+}
+
+Logging::Context::~Context() {
+ Logging::GetInstance()->PopState();
+}
+
+Logging* Logging::GetInstance() {
+ return &g_Logging;
+}
+
+void Logging::SetGlobalContext(uint32_t name) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ thread_map_[ThreadWrapper::GetThreadId()].global_state.tag = ToString(name);
+}
+
+void Logging::SetGlobalContext(const std::string& name) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ thread_map_[ThreadWrapper::GetThreadId()].global_state.tag = name;
+}
+
+void Logging::SetGlobalContext(const char* name) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ thread_map_[ThreadWrapper::GetThreadId()].global_state.tag = name;
+}
+
+void Logging::SetGlobalEnable(bool enabled) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ thread_map_[ThreadWrapper::GetThreadId()].global_state.enabled = enabled;
+}
+
+void Logging::Log(const char format[], ...) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ if (state.enabled) {
+ printf("%s\t", state.tag.c_str());
+ va_list args;
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf("\n");
+ }
+}
+
+void Logging::Plot(int figure, double value) {
+ Plot(figure, value, "-");
+}
+
+void Logging::Plot(int figure, double value, const std::string& alg_name) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ std::string label = state.tag + '@' + alg_name;
+ std::string prefix("Available");
+ if (alg_name.compare(0, prefix.length(), prefix) == 0) {
+ std::string receiver("Receiver");
+ size_t start_pos = label.find(receiver);
+ if (start_pos != std::string::npos) {
+ label.replace(start_pos, receiver.length(), "Sender");
+ }
+ }
+ if (state.enabled) {
+ printf("PLOT\t%d\t%s\t%f\t%f\n", figure, label.c_str(),
+ state.timestamp_ms * 0.001, value);
+ }
+}
+
+void Logging::PlotBar(int figure,
+ const std::string& name,
+ double value,
+ int flow_id) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ if (state.enabled) {
+ printf("BAR\t%d\t%s_%d\t%f\n", figure, name.c_str(), flow_id, value);
+ }
+}
+
+void Logging::PlotBaselineBar(int figure,
+ const std::string& name,
+ double value,
+ int flow_id) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ if (state.enabled) {
+ printf("BASELINE\t%d\t%s_%d\t%f\n", figure, name.c_str(), flow_id, value);
+ }
+}
+
+void Logging::PlotErrorBar(int figure,
+ const std::string& name,
+ double value,
+ double ylow,
+ double yhigh,
+ const std::string& error_title,
+ int flow_id) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ if (state.enabled) {
+ printf("ERRORBAR\t%d\t%s_%d\t%f\t%f\t%f\t%s\n", figure, name.c_str(),
+ flow_id, value, ylow, yhigh, error_title.c_str());
+ }
+}
+
+void Logging::PlotLimitErrorBar(int figure,
+ const std::string& name,
+ double value,
+ double ylow,
+ double yhigh,
+ const std::string& error_title,
+ double ymax,
+ const std::string& limit_title,
+ int flow_id) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ if (state.enabled) {
+ printf("LIMITERRORBAR\t%d\t%s_%d\t%f\t%f\t%f\t%s\t%f\t%s\n", figure,
+ name.c_str(), flow_id, value, ylow, yhigh, error_title.c_str(), ymax,
+ limit_title.c_str());
+ }
+}
+
+void Logging::PlotLabel(int figure,
+ const std::string& title,
+ const std::string& y_label,
+ int num_flows) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ if (state.enabled) {
+ printf("LABEL\t%d\t%s\t%s\t%d\n", figure, title.c_str(), y_label.c_str(),
+ num_flows);
+ }
+}
+
+Logging::Logging()
+ : crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
+ thread_map_() {
+}
+
+Logging::State::State() : tag(""), timestamp_ms(0), enabled(true) {}
+
+Logging::State::State(const std::string& tag, int64_t timestamp_ms,
+ bool enabled)
+ : tag(tag),
+ timestamp_ms(timestamp_ms),
+ enabled(enabled) {
+}
+
+void Logging::State::MergePrevious(const State& previous) {
+ if (tag.empty()) {
+ tag = previous.tag;
+ } else if (!previous.tag.empty()) {
+ tag = previous.tag + "_" + tag;
+ }
+ timestamp_ms = std::max(previous.timestamp_ms, timestamp_ms);
+ enabled = previous.enabled && enabled;
+}
+
+void Logging::PushState(const std::string& append_to_tag, int64_t timestamp_ms,
+ bool enabled) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ State new_state(append_to_tag, timestamp_ms, enabled);
+ ThreadState* thread_state = &thread_map_[ThreadWrapper::GetThreadId()];
+ std::stack<State>* stack = &thread_state->stack;
+ if (stack->empty()) {
+ new_state.MergePrevious(thread_state->global_state);
+ } else {
+ new_state.MergePrevious(stack->top());
+ }
+ stack->push(new_state);
+}
+
+void Logging::PopState() {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ std::stack<State>* stack = &it->second.stack;
+ int64_t newest_timestamp_ms = stack->top().timestamp_ms;
+ stack->pop();
+ if (!stack->empty()) {
+ State* state = &stack->top();
+ // Update time so that next log/plot will use the latest time seen so far
+ // in this call tree.
+ state->timestamp_ms = std::max(state->timestamp_ms, newest_timestamp_ms);
+ }
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h
new file mode 100644
index 0000000000..4115d30c2a
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
+
+// To enable BWE logging, run this command from trunk/ :
+// build/gyp_chromium --depth=. webrtc/modules/modules.gyp
+// -Denable_bwe_test_logging=1
+#ifndef BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
+#define BWE_TEST_LOGGING_COMPILE_TIME_ENABLE 0
+#endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
+
+// BWE logging allows you to insert dynamically named log/plot points in the
+// call tree. E.g. the function:
+// void f1() {
+// BWE_TEST_LOGGING_TIME(clock_->TimeInMilliseconds());
+// BWE_TEST_LOGGING_CONTEXT("stream");
+// for (uint32_t i=0; i<4; ++i) {
+// BWE_TEST_LOGGING_ENABLE(i & 1);
+// BWE_TEST_LOGGING_CONTEXT(i);
+// BWE_TEST_LOGGING_LOG1("weight", "%f tonnes", weights_[i]);
+// for (float j=0.0f; j<1.0; j+=0.4f) {
+// BWE_TEST_LOGGING_PLOT(0, "bps", -1, j);
+// }
+// }
+// }
+//
+// Might produce the output:
+// stream_00000001_weight 13.000000 tonnes
+// PLOT stream_00000001_bps 1.000000 0.000000
+// PLOT stream_00000001_bps 1.000000 0.400000
+// PLOT stream_00000001_bps 1.000000 0.800000
+// stream_00000003_weight 39.000000 tonnes
+// PLOT stream_00000003_bps 1.000000 0.000000
+// PLOT stream_00000003_bps 1.000000 0.400000
+// PLOT stream_00000003_bps 1.000000 0.800000
+//
+// Log *contexts* are names concatenated with '_' between them, with the name
+// of the logged/plotted string/value last. Plot *time* is inherited down the
+// tree. A branch is enabled by default but can be *disabled* to reduce output.
+// The difference between the LOG and PLOT macros is that PLOT prefixes the line
+// so it can be easily filtered, plus it outputs the current time.
+
+#if !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
+
+// Set a thread-global base logging context. This name will be prepended to all
+// hierarchical contexts.
+// |name| is a char*, std::string or uint32_t to name the context.
+#define BWE_TEST_LOGGING_GLOBAL_CONTEXT(name)
+
+// Thread-globally allow/disallow logging.
+// |enable| is expected to be a bool.
+#define BWE_TEST_LOGGING_GLOBAL_ENABLE(enabled)
+
+// Insert a (hierarchical) logging context.
+// |name| is a char*, std::string or uint32_t to name the context.
+#define BWE_TEST_LOGGING_CONTEXT(name)
+
+// Allow/disallow logging down the call tree from this point. Logging must be
+// enabled all the way to the root of the call tree to take place.
+// |enable| is expected to be a bool.
+#define BWE_TEST_LOGGING_ENABLE(enabled)
+
+// Set current time (only affects PLOT output). Down the call tree, the latest
+// time set always takes precedence.
+// |time| is an int64_t time in ms, or -1 to inherit time from previous context.
+#define BWE_TEST_LOGGING_TIME(time)
+
+// Print to stdout, e.g.:
+// Context1_Context2_Name printf-formated-string
+// |name| is a char*, std::string or uint32_t to name the log line.
+// |format| is a printf format string.
+// |_1...| are arguments for printf.
+#define BWE_TEST_LOGGING_LOG1(name, format, _1)
+#define BWE_TEST_LOGGING_LOG2(name, format, _1, _2)
+#define BWE_TEST_LOGGING_LOG3(name, format, _1, _2, _3)
+#define BWE_TEST_LOGGING_LOG4(name, format, _1, _2, _3, _4)
+#define BWE_TEST_LOGGING_LOG5(name, format, _1, _2, _3, _4, _5)
+
+// Print to stdout in tab-separated format suitable for plotting, e.g.:
+// PLOT figure Context1_Context2_Name time value
+// |figure| is a figure id. Different figures are plotted in different windows.
+// |name| is a char*, std::string or uint32_t to name the plotted value.
+// |time| is an int64_t time in ms, or -1 to inherit time from previous context.
+// |value| is a double precision float to be plotted.
+// |alg_name| is an optional argument, a string
+#define BWE_TEST_LOGGING_PLOT(figure, name, time, value)
+#define BWE_TEST_LOGGING_PLOT_WITH_NAME(figure, name, time, value, alg_name)
+
+// Print to stdout in tab-separated format suitable for plotting, e.g.:
+// BAR figure Context1_Context2_Name x_left width value
+// |figure| is a figure id. Different figures are plotted in different windows.
+// |name| is a char*, std::string or uint32_t to name the plotted value.
+// |value| is a double precision float to be plotted.
+// |ylow| and |yhigh| are double precision float for the error line.
+// |title| is a string and refers to the error label.
+// |ymax| is a double precision float for the limit horizontal line.
+// |limit_title| is a string and refers to the limit label.
+#define BWE_TEST_LOGGING_BAR(figure, name, value, flow_id)
+#define BWE_TEST_LOGGING_ERRORBAR(figure, name, value, ylow, yhigh, \
+ error_title, flow_id)
+#define BWE_TEST_LOGGING_LIMITERRORBAR( \
+ figure, name, value, ylow, yhigh, error_title, ymax, limit_title, flow_id)
+
+#define BWE_TEST_LOGGING_BASELINEBAR(figure, name, value, flow_id)
+
+// |num_flows| is an integer refering to the number of RMCAT flows in the
+// scenario.
+// Define |x_label| and |y_label| for plots.
+#define BWE_TEST_LOGGING_LABEL(figure, x_label, y_label, num_flows)
+
+#else // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
+
+#include <map>
+#include <stack>
+#include <string>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/common_types.h"
+
+#define BWE_TEST_LOGGING_GLOBAL_CONTEXT(name) \
+ do { \
+ webrtc::testing::bwe::Logging::GetInstance()->SetGlobalContext(name); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_GLOBAL_ENABLE(enabled) \
+ do { \
+ webrtc::testing::bwe::Logging::GetInstance()->SetGlobalEnable(enabled); \
+ } while (0);
+
+#define __BWE_TEST_LOGGING_CONTEXT_NAME(ctx, line) ctx ## line
+#define __BWE_TEST_LOGGING_CONTEXT_DECLARE(ctx, line, name, time, enabled) \
+ webrtc::testing::bwe::Logging::Context \
+ __BWE_TEST_LOGGING_CONTEXT_NAME(ctx, line)(name, time, enabled)
+
+#define BWE_TEST_LOGGING_CONTEXT(name) \
+ __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, name, -1, true)
+#define BWE_TEST_LOGGING_ENABLE(enabled) \
+ __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, "", -1, \
+ static_cast<bool>(enabled))
+#define BWE_TEST_LOGGING_TIME(time) \
+ __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, "", \
+ static_cast<int64_t>(time), true)
+
+#define BWE_TEST_LOGGING_LOG1(name, format, _1) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1); \
+ } while (0);
+#define BWE_TEST_LOGGING_LOG2(name, format, _1, _2) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2); \
+ } while (0);
+#define BWE_TEST_LOGGING_LOG3(name, format, _1, _2, _3) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2, _3); \
+ } while (0);
+#define BWE_TEST_LOGGING_LOG4(name, format, _1, _2, _3, _4) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2, _3, \
+ _4); \
+ } while (0);
+#define BWE_TEST_LOGGING_LOG5(name, format, _1, _2, _3, _4, _5) \
+ do {\
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2, _3, \
+ _4, _5); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_PLOT(figure, name, time, value) \
+ do { \
+ __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
+ static_cast<int64_t>(time), true); \
+ webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, value); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_PLOT_WITH_NAME(figure, name, time, value, alg_name) \
+ do { \
+ __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
+ static_cast<int64_t>(time), true); \
+ webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, value, \
+ alg_name); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_BAR(figure, name, value, flow_id) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->PlotBar(figure, name, value, \
+ flow_id); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_BASELINEBAR(figure, name, value, flow_id) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->PlotBaselineBar( \
+ figure, name, value, flow_id); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_ERRORBAR(figure, name, value, ylow, yhigh, title, \
+ flow_id) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->PlotErrorBar( \
+ figure, name, value, ylow, yhigh, title, flow_id); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_LIMITERRORBAR( \
+ figure, name, value, ylow, yhigh, error_title, ymax, limit_title, flow_id) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->PlotLimitErrorBar( \
+ figure, name, value, ylow, yhigh, error_title, ymax, limit_title, \
+ flow_id); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_LABEL(figure, title, y_label, num_flows) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(title); \
+ webrtc::testing::bwe::Logging::GetInstance()->PlotLabel( \
+ figure, title, y_label, num_flows); \
+ } while (0);
+
+namespace webrtc {
+
+class CriticalSectionWrapper;
+
+namespace testing {
+namespace bwe {
+
+class Logging {
+ public:
+ class Context {
+ public:
+ Context(uint32_t name, int64_t timestamp_ms, bool enabled);
+ Context(const std::string& name, int64_t timestamp_ms, bool enabled);
+ Context(const char* name, int64_t timestamp_ms, bool enabled);
+ ~Context();
+ private:
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(Context);
+ };
+
+ static Logging* GetInstance();
+
+ void SetGlobalContext(uint32_t name);
+ void SetGlobalContext(const std::string& name);
+ void SetGlobalContext(const char* name);
+ void SetGlobalEnable(bool enabled);
+
+ void Log(const char format[], ...);
+ void Plot(int figure, double value);
+ void Plot(int figure, double value, const std::string& alg_name);
+ void PlotBar(int figure, const std::string& name, double value, int flow_id);
+ void PlotBaselineBar(int figure,
+ const std::string& name,
+ double value,
+ int flow_id);
+ void PlotErrorBar(int figure,
+ const std::string& name,
+ double value,
+ double ylow,
+ double yhigh,
+ const std::string& error_title,
+ int flow_id);
+
+ void PlotLimitErrorBar(int figure,
+ const std::string& name,
+ double value,
+ double ylow,
+ double yhigh,
+ const std::string& error_title,
+ double ymax,
+ const std::string& limit_title,
+ int flow_id);
+ void PlotLabel(int figure,
+ const std::string& title,
+ const std::string& y_label,
+ int num_flows);
+
+ private:
+ struct State {
+ State();
+ State(const std::string& new_tag, int64_t timestamp_ms, bool enabled);
+ void MergePrevious(const State& previous);
+
+ std::string tag;
+ int64_t timestamp_ms;
+ bool enabled;
+ };
+ struct ThreadState {
+ State global_state;
+ std::stack<State> stack;
+ };
+ typedef std::map<uint32_t, ThreadState> ThreadMap;
+
+ Logging();
+ void PushState(const std::string& append_to_tag, int64_t timestamp_ms,
+ bool enabled);
+ void PopState();
+
+ static Logging g_Logging;
+ rtc::scoped_ptr<CriticalSectionWrapper> crit_sect_;
+ ThreadMap thread_map_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(Logging);
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_unittest.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_unittest.cc
new file mode 100644
index 0000000000..6b3ce4847c
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_unittest.cc
@@ -0,0 +1,393 @@
+/*
+ * 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 "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+const int kSetCapacity = 1000;
+
+class LinkedSetTest : public ::testing::Test {
+ public:
+ LinkedSetTest() : linked_set_(kSetCapacity) {}
+
+ ~LinkedSetTest() {}
+
+ protected:
+ LinkedSet linked_set_;
+};
+
+TEST_F(LinkedSetTest, EmptySet) {
+ EXPECT_EQ(linked_set_.OldestSeqNumber(), 0);
+ EXPECT_EQ(linked_set_.NewestSeqNumber(), 0);
+}
+
+TEST_F(LinkedSetTest, SinglePacket) {
+ const uint16_t kSeqNumber = 1; // Arbitrary.
+ // Other parameters don't matter here.
+ linked_set_.Insert(kSeqNumber, 0, 0, 0);
+
+ EXPECT_EQ(linked_set_.OldestSeqNumber(), kSeqNumber);
+ EXPECT_EQ(linked_set_.NewestSeqNumber(), kSeqNumber);
+}
+
+TEST_F(LinkedSetTest, MultiplePackets) {
+ const uint16_t kNumberPackets = 100;
+
+ std::vector<uint16_t> sequence_numbers;
+ for (size_t i = 0; i < kNumberPackets; ++i) {
+ sequence_numbers.push_back(static_cast<uint16_t>(i + 1));
+ }
+ random_shuffle(sequence_numbers.begin(), sequence_numbers.end());
+
+ for (size_t i = 0; i < kNumberPackets; ++i) {
+ // Other parameters don't matter here.
+ linked_set_.Insert(static_cast<uint16_t>(i), 0, 0, 0);
+ }
+
+ // Packets arriving out of order should not affect the following values:
+ EXPECT_EQ(linked_set_.OldestSeqNumber(), 0);
+ EXPECT_EQ(linked_set_.NewestSeqNumber(), kNumberPackets - 1);
+}
+
+TEST_F(LinkedSetTest, Overflow) {
+ const int kFirstSeqNumber = -100;
+ const int kLastSeqNumber = 100;
+
+ for (int i = kFirstSeqNumber; i <= kLastSeqNumber; ++i) {
+ // Other parameters don't matter here.
+ linked_set_.Insert(static_cast<uint16_t>(i), 0, 0, 0);
+ }
+
+ // Packets arriving out of order should not affect the following values:
+ EXPECT_EQ(linked_set_.OldestSeqNumber(),
+ static_cast<uint16_t>(kFirstSeqNumber));
+ EXPECT_EQ(linked_set_.NewestSeqNumber(),
+ static_cast<uint16_t>(kLastSeqNumber));
+}
+
+class SequenceNumberOlderThanTest : public ::testing::Test {
+ public:
+ SequenceNumberOlderThanTest() {}
+ ~SequenceNumberOlderThanTest() {}
+
+ protected:
+ SequenceNumberOlderThan comparator_;
+};
+
+TEST_F(SequenceNumberOlderThanTest, Operator) {
+ // Operator()(x, y) returns true <==> y is newer than x.
+ EXPECT_TRUE(comparator_.operator()(0x0000, 0x0001));
+ EXPECT_TRUE(comparator_.operator()(0x0001, 0x1000));
+ EXPECT_FALSE(comparator_.operator()(0x0001, 0x0000));
+ EXPECT_FALSE(comparator_.operator()(0x0002, 0x0002));
+ EXPECT_TRUE(comparator_.operator()(0xFFF6, 0x000A));
+ EXPECT_FALSE(comparator_.operator()(0x000A, 0xFFF6));
+ EXPECT_TRUE(comparator_.operator()(0x0000, 0x8000));
+ EXPECT_FALSE(comparator_.operator()(0x8000, 0x0000));
+}
+
+class LossAccountTest : public ::testing::Test {
+ public:
+ LossAccountTest() {}
+ ~LossAccountTest() {}
+
+ protected:
+ LossAccount loss_account_;
+};
+
+TEST_F(LossAccountTest, Operations) {
+ const size_t kTotal = 100; // Arbitrary values.
+ const size_t kLost = 10;
+
+ LossAccount rhs(kTotal, kLost);
+
+ loss_account_.Add(rhs);
+ EXPECT_EQ(loss_account_.num_total, kTotal);
+ EXPECT_EQ(loss_account_.num_lost, kLost);
+ EXPECT_NEAR(loss_account_.LossRatio(), static_cast<float>(kLost) / kTotal,
+ 0.001f);
+
+ loss_account_.Subtract(rhs);
+ EXPECT_EQ(loss_account_.num_total, 0UL);
+ EXPECT_EQ(loss_account_.num_lost, 0UL);
+ EXPECT_NEAR(loss_account_.LossRatio(), 0.0f, 0.001f);
+}
+
+class BweReceiverTest : public ::testing::Test {
+ public:
+ BweReceiverTest() : bwe_receiver_(kFlowId) {}
+ ~BweReceiverTest() {}
+
+ protected:
+ const int kFlowId = 1; // Arbitrary.
+ BweReceiver bwe_receiver_;
+};
+
+TEST_F(BweReceiverTest, ReceivingRateNoPackets) {
+ EXPECT_EQ(bwe_receiver_.RecentKbps(), static_cast<size_t>(0));
+}
+
+TEST_F(BweReceiverTest, 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 = BweReceiver::kReceivingRateTimeWindowMs;
+
+ const MediaPacket media_packet(kFlowId, kSendTimeUs, kPayloadSizeBytes,
+ kSequenceNumber);
+ bwe_receiver_.ReceivePacket(kArrivalTimeMs, media_packet);
+
+ const size_t kReceivingRateKbps = 8 * kPayloadSizeBytes / kTimeWindowMs;
+
+ EXPECT_NEAR(bwe_receiver_.RecentKbps(), kReceivingRateKbps,
+ static_cast<float>(kReceivingRateKbps) / 100.0f);
+}
+
+TEST_F(BweReceiverTest, 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);
+ bwe_receiver_.ReceivePacket(arrival_time_ms, media_packet);
+ }
+
+ const size_t kReceivingRateKbps = 8 * kPayloadSizeBytes / kTimeGapMs;
+ EXPECT_NEAR(bwe_receiver_.RecentKbps(), kReceivingRateKbps,
+ static_cast<float>(kReceivingRateKbps) / 100.0f);
+}
+
+TEST_F(BweReceiverTest, PacketLossNoPackets) {
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+}
+
+TEST_F(BweReceiverTest, PacketLossSinglePacket) {
+ const MediaPacket media_packet(kFlowId, 0, 0, 0);
+ bwe_receiver_.ReceivePacket(0, media_packet);
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+}
+
+TEST_F(BweReceiverTest, PacketLossContiguousPackets) {
+ const int64_t kTimeWindowMs = BweReceiver::kPacketLossTimeWindowMs;
+ size_t set_capacity = bwe_receiver_.GetSetCapacity();
+
+ for (int i = 0; i < 10; ++i) {
+ uint16_t sequence_number = static_cast<uint16_t>(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.
+ bwe_receiver_.ReceivePacket(0, media_packet);
+ }
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+
+ for (int i = 30; i > 20; i--) {
+ uint16_t sequence_number = static_cast<uint16_t>(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.
+ bwe_receiver_.ReceivePacket(2 * kTimeWindowMs, media_packet);
+ }
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+
+ // Should handle uint16_t overflow.
+ for (int i = 0xFFFF - 10; i < 0xFFFF + 10; ++i) {
+ uint16_t sequence_number = static_cast<uint16_t>(i);
+ const MediaPacket media_packet(kFlowId, 0, 0, sequence_number);
+ // Only the packets sent in this for loop will be considered.
+ bwe_receiver_.ReceivePacket(4 * kTimeWindowMs, media_packet);
+ }
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+
+ // Should handle set overflow.
+ for (int i = 0; i < set_capacity * 1.5; ++i) {
+ uint16_t sequence_number = static_cast<uint16_t>(i);
+ const MediaPacket media_packet(kFlowId, 0, 0, sequence_number);
+ // Only the packets sent in this for loop will be considered.
+ bwe_receiver_.ReceivePacket(6 * kTimeWindowMs, media_packet);
+ }
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+}
+
+// Should handle duplicates.
+TEST_F(BweReceiverTest, PacketLossDuplicatedPackets) {
+ const int64_t kTimeWindowMs = BweReceiver::kPacketLossTimeWindowMs;
+
+ for (int i = 0; i < 10; ++i) {
+ const MediaPacket media_packet(kFlowId, 0, 0, 0);
+ // Arrival time = 0, all packets will be considered.
+ bwe_receiver_.ReceivePacket(0, media_packet);
+ }
+ EXPECT_EQ(bwe_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.
+ bwe_receiver_.ReceivePacket(2 * kTimeWindowMs, media_packet);
+ }
+ }
+
+ EXPECT_NEAR(bwe_receiver_.RecentPacketLossRatio(), 1.0f / (kNumPackets + 1),
+ 0.1f / (kNumPackets + 1));
+}
+
+TEST_F(BweReceiverTest, PacketLossLakingPackets) {
+ size_t set_capacity = bwe_receiver_.GetSetCapacity();
+ EXPECT_LT(set_capacity, static_cast<size_t>(0xFFFF));
+
+ // Missing every other packet.
+ for (size_t i = 0; i < set_capacity; ++i) {
+ if ((i & 1) == 0) { // Only even sequence numbers.
+ uint16_t sequence_number = static_cast<uint16_t>(i);
+ const MediaPacket media_packet(kFlowId, 0, 0, sequence_number);
+ // Arrival time = 0, all packets will be considered.
+ bwe_receiver_.ReceivePacket(0, media_packet);
+ }
+ }
+ EXPECT_NEAR(bwe_receiver_.RecentPacketLossRatio(), 0.5f, 0.01f);
+}
+
+TEST_F(BweReceiverTest, PacketLossLakingFewPackets) {
+ size_t set_capacity = bwe_receiver_.GetSetCapacity();
+ EXPECT_LT(set_capacity, static_cast<size_t>(0xFFFF));
+
+ const int kPeriod = 100;
+ // Missing one for each kPeriod packets.
+ for (size_t i = 0; i < set_capacity; ++i) {
+ if ((i % kPeriod) != 0) {
+ uint16_t sequence_number = static_cast<uint16_t>(i);
+ const MediaPacket media_packet(kFlowId, 0, 0, sequence_number);
+ // Arrival time = 0, all packets will be considered.
+ bwe_receiver_.ReceivePacket(0, media_packet);
+ }
+ }
+ EXPECT_NEAR(bwe_receiver_.RecentPacketLossRatio(), 1.0f / kPeriod,
+ 0.1f / kPeriod);
+}
+
+// Packet's sequence numbers greatly apart, expect high loss.
+TEST_F(BweReceiverTest, PacketLossWideGap) {
+ const int64_t kTimeWindowMs = BweReceiver::kPacketLossTimeWindowMs;
+
+ const MediaPacket media_packet1(0, 0, 0, 1);
+ const MediaPacket media_packet2(0, 0, 0, 1000);
+ // Only these two packets will be considered.
+ bwe_receiver_.ReceivePacket(0, media_packet1);
+ bwe_receiver_.ReceivePacket(0, media_packet2);
+ EXPECT_NEAR(bwe_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.
+ bwe_receiver_.ReceivePacket(2 * kTimeWindowMs, media_packet3);
+ bwe_receiver_.ReceivePacket(2 * kTimeWindowMs, media_packet4);
+ EXPECT_NEAR(bwe_receiver_.RecentPacketLossRatio(), 0.99994f, 0.00001f);
+}
+
+// Packets arriving unordered should not be counted as losted.
+TEST_F(BweReceiverTest, PacketLossUnorderedPackets) {
+ size_t num_packets = bwe_receiver_.GetSetCapacity() / 2;
+ std::vector<uint16_t> sequence_numbers;
+
+ for (size_t i = 0; i < num_packets; ++i) {
+ sequence_numbers.push_back(static_cast<uint16_t>(i + 1));
+ }
+
+ random_shuffle(sequence_numbers.begin(), sequence_numbers.end());
+
+ for (size_t i = 0; i < num_packets; ++i) {
+ const MediaPacket media_packet(kFlowId, 0, 0, sequence_numbers[i]);
+ // Arrival time = 0, all packets will be considered.
+ bwe_receiver_.ReceivePacket(0, media_packet);
+ }
+
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+}
+
+TEST_F(BweReceiverTest, RecentKbps) {
+ EXPECT_EQ(bwe_receiver_.RecentKbps(), 0U);
+
+ const size_t kPacketSizeBytes = 1200;
+ const int kNumPackets = 100;
+
+ double window_size_s = bwe_receiver_.BitrateWindowS();
+
+ // Receive packets at the same time.
+ for (int i = 0; i < kNumPackets; ++i) {
+ MediaPacket packet(kFlowId, 0L, kPacketSizeBytes, static_cast<uint16_t>(i));
+ bwe_receiver_.ReceivePacket(0, packet);
+ }
+
+ EXPECT_NEAR(bwe_receiver_.RecentKbps(),
+ (8 * kNumPackets * kPacketSizeBytes) / (1000 * window_size_s),
+ 10);
+
+ int64_t time_gap_ms =
+ 2 * 1000 * window_size_s; // Larger than rate_counter time window.
+
+ MediaPacket packet(kFlowId, time_gap_ms * 1000, kPacketSizeBytes,
+ static_cast<uint16_t>(kNumPackets));
+ bwe_receiver_.ReceivePacket(time_gap_ms, packet);
+
+ EXPECT_NEAR(bwe_receiver_.RecentKbps(),
+ (8 * kPacketSizeBytes) / (1000 * window_size_s), 10);
+}
+
+TEST_F(BweReceiverTest, Loss) {
+ EXPECT_NEAR(bwe_receiver_.GlobalReceiverPacketLossRatio(), 0.0f, 0.001f);
+
+ LossAccount loss_account = bwe_receiver_.LinkedSetPacketLossRatio();
+ EXPECT_NEAR(loss_account.LossRatio(), 0.0f, 0.001f);
+
+ // Insert packets 1-50 and 151-200;
+ for (int i = 1; i <= 200; ++i) {
+ // Packet size and timestamp do not matter here.
+ MediaPacket packet(kFlowId, 0L, 0UL, static_cast<uint16_t>(i));
+ bwe_receiver_.ReceivePacket(0, packet);
+ if (i == 50) {
+ i += 100;
+ }
+ }
+
+ loss_account = bwe_receiver_.LinkedSetPacketLossRatio();
+ EXPECT_NEAR(loss_account.LossRatio(), 0.5f, 0.001f);
+
+ bwe_receiver_.RelieveSetAndUpdateLoss();
+ EXPECT_EQ(bwe_receiver_.received_packets_.size(), 100U / 10);
+
+ // No packet loss within the preserved packets.
+ loss_account = bwe_receiver_.LinkedSetPacketLossRatio();
+ EXPECT_NEAR(loss_account.LossRatio(), 0.0f, 0.001f);
+
+ // RelieveSetAndUpdateLoss automatically updates loss account.
+ EXPECT_NEAR(bwe_receiver_.GlobalReceiverPacketLossRatio(), 0.5f, 0.001f);
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc
new file mode 100644
index 0000000000..d77447f1ea
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc
@@ -0,0 +1,287 @@
+/*
+ * 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.
+ *
+ */
+
+// 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 <math.h>
+#include <algorithm>
+#include <vector>
+
+#include "webrtc/base/common.h"
+#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 {
+namespace testing {
+namespace bwe {
+
+const int64_t NadaBweReceiver::kReceivingRateTimeWindowMs = 500;
+
+NadaBweReceiver::NadaBweReceiver(int flow_id)
+ : BweReceiver(flow_id, kReceivingRateTimeWindowMs),
+ clock_(0),
+ last_feedback_ms_(0),
+ recv_stats_(ReceiveStatistics::Create(&clock_)),
+ baseline_delay_ms_(10000), // Initialized as an upper bound.
+ delay_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() {
+}
+
+void NadaBweReceiver::ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) {
+ 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.
+
+ clock_.AdvanceTimeMilliseconds(arrival_time_ms - clock_.TimeInMilliseconds());
+ recv_stats_->IncomingPacket(media_packet.header(),
+ media_packet.payload_size(), false);
+ // Refered as x_n.
+ int64_t delay_ms = arrival_time_ms - media_packet.sender_timestamp_ms();
+
+ // 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_; // Refered as d_n.
+ const int kMedian = ARRAY_SIZE(last_delays_ms_);
+ 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<int64_t>(
+ pow((static_cast<double>(kDelayMaxThresholdMs -
+ exp_smoothed_delay_ms_)) /
+ (kDelayMaxThresholdMs - kDelayLowThresholdMs),
+ 4.0) *
+ kDelayLowThresholdMs);
+ } else {
+ est_queuing_delay_signal_ms_ = 0;
+ }
+
+ // Log received packet information.
+ BweReceiver::ReceivePacket(arrival_time_ms, media_packet);
+}
+
+FeedbackPacket* NadaBweReceiver::GetFeedback(int64_t now_ms) {
+ const int64_t kPacketLossPenaltyMs = 1000; // Referred as d_L.
+
+ if (now_ms - last_feedback_ms_ < 100) {
+ return NULL;
+ }
+
+ float loss_fraction = RecentPacketLossRatio();
+
+ int64_t loss_signal_ms =
+ static_cast<int64_t>(loss_fraction * 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) {
+ derivative = (congestion_signal_ms - last_congestion_signal_ms_) /
+ static_cast<float>(now_ms - last_feedback_ms_);
+ }
+ last_feedback_ms_ = now_ms;
+ last_congestion_signal_ms_ = congestion_signal_ms;
+
+ int64_t corrected_send_time_ms = 0L;
+
+ if (!received_packets_.empty()) {
+ PacketIdentifierNode* latest = *(received_packets_.begin());
+ corrected_send_time_ms =
+ latest->send_time_ms + now_ms - latest->arrival_time_ms;
+ }
+
+ // Sends a tuple containing latest values of <d_hat_n, d_tilde_n, x_n, x'_n,
+ // R_r> and additional information.
+ return new NadaFeedback(flow_id_, now_ms * 1000, exp_smoothed_delay_ms_,
+ est_queuing_delay_signal_ms_, congestion_signal_ms,
+ derivative, RecentKbps(), corrected_send_time_ms);
+}
+
+// If size is even, the median is the average of the two middlemost numbers.
+int64_t NadaBweReceiver::MedianFilter(int64_t* last_delays_ms, int size) {
+ std::vector<int64_t> array_copy(last_delays_ms, last_delays_ms + size);
+ std::nth_element(array_copy.begin(), array_copy.begin() + size / 2,
+ array_copy.end());
+ if (size % 2 == 1) {
+ // Typically, size = 5. For odd size values, right and left are equal.
+ return array_copy.at(size / 2);
+ }
+ int64_t right = array_copy.at(size / 2);
+ std::nth_element(array_copy.begin(), array_copy.begin() + (size - 1) / 2,
+ array_copy.end());
+ int64_t left = array_copy.at((size - 1) / 2);
+ return (left + right + 1) / 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<int64_t>(alpha * 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)
+ : BweSender(kbps), // Referred as "Reference Rate" = R_n.,
+ clock_(clock),
+ observer_(observer),
+ original_operating_mode_(true) {
+}
+
+NadaBweSender::NadaBweSender(BitrateObserver* observer, Clock* clock)
+ : BweSender(kMinBitrateKbps), // Referred as "Reference Rate" = R_n.
+ clock_(clock),
+ observer_(observer),
+ original_operating_mode_(true) {
+}
+
+NadaBweSender::~NadaBweSender() {
+}
+
+int NadaBweSender::GetFeedbackIntervalMs() const {
+ return 100;
+}
+
+void NadaBweSender::GiveFeedback(const FeedbackPacket& feedback) {
+ const NadaFeedback& fb = static_cast<const NadaFeedback&>(feedback);
+
+ // Following parameters might be optimized.
+ const int64_t kQueuingDelayUpperBoundMs = 10;
+ 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_;
+ last_feedback_ms_ = now_ms;
+ // Update delta_0.
+ min_feedback_delay_ms_ =
+ std::min(min_feedback_delay_ms_, static_cast<int64_t>(delta_s));
+
+ // Update RTT_0.
+ 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 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 (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 {
+ // 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() <
+ kMinBitrateKbps / kProportionalityDelayBits &&
+ fb.derivative() < kDerivativeUpperBound &&
+ fb.receiving_rate() > kMinBitrateKbps) {
+ AcceleratedRampUp(fb);
+ } else if (fb.congestion_signal() > kMaxCongestionSignalMs ||
+ fb.exp_smoothed_delay_ms() > kMaxCongestionSignalMs) {
+ AcceleratedRampDown(fb);
+ } else {
+ double bitrate_reference =
+ (2.0 * bitrate_kbps_) / (kMaxBitrateKbps + kMinBitrateKbps);
+ double smoothing_factor = pow(bitrate_reference, 0.75);
+ GradualRateUpdate(fb, delta_s, smoothing_factor);
+ }
+ }
+
+ bitrate_kbps_ = std::min(bitrate_kbps_, kMaxBitrateKbps);
+ bitrate_kbps_ = std::max(bitrate_kbps_, kMinBitrateKbps);
+
+ observer_->OnNetworkChanged(1000 * bitrate_kbps_, 0, rtt_ms);
+}
+
+int64_t NadaBweSender::TimeUntilNextProcess() {
+ return 100;
+}
+
+int NadaBweSender::Process() {
+ return 0;
+}
+
+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<float>(kMaxRampUpQueuingDelayMs) /
+ (min_round_trip_time_ms_ + min_feedback_delay_ms_));
+
+ bitrate_kbps_ = static_cast<int>((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,
+ 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 x_hat = fb.congestion_signal() + kEta * kTauOMs * fb.derivative();
+
+ float kTheta =
+ kPriorityWeight * (kMaxBitrateKbps - kMinBitrateKbps) * kReferenceDelayMs;
+
+ int original_increase =
+ static_cast<int>((kKappa * delta_s *
+ (kTheta - (bitrate_kbps_ - kMinBitrateKbps) * x_hat)) /
+ (kTauOMs * kTauOMs) +
+ 0.5f);
+
+ bitrate_kbps_ = bitrate_kbps_ + smoothing_factor * original_increase;
+}
+
+} // 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
new file mode 100644
index 0000000000..eee90cf463
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ *
+*/
+
+// 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 <list>
+#include <map>
+
+#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 {
+
+class ReceiveStatistics;
+
+namespace testing {
+namespace bwe {
+
+class NadaBweReceiver : public BweReceiver {
+ public:
+ explicit NadaBweReceiver(int flow_id);
+ virtual ~NadaBweReceiver();
+
+ void ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) override;
+ FeedbackPacket* GetFeedback(int64_t now_ms) override;
+
+ static int64_t MedianFilter(int64_t* v, int size);
+ static int64_t ExponentialSmoothingFilter(int64_t new_value,
+ int64_t last_smoothed_value,
+ float alpha);
+
+ static const int64_t kReceivingRateTimeWindowMs;
+
+ private:
+ SimulatedClock clock_;
+ int64_t last_feedback_ms_;
+ rtc::scoped_ptr<ReceiveStatistics> recv_stats_;
+ 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.
+ int64_t last_delays_ms_[5]; // 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);
+ void AcceleratedRampDown(const NadaFeedback& fb);
+ void GradualRateUpdate(const NadaFeedback& fb,
+ 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(); }
+
+ private:
+ Clock* const clock_;
+ BitrateObserver* const observer_;
+ // Used as an upper bound for calling AcceleratedRampDown.
+ const float kMaxCongestionSignalMs = 40.0f + kMinBitrateKbps / 15;
+ // Referred as R_min, default initialization for bitrate R_n.
+ int64_t last_feedback_ms_ = 0;
+ // Referred as delta_0, initialized as an upper bound.
+ int64_t min_feedback_delay_ms_ = 200;
+ // Referred as RTT_0, initialized as an upper bound.
+ int64_t min_round_trip_time_ms_ = 100;
+ bool original_operating_mode_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(NadaBweSender);
+};
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_NADA_H_
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 0000000000..a0f56b73b7
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada_unittest.cc
@@ -0,0 +1,495 @@
+/*
+ * 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 "webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h"
+
+#include <algorithm>
+#include <numeric>
+
+#include "webrtc/base/common.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet.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 FilterTest : public ::testing::Test {
+ public:
+ void MedianFilterConstantArray() {
+ std::fill_n(raw_signal_, kNumElements, kSignalValue);
+ for (int i = 0; i < kNumElements; ++i) {
+ int size = std::min(5, i + 1);
+ median_filtered_[i] =
+ NadaBweReceiver::MedianFilter(&raw_signal_[i + 1 - size], size);
+ }
+ }
+
+ void MedianFilterIntermittentNoise() {
+ const int kValue = 500;
+ const int kNoise = 100;
+
+ for (int i = 0; i < kNumElements; ++i) {
+ raw_signal_[i] = kValue + kNoise * (i % 10 == 9 ? 1 : 0);
+ }
+ for (int i = 0; i < kNumElements; ++i) {
+ int size = std::min(5, i + 1);
+ median_filtered_[i] =
+ NadaBweReceiver::MedianFilter(&raw_signal_[i + 1 - size], size);
+ EXPECT_EQ(median_filtered_[i], kValue);
+ }
+ }
+
+ void ExponentialSmoothingFilter(const int64_t raw_signal_[],
+ int num_elements,
+ int64_t exp_smoothed[]) {
+ exp_smoothed[0] =
+ NadaBweReceiver::ExponentialSmoothingFilter(raw_signal_[0], -1, kAlpha);
+ for (int i = 1; i < num_elements; ++i) {
+ exp_smoothed[i] = NadaBweReceiver::ExponentialSmoothingFilter(
+ 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 kNumElements = 1000;
+ static const int64_t kSignalValue;
+ static const float kAlpha;
+ int64_t raw_signal_[kNumElements];
+ int64_t median_filtered_[kNumElements];
+};
+
+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 {
+ public:
+ NadaReceiverSideTest() : nada_receiver_(kFlowId) {}
+ ~NadaReceiverSideTest() {}
+
+ protected:
+ const int kFlowId = 1; // Arbitrary.
+ 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 = 1;
+ const int64_t kOneWayDelayMs = 50;
+ int original_bitrate = 2 * kMinBitrateKbps;
+ size_t receiving_rate = static_cast<size_t>(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 * kMinBitrateKbps;
+ size_t receiving_rate = static_cast<size_t>(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(), kMinBitrateKbps);
+ 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 * kMinBitrateKbps;
+ size_t receiving_rate = static_cast<size_t>(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;
+
+ size_t receiving_rate = static_cast<size_t>(kMinBitrateKbps);
+ 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 * kMinBitrateKbps);
+ 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(), kMinBitrateKbps);
+
+ nada_sender_.set_original_operating_mode(false);
+ nada_sender_.set_bitrate_kbps(5 * kMinBitrateKbps);
+
+ for (int i = 0; i < 1000; ++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(), kMinBitrateKbps);
+}
+
+// Sending bitrate should increase and reach its Max bound.
+TEST_F(NadaSenderSideTest, VeryHighBandwith) {
+ const int64_t kOneWayDelayMs = 50;
+ const size_t kRecentReceivingRate = static_cast<size_t>(kMaxBitrateKbps);
+ const int64_t kRefSignalMs = 1;
+ 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(), kMaxBitrateKbps);
+
+ nada_sender_.set_original_operating_mode(false);
+ nada_sender_.set_bitrate_kbps(kMinBitrateKbps);
+
+ 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(), kMaxBitrateKbps);
+}
+
+TEST_F(NadaReceiverSideTest, FeedbackInitialCases) {
+ rtc::scoped_ptr<NadaFeedback> nada_feedback(
+ static_cast<NadaFeedback*>(nada_receiver_.GetFeedback(0)));
+ EXPECT_EQ(nada_feedback, nullptr);
+
+ nada_feedback.reset(
+ static_cast<NadaFeedback*>(nada_receiver_.GetFeedback(100)));
+ EXPECT_EQ(nada_feedback->exp_smoothed_delay_ms(), -1);
+ EXPECT_EQ(nada_feedback->est_queuing_delay_signal_ms(), 0L);
+ EXPECT_EQ(nada_feedback->congestion_signal(), 0L);
+ EXPECT_EQ(nada_feedback->derivative(), 0.0f);
+ EXPECT_EQ(nada_feedback->receiving_rate(), 0.0f);
+}
+
+TEST_F(NadaReceiverSideTest, FeedbackEmptyQueues) {
+ const int64_t kTimeGapMs = 50; // Between each packet.
+ const int64_t kOneWayDelayMs = 50;
+
+ // No added latency, delay = kOneWayDelayMs.
+ for (int i = 1; i < 10; ++i) {
+ int64_t send_time_us = i * kTimeGapMs * 1000;
+ int64_t arrival_time_ms = send_time_us / 1000 + kOneWayDelayMs;
+ uint16_t sequence_number = static_cast<uint16_t>(i);
+ // Payload sizes are not important here.
+ const MediaPacket media_packet(kFlowId, send_time_us, 0, sequence_number);
+ nada_receiver_.ReceivePacket(arrival_time_ms, media_packet);
+ }
+
+ // Baseline delay will be equal kOneWayDelayMs.
+ rtc::scoped_ptr<NadaFeedback> nada_feedback(
+ static_cast<NadaFeedback*>(nada_receiver_.GetFeedback(500)));
+ EXPECT_EQ(nada_feedback->exp_smoothed_delay_ms(), 0L);
+ EXPECT_EQ(nada_feedback->est_queuing_delay_signal_ms(), 0L);
+ EXPECT_EQ(nada_feedback->congestion_signal(), 0L);
+ EXPECT_EQ(nada_feedback->derivative(), 0.0f);
+}
+
+TEST_F(NadaReceiverSideTest, FeedbackIncreasingDelay) {
+ // Since packets are 100ms apart, each one corresponds to a feedback.
+ const int64_t kTimeGapMs = 100; // Between each packet.
+
+ // Raw delays are = [10 20 30 40 50 60 70 80] ms.
+ // Baseline delay will be 50 ms.
+ // Delay signals should be: [0 10 20 30 40 50 60 70] ms.
+ const int64_t kMedianFilteredDelaysMs[] = {0, 5, 10, 15, 20, 30, 40, 50};
+ const int kNumPackets = ARRAY_SIZE(kMedianFilteredDelaysMs);
+ const float kAlpha = 0.1f; // Used for exponential smoothing.
+
+ int64_t exp_smoothed_delays_ms[kNumPackets];
+ exp_smoothed_delays_ms[0] = kMedianFilteredDelaysMs[0];
+
+ for (int i = 1; i < kNumPackets; ++i) {
+ exp_smoothed_delays_ms[i] = static_cast<int64_t>(
+ kAlpha * kMedianFilteredDelaysMs[i] +
+ (1.0f - kAlpha) * exp_smoothed_delays_ms[i - 1] + 0.5f);
+ }
+
+ for (int i = 0; i < kNumPackets; ++i) {
+ int64_t send_time_us = (i + 1) * kTimeGapMs * 1000;
+ int64_t arrival_time_ms = send_time_us / 1000 + 10 * (i + 1);
+ uint16_t sequence_number = static_cast<uint16_t>(i + 1);
+ // Payload sizes are not important here.
+ const MediaPacket media_packet(kFlowId, send_time_us, 0, sequence_number);
+ nada_receiver_.ReceivePacket(arrival_time_ms, media_packet);
+
+ rtc::scoped_ptr<NadaFeedback> nada_feedback(static_cast<NadaFeedback*>(
+ nada_receiver_.GetFeedback(arrival_time_ms)));
+ EXPECT_EQ(nada_feedback->exp_smoothed_delay_ms(),
+ exp_smoothed_delays_ms[i]);
+ // Since delay signals are lower than 50ms, they will not be non-linearly
+ // warped.
+ EXPECT_EQ(nada_feedback->est_queuing_delay_signal_ms(),
+ exp_smoothed_delays_ms[i]);
+ // Zero loss, congestion signal = queuing_delay
+ EXPECT_EQ(nada_feedback->congestion_signal(), exp_smoothed_delays_ms[i]);
+ if (i == 0) {
+ EXPECT_NEAR(nada_feedback->derivative(),
+ static_cast<float>(exp_smoothed_delays_ms[i]) / kTimeGapMs,
+ 0.005f);
+ } else {
+ EXPECT_NEAR(nada_feedback->derivative(),
+ static_cast<float>(exp_smoothed_delays_ms[i] -
+ exp_smoothed_delays_ms[i - 1]) /
+ kTimeGapMs,
+ 0.005f);
+ }
+ }
+}
+
+int64_t Warp(int64_t input) {
+ const int64_t kMinThreshold = 50; // Referred as d_th.
+ const int64_t kMaxThreshold = 400; // Referred as d_max.
+ if (input < kMinThreshold) {
+ return input;
+ } else if (input < kMaxThreshold) {
+ return static_cast<int64_t>(
+ pow((static_cast<double>(kMaxThreshold - input)) /
+ (kMaxThreshold - kMinThreshold),
+ 4.0) *
+ kMinThreshold);
+ } else {
+ return 0L;
+ }
+}
+
+TEST_F(NadaReceiverSideTest, FeedbackWarpedDelay) {
+ // Since packets are 100ms apart, each one corresponds to a feedback.
+ const int64_t kTimeGapMs = 100; // Between each packet.
+
+ // Raw delays are = [50 250 450 650 850 1050 1250 1450] ms.
+ // Baseline delay will be 50 ms.
+ // Delay signals should be: [0 200 400 600 800 1000 1200 1400] ms.
+ const int64_t kMedianFilteredDelaysMs[] = {
+ 0, 100, 200, 300, 400, 600, 800, 1000};
+ const int kNumPackets = ARRAY_SIZE(kMedianFilteredDelaysMs);
+ const float kAlpha = 0.1f; // Used for exponential smoothing.
+
+ int64_t exp_smoothed_delays_ms[kNumPackets];
+ exp_smoothed_delays_ms[0] = kMedianFilteredDelaysMs[0];
+
+ for (int i = 1; i < kNumPackets; ++i) {
+ exp_smoothed_delays_ms[i] = static_cast<int64_t>(
+ kAlpha * kMedianFilteredDelaysMs[i] +
+ (1.0f - kAlpha) * exp_smoothed_delays_ms[i - 1] + 0.5f);
+ }
+
+ for (int i = 0; i < kNumPackets; ++i) {
+ int64_t send_time_us = (i + 1) * kTimeGapMs * 1000;
+ int64_t arrival_time_ms = send_time_us / 1000 + 50 + 200 * i;
+ uint16_t sequence_number = static_cast<uint16_t>(i + 1);
+ // Payload sizes are not important here.
+ const MediaPacket media_packet(kFlowId, send_time_us, 0, sequence_number);
+ nada_receiver_.ReceivePacket(arrival_time_ms, media_packet);
+
+ rtc::scoped_ptr<NadaFeedback> nada_feedback(static_cast<NadaFeedback*>(
+ nada_receiver_.GetFeedback(arrival_time_ms)));
+ EXPECT_EQ(nada_feedback->exp_smoothed_delay_ms(),
+ exp_smoothed_delays_ms[i]);
+ // Delays can be non-linearly warped.
+ EXPECT_EQ(nada_feedback->est_queuing_delay_signal_ms(),
+ Warp(exp_smoothed_delays_ms[i]));
+ // Zero loss, congestion signal = queuing_delay
+ EXPECT_EQ(nada_feedback->congestion_signal(),
+ Warp(exp_smoothed_delays_ms[i]));
+ }
+}
+
+TEST_F(FilterTest, MedianConstantArray) {
+ MedianFilterConstantArray();
+ for (int i = 0; i < kNumElements; ++i) {
+ EXPECT_EQ(median_filtered_[i], raw_signal_[i]);
+ }
+}
+
+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<int64_t>(exp_smoothed[i - 1] * (1.0f - kAlpha) + 0.5f));
+ }
+}
+
+} // 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
new file mode 100644
index 0000000000..b18b9f06b9
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.cc
@@ -0,0 +1,164 @@
+/*
+ * 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 <algorithm>
+
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/common.h"
+#include "webrtc/modules/bitrate_controller/include/bitrate_controller.h"
+#include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "webrtc/modules/rtp_rtcp/interface/receive_statistics.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+RembBweSender::RembBweSender(int kbps, BitrateObserver* observer, Clock* clock)
+ : bitrate_controller_(
+ BitrateController::CreateBitrateController(clock, observer)),
+ feedback_observer_(bitrate_controller_->CreateRtcpBandwidthObserver()),
+ clock_(clock) {
+ assert(kbps >= kMinBitrateKbps);
+ assert(kbps <= kMaxBitrateKbps);
+ bitrate_controller_->SetStartBitrate(1000 * kbps);
+ bitrate_controller_->SetMinMaxBitrate(1000 * kMinBitrateKbps,
+ 1000 * kMaxBitrateKbps);
+}
+
+RembBweSender::~RembBweSender() {
+}
+
+void RembBweSender::GiveFeedback(const FeedbackPacket& feedback) {
+ const RembFeedback& remb_feedback =
+ static_cast<const RembFeedback&>(feedback);
+ feedback_observer_->OnReceivedEstimatedBitrate(remb_feedback.estimated_bps());
+ ReportBlockList report_blocks;
+ report_blocks.push_back(remb_feedback.report_block());
+ feedback_observer_->OnReceivedRtcpReceiverReport(
+ report_blocks, 0, clock_->TimeInMilliseconds());
+ bitrate_controller_->Process();
+}
+
+int64_t RembBweSender::TimeUntilNextProcess() {
+ return bitrate_controller_->TimeUntilNextProcess();
+}
+
+int RembBweSender::Process() {
+ return bitrate_controller_->Process();
+}
+
+int RembBweSender::GetFeedbackIntervalMs() const {
+ return 100;
+}
+
+RembReceiver::RembReceiver(int flow_id, bool plot)
+ : BweReceiver(flow_id),
+ estimate_log_prefix_(),
+ plot_estimate_(plot),
+ clock_(0),
+ recv_stats_(ReceiveStatistics::Create(&clock_)),
+ latest_estimate_bps_(-1),
+ last_feedback_ms_(-1),
+ estimator_(new RemoteBitrateEstimatorAbsSendTime(this, &clock_)) {
+ std::stringstream ss;
+ ss << "Estimate_" << flow_id_ << "#1";
+ estimate_log_prefix_ = ss.str();
+ // Default RTT in RemoteRateControl is 200 ms ; 50 ms is more realistic.
+ estimator_->OnRttUpdate(50, 50);
+ estimator_->SetMinBitrate(kRemoteBitrateEstimatorMinBitrateBps);
+}
+
+RembReceiver::~RembReceiver() {
+}
+
+void RembReceiver::ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) {
+ recv_stats_->IncomingPacket(media_packet.header(),
+ media_packet.payload_size(), false);
+
+ latest_estimate_bps_ = -1;
+
+ int64_t step_ms = std::max<int64_t>(estimator_->TimeUntilNextProcess(), 0);
+ while ((clock_.TimeInMilliseconds() + step_ms) < arrival_time_ms) {
+ clock_.AdvanceTimeMilliseconds(step_ms);
+ estimator_->Process();
+ step_ms = std::max<int64_t>(estimator_->TimeUntilNextProcess(), 0);
+ }
+ estimator_->IncomingPacket(arrival_time_ms, media_packet.payload_size(),
+ media_packet.header(), true);
+ clock_.AdvanceTimeMilliseconds(arrival_time_ms - clock_.TimeInMilliseconds());
+ ASSERT_TRUE(arrival_time_ms == clock_.TimeInMilliseconds());
+
+ // Log received packet information.
+ BweReceiver::ReceivePacket(arrival_time_ms, media_packet);
+}
+
+FeedbackPacket* RembReceiver::GetFeedback(int64_t now_ms) {
+ BWE_TEST_LOGGING_CONTEXT("Remb");
+ uint32_t estimated_bps = 0;
+ RembFeedback* feedback = NULL;
+ if (LatestEstimate(&estimated_bps)) {
+ StatisticianMap statisticians = recv_stats_->GetActiveStatisticians();
+ RTCPReportBlock report_block;
+ if (!statisticians.empty()) {
+ report_block = BuildReportBlock(statisticians.begin()->second);
+ }
+
+ 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<double>(estimated_bps) / 1000.0;
+ RTC_UNUSED(estimated_kbps);
+ if (plot_estimate_) {
+ BWE_TEST_LOGGING_PLOT(0, estimate_log_prefix_,
+ clock_.TimeInMilliseconds(), estimated_kbps);
+ }
+ }
+ return feedback;
+}
+
+void RembReceiver::OnReceiveBitrateChanged(
+ const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) {
+}
+
+RTCPReportBlock RembReceiver::BuildReportBlock(
+ StreamStatistician* statistician) {
+ RTCPReportBlock report_block;
+ RtcpStatistics stats;
+ if (!statistician->GetStatistics(&stats, true))
+ return report_block;
+ report_block.fractionLost = stats.fraction_lost;
+ report_block.cumulativeLost = stats.cumulative_lost;
+ report_block.extendedHighSeqNum = stats.extended_max_sequence_number;
+ report_block.jitter = stats.jitter;
+ return report_block;
+}
+
+bool RembReceiver::LatestEstimate(uint32_t* estimate_bps) {
+ if (latest_estimate_bps_ < 0) {
+ std::vector<unsigned int> ssrcs;
+ unsigned int bps = 0;
+ if (!estimator_->LatestEstimate(&ssrcs, &bps)) {
+ return false;
+ }
+ latest_estimate_bps_ = bps;
+ }
+ *estimate_bps = latest_estimate_bps_;
+ return true;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h b/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h
new file mode 100644
index 0000000000..7dfd7a8459
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_REMB_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_REMB_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+
+namespace webrtc {
+
+class BitrateObserver;
+class BitrateController;
+class ReceiveStatistics;
+class StreamStatistician;
+
+namespace testing {
+namespace bwe {
+
+class RembBweSender : public BweSender {
+ public:
+ RembBweSender(int kbps, BitrateObserver* observer, Clock* clock);
+ virtual ~RembBweSender();
+
+ int GetFeedbackIntervalMs() const override;
+ void GiveFeedback(const FeedbackPacket& feedback) override;
+ void OnPacketsSent(const Packets& packets) override {}
+ int64_t TimeUntilNextProcess() override;
+ int Process() override;
+
+ protected:
+ rtc::scoped_ptr<BitrateController> bitrate_controller_;
+ rtc::scoped_ptr<RtcpBandwidthObserver> feedback_observer_;
+
+ private:
+ Clock* clock_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RembBweSender);
+};
+
+class RembReceiver : public BweReceiver, public RemoteBitrateObserver {
+ public:
+ static const uint32_t kRemoteBitrateEstimatorMinBitrateBps = 30000;
+
+ RembReceiver(int flow_id, bool plot);
+ virtual ~RembReceiver();
+
+ void ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) override;
+ FeedbackPacket* GetFeedback(int64_t now_ms) override;
+ // Implements RemoteBitrateObserver.
+ void OnReceiveBitrateChanged(const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) override;
+
+ private:
+ static RTCPReportBlock BuildReportBlock(StreamStatistician* statistician);
+ bool LatestEstimate(uint32_t* estimate_bps);
+
+ std::string estimate_log_prefix_;
+ bool plot_estimate_;
+ SimulatedClock clock_;
+ rtc::scoped_ptr<ReceiveStatistics> recv_stats_;
+ int64_t latest_estimate_bps_;
+ int64_t last_feedback_ms_;
+ rtc::scoped_ptr<RemoteBitrateEstimator> estimator_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RembReceiver);
+};
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_REMB_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.cc
new file mode 100644
index 0000000000..8a7352874b
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.cc
@@ -0,0 +1,151 @@
+/*
+ * 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 "webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.h"
+
+#include "webrtc/base/logging.h"
+#include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+const int kFeedbackIntervalMs = 50;
+
+FullBweSender::FullBweSender(int kbps, BitrateObserver* observer, Clock* clock)
+ : bitrate_controller_(
+ BitrateController::CreateBitrateController(clock, observer)),
+ rbe_(new RemoteBitrateEstimatorAbsSendTime(this, clock)),
+ feedback_observer_(bitrate_controller_->CreateRtcpBandwidthObserver()),
+ clock_(clock),
+ send_time_history_(clock_, 10000),
+ has_received_ack_(false),
+ last_acked_seq_num_(0) {
+ assert(kbps >= kMinBitrateKbps);
+ assert(kbps <= kMaxBitrateKbps);
+ bitrate_controller_->SetStartBitrate(1000 * kbps);
+ bitrate_controller_->SetMinMaxBitrate(1000 * kMinBitrateKbps,
+ 1000 * kMaxBitrateKbps);
+ rbe_->SetMinBitrate(1000 * kMinBitrateKbps);
+}
+
+FullBweSender::~FullBweSender() {
+}
+
+int FullBweSender::GetFeedbackIntervalMs() const {
+ return kFeedbackIntervalMs;
+}
+
+void FullBweSender::GiveFeedback(const FeedbackPacket& feedback) {
+ const SendSideBweFeedback& fb =
+ static_cast<const SendSideBweFeedback&>(feedback);
+ if (fb.packet_feedback_vector().empty())
+ return;
+ std::vector<PacketInfo> packet_feedback_vector(fb.packet_feedback_vector());
+ for (PacketInfo& packet_info : packet_feedback_vector) {
+ if (!send_time_history_.GetInfo(&packet_info, true)) {
+ LOG(LS_WARNING) << "Ack arrived too late.";
+ }
+ }
+
+ int64_t rtt_ms =
+ clock_->TimeInMilliseconds() - feedback.latest_send_time_ms();
+ rbe_->OnRttUpdate(rtt_ms, rtt_ms);
+ BWE_TEST_LOGGING_PLOT(1, "RTT", clock_->TimeInMilliseconds(), rtt_ms);
+
+ rbe_->IncomingPacketFeedbackVector(packet_feedback_vector);
+ if (has_received_ack_) {
+ int expected_packets = fb.packet_feedback_vector().back().sequence_number -
+ last_acked_seq_num_;
+ // Assuming no reordering for now.
+ if (expected_packets > 0) {
+ int lost_packets = expected_packets -
+ static_cast<int>(fb.packet_feedback_vector().size());
+ report_block_.fractionLost = (lost_packets << 8) / expected_packets;
+ report_block_.cumulativeLost += lost_packets;
+ report_block_.extendedHighSeqNum =
+ packet_feedback_vector.back().sequence_number;
+ ReportBlockList report_blocks;
+ report_blocks.push_back(report_block_);
+ feedback_observer_->OnReceivedRtcpReceiverReport(
+ report_blocks, rtt_ms, clock_->TimeInMilliseconds());
+ }
+ bitrate_controller_->Process();
+
+ last_acked_seq_num_ = LatestSequenceNumber(
+ packet_feedback_vector.back().sequence_number, last_acked_seq_num_);
+ } else {
+ last_acked_seq_num_ = packet_feedback_vector.back().sequence_number;
+ has_received_ack_ = true;
+ }
+}
+
+void FullBweSender::OnPacketsSent(const Packets& packets) {
+ for (Packet* packet : packets) {
+ if (packet->GetPacketType() == Packet::kMedia) {
+ MediaPacket* media_packet = static_cast<MediaPacket*>(packet);
+ send_time_history_.AddAndRemoveOld(media_packet->header().sequenceNumber,
+ media_packet->payload_size(),
+ packet->paced());
+ send_time_history_.OnSentPacket(media_packet->header().sequenceNumber,
+ media_packet->sender_timestamp_ms());
+ }
+ }
+}
+
+void FullBweSender::OnReceiveBitrateChanged(
+ const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) {
+ feedback_observer_->OnReceivedEstimatedBitrate(bitrate);
+}
+
+int64_t FullBweSender::TimeUntilNextProcess() {
+ return bitrate_controller_->TimeUntilNextProcess();
+}
+
+int FullBweSender::Process() {
+ rbe_->Process();
+ return bitrate_controller_->Process();
+}
+
+SendSideBweReceiver::SendSideBweReceiver(int flow_id)
+ : BweReceiver(flow_id), last_feedback_ms_(0) {
+}
+
+SendSideBweReceiver::~SendSideBweReceiver() {
+}
+
+void SendSideBweReceiver::ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) {
+ packet_feedback_vector_.push_back(PacketInfo(
+ -1, arrival_time_ms, media_packet.sender_timestamp_ms(),
+ media_packet.header().sequenceNumber, media_packet.payload_size(), true));
+
+ // Log received packet information.
+ BweReceiver::ReceivePacket(arrival_time_ms, media_packet);
+}
+
+FeedbackPacket* SendSideBweReceiver::GetFeedback(int64_t now_ms) {
+ if (now_ms - last_feedback_ms_ < kFeedbackIntervalMs)
+ return NULL;
+ last_feedback_ms_ = now_ms;
+ int64_t corrected_send_time_ms =
+ packet_feedback_vector_.back().send_time_ms + now_ms -
+ packet_feedback_vector_.back().arrival_time_ms;
+ FeedbackPacket* fb = new SendSideBweFeedback(
+ flow_id_, now_ms * 1000, corrected_send_time_ms, packet_feedback_vector_);
+ packet_feedback_vector_.clear();
+ return fb;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.h b/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.h
new file mode 100644
index 0000000000..ab9abc5cbc
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_SEND_SIDE_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_SEND_SIDE_H_
+
+#include <vector>
+
+#include "webrtc/modules/remote_bitrate_estimator/include/send_time_history.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class FullBweSender : public BweSender, public RemoteBitrateObserver {
+ public:
+ FullBweSender(int kbps, BitrateObserver* observer, Clock* clock);
+ virtual ~FullBweSender();
+
+ int GetFeedbackIntervalMs() const override;
+ void GiveFeedback(const FeedbackPacket& feedback) override;
+ void OnPacketsSent(const Packets& packets) override;
+ void OnReceiveBitrateChanged(const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) override;
+ int64_t TimeUntilNextProcess() override;
+ int Process() override;
+
+ protected:
+ rtc::scoped_ptr<BitrateController> bitrate_controller_;
+ rtc::scoped_ptr<RemoteBitrateEstimator> rbe_;
+ rtc::scoped_ptr<RtcpBandwidthObserver> feedback_observer_;
+
+ private:
+ Clock* const clock_;
+ RTCPReportBlock report_block_;
+ SendTimeHistory send_time_history_;
+ bool has_received_ack_;
+ uint16_t last_acked_seq_num_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(FullBweSender);
+};
+
+class SendSideBweReceiver : public BweReceiver {
+ public:
+ explicit SendSideBweReceiver(int flow_id);
+ virtual ~SendSideBweReceiver();
+
+ void ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) override;
+ FeedbackPacket* GetFeedback(int64_t now_ms) override;
+
+ private:
+ int64_t last_feedback_ms_;
+ std::vector<PacketInfo> packet_feedback_vector_;
+};
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_SEND_SIDE_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.cc
new file mode 100644
index 0000000000..a02abc6ab8
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.cc
@@ -0,0 +1,54 @@
+/*
+ * 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 <algorithm>
+
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/common.h"
+#include "webrtc/modules/bitrate_controller/include/bitrate_controller.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "webrtc/modules/rtp_rtcp/interface/receive_statistics.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+TcpBweReceiver::TcpBweReceiver(int flow_id)
+ : BweReceiver(flow_id),
+ last_feedback_ms_(0),
+ latest_owd_ms_(0) {
+}
+
+TcpBweReceiver::~TcpBweReceiver() {
+}
+
+void TcpBweReceiver::ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) {
+ latest_owd_ms_ = arrival_time_ms - media_packet.sender_timestamp_ms() / 1000;
+ acks_.push_back(media_packet.header().sequenceNumber);
+
+ // Log received packet information.
+ BweReceiver::ReceivePacket(arrival_time_ms, media_packet);
+}
+
+FeedbackPacket* TcpBweReceiver::GetFeedback(int64_t now_ms) {
+ int64_t corrected_send_time_ms = now_ms - latest_owd_ms_;
+ FeedbackPacket* fb =
+ new TcpFeedback(flow_id_, now_ms * 1000, corrected_send_time_ms, acks_);
+ last_feedback_ms_ = now_ms;
+ acks_.clear();
+ return fb;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.h b/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.h
new file mode 100644
index 0000000000..b33c93eef7
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_TCP_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_TCP_H_
+
+#include <vector>
+
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+class TcpBweReceiver : public BweReceiver {
+ public:
+ explicit TcpBweReceiver(int flow_id);
+ virtual ~TcpBweReceiver();
+
+ void ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) override;
+ FeedbackPacket* GetFeedback(int64_t now_ms) override;
+
+ private:
+ int64_t last_feedback_ms_;
+ int64_t latest_owd_ms_;
+ std::vector<uint16_t> acks_;
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_TCP_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.cc b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.cc
new file mode 100644
index 0000000000..6202b4a6a3
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.cc
@@ -0,0 +1,445 @@
+/*
+ * 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 "webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h"
+
+#include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h"
+
+#include <algorithm>
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+namespace {
+// Holder mean, Manhattan distance for p=1, EuclidianNorm/sqrt(n) for p=2.
+template <typename T>
+double NormLp(T sum, size_t size, double p) {
+ return pow(sum / size, 1.0 / p);
+}
+}
+
+const double kP = 1.0; // Used for Norm Lp.
+
+LinkShare::LinkShare(ChokeFilter* choke_filter)
+ : choke_filter_(choke_filter), running_flows_(choke_filter->flow_ids()) {
+}
+
+void LinkShare::PauseFlow(int flow_id) {
+ running_flows_.erase(flow_id);
+}
+
+void LinkShare::ResumeFlow(int flow_id) {
+ running_flows_.insert(flow_id);
+}
+
+uint32_t LinkShare::TotalAvailableKbps() {
+ return choke_filter_->capacity_kbps();
+}
+
+uint32_t LinkShare::AvailablePerFlowKbps(int flow_id) {
+ uint32_t available_capacity_per_flow_kbps = 0;
+ if (running_flows_.find(flow_id) != running_flows_.end()) {
+ available_capacity_per_flow_kbps =
+ TotalAvailableKbps() / static_cast<uint32_t>(running_flows_.size());
+ }
+ return available_capacity_per_flow_kbps;
+}
+
+MetricRecorder::MetricRecorder(const std::string algorithm_name,
+ int flow_id,
+ PacketSender* packet_sender,
+ LinkShare* link_share)
+ : algorithm_name_(algorithm_name),
+ flow_id_(flow_id),
+ link_share_(link_share),
+ now_ms_(0),
+ sum_delays_ms_(0),
+ delay_histogram_ms_(),
+ sum_delays_square_ms2_(0),
+ sum_throughput_bytes_(0),
+ last_unweighted_estimate_error_(0),
+ optimal_throughput_bits_(0),
+ last_available_bitrate_per_flow_kbps_(0),
+ start_computing_metrics_ms_(0),
+ started_computing_metrics_(false),
+ num_packets_received_(0) {
+ std::fill_n(sum_lp_weighted_estimate_error_, 2, 0);
+ if (packet_sender != nullptr)
+ packet_sender->set_metric_recorder(this);
+}
+
+void MetricRecorder::SetPlotInformation(
+ const std::vector<std::string>& prefixes,
+ bool plot_delay,
+ bool plot_loss) {
+ assert(prefixes.size() == kNumMetrics);
+ for (size_t i = 0; i < kNumMetrics; ++i) {
+ plot_information_[i].prefix = prefixes[i];
+ }
+ plot_information_[kThroughput].plot_interval_ms = 100;
+ plot_information_[kSendingEstimate].plot_interval_ms = 100;
+ plot_information_[kDelay].plot_interval_ms = 100;
+ plot_information_[kLoss].plot_interval_ms = 500;
+ plot_information_[kObjective].plot_interval_ms = 1000;
+ plot_information_[kTotalAvailable].plot_interval_ms = 1000;
+ plot_information_[kAvailablePerFlow].plot_interval_ms = 1000;
+
+ for (int i = kThroughput; i < kNumMetrics; ++i) {
+ plot_information_[i].last_plot_ms = 0;
+ switch (i) {
+ case kSendingEstimate:
+ case kObjective:
+ case kAvailablePerFlow:
+ plot_information_[i].plot = false;
+ break;
+ case kLoss:
+ plot_information_[i].plot = plot_loss;
+ break;
+ case kDelay:
+ plot_information_[i].plot = plot_delay;
+ break;
+ default:
+ plot_information_[i].plot = true;
+ }
+ }
+}
+
+void MetricRecorder::PlotAllDynamics() {
+ for (int i = kThroughput; i < kNumMetrics; ++i) {
+ if (plot_information_[i].plot &&
+ now_ms_ - plot_information_[i].last_plot_ms >=
+ plot_information_[i].plot_interval_ms) {
+ PlotDynamics(i);
+ }
+ }
+}
+
+void MetricRecorder::PlotDynamics(int metric) {
+ if (metric == kTotalAvailable) {
+ BWE_TEST_LOGGING_PLOT_WITH_NAME(
+ 0, plot_information_[kTotalAvailable].prefix, now_ms_,
+ GetTotalAvailableKbps(), "Available");
+ } else if (metric == kAvailablePerFlow) {
+ BWE_TEST_LOGGING_PLOT_WITH_NAME(
+ 0, plot_information_[kAvailablePerFlow].prefix, now_ms_,
+ GetAvailablePerFlowKbps(), "Available_per_flow");
+ } else {
+ PlotLine(metric, plot_information_[metric].prefix,
+ plot_information_[metric].time_ms,
+ plot_information_[metric].value);
+ }
+ plot_information_[metric].last_plot_ms = now_ms_;
+}
+
+template <typename T>
+void MetricRecorder::PlotLine(int windows_id,
+ const std::string& prefix,
+ int64_t time_ms,
+ T y) {
+ BWE_TEST_LOGGING_PLOT_WITH_NAME(windows_id, prefix, time_ms,
+ static_cast<double>(y), algorithm_name_);
+}
+
+void MetricRecorder::UpdateTimeMs(int64_t time_ms) {
+ now_ms_ = std::max(now_ms_, time_ms);
+}
+
+void MetricRecorder::UpdateThroughput(int64_t bitrate_kbps,
+ size_t payload_size) {
+ // Total throughput should be computed before updating the time.
+ PushThroughputBytes(payload_size, now_ms_);
+ plot_information_[kThroughput].Update(now_ms_, bitrate_kbps);
+}
+
+void MetricRecorder::UpdateSendingEstimateKbps(int64_t bitrate_kbps) {
+ plot_information_[kSendingEstimate].Update(now_ms_, bitrate_kbps);
+}
+
+void MetricRecorder::UpdateDelayMs(int64_t delay_ms) {
+ PushDelayMs(delay_ms, now_ms_);
+ plot_information_[kDelay].Update(now_ms_, delay_ms);
+}
+
+void MetricRecorder::UpdateLoss(float loss_ratio) {
+ plot_information_[kLoss].Update(now_ms_, loss_ratio);
+}
+
+void MetricRecorder::UpdateObjective() {
+ plot_information_[kObjective].Update(now_ms_, ObjectiveFunction());
+}
+
+uint32_t MetricRecorder::GetTotalAvailableKbps() {
+ if (link_share_ == nullptr)
+ return 0;
+ return link_share_->TotalAvailableKbps();
+}
+
+uint32_t MetricRecorder::GetAvailablePerFlowKbps() {
+ if (link_share_ == nullptr)
+ return 0;
+ return link_share_->AvailablePerFlowKbps(flow_id_);
+}
+
+uint32_t MetricRecorder::GetSendingEstimateKbps() {
+ return static_cast<uint32_t>(plot_information_[kSendingEstimate].value);
+}
+
+void MetricRecorder::PushDelayMs(int64_t delay_ms, int64_t arrival_time_ms) {
+ if (ShouldRecord(arrival_time_ms)) {
+ sum_delays_ms_ += delay_ms;
+ sum_delays_square_ms2_ += delay_ms * delay_ms;
+ if (delay_histogram_ms_.find(delay_ms) == delay_histogram_ms_.end()) {
+ delay_histogram_ms_[delay_ms] = 0;
+ }
+ ++delay_histogram_ms_[delay_ms];
+ }
+}
+
+void MetricRecorder::UpdateEstimateError(int64_t new_value) {
+ int64_t lp_value = pow(static_cast<double>(std::abs(new_value)), kP);
+ if (new_value < 0) {
+ sum_lp_weighted_estimate_error_[0] += lp_value;
+ } else {
+ sum_lp_weighted_estimate_error_[1] += lp_value;
+ }
+}
+
+void MetricRecorder::PushThroughputBytes(size_t payload_size,
+ int64_t arrival_time_ms) {
+ if (ShouldRecord(arrival_time_ms)) {
+ ++num_packets_received_;
+ sum_throughput_bytes_ += payload_size;
+
+ int64_t current_available_per_flow_kbps =
+ static_cast<int64_t>(GetAvailablePerFlowKbps());
+
+ int64_t current_bitrate_diff_kbps =
+ static_cast<int64_t>(GetSendingEstimateKbps()) -
+ current_available_per_flow_kbps;
+
+ int64_t weighted_estimate_error =
+ (((current_bitrate_diff_kbps + last_unweighted_estimate_error_) *
+ (arrival_time_ms - plot_information_[kThroughput].time_ms)) /
+ 2);
+
+ UpdateEstimateError(weighted_estimate_error);
+
+ optimal_throughput_bits_ +=
+ ((current_available_per_flow_kbps +
+ last_available_bitrate_per_flow_kbps_) *
+ (arrival_time_ms - plot_information_[kThroughput].time_ms)) /
+ 2;
+
+ last_available_bitrate_per_flow_kbps_ = current_available_per_flow_kbps;
+ }
+}
+
+bool MetricRecorder::ShouldRecord(int64_t arrival_time_ms) {
+ if (arrival_time_ms >= start_computing_metrics_ms_) {
+ if (!started_computing_metrics_) {
+ start_computing_metrics_ms_ = arrival_time_ms;
+ now_ms_ = arrival_time_ms;
+ started_computing_metrics_ = true;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void MetricRecorder::PlotThroughputHistogram(
+ const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ int64_t extra_offset_ms,
+ const std::string optimum_id) const {
+ double optimal_bitrate_per_flow_kbps = static_cast<double>(
+ optimal_throughput_bits_ / RunDurationMs(extra_offset_ms));
+
+ double neg_error = Renormalize(
+ NormLp(sum_lp_weighted_estimate_error_[0], num_packets_received_, kP));
+ double pos_error = Renormalize(
+ NormLp(sum_lp_weighted_estimate_error_[1], num_packets_received_, kP));
+
+ double average_bitrate_kbps = AverageBitrateKbps(extra_offset_ms);
+
+ // Prevent the error to be too close to zero (plotting issue).
+ double extra_error = average_bitrate_kbps / 500;
+
+ std::string optimum_title =
+ optimum_id.empty() ? "optimal_bitrate" : "optimal_bitrates#" + optimum_id;
+
+ BWE_TEST_LOGGING_LABEL(4, title, "average_bitrate_(kbps)", num_flows);
+ BWE_TEST_LOGGING_LIMITERRORBAR(
+ 4, bwe_name, average_bitrate_kbps,
+ average_bitrate_kbps - neg_error - extra_error,
+ average_bitrate_kbps + pos_error + extra_error, "estimate_error",
+ optimal_bitrate_per_flow_kbps, optimum_title, flow_id_);
+
+ BWE_TEST_LOGGING_LOG1("RESULTS >>> " + bwe_name + " Channel utilization : ",
+ "%lf %%",
+ 100.0 * static_cast<double>(average_bitrate_kbps) /
+ optimal_bitrate_per_flow_kbps);
+
+ RTC_UNUSED(pos_error);
+ RTC_UNUSED(neg_error);
+ RTC_UNUSED(extra_error);
+ RTC_UNUSED(optimal_bitrate_per_flow_kbps);
+}
+
+void MetricRecorder::PlotThroughputHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ int64_t extra_offset_ms) const {
+ PlotThroughputHistogram(title, bwe_name, num_flows, extra_offset_ms, "");
+}
+
+void MetricRecorder::PlotDelayHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ int64_t one_way_path_delay_ms) const {
+ double average_delay_ms =
+ static_cast<double>(sum_delays_ms_) / num_packets_received_;
+
+ // Prevent the error to be too close to zero (plotting issue).
+ double extra_error = average_delay_ms / 500;
+ double tenth_sigma_ms = DelayStdDev() / 10.0 + extra_error;
+ int64_t percentile_5_ms = NthDelayPercentile(5);
+ int64_t percentile_95_ms = NthDelayPercentile(95);
+
+ BWE_TEST_LOGGING_LABEL(5, title, "average_delay_(ms)", num_flows)
+ BWE_TEST_LOGGING_ERRORBAR(5, bwe_name, average_delay_ms, percentile_5_ms,
+ percentile_95_ms, "5th and 95th percentiles",
+ flow_id_);
+
+ // Log added latency, disregard baseline path delay.
+ BWE_TEST_LOGGING_LOG1("RESULTS >>> " + bwe_name + " Delay average : ",
+ "%lf ms", average_delay_ms - one_way_path_delay_ms);
+ BWE_TEST_LOGGING_LOG1("RESULTS >>> " + bwe_name + " Delay 5th percentile : ",
+ "%ld ms", percentile_5_ms - one_way_path_delay_ms);
+ BWE_TEST_LOGGING_LOG1("RESULTS >>> " + bwe_name + " Delay 95th percentile : ",
+ "%ld ms", percentile_95_ms - one_way_path_delay_ms);
+
+ RTC_UNUSED(tenth_sigma_ms);
+ RTC_UNUSED(percentile_5_ms);
+ RTC_UNUSED(percentile_95_ms);
+}
+
+void MetricRecorder::PlotLossHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ float global_loss_ratio) const {
+ BWE_TEST_LOGGING_LABEL(6, title, "packet_loss_ratio_(%)", num_flows)
+ BWE_TEST_LOGGING_BAR(6, bwe_name, 100.0f * global_loss_ratio, flow_id_);
+
+ BWE_TEST_LOGGING_LOG1("RESULTS >>> " + bwe_name + " Loss Ratio : ", "%f %%",
+ 100.0f * global_loss_ratio);
+}
+
+void MetricRecorder::PlotObjectiveHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows) const {
+ BWE_TEST_LOGGING_LABEL(7, title, "objective_function", num_flows)
+ BWE_TEST_LOGGING_BAR(7, bwe_name, ObjectiveFunction(), flow_id_);
+}
+
+void MetricRecorder::PlotZero() {
+ for (int i = kThroughput; i <= kLoss; ++i) {
+ if (plot_information_[i].plot) {
+ std::stringstream prefix;
+ prefix << "Receiver_" << flow_id_ << "_" + plot_information_[i].prefix;
+ PlotLine(i, prefix.str(), now_ms_, 0);
+ plot_information_[i].last_plot_ms = now_ms_;
+ }
+ }
+}
+
+void MetricRecorder::PauseFlow() {
+ PlotZero();
+ link_share_->PauseFlow(flow_id_);
+}
+
+void MetricRecorder::ResumeFlow(int64_t paused_time_ms) {
+ UpdateTimeMs(now_ms_ + paused_time_ms);
+ PlotZero();
+ link_share_->ResumeFlow(flow_id_);
+}
+
+double MetricRecorder::AverageBitrateKbps(int64_t extra_offset_ms) const {
+ int64_t duration_ms = RunDurationMs(extra_offset_ms);
+ if (duration_ms == 0)
+ return 0.0;
+ return static_cast<double>(8 * sum_throughput_bytes_ / duration_ms);
+}
+
+int64_t MetricRecorder::RunDurationMs(int64_t extra_offset_ms) const {
+ return now_ms_ - start_computing_metrics_ms_ - extra_offset_ms;
+}
+
+double MetricRecorder::DelayStdDev() const {
+ if (num_packets_received_ == 0) {
+ return 0.0;
+ }
+ double mean = static_cast<double>(sum_delays_ms_) / num_packets_received_;
+ double mean2 =
+ static_cast<double>(sum_delays_square_ms2_) / num_packets_received_;
+ return sqrt(mean2 - pow(mean, 2.0));
+}
+
+// Since delay values are bounded in a subset of [0, 5000] ms,
+// this function's execution time is O(1), independend of num_packets_received_.
+int64_t MetricRecorder::NthDelayPercentile(int n) const {
+ if (num_packets_received_ == 0) {
+ return 0;
+ }
+ size_t num_packets_remaining = (n * num_packets_received_) / 100;
+ for (auto hist : delay_histogram_ms_) {
+ if (num_packets_remaining <= hist.second)
+ return static_cast<int64_t>(hist.first);
+ num_packets_remaining -= hist.second;
+ }
+
+ assert(false);
+ return -1;
+}
+
+// The weighted_estimate_error_ was weighted based on time windows.
+// This function scales back the result before plotting.
+double MetricRecorder::Renormalize(double x) const {
+ return (x * num_packets_received_) / now_ms_;
+}
+
+inline double U(int64_t x, double alpha) {
+ if (alpha == 1.0) {
+ return log(static_cast<double>(x));
+ }
+ return pow(static_cast<double>(x), 1.0 - alpha) / (1.0 - alpha);
+}
+
+inline double U(size_t x, double alpha) {
+ return U(static_cast<int64_t>(x), alpha);
+}
+
+// TODO(magalhaesc): Update ObjectiveFunction.
+double MetricRecorder::ObjectiveFunction() const {
+ const double kDelta = 0.15; // Delay penalty factor.
+ const double kAlpha = 1.0;
+ const double kBeta = 1.0;
+
+ double throughput_metric = U(sum_throughput_bytes_, kAlpha);
+ double delay_penalty = kDelta * U(sum_delays_ms_, kBeta);
+
+ return throughput_metric - delay_penalty;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h
new file mode 100644
index 0000000000..2be13e0b0b
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_METRIC_RECORDER_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_METRIC_RECORDER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "webrtc/base/common.h"
+#include "webrtc/test/testsupport/gtest_prod_util.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class ChokeFilter;
+class PacketSender;
+
+class LinkShare {
+ public:
+ explicit LinkShare(ChokeFilter* choke_filter);
+
+ void PauseFlow(int flow_id); // Increases available capacity per flow.
+ void ResumeFlow(int flow_id); // Decreases available capacity per flow.
+
+ uint32_t TotalAvailableKbps();
+ // If the given flow is paused, its output is zero.
+ uint32_t AvailablePerFlowKbps(int flow_id);
+
+ private:
+ ChokeFilter* choke_filter_;
+ std::set<int> running_flows_;
+};
+
+struct PlotInformation {
+ PlotInformation()
+ : prefix(),
+ last_plot_ms(0),
+ time_ms(0),
+ value(0.0),
+ plot_interval_ms(0) {}
+ template <typename T>
+ void Update(int64_t now_ms, T new_value) {
+ time_ms = now_ms;
+ value = static_cast<double>(new_value);
+ }
+ std::string prefix;
+ bool plot;
+ int64_t last_plot_ms;
+ int64_t time_ms;
+ double value;
+ int64_t plot_interval_ms;
+};
+
+class MetricRecorder {
+ public:
+ MetricRecorder(const std::string algorithm_name,
+ int flow_id,
+ PacketSender* packet_sender,
+ LinkShare* link_share);
+
+ void SetPlotInformation(const std::vector<std::string>& prefixes,
+ bool plot_delay,
+ bool plot_loss);
+
+ template <typename T>
+ void PlotLine(int windows_id,
+ const std::string& prefix,
+ int64_t time_ms,
+ T y);
+
+ void PlotDynamics(int metric);
+ void PlotAllDynamics();
+
+ void UpdateTimeMs(int64_t time_ms);
+ void UpdateThroughput(int64_t bitrate_kbps, size_t payload_size);
+ void UpdateSendingEstimateKbps(int64_t bitrate_kbps);
+ void UpdateDelayMs(int64_t delay_ms);
+ void UpdateLoss(float loss_ratio);
+ void UpdateObjective();
+
+ void PlotThroughputHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ int64_t extra_offset_ms,
+ const std::string optimum_id) const;
+
+ void PlotThroughputHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ int64_t extra_offset_ms) const;
+
+ void PlotDelayHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ int64_t one_way_path_delay_ms) const;
+
+ void PlotLossHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ float global_loss_ratio) const;
+
+ void PlotObjectiveHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows) const;
+
+ void set_start_computing_metrics_ms(int64_t start_computing_metrics_ms) {
+ start_computing_metrics_ms_ = start_computing_metrics_ms;
+ }
+
+ void set_plot_available_capacity(bool plot) {
+ plot_information_[kTotalAvailable].plot = plot;
+ }
+
+ void PauseFlow(); // Plot zero.
+ void ResumeFlow(int64_t paused_time_ms); // Plot zero.
+ void PlotZero();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(MetricRecorderTest, NoPackets);
+ FRIEND_TEST_ALL_PREFIXES(MetricRecorderTest, RegularPackets);
+ FRIEND_TEST_ALL_PREFIXES(MetricRecorderTest, VariableDelayPackets);
+
+ uint32_t GetTotalAvailableKbps();
+ uint32_t GetAvailablePerFlowKbps();
+ uint32_t GetSendingEstimateKbps();
+ double ObjectiveFunction() const;
+
+ double Renormalize(double x) const;
+ bool ShouldRecord(int64_t arrival_time_ms);
+
+ void PushDelayMs(int64_t delay_ms, int64_t arrival_time_ms);
+ void PushThroughputBytes(size_t throughput_bytes, int64_t arrival_time_ms);
+
+ void UpdateEstimateError(int64_t new_value);
+ double DelayStdDev() const;
+ int64_t NthDelayPercentile(int n) const;
+ double AverageBitrateKbps(int64_t extra_offset_ms) const;
+ int64_t RunDurationMs(int64_t extra_offset_ms) const;
+
+ enum Metrics {
+ kThroughput = 0,
+ kSendingEstimate,
+ kDelay,
+ kLoss,
+ kObjective,
+ kTotalAvailable,
+ kAvailablePerFlow,
+ kNumMetrics
+ };
+
+ std::string algorithm_name_;
+ int flow_id_;
+ LinkShare* link_share_;
+
+ int64_t now_ms_;
+
+ PlotInformation plot_information_[kNumMetrics];
+
+ int64_t sum_delays_ms_;
+ // delay_histogram_ms_[i] counts how many packets have delay = i ms.
+ std::map<int64_t, size_t> delay_histogram_ms_;
+ int64_t sum_delays_square_ms2_; // Used to compute standard deviation.
+ size_t sum_throughput_bytes_;
+ // ((Receiving rate - available bitrate per flow) * time window)^p.
+ // 0 for negative values, 1 for positive values.
+ int64_t sum_lp_weighted_estimate_error_[2];
+ int64_t last_unweighted_estimate_error_;
+ int64_t optimal_throughput_bits_;
+ int64_t last_available_bitrate_per_flow_kbps_;
+ int64_t start_computing_metrics_ms_;
+ bool started_computing_metrics_;
+ size_t num_packets_received_;
+};
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_METRIC_RECORDER_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/metric_recorder_unittest.cc b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder_unittest.cc
new file mode 100644
index 0000000000..7d4ed5fd5f
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder_unittest.cc
@@ -0,0 +1,108 @@
+/*
+ * 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 "webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h"
+
+#include <math.h>
+#include <algorithm>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class MetricRecorderTest : public ::testing::Test {
+ public:
+ MetricRecorderTest() : metric_recorder_("Test", 0, nullptr, nullptr) {}
+
+ ~MetricRecorderTest() {}
+
+ protected:
+ MetricRecorder metric_recorder_;
+};
+
+TEST_F(MetricRecorderTest, NoPackets) {
+ EXPECT_EQ(metric_recorder_.AverageBitrateKbps(0), 0);
+ EXPECT_EQ(metric_recorder_.DelayStdDev(), 0.0);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(0), 0);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(5), 0);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(95), 0);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(100), 0);
+}
+
+TEST_F(MetricRecorderTest, RegularPackets) {
+ const size_t kPayloadSizeBytes = 1200;
+ const int64_t kDelayMs = 20;
+ const int64_t kInterpacketGapMs = 5;
+ const int kNumPackets = 1000;
+
+ for (int i = 0; i < kNumPackets; ++i) {
+ int64_t arrival_time_ms = kInterpacketGapMs * i + kDelayMs;
+ metric_recorder_.UpdateTimeMs(arrival_time_ms);
+ metric_recorder_.PushDelayMs(kDelayMs, arrival_time_ms);
+ metric_recorder_.PushThroughputBytes(kPayloadSizeBytes, arrival_time_ms);
+ }
+
+ EXPECT_NEAR(
+ metric_recorder_.AverageBitrateKbps(0),
+ static_cast<uint32_t>(kPayloadSizeBytes * 8) / (kInterpacketGapMs), 10);
+
+ EXPECT_EQ(metric_recorder_.DelayStdDev(), 0.0);
+
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(0), kDelayMs);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(5), kDelayMs);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(95), kDelayMs);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(100), kDelayMs);
+}
+
+TEST_F(MetricRecorderTest, VariableDelayPackets) {
+ const size_t kPayloadSizeBytes = 1200;
+ const int64_t kInterpacketGapMs = 2000;
+ const int kNumPackets = 1000;
+
+ std::vector<int64_t> delays_ms;
+ for (int i = 0; i < kNumPackets; ++i) {
+ delays_ms.push_back(static_cast<int64_t>(i + 1));
+ }
+ // Order of packets should not matter here.
+ std::random_shuffle(delays_ms.begin(), delays_ms.end());
+
+ int first_received_ms = delays_ms[0];
+ int64_t last_received_ms = 0;
+ for (int i = 0; i < kNumPackets; ++i) {
+ int64_t arrival_time_ms = kInterpacketGapMs * i + delays_ms[i];
+ last_received_ms = std::max(last_received_ms, arrival_time_ms);
+ metric_recorder_.UpdateTimeMs(arrival_time_ms);
+ metric_recorder_.PushDelayMs(delays_ms[i], arrival_time_ms);
+ metric_recorder_.PushThroughputBytes(kPayloadSizeBytes, arrival_time_ms);
+ }
+
+ size_t received_bits = kPayloadSizeBytes * 8 * kNumPackets;
+ EXPECT_NEAR(metric_recorder_.AverageBitrateKbps(0),
+ static_cast<uint32_t>(received_bits) /
+ ((last_received_ms - first_received_ms)),
+ 10);
+
+ double expected_x = (kNumPackets + 1) / 2.0;
+ double expected_x2 = ((kNumPackets + 1) * (2 * kNumPackets + 1)) / 6.0;
+ double var = expected_x2 - pow(expected_x, 2.0);
+ EXPECT_NEAR(metric_recorder_.DelayStdDev(), sqrt(var), kNumPackets / 1000.0);
+
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(0), 1);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(5), (5 * kNumPackets) / 100);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(95), (95 * kNumPackets) / 100);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(100), kNumPackets);
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet.h b/webrtc/modules/remote_bitrate_estimator/test/packet.h
new file mode 100644
index 0000000000..11885a4544
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/packet.h
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_H_
+
+#include <list>
+#include <map>
+#include <vector>
+
+#include "webrtc/common_types.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class Packet {
+ public:
+ enum Type { kMedia, kFeedback };
+
+ Packet();
+ Packet(int flow_id, int64_t send_time_us, size_t payload_size);
+ virtual ~Packet();
+
+ virtual bool operator<(const Packet& rhs) const;
+
+ virtual int flow_id() const { return flow_id_; }
+ virtual void set_send_time_us(int64_t send_time_us);
+ virtual int64_t send_time_us() const { return send_time_us_; }
+ virtual int64_t sender_timestamp_us() const { return sender_timestamp_us_; }
+ virtual size_t payload_size() const { return payload_size_; }
+ virtual Packet::Type GetPacketType() const = 0;
+ virtual void set_sender_timestamp_us(int64_t sender_timestamp_us) {
+ sender_timestamp_us_ = sender_timestamp_us;
+ }
+ virtual void set_paced(bool paced) { paced_ = paced; }
+ virtual bool paced() const { return paced_; }
+ virtual int64_t creation_time_ms() const {
+ return (creation_time_us_ + 500) / 1000;
+ }
+ virtual int64_t sender_timestamp_ms() const {
+ return (sender_timestamp_us_ + 500) / 1000;
+ }
+ virtual int64_t send_time_ms() const { return (send_time_us_ + 500) / 1000; }
+
+ 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.
+ int64_t sender_timestamp_us_; // Time the packet left the Sender.
+ size_t payload_size_; // Size of the (non-existent, simulated) payload.
+ bool paced_; // True if sent through paced sender.
+};
+
+class MediaPacket : public Packet {
+ public:
+ MediaPacket();
+ MediaPacket(int flow_id,
+ int64_t send_time_us,
+ size_t payload_size,
+ uint16_t sequence_number);
+ MediaPacket(int flow_id,
+ int64_t send_time_us,
+ size_t payload_size,
+ const RTPHeader& header);
+ MediaPacket(int64_t send_time_us, uint16_t sequence_number);
+
+ virtual ~MediaPacket() {}
+
+ int64_t GetAbsSendTimeInMs() const {
+ int64_t timestamp = header_.extension.absoluteSendTime
+ << kAbsSendTimeInterArrivalUpshift;
+ return 1000.0 * timestamp / static_cast<double>(1 << kInterArrivalShift);
+ }
+ 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; }
+
+ private:
+ static const int kAbsSendTimeFraction = 18;
+ static const int kAbsSendTimeInterArrivalUpshift = 8;
+ static const int kInterArrivalShift =
+ kAbsSendTimeFraction + kAbsSendTimeInterArrivalUpshift;
+
+ RTPHeader header_;
+};
+
+class FeedbackPacket : public Packet {
+ public:
+ 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 FbPacket.
+};
+
+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() {}
+
+ uint32_t estimated_bps() const { return estimated_bps_; }
+ RTCPReportBlock report_block() const { return report_block_; }
+
+ private:
+ const uint32_t estimated_bps_;
+ const RTCPReportBlock report_block_;
+};
+
+class SendSideBweFeedback : public FeedbackPacket {
+ public:
+ typedef std::map<uint16_t, int64_t> ArrivalTimesMap;
+ SendSideBweFeedback(int flow_id,
+ int64_t send_time_us,
+ int64_t latest_send_time_ms,
+ const std::vector<PacketInfo>& packet_feedback_vector);
+ virtual ~SendSideBweFeedback() {}
+
+ const std::vector<PacketInfo>& packet_feedback_vector() const {
+ return packet_feedback_vector_;
+ }
+
+ private:
+ const std::vector<PacketInfo> packet_feedback_vector_;
+};
+
+class NadaFeedback : public FeedbackPacket {
+ public:
+ NadaFeedback(int flow_id,
+ 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,
+ 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),
+ 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 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<uint16_t>& acked_packets)
+ : FeedbackPacket(flow_id, send_time_us, latest_send_time_ms),
+ acked_packets_(acked_packets) {}
+ virtual ~TcpFeedback() {}
+
+ const std::vector<uint16_t>& acked_packets() const { return acked_packets_; }
+
+ private:
+ const std::vector<uint16_t> acked_packets_;
+};
+
+typedef std::list<Packet*> Packets;
+typedef std::list<Packet*>::iterator PacketsIt;
+typedef std::list<Packet*>::const_iterator PacketsConstIt;
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet_receiver.cc b/webrtc/modules/remote_bitrate_estimator/test/packet_receiver.cc
new file mode 100644
index 0000000000..f70c212af7
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/packet_receiver.cc
@@ -0,0 +1,147 @@
+/*
+ * 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 "webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h"
+
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/common.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+#include "webrtc/modules/rtp_rtcp/interface/receive_statistics.h"
+#include "webrtc/system_wrappers/include/clock.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+PacketReceiver::PacketReceiver(PacketProcessorListener* listener,
+ int flow_id,
+ BandwidthEstimatorType bwe_type,
+ bool plot_delay,
+ bool plot_bwe,
+ MetricRecorder* metric_recorder)
+ : PacketProcessor(listener, flow_id, kReceiver),
+ bwe_receiver_(CreateBweReceiver(bwe_type, flow_id, plot_bwe)),
+ metric_recorder_(metric_recorder),
+ plot_delay_(plot_delay),
+ last_delay_plot_ms_(0),
+ // #2 aligns the plot with the right axis.
+ delay_prefix_("Delay_ms#2"),
+ bwe_type_(bwe_type) {
+ if (metric_recorder_ != nullptr) {
+ // Setup the prefix std::strings used when logging.
+ std::vector<std::string> prefixes;
+
+ // Metric recorder plots them in separated figures,
+ // alignment will take place with the #1 left axis.
+ prefixes.push_back("Throughput_kbps#1");
+ prefixes.push_back("Sending_Estimate_kbps#1");
+ prefixes.push_back("Delay_ms_#1");
+ prefixes.push_back("Packet_Loss_#1");
+ prefixes.push_back("Objective_function_#1");
+
+ // Plot Total/PerFlow Available capacity together with throughputs.
+ prefixes.push_back("Throughput_kbps#1"); // Total Available.
+ prefixes.push_back("Throughput_kbps#1"); // Available per flow.
+
+ bool plot_loss = plot_delay; // Plot loss if delay is plotted.
+ metric_recorder_->SetPlotInformation(prefixes, plot_delay, plot_loss);
+ }
+}
+
+PacketReceiver::PacketReceiver(PacketProcessorListener* listener,
+ int flow_id,
+ BandwidthEstimatorType bwe_type,
+ bool plot_delay,
+ bool plot_bwe)
+ : PacketReceiver(listener,
+ flow_id,
+ bwe_type,
+ plot_delay,
+ plot_bwe,
+ nullptr) {
+}
+
+PacketReceiver::~PacketReceiver() {
+}
+
+void PacketReceiver::RunFor(int64_t time_ms, Packets* in_out) {
+ Packets feedback;
+ for (auto it = in_out->begin(); it != in_out->end();) {
+ // PacketReceivers are only associated with a single stream, and therefore
+ // should only process a single flow id.
+ // TODO(holmer): Break this out into a Demuxer which implements both
+ // PacketProcessorListener and PacketProcessor.
+ BWE_TEST_LOGGING_CONTEXT("Receiver");
+ if ((*it)->GetPacketType() == Packet::kMedia &&
+ (*it)->flow_id() == *flow_ids().begin()) {
+ BWE_TEST_LOGGING_CONTEXT(*flow_ids().begin());
+ const MediaPacket* media_packet = static_cast<const MediaPacket*>(*it);
+ // We're treating the send time (from previous filter) as the arrival
+ // time once packet reaches the estimator.
+ int64_t arrival_time_ms = media_packet->send_time_ms();
+ int64_t send_time_ms = media_packet->creation_time_ms();
+ delay_stats_.Push(arrival_time_ms - send_time_ms);
+
+ if (metric_recorder_ != nullptr) {
+ metric_recorder_->UpdateTimeMs(arrival_time_ms);
+ UpdateMetrics(arrival_time_ms, send_time_ms,
+ media_packet->payload_size());
+ metric_recorder_->PlotAllDynamics();
+ } else if (plot_delay_) {
+ PlotDelay(arrival_time_ms, send_time_ms);
+ }
+
+ bwe_receiver_->ReceivePacket(arrival_time_ms, *media_packet);
+ FeedbackPacket* fb = bwe_receiver_->GetFeedback(arrival_time_ms);
+ if (fb)
+ feedback.push_back(fb);
+ delete media_packet;
+ it = in_out->erase(it);
+ } else {
+ ++it;
+ }
+ }
+ // Insert feedback packets to be sent back to the sender.
+ in_out->merge(feedback, DereferencingComparator<Packet>);
+}
+
+void PacketReceiver::UpdateMetrics(int64_t arrival_time_ms,
+ int64_t send_time_ms,
+ size_t payload_size) {
+ metric_recorder_->UpdateThroughput(bwe_receiver_->RecentKbps(), payload_size);
+ metric_recorder_->UpdateDelayMs(arrival_time_ms - send_time_ms);
+ metric_recorder_->UpdateLoss(bwe_receiver_->RecentPacketLossRatio());
+ metric_recorder_->UpdateObjective();
+}
+
+void PacketReceiver::PlotDelay(int64_t arrival_time_ms, int64_t send_time_ms) {
+ const int64_t kDelayPlotIntervalMs = 100;
+ if (arrival_time_ms >= last_delay_plot_ms_ + kDelayPlotIntervalMs) {
+ BWE_TEST_LOGGING_PLOT_WITH_NAME(0, delay_prefix_, arrival_time_ms,
+ arrival_time_ms - send_time_ms,
+ bwe_names[bwe_type_]);
+ last_delay_plot_ms_ = arrival_time_ms;
+ }
+}
+
+float PacketReceiver::GlobalPacketLoss() {
+ return bwe_receiver_->GlobalReceiverPacketLossRatio();
+}
+
+Stats<double> PacketReceiver::GetDelayStats() const {
+ return delay_stats_;
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h b/webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h
new file mode 100644
index 0000000000..fb9e9fd7ab
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_RECEIVER_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_RECEIVER_H_
+
+#include <string>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class PacketReceiver : public PacketProcessor {
+ public:
+ PacketReceiver(PacketProcessorListener* listener,
+ int flow_id,
+ BandwidthEstimatorType bwe_type,
+ bool plot_delay,
+ bool plot_bwe);
+ PacketReceiver(PacketProcessorListener* listener,
+ int flow_id,
+ BandwidthEstimatorType bwe_type,
+ bool plot_delay,
+ bool plot_bwe,
+ MetricRecorder* metric_recorder);
+ ~PacketReceiver();
+
+ // Implements PacketProcessor.
+ void RunFor(int64_t time_ms, Packets* in_out) override;
+
+ void LogStats();
+
+ Stats<double> GetDelayStats() const;
+
+ float GlobalPacketLoss();
+
+ protected:
+ void UpdateMetrics(int64_t arrival_time_ms,
+ int64_t send_time_ms,
+ size_t payload_size);
+
+ Stats<double> delay_stats_;
+ rtc::scoped_ptr<BweReceiver> bwe_receiver_;
+
+ private:
+ void PlotDelay(int64_t arrival_time_ms, int64_t send_time_ms);
+ MetricRecorder* metric_recorder_;
+ bool plot_delay_; // Used in case there isn't a metric recorder.
+ int64_t last_delay_plot_ms_;
+ std::string delay_prefix_;
+ BandwidthEstimatorType bwe_type_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(PacketReceiver);
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_RECEIVER_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet_sender.cc b/webrtc/modules/remote_bitrate_estimator/test/packet_sender.cc
new file mode 100644
index 0000000000..f1faa49d7e
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/packet_sender.cc
@@ -0,0 +1,494 @@
+/*
+ * 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 "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h"
+
+#include <algorithm>
+#include <list>
+#include <sstream>
+
+#include "webrtc/base/checks.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+void PacketSender::Pause() {
+ running_ = false;
+ if (metric_recorder_ != nullptr) {
+ metric_recorder_->PauseFlow();
+ }
+}
+
+void PacketSender::Resume(int64_t paused_time_ms) {
+ running_ = true;
+ if (metric_recorder_ != nullptr) {
+ metric_recorder_->ResumeFlow(paused_time_ms);
+ }
+}
+
+void PacketSender::set_metric_recorder(MetricRecorder* metric_recorder) {
+ metric_recorder_ = metric_recorder;
+}
+
+void PacketSender::RecordBitrate() {
+ if (metric_recorder_ != nullptr) {
+ BWE_TEST_LOGGING_CONTEXT("Sender");
+ BWE_TEST_LOGGING_CONTEXT(*flow_ids().begin());
+ metric_recorder_->UpdateTimeMs(clock_.TimeInMilliseconds());
+ metric_recorder_->UpdateSendingEstimateKbps(TargetBitrateKbps());
+ }
+}
+
+std::list<FeedbackPacket*> GetFeedbackPackets(Packets* in_out,
+ int64_t end_time_ms,
+ int flow_id) {
+ std::list<FeedbackPacket*> fb_packets;
+ for (auto it = in_out->begin(); it != in_out->end();) {
+ if ((*it)->send_time_us() > 1000 * end_time_ms)
+ break;
+ if ((*it)->GetPacketType() == Packet::kFeedback &&
+ flow_id == (*it)->flow_id()) {
+ fb_packets.push_back(static_cast<FeedbackPacket*>(*it));
+ it = in_out->erase(it);
+ } else {
+ ++it;
+ }
+ }
+ return fb_packets;
+}
+
+VideoSender::VideoSender(PacketProcessorListener* listener,
+ VideoSource* source,
+ BandwidthEstimatorType estimator_type)
+ : PacketSender(listener, source->flow_id()),
+ source_(source),
+ bwe_(CreateBweSender(estimator_type,
+ source_->bits_per_second() / 1000,
+ this,
+ &clock_)),
+ previous_sending_bitrate_(0) {
+ modules_.push_back(bwe_.get());
+}
+
+VideoSender::~VideoSender() {
+}
+
+void VideoSender::Pause() {
+ previous_sending_bitrate_ = TargetBitrateKbps();
+ PacketSender::Pause();
+}
+
+void VideoSender::Resume(int64_t paused_time_ms) {
+ source_->SetBitrateBps(previous_sending_bitrate_);
+ PacketSender::Resume(paused_time_ms);
+}
+
+void VideoSender::RunFor(int64_t time_ms, Packets* in_out) {
+ std::list<FeedbackPacket*> feedbacks = GetFeedbackPackets(
+ in_out, clock_.TimeInMilliseconds() + time_ms, source_->flow_id());
+ ProcessFeedbackAndGeneratePackets(time_ms, &feedbacks, in_out);
+}
+
+void VideoSender::ProcessFeedbackAndGeneratePackets(
+ int64_t time_ms,
+ std::list<FeedbackPacket*>* feedbacks,
+ Packets* packets) {
+ do {
+ // Make sure to at least run Process() below every 100 ms.
+ int64_t time_to_run_ms = std::min<int64_t>(time_ms, 100);
+ if (!feedbacks->empty()) {
+ int64_t time_until_feedback_ms =
+ feedbacks->front()->send_time_ms() - clock_.TimeInMilliseconds();
+ time_to_run_ms =
+ std::max<int64_t>(std::min(time_ms, time_until_feedback_ms), 0);
+ }
+
+ if (!running_) {
+ source_->SetBitrateBps(0);
+ }
+
+ Packets generated;
+ source_->RunFor(time_to_run_ms, &generated);
+ bwe_->OnPacketsSent(generated);
+ packets->merge(generated, DereferencingComparator<Packet>);
+
+ clock_.AdvanceTimeMilliseconds(time_to_run_ms);
+
+ if (!feedbacks->empty()) {
+ bwe_->GiveFeedback(*feedbacks->front());
+ delete feedbacks->front();
+ feedbacks->pop_front();
+ }
+
+ bwe_->Process();
+
+ time_ms -= time_to_run_ms;
+ } while (time_ms > 0);
+ assert(feedbacks->empty());
+}
+
+int VideoSender::GetFeedbackIntervalMs() const {
+ return bwe_->GetFeedbackIntervalMs();
+}
+
+void VideoSender::OnNetworkChanged(uint32_t target_bitrate_bps,
+ uint8_t fraction_lost,
+ int64_t rtt) {
+ source_->SetBitrateBps(target_bitrate_bps);
+ RecordBitrate();
+}
+
+uint32_t VideoSender::TargetBitrateKbps() {
+ return (source_->bits_per_second() + 500) / 1000;
+}
+
+PacedVideoSender::PacedVideoSender(PacketProcessorListener* listener,
+ VideoSource* source,
+ BandwidthEstimatorType estimator)
+ : VideoSender(listener, source, estimator),
+ pacer_(&clock_,
+ this,
+ source->bits_per_second() / 1000,
+ PacedSender::kDefaultPaceMultiplier * source->bits_per_second() /
+ 1000,
+ 0) {
+ modules_.push_back(&pacer_);
+}
+
+PacedVideoSender::~PacedVideoSender() {
+ for (Packet* packet : pacer_queue_)
+ delete packet;
+ for (Packet* packet : queue_)
+ delete packet;
+}
+
+void PacedVideoSender::RunFor(int64_t time_ms, Packets* in_out) {
+ int64_t end_time_ms = clock_.TimeInMilliseconds() + time_ms;
+ // Run process periodically to allow the packets to be paced out.
+ std::list<FeedbackPacket*> feedbacks =
+ GetFeedbackPackets(in_out, end_time_ms, source_->flow_id());
+ int64_t last_run_time_ms = -1;
+ BWE_TEST_LOGGING_CONTEXT("Sender");
+ BWE_TEST_LOGGING_CONTEXT(source_->flow_id());
+ do {
+ int64_t time_until_process_ms = TimeUntilNextProcess(modules_);
+ int64_t time_until_feedback_ms = time_ms;
+ if (!feedbacks.empty())
+ time_until_feedback_ms = std::max<int64_t>(
+ feedbacks.front()->send_time_ms() - clock_.TimeInMilliseconds(), 0);
+
+ int64_t time_until_next_event_ms =
+ std::min(time_until_feedback_ms, time_until_process_ms);
+
+ time_until_next_event_ms =
+ std::min(source_->GetTimeUntilNextFrameMs(), time_until_next_event_ms);
+
+ // Never run for longer than we have been asked for.
+ if (clock_.TimeInMilliseconds() + time_until_next_event_ms > end_time_ms)
+ time_until_next_event_ms = end_time_ms - clock_.TimeInMilliseconds();
+
+ // Make sure we don't get stuck if an event doesn't trigger. This typically
+ // happens if the prober wants to probe, but there's no packet to send.
+ if (time_until_next_event_ms == 0 && last_run_time_ms == 0)
+ time_until_next_event_ms = 1;
+ last_run_time_ms = time_until_next_event_ms;
+
+ Packets generated_packets;
+ source_->RunFor(time_until_next_event_ms, &generated_packets);
+ if (!generated_packets.empty()) {
+ for (Packet* packet : generated_packets) {
+ MediaPacket* media_packet = static_cast<MediaPacket*>(packet);
+ pacer_.InsertPacket(
+ PacedSender::kNormalPriority, media_packet->header().ssrc,
+ media_packet->header().sequenceNumber, media_packet->send_time_ms(),
+ media_packet->payload_size(), false);
+ pacer_queue_.push_back(packet);
+ assert(pacer_queue_.size() < 10000);
+ }
+ }
+
+ clock_.AdvanceTimeMilliseconds(time_until_next_event_ms);
+
+ if (time_until_next_event_ms == time_until_feedback_ms) {
+ if (!feedbacks.empty()) {
+ bwe_->GiveFeedback(*feedbacks.front());
+ delete feedbacks.front();
+ feedbacks.pop_front();
+ }
+ bwe_->Process();
+ }
+
+ if (time_until_next_event_ms == time_until_process_ms) {
+ CallProcess(modules_);
+ }
+ } while (clock_.TimeInMilliseconds() < end_time_ms);
+ QueuePackets(in_out, end_time_ms * 1000);
+}
+
+int64_t PacedVideoSender::TimeUntilNextProcess(
+ const std::list<Module*>& modules) {
+ int64_t time_until_next_process_ms = 10;
+ for (Module* module : modules) {
+ int64_t next_process_ms = module->TimeUntilNextProcess();
+ if (next_process_ms < time_until_next_process_ms)
+ time_until_next_process_ms = next_process_ms;
+ }
+ if (time_until_next_process_ms < 0)
+ time_until_next_process_ms = 0;
+ return time_until_next_process_ms;
+}
+
+void PacedVideoSender::CallProcess(const std::list<Module*>& modules) {
+ for (Module* module : modules) {
+ if (module->TimeUntilNextProcess() <= 0) {
+ module->Process();
+ }
+ }
+}
+
+void PacedVideoSender::QueuePackets(Packets* batch,
+ int64_t end_of_batch_time_us) {
+ queue_.merge(*batch, DereferencingComparator<Packet>);
+ if (queue_.empty()) {
+ return;
+ }
+ Packets::iterator it = queue_.begin();
+ for (; it != queue_.end(); ++it) {
+ if ((*it)->send_time_us() > end_of_batch_time_us) {
+ break;
+ }
+ }
+ Packets to_transfer;
+ to_transfer.splice(to_transfer.begin(), queue_, queue_.begin(), it);
+ for (Packet* packet : to_transfer)
+ packet->set_paced(true);
+ bwe_->OnPacketsSent(to_transfer);
+ batch->merge(to_transfer, DereferencingComparator<Packet>);
+}
+
+bool PacedVideoSender::TimeToSendPacket(uint32_t ssrc,
+ uint16_t sequence_number,
+ int64_t capture_time_ms,
+ bool retransmission) {
+ for (Packets::iterator it = pacer_queue_.begin(); it != pacer_queue_.end();
+ ++it) {
+ MediaPacket* media_packet = static_cast<MediaPacket*>(*it);
+ if (media_packet->header().sequenceNumber == sequence_number) {
+ int64_t pace_out_time_ms = clock_.TimeInMilliseconds();
+
+ // Make sure a packet is never paced out earlier than when it was put into
+ // the pacer.
+ assert(pace_out_time_ms >= media_packet->send_time_ms());
+
+ media_packet->SetAbsSendTimeMs(pace_out_time_ms);
+ media_packet->set_send_time_us(1000 * pace_out_time_ms);
+ media_packet->set_sender_timestamp_us(1000 * pace_out_time_ms);
+ queue_.push_back(media_packet);
+ pacer_queue_.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+size_t PacedVideoSender::TimeToSendPadding(size_t bytes) {
+ return 0;
+}
+
+void PacedVideoSender::OnNetworkChanged(uint32_t target_bitrate_bps,
+ uint8_t fraction_lost,
+ int64_t rtt) {
+ VideoSender::OnNetworkChanged(target_bitrate_bps, fraction_lost, rtt);
+ pacer_.UpdateBitrate(
+ target_bitrate_bps / 1000,
+ PacedSender::kDefaultPaceMultiplier * target_bitrate_bps / 1000, 0);
+}
+
+const int kNoLimit = std::numeric_limits<int>::max();
+const int kPacketSizeBytes = 1200;
+
+TcpSender::TcpSender(PacketProcessorListener* listener,
+ int flow_id,
+ int64_t offset_ms)
+ : TcpSender(listener, flow_id, offset_ms, kNoLimit) {
+}
+
+TcpSender::TcpSender(PacketProcessorListener* listener,
+ int flow_id,
+ int64_t offset_ms,
+ int send_limit_bytes)
+ : PacketSender(listener, flow_id),
+ cwnd_(10),
+ ssthresh_(kNoLimit),
+ ack_received_(false),
+ last_acked_seq_num_(0),
+ next_sequence_number_(0),
+ offset_ms_(offset_ms),
+ last_reduction_time_ms_(-1),
+ last_rtt_ms_(0),
+ total_sent_bytes_(0),
+ send_limit_bytes_(send_limit_bytes),
+ last_generated_packets_ms_(0),
+ num_recent_sent_packets_(0),
+ bitrate_kbps_(0) {
+}
+
+void TcpSender::RunFor(int64_t time_ms, Packets* in_out) {
+ if (clock_.TimeInMilliseconds() + time_ms < offset_ms_) {
+ clock_.AdvanceTimeMilliseconds(time_ms);
+ if (running_) {
+ Pause();
+ }
+ return;
+ }
+
+ if (!running_ && total_sent_bytes_ == 0) {
+ Resume(offset_ms_);
+ }
+
+ int64_t start_time_ms = clock_.TimeInMilliseconds();
+
+ std::list<FeedbackPacket*> 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.
+ for (FeedbackPacket* fb : feedbacks) {
+ clock_.AdvanceTimeMilliseconds(fb->send_time_ms() -
+ clock_.TimeInMilliseconds());
+ last_rtt_ms_ = fb->send_time_ms() - fb->latest_send_time_ms();
+ UpdateCongestionControl(fb);
+ 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;
+ }
+
+ clock_.AdvanceTimeMilliseconds(time_ms -
+ (clock_.TimeInMilliseconds() - start_time_ms));
+ SendPackets(in_out);
+}
+
+void TcpSender::SendPackets(Packets* in_out) {
+ int cwnd = ceil(cwnd_);
+ int packets_to_send = std::max(cwnd - static_cast<int>(in_flight_.size()), 0);
+ int timed_out = TriggerTimeouts();
+ if (timed_out > 0) {
+ HandleLoss();
+ }
+ if (packets_to_send > 0) {
+ Packets generated = GeneratePackets(packets_to_send);
+ for (Packet* packet : generated)
+ in_flight_.insert(InFlight(*static_cast<MediaPacket*>(packet)));
+
+ in_out->merge(generated, DereferencingComparator<Packet>);
+ }
+}
+
+void TcpSender::UpdateCongestionControl(const FeedbackPacket* fb) {
+ const TcpFeedback* tcp_fb = static_cast<const TcpFeedback*>(fb);
+ RTC_DCHECK(!tcp_fb->acked_packets().empty());
+ ack_received_ = true;
+
+ uint16_t expected = tcp_fb->acked_packets().back() - last_acked_seq_num_;
+ uint16_t missing =
+ expected - static_cast<uint16_t>(tcp_fb->acked_packets().size());
+
+ for (uint16_t ack_seq_num : tcp_fb->acked_packets())
+ in_flight_.erase(InFlight(ack_seq_num, clock_.TimeInMilliseconds()));
+
+ if (missing > 0) {
+ HandleLoss();
+ } else if (cwnd_ <= ssthresh_) {
+ cwnd_ += tcp_fb->acked_packets().size();
+ } else {
+ cwnd_ += 1.0f / cwnd_;
+ }
+
+ last_acked_seq_num_ =
+ LatestSequenceNumber(tcp_fb->acked_packets().back(), last_acked_seq_num_);
+}
+
+int TcpSender::TriggerTimeouts() {
+ int timed_out = 0;
+ for (auto it = in_flight_.begin(); it != in_flight_.end();) {
+ if (it->time_ms < clock_.TimeInMilliseconds() - 1000) {
+ in_flight_.erase(it++);
+ ++timed_out;
+ } else {
+ ++it;
+ }
+ }
+ return timed_out;
+}
+
+void TcpSender::HandleLoss() {
+ if (clock_.TimeInMilliseconds() - last_reduction_time_ms_ < last_rtt_ms_)
+ return;
+ last_reduction_time_ms_ = clock_.TimeInMilliseconds();
+ ssthresh_ = std::max(static_cast<int>(in_flight_.size() / 2), 2);
+ cwnd_ = ssthresh_;
+}
+
+Packets TcpSender::GeneratePackets(size_t num_packets) {
+ Packets generated;
+
+ UpdateSendBitrateEstimate(num_packets);
+
+ for (size_t i = 0; i < num_packets; ++i) {
+ if ((total_sent_bytes_ + kPacketSizeBytes) > send_limit_bytes_) {
+ if (running_) {
+ Pause();
+ }
+ break;
+ }
+ generated.push_back(
+ new MediaPacket(*flow_ids().begin(), 1000 * clock_.TimeInMilliseconds(),
+ kPacketSizeBytes, next_sequence_number_++));
+ generated.back()->set_sender_timestamp_us(
+ 1000 * clock_.TimeInMilliseconds());
+
+ total_sent_bytes_ += kPacketSizeBytes;
+ }
+
+ return generated;
+}
+
+void TcpSender::UpdateSendBitrateEstimate(size_t num_packets) {
+ const int kTimeWindowMs = 500;
+ num_recent_sent_packets_ += num_packets;
+
+ int64_t delta_ms = clock_.TimeInMilliseconds() - last_generated_packets_ms_;
+ if (delta_ms >= kTimeWindowMs) {
+ bitrate_kbps_ =
+ static_cast<uint32_t>(8 * num_recent_sent_packets_ * kPacketSizeBytes) /
+ delta_ms;
+ last_generated_packets_ms_ = clock_.TimeInMilliseconds();
+ num_recent_sent_packets_ = 0;
+ }
+
+ RecordBitrate();
+}
+
+uint32_t TcpSender::TargetBitrateKbps() {
+ return bitrate_kbps_;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet_sender.h b/webrtc/modules/remote_bitrate_estimator/test/packet_sender.h
new file mode 100644
index 0000000000..c42647e2d3
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/packet_sender.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_SENDER_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_SENDER_H_
+
+#include <list>
+#include <limits>
+#include <string>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/interface/module.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class MetricRecorder;
+
+class PacketSender : public PacketProcessor {
+ public:
+ PacketSender(PacketProcessorListener* listener, int flow_id)
+ : PacketProcessor(listener, flow_id, kSender),
+ running_(true),
+ // 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),
+ metric_recorder_(nullptr) {}
+ virtual ~PacketSender() {}
+ // Call GiveFeedback() with the returned interval in milliseconds, provided
+ // there is a new estimate available.
+ // Note that changing the feedback interval affects the timing of when the
+ // 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);
+
+ virtual uint32_t TargetBitrateKbps() { return 0; }
+
+ virtual void Pause();
+ virtual void Resume(int64_t paused_time_ms);
+
+ void set_metric_recorder(MetricRecorder* metric_recorder);
+ virtual void RecordBitrate();
+
+ protected:
+ bool running_; // Initialized by default as true.
+ SimulatedClock clock_;
+
+ private:
+ MetricRecorder* metric_recorder_;
+};
+
+class VideoSender : public PacketSender, public BitrateObserver {
+ public:
+ VideoSender(PacketProcessorListener* listener,
+ VideoSource* source,
+ BandwidthEstimatorType estimator);
+ virtual ~VideoSender();
+
+ int GetFeedbackIntervalMs() const override;
+ void RunFor(int64_t time_ms, Packets* in_out) override;
+
+ virtual VideoSource* source() const { return source_; }
+
+ uint32_t TargetBitrateKbps() override;
+
+ // Implements BitrateObserver.
+ void OnNetworkChanged(uint32_t target_bitrate_bps,
+ uint8_t fraction_lost,
+ int64_t rtt) override;
+
+ void Pause() override;
+ void Resume(int64_t paused_time_ms) override;
+
+ protected:
+ void ProcessFeedbackAndGeneratePackets(int64_t time_ms,
+ std::list<FeedbackPacket*>* feedbacks,
+ Packets* generated);
+
+ VideoSource* source_;
+ rtc::scoped_ptr<BweSender> bwe_;
+ int64_t start_of_run_ms_;
+ std::list<Module*> modules_;
+
+ private:
+ uint32_t previous_sending_bitrate_;
+ RTC_DISALLOW_COPY_AND_ASSIGN(VideoSender);
+};
+
+class PacedVideoSender : public VideoSender, public PacedSender::Callback {
+ public:
+ PacedVideoSender(PacketProcessorListener* listener,
+ VideoSource* source,
+ BandwidthEstimatorType estimator);
+ virtual ~PacedVideoSender();
+
+ void RunFor(int64_t time_ms, Packets* in_out) override;
+
+ // Implements PacedSender::Callback.
+ bool TimeToSendPacket(uint32_t ssrc,
+ uint16_t sequence_number,
+ int64_t capture_time_ms,
+ bool retransmission) override;
+ size_t TimeToSendPadding(size_t bytes) override;
+
+ // Implements BitrateObserver.
+ void OnNetworkChanged(uint32_t target_bitrate_bps,
+ uint8_t fraction_lost,
+ int64_t rtt) override;
+
+ private:
+ int64_t TimeUntilNextProcess(const std::list<Module*>& modules);
+ void CallProcess(const std::list<Module*>& modules);
+ void QueuePackets(Packets* batch, int64_t end_of_batch_time_us);
+
+ PacedSender pacer_;
+ Packets queue_;
+ Packets pacer_queue_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(PacedVideoSender);
+};
+
+class TcpSender : public PacketSender {
+ public:
+ TcpSender(PacketProcessorListener* listener, int flow_id, int64_t offset_ms);
+ TcpSender(PacketProcessorListener* listener,
+ int flow_id,
+ int64_t offset_ms,
+ int send_limit_bytes);
+ virtual ~TcpSender() {}
+
+ void RunFor(int64_t time_ms, Packets* in_out) override;
+ int GetFeedbackIntervalMs() const override { return 10; }
+
+ uint32_t TargetBitrateKbps() override;
+
+ private:
+ struct InFlight {
+ public:
+ InFlight(const MediaPacket& packet)
+ : sequence_number(packet.header().sequenceNumber),
+ time_ms(packet.send_time_ms()) {}
+
+ InFlight(uint16_t seq_num, int64_t now_ms)
+ : sequence_number(seq_num), time_ms(now_ms) {}
+
+ bool operator<(const InFlight& rhs) const {
+ return sequence_number < rhs.sequence_number;
+ }
+
+ uint16_t sequence_number; // Sequence number of a packet in flight, or a
+ // packet which has just been acked.
+ int64_t time_ms; // Time of when the packet left the sender, or when the
+ // ack was received.
+ };
+
+ void SendPackets(Packets* in_out);
+ void UpdateCongestionControl(const FeedbackPacket* fb);
+ int TriggerTimeouts();
+ void HandleLoss();
+ Packets GeneratePackets(size_t num_packets);
+ void UpdateSendBitrateEstimate(size_t num_packets);
+
+ float cwnd_;
+ int ssthresh_;
+ std::set<InFlight> in_flight_;
+ bool ack_received_;
+ uint16_t last_acked_seq_num_;
+ uint16_t next_sequence_number_;
+ int64_t offset_ms_;
+ int64_t last_reduction_time_ms_;
+ int64_t last_rtt_ms_;
+ int total_sent_bytes_;
+ int send_limit_bytes_; // Initialized by default as kNoLimit.
+ int64_t last_generated_packets_ms_;
+ size_t num_recent_sent_packets_;
+ uint32_t bitrate_kbps_;
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_SENDER_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/plot_bars.sh b/webrtc/modules/remote_bitrate_estimator/test/plot_bars.sh
new file mode 100755
index 0000000000..9f7fb16203
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/plot_bars.sh
@@ -0,0 +1,286 @@
+#!/bin/bash
+
+# Copyright (c) 2013 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.
+
+# To set up in e.g. Eclipse, run a separate shell and pipe the output from the
+# test into this script.
+#
+# In Eclipse, that amounts to creating a Run Configuration which starts
+# "/bin/bash" with the arguments "-c [trunk_path]/out/Debug/modules_unittests
+# --gtest_filter=*BweTest* | [trunk_path]/webrtc/modules/
+# remote_bitrate_estimator/test/plot_bars.sh
+
+# This script supports multiple figures (windows), the figure is specified as an
+# identifier at the first argument after the PLOT command. Each figure has a
+# single y axis and a dual y axis mode. If any line specifies an axis by ending
+# with "#<axis number (1 or 2)>" two y axis will be used, the first will be
+# assumed to represent bitrate (in kbps) and the second will be assumed to
+# represent time deltas (in ms).
+
+log=$(</dev/stdin)
+
+# Plot histograms.
+function gen_gnuplot_bar_input {
+ x_start=1
+ x_end=3.75
+ bars=$(echo "$log" | grep "BAR")
+
+ labels=$(echo "$log" | grep "^LABEL")
+ figures=($(echo "$bars" | cut -f 2 | sort | uniq))
+
+ echo "reset" # Clears previous settings.
+
+ echo "set title font 'Verdana,22'"
+ echo "set xtics font 'Verdana,24'"
+ echo "set ytics font 'Verdana,14'"
+ echo "set ylabel font 'Verdana,16'"
+
+ echo "set xrange[$x_start:$x_end]"
+ echo "set style fill solid 0.5"
+ echo "set style fill solid border -1"
+
+ declare -a ydist=(11.5 10.5 10.5) # Used to correctly offset the y label.
+ i=0
+ for figure in "${figures[@]}" ; do
+
+ echo "set terminal wxt $figure size 440,440 dashed"
+ echo "set ylabel offset ${ydist[$i]}, -3"
+ (( i++ ))
+
+ title=$(echo "$labels" | grep "^LABEL.$figure" | cut -f 3 | \
+ head -n 1 | sed 's/_/ /g')
+ y_label=$(echo "$labels" | grep "^LABEL.$figure" | cut -f 4 | \
+ head -n 1 | sed 's/_/ /g')
+
+ # RMCAT flows.
+ num_flows=$(echo "$labels" | grep "^LABEL.$figure" | cut -f 5 | \
+ head -n 1)
+
+ # RMCAT algorithm 1.
+ x_label_1=$(echo "$log" | grep "BAR.$figure" | cut -f 3 | sed 's/_/\t/g' \
+ | cut -f 1 | sort | uniq | head -n 1 )
+
+ # RMCAT algorithm 2.
+ x_label_2=$(echo "$log" | grep "BAR.$figure" | cut -f 3 | sed 's/_/\t/g' \
+ | cut -f 1 | sort | uniq | sed -n 2p)
+
+ x_labels="('$x_label_1' 2, '$x_label_2' 3)"
+ tcp_flow=false
+
+ tcp_space=0.2 # Extra horizontal space between bars.
+
+ # Parse labels if there are other flows in addition to RMCAT ones.
+ IFS='x' read -ra split_label_1 <<< "$x_label_1"
+
+ if (( ${#split_label_1[@]} > "1" )); then
+ tcp_flow=true
+ box_width=$(echo "(1.0-$tcp_space/2)/$num_flows" | bc -l)
+ echo "set xtics font 'Verdana,16'"
+ x_labels="("
+ delimiter=""
+ abscissa=$(echo $x_start + 0.5 + 0.5*$box_width | bc)
+ for label in "${split_label_1[@]}" ; do
+ x_labels+="$delimiter'$label' $abscissa"
+ abscissa=$(echo $abscissa + $box_width | bc)
+ delimiter=", "
+ done
+ abscissa=$(echo $abscissa + $tcp_space | bc)
+ IFS='x' read -ra split_label_2 <<< "$x_label_2"
+ for label in "${split_label_2[@]}" ; do
+ x_labels+="$delimiter'$label' $abscissa"
+ abscissa=$(echo $abscissa + $box_width | bc)
+ done
+ x_labels="$x_labels)"
+ else
+ box_width=$(echo 1.0/$num_flows | bc -l)
+ fi
+
+ echo "set boxwidth $box_width"
+
+ # Plots can be directly exported to image files.
+ file_name=$(echo "$labels" | grep "^LABEL.$figure" | cut -f 5 | head -n 1)
+
+ y_max=0 # Used to scale the plot properly.
+
+ # Scale all latency plots with the same vertical scale.
+ delay_figure=5
+ if (( $figure==$delay_figure )) ; then
+ y_max=400
+ else # Take y_max = 1.1 * highest plot value.
+
+ # Since only the optimal bitrate for the first flow is being ploted,
+ # consider only this one for scalling purposes.
+ data_sets=$(echo "$bars" | grep "LIMITERRORBAR.$figure" | cut -f 3 | \
+ sed 's/_/\t/g' | cut -f 1 | sort | uniq)
+
+ if (( ${#data_sets[@]} > "0" )); then
+ for set in $data_sets ; do
+ y=$(echo "$bars" | grep "LIMITERRORBAR.$figure.$set" | cut -f 8 | \
+ head -n 1)
+ if (( $(bc <<< "$y > $y_max") == 1 )); then
+ y_max=$y
+ fi
+ done
+ fi
+
+ data_sets=$(echo "$bars" | grep "ERRORBAR.$figure" | cut -f 3 | \
+ sort | uniq)
+ if (( ${#data_sets[@]} > "0" )); then
+ for set in $data_sets ; do
+ y=$(echo "$bars" | grep "ERRORBAR.$figure.$set" | cut -f 6 | \
+ head -n 1)
+ if (( $(bc <<< "$y > $y_max") == 1 )) ; then
+ y_max=$y
+ fi
+ done
+ fi
+
+ data_sets=$(echo "$bars" | grep "BAR.$figure" | cut -f 3 | sort | uniq)
+
+ for set in $data_sets ; do
+ y=$(echo "$bars" | grep "BAR.$figure.$set" | cut -f 4 | head -n 1)
+ if (( $(bc <<< "$y > $y_max") == 1 )) ; then
+ y_max=$y
+ fi
+ done
+
+ y_max=$(echo $y_max*1.1 | bc)
+ fi
+
+
+ echo "set ylabel \"$y_label\""
+ echo "set yrange[0:$y_max]"
+
+ echo "set multiplot"
+
+ # Plot bars.
+ data_sets=$(echo "$bars" | grep "BAR.$figure" | cut -f 3 | sort | uniq)
+
+ echo "set xtics $x_labels"
+ echo "plot '-' using 1:4:2 with boxes lc variable notitle"
+
+ echo
+
+ color=11 # Green.
+ x_bar=$(echo $x_start + 0.5 + 0.5*$box_width | bc)
+ for set in $data_sets ; do
+ echo -n "$x_bar $color "
+ echo "$bars" | grep "BAR.$figure.$set" | cut -f 3,4
+
+ # Add extra space if TCP flows are being plotted.
+ if $tcp_flow && \
+ (( $(bc <<< "$x_bar < $x_start + 1.5 - 0.5*$tcp_space") == 1 )) && \
+ (( $(bc <<< "$x_bar + $box_width > $x_start + 1.5 + 0.5*$tcp_space") \
+ == 1 )); then
+ x_bar=$(echo $x_bar + $tcp_space | bc)
+ fi
+
+ x_bar=$(echo $x_bar + $box_width | bc)
+
+ if (( $(bc <<< "$x_bar > 2.5") == 1 )) ; then
+ color=12 # Blue.
+ fi
+ # Different bar color for TCP flows:
+ if $tcp_flow && \
+ (( $(bc <<< "(100*$x_bar)%100 < 50") == 1 ))
+ then
+ color=18 # Gray.
+ fi
+ done
+ echo "e"
+
+ # Plot Baseline bars, e.g. one-way path delay on latency plots.
+ data_sets=$(echo "$log" | grep "BASELINE.$figure" | cut -f 3 | sort | uniq)
+
+ if (( ${#data_sets} > "0" )); then
+ echo "set xtics $x_labels"
+ echo "plot '-' using 1:4:2 with boxes lc variable notitle"
+
+ echo
+
+ color=18 # Gray.
+ x_bar=$(echo $x_start + 0.5 + 0.5*$box_width | bc)
+ for set in $data_sets ; do
+ echo -n "$x_bar $color "
+ echo "$log" | grep "BASELINE.$figure.$set" | cut -f 3,4
+
+ # Add extra space if TCP flows are being plotted.
+ if $tcp_flow && \
+ (( $(bc <<< "$x_bar < $x_start + 1.5 - 0.5*$tcp_space") == 1 )) && \
+ (( $(bc <<< "$x_bar + $box_width > $x_start + 1.5 \
+ + 0.5*$tcp_space") == 1 )); then
+ x_bar=$(echo $x_bar + $tcp_space | bc)
+ fi
+
+ x_bar=$(echo $x_bar + $box_width | bc)
+
+ done
+ echo "e"
+ fi
+
+ # Plot vertical error lines, e.g. y +- sigma.
+ data_sets=$(echo "$bars" | grep "ERRORBAR.$figure" | cut -f 3 | sort | uniq)
+
+ if (( ${#data_sets} > "0" )); then
+
+ echo "set key left"
+ error_title=$(echo "$bars" | grep "ERRORBAR.$figure" | cut -f 7 | \
+ head -n 1 | sed 's/_/ /g')
+
+ echo "set xtics $x_labels"
+ echo "plot '-' using 1:3:4:5 title '$error_title' with yerr"
+
+ x_error_line=$(echo $x_start + 0.5 + 0.5*$box_width | bc)
+ for set in $data_sets ; do
+ echo -n "$x_error_line "
+ echo "$bars" | grep "ERRORBAR.$figure.$set" | cut -f 3,4,5,6
+
+ # Add extra space if TCP flows are being plotted.
+ if $tcp_flow && \
+ (( $(bc <<< "$x_error_line < $x_start + 1.5 - 0.5*$tcp_space") == 1 \
+ )) && (( $(bc <<< "$x_error_line + $box_width > $x_start + 1.5 \
+ + 0.5*$tcp_space") == 1 )); then
+ x_error_line=$(echo $x_error_line + $tcp_space | bc)
+ fi
+
+ x_error_line=$(echo $x_error_line + $box_width | bc)
+ done
+ echo "e"
+ fi
+
+ # Plot horizontal dashed lines, e.g. y = optimal bitrate.
+ data_sets=$(echo "$bars" | grep "LIMITERRORBAR.$figure" | cut -f 3 \
+ | sort | uniq)
+ if (( ${#data_sets} > "0" )); then
+
+ echo "set style line 1 lt 1 lw 3 pt 3 ps 0 linecolor rgb 'black'"
+
+ limit_titles=$(echo "$bars" | grep "LIMITERRORBAR.$figure" | cut -f 9 \
+ | sort | uniq)
+
+ for title in $limit_titles ; do
+ y_max=$(echo "$bars" | grep "LIMITERRORBAR.$figure" | grep "$title" \
+ | cut -f 8 | head -n 1)
+
+ retouched_title=$(echo "$title" | sed 's/#/\t/g' | cut -f 1 \
+ | sed 's/_/ /g')
+
+ echo "set key right top"
+ echo "set xtics $x_labels"
+ echo "plot $y_max lt 7 lw 1 linecolor rgb 'black' \
+ title '$retouched_title'"
+ done
+
+ fi
+
+ echo "unset multiplot"
+ done
+}
+
+gen_gnuplot_bar_input | gnuplot -persist
diff --git a/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py b/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py
new file mode 100644
index 0000000000..1bae1e81f0
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python
+# 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.
+
+# This script is used to plot simulation dynamics.
+# Able to plot each flow separately. Other plot boxes can be added,
+# currently one for Throughput, one for Latency and one for Packet Loss.
+
+import matplotlib
+import matplotlib.pyplot as plt
+import numpy
+import re
+import sys
+
+# Change this to True to save the figure to a file. Look below for details.
+save_figure = False
+
+class Variable(object):
+ def __init__(self, variable):
+ self._ID = variable[0]
+ self._xlabel = variable[1]
+ self._ylabel = variable[2]
+ self._subplot = variable[3]
+ self._y_max = variable[4]
+ self.samples = dict()
+
+ def getID(self):
+ return self._ID
+
+ def getXLabel(self):
+ return self._xlabel
+
+ def getYLabel(self):
+ return self._ylabel
+
+ def getSubplot(self):
+ return self._subplot
+
+ def getYMax(self):
+ return self._y_max
+
+ def getNumberOfFlows(self):
+ return len(self.samples)
+
+
+ def addSample(self, line):
+ groups = re.search(r'_(((\d)+((,(\d)+)*))_(\D+))#\d@(\S+)', line)
+
+ # Each variable will be plotted in a separated box.
+ var_name = groups.group(1)
+ alg_name = groups.group(8)
+
+ alg_name = alg_name.replace('_', ' ')
+
+ if alg_name not in self.samples.keys():
+ self.samples[alg_name] = {}
+
+ if var_name not in self.samples[alg_name].keys():
+ self.samples[alg_name][var_name] = []
+
+ sample = re.search(r'(\d+\.\d+)\t([-]?\d+\.\d+)', line)
+
+ s = (sample.group(1), sample.group(2))
+ self.samples[alg_name][var_name].append(s)
+
+def plotVar(v, ax, show_legend, show_x_label):
+ if show_x_label:
+ ax.set_xlabel(v.getXLabel(), fontsize='large')
+ ax.set_ylabel(v.getYLabel(), fontsize='large')
+
+ for alg in v.samples.keys():
+
+ for series in v.samples[alg].keys():
+
+ x = [sample[0] for sample in v.samples[alg][series]]
+ y = [sample[1] for sample in v.samples[alg][series]]
+ x = numpy.array(x)
+ y = numpy.array(y)
+
+ line = plt.plot(x, y, label=alg, linewidth=4.0)
+
+ colormap = {'Available0':'#AAAAAA',
+ 'Available1':'#AAAAAA',
+ 'GCC0':'#80D000',
+ 'GCC1':'#008000',
+ 'GCC2':'#00F000',
+ 'GCC3':'#00B000',
+ 'GCC4':'#70B020',
+ 'NADA0':'#0000AA',
+ 'NADA1':'#A0A0FF',
+ 'NADA2':'#0000FF',
+ 'NADA3':'#C0A0FF',
+ 'NADA4':'#9060B0',}
+
+ flow_id = re.search(r'(\d+(,\d+)*)', series) # One or multiple ids.
+ key = alg + flow_id.group(1)
+
+ if key in colormap:
+ plt.setp(line, color=colormap[key])
+ elif alg == 'TCP':
+ plt.setp(line, color='#AAAAAA')
+ else:
+ plt.setp(line, color='#654321')
+
+ if alg.startswith('Available'):
+ plt.setp(line, linestyle='--')
+ plt.grid(True)
+
+ # x1, x2, y1, y2
+ _, x2, _, y2 = plt.axis()
+ if v.getYMax() >= 0:
+ y2 = v.getYMax()
+ plt.axis((0, x2, 0, y2))
+
+ if show_legend:
+ plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.40),
+ shadow=True, fontsize='large', ncol=len(v.samples))
+
+def main():
+ variables = [
+ ('Throughput_kbps', "Time (s)", "Throughput (kbps)", 1, 4000),
+ ('Delay_ms', "Time (s)", "One-way Delay (ms)", 2, 500),
+ ('Packet_Loss', "Time (s)", "Packet Loss Ratio", 3, 1.0),
+ # ('Sending_Estimate_kbps', "Time (s)", "Sending Estimate (kbps)",
+ # 4, 4000),
+ ]
+
+ var = []
+
+ # Create objects.
+ for variable in variables:
+ var.append(Variable(variable))
+
+ # Add samples to the objects.
+ for line in sys.stdin:
+ if line.startswith("[ RUN ]"):
+ test_name = re.search(r'\.(\w+)', line).group(1)
+ if line.startswith("PLOT"):
+ for v in var:
+ if v.getID() in line:
+ v.addSample(line)
+
+ matplotlib.rcParams.update({'font.size': 48/len(variables)})
+
+ # Plot variables.
+ fig = plt.figure()
+
+ # Offest and threshold on the same plot.
+ n = var[-1].getSubplot()
+ i = 0
+ for v in var:
+ ax = fig.add_subplot(n, 1, v.getSubplot())
+ plotVar(v, ax, i == 0, i == n - 1)
+ i += 1
+
+ if save_figure:
+ fig.savefig(test_name + ".png")
+ plt.show()
+
+if __name__ == '__main__':
+ main()
diff --git a/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.sh b/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.sh
new file mode 100755
index 0000000000..fd104a1704
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+# Copyright (c) 2013 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.
+
+# To set up in e.g. Eclipse, run a separate shell and pipe the output from the
+# test into this script.
+#
+# In Eclipse, that amounts to creating a Run Configuration which starts
+# "/bin/bash" with the arguments "-c [trunk_path]/out/Debug/modules_unittests
+# --gtest_filter=*BweTest* | [trunk_path]/webrtc/modules/
+# remote_bitrate_estimator/test/plot_dynamics.sh
+
+# This script supports multiple figures (windows), the figure is specified as an
+# identifier at the first argument after the PLOT command. Each figure has a
+# single y axis and a dual y axis mode. If any line specifies an axis by ending
+# with "#<axis number (1 or 2)>" two y axis will be used, the first will be
+# assumed to represent bitrate (in kbps) and the second will be assumed to
+# represent time deltas (in ms).
+
+log=$(</dev/stdin)
+
+# Plot dynamics.
+function gen_gnuplot_input {
+ colors=(a7001f 0a60c2 b2582b 21a66c d6604d 4393c3 f4a582 92c5de edcbb7 b1c5d0)
+ plots=$(echo "$log" | grep "^PLOT")
+ # Each figure corresponds to a separate plot window.
+ figures=($(echo "$plots" | cut -f 2 | sort | uniq))
+
+ for figure in "${figures[@]}" ; do
+ # Each data set corresponds to a plot line.
+ data_sets=$(echo "$plots" | grep "^PLOT.$figure" | cut -f 3 | sort | uniq)
+ # Lines can be scaled on the left (1) or right (2) axis.
+ linetypes=($(echo "$data_sets" | grep "#" | cut -d '#' -f 2 | \
+ cut -d '@' -f 1 | uniq))
+
+ # Set plot configurations.
+ echo "reset; "
+ echo "set terminal wxt $figure size 1440,900 font \"Arial,9\"; "
+ echo "set xlabel \"Seconds\"; "
+ if (( "${#linetypes[@]}" > "1" )); then
+ echo "set ylabel 'Bitrate (kbps)';" # Left side.
+ echo "set ytics nomirror;"
+ echo "set y2label 'Time delta (ms)';" # Right side.
+ echo "set y2tics nomirror;"
+ else
+ # Single axis (left side), set its label according to data.
+ y_label=$(echo "$data_sets" | grep "#" | cut -d '#' -f 1 | \
+ cut -d ' ' -f 1 | cut -d '/' -f 3 | sed 's/[0-9]/#/g' | \
+ cut -d '#' -f 3 | head -n 1 | sed 's/_/ /g')
+ echo "set ylabel \"$y_label\";"
+ fi
+
+ i=0
+ echo -n "plot "
+ for set in $data_sets ; do
+ (( i++ )) && echo -n ","
+ echo -n "'-' with "
+ echo -n "linespoints "
+ echo -n "ps 0.5 "
+ echo -n "lc rgbcolor \"#${colors[$(($i % 10))]}\" "
+ if (( "${#linetypes[@]}" > "1" )); then
+ # Multiple sets can have a same line plot.
+ linetype=$(echo "$set" | grep "#" | cut -d '#' -f 2 | cut -d '@' -f 1)
+ if (( "${#linetype}" > "0")); then
+ echo -n "axes x1y$linetype "
+ else
+ # If no line type is specified, but line types are used, we will
+ # default to scale on the left axis.
+ echo -n "axes x1y1 "
+ fi
+ fi
+ echo -n "title \"$set\" "
+ done
+ echo
+ for set in $data_sets ; do
+ echo "$log" | grep "^PLOT.$figure.$set" | cut -f 4,5
+ echo "e"
+ done
+ done
+}
+gen_gnuplot_input | gnuplot -persist