diff --git a/webrtc/voice_engine/test/auto_test/fakes/conference_transport.cc b/webrtc/voice_engine/test/auto_test/fakes/conference_transport.cc new file mode 100644 index 0000000000..97b95c474a --- /dev/null +++ b/webrtc/voice_engine/test/auto_test/fakes/conference_transport.cc @@ -0,0 +1,275 @@ +/* + * 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/voice_engine/test/auto_test/fakes/conference_transport.h" + +#include + +#include "webrtc/base/byteorder.h" +#include "webrtc/base/timeutils.h" +#include "webrtc/test/testsupport/fileutils.h" +#include "webrtc/system_wrappers/interface/sleep.h" + +namespace { + static const unsigned int kReflectorSsrc = 0x0000; + static const unsigned int kLocalSsrc = 0x0001; + static const unsigned int kFirstRemoteSsrc = 0x0002; + static const std::string kInputFileName = + webrtc::test::ResourcePath("voice_engine/audio_long16", "pcm"); + static const webrtc::FileFormats kInputFileFormat = + webrtc::kFileFormatPcm16kHzFile; + static const webrtc::CodecInst kCodecInst = + {120, "opus", 48000, 960, 2, 64000}; + + static unsigned int ParseSsrc(const void* data, size_t len, bool rtcp) { + const size_t ssrc_pos = (!rtcp) ? 8 : 4; + unsigned int ssrc = 0; + if (len >= (ssrc_pos + sizeof(ssrc))) { + ssrc = rtc::GetBE32(static_cast(data) + ssrc_pos); + } + return ssrc; + } +} // namespace + +namespace voetest { + +ConferenceTransport::ConferenceTransport() + : pq_crit_(webrtc::CriticalSectionWrapper::CreateCriticalSection()), + stream_crit_(webrtc::CriticalSectionWrapper::CreateCriticalSection()), + packet_event_(webrtc::EventWrapper::Create()), + thread_(webrtc::ThreadWrapper::CreateThread(Run, + this, + "ConferenceTransport")), + rtt_ms_(0), + stream_count_(0) { + local_voe_ = webrtc::VoiceEngine::Create(); + local_base_ = webrtc::VoEBase::GetInterface(local_voe_); + local_network_ = webrtc::VoENetwork::GetInterface(local_voe_); + local_rtp_rtcp_ = webrtc::VoERTP_RTCP::GetInterface(local_voe_); + + // In principle, we can use one VoiceEngine to achieve the same goal. Well, in + // here, we use two engines to make it more like reality. + remote_voe_ = webrtc::VoiceEngine::Create(); + remote_base_ = webrtc::VoEBase::GetInterface(remote_voe_); + remote_codec_ = webrtc::VoECodec::GetInterface(remote_voe_); + remote_network_ = webrtc::VoENetwork::GetInterface(remote_voe_); + remote_rtp_rtcp_ = webrtc::VoERTP_RTCP::GetInterface(remote_voe_); + remote_file_ = webrtc::VoEFile::GetInterface(remote_voe_); + + EXPECT_EQ(0, local_base_->Init()); + local_sender_ = local_base_->CreateChannel(); + EXPECT_EQ(0, local_network_->RegisterExternalTransport(local_sender_, *this)); + EXPECT_EQ(0, local_rtp_rtcp_->SetLocalSSRC(local_sender_, kLocalSsrc)); + EXPECT_EQ(0, local_base_->StartSend(local_sender_)); + + EXPECT_EQ(0, remote_base_->Init()); + reflector_ = remote_base_->CreateChannel(); + EXPECT_EQ(0, remote_network_->RegisterExternalTransport(reflector_, *this)); + EXPECT_EQ(0, remote_rtp_rtcp_->SetLocalSSRC(reflector_, kReflectorSsrc)); + + thread_->Start(); + thread_->SetPriority(webrtc::kHighPriority); +} + +ConferenceTransport::~ConferenceTransport() { + // Must stop sending, otherwise DispatchPackets() cannot quit. + EXPECT_EQ(0, remote_network_->DeRegisterExternalTransport(reflector_)); + EXPECT_EQ(0, local_network_->DeRegisterExternalTransport(local_sender_)); + + for (auto stream : streams_) { + RemoveStream(stream.first); + } + + EXPECT_TRUE(thread_->Stop()); + + remote_file_->Release(); + remote_rtp_rtcp_->Release(); + remote_network_->Release(); + remote_base_->Release(); + + local_rtp_rtcp_->Release(); + local_network_->Release(); + local_base_->Release(); + + EXPECT_TRUE(webrtc::VoiceEngine::Delete(remote_voe_)); + EXPECT_TRUE(webrtc::VoiceEngine::Delete(local_voe_)); +} + +int ConferenceTransport::SendPacket(int channel, const void* data, size_t len) { + StorePacket(Packet::Rtp, channel, data, len); + return static_cast(len); +} + +int ConferenceTransport::SendRTCPPacket(int channel, const void* data, + size_t len) { + StorePacket(Packet::Rtcp, channel, data, len); + return static_cast(len); +} + +int ConferenceTransport::GetReceiverChannelForSsrc(unsigned int sender_ssrc) + const { + webrtc::CriticalSectionScoped lock(stream_crit_.get()); + auto it = streams_.find(sender_ssrc); + if (it != streams_.end()) { + return it->second.second; + } + return -1; +} + +void ConferenceTransport::StorePacket(Packet::Type type, int channel, + const void* data, size_t len) { + { + webrtc::CriticalSectionScoped lock(pq_crit_.get()); + packet_queue_.push_back(Packet(type, channel, data, len, rtc::Time())); + } + packet_event_->Set(); +} + +// This simulates the flow of RTP and RTCP packets. Complications like that +// a packet is first sent to the reflector, and then forwarded to the receiver +// are simplified, in this particular case, to a direct link between the sender +// and the receiver. +void ConferenceTransport::SendPacket(const Packet& packet) const { + unsigned int sender_ssrc; + int destination = -1; + switch (packet.type_) { + case Packet::Rtp: + sender_ssrc = ParseSsrc(packet.data_, packet.len_, false); + if (sender_ssrc == kLocalSsrc) { + remote_network_->ReceivedRTPPacket(reflector_, packet.data_, + packet.len_, webrtc::PacketTime()); + } else { + destination = GetReceiverChannelForSsrc(sender_ssrc); + if (destination != -1) { + local_network_->ReceivedRTPPacket(destination, packet.data_, + packet.len_, + webrtc::PacketTime()); + } + } + break; + case Packet::Rtcp: + sender_ssrc = ParseSsrc(packet.data_, packet.len_, true); + if (sender_ssrc == kLocalSsrc) { + remote_network_->ReceivedRTCPPacket(reflector_, packet.data_, + packet.len_); + } else if (sender_ssrc == kReflectorSsrc) { + local_network_->ReceivedRTCPPacket(local_sender_, packet.data_, + packet.len_); + } else { + destination = GetReceiverChannelForSsrc(sender_ssrc); + if (destination != -1) { + local_network_->ReceivedRTCPPacket(destination, packet.data_, + packet.len_); + } + } + break; + } +} + +bool ConferenceTransport::DispatchPackets() { + switch (packet_event_->Wait(1000)) { + case webrtc::kEventSignaled: + break; + case webrtc::kEventTimeout: + return true; + case webrtc::kEventError: + ADD_FAILURE() << "kEventError encountered."; + return true; + } + + while (true) { + Packet packet; + { + webrtc::CriticalSectionScoped lock(pq_crit_.get()); + if (packet_queue_.empty()) + break; + packet = packet_queue_.front(); + packet_queue_.pop_front(); + } + + int32 elapsed_time_ms = rtc::TimeSince(packet.send_time_ms_); + int32 sleep_ms = rtt_ms_ / 2 - elapsed_time_ms; + if (sleep_ms > 0) { + // Every packet should be delayed by half of RTT. + webrtc::SleepMs(sleep_ms); + } + + SendPacket(packet); + } + return true; +} + +void ConferenceTransport::SetRtt(unsigned int rtt_ms) { + rtt_ms_ = rtt_ms; +} + +unsigned int ConferenceTransport::AddStream() { + const int new_sender = remote_base_->CreateChannel(); + EXPECT_EQ(0, remote_network_->RegisterExternalTransport(new_sender, *this)); + + const unsigned int remote_ssrc = kFirstRemoteSsrc + stream_count_++; + EXPECT_EQ(0, remote_rtp_rtcp_->SetLocalSSRC(new_sender, remote_ssrc)); + + EXPECT_EQ(0, remote_codec_->SetSendCodec(new_sender, kCodecInst)); + EXPECT_EQ(0, remote_base_->StartSend(new_sender)); + EXPECT_EQ(0, remote_file_->StartPlayingFileAsMicrophone( + new_sender, kInputFileName.c_str(), true, false, + kInputFileFormat, 1.0)); + + const int new_receiver = local_base_->CreateChannel(); + EXPECT_EQ(0, local_base_->AssociateSendChannel(new_receiver, local_sender_)); + + EXPECT_EQ(0, local_network_->RegisterExternalTransport(new_receiver, *this)); + // Receive channels have to have the same SSRC in order to send receiver + // reports with this SSRC. + EXPECT_EQ(0, local_rtp_rtcp_->SetLocalSSRC(new_receiver, kLocalSsrc)); + + { + webrtc::CriticalSectionScoped lock(stream_crit_.get()); + streams_[remote_ssrc] = std::make_pair(new_sender, new_receiver); + } + return remote_ssrc; // remote ssrc used as stream id. +} + +bool ConferenceTransport::RemoveStream(unsigned int id) { + webrtc::CriticalSectionScoped lock(stream_crit_.get()); + auto it = streams_.find(id); + if (it == streams_.end()) { + return false; + } + EXPECT_EQ(0, remote_network_-> + DeRegisterExternalTransport(it->second.second)); + EXPECT_EQ(0, local_network_-> + DeRegisterExternalTransport(it->second.first)); + EXPECT_EQ(0, remote_base_->DeleteChannel(it->second.second)); + EXPECT_EQ(0, local_base_->DeleteChannel(it->second.first)); + streams_.erase(it); + return true; +} + +bool ConferenceTransport::StartPlayout(unsigned int id) { + int dst = GetReceiverChannelForSsrc(id); + if (dst == -1) { + return false; + } + EXPECT_EQ(0, local_base_->StartPlayout(dst)); + return true; +} + +bool ConferenceTransport::GetReceiverStatistics(unsigned int id, + webrtc::CallStatistics* stats) { + int dst = GetReceiverChannelForSsrc(id); + if (dst == -1) { + return false; + } + EXPECT_EQ(0, local_rtp_rtcp_->GetRTCPStatistics(dst, *stats)); + return true; +} +} // namespace voetest diff --git a/webrtc/voice_engine/test/auto_test/fakes/conference_transport.h b/webrtc/voice_engine/test/auto_test/fakes/conference_transport.h new file mode 100644 index 0000000000..9f5546eecd --- /dev/null +++ b/webrtc/voice_engine/test/auto_test/fakes/conference_transport.h @@ -0,0 +1,158 @@ +/* + * 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_VOICE_ENGINE_TEST_AUTO_TEST_FAKES_CONFERENCE_TRANSPORT_H_ +#define WEBRTC_VOICE_ENGINE_TEST_AUTO_TEST_FAKES_CONFERENCE_TRANSPORT_H_ + +#include +#include +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/basictypes.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/common_types.h" +#include "webrtc/system_wrappers/interface/critical_section_wrapper.h" +#include "webrtc/system_wrappers/interface/event_wrapper.h" +#include "webrtc/system_wrappers/interface/thread_wrapper.h" +#include "webrtc/voice_engine/include/voe_base.h" +#include "webrtc/voice_engine/include/voe_codec.h" +#include "webrtc/voice_engine/include/voe_file.h" +#include "webrtc/voice_engine/include/voe_network.h" +#include "webrtc/voice_engine/include/voe_rtp_rtcp.h" + + +static const size_t kMaxPacketSizeByte = 1500; + +namespace voetest { + +// This class is to simulate a conference call. There are two Voice Engines, one +// for local channels and the other for remote channels. There is a simulated +// reflector, which exchanges RTCP with local channels. For simplicity, it +// also uses the Voice Engine for remote channels. One can add streams by +// calling AddStream(), which creates a remote sender channel and a local +// receive channel. The remote sender channel plays a file as microphone in a +// looped fashion. Received streams are mixed and played. + +class ConferenceTransport: public webrtc::Transport { + public: + ConferenceTransport(); + virtual ~ConferenceTransport(); + + /* SetRtt() + * Set RTT between local channels and reflector. + * + * Input: + * rtt_ms : RTT in milliseconds. + */ + void SetRtt(unsigned int rtt_ms); + + /* AddStream() + * Adds a stream in the conference. + * + * Returns stream id. + */ + unsigned int AddStream(); + + /* RemoveStream() + * Removes a stream with specified ID from the conference. + * + * Input: + * id : stream id. + * + * Returns false if the specified stream does not exist, true if succeeds. + */ + bool RemoveStream(unsigned int id); + + /* StartPlayout() + * Starts playing out the stream with specified ID, using the default device. + * + * Input: + * id : stream id. + * + * Returns false if the specified stream does not exist, true if succeeds. + */ + bool StartPlayout(unsigned int id); + + /* GetReceiverStatistics() + * Gets RTCP statistics of the stream with specified ID. + * + * Input: + * id : stream id; + * stats : pointer to a CallStatistics to store the result. + * + * Returns false if the specified stream does not exist, true if succeeds. + */ + bool GetReceiverStatistics(unsigned int id, webrtc::CallStatistics* stats); + + // Inherit from class webrtc::Transport. + int SendPacket(int channel, const void *data, size_t len) override; + int SendRTCPPacket(int channel, const void *data, size_t len) override; + + private: + struct Packet { + enum Type { Rtp, Rtcp, } type_; + + Packet() : len_(0) {} + Packet(Type type, int channel, const void* data, size_t len, uint32 time_ms) + : type_(type), + channel_(channel), + len_(len), + send_time_ms_(time_ms) { + EXPECT_LE(len_, kMaxPacketSizeByte); + memcpy(data_, data, len_); + } + + int channel_; + uint8_t data_[kMaxPacketSizeByte]; + size_t len_; + uint32 send_time_ms_; + }; + + static bool Run(void* transport) { + return static_cast(transport)->DispatchPackets(); + } + + int GetReceiverChannelForSsrc(unsigned int sender_ssrc) const; + void StorePacket(Packet::Type type, int channel, const void* data, + size_t len); + void SendPacket(const Packet& packet) const; + bool DispatchPackets(); + + const rtc::scoped_ptr pq_crit_; + const rtc::scoped_ptr stream_crit_; + const rtc::scoped_ptr packet_event_; + const rtc::scoped_ptr thread_; + + unsigned int rtt_ms_; + unsigned int stream_count_; + + std::map> streams_ + GUARDED_BY(stream_crit_.get()); + std::deque packet_queue_ GUARDED_BY(pq_crit_.get()); + + int local_sender_; // Channel Id of local sender + int reflector_; + + webrtc::VoiceEngine* local_voe_; + webrtc::VoEBase* local_base_; + webrtc::VoERTP_RTCP* local_rtp_rtcp_; + webrtc::VoENetwork* local_network_; + + webrtc::VoiceEngine* remote_voe_; + webrtc::VoEBase* remote_base_; + webrtc::VoECodec* remote_codec_; + webrtc::VoERTP_RTCP* remote_rtp_rtcp_; + webrtc::VoENetwork* remote_network_; + webrtc::VoEFile* remote_file_; +}; +} // namespace voetest + +#endif // WEBRTC_VOICE_ENGINE_TEST_AUTO_TEST_FAKES_CONFERENCE_TRANSPORT_H_ diff --git a/webrtc/voice_engine/test/auto_test/voe_conference_test.cc b/webrtc/voice_engine/test/auto_test/voe_conference_test.cc new file mode 100644 index 0000000000..20a74b46b0 --- /dev/null +++ b/webrtc/voice_engine/test/auto_test/voe_conference_test.cc @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/format_macros.h" +#include "webrtc/base/timeutils.h" +#include "webrtc/system_wrappers/interface/sleep.h" +#include "webrtc/voice_engine/test/auto_test/fakes/conference_transport.h" + +namespace { + static const int kRttMs = 25; + + static bool IsNear(int ref, int comp, int error) { + return (ref - comp <= error) && (comp - ref >= -error); + } +} + +namespace voetest { + +TEST(VoeConferenceTest, RttAndStartNtpTime) { + struct Stats { + Stats(int64_t rtt_receiver_1, int64_t rtt_receiver_2, int64_t ntp_delay) + : rtt_receiver_1_(rtt_receiver_1), + rtt_receiver_2_(rtt_receiver_2), + ntp_delay_(ntp_delay) { + } + int64_t rtt_receiver_1_; + int64_t rtt_receiver_2_; + int64_t ntp_delay_; + }; + + const int kDelayMs = 987; + ConferenceTransport trans; + trans.SetRtt(kRttMs); + + unsigned int id_1 = trans.AddStream(); + unsigned int id_2 = trans.AddStream(); + + EXPECT_TRUE(trans.StartPlayout(id_1)); + // Start NTP time is the time when a stream is played out, rather than + // when it is added. + webrtc::SleepMs(kDelayMs); + EXPECT_TRUE(trans.StartPlayout(id_2)); + + const int kMaxRunTimeMs = 25000; + const int kNeedSuccessivePass = 3; + const int kStatsRequestIntervalMs = 1000; + const int kStatsBufferSize = 3; + + uint32 deadline = rtc::TimeAfter(kMaxRunTimeMs); + // Run the following up to |kMaxRunTimeMs| milliseconds. + int successive_pass = 0; + webrtc::CallStatistics stats_1; + webrtc::CallStatistics stats_2; + std::queue stats_buffer; + + while (rtc::TimeIsLater(rtc::Time(), deadline) && + successive_pass < kNeedSuccessivePass) { + webrtc::SleepMs(kStatsRequestIntervalMs); + + EXPECT_TRUE(trans.GetReceiverStatistics(id_1, &stats_1)); + EXPECT_TRUE(trans.GetReceiverStatistics(id_2, &stats_2)); + + // It is not easy to verify the NTP time directly. We verify it by testing + // the difference of two start NTP times. + int64_t captured_start_ntp_delay = stats_2.capture_start_ntp_time_ms_ - + stats_1.capture_start_ntp_time_ms_; + + // For the checks of RTT and start NTP time, We allow 10% accuracy. + if (IsNear(kRttMs, stats_1.rttMs, kRttMs / 10 + 1) && + IsNear(kRttMs, stats_2.rttMs, kRttMs / 10 + 1) && + IsNear(kDelayMs, captured_start_ntp_delay, kDelayMs / 10 + 1)) { + successive_pass++; + } else { + successive_pass = 0; + } + if (stats_buffer.size() >= kStatsBufferSize) { + stats_buffer.pop(); + } + stats_buffer.push(Stats(stats_1.rttMs, stats_2.rttMs, + captured_start_ntp_delay)); + } + + EXPECT_GE(successive_pass, kNeedSuccessivePass) << "Expected to get RTT and" + " start NTP time estimate within 10% of the correct value over " + << kStatsRequestIntervalMs * kNeedSuccessivePass / 1000 + << " seconds."; + if (successive_pass < kNeedSuccessivePass) { + printf("The most recent values (RTT for receiver 1, RTT for receiver 2, " + "NTP delay between receiver 1 and 2) are (from oldest):\n"); + while (!stats_buffer.empty()) { + Stats stats = stats_buffer.front(); + printf("(%" PRId64 ", %" PRId64 ", %" PRId64 ")\n", stats.rtt_receiver_1_, + stats.rtt_receiver_2_, stats.ntp_delay_); + stats_buffer.pop(); + } + } +} +} // namespace voetest diff --git a/webrtc/voice_engine/voice_engine.gyp b/webrtc/voice_engine/voice_engine.gyp index 077b19372e..ad4a625aa0 100644 --- a/webrtc/voice_engine/voice_engine.gyp +++ b/webrtc/voice_engine/voice_engine.gyp @@ -158,6 +158,8 @@ 'test/auto_test/automated_mode.cc', 'test/auto_test/extended/agc_config_test.cc', 'test/auto_test/extended/ec_metrics_test.cc', + 'test/auto_test/fakes/conference_transport.cc', + 'test/auto_test/fakes/conference_transport.h', 'test/auto_test/fakes/fake_external_transport.cc', 'test/auto_test/fakes/fake_external_transport.h', 'test/auto_test/fixtures/after_initialization_fixture.cc', @@ -187,6 +189,7 @@ 'test/auto_test/standard/video_sync_test.cc', 'test/auto_test/standard/volume_test.cc', 'test/auto_test/resource_manager.cc', + 'test/auto_test/voe_conference_test.cc', 'test/auto_test/voe_cpu_test.cc', 'test/auto_test/voe_cpu_test.h', 'test/auto_test/voe_standard_test.cc',