diff --git a/webrtc/video_engine/test/libvietest/include/fake_network_pipe.h b/webrtc/video_engine/test/libvietest/include/fake_network_pipe.h new file mode 100644 index 000000000..55ffde462 --- /dev/null +++ b/webrtc/video_engine/test/libvietest/include/fake_network_pipe.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_TEST_LIBVIETEST_INCLUDE_FAKE_NETWORK_PIPE_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_LIBVIETEST_INCLUDE_FAKE_NETWORK_PIPE_H_ + +#include + +#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class CriticalSectionWrapper; +class NetworkPacket; + +class PacketReceiver { + public: + // Delivers a new packet to the receive side of the network pipe. The + // implementor of PacketReceiver now owns the memory. + virtual void IncomingPacket(uint8_t* packet, int length) = 0; + virtual ~PacketReceiver() {} +}; + +// Class faking a network link. This is a simple and naive solution just faking +// capacity and adding an extra transport delay in addition to the capacity +// introduced delay. + +// TODO(mflodman) Add random and bursty packet loss. +class FakeNetworkPipe { + public: + FakeNetworkPipe(PacketReceiver* packet_receiver, + size_t queue_length, + int queue_delay_ms, + int link_capacity_kbps, + int loss_percent); + ~FakeNetworkPipe(); + + // Sends a new packet to the link. + void SendPacket(void* packet, int packet_length); + + // Processes the network queues and trigger PacketReceiver::IncomingPacket for + // packets ready to be delivered. + void NetworkProcess(); + + // Get statistics. + float PercentageLoss(); + int AverageDelay(); + int dropped_packets() { return dropped_packets_; } + int sent_packets() { return sent_packets_; } + + private: + PacketReceiver* packet_receiver_; + scoped_ptr link_cs_; + std::queue capacity_link_; + std::queue delay_link_; + + // Link configuration. + const size_t queue_length_; + const int queue_delay_ms_; + const int link_capacity_bytes_ms_; // In bytes per ms. + + const int loss_percent_; + + // Statistics. + int dropped_packets_; + int sent_packets_; + int total_packet_delay_; + + DISALLOW_COPY_AND_ASSIGN(FakeNetworkPipe); +}; + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_LIBVIETEST_INCLUDE_FAKE_NETWORK_PIPE_H_ diff --git a/webrtc/video_engine/test/libvietest/libvietest.gypi b/webrtc/video_engine/test/libvietest/libvietest.gypi index 6c74ec0be..fdfb292d2 100644 --- a/webrtc/video_engine/test/libvietest/libvietest.gypi +++ b/webrtc/video_engine/test/libvietest/libvietest.gypi @@ -37,12 +37,14 @@ 'helpers/vie_to_file_renderer.cc', # Testbed classes + 'include/fake_network_pipe.h', 'include/tb_capture_device.h', 'include/tb_external_transport.h', 'include/tb_I420_codec.h', 'include/tb_interfaces.h', 'include/tb_video_channel.h', + 'testbed/fake_network_pipe.cc', 'testbed/tb_capture_device.cc', 'testbed/tb_external_transport.cc', 'testbed/tb_I420_codec.cc', @@ -51,4 +53,26 @@ ], }, ], + 'conditions': [ + ['include_tests==1', { + 'targets': [ + { + 'target_name': 'libvietest_unittests', + 'type': 'executable', + 'dependencies': [ + 'libvietest', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/testing/gmock.gyp:gmock', + '<(webrtc_root)/test/test.gyp:test_support_main', + ], + 'include_dirs': [ + 'include/', + ], + 'sources': [ + 'testbed/fake_network_pipe_unittest.cc', + ], + }, + ], #targets + }], # include_tests + ], # conditions } diff --git a/webrtc/video_engine/test/libvietest/testbed/fake_network_pipe.cc b/webrtc/video_engine/test/libvietest/testbed/fake_network_pipe.cc new file mode 100644 index 000000000..c871e5c6d --- /dev/null +++ b/webrtc/video_engine/test/libvietest/testbed/fake_network_pipe.cc @@ -0,0 +1,154 @@ +/* + * 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/video_engine/test/libvietest/include/fake_network_pipe.h" + +#include +#include + +#include "webrtc/system_wrappers/interface/critical_section_wrapper.h" +#include "webrtc/system_wrappers/interface/tick_util.h" + +namespace webrtc { + +const int kNetworkProcessMaxWaitTime = 10; + +class NetworkPacket { + public: + NetworkPacket(void* data, int 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() {} + + void ReleaseData() { + delete [] data_; + data_ = NULL; + } + uint8_t* data() const { return data_; } + int 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_. + int 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(PacketReceiver* packet_receiver, + size_t queue_length, int queue_delay_ms, + int link_capacity_kbps, int loss_percent) + : packet_receiver_(packet_receiver), + link_cs_(CriticalSectionWrapper::CreateCriticalSection()), + queue_length_(queue_length), + queue_delay_ms_(queue_delay_ms), + link_capacity_bytes_ms_(link_capacity_kbps / 8), + loss_percent_(loss_percent), + dropped_packets_(0), + sent_packets_(0), + total_packet_delay_(0) { + assert(link_capacity_bytes_ms_ > 0); +} + +FakeNetworkPipe::~FakeNetworkPipe() { +} + +void FakeNetworkPipe::SendPacket(void* data, int data_length) { + CriticalSectionScoped cs(link_cs_.get()); + if (capacity_link_.size() >= queue_length_) { + // Too many packet on the link, drop this one. + ++dropped_packets_; + return; + } + + int64_t time_now = TickTime::MillisecondTimestamp(); + + // Delay introduced by the link capacity. + int64_t capacity_delay_ms = data_length / link_capacity_bytes_ms_; + 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() { + CriticalSectionScoped cs(link_cs_.get()); + if (sent_packets_ == 0) + return 0; + + return static_cast(dropped_packets_) / + (sent_packets_ + dropped_packets_); +} + +int FakeNetworkPipe::AverageDelay() { + CriticalSectionScoped cs(link_cs_.get()); + if (sent_packets_ == 0) + return 0; + + return total_packet_delay_ / sent_packets_; +} + +void FakeNetworkPipe::NetworkProcess() { + CriticalSectionScoped cs(link_cs_.get()); + if (capacity_link_.size() == 0 && delay_link_.size() == 0) + return; + + int64_t time_now = TickTime::MillisecondTimestamp(); + + // 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(); + + // Add the packet to the extra delay queue. + packet->IncrementArrivalTime(queue_delay_ms_); + 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(); + delay_link_.pop(); + packet_receiver_->IncomingPacket(packet->data(), packet->data_length()); + ++sent_packets_; + + // |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(); + delete packet; + } +} + +} // namespace webrtc diff --git a/webrtc/video_engine/test/libvietest/testbed/fake_network_pipe_unittest.cc b/webrtc/video_engine/test/libvietest/testbed/fake_network_pipe_unittest.cc new file mode 100644 index 000000000..845cf7000 --- /dev/null +++ b/webrtc/video_engine/test/libvietest/testbed/fake_network_pipe_unittest.cc @@ -0,0 +1,205 @@ +/* + * 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 +#include + +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/system_wrappers/interface/tick_util.h" +#include "webrtc/video_engine/test/libvietest/include/fake_network_pipe.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Return; +using ::testing::Invoke; + +namespace webrtc { + +class MockReceiver : public PacketReceiver { + public: + MockReceiver() {} + virtual ~MockReceiver() {} + + void IncomingPacket(uint8_t* data, int length) { + IncomingData(data, length); + delete [] data; + } + + MOCK_METHOD2(IncomingData, void(uint8_t*, int)); +}; + +class FakeNetworkPipeTest : public ::testing::Test { + protected: + virtual void SetUp() { + TickTime::UseFakeClock(12345); + receiver_.reset(new MockReceiver()); + } + + virtual void TearDown() { + } + + void SendPackets(FakeNetworkPipe* pipe, int number_packets, int kPacketSize) { + scoped_array packet(new uint8_t[kPacketSize]); + for (int i = 0; i < number_packets; ++i) { + pipe->SendPacket(packet.get(), kPacketSize); + } + } + + int PacketTimeMs(int capacity_kbps, int kPacketSize) { + return 8 * kPacketSize / capacity_kbps; + } + + scoped_ptr receiver_; +}; + +void DeleteMemory(uint8_t* data, int length) { delete [] data; } + +// Test the capacity link and verify we get as many packets as we expect. +TEST_F(FakeNetworkPipeTest, CapacityTest) { + const int kQueueLength = 20; + const int kNetworkDelayMs = 0; + const int kLinkCapacityKbps = 80; + const int kLossPercent = 0; + scoped_ptr pipe(new FakeNetworkPipe(receiver_.get(), + kQueueLength, + kNetworkDelayMs, + kLinkCapacityKbps, + kLossPercent)); + + // Add 10 packets of 1000 bytes, = 80 kb, and verify it takes one second to + // get through the pipe. + const int kNumPackets = 10; + const int kPacketSize = 1000; + SendPackets(pipe.get(), kNumPackets , kPacketSize); + + // Time to get one packet through the link. + const int kPacketTimeMs = PacketTimeMs(kLinkCapacityKbps, kPacketSize); + + // Time haven't increased yet, so we souldn't get any packets. + EXPECT_CALL(*receiver_, IncomingData(_, _)) + .Times(0); + pipe->NetworkProcess(); + + // Advance enough time to release one packet. + TickTime::AdvanceFakeClock(kPacketTimeMs); + EXPECT_CALL(*receiver_, IncomingData(_, _)) + .Times(1); + pipe->NetworkProcess(); + + // Release all but one packet + TickTime::AdvanceFakeClock(9 * kPacketTimeMs - 1); + EXPECT_CALL(*receiver_, IncomingData(_, _)) + .Times(8); + pipe->NetworkProcess(); + + // And the last one. + TickTime::AdvanceFakeClock(1); + EXPECT_CALL(*receiver_, IncomingData(_, _)) + .Times(1); + pipe->NetworkProcess(); +} + +// Test the extra network delay. +TEST_F(FakeNetworkPipeTest, ExtraDelayTest) { + const int kQueueLength = 20; + const int kNetworkDelayMs = 100; + const int kLinkCapacityKbps = 80; + const int kLossPercent = 0; + scoped_ptr pipe(new FakeNetworkPipe(receiver_.get(), + kQueueLength, + kNetworkDelayMs, + kLinkCapacityKbps, + kLossPercent)); + + const int kNumPackets = 2; + const int kPacketSize = 1000; + SendPackets(pipe.get(), kNumPackets , kPacketSize); + + // Time to get one packet through the link. + const int kPacketTimeMs = PacketTimeMs(kLinkCapacityKbps, kPacketSize); + + // Increase more than kPacketTimeMs, but not more than the extra delay. + TickTime::AdvanceFakeClock(kPacketTimeMs); + EXPECT_CALL(*receiver_, IncomingData(_, _)) + .Times(0); + pipe->NetworkProcess(); + + // Advance the network delay to get the first packet. + TickTime::AdvanceFakeClock(kNetworkDelayMs); + EXPECT_CALL(*receiver_, IncomingData(_, _)) + .Times(1); + pipe->NetworkProcess(); + + // Advance one more kPacketTimeMs to get the last packet. + TickTime::AdvanceFakeClock(kPacketTimeMs); + EXPECT_CALL(*receiver_, IncomingData(_, _)) + .Times(1); + pipe->NetworkProcess(); +} + +// Test the number of buffers and packets are dropped when sending too many +// packets too quickly. +TEST_F(FakeNetworkPipeTest, QueueLengthTest) { + const int kQueueLength = 2; + const int kNetworkDelayMs = 0; + const int kLinkCapacityKbps = 80; + const int kLossPercent = 0; + scoped_ptr pipe(new FakeNetworkPipe(receiver_.get(), + kQueueLength, + kNetworkDelayMs, + kLinkCapacityKbps, + kLossPercent)); + + const int kPacketSize = 1000; + const int kPacketTimeMs = PacketTimeMs(kLinkCapacityKbps, kPacketSize); + + // Send three packets and verify only 2 are delivered. + SendPackets(pipe.get(), 3, kPacketSize); + + // Increase time enough to deliver all three packets, verify only two are + // delivered. + TickTime::AdvanceFakeClock(3 * kPacketTimeMs); + EXPECT_CALL(*receiver_, IncomingData(_, _)) + .Times(2); + pipe->NetworkProcess(); +} + +// Test we get statistics as expected. +TEST_F(FakeNetworkPipeTest, StatisticsTest) { + const int kQueueLength = 2; + const int kNetworkDelayMs = 20; + const int kLinkCapacityKbps = 80; + const int kLossPercent = 0; + scoped_ptr pipe(new FakeNetworkPipe(receiver_.get(), + kQueueLength, + kNetworkDelayMs, + kLinkCapacityKbps, + kLossPercent)); + + const int kPacketSize = 1000; + const int kPacketTimeMs = PacketTimeMs(kLinkCapacityKbps, kPacketSize); + + // Send three packets and verify only 2 are delivered. + SendPackets(pipe.get(), 3, kPacketSize); + TickTime::AdvanceFakeClock(3 * kPacketTimeMs + kNetworkDelayMs); + + EXPECT_CALL(*receiver_, IncomingData(_, _)) + .Times(2); + pipe->NetworkProcess(); + + // Packet 1: kPacketTimeMs + kNetworkDelayMs, packet 2: 2 * kPacketTimeMs + + // kNetworkDelayMs => 170 ms average. + EXPECT_EQ(pipe->AverageDelay(), 170); + EXPECT_EQ(pipe->sent_packets(), 2); + EXPECT_EQ(pipe->dropped_packets(), 1); + EXPECT_EQ(pipe->PercentageLoss(), 1/3.f); +} + +} // namespace webrtc