/* * Copyright (c) 2012 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/test/fake_network_pipe.h" #include #include #include #include #include "webrtc/call.h" #include "webrtc/system_wrappers/include/clock.h" namespace webrtc { const double kPi = 3.14159265; static int GaussianRandom(int mean_delay_ms, int standard_deviation_ms) { // Creating a Normal distribution variable from two independent uniform // variables based on the Box-Muller transform. double uniform1 = (rand() + 1.0) / (RAND_MAX + 1.0); // NOLINT double uniform2 = (rand() + 1.0) / (RAND_MAX + 1.0); // NOLINT return static_cast(mean_delay_ms + standard_deviation_ms * sqrt(-2 * log(uniform1)) * cos(2 * kPi * uniform2)); } static bool UniformLoss(int loss_percent) { int outcome = rand() % 100; return outcome < loss_percent; } class NetworkPacket { public: NetworkPacket(const uint8_t* data, size_t length, int64_t send_time, int64_t arrival_time) : data_(NULL), data_length_(length), send_time_(send_time), arrival_time_(arrival_time) { data_ = new uint8_t[length]; memcpy(data_, data, length); } ~NetworkPacket() { delete [] data_; } uint8_t* data() const { return data_; } size_t data_length() const { return data_length_; } int64_t send_time() const { return send_time_; } int64_t arrival_time() const { return arrival_time_; } void IncrementArrivalTime(int64_t extra_delay) { arrival_time_+= extra_delay; } private: // The packet data. uint8_t* data_; // Length of data_. size_t data_length_; // The time the packet was sent out on the network. const int64_t send_time_; // The time the packet should arrive at the reciver. int64_t arrival_time_; }; FakeNetworkPipe::FakeNetworkPipe(Clock* clock, const FakeNetworkPipe::Config& config) : clock_(clock), packet_receiver_(NULL), config_(config), dropped_packets_(0), sent_packets_(0), total_packet_delay_(0), next_process_time_(clock_->TimeInMilliseconds()) {} FakeNetworkPipe::~FakeNetworkPipe() { while (!capacity_link_.empty()) { delete capacity_link_.front(); capacity_link_.pop(); } while (!delay_link_.empty()) { delete delay_link_.front(); delay_link_.pop(); } } void FakeNetworkPipe::SetReceiver(PacketReceiver* receiver) { packet_receiver_ = receiver; } void FakeNetworkPipe::SetConfig(const FakeNetworkPipe::Config& config) { rtc::CritScope crit(&lock_); config_ = config; // Shallow copy of the struct. } void FakeNetworkPipe::SendPacket(const uint8_t* data, size_t data_length) { // A NULL packet_receiver_ means that this pipe will terminate the flow of // packets. if (packet_receiver_ == NULL) return; rtc::CritScope crit(&lock_); if (config_.queue_length_packets > 0 && capacity_link_.size() >= config_.queue_length_packets) { // Too many packet on the link, drop this one. ++dropped_packets_; return; } int64_t time_now = clock_->TimeInMilliseconds(); // Delay introduced by the link capacity. int64_t capacity_delay_ms = 0; if (config_.link_capacity_kbps > 0) capacity_delay_ms = data_length / (config_.link_capacity_kbps / 8); int64_t network_start_time = time_now; // Check if there already are packets on the link and change network start // time if there is. if (capacity_link_.size() > 0) network_start_time = capacity_link_.back()->arrival_time(); int64_t arrival_time = network_start_time + capacity_delay_ms; NetworkPacket* packet = new NetworkPacket(data, data_length, time_now, arrival_time); capacity_link_.push(packet); } float FakeNetworkPipe::PercentageLoss() { rtc::CritScope crit(&lock_); if (sent_packets_ == 0) return 0; return static_cast(dropped_packets_) / (sent_packets_ + dropped_packets_); } int FakeNetworkPipe::AverageDelay() { rtc::CritScope crit(&lock_); if (sent_packets_ == 0) return 0; return static_cast(total_packet_delay_ / static_cast(sent_packets_)); } void FakeNetworkPipe::Process() { int64_t time_now = clock_->TimeInMilliseconds(); std::queue packets_to_deliver; { rtc::CritScope crit(&lock_); // Check the capacity link first. while (capacity_link_.size() > 0 && time_now >= capacity_link_.front()->arrival_time()) { // Time to get this packet. NetworkPacket* packet = capacity_link_.front(); capacity_link_.pop(); // Packets are randomly dropped after being affected by the bottleneck. if (UniformLoss(config_.loss_percent)) { delete packet; continue; } // Add extra delay and jitter, but make sure the arrival time is not // earlier than the last packet in the queue. int extra_delay = GaussianRandom(config_.queue_delay_ms, config_.delay_standard_deviation_ms); if (delay_link_.size() > 0 && packet->arrival_time() + extra_delay < delay_link_.back()->arrival_time()) { extra_delay = delay_link_.back()->arrival_time() - packet->arrival_time(); } packet->IncrementArrivalTime(extra_delay); if (packet->arrival_time() < next_process_time_) next_process_time_ = packet->arrival_time(); delay_link_.push(packet); } // Check the extra delay queue. while (delay_link_.size() > 0 && time_now >= delay_link_.front()->arrival_time()) { // Deliver this packet. NetworkPacket* packet = delay_link_.front(); packets_to_deliver.push(packet); delay_link_.pop(); // |time_now| might be later than when the packet should have arrived, due // to NetworkProcess being called too late. For stats, use the time it // should have been on the link. total_packet_delay_ += packet->arrival_time() - packet->send_time(); } sent_packets_ += packets_to_deliver.size(); } while (!packets_to_deliver.empty()) { NetworkPacket* packet = packets_to_deliver.front(); packets_to_deliver.pop(); packet_receiver_->DeliverPacket(MediaType::ANY, packet->data(), packet->data_length(), PacketTime()); delete packet; } } int64_t FakeNetworkPipe::TimeUntilNextProcess() const { rtc::CritScope crit(&lock_); const int64_t kDefaultProcessIntervalMs = 30; if (capacity_link_.size() == 0 || delay_link_.size() == 0) return kDefaultProcessIntervalMs; return std::max(next_process_time_ - clock_->TimeInMilliseconds(), 0); } } // namespace webrtc