diff --git a/webrtc/voice_engine/channel.cc b/webrtc/voice_engine/channel.cc index ff4a5cedd7..3548f4aef6 100644 --- a/webrtc/voice_engine/channel.cc +++ b/webrtc/voice_engine/channel.cc @@ -1133,6 +1133,7 @@ Channel::Channel(const WebRtc_Word32 channelId, _rtcpObserverPtr(NULL), _outputIsOnHold(false), _externalPlayout(false), + _externalMixing(false), _inputIsOnHold(false), _playing(false), _sending(false), @@ -1601,13 +1602,16 @@ Channel::StartPlayout() { return 0; } - // Add participant as candidates for mixing. - if (_outputMixerPtr->SetMixabilityStatus(*this, true) != 0) - { - _engineStatisticsPtr->SetLastError( - VE_AUDIO_CONF_MIX_MODULE_ERROR, kTraceError, - "StartPlayout() failed to add participant to mixer"); - return -1; + + if (!_externalMixing) { + // Add participant as candidates for mixing. + if (_outputMixerPtr->SetMixabilityStatus(*this, true) != 0) + { + _engineStatisticsPtr->SetLastError( + VE_AUDIO_CONF_MIX_MODULE_ERROR, kTraceError, + "StartPlayout() failed to add participant to mixer"); + return -1; + } } _playing = true; @@ -1627,13 +1631,16 @@ Channel::StopPlayout() { return 0; } - // Remove participant as candidates for mixing - if (_outputMixerPtr->SetMixabilityStatus(*this, false) != 0) - { - _engineStatisticsPtr->SetLastError( - VE_AUDIO_CONF_MIX_MODULE_ERROR, kTraceError, - "StartPlayout() failed to remove participant from mixer"); - return -1; + + if (!_externalMixing) { + // Remove participant as candidates for mixing + if (_outputMixerPtr->SetMixabilityStatus(*this, false) != 0) + { + _engineStatisticsPtr->SetLastError( + VE_AUDIO_CONF_MIX_MODULE_ERROR, kTraceError, + "StopPlayout() failed to remove participant from mixer"); + return -1; + } } _playing = false; @@ -5981,6 +5988,24 @@ int Channel::DeRegisterExternalMediaProcessing(ProcessingTypes type) return 0; } +int Channel::SetExternalMixing(bool enabled) { + WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId,_channelId), + "Channel::SetExternalMixing(enabled=%d)", enabled); + + if (_playing) + { + _engineStatisticsPtr->SetLastError( + VE_INVALID_OPERATION, kTraceError, + "Channel::SetExternalMixing() " + "external mixing cannot be changed while playing."); + return -1; + } + + _externalMixing = enabled; + + return 0; +} + int Channel::ResetRTCPStatistics() { diff --git a/webrtc/voice_engine/channel.h b/webrtc/voice_engine/channel.h index 7469a77b98..7e8f90865b 100644 --- a/webrtc/voice_engine/channel.h +++ b/webrtc/voice_engine/channel.h @@ -231,6 +231,7 @@ public: int RegisterExternalMediaProcessing(ProcessingTypes type, VoEMediaProcess& processObject); int DeRegisterExternalMediaProcessing(ProcessingTypes type); + int SetExternalMixing(bool enabled); // VoEVolumeControl int GetSpeechOutputLevel(WebRtc_UWord32& level) const; @@ -494,6 +495,10 @@ public: { return _externalTransport; } + bool ExternalMixing() const + { + return _externalMixing; + } bool OutputIsOnHold() const { return _outputIsOnHold; @@ -611,6 +616,7 @@ private: // VoEBase bool _outputIsOnHold; bool _externalPlayout; + bool _externalMixing; bool _inputIsOnHold; bool _playing; bool _sending; diff --git a/webrtc/voice_engine/include/voe_external_media.h b/webrtc/voice_engine/include/voe_external_media.h index a4ef7e20f1..5f576d41ec 100644 --- a/webrtc/voice_engine/include/voe_external_media.h +++ b/webrtc/voice_engine/include/voe_external_media.h @@ -37,6 +37,7 @@ namespace webrtc { class VoiceEngine; +class AudioFrame; class WEBRTC_DLLEXPORT VoEMediaProcess { @@ -104,6 +105,16 @@ public: WebRtc_Word16 speechData10ms[], int samplingFreqHz, int current_delay_ms, int& lengthSamples) = 0; + // Pulls an audio frame from the specified |channel| for external mixing. + // If the |desired_sample_rate_hz| is 0, the signal will be returned with + // its native frequency, otherwise it will be resampled. Valid frequencies + // are 16, 22, 32, 44 or 48 kHz. + virtual int GetAudioFrame(int channel, int desired_sample_rate_hz, + AudioFrame* frame) = 0; + + // Sets the state of external mixing. Cannot be changed during playback. + virtual int SetExternalMixing(int channel, bool enable) = 0; + protected: VoEExternalMedia() {} virtual ~VoEExternalMedia() {} diff --git a/webrtc/voice_engine/test/auto_test/standard/external_media_test.cc b/webrtc/voice_engine/test/auto_test/standard/external_media_test.cc index 5c641ba162..dcbd05ee4c 100644 --- a/webrtc/voice_engine/test/auto_test/standard/external_media_test.cc +++ b/webrtc/voice_engine/test/auto_test/standard/external_media_test.cc @@ -8,6 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include "modules/interface/module_common_types.h" #include "voice_engine/include/voe_external_media.h" #include "voice_engine/test/auto_test/fakes/fake_media_process.h" #include "voice_engine/test/auto_test/fixtures/after_streaming_fixture.h" @@ -82,3 +83,80 @@ TEST_F(ExternalMediaTest, TEST_LOG("Speak and verify your voice is distorted.\n"); TestRegisterExternalMedia(-1, webrtc::kRecordingAllChannelsMixed); } + +TEST_F(ExternalMediaTest, + ExternalMixingCannotBeChangedDuringPlayback) { + EXPECT_EQ(-1, voe_xmedia_->SetExternalMixing(channel_, true)); + EXPECT_EQ(-1, voe_xmedia_->SetExternalMixing(channel_, false)); +} + +TEST_F(ExternalMediaTest, + ExternalMixingIsRequiredForGetAudioFrame) { + webrtc::AudioFrame frame; + EXPECT_EQ(-1, voe_xmedia_->GetAudioFrame(channel_, 0, &frame)); +} + +TEST_F(ExternalMediaTest, + ExternalMixingPreventsAndRestoresRegularPlayback) { + PausePlaying(); + ASSERT_EQ(0, voe_xmedia_->SetExternalMixing(channel_, true)); + TEST_LOG("Verify that no sound is played out.\n"); + ResumePlaying(); + Sleep(1000); + PausePlaying(); + ASSERT_EQ(0, voe_xmedia_->SetExternalMixing(channel_, false)); + ResumePlaying(); + TEST_LOG("Verify that sound is played out.\n"); + ResumePlaying(); + Sleep(1000); +} + +TEST_F(ExternalMediaTest, + ExternalMixingWorks) { + webrtc::AudioFrame frame; + PausePlaying(); + EXPECT_EQ(0, voe_xmedia_->SetExternalMixing(channel_, true)); + ResumePlaying(); + EXPECT_EQ(0, voe_xmedia_->GetAudioFrame(channel_, 0, &frame)); + EXPECT_LT(0, frame.sample_rate_hz_); + EXPECT_LT(0, frame.samples_per_channel_); + PausePlaying(); + EXPECT_EQ(0, voe_xmedia_->SetExternalMixing(channel_, false)); + ResumePlaying(); +} + +TEST_F(ExternalMediaTest, + ExternalMixingResamplesToDesiredFrequency) { + const int kValidFrequencies[] = {8000, 16000, 22000, 32000, 48000}; + webrtc::AudioFrame frame; + PausePlaying(); + EXPECT_EQ(0, voe_xmedia_->SetExternalMixing(channel_, true)); + ResumePlaying(); + for (size_t i = 0; i < sizeof(kValidFrequencies) / sizeof(int); i++) { + int f = kValidFrequencies[i]; + EXPECT_EQ(0, voe_xmedia_->GetAudioFrame(channel_, f, &frame)) + << "Resampling succeeds for freq=" << f; + EXPECT_EQ(f, frame.sample_rate_hz_); + EXPECT_EQ(f / 100, frame.samples_per_channel_); + } + PausePlaying(); + EXPECT_EQ(0, voe_xmedia_->SetExternalMixing(channel_, false)); + ResumePlaying(); +} + +TEST_F(ExternalMediaTest, + ExternalMixingResamplingToInvalidFrequenciesFails) { + const int kInvalidFrequencies[] = {-8000, -1, 1, 1000, 8001, 16001}; + webrtc::AudioFrame frame; + PausePlaying(); + EXPECT_EQ(0, voe_xmedia_->SetExternalMixing(channel_, true)); + ResumePlaying(); + for (size_t i = 0; i < sizeof(kInvalidFrequencies) / sizeof(int); i++) { + int f = kInvalidFrequencies[i]; + EXPECT_EQ(-1, voe_xmedia_->GetAudioFrame(channel_, f, &frame)) + << "Resampling fails for freq=" << f; + } + PausePlaying(); + EXPECT_EQ(0, voe_xmedia_->SetExternalMixing(channel_, false)); + ResumePlaying(); +} diff --git a/webrtc/voice_engine/voe_external_media_impl.cc b/webrtc/voice_engine/voe_external_media_impl.cc index 0216023800..4c88c9a490 100644 --- a/webrtc/voice_engine/voe_external_media_impl.cc +++ b/webrtc/voice_engine/voe_external_media_impl.cc @@ -346,6 +346,66 @@ int VoEExternalMediaImpl::ExternalPlayoutGetData( #endif } +int VoEExternalMediaImpl::GetAudioFrame(int channel, int desired_sample_rate_hz, + AudioFrame* frame) { + WEBRTC_TRACE(kTraceApiCall, kTraceVoice, + VoEId(shared_->instance_id(), channel), + "GetAudioFrame(channel=%d, desired_sample_rate_hz=%d)", + channel, desired_sample_rate_hz); + if (!shared_->statistics().Initialized()) + { + shared_->SetLastError(VE_NOT_INITED, kTraceError); + return -1; + } + voe::ScopedChannel sc(shared_->channel_manager(), channel); + voe::Channel* channelPtr = sc.ChannelPtr(); + if (channelPtr == NULL) + { + shared_->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError, + "GetAudioFrame() failed to locate channel"); + return -1; + } + if (!channelPtr->ExternalMixing()) { + shared_->SetLastError(VE_INVALID_OPERATION, kTraceError, + "GetAudioFrame() was called on channel that is not" + " externally mixed."); + return -1; + } + if (!channelPtr->Playing()) { + shared_->SetLastError(VE_INVALID_OPERATION, kTraceError, + "GetAudioFrame() was called on channel that is not playing."); + return -1; + } + if (desired_sample_rate_hz == -1) { + shared_->SetLastError(VE_BAD_ARGUMENT, kTraceError, + "GetAudioFrame() was called with bad sample rate."); + return -1; + } + frame->sample_rate_hz_ = desired_sample_rate_hz == 0 ? -1 : + desired_sample_rate_hz; + return channelPtr->GetAudioFrame(channel, *frame); +} + +int VoEExternalMediaImpl::SetExternalMixing(int channel, bool enable) { + WEBRTC_TRACE(kTraceApiCall, kTraceVoice, + VoEId(shared_->instance_id(), channel), + "SetExternalMixing(channel=%d, enable=%d)", channel, enable); + if (!shared_->statistics().Initialized()) + { + shared_->SetLastError(VE_NOT_INITED, kTraceError); + return -1; + } + voe::ScopedChannel sc(shared_->channel_manager(), channel); + voe::Channel* channelPtr = sc.ChannelPtr(); + if (channelPtr == NULL) + { + shared_->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError, + "SetExternalMixing() failed to locate channel"); + return -1; + } + return channelPtr->SetExternalMixing(enable); +} + #endif // WEBRTC_VOICE_ENGINE_EXTERNAL_MEDIA_API } // namespace webrtc diff --git a/webrtc/voice_engine/voe_external_media_impl.h b/webrtc/voice_engine/voe_external_media_impl.h index c922392300..e8f1f59b5f 100644 --- a/webrtc/voice_engine/voe_external_media_impl.h +++ b/webrtc/voice_engine/voe_external_media_impl.h @@ -44,6 +44,11 @@ public: int current_delay_ms, int& lengthSamples); + virtual int GetAudioFrame(int channel, int desired_sample_rate_hz, + AudioFrame* frame); + + virtual int SetExternalMixing(int channel, bool enable); + protected: VoEExternalMediaImpl(voe::SharedData* shared); virtual ~VoEExternalMediaImpl();