1 /*
2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "modules/audio_processing/echo_control_mobile_impl.h"
12
13 #include <string.h>
14
15 #include "modules/audio_processing/aecm/echo_control_mobile.h"
16 #include "modules/audio_processing/audio_buffer.h"
17 #include "rtc_base/constructormagic.h"
18 #include "rtc_base/logging.h"
19
20 namespace webrtc {
21
22 namespace {
MapSetting(EchoControlMobile::RoutingMode mode)23 int16_t MapSetting(EchoControlMobile::RoutingMode mode) {
24 switch (mode) {
25 case EchoControlMobile::kQuietEarpieceOrHeadset:
26 return 0;
27 case EchoControlMobile::kEarpiece:
28 return 1;
29 case EchoControlMobile::kLoudEarpiece:
30 return 2;
31 case EchoControlMobile::kSpeakerphone:
32 return 3;
33 case EchoControlMobile::kLoudSpeakerphone:
34 return 4;
35 }
36 RTC_NOTREACHED();
37 return -1;
38 }
39
MapError(int err)40 AudioProcessing::Error MapError(int err) {
41 switch (err) {
42 case AECM_UNSUPPORTED_FUNCTION_ERROR:
43 return AudioProcessing::kUnsupportedFunctionError;
44 case AECM_NULL_POINTER_ERROR:
45 return AudioProcessing::kNullPointerError;
46 case AECM_BAD_PARAMETER_ERROR:
47 return AudioProcessing::kBadParameterError;
48 case AECM_BAD_PARAMETER_WARNING:
49 return AudioProcessing::kBadStreamParameterWarning;
50 default:
51 // AECM_UNSPECIFIED_ERROR
52 // AECM_UNINITIALIZED_ERROR
53 return AudioProcessing::kUnspecifiedError;
54 }
55 }
56 } // namespace
57
echo_path_size_bytes()58 size_t EchoControlMobile::echo_path_size_bytes() {
59 return WebRtcAecm_echo_path_size_bytes();
60 }
61
62 struct EchoControlMobileImpl::StreamProperties {
63 StreamProperties() = delete;
StreamPropertieswebrtc::EchoControlMobileImpl::StreamProperties64 StreamProperties(int sample_rate_hz,
65 size_t num_reverse_channels,
66 size_t num_output_channels)
67 : sample_rate_hz(sample_rate_hz),
68 num_reverse_channels(num_reverse_channels),
69 num_output_channels(num_output_channels) {}
70
71 int sample_rate_hz;
72 size_t num_reverse_channels;
73 size_t num_output_channels;
74 };
75
76 class EchoControlMobileImpl::Canceller {
77 public:
Canceller()78 Canceller() {
79 state_ = WebRtcAecm_Create();
80 RTC_CHECK(state_);
81 }
82
~Canceller()83 ~Canceller() {
84 RTC_DCHECK(state_);
85 WebRtcAecm_Free(state_);
86 }
87
state()88 void* state() {
89 RTC_DCHECK(state_);
90 return state_;
91 }
92
Initialize(int sample_rate_hz,unsigned char * external_echo_path,size_t echo_path_size_bytes)93 void Initialize(int sample_rate_hz,
94 unsigned char* external_echo_path,
95 size_t echo_path_size_bytes) {
96 RTC_DCHECK(state_);
97 int error = WebRtcAecm_Init(state_, sample_rate_hz);
98 RTC_DCHECK_EQ(AudioProcessing::kNoError, error);
99 if (external_echo_path != NULL) {
100 error = WebRtcAecm_InitEchoPath(state_, external_echo_path,
101 echo_path_size_bytes);
102 RTC_DCHECK_EQ(AudioProcessing::kNoError, error);
103 }
104 }
105
106 private:
107 void* state_;
108 RTC_DISALLOW_COPY_AND_ASSIGN(Canceller);
109 };
110
EchoControlMobileImpl(rtc::CriticalSection * crit_render,rtc::CriticalSection * crit_capture)111 EchoControlMobileImpl::EchoControlMobileImpl(rtc::CriticalSection* crit_render,
112 rtc::CriticalSection* crit_capture)
113 : crit_render_(crit_render),
114 crit_capture_(crit_capture),
115 routing_mode_(kSpeakerphone),
116 comfort_noise_enabled_(true),
117 external_echo_path_(NULL) {
118 RTC_DCHECK(crit_render);
119 RTC_DCHECK(crit_capture);
120 }
121
~EchoControlMobileImpl()122 EchoControlMobileImpl::~EchoControlMobileImpl() {
123 if (external_echo_path_ != NULL) {
124 delete [] external_echo_path_;
125 external_echo_path_ = NULL;
126 }
127 }
128
ProcessRenderAudio(rtc::ArrayView<const int16_t> packed_render_audio)129 void EchoControlMobileImpl::ProcessRenderAudio(
130 rtc::ArrayView<const int16_t> packed_render_audio) {
131 rtc::CritScope cs_capture(crit_capture_);
132 if (!enabled_) {
133 return;
134 }
135
136 RTC_DCHECK(stream_properties_);
137
138 size_t buffer_index = 0;
139 size_t num_frames_per_band =
140 packed_render_audio.size() / (stream_properties_->num_output_channels *
141 stream_properties_->num_reverse_channels);
142
143 for (auto& canceller : cancellers_) {
144 WebRtcAecm_BufferFarend(canceller->state(),
145 &packed_render_audio[buffer_index],
146 num_frames_per_band);
147
148 buffer_index += num_frames_per_band;
149 }
150 }
151
PackRenderAudioBuffer(const AudioBuffer * audio,size_t num_output_channels,size_t num_channels,std::vector<int16_t> * packed_buffer)152 void EchoControlMobileImpl::PackRenderAudioBuffer(
153 const AudioBuffer* audio,
154 size_t num_output_channels,
155 size_t num_channels,
156 std::vector<int16_t>* packed_buffer) {
157 RTC_DCHECK_GE(160, audio->num_frames_per_band());
158 RTC_DCHECK_EQ(num_channels, audio->num_channels());
159
160 // The ordering convention must be followed to pass to the correct AECM.
161 packed_buffer->clear();
162 int render_channel = 0;
163 for (size_t i = 0; i < num_output_channels; i++) {
164 for (size_t j = 0; j < audio->num_channels(); j++) {
165 // Buffer the samples in the render queue.
166 packed_buffer->insert(
167 packed_buffer->end(),
168 audio->split_bands_const(render_channel)[kBand0To8kHz],
169 (audio->split_bands_const(render_channel)[kBand0To8kHz] +
170 audio->num_frames_per_band()));
171 render_channel = (render_channel + 1) % audio->num_channels();
172 }
173 }
174 }
175
NumCancellersRequired(size_t num_output_channels,size_t num_reverse_channels)176 size_t EchoControlMobileImpl::NumCancellersRequired(
177 size_t num_output_channels,
178 size_t num_reverse_channels) {
179 return num_output_channels * num_reverse_channels;
180 }
181
ProcessCaptureAudio(AudioBuffer * audio,int stream_delay_ms)182 int EchoControlMobileImpl::ProcessCaptureAudio(AudioBuffer* audio,
183 int stream_delay_ms) {
184 rtc::CritScope cs_capture(crit_capture_);
185 if (!enabled_) {
186 return AudioProcessing::kNoError;
187 }
188
189 RTC_DCHECK(stream_properties_);
190 RTC_DCHECK_GE(160, audio->num_frames_per_band());
191 RTC_DCHECK_EQ(audio->num_channels(), stream_properties_->num_output_channels);
192 RTC_DCHECK_GE(cancellers_.size(), stream_properties_->num_reverse_channels *
193 audio->num_channels());
194
195 int err = AudioProcessing::kNoError;
196
197 // The ordering convention must be followed to pass to the correct AECM.
198 size_t handle_index = 0;
199 for (size_t capture = 0; capture < audio->num_channels(); ++capture) {
200 // TODO(ajm): improve how this works, possibly inside AECM.
201 // This is kind of hacked up.
202 const int16_t* noisy = audio->low_pass_reference(capture);
203 const int16_t* clean = audio->split_bands_const(capture)[kBand0To8kHz];
204 if (noisy == NULL) {
205 noisy = clean;
206 clean = NULL;
207 }
208 for (size_t render = 0; render < stream_properties_->num_reverse_channels;
209 ++render) {
210 err = WebRtcAecm_Process(cancellers_[handle_index]->state(), noisy, clean,
211 audio->split_bands(capture)[kBand0To8kHz],
212 audio->num_frames_per_band(), stream_delay_ms);
213
214 if (err != AudioProcessing::kNoError) {
215 return MapError(err);
216 }
217
218 ++handle_index;
219 }
220 for (size_t band = 1u; band < audio->num_bands(); ++band) {
221 memset(audio->split_bands(capture)[band],
222 0,
223 audio->num_frames_per_band() *
224 sizeof(audio->split_bands(capture)[band][0]));
225 }
226 }
227 return AudioProcessing::kNoError;
228 }
229
Enable(bool enable)230 int EchoControlMobileImpl::Enable(bool enable) {
231 // Ensure AEC and AECM are not both enabled.
232 rtc::CritScope cs_render(crit_render_);
233 rtc::CritScope cs_capture(crit_capture_);
234 RTC_DCHECK(stream_properties_);
235
236 if (enable &&
237 stream_properties_->sample_rate_hz > AudioProcessing::kSampleRate16kHz) {
238 return AudioProcessing::kBadSampleRateError;
239 }
240
241 if (enable && !enabled_) {
242 enabled_ = enable; // Must be set before Initialize() is called.
243
244 // TODO(peah): Simplify once the Enable function has been removed from
245 // the public APM API.
246 Initialize(stream_properties_->sample_rate_hz,
247 stream_properties_->num_reverse_channels,
248 stream_properties_->num_output_channels);
249 } else {
250 enabled_ = enable;
251 }
252 return AudioProcessing::kNoError;
253 }
254
is_enabled() const255 bool EchoControlMobileImpl::is_enabled() const {
256 rtc::CritScope cs(crit_capture_);
257 return enabled_;
258 }
259
set_routing_mode(RoutingMode mode)260 int EchoControlMobileImpl::set_routing_mode(RoutingMode mode) {
261 if (MapSetting(mode) == -1) {
262 return AudioProcessing::kBadParameterError;
263 }
264
265 {
266 rtc::CritScope cs(crit_capture_);
267 routing_mode_ = mode;
268 }
269 return Configure();
270 }
271
routing_mode() const272 EchoControlMobile::RoutingMode EchoControlMobileImpl::routing_mode()
273 const {
274 rtc::CritScope cs(crit_capture_);
275 return routing_mode_;
276 }
277
enable_comfort_noise(bool enable)278 int EchoControlMobileImpl::enable_comfort_noise(bool enable) {
279 {
280 rtc::CritScope cs(crit_capture_);
281 comfort_noise_enabled_ = enable;
282 }
283 return Configure();
284 }
285
is_comfort_noise_enabled() const286 bool EchoControlMobileImpl::is_comfort_noise_enabled() const {
287 rtc::CritScope cs(crit_capture_);
288 return comfort_noise_enabled_;
289 }
290
SetEchoPath(const void * echo_path,size_t size_bytes)291 int EchoControlMobileImpl::SetEchoPath(const void* echo_path,
292 size_t size_bytes) {
293 {
294 rtc::CritScope cs_render(crit_render_);
295 rtc::CritScope cs_capture(crit_capture_);
296 if (echo_path == NULL) {
297 return AudioProcessing::kNullPointerError;
298 }
299 if (size_bytes != echo_path_size_bytes()) {
300 // Size mismatch
301 return AudioProcessing::kBadParameterError;
302 }
303
304 if (external_echo_path_ == NULL) {
305 external_echo_path_ = new unsigned char[size_bytes];
306 }
307 memcpy(external_echo_path_, echo_path, size_bytes);
308 }
309
310 // TODO(peah): Simplify once the Enable function has been removed from
311 // the public APM API.
312 RTC_DCHECK(stream_properties_);
313 Initialize(stream_properties_->sample_rate_hz,
314 stream_properties_->num_reverse_channels,
315 stream_properties_->num_output_channels);
316 return AudioProcessing::kNoError;
317 }
318
GetEchoPath(void * echo_path,size_t size_bytes) const319 int EchoControlMobileImpl::GetEchoPath(void* echo_path,
320 size_t size_bytes) const {
321 rtc::CritScope cs(crit_capture_);
322 if (echo_path == NULL) {
323 return AudioProcessing::kNullPointerError;
324 }
325 if (size_bytes != echo_path_size_bytes()) {
326 // Size mismatch
327 return AudioProcessing::kBadParameterError;
328 }
329 if (!enabled_) {
330 return AudioProcessing::kNotEnabledError;
331 }
332
333 // Get the echo path from the first channel
334 int32_t err =
335 WebRtcAecm_GetEchoPath(cancellers_[0]->state(), echo_path, size_bytes);
336 if (err != 0) {
337 return MapError(err);
338 }
339
340 return AudioProcessing::kNoError;
341 }
342
Initialize(int sample_rate_hz,size_t num_reverse_channels,size_t num_output_channels)343 void EchoControlMobileImpl::Initialize(int sample_rate_hz,
344 size_t num_reverse_channels,
345 size_t num_output_channels) {
346 rtc::CritScope cs_render(crit_render_);
347 rtc::CritScope cs_capture(crit_capture_);
348
349 stream_properties_.reset(new StreamProperties(
350 sample_rate_hz, num_reverse_channels, num_output_channels));
351
352 if (!enabled_) {
353 return;
354 }
355
356 if (stream_properties_->sample_rate_hz > AudioProcessing::kSampleRate16kHz) {
357 RTC_LOG(LS_ERROR) << "AECM only supports 16 kHz or lower sample rates";
358 }
359
360 cancellers_.resize(
361 NumCancellersRequired(stream_properties_->num_output_channels,
362 stream_properties_->num_reverse_channels));
363
364 for (auto& canceller : cancellers_) {
365 if (!canceller) {
366 canceller.reset(new Canceller());
367 }
368 canceller->Initialize(sample_rate_hz, external_echo_path_,
369 echo_path_size_bytes());
370 }
371
372 Configure();
373 }
374
Configure()375 int EchoControlMobileImpl::Configure() {
376 rtc::CritScope cs_render(crit_render_);
377 rtc::CritScope cs_capture(crit_capture_);
378 AecmConfig config;
379 config.cngMode = comfort_noise_enabled_;
380 config.echoMode = MapSetting(routing_mode_);
381 int error = AudioProcessing::kNoError;
382 for (auto& canceller : cancellers_) {
383 int handle_error = WebRtcAecm_set_config(canceller->state(), config);
384 if (handle_error != AudioProcessing::kNoError) {
385 error = handle_error;
386 }
387 }
388 return error;
389 }
390
391 } // namespace webrtc
392