| /* Copyright (c) 2018 The Chromium Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "api/audio/echo_canceller3_factory.h" |
| #include "modules/audio_processing/aec_dump/aec_dump_factory.h" |
| #include "modules/audio_processing/include/aec_dump.h" |
| #include "modules/audio_processing/include/audio_processing.h" |
| #include "modules/include/module_common_types.h" |
| #include "rtc_base/task_queue.h" |
| |
| extern "C" { |
| #include "webrtc_apm.h" |
| |
| int convert_to_aec3_config( |
| const struct aec_config *config, |
| webrtc::EchoCanceller3Config *aec3_config) |
| { |
| aec3_config->delay = { |
| config->delay.default_delay, |
| config->delay.down_sampling_factor, |
| config->delay.num_filters, |
| config->delay.api_call_jitter_blocks, |
| config->delay.min_echo_path_delay_blocks, |
| config->delay.delay_headroom_blocks, |
| config->delay.hysteresis_limit_1_blocks, |
| config->delay.hysteresis_limit_2_blocks, |
| config->delay.skew_hysteresis_blocks |
| }; |
| aec3_config->filter.main = { |
| config->filter.main.length_blocks, |
| config->filter.main.leakage_converged, |
| config->filter.main.leakage_diverged, |
| config->filter.main.error_floor, |
| config->filter.main.noise_gate |
| }; |
| aec3_config->filter.shadow = { |
| config->filter.shadow.length_blocks, |
| config->filter.shadow.rate, |
| config->filter.shadow.noise_gate |
| }; |
| aec3_config->filter.main_initial = { |
| config->filter.main_initial.length_blocks, |
| config->filter.main_initial.leakage_converged, |
| config->filter.main_initial.leakage_diverged, |
| config->filter.main_initial.error_floor, |
| config->filter.main_initial.noise_gate |
| }; |
| aec3_config->filter.shadow_initial = { |
| config->filter.shadow_initial.length_blocks, |
| config->filter.shadow_initial.rate, |
| config->filter.shadow_initial.noise_gate |
| }; |
| aec3_config->filter.config_change_duration_blocks = |
| config->filter.config_change_duration_blocks; |
| aec3_config->erle = { |
| config->erle.min, |
| config->erle.max_l, |
| config->erle.max_h, |
| }; |
| aec3_config->ep_strength = { |
| config->ep_strength.lf, |
| config->ep_strength.mf, |
| config->ep_strength.hf, |
| config->ep_strength.default_len, |
| static_cast<bool>(config->ep_strength.echo_can_saturate), |
| static_cast<bool>(config->ep_strength.bounded_erl) |
| }; |
| |
| aec3_config->gain_mask.m1 = config->gain_mask.m1; |
| aec3_config->gain_mask.m2 = config->gain_mask.m2; |
| aec3_config->gain_mask.m3 = config->gain_mask.m3; |
| aec3_config->gain_mask.m5 = config->gain_mask.m5; |
| aec3_config->gain_mask.m6 = config->gain_mask.m6; |
| aec3_config->gain_mask.m7 = config->gain_mask.m7; |
| aec3_config->gain_mask.m8 = config->gain_mask.m8; |
| aec3_config->gain_mask.m9 = config->gain_mask.m9; |
| aec3_config->gain_mask.gain_curve_offset = |
| config->gain_mask.gain_curve_offset; |
| aec3_config->gain_mask.gain_curve_slope = |
| config->gain_mask.gain_curve_slope; |
| aec3_config->gain_mask.temporal_masking_lf = |
| config->gain_mask.temporal_masking_lf; |
| aec3_config->gain_mask.temporal_masking_hf = |
| config->gain_mask.temporal_masking_hf; |
| aec3_config->gain_mask.temporal_masking_lf_bands = |
| config->gain_mask.temporal_masking_lf_bands; |
| |
| aec3_config->echo_audibility = { |
| config->echo_audibility.low_render_limit, |
| config->echo_audibility.normal_render_limit, |
| config->echo_audibility.floor_power, |
| config->echo_audibility.audibility_threshold_lf, |
| config->echo_audibility.audibility_threshold_mf, |
| config->echo_audibility.audibility_threshold_hf, |
| static_cast<bool>( |
| config->echo_audibility.use_stationary_properties) |
| }; |
| aec3_config->render_levels = { |
| config->render_levels.active_render_limit, |
| config->render_levels.poor_excitation_render_limit, |
| }; |
| aec3_config->gain_updates.low_noise = { |
| config->gain_updates.low_noise.max_inc, |
| config->gain_updates.low_noise.max_dec, |
| config->gain_updates.low_noise.rate_inc, |
| config->gain_updates.low_noise.rate_dec, |
| config->gain_updates.low_noise.min_inc, |
| config->gain_updates.low_noise.min_dec, |
| }; |
| aec3_config->gain_updates.initial = { |
| config->gain_updates.initial.max_inc, |
| config->gain_updates.initial.max_dec, |
| config->gain_updates.initial.rate_inc, |
| config->gain_updates.initial.rate_dec, |
| config->gain_updates.initial.min_inc, |
| config->gain_updates.initial.min_dec, |
| }; |
| aec3_config->gain_updates.normal = { |
| config->gain_updates.normal.max_inc, |
| config->gain_updates.normal.max_dec, |
| config->gain_updates.normal.rate_inc, |
| config->gain_updates.normal.rate_dec, |
| config->gain_updates.normal.min_inc, |
| config->gain_updates.normal.min_dec, |
| }; |
| aec3_config->gain_updates.saturation = { |
| config->gain_updates.saturation.max_inc, |
| config->gain_updates.saturation.max_dec, |
| config->gain_updates.saturation.rate_inc, |
| config->gain_updates.saturation.rate_dec, |
| config->gain_updates.saturation.min_inc, |
| config->gain_updates.saturation.min_dec, |
| }; |
| aec3_config->gain_updates.nonlinear = { |
| config->gain_updates.nonlinear.max_inc, |
| config->gain_updates.nonlinear.max_dec, |
| config->gain_updates.nonlinear.rate_inc, |
| config->gain_updates.nonlinear.rate_dec, |
| config->gain_updates.nonlinear.min_inc, |
| config->gain_updates.nonlinear.min_dec, |
| }; |
| aec3_config->gain_updates.floor_first_increase = |
| config->gain_updates.floor_first_increase; |
| |
| aec3_config->echo_removal_control = { |
| { |
| config->echo_removal_control.gain_rampup.first_non_zero_gain, |
| config->echo_removal_control.gain_rampup.non_zero_gain_blocks, |
| config->echo_removal_control.gain_rampup.full_gain_blocks |
| }, |
| static_cast<bool>(config->echo_removal_control.has_clock_drift) |
| }; |
| |
| aec3_config->echo_model = { |
| config->echo_model.noise_floor_hold, |
| config->echo_model.min_noise_floor_power, |
| config->echo_model.stationary_gate_slope, |
| config->echo_model.noise_gate_power, |
| config->echo_model.noise_gate_slope, |
| config->echo_model.render_pre_window_size, |
| config->echo_model.render_post_window_size, |
| config->echo_model.nonlinear_hold, |
| config->echo_model.nonlinear_release |
| }; |
| |
| |
| return 0; |
| } |
| |
| webrtc_apm webrtc_apm_create(unsigned int num_channels, |
| unsigned int frame_rate, |
| struct aec_config *config) |
| { |
| int err; |
| webrtc::AudioProcessing *apm; |
| webrtc::AudioProcessing::ChannelLayout channel_layout; |
| webrtc::AudioProcessingBuilder apm_builder; |
| webrtc::EchoCanceller3Config aec3_config; |
| std::unique_ptr<webrtc::EchoControlFactory> ec3_factory; |
| |
| if (config) { |
| convert_to_aec3_config(config, &aec3_config); |
| ec3_factory.reset( |
| new webrtc::EchoCanceller3Factory(aec3_config)); |
| } else { |
| ec3_factory.reset(new webrtc::EchoCanceller3Factory()); |
| } |
| |
| switch (num_channels) { |
| case 1: |
| channel_layout = webrtc::AudioProcessing::kMono; |
| break; |
| case 2: |
| channel_layout = webrtc::AudioProcessing::kStereo; |
| break; |
| default: |
| return NULL; |
| } |
| |
| apm_builder.SetEchoControlFactory(std::move(ec3_factory)); |
| apm = apm_builder.Create(); |
| |
| err = apm->Initialize(frame_rate, frame_rate, frame_rate, |
| channel_layout, channel_layout, channel_layout); |
| if (err) { |
| delete apm; |
| return NULL; |
| } |
| |
| return reinterpret_cast<webrtc_apm>(apm); |
| } |
| |
| int webrtc_apm_process_reverse_stream_f( |
| webrtc_apm ptr, |
| int num_channels, int rate, |
| float *const *data) |
| { |
| webrtc::AudioProcessing *apm; |
| webrtc::StreamConfig config = |
| webrtc::StreamConfig(rate, num_channels); |
| |
| apm = reinterpret_cast<webrtc::AudioProcessing *>(ptr); |
| |
| return apm->ProcessReverseStream(data, config, config, data); |
| } |
| |
| int webrtc_apm_process_reverse_stream(webrtc_apm ptr, |
| int num_channels, int rate, |
| int16_t *data, int nframes) |
| { |
| webrtc::AudioFrame af; |
| webrtc::AudioProcessing *apm; |
| |
| apm = reinterpret_cast<webrtc::AudioProcessing *>(ptr); |
| |
| af.UpdateFrame(0xFFFFFFFF, data, nframes, rate, |
| webrtc::AudioFrame::kNormalSpeech, |
| webrtc::AudioFrame::kVadUnknown, |
| num_channels); |
| return apm->ProcessReverseStream(&af); |
| } |
| |
| int webrtc_apm_process_stream_f(webrtc_apm ptr, |
| int num_channels, |
| int rate, |
| float *const *data) |
| { |
| webrtc::AudioProcessing *apm; |
| |
| webrtc::StreamConfig config = |
| webrtc::StreamConfig(rate, num_channels); |
| apm = reinterpret_cast<webrtc::AudioProcessing *>(ptr); |
| return apm->ProcessStream(data, config, config, data); |
| } |
| |
| |
| int webrtc_apm_process_stream(webrtc_apm ptr, int num_channels, |
| int rate, int16_t *data, int nframes) |
| { |
| int ret; |
| webrtc::AudioFrame af; |
| webrtc::AudioProcessing *apm; |
| |
| apm = reinterpret_cast<webrtc::AudioProcessing *>(ptr); |
| //set stream delay |
| af.UpdateFrame(0xFFFFFFFF, data, nframes, rate, |
| webrtc::AudioFrame::kNormalSpeech, |
| webrtc::AudioFrame::kVadUnknown, |
| num_channels); |
| ret = apm->ProcessStream(&af); |
| if (ret) |
| return ret; |
| |
| memcpy(data, af.data(), nframes * num_channels * 2); |
| return ret; |
| } |
| |
| void webrtc_apm_destroy(webrtc_apm ptr) |
| { |
| webrtc::AudioProcessing *apm; |
| apm = reinterpret_cast<webrtc::AudioProcessing *>(ptr); |
| delete apm; |
| } |
| |
| int webrtc_apm_set_stream_delay(webrtc_apm ptr, int delay_ms) |
| { |
| webrtc::AudioProcessing *apm; |
| |
| apm = reinterpret_cast<webrtc::AudioProcessing *>(ptr); |
| return apm->set_stream_delay_ms(delay_ms); |
| } |
| |
| int webrtc_apm_aec_dump(webrtc_apm ptr, void** wq_ptr, int start, FILE *handle) |
| { |
| webrtc::AudioProcessing *apm; |
| rtc::TaskQueue *work_queue; |
| |
| apm = reinterpret_cast<webrtc::AudioProcessing *>(ptr); |
| |
| if (start) { |
| work_queue = new rtc::TaskQueue("aecdump-worker-queue", |
| rtc::TaskQueue::Priority::LOW); |
| auto aec_dump = webrtc::AecDumpFactory::Create(handle, -1, work_queue); |
| if (!aec_dump) |
| return -ENOMEM; |
| apm->AttachAecDump(std::move(aec_dump)); |
| *wq_ptr = reinterpret_cast<void *>(work_queue); |
| } else { |
| apm->DetachAecDump(); |
| work_queue = reinterpret_cast<rtc::TaskQueue *>(*wq_ptr); |
| if (work_queue) { |
| delete work_queue; |
| work_queue = NULL; |
| } |
| } |
| return 0; |
| } |
| |
| int webrtc_apm_has_echo(webrtc_apm ptr) |
| { |
| webrtc::AudioProcessing *apm; |
| |
| apm = reinterpret_cast<webrtc::AudioProcessing *>(ptr); |
| return apm->echo_cancellation()->stream_has_echo(); |
| } |
| |
| } // extern "C" |