diff --git a/webrtc/voice_engine/test/auto_test/fakes/conference_transport.cc b/webrtc/voice_engine/test/auto_test/fakes/conference_transport.cc index d0fd8a37af..ef245e9836 100644 --- a/webrtc/voice_engine/test/auto_test/fakes/conference_transport.cc +++ b/webrtc/voice_engine/test/auto_test/fakes/conference_transport.cc @@ -14,7 +14,6 @@ #include "webrtc/base/byteorder.h" #include "webrtc/base/timeutils.h" -#include "webrtc/test/testsupport/fileutils.h" #include "webrtc/system_wrappers/interface/sleep.h" namespace { @@ -23,9 +22,10 @@ namespace { static const unsigned int kFirstRemoteSsrc = 0x0002; static const webrtc::CodecInst kCodecInst = {120, "opus", 48000, 960, 2, 64000}; + static const int kAudioLevelHeaderId = 1; - static unsigned int ParseSsrc(const void* data, size_t len, bool rtcp) { - const size_t ssrc_pos = (!rtcp) ? 8 : 4; + static unsigned int ParseRtcpSsrc(const void* data, size_t len) { + const size_t ssrc_pos = 4; unsigned int ssrc = 0; if (len >= (ssrc_pos + sizeof(ssrc))) { ssrc = rtc::GetBE32(static_cast(data) + ssrc_pos); @@ -44,7 +44,12 @@ ConferenceTransport::ConferenceTransport() this, "ConferenceTransport")), rtt_ms_(0), - stream_count_(0) { + stream_count_(0), + rtp_header_parser_(webrtc::RtpHeaderParser::Create()) { + rtp_header_parser_-> + RegisterRtpHeaderExtension(webrtc::kRtpExtensionAudioLevel, + kAudioLevelHeaderId); + local_voe_ = webrtc::VoiceEngine::Create(); local_base_ = webrtc::VoEBase::GetInterface(local_voe_); local_network_ = webrtc::VoENetwork::GetInterface(local_voe_); @@ -63,6 +68,10 @@ ConferenceTransport::ConferenceTransport() 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_rtp_rtcp_-> + SetSendAudioLevelIndicationStatus(local_sender_, true, + kAudioLevelHeaderId)); + EXPECT_EQ(0, local_base_->StartSend(local_sender_)); EXPECT_EQ(0, remote_base_->Init()); @@ -133,26 +142,30 @@ void ConferenceTransport::StorePacket(Packet::Type type, int channel, // 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; +void ConferenceTransport::SendPacket(const Packet& packet) { int destination = -1; + switch (packet.type_) { - case Packet::Rtp: - sender_ssrc = ParseSsrc(packet.data_, packet.len_, false); - if (sender_ssrc == kLocalSsrc) { + case Packet::Rtp: { + webrtc::RTPHeader rtp_header; + rtp_header_parser_->Parse(packet.data_, packet.len_, &rtp_header); + if (rtp_header.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()); + if (loudest_filter_.ForwardThisPacket(rtp_header)) { + destination = GetReceiverChannelForSsrc(rtp_header.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); + } + case Packet::Rtcp: { + unsigned int sender_ssrc = ParseRtcpSsrc(packet.data_, packet.len_); if (sender_ssrc == kLocalSsrc) { remote_network_->ReceivedRTCPPacket(reflector_, packet.data_, packet.len_); @@ -167,6 +180,7 @@ void ConferenceTransport::SendPacket(const Packet& packet) const { } } break; + } } } @@ -207,21 +221,20 @@ void ConferenceTransport::SetRtt(unsigned int rtt_ms) { rtt_ms_ = rtt_ms; } -unsigned int ConferenceTransport::AddStream() { - const std::string kInputFileName = - webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"); - +unsigned int ConferenceTransport::AddStream(std::string file_name, + webrtc::FileFormats format) { 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_rtp_rtcp_-> + SetSendAudioLevelIndicationStatus(new_sender, true, kAudioLevelHeaderId)); 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, - webrtc::kFileFormatPcm32kHzFile, 1.0)); + new_sender, file_name.c_str(), true, false, format, 1.0)); const int new_receiver = local_base_->CreateChannel(); EXPECT_EQ(0, local_base_->AssociateSendChannel(new_receiver, local_sender_)); diff --git a/webrtc/voice_engine/test/auto_test/fakes/conference_transport.h b/webrtc/voice_engine/test/auto_test/fakes/conference_transport.h index 9f5546eecd..10bf411a3d 100644 --- a/webrtc/voice_engine/test/auto_test/fakes/conference_transport.h +++ b/webrtc/voice_engine/test/auto_test/fakes/conference_transport.h @@ -19,6 +19,7 @@ #include "webrtc/base/basictypes.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/common_types.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.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" @@ -27,7 +28,7 @@ #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" - +#include "webrtc/voice_engine/test/auto_test/fakes/loudest_filter.h" static const size_t kMaxPacketSizeByte = 1500; @@ -57,9 +58,13 @@ class ConferenceTransport: public webrtc::Transport { /* AddStream() * Adds a stream in the conference. * + * Input: + * file_name : name of the file to be added as microphone input. + * format : format of the input file. + * * Returns stream id. */ - unsigned int AddStream(); + unsigned int AddStream(std::string file_name, webrtc::FileFormats format); /* RemoveStream() * Removes a stream with specified ID from the conference. @@ -123,7 +128,7 @@ class ConferenceTransport: public webrtc::Transport { 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; + void SendPacket(const Packet& packet); bool DispatchPackets(); const rtc::scoped_ptr pq_crit_; @@ -152,6 +157,10 @@ class ConferenceTransport: public webrtc::Transport { webrtc::VoERTP_RTCP* remote_rtp_rtcp_; webrtc::VoENetwork* remote_network_; webrtc::VoEFile* remote_file_; + + LoudestFilter loudest_filter_; + + const rtc::scoped_ptr rtp_header_parser_; }; } // namespace voetest diff --git a/webrtc/voice_engine/test/auto_test/fakes/loudest_filter.cc b/webrtc/voice_engine/test/auto_test/fakes/loudest_filter.cc new file mode 100644 index 0000000000..29dda630d7 --- /dev/null +++ b/webrtc/voice_engine/test/auto_test/fakes/loudest_filter.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/voice_engine/test/auto_test/fakes/loudest_filter.h" + +#include "webrtc/base/checks.h" + +namespace voetest { + +void LoudestFilter::RemoveTimeoutStreams(uint32 time_ms) { + auto it = stream_levels_.begin(); + while (it != stream_levels_.end()) { + if (rtc::TimeDiff(time_ms, it->second.last_time_ms) > + kStreamTimeOutMs) { + stream_levels_.erase(it++); + } else { + ++it; + } + } +} + +unsigned int LoudestFilter::FindQuietestStream() { + int quietest_level = kInvalidAudioLevel; + unsigned int quietest_ssrc = 0; + for (auto stream : stream_levels_) { + // A smaller value if audio level corresponds to a louder sound. + if (quietest_level == kInvalidAudioLevel || + stream.second.audio_level > quietest_level) { + quietest_level = stream.second.audio_level; + quietest_ssrc = stream.first; + } + } + return quietest_ssrc; +} + +bool LoudestFilter::ForwardThisPacket(const webrtc::RTPHeader& rtp_header) { + uint32 time_now_ms = rtc::Time(); + RemoveTimeoutStreams(time_now_ms); + + int source_ssrc = rtp_header.ssrc; + int audio_level = rtp_header.extension.hasAudioLevel ? + rtp_header.extension.audioLevel : kInvalidAudioLevel; + + if (audio_level == kInvalidAudioLevel) { + // Always forward streams with unknown audio level, and don't keep their + // states. + return true; + } + + auto it = stream_levels_.find(source_ssrc); + if (it != stream_levels_.end()) { + // Stream has been forwarded. Update and continue to forward. + it->second.audio_level = audio_level; + it->second.last_time_ms = time_now_ms; + return true; + } + + if (stream_levels_.size() < kMaxMixSize) { + stream_levels_[source_ssrc].Set(audio_level, time_now_ms); + return true; + } + + unsigned int quietest_ssrc = FindQuietestStream(); + CHECK_NE(0u, quietest_ssrc); + // A smaller value if audio level corresponds to a louder sound. + if (audio_level < stream_levels_[quietest_ssrc].audio_level) { + stream_levels_.erase(quietest_ssrc); + stream_levels_[source_ssrc].Set(audio_level, time_now_ms); + return true; + } + return false; +} + +} // namespace voetest + diff --git a/webrtc/voice_engine/test/auto_test/fakes/loudest_filter.h b/webrtc/voice_engine/test/auto_test/fakes/loudest_filter.h new file mode 100644 index 0000000000..79b0105c5a --- /dev/null +++ b/webrtc/voice_engine/test/auto_test/fakes/loudest_filter.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VOICE_ENGINE_TEST_AUTO_TEST_FAKES_LOUDEST_FILTER_H_ +#define WEBRTC_VOICE_ENGINE_TEST_AUTO_TEST_FAKES_LOUDEST_FILTER_H_ + +#include +#include "webrtc/base/timeutils.h" +#include "webrtc/common_types.h" + +namespace voetest { + +class LoudestFilter { + public: + /* ForwardThisPacket() + * Decide whether to forward a RTP packet, given its header. + * + * Input: + * rtp_header : Header of the RTP packet of interest. + */ + bool ForwardThisPacket(const webrtc::RTPHeader& rtp_header); + + private: + struct Status { + void Set(int audio_level, uint32 last_time_ms) { + this->audio_level = audio_level; + this->last_time_ms = last_time_ms; + } + int audio_level; + uint32 last_time_ms; + }; + + void RemoveTimeoutStreams(uint32 time_ms); + unsigned int FindQuietestStream(); + + // Keeps the streams being forwarded in pair. + std::map stream_levels_; + + const int32 kStreamTimeOutMs = 5000; + const size_t kMaxMixSize = 3; + const int kInvalidAudioLevel = 128; +}; + + +} // namespace voetest + +#endif // WEBRTC_VOICE_ENGINE_TEST_AUTO_TEST_FAKES_LOUDEST_FILTER_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 index 20a74b46b0..d2407f30e4 100644 --- a/webrtc/voice_engine/test/auto_test/voe_conference_test.cc +++ b/webrtc/voice_engine/test/auto_test/voe_conference_test.cc @@ -14,16 +14,28 @@ #include "webrtc/base/format_macros.h" #include "webrtc/base/timeutils.h" #include "webrtc/system_wrappers/interface/sleep.h" +#include "webrtc/test/testsupport/fileutils.h" #include "webrtc/voice_engine/test/auto_test/fakes/conference_transport.h" namespace { - static const int kRttMs = 25; +const int kRttMs = 25; - static bool IsNear(int ref, int comp, int error) { - return (ref - comp <= error) && (comp - ref >= -error); - } +bool IsNear(int ref, int comp, int error) { + return (ref - comp <= error) && (comp - ref >= -error); } +void CreateSilenceFile(const std::string& silence_file, int sample_rate_hz) { + FILE* fid = fopen(silence_file.c_str(), "wb"); + int16_t zero = 0; + for (int i = 0; i < sample_rate_hz; ++i) { + // Write 1 second, but it does not matter since the file will be looped. + fwrite(&zero, sizeof(int16_t), 1, fid); + } + fclose(fid); +} + +} // namespace + namespace voetest { TEST(VoeConferenceTest, RttAndStartNtpTime) { @@ -38,12 +50,16 @@ TEST(VoeConferenceTest, RttAndStartNtpTime) { int64_t ntp_delay_; }; + const std::string input_file = + webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"); + const webrtc::FileFormats kInputFormat = webrtc::kFileFormatPcm32kHzFile; + const int kDelayMs = 987; ConferenceTransport trans; trans.SetRtt(kRttMs); - unsigned int id_1 = trans.AddStream(); - unsigned int id_2 = trans.AddStream(); + unsigned int id_1 = trans.AddStream(input_file, kInputFormat); + unsigned int id_2 = trans.AddStream(input_file, kInputFormat); EXPECT_TRUE(trans.StartPlayout(id_1)); // Start NTP time is the time when a stream is played out, rather than @@ -105,4 +121,56 @@ TEST(VoeConferenceTest, RttAndStartNtpTime) { } } } + + +TEST(VoeConferenceTest, ReceivedPackets) { + const int kPackets = 50; + const int kPacketDurationMs = 20; // Correspond to Opus. + + const std::string input_file = + webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"); + const webrtc::FileFormats kInputFormat = webrtc::kFileFormatPcm32kHzFile; + + const std::string silence_file = + webrtc::test::TempFilename(webrtc::test::OutputPath(), "silence"); + CreateSilenceFile(silence_file, 32000); + + { + ConferenceTransport trans; + // Add silence to stream 0, so that it will be filtered out. + unsigned int id_0 = trans.AddStream(silence_file, kInputFormat); + unsigned int id_1 = trans.AddStream(input_file, kInputFormat); + unsigned int id_2 = trans.AddStream(input_file, kInputFormat); + unsigned int id_3 = trans.AddStream(input_file, kInputFormat); + + EXPECT_TRUE(trans.StartPlayout(id_0)); + EXPECT_TRUE(trans.StartPlayout(id_1)); + EXPECT_TRUE(trans.StartPlayout(id_2)); + EXPECT_TRUE(trans.StartPlayout(id_3)); + + webrtc::SleepMs(kPacketDurationMs * kPackets); + + webrtc::CallStatistics stats_0; + webrtc::CallStatistics stats_1; + webrtc::CallStatistics stats_2; + webrtc::CallStatistics stats_3; + EXPECT_TRUE(trans.GetReceiverStatistics(id_0, &stats_0)); + EXPECT_TRUE(trans.GetReceiverStatistics(id_1, &stats_1)); + EXPECT_TRUE(trans.GetReceiverStatistics(id_2, &stats_2)); + EXPECT_TRUE(trans.GetReceiverStatistics(id_3, &stats_3)); + + // We expect stream 0 to be filtered out totally, but since it may join the + // call earlier than other streams and the beginning packets might have got + // through. So we only expect |packetsReceived| to be close to zero. + EXPECT_NEAR(stats_0.packetsReceived, 0, 2); + // We expect |packetsReceived| to match |kPackets|, but the actual value + // depends on the sleep timer. So we allow a small off from |kPackets|. + EXPECT_NEAR(stats_1.packetsReceived, kPackets, 2); + EXPECT_NEAR(stats_2.packetsReceived, kPackets, 2); + EXPECT_NEAR(stats_3.packetsReceived, kPackets, 2); + } + + remove(silence_file.c_str()); +} + } // namespace voetest diff --git a/webrtc/voice_engine/voice_engine.gyp b/webrtc/voice_engine/voice_engine.gyp index ad4a625aa0..8bd606d1dc 100644 --- a/webrtc/voice_engine/voice_engine.gyp +++ b/webrtc/voice_engine/voice_engine.gyp @@ -162,6 +162,8 @@ '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/fakes/loudest_filter.cc', + 'test/auto_test/fakes/loudest_filter.h', 'test/auto_test/fixtures/after_initialization_fixture.cc', 'test/auto_test/fixtures/after_initialization_fixture.h', 'test/auto_test/fixtures/after_streaming_fixture.cc',