| /* |
| * 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/tools/agc/agc_manager.h" |
| |
| #include <assert.h> |
| |
| #include "webrtc/modules/audio_processing/agc/agc.h" |
| #include "webrtc/modules/audio_processing/include/audio_processing.h" |
| #include "webrtc/modules/interface/module_common_types.h" |
| #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" |
| #include "webrtc/system_wrappers/interface/logging.h" |
| #include "webrtc/voice_engine/include/voe_external_media.h" |
| #include "webrtc/voice_engine/include/voe_volume_control.h" |
| |
| namespace webrtc { |
| |
| class AgcManagerVolume : public VolumeCallbacks { |
| public: |
| // AgcManagerVolume acquires ownership of |volume|. |
| explicit AgcManagerVolume(VoEVolumeControl* volume) |
| : volume_(volume) { |
| } |
| |
| ~AgcManagerVolume() { |
| if (volume_) { |
| volume_->Release(); |
| } |
| } |
| |
| virtual void SetMicVolume(int volume) { |
| if (volume_->SetMicVolume(volume) != 0) { |
| LOG_FERR1(LS_WARNING, SetMicVolume, volume); |
| } |
| } |
| |
| int GetMicVolume() { |
| unsigned int volume = 0; |
| if (volume_->GetMicVolume(volume) != 0) { |
| LOG_FERR0(LS_WARNING, GetMicVolume); |
| return -1; |
| } |
| return volume; |
| } |
| |
| private: |
| VoEVolumeControl* volume_; |
| }; |
| |
| class MediaCallback : public VoEMediaProcess { |
| public: |
| MediaCallback(AgcManagerDirect* direct, AudioProcessing* audioproc, |
| CriticalSectionWrapper* crit) |
| : direct_(direct), |
| audioproc_(audioproc), |
| crit_(crit), |
| frame_() { |
| } |
| |
| protected: |
| virtual void Process(const int channel, const ProcessingTypes type, |
| int16_t audio[], const size_t samples_per_channel, |
| const int sample_rate_hz, const bool is_stereo) { |
| CriticalSectionScoped cs(crit_); |
| if (direct_->capture_muted()) { |
| return; |
| } |
| |
| // Extract the first channel. |
| const int kMaxSampleRateHz = 48000; |
| const int kMaxSamplesPerChannel = kMaxSampleRateHz / 100; |
| assert(samples_per_channel < kMaxSamplesPerChannel && |
| sample_rate_hz < kMaxSampleRateHz); |
| int16_t mono[kMaxSamplesPerChannel]; |
| int16_t* mono_ptr = audio; |
| if (is_stereo) { |
| for (size_t n = 0; n < samples_per_channel; n++) { |
| mono[n] = audio[n * 2]; |
| } |
| mono_ptr = mono; |
| } |
| |
| direct_->Process(mono_ptr, samples_per_channel, sample_rate_hz); |
| |
| // TODO(ajm): It's unfortunate we have to memcpy to this frame here, but |
| // it's needed for use with AudioProcessing. |
| frame_.num_channels_ = is_stereo ? 2 : 1; |
| frame_.samples_per_channel_ = samples_per_channel; |
| frame_.sample_rate_hz_ = sample_rate_hz; |
| const size_t length_samples = frame_.num_channels_ * samples_per_channel; |
| memcpy(frame_.data_, audio, length_samples * sizeof(int16_t)); |
| |
| // Apply compression to the audio. |
| if (audioproc_->ProcessStream(&frame_) != 0) { |
| LOG_FERR0(LS_ERROR, ProcessStream); |
| } |
| |
| // Copy the compressed audio back to voice engine's array. |
| memcpy(audio, frame_.data_, length_samples * sizeof(int16_t)); |
| } |
| |
| private: |
| AgcManagerDirect* direct_; |
| AudioProcessing* audioproc_; |
| CriticalSectionWrapper* crit_; |
| AudioFrame frame_; |
| }; |
| |
| class PreprocCallback : public VoEMediaProcess { |
| public: |
| PreprocCallback(AgcManagerDirect* direct, CriticalSectionWrapper* crit) |
| : direct_(direct), |
| crit_(crit) { |
| } |
| |
| protected: |
| virtual void Process(const int channel, const ProcessingTypes type, |
| int16_t audio[], const size_t samples_per_channel, |
| const int sample_rate_hz, const bool is_stereo) { |
| CriticalSectionScoped cs(crit_); |
| if (direct_->capture_muted()) { |
| return; |
| } |
| direct_->AnalyzePreProcess(audio, is_stereo ? 2 : 1, samples_per_channel); |
| } |
| |
| private: |
| AgcManagerDirect* direct_; |
| CriticalSectionWrapper* crit_; |
| }; |
| |
| AgcManager::AgcManager(VoiceEngine* voe) |
| : media_(VoEExternalMedia::GetInterface(voe)), |
| volume_callbacks_(new AgcManagerVolume(VoEVolumeControl::GetInterface( |
| voe))), |
| crit_(CriticalSectionWrapper::CreateCriticalSection()), |
| enabled_(false), |
| initialized_(false) { |
| Config config; |
| config.Set<ExperimentalAgc>(new ExperimentalAgc(false)); |
| audioproc_.reset(AudioProcessing::Create(config)); |
| direct_.reset(new AgcManagerDirect(audioproc_->gain_control(), |
| volume_callbacks_.get(), |
| kAgcStartupMinVolume)); |
| media_callback_.reset(new MediaCallback(direct_.get(), |
| audioproc_.get(), |
| crit_.get())); |
| preproc_callback_.reset(new PreprocCallback(direct_.get(), crit_.get())); |
| } |
| |
| AgcManager::AgcManager(VoEExternalMedia* media, |
| VoEVolumeControl* volume, |
| Agc* agc, |
| AudioProcessing* audioproc) |
| : media_(media), |
| volume_callbacks_(new AgcManagerVolume(volume)), |
| crit_(CriticalSectionWrapper::CreateCriticalSection()), |
| audioproc_(audioproc), |
| direct_(new AgcManagerDirect(agc, |
| audioproc_->gain_control(), |
| volume_callbacks_.get(), |
| kAgcStartupMinVolume)), |
| media_callback_( |
| new MediaCallback(direct_.get(), audioproc_.get(), crit_.get())), |
| preproc_callback_(new PreprocCallback(direct_.get(), crit_.get())), |
| enabled_(false), |
| initialized_(false) { |
| } |
| |
| AgcManager::AgcManager() |
| : media_(NULL), |
| enabled_(false), |
| initialized_(false) { |
| } |
| |
| AgcManager::~AgcManager() { |
| if (media_) { |
| if (enabled_) { |
| DeregisterCallbacks(); |
| } |
| media_->Release(); |
| } |
| } |
| |
| int AgcManager::Enable(bool enable) { |
| if (enable == enabled_) { |
| return 0; |
| } |
| if (!initialized_) { |
| CriticalSectionScoped cs(crit_.get()); |
| if (audioproc_->gain_control()->Enable(true) != 0) { |
| LOG_FERR1(LS_ERROR, gain_control()->Enable, true); |
| return -1; |
| } |
| if (direct_->Initialize() != 0) { |
| assert(false); |
| return -1; |
| } |
| initialized_ = true; |
| } |
| |
| if (enable) { |
| if (media_->RegisterExternalMediaProcessing(0, kRecordingAllChannelsMixed, |
| *media_callback_) != 0) { |
| LOG(LS_ERROR) << "Failed to register postproc callback"; |
| return -1; |
| } |
| if (media_->RegisterExternalMediaProcessing(0, kRecordingPreprocessing, |
| *preproc_callback_) != 0) { |
| LOG(LS_ERROR) << "Failed to register preproc callback"; |
| return -1; |
| } |
| } else { |
| if (DeregisterCallbacks() != 0) |
| return -1; |
| } |
| enabled_ = enable; |
| return 0; |
| } |
| |
| void AgcManager::CaptureDeviceChanged() { |
| CriticalSectionScoped cs(crit_.get()); |
| direct_->Initialize(); |
| } |
| |
| void AgcManager::SetCaptureMuted(bool muted) { |
| CriticalSectionScoped cs(crit_.get()); |
| direct_->SetCaptureMuted(muted); |
| } |
| |
| int AgcManager::DeregisterCallbacks() { |
| // DeRegister shares a lock with the Process() callback. This call will block |
| // until the callback is finished and it's safe to continue teardown. |
| int err = 0; |
| if (media_->DeRegisterExternalMediaProcessing(0, |
| kRecordingAllChannelsMixed) != 0) { |
| LOG(LS_ERROR) << "Failed to deregister postproc callback"; |
| err = -1; |
| } |
| if (media_->DeRegisterExternalMediaProcessing(0, |
| kRecordingPreprocessing) != 0) { |
| LOG(LS_ERROR) << "Failed to deregister preproc callback"; |
| err = -1; |
| } |
| return err; |
| } |
| |
| } // namespace webrtc |