| /* |
| * Copyright (c) 2016 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 <memory> |
| #include <utility> |
| |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| #include "webrtc/modules/audio_conference_mixer/include/audio_conference_mixer.h" |
| #include "webrtc/modules/audio_conference_mixer/include/audio_conference_mixer_defines.h" |
| #include "webrtc/modules/audio_conference_mixer/source/audio_frame_manipulator.h" |
| #include "webrtc/modules/audio_mixer/audio_mixer.h" |
| #include "webrtc/modules/audio_mixer/include/audio_mixer_defines.h" |
| #include "webrtc/modules/audio_mixer/include/new_audio_conference_mixer.h" |
| #include "webrtc/modules/audio_mixer/source/new_audio_conference_mixer_impl.h" |
| |
| using testing::_; |
| using testing::Exactly; |
| using testing::Invoke; |
| using testing::Return; |
| |
| using webrtc::voe::AudioMixer; |
| |
| namespace webrtc { |
| class MockAudioMixerParticipant : public MixerParticipant { |
| public: |
| MockAudioMixerParticipant() |
| : fake_audio_frame_info_(MixerParticipant::AudioFrameInfo::kNormal) { |
| ON_CALL(*this, GetAudioFrameWithMuted(_, _)) |
| .WillByDefault( |
| Invoke(this, &MockAudioMixerParticipant::FakeAudioFrameWithMuted)); |
| } |
| |
| MOCK_METHOD2(GetAudioFrameWithMuted, |
| AudioFrameInfo(const int32_t id, AudioFrame* audio_frame)); |
| MOCK_CONST_METHOD1(NeededFrequency, int32_t(const int32_t id)); |
| |
| AudioFrame* fake_frame() { return &fake_frame_; } |
| AudioFrameInfo fake_info() { return this->fake_audio_frame_info_; } |
| void set_fake_info(const AudioFrameInfo audio_frame_info) { |
| fake_audio_frame_info_ = audio_frame_info; |
| } |
| |
| private: |
| AudioFrame fake_frame_; |
| AudioFrameInfo fake_audio_frame_info_; |
| AudioFrameInfo FakeAudioFrameWithMuted(const int32_t id, |
| AudioFrame* audio_frame) { |
| audio_frame->CopyFrom(*fake_frame()); |
| return fake_info(); |
| } |
| }; |
| |
| class MockMixerAudioSource : public MixerAudioSource { |
| public: |
| MockMixerAudioSource() |
| : fake_audio_frame_info_(MixerAudioSource::AudioFrameInfo::kNormal) { |
| ON_CALL(*this, GetAudioFrameWithMuted(_, _)) |
| .WillByDefault( |
| Invoke(this, &MockMixerAudioSource::FakeAudioFrameWithMuted)); |
| } |
| |
| MOCK_METHOD2(GetAudioFrameWithMuted, |
| AudioFrameWithMuted(const int32_t id, int sample_rate_hz)); |
| |
| AudioFrame* fake_frame() { return &fake_frame_; } |
| AudioFrameInfo fake_info() { return fake_audio_frame_info_; } |
| void set_fake_info(const AudioFrameInfo audio_frame_info) { |
| fake_audio_frame_info_ = audio_frame_info; |
| } |
| |
| private: |
| AudioFrame fake_frame_; |
| AudioFrameInfo fake_audio_frame_info_; |
| AudioFrameWithMuted FakeAudioFrameWithMuted(const int32_t id, |
| int sample_rate_hz) { |
| return { |
| fake_frame(), // audio_frame_pointer |
| fake_info(), // audio_frame_info |
| }; |
| } |
| }; |
| |
| // Keeps two identical sets of participants and two mixers to test |
| // that the same participants are chosen for mixing. |
| class CompareWithOldMixerTest : public testing::Test, AudioMixerOutputReceiver { |
| protected: |
| constexpr static int kId = 1; |
| constexpr static int kSampleRateHz = 32000; |
| |
| CompareWithOldMixerTest() |
| : old_mixer_(AudioConferenceMixer::Create(kId)), |
| new_mixer_(NewAudioConferenceMixer::Create(kId)) {} |
| |
| ~CompareWithOldMixerTest() { Reset(); } |
| |
| // Mixes with both mixers and compares results: resulting frames and |
| // mix statuses. |
| void MixAndCompare() { |
| old_mixer_->Process(); |
| new_mixer_->Mix(kSampleRateHz, |
| 1, // number of channels |
| &new_mixer_frame_); |
| EXPECT_EQ(0, memcmp(old_mixer_frame_.data_, new_mixer_frame_.data_, |
| sizeof(old_mixer_frame_.data_))); |
| |
| for (auto& participant_pair : participants_) { |
| EXPECT_EQ(participant_pair.first->IsMixed(), |
| participant_pair.second->IsMixed()); |
| } |
| } |
| |
| std::unique_ptr<AudioFrame> last_mixed_audio_old() { |
| std::unique_ptr<AudioFrame> result(new AudioFrame); |
| result->CopyFrom(old_mixer_frame_); |
| return result; |
| } |
| |
| void Reset() { |
| old_mixer_.reset(AudioConferenceMixer::Create(kId)); |
| new_mixer_.reset(NewAudioConferenceMixer::Create(kId)); |
| for (auto& participant_pair : participants_) { |
| delete participant_pair.first; |
| delete participant_pair.second; |
| } |
| participants_.clear(); |
| } |
| |
| void ResetFrame(AudioFrame* audio_frame) { |
| audio_frame->sample_rate_hz_ = kSampleRateHz; |
| audio_frame->speech_type_ = AudioFrame::kNormalSpeech; |
| audio_frame->vad_activity_ = AudioFrame::kVadActive; |
| audio_frame->num_channels_ = 1; |
| } |
| |
| void AddParticipant(AudioFrame* audio_frame, |
| MixerParticipant::AudioFrameInfo audio_frame_info) { |
| auto old_participant = new MockAudioMixerParticipant; |
| auto new_participant = new MockMixerAudioSource; |
| old_participant->fake_frame()->CopyFrom(*audio_frame); |
| new_participant->fake_frame()->CopyFrom(*audio_frame); |
| old_participant->set_fake_info(audio_frame_info); |
| MixerAudioSource::AudioFrameInfo new_audio_frame_info; |
| switch (audio_frame_info) { |
| case MixerParticipant::AudioFrameInfo::kNormal: |
| new_audio_frame_info = MixerAudioSource::AudioFrameInfo::kNormal; |
| break; |
| case MixerParticipant::AudioFrameInfo::kMuted: |
| new_audio_frame_info = MixerAudioSource::AudioFrameInfo::kMuted; |
| break; |
| default: |
| new_audio_frame_info = MixerAudioSource::AudioFrameInfo::kError; |
| } |
| new_participant->set_fake_info(new_audio_frame_info); |
| participants_.emplace_back(old_participant, new_participant); |
| } |
| |
| void NewMixedAudio(const int32_t id, |
| const AudioFrame& generalAudioFrame, |
| const AudioFrame** uniqueAudioFrames, |
| const uint32_t size) override { |
| old_mixer_frame_.CopyFrom(generalAudioFrame); |
| } |
| |
| AudioFrame old_mixer_frame_; |
| AudioFrame new_mixer_frame_; |
| |
| std::vector<std::pair<MockAudioMixerParticipant*, MockMixerAudioSource*>> |
| participants_; |
| std::unique_ptr<AudioConferenceMixer> old_mixer_; |
| std::unique_ptr<NewAudioConferenceMixer> new_mixer_; |
| }; |
| |
| class BothMixersTest : public testing::Test { |
| protected: |
| BothMixersTest() { |
| // Create an OutputMixer. |
| AudioMixer::Create(audio_mixer_, kId); |
| |
| // Create one mixer participant and add it to the mixer. |
| EXPECT_EQ(0, audio_mixer_->SetMixabilityStatus(participant_, true)); |
| |
| // Each iteration, the participant will return a frame with this content: |
| participant_.fake_frame()->id_ = 1; |
| participant_.fake_frame()->sample_rate_hz_ = kSampleRateHz; |
| participant_.fake_frame()->speech_type_ = AudioFrame::kNormalSpeech; |
| participant_.fake_frame()->vad_activity_ = AudioFrame::kVadActive; |
| participant_.fake_frame()->num_channels_ = 1; |
| |
| // We modify one sample within the RampIn window and one sample |
| // outside of it. |
| participant_.fake_frame()->data_[10] = 100; |
| participant_.fake_frame()->data_[20] = -200; |
| participant_.fake_frame()->data_[30] = 300; |
| participant_.fake_frame()->data_[90] = -400; |
| |
| // Frame duration 10ms. |
| participant_.fake_frame()->samples_per_channel_ = kSampleRateHz / 100; |
| } |
| |
| ~BothMixersTest() { AudioMixer::Destroy(audio_mixer_); } |
| |
| // Mark the participant as 'unmixed' last round. |
| void ResetAudioSource() { participant_._mixHistory->SetIsMixed(false); } |
| |
| AudioMixer* audio_mixer_; |
| MockMixerAudioSource participant_; |
| AudioFrame mixing_round_frame, mixed_results_frame_; |
| |
| constexpr static int kSampleRateHz = 48000; |
| constexpr static int kId = 1; |
| }; |
| |
| TEST(AudioMixer, AnonymousAndNamed) { |
| constexpr int kId = 1; |
| // Should not matter even if partipants are more than |
| // kMaximumAmountOfMixedAudioSources. |
| constexpr int kNamed = |
| NewAudioConferenceMixer::kMaximumAmountOfMixedAudioSources + 1; |
| constexpr int kAnonymous = |
| NewAudioConferenceMixer::kMaximumAmountOfMixedAudioSources + 1; |
| |
| std::unique_ptr<NewAudioConferenceMixer> mixer( |
| NewAudioConferenceMixer::Create(kId)); |
| |
| MockMixerAudioSource named[kNamed]; |
| MockMixerAudioSource anonymous[kAnonymous]; |
| |
| for (int i = 0; i < kNamed; ++i) { |
| EXPECT_EQ(0, mixer->SetMixabilityStatus(&named[i], true)); |
| EXPECT_TRUE(mixer->MixabilityStatus(named[i])); |
| } |
| |
| for (int i = 0; i < kAnonymous; ++i) { |
| // AudioSource must be registered before turning it into anonymous. |
| EXPECT_EQ(-1, mixer->SetAnonymousMixabilityStatus(&anonymous[i], true)); |
| EXPECT_EQ(0, mixer->SetMixabilityStatus(&anonymous[i], true)); |
| EXPECT_TRUE(mixer->MixabilityStatus(anonymous[i])); |
| EXPECT_FALSE(mixer->AnonymousMixabilityStatus(anonymous[i])); |
| |
| EXPECT_EQ(0, mixer->SetAnonymousMixabilityStatus(&anonymous[i], true)); |
| EXPECT_TRUE(mixer->AnonymousMixabilityStatus(anonymous[i])); |
| |
| // Anonymous participants do not show status by MixabilityStatus. |
| EXPECT_FALSE(mixer->MixabilityStatus(anonymous[i])); |
| } |
| |
| for (int i = 0; i < kNamed; ++i) { |
| EXPECT_EQ(0, mixer->SetMixabilityStatus(&named[i], false)); |
| EXPECT_FALSE(mixer->MixabilityStatus(named[i])); |
| } |
| |
| for (int i = 0; i < kAnonymous - 1; i++) { |
| EXPECT_EQ(0, mixer->SetAnonymousMixabilityStatus(&anonymous[i], false)); |
| EXPECT_FALSE(mixer->AnonymousMixabilityStatus(anonymous[i])); |
| |
| // SetAnonymousMixabilityStatus(anonymous, false) moves anonymous to the |
| // named group. |
| EXPECT_TRUE(mixer->MixabilityStatus(anonymous[i])); |
| } |
| |
| // SetMixabilityStatus(anonymous, false) will remove anonymous from both |
| // anonymous and named groups. |
| EXPECT_EQ(0, mixer->SetMixabilityStatus(&anonymous[kAnonymous - 1], false)); |
| EXPECT_FALSE(mixer->AnonymousMixabilityStatus(anonymous[kAnonymous - 1])); |
| EXPECT_FALSE(mixer->MixabilityStatus(anonymous[kAnonymous - 1])); |
| } |
| |
| TEST(AudioMixer, LargestEnergyVadActiveMixed) { |
| const int kId = 1; |
| const int kAudioSources = |
| NewAudioConferenceMixer::kMaximumAmountOfMixedAudioSources + 3; |
| const int kSampleRateHz = 32000; |
| |
| std::unique_ptr<NewAudioConferenceMixer> mixer( |
| NewAudioConferenceMixer::Create(kId)); |
| |
| MockMixerAudioSource participants[kAudioSources]; |
| |
| for (int i = 0; i < kAudioSources; ++i) { |
| participants[i].fake_frame()->id_ = i; |
| participants[i].fake_frame()->sample_rate_hz_ = kSampleRateHz; |
| participants[i].fake_frame()->speech_type_ = AudioFrame::kNormalSpeech; |
| participants[i].fake_frame()->vad_activity_ = AudioFrame::kVadActive; |
| participants[i].fake_frame()->num_channels_ = 1; |
| |
| // Frame duration 10ms. |
| participants[i].fake_frame()->samples_per_channel_ = kSampleRateHz / 100; |
| |
| // We set the 80-th sample value since the first 80 samples may be |
| // modified by a ramped-in window. |
| participants[i].fake_frame()->data_[80] = i; |
| |
| EXPECT_EQ(0, mixer->SetMixabilityStatus(&participants[i], true)); |
| EXPECT_CALL(participants[i], GetAudioFrameWithMuted(_, _)) |
| .Times(Exactly(1)); |
| } |
| |
| // Last participant gives audio frame with passive VAD, although it has the |
| // largest energy. |
| participants[kAudioSources - 1].fake_frame()->vad_activity_ = |
| AudioFrame::kVadPassive; |
| |
| AudioFrame audio_frame; |
| mixer->Mix(kSampleRateHz, |
| 1, // number of channels |
| &audio_frame); |
| |
| for (int i = 0; i < kAudioSources; ++i) { |
| bool is_mixed = participants[i].IsMixed(); |
| if (i == kAudioSources - 1 || |
| i < kAudioSources - 1 - |
| NewAudioConferenceMixer::kMaximumAmountOfMixedAudioSources) { |
| EXPECT_FALSE(is_mixed) << "Mixing status of AudioSource #" << i |
| << " wrong."; |
| } else { |
| EXPECT_TRUE(is_mixed) << "Mixing status of AudioSource #" << i |
| << " wrong."; |
| } |
| } |
| } |
| |
| TEST(AudioMixer, ParticipantSampleRate) { |
| const int kId = 1; |
| std::unique_ptr<NewAudioConferenceMixer> mixer( |
| NewAudioConferenceMixer::Create(kId)); |
| AudioFrame frame_for_mixing; |
| |
| MockMixerAudioSource participant; |
| participant.fake_frame()->sample_rate_hz_ = 8000; |
| participant.fake_frame()->num_channels_ = 1; |
| |
| // Frame duration 10ms. |
| participant.fake_frame()->samples_per_channel_ = 8000 / 100; |
| |
| EXPECT_EQ(0, mixer->SetMixabilityStatus(&participant, true)); |
| for (auto frequency : {8000, 16000, 32000, 48000}) { |
| EXPECT_CALL(participant, GetAudioFrameWithMuted(_, frequency)) |
| .Times(Exactly(1)); |
| mixer->Mix(frequency, 1, &frame_for_mixing); |
| EXPECT_EQ(frequency, frame_for_mixing.sample_rate_hz_); |
| } |
| } |
| |
| TEST(AudioMixer, ParticipantNumberOfChannels) { |
| const int kId = 1; |
| std::unique_ptr<NewAudioConferenceMixer> mixer( |
| NewAudioConferenceMixer::Create(kId)); |
| AudioFrame frame_for_mixing; |
| |
| MockMixerAudioSource participant; |
| participant.fake_frame()->sample_rate_hz_ = 8000; |
| participant.fake_frame()->num_channels_ = 1; |
| |
| // Frame duration 10ms. |
| participant.fake_frame()->samples_per_channel_ = 8000 / 100; |
| |
| EXPECT_EQ(0, mixer->SetMixabilityStatus(&participant, true)); |
| for (size_t number_of_channels : {1, 2}) { |
| EXPECT_CALL(participant, GetAudioFrameWithMuted(_, 8000)).Times(Exactly(1)); |
| mixer->Mix(8000, number_of_channels, &frame_for_mixing); |
| EXPECT_EQ(number_of_channels, frame_for_mixing.num_channels_); |
| } |
| } |
| |
| TEST_F(BothMixersTest, CompareInitialFrameAudio) { |
| EXPECT_CALL(participant_, GetAudioFrameWithMuted(_, _)).Times(Exactly(1)); |
| |
| // Make sure the participant is marked as 'non-mixed' so that it is |
| // ramped in next round. |
| ResetAudioSource(); |
| |
| // Construct the expected sound for the first mixing round. |
| mixing_round_frame.CopyFrom(*participant_.fake_frame()); |
| RampIn(mixing_round_frame); |
| |
| // Mix frames and put the result into a frame. |
| audio_mixer_->GetMixedAudio(kSampleRateHz, 1, &mixed_results_frame_); |
| |
| // Compare the received frame with the expected. |
| EXPECT_EQ(mixing_round_frame.sample_rate_hz_, |
| mixed_results_frame_.sample_rate_hz_); |
| EXPECT_EQ(mixing_round_frame.num_channels_, |
| mixed_results_frame_.num_channels_); |
| EXPECT_EQ(mixing_round_frame.samples_per_channel_, |
| mixed_results_frame_.samples_per_channel_); |
| EXPECT_EQ(0, memcmp(mixing_round_frame.data_, mixed_results_frame_.data_, |
| sizeof(mixing_round_frame.data_))); |
| } |
| |
| TEST_F(BothMixersTest, CompareSecondFrameAudio) { |
| EXPECT_CALL(participant_, GetAudioFrameWithMuted(_, _)).Times(Exactly(2)); |
| |
| // Make sure the participant is marked as 'non-mixed' so that it is |
| // ramped in next round. |
| ResetAudioSource(); |
| |
| // Do one mixing iteration. |
| audio_mixer_->GetMixedAudio(kSampleRateHz, 1, &mixed_results_frame_); |
| |
| // Mix frames a second time and compare with the expected frame |
| // (which is the participant's frame). |
| audio_mixer_->GetMixedAudio(kSampleRateHz, 1, &mixed_results_frame_); |
| EXPECT_EQ(0, |
| memcmp(participant_.fake_frame()->data_, mixed_results_frame_.data_, |
| sizeof(mixing_round_frame.data_))); |
| } |
| |
| TEST_F(CompareWithOldMixerTest, TwoParticipantsNormalFrames) { |
| Reset(); |
| AudioFrame first_frame, second_frame; |
| |
| ResetFrame(&first_frame); |
| ResetFrame(&second_frame); |
| |
| first_frame.id_ = 1; |
| second_frame.id_ = 2; |
| |
| AddParticipant(&first_frame, MixerParticipant::AudioFrameInfo::kNormal); |
| AddParticipant(&second_frame, MixerParticipant::AudioFrameInfo::kNormal); |
| |
| for (int i = 0; i < 3; ++i) { |
| MixAndCompare(); |
| } |
| } |
| |
| TEST_F(CompareWithOldMixerTest, ThreeParticipantsDifferentFrames) { |
| Reset(); |
| AudioFrame first_frame, second_frame, third_frame; |
| |
| ResetFrame(&first_frame); |
| ResetFrame(&second_frame); |
| ResetFrame(&third_frame); |
| |
| first_frame.id_ = 1; |
| second_frame.id_ = 2; |
| third_frame.id_ = 3; |
| second_frame.vad_activity_ = AudioFrame::kVadPassive; |
| |
| AddParticipant(&first_frame, MixerParticipant::AudioFrameInfo::kNormal); |
| AddParticipant(&second_frame, MixerParticipant::AudioFrameInfo::kMuted); |
| AddParticipant(&third_frame, MixerParticipant::AudioFrameInfo::kMuted); |
| |
| for (int i = 0; i < 3; ++i) { |
| MixAndCompare(); |
| } |
| } |
| |
| TEST_F(CompareWithOldMixerTest, ManyParticipantsDifferentFrames) { |
| Reset(); |
| constexpr int num_participants = 20; |
| AudioFrame audio_frames[num_participants]; |
| |
| for (int i = 0; i < num_participants; ++i) { |
| ResetFrame(&audio_frames[i]); |
| audio_frames[i].id_ = 1; |
| audio_frames[i].data_[10] = 100 * (i % 5); |
| audio_frames[i].data_[100] = 100 * (i % 5); |
| if (i % 2 == 0) { |
| audio_frames[i].vad_activity_ = AudioFrame::kVadPassive; |
| } |
| } |
| |
| for (int i = 0; i < num_participants; ++i) { |
| if (i % 2 == 0) { |
| AddParticipant(&audio_frames[i], |
| MixerParticipant::AudioFrameInfo::kMuted); |
| } else { |
| AddParticipant(&audio_frames[i], |
| MixerParticipant::AudioFrameInfo::kNormal); |
| } |
| MixAndCompare(); |
| } |
| } |
| |
| } // namespace webrtc |