diff options
Diffstat (limited to 'webrtc/test')
134 files changed, 19023 insertions, 0 deletions
diff --git a/webrtc/test/BUILD.gn b/webrtc/test/BUILD.gn new file mode 100644 index 0000000000..ed1dc75078 --- /dev/null +++ b/webrtc/test/BUILD.gn @@ -0,0 +1,107 @@ +# Copyright (c) 2014 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. + +# TODO(kjellander): Convert the rest of the test.gyp targets and put here. + +source_set("test") { + testonly = true + + deps = [ + ":field_trial", + ":test_support", + ":test_support_main", + ] +} + +source_set("field_trial") { + sources = [ + "field_trial.cc", + "field_trial.h", + ] + + deps = [ + "..:webrtc_common", + "../system_wrappers", + ] + + configs += [ "..:common_config" ] + public_configs = [ "..:common_inherited_config" ] +} + +source_set("histogram") { + sources = [ + "histogram.cc", + "histogram.h", + ] + + deps = [ + "..:webrtc_common", + "../system_wrappers", + ] + + configs += [ "..:common_config" ] + public_configs = [ "..:common_inherited_config" ] +} + +source_set("test_support") { + testonly = true + + sources = [ + "testsupport/fileutils.cc", + "testsupport/fileutils.h", + "testsupport/frame_reader.cc", + "testsupport/frame_reader.h", + "testsupport/frame_writer.cc", + "testsupport/frame_writer.h", + "testsupport/gtest_disable.h", + "testsupport/mock/mock_frame_reader.h", + "testsupport/mock/mock_frame_writer.h", + "testsupport/packet_reader.cc", + "testsupport/packet_reader.h", + "testsupport/perf_test.cc", + "testsupport/perf_test.h", + "testsupport/trace_to_stderr.cc", + "testsupport/trace_to_stderr.h", + ] + + deps = [ + "//testing/gmock", + "//testing/gtest", + "..:gtest_prod", + "../system_wrappers", + ] + + if (is_android) { + deps += [ "//base:base" ] + } + + configs += [ "..:common_config" ] + public_configs = [ "..:common_inherited_config" ] +} + +source_set("test_support_main") { + testonly = true + + sources = [ + "run_all_unittests.cc", + "test_suite.cc", + "test_suite.h", + ] + + deps = [ + ":field_trial", + ":histogram", + ":test_support", + "//testing/gmock", + "//testing/gtest", + "//third_party/gflags", + ] + + configs += [ "..:common_config" ] + public_configs = [ "..:common_inherited_config" ] +} diff --git a/webrtc/test/OWNERS b/webrtc/test/OWNERS new file mode 100644 index 0000000000..98a0d4d1b5 --- /dev/null +++ b/webrtc/test/OWNERS @@ -0,0 +1,10 @@ +kjellander@webrtc.org +pbos@webrtc.org +phoglund@webrtc.org + +per-file *.isolate=kjellander@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gyp=* +per-file *.gypi=* diff --git a/webrtc/test/call_test.cc b/webrtc/test/call_test.cc new file mode 100644 index 0000000000..0a8b686974 --- /dev/null +++ b/webrtc/test/call_test.cc @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2014 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/call_test.h" +#include "webrtc/test/encoder_settings.h" + +namespace webrtc { +namespace test { + +namespace { +const int kVideoRotationRtpExtensionId = 4; +} + +CallTest::CallTest() + : clock_(Clock::GetRealTimeClock()), + send_config_(nullptr), + send_stream_(NULL), + fake_encoder_(clock_) { +} + +CallTest::~CallTest() { +} + +void CallTest::RunBaseTest(BaseTest* test, + const FakeNetworkPipe::Config& config) { + CreateSenderCall(test->GetSenderCallConfig()); + if (test->ShouldCreateReceivers()) + CreateReceiverCall(test->GetReceiverCallConfig()); + send_transport_.reset(new PacketTransport( + sender_call_.get(), test, test::PacketTransport::kSender, config)); + receive_transport_.reset(new PacketTransport( + nullptr, test, test::PacketTransport::kReceiver, config)); + test->OnTransportsCreated(send_transport_.get(), receive_transport_.get()); + test->OnCallsCreated(sender_call_.get(), receiver_call_.get()); + + if (test->ShouldCreateReceivers()) { + send_transport_->SetReceiver(receiver_call_->Receiver()); + receive_transport_->SetReceiver(sender_call_->Receiver()); + } else { + // Sender-only call delivers to itself. + send_transport_->SetReceiver(sender_call_->Receiver()); + receive_transport_->SetReceiver(nullptr); + } + + CreateSendConfig(test->GetNumStreams(), send_transport_.get()); + if (test->ShouldCreateReceivers()) { + CreateMatchingReceiveConfigs(receive_transport_.get()); + } + test->ModifyConfigs(&send_config_, &receive_configs_, &encoder_config_); + CreateStreams(); + test->OnStreamsCreated(send_stream_, receive_streams_); + + CreateFrameGeneratorCapturer(); + test->OnFrameGeneratorCapturerCreated(frame_generator_capturer_.get()); + + Start(); + test->PerformTest(); + send_transport_->StopSending(); + receive_transport_->StopSending(); + Stop(); + + DestroyStreams(); +} + +void CallTest::Start() { + send_stream_->Start(); + for (size_t i = 0; i < receive_streams_.size(); ++i) + receive_streams_[i]->Start(); + if (frame_generator_capturer_.get() != NULL) + frame_generator_capturer_->Start(); +} + +void CallTest::Stop() { + if (frame_generator_capturer_.get() != NULL) + frame_generator_capturer_->Stop(); + for (size_t i = 0; i < receive_streams_.size(); ++i) + receive_streams_[i]->Stop(); + send_stream_->Stop(); +} + +void CallTest::CreateCalls(const Call::Config& sender_config, + const Call::Config& receiver_config) { + CreateSenderCall(sender_config); + CreateReceiverCall(receiver_config); +} + +void CallTest::CreateSenderCall(const Call::Config& config) { + sender_call_.reset(Call::Create(config)); +} + +void CallTest::CreateReceiverCall(const Call::Config& config) { + receiver_call_.reset(Call::Create(config)); +} + +void CallTest::DestroyCalls() { + sender_call_.reset(nullptr); + receiver_call_.reset(nullptr); +} + +void CallTest::CreateSendConfig(size_t num_streams, + Transport* send_transport) { + assert(num_streams <= kNumSsrcs); + send_config_ = VideoSendStream::Config(send_transport); + send_config_.encoder_settings.encoder = &fake_encoder_; + send_config_.encoder_settings.payload_name = "FAKE"; + send_config_.encoder_settings.payload_type = kFakeSendPayloadType; + send_config_.rtp.extensions.push_back( + RtpExtension(RtpExtension::kAbsSendTime, kAbsSendTimeExtensionId)); + encoder_config_.streams = test::CreateVideoStreams(num_streams); + for (size_t i = 0; i < num_streams; ++i) + send_config_.rtp.ssrcs.push_back(kSendSsrcs[i]); + send_config_.rtp.extensions.push_back( + RtpExtension(RtpExtension::kVideoRotation, kVideoRotationRtpExtensionId)); +} + +void CallTest::CreateMatchingReceiveConfigs( + Transport* rtcp_send_transport) { + assert(!send_config_.rtp.ssrcs.empty()); + assert(receive_configs_.empty()); + assert(allocated_decoders_.empty()); + VideoReceiveStream::Config config(rtcp_send_transport); + config.rtp.remb = true; + config.rtp.local_ssrc = kReceiverLocalSsrc; + for (const RtpExtension& extension : send_config_.rtp.extensions) + config.rtp.extensions.push_back(extension); + for (size_t i = 0; i < send_config_.rtp.ssrcs.size(); ++i) { + VideoReceiveStream::Decoder decoder = + test::CreateMatchingDecoder(send_config_.encoder_settings); + allocated_decoders_.push_back(decoder.decoder); + config.decoders.clear(); + config.decoders.push_back(decoder); + config.rtp.remote_ssrc = send_config_.rtp.ssrcs[i]; + receive_configs_.push_back(config); + } +} + +void CallTest::CreateFrameGeneratorCapturer() { + VideoStream stream = encoder_config_.streams.back(); + frame_generator_capturer_.reset( + test::FrameGeneratorCapturer::Create(send_stream_->Input(), + stream.width, + stream.height, + stream.max_framerate, + clock_)); +} + +void CallTest::CreateStreams() { + assert(send_stream_ == NULL); + assert(receive_streams_.empty()); + + send_stream_ = + sender_call_->CreateVideoSendStream(send_config_, encoder_config_); + + for (size_t i = 0; i < receive_configs_.size(); ++i) { + receive_streams_.push_back( + receiver_call_->CreateVideoReceiveStream(receive_configs_[i])); + } +} + +void CallTest::DestroyStreams() { + if (send_stream_ != NULL) + sender_call_->DestroyVideoSendStream(send_stream_); + send_stream_ = NULL; + for (size_t i = 0; i < receive_streams_.size(); ++i) + receiver_call_->DestroyVideoReceiveStream(receive_streams_[i]); + receive_streams_.clear(); + allocated_decoders_.clear(); +} + +const unsigned int CallTest::kDefaultTimeoutMs = 30 * 1000; +const unsigned int CallTest::kLongTimeoutMs = 120 * 1000; +const uint8_t CallTest::kSendPayloadType = 100; +const uint8_t CallTest::kFakeSendPayloadType = 125; +const uint8_t CallTest::kSendRtxPayloadType = 98; +const uint8_t CallTest::kRedPayloadType = 118; +const uint8_t CallTest::kRtxRedPayloadType = 99; +const uint8_t CallTest::kUlpfecPayloadType = 119; +const uint32_t CallTest::kSendRtxSsrcs[kNumSsrcs] = {0xBADCAFD, 0xBADCAFE, + 0xBADCAFF}; +const uint32_t CallTest::kSendSsrcs[kNumSsrcs] = {0xC0FFED, 0xC0FFEE, 0xC0FFEF}; +const uint32_t CallTest::kReceiverLocalSsrc = 0x123456; +const int CallTest::kNackRtpHistoryMs = 1000; + +BaseTest::BaseTest(unsigned int timeout_ms) : RtpRtcpObserver(timeout_ms) { +} + +BaseTest::~BaseTest() { +} + +Call::Config BaseTest::GetSenderCallConfig() { + return Call::Config(); +} + +Call::Config BaseTest::GetReceiverCallConfig() { + return Call::Config(); +} + +void BaseTest::OnCallsCreated(Call* sender_call, Call* receiver_call) { +} + +void BaseTest::OnTransportsCreated(PacketTransport* send_transport, + PacketTransport* receive_transport) {} + +size_t BaseTest::GetNumStreams() const { + return 1; +} + +void BaseTest::ModifyConfigs( + VideoSendStream::Config* send_config, + std::vector<VideoReceiveStream::Config>* receive_configs, + VideoEncoderConfig* encoder_config) { +} + +void BaseTest::OnStreamsCreated( + VideoSendStream* send_stream, + const std::vector<VideoReceiveStream*>& receive_streams) { +} + +void BaseTest::OnFrameGeneratorCapturerCreated( + FrameGeneratorCapturer* frame_generator_capturer) { +} + +SendTest::SendTest(unsigned int timeout_ms) : BaseTest(timeout_ms) { +} + +bool SendTest::ShouldCreateReceivers() const { + return false; +} + +EndToEndTest::EndToEndTest(unsigned int timeout_ms) : BaseTest(timeout_ms) { +} + +bool EndToEndTest::ShouldCreateReceivers() const { + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/call_test.h b/webrtc/test/call_test.h new file mode 100644 index 0000000000..cf024d9c65 --- /dev/null +++ b/webrtc/test/call_test.h @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2014 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_TEST_COMMON_CALL_TEST_H_ +#define WEBRTC_TEST_COMMON_CALL_TEST_H_ + +#include <vector> + +#include "webrtc/call.h" +#include "webrtc/system_wrappers/include/scoped_vector.h" +#include "webrtc/test/fake_decoder.h" +#include "webrtc/test/fake_encoder.h" +#include "webrtc/test/frame_generator_capturer.h" +#include "webrtc/test/rtp_rtcp_observer.h" + +namespace webrtc { +namespace test { + +class BaseTest; + +class CallTest : public ::testing::Test { + public: + CallTest(); + ~CallTest(); + + static const size_t kNumSsrcs = 3; + + static const unsigned int kDefaultTimeoutMs; + static const unsigned int kLongTimeoutMs; + static const uint8_t kSendPayloadType; + static const uint8_t kSendRtxPayloadType; + static const uint8_t kFakeSendPayloadType; + static const uint8_t kRedPayloadType; + static const uint8_t kRtxRedPayloadType; + static const uint8_t kUlpfecPayloadType; + static const uint32_t kSendRtxSsrcs[kNumSsrcs]; + static const uint32_t kSendSsrcs[kNumSsrcs]; + static const uint32_t kReceiverLocalSsrc; + static const int kNackRtpHistoryMs; + + protected: + void RunBaseTest(BaseTest* test, const FakeNetworkPipe::Config& config); + + void CreateCalls(const Call::Config& sender_config, + const Call::Config& receiver_config); + void CreateSenderCall(const Call::Config& config); + void CreateReceiverCall(const Call::Config& config); + void DestroyCalls(); + + void CreateSendConfig(size_t num_streams, Transport* send_transport); + void CreateMatchingReceiveConfigs(Transport* rtcp_send_transport); + + void CreateFrameGeneratorCapturer(); + + void CreateStreams(); + void Start(); + void Stop(); + void DestroyStreams(); + + Clock* const clock_; + + rtc::scoped_ptr<Call> sender_call_; + rtc::scoped_ptr<PacketTransport> send_transport_; + VideoSendStream::Config send_config_; + VideoEncoderConfig encoder_config_; + VideoSendStream* send_stream_; + + rtc::scoped_ptr<Call> receiver_call_; + rtc::scoped_ptr<PacketTransport> receive_transport_; + std::vector<VideoReceiveStream::Config> receive_configs_; + std::vector<VideoReceiveStream*> receive_streams_; + + rtc::scoped_ptr<test::FrameGeneratorCapturer> frame_generator_capturer_; + test::FakeEncoder fake_encoder_; + ScopedVector<VideoDecoder> allocated_decoders_; +}; + +class BaseTest : public RtpRtcpObserver { + public: + explicit BaseTest(unsigned int timeout_ms); + virtual ~BaseTest(); + + virtual void PerformTest() = 0; + virtual bool ShouldCreateReceivers() const = 0; + + virtual size_t GetNumStreams() const; + + virtual Call::Config GetSenderCallConfig(); + virtual Call::Config GetReceiverCallConfig(); + virtual void OnCallsCreated(Call* sender_call, Call* receiver_call); + virtual void OnTransportsCreated(PacketTransport* send_transport, + PacketTransport* receive_transport); + + virtual void ModifyConfigs( + VideoSendStream::Config* send_config, + std::vector<VideoReceiveStream::Config>* receive_configs, + VideoEncoderConfig* encoder_config); + virtual void OnStreamsCreated( + VideoSendStream* send_stream, + const std::vector<VideoReceiveStream*>& receive_streams); + + virtual void OnFrameGeneratorCapturerCreated( + FrameGeneratorCapturer* frame_generator_capturer); +}; + +class SendTest : public BaseTest { + public: + explicit SendTest(unsigned int timeout_ms); + + bool ShouldCreateReceivers() const override; +}; + +class EndToEndTest : public BaseTest { + public: + explicit EndToEndTest(unsigned int timeout_ms); + + bool ShouldCreateReceivers() const override; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_COMMON_CALL_TEST_H_ diff --git a/webrtc/test/channel_transport/OWNERS b/webrtc/test/channel_transport/OWNERS new file mode 100644 index 0000000000..1acb902bc0 --- /dev/null +++ b/webrtc/test/channel_transport/OWNERS @@ -0,0 +1,2 @@ +henrikg@webrtc.org + diff --git a/webrtc/test/channel_transport/channel_transport.cc b/webrtc/test/channel_transport/channel_transport.cc new file mode 100644 index 0000000000..25eb59d887 --- /dev/null +++ b/webrtc/test/channel_transport/channel_transport.cc @@ -0,0 +1,83 @@ +/* + * 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/test/channel_transport/include/channel_transport.h" + +#include <stdio.h> + +#if !defined(WEBRTC_ANDROID) && !defined(WEBRTC_IOS) +#include "testing/gtest/include/gtest/gtest.h" +#endif +#include "webrtc/test/channel_transport/udp_transport.h" +#include "webrtc/video_engine/vie_defines.h" +#include "webrtc/voice_engine/include/voe_network.h" + +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) +#undef NDEBUG +#include <assert.h> +#endif + +namespace webrtc { +namespace test { + +VoiceChannelTransport::VoiceChannelTransport(VoENetwork* voe_network, + int channel) + : channel_(channel), + voe_network_(voe_network) { + uint8_t socket_threads = 1; + socket_transport_ = UdpTransport::Create(channel, socket_threads); + int registered = voe_network_->RegisterExternalTransport(channel, + *socket_transport_); +#if !defined(WEBRTC_ANDROID) && !defined(WEBRTC_IOS) + EXPECT_EQ(0, registered); +#else + assert(registered == 0); +#endif +} + +VoiceChannelTransport::~VoiceChannelTransport() { + voe_network_->DeRegisterExternalTransport(channel_); + UdpTransport::Destroy(socket_transport_); +} + +void VoiceChannelTransport::IncomingRTPPacket( + const int8_t* incoming_rtp_packet, + const size_t packet_length, + const char* /*from_ip*/, + const uint16_t /*from_port*/) { + voe_network_->ReceivedRTPPacket( + channel_, incoming_rtp_packet, packet_length, PacketTime()); +} + +void VoiceChannelTransport::IncomingRTCPPacket( + const int8_t* incoming_rtcp_packet, + const size_t packet_length, + const char* /*from_ip*/, + const uint16_t /*from_port*/) { + voe_network_->ReceivedRTCPPacket(channel_, incoming_rtcp_packet, + packet_length); +} + +int VoiceChannelTransport::SetLocalReceiver(uint16_t rtp_port) { + int return_value = socket_transport_->InitializeReceiveSockets(this, + rtp_port); + if (return_value == 0) { + return socket_transport_->StartReceiving(kViENumReceiveSocketBuffers); + } + return return_value; +} + +int VoiceChannelTransport::SetSendDestination(const char* ip_address, + uint16_t rtp_port) { + return socket_transport_->InitializeSendSockets(ip_address, rtp_port); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/channel_transport/include/channel_transport.h b/webrtc/test/channel_transport/include/channel_transport.h new file mode 100644 index 0000000000..8b84517de0 --- /dev/null +++ b/webrtc/test/channel_transport/include/channel_transport.h @@ -0,0 +1,56 @@ +/* + * 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_TEST_CHANNEL_TRANSPORT_INCLUDE_CHANNEL_TRANSPORT_H_ +#define WEBRTC_TEST_CHANNEL_TRANSPORT_INCLUDE_CHANNEL_TRANSPORT_H_ + +#include "webrtc/test/channel_transport/udp_transport.h" + +namespace webrtc { + +class VoENetwork; + +namespace test { + +// Helper class for VoiceEngine tests. +class VoiceChannelTransport : public UdpTransportData { + public: + VoiceChannelTransport(VoENetwork* voe_network, int channel); + + virtual ~VoiceChannelTransport(); + + // Start implementation of UdpTransportData. + void IncomingRTPPacket(const int8_t* incoming_rtp_packet, + const size_t packet_length, + const char* /*from_ip*/, + const uint16_t /*from_port*/) override; + + void IncomingRTCPPacket(const int8_t* incoming_rtcp_packet, + const size_t packet_length, + const char* /*from_ip*/, + const uint16_t /*from_port*/) override; + // End implementation of UdpTransportData. + + // Specifies the ports to receive RTP packets on. + int SetLocalReceiver(uint16_t rtp_port); + + // Specifies the destination port and IP address for a specified channel. + int SetSendDestination(const char* ip_address, uint16_t rtp_port); + + private: + int channel_; + VoENetwork* voe_network_; + UdpTransport* socket_transport_; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_CHANNEL_TRANSPORT_INCLUDE_CHANNEL_TRANSPORT_H_ diff --git a/webrtc/test/channel_transport/traffic_control_win.cc b/webrtc/test/channel_transport/traffic_control_win.cc new file mode 100644 index 0000000000..3584359758 --- /dev/null +++ b/webrtc/test/channel_transport/traffic_control_win.cc @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2011 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/channel_transport/traffic_control_win.h" + +#include <assert.h> + +#include "webrtc/system_wrappers/include/trace.h" + +namespace webrtc { +namespace test { + +TrafficControlWindows* TrafficControlWindows::instance = NULL; +uint32_t TrafficControlWindows::refCounter = 0; + +TrafficControlWindows::TrafficControlWindows(const int32_t id) +{ +} + +TrafficControlWindows* TrafficControlWindows::GetInstance( + const int32_t id) +{ + if(instance != NULL) + { + WEBRTC_TRACE( + kTraceDebug, + kTraceTransport, + id, + "TrafficControlWindows - Returning already created object"); + refCounter++; + return instance; + } + + WEBRTC_TRACE(kTraceMemory, kTraceTransport, id, + "TrafficControlWindows - Creating new object"); + instance = new TrafficControlWindows(id); + if(instance == NULL) + { + WEBRTC_TRACE(kTraceMemory, kTraceTransport, id, + "TrafficControlWindows - Error allocating memory"); + return NULL; + } + + instance->tcRegister = NULL; + instance->tcDeregister = NULL; + + instance->tcEnumerate = NULL; + instance->tcOpenInterface = NULL; + instance->tcCloseInterface = NULL; + + instance->tcAddFlow = NULL; + instance->tcDeleteFlow = NULL; + + instance->tcAddFilter = NULL; + instance->tcDeleteFilter = NULL; + + HMODULE trafficLib = LoadLibrary(TEXT("traffic.dll")); + if(trafficLib == NULL) + { + WEBRTC_TRACE( + kTraceWarning, + kTraceTransport, + id, + "TrafficControlWindows - No QOS support, LoadLibrary returned NULL,\ + last error: %d\n", + GetLastError()); + delete instance; + instance = NULL; + return NULL; + } + + instance->tcRegister = (registerFn)GetProcAddress(trafficLib, + "TcRegisterClient"); + instance->tcDeregister = (deregisterFn)GetProcAddress(trafficLib, + "TcDeregisterClient"); + instance->tcEnumerate = (enumerateFn)GetProcAddress( + trafficLib, + "TcEnumerateInterfaces"); + instance->tcOpenInterface = (openInterfaceFn)GetProcAddress( + trafficLib, + "TcOpenInterfaceW"); + instance->tcCloseInterface = (closeInterfaceFn)GetProcAddress( + trafficLib, + "TcCloseInterface"); + instance->tcAddFlow = (flowAddFn)GetProcAddress(trafficLib, + "TcAddFlow"); + instance->tcDeleteFlow = (flowDeleteFn)GetProcAddress(trafficLib, + "TcDeleteFlow"); + + instance->tcAddFilter = (filterAddFn)GetProcAddress(trafficLib, + "TcAddFilter"); + instance->tcDeleteFilter = (filterDeleteFn)GetProcAddress(trafficLib, + "TcDeleteFilter"); + + if(instance->tcRegister == NULL || + instance->tcDeregister == NULL || + instance->tcEnumerate == NULL || + instance->tcOpenInterface == NULL || + instance->tcCloseInterface == NULL || + instance->tcAddFlow == NULL || + instance->tcAddFilter == NULL || + instance->tcDeleteFlow == NULL || + instance->tcDeleteFilter == NULL) + { + delete instance; + instance = NULL; + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + id, + "TrafficControlWindows - Could not find function pointer for\ + traffic control functions"); + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + id, + "Tcregister : %x, tcDeregister: %x, tcEnumerate: %x,\ + tcOpenInterface: %x, tcCloseInterface: %x, tcAddFlow: %x, tcAddFilter: %x,\ + tcDeleteFlow: %x, tcDeleteFilter: %x", + instance->tcRegister, + instance->tcDeregister, + instance->tcEnumerate, + instance->tcOpenInterface, + instance->tcCloseInterface, + instance->tcAddFlow, + instance->tcAddFilter, + instance->tcDeleteFlow, + instance->tcDeleteFilter ); + return NULL; + } + refCounter++; + return instance; +} + +void TrafficControlWindows::Release(TrafficControlWindows* gtc) +{ + if (0 == refCounter) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, -1, + "TrafficControlWindows - Cannot release, refCounter is 0"); + return; + } + if (NULL == gtc) + { + WEBRTC_TRACE(kTraceDebug, kTraceTransport, -1, + "TrafficControlWindows - Not releasing, gtc is NULL"); + return; + } + + WEBRTC_TRACE(kTraceDebug, kTraceTransport, -1, + "TrafficControlWindows - Releasing object"); + refCounter--; + if ((0 == refCounter) && instance) + { + WEBRTC_TRACE(kTraceMemory, kTraceTransport, -1, + "TrafficControlWindows - Deleting object"); + delete instance; + instance = NULL; + } +} + +ULONG TrafficControlWindows::TcRegisterClient( + ULONG TciVersion, + HANDLE ClRegCtx, + PTCI_CLIENT_FUNC_LIST ClientHandlerList, + PHANDLE pClientHandle) +{ + assert(tcRegister != NULL); + + return tcRegister(TciVersion, ClRegCtx, ClientHandlerList, pClientHandle); +} + +ULONG TrafficControlWindows::TcDeregisterClient(HANDLE clientHandle) +{ + assert(tcDeregister != NULL); + + return tcDeregister(clientHandle); +} + + +ULONG TrafficControlWindows::TcEnumerateInterfaces( + HANDLE ClientHandle, + PULONG pBufferSize, + PTC_IFC_DESCRIPTOR interfaceBuffer) +{ + assert(tcEnumerate != NULL); + + return tcEnumerate(ClientHandle, pBufferSize, interfaceBuffer); +} + + +ULONG TrafficControlWindows::TcOpenInterfaceW(LPWSTR pInterfaceName, + HANDLE ClientHandle, + HANDLE ClIfcCtx, + PHANDLE pIfcHandle) +{ + assert(tcOpenInterface != NULL); + + return tcOpenInterface(pInterfaceName, ClientHandle, ClIfcCtx, pIfcHandle); + +} + +ULONG TrafficControlWindows::TcCloseInterface(HANDLE IfcHandle) +{ + assert(tcCloseInterface != NULL); + + return tcCloseInterface(IfcHandle); +} + +ULONG TrafficControlWindows::TcAddFlow(HANDLE IfcHandle, HANDLE ClFlowCtx, + ULONG Flags, PTC_GEN_FLOW pGenericFlow, + PHANDLE pFlowHandle) +{ + assert(tcAddFlow != NULL); + return tcAddFlow(IfcHandle, ClFlowCtx, Flags, pGenericFlow, pFlowHandle); +} + +ULONG TrafficControlWindows::TcAddFilter(HANDLE FlowHandle, + PTC_GEN_FILTER pGenericFilter, + PHANDLE pFilterHandle) +{ + assert(tcAddFilter != NULL); + return tcAddFilter(FlowHandle, pGenericFilter, pFilterHandle); +} + +ULONG TrafficControlWindows::TcDeleteFlow(HANDLE FlowHandle) +{ + assert(tcDeleteFlow != NULL); + return tcDeleteFlow(FlowHandle); + +} + +ULONG TrafficControlWindows::TcDeleteFilter(HANDLE FilterHandle) +{ + assert(tcDeleteFilter != NULL); + return tcDeleteFilter(FilterHandle); +} + +void MyClNotifyHandler(HANDLE ClRegCtx, HANDLE ClIfcCtx, ULONG Event, + HANDLE SubCode, ULONG BufSize, PVOID Buffer) +{ +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/channel_transport/traffic_control_win.h b/webrtc/test/channel_transport/traffic_control_win.h new file mode 100644 index 0000000000..1197b94b20 --- /dev/null +++ b/webrtc/test/channel_transport/traffic_control_win.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2011 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_TEST_CHANNEL_TRANSPORT_TRAFFIC_CONTROL_WINDOWS_H_ +#define WEBRTC_TEST_CHANNEL_TRANSPORT_TRAFFIC_CONTROL_WINDOWS_H_ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +// Disable deprication warning from traffic.h +#pragma warning(disable : 4995) + +#include <windows.h> +#include <qos.h> +#include <ntddndis.h> +#include <traffic.h> + +#include "webrtc/system_wrappers/include/trace.h" + +namespace webrtc { +namespace test { +void MyClNotifyHandler(HANDLE ClRegCtx, HANDLE ClIfcCtx, ULONG Event, + HANDLE SubCode, ULONG BufSize, PVOID Buffer); + + +typedef ULONG (WINAPI *registerFn)(ULONG, HANDLE, PTCI_CLIENT_FUNC_LIST, + PHANDLE); +typedef ULONG (WINAPI *deregisterFn)(HANDLE); +typedef ULONG (WINAPI *enumerateFn)(HANDLE, PULONG, PTC_IFC_DESCRIPTOR); +typedef ULONG (WINAPI *openInterfaceFn)(LPWSTR, HANDLE, HANDLE, PHANDLE); +typedef ULONG (WINAPI *closeInterfaceFn)(HANDLE); +typedef ULONG (WINAPI *flowAddFn)(HANDLE, HANDLE, ULONG, PTC_GEN_FLOW, PHANDLE); +typedef ULONG (WINAPI *filterAddFn)(HANDLE, PTC_GEN_FILTER, PHANDLE); +typedef ULONG (WINAPI *flowDeleteFn)(HANDLE); +typedef ULONG (WINAPI *filterDeleteFn)(HANDLE); + +class TrafficControlWindows +{ + public: + // Factory method. Constructor disabled. + static TrafficControlWindows* GetInstance(const int32_t id); + static void Release(TrafficControlWindows* gtc); + + ULONG TcRegisterClient(ULONG TciVersion, HANDLE ClRegCtx, + PTCI_CLIENT_FUNC_LIST ClientHandlerList, + PHANDLE pClientHandle); + + ULONG TcDeregisterClient(HANDLE clientHandle); + + ULONG TcEnumerateInterfaces(HANDLE ClientHandle, PULONG pBufferSize, + PTC_IFC_DESCRIPTOR interfaceBuffer); + + ULONG TcOpenInterfaceW(LPWSTR pInterfaceName, HANDLE ClientHandle, + HANDLE ClIfcCtx, PHANDLE pIfcHandle); + + ULONG TcCloseInterface(HANDLE IfcHandle); + + ULONG TcAddFlow(HANDLE IfcHandle, HANDLE ClFlowCtx, ULONG Flags, + PTC_GEN_FLOW pGenericFlow, PHANDLE pFlowHandle); + + ULONG TcAddFilter(HANDLE FlowHandle, PTC_GEN_FILTER pGenericFilter, + PHANDLE pFilterHandle); + + ULONG TcDeleteFlow(HANDLE FlowHandle); + ULONG TcDeleteFilter(HANDLE FilterHandle); +private: + TrafficControlWindows(const int32_t id); + TCI_CLIENT_FUNC_LIST QoSFunctions; + + static TrafficControlWindows* instance; + + registerFn tcRegister; + deregisterFn tcDeregister; + + enumerateFn tcEnumerate; + openInterfaceFn tcOpenInterface; + closeInterfaceFn tcCloseInterface; + + flowAddFn tcAddFlow; + flowDeleteFn tcDeleteFlow; + + filterAddFn tcAddFilter; + filterDeleteFn tcDeleteFilter; + + static uint32_t refCounter; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_CHANNEL_TRANSPORT_TRAFFIC_CONTROL_WINDOWS_H_ diff --git a/webrtc/test/channel_transport/udp_socket2_manager_win.cc b/webrtc/test/channel_transport/udp_socket2_manager_win.cc new file mode 100644 index 0000000000..5a11abbd6e --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket2_manager_win.cc @@ -0,0 +1,613 @@ +/* + * 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/channel_transport/udp_socket2_manager_win.h" + +#include <assert.h> +#include <stdio.h> + +#include "webrtc/system_wrappers/include/aligned_malloc.h" +#include "webrtc/test/channel_transport/udp_socket2_win.h" + +namespace webrtc { +namespace test { + +uint32_t UdpSocket2ManagerWindows::_numOfActiveManagers = 0; +bool UdpSocket2ManagerWindows::_wsaInit = false; + +UdpSocket2ManagerWindows::UdpSocket2ManagerWindows() + : UdpSocketManager(), + _id(-1), + _stopped(false), + _init(false), + _pCrit(CriticalSectionWrapper::CreateCriticalSection()), + _ioCompletionHandle(NULL), + _numActiveSockets(0), + _event(EventWrapper::Create()) +{ + _managerNumber = _numOfActiveManagers++; + + if(_numOfActiveManagers == 1) + { + WORD wVersionRequested = MAKEWORD(2, 2); + WSADATA wsaData; + _wsaInit = WSAStartup(wVersionRequested, &wsaData) == 0; + // TODO (hellner): seems safer to use RAII for this. E.g. what happens + // if a UdpSocket2ManagerWindows() created and destroyed + // without being initialized. + } +} + +UdpSocket2ManagerWindows::~UdpSocket2ManagerWindows() +{ + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocket2ManagerWindows(%d)::~UdpSocket2ManagerWindows()", + _managerNumber); + + if(_init) + { + _pCrit->Enter(); + if(_numActiveSockets) + { + _pCrit->Leave(); + _event->Wait(INFINITE); + } + else + { + _pCrit->Leave(); + } + StopWorkerThreads(); + + for (WorkerList::iterator iter = _workerThreadsList.begin(); + iter != _workerThreadsList.end(); ++iter) { + delete *iter; + } + _workerThreadsList.clear(); + _ioContextPool.Free(); + + _numOfActiveManagers--; + if(_ioCompletionHandle) + { + CloseHandle(_ioCompletionHandle); + } + if (_numOfActiveManagers == 0) + { + if(_wsaInit) + { + WSACleanup(); + } + } + } + if(_pCrit) + { + delete _pCrit; + } + if(_event) + { + delete _event; + } +} + +bool UdpSocket2ManagerWindows::Init(int32_t id, + uint8_t& numOfWorkThreads) { + CriticalSectionScoped cs(_pCrit); + if ((_id != -1) || (_numOfWorkThreads != 0)) { + assert(_id != -1); + assert(_numOfWorkThreads != 0); + return false; + } + _id = id; + _numOfWorkThreads = numOfWorkThreads; + return true; +} + +bool UdpSocket2ManagerWindows::Start() +{ + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocket2ManagerWindows(%d)::Start()",_managerNumber); + if(!_init) + { + StartWorkerThreads(); + } + + if(!_init) + { + return false; + } + _pCrit->Enter(); + // Start worker threads. + _stopped = false; + int32_t error = 0; + for (WorkerList::iterator iter = _workerThreadsList.begin(); + iter != _workerThreadsList.end() && !error; ++iter) { + if(!(*iter)->Start()) + error = 1; + } + if(error) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows(%d)::Start() error starting worker\ + threads", + _managerNumber); + _pCrit->Leave(); + return false; + } + _pCrit->Leave(); + return true; +} + +bool UdpSocket2ManagerWindows::StartWorkerThreads() +{ + if(!_init) + { + _pCrit->Enter(); + + _ioCompletionHandle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, + 0, 0); + if(_ioCompletionHandle == NULL) + { + int32_t error = GetLastError(); + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows(%d)::StartWorkerThreads()" + "_ioCompletioHandle == NULL: error:%d", + _managerNumber,error); + _pCrit->Leave(); + return false; + } + + // Create worker threads. + uint32_t i = 0; + bool error = false; + while(i < _numOfWorkThreads && !error) + { + UdpSocket2WorkerWindows* pWorker = + new UdpSocket2WorkerWindows(_ioCompletionHandle); + if(pWorker->Init() != 0) + { + error = true; + delete pWorker; + break; + } + _workerThreadsList.push_front(pWorker); + i++; + } + if(error) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows(%d)::StartWorkerThreads() error " + "creating work threads", + _managerNumber); + // Delete worker threads. + for (WorkerList::iterator iter = _workerThreadsList.begin(); + iter != _workerThreadsList.end(); ++iter) { + delete *iter; + } + _workerThreadsList.clear(); + _pCrit->Leave(); + return false; + } + if(_ioContextPool.Init()) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows(%d)::StartWorkerThreads() error " + "initiating _ioContextPool", + _managerNumber); + _pCrit->Leave(); + return false; + } + _init = true; + WEBRTC_TRACE( + kTraceDebug, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows::StartWorkerThreads %d number of work " + "threads created and initialized", + _numOfWorkThreads); + _pCrit->Leave(); + } + return true; +} + +bool UdpSocket2ManagerWindows::Stop() +{ + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocket2ManagerWindows(%d)::Stop()",_managerNumber); + + if(!_init) + { + return false; + } + _pCrit->Enter(); + _stopped = true; + if(_numActiveSockets) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows(%d)::Stop() there is still active\ + sockets", + _managerNumber); + _pCrit->Leave(); + return false; + } + // No active sockets. Stop all worker threads. + bool result = StopWorkerThreads(); + _pCrit->Leave(); + return result; +} + +bool UdpSocket2ManagerWindows::StopWorkerThreads() +{ + int32_t error = 0; + WEBRTC_TRACE( + kTraceDebug, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows(%d)::StopWorkerThreads() Worker\ + threadsStoped, numActicve Sockets=%d", + _managerNumber, + _numActiveSockets); + + // Release all threads waiting for GetQueuedCompletionStatus(..). + if(_ioCompletionHandle) + { + uint32_t i = 0; + for(i = 0; i < _workerThreadsList.size(); i++) + { + PostQueuedCompletionStatus(_ioCompletionHandle, 0 ,0 , NULL); + } + } + for (WorkerList::iterator iter = _workerThreadsList.begin(); + iter != _workerThreadsList.end(); ++iter) { + if((*iter)->Stop() == false) + { + error = -1; + WEBRTC_TRACE(kTraceWarning, kTraceTransport, -1, + "failed to stop worker thread"); + } + } + + if(error) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows(%d)::StopWorkerThreads() error stopping\ + worker threads", + _managerNumber); + return false; + } + return true; +} + +bool UdpSocket2ManagerWindows::AddSocketPrv(UdpSocket2Windows* s) +{ + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocket2ManagerWindows(%d)::AddSocketPrv()",_managerNumber); + if(!_init) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows(%d)::AddSocketPrv() manager not\ + initialized", + _managerNumber); + return false; + } + _pCrit->Enter(); + if(s == NULL) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows(%d)::AddSocketPrv() socket == NULL", + _managerNumber); + _pCrit->Leave(); + return false; + } + if(s->GetFd() == NULL || s->GetFd() == INVALID_SOCKET) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows(%d)::AddSocketPrv() socket->GetFd() ==\ + %d", + _managerNumber, + (int32_t)s->GetFd()); + _pCrit->Leave(); + return false; + + } + _ioCompletionHandle = CreateIoCompletionPort((HANDLE)s->GetFd(), + _ioCompletionHandle, + (ULONG_PTR)(s), 0); + if(_ioCompletionHandle == NULL) + { + int32_t error = GetLastError(); + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows(%d)::AddSocketPrv() Error adding to IO\ + completion: %d", + _managerNumber, + error); + _pCrit->Leave(); + return false; + } + _numActiveSockets++; + _pCrit->Leave(); + return true; +} +bool UdpSocket2ManagerWindows::RemoveSocketPrv(UdpSocket2Windows* s) +{ + if(!_init) + { + return false; + } + _pCrit->Enter(); + _numActiveSockets--; + if(_numActiveSockets == 0) + { + _event->Set(); + } + _pCrit->Leave(); + return true; +} + +PerIoContext* UdpSocket2ManagerWindows::PopIoContext() +{ + if(!_init) + { + return NULL; + } + + PerIoContext* pIoC = NULL; + if(!_stopped) + { + pIoC = _ioContextPool.PopIoContext(); + }else + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2ManagerWindows(%d)::PopIoContext() Manager Not started", + _managerNumber); + } + return pIoC; +} + +int32_t UdpSocket2ManagerWindows::PushIoContext(PerIoContext* pIoContext) +{ + return _ioContextPool.PushIoContext(pIoContext); +} + +IoContextPool::IoContextPool() + : _pListHead(NULL), + _init(false), + _size(0), + _inUse(0) +{ +} + +IoContextPool::~IoContextPool() +{ + Free(); + assert(_size.Value() == 0); + AlignedFree(_pListHead); +} + +int32_t IoContextPool::Init(uint32_t /*increaseSize*/) +{ + if(_init) + { + return 0; + } + + _pListHead = (PSLIST_HEADER)AlignedMalloc(sizeof(SLIST_HEADER), + MEMORY_ALLOCATION_ALIGNMENT); + if(_pListHead == NULL) + { + return -1; + } + InitializeSListHead(_pListHead); + _init = true; + return 0; +} + +PerIoContext* IoContextPool::PopIoContext() +{ + if(!_init) + { + return NULL; + } + + PSLIST_ENTRY pListEntry = InterlockedPopEntrySList(_pListHead); + if(pListEntry == NULL) + { + IoContextPoolItem* item = (IoContextPoolItem*) + AlignedMalloc( + sizeof(IoContextPoolItem), + MEMORY_ALLOCATION_ALIGNMENT); + if(item == NULL) + { + return NULL; + } + memset(&item->payload.ioContext,0,sizeof(PerIoContext)); + item->payload.base = item; + pListEntry = &(item->itemEntry); + ++_size; + } + ++_inUse; + return &((IoContextPoolItem*)pListEntry)->payload.ioContext; +} + +int32_t IoContextPool::PushIoContext(PerIoContext* pIoContext) +{ + // TODO (hellner): Overlapped IO should be completed at this point. Perhaps + // add an assert? + const bool overlappedIOCompleted = HasOverlappedIoCompleted( + (LPOVERLAPPED)pIoContext); + + IoContextPoolItem* item = ((IoContextPoolItemPayload*)pIoContext)->base; + + const int32_t usedItems = --_inUse; + const int32_t totalItems = _size.Value(); + const int32_t freeItems = totalItems - usedItems; + if(freeItems < 0) + { + assert(false); + AlignedFree(item); + return -1; + } + if((freeItems >= totalItems>>1) && + overlappedIOCompleted) + { + AlignedFree(item); + --_size; + return 0; + } + InterlockedPushEntrySList(_pListHead, &(item->itemEntry)); + return 0; +} + +int32_t IoContextPool::Free() +{ + if(!_init) + { + return 0; + } + + int32_t itemsFreed = 0; + PSLIST_ENTRY pListEntry = InterlockedPopEntrySList(_pListHead); + while(pListEntry != NULL) + { + IoContextPoolItem* item = ((IoContextPoolItem*)pListEntry); + AlignedFree(item); + --_size; + itemsFreed++; + pListEntry = InterlockedPopEntrySList(_pListHead); + } + return itemsFreed; +} + +int32_t UdpSocket2WorkerWindows::_numOfWorkers = 0; + +UdpSocket2WorkerWindows::UdpSocket2WorkerWindows(HANDLE ioCompletionHandle) + : _ioCompletionHandle(ioCompletionHandle), + _init(false) +{ + _workerNumber = _numOfWorkers++; + WEBRTC_TRACE(kTraceMemory, kTraceTransport, -1, + "UdpSocket2WorkerWindows created"); +} + +UdpSocket2WorkerWindows::~UdpSocket2WorkerWindows() +{ + WEBRTC_TRACE(kTraceMemory, kTraceTransport, -1, + "UdpSocket2WorkerWindows deleted"); +} + +bool UdpSocket2WorkerWindows::Start() +{ + WEBRTC_TRACE(kTraceStateInfo, kTraceTransport, -1, + "Start UdpSocket2WorkerWindows"); + if (!_pThread->Start()) + return false; + + _pThread->SetPriority(kRealtimePriority); + return true; +} + +bool UdpSocket2WorkerWindows::Stop() +{ + WEBRTC_TRACE(kTraceStateInfo, kTraceTransport, -1, + "Stop UdpSocket2WorkerWindows"); + return _pThread->Stop(); +} + +int32_t UdpSocket2WorkerWindows::Init() +{ + if(!_init) + { + const char* threadName = "UdpSocket2ManagerWindows_thread"; + _pThread = ThreadWrapper::CreateThread(Run, this, threadName); + _init = true; + } + return 0; +} + +bool UdpSocket2WorkerWindows::Run(void* obj) +{ + UdpSocket2WorkerWindows* pWorker = + static_cast<UdpSocket2WorkerWindows*>(obj); + return pWorker->Process(); +} + +// Process should always return true. Stopping the worker threads is done in +// the UdpSocket2ManagerWindows::StopWorkerThreads() function. +bool UdpSocket2WorkerWindows::Process() +{ + int32_t success = 0; + DWORD ioSize = 0; + UdpSocket2Windows* pSocket = NULL; + PerIoContext* pIOContext = 0; + OVERLAPPED* pOverlapped = 0; + success = GetQueuedCompletionStatus(_ioCompletionHandle, + &ioSize, + (ULONG_PTR*)&pSocket, &pOverlapped, 200); + + uint32_t error = 0; + if(!success) + { + error = GetLastError(); + if(error == WAIT_TIMEOUT) + { + return true; + } + // This may happen if e.g. PostQueuedCompletionStatus() has been called. + // The IO context still needs to be reclaimed or re-used which is done + // in UdpSocket2Windows::IOCompleted(..). + } + if(pSocket == NULL) + { + WEBRTC_TRACE( + kTraceDebug, + kTraceTransport, + -1, + "UdpSocket2WorkerWindows(%d)::Process(), pSocket == 0, end thread", + _workerNumber); + return true; + } + pIOContext = (PerIoContext*)pOverlapped; + pSocket->IOCompleted(pIOContext,ioSize,error); + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/channel_transport/udp_socket2_manager_win.h b/webrtc/test/channel_transport/udp_socket2_manager_win.h new file mode 100644 index 0000000000..c6af03a702 --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket2_manager_win.h @@ -0,0 +1,162 @@ +/* + * 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_TEST_CHANNEL_TRANSPORT_UDP_SOCKET2_MANAGER_WINDOWS_H_ +#define WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_SOCKET2_MANAGER_WINDOWS_H_ + +#include <winsock2.h> +#include <list> + +#include "webrtc/system_wrappers/include/atomic32.h" +#include "webrtc/system_wrappers/include/critical_section_wrapper.h" +#include "webrtc/system_wrappers/include/event_wrapper.h" +#include "webrtc/system_wrappers/include/thread_wrapper.h" +#include "webrtc/test/channel_transport/udp_socket2_win.h" +#include "webrtc/test/channel_transport/udp_socket_manager_wrapper.h" +#include "webrtc/test/channel_transport/udp_transport.h" + +#define MAX_IO_BUFF_SIZE 1600 + +namespace webrtc { +namespace test { + +enum IO_OPERATION { + OP_READ, + OP_WRITE +}; + +class UdpSocket2Windows; + +// Struct used for all socket I/O operations. +struct PerIoContext { + WSAOVERLAPPED overlapped; + char buffer[MAX_IO_BUFF_SIZE]; + WSABUF wsabuf; + size_t nTotalBytes; + int nSentBytes; + int bytes; + IO_OPERATION ioOperation; + SocketAddress from; + int fromLen; + // Should be set to true if the I/O context was passed to the system by + // a thread not controlled by the socket implementation. + bool ioInitiatedByThreadWrapper; + // TODO (hellner): Not used. Delete it. + PerIoContext* pNextFree; +}; + +struct IoContextPoolItem; +struct IoContextPoolItemPayload +{ + PerIoContext ioContext; + IoContextPoolItem* base; +}; + +struct IoContextPoolItem +{ + // Atomic single linked list entry header. + SLIST_ENTRY itemEntry; + // Atomic single linked list payload + IoContextPoolItemPayload payload; +}; + +class IoContextPool +{ +public: + IoContextPool(); + virtual ~IoContextPool(); + virtual int32_t Init(uint32_t increaseSize = 128); + // Re-use an old unused IO context or create a new one. + virtual PerIoContext* PopIoContext(); + virtual int32_t PushIoContext(PerIoContext* pIoContext); + virtual inline int32_t GetSize(uint32_t* inUse = 0) + {return _size.Value();} + virtual int32_t Free(); +private: + // Sample code for use of msfts single linked atomic list can be found here: + // http://msdn.microsoft.com/en-us/library/ms686962(VS.85).aspx + + // Atomic single linked list head. + PSLIST_HEADER _pListHead; + + bool _init; + Atomic32 _size; + Atomic32 _inUse; +}; + +class UdpSocket2WorkerWindows +{ +public: + UdpSocket2WorkerWindows(HANDLE ioCompletionHandle); + virtual ~UdpSocket2WorkerWindows(); + + virtual bool Start(); + virtual bool Stop(); + virtual int32_t Init(); +protected: + static bool Run(void* obj); + bool Process(); +private: + HANDLE _ioCompletionHandle; + rtc::scoped_ptr<ThreadWrapper> _pThread; + static int32_t _numOfWorkers; + int32_t _workerNumber; + volatile bool _stop; + bool _init; +}; + +class UdpSocket2ManagerWindows : public UdpSocketManager +{ +public: + UdpSocket2ManagerWindows(); + virtual ~UdpSocket2ManagerWindows(); + + virtual bool Init(int32_t id, uint8_t& numOfWorkThreads); + + virtual bool Start(); + virtual bool Stop(); + + virtual inline bool AddSocket(UdpSocketWrapper* s) + {if(s) return AddSocketPrv(reinterpret_cast<UdpSocket2Windows*>(s)); + return false;} + virtual bool RemoveSocket(UdpSocketWrapper* s) + {if(s) return RemoveSocketPrv(reinterpret_cast<UdpSocket2Windows*>(s)); + return false;} + + PerIoContext* PopIoContext(void); + int32_t PushIoContext(PerIoContext* pIoContext); + +private: + typedef std::list<UdpSocket2WorkerWindows*> WorkerList; + bool StopWorkerThreads(); + bool StartWorkerThreads(); + bool AddSocketPrv(UdpSocket2Windows* s); + bool RemoveSocketPrv(UdpSocket2Windows* s); + + static uint32_t _numOfActiveManagers; + static bool _wsaInit; + + int32_t _id; + CriticalSectionWrapper* _pCrit; + int32_t _managerNumber; + volatile bool _stopped; + bool _init; + int32_t _numActiveSockets; + WorkerList _workerThreadsList; + EventWrapper* _event; + + HANDLE _ioCompletionHandle; + IoContextPool _ioContextPool; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_SOCKET2_MANAGER_WINDOWS_H_ diff --git a/webrtc/test/channel_transport/udp_socket2_win.cc b/webrtc/test/channel_transport/udp_socket2_win.cc new file mode 100644 index 0000000000..4c63dc938d --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket2_win.cc @@ -0,0 +1,1374 @@ +/* + * 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/channel_transport/udp_socket2_win.h" + +#include <assert.h> +#include <stdlib.h> +#include <winsock2.h> + +#include "webrtc/base/format_macros.h" +#include "webrtc/system_wrappers/include/sleep.h" +#include "webrtc/test/channel_transport/traffic_control_win.h" +#include "webrtc/test/channel_transport/udp_socket2_manager_win.h" + +#pragma warning(disable : 4311) + +namespace webrtc { +namespace test { + +typedef struct _QOS_DESTADDR +{ + QOS_OBJECT_HDR ObjectHdr; + const struct sockaddr* SocketAddress; + ULONG SocketAddressLength; +} QOS_DESTADDR, *LPQOS_DESTADDR; + +typedef const QOS_DESTADDR* LPCQOS_DESTADDR; + +// TODO (patrikw): seems to be defined in ws2ipdef.h as 3. How come it's +// redefined here (as a different value)? +#define IP_TOS 8 + +#define QOS_GENERAL_ID_BASE 2000 +#define QOS_OBJECT_DESTADDR (0x00000004 + QOS_GENERAL_ID_BASE) + +UdpSocket2Windows::UdpSocket2Windows(const int32_t id, + UdpSocketManager* mgr, bool ipV6Enable, + bool disableGQOS) + : _id(id), + _qos(true), + _iProtocol(0), + _outstandingCalls(0), + _outstandingCallComplete(0), + _terminate(false), + _addedToMgr(false), + _safeTodelete(false), + _outstandingCallsDisabled(false), + _clientHandle(NULL), + _flowHandle(NULL), + _filterHandle(NULL), + _flow(NULL), + _gtc(NULL), + _pcp(-2), + _receiveBuffers(0) +{ + WEBRTC_TRACE(kTraceMemory, kTraceTransport, _id, + "UdpSocket2Windows::UdpSocket2Windows()"); + + _wantsIncoming = false; + _mgr = static_cast<UdpSocket2ManagerWindows *>(mgr); + + _obj = NULL; + _incomingCb = NULL; + _socket = INVALID_SOCKET; + _pCrit = CriticalSectionWrapper::CreateCriticalSection(); + _ptrCbRWLock = RWLockWrapper::CreateRWLock(); + _ptrDestRWLock = RWLockWrapper::CreateRWLock(); + _ptrSocketRWLock = RWLockWrapper::CreateRWLock(); + _ptrDeleteCrit = CriticalSectionWrapper::CreateCriticalSection(); + _ptrDeleteCond = ConditionVariableWrapper::CreateConditionVariable(); + + // Check if QoS is supported. + BOOL bProtocolFound = FALSE; + WSAPROTOCOL_INFO *lpProtocolBuf = NULL; + WSAPROTOCOL_INFO pProtocolInfo; + + if(!disableGQOS) + { + DWORD dwBufLen = 0; + // Set dwBufLen to the size needed to retreive all the requested + // information from WSAEnumProtocols. + int32_t nRet = WSAEnumProtocols(NULL, lpProtocolBuf, &dwBufLen); + lpProtocolBuf = (WSAPROTOCOL_INFO*)malloc(dwBufLen); + nRet = WSAEnumProtocols(NULL, lpProtocolBuf, &dwBufLen); + + if (ipV6Enable) + { + _iProtocol=AF_INET6; + } else { + _iProtocol=AF_INET; + } + + for (int32_t i=0; i<nRet; i++) + { + if (_iProtocol == lpProtocolBuf[i].iAddressFamily && + IPPROTO_UDP == lpProtocolBuf[i].iProtocol) + { + if ((XP1_QOS_SUPPORTED == + (XP1_QOS_SUPPORTED & lpProtocolBuf[i].dwServiceFlags1))) + { + pProtocolInfo = lpProtocolBuf[i]; + bProtocolFound = TRUE; + break; + } + } + } + } + + if(!bProtocolFound) + { + free(lpProtocolBuf); + _qos=false; + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2Windows::UdpSocket2Windows(), SOCKET_ERROR_NO_QOS,\ + !bProtocolFound"); + } else { + + _socket = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, + FROM_PROTOCOL_INFO,&pProtocolInfo, 0, + WSA_FLAG_OVERLAPPED); + free(lpProtocolBuf); + + if (_socket != INVALID_SOCKET) + { + return; + } else { + _qos = false; + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2Windows::UdpSocket2Windows(), SOCKET_ERROR_NO_QOS"); + } + } + // QoS not supported. + if(ipV6Enable) + { + _socket = WSASocket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, 0 , 0, + WSA_FLAG_OVERLAPPED); + }else + { + _socket = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0 , 0, + WSA_FLAG_OVERLAPPED); + } + if (_socket == INVALID_SOCKET) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2Windows::UdpSocket2Windows(), INVALID_SOCKET,\ + WSAerror: %d", + WSAGetLastError()); + } + + // Disable send buffering on the socket to improve CPU usage. + // This is done by setting SO_SNDBUF to 0. + int32_t nZero = 0; + int32_t nRet = setsockopt(_socket, SOL_SOCKET, SO_SNDBUF, + (char*)&nZero, sizeof(nZero)); + if( nRet == SOCKET_ERROR ) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2Windows::UdpSocket2Windows(), SOCKET_ERROR,\ + WSAerror: %d", + WSAGetLastError()); + } +} + +UdpSocket2Windows::~UdpSocket2Windows() +{ + WEBRTC_TRACE(kTraceMemory, kTraceTransport, _id, + "UdpSocket2Windows::~UdpSocket2Windows()"); + + WaitForOutstandingCalls(); + + delete _ptrCbRWLock; + delete _ptrDeleteCrit; + delete _ptrDeleteCond; + delete _ptrDestRWLock; + delete _ptrSocketRWLock; + + if(_pCrit) + delete _pCrit; + + if (_flow) + { + free(_flow); + _flow = NULL; + } + + if (_gtc) + { + if(_filterHandle) + { + _gtc->TcDeleteFilter(_filterHandle); + } + if(_flowHandle) + { + _gtc->TcDeleteFlow(_flowHandle); + } + TrafficControlWindows::Release( _gtc); + } +} + +bool UdpSocket2Windows::ValidHandle() +{ + return GetFd() != INVALID_SOCKET; +} + +bool UdpSocket2Windows::SetCallback(CallbackObj obj, IncomingSocketCallback cb) +{ + _ptrCbRWLock->AcquireLockExclusive(); + _obj = obj; + _incomingCb = cb; + _ptrCbRWLock->ReleaseLockExclusive(); + + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocket2Windows(%d)::SetCallback ",(int32_t)this); + if(_addedToMgr) + { + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocket2Windows(%d)::SetCallback alreadey added", + (int32_t) this); + return false; + + } + if (_mgr->AddSocket(this)) + { + WEBRTC_TRACE( + kTraceDebug, kTraceTransport, _id, + "UdpSocket2Windows(%d)::SetCallback socket added to manager", + (int32_t)this); + _addedToMgr = true; + return true; + } + + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocket2Windows(%d)::SetCallback error adding me to mgr", + (int32_t) this); + return false; +} + +bool UdpSocket2Windows::SetSockopt(int32_t level, int32_t optname, + const int8_t* optval, int32_t optlen) +{ + bool returnValue = true; + if(!AquireSocket()) + { + return false; + } + if(0 != setsockopt(_socket, level, optname, + reinterpret_cast<const char*>(optval), optlen )) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocket2Windows::SetSockopt(), WSAerror:%d", + WSAGetLastError()); + returnValue = false; + } + ReleaseSocket(); + return returnValue; +} + +bool UdpSocket2Windows::StartReceiving(uint32_t receiveBuffers) +{ + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocket2Windows(%d)::StartReceiving(%d)", (int32_t)this, + receiveBuffers); + + _wantsIncoming = true; + + int32_t numberOfReceiveBuffersToCreate = + receiveBuffers - _receiveBuffers.Value(); + numberOfReceiveBuffersToCreate = (numberOfReceiveBuffersToCreate < 0) ? + 0 : numberOfReceiveBuffersToCreate; + + int32_t error = 0; + for(int32_t i = 0; + i < numberOfReceiveBuffersToCreate; + i++) + { + if(PostRecv()) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocket2Windows::StartReceiving() i=%d", i); + error = -1; + break; + } + ++_receiveBuffers; + } + if(error == -1) + { + return false; + } + + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "Socket receiving using:%d number of buffers", + _receiveBuffers.Value()); + return true; +} + +bool UdpSocket2Windows::StopReceiving() +{ + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocket2Windows::StopReceiving()"); + _wantsIncoming = false; + return true; +} + +bool UdpSocket2Windows::Bind(const SocketAddress& name) +{ + const struct sockaddr* addr = + reinterpret_cast<const struct sockaddr*>(&name); + bool returnValue = true; + if(!AquireSocket()) + { + return false; + } + if (0 != bind(_socket, addr, sizeof(SocketAddress))) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocket2Windows::Bind() WSAerror: %d", + WSAGetLastError()); + returnValue = false; + } + ReleaseSocket(); + return returnValue; +} + +int32_t UdpSocket2Windows::SendTo(const int8_t* buf, size_t len, + const SocketAddress& to) +{ + int32_t retVal = 0; + int32_t error = 0; + PerIoContext* pIoContext = _mgr->PopIoContext(); + if(pIoContext == 0) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocket2Windows(%d)::SendTo(), pIoContext==0", + (int32_t) this); + return -1; + } + // sizeof(pIoContext->buffer) is smaller than the highest number that + // can be represented by a size_t. + if(len >= sizeof(pIoContext->buffer)) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2Windows(%d)::SendTo(), len= %" PRIuS + " > buffer_size = %d", + (int32_t) this, + len,sizeof(pIoContext->buffer)); + len = sizeof(pIoContext->buffer); + } + + memcpy(pIoContext->buffer,buf,len); + pIoContext->wsabuf.buf = pIoContext->buffer; + pIoContext->wsabuf.len = static_cast<ULONG>(len); + pIoContext->fromLen=sizeof(SocketAddress); + pIoContext->ioOperation = OP_WRITE; + pIoContext->nTotalBytes = len; + pIoContext->nSentBytes=0; + + DWORD numOfbytesSent = 0; + const struct sockaddr* addr = reinterpret_cast<const struct sockaddr*>(&to); + + if(!AquireSocket()) + { + _mgr->PushIoContext(pIoContext); + return -1; + } + // Assume that the WSASendTo call will be successfull to make sure that + // _outstandingCalls is positive. Roll back if WSASendTo failed. + if(!NewOutstandingCall()) + { + _mgr->PushIoContext(pIoContext); + ReleaseSocket(); + return -1; + } + retVal = WSASendTo(_socket, &pIoContext->wsabuf, 1, &numOfbytesSent, + 0, addr, sizeof(SocketAddress), + &(pIoContext->overlapped), 0); + ReleaseSocket(); + + if( retVal == SOCKET_ERROR ) + { + error = WSAGetLastError(); + if(error != ERROR_IO_PENDING) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocket2Windows::SendTo() WSAerror: %d",error); + } + } + if(retVal == 0 || (retVal == SOCKET_ERROR && error == ERROR_IO_PENDING)) + { + return static_cast<int32_t>(len); + } + error = _mgr->PushIoContext(pIoContext); + if(error) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2Windows(%d)::SendTo(), error:%d pushing ioContext", + (int32_t)this, error); + } + + // Roll back. + OutstandingCallCompleted(); + return -1; +} + +void UdpSocket2Windows::IOCompleted(PerIoContext* pIOContext, + uint32_t ioSize, uint32_t error) +{ + if(pIOContext == NULL || error == ERROR_OPERATION_ABORTED) + { + if ((pIOContext != NULL) && + !pIOContext->ioInitiatedByThreadWrapper && + (error == ERROR_OPERATION_ABORTED) && + (pIOContext->ioOperation == OP_READ) && + _outstandingCallsDisabled) + { + // !pIOContext->initiatedIOByThreadWrapper indicate that the I/O + // was not initiated by a ThreadWrapper thread. + // This may happen if the thread that initiated receiving (e.g. + // by calling StartListen())) is deleted before any packets have + // been received. + // In this case there is no packet in the PerIoContext. Re-use it + // to post a new PostRecv(..). + // Note 1: the PerIoContext will henceforth be posted by a thread + // that is controlled by the socket implementation. + // Note 2: This is more likely to happen to RTCP packets as + // they are less frequent than RTP packets. + // Note 3: _outstandingCallsDisabled being false indicates + // that the socket isn't being shut down. + // Note 4: This should only happen buffers set to receive packets + // (OP_READ). + } else { + if(pIOContext == NULL) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2Windows::IOCompleted(%d,%d,%d), %d", + (int32_t)pIOContext, + ioSize, + error, + pIOContext ? (int32_t)pIOContext->ioOperation : -1); + } else { + WEBRTC_TRACE( + kTraceDebug, + kTraceTransport, + _id, + "UdpSocket2Windows::IOCompleted() Operation aborted"); + } + if(pIOContext) + { + int32_t remainingReceiveBuffers = --_receiveBuffers; + if(remainingReceiveBuffers < 0) + { + assert(false); + } + int32_t err = _mgr->PushIoContext(pIOContext); + if(err) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2Windows::IOCompleted(), err = %d, when\ + pushing ioContext after error", + err); + } + } + OutstandingCallCompleted(); + return; + } + } // if (pIOContext == NULL || error == ERROR_OPERATION_ABORTED) + + if(pIOContext->ioOperation == OP_WRITE) + { + _mgr->PushIoContext(pIOContext); + } + else if(pIOContext->ioOperation == OP_READ) + { + if(!error && ioSize != 0) + { + _ptrCbRWLock->AcquireLockShared(); + if(_wantsIncoming && _incomingCb) + { + _incomingCb(_obj, + reinterpret_cast<const int8_t*>( + pIOContext->wsabuf.buf), + ioSize, + &pIOContext->from); + } + _ptrCbRWLock->ReleaseLockShared(); + } + int32_t err = PostRecv(pIOContext); + if(err == 0) + { + // The PerIoContext was posted by a thread controlled by the socket + // implementation. + pIOContext->ioInitiatedByThreadWrapper = true; + } + OutstandingCallCompleted(); + return; + } else { + // Unknown operation. Should not happen. Return pIOContext to avoid + // memory leak. + assert(false); + _mgr->PushIoContext(pIOContext); + } + OutstandingCallCompleted(); + // Don't touch any members after OutstandingCallCompleted() since the socket + // may be deleted at this point. +} + +int32_t UdpSocket2Windows::PostRecv() +{ + PerIoContext* pIoContext=_mgr->PopIoContext(); + if(pIoContext == 0) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocket2Windows(%d)::PostRecv(), pIoContext == 0", + (int32_t)this); + return -1; + } + // This function may have been called by thread not controlled by the socket + // implementation. + pIoContext->ioInitiatedByThreadWrapper = false; + return PostRecv(pIoContext); +} + +int32_t UdpSocket2Windows::PostRecv(PerIoContext* pIoContext) +{ + if(pIoContext==0) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocket2Windows(%d)::PostRecv(?), pIoContext==0", + (int32_t)this); + return -1; + } + + DWORD numOfRecivedBytes = 0; + DWORD flags = 0; + pIoContext->wsabuf.buf = pIoContext->buffer; + pIoContext->wsabuf.len = sizeof(pIoContext->buffer); + pIoContext->fromLen = sizeof(SocketAddress); + pIoContext->ioOperation = OP_READ; + int32_t rxError = 0; + int32_t nRet = 0; + int32_t postingSucessfull = false; + + if(!AquireSocket()) + { + _mgr->PushIoContext(pIoContext); + return -1; + } + + // Assume that the WSARecvFrom() call will be successfull to make sure that + // _outstandingCalls is positive. Roll back if WSARecvFrom() failed. + if(!NewOutstandingCall()) + { + _mgr->PushIoContext(pIoContext); + ReleaseSocket(); + return -1; + } + for(int32_t tries = 0; tries < 10; tries++) + { + nRet = WSARecvFrom( + _socket, + &(pIoContext->wsabuf), + 1, + &numOfRecivedBytes, + &flags, + reinterpret_cast<struct sockaddr*>(&(pIoContext->from)), + &(pIoContext->fromLen), + &(pIoContext->overlapped), + 0); + + if( nRet == SOCKET_ERROR) + { + rxError = WSAGetLastError(); + if(rxError != ERROR_IO_PENDING) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2Windows(%d)::PostRecv(?), WSAerror:%d when\ + posting new recieve,trie:%d", + (int32_t)this, + rxError, + tries); + // Tell the OS that this is a good place to context switch if + // it wants to. + SleepMs(0); + } + } + if((rxError == ERROR_IO_PENDING) || (nRet == 0)) + { + postingSucessfull = true; + break; + } + } + ReleaseSocket(); + + if(postingSucessfull) + { + return 0; + } + int32_t remainingReceiveBuffers = --_receiveBuffers; + if(remainingReceiveBuffers < 0) + { + assert(false); + } + int32_t error = _mgr->PushIoContext(pIoContext); + if(error) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocket2Windows(%d)::PostRecv(?), error:%d when PushIoContext", + (int32_t)this, + error); + } + // Roll back. + OutstandingCallCompleted(); + return -1; +} + +void UdpSocket2Windows::CloseBlocking() +{ + LINGER lingerStruct; + + lingerStruct.l_onoff = 1; + lingerStruct.l_linger = 0; + if(AquireSocket()) + { + setsockopt(_socket, SOL_SOCKET, SO_LINGER, + reinterpret_cast<const char*>(&lingerStruct), + sizeof(lingerStruct)); + ReleaseSocket(); + } + + _wantsIncoming = false; + // Reclaims the socket and prevents it from being used again. + InvalidateSocket(); + DisableNewOutstandingCalls(); + WaitForOutstandingCalls(); + delete this; +} + +bool UdpSocket2Windows::SetQos(int32_t serviceType, + int32_t tokenRate, + int32_t bucketSize, + int32_t peekBandwith, + int32_t minPolicedSize, + int32_t maxSduSize, + const SocketAddress &stRemName, + int32_t overrideDSCP) +{ + if(_qos == false) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocket2Windows::SetQos(), socket not capable of QOS"); + return false; + } + if(overrideDSCP != 0) + { + FLOWSPEC f; + int32_t err = CreateFlowSpec(serviceType, tokenRate, bucketSize, + peekBandwith, minPolicedSize, + maxSduSize, &f); + if(err == -1) + { + return false; + } + + SocketAddress socketName; + struct sockaddr_in* name = + reinterpret_cast<struct sockaddr_in*>(&socketName); + int nameLength = sizeof(SocketAddress); + if(AquireSocket()) + { + getsockname(_socket, (struct sockaddr*)name, &nameLength); + ReleaseSocket(); + } + + if(serviceType == 0) + { + // Disable TOS byte setting. + return SetTrafficControl(0, -1, name, &f, &f) == 0; + } + return SetTrafficControl(overrideDSCP, -1, name, &f, &f) == 0; + } + + QOS Qos; + DWORD BytesRet; + QOS_DESTADDR QosDestaddr; + + memset (&Qos, QOS_NOT_SPECIFIED, sizeof(QOS)); + + Qos.SendingFlowspec.ServiceType = serviceType; + Qos.SendingFlowspec.TokenRate = tokenRate; + Qos.SendingFlowspec.TokenBucketSize = QOS_NOT_SPECIFIED; + Qos.SendingFlowspec.PeakBandwidth = QOS_NOT_SPECIFIED; + Qos.SendingFlowspec.DelayVariation = QOS_NOT_SPECIFIED; + Qos.SendingFlowspec.Latency = QOS_NOT_SPECIFIED; + Qos.SendingFlowspec.MinimumPolicedSize = QOS_NOT_SPECIFIED; + Qos.SendingFlowspec.MaxSduSize = QOS_NOT_SPECIFIED; + + // Only ServiceType is needed for receiving. + Qos.ReceivingFlowspec.ServiceType = serviceType; + Qos.ReceivingFlowspec.TokenRate = QOS_NOT_SPECIFIED; + Qos.ReceivingFlowspec.TokenBucketSize = QOS_NOT_SPECIFIED; + Qos.ReceivingFlowspec.PeakBandwidth = QOS_NOT_SPECIFIED; + Qos.ReceivingFlowspec.Latency = QOS_NOT_SPECIFIED; + Qos.ReceivingFlowspec.DelayVariation = QOS_NOT_SPECIFIED; + Qos.ReceivingFlowspec.MinimumPolicedSize = QOS_NOT_SPECIFIED; + Qos.ReceivingFlowspec.MaxSduSize = QOS_NOT_SPECIFIED; + + Qos.ProviderSpecific.len = 0; + + Qos.ProviderSpecific.buf = NULL; + + ZeroMemory((int8_t *)&QosDestaddr, sizeof(QosDestaddr)); + + OSVERSIONINFOEX osvie; + osvie.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + GetVersionEx((LPOSVERSIONINFO)&osvie); + +// Operating system Version number dwMajorVersion dwMinorVersion +// Windows 7 6.1 6 1 +// Windows Server 2008 R2 6.1 6 1 +// Windows Server 2008 6.0 6 0 +// Windows Vista 6.0 6 0 +// Windows Server 2003 R2 5.2 5 2 +// Windows Server 2003 5.2 5 2 +// Windows XP 5.1 5 1 +// Windows 2000 5.0 5 0 + + // SERVICE_NO_QOS_SIGNALING and QOS_DESTADDR should not be used if version + // is 6.0 or greater. + if(osvie.dwMajorVersion >= 6) + { + Qos.SendingFlowspec.MinimumPolicedSize = QOS_NOT_SPECIFIED; + Qos.ReceivingFlowspec.ServiceType = serviceType; + + } else { + Qos.SendingFlowspec.MinimumPolicedSize = + QOS_NOT_SPECIFIED | SERVICE_NO_QOS_SIGNALING; + Qos.ReceivingFlowspec.ServiceType = + serviceType | SERVICE_NO_QOS_SIGNALING; + + QosDestaddr.ObjectHdr.ObjectType = QOS_OBJECT_DESTADDR; + QosDestaddr.ObjectHdr.ObjectLength = sizeof(QosDestaddr); + QosDestaddr.SocketAddress = (SOCKADDR *)&stRemName; + if (AF_INET6 == _iProtocol) + { + QosDestaddr.SocketAddressLength = sizeof(SocketAddressInVersion6); + } else { + QosDestaddr.SocketAddressLength = sizeof(SocketAddressIn); + } + + Qos.ProviderSpecific.len = QosDestaddr.ObjectHdr.ObjectLength; + Qos.ProviderSpecific.buf = (char*)&QosDestaddr; + } + + if(!AquireSocket()) { + return false; + } + // To set QoS with SIO_SET_QOS the socket must be locally bound first + // or the call will fail with error code 10022. + int32_t result = WSAIoctl(GetFd(), SIO_SET_QOS, &Qos, sizeof(QOS), + NULL, 0, &BytesRet, NULL,NULL); + ReleaseSocket(); + if (result == SOCKET_ERROR) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocket2Windows::SetQos() WSAerror : %d", + WSAGetLastError()); + return false; + } + return true; +} + +int32_t UdpSocket2Windows::SetTOS(int32_t serviceType) +{ + SocketAddress socketName; + + struct sockaddr_in* name = + reinterpret_cast<struct sockaddr_in*>(&socketName); + int nameLength = sizeof(SocketAddress); + if(AquireSocket()) + { + getsockname(_socket, (struct sockaddr*)name, &nameLength); + ReleaseSocket(); + } + + int32_t res = SetTrafficControl(serviceType, -1, name); + if (res == -1) + { + OSVERSIONINFO OsVersion; + OsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx (&OsVersion); + + if ((OsVersion.dwMajorVersion == 4)) // NT 4.0 + { + if(SetSockopt(IPPROTO_IP,IP_TOS , + (int8_t*)&serviceType, 4) != 0) + { + return -1; + } + } + } + return res; +} + +int32_t UdpSocket2Windows::SetPCP(int32_t pcp) +{ + SocketAddress socketName; + struct sockaddr_in* name = + reinterpret_cast<struct sockaddr_in*>(&socketName); + int nameLength = sizeof(SocketAddress); + if(AquireSocket()) + { + getsockname(_socket, (struct sockaddr*)name, &nameLength); + ReleaseSocket(); + } + return SetTrafficControl(-1, pcp, name); +} + +int32_t UdpSocket2Windows::SetTrafficControl( + int32_t dscp, + int32_t pcp, + const struct sockaddr_in* name, + FLOWSPEC* send, FLOWSPEC* recv) +{ + if (pcp == _pcp) + { + // No change. + pcp = -1; + } + if ((-1 == pcp) && (-1 == dscp)) + { + return 0; + } + if (!_gtc) + { + _gtc = TrafficControlWindows::GetInstance(_id); + } + if (!_gtc) + { + return -1; + } + if(_filterHandle) + { + _gtc->TcDeleteFilter(_filterHandle); + _filterHandle = NULL; + } + if(_flowHandle) + { + _gtc->TcDeleteFlow(_flowHandle); + _flowHandle = NULL; + } + if(_clientHandle) + { + _gtc->TcDeregisterClient(_clientHandle); + _clientHandle = NULL; + } + if ((0 == dscp) && (-2 == _pcp) && (-1 == pcp)) + { + // TODO (pwestin): why is this not done before deleting old filter and + // flow? This scenario should probably be documented in + // the function declaration. + return 0; + } + + TCI_CLIENT_FUNC_LIST QoSFunctions; + QoSFunctions.ClAddFlowCompleteHandler = NULL; + QoSFunctions.ClDeleteFlowCompleteHandler = NULL; + QoSFunctions.ClModifyFlowCompleteHandler = NULL; + QoSFunctions.ClNotifyHandler = (TCI_NOTIFY_HANDLER)MyClNotifyHandler; + // Register the client with Traffic control interface. + HANDLE ClientHandle; + ULONG result = _gtc->TcRegisterClient(CURRENT_TCI_VERSION, NULL, + &QoSFunctions,&ClientHandle); + if(result != NO_ERROR) + { + // This is likely caused by the application not being run as + // administrator. + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "TcRegisterClient returned %d", result); + return result; + } + + // Find traffic control-enabled network interfaces that matches this + // socket's IP address. + ULONG BufferSize = 0; + result = _gtc->TcEnumerateInterfaces(ClientHandle, &BufferSize, NULL); + + if(result != NO_ERROR && result != ERROR_INSUFFICIENT_BUFFER) + { + _gtc->TcDeregisterClient(ClientHandle); + return result; + } + + if(result != ERROR_INSUFFICIENT_BUFFER) + { + // Empty buffer contains all control-enabled network interfaces. I.e. + // QoS is not enabled. + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "QOS faild since QOS is not installed on the interface"); + + _gtc->TcDeregisterClient(ClientHandle); + return -1; + } + + PTC_IFC_DESCRIPTOR pInterfaceBuffer = + (PTC_IFC_DESCRIPTOR)malloc(BufferSize); + if(pInterfaceBuffer == NULL) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Out ot memory failure"); + _gtc->TcDeregisterClient(ClientHandle); + return ERROR_NOT_ENOUGH_MEMORY; + } + + result = _gtc->TcEnumerateInterfaces(ClientHandle, &BufferSize, + pInterfaceBuffer); + + if(result != NO_ERROR) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "Critical: error enumerating interfaces when passing in correct\ + buffer size: %d", result); + _gtc->TcDeregisterClient(ClientHandle); + free(pInterfaceBuffer); + return result; + } + + PTC_IFC_DESCRIPTOR oneinterface; + HANDLE ifcHandle, iFilterHandle, iflowHandle; + bool addrFound = false; + ULONG filterSourceAddress = ULONG_MAX; + + // Find the interface corresponding to the local address. + for(oneinterface = pInterfaceBuffer; + oneinterface != (PTC_IFC_DESCRIPTOR) + (((int8_t*)pInterfaceBuffer) + BufferSize); + oneinterface = (PTC_IFC_DESCRIPTOR) + ((int8_t *)oneinterface + oneinterface->Length)) + { + + char interfaceName[500]; + WideCharToMultiByte(CP_ACP, 0, oneinterface->pInterfaceName, -1, + interfaceName, sizeof(interfaceName), 0, 0 ); + + PNETWORK_ADDRESS_LIST addresses = + &(oneinterface->AddressListDesc.AddressList); + for(LONG i = 0; i < addresses->AddressCount ; i++) + { + // Only look at TCP/IP addresses. + if(addresses->Address[i].AddressType != NDIS_PROTOCOL_ID_TCP_IP) + { + continue; + } + + NETWORK_ADDRESS_IP* pIpAddr = + (NETWORK_ADDRESS_IP*)&(addresses->Address[i].Address); + struct in_addr in; + in.S_un.S_addr = pIpAddr->in_addr; + if(pIpAddr->in_addr == name->sin_addr.S_un.S_addr) + { + filterSourceAddress = pIpAddr->in_addr; + addrFound = true; + } + } + if(!addrFound) + { + continue; + } else + { + break; + } + } + if(!addrFound) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "QOS faild since address is not found"); + _gtc->TcDeregisterClient(ClientHandle); + free(pInterfaceBuffer); + return -1; + } + result = _gtc->TcOpenInterfaceW(oneinterface->pInterfaceName, ClientHandle, + NULL, &ifcHandle); + if(result != NO_ERROR) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Error opening interface: %d", result); + _gtc->TcDeregisterClient(ClientHandle); + free(pInterfaceBuffer); + return result; + } + + // Create flow if one doesn't exist. + if (!_flow) + { + bool addPCP = ((pcp >= 0) || ((-1 == pcp) && (_pcp >= 0))); + int allocSize = sizeof(TC_GEN_FLOW) + sizeof(QOS_DS_CLASS) + + (addPCP ? sizeof(QOS_TRAFFIC_CLASS) : 0); + _flow = (PTC_GEN_FLOW)malloc(allocSize); + + _flow->SendingFlowspec.DelayVariation = QOS_NOT_SPECIFIED; + _flow->SendingFlowspec.Latency = QOS_NOT_SPECIFIED; + _flow->SendingFlowspec.MaxSduSize = QOS_NOT_SPECIFIED; + _flow->SendingFlowspec.MinimumPolicedSize = QOS_NOT_SPECIFIED; + _flow->SendingFlowspec.PeakBandwidth = QOS_NOT_SPECIFIED; + _flow->SendingFlowspec.ServiceType = SERVICETYPE_BESTEFFORT; + _flow->SendingFlowspec.TokenBucketSize = QOS_NOT_SPECIFIED; + _flow->SendingFlowspec.TokenRate = QOS_NOT_SPECIFIED; + + _flow->ReceivingFlowspec.DelayVariation = QOS_NOT_SPECIFIED; + _flow->ReceivingFlowspec.Latency = QOS_NOT_SPECIFIED; + _flow->ReceivingFlowspec.MaxSduSize = QOS_NOT_SPECIFIED; + _flow->ReceivingFlowspec.MinimumPolicedSize = QOS_NOT_SPECIFIED; + _flow->ReceivingFlowspec.PeakBandwidth = QOS_NOT_SPECIFIED; + _flow->ReceivingFlowspec.ServiceType = SERVICETYPE_BESTEFFORT; + _flow->ReceivingFlowspec.TokenBucketSize = QOS_NOT_SPECIFIED; + _flow->ReceivingFlowspec.TokenRate = QOS_NOT_SPECIFIED; + + QOS_DS_CLASS* dsClass = (QOS_DS_CLASS*)_flow->TcObjects; + dsClass->DSField = 0; + dsClass->ObjectHdr.ObjectType = QOS_OBJECT_DS_CLASS; + dsClass->ObjectHdr.ObjectLength = sizeof(QOS_DS_CLASS); + + if (addPCP) + { + QOS_TRAFFIC_CLASS* trafficClass = (QOS_TRAFFIC_CLASS*)(dsClass + 1); + trafficClass->TrafficClass = 0; + trafficClass->ObjectHdr.ObjectType = QOS_OBJECT_TRAFFIC_CLASS; + trafficClass->ObjectHdr.ObjectLength = sizeof(QOS_TRAFFIC_CLASS); + } + + _flow->TcObjectsLength = sizeof(QOS_DS_CLASS) + + (addPCP ? sizeof(QOS_TRAFFIC_CLASS) : 0); + } else if (-1 != pcp) { + // Reallocate memory since pcp has changed. + PTC_GEN_FLOW oldFlow = _flow; + bool addPCP = (pcp >= 0); + int allocSize = sizeof(TC_GEN_FLOW) + sizeof(QOS_DS_CLASS) + + (addPCP ? sizeof(QOS_TRAFFIC_CLASS) : 0); + _flow = (PTC_GEN_FLOW)malloc(allocSize); + + // Copy old flow. + _flow->ReceivingFlowspec = oldFlow->ReceivingFlowspec; + _flow->SendingFlowspec = oldFlow->SendingFlowspec; + // The DS info is always the first object. + QOS_DS_CLASS* dsClass = (QOS_DS_CLASS*)_flow->TcObjects; + QOS_DS_CLASS* oldDsClass = (QOS_DS_CLASS*)oldFlow->TcObjects; + dsClass->DSField = oldDsClass->DSField; + dsClass->ObjectHdr.ObjectType = oldDsClass->ObjectHdr.ObjectType; + dsClass->ObjectHdr.ObjectLength = oldDsClass->ObjectHdr.ObjectLength; + + if (addPCP) + { + QOS_TRAFFIC_CLASS* trafficClass = (QOS_TRAFFIC_CLASS*)(dsClass + 1); + trafficClass->TrafficClass = 0; + trafficClass->ObjectHdr.ObjectType = QOS_OBJECT_TRAFFIC_CLASS; + trafficClass->ObjectHdr.ObjectLength = sizeof(QOS_TRAFFIC_CLASS); + } + + _flow->TcObjectsLength = sizeof(QOS_DS_CLASS) + + (addPCP ? sizeof(QOS_TRAFFIC_CLASS) : 0); + free(oldFlow); + } + + // Setup send and receive flow and DS object. + if (dscp >= 0) + { + if (!send || (0 == dscp)) + { + _flow->SendingFlowspec.DelayVariation = QOS_NOT_SPECIFIED; + _flow->SendingFlowspec.Latency = QOS_NOT_SPECIFIED; + _flow->SendingFlowspec.MaxSduSize = QOS_NOT_SPECIFIED; + _flow->SendingFlowspec.MinimumPolicedSize = QOS_NOT_SPECIFIED; + _flow->SendingFlowspec.PeakBandwidth = + (0 == dscp ? QOS_NOT_SPECIFIED : POSITIVE_INFINITY_RATE); + _flow->SendingFlowspec.ServiceType = SERVICETYPE_BESTEFFORT; + _flow->SendingFlowspec.TokenBucketSize = QOS_NOT_SPECIFIED; + // 128000 * 10 is 10mbit/s. + _flow->SendingFlowspec.TokenRate = + (0 == dscp ? QOS_NOT_SPECIFIED : 128000 * 10); + } + else + { + _flow->SendingFlowspec.DelayVariation = send->DelayVariation; + _flow->SendingFlowspec.Latency = send->Latency; + _flow->SendingFlowspec.MaxSduSize = send->MaxSduSize; + _flow->SendingFlowspec.MinimumPolicedSize = + send->MinimumPolicedSize; + _flow->SendingFlowspec.PeakBandwidth = send->PeakBandwidth; + _flow->SendingFlowspec.PeakBandwidth = POSITIVE_INFINITY_RATE; + _flow->SendingFlowspec.ServiceType = send->ServiceType; + _flow->SendingFlowspec.TokenBucketSize = send->TokenBucketSize; + _flow->SendingFlowspec.TokenRate = send->TokenRate; + } + + if (!recv || (0 == dscp)) + { + _flow->ReceivingFlowspec.DelayVariation = + _flow->SendingFlowspec.DelayVariation; + _flow->ReceivingFlowspec.Latency = _flow->SendingFlowspec.Latency; + _flow->ReceivingFlowspec.MaxSduSize = + _flow->SendingFlowspec.MaxSduSize; + _flow->ReceivingFlowspec.MinimumPolicedSize = + _flow->SendingFlowspec.MinimumPolicedSize; + _flow->ReceivingFlowspec.PeakBandwidth = QOS_NOT_SPECIFIED; + _flow->ReceivingFlowspec.ServiceType = + 0 == dscp ? SERVICETYPE_BESTEFFORT : SERVICETYPE_CONTROLLEDLOAD; + _flow->ReceivingFlowspec.TokenBucketSize = + _flow->SendingFlowspec.TokenBucketSize; + _flow->ReceivingFlowspec.TokenRate = + _flow->SendingFlowspec.TokenRate; + } else { + _flow->ReceivingFlowspec.DelayVariation = recv->DelayVariation; + _flow->ReceivingFlowspec.Latency = recv->Latency; + _flow->ReceivingFlowspec.MaxSduSize = recv->MaxSduSize; + _flow->ReceivingFlowspec.MinimumPolicedSize = + recv->MinimumPolicedSize; + _flow->ReceivingFlowspec.PeakBandwidth = recv->PeakBandwidth; + _flow->ReceivingFlowspec.ServiceType = recv->ServiceType; + _flow->ReceivingFlowspec.TokenBucketSize = recv->TokenBucketSize; + _flow->ReceivingFlowspec.TokenRate = QOS_NOT_SPECIFIED; + } + + // Setup DS (for DSCP value). + // DS is always the first object. + QOS_DS_CLASS* dsClass = (QOS_DS_CLASS*)_flow->TcObjects; + dsClass->DSField = dscp; + } + + // Setup PCP (802.1p priority in 802.1Q/VLAN tagging) + if (pcp >= 0) + { + // DS is always first object. + QOS_DS_CLASS* dsClass = (QOS_DS_CLASS*)_flow->TcObjects; + QOS_TRAFFIC_CLASS* trafficClass = (QOS_TRAFFIC_CLASS*)(dsClass + 1); + trafficClass->TrafficClass = pcp; + } + + result = _gtc->TcAddFlow(ifcHandle, NULL, 0, _flow, &iflowHandle); + if(result != NO_ERROR) + { + _gtc->TcCloseInterface(ifcHandle); + _gtc->TcDeregisterClient(ClientHandle); + free(pInterfaceBuffer); + return -1; + } + + IP_PATTERN filterPattern, mask; + + ZeroMemory((int8_t*)&filterPattern, sizeof(IP_PATTERN)); + ZeroMemory((int8_t*)&mask, sizeof(IP_PATTERN)); + + filterPattern.ProtocolId = IPPROTO_UDP; + // "name" fields already in network order. + filterPattern.S_un.S_un_ports.s_srcport = name->sin_port; + filterPattern.SrcAddr = filterSourceAddress; + + // Unsigned max of a type corresponds to a bitmask with all bits set to 1. + // I.e. the filter should allow all ProtocolIds, any source port and any + // IP address + mask.ProtocolId = UCHAR_MAX; + mask.S_un.S_un_ports.s_srcport = USHRT_MAX; + mask.SrcAddr = ULONG_MAX; + + TC_GEN_FILTER filter; + + filter.AddressType = NDIS_PROTOCOL_ID_TCP_IP; + filter.Mask = (LPVOID)&mask; + filter.Pattern = (LPVOID)&filterPattern; + filter.PatternSize = sizeof(IP_PATTERN); + + result = _gtc->TcAddFilter(iflowHandle, &filter, &iFilterHandle); + if(result != NO_ERROR) + { + _gtc->TcDeleteFlow(iflowHandle); + _gtc->TcCloseInterface(ifcHandle); + _gtc->TcDeregisterClient(ClientHandle); + free(pInterfaceBuffer); + return result; + } + + _flowHandle = iflowHandle; + _filterHandle = iFilterHandle; + _clientHandle = ClientHandle; + if (-1 != pcp) + { + _pcp = pcp; + } + + _gtc->TcCloseInterface(ifcHandle); + free(pInterfaceBuffer); + + return 0; +} + +int32_t UdpSocket2Windows::CreateFlowSpec(int32_t serviceType, + int32_t tokenRate, + int32_t bucketSize, + int32_t peekBandwith, + int32_t minPolicedSize, + int32_t maxSduSize, + FLOWSPEC* f) +{ + if (!f) + { + return -1; + } + + f->ServiceType = serviceType; + f->TokenRate = tokenRate; + f->TokenBucketSize = QOS_NOT_SPECIFIED; + f->PeakBandwidth = QOS_NOT_SPECIFIED; + f->DelayVariation = QOS_NOT_SPECIFIED; + f->Latency = QOS_NOT_SPECIFIED; + f->MaxSduSize = QOS_NOT_SPECIFIED; + f->MinimumPolicedSize = QOS_NOT_SPECIFIED; + return 0; +} + +bool UdpSocket2Windows::NewOutstandingCall() +{ + assert(!_outstandingCallsDisabled); + + ++_outstandingCalls; + return true; +} + +void UdpSocket2Windows::OutstandingCallCompleted() +{ + _ptrDestRWLock->AcquireLockShared(); + ++_outstandingCallComplete; + if((--_outstandingCalls == 0) && _outstandingCallsDisabled) + { + // When there are no outstanding calls and new outstanding calls are + // disabled it is time to terminate. + _terminate = true; + } + _ptrDestRWLock->ReleaseLockShared(); + + if((--_outstandingCallComplete == 0) && + (_terminate)) + { + // Only one thread will enter here. The thread with the last outstanding + // call. + CriticalSectionScoped cs(_ptrDeleteCrit); + _safeTodelete = true; + _ptrDeleteCond->Wake(); + } +} + +void UdpSocket2Windows::DisableNewOutstandingCalls() +{ + _ptrDestRWLock->AcquireLockExclusive(); + if(_outstandingCallsDisabled) + { + // Outstandning calls are already disabled. + _ptrDestRWLock->ReleaseLockExclusive(); + return; + } + _outstandingCallsDisabled = true; + const bool noOutstandingCalls = (_outstandingCalls.Value() == 0); + _ptrDestRWLock->ReleaseLockExclusive(); + + RemoveSocketFromManager(); + + if(noOutstandingCalls) + { + CriticalSectionScoped cs(_ptrDeleteCrit); + _safeTodelete = true; + _ptrDeleteCond->Wake(); + } +} + +void UdpSocket2Windows::WaitForOutstandingCalls() +{ + CriticalSectionScoped cs(_ptrDeleteCrit); + while(!_safeTodelete) + { + _ptrDeleteCond->SleepCS(*_ptrDeleteCrit); + } +} + +void UdpSocket2Windows::RemoveSocketFromManager() +{ + // New outstanding calls should be disabled at this point. + assert(_outstandingCallsDisabled); + + if(_addedToMgr) + { + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "calling UdpSocketManager::RemoveSocket()"); + if(_mgr->RemoveSocket(this)) + { + _addedToMgr=false; + } + } +} + +bool UdpSocket2Windows::AquireSocket() +{ + _ptrSocketRWLock->AcquireLockShared(); + const bool returnValue = _socket != INVALID_SOCKET; + if(!returnValue) + { + _ptrSocketRWLock->ReleaseLockShared(); + } + return returnValue; +} + +void UdpSocket2Windows::ReleaseSocket() +{ + _ptrSocketRWLock->ReleaseLockShared(); +} + +bool UdpSocket2Windows::InvalidateSocket() +{ + _ptrSocketRWLock->AcquireLockExclusive(); + if(_socket == INVALID_SOCKET) + { + _ptrSocketRWLock->ReleaseLockExclusive(); + return true; + } + // Give the socket back to the system. All socket calls will fail from now + // on. + if(closesocket(_socket) == SOCKET_ERROR) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocket2Windows(%d)::InvalidateSocket() WSAerror: %d", + (int32_t)this, WSAGetLastError()); + } + _socket = INVALID_SOCKET; + _ptrSocketRWLock->ReleaseLockExclusive(); + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/channel_transport/udp_socket2_win.h b/webrtc/test/channel_transport/udp_socket2_win.h new file mode 100644 index 0000000000..ea37ac47c5 --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket2_win.h @@ -0,0 +1,179 @@ +/* + * 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_TEST_CHANNEL_TRANSPORT_UDP_SOCKET2_WINDOWS_H_ +#define WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_SOCKET2_WINDOWS_H_ + +// Disable deprication warning from traffic.h +#pragma warning(disable : 4995) + +// Don't change include order for these header files. +#include <Winsock2.h> +#include <Ntddndis.h> +#include <traffic.h> + +#include "webrtc/system_wrappers/include/atomic32.h" +#include "webrtc/system_wrappers/include/condition_variable_wrapper.h" +#include "webrtc/system_wrappers/include/critical_section_wrapper.h" +#include "webrtc/system_wrappers/include/event_wrapper.h" +#include "webrtc/system_wrappers/include/rw_lock_wrapper.h" +#include "webrtc/system_wrappers/include/trace.h" +#include "webrtc/test/channel_transport/udp_socket2_manager_win.h" +#include "webrtc/test/channel_transport/udp_socket_wrapper.h" + +namespace webrtc { +namespace test { + +class UdpSocket2ManagerWindows; +class TrafficControlWindows; +struct PerIoContext; + +class UdpSocket2Windows : public UdpSocketWrapper +{ +public: + UdpSocket2Windows(const int32_t id, UdpSocketManager* mgr, + bool ipV6Enable = false, bool disableGQOS = false); + virtual ~UdpSocket2Windows(); + + bool ValidHandle() override; + + bool SetCallback(CallbackObj, IncomingSocketCallback) override; + + bool Bind(const SocketAddress& name) override; + bool SetSockopt(int32_t level, + int32_t optname, + const int8_t* optval, + int32_t optlen) override; + + bool StartReceiving(const uint32_t receiveBuffers) override; + inline bool StartReceiving() override { return StartReceiving(8); } + bool StopReceiving() override; + + int32_t SendTo(const int8_t* buf, + size_t len, + const SocketAddress& to) override; + + void CloseBlocking() override; + + SOCKET GetFd() { return _socket;} + + bool SetQos(int32_t serviceType, + int32_t tokenRate, + int32_t bucketSize, + int32_t peekBandwith, + int32_t minPolicedSize, + int32_t maxSduSize, + const SocketAddress& stRemName, + int32_t overrideDSCP = 0) override; + + int32_t SetTOS(const int32_t serviceType) override; + int32_t SetPCP(const int32_t pcp) override; + + uint32_t ReceiveBuffers() override { return _receiveBuffers.Value(); } + +protected: + void IOCompleted(PerIoContext* pIOContext, uint32_t ioSize, uint32_t error); + + int32_t PostRecv(); + // Use pIoContext to post a new WSARecvFrom(..). + int32_t PostRecv(PerIoContext* pIoContext); + +private: + friend class UdpSocket2WorkerWindows; + + // Set traffic control (TC) flow adding it the interface that matches this + // sockets address. + // A filter is created and added to the flow. + // The flow consists of: + // (1) QoS send and receive information (flow specifications). + // (2) A DS object (for specifying exact DSCP value). + // (3) Possibly a traffic object (for specifying exact 802.1p priority (PCP) + // value). + // + // dscp values: + // -1 don't change the current dscp value. + // 0 don't add any flow to TC, unless pcp is specified. + // 1-63 Add a flow to TC with the specified dscp value. + // pcp values: + // -2 Don't add pcp info to the flow, (3) will not be added. + // -1 Don't change the current value. + // 0-7 Add pcp info to the flow with the specified value, + // (3) will be added. + // + // If both dscp and pcp are -1 no flow will be created or added to TC. + // If dscp is 0 and pcp is 0-7 (1), (2) and (3) will be created. + // Note: input parameter values are assumed to be in valid range, checks + // must be done by caller. + int32_t SetTrafficControl(int32_t dscp, int32_t pcp, + const struct sockaddr_in* name, + FLOWSPEC* send = NULL, + FLOWSPEC* recv = NULL); + int32_t CreateFlowSpec(int32_t serviceType, + int32_t tokenRate, + int32_t bucketSize, + int32_t peekBandwith, + int32_t minPolicedSize, + int32_t maxSduSize, FLOWSPEC *f); + + int32_t _id; + RWLockWrapper* _ptrCbRWLock; + IncomingSocketCallback _incomingCb; + CallbackObj _obj; + bool _qos; + + SocketAddress _remoteAddr; + SOCKET _socket; + int32_t _iProtocol; + UdpSocket2ManagerWindows* _mgr; + + CriticalSectionWrapper* _pCrit; + Atomic32 _outstandingCalls; + Atomic32 _outstandingCallComplete; + volatile bool _terminate; + volatile bool _addedToMgr; + + CriticalSectionWrapper* _ptrDeleteCrit; + ConditionVariableWrapper* _ptrDeleteCond; + bool _safeTodelete; + + RWLockWrapper* _ptrDestRWLock; + bool _outstandingCallsDisabled; + bool NewOutstandingCall(); + void OutstandingCallCompleted(); + void DisableNewOutstandingCalls(); + void WaitForOutstandingCalls(); + + void RemoveSocketFromManager(); + + // RWLockWrapper is used as a reference counter for the socket. Write lock + // is used for creating and deleting socket. Read lock is used for + // accessing the socket. + RWLockWrapper* _ptrSocketRWLock; + bool AquireSocket(); + void ReleaseSocket(); + bool InvalidateSocket(); + + // Traffic control handles and structure pointers. + HANDLE _clientHandle; + HANDLE _flowHandle; + HANDLE _filterHandle; + PTC_GEN_FLOW _flow; + // TrafficControlWindows implements TOS and PCP. + TrafficControlWindows* _gtc; + // Holds the current pcp value. Can be -2 or 0 - 7. + int _pcp; + + Atomic32 _receiveBuffers; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_SOCKET2_WINDOWS_H_ diff --git a/webrtc/test/channel_transport/udp_socket_manager_posix.cc b/webrtc/test/channel_transport/udp_socket_manager_posix.cc new file mode 100644 index 0000000000..145efcbc58 --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket_manager_posix.cc @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2011 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/channel_transport/udp_socket_manager_posix.h" + +#include <stdio.h> +#include <strings.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include "webrtc/system_wrappers/include/sleep.h" +#include "webrtc/system_wrappers/include/trace.h" +#include "webrtc/test/channel_transport/udp_socket_posix.h" + +namespace webrtc { +namespace test { + +UdpSocketManagerPosix::UdpSocketManagerPosix() + : UdpSocketManager(), + _id(-1), + _critSect(CriticalSectionWrapper::CreateCriticalSection()), + _numberOfSocketMgr(-1), + _incSocketMgrNextTime(0), + _nextSocketMgrToAssign(0), + _socketMgr() +{ +} + +bool UdpSocketManagerPosix::Init(int32_t id, uint8_t& numOfWorkThreads) { + CriticalSectionScoped cs(_critSect); + if ((_id != -1) || (_numOfWorkThreads != 0)) { + assert(_id != -1); + assert(_numOfWorkThreads != 0); + return false; + } + + _id = id; + _numberOfSocketMgr = numOfWorkThreads; + _numOfWorkThreads = numOfWorkThreads; + + if(MAX_NUMBER_OF_SOCKET_MANAGERS_LINUX < _numberOfSocketMgr) + { + _numberOfSocketMgr = MAX_NUMBER_OF_SOCKET_MANAGERS_LINUX; + } + for(int i = 0;i < _numberOfSocketMgr; i++) + { + _socketMgr[i] = new UdpSocketManagerPosixImpl(); + } + return true; +} + + +UdpSocketManagerPosix::~UdpSocketManagerPosix() +{ + Stop(); + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocketManagerPosix(%d)::UdpSocketManagerPosix()", + _numberOfSocketMgr); + + for(int i = 0;i < _numberOfSocketMgr; i++) + { + delete _socketMgr[i]; + } + delete _critSect; +} + +bool UdpSocketManagerPosix::Start() +{ + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocketManagerPosix(%d)::Start()", + _numberOfSocketMgr); + + _critSect->Enter(); + bool retVal = true; + for(int i = 0;i < _numberOfSocketMgr && retVal; i++) + { + retVal = _socketMgr[i]->Start(); + } + if(!retVal) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocketManagerPosix(%d)::Start() error starting socket managers", + _numberOfSocketMgr); + } + _critSect->Leave(); + return retVal; +} + +bool UdpSocketManagerPosix::Stop() +{ + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocketManagerPosix(%d)::Stop()",_numberOfSocketMgr); + + _critSect->Enter(); + bool retVal = true; + for(int i = 0; i < _numberOfSocketMgr && retVal; i++) + { + retVal = _socketMgr[i]->Stop(); + } + if(!retVal) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocketManagerPosix(%d)::Stop() there are still active socket " + "managers", + _numberOfSocketMgr); + } + _critSect->Leave(); + return retVal; +} + +bool UdpSocketManagerPosix::AddSocket(UdpSocketWrapper* s) +{ + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocketManagerPosix(%d)::AddSocket()",_numberOfSocketMgr); + + _critSect->Enter(); + bool retVal = _socketMgr[_nextSocketMgrToAssign]->AddSocket(s); + if(!retVal) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocketManagerPosix(%d)::AddSocket() failed to add socket to\ + manager", + _numberOfSocketMgr); + } + + // Distribute sockets on UdpSocketManagerPosixImpls in a round-robin + // fashion. + if(_incSocketMgrNextTime == 0) + { + _incSocketMgrNextTime++; + } else { + _incSocketMgrNextTime = 0; + _nextSocketMgrToAssign++; + if(_nextSocketMgrToAssign >= _numberOfSocketMgr) + { + _nextSocketMgrToAssign = 0; + } + } + _critSect->Leave(); + return retVal; +} + +bool UdpSocketManagerPosix::RemoveSocket(UdpSocketWrapper* s) +{ + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocketManagerPosix(%d)::RemoveSocket()", + _numberOfSocketMgr); + + _critSect->Enter(); + bool retVal = false; + for(int i = 0;i < _numberOfSocketMgr && (retVal == false); i++) + { + retVal = _socketMgr[i]->RemoveSocket(s); + } + if(!retVal) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpSocketManagerPosix(%d)::RemoveSocket() failed to remove socket\ + from manager", + _numberOfSocketMgr); + } + _critSect->Leave(); + return retVal; +} + + +UdpSocketManagerPosixImpl::UdpSocketManagerPosixImpl() +{ + _critSectList = CriticalSectionWrapper::CreateCriticalSection(); + _thread = ThreadWrapper::CreateThread(UdpSocketManagerPosixImpl::Run, this, + "UdpSocketManagerPosixImplThread"); + FD_ZERO(&_readFds); + WEBRTC_TRACE(kTraceMemory, kTraceTransport, -1, + "UdpSocketManagerPosix created"); +} + +UdpSocketManagerPosixImpl::~UdpSocketManagerPosixImpl() +{ + if (_critSectList != NULL) + { + UpdateSocketMap(); + + _critSectList->Enter(); + for (std::map<SOCKET, UdpSocketPosix*>::iterator it = + _socketMap.begin(); + it != _socketMap.end(); + ++it) { + delete it->second; + } + _socketMap.clear(); + _critSectList->Leave(); + + delete _critSectList; + } + + WEBRTC_TRACE(kTraceMemory, kTraceTransport, -1, + "UdpSocketManagerPosix deleted"); +} + +bool UdpSocketManagerPosixImpl::Start() +{ + if (!_thread) + { + return false; + } + + WEBRTC_TRACE(kTraceStateInfo, kTraceTransport, -1, + "Start UdpSocketManagerPosix"); + if (!_thread->Start()) + return false; + _thread->SetPriority(kRealtimePriority); + return true; +} + +bool UdpSocketManagerPosixImpl::Stop() +{ + if (!_thread) + { + return true; + } + + WEBRTC_TRACE(kTraceStateInfo, kTraceTransport, -1, + "Stop UdpSocketManagerPosix"); + return _thread->Stop(); +} + +bool UdpSocketManagerPosixImpl::Process() +{ + bool doSelect = false; + // Timeout = 1 second. + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 10000; + + FD_ZERO(&_readFds); + + UpdateSocketMap(); + + SOCKET maxFd = 0; + for (std::map<SOCKET, UdpSocketPosix*>::iterator it = _socketMap.begin(); + it != _socketMap.end(); + ++it) { + doSelect = true; + if (it->first > maxFd) + maxFd = it->first; + FD_SET(it->first, &_readFds); + } + + int num = 0; + if (doSelect) + { + num = select(maxFd+1, &_readFds, NULL, NULL, &timeout); + + if (num == SOCKET_ERROR) + { + // Timeout = 10 ms. + SleepMs(10); + return true; + } + }else + { + // Timeout = 10 ms. + SleepMs(10); + return true; + } + + for (std::map<SOCKET, UdpSocketPosix*>::iterator it = _socketMap.begin(); + it != _socketMap.end(); + ++it) { + if (FD_ISSET(it->first, &_readFds)) { + it->second->HasIncoming(); + --num; + } + } + + return true; +} + +bool UdpSocketManagerPosixImpl::Run(void* obj) +{ + UdpSocketManagerPosixImpl* mgr = + static_cast<UdpSocketManagerPosixImpl*>(obj); + return mgr->Process(); +} + +bool UdpSocketManagerPosixImpl::AddSocket(UdpSocketWrapper* s) +{ + UdpSocketPosix* sl = static_cast<UdpSocketPosix*>(s); + if(sl->GetFd() == INVALID_SOCKET || !(sl->GetFd() < FD_SETSIZE)) + { + return false; + } + _critSectList->Enter(); + _addList.push_back(s); + _critSectList->Leave(); + return true; +} + +bool UdpSocketManagerPosixImpl::RemoveSocket(UdpSocketWrapper* s) +{ + // Put in remove list if this is the correct UdpSocketManagerPosixImpl. + _critSectList->Enter(); + + // If the socket is in the add list it's safe to remove and delete it. + for (SocketList::iterator iter = _addList.begin(); + iter != _addList.end(); ++iter) { + UdpSocketPosix* addSocket = static_cast<UdpSocketPosix*>(*iter); + unsigned int addFD = addSocket->GetFd(); + unsigned int removeFD = static_cast<UdpSocketPosix*>(s)->GetFd(); + if(removeFD == addFD) + { + _removeList.push_back(removeFD); + _critSectList->Leave(); + return true; + } + } + + // Checking the socket map is safe since all Erase and Insert calls to this + // map are also protected by _critSectList. + if (_socketMap.find(static_cast<UdpSocketPosix*>(s)->GetFd()) != + _socketMap.end()) { + _removeList.push_back(static_cast<UdpSocketPosix*>(s)->GetFd()); + _critSectList->Leave(); + return true; + } + _critSectList->Leave(); + return false; +} + +void UdpSocketManagerPosixImpl::UpdateSocketMap() +{ + // Remove items in remove list. + _critSectList->Enter(); + for (FdList::iterator iter = _removeList.begin(); + iter != _removeList.end(); ++iter) { + UdpSocketPosix* deleteSocket = NULL; + SOCKET removeFD = *iter; + + // If the socket is in the add list it hasn't been added to the socket + // map yet. Just remove the socket from the add list. + for (SocketList::iterator iter = _addList.begin(); + iter != _addList.end(); ++iter) { + UdpSocketPosix* addSocket = static_cast<UdpSocketPosix*>(*iter); + SOCKET addFD = addSocket->GetFd(); + if(removeFD == addFD) + { + deleteSocket = addSocket; + _addList.erase(iter); + break; + } + } + + // Find and remove socket from _socketMap. + std::map<SOCKET, UdpSocketPosix*>::iterator it = + _socketMap.find(removeFD); + if(it != _socketMap.end()) + { + deleteSocket = it->second; + _socketMap.erase(it); + } + if(deleteSocket) + { + deleteSocket->ReadyForDeletion(); + delete deleteSocket; + } + } + _removeList.clear(); + + // Add sockets from add list. + for (SocketList::iterator iter = _addList.begin(); + iter != _addList.end(); ++iter) { + UdpSocketPosix* s = static_cast<UdpSocketPosix*>(*iter); + if(s) { + _socketMap[s->GetFd()] = s; + } + } + _addList.clear(); + _critSectList->Leave(); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/channel_transport/udp_socket_manager_posix.h b/webrtc/test/channel_transport/udp_socket_manager_posix.h new file mode 100644 index 0000000000..64156fd20f --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket_manager_posix.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2011 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_TEST_CHANNEL_TRANSPORT_UDP_SOCKET_MANAGER_POSIX_H_ +#define WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_SOCKET_MANAGER_POSIX_H_ + +#include <sys/types.h> +#include <unistd.h> + +#include <list> +#include <map> + +#include "webrtc/system_wrappers/include/critical_section_wrapper.h" +#include "webrtc/system_wrappers/include/thread_wrapper.h" +#include "webrtc/test/channel_transport/udp_socket_manager_wrapper.h" +#include "webrtc/test/channel_transport/udp_socket_wrapper.h" + +namespace webrtc { + +class ConditionVariableWrapper; + +namespace test { + +class UdpSocketPosix; +class UdpSocketManagerPosixImpl; +#define MAX_NUMBER_OF_SOCKET_MANAGERS_LINUX 8 + +class UdpSocketManagerPosix : public UdpSocketManager +{ +public: + UdpSocketManagerPosix(); + virtual ~UdpSocketManagerPosix(); + + bool Init(int32_t id, uint8_t& numOfWorkThreads) override; + + bool Start() override; + bool Stop() override; + + bool AddSocket(UdpSocketWrapper* s) override; + bool RemoveSocket(UdpSocketWrapper* s) override; + +private: + int32_t _id; + CriticalSectionWrapper* _critSect; + uint8_t _numberOfSocketMgr; + uint8_t _incSocketMgrNextTime; + uint8_t _nextSocketMgrToAssign; + UdpSocketManagerPosixImpl* _socketMgr[MAX_NUMBER_OF_SOCKET_MANAGERS_LINUX]; +}; + +class UdpSocketManagerPosixImpl +{ +public: + UdpSocketManagerPosixImpl(); + virtual ~UdpSocketManagerPosixImpl(); + + virtual bool Start(); + virtual bool Stop(); + + virtual bool AddSocket(UdpSocketWrapper* s); + virtual bool RemoveSocket(UdpSocketWrapper* s); + +protected: + static bool Run(void* obj); + bool Process(); + void UpdateSocketMap(); + +private: + typedef std::list<UdpSocketWrapper*> SocketList; + typedef std::list<SOCKET> FdList; + rtc::scoped_ptr<ThreadWrapper> _thread; + CriticalSectionWrapper* _critSectList; + + fd_set _readFds; + + std::map<SOCKET, UdpSocketPosix*> _socketMap; + SocketList _addList; + FdList _removeList; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_SOCKET_MANAGER_POSIX_H_ diff --git a/webrtc/test/channel_transport/udp_socket_manager_unittest.cc b/webrtc/test/channel_transport/udp_socket_manager_unittest.cc new file mode 100644 index 0000000000..b49021bffe --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket_manager_unittest.cc @@ -0,0 +1,84 @@ +/* + * 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. + */ + +// Tests for the UdpSocketManager interface. +// Note: This tests UdpSocketManager together with UdpSocketWrapper, +// due to the way the code is full of static-casts to the platform dependent +// subtypes. +// It also uses the static UdpSocketManager object. +// The most important property of these tests is that they do not leak memory. + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/system_wrappers/include/trace.h" +#include "webrtc/test/channel_transport/udp_socket_manager_wrapper.h" +#include "webrtc/test/channel_transport/udp_socket_wrapper.h" + +namespace webrtc { +namespace test { + +TEST(UdpSocketManager, CreateCallsInitAndDoesNotLeakMemory) { + int32_t id = 42; + uint8_t threads = 1; + UdpSocketManager* mgr = UdpSocketManager::Create(id, threads); + // Create is supposed to have called init on the object. + EXPECT_FALSE(mgr->Init(id, threads)) + << "Init should return false since Create is supposed to call it."; + UdpSocketManager::Return(); +} + +// Creates a socket and adds it to the socket manager, and then removes it +// before destroying the socket manager. +TEST(UdpSocketManager, AddAndRemoveSocketDoesNotLeakMemory) { + int32_t id = 42; + uint8_t threads = 1; + UdpSocketManager* mgr = UdpSocketManager::Create(id, threads); + UdpSocketWrapper* socket = + UdpSocketWrapper::CreateSocket(id, + mgr, + NULL, // CallbackObj + NULL, // IncomingSocketCallback + false, // ipV6Enable + false); // disableGQOS + // The constructor will do AddSocket on the manager. + // RemoveSocket indirectly calls Delete. + EXPECT_EQ(true, mgr->RemoveSocket(socket)); + UdpSocketManager::Return(); +} + +// Creates a socket and add it to the socket manager, but does not remove it +// before destroying the socket manager. +// On Posix, this destroys the socket. +// On Winsock2 Windows, it enters an infinite wait for all the sockets +// to go away. +TEST(UdpSocketManager, UnremovedSocketsGetCollectedAtManagerDeletion) { +#if defined(_WIN32) + // It's hard to test an infinite wait, so we don't. +#else + int32_t id = 42; + uint8_t threads = 1; + UdpSocketManager* mgr = UdpSocketManager::Create(id, threads); + UdpSocketWrapper* unused_socket = UdpSocketWrapper::CreateSocket( + id, + mgr, + NULL, // CallbackObj + NULL, // IncomingSocketCallback + false, // ipV6Enable + false); // disableGQOS + // The constructor will do AddSocket on the manager. + // Call a member funtion to work around "set but not used" compliation + // error on ChromeOS ARM. + unused_socket->SetEventToNull(); + unused_socket = NULL; + UdpSocketManager::Return(); +#endif +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/channel_transport/udp_socket_manager_wrapper.cc b/webrtc/test/channel_transport/udp_socket_manager_wrapper.cc new file mode 100644 index 0000000000..3127767cbc --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket_manager_wrapper.cc @@ -0,0 +1,72 @@ +/* + * 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/channel_transport/udp_socket_manager_wrapper.h" + +#include <assert.h> + +#ifdef _WIN32 +#include "webrtc/system_wrappers/include/fix_interlocked_exchange_pointer_win.h" +#include "webrtc/test/channel_transport/udp_socket2_manager_win.h" +#else +#include "webrtc/test/channel_transport/udp_socket_manager_posix.h" +#endif + +namespace webrtc { +namespace test { + +UdpSocketManager* UdpSocketManager::CreateInstance() +{ +#if defined(_WIN32) + return static_cast<UdpSocketManager*>(new UdpSocket2ManagerWindows()); +#else + return new UdpSocketManagerPosix(); +#endif +} + +UdpSocketManager* UdpSocketManager::StaticInstance( + CountOperation count_operation, + const int32_t id, + uint8_t& numOfWorkThreads) +{ + UdpSocketManager* impl = + GetStaticInstance<UdpSocketManager>(count_operation); + if (count_operation == kAddRef && impl != NULL) { + if (impl->Init(id, numOfWorkThreads)) { + impl->Start(); + } + } + return impl; +} + +UdpSocketManager* UdpSocketManager::Create(const int32_t id, + uint8_t& numOfWorkThreads) +{ + return UdpSocketManager::StaticInstance(kAddRef, id, numOfWorkThreads); +} + +void UdpSocketManager::Return() +{ + uint8_t numOfWorkThreads = 0; + UdpSocketManager::StaticInstance(kRelease, -1, + numOfWorkThreads); +} + +UdpSocketManager::UdpSocketManager() : _numOfWorkThreads(0) +{ +} + +uint8_t UdpSocketManager::WorkThreads() const +{ + return _numOfWorkThreads; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/channel_transport/udp_socket_manager_wrapper.h b/webrtc/test/channel_transport/udp_socket_manager_wrapper.h new file mode 100644 index 0000000000..0c3c3850d9 --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket_manager_wrapper.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2011 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_TEST_CHANNEL_TRANSPORT_UDP_SOCKET_MANAGER_WRAPPER_H_ +#define WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_SOCKET_MANAGER_WRAPPER_H_ + +#include "webrtc/system_wrappers/include/static_instance.h" +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +class UdpSocketWrapper; + +class UdpSocketManager +{ +public: + static UdpSocketManager* Create(const int32_t id, + uint8_t& numOfWorkThreads); + static void Return(); + + // Initializes the socket manager. Returns true if the manager wasn't + // already initialized. + virtual bool Init(int32_t id, uint8_t& numOfWorkThreads) = 0; + + // Start listening to sockets that have been registered via the + // AddSocket(..) API. + virtual bool Start() = 0; + // Stop listening to sockets. + virtual bool Stop() = 0; + + virtual uint8_t WorkThreads() const; + + // Register a socket with the socket manager. + virtual bool AddSocket(UdpSocketWrapper* s) = 0; + // Unregister a socket from the manager. + virtual bool RemoveSocket(UdpSocketWrapper* s) = 0; + +protected: + UdpSocketManager(); + virtual ~UdpSocketManager() {} + + uint8_t _numOfWorkThreads; + + // Factory method. + static UdpSocketManager* CreateInstance(); + +private: + // Friend function to allow the UDP destructor to be accessed from the + // instance template. + friend UdpSocketManager* webrtc::GetStaticInstance<UdpSocketManager>( + CountOperation count_operation); + + static UdpSocketManager* StaticInstance( + CountOperation count_operation, + const int32_t id, + uint8_t& numOfWorkThreads); +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_SOCKET_MANAGER_WRAPPER_H_ diff --git a/webrtc/test/channel_transport/udp_socket_posix.cc b/webrtc/test/channel_transport/udp_socket_posix.cc new file mode 100644 index 0000000000..639d444f55 --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket_posix.cc @@ -0,0 +1,285 @@ +/* + * 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/channel_transport/udp_socket_posix.h" + +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include "webrtc/system_wrappers/include/trace.h" +#include "webrtc/test/channel_transport/udp_socket_manager_wrapper.h" +#include "webrtc/test/channel_transport/udp_socket_wrapper.h" + +namespace webrtc { +namespace test { +UdpSocketPosix::UdpSocketPosix(const int32_t id, UdpSocketManager* mgr, + bool ipV6Enable) : _id(id) +{ + WEBRTC_TRACE(kTraceMemory, kTraceTransport, id, + "UdpSocketPosix::UdpSocketPosix()"); + + _wantsIncoming = false; + _mgr = mgr; + + _obj = NULL; + _incomingCb = NULL; + _readyForDeletionCond = ConditionVariableWrapper::CreateConditionVariable(); + _closeBlockingCompletedCond = + ConditionVariableWrapper::CreateConditionVariable(); + _cs = CriticalSectionWrapper::CreateCriticalSection(); + _readyForDeletion = false; + _closeBlockingActive = false; + _closeBlockingCompleted= false; + if(ipV6Enable) + { + _socket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + } + else { + _socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + } + + // Set socket to nonblocking mode. + int enable_non_blocking = 1; + if(ioctl(_socket, FIONBIO, &enable_non_blocking) == -1) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, id, + "Failed to make socket nonblocking"); + } + // Enable close on fork for file descriptor so that it will not block until + // forked process terminates. + if(fcntl(_socket, F_SETFD, FD_CLOEXEC) == -1) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, id, + "Failed to set FD_CLOEXEC for socket"); + } +} + +UdpSocketPosix::~UdpSocketPosix() +{ + if(_socket != INVALID_SOCKET) + { + close(_socket); + _socket = INVALID_SOCKET; + } + if(_readyForDeletionCond) + { + delete _readyForDeletionCond; + } + + if(_closeBlockingCompletedCond) + { + delete _closeBlockingCompletedCond; + } + + if(_cs) + { + delete _cs; + } +} + +bool UdpSocketPosix::SetCallback(CallbackObj obj, IncomingSocketCallback cb) +{ + _obj = obj; + _incomingCb = cb; + + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocketPosix(%p)::SetCallback", this); + + if (_mgr->AddSocket(this)) + { + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocketPosix(%p)::SetCallback socket added to manager", + this); + return true; // socket is now ready for action + } + + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "UdpSocketPosix(%p)::SetCallback error adding me to mgr", + this); + return false; +} + +bool UdpSocketPosix::SetSockopt(int32_t level, int32_t optname, + const int8_t* optval, int32_t optlen) +{ + if(0 == setsockopt(_socket, level, optname, optval, optlen )) + { + return true; + } + + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocketPosix::SetSockopt(), error:%d", errno); + return false; +} + +int32_t UdpSocketPosix::SetTOS(int32_t serviceType) +{ + if (SetSockopt(IPPROTO_IP, IP_TOS ,(int8_t*)&serviceType ,4) != 0) + { + return -1; + } + return 0; +} + +bool UdpSocketPosix::Bind(const SocketAddress& name) +{ + int size = sizeof(sockaddr); + if (0 == bind(_socket, reinterpret_cast<const sockaddr*>(&name),size)) + { + return true; + } + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocketPosix::Bind() error: %d", errno); + return false; +} + +int32_t UdpSocketPosix::SendTo(const int8_t* buf, size_t len, + const SocketAddress& to) +{ + int size = sizeof(sockaddr); + int retVal = sendto(_socket,buf, len, 0, + reinterpret_cast<const sockaddr*>(&to), size); + if(retVal == SOCKET_ERROR) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "UdpSocketPosix::SendTo() error: %d", errno); + } + + return retVal; +} + +SOCKET UdpSocketPosix::GetFd() { return _socket; } + +bool UdpSocketPosix::ValidHandle() +{ + return _socket != INVALID_SOCKET; +} + +bool UdpSocketPosix::SetQos(int32_t /*serviceType*/, + int32_t /*tokenRate*/, + int32_t /*bucketSize*/, + int32_t /*peekBandwith*/, + int32_t /*minPolicedSize*/, + int32_t /*maxSduSize*/, + const SocketAddress& /*stRemName*/, + int32_t /*overrideDSCP*/) { + return false; +} + +void UdpSocketPosix::HasIncoming() +{ + // replace 2048 with a mcro define and figure out + // where 2048 comes from + int8_t buf[2048]; + int retval; + SocketAddress from; +#if defined(WEBRTC_MAC) + sockaddr sockaddrfrom; + memset(&from, 0, sizeof(from)); + memset(&sockaddrfrom, 0, sizeof(sockaddrfrom)); + socklen_t fromlen = sizeof(sockaddrfrom); +#else + memset(&from, 0, sizeof(from)); + socklen_t fromlen = sizeof(from); +#endif + +#if defined(WEBRTC_MAC) + retval = recvfrom(_socket,buf, sizeof(buf), 0, + reinterpret_cast<sockaddr*>(&sockaddrfrom), &fromlen); + memcpy(&from, &sockaddrfrom, fromlen); + from._sockaddr_storage.sin_family = sockaddrfrom.sa_family; +#else + retval = recvfrom(_socket,buf, sizeof(buf), 0, + reinterpret_cast<sockaddr*>(&from), &fromlen); +#endif + + switch(retval) + { + case 0: + // The peer has performed an orderly shutdown. + break; + case SOCKET_ERROR: + break; + default: + if (_wantsIncoming && _incomingCb) + { + _incomingCb(_obj, buf, retval, &from); + } + break; + } +} + +bool UdpSocketPosix::WantsIncoming() { return _wantsIncoming; } + +void UdpSocketPosix::CloseBlocking() +{ + _cs->Enter(); + _closeBlockingActive = true; + if(!CleanUp()) + { + _closeBlockingActive = false; + _cs->Leave(); + return; + } + + while(!_readyForDeletion) + { + _readyForDeletionCond->SleepCS(*_cs); + } + _closeBlockingCompleted = true; + _closeBlockingCompletedCond->Wake(); + _cs->Leave(); +} + +void UdpSocketPosix::ReadyForDeletion() +{ + _cs->Enter(); + if(!_closeBlockingActive) + { + _cs->Leave(); + return; + } + close(_socket); + _socket = INVALID_SOCKET; + _readyForDeletion = true; + _readyForDeletionCond->Wake(); + while(!_closeBlockingCompleted) + { + _closeBlockingCompletedCond->SleepCS(*_cs); + } + _cs->Leave(); +} + +bool UdpSocketPosix::CleanUp() +{ + _wantsIncoming = false; + + if (_socket == INVALID_SOCKET) + { + return false; + } + + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "calling UdpSocketManager::RemoveSocket()..."); + _mgr->RemoveSocket(this); + // After this, the socket should may be or will be as deleted. Return + // immediately. + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/channel_transport/udp_socket_posix.h b/webrtc/test/channel_transport/udp_socket_posix.h new file mode 100644 index 0000000000..c391b2e397 --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket_posix.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2011 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_TEST_CHANNEL_TRANSPORT_UDP_SOCKET_POSIX_H_ +#define WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_SOCKET_POSIX_H_ + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include "webrtc/system_wrappers/include/condition_variable_wrapper.h" +#include "webrtc/system_wrappers/include/critical_section_wrapper.h" +#include "webrtc/test/channel_transport/udp_socket_wrapper.h" + +namespace webrtc { +namespace test { + +#define SOCKET_ERROR -1 + +class UdpSocketPosix : public UdpSocketWrapper +{ +public: + UdpSocketPosix(const int32_t id, UdpSocketManager* mgr, + bool ipV6Enable = false); + + virtual ~UdpSocketPosix(); + + bool SetCallback(CallbackObj obj, IncomingSocketCallback cb) override; + + bool Bind(const SocketAddress& name) override; + + bool SetSockopt(int32_t level, + int32_t optname, + const int8_t* optval, + int32_t optlen) override; + + int32_t SetTOS(const int32_t serviceType) override; + + int32_t SendTo(const int8_t* buf, + size_t len, + const SocketAddress& to) override; + + // Deletes socket in addition to closing it. + // TODO (hellner): make destructor protected. + void CloseBlocking() override; + + SOCKET GetFd(); + + bool ValidHandle() override; + + bool SetQos(int32_t /*serviceType*/, + int32_t /*tokenRate*/, + int32_t /*bucketSize*/, + int32_t /*peekBandwith*/, + int32_t /*minPolicedSize*/, + int32_t /*maxSduSize*/, + const SocketAddress& /*stRemName*/, + int32_t /*overrideDSCP*/) override; + + bool CleanUp(); + void HasIncoming(); + bool WantsIncoming(); + void ReadyForDeletion(); +private: + friend class UdpSocketManagerPosix; + + const int32_t _id; + IncomingSocketCallback _incomingCb; + CallbackObj _obj; + + SOCKET _socket; + UdpSocketManager* _mgr; + ConditionVariableWrapper* _closeBlockingCompletedCond; + ConditionVariableWrapper* _readyForDeletionCond; + + bool _closeBlockingActive; + bool _closeBlockingCompleted; + bool _readyForDeletion; + + CriticalSectionWrapper* _cs; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_SOCKET_POSIX_H_ diff --git a/webrtc/test/channel_transport/udp_socket_wrapper.cc b/webrtc/test/channel_transport/udp_socket_wrapper.cc new file mode 100644 index 0000000000..f4fa3e950b --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket_wrapper.cc @@ -0,0 +1,159 @@ +/* + * 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/channel_transport/udp_socket_wrapper.h" + +#include <stdlib.h> +#include <string.h> + +#include "webrtc/system_wrappers/include/event_wrapper.h" +#include "webrtc/system_wrappers/include/trace.h" +#include "webrtc/test/channel_transport/udp_socket_manager_wrapper.h" + +#if defined(_WIN32) + #include "webrtc/test/channel_transport/udp_socket2_win.h" +#else + #include "webrtc/test/channel_transport/udp_socket_posix.h" +#endif + + +namespace webrtc { +namespace test { + +bool UdpSocketWrapper::_initiated = false; + +// Temporary Android hack. The value 1024 is taken from +// <ndk>/build/platforms/android-1.5/arch-arm/usr/include/linux/posix_types.h +// TODO (tomasl): can we remove this now? +#ifndef FD_SETSIZE +#define FD_SETSIZE 1024 +#endif + +UdpSocketWrapper::UdpSocketWrapper() + : _wantsIncoming(false), + _deleteEvent(NULL) +{ +} + +UdpSocketWrapper::~UdpSocketWrapper() +{ + if(_deleteEvent) + { + _deleteEvent->Set(); + _deleteEvent = NULL; + } +} + +void UdpSocketWrapper::SetEventToNull() +{ + if (_deleteEvent) + { + _deleteEvent = NULL; + } +} + +UdpSocketWrapper* UdpSocketWrapper::CreateSocket(const int32_t id, + UdpSocketManager* mgr, + CallbackObj obj, + IncomingSocketCallback cb, + bool ipV6Enable, + bool disableGQOS) + +{ + WEBRTC_TRACE(kTraceMemory, kTraceTransport, id, + "UdpSocketWrapper::CreateSocket"); + + UdpSocketWrapper* s = 0; + +#ifdef _WIN32 + if (!_initiated) + { + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD( 2, 2 ); + int32_t err = WSAStartup( wVersionRequested, &wsaData); + if (err != 0) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + id, + "UdpSocketWrapper::CreateSocket failed to initialize sockets\ + WSAStartup error:%d", + err); + return NULL; + } + + _initiated = true; + } + + s = new UdpSocket2Windows(id, mgr, ipV6Enable, disableGQOS); + +#else + if (!_initiated) + { + _initiated = true; + } + s = new UdpSocketPosix(id, mgr, ipV6Enable); + if (s) + { + UdpSocketPosix* sl = static_cast<UdpSocketPosix*>(s); + if (sl->GetFd() != INVALID_SOCKET && sl->GetFd() < FD_SETSIZE) + { + // ok + } else + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + id, + "UdpSocketWrapper::CreateSocket failed to initialize socket"); + delete s; + s = NULL; + } + } +#endif + if (s) + { + s->_deleteEvent = NULL; + if (!s->SetCallback(obj, cb)) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + id, + "UdpSocketWrapper::CreateSocket failed to ser callback"); + return(NULL); + } + } + return s; +} + +bool UdpSocketWrapper::StartReceiving() +{ + _wantsIncoming = true; + return true; +} + +bool UdpSocketWrapper::StartReceiving(const uint32_t /*receiveBuffers*/) { + return StartReceiving(); +} + +bool UdpSocketWrapper::StopReceiving() +{ + _wantsIncoming = false; + return true; +} + +int32_t UdpSocketWrapper::SetPCP(const int32_t /*pcp*/) { return -1; } + +uint32_t UdpSocketWrapper::ReceiveBuffers() { return 0; } + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/channel_transport/udp_socket_wrapper.h b/webrtc/test/channel_transport/udp_socket_wrapper.h new file mode 100644 index 0000000000..d2a1ce6b61 --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket_wrapper.h @@ -0,0 +1,112 @@ +/* + * 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_TEST_CHANNEL_TRANSPORT_UDP_SOCKET_WRAPPER_H_ +#define WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_SOCKET_WRAPPER_H_ + +#include "webrtc/test/channel_transport/udp_transport.h" + +namespace webrtc { + +class EventWrapper; + +namespace test { + +class UdpSocketManager; + +#define SOCKET_ERROR_NO_QOS -1000 + +#ifndef _WIN32 +typedef int SOCKET; +#endif + +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (SOCKET)(~0) + +#ifndef AF_INET +#define AF_INET 2 +#endif + +#endif + +typedef void* CallbackObj; +typedef void(*IncomingSocketCallback)(CallbackObj obj, const int8_t* buf, + size_t len, const SocketAddress* from); + +class UdpSocketWrapper +{ +public: + static UdpSocketWrapper* CreateSocket(const int32_t id, + UdpSocketManager* mgr, + CallbackObj obj, + IncomingSocketCallback cb, + bool ipV6Enable = false, + bool disableGQOS = false); + + // Register cb for receiving callbacks when there are incoming packets. + // Register obj so that it will be passed in calls to cb. + virtual bool SetCallback(CallbackObj obj, IncomingSocketCallback cb) = 0; + + // Socket to local address specified by name. + virtual bool Bind(const SocketAddress& name) = 0; + + // Start receiving UDP data. + virtual bool StartReceiving(); + virtual bool StartReceiving(const uint32_t /*receiveBuffers*/); + // Stop receiving UDP data. + virtual bool StopReceiving(); + + virtual bool ValidHandle() = 0; + + // Set socket options. + virtual bool SetSockopt(int32_t level, int32_t optname, + const int8_t* optval, int32_t optlen) = 0; + + // Set TOS for outgoing packets. + virtual int32_t SetTOS(const int32_t serviceType) = 0; + + // Set 802.1Q PCP field (802.1p) for outgoing VLAN traffic. + virtual int32_t SetPCP(const int32_t /*pcp*/); + + // Send buf of length len to the address specified by to. + virtual int32_t SendTo(const int8_t* buf, size_t len, + const SocketAddress& to) = 0; + + virtual void SetEventToNull(); + + // Close socket and don't return until completed. + virtual void CloseBlocking() {} + + // tokenRate is in bit/s. peakBandwidt is in byte/s + virtual bool SetQos(int32_t serviceType, int32_t tokenRate, + int32_t bucketSize, int32_t peekBandwith, + int32_t minPolicedSize, int32_t maxSduSize, + const SocketAddress &stRemName, + int32_t overrideDSCP = 0) = 0; + + virtual uint32_t ReceiveBuffers(); + +protected: + // Creating the socket is done via CreateSocket(). + UdpSocketWrapper(); + // Destroying the socket is done via CloseBlocking(). + virtual ~UdpSocketWrapper(); + + bool _wantsIncoming; + EventWrapper* _deleteEvent; + +private: + static bool _initiated; +}; + +} // namespac test +} // namespace webrtc + +#endif // WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_SOCKET_WRAPPER_H_ diff --git a/webrtc/test/channel_transport/udp_socket_wrapper_unittest.cc b/webrtc/test/channel_transport/udp_socket_wrapper_unittest.cc new file mode 100644 index 0000000000..885e19c237 --- /dev/null +++ b/webrtc/test/channel_transport/udp_socket_wrapper_unittest.cc @@ -0,0 +1,67 @@ +/* + * 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. + */ + +// Tests for the UdpSocketWrapper interface. +// This will test the UdpSocket implementations on various platforms. +// Note that this test is using a real SocketManager, which starts up +// an extra worker thread, making the testing more complex than it +// should be. +// This is because on Posix, the CloseBlocking function waits for the +// ReadyForDeletion function to be called, which has to be called after +// CloseBlocking, and thus has to be called from another thread. +// The manager is the one actually doing the deleting. +// This is done differently in the Winsock2 code, but that code +// will also hang if the destructor is called directly. + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/test/channel_transport/udp_socket_manager_wrapper.h" +#include "webrtc/test/channel_transport/udp_socket_wrapper.h" + +using ::testing::_; +using ::testing::Return; + +namespace webrtc { +namespace test { + +class MockSocketManager : public UdpSocketManager { + public: + MockSocketManager() {} + // Access to protected destructor. + void Destroy() { + delete this; + } + MOCK_METHOD2(Init, bool(int32_t, uint8_t&)); + MOCK_METHOD0(Start, bool()); + MOCK_METHOD0(Stop, bool()); + MOCK_METHOD1(AddSocket, bool(UdpSocketWrapper*)); + MOCK_METHOD1(RemoveSocket, bool(UdpSocketWrapper*)); +}; + +// Creates a socket using the static constructor method and verifies that +// it's added to the socket manager. +TEST(UdpSocketWrapper, CreateSocket) { + int32_t id = 42; + // We can't test deletion of sockets without a socket manager. + uint8_t threads = 1; + UdpSocketManager* mgr = UdpSocketManager::Create(id, threads); + UdpSocketWrapper* socket = + UdpSocketWrapper::CreateSocket(id, + mgr, + NULL, // CallbackObj + NULL, // IncomingSocketCallback + false, // ipV6Enable + false); // disableGQOS + socket->CloseBlocking(); + UdpSocketManager::Return(); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/channel_transport/udp_transport.h b/webrtc/test/channel_transport/udp_transport.h new file mode 100644 index 0000000000..0c5079e69f --- /dev/null +++ b/webrtc/test/channel_transport/udp_transport.h @@ -0,0 +1,381 @@ +/* + * 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_TEST_CHANNEL_TRANSPORT_UDP_TRANSPORT_H_ +#define WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_TRANSPORT_H_ + +#include "webrtc/common_types.h" +#include "webrtc/transport.h" +#include "webrtc/typedefs.h" + +/* + * WARNING + * This code is not use in production/testing and might have security issues + * for example: http://code.google.com/p/webrtc/issues/detail?id=1028 + * + */ + +#define SS_MAXSIZE 128 +#define SS_ALIGNSIZE (sizeof (uint64_t)) +#define SS_PAD1SIZE (SS_ALIGNSIZE - sizeof(int16_t)) +#define SS_PAD2SIZE (SS_MAXSIZE - (sizeof(int16_t) + SS_PAD1SIZE +\ + SS_ALIGNSIZE)) + +// BSD requires use of HAVE_STRUCT_SOCKADDR_SA_LEN +namespace webrtc { +namespace test { + +struct SocketAddressIn { + // sin_family should be either AF_INET (IPv4) or AF_INET6 (IPv6) +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + int8_t sin_length; + int8_t sin_family; +#else + int16_t sin_family; +#endif + uint16_t sin_port; + uint32_t sin_addr; + int8_t sin_zero[8]; +}; + +struct Version6InAddress { + union { + uint8_t _s6_u8[16]; + uint32_t _s6_u32[4]; + uint64_t _s6_u64[2]; + } Version6AddressUnion; +}; + +struct SocketAddressInVersion6 { + // sin_family should be either AF_INET (IPv4) or AF_INET6 (IPv6) +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + int8_t sin_length; + int8_t sin_family; +#else + int16_t sin_family; +#endif + // Transport layer port number. + uint16_t sin6_port; + // IPv6 traffic class and flow info or ip4 address. + uint32_t sin6_flowinfo; + // IPv6 address + struct Version6InAddress sin6_addr; + // Set of interfaces for a scope. + uint32_t sin6_scope_id; +}; + +struct SocketAddressStorage { + // sin_family should be either AF_INET (IPv4) or AF_INET6 (IPv6) +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + int8_t sin_length; + int8_t sin_family; +#else + int16_t sin_family; +#endif + int8_t __ss_pad1[SS_PAD1SIZE]; + uint64_t __ss_align; + int8_t __ss_pad2[SS_PAD2SIZE]; +}; + +struct SocketAddress { + union { + struct SocketAddressIn _sockaddr_in; + struct SocketAddressInVersion6 _sockaddr_in6; + struct SocketAddressStorage _sockaddr_storage; + }; +}; + +// Callback class that receives packets from UdpTransport. +class UdpTransportData { + public: + virtual ~UdpTransportData() {}; + + virtual void IncomingRTPPacket(const int8_t* incomingRtpPacket, + const size_t rtpPacketLength, + const char* fromIP, + const uint16_t fromPort) = 0; + + virtual void IncomingRTCPPacket(const int8_t* incomingRtcpPacket, + const size_t rtcpPacketLength, + const char* fromIP, + const uint16_t fromPort) = 0; +}; + +class UdpTransport : public Transport { + public: + enum + { + kIpAddressVersion6Length = 64, + kIpAddressVersion4Length = 16 + }; + enum ErrorCode + { + kNoSocketError = 0, + kFailedToBindPort = 1, + kIpAddressInvalid = 2, + kAddressInvalid = 3, + kSocketInvalid = 4, + kPortInvalid = 5, + kTosInvalid = 6, + kMulticastAddressInvalid = 7, + kQosError = 8, + kSocketAlreadyInitialized = 9, + kIpVersion6Error = 10, + FILTER_ERROR = 11, + kStartReceiveError = 12, + kStopReceiveError = 13, + kCannotFindLocalIp = 14, + kTosError = 16, + kNotInitialized = 17, + kPcpError = 18 + }; + + // Factory method. Constructor disabled. + static UdpTransport* Create(const int32_t id, uint8_t& numSocketThreads); + static void Destroy(UdpTransport* module); + + // Prepares the class for sending RTP packets to ipAddr:rtpPort and RTCP + // packets to ipAddr:rtpPort+1 if rtcpPort is zero. Otherwise to + // ipAddr:rtcpPort. + virtual int32_t InitializeSendSockets(const char* ipAddr, + const uint16_t rtpPort, + const uint16_t rtcpPort = 0) = 0; + + // Register packetCallback for receiving incoming packets. Set the local + // RTP port to rtpPort. Bind local IP address to ipAddr. If ipAddr is NULL + // bind to local IP ANY. Set the local rtcp port to rtcpPort or rtpPort + 1 + // if rtcpPort is 0. + virtual int32_t InitializeReceiveSockets( + UdpTransportData* const packetCallback, + const uint16_t rtpPort, + const char* ipAddr = NULL, + const char* multicastIpAddr = NULL, + const uint16_t rtcpPort = 0) = 0; + + // Set local RTP port to rtpPort and RTCP port to rtcpPort or rtpPort + 1 if + // rtcpPort is 0. These ports will be used for sending instead of the local + // ports set by InitializeReceiveSockets(..). + virtual int32_t InitializeSourcePorts(const uint16_t rtpPort, + const uint16_t rtcpPort = 0) = 0; + + // Retrieve local ports used for sending if other than the ports specified + // by InitializeReceiveSockets(..). rtpPort is set to the RTP port. + // rtcpPort is set to the RTCP port. + virtual int32_t SourcePorts(uint16_t& rtpPort, + uint16_t& rtcpPort) const = 0; + + // Set ipAddr to the IP address that is currently being listened on. rtpPort + // to the RTP port listened to. rtcpPort to the RTCP port listened on. + // multicastIpAddr to the multicast IP address group joined (the address + // is NULL terminated). + virtual int32_t ReceiveSocketInformation( + char ipAddr[kIpAddressVersion6Length], + uint16_t& rtpPort, + uint16_t& rtcpPort, + char multicastIpAddr[kIpAddressVersion6Length]) const = 0; + + // Set ipAddr to the IP address being sent from. rtpPort to the local RTP + // port used for sending and rtcpPort to the local RTCP port used for + // sending. + virtual int32_t SendSocketInformation(char ipAddr[kIpAddressVersion6Length], + uint16_t& rtpPort, + uint16_t& rtcpPort) const = 0; + + // Put the IP address, RTP port and RTCP port from the last received packet + // into ipAddr, rtpPort and rtcpPort respectively. + virtual int32_t RemoteSocketInformation( + char ipAddr[kIpAddressVersion6Length], + uint16_t& rtpPort, + uint16_t& rtcpPort) const = 0; + + // Enable/disable quality of service if QoS is true or false respectively. + // Set the type of service to serviceType, max bitrate in kbit/s to + // maxBitrate and override DSCP if overrideDSCP is not 0. + // Note: Must be called both InitializeSendSockets() and + // InitializeReceiveSockets() has been called. + virtual int32_t SetQoS(const bool QoS, + const int32_t serviceType, + const uint32_t maxBitrate = 0, + const int32_t overrideDSCP = 0, + const bool audio = false) = 0; + + // Set QoS to true if quality of service has been turned on. If QoS is true, + // also set serviceType to type of service and overrideDSCP to override + // DSCP. + virtual int32_t QoS(bool& QoS, + int32_t& serviceType, + int32_t& overrideDSCP) const = 0; + + // Set type of service. + virtual int32_t SetToS(const int32_t DSCP, + const bool useSetSockOpt = false) = 0; + + // Get type of service configuration. + virtual int32_t ToS(int32_t& DSCP, + bool& useSetSockOpt) const = 0; + + // Set Priority Code Point (IEEE 802.1Q) + // Note: for Linux this function will set the priority for the socket, + // which then can be mapped to a PCP value with vconfig. + virtual int32_t SetPCP(const int32_t PCP) = 0; + + // Get Priority Code Point + virtual int32_t PCP(int32_t& PCP) const = 0; + + // Enable IPv6. + // Note: this API must be called before any call to + // InitializeReceiveSockets() or InitializeSendSockets(). It is not + // possible to go back to IPv4 (default) after this call. + virtual int32_t EnableIpV6() = 0; + + // Return true if IPv6 has been enabled. + virtual bool IpV6Enabled() const = 0; + + // Only allow packets received from filterIPAddress to be processed. + // Note: must be called after EnableIPv6(), if IPv6 is used. + virtual int32_t SetFilterIP( + const char filterIPAddress[kIpAddressVersion6Length]) = 0; + + // Write the filter IP address (if any) to filterIPAddress. + virtual int32_t FilterIP( + char filterIPAddress[kIpAddressVersion6Length]) const = 0; + + // Only allow RTP packets from rtpFilterPort and RTCP packets from + // rtcpFilterPort be processed. + // Note: must be called after EnableIPv6(), if IPv6 is used. + virtual int32_t SetFilterPorts(const uint16_t rtpFilterPort, + const uint16_t rtcpFilterPort) = 0; + + // Set rtpFilterPort to the filter RTP port and rtcpFilterPort to the + // filter RTCP port (if filtering based on port is enabled). + virtual int32_t FilterPorts(uint16_t& rtpFilterPort, + uint16_t& rtcpFilterPort) const = 0; + + // Set the number of buffers that the socket implementation may use for + // receiving packets to numberOfSocketBuffers. I.e. the number of packets + // that can be received in parallell. + // Note: this API only has effect on Windows. + virtual int32_t StartReceiving(const uint32_t numberOfSocketBuffers) = 0; + + // Stop receive incoming packets. + virtual int32_t StopReceiving() = 0; + + // Return true incoming packets are received. + virtual bool Receiving() const = 0; + + // Return true if send sockets have been initialized. + virtual bool SendSocketsInitialized() const = 0; + + // Return true if local ports for sending has been set. + virtual bool SourcePortsInitialized() const = 0; + + // Return true if receive sockets have been initialized. + virtual bool ReceiveSocketsInitialized() const = 0; + + // Send data with size length to ip:portnr. The same port as the set + // with InitializeSendSockets(..) is used if portnr is 0. The same IP + // address as set with InitializeSendSockets(..) is used if ip is NULL. + // If isRTCP is true the port used will be the RTCP port. + virtual int32_t SendRaw(const int8_t* data, + size_t length, + int32_t isRTCP, + uint16_t portnr = 0, + const char* ip = NULL) = 0; + + // Send RTP data with size length to the address specified by to. + virtual int32_t SendRTPPacketTo(const int8_t* data, + size_t length, + const SocketAddress& to) = 0; + + + // Send RTCP data with size length to the address specified by to. + virtual int32_t SendRTCPPacketTo(const int8_t* data, + size_t length, + const SocketAddress& to) = 0; + + // Send RTP data with size length to ip:rtpPort where ip is the ip set by + // the InitializeSendSockets(..) call. + virtual int32_t SendRTPPacketTo(const int8_t* data, + size_t length, + uint16_t rtpPort) = 0; + + + // Send RTCP data with size length to ip:rtcpPort where ip is the ip set by + // the InitializeSendSockets(..) call. + virtual int32_t SendRTCPPacketTo(const int8_t* data, + size_t length, + uint16_t rtcpPort) = 0; + + // Set the IP address to which packets are sent to ipaddr. + virtual int32_t SetSendIP( + const char ipaddr[kIpAddressVersion6Length]) = 0; + + // Set the send RTP and RTCP port to rtpPort and rtcpPort respectively. + virtual int32_t SetSendPorts(const uint16_t rtpPort, + const uint16_t rtcpPort = 0) = 0; + + // Retreive the last registered error code. + virtual ErrorCode LastError() const = 0; + + // Put the local IPv4 address in localIP. + // Note: this API is for IPv4 only. + static int32_t LocalHostAddress(uint32_t& localIP); + + // Put the local IP6 address in localIP. + // Note: this API is for IPv6 only. + static int32_t LocalHostAddressIPV6(char localIP[16]); + + // Return a copy of hostOrder (host order) in network order. + static uint16_t Htons(uint16_t hostOrder); + + // Return a copy of hostOrder (host order) in network order. + static uint32_t Htonl(uint32_t hostOrder); + + // Return IPv4 address in ip as 32 bit integer. + static uint32_t InetAddrIPV4(const char* ip); + + // Convert the character string src into a network address structure in + // the af address family and put it in dst. + // Note: same functionality as inet_pton(..) + static int32_t InetPresentationToNumeric(int32_t af, + const char* src, + void* dst); + + // Set ip and sourcePort according to address. As input parameter ipSize + // is the length of ip. As output parameter it's the number of characters + // written to ip (not counting the '\0' character). + // Note: this API is only implemented on Windows and Linux. + static int32_t IPAddress(const SocketAddress& address, + char* ip, + uint32_t& ipSize, + uint16_t& sourcePort); + + // Set ip and sourcePort according to address. As input parameter ipSize + // is the length of ip. As output parameter it's the number of characters + // written to ip (not counting the '\0' character). + // Note: this API is only implemented on Windows and Linux. + // Additional note: this API caches the address of the last call to it. If + // address is likley to be the same for multiple calls it may be beneficial + // to call this API instead of IPAddress(). + virtual int32_t IPAddressCached(const SocketAddress& address, + char* ip, + uint32_t& ipSize, + uint16_t& sourcePort) = 0; + + // Return true if ipaddr is a valid IP address. + // If ipV6 is false ipaddr is interpreted as an IPv4 address otherwise it + // is interptreted as IPv6. + static bool IsIpAddressValid(const char* ipaddr, const bool ipV6); +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_TRANSPORT_H_ diff --git a/webrtc/test/channel_transport/udp_transport_impl.cc b/webrtc/test/channel_transport/udp_transport_impl.cc new file mode 100644 index 0000000000..c7049aa8a2 --- /dev/null +++ b/webrtc/test/channel_transport/udp_transport_impl.cc @@ -0,0 +1,2994 @@ +/* + * 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/channel_transport/udp_transport_impl.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#if defined(_WIN32) +#include <winsock2.h> +#include <ws2tcpip.h> +#elif defined(WEBRTC_LINUX) || defined(WEBRTC_MAC) +#include <arpa/inet.h> +#include <ctype.h> +#include <fcntl.h> +#include <net/if.h> +#include <netdb.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <unistd.h> +#ifndef WEBRTC_IOS +#include <net/if_arp.h> +#endif +#endif // defined(WEBRTC_LINUX) || defined(WEBRTC_MAC) + +#if defined(WEBRTC_MAC) +#include <ifaddrs.h> +#include <machine/types.h> +#endif +#if defined(WEBRTC_LINUX) +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#endif + +#include "webrtc/common_types.h" +#include "webrtc/system_wrappers/include/critical_section_wrapper.h" +#include "webrtc/system_wrappers/include/rw_lock_wrapper.h" +#include "webrtc/system_wrappers/include/trace.h" +#include "webrtc/test/channel_transport/udp_socket_manager_wrapper.h" +#include "webrtc/typedefs.h" + +#if defined(WEBRTC_LINUX) || defined(WEBRTC_MAC) +#define GetLastError() errno + +#define IFRSIZE ((int)(size * sizeof (struct ifreq))) + +#define NLMSG_OK_NO_WARNING(nlh,len) \ + ((len) >= (int)sizeof(struct nlmsghdr) && \ + (int)(nlh)->nlmsg_len >= (int)sizeof(struct nlmsghdr) && \ + (int)(nlh)->nlmsg_len <= (len)) + +#endif // defined(WEBRTC_LINUX) || defined(WEBRTC_MAC) + +namespace webrtc { +namespace test { + +class SocketFactory : public UdpTransportImpl::SocketFactoryInterface { + public: + UdpSocketWrapper* CreateSocket(const int32_t id, + UdpSocketManager* mgr, + CallbackObj obj, + IncomingSocketCallback cb, + bool ipV6Enable, + bool disableGQOS) override { + return UdpSocketWrapper::CreateSocket(id, mgr, obj, cb, ipV6Enable, + disableGQOS); + } +}; + +// Creates an UdpTransport using the definition of SocketFactory above, +// and passes (creating if needed) a pointer to the static singleton +// UdpSocketManager. +UdpTransport* UdpTransport::Create(const int32_t id, + uint8_t& numSocketThreads) +{ + return new UdpTransportImpl(id, + new SocketFactory(), + UdpSocketManager::Create(id, numSocketThreads)); +} + +// Deletes the UdpTransport and decrements the refcount of the +// static singleton UdpSocketManager, possibly destroying it. +// Should only be used on UdpTransports that are created using Create. +void UdpTransport::Destroy(UdpTransport* module) +{ + if(module) + { + delete module; + UdpSocketManager::Return(); + } +} + +UdpTransportImpl::UdpTransportImpl(const int32_t id, + SocketFactoryInterface* maker, + UdpSocketManager* socket_manager) + : _id(id), + _socket_creator(maker), + _crit(CriticalSectionWrapper::CreateCriticalSection()), + _critFilter(CriticalSectionWrapper::CreateCriticalSection()), + _critPacketCallback(CriticalSectionWrapper::CreateCriticalSection()), + _mgr(socket_manager), + _lastError(kNoSocketError), + _destPort(0), + _destPortRTCP(0), + _localPort(0), + _localPortRTCP(0), + _srcPort(0), + _srcPortRTCP(0), + _fromPort(0), + _fromPortRTCP(0), + _fromIP(), + _destIP(), + _localIP(), + _localMulticastIP(), + _ptrRtpSocket(NULL), + _ptrRtcpSocket(NULL), + _ptrSendRtpSocket(NULL), + _ptrSendRtcpSocket(NULL), + _remoteRTPAddr(), + _remoteRTCPAddr(), + _localRTPAddr(), + _localRTCPAddr(), + _tos(0), + _receiving(false), + _useSetSockOpt(false), + _qos(false), + _pcp(0), + _ipV6Enabled(false), + _serviceType(0), + _overrideDSCP(0), + _maxBitrate(0), + _cachLock(RWLockWrapper::CreateRWLock()), + _previousAddress(), + _previousIP(), + _previousIPSize(0), + _previousSourcePort(0), + _filterIPAddress(), + _rtpFilterPort(0), + _rtcpFilterPort(0), + _packetCallback(0) +{ + memset(&_remoteRTPAddr, 0, sizeof(_remoteRTPAddr)); + memset(&_remoteRTCPAddr, 0, sizeof(_remoteRTCPAddr)); + memset(&_localRTPAddr, 0, sizeof(_localRTPAddr)); + memset(&_localRTCPAddr, 0, sizeof(_localRTCPAddr)); + + memset(_fromIP, 0, sizeof(_fromIP)); + memset(_destIP, 0, sizeof(_destIP)); + memset(_localIP, 0, sizeof(_localIP)); + memset(_localMulticastIP, 0, sizeof(_localMulticastIP)); + + memset(&_filterIPAddress, 0, sizeof(_filterIPAddress)); + + WEBRTC_TRACE(kTraceMemory, kTraceTransport, id, "%s created", __FUNCTION__); +} + +UdpTransportImpl::~UdpTransportImpl() +{ + CloseSendSockets(); + CloseReceiveSockets(); + delete _crit; + delete _critFilter; + delete _critPacketCallback; + delete _cachLock; + delete _socket_creator; + + WEBRTC_TRACE(kTraceMemory, kTraceTransport, _id, "%s deleted", + __FUNCTION__); +} + +UdpTransport::ErrorCode UdpTransportImpl::LastError() const +{ + return _lastError; +} + +bool SameAddress(const SocketAddress& address1, const SocketAddress& address2) +{ + return (memcmp(&address1,&address2,sizeof(address1)) == 0); +} + +void UdpTransportImpl::GetCachedAddress(char* ip, + uint32_t& ipSize, + uint16_t& sourcePort) +{ + const uint32_t originalIPSize = ipSize; + // If the incoming string is too small, fill it as much as there is room + // for. Make sure that there is room for the '\0' character. + ipSize = (ipSize - 1 < _previousIPSize) ? ipSize - 1 : _previousIPSize; + memcpy(ip,_previousIP,sizeof(int8_t)*(ipSize + 1)); + ip[originalIPSize - 1] = '\0'; + sourcePort = _previousSourcePort; +} + +int32_t UdpTransportImpl::IPAddressCached(const SocketAddress& address, + char* ip, + uint32_t& ipSize, + uint16_t& sourcePort) +{ + { + ReadLockScoped rl(*_cachLock); + // Check if the old address can be re-used (is the same). + if(SameAddress(address,_previousAddress)) + { + GetCachedAddress(ip,ipSize,sourcePort); + return 0; + } + } + // Get the new address and store it. + WriteLockScoped wl(*_cachLock); + ipSize = kIpAddressVersion6Length; + if(IPAddress(address,_previousIP,ipSize,_previousSourcePort) != 0) + { + return -1; + } + _previousIPSize = ipSize; + memcpy(&_previousAddress, &address, sizeof(address)); + // Address has been cached at this point. + GetCachedAddress(ip,ipSize,sourcePort); + return 0; +} + +int32_t UdpTransportImpl::InitializeReceiveSockets( + UdpTransportData* const packetCallback, + const uint16_t portnr, + const char* ip, + const char* multicastIpAddr, + const uint16_t rtcpPort) +{ + { + CriticalSectionScoped cs(_critPacketCallback); + _packetCallback = packetCallback; + + if(packetCallback == NULL) + { + WEBRTC_TRACE(kTraceStateInfo, kTraceTransport, _id, + "Closing down receive sockets"); + return 0; + } + } + + CriticalSectionScoped cs(_crit); + CloseReceiveSockets(); + + if(portnr == 0) + { + // TODO (hellner): why not just fail here? + if(_destPort == 0) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "InitializeReceiveSockets port 0 not allowed"); + _lastError = kPortInvalid; + return -1; + } + _localPort = _destPort; + } else { + _localPort = portnr; + } + if(rtcpPort) + { + _localPortRTCP = rtcpPort; + }else { + _localPortRTCP = _localPort + 1; + WEBRTC_TRACE( + kTraceStateInfo, + kTraceTransport, + _id, + "InitializeReceiveSockets RTCP port not configured using RTP\ + port+1=%d", + _localPortRTCP); + } + + if(ip) + { + if(IsIpAddressValid(ip,IpV6Enabled())) + { + strncpy(_localIP, ip,kIpAddressVersion6Length); + } else + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "InitializeReceiveSockets invalid IP address"); + _lastError = kIpAddressInvalid; + return -1; + } + }else + { + // Don't bind to a specific IP address. + if(! IpV6Enabled()) + { + strncpy(_localIP, "0.0.0.0",16); + } else + { + strncpy(_localIP, "0000:0000:0000:0000:0000:0000:0000:0000", + kIpAddressVersion6Length); + } + } + if(multicastIpAddr && !IpV6Enabled()) + { + if(IsIpAddressValid(multicastIpAddr,IpV6Enabled())) + { + strncpy(_localMulticastIP, multicastIpAddr, + kIpAddressVersion6Length); + } else + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "InitializeReceiveSockets invalid IP address"); + _lastError = kIpAddressInvalid; + return -1; + } + } + if(_mgr == NULL) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "InitializeReceiveSockets no socket manager"); + return -1; + } + + _useSetSockOpt=false; + _tos=0; + _pcp=0; + + _ptrRtpSocket = _socket_creator->CreateSocket(_id, _mgr, this, + IncomingRTPCallback, + IpV6Enabled(), false); + + _ptrRtcpSocket = _socket_creator->CreateSocket(_id, _mgr, this, + IncomingRTCPCallback, + IpV6Enabled(), false); + + ErrorCode retVal = BindLocalRTPSocket(); + if(retVal != kNoSocketError) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "InitializeReceiveSockets faild to bind RTP socket"); + _lastError = retVal; + CloseReceiveSockets(); + return -1; + } + retVal = BindLocalRTCPSocket(); + if(retVal != kNoSocketError) + { + _lastError = retVal; + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "InitializeReceiveSockets faild to bind RTCP socket"); + CloseReceiveSockets(); + return -1; + } + return 0; +} + +int32_t UdpTransportImpl::ReceiveSocketInformation( + char ipAddr[kIpAddressVersion6Length], + uint16_t& rtpPort, + uint16_t& rtcpPort, + char multicastIpAddr[kIpAddressVersion6Length]) const +{ + CriticalSectionScoped cs(_crit); + rtpPort = _localPort; + rtcpPort = _localPortRTCP; + if (ipAddr) + { + strncpy(ipAddr, _localIP, IpV6Enabled() ? + UdpTransport::kIpAddressVersion6Length : + UdpTransport::kIpAddressVersion4Length); + } + if (multicastIpAddr) + { + strncpy(multicastIpAddr, _localMulticastIP, IpV6Enabled() ? + UdpTransport::kIpAddressVersion6Length : + UdpTransport::kIpAddressVersion4Length); + } + return 0; +} + +int32_t UdpTransportImpl::SendSocketInformation( + char ipAddr[kIpAddressVersion6Length], + uint16_t& rtpPort, + uint16_t& rtcpPort) const +{ + CriticalSectionScoped cs(_crit); + rtpPort = _destPort; + rtcpPort = _destPortRTCP; + strncpy(ipAddr, _destIP, IpV6Enabled() ? + UdpTransport::kIpAddressVersion6Length : + UdpTransport::kIpAddressVersion4Length); + return 0; +} + +int32_t UdpTransportImpl::RemoteSocketInformation( + char ipAddr[kIpAddressVersion6Length], + uint16_t& rtpPort, + uint16_t& rtcpPort) const +{ + CriticalSectionScoped cs(_crit); + rtpPort = _fromPort; + rtcpPort = _fromPortRTCP; + if(ipAddr) + { + strncpy(ipAddr, _fromIP, IpV6Enabled() ? + kIpAddressVersion6Length : + kIpAddressVersion4Length); + } + return 0; +} + +int32_t UdpTransportImpl::FilterPorts( + uint16_t& rtpFilterPort, + uint16_t& rtcpFilterPort) const +{ + CriticalSectionScoped cs(_critFilter); + rtpFilterPort = _rtpFilterPort; + rtcpFilterPort = _rtcpFilterPort; + return 0; +} + +int32_t UdpTransportImpl::SetQoS(bool QoS, int32_t serviceType, + uint32_t maxBitrate, + int32_t overrideDSCP, bool audio) +{ + if(QoS) + { + return EnableQoS(serviceType, audio, maxBitrate, overrideDSCP); + }else + { + return DisableQoS(); + } +} + +int32_t UdpTransportImpl::EnableQoS(int32_t serviceType, + bool audio, uint32_t maxBitrate, + int32_t overrideDSCP) +{ + if (_ipV6Enabled) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "QOS is enabled but will be ignored since IPv6 is enabled"); + _lastError = kQosError; + return -1; + } + if (_tos) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "TOS already enabled, can't use TOS and QoS at the same time"); + _lastError = kQosError; + return -1; + } + if (_pcp) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "PCP already enabled, can't use PCP and QoS at the same time"); + _lastError = kQosError; + return -1; + } + if(_destPort == 0) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "QOS is enabled but not started since we have not yet configured\ + the send destination"); + return -1; + } + if(_qos) + { + if(_overrideDSCP == 0 && overrideDSCP != 0) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "QOS is already enabled and overrideDSCP differs, not allowed"); + return -1; + } + } + CriticalSectionScoped cs(_crit); + + UdpSocketWrapper* rtpSock = _ptrSendRtpSocket ? + _ptrSendRtpSocket : + _ptrRtpSocket; + if (!rtpSock || !rtpSock->ValidHandle()) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "QOS is enabled but not started since we have not yet created the\ + RTP socket"); + return -1; + } + UdpSocketWrapper* rtcpSock = _ptrSendRtcpSocket ? + _ptrSendRtcpSocket : + _ptrRtcpSocket; + if (!rtcpSock || !rtcpSock->ValidHandle()) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "QOS is enabled but not started since we have not yet created the\ + RTCP socket"); + return -1; + } + + // Minimum packet size in bytes for which the requested quality of service + // will be provided. The smallest RTP header is 12 byte. + const int32_t min_policed_size = 12; + // Max SDU, maximum packet size permitted or used in the traffic flow, in + // bytes. + const int32_t max_sdu_size = 1500; + + // Enable QoS for RTP sockets. + if(maxBitrate) + { + // Note: 1 kbit is 125 bytes. + // Token Rate is typically set to the average bit rate from peak to + // peak. + // Bucket size is normally set to the largest average frame size. + if(audio) + { + WEBRTC_TRACE(kTraceStateInfo, + kTraceTransport, + _id, + "Enable QOS for audio with max bitrate:%d", + maxBitrate); + + const int32_t token_rate = maxBitrate*125; + // The largest audio packets are 60ms frames. This is a fraction + // more than 16 packets/second. These 16 frames are sent, at max, + // at a bitrate of maxBitrate*125 -> 1 frame is maxBitrate*125/16 ~ + // maxBitrate * 8. + const int32_t bucket_size = maxBitrate * 8; + const int32_t peek_bandwith = maxBitrate * 125; + if (!rtpSock->SetQos(serviceType, token_rate, bucket_size, + peek_bandwith, min_policed_size, + max_sdu_size, _remoteRTPAddr, overrideDSCP)) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "QOS failed on the RTP socket"); + _lastError = kQosError; + return -1; + } + }else + { + WEBRTC_TRACE(kTraceStateInfo, kTraceTransport, _id, + "Enable QOS for video with max bitrate:%d", + maxBitrate); + + // Allow for a token rate that is twice that of the maximum bitrate + // (in bytes). + const int32_t token_rate = maxBitrate*250; + // largest average frame size (key frame size). Assuming that a + // keyframe is 25% of the bitrate during the second its sent + // Assume that a key frame is 25% of the bitrate the second that it + // is sent. The largest frame size is then maxBitrate* 125 * 0.25 ~ + // 31. + const int32_t bucket_size = maxBitrate*31; + const int32_t peek_bandwith = maxBitrate*125; + if (!rtpSock->SetQos(serviceType, token_rate, bucket_size, + peek_bandwith, min_policed_size, max_sdu_size, + _remoteRTPAddr, overrideDSCP)) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "QOS failed on the RTP socket"); + _lastError = kQosError; + return -1; + } + } + } else if(audio) + { + // No max bitrate set. Audio. + WEBRTC_TRACE(kTraceStateInfo, kTraceTransport, _id, + "Enable QOS for audio with default max bitrate"); + + // Let max bitrate be 240kbit/s. + const int32_t token_rate = 30000; + const int32_t bucket_size = 2000; + const int32_t peek_bandwith = 30000; + if (!rtpSock->SetQos(serviceType, token_rate, bucket_size, + peek_bandwith, min_policed_size, max_sdu_size, + _remoteRTPAddr, overrideDSCP)) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "QOS failed on the RTP socket"); + _lastError = kQosError; + return -1; + } + }else + { + // No max bitrate set. Video. + WEBRTC_TRACE(kTraceStateInfo, kTraceTransport, _id, + "Enable QOS for video with default max bitrate"); + + // Let max bitrate be 10mbit/s. + const int32_t token_rate = 128000*10; + const int32_t bucket_size = 32000; + const int32_t peek_bandwith = 256000; + if (!rtpSock->SetQos(serviceType, token_rate, bucket_size, + peek_bandwith, min_policed_size, max_sdu_size, + _remoteRTPAddr, overrideDSCP)) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "QOS failed on the RTP socket"); + _lastError = kQosError; + return -1; + } + } + + // Enable QoS for RTCP sockets. + // TODO (hellner): shouldn't RTCP be based on 5% of the maximum bandwidth? + if(audio) + { + const int32_t token_rate = 200; + const int32_t bucket_size = 200; + const int32_t peek_bandwith = 400; + if (!rtcpSock->SetQos(serviceType, token_rate, bucket_size, + peek_bandwith, min_policed_size, max_sdu_size, + _remoteRTCPAddr, overrideDSCP)) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, _id, + "QOS failed on the RTCP socket"); + _lastError = kQosError; + } + }else + { + const int32_t token_rate = 5000; + const int32_t bucket_size = 100; + const int32_t peek_bandwith = 10000; + if (!rtcpSock->SetQos(serviceType, token_rate, bucket_size, + peek_bandwith, min_policed_size, max_sdu_size, + _remoteRTCPAddr, _overrideDSCP)) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, _id, + "QOS failed on the RTCP socket"); + _lastError = kQosError; + } + } + _qos = true; + _serviceType = serviceType; + _maxBitrate = maxBitrate; + _overrideDSCP = overrideDSCP; + return 0; +} + +int32_t UdpTransportImpl::DisableQoS() +{ + if(_qos == false) + { + return 0; + } + CriticalSectionScoped cs(_crit); + + UdpSocketWrapper* rtpSock = (_ptrSendRtpSocket ? + _ptrSendRtpSocket : _ptrRtpSocket); + if (!rtpSock || !rtpSock->ValidHandle()) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "QOS is enabled but not started since we have not yet created the\ + RTP socket"); + return -1; + } + UdpSocketWrapper* rtcpSock = (_ptrSendRtcpSocket ? + _ptrSendRtcpSocket : _ptrRtcpSocket); + if (!rtcpSock || !rtcpSock->ValidHandle()) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "QOS is enabled but not started since we have not yet created the\ + RTCP socket"); + return -1; + } + + const int32_t service_type = 0; // = SERVICETYPE_NOTRAFFIC + const int32_t not_specified = -1; + if (!rtpSock->SetQos(service_type, not_specified, not_specified, + not_specified, not_specified, not_specified, + _remoteRTPAddr, _overrideDSCP)) + { + _lastError = kQosError; + return -1; + } + if (!rtcpSock->SetQos(service_type, not_specified, not_specified, + not_specified, not_specified, not_specified, + _remoteRTCPAddr,_overrideDSCP)) + { + _lastError = kQosError; + } + _qos = false; + return 0; +} + +int32_t UdpTransportImpl::QoS(bool& QoS, int32_t& serviceType, + int32_t& overrideDSCP) const +{ + CriticalSectionScoped cs(_crit); + QoS = _qos; + serviceType = _serviceType; + overrideDSCP = _overrideDSCP; + return 0; +} + +int32_t UdpTransportImpl::SetToS(int32_t DSCP, bool useSetSockOpt) +{ + if (_qos) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, "QoS already enabled"); + _lastError = kQosError; + return -1; + } + if (DSCP < 0 || DSCP > 63) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, "Invalid DSCP"); + _lastError = kTosInvalid; + return -1; + } + if(_tos) + { + if(useSetSockOpt != _useSetSockOpt) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "Can't switch SetSockOpt method without disabling TOS first"); + _lastError = kTosInvalid; + return -1; + } + } + CriticalSectionScoped cs(_crit); + UdpSocketWrapper* rtpSock = NULL; + UdpSocketWrapper* rtcpSock = NULL; + if(_ptrSendRtpSocket) + { + rtpSock = _ptrSendRtpSocket; + }else + { + rtpSock = _ptrRtpSocket; + } + if (rtpSock == NULL) + { + _lastError = kSocketInvalid; + return -1; + } + if(!rtpSock->ValidHandle()) + { + _lastError = kSocketInvalid; + return -1; + } + if(_ptrSendRtcpSocket) + { + rtcpSock = _ptrSendRtcpSocket; + }else + { + rtcpSock = _ptrRtcpSocket; + } + if (rtcpSock == NULL) + { + _lastError = kSocketInvalid; + return -1; + } + if(!rtcpSock->ValidHandle()) + { + _lastError = kSocketInvalid; + return -1; + } + + if (useSetSockOpt) + { +#ifdef _WIN32 + OSVERSIONINFO OsVersion; + OsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&OsVersion); + // Disable QoS before setting ToS on Windows XP. This is done by closing + // and re-opening the sockets. + // TODO (hellner): why not just fail here and force the user to + // re-initialize sockets? Doing this may trick the user + // into thinking that the sockets are in a state which + // they aren't. + if (OsVersion.dwMajorVersion == 5 && + OsVersion.dwMinorVersion == 1) + { + if(!_useSetSockOpt) + { + if(_ptrSendRtpSocket) + { + CloseSendSockets(); + _ptrSendRtpSocket = + _socket_creator->CreateSocket(_id, _mgr, NULL, + NULL, IpV6Enabled(), + true); + _ptrSendRtcpSocket = + _socket_creator->CreateSocket(_id, _mgr, NULL, + NULL, IpV6Enabled(), + true); + rtpSock=_ptrSendRtpSocket; + rtcpSock=_ptrSendRtcpSocket; + ErrorCode retVal = BindRTPSendSocket(); + if(retVal != kNoSocketError) + { + _lastError = retVal; + return -1; + } + retVal = BindRTCPSendSocket(); + if(retVal != kNoSocketError) + { + _lastError = retVal; + return -1; + } + } + else + { + bool receiving=_receiving; + uint32_t noOfReceiveBuffers = 0; + if(receiving) + { + noOfReceiveBuffers=_ptrRtpSocket->ReceiveBuffers(); + if(StopReceiving()!=0) + { + return -1; + } + } + CloseReceiveSockets(); + _ptrRtpSocket = _socket_creator->CreateSocket( + _id, _mgr, this, IncomingRTPCallback, IpV6Enabled(), + true); + _ptrRtcpSocket = _socket_creator->CreateSocket( + _id, _mgr, this, IncomingRTCPCallback, IpV6Enabled(), + true); + rtpSock=_ptrRtpSocket; + rtcpSock=_ptrRtcpSocket; + ErrorCode retVal = BindLocalRTPSocket(); + if(retVal != kNoSocketError) + { + _lastError = retVal; + return -1; + } + retVal = BindLocalRTCPSocket(); + if(retVal != kNoSocketError) + { + _lastError = retVal; + return -1; + } + if(receiving) + { + if(StartReceiving(noOfReceiveBuffers) != + kNoSocketError) + { + return -1; + } + } + } + } + } +#endif // #ifdef _WIN32 + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "Setting TOS using SetSockopt"); + int32_t TOSShifted = DSCP << 2; + if (!rtpSock->SetSockopt(IPPROTO_IP, IP_TOS, + (int8_t*) &TOSShifted, 4)) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Could not SetSockopt tos value on RTP socket"); + _lastError = kTosInvalid; + return -1; + } + if (!rtcpSock->SetSockopt(IPPROTO_IP, IP_TOS, + (int8_t*) &TOSShifted, 4)) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Could not sSetSockopt tos value on RTCP socket"); + _lastError = kTosInvalid; + return -1; + } + } else + { + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, + "Setting TOS NOT using SetSockopt"); + if (rtpSock->SetTOS(DSCP) != 0) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Could not set tos value on RTP socket"); + _lastError = kTosError; + return -1; + } + if (rtcpSock->SetTOS(DSCP) != 0) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Could not set tos value on RTCP socket"); + _lastError = kTosError; + return -1; + } + } + _useSetSockOpt = useSetSockOpt; + _tos = DSCP; + return 0; +} + +int32_t UdpTransportImpl::ToS(int32_t& DSCP, + bool& useSetSockOpt) const +{ + CriticalSectionScoped cs(_crit); + DSCP = _tos; + useSetSockOpt = _useSetSockOpt; + return 0; +} + +int32_t UdpTransportImpl::SetPCP(int32_t PCP) +{ + + if (_qos) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, "QoS already enabled"); + _lastError = kQosError; + return -1; + } + if ((PCP < 0) || (PCP > 7)) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, "Invalid PCP"); + _lastError = kPcpError; + return -1; + } + + CriticalSectionScoped cs(_crit); + UdpSocketWrapper* rtpSock = NULL; + UdpSocketWrapper* rtcpSock = NULL; + if(_ptrSendRtpSocket) + { + rtpSock = _ptrSendRtpSocket; + }else + { + rtpSock = _ptrRtpSocket; + } + if (rtpSock == NULL) + { + _lastError = kSocketInvalid; + return -1; + } + if(!rtpSock->ValidHandle()) + { + _lastError = kSocketInvalid; + return -1; + } + if(_ptrSendRtcpSocket) + { + rtcpSock = _ptrSendRtcpSocket; + }else + { + rtcpSock = _ptrRtcpSocket; + } + if (rtcpSock == NULL) + { + _lastError = kSocketInvalid; + return -1; + } + if(!rtcpSock->ValidHandle()) + { + _lastError = kSocketInvalid; + return -1; + } + +#if defined(_WIN32) + if (rtpSock->SetPCP(PCP) != 0) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Could not set PCP value on RTP socket"); + _lastError = kPcpError; + return -1; + } + if (rtcpSock->SetPCP(PCP) != 0) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Could not set PCP value on RTCP socket"); + _lastError = kPcpError; + return -1; + } + +#elif defined(WEBRTC_LINUX) + if (!rtpSock->SetSockopt(SOL_SOCKET, SO_PRIORITY, (int8_t*) &PCP, + sizeof(PCP))) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Could not SetSockopt PCP value on RTP socket"); + _lastError = kPcpError; + return -1; + } + if (!rtcpSock->SetSockopt(SOL_SOCKET, SO_PRIORITY, (int8_t*) &PCP, + sizeof(PCP))) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Could not SetSockopt PCP value on RTCP socket"); + _lastError = kPcpError; + return -1; + } +#else + // Not supported on other platforms (WEBRTC_MAC) + _lastError = kPcpError; + return -1; +#endif + _pcp = PCP; + return 0; +} + +int32_t UdpTransportImpl::PCP(int32_t& PCP) const +{ + CriticalSectionScoped cs(_crit); + PCP = _pcp; + return 0; +} + +bool UdpTransportImpl::SetSockOptUsed() +{ + return _useSetSockOpt; +} + +int32_t UdpTransportImpl::EnableIpV6() { + + CriticalSectionScoped cs(_crit); + const bool initialized = (_ptrSendRtpSocket || _ptrRtpSocket); + + if (_ipV6Enabled) { + return 0; + } + if (initialized) { + _lastError = kIpVersion6Error; + return -1; + } + _ipV6Enabled = true; + return 0; +} + +int32_t UdpTransportImpl::FilterIP( + char filterIPAddress[kIpAddressVersion6Length]) const +{ + + if(filterIPAddress == NULL) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "FilterIP: Invalid argument"); + return -1; + } + if(_filterIPAddress._sockaddr_storage.sin_family == 0) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, "No Filter configured"); + return -1; + } + CriticalSectionScoped cs(_critFilter); + uint32_t ipSize = kIpAddressVersion6Length; + uint16_t sourcePort; + return IPAddress(_filterIPAddress, filterIPAddress, ipSize, sourcePort); +} + +int32_t UdpTransportImpl::SetFilterIP( + const char filterIPAddress[kIpAddressVersion6Length]) +{ + if(filterIPAddress == NULL) + { + memset(&_filterIPAddress, 0, sizeof(_filterIPAddress)); + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, "Filter IP reset"); + return 0; + } + CriticalSectionScoped cs(_critFilter); + if (_ipV6Enabled) + { + _filterIPAddress._sockaddr_storage.sin_family = AF_INET6; + + if (InetPresentationToNumeric( + AF_INET6, + filterIPAddress, + &_filterIPAddress._sockaddr_in6.sin6_addr) < 0) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, "Failed to set\ + filter IP for IPv6"); + _lastError = FILTER_ERROR; + return -1; + } + } + else + { + _filterIPAddress._sockaddr_storage.sin_family = AF_INET; + + if(InetPresentationToNumeric( + AF_INET, + filterIPAddress, + &_filterIPAddress._sockaddr_in.sin_addr) < 0) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Failed to set filter IP for IPv4"); + _lastError = FILTER_ERROR; + return -1; + } + } + WEBRTC_TRACE(kTraceDebug, kTraceTransport, _id, "Filter IP set"); + return 0; +} + +int32_t UdpTransportImpl::SetFilterPorts(uint16_t rtpFilterPort, + uint16_t rtcpFilterPort) +{ + CriticalSectionScoped cs(_critFilter); + _rtpFilterPort = rtpFilterPort; + _rtcpFilterPort = rtcpFilterPort; + return 0; +} + +bool UdpTransportImpl::SendSocketsInitialized() const +{ + CriticalSectionScoped cs(_crit); + if(_ptrSendRtpSocket) + { + return true; + } + if(_destPort !=0) + { + return true; + } + return false; +} + +bool UdpTransportImpl::ReceiveSocketsInitialized() const +{ + if(_ptrRtpSocket) + { + return true; + } + return false; +} + +bool UdpTransportImpl::SourcePortsInitialized() const +{ + if(_ptrSendRtpSocket) + { + return true; + } + return false; +} + +bool UdpTransportImpl::IpV6Enabled() const +{ + WEBRTC_TRACE(kTraceStream, kTraceTransport, _id, "%s", __FUNCTION__); + return _ipV6Enabled; +} + +void UdpTransportImpl::BuildRemoteRTPAddr() +{ + if(_ipV6Enabled) + { +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + _remoteRTPAddr.sin_length = 0; + _remoteRTPAddr.sin_family = PF_INET6; +#else + _remoteRTPAddr._sockaddr_storage.sin_family = PF_INET6; +#endif + + _remoteRTPAddr._sockaddr_in6.sin6_flowinfo=0; + _remoteRTPAddr._sockaddr_in6.sin6_scope_id=0; + _remoteRTPAddr._sockaddr_in6.sin6_port = Htons(_destPort); + InetPresentationToNumeric(AF_INET6,_destIP, + &_remoteRTPAddr._sockaddr_in6.sin6_addr); + } else + { +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + _remoteRTPAddr.sin_length = 0; + _remoteRTPAddr.sin_family = PF_INET; +#else + _remoteRTPAddr._sockaddr_storage.sin_family = PF_INET; +#endif + _remoteRTPAddr._sockaddr_in.sin_port = Htons(_destPort); + _remoteRTPAddr._sockaddr_in.sin_addr = InetAddrIPV4(_destIP); + } +} + +void UdpTransportImpl::BuildRemoteRTCPAddr() +{ + if(_ipV6Enabled) + { +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + _remoteRTCPAddr.sin_length = 0; + _remoteRTCPAddr.sin_family = PF_INET6; +#else + _remoteRTCPAddr._sockaddr_storage.sin_family = PF_INET6; +#endif + + _remoteRTCPAddr._sockaddr_in6.sin6_flowinfo=0; + _remoteRTCPAddr._sockaddr_in6.sin6_scope_id=0; + _remoteRTCPAddr._sockaddr_in6.sin6_port = Htons(_destPortRTCP); + InetPresentationToNumeric(AF_INET6,_destIP, + &_remoteRTCPAddr._sockaddr_in6.sin6_addr); + + } else + { +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + _remoteRTCPAddr.sin_length = 0; + _remoteRTCPAddr.sin_family = PF_INET; +#else + _remoteRTCPAddr._sockaddr_storage.sin_family = PF_INET; +#endif + _remoteRTCPAddr._sockaddr_in.sin_port = Htons(_destPortRTCP); + _remoteRTCPAddr._sockaddr_in.sin_addr= InetAddrIPV4(_destIP); + } +} + +UdpTransportImpl::ErrorCode UdpTransportImpl::BindRTPSendSocket() +{ + if(!_ptrSendRtpSocket) + { + return kSocketInvalid; + } + if(!_ptrSendRtpSocket->ValidHandle()) + { + return kIpAddressInvalid; + } + if(_ipV6Enabled) + { +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + _localRTPAddr.sin_length = 0; + _localRTPAddr.sin_family = PF_INET6; +#else + _localRTPAddr._sockaddr_storage.sin_family = PF_INET6; +#endif + _localRTPAddr._sockaddr_in6.sin6_flowinfo=0; + _localRTPAddr._sockaddr_in6.sin6_scope_id=0; + _localRTPAddr._sockaddr_in6.sin6_addr.Version6AddressUnion._s6_u32[0] = + 0; // = INADDR_ANY + _localRTPAddr._sockaddr_in6.sin6_addr.Version6AddressUnion._s6_u32[1] = + 0; + _localRTPAddr._sockaddr_in6.sin6_addr.Version6AddressUnion._s6_u32[2] = + 0; + _localRTPAddr._sockaddr_in6.sin6_addr.Version6AddressUnion._s6_u32[3] = + 0; + _localRTPAddr._sockaddr_in6.sin6_port = Htons(_srcPort); + if(_ptrSendRtpSocket->Bind(_localRTPAddr) == false) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, _id, + "Failed to bind to port:%d ", _srcPort); + return kFailedToBindPort; + } + + } else { +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + _localRTPAddr.sin_length = 0; + _localRTPAddr.sin_family = PF_INET; +#else + _localRTPAddr._sockaddr_storage.sin_family = PF_INET; +#endif + _localRTPAddr._sockaddr_in.sin_addr = 0; + _localRTPAddr._sockaddr_in.sin_port = Htons(_srcPort); + if(_ptrSendRtpSocket->Bind(_localRTPAddr) == false) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, _id, + "Failed to bind to port:%d ", _srcPort); + return kFailedToBindPort; + } + } + return kNoSocketError; +} + +UdpTransportImpl::ErrorCode UdpTransportImpl::BindRTCPSendSocket() +{ + if(!_ptrSendRtcpSocket) + { + return kSocketInvalid; + } + + if(_ipV6Enabled) + { +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + _localRTCPAddr.sin_length = 0; + _localRTCPAddr.sin_family = PF_INET6; +#else + _localRTCPAddr._sockaddr_storage.sin_family = PF_INET6; +#endif + _localRTCPAddr._sockaddr_in6.sin6_flowinfo=0; + _localRTCPAddr._sockaddr_in6.sin6_scope_id=0; + _localRTCPAddr._sockaddr_in6.sin6_addr.Version6AddressUnion._s6_u32[0] = + 0; // = INADDR_ANY + _localRTCPAddr._sockaddr_in6.sin6_addr.Version6AddressUnion._s6_u32[1] = + 0; + _localRTCPAddr._sockaddr_in6.sin6_addr.Version6AddressUnion._s6_u32[2] = + 0; + _localRTCPAddr._sockaddr_in6.sin6_addr.Version6AddressUnion._s6_u32[3] = + 0; + _localRTCPAddr._sockaddr_in6.sin6_port = Htons(_srcPortRTCP); + if(_ptrSendRtcpSocket->Bind(_localRTCPAddr) == false) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, _id, + "Failed to bind to port:%d ", _srcPortRTCP); + return kFailedToBindPort; + } + } else { +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + _localRTCPAddr.sin_length = 0; + _localRTCPAddr.sin_family = PF_INET; +#else + _localRTCPAddr._sockaddr_storage.sin_family = PF_INET; +#endif + _localRTCPAddr._sockaddr_in.sin_addr= 0; + _localRTCPAddr._sockaddr_in.sin_port = Htons(_srcPortRTCP); + if(_ptrSendRtcpSocket->Bind(_localRTCPAddr) == false) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, _id, + "Failed to bind to port:%d ", _srcPortRTCP); + return kFailedToBindPort; + } + } + return kNoSocketError; +} + +UdpTransportImpl::ErrorCode UdpTransportImpl::BindLocalRTPSocket() +{ + if(!_ptrRtpSocket) + { + return kSocketInvalid; + } + if(!IpV6Enabled()) + { + SocketAddress recAddr; + memset(&recAddr, 0, sizeof(SocketAddress)); + recAddr._sockaddr_storage.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + recAddr.sin_length = 0; + recAddr.sin_family = PF_INET; +#else + recAddr._sockaddr_storage.sin_family = PF_INET; +#endif + recAddr._sockaddr_in.sin_addr = InetAddrIPV4(_localIP); + recAddr._sockaddr_in.sin_port = Htons(_localPort); + + if (!_ptrRtpSocket->Bind(recAddr)) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, _id, + "Failed to bind to port:%d ", _localPort); + return kFailedToBindPort; + } + } + else + { + SocketAddress stLclName; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + stLclName.sin_lenght = 0; + stLclName.sin_family = PF_INET6; +#else + stLclName._sockaddr_storage.sin_family = PF_INET6; +#endif + InetPresentationToNumeric(AF_INET6,_localIP, + &stLclName._sockaddr_in6.sin6_addr); + stLclName._sockaddr_in6.sin6_port = Htons(_localPort); + stLclName._sockaddr_in6.sin6_flowinfo = 0; + stLclName._sockaddr_in6.sin6_scope_id = 0; + + if (!_ptrRtpSocket->Bind(stLclName)) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, _id, + "Failed to bind to port:%d ", _localPort); + return kFailedToBindPort; + } + } + + if(_localMulticastIP[0] != 0) + { + // Join the multicast group from which to receive datagrams. + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = InetAddrIPV4(_localMulticastIP); + mreq.imr_interface.s_addr = INADDR_ANY; + + if (!_ptrRtpSocket->SetSockopt(IPPROTO_IP,IP_ADD_MEMBERSHIP, + (int8_t*)&mreq,sizeof (mreq))) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "setsockopt() for multicast failed, not closing socket"); + }else + { + WEBRTC_TRACE(kTraceInfo, kTraceTransport, _id, + "multicast group successfully joined"); + } + } + return kNoSocketError; +} + +UdpTransportImpl::ErrorCode UdpTransportImpl::BindLocalRTCPSocket() +{ + if(!_ptrRtcpSocket) + { + return kSocketInvalid; + } + if(! IpV6Enabled()) + { + SocketAddress recAddr; + memset(&recAddr, 0, sizeof(SocketAddress)); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + recAddr.sin_length = 0; + recAddr.sin_family = AF_INET; +#else + recAddr._sockaddr_storage.sin_family = AF_INET; +#endif + recAddr._sockaddr_in.sin_addr = InetAddrIPV4(_localIP); + recAddr._sockaddr_in.sin_port = Htons(_localPortRTCP); + + if (!_ptrRtcpSocket->Bind(recAddr)) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, _id, + "Failed to bind to port:%d ", _localPortRTCP); + return kFailedToBindPort; + } + } + else + { + SocketAddress stLclName; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + stLclName.sin_length = 0; + stLclName.sin_family = PF_INET6; +#else + stLclName._sockaddr_storage.sin_family = PF_INET6; +#endif + stLclName._sockaddr_in6.sin6_flowinfo = 0; + stLclName._sockaddr_in6.sin6_scope_id = 0; + stLclName._sockaddr_in6.sin6_port = Htons(_localPortRTCP); + + InetPresentationToNumeric(AF_INET6,_localIP, + &stLclName._sockaddr_in6.sin6_addr); + if (!_ptrRtcpSocket->Bind(stLclName)) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, _id, + "Failed to bind to port:%d ", _localPortRTCP); + return kFailedToBindPort; + } + } + if(_localMulticastIP[0] != 0) + { + // Join the multicast group from which to receive datagrams. + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = InetAddrIPV4(_localMulticastIP); + mreq.imr_interface.s_addr = INADDR_ANY; + + if (!_ptrRtcpSocket->SetSockopt(IPPROTO_IP,IP_ADD_MEMBERSHIP, + (int8_t*)&mreq,sizeof (mreq))) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "setsockopt() for multicast failed, not closing socket"); + }else + { + WEBRTC_TRACE(kTraceInfo, kTraceTransport, _id, + "multicast group successfully joined"); + } + } + return kNoSocketError; +} + +int32_t UdpTransportImpl::InitializeSourcePorts(uint16_t rtpPort, + uint16_t rtcpPort) +{ + + if(rtpPort == 0) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "InitializeSourcePorts port 0 not allowed"); + _lastError = kPortInvalid; + return -1; + } + + CriticalSectionScoped cs(_crit); + + CloseSendSockets(); + + if(_mgr == NULL) + { + return -1; + } + + _srcPort = rtpPort; + if(rtcpPort == 0) + { + _srcPortRTCP = rtpPort+1; + } else + { + _srcPortRTCP = rtcpPort; + } + _useSetSockOpt =false; + _tos=0; + _pcp=0; + + _ptrSendRtpSocket = _socket_creator->CreateSocket(_id, _mgr, NULL, NULL, + IpV6Enabled(), false); + _ptrSendRtcpSocket = _socket_creator->CreateSocket(_id, _mgr, NULL, NULL, + IpV6Enabled(), false); + + ErrorCode retVal = BindRTPSendSocket(); + if(retVal != kNoSocketError) + { + _lastError = retVal; + return -1; + } + retVal = BindRTCPSendSocket(); + if(retVal != kNoSocketError) + { + _lastError = retVal; + return -1; + } + return 0; +} + +int32_t UdpTransportImpl::SourcePorts(uint16_t& rtpPort, + uint16_t& rtcpPort) const +{ + CriticalSectionScoped cs(_crit); + + rtpPort = (_srcPort != 0) ? _srcPort : _localPort; + rtcpPort = (_srcPortRTCP != 0) ? _srcPortRTCP : _localPortRTCP; + return 0; +} + + +#ifdef _WIN32 +int32_t UdpTransportImpl::StartReceiving(uint32_t numberOfSocketBuffers) +#else +int32_t UdpTransportImpl::StartReceiving(uint32_t /*numberOfSocketBuffers*/) +#endif +{ + CriticalSectionScoped cs(_crit); + if(_receiving) + { + return 0; + } + if(_ptrRtpSocket) + { +#ifdef _WIN32 + if(!_ptrRtpSocket->StartReceiving(numberOfSocketBuffers)) +#else + if(!_ptrRtpSocket->StartReceiving()) +#endif + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Failed to start receive on RTP socket"); + _lastError = kStartReceiveError; + return -1; + } + } + if(_ptrRtcpSocket) + { + if(!_ptrRtcpSocket->StartReceiving()) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Failed to start receive on RTCP socket"); + _lastError = kStartReceiveError; + return -1; + } + } + if( _ptrRtpSocket == NULL && + _ptrRtcpSocket == NULL) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Failed to StartReceiving, no socket initialized"); + _lastError = kStartReceiveError; + return -1; + } + _receiving = true; + return 0; +} + +bool UdpTransportImpl::Receiving() const +{ + return _receiving; +} + +int32_t UdpTransportImpl::StopReceiving() +{ + + CriticalSectionScoped cs(_crit); + + _receiving = false; + + if (_ptrRtpSocket) + { + if (!_ptrRtpSocket->StopReceiving()) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Failed to stop receiving on RTP socket"); + _lastError = kStopReceiveError; + return -1; + } + } + if (_ptrRtcpSocket) + { + if (!_ptrRtcpSocket->StopReceiving()) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "Failed to stop receiving on RTCP socket"); + _lastError = kStopReceiveError; + return -1; + } + } + return 0; +} + +int32_t UdpTransportImpl::InitializeSendSockets( + const char* ipaddr, + const uint16_t rtpPort, + const uint16_t rtcpPort) +{ + { + CriticalSectionScoped cs(_crit); + _destPort = rtpPort; + if(rtcpPort == 0) + { + _destPortRTCP = _destPort+1; + } else + { + _destPortRTCP = rtcpPort; + } + + if(ipaddr == NULL) + { + if (!IsIpAddressValid(_destIP, IpV6Enabled())) + { + _destPort = 0; + _destPortRTCP = 0; + _lastError = kIpAddressInvalid; + return -1; + } + } else + { + if (IsIpAddressValid(ipaddr, IpV6Enabled())) + { + strncpy( + _destIP, + ipaddr, + IpV6Enabled() ? kIpAddressVersion6Length : + kIpAddressVersion4Length); + } else { + _destPort = 0; + _destPortRTCP = 0; + _lastError = kIpAddressInvalid; + return -1; + } + } + BuildRemoteRTPAddr(); + BuildRemoteRTCPAddr(); + } + + if (_ipV6Enabled) + { + if (_qos) + { + WEBRTC_TRACE( + kTraceWarning, + kTraceTransport, + _id, + "QOS is enabled but will be ignored since IPv6 is enabled"); + } + }else + { + // TODO (grunell): Multicast support is experimantal. + + // Put the first digit of the remote address in val. + int32_t val = ntohl(_remoteRTPAddr._sockaddr_in.sin_addr)>> 24; + + if((val > 223) && (val < 240)) + { + // Multicast address. + CriticalSectionScoped cs(_crit); + + UdpSocketWrapper* rtpSock = (_ptrSendRtpSocket ? + _ptrSendRtpSocket : _ptrRtpSocket); + if (!rtpSock || !rtpSock->ValidHandle()) + { + _lastError = kSocketInvalid; + return -1; + } + UdpSocketWrapper* rtcpSock = (_ptrSendRtcpSocket ? + _ptrSendRtcpSocket : _ptrRtcpSocket); + if (!rtcpSock || !rtcpSock->ValidHandle()) + { + _lastError = kSocketInvalid; + return -1; + } + + // Set Time To Live to same region + int32_t iOptVal = 64; + if (!rtpSock->SetSockopt(IPPROTO_IP, IP_MULTICAST_TTL, + (int8_t*)&iOptVal, + sizeof (int32_t))) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "setsockopt for multicast error on RTP socket"); + _ptrRtpSocket->CloseBlocking(); + _ptrRtpSocket = NULL; + _lastError = kMulticastAddressInvalid; + return -1; + } + if (!rtcpSock->SetSockopt(IPPROTO_IP, IP_MULTICAST_TTL, + (int8_t*)&iOptVal, + sizeof (int32_t))) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "setsockopt for multicast error on RTCP socket"); + _ptrRtpSocket->CloseBlocking(); + _ptrRtpSocket = NULL; + _lastError = kMulticastAddressInvalid; + return -1; + } + } + } + return 0; +} + +void UdpTransportImpl::BuildSockaddrIn(uint16_t portnr, + const char* ip, + SocketAddress& remoteAddr) const +{ + if(_ipV6Enabled) + { +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + remoteAddr.sin_length = 0; + remoteAddr.sin_family = PF_INET6; +#else + remoteAddr._sockaddr_storage.sin_family = PF_INET6; +#endif + remoteAddr._sockaddr_in6.sin6_port = Htons(portnr); + InetPresentationToNumeric(AF_INET6, ip, + &remoteAddr._sockaddr_in6.sin6_addr); + remoteAddr._sockaddr_in6.sin6_flowinfo=0; + remoteAddr._sockaddr_in6.sin6_scope_id=0; + } else + { +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + remoteAddr.sin_length = 0; + remoteAddr.sin_family = PF_INET; +#else + remoteAddr._sockaddr_storage.sin_family = PF_INET; +#endif + remoteAddr._sockaddr_in.sin_port = Htons(portnr); + remoteAddr._sockaddr_in.sin_addr= InetAddrIPV4( + const_cast<char*>(ip)); + } +} + +int32_t UdpTransportImpl::SendRaw(const int8_t *data, + size_t length, + int32_t isRTCP, + uint16_t portnr, + const char* ip) +{ + CriticalSectionScoped cs(_crit); + if(isRTCP) + { + UdpSocketWrapper* rtcpSock = NULL; + if(_ptrSendRtcpSocket) + { + rtcpSock = _ptrSendRtcpSocket; + } else if(_ptrRtcpSocket) + { + rtcpSock = _ptrRtcpSocket; + } else + { + return -1; + } + if(portnr == 0 && ip == NULL) + { + return rtcpSock->SendTo(data,length,_remoteRTCPAddr); + + } else if(portnr != 0 && ip != NULL) + { + SocketAddress remoteAddr; + BuildSockaddrIn(portnr, ip, remoteAddr); + return rtcpSock->SendTo(data,length,remoteAddr); + } else if(ip != NULL) + { + SocketAddress remoteAddr; + BuildSockaddrIn(_destPortRTCP, ip, remoteAddr); + return rtcpSock->SendTo(data,length,remoteAddr); + } else + { + SocketAddress remoteAddr; + BuildSockaddrIn(portnr, _destIP, remoteAddr); + return rtcpSock->SendTo(data,length,remoteAddr); + } + } else { + UdpSocketWrapper* rtpSock = NULL; + if(_ptrSendRtpSocket) + { + rtpSock = _ptrSendRtpSocket; + + } else if(_ptrRtpSocket) + { + rtpSock = _ptrRtpSocket; + } else + { + return -1; + } + if(portnr == 0 && ip == NULL) + { + return rtpSock->SendTo(data,length,_remoteRTPAddr); + + } else if(portnr != 0 && ip != NULL) + { + SocketAddress remoteAddr; + BuildSockaddrIn(portnr, ip, remoteAddr); + return rtpSock->SendTo(data,length,remoteAddr); + } else if(ip != NULL) + { + SocketAddress remoteAddr; + BuildSockaddrIn(_destPort, ip, remoteAddr); + return rtpSock->SendTo(data,length,remoteAddr); + } else + { + SocketAddress remoteAddr; + BuildSockaddrIn(portnr, _destIP, remoteAddr); + return rtpSock->SendTo(data,length,remoteAddr); + } + } +} + +int32_t UdpTransportImpl::SendRTPPacketTo(const int8_t* data, + size_t length, + const SocketAddress& to) +{ + CriticalSectionScoped cs(_crit); + if(_ptrSendRtpSocket) + { + return _ptrSendRtpSocket->SendTo(data,length,to); + + } else if(_ptrRtpSocket) + { + return _ptrRtpSocket->SendTo(data,length,to); + } + return -1; +} + +int32_t UdpTransportImpl::SendRTCPPacketTo(const int8_t* data, + size_t length, + const SocketAddress& to) +{ + + CriticalSectionScoped cs(_crit); + + if(_ptrSendRtcpSocket) + { + return _ptrSendRtcpSocket->SendTo(data,length,to); + + } else if(_ptrRtcpSocket) + { + return _ptrRtcpSocket->SendTo(data,length,to); + } + return -1; +} + +int32_t UdpTransportImpl::SendRTPPacketTo(const int8_t* data, + size_t length, + const uint16_t rtpPort) +{ + CriticalSectionScoped cs(_crit); + // Use the current SocketAdress but update it with rtpPort. + SocketAddress to; + memcpy(&to, &_remoteRTPAddr, sizeof(SocketAddress)); + + if(_ipV6Enabled) + { + to._sockaddr_in6.sin6_port = Htons(rtpPort); + } else + { + to._sockaddr_in.sin_port = Htons(rtpPort); + } + + if(_ptrSendRtpSocket) + { + return _ptrSendRtpSocket->SendTo(data,length,to); + + } else if(_ptrRtpSocket) + { + return _ptrRtpSocket->SendTo(data,length,to); + } + return -1; +} + +int32_t UdpTransportImpl::SendRTCPPacketTo(const int8_t* data, + size_t length, + const uint16_t rtcpPort) +{ + CriticalSectionScoped cs(_crit); + + // Use the current SocketAdress but update it with rtcpPort. + SocketAddress to; + memcpy(&to, &_remoteRTCPAddr, sizeof(SocketAddress)); + + if(_ipV6Enabled) + { + to._sockaddr_in6.sin6_port = Htons(rtcpPort); + } else + { + to._sockaddr_in.sin_port = Htons(rtcpPort); + } + + if(_ptrSendRtcpSocket) + { + return _ptrSendRtcpSocket->SendTo(data,length,to); + + } else if(_ptrRtcpSocket) + { + return _ptrRtcpSocket->SendTo(data,length,to); + } + return -1; +} + +bool UdpTransportImpl::SendRtp(const uint8_t* data, + size_t length, + const PacketOptions& packet_options) { + WEBRTC_TRACE(kTraceStream, kTraceTransport, _id, "%s", __FUNCTION__); + + CriticalSectionScoped cs(_crit); + + if(_destIP[0] == 0) + { + return false; + } + if(_destPort == 0) + { + return false; + } + + // Create socket if it hasn't been set up already. + // TODO (hellner): why not fail here instead. Sockets not being initialized + // indicates that there is a problem somewhere. + if( _ptrSendRtpSocket == NULL && + _ptrRtpSocket == NULL) + { + WEBRTC_TRACE( + kTraceStateInfo, + kTraceTransport, + _id, + "Creating RTP socket since no receive or source socket is\ + configured"); + + _ptrRtpSocket = _socket_creator->CreateSocket(_id, _mgr, this, + IncomingRTPCallback, + IpV6Enabled(), false); + + // Don't bind to a specific IP address. + if(! IpV6Enabled()) + { + strncpy(_localIP, "0.0.0.0",16); + } else + { + strncpy(_localIP, "0000:0000:0000:0000:0000:0000:0000:0000", + kIpAddressVersion6Length); + } + _localPort = _destPort; + + ErrorCode retVal = BindLocalRTPSocket(); + if(retVal != kNoSocketError) + { + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "SendPacket() failed to bind RTP socket"); + _lastError = retVal; + CloseReceiveSockets(); + return false; + } + } + + if(_ptrSendRtpSocket) + { + return _ptrSendRtpSocket->SendTo((const int8_t*)data, length, + _remoteRTPAddr) >= 0; + + } else if(_ptrRtpSocket) + { + return _ptrRtpSocket->SendTo((const int8_t*)data, length, + _remoteRTPAddr) >= 0; + } + return false; +} + +bool UdpTransportImpl::SendRtcp(const uint8_t* data, size_t length) { + CriticalSectionScoped cs(_crit); + if(_destIP[0] == 0) + { + return false; + } + if(_destPortRTCP == 0) + { + return false; + } + + // Create socket if it hasn't been set up already. + // TODO (hellner): why not fail here instead. Sockets not being initialized + // indicates that there is a problem somewhere. + if( _ptrSendRtcpSocket == NULL && + _ptrRtcpSocket == NULL) + { + WEBRTC_TRACE( + kTraceStateInfo, + kTraceTransport, + _id, + "Creating RTCP socket since no receive or source socket is\ + configured"); + + _ptrRtcpSocket = _socket_creator->CreateSocket(_id, _mgr, this, + IncomingRTCPCallback, + IpV6Enabled(), false); + + // Don't bind to a specific IP address. + if(! IpV6Enabled()) + { + strncpy(_localIP, "0.0.0.0",16); + } else + { + strncpy(_localIP, "0000:0000:0000:0000:0000:0000:0000:0000", + kIpAddressVersion6Length); + } + _localPortRTCP = _destPortRTCP; + + ErrorCode retVal = BindLocalRTCPSocket(); + if(retVal != kNoSocketError) + { + _lastError = retVal; + WEBRTC_TRACE(kTraceError, kTraceTransport, _id, + "SendRtcp() failed to bind RTCP socket"); + CloseReceiveSockets(); + return false; + } + } + + if(_ptrSendRtcpSocket) + { + return _ptrSendRtcpSocket->SendTo((const int8_t*)data, length, + _remoteRTCPAddr) >= 0; + } else if(_ptrRtcpSocket) + { + return _ptrRtcpSocket->SendTo((const int8_t*)data, length, + _remoteRTCPAddr) >= 0; + } + return false; +} + +int32_t UdpTransportImpl::SetSendIP(const char* ipaddr) +{ + if(!IsIpAddressValid(ipaddr,IpV6Enabled())) + { + return kIpAddressInvalid; + } + CriticalSectionScoped cs(_crit); + strncpy(_destIP, ipaddr,kIpAddressVersion6Length); + BuildRemoteRTPAddr(); + BuildRemoteRTCPAddr(); + return 0; +} + +int32_t UdpTransportImpl::SetSendPorts(uint16_t rtpPort, uint16_t rtcpPort) +{ + CriticalSectionScoped cs(_crit); + _destPort = rtpPort; + if(rtcpPort == 0) + { + _destPortRTCP = _destPort+1; + } else + { + _destPortRTCP = rtcpPort; + } + BuildRemoteRTPAddr(); + BuildRemoteRTCPAddr(); + return 0; +} + +void UdpTransportImpl::IncomingRTPCallback(CallbackObj obj, + const int8_t* rtpPacket, + size_t rtpPacketLength, + const SocketAddress* from) +{ + if (rtpPacket && rtpPacketLength > 0) + { + UdpTransportImpl* socketTransport = (UdpTransportImpl*) obj; + socketTransport->IncomingRTPFunction(rtpPacket, rtpPacketLength, from); + } +} + +void UdpTransportImpl::IncomingRTCPCallback(CallbackObj obj, + const int8_t* rtcpPacket, + size_t rtcpPacketLength, + const SocketAddress* from) +{ + if (rtcpPacket && rtcpPacketLength > 0) + { + UdpTransportImpl* socketTransport = (UdpTransportImpl*) obj; + socketTransport->IncomingRTCPFunction(rtcpPacket, rtcpPacketLength, + from); + } +} + +void UdpTransportImpl::IncomingRTPFunction(const int8_t* rtpPacket, + size_t rtpPacketLength, + const SocketAddress* fromSocket) +{ + char ipAddress[kIpAddressVersion6Length]; + uint32_t ipAddressLength = kIpAddressVersion6Length; + uint16_t portNr = 0; + + { + CriticalSectionScoped cs(_critFilter); + if (FilterIPAddress(fromSocket) == false) + { + // Packet should be filtered out. Drop it. + WEBRTC_TRACE(kTraceStream, kTraceTransport, _id, + "Incoming RTP packet blocked by IP filter"); + return; + } + + if (IPAddressCached(*fromSocket, ipAddress, ipAddressLength, portNr) < + 0) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpTransportImpl::IncomingRTPFunction - Cannot get sender\ + information"); + }else + { + // Make sure ipAddress is null terminated. + ipAddress[kIpAddressVersion6Length - 1] = 0; + strncpy(_fromIP, ipAddress, kIpAddressVersion6Length - 1); + } + + // Filter based on port. + if (_rtpFilterPort != 0 && + _rtpFilterPort != portNr) + { + // Drop packet. + memset(_fromIP, 0, sizeof(_fromIP)); + WEBRTC_TRACE( + kTraceStream, + kTraceTransport, + _id, + "Incoming RTP packet blocked by filter incoming from port:%d\ + allowed port:%d", + portNr, + _rtpFilterPort); + return; + } + _fromPort = portNr; + } + + CriticalSectionScoped cs(_critPacketCallback); + if (_packetCallback) + { + WEBRTC_TRACE(kTraceStream, kTraceTransport, _id, + "Incoming RTP packet from ip:%s port:%d", ipAddress, portNr); + _packetCallback->IncomingRTPPacket(rtpPacket, rtpPacketLength, + ipAddress, portNr); + } +} + +void UdpTransportImpl::IncomingRTCPFunction(const int8_t* rtcpPacket, + size_t rtcpPacketLength, + const SocketAddress* fromSocket) +{ + char ipAddress[kIpAddressVersion6Length]; + uint32_t ipAddressLength = kIpAddressVersion6Length; + uint16_t portNr = 0; + + { + CriticalSectionScoped cs(_critFilter); + if (FilterIPAddress(fromSocket) == false) + { + // Packet should be filtered out. Drop it. + WEBRTC_TRACE(kTraceStream, kTraceTransport, _id, + "Incoming RTCP packet blocked by IP filter"); + return; + } + if (IPAddress(*fromSocket, ipAddress, ipAddressLength, portNr) < 0) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpTransportImpl::IncomingRTCPFunction - Cannot get sender\ + information"); + }else { + // Make sure ipAddress is null terminated. + ipAddress[kIpAddressVersion6Length - 1] = 0; + strncpy(_fromIP, ipAddress, kIpAddressVersion6Length - 1); + } + + // Filter based on port. + if (_rtcpFilterPort != 0 && + _rtcpFilterPort != portNr) + { + // Drop packet. + WEBRTC_TRACE( + kTraceStream, + kTraceTransport, + _id, + "Incoming RTCP packet blocked by filter incoming from port:%d\ + allowed port:%d", + portNr, + _rtpFilterPort); + return; + } + _fromPortRTCP = portNr; + } + + CriticalSectionScoped cs(_critPacketCallback); + if (_packetCallback) + { + WEBRTC_TRACE(kTraceStream, kTraceTransport, _id, + "Incoming RTCP packet from ip:%s port:%d", ipAddress, + portNr); + _packetCallback->IncomingRTCPPacket(rtcpPacket, rtcpPacketLength, + ipAddress, portNr); + } +} + +bool UdpTransportImpl::FilterIPAddress(const SocketAddress* fromAddress) +{ + if(fromAddress->_sockaddr_storage.sin_family == AF_INET) + { + if (_filterIPAddress._sockaddr_storage.sin_family == AF_INET) + { + // IP is stored in sin_addr. + if (_filterIPAddress._sockaddr_in.sin_addr != 0 && + (_filterIPAddress._sockaddr_in.sin_addr != + fromAddress->_sockaddr_in.sin_addr)) + { + return false; + } + } + } + else if(fromAddress->_sockaddr_storage.sin_family == AF_INET6) + { + if (_filterIPAddress._sockaddr_storage.sin_family == AF_INET6) + { + // IP is stored in sin_6addr. + for (int32_t i = 0; i < 4; i++) + { + if (_filterIPAddress._sockaddr_in6.sin6_addr.Version6AddressUnion._s6_u32[i] != 0 && + _filterIPAddress._sockaddr_in6.sin6_addr.Version6AddressUnion._s6_u32[i] != fromAddress->_sockaddr_in6.sin6_addr.Version6AddressUnion._s6_u32[i]) + { + return false; + } + } + } + } + else + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + _id, + "UdpTransportImpl::FilterIPAddress() unknown address family"); + return false; + } + return true; +} + +void UdpTransportImpl::CloseReceiveSockets() +{ + if(_ptrRtpSocket) + { + _ptrRtpSocket->CloseBlocking(); + _ptrRtpSocket = NULL; + } + if(_ptrRtcpSocket) + { + _ptrRtcpSocket->CloseBlocking(); + _ptrRtcpSocket = NULL; + } + _receiving = false; +} + +void UdpTransportImpl::CloseSendSockets() +{ + if(_ptrSendRtpSocket) + { + _ptrSendRtpSocket->CloseBlocking(); + _ptrSendRtpSocket = 0; + } + if(_ptrSendRtcpSocket) + { + _ptrSendRtcpSocket->CloseBlocking(); + _ptrSendRtcpSocket = 0; + } +} + +uint16_t UdpTransport::Htons(const uint16_t port) +{ + return htons(port); +} + +uint32_t UdpTransport::Htonl(const uint32_t a) +{ + return htonl(a); +} + +uint32_t UdpTransport::InetAddrIPV4(const char* ip) +{ + return ::inet_addr(ip); +} + +int32_t UdpTransport::InetPresentationToNumeric(int32_t af, + const char* src, + void* dst) +{ +#if defined(WEBRTC_LINUX) || defined(WEBRTC_MAC) + const int32_t result = inet_pton(af, src, dst); + return result > 0 ? 0 : -1; + +#elif defined(_WIN32) + SocketAddress temp; + int length=sizeof(SocketAddress); + + if(af == AF_INET) + { + int32_t result = WSAStringToAddressA( + (const LPSTR)src, + af, + 0, + reinterpret_cast<struct sockaddr*>(&temp), + &length); + if(result != 0) + { + return -1; + } + memcpy(dst,&(temp._sockaddr_in.sin_addr), + sizeof(temp._sockaddr_in.sin_addr)); + return 0; + } + else if(af == AF_INET6) + { + int32_t result = WSAStringToAddressA( + (const LPSTR)src, + af, + 0, + reinterpret_cast<struct sockaddr*>(&temp), + &length); + if(result !=0) + { + return -1; + } + memcpy(dst,&(temp._sockaddr_in6.sin6_addr), + sizeof(temp._sockaddr_in6.sin6_addr)); + return 0; + + }else + { + return -1; + } +#else + return -1; +#endif +} + +int32_t UdpTransport::LocalHostAddressIPV6(char n_localIP[16]) +{ + +#if defined(_WIN32) + struct addrinfo *result = NULL; + struct addrinfo *ptr = NULL; + struct addrinfo hints; + + ZeroMemory(&hints, sizeof(hints)); + hints.ai_family = AF_INET6; + + char szHostName[256] = ""; + if(::gethostname(szHostName, sizeof(szHostName) - 1)) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, -1, "gethostname failed"); + return -1; + } + + DWORD dwRetval = getaddrinfo(szHostName, NULL, &hints, &result); + if ( dwRetval != 0 ) + { + WEBRTC_TRACE(kTraceWarning, kTraceTransport, -1, + "getaddrinfo failed, error:%d", dwRetval); + return -1; + } + for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) + { + switch (ptr->ai_family) + { + case AF_INET6: + { + for(int i = 0; i< 16; i++) + { + n_localIP[i] = (*(SocketAddress*)ptr->ai_addr). + _sockaddr_in6.sin6_addr.Version6AddressUnion._s6_u8[i]; + } + bool islocalIP = true; + + for(int n = 0; n< 15; n++) + { + if(n_localIP[n] != 0) + { + islocalIP = false; + break; + } + } + + if(islocalIP && n_localIP[15] != 1) + { + islocalIP = false; + } + + if(islocalIP && ptr->ai_next) + { + continue; + } + if(n_localIP[0] == 0xfe && + n_localIP[1] == 0x80 && ptr->ai_next) + { + continue; + } + freeaddrinfo(result); + } + return 0; + default: + break; + }; + } + freeaddrinfo(result); + WEBRTC_TRACE(kTraceWarning, kTraceTransport, -1, + "getaddrinfo failed to find address"); + return -1; + +#elif defined(WEBRTC_MAC) + struct ifaddrs* ptrIfAddrs = NULL; + struct ifaddrs* ptrIfAddrsStart = NULL; + + getifaddrs(&ptrIfAddrsStart); + ptrIfAddrs = ptrIfAddrsStart; + while(ptrIfAddrs) + { + if(ptrIfAddrs->ifa_addr->sa_family == AF_INET6) + { + const struct sockaddr_in6* sock_in6 = + reinterpret_cast<struct sockaddr_in6*>(ptrIfAddrs->ifa_addr); + const struct in6_addr* sin6_addr = &sock_in6->sin6_addr; + + if (IN6_IS_ADDR_LOOPBACK(sin6_addr) || + IN6_IS_ADDR_LINKLOCAL(sin6_addr)) { + ptrIfAddrs = ptrIfAddrs->ifa_next; + continue; + } + memcpy(n_localIP, sin6_addr->s6_addr, sizeof(sin6_addr->s6_addr)); + freeifaddrs(ptrIfAddrsStart); + return 0; + } + ptrIfAddrs = ptrIfAddrs->ifa_next; + } + freeifaddrs(ptrIfAddrsStart); + return -1; +#elif defined(WEBRTC_ANDROID) + return -1; +#else // WEBRTC_LINUX + struct + { + struct nlmsghdr n; + struct ifaddrmsg r; + } req; + + struct rtattr* rta = NULL; + int status; + char buf[16384]; // = 16 * 1024 (16 kB) + struct nlmsghdr* nlmp; + struct ifaddrmsg* rtmp; + struct rtattr* rtatp; + int rtattrlen; + struct in6_addr* in6p; + + int fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); + if (fd == -1) + { + return -1; + } + + // RTM_GETADDR is used to fetch the ip address from the kernel interface + // table. Populate the msg structure (req) the size of the message buffer + // is specified to netlinkmessage header, and flags values are set as + // NLM_F_ROOT | NLM_F_REQUEST. + // The request flag must be set for all messages requesting the data from + // kernel. The root flag is used to notify the kernel to return the full + // tabel. Another flag (not used) is NLM_F_MATCH. This is used to get only + // specified entries in the table. At the time of writing this program this + // flag is not implemented in kernel + + memset(&req, 0, sizeof(req)); + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT; + req.n.nlmsg_type = RTM_GETADDR; + req.r.ifa_family = AF_INET6; + + // Fill up all the attributes for the rtnetlink header. + // The lenght is very important. 16 signifies the ipv6 address. + rta = (struct rtattr*)(((char*)&req) + NLMSG_ALIGN(req.n.nlmsg_len)); + rta->rta_len = RTA_LENGTH(16); + + status = send(fd, &req, req.n.nlmsg_len, 0); + if (status < 0) + { + close(fd); + return -1; + } + status = recv(fd, buf, sizeof(buf), 0); + if (status < 0) + { + close(fd); + return -1; + } + if(status == 0) + { + close(fd); + return -1; + } + close(fd); + + // The message is stored in buff. Parse the message to get the requested + // data. + { + nlmp = (struct nlmsghdr*)buf; + int len = nlmp->nlmsg_len; + int req_len = len - sizeof(*nlmp); + + if (req_len < 0 || len > status) + { + return -1; + } + if (!NLMSG_OK_NO_WARNING(nlmp, status)) + { + return -1; + } + rtmp = (struct ifaddrmsg*)NLMSG_DATA(nlmp); + rtatp = (struct rtattr*)IFA_RTA(rtmp); + + rtattrlen = IFA_PAYLOAD(nlmp); + + for (; RTA_OK(rtatp, rtattrlen); rtatp = RTA_NEXT(rtatp, rtattrlen)) + { + + // Here we hit the fist chunk of the message. Time to validate the + // type. For more info on the different types see + // "man(7) rtnetlink" The table below is taken from man pages. + // Attributes + // rta_type value type description + // ------------------------------------------------------------- + // IFA_UNSPEC - unspecified. + // IFA_ADDRESS raw protocol address interface address + // IFA_LOCAL raw protocol address local address + // IFA_LABEL asciiz string name of the interface + // IFA_BROADCAST raw protocol address broadcast address. + // IFA_ANYCAST raw protocol address anycast address + // IFA_CACHEINFO struct ifa_cacheinfo Address information. + + if(rtatp->rta_type == IFA_ADDRESS) + { + bool islocalIP = true; + in6p = (struct in6_addr*)RTA_DATA(rtatp); + for(int n = 0; n< 15; n++) + { + if(in6p->s6_addr[n] != 0) + { + islocalIP = false; + break; + } + } + if(islocalIP && in6p->s6_addr[15] != 1) + { + islocalIP = false; + } + if(!islocalIP) + { + for(int i = 0; i< 16; i++) + { + n_localIP[i] = in6p->s6_addr[i]; + } + if(n_localIP[0] == static_cast<char> (0xfe) + && n_localIP[1] == static_cast<char>(0x80) ) + { + // Auto configured IP. + continue; + } + break; + } + } + } + } + return 0; +#endif +} + +int32_t UdpTransport::LocalHostAddress(uint32_t& localIP) +{ + #if defined(_WIN32) + hostent* localHost; + localHost = gethostbyname( "" ); + if(localHost) + { + if(localHost->h_addrtype != AF_INET) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + -1, + "LocalHostAddress can only get local IP for IP Version 4"); + return -1; + } + localIP= Htonl( + (*(struct in_addr *)localHost->h_addr_list[0]).S_un.S_addr); + return 0; + } + else + { + int32_t error = WSAGetLastError(); + WEBRTC_TRACE(kTraceWarning, kTraceTransport, -1, + "gethostbyname failed, error:%d", error); + return -1; + } +#elif (defined(WEBRTC_MAC)) + char localname[255]; + if (gethostname(localname, 255) != -1) + { + hostent* localHost; + localHost = gethostbyname(localname); + if(localHost) + { + if(localHost->h_addrtype != AF_INET) + { + WEBRTC_TRACE( + kTraceError, + kTraceTransport, + -1, + "LocalHostAddress can only get local IP for IP Version 4"); + return -1; + } + localIP = Htonl((*(struct in_addr*)*localHost->h_addr_list).s_addr); + return 0; + } + } + WEBRTC_TRACE(kTraceWarning, kTraceTransport, -1, "gethostname failed"); + return -1; +#else // WEBRTC_LINUX + int sockfd, size = 1; + struct ifreq* ifr; + struct ifconf ifc; + + if (0 > (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP))) + { + return -1; + } + ifc.ifc_len = IFRSIZE; + ifc.ifc_req = NULL; + do + { + ++size; + // Buffer size needed is unknown. Try increasing it until no overflow + // occurs. + if (NULL == (ifc.ifc_req = (ifreq*)realloc(ifc.ifc_req, IFRSIZE))) { + fprintf(stderr, "Out of memory.\n"); + exit(EXIT_FAILURE); + } + ifc.ifc_len = IFRSIZE; + if (ioctl(sockfd, SIOCGIFCONF, &ifc)) + { + free(ifc.ifc_req); + close(sockfd); + return -1; + } + } while (IFRSIZE <= ifc.ifc_len); + + ifr = ifc.ifc_req; + for (;(char *) ifr < (char *) ifc.ifc_req + ifc.ifc_len; ++ifr) + { + if (ifr->ifr_addr.sa_data == (ifr+1)->ifr_addr.sa_data) + { + continue; // duplicate, skip it + } + if (ioctl(sockfd, SIOCGIFFLAGS, ifr)) + { + continue; // failed to get flags, skip it + } + if(strncmp(ifr->ifr_name, "lo",3) == 0) + { + continue; + }else + { + struct sockaddr* saddr = &(ifr->ifr_addr); + SocketAddress* socket_addess = reinterpret_cast<SocketAddress*>( + saddr); + localIP = Htonl(socket_addess->_sockaddr_in.sin_addr); + close(sockfd); + free(ifc.ifc_req); + return 0; + } + } + free(ifc.ifc_req); + close(sockfd); + return -1; +#endif +} + +int32_t UdpTransport::IPAddress(const SocketAddress& address, + char* ip, + uint32_t& ipSize, + uint16_t& sourcePort) +{ + #if defined(_WIN32) + DWORD dwIPSize = ipSize; + int32_t returnvalue = WSAAddressToStringA((LPSOCKADDR)(&address), + sizeof(SocketAddress), + NULL, + ip, + &dwIPSize); + if(returnvalue == -1) + { + return -1; + } + + uint16_t source_port = 0; + if(address._sockaddr_storage.sin_family == AF_INET) + { + // Parse IP assuming format "a.b.c.d:port". + char* ipEnd = strchr(ip,':'); + if(ipEnd != NULL) + { + *ipEnd = '\0'; + } + ipSize = (int32_t)strlen(ip); + if(ipSize == 0) + { + return -1; + } + source_port = address._sockaddr_in.sin_port; + } + else + { + // Parse IP assuming format "[address]:port". + char* ipEnd = strchr(ip,']'); + if(ipEnd != NULL) + { + // Calculate length + int32_t adrSize = int32_t(ipEnd - ip) - 1; + memmove(ip, &ip[1], adrSize); // Remove '[' + *(ipEnd - 1) = '\0'; + } + ipSize = (int32_t)strlen(ip); + if(ipSize == 0) + { + return -1; + } + + source_port = address._sockaddr_in6.sin6_port; + } + // Convert port number to network byte order. + sourcePort = htons(source_port); + return 0; + + #elif defined(WEBRTC_LINUX) || defined(WEBRTC_MAC) + int32_t ipFamily = address._sockaddr_storage.sin_family; + const void* ptrNumericIP = NULL; + + if(ipFamily == AF_INET) + { + ptrNumericIP = &(address._sockaddr_in.sin_addr); + } + else if(ipFamily == AF_INET6) + { + ptrNumericIP = &(address._sockaddr_in6.sin6_addr); + } + else + { + return -1; + } + if(inet_ntop(ipFamily, ptrNumericIP, ip, ipSize) == NULL) + { + return -1; + } + uint16_t source_port; + if(ipFamily == AF_INET) + { + source_port = address._sockaddr_in.sin_port; + } else + { + source_port = address._sockaddr_in6.sin6_port; + } + // Convert port number to network byte order. + sourcePort = htons(source_port); + return 0; + #else + return -1; + #endif +} + +bool UdpTransport::IsIpAddressValid(const char* ipadr, const bool ipV6) +{ + if(ipV6) + { + int32_t len = (int32_t)strlen(ipadr); + if( len>39 || len == 0) + { + return false; + } + + int32_t i; + int32_t colonPos[7] = {0,0,0,0,0,0,0}; + int32_t lastColonPos = -2; + int32_t nColons = 0; + int32_t nDubbleColons = 0; + int32_t nDots = 0; + int32_t error = 0; + char c; + for(i = 0; i < len ; i++) + { + c=ipadr[i]; + if(isxdigit(c)) + ; + else if(c == ':') + { + if(nColons < 7) + colonPos[nColons] = i; + if((i-lastColonPos)==1) + nDubbleColons++; + lastColonPos=i; + if(nDots != 0) + { + error = 1; + } + nColons++; + } + else if(c == '.') + { + nDots++; + } + else + { + error = 1; + } + + } + if(error) + { + return false; + } + if(nDubbleColons > 1) + { + return false; + } + if(nColons > 7 || nColons < 2) + { + return false; + } + if(!(nDots == 3 || nDots == 0)) + { + return false; + } + lastColonPos = -1; + int32_t charsBeforeColon = 0; + for(i = 0; i < nColons; i++) + { + charsBeforeColon=colonPos[i]-lastColonPos-1; + if(charsBeforeColon > 4) + { + return false; + } + lastColonPos=colonPos[i]; + } + int32_t lengthAfterLastColon = len - lastColonPos - 1; + if(nDots == 0) + { + if(lengthAfterLastColon > 4) + return false; + } + if(nDots == 3 && lengthAfterLastColon > 0) + { + return IsIpAddressValid((ipadr+lastColonPos+1),false); + } + + } + else + { + int32_t len = (int32_t)strlen(ipadr); + if((len>15)||(len==0)) + { + return false; + } + + // IPv4 should be [0-255].[0-255].[0-255].[0-255] + int32_t i; + int32_t nDots = 0; + int32_t iDotPos[4] = {0,0,0,0}; + + for (i = 0; (i < len) && (nDots < 4); i++) + { + if (ipadr[i] == (char)'.') + { + // Store index of dots and count number of dots. + iDotPos[nDots++] = i; + } + } + + bool allUnder256 = false; + // TODO (hellner): while loop seems to be abused here to get + // label like functionality. Fix later to avoid introducing bugs now. + + // Check that all numbers are smaller than 256. + do + { + if (nDots != 3 ) + { + break; + } + + if (iDotPos[0] <= 3) + { + char nr[4]; + memset(nr,0,4); + strncpy(nr,&ipadr[0],iDotPos[0]); + int32_t num = atoi(nr); + if (num > 255) + { + break; + } + } else { + break; + } + + if (iDotPos[1] - iDotPos[0] <= 4) + { + char nr[4]; + memset(nr,0,4); + strncpy(nr,&ipadr[iDotPos[0]+1], iDotPos[1] - iDotPos[0] - 1); + int32_t num = atoi(nr); + if (num > 255) + break; + } else { + break; + } + + if (iDotPos[2] - iDotPos[1] <= 4) + { + char nr[4]; + memset(nr,0,4); + strncpy(nr,&ipadr[iDotPos[1]+1], iDotPos[1] - iDotPos[0] - 1); + int32_t num = atoi(nr); + if (num > 255) + break; + + memset(nr,0,4); + strncpy(nr,&ipadr[iDotPos[2]+1], len - iDotPos[2] -1); + num = atoi(nr); + if (num > 255) + break; + else + allUnder256 = true; + } else + break; + } while(false); + + if (nDots != 3 || !allUnder256) + { + return false; + } + } + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/channel_transport/udp_transport_impl.h b/webrtc/test/channel_transport/udp_transport_impl.h new file mode 100644 index 0000000000..f80ee02d71 --- /dev/null +++ b/webrtc/test/channel_transport/udp_transport_impl.h @@ -0,0 +1,259 @@ +/* + * 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_TEST_CHANNEL_TRANSPORT_UDP_TRANSPORT_IMPL_H_ +#define WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_TRANSPORT_IMPL_H_ + +#include "webrtc/test/channel_transport/udp_socket_wrapper.h" +#include "webrtc/test/channel_transport/udp_transport.h" + +namespace webrtc { + +class CriticalSectionWrapper; +class RWLockWrapper; + +namespace test { + +class UdpSocketManager; + +class UdpTransportImpl : public UdpTransport +{ +public: + // A factory that returns a wrapped UDP socket or equivalent. + class SocketFactoryInterface { + public: + virtual ~SocketFactoryInterface() {} + virtual UdpSocketWrapper* CreateSocket(const int32_t id, + UdpSocketManager* mgr, + CallbackObj obj, + IncomingSocketCallback cb, + bool ipV6Enable, + bool disableGQOS) = 0; + }; + + // Constructor, only called by UdpTransport::Create and tests. + // The constructor takes ownership of the "maker". + // The constructor does not take ownership of socket_manager. + UdpTransportImpl(const int32_t id, + SocketFactoryInterface* maker, + UdpSocketManager* socket_manager); + virtual ~UdpTransportImpl(); + + // UdpTransport functions + int32_t InitializeSendSockets(const char* ipAddr, + const uint16_t rtpPort, + const uint16_t rtcpPort = 0) override; + int32_t InitializeReceiveSockets(UdpTransportData* const packetCallback, + const uint16_t rtpPort, + const char* ipAddr = NULL, + const char* multicastIpAddr = NULL, + const uint16_t rtcpPort = 0) override; + int32_t InitializeSourcePorts(const uint16_t rtpPort, + const uint16_t rtcpPort = 0) override; + int32_t SourcePorts(uint16_t& rtpPort, uint16_t& rtcpPort) const override; + int32_t ReceiveSocketInformation( + char ipAddr[kIpAddressVersion6Length], + uint16_t& rtpPort, + uint16_t& rtcpPort, + char multicastIpAddr[kIpAddressVersion6Length]) const override; + int32_t SendSocketInformation(char ipAddr[kIpAddressVersion6Length], + uint16_t& rtpPort, + uint16_t& rtcpPort) const override; + int32_t RemoteSocketInformation(char ipAddr[kIpAddressVersion6Length], + uint16_t& rtpPort, + uint16_t& rtcpPort) const override; + int32_t SetQoS(const bool QoS, + const int32_t serviceType, + const uint32_t maxBitrate = 0, + const int32_t overrideDSCP = 0, + const bool audio = false) override; + int32_t QoS(bool& QoS, + int32_t& serviceType, + int32_t& overrideDSCP) const override; + int32_t SetToS(const int32_t DSCP, + const bool useSetSockOpt = false) override; + int32_t ToS(int32_t& DSCP, bool& useSetSockOpt) const override; + int32_t SetPCP(const int32_t PCP) override; + int32_t PCP(int32_t& PCP) const override; + int32_t EnableIpV6() override; + bool IpV6Enabled() const override; + int32_t SetFilterIP( + const char filterIPAddress[kIpAddressVersion6Length]) override; + int32_t FilterIP( + char filterIPAddress[kIpAddressVersion6Length]) const override; + int32_t SetFilterPorts(const uint16_t rtpFilterPort, + const uint16_t rtcpFilterPort) override; + int32_t FilterPorts(uint16_t& rtpFilterPort, + uint16_t& rtcpFilterPort) const override; + int32_t StartReceiving(const uint32_t numberOfSocketBuffers) override; + int32_t StopReceiving() override; + bool Receiving() const override; + bool SendSocketsInitialized() const override; + bool SourcePortsInitialized() const override; + bool ReceiveSocketsInitialized() const override; + int32_t SendRaw(const int8_t* data, + size_t length, + int32_t isRTCP, + uint16_t portnr = 0, + const char* ip = NULL) override; + int32_t SendRTPPacketTo(const int8_t* data, + size_t length, + const SocketAddress& to) override; + int32_t SendRTCPPacketTo(const int8_t* data, + size_t length, + const SocketAddress& to) override; + int32_t SendRTPPacketTo(const int8_t* data, + size_t length, + uint16_t rtpPort) override; + int32_t SendRTCPPacketTo(const int8_t* data, + size_t length, + uint16_t rtcpPort) override; + // Transport functions + bool SendRtp(const uint8_t* data, + size_t length, + const PacketOptions& packet_options) override; + bool SendRtcp(const uint8_t* data, size_t length) override; + + // UdpTransport functions continue. + int32_t SetSendIP(const char* ipaddr) override; + int32_t SetSendPorts(const uint16_t rtpPort, + const uint16_t rtcpPort = 0) override; + + ErrorCode LastError() const override; + + int32_t IPAddressCached(const SocketAddress& address, + char* ip, + uint32_t& ipSize, + uint16_t& sourcePort) override; + + int32_t Id() const {return _id;} +protected: + // IncomingSocketCallback signature functions for receiving callbacks from + // UdpSocketWrapper. + static void IncomingRTPCallback(CallbackObj obj, + const int8_t* rtpPacket, + size_t rtpPacketLength, + const SocketAddress* from); + static void IncomingRTCPCallback(CallbackObj obj, + const int8_t* rtcpPacket, + size_t rtcpPacketLength, + const SocketAddress* from); + + void CloseSendSockets(); + void CloseReceiveSockets(); + + // Update _remoteRTPAddr according to _destPort and _destIP + void BuildRemoteRTPAddr(); + // Update _remoteRTCPAddr according to _destPortRTCP and _destIP + void BuildRemoteRTCPAddr(); + + void BuildSockaddrIn(uint16_t portnr, const char* ip, + SocketAddress& remoteAddr) const; + + ErrorCode BindLocalRTPSocket(); + ErrorCode BindLocalRTCPSocket(); + + ErrorCode BindRTPSendSocket(); + ErrorCode BindRTCPSendSocket(); + + void IncomingRTPFunction(const int8_t* rtpPacket, + size_t rtpPacketLength, + const SocketAddress* from); + void IncomingRTCPFunction(const int8_t* rtcpPacket, + size_t rtcpPacketLength, + const SocketAddress* from); + + bool FilterIPAddress(const SocketAddress* fromAddress); + + bool SetSockOptUsed(); + + int32_t EnableQoS(int32_t serviceType, bool audio, + uint32_t maxBitrate, int32_t overrideDSCP); + + int32_t DisableQoS(); + +private: + void GetCachedAddress(char* ip, uint32_t& ipSize, + uint16_t& sourcePort); + + int32_t _id; + SocketFactoryInterface* _socket_creator; + // Protects the sockets from being re-configured while receiving packets. + CriticalSectionWrapper* _crit; + CriticalSectionWrapper* _critFilter; + // _packetCallback's critical section. + CriticalSectionWrapper* _critPacketCallback; + UdpSocketManager* _mgr; + ErrorCode _lastError; + + // Remote RTP and RTCP ports. + uint16_t _destPort; + uint16_t _destPortRTCP; + + // Local RTP and RTCP ports. + uint16_t _localPort; + uint16_t _localPortRTCP; + + // Local port number when the local port for receiving and local port number + // for sending are not the same. + uint16_t _srcPort; + uint16_t _srcPortRTCP; + + // Remote port from which last received packet was sent. + uint16_t _fromPort; + uint16_t _fromPortRTCP; + + char _fromIP[kIpAddressVersion6Length]; + char _destIP[kIpAddressVersion6Length]; + char _localIP[kIpAddressVersion6Length]; + char _localMulticastIP[kIpAddressVersion6Length]; + + UdpSocketWrapper* _ptrRtpSocket; + UdpSocketWrapper* _ptrRtcpSocket; + + // Local port when the local port for receiving and local port for sending + // are not the same. + UdpSocketWrapper* _ptrSendRtpSocket; + UdpSocketWrapper* _ptrSendRtcpSocket; + + SocketAddress _remoteRTPAddr; + SocketAddress _remoteRTCPAddr; + + SocketAddress _localRTPAddr; + SocketAddress _localRTCPAddr; + + int32_t _tos; + bool _receiving; + bool _useSetSockOpt; + bool _qos; + int32_t _pcp; + bool _ipV6Enabled; + int32_t _serviceType; + int32_t _overrideDSCP; + uint32_t _maxBitrate; + + // Cache used by GetCachedAddress(..). + RWLockWrapper* _cachLock; + SocketAddress _previousAddress; + char _previousIP[kIpAddressVersion6Length]; + uint32_t _previousIPSize; + uint16_t _previousSourcePort; + + SocketAddress _filterIPAddress; + uint16_t _rtpFilterPort; + uint16_t _rtcpFilterPort; + + UdpTransportData* _packetCallback; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_CHANNEL_TRANSPORT_UDP_TRANSPORT_IMPL_H_ diff --git a/webrtc/test/channel_transport/udp_transport_unittest.cc b/webrtc/test/channel_transport/udp_transport_unittest.cc new file mode 100644 index 0000000000..975f52b756 --- /dev/null +++ b/webrtc/test/channel_transport/udp_transport_unittest.cc @@ -0,0 +1,143 @@ +/* + * 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 <vector> + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/test/channel_transport/udp_transport.h" +// We include the implementation header file to get at the dependency-injecting +// constructor. +#include "webrtc/test/channel_transport/udp_transport_impl.h" +// We must mock the socket manager, for which we need its definition. +#include "webrtc/test/channel_transport/udp_socket_manager_wrapper.h" + +using ::testing::_; +using ::testing::Return; + +namespace webrtc { +namespace test { + +class MockUdpSocketWrapper : public UdpSocketWrapper { + public: + // The following methods have to be mocked because they are pure. + MOCK_METHOD2(SetCallback, bool(CallbackObj, IncomingSocketCallback)); + MOCK_METHOD1(Bind, bool(const SocketAddress&)); + MOCK_METHOD0(ValidHandle, bool()); + MOCK_METHOD4(SetSockopt, bool(int32_t, int32_t, + const int8_t*, + int32_t)); + MOCK_METHOD1(SetTOS, int32_t(int32_t)); + MOCK_METHOD3(SendTo, int32_t(const int8_t*, size_t, const SocketAddress&)); + MOCK_METHOD8(SetQos, bool(int32_t, int32_t, + int32_t, int32_t, + int32_t, int32_t, + const SocketAddress &, + int32_t)); +}; + +class MockUdpSocketManager : public UdpSocketManager { + public: + // Access to protected destructor. + void Destroy() { + delete this; + } + MOCK_METHOD2(Init, bool(int32_t, uint8_t&)); + MOCK_METHOD0(Start, bool()); + MOCK_METHOD0(Stop, bool()); + MOCK_METHOD1(AddSocket, bool(UdpSocketWrapper*)); + MOCK_METHOD1(RemoveSocket, bool(UdpSocketWrapper*)); +}; + +class MockSocketFactory : + public UdpTransportImpl::SocketFactoryInterface { + public: + MockSocketFactory(std::vector<MockUdpSocketWrapper*>* socket_counter) + : socket_counter_(socket_counter) { + } + UdpSocketWrapper* CreateSocket(const int32_t id, + UdpSocketManager* mgr, + CallbackObj obj, + IncomingSocketCallback cb, + bool ipV6Enable, + bool disableGQOS) { + MockUdpSocketWrapper* socket = new MockUdpSocketWrapper(); + // We instrument the socket with calls that are expected, but do + // not matter for any specific test, in order to avoid warning messages. + EXPECT_CALL(*socket, ValidHandle()).WillRepeatedly(Return(true)); + EXPECT_CALL(*socket, Bind(_)).WillOnce(Return(true)); + socket_counter_->push_back(socket); + return socket; + } + std::vector<MockUdpSocketWrapper*>* socket_counter_; +}; + +class UDPTransportTest : public ::testing::Test { + public: + UDPTransportTest() + : sockets_created_(0) { + } + + ~UDPTransportTest() { + // In production, sockets register themselves at creation time with + // an UdpSocketManager, and the UdpSocketManager is responsible for + // deleting them. In this test, we just delete them after the test. + while (!sockets_created_.empty()) { + delete sockets_created_.back(); + sockets_created_.pop_back(); + } + } + + int NumSocketsCreated() { + return sockets_created_.size(); + } + + std::vector<MockUdpSocketWrapper*>* sockets_created() { + return &sockets_created_; + } +private: + std::vector<MockUdpSocketWrapper*> sockets_created_; +}; + +TEST_F(UDPTransportTest, CreateTransport) { + int32_t id = 0; + uint8_t threads = 1; + UdpTransport* transport = UdpTransport::Create(id, threads); + UdpTransport::Destroy(transport); +} + +// This test verifies that the mock_socket is not called from the constructor. +TEST_F(UDPTransportTest, ConstructorDoesNotCreateSocket) { + int32_t id = 0; + UdpTransportImpl::SocketFactoryInterface* null_maker = NULL; + UdpSocketManager* null_manager = NULL; + UdpTransport* transport = new UdpTransportImpl(id, + null_maker, + null_manager); + delete transport; +} + +TEST_F(UDPTransportTest, InitializeSourcePorts) { + int32_t id = 0; + UdpTransportImpl::SocketFactoryInterface* mock_maker + = new MockSocketFactory(sockets_created()); + MockUdpSocketManager* mock_manager = new MockUdpSocketManager(); + UdpTransport* transport = new UdpTransportImpl(id, + mock_maker, + mock_manager); + EXPECT_EQ(0, transport->InitializeSourcePorts(4711, 4712)); + EXPECT_EQ(2, NumSocketsCreated()); + + delete transport; + mock_manager->Destroy(); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/common_unittest.cc b/webrtc/test/common_unittest.cc new file mode 100644 index 0000000000..082c18c2c7 --- /dev/null +++ b/webrtc/test/common_unittest.cc @@ -0,0 +1,80 @@ +/* + * 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/common.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace webrtc { +namespace { + +struct MyExperiment { + static const int kDefaultFactor; + static const int kDefaultOffset; + + MyExperiment() + : factor(kDefaultFactor), offset(kDefaultOffset) {} + + MyExperiment(int factor, int offset) + : factor(factor), offset(offset) {} + + int factor; + int offset; +}; + +const int MyExperiment::kDefaultFactor = 1; +const int MyExperiment::kDefaultOffset = 2; + +TEST(Config, ReturnsDefaultInstanceIfNotConfigured) { + Config config; + const MyExperiment& my_exp = config.Get<MyExperiment>(); + EXPECT_EQ(MyExperiment::kDefaultFactor, my_exp.factor); + EXPECT_EQ(MyExperiment::kDefaultOffset, my_exp.offset); +} + +TEST(Config, ReturnOptionWhenSet) { + Config config; + config.Set<MyExperiment>(new MyExperiment(5, 1)); + const MyExperiment& my_exp = config.Get<MyExperiment>(); + EXPECT_EQ(5, my_exp.factor); + EXPECT_EQ(1, my_exp.offset); +} + +TEST(Config, SetNullSetsTheOptionBackToDefault) { + Config config; + config.Set<MyExperiment>(new MyExperiment(5, 1)); + config.Set<MyExperiment>(NULL); + const MyExperiment& my_exp = config.Get<MyExperiment>(); + EXPECT_EQ(MyExperiment::kDefaultFactor, my_exp.factor); + EXPECT_EQ(MyExperiment::kDefaultOffset, my_exp.offset); +} + +struct Algo1_CostFunction { + Algo1_CostFunction() {} + + virtual int cost(int x) const { + return x; + } + + virtual ~Algo1_CostFunction() {} +}; + +struct SqrCost : Algo1_CostFunction { + virtual int cost(int x) const { + return x*x; + } +}; + +TEST(Config, SupportsPolymorphism) { + Config config; + config.Set<Algo1_CostFunction>(new SqrCost()); + EXPECT_EQ(25, config.Get<Algo1_CostFunction>().cost(5)); +} +} // namespace +} // namespace webrtc diff --git a/webrtc/test/configurable_frame_size_encoder.cc b/webrtc/test/configurable_frame_size_encoder.cc new file mode 100644 index 0000000000..2cd47504a5 --- /dev/null +++ b/webrtc/test/configurable_frame_size_encoder.cc @@ -0,0 +1,92 @@ +/* + * 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/test/configurable_frame_size_encoder.h" + +#include <string.h> + +#include "testing/gtest/include/gtest/gtest.h" + +#include "webrtc/common_video/interface/video_image.h" +#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h" + +namespace webrtc { +namespace test { + +ConfigurableFrameSizeEncoder::ConfigurableFrameSizeEncoder( + size_t max_frame_size) + : callback_(NULL), + max_frame_size_(max_frame_size), + current_frame_size_(max_frame_size), + buffer_(new uint8_t[max_frame_size]) { + memset(buffer_.get(), 0, max_frame_size); +} + +ConfigurableFrameSizeEncoder::~ConfigurableFrameSizeEncoder() {} + +int32_t ConfigurableFrameSizeEncoder::InitEncode( + const VideoCodec* codec_settings, + int32_t number_of_cores, + size_t max_payload_size) { + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t ConfigurableFrameSizeEncoder::Encode( + const VideoFrame& inputImage, + const CodecSpecificInfo* codecSpecificInfo, + const std::vector<FrameType>* frame_types) { + EncodedImage encodedImage( + buffer_.get(), current_frame_size_, max_frame_size_); + encodedImage._completeFrame = true; + encodedImage._encodedHeight = inputImage.height(); + encodedImage._encodedWidth = inputImage.width(); + encodedImage._frameType = kVideoFrameKey; + encodedImage._timeStamp = inputImage.timestamp(); + encodedImage.capture_time_ms_ = inputImage.render_time_ms(); + RTPFragmentationHeader* fragmentation = NULL; + CodecSpecificInfo specific; + memset(&specific, 0, sizeof(specific)); + callback_->Encoded(encodedImage, &specific, fragmentation); + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t ConfigurableFrameSizeEncoder::RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) { + callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t ConfigurableFrameSizeEncoder::Release() { + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t ConfigurableFrameSizeEncoder::SetChannelParameters(uint32_t packet_loss, + int64_t rtt) { + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t ConfigurableFrameSizeEncoder::SetRates(uint32_t new_bit_rate, + uint32_t frame_rate) { + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t ConfigurableFrameSizeEncoder::SetPeriodicKeyFrames(bool enable) { + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t ConfigurableFrameSizeEncoder::SetFrameSize(size_t size) { + assert(size <= max_frame_size_); + current_frame_size_ = size; + return WEBRTC_VIDEO_CODEC_OK; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/configurable_frame_size_encoder.h b/webrtc/test/configurable_frame_size_encoder.h new file mode 100644 index 0000000000..3794e8db08 --- /dev/null +++ b/webrtc/test/configurable_frame_size_encoder.h @@ -0,0 +1,58 @@ +/* + * 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_TEST_CONFIGURABLE_FRAME_SIZE_ENCODER_H_ +#define WEBRTC_TEST_CONFIGURABLE_FRAME_SIZE_ENCODER_H_ + +#include <vector> + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/video_encoder.h" + +namespace webrtc { +namespace test { + +class ConfigurableFrameSizeEncoder : public VideoEncoder { + public: + explicit ConfigurableFrameSizeEncoder(size_t max_frame_size); + virtual ~ConfigurableFrameSizeEncoder(); + + int32_t InitEncode(const VideoCodec* codec_settings, + int32_t number_of_cores, + size_t max_payload_size) override; + + int32_t Encode(const VideoFrame& input_image, + const CodecSpecificInfo* codec_specific_info, + const std::vector<FrameType>* frame_types) override; + + int32_t RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) override; + + int32_t Release() override; + + int32_t SetChannelParameters(uint32_t packet_loss, int64_t rtt) override; + + int32_t SetRates(uint32_t new_bit_rate, uint32_t frame_rate) override; + + int32_t SetPeriodicKeyFrames(bool enable) override; + + int32_t SetFrameSize(size_t size); + + private: + EncodedImageCallback* callback_; + const size_t max_frame_size_; + size_t current_frame_size_; + rtc::scoped_ptr<uint8_t[]> buffer_; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_CONFIGURABLE_FRAME_SIZE_ENCODER_H_ diff --git a/webrtc/test/constants.cc b/webrtc/test/constants.cc new file mode 100644 index 0000000000..7e94fe58fb --- /dev/null +++ b/webrtc/test/constants.cc @@ -0,0 +1,20 @@ +/* + * 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/test/constants.h" + +namespace webrtc { +namespace test { + +const int kTOffsetExtensionId = 6; +const int kAbsSendTimeExtensionId = 7; +const int kTransportSequenceNumberExtensionId = 8; +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/constants.h b/webrtc/test/constants.h new file mode 100644 index 0000000000..14b2ba65bc --- /dev/null +++ b/webrtc/test/constants.h @@ -0,0 +1,18 @@ +/* + * 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. + */ + +namespace webrtc { +namespace test { + +extern const int kTOffsetExtensionId; +extern const int kAbsSendTimeExtensionId; +extern const int kTransportSequenceNumberExtensionId; +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/direct_transport.cc b/webrtc/test/direct_transport.cc new file mode 100644 index 0000000000..6dcba81c88 --- /dev/null +++ b/webrtc/test/direct_transport.cc @@ -0,0 +1,104 @@ +/* + * 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/test/direct_transport.h" + +#include "testing/gtest/include/gtest/gtest.h" + +#include "webrtc/call.h" +#include "webrtc/system_wrappers/include/clock.h" + +namespace webrtc { +namespace test { + +DirectTransport::DirectTransport(Call* send_call) + : send_call_(send_call), + packet_event_(EventWrapper::Create()), + thread_( + ThreadWrapper::CreateThread(NetworkProcess, this, "NetworkProcess")), + clock_(Clock::GetRealTimeClock()), + shutting_down_(false), + fake_network_(FakeNetworkPipe::Config()) { + EXPECT_TRUE(thread_->Start()); +} + +DirectTransport::DirectTransport(const FakeNetworkPipe::Config& config, + Call* send_call) + : send_call_(send_call), + packet_event_(EventWrapper::Create()), + thread_( + ThreadWrapper::CreateThread(NetworkProcess, this, "NetworkProcess")), + clock_(Clock::GetRealTimeClock()), + shutting_down_(false), + fake_network_(config) { + EXPECT_TRUE(thread_->Start()); +} + +DirectTransport::~DirectTransport() { StopSending(); } + +void DirectTransport::SetConfig(const FakeNetworkPipe::Config& config) { + fake_network_.SetConfig(config); +} + +void DirectTransport::StopSending() { + { + rtc::CritScope crit(&lock_); + shutting_down_ = true; + } + + packet_event_->Set(); + EXPECT_TRUE(thread_->Stop()); +} + +void DirectTransport::SetReceiver(PacketReceiver* receiver) { + fake_network_.SetReceiver(receiver); +} + +bool DirectTransport::SendRtp(const uint8_t* data, + size_t length, + const PacketOptions& options) { + if (send_call_) { + rtc::SentPacket sent_packet(options.packet_id, + clock_->TimeInMilliseconds()); + send_call_->OnSentPacket(sent_packet); + } + fake_network_.SendPacket(data, length); + packet_event_->Set(); + return true; +} + +bool DirectTransport::SendRtcp(const uint8_t* data, size_t length) { + fake_network_.SendPacket(data, length); + packet_event_->Set(); + return true; +} + +bool DirectTransport::NetworkProcess(void* transport) { + return static_cast<DirectTransport*>(transport)->SendPackets(); +} + +bool DirectTransport::SendPackets() { + fake_network_.Process(); + int64_t wait_time_ms = fake_network_.TimeUntilNextProcess(); + if (wait_time_ms > 0) { + switch (packet_event_->Wait(static_cast<unsigned long>(wait_time_ms))) { + case kEventSignaled: + break; + case kEventTimeout: + break; + case kEventError: + // TODO(pbos): Log a warning here? + return true; + } + } + rtc::CritScope crit(&lock_); + return shutting_down_ ? false : true; +} +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/direct_transport.h b/webrtc/test/direct_transport.h new file mode 100644 index 0000000000..241a5bc110 --- /dev/null +++ b/webrtc/test/direct_transport.h @@ -0,0 +1,66 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_DIRECT_TRANSPORT_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_DIRECT_TRANSPORT_H_ + +#include <assert.h> + +#include <deque> + +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/system_wrappers/include/event_wrapper.h" +#include "webrtc/system_wrappers/include/thread_wrapper.h" +#include "webrtc/test/fake_network_pipe.h" +#include "webrtc/transport.h" + +namespace webrtc { + +class Call; +class Clock; +class PacketReceiver; + +namespace test { + +class DirectTransport : public Transport { + public: + explicit DirectTransport(Call* send_call); + DirectTransport(const FakeNetworkPipe::Config& config, Call* send_call); + ~DirectTransport(); + + void SetConfig(const FakeNetworkPipe::Config& config); + + virtual void StopSending(); + // TODO(holmer): Look into moving this to the constructor. + virtual void SetReceiver(PacketReceiver* receiver); + + bool SendRtp(const uint8_t* data, + size_t length, + const PacketOptions& options) override; + bool SendRtcp(const uint8_t* data, size_t length) override; + + private: + static bool NetworkProcess(void* transport); + bool SendPackets(); + + rtc::CriticalSection lock_; + Call* const send_call_; + rtc::scoped_ptr<EventWrapper> packet_event_; + rtc::scoped_ptr<ThreadWrapper> thread_; + Clock* const clock_; + + bool shutting_down_; + + FakeNetworkPipe fake_network_; +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_DIRECT_TRANSPORT_H_ diff --git a/webrtc/test/encoder_settings.cc b/webrtc/test/encoder_settings.cc new file mode 100644 index 0000000000..bae13505c0 --- /dev/null +++ b/webrtc/test/encoder_settings.cc @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2014 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/encoder_settings.h" + +#include <assert.h> +#include <string.h> + +#include "webrtc/test/fake_decoder.h" +#include "webrtc/video_decoder.h" + +namespace webrtc { +namespace test { +std::vector<VideoStream> CreateVideoStreams(size_t num_streams) { + assert(num_streams > 0); + + // Add more streams to the settings above with reasonable values if required. + static const size_t kNumSettings = 3; + assert(num_streams <= kNumSettings); + + std::vector<VideoStream> stream_settings(kNumSettings); + + stream_settings[0].width = 320; + stream_settings[0].height = 180; + stream_settings[0].max_framerate = 30; + stream_settings[0].min_bitrate_bps = 50000; + stream_settings[0].target_bitrate_bps = stream_settings[0].max_bitrate_bps = + 150000; + stream_settings[0].max_qp = 56; + + stream_settings[1].width = 640; + stream_settings[1].height = 360; + stream_settings[1].max_framerate = 30; + stream_settings[1].min_bitrate_bps = 200000; + stream_settings[1].target_bitrate_bps = stream_settings[1].max_bitrate_bps = + 450000; + stream_settings[1].max_qp = 56; + + stream_settings[2].width = 1280; + stream_settings[2].height = 720; + stream_settings[2].max_framerate = 30; + stream_settings[2].min_bitrate_bps = 700000; + stream_settings[2].target_bitrate_bps = stream_settings[2].max_bitrate_bps = + 1500000; + stream_settings[2].max_qp = 56; + stream_settings.resize(num_streams); + return stream_settings; +} + +VideoReceiveStream::Decoder CreateMatchingDecoder( + const VideoSendStream::Config::EncoderSettings& encoder_settings) { + VideoReceiveStream::Decoder decoder; + decoder.payload_type = encoder_settings.payload_type; + decoder.payload_name = encoder_settings.payload_name; + if (encoder_settings.payload_name == "VP8") { + decoder.decoder = VideoDecoder::Create(VideoDecoder::kVp8); + } else if (encoder_settings.payload_name == "VP9") { + decoder.decoder = VideoDecoder::Create(VideoDecoder::kVp9); + } else { + decoder.decoder = new FakeDecoder(); + } + return decoder; +} +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/encoder_settings.h b/webrtc/test/encoder_settings.h new file mode 100644 index 0000000000..a44d3661e1 --- /dev/null +++ b/webrtc/test/encoder_settings.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2014 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_TEST_ENCODER_SETTINGS_H_ +#define WEBRTC_TEST_ENCODER_SETTINGS_H_ + +#include "webrtc/video_receive_stream.h" +#include "webrtc/video_send_stream.h" + +namespace webrtc { +namespace test { +std::vector<VideoStream> CreateVideoStreams(size_t num_streams); + +VideoReceiveStream::Decoder CreateMatchingDecoder( + const VideoSendStream::Config::EncoderSettings& encoder_settings); +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_ENCODER_SETTINGS_H_ diff --git a/webrtc/test/fake_audio_device.cc b/webrtc/test/fake_audio_device.cc new file mode 100644 index 0000000000..e307dd7664 --- /dev/null +++ b/webrtc/test/fake_audio_device.cc @@ -0,0 +1,151 @@ +/* + * 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/test/fake_audio_device.h" + +#include <algorithm> + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/media_file/source/media_file_utility.h" +#include "webrtc/system_wrappers/include/clock.h" +#include "webrtc/system_wrappers/include/event_wrapper.h" +#include "webrtc/system_wrappers/include/file_wrapper.h" +#include "webrtc/system_wrappers/include/thread_wrapper.h" + +namespace webrtc { +namespace test { + +FakeAudioDevice::FakeAudioDevice(Clock* clock, const std::string& filename) + : audio_callback_(NULL), + capturing_(false), + captured_audio_(), + playout_buffer_(), + last_playout_ms_(-1), + clock_(clock), + tick_(EventTimerWrapper::Create()), + file_utility_(new ModuleFileUtility(0)), + input_stream_(FileWrapper::Create()) { + memset(captured_audio_, 0, sizeof(captured_audio_)); + memset(playout_buffer_, 0, sizeof(playout_buffer_)); + // Open audio input file as read-only and looping. + EXPECT_EQ(0, input_stream_->OpenFile(filename.c_str(), true, true)) + << filename; +} + +FakeAudioDevice::~FakeAudioDevice() { + Stop(); + + if (thread_.get() != NULL) + thread_->Stop(); +} + +int32_t FakeAudioDevice::Init() { + rtc::CritScope cs(&lock_); + if (file_utility_->InitPCMReading(*input_stream_.get()) != 0) + return -1; + + if (!tick_->StartTimer(true, 10)) + return -1; + thread_ = ThreadWrapper::CreateThread(FakeAudioDevice::Run, this, + "FakeAudioDevice"); + if (thread_.get() == NULL) + return -1; + if (!thread_->Start()) { + thread_.reset(); + return -1; + } + thread_->SetPriority(webrtc::kHighPriority); + return 0; +} + +int32_t FakeAudioDevice::RegisterAudioCallback(AudioTransport* callback) { + rtc::CritScope cs(&lock_); + audio_callback_ = callback; + return 0; +} + +bool FakeAudioDevice::Playing() const { + rtc::CritScope cs(&lock_); + return capturing_; +} + +int32_t FakeAudioDevice::PlayoutDelay(uint16_t* delay_ms) const { + *delay_ms = 0; + return 0; +} + +bool FakeAudioDevice::Recording() const { + rtc::CritScope cs(&lock_); + return capturing_; +} + +bool FakeAudioDevice::Run(void* obj) { + static_cast<FakeAudioDevice*>(obj)->CaptureAudio(); + return true; +} + +void FakeAudioDevice::CaptureAudio() { + { + rtc::CritScope cs(&lock_); + if (capturing_) { + int bytes_read = file_utility_->ReadPCMData( + *input_stream_.get(), captured_audio_, kBufferSizeBytes); + if (bytes_read <= 0) + return; + // 2 bytes per sample. + size_t num_samples = static_cast<size_t>(bytes_read / 2); + uint32_t new_mic_level; + EXPECT_EQ(0, + audio_callback_->RecordedDataIsAvailable(captured_audio_, + num_samples, + 2, + 1, + kFrequencyHz, + 0, + 0, + 0, + false, + new_mic_level)); + size_t samples_needed = kFrequencyHz / 100; + int64_t now_ms = clock_->TimeInMilliseconds(); + uint32_t time_since_last_playout_ms = now_ms - last_playout_ms_; + if (last_playout_ms_ > 0 && time_since_last_playout_ms > 0) { + samples_needed = std::min( + static_cast<size_t>(kFrequencyHz / time_since_last_playout_ms), + kBufferSizeBytes / 2); + } + size_t samples_out = 0; + int64_t elapsed_time_ms = -1; + int64_t ntp_time_ms = -1; + EXPECT_EQ(0, + audio_callback_->NeedMorePlayData(samples_needed, + 2, + 1, + kFrequencyHz, + playout_buffer_, + samples_out, + &elapsed_time_ms, + &ntp_time_ms)); + } + } + tick_->Wait(WEBRTC_EVENT_INFINITE); +} + +void FakeAudioDevice::Start() { + rtc::CritScope cs(&lock_); + capturing_ = true; +} + +void FakeAudioDevice::Stop() { + rtc::CritScope cs(&lock_); + capturing_ = false; +} +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/fake_audio_device.h b/webrtc/test/fake_audio_device.h new file mode 100644 index 0000000000..bdc672892c --- /dev/null +++ b/webrtc/test/fake_audio_device.h @@ -0,0 +1,69 @@ +/* + * 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_TEST_FAKE_AUDIO_DEVICE_H_ +#define WEBRTC_TEST_FAKE_AUDIO_DEVICE_H_ + +#include <string> + +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/audio_device/include/fake_audio_device.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class Clock; +class EventTimerWrapper; +class FileWrapper; +class ModuleFileUtility; +class ThreadWrapper; + +namespace test { + +class FakeAudioDevice : public FakeAudioDeviceModule { + public: + FakeAudioDevice(Clock* clock, const std::string& filename); + + virtual ~FakeAudioDevice(); + + int32_t Init() override; + int32_t RegisterAudioCallback(AudioTransport* callback) override; + + bool Playing() const override; + int32_t PlayoutDelay(uint16_t* delay_ms) const override; + bool Recording() const override; + + void Start(); + void Stop(); + + private: + static bool Run(void* obj); + void CaptureAudio(); + + static const uint32_t kFrequencyHz = 16000; + static const size_t kBufferSizeBytes = 2 * kFrequencyHz; + + AudioTransport* audio_callback_; + bool capturing_; + int8_t captured_audio_[kBufferSizeBytes]; + int8_t playout_buffer_[kBufferSizeBytes]; + int64_t last_playout_ms_; + + Clock* clock_; + rtc::scoped_ptr<EventTimerWrapper> tick_; + mutable rtc::CriticalSection lock_; + rtc::scoped_ptr<ThreadWrapper> thread_; + rtc::scoped_ptr<ModuleFileUtility> file_utility_; + rtc::scoped_ptr<FileWrapper> input_stream_; +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_FAKE_AUDIO_DEVICE_H_ diff --git a/webrtc/test/fake_decoder.cc b/webrtc/test/fake_decoder.cc new file mode 100644 index 0000000000..63316e0dab --- /dev/null +++ b/webrtc/test/fake_decoder.cc @@ -0,0 +1,87 @@ +/* + * 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/test/fake_decoder.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace webrtc { +namespace test { + +FakeDecoder::FakeDecoder() : callback_(NULL) {} + +int32_t FakeDecoder::InitDecode(const VideoCodec* config, + int32_t number_of_cores) { + config_ = *config; + size_t width = config->width; + size_t height = config->height; + frame_.CreateEmptyFrame(static_cast<int>(width), + static_cast<int>(height), + static_cast<int>(width), + static_cast<int>((width + 1) / 2), + static_cast<int>((width + 1) / 2)); + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t FakeDecoder::Decode(const EncodedImage& input, + bool missing_frames, + const RTPFragmentationHeader* fragmentation, + const CodecSpecificInfo* codec_specific_info, + int64_t render_time_ms) { + frame_.set_timestamp(input._timeStamp); + frame_.set_ntp_time_ms(input.ntp_time_ms_); + frame_.set_render_time_ms(render_time_ms); + + callback_->Decoded(frame_); + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t FakeDecoder::RegisterDecodeCompleteCallback( + DecodedImageCallback* callback) { + callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t FakeDecoder::Release() { + return WEBRTC_VIDEO_CODEC_OK; +} +int32_t FakeDecoder::Reset() { + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t FakeH264Decoder::Decode(const EncodedImage& input, + bool missing_frames, + const RTPFragmentationHeader* fragmentation, + const CodecSpecificInfo* codec_specific_info, + int64_t render_time_ms) { + uint8_t value = 0; + for (size_t i = 0; i < input._length; ++i) { + uint8_t kStartCode[] = {0, 0, 0, 1}; + if (i < input._length - sizeof(kStartCode) && + !memcmp(&input._buffer[i], kStartCode, sizeof(kStartCode))) { + i += sizeof(kStartCode) + 1; // Skip start code and NAL header. + } + if (input._buffer[i] != value) { + EXPECT_EQ(value, input._buffer[i]) + << "Bitstream mismatch between sender and receiver."; + return -1; + } + ++value; + } + return FakeDecoder::Decode(input, + missing_frames, + fragmentation, + codec_specific_info, + render_time_ms); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/fake_decoder.h b/webrtc/test/fake_decoder.h new file mode 100644 index 0000000000..593af512f8 --- /dev/null +++ b/webrtc/test/fake_decoder.h @@ -0,0 +1,74 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_FAKE_DECODER_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_FAKE_DECODER_H_ + +#include <vector> + +#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h" +#include "webrtc/system_wrappers/include/clock.h" + +namespace webrtc { +namespace test { + +class FakeDecoder : public VideoDecoder { + public: + FakeDecoder(); + virtual ~FakeDecoder() {} + + int32_t InitDecode(const VideoCodec* config, + int32_t number_of_cores) override; + + int32_t Decode(const EncodedImage& input, + bool missing_frames, + const RTPFragmentationHeader* fragmentation, + const CodecSpecificInfo* codec_specific_info, + int64_t render_time_ms) override; + + int32_t RegisterDecodeCompleteCallback( + DecodedImageCallback* callback) override; + + int32_t Release() override; + int32_t Reset() override; + + private: + VideoCodec config_; + VideoFrame frame_; + DecodedImageCallback* callback_; +}; + +class FakeH264Decoder : public FakeDecoder { + public: + virtual ~FakeH264Decoder() {} + + int32_t Decode(const EncodedImage& input, + bool missing_frames, + const RTPFragmentationHeader* fragmentation, + const CodecSpecificInfo* codec_specific_info, + int64_t render_time_ms) override; +}; + +class FakeNullDecoder : public FakeDecoder { + public: + virtual ~FakeNullDecoder() {} + + int32_t Decode(const EncodedImage& input, + bool missing_frames, + const RTPFragmentationHeader* fragmentation, + const CodecSpecificInfo* codec_specific_info, + int64_t render_time_ms) override { + return 0; + } +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_FAKE_DECODER_H_ diff --git a/webrtc/test/fake_encoder.cc b/webrtc/test/fake_encoder.cc new file mode 100644 index 0000000000..a3ade6e97a --- /dev/null +++ b/webrtc/test/fake_encoder.cc @@ -0,0 +1,197 @@ +/* + * 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/test/fake_encoder.h" + +#include "testing/gtest/include/gtest/gtest.h" + +#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h" +#include "webrtc/system_wrappers/include/sleep.h" + +namespace webrtc { +namespace test { + +FakeEncoder::FakeEncoder(Clock* clock) + : clock_(clock), + callback_(NULL), + target_bitrate_kbps_(0), + max_target_bitrate_kbps_(-1), + last_encode_time_ms_(0) { + // Generate some arbitrary not-all-zero data + for (size_t i = 0; i < sizeof(encoded_buffer_); ++i) { + encoded_buffer_[i] = static_cast<uint8_t>(i); + } +} + +FakeEncoder::~FakeEncoder() {} + +void FakeEncoder::SetMaxBitrate(int max_kbps) { + assert(max_kbps >= -1); // max_kbps == -1 disables it. + max_target_bitrate_kbps_ = max_kbps; +} + +int32_t FakeEncoder::InitEncode(const VideoCodec* config, + int32_t number_of_cores, + size_t max_payload_size) { + config_ = *config; + target_bitrate_kbps_ = config_.startBitrate; + return 0; +} + +int32_t FakeEncoder::Encode(const VideoFrame& input_image, + const CodecSpecificInfo* codec_specific_info, + const std::vector<FrameType>* frame_types) { + assert(config_.maxFramerate > 0); + int64_t time_since_last_encode_ms = 1000 / config_.maxFramerate; + int64_t time_now_ms = clock_->TimeInMilliseconds(); + const bool first_encode = last_encode_time_ms_ == 0; + if (!first_encode) { + // For all frames but the first we can estimate the display time by looking + // at the display time of the previous frame. + time_since_last_encode_ms = time_now_ms - last_encode_time_ms_; + } + + size_t bits_available = + static_cast<size_t>(target_bitrate_kbps_ * time_since_last_encode_ms); + size_t min_bits = static_cast<size_t>( + config_.simulcastStream[0].minBitrate * time_since_last_encode_ms); + if (bits_available < min_bits) + bits_available = min_bits; + size_t max_bits = + static_cast<size_t>(max_target_bitrate_kbps_ * time_since_last_encode_ms); + if (max_bits > 0 && max_bits < bits_available) + bits_available = max_bits; + last_encode_time_ms_ = time_now_ms; + + assert(config_.numberOfSimulcastStreams > 0); + for (unsigned char i = 0; i < config_.numberOfSimulcastStreams; ++i) { + CodecSpecificInfo specifics; + memset(&specifics, 0, sizeof(specifics)); + specifics.codecType = kVideoCodecGeneric; + specifics.codecSpecific.generic.simulcast_idx = i; + size_t min_stream_bits = static_cast<size_t>( + config_.simulcastStream[i].minBitrate * time_since_last_encode_ms); + size_t max_stream_bits = static_cast<size_t>( + config_.simulcastStream[i].maxBitrate * time_since_last_encode_ms); + size_t stream_bits = (bits_available > max_stream_bits) ? max_stream_bits : + bits_available; + size_t stream_bytes = (stream_bits + 7) / 8; + if (first_encode) { + // The first frame is a key frame and should be larger. + // TODO(holmer): The FakeEncoder should store the bits_available between + // encodes so that it can compensate for oversized frames. + stream_bytes *= 10; + } + if (stream_bytes > sizeof(encoded_buffer_)) + stream_bytes = sizeof(encoded_buffer_); + + EncodedImage encoded( + encoded_buffer_, stream_bytes, sizeof(encoded_buffer_)); + encoded._timeStamp = input_image.timestamp(); + encoded.capture_time_ms_ = input_image.render_time_ms(); + encoded._frameType = (*frame_types)[i]; + encoded._encodedWidth = config_.simulcastStream[i].width; + encoded._encodedHeight = config_.simulcastStream[i].height; + // Always encode something on the first frame. + if (min_stream_bits > bits_available && i > 0) + continue; + assert(callback_ != NULL); + if (callback_->Encoded(encoded, &specifics, NULL) != 0) + return -1; + bits_available -= std::min(encoded._length * 8, bits_available); + } + return 0; +} + +int32_t FakeEncoder::RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) { + callback_ = callback; + return 0; +} + +int32_t FakeEncoder::Release() { return 0; } + +int32_t FakeEncoder::SetChannelParameters(uint32_t packet_loss, int64_t rtt) { + return 0; +} + +int32_t FakeEncoder::SetRates(uint32_t new_target_bitrate, uint32_t framerate) { + target_bitrate_kbps_ = new_target_bitrate; + return 0; +} + +FakeH264Encoder::FakeH264Encoder(Clock* clock) + : FakeEncoder(clock), callback_(NULL), idr_counter_(0) { + FakeEncoder::RegisterEncodeCompleteCallback(this); +} + +int32_t FakeH264Encoder::RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) { + callback_ = callback; + return 0; +} + +int32_t FakeH264Encoder::Encoded(const EncodedImage& encoded_image, + const CodecSpecificInfo* codec_specific_info, + const RTPFragmentationHeader* fragments) { + const size_t kSpsSize = 8; + const size_t kPpsSize = 11; + const int kIdrFrequency = 10; + RTPFragmentationHeader fragmentation; + if (idr_counter_++ % kIdrFrequency == 0 && + encoded_image._length > kSpsSize + kPpsSize + 1) { + const size_t kNumSlices = 3; + fragmentation.VerifyAndAllocateFragmentationHeader(kNumSlices); + fragmentation.fragmentationOffset[0] = 0; + fragmentation.fragmentationLength[0] = kSpsSize; + fragmentation.fragmentationOffset[1] = kSpsSize; + fragmentation.fragmentationLength[1] = kPpsSize; + fragmentation.fragmentationOffset[2] = kSpsSize + kPpsSize; + fragmentation.fragmentationLength[2] = + encoded_image._length - (kSpsSize + kPpsSize); + const size_t kSpsNalHeader = 0x67; + const size_t kPpsNalHeader = 0x68; + const size_t kIdrNalHeader = 0x65; + encoded_image._buffer[fragmentation.fragmentationOffset[0]] = kSpsNalHeader; + encoded_image._buffer[fragmentation.fragmentationOffset[1]] = kPpsNalHeader; + encoded_image._buffer[fragmentation.fragmentationOffset[2]] = kIdrNalHeader; + } else { + const size_t kNumSlices = 1; + fragmentation.VerifyAndAllocateFragmentationHeader(kNumSlices); + fragmentation.fragmentationOffset[0] = 0; + fragmentation.fragmentationLength[0] = encoded_image._length; + const size_t kNalHeader = 0x41; + encoded_image._buffer[fragmentation.fragmentationOffset[0]] = kNalHeader; + } + uint8_t value = 0; + int fragment_counter = 0; + for (size_t i = 0; i < encoded_image._length; ++i) { + if (fragment_counter == fragmentation.fragmentationVectorSize || + i != fragmentation.fragmentationOffset[fragment_counter]) { + encoded_image._buffer[i] = value++; + } else { + ++fragment_counter; + } + } + return callback_->Encoded(encoded_image, NULL, &fragmentation); +} + +DelayedEncoder::DelayedEncoder(Clock* clock, int delay_ms) + : test::FakeEncoder(clock), + delay_ms_(delay_ms) {} + +int32_t DelayedEncoder::Encode(const VideoFrame& input_image, + const CodecSpecificInfo* codec_specific_info, + const std::vector<FrameType>* frame_types) { + SleepMs(delay_ms_); + return FakeEncoder::Encode(input_image, codec_specific_info, frame_types); +} +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/fake_encoder.h b/webrtc/test/fake_encoder.h new file mode 100644 index 0000000000..d677b92e4d --- /dev/null +++ b/webrtc/test/fake_encoder.h @@ -0,0 +1,85 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_FAKE_ENCODER_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_FAKE_ENCODER_H_ + +#include <vector> + +#include "webrtc/common_types.h" +#include "webrtc/system_wrappers/include/clock.h" +#include "webrtc/video_encoder.h" + +namespace webrtc { +namespace test { + +class FakeEncoder : public VideoEncoder { + public: + explicit FakeEncoder(Clock* clock); + virtual ~FakeEncoder(); + + // Sets max bitrate. Not thread-safe, call before registering the encoder. + void SetMaxBitrate(int max_kbps); + + int32_t InitEncode(const VideoCodec* config, + int32_t number_of_cores, + size_t max_payload_size) override; + int32_t Encode(const VideoFrame& input_image, + const CodecSpecificInfo* codec_specific_info, + const std::vector<FrameType>* frame_types) override; + int32_t RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) override; + int32_t Release() override; + int32_t SetChannelParameters(uint32_t packet_loss, int64_t rtt) override; + int32_t SetRates(uint32_t new_target_bitrate, uint32_t framerate) override; + + protected: + Clock* const clock_; + VideoCodec config_; + EncodedImageCallback* callback_; + int target_bitrate_kbps_; + int max_target_bitrate_kbps_; + int64_t last_encode_time_ms_; + uint8_t encoded_buffer_[100000]; +}; + +class FakeH264Encoder : public FakeEncoder, public EncodedImageCallback { + public: + explicit FakeH264Encoder(Clock* clock); + virtual ~FakeH264Encoder() {} + + int32_t RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) override; + + int32_t Encoded(const EncodedImage& encodedImage, + const CodecSpecificInfo* codecSpecificInfo, + const RTPFragmentationHeader* fragments) override; + + private: + EncodedImageCallback* callback_; + int idr_counter_; +}; + +class DelayedEncoder : public test::FakeEncoder { + public: + DelayedEncoder(Clock* clock, int delay_ms); + virtual ~DelayedEncoder() {} + + int32_t Encode(const VideoFrame& input_image, + const CodecSpecificInfo* codec_specific_info, + const std::vector<FrameType>* frame_types) override; + + private: + const int delay_ms_; +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_FAKE_ENCODER_H_ diff --git a/webrtc/test/fake_network_pipe.cc b/webrtc/test/fake_network_pipe.cc new file mode 100644 index 0000000000..c36059356a --- /dev/null +++ b/webrtc/test/fake_network_pipe.cc @@ -0,0 +1,217 @@ +/* + * 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 <assert.h> +#include <math.h> +#include <string.h> +#include <algorithm> + +#include "webrtc/call.h" +#include "webrtc/system_wrappers/include/tick_util.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<int>(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(const FakeNetworkPipe::Config& config) + : packet_receiver_(NULL), + config_(config), + dropped_packets_(0), + sent_packets_(0), + total_packet_delay_(0), + next_process_time_(TickTime::MillisecondTimestamp()) { +} + +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 = TickTime::MillisecondTimestamp(); + + // 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<float>(dropped_packets_) / + (sent_packets_ + dropped_packets_); +} + +int FakeNetworkPipe::AverageDelay() { + rtc::CritScope crit(&lock_); + if (sent_packets_ == 0) + return 0; + + return total_packet_delay_ / static_cast<int>(sent_packets_); +} + +void FakeNetworkPipe::Process() { + int64_t time_now = TickTime::MillisecondTimestamp(); + std::queue<NetworkPacket*> 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<int64_t>( + next_process_time_ - TickTime::MillisecondTimestamp(), 0); +} + +} // namespace webrtc diff --git a/webrtc/test/fake_network_pipe.h b/webrtc/test/fake_network_pipe.h new file mode 100644 index 0000000000..74189a594c --- /dev/null +++ b/webrtc/test/fake_network_pipe.h @@ -0,0 +1,99 @@ +/* + * 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_TEST_FAKE_NETWORK_PIPE_H_ +#define WEBRTC_TEST_FAKE_NETWORK_PIPE_H_ + +#include <queue> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/system_wrappers/include/event_wrapper.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class CriticalSectionWrapper; +class NetworkPacket; +class 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: + struct Config { + Config() + : queue_length_packets(0), + queue_delay_ms(0), + delay_standard_deviation_ms(0), + link_capacity_kbps(0), + loss_percent(0) { + } + // Queue length in number of packets. + size_t queue_length_packets; + // Delay in addition to capacity induced delay. + int queue_delay_ms; + // Standard deviation of the extra delay. + int delay_standard_deviation_ms; + // Link capacity in kbps. + int link_capacity_kbps; + // Random packet loss. + int loss_percent; + }; + + explicit FakeNetworkPipe(const FakeNetworkPipe::Config& config); + ~FakeNetworkPipe(); + + // Must not be called in parallel with SendPacket or Process. + void SetReceiver(PacketReceiver* receiver); + + // Sets a new configuration. This won't affect packets already in the pipe. + void SetConfig(const FakeNetworkPipe::Config& config); + + // Sends a new packet to the link. + void SendPacket(const uint8_t* packet, size_t packet_length); + + // Processes the network queues and trigger PacketReceiver::IncomingPacket for + // packets ready to be delivered. + void Process(); + int64_t TimeUntilNextProcess() const; + + // Get statistics. + float PercentageLoss(); + int AverageDelay(); + size_t dropped_packets() { return dropped_packets_; } + size_t sent_packets() { return sent_packets_; } + + private: + mutable rtc::CriticalSection lock_; + PacketReceiver* packet_receiver_; + std::queue<NetworkPacket*> capacity_link_; + std::queue<NetworkPacket*> delay_link_; + + // Link configuration. + Config config_; + + // Statistics. + size_t dropped_packets_; + size_t sent_packets_; + int total_packet_delay_; + + int64_t next_process_time_; + + RTC_DISALLOW_COPY_AND_ASSIGN(FakeNetworkPipe); +}; + +} // namespace webrtc + +#endif // WEBRTC_TEST_FAKE_NETWORK_PIPE_H_ diff --git a/webrtc/test/fake_network_pipe_unittest.cc b/webrtc/test/fake_network_pipe_unittest.cc new file mode 100644 index 0000000000..02438c59f3 --- /dev/null +++ b/webrtc/test/fake_network_pipe_unittest.cc @@ -0,0 +1,301 @@ +/* + * 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 "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/call.h" +#include "webrtc/system_wrappers/include/tick_util.h" +#include "webrtc/test/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(const uint8_t* data, size_t length) { + DeliverPacket(MediaType::ANY, data, length, PacketTime()); + delete [] data; + } + + MOCK_METHOD4( + DeliverPacket, + DeliveryStatus(MediaType, const uint8_t*, size_t, const PacketTime&)); +}; + +class FakeNetworkPipeTest : public ::testing::Test { + protected: + virtual void SetUp() { + TickTime::UseFakeClock(12345); + receiver_.reset(new MockReceiver()); + ON_CALL(*receiver_, DeliverPacket(_, _, _, _)) + .WillByDefault(Return(PacketReceiver::DELIVERY_OK)); + } + + virtual void TearDown() { + } + + void SendPackets(FakeNetworkPipe* pipe, int number_packets, int kPacketSize) { + rtc::scoped_ptr<uint8_t[]> 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) const { + return 8 * kPacketSize / capacity_kbps; + } + + rtc::scoped_ptr<MockReceiver> 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) { + FakeNetworkPipe::Config config; + config.queue_length_packets = 20; + config.link_capacity_kbps = 80; + rtc::scoped_ptr<FakeNetworkPipe> pipe(new FakeNetworkPipe(config)); + pipe->SetReceiver(receiver_.get()); + + // 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(config.link_capacity_kbps, + kPacketSize); + + // Time haven't increased yet, so we souldn't get any packets. + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(0); + pipe->Process(); + + // Advance enough time to release one packet. + TickTime::AdvanceFakeClock(kPacketTimeMs); + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(1); + pipe->Process(); + + // Release all but one packet + TickTime::AdvanceFakeClock(9 * kPacketTimeMs - 1); + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(8); + pipe->Process(); + + // And the last one. + TickTime::AdvanceFakeClock(1); + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(1); + pipe->Process(); +} + +// Test the extra network delay. +TEST_F(FakeNetworkPipeTest, ExtraDelayTest) { + FakeNetworkPipe::Config config; + config.queue_length_packets = 20; + config.queue_delay_ms = 100; + config.link_capacity_kbps = 80; + rtc::scoped_ptr<FakeNetworkPipe> pipe(new FakeNetworkPipe(config)); + pipe->SetReceiver(receiver_.get()); + + 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(config.link_capacity_kbps, + kPacketSize); + + // Increase more than kPacketTimeMs, but not more than the extra delay. + TickTime::AdvanceFakeClock(kPacketTimeMs); + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(0); + pipe->Process(); + + // Advance the network delay to get the first packet. + TickTime::AdvanceFakeClock(config.queue_delay_ms); + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(1); + pipe->Process(); + + // Advance one more kPacketTimeMs to get the last packet. + TickTime::AdvanceFakeClock(kPacketTimeMs); + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(1); + pipe->Process(); +} + +// Test the number of buffers and packets are dropped when sending too many +// packets too quickly. +TEST_F(FakeNetworkPipeTest, QueueLengthTest) { + FakeNetworkPipe::Config config; + config.queue_length_packets = 2; + config.link_capacity_kbps = 80; + rtc::scoped_ptr<FakeNetworkPipe> pipe(new FakeNetworkPipe(config)); + pipe->SetReceiver(receiver_.get()); + + const int kPacketSize = 1000; + const int kPacketTimeMs = PacketTimeMs(config.link_capacity_kbps, + 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_, DeliverPacket(_, _, _, _)).Times(2); + pipe->Process(); +} + +// Test we get statistics as expected. +TEST_F(FakeNetworkPipeTest, StatisticsTest) { + FakeNetworkPipe::Config config; + config.queue_length_packets = 2; + config.queue_delay_ms = 20; + config.link_capacity_kbps = 80; + rtc::scoped_ptr<FakeNetworkPipe> pipe(new FakeNetworkPipe(config)); + pipe->SetReceiver(receiver_.get()); + + const int kPacketSize = 1000; + const int kPacketTimeMs = PacketTimeMs(config.link_capacity_kbps, + kPacketSize); + + // Send three packets and verify only 2 are delivered. + SendPackets(pipe.get(), 3, kPacketSize); + TickTime::AdvanceFakeClock(3 * kPacketTimeMs + config.queue_delay_ms); + + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(2); + pipe->Process(); + + // Packet 1: kPacketTimeMs + config.queue_delay_ms, + // packet 2: 2 * kPacketTimeMs + config.queue_delay_ms => 170 ms average. + EXPECT_EQ(pipe->AverageDelay(), 170); + EXPECT_EQ(pipe->sent_packets(), 2u); + EXPECT_EQ(pipe->dropped_packets(), 1u); + EXPECT_EQ(pipe->PercentageLoss(), 1/3.f); +} + +// Change the link capacity half-way through the test and verify that the +// delivery times change accordingly. +TEST_F(FakeNetworkPipeTest, ChangingCapacityWithEmptyPipeTest) { + FakeNetworkPipe::Config config; + config.queue_length_packets = 20; + config.link_capacity_kbps = 80; + rtc::scoped_ptr<FakeNetworkPipe> pipe(new FakeNetworkPipe(config)); + pipe->SetReceiver(receiver_.get()); + + // 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. + int packet_time_ms = PacketTimeMs(config.link_capacity_kbps, kPacketSize); + + // Time hasn't increased yet, so we souldn't get any packets. + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(0); + pipe->Process(); + + // Advance time in steps to release one packet at a time. + for (int i = 0; i < kNumPackets; ++i) { + TickTime::AdvanceFakeClock(packet_time_ms); + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(1); + pipe->Process(); + } + + // Change the capacity. + config.link_capacity_kbps /= 2; // Reduce to 50%. + pipe->SetConfig(config); + + // Add another 10 packets of 1000 bytes, = 80 kb, and verify it takes two + // seconds to get them through the pipe. + SendPackets(pipe.get(), kNumPackets, kPacketSize); + + // Time to get one packet through the link. + packet_time_ms = PacketTimeMs(config.link_capacity_kbps, kPacketSize); + + // Time hasn't increased yet, so we souldn't get any packets. + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(0); + pipe->Process(); + + // Advance time in steps to release one packet at a time. + for (int i = 0; i < kNumPackets; ++i) { + TickTime::AdvanceFakeClock(packet_time_ms); + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(1); + pipe->Process(); + } + + // Check that all the packets were sent. + EXPECT_EQ(static_cast<size_t>(2 * kNumPackets), pipe->sent_packets()); + TickTime::AdvanceFakeClock(pipe->TimeUntilNextProcess()); + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(0); + pipe->Process(); +} + +// Change the link capacity half-way through the test and verify that the +// delivery times change accordingly. +TEST_F(FakeNetworkPipeTest, ChangingCapacityWithPacketsInPipeTest) { + FakeNetworkPipe::Config config; + config.queue_length_packets = 20; + config.link_capacity_kbps = 80; + rtc::scoped_ptr<FakeNetworkPipe> pipe(new FakeNetworkPipe(config)); + pipe->SetReceiver(receiver_.get()); + + // Add 10 packets of 1000 bytes, = 80 kb. + const int kNumPackets = 10; + const int kPacketSize = 1000; + SendPackets(pipe.get(), kNumPackets, kPacketSize); + + // Time to get one packet through the link at the initial speed. + int packet_time_1_ms = PacketTimeMs(config.link_capacity_kbps, kPacketSize); + + // Change the capacity. + config.link_capacity_kbps *= 2; // Double the capacity. + pipe->SetConfig(config); + + // Add another 10 packets of 1000 bytes, = 80 kb, and verify it takes two + // seconds to get them through the pipe. + SendPackets(pipe.get(), kNumPackets, kPacketSize); + + // Time to get one packet through the link at the new capacity. + int packet_time_2_ms = PacketTimeMs(config.link_capacity_kbps, kPacketSize); + + // Time hasn't increased yet, so we souldn't get any packets. + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(0); + pipe->Process(); + + // Advance time in steps to release one packet at a time. + for (int i = 0; i < kNumPackets; ++i) { + TickTime::AdvanceFakeClock(packet_time_1_ms); + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(1); + pipe->Process(); + } + + // Advance time in steps to release one packet at a time. + for (int i = 0; i < kNumPackets; ++i) { + TickTime::AdvanceFakeClock(packet_time_2_ms); + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(1); + pipe->Process(); + } + + // Check that all the packets were sent. + EXPECT_EQ(static_cast<size_t>(2 * kNumPackets), pipe->sent_packets()); + TickTime::AdvanceFakeClock(pipe->TimeUntilNextProcess()); + EXPECT_CALL(*receiver_, DeliverPacket(_, _, _, _)).Times(0); + pipe->Process(); +} +} // namespace webrtc diff --git a/webrtc/test/fake_texture_frame.h b/webrtc/test/fake_texture_frame.h new file mode 100644 index 0000000000..dc6abaf745 --- /dev/null +++ b/webrtc/test/fake_texture_frame.h @@ -0,0 +1,56 @@ +/* + * 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_TEST_FAKE_TEXTURE_FRAME_H_ +#define WEBRTC_TEST_FAKE_TEXTURE_FRAME_H_ + +#include "webrtc/base/checks.h" +#include "webrtc/common_video/interface/video_frame_buffer.h" +#include "webrtc/video_frame.h" + +namespace webrtc { +namespace test { + +class FakeNativeHandle {}; + +class FakeNativeHandleBuffer : public NativeHandleBuffer { + public: + FakeNativeHandleBuffer(void* native_handle, int width, int height) + : NativeHandleBuffer(native_handle, width, height) {} + + ~FakeNativeHandleBuffer() { + delete reinterpret_cast<FakeNativeHandle*>(native_handle_); + } + + private: + rtc::scoped_refptr<VideoFrameBuffer> NativeToI420Buffer() override { + rtc::scoped_refptr<VideoFrameBuffer> buffer( + new rtc::RefCountedObject<I420Buffer>(width_, height_)); + int half_height = (height_ + 1) / 2; + int half_width = (width_ + 1) / 2; + memset(buffer->MutableData(kYPlane), 0, height_ * width_); + memset(buffer->MutableData(kUPlane), 0, half_height * half_width); + memset(buffer->MutableData(kVPlane), 0, half_height * half_width); + return buffer; + } +}; + +static VideoFrame CreateFakeNativeHandleFrame(FakeNativeHandle* native_handle, + int width, + int height, + uint32_t timestamp, + int64_t render_time_ms, + VideoRotation rotation) { + return VideoFrame(new rtc::RefCountedObject<FakeNativeHandleBuffer>( + native_handle, width, height), + timestamp, render_time_ms, rotation); +} +} // namespace test +} // namespace webrtc +#endif // WEBRTC_TEST_FAKE_TEXTURE_FRAME_H_ diff --git a/webrtc/test/fake_voice_engine.cc b/webrtc/test/fake_voice_engine.cc new file mode 100644 index 0000000000..1a32e082b7 --- /dev/null +++ b/webrtc/test/fake_voice_engine.cc @@ -0,0 +1,70 @@ +/* + * 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/test/fake_voice_engine.h" + +namespace { + +webrtc::AudioDecodingCallStats MakeAudioDecodingCallStats() { + webrtc::AudioDecodingCallStats stats; + stats.calls_to_silence_generator = 234; + stats.calls_to_neteq = 567; + stats.decoded_normal = 890; + stats.decoded_plc = 123; + stats.decoded_cng = 456; + stats.decoded_plc_cng = 789; + return stats; +} +} // namespace + +namespace webrtc { +namespace test { + +const int FakeVoiceEngine::kSendChannelId = 1; +const int FakeVoiceEngine::kRecvChannelId = 2; +const uint32_t FakeVoiceEngine::kSendSsrc = 665; +const uint32_t FakeVoiceEngine::kRecvSsrc = 667; +const int FakeVoiceEngine::kSendEchoDelayMedian = 254; +const int FakeVoiceEngine::kSendEchoDelayStdDev = -3; +const int FakeVoiceEngine::kSendEchoReturnLoss = -65; +const int FakeVoiceEngine::kSendEchoReturnLossEnhancement = 101; +const int FakeVoiceEngine::kRecvJitterBufferDelay = -7; +const int FakeVoiceEngine::kRecvPlayoutBufferDelay = 302; +const unsigned int FakeVoiceEngine::kSendSpeechInputLevel = 96; +const unsigned int FakeVoiceEngine::kRecvSpeechOutputLevel = 99; + +const CallStatistics FakeVoiceEngine::kSendCallStats = { + 1345, 1678, 1901, 1234, 112, 13456, 17890, 1567, -1890, -1123 +}; + +const CodecInst FakeVoiceEngine::kSendCodecInst = { + -121, "codec_name_send", 48000, -231, -451, -671 +}; + +const ReportBlock FakeVoiceEngine::kSendReportBlock = { + 456, 780, 123, 567, 890, 132, 143, 13354 +}; + +const CallStatistics FakeVoiceEngine::kRecvCallStats = { + 345, 678, 901, 234, -12, 3456, 7890, 567, 890, 123 +}; + +const CodecInst FakeVoiceEngine::kRecvCodecInst = { + 123, "codec_name_recv", 96000, -187, -198, -103 +}; + +const NetworkStatistics FakeVoiceEngine::kRecvNetworkStats = { + 123, 456, false, 0, 0, 789, 12, 345, 678, 901, -1, -1, -1, -1, -1, 0 +}; + +const AudioDecodingCallStats FakeVoiceEngine::kRecvAudioDecodingCallStats = + MakeAudioDecodingCallStats(); +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/fake_voice_engine.h b/webrtc/test/fake_voice_engine.h new file mode 100644 index 0000000000..8f08929720 --- /dev/null +++ b/webrtc/test/fake_voice_engine.h @@ -0,0 +1,504 @@ +/* + * 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_AUDIO_FAKE_VOICE_ENGINE_H_ +#define WEBRTC_AUDIO_FAKE_VOICE_ENGINE_H_ + +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" + +#include "webrtc/voice_engine/voice_engine_impl.h" + +namespace webrtc { +namespace test { + +// NOTE: This class inherits from VoiceEngineImpl so that its clients will be +// able to get the various interfaces as usual, via T::GetInterface(). +class FakeVoiceEngine final : public VoiceEngineImpl { + public: + static const int kSendChannelId; + static const int kRecvChannelId; + static const uint32_t kSendSsrc; + static const uint32_t kRecvSsrc; + static const int kSendEchoDelayMedian; + static const int kSendEchoDelayStdDev; + static const int kSendEchoReturnLoss; + static const int kSendEchoReturnLossEnhancement; + static const int kRecvJitterBufferDelay; + static const int kRecvPlayoutBufferDelay; + static const unsigned int kSendSpeechInputLevel; + static const unsigned int kRecvSpeechOutputLevel; + static const CallStatistics kSendCallStats; + static const CodecInst kSendCodecInst; + static const ReportBlock kSendReportBlock; + static const CallStatistics kRecvCallStats; + static const CodecInst kRecvCodecInst; + static const NetworkStatistics kRecvNetworkStats; + static const AudioDecodingCallStats kRecvAudioDecodingCallStats; + + FakeVoiceEngine() : VoiceEngineImpl(new Config(), true) { + // Increase ref count so this object isn't automatically deleted whenever + // interfaces are Release():d. + ++_ref_count; + } + ~FakeVoiceEngine() override { + // Decrease ref count before base class d-tor is called; otherwise it will + // trigger an assertion. + --_ref_count; + } + + // VoEAudioProcessing + int SetNsStatus(bool enable, NsModes mode = kNsUnchanged) override { + return -1; + } + int GetNsStatus(bool& enabled, NsModes& mode) override { return -1; } + int SetAgcStatus(bool enable, AgcModes mode = kAgcUnchanged) override { + return -1; + } + int GetAgcStatus(bool& enabled, AgcModes& mode) override { return -1; } + int SetAgcConfig(AgcConfig config) override { return -1; } + int GetAgcConfig(AgcConfig& config) override { return -1; } + int SetEcStatus(bool enable, EcModes mode = kEcUnchanged) override { + return -1; + } + int GetEcStatus(bool& enabled, EcModes& mode) override { return -1; } + int EnableDriftCompensation(bool enable) override { return -1; } + bool DriftCompensationEnabled() override { return false; } + void SetDelayOffsetMs(int offset) override {} + int DelayOffsetMs() override { return -1; } + int SetAecmMode(AecmModes mode = kAecmSpeakerphone, + bool enableCNG = true) override { return -1; } + int GetAecmMode(AecmModes& mode, bool& enabledCNG) override { return -1; } + int EnableHighPassFilter(bool enable) override { return -1; } + bool IsHighPassFilterEnabled() override { return false; } + int SetRxNsStatus(int channel, + bool enable, + NsModes mode = kNsUnchanged) override { return -1; } + int GetRxNsStatus(int channel, bool& enabled, NsModes& mode) override { + return -1; + } + int SetRxAgcStatus(int channel, + bool enable, + AgcModes mode = kAgcUnchanged) override { return -1; } + int GetRxAgcStatus(int channel, bool& enabled, AgcModes& mode) override { + return -1; + } + int SetRxAgcConfig(int channel, AgcConfig config) override { return -1; } + int GetRxAgcConfig(int channel, AgcConfig& config) override { return -1; } + int RegisterRxVadObserver(int channel, + VoERxVadCallback& observer) override { return -1; } + int DeRegisterRxVadObserver(int channel) override { return -1; } + int VoiceActivityIndicator(int channel) override { return -1; } + int SetEcMetricsStatus(bool enable) override { return -1; } + int GetEcMetricsStatus(bool& enabled) override { + enabled = true; + return 0; + } + int GetEchoMetrics(int& ERL, int& ERLE, int& RERL, int& A_NLP) override { + ERL = kSendEchoReturnLoss; + ERLE = kSendEchoReturnLossEnhancement; + RERL = -123456789; + A_NLP = 123456789; + return 0; + } + int GetEcDelayMetrics(int& delay_median, + int& delay_std, + float& fraction_poor_delays) override { + delay_median = kSendEchoDelayMedian; + delay_std = kSendEchoDelayStdDev; + fraction_poor_delays = -12345.7890f; + return 0; + } + int StartDebugRecording(const char* fileNameUTF8) override { return -1; } + int StartDebugRecording(FILE* file_handle) override { return -1; } + int StopDebugRecording() override { return -1; } + int SetTypingDetectionStatus(bool enable) override { return -1; } + int GetTypingDetectionStatus(bool& enabled) override { return -1; } + int TimeSinceLastTyping(int& seconds) override { return -1; } + int SetTypingDetectionParameters(int timeWindow, + int costPerTyping, + int reportingThreshold, + int penaltyDecay, + int typeEventDelay = 0) override { + return -1; + } + void EnableStereoChannelSwapping(bool enable) override {} + bool IsStereoChannelSwappingEnabled() override { return false; } + + // VoEBase + int RegisterVoiceEngineObserver(VoiceEngineObserver& observer) override { + return -1; + } + int DeRegisterVoiceEngineObserver() override { return -1; } + int Init(AudioDeviceModule* external_adm = NULL, + AudioProcessing* audioproc = NULL) override { return -1; } + AudioProcessing* audio_processing() override { return nullptr; } + int Terminate() override { return -1; } + int CreateChannel() override { return -1; } + int CreateChannel(const Config& config) override { return -1; } + int DeleteChannel(int channel) override { return -1; } + int StartReceive(int channel) override { return -1; } + int StopReceive(int channel) override { return -1; } + int StartPlayout(int channel) override { return -1; } + int StopPlayout(int channel) override { return -1; } + int StartSend(int channel) override { return -1; } + int StopSend(int channel) override { return -1; } + int GetVersion(char version[1024]) override { return -1; } + int LastError() override { return -1; } + AudioTransport* audio_transport() { return nullptr; } + int AssociateSendChannel(int channel, int accociate_send_channel) override { + return -1; + } + + // VoECodec + int NumOfCodecs() override { return -1; } + int GetCodec(int index, CodecInst& codec) override { return -1; } + int SetSendCodec(int channel, const CodecInst& codec) override { return -1; } + int GetSendCodec(int channel, CodecInst& codec) override { + EXPECT_EQ(channel, kSendChannelId); + codec = kSendCodecInst; + return 0; + } + int SetBitRate(int channel, int bitrate_bps) override { return -1; } + int GetRecCodec(int channel, CodecInst& codec) override { + EXPECT_EQ(channel, kRecvChannelId); + codec = kRecvCodecInst; + return 0; + } + int SetRecPayloadType(int channel, const CodecInst& codec) override { + return -1; + } + int GetRecPayloadType(int channel, CodecInst& codec) override { return -1; } + int SetSendCNPayloadType(int channel, int type, + PayloadFrequencies frequency = kFreq16000Hz) override { return -1; } + int SetVADStatus(int channel, + bool enable, + VadModes mode = kVadConventional, + bool disableDTX = false) override { return -1; } + int GetVADStatus(int channel, + bool& enabled, + VadModes& mode, + bool& disabledDTX) override { return -1; } + int SetOpusMaxPlaybackRate(int channel, int frequency_hz) override { + return -1; + } + int SetOpusDtx(int channel, bool enable_dtx) override { return -1; } + RtcEventLog* GetEventLog() override { return nullptr; } + + // VoEDtmf + int SendTelephoneEvent(int channel, + int eventCode, + bool outOfBand = true, + int lengthMs = 160, + int attenuationDb = 10) override { return -1; } + int SetSendTelephoneEventPayloadType(int channel, + unsigned char type) override { + return -1; + } + int GetSendTelephoneEventPayloadType(int channel, + unsigned char& type) override { + return -1; + } + int SetDtmfFeedbackStatus(bool enable, + bool directFeedback = false) override { return -1; } + int GetDtmfFeedbackStatus(bool& enabled, bool& directFeedback) override { + return -1; + } + int PlayDtmfTone(int eventCode, + int lengthMs = 200, + int attenuationDb = 10) override { return -1; } + + // VoEExternalMedia + int RegisterExternalMediaProcessing( + int channel, + ProcessingTypes type, + VoEMediaProcess& processObject) override { return -1; } + int DeRegisterExternalMediaProcessing(int channel, + ProcessingTypes type) override { + return -1; + } + int GetAudioFrame(int channel, + int desired_sample_rate_hz, + AudioFrame* frame) override { return -1; } + int SetExternalMixing(int channel, bool enable) override { return -1; } + + // VoEFile + int StartPlayingFileLocally( + int channel, + const char fileNameUTF8[1024], + bool loop = false, + FileFormats format = kFileFormatPcm16kHzFile, + float volumeScaling = 1.0, + int startPointMs = 0, + int stopPointMs = 0) override { return -1; } + int StartPlayingFileLocally( + int channel, + InStream* stream, + FileFormats format = kFileFormatPcm16kHzFile, + float volumeScaling = 1.0, + int startPointMs = 0, + int stopPointMs = 0) override { return -1; } + int StopPlayingFileLocally(int channel) override { return -1; } + int IsPlayingFileLocally(int channel) override { return -1; } + int StartPlayingFileAsMicrophone( + int channel, + const char fileNameUTF8[1024], + bool loop = false, + bool mixWithMicrophone = false, + FileFormats format = kFileFormatPcm16kHzFile, + float volumeScaling = 1.0) override { return -1; } + int StartPlayingFileAsMicrophone( + int channel, + InStream* stream, + bool mixWithMicrophone = false, + FileFormats format = kFileFormatPcm16kHzFile, + float volumeScaling = 1.0) override { return -1; } + int StopPlayingFileAsMicrophone(int channel) override { return -1; } + int IsPlayingFileAsMicrophone(int channel) override { return -1; } + int StartRecordingPlayout(int channel, + const char* fileNameUTF8, + CodecInst* compression = NULL, + int maxSizeBytes = -1) override { return -1; } + int StopRecordingPlayout(int channel) override { return -1; } + int StartRecordingPlayout(int channel, + OutStream* stream, + CodecInst* compression = NULL) override { + return -1; + } + int StartRecordingMicrophone(const char* fileNameUTF8, + CodecInst* compression = NULL, + int maxSizeBytes = -1) override { return -1; } + int StartRecordingMicrophone(OutStream* stream, + CodecInst* compression = NULL) override { + return -1; + } + int StopRecordingMicrophone() override { return -1; } + + // VoEHardware + int GetNumOfRecordingDevices(int& devices) override { return -1; } + + // Gets the number of audio devices available for playout. + int GetNumOfPlayoutDevices(int& devices) override { return -1; } + + // Gets the name of a specific recording device given by an |index|. + // On Windows Vista/7, it also retrieves an additional unique ID + // (GUID) for the recording device. + int GetRecordingDeviceName(int index, + char strNameUTF8[128], + char strGuidUTF8[128]) override { return -1; } + + // Gets the name of a specific playout device given by an |index|. + // On Windows Vista/7, it also retrieves an additional unique ID + // (GUID) for the playout device. + int GetPlayoutDeviceName(int index, + char strNameUTF8[128], + char strGuidUTF8[128]) override { return -1; } + + // Sets the audio device used for recording. + int SetRecordingDevice( + int index, + StereoChannel recordingChannel = kStereoBoth) override { return -1; } + + // Sets the audio device used for playout. + int SetPlayoutDevice(int index) override { return -1; } + + // Sets the type of audio device layer to use. + int SetAudioDeviceLayer(AudioLayers audioLayer) override { return -1; } + + // Gets the currently used (active) audio device layer. + int GetAudioDeviceLayer(AudioLayers& audioLayer) override { return -1; } + + // Native sample rate controls (samples/sec) + int SetRecordingSampleRate(unsigned int samples_per_sec) override { + return -1; + } + int RecordingSampleRate(unsigned int* samples_per_sec) const override { + return -1; + } + int SetPlayoutSampleRate(unsigned int samples_per_sec) override { + return -1; + } + int PlayoutSampleRate(unsigned int* samples_per_sec) const override { + return -1; + } + + // Queries and controls platform audio effects on Android devices. + bool BuiltInAECIsAvailable() const override { return false; } + int EnableBuiltInAEC(bool enable) override { return -1; } + bool BuiltInAGCIsAvailable() const override { return false; } + int EnableBuiltInAGC(bool enable) override { return -1; } + bool BuiltInNSIsAvailable() const override { return false; } + int EnableBuiltInNS(bool enable) override { return -1; } + + // VoENetwork + int RegisterExternalTransport(int channel, Transport& transport) override { + return -1; + } + int DeRegisterExternalTransport(int channel) override { return -1; } + int ReceivedRTPPacket(int channel, + const void* data, + size_t length) override { return -1; } + int ReceivedRTPPacket(int channel, + const void* data, + size_t length, + const PacketTime& packet_time) override { return -1; } + int ReceivedRTCPPacket(int channel, + const void* data, + size_t length) { return -1; } + + // VoENetEqStats + int GetNetworkStatistics(int channel, NetworkStatistics& stats) override { + EXPECT_EQ(channel, kRecvChannelId); + stats = kRecvNetworkStats; + return 0; + } + int GetDecodingCallStatistics(int channel, + AudioDecodingCallStats* stats) const override { + EXPECT_EQ(channel, kRecvChannelId); + EXPECT_NE(nullptr, stats); + *stats = kRecvAudioDecodingCallStats; + return 0; + } + + // VoERTP_RTCP + int SetLocalSSRC(int channel, unsigned int ssrc) override { return -1; } + int GetLocalSSRC(int channel, unsigned int& ssrc) override { + EXPECT_EQ(channel, kSendChannelId); + ssrc = 0; + return 0; + } + int GetRemoteSSRC(int channel, unsigned int& ssrc) override { + EXPECT_EQ(channel, kRecvChannelId); + ssrc = 0; + return 0; + } + int SetSendAudioLevelIndicationStatus(int channel, + bool enable, + unsigned char id = 1) override { + return -1; + } + int SetSendAbsoluteSenderTimeStatus(int channel, + bool enable, + unsigned char id) override { return -1; } + int SetReceiveAbsoluteSenderTimeStatus(int channel, + bool enable, + unsigned char id) override { + return -1; + } + int SetRTCPStatus(int channel, bool enable) override { return -1; } + int GetRTCPStatus(int channel, bool& enabled) override { return -1; } + int SetRTCP_CNAME(int channel, const char cName[256]) override { return -1; } + int GetRTCP_CNAME(int channel, char cName[256]) { return -1; } + int GetRemoteRTCP_CNAME(int channel, char cName[256]) override { return -1; } + int GetRemoteRTCPData(int channel, + unsigned int& NTPHigh, + unsigned int& NTPLow, + unsigned int& timestamp, + unsigned int& playoutTimestamp, + unsigned int* jitter = NULL, + unsigned short* fractionLost = NULL) override { + return -1; + } + int GetRTPStatistics(int channel, + unsigned int& averageJitterMs, + unsigned int& maxJitterMs, + unsigned int& discardedPackets) override { return -1; } + int GetRTCPStatistics(int channel, CallStatistics& stats) override { + if (channel == kSendChannelId) { + stats = kSendCallStats; + } else { + EXPECT_EQ(channel, kRecvChannelId); + stats = kRecvCallStats; + } + return 0; + } + int GetRemoteRTCPReportBlocks( + int channel, + std::vector<ReportBlock>* receive_blocks) override { + EXPECT_EQ(channel, kSendChannelId); + EXPECT_NE(receive_blocks, nullptr); + EXPECT_EQ(receive_blocks->size(), 0u); + webrtc::ReportBlock block = kSendReportBlock; + receive_blocks->push_back(block); // Has wrong SSRC. + block.source_SSRC = kSendSsrc; + receive_blocks->push_back(block); // Correct block. + block.fraction_lost = 0; + receive_blocks->push_back(block); // Duplicate SSRC, bad fraction_lost. + return 0; + } + int SetNACKStatus(int channel, bool enable, int maxNoPackets) override { + return -1; + } + + // VoEVideoSync + int GetPlayoutBufferSize(int& buffer_ms) override { return -1; } + int SetMinimumPlayoutDelay(int channel, int delay_ms) override { return -1; } + int SetInitialPlayoutDelay(int channel, int delay_ms) override { return -1; } + int GetDelayEstimate(int channel, + int* jitter_buffer_delay_ms, + int* playout_buffer_delay_ms) override { + EXPECT_EQ(channel, kRecvChannelId); + *jitter_buffer_delay_ms = kRecvJitterBufferDelay; + *playout_buffer_delay_ms = kRecvPlayoutBufferDelay; + return 0; + } + int GetLeastRequiredDelayMs(int channel) const override { return -1; } + int SetInitTimestamp(int channel, unsigned int timestamp) override { + return -1; + } + int SetInitSequenceNumber(int channel, short sequenceNumber) override { + return -1; + } + int GetPlayoutTimestamp(int channel, unsigned int& timestamp) override { + return -1; + } + int GetRtpRtcp(int channel, + RtpRtcp** rtpRtcpModule, + RtpReceiver** rtp_receiver) override { return -1; } + + // VoEVolumeControl + int SetSpeakerVolume(unsigned int volume) override { return -1; } + int GetSpeakerVolume(unsigned int& volume) override { return -1; } + int SetMicVolume(unsigned int volume) override { return -1; } + int GetMicVolume(unsigned int& volume) override { return -1; } + int SetInputMute(int channel, bool enable) override { return -1; } + int GetInputMute(int channel, bool& enabled) override { return -1; } + int GetSpeechInputLevel(unsigned int& level) override { return -1; } + int GetSpeechOutputLevel(int channel, unsigned int& level) override { + return -1; + } + int GetSpeechInputLevelFullRange(unsigned int& level) override { + level = kSendSpeechInputLevel; + return 0; + } + int GetSpeechOutputLevelFullRange(int channel, + unsigned int& level) override { + EXPECT_EQ(channel, kRecvChannelId); + level = kRecvSpeechOutputLevel; + return 0; + } + int SetChannelOutputVolumeScaling(int channel, float scaling) override { + return -1; + } + int GetChannelOutputVolumeScaling(int channel, float& scaling) override { + return -1; + } + int SetOutputVolumePan(int channel, float left, float right) override { + return -1; + } + int GetOutputVolumePan(int channel, float& left, float& right) override { + return -1; + } +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_AUDIO_FAKE_VOICE_ENGINE_H_ diff --git a/webrtc/test/field_trial.cc b/webrtc/test/field_trial.cc new file mode 100644 index 0000000000..613fb67679 --- /dev/null +++ b/webrtc/test/field_trial.cc @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2014 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/field_trial.h" + +#include <algorithm> +#include <cassert> +#include <cstdio> +#include <cstdlib> +#include <map> +#include <string> + +#include "webrtc/system_wrappers/include/field_trial.h" + +namespace webrtc { +namespace { +// Clients of this library have show a clear intent to setup field trials by +// linking with it. As so try to crash if they forget to call +// InitFieldTrialsFromString before webrtc tries to access a field trial. +bool field_trials_initiated_ = false; +std::map<std::string, std::string> field_trials_; +} // namespace + +namespace field_trial { +std::string FindFullName(const std::string& trial_name) { + assert(field_trials_initiated_); + std::map<std::string, std::string>::const_iterator it = + field_trials_.find(trial_name); + if (it == field_trials_.end()) + return std::string(); + return it->second; +} +} // namespace field_trial + +namespace test { +// Note: this code is copied from src/base/metrics/field_trial.cc since the aim +// is to mimic chromium --force-fieldtrials. +void InitFieldTrialsFromString(const std::string& trials_string) { + static const char kPersistentStringSeparator = '/'; + + // Catch an error if this is called more than once. + assert(!field_trials_initiated_); + field_trials_initiated_ = true; + + if (trials_string.empty()) + return; + + size_t next_item = 0; + while (next_item < trials_string.length()) { + size_t name_end = trials_string.find(kPersistentStringSeparator, next_item); + if (name_end == trials_string.npos || next_item == name_end) + break; + size_t group_name_end = trials_string.find(kPersistentStringSeparator, + name_end + 1); + if (group_name_end == trials_string.npos || name_end + 1 == group_name_end) + break; + std::string name(trials_string, next_item, name_end - next_item); + std::string group_name(trials_string, name_end + 1, + group_name_end - name_end - 1); + next_item = group_name_end + 1; + + // Fail if duplicate with different group name. + if (field_trials_.find(name) != field_trials_.end() && + field_trials_.find(name)->second != group_name) + break; + + field_trials_[name] = group_name; + + // Successfully parsed all field trials from the string. + if (next_item == trials_string.length()) + return; + } + // Using fprintf as LOG does not print when this is called early in main. + fprintf(stderr, "Invalid field trials string.\n"); + + // Using abort so it crashes in both debug and release mode. + abort(); +} + +ScopedFieldTrials::ScopedFieldTrials(const std::string& config) + : previous_field_trials_(field_trials_) { + assert(field_trials_initiated_); + field_trials_initiated_ = false; + field_trials_.clear(); + InitFieldTrialsFromString(config); +} + +ScopedFieldTrials::~ScopedFieldTrials() { + // Should still be initialized, since InitFieldTrials is called from ctor. + // That's why we don't restore the flag. + assert(field_trials_initiated_); + field_trials_ = previous_field_trials_; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/field_trial.h b/webrtc/test/field_trial.h new file mode 100644 index 0000000000..d448f3411d --- /dev/null +++ b/webrtc/test/field_trial.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2014 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_TEST_FIELD_TRIAL_H_ +#define WEBRTC_TEST_FIELD_TRIAL_H_ + +#include <string> +#include <map> + +namespace webrtc { +namespace test { + +// Parses enabled field trials from a string config, such as the one passed +// to chrome's argument --force-fieldtrials and initializes webrtc::field_trial +// with such a config. +// E.g.: +// "WebRTC-experimentFoo/Enabled/WebRTC-experimentBar/Enabled100kbps/" +// Assigns the process to group "Enabled" on WebRTCExperimentFoo trial +// and to group "Enabled100kbps" on WebRTCExperimentBar. +// +// E.g. invalid config: +// "WebRTC-experiment1/Enabled" (note missing / separator at the end). +// +// Note: This method crashes with an error message if an invalid config is +// passed to it. That can be used to find out if a binary is parsing the flags. +void InitFieldTrialsFromString(const std::string& config); + +// This class is used to override field-trial configs within specific tests. +// After this class goes out of scope previous field trials will be restored. +class ScopedFieldTrials { + public: + explicit ScopedFieldTrials(const std::string& config); + ~ScopedFieldTrials(); + private: + const std::map<std::string, std::string> previous_field_trials_; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_FIELD_TRIAL_H_ diff --git a/webrtc/test/frame_generator.cc b/webrtc/test/frame_generator.cc new file mode 100644 index 0000000000..589dde4bad --- /dev/null +++ b/webrtc/test/frame_generator.cc @@ -0,0 +1,280 @@ +/* + * 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/test/frame_generator.h" + +#include <math.h> +#include <stdio.h> +#include <string.h> + +#include "webrtc/base/checks.h" +#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" +#include "webrtc/system_wrappers/include/clock.h" + +namespace webrtc { +namespace test { +namespace { + +class ChromaGenerator : public FrameGenerator { + public: + ChromaGenerator(size_t width, size_t height) + : angle_(0.0), width_(width), height_(height) { + assert(width > 0); + assert(height > 0); + } + + VideoFrame* NextFrame() override { + frame_.CreateEmptyFrame(static_cast<int>(width_), + static_cast<int>(height_), + static_cast<int>(width_), + static_cast<int>((width_ + 1) / 2), + static_cast<int>((width_ + 1) / 2)); + angle_ += 30.0; + uint8_t u = fabs(sin(angle_)) * 0xFF; + uint8_t v = fabs(cos(angle_)) * 0xFF; + + memset(frame_.buffer(kYPlane), 0x80, frame_.allocated_size(kYPlane)); + memset(frame_.buffer(kUPlane), u, frame_.allocated_size(kUPlane)); + memset(frame_.buffer(kVPlane), v, frame_.allocated_size(kVPlane)); + return &frame_; + } + + private: + double angle_; + size_t width_; + size_t height_; + VideoFrame frame_; +}; + +class YuvFileGenerator : public FrameGenerator { + public: + YuvFileGenerator(std::vector<FILE*> files, + size_t width, + size_t height, + int frame_repeat_count) + : file_index_(0), + files_(files), + width_(width), + height_(height), + frame_size_(CalcBufferSize(kI420, + static_cast<int>(width_), + static_cast<int>(height_))), + frame_buffer_(new uint8_t[frame_size_]), + frame_display_count_(frame_repeat_count), + current_display_count_(0) { + assert(width > 0); + assert(height > 0); + assert(frame_repeat_count > 0); + } + + virtual ~YuvFileGenerator() { + for (FILE* file : files_) + fclose(file); + } + + VideoFrame* NextFrame() override { + if (current_display_count_ == 0) + ReadNextFrame(); + if (++current_display_count_ >= frame_display_count_) + current_display_count_ = 0; + + // If this is the last repeatition of this frame, it's OK to use the + // original instance, otherwise use a copy. + if (current_display_count_ == frame_display_count_) + return &last_read_frame_; + + temp_frame_copy_.CopyFrame(last_read_frame_); + return &temp_frame_copy_; + } + + void ReadNextFrame() { + size_t bytes_read = + fread(frame_buffer_.get(), 1, frame_size_, files_[file_index_]); + if (bytes_read < frame_size_) { + // No more frames to read in this file, rewind and move to next file. + rewind(files_[file_index_]); + file_index_ = (file_index_ + 1) % files_.size(); + bytes_read = fread(frame_buffer_.get(), 1, frame_size_, + files_[file_index_]); + assert(bytes_read >= frame_size_); + } + + last_read_frame_.CreateEmptyFrame( + static_cast<int>(width_), static_cast<int>(height_), + static_cast<int>(width_), static_cast<int>((width_ + 1) / 2), + static_cast<int>((width_ + 1) / 2)); + + ConvertToI420(kI420, frame_buffer_.get(), 0, 0, static_cast<int>(width_), + static_cast<int>(height_), 0, kVideoRotation_0, + &last_read_frame_); + } + + private: + size_t file_index_; + const std::vector<FILE*> files_; + const size_t width_; + const size_t height_; + const size_t frame_size_; + const rtc::scoped_ptr<uint8_t[]> frame_buffer_; + const int frame_display_count_; + int current_display_count_; + VideoFrame last_read_frame_; + VideoFrame temp_frame_copy_; +}; + +class ScrollingImageFrameGenerator : public FrameGenerator { + public: + ScrollingImageFrameGenerator(Clock* clock, + const std::vector<FILE*>& files, + size_t source_width, + size_t source_height, + size_t target_width, + size_t target_height, + int64_t scroll_time_ms, + int64_t pause_time_ms) + : clock_(clock), + start_time_(clock->TimeInMilliseconds()), + scroll_time_(scroll_time_ms), + pause_time_(pause_time_ms), + num_frames_(files.size()), + current_frame_num_(num_frames_ - 1), + current_source_frame_(nullptr), + file_generator_(files, source_width, source_height, 1) { + RTC_DCHECK(clock_ != nullptr); + RTC_DCHECK_GT(num_frames_, 0u); + RTC_DCHECK_GE(source_height, target_height); + RTC_DCHECK_GE(source_width, target_width); + RTC_DCHECK_GE(scroll_time_ms, 0); + RTC_DCHECK_GE(pause_time_ms, 0); + RTC_DCHECK_GT(scroll_time_ms + pause_time_ms, 0); + current_frame_.CreateEmptyFrame(static_cast<int>(target_width), + static_cast<int>(target_height), + static_cast<int>(target_width), + static_cast<int>((target_width + 1) / 2), + static_cast<int>((target_width + 1) / 2)); + } + + virtual ~ScrollingImageFrameGenerator() {} + + VideoFrame* NextFrame() override { + const int64_t kFrameDisplayTime = scroll_time_ + pause_time_; + const int64_t now = clock_->TimeInMilliseconds(); + int64_t ms_since_start = now - start_time_; + + size_t frame_num = (ms_since_start / kFrameDisplayTime) % num_frames_; + UpdateSourceFrame(frame_num); + + double scroll_factor; + int64_t time_into_frame = ms_since_start % kFrameDisplayTime; + if (time_into_frame < scroll_time_) { + scroll_factor = static_cast<double>(time_into_frame) / scroll_time_; + } else { + scroll_factor = 1.0; + } + CropSourceToScrolledImage(scroll_factor); + + return ¤t_frame_; + } + + void UpdateSourceFrame(size_t frame_num) { + while (current_frame_num_ != frame_num) { + current_source_frame_ = file_generator_.NextFrame(); + current_frame_num_ = (current_frame_num_ + 1) % num_frames_; + } + RTC_DCHECK(current_source_frame_ != nullptr); + } + + void CropSourceToScrolledImage(double scroll_factor) { + const int kTargetWidth = current_frame_.width(); + const int kTargetHeight = current_frame_.height(); + int scroll_margin_x = current_source_frame_->width() - kTargetWidth; + int pixels_scrolled_x = + static_cast<int>(scroll_margin_x * scroll_factor + 0.5); + int scroll_margin_y = current_source_frame_->height() - kTargetHeight; + int pixels_scrolled_y = + static_cast<int>(scroll_margin_y * scroll_factor + 0.5); + + int offset_y = (current_source_frame_->stride(PlaneType::kYPlane) * + pixels_scrolled_y) + + pixels_scrolled_x; + int offset_u = (current_source_frame_->stride(PlaneType::kUPlane) * + (pixels_scrolled_y / 2)) + + (pixels_scrolled_x / 2); + int offset_v = (current_source_frame_->stride(PlaneType::kVPlane) * + (pixels_scrolled_y / 2)) + + (pixels_scrolled_x / 2); + + current_frame_.CreateFrame( + ¤t_source_frame_->buffer(PlaneType::kYPlane)[offset_y], + ¤t_source_frame_->buffer(PlaneType::kUPlane)[offset_u], + ¤t_source_frame_->buffer(PlaneType::kVPlane)[offset_v], + kTargetWidth, kTargetHeight, + current_source_frame_->stride(PlaneType::kYPlane), + current_source_frame_->stride(PlaneType::kUPlane), + current_source_frame_->stride(PlaneType::kVPlane)); + } + + Clock* const clock_; + const int64_t start_time_; + const int64_t scroll_time_; + const int64_t pause_time_; + const size_t num_frames_; + size_t current_frame_num_; + VideoFrame* current_source_frame_; + VideoFrame current_frame_; + YuvFileGenerator file_generator_; +}; + +} // namespace + +FrameGenerator* FrameGenerator::CreateChromaGenerator(size_t width, + size_t height) { + return new ChromaGenerator(width, height); +} + +FrameGenerator* FrameGenerator::CreateFromYuvFile( + std::vector<std::string> filenames, + size_t width, + size_t height, + int frame_repeat_count) { + assert(!filenames.empty()); + std::vector<FILE*> files; + for (const std::string& filename : filenames) { + FILE* file = fopen(filename.c_str(), "rb"); + RTC_DCHECK(file != nullptr); + files.push_back(file); + } + + return new YuvFileGenerator(files, width, height, frame_repeat_count); +} + +FrameGenerator* FrameGenerator::CreateScrollingInputFromYuvFiles( + Clock* clock, + std::vector<std::string> filenames, + size_t source_width, + size_t source_height, + size_t target_width, + size_t target_height, + int64_t scroll_time_ms, + int64_t pause_time_ms) { + assert(!filenames.empty()); + std::vector<FILE*> files; + for (const std::string& filename : filenames) { + FILE* file = fopen(filename.c_str(), "rb"); + RTC_DCHECK(file != nullptr); + files.push_back(file); + } + + return new ScrollingImageFrameGenerator( + clock, files, source_width, source_height, target_width, target_height, + scroll_time_ms, pause_time_ms); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/frame_generator.h b/webrtc/test/frame_generator.h new file mode 100644 index 0000000000..7f20c749e8 --- /dev/null +++ b/webrtc/test/frame_generator.h @@ -0,0 +1,64 @@ +/* + * 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_COMMON_VIDEO_TEST_FRAME_GENERATOR_H_ +#define WEBRTC_COMMON_VIDEO_TEST_FRAME_GENERATOR_H_ + +#include <string> +#include <vector> + +#include "webrtc/typedefs.h" +#include "webrtc/video_frame.h" + +namespace webrtc { +class Clock; +namespace test { + +class FrameGenerator { + public: + FrameGenerator() {} + virtual ~FrameGenerator() {} + + // Returns video frame that remains valid until next call. + virtual VideoFrame* NextFrame() = 0; + + // Creates a test frame generator that creates fully saturated frames with + // varying U, V values over time. + static FrameGenerator* CreateChromaGenerator(size_t width, size_t height); + + // Creates a frame generator that repeatedly plays a set of yuv files. + // The frame_repeat_count determines how many times each frame is shown, + // with 1 = show each frame once, etc. + static FrameGenerator* CreateFromYuvFile(std::vector<std::string> files, + size_t width, + size_t height, + int frame_repeat_count); + + // Creates a frame generator which takes a set of yuv files (wrapping a + // frame generator created by CreateFromYuvFile() above), but outputs frames + // that have been cropped to specified resolution: source_width/source_height + // is the size of the source images, target_width/target_height is the size of + // the cropped output. For each source image read, the cropped viewport will + // be scrolled top to bottom/left to right for scroll_tim_ms milliseconds. + // After that the image will stay in place for pause_time_ms milliseconds, + // and then this will be repeated with the next file from the input set. + static FrameGenerator* CreateScrollingInputFromYuvFiles( + Clock* clock, + std::vector<std::string> filenames, + size_t source_width, + size_t source_height, + size_t target_width, + size_t target_height, + int64_t scroll_time_ms, + int64_t pause_time_ms); +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_COMMON_VIDEO_TEST_FRAME_GENERATOR_H_ diff --git a/webrtc/test/frame_generator_capturer.cc b/webrtc/test/frame_generator_capturer.cc new file mode 100644 index 0000000000..70e2c85698 --- /dev/null +++ b/webrtc/test/frame_generator_capturer.cc @@ -0,0 +1,137 @@ +/* + * 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/test/frame_generator_capturer.h" + +#include "webrtc/base/criticalsection.h" +#include "webrtc/test/frame_generator.h" +#include "webrtc/system_wrappers/include/clock.h" +#include "webrtc/system_wrappers/include/event_wrapper.h" +#include "webrtc/system_wrappers/include/sleep.h" +#include "webrtc/system_wrappers/include/thread_wrapper.h" +#include "webrtc/video_send_stream.h" + +namespace webrtc { +namespace test { + +FrameGeneratorCapturer* FrameGeneratorCapturer::Create(VideoCaptureInput* input, + size_t width, + size_t height, + int target_fps, + Clock* clock) { + FrameGeneratorCapturer* capturer = new FrameGeneratorCapturer( + clock, input, FrameGenerator::CreateChromaGenerator(width, height), + target_fps); + if (!capturer->Init()) { + delete capturer; + return NULL; + } + + return capturer; +} + +FrameGeneratorCapturer* FrameGeneratorCapturer::CreateFromYuvFile( + VideoCaptureInput* input, + const std::string& file_name, + size_t width, + size_t height, + int target_fps, + Clock* clock) { + FrameGeneratorCapturer* capturer = new FrameGeneratorCapturer( + clock, input, + FrameGenerator::CreateFromYuvFile(std::vector<std::string>(1, file_name), + width, height, 1), + target_fps); + if (!capturer->Init()) { + delete capturer; + return NULL; + } + + return capturer; +} + +FrameGeneratorCapturer::FrameGeneratorCapturer(Clock* clock, + VideoCaptureInput* input, + FrameGenerator* frame_generator, + int target_fps) + : VideoCapturer(input), + clock_(clock), + sending_(false), + tick_(EventTimerWrapper::Create()), + frame_generator_(frame_generator), + target_fps_(target_fps), + first_frame_capture_time_(-1) { + assert(input != NULL); + assert(frame_generator != NULL); + assert(target_fps > 0); +} + +FrameGeneratorCapturer::~FrameGeneratorCapturer() { + Stop(); + + if (thread_.get() != NULL) + thread_->Stop(); +} + +bool FrameGeneratorCapturer::Init() { + // This check is added because frame_generator_ might be file based and should + // not crash because a file moved. + if (frame_generator_.get() == NULL) + return false; + + if (!tick_->StartTimer(true, 1000 / target_fps_)) + return false; + thread_ = ThreadWrapper::CreateThread(FrameGeneratorCapturer::Run, this, + "FrameGeneratorCapturer"); + if (thread_.get() == NULL) + return false; + if (!thread_->Start()) { + thread_.reset(); + return false; + } + thread_->SetPriority(webrtc::kHighPriority); + return true; +} + +bool FrameGeneratorCapturer::Run(void* obj) { + static_cast<FrameGeneratorCapturer*>(obj)->InsertFrame(); + return true; +} + +void FrameGeneratorCapturer::InsertFrame() { + { + rtc::CritScope cs(&lock_); + if (sending_) { + VideoFrame* frame = frame_generator_->NextFrame(); + frame->set_ntp_time_ms(clock_->CurrentNtpInMilliseconds()); + if (first_frame_capture_time_ == -1) { + first_frame_capture_time_ = frame->ntp_time_ms(); + } + input_->IncomingCapturedFrame(*frame); + } + } + tick_->Wait(WEBRTC_EVENT_INFINITE); +} + +void FrameGeneratorCapturer::Start() { + rtc::CritScope cs(&lock_); + sending_ = true; +} + +void FrameGeneratorCapturer::Stop() { + rtc::CritScope cs(&lock_); + sending_ = false; +} + +void FrameGeneratorCapturer::ForceFrame() { + tick_->Set(); +} +} // test +} // webrtc diff --git a/webrtc/test/frame_generator_capturer.h b/webrtc/test/frame_generator_capturer.h new file mode 100644 index 0000000000..aff906dfa2 --- /dev/null +++ b/webrtc/test/frame_generator_capturer.h @@ -0,0 +1,77 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_FRAME_GENERATOR_CAPTURER_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_FRAME_GENERATOR_CAPTURER_H_ + +#include <string> + +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/test/video_capturer.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class CriticalSectionWrapper; +class EventTimerWrapper; +class ThreadWrapper; + +namespace test { + +class FrameGenerator; + +class FrameGeneratorCapturer : public VideoCapturer { + public: + static FrameGeneratorCapturer* Create(VideoCaptureInput* input, + size_t width, + size_t height, + int target_fps, + Clock* clock); + + static FrameGeneratorCapturer* CreateFromYuvFile(VideoCaptureInput* input, + const std::string& file_name, + size_t width, + size_t height, + int target_fps, + Clock* clock); + virtual ~FrameGeneratorCapturer(); + + void Start() override; + void Stop() override; + void ForceFrame(); + + int64_t first_frame_capture_time() const { return first_frame_capture_time_; } + + FrameGeneratorCapturer(Clock* clock, + VideoCaptureInput* input, + FrameGenerator* frame_generator, + int target_fps); + bool Init(); + + private: + void InsertFrame(); + static bool Run(void* obj); + + Clock* const clock_; + bool sending_; + + rtc::scoped_ptr<EventTimerWrapper> tick_; + rtc::CriticalSection lock_; + rtc::scoped_ptr<ThreadWrapper> thread_; + rtc::scoped_ptr<FrameGenerator> frame_generator_; + + int target_fps_; + + int64_t first_frame_capture_time_; +}; +} // test +} // webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_FRAME_GENERATOR_CAPTURER_H_ diff --git a/webrtc/test/frame_generator_unittest.cc b/webrtc/test/frame_generator_unittest.cc new file mode 100644 index 0000000000..6376e2c221 --- /dev/null +++ b/webrtc/test/frame_generator_unittest.cc @@ -0,0 +1,149 @@ +/* + * 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 <stdio.h> +#include <string> + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/test/frame_generator.h" +#include "webrtc/test/testsupport/fileutils.h" + +namespace webrtc { +namespace test { + +static const int kFrameWidth = 4; +static const int kFrameHeight = 4; + +class FrameGeneratorTest : public ::testing::Test { + public: + void SetUp() override { + two_frame_filename_ = + test::TempFilename(test::OutputPath(), "2_frame_yuv_file"); + one_frame_filename_ = + test::TempFilename(test::OutputPath(), "1_frame_yuv_file"); + + FILE* file = fopen(two_frame_filename_.c_str(), "wb"); + WriteYuvFile(file, 0, 0, 0); + WriteYuvFile(file, 127, 127, 127); + fclose(file); + file = fopen(one_frame_filename_.c_str(), "wb"); + WriteYuvFile(file, 255, 255, 255); + fclose(file); + } + void TearDown() override { + remove(one_frame_filename_.c_str()); + remove(two_frame_filename_.c_str()); + } + + protected: + void WriteYuvFile(FILE* file, uint8_t y, uint8_t u, uint8_t v) { + assert(file); + rtc::scoped_ptr<uint8_t[]> plane_buffer(new uint8_t[y_size]); + memset(plane_buffer.get(), y, y_size); + fwrite(plane_buffer.get(), 1, y_size, file); + memset(plane_buffer.get(), u, uv_size); + fwrite(plane_buffer.get(), 1, uv_size, file); + memset(plane_buffer.get(), v, uv_size); + fwrite(plane_buffer.get(), 1, uv_size, file); + } + + void CheckFrameAndMutate(VideoFrame* frame, uint8_t y, uint8_t u, uint8_t v) { + // Check that frame is valid, has the correct color and timestamp are clean. + ASSERT_NE(nullptr, frame); + uint8_t* buffer; + ASSERT_EQ(y_size, frame->allocated_size(PlaneType::kYPlane)); + buffer = frame->buffer(PlaneType::kYPlane); + for (int i = 0; i < y_size; ++i) + ASSERT_EQ(y, buffer[i]); + ASSERT_EQ(uv_size, frame->allocated_size(PlaneType::kUPlane)); + buffer = frame->buffer(PlaneType::kUPlane); + for (int i = 0; i < uv_size; ++i) + ASSERT_EQ(u, buffer[i]); + ASSERT_EQ(uv_size, frame->allocated_size(PlaneType::kVPlane)); + buffer = frame->buffer(PlaneType::kVPlane); + for (int i = 0; i < uv_size; ++i) + ASSERT_EQ(v, buffer[i]); + EXPECT_EQ(0, frame->ntp_time_ms()); + EXPECT_EQ(0, frame->render_time_ms()); + EXPECT_EQ(0u, frame->timestamp()); + + // Mutate to something arbitrary non-zero. + frame->set_ntp_time_ms(11); + frame->set_render_time_ms(12); + frame->set_timestamp(13); + } + + std::string two_frame_filename_; + std::string one_frame_filename_; + const int y_size = kFrameWidth * kFrameHeight; + const int uv_size = ((kFrameHeight + 1) / 2) * ((kFrameWidth + 1) / 2); +}; + +TEST_F(FrameGeneratorTest, SingleFrameFile) { + rtc::scoped_ptr<FrameGenerator> generator(FrameGenerator::CreateFromYuvFile( + std::vector<std::string>(1, one_frame_filename_), kFrameWidth, + kFrameHeight, 1)); + CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255); + CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255); +} + +TEST_F(FrameGeneratorTest, TwoFrameFile) { + rtc::scoped_ptr<FrameGenerator> generator(FrameGenerator::CreateFromYuvFile( + std::vector<std::string>(1, two_frame_filename_), kFrameWidth, + kFrameHeight, 1)); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); + CheckFrameAndMutate(generator->NextFrame(), 127, 127, 127); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); +} + +TEST_F(FrameGeneratorTest, MultipleFrameFiles) { + std::vector<std::string> files; + files.push_back(two_frame_filename_); + files.push_back(one_frame_filename_); + + rtc::scoped_ptr<FrameGenerator> generator( + FrameGenerator::CreateFromYuvFile(files, kFrameWidth, kFrameHeight, 1)); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); + CheckFrameAndMutate(generator->NextFrame(), 127, 127, 127); + CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); +} + +TEST_F(FrameGeneratorTest, TwoFrameFileWithRepeat) { + const int kRepeatCount = 3; + rtc::scoped_ptr<FrameGenerator> generator(FrameGenerator::CreateFromYuvFile( + std::vector<std::string>(1, two_frame_filename_), kFrameWidth, + kFrameHeight, kRepeatCount)); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 127, 127, 127); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); +} + +TEST_F(FrameGeneratorTest, MultipleFrameFilesWithRepeat) { + const int kRepeatCount = 3; + std::vector<std::string> files; + files.push_back(two_frame_filename_); + files.push_back(one_frame_filename_); + rtc::scoped_ptr<FrameGenerator> generator(FrameGenerator::CreateFromYuvFile( + files, kFrameWidth, kFrameHeight, kRepeatCount)); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 127, 127, 127); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/gl/gl_renderer.cc b/webrtc/test/gl/gl_renderer.cc new file mode 100644 index 0000000000..ff87d9999a --- /dev/null +++ b/webrtc/test/gl/gl_renderer.cc @@ -0,0 +1,112 @@ +/* + * 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/test/gl/gl_renderer.h" + +#include <string.h> + +#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" + +namespace webrtc { +namespace test { + +GlRenderer::GlRenderer() + : is_init_(false), buffer_(NULL), width_(0), height_(0) {} + +void GlRenderer::Init() { + assert(!is_init_); + is_init_ = true; + + glGenTextures(1, &texture_); +} + +void GlRenderer::Destroy() { + if (!is_init_) { + return; + } + + is_init_ = false; + + delete[] buffer_; + buffer_ = NULL; + + glDeleteTextures(1, &texture_); +} + +void GlRenderer::ResizeViewport(size_t width, size_t height) { + // TODO(pbos): Aspect ratio, letterbox the video. + glViewport(0, 0, width, height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glOrtho(0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f); + glMatrixMode(GL_MODELVIEW); +} + +void GlRenderer::ResizeVideo(size_t width, size_t height) { + assert(is_init_); + width_ = width; + height_ = height; + + buffer_size_ = width * height * 4; // BGRA + + delete[] buffer_; + buffer_ = new uint8_t[buffer_size_]; + assert(buffer_ != NULL); + memset(buffer_, 0, buffer_size_); + glBindTexture(GL_TEXTURE_2D, texture_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_BGRA, + GL_UNSIGNED_INT_8_8_8_8, static_cast<GLvoid*>(buffer_)); +} + +void GlRenderer::RenderFrame(const webrtc::VideoFrame& frame, + int /*render_delay_ms*/) { + assert(is_init_); + + if (static_cast<size_t>(frame.width()) != width_ || + static_cast<size_t>(frame.height()) != height_) { + ResizeVideo(frame.width(), frame.height()); + } + + webrtc::ConvertFromI420(frame, kBGRA, 0, buffer_); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, texture_); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_BGRA, + GL_UNSIGNED_INT_8_8_8_8, buffer_); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glLoadIdentity(); + + glBegin(GL_QUADS); + { + glTexCoord2f(0.0f, 0.0f); + glVertex3f(0.0f, 0.0f, 0.0f); + + glTexCoord2f(0.0f, 1.0f); + glVertex3f(0.0f, 1.0f, 0.0f); + + glTexCoord2f(1.0f, 1.0f); + glVertex3f(1.0f, 1.0f, 0.0f); + + glTexCoord2f(1.0f, 0.0f); + glVertex3f(1.0f, 0.0f, 0.0f); + } + glEnd(); + + glBindTexture(GL_TEXTURE_2D, 0); + glFlush(); +} +} // test +} // webrtc diff --git a/webrtc/test/gl/gl_renderer.h b/webrtc/test/gl/gl_renderer.h new file mode 100644 index 0000000000..27749468a2 --- /dev/null +++ b/webrtc/test/gl/gl_renderer.h @@ -0,0 +1,51 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_GL_GL_RENDERER_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_GL_GL_RENDERER_H_ + +#ifdef WEBRTC_MAC +#include <OpenGL/gl.h> +#else +#include <GL/gl.h> +#endif + +#include "webrtc/test/video_renderer.h" +#include "webrtc/typedefs.h" + + +namespace webrtc { +namespace test { + +class GlRenderer : public VideoRenderer { + public: + void RenderFrame(const webrtc::VideoFrame& frame, + int time_to_render_ms) override; + + protected: + GlRenderer(); + + void Init(); + void Destroy(); + + void ResizeViewport(size_t width, size_t height); + + private: + bool is_init_; + uint8_t* buffer_; + GLuint texture_; + size_t width_, height_, buffer_size_; + + void ResizeVideo(size_t width, size_t height); +}; +} // test +} // webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_GL_GL_RENDERER_H_ diff --git a/webrtc/test/histogram.cc b/webrtc/test/histogram.cc new file mode 100644 index 0000000000..6fcdb6864f --- /dev/null +++ b/webrtc/test/histogram.cc @@ -0,0 +1,82 @@ +/* + * 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/test/histogram.h" + +#include <map> + +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/thread_annotations.h" +#include "webrtc/system_wrappers/include/metrics.h" + +// Test implementation of histogram methods in +// webrtc/system_wrappers/include/metrics.h. + +namespace webrtc { +namespace { +struct SampleInfo { + SampleInfo(int sample) + : last(sample), total(1) {} + int last; // Last added sample. + int total; // Total number of added samples. +}; + +rtc::CriticalSection histogram_crit_; +// Map holding info about added samples to a histogram (mapped by the histogram +// name). +std::map<std::string, SampleInfo> histograms_ GUARDED_BY(histogram_crit_); +} // namespace + +namespace metrics { +Histogram* HistogramFactoryGetCounts(const std::string& name, int min, int max, + int bucket_count) { return NULL; } + +Histogram* HistogramFactoryGetEnumeration(const std::string& name, + int boundary) { return NULL; } + +void HistogramAdd( + Histogram* histogram_pointer, const std::string& name, int sample) { + rtc::CritScope cs(&histogram_crit_); + auto it = histograms_.find(name); + if (it == histograms_.end()) { + histograms_.insert(std::make_pair(name, SampleInfo(sample))); + return; + } + it->second.last = sample; + ++it->second.total; +} +} // namespace metrics + +namespace test { +int LastHistogramSample(const std::string& name) { + rtc::CritScope cs(&histogram_crit_); + const auto it = histograms_.find(name); + if (it == histograms_.end()) { + return -1; + } + return it->second.last; +} + +int NumHistogramSamples(const std::string& name) { + rtc::CritScope cs(&histogram_crit_); + const auto it = histograms_.find(name); + if (it == histograms_.end()) { + return 0; + } + return it->second.total; +} + +void ClearHistograms() { + rtc::CritScope cs(&histogram_crit_); + histograms_.clear(); +} +} // namespace test +} // namespace webrtc + diff --git a/webrtc/test/histogram.h b/webrtc/test/histogram.h new file mode 100644 index 0000000000..44ce32b4f4 --- /dev/null +++ b/webrtc/test/histogram.h @@ -0,0 +1,33 @@ +/* + * 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_TEST_HISTOGRAM_H_ +#define WEBRTC_TEST_HISTOGRAM_H_ + +#include <string> + +namespace webrtc { +namespace test { + +// Returns the last added sample to a histogram (or -1 if the histogram is not +// found). +int LastHistogramSample(const std::string& name); + +// Returns the number of added samples to a histogram. +int NumHistogramSamples(const std::string& name); + +// Removes all histograms. +void ClearHistograms(); + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_HISTOGRAM_H_ + diff --git a/webrtc/test/layer_filtering_transport.cc b/webrtc/test/layer_filtering_transport.cc new file mode 100644 index 0000000000..a4ebf47f93 --- /dev/null +++ b/webrtc/test/layer_filtering_transport.cc @@ -0,0 +1,106 @@ +/* + * 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/base/checks.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" +#include "webrtc/modules/rtp_rtcp/source/byte_io.h" +#include "webrtc/modules/rtp_rtcp/source/rtp_format.h" +#include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" +#include "webrtc/test/layer_filtering_transport.h" + +namespace webrtc { +namespace test { + +LayerFilteringTransport::LayerFilteringTransport( + const FakeNetworkPipe::Config& config, + Call* send_call, + uint8_t vp8_video_payload_type, + uint8_t vp9_video_payload_type, + uint8_t tl_discard_threshold, + uint8_t sl_discard_threshold) + : test::DirectTransport(config, send_call), + vp8_video_payload_type_(vp8_video_payload_type), + vp9_video_payload_type_(vp9_video_payload_type), + tl_discard_threshold_(tl_discard_threshold), + sl_discard_threshold_(sl_discard_threshold) {} + +uint16_t LayerFilteringTransport::NextSequenceNumber(uint32_t ssrc) { + auto it = current_seq_nums_.find(ssrc); + if (it == current_seq_nums_.end()) + return current_seq_nums_[ssrc] = 10000; + return ++it->second; +} + +bool LayerFilteringTransport::SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) { + if (tl_discard_threshold_ == 0 && sl_discard_threshold_ == 0) { + // Nothing to change, forward the packet immediately. + return test::DirectTransport::SendRtp(packet, length, options); + } + + bool set_marker_bit = false; + rtc::scoped_ptr<RtpHeaderParser> parser(RtpHeaderParser::Create()); + RTPHeader header; + parser->Parse(packet, length, &header); + + if (header.payloadType == vp8_video_payload_type_ || + header.payloadType == vp9_video_payload_type_) { + const uint8_t* payload = packet + header.headerLength; + RTC_DCHECK_GT(length, header.headerLength); + const size_t payload_length = length - header.headerLength; + RTC_DCHECK_GT(payload_length, header.paddingLength); + const size_t payload_data_length = payload_length - header.paddingLength; + + const bool is_vp8 = header.payloadType == vp8_video_payload_type_; + rtc::scoped_ptr<RtpDepacketizer> depacketizer( + RtpDepacketizer::Create(is_vp8 ? kRtpVideoVp8 : kRtpVideoVp9)); + RtpDepacketizer::ParsedPayload parsed_payload; + if (depacketizer->Parse(&parsed_payload, payload, payload_data_length)) { + const uint8_t temporalIdx = + is_vp8 ? parsed_payload.type.Video.codecHeader.VP8.temporalIdx + : parsed_payload.type.Video.codecHeader.VP9.temporal_idx; + const uint8_t spatialIdx = + is_vp8 ? kNoSpatialIdx + : parsed_payload.type.Video.codecHeader.VP9.spatial_idx; + if (sl_discard_threshold_ > 0 && + spatialIdx == sl_discard_threshold_ - 1 && + parsed_payload.type.Video.codecHeader.VP9.end_of_frame) { + // This layer is now the last in the superframe. + set_marker_bit = true; + } + if ((tl_discard_threshold_ > 0 && temporalIdx != kNoTemporalIdx && + temporalIdx >= tl_discard_threshold_) || + (sl_discard_threshold_ > 0 && spatialIdx != kNoSpatialIdx && + spatialIdx >= sl_discard_threshold_)) { + return true; // Discard the packet. + } + } else { + RTC_NOTREACHED() << "Parse error"; + } + } + + uint8_t temp_buffer[IP_PACKET_SIZE]; + memcpy(temp_buffer, packet, length); + + // We are discarding some of the packets (specifically, whole layers), so + // make sure the marker bit is set properly, and that sequence numbers are + // continuous. + if (set_marker_bit) + temp_buffer[1] |= kRtpMarkerBitMask; + + uint16_t seq_num = NextSequenceNumber(header.ssrc); + ByteWriter<uint16_t>::WriteBigEndian(&temp_buffer[2], seq_num); + return test::DirectTransport::SendRtp(temp_buffer, length, options); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/layer_filtering_transport.h b/webrtc/test/layer_filtering_transport.h new file mode 100644 index 0000000000..3f2389a51b --- /dev/null +++ b/webrtc/test/layer_filtering_transport.h @@ -0,0 +1,51 @@ +/* + * 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_TEST_LAYER_FILTERING_TRANSPORT_H_ +#define WEBRTC_TEST_LAYER_FILTERING_TRANSPORT_H_ + +#include "webrtc/call.h" +#include "webrtc/test/direct_transport.h" +#include "webrtc/test/fake_network_pipe.h" + +#include <map> + +namespace webrtc { + +namespace test { + +class LayerFilteringTransport : public test::DirectTransport { + public: + LayerFilteringTransport(const FakeNetworkPipe::Config& config, + Call* send_call, + uint8_t vp8_video_payload_type, + uint8_t vp9_video_payload_type, + uint8_t tl_discard_threshold, + uint8_t sl_discard_threshold); + bool SendRtp(const uint8_t* data, + size_t length, + const PacketOptions& options) override; + + private: + uint16_t NextSequenceNumber(uint32_t ssrc); + // Used to distinguish between VP8 and VP9. + const uint8_t vp8_video_payload_type_; + const uint8_t vp9_video_payload_type_; + // Discard all temporal/spatial layers with id greater or equal the + // threshold. 0 to disable. + const uint8_t tl_discard_threshold_; + const uint8_t sl_discard_threshold_; + // Current sequence number for each SSRC separately. + std::map<uint32_t, uint16_t> current_seq_nums_; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_LAYER_FILTERING_TRANSPORT_H_ diff --git a/webrtc/test/linux/glx_renderer.cc b/webrtc/test/linux/glx_renderer.cc new file mode 100644 index 0000000000..450c6bd8a5 --- /dev/null +++ b/webrtc/test/linux/glx_renderer.cc @@ -0,0 +1,178 @@ +/* + * 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/test/linux/glx_renderer.h" + +#include <assert.h> + +#include <X11/Xatom.h> +#include <X11/Xlib.h> + +#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" + +namespace webrtc { +namespace test { + +GlxRenderer::GlxRenderer(size_t width, size_t height) + : width_(width), + height_(height), + display_(NULL), + context_(NULL) { + assert(width > 0); + assert(height > 0); +} + +GlxRenderer::~GlxRenderer() { Destroy(); } + +bool GlxRenderer::Init(const char* window_title) { + if ((display_ = XOpenDisplay(NULL)) == NULL) { + Destroy(); + return false; + } + + int screen = DefaultScreen(display_); + + XVisualInfo* vi; + int attr_list[] = { GLX_DOUBLEBUFFER, GLX_RGBA, GLX_RED_SIZE, 4, + GLX_GREEN_SIZE, 4, GLX_BLUE_SIZE, 4, GLX_DEPTH_SIZE, 16, + None, }; + + if ((vi = glXChooseVisual(display_, screen, attr_list)) == NULL) { + Destroy(); + return false; + } + + context_ = glXCreateContext(display_, vi, 0, true); + if (context_ == NULL) { + Destroy(); + return false; + } + + XSetWindowAttributes window_attributes; + window_attributes.colormap = XCreateColormap( + display_, RootWindow(display_, vi->screen), vi->visual, AllocNone); + window_attributes.border_pixel = 0; + window_attributes.event_mask = StructureNotifyMask | ExposureMask; + window_ = XCreateWindow(display_, RootWindow(display_, vi->screen), 0, 0, + width_, height_, 0, vi->depth, InputOutput, + vi->visual, CWBorderPixel | CWColormap | CWEventMask, + &window_attributes); + XFree(vi); + + XSetStandardProperties(display_, window_, window_title, window_title, None, + NULL, 0, NULL); + + Atom wm_delete = XInternAtom(display_, "WM_DELETE_WINDOW", True); + if (wm_delete != None) { + XSetWMProtocols(display_, window_, &wm_delete, 1); + } + + XMapRaised(display_, window_); + + if (!glXMakeCurrent(display_, window_, context_)) { + Destroy(); + return false; + } + GlRenderer::Init(); + if (!glXMakeCurrent(display_, None, NULL)) { + Destroy(); + return false; + } + + Resize(width_, height_); + return true; +} + +void GlxRenderer::Destroy() { + if (context_ != NULL) { + glXMakeCurrent(display_, window_, context_); + GlRenderer::Destroy(); + glXMakeCurrent(display_, None, NULL); + glXDestroyContext(display_, context_); + context_ = NULL; + } + + if (display_ != NULL) { + XCloseDisplay(display_); + display_ = NULL; + } +} + +GlxRenderer* GlxRenderer::Create(const char* window_title, size_t width, + size_t height) { + GlxRenderer* glx_renderer = new GlxRenderer(width, height); + if (!glx_renderer->Init(window_title)) { + // TODO(pbos): Add GLX-failed warning here? + delete glx_renderer; + return NULL; + } + return glx_renderer; +} + +void GlxRenderer::Resize(size_t width, size_t height) { + width_ = width; + height_ = height; + if (!glXMakeCurrent(display_, window_, context_)) { + abort(); + } + GlRenderer::ResizeViewport(width_, height_); + if (!glXMakeCurrent(display_, None, NULL)) { + abort(); + } + + XSizeHints* size_hints = XAllocSizeHints(); + if (size_hints == NULL) { + abort(); + } + size_hints->flags = PAspect; + size_hints->min_aspect.x = size_hints->max_aspect.x = width_; + size_hints->min_aspect.y = size_hints->max_aspect.y = height_; + XSetWMNormalHints(display_, window_, size_hints); + XFree(size_hints); + + XWindowChanges wc; + wc.width = static_cast<int>(width); + wc.height = static_cast<int>(height); + XConfigureWindow(display_, window_, CWWidth | CWHeight, &wc); +} + +void GlxRenderer::RenderFrame(const webrtc::VideoFrame& frame, + int /*render_delay_ms*/) { + if (static_cast<size_t>(frame.width()) != width_ || + static_cast<size_t>(frame.height()) != height_) { + Resize(static_cast<size_t>(frame.width()), + static_cast<size_t>(frame.height())); + } + + XEvent event; + if (!glXMakeCurrent(display_, window_, context_)) { + abort(); + } + while (XPending(display_)) { + XNextEvent(display_, &event); + switch (event.type) { + case ConfigureNotify: + GlRenderer::ResizeViewport(event.xconfigure.width, + event.xconfigure.height); + break; + default: + break; + } + } + + GlRenderer::RenderFrame(frame, 0); + glXSwapBuffers(display_, window_); + + if (!glXMakeCurrent(display_, None, NULL)) { + abort(); + } +} +} // test +} // webrtc diff --git a/webrtc/test/linux/glx_renderer.h b/webrtc/test/linux/glx_renderer.h new file mode 100644 index 0000000000..517f22a038 --- /dev/null +++ b/webrtc/test/linux/glx_renderer.h @@ -0,0 +1,48 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_LINUX_GLX_RENDERER_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_LINUX_GLX_RENDERER_H_ + +#include <GL/glx.h> +#include <X11/Xlib.h> + +#include "webrtc/test/gl/gl_renderer.h" +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +class GlxRenderer : public GlRenderer { + public: + static GlxRenderer* Create(const char* window_title, size_t width, + size_t height); + virtual ~GlxRenderer(); + + void RenderFrame(const webrtc::VideoFrame& frame, int delta) override; + bool IsTextureSupported() const override { return false; } + + private: + GlxRenderer(size_t width, size_t height); + + bool Init(const char* window_title); + void Resize(size_t width, size_t height); + void Destroy(); + + size_t width_, height_; + + Display* display_; + Window window_; + GLXContext context_; +}; +} // test +} // webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_LINUX_GLX_RENDERER_H_ diff --git a/webrtc/test/linux/video_renderer_linux.cc b/webrtc/test/linux/video_renderer_linux.cc new file mode 100644 index 0000000000..6f69dd7498 --- /dev/null +++ b/webrtc/test/linux/video_renderer_linux.cc @@ -0,0 +1,27 @@ +/* + * 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/test/video_renderer.h" + +#include "webrtc/test/linux/glx_renderer.h" + +namespace webrtc { +namespace test { + +VideoRenderer* VideoRenderer::CreatePlatformRenderer(const char* window_title, + size_t width, + size_t height) { + GlxRenderer* glx_renderer = GlxRenderer::Create(window_title, width, height); + if (glx_renderer != NULL) { + return glx_renderer; + } + return NULL; +} +} // test +} // webrtc diff --git a/webrtc/test/mac/run_test.mm b/webrtc/test/mac/run_test.mm new file mode 100644 index 0000000000..4e0093a9b6 --- /dev/null +++ b/webrtc/test/mac/run_test.mm @@ -0,0 +1,75 @@ +/* + * 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. + */ + +#import <Cocoa/Cocoa.h> + +#include "webrtc/test/run_test.h" + +// Converting a C++ function pointer to an Objective-C block. +typedef void(^TestBlock)(); +TestBlock functionToBlock(void(*function)()) { + return [^(void) { function(); } copy]; +} + +// Class calling the test function on the platform specific thread. +@interface TestRunner : NSObject { + BOOL running_; +} +- (void)runAllTests:(TestBlock)ignored; +- (BOOL)running; +@end + +@implementation TestRunner +- (id)init { + self = [super init]; + if (self) { + running_ = YES; + } + return self; +} + +- (void)runAllTests:(TestBlock)testBlock { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + testBlock(); + running_ = NO; + [pool release]; +} + +- (BOOL)running { + return running_; +} +@end + +namespace webrtc { +namespace test { + +void RunTest(void(*test)()) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [NSApplication sharedApplication]; + + // Convert the function pointer to an Objective-C block and call on a + // separate thread, to avoid blocking the main thread. + TestRunner *testRunner = [[TestRunner alloc] init]; + TestBlock testBlock = functionToBlock(test); + [NSThread detachNewThreadSelector:@selector(runAllTests:) + toTarget:testRunner + withObject:testBlock]; + + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + while ([testRunner running] && + [runLoop runMode:NSDefaultRunLoopMode + beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]); + + [testRunner release]; + [pool release]; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/mac/video_renderer_mac.h b/webrtc/test/mac/video_renderer_mac.h new file mode 100644 index 0000000000..2e55538954 --- /dev/null +++ b/webrtc/test/mac/video_renderer_mac.h @@ -0,0 +1,41 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_MAC_VIDEO_RENDERER_MAC_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_MAC_VIDEO_RENDERER_MAC_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/test/gl/gl_renderer.h" + +@class CocoaWindow; + +namespace webrtc { +namespace test { + +class MacRenderer : public GlRenderer { + public: + MacRenderer(); + virtual ~MacRenderer(); + + bool Init(const char* window_title, int width, int height); + + // Implements GlRenderer. + void RenderFrame(const VideoFrame& frame, int delta) override; + bool IsTextureSupported() const override { return false; } + + private: + CocoaWindow* window_; + + RTC_DISALLOW_COPY_AND_ASSIGN(MacRenderer); +}; +} // test +} // webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_MAC_VIDEO_RENDERER_MAC_H_ diff --git a/webrtc/test/mac/video_renderer_mac.mm b/webrtc/test/mac/video_renderer_mac.mm new file mode 100644 index 0000000000..9cde95a982 --- /dev/null +++ b/webrtc/test/mac/video_renderer_mac.mm @@ -0,0 +1,134 @@ +/* + * 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/test/mac/video_renderer_mac.h" + +#import <Cocoa/Cocoa.h> + +// Creates a Cocoa Window with an OpenGL context, used together with an OpenGL +// renderer. +@interface CocoaWindow : NSObject { + @private + NSWindow *window_; + NSOpenGLContext *context_; + NSString *title_; + int width_; + int height_; +} + +- (id)initWithTitle:(NSString *)title width:(int)width height:(int)height; +// 'createWindow' must be called on the main thread. +- (void)createWindow:(NSObject *)ignored; +- (void)makeCurrentContext; + +@end + +@implementation CocoaWindow + static NSInteger nextXOrigin_; + static NSInteger nextYOrigin_; + +- (id)initWithTitle:(NSString *)title width:(int)width height:(int)height { + if (self = [super init]) { + title_ = title; + width_ = width; + height_ = height; + } + return self; +} + +- (void)dealloc { + [window_ release]; + [super dealloc]; +} + +- (void)createWindow:(NSObject *)ignored { + NSInteger xOrigin = nextXOrigin_; + NSRect screenFrame = [[NSScreen mainScreen] frame]; + if (nextXOrigin_ + width_ < screenFrame.size.width) { + nextXOrigin_ += width_; + } else { + xOrigin = 0; + nextXOrigin_ = 0; + nextYOrigin_ += height_; + } + if (nextYOrigin_ + height_ > screenFrame.size.height) { + xOrigin = 0; + nextXOrigin_ = 0; + nextYOrigin_ = 0; + } + NSInteger yOrigin = nextYOrigin_; + NSRect windowFrame = NSMakeRect(xOrigin, yOrigin, width_, height_); + window_ = [[NSWindow alloc] initWithContentRect:windowFrame + styleMask:NSTitledWindowMask + backing:NSBackingStoreBuffered + defer:NO]; + + NSRect viewFrame = NSMakeRect(0, 0, width_, height_); + NSOpenGLView *view = [[[NSOpenGLView alloc] initWithFrame:viewFrame + pixelFormat:nil] autorelease]; + context_ = [view openGLContext]; + + [[window_ contentView] addSubview:view]; + [window_ setTitle:title_]; + [window_ makeKeyAndOrderFront:NSApp]; +} + +- (void)makeCurrentContext { + [context_ makeCurrentContext]; +} + +@end + +namespace webrtc { +namespace test { + +VideoRenderer* VideoRenderer::CreatePlatformRenderer(const char* window_title, + size_t width, + size_t height) { + MacRenderer* renderer = new MacRenderer(); + if (!renderer->Init(window_title, width, height)) { + delete renderer; + return NULL; + } + return renderer; +} + +MacRenderer::MacRenderer() + : window_(NULL) {} + +MacRenderer::~MacRenderer() { + GlRenderer::Destroy(); + [window_ release]; +} + +bool MacRenderer::Init(const char* window_title, int width, int height) { + window_ = [[CocoaWindow alloc] + initWithTitle:[NSString stringWithUTF8String:window_title] + width:width + height:height]; + if (!window_) + return false; + [window_ performSelectorOnMainThread:@selector(createWindow:) + withObject:nil + waitUntilDone:YES]; + + [window_ makeCurrentContext]; + GlRenderer::Init(); + GlRenderer::ResizeViewport(width, height); + return true; +} + +void MacRenderer::RenderFrame(const VideoFrame& frame, int /*delta*/) { + [window_ makeCurrentContext]; + GlRenderer::RenderFrame(frame, 0); +} + +} // test +} // webrtc diff --git a/webrtc/test/metrics.gyp b/webrtc/test/metrics.gyp new file mode 100644 index 0000000000..eda0b054ee --- /dev/null +++ b/webrtc/test/metrics.gyp @@ -0,0 +1,32 @@ +# Copyright (c) 2011 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. + +{ + 'includes': [ + '../build/common.gypi', + ], + 'targets': [ + { + # The metrics code must be kept in its own GYP file in order to + # avoid a circular dependency error due to the dependency on libyuv. + # If the code would be put in test.gyp a circular dependency error during + # GYP generation would occur, because the libyuv.gypi unittest target + # depends on test_support_main. See issue #160 for more info. + 'target_name': 'metrics', + 'type': 'static_library', + 'dependencies': [ + '<(webrtc_root)/common_video/common_video.gyp:common_video', + '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers', + ], + 'sources': [ + 'testsupport/metrics/video_metrics.h', + 'testsupport/metrics/video_metrics.cc', + ], + }, + ], # targets +} diff --git a/webrtc/test/mock_transport.h b/webrtc/test/mock_transport.h new file mode 100644 index 0000000000..4937134512 --- /dev/null +++ b/webrtc/test/mock_transport.h @@ -0,0 +1,28 @@ +/* + * 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_TEST_MOCK_TRANSPORT_H_ +#define WEBRTC_TEST_MOCK_TRANSPORT_H_ + +#include "testing/gmock/include/gmock/gmock.h" +#include "webrtc/transport.h" + +namespace webrtc { + +class MockTransport : public Transport { + public: + MOCK_METHOD3(SendRtp, + bool(const uint8_t* data, + size_t len, + const PacketOptions& options)); + MOCK_METHOD2(SendRtcp, bool(const uint8_t* data, size_t len)); +}; +} // namespace webrtc +#endif // WEBRTC_TEST_MOCK_TRANSPORT_H_ diff --git a/webrtc/test/null_platform_renderer.cc b/webrtc/test/null_platform_renderer.cc new file mode 100644 index 0000000000..362f7db762 --- /dev/null +++ b/webrtc/test/null_platform_renderer.cc @@ -0,0 +1,22 @@ +/* + * 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/test/video_renderer.h" + +namespace webrtc { +namespace test { + +VideoRenderer* VideoRenderer::CreatePlatformRenderer(const char* window_title, + size_t width, + size_t height) { + return NULL; +} +} // test +} // webrtc diff --git a/webrtc/test/null_transport.cc b/webrtc/test/null_transport.cc new file mode 100644 index 0000000000..7fa36d1246 --- /dev/null +++ b/webrtc/test/null_transport.cc @@ -0,0 +1,26 @@ +/* + * 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/test/null_transport.h" + +namespace webrtc { +namespace test { + +bool NullTransport::SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) { + return true; +} + +bool NullTransport::SendRtcp(const uint8_t* packet, size_t length) { + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/null_transport.h b/webrtc/test/null_transport.h new file mode 100644 index 0000000000..f4b704634d --- /dev/null +++ b/webrtc/test/null_transport.h @@ -0,0 +1,30 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_NULL_TRANSPORT_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_NULL_TRANSPORT_H_ + +#include "webrtc/transport.h" + +namespace webrtc { + +class PacketReceiver; + +namespace test { +class NullTransport : public Transport { + public: + bool SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) override; + bool SendRtcp(const uint8_t* packet, size_t length) override; +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_NULL_TRANSPORT_H_ diff --git a/webrtc/test/random.cc b/webrtc/test/random.cc new file mode 100644 index 0000000000..c4c405f6b8 --- /dev/null +++ b/webrtc/test/random.cc @@ -0,0 +1,57 @@ +/* + * 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/test/random.h" + +#include <math.h> + +#include "webrtc/base/checks.h" + +namespace webrtc { + +namespace test { + +Random::Random(uint32_t seed) : a_(0x531FDB97 ^ seed), b_(0x6420ECA8 + seed) { +} + +float Random::Rand() { + const double kScale = 1.0f / (static_cast<uint64_t>(1) << 32); + double result = kScale * b_; + a_ ^= b_; + b_ += a_; + return static_cast<float>(result); +} + +int Random::Rand(int low, int high) { + RTC_DCHECK(low <= high); + float uniform = Rand() * (high - low + 1) + low; + return static_cast<int>(uniform); +} + +int Random::Gaussian(int mean, int standard_deviation) { + // Creating a Normal distribution variable from two independent uniform + // variables based on the Box-Muller transform, which is defined on the + // interval (0, 1], hence the mask+add below. + const double kPi = 3.14159265358979323846; + const double kScale = 1.0 / 0x80000000ul; + double u1 = kScale * ((a_ & 0x7ffffffful) + 1); + double u2 = kScale * ((b_ & 0x7ffffffful) + 1); + a_ ^= b_; + b_ += a_; + return static_cast<int>( + mean + standard_deviation * sqrt(-2 * log(u1)) * cos(2 * kPi * u2)); +} + +int Random::Exponential(float lambda) { + float uniform = Rand(); + return static_cast<int>(-log(uniform) / lambda); +} +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/random.h b/webrtc/test/random.h new file mode 100644 index 0000000000..5cc54f2129 --- /dev/null +++ b/webrtc/test/random.h @@ -0,0 +1,49 @@ +/* + * 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_TEST_RANDOM_H_ +#define WEBRTC_TEST_RANDOM_H_ + +#include "webrtc/typedefs.h" +#include "webrtc/base/constructormagic.h" + +namespace webrtc { + +namespace test { + +class Random { + public: + explicit Random(uint32_t seed); + + // Return pseudo-random number in the interval [0.0, 1.0). + float Rand(); + + // Return pseudo-random number mapped to the interval [low, high]. + int Rand(int low, int high); + + // Normal Distribution. + int Gaussian(int mean, int standard_deviation); + + // Exponential Distribution. + int Exponential(float lambda); + + // TODO(solenberg): Random from histogram. + // template<typename T> int Distribution(const std::vector<T> histogram) { + + private: + uint32_t a_; + uint32_t b_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(Random); +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_RANDOM_H_ diff --git a/webrtc/test/rtcp_packet_parser.cc b/webrtc/test/rtcp_packet_parser.cc new file mode 100644 index 0000000000..8ce249e0b6 --- /dev/null +++ b/webrtc/test/rtcp_packet_parser.cc @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2014 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/rtcp_packet_parser.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace webrtc { +namespace test { + +using namespace RTCPUtility; + +RtcpPacketParser::RtcpPacketParser() {} + +RtcpPacketParser::~RtcpPacketParser() {} + +void RtcpPacketParser::Parse(const void *data, size_t len) { + const uint8_t* packet = static_cast<const uint8_t*>(data); + RTCPUtility::RTCPParserV2 parser(packet, len, true); + EXPECT_TRUE(parser.IsValid()); + for (RTCPUtility::RTCPPacketTypes type = parser.Begin(); + type != RTCPPacketTypes::kInvalid; type = parser.Iterate()) { + switch (type) { + case RTCPPacketTypes::kSr: + sender_report_.Set(parser.Packet().SR); + break; + case RTCPPacketTypes::kRr: + receiver_report_.Set(parser.Packet().RR); + break; + case RTCPPacketTypes::kReportBlockItem: + report_block_.Set(parser.Packet().ReportBlockItem); + ++report_blocks_per_ssrc_[parser.Packet().ReportBlockItem.SSRC]; + break; + case RTCPPacketTypes::kSdes: + sdes_.Set(); + break; + case RTCPPacketTypes::kSdesChunk: + sdes_chunk_.Set(parser.Packet().CName); + break; + case RTCPPacketTypes::kBye: + bye_.Set(parser.Packet().BYE); + break; + case RTCPPacketTypes::kApp: + app_.Set(parser.Packet().APP); + break; + case RTCPPacketTypes::kAppItem: + app_item_.Set(parser.Packet().APP); + break; + case RTCPPacketTypes::kExtendedIj: + ij_.Set(); + break; + case RTCPPacketTypes::kExtendedIjItem: + ij_item_.Set(parser.Packet().ExtendedJitterReportItem); + break; + case RTCPPacketTypes::kPsfbPli: + pli_.Set(parser.Packet().PLI); + break; + case RTCPPacketTypes::kPsfbSli: + sli_.Set(parser.Packet().SLI); + break; + case RTCPPacketTypes::kPsfbSliItem: + sli_item_.Set(parser.Packet().SLIItem); + break; + case RTCPPacketTypes::kPsfbRpsi: + rpsi_.Set(parser.Packet().RPSI); + break; + case RTCPPacketTypes::kPsfbFir: + fir_.Set(parser.Packet().FIR); + break; + case RTCPPacketTypes::kPsfbFirItem: + fir_item_.Set(parser.Packet().FIRItem); + break; + case RTCPPacketTypes::kRtpfbNack: + nack_.Set(parser.Packet().NACK); + nack_item_.Clear(); + break; + case RTCPPacketTypes::kRtpfbNackItem: + nack_item_.Set(parser.Packet().NACKItem); + break; + case RTCPPacketTypes::kPsfbApp: + psfb_app_.Set(parser.Packet().PSFBAPP); + break; + case RTCPPacketTypes::kPsfbRembItem: + remb_item_.Set(parser.Packet().REMBItem); + break; + case RTCPPacketTypes::kRtpfbTmmbr: + tmmbr_.Set(parser.Packet().TMMBR); + break; + case RTCPPacketTypes::kRtpfbTmmbrItem: + tmmbr_item_.Set(parser.Packet().TMMBRItem); + break; + case RTCPPacketTypes::kRtpfbTmmbn: + tmmbn_.Set(parser.Packet().TMMBN); + tmmbn_items_.Clear(); + break; + case RTCPPacketTypes::kRtpfbTmmbnItem: + tmmbn_items_.Set(parser.Packet().TMMBNItem); + break; + case RTCPPacketTypes::kXrHeader: + xr_header_.Set(parser.Packet().XR); + dlrr_items_.Clear(); + break; + case RTCPPacketTypes::kXrReceiverReferenceTime: + rrtr_.Set(parser.Packet().XRReceiverReferenceTimeItem); + break; + case RTCPPacketTypes::kXrDlrrReportBlock: + dlrr_.Set(); + break; + case RTCPPacketTypes::kXrDlrrReportBlockItem: + dlrr_items_.Set(parser.Packet().XRDLRRReportBlockItem); + break; + case RTCPPacketTypes::kXrVoipMetric: + voip_metric_.Set(parser.Packet().XRVOIPMetricItem); + break; + default: + break; + } + } +} + +uint64_t Rpsi::PictureId() const { + assert(num_packets_ > 0); + uint16_t num_bytes = rpsi_.NumberOfValidBits / 8; + assert(num_bytes > 0); + uint64_t picture_id = 0; + for (uint16_t i = 0; i < num_bytes - 1; ++i) { + picture_id += (rpsi_.NativeBitString[i] & 0x7f); + picture_id <<= 7; + } + picture_id += (rpsi_.NativeBitString[num_bytes - 1] & 0x7f); + return picture_id; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/rtcp_packet_parser.h b/webrtc/test/rtcp_packet_parser.h new file mode 100644 index 0000000000..cc890b4f2f --- /dev/null +++ b/webrtc/test/rtcp_packet_parser.h @@ -0,0 +1,705 @@ +/* + * Copyright (c) 2014 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_TEST_RTCP_PACKET_PARSER_H_ +#define WEBRTC_TEST_RTCP_PACKET_PARSER_H_ + +#include <map> +#include <string> +#include <vector> + +#include "webrtc/modules/rtp_rtcp/source/rtcp_utility.h" +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +class RtcpPacketParser; + +class PacketType { + public: + virtual ~PacketType() {} + + int num_packets() const { return num_packets_; } + + protected: + PacketType() : num_packets_(0) {} + + int num_packets_; +}; + +class SenderReport : public PacketType { + public: + SenderReport() {} + virtual ~SenderReport() {} + + uint32_t Ssrc() const { return sr_.SenderSSRC; } + uint32_t NtpSec() const { return sr_.NTPMostSignificant; } + uint32_t NtpFrac() const { return sr_.NTPLeastSignificant; } + uint32_t RtpTimestamp() const { return sr_.RTPTimestamp; } + uint32_t PacketCount() const { return sr_.SenderPacketCount; } + uint32_t OctetCount() const { return sr_.SenderOctetCount; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketSR& sr) { + sr_ = sr; + ++num_packets_; + } + + RTCPUtility::RTCPPacketSR sr_; +}; + +class ReceiverReport : public PacketType { + public: + ReceiverReport() {} + virtual ~ReceiverReport() {} + + uint32_t Ssrc() const { return rr_.SenderSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketRR& rr) { + rr_ = rr; + ++num_packets_; + } + + RTCPUtility::RTCPPacketRR rr_; +}; + +class ReportBlock : public PacketType { + public: + ReportBlock() {} + virtual ~ReportBlock() {} + + uint32_t Ssrc() const { return rb_.SSRC; } + uint8_t FractionLost() const { return rb_.FractionLost; } + uint32_t CumPacketLost() const { return rb_.CumulativeNumOfPacketsLost; } + uint32_t ExtHighestSeqNum() const { return rb_.ExtendedHighestSequenceNumber;} + uint32_t Jitter() const { return rb_.Jitter; } + uint32_t LastSr() const { return rb_.LastSR; } + uint32_t DelayLastSr()const { return rb_.DelayLastSR; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketReportBlockItem& rb) { + rb_ = rb; + ++num_packets_; + } + + RTCPUtility::RTCPPacketReportBlockItem rb_; +}; + +class Ij : public PacketType { + public: + Ij() {} + virtual ~Ij() {} + + private: + friend class RtcpPacketParser; + + void Set() { ++num_packets_; } +}; + +class IjItem : public PacketType { + public: + IjItem() {} + virtual ~IjItem() {} + + uint32_t Jitter() const { return ij_item_.Jitter; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketExtendedJitterReportItem& ij_item) { + ij_item_ = ij_item; + ++num_packets_; + } + + RTCPUtility::RTCPPacketExtendedJitterReportItem ij_item_; +}; + +class Sdes : public PacketType { + public: + Sdes() {} + virtual ~Sdes() {} + + private: + friend class RtcpPacketParser; + + void Set() { ++num_packets_; } +}; + +class SdesChunk : public PacketType { + public: + SdesChunk() {} + virtual ~SdesChunk() {} + + uint32_t Ssrc() const { return cname_.SenderSSRC; } + std::string Cname() const { return cname_.CName; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketSDESCName& cname) { + cname_ = cname; + ++num_packets_; + } + + RTCPUtility::RTCPPacketSDESCName cname_; +}; + +class Bye : public PacketType { + public: + Bye() {} + virtual ~Bye() {} + + uint32_t Ssrc() const { return bye_.SenderSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketBYE& bye) { + bye_ = bye; + ++num_packets_; + } + + RTCPUtility::RTCPPacketBYE bye_; +}; + +class Rpsi : public PacketType { + public: + Rpsi() {} + virtual ~Rpsi() {} + + uint32_t Ssrc() const { return rpsi_.SenderSSRC; } + uint32_t MediaSsrc() const { return rpsi_.MediaSSRC; } + uint8_t PayloadType() const { return rpsi_.PayloadType; } + uint16_t NumberOfValidBits() const { return rpsi_.NumberOfValidBits; } + uint64_t PictureId() const; + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBRPSI& rpsi) { + rpsi_ = rpsi; + ++num_packets_; + } + + RTCPUtility::RTCPPacketPSFBRPSI rpsi_; +}; + +class App : public PacketType { + public: + App() {} + virtual ~App() {} + + uint8_t SubType() const { return app_.SubType; } + uint32_t Name() const { return app_.Name; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketAPP& app) { + app_ = app; + ++num_packets_; + } + + RTCPUtility::RTCPPacketAPP app_; +}; + +class AppItem : public PacketType { + public: + AppItem() {} + virtual ~AppItem() {} + + uint8_t* Data() { return app_item_.Data; } + uint16_t DataLength() const { return app_item_.Size; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketAPP& app) { + app_item_ = app; + ++num_packets_; + } + + RTCPUtility::RTCPPacketAPP app_item_; +}; + +class Pli : public PacketType { + public: + Pli() {} + virtual ~Pli() {} + + uint32_t Ssrc() const { return pli_.SenderSSRC; } + uint32_t MediaSsrc() const { return pli_.MediaSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBPLI& pli) { + pli_ = pli; + ++num_packets_; + } + + RTCPUtility::RTCPPacketPSFBPLI pli_; +}; + +class Sli : public PacketType { + public: + Sli() {} + virtual ~Sli() {} + + uint32_t Ssrc() const { return sli_.SenderSSRC; } + uint32_t MediaSsrc() const { return sli_.MediaSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBSLI& sli) { + sli_ = sli; + ++num_packets_; + } + + RTCPUtility::RTCPPacketPSFBSLI sli_; +}; + +class SliItem : public PacketType { + public: + SliItem() {} + virtual ~SliItem() {} + + uint16_t FirstMb() const { return sli_item_.FirstMB; } + uint16_t NumberOfMb() const { return sli_item_.NumberOfMB; } + uint8_t PictureId() const { return sli_item_.PictureId; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBSLIItem& sli_item) { + sli_item_ = sli_item; + ++num_packets_; + } + + RTCPUtility::RTCPPacketPSFBSLIItem sli_item_; +}; + +class Fir : public PacketType { + public: + Fir() {} + virtual ~Fir() {} + + uint32_t Ssrc() const { return fir_.SenderSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBFIR& fir) { + fir_ = fir; + ++num_packets_; + } + + RTCPUtility::RTCPPacketPSFBFIR fir_; +}; + +class FirItem : public PacketType { + public: + FirItem() {} + virtual ~FirItem() {} + + uint32_t Ssrc() const { return fir_item_.SSRC; } + uint8_t SeqNum() const { return fir_item_.CommandSequenceNumber; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBFIRItem& fir_item) { + fir_item_ = fir_item; + ++num_packets_; + } + + RTCPUtility::RTCPPacketPSFBFIRItem fir_item_; +}; + +class Nack : public PacketType { + public: + Nack() {} + virtual ~Nack() {} + + uint32_t Ssrc() const { return nack_.SenderSSRC; } + uint32_t MediaSsrc() const { return nack_.MediaSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketRTPFBNACK& nack) { + nack_ = nack; + ++num_packets_; + } + + RTCPUtility::RTCPPacketRTPFBNACK nack_; +}; + +class NackItem : public PacketType { + public: + NackItem() {} + virtual ~NackItem() {} + + std::vector<uint16_t> last_nack_list() const { + return last_nack_list_; + } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketRTPFBNACKItem& nack_item) { + last_nack_list_.push_back(nack_item.PacketID); + for (int i = 0; i < 16; ++i) { + if (nack_item.BitMask & (1 << i)) { + last_nack_list_.push_back(nack_item.PacketID + i + 1); + } + } + ++num_packets_; + } + void Clear() { last_nack_list_.clear(); } + + std::vector<uint16_t> last_nack_list_; +}; + +class PsfbApp : public PacketType { + public: + PsfbApp() {} + virtual ~PsfbApp() {} + + uint32_t Ssrc() const { return psfb_app_.SenderSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBAPP& psfb_app) { + psfb_app_ = psfb_app; + ++num_packets_; + } + + RTCPUtility::RTCPPacketPSFBAPP psfb_app_; +}; + +class RembItem : public PacketType { + public: + RembItem() : last_bitrate_bps_(0) {} + virtual ~RembItem() {} + + int last_bitrate_bps() const { return last_bitrate_bps_; } + std::vector<uint32_t> last_ssrc_list() { + return last_ssrc_list_; + } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketPSFBREMBItem& remb_item) { + last_bitrate_bps_ = remb_item.BitRate; + last_ssrc_list_.clear(); + last_ssrc_list_.insert( + last_ssrc_list_.end(), + remb_item.SSRCs, + remb_item.SSRCs + remb_item.NumberOfSSRCs); + ++num_packets_; + } + + uint32_t last_bitrate_bps_; + std::vector<uint32_t> last_ssrc_list_; +}; + +class Tmmbr : public PacketType { + public: + Tmmbr() {} + virtual ~Tmmbr() {} + + uint32_t Ssrc() const { return tmmbr_.SenderSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketRTPFBTMMBR& tmmbr) { + tmmbr_ = tmmbr; + ++num_packets_; + } + + RTCPUtility::RTCPPacketRTPFBTMMBR tmmbr_; +}; + +class TmmbrItem : public PacketType { + public: + TmmbrItem() {} + virtual ~TmmbrItem() {} + + uint32_t Ssrc() const { return tmmbr_item_.SSRC; } + uint32_t BitrateKbps() const { return tmmbr_item_.MaxTotalMediaBitRate; } + uint32_t Overhead() const { return tmmbr_item_.MeasuredOverhead; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketRTPFBTMMBRItem& tmmbr_item) { + tmmbr_item_ = tmmbr_item; + ++num_packets_; + } + + RTCPUtility::RTCPPacketRTPFBTMMBRItem tmmbr_item_; +}; + + +class Tmmbn : public PacketType { + public: + Tmmbn() {} + virtual ~Tmmbn() {} + + uint32_t Ssrc() const { return tmmbn_.SenderSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketRTPFBTMMBN& tmmbn) { + tmmbn_ = tmmbn; + ++num_packets_; + } + + RTCPUtility::RTCPPacketRTPFBTMMBN tmmbn_; +}; + +class TmmbnItems : public PacketType { + public: + TmmbnItems() {} + virtual ~TmmbnItems() {} + + uint32_t Ssrc(uint8_t num) const { + assert(num < tmmbns_.size()); + return tmmbns_[num].SSRC; + } + uint32_t BitrateKbps(uint8_t num) const { + assert(num < tmmbns_.size()); + return tmmbns_[num].MaxTotalMediaBitRate; + } + uint32_t Overhead(uint8_t num) const { + assert(num < tmmbns_.size()); + return tmmbns_[num].MeasuredOverhead; + } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketRTPFBTMMBNItem& tmmbn_item) { + tmmbns_.push_back(tmmbn_item); + ++num_packets_; + } + void Clear() { tmmbns_.clear(); } + + std::vector<RTCPUtility::RTCPPacketRTPFBTMMBNItem> tmmbns_; +}; + +class XrHeader : public PacketType { + public: + XrHeader() {} + virtual ~XrHeader() {} + + uint32_t Ssrc() const { return xr_header_.OriginatorSSRC; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketXR& xr_header) { + xr_header_ = xr_header; + ++num_packets_; + } + + RTCPUtility::RTCPPacketXR xr_header_; +}; + +class Rrtr : public PacketType { + public: + Rrtr() {} + virtual ~Rrtr() {} + + uint32_t NtpSec() const { return rrtr_.NTPMostSignificant; } + uint32_t NtpFrac() const { return rrtr_.NTPLeastSignificant; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketXRReceiverReferenceTimeItem& rrtr) { + rrtr_ = rrtr; + ++num_packets_; + } + + RTCPUtility::RTCPPacketXRReceiverReferenceTimeItem rrtr_; +}; + +class Dlrr : public PacketType { + public: + Dlrr() {} + virtual ~Dlrr() {} + + private: + friend class RtcpPacketParser; + + void Set() { ++num_packets_; } +}; + +class DlrrItems : public PacketType { + public: + DlrrItems() {} + virtual ~DlrrItems() {} + + uint32_t Ssrc(uint8_t num) const { + assert(num < dlrrs_.size()); + return dlrrs_[num].SSRC; + } + uint32_t LastRr(uint8_t num) const { + assert(num < dlrrs_.size()); + return dlrrs_[num].LastRR; + } + uint32_t DelayLastRr(uint8_t num) const { + assert(num < dlrrs_.size()); + return dlrrs_[num].DelayLastRR; + } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketXRDLRRReportBlockItem& dlrr) { + dlrrs_.push_back(dlrr); + ++num_packets_; + } + void Clear() { dlrrs_.clear(); } + + std::vector<RTCPUtility::RTCPPacketXRDLRRReportBlockItem> dlrrs_; +}; + +class VoipMetric : public PacketType { + public: + VoipMetric() {} + virtual ~VoipMetric() {} + + uint32_t Ssrc() const { return voip_metric_.SSRC; } + uint8_t LossRate() { return voip_metric_.lossRate; } + uint8_t DiscardRate() { return voip_metric_.discardRate; } + uint8_t BurstDensity() { return voip_metric_.burstDensity; } + uint8_t GapDensity() { return voip_metric_.gapDensity; } + uint16_t BurstDuration() { return voip_metric_.burstDuration; } + uint16_t GapDuration() { return voip_metric_.gapDuration; } + uint16_t RoundTripDelay() { return voip_metric_.roundTripDelay; } + uint16_t EndSystemDelay() { return voip_metric_.endSystemDelay; } + uint8_t SignalLevel() { return voip_metric_.signalLevel; } + uint8_t NoiseLevel() { return voip_metric_.noiseLevel; } + uint8_t Rerl() { return voip_metric_.RERL; } + uint8_t Gmin() { return voip_metric_.Gmin; } + uint8_t Rfactor() { return voip_metric_.Rfactor; } + uint8_t ExtRfactor() { return voip_metric_.extRfactor; } + uint8_t MosLq() { return voip_metric_.MOSLQ; } + uint8_t MosCq() { return voip_metric_.MOSCQ; } + uint8_t RxConfig() { return voip_metric_.RXconfig; } + uint16_t JbNominal() { return voip_metric_.JBnominal; } + uint16_t JbMax() { return voip_metric_.JBmax; } + uint16_t JbAbsMax() { return voip_metric_.JBabsMax; } + + private: + friend class RtcpPacketParser; + + void Set(const RTCPUtility::RTCPPacketXRVOIPMetricItem& voip_metric) { + voip_metric_ = voip_metric; + ++num_packets_; + } + + RTCPUtility::RTCPPacketXRVOIPMetricItem voip_metric_; +}; + +class RtcpPacketParser { + public: + RtcpPacketParser(); + ~RtcpPacketParser(); + + void Parse(const void *packet, size_t packet_len); + + SenderReport* sender_report() { return &sender_report_; } + ReceiverReport* receiver_report() { return &receiver_report_; } + ReportBlock* report_block() { return &report_block_; } + Sdes* sdes() { return &sdes_; } + SdesChunk* sdes_chunk() { return &sdes_chunk_; } + Bye* bye() { return &bye_; } + App* app() { return &app_; } + AppItem* app_item() { return &app_item_; } + Ij* ij() { return &ij_; } + IjItem* ij_item() { return &ij_item_; } + Pli* pli() { return &pli_; } + Sli* sli() { return &sli_; } + SliItem* sli_item() { return &sli_item_; } + Rpsi* rpsi() { return &rpsi_; } + Fir* fir() { return &fir_; } + FirItem* fir_item() { return &fir_item_; } + Nack* nack() { return &nack_; } + NackItem* nack_item() { return &nack_item_; } + PsfbApp* psfb_app() { return &psfb_app_; } + RembItem* remb_item() { return &remb_item_; } + Tmmbr* tmmbr() { return &tmmbr_; } + TmmbrItem* tmmbr_item() { return &tmmbr_item_; } + Tmmbn* tmmbn() { return &tmmbn_; } + TmmbnItems* tmmbn_items() { return &tmmbn_items_; } + XrHeader* xr_header() { return &xr_header_; } + Rrtr* rrtr() { return &rrtr_; } + Dlrr* dlrr() { return &dlrr_; } + DlrrItems* dlrr_items() { return &dlrr_items_; } + VoipMetric* voip_metric() { return &voip_metric_; } + + int report_blocks_per_ssrc(uint32_t ssrc) { + return report_blocks_per_ssrc_[ssrc]; + } + + private: + SenderReport sender_report_; + ReceiverReport receiver_report_; + ReportBlock report_block_; + Sdes sdes_; + SdesChunk sdes_chunk_; + Bye bye_; + App app_; + AppItem app_item_; + Ij ij_; + IjItem ij_item_; + Pli pli_; + Sli sli_; + SliItem sli_item_; + Rpsi rpsi_; + Fir fir_; + FirItem fir_item_; + Nack nack_; + NackItem nack_item_; + PsfbApp psfb_app_; + RembItem remb_item_; + Tmmbr tmmbr_; + TmmbrItem tmmbr_item_; + Tmmbn tmmbn_; + TmmbnItems tmmbn_items_; + XrHeader xr_header_; + Rrtr rrtr_; + Dlrr dlrr_; + DlrrItems dlrr_items_; + VoipMetric voip_metric_; + + std::map<uint32_t, int> report_blocks_per_ssrc_; +}; +} // namespace test +} // namespace webrtc +#endif // WEBRTC_TEST_RTCP_PACKET_PARSER_H_ diff --git a/webrtc/test/rtp_file_reader.cc b/webrtc/test/rtp_file_reader.cc new file mode 100644 index 0000000000..cb0e40705f --- /dev/null +++ b/webrtc/test/rtp_file_reader.cc @@ -0,0 +1,674 @@ +/* + * Copyright (c) 2014 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/rtp_file_reader.h" + +#include <stdio.h> + +#include <map> +#include <string> +#include <vector> + +#include "webrtc/base/checks.h" +#include "webrtc/base/format_macros.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" + +namespace webrtc { +namespace test { + +static const size_t kFirstLineLength = 40; +static uint16_t kPacketHeaderSize = 8; + +#if 1 +# define DEBUG_LOG(text) +# define DEBUG_LOG1(text, arg) +#else +# define DEBUG_LOG(text) (printf(text "\n")) +# define DEBUG_LOG1(text, arg) (printf(text "\n", arg)) +#endif + +#define TRY(expr) \ + do { \ + if (!(expr)) { \ + DEBUG_LOG1("FAIL at " __FILE__ ":%d", __LINE__); \ + return false; \ + } \ + } while (0) + +bool ReadUint32(uint32_t* out, FILE* file) { + *out = 0; + for (size_t i = 0; i < 4; ++i) { + *out <<= 8; + uint8_t tmp; + if (fread(&tmp, 1, sizeof(uint8_t), file) != sizeof(uint8_t)) + return false; + *out |= tmp; + } + return true; +} + +bool ReadUint16(uint16_t* out, FILE* file) { + *out = 0; + for (size_t i = 0; i < 2; ++i) { + *out <<= 8; + uint8_t tmp; + if (fread(&tmp, 1, sizeof(uint8_t), file) != sizeof(uint8_t)) + return false; + *out |= tmp; + } + return true; +} + +class RtpFileReaderImpl : public RtpFileReader { + public: + virtual bool Init(const std::string& filename, + const std::set<uint32_t>& ssrc_filter) = 0; +}; + +class InterleavedRtpFileReader : public RtpFileReaderImpl { + public: + virtual ~InterleavedRtpFileReader() { + if (file_ != NULL) { + fclose(file_); + file_ = NULL; + } + } + + virtual bool Init(const std::string& filename, + const std::set<uint32_t>& ssrc_filter) { + file_ = fopen(filename.c_str(), "rb"); + if (file_ == NULL) { + printf("ERROR: Can't open file: %s\n", filename.c_str()); + return false; + } + return true; + } + virtual bool NextPacket(RtpPacket* packet) { + assert(file_ != NULL); + packet->length = RtpPacket::kMaxPacketBufferSize; + uint32_t len = 0; + TRY(ReadUint32(&len, file_)); + if (packet->length < len) { + FATAL() << "Packet is too large to fit: " << len << " bytes vs " + << packet->length + << " bytes allocated. Consider increasing the buffer " + "size"; + } + if (fread(packet->data, 1, len, file_) != len) + return false; + + packet->length = len; + packet->original_length = len; + packet->time_ms = time_ms_; + time_ms_ += 5; + return true; + } + + private: + FILE* file_ = NULL; + int64_t time_ms_ = 0; +}; + +// Read RTP packets from file in rtpdump format, as documented at: +// http://www.cs.columbia.edu/irt/software/rtptools/ +class RtpDumpReader : public RtpFileReaderImpl { + public: + RtpDumpReader() : file_(NULL) {} + virtual ~RtpDumpReader() { + if (file_ != NULL) { + fclose(file_); + file_ = NULL; + } + } + + bool Init(const std::string& filename, + const std::set<uint32_t>& ssrc_filter) { + file_ = fopen(filename.c_str(), "rb"); + if (file_ == NULL) { + printf("ERROR: Can't open file: %s\n", filename.c_str()); + return false; + } + + char firstline[kFirstLineLength + 1] = {0}; + if (fgets(firstline, kFirstLineLength, file_) == NULL) { + DEBUG_LOG("ERROR: Can't read from file\n"); + return false; + } + if (strncmp(firstline, "#!rtpplay", 9) == 0) { + if (strncmp(firstline, "#!rtpplay1.0", 12) != 0) { + DEBUG_LOG("ERROR: wrong rtpplay version, must be 1.0\n"); + return false; + } + } else if (strncmp(firstline, "#!RTPencode", 11) == 0) { + if (strncmp(firstline, "#!RTPencode1.0", 14) != 0) { + DEBUG_LOG("ERROR: wrong RTPencode version, must be 1.0\n"); + return false; + } + } else { + DEBUG_LOG("ERROR: wrong file format of input file\n"); + return false; + } + + uint32_t start_sec; + uint32_t start_usec; + uint32_t source; + uint16_t port; + uint16_t padding; + TRY(ReadUint32(&start_sec, file_)); + TRY(ReadUint32(&start_usec, file_)); + TRY(ReadUint32(&source, file_)); + TRY(ReadUint16(&port, file_)); + TRY(ReadUint16(&padding, file_)); + + return true; + } + + bool NextPacket(RtpPacket* packet) override { + uint8_t* rtp_data = packet->data; + packet->length = RtpPacket::kMaxPacketBufferSize; + + uint16_t len; + uint16_t plen; + uint32_t offset; + TRY(ReadUint16(&len, file_)); + TRY(ReadUint16(&plen, file_)); + TRY(ReadUint32(&offset, file_)); + + // Use 'len' here because a 'plen' of 0 specifies rtcp. + len -= kPacketHeaderSize; + if (packet->length < len) { + FATAL() << "Packet is too large to fit: " << len << " bytes vs " + << packet->length + << " bytes allocated. Consider increasing the buffer " + "size"; + } + if (fread(rtp_data, 1, len, file_) != len) { + return false; + } + + packet->length = len; + packet->original_length = plen; + packet->time_ms = offset; + return true; + } + + private: + FILE* file_; + + RTC_DISALLOW_COPY_AND_ASSIGN(RtpDumpReader); +}; + +enum { + kResultFail = -1, + kResultSuccess = 0, + kResultSkip = 1, + + kPcapVersionMajor = 2, + kPcapVersionMinor = 4, + kLinktypeNull = 0, + kLinktypeEthernet = 1, + kBsdNullLoopback1 = 0x00000002, + kBsdNullLoopback2 = 0x02000000, + kEthernetIIHeaderMacSkip = 12, + kEthertypeIp = 0x0800, + kIpVersion4 = 4, + kMinIpHeaderLength = 20, + kFragmentOffsetClear = 0x0000, + kFragmentOffsetDoNotFragment = 0x4000, + kProtocolTcp = 0x06, + kProtocolUdp = 0x11, + kUdpHeaderLength = 8, + kMaxReadBufferSize = 4096 +}; + +const uint32_t kPcapBOMSwapOrder = 0xd4c3b2a1UL; +const uint32_t kPcapBOMNoSwapOrder = 0xa1b2c3d4UL; + +#define TRY_PCAP(expr) \ + do { \ + int r = (expr); \ + if (r == kResultFail) { \ + DEBUG_LOG1("FAIL at " __FILE__ ":%d", __LINE__); \ + return kResultFail; \ + } else if (r == kResultSkip) { \ + return kResultSkip; \ + } \ + } while (0) + +// Read RTP packets from file in tcpdump/libpcap format, as documented at: +// http://wiki.wireshark.org/Development/LibpcapFileFormat +class PcapReader : public RtpFileReaderImpl { + public: + PcapReader() + : file_(NULL), + swap_pcap_byte_order_(false), +#ifdef WEBRTC_ARCH_BIG_ENDIAN + swap_network_byte_order_(false), +#else + swap_network_byte_order_(true), +#endif + read_buffer_(), + packets_by_ssrc_(), + packets_(), + next_packet_it_() { + } + + virtual ~PcapReader() { + if (file_ != NULL) { + fclose(file_); + file_ = NULL; + } + } + + bool Init(const std::string& filename, + const std::set<uint32_t>& ssrc_filter) override { + return Initialize(filename, ssrc_filter) == kResultSuccess; + } + + int Initialize(const std::string& filename, + const std::set<uint32_t>& ssrc_filter) { + file_ = fopen(filename.c_str(), "rb"); + if (file_ == NULL) { + printf("ERROR: Can't open file: %s\n", filename.c_str()); + return kResultFail; + } + + if (ReadGlobalHeader() < 0) { + return kResultFail; + } + + int total_packet_count = 0; + uint32_t stream_start_ms = 0; + int32_t next_packet_pos = ftell(file_); + for (;;) { + TRY_PCAP(fseek(file_, next_packet_pos, SEEK_SET)); + int result = ReadPacket(&next_packet_pos, stream_start_ms, + ++total_packet_count, ssrc_filter); + if (result == kResultFail) { + break; + } else if (result == kResultSuccess && packets_.size() == 1) { + assert(stream_start_ms == 0); + PacketIterator it = packets_.begin(); + stream_start_ms = it->time_offset_ms; + it->time_offset_ms = 0; + } + } + + if (feof(file_) == 0) { + printf("Failed reading file!\n"); + return kResultFail; + } + + printf("Total packets in file: %d\n", total_packet_count); + printf("Total RTP/RTCP packets: %" PRIuS "\n", packets_.size()); + + for (SsrcMapIterator mit = packets_by_ssrc_.begin(); + mit != packets_by_ssrc_.end(); ++mit) { + uint32_t ssrc = mit->first; + const std::vector<uint32_t>& packet_indices = mit->second; + uint8_t pt = packets_[packet_indices[0]].rtp_header.payloadType; + printf("SSRC: %08x, %" PRIuS " packets, pt=%d\n", ssrc, + packet_indices.size(), pt); + } + + // TODO(solenberg): Better validation of identified SSRC streams. + // + // Since we're dealing with raw network data here, we will wrongly identify + // some packets as RTP. When these packets are consumed by RtpPlayer, they + // are unlikely to cause issues as they will ultimately be filtered out by + // the RtpRtcp module. However, we should really do better filtering here, + // which we can accomplish in a number of ways, e.g.: + // + // - Verify that the time stamps and sequence numbers for RTP packets are + // both increasing/decreasing. If they move in different directions, the + // SSRC is likely bogus and can be dropped. (Normally they should be inc- + // reasing but we must allow packet reordering). + // - If RTP sequence number is not changing, drop the stream. + // - Can also use srcip:port->dstip:port pairs, assuming few SSRC collisions + // for up/down streams. + + next_packet_it_ = packets_.begin(); + return kResultSuccess; + } + + bool NextPacket(RtpPacket* packet) override { + uint32_t length = RtpPacket::kMaxPacketBufferSize; + if (NextPcap(packet->data, &length, &packet->time_ms) != kResultSuccess) + return false; + packet->length = static_cast<size_t>(length); + packet->original_length = packet->length; + return true; + } + + virtual int NextPcap(uint8_t* data, uint32_t* length, uint32_t* time_ms) { + assert(data); + assert(length); + assert(time_ms); + + if (next_packet_it_ == packets_.end()) { + return -1; + } + if (*length < next_packet_it_->payload_length) { + return -1; + } + TRY_PCAP(fseek(file_, next_packet_it_->pos_in_file, SEEK_SET)); + TRY_PCAP(Read(data, next_packet_it_->payload_length)); + *length = next_packet_it_->payload_length; + *time_ms = next_packet_it_->time_offset_ms; + next_packet_it_++; + + return 0; + } + + private: + // A marker of an RTP packet within the file. + struct RtpPacketMarker { + uint32_t packet_number; // One-based index (like in WireShark) + uint32_t time_offset_ms; + uint32_t source_ip; + uint32_t dest_ip; + uint16_t source_port; + uint16_t dest_port; + RTPHeader rtp_header; + int32_t pos_in_file; // Byte offset of payload from start of file. + uint32_t payload_length; + }; + + typedef std::vector<RtpPacketMarker>::iterator PacketIterator; + typedef std::map<uint32_t, std::vector<uint32_t> > SsrcMap; + typedef std::map<uint32_t, std::vector<uint32_t> >::iterator SsrcMapIterator; + + int ReadGlobalHeader() { + uint32_t magic; + TRY_PCAP(Read(&magic, false)); + if (magic == kPcapBOMSwapOrder) { + swap_pcap_byte_order_ = true; + } else if (magic == kPcapBOMNoSwapOrder) { + swap_pcap_byte_order_ = false; + } else { + return kResultFail; + } + + uint16_t version_major; + uint16_t version_minor; + TRY_PCAP(Read(&version_major, false)); + TRY_PCAP(Read(&version_minor, false)); + if (version_major != kPcapVersionMajor || + version_minor != kPcapVersionMinor) { + return kResultFail; + } + + int32_t this_zone; // GMT to local correction. + uint32_t sigfigs; // Accuracy of timestamps. + uint32_t snaplen; // Max length of captured packets, in octets. + uint32_t network; // Data link type. + TRY_PCAP(Read(&this_zone, false)); + TRY_PCAP(Read(&sigfigs, false)); + TRY_PCAP(Read(&snaplen, false)); + TRY_PCAP(Read(&network, false)); + + // Accept only LINKTYPE_NULL and LINKTYPE_ETHERNET. + // See: http://www.tcpdump.org/linktypes.html + if (network != kLinktypeNull && network != kLinktypeEthernet) { + return kResultFail; + } + + return kResultSuccess; + } + + int ReadPacket(int32_t* next_packet_pos, + uint32_t stream_start_ms, + uint32_t number, + const std::set<uint32_t>& ssrc_filter) { + assert(next_packet_pos); + + uint32_t ts_sec; // Timestamp seconds. + uint32_t ts_usec; // Timestamp microseconds. + uint32_t incl_len; // Number of octets of packet saved in file. + uint32_t orig_len; // Actual length of packet. + TRY_PCAP(Read(&ts_sec, false)); + TRY_PCAP(Read(&ts_usec, false)); + TRY_PCAP(Read(&incl_len, false)); + TRY_PCAP(Read(&orig_len, false)); + + *next_packet_pos = ftell(file_) + incl_len; + + RtpPacketMarker marker = {0}; + marker.packet_number = number; + marker.time_offset_ms = CalcTimeDelta(ts_sec, ts_usec, stream_start_ms); + TRY_PCAP(ReadPacketHeader(&marker)); + marker.pos_in_file = ftell(file_); + + if (marker.payload_length > sizeof(read_buffer_)) { + printf("Packet too large!\n"); + return kResultFail; + } + TRY_PCAP(Read(read_buffer_, marker.payload_length)); + + RtpUtility::RtpHeaderParser rtp_parser(read_buffer_, marker.payload_length); + if (rtp_parser.RTCP()) { + rtp_parser.ParseRtcp(&marker.rtp_header); + packets_.push_back(marker); + } else { + if (!rtp_parser.Parse(marker.rtp_header, NULL)) { + DEBUG_LOG("Not recognized as RTP/RTCP"); + return kResultSkip; + } + + uint32_t ssrc = marker.rtp_header.ssrc; + if (ssrc_filter.empty() || ssrc_filter.find(ssrc) != ssrc_filter.end()) { + packets_by_ssrc_[ssrc].push_back( + static_cast<uint32_t>(packets_.size())); + packets_.push_back(marker); + } else { + return kResultSkip; + } + } + + return kResultSuccess; + } + + int ReadPacketHeader(RtpPacketMarker* marker) { + int32_t file_pos = ftell(file_); + + // Check for BSD null/loopback frame header. The header is just 4 bytes in + // native byte order, so we check for both versions as we don't care about + // the header as such and will likely fail reading the IP header if this is + // something else than null/loopback. + uint32_t protocol; + TRY_PCAP(Read(&protocol, true)); + if (protocol == kBsdNullLoopback1 || protocol == kBsdNullLoopback2) { + int result = ReadXxpIpHeader(marker); + DEBUG_LOG("Recognized loopback frame"); + if (result != kResultSkip) { + return result; + } + } + + TRY_PCAP(fseek(file_, file_pos, SEEK_SET)); + + // Check for Ethernet II, IP frame header. + uint16_t type; + TRY_PCAP(Skip(kEthernetIIHeaderMacSkip)); // Source+destination MAC. + TRY_PCAP(Read(&type, true)); + if (type == kEthertypeIp) { + int result = ReadXxpIpHeader(marker); + DEBUG_LOG("Recognized ethernet 2 frame"); + if (result != kResultSkip) { + return result; + } + } + + return kResultSkip; + } + + uint32_t CalcTimeDelta(uint32_t ts_sec, uint32_t ts_usec, uint32_t start_ms) { + // Round to nearest ms. + uint64_t t2_ms = ((static_cast<uint64_t>(ts_sec) * 1000000) + ts_usec + + 500) / 1000; + uint64_t t1_ms = static_cast<uint64_t>(start_ms); + if (t2_ms < t1_ms) { + return 0; + } else { + return t2_ms - t1_ms; + } + } + + int ReadXxpIpHeader(RtpPacketMarker* marker) { + assert(marker); + + uint16_t version; + uint16_t length; + uint16_t id; + uint16_t fragment; + uint16_t protocol; + uint16_t checksum; + TRY_PCAP(Read(&version, true)); + TRY_PCAP(Read(&length, true)); + TRY_PCAP(Read(&id, true)); + TRY_PCAP(Read(&fragment, true)); + TRY_PCAP(Read(&protocol, true)); + TRY_PCAP(Read(&checksum, true)); + TRY_PCAP(Read(&marker->source_ip, true)); + TRY_PCAP(Read(&marker->dest_ip, true)); + + if (((version >> 12) & 0x000f) != kIpVersion4) { + DEBUG_LOG("IP header is not IPv4"); + return kResultSkip; + } + + if (fragment != kFragmentOffsetClear && + fragment != kFragmentOffsetDoNotFragment) { + DEBUG_LOG("IP fragments cannot be handled"); + return kResultSkip; + } + + // Skip remaining fields of IP header. + uint16_t header_length = (version & 0x0f00) >> (8 - 2); + assert(header_length >= kMinIpHeaderLength); + TRY_PCAP(Skip(header_length - kMinIpHeaderLength)); + + protocol = protocol & 0x00ff; + if (protocol == kProtocolTcp) { + DEBUG_LOG("TCP packets are not handled"); + return kResultSkip; + } else if (protocol == kProtocolUdp) { + uint16_t length; + uint16_t checksum; + TRY_PCAP(Read(&marker->source_port, true)); + TRY_PCAP(Read(&marker->dest_port, true)); + TRY_PCAP(Read(&length, true)); + TRY_PCAP(Read(&checksum, true)); + marker->payload_length = length - kUdpHeaderLength; + } else { + DEBUG_LOG("Unknown transport (expected UDP or TCP)"); + return kResultSkip; + } + + return kResultSuccess; + } + + int Read(uint32_t* out, bool expect_network_order) { + uint32_t tmp = 0; + if (fread(&tmp, 1, sizeof(uint32_t), file_) != sizeof(uint32_t)) { + return kResultFail; + } + if ((!expect_network_order && swap_pcap_byte_order_) || + (expect_network_order && swap_network_byte_order_)) { + tmp = ((tmp >> 24) & 0x000000ff) | (tmp << 24) | + ((tmp >> 8) & 0x0000ff00) | ((tmp << 8) & 0x00ff0000); + } + *out = tmp; + return kResultSuccess; + } + + int Read(uint16_t* out, bool expect_network_order) { + uint16_t tmp = 0; + if (fread(&tmp, 1, sizeof(uint16_t), file_) != sizeof(uint16_t)) { + return kResultFail; + } + if ((!expect_network_order && swap_pcap_byte_order_) || + (expect_network_order && swap_network_byte_order_)) { + tmp = ((tmp >> 8) & 0x00ff) | (tmp << 8); + } + *out = tmp; + return kResultSuccess; + } + + int Read(uint8_t* out, uint32_t count) { + if (fread(out, 1, count, file_) != count) { + return kResultFail; + } + return kResultSuccess; + } + + int Read(int32_t* out, bool expect_network_order) { + int32_t tmp = 0; + if (fread(&tmp, 1, sizeof(uint32_t), file_) != sizeof(uint32_t)) { + return kResultFail; + } + if ((!expect_network_order && swap_pcap_byte_order_) || + (expect_network_order && swap_network_byte_order_)) { + tmp = ((tmp >> 24) & 0x000000ff) | (tmp << 24) | + ((tmp >> 8) & 0x0000ff00) | ((tmp << 8) & 0x00ff0000); + } + *out = tmp; + return kResultSuccess; + } + + int Skip(uint32_t length) { + if (fseek(file_, length, SEEK_CUR) != 0) { + return kResultFail; + } + return kResultSuccess; + } + + FILE* file_; + bool swap_pcap_byte_order_; + const bool swap_network_byte_order_; + uint8_t read_buffer_[kMaxReadBufferSize]; + + SsrcMap packets_by_ssrc_; + std::vector<RtpPacketMarker> packets_; + PacketIterator next_packet_it_; + + RTC_DISALLOW_COPY_AND_ASSIGN(PcapReader); +}; + +RtpFileReader* RtpFileReader::Create(FileFormat format, + const std::string& filename, + const std::set<uint32_t>& ssrc_filter) { + RtpFileReaderImpl* reader = NULL; + switch (format) { + case kPcap: + reader = new PcapReader(); + break; + case kRtpDump: + reader = new RtpDumpReader(); + break; + case kLengthPacketInterleaved: + reader = new InterleavedRtpFileReader(); + break; + } + if (!reader->Init(filename, ssrc_filter)) { + delete reader; + return NULL; + } + return reader; +} + +RtpFileReader* RtpFileReader::Create(FileFormat format, + const std::string& filename) { + return RtpFileReader::Create(format, filename, std::set<uint32_t>()); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/rtp_file_reader.h b/webrtc/test/rtp_file_reader.h new file mode 100644 index 0000000000..c132d318fd --- /dev/null +++ b/webrtc/test/rtp_file_reader.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014 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_TEST_RTP_FILE_READER_H_ +#define WEBRTC_TEST_RTP_FILE_READER_H_ + +#include <set> +#include <string> + +#include "webrtc/common_types.h" + +namespace webrtc { +namespace test { + +struct RtpPacket { + // Accommodate for 50 ms packets of 32 kHz PCM16 samples (3200 bytes) plus + // some overhead. + static const size_t kMaxPacketBufferSize = 3500; + uint8_t data[kMaxPacketBufferSize]; + size_t length; + // The length the packet had on wire. Will be different from |length| when + // reading a header-only RTP dump. + size_t original_length; + + uint32_t time_ms; +}; + +class RtpFileReader { + public: + enum FileFormat { kPcap, kRtpDump, kLengthPacketInterleaved }; + + virtual ~RtpFileReader() {} + static RtpFileReader* Create(FileFormat format, + const std::string& filename); + static RtpFileReader* Create(FileFormat format, + const std::string& filename, + const std::set<uint32_t>& ssrc_filter); + + virtual bool NextPacket(RtpPacket* packet) = 0; +}; +} // namespace test +} // namespace webrtc +#endif // WEBRTC_TEST_RTP_FILE_READER_H_ diff --git a/webrtc/test/rtp_file_reader_unittest.cc b/webrtc/test/rtp_file_reader_unittest.cc new file mode 100644 index 0000000000..929813f999 --- /dev/null +++ b/webrtc/test/rtp_file_reader_unittest.cc @@ -0,0 +1,125 @@ +/* + * 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 <map> + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" +#include "webrtc/test/rtp_file_reader.h" +#include "webrtc/test/testsupport/fileutils.h" + +namespace webrtc { + +class TestRtpFileReader : public ::testing::Test { + public: + void Init(const std::string& filename, bool headers_only_file) { + std::string filepath = + test::ResourcePath("video_coding/" + filename, "rtp"); + rtp_packet_source_.reset( + test::RtpFileReader::Create(test::RtpFileReader::kRtpDump, filepath)); + ASSERT_TRUE(rtp_packet_source_.get() != NULL); + headers_only_file_ = headers_only_file; + } + + int CountRtpPackets() { + test::RtpPacket packet; + int c = 0; + while (rtp_packet_source_->NextPacket(&packet)) { + if (headers_only_file_) + EXPECT_LT(packet.length, packet.original_length); + else + EXPECT_EQ(packet.length, packet.original_length); + c++; + } + return c; + } + + private: + rtc::scoped_ptr<test::RtpFileReader> rtp_packet_source_; + bool headers_only_file_; +}; + +TEST_F(TestRtpFileReader, Test60Packets) { + Init("pltype103", false); + EXPECT_EQ(60, CountRtpPackets()); +} + +TEST_F(TestRtpFileReader, Test60PacketsHeaderOnly) { + Init("pltype103_header_only", true); + EXPECT_EQ(60, CountRtpPackets()); +} + +typedef std::map<uint32_t, int> PacketsPerSsrc; + +class TestPcapFileReader : public ::testing::Test { + public: + void Init(const std::string& filename) { + std::string filepath = + test::ResourcePath("video_coding/" + filename, "pcap"); + rtp_packet_source_.reset( + test::RtpFileReader::Create(test::RtpFileReader::kPcap, filepath)); + ASSERT_TRUE(rtp_packet_source_.get() != NULL); + } + + int CountRtpPackets() { + int c = 0; + test::RtpPacket packet; + while (rtp_packet_source_->NextPacket(&packet)) { + EXPECT_EQ(packet.length, packet.original_length); + c++; + } + return c; + } + + PacketsPerSsrc CountRtpPacketsPerSsrc() { + PacketsPerSsrc pps; + test::RtpPacket packet; + while (rtp_packet_source_->NextPacket(&packet)) { + RtpUtility::RtpHeaderParser rtp_header_parser(packet.data, packet.length); + webrtc::RTPHeader header; + if (!rtp_header_parser.RTCP() && rtp_header_parser.Parse(header, NULL)) { + pps[header.ssrc]++; + } + } + return pps; + } + + private: + rtc::scoped_ptr<test::RtpFileReader> rtp_packet_source_; +}; + +TEST_F(TestPcapFileReader, TestEthernetIIFrame) { + Init("frame-ethernet-ii"); + EXPECT_EQ(368, CountRtpPackets()); +} + +TEST_F(TestPcapFileReader, TestLoopbackFrame) { + Init("frame-loopback"); + EXPECT_EQ(491, CountRtpPackets()); +} + +TEST_F(TestPcapFileReader, TestTwoSsrc) { + Init("ssrcs-2"); + PacketsPerSsrc pps = CountRtpPacketsPerSsrc(); + EXPECT_EQ(2UL, pps.size()); + EXPECT_EQ(370, pps[0x78d48f61]); + EXPECT_EQ(60, pps[0xae94130b]); +} + +TEST_F(TestPcapFileReader, TestThreeSsrc) { + Init("ssrcs-3"); + PacketsPerSsrc pps = CountRtpPacketsPerSsrc(); + EXPECT_EQ(3UL, pps.size()); + EXPECT_EQ(162, pps[0x938c5eaa]); + EXPECT_EQ(113, pps[0x59fe6ef0]); + EXPECT_EQ(61, pps[0xed2bd2ac]); +} +} // namespace webrtc diff --git a/webrtc/test/rtp_file_writer.cc b/webrtc/test/rtp_file_writer.cc new file mode 100644 index 0000000000..d9e0586468 --- /dev/null +++ b/webrtc/test/rtp_file_writer.cc @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2014 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/rtp_file_writer.h" + +#include <stdio.h> + +#include <string> + +#include "webrtc/base/checks.h" +#include "webrtc/base/constructormagic.h" + +namespace webrtc { +namespace test { + +static const uint16_t kPacketHeaderSize = 8; +static const char kFirstLine[] = "#!rtpplay1.0 0.0.0.0/0\n"; + +// Write RTP packets to file in rtpdump format, as documented at: +// http://www.cs.columbia.edu/irt/software/rtptools/ +class RtpDumpWriter : public RtpFileWriter { + public: + explicit RtpDumpWriter(FILE* file) : file_(file) { + RTC_CHECK(file_ != NULL); + Init(); + } + virtual ~RtpDumpWriter() { + if (file_ != NULL) { + fclose(file_); + file_ = NULL; + } + } + + bool WritePacket(const RtpPacket* packet) override { + uint16_t len = static_cast<uint16_t>(packet->length + kPacketHeaderSize); + uint16_t plen = static_cast<uint16_t>(packet->original_length); + uint32_t offset = packet->time_ms; + RTC_CHECK(WriteUint16(len)); + RTC_CHECK(WriteUint16(plen)); + RTC_CHECK(WriteUint32(offset)); + return fwrite(packet->data, sizeof(uint8_t), packet->length, file_) == + packet->length; + } + + private: + bool Init() { + fprintf(file_, "%s", kFirstLine); + + RTC_CHECK(WriteUint32(0)); + RTC_CHECK(WriteUint32(0)); + RTC_CHECK(WriteUint32(0)); + RTC_CHECK(WriteUint16(0)); + RTC_CHECK(WriteUint16(0)); + + return true; + } + + bool WriteUint32(uint32_t in) { + // Loop through shifts = {24, 16, 8, 0}. + for (int shifts = 24; shifts >= 0; shifts -= 8) { + uint8_t tmp = static_cast<uint8_t>((in >> shifts) & 0xFF); + if (fwrite(&tmp, sizeof(uint8_t), 1, file_) != 1) + return false; + } + return true; + } + + bool WriteUint16(uint16_t in) { + // Write 8 MSBs. + uint8_t tmp = static_cast<uint8_t>((in >> 8) & 0xFF); + if (fwrite(&tmp, sizeof(uint8_t), 1, file_) != 1) + return false; + // Write 8 LSBs. + tmp = static_cast<uint8_t>(in & 0xFF); + if (fwrite(&tmp, sizeof(uint8_t), 1, file_) != 1) + return false; + return true; + } + + FILE* file_; + + RTC_DISALLOW_COPY_AND_ASSIGN(RtpDumpWriter); +}; + +RtpFileWriter* RtpFileWriter::Create(FileFormat format, + const std::string& filename) { + FILE* file = fopen(filename.c_str(), "wb"); + if (file == NULL) { + printf("ERROR: Can't open file: %s\n", filename.c_str()); + return NULL; + } + switch (format) { + case kRtpDump: + return new RtpDumpWriter(file); + } + fclose(file); + return NULL; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/rtp_file_writer.h b/webrtc/test/rtp_file_writer.h new file mode 100644 index 0000000000..453b27762e --- /dev/null +++ b/webrtc/test/rtp_file_writer.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2014 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_TEST_RTP_FILE_WRITER_H_ +#define WEBRTC_TEST_RTP_FILE_WRITER_H_ + +#include <string> + +#include "webrtc/common_types.h" +#include "webrtc/test/rtp_file_reader.h" + +namespace webrtc { +namespace test { +class RtpFileWriter { + public: + enum FileFormat { + kRtpDump, + }; + + virtual ~RtpFileWriter() {} + static RtpFileWriter* Create(FileFormat format, const std::string& filename); + + virtual bool WritePacket(const RtpPacket* packet) = 0; +}; +} // namespace test +} // namespace webrtc +#endif // WEBRTC_TEST_RTP_FILE_WRITER_H_ diff --git a/webrtc/test/rtp_file_writer_unittest.cc b/webrtc/test/rtp_file_writer_unittest.cc new file mode 100644 index 0000000000..2c7c88cc3f --- /dev/null +++ b/webrtc/test/rtp_file_writer_unittest.cc @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2014 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 <string.h> + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/test/rtp_file_reader.h" +#include "webrtc/test/rtp_file_writer.h" +#include "webrtc/test/testsupport/fileutils.h" + +namespace webrtc { + +class RtpFileWriterTest : public ::testing::Test { + public: + void Init(const std::string& filename) { + filename_ = test::OutputPath() + filename; + rtp_writer_.reset( + test::RtpFileWriter::Create(test::RtpFileWriter::kRtpDump, filename_)); + } + + void WriteRtpPackets(int num_packets) { + ASSERT_TRUE(rtp_writer_.get() != NULL); + test::RtpPacket packet; + for (int i = 1; i <= num_packets; ++i) { + packet.length = i; + packet.original_length = i; + packet.time_ms = i; + memset(packet.data, i, packet.length); + EXPECT_TRUE(rtp_writer_->WritePacket(&packet)); + } + } + + void CloseOutputFile() { rtp_writer_.reset(); } + + void VerifyFileContents(int expected_packets) { + ASSERT_TRUE(rtp_writer_.get() == NULL) + << "Must call CloseOutputFile before VerifyFileContents"; + rtc::scoped_ptr<test::RtpFileReader> rtp_reader( + test::RtpFileReader::Create(test::RtpFileReader::kRtpDump, filename_)); + ASSERT_TRUE(rtp_reader.get() != NULL); + test::RtpPacket packet; + int i = 0; + while (rtp_reader->NextPacket(&packet)) { + ++i; + EXPECT_EQ(static_cast<size_t>(i), packet.length); + EXPECT_EQ(static_cast<size_t>(i), packet.original_length); + EXPECT_EQ(static_cast<uint32_t>(i), packet.time_ms); + for (int j = 0; j < i; ++j) { + EXPECT_EQ(i, packet.data[j]); + } + } + EXPECT_EQ(expected_packets, i); + } + + private: + rtc::scoped_ptr<test::RtpFileWriter> rtp_writer_; + std::string filename_; +}; + +TEST_F(RtpFileWriterTest, WriteToRtpDump) { + Init("test_rtp_file_writer.rtp"); + WriteRtpPackets(10); + CloseOutputFile(); + VerifyFileContents(10); +} + +} // namespace webrtc diff --git a/webrtc/test/rtp_rtcp_observer.h b/webrtc/test/rtp_rtcp_observer.h new file mode 100644 index 0000000000..89b6dd06bd --- /dev/null +++ b/webrtc/test/rtp_rtcp_observer.h @@ -0,0 +1,141 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_RTP_RTCP_OBSERVER_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_RTP_RTCP_OBSERVER_H_ + +#include <map> +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" + +#include "webrtc/base/criticalsection.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h" +#include "webrtc/test/constants.h" +#include "webrtc/test/direct_transport.h" +#include "webrtc/typedefs.h" +#include "webrtc/video_send_stream.h" + +namespace webrtc { +namespace test { + +class PacketTransport; + +class RtpRtcpObserver { + public: + enum Action { + SEND_PACKET, + DROP_PACKET, + }; + + virtual ~RtpRtcpObserver() {} + + virtual EventTypeWrapper Wait() { + EventTypeWrapper result = observation_complete_->Wait(timeout_ms_); + return result; + } + + virtual Action OnSendRtp(const uint8_t* packet, size_t length) { + return SEND_PACKET; + } + + virtual Action OnSendRtcp(const uint8_t* packet, size_t length) { + return SEND_PACKET; + } + + virtual Action OnReceiveRtp(const uint8_t* packet, size_t length) { + return SEND_PACKET; + } + + virtual Action OnReceiveRtcp(const uint8_t* packet, size_t length) { + return SEND_PACKET; + } + + protected: + explicit RtpRtcpObserver(unsigned int event_timeout_ms) + : observation_complete_(EventWrapper::Create()), + parser_(RtpHeaderParser::Create()), + timeout_ms_(event_timeout_ms) { + parser_->RegisterRtpHeaderExtension(kRtpExtensionTransmissionTimeOffset, + kTOffsetExtensionId); + parser_->RegisterRtpHeaderExtension(kRtpExtensionAbsoluteSendTime, + kAbsSendTimeExtensionId); + parser_->RegisterRtpHeaderExtension(kRtpExtensionTransportSequenceNumber, + kTransportSequenceNumberExtensionId); + } + + const rtc::scoped_ptr<EventWrapper> observation_complete_; + const rtc::scoped_ptr<RtpHeaderParser> parser_; + + private: + unsigned int timeout_ms_; +}; + +class PacketTransport : public test::DirectTransport { + public: + enum TransportType { kReceiver, kSender }; + + PacketTransport(Call* send_call, + RtpRtcpObserver* observer, + TransportType transport_type, + const FakeNetworkPipe::Config& configuration) + : test::DirectTransport(configuration, send_call), + observer_(observer), + transport_type_(transport_type) {} + + private: + bool SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) override { + EXPECT_FALSE(RtpHeaderParser::IsRtcp(packet, length)); + RtpRtcpObserver::Action action; + { + if (transport_type_ == kSender) { + action = observer_->OnSendRtp(packet, length); + } else { + action = observer_->OnReceiveRtp(packet, length); + } + } + switch (action) { + case RtpRtcpObserver::DROP_PACKET: + // Drop packet silently. + return true; + case RtpRtcpObserver::SEND_PACKET: + return test::DirectTransport::SendRtp(packet, length, options); + } + return true; // Will never happen, makes compiler happy. + } + + bool SendRtcp(const uint8_t* packet, size_t length) override { + EXPECT_TRUE(RtpHeaderParser::IsRtcp(packet, length)); + RtpRtcpObserver::Action action; + { + if (transport_type_ == kSender) { + action = observer_->OnSendRtcp(packet, length); + } else { + action = observer_->OnReceiveRtcp(packet, length); + } + } + switch (action) { + case RtpRtcpObserver::DROP_PACKET: + // Drop packet silently. + return true; + case RtpRtcpObserver::SEND_PACKET: + return test::DirectTransport::SendRtcp(packet, length); + } + return true; // Will never happen, makes compiler happy. + } + + RtpRtcpObserver* const observer_; + TransportType transport_type_; +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_RTP_RTCP_OBSERVER_H_ diff --git a/webrtc/test/run_all_unittests.cc b/webrtc/test/run_all_unittests.cc new file mode 100644 index 0000000000..486df5ea8c --- /dev/null +++ b/webrtc/test/run_all_unittests.cc @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2011 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/test_suite.h" + +int main(int argc, char** argv) { + webrtc::test::TestSuite test_suite(argc, argv); + return test_suite.Run(); +} diff --git a/webrtc/test/run_loop.cc b/webrtc/test/run_loop.cc new file mode 100644 index 0000000000..92f85dd09d --- /dev/null +++ b/webrtc/test/run_loop.cc @@ -0,0 +1,22 @@ +/* + * 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/test/run_loop.h" + +#include <stdio.h> + +namespace webrtc { +namespace test { + +void PressEnterToContinue() { + puts(">> Press ENTER to continue..."); + while (getc(stdin) != '\n' && !feof(stdin)); +} +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/run_loop.h b/webrtc/test/run_loop.h new file mode 100644 index 0000000000..31012525e2 --- /dev/null +++ b/webrtc/test/run_loop.h @@ -0,0 +1,22 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_RUN_LOOP_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_RUN_LOOP_H_ + +namespace webrtc { +namespace test { + +// Blocks until the user presses enter. +void PressEnterToContinue(); + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_RUN_LOOP_H_ diff --git a/webrtc/test/run_test.cc b/webrtc/test/run_test.cc new file mode 100644 index 0000000000..4daea42a1e --- /dev/null +++ b/webrtc/test/run_test.cc @@ -0,0 +1,23 @@ +/* + * 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/test/run_test.h" + +#include <stdio.h> + +namespace webrtc { +namespace test { + +void RunTest(void(*test)()) { + (*test)(); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/run_test.h b/webrtc/test/run_test.h new file mode 100644 index 0000000000..b515254685 --- /dev/null +++ b/webrtc/test/run_test.h @@ -0,0 +1,22 @@ +/* + * 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_TEST_RUN_TEST_H +#define WEBRTC_TEST_RUN_TEST_H + +namespace webrtc { +namespace test { + +// Running a test function on a separate thread, if required by the OS. +void RunTest(void(*test)()); + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_RUN_TEST_H diff --git a/webrtc/test/statistics.cc b/webrtc/test/statistics.cc new file mode 100644 index 0000000000..0075d4c9a3 --- /dev/null +++ b/webrtc/test/statistics.cc @@ -0,0 +1,41 @@ +/* + * 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/test/statistics.h" + +#include <math.h> + +namespace webrtc { +namespace test { + +Statistics::Statistics() : sum_(0.0), sum_squared_(0.0), count_(0) {} + +void Statistics::AddSample(double sample) { + sum_ += sample; + sum_squared_ += sample * sample; + ++count_; +} + +double Statistics::Mean() const { + if (count_ == 0) + return 0.0; + return sum_ / count_; +} + +double Statistics::Variance() const { + if (count_ == 0) + return 0.0; + return sum_squared_ / count_ - Mean() * Mean(); +} + +double Statistics::StandardDeviation() const { + return sqrt(Variance()); +} +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/statistics.h b/webrtc/test/statistics.h new file mode 100644 index 0000000000..0fc3a04ea9 --- /dev/null +++ b/webrtc/test/statistics.h @@ -0,0 +1,36 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_STATISTICS_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_STATISTICS_H_ + +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +class Statistics { + public: + Statistics(); + + void AddSample(double sample); + + double Mean() const; + double Variance() const; + double StandardDeviation() const; + + private: + double sum_; + double sum_squared_; + uint64_t count_; +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_STATISTICS_H_ diff --git a/webrtc/test/test.gyp b/webrtc/test/test.gyp new file mode 100644 index 0000000000..0d251dffdd --- /dev/null +++ b/webrtc/test/test.gyp @@ -0,0 +1,251 @@ +# Copyright (c) 2011 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. + +# TODO(andrew): consider moving test_support to src/base/test. +{ + 'includes': [ + '../build/common.gypi', + ], + 'targets': [ + { + 'target_name': 'channel_transport', + 'type': 'static_library', + 'dependencies': [ + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(webrtc_root)/common.gyp:webrtc_common', + '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers', + ], + 'sources': [ + 'channel_transport/channel_transport.cc', + 'channel_transport/include/channel_transport.h', + 'channel_transport/traffic_control_win.cc', + 'channel_transport/traffic_control_win.h', + 'channel_transport/udp_socket_manager_posix.cc', + 'channel_transport/udp_socket_manager_posix.h', + 'channel_transport/udp_socket_manager_wrapper.cc', + 'channel_transport/udp_socket_manager_wrapper.h', + 'channel_transport/udp_socket_posix.cc', + 'channel_transport/udp_socket_posix.h', + 'channel_transport/udp_socket_wrapper.cc', + 'channel_transport/udp_socket_wrapper.h', + 'channel_transport/udp_socket2_manager_win.cc', + 'channel_transport/udp_socket2_manager_win.h', + 'channel_transport/udp_socket2_win.cc', + 'channel_transport/udp_socket2_win.h', + 'channel_transport/udp_transport.h', + 'channel_transport/udp_transport_impl.cc', + 'channel_transport/udp_transport_impl.h', + ], + }, + { + 'target_name': 'frame_generator', + 'type': 'static_library', + 'sources': [ + 'frame_generator.cc', + 'frame_generator.h', + ], + 'dependencies': [ + '<(webrtc_root)/common_video/common_video.gyp:common_video', + ], + }, + { + 'target_name': 'rtp_test_utils', + 'type': 'static_library', + 'sources': [ + 'rtcp_packet_parser.cc', + 'rtcp_packet_parser.h', + 'rtp_file_reader.cc', + 'rtp_file_reader.h', + 'rtp_file_writer.cc', + 'rtp_file_writer.h', + ], + 'dependencies': [ + '<(DEPTH)/webrtc/common.gyp:webrtc_common', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(webrtc_root)/modules/modules.gyp:rtp_rtcp', + ], + }, + { + 'target_name': 'field_trial', + 'type': 'static_library', + 'sources': [ + 'field_trial.cc', + 'field_trial.h', + ], + 'dependencies': [ + '<(webrtc_root)/common.gyp:webrtc_common', + '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers', + ], + }, + { + 'target_name': 'histogram', + 'type': 'static_library', + 'sources': [ + 'histogram.cc', + 'histogram.h', + ], + 'dependencies': [ + '<(webrtc_root)/common.gyp:webrtc_common', + '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers', + ], + }, + { + 'target_name': 'test_main', + 'type': 'static_library', + 'sources': [ + 'test_main.cc', + ], + 'dependencies': [ + 'field_trial', + 'histogram', + 'test_support', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', + ], + }, + { + 'target_name': 'test_support', + 'type': 'static_library', + 'dependencies': [ + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/testing/gmock.gyp:gmock', + '<(webrtc_root)/common.gyp:gtest_prod', + '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers', + ], + 'sources': [ + 'testsupport/fileutils.cc', + 'testsupport/fileutils.h', + 'testsupport/frame_reader.cc', + 'testsupport/frame_reader.h', + 'testsupport/frame_writer.cc', + 'testsupport/frame_writer.h', + 'testsupport/gtest_disable.h', + 'testsupport/iosfileutils.mm', + 'testsupport/mock/mock_frame_reader.h', + 'testsupport/mock/mock_frame_writer.h', + 'testsupport/packet_reader.cc', + 'testsupport/packet_reader.h', + 'testsupport/perf_test.cc', + 'testsupport/perf_test.h', + 'testsupport/trace_to_stderr.cc', + 'testsupport/trace_to_stderr.h', + ], + 'conditions': [ + ['OS=="ios"', { + 'xcode_settings': { + 'CLANG_ENABLE_OBJC_ARC': 'YES', + }, + }], + ['use_x11==1', { + 'dependencies': [ + '<(DEPTH)/tools/xdisplaycheck/xdisplaycheck.gyp:xdisplaycheck', + ], + }], + ], + }, + { + # Depend on this target when you want to have test_support but also the + # main method needed for gtest to execute! + 'target_name': 'test_support_main', + 'type': 'static_library', + 'dependencies': [ + 'field_trial', + 'histogram', + 'test_support', + '<(DEPTH)/testing/gmock.gyp:gmock', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', + ], + 'sources': [ + 'run_all_unittests.cc', + 'test_suite.cc', + 'test_suite.h', + ], + }, + { + # Depend on this target when you want to have test_support and a special + # main for mac which will run your test on a worker thread and consume + # events on the main thread. Useful if you want to access a webcam. + # This main will provide all the scaffolding and objective-c black magic + # for you. All you need to do is to implement a function in the + # run_threaded_main_mac.h file (ImplementThisToRunYourTest). + 'target_name': 'test_support_main_threaded_mac', + 'type': 'static_library', + 'dependencies': [ + 'test_support', + ], + 'sources': [ + 'testsupport/mac/run_threaded_main_mac.h', + 'testsupport/mac/run_threaded_main_mac.mm', + ], + }, + { + 'target_name': 'test_support_unittests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + 'channel_transport', + 'test_support_main', + '<(DEPTH)/testing/gmock.gyp:gmock', + '<(DEPTH)/testing/gtest.gyp:gtest', + ], + 'sources': [ + 'channel_transport/udp_transport_unittest.cc', + 'channel_transport/udp_socket_manager_unittest.cc', + 'channel_transport/udp_socket_wrapper_unittest.cc', + 'testsupport/always_passing_unittest.cc', + 'testsupport/unittest_utils.h', + 'testsupport/fileutils_unittest.cc', + 'testsupport/frame_reader_unittest.cc', + 'testsupport/frame_writer_unittest.cc', + 'testsupport/packet_reader_unittest.cc', + 'testsupport/perf_test_unittest.cc', + ], + # Disable warnings to enable Win64 build, issue 1323. + 'msvs_disabled_warnings': [ + 4267, # size_t to int truncation. + ], + 'conditions': [ + ['OS=="android"', { + 'dependencies': [ + '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', + ], + }], + ], + }, + ], + 'conditions': [ + ['include_tests==1 and OS=="android"', { + 'targets': [ + { + 'target_name': 'test_support_unittests_apk_target', + 'type': 'none', + 'dependencies': [ + '<(apk_tests_path):test_support_unittests_apk', + ], + }, + ], + }], + ['test_isolation_mode != "noop"', { + 'targets': [ + { + 'target_name': 'test_support_unittests_run', + 'type': 'none', + 'dependencies': [ + 'test_support_unittests', + ], + 'includes': [ + '../build/isolate.gypi', + ], + 'sources': [ + 'test_support_unittests.isolate', + ], + }, + ], + }], + ], +} diff --git a/webrtc/test/test_main.cc b/webrtc/test/test_main.cc new file mode 100644 index 0000000000..733831f5be --- /dev/null +++ b/webrtc/test/test_main.cc @@ -0,0 +1,32 @@ +/* + * 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 "gflags/gflags.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/test/field_trial.h" +#include "webrtc/test/testsupport/fileutils.h" + +DEFINE_string(force_fieldtrials, "", + "Field trials control experimental feature code which can be forced. " + "E.g. running with --force_fieldtrials=WebRTC-FooFeature/Enable/" + " will assign the group Enable to field trial WebRTC-FooFeature."); + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + + // AllowCommandLineParsing allows us to ignore flags passed on to us by + // Chromium build bots without having to explicitly disable them. + google::AllowCommandLineReparsing(); + google::ParseCommandLineFlags(&argc, &argv, false); + + webrtc::test::SetExecutablePath(argv[0]); + webrtc::test::InitFieldTrialsFromString(FLAGS_force_fieldtrials); + return RUN_ALL_TESTS(); +} diff --git a/webrtc/test/test_suite.cc b/webrtc/test/test_suite.cc new file mode 100644 index 0000000000..2900f0eddb --- /dev/null +++ b/webrtc/test/test_suite.cc @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2011 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/test_suite.h" + +#include "gflags/gflags.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/logging.h" +#include "webrtc/test/testsupport/fileutils.h" +#include "webrtc/test/testsupport/trace_to_stderr.h" +#include "webrtc/test/field_trial.h" + +DEFINE_bool(logs, false, "print logs to stderr"); + +DEFINE_string(force_fieldtrials, "", + "Field trials control experimental feature code which can be forced. " + "E.g. running with --force_fieldtrials=WebRTC-FooFeature/Enable/" + " will assign the group Enable to field trial WebRTC-FooFeature."); + +namespace webrtc { +namespace test { + +TestSuite::TestSuite(int argc, char** argv) { + SetExecutablePath(argv[0]); + testing::InitGoogleMock(&argc, argv); // Runs InitGoogleTest() internally. + // AllowCommandLineParsing allows us to ignore flags passed on to us by + // Chromium build bots without having to explicitly disable them. + google::AllowCommandLineReparsing(); + google::ParseCommandLineFlags(&argc, &argv, true); + + webrtc::test::InitFieldTrialsFromString(FLAGS_force_fieldtrials); +} + +TestSuite::~TestSuite() { +} + +int TestSuite::Run() { + Initialize(); + int result = RUN_ALL_TESTS(); + Shutdown(); + return result; +} + +void TestSuite::Initialize() { + rtc::LogMessage::SetLogToStderr(FLAGS_logs); + if (FLAGS_logs) + trace_to_stderr_.reset(new TraceToStderr); +} + +void TestSuite::Shutdown() { +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/test_suite.h b/webrtc/test/test_suite.h new file mode 100644 index 0000000000..dab2acd388 --- /dev/null +++ b/webrtc/test/test_suite.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2011 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_TEST_TEST_SUITE_H_ +#define WEBRTC_TEST_TEST_SUITE_H_ + +// Derived from Chromium's src/base/test/test_suite.h. + +// Defines a basic test suite framework for running gtest based tests. You can +// instantiate this class in your main function and call its Run method to run +// any gtest based tests that are linked into your executable. + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ptr.h" + +namespace webrtc { +namespace test { + +class TraceToStderr; + +class TestSuite { + public: + TestSuite(int argc, char** argv); + virtual ~TestSuite(); + + int Run(); + + protected: + // Override these for custom initialization and shutdown handling. Use these + // instead of putting complex code in your constructor/destructor. + virtual void Initialize(); + virtual void Shutdown(); + + RTC_DISALLOW_COPY_AND_ASSIGN(TestSuite); + + private: + rtc::scoped_ptr<TraceToStderr> trace_to_stderr_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_TEST_SUITE_H_ diff --git a/webrtc/test/test_support_unittests.isolate b/webrtc/test/test_support_unittests.isolate new file mode 100644 index 0000000000..3fd89d602e --- /dev/null +++ b/webrtc/test/test_support_unittests.isolate @@ -0,0 +1,24 @@ +# 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. +{ + 'conditions': [ + ['OS=="linux" or OS=="mac" or OS=="win"', { + 'variables': { + 'command': [ + '<(DEPTH)/testing/test_env.py', + '<(PRODUCT_DIR)/test_support_unittests<(EXECUTABLE_SUFFIX)', + ], + 'files': [ + '<(DEPTH)/DEPS', + '<(DEPTH)/testing/test_env.py', + '<(PRODUCT_DIR)/test_support_unittests<(EXECUTABLE_SUFFIX)', + ], + }, + }], + ], +} diff --git a/webrtc/test/testsupport/always_passing_unittest.cc b/webrtc/test/testsupport/always_passing_unittest.cc new file mode 100644 index 0000000000..afb80e662c --- /dev/null +++ b/webrtc/test/testsupport/always_passing_unittest.cc @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014 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 "testing/gtest/include/gtest/gtest.h" + +namespace webrtc { + +// A test that always passes. Useful when all tests in a executable are +// disabled, since a gtest returns exit code 1 if no tests have executed. +TEST(AlwaysPassingTest, AlwaysPassingTest) {} + +} // namespace webrtc diff --git a/webrtc/test/testsupport/fileutils.cc b/webrtc/test/testsupport/fileutils.cc new file mode 100644 index 0000000000..15abf5c517 --- /dev/null +++ b/webrtc/test/testsupport/fileutils.cc @@ -0,0 +1,268 @@ +/* + * 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/testsupport/fileutils.h" + +#include <assert.h> + +#ifdef WIN32 +#include <direct.h> +#include <tchar.h> +#include <windows.h> +#include <algorithm> + +#include "webrtc/system_wrappers/include/utf_util_win.h" +#define GET_CURRENT_DIR _getcwd +#else +#include <unistd.h> + +#include "webrtc/base/scoped_ptr.h" +#define GET_CURRENT_DIR getcwd +#endif + +#include <sys/stat.h> // To check for directory existence. +#ifndef S_ISDIR // Not defined in stat.h on Windows. +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "webrtc/typedefs.h" // For architecture defines + +namespace webrtc { +namespace test { + +#if defined(WEBRTC_IOS) +// Defined in iosfileutils.mm. No header file to discourage use elsewhere. +std::string IOSResourcePath(std::string name, std::string extension); +#endif + +namespace { + +#ifdef WIN32 +const char* kPathDelimiter = "\\"; +#else +const char* kPathDelimiter = "/"; +#endif + +#ifdef WEBRTC_ANDROID +const char* kRootDirName = "/sdcard/"; +#else +// The file we're looking for to identify the project root dir. +const char* kProjectRootFileName = "DEPS"; +const char* kOutputDirName = "out"; +const char* kFallbackPath = "./"; +#endif +#if !defined(WEBRTC_IOS) +const char* kResourcesDirName = "resources"; +#endif + +char relative_dir_path[FILENAME_MAX]; +bool relative_dir_path_set = false; + +} // namespace + +const char* kCannotFindProjectRootDir = "ERROR_CANNOT_FIND_PROJECT_ROOT_DIR"; + +void SetExecutablePath(const std::string& path) { + std::string working_dir = WorkingDir(); + std::string temp_path = path; + + // Handle absolute paths; convert them to relative paths to the working dir. + if (path.find(working_dir) != std::string::npos) { + temp_path = path.substr(working_dir.length() + 1); + } + // On Windows, when tests are run under memory tools like DrMemory and TSan, + // slashes occur in the path as directory separators. Make sure we replace + // such cases with backslashes in order for the paths to be correct. +#ifdef WIN32 + std::replace(temp_path.begin(), temp_path.end(), '/', '\\'); +#endif + + // Trim away the executable name; only store the relative dir path. + temp_path = temp_path.substr(0, temp_path.find_last_of(kPathDelimiter)); + strncpy(relative_dir_path, temp_path.c_str(), FILENAME_MAX); + relative_dir_path_set = true; +} + +bool FileExists(std::string& file_name) { + struct stat file_info = {0}; + return stat(file_name.c_str(), &file_info) == 0; +} + +#ifdef WEBRTC_ANDROID + +std::string ProjectRootPath() { + return kRootDirName; +} + +std::string OutputPath() { + return kRootDirName; +} + +std::string WorkingDir() { + return kRootDirName; +} + +#else // WEBRTC_ANDROID + +std::string ProjectRootPath() { + std::string path = WorkingDir(); + if (path == kFallbackPath) { + return kCannotFindProjectRootDir; + } + if (relative_dir_path_set) { + path = path + kPathDelimiter + relative_dir_path; + } + // Check for our file that verifies the root dir. + size_t path_delimiter_index = path.find_last_of(kPathDelimiter); + while (path_delimiter_index != std::string::npos) { + std::string root_filename = path + kPathDelimiter + kProjectRootFileName; + if (FileExists(root_filename)) { + return path + kPathDelimiter; + } + // Move up one directory in the directory tree. + path = path.substr(0, path_delimiter_index); + path_delimiter_index = path.find_last_of(kPathDelimiter); + } + // Reached the root directory. + fprintf(stderr, "Cannot find project root directory!\n"); + return kCannotFindProjectRootDir; +} + +std::string OutputPath() { + std::string path = ProjectRootPath(); + if (path == kCannotFindProjectRootDir) { + return kFallbackPath; + } + path += kOutputDirName; + if (!CreateDir(path)) { + return kFallbackPath; + } + return path + kPathDelimiter; +} + +std::string WorkingDir() { + char path_buffer[FILENAME_MAX]; + if (!GET_CURRENT_DIR(path_buffer, sizeof(path_buffer))) { + fprintf(stderr, "Cannot get current directory!\n"); + return kFallbackPath; + } else { + return std::string(path_buffer); + } +} + +#endif // !WEBRTC_ANDROID + +// Generate a temporary filename in a safe way. +// Largely copied from talk/base/{unixfilesystem,win32filesystem}.cc. +std::string TempFilename(const std::string &dir, const std::string &prefix) { +#ifdef WIN32 + wchar_t filename[MAX_PATH]; + if (::GetTempFileName(ToUtf16(dir).c_str(), + ToUtf16(prefix).c_str(), 0, filename) != 0) + return ToUtf8(filename); + assert(false); + return ""; +#else + int len = dir.size() + prefix.size() + 2 + 6; + rtc::scoped_ptr<char[]> tempname(new char[len]); + + snprintf(tempname.get(), len, "%s/%sXXXXXX", dir.c_str(), + prefix.c_str()); + int fd = ::mkstemp(tempname.get()); + if (fd == -1) { + assert(false); + return ""; + } else { + ::close(fd); + } + std::string ret(tempname.get()); + return ret; +#endif +} + +bool CreateDir(std::string directory_name) { + struct stat path_info = {0}; + // Check if the path exists already: + if (stat(directory_name.c_str(), &path_info) == 0) { + if (!S_ISDIR(path_info.st_mode)) { + fprintf(stderr, "Path %s exists but is not a directory! Remove this " + "file and re-run to create the directory.\n", + directory_name.c_str()); + return false; + } + } else { +#ifdef WIN32 + return _mkdir(directory_name.c_str()) == 0; +#else + return mkdir(directory_name.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0; +#endif + } + return true; +} + +std::string ResourcePath(std::string name, std::string extension) { +#if defined(WEBRTC_IOS) + return IOSResourcePath(name, extension); +#else + std::string platform = "win"; +#ifdef WEBRTC_LINUX + platform = "linux"; +#endif // WEBRTC_LINUX +#ifdef WEBRTC_MAC + platform = "mac"; +#endif // WEBRTC_MAC + +#ifdef WEBRTC_ARCH_64_BITS + std::string architecture = "64"; +#else + std::string architecture = "32"; +#endif // WEBRTC_ARCH_64_BITS + + std::string resources_path = ProjectRootPath() + kResourcesDirName + + kPathDelimiter; + std::string resource_file = resources_path + name + "_" + platform + "_" + + architecture + "." + extension; + if (FileExists(resource_file)) { + return resource_file; + } + // Try without architecture. + resource_file = resources_path + name + "_" + platform + "." + extension; + if (FileExists(resource_file)) { + return resource_file; + } + // Try without platform. + resource_file = resources_path + name + "_" + architecture + "." + extension; + if (FileExists(resource_file)) { + return resource_file; + } + + // Fall back on name without architecture or platform. + return resources_path + name + "." + extension; +#endif // defined (WEBRTC_IOS) +} + +size_t GetFileSize(std::string filename) { + FILE* f = fopen(filename.c_str(), "rb"); + size_t size = 0; + if (f != NULL) { + if (fseek(f, 0, SEEK_END) == 0) { + size = ftell(f); + } + fclose(f); + } + return size; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/fileutils.h b/webrtc/test/testsupport/fileutils.h new file mode 100644 index 0000000000..78789fa870 --- /dev/null +++ b/webrtc/test/testsupport/fileutils.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2011 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 <stdio.h> + +// File utilities for testing purposes. +// +// The ProjectRootPath() method is a convenient way of getting an absolute +// path to the project source tree root directory. Using this, it is easy to +// refer to test resource files in a portable way. +// +// Notice that even if Windows platforms use backslash as path delimiter, it is +// also supported to use slash, so there's no need for #ifdef checks in test +// code for setting up the paths to the resource files. +// +// Example use: +// Assume we have the following code being used in a test source file: +// const std::string kInputFile = webrtc::test::ProjectRootPath() + +// "test/data/voice_engine/audio_long16.wav"; +// // Use the kInputFile for the tests... +// +// Then here's some example outputs for different platforms: +// Linux: +// * Source tree located in /home/user/webrtc/trunk +// * Test project located in /home/user/webrtc/trunk/src/testproject +// * Test binary compiled as: +// /home/user/webrtc/trunk/out/Debug/testproject_unittests +// Then ProjectRootPath() will return /home/user/webrtc/trunk/ no matter if +// the test binary is executed from standing in either of: +// /home/user/webrtc/trunk +// or +// /home/user/webrtc/trunk/out/Debug +// (or any other directory below the trunk for that matter). +// +// Windows: +// * Source tree located in C:\Users\user\webrtc\trunk +// * Test project located in C:\Users\user\webrtc\trunk\src\testproject +// * Test binary compiled as: +// C:\Users\user\webrtc\trunk\src\testproject\Debug\testproject_unittests.exe +// Then ProjectRootPath() will return C:\Users\user\webrtc\trunk\ when the +// test binary is executed from inside Visual Studio. +// It will also return the same path if the test is executed from a command +// prompt standing in C:\Users\user\webrtc\trunk\src\testproject\Debug +// +// Mac: +// * Source tree located in /Users/user/webrtc/trunk +// * Test project located in /Users/user/webrtc/trunk/src/testproject +// * Test binary compiled as: +// /Users/user/webrtc/trunk/xcodebuild/Debug/testproject_unittests +// Then ProjectRootPath() will return /Users/user/webrtc/trunk/ no matter if +// the test binary is executed from standing in either of: +// /Users/user/webrtc/trunk +// or +// /Users/user/webrtc/trunk/out/Debug +// (or any other directory below the trunk for that matter). + +#ifndef WEBRTC_TEST_TESTSUPPORT_FILEUTILS_H_ +#define WEBRTC_TEST_TESTSUPPORT_FILEUTILS_H_ + +#include <string> + +namespace webrtc { +namespace test { + +// This is the "directory" returned if the ProjectPath() function fails +// to find the project root. +extern const char* kCannotFindProjectRootDir; + +// Finds the root dir of the project, to be able to set correct paths to +// resource files used by tests. +// The implementation is simple: it just looks for the file defined by +// kProjectRootFileName, starting in the current directory (the working +// directory) and then steps upward until it is found (or it is at the root of +// the file system). +// If the current working directory is above the project root dir, it will not +// be found. +// +// If symbolic links occur in the path they will be resolved and the actual +// directory will be returned. +// +// Returns the absolute path to the project root dir (usually the trunk dir) +// WITH a trailing path delimiter. +// If the project root is not found, the string specified by +// kCannotFindProjectRootDir is returned. +std::string ProjectRootPath(); + +// Creates and returns the absolute path to the output directory where log files +// and other test artifacts should be put. The output directory is generally a +// directory named "out" at the top-level of the project, i.e. a subfolder to +// the path returned by ProjectRootPath(). The exception is Android where we use +// /sdcard/ instead. +// +// Details described for ProjectRootPath() apply here too. +// +// Returns the path WITH a trailing path delimiter. If the project root is not +// found, the current working directory ("./") is returned as a fallback. +std::string OutputPath(); + +// Generates an empty file with a unique name in the specified directory and +// returns the file name and path. +std::string TempFilename(const std::string &dir, const std::string &prefix); + +// Returns a path to a resource file for the currently executing platform. +// Adapts to what filenames are currently present in the +// [project-root]/resources/ dir. +// Returns an absolute path according to this priority list (the directory +// part of the path is left out for readability): +// 1. [name]_[platform]_[architecture].[extension] +// 2. [name]_[platform].[extension] +// 3. [name]_[architecture].[extension] +// 4. [name].[extension] +// Where +// * platform is either of "win", "mac" or "linux". +// * architecture is either of "32" or "64". +// +// Arguments: +// name - Name of the resource file. If a plain filename (no directory path) +// is supplied, the file is assumed to be located in resources/ +// If a directory path is prepended to the filename, a subdirectory +// hierarchy reflecting that path is assumed to be present. +// extension - File extension, without the dot, i.e. "bmp" or "yuv". +std::string ResourcePath(std::string name, std::string extension); + +// Gets the current working directory for the executing program. +// Returns "./" if for some reason it is not possible to find the working +// directory. +std::string WorkingDir(); + +// Creates a directory if it not already exists. +// Returns true if successful. Will print an error message to stderr and return +// false if a file with the same name already exists. +bool CreateDir(std::string directory_name); + +// Checks if a file exists. +bool FileExists(std::string& file_name); + +// File size of the supplied file in bytes. Will return 0 if the file is +// empty or if the file does not exist/is readable. +size_t GetFileSize(std::string filename); + +// Sets the executable path, i.e. the path to the executable that is being used +// when launching it. This is usually the path relative to the working directory +// but can also be an absolute path. The intention with this function is to pass +// the argv[0] being sent into the main function to make it possible for +// fileutils.h to find the correct project paths even when the working directory +// is outside the project tree (which happens in some cases). +void SetExecutablePath(const std::string& path_to_executable); + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_TESTSUPPORT_FILEUTILS_H_ diff --git a/webrtc/test/testsupport/fileutils_unittest.cc b/webrtc/test/testsupport/fileutils_unittest.cc new file mode 100644 index 0000000000..dff7f2249b --- /dev/null +++ b/webrtc/test/testsupport/fileutils_unittest.cc @@ -0,0 +1,142 @@ +/* + * 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/testsupport/fileutils.h" + +#include <stdio.h> + +#include <list> +#include <string> + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/test/testsupport/gtest_disable.h" + +#ifdef WIN32 +#define chdir _chdir +static const char* kPathDelimiter = "\\"; +#else +static const char* kPathDelimiter = "/"; +#endif + +static const std::string kResourcesDir = "resources"; +static const std::string kTestName = "fileutils_unittest"; +static const std::string kExtension = "tmp"; + +namespace webrtc { + +// Test fixture to restore the working directory between each test, since some +// of them change it with chdir during execution (not restored by the +// gtest framework). +class FileUtilsTest : public testing::Test { + protected: + FileUtilsTest() { + } + virtual ~FileUtilsTest() {} + // Runs before the first test + static void SetUpTestCase() { + original_working_dir_ = webrtc::test::WorkingDir(); + } + void SetUp() { + ASSERT_EQ(chdir(original_working_dir_.c_str()), 0); + } + void TearDown() { + ASSERT_EQ(chdir(original_working_dir_.c_str()), 0); + } + private: + static std::string original_working_dir_; +}; + +std::string FileUtilsTest::original_working_dir_ = ""; + +// Tests that the project root path is returned for the default working +// directory that is automatically set when the test executable is launched. +// The test is not fully testing the implementation, since we cannot be sure +// of where the executable was launched from. +TEST_F(FileUtilsTest, ProjectRootPath) { + std::string project_root = webrtc::test::ProjectRootPath(); + // Not very smart, but at least tests something. + ASSERT_GT(project_root.length(), 0u); +} + +// Similar to the above test, but for the output dir +TEST_F(FileUtilsTest, DISABLED_ON_ANDROID(OutputPathFromUnchangedWorkingDir)) { + std::string path = webrtc::test::OutputPath(); + std::string expected_end = "out"; + expected_end = kPathDelimiter + expected_end + kPathDelimiter; + ASSERT_EQ(path.length() - expected_end.length(), path.find(expected_end)); +} + +// Tests with current working directory set to a directory higher up in the +// directory tree than the project root dir. +TEST_F(FileUtilsTest, DISABLED_ON_ANDROID(OutputPathFromRootWorkingDir)) { + ASSERT_EQ(0, chdir(kPathDelimiter)); + ASSERT_EQ("./", webrtc::test::OutputPath()); +} + +TEST_F(FileUtilsTest, TempFilename) { + std::string temp_filename = webrtc::test::TempFilename( + webrtc::test::OutputPath(), "TempFilenameTest"); + ASSERT_TRUE(webrtc::test::FileExists(temp_filename)) + << "Couldn't find file: " << temp_filename; + remove(temp_filename.c_str()); +} + +// Only tests that the code executes +TEST_F(FileUtilsTest, CreateDir) { + std::string directory = "fileutils-unittest-empty-dir"; + // Make sure it's removed if a previous test has failed: + remove(directory.c_str()); + ASSERT_TRUE(webrtc::test::CreateDir(directory)); + remove(directory.c_str()); +} + +TEST_F(FileUtilsTest, WorkingDirReturnsValue) { + // Hard to cover all platforms. Just test that it returns something without + // crashing: + std::string working_dir = webrtc::test::WorkingDir(); + ASSERT_GT(working_dir.length(), 0u); +} + +// Due to multiple platforms, it is hard to make a complete test for +// ResourcePath. Manual testing has been performed by removing files and +// verified the result confirms with the specified documentation for the +// function. +TEST_F(FileUtilsTest, ResourcePathReturnsValue) { + std::string resource = webrtc::test::ResourcePath(kTestName, kExtension); + ASSERT_GT(resource.find(kTestName), 0u); + ASSERT_GT(resource.find(kExtension), 0u); +} + +TEST_F(FileUtilsTest, ResourcePathFromRootWorkingDir) { + ASSERT_EQ(0, chdir(kPathDelimiter)); + std::string resource = webrtc::test::ResourcePath(kTestName, kExtension); + ASSERT_NE(resource.find("resources"), std::string::npos); + ASSERT_GT(resource.find(kTestName), 0u); + ASSERT_GT(resource.find(kExtension), 0u); +} + +TEST_F(FileUtilsTest, GetFileSizeExistingFile) { + // Create a file with some dummy data in. + std::string temp_filename = webrtc::test::TempFilename( + webrtc::test::OutputPath(), "fileutils_unittest"); + FILE* file = fopen(temp_filename.c_str(), "wb"); + ASSERT_TRUE(file != NULL) << "Failed to open file: " << temp_filename; + ASSERT_GT(fprintf(file, "%s", "Dummy data"), 0) << + "Failed to write to file: " << temp_filename; + fclose(file); + ASSERT_GT(webrtc::test::GetFileSize(std::string(temp_filename.c_str())), 0u); + remove(temp_filename.c_str()); +} + +TEST_F(FileUtilsTest, GetFileSizeNonExistingFile) { + ASSERT_EQ(0u, webrtc::test::GetFileSize("non-existing-file.tmp")); +} + +} // namespace webrtc diff --git a/webrtc/test/testsupport/frame_reader.cc b/webrtc/test/testsupport/frame_reader.cc new file mode 100644 index 0000000000..b03616aa44 --- /dev/null +++ b/webrtc/test/testsupport/frame_reader.cc @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2011 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/testsupport/frame_reader.h" + +#include <assert.h> + +#include "webrtc/test/testsupport/fileutils.h" + +namespace webrtc { +namespace test { + +FrameReaderImpl::FrameReaderImpl(std::string input_filename, + size_t frame_length_in_bytes) + : input_filename_(input_filename), + frame_length_in_bytes_(frame_length_in_bytes), + input_file_(NULL) { +} + +FrameReaderImpl::~FrameReaderImpl() { + Close(); +} + +bool FrameReaderImpl::Init() { + if (frame_length_in_bytes_ <= 0) { + fprintf(stderr, "Frame length must be >0, was %zu\n", + frame_length_in_bytes_); + return false; + } + input_file_ = fopen(input_filename_.c_str(), "rb"); + if (input_file_ == NULL) { + fprintf(stderr, "Couldn't open input file for reading: %s\n", + input_filename_.c_str()); + return false; + } + // Calculate total number of frames. + size_t source_file_size = GetFileSize(input_filename_); + if (source_file_size <= 0u) { + fprintf(stderr, "Found empty file: %s\n", input_filename_.c_str()); + return false; + } + number_of_frames_ = static_cast<int>(source_file_size / + frame_length_in_bytes_); + return true; +} + +void FrameReaderImpl::Close() { + if (input_file_ != NULL) { + fclose(input_file_); + input_file_ = NULL; + } +} + +bool FrameReaderImpl::ReadFrame(uint8_t* source_buffer) { + assert(source_buffer); + if (input_file_ == NULL) { + fprintf(stderr, "FrameReader is not initialized (input file is NULL)\n"); + return false; + } + size_t nbr_read = fread(source_buffer, 1, frame_length_in_bytes_, + input_file_); + if (nbr_read != static_cast<unsigned int>(frame_length_in_bytes_) && + ferror(input_file_)) { + fprintf(stderr, "Error reading from input file: %s\n", + input_filename_.c_str()); + return false; + } + if (feof(input_file_) != 0) { + return false; // No more frames to process. + } + return true; +} + +size_t FrameReaderImpl::FrameLength() { return frame_length_in_bytes_; } +int FrameReaderImpl::NumberOfFrames() { return number_of_frames_; } + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/frame_reader.h b/webrtc/test/testsupport/frame_reader.h new file mode 100644 index 0000000000..9b50ec7bf4 --- /dev/null +++ b/webrtc/test/testsupport/frame_reader.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2011 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_TEST_TESTSUPPORT_FRAME_READER_H_ +#define WEBRTC_TEST_TESTSUPPORT_FRAME_READER_H_ + +#include <stdio.h> + +#include <string> + +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +// Handles reading of frames from video files. +class FrameReader { + public: + virtual ~FrameReader() {} + + // Initializes the frame reader, i.e. opens the input file. + // This must be called before reading of frames has started. + // Returns false if an error has occurred, in addition to printing to stderr. + virtual bool Init() = 0; + + // Reads a frame into the supplied buffer, which must contain enough space + // for the frame size. + // Returns true if there are more frames to read, false if we've already + // read the last frame (in the previous call). + virtual bool ReadFrame(uint8_t* source_buffer) = 0; + + // Closes the input file if open. Essentially makes this class impossible + // to use anymore. Will also be invoked by the destructor. + virtual void Close() = 0; + + // Frame length in bytes of a single frame image. + virtual size_t FrameLength() = 0; + // Total number of frames in the input video source. + virtual int NumberOfFrames() = 0; +}; + +class FrameReaderImpl : public FrameReader { + public: + // Creates a file handler. The input file is assumed to exist and be readable. + // Parameters: + // input_filename The file to read from. + // frame_length_in_bytes The size of each frame. + // For YUV this is 3 * width * height / 2 + FrameReaderImpl(std::string input_filename, size_t frame_length_in_bytes); + ~FrameReaderImpl() override; + bool Init() override; + bool ReadFrame(uint8_t* source_buffer) override; + void Close() override; + size_t FrameLength() override; + int NumberOfFrames() override; + + private: + std::string input_filename_; + size_t frame_length_in_bytes_; + int number_of_frames_; + FILE* input_file_; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_TESTSUPPORT_FRAME_READER_H_ diff --git a/webrtc/test/testsupport/frame_reader_unittest.cc b/webrtc/test/testsupport/frame_reader_unittest.cc new file mode 100644 index 0000000000..25ea9132f2 --- /dev/null +++ b/webrtc/test/testsupport/frame_reader_unittest.cc @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2011 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/testsupport/frame_reader.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/test/testsupport/fileutils.h" + +namespace webrtc { +namespace test { + +const std::string kInputFileContents = "baz"; +// Setting the kFrameLength value to a value much larger than the +// file to test causes the ReadFrame test to fail on Windows. +const size_t kFrameLength = 1000; + +class FrameReaderTest: public testing::Test { + protected: + FrameReaderTest() {} + virtual ~FrameReaderTest() {} + void SetUp() { + // Create a dummy input file. + temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(), + "frame_reader_unittest"); + FILE* dummy = fopen(temp_filename_.c_str(), "wb"); + fprintf(dummy, "%s", kInputFileContents.c_str()); + fclose(dummy); + + frame_reader_ = new FrameReaderImpl(temp_filename_, kFrameLength); + ASSERT_TRUE(frame_reader_->Init()); + } + void TearDown() { + delete frame_reader_; + // Cleanup the dummy input file. + remove(temp_filename_.c_str()); + } + FrameReader* frame_reader_; + std::string temp_filename_; +}; + +TEST_F(FrameReaderTest, InitSuccess) { + FrameReaderImpl frame_reader(temp_filename_, kFrameLength); + ASSERT_TRUE(frame_reader.Init()); + ASSERT_EQ(kFrameLength, frame_reader.FrameLength()); + ASSERT_EQ(0, frame_reader.NumberOfFrames()); +} + +TEST_F(FrameReaderTest, ReadFrame) { + uint8_t buffer[3]; + bool result = frame_reader_->ReadFrame(buffer); + ASSERT_FALSE(result); // No more files to read. + ASSERT_EQ(kInputFileContents[0], buffer[0]); + ASSERT_EQ(kInputFileContents[1], buffer[1]); + ASSERT_EQ(kInputFileContents[2], buffer[2]); +} + +TEST_F(FrameReaderTest, ReadFrameUninitialized) { + uint8_t buffer[3]; + FrameReaderImpl file_reader(temp_filename_, kFrameLength); + ASSERT_FALSE(file_reader.ReadFrame(buffer)); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/frame_writer.cc b/webrtc/test/testsupport/frame_writer.cc new file mode 100644 index 0000000000..1b9e8a82ef --- /dev/null +++ b/webrtc/test/testsupport/frame_writer.cc @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2011 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/testsupport/frame_writer.h" + +#include <assert.h> + +namespace webrtc { +namespace test { + +FrameWriterImpl::FrameWriterImpl(std::string output_filename, + size_t frame_length_in_bytes) + : output_filename_(output_filename), + frame_length_in_bytes_(frame_length_in_bytes), + output_file_(NULL) { +} + +FrameWriterImpl::~FrameWriterImpl() { + Close(); +} + +bool FrameWriterImpl::Init() { + if (frame_length_in_bytes_ <= 0) { + fprintf(stderr, "Frame length must be >0, was %zu\n", + frame_length_in_bytes_); + return false; + } + output_file_ = fopen(output_filename_.c_str(), "wb"); + if (output_file_ == NULL) { + fprintf(stderr, "Couldn't open output file for writing: %s\n", + output_filename_.c_str()); + return false; + } + return true; +} + +void FrameWriterImpl::Close() { + if (output_file_ != NULL) { + fclose(output_file_); + output_file_ = NULL; + } +} + +size_t FrameWriterImpl::FrameLength() { return frame_length_in_bytes_; } + +bool FrameWriterImpl::WriteFrame(uint8_t* frame_buffer) { + assert(frame_buffer); + if (output_file_ == NULL) { + fprintf(stderr, "FrameWriter is not initialized (output file is NULL)\n"); + return false; + } + size_t bytes_written = fwrite(frame_buffer, 1, frame_length_in_bytes_, + output_file_); + if (bytes_written != frame_length_in_bytes_) { + fprintf(stderr, "Failed to write %zu bytes to file %s\n", + frame_length_in_bytes_, output_filename_.c_str()); + return false; + } + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/frame_writer.h b/webrtc/test/testsupport/frame_writer.h new file mode 100644 index 0000000000..8a6b1c2152 --- /dev/null +++ b/webrtc/test/testsupport/frame_writer.h @@ -0,0 +1,70 @@ +/* + * 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_TEST_TESTSUPPORT_FRAME_WRITER_H_ +#define WEBRTC_TEST_TESTSUPPORT_FRAME_WRITER_H_ + +#include <stdio.h> + +#include <string> + +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +// Handles writing of video files. +class FrameWriter { + public: + virtual ~FrameWriter() {} + + // Initializes the file handler, i.e. opens the input and output files etc. + // This must be called before reading or writing frames has started. + // Returns false if an error has occurred, in addition to printing to stderr. + virtual bool Init() = 0; + + // Writes a frame of the configured frame length to the output file. + // Returns true if the write was successful, false otherwise. + virtual bool WriteFrame(uint8_t* frame_buffer) = 0; + + // Closes the output file if open. Essentially makes this class impossible + // to use anymore. Will also be invoked by the destructor. + virtual void Close() = 0; + + // Frame length in bytes of a single frame image. + virtual size_t FrameLength() = 0; +}; + +class FrameWriterImpl : public FrameWriter { + public: + // Creates a file handler. The input file is assumed to exist and be readable + // and the output file must be writable. + // Parameters: + // output_filename The file to write. Will be overwritten if already + // existing. + // frame_length_in_bytes The size of each frame. + // For YUV: 3*width*height/2 + FrameWriterImpl(std::string output_filename, size_t frame_length_in_bytes); + ~FrameWriterImpl() override; + bool Init() override; + bool WriteFrame(uint8_t* frame_buffer) override; + void Close() override; + size_t FrameLength() override; + + private: + std::string output_filename_; + size_t frame_length_in_bytes_; + FILE* output_file_; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_TESTSUPPORT_FRAME_WRITER_H_ diff --git a/webrtc/test/testsupport/frame_writer_unittest.cc b/webrtc/test/testsupport/frame_writer_unittest.cc new file mode 100644 index 0000000000..da1e268662 --- /dev/null +++ b/webrtc/test/testsupport/frame_writer_unittest.cc @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2011 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/testsupport/frame_writer.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/test/testsupport/fileutils.h" + +namespace webrtc { +namespace test { + +const size_t kFrameLength = 1000; + +class FrameWriterTest: public testing::Test { + protected: + FrameWriterTest() {} + virtual ~FrameWriterTest() {} + void SetUp() { + temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(), + "frame_writer_unittest"); + frame_writer_ = new FrameWriterImpl(temp_filename_, kFrameLength); + ASSERT_TRUE(frame_writer_->Init()); + } + void TearDown() { + delete frame_writer_; + // Cleanup the temporary file. + remove(temp_filename_.c_str()); + } + FrameWriter* frame_writer_; + std::string temp_filename_; +}; + +TEST_F(FrameWriterTest, InitSuccess) { + FrameWriterImpl frame_writer(temp_filename_, kFrameLength); + ASSERT_TRUE(frame_writer.Init()); + ASSERT_EQ(kFrameLength, frame_writer.FrameLength()); +} + +TEST_F(FrameWriterTest, WriteFrame) { + uint8_t buffer[kFrameLength]; + memset(buffer, 9, kFrameLength); // Write lots of 9s to the buffer + bool result = frame_writer_->WriteFrame(buffer); + ASSERT_TRUE(result); // success + // Close the file and verify the size. + frame_writer_->Close(); + ASSERT_EQ(kFrameLength, GetFileSize(temp_filename_)); +} + +TEST_F(FrameWriterTest, WriteFrameUninitialized) { + uint8_t buffer[3]; + FrameWriterImpl frame_writer(temp_filename_, kFrameLength); + ASSERT_FALSE(frame_writer.WriteFrame(buffer)); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/gtest_disable.h b/webrtc/test/testsupport/gtest_disable.h new file mode 100644 index 0000000000..fdc56acc05 --- /dev/null +++ b/webrtc/test/testsupport/gtest_disable.h @@ -0,0 +1,57 @@ +/* + * 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 TEST_TESTSUPPORT_INCLUDE_GTEST_DISABLE_H_ +#define TEST_TESTSUPPORT_INCLUDE_GTEST_DISABLE_H_ + +// Helper macros for platform disables. These can be chained. Example use: +// TEST_F(ViEStandardIntegrationTest, +// DISABLED_ON_LINUX(RunsBaseTestWithoutErrors)) { // ... +// +// Or, you can disable a whole test class by wrapping all mentions of the test +// class name inside one of these macros. +// +// The platform #defines we are looking at here are set by the build system. +#ifdef WEBRTC_LINUX +#define DISABLED_ON_LINUX(test) DISABLED_##test +#else +#define DISABLED_ON_LINUX(test) test +#endif + +#ifdef WEBRTC_MAC +#define DISABLED_ON_MAC(test) DISABLED_##test +#else +#define DISABLED_ON_MAC(test) test +#endif + +#ifdef _WIN32 +#define DISABLED_ON_WIN(test) DISABLED_##test +#else +#define DISABLED_ON_WIN(test) test +#endif + +// Using some extra magic here to be able to chain Android and iOS macros. +// http://stackoverflow.com/questions/8231966/why-do-i-need-double-layer-of-indirection-for-macros +#ifdef WEBRTC_ANDROID +#define DISABLED_ON_ANDROID_HIDDEN(test) DISABLED_##test +#define DISABLED_ON_ANDROID(test) DISABLED_ON_ANDROID_HIDDEN(test) +#else +#define DISABLED_ON_ANDROID_HIDDEN(test) test +#define DISABLED_ON_ANDROID(test) DISABLED_ON_ANDROID_HIDDEN(test) +#endif + +#ifdef WEBRTC_IOS +#define DISABLED_ON_IOS_HIDDEN(test) DISABLED_##test +#define DISABLED_ON_IOS(test) DISABLED_ON_IOS_HIDDEN(test) +#else +#define DISABLED_ON_IOS_HIDDEN(test) test +#define DISABLED_ON_IOS(test) DISABLED_ON_IOS_HIDDEN(test) +#endif + +#endif // TEST_TESTSUPPORT_INCLUDE_GTEST_DISABLE_H_ diff --git a/webrtc/test/testsupport/gtest_prod_util.h b/webrtc/test/testsupport/gtest_prod_util.h new file mode 100644 index 0000000000..5370589899 --- /dev/null +++ b/webrtc/test/testsupport/gtest_prod_util.h @@ -0,0 +1,38 @@ +/* + * 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_TEST_TESTSUPPORT_GTEST_PROD_UTIL_H_ +#define WEBRTC_TEST_TESTSUPPORT_GTEST_PROD_UTIL_H_ + +// Define our own version of FRIEND_TEST here rather than including +// gtest_prod.h to avoid depending on any part of GTest in production code. +#define FRIEND_TEST_WEBRTC(test_case_name, test_name)\ +friend class test_case_name##_##test_name##_Test + +// This file is a plain copy of Chromium's base/gtest_prod_util.h. +// +// This is a wrapper for gtest's FRIEND_TEST macro that friends +// test with all possible prefixes. This is very helpful when changing the test +// prefix, because the friend declarations don't need to be updated. +// +// Example usage: +// +// class MyClass { +// private: +// void MyMethod(); +// FRIEND_TEST_ALL_PREFIXES(MyClassTest, MyMethod); +// }; +#define FRIEND_TEST_ALL_PREFIXES(test_case_name, test_name) \ + FRIEND_TEST_WEBRTC(test_case_name, test_name); \ + FRIEND_TEST_WEBRTC(test_case_name, DISABLED_##test_name); \ + FRIEND_TEST_WEBRTC(test_case_name, FLAKY_##test_name); \ + FRIEND_TEST_WEBRTC(test_case_name, FAILS_##test_name) + +#endif // WEBRTC_TEST_TESTSUPPORT_GTEST_PROD_UTIL_H_ diff --git a/webrtc/test/testsupport/iosfileutils.mm b/webrtc/test/testsupport/iosfileutils.mm new file mode 100644 index 0000000000..f3615ed681 --- /dev/null +++ b/webrtc/test/testsupport/iosfileutils.mm @@ -0,0 +1,60 @@ +/* + * Copyright 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. + */ + +#if defined(WEBRTC_IOS) + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import <Foundation/Foundation.h> +#include <string.h> + +#include "webrtc/base/checks.h" +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +// TODO(henrika): move to shared location. +// See https://code.google.com/p/webrtc/issues/detail?id=4773 for details. +NSString* NSStringFromStdString(const std::string& stdString) { + // std::string may contain null termination character so we construct + // using length. + return [[NSString alloc] initWithBytes:stdString.data() + length:stdString.length() + encoding:NSUTF8StringEncoding]; +} + +std::string StdStringFromNSString(NSString* nsString) { + NSData* charData = [nsString dataUsingEncoding:NSUTF8StringEncoding]; + return std::string(reinterpret_cast<const char*>([charData bytes]), + [charData length]); +} + +// For iOS, resource files are added to the application bundle in the root +// and not in separate folders as is the case for other platforms. This method +// therefore removes any prepended folders and uses only the actual file name. +std::string IOSResourcePath(std::string name, std::string extension) { + @autoreleasepool { + NSString* path = NSStringFromStdString(name); + NSString* fileName = path.lastPathComponent; + NSString* fileType = NSStringFromStdString(extension); + // Get full pathname for the resource identified by the name and extension. + NSString* pathString = [[NSBundle mainBundle] pathForResource:fileName + ofType:fileType]; + return StdStringFromNSString(pathString); + } +} + +} // namespace test +} // namespace webrtc + +#endif // defined(WEBRTC_IOS) diff --git a/webrtc/test/testsupport/mac/run_threaded_main_mac.h b/webrtc/test/testsupport/mac/run_threaded_main_mac.h new file mode 100644 index 0000000000..c8cc4bbaeb --- /dev/null +++ b/webrtc/test/testsupport/mac/run_threaded_main_mac.h @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * This file and its corresponding .mm file implement a main function on Mac. + * It's useful if you need to access a webcam in your Mac application. The code + * forks a worker thread which runs the below ImplementThisToRunYourTest + * function, and uses the main thread to pump messages. That way we can run our + * code in a regular sequential fashion and still pump events, which are + * necessary to access the webcam for instance. + */ + +// Implement this method to do whatever you want to do in the worker thread. +// The argc and argv variables are the unmodified command line from main. +int ImplementThisToRunYourTest(int argc, char** argv); diff --git a/webrtc/test/testsupport/mac/run_threaded_main_mac.mm b/webrtc/test/testsupport/mac/run_threaded_main_mac.mm new file mode 100644 index 0000000000..4a908fbf2d --- /dev/null +++ b/webrtc/test/testsupport/mac/run_threaded_main_mac.mm @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#import "webrtc/test/testsupport/mac/run_threaded_main_mac.h" + +#import <Cocoa/Cocoa.h> + +// This class passes parameter from main to the worked thread and back. +@interface AutoTestInWorkerThread : NSObject { + int argc_; + char** argv_; + int result_; + bool done_; +} + +- (void)setDone:(bool)done; +- (bool)done; +- (void)setArgc:(int)argc argv:(char**)argv; +- (int) result; +- (void)runTest:(NSObject*)ignored; + +@end + +@implementation AutoTestInWorkerThread + +- (void)setDone:(bool)done { + done_ = done; +} + +- (bool)done { + return done_; +} + +- (void)setArgc:(int)argc argv:(char**)argv { + argc_ = argc; + argv_ = argv; +} + +- (void)runTest:(NSObject*)ignored { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + result_ = ImplementThisToRunYourTest(argc_, argv_); + done_ = true; + + [pool release]; + return; +} + +- (int)result { + return result_; +} + +@end + +int main(int argc, char * argv[]) { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + [NSApplication sharedApplication]; + + int result = 0; + AutoTestInWorkerThread* tests = [[AutoTestInWorkerThread alloc] init]; + + [tests setArgc:argc argv:argv]; + [tests setDone:false]; + + [NSThread detachNewThreadSelector:@selector(runTest:) + toTarget:tests + withObject:nil]; + + NSRunLoop* main_run_loop = [NSRunLoop mainRunLoop]; + NSDate *loop_until = [NSDate dateWithTimeIntervalSinceNow:0.1]; + bool runloop_ok = true; + while (![tests done] && runloop_ok) { + runloop_ok = [main_run_loop runMode:NSDefaultRunLoopMode + beforeDate:loop_until]; + loop_until = [NSDate dateWithTimeIntervalSinceNow:0.1]; + } + + result = [tests result]; + + [pool release]; + return result; +} diff --git a/webrtc/test/testsupport/metrics/video_metrics.cc b/webrtc/test/testsupport/metrics/video_metrics.cc new file mode 100644 index 0000000000..947b81d442 --- /dev/null +++ b/webrtc/test/testsupport/metrics/video_metrics.cc @@ -0,0 +1,198 @@ +/* + * 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/testsupport/metrics/video_metrics.h" + +#include <assert.h> +#include <stdio.h> + +#include <algorithm> // min_element, max_element + +#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" +#include "webrtc/video_frame.h" + +namespace webrtc { +namespace test { + +// Copy here so our callers won't need to include libyuv for this constant. +double kMetricsPerfectPSNR = kPerfectPSNR; + +// Used for calculating min and max values. +static bool LessForFrameResultValue(const FrameResult& s1, + const FrameResult& s2) { + return s1.value < s2.value; +} + +enum VideoMetricsType { kPSNR, kSSIM, kBoth }; + +// Calculates metrics for a frame and adds statistics to the result for it. +void CalculateFrame(VideoMetricsType video_metrics_type, + const VideoFrame* ref, + const VideoFrame* test, + int frame_number, + QualityMetricsResult* result) { + FrameResult frame_result = {0, 0}; + frame_result.frame_number = frame_number; + switch (video_metrics_type) { + case kPSNR: + frame_result.value = I420PSNR(ref, test); + break; + case kSSIM: + frame_result.value = I420SSIM(ref, test); + break; + default: + assert(false); + } + result->frames.push_back(frame_result); +} + +// Calculates average, min and max values for the supplied struct, if non-NULL. +void CalculateStats(QualityMetricsResult* result) { + if (result == NULL || result->frames.size() == 0) { + return; + } + // Calculate average. + std::vector<FrameResult>::iterator iter; + double metrics_values_sum = 0.0; + for (iter = result->frames.begin(); iter != result->frames.end(); ++iter) { + metrics_values_sum += iter->value; + } + result->average = metrics_values_sum / result->frames.size(); + + // Calculate min/max statistics. + iter = std::min_element(result->frames.begin(), result->frames.end(), + LessForFrameResultValue); + result->min = iter->value; + result->min_frame_number = iter->frame_number; + iter = std::max_element(result->frames.begin(), result->frames.end(), + LessForFrameResultValue); + result->max = iter->value; + result->max_frame_number = iter->frame_number; +} + +// Single method that handles all combinations of video metrics calculation, to +// minimize code duplication. Either psnr_result or ssim_result may be NULL, +// depending on which VideoMetricsType is targeted. +int CalculateMetrics(VideoMetricsType video_metrics_type, + const char* ref_filename, + const char* test_filename, + int width, + int height, + QualityMetricsResult* psnr_result, + QualityMetricsResult* ssim_result) { + assert(ref_filename != NULL); + assert(test_filename != NULL); + assert(width > 0); + assert(height > 0); + + FILE* ref_fp = fopen(ref_filename, "rb"); + if (ref_fp == NULL) { + // Cannot open reference file. + fprintf(stderr, "Cannot open file %s\n", ref_filename); + return -1; + } + FILE* test_fp = fopen(test_filename, "rb"); + if (test_fp == NULL) { + // Cannot open test file. + fprintf(stderr, "Cannot open file %s\n", test_filename); + fclose(ref_fp); + return -2; + } + int frame_number = 0; + + // Read reference and test frames. + const size_t frame_length = 3 * width * height >> 1; + VideoFrame ref_frame; + VideoFrame test_frame; + rtc::scoped_ptr<uint8_t[]> ref_buffer(new uint8_t[frame_length]); + rtc::scoped_ptr<uint8_t[]> test_buffer(new uint8_t[frame_length]); + + // Set decoded image parameters. + int half_width = (width + 1) / 2; + ref_frame.CreateEmptyFrame(width, height, width, half_width, half_width); + test_frame.CreateEmptyFrame(width, height, width, half_width, half_width); + + size_t ref_bytes = fread(ref_buffer.get(), 1, frame_length, ref_fp); + size_t test_bytes = fread(test_buffer.get(), 1, frame_length, test_fp); + while (ref_bytes == frame_length && test_bytes == frame_length) { + // Converting from buffer to plane representation. + ConvertToI420(kI420, ref_buffer.get(), 0, 0, width, height, 0, + kVideoRotation_0, &ref_frame); + ConvertToI420(kI420, test_buffer.get(), 0, 0, width, height, 0, + kVideoRotation_0, &test_frame); + switch (video_metrics_type) { + case kPSNR: + CalculateFrame(kPSNR, &ref_frame, &test_frame, frame_number, + psnr_result); + break; + case kSSIM: + CalculateFrame(kSSIM, &ref_frame, &test_frame, frame_number, + ssim_result); + break; + case kBoth: + CalculateFrame(kPSNR, &ref_frame, &test_frame, frame_number, + psnr_result); + CalculateFrame(kSSIM, &ref_frame, &test_frame, frame_number, + ssim_result); + break; + } + frame_number++; + ref_bytes = fread(ref_buffer.get(), 1, frame_length, ref_fp); + test_bytes = fread(test_buffer.get(), 1, frame_length, test_fp); + } + int return_code = 0; + if (frame_number == 0) { + fprintf(stderr, "Tried to measure video metrics from empty files " + "(reference file: %s test file: %s)\n", ref_filename, + test_filename); + return_code = -3; + } else { + CalculateStats(psnr_result); + CalculateStats(ssim_result); + } + fclose(ref_fp); + fclose(test_fp); + return return_code; +} + +int I420MetricsFromFiles(const char* ref_filename, + const char* test_filename, + int width, + int height, + QualityMetricsResult* psnr_result, + QualityMetricsResult* ssim_result) { + assert(psnr_result != NULL); + assert(ssim_result != NULL); + return CalculateMetrics(kBoth, ref_filename, test_filename, width, height, + psnr_result, ssim_result); +} + +int I420PSNRFromFiles(const char* ref_filename, + const char* test_filename, + int width, + int height, + QualityMetricsResult* result) { + assert(result != NULL); + return CalculateMetrics(kPSNR, ref_filename, test_filename, width, height, + result, NULL); +} + +int I420SSIMFromFiles(const char* ref_filename, + const char* test_filename, + int width, + int height, + QualityMetricsResult* result) { + assert(result != NULL); + return CalculateMetrics(kSSIM, ref_filename, test_filename, width, height, + NULL, result); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/metrics/video_metrics.h b/webrtc/test/testsupport/metrics/video_metrics.h new file mode 100644 index 0000000000..8448ccfd24 --- /dev/null +++ b/webrtc/test/testsupport/metrics/video_metrics.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2011 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_TESTSUPPORT_METRICS_VIDEO_METRICS_H_ +#define WEBRTC_TESTSUPPORT_METRICS_VIDEO_METRICS_H_ + +#include <limits> +#include <vector> + +namespace webrtc { +namespace test { + +// The highest PSNR value our algorithms will return. +extern double kMetricsPerfectPSNR; + +// Contains video quality metrics result for a single frame. +struct FrameResult { + int frame_number; + double value; +}; + +// Result from a PSNR/SSIM calculation operation. +// The frames in this data structure are 0-indexed. +struct QualityMetricsResult { + QualityMetricsResult() : + average(0.0), + min(std::numeric_limits<double>::max()), + max(std::numeric_limits<double>::min()), + min_frame_number(-1), + max_frame_number(-1) + {}; + double average; + double min; + double max; + int min_frame_number; + int max_frame_number; + std::vector<FrameResult> frames; +}; + +// Calculates PSNR and SSIM values for the reference and test video files +// (must be in I420 format). All calculated values are filled into the +// QualityMetricsResult structs. +// +// PSNR values have the unit decibel (dB) where a high value means the test file +// is similar to the reference file. The higher value, the more similar. The +// maximum PSNR value is kMetricsInfinitePSNR. For more info about PSNR, see +// http://en.wikipedia.org/wiki/PSNR. +// +// SSIM values range between -1.0 and 1.0, where 1.0 means the files are +// identical. For more info about SSIM, see http://en.wikipedia.org/wiki/SSIM +// This function only compares video frames up to the point when the shortest +// video ends. +// Return value: +// 0 if successful, negative on errors: +// -1 if the source file cannot be opened +// -2 if the test file cannot be opened +// -3 if any of the files are empty +// -4 if any arguments are invalid. +int I420MetricsFromFiles(const char* ref_filename, + const char* test_filename, + int width, + int height, + QualityMetricsResult* psnr_result, + QualityMetricsResult* ssim_result); + +// Calculates PSNR values for the reference and test video files (must be in +// I420 format). All calculated values are filled into the QualityMetricsResult +// struct. +// +// PSNR values have the unit decibel (dB) where a high value means the test file +// is similar to the reference file. The higher value, the more similar. The +// maximum PSNR value is kMetricsInfinitePSNR. For more info about PSNR, see +// http://en.wikipedia.org/wiki/PSNR. +// +// This function only compares video frames up to the point when the shortest +// video ends. +// +// Return value: +// 0 if successful, negative on errors: +// -1 if the source file cannot be opened +// -2 if the test file cannot be opened +// -3 if any of the files are empty +// -4 if any arguments are invalid. +int I420PSNRFromFiles(const char* ref_filename, + const char* test_filename, + int width, + int height, + QualityMetricsResult* result); + +// Calculates SSIM values for the reference and test video files (must be in +// I420 format). All calculated values are filled into the QualityMetricsResult +// struct. +// SSIM values range between -1.0 and 1.0, where 1.0 means the files are +// identical. +// This function only compares video frames up to the point when the shortest +// video ends. +// For more info about SSIM, see http://en.wikipedia.org/wiki/SSIM +// +// Return value: +// 0 if successful, negative on errors: +// -1 if the source file cannot be opened +// -2 if the test file cannot be opened +// -3 if any of the files are empty +// -4 if any arguments are invalid. +int I420SSIMFromFiles(const char* ref_filename, + const char* test_filename, + int width, + int height, + QualityMetricsResult* result); + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TESTSUPPORT_METRICS_VIDEO_METRICS_H_ diff --git a/webrtc/test/testsupport/metrics/video_metrics_unittest.cc b/webrtc/test/testsupport/metrics/video_metrics_unittest.cc new file mode 100644 index 0000000000..0958dcfbad --- /dev/null +++ b/webrtc/test/testsupport/metrics/video_metrics_unittest.cc @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2011 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/testsupport/metrics/video_metrics.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/test/testsupport/fileutils.h" + +namespace webrtc { + +static const int kWidth = 352; +static const int kHeight = 288; + +static const int kMissingReferenceFileReturnCode = -1; +static const int kMissingTestFileReturnCode = -2; +static const int kEmptyFileReturnCode = -3; +static const double kPsnrPerfectResult = 48.0; +static const double kSsimPerfectResult = 1.0; + +class VideoMetricsTest: public testing::Test { + protected: + VideoMetricsTest() { + video_file_ = webrtc::test::ResourcePath("foreman_cif_short", "yuv"); + } + virtual ~VideoMetricsTest() {} + void SetUp() { + non_existing_file_ = webrtc::test::OutputPath() + + "video_metrics_unittest_non_existing"; + remove(non_existing_file_.c_str()); // To be sure it doesn't exist. + + // Create an empty file: + empty_file_ = webrtc::test::TempFilename( + webrtc::test::OutputPath(), "video_metrics_unittest_empty_file"); + FILE* dummy = fopen(empty_file_.c_str(), "wb"); + fclose(dummy); + } + void TearDown() { + remove(empty_file_.c_str()); + } + webrtc::test::QualityMetricsResult psnr_result_; + webrtc::test::QualityMetricsResult ssim_result_; + std::string non_existing_file_; + std::string empty_file_; + std::string video_file_; +}; + +// Tests that it is possible to run with the same reference as test file +TEST_F(VideoMetricsTest, ReturnsPerfectResultForIdenticalFilesPSNR) { + EXPECT_EQ(0, I420PSNRFromFiles(video_file_.c_str(), video_file_.c_str(), + kWidth, kHeight, &psnr_result_)); + EXPECT_EQ(kPsnrPerfectResult, psnr_result_.average); +} + +TEST_F(VideoMetricsTest, ReturnsPerfectResultForIdenticalFilesSSIM) { + EXPECT_EQ(0, I420SSIMFromFiles(video_file_.c_str(), video_file_.c_str(), + kWidth, kHeight, &ssim_result_)); + EXPECT_EQ(kSsimPerfectResult, ssim_result_.average); +} + +TEST_F(VideoMetricsTest, ReturnsPerfectResultForIdenticalFilesBothMetrics) { + EXPECT_EQ(0, I420MetricsFromFiles(video_file_.c_str(), video_file_.c_str(), + kWidth, kHeight, &psnr_result_, + &ssim_result_)); + EXPECT_EQ(kPsnrPerfectResult, psnr_result_.average); + EXPECT_EQ(kSsimPerfectResult, ssim_result_.average); +} + +// Tests that the right return code is given when the reference file is missing. +TEST_F(VideoMetricsTest, MissingReferenceFilePSNR) { + EXPECT_EQ(kMissingReferenceFileReturnCode, + I420PSNRFromFiles(non_existing_file_.c_str(), + video_file_.c_str(), kWidth, kHeight, + &ssim_result_)); +} + +TEST_F(VideoMetricsTest, MissingReferenceFileSSIM) { + EXPECT_EQ(kMissingReferenceFileReturnCode, + I420SSIMFromFiles(non_existing_file_.c_str(), + video_file_.c_str(), kWidth, kHeight, + &ssim_result_)); +} + +TEST_F(VideoMetricsTest, MissingReferenceFileBothMetrics) { + EXPECT_EQ(kMissingReferenceFileReturnCode, + I420MetricsFromFiles(non_existing_file_.c_str(), + video_file_.c_str(), kWidth, kHeight, + &psnr_result_, &ssim_result_)); +} + +// Tests that the right return code is given when the test file is missing. +TEST_F(VideoMetricsTest, MissingTestFilePSNR) { + EXPECT_EQ(kMissingTestFileReturnCode, + I420PSNRFromFiles(video_file_.c_str(), non_existing_file_.c_str(), + kWidth, kHeight, &ssim_result_)); +} + +TEST_F(VideoMetricsTest, MissingTestFileSSIM) { + EXPECT_EQ(kMissingTestFileReturnCode, + I420SSIMFromFiles(video_file_.c_str(), non_existing_file_.c_str(), + kWidth, kHeight, &ssim_result_)); +} + +TEST_F(VideoMetricsTest, MissingTestFileBothMetrics) { + EXPECT_EQ(kMissingTestFileReturnCode, + I420MetricsFromFiles(video_file_.c_str(), + non_existing_file_.c_str(), kWidth, kHeight, + &psnr_result_, &ssim_result_)); +} + +// Tests that the method can be executed with empty files. +TEST_F(VideoMetricsTest, EmptyFilesPSNR) { + EXPECT_EQ(kEmptyFileReturnCode, + I420PSNRFromFiles(empty_file_.c_str(), video_file_.c_str(), + kWidth, kHeight, &ssim_result_)); + EXPECT_EQ(kEmptyFileReturnCode, + I420PSNRFromFiles(video_file_.c_str(), empty_file_.c_str(), + kWidth, kHeight, &ssim_result_)); +} + +TEST_F(VideoMetricsTest, EmptyFilesSSIM) { + EXPECT_EQ(kEmptyFileReturnCode, + I420SSIMFromFiles(empty_file_.c_str(), video_file_.c_str(), + kWidth, kHeight, &ssim_result_)); + EXPECT_EQ(kEmptyFileReturnCode, + I420SSIMFromFiles(video_file_.c_str(), empty_file_.c_str(), + kWidth, kHeight, &ssim_result_)); +} + +TEST_F(VideoMetricsTest, EmptyFilesBothMetrics) { + EXPECT_EQ(kEmptyFileReturnCode, + I420MetricsFromFiles(empty_file_.c_str(), video_file_.c_str(), + kWidth, kHeight, + &psnr_result_, &ssim_result_)); + EXPECT_EQ(kEmptyFileReturnCode, + I420MetricsFromFiles(video_file_.c_str(), empty_file_.c_str(), + kWidth, kHeight, + &psnr_result_, &ssim_result_)); +} + +} // namespace webrtc diff --git a/webrtc/test/testsupport/mock/mock_frame_reader.h b/webrtc/test/testsupport/mock/mock_frame_reader.h new file mode 100644 index 0000000000..f06750ef4a --- /dev/null +++ b/webrtc/test/testsupport/mock/mock_frame_reader.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 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_TEST_TESTSUPPORT_MOCK_MOCK_FRAME_READER_H_ +#define WEBRTC_TEST_TESTSUPPORT_MOCK_MOCK_FRAME_READER_H_ + +#include "webrtc/test/testsupport/frame_reader.h" + +#include "testing/gmock/include/gmock/gmock.h" + +namespace webrtc { +namespace test { + +class MockFrameReader : public FrameReader { + public: + MOCK_METHOD0(Init, bool()); + MOCK_METHOD1(ReadFrame, bool(uint8_t* source_buffer)); + MOCK_METHOD0(Close, void()); + MOCK_METHOD0(FrameLength, size_t()); + MOCK_METHOD0(NumberOfFrames, int()); +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_TESTSUPPORT_MOCK_MOCK_FRAME_READER_H_ diff --git a/webrtc/test/testsupport/mock/mock_frame_writer.h b/webrtc/test/testsupport/mock/mock_frame_writer.h new file mode 100644 index 0000000000..10f58fb47b --- /dev/null +++ b/webrtc/test/testsupport/mock/mock_frame_writer.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2011 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_TEST_TESTSUPPORT_MOCK_MOCK_FRAME_WRITER_H_ +#define WEBRTC_TEST_TESTSUPPORT_MOCK_MOCK_FRAME_WRITER_H_ + +#include "webrtc/test/testsupport/frame_writer.h" + +#include "testing/gmock/include/gmock/gmock.h" + +namespace webrtc { +namespace test { + +class MockFrameWriter : public FrameWriter { + public: + MOCK_METHOD0(Init, bool()); + MOCK_METHOD1(WriteFrame, bool(uint8_t* frame_buffer)); + MOCK_METHOD0(Close, void()); + MOCK_METHOD0(FrameLength, size_t()); +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_TESTSUPPORT_MOCK_MOCK_FRAME_WRITER_H_ diff --git a/webrtc/test/testsupport/packet_reader.cc b/webrtc/test/testsupport/packet_reader.cc new file mode 100644 index 0000000000..e27ec22f16 --- /dev/null +++ b/webrtc/test/testsupport/packet_reader.cc @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2011 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/testsupport/packet_reader.h" + +#include <assert.h> +#include <stdio.h> +#include <algorithm> + +namespace webrtc { +namespace test { + +PacketReader::PacketReader() + : initialized_(false) {} + +PacketReader::~PacketReader() {} + +void PacketReader::InitializeReading(uint8_t* data, + size_t data_length_in_bytes, + size_t packet_size_in_bytes) { + assert(data); + assert(packet_size_in_bytes > 0); + data_ = data; + data_length_ = data_length_in_bytes; + packet_size_ = packet_size_in_bytes; + currentIndex_ = 0; + initialized_ = true; +} + +int PacketReader::NextPacket(uint8_t** packet_pointer) { + if (!initialized_) { + fprintf(stderr, "Attempting to use uninitialized PacketReader!\n"); + return -1; + } + *packet_pointer = data_ + currentIndex_; + size_t old_index = currentIndex_; + currentIndex_ = std::min(currentIndex_ + packet_size_, data_length_); + return static_cast<int>(currentIndex_ - old_index); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/packet_reader.h b/webrtc/test/testsupport/packet_reader.h new file mode 100644 index 0000000000..b58db4d093 --- /dev/null +++ b/webrtc/test/testsupport/packet_reader.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2011 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_TEST_TESTSUPPORT_PACKET_READER_H_ +#define WEBRTC_TEST_TESTSUPPORT_PACKET_READER_H_ + +#include <cstddef> +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +// Reads chunks of data to simulate network packets from a byte array. +class PacketReader { + public: + PacketReader(); + virtual ~PacketReader(); + + // Inizializes a new reading operation. Must be done before invoking the + // NextPacket method. + // * data_length_in_bytes is the length of the data byte array. + // 0 length will result in no packets are read. + // * packet_size_in_bytes is the number of bytes to read in each NextPacket + // method call. Must be > 0 + virtual void InitializeReading(uint8_t* data, size_t data_length_in_bytes, + size_t packet_size_in_bytes); + + // Moves the supplied pointer to the beginning of the next packet. + // Returns: + // * The size of the packet ready to read (lower than the packet size for + // the last packet) + // * 0 if there are no more packets to read + // * -1 if InitializeReading has not been called (also prints to stderr). + virtual int NextPacket(uint8_t** packet_pointer); + + private: + uint8_t* data_; + size_t data_length_; + size_t packet_size_; + size_t currentIndex_; + bool initialized_; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_TESTSUPPORT_PACKET_READER_H_ diff --git a/webrtc/test/testsupport/packet_reader_unittest.cc b/webrtc/test/testsupport/packet_reader_unittest.cc new file mode 100644 index 0000000000..2679be473f --- /dev/null +++ b/webrtc/test/testsupport/packet_reader_unittest.cc @@ -0,0 +1,125 @@ +/* + * 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/testsupport/packet_reader.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/test/testsupport/unittest_utils.h" + +namespace webrtc { +namespace test { + +class PacketReaderTest: public PacketRelatedTest { + protected: + PacketReaderTest() {} + virtual ~PacketReaderTest() {} + void SetUp() { + reader_ = new PacketReader(); + } + void TearDown() { + delete reader_; + } + void VerifyPacketData(size_t expected_length, + int actual_length, + uint8_t* original_data_pointer, + uint8_t* new_data_pointer) { + EXPECT_EQ(static_cast<int>(expected_length), actual_length); + EXPECT_EQ(*original_data_pointer, *new_data_pointer); + EXPECT_EQ(0, memcmp(original_data_pointer, new_data_pointer, + actual_length)); + } + PacketReader* reader_; +}; + +// Test lack of initialization +TEST_F(PacketReaderTest, Uninitialized) { + uint8_t* data_pointer = NULL; + EXPECT_EQ(-1, reader_->NextPacket(&data_pointer)); + EXPECT_EQ(NULL, data_pointer); +} + +TEST_F(PacketReaderTest, InitializeZeroLengthArgument) { + reader_->InitializeReading(packet_data_, 0, kPacketSizeInBytes); + ASSERT_EQ(0, reader_->NextPacket(&packet_data_pointer_)); +} + +// Test with something smaller than one packet +TEST_F(PacketReaderTest, NormalSmallData) { + const int kDataLengthInBytes = 1499; + uint8_t data[kDataLengthInBytes]; + uint8_t* data_pointer = data; + memset(data, 1, kDataLengthInBytes); + + reader_->InitializeReading(data, kDataLengthInBytes, kPacketSizeInBytes); + int length_to_read = reader_->NextPacket(&data_pointer); + VerifyPacketData(kDataLengthInBytes, length_to_read, data, data_pointer); + EXPECT_EQ(0, data_pointer - data); // pointer hasn't moved + + // Reading another one shall result in 0 bytes: + length_to_read = reader_->NextPacket(&data_pointer); + EXPECT_EQ(0, length_to_read); + EXPECT_EQ(kDataLengthInBytes, data_pointer - data); +} + +// Test with data length that exactly matches one packet +TEST_F(PacketReaderTest, NormalOnePacketData) { + uint8_t data[kPacketSizeInBytes]; + uint8_t* data_pointer = data; + memset(data, 1, kPacketSizeInBytes); + + reader_->InitializeReading(data, kPacketSizeInBytes, kPacketSizeInBytes); + int length_to_read = reader_->NextPacket(&data_pointer); + VerifyPacketData(kPacketSizeInBytes, length_to_read, data, data_pointer); + EXPECT_EQ(0, data_pointer - data); // pointer hasn't moved + + // Reading another one shall result in 0 bytes: + length_to_read = reader_->NextPacket(&data_pointer); + EXPECT_EQ(0, length_to_read); + EXPECT_EQ(kPacketSizeInBytes, static_cast<size_t>(data_pointer - data)); +} + +// Test with data length that will result in 3 packets +TEST_F(PacketReaderTest, NormalLargeData) { + reader_->InitializeReading(packet_data_, kPacketDataLength, + kPacketSizeInBytes); + + int length_to_read = reader_->NextPacket(&packet_data_pointer_); + VerifyPacketData(kPacketSizeInBytes, length_to_read, + packet1_, packet_data_pointer_); + + length_to_read = reader_->NextPacket(&packet_data_pointer_); + VerifyPacketData(kPacketSizeInBytes, length_to_read, + packet2_, packet_data_pointer_); + + length_to_read = reader_->NextPacket(&packet_data_pointer_); + VerifyPacketData(1u, length_to_read, + packet3_, packet_data_pointer_); + + // Reading another one shall result in 0 bytes: + length_to_read = reader_->NextPacket(&packet_data_pointer_); + EXPECT_EQ(0, length_to_read); + EXPECT_EQ(kPacketDataLength, + static_cast<size_t>(packet_data_pointer_ - packet_data_)); +} + +// Test with empty data. +TEST_F(PacketReaderTest, EmptyData) { + const int kDataLengthInBytes = 0; + // But don't really try to allocate a zero-length array... + uint8_t data[kPacketSizeInBytes]; + uint8_t* data_pointer = data; + reader_->InitializeReading(data, kDataLengthInBytes, kPacketSizeInBytes); + EXPECT_EQ(kDataLengthInBytes, reader_->NextPacket(&data_pointer)); + // Do it again to make sure nothing changes + EXPECT_EQ(kDataLengthInBytes, reader_->NextPacket(&data_pointer)); +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/perf_test.cc b/webrtc/test/testsupport/perf_test.cc new file mode 100644 index 0000000000..3266bb7945 --- /dev/null +++ b/webrtc/test/testsupport/perf_test.cc @@ -0,0 +1,177 @@ +/* + * 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. + */ + +// A stripped-down version of Chromium's chrome/test/perf/perf_test.cc. +// ResultsToString(), PrintResult(size_t value) and AppendResult(size_t value) +// have been modified. The remainder are identical to the Chromium version. + +#include "webrtc/test/testsupport/perf_test.h" + +#include <sstream> +#include <stdio.h> + +namespace { + +std::string ResultsToString(const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& values, + const std::string& prefix, + const std::string& suffix, + const std::string& units, + bool important) { + // <*>RESULT <graph_name>: <trace_name>= <value> <units> + // <*>RESULT <graph_name>: <trace_name>= {<mean>, <std deviation>} <units> + // <*>RESULT <graph_name>: <trace_name>= [<value>,value,value,...,] <units> + + // TODO(ajm): Use of a stream here may violate the style guide (depending on + // one's definition of "logging"). Consider adding StringPrintf-like + // functionality as in the original Chromium implementation. + std::ostringstream stream; + if (important) { + stream << "*"; + } + stream << "RESULT " << measurement << modifier << ": " << trace << "= " + << prefix << values << suffix << " " << units << std::endl; + return stream.str(); +} + +void PrintResultsImpl(const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& values, + const std::string& prefix, + const std::string& suffix, + const std::string& units, + bool important) { + printf("%s", ResultsToString(measurement, modifier, trace, values, + prefix, suffix, units, important).c_str()); +} + +} // namespace + +namespace webrtc { +namespace test { + +void PrintResult(const std::string& measurement, + const std::string& modifier, + const std::string& trace, + size_t value, + const std::string& units, + bool important) { + std::ostringstream value_stream; + value_stream << value; + PrintResultsImpl(measurement, modifier, trace, value_stream.str(), "", "", + units, important); +} + +void AppendResult(std::string& output, + const std::string& measurement, + const std::string& modifier, + const std::string& trace, + size_t value, + const std::string& units, + bool important) { + std::ostringstream value_stream; + value_stream << value; + output += ResultsToString(measurement, modifier, trace, + value_stream.str(), + "", "", units, important); +} + +void PrintResult(const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& value, + const std::string& units, + bool important) { + PrintResultsImpl(measurement, modifier, trace, value, "", "", units, + important); +} + +void AppendResult(std::string& output, + const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& value, + const std::string& units, + bool important) { + output += ResultsToString(measurement, modifier, trace, value, "", "", units, + important); +} + +void PrintResultMeanAndError(const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& mean_and_error, + const std::string& units, + bool important) { + PrintResultsImpl(measurement, modifier, trace, mean_and_error, + "{", "}", units, important); +} + +void AppendResultMeanAndError(std::string& output, + const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& mean_and_error, + const std::string& units, + bool important) { + output += ResultsToString(measurement, modifier, trace, mean_and_error, + "{", "}", units, important); +} + +void PrintResultList(const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& values, + const std::string& units, + bool important) { + PrintResultsImpl(measurement, modifier, trace, values, + "[", "]", units, important); +} + +void AppendResultList(std::string& output, + const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& values, + const std::string& units, + bool important) { + output += ResultsToString(measurement, modifier, trace, values, + "[", "]", units, important); +} + +void PrintSystemCommitCharge(const std::string& test_name, + size_t charge, + bool important) { + PrintSystemCommitCharge(stdout, test_name, charge, important); +} + +void PrintSystemCommitCharge(FILE* target, + const std::string& test_name, + size_t charge, + bool important) { + fprintf(target, "%s", SystemCommitChargeToString(test_name, charge, + important).c_str()); +} + +std::string SystemCommitChargeToString(const std::string& test_name, + size_t charge, + bool important) { + std::string trace_name(test_name); + std::string output; + AppendResult(output, "commit_charge", "", "cc" + trace_name, charge, "kb", + important); + return output; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/perf_test.h b/webrtc/test/testsupport/perf_test.h new file mode 100644 index 0000000000..76e09e1cbb --- /dev/null +++ b/webrtc/test/testsupport/perf_test.h @@ -0,0 +1,122 @@ +/* + * 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. + */ + +// A stripped-down version of Chromium's chrome/test/perf/perf_test.h. +// Several functions have been removed; the prototypes of the remainder have +// not been changed. + +#ifndef WEBRTC_TEST_TESTSUPPORT_PERF_TEST_H_ +#define WEBRTC_TEST_TESTSUPPORT_PERF_TEST_H_ + +#include <string> + +namespace webrtc { +namespace test { + +// Prints numerical information to stdout in a controlled format, for +// post-processing. |measurement| is a description of the quantity being +// measured, e.g. "vm_peak"; |modifier| is provided as a convenience and +// will be appended directly to the name of the |measurement|, e.g. +// "_browser"; |trace| is a description of the particular data point, e.g. +// "reference"; |value| is the measured value; and |units| is a description +// of the units of measure, e.g. "bytes". If |important| is true, the output +// line will be specially marked, to notify the post-processor. The strings +// may be empty. They should not contain any colons (:) or equals signs (=). +// A typical post-processing step would be to produce graphs of the data +// produced for various builds, using the combined |measurement| + |modifier| +// string to specify a particular graph and the |trace| to identify a trace +// (i.e., data series) on that graph. +void PrintResult(const std::string& measurement, + const std::string& modifier, + const std::string& trace, + size_t value, + const std::string& units, + bool important); + +void AppendResult(std::string& output, + const std::string& measurement, + const std::string& modifier, + const std::string& trace, + size_t value, + const std::string& units, + bool important); + +// Like the above version of PrintResult(), but takes a std::string value +// instead of a size_t. +void PrintResult(const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& value, + const std::string& units, + bool important); + +void AppendResult(std::string& output, + const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& value, + const std::string& units, + bool important); + +// Like PrintResult(), but prints a (mean, standard deviation) result pair. +// The |<values>| should be two comma-separated numbers, the mean and +// standard deviation (or other error metric) of the measurement. +void PrintResultMeanAndError(const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& mean_and_error, + const std::string& units, + bool important); + +void AppendResultMeanAndError(std::string& output, + const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& mean_and_error, + const std::string& units, + bool important); + +// Like PrintResult(), but prints an entire list of results. The |values| +// will generally be a list of comma-separated numbers. A typical +// post-processing step might produce plots of their mean and standard +// deviation. +void PrintResultList(const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& values, + const std::string& units, + bool important); + +void AppendResultList(std::string& output, + const std::string& measurement, + const std::string& modifier, + const std::string& trace, + const std::string& values, + const std::string& units, + bool important); + +// Prints memory commit charge stats for use by perf graphs. +void PrintSystemCommitCharge(const std::string& test_name, + size_t charge, + bool important); + +void PrintSystemCommitCharge(FILE* target, + const std::string& test_name, + size_t charge, + bool important); + +std::string SystemCommitChargeToString(const std::string& test_name, + size_t charge, + bool important); + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_TESTSUPPORT_PERF_TEST_H_ diff --git a/webrtc/test/testsupport/perf_test_unittest.cc b/webrtc/test/testsupport/perf_test_unittest.cc new file mode 100644 index 0000000000..f7e50e1bc6 --- /dev/null +++ b/webrtc/test/testsupport/perf_test_unittest.cc @@ -0,0 +1,34 @@ +/* + * 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/testsupport/perf_test.h" + +#include <string> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace webrtc { +namespace test { + +TEST(PerfTest, AppendResult) { + std::string expected = "RESULT measurementmodifier: trace= 42 units\n"; + std::string output; + AppendResult(output, "measurement", "modifier", "trace", 42, "units", false); + EXPECT_EQ(expected, output); + std::cout << output; + + expected += "*RESULT foobar: baz= 7 widgets\n"; + AppendResult(output, "foo", "bar", "baz", 7, "widgets", true); + EXPECT_EQ(expected, output); + std::cout << output; +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/trace_to_stderr.cc b/webrtc/test/testsupport/trace_to_stderr.cc new file mode 100644 index 0000000000..7338441fa5 --- /dev/null +++ b/webrtc/test/testsupport/trace_to_stderr.cc @@ -0,0 +1,63 @@ +/* + * 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/test/testsupport/trace_to_stderr.h" + +#include <assert.h> +#include <stdio.h> + +#include <string> + +namespace webrtc { +namespace test { + +static const int kLevelFilter = kTraceError | kTraceWarning | kTraceTerseInfo; + +TraceToStderr::TraceToStderr() + : override_time_(false), + time_seconds_(0) { + Trace::set_level_filter(kLevelFilter); + Trace::CreateTrace(); + Trace::SetTraceCallback(this); +} + +TraceToStderr::TraceToStderr(bool override_time) + : override_time_(override_time), + time_seconds_(0) { + Trace::set_level_filter(kLevelFilter); + Trace::CreateTrace(); + Trace::SetTraceCallback(this); +} + +TraceToStderr::~TraceToStderr() { + Trace::SetTraceCallback(NULL); + Trace::ReturnTrace(); +} + +void TraceToStderr::SetTimeSeconds(float time) { time_seconds_ = time; } + +void TraceToStderr::Print(TraceLevel level, const char* msg_array, int length) { + if (level & kLevelFilter) { + assert(length > Trace::kBoilerplateLength); + std::string msg = msg_array; + std::string msg_log = msg.substr(Trace::kBoilerplateLength); + if (override_time_) { + fprintf(stderr, "%.2fs %s\n", time_seconds_, msg_log.c_str()); + } else { + std::string msg_time = msg.substr(Trace::kTimestampPosition, + Trace::kTimestampLength); + fprintf(stderr, "%s %s\n", msg_time.c_str(), msg_log.c_str()); + } + fflush(stderr); + } +} + +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/testsupport/trace_to_stderr.h b/webrtc/test/testsupport/trace_to_stderr.h new file mode 100644 index 0000000000..a713b798c5 --- /dev/null +++ b/webrtc/test/testsupport/trace_to_stderr.h @@ -0,0 +1,52 @@ +/* + * 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_TEST_TEST_SUPPORT_TRACE_TO_STDERR_H_ +#define WEBRTC_TEST_TEST_SUPPORT_TRACE_TO_STDERR_H_ + +#include "webrtc/system_wrappers/include/trace.h" + +namespace webrtc { +namespace test { + +// Upon constructing an instance of this class, all traces will be redirected +// to stderr. At destruction, redirection is halted. +class TraceToStderr : public TraceCallback { + public: + TraceToStderr(); + // Set |override_time| to true to control the time printed with each trace + // through SetTimeSeconds(). Otherwise, the trace's usual wallclock time is + // used. + // + // This is useful for offline test tools, where the file time is much more + // informative than the real time. + explicit TraceToStderr(bool override_time); + ~TraceToStderr() override; + + // Every subsequent trace printout will use |time|. Has no effect if + // |override_time| in the constructor was set to false. + // + // No attempt is made to ensure thread-safety between the trace writing and + // time updating. In tests, since traces will normally be triggered by the + // main thread doing the time updating, this should be of no concern. + virtual void SetTimeSeconds(float time); + + // Implements TraceCallback. + void Print(TraceLevel level, const char* msg_array, int length) override; + + private: + bool override_time_; + float time_seconds_; +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_TEST_SUPPORT_TRACE_TO_STDERR_H_ diff --git a/webrtc/test/testsupport/unittest_utils.h b/webrtc/test/testsupport/unittest_utils.h new file mode 100644 index 0000000000..ba6db9883b --- /dev/null +++ b/webrtc/test/testsupport/unittest_utils.h @@ -0,0 +1,56 @@ +/* + * 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_TEST_TESTSUPPORT_UNITTEST_UTILS_H_ +#define WEBRTC_TEST_TESTSUPPORT_UNITTEST_UTILS_H_ + +namespace webrtc { +namespace test { + +const size_t kPacketSizeInBytes = 1500; +const size_t kPacketDataLength = kPacketSizeInBytes * 2 + 1; +const int kPacketDataNumberOfPackets = 3; + +// A base test fixture for packet related tests. Contains +// two full prepared packets with 1s, 2s in their data and a third packet with +// a single 3 in it (size=1). +// A packet data structure is also available, that contains these three packets +// in order. +class PacketRelatedTest: public testing::Test { + protected: + // Tree packet byte arrays with data used for verification: + uint8_t packet1_[kPacketSizeInBytes]; + uint8_t packet2_[kPacketSizeInBytes]; + uint8_t packet3_[1]; + // Construct a data structure containing these packets + uint8_t packet_data_[kPacketDataLength]; + uint8_t* packet_data_pointer_; + + PacketRelatedTest() { + packet_data_pointer_ = packet_data_; + + memset(packet1_, 1, kPacketSizeInBytes); + memset(packet2_, 2, kPacketSizeInBytes); + memset(packet3_, 3, 1); + // Fill the packet_data: + memcpy(packet_data_pointer_, packet1_, kPacketSizeInBytes); + memcpy(packet_data_pointer_ + kPacketSizeInBytes, packet2_, + kPacketSizeInBytes); + memcpy(packet_data_pointer_ + kPacketSizeInBytes * 2, packet3_, 1); + } + virtual ~PacketRelatedTest() {} + void SetUp() {} + void TearDown() {} +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_TEST_TESTSUPPORT_UNITTEST_UTILS_H_ diff --git a/webrtc/test/vcm_capturer.cc b/webrtc/test/vcm_capturer.cc new file mode 100644 index 0000000000..1c6b91915e --- /dev/null +++ b/webrtc/test/vcm_capturer.cc @@ -0,0 +1,107 @@ +/* + * 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/test/vcm_capturer.h" + +#include "webrtc/modules/video_capture/include/video_capture_factory.h" +#include "webrtc/video_send_stream.h" + +namespace webrtc { +namespace test { + +VcmCapturer::VcmCapturer(webrtc::VideoCaptureInput* input) + : VideoCapturer(input), started_(false), vcm_(NULL) { +} + +bool VcmCapturer::Init(size_t width, size_t height, size_t target_fps) { + VideoCaptureModule::DeviceInfo* device_info = + VideoCaptureFactory::CreateDeviceInfo(42); // Any ID (42) will do. + + char device_name[256]; + char unique_name[256]; + if (device_info->GetDeviceName(0, device_name, sizeof(device_name), + unique_name, sizeof(unique_name)) != + 0) { + Destroy(); + return false; + } + + vcm_ = webrtc::VideoCaptureFactory::Create(0, unique_name); + vcm_->RegisterCaptureDataCallback(*this); + + device_info->GetCapability(vcm_->CurrentDeviceName(), 0, capability_); + delete device_info; + + capability_.width = static_cast<int32_t>(width); + capability_.height = static_cast<int32_t>(height); + capability_.maxFPS = static_cast<int32_t>(target_fps); + capability_.rawType = kVideoI420; + + if (vcm_->StartCapture(capability_) != 0) { + Destroy(); + return false; + } + + assert(vcm_->CaptureStarted()); + + return true; +} + +VcmCapturer* VcmCapturer::Create(VideoCaptureInput* input, + size_t width, + size_t height, + size_t target_fps) { + VcmCapturer* vcm_capturer = new VcmCapturer(input); + if (!vcm_capturer->Init(width, height, target_fps)) { + // TODO(pbos): Log a warning that this failed. + delete vcm_capturer; + return NULL; + } + return vcm_capturer; +} + + +void VcmCapturer::Start() { + rtc::CritScope lock(&crit_); + started_ = true; +} + +void VcmCapturer::Stop() { + rtc::CritScope lock(&crit_); + started_ = false; +} + +void VcmCapturer::Destroy() { + if (vcm_ == NULL) { + return; + } + + vcm_->StopCapture(); + vcm_->DeRegisterCaptureDataCallback(); + vcm_->Release(); + + // TODO(pbos): How do I destroy the VideoCaptureModule? This still leaves + // non-freed memory. + vcm_ = NULL; +} + +VcmCapturer::~VcmCapturer() { Destroy(); } + +void VcmCapturer::OnIncomingCapturedFrame(const int32_t id, + const VideoFrame& frame) { + rtc::CritScope lock(&crit_); + if (started_) + input_->IncomingCapturedFrame(frame); +} + +void VcmCapturer::OnCaptureDelayChanged(const int32_t id, const int32_t delay) { +} +} // test +} // webrtc diff --git a/webrtc/test/vcm_capturer.h b/webrtc/test/vcm_capturer.h new file mode 100644 index 0000000000..53d61fc53a --- /dev/null +++ b/webrtc/test/vcm_capturer.h @@ -0,0 +1,50 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_VCM_CAPTURER_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_VCM_CAPTURER_H_ + +#include "webrtc/base/criticalsection.h" +#include "webrtc/common_types.h" +#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" +#include "webrtc/modules/video_capture/include/video_capture.h" +#include "webrtc/test/video_capturer.h" + +namespace webrtc { +namespace test { + +class VcmCapturer : public VideoCapturer, public VideoCaptureDataCallback { + public: + static VcmCapturer* Create(VideoCaptureInput* input, + size_t width, + size_t height, + size_t target_fps); + virtual ~VcmCapturer(); + + void Start() override; + void Stop() override; + + void OnIncomingCapturedFrame(const int32_t id, + const VideoFrame& frame) override; // NOLINT + void OnCaptureDelayChanged(const int32_t id, const int32_t delay) override; + + private: + explicit VcmCapturer(VideoCaptureInput* input); + bool Init(size_t width, size_t height, size_t target_fps); + void Destroy(); + + rtc::CriticalSection crit_; + bool started_ GUARDED_BY(crit_); + VideoCaptureModule* vcm_; + VideoCaptureCapability capability_; +}; +} // test +} // webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_VCM_CAPTURER_H_ diff --git a/webrtc/test/video_capturer.cc b/webrtc/test/video_capturer.cc new file mode 100644 index 0000000000..840378f013 --- /dev/null +++ b/webrtc/test/video_capturer.cc @@ -0,0 +1,54 @@ +/* + * 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/test/video_capturer.h" + +#include "webrtc/test/testsupport/fileutils.h" +#include "webrtc/test/frame_generator_capturer.h" +#include "webrtc/test/vcm_capturer.h" + +namespace webrtc { +namespace test { + +class NullCapturer : public VideoCapturer { + public: + NullCapturer() : VideoCapturer(NULL) {} + virtual ~NullCapturer() {} + + virtual void Start() {} + virtual void Stop() {} +}; + +VideoCapturer::VideoCapturer(VideoCaptureInput* input) : input_(input) { +} + +VideoCapturer* VideoCapturer::Create(VideoCaptureInput* input, + size_t width, + size_t height, + int fps, + Clock* clock) { + VcmCapturer* vcm_capturer = VcmCapturer::Create(input, width, height, fps); + + if (vcm_capturer != NULL) { + return vcm_capturer; + } + // TODO(pbos): Log a warning that this failed. + + FrameGeneratorCapturer* frame_generator_capturer = + FrameGeneratorCapturer::Create(input, width, height, fps, clock); + if (frame_generator_capturer != NULL) { + return frame_generator_capturer; + } + // TODO(pbos): Log a warning that this failed. + + return new NullCapturer(); +} +} // test +} // webrtc diff --git a/webrtc/test/video_capturer.h b/webrtc/test/video_capturer.h new file mode 100644 index 0000000000..3fe86f1998 --- /dev/null +++ b/webrtc/test/video_capturer.h @@ -0,0 +1,42 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_VIDEO_CAPTURER_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_VIDEO_CAPTURER_H_ + +#include <stddef.h> + +namespace webrtc { + +class Clock; + +class VideoCaptureInput; + +namespace test { + +class VideoCapturer { + public: + static VideoCapturer* Create(VideoCaptureInput* input, + size_t width, + size_t height, + int fps, + Clock* clock); + virtual ~VideoCapturer() {} + + virtual void Start() = 0; + virtual void Stop() = 0; + + protected: + explicit VideoCapturer(VideoCaptureInput* input); + VideoCaptureInput* input_; +}; +} // test +} // webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_VIDEO_CAPTURER_H_ diff --git a/webrtc/test/video_renderer.cc b/webrtc/test/video_renderer.cc new file mode 100644 index 0000000000..c7b60e5949 --- /dev/null +++ b/webrtc/test/video_renderer.cc @@ -0,0 +1,37 @@ +/* + * 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/test/video_renderer.h" + +// TODO(pbos): Android renderer + +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +class NullRenderer : public VideoRenderer { + void RenderFrame(const VideoFrame& video_frame, + int time_to_render_ms) override {} + bool IsTextureSupported() const override { return false; } +}; + +VideoRenderer* VideoRenderer::Create(const char* window_title, + size_t width, + size_t height) { + VideoRenderer* renderer = CreatePlatformRenderer(window_title, width, height); + if (renderer != NULL) { + // TODO(mflodman) Add a warning log. + return renderer; + } + + return new NullRenderer(); +} +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/video_renderer.h b/webrtc/test/video_renderer.h new file mode 100644 index 0000000000..c8623270a7 --- /dev/null +++ b/webrtc/test/video_renderer.h @@ -0,0 +1,39 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_VIDEO_RENDERER_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_VIDEO_RENDERER_H_ + +#include <stddef.h> + +#include "webrtc/video_renderer.h" + +namespace webrtc { +namespace test { +class VideoRenderer : public webrtc::VideoRenderer { + public: + // Creates a platform-specific renderer if possible, or a null implementation + // if failing. + static VideoRenderer* Create(const char* window_title, size_t width, + size_t height); + // Returns a renderer rendering to a platform specific window if possible, + // NULL if none can be created. + // Creates a platform-specific renderer if possible, returns NULL if a + // platform renderer could not be created. This occurs, for instance, when + // running without an X environment on Linux. + static VideoRenderer* CreatePlatformRenderer(const char* window_title, + size_t width, size_t height); + virtual ~VideoRenderer() {} + protected: + VideoRenderer() {} +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_VIDEO_RENDERER_H_ diff --git a/webrtc/test/webrtc_test_common.gyp b/webrtc/test/webrtc_test_common.gyp new file mode 100644 index 0000000000..d075cb470b --- /dev/null +++ b/webrtc/test/webrtc_test_common.gyp @@ -0,0 +1,176 @@ +# 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. +{ + 'includes': [ + '../build/common.gypi', + ], + 'targets': [ + { + 'target_name': 'webrtc_test_common', + 'type': 'static_library', + 'sources': [ + 'call_test.cc', + 'call_test.h', + 'configurable_frame_size_encoder.cc', + 'configurable_frame_size_encoder.h', + 'constants.cc', + 'constants.h', + 'direct_transport.cc', + 'direct_transport.h', + 'encoder_settings.cc', + 'encoder_settings.h', + 'fake_audio_device.cc', + 'fake_audio_device.h', + 'fake_decoder.cc', + 'fake_decoder.h', + 'fake_encoder.cc', + 'fake_encoder.h', + 'fake_network_pipe.cc', + 'fake_network_pipe.h', + 'fake_voice_engine.cc', + 'fake_voice_engine.h', + 'frame_generator_capturer.cc', + 'frame_generator_capturer.h', + 'layer_filtering_transport.cc', + 'layer_filtering_transport.h', + 'mock_transport.h', + 'null_transport.cc', + 'null_transport.h', + 'random.cc', + 'random.h', + 'rtp_rtcp_observer.h', + 'run_loop.cc', + 'run_loop.h', + 'statistics.cc', + 'statistics.h', + 'vcm_capturer.cc', + 'vcm_capturer.h', + 'video_capturer.cc', + 'video_capturer.h', + 'win/run_loop_win.cc', + ], + 'conditions': [ + ['OS=="win"', { + 'sources!': [ + 'run_loop.cc', + ], + }], + ], + 'dependencies': [ + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/third_party/gflags/gflags.gyp:gflags', + '<(webrtc_root)/base/base.gyp:rtc_base', + '<(webrtc_root)/common.gyp:webrtc_common', + '<(webrtc_root)/modules/modules.gyp:media_file', + '<(webrtc_root)/modules/modules.gyp:video_render', + '<(webrtc_root)/test/test.gyp:frame_generator', + '<(webrtc_root)/test/test.gyp:test_support', + '<(webrtc_root)/test/test.gyp:rtp_test_utils', + '<(webrtc_root)/webrtc.gyp:webrtc', + ], + }, + { + 'target_name': 'webrtc_test_renderer', + 'type': 'static_library', + 'sources': [ + 'gl/gl_renderer.cc', + 'gl/gl_renderer.h', + 'linux/glx_renderer.cc', + 'linux/glx_renderer.h', + 'linux/video_renderer_linux.cc', + 'mac/video_renderer_mac.h', + 'mac/video_renderer_mac.mm', + 'null_platform_renderer.cc', + 'video_renderer.cc', + 'video_renderer.h', + 'win/d3d_renderer.cc', + 'win/d3d_renderer.h', + ], + 'conditions': [ + ['OS=="linux"', { + 'sources!': [ + 'null_platform_renderer.cc', + ], + }], + ['OS=="mac"', { + 'sources!': [ + 'null_platform_renderer.cc', + ], + }], + ['OS!="linux" and OS!="mac"', { + 'sources!' : [ + 'gl/gl_renderer.cc', + 'gl/gl_renderer.h', + ], + }], + ['OS=="win"', { + 'sources!': [ + 'null_platform_renderer.cc', + ], + 'include_dirs': [ + '<(directx_sdk_path)/Include', + ], + }], + ], + 'dependencies': [ + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(webrtc_root)/modules/modules.gyp:media_file', + '<(webrtc_root)/test/test.gyp:frame_generator', + '<(webrtc_root)/test/test.gyp:test_support', + ], + 'direct_dependent_settings': { + 'conditions': [ + ['OS=="linux"', { + 'libraries': [ + '-lXext', + '-lX11', + '-lGL', + ], + }], + ['OS=="android"', { + 'libraries' : [ + '-lGLESv2', '-llog', + ], + }], + ['OS=="mac"', { + 'xcode_settings' : { + 'OTHER_LDFLAGS' : [ + '-framework Cocoa', + '-framework OpenGL', + '-framework CoreVideo', + ], + }, + }], + ], + }, + }, + ], + 'conditions': [ + ['include_tests==1', { + 'targets': [ + { + 'target_name': 'webrtc_test_common_unittests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + 'webrtc_test_common', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/testing/gmock.gyp:gmock', + '<(webrtc_root)/modules/modules.gyp:video_capture', + '<(webrtc_root)/test/test.gyp:test_support_main', + ], + 'sources': [ + 'fake_network_pipe_unittest.cc', + 'frame_generator_unittest.cc', + 'rtp_file_reader_unittest.cc', + 'rtp_file_writer_unittest.cc', + ], + }, + ], #targets + }], # include_tests + ], # conditions +} diff --git a/webrtc/test/win/d3d_renderer.cc b/webrtc/test/win/d3d_renderer.cc new file mode 100644 index 0000000000..86900e93dd --- /dev/null +++ b/webrtc/test/win/d3d_renderer.cc @@ -0,0 +1,219 @@ +/* + * 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/test/win/d3d_renderer.h" + +#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" + +namespace webrtc { +namespace test { + +#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_TEX1) + +struct D3dCustomVertex { + float x, y, z; + float u, v; +}; + +const char kD3DClassName[] = "d3d_renderer"; + +VideoRenderer* VideoRenderer::CreatePlatformRenderer(const char* window_title, + size_t width, + size_t height) { + return D3dRenderer::Create(window_title, width, height); +} + +D3dRenderer::D3dRenderer(size_t width, size_t height) + : width_(width), + height_(height), + hwnd_(NULL), + d3d_(NULL), + d3d_device_(NULL), + texture_(NULL), + vertex_buffer_(NULL) { + assert(width > 0); + assert(height > 0); +} + +D3dRenderer::~D3dRenderer() { Destroy(); } + +LRESULT WINAPI D3dRenderer::WindowProc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) { + if (msg == WM_DESTROY || (msg == WM_CHAR && wparam == VK_RETURN)) { + PostQuitMessage(0); + return 0; + } + + return DefWindowProcA(hwnd, msg, wparam, lparam); +} + +void D3dRenderer::Destroy() { + texture_ = NULL; + vertex_buffer_ = NULL; + d3d_device_ = NULL; + d3d_ = NULL; + + if (hwnd_ != NULL) { + DestroyWindow(hwnd_); + assert(!IsWindow(hwnd_)); + hwnd_ = NULL; + } +} + +bool D3dRenderer::Init(const char* window_title) { + hwnd_ = CreateWindowA(kD3DClassName, + window_title, + WS_OVERLAPPEDWINDOW, + 0, + 0, + static_cast<int>(width_), + static_cast<int>(height_), + NULL, + NULL, + NULL, + NULL); + + if (hwnd_ == NULL) { + Destroy(); + return false; + } + + d3d_ = Direct3DCreate9(D3D_SDK_VERSION); + if (d3d_ == NULL) { + Destroy(); + return false; + } + + D3DPRESENT_PARAMETERS d3d_params = {}; + + d3d_params.Windowed = TRUE; + d3d_params.SwapEffect = D3DSWAPEFFECT_COPY; + + IDirect3DDevice9* d3d_device; + if (d3d_->CreateDevice(D3DADAPTER_DEFAULT, + D3DDEVTYPE_HAL, + hwnd_, + D3DCREATE_SOFTWARE_VERTEXPROCESSING, + &d3d_params, + &d3d_device) != D3D_OK) { + Destroy(); + return false; + } + d3d_device_ = d3d_device; + d3d_device->Release(); + + IDirect3DVertexBuffer9* vertex_buffer; + const int kRectVertices = 4; + if (d3d_device_->CreateVertexBuffer(kRectVertices * sizeof(D3dCustomVertex), + 0, + D3DFVF_CUSTOMVERTEX, + D3DPOOL_MANAGED, + &vertex_buffer, + NULL) != D3D_OK) { + Destroy(); + return false; + } + vertex_buffer_ = vertex_buffer; + vertex_buffer->Release(); + + d3d_device_->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + d3d_device_->SetRenderState(D3DRS_LIGHTING, FALSE); + Resize(width_, height_); + + ShowWindow(hwnd_, SW_SHOWNOACTIVATE); + d3d_device_->Present(NULL, NULL, NULL, NULL); + + return true; +} + +D3dRenderer* D3dRenderer::Create(const char* window_title, + size_t width, + size_t height) { + static ATOM wc_atom = 0; + if (wc_atom == 0) { + WNDCLASSA wc = {}; + + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WindowProc; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW); + wc.lpszClassName = kD3DClassName; + + wc_atom = RegisterClassA(&wc); + if (wc_atom == 0) + return false; + } + + D3dRenderer* d3d_renderer = new D3dRenderer(width, height); + if (!d3d_renderer->Init(window_title)) { + delete d3d_renderer; + return NULL; + } + + return d3d_renderer; +} + +void D3dRenderer::Resize(size_t width, size_t height) { + width_ = width; + height_ = height; + IDirect3DTexture9* texture; + + d3d_device_->CreateTexture(static_cast<UINT>(width_), + static_cast<UINT>(height_), + 1, + 0, + D3DFMT_A8R8G8B8, + D3DPOOL_MANAGED, + &texture, + NULL); + texture_ = texture; + texture->Release(); + + // Vertices for the video frame to be rendered to. + static const D3dCustomVertex rect[] = { + {-1.0f, -1.0f, 0.0f, 0.0f, 1.0f}, + {-1.0f, 1.0f, 0.0f, 0.0f, 0.0f}, + {1.0f, -1.0f, 0.0f, 1.0f, 1.0f}, + {1.0f, 1.0f, 0.0f, 1.0f, 0.0f}, + }; + + void* buf_data; + if (vertex_buffer_->Lock(0, 0, &buf_data, 0) != D3D_OK) + return; + + memcpy(buf_data, &rect, sizeof(rect)); + vertex_buffer_->Unlock(); +} + +void D3dRenderer::RenderFrame(const webrtc::VideoFrame& frame, + int /*render_delay_ms*/) { + if (static_cast<size_t>(frame.width()) != width_ || + static_cast<size_t>(frame.height()) != height_) { + Resize(static_cast<size_t>(frame.width()), + static_cast<size_t>(frame.height())); + } + + D3DLOCKED_RECT lock_rect; + if (texture_->LockRect(0, &lock_rect, NULL, 0) != D3D_OK) + return; + + ConvertFromI420(frame, kARGB, 0, static_cast<uint8_t*>(lock_rect.pBits)); + texture_->UnlockRect(0); + + d3d_device_->BeginScene(); + d3d_device_->SetFVF(D3DFVF_CUSTOMVERTEX); + d3d_device_->SetStreamSource(0, vertex_buffer_, 0, sizeof(D3dCustomVertex)); + d3d_device_->SetTexture(0, texture_); + d3d_device_->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + d3d_device_->EndScene(); + + d3d_device_->Present(NULL, NULL, NULL, NULL); +} +} // namespace test +} // namespace webrtc diff --git a/webrtc/test/win/d3d_renderer.h b/webrtc/test/win/d3d_renderer.h new file mode 100644 index 0000000000..46ce266460 --- /dev/null +++ b/webrtc/test/win/d3d_renderer.h @@ -0,0 +1,54 @@ +/* + * 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_VIDEO_ENGINE_TEST_COMMON_WIN_D3D_RENDERER_H_ +#define WEBRTC_VIDEO_ENGINE_TEST_COMMON_WIN_D3D_RENDERER_H_ + +#include <Windows.h> +#include <d3d9.h> +#pragma comment(lib, "d3d9.lib") // located in DirectX SDK + +#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/test/video_renderer.h" +#include "webrtc/typedefs.h" + +namespace webrtc { +namespace test { + +class D3dRenderer : public VideoRenderer { + public: + static D3dRenderer* Create(const char* window_title, size_t width, + size_t height); + virtual ~D3dRenderer(); + + void RenderFrame(const webrtc::VideoFrame& frame, int delta) override; + bool IsTextureSupported() const override { return false; } + + private: + D3dRenderer(size_t width, size_t height); + + static LRESULT WINAPI WindowProc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); + bool Init(const char* window_title); + void Resize(size_t width, size_t height); + void Destroy(); + + size_t width_, height_; + + HWND hwnd_; + rtc::scoped_refptr<IDirect3D9> d3d_; + rtc::scoped_refptr<IDirect3DDevice9> d3d_device_; + + rtc::scoped_refptr<IDirect3DTexture9> texture_; + rtc::scoped_refptr<IDirect3DVertexBuffer9> vertex_buffer_; +}; +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_TEST_COMMON_WIN_D3D_RENDERER_H_ diff --git a/webrtc/test/win/run_loop_win.cc b/webrtc/test/win/run_loop_win.cc new file mode 100644 index 0000000000..ec29cc5a67 --- /dev/null +++ b/webrtc/test/win/run_loop_win.cc @@ -0,0 +1,33 @@ +/* + * 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/test/run_loop.h" + +#include <assert.h> + +#include <conio.h> +#include <stdio.h> +#include <Windows.h> + +namespace webrtc { +namespace test { + +void PressEnterToContinue() { + puts(">> Press ENTER to continue..."); + + MSG msg; + BOOL ret; + while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0) { + assert(ret != -1); + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} +} // namespace test +} // namespace webrtc |