Moving src/webrtc into src/.
In order to eliminate the WebRTC Subtree mirror in Chromium, WebRTC is moving the content of the src/webrtc directory up to the src/ directory. NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true TBR=tommi@webrtc.org Bug: chromium:611808 Change-Id: Iac59c5b51b950f174119565bac87955a7994bc38 Reviewed-on: https://webrtc-review.googlesource.com/1560 Commit-Queue: Mirko Bonadei <mbonadei@webrtc.org> Reviewed-by: Henrik Kjellander <kjellander@webrtc.org> Cr-Commit-Position: refs/heads/master@{#19845}
This commit is contained in:
committed by
Commit Bot
parent
6674846b4a
commit
bb547203bf
@ -1,883 +0,0 @@
|
||||
# Copyright (c) 2014 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.
|
||||
|
||||
import("//build/config/arm.gni")
|
||||
import("//third_party/protobuf/proto_library.gni")
|
||||
import("../../webrtc.gni")
|
||||
|
||||
declare_args() {
|
||||
# Disables the usual mode where we trust the reported system delay
|
||||
# values the AEC receives. The corresponding define is set appropriately
|
||||
# in the code, but it can be force-enabled here for testing.
|
||||
aec_untrusted_delay_for_testing = false
|
||||
}
|
||||
|
||||
rtc_static_library("audio_processing") {
|
||||
sources = [
|
||||
"aec/aec_core.cc",
|
||||
"aec/aec_core.h",
|
||||
"aec/aec_core_optimized_methods.h",
|
||||
"aec/aec_resampler.cc",
|
||||
"aec/aec_resampler.h",
|
||||
"aec/echo_cancellation.cc",
|
||||
"aec/echo_cancellation.h",
|
||||
"aec3/adaptive_fir_filter.cc",
|
||||
"aec3/adaptive_fir_filter.h",
|
||||
"aec3/aec3_common.cc",
|
||||
"aec3/aec3_common.h",
|
||||
"aec3/aec3_fft.cc",
|
||||
"aec3/aec3_fft.h",
|
||||
"aec3/aec_state.cc",
|
||||
"aec3/aec_state.h",
|
||||
"aec3/block_framer.cc",
|
||||
"aec3/block_framer.h",
|
||||
"aec3/block_processor.cc",
|
||||
"aec3/block_processor.h",
|
||||
"aec3/block_processor_metrics.cc",
|
||||
"aec3/block_processor_metrics.h",
|
||||
"aec3/cascaded_biquad_filter.cc",
|
||||
"aec3/cascaded_biquad_filter.h",
|
||||
"aec3/comfort_noise_generator.cc",
|
||||
"aec3/comfort_noise_generator.h",
|
||||
"aec3/decimator_by_4.cc",
|
||||
"aec3/decimator_by_4.h",
|
||||
"aec3/downsampled_render_buffer.cc",
|
||||
"aec3/downsampled_render_buffer.h",
|
||||
"aec3/echo_canceller3.cc",
|
||||
"aec3/echo_canceller3.h",
|
||||
"aec3/echo_path_delay_estimator.cc",
|
||||
"aec3/echo_path_delay_estimator.h",
|
||||
"aec3/echo_path_variability.cc",
|
||||
"aec3/echo_path_variability.h",
|
||||
"aec3/echo_remover.cc",
|
||||
"aec3/echo_remover.h",
|
||||
"aec3/echo_remover_metrics.cc",
|
||||
"aec3/echo_remover_metrics.h",
|
||||
"aec3/erl_estimator.cc",
|
||||
"aec3/erl_estimator.h",
|
||||
"aec3/erle_estimator.cc",
|
||||
"aec3/erle_estimator.h",
|
||||
"aec3/fft_data.h",
|
||||
"aec3/frame_blocker.cc",
|
||||
"aec3/frame_blocker.h",
|
||||
"aec3/main_filter_update_gain.cc",
|
||||
"aec3/main_filter_update_gain.h",
|
||||
"aec3/matched_filter.cc",
|
||||
"aec3/matched_filter.h",
|
||||
"aec3/matched_filter_lag_aggregator.cc",
|
||||
"aec3/matched_filter_lag_aggregator.h",
|
||||
"aec3/output_selector.cc",
|
||||
"aec3/output_selector.h",
|
||||
"aec3/render_buffer.cc",
|
||||
"aec3/render_buffer.h",
|
||||
"aec3/render_delay_buffer.cc",
|
||||
"aec3/render_delay_buffer.h",
|
||||
"aec3/render_delay_controller.cc",
|
||||
"aec3/render_delay_controller.h",
|
||||
"aec3/render_delay_controller_metrics.cc",
|
||||
"aec3/render_delay_controller_metrics.h",
|
||||
"aec3/render_signal_analyzer.cc",
|
||||
"aec3/render_signal_analyzer.h",
|
||||
"aec3/residual_echo_estimator.cc",
|
||||
"aec3/residual_echo_estimator.h",
|
||||
"aec3/shadow_filter_update_gain.cc",
|
||||
"aec3/shadow_filter_update_gain.h",
|
||||
"aec3/subtractor.cc",
|
||||
"aec3/subtractor.h",
|
||||
"aec3/subtractor_output.h",
|
||||
"aec3/suppression_filter.cc",
|
||||
"aec3/suppression_filter.h",
|
||||
"aec3/suppression_gain.cc",
|
||||
"aec3/suppression_gain.h",
|
||||
"aec3/vector_math.h",
|
||||
"aecm/aecm_core.cc",
|
||||
"aecm/aecm_core.h",
|
||||
"aecm/echo_control_mobile.cc",
|
||||
"aecm/echo_control_mobile.h",
|
||||
"agc/agc.cc",
|
||||
"agc/agc.h",
|
||||
"agc/agc_manager_direct.cc",
|
||||
"agc/agc_manager_direct.h",
|
||||
"agc/gain_map_internal.h",
|
||||
"agc/loudness_histogram.cc",
|
||||
"agc/loudness_histogram.h",
|
||||
"agc/utility.cc",
|
||||
"agc/utility.h",
|
||||
"agc2/digital_gain_applier.cc",
|
||||
"agc2/digital_gain_applier.h",
|
||||
"agc2/gain_controller2.cc",
|
||||
"agc2/gain_controller2.h",
|
||||
"audio_buffer.cc",
|
||||
"audio_buffer.h",
|
||||
"audio_processing_impl.cc",
|
||||
"audio_processing_impl.h",
|
||||
"beamformer/array_util.cc",
|
||||
"beamformer/array_util.h",
|
||||
"beamformer/complex_matrix.h",
|
||||
"beamformer/covariance_matrix_generator.cc",
|
||||
"beamformer/covariance_matrix_generator.h",
|
||||
"beamformer/matrix.h",
|
||||
"beamformer/nonlinear_beamformer.cc",
|
||||
"beamformer/nonlinear_beamformer.h",
|
||||
"common.h",
|
||||
"echo_cancellation_impl.cc",
|
||||
"echo_cancellation_impl.h",
|
||||
"echo_control_mobile_impl.cc",
|
||||
"echo_control_mobile_impl.h",
|
||||
"echo_detector/circular_buffer.cc",
|
||||
"echo_detector/circular_buffer.h",
|
||||
"echo_detector/mean_variance_estimator.cc",
|
||||
"echo_detector/mean_variance_estimator.h",
|
||||
"echo_detector/moving_max.cc",
|
||||
"echo_detector/moving_max.h",
|
||||
"echo_detector/normalized_covariance_estimator.cc",
|
||||
"echo_detector/normalized_covariance_estimator.h",
|
||||
"gain_control_for_experimental_agc.cc",
|
||||
"gain_control_for_experimental_agc.h",
|
||||
"gain_control_impl.cc",
|
||||
"gain_control_impl.h",
|
||||
"include/audio_processing.cc",
|
||||
"include/audio_processing.h",
|
||||
"include/config.cc",
|
||||
"include/config.h",
|
||||
"level_controller/biquad_filter.cc",
|
||||
"level_controller/biquad_filter.h",
|
||||
"level_controller/down_sampler.cc",
|
||||
"level_controller/down_sampler.h",
|
||||
"level_controller/gain_applier.cc",
|
||||
"level_controller/gain_applier.h",
|
||||
"level_controller/gain_selector.cc",
|
||||
"level_controller/gain_selector.h",
|
||||
"level_controller/level_controller.cc",
|
||||
"level_controller/level_controller.h",
|
||||
"level_controller/level_controller_constants.h",
|
||||
"level_controller/noise_level_estimator.cc",
|
||||
"level_controller/noise_level_estimator.h",
|
||||
"level_controller/noise_spectrum_estimator.cc",
|
||||
"level_controller/noise_spectrum_estimator.h",
|
||||
"level_controller/peak_level_estimator.cc",
|
||||
"level_controller/peak_level_estimator.h",
|
||||
"level_controller/saturating_gain_estimator.cc",
|
||||
"level_controller/saturating_gain_estimator.h",
|
||||
"level_controller/signal_classifier.cc",
|
||||
"level_controller/signal_classifier.h",
|
||||
"level_estimator_impl.cc",
|
||||
"level_estimator_impl.h",
|
||||
"logging/apm_data_dumper.cc",
|
||||
"logging/apm_data_dumper.h",
|
||||
"low_cut_filter.cc",
|
||||
"low_cut_filter.h",
|
||||
"noise_suppression_impl.cc",
|
||||
"noise_suppression_impl.h",
|
||||
"render_queue_item_verifier.h",
|
||||
"residual_echo_detector.cc",
|
||||
"residual_echo_detector.h",
|
||||
"rms_level.cc",
|
||||
"rms_level.h",
|
||||
"splitting_filter.cc",
|
||||
"splitting_filter.h",
|
||||
"three_band_filter_bank.cc",
|
||||
"three_band_filter_bank.h",
|
||||
"transient/common.h",
|
||||
"transient/daubechies_8_wavelet_coeffs.h",
|
||||
"transient/dyadic_decimator.h",
|
||||
"transient/moving_moments.cc",
|
||||
"transient/moving_moments.h",
|
||||
"transient/transient_detector.cc",
|
||||
"transient/transient_detector.h",
|
||||
"transient/transient_suppressor.cc",
|
||||
"transient/transient_suppressor.h",
|
||||
"transient/wpd_node.cc",
|
||||
"transient/wpd_node.h",
|
||||
"transient/wpd_tree.cc",
|
||||
"transient/wpd_tree.h",
|
||||
"typing_detection.cc",
|
||||
"typing_detection.h",
|
||||
"utility/block_mean_calculator.cc",
|
||||
"utility/block_mean_calculator.h",
|
||||
"utility/delay_estimator.cc",
|
||||
"utility/delay_estimator.h",
|
||||
"utility/delay_estimator_internal.h",
|
||||
"utility/delay_estimator_wrapper.cc",
|
||||
"utility/delay_estimator_wrapper.h",
|
||||
"utility/ooura_fft.cc",
|
||||
"utility/ooura_fft.h",
|
||||
"utility/ooura_fft_tables_common.h",
|
||||
"vad/common.h",
|
||||
"vad/gmm.cc",
|
||||
"vad/gmm.h",
|
||||
"vad/noise_gmm_tables.h",
|
||||
"vad/pitch_based_vad.cc",
|
||||
"vad/pitch_based_vad.h",
|
||||
"vad/pitch_internal.cc",
|
||||
"vad/pitch_internal.h",
|
||||
"vad/pole_zero_filter.cc",
|
||||
"vad/pole_zero_filter.h",
|
||||
"vad/standalone_vad.cc",
|
||||
"vad/standalone_vad.h",
|
||||
"vad/vad_audio_proc.cc",
|
||||
"vad/vad_audio_proc.h",
|
||||
"vad/vad_audio_proc_internal.h",
|
||||
"vad/vad_circular_buffer.cc",
|
||||
"vad/vad_circular_buffer.h",
|
||||
"vad/voice_activity_detector.cc",
|
||||
"vad/voice_activity_detector.h",
|
||||
"vad/voice_gmm_tables.h",
|
||||
"voice_detection_impl.cc",
|
||||
"voice_detection_impl.h",
|
||||
]
|
||||
|
||||
defines = []
|
||||
deps = [
|
||||
":aec_dump_interface",
|
||||
"..:module_api",
|
||||
"../..:webrtc_common",
|
||||
"../../api:array_view",
|
||||
"../../api:optional",
|
||||
"../../audio/utility:audio_frame_operations",
|
||||
"../../rtc_base:gtest_prod",
|
||||
"../../rtc_base:protobuf_utils",
|
||||
"../audio_coding:isac",
|
||||
]
|
||||
public_deps = [
|
||||
":audio_processing_c",
|
||||
]
|
||||
|
||||
if (apm_debug_dump) {
|
||||
defines += [ "WEBRTC_APM_DEBUG_DUMP=1" ]
|
||||
} else {
|
||||
defines += [ "WEBRTC_APM_DEBUG_DUMP=0" ]
|
||||
}
|
||||
|
||||
if (aec_untrusted_delay_for_testing) {
|
||||
defines += [ "WEBRTC_UNTRUSTED_DELAY" ]
|
||||
}
|
||||
|
||||
if (rtc_enable_protobuf) {
|
||||
defines += [ "WEBRTC_AUDIOPROC_DEBUG_DUMP" ]
|
||||
deps += [ ":audioproc_debug_proto" ]
|
||||
}
|
||||
|
||||
if (rtc_enable_intelligibility_enhancer) {
|
||||
defines += [ "WEBRTC_INTELLIGIBILITY_ENHANCER=1" ]
|
||||
sources += [
|
||||
"intelligibility/intelligibility_enhancer.cc",
|
||||
"intelligibility/intelligibility_enhancer.h",
|
||||
"intelligibility/intelligibility_utils.cc",
|
||||
"intelligibility/intelligibility_utils.h",
|
||||
]
|
||||
} else {
|
||||
defines += [ "WEBRTC_INTELLIGIBILITY_ENHANCER=0" ]
|
||||
}
|
||||
|
||||
if (rtc_prefer_fixed_point) {
|
||||
defines += [ "WEBRTC_NS_FIXED" ]
|
||||
} else {
|
||||
defines += [ "WEBRTC_NS_FLOAT" ]
|
||||
}
|
||||
|
||||
if (current_cpu == "x86" || current_cpu == "x64") {
|
||||
deps += [ ":audio_processing_sse2" ]
|
||||
}
|
||||
|
||||
if (rtc_build_with_neon) {
|
||||
deps += [ ":audio_processing_neon" ]
|
||||
}
|
||||
|
||||
if (current_cpu == "mipsel") {
|
||||
sources += [ "aecm/aecm_core_mips.cc" ]
|
||||
if (mips_float_abi == "hard") {
|
||||
sources += [
|
||||
"aec/aec_core_mips.cc",
|
||||
"utility/ooura_fft_mips.cc",
|
||||
]
|
||||
}
|
||||
} else {
|
||||
sources += [ "aecm/aecm_core_c.cc" ]
|
||||
}
|
||||
|
||||
# TODO(jschuh): Bug 1348: fix this warning.
|
||||
configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
|
||||
|
||||
deps += [
|
||||
"../../common_audio",
|
||||
"../../rtc_base:rtc_base_approved",
|
||||
"../../system_wrappers",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_source_set("aec_dump_interface") {
|
||||
sources = [
|
||||
"include/aec_dump.cc",
|
||||
"include/aec_dump.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"../../api:array_view",
|
||||
"../../rtc_base:rtc_base_approved",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_source_set("audio_processing_c") {
|
||||
visibility = [ ":*" ] # Only targets in this file can depend on this.
|
||||
sources = [
|
||||
"agc/legacy/analog_agc.c",
|
||||
"agc/legacy/analog_agc.h",
|
||||
"agc/legacy/digital_agc.c",
|
||||
"agc/legacy/digital_agc.h",
|
||||
"agc/legacy/gain_control.h",
|
||||
]
|
||||
|
||||
if (rtc_prefer_fixed_point) {
|
||||
sources += [
|
||||
"ns/noise_suppression_x.c",
|
||||
"ns/noise_suppression_x.h",
|
||||
"ns/nsx_core.c",
|
||||
"ns/nsx_core.h",
|
||||
"ns/nsx_defines.h",
|
||||
]
|
||||
if (current_cpu == "mipsel") {
|
||||
sources += [ "ns/nsx_core_mips.c" ]
|
||||
} else {
|
||||
sources += [ "ns/nsx_core_c.c" ]
|
||||
}
|
||||
} else {
|
||||
sources += [
|
||||
"ns/defines.h",
|
||||
"ns/noise_suppression.c",
|
||||
"ns/noise_suppression.h",
|
||||
"ns/ns_core.c",
|
||||
"ns/ns_core.h",
|
||||
"ns/windows_private.h",
|
||||
]
|
||||
}
|
||||
|
||||
deps = [
|
||||
"../..:webrtc_common",
|
||||
"../../common_audio",
|
||||
"../../rtc_base:rtc_base_approved",
|
||||
"../../system_wrappers",
|
||||
]
|
||||
|
||||
if (rtc_build_with_neon) {
|
||||
deps += [ ":audio_processing_neon_c" ]
|
||||
}
|
||||
}
|
||||
|
||||
if (rtc_enable_protobuf) {
|
||||
proto_library("audioproc_debug_proto") {
|
||||
sources = [
|
||||
"debug.proto",
|
||||
]
|
||||
|
||||
proto_out_dir = "webrtc/modules/audio_processing"
|
||||
}
|
||||
}
|
||||
|
||||
if (current_cpu == "x86" || current_cpu == "x64") {
|
||||
rtc_static_library("audio_processing_sse2") {
|
||||
# TODO(ehmaldonado): Remove (bugs.webrtc.org/6828)
|
||||
# Errors on cyclic dependency with :audio_processing if enabled.
|
||||
check_includes = false
|
||||
|
||||
sources = [
|
||||
"aec/aec_core_sse2.cc",
|
||||
"utility/ooura_fft_sse2.cc",
|
||||
"utility/ooura_fft_tables_neon_sse2.h",
|
||||
]
|
||||
|
||||
if (is_posix) {
|
||||
cflags = [ "-msse2" ]
|
||||
}
|
||||
|
||||
if (apm_debug_dump) {
|
||||
defines = [ "WEBRTC_APM_DEBUG_DUMP=1" ]
|
||||
} else {
|
||||
defines = [ "WEBRTC_APM_DEBUG_DUMP=0" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rtc_build_with_neon) {
|
||||
rtc_static_library("audio_processing_neon") {
|
||||
# TODO(ehmaldonado): Remove (bugs.webrtc.org/6828)
|
||||
# Errors on cyclic dependency with :audio_processing if enabled.
|
||||
check_includes = false
|
||||
|
||||
sources = [
|
||||
"aec/aec_core_neon.cc",
|
||||
"aecm/aecm_core_neon.cc",
|
||||
"utility/ooura_fft_neon.cc",
|
||||
"utility/ooura_fft_tables_neon_sse2.h",
|
||||
]
|
||||
|
||||
if (current_cpu != "arm64") {
|
||||
# Enable compilation for the NEON instruction set. This is needed
|
||||
# since //build/config/arm.gni only enables NEON for iOS, not Android.
|
||||
# This provides the same functionality as webrtc/build/arm_neon.gypi.
|
||||
suppressed_configs += [ "//build/config/compiler:compiler_arm_fpu" ]
|
||||
cflags = [ "-mfpu=neon" ]
|
||||
}
|
||||
|
||||
# Disable LTO on NEON targets due to compiler bug.
|
||||
# TODO(fdegans): Enable this. See crbug.com/408997.
|
||||
if (rtc_use_lto) {
|
||||
cflags -= [
|
||||
"-flto",
|
||||
"-ffat-lto-objects",
|
||||
]
|
||||
}
|
||||
|
||||
deps = [
|
||||
"../../common_audio",
|
||||
]
|
||||
public_deps = [
|
||||
":audio_processing_neon_c",
|
||||
]
|
||||
|
||||
if (apm_debug_dump) {
|
||||
defines = [ "WEBRTC_APM_DEBUG_DUMP=1" ]
|
||||
} else {
|
||||
defines = [ "WEBRTC_APM_DEBUG_DUMP=0" ]
|
||||
}
|
||||
}
|
||||
|
||||
rtc_static_library("audio_processing_neon_c") {
|
||||
# TODO(mbonadei): Remove (bugs.webrtc.org/6828)
|
||||
# Errors on cyclic dependency with :audio_processing_c if enabled.
|
||||
check_includes = false
|
||||
|
||||
sources = [
|
||||
"ns/nsx_core_neon.c",
|
||||
]
|
||||
|
||||
if (current_cpu != "arm64") {
|
||||
# Enable compilation for the NEON instruction set. This is needed
|
||||
# since //build/config/arm.gni only enables NEON for iOS, not Android.
|
||||
# This provides the same functionality as webrtc/build/arm_neon.gypi.
|
||||
suppressed_configs += [ "//build/config/compiler:compiler_arm_fpu" ]
|
||||
cflags = [ "-mfpu=neon" ]
|
||||
}
|
||||
|
||||
# Disable LTO on NEON targets due to compiler bug.
|
||||
# TODO(fdegans): Enable this. See crbug.com/408997.
|
||||
if (rtc_use_lto) {
|
||||
cflags -= [
|
||||
"-flto",
|
||||
"-ffat-lto-objects",
|
||||
]
|
||||
}
|
||||
deps = [
|
||||
"../../rtc_base:rtc_base_approved",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (rtc_include_tests) {
|
||||
group("audio_processing_tests") {
|
||||
testonly = true
|
||||
public_deps = [
|
||||
":audioproc_test_utils",
|
||||
":click_annotate",
|
||||
":nonlinear_beamformer_test",
|
||||
":transient_suppression_test",
|
||||
]
|
||||
|
||||
if (rtc_enable_intelligibility_enhancer) {
|
||||
public_deps += [ ":intelligibility_proc" ]
|
||||
}
|
||||
|
||||
if (rtc_enable_protobuf) {
|
||||
public_deps += [
|
||||
":audioproc_f",
|
||||
":audioproc_unittest_proto",
|
||||
":unpack_aecdump",
|
||||
"test/conversational_speech",
|
||||
"test/py_quality_assessment",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
rtc_source_set("audio_processing_unittests") {
|
||||
testonly = true
|
||||
|
||||
# Skip restricting visibility on mobile platforms since the tests on those
|
||||
# gets additional generated targets which would require many lines here to
|
||||
# cover (which would be confusing to read and hard to maintain).
|
||||
if (!is_android && !is_ios) {
|
||||
visibility = [ "..:modules_unittests" ]
|
||||
}
|
||||
sources = [
|
||||
"aec/echo_cancellation_unittest.cc",
|
||||
"aec/system_delay_unittest.cc",
|
||||
"agc/agc_manager_direct_unittest.cc",
|
||||
"agc/loudness_histogram_unittest.cc",
|
||||
"agc/mock_agc.h",
|
||||
"audio_buffer_unittest.cc",
|
||||
"beamformer/array_util_unittest.cc",
|
||||
"beamformer/complex_matrix_unittest.cc",
|
||||
"beamformer/covariance_matrix_generator_unittest.cc",
|
||||
"beamformer/matrix_unittest.cc",
|
||||
"beamformer/mock_nonlinear_beamformer.h",
|
||||
"config_unittest.cc",
|
||||
"echo_cancellation_impl_unittest.cc",
|
||||
"splitting_filter_unittest.cc",
|
||||
"transient/dyadic_decimator_unittest.cc",
|
||||
"transient/file_utils.cc",
|
||||
"transient/file_utils.h",
|
||||
"transient/file_utils_unittest.cc",
|
||||
"transient/moving_moments_unittest.cc",
|
||||
"transient/transient_detector_unittest.cc",
|
||||
"transient/transient_suppressor_unittest.cc",
|
||||
"transient/wpd_node_unittest.cc",
|
||||
"transient/wpd_tree_unittest.cc",
|
||||
"utility/block_mean_calculator_unittest.cc",
|
||||
"utility/delay_estimator_unittest.cc",
|
||||
"vad/gmm_unittest.cc",
|
||||
"vad/pitch_based_vad_unittest.cc",
|
||||
"vad/pitch_internal_unittest.cc",
|
||||
"vad/pole_zero_filter_unittest.cc",
|
||||
"vad/standalone_vad_unittest.cc",
|
||||
"vad/vad_audio_proc_unittest.cc",
|
||||
"vad/vad_circular_buffer_unittest.cc",
|
||||
"vad/voice_activity_detector_unittest.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":audio_processing",
|
||||
":audioproc_test_utils",
|
||||
"..:module_api",
|
||||
"../..:webrtc_common",
|
||||
"../../api:array_view",
|
||||
"../../api:optional",
|
||||
"../../common_audio:common_audio",
|
||||
"../../rtc_base:gtest_prod",
|
||||
"../../rtc_base:protobuf_utils",
|
||||
"../../rtc_base:rtc_base",
|
||||
"../../rtc_base:rtc_base_approved",
|
||||
"../../system_wrappers:system_wrappers",
|
||||
"../../test:test_support",
|
||||
"../audio_coding:neteq_tools",
|
||||
"aec_dump:mock_aec_dump_unittests",
|
||||
"test/conversational_speech:unittest",
|
||||
"//testing/gmock",
|
||||
"//testing/gtest",
|
||||
]
|
||||
|
||||
defines = []
|
||||
|
||||
if (apm_debug_dump) {
|
||||
defines += [ "WEBRTC_APM_DEBUG_DUMP=1" ]
|
||||
} else {
|
||||
defines += [ "WEBRTC_APM_DEBUG_DUMP=0" ]
|
||||
}
|
||||
|
||||
if (rtc_enable_intelligibility_enhancer) {
|
||||
defines += [ "WEBRTC_INTELLIGIBILITY_ENHANCER=1" ]
|
||||
sources += [
|
||||
"intelligibility/intelligibility_enhancer_unittest.cc",
|
||||
"intelligibility/intelligibility_utils_unittest.cc",
|
||||
]
|
||||
} else {
|
||||
defines += [ "WEBRTC_INTELLIGIBILITY_ENHANCER=0" ]
|
||||
}
|
||||
|
||||
if (rtc_prefer_fixed_point) {
|
||||
defines += [ "WEBRTC_AUDIOPROC_FIXED_PROFILE" ]
|
||||
} else {
|
||||
defines += [ "WEBRTC_AUDIOPROC_FLOAT_PROFILE" ]
|
||||
}
|
||||
|
||||
if (rtc_enable_protobuf) {
|
||||
defines += [ "WEBRTC_AUDIOPROC_DEBUG_DUMP" ]
|
||||
deps += [
|
||||
":audioproc_debug_proto",
|
||||
":audioproc_protobuf_utils",
|
||||
":audioproc_test_utils",
|
||||
":audioproc_unittest_proto",
|
||||
"../../rtc_base:rtc_task_queue",
|
||||
"aec_dump",
|
||||
"aec_dump:aec_dump_unittests",
|
||||
]
|
||||
sources += [
|
||||
"aec3/adaptive_fir_filter_unittest.cc",
|
||||
"aec3/aec3_fft_unittest.cc",
|
||||
"aec3/aec_state_unittest.cc",
|
||||
"aec3/block_framer_unittest.cc",
|
||||
"aec3/block_processor_metrics_unittest.cc",
|
||||
"aec3/block_processor_unittest.cc",
|
||||
"aec3/cascaded_biquad_filter_unittest.cc",
|
||||
"aec3/comfort_noise_generator_unittest.cc",
|
||||
"aec3/decimator_by_4_unittest.cc",
|
||||
"aec3/echo_canceller3_unittest.cc",
|
||||
"aec3/echo_path_delay_estimator_unittest.cc",
|
||||
"aec3/echo_path_variability_unittest.cc",
|
||||
"aec3/echo_remover_metrics_unittest.cc",
|
||||
"aec3/echo_remover_unittest.cc",
|
||||
"aec3/erl_estimator_unittest.cc",
|
||||
"aec3/erle_estimator_unittest.cc",
|
||||
"aec3/fft_data_unittest.cc",
|
||||
"aec3/frame_blocker_unittest.cc",
|
||||
"aec3/main_filter_update_gain_unittest.cc",
|
||||
"aec3/matched_filter_lag_aggregator_unittest.cc",
|
||||
"aec3/matched_filter_unittest.cc",
|
||||
"aec3/output_selector_unittest.cc",
|
||||
"aec3/render_buffer_unittest.cc",
|
||||
"aec3/render_delay_buffer_unittest.cc",
|
||||
"aec3/render_delay_controller_metrics_unittest.cc",
|
||||
"aec3/render_delay_controller_unittest.cc",
|
||||
"aec3/render_signal_analyzer_unittest.cc",
|
||||
"aec3/residual_echo_estimator_unittest.cc",
|
||||
"aec3/shadow_filter_update_gain_unittest.cc",
|
||||
"aec3/subtractor_unittest.cc",
|
||||
"aec3/suppression_filter_unittest.cc",
|
||||
"aec3/suppression_gain_unittest.cc",
|
||||
"aec3/vector_math_unittest.cc",
|
||||
"agc2/gain_controller2_unittest.cc",
|
||||
"audio_processing_impl_locking_unittest.cc",
|
||||
"audio_processing_impl_unittest.cc",
|
||||
"audio_processing_unittest.cc",
|
||||
"beamformer/nonlinear_beamformer_unittest.cc",
|
||||
"echo_cancellation_bit_exact_unittest.cc",
|
||||
"echo_control_mobile_unittest.cc",
|
||||
"echo_detector/circular_buffer_unittest.cc",
|
||||
"echo_detector/mean_variance_estimator_unittest.cc",
|
||||
"echo_detector/moving_max_unittest.cc",
|
||||
"echo_detector/normalized_covariance_estimator_unittest.cc",
|
||||
"gain_control_unittest.cc",
|
||||
"level_controller/level_controller_unittest.cc",
|
||||
"level_estimator_unittest.cc",
|
||||
"low_cut_filter_unittest.cc",
|
||||
"noise_suppression_unittest.cc",
|
||||
"residual_echo_detector_unittest.cc",
|
||||
"rms_level_unittest.cc",
|
||||
"test/debug_dump_replayer.cc",
|
||||
"test/debug_dump_replayer.h",
|
||||
"test/debug_dump_test.cc",
|
||||
"test/echo_canceller_test_tools.cc",
|
||||
"test/echo_canceller_test_tools.h",
|
||||
"test/echo_canceller_test_tools_unittest.cc",
|
||||
"test/test_utils.h",
|
||||
"voice_detection_unittest.cc",
|
||||
]
|
||||
}
|
||||
|
||||
if ((!build_with_chromium || is_win) && is_clang) {
|
||||
# Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163).
|
||||
suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
|
||||
}
|
||||
}
|
||||
|
||||
rtc_source_set("audio_processing_perf_tests") {
|
||||
# Has problems with autogenerated targets on Android and iOS
|
||||
# Dependency chain (there may also be others):
|
||||
# :audio_processing_perf_tests -->
|
||||
# ..:modules_unittests --[private]-->
|
||||
# ..:modules_unittests_apk -->
|
||||
# ..:modules_unittests_apk__create -->
|
||||
# ..:modules_unittests_apk__create__finalize -->
|
||||
# ..:modules_unittests_apk__create__package --[private]-->
|
||||
# ..:_modules_unittests__library
|
||||
check_includes = false
|
||||
testonly = true
|
||||
|
||||
# Skip restricting visibility on mobile platforms since the tests on those
|
||||
# gets additional generated targets which would require many lines here to
|
||||
# cover (which would be confusing to read and hard to maintain).
|
||||
if (!is_android && !is_ios) {
|
||||
visibility = [ "../..:webrtc_perf_tests" ]
|
||||
}
|
||||
sources = [
|
||||
"audio_processing_performance_unittest.cc",
|
||||
"level_controller/level_controller_complexity_unittest.cc",
|
||||
"residual_echo_detector_complexity_unittest.cc",
|
||||
]
|
||||
deps = [
|
||||
":audio_processing",
|
||||
":audioproc_test_utils",
|
||||
"../../rtc_base:protobuf_utils",
|
||||
"//testing/gtest",
|
||||
]
|
||||
|
||||
if (rtc_enable_intelligibility_enhancer) {
|
||||
defines = [ "WEBRTC_INTELLIGIBILITY_ENHANCER=1" ]
|
||||
} else {
|
||||
defines = [ "WEBRTC_INTELLIGIBILITY_ENHANCER=0" ]
|
||||
}
|
||||
}
|
||||
|
||||
if (rtc_enable_protobuf) {
|
||||
rtc_executable("unpack_aecdump") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"test/unpack.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":audio_processing",
|
||||
":audioproc_debug_proto",
|
||||
":audioproc_protobuf_utils",
|
||||
":audioproc_test_utils",
|
||||
"../..:webrtc_common",
|
||||
"../../common_audio",
|
||||
"../../rtc_base:protobuf_utils",
|
||||
"../../rtc_base:rtc_base_approved",
|
||||
"../../system_wrappers:system_wrappers_default",
|
||||
]
|
||||
} # unpack_aecdump
|
||||
|
||||
rtc_executable("audioproc_f") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"test/aec_dump_based_simulator.cc",
|
||||
"test/aec_dump_based_simulator.h",
|
||||
"test/audio_processing_simulator.cc",
|
||||
"test/audio_processing_simulator.h",
|
||||
"test/audioproc_float.cc",
|
||||
"test/wav_based_simulator.cc",
|
||||
"test/wav_based_simulator.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":audio_processing",
|
||||
":audioproc_debug_proto",
|
||||
":audioproc_protobuf_utils",
|
||||
":audioproc_test_utils",
|
||||
"../../api:optional",
|
||||
"../../common_audio:common_audio",
|
||||
"../../rtc_base:protobuf_utils",
|
||||
"../../rtc_base:rtc_base_approved",
|
||||
"../../rtc_base:rtc_task_queue",
|
||||
"../../system_wrappers",
|
||||
"../../system_wrappers:system_wrappers_default",
|
||||
"../../test:test_support",
|
||||
"aec_dump",
|
||||
"aec_dump:aec_dump_impl",
|
||||
"//testing/gtest",
|
||||
]
|
||||
} # audioproc_f
|
||||
}
|
||||
|
||||
rtc_source_set("audioproc_test_utils") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"test/audio_buffer_tools.cc",
|
||||
"test/audio_buffer_tools.h",
|
||||
"test/bitexactness_tools.cc",
|
||||
"test/bitexactness_tools.h",
|
||||
"test/performance_timer.cc",
|
||||
"test/performance_timer.h",
|
||||
"test/simulator_buffers.cc",
|
||||
"test/simulator_buffers.h",
|
||||
"test/test_utils.cc",
|
||||
"test/test_utils.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":audio_processing",
|
||||
"..:module_api",
|
||||
"../../api:array_view",
|
||||
"../../api:optional",
|
||||
"../../common_audio",
|
||||
"../../rtc_base:rtc_base_approved",
|
||||
"../../system_wrappers:system_wrappers",
|
||||
"../../test:test_support",
|
||||
"../audio_coding:neteq_tools",
|
||||
"//testing/gtest",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_executable("transient_suppression_test") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"transient/file_utils.cc",
|
||||
"transient/file_utils.h",
|
||||
"transient/transient_suppression_test.cc",
|
||||
]
|
||||
deps = [
|
||||
":audio_processing",
|
||||
"..:module_api",
|
||||
"../..:webrtc_common",
|
||||
"../../common_audio:common_audio",
|
||||
"../../rtc_base:rtc_base_approved",
|
||||
"../../system_wrappers:metrics_default",
|
||||
"../../system_wrappers:system_wrappers",
|
||||
"../../test:test_support",
|
||||
"//testing/gtest",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_executable("click_annotate") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"transient/click_annotate.cc",
|
||||
"transient/file_utils.cc",
|
||||
"transient/file_utils.h",
|
||||
]
|
||||
deps = [
|
||||
":audio_processing",
|
||||
"../..:webrtc_common",
|
||||
"../../system_wrappers:metrics_default",
|
||||
"../../system_wrappers:system_wrappers",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_executable("nonlinear_beamformer_test") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"beamformer/nonlinear_beamformer_test.cc",
|
||||
]
|
||||
deps = [
|
||||
":audio_processing",
|
||||
":audioproc_test_utils",
|
||||
"../../common_audio:common_audio",
|
||||
"../../rtc_base:rtc_base_approved",
|
||||
"../../system_wrappers:metrics_default",
|
||||
]
|
||||
}
|
||||
|
||||
if (rtc_enable_intelligibility_enhancer) {
|
||||
rtc_executable("intelligibility_proc") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"intelligibility/test/intelligibility_proc.cc",
|
||||
]
|
||||
deps = [
|
||||
":audio_processing",
|
||||
":audioproc_test_utils",
|
||||
"../../rtc_base:rtc_base_approved",
|
||||
"../../system_wrappers:metrics_default",
|
||||
"../../test:test_support",
|
||||
"//testing/gtest",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (rtc_enable_protobuf) {
|
||||
proto_library("audioproc_unittest_proto") {
|
||||
sources = [
|
||||
"test/unittest.proto",
|
||||
]
|
||||
proto_out_dir = "webrtc/modules/audio_processing/test"
|
||||
}
|
||||
|
||||
rtc_static_library("audioproc_protobuf_utils") {
|
||||
sources = [
|
||||
"test/protobuf_utils.cc",
|
||||
"test/protobuf_utils.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":audioproc_debug_proto",
|
||||
"../..:webrtc_common",
|
||||
"../../rtc_base:protobuf_utils",
|
||||
"../../rtc_base:rtc_base_approved",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
include_rules = [
|
||||
"+webrtc/audio/utility/audio_frame_operations.h",
|
||||
"+webrtc/common_audio",
|
||||
"+webrtc/system_wrappers",
|
||||
]
|
||||
|
||||
specific_include_rules = {
|
||||
".*test\.cc": [
|
||||
"+webrtc/rtc_tools",
|
||||
# Android platform build has different paths.
|
||||
"+gtest",
|
||||
"+external/webrtc",
|
||||
],
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
aleloi@webrtc.org
|
||||
aluebs@webrtc.org
|
||||
henrik.lundin@webrtc.org
|
||||
peah@webrtc.org
|
||||
|
||||
# These are for the common case of adding or renaming files. If you're doing
|
||||
# structural changes, please get a review from a reviewer in this file.
|
||||
per-file *.gn=*
|
||||
per-file *.gni=*
|
||||
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_COMMON_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_COMMON_H_
|
||||
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
#ifdef _MSC_VER /* visual c++ */
|
||||
#define ALIGN16_BEG __declspec(align(16))
|
||||
#define ALIGN16_END
|
||||
#else /* gcc or icc */
|
||||
#define ALIGN16_BEG
|
||||
#define ALIGN16_END __attribute__((aligned(16)))
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace webrtc {
|
||||
#endif
|
||||
|
||||
extern ALIGN16_BEG const float ALIGN16_END WebRtcAec_sqrtHanning[65];
|
||||
extern ALIGN16_BEG const float ALIGN16_END WebRtcAec_weightCurve[65];
|
||||
extern ALIGN16_BEG const float ALIGN16_END WebRtcAec_overDriveCurve[65];
|
||||
extern const float WebRtcAec_kExtendedSmoothingCoefficients[2][2];
|
||||
extern const float WebRtcAec_kNormalSmoothingCoefficients[2][2];
|
||||
extern const float WebRtcAec_kMinFarendPSD;
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // namespace webrtc
|
||||
#endif
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_COMMON_H_
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,335 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Specifies the interface for the AEC core.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_CORE_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_CORE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
extern "C" {
|
||||
#include "webrtc/common_audio/ring_buffer.h"
|
||||
}
|
||||
#include "webrtc/common_audio/wav_file.h"
|
||||
#include "webrtc/modules/audio_processing/aec/aec_common.h"
|
||||
#include "webrtc/modules/audio_processing/utility/block_mean_calculator.h"
|
||||
#include "webrtc/modules/audio_processing/utility/ooura_fft.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
#define FRAME_LEN 80
|
||||
#define PART_LEN 64 // Length of partition
|
||||
#define PART_LEN1 (PART_LEN + 1) // Unique fft coefficients
|
||||
#define PART_LEN2 (PART_LEN * 2) // Length of partition * 2
|
||||
#define NUM_HIGH_BANDS_MAX 2 // Max number of high bands
|
||||
|
||||
class ApmDataDumper;
|
||||
|
||||
typedef float complex_t[2];
|
||||
// For performance reasons, some arrays of complex numbers are replaced by twice
|
||||
// as long arrays of float, all the real parts followed by all the imaginary
|
||||
// ones (complex_t[SIZE] -> float[2][SIZE]). This allows SIMD optimizations and
|
||||
// is better than two arrays (one for the real parts and one for the imaginary
|
||||
// parts) as this other way would require two pointers instead of one and cause
|
||||
// extra register spilling. This also allows the offsets to be calculated at
|
||||
// compile time.
|
||||
|
||||
// Metrics
|
||||
enum { kOffsetLevel = -100 };
|
||||
|
||||
typedef struct Stats {
|
||||
float instant;
|
||||
float average;
|
||||
float min;
|
||||
float max;
|
||||
float sum;
|
||||
float hisum;
|
||||
float himean;
|
||||
size_t counter;
|
||||
size_t hicounter;
|
||||
} Stats;
|
||||
|
||||
// Number of partitions for the extended filter mode. The first one is an enum
|
||||
// to be used in array declarations, as it represents the maximum filter length.
|
||||
enum { kExtendedNumPartitions = 32 };
|
||||
static const int kNormalNumPartitions = 12;
|
||||
|
||||
// Delay estimator constants, used for logging and delay compensation if
|
||||
// if reported delays are disabled.
|
||||
enum { kLookaheadBlocks = 15 };
|
||||
enum {
|
||||
// 500 ms for 16 kHz which is equivalent with the limit of reported delays.
|
||||
kHistorySizeBlocks = 125
|
||||
};
|
||||
|
||||
typedef struct PowerLevel {
|
||||
PowerLevel();
|
||||
|
||||
BlockMeanCalculator framelevel;
|
||||
BlockMeanCalculator averagelevel;
|
||||
float minlevel;
|
||||
} PowerLevel;
|
||||
|
||||
class BlockBuffer {
|
||||
public:
|
||||
BlockBuffer();
|
||||
~BlockBuffer();
|
||||
void ReInit();
|
||||
void Insert(const float block[PART_LEN]);
|
||||
void ExtractExtendedBlock(float extended_block[PART_LEN]);
|
||||
int AdjustSize(int buffer_size_decrease);
|
||||
size_t Size();
|
||||
size_t AvaliableSpace();
|
||||
|
||||
private:
|
||||
RingBuffer* buffer_;
|
||||
};
|
||||
|
||||
class DivergentFilterFraction {
|
||||
public:
|
||||
DivergentFilterFraction();
|
||||
|
||||
// Reset.
|
||||
void Reset();
|
||||
|
||||
void AddObservation(const PowerLevel& nearlevel,
|
||||
const PowerLevel& linoutlevel,
|
||||
const PowerLevel& nlpoutlevel);
|
||||
|
||||
// Return the latest fraction.
|
||||
float GetLatestFraction() const;
|
||||
|
||||
private:
|
||||
// Clear all values added.
|
||||
void Clear();
|
||||
|
||||
size_t count_;
|
||||
size_t occurrence_;
|
||||
float fraction_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(DivergentFilterFraction);
|
||||
};
|
||||
|
||||
typedef struct CoherenceState {
|
||||
complex_t sde[PART_LEN1]; // cross-psd of nearend and error
|
||||
complex_t sxd[PART_LEN1]; // cross-psd of farend and nearend
|
||||
float sx[PART_LEN1], sd[PART_LEN1], se[PART_LEN1]; // far, near, error psd
|
||||
} CoherenceState;
|
||||
|
||||
struct AecCore {
|
||||
explicit AecCore(int instance_index);
|
||||
~AecCore();
|
||||
|
||||
std::unique_ptr<ApmDataDumper> data_dumper;
|
||||
const OouraFft ooura_fft;
|
||||
|
||||
CoherenceState coherence_state;
|
||||
|
||||
int farBufWritePos, farBufReadPos;
|
||||
|
||||
int knownDelay;
|
||||
int inSamples, outSamples;
|
||||
int delayEstCtr;
|
||||
|
||||
// Nearend buffer used for changing from FRAME_LEN to PART_LEN sample block
|
||||
// sizes. The buffer stores all the incoming bands and for each band a maximum
|
||||
// of PART_LEN - (FRAME_LEN - PART_LEN) values need to be buffered in order to
|
||||
// change the block size from FRAME_LEN to PART_LEN.
|
||||
float nearend_buffer[NUM_HIGH_BANDS_MAX + 1]
|
||||
[PART_LEN - (FRAME_LEN - PART_LEN)];
|
||||
size_t nearend_buffer_size;
|
||||
float output_buffer[NUM_HIGH_BANDS_MAX + 1][2 * PART_LEN];
|
||||
size_t output_buffer_size;
|
||||
|
||||
float eBuf[PART_LEN2]; // error
|
||||
|
||||
float previous_nearend_block[NUM_HIGH_BANDS_MAX + 1][PART_LEN];
|
||||
|
||||
float xPow[PART_LEN1];
|
||||
float dPow[PART_LEN1];
|
||||
float dMinPow[PART_LEN1];
|
||||
float dInitMinPow[PART_LEN1];
|
||||
float* noisePow;
|
||||
|
||||
float xfBuf[2][kExtendedNumPartitions * PART_LEN1]; // farend fft buffer
|
||||
float wfBuf[2][kExtendedNumPartitions * PART_LEN1]; // filter fft
|
||||
// Farend windowed fft buffer.
|
||||
complex_t xfwBuf[kExtendedNumPartitions * PART_LEN1];
|
||||
|
||||
float hNs[PART_LEN1];
|
||||
float hNlFbMin, hNlFbLocalMin;
|
||||
float hNlXdAvgMin;
|
||||
int hNlNewMin, hNlMinCtr;
|
||||
float overDrive;
|
||||
float overdrive_scaling;
|
||||
int nlp_mode;
|
||||
float outBuf[PART_LEN];
|
||||
int delayIdx;
|
||||
|
||||
short stNearState, echoState;
|
||||
short divergeState;
|
||||
|
||||
int xfBufBlockPos;
|
||||
|
||||
BlockBuffer farend_block_buffer_;
|
||||
|
||||
int system_delay; // Current system delay buffered in AEC.
|
||||
|
||||
int mult; // sampling frequency multiple
|
||||
int sampFreq = 16000;
|
||||
size_t num_bands;
|
||||
uint32_t seed;
|
||||
|
||||
float filter_step_size; // stepsize
|
||||
float error_threshold; // error threshold
|
||||
|
||||
int noiseEstCtr;
|
||||
|
||||
PowerLevel farlevel;
|
||||
PowerLevel nearlevel;
|
||||
PowerLevel linoutlevel;
|
||||
PowerLevel nlpoutlevel;
|
||||
|
||||
int metricsMode;
|
||||
int stateCounter;
|
||||
Stats erl;
|
||||
Stats erle;
|
||||
Stats aNlp;
|
||||
Stats rerl;
|
||||
DivergentFilterFraction divergent_filter_fraction;
|
||||
|
||||
// Quantities to control H band scaling for SWB input
|
||||
int freq_avg_ic; // initial bin for averaging nlp gain
|
||||
int flag_Hband_cn; // for comfort noise
|
||||
float cn_scale_Hband; // scale for comfort noise in H band
|
||||
|
||||
int delay_metrics_delivered;
|
||||
int delay_histogram[kHistorySizeBlocks];
|
||||
int num_delay_values;
|
||||
int delay_median;
|
||||
int delay_std;
|
||||
float fraction_poor_delays;
|
||||
int delay_logging_enabled;
|
||||
void* delay_estimator_farend;
|
||||
void* delay_estimator;
|
||||
// Variables associated with delay correction through signal based delay
|
||||
// estimation feedback.
|
||||
int previous_delay;
|
||||
int delay_correction_count;
|
||||
int shift_offset;
|
||||
float delay_quality_threshold;
|
||||
int frame_count;
|
||||
|
||||
// 0 = delay agnostic mode (signal based delay correction) disabled.
|
||||
// Otherwise enabled.
|
||||
int delay_agnostic_enabled;
|
||||
// 1 = extended filter mode enabled, 0 = disabled.
|
||||
int extended_filter_enabled;
|
||||
// 1 = refined filter adaptation aec mode enabled, 0 = disabled.
|
||||
bool refined_adaptive_filter_enabled;
|
||||
|
||||
// Runtime selection of number of filter partitions.
|
||||
int num_partitions;
|
||||
|
||||
// Flag that extreme filter divergence has been detected by the Echo
|
||||
// Suppressor.
|
||||
int extreme_filter_divergence;
|
||||
};
|
||||
|
||||
AecCore* WebRtcAec_CreateAec(int instance_count); // Returns NULL on error.
|
||||
void WebRtcAec_FreeAec(AecCore* aec);
|
||||
int WebRtcAec_InitAec(AecCore* aec, int sampFreq);
|
||||
void WebRtcAec_InitAec_SSE2(void);
|
||||
#if defined(MIPS_FPU_LE)
|
||||
void WebRtcAec_InitAec_mips(void);
|
||||
#endif
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
void WebRtcAec_InitAec_neon(void);
|
||||
#endif
|
||||
|
||||
void WebRtcAec_BufferFarendBlock(AecCore* aec, const float* farend);
|
||||
void WebRtcAec_ProcessFrames(AecCore* aec,
|
||||
const float* const* nearend,
|
||||
size_t num_bands,
|
||||
size_t num_samples,
|
||||
int knownDelay,
|
||||
float* const* out);
|
||||
|
||||
// A helper function to call adjust the farend buffer size.
|
||||
// Returns the number of elements the size was decreased with, and adjusts
|
||||
// |system_delay| by the corresponding amount in ms.
|
||||
int WebRtcAec_AdjustFarendBufferSizeAndSystemDelay(AecCore* aec,
|
||||
int size_decrease);
|
||||
|
||||
// Calculates the median, standard deviation and amount of poor values among the
|
||||
// delay estimates aggregated up to the first call to the function. After that
|
||||
// first call the metrics are aggregated and updated every second. With poor
|
||||
// values we mean values that most likely will cause the AEC to perform poorly.
|
||||
// TODO(bjornv): Consider changing tests and tools to handle constant
|
||||
// constant aggregation window throughout the session instead.
|
||||
int WebRtcAec_GetDelayMetricsCore(AecCore* self,
|
||||
int* median,
|
||||
int* std,
|
||||
float* fraction_poor_delays);
|
||||
|
||||
// Returns the echo state (1: echo, 0: no echo).
|
||||
int WebRtcAec_echo_state(AecCore* self);
|
||||
|
||||
// Gets statistics of the echo metrics ERL, ERLE, A_NLP.
|
||||
void WebRtcAec_GetEchoStats(AecCore* self,
|
||||
Stats* erl,
|
||||
Stats* erle,
|
||||
Stats* a_nlp,
|
||||
float* divergent_filter_fraction);
|
||||
|
||||
// Sets local configuration modes.
|
||||
void WebRtcAec_SetConfigCore(AecCore* self,
|
||||
int nlp_mode,
|
||||
int metrics_mode,
|
||||
int delay_logging);
|
||||
|
||||
// Non-zero enables, zero disables.
|
||||
void WebRtcAec_enable_delay_agnostic(AecCore* self, int enable);
|
||||
|
||||
// Returns non-zero if delay agnostic (i.e., signal based delay estimation) is
|
||||
// enabled and zero if disabled.
|
||||
int WebRtcAec_delay_agnostic_enabled(AecCore* self);
|
||||
|
||||
// Turns on/off the refined adaptive filter feature.
|
||||
void WebRtcAec_enable_refined_adaptive_filter(AecCore* self, bool enable);
|
||||
|
||||
// Returns whether the refined adaptive filter is enabled.
|
||||
bool WebRtcAec_refined_adaptive_filter(const AecCore* self);
|
||||
|
||||
// Enables or disables extended filter mode. Non-zero enables, zero disables.
|
||||
void WebRtcAec_enable_extended_filter(AecCore* self, int enable);
|
||||
|
||||
// Returns non-zero if extended filter mode is enabled and zero if disabled.
|
||||
int WebRtcAec_extended_filter_enabled(AecCore* self);
|
||||
|
||||
// Returns the current |system_delay|, i.e., the buffered difference between
|
||||
// far-end and near-end.
|
||||
int WebRtcAec_system_delay(AecCore* self);
|
||||
|
||||
// Sets the |system_delay| to |value|. Note that if the value is changed
|
||||
// improperly, there can be a performance regression. So it should be used with
|
||||
// care.
|
||||
void WebRtcAec_SetSystemDelay(AecCore* self, int delay);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_CORE_H_
|
||||
@ -1,490 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The core AEC algorithm, which is presented with time-aligned signals.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec/aec_core.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
extern "C" {
|
||||
#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h"
|
||||
}
|
||||
#include "webrtc/modules/audio_processing/aec/aec_core_optimized_methods.h"
|
||||
#include "webrtc/modules/audio_processing/utility/ooura_fft.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
extern const float WebRtcAec_weightCurve[65];
|
||||
extern const float WebRtcAec_overDriveCurve[65];
|
||||
|
||||
void WebRtcAec_FilterFar_mips(
|
||||
int num_partitions,
|
||||
int x_fft_buf_block_pos,
|
||||
float x_fft_buf[2][kExtendedNumPartitions * PART_LEN1],
|
||||
float h_fft_buf[2][kExtendedNumPartitions * PART_LEN1],
|
||||
float y_fft[2][PART_LEN1]) {
|
||||
int i;
|
||||
for (i = 0; i < num_partitions; i++) {
|
||||
int xPos = (i + x_fft_buf_block_pos) * PART_LEN1;
|
||||
int pos = i * PART_LEN1;
|
||||
// Check for wrap
|
||||
if (i + x_fft_buf_block_pos >= num_partitions) {
|
||||
xPos -= num_partitions * (PART_LEN1);
|
||||
}
|
||||
float* yf0 = y_fft[0];
|
||||
float* yf1 = y_fft[1];
|
||||
float* aRe = x_fft_buf[0] + xPos;
|
||||
float* aIm = x_fft_buf[1] + xPos;
|
||||
float* bRe = h_fft_buf[0] + pos;
|
||||
float* bIm = h_fft_buf[1] + pos;
|
||||
float f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13;
|
||||
int len = PART_LEN1 >> 1;
|
||||
|
||||
__asm __volatile(
|
||||
".set push \n\t"
|
||||
".set noreorder \n\t"
|
||||
"1: \n\t"
|
||||
"lwc1 %[f0], 0(%[aRe]) \n\t"
|
||||
"lwc1 %[f1], 0(%[bRe]) \n\t"
|
||||
"lwc1 %[f2], 0(%[bIm]) \n\t"
|
||||
"lwc1 %[f3], 0(%[aIm]) \n\t"
|
||||
"lwc1 %[f4], 4(%[aRe]) \n\t"
|
||||
"lwc1 %[f5], 4(%[bRe]) \n\t"
|
||||
"lwc1 %[f6], 4(%[bIm]) \n\t"
|
||||
"mul.s %[f8], %[f0], %[f1] \n\t"
|
||||
"mul.s %[f0], %[f0], %[f2] \n\t"
|
||||
"mul.s %[f9], %[f4], %[f5] \n\t"
|
||||
"mul.s %[f4], %[f4], %[f6] \n\t"
|
||||
"lwc1 %[f7], 4(%[aIm]) \n\t"
|
||||
#if !defined(MIPS32_R2_LE)
|
||||
"mul.s %[f12], %[f2], %[f3] \n\t"
|
||||
"mul.s %[f1], %[f3], %[f1] \n\t"
|
||||
"mul.s %[f11], %[f6], %[f7] \n\t"
|
||||
"addiu %[aRe], %[aRe], 8 \n\t"
|
||||
"addiu %[aIm], %[aIm], 8 \n\t"
|
||||
"addiu %[len], %[len], -1 \n\t"
|
||||
"sub.s %[f8], %[f8], %[f12] \n\t"
|
||||
"mul.s %[f12], %[f7], %[f5] \n\t"
|
||||
"lwc1 %[f2], 0(%[yf0]) \n\t"
|
||||
"add.s %[f1], %[f0], %[f1] \n\t"
|
||||
"lwc1 %[f3], 0(%[yf1]) \n\t"
|
||||
"sub.s %[f9], %[f9], %[f11] \n\t"
|
||||
"lwc1 %[f6], 4(%[yf0]) \n\t"
|
||||
"add.s %[f4], %[f4], %[f12] \n\t"
|
||||
#else // #if !defined(MIPS32_R2_LE)
|
||||
"addiu %[aRe], %[aRe], 8 \n\t"
|
||||
"addiu %[aIm], %[aIm], 8 \n\t"
|
||||
"addiu %[len], %[len], -1 \n\t"
|
||||
"nmsub.s %[f8], %[f8], %[f2], %[f3] \n\t"
|
||||
"lwc1 %[f2], 0(%[yf0]) \n\t"
|
||||
"madd.s %[f1], %[f0], %[f3], %[f1] \n\t"
|
||||
"lwc1 %[f3], 0(%[yf1]) \n\t"
|
||||
"nmsub.s %[f9], %[f9], %[f6], %[f7] \n\t"
|
||||
"lwc1 %[f6], 4(%[yf0]) \n\t"
|
||||
"madd.s %[f4], %[f4], %[f7], %[f5] \n\t"
|
||||
#endif // #if !defined(MIPS32_R2_LE)
|
||||
"lwc1 %[f5], 4(%[yf1]) \n\t"
|
||||
"add.s %[f2], %[f2], %[f8] \n\t"
|
||||
"addiu %[bRe], %[bRe], 8 \n\t"
|
||||
"addiu %[bIm], %[bIm], 8 \n\t"
|
||||
"add.s %[f3], %[f3], %[f1] \n\t"
|
||||
"add.s %[f6], %[f6], %[f9] \n\t"
|
||||
"add.s %[f5], %[f5], %[f4] \n\t"
|
||||
"swc1 %[f2], 0(%[yf0]) \n\t"
|
||||
"swc1 %[f3], 0(%[yf1]) \n\t"
|
||||
"swc1 %[f6], 4(%[yf0]) \n\t"
|
||||
"swc1 %[f5], 4(%[yf1]) \n\t"
|
||||
"addiu %[yf0], %[yf0], 8 \n\t"
|
||||
"bgtz %[len], 1b \n\t"
|
||||
" addiu %[yf1], %[yf1], 8 \n\t"
|
||||
"lwc1 %[f0], 0(%[aRe]) \n\t"
|
||||
"lwc1 %[f1], 0(%[bRe]) \n\t"
|
||||
"lwc1 %[f2], 0(%[bIm]) \n\t"
|
||||
"lwc1 %[f3], 0(%[aIm]) \n\t"
|
||||
"mul.s %[f8], %[f0], %[f1] \n\t"
|
||||
"mul.s %[f0], %[f0], %[f2] \n\t"
|
||||
#if !defined(MIPS32_R2_LE)
|
||||
"mul.s %[f12], %[f2], %[f3] \n\t"
|
||||
"mul.s %[f1], %[f3], %[f1] \n\t"
|
||||
"sub.s %[f8], %[f8], %[f12] \n\t"
|
||||
"lwc1 %[f2], 0(%[yf0]) \n\t"
|
||||
"add.s %[f1], %[f0], %[f1] \n\t"
|
||||
"lwc1 %[f3], 0(%[yf1]) \n\t"
|
||||
#else // #if !defined(MIPS32_R2_LE)
|
||||
"nmsub.s %[f8], %[f8], %[f2], %[f3] \n\t"
|
||||
"lwc1 %[f2], 0(%[yf0]) \n\t"
|
||||
"madd.s %[f1], %[f0], %[f3], %[f1] \n\t"
|
||||
"lwc1 %[f3], 0(%[yf1]) \n\t"
|
||||
#endif // #if !defined(MIPS32_R2_LE)
|
||||
"add.s %[f2], %[f2], %[f8] \n\t"
|
||||
"add.s %[f3], %[f3], %[f1] \n\t"
|
||||
"swc1 %[f2], 0(%[yf0]) \n\t"
|
||||
"swc1 %[f3], 0(%[yf1]) \n\t"
|
||||
".set pop \n\t"
|
||||
: [f0] "=&f" (f0), [f1] "=&f" (f1), [f2] "=&f" (f2),
|
||||
[f3] "=&f" (f3), [f4] "=&f" (f4), [f5] "=&f" (f5),
|
||||
[f6] "=&f" (f6), [f7] "=&f" (f7), [f8] "=&f" (f8),
|
||||
[f9] "=&f" (f9), [f10] "=&f" (f10), [f11] "=&f" (f11),
|
||||
[f12] "=&f" (f12), [f13] "=&f" (f13), [aRe] "+r" (aRe),
|
||||
[aIm] "+r" (aIm), [bRe] "+r" (bRe), [bIm] "+r" (bIm),
|
||||
[yf0] "+r" (yf0), [yf1] "+r" (yf1), [len] "+r" (len)
|
||||
:
|
||||
: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcAec_FilterAdaptation_mips(
|
||||
const OouraFft& ooura_fft,
|
||||
int num_partitions,
|
||||
int x_fft_buf_block_pos,
|
||||
float x_fft_buf[2][kExtendedNumPartitions * PART_LEN1],
|
||||
float e_fft[2][PART_LEN1],
|
||||
float h_fft_buf[2][kExtendedNumPartitions * PART_LEN1]) {
|
||||
float fft[PART_LEN2];
|
||||
int i;
|
||||
for (i = 0; i < num_partitions; i++) {
|
||||
int xPos = (i + x_fft_buf_block_pos) * (PART_LEN1);
|
||||
int pos;
|
||||
// Check for wrap
|
||||
if (i + x_fft_buf_block_pos >= num_partitions) {
|
||||
xPos -= num_partitions * PART_LEN1;
|
||||
}
|
||||
|
||||
pos = i * PART_LEN1;
|
||||
float* aRe = x_fft_buf[0] + xPos;
|
||||
float* aIm = x_fft_buf[1] + xPos;
|
||||
float* bRe = e_fft[0];
|
||||
float* bIm = e_fft[1];
|
||||
float* fft_tmp;
|
||||
|
||||
float f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12;
|
||||
int len = PART_LEN >> 1;
|
||||
|
||||
__asm __volatile(
|
||||
".set push \n\t"
|
||||
".set noreorder \n\t"
|
||||
"addiu %[fft_tmp], %[fft], 0 \n\t"
|
||||
"1: \n\t"
|
||||
"lwc1 %[f0], 0(%[aRe]) \n\t"
|
||||
"lwc1 %[f1], 0(%[bRe]) \n\t"
|
||||
"lwc1 %[f2], 0(%[bIm]) \n\t"
|
||||
"lwc1 %[f4], 4(%[aRe]) \n\t"
|
||||
"lwc1 %[f5], 4(%[bRe]) \n\t"
|
||||
"lwc1 %[f6], 4(%[bIm]) \n\t"
|
||||
"addiu %[aRe], %[aRe], 8 \n\t"
|
||||
"addiu %[bRe], %[bRe], 8 \n\t"
|
||||
"mul.s %[f8], %[f0], %[f1] \n\t"
|
||||
"mul.s %[f0], %[f0], %[f2] \n\t"
|
||||
"lwc1 %[f3], 0(%[aIm]) \n\t"
|
||||
"mul.s %[f9], %[f4], %[f5] \n\t"
|
||||
"lwc1 %[f7], 4(%[aIm]) \n\t"
|
||||
"mul.s %[f4], %[f4], %[f6] \n\t"
|
||||
#if !defined(MIPS32_R2_LE)
|
||||
"mul.s %[f10], %[f3], %[f2] \n\t"
|
||||
"mul.s %[f1], %[f3], %[f1] \n\t"
|
||||
"mul.s %[f11], %[f7], %[f6] \n\t"
|
||||
"mul.s %[f5], %[f7], %[f5] \n\t"
|
||||
"addiu %[aIm], %[aIm], 8 \n\t"
|
||||
"addiu %[bIm], %[bIm], 8 \n\t"
|
||||
"addiu %[len], %[len], -1 \n\t"
|
||||
"add.s %[f8], %[f8], %[f10] \n\t"
|
||||
"sub.s %[f1], %[f0], %[f1] \n\t"
|
||||
"add.s %[f9], %[f9], %[f11] \n\t"
|
||||
"sub.s %[f5], %[f4], %[f5] \n\t"
|
||||
#else // #if !defined(MIPS32_R2_LE)
|
||||
"addiu %[aIm], %[aIm], 8 \n\t"
|
||||
"addiu %[bIm], %[bIm], 8 \n\t"
|
||||
"addiu %[len], %[len], -1 \n\t"
|
||||
"madd.s %[f8], %[f8], %[f3], %[f2] \n\t"
|
||||
"nmsub.s %[f1], %[f0], %[f3], %[f1] \n\t"
|
||||
"madd.s %[f9], %[f9], %[f7], %[f6] \n\t"
|
||||
"nmsub.s %[f5], %[f4], %[f7], %[f5] \n\t"
|
||||
#endif // #if !defined(MIPS32_R2_LE)
|
||||
"swc1 %[f8], 0(%[fft_tmp]) \n\t"
|
||||
"swc1 %[f1], 4(%[fft_tmp]) \n\t"
|
||||
"swc1 %[f9], 8(%[fft_tmp]) \n\t"
|
||||
"swc1 %[f5], 12(%[fft_tmp]) \n\t"
|
||||
"bgtz %[len], 1b \n\t"
|
||||
" addiu %[fft_tmp], %[fft_tmp], 16 \n\t"
|
||||
"lwc1 %[f0], 0(%[aRe]) \n\t"
|
||||
"lwc1 %[f1], 0(%[bRe]) \n\t"
|
||||
"lwc1 %[f2], 0(%[bIm]) \n\t"
|
||||
"lwc1 %[f3], 0(%[aIm]) \n\t"
|
||||
"mul.s %[f8], %[f0], %[f1] \n\t"
|
||||
#if !defined(MIPS32_R2_LE)
|
||||
"mul.s %[f10], %[f3], %[f2] \n\t"
|
||||
"add.s %[f8], %[f8], %[f10] \n\t"
|
||||
#else // #if !defined(MIPS32_R2_LE)
|
||||
"madd.s %[f8], %[f8], %[f3], %[f2] \n\t"
|
||||
#endif // #if !defined(MIPS32_R2_LE)
|
||||
"swc1 %[f8], 4(%[fft]) \n\t"
|
||||
".set pop \n\t"
|
||||
: [f0] "=&f" (f0), [f1] "=&f" (f1), [f2] "=&f" (f2),
|
||||
[f3] "=&f" (f3), [f4] "=&f" (f4), [f5] "=&f" (f5),
|
||||
[f6] "=&f" (f6), [f7] "=&f" (f7), [f8] "=&f" (f8),
|
||||
[f9] "=&f" (f9), [f10] "=&f" (f10), [f11] "=&f" (f11),
|
||||
[f12] "=&f" (f12), [aRe] "+r" (aRe), [aIm] "+r" (aIm),
|
||||
[bRe] "+r" (bRe), [bIm] "+r" (bIm), [fft_tmp] "=&r" (fft_tmp),
|
||||
[len] "+r" (len)
|
||||
: [fft] "r" (fft)
|
||||
: "memory");
|
||||
|
||||
ooura_fft.InverseFft(fft);
|
||||
memset(fft + PART_LEN, 0, sizeof(float) * PART_LEN);
|
||||
|
||||
// fft scaling
|
||||
{
|
||||
float scale = 2.0f / PART_LEN2;
|
||||
__asm __volatile(
|
||||
".set push \n\t"
|
||||
".set noreorder \n\t"
|
||||
"addiu %[fft_tmp], %[fft], 0 \n\t"
|
||||
"addiu %[len], $zero, 8 \n\t"
|
||||
"1: \n\t"
|
||||
"addiu %[len], %[len], -1 \n\t"
|
||||
"lwc1 %[f0], 0(%[fft_tmp]) \n\t"
|
||||
"lwc1 %[f1], 4(%[fft_tmp]) \n\t"
|
||||
"lwc1 %[f2], 8(%[fft_tmp]) \n\t"
|
||||
"lwc1 %[f3], 12(%[fft_tmp]) \n\t"
|
||||
"mul.s %[f0], %[f0], %[scale] \n\t"
|
||||
"mul.s %[f1], %[f1], %[scale] \n\t"
|
||||
"mul.s %[f2], %[f2], %[scale] \n\t"
|
||||
"mul.s %[f3], %[f3], %[scale] \n\t"
|
||||
"lwc1 %[f4], 16(%[fft_tmp]) \n\t"
|
||||
"lwc1 %[f5], 20(%[fft_tmp]) \n\t"
|
||||
"lwc1 %[f6], 24(%[fft_tmp]) \n\t"
|
||||
"lwc1 %[f7], 28(%[fft_tmp]) \n\t"
|
||||
"mul.s %[f4], %[f4], %[scale] \n\t"
|
||||
"mul.s %[f5], %[f5], %[scale] \n\t"
|
||||
"mul.s %[f6], %[f6], %[scale] \n\t"
|
||||
"mul.s %[f7], %[f7], %[scale] \n\t"
|
||||
"swc1 %[f0], 0(%[fft_tmp]) \n\t"
|
||||
"swc1 %[f1], 4(%[fft_tmp]) \n\t"
|
||||
"swc1 %[f2], 8(%[fft_tmp]) \n\t"
|
||||
"swc1 %[f3], 12(%[fft_tmp]) \n\t"
|
||||
"swc1 %[f4], 16(%[fft_tmp]) \n\t"
|
||||
"swc1 %[f5], 20(%[fft_tmp]) \n\t"
|
||||
"swc1 %[f6], 24(%[fft_tmp]) \n\t"
|
||||
"swc1 %[f7], 28(%[fft_tmp]) \n\t"
|
||||
"bgtz %[len], 1b \n\t"
|
||||
" addiu %[fft_tmp], %[fft_tmp], 32 \n\t"
|
||||
".set pop \n\t"
|
||||
: [f0] "=&f" (f0), [f1] "=&f" (f1), [f2] "=&f" (f2),
|
||||
[f3] "=&f" (f3), [f4] "=&f" (f4), [f5] "=&f" (f5),
|
||||
[f6] "=&f" (f6), [f7] "=&f" (f7), [len] "=&r" (len),
|
||||
[fft_tmp] "=&r" (fft_tmp)
|
||||
: [scale] "f" (scale), [fft] "r" (fft)
|
||||
: "memory");
|
||||
}
|
||||
ooura_fft.Fft(fft);
|
||||
aRe = h_fft_buf[0] + pos;
|
||||
aIm = h_fft_buf[1] + pos;
|
||||
__asm __volatile(
|
||||
".set push \n\t"
|
||||
".set noreorder \n\t"
|
||||
"addiu %[fft_tmp], %[fft], 0 \n\t"
|
||||
"addiu %[len], $zero, 31 \n\t"
|
||||
"lwc1 %[f0], 0(%[aRe]) \n\t"
|
||||
"lwc1 %[f1], 0(%[fft_tmp]) \n\t"
|
||||
"lwc1 %[f2], 256(%[aRe]) \n\t"
|
||||
"lwc1 %[f3], 4(%[fft_tmp]) \n\t"
|
||||
"lwc1 %[f4], 4(%[aRe]) \n\t"
|
||||
"lwc1 %[f5], 8(%[fft_tmp]) \n\t"
|
||||
"lwc1 %[f6], 4(%[aIm]) \n\t"
|
||||
"lwc1 %[f7], 12(%[fft_tmp]) \n\t"
|
||||
"add.s %[f0], %[f0], %[f1] \n\t"
|
||||
"add.s %[f2], %[f2], %[f3] \n\t"
|
||||
"add.s %[f4], %[f4], %[f5] \n\t"
|
||||
"add.s %[f6], %[f6], %[f7] \n\t"
|
||||
"addiu %[fft_tmp], %[fft_tmp], 16 \n\t"
|
||||
"swc1 %[f0], 0(%[aRe]) \n\t"
|
||||
"swc1 %[f2], 256(%[aRe]) \n\t"
|
||||
"swc1 %[f4], 4(%[aRe]) \n\t"
|
||||
"addiu %[aRe], %[aRe], 8 \n\t"
|
||||
"swc1 %[f6], 4(%[aIm]) \n\t"
|
||||
"addiu %[aIm], %[aIm], 8 \n\t"
|
||||
"1: \n\t"
|
||||
"lwc1 %[f0], 0(%[aRe]) \n\t"
|
||||
"lwc1 %[f1], 0(%[fft_tmp]) \n\t"
|
||||
"lwc1 %[f2], 0(%[aIm]) \n\t"
|
||||
"lwc1 %[f3], 4(%[fft_tmp]) \n\t"
|
||||
"lwc1 %[f4], 4(%[aRe]) \n\t"
|
||||
"lwc1 %[f5], 8(%[fft_tmp]) \n\t"
|
||||
"lwc1 %[f6], 4(%[aIm]) \n\t"
|
||||
"lwc1 %[f7], 12(%[fft_tmp]) \n\t"
|
||||
"add.s %[f0], %[f0], %[f1] \n\t"
|
||||
"add.s %[f2], %[f2], %[f3] \n\t"
|
||||
"add.s %[f4], %[f4], %[f5] \n\t"
|
||||
"add.s %[f6], %[f6], %[f7] \n\t"
|
||||
"addiu %[len], %[len], -1 \n\t"
|
||||
"addiu %[fft_tmp], %[fft_tmp], 16 \n\t"
|
||||
"swc1 %[f0], 0(%[aRe]) \n\t"
|
||||
"swc1 %[f2], 0(%[aIm]) \n\t"
|
||||
"swc1 %[f4], 4(%[aRe]) \n\t"
|
||||
"addiu %[aRe], %[aRe], 8 \n\t"
|
||||
"swc1 %[f6], 4(%[aIm]) \n\t"
|
||||
"bgtz %[len], 1b \n\t"
|
||||
" addiu %[aIm], %[aIm], 8 \n\t"
|
||||
".set pop \n\t"
|
||||
: [f0] "=&f" (f0), [f1] "=&f" (f1), [f2] "=&f" (f2),
|
||||
[f3] "=&f" (f3), [f4] "=&f" (f4), [f5] "=&f" (f5),
|
||||
[f6] "=&f" (f6), [f7] "=&f" (f7), [len] "=&r" (len),
|
||||
[fft_tmp] "=&r" (fft_tmp), [aRe] "+r" (aRe), [aIm] "+r" (aIm)
|
||||
: [fft] "r" (fft)
|
||||
: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcAec_Overdrive_mips(float overdrive_scaling,
|
||||
float hNlFb,
|
||||
float hNl[PART_LEN1]) {
|
||||
const float one = 1.0;
|
||||
float* p_hNl;
|
||||
const float* p_WebRtcAec_wC;
|
||||
float temp1, temp2, temp3, temp4;
|
||||
|
||||
p_hNl = &hNl[0];
|
||||
p_WebRtcAec_wC = &WebRtcAec_weightCurve[0];
|
||||
|
||||
for (int i = 0; i < PART_LEN1; ++i) {
|
||||
// Weight subbands
|
||||
__asm __volatile(
|
||||
".set push \n\t"
|
||||
".set noreorder \n\t"
|
||||
"lwc1 %[temp1], 0(%[p_hNl]) \n\t"
|
||||
"lwc1 %[temp2], 0(%[p_wC]) \n\t"
|
||||
"c.lt.s %[hNlFb], %[temp1] \n\t"
|
||||
"bc1f 1f \n\t"
|
||||
" mul.s %[temp3], %[temp2], %[hNlFb] \n\t"
|
||||
"sub.s %[temp4], %[one], %[temp2] \n\t"
|
||||
#if !defined(MIPS32_R2_LE)
|
||||
"mul.s %[temp1], %[temp1], %[temp4] \n\t"
|
||||
"add.s %[temp1], %[temp3], %[temp1] \n\t"
|
||||
#else // #if !defined(MIPS32_R2_LE)
|
||||
"madd.s %[temp1], %[temp3], %[temp1], %[temp4] \n\t"
|
||||
#endif // #if !defined(MIPS32_R2_LE)
|
||||
"swc1 %[temp1], 0(%[p_hNl]) \n\t"
|
||||
"1: \n\t"
|
||||
"addiu %[p_wC], %[p_wC], 4 \n\t"
|
||||
".set pop \n\t"
|
||||
: [temp1] "=&f" (temp1), [temp2] "=&f" (temp2), [temp3] "=&f" (temp3),
|
||||
[temp4] "=&f" (temp4), [p_wC] "+r" (p_WebRtcAec_wC)
|
||||
: [hNlFb] "f" (hNlFb), [one] "f" (one), [p_hNl] "r" (p_hNl)
|
||||
: "memory");
|
||||
|
||||
hNl[i] = powf(hNl[i], overdrive_scaling * WebRtcAec_overDriveCurve[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcAec_Suppress_mips(const float hNl[PART_LEN1],
|
||||
float efw[2][PART_LEN1]) {
|
||||
const float* p_hNl;
|
||||
float* p_efw0;
|
||||
float* p_efw1;
|
||||
float temp1, temp2, temp3, temp4;
|
||||
|
||||
p_hNl = &hNl[0];
|
||||
p_efw0 = &efw[0][0];
|
||||
p_efw1 = &efw[1][0];
|
||||
|
||||
for (int i = 0; i < PART_LEN1; ++i) {
|
||||
__asm __volatile(
|
||||
"lwc1 %[temp1], 0(%[p_hNl]) \n\t"
|
||||
"lwc1 %[temp3], 0(%[p_efw1]) \n\t"
|
||||
"lwc1 %[temp2], 0(%[p_efw0]) \n\t"
|
||||
"addiu %[p_hNl], %[p_hNl], 4 \n\t"
|
||||
"mul.s %[temp3], %[temp3], %[temp1] \n\t"
|
||||
"mul.s %[temp2], %[temp2], %[temp1] \n\t"
|
||||
"addiu %[p_efw0], %[p_efw0], 4 \n\t"
|
||||
"addiu %[p_efw1], %[p_efw1], 4 \n\t"
|
||||
"neg.s %[temp4], %[temp3] \n\t"
|
||||
"swc1 %[temp2], -4(%[p_efw0]) \n\t"
|
||||
"swc1 %[temp4], -4(%[p_efw1]) \n\t"
|
||||
: [temp1] "=&f" (temp1), [temp2] "=&f" (temp2), [temp3] "=&f" (temp3),
|
||||
[temp4] "=&f" (temp4), [p_efw0] "+r" (p_efw0), [p_efw1] "+r" (p_efw1),
|
||||
[p_hNl] "+r" (p_hNl)
|
||||
:
|
||||
: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcAec_ScaleErrorSignal_mips(float mu,
|
||||
float error_threshold,
|
||||
float x_pow[PART_LEN1],
|
||||
float ef[2][PART_LEN1]) {
|
||||
int len = (PART_LEN1);
|
||||
float* ef0 = ef[0];
|
||||
float* ef1 = ef[1];
|
||||
float fac1 = 1e-10f;
|
||||
float err_th2 = error_threshold * error_threshold;
|
||||
float f0, f1, f2;
|
||||
#if !defined(MIPS32_R2_LE)
|
||||
float f3;
|
||||
#endif
|
||||
|
||||
__asm __volatile(
|
||||
".set push \n\t"
|
||||
".set noreorder \n\t"
|
||||
"1: \n\t"
|
||||
"lwc1 %[f0], 0(%[x_pow]) \n\t"
|
||||
"lwc1 %[f1], 0(%[ef0]) \n\t"
|
||||
"lwc1 %[f2], 0(%[ef1]) \n\t"
|
||||
"add.s %[f0], %[f0], %[fac1] \n\t"
|
||||
"div.s %[f1], %[f1], %[f0] \n\t"
|
||||
"div.s %[f2], %[f2], %[f0] \n\t"
|
||||
"mul.s %[f0], %[f1], %[f1] \n\t"
|
||||
#if defined(MIPS32_R2_LE)
|
||||
"madd.s %[f0], %[f0], %[f2], %[f2] \n\t"
|
||||
#else
|
||||
"mul.s %[f3], %[f2], %[f2] \n\t"
|
||||
"add.s %[f0], %[f0], %[f3] \n\t"
|
||||
#endif
|
||||
"c.le.s %[f0], %[err_th2] \n\t"
|
||||
"nop \n\t"
|
||||
"bc1t 2f \n\t"
|
||||
" nop \n\t"
|
||||
"sqrt.s %[f0], %[f0] \n\t"
|
||||
"add.s %[f0], %[f0], %[fac1] \n\t"
|
||||
"div.s %[f0], %[err_th], %[f0] \n\t"
|
||||
"mul.s %[f1], %[f1], %[f0] \n\t"
|
||||
"mul.s %[f2], %[f2], %[f0] \n\t"
|
||||
"2: \n\t"
|
||||
"mul.s %[f1], %[f1], %[mu] \n\t"
|
||||
"mul.s %[f2], %[f2], %[mu] \n\t"
|
||||
"swc1 %[f1], 0(%[ef0]) \n\t"
|
||||
"swc1 %[f2], 0(%[ef1]) \n\t"
|
||||
"addiu %[len], %[len], -1 \n\t"
|
||||
"addiu %[x_pow], %[x_pow], 4 \n\t"
|
||||
"addiu %[ef0], %[ef0], 4 \n\t"
|
||||
"bgtz %[len], 1b \n\t"
|
||||
" addiu %[ef1], %[ef1], 4 \n\t"
|
||||
".set pop \n\t"
|
||||
: [f0] "=&f" (f0), [f1] "=&f" (f1), [f2] "=&f" (f2),
|
||||
#if !defined(MIPS32_R2_LE)
|
||||
[f3] "=&f" (f3),
|
||||
#endif
|
||||
[x_pow] "+r" (x_pow), [ef0] "+r" (ef0), [ef1] "+r" (ef1),
|
||||
[len] "+r" (len)
|
||||
: [fac1] "f" (fac1), [err_th2] "f" (err_th2), [mu] "f" (mu),
|
||||
[err_th] "f" (error_threshold)
|
||||
: "memory");
|
||||
}
|
||||
|
||||
void WebRtcAec_InitAec_mips(void) {
|
||||
WebRtcAec_FilterFar = WebRtcAec_FilterFar_mips;
|
||||
WebRtcAec_FilterAdaptation = WebRtcAec_FilterAdaptation_mips;
|
||||
WebRtcAec_ScaleErrorSignal = WebRtcAec_ScaleErrorSignal_mips;
|
||||
WebRtcAec_Overdrive = WebRtcAec_Overdrive_mips;
|
||||
WebRtcAec_Suppress = WebRtcAec_Suppress_mips;
|
||||
}
|
||||
} // namespace webrtc
|
||||
@ -1,737 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The core AEC algorithm, neon version of speed-critical functions.
|
||||
*
|
||||
* Based on aec_core_sse2.c.
|
||||
*/
|
||||
|
||||
#include <arm_neon.h>
|
||||
#include <math.h>
|
||||
#include <string.h> // memset
|
||||
|
||||
extern "C" {
|
||||
#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h"
|
||||
}
|
||||
#include "webrtc/modules/audio_processing/aec/aec_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec/aec_core_optimized_methods.h"
|
||||
#include "webrtc/modules/audio_processing/utility/ooura_fft.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
enum { kShiftExponentIntoTopMantissa = 8 };
|
||||
enum { kFloatExponentShift = 23 };
|
||||
|
||||
__inline static float MulRe(float aRe, float aIm, float bRe, float bIm) {
|
||||
return aRe * bRe - aIm * bIm;
|
||||
}
|
||||
|
||||
__inline static float MulIm(float aRe, float aIm, float bRe, float bIm) {
|
||||
return aRe * bIm + aIm * bRe;
|
||||
}
|
||||
|
||||
static void FilterFarNEON(int num_partitions,
|
||||
int x_fft_buf_block_pos,
|
||||
float x_fft_buf[2]
|
||||
[kExtendedNumPartitions * PART_LEN1],
|
||||
float h_fft_buf[2]
|
||||
[kExtendedNumPartitions * PART_LEN1],
|
||||
float y_fft[2][PART_LEN1]) {
|
||||
int i;
|
||||
for (i = 0; i < num_partitions; i++) {
|
||||
int j;
|
||||
int xPos = (i + x_fft_buf_block_pos) * PART_LEN1;
|
||||
int pos = i * PART_LEN1;
|
||||
// Check for wrap
|
||||
if (i + x_fft_buf_block_pos >= num_partitions) {
|
||||
xPos -= num_partitions * PART_LEN1;
|
||||
}
|
||||
|
||||
// vectorized code (four at once)
|
||||
for (j = 0; j + 3 < PART_LEN1; j += 4) {
|
||||
const float32x4_t x_fft_buf_re = vld1q_f32(&x_fft_buf[0][xPos + j]);
|
||||
const float32x4_t x_fft_buf_im = vld1q_f32(&x_fft_buf[1][xPos + j]);
|
||||
const float32x4_t h_fft_buf_re = vld1q_f32(&h_fft_buf[0][pos + j]);
|
||||
const float32x4_t h_fft_buf_im = vld1q_f32(&h_fft_buf[1][pos + j]);
|
||||
const float32x4_t y_fft_re = vld1q_f32(&y_fft[0][j]);
|
||||
const float32x4_t y_fft_im = vld1q_f32(&y_fft[1][j]);
|
||||
const float32x4_t a = vmulq_f32(x_fft_buf_re, h_fft_buf_re);
|
||||
const float32x4_t e = vmlsq_f32(a, x_fft_buf_im, h_fft_buf_im);
|
||||
const float32x4_t c = vmulq_f32(x_fft_buf_re, h_fft_buf_im);
|
||||
const float32x4_t f = vmlaq_f32(c, x_fft_buf_im, h_fft_buf_re);
|
||||
const float32x4_t g = vaddq_f32(y_fft_re, e);
|
||||
const float32x4_t h = vaddq_f32(y_fft_im, f);
|
||||
vst1q_f32(&y_fft[0][j], g);
|
||||
vst1q_f32(&y_fft[1][j], h);
|
||||
}
|
||||
// scalar code for the remaining items.
|
||||
for (; j < PART_LEN1; j++) {
|
||||
y_fft[0][j] += MulRe(x_fft_buf[0][xPos + j], x_fft_buf[1][xPos + j],
|
||||
h_fft_buf[0][pos + j], h_fft_buf[1][pos + j]);
|
||||
y_fft[1][j] += MulIm(x_fft_buf[0][xPos + j], x_fft_buf[1][xPos + j],
|
||||
h_fft_buf[0][pos + j], h_fft_buf[1][pos + j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ARM64's arm_neon.h has already defined vdivq_f32 vsqrtq_f32.
|
||||
#if !defined(WEBRTC_ARCH_ARM64)
|
||||
static float32x4_t vdivq_f32(float32x4_t a, float32x4_t b) {
|
||||
int i;
|
||||
float32x4_t x = vrecpeq_f32(b);
|
||||
// from arm documentation
|
||||
// The Newton-Raphson iteration:
|
||||
// x[n+1] = x[n] * (2 - d * x[n])
|
||||
// converges to (1/d) if x0 is the result of VRECPE applied to d.
|
||||
//
|
||||
// Note: The precision did not improve after 2 iterations.
|
||||
for (i = 0; i < 2; i++) {
|
||||
x = vmulq_f32(vrecpsq_f32(b, x), x);
|
||||
}
|
||||
// a/b = a*(1/b)
|
||||
return vmulq_f32(a, x);
|
||||
}
|
||||
|
||||
static float32x4_t vsqrtq_f32(float32x4_t s) {
|
||||
int i;
|
||||
float32x4_t x = vrsqrteq_f32(s);
|
||||
|
||||
// Code to handle sqrt(0).
|
||||
// If the input to sqrtf() is zero, a zero will be returned.
|
||||
// If the input to vrsqrteq_f32() is zero, positive infinity is returned.
|
||||
const uint32x4_t vec_p_inf = vdupq_n_u32(0x7F800000);
|
||||
// check for divide by zero
|
||||
const uint32x4_t div_by_zero = vceqq_u32(vec_p_inf, vreinterpretq_u32_f32(x));
|
||||
// zero out the positive infinity results
|
||||
x = vreinterpretq_f32_u32(
|
||||
vandq_u32(vmvnq_u32(div_by_zero), vreinterpretq_u32_f32(x)));
|
||||
// from arm documentation
|
||||
// The Newton-Raphson iteration:
|
||||
// x[n+1] = x[n] * (3 - d * (x[n] * x[n])) / 2)
|
||||
// converges to (1/√d) if x0 is the result of VRSQRTE applied to d.
|
||||
//
|
||||
// Note: The precision did not improve after 2 iterations.
|
||||
for (i = 0; i < 2; i++) {
|
||||
x = vmulq_f32(vrsqrtsq_f32(vmulq_f32(x, x), s), x);
|
||||
}
|
||||
// sqrt(s) = s * 1/sqrt(s)
|
||||
return vmulq_f32(s, x);
|
||||
}
|
||||
#endif // WEBRTC_ARCH_ARM64
|
||||
|
||||
static void ScaleErrorSignalNEON(float mu,
|
||||
float error_threshold,
|
||||
float x_pow[PART_LEN1],
|
||||
float ef[2][PART_LEN1]) {
|
||||
const float32x4_t k1e_10f = vdupq_n_f32(1e-10f);
|
||||
const float32x4_t kMu = vmovq_n_f32(mu);
|
||||
const float32x4_t kThresh = vmovq_n_f32(error_threshold);
|
||||
int i;
|
||||
// vectorized code (four at once)
|
||||
for (i = 0; i + 3 < PART_LEN1; i += 4) {
|
||||
const float32x4_t x_pow_local = vld1q_f32(&x_pow[i]);
|
||||
const float32x4_t ef_re_base = vld1q_f32(&ef[0][i]);
|
||||
const float32x4_t ef_im_base = vld1q_f32(&ef[1][i]);
|
||||
const float32x4_t xPowPlus = vaddq_f32(x_pow_local, k1e_10f);
|
||||
float32x4_t ef_re = vdivq_f32(ef_re_base, xPowPlus);
|
||||
float32x4_t ef_im = vdivq_f32(ef_im_base, xPowPlus);
|
||||
const float32x4_t ef_re2 = vmulq_f32(ef_re, ef_re);
|
||||
const float32x4_t ef_sum2 = vmlaq_f32(ef_re2, ef_im, ef_im);
|
||||
const float32x4_t absEf = vsqrtq_f32(ef_sum2);
|
||||
const uint32x4_t bigger = vcgtq_f32(absEf, kThresh);
|
||||
const float32x4_t absEfPlus = vaddq_f32(absEf, k1e_10f);
|
||||
const float32x4_t absEfInv = vdivq_f32(kThresh, absEfPlus);
|
||||
uint32x4_t ef_re_if = vreinterpretq_u32_f32(vmulq_f32(ef_re, absEfInv));
|
||||
uint32x4_t ef_im_if = vreinterpretq_u32_f32(vmulq_f32(ef_im, absEfInv));
|
||||
uint32x4_t ef_re_u32 =
|
||||
vandq_u32(vmvnq_u32(bigger), vreinterpretq_u32_f32(ef_re));
|
||||
uint32x4_t ef_im_u32 =
|
||||
vandq_u32(vmvnq_u32(bigger), vreinterpretq_u32_f32(ef_im));
|
||||
ef_re_if = vandq_u32(bigger, ef_re_if);
|
||||
ef_im_if = vandq_u32(bigger, ef_im_if);
|
||||
ef_re_u32 = vorrq_u32(ef_re_u32, ef_re_if);
|
||||
ef_im_u32 = vorrq_u32(ef_im_u32, ef_im_if);
|
||||
ef_re = vmulq_f32(vreinterpretq_f32_u32(ef_re_u32), kMu);
|
||||
ef_im = vmulq_f32(vreinterpretq_f32_u32(ef_im_u32), kMu);
|
||||
vst1q_f32(&ef[0][i], ef_re);
|
||||
vst1q_f32(&ef[1][i], ef_im);
|
||||
}
|
||||
// scalar code for the remaining items.
|
||||
for (; i < PART_LEN1; i++) {
|
||||
float abs_ef;
|
||||
ef[0][i] /= (x_pow[i] + 1e-10f);
|
||||
ef[1][i] /= (x_pow[i] + 1e-10f);
|
||||
abs_ef = sqrtf(ef[0][i] * ef[0][i] + ef[1][i] * ef[1][i]);
|
||||
|
||||
if (abs_ef > error_threshold) {
|
||||
abs_ef = error_threshold / (abs_ef + 1e-10f);
|
||||
ef[0][i] *= abs_ef;
|
||||
ef[1][i] *= abs_ef;
|
||||
}
|
||||
|
||||
// Stepsize factor
|
||||
ef[0][i] *= mu;
|
||||
ef[1][i] *= mu;
|
||||
}
|
||||
}
|
||||
|
||||
static void FilterAdaptationNEON(
|
||||
const OouraFft& ooura_fft,
|
||||
int num_partitions,
|
||||
int x_fft_buf_block_pos,
|
||||
float x_fft_buf[2][kExtendedNumPartitions * PART_LEN1],
|
||||
float e_fft[2][PART_LEN1],
|
||||
float h_fft_buf[2][kExtendedNumPartitions * PART_LEN1]) {
|
||||
float fft[PART_LEN2];
|
||||
int i;
|
||||
for (i = 0; i < num_partitions; i++) {
|
||||
int xPos = (i + x_fft_buf_block_pos) * PART_LEN1;
|
||||
int pos = i * PART_LEN1;
|
||||
int j;
|
||||
// Check for wrap
|
||||
if (i + x_fft_buf_block_pos >= num_partitions) {
|
||||
xPos -= num_partitions * PART_LEN1;
|
||||
}
|
||||
|
||||
// Process the whole array...
|
||||
for (j = 0; j < PART_LEN; j += 4) {
|
||||
// Load x_fft_buf and e_fft.
|
||||
const float32x4_t x_fft_buf_re = vld1q_f32(&x_fft_buf[0][xPos + j]);
|
||||
const float32x4_t x_fft_buf_im = vld1q_f32(&x_fft_buf[1][xPos + j]);
|
||||
const float32x4_t e_fft_re = vld1q_f32(&e_fft[0][j]);
|
||||
const float32x4_t e_fft_im = vld1q_f32(&e_fft[1][j]);
|
||||
// Calculate the product of conjugate(x_fft_buf) by e_fft.
|
||||
// re(conjugate(a) * b) = aRe * bRe + aIm * bIm
|
||||
// im(conjugate(a) * b)= aRe * bIm - aIm * bRe
|
||||
const float32x4_t a = vmulq_f32(x_fft_buf_re, e_fft_re);
|
||||
const float32x4_t e = vmlaq_f32(a, x_fft_buf_im, e_fft_im);
|
||||
const float32x4_t c = vmulq_f32(x_fft_buf_re, e_fft_im);
|
||||
const float32x4_t f = vmlsq_f32(c, x_fft_buf_im, e_fft_re);
|
||||
// Interleave real and imaginary parts.
|
||||
const float32x4x2_t g_n_h = vzipq_f32(e, f);
|
||||
// Store
|
||||
vst1q_f32(&fft[2 * j + 0], g_n_h.val[0]);
|
||||
vst1q_f32(&fft[2 * j + 4], g_n_h.val[1]);
|
||||
}
|
||||
// ... and fixup the first imaginary entry.
|
||||
fft[1] =
|
||||
MulRe(x_fft_buf[0][xPos + PART_LEN], -x_fft_buf[1][xPos + PART_LEN],
|
||||
e_fft[0][PART_LEN], e_fft[1][PART_LEN]);
|
||||
|
||||
ooura_fft.InverseFft(fft);
|
||||
memset(fft + PART_LEN, 0, sizeof(float) * PART_LEN);
|
||||
|
||||
// fft scaling
|
||||
{
|
||||
const float scale = 2.0f / PART_LEN2;
|
||||
const float32x4_t scale_ps = vmovq_n_f32(scale);
|
||||
for (j = 0; j < PART_LEN; j += 4) {
|
||||
const float32x4_t fft_ps = vld1q_f32(&fft[j]);
|
||||
const float32x4_t fft_scale = vmulq_f32(fft_ps, scale_ps);
|
||||
vst1q_f32(&fft[j], fft_scale);
|
||||
}
|
||||
}
|
||||
ooura_fft.Fft(fft);
|
||||
|
||||
{
|
||||
const float wt1 = h_fft_buf[1][pos];
|
||||
h_fft_buf[0][pos + PART_LEN] += fft[1];
|
||||
for (j = 0; j < PART_LEN; j += 4) {
|
||||
float32x4_t wtBuf_re = vld1q_f32(&h_fft_buf[0][pos + j]);
|
||||
float32x4_t wtBuf_im = vld1q_f32(&h_fft_buf[1][pos + j]);
|
||||
const float32x4_t fft0 = vld1q_f32(&fft[2 * j + 0]);
|
||||
const float32x4_t fft4 = vld1q_f32(&fft[2 * j + 4]);
|
||||
const float32x4x2_t fft_re_im = vuzpq_f32(fft0, fft4);
|
||||
wtBuf_re = vaddq_f32(wtBuf_re, fft_re_im.val[0]);
|
||||
wtBuf_im = vaddq_f32(wtBuf_im, fft_re_im.val[1]);
|
||||
|
||||
vst1q_f32(&h_fft_buf[0][pos + j], wtBuf_re);
|
||||
vst1q_f32(&h_fft_buf[1][pos + j], wtBuf_im);
|
||||
}
|
||||
h_fft_buf[1][pos] = wt1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static float32x4_t vpowq_f32(float32x4_t a, float32x4_t b) {
|
||||
// a^b = exp2(b * log2(a))
|
||||
// exp2(x) and log2(x) are calculated using polynomial approximations.
|
||||
float32x4_t log2_a, b_log2_a, a_exp_b;
|
||||
|
||||
// Calculate log2(x), x = a.
|
||||
{
|
||||
// To calculate log2(x), we decompose x like this:
|
||||
// x = y * 2^n
|
||||
// n is an integer
|
||||
// y is in the [1.0, 2.0) range
|
||||
//
|
||||
// log2(x) = log2(y) + n
|
||||
// n can be evaluated by playing with float representation.
|
||||
// log2(y) in a small range can be approximated, this code uses an order
|
||||
// five polynomial approximation. The coefficients have been
|
||||
// estimated with the Remez algorithm and the resulting
|
||||
// polynomial has a maximum relative error of 0.00086%.
|
||||
|
||||
// Compute n.
|
||||
// This is done by masking the exponent, shifting it into the top bit of
|
||||
// the mantissa, putting eight into the biased exponent (to shift/
|
||||
// compensate the fact that the exponent has been shifted in the top/
|
||||
// fractional part and finally getting rid of the implicit leading one
|
||||
// from the mantissa by substracting it out.
|
||||
const uint32x4_t vec_float_exponent_mask = vdupq_n_u32(0x7F800000);
|
||||
const uint32x4_t vec_eight_biased_exponent = vdupq_n_u32(0x43800000);
|
||||
const uint32x4_t vec_implicit_leading_one = vdupq_n_u32(0x43BF8000);
|
||||
const uint32x4_t two_n =
|
||||
vandq_u32(vreinterpretq_u32_f32(a), vec_float_exponent_mask);
|
||||
const uint32x4_t n_1 = vshrq_n_u32(two_n, kShiftExponentIntoTopMantissa);
|
||||
const uint32x4_t n_0 = vorrq_u32(n_1, vec_eight_biased_exponent);
|
||||
const float32x4_t n =
|
||||
vsubq_f32(vreinterpretq_f32_u32(n_0),
|
||||
vreinterpretq_f32_u32(vec_implicit_leading_one));
|
||||
// Compute y.
|
||||
const uint32x4_t vec_mantissa_mask = vdupq_n_u32(0x007FFFFF);
|
||||
const uint32x4_t vec_zero_biased_exponent_is_one = vdupq_n_u32(0x3F800000);
|
||||
const uint32x4_t mantissa =
|
||||
vandq_u32(vreinterpretq_u32_f32(a), vec_mantissa_mask);
|
||||
const float32x4_t y = vreinterpretq_f32_u32(
|
||||
vorrq_u32(mantissa, vec_zero_biased_exponent_is_one));
|
||||
// Approximate log2(y) ~= (y - 1) * pol5(y).
|
||||
// pol5(y) = C5 * y^5 + C4 * y^4 + C3 * y^3 + C2 * y^2 + C1 * y + C0
|
||||
const float32x4_t C5 = vdupq_n_f32(-3.4436006e-2f);
|
||||
const float32x4_t C4 = vdupq_n_f32(3.1821337e-1f);
|
||||
const float32x4_t C3 = vdupq_n_f32(-1.2315303f);
|
||||
const float32x4_t C2 = vdupq_n_f32(2.5988452f);
|
||||
const float32x4_t C1 = vdupq_n_f32(-3.3241990f);
|
||||
const float32x4_t C0 = vdupq_n_f32(3.1157899f);
|
||||
float32x4_t pol5_y = C5;
|
||||
pol5_y = vmlaq_f32(C4, y, pol5_y);
|
||||
pol5_y = vmlaq_f32(C3, y, pol5_y);
|
||||
pol5_y = vmlaq_f32(C2, y, pol5_y);
|
||||
pol5_y = vmlaq_f32(C1, y, pol5_y);
|
||||
pol5_y = vmlaq_f32(C0, y, pol5_y);
|
||||
const float32x4_t y_minus_one =
|
||||
vsubq_f32(y, vreinterpretq_f32_u32(vec_zero_biased_exponent_is_one));
|
||||
const float32x4_t log2_y = vmulq_f32(y_minus_one, pol5_y);
|
||||
|
||||
// Combine parts.
|
||||
log2_a = vaddq_f32(n, log2_y);
|
||||
}
|
||||
|
||||
// b * log2(a)
|
||||
b_log2_a = vmulq_f32(b, log2_a);
|
||||
|
||||
// Calculate exp2(x), x = b * log2(a).
|
||||
{
|
||||
// To calculate 2^x, we decompose x like this:
|
||||
// x = n + y
|
||||
// n is an integer, the value of x - 0.5 rounded down, therefore
|
||||
// y is in the [0.5, 1.5) range
|
||||
//
|
||||
// 2^x = 2^n * 2^y
|
||||
// 2^n can be evaluated by playing with float representation.
|
||||
// 2^y in a small range can be approximated, this code uses an order two
|
||||
// polynomial approximation. The coefficients have been estimated
|
||||
// with the Remez algorithm and the resulting polynomial has a
|
||||
// maximum relative error of 0.17%.
|
||||
// To avoid over/underflow, we reduce the range of input to ]-127, 129].
|
||||
const float32x4_t max_input = vdupq_n_f32(129.f);
|
||||
const float32x4_t min_input = vdupq_n_f32(-126.99999f);
|
||||
const float32x4_t x_min = vminq_f32(b_log2_a, max_input);
|
||||
const float32x4_t x_max = vmaxq_f32(x_min, min_input);
|
||||
// Compute n.
|
||||
const float32x4_t half = vdupq_n_f32(0.5f);
|
||||
const float32x4_t x_minus_half = vsubq_f32(x_max, half);
|
||||
const int32x4_t x_minus_half_floor = vcvtq_s32_f32(x_minus_half);
|
||||
|
||||
// Compute 2^n.
|
||||
const int32x4_t float_exponent_bias = vdupq_n_s32(127);
|
||||
const int32x4_t two_n_exponent =
|
||||
vaddq_s32(x_minus_half_floor, float_exponent_bias);
|
||||
const float32x4_t two_n =
|
||||
vreinterpretq_f32_s32(vshlq_n_s32(two_n_exponent, kFloatExponentShift));
|
||||
// Compute y.
|
||||
const float32x4_t y = vsubq_f32(x_max, vcvtq_f32_s32(x_minus_half_floor));
|
||||
|
||||
// Approximate 2^y ~= C2 * y^2 + C1 * y + C0.
|
||||
const float32x4_t C2 = vdupq_n_f32(3.3718944e-1f);
|
||||
const float32x4_t C1 = vdupq_n_f32(6.5763628e-1f);
|
||||
const float32x4_t C0 = vdupq_n_f32(1.0017247f);
|
||||
float32x4_t exp2_y = C2;
|
||||
exp2_y = vmlaq_f32(C1, y, exp2_y);
|
||||
exp2_y = vmlaq_f32(C0, y, exp2_y);
|
||||
|
||||
// Combine parts.
|
||||
a_exp_b = vmulq_f32(exp2_y, two_n);
|
||||
}
|
||||
|
||||
return a_exp_b;
|
||||
}
|
||||
|
||||
static void OverdriveNEON(float overdrive_scaling,
|
||||
float hNlFb,
|
||||
float hNl[PART_LEN1]) {
|
||||
int i;
|
||||
const float32x4_t vec_hNlFb = vmovq_n_f32(hNlFb);
|
||||
const float32x4_t vec_one = vdupq_n_f32(1.0f);
|
||||
const float32x4_t vec_overdrive_scaling = vmovq_n_f32(overdrive_scaling);
|
||||
|
||||
// vectorized code (four at once)
|
||||
for (i = 0; i + 3 < PART_LEN1; i += 4) {
|
||||
// Weight subbands
|
||||
float32x4_t vec_hNl = vld1q_f32(&hNl[i]);
|
||||
const float32x4_t vec_weightCurve = vld1q_f32(&WebRtcAec_weightCurve[i]);
|
||||
const uint32x4_t bigger = vcgtq_f32(vec_hNl, vec_hNlFb);
|
||||
const float32x4_t vec_weightCurve_hNlFb =
|
||||
vmulq_f32(vec_weightCurve, vec_hNlFb);
|
||||
const float32x4_t vec_one_weightCurve = vsubq_f32(vec_one, vec_weightCurve);
|
||||
const float32x4_t vec_one_weightCurve_hNl =
|
||||
vmulq_f32(vec_one_weightCurve, vec_hNl);
|
||||
const uint32x4_t vec_if0 =
|
||||
vandq_u32(vmvnq_u32(bigger), vreinterpretq_u32_f32(vec_hNl));
|
||||
const float32x4_t vec_one_weightCurve_add =
|
||||
vaddq_f32(vec_weightCurve_hNlFb, vec_one_weightCurve_hNl);
|
||||
const uint32x4_t vec_if1 =
|
||||
vandq_u32(bigger, vreinterpretq_u32_f32(vec_one_weightCurve_add));
|
||||
|
||||
vec_hNl = vreinterpretq_f32_u32(vorrq_u32(vec_if0, vec_if1));
|
||||
|
||||
const float32x4_t vec_overDriveCurve =
|
||||
vld1q_f32(&WebRtcAec_overDriveCurve[i]);
|
||||
const float32x4_t vec_overDriveSm_overDriveCurve =
|
||||
vmulq_f32(vec_overdrive_scaling, vec_overDriveCurve);
|
||||
vec_hNl = vpowq_f32(vec_hNl, vec_overDriveSm_overDriveCurve);
|
||||
vst1q_f32(&hNl[i], vec_hNl);
|
||||
}
|
||||
|
||||
// scalar code for the remaining items.
|
||||
for (; i < PART_LEN1; i++) {
|
||||
// Weight subbands
|
||||
if (hNl[i] > hNlFb) {
|
||||
hNl[i] = WebRtcAec_weightCurve[i] * hNlFb +
|
||||
(1 - WebRtcAec_weightCurve[i]) * hNl[i];
|
||||
}
|
||||
|
||||
hNl[i] = powf(hNl[i], overdrive_scaling * WebRtcAec_overDriveCurve[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void SuppressNEON(const float hNl[PART_LEN1], float efw[2][PART_LEN1]) {
|
||||
int i;
|
||||
const float32x4_t vec_minus_one = vdupq_n_f32(-1.0f);
|
||||
// vectorized code (four at once)
|
||||
for (i = 0; i + 3 < PART_LEN1; i += 4) {
|
||||
float32x4_t vec_hNl = vld1q_f32(&hNl[i]);
|
||||
float32x4_t vec_efw_re = vld1q_f32(&efw[0][i]);
|
||||
float32x4_t vec_efw_im = vld1q_f32(&efw[1][i]);
|
||||
vec_efw_re = vmulq_f32(vec_efw_re, vec_hNl);
|
||||
vec_efw_im = vmulq_f32(vec_efw_im, vec_hNl);
|
||||
|
||||
// Ooura fft returns incorrect sign on imaginary component. It matters
|
||||
// here because we are making an additive change with comfort noise.
|
||||
vec_efw_im = vmulq_f32(vec_efw_im, vec_minus_one);
|
||||
vst1q_f32(&efw[0][i], vec_efw_re);
|
||||
vst1q_f32(&efw[1][i], vec_efw_im);
|
||||
}
|
||||
|
||||
// scalar code for the remaining items.
|
||||
for (; i < PART_LEN1; i++) {
|
||||
efw[0][i] *= hNl[i];
|
||||
efw[1][i] *= hNl[i];
|
||||
|
||||
// Ooura fft returns incorrect sign on imaginary component. It matters
|
||||
// here because we are making an additive change with comfort noise.
|
||||
efw[1][i] *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int PartitionDelayNEON(
|
||||
int num_partitions,
|
||||
float h_fft_buf[2][kExtendedNumPartitions * PART_LEN1]) {
|
||||
// Measures the energy in each filter partition and returns the partition with
|
||||
// highest energy.
|
||||
// TODO(bjornv): Spread computational cost by computing one partition per
|
||||
// block?
|
||||
float wfEnMax = 0;
|
||||
int i;
|
||||
int delay = 0;
|
||||
|
||||
for (i = 0; i < num_partitions; i++) {
|
||||
int j;
|
||||
int pos = i * PART_LEN1;
|
||||
float wfEn = 0;
|
||||
float32x4_t vec_wfEn = vdupq_n_f32(0.0f);
|
||||
// vectorized code (four at once)
|
||||
for (j = 0; j + 3 < PART_LEN1; j += 4) {
|
||||
const float32x4_t vec_wfBuf0 = vld1q_f32(&h_fft_buf[0][pos + j]);
|
||||
const float32x4_t vec_wfBuf1 = vld1q_f32(&h_fft_buf[1][pos + j]);
|
||||
vec_wfEn = vmlaq_f32(vec_wfEn, vec_wfBuf0, vec_wfBuf0);
|
||||
vec_wfEn = vmlaq_f32(vec_wfEn, vec_wfBuf1, vec_wfBuf1);
|
||||
}
|
||||
{
|
||||
float32x2_t vec_total;
|
||||
// A B C D
|
||||
vec_total = vpadd_f32(vget_low_f32(vec_wfEn), vget_high_f32(vec_wfEn));
|
||||
// A+B C+D
|
||||
vec_total = vpadd_f32(vec_total, vec_total);
|
||||
// A+B+C+D A+B+C+D
|
||||
wfEn = vget_lane_f32(vec_total, 0);
|
||||
}
|
||||
|
||||
// scalar code for the remaining items.
|
||||
for (; j < PART_LEN1; j++) {
|
||||
wfEn += h_fft_buf[0][pos + j] * h_fft_buf[0][pos + j] +
|
||||
h_fft_buf[1][pos + j] * h_fft_buf[1][pos + j];
|
||||
}
|
||||
|
||||
if (wfEn > wfEnMax) {
|
||||
wfEnMax = wfEn;
|
||||
delay = i;
|
||||
}
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
// Updates the following smoothed Power Spectral Densities (PSD):
|
||||
// - sd : near-end
|
||||
// - se : residual echo
|
||||
// - sx : far-end
|
||||
// - sde : cross-PSD of near-end and residual echo
|
||||
// - sxd : cross-PSD of near-end and far-end
|
||||
//
|
||||
// In addition to updating the PSDs, also the filter diverge state is determined
|
||||
// upon actions are taken.
|
||||
static void UpdateCoherenceSpectraNEON(int mult,
|
||||
bool extended_filter_enabled,
|
||||
float efw[2][PART_LEN1],
|
||||
float dfw[2][PART_LEN1],
|
||||
float xfw[2][PART_LEN1],
|
||||
CoherenceState* coherence_state,
|
||||
short* filter_divergence_state,
|
||||
int* extreme_filter_divergence) {
|
||||
// Power estimate smoothing coefficients.
|
||||
const float* ptrGCoh =
|
||||
extended_filter_enabled
|
||||
? WebRtcAec_kExtendedSmoothingCoefficients[mult - 1]
|
||||
: WebRtcAec_kNormalSmoothingCoefficients[mult - 1];
|
||||
int i;
|
||||
float sdSum = 0, seSum = 0;
|
||||
const float32x4_t vec_15 = vdupq_n_f32(WebRtcAec_kMinFarendPSD);
|
||||
float32x4_t vec_sdSum = vdupq_n_f32(0.0f);
|
||||
float32x4_t vec_seSum = vdupq_n_f32(0.0f);
|
||||
|
||||
for (i = 0; i + 3 < PART_LEN1; i += 4) {
|
||||
const float32x4_t vec_dfw0 = vld1q_f32(&dfw[0][i]);
|
||||
const float32x4_t vec_dfw1 = vld1q_f32(&dfw[1][i]);
|
||||
const float32x4_t vec_efw0 = vld1q_f32(&efw[0][i]);
|
||||
const float32x4_t vec_efw1 = vld1q_f32(&efw[1][i]);
|
||||
const float32x4_t vec_xfw0 = vld1q_f32(&xfw[0][i]);
|
||||
const float32x4_t vec_xfw1 = vld1q_f32(&xfw[1][i]);
|
||||
float32x4_t vec_sd =
|
||||
vmulq_n_f32(vld1q_f32(&coherence_state->sd[i]), ptrGCoh[0]);
|
||||
float32x4_t vec_se =
|
||||
vmulq_n_f32(vld1q_f32(&coherence_state->se[i]), ptrGCoh[0]);
|
||||
float32x4_t vec_sx =
|
||||
vmulq_n_f32(vld1q_f32(&coherence_state->sx[i]), ptrGCoh[0]);
|
||||
float32x4_t vec_dfw_sumsq = vmulq_f32(vec_dfw0, vec_dfw0);
|
||||
float32x4_t vec_efw_sumsq = vmulq_f32(vec_efw0, vec_efw0);
|
||||
float32x4_t vec_xfw_sumsq = vmulq_f32(vec_xfw0, vec_xfw0);
|
||||
|
||||
vec_dfw_sumsq = vmlaq_f32(vec_dfw_sumsq, vec_dfw1, vec_dfw1);
|
||||
vec_efw_sumsq = vmlaq_f32(vec_efw_sumsq, vec_efw1, vec_efw1);
|
||||
vec_xfw_sumsq = vmlaq_f32(vec_xfw_sumsq, vec_xfw1, vec_xfw1);
|
||||
vec_xfw_sumsq = vmaxq_f32(vec_xfw_sumsq, vec_15);
|
||||
vec_sd = vmlaq_n_f32(vec_sd, vec_dfw_sumsq, ptrGCoh[1]);
|
||||
vec_se = vmlaq_n_f32(vec_se, vec_efw_sumsq, ptrGCoh[1]);
|
||||
vec_sx = vmlaq_n_f32(vec_sx, vec_xfw_sumsq, ptrGCoh[1]);
|
||||
|
||||
vst1q_f32(&coherence_state->sd[i], vec_sd);
|
||||
vst1q_f32(&coherence_state->se[i], vec_se);
|
||||
vst1q_f32(&coherence_state->sx[i], vec_sx);
|
||||
|
||||
{
|
||||
float32x4x2_t vec_sde = vld2q_f32(&coherence_state->sde[i][0]);
|
||||
float32x4_t vec_dfwefw0011 = vmulq_f32(vec_dfw0, vec_efw0);
|
||||
float32x4_t vec_dfwefw0110 = vmulq_f32(vec_dfw0, vec_efw1);
|
||||
vec_sde.val[0] = vmulq_n_f32(vec_sde.val[0], ptrGCoh[0]);
|
||||
vec_sde.val[1] = vmulq_n_f32(vec_sde.val[1], ptrGCoh[0]);
|
||||
vec_dfwefw0011 = vmlaq_f32(vec_dfwefw0011, vec_dfw1, vec_efw1);
|
||||
vec_dfwefw0110 = vmlsq_f32(vec_dfwefw0110, vec_dfw1, vec_efw0);
|
||||
vec_sde.val[0] = vmlaq_n_f32(vec_sde.val[0], vec_dfwefw0011, ptrGCoh[1]);
|
||||
vec_sde.val[1] = vmlaq_n_f32(vec_sde.val[1], vec_dfwefw0110, ptrGCoh[1]);
|
||||
vst2q_f32(&coherence_state->sde[i][0], vec_sde);
|
||||
}
|
||||
|
||||
{
|
||||
float32x4x2_t vec_sxd = vld2q_f32(&coherence_state->sxd[i][0]);
|
||||
float32x4_t vec_dfwxfw0011 = vmulq_f32(vec_dfw0, vec_xfw0);
|
||||
float32x4_t vec_dfwxfw0110 = vmulq_f32(vec_dfw0, vec_xfw1);
|
||||
vec_sxd.val[0] = vmulq_n_f32(vec_sxd.val[0], ptrGCoh[0]);
|
||||
vec_sxd.val[1] = vmulq_n_f32(vec_sxd.val[1], ptrGCoh[0]);
|
||||
vec_dfwxfw0011 = vmlaq_f32(vec_dfwxfw0011, vec_dfw1, vec_xfw1);
|
||||
vec_dfwxfw0110 = vmlsq_f32(vec_dfwxfw0110, vec_dfw1, vec_xfw0);
|
||||
vec_sxd.val[0] = vmlaq_n_f32(vec_sxd.val[0], vec_dfwxfw0011, ptrGCoh[1]);
|
||||
vec_sxd.val[1] = vmlaq_n_f32(vec_sxd.val[1], vec_dfwxfw0110, ptrGCoh[1]);
|
||||
vst2q_f32(&coherence_state->sxd[i][0], vec_sxd);
|
||||
}
|
||||
|
||||
vec_sdSum = vaddq_f32(vec_sdSum, vec_sd);
|
||||
vec_seSum = vaddq_f32(vec_seSum, vec_se);
|
||||
}
|
||||
{
|
||||
float32x2_t vec_sdSum_total;
|
||||
float32x2_t vec_seSum_total;
|
||||
// A B C D
|
||||
vec_sdSum_total =
|
||||
vpadd_f32(vget_low_f32(vec_sdSum), vget_high_f32(vec_sdSum));
|
||||
vec_seSum_total =
|
||||
vpadd_f32(vget_low_f32(vec_seSum), vget_high_f32(vec_seSum));
|
||||
// A+B C+D
|
||||
vec_sdSum_total = vpadd_f32(vec_sdSum_total, vec_sdSum_total);
|
||||
vec_seSum_total = vpadd_f32(vec_seSum_total, vec_seSum_total);
|
||||
// A+B+C+D A+B+C+D
|
||||
sdSum = vget_lane_f32(vec_sdSum_total, 0);
|
||||
seSum = vget_lane_f32(vec_seSum_total, 0);
|
||||
}
|
||||
|
||||
// scalar code for the remaining items.
|
||||
for (; i < PART_LEN1; i++) {
|
||||
coherence_state->sd[i] =
|
||||
ptrGCoh[0] * coherence_state->sd[i] +
|
||||
ptrGCoh[1] * (dfw[0][i] * dfw[0][i] + dfw[1][i] * dfw[1][i]);
|
||||
coherence_state->se[i] =
|
||||
ptrGCoh[0] * coherence_state->se[i] +
|
||||
ptrGCoh[1] * (efw[0][i] * efw[0][i] + efw[1][i] * efw[1][i]);
|
||||
// We threshold here to protect against the ill-effects of a zero farend.
|
||||
// The threshold is not arbitrarily chosen, but balances protection and
|
||||
// adverse interaction with the algorithm's tuning.
|
||||
// TODO(bjornv): investigate further why this is so sensitive.
|
||||
coherence_state->sx[i] =
|
||||
ptrGCoh[0] * coherence_state->sx[i] +
|
||||
ptrGCoh[1] *
|
||||
WEBRTC_SPL_MAX(xfw[0][i] * xfw[0][i] + xfw[1][i] * xfw[1][i],
|
||||
WebRtcAec_kMinFarendPSD);
|
||||
|
||||
coherence_state->sde[i][0] =
|
||||
ptrGCoh[0] * coherence_state->sde[i][0] +
|
||||
ptrGCoh[1] * (dfw[0][i] * efw[0][i] + dfw[1][i] * efw[1][i]);
|
||||
coherence_state->sde[i][1] =
|
||||
ptrGCoh[0] * coherence_state->sde[i][1] +
|
||||
ptrGCoh[1] * (dfw[0][i] * efw[1][i] - dfw[1][i] * efw[0][i]);
|
||||
|
||||
coherence_state->sxd[i][0] =
|
||||
ptrGCoh[0] * coherence_state->sxd[i][0] +
|
||||
ptrGCoh[1] * (dfw[0][i] * xfw[0][i] + dfw[1][i] * xfw[1][i]);
|
||||
coherence_state->sxd[i][1] =
|
||||
ptrGCoh[0] * coherence_state->sxd[i][1] +
|
||||
ptrGCoh[1] * (dfw[0][i] * xfw[1][i] - dfw[1][i] * xfw[0][i]);
|
||||
|
||||
sdSum += coherence_state->sd[i];
|
||||
seSum += coherence_state->se[i];
|
||||
}
|
||||
|
||||
// Divergent filter safeguard update.
|
||||
*filter_divergence_state =
|
||||
(*filter_divergence_state ? 1.05f : 1.0f) * seSum > sdSum;
|
||||
|
||||
// Signal extreme filter divergence if the error is significantly larger
|
||||
// than the nearend (13 dB).
|
||||
*extreme_filter_divergence = (seSum > (19.95f * sdSum));
|
||||
}
|
||||
|
||||
// Window time domain data to be used by the fft.
|
||||
static void WindowDataNEON(float* x_windowed, const float* x) {
|
||||
int i;
|
||||
for (i = 0; i < PART_LEN; i += 4) {
|
||||
const float32x4_t vec_Buf1 = vld1q_f32(&x[i]);
|
||||
const float32x4_t vec_Buf2 = vld1q_f32(&x[PART_LEN + i]);
|
||||
const float32x4_t vec_sqrtHanning = vld1q_f32(&WebRtcAec_sqrtHanning[i]);
|
||||
// A B C D
|
||||
float32x4_t vec_sqrtHanning_rev =
|
||||
vld1q_f32(&WebRtcAec_sqrtHanning[PART_LEN - i - 3]);
|
||||
// B A D C
|
||||
vec_sqrtHanning_rev = vrev64q_f32(vec_sqrtHanning_rev);
|
||||
// D C B A
|
||||
vec_sqrtHanning_rev = vcombine_f32(vget_high_f32(vec_sqrtHanning_rev),
|
||||
vget_low_f32(vec_sqrtHanning_rev));
|
||||
vst1q_f32(&x_windowed[i], vmulq_f32(vec_Buf1, vec_sqrtHanning));
|
||||
vst1q_f32(&x_windowed[PART_LEN + i],
|
||||
vmulq_f32(vec_Buf2, vec_sqrtHanning_rev));
|
||||
}
|
||||
}
|
||||
|
||||
// Puts fft output data into a complex valued array.
|
||||
static void StoreAsComplexNEON(const float* data,
|
||||
float data_complex[2][PART_LEN1]) {
|
||||
int i;
|
||||
for (i = 0; i < PART_LEN; i += 4) {
|
||||
const float32x4x2_t vec_data = vld2q_f32(&data[2 * i]);
|
||||
vst1q_f32(&data_complex[0][i], vec_data.val[0]);
|
||||
vst1q_f32(&data_complex[1][i], vec_data.val[1]);
|
||||
}
|
||||
// fix beginning/end values
|
||||
data_complex[1][0] = 0;
|
||||
data_complex[1][PART_LEN] = 0;
|
||||
data_complex[0][0] = data[0];
|
||||
data_complex[0][PART_LEN] = data[1];
|
||||
}
|
||||
|
||||
static void ComputeCoherenceNEON(const CoherenceState* coherence_state,
|
||||
float* cohde,
|
||||
float* cohxd) {
|
||||
int i;
|
||||
|
||||
{
|
||||
const float32x4_t vec_1eminus10 = vdupq_n_f32(1e-10f);
|
||||
|
||||
// Subband coherence
|
||||
for (i = 0; i + 3 < PART_LEN1; i += 4) {
|
||||
const float32x4_t vec_sd = vld1q_f32(&coherence_state->sd[i]);
|
||||
const float32x4_t vec_se = vld1q_f32(&coherence_state->se[i]);
|
||||
const float32x4_t vec_sx = vld1q_f32(&coherence_state->sx[i]);
|
||||
const float32x4_t vec_sdse = vmlaq_f32(vec_1eminus10, vec_sd, vec_se);
|
||||
const float32x4_t vec_sdsx = vmlaq_f32(vec_1eminus10, vec_sd, vec_sx);
|
||||
float32x4x2_t vec_sde = vld2q_f32(&coherence_state->sde[i][0]);
|
||||
float32x4x2_t vec_sxd = vld2q_f32(&coherence_state->sxd[i][0]);
|
||||
float32x4_t vec_cohde = vmulq_f32(vec_sde.val[0], vec_sde.val[0]);
|
||||
float32x4_t vec_cohxd = vmulq_f32(vec_sxd.val[0], vec_sxd.val[0]);
|
||||
vec_cohde = vmlaq_f32(vec_cohde, vec_sde.val[1], vec_sde.val[1]);
|
||||
vec_cohde = vdivq_f32(vec_cohde, vec_sdse);
|
||||
vec_cohxd = vmlaq_f32(vec_cohxd, vec_sxd.val[1], vec_sxd.val[1]);
|
||||
vec_cohxd = vdivq_f32(vec_cohxd, vec_sdsx);
|
||||
|
||||
vst1q_f32(&cohde[i], vec_cohde);
|
||||
vst1q_f32(&cohxd[i], vec_cohxd);
|
||||
}
|
||||
}
|
||||
// scalar code for the remaining items.
|
||||
for (; i < PART_LEN1; i++) {
|
||||
cohde[i] = (coherence_state->sde[i][0] * coherence_state->sde[i][0] +
|
||||
coherence_state->sde[i][1] * coherence_state->sde[i][1]) /
|
||||
(coherence_state->sd[i] * coherence_state->se[i] + 1e-10f);
|
||||
cohxd[i] = (coherence_state->sxd[i][0] * coherence_state->sxd[i][0] +
|
||||
coherence_state->sxd[i][1] * coherence_state->sxd[i][1]) /
|
||||
(coherence_state->sx[i] * coherence_state->sd[i] + 1e-10f);
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcAec_InitAec_neon(void) {
|
||||
WebRtcAec_FilterFar = FilterFarNEON;
|
||||
WebRtcAec_ScaleErrorSignal = ScaleErrorSignalNEON;
|
||||
WebRtcAec_FilterAdaptation = FilterAdaptationNEON;
|
||||
WebRtcAec_Overdrive = OverdriveNEON;
|
||||
WebRtcAec_Suppress = SuppressNEON;
|
||||
WebRtcAec_ComputeCoherence = ComputeCoherenceNEON;
|
||||
WebRtcAec_UpdateCoherenceSpectra = UpdateCoherenceSpectraNEON;
|
||||
WebRtcAec_StoreAsComplex = StoreAsComplexNEON;
|
||||
WebRtcAec_PartitionDelay = PartitionDelayNEON;
|
||||
WebRtcAec_WindowData = WindowDataNEON;
|
||||
}
|
||||
} // namespace webrtc
|
||||
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_CORE_OPTIMIZED_METHODS_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_CORE_OPTIMIZED_METHODS_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec/aec_core.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
typedef void (*WebRtcAecFilterFar)(
|
||||
int num_partitions,
|
||||
int x_fft_buf_block_pos,
|
||||
float x_fft_buf[2][kExtendedNumPartitions * PART_LEN1],
|
||||
float h_fft_buf[2][kExtendedNumPartitions * PART_LEN1],
|
||||
float y_fft[2][PART_LEN1]);
|
||||
extern WebRtcAecFilterFar WebRtcAec_FilterFar;
|
||||
typedef void (*WebRtcAecScaleErrorSignal)(float mu,
|
||||
float error_threshold,
|
||||
float x_pow[PART_LEN1],
|
||||
float ef[2][PART_LEN1]);
|
||||
extern WebRtcAecScaleErrorSignal WebRtcAec_ScaleErrorSignal;
|
||||
typedef void (*WebRtcAecFilterAdaptation)(
|
||||
const OouraFft& ooura_fft,
|
||||
int num_partitions,
|
||||
int x_fft_buf_block_pos,
|
||||
float x_fft_buf[2][kExtendedNumPartitions * PART_LEN1],
|
||||
float e_fft[2][PART_LEN1],
|
||||
float h_fft_buf[2][kExtendedNumPartitions * PART_LEN1]);
|
||||
extern WebRtcAecFilterAdaptation WebRtcAec_FilterAdaptation;
|
||||
|
||||
typedef void (*WebRtcAecOverdrive)(float overdrive_scaling,
|
||||
const float hNlFb,
|
||||
float hNl[PART_LEN1]);
|
||||
extern WebRtcAecOverdrive WebRtcAec_Overdrive;
|
||||
|
||||
typedef void (*WebRtcAecSuppress)(const float hNl[PART_LEN1],
|
||||
float efw[2][PART_LEN1]);
|
||||
extern WebRtcAecSuppress WebRtcAec_Suppress;
|
||||
|
||||
typedef void (*WebRtcAecComputeCoherence)(const CoherenceState* coherence_state,
|
||||
float* cohde,
|
||||
float* cohxd);
|
||||
extern WebRtcAecComputeCoherence WebRtcAec_ComputeCoherence;
|
||||
|
||||
typedef void (*WebRtcAecUpdateCoherenceSpectra)(int mult,
|
||||
bool extended_filter_enabled,
|
||||
float efw[2][PART_LEN1],
|
||||
float dfw[2][PART_LEN1],
|
||||
float xfw[2][PART_LEN1],
|
||||
CoherenceState* coherence_state,
|
||||
short* filter_divergence_state,
|
||||
int* extreme_filter_divergence);
|
||||
extern WebRtcAecUpdateCoherenceSpectra WebRtcAec_UpdateCoherenceSpectra;
|
||||
|
||||
typedef int (*WebRtcAecPartitionDelay)(
|
||||
int num_partitions,
|
||||
float h_fft_buf[2][kExtendedNumPartitions * PART_LEN1]);
|
||||
extern WebRtcAecPartitionDelay WebRtcAec_PartitionDelay;
|
||||
|
||||
typedef void (*WebRtcAecStoreAsComplex)(const float* data,
|
||||
float data_complex[2][PART_LEN1]);
|
||||
extern WebRtcAecStoreAsComplex WebRtcAec_StoreAsComplex;
|
||||
|
||||
typedef void (*WebRtcAecWindowData)(float* x_windowed, const float* x);
|
||||
extern WebRtcAecWindowData WebRtcAec_WindowData;
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_CORE_OPTIMIZED_METHODS_H_
|
||||
@ -1,751 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2011 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The core AEC algorithm, SSE2 version of speed-critical functions.
|
||||
*/
|
||||
|
||||
#include <emmintrin.h>
|
||||
#include <math.h>
|
||||
#include <string.h> // memset
|
||||
|
||||
extern "C" {
|
||||
#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h"
|
||||
}
|
||||
#include "webrtc/modules/audio_processing/aec/aec_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec/aec_core_optimized_methods.h"
|
||||
#include "webrtc/modules/audio_processing/utility/ooura_fft.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
__inline static float MulRe(float aRe, float aIm, float bRe, float bIm) {
|
||||
return aRe * bRe - aIm * bIm;
|
||||
}
|
||||
|
||||
__inline static float MulIm(float aRe, float aIm, float bRe, float bIm) {
|
||||
return aRe * bIm + aIm * bRe;
|
||||
}
|
||||
|
||||
static void FilterFarSSE2(int num_partitions,
|
||||
int x_fft_buf_block_pos,
|
||||
float x_fft_buf[2]
|
||||
[kExtendedNumPartitions * PART_LEN1],
|
||||
float h_fft_buf[2]
|
||||
[kExtendedNumPartitions * PART_LEN1],
|
||||
float y_fft[2][PART_LEN1]) {
|
||||
int i;
|
||||
for (i = 0; i < num_partitions; i++) {
|
||||
int j;
|
||||
int xPos = (i + x_fft_buf_block_pos) * PART_LEN1;
|
||||
int pos = i * PART_LEN1;
|
||||
// Check for wrap
|
||||
if (i + x_fft_buf_block_pos >= num_partitions) {
|
||||
xPos -= num_partitions * (PART_LEN1);
|
||||
}
|
||||
|
||||
// vectorized code (four at once)
|
||||
for (j = 0; j + 3 < PART_LEN1; j += 4) {
|
||||
const __m128 x_fft_buf_re = _mm_loadu_ps(&x_fft_buf[0][xPos + j]);
|
||||
const __m128 x_fft_buf_im = _mm_loadu_ps(&x_fft_buf[1][xPos + j]);
|
||||
const __m128 h_fft_buf_re = _mm_loadu_ps(&h_fft_buf[0][pos + j]);
|
||||
const __m128 h_fft_buf_im = _mm_loadu_ps(&h_fft_buf[1][pos + j]);
|
||||
const __m128 y_fft_re = _mm_loadu_ps(&y_fft[0][j]);
|
||||
const __m128 y_fft_im = _mm_loadu_ps(&y_fft[1][j]);
|
||||
const __m128 a = _mm_mul_ps(x_fft_buf_re, h_fft_buf_re);
|
||||
const __m128 b = _mm_mul_ps(x_fft_buf_im, h_fft_buf_im);
|
||||
const __m128 c = _mm_mul_ps(x_fft_buf_re, h_fft_buf_im);
|
||||
const __m128 d = _mm_mul_ps(x_fft_buf_im, h_fft_buf_re);
|
||||
const __m128 e = _mm_sub_ps(a, b);
|
||||
const __m128 f = _mm_add_ps(c, d);
|
||||
const __m128 g = _mm_add_ps(y_fft_re, e);
|
||||
const __m128 h = _mm_add_ps(y_fft_im, f);
|
||||
_mm_storeu_ps(&y_fft[0][j], g);
|
||||
_mm_storeu_ps(&y_fft[1][j], h);
|
||||
}
|
||||
// scalar code for the remaining items.
|
||||
for (; j < PART_LEN1; j++) {
|
||||
y_fft[0][j] += MulRe(x_fft_buf[0][xPos + j], x_fft_buf[1][xPos + j],
|
||||
h_fft_buf[0][pos + j], h_fft_buf[1][pos + j]);
|
||||
y_fft[1][j] += MulIm(x_fft_buf[0][xPos + j], x_fft_buf[1][xPos + j],
|
||||
h_fft_buf[0][pos + j], h_fft_buf[1][pos + j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ScaleErrorSignalSSE2(float mu,
|
||||
float error_threshold,
|
||||
float x_pow[PART_LEN1],
|
||||
float ef[2][PART_LEN1]) {
|
||||
const __m128 k1e_10f = _mm_set1_ps(1e-10f);
|
||||
const __m128 kMu = _mm_set1_ps(mu);
|
||||
const __m128 kThresh = _mm_set1_ps(error_threshold);
|
||||
|
||||
int i;
|
||||
// vectorized code (four at once)
|
||||
for (i = 0; i + 3 < PART_LEN1; i += 4) {
|
||||
const __m128 x_pow_local = _mm_loadu_ps(&x_pow[i]);
|
||||
const __m128 ef_re_base = _mm_loadu_ps(&ef[0][i]);
|
||||
const __m128 ef_im_base = _mm_loadu_ps(&ef[1][i]);
|
||||
|
||||
const __m128 xPowPlus = _mm_add_ps(x_pow_local, k1e_10f);
|
||||
__m128 ef_re = _mm_div_ps(ef_re_base, xPowPlus);
|
||||
__m128 ef_im = _mm_div_ps(ef_im_base, xPowPlus);
|
||||
const __m128 ef_re2 = _mm_mul_ps(ef_re, ef_re);
|
||||
const __m128 ef_im2 = _mm_mul_ps(ef_im, ef_im);
|
||||
const __m128 ef_sum2 = _mm_add_ps(ef_re2, ef_im2);
|
||||
const __m128 absEf = _mm_sqrt_ps(ef_sum2);
|
||||
const __m128 bigger = _mm_cmpgt_ps(absEf, kThresh);
|
||||
__m128 absEfPlus = _mm_add_ps(absEf, k1e_10f);
|
||||
const __m128 absEfInv = _mm_div_ps(kThresh, absEfPlus);
|
||||
__m128 ef_re_if = _mm_mul_ps(ef_re, absEfInv);
|
||||
__m128 ef_im_if = _mm_mul_ps(ef_im, absEfInv);
|
||||
ef_re_if = _mm_and_ps(bigger, ef_re_if);
|
||||
ef_im_if = _mm_and_ps(bigger, ef_im_if);
|
||||
ef_re = _mm_andnot_ps(bigger, ef_re);
|
||||
ef_im = _mm_andnot_ps(bigger, ef_im);
|
||||
ef_re = _mm_or_ps(ef_re, ef_re_if);
|
||||
ef_im = _mm_or_ps(ef_im, ef_im_if);
|
||||
ef_re = _mm_mul_ps(ef_re, kMu);
|
||||
ef_im = _mm_mul_ps(ef_im, kMu);
|
||||
|
||||
_mm_storeu_ps(&ef[0][i], ef_re);
|
||||
_mm_storeu_ps(&ef[1][i], ef_im);
|
||||
}
|
||||
// scalar code for the remaining items.
|
||||
{
|
||||
for (; i < (PART_LEN1); i++) {
|
||||
float abs_ef;
|
||||
ef[0][i] /= (x_pow[i] + 1e-10f);
|
||||
ef[1][i] /= (x_pow[i] + 1e-10f);
|
||||
abs_ef = sqrtf(ef[0][i] * ef[0][i] + ef[1][i] * ef[1][i]);
|
||||
|
||||
if (abs_ef > error_threshold) {
|
||||
abs_ef = error_threshold / (abs_ef + 1e-10f);
|
||||
ef[0][i] *= abs_ef;
|
||||
ef[1][i] *= abs_ef;
|
||||
}
|
||||
|
||||
// Stepsize factor
|
||||
ef[0][i] *= mu;
|
||||
ef[1][i] *= mu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void FilterAdaptationSSE2(
|
||||
const OouraFft& ooura_fft,
|
||||
int num_partitions,
|
||||
int x_fft_buf_block_pos,
|
||||
float x_fft_buf[2][kExtendedNumPartitions * PART_LEN1],
|
||||
float e_fft[2][PART_LEN1],
|
||||
float h_fft_buf[2][kExtendedNumPartitions * PART_LEN1]) {
|
||||
float fft[PART_LEN2];
|
||||
int i, j;
|
||||
for (i = 0; i < num_partitions; i++) {
|
||||
int xPos = (i + x_fft_buf_block_pos) * (PART_LEN1);
|
||||
int pos = i * PART_LEN1;
|
||||
// Check for wrap
|
||||
if (i + x_fft_buf_block_pos >= num_partitions) {
|
||||
xPos -= num_partitions * PART_LEN1;
|
||||
}
|
||||
|
||||
// Process the whole array...
|
||||
for (j = 0; j < PART_LEN; j += 4) {
|
||||
// Load x_fft_buf and e_fft.
|
||||
const __m128 x_fft_buf_re = _mm_loadu_ps(&x_fft_buf[0][xPos + j]);
|
||||
const __m128 x_fft_buf_im = _mm_loadu_ps(&x_fft_buf[1][xPos + j]);
|
||||
const __m128 e_fft_re = _mm_loadu_ps(&e_fft[0][j]);
|
||||
const __m128 e_fft_im = _mm_loadu_ps(&e_fft[1][j]);
|
||||
// Calculate the product of conjugate(x_fft_buf) by e_fft.
|
||||
// re(conjugate(a) * b) = aRe * bRe + aIm * bIm
|
||||
// im(conjugate(a) * b)= aRe * bIm - aIm * bRe
|
||||
const __m128 a = _mm_mul_ps(x_fft_buf_re, e_fft_re);
|
||||
const __m128 b = _mm_mul_ps(x_fft_buf_im, e_fft_im);
|
||||
const __m128 c = _mm_mul_ps(x_fft_buf_re, e_fft_im);
|
||||
const __m128 d = _mm_mul_ps(x_fft_buf_im, e_fft_re);
|
||||
const __m128 e = _mm_add_ps(a, b);
|
||||
const __m128 f = _mm_sub_ps(c, d);
|
||||
// Interleave real and imaginary parts.
|
||||
const __m128 g = _mm_unpacklo_ps(e, f);
|
||||
const __m128 h = _mm_unpackhi_ps(e, f);
|
||||
// Store
|
||||
_mm_storeu_ps(&fft[2 * j + 0], g);
|
||||
_mm_storeu_ps(&fft[2 * j + 4], h);
|
||||
}
|
||||
// ... and fixup the first imaginary entry.
|
||||
fft[1] =
|
||||
MulRe(x_fft_buf[0][xPos + PART_LEN], -x_fft_buf[1][xPos + PART_LEN],
|
||||
e_fft[0][PART_LEN], e_fft[1][PART_LEN]);
|
||||
|
||||
ooura_fft.InverseFft(fft);
|
||||
memset(fft + PART_LEN, 0, sizeof(float) * PART_LEN);
|
||||
|
||||
// fft scaling
|
||||
{
|
||||
float scale = 2.0f / PART_LEN2;
|
||||
const __m128 scale_ps = _mm_load_ps1(&scale);
|
||||
for (j = 0; j < PART_LEN; j += 4) {
|
||||
const __m128 fft_ps = _mm_loadu_ps(&fft[j]);
|
||||
const __m128 fft_scale = _mm_mul_ps(fft_ps, scale_ps);
|
||||
_mm_storeu_ps(&fft[j], fft_scale);
|
||||
}
|
||||
}
|
||||
ooura_fft.Fft(fft);
|
||||
|
||||
{
|
||||
float wt1 = h_fft_buf[1][pos];
|
||||
h_fft_buf[0][pos + PART_LEN] += fft[1];
|
||||
for (j = 0; j < PART_LEN; j += 4) {
|
||||
__m128 wtBuf_re = _mm_loadu_ps(&h_fft_buf[0][pos + j]);
|
||||
__m128 wtBuf_im = _mm_loadu_ps(&h_fft_buf[1][pos + j]);
|
||||
const __m128 fft0 = _mm_loadu_ps(&fft[2 * j + 0]);
|
||||
const __m128 fft4 = _mm_loadu_ps(&fft[2 * j + 4]);
|
||||
const __m128 fft_re =
|
||||
_mm_shuffle_ps(fft0, fft4, _MM_SHUFFLE(2, 0, 2, 0));
|
||||
const __m128 fft_im =
|
||||
_mm_shuffle_ps(fft0, fft4, _MM_SHUFFLE(3, 1, 3, 1));
|
||||
wtBuf_re = _mm_add_ps(wtBuf_re, fft_re);
|
||||
wtBuf_im = _mm_add_ps(wtBuf_im, fft_im);
|
||||
_mm_storeu_ps(&h_fft_buf[0][pos + j], wtBuf_re);
|
||||
_mm_storeu_ps(&h_fft_buf[1][pos + j], wtBuf_im);
|
||||
}
|
||||
h_fft_buf[1][pos] = wt1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static __m128 mm_pow_ps(__m128 a, __m128 b) {
|
||||
// a^b = exp2(b * log2(a))
|
||||
// exp2(x) and log2(x) are calculated using polynomial approximations.
|
||||
__m128 log2_a, b_log2_a, a_exp_b;
|
||||
|
||||
// Calculate log2(x), x = a.
|
||||
{
|
||||
// To calculate log2(x), we decompose x like this:
|
||||
// x = y * 2^n
|
||||
// n is an integer
|
||||
// y is in the [1.0, 2.0) range
|
||||
//
|
||||
// log2(x) = log2(y) + n
|
||||
// n can be evaluated by playing with float representation.
|
||||
// log2(y) in a small range can be approximated, this code uses an order
|
||||
// five polynomial approximation. The coefficients have been
|
||||
// estimated with the Remez algorithm and the resulting
|
||||
// polynomial has a maximum relative error of 0.00086%.
|
||||
|
||||
// Compute n.
|
||||
// This is done by masking the exponent, shifting it into the top bit of
|
||||
// the mantissa, putting eight into the biased exponent (to shift/
|
||||
// compensate the fact that the exponent has been shifted in the top/
|
||||
// fractional part and finally getting rid of the implicit leading one
|
||||
// from the mantissa by substracting it out.
|
||||
static const ALIGN16_BEG int float_exponent_mask[4] ALIGN16_END = {
|
||||
0x7F800000, 0x7F800000, 0x7F800000, 0x7F800000};
|
||||
static const ALIGN16_BEG int eight_biased_exponent[4] ALIGN16_END = {
|
||||
0x43800000, 0x43800000, 0x43800000, 0x43800000};
|
||||
static const ALIGN16_BEG int implicit_leading_one[4] ALIGN16_END = {
|
||||
0x43BF8000, 0x43BF8000, 0x43BF8000, 0x43BF8000};
|
||||
static const int shift_exponent_into_top_mantissa = 8;
|
||||
const __m128 two_n =
|
||||
_mm_and_ps(a, *(reinterpret_cast<const __m128*>(float_exponent_mask)));
|
||||
const __m128 n_1 = _mm_castsi128_ps(_mm_srli_epi32(
|
||||
_mm_castps_si128(two_n), shift_exponent_into_top_mantissa));
|
||||
const __m128 n_0 =
|
||||
_mm_or_ps(n_1, *(reinterpret_cast<const __m128*>(eight_biased_exponent)));
|
||||
const __m128 n =
|
||||
_mm_sub_ps(n_0, *(reinterpret_cast<const __m128*>(implicit_leading_one)));
|
||||
|
||||
// Compute y.
|
||||
static const ALIGN16_BEG int mantissa_mask[4] ALIGN16_END = {
|
||||
0x007FFFFF, 0x007FFFFF, 0x007FFFFF, 0x007FFFFF};
|
||||
static const ALIGN16_BEG int zero_biased_exponent_is_one[4] ALIGN16_END = {
|
||||
0x3F800000, 0x3F800000, 0x3F800000, 0x3F800000};
|
||||
const __m128 mantissa =
|
||||
_mm_and_ps(a, *(reinterpret_cast<const __m128*>(mantissa_mask)));
|
||||
const __m128 y =
|
||||
_mm_or_ps(mantissa,
|
||||
*(reinterpret_cast<const __m128*>(zero_biased_exponent_is_one)));
|
||||
|
||||
// Approximate log2(y) ~= (y - 1) * pol5(y).
|
||||
// pol5(y) = C5 * y^5 + C4 * y^4 + C3 * y^3 + C2 * y^2 + C1 * y + C0
|
||||
static const ALIGN16_BEG float ALIGN16_END C5[4] = {
|
||||
-3.4436006e-2f, -3.4436006e-2f, -3.4436006e-2f, -3.4436006e-2f};
|
||||
static const ALIGN16_BEG float ALIGN16_END C4[4] = {
|
||||
3.1821337e-1f, 3.1821337e-1f, 3.1821337e-1f, 3.1821337e-1f};
|
||||
static const ALIGN16_BEG float ALIGN16_END C3[4] = {
|
||||
-1.2315303f, -1.2315303f, -1.2315303f, -1.2315303f};
|
||||
static const ALIGN16_BEG float ALIGN16_END C2[4] = {2.5988452f, 2.5988452f,
|
||||
2.5988452f, 2.5988452f};
|
||||
static const ALIGN16_BEG float ALIGN16_END C1[4] = {
|
||||
-3.3241990f, -3.3241990f, -3.3241990f, -3.3241990f};
|
||||
static const ALIGN16_BEG float ALIGN16_END C0[4] = {3.1157899f, 3.1157899f,
|
||||
3.1157899f, 3.1157899f};
|
||||
const __m128 pol5_y_0 =
|
||||
_mm_mul_ps(y, *(reinterpret_cast<const __m128*>(C5)));
|
||||
const __m128 pol5_y_1 =
|
||||
_mm_add_ps(pol5_y_0, *(reinterpret_cast<const __m128*>(C4)));
|
||||
const __m128 pol5_y_2 = _mm_mul_ps(pol5_y_1, y);
|
||||
const __m128 pol5_y_3 =
|
||||
_mm_add_ps(pol5_y_2, *(reinterpret_cast<const __m128*>(C3)));
|
||||
const __m128 pol5_y_4 = _mm_mul_ps(pol5_y_3, y);
|
||||
const __m128 pol5_y_5 =
|
||||
_mm_add_ps(pol5_y_4, *(reinterpret_cast<const __m128*>(C2)));
|
||||
const __m128 pol5_y_6 = _mm_mul_ps(pol5_y_5, y);
|
||||
const __m128 pol5_y_7 =
|
||||
_mm_add_ps(pol5_y_6, *(reinterpret_cast<const __m128*>(C1)));
|
||||
const __m128 pol5_y_8 = _mm_mul_ps(pol5_y_7, y);
|
||||
const __m128 pol5_y =
|
||||
_mm_add_ps(pol5_y_8, *(reinterpret_cast<const __m128*>(C0)));
|
||||
const __m128 y_minus_one =
|
||||
_mm_sub_ps(y,
|
||||
*(reinterpret_cast<const __m128*>(zero_biased_exponent_is_one)));
|
||||
const __m128 log2_y = _mm_mul_ps(y_minus_one, pol5_y);
|
||||
|
||||
// Combine parts.
|
||||
log2_a = _mm_add_ps(n, log2_y);
|
||||
}
|
||||
|
||||
// b * log2(a)
|
||||
b_log2_a = _mm_mul_ps(b, log2_a);
|
||||
|
||||
// Calculate exp2(x), x = b * log2(a).
|
||||
{
|
||||
// To calculate 2^x, we decompose x like this:
|
||||
// x = n + y
|
||||
// n is an integer, the value of x - 0.5 rounded down, therefore
|
||||
// y is in the [0.5, 1.5) range
|
||||
//
|
||||
// 2^x = 2^n * 2^y
|
||||
// 2^n can be evaluated by playing with float representation.
|
||||
// 2^y in a small range can be approximated, this code uses an order two
|
||||
// polynomial approximation. The coefficients have been estimated
|
||||
// with the Remez algorithm and the resulting polynomial has a
|
||||
// maximum relative error of 0.17%.
|
||||
|
||||
// To avoid over/underflow, we reduce the range of input to ]-127, 129].
|
||||
static const ALIGN16_BEG float max_input[4] ALIGN16_END = {129.f, 129.f,
|
||||
129.f, 129.f};
|
||||
static const ALIGN16_BEG float min_input[4] ALIGN16_END = {
|
||||
-126.99999f, -126.99999f, -126.99999f, -126.99999f};
|
||||
const __m128 x_min =
|
||||
_mm_min_ps(b_log2_a, *(reinterpret_cast<const __m128*>(max_input)));
|
||||
const __m128 x_max =
|
||||
_mm_max_ps(x_min, *(reinterpret_cast<const __m128*>(min_input)));
|
||||
// Compute n.
|
||||
static const ALIGN16_BEG float half[4] ALIGN16_END = {0.5f, 0.5f, 0.5f,
|
||||
0.5f};
|
||||
const __m128 x_minus_half =
|
||||
_mm_sub_ps(x_max, *(reinterpret_cast<const __m128*>(half)));
|
||||
const __m128i x_minus_half_floor = _mm_cvtps_epi32(x_minus_half);
|
||||
// Compute 2^n.
|
||||
static const ALIGN16_BEG int float_exponent_bias[4] ALIGN16_END = {
|
||||
127, 127, 127, 127};
|
||||
static const int float_exponent_shift = 23;
|
||||
const __m128i two_n_exponent =
|
||||
_mm_add_epi32(x_minus_half_floor,
|
||||
*(reinterpret_cast<const __m128i*>(float_exponent_bias)));
|
||||
const __m128 two_n =
|
||||
_mm_castsi128_ps(_mm_slli_epi32(two_n_exponent, float_exponent_shift));
|
||||
// Compute y.
|
||||
const __m128 y = _mm_sub_ps(x_max, _mm_cvtepi32_ps(x_minus_half_floor));
|
||||
// Approximate 2^y ~= C2 * y^2 + C1 * y + C0.
|
||||
static const ALIGN16_BEG float C2[4] ALIGN16_END = {
|
||||
3.3718944e-1f, 3.3718944e-1f, 3.3718944e-1f, 3.3718944e-1f};
|
||||
static const ALIGN16_BEG float C1[4] ALIGN16_END = {
|
||||
6.5763628e-1f, 6.5763628e-1f, 6.5763628e-1f, 6.5763628e-1f};
|
||||
static const ALIGN16_BEG float C0[4] ALIGN16_END = {1.0017247f, 1.0017247f,
|
||||
1.0017247f, 1.0017247f};
|
||||
const __m128 exp2_y_0 =
|
||||
_mm_mul_ps(y, *(reinterpret_cast<const __m128*>(C2)));
|
||||
const __m128 exp2_y_1 =
|
||||
_mm_add_ps(exp2_y_0, *(reinterpret_cast<const __m128*>(C1)));
|
||||
const __m128 exp2_y_2 = _mm_mul_ps(exp2_y_1, y);
|
||||
const __m128 exp2_y =
|
||||
_mm_add_ps(exp2_y_2, *(reinterpret_cast<const __m128*>(C0)));
|
||||
|
||||
// Combine parts.
|
||||
a_exp_b = _mm_mul_ps(exp2_y, two_n);
|
||||
}
|
||||
return a_exp_b;
|
||||
}
|
||||
|
||||
static void OverdriveSSE2(float overdrive_scaling,
|
||||
float hNlFb,
|
||||
float hNl[PART_LEN1]) {
|
||||
int i;
|
||||
const __m128 vec_hNlFb = _mm_set1_ps(hNlFb);
|
||||
const __m128 vec_one = _mm_set1_ps(1.0f);
|
||||
const __m128 vec_overdrive_scaling = _mm_set1_ps(overdrive_scaling);
|
||||
// vectorized code (four at once)
|
||||
for (i = 0; i + 3 < PART_LEN1; i += 4) {
|
||||
// Weight subbands
|
||||
__m128 vec_hNl = _mm_loadu_ps(&hNl[i]);
|
||||
const __m128 vec_weightCurve = _mm_loadu_ps(&WebRtcAec_weightCurve[i]);
|
||||
const __m128 bigger = _mm_cmpgt_ps(vec_hNl, vec_hNlFb);
|
||||
const __m128 vec_weightCurve_hNlFb = _mm_mul_ps(vec_weightCurve, vec_hNlFb);
|
||||
const __m128 vec_one_weightCurve = _mm_sub_ps(vec_one, vec_weightCurve);
|
||||
const __m128 vec_one_weightCurve_hNl =
|
||||
_mm_mul_ps(vec_one_weightCurve, vec_hNl);
|
||||
const __m128 vec_if0 = _mm_andnot_ps(bigger, vec_hNl);
|
||||
const __m128 vec_if1 = _mm_and_ps(
|
||||
bigger, _mm_add_ps(vec_weightCurve_hNlFb, vec_one_weightCurve_hNl));
|
||||
vec_hNl = _mm_or_ps(vec_if0, vec_if1);
|
||||
|
||||
const __m128 vec_overDriveCurve =
|
||||
_mm_loadu_ps(&WebRtcAec_overDriveCurve[i]);
|
||||
const __m128 vec_overDriveSm_overDriveCurve =
|
||||
_mm_mul_ps(vec_overdrive_scaling, vec_overDriveCurve);
|
||||
vec_hNl = mm_pow_ps(vec_hNl, vec_overDriveSm_overDriveCurve);
|
||||
_mm_storeu_ps(&hNl[i], vec_hNl);
|
||||
}
|
||||
// scalar code for the remaining items.
|
||||
for (; i < PART_LEN1; i++) {
|
||||
// Weight subbands
|
||||
if (hNl[i] > hNlFb) {
|
||||
hNl[i] = WebRtcAec_weightCurve[i] * hNlFb +
|
||||
(1 - WebRtcAec_weightCurve[i]) * hNl[i];
|
||||
}
|
||||
hNl[i] = powf(hNl[i], overdrive_scaling * WebRtcAec_overDriveCurve[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void SuppressSSE2(const float hNl[PART_LEN1], float efw[2][PART_LEN1]) {
|
||||
int i;
|
||||
const __m128 vec_minus_one = _mm_set1_ps(-1.0f);
|
||||
// vectorized code (four at once)
|
||||
for (i = 0; i + 3 < PART_LEN1; i += 4) {
|
||||
// Suppress error signal
|
||||
__m128 vec_hNl = _mm_loadu_ps(&hNl[i]);
|
||||
__m128 vec_efw_re = _mm_loadu_ps(&efw[0][i]);
|
||||
__m128 vec_efw_im = _mm_loadu_ps(&efw[1][i]);
|
||||
vec_efw_re = _mm_mul_ps(vec_efw_re, vec_hNl);
|
||||
vec_efw_im = _mm_mul_ps(vec_efw_im, vec_hNl);
|
||||
|
||||
// Ooura fft returns incorrect sign on imaginary component. It matters
|
||||
// here because we are making an additive change with comfort noise.
|
||||
vec_efw_im = _mm_mul_ps(vec_efw_im, vec_minus_one);
|
||||
_mm_storeu_ps(&efw[0][i], vec_efw_re);
|
||||
_mm_storeu_ps(&efw[1][i], vec_efw_im);
|
||||
}
|
||||
// scalar code for the remaining items.
|
||||
for (; i < PART_LEN1; i++) {
|
||||
// Suppress error signal
|
||||
efw[0][i] *= hNl[i];
|
||||
efw[1][i] *= hNl[i];
|
||||
|
||||
// Ooura fft returns incorrect sign on imaginary component. It matters
|
||||
// here because we are making an additive change with comfort noise.
|
||||
efw[1][i] *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
__inline static void _mm_add_ps_4x1(__m128 sum, float* dst) {
|
||||
// A+B C+D
|
||||
sum = _mm_add_ps(sum, _mm_shuffle_ps(sum, sum, _MM_SHUFFLE(0, 0, 3, 2)));
|
||||
// A+B+C+D A+B+C+D
|
||||
sum = _mm_add_ps(sum, _mm_shuffle_ps(sum, sum, _MM_SHUFFLE(1, 1, 1, 1)));
|
||||
_mm_store_ss(dst, sum);
|
||||
}
|
||||
|
||||
static int PartitionDelaySSE2(
|
||||
int num_partitions,
|
||||
float h_fft_buf[2][kExtendedNumPartitions * PART_LEN1]) {
|
||||
// Measures the energy in each filter partition and returns the partition with
|
||||
// highest energy.
|
||||
// TODO(bjornv): Spread computational cost by computing one partition per
|
||||
// block?
|
||||
float wfEnMax = 0;
|
||||
int i;
|
||||
int delay = 0;
|
||||
|
||||
for (i = 0; i < num_partitions; i++) {
|
||||
int j;
|
||||
int pos = i * PART_LEN1;
|
||||
float wfEn = 0;
|
||||
__m128 vec_wfEn = _mm_set1_ps(0.0f);
|
||||
// vectorized code (four at once)
|
||||
for (j = 0; j + 3 < PART_LEN1; j += 4) {
|
||||
const __m128 vec_wfBuf0 = _mm_loadu_ps(&h_fft_buf[0][pos + j]);
|
||||
const __m128 vec_wfBuf1 = _mm_loadu_ps(&h_fft_buf[1][pos + j]);
|
||||
vec_wfEn = _mm_add_ps(vec_wfEn, _mm_mul_ps(vec_wfBuf0, vec_wfBuf0));
|
||||
vec_wfEn = _mm_add_ps(vec_wfEn, _mm_mul_ps(vec_wfBuf1, vec_wfBuf1));
|
||||
}
|
||||
_mm_add_ps_4x1(vec_wfEn, &wfEn);
|
||||
|
||||
// scalar code for the remaining items.
|
||||
for (; j < PART_LEN1; j++) {
|
||||
wfEn += h_fft_buf[0][pos + j] * h_fft_buf[0][pos + j] +
|
||||
h_fft_buf[1][pos + j] * h_fft_buf[1][pos + j];
|
||||
}
|
||||
|
||||
if (wfEn > wfEnMax) {
|
||||
wfEnMax = wfEn;
|
||||
delay = i;
|
||||
}
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
// Updates the following smoothed Power Spectral Densities (PSD):
|
||||
// - sd : near-end
|
||||
// - se : residual echo
|
||||
// - sx : far-end
|
||||
// - sde : cross-PSD of near-end and residual echo
|
||||
// - sxd : cross-PSD of near-end and far-end
|
||||
//
|
||||
// In addition to updating the PSDs, also the filter diverge state is determined
|
||||
// upon actions are taken.
|
||||
static void UpdateCoherenceSpectraSSE2(int mult,
|
||||
bool extended_filter_enabled,
|
||||
float efw[2][PART_LEN1],
|
||||
float dfw[2][PART_LEN1],
|
||||
float xfw[2][PART_LEN1],
|
||||
CoherenceState* coherence_state,
|
||||
short* filter_divergence_state,
|
||||
int* extreme_filter_divergence) {
|
||||
// Power estimate smoothing coefficients.
|
||||
const float* ptrGCoh =
|
||||
extended_filter_enabled
|
||||
? WebRtcAec_kExtendedSmoothingCoefficients[mult - 1]
|
||||
: WebRtcAec_kNormalSmoothingCoefficients[mult - 1];
|
||||
int i;
|
||||
float sdSum = 0, seSum = 0;
|
||||
const __m128 vec_15 = _mm_set1_ps(WebRtcAec_kMinFarendPSD);
|
||||
const __m128 vec_GCoh0 = _mm_set1_ps(ptrGCoh[0]);
|
||||
const __m128 vec_GCoh1 = _mm_set1_ps(ptrGCoh[1]);
|
||||
__m128 vec_sdSum = _mm_set1_ps(0.0f);
|
||||
__m128 vec_seSum = _mm_set1_ps(0.0f);
|
||||
|
||||
for (i = 0; i + 3 < PART_LEN1; i += 4) {
|
||||
const __m128 vec_dfw0 = _mm_loadu_ps(&dfw[0][i]);
|
||||
const __m128 vec_dfw1 = _mm_loadu_ps(&dfw[1][i]);
|
||||
const __m128 vec_efw0 = _mm_loadu_ps(&efw[0][i]);
|
||||
const __m128 vec_efw1 = _mm_loadu_ps(&efw[1][i]);
|
||||
const __m128 vec_xfw0 = _mm_loadu_ps(&xfw[0][i]);
|
||||
const __m128 vec_xfw1 = _mm_loadu_ps(&xfw[1][i]);
|
||||
__m128 vec_sd =
|
||||
_mm_mul_ps(_mm_loadu_ps(&coherence_state->sd[i]), vec_GCoh0);
|
||||
__m128 vec_se =
|
||||
_mm_mul_ps(_mm_loadu_ps(&coherence_state->se[i]), vec_GCoh0);
|
||||
__m128 vec_sx =
|
||||
_mm_mul_ps(_mm_loadu_ps(&coherence_state->sx[i]), vec_GCoh0);
|
||||
__m128 vec_dfw_sumsq = _mm_mul_ps(vec_dfw0, vec_dfw0);
|
||||
__m128 vec_efw_sumsq = _mm_mul_ps(vec_efw0, vec_efw0);
|
||||
__m128 vec_xfw_sumsq = _mm_mul_ps(vec_xfw0, vec_xfw0);
|
||||
vec_dfw_sumsq = _mm_add_ps(vec_dfw_sumsq, _mm_mul_ps(vec_dfw1, vec_dfw1));
|
||||
vec_efw_sumsq = _mm_add_ps(vec_efw_sumsq, _mm_mul_ps(vec_efw1, vec_efw1));
|
||||
vec_xfw_sumsq = _mm_add_ps(vec_xfw_sumsq, _mm_mul_ps(vec_xfw1, vec_xfw1));
|
||||
vec_xfw_sumsq = _mm_max_ps(vec_xfw_sumsq, vec_15);
|
||||
vec_sd = _mm_add_ps(vec_sd, _mm_mul_ps(vec_dfw_sumsq, vec_GCoh1));
|
||||
vec_se = _mm_add_ps(vec_se, _mm_mul_ps(vec_efw_sumsq, vec_GCoh1));
|
||||
vec_sx = _mm_add_ps(vec_sx, _mm_mul_ps(vec_xfw_sumsq, vec_GCoh1));
|
||||
_mm_storeu_ps(&coherence_state->sd[i], vec_sd);
|
||||
_mm_storeu_ps(&coherence_state->se[i], vec_se);
|
||||
_mm_storeu_ps(&coherence_state->sx[i], vec_sx);
|
||||
|
||||
{
|
||||
const __m128 vec_3210 = _mm_loadu_ps(&coherence_state->sde[i][0]);
|
||||
const __m128 vec_7654 = _mm_loadu_ps(&coherence_state->sde[i + 2][0]);
|
||||
__m128 vec_a =
|
||||
_mm_shuffle_ps(vec_3210, vec_7654, _MM_SHUFFLE(2, 0, 2, 0));
|
||||
__m128 vec_b =
|
||||
_mm_shuffle_ps(vec_3210, vec_7654, _MM_SHUFFLE(3, 1, 3, 1));
|
||||
__m128 vec_dfwefw0011 = _mm_mul_ps(vec_dfw0, vec_efw0);
|
||||
__m128 vec_dfwefw0110 = _mm_mul_ps(vec_dfw0, vec_efw1);
|
||||
vec_a = _mm_mul_ps(vec_a, vec_GCoh0);
|
||||
vec_b = _mm_mul_ps(vec_b, vec_GCoh0);
|
||||
vec_dfwefw0011 =
|
||||
_mm_add_ps(vec_dfwefw0011, _mm_mul_ps(vec_dfw1, vec_efw1));
|
||||
vec_dfwefw0110 =
|
||||
_mm_sub_ps(vec_dfwefw0110, _mm_mul_ps(vec_dfw1, vec_efw0));
|
||||
vec_a = _mm_add_ps(vec_a, _mm_mul_ps(vec_dfwefw0011, vec_GCoh1));
|
||||
vec_b = _mm_add_ps(vec_b, _mm_mul_ps(vec_dfwefw0110, vec_GCoh1));
|
||||
_mm_storeu_ps(&coherence_state->sde[i][0], _mm_unpacklo_ps(vec_a, vec_b));
|
||||
_mm_storeu_ps(&coherence_state->sde[i + 2][0],
|
||||
_mm_unpackhi_ps(vec_a, vec_b));
|
||||
}
|
||||
|
||||
{
|
||||
const __m128 vec_3210 = _mm_loadu_ps(&coherence_state->sxd[i][0]);
|
||||
const __m128 vec_7654 = _mm_loadu_ps(&coherence_state->sxd[i + 2][0]);
|
||||
__m128 vec_a =
|
||||
_mm_shuffle_ps(vec_3210, vec_7654, _MM_SHUFFLE(2, 0, 2, 0));
|
||||
__m128 vec_b =
|
||||
_mm_shuffle_ps(vec_3210, vec_7654, _MM_SHUFFLE(3, 1, 3, 1));
|
||||
__m128 vec_dfwxfw0011 = _mm_mul_ps(vec_dfw0, vec_xfw0);
|
||||
__m128 vec_dfwxfw0110 = _mm_mul_ps(vec_dfw0, vec_xfw1);
|
||||
vec_a = _mm_mul_ps(vec_a, vec_GCoh0);
|
||||
vec_b = _mm_mul_ps(vec_b, vec_GCoh0);
|
||||
vec_dfwxfw0011 =
|
||||
_mm_add_ps(vec_dfwxfw0011, _mm_mul_ps(vec_dfw1, vec_xfw1));
|
||||
vec_dfwxfw0110 =
|
||||
_mm_sub_ps(vec_dfwxfw0110, _mm_mul_ps(vec_dfw1, vec_xfw0));
|
||||
vec_a = _mm_add_ps(vec_a, _mm_mul_ps(vec_dfwxfw0011, vec_GCoh1));
|
||||
vec_b = _mm_add_ps(vec_b, _mm_mul_ps(vec_dfwxfw0110, vec_GCoh1));
|
||||
_mm_storeu_ps(&coherence_state->sxd[i][0], _mm_unpacklo_ps(vec_a, vec_b));
|
||||
_mm_storeu_ps(&coherence_state->sxd[i + 2][0],
|
||||
_mm_unpackhi_ps(vec_a, vec_b));
|
||||
}
|
||||
|
||||
vec_sdSum = _mm_add_ps(vec_sdSum, vec_sd);
|
||||
vec_seSum = _mm_add_ps(vec_seSum, vec_se);
|
||||
}
|
||||
|
||||
_mm_add_ps_4x1(vec_sdSum, &sdSum);
|
||||
_mm_add_ps_4x1(vec_seSum, &seSum);
|
||||
|
||||
for (; i < PART_LEN1; i++) {
|
||||
coherence_state->sd[i] =
|
||||
ptrGCoh[0] * coherence_state->sd[i] +
|
||||
ptrGCoh[1] * (dfw[0][i] * dfw[0][i] + dfw[1][i] * dfw[1][i]);
|
||||
coherence_state->se[i] =
|
||||
ptrGCoh[0] * coherence_state->se[i] +
|
||||
ptrGCoh[1] * (efw[0][i] * efw[0][i] + efw[1][i] * efw[1][i]);
|
||||
// We threshold here to protect against the ill-effects of a zero farend.
|
||||
// The threshold is not arbitrarily chosen, but balances protection and
|
||||
// adverse interaction with the algorithm's tuning.
|
||||
// TODO(bjornv): investigate further why this is so sensitive.
|
||||
coherence_state->sx[i] =
|
||||
ptrGCoh[0] * coherence_state->sx[i] +
|
||||
ptrGCoh[1] *
|
||||
WEBRTC_SPL_MAX(xfw[0][i] * xfw[0][i] + xfw[1][i] * xfw[1][i],
|
||||
WebRtcAec_kMinFarendPSD);
|
||||
|
||||
coherence_state->sde[i][0] =
|
||||
ptrGCoh[0] * coherence_state->sde[i][0] +
|
||||
ptrGCoh[1] * (dfw[0][i] * efw[0][i] + dfw[1][i] * efw[1][i]);
|
||||
coherence_state->sde[i][1] =
|
||||
ptrGCoh[0] * coherence_state->sde[i][1] +
|
||||
ptrGCoh[1] * (dfw[0][i] * efw[1][i] - dfw[1][i] * efw[0][i]);
|
||||
|
||||
coherence_state->sxd[i][0] =
|
||||
ptrGCoh[0] * coherence_state->sxd[i][0] +
|
||||
ptrGCoh[1] * (dfw[0][i] * xfw[0][i] + dfw[1][i] * xfw[1][i]);
|
||||
coherence_state->sxd[i][1] =
|
||||
ptrGCoh[0] * coherence_state->sxd[i][1] +
|
||||
ptrGCoh[1] * (dfw[0][i] * xfw[1][i] - dfw[1][i] * xfw[0][i]);
|
||||
|
||||
sdSum += coherence_state->sd[i];
|
||||
seSum += coherence_state->se[i];
|
||||
}
|
||||
|
||||
// Divergent filter safeguard update.
|
||||
*filter_divergence_state =
|
||||
(*filter_divergence_state ? 1.05f : 1.0f) * seSum > sdSum;
|
||||
|
||||
// Signal extreme filter divergence if the error is significantly larger
|
||||
// than the nearend (13 dB).
|
||||
*extreme_filter_divergence = (seSum > (19.95f * sdSum));
|
||||
}
|
||||
|
||||
// Window time domain data to be used by the fft.
|
||||
static void WindowDataSSE2(float* x_windowed, const float* x) {
|
||||
int i;
|
||||
for (i = 0; i < PART_LEN; i += 4) {
|
||||
const __m128 vec_Buf1 = _mm_loadu_ps(&x[i]);
|
||||
const __m128 vec_Buf2 = _mm_loadu_ps(&x[PART_LEN + i]);
|
||||
const __m128 vec_sqrtHanning = _mm_load_ps(&WebRtcAec_sqrtHanning[i]);
|
||||
// A B C D
|
||||
__m128 vec_sqrtHanning_rev =
|
||||
_mm_loadu_ps(&WebRtcAec_sqrtHanning[PART_LEN - i - 3]);
|
||||
// D C B A
|
||||
vec_sqrtHanning_rev = _mm_shuffle_ps(
|
||||
vec_sqrtHanning_rev, vec_sqrtHanning_rev, _MM_SHUFFLE(0, 1, 2, 3));
|
||||
_mm_storeu_ps(&x_windowed[i], _mm_mul_ps(vec_Buf1, vec_sqrtHanning));
|
||||
_mm_storeu_ps(&x_windowed[PART_LEN + i],
|
||||
_mm_mul_ps(vec_Buf2, vec_sqrtHanning_rev));
|
||||
}
|
||||
}
|
||||
|
||||
// Puts fft output data into a complex valued array.
|
||||
static void StoreAsComplexSSE2(const float* data,
|
||||
float data_complex[2][PART_LEN1]) {
|
||||
int i;
|
||||
for (i = 0; i < PART_LEN; i += 4) {
|
||||
const __m128 vec_fft0 = _mm_loadu_ps(&data[2 * i]);
|
||||
const __m128 vec_fft4 = _mm_loadu_ps(&data[2 * i + 4]);
|
||||
const __m128 vec_a =
|
||||
_mm_shuffle_ps(vec_fft0, vec_fft4, _MM_SHUFFLE(2, 0, 2, 0));
|
||||
const __m128 vec_b =
|
||||
_mm_shuffle_ps(vec_fft0, vec_fft4, _MM_SHUFFLE(3, 1, 3, 1));
|
||||
_mm_storeu_ps(&data_complex[0][i], vec_a);
|
||||
_mm_storeu_ps(&data_complex[1][i], vec_b);
|
||||
}
|
||||
// fix beginning/end values
|
||||
data_complex[1][0] = 0;
|
||||
data_complex[1][PART_LEN] = 0;
|
||||
data_complex[0][0] = data[0];
|
||||
data_complex[0][PART_LEN] = data[1];
|
||||
}
|
||||
|
||||
static void ComputeCoherenceSSE2(const CoherenceState* coherence_state,
|
||||
float* cohde,
|
||||
float* cohxd) {
|
||||
int i;
|
||||
|
||||
{
|
||||
const __m128 vec_1eminus10 = _mm_set1_ps(1e-10f);
|
||||
|
||||
// Subband coherence
|
||||
for (i = 0; i + 3 < PART_LEN1; i += 4) {
|
||||
const __m128 vec_sd = _mm_loadu_ps(&coherence_state->sd[i]);
|
||||
const __m128 vec_se = _mm_loadu_ps(&coherence_state->se[i]);
|
||||
const __m128 vec_sx = _mm_loadu_ps(&coherence_state->sx[i]);
|
||||
const __m128 vec_sdse =
|
||||
_mm_add_ps(vec_1eminus10, _mm_mul_ps(vec_sd, vec_se));
|
||||
const __m128 vec_sdsx =
|
||||
_mm_add_ps(vec_1eminus10, _mm_mul_ps(vec_sd, vec_sx));
|
||||
const __m128 vec_sde_3210 = _mm_loadu_ps(&coherence_state->sde[i][0]);
|
||||
const __m128 vec_sde_7654 = _mm_loadu_ps(&coherence_state->sde[i + 2][0]);
|
||||
const __m128 vec_sxd_3210 = _mm_loadu_ps(&coherence_state->sxd[i][0]);
|
||||
const __m128 vec_sxd_7654 = _mm_loadu_ps(&coherence_state->sxd[i + 2][0]);
|
||||
const __m128 vec_sde_0 =
|
||||
_mm_shuffle_ps(vec_sde_3210, vec_sde_7654, _MM_SHUFFLE(2, 0, 2, 0));
|
||||
const __m128 vec_sde_1 =
|
||||
_mm_shuffle_ps(vec_sde_3210, vec_sde_7654, _MM_SHUFFLE(3, 1, 3, 1));
|
||||
const __m128 vec_sxd_0 =
|
||||
_mm_shuffle_ps(vec_sxd_3210, vec_sxd_7654, _MM_SHUFFLE(2, 0, 2, 0));
|
||||
const __m128 vec_sxd_1 =
|
||||
_mm_shuffle_ps(vec_sxd_3210, vec_sxd_7654, _MM_SHUFFLE(3, 1, 3, 1));
|
||||
__m128 vec_cohde = _mm_mul_ps(vec_sde_0, vec_sde_0);
|
||||
__m128 vec_cohxd = _mm_mul_ps(vec_sxd_0, vec_sxd_0);
|
||||
vec_cohde = _mm_add_ps(vec_cohde, _mm_mul_ps(vec_sde_1, vec_sde_1));
|
||||
vec_cohde = _mm_div_ps(vec_cohde, vec_sdse);
|
||||
vec_cohxd = _mm_add_ps(vec_cohxd, _mm_mul_ps(vec_sxd_1, vec_sxd_1));
|
||||
vec_cohxd = _mm_div_ps(vec_cohxd, vec_sdsx);
|
||||
_mm_storeu_ps(&cohde[i], vec_cohde);
|
||||
_mm_storeu_ps(&cohxd[i], vec_cohxd);
|
||||
}
|
||||
|
||||
// scalar code for the remaining items.
|
||||
for (; i < PART_LEN1; i++) {
|
||||
cohde[i] = (coherence_state->sde[i][0] * coherence_state->sde[i][0] +
|
||||
coherence_state->sde[i][1] * coherence_state->sde[i][1]) /
|
||||
(coherence_state->sd[i] * coherence_state->se[i] + 1e-10f);
|
||||
cohxd[i] = (coherence_state->sxd[i][0] * coherence_state->sxd[i][0] +
|
||||
coherence_state->sxd[i][1] * coherence_state->sxd[i][1]) /
|
||||
(coherence_state->sx[i] * coherence_state->sd[i] + 1e-10f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcAec_InitAec_SSE2(void) {
|
||||
WebRtcAec_FilterFar = FilterFarSSE2;
|
||||
WebRtcAec_ScaleErrorSignal = ScaleErrorSignalSSE2;
|
||||
WebRtcAec_FilterAdaptation = FilterAdaptationSSE2;
|
||||
WebRtcAec_Overdrive = OverdriveSSE2;
|
||||
WebRtcAec_Suppress = SuppressSSE2;
|
||||
WebRtcAec_ComputeCoherence = ComputeCoherenceSSE2;
|
||||
WebRtcAec_UpdateCoherenceSpectra = UpdateCoherenceSpectraSSE2;
|
||||
WebRtcAec_StoreAsComplex = StoreAsComplexSSE2;
|
||||
WebRtcAec_PartitionDelay = PartitionDelaySSE2;
|
||||
WebRtcAec_WindowData = WindowDataSSE2;
|
||||
}
|
||||
} // namespace webrtc
|
||||
@ -1,207 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* Resamples a signal to an arbitrary rate. Used by the AEC to compensate for
|
||||
* clock skew by resampling the farend signal.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec/aec_resampler.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec/aec_core.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
enum { kEstimateLengthFrames = 400 };
|
||||
|
||||
typedef struct {
|
||||
float buffer[kResamplerBufferSize];
|
||||
float position;
|
||||
|
||||
int deviceSampleRateHz;
|
||||
int skewData[kEstimateLengthFrames];
|
||||
int skewDataIndex;
|
||||
float skewEstimate;
|
||||
} AecResampler;
|
||||
|
||||
static int EstimateSkew(const int* rawSkew,
|
||||
int size,
|
||||
int deviceSampleRateHz,
|
||||
float* skewEst);
|
||||
|
||||
void* WebRtcAec_CreateResampler() {
|
||||
return malloc(sizeof(AecResampler));
|
||||
}
|
||||
|
||||
int WebRtcAec_InitResampler(void* resampInst, int deviceSampleRateHz) {
|
||||
AecResampler* obj = static_cast<AecResampler*>(resampInst);
|
||||
memset(obj->buffer, 0, sizeof(obj->buffer));
|
||||
obj->position = 0.0;
|
||||
|
||||
obj->deviceSampleRateHz = deviceSampleRateHz;
|
||||
memset(obj->skewData, 0, sizeof(obj->skewData));
|
||||
obj->skewDataIndex = 0;
|
||||
obj->skewEstimate = 0.0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WebRtcAec_FreeResampler(void* resampInst) {
|
||||
AecResampler* obj = static_cast<AecResampler*>(resampInst);
|
||||
free(obj);
|
||||
}
|
||||
|
||||
void WebRtcAec_ResampleLinear(void* resampInst,
|
||||
const float* inspeech,
|
||||
size_t size,
|
||||
float skew,
|
||||
float* outspeech,
|
||||
size_t* size_out) {
|
||||
AecResampler* obj = static_cast<AecResampler*>(resampInst);
|
||||
|
||||
float* y;
|
||||
float be, tnew;
|
||||
size_t tn, mm;
|
||||
|
||||
RTC_DCHECK_LE(size, 2 * FRAME_LEN);
|
||||
RTC_DCHECK(resampInst);
|
||||
RTC_DCHECK(inspeech);
|
||||
RTC_DCHECK(outspeech);
|
||||
RTC_DCHECK(size_out);
|
||||
|
||||
// Add new frame data in lookahead
|
||||
memcpy(&obj->buffer[FRAME_LEN + kResamplingDelay], inspeech,
|
||||
size * sizeof(inspeech[0]));
|
||||
|
||||
// Sample rate ratio
|
||||
be = 1 + skew;
|
||||
|
||||
// Loop over input frame
|
||||
mm = 0;
|
||||
y = &obj->buffer[FRAME_LEN]; // Point at current frame
|
||||
|
||||
tnew = be * mm + obj->position;
|
||||
tn = (size_t)tnew;
|
||||
|
||||
while (tn < size) {
|
||||
// Interpolation
|
||||
outspeech[mm] = y[tn] + (tnew - tn) * (y[tn + 1] - y[tn]);
|
||||
mm++;
|
||||
|
||||
tnew = be * mm + obj->position;
|
||||
tn = static_cast<int>(tnew);
|
||||
}
|
||||
|
||||
*size_out = mm;
|
||||
obj->position += (*size_out) * be - size;
|
||||
|
||||
// Shift buffer
|
||||
memmove(obj->buffer, &obj->buffer[size],
|
||||
(kResamplerBufferSize - size) * sizeof(obj->buffer[0]));
|
||||
}
|
||||
|
||||
int WebRtcAec_GetSkew(void* resampInst, int rawSkew, float* skewEst) {
|
||||
AecResampler* obj = static_cast<AecResampler*>(resampInst);
|
||||
int err = 0;
|
||||
|
||||
if (obj->skewDataIndex < kEstimateLengthFrames) {
|
||||
obj->skewData[obj->skewDataIndex] = rawSkew;
|
||||
obj->skewDataIndex++;
|
||||
} else if (obj->skewDataIndex == kEstimateLengthFrames) {
|
||||
err = EstimateSkew(obj->skewData, kEstimateLengthFrames,
|
||||
obj->deviceSampleRateHz, skewEst);
|
||||
obj->skewEstimate = *skewEst;
|
||||
obj->skewDataIndex++;
|
||||
} else {
|
||||
*skewEst = obj->skewEstimate;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int EstimateSkew(const int* rawSkew,
|
||||
int size,
|
||||
int deviceSampleRateHz,
|
||||
float* skewEst) {
|
||||
const int absLimitOuter = static_cast<int>(0.04f * deviceSampleRateHz);
|
||||
const int absLimitInner = static_cast<int>(0.0025f * deviceSampleRateHz);
|
||||
int i = 0;
|
||||
int n = 0;
|
||||
float rawAvg = 0;
|
||||
float err = 0;
|
||||
float rawAbsDev = 0;
|
||||
int upperLimit = 0;
|
||||
int lowerLimit = 0;
|
||||
float cumSum = 0;
|
||||
float x = 0;
|
||||
float x2 = 0;
|
||||
float y = 0;
|
||||
float xy = 0;
|
||||
float xAvg = 0;
|
||||
float denom = 0;
|
||||
float skew = 0;
|
||||
|
||||
*skewEst = 0; // Set in case of error below.
|
||||
for (i = 0; i < size; i++) {
|
||||
if ((rawSkew[i] < absLimitOuter && rawSkew[i] > -absLimitOuter)) {
|
||||
n++;
|
||||
rawAvg += rawSkew[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
return -1;
|
||||
}
|
||||
RTC_DCHECK_GT(n, 0);
|
||||
rawAvg /= n;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
if ((rawSkew[i] < absLimitOuter && rawSkew[i] > -absLimitOuter)) {
|
||||
err = rawSkew[i] - rawAvg;
|
||||
rawAbsDev += err >= 0 ? err : -err;
|
||||
}
|
||||
}
|
||||
RTC_DCHECK_GT(n, 0);
|
||||
rawAbsDev /= n;
|
||||
upperLimit = static_cast<int>(rawAvg + 5 * rawAbsDev + 1); // +1 for ceiling.
|
||||
lowerLimit = static_cast<int>(rawAvg - 5 * rawAbsDev - 1); // -1 for floor.
|
||||
|
||||
n = 0;
|
||||
for (i = 0; i < size; i++) {
|
||||
if ((rawSkew[i] < absLimitInner && rawSkew[i] > -absLimitInner) ||
|
||||
(rawSkew[i] < upperLimit && rawSkew[i] > lowerLimit)) {
|
||||
n++;
|
||||
cumSum += rawSkew[i];
|
||||
x += n;
|
||||
x2 += n * n;
|
||||
y += cumSum;
|
||||
xy += n * cumSum;
|
||||
}
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
return -1;
|
||||
}
|
||||
RTC_DCHECK_GT(n, 0);
|
||||
xAvg = x / n;
|
||||
denom = x2 - xAvg * x;
|
||||
|
||||
if (denom != 0) {
|
||||
skew = (xy - xAvg * y) / denom;
|
||||
}
|
||||
|
||||
*skewEst = skew;
|
||||
return 0;
|
||||
}
|
||||
} // namespace webrtc
|
||||
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_RESAMPLER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_RESAMPLER_H_
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec/aec_core.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
enum { kResamplingDelay = 1 };
|
||||
enum { kResamplerBufferSize = FRAME_LEN * 4 };
|
||||
|
||||
// Unless otherwise specified, functions return 0 on success and -1 on error.
|
||||
void* WebRtcAec_CreateResampler(); // Returns NULL on error.
|
||||
int WebRtcAec_InitResampler(void* resampInst, int deviceSampleRateHz);
|
||||
void WebRtcAec_FreeResampler(void* resampInst);
|
||||
|
||||
// Estimates skew from raw measurement.
|
||||
int WebRtcAec_GetSkew(void* resampInst, int rawSkew, float* skewEst);
|
||||
|
||||
// Resamples input using linear interpolation.
|
||||
void WebRtcAec_ResampleLinear(void* resampInst,
|
||||
const float* inspeech,
|
||||
size_t size,
|
||||
float skew,
|
||||
float* outspeech,
|
||||
size_t* size_out);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_RESAMPLER_H_
|
||||
@ -1,868 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Contains the API functions for the AEC.
|
||||
*/
|
||||
#include "webrtc/modules/audio_processing/aec/echo_cancellation.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
extern "C" {
|
||||
#include "webrtc/common_audio/ring_buffer.h"
|
||||
#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h"
|
||||
}
|
||||
#include "webrtc/modules/audio_processing/aec/aec_core.h"
|
||||
#include "webrtc/modules/audio_processing/aec/aec_resampler.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
Aec::Aec() = default;
|
||||
Aec::~Aec() = default;
|
||||
|
||||
// Measured delays [ms]
|
||||
// Device Chrome GTP
|
||||
// MacBook Air 10
|
||||
// MacBook Retina 10 100
|
||||
// MacPro 30?
|
||||
//
|
||||
// Win7 Desktop 70 80?
|
||||
// Win7 T430s 110
|
||||
// Win8 T420s 70
|
||||
//
|
||||
// Daisy 50
|
||||
// Pixel (w/ preproc?) 240
|
||||
// Pixel (w/o preproc?) 110 110
|
||||
|
||||
// The extended filter mode gives us the flexibility to ignore the system's
|
||||
// reported delays. We do this for platforms which we believe provide results
|
||||
// which are incompatible with the AEC's expectations. Based on measurements
|
||||
// (some provided above) we set a conservative (i.e. lower than measured)
|
||||
// fixed delay.
|
||||
//
|
||||
// WEBRTC_UNTRUSTED_DELAY will only have an impact when |extended_filter_mode|
|
||||
// is enabled. See the note along with |DelayCorrection| in
|
||||
// echo_cancellation_impl.h for more details on the mode.
|
||||
//
|
||||
// Justification:
|
||||
// Chromium/Mac: Here, the true latency is so low (~10-20 ms), that it plays
|
||||
// havoc with the AEC's buffering. To avoid this, we set a fixed delay of 20 ms
|
||||
// and then compensate by rewinding by 10 ms (in wideband) through
|
||||
// kDelayDiffOffsetSamples. This trick does not seem to work for larger rewind
|
||||
// values, but fortunately this is sufficient.
|
||||
//
|
||||
// Chromium/Linux(ChromeOS): The values we get on this platform don't correspond
|
||||
// well to reality. The variance doesn't match the AEC's buffer changes, and the
|
||||
// bulk values tend to be too low. However, the range across different hardware
|
||||
// appears to be too large to choose a single value.
|
||||
//
|
||||
// GTP/Linux(ChromeOS): TBD, but for the moment we will trust the values.
|
||||
#if defined(WEBRTC_CHROMIUM_BUILD) && defined(WEBRTC_MAC)
|
||||
#define WEBRTC_UNTRUSTED_DELAY
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_UNTRUSTED_DELAY) && defined(WEBRTC_MAC)
|
||||
static const int kDelayDiffOffsetSamples = -160;
|
||||
#else
|
||||
// Not enabled for now.
|
||||
static const int kDelayDiffOffsetSamples = 0;
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_MAC)
|
||||
static const int kFixedDelayMs = 20;
|
||||
#else
|
||||
static const int kFixedDelayMs = 50;
|
||||
#endif
|
||||
#if !defined(WEBRTC_UNTRUSTED_DELAY)
|
||||
static const int kMinTrustedDelayMs = 20;
|
||||
#endif
|
||||
static const int kMaxTrustedDelayMs = 500;
|
||||
|
||||
// Maximum length of resampled signal. Must be an integer multiple of frames
|
||||
// (ceil(1/(1 + MIN_SKEW)*2) + 1)*FRAME_LEN
|
||||
// The factor of 2 handles wb, and the + 1 is as a safety margin
|
||||
// TODO(bjornv): Replace with kResamplerBufferSize
|
||||
#define MAX_RESAMP_LEN (5 * FRAME_LEN)
|
||||
|
||||
static const int kMaxBufSizeStart = 62; // In partitions
|
||||
static const int sampMsNb = 8; // samples per ms in nb
|
||||
static const int initCheck = 42;
|
||||
|
||||
int Aec::instance_count = 0;
|
||||
|
||||
// Estimates delay to set the position of the far-end buffer read pointer
|
||||
// (controlled by knownDelay)
|
||||
static void EstBufDelayNormal(Aec* aecInst);
|
||||
static void EstBufDelayExtended(Aec* aecInst);
|
||||
static int ProcessNormal(Aec* aecInst,
|
||||
const float* const* nearend,
|
||||
size_t num_bands,
|
||||
float* const* out,
|
||||
size_t num_samples,
|
||||
int16_t reported_delay_ms,
|
||||
int32_t skew);
|
||||
static void ProcessExtended(Aec* aecInst,
|
||||
const float* const* nearend,
|
||||
size_t num_bands,
|
||||
float* const* out,
|
||||
size_t num_samples,
|
||||
int16_t reported_delay_ms,
|
||||
int32_t skew);
|
||||
|
||||
void* WebRtcAec_Create() {
|
||||
Aec* aecpc = new Aec();
|
||||
|
||||
if (!aecpc) {
|
||||
return NULL;
|
||||
}
|
||||
aecpc->data_dumper.reset(new ApmDataDumper(aecpc->instance_count));
|
||||
|
||||
aecpc->aec = WebRtcAec_CreateAec(aecpc->instance_count);
|
||||
if (!aecpc->aec) {
|
||||
WebRtcAec_Free(aecpc);
|
||||
return NULL;
|
||||
}
|
||||
aecpc->resampler = WebRtcAec_CreateResampler();
|
||||
if (!aecpc->resampler) {
|
||||
WebRtcAec_Free(aecpc);
|
||||
return NULL;
|
||||
}
|
||||
// Create far-end pre-buffer. The buffer size has to be large enough for
|
||||
// largest possible drift compensation (kResamplerBufferSize) + "almost" an
|
||||
// FFT buffer (PART_LEN2 - 1).
|
||||
aecpc->far_pre_buf =
|
||||
WebRtc_CreateBuffer(PART_LEN2 + kResamplerBufferSize, sizeof(float));
|
||||
if (!aecpc->far_pre_buf) {
|
||||
WebRtcAec_Free(aecpc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
aecpc->initFlag = 0;
|
||||
|
||||
aecpc->instance_count++;
|
||||
return aecpc;
|
||||
}
|
||||
|
||||
void WebRtcAec_Free(void* aecInst) {
|
||||
Aec* aecpc = reinterpret_cast<Aec*>(aecInst);
|
||||
|
||||
if (aecpc == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
WebRtc_FreeBuffer(aecpc->far_pre_buf);
|
||||
|
||||
WebRtcAec_FreeAec(aecpc->aec);
|
||||
WebRtcAec_FreeResampler(aecpc->resampler);
|
||||
delete aecpc;
|
||||
}
|
||||
|
||||
int32_t WebRtcAec_Init(void* aecInst, int32_t sampFreq, int32_t scSampFreq) {
|
||||
Aec* aecpc = reinterpret_cast<Aec*>(aecInst);
|
||||
aecpc->data_dumper->InitiateNewSetOfRecordings();
|
||||
AecConfig aecConfig;
|
||||
|
||||
if (sampFreq != 8000 && sampFreq != 16000 && sampFreq != 32000 &&
|
||||
sampFreq != 48000) {
|
||||
return AEC_BAD_PARAMETER_ERROR;
|
||||
}
|
||||
aecpc->sampFreq = sampFreq;
|
||||
|
||||
if (scSampFreq < 1 || scSampFreq > 96000) {
|
||||
return AEC_BAD_PARAMETER_ERROR;
|
||||
}
|
||||
aecpc->scSampFreq = scSampFreq;
|
||||
|
||||
// Initialize echo canceller core
|
||||
if (WebRtcAec_InitAec(aecpc->aec, aecpc->sampFreq) == -1) {
|
||||
return AEC_UNSPECIFIED_ERROR;
|
||||
}
|
||||
|
||||
if (WebRtcAec_InitResampler(aecpc->resampler, aecpc->scSampFreq) == -1) {
|
||||
return AEC_UNSPECIFIED_ERROR;
|
||||
}
|
||||
|
||||
WebRtc_InitBuffer(aecpc->far_pre_buf);
|
||||
WebRtc_MoveReadPtr(aecpc->far_pre_buf, -PART_LEN); // Start overlap.
|
||||
|
||||
aecpc->initFlag = initCheck; // indicates that initialization has been done
|
||||
|
||||
if (aecpc->sampFreq == 32000 || aecpc->sampFreq == 48000) {
|
||||
aecpc->splitSampFreq = 16000;
|
||||
} else {
|
||||
aecpc->splitSampFreq = sampFreq;
|
||||
}
|
||||
|
||||
aecpc->delayCtr = 0;
|
||||
aecpc->sampFactor = (aecpc->scSampFreq * 1.0f) / aecpc->splitSampFreq;
|
||||
// Sampling frequency multiplier (SWB is processed as 160 frame size).
|
||||
aecpc->rate_factor = aecpc->splitSampFreq / 8000;
|
||||
|
||||
aecpc->sum = 0;
|
||||
aecpc->counter = 0;
|
||||
aecpc->checkBuffSize = 1;
|
||||
aecpc->firstVal = 0;
|
||||
|
||||
// We skip the startup_phase completely (setting to 0) if DA-AEC is enabled,
|
||||
// but not extended_filter mode.
|
||||
aecpc->startup_phase = WebRtcAec_extended_filter_enabled(aecpc->aec) ||
|
||||
!WebRtcAec_delay_agnostic_enabled(aecpc->aec);
|
||||
aecpc->bufSizeStart = 0;
|
||||
aecpc->checkBufSizeCtr = 0;
|
||||
aecpc->msInSndCardBuf = 0;
|
||||
aecpc->filtDelay = -1; // -1 indicates an initialized state.
|
||||
aecpc->timeForDelayChange = 0;
|
||||
aecpc->knownDelay = 0;
|
||||
aecpc->lastDelayDiff = 0;
|
||||
|
||||
aecpc->skewFrCtr = 0;
|
||||
aecpc->resample = kAecFalse;
|
||||
aecpc->highSkewCtr = 0;
|
||||
aecpc->skew = 0;
|
||||
|
||||
aecpc->farend_started = 0;
|
||||
|
||||
// Default settings.
|
||||
aecConfig.nlpMode = kAecNlpModerate;
|
||||
aecConfig.skewMode = kAecFalse;
|
||||
aecConfig.metricsMode = kAecFalse;
|
||||
aecConfig.delay_logging = kAecFalse;
|
||||
|
||||
if (WebRtcAec_set_config(aecpc, aecConfig) == -1) {
|
||||
return AEC_UNSPECIFIED_ERROR;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns any error that is caused when buffering the
|
||||
// far-end signal.
|
||||
int32_t WebRtcAec_GetBufferFarendError(void* aecInst,
|
||||
const float* farend,
|
||||
size_t nrOfSamples) {
|
||||
Aec* aecpc = reinterpret_cast<Aec*>(aecInst);
|
||||
|
||||
if (!farend)
|
||||
return AEC_NULL_POINTER_ERROR;
|
||||
|
||||
if (aecpc->initFlag != initCheck)
|
||||
return AEC_UNINITIALIZED_ERROR;
|
||||
|
||||
// number of samples == 160 for SWB input
|
||||
if (nrOfSamples != 80 && nrOfSamples != 160)
|
||||
return AEC_BAD_PARAMETER_ERROR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// only buffer L band for farend
|
||||
int32_t WebRtcAec_BufferFarend(void* aecInst,
|
||||
const float* farend,
|
||||
size_t nrOfSamples) {
|
||||
Aec* aecpc = reinterpret_cast<Aec*>(aecInst);
|
||||
size_t newNrOfSamples = nrOfSamples;
|
||||
float new_farend[MAX_RESAMP_LEN];
|
||||
const float* farend_ptr = farend;
|
||||
|
||||
// Get any error caused by buffering the farend signal.
|
||||
int32_t error_code =
|
||||
WebRtcAec_GetBufferFarendError(aecInst, farend, nrOfSamples);
|
||||
|
||||
if (error_code != 0)
|
||||
return error_code;
|
||||
|
||||
if (aecpc->skewMode == kAecTrue && aecpc->resample == kAecTrue) {
|
||||
// Resample and get a new number of samples
|
||||
WebRtcAec_ResampleLinear(aecpc->resampler, farend, nrOfSamples, aecpc->skew,
|
||||
new_farend, &newNrOfSamples);
|
||||
farend_ptr = new_farend;
|
||||
}
|
||||
|
||||
aecpc->farend_started = 1;
|
||||
WebRtcAec_SetSystemDelay(aecpc->aec, WebRtcAec_system_delay(aecpc->aec) +
|
||||
static_cast<int>(newNrOfSamples));
|
||||
|
||||
// Write the time-domain data to |far_pre_buf|.
|
||||
WebRtc_WriteBuffer(aecpc->far_pre_buf, farend_ptr, newNrOfSamples);
|
||||
|
||||
// TODO(minyue): reduce to |PART_LEN| samples for each buffering.
|
||||
while (WebRtc_available_read(aecpc->far_pre_buf) >= PART_LEN2) {
|
||||
// We have enough data to pass to the FFT, hence read PART_LEN2 samples.
|
||||
{
|
||||
float* ptmp = NULL;
|
||||
float tmp[PART_LEN2];
|
||||
WebRtc_ReadBuffer(aecpc->far_pre_buf,
|
||||
reinterpret_cast<void**>(&ptmp), tmp, PART_LEN2);
|
||||
WebRtcAec_BufferFarendBlock(aecpc->aec, &ptmp[PART_LEN]);
|
||||
}
|
||||
|
||||
// Rewind |far_pre_buf| PART_LEN samples for overlap before continuing.
|
||||
WebRtc_MoveReadPtr(aecpc->far_pre_buf, -PART_LEN);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t WebRtcAec_Process(void* aecInst,
|
||||
const float* const* nearend,
|
||||
size_t num_bands,
|
||||
float* const* out,
|
||||
size_t nrOfSamples,
|
||||
int16_t msInSndCardBuf,
|
||||
int32_t skew) {
|
||||
Aec* aecpc = reinterpret_cast<Aec*>(aecInst);
|
||||
int32_t retVal = 0;
|
||||
|
||||
if (out == NULL) {
|
||||
return AEC_NULL_POINTER_ERROR;
|
||||
}
|
||||
|
||||
if (aecpc->initFlag != initCheck) {
|
||||
return AEC_UNINITIALIZED_ERROR;
|
||||
}
|
||||
|
||||
// number of samples == 160 for SWB input
|
||||
if (nrOfSamples != 80 && nrOfSamples != 160) {
|
||||
return AEC_BAD_PARAMETER_ERROR;
|
||||
}
|
||||
|
||||
if (msInSndCardBuf < 0) {
|
||||
msInSndCardBuf = 0;
|
||||
retVal = AEC_BAD_PARAMETER_WARNING;
|
||||
} else if (msInSndCardBuf > kMaxTrustedDelayMs) {
|
||||
// The clamping is now done in ProcessExtended/Normal().
|
||||
retVal = AEC_BAD_PARAMETER_WARNING;
|
||||
}
|
||||
|
||||
// This returns the value of aec->extended_filter_enabled.
|
||||
if (WebRtcAec_extended_filter_enabled(aecpc->aec)) {
|
||||
ProcessExtended(aecpc, nearend, num_bands, out, nrOfSamples, msInSndCardBuf,
|
||||
skew);
|
||||
} else {
|
||||
retVal = ProcessNormal(aecpc, nearend, num_bands, out, nrOfSamples,
|
||||
msInSndCardBuf, skew);
|
||||
}
|
||||
|
||||
int far_buf_size_samples = WebRtcAec_system_delay(aecpc->aec);
|
||||
aecpc->data_dumper->DumpRaw("aec_system_delay", 1, &far_buf_size_samples);
|
||||
aecpc->data_dumper->DumpRaw("aec_known_delay", 1, &aecpc->knownDelay);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
int WebRtcAec_set_config(void* handle, AecConfig config) {
|
||||
Aec* self = reinterpret_cast<Aec*>(handle);
|
||||
if (self->initFlag != initCheck) {
|
||||
return AEC_UNINITIALIZED_ERROR;
|
||||
}
|
||||
|
||||
if (config.skewMode != kAecFalse && config.skewMode != kAecTrue) {
|
||||
return AEC_BAD_PARAMETER_ERROR;
|
||||
}
|
||||
self->skewMode = config.skewMode;
|
||||
|
||||
if (config.nlpMode != kAecNlpConservative &&
|
||||
config.nlpMode != kAecNlpModerate &&
|
||||
config.nlpMode != kAecNlpAggressive) {
|
||||
return AEC_BAD_PARAMETER_ERROR;
|
||||
}
|
||||
|
||||
if (config.metricsMode != kAecFalse && config.metricsMode != kAecTrue) {
|
||||
return AEC_BAD_PARAMETER_ERROR;
|
||||
}
|
||||
|
||||
if (config.delay_logging != kAecFalse && config.delay_logging != kAecTrue) {
|
||||
return AEC_BAD_PARAMETER_ERROR;
|
||||
}
|
||||
|
||||
WebRtcAec_SetConfigCore(self->aec, config.nlpMode, config.metricsMode,
|
||||
config.delay_logging);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebRtcAec_get_echo_status(void* handle, int* status) {
|
||||
Aec* self = reinterpret_cast<Aec*>(handle);
|
||||
if (status == NULL) {
|
||||
return AEC_NULL_POINTER_ERROR;
|
||||
}
|
||||
if (self->initFlag != initCheck) {
|
||||
return AEC_UNINITIALIZED_ERROR;
|
||||
}
|
||||
|
||||
*status = WebRtcAec_echo_state(self->aec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebRtcAec_GetMetrics(void* handle, AecMetrics* metrics) {
|
||||
const float kUpWeight = 0.7f;
|
||||
float dtmp;
|
||||
int stmp;
|
||||
Aec* self = reinterpret_cast<Aec*>(handle);
|
||||
Stats erl;
|
||||
Stats erle;
|
||||
Stats a_nlp;
|
||||
|
||||
if (handle == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (metrics == NULL) {
|
||||
return AEC_NULL_POINTER_ERROR;
|
||||
}
|
||||
if (self->initFlag != initCheck) {
|
||||
return AEC_UNINITIALIZED_ERROR;
|
||||
}
|
||||
|
||||
WebRtcAec_GetEchoStats(self->aec, &erl, &erle, &a_nlp,
|
||||
&metrics->divergent_filter_fraction);
|
||||
|
||||
// ERL
|
||||
metrics->erl.instant = static_cast<int>(erl.instant);
|
||||
|
||||
if ((erl.himean > kOffsetLevel) && (erl.average > kOffsetLevel)) {
|
||||
// Use a mix between regular average and upper part average.
|
||||
dtmp = kUpWeight * erl.himean + (1 - kUpWeight) * erl.average;
|
||||
metrics->erl.average = static_cast<int>(dtmp);
|
||||
} else {
|
||||
metrics->erl.average = kOffsetLevel;
|
||||
}
|
||||
|
||||
metrics->erl.max = static_cast<int>(erl.max);
|
||||
|
||||
if (erl.min < (kOffsetLevel * (-1))) {
|
||||
metrics->erl.min = static_cast<int>(erl.min);
|
||||
} else {
|
||||
metrics->erl.min = kOffsetLevel;
|
||||
}
|
||||
|
||||
// ERLE
|
||||
metrics->erle.instant = static_cast<int>(erle.instant);
|
||||
|
||||
if ((erle.himean > kOffsetLevel) && (erle.average > kOffsetLevel)) {
|
||||
// Use a mix between regular average and upper part average.
|
||||
dtmp = kUpWeight * erle.himean + (1 - kUpWeight) * erle.average;
|
||||
metrics->erle.average = static_cast<int>(dtmp);
|
||||
} else {
|
||||
metrics->erle.average = kOffsetLevel;
|
||||
}
|
||||
|
||||
metrics->erle.max = static_cast<int>(erle.max);
|
||||
|
||||
if (erle.min < (kOffsetLevel * (-1))) {
|
||||
metrics->erle.min = static_cast<int>(erle.min);
|
||||
} else {
|
||||
metrics->erle.min = kOffsetLevel;
|
||||
}
|
||||
|
||||
// RERL
|
||||
if ((metrics->erl.average > kOffsetLevel) &&
|
||||
(metrics->erle.average > kOffsetLevel)) {
|
||||
stmp = metrics->erl.average + metrics->erle.average;
|
||||
} else {
|
||||
stmp = kOffsetLevel;
|
||||
}
|
||||
metrics->rerl.average = stmp;
|
||||
|
||||
// No other statistics needed, but returned for completeness.
|
||||
metrics->rerl.instant = stmp;
|
||||
metrics->rerl.max = stmp;
|
||||
metrics->rerl.min = stmp;
|
||||
|
||||
// A_NLP
|
||||
metrics->aNlp.instant = static_cast<int>(a_nlp.instant);
|
||||
|
||||
if ((a_nlp.himean > kOffsetLevel) && (a_nlp.average > kOffsetLevel)) {
|
||||
// Use a mix between regular average and upper part average.
|
||||
dtmp = kUpWeight * a_nlp.himean + (1 - kUpWeight) * a_nlp.average;
|
||||
metrics->aNlp.average = static_cast<int>(dtmp);
|
||||
} else {
|
||||
metrics->aNlp.average = kOffsetLevel;
|
||||
}
|
||||
|
||||
metrics->aNlp.max = static_cast<int>(a_nlp.max);
|
||||
|
||||
if (a_nlp.min < (kOffsetLevel * (-1))) {
|
||||
metrics->aNlp.min = static_cast<int>(a_nlp.min);
|
||||
} else {
|
||||
metrics->aNlp.min = kOffsetLevel;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebRtcAec_GetDelayMetrics(void* handle,
|
||||
int* median,
|
||||
int* std,
|
||||
float* fraction_poor_delays) {
|
||||
Aec* self = reinterpret_cast<Aec*>(handle);
|
||||
if (median == NULL) {
|
||||
return AEC_NULL_POINTER_ERROR;
|
||||
}
|
||||
if (std == NULL) {
|
||||
return AEC_NULL_POINTER_ERROR;
|
||||
}
|
||||
if (self->initFlag != initCheck) {
|
||||
return AEC_UNINITIALIZED_ERROR;
|
||||
}
|
||||
if (WebRtcAec_GetDelayMetricsCore(self->aec, median, std,
|
||||
fraction_poor_delays) == -1) {
|
||||
// Logging disabled.
|
||||
return AEC_UNSUPPORTED_FUNCTION_ERROR;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
AecCore* WebRtcAec_aec_core(void* handle) {
|
||||
if (!handle) {
|
||||
return NULL;
|
||||
}
|
||||
return reinterpret_cast<Aec*>(handle)->aec;
|
||||
}
|
||||
|
||||
static int ProcessNormal(Aec* aecInst,
|
||||
const float* const* nearend,
|
||||
size_t num_bands,
|
||||
float* const* out,
|
||||
size_t num_samples,
|
||||
int16_t reported_delay_ms,
|
||||
int32_t skew) {
|
||||
int retVal = 0;
|
||||
size_t i;
|
||||
size_t nBlocks10ms;
|
||||
// Limit resampling to doubling/halving of signal
|
||||
const float minSkewEst = -0.5f;
|
||||
const float maxSkewEst = 1.0f;
|
||||
|
||||
reported_delay_ms =
|
||||
reported_delay_ms > kMaxTrustedDelayMs ? kMaxTrustedDelayMs :
|
||||
reported_delay_ms;
|
||||
// TODO(andrew): we need to investigate if this +10 is really wanted.
|
||||
reported_delay_ms += 10;
|
||||
aecInst->msInSndCardBuf = reported_delay_ms;
|
||||
|
||||
if (aecInst->skewMode == kAecTrue) {
|
||||
if (aecInst->skewFrCtr < 25) {
|
||||
aecInst->skewFrCtr++;
|
||||
} else {
|
||||
retVal = WebRtcAec_GetSkew(aecInst->resampler, skew, &aecInst->skew);
|
||||
if (retVal == -1) {
|
||||
aecInst->skew = 0;
|
||||
retVal = AEC_BAD_PARAMETER_WARNING;
|
||||
}
|
||||
|
||||
aecInst->skew /= aecInst->sampFactor * num_samples;
|
||||
|
||||
if (aecInst->skew < 1.0e-3 && aecInst->skew > -1.0e-3) {
|
||||
aecInst->resample = kAecFalse;
|
||||
} else {
|
||||
aecInst->resample = kAecTrue;
|
||||
}
|
||||
|
||||
if (aecInst->skew < minSkewEst) {
|
||||
aecInst->skew = minSkewEst;
|
||||
} else if (aecInst->skew > maxSkewEst) {
|
||||
aecInst->skew = maxSkewEst;
|
||||
}
|
||||
|
||||
aecInst->data_dumper->DumpRaw("aec_skew", 1, &aecInst->skew);
|
||||
}
|
||||
}
|
||||
|
||||
nBlocks10ms = num_samples / (FRAME_LEN * aecInst->rate_factor);
|
||||
|
||||
if (aecInst->startup_phase) {
|
||||
for (i = 0; i < num_bands; ++i) {
|
||||
// Only needed if they don't already point to the same place.
|
||||
if (nearend[i] != out[i]) {
|
||||
memcpy(out[i], nearend[i], sizeof(nearend[i][0]) * num_samples);
|
||||
}
|
||||
}
|
||||
|
||||
// The AEC is in the start up mode
|
||||
// AEC is disabled until the system delay is OK
|
||||
|
||||
// Mechanism to ensure that the system delay is reasonably stable.
|
||||
if (aecInst->checkBuffSize) {
|
||||
aecInst->checkBufSizeCtr++;
|
||||
// Before we fill up the far-end buffer we require the system delay
|
||||
// to be stable (+/-8 ms) compared to the first value. This
|
||||
// comparison is made during the following 6 consecutive 10 ms
|
||||
// blocks. If it seems to be stable then we start to fill up the
|
||||
// far-end buffer.
|
||||
if (aecInst->counter == 0) {
|
||||
aecInst->firstVal = aecInst->msInSndCardBuf;
|
||||
aecInst->sum = 0;
|
||||
}
|
||||
|
||||
if (abs(aecInst->firstVal - aecInst->msInSndCardBuf) <
|
||||
WEBRTC_SPL_MAX(0.2 * aecInst->msInSndCardBuf, sampMsNb)) {
|
||||
aecInst->sum += aecInst->msInSndCardBuf;
|
||||
aecInst->counter++;
|
||||
} else {
|
||||
aecInst->counter = 0;
|
||||
}
|
||||
|
||||
if (aecInst->counter * nBlocks10ms >= 6) {
|
||||
// The far-end buffer size is determined in partitions of
|
||||
// PART_LEN samples. Use 75% of the average value of the system
|
||||
// delay as buffer size to start with.
|
||||
aecInst->bufSizeStart =
|
||||
WEBRTC_SPL_MIN((3 * aecInst->sum * aecInst->rate_factor * 8) /
|
||||
(4 * aecInst->counter * PART_LEN),
|
||||
kMaxBufSizeStart);
|
||||
// Buffer size has now been determined.
|
||||
aecInst->checkBuffSize = 0;
|
||||
}
|
||||
|
||||
if (aecInst->checkBufSizeCtr * nBlocks10ms > 50) {
|
||||
// For really bad systems, don't disable the echo canceller for
|
||||
// more than 0.5 sec.
|
||||
aecInst->bufSizeStart = WEBRTC_SPL_MIN(
|
||||
(aecInst->msInSndCardBuf * aecInst->rate_factor * 3) / 40,
|
||||
kMaxBufSizeStart);
|
||||
aecInst->checkBuffSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// If |checkBuffSize| changed in the if-statement above.
|
||||
if (!aecInst->checkBuffSize) {
|
||||
// The system delay is now reasonably stable (or has been unstable
|
||||
// for too long). When the far-end buffer is filled with
|
||||
// approximately the same amount of data as reported by the system
|
||||
// we end the startup phase.
|
||||
int overhead_elements =
|
||||
WebRtcAec_system_delay(aecInst->aec) / PART_LEN -
|
||||
aecInst->bufSizeStart;
|
||||
if (overhead_elements == 0) {
|
||||
// Enable the AEC
|
||||
aecInst->startup_phase = 0;
|
||||
} else if (overhead_elements > 0) {
|
||||
// TODO(bjornv): Do we need a check on how much we actually
|
||||
// moved the read pointer? It should always be possible to move
|
||||
// the pointer |overhead_elements| since we have only added data
|
||||
// to the buffer and no delay compensation nor AEC processing
|
||||
// has been done.
|
||||
WebRtcAec_AdjustFarendBufferSizeAndSystemDelay(aecInst->aec,
|
||||
overhead_elements);
|
||||
|
||||
// Enable the AEC
|
||||
aecInst->startup_phase = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// AEC is enabled.
|
||||
EstBufDelayNormal(aecInst);
|
||||
|
||||
// Call the AEC.
|
||||
// TODO(bjornv): Re-structure such that we don't have to pass
|
||||
// |aecInst->knownDelay| as input. Change name to something like
|
||||
// |system_buffer_diff|.
|
||||
WebRtcAec_ProcessFrames(aecInst->aec, nearend, num_bands, num_samples,
|
||||
aecInst->knownDelay, out);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
static void ProcessExtended(Aec* self,
|
||||
const float* const* near,
|
||||
size_t num_bands,
|
||||
float* const* out,
|
||||
size_t num_samples,
|
||||
int16_t reported_delay_ms,
|
||||
int32_t skew) {
|
||||
size_t i;
|
||||
const int delay_diff_offset = kDelayDiffOffsetSamples;
|
||||
RTC_DCHECK(num_samples == 80 || num_samples == 160);
|
||||
#if defined(WEBRTC_UNTRUSTED_DELAY)
|
||||
reported_delay_ms = kFixedDelayMs;
|
||||
#else
|
||||
// This is the usual mode where we trust the reported system delay values.
|
||||
// Due to the longer filter, we no longer add 10 ms to the reported delay
|
||||
// to reduce chance of non-causality. Instead we apply a minimum here to avoid
|
||||
// issues with the read pointer jumping around needlessly.
|
||||
reported_delay_ms = reported_delay_ms < kMinTrustedDelayMs
|
||||
? kMinTrustedDelayMs
|
||||
: reported_delay_ms;
|
||||
// If the reported delay appears to be bogus, we attempt to recover by using
|
||||
// the measured fixed delay values. We use >= here because higher layers
|
||||
// may already clamp to this maximum value, and we would otherwise not
|
||||
// detect it here.
|
||||
reported_delay_ms = reported_delay_ms >= kMaxTrustedDelayMs
|
||||
? kFixedDelayMs
|
||||
: reported_delay_ms;
|
||||
#endif
|
||||
self->msInSndCardBuf = reported_delay_ms;
|
||||
|
||||
if (!self->farend_started) {
|
||||
for (i = 0; i < num_bands; ++i) {
|
||||
// Only needed if they don't already point to the same place.
|
||||
if (near[i] != out[i]) {
|
||||
memcpy(out[i], near[i], sizeof(near[i][0]) * num_samples);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (self->startup_phase) {
|
||||
// In the extended mode, there isn't a startup "phase", just a special
|
||||
// action on the first frame. In the trusted delay case, we'll take the
|
||||
// current reported delay, unless it's less then our conservative
|
||||
// measurement.
|
||||
int startup_size_ms =
|
||||
reported_delay_ms < kFixedDelayMs ? kFixedDelayMs : reported_delay_ms;
|
||||
#if defined(WEBRTC_ANDROID)
|
||||
int target_delay = startup_size_ms * self->rate_factor * 8;
|
||||
#else
|
||||
// To avoid putting the AEC in a non-causal state we're being slightly
|
||||
// conservative and scale by 2. On Android we use a fixed delay and
|
||||
// therefore there is no need to scale the target_delay.
|
||||
int target_delay = startup_size_ms * self->rate_factor * 8 / 2;
|
||||
#endif
|
||||
int overhead_elements =
|
||||
(WebRtcAec_system_delay(self->aec) - target_delay) / PART_LEN;
|
||||
WebRtcAec_AdjustFarendBufferSizeAndSystemDelay(self->aec,
|
||||
overhead_elements);
|
||||
self->startup_phase = 0;
|
||||
}
|
||||
|
||||
EstBufDelayExtended(self);
|
||||
|
||||
{
|
||||
// |delay_diff_offset| gives us the option to manually rewind the delay on
|
||||
// very low delay platforms which can't be expressed purely through
|
||||
// |reported_delay_ms|.
|
||||
const int adjusted_known_delay =
|
||||
WEBRTC_SPL_MAX(0, self->knownDelay + delay_diff_offset);
|
||||
|
||||
WebRtcAec_ProcessFrames(self->aec, near, num_bands, num_samples,
|
||||
adjusted_known_delay, out);
|
||||
}
|
||||
}
|
||||
|
||||
static void EstBufDelayNormal(Aec* aecInst) {
|
||||
int nSampSndCard = aecInst->msInSndCardBuf * sampMsNb * aecInst->rate_factor;
|
||||
int current_delay = nSampSndCard - WebRtcAec_system_delay(aecInst->aec);
|
||||
int delay_difference = 0;
|
||||
|
||||
// Before we proceed with the delay estimate filtering we:
|
||||
// 1) Compensate for the frame that will be read.
|
||||
// 2) Compensate for drift resampling.
|
||||
// 3) Compensate for non-causality if needed, since the estimated delay can't
|
||||
// be negative.
|
||||
|
||||
// 1) Compensating for the frame(s) that will be read/processed.
|
||||
current_delay += FRAME_LEN * aecInst->rate_factor;
|
||||
|
||||
// 2) Account for resampling frame delay.
|
||||
if (aecInst->skewMode == kAecTrue && aecInst->resample == kAecTrue) {
|
||||
current_delay -= kResamplingDelay;
|
||||
}
|
||||
|
||||
// 3) Compensate for non-causality, if needed, by flushing one block.
|
||||
if (current_delay < PART_LEN) {
|
||||
current_delay +=
|
||||
WebRtcAec_AdjustFarendBufferSizeAndSystemDelay(aecInst->aec, 1) *
|
||||
PART_LEN;
|
||||
}
|
||||
|
||||
// We use -1 to signal an initialized state in the "extended" implementation;
|
||||
// compensate for that.
|
||||
aecInst->filtDelay = aecInst->filtDelay < 0 ? 0 : aecInst->filtDelay;
|
||||
aecInst->filtDelay =
|
||||
WEBRTC_SPL_MAX(0, static_cast<int16_t>(0.8 *
|
||||
aecInst->filtDelay +
|
||||
0.2 * current_delay));
|
||||
|
||||
delay_difference = aecInst->filtDelay - aecInst->knownDelay;
|
||||
if (delay_difference > 224) {
|
||||
if (aecInst->lastDelayDiff < 96) {
|
||||
aecInst->timeForDelayChange = 0;
|
||||
} else {
|
||||
aecInst->timeForDelayChange++;
|
||||
}
|
||||
} else if (delay_difference < 96 && aecInst->knownDelay > 0) {
|
||||
if (aecInst->lastDelayDiff > 224) {
|
||||
aecInst->timeForDelayChange = 0;
|
||||
} else {
|
||||
aecInst->timeForDelayChange++;
|
||||
}
|
||||
} else {
|
||||
aecInst->timeForDelayChange = 0;
|
||||
}
|
||||
aecInst->lastDelayDiff = delay_difference;
|
||||
|
||||
if (aecInst->timeForDelayChange > 25) {
|
||||
aecInst->knownDelay = WEBRTC_SPL_MAX((int)aecInst->filtDelay - 160, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void EstBufDelayExtended(Aec* aecInst) {
|
||||
int reported_delay = aecInst->msInSndCardBuf * sampMsNb *
|
||||
aecInst->rate_factor;
|
||||
int current_delay = reported_delay - WebRtcAec_system_delay(aecInst->aec);
|
||||
int delay_difference = 0;
|
||||
|
||||
// Before we proceed with the delay estimate filtering we:
|
||||
// 1) Compensate for the frame that will be read.
|
||||
// 2) Compensate for drift resampling.
|
||||
// 3) Compensate for non-causality if needed, since the estimated delay can't
|
||||
// be negative.
|
||||
|
||||
// 1) Compensating for the frame(s) that will be read/processed.
|
||||
current_delay += FRAME_LEN * aecInst->rate_factor;
|
||||
|
||||
// 2) Account for resampling frame delay.
|
||||
if (aecInst->skewMode == kAecTrue && aecInst->resample == kAecTrue) {
|
||||
current_delay -= kResamplingDelay;
|
||||
}
|
||||
|
||||
// 3) Compensate for non-causality, if needed, by flushing two blocks.
|
||||
if (current_delay < PART_LEN) {
|
||||
current_delay +=
|
||||
WebRtcAec_AdjustFarendBufferSizeAndSystemDelay(aecInst->aec, 2) *
|
||||
PART_LEN;
|
||||
}
|
||||
|
||||
if (aecInst->filtDelay == -1) {
|
||||
aecInst->filtDelay = WEBRTC_SPL_MAX(0, 0.5 * current_delay);
|
||||
} else {
|
||||
aecInst->filtDelay = WEBRTC_SPL_MAX(
|
||||
0, static_cast<int16_t>(0.95 * aecInst->filtDelay + 0.05 *
|
||||
current_delay));
|
||||
}
|
||||
|
||||
delay_difference = aecInst->filtDelay - aecInst->knownDelay;
|
||||
if (delay_difference > 384) {
|
||||
if (aecInst->lastDelayDiff < 128) {
|
||||
aecInst->timeForDelayChange = 0;
|
||||
} else {
|
||||
aecInst->timeForDelayChange++;
|
||||
}
|
||||
} else if (delay_difference < 128 && aecInst->knownDelay > 0) {
|
||||
if (aecInst->lastDelayDiff > 384) {
|
||||
aecInst->timeForDelayChange = 0;
|
||||
} else {
|
||||
aecInst->timeForDelayChange++;
|
||||
}
|
||||
} else {
|
||||
aecInst->timeForDelayChange = 0;
|
||||
}
|
||||
aecInst->lastDelayDiff = delay_difference;
|
||||
|
||||
if (aecInst->timeForDelayChange > 25) {
|
||||
aecInst->knownDelay = WEBRTC_SPL_MAX((int)aecInst->filtDelay - 256, 0);
|
||||
}
|
||||
}
|
||||
} // namespace webrtc
|
||||
@ -1,299 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC_ECHO_CANCELLATION_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC_ECHO_CANCELLATION_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
extern "C" {
|
||||
#include "webrtc/common_audio/ring_buffer.h"
|
||||
}
|
||||
#include "webrtc/modules/audio_processing/aec/aec_core.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Errors
|
||||
#define AEC_UNSPECIFIED_ERROR 12000
|
||||
#define AEC_UNSUPPORTED_FUNCTION_ERROR 12001
|
||||
#define AEC_UNINITIALIZED_ERROR 12002
|
||||
#define AEC_NULL_POINTER_ERROR 12003
|
||||
#define AEC_BAD_PARAMETER_ERROR 12004
|
||||
|
||||
// Warnings
|
||||
#define AEC_BAD_PARAMETER_WARNING 12050
|
||||
|
||||
enum { kAecNlpConservative = 0, kAecNlpModerate, kAecNlpAggressive };
|
||||
|
||||
enum { kAecFalse = 0, kAecTrue };
|
||||
|
||||
typedef struct {
|
||||
int16_t nlpMode; // default kAecNlpModerate
|
||||
int16_t skewMode; // default kAecFalse
|
||||
int16_t metricsMode; // default kAecFalse
|
||||
int delay_logging; // default kAecFalse
|
||||
// float realSkew;
|
||||
} AecConfig;
|
||||
|
||||
typedef struct {
|
||||
int instant;
|
||||
int average;
|
||||
int max;
|
||||
int min;
|
||||
} AecLevel;
|
||||
|
||||
typedef struct {
|
||||
AecLevel rerl;
|
||||
AecLevel erl;
|
||||
AecLevel erle;
|
||||
AecLevel aNlp;
|
||||
float divergent_filter_fraction;
|
||||
} AecMetrics;
|
||||
|
||||
struct AecCore;
|
||||
|
||||
class ApmDataDumper;
|
||||
|
||||
typedef struct Aec {
|
||||
Aec();
|
||||
~Aec();
|
||||
|
||||
std::unique_ptr<ApmDataDumper> data_dumper;
|
||||
|
||||
int delayCtr;
|
||||
int sampFreq;
|
||||
int splitSampFreq;
|
||||
int scSampFreq;
|
||||
float sampFactor; // scSampRate / sampFreq
|
||||
short skewMode;
|
||||
int bufSizeStart;
|
||||
int knownDelay;
|
||||
int rate_factor;
|
||||
|
||||
short initFlag; // indicates if AEC has been initialized
|
||||
|
||||
// Variables used for averaging far end buffer size
|
||||
short counter;
|
||||
int sum;
|
||||
short firstVal;
|
||||
short checkBufSizeCtr;
|
||||
|
||||
// Variables used for delay shifts
|
||||
short msInSndCardBuf;
|
||||
short filtDelay; // Filtered delay estimate.
|
||||
int timeForDelayChange;
|
||||
int startup_phase;
|
||||
int checkBuffSize;
|
||||
short lastDelayDiff;
|
||||
|
||||
// Structures
|
||||
void* resampler;
|
||||
|
||||
int skewFrCtr;
|
||||
int resample; // if the skew is small enough we don't resample
|
||||
int highSkewCtr;
|
||||
float skew;
|
||||
|
||||
RingBuffer* far_pre_buf; // Time domain far-end pre-buffer.
|
||||
|
||||
int farend_started;
|
||||
|
||||
// Aec instance counter.
|
||||
static int instance_count;
|
||||
AecCore* aec;
|
||||
} Aec;
|
||||
|
||||
/*
|
||||
* Allocates the memory needed by the AEC. The memory needs to be initialized
|
||||
* separately using the WebRtcAec_Init() function. Returns a pointer to the
|
||||
* object or NULL on error.
|
||||
*/
|
||||
void* WebRtcAec_Create();
|
||||
|
||||
/*
|
||||
* This function releases the memory allocated by WebRtcAec_Create().
|
||||
*
|
||||
* Inputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* void* aecInst Pointer to the AEC instance
|
||||
*/
|
||||
void WebRtcAec_Free(void* aecInst);
|
||||
|
||||
/*
|
||||
* Initializes an AEC instance.
|
||||
*
|
||||
* Inputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* void* aecInst Pointer to the AEC instance
|
||||
* int32_t sampFreq Sampling frequency of data
|
||||
* int32_t scSampFreq Soundcard sampling frequency
|
||||
*
|
||||
* Outputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* int32_t return 0: OK
|
||||
* -1: error
|
||||
*/
|
||||
int32_t WebRtcAec_Init(void* aecInst, int32_t sampFreq, int32_t scSampFreq);
|
||||
|
||||
/*
|
||||
* Inserts an 80 or 160 sample block of data into the farend buffer.
|
||||
*
|
||||
* Inputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* void* aecInst Pointer to the AEC instance
|
||||
* const float* farend In buffer containing one frame of
|
||||
* farend signal for L band
|
||||
* int16_t nrOfSamples Number of samples in farend buffer
|
||||
*
|
||||
* Outputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* int32_t return 0: OK
|
||||
* 12000-12050: error code
|
||||
*/
|
||||
int32_t WebRtcAec_BufferFarend(void* aecInst,
|
||||
const float* farend,
|
||||
size_t nrOfSamples);
|
||||
|
||||
/*
|
||||
* Reports any errors that would arise if buffering a farend buffer
|
||||
*
|
||||
* Inputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* void* aecInst Pointer to the AEC instance
|
||||
* const float* farend In buffer containing one frame of
|
||||
* farend signal for L band
|
||||
* int16_t nrOfSamples Number of samples in farend buffer
|
||||
*
|
||||
* Outputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* int32_t return 0: OK
|
||||
* 12000-12050: error code
|
||||
*/
|
||||
int32_t WebRtcAec_GetBufferFarendError(void* aecInst,
|
||||
const float* farend,
|
||||
size_t nrOfSamples);
|
||||
|
||||
/*
|
||||
* Runs the echo canceller on an 80 or 160 sample blocks of data.
|
||||
*
|
||||
* Inputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* void* aecInst Pointer to the AEC instance
|
||||
* float* const* nearend In buffer containing one frame of
|
||||
* nearend+echo signal for each band
|
||||
* int num_bands Number of bands in nearend buffer
|
||||
* int16_t nrOfSamples Number of samples in nearend buffer
|
||||
* int16_t msInSndCardBuf Delay estimate for sound card and
|
||||
* system buffers
|
||||
* int16_t skew Difference between number of samples played
|
||||
* and recorded at the soundcard (for clock skew
|
||||
* compensation)
|
||||
*
|
||||
* Outputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* float* const* out Out buffer, one frame of processed nearend
|
||||
* for each band
|
||||
* int32_t return 0: OK
|
||||
* 12000-12050: error code
|
||||
*/
|
||||
int32_t WebRtcAec_Process(void* aecInst,
|
||||
const float* const* nearend,
|
||||
size_t num_bands,
|
||||
float* const* out,
|
||||
size_t nrOfSamples,
|
||||
int16_t msInSndCardBuf,
|
||||
int32_t skew);
|
||||
|
||||
/*
|
||||
* This function enables the user to set certain parameters on-the-fly.
|
||||
*
|
||||
* Inputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* void* handle Pointer to the AEC instance
|
||||
* AecConfig config Config instance that contains all
|
||||
* properties to be set
|
||||
*
|
||||
* Outputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* int return 0: OK
|
||||
* 12000-12050: error code
|
||||
*/
|
||||
int WebRtcAec_set_config(void* handle, AecConfig config);
|
||||
|
||||
/*
|
||||
* Gets the current echo status of the nearend signal.
|
||||
*
|
||||
* Inputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* void* handle Pointer to the AEC instance
|
||||
*
|
||||
* Outputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* int* status 0: Almost certainly nearend single-talk
|
||||
* 1: Might not be neared single-talk
|
||||
* int return 0: OK
|
||||
* 12000-12050: error code
|
||||
*/
|
||||
int WebRtcAec_get_echo_status(void* handle, int* status);
|
||||
|
||||
/*
|
||||
* Gets the current echo metrics for the session.
|
||||
*
|
||||
* Inputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* void* handle Pointer to the AEC instance
|
||||
*
|
||||
* Outputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* AecMetrics* metrics Struct which will be filled out with the
|
||||
* current echo metrics.
|
||||
* int return 0: OK
|
||||
* 12000-12050: error code
|
||||
*/
|
||||
int WebRtcAec_GetMetrics(void* handle, AecMetrics* metrics);
|
||||
|
||||
/*
|
||||
* Gets the current delay metrics for the session.
|
||||
*
|
||||
* Inputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* void* handle Pointer to the AEC instance
|
||||
*
|
||||
* Outputs Description
|
||||
* -------------------------------------------------------------------
|
||||
* int* median Delay median value.
|
||||
* int* std Delay standard deviation.
|
||||
* float* fraction_poor_delays Fraction of the delay estimates that may
|
||||
* cause the AEC to perform poorly.
|
||||
*
|
||||
* int return 0: OK
|
||||
* 12000-12050: error code
|
||||
*/
|
||||
int WebRtcAec_GetDelayMetrics(void* handle,
|
||||
int* median,
|
||||
int* std,
|
||||
float* fraction_poor_delays);
|
||||
|
||||
// Returns a pointer to the low level AEC handle.
|
||||
//
|
||||
// Input:
|
||||
// - handle : Pointer to the AEC instance.
|
||||
//
|
||||
// Return value:
|
||||
// - AecCore pointer : NULL for error.
|
||||
//
|
||||
struct AecCore* WebRtcAec_aec_core(void* handle);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC_ECHO_CANCELLATION_H_
|
||||
@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013 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.
|
||||
*/
|
||||
|
||||
// TODO(bjornv): Make this a comprehensive test.
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec/echo_cancellation.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec/aec_core.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
TEST(EchoCancellationTest, CreateAndFreeHasExpectedBehavior) {
|
||||
void* handle = WebRtcAec_Create();
|
||||
ASSERT_TRUE(handle);
|
||||
WebRtcAec_Free(nullptr);
|
||||
WebRtcAec_Free(handle);
|
||||
}
|
||||
|
||||
TEST(EchoCancellationTest, ApplyAecCoreHandle) {
|
||||
void* handle = WebRtcAec_Create();
|
||||
ASSERT_TRUE(handle);
|
||||
EXPECT_TRUE(WebRtcAec_aec_core(NULL) == NULL);
|
||||
AecCore* aec_core = WebRtcAec_aec_core(handle);
|
||||
EXPECT_TRUE(aec_core != NULL);
|
||||
// A simple test to verify that we can set and get a value from the lower
|
||||
// level |aec_core| handle.
|
||||
int delay = 111;
|
||||
WebRtcAec_SetSystemDelay(aec_core, delay);
|
||||
EXPECT_EQ(delay, WebRtcAec_system_delay(aec_core));
|
||||
WebRtcAec_Free(handle);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,599 +0,0 @@
|
||||
/*
|
||||
* 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/modules/audio_processing/aec/aec_core.h"
|
||||
#include "webrtc/modules/audio_processing/aec/echo_cancellation.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
class SystemDelayTest : public ::testing::Test {
|
||||
protected:
|
||||
SystemDelayTest();
|
||||
virtual void SetUp();
|
||||
virtual void TearDown();
|
||||
|
||||
// Initialization of AEC handle with respect to |sample_rate_hz|. Since the
|
||||
// device sample rate is unimportant we set that value to 48000 Hz.
|
||||
void Init(int sample_rate_hz);
|
||||
|
||||
// Makes one render call and one capture call in that specific order.
|
||||
void RenderAndCapture(int device_buffer_ms);
|
||||
|
||||
// Fills up the far-end buffer with respect to the default device buffer size.
|
||||
size_t BufferFillUp();
|
||||
|
||||
// Runs and verifies the behavior in a stable startup procedure.
|
||||
void RunStableStartup();
|
||||
|
||||
// Maps buffer size in ms into samples, taking the unprocessed frame into
|
||||
// account.
|
||||
int MapBufferSizeToSamples(int size_in_ms, bool extended_filter);
|
||||
|
||||
void* handle_;
|
||||
Aec* self_;
|
||||
size_t samples_per_frame_;
|
||||
// Dummy input/output speech data.
|
||||
static const int kSamplesPerChunk = 160;
|
||||
float far_[kSamplesPerChunk];
|
||||
float near_[kSamplesPerChunk];
|
||||
float out_[kSamplesPerChunk];
|
||||
const float* near_ptr_;
|
||||
float* out_ptr_;
|
||||
};
|
||||
|
||||
SystemDelayTest::SystemDelayTest()
|
||||
: handle_(NULL), self_(NULL), samples_per_frame_(0) {
|
||||
// Dummy input data are set with more or less arbitrary non-zero values.
|
||||
for (int i = 0; i < kSamplesPerChunk; i++) {
|
||||
far_[i] = 257.0;
|
||||
near_[i] = 514.0;
|
||||
}
|
||||
memset(out_, 0, sizeof(out_));
|
||||
near_ptr_ = near_;
|
||||
out_ptr_ = out_;
|
||||
}
|
||||
|
||||
void SystemDelayTest::SetUp() {
|
||||
handle_ = WebRtcAec_Create();
|
||||
ASSERT_TRUE(handle_);
|
||||
self_ = reinterpret_cast<Aec*>(handle_);
|
||||
}
|
||||
|
||||
void SystemDelayTest::TearDown() {
|
||||
// Free AEC
|
||||
WebRtcAec_Free(handle_);
|
||||
handle_ = NULL;
|
||||
}
|
||||
|
||||
// In SWB mode nothing is added to the buffer handling with respect to
|
||||
// functionality compared to WB. We therefore only verify behavior in NB and WB.
|
||||
static const int kSampleRateHz[] = {8000, 16000};
|
||||
static const size_t kNumSampleRates =
|
||||
sizeof(kSampleRateHz) / sizeof(*kSampleRateHz);
|
||||
|
||||
// Default audio device buffer size used.
|
||||
static const int kDeviceBufMs = 100;
|
||||
|
||||
// Requirement for a stable device convergence time in ms. Should converge in
|
||||
// less than |kStableConvergenceMs|.
|
||||
static const int kStableConvergenceMs = 100;
|
||||
|
||||
// Maximum convergence time in ms. This means that we should leave the startup
|
||||
// phase after |kMaxConvergenceMs| independent of device buffer stability
|
||||
// conditions.
|
||||
static const int kMaxConvergenceMs = 500;
|
||||
|
||||
void SystemDelayTest::Init(int sample_rate_hz) {
|
||||
// Initialize AEC
|
||||
EXPECT_EQ(0, WebRtcAec_Init(handle_, sample_rate_hz, 48000));
|
||||
EXPECT_EQ(0, WebRtcAec_system_delay(self_->aec));
|
||||
|
||||
// One frame equals 10 ms of data.
|
||||
samples_per_frame_ = static_cast<size_t>(sample_rate_hz / 100);
|
||||
}
|
||||
|
||||
void SystemDelayTest::RenderAndCapture(int device_buffer_ms) {
|
||||
EXPECT_EQ(0, WebRtcAec_BufferFarend(handle_, far_, samples_per_frame_));
|
||||
EXPECT_EQ(0,
|
||||
WebRtcAec_Process(handle_,
|
||||
&near_ptr_,
|
||||
1,
|
||||
&out_ptr_,
|
||||
samples_per_frame_,
|
||||
device_buffer_ms,
|
||||
0));
|
||||
}
|
||||
|
||||
size_t SystemDelayTest::BufferFillUp() {
|
||||
// To make sure we have a full buffer when we verify stability we first fill
|
||||
// up the far-end buffer with the same amount as we will report in through
|
||||
// Process().
|
||||
size_t buffer_size = 0;
|
||||
for (int i = 0; i < kDeviceBufMs / 10; i++) {
|
||||
EXPECT_EQ(0, WebRtcAec_BufferFarend(handle_, far_, samples_per_frame_));
|
||||
buffer_size += samples_per_frame_;
|
||||
EXPECT_EQ(static_cast<int>(buffer_size),
|
||||
WebRtcAec_system_delay(self_->aec));
|
||||
}
|
||||
return buffer_size;
|
||||
}
|
||||
|
||||
void SystemDelayTest::RunStableStartup() {
|
||||
// To make sure we have a full buffer when we verify stability we first fill
|
||||
// up the far-end buffer with the same amount as we will report in through
|
||||
// Process().
|
||||
size_t buffer_size = BufferFillUp();
|
||||
|
||||
if (WebRtcAec_delay_agnostic_enabled(self_->aec) == 1) {
|
||||
// In extended_filter mode we set the buffer size after the first processed
|
||||
// 10 ms chunk. Hence, we don't need to wait for the reported system delay
|
||||
// values to become stable.
|
||||
RenderAndCapture(kDeviceBufMs);
|
||||
buffer_size += samples_per_frame_;
|
||||
EXPECT_EQ(0, self_->startup_phase);
|
||||
} else {
|
||||
// A stable device should be accepted and put in a regular process mode
|
||||
// within |kStableConvergenceMs|.
|
||||
int process_time_ms = 0;
|
||||
for (; process_time_ms < kStableConvergenceMs; process_time_ms += 10) {
|
||||
RenderAndCapture(kDeviceBufMs);
|
||||
buffer_size += samples_per_frame_;
|
||||
if (self_->startup_phase == 0) {
|
||||
// We have left the startup phase.
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Verify convergence time.
|
||||
EXPECT_GT(kStableConvergenceMs, process_time_ms);
|
||||
}
|
||||
// Verify that the buffer has been flushed.
|
||||
EXPECT_GE(static_cast<int>(buffer_size),
|
||||
WebRtcAec_system_delay(self_->aec));
|
||||
}
|
||||
|
||||
int SystemDelayTest::MapBufferSizeToSamples(int size_in_ms,
|
||||
bool extended_filter) {
|
||||
// If extended_filter is disabled we add an extra 10 ms for the unprocessed
|
||||
// frame. That is simply how the algorithm is constructed.
|
||||
return static_cast<int>(
|
||||
(size_in_ms + (extended_filter ? 0 : 10)) * samples_per_frame_ / 10);
|
||||
}
|
||||
|
||||
// The tests should meet basic requirements and not be adjusted to what is
|
||||
// actually implemented. If we don't get good code coverage this way we either
|
||||
// lack in tests or have unnecessary code.
|
||||
// General requirements:
|
||||
// 1) If we add far-end data the system delay should be increased with the same
|
||||
// amount we add.
|
||||
// 2) If the far-end buffer is full we should flush the oldest data to make room
|
||||
// for the new. In this case the system delay is unaffected.
|
||||
// 3) There should exist a startup phase in which the buffer size is to be
|
||||
// determined. In this phase no cancellation should be performed.
|
||||
// 4) Under stable conditions (small variations in device buffer sizes) the AEC
|
||||
// should determine an appropriate local buffer size within
|
||||
// |kStableConvergenceMs| ms.
|
||||
// 5) Under unstable conditions the AEC should make a decision within
|
||||
// |kMaxConvergenceMs| ms.
|
||||
// 6) If the local buffer runs out of data we should stuff the buffer with older
|
||||
// frames.
|
||||
// 7) The system delay should within |kMaxConvergenceMs| ms heal from
|
||||
// disturbances like drift, data glitches, toggling events and outliers.
|
||||
// 8) The system delay should never become negative.
|
||||
|
||||
TEST_F(SystemDelayTest, CorrectIncreaseWhenBufferFarend) {
|
||||
// When we add data to the AEC buffer the internal system delay should be
|
||||
// incremented with the same amount as the size of data.
|
||||
// This process should be independent of DA-AEC and extended_filter mode.
|
||||
for (int extended_filter = 0; extended_filter <= 1; ++extended_filter) {
|
||||
WebRtcAec_enable_extended_filter(self_->aec, extended_filter);
|
||||
EXPECT_EQ(extended_filter, WebRtcAec_extended_filter_enabled(self_->aec));
|
||||
for (int da_aec = 0; da_aec <= 1; ++da_aec) {
|
||||
WebRtcAec_enable_delay_agnostic(self_->aec, da_aec);
|
||||
EXPECT_EQ(da_aec, WebRtcAec_delay_agnostic_enabled(self_->aec));
|
||||
for (size_t i = 0; i < kNumSampleRates; i++) {
|
||||
Init(kSampleRateHz[i]);
|
||||
// Loop through a couple of calls to make sure the system delay
|
||||
// increments correctly.
|
||||
for (int j = 1; j <= 5; j++) {
|
||||
EXPECT_EQ(0,
|
||||
WebRtcAec_BufferFarend(handle_, far_, samples_per_frame_));
|
||||
EXPECT_EQ(static_cast<int>(j * samples_per_frame_),
|
||||
WebRtcAec_system_delay(self_->aec));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(bjornv): Add a test to verify behavior if the far-end buffer is full
|
||||
// when adding new data.
|
||||
|
||||
TEST_F(SystemDelayTest, CorrectDelayAfterStableStartup) {
|
||||
// We run the system in a stable startup. After that we verify that the system
|
||||
// delay meets the requirements.
|
||||
// This process should be independent of DA-AEC and extended_filter mode.
|
||||
for (int extended_filter = 0; extended_filter <= 1; ++extended_filter) {
|
||||
WebRtcAec_enable_extended_filter(self_->aec, extended_filter);
|
||||
EXPECT_EQ(extended_filter, WebRtcAec_extended_filter_enabled(self_->aec));
|
||||
for (int da_aec = 0; da_aec <= 1; ++da_aec) {
|
||||
WebRtcAec_enable_delay_agnostic(self_->aec, da_aec);
|
||||
EXPECT_EQ(da_aec, WebRtcAec_delay_agnostic_enabled(self_->aec));
|
||||
for (size_t i = 0; i < kNumSampleRates; i++) {
|
||||
Init(kSampleRateHz[i]);
|
||||
RunStableStartup();
|
||||
|
||||
// Verify system delay with respect to requirements, i.e., the
|
||||
// |system_delay| is in the interval [75%, 100%] of what's reported on
|
||||
// the average.
|
||||
// In extended_filter mode we target 50% and measure after one processed
|
||||
// 10 ms chunk.
|
||||
int average_reported_delay =
|
||||
static_cast<int>(kDeviceBufMs * samples_per_frame_ / 10);
|
||||
EXPECT_GE(average_reported_delay, WebRtcAec_system_delay(self_->aec));
|
||||
int lower_bound = WebRtcAec_extended_filter_enabled(self_->aec)
|
||||
? average_reported_delay / 2 - samples_per_frame_
|
||||
: average_reported_delay * 3 / 4;
|
||||
EXPECT_LE(lower_bound, WebRtcAec_system_delay(self_->aec));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SystemDelayTest, CorrectDelayAfterUnstableStartup) {
|
||||
// This test does not apply in extended_filter mode, since we only use the
|
||||
// the first 10 ms chunk to determine a reasonable buffer size. Neither does
|
||||
// it apply if DA-AEC is on because that overrides the startup procedure.
|
||||
WebRtcAec_enable_extended_filter(self_->aec, 0);
|
||||
EXPECT_EQ(0, WebRtcAec_extended_filter_enabled(self_->aec));
|
||||
WebRtcAec_enable_delay_agnostic(self_->aec, 0);
|
||||
EXPECT_EQ(0, WebRtcAec_delay_agnostic_enabled(self_->aec));
|
||||
|
||||
// In an unstable system we would start processing after |kMaxConvergenceMs|.
|
||||
// On the last frame the AEC buffer is adjusted to 60% of the last reported
|
||||
// device buffer size.
|
||||
// We construct an unstable system by altering the device buffer size between
|
||||
// two values |kDeviceBufMs| +- 25 ms.
|
||||
for (size_t i = 0; i < kNumSampleRates; i++) {
|
||||
Init(kSampleRateHz[i]);
|
||||
|
||||
// To make sure we have a full buffer when we verify stability we first fill
|
||||
// up the far-end buffer with the same amount as we will report in on the
|
||||
// average through Process().
|
||||
size_t buffer_size = BufferFillUp();
|
||||
|
||||
int buffer_offset_ms = 25;
|
||||
int reported_delay_ms = 0;
|
||||
int process_time_ms = 0;
|
||||
for (; process_time_ms <= kMaxConvergenceMs; process_time_ms += 10) {
|
||||
reported_delay_ms = kDeviceBufMs + buffer_offset_ms;
|
||||
RenderAndCapture(reported_delay_ms);
|
||||
buffer_size += samples_per_frame_;
|
||||
buffer_offset_ms = -buffer_offset_ms;
|
||||
if (self_->startup_phase == 0) {
|
||||
// We have left the startup phase.
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Verify convergence time.
|
||||
EXPECT_GE(kMaxConvergenceMs, process_time_ms);
|
||||
// Verify that the buffer has been flushed.
|
||||
EXPECT_GE(static_cast<int>(buffer_size),
|
||||
WebRtcAec_system_delay(self_->aec));
|
||||
|
||||
// Verify system delay with respect to requirements, i.e., the
|
||||
// |system_delay| is in the interval [60%, 100%] of what's last reported.
|
||||
EXPECT_GE(static_cast<int>(reported_delay_ms * samples_per_frame_ / 10),
|
||||
WebRtcAec_system_delay(self_->aec));
|
||||
EXPECT_LE(
|
||||
static_cast<int>(reported_delay_ms * samples_per_frame_ / 10 * 3 / 5),
|
||||
WebRtcAec_system_delay(self_->aec));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SystemDelayTest, CorrectDelayAfterStableBufferBuildUp) {
|
||||
// This test does not apply in extended_filter mode, since we only use the
|
||||
// the first 10 ms chunk to determine a reasonable buffer size. Neither does
|
||||
// it apply if DA-AEC is on because that overrides the startup procedure.
|
||||
WebRtcAec_enable_extended_filter(self_->aec, 0);
|
||||
EXPECT_EQ(0, WebRtcAec_extended_filter_enabled(self_->aec));
|
||||
WebRtcAec_enable_delay_agnostic(self_->aec, 0);
|
||||
EXPECT_EQ(0, WebRtcAec_delay_agnostic_enabled(self_->aec));
|
||||
|
||||
// In this test we start by establishing the device buffer size during stable
|
||||
// conditions, but with an empty internal far-end buffer. Once that is done we
|
||||
// verify that the system delay is increased correctly until we have reach an
|
||||
// internal buffer size of 75% of what's been reported.
|
||||
for (size_t i = 0; i < kNumSampleRates; i++) {
|
||||
Init(kSampleRateHz[i]);
|
||||
|
||||
// We assume that running |kStableConvergenceMs| calls will put the
|
||||
// algorithm in a state where the device buffer size has been determined. We
|
||||
// can make that assumption since we have a separate stability test.
|
||||
int process_time_ms = 0;
|
||||
for (; process_time_ms < kStableConvergenceMs; process_time_ms += 10) {
|
||||
EXPECT_EQ(0,
|
||||
WebRtcAec_Process(handle_,
|
||||
&near_ptr_,
|
||||
1,
|
||||
&out_ptr_,
|
||||
samples_per_frame_,
|
||||
kDeviceBufMs,
|
||||
0));
|
||||
}
|
||||
// Verify that a buffer size has been established.
|
||||
EXPECT_EQ(0, self_->checkBuffSize);
|
||||
|
||||
// We now have established the required buffer size. Let us verify that we
|
||||
// fill up before leaving the startup phase for normal processing.
|
||||
size_t buffer_size = 0;
|
||||
size_t target_buffer_size = kDeviceBufMs * samples_per_frame_ / 10 * 3 / 4;
|
||||
process_time_ms = 0;
|
||||
for (; process_time_ms <= kMaxConvergenceMs; process_time_ms += 10) {
|
||||
RenderAndCapture(kDeviceBufMs);
|
||||
buffer_size += samples_per_frame_;
|
||||
if (self_->startup_phase == 0) {
|
||||
// We have left the startup phase.
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Verify convergence time.
|
||||
EXPECT_GT(kMaxConvergenceMs, process_time_ms);
|
||||
// Verify that the buffer has reached the desired size.
|
||||
EXPECT_LE(static_cast<int>(target_buffer_size),
|
||||
WebRtcAec_system_delay(self_->aec));
|
||||
|
||||
// Verify normal behavior (system delay is kept constant) after startup by
|
||||
// running a couple of calls to BufferFarend() and Process().
|
||||
for (int j = 0; j < 6; j++) {
|
||||
int system_delay_before_calls = WebRtcAec_system_delay(self_->aec);
|
||||
RenderAndCapture(kDeviceBufMs);
|
||||
EXPECT_EQ(system_delay_before_calls, WebRtcAec_system_delay(self_->aec));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SystemDelayTest, CorrectDelayWhenBufferUnderrun) {
|
||||
// Here we test a buffer under run scenario. If we keep on calling
|
||||
// WebRtcAec_Process() we will finally run out of data, but should
|
||||
// automatically stuff the buffer. We verify this behavior by checking if the
|
||||
// system delay goes negative.
|
||||
// This process should be independent of DA-AEC and extended_filter mode.
|
||||
for (int extended_filter = 0; extended_filter <= 1; ++extended_filter) {
|
||||
WebRtcAec_enable_extended_filter(self_->aec, extended_filter);
|
||||
EXPECT_EQ(extended_filter, WebRtcAec_extended_filter_enabled(self_->aec));
|
||||
for (int da_aec = 0; da_aec <= 1; ++da_aec) {
|
||||
WebRtcAec_enable_delay_agnostic(self_->aec, da_aec);
|
||||
EXPECT_EQ(da_aec, WebRtcAec_delay_agnostic_enabled(self_->aec));
|
||||
for (size_t i = 0; i < kNumSampleRates; i++) {
|
||||
Init(kSampleRateHz[i]);
|
||||
RunStableStartup();
|
||||
|
||||
// The AEC has now left the Startup phase. We now have at most
|
||||
// |kStableConvergenceMs| in the buffer. Keep on calling Process() until
|
||||
// we run out of data and verify that the system delay is non-negative.
|
||||
for (int j = 0; j <= kStableConvergenceMs; j += 10) {
|
||||
EXPECT_EQ(0, WebRtcAec_Process(handle_, &near_ptr_, 1, &out_ptr_,
|
||||
samples_per_frame_, kDeviceBufMs, 0));
|
||||
EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SystemDelayTest, CorrectDelayDuringDrift) {
|
||||
// This drift test should verify that the system delay is never exceeding the
|
||||
// device buffer. The drift is simulated by decreasing the reported device
|
||||
// buffer size by 1 ms every 100 ms. If the device buffer size goes below 30
|
||||
// ms we jump (add) 10 ms to give a repeated pattern.
|
||||
|
||||
// This process should be independent of DA-AEC and extended_filter mode.
|
||||
for (int extended_filter = 0; extended_filter <= 1; ++extended_filter) {
|
||||
WebRtcAec_enable_extended_filter(self_->aec, extended_filter);
|
||||
EXPECT_EQ(extended_filter, WebRtcAec_extended_filter_enabled(self_->aec));
|
||||
for (int da_aec = 0; da_aec <= 1; ++da_aec) {
|
||||
WebRtcAec_enable_delay_agnostic(self_->aec, da_aec);
|
||||
EXPECT_EQ(da_aec, WebRtcAec_delay_agnostic_enabled(self_->aec));
|
||||
for (size_t i = 0; i < kNumSampleRates; i++) {
|
||||
Init(kSampleRateHz[i]);
|
||||
RunStableStartup();
|
||||
|
||||
// We have left the startup phase and proceed with normal processing.
|
||||
int jump = 0;
|
||||
for (int j = 0; j < 1000; j++) {
|
||||
// Drift = -1 ms per 100 ms of data.
|
||||
int device_buf_ms = kDeviceBufMs - (j / 10) + jump;
|
||||
int device_buf = MapBufferSizeToSamples(device_buf_ms,
|
||||
extended_filter == 1);
|
||||
|
||||
if (device_buf_ms < 30) {
|
||||
// Add 10 ms data, taking affect next frame.
|
||||
jump += 10;
|
||||
}
|
||||
RenderAndCapture(device_buf_ms);
|
||||
|
||||
// Verify that the system delay does not exceed the device buffer.
|
||||
EXPECT_GE(device_buf, WebRtcAec_system_delay(self_->aec));
|
||||
|
||||
// Verify that the system delay is non-negative.
|
||||
EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SystemDelayTest, ShouldRecoverAfterGlitch) {
|
||||
// This glitch test should verify that the system delay recovers if there is
|
||||
// a glitch in data. The data glitch is constructed as 200 ms of buffering
|
||||
// after which the stable procedure continues. The glitch is never reported by
|
||||
// the device.
|
||||
// The system is said to be in a non-causal state if the difference between
|
||||
// the device buffer and system delay is less than a block (64 samples).
|
||||
|
||||
// This process should be independent of DA-AEC and extended_filter mode.
|
||||
for (int extended_filter = 0; extended_filter <= 1; ++extended_filter) {
|
||||
WebRtcAec_enable_extended_filter(self_->aec, extended_filter);
|
||||
EXPECT_EQ(extended_filter, WebRtcAec_extended_filter_enabled(self_->aec));
|
||||
for (int da_aec = 0; da_aec <= 1; ++da_aec) {
|
||||
WebRtcAec_enable_delay_agnostic(self_->aec, da_aec);
|
||||
EXPECT_EQ(da_aec, WebRtcAec_delay_agnostic_enabled(self_->aec));
|
||||
for (size_t i = 0; i < kNumSampleRates; i++) {
|
||||
Init(kSampleRateHz[i]);
|
||||
RunStableStartup();
|
||||
int device_buf = MapBufferSizeToSamples(kDeviceBufMs,
|
||||
extended_filter == 1);
|
||||
// Glitch state.
|
||||
for (int j = 0; j < 20; j++) {
|
||||
EXPECT_EQ(0,
|
||||
WebRtcAec_BufferFarend(handle_, far_, samples_per_frame_));
|
||||
// No need to verify system delay, since that is done in a separate
|
||||
// test.
|
||||
}
|
||||
// Verify that we are in a non-causal state, i.e.,
|
||||
// |system_delay| > |device_buf|.
|
||||
EXPECT_LT(device_buf, WebRtcAec_system_delay(self_->aec));
|
||||
|
||||
// Recover state. Should recover at least 4 ms of data per 10 ms, hence
|
||||
// a glitch of 200 ms will take at most 200 * 10 / 4 = 500 ms to recover
|
||||
// from.
|
||||
bool non_causal = true; // We are currently in a non-causal state.
|
||||
for (int j = 0; j < 50; j++) {
|
||||
int system_delay_before = WebRtcAec_system_delay(self_->aec);
|
||||
RenderAndCapture(kDeviceBufMs);
|
||||
int system_delay_after = WebRtcAec_system_delay(self_->aec);
|
||||
// We have recovered if
|
||||
// |device_buf| - |system_delay_after| >= PART_LEN (1 block).
|
||||
// During recovery, |system_delay_after| < |system_delay_before|,
|
||||
// otherwise they are equal.
|
||||
if (non_causal) {
|
||||
EXPECT_LT(system_delay_after, system_delay_before);
|
||||
if (device_buf - system_delay_after >= PART_LEN) {
|
||||
non_causal = false;
|
||||
}
|
||||
} else {
|
||||
EXPECT_EQ(system_delay_before, system_delay_after);
|
||||
}
|
||||
// Verify that the system delay is non-negative.
|
||||
EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
|
||||
}
|
||||
// Check that we have recovered.
|
||||
EXPECT_FALSE(non_causal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SystemDelayTest, UnaffectedWhenSpuriousDeviceBufferValues) {
|
||||
// This test does not apply in extended_filter mode, since we only use the
|
||||
// the first 10 ms chunk to determine a reasonable buffer size.
|
||||
const int extended_filter = 0;
|
||||
WebRtcAec_enable_extended_filter(self_->aec, extended_filter);
|
||||
EXPECT_EQ(extended_filter, WebRtcAec_extended_filter_enabled(self_->aec));
|
||||
|
||||
// Should be DA-AEC independent.
|
||||
for (int da_aec = 0; da_aec <= 1; ++da_aec) {
|
||||
WebRtcAec_enable_delay_agnostic(self_->aec, da_aec);
|
||||
EXPECT_EQ(da_aec, WebRtcAec_delay_agnostic_enabled(self_->aec));
|
||||
// This spurious device buffer data test aims at verifying that the system
|
||||
// delay is unaffected by large outliers.
|
||||
// The system is said to be in a non-causal state if the difference between
|
||||
// the device buffer and system delay is less than a block (64 samples).
|
||||
for (size_t i = 0; i < kNumSampleRates; i++) {
|
||||
Init(kSampleRateHz[i]);
|
||||
RunStableStartup();
|
||||
int device_buf = MapBufferSizeToSamples(kDeviceBufMs,
|
||||
extended_filter == 1);
|
||||
|
||||
// Normal state. We are currently not in a non-causal state.
|
||||
bool non_causal = false;
|
||||
|
||||
// Run 1 s and replace device buffer size with 500 ms every 100 ms.
|
||||
for (int j = 0; j < 100; j++) {
|
||||
int system_delay_before_calls = WebRtcAec_system_delay(self_->aec);
|
||||
int device_buf_ms = j % 10 == 0 ? 500 : kDeviceBufMs;
|
||||
RenderAndCapture(device_buf_ms);
|
||||
|
||||
// Check for non-causality.
|
||||
if (device_buf - WebRtcAec_system_delay(self_->aec) < PART_LEN) {
|
||||
non_causal = true;
|
||||
}
|
||||
EXPECT_FALSE(non_causal);
|
||||
EXPECT_EQ(system_delay_before_calls,
|
||||
WebRtcAec_system_delay(self_->aec));
|
||||
|
||||
// Verify that the system delay is non-negative.
|
||||
EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SystemDelayTest, CorrectImpactWhenTogglingDeviceBufferValues) {
|
||||
// This test aims at verifying that the system delay is "unaffected" by
|
||||
// toggling values reported by the device.
|
||||
// The test is constructed such that every other device buffer value is zero
|
||||
// and then 2 * |kDeviceBufMs|, hence the size is constant on the average. The
|
||||
// zero values will force us into a non-causal state and thereby lowering the
|
||||
// system delay until we basically run out of data. Once that happens the
|
||||
// buffer will be stuffed.
|
||||
// TODO(bjornv): This test will have a better impact if we verified that the
|
||||
// delay estimate goes up when the system delay goes down to meet the average
|
||||
// device buffer size.
|
||||
|
||||
// This test does not apply if DA-AEC is enabled and extended_filter mode
|
||||
// disabled.
|
||||
for (int extended_filter = 0; extended_filter <= 1; ++extended_filter) {
|
||||
WebRtcAec_enable_extended_filter(self_->aec, extended_filter);
|
||||
EXPECT_EQ(extended_filter, WebRtcAec_extended_filter_enabled(self_->aec));
|
||||
for (int da_aec = 0; da_aec <= 1; ++da_aec) {
|
||||
WebRtcAec_enable_delay_agnostic(self_->aec, da_aec);
|
||||
EXPECT_EQ(da_aec, WebRtcAec_delay_agnostic_enabled(self_->aec));
|
||||
if (extended_filter == 0 && da_aec == 1) {
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 0; i < kNumSampleRates; i++) {
|
||||
Init(kSampleRateHz[i]);
|
||||
RunStableStartup();
|
||||
const int device_buf = MapBufferSizeToSamples(kDeviceBufMs,
|
||||
extended_filter == 1);
|
||||
|
||||
// Normal state. We are currently not in a non-causal state.
|
||||
bool non_causal = false;
|
||||
|
||||
// Loop through 100 frames (both render and capture), which equals 1 s
|
||||
// of data. Every odd frame we set the device buffer size to
|
||||
// 2 * |kDeviceBufMs| and even frames we set the device buffer size to
|
||||
// zero.
|
||||
for (int j = 0; j < 100; j++) {
|
||||
int system_delay_before_calls = WebRtcAec_system_delay(self_->aec);
|
||||
int device_buf_ms = 2 * (j % 2) * kDeviceBufMs;
|
||||
RenderAndCapture(device_buf_ms);
|
||||
|
||||
// Check for non-causality, compared with the average device buffer
|
||||
// size.
|
||||
non_causal |= (device_buf - WebRtcAec_system_delay(self_->aec) < 64);
|
||||
EXPECT_GE(system_delay_before_calls,
|
||||
WebRtcAec_system_delay(self_->aec));
|
||||
|
||||
// Verify that the system delay is non-negative.
|
||||
EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
|
||||
}
|
||||
// Verify we are not in a non-causal state.
|
||||
EXPECT_FALSE(non_causal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace webrtc
|
||||
@ -1,525 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/adaptive_fir_filter.h"
|
||||
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
#include "webrtc/typedefs.h"
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/fft_data.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace aec3 {
|
||||
|
||||
// Computes and stores the frequency response of the filter.
|
||||
void UpdateFrequencyResponse(
|
||||
rtc::ArrayView<const FftData> H,
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>* H2) {
|
||||
RTC_DCHECK_EQ(H.size(), H2->size());
|
||||
for (size_t k = 0; k < H.size(); ++k) {
|
||||
std::transform(H[k].re.begin(), H[k].re.end(), H[k].im.begin(),
|
||||
(*H2)[k].begin(),
|
||||
[](float a, float b) { return a * a + b * b; });
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
// Computes and stores the frequency response of the filter.
|
||||
void UpdateFrequencyResponse_NEON(
|
||||
rtc::ArrayView<const FftData> H,
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>* H2) {
|
||||
RTC_DCHECK_EQ(H.size(), H2->size());
|
||||
for (size_t k = 0; k < H.size(); ++k) {
|
||||
for (size_t j = 0; j < kFftLengthBy2; j += 4) {
|
||||
const float32x4_t re = vld1q_f32(&H[k].re[j]);
|
||||
const float32x4_t im = vld1q_f32(&H[k].im[j]);
|
||||
float32x4_t H2_k_j = vmulq_f32(re, re);
|
||||
H2_k_j = vmlaq_f32(H2_k_j, im, im);
|
||||
vst1q_f32(&(*H2)[k][j], H2_k_j);
|
||||
}
|
||||
(*H2)[k][kFftLengthBy2] = H[k].re[kFftLengthBy2] * H[k].re[kFftLengthBy2] +
|
||||
H[k].im[kFftLengthBy2] * H[k].im[kFftLengthBy2];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
// Computes and stores the frequency response of the filter.
|
||||
void UpdateFrequencyResponse_SSE2(
|
||||
rtc::ArrayView<const FftData> H,
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>* H2) {
|
||||
RTC_DCHECK_EQ(H.size(), H2->size());
|
||||
for (size_t k = 0; k < H.size(); ++k) {
|
||||
for (size_t j = 0; j < kFftLengthBy2; j += 4) {
|
||||
const __m128 re = _mm_loadu_ps(&H[k].re[j]);
|
||||
const __m128 re2 = _mm_mul_ps(re, re);
|
||||
const __m128 im = _mm_loadu_ps(&H[k].im[j]);
|
||||
const __m128 im2 = _mm_mul_ps(im, im);
|
||||
const __m128 H2_k_j = _mm_add_ps(re2, im2);
|
||||
_mm_storeu_ps(&(*H2)[k][j], H2_k_j);
|
||||
}
|
||||
(*H2)[k][kFftLengthBy2] = H[k].re[kFftLengthBy2] * H[k].re[kFftLengthBy2] +
|
||||
H[k].im[kFftLengthBy2] * H[k].im[kFftLengthBy2];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Computes and stores the echo return loss estimate of the filter, which is the
|
||||
// sum of the partition frequency responses.
|
||||
void UpdateErlEstimator(
|
||||
const std::vector<std::array<float, kFftLengthBy2Plus1>>& H2,
|
||||
std::array<float, kFftLengthBy2Plus1>* erl) {
|
||||
erl->fill(0.f);
|
||||
for (auto& H2_j : H2) {
|
||||
std::transform(H2_j.begin(), H2_j.end(), erl->begin(), erl->begin(),
|
||||
std::plus<float>());
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
// Computes and stores the echo return loss estimate of the filter, which is the
|
||||
// sum of the partition frequency responses.
|
||||
void UpdateErlEstimator_NEON(
|
||||
const std::vector<std::array<float, kFftLengthBy2Plus1>>& H2,
|
||||
std::array<float, kFftLengthBy2Plus1>* erl) {
|
||||
erl->fill(0.f);
|
||||
for (auto& H2_j : H2) {
|
||||
for (size_t k = 0; k < kFftLengthBy2; k += 4) {
|
||||
const float32x4_t H2_j_k = vld1q_f32(&H2_j[k]);
|
||||
float32x4_t erl_k = vld1q_f32(&(*erl)[k]);
|
||||
erl_k = vaddq_f32(erl_k, H2_j_k);
|
||||
vst1q_f32(&(*erl)[k], erl_k);
|
||||
}
|
||||
(*erl)[kFftLengthBy2] += H2_j[kFftLengthBy2];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
// Computes and stores the echo return loss estimate of the filter, which is the
|
||||
// sum of the partition frequency responses.
|
||||
void UpdateErlEstimator_SSE2(
|
||||
const std::vector<std::array<float, kFftLengthBy2Plus1>>& H2,
|
||||
std::array<float, kFftLengthBy2Plus1>* erl) {
|
||||
erl->fill(0.f);
|
||||
for (auto& H2_j : H2) {
|
||||
for (size_t k = 0; k < kFftLengthBy2; k += 4) {
|
||||
const __m128 H2_j_k = _mm_loadu_ps(&H2_j[k]);
|
||||
__m128 erl_k = _mm_loadu_ps(&(*erl)[k]);
|
||||
erl_k = _mm_add_ps(erl_k, H2_j_k);
|
||||
_mm_storeu_ps(&(*erl)[k], erl_k);
|
||||
}
|
||||
(*erl)[kFftLengthBy2] += H2_j[kFftLengthBy2];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Adapts the filter partitions as H(t+1)=H(t)+G(t)*conj(X(t)).
|
||||
void AdaptPartitions(const RenderBuffer& render_buffer,
|
||||
const FftData& G,
|
||||
rtc::ArrayView<FftData> H) {
|
||||
rtc::ArrayView<const FftData> render_buffer_data = render_buffer.Buffer();
|
||||
size_t index = render_buffer.Position();
|
||||
for (auto& H_j : H) {
|
||||
const FftData& X = render_buffer_data[index];
|
||||
for (size_t k = 0; k < kFftLengthBy2Plus1; ++k) {
|
||||
H_j.re[k] += X.re[k] * G.re[k] + X.im[k] * G.im[k];
|
||||
H_j.im[k] += X.re[k] * G.im[k] - X.im[k] * G.re[k];
|
||||
}
|
||||
|
||||
index = index < (render_buffer_data.size() - 1) ? index + 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
// Adapts the filter partitions. (NEON variant)
|
||||
void AdaptPartitions_NEON(const RenderBuffer& render_buffer,
|
||||
const FftData& G,
|
||||
rtc::ArrayView<FftData> H) {
|
||||
rtc::ArrayView<const FftData> render_buffer_data = render_buffer.Buffer();
|
||||
const int lim1 =
|
||||
std::min(render_buffer_data.size() - render_buffer.Position(), H.size());
|
||||
const int lim2 = H.size();
|
||||
constexpr int kNumFourBinBands = kFftLengthBy2 / 4;
|
||||
FftData* H_j = &H[0];
|
||||
const FftData* X = &render_buffer_data[render_buffer.Position()];
|
||||
int limit = lim1;
|
||||
int j = 0;
|
||||
do {
|
||||
for (; j < limit; ++j, ++H_j, ++X) {
|
||||
for (int k = 0, n = 0; n < kNumFourBinBands; ++n, k += 4) {
|
||||
const float32x4_t G_re = vld1q_f32(&G.re[k]);
|
||||
const float32x4_t G_im = vld1q_f32(&G.im[k]);
|
||||
const float32x4_t X_re = vld1q_f32(&X->re[k]);
|
||||
const float32x4_t X_im = vld1q_f32(&X->im[k]);
|
||||
const float32x4_t H_re = vld1q_f32(&H_j->re[k]);
|
||||
const float32x4_t H_im = vld1q_f32(&H_j->im[k]);
|
||||
const float32x4_t a = vmulq_f32(X_re, G_re);
|
||||
const float32x4_t e = vmlaq_f32(a, X_im, G_im);
|
||||
const float32x4_t c = vmulq_f32(X_re, G_im);
|
||||
const float32x4_t f = vmlsq_f32(c, X_im, G_re);
|
||||
const float32x4_t g = vaddq_f32(H_re, e);
|
||||
const float32x4_t h = vaddq_f32(H_im, f);
|
||||
|
||||
vst1q_f32(&H_j->re[k], g);
|
||||
vst1q_f32(&H_j->im[k], h);
|
||||
}
|
||||
}
|
||||
|
||||
X = &render_buffer_data[0];
|
||||
limit = lim2;
|
||||
} while (j < lim2);
|
||||
|
||||
H_j = &H[0];
|
||||
X = &render_buffer_data[render_buffer.Position()];
|
||||
limit = lim1;
|
||||
j = 0;
|
||||
do {
|
||||
for (; j < limit; ++j, ++H_j, ++X) {
|
||||
H_j->re[kFftLengthBy2] += X->re[kFftLengthBy2] * G.re[kFftLengthBy2] +
|
||||
X->im[kFftLengthBy2] * G.im[kFftLengthBy2];
|
||||
H_j->im[kFftLengthBy2] += X->re[kFftLengthBy2] * G.im[kFftLengthBy2] -
|
||||
X->im[kFftLengthBy2] * G.re[kFftLengthBy2];
|
||||
}
|
||||
|
||||
X = &render_buffer_data[0];
|
||||
limit = lim2;
|
||||
} while (j < lim2);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
// Adapts the filter partitions. (SSE2 variant)
|
||||
void AdaptPartitions_SSE2(const RenderBuffer& render_buffer,
|
||||
const FftData& G,
|
||||
rtc::ArrayView<FftData> H) {
|
||||
rtc::ArrayView<const FftData> render_buffer_data = render_buffer.Buffer();
|
||||
const int lim1 =
|
||||
std::min(render_buffer_data.size() - render_buffer.Position(), H.size());
|
||||
const int lim2 = H.size();
|
||||
constexpr int kNumFourBinBands = kFftLengthBy2 / 4;
|
||||
FftData* H_j;
|
||||
const FftData* X;
|
||||
int limit;
|
||||
int j;
|
||||
for (int k = 0, n = 0; n < kNumFourBinBands; ++n, k += 4) {
|
||||
const __m128 G_re = _mm_loadu_ps(&G.re[k]);
|
||||
const __m128 G_im = _mm_loadu_ps(&G.im[k]);
|
||||
|
||||
H_j = &H[0];
|
||||
X = &render_buffer_data[render_buffer.Position()];
|
||||
limit = lim1;
|
||||
j = 0;
|
||||
do {
|
||||
for (; j < limit; ++j, ++H_j, ++X) {
|
||||
const __m128 X_re = _mm_loadu_ps(&X->re[k]);
|
||||
const __m128 X_im = _mm_loadu_ps(&X->im[k]);
|
||||
const __m128 H_re = _mm_loadu_ps(&H_j->re[k]);
|
||||
const __m128 H_im = _mm_loadu_ps(&H_j->im[k]);
|
||||
const __m128 a = _mm_mul_ps(X_re, G_re);
|
||||
const __m128 b = _mm_mul_ps(X_im, G_im);
|
||||
const __m128 c = _mm_mul_ps(X_re, G_im);
|
||||
const __m128 d = _mm_mul_ps(X_im, G_re);
|
||||
const __m128 e = _mm_add_ps(a, b);
|
||||
const __m128 f = _mm_sub_ps(c, d);
|
||||
const __m128 g = _mm_add_ps(H_re, e);
|
||||
const __m128 h = _mm_add_ps(H_im, f);
|
||||
_mm_storeu_ps(&H_j->re[k], g);
|
||||
_mm_storeu_ps(&H_j->im[k], h);
|
||||
}
|
||||
|
||||
X = &render_buffer_data[0];
|
||||
limit = lim2;
|
||||
} while (j < lim2);
|
||||
}
|
||||
|
||||
H_j = &H[0];
|
||||
X = &render_buffer_data[render_buffer.Position()];
|
||||
limit = lim1;
|
||||
j = 0;
|
||||
do {
|
||||
for (; j < limit; ++j, ++H_j, ++X) {
|
||||
H_j->re[kFftLengthBy2] += X->re[kFftLengthBy2] * G.re[kFftLengthBy2] +
|
||||
X->im[kFftLengthBy2] * G.im[kFftLengthBy2];
|
||||
H_j->im[kFftLengthBy2] += X->re[kFftLengthBy2] * G.im[kFftLengthBy2] -
|
||||
X->im[kFftLengthBy2] * G.re[kFftLengthBy2];
|
||||
}
|
||||
|
||||
X = &render_buffer_data[0];
|
||||
limit = lim2;
|
||||
} while (j < lim2);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Produces the filter output.
|
||||
void ApplyFilter(const RenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const FftData> H,
|
||||
FftData* S) {
|
||||
S->re.fill(0.f);
|
||||
S->im.fill(0.f);
|
||||
|
||||
rtc::ArrayView<const FftData> render_buffer_data = render_buffer.Buffer();
|
||||
size_t index = render_buffer.Position();
|
||||
for (auto& H_j : H) {
|
||||
const FftData& X = render_buffer_data[index];
|
||||
for (size_t k = 0; k < kFftLengthBy2Plus1; ++k) {
|
||||
S->re[k] += X.re[k] * H_j.re[k] - X.im[k] * H_j.im[k];
|
||||
S->im[k] += X.re[k] * H_j.im[k] + X.im[k] * H_j.re[k];
|
||||
}
|
||||
index = index < (render_buffer_data.size() - 1) ? index + 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
// Produces the filter output (NEON variant).
|
||||
void ApplyFilter_NEON(const RenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const FftData> H,
|
||||
FftData* S) {
|
||||
RTC_DCHECK_GE(H.size(), H.size() - 1);
|
||||
S->re.fill(0.f);
|
||||
S->im.fill(0.f);
|
||||
|
||||
rtc::ArrayView<const FftData> render_buffer_data = render_buffer.Buffer();
|
||||
const int lim1 =
|
||||
std::min(render_buffer_data.size() - render_buffer.Position(), H.size());
|
||||
const int lim2 = H.size();
|
||||
constexpr int kNumFourBinBands = kFftLengthBy2 / 4;
|
||||
const FftData* H_j = &H[0];
|
||||
const FftData* X = &render_buffer_data[render_buffer.Position()];
|
||||
|
||||
int j = 0;
|
||||
int limit = lim1;
|
||||
do {
|
||||
for (; j < limit; ++j, ++H_j, ++X) {
|
||||
for (int k = 0, n = 0; n < kNumFourBinBands; ++n, k += 4) {
|
||||
const float32x4_t X_re = vld1q_f32(&X->re[k]);
|
||||
const float32x4_t X_im = vld1q_f32(&X->im[k]);
|
||||
const float32x4_t H_re = vld1q_f32(&H_j->re[k]);
|
||||
const float32x4_t H_im = vld1q_f32(&H_j->im[k]);
|
||||
const float32x4_t S_re = vld1q_f32(&S->re[k]);
|
||||
const float32x4_t S_im = vld1q_f32(&S->im[k]);
|
||||
const float32x4_t a = vmulq_f32(X_re, H_re);
|
||||
const float32x4_t e = vmlsq_f32(a, X_im, H_im);
|
||||
const float32x4_t c = vmulq_f32(X_re, H_im);
|
||||
const float32x4_t f = vmlaq_f32(c, X_im, H_re);
|
||||
const float32x4_t g = vaddq_f32(S_re, e);
|
||||
const float32x4_t h = vaddq_f32(S_im, f);
|
||||
vst1q_f32(&S->re[k], g);
|
||||
vst1q_f32(&S->im[k], h);
|
||||
}
|
||||
}
|
||||
limit = lim2;
|
||||
X = &render_buffer_data[0];
|
||||
} while (j < lim2);
|
||||
|
||||
H_j = &H[0];
|
||||
X = &render_buffer_data[render_buffer.Position()];
|
||||
j = 0;
|
||||
limit = lim1;
|
||||
do {
|
||||
for (; j < limit; ++j, ++H_j, ++X) {
|
||||
S->re[kFftLengthBy2] += X->re[kFftLengthBy2] * H_j->re[kFftLengthBy2] -
|
||||
X->im[kFftLengthBy2] * H_j->im[kFftLengthBy2];
|
||||
S->im[kFftLengthBy2] += X->re[kFftLengthBy2] * H_j->im[kFftLengthBy2] +
|
||||
X->im[kFftLengthBy2] * H_j->re[kFftLengthBy2];
|
||||
}
|
||||
limit = lim2;
|
||||
X = &render_buffer_data[0];
|
||||
} while (j < lim2);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
// Produces the filter output (SSE2 variant).
|
||||
void ApplyFilter_SSE2(const RenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const FftData> H,
|
||||
FftData* S) {
|
||||
RTC_DCHECK_GE(H.size(), H.size() - 1);
|
||||
S->re.fill(0.f);
|
||||
S->im.fill(0.f);
|
||||
|
||||
rtc::ArrayView<const FftData> render_buffer_data = render_buffer.Buffer();
|
||||
const int lim1 =
|
||||
std::min(render_buffer_data.size() - render_buffer.Position(), H.size());
|
||||
const int lim2 = H.size();
|
||||
constexpr int kNumFourBinBands = kFftLengthBy2 / 4;
|
||||
const FftData* H_j = &H[0];
|
||||
const FftData* X = &render_buffer_data[render_buffer.Position()];
|
||||
|
||||
int j = 0;
|
||||
int limit = lim1;
|
||||
do {
|
||||
for (; j < limit; ++j, ++H_j, ++X) {
|
||||
for (int k = 0, n = 0; n < kNumFourBinBands; ++n, k += 4) {
|
||||
const __m128 X_re = _mm_loadu_ps(&X->re[k]);
|
||||
const __m128 X_im = _mm_loadu_ps(&X->im[k]);
|
||||
const __m128 H_re = _mm_loadu_ps(&H_j->re[k]);
|
||||
const __m128 H_im = _mm_loadu_ps(&H_j->im[k]);
|
||||
const __m128 S_re = _mm_loadu_ps(&S->re[k]);
|
||||
const __m128 S_im = _mm_loadu_ps(&S->im[k]);
|
||||
const __m128 a = _mm_mul_ps(X_re, H_re);
|
||||
const __m128 b = _mm_mul_ps(X_im, H_im);
|
||||
const __m128 c = _mm_mul_ps(X_re, H_im);
|
||||
const __m128 d = _mm_mul_ps(X_im, H_re);
|
||||
const __m128 e = _mm_sub_ps(a, b);
|
||||
const __m128 f = _mm_add_ps(c, d);
|
||||
const __m128 g = _mm_add_ps(S_re, e);
|
||||
const __m128 h = _mm_add_ps(S_im, f);
|
||||
_mm_storeu_ps(&S->re[k], g);
|
||||
_mm_storeu_ps(&S->im[k], h);
|
||||
}
|
||||
}
|
||||
limit = lim2;
|
||||
X = &render_buffer_data[0];
|
||||
} while (j < lim2);
|
||||
|
||||
H_j = &H[0];
|
||||
X = &render_buffer_data[render_buffer.Position()];
|
||||
j = 0;
|
||||
limit = lim1;
|
||||
do {
|
||||
for (; j < limit; ++j, ++H_j, ++X) {
|
||||
S->re[kFftLengthBy2] += X->re[kFftLengthBy2] * H_j->re[kFftLengthBy2] -
|
||||
X->im[kFftLengthBy2] * H_j->im[kFftLengthBy2];
|
||||
S->im[kFftLengthBy2] += X->re[kFftLengthBy2] * H_j->im[kFftLengthBy2] +
|
||||
X->im[kFftLengthBy2] * H_j->re[kFftLengthBy2];
|
||||
}
|
||||
limit = lim2;
|
||||
X = &render_buffer_data[0];
|
||||
} while (j < lim2);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace aec3
|
||||
|
||||
AdaptiveFirFilter::AdaptiveFirFilter(size_t size_partitions,
|
||||
Aec3Optimization optimization,
|
||||
ApmDataDumper* data_dumper)
|
||||
: data_dumper_(data_dumper),
|
||||
fft_(),
|
||||
optimization_(optimization),
|
||||
H_(size_partitions),
|
||||
H2_(size_partitions, std::array<float, kFftLengthBy2Plus1>()) {
|
||||
RTC_DCHECK(data_dumper_);
|
||||
|
||||
h_.fill(0.f);
|
||||
for (auto& H_j : H_) {
|
||||
H_j.Clear();
|
||||
}
|
||||
for (auto& H2_k : H2_) {
|
||||
H2_k.fill(0.f);
|
||||
}
|
||||
erl_.fill(0.f);
|
||||
}
|
||||
|
||||
AdaptiveFirFilter::~AdaptiveFirFilter() = default;
|
||||
|
||||
void AdaptiveFirFilter::HandleEchoPathChange() {
|
||||
h_.fill(0.f);
|
||||
for (auto& H_j : H_) {
|
||||
H_j.Clear();
|
||||
}
|
||||
for (auto& H2_k : H2_) {
|
||||
H2_k.fill(0.f);
|
||||
}
|
||||
erl_.fill(0.f);
|
||||
}
|
||||
|
||||
void AdaptiveFirFilter::Filter(const RenderBuffer& render_buffer,
|
||||
FftData* S) const {
|
||||
RTC_DCHECK(S);
|
||||
switch (optimization_) {
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
case Aec3Optimization::kSse2:
|
||||
aec3::ApplyFilter_SSE2(render_buffer, H_, S);
|
||||
break;
|
||||
#endif
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
case Aec3Optimization::kNeon:
|
||||
aec3::ApplyFilter_NEON(render_buffer, H_, S);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
aec3::ApplyFilter(render_buffer, H_, S);
|
||||
}
|
||||
}
|
||||
|
||||
void AdaptiveFirFilter::Adapt(const RenderBuffer& render_buffer,
|
||||
const FftData& G) {
|
||||
// Adapt the filter.
|
||||
switch (optimization_) {
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
case Aec3Optimization::kSse2:
|
||||
aec3::AdaptPartitions_SSE2(render_buffer, G, H_);
|
||||
break;
|
||||
#endif
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
case Aec3Optimization::kNeon:
|
||||
aec3::AdaptPartitions_NEON(render_buffer, G, H_);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
aec3::AdaptPartitions(render_buffer, G, H_);
|
||||
}
|
||||
|
||||
// Constrain the filter partitions in a cyclic manner.
|
||||
Constrain();
|
||||
|
||||
// Update the frequency response and echo return loss for the filter.
|
||||
switch (optimization_) {
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
case Aec3Optimization::kSse2:
|
||||
aec3::UpdateFrequencyResponse_SSE2(H_, &H2_);
|
||||
aec3::UpdateErlEstimator_SSE2(H2_, &erl_);
|
||||
break;
|
||||
#endif
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
case Aec3Optimization::kNeon:
|
||||
aec3::UpdateFrequencyResponse_NEON(H_, &H2_);
|
||||
aec3::UpdateErlEstimator_NEON(H2_, &erl_);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
aec3::UpdateFrequencyResponse(H_, &H2_);
|
||||
aec3::UpdateErlEstimator(H2_, &erl_);
|
||||
}
|
||||
}
|
||||
|
||||
// Constrains the a partiton of the frequency domain filter to be limited in
|
||||
// time via setting the relevant time-domain coefficients to zero.
|
||||
void AdaptiveFirFilter::Constrain() {
|
||||
std::array<float, kFftLength> h;
|
||||
fft_.Ifft(H_[partition_to_constrain_], &h);
|
||||
|
||||
static constexpr float kScale = 1.0f / kFftLengthBy2;
|
||||
std::for_each(h.begin(), h.begin() + kFftLengthBy2,
|
||||
[](float& a) { a *= kScale; });
|
||||
std::fill(h.begin() + kFftLengthBy2, h.end(), 0.f);
|
||||
|
||||
std::copy(h.begin(), h.begin() + kFftLengthBy2,
|
||||
h_.begin() + partition_to_constrain_ * kFftLengthBy2);
|
||||
|
||||
fft_.Fft(&h, &H_[partition_to_constrain_]);
|
||||
|
||||
partition_to_constrain_ = partition_to_constrain_ < (H_.size() - 1)
|
||||
? partition_to_constrain_ + 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ADAPTIVE_FIR_FILTER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ADAPTIVE_FIR_FILTER_H_
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_fft.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/fft_data.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace aec3 {
|
||||
// Computes and stores the frequency response of the filter.
|
||||
void UpdateFrequencyResponse(
|
||||
rtc::ArrayView<const FftData> H,
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>* H2);
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
void UpdateFrequencyResponse_NEON(
|
||||
rtc::ArrayView<const FftData> H,
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>* H2);
|
||||
#endif
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
void UpdateFrequencyResponse_SSE2(
|
||||
rtc::ArrayView<const FftData> H,
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>* H2);
|
||||
#endif
|
||||
|
||||
// Computes and stores the echo return loss estimate of the filter, which is the
|
||||
// sum of the partition frequency responses.
|
||||
void UpdateErlEstimator(
|
||||
const std::vector<std::array<float, kFftLengthBy2Plus1>>& H2,
|
||||
std::array<float, kFftLengthBy2Plus1>* erl);
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
void UpdateErlEstimator_NEON(
|
||||
const std::vector<std::array<float, kFftLengthBy2Plus1>>& H2,
|
||||
std::array<float, kFftLengthBy2Plus1>* erl);
|
||||
#endif
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
void UpdateErlEstimator_SSE2(
|
||||
const std::vector<std::array<float, kFftLengthBy2Plus1>>& H2,
|
||||
std::array<float, kFftLengthBy2Plus1>* erl);
|
||||
#endif
|
||||
|
||||
// Adapts the filter partitions.
|
||||
void AdaptPartitions(const RenderBuffer& render_buffer,
|
||||
const FftData& G,
|
||||
rtc::ArrayView<FftData> H);
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
void AdaptPartitions_NEON(const RenderBuffer& render_buffer,
|
||||
const FftData& G,
|
||||
rtc::ArrayView<FftData> H);
|
||||
#endif
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
void AdaptPartitions_SSE2(const RenderBuffer& render_buffer,
|
||||
const FftData& G,
|
||||
rtc::ArrayView<FftData> H);
|
||||
#endif
|
||||
|
||||
// Produces the filter output.
|
||||
void ApplyFilter(const RenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const FftData> H,
|
||||
FftData* S);
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
void ApplyFilter_NEON(const RenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const FftData> H,
|
||||
FftData* S);
|
||||
#endif
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
void ApplyFilter_SSE2(const RenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const FftData> H,
|
||||
FftData* S);
|
||||
#endif
|
||||
|
||||
} // namespace aec3
|
||||
|
||||
// Provides a frequency domain adaptive filter functionality.
|
||||
class AdaptiveFirFilter {
|
||||
public:
|
||||
AdaptiveFirFilter(size_t size_partitions,
|
||||
Aec3Optimization optimization,
|
||||
ApmDataDumper* data_dumper);
|
||||
|
||||
~AdaptiveFirFilter();
|
||||
|
||||
// Produces the output of the filter.
|
||||
void Filter(const RenderBuffer& render_buffer, FftData* S) const;
|
||||
|
||||
// Adapts the filter.
|
||||
void Adapt(const RenderBuffer& render_buffer, const FftData& G);
|
||||
|
||||
// Receives reports that known echo path changes have occured and adjusts
|
||||
// the filter adaptation accordingly.
|
||||
void HandleEchoPathChange();
|
||||
|
||||
// Returns the filter size.
|
||||
size_t SizePartitions() const { return H_.size(); }
|
||||
|
||||
// Returns the filter based echo return loss.
|
||||
const std::array<float, kFftLengthBy2Plus1>& Erl() const { return erl_; }
|
||||
|
||||
// Returns the frequency responses for the filter partitions.
|
||||
const std::vector<std::array<float, kFftLengthBy2Plus1>>&
|
||||
FilterFrequencyResponse() const {
|
||||
return H2_;
|
||||
}
|
||||
|
||||
// Returns the estimate of the impulse response.
|
||||
const std::array<float, kAdaptiveFilterTimeDomainLength>&
|
||||
FilterImpulseResponse() const {
|
||||
return h_;
|
||||
}
|
||||
|
||||
void DumpFilter(const char* name) {
|
||||
for (auto& H : H_) {
|
||||
data_dumper_->DumpRaw(name, H.re);
|
||||
data_dumper_->DumpRaw(name, H.im);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Constrain the filter partitions in a cyclic manner.
|
||||
void Constrain();
|
||||
|
||||
ApmDataDumper* const data_dumper_;
|
||||
const Aec3Fft fft_;
|
||||
const Aec3Optimization optimization_;
|
||||
std::vector<FftData> H_;
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> H2_;
|
||||
std::array<float, kAdaptiveFilterTimeDomainLength> h_;
|
||||
std::array<float, kFftLengthBy2Plus1> erl_;
|
||||
size_t partition_to_constrain_ = 0;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AdaptiveFirFilter);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ADAPTIVE_FIR_FILTER_H_
|
||||
@ -1,379 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/adaptive_fir_filter.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include "webrtc/typedefs.h"
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_fft.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec_state.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_signal_analyzer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/shadow_filter_update_gain.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/modules/audio_processing/test/echo_canceller_test_tools.h"
|
||||
#include "webrtc/rtc_base/arraysize.h"
|
||||
#include "webrtc/rtc_base/random.h"
|
||||
#include "webrtc/rtc_base/safe_minmax.h"
|
||||
#include "webrtc/system_wrappers/include/cpu_features_wrapper.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace aec3 {
|
||||
namespace {
|
||||
|
||||
std::string ProduceDebugText(size_t delay) {
|
||||
std::ostringstream ss;
|
||||
ss << ", Delay: " << delay;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
// Verifies that the optimized methods for filter adaptation are similar to
|
||||
// their reference counterparts.
|
||||
TEST(AdaptiveFirFilter, FilterAdaptationNeonOptimizations) {
|
||||
RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 12,
|
||||
std::vector<size_t>(1, 12));
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
|
||||
FftData S_C;
|
||||
FftData S_NEON;
|
||||
FftData G;
|
||||
Aec3Fft fft;
|
||||
std::vector<FftData> H_C(10);
|
||||
std::vector<FftData> H_NEON(10);
|
||||
for (auto& H_j : H_C) {
|
||||
H_j.Clear();
|
||||
}
|
||||
for (auto& H_j : H_NEON) {
|
||||
H_j.Clear();
|
||||
}
|
||||
|
||||
for (size_t k = 0; k < 30; ++k) {
|
||||
RandomizeSampleVector(&random_generator, x[0]);
|
||||
render_buffer.Insert(x);
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < G.re.size(); ++j) {
|
||||
G.re[j] = j / 10001.f;
|
||||
}
|
||||
for (size_t j = 1; j < G.im.size() - 1; ++j) {
|
||||
G.im[j] = j / 20001.f;
|
||||
}
|
||||
G.im[0] = 0.f;
|
||||
G.im[G.im.size() - 1] = 0.f;
|
||||
|
||||
AdaptPartitions_NEON(render_buffer, G, H_NEON);
|
||||
AdaptPartitions(render_buffer, G, H_C);
|
||||
AdaptPartitions_NEON(render_buffer, G, H_NEON);
|
||||
AdaptPartitions(render_buffer, G, H_C);
|
||||
|
||||
for (size_t l = 0; l < H_C.size(); ++l) {
|
||||
for (size_t j = 0; j < H_C[l].im.size(); ++j) {
|
||||
EXPECT_NEAR(H_C[l].re[j], H_NEON[l].re[j], fabs(H_C[l].re[j] * 0.00001f));
|
||||
EXPECT_NEAR(H_C[l].im[j], H_NEON[l].im[j], fabs(H_C[l].im[j] * 0.00001f));
|
||||
}
|
||||
}
|
||||
|
||||
ApplyFilter_NEON(render_buffer, H_NEON, &S_NEON);
|
||||
ApplyFilter(render_buffer, H_C, &S_C);
|
||||
for (size_t j = 0; j < S_C.re.size(); ++j) {
|
||||
EXPECT_NEAR(S_C.re[j], S_NEON.re[j], fabs(S_C.re[j] * 0.00001f));
|
||||
EXPECT_NEAR(S_C.im[j], S_NEON.im[j], fabs(S_C.re[j] * 0.00001f));
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the optimized method for frequency response computation is
|
||||
// bitexact to the reference counterpart.
|
||||
TEST(AdaptiveFirFilter, UpdateFrequencyResponseNeonOptimization) {
|
||||
const size_t kNumPartitions = 12;
|
||||
std::vector<FftData> H(kNumPartitions);
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> H2(kNumPartitions);
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> H2_NEON(kNumPartitions);
|
||||
|
||||
for (size_t j = 0; j < H.size(); ++j) {
|
||||
for (size_t k = 0; k < H[j].re.size(); ++k) {
|
||||
H[j].re[k] = k + j / 3.f;
|
||||
H[j].im[k] = j + k / 7.f;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateFrequencyResponse(H, &H2);
|
||||
UpdateFrequencyResponse_NEON(H, &H2_NEON);
|
||||
|
||||
for (size_t j = 0; j < H2.size(); ++j) {
|
||||
for (size_t k = 0; k < H[j].re.size(); ++k) {
|
||||
EXPECT_FLOAT_EQ(H2[j][k], H2_NEON[j][k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the optimized method for echo return loss computation is
|
||||
// bitexact to the reference counterpart.
|
||||
TEST(AdaptiveFirFilter, UpdateErlNeonOptimization) {
|
||||
const size_t kNumPartitions = 12;
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> H2(kNumPartitions);
|
||||
std::array<float, kFftLengthBy2Plus1> erl;
|
||||
std::array<float, kFftLengthBy2Plus1> erl_NEON;
|
||||
|
||||
for (size_t j = 0; j < H2.size(); ++j) {
|
||||
for (size_t k = 0; k < H2[j].size(); ++k) {
|
||||
H2[j][k] = k + j / 3.f;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateErlEstimator(H2, &erl);
|
||||
UpdateErlEstimator_NEON(H2, &erl_NEON);
|
||||
|
||||
for (size_t j = 0; j < erl.size(); ++j) {
|
||||
EXPECT_FLOAT_EQ(erl[j], erl_NEON[j]);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
// Verifies that the optimized methods for filter adaptation are bitexact to
|
||||
// their reference counterparts.
|
||||
TEST(AdaptiveFirFilter, FilterAdaptationSse2Optimizations) {
|
||||
bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0);
|
||||
if (use_sse2) {
|
||||
RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 12,
|
||||
std::vector<size_t>(1, 12));
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
|
||||
FftData S_C;
|
||||
FftData S_SSE2;
|
||||
FftData G;
|
||||
Aec3Fft fft;
|
||||
std::vector<FftData> H_C(10);
|
||||
std::vector<FftData> H_SSE2(10);
|
||||
for (auto& H_j : H_C) {
|
||||
H_j.Clear();
|
||||
}
|
||||
for (auto& H_j : H_SSE2) {
|
||||
H_j.Clear();
|
||||
}
|
||||
|
||||
for (size_t k = 0; k < 500; ++k) {
|
||||
RandomizeSampleVector(&random_generator, x[0]);
|
||||
render_buffer.Insert(x);
|
||||
|
||||
ApplyFilter_SSE2(render_buffer, H_SSE2, &S_SSE2);
|
||||
ApplyFilter(render_buffer, H_C, &S_C);
|
||||
for (size_t j = 0; j < S_C.re.size(); ++j) {
|
||||
EXPECT_FLOAT_EQ(S_C.re[j], S_SSE2.re[j]);
|
||||
EXPECT_FLOAT_EQ(S_C.im[j], S_SSE2.im[j]);
|
||||
}
|
||||
|
||||
std::for_each(G.re.begin(), G.re.end(),
|
||||
[&](float& a) { a = random_generator.Rand<float>(); });
|
||||
std::for_each(G.im.begin(), G.im.end(),
|
||||
[&](float& a) { a = random_generator.Rand<float>(); });
|
||||
|
||||
AdaptPartitions_SSE2(render_buffer, G, H_SSE2);
|
||||
AdaptPartitions(render_buffer, G, H_C);
|
||||
|
||||
for (size_t k = 0; k < H_C.size(); ++k) {
|
||||
for (size_t j = 0; j < H_C[k].re.size(); ++j) {
|
||||
EXPECT_FLOAT_EQ(H_C[k].re[j], H_SSE2[k].re[j]);
|
||||
EXPECT_FLOAT_EQ(H_C[k].im[j], H_SSE2[k].im[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the optimized method for frequency response computation is
|
||||
// bitexact to the reference counterpart.
|
||||
TEST(AdaptiveFirFilter, UpdateFrequencyResponseSse2Optimization) {
|
||||
bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0);
|
||||
if (use_sse2) {
|
||||
const size_t kNumPartitions = 12;
|
||||
std::vector<FftData> H(kNumPartitions);
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> H2(kNumPartitions);
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> H2_SSE2(kNumPartitions);
|
||||
|
||||
for (size_t j = 0; j < H.size(); ++j) {
|
||||
for (size_t k = 0; k < H[j].re.size(); ++k) {
|
||||
H[j].re[k] = k + j / 3.f;
|
||||
H[j].im[k] = j + k / 7.f;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateFrequencyResponse(H, &H2);
|
||||
UpdateFrequencyResponse_SSE2(H, &H2_SSE2);
|
||||
|
||||
for (size_t j = 0; j < H2.size(); ++j) {
|
||||
for (size_t k = 0; k < H[j].re.size(); ++k) {
|
||||
EXPECT_FLOAT_EQ(H2[j][k], H2_SSE2[j][k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the optimized method for echo return loss computation is
|
||||
// bitexact to the reference counterpart.
|
||||
TEST(AdaptiveFirFilter, UpdateErlSse2Optimization) {
|
||||
bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0);
|
||||
if (use_sse2) {
|
||||
const size_t kNumPartitions = 12;
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> H2(kNumPartitions);
|
||||
std::array<float, kFftLengthBy2Plus1> erl;
|
||||
std::array<float, kFftLengthBy2Plus1> erl_SSE2;
|
||||
|
||||
for (size_t j = 0; j < H2.size(); ++j) {
|
||||
for (size_t k = 0; k < H2[j].size(); ++k) {
|
||||
H2[j][k] = k + j / 3.f;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateErlEstimator(H2, &erl);
|
||||
UpdateErlEstimator_SSE2(H2, &erl_SSE2);
|
||||
|
||||
for (size_t j = 0; j < erl.size(); ++j) {
|
||||
EXPECT_FLOAT_EQ(erl[j], erl_SSE2[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
// Verifies that the check for non-null data dumper works.
|
||||
TEST(AdaptiveFirFilter, NullDataDumper) {
|
||||
EXPECT_DEATH(AdaptiveFirFilter(9, DetectOptimization(), nullptr), "");
|
||||
}
|
||||
|
||||
// Verifies that the check for non-null filter output works.
|
||||
TEST(AdaptiveFirFilter, NullFilterOutput) {
|
||||
ApmDataDumper data_dumper(42);
|
||||
AdaptiveFirFilter filter(9, DetectOptimization(), &data_dumper);
|
||||
RenderBuffer render_buffer(Aec3Optimization::kNone, 3,
|
||||
filter.SizePartitions(),
|
||||
std::vector<size_t>(1, filter.SizePartitions()));
|
||||
EXPECT_DEATH(filter.Filter(render_buffer, nullptr), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Verifies that the filter statistics can be accessed when filter statistics
|
||||
// are turned on.
|
||||
TEST(AdaptiveFirFilter, FilterStatisticsAccess) {
|
||||
ApmDataDumper data_dumper(42);
|
||||
AdaptiveFirFilter filter(9, DetectOptimization(), &data_dumper);
|
||||
filter.Erl();
|
||||
filter.FilterFrequencyResponse();
|
||||
}
|
||||
|
||||
// Verifies that the filter size if correctly repported.
|
||||
TEST(AdaptiveFirFilter, FilterSize) {
|
||||
ApmDataDumper data_dumper(42);
|
||||
for (size_t filter_size = 1; filter_size < 5; ++filter_size) {
|
||||
AdaptiveFirFilter filter(filter_size, DetectOptimization(), &data_dumper);
|
||||
EXPECT_EQ(filter_size, filter.SizePartitions());
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the filter is being able to properly filter a signal and to
|
||||
// adapt its coefficients.
|
||||
TEST(AdaptiveFirFilter, FilterAndAdapt) {
|
||||
constexpr size_t kNumBlocksToProcess = 500;
|
||||
ApmDataDumper data_dumper(42);
|
||||
AdaptiveFirFilter filter(9, DetectOptimization(), &data_dumper);
|
||||
Aec3Fft fft;
|
||||
RenderBuffer render_buffer(Aec3Optimization::kNone, 3,
|
||||
filter.SizePartitions(),
|
||||
std::vector<size_t>(1, filter.SizePartitions()));
|
||||
ShadowFilterUpdateGain gain;
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<float> n(kBlockSize, 0.f);
|
||||
std::vector<float> y(kBlockSize, 0.f);
|
||||
AecState aec_state(AudioProcessing::Config::EchoCanceller3{});
|
||||
RenderSignalAnalyzer render_signal_analyzer;
|
||||
std::vector<float> e(kBlockSize, 0.f);
|
||||
std::array<float, kFftLength> s_scratch;
|
||||
std::array<float, kBlockSize> s;
|
||||
FftData S;
|
||||
FftData G;
|
||||
FftData E;
|
||||
std::array<float, kFftLengthBy2Plus1> Y2;
|
||||
std::array<float, kFftLengthBy2Plus1> E2_main;
|
||||
std::array<float, kFftLengthBy2Plus1> E2_shadow;
|
||||
// [B,A] = butter(2,100/8000,'high')
|
||||
constexpr CascadedBiQuadFilter::BiQuadCoefficients
|
||||
kHighPassFilterCoefficients = {{0.97261f, -1.94523f, 0.97261f},
|
||||
{-1.94448f, 0.94598f}};
|
||||
Y2.fill(0.f);
|
||||
E2_main.fill(0.f);
|
||||
E2_shadow.fill(0.f);
|
||||
|
||||
constexpr float kScale = 1.0f / kFftLengthBy2;
|
||||
|
||||
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
|
||||
DelayBuffer<float> delay_buffer(delay_samples);
|
||||
CascadedBiQuadFilter x_hp_filter(kHighPassFilterCoefficients, 1);
|
||||
CascadedBiQuadFilter y_hp_filter(kHighPassFilterCoefficients, 1);
|
||||
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples));
|
||||
for (size_t k = 0; k < kNumBlocksToProcess; ++k) {
|
||||
RandomizeSampleVector(&random_generator, x[0]);
|
||||
delay_buffer.Delay(x[0], y);
|
||||
|
||||
RandomizeSampleVector(&random_generator, n);
|
||||
static constexpr float kNoiseScaling = 1.f / 100.f;
|
||||
std::transform(
|
||||
y.begin(), y.end(), n.begin(), y.begin(),
|
||||
[](float a, float b) { return a + b * kNoiseScaling; });
|
||||
|
||||
x_hp_filter.Process(x[0]);
|
||||
y_hp_filter.Process(y);
|
||||
|
||||
render_buffer.Insert(x);
|
||||
render_signal_analyzer.Update(render_buffer, aec_state.FilterDelay());
|
||||
|
||||
filter.Filter(render_buffer, &S);
|
||||
fft.Ifft(S, &s_scratch);
|
||||
std::transform(y.begin(), y.end(), s_scratch.begin() + kFftLengthBy2,
|
||||
e.begin(),
|
||||
[&](float a, float b) { return a - b * kScale; });
|
||||
std::for_each(e.begin(), e.end(),
|
||||
[](float& a) { a = rtc::SafeClamp(a, -32768.f, 32767.f); });
|
||||
fft.ZeroPaddedFft(e, &E);
|
||||
for (size_t k = 0; k < kBlockSize; ++k) {
|
||||
s[k] = kScale * s_scratch[k + kFftLengthBy2];
|
||||
}
|
||||
|
||||
gain.Compute(render_buffer, render_signal_analyzer, E,
|
||||
filter.SizePartitions(), false, &G);
|
||||
filter.Adapt(render_buffer, G);
|
||||
aec_state.HandleEchoPathChange(EchoPathVariability(false, false));
|
||||
aec_state.Update(filter.FilterFrequencyResponse(),
|
||||
filter.FilterImpulseResponse(), rtc::Optional<size_t>(),
|
||||
render_buffer, E2_main, Y2, x[0], s, false);
|
||||
}
|
||||
// Verify that the filter is able to perform well.
|
||||
EXPECT_LT(1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f),
|
||||
std::inner_product(y.begin(), y.end(), y.begin(), 0.f));
|
||||
ASSERT_TRUE(aec_state.FilterDelay());
|
||||
EXPECT_EQ(delay_samples / kBlockSize, *aec_state.FilterDelay());
|
||||
}
|
||||
}
|
||||
} // namespace aec3
|
||||
} // namespace webrtc
|
||||
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/aec3_common.h"
|
||||
|
||||
#include "webrtc/typedefs.h"
|
||||
#include "webrtc/system_wrappers/include/cpu_features_wrapper.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
Aec3Optimization DetectOptimization() {
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
if (WebRtc_GetCPUInfo(kSSE2) != 0) {
|
||||
return Aec3Optimization::kSse2;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
return Aec3Optimization::kNeon;
|
||||
#endif
|
||||
|
||||
return Aec3Optimization::kNone;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,115 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_AEC3_COMMON_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_AEC3_COMMON_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
#ifdef _MSC_VER /* visual c++ */
|
||||
#define ALIGN16_BEG __declspec(align(16))
|
||||
#define ALIGN16_END
|
||||
#else /* gcc or icc */
|
||||
#define ALIGN16_BEG
|
||||
#define ALIGN16_END __attribute__((aligned(16)))
|
||||
#endif
|
||||
|
||||
enum class Aec3Optimization { kNone, kSse2, kNeon };
|
||||
|
||||
constexpr int kNumBlocksPerSecond = 250;
|
||||
|
||||
constexpr int kMetricsReportingIntervalBlocks = 10 * kNumBlocksPerSecond;
|
||||
constexpr int kMetricsComputationBlocks = 9;
|
||||
constexpr int kMetricsCollectionBlocks =
|
||||
kMetricsReportingIntervalBlocks - kMetricsComputationBlocks;
|
||||
|
||||
constexpr size_t kFftLengthBy2 = 64;
|
||||
constexpr size_t kFftLengthBy2Plus1 = kFftLengthBy2 + 1;
|
||||
constexpr size_t kFftLengthBy2Minus1 = kFftLengthBy2 - 1;
|
||||
constexpr size_t kFftLength = 2 * kFftLengthBy2;
|
||||
|
||||
constexpr int kAdaptiveFilterLength = 12;
|
||||
constexpr int kResidualEchoPowerRenderWindowSize = 30;
|
||||
constexpr int kAdaptiveFilterTimeDomainLength =
|
||||
kAdaptiveFilterLength * kFftLengthBy2;
|
||||
|
||||
constexpr size_t kMaxNumBands = 3;
|
||||
constexpr size_t kSubFrameLength = 80;
|
||||
|
||||
constexpr size_t kBlockSize = kFftLengthBy2;
|
||||
constexpr size_t kExtendedBlockSize = 2 * kFftLengthBy2;
|
||||
constexpr size_t kSubBlockSize = 16;
|
||||
|
||||
constexpr size_t kNumMatchedFilters = 4;
|
||||
constexpr size_t kMatchedFilterWindowSizeSubBlocks = 32;
|
||||
constexpr size_t kMatchedFilterAlignmentShiftSizeSubBlocks =
|
||||
kMatchedFilterWindowSizeSubBlocks * 3 / 4;
|
||||
constexpr size_t kDownsampledRenderBufferSize =
|
||||
kSubBlockSize *
|
||||
(kMatchedFilterAlignmentShiftSizeSubBlocks * kNumMatchedFilters +
|
||||
kMatchedFilterWindowSizeSubBlocks +
|
||||
1);
|
||||
|
||||
constexpr size_t kRenderDelayBufferSize =
|
||||
(3 * kDownsampledRenderBufferSize) / (4 * kSubBlockSize);
|
||||
|
||||
constexpr size_t kMinEchoPathDelayBlocks = 5;
|
||||
constexpr size_t kMaxApiCallsJitterBlocks = 26;
|
||||
constexpr size_t kRenderTransferQueueSize = kMaxApiCallsJitterBlocks / 2;
|
||||
static_assert(2 * kRenderTransferQueueSize >= kMaxApiCallsJitterBlocks,
|
||||
"Requirement to ensure buffer overflow detection");
|
||||
|
||||
// TODO(peah): Integrate this with how it is done inside audio_processing_impl.
|
||||
constexpr size_t NumBandsForRate(int sample_rate_hz) {
|
||||
return static_cast<size_t>(sample_rate_hz == 8000 ? 1
|
||||
: sample_rate_hz / 16000);
|
||||
}
|
||||
constexpr int LowestBandRate(int sample_rate_hz) {
|
||||
return sample_rate_hz == 8000 ? sample_rate_hz : 16000;
|
||||
}
|
||||
|
||||
constexpr bool ValidFullBandRate(int sample_rate_hz) {
|
||||
return sample_rate_hz == 8000 || sample_rate_hz == 16000 ||
|
||||
sample_rate_hz == 32000 || sample_rate_hz == 48000;
|
||||
}
|
||||
|
||||
// Detects what kind of optimizations to use for the code.
|
||||
Aec3Optimization DetectOptimization();
|
||||
|
||||
static_assert(1 == NumBandsForRate(8000), "Number of bands for 8 kHz");
|
||||
static_assert(1 == NumBandsForRate(16000), "Number of bands for 16 kHz");
|
||||
static_assert(2 == NumBandsForRate(32000), "Number of bands for 32 kHz");
|
||||
static_assert(3 == NumBandsForRate(48000), "Number of bands for 48 kHz");
|
||||
|
||||
static_assert(8000 == LowestBandRate(8000), "Sample rate of band 0 for 8 kHz");
|
||||
static_assert(16000 == LowestBandRate(16000),
|
||||
"Sample rate of band 0 for 16 kHz");
|
||||
static_assert(16000 == LowestBandRate(32000),
|
||||
"Sample rate of band 0 for 32 kHz");
|
||||
static_assert(16000 == LowestBandRate(48000),
|
||||
"Sample rate of band 0 for 48 kHz");
|
||||
|
||||
static_assert(ValidFullBandRate(8000),
|
||||
"Test that 8 kHz is a valid sample rate");
|
||||
static_assert(ValidFullBandRate(16000),
|
||||
"Test that 16 kHz is a valid sample rate");
|
||||
static_assert(ValidFullBandRate(32000),
|
||||
"Test that 32 kHz is a valid sample rate");
|
||||
static_assert(ValidFullBandRate(48000),
|
||||
"Test that 48 kHz is a valid sample rate");
|
||||
static_assert(!ValidFullBandRate(8001),
|
||||
"Test that 8001 Hz is not a valid sample rate");
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_AEC3_COMMON_H_
|
||||
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/aec3_fft.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// TODO(peah): Change x to be std::array once the rest of the code allows this.
|
||||
void Aec3Fft::ZeroPaddedFft(rtc::ArrayView<const float> x, FftData* X) const {
|
||||
RTC_DCHECK(X);
|
||||
RTC_DCHECK_EQ(kFftLengthBy2, x.size());
|
||||
std::array<float, kFftLength> fft;
|
||||
std::fill(fft.begin(), fft.begin() + kFftLengthBy2, 0.f);
|
||||
std::copy(x.begin(), x.end(), fft.begin() + kFftLengthBy2);
|
||||
Fft(&fft, X);
|
||||
}
|
||||
|
||||
void Aec3Fft::PaddedFft(rtc::ArrayView<const float> x,
|
||||
rtc::ArrayView<float> x_old,
|
||||
FftData* X) const {
|
||||
RTC_DCHECK(X);
|
||||
RTC_DCHECK_EQ(kFftLengthBy2, x.size());
|
||||
RTC_DCHECK_EQ(kFftLengthBy2, x_old.size());
|
||||
std::array<float, kFftLength> fft;
|
||||
std::copy(x_old.begin(), x_old.end(), fft.begin());
|
||||
std::copy(x.begin(), x.end(), fft.begin() + x_old.size());
|
||||
std::copy(x.begin(), x.end(), x_old.begin());
|
||||
Fft(&fft, X);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_AEC3_FFT_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_AEC3_FFT_H_
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/fft_data.h"
|
||||
#include "webrtc/modules/audio_processing/utility/ooura_fft.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Wrapper class that provides 128 point real valued FFT functionality with the
|
||||
// FftData type.
|
||||
class Aec3Fft {
|
||||
public:
|
||||
Aec3Fft() = default;
|
||||
// Computes the FFT. Note that both the input and output are modified.
|
||||
void Fft(std::array<float, kFftLength>* x, FftData* X) const {
|
||||
RTC_DCHECK(x);
|
||||
RTC_DCHECK(X);
|
||||
ooura_fft_.Fft(x->data());
|
||||
X->CopyFromPackedArray(*x);
|
||||
}
|
||||
// Computes the inverse Fft.
|
||||
void Ifft(const FftData& X, std::array<float, kFftLength>* x) const {
|
||||
RTC_DCHECK(x);
|
||||
X.CopyToPackedArray(x);
|
||||
ooura_fft_.InverseFft(x->data());
|
||||
}
|
||||
|
||||
// Pads the input with kFftLengthBy2 initial zeros before computing the Fft.
|
||||
void ZeroPaddedFft(rtc::ArrayView<const float> x, FftData* X) const;
|
||||
|
||||
// Concatenates the kFftLengthBy2 values long x and x_old before computing the
|
||||
// Fft. After that, x is copied to x_old.
|
||||
void PaddedFft(rtc::ArrayView<const float> x,
|
||||
rtc::ArrayView<float> x_old,
|
||||
FftData* X) const;
|
||||
|
||||
private:
|
||||
const OouraFft ooura_fft_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(Aec3Fft);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_AEC3_FFT_H_
|
||||
@ -1,211 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/aec3_fft.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "webrtc/test/gmock.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
|
||||
// Verifies that the check for non-null input in Fft works.
|
||||
TEST(Aec3Fft, NullFftInput) {
|
||||
Aec3Fft fft;
|
||||
FftData X;
|
||||
EXPECT_DEATH(fft.Fft(nullptr, &X), "");
|
||||
}
|
||||
|
||||
// Verifies that the check for non-null input in Fft works.
|
||||
TEST(Aec3Fft, NullFftOutput) {
|
||||
Aec3Fft fft;
|
||||
std::array<float, kFftLength> x;
|
||||
EXPECT_DEATH(fft.Fft(&x, nullptr), "");
|
||||
}
|
||||
|
||||
// Verifies that the check for non-null output in Ifft works.
|
||||
TEST(Aec3Fft, NullIfftOutput) {
|
||||
Aec3Fft fft;
|
||||
FftData X;
|
||||
EXPECT_DEATH(fft.Ifft(X, nullptr), "");
|
||||
}
|
||||
|
||||
// Verifies that the check for non-null output in ZeroPaddedFft works.
|
||||
TEST(Aec3Fft, NullZeroPaddedFftOutput) {
|
||||
Aec3Fft fft;
|
||||
std::array<float, kFftLengthBy2> x;
|
||||
EXPECT_DEATH(fft.ZeroPaddedFft(x, nullptr), "");
|
||||
}
|
||||
|
||||
// Verifies that the check for input length in ZeroPaddedFft works.
|
||||
TEST(Aec3Fft, ZeroPaddedFftWrongInputLength) {
|
||||
Aec3Fft fft;
|
||||
FftData X;
|
||||
std::array<float, kFftLengthBy2 - 1> x;
|
||||
EXPECT_DEATH(fft.ZeroPaddedFft(x, &X), "");
|
||||
}
|
||||
|
||||
// Verifies that the check for non-null output in PaddedFft works.
|
||||
TEST(Aec3Fft, NullPaddedFftOutput) {
|
||||
Aec3Fft fft;
|
||||
std::array<float, kFftLengthBy2> x;
|
||||
std::array<float, kFftLengthBy2> x_old;
|
||||
EXPECT_DEATH(fft.PaddedFft(x, x_old, nullptr), "");
|
||||
}
|
||||
|
||||
// Verifies that the check for input length in PaddedFft works.
|
||||
TEST(Aec3Fft, PaddedFftWrongInputLength) {
|
||||
Aec3Fft fft;
|
||||
FftData X;
|
||||
std::array<float, kFftLengthBy2 - 1> x;
|
||||
std::array<float, kFftLengthBy2> x_old;
|
||||
EXPECT_DEATH(fft.PaddedFft(x, x_old, &X), "");
|
||||
}
|
||||
|
||||
// Verifies that the check for length in the old value in PaddedFft works.
|
||||
TEST(Aec3Fft, PaddedFftWrongOldValuesLength) {
|
||||
Aec3Fft fft;
|
||||
FftData X;
|
||||
std::array<float, kFftLengthBy2> x;
|
||||
std::array<float, kFftLengthBy2 - 1> x_old;
|
||||
EXPECT_DEATH(fft.PaddedFft(x, x_old, &X), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Verifies that Fft works as intended.
|
||||
TEST(Aec3Fft, Fft) {
|
||||
Aec3Fft fft;
|
||||
FftData X;
|
||||
std::array<float, kFftLength> x;
|
||||
x.fill(0.f);
|
||||
fft.Fft(&x, &X);
|
||||
EXPECT_THAT(X.re, ::testing::Each(0.f));
|
||||
EXPECT_THAT(X.im, ::testing::Each(0.f));
|
||||
|
||||
x.fill(0.f);
|
||||
x[0] = 1.f;
|
||||
fft.Fft(&x, &X);
|
||||
EXPECT_THAT(X.re, ::testing::Each(1.f));
|
||||
EXPECT_THAT(X.im, ::testing::Each(0.f));
|
||||
|
||||
x.fill(1.f);
|
||||
fft.Fft(&x, &X);
|
||||
EXPECT_EQ(128.f, X.re[0]);
|
||||
std::for_each(X.re.begin() + 1, X.re.end(),
|
||||
[](float a) { EXPECT_EQ(0.f, a); });
|
||||
EXPECT_THAT(X.im, ::testing::Each(0.f));
|
||||
}
|
||||
|
||||
// Verifies that InverseFft works as intended.
|
||||
TEST(Aec3Fft, Ifft) {
|
||||
Aec3Fft fft;
|
||||
FftData X;
|
||||
std::array<float, kFftLength> x;
|
||||
|
||||
X.re.fill(0.f);
|
||||
X.im.fill(0.f);
|
||||
fft.Ifft(X, &x);
|
||||
EXPECT_THAT(x, ::testing::Each(0.f));
|
||||
|
||||
X.re.fill(1.f);
|
||||
X.im.fill(0.f);
|
||||
fft.Ifft(X, &x);
|
||||
EXPECT_EQ(64.f, x[0]);
|
||||
std::for_each(x.begin() + 1, x.end(), [](float a) { EXPECT_EQ(0.f, a); });
|
||||
|
||||
X.re.fill(0.f);
|
||||
X.re[0] = 128;
|
||||
X.im.fill(0.f);
|
||||
fft.Ifft(X, &x);
|
||||
EXPECT_THAT(x, ::testing::Each(64.f));
|
||||
}
|
||||
|
||||
// Verifies that InverseFft and Fft work as intended.
|
||||
TEST(Aec3Fft, FftAndIfft) {
|
||||
Aec3Fft fft;
|
||||
FftData X;
|
||||
std::array<float, kFftLength> x;
|
||||
std::array<float, kFftLength> x_ref;
|
||||
|
||||
int v = 0;
|
||||
for (int k = 0; k < 20; ++k) {
|
||||
for (size_t j = 0; j < x.size(); ++j) {
|
||||
x[j] = v++;
|
||||
x_ref[j] = x[j] * 64.f;
|
||||
}
|
||||
fft.Fft(&x, &X);
|
||||
fft.Ifft(X, &x);
|
||||
for (size_t j = 0; j < x.size(); ++j) {
|
||||
EXPECT_NEAR(x_ref[j], x[j], 0.001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that ZeroPaddedFft work as intended.
|
||||
TEST(Aec3Fft, ZeroPaddedFft) {
|
||||
Aec3Fft fft;
|
||||
FftData X;
|
||||
std::array<float, kFftLengthBy2> x_in;
|
||||
std::array<float, kFftLength> x_ref;
|
||||
std::array<float, kFftLength> x_out;
|
||||
|
||||
int v = 0;
|
||||
x_ref.fill(0.f);
|
||||
for (int k = 0; k < 20; ++k) {
|
||||
for (size_t j = 0; j < x_in.size(); ++j) {
|
||||
x_in[j] = v++;
|
||||
x_ref[j + kFftLengthBy2] = x_in[j] * 64.f;
|
||||
}
|
||||
fft.ZeroPaddedFft(x_in, &X);
|
||||
fft.Ifft(X, &x_out);
|
||||
for (size_t j = 0; j < x_out.size(); ++j) {
|
||||
EXPECT_NEAR(x_ref[j], x_out[j], 0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that ZeroPaddedFft work as intended.
|
||||
TEST(Aec3Fft, PaddedFft) {
|
||||
Aec3Fft fft;
|
||||
FftData X;
|
||||
std::array<float, kFftLengthBy2> x_in;
|
||||
std::array<float, kFftLength> x_out;
|
||||
std::array<float, kFftLengthBy2> x_old;
|
||||
std::array<float, kFftLengthBy2> x_old_ref;
|
||||
std::array<float, kFftLength> x_ref;
|
||||
|
||||
int v = 0;
|
||||
x_old.fill(0.f);
|
||||
for (int k = 0; k < 20; ++k) {
|
||||
for (size_t j = 0; j < x_in.size(); ++j) {
|
||||
x_in[j] = v++;
|
||||
}
|
||||
|
||||
std::copy(x_old.begin(), x_old.end(), x_ref.begin());
|
||||
std::copy(x_in.begin(), x_in.end(), x_ref.begin() + kFftLengthBy2);
|
||||
std::copy(x_in.begin(), x_in.end(), x_old_ref.begin());
|
||||
std::for_each(x_ref.begin(), x_ref.end(), [](float& a) { a *= 64.f; });
|
||||
|
||||
fft.PaddedFft(x_in, x_old, &X);
|
||||
fft.Ifft(X, &x_out);
|
||||
|
||||
for (size_t j = 0; j < x_out.size(); ++j) {
|
||||
EXPECT_NEAR(x_ref[j], x_out[j], 0.1f);
|
||||
}
|
||||
|
||||
EXPECT_EQ(x_old_ref, x_old);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,318 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/aec_state.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/rtc_base/atomicops.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
// Computes delay of the adaptive filter.
|
||||
rtc::Optional<size_t> EstimateFilterDelay(
|
||||
const std::vector<std::array<float, kFftLengthBy2Plus1>>&
|
||||
adaptive_filter_frequency_response) {
|
||||
const auto& H2 = adaptive_filter_frequency_response;
|
||||
|
||||
size_t reliable_delays_sum = 0;
|
||||
size_t num_reliable_delays = 0;
|
||||
|
||||
constexpr size_t kUpperBin = kFftLengthBy2 - 5;
|
||||
constexpr float kMinPeakMargin = 10.f;
|
||||
const size_t kTailPartition = H2.size() - 1;
|
||||
for (size_t k = 1; k < kUpperBin; ++k) {
|
||||
// Find the maximum of H2[j].
|
||||
int peak = 0;
|
||||
for (size_t j = 0; j < H2.size(); ++j) {
|
||||
if (H2[j][k] > H2[peak][k]) {
|
||||
peak = j;
|
||||
}
|
||||
}
|
||||
|
||||
// Count the peak as a delay only if the peak is sufficiently larger than
|
||||
// the tail.
|
||||
if (kMinPeakMargin * H2[kTailPartition][k] < H2[peak][k]) {
|
||||
reliable_delays_sum += peak;
|
||||
++num_reliable_delays;
|
||||
}
|
||||
}
|
||||
|
||||
// Return no delay if not sufficient delays have been found.
|
||||
if (num_reliable_delays < 21) {
|
||||
return rtc::Optional<size_t>();
|
||||
}
|
||||
|
||||
const size_t delay = reliable_delays_sum / num_reliable_delays;
|
||||
// Sanity check that the peak is not caused by a false strong DC-component in
|
||||
// the filter.
|
||||
for (size_t k = 1; k < kUpperBin; ++k) {
|
||||
if (H2[delay][k] > H2[delay][0]) {
|
||||
RTC_DCHECK_GT(H2.size(), delay);
|
||||
return rtc::Optional<size_t>(delay);
|
||||
}
|
||||
}
|
||||
return rtc::Optional<size_t>();
|
||||
}
|
||||
|
||||
constexpr int kEchoPathChangeCounterInitial = kNumBlocksPerSecond / 5;
|
||||
constexpr int kEchoPathChangeCounterMax = 2 * kNumBlocksPerSecond;
|
||||
|
||||
} // namespace
|
||||
|
||||
int AecState::instance_count_ = 0;
|
||||
|
||||
AecState::AecState(const AudioProcessing::Config::EchoCanceller3& config)
|
||||
: data_dumper_(
|
||||
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
||||
erle_estimator_(config.param.erle.min,
|
||||
config.param.erle.max_l,
|
||||
config.param.erle.max_h),
|
||||
echo_path_change_counter_(kEchoPathChangeCounterInitial),
|
||||
config_(config),
|
||||
reverb_decay_(config_.param.ep_strength.default_len) {}
|
||||
|
||||
AecState::~AecState() = default;
|
||||
|
||||
void AecState::HandleEchoPathChange(
|
||||
const EchoPathVariability& echo_path_variability) {
|
||||
if (echo_path_variability.AudioPathChanged()) {
|
||||
blocks_since_last_saturation_ = 0;
|
||||
usable_linear_estimate_ = false;
|
||||
echo_leakage_detected_ = false;
|
||||
capture_signal_saturation_ = false;
|
||||
echo_saturation_ = false;
|
||||
previous_max_sample_ = 0.f;
|
||||
|
||||
if (echo_path_variability.delay_change) {
|
||||
force_zero_gain_counter_ = 0;
|
||||
blocks_with_filter_adaptation_ = 0;
|
||||
render_received_ = false;
|
||||
force_zero_gain_ = true;
|
||||
echo_path_change_counter_ = kEchoPathChangeCounterMax;
|
||||
}
|
||||
if (echo_path_variability.gain_change) {
|
||||
echo_path_change_counter_ = kEchoPathChangeCounterInitial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AecState::Update(const std::vector<std::array<float, kFftLengthBy2Plus1>>&
|
||||
adaptive_filter_frequency_response,
|
||||
const std::array<float, kAdaptiveFilterTimeDomainLength>&
|
||||
adaptive_filter_impulse_response,
|
||||
const rtc::Optional<size_t>& external_delay_samples,
|
||||
const RenderBuffer& render_buffer,
|
||||
const std::array<float, kFftLengthBy2Plus1>& E2_main,
|
||||
const std::array<float, kFftLengthBy2Plus1>& Y2,
|
||||
rtc::ArrayView<const float> x,
|
||||
const std::array<float, kBlockSize>& s,
|
||||
bool echo_leakage_detected) {
|
||||
// Update the echo audibility evaluator.
|
||||
echo_audibility_.Update(x, s);
|
||||
|
||||
// Store input parameters.
|
||||
echo_leakage_detected_ = echo_leakage_detected;
|
||||
|
||||
// Update counters.
|
||||
const float x_energy = std::inner_product(x.begin(), x.end(), x.begin(), 0.f);
|
||||
|
||||
const bool active_render_block =
|
||||
x_energy > (config_.param.render_levels.active_render_limit *
|
||||
config_.param.render_levels.active_render_limit) *
|
||||
kFftLengthBy2;
|
||||
if (active_render_block) {
|
||||
render_received_ = true;
|
||||
}
|
||||
blocks_with_filter_adaptation_ +=
|
||||
(active_render_block && (!SaturatedCapture()) ? 1 : 0);
|
||||
--echo_path_change_counter_;
|
||||
|
||||
// Force zero echo suppression gain after an echo path change to allow at
|
||||
// least some render data to be collected in order to avoid an initial echo
|
||||
// burst.
|
||||
constexpr size_t kZeroGainBlocksAfterChange = kNumBlocksPerSecond / 5;
|
||||
force_zero_gain_ = (++force_zero_gain_counter_) < kZeroGainBlocksAfterChange;
|
||||
|
||||
// Estimate delays.
|
||||
filter_delay_ = EstimateFilterDelay(adaptive_filter_frequency_response);
|
||||
external_delay_ =
|
||||
external_delay_samples
|
||||
? rtc::Optional<size_t>(*external_delay_samples / kBlockSize)
|
||||
: rtc::Optional<size_t>();
|
||||
|
||||
// Update the ERL and ERLE measures.
|
||||
if (filter_delay_ && echo_path_change_counter_ <= 0) {
|
||||
const auto& X2 = render_buffer.Spectrum(*filter_delay_);
|
||||
erle_estimator_.Update(X2, Y2, E2_main);
|
||||
erl_estimator_.Update(X2, Y2);
|
||||
}
|
||||
|
||||
// Detect and flag echo saturation.
|
||||
// TODO(peah): Add the delay in this computation to ensure that the render and
|
||||
// capture signals are properly aligned.
|
||||
RTC_DCHECK_LT(0, x.size());
|
||||
const float max_sample = fabs(*std::max_element(
|
||||
x.begin(), x.end(), [](float a, float b) { return a * a < b * b; }));
|
||||
const bool saturated_echo =
|
||||
previous_max_sample_ * 100 > 1600 && SaturatedCapture();
|
||||
previous_max_sample_ = max_sample;
|
||||
|
||||
// Counts the blocks since saturation.
|
||||
constexpr size_t kSaturationLeakageBlocks = 20;
|
||||
blocks_since_last_saturation_ =
|
||||
saturated_echo ? 0 : blocks_since_last_saturation_ + 1;
|
||||
echo_saturation_ = blocks_since_last_saturation_ < kSaturationLeakageBlocks;
|
||||
|
||||
// Flag whether the linear filter estimate is usable.
|
||||
constexpr size_t kEchoPathChangeConvergenceBlocks = 2 * kNumBlocksPerSecond;
|
||||
usable_linear_estimate_ =
|
||||
(!echo_saturation_) &&
|
||||
(!render_received_ ||
|
||||
blocks_with_filter_adaptation_ > kEchoPathChangeConvergenceBlocks) &&
|
||||
filter_delay_ && echo_path_change_counter_ <= 0 && external_delay_;
|
||||
|
||||
// After an amount of active render samples for which an echo should have been
|
||||
// detected in the capture signal if the ERL was not infinite, flag that a
|
||||
// headset is used.
|
||||
constexpr size_t kHeadSetDetectionBlocks = 5 * kNumBlocksPerSecond;
|
||||
headset_detected_ = !external_delay_ && !filter_delay_ &&
|
||||
(!render_received_ || blocks_with_filter_adaptation_ >=
|
||||
kHeadSetDetectionBlocks);
|
||||
|
||||
// Update the room reverb estimate.
|
||||
UpdateReverb(adaptive_filter_impulse_response);
|
||||
}
|
||||
|
||||
void AecState::UpdateReverb(
|
||||
const std::array<float, kAdaptiveFilterTimeDomainLength>&
|
||||
impulse_response) {
|
||||
if ((!(filter_delay_ && usable_linear_estimate_)) ||
|
||||
(*filter_delay_ > kAdaptiveFilterLength - 4)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Form the data to match against by squaring the impulse response
|
||||
// coefficients.
|
||||
std::array<float, kAdaptiveFilterTimeDomainLength> matching_data;
|
||||
std::transform(impulse_response.begin(), impulse_response.end(),
|
||||
matching_data.begin(), [](float a) { return a * a; });
|
||||
|
||||
// Avoid matching against noise in the model by subtracting an estimate of the
|
||||
// model noise power.
|
||||
constexpr size_t kTailLength = 64;
|
||||
constexpr size_t tail_index = kAdaptiveFilterTimeDomainLength - kTailLength;
|
||||
const float tail_power = *std::max_element(matching_data.begin() + tail_index,
|
||||
matching_data.end());
|
||||
std::for_each(matching_data.begin(), matching_data.begin() + tail_index,
|
||||
[tail_power](float& a) { a = std::max(0.f, a - tail_power); });
|
||||
|
||||
// Identify the peak index of the impulse response.
|
||||
const size_t peak_index = *std::max_element(
|
||||
matching_data.begin(), matching_data.begin() + tail_index);
|
||||
|
||||
if (peak_index + 128 < tail_index) {
|
||||
size_t start_index = peak_index + 64;
|
||||
// Compute the matching residual error for the current candidate to match.
|
||||
float residual_sqr_sum = 0.f;
|
||||
float d_k = reverb_decay_to_test_;
|
||||
for (size_t k = start_index; k < tail_index; ++k) {
|
||||
if (matching_data[start_index + 1] == 0.f) {
|
||||
break;
|
||||
}
|
||||
|
||||
float residual = matching_data[k] - matching_data[peak_index] * d_k;
|
||||
residual_sqr_sum += residual * residual;
|
||||
d_k *= reverb_decay_to_test_;
|
||||
}
|
||||
|
||||
// If needed, update the best candidate for the reverb decay.
|
||||
if (reverb_decay_candidate_residual_ < 0.f ||
|
||||
residual_sqr_sum < reverb_decay_candidate_residual_) {
|
||||
reverb_decay_candidate_residual_ = residual_sqr_sum;
|
||||
reverb_decay_candidate_ = reverb_decay_to_test_;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the next reverb candidate to evaluate such that all candidates will
|
||||
// be evaluated within one second.
|
||||
reverb_decay_to_test_ += (0.9965f - 0.9f) / (5 * kNumBlocksPerSecond);
|
||||
|
||||
// If all reverb candidates have been evaluated, choose the best one as the
|
||||
// reverb decay.
|
||||
if (reverb_decay_to_test_ >= 0.9965f) {
|
||||
if (reverb_decay_candidate_residual_ < 0.f) {
|
||||
// Transform the decay to be in the unit of blocks.
|
||||
reverb_decay_ = powf(reverb_decay_candidate_, kFftLengthBy2);
|
||||
|
||||
// Limit the estimated reverb_decay_ to the maximum one needed in practice
|
||||
// to minimize the impact of incorrect estimates.
|
||||
reverb_decay_ =
|
||||
std::min(config_.param.ep_strength.default_len, reverb_decay_);
|
||||
}
|
||||
reverb_decay_to_test_ = 0.9f;
|
||||
reverb_decay_candidate_residual_ = -1.f;
|
||||
}
|
||||
|
||||
// For noisy impulse responses, assume a fixed tail length.
|
||||
if (tail_power > 0.0005f) {
|
||||
reverb_decay_ = config_.param.ep_strength.default_len;
|
||||
}
|
||||
data_dumper_->DumpRaw("aec3_reverb_decay", reverb_decay_);
|
||||
data_dumper_->DumpRaw("aec3_tail_power", tail_power);
|
||||
}
|
||||
|
||||
void AecState::EchoAudibility::Update(rtc::ArrayView<const float> x,
|
||||
const std::array<float, kBlockSize>& s) {
|
||||
auto result_x = std::minmax_element(x.begin(), x.end());
|
||||
auto result_s = std::minmax_element(s.begin(), s.end());
|
||||
const float x_abs =
|
||||
std::max(std::abs(*result_x.first), std::abs(*result_x.second));
|
||||
const float s_abs =
|
||||
std::max(std::abs(*result_s.first), std::abs(*result_s.second));
|
||||
|
||||
if (x_abs < 5.f) {
|
||||
++low_farend_counter_;
|
||||
} else {
|
||||
low_farend_counter_ = 0;
|
||||
}
|
||||
|
||||
// The echo is deemed as not audible if the echo estimate is on the level of
|
||||
// the quantization noise in the FFTs and the nearend level is sufficiently
|
||||
// strong to mask that by ensuring that the playout and AGC gains do not boost
|
||||
// any residual echo that is below the quantization noise level. Furthermore,
|
||||
// cases where the render signal is very close to zero are also identified as
|
||||
// not producing audible echo.
|
||||
inaudible_echo_ = max_nearend_ > 500 && s_abs < 30.f;
|
||||
inaudible_echo_ = inaudible_echo_ || low_farend_counter_ > 20;
|
||||
}
|
||||
|
||||
void AecState::EchoAudibility::UpdateWithOutput(rtc::ArrayView<const float> e) {
|
||||
const float e_max = *std::max_element(e.begin(), e.end());
|
||||
const float e_min = *std::min_element(e.begin(), e.end());
|
||||
const float e_abs = std::max(std::abs(e_max), std::abs(e_min));
|
||||
|
||||
if (max_nearend_ < e_abs) {
|
||||
max_nearend_ = e_abs;
|
||||
max_nearend_counter_ = 0;
|
||||
} else {
|
||||
if (++max_nearend_counter_ > 5 * kNumBlocksPerSecond) {
|
||||
max_nearend_ *= 0.995f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_AEC_STATE_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_AEC_STATE_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/api/optional.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/echo_path_variability.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/erl_estimator.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/erle_estimator.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class ApmDataDumper;
|
||||
|
||||
// Handles the state and the conditions for the echo removal functionality.
|
||||
class AecState {
|
||||
public:
|
||||
explicit AecState(const AudioProcessing::Config::EchoCanceller3& config);
|
||||
~AecState();
|
||||
|
||||
// Returns whether the linear filter estimate is usable.
|
||||
bool UsableLinearEstimate() const { return usable_linear_estimate_; }
|
||||
|
||||
// Returns whether there has been echo leakage detected.
|
||||
bool EchoLeakageDetected() const { return echo_leakage_detected_; }
|
||||
|
||||
// Returns whether the render signal is currently active.
|
||||
// TODO(peah): Deprecate this in an upcoming CL.
|
||||
bool ActiveRender() const { return blocks_with_filter_adaptation_ > 200; }
|
||||
|
||||
// Returns the ERLE.
|
||||
const std::array<float, kFftLengthBy2Plus1>& Erle() const {
|
||||
return erle_estimator_.Erle();
|
||||
}
|
||||
|
||||
// Returns the ERL.
|
||||
const std::array<float, kFftLengthBy2Plus1>& Erl() const {
|
||||
return erl_estimator_.Erl();
|
||||
}
|
||||
|
||||
// Returns the delay estimate based on the linear filter.
|
||||
rtc::Optional<size_t> FilterDelay() const { return filter_delay_; }
|
||||
|
||||
// Returns the externally provided delay.
|
||||
rtc::Optional<size_t> ExternalDelay() const { return external_delay_; }
|
||||
|
||||
// Returns whether the capture signal is saturated.
|
||||
bool SaturatedCapture() const { return capture_signal_saturation_; }
|
||||
|
||||
// Returns whether the echo signal is saturated.
|
||||
bool SaturatedEcho() const { return echo_saturation_; }
|
||||
|
||||
// Updates the capture signal saturation.
|
||||
void UpdateCaptureSaturation(bool capture_signal_saturation) {
|
||||
capture_signal_saturation_ = capture_signal_saturation;
|
||||
}
|
||||
|
||||
// Returns whether a probable headset setup has been detected.
|
||||
bool HeadsetDetected() const { return headset_detected_; }
|
||||
|
||||
// Takes appropriate action at an echo path change.
|
||||
void HandleEchoPathChange(const EchoPathVariability& echo_path_variability);
|
||||
|
||||
// Returns the decay factor for the echo reverberation.
|
||||
float ReverbDecay() const { return reverb_decay_; }
|
||||
|
||||
// Returns whether the echo suppression gain should be forced to zero.
|
||||
bool ForcedZeroGain() const { return force_zero_gain_; }
|
||||
|
||||
// Returns whether the echo in the capture signal is audible.
|
||||
bool InaudibleEcho() const { return echo_audibility_.InaudibleEcho(); }
|
||||
|
||||
// Updates the aec state with the AEC output signal.
|
||||
void UpdateWithOutput(rtc::ArrayView<const float> e) {
|
||||
echo_audibility_.UpdateWithOutput(e);
|
||||
}
|
||||
|
||||
// Updates the aec state.
|
||||
void Update(const std::vector<std::array<float, kFftLengthBy2Plus1>>&
|
||||
adaptive_filter_frequency_response,
|
||||
const std::array<float, kAdaptiveFilterTimeDomainLength>&
|
||||
adaptive_filter_impulse_response,
|
||||
const rtc::Optional<size_t>& external_delay_samples,
|
||||
const RenderBuffer& render_buffer,
|
||||
const std::array<float, kFftLengthBy2Plus1>& E2_main,
|
||||
const std::array<float, kFftLengthBy2Plus1>& Y2,
|
||||
rtc::ArrayView<const float> x,
|
||||
const std::array<float, kBlockSize>& s_main,
|
||||
bool echo_leakage_detected);
|
||||
|
||||
private:
|
||||
class EchoAudibility {
|
||||
public:
|
||||
void Update(rtc::ArrayView<const float> x,
|
||||
const std::array<float, kBlockSize>& s);
|
||||
void UpdateWithOutput(rtc::ArrayView<const float> e);
|
||||
bool InaudibleEcho() const { return inaudible_echo_; }
|
||||
|
||||
private:
|
||||
float max_nearend_ = 0.f;
|
||||
size_t max_nearend_counter_ = 0;
|
||||
size_t low_farend_counter_ = 0;
|
||||
bool inaudible_echo_ = false;
|
||||
};
|
||||
|
||||
void UpdateReverb(const std::array<float, kAdaptiveFilterTimeDomainLength>&
|
||||
impulse_response);
|
||||
|
||||
static int instance_count_;
|
||||
std::unique_ptr<ApmDataDumper> data_dumper_;
|
||||
ErlEstimator erl_estimator_;
|
||||
ErleEstimator erle_estimator_;
|
||||
int echo_path_change_counter_;
|
||||
size_t blocks_with_filter_adaptation_ = 0;
|
||||
bool usable_linear_estimate_ = false;
|
||||
bool echo_leakage_detected_ = false;
|
||||
bool capture_signal_saturation_ = false;
|
||||
bool echo_saturation_ = false;
|
||||
bool headset_detected_ = false;
|
||||
float previous_max_sample_ = 0.f;
|
||||
bool force_zero_gain_ = false;
|
||||
bool render_received_ = false;
|
||||
size_t force_zero_gain_counter_ = 0;
|
||||
rtc::Optional<size_t> filter_delay_;
|
||||
rtc::Optional<size_t> external_delay_;
|
||||
size_t blocks_since_last_saturation_ = 1000;
|
||||
float reverb_decay_to_test_ = 0.9f;
|
||||
float reverb_decay_candidate_ = 0.f;
|
||||
float reverb_decay_candidate_residual_ = -1.f;
|
||||
EchoAudibility echo_audibility_;
|
||||
const AudioProcessing::Config::EchoCanceller3 config_;
|
||||
float reverb_decay_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(AecState);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_AEC_STATE_H_
|
||||
@ -1,269 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/aec_state.h"
|
||||
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Verify the general functionality of AecState
|
||||
TEST(AecState, NormalUsage) {
|
||||
ApmDataDumper data_dumper(42);
|
||||
AecState state(AudioProcessing::Config::EchoCanceller3{});
|
||||
RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 30,
|
||||
std::vector<size_t>(1, 30));
|
||||
std::array<float, kFftLengthBy2Plus1> E2_main = {};
|
||||
std::array<float, kFftLengthBy2Plus1> Y2 = {};
|
||||
std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
|
||||
EchoPathVariability echo_path_variability(false, false);
|
||||
std::array<float, kBlockSize> s;
|
||||
s.fill(100.f);
|
||||
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>
|
||||
converged_filter_frequency_response(10);
|
||||
for (auto& v : converged_filter_frequency_response) {
|
||||
v.fill(0.01f);
|
||||
}
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>
|
||||
diverged_filter_frequency_response = converged_filter_frequency_response;
|
||||
converged_filter_frequency_response[2].fill(100.f);
|
||||
converged_filter_frequency_response[2][0] = 1.f;
|
||||
|
||||
std::array<float, kAdaptiveFilterTimeDomainLength> impulse_response;
|
||||
impulse_response.fill(0.f);
|
||||
|
||||
// Verify that linear AEC usability is false when the filter is diverged and
|
||||
// there is no external delay reported.
|
||||
state.Update(diverged_filter_frequency_response, impulse_response,
|
||||
rtc::Optional<size_t>(), render_buffer, E2_main, Y2, x[0], s,
|
||||
false);
|
||||
EXPECT_FALSE(state.UsableLinearEstimate());
|
||||
|
||||
// Verify that linear AEC usability is true when the filter is converged
|
||||
std::fill(x[0].begin(), x[0].end(), 101.f);
|
||||
for (int k = 0; k < 3000; ++k) {
|
||||
state.Update(converged_filter_frequency_response, impulse_response,
|
||||
rtc::Optional<size_t>(2), render_buffer, E2_main, Y2, x[0], s,
|
||||
false);
|
||||
}
|
||||
EXPECT_TRUE(state.UsableLinearEstimate());
|
||||
|
||||
// Verify that linear AEC usability becomes false after an echo path change is
|
||||
// reported
|
||||
state.HandleEchoPathChange(EchoPathVariability(true, false));
|
||||
state.Update(converged_filter_frequency_response, impulse_response,
|
||||
rtc::Optional<size_t>(2), render_buffer, E2_main, Y2, x[0], s,
|
||||
false);
|
||||
EXPECT_FALSE(state.UsableLinearEstimate());
|
||||
|
||||
// Verify that the active render detection works as intended.
|
||||
std::fill(x[0].begin(), x[0].end(), 101.f);
|
||||
state.HandleEchoPathChange(EchoPathVariability(true, true));
|
||||
state.Update(converged_filter_frequency_response, impulse_response,
|
||||
rtc::Optional<size_t>(2), render_buffer, E2_main, Y2, x[0], s,
|
||||
false);
|
||||
EXPECT_FALSE(state.ActiveRender());
|
||||
|
||||
for (int k = 0; k < 1000; ++k) {
|
||||
state.Update(converged_filter_frequency_response, impulse_response,
|
||||
rtc::Optional<size_t>(2), render_buffer, E2_main, Y2, x[0], s,
|
||||
false);
|
||||
}
|
||||
EXPECT_TRUE(state.ActiveRender());
|
||||
|
||||
// Verify that echo leakage is properly reported.
|
||||
state.Update(converged_filter_frequency_response, impulse_response,
|
||||
rtc::Optional<size_t>(2), render_buffer, E2_main, Y2, x[0], s,
|
||||
false);
|
||||
EXPECT_FALSE(state.EchoLeakageDetected());
|
||||
|
||||
state.Update(converged_filter_frequency_response, impulse_response,
|
||||
rtc::Optional<size_t>(2), render_buffer, E2_main, Y2, x[0], s,
|
||||
true);
|
||||
EXPECT_TRUE(state.EchoLeakageDetected());
|
||||
|
||||
// Verify that the ERL is properly estimated
|
||||
for (auto& x_k : x) {
|
||||
x_k = std::vector<float>(kBlockSize, 0.f);
|
||||
}
|
||||
|
||||
x[0][0] = 5000.f;
|
||||
for (size_t k = 0; k < render_buffer.Buffer().size(); ++k) {
|
||||
render_buffer.Insert(x);
|
||||
}
|
||||
|
||||
Y2.fill(10.f * 10000.f * 10000.f);
|
||||
for (size_t k = 0; k < 1000; ++k) {
|
||||
state.Update(converged_filter_frequency_response, impulse_response,
|
||||
rtc::Optional<size_t>(2), render_buffer, E2_main, Y2, x[0], s,
|
||||
false);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(state.UsableLinearEstimate());
|
||||
const std::array<float, kFftLengthBy2Plus1>& erl = state.Erl();
|
||||
EXPECT_EQ(erl[0], erl[1]);
|
||||
for (size_t k = 1; k < erl.size() - 1; ++k) {
|
||||
EXPECT_NEAR(k % 2 == 0 ? 10.f : 1000.f, erl[k], 0.1);
|
||||
}
|
||||
EXPECT_EQ(erl[erl.size() - 2], erl[erl.size() - 1]);
|
||||
|
||||
// Verify that the ERLE is properly estimated
|
||||
E2_main.fill(1.f * 10000.f * 10000.f);
|
||||
Y2.fill(10.f * E2_main[0]);
|
||||
for (size_t k = 0; k < 1000; ++k) {
|
||||
state.Update(converged_filter_frequency_response, impulse_response,
|
||||
rtc::Optional<size_t>(2), render_buffer, E2_main, Y2, x[0], s,
|
||||
false);
|
||||
}
|
||||
ASSERT_TRUE(state.UsableLinearEstimate());
|
||||
{
|
||||
const auto& erle = state.Erle();
|
||||
EXPECT_EQ(erle[0], erle[1]);
|
||||
constexpr size_t kLowFrequencyLimit = 32;
|
||||
for (size_t k = 1; k < kLowFrequencyLimit; ++k) {
|
||||
EXPECT_NEAR(k % 2 == 0 ? 8.f : 1.f, erle[k], 0.1);
|
||||
}
|
||||
for (size_t k = kLowFrequencyLimit; k < erle.size() - 1; ++k) {
|
||||
EXPECT_NEAR(k % 2 == 0 ? 1.5f : 1.f, erle[k], 0.1);
|
||||
}
|
||||
EXPECT_EQ(erle[erle.size() - 2], erle[erle.size() - 1]);
|
||||
}
|
||||
|
||||
E2_main.fill(1.f * 10000.f * 10000.f);
|
||||
Y2.fill(5.f * E2_main[0]);
|
||||
for (size_t k = 0; k < 1000; ++k) {
|
||||
state.Update(converged_filter_frequency_response, impulse_response,
|
||||
rtc::Optional<size_t>(2), render_buffer, E2_main, Y2, x[0], s,
|
||||
false);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(state.UsableLinearEstimate());
|
||||
{
|
||||
const auto& erle = state.Erle();
|
||||
EXPECT_EQ(erle[0], erle[1]);
|
||||
constexpr size_t kLowFrequencyLimit = 32;
|
||||
for (size_t k = 1; k < kLowFrequencyLimit; ++k) {
|
||||
EXPECT_NEAR(k % 2 == 0 ? 5.f : 1.f, erle[k], 0.1);
|
||||
}
|
||||
for (size_t k = kLowFrequencyLimit; k < erle.size() - 1; ++k) {
|
||||
EXPECT_NEAR(k % 2 == 0 ? 1.5f : 1.f, erle[k], 0.1);
|
||||
}
|
||||
EXPECT_EQ(erle[erle.size() - 2], erle[erle.size() - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies the a non-significant delay is correctly identified.
|
||||
TEST(AecState, NonSignificantDelay) {
|
||||
AecState state(AudioProcessing::Config::EchoCanceller3{});
|
||||
RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 30,
|
||||
std::vector<size_t>(1, 30));
|
||||
std::array<float, kFftLengthBy2Plus1> E2_main;
|
||||
std::array<float, kFftLengthBy2Plus1> Y2;
|
||||
std::array<float, kBlockSize> x;
|
||||
EchoPathVariability echo_path_variability(false, false);
|
||||
std::array<float, kBlockSize> s;
|
||||
s.fill(100.f);
|
||||
x.fill(0.f);
|
||||
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> frequency_response(30);
|
||||
for (auto& v : frequency_response) {
|
||||
v.fill(0.01f);
|
||||
}
|
||||
|
||||
std::array<float, kAdaptiveFilterTimeDomainLength> impulse_response;
|
||||
impulse_response.fill(0.f);
|
||||
|
||||
// Verify that a non-significant filter delay is identified correctly.
|
||||
state.HandleEchoPathChange(echo_path_variability);
|
||||
state.Update(frequency_response, impulse_response, rtc::Optional<size_t>(),
|
||||
render_buffer, E2_main, Y2, x, s, false);
|
||||
EXPECT_FALSE(state.FilterDelay());
|
||||
}
|
||||
|
||||
// Verifies the delay for a converged filter is correctly identified.
|
||||
TEST(AecState, ConvergedFilterDelay) {
|
||||
constexpr int kFilterLength = 10;
|
||||
AecState state(AudioProcessing::Config::EchoCanceller3{});
|
||||
RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 30,
|
||||
std::vector<size_t>(1, 30));
|
||||
std::array<float, kFftLengthBy2Plus1> E2_main;
|
||||
std::array<float, kFftLengthBy2Plus1> Y2;
|
||||
std::array<float, kBlockSize> x;
|
||||
EchoPathVariability echo_path_variability(false, false);
|
||||
std::array<float, kBlockSize> s;
|
||||
s.fill(100.f);
|
||||
x.fill(0.f);
|
||||
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> frequency_response(
|
||||
kFilterLength);
|
||||
|
||||
std::array<float, kAdaptiveFilterTimeDomainLength> impulse_response;
|
||||
impulse_response.fill(0.f);
|
||||
|
||||
// Verify that the filter delay for a converged filter is properly identified.
|
||||
for (int k = 0; k < kFilterLength; ++k) {
|
||||
for (auto& v : frequency_response) {
|
||||
v.fill(0.01f);
|
||||
}
|
||||
frequency_response[k].fill(100.f);
|
||||
frequency_response[k][0] = 0.f;
|
||||
state.HandleEchoPathChange(echo_path_variability);
|
||||
state.Update(frequency_response, impulse_response, rtc::Optional<size_t>(),
|
||||
render_buffer, E2_main, Y2, x, s, false);
|
||||
EXPECT_TRUE(k == (kFilterLength - 1) || state.FilterDelay());
|
||||
if (k != (kFilterLength - 1)) {
|
||||
EXPECT_EQ(k, state.FilterDelay());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the externally reported delay is properly reported and converted.
|
||||
TEST(AecState, ExternalDelay) {
|
||||
AecState state(AudioProcessing::Config::EchoCanceller3{});
|
||||
std::array<float, kFftLengthBy2Plus1> E2_main;
|
||||
std::array<float, kFftLengthBy2Plus1> E2_shadow;
|
||||
std::array<float, kFftLengthBy2Plus1> Y2;
|
||||
std::array<float, kBlockSize> x;
|
||||
std::array<float, kBlockSize> s;
|
||||
s.fill(100.f);
|
||||
E2_main.fill(0.f);
|
||||
E2_shadow.fill(0.f);
|
||||
Y2.fill(0.f);
|
||||
x.fill(0.f);
|
||||
RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 30,
|
||||
std::vector<size_t>(1, 30));
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> frequency_response(30);
|
||||
for (auto& v : frequency_response) {
|
||||
v.fill(0.01f);
|
||||
}
|
||||
|
||||
std::array<float, kAdaptiveFilterTimeDomainLength> impulse_response;
|
||||
impulse_response.fill(0.f);
|
||||
|
||||
for (size_t k = 0; k < frequency_response.size() - 1; ++k) {
|
||||
state.HandleEchoPathChange(EchoPathVariability(false, false));
|
||||
state.Update(frequency_response, impulse_response,
|
||||
rtc::Optional<size_t>(k * kBlockSize + 5), render_buffer,
|
||||
E2_main, Y2, x, s, false);
|
||||
EXPECT_TRUE(state.ExternalDelay());
|
||||
EXPECT_EQ(k, state.ExternalDelay());
|
||||
}
|
||||
|
||||
// Verify that the externally reported delay is properly unset when it is no
|
||||
// longer present.
|
||||
state.HandleEchoPathChange(EchoPathVariability(false, false));
|
||||
state.Update(frequency_response, impulse_response, rtc::Optional<size_t>(),
|
||||
render_buffer, E2_main, Y2, x, s, false);
|
||||
EXPECT_FALSE(state.ExternalDelay());
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/audio_processing/aec3/block_framer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
BlockFramer::BlockFramer(size_t num_bands)
|
||||
: num_bands_(num_bands),
|
||||
buffer_(num_bands_, std::vector<float>(kBlockSize, 0.f)) {}
|
||||
|
||||
BlockFramer::~BlockFramer() = default;
|
||||
|
||||
// All the constants are chosen so that the buffer is either empty or has enough
|
||||
// samples for InsertBlockAndExtractSubFrame to produce a frame. In order to
|
||||
// achieve this, the InsertBlockAndExtractSubFrame and InsertBlock methods need
|
||||
// to be called in the correct order.
|
||||
void BlockFramer::InsertBlock(const std::vector<std::vector<float>>& block) {
|
||||
RTC_DCHECK_EQ(num_bands_, block.size());
|
||||
for (size_t i = 0; i < num_bands_; ++i) {
|
||||
RTC_DCHECK_EQ(kBlockSize, block[i].size());
|
||||
RTC_DCHECK_EQ(0, buffer_[i].size());
|
||||
buffer_[i].insert(buffer_[i].begin(), block[i].begin(), block[i].end());
|
||||
}
|
||||
}
|
||||
|
||||
void BlockFramer::InsertBlockAndExtractSubFrame(
|
||||
const std::vector<std::vector<float>>& block,
|
||||
std::vector<rtc::ArrayView<float>>* sub_frame) {
|
||||
RTC_DCHECK(sub_frame);
|
||||
RTC_DCHECK_EQ(num_bands_, block.size());
|
||||
RTC_DCHECK_EQ(num_bands_, sub_frame->size());
|
||||
for (size_t i = 0; i < num_bands_; ++i) {
|
||||
RTC_DCHECK_LE(kSubFrameLength, buffer_[i].size() + kBlockSize);
|
||||
RTC_DCHECK_EQ(kBlockSize, block[i].size());
|
||||
RTC_DCHECK_GE(kBlockSize, buffer_[i].size());
|
||||
RTC_DCHECK_EQ(kSubFrameLength, (*sub_frame)[i].size());
|
||||
const int samples_to_frame = kSubFrameLength - buffer_[i].size();
|
||||
std::copy(buffer_[i].begin(), buffer_[i].end(), (*sub_frame)[i].begin());
|
||||
std::copy(block[i].begin(), block[i].begin() + samples_to_frame,
|
||||
(*sub_frame)[i].begin() + buffer_[i].size());
|
||||
buffer_[i].clear();
|
||||
buffer_[i].insert(buffer_[i].begin(), block[i].begin() + samples_to_frame,
|
||||
block[i].end());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_FRAMER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_FRAMER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Class for producing frames consisting of 1 or 2 subframes of 80 samples each
|
||||
// from 64 sample blocks. The class is designed to work together with the
|
||||
// FrameBlocker class which performs the reverse conversion. Used together with
|
||||
// that, this class produces output frames are the same rate as frames are
|
||||
// received by the FrameBlocker class. Note that the internal buffers will
|
||||
// overrun if any other rate of packets insertion is used.
|
||||
class BlockFramer {
|
||||
public:
|
||||
explicit BlockFramer(size_t num_bands);
|
||||
~BlockFramer();
|
||||
// Adds a 64 sample block into the data that will form the next output frame.
|
||||
void InsertBlock(const std::vector<std::vector<float>>& block);
|
||||
// Adds a 64 sample block and extracts an 80 sample subframe.
|
||||
void InsertBlockAndExtractSubFrame(
|
||||
const std::vector<std::vector<float>>& block,
|
||||
std::vector<rtc::ArrayView<float>>* sub_frame);
|
||||
|
||||
private:
|
||||
const size_t num_bands_;
|
||||
std::vector<std::vector<float>> buffer_;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BlockFramer);
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_FRAMER_H_
|
||||
@ -1,261 +0,0 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/audio_processing/aec3/block_framer.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
void SetupSubFrameView(std::vector<std::vector<float>>* sub_frame,
|
||||
std::vector<rtc::ArrayView<float>>* sub_frame_view) {
|
||||
for (size_t k = 0; k < sub_frame_view->size(); ++k) {
|
||||
(*sub_frame_view)[k] =
|
||||
rtc::ArrayView<float>((*sub_frame)[k].data(), (*sub_frame)[k].size());
|
||||
}
|
||||
}
|
||||
|
||||
float ComputeSampleValue(size_t chunk_counter,
|
||||
size_t chunk_size,
|
||||
size_t band,
|
||||
size_t sample_index,
|
||||
int offset) {
|
||||
float value =
|
||||
static_cast<int>(chunk_counter * chunk_size + sample_index) + offset;
|
||||
return value > 0 ? 5000 * band + value : 0;
|
||||
}
|
||||
|
||||
bool VerifySubFrame(size_t sub_frame_counter,
|
||||
int offset,
|
||||
const std::vector<rtc::ArrayView<float>>& sub_frame_view) {
|
||||
for (size_t k = 0; k < sub_frame_view.size(); ++k) {
|
||||
for (size_t i = 0; i < sub_frame_view[k].size(); ++i) {
|
||||
const float reference_value =
|
||||
ComputeSampleValue(sub_frame_counter, kSubFrameLength, k, i, offset);
|
||||
if (reference_value != sub_frame_view[k][i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FillBlock(size_t block_counter, std::vector<std::vector<float>>* block) {
|
||||
for (size_t k = 0; k < block->size(); ++k) {
|
||||
for (size_t i = 0; i < (*block)[0].size(); ++i) {
|
||||
(*block)[k][i] = ComputeSampleValue(block_counter, kBlockSize, k, i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the BlockFramer is able to produce the expected frame content.
|
||||
void RunFramerTest(int sample_rate_hz) {
|
||||
constexpr size_t kNumSubFramesToProcess = 2;
|
||||
const size_t num_bands = NumBandsForRate(sample_rate_hz);
|
||||
|
||||
std::vector<std::vector<float>> block(num_bands,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> output_sub_frame(
|
||||
num_bands, std::vector<float>(kSubFrameLength, 0.f));
|
||||
std::vector<rtc::ArrayView<float>> output_sub_frame_view(num_bands);
|
||||
SetupSubFrameView(&output_sub_frame, &output_sub_frame_view);
|
||||
BlockFramer framer(num_bands);
|
||||
|
||||
size_t block_index = 0;
|
||||
for (size_t sub_frame_index = 0; sub_frame_index < kNumSubFramesToProcess;
|
||||
++sub_frame_index) {
|
||||
FillBlock(block_index++, &block);
|
||||
framer.InsertBlockAndExtractSubFrame(block, &output_sub_frame_view);
|
||||
EXPECT_TRUE(VerifySubFrame(sub_frame_index, -64, output_sub_frame_view));
|
||||
|
||||
if ((sub_frame_index + 1) % 4 == 0) {
|
||||
FillBlock(block_index++, &block);
|
||||
framer.InsertBlock(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
// Verifies that the BlockFramer crashes if the InsertBlockAndExtractSubFrame
|
||||
// method is called for inputs with the wrong number of bands or band lengths.
|
||||
void RunWronglySizedInsertAndExtractParametersTest(int sample_rate_hz,
|
||||
size_t num_block_bands,
|
||||
size_t block_length,
|
||||
size_t num_sub_frame_bands,
|
||||
size_t sub_frame_length) {
|
||||
const size_t correct_num_bands = NumBandsForRate(sample_rate_hz);
|
||||
|
||||
std::vector<std::vector<float>> block(num_block_bands,
|
||||
std::vector<float>(block_length, 0.f));
|
||||
std::vector<std::vector<float>> output_sub_frame(
|
||||
num_sub_frame_bands, std::vector<float>(sub_frame_length, 0.f));
|
||||
std::vector<rtc::ArrayView<float>> output_sub_frame_view(
|
||||
output_sub_frame.size());
|
||||
SetupSubFrameView(&output_sub_frame, &output_sub_frame_view);
|
||||
BlockFramer framer(correct_num_bands);
|
||||
EXPECT_DEATH(
|
||||
framer.InsertBlockAndExtractSubFrame(block, &output_sub_frame_view), "");
|
||||
}
|
||||
|
||||
// Verifies that the BlockFramer crashes if the InsertBlock method is called for
|
||||
// inputs with the wrong number of bands or band lengths.
|
||||
void RunWronglySizedInsertParameterTest(int sample_rate_hz,
|
||||
size_t num_block_bands,
|
||||
size_t block_length) {
|
||||
const size_t correct_num_bands = NumBandsForRate(sample_rate_hz);
|
||||
|
||||
std::vector<std::vector<float>> correct_block(
|
||||
correct_num_bands, std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> wrong_block(
|
||||
num_block_bands, std::vector<float>(block_length, 0.f));
|
||||
std::vector<std::vector<float>> output_sub_frame(
|
||||
correct_num_bands, std::vector<float>(kSubFrameLength, 0.f));
|
||||
std::vector<rtc::ArrayView<float>> output_sub_frame_view(
|
||||
output_sub_frame.size());
|
||||
SetupSubFrameView(&output_sub_frame, &output_sub_frame_view);
|
||||
BlockFramer framer(correct_num_bands);
|
||||
framer.InsertBlockAndExtractSubFrame(correct_block, &output_sub_frame_view);
|
||||
framer.InsertBlockAndExtractSubFrame(correct_block, &output_sub_frame_view);
|
||||
framer.InsertBlockAndExtractSubFrame(correct_block, &output_sub_frame_view);
|
||||
framer.InsertBlockAndExtractSubFrame(correct_block, &output_sub_frame_view);
|
||||
|
||||
EXPECT_DEATH(framer.InsertBlock(wrong_block), "");
|
||||
}
|
||||
|
||||
// Verifies that the BlockFramer crashes if the InsertBlock method is called
|
||||
// after a wrong number of previous InsertBlockAndExtractSubFrame method calls
|
||||
// have been made.
|
||||
void RunWronglyInsertOrderTest(int sample_rate_hz,
|
||||
size_t num_preceeding_api_calls) {
|
||||
const size_t correct_num_bands = NumBandsForRate(sample_rate_hz);
|
||||
|
||||
std::vector<std::vector<float>> block(correct_num_bands,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> output_sub_frame(
|
||||
correct_num_bands, std::vector<float>(kSubFrameLength, 0.f));
|
||||
std::vector<rtc::ArrayView<float>> output_sub_frame_view(
|
||||
output_sub_frame.size());
|
||||
SetupSubFrameView(&output_sub_frame, &output_sub_frame_view);
|
||||
BlockFramer framer(correct_num_bands);
|
||||
for (size_t k = 0; k < num_preceeding_api_calls; ++k) {
|
||||
framer.InsertBlockAndExtractSubFrame(block, &output_sub_frame_view);
|
||||
}
|
||||
|
||||
EXPECT_DEATH(framer.InsertBlock(block), "");
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string ProduceDebugText(int sample_rate_hz) {
|
||||
std::ostringstream ss;
|
||||
ss << "Sample rate: " << sample_rate_hz;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
TEST(BlockFramer, WrongNumberOfBandsInBlockForInsertBlockAndExtractSubFrame) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
const size_t correct_num_bands = NumBandsForRate(rate);
|
||||
const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
|
||||
RunWronglySizedInsertAndExtractParametersTest(
|
||||
rate, wrong_num_bands, kBlockSize, correct_num_bands, kSubFrameLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlockFramer,
|
||||
WrongNumberOfBandsInSubFrameForInsertBlockAndExtractSubFrame) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
const size_t correct_num_bands = NumBandsForRate(rate);
|
||||
const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
|
||||
RunWronglySizedInsertAndExtractParametersTest(
|
||||
rate, correct_num_bands, kBlockSize, wrong_num_bands, kSubFrameLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlockFramer, WrongNumberOfSamplesInBlockForInsertBlockAndExtractSubFrame) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
const size_t correct_num_bands = NumBandsForRate(rate);
|
||||
RunWronglySizedInsertAndExtractParametersTest(
|
||||
rate, correct_num_bands, kBlockSize - 1, correct_num_bands,
|
||||
kSubFrameLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlockFramer,
|
||||
WrongNumberOfSamplesInSubFrameForInsertBlockAndExtractSubFrame) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
const size_t correct_num_bands = NumBandsForRate(rate);
|
||||
RunWronglySizedInsertAndExtractParametersTest(rate, correct_num_bands,
|
||||
kBlockSize, correct_num_bands,
|
||||
kSubFrameLength - 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlockFramer, WrongNumberOfBandsInBlockForInsertBlock) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
const size_t correct_num_bands = NumBandsForRate(rate);
|
||||
const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
|
||||
RunWronglySizedInsertParameterTest(rate, wrong_num_bands, kBlockSize);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlockFramer, WrongNumberOfSamplesInBlockForInsertBlock) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
const size_t correct_num_bands = NumBandsForRate(rate);
|
||||
RunWronglySizedInsertParameterTest(rate, correct_num_bands, kBlockSize - 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlockFramer, WrongNumberOfPreceedingApiCallsForInsertBlock) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
for (size_t num_calls = 0; num_calls < 4; ++num_calls) {
|
||||
std::ostringstream ss;
|
||||
ss << "Sample rate: " << rate;
|
||||
ss << ", Num preceeding InsertBlockAndExtractSubFrame calls: "
|
||||
<< num_calls;
|
||||
|
||||
SCOPED_TRACE(ss.str());
|
||||
RunWronglyInsertOrderTest(rate, num_calls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifiers that the verification for null sub_frame pointer works.
|
||||
TEST(BlockFramer, NullSubFrameParameter) {
|
||||
EXPECT_DEATH(BlockFramer(1).InsertBlockAndExtractSubFrame(
|
||||
std::vector<std::vector<float>>(
|
||||
1, std::vector<float>(kBlockSize, 0.f)),
|
||||
nullptr),
|
||||
"");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST(BlockFramer, FrameBitexactness) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
RunFramerTest(rate);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,214 +0,0 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/audio_processing/aec3/block_processor.h"
|
||||
|
||||
#include "webrtc/api/optional.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/block_processor_metrics.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/echo_path_variability.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/rtc_base/atomicops.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
enum class BlockProcessorApiCall { kCapture, kRender };
|
||||
|
||||
class BlockProcessorImpl final : public BlockProcessor {
|
||||
public:
|
||||
BlockProcessorImpl(int sample_rate_hz,
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer,
|
||||
std::unique_ptr<RenderDelayController> delay_controller,
|
||||
std::unique_ptr<EchoRemover> echo_remover);
|
||||
|
||||
~BlockProcessorImpl() override;
|
||||
|
||||
void ProcessCapture(bool echo_path_gain_change,
|
||||
bool capture_signal_saturation,
|
||||
std::vector<std::vector<float>>* capture_block) override;
|
||||
|
||||
void BufferRender(const std::vector<std::vector<float>>& block) override;
|
||||
|
||||
void UpdateEchoLeakageStatus(bool leakage_detected) override;
|
||||
|
||||
private:
|
||||
static int instance_count_;
|
||||
bool no_capture_data_received_ = true;
|
||||
bool no_render_data_received_ = true;
|
||||
std::unique_ptr<ApmDataDumper> data_dumper_;
|
||||
const size_t sample_rate_hz_;
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer_;
|
||||
std::unique_ptr<RenderDelayController> delay_controller_;
|
||||
std::unique_ptr<EchoRemover> echo_remover_;
|
||||
BlockProcessorMetrics metrics_;
|
||||
bool render_buffer_overrun_occurred_ = false;
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BlockProcessorImpl);
|
||||
};
|
||||
|
||||
int BlockProcessorImpl::instance_count_ = 0;
|
||||
|
||||
BlockProcessorImpl::BlockProcessorImpl(
|
||||
int sample_rate_hz,
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer,
|
||||
std::unique_ptr<RenderDelayController> delay_controller,
|
||||
std::unique_ptr<EchoRemover> echo_remover)
|
||||
: data_dumper_(
|
||||
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
||||
sample_rate_hz_(sample_rate_hz),
|
||||
render_buffer_(std::move(render_buffer)),
|
||||
delay_controller_(std::move(delay_controller)),
|
||||
echo_remover_(std::move(echo_remover)) {
|
||||
RTC_DCHECK(ValidFullBandRate(sample_rate_hz_));
|
||||
}
|
||||
|
||||
BlockProcessorImpl::~BlockProcessorImpl() = default;
|
||||
|
||||
void BlockProcessorImpl::ProcessCapture(
|
||||
bool echo_path_gain_change,
|
||||
bool capture_signal_saturation,
|
||||
std::vector<std::vector<float>>* capture_block) {
|
||||
RTC_DCHECK(capture_block);
|
||||
RTC_DCHECK_EQ(NumBandsForRate(sample_rate_hz_), capture_block->size());
|
||||
RTC_DCHECK_EQ(kBlockSize, (*capture_block)[0].size());
|
||||
data_dumper_->DumpRaw("aec3_processblock_call_order",
|
||||
static_cast<int>(BlockProcessorApiCall::kCapture));
|
||||
data_dumper_->DumpWav("aec3_processblock_capture_input", kBlockSize,
|
||||
&(*capture_block)[0][0],
|
||||
LowestBandRate(sample_rate_hz_), 1);
|
||||
|
||||
// Do not start processing until render data has been buffered as that will
|
||||
// cause the buffers to be wrongly aligned.
|
||||
no_capture_data_received_ = false;
|
||||
if (no_render_data_received_) {
|
||||
return;
|
||||
}
|
||||
|
||||
data_dumper_->DumpWav("aec3_processblock_capture_input2", kBlockSize,
|
||||
&(*capture_block)[0][0],
|
||||
LowestBandRate(sample_rate_hz_), 1);
|
||||
|
||||
bool render_buffer_underrun = false;
|
||||
if (render_buffer_overrun_occurred_) {
|
||||
// Reset the render buffers and the alignment functionality when there has
|
||||
// been a render buffer overrun as the buffer alignment may be noncausal.
|
||||
delay_controller_->Reset();
|
||||
render_buffer_->Reset();
|
||||
}
|
||||
|
||||
// Update the render buffers with new render data, filling the buffers with
|
||||
// empty blocks when there is no render data available.
|
||||
render_buffer_underrun = !render_buffer_->UpdateBuffers();
|
||||
|
||||
// Compute and and apply the render delay required to achieve proper signal
|
||||
// alignment.
|
||||
const size_t old_delay = render_buffer_->Delay();
|
||||
const size_t new_delay = delay_controller_->GetDelay(
|
||||
render_buffer_->GetDownsampledRenderBuffer(), (*capture_block)[0]);
|
||||
|
||||
bool delay_change;
|
||||
if (new_delay >= kMinEchoPathDelayBlocks) {
|
||||
render_buffer_->SetDelay(new_delay);
|
||||
const size_t achieved_delay = render_buffer_->Delay();
|
||||
delay_change = old_delay != achieved_delay || old_delay != new_delay ||
|
||||
render_buffer_overrun_occurred_;
|
||||
|
||||
// Inform the delay controller of the actually set delay to allow it to
|
||||
// properly react to a non-feasible delay.
|
||||
delay_controller_->SetDelay(achieved_delay);
|
||||
} else {
|
||||
delay_controller_->Reset();
|
||||
render_buffer_->Reset();
|
||||
delay_change = true;
|
||||
}
|
||||
|
||||
// Remove the echo from the capture signal.
|
||||
echo_remover_->ProcessCapture(
|
||||
delay_controller_->AlignmentHeadroomSamples(),
|
||||
EchoPathVariability(echo_path_gain_change, delay_change),
|
||||
capture_signal_saturation, render_buffer_->GetRenderBuffer(),
|
||||
capture_block);
|
||||
|
||||
// Update the metrics.
|
||||
metrics_.UpdateCapture(render_buffer_underrun);
|
||||
|
||||
render_buffer_overrun_occurred_ = false;
|
||||
}
|
||||
|
||||
void BlockProcessorImpl::BufferRender(
|
||||
const std::vector<std::vector<float>>& block) {
|
||||
RTC_DCHECK_EQ(NumBandsForRate(sample_rate_hz_), block.size());
|
||||
RTC_DCHECK_EQ(kBlockSize, block[0].size());
|
||||
data_dumper_->DumpRaw("aec3_processblock_call_order",
|
||||
static_cast<int>(BlockProcessorApiCall::kRender));
|
||||
data_dumper_->DumpWav("aec3_processblock_render_input", kBlockSize,
|
||||
&block[0][0], LowestBandRate(sample_rate_hz_), 1);
|
||||
|
||||
no_render_data_received_ = false;
|
||||
|
||||
// Do not start buffer render data until capture data has been received as
|
||||
// that data may give a false alignment.
|
||||
if (no_capture_data_received_) {
|
||||
return;
|
||||
}
|
||||
|
||||
data_dumper_->DumpWav("aec3_processblock_render_input2", kBlockSize,
|
||||
&block[0][0], LowestBandRate(sample_rate_hz_), 1);
|
||||
|
||||
// Buffer the render data.
|
||||
render_buffer_overrun_occurred_ = !render_buffer_->Insert(block);
|
||||
|
||||
// Update the metrics.
|
||||
metrics_.UpdateRender(render_buffer_overrun_occurred_);
|
||||
}
|
||||
|
||||
void BlockProcessorImpl::UpdateEchoLeakageStatus(bool leakage_detected) {
|
||||
echo_remover_->UpdateEchoLeakageStatus(leakage_detected);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BlockProcessor* BlockProcessor::Create(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz) {
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(sample_rate_hz)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(config, sample_rate_hz));
|
||||
std::unique_ptr<EchoRemover> echo_remover(
|
||||
EchoRemover::Create(config, sample_rate_hz));
|
||||
return Create(config, sample_rate_hz, std::move(render_buffer),
|
||||
std::move(delay_controller), std::move(echo_remover));
|
||||
}
|
||||
|
||||
BlockProcessor* BlockProcessor::Create(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz,
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer) {
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(config, sample_rate_hz));
|
||||
std::unique_ptr<EchoRemover> echo_remover(
|
||||
EchoRemover::Create(config, sample_rate_hz));
|
||||
return Create(config, sample_rate_hz, std::move(render_buffer),
|
||||
std::move(delay_controller), std::move(echo_remover));
|
||||
}
|
||||
|
||||
BlockProcessor* BlockProcessor::Create(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz,
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer,
|
||||
std::unique_ptr<RenderDelayController> delay_controller,
|
||||
std::unique_ptr<EchoRemover> echo_remover) {
|
||||
return new BlockProcessorImpl(sample_rate_hz, std::move(render_buffer),
|
||||
std::move(delay_controller),
|
||||
std::move(echo_remover));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_PROCESSOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_PROCESSOR_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/echo_remover.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_delay_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_delay_controller.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Class for performing echo cancellation on 64 sample blocks of audio data.
|
||||
class BlockProcessor {
|
||||
public:
|
||||
static BlockProcessor* Create(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz);
|
||||
// Only used for testing purposes.
|
||||
static BlockProcessor* Create(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz,
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer);
|
||||
static BlockProcessor* Create(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz,
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer,
|
||||
std::unique_ptr<RenderDelayController> delay_controller,
|
||||
std::unique_ptr<EchoRemover> echo_remover);
|
||||
|
||||
virtual ~BlockProcessor() = default;
|
||||
|
||||
// Processes a block of capture data.
|
||||
virtual void ProcessCapture(
|
||||
bool echo_path_gain_change,
|
||||
bool capture_signal_saturation,
|
||||
std::vector<std::vector<float>>* capture_block) = 0;
|
||||
|
||||
// Buffers a block of render data supplied by a FrameBlocker object.
|
||||
virtual void BufferRender(
|
||||
const std::vector<std::vector<float>>& render_block) = 0;
|
||||
|
||||
// Reports whether echo leakage has been detected in the echo canceller
|
||||
// output.
|
||||
virtual void UpdateEchoLeakageStatus(bool leakage_detected) = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_PROCESSOR_H_
|
||||
@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/block_processor_metrics.h"
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
enum class RenderUnderrunCategory {
|
||||
kNone,
|
||||
kFew,
|
||||
kSeveral,
|
||||
kMany,
|
||||
kConstant,
|
||||
kNumCategories
|
||||
};
|
||||
|
||||
enum class RenderOverrunCategory {
|
||||
kNone,
|
||||
kFew,
|
||||
kSeveral,
|
||||
kMany,
|
||||
kConstant,
|
||||
kNumCategories
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void BlockProcessorMetrics::UpdateCapture(bool underrun) {
|
||||
++capture_block_counter_;
|
||||
if (underrun) {
|
||||
++render_buffer_underruns_;
|
||||
}
|
||||
|
||||
if (capture_block_counter_ == kMetricsReportingIntervalBlocks) {
|
||||
metrics_reported_ = true;
|
||||
|
||||
RenderUnderrunCategory underrun_category;
|
||||
if (render_buffer_underruns_ == 0) {
|
||||
underrun_category = RenderUnderrunCategory::kNone;
|
||||
} else if (render_buffer_underruns_ > (capture_block_counter_ >> 1)) {
|
||||
underrun_category = RenderUnderrunCategory::kConstant;
|
||||
} else if (render_buffer_underruns_ > 100) {
|
||||
underrun_category = RenderUnderrunCategory::kMany;
|
||||
} else if (render_buffer_underruns_ > 10) {
|
||||
underrun_category = RenderUnderrunCategory::kSeveral;
|
||||
} else {
|
||||
underrun_category = RenderUnderrunCategory::kFew;
|
||||
}
|
||||
RTC_HISTOGRAM_ENUMERATION(
|
||||
"WebRTC.Audio.EchoCanceller.RenderUnderruns",
|
||||
static_cast<int>(underrun_category),
|
||||
static_cast<int>(RenderUnderrunCategory::kNumCategories));
|
||||
|
||||
RenderOverrunCategory overrun_category;
|
||||
if (render_buffer_overruns_ == 0) {
|
||||
overrun_category = RenderOverrunCategory::kNone;
|
||||
} else if (render_buffer_overruns_ > (buffer_render_calls_ >> 1)) {
|
||||
overrun_category = RenderOverrunCategory::kConstant;
|
||||
} else if (render_buffer_overruns_ > 100) {
|
||||
overrun_category = RenderOverrunCategory::kMany;
|
||||
} else if (render_buffer_overruns_ > 10) {
|
||||
overrun_category = RenderOverrunCategory::kSeveral;
|
||||
} else {
|
||||
overrun_category = RenderOverrunCategory::kFew;
|
||||
}
|
||||
RTC_HISTOGRAM_ENUMERATION(
|
||||
"WebRTC.Audio.EchoCanceller.RenderOverruns",
|
||||
static_cast<int>(overrun_category),
|
||||
static_cast<int>(RenderOverrunCategory::kNumCategories));
|
||||
|
||||
ResetMetrics();
|
||||
capture_block_counter_ = 0;
|
||||
} else {
|
||||
metrics_reported_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BlockProcessorMetrics::UpdateRender(bool overrun) {
|
||||
++buffer_render_calls_;
|
||||
if (overrun) {
|
||||
++render_buffer_overruns_;
|
||||
}
|
||||
}
|
||||
|
||||
void BlockProcessorMetrics::ResetMetrics() {
|
||||
render_buffer_underruns_ = 0;
|
||||
render_buffer_overruns_ = 0;
|
||||
buffer_render_calls_ = 0;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_PROCESSOR_METRICS_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_PROCESSOR_METRICS_H_
|
||||
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Handles the reporting of metrics for the block_processor.
|
||||
class BlockProcessorMetrics {
|
||||
public:
|
||||
BlockProcessorMetrics() = default;
|
||||
|
||||
// Updates the metric with new capture data.
|
||||
void UpdateCapture(bool underrun);
|
||||
|
||||
// Updates the metric with new render data.
|
||||
void UpdateRender(bool overrun);
|
||||
|
||||
// Returns true if the metrics have just been reported, otherwise false.
|
||||
bool MetricsReported() { return metrics_reported_; }
|
||||
|
||||
private:
|
||||
// Resets the metrics.
|
||||
void ResetMetrics();
|
||||
|
||||
int capture_block_counter_ = 0;
|
||||
bool metrics_reported_ = false;
|
||||
int render_buffer_underruns_ = 0;
|
||||
int render_buffer_overruns_ = 0;
|
||||
int buffer_render_calls_ = 0;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(BlockProcessorMetrics);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_PROCESSOR_METRICS_H_
|
||||
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/block_processor_metrics.h"
|
||||
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Verify the general functionality of BlockProcessorMetrics.
|
||||
TEST(BlockProcessorMetrics, NormalUsage) {
|
||||
BlockProcessorMetrics metrics;
|
||||
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
for (int k = 0; k < kMetricsReportingIntervalBlocks - 1; ++k) {
|
||||
metrics.UpdateRender(false);
|
||||
metrics.UpdateRender(false);
|
||||
metrics.UpdateCapture(false);
|
||||
EXPECT_FALSE(metrics.MetricsReported());
|
||||
}
|
||||
metrics.UpdateCapture(false);
|
||||
EXPECT_TRUE(metrics.MetricsReported());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,261 +0,0 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/audio_processing/aec3/block_processor.h"
|
||||
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/mock/mock_echo_remover.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/mock/mock_render_delay_controller.h"
|
||||
#include "webrtc/modules/audio_processing/test/echo_canceller_test_tools.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/rtc_base/random.h"
|
||||
#include "webrtc/test/gmock.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
using testing::AtLeast;
|
||||
using testing::Return;
|
||||
using testing::StrictMock;
|
||||
using testing::_;
|
||||
|
||||
// Verifies that the basic BlockProcessor functionality works and that the API
|
||||
// methods are callable.
|
||||
void RunBasicSetupAndApiCallTest(int sample_rate_hz) {
|
||||
std::unique_ptr<BlockProcessor> block_processor(BlockProcessor::Create(
|
||||
AudioProcessing::Config::EchoCanceller3(), sample_rate_hz));
|
||||
std::vector<std::vector<float>> block(NumBandsForRate(sample_rate_hz),
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
|
||||
block_processor->BufferRender(block);
|
||||
block_processor->ProcessCapture(false, false, &block);
|
||||
block_processor->UpdateEchoLeakageStatus(false);
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
void RunRenderBlockSizeVerificationTest(int sample_rate_hz) {
|
||||
std::unique_ptr<BlockProcessor> block_processor(BlockProcessor::Create(
|
||||
AudioProcessing::Config::EchoCanceller3(), sample_rate_hz));
|
||||
std::vector<std::vector<float>> block(
|
||||
NumBandsForRate(sample_rate_hz), std::vector<float>(kBlockSize - 1, 0.f));
|
||||
|
||||
EXPECT_DEATH(block_processor->BufferRender(block), "");
|
||||
}
|
||||
|
||||
void RunCaptureBlockSizeVerificationTest(int sample_rate_hz) {
|
||||
std::unique_ptr<BlockProcessor> block_processor(BlockProcessor::Create(
|
||||
AudioProcessing::Config::EchoCanceller3(), sample_rate_hz));
|
||||
std::vector<std::vector<float>> block(
|
||||
NumBandsForRate(sample_rate_hz), std::vector<float>(kBlockSize - 1, 0.f));
|
||||
|
||||
EXPECT_DEATH(block_processor->ProcessCapture(false, false, &block), "");
|
||||
}
|
||||
|
||||
void RunRenderNumBandsVerificationTest(int sample_rate_hz) {
|
||||
const size_t wrong_num_bands = NumBandsForRate(sample_rate_hz) < 3
|
||||
? NumBandsForRate(sample_rate_hz) + 1
|
||||
: 1;
|
||||
std::unique_ptr<BlockProcessor> block_processor(BlockProcessor::Create(
|
||||
AudioProcessing::Config::EchoCanceller3(), sample_rate_hz));
|
||||
std::vector<std::vector<float>> block(wrong_num_bands,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
|
||||
EXPECT_DEATH(block_processor->BufferRender(block), "");
|
||||
}
|
||||
|
||||
void RunCaptureNumBandsVerificationTest(int sample_rate_hz) {
|
||||
const size_t wrong_num_bands = NumBandsForRate(sample_rate_hz) < 3
|
||||
? NumBandsForRate(sample_rate_hz) + 1
|
||||
: 1;
|
||||
std::unique_ptr<BlockProcessor> block_processor(BlockProcessor::Create(
|
||||
AudioProcessing::Config::EchoCanceller3(), sample_rate_hz));
|
||||
std::vector<std::vector<float>> block(wrong_num_bands,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
|
||||
EXPECT_DEATH(block_processor->ProcessCapture(false, false, &block), "");
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string ProduceDebugText(int sample_rate_hz) {
|
||||
std::ostringstream ss;
|
||||
ss << "Sample rate: " << sample_rate_hz;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies that the delay controller functionality is properly integrated with
|
||||
// the render delay buffer inside block processor.
|
||||
// TODO(peah): Activate the unittest once the required code has been landed.
|
||||
TEST(BlockProcessor, DISABLED_DelayControllerIntegration) {
|
||||
constexpr size_t kNumBlocks = 310;
|
||||
constexpr size_t kDelayInSamples = 640;
|
||||
constexpr size_t kDelayHeadroom = 1;
|
||||
constexpr size_t kDelayInBlocks =
|
||||
kDelayInSamples / kBlockSize - kDelayHeadroom;
|
||||
Random random_generator(42U);
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<testing::StrictMock<webrtc::test::MockRenderDelayBuffer>>
|
||||
render_delay_buffer_mock(
|
||||
new StrictMock<webrtc::test::MockRenderDelayBuffer>(rate));
|
||||
EXPECT_CALL(*render_delay_buffer_mock, Insert(_))
|
||||
.Times(kNumBlocks)
|
||||
.WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*render_delay_buffer_mock, IsBlockAvailable())
|
||||
.Times(kNumBlocks)
|
||||
.WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*render_delay_buffer_mock, SetDelay(kDelayInBlocks))
|
||||
.Times(AtLeast(1));
|
||||
EXPECT_CALL(*render_delay_buffer_mock, MaxDelay()).WillOnce(Return(30));
|
||||
EXPECT_CALL(*render_delay_buffer_mock, Delay())
|
||||
.Times(kNumBlocks + 1)
|
||||
.WillRepeatedly(Return(0));
|
||||
std::unique_ptr<BlockProcessor> block_processor(
|
||||
BlockProcessor::Create(AudioProcessing::Config::EchoCanceller3(), rate,
|
||||
std::move(render_delay_buffer_mock)));
|
||||
|
||||
std::vector<std::vector<float>> render_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> capture_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
DelayBuffer<float> signal_delay_buffer(kDelayInSamples);
|
||||
for (size_t k = 0; k < kNumBlocks; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render_block[0]);
|
||||
signal_delay_buffer.Delay(render_block[0], capture_block[0]);
|
||||
block_processor->BufferRender(render_block);
|
||||
block_processor->ProcessCapture(false, false, &capture_block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that BlockProcessor submodules are called in a proper manner.
|
||||
TEST(BlockProcessor, DISABLED_SubmoduleIntegration) {
|
||||
constexpr size_t kNumBlocks = 310;
|
||||
Random random_generator(42U);
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<testing::StrictMock<webrtc::test::MockRenderDelayBuffer>>
|
||||
render_delay_buffer_mock(
|
||||
new StrictMock<webrtc::test::MockRenderDelayBuffer>(rate));
|
||||
std::unique_ptr<
|
||||
testing::StrictMock<webrtc::test::MockRenderDelayController>>
|
||||
render_delay_controller_mock(
|
||||
new StrictMock<webrtc::test::MockRenderDelayController>());
|
||||
std::unique_ptr<testing::StrictMock<webrtc::test::MockEchoRemover>>
|
||||
echo_remover_mock(new StrictMock<webrtc::test::MockEchoRemover>());
|
||||
|
||||
EXPECT_CALL(*render_delay_buffer_mock, Insert(_))
|
||||
.Times(kNumBlocks - 1)
|
||||
.WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*render_delay_buffer_mock, IsBlockAvailable())
|
||||
.Times(kNumBlocks)
|
||||
.WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*render_delay_buffer_mock, UpdateBuffers()).Times(kNumBlocks);
|
||||
EXPECT_CALL(*render_delay_buffer_mock, SetDelay(9)).Times(AtLeast(1));
|
||||
EXPECT_CALL(*render_delay_buffer_mock, Delay())
|
||||
.Times(kNumBlocks)
|
||||
.WillRepeatedly(Return(0));
|
||||
EXPECT_CALL(*render_delay_controller_mock, GetDelay(_, _))
|
||||
.Times(kNumBlocks)
|
||||
.WillRepeatedly(Return(9));
|
||||
EXPECT_CALL(*render_delay_controller_mock, AlignmentHeadroomSamples())
|
||||
.Times(kNumBlocks);
|
||||
EXPECT_CALL(*echo_remover_mock, ProcessCapture(_, _, _, _, _))
|
||||
.Times(kNumBlocks);
|
||||
EXPECT_CALL(*echo_remover_mock, UpdateEchoLeakageStatus(_))
|
||||
.Times(kNumBlocks);
|
||||
|
||||
std::unique_ptr<BlockProcessor> block_processor(BlockProcessor::Create(
|
||||
AudioProcessing::Config::EchoCanceller3(), rate,
|
||||
std::move(render_delay_buffer_mock),
|
||||
std::move(render_delay_controller_mock), std::move(echo_remover_mock)));
|
||||
|
||||
std::vector<std::vector<float>> render_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> capture_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
DelayBuffer<float> signal_delay_buffer(640);
|
||||
for (size_t k = 0; k < kNumBlocks; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render_block[0]);
|
||||
signal_delay_buffer.Delay(render_block[0], capture_block[0]);
|
||||
block_processor->BufferRender(render_block);
|
||||
block_processor->ProcessCapture(false, false, &capture_block);
|
||||
block_processor->UpdateEchoLeakageStatus(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlockProcessor, BasicSetupAndApiCalls) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
RunBasicSetupAndApiCallTest(rate);
|
||||
}
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
TEST(BlockProcessor, VerifyRenderBlockSizeCheck) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
RunRenderBlockSizeVerificationTest(rate);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlockProcessor, VerifyCaptureBlockSizeCheck) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
RunCaptureBlockSizeVerificationTest(rate);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlockProcessor, VerifyRenderNumBandsCheck) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
RunRenderNumBandsVerificationTest(rate);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(peah): Verify the check for correct number of bands in the capture
|
||||
// signal.
|
||||
TEST(BlockProcessor, VerifyCaptureNumBandsCheck) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
RunCaptureNumBandsVerificationTest(rate);
|
||||
}
|
||||
}
|
||||
|
||||
// Verifiers that the verification for null ProcessCapture input works.
|
||||
TEST(BlockProcessor, NullProcessCaptureParameter) {
|
||||
EXPECT_DEATH(std::unique_ptr<BlockProcessor>(
|
||||
BlockProcessor::Create(
|
||||
AudioProcessing::Config::EchoCanceller3(), 8000))
|
||||
->ProcessCapture(false, false, nullptr),
|
||||
"");
|
||||
}
|
||||
|
||||
// Verifies the check for correct sample rate.
|
||||
// TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
|
||||
// tests on test bots has been fixed.
|
||||
TEST(BlockProcessor, DISABLED_WrongSampleRate) {
|
||||
EXPECT_DEATH(std::unique_ptr<BlockProcessor>(BlockProcessor::Create(
|
||||
AudioProcessing::Config::EchoCanceller3(), 8001)),
|
||||
"");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,58 +0,0 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.h"
|
||||
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
CascadedBiQuadFilter::CascadedBiQuadFilter(
|
||||
const CascadedBiQuadFilter::BiQuadCoefficients& coefficients,
|
||||
size_t num_biquads)
|
||||
: biquad_states_(num_biquads), coefficients_(coefficients) {}
|
||||
|
||||
CascadedBiQuadFilter::~CascadedBiQuadFilter() = default;
|
||||
|
||||
void CascadedBiQuadFilter::Process(rtc::ArrayView<const float> x,
|
||||
rtc::ArrayView<float> y) {
|
||||
ApplyBiQuad(x, y, &biquad_states_[0]);
|
||||
for (size_t k = 1; k < biquad_states_.size(); ++k) {
|
||||
ApplyBiQuad(y, y, &biquad_states_[k]);
|
||||
}
|
||||
}
|
||||
|
||||
void CascadedBiQuadFilter::Process(rtc::ArrayView<float> y) {
|
||||
for (auto& biquad : biquad_states_) {
|
||||
ApplyBiQuad(y, y, &biquad);
|
||||
}
|
||||
}
|
||||
|
||||
void CascadedBiQuadFilter::ApplyBiQuad(
|
||||
rtc::ArrayView<const float> x,
|
||||
rtc::ArrayView<float> y,
|
||||
CascadedBiQuadFilter::BiQuadState* biquad_state) {
|
||||
RTC_DCHECK_EQ(x.size(), y.size());
|
||||
RTC_DCHECK(biquad_state);
|
||||
const auto* c_b = coefficients_.b;
|
||||
const auto* c_a = coefficients_.a;
|
||||
auto* m_x = biquad_state->x;
|
||||
auto* m_y = biquad_state->y;
|
||||
for (size_t k = 0; k < x.size(); ++k) {
|
||||
const float tmp = x[k];
|
||||
y[k] = c_b[0] * tmp + c_b[1] * m_x[0] + c_b[2] * m_x[1] - c_a[0] * m_y[0] -
|
||||
c_a[1] * m_y[1];
|
||||
m_x[1] = m_x[0];
|
||||
m_x[0] = tmp;
|
||||
m_y[1] = m_y[0];
|
||||
m_y[0] = y[k];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,58 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_CASCADED_BIQUAD_FILTER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_CASCADED_BIQUAD_FILTER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Applies a number of identical biquads in a cascaded manner. The filter
|
||||
// implementation is direct form 1.
|
||||
class CascadedBiQuadFilter {
|
||||
public:
|
||||
struct BiQuadState {
|
||||
BiQuadState() : x(), y() {}
|
||||
float x[2];
|
||||
float y[2];
|
||||
};
|
||||
|
||||
struct BiQuadCoefficients {
|
||||
float b[3];
|
||||
float a[2];
|
||||
};
|
||||
|
||||
CascadedBiQuadFilter(
|
||||
const CascadedBiQuadFilter::BiQuadCoefficients& coefficients,
|
||||
size_t num_biquads);
|
||||
~CascadedBiQuadFilter();
|
||||
// Applies the biquads on the values in x in order to form the output in y.
|
||||
void Process(rtc::ArrayView<const float> x, rtc::ArrayView<float> y);
|
||||
// Applies the biquads on the values in y in an in-place manner.
|
||||
void Process(rtc::ArrayView<float> y);
|
||||
|
||||
private:
|
||||
void ApplyBiQuad(rtc::ArrayView<const float> x,
|
||||
rtc::ArrayView<float> y,
|
||||
CascadedBiQuadFilter::BiQuadState* biquad_state);
|
||||
|
||||
std::vector<BiQuadState> biquad_states_;
|
||||
const BiQuadCoefficients coefficients_;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(CascadedBiQuadFilter);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_CASCADED_BIQUAD_FILTER_H_
|
||||
@ -1,98 +0,0 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Coefficients for a second order Butterworth high-pass filter with cutoff
|
||||
// frequency 100 Hz.
|
||||
const CascadedBiQuadFilter::BiQuadCoefficients kHighPassFilterCoefficients = {
|
||||
{0.97261f, -1.94523f, 0.97261f},
|
||||
{-1.94448f, 0.94598f}};
|
||||
|
||||
const CascadedBiQuadFilter::BiQuadCoefficients kTransparentCoefficients = {
|
||||
{1.f, 0.f, 0.f},
|
||||
{0.f, 0.f}};
|
||||
|
||||
const CascadedBiQuadFilter::BiQuadCoefficients kBlockingCoefficients = {
|
||||
{0.f, 0.f, 0.f},
|
||||
{0.f, 0.f}};
|
||||
|
||||
std::vector<float> CreateInputWithIncreasingValues(size_t vector_length) {
|
||||
std::vector<float> v(vector_length);
|
||||
for (size_t k = 0; k < v.size(); ++k) {
|
||||
v[k] = k;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies that the filter applies an effect which removes the input signal.
|
||||
// The test also verifies that the in-place Process API call works as intended.
|
||||
TEST(CascadedBiquadFilter, BlockingConfiguration) {
|
||||
std::vector<float> values = CreateInputWithIncreasingValues(1000);
|
||||
|
||||
CascadedBiQuadFilter filter(kBlockingCoefficients, 1);
|
||||
filter.Process(values);
|
||||
|
||||
EXPECT_EQ(std::vector<float>(1000, 0.f), values);
|
||||
}
|
||||
|
||||
// Verifies that the filter is able to form a zero-mean output from a
|
||||
// non-zeromean input signal when coefficients for a high-pass filter are
|
||||
// applied. The test also verifies that the filter works with multiple biquads.
|
||||
TEST(CascadedBiquadFilter, HighPassConfiguration) {
|
||||
std::vector<float> values(1000);
|
||||
for (size_t k = 0; k < values.size(); ++k) {
|
||||
values[k] = 1.f;
|
||||
}
|
||||
|
||||
CascadedBiQuadFilter filter(kHighPassFilterCoefficients, 2);
|
||||
filter.Process(values);
|
||||
|
||||
for (size_t k = values.size() / 2; k < values.size(); ++k) {
|
||||
EXPECT_NEAR(0.f, values[k], 1e-4);
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the filter is able to produce a transparent effect with no
|
||||
// impact on the data when the proper coefficients are applied. The test also
|
||||
// verifies that the non-in-place Process API call works as intended.
|
||||
TEST(CascadedBiquadFilter, TransparentConfiguration) {
|
||||
const std::vector<float> input = CreateInputWithIncreasingValues(1000);
|
||||
std::vector<float> output(input.size());
|
||||
|
||||
CascadedBiQuadFilter filter(kTransparentCoefficients, 1);
|
||||
filter.Process(input, output);
|
||||
|
||||
EXPECT_EQ(input, output);
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
// Verifies that the check of the lengths for the input and output works for the
|
||||
// non-in-place call.
|
||||
TEST(CascadedBiquadFilter, InputSizeCheckVerification) {
|
||||
const std::vector<float> input = CreateInputWithIncreasingValues(10);
|
||||
std::vector<float> output(input.size() - 1);
|
||||
|
||||
CascadedBiQuadFilter filter(kTransparentCoefficients, 1);
|
||||
EXPECT_DEATH(filter.Process(input, output), "");
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,219 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/comfort_noise_generator.h"
|
||||
|
||||
#include "webrtc/typedefs.h"
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
#include <math.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
|
||||
#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Creates an array of uniformly distributed variables.
|
||||
void TableRandomValue(int16_t* vector, int16_t vector_length, uint32_t* seed) {
|
||||
for (int i = 0; i < vector_length; i++) {
|
||||
seed[0] = (seed[0] * ((int32_t)69069) + 1) & (0x80000000 - 1);
|
||||
vector[i] = (int16_t)(seed[0] >> 16);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace aec3 {
|
||||
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
|
||||
void EstimateComfortNoise_SSE2(const std::array<float, kFftLengthBy2Plus1>& N2,
|
||||
uint32_t* seed,
|
||||
FftData* lower_band_noise,
|
||||
FftData* upper_band_noise) {
|
||||
FftData* N_low = lower_band_noise;
|
||||
FftData* N_high = upper_band_noise;
|
||||
|
||||
// Compute square root spectrum.
|
||||
std::array<float, kFftLengthBy2Plus1> N;
|
||||
for (size_t k = 0; k < kFftLengthBy2; k += 4) {
|
||||
__m128 v = _mm_loadu_ps(&N2[k]);
|
||||
v = _mm_sqrt_ps(v);
|
||||
_mm_storeu_ps(&N[k], v);
|
||||
}
|
||||
|
||||
N[kFftLengthBy2] = sqrtf(N2[kFftLengthBy2]);
|
||||
|
||||
// Compute the noise level for the upper bands.
|
||||
constexpr float kOneByNumBands = 1.f / (kFftLengthBy2Plus1 / 2 + 1);
|
||||
constexpr int kFftLengthBy2Plus1By2 = kFftLengthBy2Plus1 / 2;
|
||||
const float high_band_noise_level =
|
||||
std::accumulate(N.begin() + kFftLengthBy2Plus1By2, N.end(), 0.f) *
|
||||
kOneByNumBands;
|
||||
|
||||
// Generate complex noise.
|
||||
std::array<int16_t, kFftLengthBy2 - 1> random_values_int;
|
||||
TableRandomValue(random_values_int.data(), random_values_int.size(), seed);
|
||||
|
||||
std::array<float, kFftLengthBy2 - 1> sin;
|
||||
std::array<float, kFftLengthBy2 - 1> cos;
|
||||
constexpr float kScale = 6.28318530717959f / 32768.0f;
|
||||
std::transform(random_values_int.begin(), random_values_int.end(),
|
||||
sin.begin(), [&](int16_t a) { return -sinf(kScale * a); });
|
||||
std::transform(random_values_int.begin(), random_values_int.end(),
|
||||
cos.begin(), [&](int16_t a) { return cosf(kScale * a); });
|
||||
|
||||
// Form low-frequency noise via spectral shaping.
|
||||
N_low->re[0] = N_low->re[kFftLengthBy2] = N_high->re[0] =
|
||||
N_high->re[kFftLengthBy2] = 0.f;
|
||||
std::transform(cos.begin(), cos.end(), N.begin() + 1, N_low->re.begin() + 1,
|
||||
std::multiplies<float>());
|
||||
std::transform(sin.begin(), sin.end(), N.begin() + 1, N_low->im.begin() + 1,
|
||||
std::multiplies<float>());
|
||||
|
||||
// Form the high-frequency noise via simple levelling.
|
||||
std::transform(cos.begin(), cos.end(), N_high->re.begin() + 1,
|
||||
[&](float a) { return high_band_noise_level * a; });
|
||||
std::transform(sin.begin(), sin.end(), N_high->im.begin() + 1,
|
||||
[&](float a) { return high_band_noise_level * a; });
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void EstimateComfortNoise(const std::array<float, kFftLengthBy2Plus1>& N2,
|
||||
uint32_t* seed,
|
||||
FftData* lower_band_noise,
|
||||
FftData* upper_band_noise) {
|
||||
FftData* N_low = lower_band_noise;
|
||||
FftData* N_high = upper_band_noise;
|
||||
|
||||
// Compute square root spectrum.
|
||||
std::array<float, kFftLengthBy2Plus1> N;
|
||||
std::transform(N2.begin(), N2.end(), N.begin(),
|
||||
[](float a) { return sqrtf(a); });
|
||||
|
||||
// Compute the noise level for the upper bands.
|
||||
constexpr float kOneByNumBands = 1.f / (kFftLengthBy2Plus1 / 2 + 1);
|
||||
constexpr int kFftLengthBy2Plus1By2 = kFftLengthBy2Plus1 / 2;
|
||||
const float high_band_noise_level =
|
||||
std::accumulate(N.begin() + kFftLengthBy2Plus1By2, N.end(), 0.f) *
|
||||
kOneByNumBands;
|
||||
|
||||
// Generate complex noise.
|
||||
std::array<int16_t, kFftLengthBy2 - 1> random_values_int;
|
||||
TableRandomValue(random_values_int.data(), random_values_int.size(), seed);
|
||||
|
||||
std::array<float, kFftLengthBy2 - 1> sin;
|
||||
std::array<float, kFftLengthBy2 - 1> cos;
|
||||
constexpr float kScale = 6.28318530717959f / 32768.0f;
|
||||
std::transform(random_values_int.begin(), random_values_int.end(),
|
||||
sin.begin(), [&](int16_t a) { return -sinf(kScale * a); });
|
||||
std::transform(random_values_int.begin(), random_values_int.end(),
|
||||
cos.begin(), [&](int16_t a) { return cosf(kScale * a); });
|
||||
|
||||
// Form low-frequency noise via spectral shaping.
|
||||
N_low->re[0] = N_low->re[kFftLengthBy2] = N_high->re[0] =
|
||||
N_high->re[kFftLengthBy2] = 0.f;
|
||||
std::transform(cos.begin(), cos.end(), N.begin() + 1, N_low->re.begin() + 1,
|
||||
std::multiplies<float>());
|
||||
std::transform(sin.begin(), sin.end(), N.begin() + 1, N_low->im.begin() + 1,
|
||||
std::multiplies<float>());
|
||||
|
||||
// Form the high-frequency noise via simple levelling.
|
||||
std::transform(cos.begin(), cos.end(), N_high->re.begin() + 1,
|
||||
[&](float a) { return high_band_noise_level * a; });
|
||||
std::transform(sin.begin(), sin.end(), N_high->im.begin() + 1,
|
||||
[&](float a) { return high_band_noise_level * a; });
|
||||
}
|
||||
|
||||
} // namespace aec3
|
||||
|
||||
ComfortNoiseGenerator::ComfortNoiseGenerator(Aec3Optimization optimization)
|
||||
: optimization_(optimization),
|
||||
seed_(42),
|
||||
N2_initial_(new std::array<float, kFftLengthBy2Plus1>()) {
|
||||
N2_initial_->fill(0.f);
|
||||
Y2_smoothed_.fill(0.f);
|
||||
N2_.fill(1.0e6f);
|
||||
}
|
||||
|
||||
ComfortNoiseGenerator::~ComfortNoiseGenerator() = default;
|
||||
|
||||
void ComfortNoiseGenerator::Compute(
|
||||
const AecState& aec_state,
|
||||
const std::array<float, kFftLengthBy2Plus1>& capture_spectrum,
|
||||
FftData* lower_band_noise,
|
||||
FftData* upper_band_noise) {
|
||||
RTC_DCHECK(lower_band_noise);
|
||||
RTC_DCHECK(upper_band_noise);
|
||||
const auto& Y2 = capture_spectrum;
|
||||
|
||||
if (!aec_state.SaturatedCapture()) {
|
||||
// Smooth Y2.
|
||||
std::transform(Y2_smoothed_.begin(), Y2_smoothed_.end(), Y2.begin(),
|
||||
Y2_smoothed_.begin(),
|
||||
[](float a, float b) { return a + 0.1f * (b - a); });
|
||||
|
||||
if (N2_counter_ > 50) {
|
||||
// Update N2 from Y2_smoothed.
|
||||
std::transform(N2_.begin(), N2_.end(), Y2_smoothed_.begin(), N2_.begin(),
|
||||
[](float a, float b) {
|
||||
return b < a ? (0.9f * b + 0.1f * a) * 1.0002f
|
||||
: a * 1.0002f;
|
||||
});
|
||||
}
|
||||
|
||||
if (N2_initial_) {
|
||||
if (++N2_counter_ == 1000) {
|
||||
N2_initial_.reset();
|
||||
} else {
|
||||
// Compute the N2_initial from N2.
|
||||
std::transform(
|
||||
N2_.begin(), N2_.end(), N2_initial_->begin(), N2_initial_->begin(),
|
||||
[](float a, float b) { return a > b ? b + 0.001f * (a - b) : a; });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Limit the noise to a floor of -96 dBFS.
|
||||
constexpr float kNoiseFloor = 440.f;
|
||||
for (auto& n : N2_) {
|
||||
n = std::max(n, kNoiseFloor);
|
||||
}
|
||||
if (N2_initial_) {
|
||||
for (auto& n : *N2_initial_) {
|
||||
n = std::max(n, kNoiseFloor);
|
||||
}
|
||||
}
|
||||
|
||||
// Choose N2 estimate to use.
|
||||
const std::array<float, kFftLengthBy2Plus1>& N2 =
|
||||
N2_initial_ ? *N2_initial_ : N2_;
|
||||
|
||||
switch (optimization_) {
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
case Aec3Optimization::kSse2:
|
||||
aec3::EstimateComfortNoise_SSE2(N2, &seed_, lower_band_noise,
|
||||
upper_band_noise);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
aec3::EstimateComfortNoise(N2, &seed_, lower_band_noise,
|
||||
upper_band_noise);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_COMFORT_NOISE_GENERATOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_COMFORT_NOISE_GENERATOR_H_
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec_state.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/fft_data.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace aec3 {
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
|
||||
void EstimateComfortNoise_SSE2(const std::array<float, kFftLengthBy2Plus1>& N2,
|
||||
uint32_t* seed,
|
||||
FftData* lower_band_noise,
|
||||
FftData* upper_band_noise);
|
||||
#endif
|
||||
void EstimateComfortNoise(const std::array<float, kFftLengthBy2Plus1>& N2,
|
||||
uint32_t* seed,
|
||||
FftData* lower_band_noise,
|
||||
FftData* upper_band_noise);
|
||||
|
||||
} // namespace aec3
|
||||
|
||||
// Generates the comfort noise.
|
||||
class ComfortNoiseGenerator {
|
||||
public:
|
||||
explicit ComfortNoiseGenerator(Aec3Optimization optimization);
|
||||
~ComfortNoiseGenerator();
|
||||
|
||||
// Computes the comfort noise.
|
||||
void Compute(const AecState& aec_state,
|
||||
const std::array<float, kFftLengthBy2Plus1>& capture_spectrum,
|
||||
FftData* lower_band_noise,
|
||||
FftData* upper_band_noise);
|
||||
|
||||
// Returns the estimate of the background noise spectrum.
|
||||
const std::array<float, kFftLengthBy2Plus1>& NoiseSpectrum() const {
|
||||
return N2_;
|
||||
}
|
||||
|
||||
private:
|
||||
const Aec3Optimization optimization_;
|
||||
uint32_t seed_;
|
||||
std::unique_ptr<std::array<float, kFftLengthBy2Plus1>> N2_initial_;
|
||||
std::array<float, kFftLengthBy2Plus1> Y2_smoothed_;
|
||||
std::array<float, kFftLengthBy2Plus1> N2_;
|
||||
int N2_counter_ = 0;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ComfortNoiseGenerator);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_COMFORT_NOISE_GENERATOR_H_
|
||||
@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/comfort_noise_generator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "webrtc/rtc_base/random.h"
|
||||
#include "webrtc/system_wrappers/include/cpu_features_wrapper.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace aec3 {
|
||||
namespace {
|
||||
|
||||
float Power(const FftData& N) {
|
||||
std::array<float, kFftLengthBy2Plus1> N2;
|
||||
N.Spectrum(Aec3Optimization::kNone, &N2);
|
||||
return std::accumulate(N2.begin(), N2.end(), 0.f) / N2.size();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
|
||||
TEST(ComfortNoiseGenerator, NullLowerBandNoise) {
|
||||
std::array<float, kFftLengthBy2Plus1> N2;
|
||||
FftData noise;
|
||||
EXPECT_DEATH(ComfortNoiseGenerator(DetectOptimization())
|
||||
.Compute(AecState(AudioProcessing::Config::EchoCanceller3{}),
|
||||
N2, nullptr, &noise),
|
||||
"");
|
||||
}
|
||||
|
||||
TEST(ComfortNoiseGenerator, NullUpperBandNoise) {
|
||||
std::array<float, kFftLengthBy2Plus1> N2;
|
||||
FftData noise;
|
||||
EXPECT_DEATH(ComfortNoiseGenerator(DetectOptimization())
|
||||
.Compute(AecState(AudioProcessing::Config::EchoCanceller3{}),
|
||||
N2, &noise, nullptr),
|
||||
"");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
// Verifies that the optimized methods are bitexact to their reference
|
||||
// counterparts.
|
||||
TEST(ComfortNoiseGenerator, TestOptimizations) {
|
||||
if (WebRtc_GetCPUInfo(kSSE2) != 0) {
|
||||
Random random_generator(42U);
|
||||
uint32_t seed = 42;
|
||||
uint32_t seed_SSE2 = 42;
|
||||
std::array<float, kFftLengthBy2Plus1> N2;
|
||||
FftData lower_band_noise;
|
||||
FftData upper_band_noise;
|
||||
FftData lower_band_noise_SSE2;
|
||||
FftData upper_band_noise_SSE2;
|
||||
for (int k = 0; k < 10; ++k) {
|
||||
for (size_t j = 0; j < N2.size(); ++j) {
|
||||
N2[j] = random_generator.Rand<float>() * 1000.f;
|
||||
}
|
||||
|
||||
EstimateComfortNoise(N2, &seed, &lower_band_noise, &upper_band_noise);
|
||||
EstimateComfortNoise_SSE2(N2, &seed_SSE2, &lower_band_noise_SSE2,
|
||||
&upper_band_noise_SSE2);
|
||||
for (size_t j = 0; j < lower_band_noise.re.size(); ++j) {
|
||||
EXPECT_NEAR(lower_band_noise.re[j], lower_band_noise_SSE2.re[j],
|
||||
0.00001f);
|
||||
EXPECT_NEAR(upper_band_noise.re[j], upper_band_noise_SSE2.re[j],
|
||||
0.00001f);
|
||||
}
|
||||
for (size_t j = 1; j < lower_band_noise.re.size() - 1; ++j) {
|
||||
EXPECT_NEAR(lower_band_noise.im[j], lower_band_noise_SSE2.im[j],
|
||||
0.00001f);
|
||||
EXPECT_NEAR(upper_band_noise.im[j], upper_band_noise_SSE2.im[j],
|
||||
0.00001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST(ComfortNoiseGenerator, CorrectLevel) {
|
||||
ComfortNoiseGenerator cng(DetectOptimization());
|
||||
AecState aec_state(AudioProcessing::Config::EchoCanceller3{});
|
||||
|
||||
std::array<float, kFftLengthBy2Plus1> N2;
|
||||
N2.fill(1000.f * 1000.f);
|
||||
|
||||
FftData n_lower;
|
||||
FftData n_upper;
|
||||
n_lower.re.fill(0.f);
|
||||
n_lower.im.fill(0.f);
|
||||
n_upper.re.fill(0.f);
|
||||
n_upper.im.fill(0.f);
|
||||
|
||||
// Ensure instantaneous updata to nonzero noise.
|
||||
cng.Compute(aec_state, N2, &n_lower, &n_upper);
|
||||
EXPECT_LT(0.f, Power(n_lower));
|
||||
EXPECT_LT(0.f, Power(n_upper));
|
||||
|
||||
for (int k = 0; k < 10000; ++k) {
|
||||
cng.Compute(aec_state, N2, &n_lower, &n_upper);
|
||||
}
|
||||
EXPECT_NEAR(N2[0], Power(n_lower), N2[0] / 10.f);
|
||||
EXPECT_NEAR(N2[0], Power(n_upper), N2[0] / 10.f);
|
||||
}
|
||||
|
||||
} // namespace aec3
|
||||
} // namespace webrtc
|
||||
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/decimator_by_4.h"
|
||||
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
// [B,A] = butter(2,1500/16000) which are the same as [B,A] =
|
||||
// butter(2,750/8000).
|
||||
const CascadedBiQuadFilter::BiQuadCoefficients kLowPassFilterCoefficients = {
|
||||
{0.0179f, 0.0357f, 0.0179f},
|
||||
{-1.5879f, 0.6594f}};
|
||||
|
||||
} // namespace
|
||||
|
||||
DecimatorBy4::DecimatorBy4()
|
||||
: low_pass_filter_(kLowPassFilterCoefficients, 3) {}
|
||||
|
||||
void DecimatorBy4::Decimate(rtc::ArrayView<const float> in,
|
||||
rtc::ArrayView<float> out) {
|
||||
RTC_DCHECK_EQ(kBlockSize, in.size());
|
||||
RTC_DCHECK_EQ(kSubBlockSize, out.size());
|
||||
std::array<float, kBlockSize> x;
|
||||
|
||||
// Limit the frequency content of the signal to avoid aliasing.
|
||||
low_pass_filter_.Process(in, x);
|
||||
|
||||
// Downsample the signal.
|
||||
for (size_t j = 0, k = 0; j < out.size(); ++j, k += 4) {
|
||||
RTC_DCHECK_GT(kBlockSize, k);
|
||||
out[j] = x[k];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_BY_4_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_BY_4_H_
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Provides functionality for decimating a signal by 4.
|
||||
class DecimatorBy4 {
|
||||
public:
|
||||
DecimatorBy4();
|
||||
|
||||
// Downsamples the signal.
|
||||
void Decimate(rtc::ArrayView<const float> in, rtc::ArrayView<float> out);
|
||||
|
||||
private:
|
||||
CascadedBiQuadFilter low_pass_filter_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(DecimatorBy4);
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_BY_4_H_
|
||||
@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/decimator_by_4.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string ProduceDebugText(int sample_rate_hz) {
|
||||
std::ostringstream ss;
|
||||
ss << "Sample rate: " << sample_rate_hz;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
constexpr float kPi = 3.141592f;
|
||||
constexpr size_t kNumStartupBlocks = 50;
|
||||
constexpr size_t kNumBlocks = 1000;
|
||||
|
||||
void ProduceDecimatedSinusoidalOutputPower(int sample_rate_hz,
|
||||
float sinusoidal_frequency_hz,
|
||||
float* input_power,
|
||||
float* output_power) {
|
||||
float input[kBlockSize * kNumBlocks];
|
||||
|
||||
// Produce a sinusoid of the specified frequency.
|
||||
for (size_t k = 0; k < kBlockSize * kNumBlocks; ++k) {
|
||||
input[k] =
|
||||
32767.f * sin(2.f * kPi * sinusoidal_frequency_hz * k / sample_rate_hz);
|
||||
}
|
||||
|
||||
DecimatorBy4 decimator;
|
||||
std::array<float, kSubBlockSize * kNumBlocks> output;
|
||||
|
||||
for (size_t k = 0; k < kNumBlocks; ++k) {
|
||||
std::array<float, kSubBlockSize> sub_block;
|
||||
|
||||
decimator.Decimate(
|
||||
rtc::ArrayView<const float>(&input[k * kBlockSize], kBlockSize),
|
||||
sub_block);
|
||||
|
||||
std::copy(sub_block.begin(), sub_block.end(),
|
||||
output.begin() + k * kSubBlockSize);
|
||||
}
|
||||
|
||||
ASSERT_GT(kNumBlocks, kNumStartupBlocks);
|
||||
rtc::ArrayView<const float> input_to_evaluate(
|
||||
&input[kNumStartupBlocks * kBlockSize],
|
||||
(kNumBlocks - kNumStartupBlocks) * kBlockSize);
|
||||
rtc::ArrayView<const float> output_to_evaluate(
|
||||
&output[kNumStartupBlocks * kSubBlockSize],
|
||||
(kNumBlocks - kNumStartupBlocks) * kSubBlockSize);
|
||||
*input_power =
|
||||
std::inner_product(input_to_evaluate.begin(), input_to_evaluate.end(),
|
||||
input_to_evaluate.begin(), 0.f) /
|
||||
input_to_evaluate.size();
|
||||
*output_power =
|
||||
std::inner_product(output_to_evaluate.begin(), output_to_evaluate.end(),
|
||||
output_to_evaluate.begin(), 0.f) /
|
||||
output_to_evaluate.size();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies that there is little aliasing from upper frequencies in the
|
||||
// downsampling.
|
||||
TEST(DecimatorBy4, NoLeakageFromUpperFrequencies) {
|
||||
float input_power;
|
||||
float output_power;
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
ProduceDebugText(rate);
|
||||
ProduceDecimatedSinusoidalOutputPower(rate, 3.f / 8.f * rate, &input_power,
|
||||
&output_power);
|
||||
EXPECT_GT(0.0001f * input_power, output_power);
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the impact of low-frequency content is small during the
|
||||
// downsampling.
|
||||
TEST(DecimatorBy4, NoImpactOnLowerFrequencies) {
|
||||
float input_power;
|
||||
float output_power;
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
ProduceDebugText(rate);
|
||||
ProduceDecimatedSinusoidalOutputPower(rate, 200.f, &input_power,
|
||||
&output_power);
|
||||
EXPECT_LT(0.7f * input_power, output_power);
|
||||
}
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
// Verifies the check for the input size.
|
||||
TEST(DecimatorBy4, WrongInputSize) {
|
||||
DecimatorBy4 decimator;
|
||||
std::vector<float> x(std::vector<float>(kBlockSize - 1, 0.f));
|
||||
std::array<float, kSubBlockSize> x_downsampled;
|
||||
EXPECT_DEATH(decimator.Decimate(x, x_downsampled), "");
|
||||
}
|
||||
|
||||
// Verifies the check for non-null output parameter.
|
||||
TEST(DecimatorBy4, NullOutput) {
|
||||
DecimatorBy4 decimator;
|
||||
std::vector<float> x(std::vector<float>(kBlockSize, 0.f));
|
||||
EXPECT_DEATH(decimator.Decimate(x, nullptr), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,19 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/downsampled_render_buffer.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
DownsampledRenderBuffer::DownsampledRenderBuffer() = default;
|
||||
|
||||
DownsampledRenderBuffer::~DownsampledRenderBuffer() = default;
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_DOWNSAMPLED_RENDER_BUFFER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_DOWNSAMPLED_RENDER_BUFFER_H_
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Holds the circular buffer of the downsampled render data.
|
||||
struct DownsampledRenderBuffer {
|
||||
DownsampledRenderBuffer();
|
||||
~DownsampledRenderBuffer();
|
||||
std::array<float, kDownsampledRenderBufferSize> buffer = {};
|
||||
int position = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_DOWNSAMPLED_RENDER_BUFFER_H_
|
||||
@ -1,362 +0,0 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/audio_processing/aec3/echo_canceller3.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/rtc_base/atomicops.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
enum class EchoCanceller3ApiCall { kCapture, kRender };
|
||||
|
||||
bool DetectSaturation(rtc::ArrayView<const float> y) {
|
||||
for (auto y_k : y) {
|
||||
if (y_k >= 32700.0f || y_k <= -32700.0f) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FillSubFrameView(AudioBuffer* frame,
|
||||
size_t sub_frame_index,
|
||||
std::vector<rtc::ArrayView<float>>* sub_frame_view) {
|
||||
RTC_DCHECK_GE(1, sub_frame_index);
|
||||
RTC_DCHECK_LE(0, sub_frame_index);
|
||||
RTC_DCHECK_EQ(frame->num_bands(), sub_frame_view->size());
|
||||
for (size_t k = 0; k < sub_frame_view->size(); ++k) {
|
||||
(*sub_frame_view)[k] = rtc::ArrayView<float>(
|
||||
&frame->split_bands_f(0)[k][sub_frame_index * kSubFrameLength],
|
||||
kSubFrameLength);
|
||||
}
|
||||
}
|
||||
|
||||
void FillSubFrameView(std::vector<std::vector<float>>* frame,
|
||||
size_t sub_frame_index,
|
||||
std::vector<rtc::ArrayView<float>>* sub_frame_view) {
|
||||
RTC_DCHECK_GE(1, sub_frame_index);
|
||||
RTC_DCHECK_EQ(frame->size(), sub_frame_view->size());
|
||||
for (size_t k = 0; k < frame->size(); ++k) {
|
||||
(*sub_frame_view)[k] = rtc::ArrayView<float>(
|
||||
&(*frame)[k][sub_frame_index * kSubFrameLength], kSubFrameLength);
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessCaptureFrameContent(
|
||||
AudioBuffer* capture,
|
||||
bool level_change,
|
||||
bool saturated_microphone_signal,
|
||||
size_t sub_frame_index,
|
||||
FrameBlocker* capture_blocker,
|
||||
BlockFramer* output_framer,
|
||||
BlockProcessor* block_processor,
|
||||
std::vector<std::vector<float>>* block,
|
||||
std::vector<rtc::ArrayView<float>>* sub_frame_view) {
|
||||
FillSubFrameView(capture, sub_frame_index, sub_frame_view);
|
||||
capture_blocker->InsertSubFrameAndExtractBlock(*sub_frame_view, block);
|
||||
block_processor->ProcessCapture(level_change, saturated_microphone_signal,
|
||||
block);
|
||||
output_framer->InsertBlockAndExtractSubFrame(*block, sub_frame_view);
|
||||
}
|
||||
|
||||
void ProcessRemainingCaptureFrameContent(
|
||||
bool level_change,
|
||||
bool saturated_microphone_signal,
|
||||
FrameBlocker* capture_blocker,
|
||||
BlockFramer* output_framer,
|
||||
BlockProcessor* block_processor,
|
||||
std::vector<std::vector<float>>* block) {
|
||||
if (!capture_blocker->IsBlockAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
capture_blocker->ExtractBlock(block);
|
||||
block_processor->ProcessCapture(level_change, saturated_microphone_signal,
|
||||
block);
|
||||
output_framer->InsertBlock(*block);
|
||||
}
|
||||
|
||||
void BufferRenderFrameContent(
|
||||
std::vector<std::vector<float>>* render_frame,
|
||||
size_t sub_frame_index,
|
||||
FrameBlocker* render_blocker,
|
||||
BlockProcessor* block_processor,
|
||||
std::vector<std::vector<float>>* block,
|
||||
std::vector<rtc::ArrayView<float>>* sub_frame_view) {
|
||||
FillSubFrameView(render_frame, sub_frame_index, sub_frame_view);
|
||||
render_blocker->InsertSubFrameAndExtractBlock(*sub_frame_view, block);
|
||||
block_processor->BufferRender(*block);
|
||||
}
|
||||
|
||||
void BufferRemainingRenderFrameContent(FrameBlocker* render_blocker,
|
||||
BlockProcessor* block_processor,
|
||||
std::vector<std::vector<float>>* block) {
|
||||
if (!render_blocker->IsBlockAvailable()) {
|
||||
return;
|
||||
}
|
||||
render_blocker->ExtractBlock(block);
|
||||
block_processor->BufferRender(*block);
|
||||
}
|
||||
|
||||
void CopyBufferIntoFrame(AudioBuffer* buffer,
|
||||
size_t num_bands,
|
||||
size_t frame_length,
|
||||
std::vector<std::vector<float>>* frame) {
|
||||
RTC_DCHECK_EQ(num_bands, frame->size());
|
||||
RTC_DCHECK_EQ(frame_length, (*frame)[0].size());
|
||||
for (size_t k = 0; k < num_bands; ++k) {
|
||||
rtc::ArrayView<float> buffer_view(&buffer->split_bands_f(0)[k][0],
|
||||
frame_length);
|
||||
std::copy(buffer_view.begin(), buffer_view.end(), (*frame)[k].begin());
|
||||
}
|
||||
}
|
||||
|
||||
// [B,A] = butter(2,100/4000,'high')
|
||||
const CascadedBiQuadFilter::BiQuadCoefficients
|
||||
kHighPassFilterCoefficients_8kHz = {{0.94598f, -1.89195f, 0.94598f},
|
||||
{-1.88903f, 0.89487f}};
|
||||
const int kNumberOfHighPassBiQuads_8kHz = 1;
|
||||
|
||||
// [B,A] = butter(2,100/8000,'high')
|
||||
const CascadedBiQuadFilter::BiQuadCoefficients
|
||||
kHighPassFilterCoefficients_16kHz = {{0.97261f, -1.94523f, 0.97261f},
|
||||
{-1.94448f, 0.94598f}};
|
||||
const int kNumberOfHighPassBiQuads_16kHz = 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
class EchoCanceller3::RenderWriter {
|
||||
public:
|
||||
RenderWriter(ApmDataDumper* data_dumper,
|
||||
SwapQueue<std::vector<std::vector<float>>,
|
||||
Aec3RenderQueueItemVerifier>* render_transfer_queue,
|
||||
std::unique_ptr<CascadedBiQuadFilter> render_highpass_filter,
|
||||
int sample_rate_hz,
|
||||
int frame_length,
|
||||
int num_bands);
|
||||
~RenderWriter();
|
||||
void Insert(AudioBuffer* input);
|
||||
|
||||
private:
|
||||
ApmDataDumper* data_dumper_;
|
||||
const int sample_rate_hz_;
|
||||
const size_t frame_length_;
|
||||
const int num_bands_;
|
||||
std::unique_ptr<CascadedBiQuadFilter> render_highpass_filter_;
|
||||
std::vector<std::vector<float>> render_queue_input_frame_;
|
||||
SwapQueue<std::vector<std::vector<float>>, Aec3RenderQueueItemVerifier>*
|
||||
render_transfer_queue_;
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderWriter);
|
||||
};
|
||||
|
||||
EchoCanceller3::RenderWriter::RenderWriter(
|
||||
ApmDataDumper* data_dumper,
|
||||
SwapQueue<std::vector<std::vector<float>>, Aec3RenderQueueItemVerifier>*
|
||||
render_transfer_queue,
|
||||
std::unique_ptr<CascadedBiQuadFilter> render_highpass_filter,
|
||||
int sample_rate_hz,
|
||||
int frame_length,
|
||||
int num_bands)
|
||||
: data_dumper_(data_dumper),
|
||||
sample_rate_hz_(sample_rate_hz),
|
||||
frame_length_(frame_length),
|
||||
num_bands_(num_bands),
|
||||
render_highpass_filter_(std::move(render_highpass_filter)),
|
||||
render_queue_input_frame_(num_bands_,
|
||||
std::vector<float>(frame_length_, 0.f)),
|
||||
render_transfer_queue_(render_transfer_queue) {
|
||||
RTC_DCHECK(data_dumper);
|
||||
}
|
||||
|
||||
EchoCanceller3::RenderWriter::~RenderWriter() = default;
|
||||
|
||||
void EchoCanceller3::RenderWriter::Insert(AudioBuffer* input) {
|
||||
RTC_DCHECK_EQ(1, input->num_channels());
|
||||
RTC_DCHECK_EQ(frame_length_, input->num_frames_per_band());
|
||||
data_dumper_->DumpWav("aec3_render_input", frame_length_,
|
||||
&input->split_bands_f(0)[0][0],
|
||||
LowestBandRate(sample_rate_hz_), 1);
|
||||
|
||||
CopyBufferIntoFrame(input, num_bands_, frame_length_,
|
||||
&render_queue_input_frame_);
|
||||
|
||||
if (render_highpass_filter_) {
|
||||
render_highpass_filter_->Process(render_queue_input_frame_[0]);
|
||||
}
|
||||
|
||||
static_cast<void>(render_transfer_queue_->Insert(&render_queue_input_frame_));
|
||||
}
|
||||
|
||||
int EchoCanceller3::instance_count_ = 0;
|
||||
|
||||
EchoCanceller3::EchoCanceller3(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz,
|
||||
bool use_highpass_filter)
|
||||
: EchoCanceller3(sample_rate_hz,
|
||||
use_highpass_filter,
|
||||
std::unique_ptr<BlockProcessor>(
|
||||
BlockProcessor::Create(config, sample_rate_hz))) {}
|
||||
EchoCanceller3::EchoCanceller3(int sample_rate_hz,
|
||||
bool use_highpass_filter,
|
||||
std::unique_ptr<BlockProcessor> block_processor)
|
||||
: data_dumper_(
|
||||
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
||||
sample_rate_hz_(sample_rate_hz),
|
||||
num_bands_(NumBandsForRate(sample_rate_hz_)),
|
||||
frame_length_(rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100)),
|
||||
output_framer_(num_bands_),
|
||||
capture_blocker_(num_bands_),
|
||||
render_blocker_(num_bands_),
|
||||
render_transfer_queue_(
|
||||
kRenderTransferQueueSize,
|
||||
std::vector<std::vector<float>>(
|
||||
num_bands_,
|
||||
std::vector<float>(frame_length_, 0.f)),
|
||||
Aec3RenderQueueItemVerifier(num_bands_, frame_length_)),
|
||||
block_processor_(std::move(block_processor)),
|
||||
render_queue_output_frame_(num_bands_,
|
||||
std::vector<float>(frame_length_, 0.f)),
|
||||
block_(num_bands_, std::vector<float>(kBlockSize, 0.f)),
|
||||
sub_frame_view_(num_bands_) {
|
||||
RTC_DCHECK(ValidFullBandRate(sample_rate_hz_));
|
||||
|
||||
std::unique_ptr<CascadedBiQuadFilter> render_highpass_filter;
|
||||
if (use_highpass_filter) {
|
||||
render_highpass_filter.reset(new CascadedBiQuadFilter(
|
||||
sample_rate_hz_ == 8000 ? kHighPassFilterCoefficients_8kHz
|
||||
: kHighPassFilterCoefficients_16kHz,
|
||||
sample_rate_hz_ == 8000 ? kNumberOfHighPassBiQuads_8kHz
|
||||
: kNumberOfHighPassBiQuads_16kHz));
|
||||
capture_highpass_filter_.reset(new CascadedBiQuadFilter(
|
||||
sample_rate_hz_ == 8000 ? kHighPassFilterCoefficients_8kHz
|
||||
: kHighPassFilterCoefficients_16kHz,
|
||||
sample_rate_hz_ == 8000 ? kNumberOfHighPassBiQuads_8kHz
|
||||
: kNumberOfHighPassBiQuads_16kHz));
|
||||
}
|
||||
|
||||
render_writer_.reset(
|
||||
new RenderWriter(data_dumper_.get(), &render_transfer_queue_,
|
||||
std::move(render_highpass_filter), sample_rate_hz_,
|
||||
frame_length_, num_bands_));
|
||||
|
||||
RTC_DCHECK_EQ(num_bands_, std::max(sample_rate_hz_, 16000) / 16000);
|
||||
RTC_DCHECK_GE(kMaxNumBands, num_bands_);
|
||||
}
|
||||
|
||||
EchoCanceller3::~EchoCanceller3() = default;
|
||||
|
||||
void EchoCanceller3::AnalyzeRender(AudioBuffer* render) {
|
||||
RTC_DCHECK_RUNS_SERIALIZED(&render_race_checker_);
|
||||
RTC_DCHECK(render);
|
||||
data_dumper_->DumpRaw("aec3_call_order",
|
||||
static_cast<int>(EchoCanceller3ApiCall::kRender));
|
||||
|
||||
return render_writer_->Insert(render);
|
||||
}
|
||||
|
||||
void EchoCanceller3::AnalyzeCapture(AudioBuffer* capture) {
|
||||
RTC_DCHECK_RUNS_SERIALIZED(&capture_race_checker_);
|
||||
RTC_DCHECK(capture);
|
||||
data_dumper_->DumpWav("aec3_capture_analyze_input", capture->num_frames(),
|
||||
capture->channels_f()[0], sample_rate_hz_, 1);
|
||||
|
||||
saturated_microphone_signal_ = false;
|
||||
for (size_t k = 0; k < capture->num_channels(); ++k) {
|
||||
saturated_microphone_signal_ |=
|
||||
DetectSaturation(rtc::ArrayView<const float>(capture->channels_f()[k],
|
||||
capture->num_frames()));
|
||||
if (saturated_microphone_signal_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EchoCanceller3::ProcessCapture(AudioBuffer* capture, bool level_change) {
|
||||
RTC_DCHECK_RUNS_SERIALIZED(&capture_race_checker_);
|
||||
RTC_DCHECK(capture);
|
||||
RTC_DCHECK_EQ(1u, capture->num_channels());
|
||||
RTC_DCHECK_EQ(num_bands_, capture->num_bands());
|
||||
RTC_DCHECK_EQ(frame_length_, capture->num_frames_per_band());
|
||||
data_dumper_->DumpRaw("aec3_call_order",
|
||||
static_cast<int>(EchoCanceller3ApiCall::kCapture));
|
||||
|
||||
rtc::ArrayView<float> capture_lower_band =
|
||||
rtc::ArrayView<float>(&capture->split_bands_f(0)[0][0], frame_length_);
|
||||
|
||||
data_dumper_->DumpWav("aec3_capture_input", capture_lower_band,
|
||||
LowestBandRate(sample_rate_hz_), 1);
|
||||
|
||||
EmptyRenderQueue();
|
||||
|
||||
if (capture_highpass_filter_) {
|
||||
capture_highpass_filter_->Process(capture_lower_band);
|
||||
}
|
||||
|
||||
ProcessCaptureFrameContent(
|
||||
capture, level_change, saturated_microphone_signal_, 0, &capture_blocker_,
|
||||
&output_framer_, block_processor_.get(), &block_, &sub_frame_view_);
|
||||
|
||||
if (sample_rate_hz_ != 8000) {
|
||||
ProcessCaptureFrameContent(
|
||||
capture, level_change, saturated_microphone_signal_, 1,
|
||||
&capture_blocker_, &output_framer_, block_processor_.get(), &block_,
|
||||
&sub_frame_view_);
|
||||
}
|
||||
|
||||
ProcessRemainingCaptureFrameContent(
|
||||
level_change, saturated_microphone_signal_, &capture_blocker_,
|
||||
&output_framer_, block_processor_.get(), &block_);
|
||||
|
||||
data_dumper_->DumpWav("aec3_capture_output", frame_length_,
|
||||
&capture->split_bands_f(0)[0][0],
|
||||
LowestBandRate(sample_rate_hz_), 1);
|
||||
}
|
||||
|
||||
std::string EchoCanceller3::ToString(
|
||||
const AudioProcessing::Config::EchoCanceller3& config) {
|
||||
std::stringstream ss;
|
||||
ss << "{"
|
||||
<< "enabled: " << (config.enabled ? "true" : "false") << "}";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool EchoCanceller3::Validate(
|
||||
const AudioProcessing::Config::EchoCanceller3& config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void EchoCanceller3::EmptyRenderQueue() {
|
||||
RTC_DCHECK_RUNS_SERIALIZED(&capture_race_checker_);
|
||||
bool frame_to_buffer =
|
||||
render_transfer_queue_.Remove(&render_queue_output_frame_);
|
||||
while (frame_to_buffer) {
|
||||
BufferRenderFrameContent(&render_queue_output_frame_, 0, &render_blocker_,
|
||||
block_processor_.get(), &block_, &sub_frame_view_);
|
||||
|
||||
if (sample_rate_hz_ != 8000) {
|
||||
BufferRenderFrameContent(&render_queue_output_frame_, 1, &render_blocker_,
|
||||
block_processor_.get(), &block_,
|
||||
&sub_frame_view_);
|
||||
}
|
||||
|
||||
BufferRemainingRenderFrameContent(&render_blocker_, block_processor_.get(),
|
||||
&block_);
|
||||
|
||||
frame_to_buffer =
|
||||
render_transfer_queue_.Remove(&render_queue_output_frame_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,138 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_CANCELLER3_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_CANCELLER3_H_
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/block_framer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/block_processor.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/frame_blocker.h"
|
||||
#include "webrtc/modules/audio_processing/audio_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
#include "webrtc/rtc_base/race_checker.h"
|
||||
#include "webrtc/rtc_base/swap_queue.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Functor for verifying the invariance of the frames being put into the render
|
||||
// queue.
|
||||
class Aec3RenderQueueItemVerifier {
|
||||
public:
|
||||
explicit Aec3RenderQueueItemVerifier(size_t num_bands, size_t frame_length)
|
||||
: num_bands_(num_bands), frame_length_(frame_length) {}
|
||||
|
||||
bool operator()(const std::vector<std::vector<float>>& v) const {
|
||||
if (v.size() != num_bands_) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& v_k : v) {
|
||||
if (v_k.size() != frame_length_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const size_t num_bands_;
|
||||
const size_t frame_length_;
|
||||
};
|
||||
|
||||
// Main class for the echo canceller3.
|
||||
// It does 4 things:
|
||||
// -Receives 10 ms frames of band-split audio.
|
||||
// -Optionally applies an anti-hum (high-pass) filter on the
|
||||
// received signals.
|
||||
// -Provides the lower level echo canceller functionality with
|
||||
// blocks of 64 samples of audio data.
|
||||
// -Partially handles the jitter in the render and capture API
|
||||
// call sequence.
|
||||
//
|
||||
// The class is supposed to be used in a non-concurrent manner apart from the
|
||||
// AnalyzeRender call which can be called concurrently with the other methods.
|
||||
class EchoCanceller3 {
|
||||
public:
|
||||
// Normal c-tor to use.
|
||||
EchoCanceller3(const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz,
|
||||
bool use_highpass_filter);
|
||||
// Testing c-tor that is used only for testing purposes.
|
||||
EchoCanceller3(int sample_rate_hz,
|
||||
bool use_highpass_filter,
|
||||
std::unique_ptr<BlockProcessor> block_processor);
|
||||
~EchoCanceller3();
|
||||
// Analyzes and stores an internal copy of the split-band domain render
|
||||
// signal.
|
||||
void AnalyzeRender(AudioBuffer* farend);
|
||||
// Analyzes the full-band domain capture signal to detect signal saturation.
|
||||
void AnalyzeCapture(AudioBuffer* capture);
|
||||
// Processes the split-band domain capture signal in order to remove any echo
|
||||
// present in the signal.
|
||||
void ProcessCapture(AudioBuffer* capture, bool level_change);
|
||||
|
||||
// Signals whether an external detector has detected echo leakage from the
|
||||
// echo canceller.
|
||||
// Note that in the case echo leakage has been flagged, it should be unflagged
|
||||
// once it is no longer occurring.
|
||||
void UpdateEchoLeakageStatus(bool leakage_detected) {
|
||||
RTC_DCHECK_RUNS_SERIALIZED(&capture_race_checker_);
|
||||
block_processor_->UpdateEchoLeakageStatus(leakage_detected);
|
||||
}
|
||||
|
||||
// Validates a config.
|
||||
static bool Validate(const AudioProcessing::Config::EchoCanceller3& config);
|
||||
// Dumps a config to a string.
|
||||
static std::string ToString(
|
||||
const AudioProcessing::Config::EchoCanceller3& config);
|
||||
|
||||
private:
|
||||
class RenderWriter;
|
||||
|
||||
// Empties the render SwapQueue.
|
||||
void EmptyRenderQueue();
|
||||
|
||||
rtc::RaceChecker capture_race_checker_;
|
||||
rtc::RaceChecker render_race_checker_;
|
||||
|
||||
// State that is accessed by the AnalyzeRender call.
|
||||
std::unique_ptr<RenderWriter> render_writer_
|
||||
RTC_GUARDED_BY(render_race_checker_);
|
||||
|
||||
// State that may be accessed by the capture thread.
|
||||
static int instance_count_;
|
||||
std::unique_ptr<ApmDataDumper> data_dumper_;
|
||||
const int sample_rate_hz_;
|
||||
const int num_bands_;
|
||||
const size_t frame_length_;
|
||||
BlockFramer output_framer_ RTC_GUARDED_BY(capture_race_checker_);
|
||||
FrameBlocker capture_blocker_ RTC_GUARDED_BY(capture_race_checker_);
|
||||
FrameBlocker render_blocker_ RTC_GUARDED_BY(capture_race_checker_);
|
||||
SwapQueue<std::vector<std::vector<float>>, Aec3RenderQueueItemVerifier>
|
||||
render_transfer_queue_;
|
||||
std::unique_ptr<BlockProcessor> block_processor_
|
||||
RTC_GUARDED_BY(capture_race_checker_);
|
||||
std::vector<std::vector<float>> render_queue_output_frame_
|
||||
RTC_GUARDED_BY(capture_race_checker_);
|
||||
std::unique_ptr<CascadedBiQuadFilter> capture_highpass_filter_
|
||||
RTC_GUARDED_BY(capture_race_checker_);
|
||||
bool saturated_microphone_signal_ RTC_GUARDED_BY(capture_race_checker_) =
|
||||
false;
|
||||
std::vector<std::vector<float>> block_ RTC_GUARDED_BY(capture_race_checker_);
|
||||
std::vector<rtc::ArrayView<float>> sub_frame_view_
|
||||
RTC_GUARDED_BY(capture_race_checker_);
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(EchoCanceller3);
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_CANCELLER3_H_
|
||||
@ -1,754 +0,0 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/audio_processing/aec3/echo_canceller3.h"
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/block_processor.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/frame_blocker.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/mock/mock_block_processor.h"
|
||||
#include "webrtc/modules/audio_processing/audio_buffer.h"
|
||||
#include "webrtc/test/gmock.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
using testing::StrictMock;
|
||||
using testing::_;
|
||||
|
||||
// Populates the frame with linearly increasing sample values for each band,
|
||||
// with a band-specific offset, in order to allow simple bitexactness
|
||||
// verification for each band.
|
||||
void PopulateInputFrame(size_t frame_length,
|
||||
size_t num_bands,
|
||||
size_t frame_index,
|
||||
float* const* frame,
|
||||
int offset) {
|
||||
for (size_t k = 0; k < num_bands; ++k) {
|
||||
for (size_t i = 0; i < frame_length; ++i) {
|
||||
float value = static_cast<int>(frame_index * frame_length + i) + offset;
|
||||
frame[k][i] = (value > 0 ? 5000 * k + value : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populates the frame with linearly increasing sample values.
|
||||
void PopulateInputFrame(size_t frame_length,
|
||||
size_t frame_index,
|
||||
float* frame,
|
||||
int offset) {
|
||||
for (size_t i = 0; i < frame_length; ++i) {
|
||||
float value = static_cast<int>(frame_index * frame_length + i) + offset;
|
||||
frame[i] = std::max(value, 0.f);
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies the that samples in the output frame are identical to the samples
|
||||
// that were produced for the input frame, with an offset in order to compensate
|
||||
// for buffering delays.
|
||||
bool VerifyOutputFrameBitexactness(size_t frame_length,
|
||||
size_t num_bands,
|
||||
size_t frame_index,
|
||||
const float* const* frame,
|
||||
int offset) {
|
||||
float reference_frame_data[kMaxNumBands][2 * kSubFrameLength];
|
||||
float* reference_frame[kMaxNumBands];
|
||||
for (size_t k = 0; k < num_bands; ++k) {
|
||||
reference_frame[k] = &reference_frame_data[k][0];
|
||||
}
|
||||
|
||||
PopulateInputFrame(frame_length, num_bands, frame_index, reference_frame,
|
||||
offset);
|
||||
for (size_t k = 0; k < num_bands; ++k) {
|
||||
for (size_t i = 0; i < frame_length; ++i) {
|
||||
if (reference_frame[k][i] != frame[k][i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Class for testing that the capture data is properly received by the block
|
||||
// processor and that the processor data is properly passed to the
|
||||
// EchoCanceller3 output.
|
||||
class CaptureTransportVerificationProcessor : public BlockProcessor {
|
||||
public:
|
||||
explicit CaptureTransportVerificationProcessor(size_t num_bands) {}
|
||||
~CaptureTransportVerificationProcessor() override = default;
|
||||
|
||||
void ProcessCapture(bool level_change,
|
||||
bool saturated_microphone_signal,
|
||||
std::vector<std::vector<float>>* capture_block) override {
|
||||
}
|
||||
|
||||
void BufferRender(const std::vector<std::vector<float>>& block) override {}
|
||||
|
||||
void UpdateEchoLeakageStatus(bool leakage_detected) override {}
|
||||
|
||||
private:
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTransportVerificationProcessor);
|
||||
};
|
||||
|
||||
// Class for testing that the render data is properly received by the block
|
||||
// processor.
|
||||
class RenderTransportVerificationProcessor : public BlockProcessor {
|
||||
public:
|
||||
explicit RenderTransportVerificationProcessor(size_t num_bands) {}
|
||||
~RenderTransportVerificationProcessor() override = default;
|
||||
|
||||
void ProcessCapture(bool level_change,
|
||||
bool saturated_microphone_signal,
|
||||
std::vector<std::vector<float>>* capture_block) override {
|
||||
std::vector<std::vector<float>> render_block =
|
||||
received_render_blocks_.front();
|
||||
received_render_blocks_.pop_front();
|
||||
capture_block->swap(render_block);
|
||||
}
|
||||
|
||||
void BufferRender(const std::vector<std::vector<float>>& block) override {
|
||||
received_render_blocks_.push_back(block);
|
||||
}
|
||||
|
||||
void UpdateEchoLeakageStatus(bool leakage_detected) override {}
|
||||
|
||||
private:
|
||||
std::deque<std::vector<std::vector<float>>> received_render_blocks_;
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderTransportVerificationProcessor);
|
||||
};
|
||||
|
||||
class EchoCanceller3Tester {
|
||||
public:
|
||||
explicit EchoCanceller3Tester(int sample_rate_hz)
|
||||
: sample_rate_hz_(sample_rate_hz),
|
||||
num_bands_(NumBandsForRate(sample_rate_hz_)),
|
||||
frame_length_(sample_rate_hz_ == 8000 ? 80 : 160),
|
||||
fullband_frame_length_(rtc::CheckedDivExact(sample_rate_hz_, 100)),
|
||||
capture_buffer_(fullband_frame_length_,
|
||||
1,
|
||||
fullband_frame_length_,
|
||||
1,
|
||||
fullband_frame_length_),
|
||||
render_buffer_(fullband_frame_length_,
|
||||
1,
|
||||
fullband_frame_length_,
|
||||
1,
|
||||
fullband_frame_length_) {}
|
||||
|
||||
// Verifies that the capture data is properly received by the block processor
|
||||
// and that the processor data is properly passed to the EchoCanceller3
|
||||
// output.
|
||||
void RunCaptureTransportVerificationTest() {
|
||||
EchoCanceller3 aec3(
|
||||
sample_rate_hz_, false,
|
||||
std::unique_ptr<BlockProcessor>(
|
||||
new CaptureTransportVerificationProcessor(num_bands_)));
|
||||
|
||||
for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
|
||||
++frame_index) {
|
||||
aec3.AnalyzeCapture(&capture_buffer_);
|
||||
OptionalBandSplit();
|
||||
PopulateInputFrame(frame_length_, num_bands_, frame_index,
|
||||
&capture_buffer_.split_bands_f(0)[0], 0);
|
||||
PopulateInputFrame(frame_length_, frame_index,
|
||||
&render_buffer_.channels_f()[0][0], 0);
|
||||
|
||||
aec3.AnalyzeRender(&render_buffer_);
|
||||
aec3.ProcessCapture(&capture_buffer_, false);
|
||||
EXPECT_TRUE(VerifyOutputFrameBitexactness(
|
||||
frame_length_, num_bands_, frame_index,
|
||||
&capture_buffer_.split_bands_f(0)[0], -64));
|
||||
}
|
||||
}
|
||||
|
||||
// Test method for testing that the render data is properly received by the
|
||||
// block processor.
|
||||
void RunRenderTransportVerificationTest() {
|
||||
EchoCanceller3 aec3(
|
||||
sample_rate_hz_, false,
|
||||
std::unique_ptr<BlockProcessor>(
|
||||
new RenderTransportVerificationProcessor(num_bands_)));
|
||||
|
||||
for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
|
||||
++frame_index) {
|
||||
aec3.AnalyzeCapture(&capture_buffer_);
|
||||
OptionalBandSplit();
|
||||
PopulateInputFrame(frame_length_, num_bands_, frame_index,
|
||||
&capture_buffer_.split_bands_f(0)[0], 100);
|
||||
PopulateInputFrame(frame_length_, num_bands_, frame_index,
|
||||
&render_buffer_.split_bands_f(0)[0], 0);
|
||||
|
||||
aec3.AnalyzeRender(&render_buffer_);
|
||||
aec3.ProcessCapture(&capture_buffer_, false);
|
||||
EXPECT_TRUE(VerifyOutputFrameBitexactness(
|
||||
frame_length_, num_bands_, frame_index,
|
||||
&capture_buffer_.split_bands_f(0)[0], -64));
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that information about echo path changes are properly propagated
|
||||
// to the block processor.
|
||||
// The cases tested are:
|
||||
// -That no set echo path change flags are received when there is no echo path
|
||||
// change.
|
||||
// -That set echo path change flags are received and continues to be received
|
||||
// as long as echo path changes are flagged.
|
||||
// -That set echo path change flags are no longer received when echo path
|
||||
// change events stop being flagged.
|
||||
enum class EchoPathChangeTestVariant { kNone, kOneSticky, kOneNonSticky };
|
||||
|
||||
void RunEchoPathChangeVerificationTest(
|
||||
EchoPathChangeTestVariant echo_path_change_test_variant) {
|
||||
const size_t num_full_blocks_per_frame =
|
||||
rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100) / kBlockSize;
|
||||
const size_t expected_num_block_to_process =
|
||||
(kNumFramesToProcess *
|
||||
rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100)) /
|
||||
kBlockSize;
|
||||
std::unique_ptr<testing::StrictMock<webrtc::test::MockBlockProcessor>>
|
||||
block_processor_mock(
|
||||
new StrictMock<webrtc::test::MockBlockProcessor>());
|
||||
EXPECT_CALL(*block_processor_mock, BufferRender(_))
|
||||
.Times(expected_num_block_to_process);
|
||||
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(_)).Times(0);
|
||||
|
||||
switch (echo_path_change_test_variant) {
|
||||
case EchoPathChangeTestVariant::kNone:
|
||||
EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _))
|
||||
.Times(expected_num_block_to_process);
|
||||
break;
|
||||
case EchoPathChangeTestVariant::kOneSticky:
|
||||
EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _))
|
||||
.Times(expected_num_block_to_process);
|
||||
break;
|
||||
case EchoPathChangeTestVariant::kOneNonSticky:
|
||||
EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _))
|
||||
.Times(num_full_blocks_per_frame);
|
||||
EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _))
|
||||
.Times(expected_num_block_to_process - num_full_blocks_per_frame);
|
||||
break;
|
||||
}
|
||||
|
||||
EchoCanceller3 aec3(sample_rate_hz_, false,
|
||||
std::move(block_processor_mock));
|
||||
|
||||
for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
|
||||
++frame_index) {
|
||||
bool echo_path_change = false;
|
||||
switch (echo_path_change_test_variant) {
|
||||
case EchoPathChangeTestVariant::kNone:
|
||||
break;
|
||||
case EchoPathChangeTestVariant::kOneSticky:
|
||||
echo_path_change = true;
|
||||
break;
|
||||
case EchoPathChangeTestVariant::kOneNonSticky:
|
||||
if (frame_index == 0) {
|
||||
echo_path_change = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
aec3.AnalyzeCapture(&capture_buffer_);
|
||||
OptionalBandSplit();
|
||||
|
||||
PopulateInputFrame(frame_length_, num_bands_, frame_index,
|
||||
&capture_buffer_.split_bands_f(0)[0], 0);
|
||||
PopulateInputFrame(frame_length_, frame_index,
|
||||
&render_buffer_.channels_f()[0][0], 0);
|
||||
|
||||
aec3.AnalyzeRender(&render_buffer_);
|
||||
aec3.ProcessCapture(&capture_buffer_, echo_path_change);
|
||||
}
|
||||
}
|
||||
|
||||
// Test for verifying that echo leakage information is being properly passed
|
||||
// to the processor.
|
||||
// The cases tested are:
|
||||
// -That no method calls are received when they should not.
|
||||
// -That false values are received each time they are flagged.
|
||||
// -That true values are received each time they are flagged.
|
||||
// -That a false value is received when flagged after a true value has been
|
||||
// flagged.
|
||||
enum class EchoLeakageTestVariant {
|
||||
kNone,
|
||||
kFalseSticky,
|
||||
kTrueSticky,
|
||||
kTrueNonSticky
|
||||
};
|
||||
|
||||
void RunEchoLeakageVerificationTest(
|
||||
EchoLeakageTestVariant leakage_report_variant) {
|
||||
const size_t expected_num_block_to_process =
|
||||
(kNumFramesToProcess *
|
||||
rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100)) /
|
||||
kBlockSize;
|
||||
std::unique_ptr<testing::StrictMock<webrtc::test::MockBlockProcessor>>
|
||||
block_processor_mock(
|
||||
new StrictMock<webrtc::test::MockBlockProcessor>());
|
||||
EXPECT_CALL(*block_processor_mock, BufferRender(_))
|
||||
.Times(expected_num_block_to_process);
|
||||
EXPECT_CALL(*block_processor_mock, ProcessCapture(_, _, _))
|
||||
.Times(expected_num_block_to_process);
|
||||
|
||||
switch (leakage_report_variant) {
|
||||
case EchoLeakageTestVariant::kNone:
|
||||
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(_)).Times(0);
|
||||
break;
|
||||
case EchoLeakageTestVariant::kFalseSticky:
|
||||
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(false))
|
||||
.Times(1);
|
||||
break;
|
||||
case EchoLeakageTestVariant::kTrueSticky:
|
||||
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(true))
|
||||
.Times(1);
|
||||
break;
|
||||
case EchoLeakageTestVariant::kTrueNonSticky: {
|
||||
testing::InSequence s;
|
||||
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(true))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(false))
|
||||
.Times(kNumFramesToProcess - 1);
|
||||
} break;
|
||||
}
|
||||
|
||||
EchoCanceller3 aec3(sample_rate_hz_, false,
|
||||
std::move(block_processor_mock));
|
||||
|
||||
for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
|
||||
++frame_index) {
|
||||
switch (leakage_report_variant) {
|
||||
case EchoLeakageTestVariant::kNone:
|
||||
break;
|
||||
case EchoLeakageTestVariant::kFalseSticky:
|
||||
if (frame_index == 0) {
|
||||
aec3.UpdateEchoLeakageStatus(false);
|
||||
}
|
||||
break;
|
||||
case EchoLeakageTestVariant::kTrueSticky:
|
||||
if (frame_index == 0) {
|
||||
aec3.UpdateEchoLeakageStatus(true);
|
||||
}
|
||||
break;
|
||||
case EchoLeakageTestVariant::kTrueNonSticky:
|
||||
if (frame_index == 0) {
|
||||
aec3.UpdateEchoLeakageStatus(true);
|
||||
} else {
|
||||
aec3.UpdateEchoLeakageStatus(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
aec3.AnalyzeCapture(&capture_buffer_);
|
||||
OptionalBandSplit();
|
||||
|
||||
PopulateInputFrame(frame_length_, num_bands_, frame_index,
|
||||
&capture_buffer_.split_bands_f(0)[0], 0);
|
||||
PopulateInputFrame(frame_length_, frame_index,
|
||||
&render_buffer_.channels_f()[0][0], 0);
|
||||
|
||||
aec3.AnalyzeRender(&render_buffer_);
|
||||
aec3.ProcessCapture(&capture_buffer_, false);
|
||||
}
|
||||
}
|
||||
|
||||
// This verifies that saturation information is properly passed to the
|
||||
// BlockProcessor.
|
||||
// The cases tested are:
|
||||
// -That no saturation event is passed to the processor if there is no
|
||||
// saturation.
|
||||
// -That one frame with one negative saturated sample value is reported to be
|
||||
// saturated and that following non-saturated frames are properly reported as
|
||||
// not being saturated.
|
||||
// -That one frame with one positive saturated sample value is reported to be
|
||||
// saturated and that following non-saturated frames are properly reported as
|
||||
// not being saturated.
|
||||
enum class SaturationTestVariant { kNone, kOneNegative, kOnePositive };
|
||||
|
||||
void RunCaptureSaturationVerificationTest(
|
||||
SaturationTestVariant saturation_variant) {
|
||||
const size_t num_full_blocks_per_frame =
|
||||
rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100) / kBlockSize;
|
||||
const size_t expected_num_block_to_process =
|
||||
(kNumFramesToProcess *
|
||||
rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100)) /
|
||||
kBlockSize;
|
||||
std::unique_ptr<testing::StrictMock<webrtc::test::MockBlockProcessor>>
|
||||
block_processor_mock(
|
||||
new StrictMock<webrtc::test::MockBlockProcessor>());
|
||||
EXPECT_CALL(*block_processor_mock, BufferRender(_))
|
||||
.Times(expected_num_block_to_process);
|
||||
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(_)).Times(0);
|
||||
|
||||
switch (saturation_variant) {
|
||||
case SaturationTestVariant::kNone:
|
||||
EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _))
|
||||
.Times(expected_num_block_to_process);
|
||||
break;
|
||||
case SaturationTestVariant::kOneNegative: {
|
||||
testing::InSequence s;
|
||||
EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _))
|
||||
.Times(num_full_blocks_per_frame);
|
||||
EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _))
|
||||
.Times(expected_num_block_to_process - num_full_blocks_per_frame);
|
||||
} break;
|
||||
case SaturationTestVariant::kOnePositive: {
|
||||
testing::InSequence s;
|
||||
EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _))
|
||||
.Times(num_full_blocks_per_frame);
|
||||
EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _))
|
||||
.Times(expected_num_block_to_process - num_full_blocks_per_frame);
|
||||
} break;
|
||||
}
|
||||
|
||||
EchoCanceller3 aec3(sample_rate_hz_, false,
|
||||
std::move(block_processor_mock));
|
||||
for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
|
||||
++frame_index) {
|
||||
for (int k = 0; k < fullband_frame_length_; ++k) {
|
||||
capture_buffer_.channels_f()[0][k] = 0.f;
|
||||
}
|
||||
switch (saturation_variant) {
|
||||
case SaturationTestVariant::kNone:
|
||||
break;
|
||||
case SaturationTestVariant::kOneNegative:
|
||||
if (frame_index == 0) {
|
||||
capture_buffer_.channels_f()[0][10] = -32768.f;
|
||||
}
|
||||
break;
|
||||
case SaturationTestVariant::kOnePositive:
|
||||
if (frame_index == 0) {
|
||||
capture_buffer_.channels_f()[0][10] = 32767.f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
aec3.AnalyzeCapture(&capture_buffer_);
|
||||
OptionalBandSplit();
|
||||
|
||||
PopulateInputFrame(frame_length_, num_bands_, frame_index,
|
||||
&capture_buffer_.split_bands_f(0)[0], 0);
|
||||
PopulateInputFrame(frame_length_, num_bands_, frame_index,
|
||||
&render_buffer_.split_bands_f(0)[0], 0);
|
||||
|
||||
aec3.AnalyzeRender(&render_buffer_);
|
||||
aec3.ProcessCapture(&capture_buffer_, false);
|
||||
}
|
||||
}
|
||||
|
||||
// This test verifies that the swapqueue is able to handle jitter in the
|
||||
// capture and render API calls.
|
||||
void RunRenderSwapQueueVerificationTest() {
|
||||
EchoCanceller3 aec3(
|
||||
sample_rate_hz_, false,
|
||||
std::unique_ptr<BlockProcessor>(
|
||||
new RenderTransportVerificationProcessor(num_bands_)));
|
||||
|
||||
for (size_t frame_index = 0; frame_index < kRenderTransferQueueSize;
|
||||
++frame_index) {
|
||||
if (sample_rate_hz_ > 16000) {
|
||||
render_buffer_.SplitIntoFrequencyBands();
|
||||
}
|
||||
PopulateInputFrame(frame_length_, num_bands_, frame_index,
|
||||
&render_buffer_.split_bands_f(0)[0], 0);
|
||||
|
||||
if (sample_rate_hz_ > 16000) {
|
||||
render_buffer_.SplitIntoFrequencyBands();
|
||||
}
|
||||
|
||||
aec3.AnalyzeRender(&render_buffer_);
|
||||
}
|
||||
|
||||
for (size_t frame_index = 0; frame_index < kRenderTransferQueueSize;
|
||||
++frame_index) {
|
||||
aec3.AnalyzeCapture(&capture_buffer_);
|
||||
if (sample_rate_hz_ > 16000) {
|
||||
capture_buffer_.SplitIntoFrequencyBands();
|
||||
}
|
||||
|
||||
PopulateInputFrame(frame_length_, num_bands_, frame_index,
|
||||
&capture_buffer_.split_bands_f(0)[0], 0);
|
||||
|
||||
aec3.ProcessCapture(&capture_buffer_, false);
|
||||
EXPECT_TRUE(VerifyOutputFrameBitexactness(
|
||||
frame_length_, num_bands_, frame_index,
|
||||
&capture_buffer_.split_bands_f(0)[0], -64));
|
||||
}
|
||||
}
|
||||
|
||||
// This test verifies that a buffer overrun in the render swapqueue is
|
||||
// properly reported.
|
||||
void RunRenderPipelineSwapQueueOverrunReturnValueTest() {
|
||||
EchoCanceller3 aec3(AudioProcessing::Config::EchoCanceller3(),
|
||||
sample_rate_hz_, false);
|
||||
|
||||
constexpr size_t kRenderTransferQueueSize = 30;
|
||||
for (size_t k = 0; k < 2; ++k) {
|
||||
for (size_t frame_index = 0; frame_index < kRenderTransferQueueSize;
|
||||
++frame_index) {
|
||||
if (sample_rate_hz_ > 16000) {
|
||||
render_buffer_.SplitIntoFrequencyBands();
|
||||
}
|
||||
PopulateInputFrame(frame_length_, frame_index,
|
||||
&render_buffer_.channels_f()[0][0], 0);
|
||||
|
||||
if (k == 0) {
|
||||
aec3.AnalyzeRender(&render_buffer_);
|
||||
} else {
|
||||
aec3.AnalyzeRender(&render_buffer_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
// Verifies the that the check for the number of bands in the AnalyzeRender
|
||||
// input is correct by adjusting the sample rates of EchoCanceller3 and the
|
||||
// input AudioBuffer to have a different number of bands.
|
||||
void RunAnalyzeRenderNumBandsCheckVerification() {
|
||||
// Set aec3_sample_rate_hz to be different from sample_rate_hz_ in such a
|
||||
// way that the number of bands for the rates are different.
|
||||
const int aec3_sample_rate_hz = sample_rate_hz_ == 48000 ? 32000 : 48000;
|
||||
EchoCanceller3 aec3(AudioProcessing::Config::EchoCanceller3(),
|
||||
aec3_sample_rate_hz, false);
|
||||
PopulateInputFrame(frame_length_, 0, &render_buffer_.channels_f()[0][0], 0);
|
||||
|
||||
EXPECT_DEATH(aec3.AnalyzeRender(&render_buffer_), "");
|
||||
}
|
||||
|
||||
// Verifies the that the check for the number of bands in the ProcessCapture
|
||||
// input is correct by adjusting the sample rates of EchoCanceller3 and the
|
||||
// input AudioBuffer to have a different number of bands.
|
||||
void RunProcessCaptureNumBandsCheckVerification() {
|
||||
// Set aec3_sample_rate_hz to be different from sample_rate_hz_ in such a
|
||||
// way that the number of bands for the rates are different.
|
||||
const int aec3_sample_rate_hz = sample_rate_hz_ == 48000 ? 32000 : 48000;
|
||||
EchoCanceller3 aec3(AudioProcessing::Config::EchoCanceller3(),
|
||||
aec3_sample_rate_hz, false);
|
||||
PopulateInputFrame(frame_length_, num_bands_, 0,
|
||||
&capture_buffer_.split_bands_f(0)[0], 100);
|
||||
EXPECT_DEATH(aec3.ProcessCapture(&capture_buffer_, false), "");
|
||||
}
|
||||
|
||||
// Verifies the that the check for the frame length in the AnalyzeRender input
|
||||
// is correct by adjusting the sample rates of EchoCanceller3 and the input
|
||||
// AudioBuffer to have a different frame lengths.
|
||||
void RunAnalyzeRenderFrameLengthCheckVerification() {
|
||||
// Set aec3_sample_rate_hz to be different from sample_rate_hz_ in such a
|
||||
// way that the band frame lengths are different.
|
||||
const int aec3_sample_rate_hz = sample_rate_hz_ == 8000 ? 16000 : 8000;
|
||||
EchoCanceller3 aec3(AudioProcessing::Config::EchoCanceller3(),
|
||||
aec3_sample_rate_hz, false);
|
||||
|
||||
OptionalBandSplit();
|
||||
PopulateInputFrame(frame_length_, 0, &render_buffer_.channels_f()[0][0], 0);
|
||||
|
||||
EXPECT_DEATH(aec3.AnalyzeRender(&render_buffer_), "");
|
||||
}
|
||||
|
||||
// Verifies the that the check for the frame length in the AnalyzeRender input
|
||||
// is correct by adjusting the sample rates of EchoCanceller3 and the input
|
||||
// AudioBuffer to have a different frame lengths.
|
||||
void RunProcessCaptureFrameLengthCheckVerification() {
|
||||
// Set aec3_sample_rate_hz to be different from sample_rate_hz_ in such a
|
||||
// way that the band frame lengths are different.
|
||||
const int aec3_sample_rate_hz = sample_rate_hz_ == 8000 ? 16000 : 8000;
|
||||
EchoCanceller3 aec3(AudioProcessing::Config::EchoCanceller3(),
|
||||
aec3_sample_rate_hz, false);
|
||||
|
||||
OptionalBandSplit();
|
||||
PopulateInputFrame(frame_length_, num_bands_, 0,
|
||||
&capture_buffer_.split_bands_f(0)[0], 100);
|
||||
|
||||
EXPECT_DEATH(aec3.ProcessCapture(&capture_buffer_, false), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private:
|
||||
void OptionalBandSplit() {
|
||||
if (sample_rate_hz_ > 16000) {
|
||||
capture_buffer_.SplitIntoFrequencyBands();
|
||||
render_buffer_.SplitIntoFrequencyBands();
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr size_t kNumFramesToProcess = 20;
|
||||
const int sample_rate_hz_;
|
||||
const size_t num_bands_;
|
||||
const size_t frame_length_;
|
||||
const int fullband_frame_length_;
|
||||
AudioBuffer capture_buffer_;
|
||||
AudioBuffer render_buffer_;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(EchoCanceller3Tester);
|
||||
};
|
||||
|
||||
std::string ProduceDebugText(int sample_rate_hz) {
|
||||
std::ostringstream ss;
|
||||
ss << "Sample rate: " << sample_rate_hz;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ProduceDebugText(int sample_rate_hz, int variant) {
|
||||
std::ostringstream ss;
|
||||
ss << "Sample rate: " << sample_rate_hz << ", variant: " << variant;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(EchoCanceller3Buffering, CaptureBitexactness) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
EchoCanceller3Tester(rate).RunCaptureTransportVerificationTest();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EchoCanceller3Buffering, RenderBitexactness) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
EchoCanceller3Tester(rate).RunRenderTransportVerificationTest();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EchoCanceller3Buffering, RenderSwapQueue) {
|
||||
for (auto rate : {8000, 16000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
EchoCanceller3Tester(rate).RunRenderSwapQueueVerificationTest();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EchoCanceller3Buffering, RenderSwapQueueOverrunReturnValue) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
EchoCanceller3Tester(rate)
|
||||
.RunRenderPipelineSwapQueueOverrunReturnValueTest();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EchoCanceller3Messaging, CaptureSaturation) {
|
||||
auto variants = {EchoCanceller3Tester::SaturationTestVariant::kNone,
|
||||
EchoCanceller3Tester::SaturationTestVariant::kOneNegative,
|
||||
EchoCanceller3Tester::SaturationTestVariant::kOnePositive};
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
for (auto variant : variants) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate, static_cast<int>(variant)));
|
||||
EchoCanceller3Tester(rate).RunCaptureSaturationVerificationTest(variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EchoCanceller3Messaging, EchoPathChange) {
|
||||
auto variants = {
|
||||
EchoCanceller3Tester::EchoPathChangeTestVariant::kNone,
|
||||
EchoCanceller3Tester::EchoPathChangeTestVariant::kOneSticky,
|
||||
EchoCanceller3Tester::EchoPathChangeTestVariant::kOneNonSticky};
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
for (auto variant : variants) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate, static_cast<int>(variant)));
|
||||
EchoCanceller3Tester(rate).RunEchoPathChangeVerificationTest(variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EchoCanceller3Messaging, EchoLeakage) {
|
||||
auto variants = {
|
||||
EchoCanceller3Tester::EchoLeakageTestVariant::kNone,
|
||||
EchoCanceller3Tester::EchoLeakageTestVariant::kFalseSticky,
|
||||
EchoCanceller3Tester::EchoLeakageTestVariant::kTrueSticky,
|
||||
EchoCanceller3Tester::EchoLeakageTestVariant::kTrueNonSticky};
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
for (auto variant : variants) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate, static_cast<int>(variant)));
|
||||
EchoCanceller3Tester(rate).RunEchoLeakageVerificationTest(variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EchoCanceller3, ConfigValidation) {
|
||||
AudioProcessing::Config::EchoCanceller3 config;
|
||||
EXPECT_TRUE(EchoCanceller3::Validate(config));
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
|
||||
TEST(EchoCanceller3InputCheck, WrongCaptureNumBandsCheckVerification) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
EchoCanceller3Tester(rate).RunProcessCaptureNumBandsCheckVerification();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
|
||||
// tests on test bots has been fixed.
|
||||
TEST(EchoCanceller3InputCheck,
|
||||
DISABLED_WrongRenderFrameLengthCheckVerification) {
|
||||
for (auto rate : {8000, 16000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
EchoCanceller3Tester(rate).RunAnalyzeRenderFrameLengthCheckVerification();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EchoCanceller3InputCheck, WrongCaptureFrameLengthCheckVerification) {
|
||||
for (auto rate : {8000, 16000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
EchoCanceller3Tester(rate).RunProcessCaptureFrameLengthCheckVerification();
|
||||
}
|
||||
}
|
||||
|
||||
// Verifiers that the verification for null input to the render analysis api
|
||||
// call works.
|
||||
TEST(EchoCanceller3InputCheck, NullRenderAnalysisParameter) {
|
||||
EXPECT_DEATH(
|
||||
EchoCanceller3(AudioProcessing::Config::EchoCanceller3(), 8000, false)
|
||||
.AnalyzeRender(nullptr),
|
||||
"");
|
||||
}
|
||||
|
||||
// Verifiers that the verification for null input to the capture analysis api
|
||||
// call works.
|
||||
TEST(EchoCanceller3InputCheck, NullCaptureAnalysisParameter) {
|
||||
EXPECT_DEATH(
|
||||
EchoCanceller3(AudioProcessing::Config::EchoCanceller3(), 8000, false)
|
||||
.AnalyzeCapture(nullptr),
|
||||
"");
|
||||
}
|
||||
|
||||
// Verifiers that the verification for null input to the capture processing api
|
||||
// call works.
|
||||
TEST(EchoCanceller3InputCheck, NullCaptureProcessingParameter) {
|
||||
EXPECT_DEATH(
|
||||
EchoCanceller3(AudioProcessing::Config::EchoCanceller3(), 8000, false)
|
||||
.ProcessCapture(nullptr, false),
|
||||
"");
|
||||
}
|
||||
|
||||
// Verifies the check for correct sample rate.
|
||||
// TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
|
||||
// tests on test bots has been fixed.
|
||||
TEST(EchoCanceller3InputCheck, DISABLED_WrongSampleRate) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
EXPECT_DEATH(
|
||||
EchoCanceller3(AudioProcessing::Config::EchoCanceller3(), 8001, false),
|
||||
"");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/echo_path_delay_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kDownSamplingFactor = 4;
|
||||
} // namespace
|
||||
|
||||
EchoPathDelayEstimator::EchoPathDelayEstimator(
|
||||
ApmDataDumper* data_dumper,
|
||||
const AudioProcessing::Config::EchoCanceller3& config)
|
||||
: data_dumper_(data_dumper),
|
||||
matched_filter_(data_dumper_,
|
||||
DetectOptimization(),
|
||||
kMatchedFilterWindowSizeSubBlocks,
|
||||
kNumMatchedFilters,
|
||||
kMatchedFilterAlignmentShiftSizeSubBlocks,
|
||||
config.param.render_levels.poor_excitation_render_limit),
|
||||
matched_filter_lag_aggregator_(data_dumper_,
|
||||
matched_filter_.NumLagEstimates()) {
|
||||
RTC_DCHECK(data_dumper);
|
||||
}
|
||||
|
||||
EchoPathDelayEstimator::~EchoPathDelayEstimator() = default;
|
||||
|
||||
void EchoPathDelayEstimator::Reset() {
|
||||
matched_filter_lag_aggregator_.Reset();
|
||||
matched_filter_.Reset();
|
||||
}
|
||||
|
||||
rtc::Optional<size_t> EchoPathDelayEstimator::EstimateDelay(
|
||||
const DownsampledRenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const float> capture) {
|
||||
RTC_DCHECK_EQ(kBlockSize, capture.size());
|
||||
|
||||
std::array<float, kSubBlockSize> downsampled_capture;
|
||||
capture_decimator_.Decimate(capture, downsampled_capture);
|
||||
matched_filter_.Update(render_buffer, downsampled_capture);
|
||||
|
||||
rtc::Optional<size_t> aggregated_matched_filter_lag =
|
||||
matched_filter_lag_aggregator_.Aggregate(
|
||||
matched_filter_.GetLagEstimates());
|
||||
|
||||
// TODO(peah): Move this logging outside of this class once EchoCanceller3
|
||||
// development is done.
|
||||
data_dumper_->DumpRaw("aec3_echo_path_delay_estimator_delay",
|
||||
aggregated_matched_filter_lag
|
||||
? static_cast<int>(*aggregated_matched_filter_lag *
|
||||
kDownSamplingFactor)
|
||||
: -1);
|
||||
|
||||
// Return the detected delay in samples as the aggregated matched filter lag
|
||||
// compensated by the down sampling factor for the signal being correlated.
|
||||
return aggregated_matched_filter_lag
|
||||
? rtc::Optional<size_t>(*aggregated_matched_filter_lag *
|
||||
kDownSamplingFactor)
|
||||
: rtc::Optional<size_t>();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_PATH_DELAY_ESTIMATOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_PATH_DELAY_ESTIMATOR_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/optional.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/decimator_by_4.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/downsampled_render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/matched_filter.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/matched_filter_lag_aggregator.h"
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class ApmDataDumper;
|
||||
|
||||
// Estimates the delay of the echo path.
|
||||
class EchoPathDelayEstimator {
|
||||
public:
|
||||
EchoPathDelayEstimator(ApmDataDumper* data_dumper,
|
||||
const AudioProcessing::Config::EchoCanceller3& config);
|
||||
~EchoPathDelayEstimator();
|
||||
|
||||
// Resets the estimation.
|
||||
void Reset();
|
||||
|
||||
// Produce a delay estimate if such is avaliable.
|
||||
rtc::Optional<size_t> EstimateDelay(
|
||||
const DownsampledRenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const float> capture);
|
||||
|
||||
private:
|
||||
ApmDataDumper* const data_dumper_;
|
||||
DecimatorBy4 capture_decimator_;
|
||||
MatchedFilter matched_filter_;
|
||||
MatchedFilterLagAggregator matched_filter_lag_aggregator_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(EchoPathDelayEstimator);
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_PATH_DELAY_ESTIMATOR_H_
|
||||
@ -1,194 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/echo_path_delay_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_delay_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/modules/audio_processing/test/echo_canceller_test_tools.h"
|
||||
#include "webrtc/rtc_base/random.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
std::string ProduceDebugText(size_t delay) {
|
||||
std::ostringstream ss;
|
||||
ss << "Delay: " << delay;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies that the basic API calls work.
|
||||
TEST(EchoPathDelayEstimator, BasicApiCalls) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
EchoPathDelayEstimator estimator(&data_dumper,
|
||||
AudioProcessing::Config::EchoCanceller3());
|
||||
std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
|
||||
std::vector<float> capture(kBlockSize);
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
render_delay_buffer->Insert(render);
|
||||
estimator.EstimateDelay(render_delay_buffer->GetDownsampledRenderBuffer(),
|
||||
capture);
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the delay estimator produces correct delay for artificially
|
||||
// delayed signals.
|
||||
TEST(EchoPathDelayEstimator, DelayEstimation) {
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
|
||||
std::vector<float> capture(kBlockSize);
|
||||
ApmDataDumper data_dumper(0);
|
||||
for (size_t delay_samples : {15, 64, 150, 200, 800, 4000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
DelayBuffer<float> signal_delay_buffer(delay_samples);
|
||||
EchoPathDelayEstimator estimator(&data_dumper,
|
||||
AudioProcessing::Config::EchoCanceller3());
|
||||
|
||||
rtc::Optional<size_t> estimated_delay_samples;
|
||||
for (size_t k = 0; k < (100 + delay_samples / kBlockSize); ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
signal_delay_buffer.Delay(render[0], capture);
|
||||
render_delay_buffer->Insert(render);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
estimated_delay_samples = estimator.EstimateDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture);
|
||||
}
|
||||
if (estimated_delay_samples) {
|
||||
// Due to the internal down-sampling by 4 done inside the delay estimator
|
||||
// the estimated delay cannot be expected to be closer than 4 samples to
|
||||
// the true delay.
|
||||
EXPECT_NEAR(delay_samples, *estimated_delay_samples, 4);
|
||||
} else {
|
||||
ADD_FAILURE();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the delay estimator does not produce delay estimates too
|
||||
// quickly.
|
||||
TEST(EchoPathDelayEstimator, NoInitialDelayestimates) {
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
|
||||
std::vector<float> capture(kBlockSize);
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
|
||||
EchoPathDelayEstimator estimator(&data_dumper,
|
||||
AudioProcessing::Config::EchoCanceller3());
|
||||
for (size_t k = 0; k < 19; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
std::copy(render[0].begin(), render[0].end(), capture.begin());
|
||||
render_delay_buffer->Insert(render);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
EXPECT_FALSE(estimator.EstimateDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture));
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the delay estimator does not produce delay estimates for render
|
||||
// signals of low level.
|
||||
TEST(EchoPathDelayEstimator, NoDelayEstimatesForLowLevelRenderSignals) {
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
|
||||
std::vector<float> capture(kBlockSize);
|
||||
ApmDataDumper data_dumper(0);
|
||||
EchoPathDelayEstimator estimator(&data_dumper,
|
||||
AudioProcessing::Config::EchoCanceller3());
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
for (auto& render_k : render[0]) {
|
||||
render_k *= 100.f / 32767.f;
|
||||
}
|
||||
std::copy(render[0].begin(), render[0].end(), capture.begin());
|
||||
render_delay_buffer->Insert(render);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
EXPECT_FALSE(estimator.EstimateDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture));
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the delay estimator does not produce delay estimates for
|
||||
// uncorrelated signals.
|
||||
TEST(EchoPathDelayEstimator, NoDelayEstimatesForUncorrelatedSignals) {
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
|
||||
std::vector<float> capture(kBlockSize);
|
||||
ApmDataDumper data_dumper(0);
|
||||
EchoPathDelayEstimator estimator(&data_dumper,
|
||||
AudioProcessing::Config::EchoCanceller3());
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
RandomizeSampleVector(&random_generator, capture);
|
||||
render_delay_buffer->Insert(render);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
EXPECT_FALSE(estimator.EstimateDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture));
|
||||
}
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
|
||||
// Verifies the check for the render blocksize.
|
||||
// TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
|
||||
// tests on test bots has been fixed.
|
||||
TEST(EchoPathDelayEstimator, DISABLED_WrongRenderBlockSize) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
EchoPathDelayEstimator estimator(&data_dumper,
|
||||
AudioProcessing::Config::EchoCanceller3());
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
std::vector<float> capture(kBlockSize);
|
||||
EXPECT_DEATH(estimator.EstimateDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture),
|
||||
"");
|
||||
}
|
||||
|
||||
// Verifies the check for the capture blocksize.
|
||||
// TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
|
||||
// tests on test bots has been fixed.
|
||||
TEST(EchoPathDelayEstimator, WrongCaptureBlockSize) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
EchoPathDelayEstimator estimator(&data_dumper,
|
||||
AudioProcessing::Config::EchoCanceller3());
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
std::vector<float> capture(std::vector<float>(kBlockSize - 1));
|
||||
EXPECT_DEATH(estimator.EstimateDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture),
|
||||
"");
|
||||
}
|
||||
|
||||
// Verifies the check for non-null data dumper.
|
||||
TEST(EchoPathDelayEstimator, NullDataDumper) {
|
||||
EXPECT_DEATH(EchoPathDelayEstimator(
|
||||
nullptr, AudioProcessing::Config::EchoCanceller3()),
|
||||
"");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/echo_path_variability.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
EchoPathVariability::EchoPathVariability(bool gain_change, bool delay_change)
|
||||
: gain_change(gain_change), delay_change(delay_change) {}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_PATH_VARIABILITY_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_PATH_VARIABILITY_H_
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
struct EchoPathVariability {
|
||||
EchoPathVariability(bool gain_change, bool delay_change);
|
||||
|
||||
bool AudioPathChanged() const { return gain_change || delay_change; }
|
||||
bool gain_change;
|
||||
bool delay_change;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_PATH_VARIABILITY_H_
|
||||
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/echo_path_variability.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
TEST(EchoPathVariability, CorrectBehavior) {
|
||||
// Test correct passing and reporting of the gain change information.
|
||||
EchoPathVariability v(true, true);
|
||||
EXPECT_TRUE(v.gain_change);
|
||||
EXPECT_TRUE(v.delay_change);
|
||||
EXPECT_TRUE(v.AudioPathChanged());
|
||||
|
||||
v = EchoPathVariability(true, false);
|
||||
EXPECT_TRUE(v.gain_change);
|
||||
EXPECT_FALSE(v.delay_change);
|
||||
EXPECT_TRUE(v.AudioPathChanged());
|
||||
|
||||
v = EchoPathVariability(false, true);
|
||||
EXPECT_FALSE(v.gain_change);
|
||||
EXPECT_TRUE(v.delay_change);
|
||||
EXPECT_TRUE(v.AudioPathChanged());
|
||||
|
||||
v = EchoPathVariability(false, false);
|
||||
EXPECT_FALSE(v.gain_change);
|
||||
EXPECT_FALSE(v.delay_change);
|
||||
EXPECT_FALSE(v.AudioPathChanged());
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,251 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/echo_remover.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec_state.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/comfort_noise_generator.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/echo_path_variability.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/echo_remover_metrics.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/fft_data.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/output_selector.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_delay_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/residual_echo_estimator.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/subtractor.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/suppression_filter.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/suppression_gain.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/rtc_base/atomicops.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
void LinearEchoPower(const FftData& E,
|
||||
const FftData& Y,
|
||||
std::array<float, kFftLengthBy2Plus1>* S2) {
|
||||
for (size_t k = 0; k < E.re.size(); ++k) {
|
||||
(*S2)[k] = (Y.re[k] - E.re[k]) * (Y.re[k] - E.re[k]) +
|
||||
(Y.im[k] - E.im[k]) * (Y.im[k] - E.im[k]);
|
||||
}
|
||||
}
|
||||
|
||||
// Class for removing the echo from the capture signal.
|
||||
class EchoRemoverImpl final : public EchoRemover {
|
||||
public:
|
||||
explicit EchoRemoverImpl(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz);
|
||||
~EchoRemoverImpl() override;
|
||||
|
||||
// Removes the echo from a block of samples from the capture signal. The
|
||||
// supplied render signal is assumed to be pre-aligned with the capture
|
||||
// signal.
|
||||
void ProcessCapture(const rtc::Optional<size_t>& echo_path_delay_samples,
|
||||
const EchoPathVariability& echo_path_variability,
|
||||
bool capture_signal_saturation,
|
||||
const RenderBuffer& render_buffer,
|
||||
std::vector<std::vector<float>>* capture) override;
|
||||
|
||||
// Updates the status on whether echo leakage is detected in the output of the
|
||||
// echo remover.
|
||||
void UpdateEchoLeakageStatus(bool leakage_detected) override {
|
||||
echo_leakage_detected_ = leakage_detected;
|
||||
}
|
||||
|
||||
private:
|
||||
static int instance_count_;
|
||||
const AudioProcessing::Config::EchoCanceller3 config_;
|
||||
const Aec3Fft fft_;
|
||||
std::unique_ptr<ApmDataDumper> data_dumper_;
|
||||
const Aec3Optimization optimization_;
|
||||
const int sample_rate_hz_;
|
||||
Subtractor subtractor_;
|
||||
SuppressionGain suppression_gain_;
|
||||
ComfortNoiseGenerator cng_;
|
||||
SuppressionFilter suppression_filter_;
|
||||
RenderSignalAnalyzer render_signal_analyzer_;
|
||||
OutputSelector output_selector_;
|
||||
ResidualEchoEstimator residual_echo_estimator_;
|
||||
bool echo_leakage_detected_ = false;
|
||||
AecState aec_state_;
|
||||
EchoRemoverMetrics metrics_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(EchoRemoverImpl);
|
||||
};
|
||||
|
||||
int EchoRemoverImpl::instance_count_ = 0;
|
||||
|
||||
EchoRemoverImpl::EchoRemoverImpl(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz)
|
||||
: config_(config),
|
||||
fft_(),
|
||||
data_dumper_(
|
||||
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
||||
optimization_(DetectOptimization()),
|
||||
sample_rate_hz_(sample_rate_hz),
|
||||
subtractor_(data_dumper_.get(), optimization_),
|
||||
suppression_gain_(config_, optimization_),
|
||||
cng_(optimization_),
|
||||
suppression_filter_(sample_rate_hz_),
|
||||
residual_echo_estimator_(config_),
|
||||
aec_state_(config_) {
|
||||
RTC_DCHECK(ValidFullBandRate(sample_rate_hz));
|
||||
}
|
||||
|
||||
EchoRemoverImpl::~EchoRemoverImpl() = default;
|
||||
|
||||
void EchoRemoverImpl::ProcessCapture(
|
||||
const rtc::Optional<size_t>& echo_path_delay_samples,
|
||||
const EchoPathVariability& echo_path_variability,
|
||||
bool capture_signal_saturation,
|
||||
const RenderBuffer& render_buffer,
|
||||
std::vector<std::vector<float>>* capture) {
|
||||
const std::vector<std::vector<float>>& x = render_buffer.MostRecentBlock();
|
||||
std::vector<std::vector<float>>* y = capture;
|
||||
|
||||
RTC_DCHECK(y);
|
||||
RTC_DCHECK_EQ(x.size(), NumBandsForRate(sample_rate_hz_));
|
||||
RTC_DCHECK_EQ(y->size(), NumBandsForRate(sample_rate_hz_));
|
||||
RTC_DCHECK_EQ(x[0].size(), kBlockSize);
|
||||
RTC_DCHECK_EQ((*y)[0].size(), kBlockSize);
|
||||
const std::vector<float>& x0 = x[0];
|
||||
std::vector<float>& y0 = (*y)[0];
|
||||
|
||||
data_dumper_->DumpWav("aec3_echo_remover_capture_input", kBlockSize, &y0[0],
|
||||
LowestBandRate(sample_rate_hz_), 1);
|
||||
data_dumper_->DumpWav("aec3_echo_remover_render_input", kBlockSize, &x0[0],
|
||||
LowestBandRate(sample_rate_hz_), 1);
|
||||
data_dumper_->DumpRaw("aec3_echo_remover_capture_input", y0);
|
||||
data_dumper_->DumpRaw("aec3_echo_remover_render_input", x0);
|
||||
|
||||
aec_state_.UpdateCaptureSaturation(capture_signal_saturation);
|
||||
|
||||
if (echo_path_variability.AudioPathChanged()) {
|
||||
subtractor_.HandleEchoPathChange(echo_path_variability);
|
||||
aec_state_.HandleEchoPathChange(echo_path_variability);
|
||||
}
|
||||
|
||||
std::array<float, kFftLengthBy2Plus1> Y2;
|
||||
std::array<float, kFftLengthBy2Plus1> R2;
|
||||
std::array<float, kFftLengthBy2Plus1> S2_linear;
|
||||
std::array<float, kFftLengthBy2Plus1> G;
|
||||
float high_bands_gain;
|
||||
FftData Y;
|
||||
FftData comfort_noise;
|
||||
FftData high_band_comfort_noise;
|
||||
SubtractorOutput subtractor_output;
|
||||
FftData& E_main = subtractor_output.E_main;
|
||||
auto& E2_main = subtractor_output.E2_main;
|
||||
auto& E2_shadow = subtractor_output.E2_shadow;
|
||||
auto& e_main = subtractor_output.e_main;
|
||||
|
||||
// Analyze the render signal.
|
||||
render_signal_analyzer_.Update(render_buffer, aec_state_.FilterDelay());
|
||||
|
||||
// Perform linear echo cancellation.
|
||||
subtractor_.Process(render_buffer, y0, render_signal_analyzer_, aec_state_,
|
||||
&subtractor_output);
|
||||
|
||||
// Compute spectra.
|
||||
fft_.ZeroPaddedFft(y0, &Y);
|
||||
LinearEchoPower(E_main, Y, &S2_linear);
|
||||
Y.Spectrum(optimization_, &Y2);
|
||||
|
||||
// Update the AEC state information.
|
||||
aec_state_.Update(subtractor_.FilterFrequencyResponse(),
|
||||
subtractor_.FilterImpulseResponse(),
|
||||
echo_path_delay_samples, render_buffer, E2_main, Y2, x0,
|
||||
subtractor_output.s_main, echo_leakage_detected_);
|
||||
|
||||
// Choose the linear output.
|
||||
output_selector_.FormLinearOutput(!aec_state_.HeadsetDetected(), e_main, y0);
|
||||
data_dumper_->DumpWav("aec3_output_linear", kBlockSize, &y0[0],
|
||||
LowestBandRate(sample_rate_hz_), 1);
|
||||
data_dumper_->DumpRaw("aec3_output_linear", y0);
|
||||
const auto& E2 = output_selector_.UseSubtractorOutput() ? E2_main : Y2;
|
||||
|
||||
// Estimate the residual echo power.
|
||||
residual_echo_estimator_.Estimate(output_selector_.UseSubtractorOutput(),
|
||||
aec_state_, render_buffer, S2_linear, Y2,
|
||||
&R2);
|
||||
|
||||
// Estimate the comfort noise.
|
||||
cng_.Compute(aec_state_, Y2, &comfort_noise, &high_band_comfort_noise);
|
||||
|
||||
// A choose and apply echo suppression gain.
|
||||
suppression_gain_.GetGain(E2, R2, cng_.NoiseSpectrum(),
|
||||
render_signal_analyzer_, aec_state_.SaturatedEcho(),
|
||||
x, aec_state_.ForcedZeroGain(), &high_bands_gain,
|
||||
&G);
|
||||
suppression_filter_.ApplyGain(comfort_noise, high_band_comfort_noise, G,
|
||||
high_bands_gain, y);
|
||||
|
||||
// Update the metrics.
|
||||
metrics_.Update(aec_state_, cng_.NoiseSpectrum(), G);
|
||||
|
||||
// Update the aec state with the aec output characteristics.
|
||||
aec_state_.UpdateWithOutput(y0);
|
||||
|
||||
// Debug outputs for the purpose of development and analysis.
|
||||
data_dumper_->DumpWav("aec3_echo_estimate", kBlockSize,
|
||||
&subtractor_output.s_main[0],
|
||||
LowestBandRate(sample_rate_hz_), 1);
|
||||
data_dumper_->DumpRaw("aec3_output", y0);
|
||||
data_dumper_->DumpRaw("aec3_narrow_render",
|
||||
render_signal_analyzer_.NarrowPeakBand() ? 1 : 0);
|
||||
data_dumper_->DumpRaw("aec3_N2", cng_.NoiseSpectrum());
|
||||
data_dumper_->DumpRaw("aec3_suppressor_gain", G);
|
||||
data_dumper_->DumpWav("aec3_output",
|
||||
rtc::ArrayView<const float>(&y0[0], kBlockSize),
|
||||
LowestBandRate(sample_rate_hz_), 1);
|
||||
data_dumper_->DumpRaw("aec3_using_subtractor_output",
|
||||
output_selector_.UseSubtractorOutput() ? 1 : 0);
|
||||
data_dumper_->DumpRaw("aec3_E2", E2);
|
||||
data_dumper_->DumpRaw("aec3_E2_main", E2_main);
|
||||
data_dumper_->DumpRaw("aec3_E2_shadow", E2_shadow);
|
||||
data_dumper_->DumpRaw("aec3_S2_linear", S2_linear);
|
||||
data_dumper_->DumpRaw("aec3_Y2", Y2);
|
||||
data_dumper_->DumpRaw("aec3_X2", render_buffer.Spectrum(0));
|
||||
data_dumper_->DumpRaw("aec3_R2", R2);
|
||||
data_dumper_->DumpRaw("aec3_erle", aec_state_.Erle());
|
||||
data_dumper_->DumpRaw("aec3_erl", aec_state_.Erl());
|
||||
data_dumper_->DumpRaw("aec3_active_render", aec_state_.ActiveRender());
|
||||
data_dumper_->DumpRaw("aec3_usable_linear_estimate",
|
||||
aec_state_.UsableLinearEstimate());
|
||||
data_dumper_->DumpRaw(
|
||||
"aec3_filter_delay",
|
||||
aec_state_.FilterDelay() ? *aec_state_.FilterDelay() : -1);
|
||||
data_dumper_->DumpRaw(
|
||||
"aec3_external_delay",
|
||||
aec_state_.ExternalDelay() ? *aec_state_.ExternalDelay() : -1);
|
||||
data_dumper_->DumpRaw("aec3_capture_saturation",
|
||||
aec_state_.SaturatedCapture() ? 1 : 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EchoRemover* EchoRemover::Create(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz) {
|
||||
return new EchoRemoverImpl(config, sample_rate_hz);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_REMOVER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_REMOVER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/optional.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/echo_path_variability.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Class for removing the echo from the capture signal.
|
||||
class EchoRemover {
|
||||
public:
|
||||
static EchoRemover* Create(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz);
|
||||
virtual ~EchoRemover() = default;
|
||||
|
||||
// Removes the echo from a block of samples from the capture signal. The
|
||||
// supplied render signal is assumed to be pre-aligned with the capture
|
||||
// signal.
|
||||
virtual void ProcessCapture(
|
||||
const rtc::Optional<size_t>& echo_path_delay_samples,
|
||||
const EchoPathVariability& echo_path_variability,
|
||||
bool capture_signal_saturation,
|
||||
const RenderBuffer& render_buffer,
|
||||
std::vector<std::vector<float>>* capture) = 0;
|
||||
|
||||
// Updates the status on whether echo leakage is detected in the output of the
|
||||
// echo remover.
|
||||
virtual void UpdateEchoLeakageStatus(bool leakage_detected) = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_REMOVER_H_
|
||||
@ -1,282 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/echo_remover_metrics.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "webrtc/rtc_base/safe_minmax.h"
|
||||
#include "webrtc/system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kOneByMetricsCollectionBlocks = 1.f / kMetricsCollectionBlocks;
|
||||
|
||||
} // namespace
|
||||
|
||||
EchoRemoverMetrics::DbMetric::DbMetric() : DbMetric(0.f, 0.f, 0.f) {}
|
||||
EchoRemoverMetrics::DbMetric::DbMetric(float sum_value,
|
||||
float floor_value,
|
||||
float ceil_value)
|
||||
: sum_value(sum_value), floor_value(floor_value), ceil_value(ceil_value) {}
|
||||
|
||||
void EchoRemoverMetrics::DbMetric::Update(float value) {
|
||||
sum_value += value;
|
||||
floor_value = std::min(floor_value, value);
|
||||
ceil_value = std::max(ceil_value, value);
|
||||
}
|
||||
|
||||
EchoRemoverMetrics::EchoRemoverMetrics() {
|
||||
ResetMetrics();
|
||||
}
|
||||
|
||||
void EchoRemoverMetrics::ResetMetrics() {
|
||||
erl_.fill(DbMetric(0.f, 10000.f, 0.000f));
|
||||
erle_.fill(DbMetric(0.f, 0.f, 1000.f));
|
||||
comfort_noise_.fill(DbMetric(0.f, 100000000.f, 0.f));
|
||||
suppressor_gain_.fill(DbMetric(0.f, 1.f, 0.f));
|
||||
active_render_count_ = 0;
|
||||
saturated_capture_ = false;
|
||||
}
|
||||
|
||||
void EchoRemoverMetrics::Update(
|
||||
const AecState& aec_state,
|
||||
const std::array<float, kFftLengthBy2Plus1>& comfort_noise_spectrum,
|
||||
const std::array<float, kFftLengthBy2Plus1>& suppressor_gain) {
|
||||
metrics_reported_ = false;
|
||||
if (++block_counter_ <= kMetricsCollectionBlocks) {
|
||||
aec3::UpdateDbMetric(aec_state.Erl(), &erl_);
|
||||
aec3::UpdateDbMetric(aec_state.Erle(), &erle_);
|
||||
aec3::UpdateDbMetric(comfort_noise_spectrum, &comfort_noise_);
|
||||
aec3::UpdateDbMetric(suppressor_gain, &suppressor_gain_);
|
||||
active_render_count_ += (aec_state.ActiveRender() ? 1 : 0);
|
||||
saturated_capture_ = saturated_capture_ || aec_state.SaturatedCapture();
|
||||
} else {
|
||||
// Report the metrics over several frames in order to lower the impact of
|
||||
// the logarithms involved on the computational complexity.
|
||||
constexpr int kMetricsCollectionBlocksBy2 = kMetricsCollectionBlocks / 2;
|
||||
constexpr float kComfortNoiseScaling = 1.f / (kBlockSize * kBlockSize);
|
||||
switch (block_counter_) {
|
||||
case kMetricsCollectionBlocks + 1:
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ErleBand0.Average",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 19.f, 0.f,
|
||||
kOneByMetricsCollectionBlocks,
|
||||
erle_[0].sum_value),
|
||||
0, 19, 20);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ErleBand0.Max",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 19.f, 0.f, 1.f,
|
||||
erle_[0].ceil_value),
|
||||
0, 19, 20);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ErleBand0.Min",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 19.f, 0.f, 1.f,
|
||||
erle_[0].floor_value),
|
||||
0, 19, 20);
|
||||
break;
|
||||
case kMetricsCollectionBlocks + 2:
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ErleBand1.Average",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 19.f, 0.f,
|
||||
kOneByMetricsCollectionBlocks,
|
||||
erle_[1].sum_value),
|
||||
0, 19, 20);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ErleBand1.Max",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 19.f, 0.f, 1.f,
|
||||
erle_[1].ceil_value),
|
||||
0, 19, 20);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ErleBand1.Min",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 19.f, 0.f, 1.f,
|
||||
erle_[1].floor_value),
|
||||
0, 19, 20);
|
||||
break;
|
||||
case kMetricsCollectionBlocks + 3:
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ErlBand0.Average",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 30.f,
|
||||
kOneByMetricsCollectionBlocks,
|
||||
erl_[0].sum_value),
|
||||
0, 59, 30);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ErlBand0.Max",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 30.f, 1.f,
|
||||
erl_[0].ceil_value),
|
||||
0, 59, 30);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ErlBand0.Min",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 30.f, 1.f,
|
||||
erl_[0].floor_value),
|
||||
0, 59, 30);
|
||||
break;
|
||||
case kMetricsCollectionBlocks + 4:
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ErlBand1.Average",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 30.f,
|
||||
kOneByMetricsCollectionBlocks,
|
||||
erl_[1].sum_value),
|
||||
0, 59, 30);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ErlBand1.Max",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 30.f, 1.f,
|
||||
erl_[1].ceil_value),
|
||||
0, 59, 30);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ErlBand1.Min",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 30.f, 1.f,
|
||||
erl_[1].floor_value),
|
||||
0, 59, 30);
|
||||
break;
|
||||
case kMetricsCollectionBlocks + 5:
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ComfortNoiseBand0.Average",
|
||||
aec3::TransformDbMetricForReporting(
|
||||
true, 0.f, 89.f, -90.3f,
|
||||
kComfortNoiseScaling * kOneByMetricsCollectionBlocks,
|
||||
comfort_noise_[0].sum_value),
|
||||
0, 89, 45);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ComfortNoiseBand0.Max",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 89.f, -90.3f,
|
||||
kComfortNoiseScaling,
|
||||
comfort_noise_[0].ceil_value),
|
||||
0, 89, 45);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ComfortNoiseBand0.Min",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 89.f, -90.3f,
|
||||
kComfortNoiseScaling,
|
||||
comfort_noise_[0].floor_value),
|
||||
0, 89, 45);
|
||||
break;
|
||||
case kMetricsCollectionBlocks + 6:
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ComfortNoiseBand1.Average",
|
||||
aec3::TransformDbMetricForReporting(
|
||||
true, 0.f, 89.f, -90.3f,
|
||||
kComfortNoiseScaling * kOneByMetricsCollectionBlocks,
|
||||
comfort_noise_[1].sum_value),
|
||||
0, 89, 45);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ComfortNoiseBand1.Max",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 89.f, -90.3f,
|
||||
kComfortNoiseScaling,
|
||||
comfort_noise_[1].ceil_value),
|
||||
0, 89, 45);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.ComfortNoiseBand1.Min",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 89.f, -90.3f,
|
||||
kComfortNoiseScaling,
|
||||
comfort_noise_[1].floor_value),
|
||||
0, 89, 45);
|
||||
break;
|
||||
case kMetricsCollectionBlocks + 7:
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.SuppressorGainBand0.Average",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 0.f,
|
||||
kOneByMetricsCollectionBlocks,
|
||||
suppressor_gain_[0].sum_value),
|
||||
0, 59, 30);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.SuppressorGainBand0.Max",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 0.f, 1.f,
|
||||
suppressor_gain_[0].ceil_value),
|
||||
0, 59, 30);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.SuppressorGainBand0.Min",
|
||||
aec3::TransformDbMetricForReporting(
|
||||
true, 0.f, 59.f, 0.f, 1.f, suppressor_gain_[0].floor_value),
|
||||
0, 59, 30);
|
||||
break;
|
||||
case kMetricsCollectionBlocks + 8:
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.SuppressorGainBand1.Average",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 0.f,
|
||||
kOneByMetricsCollectionBlocks,
|
||||
suppressor_gain_[1].sum_value),
|
||||
0, 59, 30);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.SuppressorGainBand1.Max",
|
||||
aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 0.f, 1.f,
|
||||
suppressor_gain_[1].ceil_value),
|
||||
0, 59, 30);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.SuppressorGainBand1.Min",
|
||||
aec3::TransformDbMetricForReporting(
|
||||
true, 0.f, 59.f, 0.f, 1.f, suppressor_gain_[1].floor_value),
|
||||
0, 59, 30);
|
||||
break;
|
||||
case kMetricsCollectionBlocks + 9:
|
||||
RTC_HISTOGRAM_BOOLEAN(
|
||||
"WebRTC.Audio.EchoCanceller.UsableLinearEstimate",
|
||||
static_cast<int>(aec_state.UsableLinearEstimate() ? 1 : 0));
|
||||
RTC_HISTOGRAM_BOOLEAN(
|
||||
"WebRTC.Audio.EchoCanceller.ActiveRender",
|
||||
static_cast<int>(
|
||||
active_render_count_ > kMetricsCollectionBlocksBy2 ? 1 : 0));
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(
|
||||
"WebRTC.Audio.EchoCanceller.FilterDelay",
|
||||
aec_state.FilterDelay() ? *aec_state.FilterDelay() + 1 : 0, 0, 30,
|
||||
31);
|
||||
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.EchoCanceller.CaptureSaturation",
|
||||
static_cast<int>(saturated_capture_ ? 1 : 0));
|
||||
metrics_reported_ = true;
|
||||
RTC_DCHECK_EQ(kMetricsReportingIntervalBlocks, block_counter_);
|
||||
block_counter_ = 0;
|
||||
ResetMetrics();
|
||||
break;
|
||||
default:
|
||||
RTC_NOTREACHED();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace aec3 {
|
||||
|
||||
void UpdateDbMetric(const std::array<float, kFftLengthBy2Plus1>& value,
|
||||
std::array<EchoRemoverMetrics::DbMetric, 2>* statistic) {
|
||||
RTC_DCHECK(statistic);
|
||||
// Truncation is intended in the band width computation.
|
||||
constexpr int kNumBands = 2;
|
||||
constexpr int kBandWidth = 65 / kNumBands;
|
||||
constexpr float kOneByBandWidth = 1.f / kBandWidth;
|
||||
RTC_DCHECK_EQ(kNumBands, statistic->size());
|
||||
RTC_DCHECK_EQ(65, value.size());
|
||||
for (size_t k = 0; k < statistic->size(); ++k) {
|
||||
float average_band =
|
||||
std::accumulate(value.begin() + kBandWidth * k,
|
||||
value.begin() + kBandWidth * (k + 1), 0.f) *
|
||||
kOneByBandWidth;
|
||||
(*statistic)[k].Update(average_band);
|
||||
}
|
||||
}
|
||||
|
||||
int TransformDbMetricForReporting(bool negate,
|
||||
float min_value,
|
||||
float max_value,
|
||||
float offset,
|
||||
float scaling,
|
||||
float value) {
|
||||
float new_value = 10.f * log10(value * scaling + 1e-10f) + offset;
|
||||
if (negate) {
|
||||
new_value = -new_value;
|
||||
}
|
||||
return static_cast<int>(rtc::SafeClamp(new_value, min_value, max_value));
|
||||
}
|
||||
|
||||
} // namespace aec3
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_REMOVER_METRICS_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_REMOVER_METRICS_H_
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec_state.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Handles the reporting of metrics for the echo remover.
|
||||
class EchoRemoverMetrics {
|
||||
public:
|
||||
struct DbMetric {
|
||||
DbMetric();
|
||||
DbMetric(float sum_value, float floor_value, float ceil_value);
|
||||
void Update(float value);
|
||||
float sum_value;
|
||||
float floor_value;
|
||||
float ceil_value;
|
||||
};
|
||||
|
||||
EchoRemoverMetrics();
|
||||
|
||||
// Updates the metric with new data.
|
||||
void Update(
|
||||
const AecState& aec_state,
|
||||
const std::array<float, kFftLengthBy2Plus1>& comfort_noise_spectrum,
|
||||
const std::array<float, kFftLengthBy2Plus1>& suppressor_gain);
|
||||
|
||||
// Returns true if the metrics have just been reported, otherwise false.
|
||||
bool MetricsReported() { return metrics_reported_; }
|
||||
|
||||
private:
|
||||
// Resets the metrics.
|
||||
void ResetMetrics();
|
||||
|
||||
int block_counter_ = 0;
|
||||
std::array<DbMetric, 2> erl_;
|
||||
std::array<DbMetric, 2> erle_;
|
||||
std::array<DbMetric, 2> comfort_noise_;
|
||||
std::array<DbMetric, 2> suppressor_gain_;
|
||||
int active_render_count_ = 0;
|
||||
bool saturated_capture_ = false;
|
||||
bool metrics_reported_ = false;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(EchoRemoverMetrics);
|
||||
};
|
||||
|
||||
namespace aec3 {
|
||||
|
||||
// Updates a banded metric of type DbMetric with the values in the supplied
|
||||
// array.
|
||||
void UpdateDbMetric(const std::array<float, kFftLengthBy2Plus1>& value,
|
||||
std::array<EchoRemoverMetrics::DbMetric, 2>* statistic);
|
||||
|
||||
// Transforms a DbMetric from the linear domain into the logarithmic domain.
|
||||
int TransformDbMetricForReporting(bool negate,
|
||||
float min_value,
|
||||
float max_value,
|
||||
float offset,
|
||||
float scaling,
|
||||
float value);
|
||||
|
||||
} // namespace aec3
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_REMOVER_METRICS_H_
|
||||
@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/echo_remover_metrics.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec_state.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_fft.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
|
||||
// Verifies the check for non-null input.
|
||||
TEST(UpdateDbMetric, NullValue) {
|
||||
std::array<float, kFftLengthBy2Plus1> value;
|
||||
value.fill(0.f);
|
||||
EXPECT_DEATH(aec3::UpdateDbMetric(value, nullptr), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Verifies the updating functionality of UpdateDbMetric.
|
||||
TEST(UpdateDbMetric, Updating) {
|
||||
std::array<float, kFftLengthBy2Plus1> value;
|
||||
std::array<EchoRemoverMetrics::DbMetric, 2> statistic;
|
||||
statistic.fill(EchoRemoverMetrics::DbMetric(0.f, 100.f, -100.f));
|
||||
constexpr float kValue0 = 10.f;
|
||||
constexpr float kValue1 = 20.f;
|
||||
std::fill(value.begin(), value.begin() + 32, kValue0);
|
||||
std::fill(value.begin() + 32, value.begin() + 64, kValue1);
|
||||
|
||||
aec3::UpdateDbMetric(value, &statistic);
|
||||
EXPECT_FLOAT_EQ(kValue0, statistic[0].sum_value);
|
||||
EXPECT_FLOAT_EQ(kValue0, statistic[0].ceil_value);
|
||||
EXPECT_FLOAT_EQ(kValue0, statistic[0].floor_value);
|
||||
EXPECT_FLOAT_EQ(kValue1, statistic[1].sum_value);
|
||||
EXPECT_FLOAT_EQ(kValue1, statistic[1].ceil_value);
|
||||
EXPECT_FLOAT_EQ(kValue1, statistic[1].floor_value);
|
||||
|
||||
aec3::UpdateDbMetric(value, &statistic);
|
||||
EXPECT_FLOAT_EQ(2.f * kValue0, statistic[0].sum_value);
|
||||
EXPECT_FLOAT_EQ(kValue0, statistic[0].ceil_value);
|
||||
EXPECT_FLOAT_EQ(kValue0, statistic[0].floor_value);
|
||||
EXPECT_FLOAT_EQ(2.f * kValue1, statistic[1].sum_value);
|
||||
EXPECT_FLOAT_EQ(kValue1, statistic[1].ceil_value);
|
||||
EXPECT_FLOAT_EQ(kValue1, statistic[1].floor_value);
|
||||
}
|
||||
|
||||
// Verifies that the TransformDbMetricForReporting method produces the desired
|
||||
// output for values for dBFS.
|
||||
TEST(TransformDbMetricForReporting, DbFsScaling) {
|
||||
std::array<float, kBlockSize> x;
|
||||
FftData X;
|
||||
std::array<float, kFftLengthBy2Plus1> X2;
|
||||
Aec3Fft fft;
|
||||
x.fill(1000.f);
|
||||
fft.ZeroPaddedFft(x, &X);
|
||||
X.Spectrum(Aec3Optimization::kNone, &X2);
|
||||
|
||||
float offset = -10.f * log10(32768.f * 32768.f);
|
||||
EXPECT_NEAR(offset, -90.3f, 0.1f);
|
||||
EXPECT_EQ(
|
||||
static_cast<int>(30.3f),
|
||||
aec3::TransformDbMetricForReporting(
|
||||
true, 0.f, 90.f, offset, 1.f / (kBlockSize * kBlockSize), X2[0]));
|
||||
}
|
||||
|
||||
// Verifies that the TransformDbMetricForReporting method is able to properly
|
||||
// limit the output.
|
||||
TEST(TransformDbMetricForReporting, Limits) {
|
||||
EXPECT_EQ(
|
||||
0,
|
||||
aec3::TransformDbMetricForReporting(false, 0.f, 10.f, 0.f, 1.f, 0.001f));
|
||||
EXPECT_EQ(
|
||||
10,
|
||||
aec3::TransformDbMetricForReporting(false, 0.f, 10.f, 0.f, 1.f, 100.f));
|
||||
}
|
||||
|
||||
// Verifies that the TransformDbMetricForReporting method is able to properly
|
||||
// negate output.
|
||||
TEST(TransformDbMetricForReporting, Negate) {
|
||||
EXPECT_EQ(
|
||||
10,
|
||||
aec3::TransformDbMetricForReporting(true, -20.f, 20.f, 0.f, 1.f, 0.1f));
|
||||
EXPECT_EQ(
|
||||
-10,
|
||||
aec3::TransformDbMetricForReporting(true, -20.f, 20.f, 0.f, 1.f, 10.f));
|
||||
}
|
||||
|
||||
// Verify the Update functionality of DbMetric.
|
||||
TEST(DbMetric, Update) {
|
||||
EchoRemoverMetrics::DbMetric metric(0.f, 20.f, -20.f);
|
||||
constexpr int kNumValues = 100;
|
||||
constexpr float kValue = 10.f;
|
||||
for (int k = 0; k < kNumValues; ++k) {
|
||||
metric.Update(kValue);
|
||||
}
|
||||
EXPECT_FLOAT_EQ(kValue * kNumValues, metric.sum_value);
|
||||
EXPECT_FLOAT_EQ(kValue, metric.ceil_value);
|
||||
EXPECT_FLOAT_EQ(kValue, metric.floor_value);
|
||||
}
|
||||
|
||||
// Verify the constructor functionality of DbMetric.
|
||||
TEST(DbMetric, Constructor) {
|
||||
EchoRemoverMetrics::DbMetric metric;
|
||||
EXPECT_FLOAT_EQ(0.f, metric.sum_value);
|
||||
EXPECT_FLOAT_EQ(0.f, metric.ceil_value);
|
||||
EXPECT_FLOAT_EQ(0.f, metric.floor_value);
|
||||
|
||||
metric = EchoRemoverMetrics::DbMetric(1.f, 2.f, 3.f);
|
||||
EXPECT_FLOAT_EQ(1.f, metric.sum_value);
|
||||
EXPECT_FLOAT_EQ(2.f, metric.floor_value);
|
||||
EXPECT_FLOAT_EQ(3.f, metric.ceil_value);
|
||||
}
|
||||
|
||||
// Verify the general functionality of EchoRemoverMetrics.
|
||||
TEST(EchoRemoverMetrics, NormalUsage) {
|
||||
EchoRemoverMetrics metrics;
|
||||
AecState aec_state(AudioProcessing::Config::EchoCanceller3{});
|
||||
std::array<float, kFftLengthBy2Plus1> comfort_noise_spectrum;
|
||||
std::array<float, kFftLengthBy2Plus1> suppressor_gain;
|
||||
comfort_noise_spectrum.fill(10.f);
|
||||
suppressor_gain.fill(1.f);
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
for (int k = 0; k < kMetricsReportingIntervalBlocks - 1; ++k) {
|
||||
metrics.Update(aec_state, comfort_noise_spectrum, suppressor_gain);
|
||||
EXPECT_FALSE(metrics.MetricsReported());
|
||||
}
|
||||
metrics.Update(aec_state, comfort_noise_spectrum, suppressor_gain);
|
||||
EXPECT_TRUE(metrics.MetricsReported());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/echo_remover.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_delay_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/modules/audio_processing/test/echo_canceller_test_tools.h"
|
||||
#include "webrtc/rtc_base/random.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
std::string ProduceDebugText(int sample_rate_hz) {
|
||||
std::ostringstream ss;
|
||||
ss << "Sample rate: " << sample_rate_hz;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ProduceDebugText(int sample_rate_hz, int delay) {
|
||||
std::ostringstream ss(ProduceDebugText(sample_rate_hz));
|
||||
ss << ", Delay: " << delay;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies the basic API call sequence
|
||||
TEST(EchoRemover, BasicApiCalls) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<EchoRemover> remover(
|
||||
EchoRemover::Create(AudioProcessing::Config::EchoCanceller3(), rate));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
|
||||
std::vector<std::vector<float>> render(NumBandsForRate(rate),
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> capture(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
EchoPathVariability echo_path_variability(k % 3 == 0 ? true : false,
|
||||
k % 5 == 0 ? true : false);
|
||||
rtc::Optional<size_t> echo_path_delay_samples =
|
||||
(k % 6 == 0 ? rtc::Optional<size_t>(k * 10)
|
||||
: rtc::Optional<size_t>());
|
||||
render_buffer->Insert(render);
|
||||
render_buffer->UpdateBuffers();
|
||||
remover->ProcessCapture(echo_path_delay_samples, echo_path_variability,
|
||||
k % 2 == 0 ? true : false,
|
||||
render_buffer->GetRenderBuffer(), &capture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
|
||||
// Verifies the check for the samplerate.
|
||||
// TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
|
||||
// tests on test bots has been fixed.
|
||||
TEST(EchoRemover, DISABLED_WrongSampleRate) {
|
||||
EXPECT_DEATH(std::unique_ptr<EchoRemover>(EchoRemover::Create(
|
||||
AudioProcessing::Config::EchoCanceller3(), 8001)),
|
||||
"");
|
||||
}
|
||||
|
||||
// Verifies the check for the capture block size.
|
||||
TEST(EchoRemover, WrongCaptureBlockSize) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<EchoRemover> remover(
|
||||
EchoRemover::Create(AudioProcessing::Config::EchoCanceller3(), rate));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::vector<std::vector<float>> capture(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize - 1, 0.f));
|
||||
EchoPathVariability echo_path_variability(false, false);
|
||||
rtc::Optional<size_t> echo_path_delay_samples;
|
||||
EXPECT_DEATH(remover->ProcessCapture(
|
||||
echo_path_delay_samples, echo_path_variability, false,
|
||||
render_buffer->GetRenderBuffer(), &capture),
|
||||
"");
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies the check for the number of capture bands.
|
||||
// TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
|
||||
// tests on test bots has been fixed.c
|
||||
TEST(EchoRemover, DISABLED_WrongCaptureNumBands) {
|
||||
for (auto rate : {16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<EchoRemover> remover(
|
||||
EchoRemover::Create(AudioProcessing::Config::EchoCanceller3(), rate));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::vector<std::vector<float>> capture(
|
||||
NumBandsForRate(rate == 48000 ? 16000 : rate + 16000),
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
EchoPathVariability echo_path_variability(false, false);
|
||||
rtc::Optional<size_t> echo_path_delay_samples;
|
||||
EXPECT_DEATH(remover->ProcessCapture(
|
||||
echo_path_delay_samples, echo_path_variability, false,
|
||||
render_buffer->GetRenderBuffer(), &capture),
|
||||
"");
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies the check for non-null capture block.
|
||||
TEST(EchoRemover, NullCapture) {
|
||||
std::unique_ptr<EchoRemover> remover(
|
||||
EchoRemover::Create(AudioProcessing::Config::EchoCanceller3(), 8000));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
EchoPathVariability echo_path_variability(false, false);
|
||||
rtc::Optional<size_t> echo_path_delay_samples;
|
||||
EXPECT_DEATH(
|
||||
remover->ProcessCapture(echo_path_delay_samples, echo_path_variability,
|
||||
false, render_buffer->GetRenderBuffer(), nullptr),
|
||||
"");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Performs a sanity check that the echo_remover is able to properly
|
||||
// remove echoes.
|
||||
TEST(EchoRemover, BasicEchoRemoval) {
|
||||
constexpr int kNumBlocksToProcess = 500;
|
||||
Random random_generator(42U);
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
std::vector<std::vector<float>> x(NumBandsForRate(rate),
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> y(NumBandsForRate(rate),
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
EchoPathVariability echo_path_variability(false, false);
|
||||
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
|
||||
std::unique_ptr<EchoRemover> remover(
|
||||
EchoRemover::Create(AudioProcessing::Config::EchoCanceller3(), rate));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::vector<std::unique_ptr<DelayBuffer<float>>> delay_buffers(x.size());
|
||||
for (size_t j = 0; j < x.size(); ++j) {
|
||||
delay_buffers[j].reset(new DelayBuffer<float>(delay_samples));
|
||||
}
|
||||
|
||||
float input_energy = 0.f;
|
||||
float output_energy = 0.f;
|
||||
for (int k = 0; k < kNumBlocksToProcess; ++k) {
|
||||
const bool silence = k < 100 || (k % 100 >= 10);
|
||||
|
||||
for (size_t j = 0; j < x.size(); ++j) {
|
||||
if (silence) {
|
||||
std::fill(x[j].begin(), x[j].end(), 0.f);
|
||||
} else {
|
||||
RandomizeSampleVector(&random_generator, x[j]);
|
||||
}
|
||||
delay_buffers[j]->Delay(x[j], y[j]);
|
||||
}
|
||||
|
||||
if (k > kNumBlocksToProcess / 2) {
|
||||
for (size_t j = 0; j < x.size(); ++j) {
|
||||
input_energy = std::inner_product(y[j].begin(), y[j].end(),
|
||||
y[j].begin(), input_energy);
|
||||
}
|
||||
}
|
||||
|
||||
render_buffer->Insert(x);
|
||||
render_buffer->UpdateBuffers();
|
||||
|
||||
remover->ProcessCapture(rtc::Optional<size_t>(delay_samples),
|
||||
echo_path_variability, false,
|
||||
render_buffer->GetRenderBuffer(), &y);
|
||||
|
||||
if (k > kNumBlocksToProcess / 2) {
|
||||
for (size_t j = 0; j < x.size(); ++j) {
|
||||
output_energy = std::inner_product(y[j].begin(), y[j].end(),
|
||||
y[j].begin(), output_energy);
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_GT(input_energy, 10.f * output_energy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/erl_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kMinErl = 0.01f;
|
||||
constexpr float kMaxErl = 1000.f;
|
||||
|
||||
} // namespace
|
||||
|
||||
ErlEstimator::ErlEstimator() {
|
||||
erl_.fill(kMaxErl);
|
||||
hold_counters_.fill(0);
|
||||
}
|
||||
|
||||
ErlEstimator::~ErlEstimator() = default;
|
||||
|
||||
void ErlEstimator::Update(
|
||||
const std::array<float, kFftLengthBy2Plus1>& render_spectrum,
|
||||
const std::array<float, kFftLengthBy2Plus1>& capture_spectrum) {
|
||||
const auto& X2 = render_spectrum;
|
||||
const auto& Y2 = capture_spectrum;
|
||||
|
||||
// Corresponds to WGN of power -46 dBFS.
|
||||
constexpr float kX2Min = 44015068.0f;
|
||||
|
||||
// Update the estimates in a maximum statistics manner.
|
||||
for (size_t k = 1; k < kFftLengthBy2; ++k) {
|
||||
if (X2[k] > kX2Min) {
|
||||
const float new_erl = Y2[k] / X2[k];
|
||||
if (new_erl < erl_[k]) {
|
||||
hold_counters_[k - 1] = 1000;
|
||||
erl_[k] += 0.1 * (new_erl - erl_[k]);
|
||||
erl_[k] = std::max(erl_[k], kMinErl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::for_each(hold_counters_.begin(), hold_counters_.end(),
|
||||
[](int& a) { --a; });
|
||||
std::transform(hold_counters_.begin(), hold_counters_.end(), erl_.begin() + 1,
|
||||
erl_.begin() + 1, [](int a, float b) {
|
||||
return a > 0 ? b : std::min(kMaxErl, 2.f * b);
|
||||
});
|
||||
|
||||
erl_[0] = erl_[1];
|
||||
erl_[kFftLengthBy2] = erl_[kFftLengthBy2 - 1];
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ERL_ESTIMATOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ERL_ESTIMATOR_H_
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Estimates the echo return loss based on the signal spectra.
|
||||
class ErlEstimator {
|
||||
public:
|
||||
ErlEstimator();
|
||||
~ErlEstimator();
|
||||
|
||||
// Updates the ERL estimate.
|
||||
void Update(const std::array<float, kFftLengthBy2Plus1>& render_spectrum,
|
||||
const std::array<float, kFftLengthBy2Plus1>& capture_spectrum);
|
||||
|
||||
// Returns the most recent ERL estimate.
|
||||
const std::array<float, kFftLengthBy2Plus1>& Erl() const { return erl_; }
|
||||
|
||||
private:
|
||||
std::array<float, kFftLengthBy2Plus1> erl_;
|
||||
std::array<int, kFftLengthBy2Minus1> hold_counters_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(ErlEstimator);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ERL_ESTIMATOR_H_
|
||||
@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/erl_estimator.h"
|
||||
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
void VerifyErl(const std::array<float, kFftLengthBy2Plus1>& erl,
|
||||
float reference) {
|
||||
std::for_each(erl.begin(), erl.end(),
|
||||
[reference](float a) { EXPECT_NEAR(reference, a, 0.001); });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies that the correct ERL estimates are achieved.
|
||||
TEST(ErlEstimator, Estimates) {
|
||||
std::array<float, kFftLengthBy2Plus1> X2;
|
||||
std::array<float, kFftLengthBy2Plus1> Y2;
|
||||
|
||||
ErlEstimator estimator;
|
||||
|
||||
// Verifies that the ERL estimate is properly reduced to lower values.
|
||||
X2.fill(500 * 1000.f * 1000.f);
|
||||
Y2.fill(10 * X2[0]);
|
||||
for (size_t k = 0; k < 200; ++k) {
|
||||
estimator.Update(X2, Y2);
|
||||
}
|
||||
VerifyErl(estimator.Erl(), 10.f);
|
||||
|
||||
// Verifies that the ERL is not immediately increased when the ERL in the data
|
||||
// increases.
|
||||
Y2.fill(10000 * X2[0]);
|
||||
for (size_t k = 0; k < 998; ++k) {
|
||||
estimator.Update(X2, Y2);
|
||||
}
|
||||
VerifyErl(estimator.Erl(), 10.f);
|
||||
|
||||
// Verifies that the rate of increase is 3 dB.
|
||||
estimator.Update(X2, Y2);
|
||||
VerifyErl(estimator.Erl(), 20.f);
|
||||
|
||||
// Verifies that the maximum ERL is achieved when there are no low RLE
|
||||
// estimates.
|
||||
for (size_t k = 0; k < 1000; ++k) {
|
||||
estimator.Update(X2, Y2);
|
||||
}
|
||||
VerifyErl(estimator.Erl(), 1000.f);
|
||||
|
||||
// Verifies that the ERL estimate is is not updated for low-level signals
|
||||
X2.fill(1000.f * 1000.f);
|
||||
Y2.fill(10 * X2[0]);
|
||||
for (size_t k = 0; k < 200; ++k) {
|
||||
estimator.Update(X2, Y2);
|
||||
}
|
||||
VerifyErl(estimator.Erl(), 1000.f);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/erle_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "webrtc/rtc_base/safe_minmax.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
ErleEstimator::ErleEstimator(float min_erle,
|
||||
float max_erle_lf,
|
||||
float max_erle_hf)
|
||||
: min_erle_(min_erle),
|
||||
max_erle_lf_(max_erle_lf),
|
||||
max_erle_hf_(max_erle_hf) {
|
||||
erle_.fill(min_erle_);
|
||||
hold_counters_.fill(0);
|
||||
}
|
||||
|
||||
ErleEstimator::~ErleEstimator() = default;
|
||||
|
||||
void ErleEstimator::Update(
|
||||
const std::array<float, kFftLengthBy2Plus1>& render_spectrum,
|
||||
const std::array<float, kFftLengthBy2Plus1>& capture_spectrum,
|
||||
const std::array<float, kFftLengthBy2Plus1>& subtractor_spectrum) {
|
||||
const auto& X2 = render_spectrum;
|
||||
const auto& Y2 = capture_spectrum;
|
||||
const auto& E2 = subtractor_spectrum;
|
||||
|
||||
// Corresponds of WGN of power -46 dBFS.
|
||||
constexpr float kX2Min = 44015068.0f;
|
||||
|
||||
// Update the estimates in a clamped minimum statistics manner.
|
||||
auto erle_update = [&](size_t start, size_t stop, float max_erle) {
|
||||
for (size_t k = start; k < stop; ++k) {
|
||||
if (X2[k] > kX2Min && E2[k] > 0.f) {
|
||||
const float new_erle = Y2[k] / E2[k];
|
||||
if (new_erle > erle_[k]) {
|
||||
hold_counters_[k - 1] = 100;
|
||||
erle_[k] += 0.1f * (new_erle - erle_[k]);
|
||||
erle_[k] = rtc::SafeClamp(erle_[k], min_erle_, max_erle);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
erle_update(1, kFftLengthBy2 / 2, max_erle_lf_);
|
||||
erle_update(kFftLengthBy2 / 2, kFftLengthBy2, max_erle_hf_);
|
||||
|
||||
std::for_each(hold_counters_.begin(), hold_counters_.end(),
|
||||
[](int& a) { --a; });
|
||||
std::transform(hold_counters_.begin(), hold_counters_.end(),
|
||||
erle_.begin() + 1, erle_.begin() + 1, [&](int a, float b) {
|
||||
return a > 0 ? b : std::max(min_erle_, 0.97f * b);
|
||||
});
|
||||
|
||||
erle_[0] = erle_[1];
|
||||
erle_[kFftLengthBy2] = erle_[kFftLengthBy2 - 1];
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ERLE_ESTIMATOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ERLE_ESTIMATOR_H_
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Estimates the echo return loss enhancement based on the signal spectra.
|
||||
class ErleEstimator {
|
||||
public:
|
||||
ErleEstimator(float min_erle, float max_erle_lf, float max_erle_hf);
|
||||
~ErleEstimator();
|
||||
|
||||
// Updates the ERLE estimate.
|
||||
void Update(const std::array<float, kFftLengthBy2Plus1>& render_spectrum,
|
||||
const std::array<float, kFftLengthBy2Plus1>& capture_spectrum,
|
||||
const std::array<float, kFftLengthBy2Plus1>& subtractor_spectrum);
|
||||
|
||||
// Returns the most recent ERLE estimate.
|
||||
const std::array<float, kFftLengthBy2Plus1>& Erle() const { return erle_; }
|
||||
|
||||
private:
|
||||
std::array<float, kFftLengthBy2Plus1> erle_;
|
||||
std::array<int, kFftLengthBy2Minus1> hold_counters_;
|
||||
const float min_erle_;
|
||||
const float max_erle_lf_;
|
||||
const float max_erle_hf_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(ErleEstimator);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ERLE_ESTIMATOR_H_
|
||||
@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/erle_estimator.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kLowFrequencyLimit = kFftLengthBy2 / 2;
|
||||
|
||||
void VerifyErle(const std::array<float, kFftLengthBy2Plus1>& erle,
|
||||
float reference_lf,
|
||||
float reference_hf) {
|
||||
std::for_each(
|
||||
erle.begin(), erle.begin() + kLowFrequencyLimit,
|
||||
[reference_lf](float a) { EXPECT_NEAR(reference_lf, a, 0.001); });
|
||||
std::for_each(
|
||||
erle.begin() + kLowFrequencyLimit, erle.end(),
|
||||
[reference_hf](float a) { EXPECT_NEAR(reference_hf, a, 0.001); });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies that the correct ERLE estimates are achieved.
|
||||
TEST(ErleEstimator, Estimates) {
|
||||
std::array<float, kFftLengthBy2Plus1> X2;
|
||||
std::array<float, kFftLengthBy2Plus1> E2;
|
||||
std::array<float, kFftLengthBy2Plus1> Y2;
|
||||
|
||||
ErleEstimator estimator(1.f, 8.f, 1.5f);
|
||||
|
||||
// Verifies that the ERLE estimate is properley increased to higher values.
|
||||
X2.fill(500 * 1000.f * 1000.f);
|
||||
E2.fill(1000.f * 1000.f);
|
||||
Y2.fill(10 * E2[0]);
|
||||
for (size_t k = 0; k < 200; ++k) {
|
||||
estimator.Update(X2, Y2, E2);
|
||||
}
|
||||
VerifyErle(estimator.Erle(), 8.f, 1.5f);
|
||||
|
||||
// Verifies that the ERLE is not immediately decreased when the ERLE in the
|
||||
// data decreases.
|
||||
Y2.fill(0.1f * E2[0]);
|
||||
for (size_t k = 0; k < 98; ++k) {
|
||||
estimator.Update(X2, Y2, E2);
|
||||
}
|
||||
VerifyErle(estimator.Erle(), 8.f, 1.5f);
|
||||
|
||||
// Verifies that the minimum ERLE is eventually achieved.
|
||||
for (size_t k = 0; k < 1000; ++k) {
|
||||
estimator.Update(X2, Y2, E2);
|
||||
}
|
||||
VerifyErle(estimator.Erle(), 1.f, 1.f);
|
||||
|
||||
// Verifies that the ERLE estimate is is not updated for low-level render
|
||||
// signals.
|
||||
X2.fill(1000.f * 1000.f);
|
||||
Y2.fill(10 * E2[0]);
|
||||
for (size_t k = 0; k < 200; ++k) {
|
||||
estimator.Update(X2, Y2, E2);
|
||||
}
|
||||
VerifyErle(estimator.Erle(), 1.f, 1.f);
|
||||
}
|
||||
} // namespace webrtc
|
||||
@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_FFT_DATA_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_FFT_DATA_H_
|
||||
|
||||
#include "webrtc/typedefs.h"
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Struct that holds imaginary data produced from 128 point real-valued FFTs.
|
||||
struct FftData {
|
||||
// Copies the data in src.
|
||||
void Assign(const FftData& src) {
|
||||
std::copy(src.re.begin(), src.re.end(), re.begin());
|
||||
std::copy(src.im.begin(), src.im.end(), im.begin());
|
||||
im[0] = im[kFftLengthBy2] = 0;
|
||||
}
|
||||
|
||||
// Clears all the imaginary.
|
||||
void Clear() {
|
||||
re.fill(0.f);
|
||||
im.fill(0.f);
|
||||
}
|
||||
|
||||
// Computes the power spectrum of the data.
|
||||
void Spectrum(Aec3Optimization optimization,
|
||||
std::array<float, kFftLengthBy2Plus1>* power_spectrum) const {
|
||||
RTC_DCHECK(power_spectrum);
|
||||
switch (optimization) {
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
case Aec3Optimization::kSse2: {
|
||||
constexpr int kNumFourBinBands = kFftLengthBy2 / 4;
|
||||
constexpr int kLimit = kNumFourBinBands * 4;
|
||||
for (size_t k = 0; k < kLimit; k += 4) {
|
||||
const __m128 r = _mm_loadu_ps(&re[k]);
|
||||
const __m128 i = _mm_loadu_ps(&im[k]);
|
||||
const __m128 ii = _mm_mul_ps(i, i);
|
||||
const __m128 rr = _mm_mul_ps(r, r);
|
||||
const __m128 rrii = _mm_add_ps(rr, ii);
|
||||
_mm_storeu_ps(&(*power_spectrum)[k], rrii);
|
||||
}
|
||||
(*power_spectrum)[kFftLengthBy2] =
|
||||
re[kFftLengthBy2] * re[kFftLengthBy2] +
|
||||
im[kFftLengthBy2] * im[kFftLengthBy2];
|
||||
} break;
|
||||
#endif
|
||||
default:
|
||||
std::transform(re.begin(), re.end(), im.begin(),
|
||||
power_spectrum->begin(),
|
||||
[](float a, float b) { return a * a + b * b; });
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the data from an interleaved array.
|
||||
void CopyFromPackedArray(const std::array<float, kFftLength>& v) {
|
||||
re[0] = v[0];
|
||||
re[kFftLengthBy2] = v[1];
|
||||
im[0] = im[kFftLengthBy2] = 0;
|
||||
for (size_t k = 1, j = 2; k < kFftLengthBy2; ++k) {
|
||||
re[k] = v[j++];
|
||||
im[k] = v[j++];
|
||||
}
|
||||
}
|
||||
|
||||
// Copies the data into an interleaved array.
|
||||
void CopyToPackedArray(std::array<float, kFftLength>* v) const {
|
||||
RTC_DCHECK(v);
|
||||
(*v)[0] = re[0];
|
||||
(*v)[1] = re[kFftLengthBy2];
|
||||
for (size_t k = 1, j = 2; k < kFftLengthBy2; ++k) {
|
||||
(*v)[j++] = re[k];
|
||||
(*v)[j++] = im[k];
|
||||
}
|
||||
}
|
||||
|
||||
std::array<float, kFftLengthBy2Plus1> re;
|
||||
std::array<float, kFftLengthBy2Plus1> im;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_FFT_DATA_H_
|
||||
@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/fft_data.h"
|
||||
|
||||
#include "webrtc/system_wrappers/include/cpu_features_wrapper.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
// Verifies that the optimized methods are bitexact to their reference
|
||||
// counterparts.
|
||||
TEST(FftData, TestOptimizations) {
|
||||
if (WebRtc_GetCPUInfo(kSSE2) != 0) {
|
||||
FftData x;
|
||||
|
||||
for (size_t k = 0; k < x.re.size(); ++k) {
|
||||
x.re[k] = k + 1;
|
||||
}
|
||||
|
||||
x.im[0] = x.im[x.im.size() - 1] = 0.f;
|
||||
for (size_t k = 1; k < x.im.size() - 1; ++k) {
|
||||
x.im[k] = 2.f * (k + 1);
|
||||
}
|
||||
|
||||
std::array<float, kFftLengthBy2Plus1> spectrum;
|
||||
std::array<float, kFftLengthBy2Plus1> spectrum_sse2;
|
||||
x.Spectrum(Aec3Optimization::kNone, &spectrum);
|
||||
x.Spectrum(Aec3Optimization::kSse2, &spectrum_sse2);
|
||||
EXPECT_EQ(spectrum, spectrum_sse2);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
|
||||
// Verifies the check for null output in CopyToPackedArray.
|
||||
TEST(FftData, NonNullCopyToPackedArrayOutput) {
|
||||
EXPECT_DEATH(FftData().CopyToPackedArray(nullptr), "");
|
||||
}
|
||||
|
||||
// Verifies the check for null output in Spectrum.
|
||||
TEST(FftData, NonNullSpectrumOutput) {
|
||||
EXPECT_DEATH(FftData().Spectrum(Aec3Optimization::kNone, nullptr), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Verifies that the Assign method properly copies the data from the source and
|
||||
// ensures that the imaginary components for the DC and Nyquist bins are 0.
|
||||
TEST(FftData, Assign) {
|
||||
FftData x;
|
||||
FftData y;
|
||||
|
||||
x.re.fill(1.f);
|
||||
x.im.fill(2.f);
|
||||
y.Assign(x);
|
||||
EXPECT_EQ(x.re, y.re);
|
||||
EXPECT_EQ(0.f, y.im[0]);
|
||||
EXPECT_EQ(0.f, y.im[x.im.size() - 1]);
|
||||
for (size_t k = 1; k < x.im.size() - 1; ++k) {
|
||||
EXPECT_EQ(x.im[k], y.im[k]);
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the Clear method properly clears all the data.
|
||||
TEST(FftData, Clear) {
|
||||
FftData x_ref;
|
||||
FftData x;
|
||||
|
||||
x_ref.re.fill(0.f);
|
||||
x_ref.im.fill(0.f);
|
||||
|
||||
x.re.fill(1.f);
|
||||
x.im.fill(2.f);
|
||||
x.Clear();
|
||||
|
||||
EXPECT_EQ(x_ref.re, x.re);
|
||||
EXPECT_EQ(x_ref.im, x.im);
|
||||
}
|
||||
|
||||
// Verifies that the spectrum is correctly computed.
|
||||
TEST(FftData, Spectrum) {
|
||||
FftData x;
|
||||
|
||||
for (size_t k = 0; k < x.re.size(); ++k) {
|
||||
x.re[k] = k + 1;
|
||||
}
|
||||
|
||||
x.im[0] = x.im[x.im.size() - 1] = 0.f;
|
||||
for (size_t k = 1; k < x.im.size() - 1; ++k) {
|
||||
x.im[k] = 2.f * (k + 1);
|
||||
}
|
||||
|
||||
std::array<float, kFftLengthBy2Plus1> spectrum;
|
||||
x.Spectrum(Aec3Optimization::kNone, &spectrum);
|
||||
|
||||
EXPECT_EQ(x.re[0] * x.re[0], spectrum[0]);
|
||||
EXPECT_EQ(x.re[spectrum.size() - 1] * x.re[spectrum.size() - 1],
|
||||
spectrum[spectrum.size() - 1]);
|
||||
for (size_t k = 1; k < spectrum.size() - 1; ++k) {
|
||||
EXPECT_EQ(x.re[k] * x.re[k] + x.im[k] * x.im[k], spectrum[k]);
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the functionality in CopyToPackedArray works as intended.
|
||||
TEST(FftData, CopyToPackedArray) {
|
||||
FftData x;
|
||||
std::array<float, kFftLength> x_packed;
|
||||
|
||||
for (size_t k = 0; k < x.re.size(); ++k) {
|
||||
x.re[k] = k + 1;
|
||||
}
|
||||
|
||||
x.im[0] = x.im[x.im.size() - 1] = 0.f;
|
||||
for (size_t k = 1; k < x.im.size() - 1; ++k) {
|
||||
x.im[k] = 2.f * (k + 1);
|
||||
}
|
||||
|
||||
x.CopyToPackedArray(&x_packed);
|
||||
|
||||
EXPECT_EQ(x.re[0], x_packed[0]);
|
||||
EXPECT_EQ(x.re[x.re.size() - 1], x_packed[1]);
|
||||
for (size_t k = 1; k < x_packed.size() / 2; ++k) {
|
||||
EXPECT_EQ(x.re[k], x_packed[2 * k]);
|
||||
EXPECT_EQ(x.im[k], x_packed[2 * k + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the functionality in CopyFromPackedArray works as intended
|
||||
// (relies on that the functionality in CopyToPackedArray has been verified in
|
||||
// the test above).
|
||||
TEST(FftData, CopyFromPackedArray) {
|
||||
FftData x_ref;
|
||||
FftData x;
|
||||
std::array<float, kFftLength> x_packed;
|
||||
|
||||
for (size_t k = 0; k < x_ref.re.size(); ++k) {
|
||||
x_ref.re[k] = k + 1;
|
||||
}
|
||||
|
||||
x_ref.im[0] = x_ref.im[x_ref.im.size() - 1] = 0.f;
|
||||
for (size_t k = 1; k < x_ref.im.size() - 1; ++k) {
|
||||
x_ref.im[k] = 2.f * (k + 1);
|
||||
}
|
||||
|
||||
x_ref.CopyToPackedArray(&x_packed);
|
||||
x.CopyFromPackedArray(x_packed);
|
||||
|
||||
EXPECT_EQ(x_ref.re, x.re);
|
||||
EXPECT_EQ(x_ref.im, x.im);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/audio_processing/aec3/frame_blocker.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
FrameBlocker::FrameBlocker(size_t num_bands)
|
||||
: num_bands_(num_bands), buffer_(num_bands_) {
|
||||
for (auto& b : buffer_) {
|
||||
b.reserve(kBlockSize);
|
||||
RTC_DCHECK(b.empty());
|
||||
}
|
||||
}
|
||||
|
||||
FrameBlocker::~FrameBlocker() = default;
|
||||
|
||||
void FrameBlocker::InsertSubFrameAndExtractBlock(
|
||||
const std::vector<rtc::ArrayView<float>>& sub_frame,
|
||||
std::vector<std::vector<float>>* block) {
|
||||
RTC_DCHECK(block);
|
||||
RTC_DCHECK_EQ(num_bands_, block->size());
|
||||
RTC_DCHECK_EQ(num_bands_, sub_frame.size());
|
||||
for (size_t i = 0; i < num_bands_; ++i) {
|
||||
RTC_DCHECK_GE(kBlockSize - 16, buffer_[i].size());
|
||||
RTC_DCHECK_EQ(kBlockSize, (*block)[i].size());
|
||||
RTC_DCHECK_EQ(kSubFrameLength, sub_frame[i].size());
|
||||
const int samples_to_block = kBlockSize - buffer_[i].size();
|
||||
(*block)[i].clear();
|
||||
(*block)[i].insert((*block)[i].begin(), buffer_[i].begin(),
|
||||
buffer_[i].end());
|
||||
(*block)[i].insert((*block)[i].begin() + buffer_[i].size(),
|
||||
sub_frame[i].begin(),
|
||||
sub_frame[i].begin() + samples_to_block);
|
||||
buffer_[i].clear();
|
||||
buffer_[i].insert(buffer_[i].begin(),
|
||||
sub_frame[i].begin() + samples_to_block,
|
||||
sub_frame[i].end());
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameBlocker::IsBlockAvailable() const {
|
||||
return kBlockSize == buffer_[0].size();
|
||||
}
|
||||
|
||||
void FrameBlocker::ExtractBlock(std::vector<std::vector<float>>* block) {
|
||||
RTC_DCHECK(block);
|
||||
RTC_DCHECK_EQ(num_bands_, block->size());
|
||||
RTC_DCHECK(IsBlockAvailable());
|
||||
for (size_t i = 0; i < num_bands_; ++i) {
|
||||
RTC_DCHECK_EQ(kBlockSize, buffer_[i].size());
|
||||
RTC_DCHECK_EQ(kBlockSize, (*block)[i].size());
|
||||
(*block)[i].clear();
|
||||
(*block)[i].insert((*block)[i].begin(), buffer_[i].begin(),
|
||||
buffer_[i].end());
|
||||
buffer_[i].clear();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_FRAME_BLOCKER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_FRAME_BLOCKER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Class for producing 64 sample multiband blocks from frames consisting of 1 or
|
||||
// 2 subframes of 80 samples.
|
||||
class FrameBlocker {
|
||||
public:
|
||||
explicit FrameBlocker(size_t num_bands);
|
||||
~FrameBlocker();
|
||||
// Inserts one 80 sample multiband subframe from the multiband frame and
|
||||
// extracts one 64 sample multiband block.
|
||||
void InsertSubFrameAndExtractBlock(
|
||||
const std::vector<rtc::ArrayView<float>>& sub_frame,
|
||||
std::vector<std::vector<float>>* block);
|
||||
// Reports whether a multiband block of 64 samples is available for
|
||||
// extraction.
|
||||
bool IsBlockAvailable() const;
|
||||
// Extracts a multiband block of 64 samples.
|
||||
void ExtractBlock(std::vector<std::vector<float>>* block);
|
||||
|
||||
private:
|
||||
const size_t num_bands_;
|
||||
std::vector<std::vector<float>> buffer_;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(FrameBlocker);
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_FRAME_BLOCKER_H_
|
||||
@ -1,341 +0,0 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/audio_processing/aec3/frame_blocker.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/block_framer.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
float ComputeSampleValue(size_t chunk_counter,
|
||||
size_t chunk_size,
|
||||
size_t band,
|
||||
size_t sample_index,
|
||||
int offset) {
|
||||
float value =
|
||||
static_cast<int>(chunk_counter * chunk_size + sample_index) + offset;
|
||||
return value > 0 ? 5000 * band + value : 0;
|
||||
}
|
||||
|
||||
void FillSubFrame(size_t sub_frame_counter,
|
||||
int offset,
|
||||
std::vector<std::vector<float>>* sub_frame) {
|
||||
for (size_t k = 0; k < sub_frame->size(); ++k) {
|
||||
for (size_t i = 0; i < (*sub_frame)[0].size(); ++i) {
|
||||
(*sub_frame)[k][i] =
|
||||
ComputeSampleValue(sub_frame_counter, kSubFrameLength, k, i, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FillSubFrameView(size_t sub_frame_counter,
|
||||
int offset,
|
||||
std::vector<std::vector<float>>* sub_frame,
|
||||
std::vector<rtc::ArrayView<float>>* sub_frame_view) {
|
||||
FillSubFrame(sub_frame_counter, offset, sub_frame);
|
||||
for (size_t k = 0; k < sub_frame_view->size(); ++k) {
|
||||
(*sub_frame_view)[k] =
|
||||
rtc::ArrayView<float>(&(*sub_frame)[k][0], (*sub_frame)[k].size());
|
||||
}
|
||||
}
|
||||
|
||||
bool VerifySubFrame(size_t sub_frame_counter,
|
||||
int offset,
|
||||
const std::vector<rtc::ArrayView<float>>& sub_frame_view) {
|
||||
std::vector<std::vector<float>> reference_sub_frame(
|
||||
sub_frame_view.size(), std::vector<float>(sub_frame_view[0].size(), 0.f));
|
||||
FillSubFrame(sub_frame_counter, offset, &reference_sub_frame);
|
||||
for (size_t k = 0; k < sub_frame_view.size(); ++k) {
|
||||
for (size_t i = 0; i < sub_frame_view[k].size(); ++i) {
|
||||
if (reference_sub_frame[k][i] != sub_frame_view[k][i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VerifyBlock(size_t block_counter,
|
||||
int offset,
|
||||
const std::vector<std::vector<float>>& block) {
|
||||
for (size_t k = 0; k < block.size(); ++k) {
|
||||
for (size_t i = 0; i < block[k].size(); ++i) {
|
||||
const float reference_value =
|
||||
ComputeSampleValue(block_counter, kBlockSize, k, i, offset);
|
||||
if (reference_value != block[k][i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Verifies that the FrameBlocker properly forms blocks out of the frames.
|
||||
void RunBlockerTest(int sample_rate_hz) {
|
||||
constexpr size_t kNumSubFramesToProcess = 20;
|
||||
const size_t num_bands = NumBandsForRate(sample_rate_hz);
|
||||
|
||||
std::vector<std::vector<float>> block(num_bands,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> input_sub_frame(
|
||||
num_bands, std::vector<float>(kSubFrameLength, 0.f));
|
||||
std::vector<rtc::ArrayView<float>> input_sub_frame_view(num_bands);
|
||||
FrameBlocker blocker(num_bands);
|
||||
|
||||
size_t block_counter = 0;
|
||||
for (size_t sub_frame_index = 0; sub_frame_index < kNumSubFramesToProcess;
|
||||
++sub_frame_index) {
|
||||
FillSubFrameView(sub_frame_index, 0, &input_sub_frame,
|
||||
&input_sub_frame_view);
|
||||
|
||||
blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &block);
|
||||
VerifyBlock(block_counter++, 0, block);
|
||||
|
||||
if ((sub_frame_index + 1) % 4 == 0) {
|
||||
EXPECT_TRUE(blocker.IsBlockAvailable());
|
||||
} else {
|
||||
EXPECT_FALSE(blocker.IsBlockAvailable());
|
||||
}
|
||||
if (blocker.IsBlockAvailable()) {
|
||||
blocker.ExtractBlock(&block);
|
||||
VerifyBlock(block_counter++, 0, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the FrameBlocker and BlockFramer work well together and produce
|
||||
// the expected output.
|
||||
void RunBlockerAndFramerTest(int sample_rate_hz) {
|
||||
const size_t kNumSubFramesToProcess = 20;
|
||||
const size_t num_bands = NumBandsForRate(sample_rate_hz);
|
||||
|
||||
std::vector<std::vector<float>> block(num_bands,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> input_sub_frame(
|
||||
num_bands, std::vector<float>(kSubFrameLength, 0.f));
|
||||
std::vector<std::vector<float>> output_sub_frame(
|
||||
num_bands, std::vector<float>(kSubFrameLength, 0.f));
|
||||
std::vector<rtc::ArrayView<float>> output_sub_frame_view(num_bands);
|
||||
std::vector<rtc::ArrayView<float>> input_sub_frame_view(num_bands);
|
||||
FrameBlocker blocker(num_bands);
|
||||
BlockFramer framer(num_bands);
|
||||
|
||||
for (size_t sub_frame_index = 0; sub_frame_index < kNumSubFramesToProcess;
|
||||
++sub_frame_index) {
|
||||
FillSubFrameView(sub_frame_index, 0, &input_sub_frame,
|
||||
&input_sub_frame_view);
|
||||
FillSubFrameView(sub_frame_index, 0, &output_sub_frame,
|
||||
&output_sub_frame_view);
|
||||
|
||||
blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &block);
|
||||
framer.InsertBlockAndExtractSubFrame(block, &output_sub_frame_view);
|
||||
|
||||
if ((sub_frame_index + 1) % 4 == 0) {
|
||||
EXPECT_TRUE(blocker.IsBlockAvailable());
|
||||
} else {
|
||||
EXPECT_FALSE(blocker.IsBlockAvailable());
|
||||
}
|
||||
if (blocker.IsBlockAvailable()) {
|
||||
blocker.ExtractBlock(&block);
|
||||
framer.InsertBlock(block);
|
||||
}
|
||||
EXPECT_TRUE(VerifySubFrame(sub_frame_index, -64, output_sub_frame_view));
|
||||
}
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
// Verifies that the FrameBlocker crashes if the InsertSubFrameAndExtractBlock
|
||||
// method is called for inputs with the wrong number of bands or band lengths.
|
||||
void RunWronglySizedInsertAndExtractParametersTest(int sample_rate_hz,
|
||||
size_t num_block_bands,
|
||||
size_t block_length,
|
||||
size_t num_sub_frame_bands,
|
||||
size_t sub_frame_length) {
|
||||
const size_t correct_num_bands = NumBandsForRate(sample_rate_hz);
|
||||
|
||||
std::vector<std::vector<float>> block(num_block_bands,
|
||||
std::vector<float>(block_length, 0.f));
|
||||
std::vector<std::vector<float>> input_sub_frame(
|
||||
num_sub_frame_bands, std::vector<float>(sub_frame_length, 0.f));
|
||||
std::vector<rtc::ArrayView<float>> input_sub_frame_view(
|
||||
input_sub_frame.size());
|
||||
FillSubFrameView(0, 0, &input_sub_frame, &input_sub_frame_view);
|
||||
FrameBlocker blocker(correct_num_bands);
|
||||
EXPECT_DEATH(
|
||||
blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &block), "");
|
||||
}
|
||||
|
||||
// Verifies that the FrameBlocker crashes if the ExtractBlock method is called
|
||||
// for inputs with the wrong number of bands or band lengths.
|
||||
void RunWronglySizedExtractParameterTest(int sample_rate_hz,
|
||||
size_t num_block_bands,
|
||||
size_t block_length) {
|
||||
const size_t correct_num_bands = NumBandsForRate(sample_rate_hz);
|
||||
|
||||
std::vector<std::vector<float>> correct_block(
|
||||
correct_num_bands, std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> wrong_block(
|
||||
num_block_bands, std::vector<float>(block_length, 0.f));
|
||||
std::vector<std::vector<float>> input_sub_frame(
|
||||
correct_num_bands, std::vector<float>(kSubFrameLength, 0.f));
|
||||
std::vector<rtc::ArrayView<float>> input_sub_frame_view(
|
||||
input_sub_frame.size());
|
||||
FillSubFrameView(0, 0, &input_sub_frame, &input_sub_frame_view);
|
||||
FrameBlocker blocker(correct_num_bands);
|
||||
blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &correct_block);
|
||||
blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &correct_block);
|
||||
blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &correct_block);
|
||||
blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &correct_block);
|
||||
|
||||
EXPECT_DEATH(blocker.ExtractBlock(&wrong_block), "");
|
||||
}
|
||||
|
||||
// Verifies that the FrameBlocker crashes if the ExtractBlock method is called
|
||||
// after a wrong number of previous InsertSubFrameAndExtractBlock method calls
|
||||
// have been made.
|
||||
void RunWrongExtractOrderTest(int sample_rate_hz,
|
||||
size_t num_preceeding_api_calls) {
|
||||
const size_t correct_num_bands = NumBandsForRate(sample_rate_hz);
|
||||
|
||||
std::vector<std::vector<float>> block(correct_num_bands,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> input_sub_frame(
|
||||
correct_num_bands, std::vector<float>(kSubFrameLength, 0.f));
|
||||
std::vector<rtc::ArrayView<float>> input_sub_frame_view(
|
||||
input_sub_frame.size());
|
||||
FillSubFrameView(0, 0, &input_sub_frame, &input_sub_frame_view);
|
||||
FrameBlocker blocker(correct_num_bands);
|
||||
for (size_t k = 0; k < num_preceeding_api_calls; ++k) {
|
||||
blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &block);
|
||||
}
|
||||
|
||||
EXPECT_DEATH(blocker.ExtractBlock(&block), "");
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string ProduceDebugText(int sample_rate_hz) {
|
||||
std::ostringstream ss;
|
||||
ss << "Sample rate: " << sample_rate_hz;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
TEST(FrameBlocker, WrongNumberOfBandsInBlockForInsertSubFrameAndExtractBlock) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
const size_t correct_num_bands = NumBandsForRate(rate);
|
||||
const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
|
||||
RunWronglySizedInsertAndExtractParametersTest(
|
||||
rate, wrong_num_bands, kBlockSize, correct_num_bands, kSubFrameLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FrameBlocker,
|
||||
WrongNumberOfBandsInSubFrameForInsertSubFrameAndExtractBlock) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
const size_t correct_num_bands = NumBandsForRate(rate);
|
||||
const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
|
||||
RunWronglySizedInsertAndExtractParametersTest(
|
||||
rate, correct_num_bands, kBlockSize, wrong_num_bands, kSubFrameLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FrameBlocker,
|
||||
WrongNumberOfSamplesInBlockForInsertSubFrameAndExtractBlock) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
const size_t correct_num_bands = NumBandsForRate(rate);
|
||||
RunWronglySizedInsertAndExtractParametersTest(
|
||||
rate, correct_num_bands, kBlockSize - 1, correct_num_bands,
|
||||
kSubFrameLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FrameBlocker,
|
||||
WrongNumberOfSamplesInSubFrameForInsertSubFrameAndExtractBlock) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
const size_t correct_num_bands = NumBandsForRate(rate);
|
||||
RunWronglySizedInsertAndExtractParametersTest(rate, correct_num_bands,
|
||||
kBlockSize, correct_num_bands,
|
||||
kSubFrameLength - 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FrameBlocker, WrongNumberOfBandsInBlockForExtractBlock) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
const size_t correct_num_bands = NumBandsForRate(rate);
|
||||
const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
|
||||
RunWronglySizedExtractParameterTest(rate, wrong_num_bands, kBlockSize);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FrameBlocker, WrongNumberOfSamplesInBlockForExtractBlock) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
const size_t correct_num_bands = NumBandsForRate(rate);
|
||||
RunWronglySizedExtractParameterTest(rate, correct_num_bands,
|
||||
kBlockSize - 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FrameBlocker, WrongNumberOfPreceedingApiCallsForExtractBlock) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
for (size_t num_calls = 0; num_calls < 4; ++num_calls) {
|
||||
std::ostringstream ss;
|
||||
ss << "Sample rate: " << rate;
|
||||
ss << ", Num preceeding InsertSubFrameAndExtractBlock calls: "
|
||||
<< num_calls;
|
||||
|
||||
SCOPED_TRACE(ss.str());
|
||||
RunWrongExtractOrderTest(rate, num_calls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifiers that the verification for null sub_frame pointer works.
|
||||
TEST(FrameBlocker, NullBlockParameter) {
|
||||
std::vector<std::vector<float>> sub_frame(
|
||||
1, std::vector<float>(kSubFrameLength, 0.f));
|
||||
std::vector<rtc::ArrayView<float>> sub_frame_view(sub_frame.size());
|
||||
FillSubFrameView(0, 0, &sub_frame, &sub_frame_view);
|
||||
EXPECT_DEATH(
|
||||
FrameBlocker(1).InsertSubFrameAndExtractBlock(sub_frame_view, nullptr),
|
||||
"");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST(FrameBlocker, BlockBitexactness) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
RunBlockerTest(rate);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FrameBlocker, BlockerAndFramer) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
RunBlockerAndFramerTest(rate);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/main_filter_update_gain.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/rtc_base/atomicops.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
constexpr float kHErrorInitial = 10000.f;
|
||||
constexpr int kPoorExcitationCounterInitial = 1000;
|
||||
|
||||
} // namespace
|
||||
|
||||
int MainFilterUpdateGain::instance_count_ = 0;
|
||||
|
||||
MainFilterUpdateGain::MainFilterUpdateGain()
|
||||
: data_dumper_(
|
||||
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
||||
poor_excitation_counter_(kPoorExcitationCounterInitial) {
|
||||
H_error_.fill(kHErrorInitial);
|
||||
}
|
||||
|
||||
MainFilterUpdateGain::~MainFilterUpdateGain() {}
|
||||
|
||||
void MainFilterUpdateGain::HandleEchoPathChange() {
|
||||
H_error_.fill(kHErrorInitial);
|
||||
poor_excitation_counter_ = kPoorExcitationCounterInitial;
|
||||
call_counter_ = 0;
|
||||
}
|
||||
|
||||
void MainFilterUpdateGain::Compute(
|
||||
const RenderBuffer& render_buffer,
|
||||
const RenderSignalAnalyzer& render_signal_analyzer,
|
||||
const SubtractorOutput& subtractor_output,
|
||||
const AdaptiveFirFilter& filter,
|
||||
bool saturated_capture_signal,
|
||||
FftData* gain_fft) {
|
||||
RTC_DCHECK(gain_fft);
|
||||
// Introducing shorter notation to improve readability.
|
||||
const FftData& E_main = subtractor_output.E_main;
|
||||
const auto& E2_main = subtractor_output.E2_main;
|
||||
const auto& E2_shadow = subtractor_output.E2_shadow;
|
||||
FftData* G = gain_fft;
|
||||
const size_t size_partitions = filter.SizePartitions();
|
||||
const auto& X2 = render_buffer.SpectralSum(size_partitions);
|
||||
const auto& erl = filter.Erl();
|
||||
|
||||
++call_counter_;
|
||||
|
||||
if (render_signal_analyzer.PoorSignalExcitation()) {
|
||||
poor_excitation_counter_ = 0;
|
||||
}
|
||||
|
||||
// Do not update the filter if the render is not sufficiently excited.
|
||||
if (++poor_excitation_counter_ < size_partitions ||
|
||||
saturated_capture_signal || call_counter_ <= size_partitions) {
|
||||
G->re.fill(0.f);
|
||||
G->im.fill(0.f);
|
||||
} else {
|
||||
// Corresponds to WGN of power -39 dBFS.
|
||||
constexpr float kNoiseGatePower = 220075344.f;
|
||||
std::array<float, kFftLengthBy2Plus1> mu;
|
||||
// mu = H_error / (0.5* H_error* X2 + n * E2).
|
||||
for (size_t k = 0; k < kFftLengthBy2Plus1; ++k) {
|
||||
mu[k] = X2[k] > kNoiseGatePower
|
||||
? H_error_[k] / (0.5f * H_error_[k] * X2[k] +
|
||||
size_partitions * E2_main[k])
|
||||
: 0.f;
|
||||
}
|
||||
|
||||
// Avoid updating the filter close to narrow bands in the render signals.
|
||||
render_signal_analyzer.MaskRegionsAroundNarrowBands(&mu);
|
||||
|
||||
// H_error = H_error - 0.5 * mu * X2 * H_error.
|
||||
for (size_t k = 0; k < H_error_.size(); ++k) {
|
||||
H_error_[k] -= 0.5f * mu[k] * X2[k] * H_error_[k];
|
||||
}
|
||||
|
||||
// G = mu * E.
|
||||
std::transform(mu.begin(), mu.end(), E_main.re.begin(), G->re.begin(),
|
||||
std::multiplies<float>());
|
||||
std::transform(mu.begin(), mu.end(), E_main.im.begin(), G->im.begin(),
|
||||
std::multiplies<float>());
|
||||
}
|
||||
|
||||
// H_error = H_error + factor * erl.
|
||||
std::array<float, kFftLengthBy2Plus1> H_error_increase;
|
||||
constexpr float kErlScaleAccurate = 1.f / 100.0f;
|
||||
constexpr float kErlScaleInaccurate = 1.f / 60.0f;
|
||||
std::transform(E2_shadow.begin(), E2_shadow.end(), E2_main.begin(),
|
||||
H_error_increase.begin(), [&](float a, float b) {
|
||||
return a >= b ? kErlScaleAccurate : kErlScaleInaccurate;
|
||||
});
|
||||
std::transform(erl.begin(), erl.end(), H_error_increase.begin(),
|
||||
H_error_increase.begin(), std::multiplies<float>());
|
||||
std::transform(H_error_.begin(), H_error_.end(), H_error_increase.begin(),
|
||||
H_error_.begin(),
|
||||
[&](float a, float b) { return std::max(a + b, 0.1f); });
|
||||
|
||||
data_dumper_->DumpRaw("aec3_main_gain_H_error", H_error_);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MAIN_FILTER_UPDATE_GAIN_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MAIN_FILTER_UPDATE_GAIN_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/adaptive_fir_filter.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_signal_analyzer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/subtractor_output.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class ApmDataDumper;
|
||||
|
||||
// Provides functionality for computing the adaptive gain for the main filter.
|
||||
class MainFilterUpdateGain {
|
||||
public:
|
||||
MainFilterUpdateGain();
|
||||
~MainFilterUpdateGain();
|
||||
|
||||
// Takes action in the case of a known echo path change.
|
||||
void HandleEchoPathChange();
|
||||
|
||||
// Computes the gain.
|
||||
void Compute(const RenderBuffer& render_buffer,
|
||||
const RenderSignalAnalyzer& render_signal_analyzer,
|
||||
const SubtractorOutput& subtractor_output,
|
||||
const AdaptiveFirFilter& filter,
|
||||
bool saturated_capture_signal,
|
||||
FftData* gain_fft);
|
||||
|
||||
private:
|
||||
static int instance_count_;
|
||||
std::unique_ptr<ApmDataDumper> data_dumper_;
|
||||
std::array<float, kFftLengthBy2Plus1> H_error_;
|
||||
size_t poor_excitation_counter_;
|
||||
size_t call_counter_ = 0;
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(MainFilterUpdateGain);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MAIN_FILTER_UPDATE_GAIN_H_
|
||||
@ -1,292 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/main_filter_update_gain.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/adaptive_fir_filter.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec_state.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_signal_analyzer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/shadow_filter_update_gain.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/subtractor_output.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/modules/audio_processing/test/echo_canceller_test_tools.h"
|
||||
#include "webrtc/rtc_base/random.h"
|
||||
#include "webrtc/rtc_base/safe_minmax.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
// Method for performing the simulations needed to test the main filter update
|
||||
// gain functionality.
|
||||
void RunFilterUpdateTest(int num_blocks_to_process,
|
||||
size_t delay_samples,
|
||||
const std::vector<int>& blocks_with_echo_path_changes,
|
||||
const std::vector<int>& blocks_with_saturation,
|
||||
bool use_silent_render_in_second_half,
|
||||
std::array<float, kBlockSize>* e_last_block,
|
||||
std::array<float, kBlockSize>* y_last_block,
|
||||
FftData* G_last_block) {
|
||||
ApmDataDumper data_dumper(42);
|
||||
AdaptiveFirFilter main_filter(9, DetectOptimization(), &data_dumper);
|
||||
AdaptiveFirFilter shadow_filter(9, DetectOptimization(), &data_dumper);
|
||||
Aec3Fft fft;
|
||||
RenderBuffer render_buffer(
|
||||
Aec3Optimization::kNone, 3, main_filter.SizePartitions(),
|
||||
std::vector<size_t>(1, main_filter.SizePartitions()));
|
||||
std::array<float, kBlockSize> x_old;
|
||||
x_old.fill(0.f);
|
||||
ShadowFilterUpdateGain shadow_gain;
|
||||
MainFilterUpdateGain main_gain;
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<float> y(kBlockSize, 0.f);
|
||||
AecState aec_state(AudioProcessing::Config::EchoCanceller3{});
|
||||
RenderSignalAnalyzer render_signal_analyzer;
|
||||
std::array<float, kFftLength> s_scratch;
|
||||
std::array<float, kBlockSize> s;
|
||||
FftData S;
|
||||
FftData G;
|
||||
SubtractorOutput output;
|
||||
output.Reset();
|
||||
FftData& E_main = output.E_main;
|
||||
FftData E_shadow;
|
||||
std::array<float, kFftLengthBy2Plus1> Y2;
|
||||
std::array<float, kFftLengthBy2Plus1>& E2_main = output.E2_main;
|
||||
std::array<float, kBlockSize>& e_main = output.e_main;
|
||||
std::array<float, kBlockSize>& e_shadow = output.e_shadow;
|
||||
Y2.fill(0.f);
|
||||
|
||||
constexpr float kScale = 1.0f / kFftLengthBy2;
|
||||
|
||||
DelayBuffer<float> delay_buffer(delay_samples);
|
||||
for (int k = 0; k < num_blocks_to_process; ++k) {
|
||||
// Handle echo path changes.
|
||||
if (std::find(blocks_with_echo_path_changes.begin(),
|
||||
blocks_with_echo_path_changes.end(),
|
||||
k) != blocks_with_echo_path_changes.end()) {
|
||||
main_filter.HandleEchoPathChange();
|
||||
}
|
||||
|
||||
// Handle saturation.
|
||||
const bool saturation =
|
||||
std::find(blocks_with_saturation.begin(), blocks_with_saturation.end(),
|
||||
k) != blocks_with_saturation.end();
|
||||
|
||||
// Create the render signal.
|
||||
if (use_silent_render_in_second_half && k > num_blocks_to_process / 2) {
|
||||
std::fill(x[0].begin(), x[0].end(), 0.f);
|
||||
} else {
|
||||
RandomizeSampleVector(&random_generator, x[0]);
|
||||
}
|
||||
delay_buffer.Delay(x[0], y);
|
||||
render_buffer.Insert(x);
|
||||
render_signal_analyzer.Update(render_buffer, aec_state.FilterDelay());
|
||||
|
||||
// Apply the main filter.
|
||||
main_filter.Filter(render_buffer, &S);
|
||||
fft.Ifft(S, &s_scratch);
|
||||
std::transform(y.begin(), y.end(), s_scratch.begin() + kFftLengthBy2,
|
||||
e_main.begin(),
|
||||
[&](float a, float b) { return a - b * kScale; });
|
||||
std::for_each(e_main.begin(), e_main.end(),
|
||||
[](float& a) { a = rtc::SafeClamp(a, -32768.f, 32767.f); });
|
||||
fft.ZeroPaddedFft(e_main, &E_main);
|
||||
for (size_t k = 0; k < kBlockSize; ++k) {
|
||||
s[k] = kScale * s_scratch[k + kFftLengthBy2];
|
||||
}
|
||||
|
||||
// Apply the shadow filter.
|
||||
shadow_filter.Filter(render_buffer, &S);
|
||||
fft.Ifft(S, &s_scratch);
|
||||
std::transform(y.begin(), y.end(), s_scratch.begin() + kFftLengthBy2,
|
||||
e_shadow.begin(),
|
||||
[&](float a, float b) { return a - b * kScale; });
|
||||
std::for_each(e_shadow.begin(), e_shadow.end(),
|
||||
[](float& a) { a = rtc::SafeClamp(a, -32768.f, 32767.f); });
|
||||
fft.ZeroPaddedFft(e_shadow, &E_shadow);
|
||||
|
||||
// Compute spectra for future use.
|
||||
E_main.Spectrum(Aec3Optimization::kNone, &output.E2_main);
|
||||
E_shadow.Spectrum(Aec3Optimization::kNone, &output.E2_shadow);
|
||||
|
||||
// Adapt the shadow filter.
|
||||
shadow_gain.Compute(render_buffer, render_signal_analyzer, E_shadow,
|
||||
shadow_filter.SizePartitions(), saturation, &G);
|
||||
shadow_filter.Adapt(render_buffer, G);
|
||||
|
||||
// Adapt the main filter
|
||||
main_gain.Compute(render_buffer, render_signal_analyzer, output,
|
||||
main_filter, saturation, &G);
|
||||
main_filter.Adapt(render_buffer, G);
|
||||
|
||||
// Update the delay.
|
||||
aec_state.HandleEchoPathChange(EchoPathVariability(false, false));
|
||||
aec_state.Update(main_filter.FilterFrequencyResponse(),
|
||||
main_filter.FilterImpulseResponse(),
|
||||
rtc::Optional<size_t>(), render_buffer, E2_main, Y2, x[0],
|
||||
s, false);
|
||||
}
|
||||
|
||||
std::copy(e_main.begin(), e_main.end(), e_last_block->begin());
|
||||
std::copy(y.begin(), y.end(), y_last_block->begin());
|
||||
std::copy(G.re.begin(), G.re.end(), G_last_block->re.begin());
|
||||
std::copy(G.im.begin(), G.im.end(), G_last_block->im.begin());
|
||||
}
|
||||
|
||||
std::string ProduceDebugText(size_t delay) {
|
||||
std::ostringstream ss;
|
||||
ss << "Delay: " << delay;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
|
||||
// Verifies that the check for non-null output gain parameter works.
|
||||
TEST(MainFilterUpdateGain, NullDataOutputGain) {
|
||||
ApmDataDumper data_dumper(42);
|
||||
AdaptiveFirFilter filter(9, DetectOptimization(), &data_dumper);
|
||||
RenderBuffer render_buffer(Aec3Optimization::kNone, 3,
|
||||
filter.SizePartitions(),
|
||||
std::vector<size_t>(1, filter.SizePartitions()));
|
||||
RenderSignalAnalyzer analyzer;
|
||||
SubtractorOutput output;
|
||||
MainFilterUpdateGain gain;
|
||||
EXPECT_DEATH(
|
||||
gain.Compute(render_buffer, analyzer, output, filter, false, nullptr),
|
||||
"");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Verifies that the gain formed causes the filter using it to converge.
|
||||
TEST(MainFilterUpdateGain, GainCausesFilterToConverge) {
|
||||
std::vector<int> blocks_with_echo_path_changes;
|
||||
std::vector<int> blocks_with_saturation;
|
||||
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples));
|
||||
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G;
|
||||
|
||||
RunFilterUpdateTest(500, delay_samples, blocks_with_echo_path_changes,
|
||||
blocks_with_saturation, false, &e, &y, &G);
|
||||
|
||||
// Verify that the main filter is able to perform well.
|
||||
EXPECT_LT(1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f),
|
||||
std::inner_product(y.begin(), y.end(), y.begin(), 0.f));
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the magnitude of the gain on average decreases for a
|
||||
// persistently exciting signal.
|
||||
TEST(MainFilterUpdateGain, DecreasingGain) {
|
||||
std::vector<int> blocks_with_echo_path_changes;
|
||||
std::vector<int> blocks_with_saturation;
|
||||
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G_a;
|
||||
FftData G_b;
|
||||
FftData G_c;
|
||||
std::array<float, kFftLengthBy2Plus1> G_a_power;
|
||||
std::array<float, kFftLengthBy2Plus1> G_b_power;
|
||||
std::array<float, kFftLengthBy2Plus1> G_c_power;
|
||||
|
||||
RunFilterUpdateTest(100, 65, blocks_with_echo_path_changes,
|
||||
blocks_with_saturation, false, &e, &y, &G_a);
|
||||
RunFilterUpdateTest(200, 65, blocks_with_echo_path_changes,
|
||||
blocks_with_saturation, false, &e, &y, &G_b);
|
||||
RunFilterUpdateTest(300, 65, blocks_with_echo_path_changes,
|
||||
blocks_with_saturation, false, &e, &y, &G_c);
|
||||
|
||||
G_a.Spectrum(Aec3Optimization::kNone, &G_a_power);
|
||||
G_b.Spectrum(Aec3Optimization::kNone, &G_b_power);
|
||||
G_c.Spectrum(Aec3Optimization::kNone, &G_c_power);
|
||||
|
||||
EXPECT_GT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
|
||||
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
||||
|
||||
EXPECT_GT(std::accumulate(G_b_power.begin(), G_b_power.end(), 0.),
|
||||
std::accumulate(G_c_power.begin(), G_c_power.end(), 0.));
|
||||
}
|
||||
|
||||
// Verifies that the gain is zero when there is saturation and that the internal
|
||||
// error estimates cause the gain to increase after a period of saturation.
|
||||
TEST(MainFilterUpdateGain, SaturationBehavior) {
|
||||
std::vector<int> blocks_with_echo_path_changes;
|
||||
std::vector<int> blocks_with_saturation;
|
||||
for (int k = 99; k < 200; ++k) {
|
||||
blocks_with_saturation.push_back(k);
|
||||
}
|
||||
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G_a;
|
||||
FftData G_b;
|
||||
FftData G_a_ref;
|
||||
G_a_ref.re.fill(0.f);
|
||||
G_a_ref.im.fill(0.f);
|
||||
|
||||
std::array<float, kFftLengthBy2Plus1> G_a_power;
|
||||
std::array<float, kFftLengthBy2Plus1> G_b_power;
|
||||
|
||||
RunFilterUpdateTest(100, 65, blocks_with_echo_path_changes,
|
||||
blocks_with_saturation, false, &e, &y, &G_a);
|
||||
|
||||
EXPECT_EQ(G_a_ref.re, G_a.re);
|
||||
EXPECT_EQ(G_a_ref.im, G_a.im);
|
||||
|
||||
RunFilterUpdateTest(99, 65, blocks_with_echo_path_changes,
|
||||
blocks_with_saturation, false, &e, &y, &G_a);
|
||||
RunFilterUpdateTest(201, 65, blocks_with_echo_path_changes,
|
||||
blocks_with_saturation, false, &e, &y, &G_b);
|
||||
|
||||
G_a.Spectrum(Aec3Optimization::kNone, &G_a_power);
|
||||
G_b.Spectrum(Aec3Optimization::kNone, &G_b_power);
|
||||
|
||||
EXPECT_LT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
|
||||
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
||||
}
|
||||
|
||||
// Verifies that the gain increases after an echo path change.
|
||||
TEST(MainFilterUpdateGain, EchoPathChangeBehavior) {
|
||||
std::vector<int> blocks_with_echo_path_changes;
|
||||
std::vector<int> blocks_with_saturation;
|
||||
blocks_with_echo_path_changes.push_back(99);
|
||||
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G_a;
|
||||
FftData G_b;
|
||||
std::array<float, kFftLengthBy2Plus1> G_a_power;
|
||||
std::array<float, kFftLengthBy2Plus1> G_b_power;
|
||||
|
||||
RunFilterUpdateTest(99, 65, blocks_with_echo_path_changes,
|
||||
blocks_with_saturation, false, &e, &y, &G_a);
|
||||
RunFilterUpdateTest(100, 65, blocks_with_echo_path_changes,
|
||||
blocks_with_saturation, false, &e, &y, &G_b);
|
||||
|
||||
G_a.Spectrum(Aec3Optimization::kNone, &G_a_power);
|
||||
G_b.Spectrum(Aec3Optimization::kNone, &G_b_power);
|
||||
|
||||
EXPECT_LT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
|
||||
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,401 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/matched_filter.h"
|
||||
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
#include "webrtc/typedefs.h"
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace aec3 {
|
||||
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
|
||||
void MatchedFilterCore_NEON(size_t x_start_index,
|
||||
float x2_sum_threshold,
|
||||
rtc::ArrayView<const float> x,
|
||||
rtc::ArrayView<const float> y,
|
||||
rtc::ArrayView<float> h,
|
||||
bool* filters_updated,
|
||||
float* error_sum) {
|
||||
const int h_size = static_cast<int>(h.size());
|
||||
const int x_size = static_cast<int>(x.size());
|
||||
RTC_DCHECK_EQ(0, h_size % 4);
|
||||
|
||||
// Process for all samples in the sub-block.
|
||||
for (size_t i = 0; i < kSubBlockSize; ++i) {
|
||||
// Apply the matched filter as filter * x, and compute x * x.
|
||||
|
||||
RTC_DCHECK_GT(x_size, x_start_index);
|
||||
const float* x_p = &x[x_start_index];
|
||||
const float* h_p = &h[0];
|
||||
|
||||
// Initialize values for the accumulation.
|
||||
float32x4_t s_128 = vdupq_n_f32(0);
|
||||
float32x4_t x2_sum_128 = vdupq_n_f32(0);
|
||||
float x2_sum = 0.f;
|
||||
float s = 0;
|
||||
|
||||
// Compute loop chunk sizes until, and after, the wraparound of the circular
|
||||
// buffer for x.
|
||||
const int chunk1 =
|
||||
std::min(h_size, static_cast<int>(x_size - x_start_index));
|
||||
|
||||
// Perform the loop in two chunks.
|
||||
const int chunk2 = h_size - chunk1;
|
||||
for (int limit : {chunk1, chunk2}) {
|
||||
// Perform 128 bit vector operations.
|
||||
const int limit_by_4 = limit >> 2;
|
||||
for (int k = limit_by_4; k > 0; --k, h_p += 4, x_p += 4) {
|
||||
// Load the data into 128 bit vectors.
|
||||
const float32x4_t x_k = vld1q_f32(x_p);
|
||||
const float32x4_t h_k = vld1q_f32(h_p);
|
||||
// Compute and accumulate x * x and h * x.
|
||||
x2_sum_128 = vmlaq_f32(x2_sum_128, x_k, x_k);
|
||||
s_128 = vmlaq_f32(s_128, h_k, x_k);
|
||||
}
|
||||
|
||||
// Perform non-vector operations for any remaining items.
|
||||
for (int k = limit - limit_by_4 * 4; k > 0; --k, ++h_p, ++x_p) {
|
||||
const float x_k = *x_p;
|
||||
x2_sum += x_k * x_k;
|
||||
s += *h_p * x_k;
|
||||
}
|
||||
|
||||
x_p = &x[0];
|
||||
}
|
||||
|
||||
// Combine the accumulated vector and scalar values.
|
||||
float* v = reinterpret_cast<float*>(&x2_sum_128);
|
||||
x2_sum += v[0] + v[1] + v[2] + v[3];
|
||||
v = reinterpret_cast<float*>(&s_128);
|
||||
s += v[0] + v[1] + v[2] + v[3];
|
||||
|
||||
// Compute the matched filter error.
|
||||
const float e = std::min(32767.f, std::max(-32768.f, y[i] - s));
|
||||
*error_sum += e * e;
|
||||
|
||||
// Update the matched filter estimate in an NLMS manner.
|
||||
if (x2_sum > x2_sum_threshold) {
|
||||
RTC_DCHECK_LT(0.f, x2_sum);
|
||||
const float alpha = 0.7f * e / x2_sum;
|
||||
const float32x4_t alpha_128 = vmovq_n_f32(alpha);
|
||||
|
||||
// filter = filter + 0.7 * (y - filter * x) / x * x.
|
||||
float* h_p = &h[0];
|
||||
x_p = &x[x_start_index];
|
||||
|
||||
// Perform the loop in two chunks.
|
||||
for (int limit : {chunk1, chunk2}) {
|
||||
// Perform 128 bit vector operations.
|
||||
const int limit_by_4 = limit >> 2;
|
||||
for (int k = limit_by_4; k > 0; --k, h_p += 4, x_p += 4) {
|
||||
// Load the data into 128 bit vectors.
|
||||
float32x4_t h_k = vld1q_f32(h_p);
|
||||
const float32x4_t x_k = vld1q_f32(x_p);
|
||||
// Compute h = h + alpha * x.
|
||||
h_k = vmlaq_f32(h_k, alpha_128, x_k);
|
||||
|
||||
// Store the result.
|
||||
vst1q_f32(h_p, h_k);
|
||||
}
|
||||
|
||||
// Perform non-vector operations for any remaining items.
|
||||
for (int k = limit - limit_by_4 * 4; k > 0; --k, ++h_p, ++x_p) {
|
||||
*h_p += alpha * *x_p;
|
||||
}
|
||||
|
||||
x_p = &x[0];
|
||||
}
|
||||
|
||||
*filters_updated = true;
|
||||
}
|
||||
|
||||
x_start_index = x_start_index > 0 ? x_start_index - 1 : x_size - 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
|
||||
void MatchedFilterCore_SSE2(size_t x_start_index,
|
||||
float x2_sum_threshold,
|
||||
rtc::ArrayView<const float> x,
|
||||
rtc::ArrayView<const float> y,
|
||||
rtc::ArrayView<float> h,
|
||||
bool* filters_updated,
|
||||
float* error_sum) {
|
||||
const int h_size = static_cast<int>(h.size());
|
||||
const int x_size = static_cast<int>(x.size());
|
||||
RTC_DCHECK_EQ(0, h_size % 4);
|
||||
|
||||
// Process for all samples in the sub-block.
|
||||
for (size_t i = 0; i < kSubBlockSize; ++i) {
|
||||
// Apply the matched filter as filter * x, and compute x * x.
|
||||
|
||||
RTC_DCHECK_GT(x_size, x_start_index);
|
||||
const float* x_p = &x[x_start_index];
|
||||
const float* h_p = &h[0];
|
||||
|
||||
// Initialize values for the accumulation.
|
||||
__m128 s_128 = _mm_set1_ps(0);
|
||||
__m128 x2_sum_128 = _mm_set1_ps(0);
|
||||
float x2_sum = 0.f;
|
||||
float s = 0;
|
||||
|
||||
// Compute loop chunk sizes until, and after, the wraparound of the circular
|
||||
// buffer for x.
|
||||
const int chunk1 =
|
||||
std::min(h_size, static_cast<int>(x_size - x_start_index));
|
||||
|
||||
// Perform the loop in two chunks.
|
||||
const int chunk2 = h_size - chunk1;
|
||||
for (int limit : {chunk1, chunk2}) {
|
||||
// Perform 128 bit vector operations.
|
||||
const int limit_by_4 = limit >> 2;
|
||||
for (int k = limit_by_4; k > 0; --k, h_p += 4, x_p += 4) {
|
||||
// Load the data into 128 bit vectors.
|
||||
const __m128 x_k = _mm_loadu_ps(x_p);
|
||||
const __m128 h_k = _mm_loadu_ps(h_p);
|
||||
const __m128 xx = _mm_mul_ps(x_k, x_k);
|
||||
// Compute and accumulate x * x and h * x.
|
||||
x2_sum_128 = _mm_add_ps(x2_sum_128, xx);
|
||||
const __m128 hx = _mm_mul_ps(h_k, x_k);
|
||||
s_128 = _mm_add_ps(s_128, hx);
|
||||
}
|
||||
|
||||
// Perform non-vector operations for any remaining items.
|
||||
for (int k = limit - limit_by_4 * 4; k > 0; --k, ++h_p, ++x_p) {
|
||||
const float x_k = *x_p;
|
||||
x2_sum += x_k * x_k;
|
||||
s += *h_p * x_k;
|
||||
}
|
||||
|
||||
x_p = &x[0];
|
||||
}
|
||||
|
||||
// Combine the accumulated vector and scalar values.
|
||||
float* v = reinterpret_cast<float*>(&x2_sum_128);
|
||||
x2_sum += v[0] + v[1] + v[2] + v[3];
|
||||
v = reinterpret_cast<float*>(&s_128);
|
||||
s += v[0] + v[1] + v[2] + v[3];
|
||||
|
||||
// Compute the matched filter error.
|
||||
const float e = std::min(32767.f, std::max(-32768.f, y[i] - s));
|
||||
*error_sum += e * e;
|
||||
|
||||
// Update the matched filter estimate in an NLMS manner.
|
||||
if (x2_sum > x2_sum_threshold) {
|
||||
RTC_DCHECK_LT(0.f, x2_sum);
|
||||
const float alpha = 0.7f * e / x2_sum;
|
||||
const __m128 alpha_128 = _mm_set1_ps(alpha);
|
||||
|
||||
// filter = filter + 0.7 * (y - filter * x) / x * x.
|
||||
float* h_p = &h[0];
|
||||
x_p = &x[x_start_index];
|
||||
|
||||
// Perform the loop in two chunks.
|
||||
for (int limit : {chunk1, chunk2}) {
|
||||
// Perform 128 bit vector operations.
|
||||
const int limit_by_4 = limit >> 2;
|
||||
for (int k = limit_by_4; k > 0; --k, h_p += 4, x_p += 4) {
|
||||
// Load the data into 128 bit vectors.
|
||||
__m128 h_k = _mm_loadu_ps(h_p);
|
||||
const __m128 x_k = _mm_loadu_ps(x_p);
|
||||
|
||||
// Compute h = h + alpha * x.
|
||||
const __m128 alpha_x = _mm_mul_ps(alpha_128, x_k);
|
||||
h_k = _mm_add_ps(h_k, alpha_x);
|
||||
|
||||
// Store the result.
|
||||
_mm_storeu_ps(h_p, h_k);
|
||||
}
|
||||
|
||||
// Perform non-vector operations for any remaining items.
|
||||
for (int k = limit - limit_by_4 * 4; k > 0; --k, ++h_p, ++x_p) {
|
||||
*h_p += alpha * *x_p;
|
||||
}
|
||||
|
||||
x_p = &x[0];
|
||||
}
|
||||
|
||||
*filters_updated = true;
|
||||
}
|
||||
|
||||
x_start_index = x_start_index > 0 ? x_start_index - 1 : x_size - 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void MatchedFilterCore(size_t x_start_index,
|
||||
float x2_sum_threshold,
|
||||
rtc::ArrayView<const float> x,
|
||||
rtc::ArrayView<const float> y,
|
||||
rtc::ArrayView<float> h,
|
||||
bool* filters_updated,
|
||||
float* error_sum) {
|
||||
// Process for all samples in the sub-block.
|
||||
for (size_t i = 0; i < kSubBlockSize; ++i) {
|
||||
// Apply the matched filter as filter * x, and compute x * x.
|
||||
float x2_sum = 0.f;
|
||||
float s = 0;
|
||||
size_t x_index = x_start_index;
|
||||
for (size_t k = 0; k < h.size(); ++k) {
|
||||
x2_sum += x[x_index] * x[x_index];
|
||||
s += h[k] * x[x_index];
|
||||
x_index = x_index < (x.size() - 1) ? x_index + 1 : 0;
|
||||
}
|
||||
|
||||
// Compute the matched filter error.
|
||||
const float e = std::min(32767.f, std::max(-32768.f, y[i] - s));
|
||||
(*error_sum) += e * e;
|
||||
|
||||
// Update the matched filter estimate in an NLMS manner.
|
||||
if (x2_sum > x2_sum_threshold) {
|
||||
RTC_DCHECK_LT(0.f, x2_sum);
|
||||
const float alpha = 0.7f * e / x2_sum;
|
||||
|
||||
// filter = filter + 0.7 * (y - filter * x) / x * x.
|
||||
size_t x_index = x_start_index;
|
||||
for (size_t k = 0; k < h.size(); ++k) {
|
||||
h[k] += alpha * x[x_index];
|
||||
x_index = x_index < (x.size() - 1) ? x_index + 1 : 0;
|
||||
}
|
||||
*filters_updated = true;
|
||||
}
|
||||
|
||||
x_start_index = x_start_index > 0 ? x_start_index - 1 : x.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aec3
|
||||
|
||||
MatchedFilter::MatchedFilter(ApmDataDumper* data_dumper,
|
||||
Aec3Optimization optimization,
|
||||
size_t window_size_sub_blocks,
|
||||
int num_matched_filters,
|
||||
size_t alignment_shift_sub_blocks,
|
||||
float excitation_limit)
|
||||
: data_dumper_(data_dumper),
|
||||
optimization_(optimization),
|
||||
filter_intra_lag_shift_(alignment_shift_sub_blocks * kSubBlockSize),
|
||||
filters_(num_matched_filters,
|
||||
std::vector<float>(window_size_sub_blocks * kSubBlockSize, 0.f)),
|
||||
lag_estimates_(num_matched_filters),
|
||||
excitation_limit_(excitation_limit) {
|
||||
RTC_DCHECK(data_dumper);
|
||||
RTC_DCHECK_LT(0, window_size_sub_blocks);
|
||||
}
|
||||
|
||||
MatchedFilter::~MatchedFilter() = default;
|
||||
|
||||
void MatchedFilter::Reset() {
|
||||
for (auto& f : filters_) {
|
||||
std::fill(f.begin(), f.end(), 0.f);
|
||||
}
|
||||
|
||||
for (auto& l : lag_estimates_) {
|
||||
l = MatchedFilter::LagEstimate();
|
||||
}
|
||||
}
|
||||
|
||||
void MatchedFilter::Update(const DownsampledRenderBuffer& render_buffer,
|
||||
const std::array<float, kSubBlockSize>& capture) {
|
||||
const std::array<float, kSubBlockSize>& y = capture;
|
||||
|
||||
const float x2_sum_threshold =
|
||||
filters_[0].size() * excitation_limit_ * excitation_limit_;
|
||||
|
||||
// Apply all matched filters.
|
||||
size_t alignment_shift = 0;
|
||||
for (size_t n = 0; n < filters_.size(); ++n) {
|
||||
float error_sum = 0.f;
|
||||
bool filters_updated = false;
|
||||
|
||||
size_t x_start_index =
|
||||
(render_buffer.position + alignment_shift + kSubBlockSize - 1) %
|
||||
render_buffer.buffer.size();
|
||||
|
||||
switch (optimization_) {
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
case Aec3Optimization::kSse2:
|
||||
aec3::MatchedFilterCore_SSE2(x_start_index, x2_sum_threshold,
|
||||
render_buffer.buffer, y, filters_[n],
|
||||
&filters_updated, &error_sum);
|
||||
break;
|
||||
#endif
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
case Aec3Optimization::kNeon:
|
||||
aec3::MatchedFilterCore_NEON(x_start_index, x2_sum_threshold,
|
||||
render_buffer.buffer, y, filters_[n],
|
||||
&filters_updated, &error_sum);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
aec3::MatchedFilterCore(x_start_index, x2_sum_threshold,
|
||||
render_buffer.buffer, y, filters_[n],
|
||||
&filters_updated, &error_sum);
|
||||
}
|
||||
|
||||
// Compute anchor for the matched filter error.
|
||||
const float error_sum_anchor =
|
||||
std::inner_product(y.begin(), y.end(), y.begin(), 0.f);
|
||||
|
||||
// Estimate the lag in the matched filter as the distance to the portion in
|
||||
// the filter that contributes the most to the matched filter output. This
|
||||
// is detected as the peak of the matched filter.
|
||||
const size_t lag_estimate = std::distance(
|
||||
filters_[n].begin(),
|
||||
std::max_element(
|
||||
filters_[n].begin(), filters_[n].end(),
|
||||
[](float a, float b) -> bool { return a * a < b * b; }));
|
||||
|
||||
// Update the lag estimates for the matched filter.
|
||||
const float kMatchingFilterThreshold = 0.1f;
|
||||
lag_estimates_[n] = LagEstimate(
|
||||
error_sum_anchor - error_sum,
|
||||
(lag_estimate > 2 && lag_estimate < (filters_[n].size() - 10) &&
|
||||
error_sum < kMatchingFilterThreshold * error_sum_anchor),
|
||||
lag_estimate + alignment_shift, filters_updated);
|
||||
|
||||
// TODO(peah): Remove once development of EchoCanceller3 is fully done.
|
||||
RTC_DCHECK_EQ(4, filters_.size());
|
||||
switch (n) {
|
||||
case 0:
|
||||
data_dumper_->DumpRaw("aec3_correlator_0_h", filters_[0]);
|
||||
break;
|
||||
case 1:
|
||||
data_dumper_->DumpRaw("aec3_correlator_1_h", filters_[1]);
|
||||
break;
|
||||
case 2:
|
||||
data_dumper_->DumpRaw("aec3_correlator_2_h", filters_[2]);
|
||||
break;
|
||||
case 3:
|
||||
data_dumper_->DumpRaw("aec3_correlator_3_h", filters_[3]);
|
||||
break;
|
||||
default:
|
||||
RTC_DCHECK(false);
|
||||
}
|
||||
|
||||
alignment_shift += filter_intra_lag_shift_;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MATCHED_FILTER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MATCHED_FILTER_H_
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/optional.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/downsampled_render_buffer.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace aec3 {
|
||||
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
|
||||
// Filter core for the matched filter that is optimized for NEON.
|
||||
void MatchedFilterCore_NEON(size_t x_start_index,
|
||||
float x2_sum_threshold,
|
||||
rtc::ArrayView<const float> x,
|
||||
rtc::ArrayView<const float> y,
|
||||
rtc::ArrayView<float> h,
|
||||
bool* filters_updated,
|
||||
float* error_sum);
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
|
||||
// Filter core for the matched filter that is optimized for SSE2.
|
||||
void MatchedFilterCore_SSE2(size_t x_start_index,
|
||||
float x2_sum_threshold,
|
||||
rtc::ArrayView<const float> x,
|
||||
rtc::ArrayView<const float> y,
|
||||
rtc::ArrayView<float> h,
|
||||
bool* filters_updated,
|
||||
float* error_sum);
|
||||
|
||||
#endif
|
||||
|
||||
// Filter core for the matched filter.
|
||||
void MatchedFilterCore(size_t x_start_index,
|
||||
float x2_sum_threshold,
|
||||
rtc::ArrayView<const float> x,
|
||||
rtc::ArrayView<const float> y,
|
||||
rtc::ArrayView<float> h,
|
||||
bool* filters_updated,
|
||||
float* error_sum);
|
||||
|
||||
} // namespace aec3
|
||||
|
||||
class ApmDataDumper;
|
||||
|
||||
// Produces recursively updated cross-correlation estimates for several signal
|
||||
// shifts where the intra-shift spacing is uniform.
|
||||
class MatchedFilter {
|
||||
public:
|
||||
// Stores properties for the lag estimate corresponding to a particular signal
|
||||
// shift.
|
||||
struct LagEstimate {
|
||||
LagEstimate() = default;
|
||||
LagEstimate(float accuracy, bool reliable, size_t lag, bool updated)
|
||||
: accuracy(accuracy), reliable(reliable), lag(lag), updated(updated) {}
|
||||
|
||||
float accuracy = 0.f;
|
||||
bool reliable = false;
|
||||
size_t lag = 0;
|
||||
bool updated = false;
|
||||
};
|
||||
|
||||
MatchedFilter(ApmDataDumper* data_dumper,
|
||||
Aec3Optimization optimization,
|
||||
size_t window_size_sub_blocks,
|
||||
int num_matched_filters,
|
||||
size_t alignment_shift_sub_blocks,
|
||||
float excitation_limit);
|
||||
|
||||
~MatchedFilter();
|
||||
|
||||
// Updates the correlation with the values in the capture buffer.
|
||||
void Update(const DownsampledRenderBuffer& render_buffer,
|
||||
const std::array<float, kSubBlockSize>& capture);
|
||||
|
||||
// Resets the matched filter.
|
||||
void Reset();
|
||||
|
||||
// Returns the current lag estimates.
|
||||
rtc::ArrayView<const MatchedFilter::LagEstimate> GetLagEstimates() const {
|
||||
return lag_estimates_;
|
||||
}
|
||||
|
||||
// Returns the number of lag estimates produced using the shifted signals.
|
||||
size_t NumLagEstimates() const { return filters_.size(); }
|
||||
|
||||
private:
|
||||
ApmDataDumper* const data_dumper_;
|
||||
const Aec3Optimization optimization_;
|
||||
const size_t filter_intra_lag_shift_;
|
||||
std::vector<std::vector<float>> filters_;
|
||||
std::vector<LagEstimate> lag_estimates_;
|
||||
const float excitation_limit_;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(MatchedFilter);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MATCHED_FILTER_H_
|
||||
@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/matched_filter_lag_aggregator.h"
|
||||
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
MatchedFilterLagAggregator::MatchedFilterLagAggregator(
|
||||
ApmDataDumper* data_dumper,
|
||||
size_t num_lag_estimates)
|
||||
: data_dumper_(data_dumper), lag_updates_in_a_row_(num_lag_estimates, 0) {
|
||||
RTC_DCHECK(data_dumper);
|
||||
RTC_DCHECK_LT(0, num_lag_estimates);
|
||||
}
|
||||
|
||||
MatchedFilterLagAggregator::~MatchedFilterLagAggregator() = default;
|
||||
|
||||
void MatchedFilterLagAggregator::Reset() {
|
||||
candidate_ = 0;
|
||||
candidate_counter_ = 0;
|
||||
std::fill(lag_updates_in_a_row_.begin(), lag_updates_in_a_row_.end(), 0.f);
|
||||
}
|
||||
|
||||
rtc::Optional<size_t> MatchedFilterLagAggregator::Aggregate(
|
||||
rtc::ArrayView<const MatchedFilter::LagEstimate> lag_estimates) {
|
||||
RTC_DCHECK_EQ(lag_updates_in_a_row_.size(), lag_estimates.size());
|
||||
|
||||
// Count the number of lag updates in a row to ensure that only stable lags
|
||||
// are taken into account.
|
||||
for (size_t k = 0; k < lag_estimates.size(); ++k) {
|
||||
lag_updates_in_a_row_[k] =
|
||||
lag_estimates[k].updated ? lag_updates_in_a_row_[k] + 1 : 0;
|
||||
}
|
||||
|
||||
// If available, choose the strongest lag estimate as the best one.
|
||||
int best_lag_estimate_index = -1;
|
||||
for (size_t k = 0; k < lag_estimates.size(); ++k) {
|
||||
if (lag_updates_in_a_row_[k] > 10 && lag_estimates[k].reliable &&
|
||||
(best_lag_estimate_index == -1 ||
|
||||
lag_estimates[k].accuracy >
|
||||
lag_estimates[best_lag_estimate_index].accuracy)) {
|
||||
best_lag_estimate_index = k;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(peah): Remove this logging once all development is done.
|
||||
data_dumper_->DumpRaw("aec3_echo_path_delay_estimator_best_index",
|
||||
best_lag_estimate_index);
|
||||
|
||||
// Require the same lag to be detected 10 times in a row before considering
|
||||
// it reliable.
|
||||
if (best_lag_estimate_index >= 0) {
|
||||
candidate_counter_ =
|
||||
(candidate_ == lag_estimates[best_lag_estimate_index].lag)
|
||||
? candidate_counter_ + 1
|
||||
: 0;
|
||||
candidate_ = lag_estimates[best_lag_estimate_index].lag;
|
||||
}
|
||||
|
||||
return candidate_counter_ >= 15 ? rtc::Optional<size_t>(candidate_)
|
||||
: rtc::Optional<size_t>();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MATCHED_FILTER_LAG_AGGREGATOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MATCHED_FILTER_LAG_AGGREGATOR_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/optional.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/matched_filter.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class ApmDataDumper;
|
||||
|
||||
// Aggregates lag estimates produced by the MatchedFilter class into a single
|
||||
// reliable combined lag estimate.
|
||||
class MatchedFilterLagAggregator {
|
||||
public:
|
||||
MatchedFilterLagAggregator(ApmDataDumper* data_dumper,
|
||||
size_t num_lag_estimates);
|
||||
~MatchedFilterLagAggregator();
|
||||
|
||||
// Resets the aggregator.
|
||||
void Reset();
|
||||
|
||||
// Aggregates the provided lag estimates.
|
||||
rtc::Optional<size_t> Aggregate(
|
||||
rtc::ArrayView<const MatchedFilter::LagEstimate> lag_estimates);
|
||||
|
||||
private:
|
||||
ApmDataDumper* const data_dumper_;
|
||||
std::vector<size_t> lag_updates_in_a_row_;
|
||||
size_t candidate_ = 0;
|
||||
size_t candidate_counter_ = 0;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(MatchedFilterLagAggregator);
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MATCHED_FILTER_LAG_AGGREGATOR_H_
|
||||
@ -1,192 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/matched_filter_lag_aggregator.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
void VerifyNoAggregateOutputForRepeatedLagAggregation(
|
||||
size_t num_repetitions,
|
||||
rtc::ArrayView<const MatchedFilter::LagEstimate> lag_estimates,
|
||||
MatchedFilterLagAggregator* aggregator) {
|
||||
for (size_t k = 0; k < num_repetitions; ++k) {
|
||||
EXPECT_FALSE(aggregator->Aggregate(lag_estimates));
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t kThresholdForRequiredLagUpdatesInARow = 10;
|
||||
constexpr size_t kThresholdForRequiredIdenticalLagAggregates = 15;
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies that the most accurate lag estimate is chosen.
|
||||
TEST(MatchedFilterLagAggregator, MostAccurateLagChosen) {
|
||||
constexpr size_t kArtificialLag1 = 5;
|
||||
constexpr size_t kArtificialLag2 = 10;
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::vector<MatchedFilter::LagEstimate> lag_estimates(2);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper, lag_estimates.size());
|
||||
lag_estimates[0] =
|
||||
MatchedFilter::LagEstimate(1.f, true, kArtificialLag1, true);
|
||||
lag_estimates[1] =
|
||||
MatchedFilter::LagEstimate(0.5f, true, kArtificialLag2, true);
|
||||
|
||||
VerifyNoAggregateOutputForRepeatedLagAggregation(
|
||||
kThresholdForRequiredLagUpdatesInARow +
|
||||
kThresholdForRequiredIdenticalLagAggregates,
|
||||
lag_estimates, &aggregator);
|
||||
rtc::Optional<size_t> aggregated_lag = aggregator.Aggregate(lag_estimates);
|
||||
EXPECT_TRUE(aggregated_lag);
|
||||
EXPECT_EQ(kArtificialLag1, *aggregated_lag);
|
||||
|
||||
lag_estimates[0] =
|
||||
MatchedFilter::LagEstimate(0.5f, true, kArtificialLag1, true);
|
||||
lag_estimates[1] =
|
||||
MatchedFilter::LagEstimate(1.f, true, kArtificialLag2, true);
|
||||
|
||||
VerifyNoAggregateOutputForRepeatedLagAggregation(
|
||||
kThresholdForRequiredIdenticalLagAggregates, lag_estimates, &aggregator);
|
||||
aggregated_lag = aggregator.Aggregate(lag_estimates);
|
||||
EXPECT_TRUE(aggregated_lag);
|
||||
EXPECT_EQ(kArtificialLag2, *aggregated_lag);
|
||||
}
|
||||
|
||||
// Verifies that varying lag estimates causes lag estimates to not be deemed
|
||||
// reliable.
|
||||
TEST(MatchedFilterLagAggregator,
|
||||
LagEstimateInvarianceRequiredForAggregatedLag) {
|
||||
constexpr size_t kArtificialLag1 = 5;
|
||||
constexpr size_t kArtificialLag2 = 10;
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::vector<MatchedFilter::LagEstimate> lag_estimates(1);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper, lag_estimates.size());
|
||||
lag_estimates[0] =
|
||||
MatchedFilter::LagEstimate(1.f, true, kArtificialLag1, true);
|
||||
VerifyNoAggregateOutputForRepeatedLagAggregation(
|
||||
kThresholdForRequiredLagUpdatesInARow +
|
||||
kThresholdForRequiredIdenticalLagAggregates,
|
||||
lag_estimates, &aggregator);
|
||||
rtc::Optional<size_t> aggregated_lag = aggregator.Aggregate(lag_estimates);
|
||||
EXPECT_TRUE(aggregated_lag);
|
||||
EXPECT_EQ(kArtificialLag1, *aggregated_lag);
|
||||
|
||||
lag_estimates[0] =
|
||||
MatchedFilter::LagEstimate(1.f, true, kArtificialLag2, true);
|
||||
|
||||
VerifyNoAggregateOutputForRepeatedLagAggregation(
|
||||
kThresholdForRequiredIdenticalLagAggregates, lag_estimates, &aggregator);
|
||||
aggregated_lag = aggregator.Aggregate(lag_estimates);
|
||||
EXPECT_TRUE(aggregated_lag);
|
||||
EXPECT_EQ(kArtificialLag2, *aggregated_lag);
|
||||
}
|
||||
|
||||
// Verifies that lag estimate updates are required to produce an updated lag
|
||||
// aggregate.
|
||||
TEST(MatchedFilterLagAggregator, LagEstimateUpdatesRequiredForAggregatedLag) {
|
||||
constexpr size_t kArtificialLag1 = 5;
|
||||
constexpr size_t kArtificialLag2 = 10;
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::vector<MatchedFilter::LagEstimate> lag_estimates(1);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper, lag_estimates.size());
|
||||
lag_estimates[0] =
|
||||
MatchedFilter::LagEstimate(1.f, true, kArtificialLag1, true);
|
||||
VerifyNoAggregateOutputForRepeatedLagAggregation(
|
||||
kThresholdForRequiredLagUpdatesInARow +
|
||||
kThresholdForRequiredIdenticalLagAggregates,
|
||||
lag_estimates, &aggregator);
|
||||
rtc::Optional<size_t> aggregated_lag = aggregator.Aggregate(lag_estimates);
|
||||
EXPECT_TRUE(aggregated_lag);
|
||||
EXPECT_EQ(kArtificialLag1, *aggregated_lag);
|
||||
|
||||
lag_estimates[0] =
|
||||
MatchedFilter::LagEstimate(1.f, true, kArtificialLag2, false);
|
||||
|
||||
for (size_t k = 0; k < kThresholdForRequiredLagUpdatesInARow +
|
||||
kThresholdForRequiredIdenticalLagAggregates + 1;
|
||||
++k) {
|
||||
aggregated_lag = aggregator.Aggregate(lag_estimates);
|
||||
EXPECT_TRUE(aggregated_lag);
|
||||
EXPECT_EQ(kArtificialLag1, *aggregated_lag);
|
||||
}
|
||||
|
||||
lag_estimates[0] =
|
||||
MatchedFilter::LagEstimate(1.f, true, kArtificialLag2, true);
|
||||
for (size_t k = 0; k < kThresholdForRequiredLagUpdatesInARow; ++k) {
|
||||
aggregated_lag = aggregator.Aggregate(lag_estimates);
|
||||
EXPECT_TRUE(aggregated_lag);
|
||||
EXPECT_EQ(kArtificialLag1, *aggregated_lag);
|
||||
}
|
||||
|
||||
VerifyNoAggregateOutputForRepeatedLagAggregation(
|
||||
kThresholdForRequiredIdenticalLagAggregates, lag_estimates, &aggregator);
|
||||
|
||||
aggregated_lag = aggregator.Aggregate(lag_estimates);
|
||||
EXPECT_TRUE(aggregated_lag);
|
||||
EXPECT_EQ(kArtificialLag2, *aggregated_lag);
|
||||
}
|
||||
|
||||
// Verifies that an aggregated lag is persistent if the lag estimates do not
|
||||
// change and that an aggregated lag is not produced without gaining lag
|
||||
// estimate confidence.
|
||||
TEST(MatchedFilterLagAggregator, PersistentAggregatedLag) {
|
||||
constexpr size_t kArtificialLag = 5;
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::vector<MatchedFilter::LagEstimate> lag_estimates(1);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper, lag_estimates.size());
|
||||
lag_estimates[0] =
|
||||
MatchedFilter::LagEstimate(1.f, true, kArtificialLag, true);
|
||||
VerifyNoAggregateOutputForRepeatedLagAggregation(
|
||||
kThresholdForRequiredLagUpdatesInARow +
|
||||
kThresholdForRequiredIdenticalLagAggregates,
|
||||
lag_estimates, &aggregator);
|
||||
rtc::Optional<size_t> aggregated_lag = aggregator.Aggregate(lag_estimates);
|
||||
EXPECT_TRUE(aggregated_lag);
|
||||
EXPECT_EQ(kArtificialLag, *aggregated_lag);
|
||||
|
||||
aggregated_lag = aggregator.Aggregate(lag_estimates);
|
||||
EXPECT_TRUE(aggregated_lag);
|
||||
EXPECT_EQ(kArtificialLag, *aggregated_lag);
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
|
||||
// Verifies the check for correct number of lag estimates.
|
||||
TEST(MatchedFilterLagAggregator, IncorrectNumberOfLagEstimates) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper, 1);
|
||||
std::vector<MatchedFilter::LagEstimate> lag_estimates(2);
|
||||
|
||||
EXPECT_DEATH(aggregator.Aggregate(lag_estimates), "");
|
||||
}
|
||||
|
||||
// Verifies the check for non-zero number of lag estimates.
|
||||
TEST(MatchedFilterLagAggregator, NonZeroLagEstimates) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
EXPECT_DEATH(MatchedFilterLagAggregator(&data_dumper, 0), "");
|
||||
}
|
||||
|
||||
// Verifies the check for non-null data dumper.
|
||||
TEST(MatchedFilterLagAggregator, NullDataDumper) {
|
||||
EXPECT_DEATH(MatchedFilterLagAggregator(nullptr, 1), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,298 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/matched_filter.h"
|
||||
|
||||
#include "webrtc/typedefs.h"
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/decimator_by_4.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_delay_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/modules/audio_processing/test/echo_canceller_test_tools.h"
|
||||
#include "webrtc/rtc_base/random.h"
|
||||
#include "webrtc/system_wrappers/include/cpu_features_wrapper.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace aec3 {
|
||||
namespace {
|
||||
|
||||
std::string ProduceDebugText(size_t delay) {
|
||||
std::ostringstream ss;
|
||||
ss << "Delay: " << delay;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
constexpr size_t kWindowSizeSubBlocks = 32;
|
||||
constexpr size_t kAlignmentShiftSubBlocks = kWindowSizeSubBlocks * 3 / 4;
|
||||
constexpr size_t kNumMatchedFilters = 4;
|
||||
|
||||
} // namespace
|
||||
|
||||
#if defined(WEBRTC_HAS_NEON)
|
||||
// Verifies that the optimized methods for NEON are similar to their reference
|
||||
// counterparts.
|
||||
TEST(MatchedFilter, TestNeonOptimizations) {
|
||||
Random random_generator(42U);
|
||||
std::vector<float> x(2000);
|
||||
RandomizeSampleVector(&random_generator, x);
|
||||
std::vector<float> y(kSubBlockSize);
|
||||
std::vector<float> h_NEON(512);
|
||||
std::vector<float> h(512);
|
||||
int x_index = 0;
|
||||
for (int k = 0; k < 1000; ++k) {
|
||||
RandomizeSampleVector(&random_generator, y);
|
||||
|
||||
bool filters_updated = false;
|
||||
float error_sum = 0.f;
|
||||
bool filters_updated_NEON = false;
|
||||
float error_sum_NEON = 0.f;
|
||||
|
||||
MatchedFilterCore_NEON(x_index, h.size() * 150.f * 150.f, x, y, h_NEON,
|
||||
&filters_updated_NEON, &error_sum_NEON);
|
||||
|
||||
MatchedFilterCore(x_index, h.size() * 150.f * 150.f, x, y, h,
|
||||
&filters_updated, &error_sum);
|
||||
|
||||
EXPECT_EQ(filters_updated, filters_updated_NEON);
|
||||
EXPECT_NEAR(error_sum, error_sum_NEON, error_sum / 100000.f);
|
||||
|
||||
for (size_t j = 0; j < h.size(); ++j) {
|
||||
EXPECT_NEAR(h[j], h_NEON[j], 0.00001f);
|
||||
}
|
||||
|
||||
x_index = (x_index + kSubBlockSize) % x.size();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
// Verifies that the optimized methods for SSE2 are bitexact to their reference
|
||||
// counterparts.
|
||||
TEST(MatchedFilter, TestSse2Optimizations) {
|
||||
bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0);
|
||||
if (use_sse2) {
|
||||
Random random_generator(42U);
|
||||
std::vector<float> x(2000);
|
||||
RandomizeSampleVector(&random_generator, x);
|
||||
std::vector<float> y(kSubBlockSize);
|
||||
std::vector<float> h_SSE2(512);
|
||||
std::vector<float> h(512);
|
||||
int x_index = 0;
|
||||
for (int k = 0; k < 1000; ++k) {
|
||||
RandomizeSampleVector(&random_generator, y);
|
||||
|
||||
bool filters_updated = false;
|
||||
float error_sum = 0.f;
|
||||
bool filters_updated_SSE2 = false;
|
||||
float error_sum_SSE2 = 0.f;
|
||||
|
||||
MatchedFilterCore_SSE2(x_index, h.size() * 150.f * 150.f, x, y, h_SSE2,
|
||||
&filters_updated_SSE2, &error_sum_SSE2);
|
||||
|
||||
MatchedFilterCore(x_index, h.size() * 150.f * 150.f, x, y, h,
|
||||
&filters_updated, &error_sum);
|
||||
|
||||
EXPECT_EQ(filters_updated, filters_updated_SSE2);
|
||||
EXPECT_NEAR(error_sum, error_sum_SSE2, error_sum / 100000.f);
|
||||
|
||||
for (size_t j = 0; j < h.size(); ++j) {
|
||||
EXPECT_NEAR(h[j], h_SSE2[j], 0.00001f);
|
||||
}
|
||||
|
||||
x_index = (x_index + kSubBlockSize) % x.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Verifies that the matched filter produces proper lag estimates for
|
||||
// artificially
|
||||
// delayed signals.
|
||||
TEST(MatchedFilter, LagEstimation) {
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> render(3,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::array<float, kBlockSize> capture;
|
||||
capture.fill(0.f);
|
||||
ApmDataDumper data_dumper(0);
|
||||
for (size_t delay_samples : {5, 64, 150, 200, 800, 1000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples));
|
||||
DecimatorBy4 capture_decimator;
|
||||
DelayBuffer<float> signal_delay_buffer(4 * delay_samples);
|
||||
MatchedFilter filter(&data_dumper, DetectOptimization(),
|
||||
kWindowSizeSubBlocks, kNumMatchedFilters,
|
||||
kAlignmentShiftSubBlocks, 150);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
|
||||
// Analyze the correlation between render and capture.
|
||||
for (size_t k = 0; k < (100 + delay_samples / kSubBlockSize); ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
signal_delay_buffer.Delay(render[0], capture);
|
||||
render_delay_buffer->Insert(render);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
std::array<float, kSubBlockSize> downsampled_capture;
|
||||
capture_decimator.Decimate(capture, downsampled_capture);
|
||||
filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(),
|
||||
downsampled_capture);
|
||||
}
|
||||
|
||||
// Obtain the lag estimates.
|
||||
auto lag_estimates = filter.GetLagEstimates();
|
||||
|
||||
// Find which lag estimate should be the most accurate.
|
||||
rtc::Optional<size_t> expected_most_accurate_lag_estimate;
|
||||
size_t alignment_shift_sub_blocks = 0;
|
||||
for (size_t k = 0; k < kNumMatchedFilters; ++k) {
|
||||
if ((alignment_shift_sub_blocks + kWindowSizeSubBlocks / 2) *
|
||||
kSubBlockSize >
|
||||
delay_samples) {
|
||||
expected_most_accurate_lag_estimate = rtc::Optional<size_t>(k);
|
||||
break;
|
||||
}
|
||||
alignment_shift_sub_blocks += kAlignmentShiftSubBlocks;
|
||||
}
|
||||
ASSERT_TRUE(expected_most_accurate_lag_estimate);
|
||||
|
||||
// Verify that the expected most accurate lag estimate is the most accurate
|
||||
// estimate.
|
||||
for (size_t k = 0; k < kNumMatchedFilters; ++k) {
|
||||
if (k != *expected_most_accurate_lag_estimate) {
|
||||
EXPECT_GT(lag_estimates[*expected_most_accurate_lag_estimate].accuracy,
|
||||
lag_estimates[k].accuracy);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that all lag estimates are updated as expected for signals
|
||||
// containing strong noise.
|
||||
for (auto& le : lag_estimates) {
|
||||
EXPECT_TRUE(le.updated);
|
||||
}
|
||||
|
||||
// Verify that the expected most accurate lag estimate is reliable.
|
||||
EXPECT_TRUE(lag_estimates[*expected_most_accurate_lag_estimate].reliable);
|
||||
|
||||
// Verify that the expected most accurate lag estimate is correct.
|
||||
EXPECT_EQ(delay_samples,
|
||||
lag_estimates[*expected_most_accurate_lag_estimate].lag);
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the matched filter does not produce reliable and accurate
|
||||
// estimates for uncorrelated render and capture signals.
|
||||
TEST(MatchedFilter, LagNotReliableForUncorrelatedRenderAndCapture) {
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> render(3,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::array<float, kSubBlockSize> capture;
|
||||
capture.fill(0.f);
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
MatchedFilter filter(&data_dumper, DetectOptimization(), kWindowSizeSubBlocks,
|
||||
kNumMatchedFilters, kAlignmentShiftSubBlocks, 150);
|
||||
|
||||
// Analyze the correlation between render and capture.
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
RandomizeSampleVector(&random_generator, capture);
|
||||
render_delay_buffer->Insert(render);
|
||||
filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(), capture);
|
||||
}
|
||||
|
||||
// Obtain the lag estimates.
|
||||
auto lag_estimates = filter.GetLagEstimates();
|
||||
EXPECT_EQ(kNumMatchedFilters, lag_estimates.size());
|
||||
|
||||
// Verify that no lag estimates are reliable.
|
||||
for (auto& le : lag_estimates) {
|
||||
EXPECT_FALSE(le.reliable);
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the matched filter does not produce updated lag estimates for
|
||||
// render signals of low level.
|
||||
TEST(MatchedFilter, LagNotUpdatedForLowLevelRender) {
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> render(3,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::array<float, kBlockSize> capture;
|
||||
capture.fill(0.f);
|
||||
ApmDataDumper data_dumper(0);
|
||||
MatchedFilter filter(&data_dumper, DetectOptimization(), kWindowSizeSubBlocks,
|
||||
kNumMatchedFilters, kAlignmentShiftSubBlocks, 150);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
DecimatorBy4 capture_decimator;
|
||||
|
||||
// Analyze the correlation between render and capture.
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
for (auto& render_k : render[0]) {
|
||||
render_k *= 149.f / 32767.f;
|
||||
}
|
||||
std::copy(render[0].begin(), render[0].end(), capture.begin());
|
||||
std::array<float, kSubBlockSize> downsampled_capture;
|
||||
capture_decimator.Decimate(capture, downsampled_capture);
|
||||
filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(),
|
||||
downsampled_capture);
|
||||
}
|
||||
|
||||
// Obtain the lag estimates.
|
||||
auto lag_estimates = filter.GetLagEstimates();
|
||||
EXPECT_EQ(kNumMatchedFilters, lag_estimates.size());
|
||||
|
||||
// Verify that no lag estimates are updated and that no lag estimates are
|
||||
// reliable.
|
||||
for (auto& le : lag_estimates) {
|
||||
EXPECT_FALSE(le.updated);
|
||||
EXPECT_FALSE(le.reliable);
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the correct number of lag estimates are produced for a certain
|
||||
// number of alignment shifts.
|
||||
TEST(MatchedFilter, NumberOfLagEstimates) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
for (size_t num_matched_filters = 0; num_matched_filters < 10;
|
||||
++num_matched_filters) {
|
||||
MatchedFilter filter(&data_dumper, DetectOptimization(), 32,
|
||||
num_matched_filters, 1, 150);
|
||||
EXPECT_EQ(num_matched_filters, filter.GetLagEstimates().size());
|
||||
}
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
|
||||
// Verifies the check for non-zero windows size.
|
||||
TEST(MatchedFilter, ZeroWindowSize) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
EXPECT_DEATH(MatchedFilter(&data_dumper, DetectOptimization(), 0, 1, 1, 150),
|
||||
"");
|
||||
}
|
||||
|
||||
// Verifies the check for non-null data dumper.
|
||||
TEST(MatchedFilter, NullDataDumper) {
|
||||
EXPECT_DEATH(MatchedFilter(nullptr, DetectOptimization(), 1, 1, 1, 150), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace aec3
|
||||
} // namespace webrtc
|
||||
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_BLOCK_PROCESSOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_BLOCK_PROCESSOR_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/block_processor.h"
|
||||
#include "webrtc/test/gmock.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
class MockBlockProcessor : public BlockProcessor {
|
||||
public:
|
||||
virtual ~MockBlockProcessor() {}
|
||||
|
||||
MOCK_METHOD3(ProcessCapture,
|
||||
void(bool level_change,
|
||||
bool saturated_microphone_signal,
|
||||
std::vector<std::vector<float>>* capture_block));
|
||||
MOCK_METHOD1(BufferRender,
|
||||
void(const std::vector<std::vector<float>>& block));
|
||||
MOCK_METHOD1(UpdateEchoLeakageStatus, void(bool leakage_detected));
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_BLOCK_PROCESSOR_H_
|
||||
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_ECHO_REMOVER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_ECHO_REMOVER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/optional.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/echo_path_variability.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/echo_remover.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_buffer.h"
|
||||
#include "webrtc/test/gmock.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
class MockEchoRemover : public EchoRemover {
|
||||
public:
|
||||
virtual ~MockEchoRemover() = default;
|
||||
|
||||
MOCK_METHOD5(ProcessCapture,
|
||||
void(const rtc::Optional<size_t>& echo_path_delay_samples,
|
||||
const EchoPathVariability& echo_path_variability,
|
||||
bool capture_signal_saturation,
|
||||
const RenderBuffer& render_buffer,
|
||||
std::vector<std::vector<float>>* capture));
|
||||
|
||||
MOCK_METHOD1(UpdateEchoLeakageStatus, void(bool leakage_detected));
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_ECHO_REMOVER_H_
|
||||
@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_RENDER_DELAY_BUFFER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_RENDER_DELAY_BUFFER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/downsampled_render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_delay_buffer.h"
|
||||
#include "webrtc/test/gmock.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
class MockRenderDelayBuffer : public RenderDelayBuffer {
|
||||
public:
|
||||
explicit MockRenderDelayBuffer(int sample_rate_hz)
|
||||
: render_buffer_(Aec3Optimization::kNone,
|
||||
NumBandsForRate(sample_rate_hz),
|
||||
kRenderDelayBufferSize,
|
||||
std::vector<size_t>(1, kAdaptiveFilterLength)) {
|
||||
ON_CALL(*this, GetRenderBuffer())
|
||||
.WillByDefault(
|
||||
testing::Invoke(this, &MockRenderDelayBuffer::FakeGetRenderBuffer));
|
||||
ON_CALL(*this, GetDownsampledRenderBuffer())
|
||||
.WillByDefault(testing::Invoke(
|
||||
this, &MockRenderDelayBuffer::FakeGetDownsampledRenderBuffer));
|
||||
}
|
||||
virtual ~MockRenderDelayBuffer() = default;
|
||||
|
||||
MOCK_METHOD0(Reset, void());
|
||||
MOCK_METHOD1(Insert, bool(const std::vector<std::vector<float>>& block));
|
||||
MOCK_METHOD0(UpdateBuffers, bool());
|
||||
MOCK_METHOD1(SetDelay, void(size_t delay));
|
||||
MOCK_CONST_METHOD0(Delay, size_t());
|
||||
MOCK_CONST_METHOD0(MaxDelay, size_t());
|
||||
MOCK_CONST_METHOD0(IsBlockAvailable, bool());
|
||||
MOCK_CONST_METHOD0(GetRenderBuffer, const RenderBuffer&());
|
||||
MOCK_CONST_METHOD0(GetDownsampledRenderBuffer,
|
||||
const DownsampledRenderBuffer&());
|
||||
|
||||
private:
|
||||
const RenderBuffer& FakeGetRenderBuffer() const { return render_buffer_; }
|
||||
const DownsampledRenderBuffer& FakeGetDownsampledRenderBuffer() const {
|
||||
return downsampled_render_buffer_;
|
||||
}
|
||||
RenderBuffer render_buffer_;
|
||||
DownsampledRenderBuffer downsampled_render_buffer_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_RENDER_DELAY_BUFFER_H_
|
||||
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_RENDER_DELAY_CONTROLLER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_RENDER_DELAY_CONTROLLER_H_
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/api/optional.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/downsampled_render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_delay_controller.h"
|
||||
#include "webrtc/test/gmock.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
class MockRenderDelayController : public RenderDelayController {
|
||||
public:
|
||||
virtual ~MockRenderDelayController() = default;
|
||||
|
||||
MOCK_METHOD0(Reset, void());
|
||||
MOCK_METHOD1(SetDelay, void(size_t render_delay));
|
||||
MOCK_METHOD2(GetDelay,
|
||||
size_t(const DownsampledRenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const float> capture));
|
||||
MOCK_CONST_METHOD0(AlignmentHeadroomSamples, rtc::Optional<size_t>());
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_RENDER_DELAY_CONTROLLER_H_
|
||||
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/output_selector.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
// Performs the transition between the signals in a smooth manner.
|
||||
void SmoothFrameTransition(bool from_y_to_e,
|
||||
rtc::ArrayView<const float> e,
|
||||
rtc::ArrayView<float> y) {
|
||||
RTC_DCHECK_LT(0u, e.size());
|
||||
RTC_DCHECK_EQ(y.size(), e.size());
|
||||
|
||||
const float change_factor = (from_y_to_e ? 1.f : -1.f) / e.size();
|
||||
float averaging = from_y_to_e ? 0.f : 1.f;
|
||||
for (size_t k = 0; k < e.size(); ++k) {
|
||||
y[k] += averaging * (e[k] - y[k]);
|
||||
averaging += change_factor;
|
||||
}
|
||||
RTC_DCHECK_EQ(from_y_to_e ? 1.f : 0.f, averaging);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OutputSelector::OutputSelector() = default;
|
||||
|
||||
OutputSelector::~OutputSelector() = default;
|
||||
|
||||
void OutputSelector::FormLinearOutput(
|
||||
bool use_subtractor_output,
|
||||
rtc::ArrayView<const float> subtractor_output,
|
||||
rtc::ArrayView<float> capture) {
|
||||
RTC_DCHECK_EQ(subtractor_output.size(), capture.size());
|
||||
rtc::ArrayView<const float>& e_main = subtractor_output;
|
||||
rtc::ArrayView<float> y = capture;
|
||||
|
||||
if (use_subtractor_output != use_subtractor_output_) {
|
||||
use_subtractor_output_ = use_subtractor_output;
|
||||
SmoothFrameTransition(use_subtractor_output_, e_main, y);
|
||||
} else if (use_subtractor_output_) {
|
||||
std::copy(e_main.begin(), e_main.end(), y.begin());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_OUTPUT_SELECTOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_OUTPUT_SELECTOR_H_
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Performs the selection between which of the linear aec output and the
|
||||
// microphone signal should be used as the echo suppressor output.
|
||||
class OutputSelector {
|
||||
public:
|
||||
OutputSelector();
|
||||
~OutputSelector();
|
||||
|
||||
// Forms the most appropriate output signal.
|
||||
void FormLinearOutput(bool use_subtractor_output,
|
||||
rtc::ArrayView<const float> subtractor_output,
|
||||
rtc::ArrayView<float> capture);
|
||||
|
||||
// Returns true if the linear aec output is the one used.
|
||||
bool UseSubtractorOutput() const { return use_subtractor_output_; }
|
||||
|
||||
private:
|
||||
bool use_subtractor_output_ = false;
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(OutputSelector);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_OUTPUT_SELECTOR_H_
|
||||
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/output_selector.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Verifies that the switching between the signals in the output works as
|
||||
// intended.
|
||||
TEST(OutputSelector, ProperSwitching) {
|
||||
OutputSelector selector;
|
||||
|
||||
std::array<float, kBlockSize> y;
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> e_ref;
|
||||
std::array<float, kBlockSize> y_ref;
|
||||
auto init_blocks = [](std::array<float, kBlockSize>* e,
|
||||
std::array<float, kBlockSize>* y) {
|
||||
e->fill(10.f);
|
||||
y->fill(20.f);
|
||||
};
|
||||
|
||||
init_blocks(&e_ref, &y_ref);
|
||||
|
||||
init_blocks(&e, &y);
|
||||
selector.FormLinearOutput(false, e, y);
|
||||
EXPECT_EQ(y_ref, y);
|
||||
|
||||
init_blocks(&e, &y);
|
||||
selector.FormLinearOutput(true, e, y);
|
||||
EXPECT_NE(e_ref, y);
|
||||
EXPECT_NE(y_ref, y);
|
||||
|
||||
init_blocks(&e, &y);
|
||||
selector.FormLinearOutput(true, e, y);
|
||||
EXPECT_EQ(e_ref, y);
|
||||
|
||||
init_blocks(&e, &y);
|
||||
selector.FormLinearOutput(true, e, y);
|
||||
EXPECT_EQ(e_ref, y);
|
||||
|
||||
init_blocks(&e, &y);
|
||||
selector.FormLinearOutput(false, e, y);
|
||||
EXPECT_NE(e_ref, y);
|
||||
EXPECT_NE(y_ref, y);
|
||||
|
||||
init_blocks(&e, &y);
|
||||
selector.FormLinearOutput(false, e, y);
|
||||
EXPECT_EQ(y_ref, y);
|
||||
|
||||
init_blocks(&e, &y);
|
||||
selector.FormLinearOutput(false, e, y);
|
||||
EXPECT_EQ(y_ref, y);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/render_buffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
RenderBuffer::RenderBuffer(Aec3Optimization optimization,
|
||||
size_t num_bands,
|
||||
size_t num_partitions,
|
||||
const std::vector<size_t> num_ffts_for_spectral_sums)
|
||||
: optimization_(optimization),
|
||||
fft_buffer_(num_partitions),
|
||||
spectrum_buffer_(num_partitions, std::array<float, kFftLengthBy2Plus1>()),
|
||||
spectral_sums_(num_ffts_for_spectral_sums.size(),
|
||||
std::array<float, kFftLengthBy2Plus1>()),
|
||||
last_block_(num_bands, std::vector<float>(kBlockSize, 0.f)),
|
||||
fft_() {
|
||||
// Current implementation only allows a maximum of one spectral sum lengths.
|
||||
RTC_DCHECK_EQ(1, num_ffts_for_spectral_sums.size());
|
||||
spectral_sums_length_ = num_ffts_for_spectral_sums[0];
|
||||
RTC_DCHECK_GE(fft_buffer_.size(), spectral_sums_length_);
|
||||
|
||||
Clear();
|
||||
}
|
||||
|
||||
RenderBuffer::~RenderBuffer() = default;
|
||||
|
||||
void RenderBuffer::Clear() {
|
||||
position_ = 0;
|
||||
for (auto& sum : spectral_sums_) {
|
||||
sum.fill(0.f);
|
||||
}
|
||||
|
||||
for (auto& spectrum : spectrum_buffer_) {
|
||||
spectrum.fill(0.f);
|
||||
}
|
||||
|
||||
for (auto& fft : fft_buffer_) {
|
||||
fft.Clear();
|
||||
}
|
||||
|
||||
for (auto& b : last_block_) {
|
||||
std::fill(b.begin(), b.end(), 0.f);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBuffer::Insert(const std::vector<std::vector<float>>& block) {
|
||||
// Compute the FFT of the data in the lowest band.
|
||||
FftData X;
|
||||
fft_.PaddedFft(block[0], last_block_[0], &X);
|
||||
|
||||
// Copy the last render frame.
|
||||
RTC_DCHECK_EQ(last_block_.size(), block.size());
|
||||
for (size_t k = 0; k < block.size(); ++k) {
|
||||
RTC_DCHECK_EQ(last_block_[k].size(), block[k].size());
|
||||
std::copy(block[k].begin(), block[k].end(), last_block_[k].begin());
|
||||
}
|
||||
|
||||
// Insert X into the buffer.
|
||||
position_ = position_ > 0 ? position_ - 1 : fft_buffer_.size() - 1;
|
||||
fft_buffer_[position_].Assign(X);
|
||||
|
||||
// Compute and insert the spectrum for the FFT into the spectrum buffer.
|
||||
X.Spectrum(optimization_, &spectrum_buffer_[position_]);
|
||||
|
||||
// Pre-compute and cache the spectral sums.
|
||||
std::copy(spectrum_buffer_[position_].begin(),
|
||||
spectrum_buffer_[position_].end(), spectral_sums_[0].begin());
|
||||
size_t position = (position_ + 1) % fft_buffer_.size();
|
||||
for (size_t j = 1; j < spectral_sums_length_; ++j) {
|
||||
const std::array<float, kFftLengthBy2Plus1>& spectrum =
|
||||
spectrum_buffer_[position];
|
||||
|
||||
for (size_t k = 0; k < spectral_sums_[0].size(); ++k) {
|
||||
spectral_sums_[0][k] += spectrum[k];
|
||||
}
|
||||
|
||||
position = position < (fft_buffer_.size() - 1) ? position + 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_BUFFER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_BUFFER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_fft.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/fft_data.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Provides a buffer of the render data for the echo remover.
|
||||
class RenderBuffer {
|
||||
public:
|
||||
// The constructor takes, besides from the other parameters, a vector
|
||||
// containing the number of FFTs that will be included in the spectral sums in
|
||||
// the call to SpectralSum.
|
||||
RenderBuffer(Aec3Optimization optimization,
|
||||
size_t num_bands,
|
||||
size_t size,
|
||||
const std::vector<size_t> num_ffts_for_spectral_sums);
|
||||
~RenderBuffer();
|
||||
|
||||
// Clears the buffer.
|
||||
void Clear();
|
||||
|
||||
// Insert a block into the buffer.
|
||||
void Insert(const std::vector<std::vector<float>>& block);
|
||||
|
||||
// Gets the last inserted block.
|
||||
const std::vector<std::vector<float>>& MostRecentBlock() const {
|
||||
return last_block_;
|
||||
}
|
||||
|
||||
// Get the spectrum from one of the FFTs in the buffer
|
||||
const std::array<float, kFftLengthBy2Plus1>& Spectrum(
|
||||
size_t buffer_offset_ffts) const {
|
||||
return spectrum_buffer_[(position_ + buffer_offset_ffts) %
|
||||
fft_buffer_.size()];
|
||||
}
|
||||
|
||||
// Returns the sum of the spectrums for a certain number of FFTs.
|
||||
const std::array<float, kFftLengthBy2Plus1>& SpectralSum(
|
||||
size_t num_ffts) const {
|
||||
RTC_DCHECK_EQ(spectral_sums_length_, num_ffts);
|
||||
return spectral_sums_[0];
|
||||
}
|
||||
|
||||
// Returns the circular buffer.
|
||||
rtc::ArrayView<const FftData> Buffer() const { return fft_buffer_; }
|
||||
|
||||
// Returns the current position in the circular buffer
|
||||
size_t Position() const { return position_; }
|
||||
|
||||
private:
|
||||
const Aec3Optimization optimization_;
|
||||
std::vector<FftData> fft_buffer_;
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> spectrum_buffer_;
|
||||
size_t spectral_sums_length_;
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> spectral_sums_;
|
||||
size_t position_ = 0;
|
||||
std::vector<std::vector<float>> last_block_;
|
||||
const Aec3Fft fft_;
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderBuffer);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_BUFFER_H_
|
||||
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/render_buffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
|
||||
// Verifies the check for the provided numbers of Ffts to include in the
|
||||
// spectral sum.
|
||||
TEST(RenderBuffer, TooLargeNumberOfSpectralSums) {
|
||||
EXPECT_DEATH(
|
||||
RenderBuffer(Aec3Optimization::kNone, 3, 1, std::vector<size_t>(2, 1)),
|
||||
"");
|
||||
}
|
||||
|
||||
TEST(RenderBuffer, TooSmallNumberOfSpectralSums) {
|
||||
EXPECT_DEATH(
|
||||
RenderBuffer(Aec3Optimization::kNone, 3, 1, std::vector<size_t>()), "");
|
||||
}
|
||||
|
||||
// Verifies the feasibility check for the provided number of Ffts to include in
|
||||
// the spectral.
|
||||
TEST(RenderBuffer, FeasibleNumberOfFftsInSum) {
|
||||
EXPECT_DEATH(
|
||||
RenderBuffer(Aec3Optimization::kNone, 3, 1, std::vector<size_t>(1, 2)),
|
||||
"");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,217 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/render_delay_buffer.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/block_processor.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/decimator_by_4.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/fft_data.h"
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
#include "webrtc/rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
class ApiCallJitterBuffer {
|
||||
public:
|
||||
explicit ApiCallJitterBuffer(size_t num_bands) {
|
||||
buffer_.fill(std::vector<std::vector<float>>(
|
||||
num_bands, std::vector<float>(kBlockSize, 0.f)));
|
||||
}
|
||||
|
||||
~ApiCallJitterBuffer() = default;
|
||||
|
||||
void Reset() {
|
||||
size_ = 0;
|
||||
last_insert_index_ = 0;
|
||||
}
|
||||
|
||||
void Insert(const std::vector<std::vector<float>>& block) {
|
||||
RTC_DCHECK_LT(size_, buffer_.size());
|
||||
last_insert_index_ = (last_insert_index_ + 1) % buffer_.size();
|
||||
RTC_DCHECK_EQ(buffer_[last_insert_index_].size(), block.size());
|
||||
RTC_DCHECK_EQ(buffer_[last_insert_index_][0].size(), block[0].size());
|
||||
for (size_t k = 0; k < block.size(); ++k) {
|
||||
std::copy(block[k].begin(), block[k].end(),
|
||||
buffer_[last_insert_index_][k].begin());
|
||||
}
|
||||
++size_;
|
||||
}
|
||||
|
||||
void Remove(std::vector<std::vector<float>>* block) {
|
||||
RTC_DCHECK_LT(0, size_);
|
||||
--size_;
|
||||
const size_t extract_index =
|
||||
(last_insert_index_ - size_ + buffer_.size()) % buffer_.size();
|
||||
for (size_t k = 0; k < block->size(); ++k) {
|
||||
std::copy(buffer_[extract_index][k].begin(),
|
||||
buffer_[extract_index][k].end(), (*block)[k].begin());
|
||||
}
|
||||
}
|
||||
|
||||
size_t Size() const { return size_; }
|
||||
bool Full() const { return size_ >= (buffer_.size()); }
|
||||
bool Empty() const { return size_ == 0; }
|
||||
|
||||
private:
|
||||
std::array<std::vector<std::vector<float>>, kMaxApiCallsJitterBlocks> buffer_;
|
||||
size_t size_ = 0;
|
||||
int last_insert_index_ = 0;
|
||||
};
|
||||
|
||||
class RenderDelayBufferImpl final : public RenderDelayBuffer {
|
||||
public:
|
||||
explicit RenderDelayBufferImpl(size_t num_bands);
|
||||
~RenderDelayBufferImpl() override;
|
||||
|
||||
void Reset() override;
|
||||
bool Insert(const std::vector<std::vector<float>>& block) override;
|
||||
bool UpdateBuffers() override;
|
||||
void SetDelay(size_t delay) override;
|
||||
size_t Delay() const override { return delay_; }
|
||||
|
||||
const RenderBuffer& GetRenderBuffer() const override { return fft_buffer_; }
|
||||
|
||||
const DownsampledRenderBuffer& GetDownsampledRenderBuffer() const override {
|
||||
return downsampled_render_buffer_;
|
||||
}
|
||||
|
||||
private:
|
||||
const Aec3Optimization optimization_;
|
||||
std::array<std::vector<std::vector<float>>, kRenderDelayBufferSize> buffer_;
|
||||
size_t delay_ = 0;
|
||||
size_t last_insert_index_ = 0;
|
||||
RenderBuffer fft_buffer_;
|
||||
DownsampledRenderBuffer downsampled_render_buffer_;
|
||||
DecimatorBy4 render_decimator_;
|
||||
ApiCallJitterBuffer api_call_jitter_buffer_;
|
||||
const std::vector<std::vector<float>> zero_block_;
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayBufferImpl);
|
||||
};
|
||||
|
||||
RenderDelayBufferImpl::RenderDelayBufferImpl(size_t num_bands)
|
||||
: optimization_(DetectOptimization()),
|
||||
fft_buffer_(
|
||||
optimization_,
|
||||
num_bands,
|
||||
std::max(kResidualEchoPowerRenderWindowSize, kAdaptiveFilterLength),
|
||||
std::vector<size_t>(1, kAdaptiveFilterLength)),
|
||||
api_call_jitter_buffer_(num_bands),
|
||||
zero_block_(num_bands, std::vector<float>(kBlockSize, 0.f)) {
|
||||
buffer_.fill(std::vector<std::vector<float>>(
|
||||
num_bands, std::vector<float>(kBlockSize, 0.f)));
|
||||
|
||||
RTC_DCHECK_LT(buffer_.size(), downsampled_render_buffer_.buffer.size());
|
||||
}
|
||||
|
||||
RenderDelayBufferImpl::~RenderDelayBufferImpl() = default;
|
||||
|
||||
void RenderDelayBufferImpl::Reset() {
|
||||
// Empty all data in the buffers.
|
||||
delay_ = 0;
|
||||
last_insert_index_ = 0;
|
||||
downsampled_render_buffer_.position = 0;
|
||||
downsampled_render_buffer_.buffer.fill(0.f);
|
||||
fft_buffer_.Clear();
|
||||
api_call_jitter_buffer_.Reset();
|
||||
for (auto& c : buffer_) {
|
||||
for (auto& b : c) {
|
||||
std::fill(b.begin(), b.end(), 0.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderDelayBufferImpl::Insert(
|
||||
const std::vector<std::vector<float>>& block) {
|
||||
RTC_DCHECK_EQ(block.size(), buffer_[0].size());
|
||||
RTC_DCHECK_EQ(block[0].size(), buffer_[0][0].size());
|
||||
|
||||
if (api_call_jitter_buffer_.Full()) {
|
||||
// Report buffer overrun and let the caller handle the overrun.
|
||||
return false;
|
||||
}
|
||||
api_call_jitter_buffer_.Insert(block);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderDelayBufferImpl::UpdateBuffers() {
|
||||
bool underrun = true;
|
||||
// Update the buffers with a new block if such is available, otherwise insert
|
||||
// a block of silence.
|
||||
if (api_call_jitter_buffer_.Size() > 0) {
|
||||
last_insert_index_ = (last_insert_index_ + 1) % buffer_.size();
|
||||
api_call_jitter_buffer_.Remove(&buffer_[last_insert_index_]);
|
||||
underrun = false;
|
||||
}
|
||||
|
||||
downsampled_render_buffer_.position =
|
||||
(downsampled_render_buffer_.position - kSubBlockSize +
|
||||
downsampled_render_buffer_.buffer.size()) %
|
||||
downsampled_render_buffer_.buffer.size();
|
||||
|
||||
std::array<float, kSubBlockSize> render_downsampled;
|
||||
if (underrun) {
|
||||
render_decimator_.Decimate(zero_block_[0], render_downsampled);
|
||||
} else {
|
||||
render_decimator_.Decimate(buffer_[last_insert_index_][0],
|
||||
render_downsampled);
|
||||
}
|
||||
std::copy(render_downsampled.rbegin(), render_downsampled.rend(),
|
||||
downsampled_render_buffer_.buffer.begin() +
|
||||
downsampled_render_buffer_.position);
|
||||
|
||||
if (underrun) {
|
||||
fft_buffer_.Insert(zero_block_);
|
||||
} else {
|
||||
fft_buffer_.Insert(buffer_[(last_insert_index_ - delay_ + buffer_.size()) %
|
||||
buffer_.size()]);
|
||||
}
|
||||
return !underrun;
|
||||
}
|
||||
|
||||
void RenderDelayBufferImpl::SetDelay(size_t delay) {
|
||||
if (delay_ == delay) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is a new delay set, clear the fft buffer.
|
||||
fft_buffer_.Clear();
|
||||
|
||||
if ((buffer_.size() - 1) < delay) {
|
||||
// If the desired delay is larger than the delay buffer, shorten the delay
|
||||
// buffer size to achieve the desired alignment with the available buffer
|
||||
// size.
|
||||
downsampled_render_buffer_.position =
|
||||
(downsampled_render_buffer_.position +
|
||||
kSubBlockSize * (delay - (buffer_.size() - 1))) %
|
||||
downsampled_render_buffer_.buffer.size();
|
||||
|
||||
last_insert_index_ =
|
||||
(last_insert_index_ - (delay - (buffer_.size() - 1)) + buffer_.size()) %
|
||||
buffer_.size();
|
||||
delay_ = buffer_.size() - 1;
|
||||
} else {
|
||||
delay_ = delay;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderDelayBuffer* RenderDelayBuffer::Create(size_t num_bands) {
|
||||
return new RenderDelayBufferImpl(num_bands);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_DELAY_BUFFER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_DELAY_BUFFER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/downsampled_render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/fft_data.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_buffer.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Class for buffering the incoming render blocks such that these may be
|
||||
// extracted with a specified delay.
|
||||
class RenderDelayBuffer {
|
||||
public:
|
||||
static RenderDelayBuffer* Create(size_t num_bands);
|
||||
virtual ~RenderDelayBuffer() = default;
|
||||
|
||||
// Resets the buffer data.
|
||||
virtual void Reset() = 0;
|
||||
|
||||
// Inserts a block into the buffer and returns true if the insert is
|
||||
// successful.
|
||||
virtual bool Insert(const std::vector<std::vector<float>>& block) = 0;
|
||||
|
||||
// Updates the buffers one step based on the specified buffer delay. Returns
|
||||
// true if there was no overrun, otherwise returns false.
|
||||
virtual bool UpdateBuffers() = 0;
|
||||
|
||||
// Sets the buffer delay.
|
||||
virtual void SetDelay(size_t delay) = 0;
|
||||
|
||||
// Gets the buffer delay.
|
||||
virtual size_t Delay() const = 0;
|
||||
|
||||
// Returns the render buffer for the echo remover.
|
||||
virtual const RenderBuffer& GetRenderBuffer() const = 0;
|
||||
|
||||
// Returns the downsampled render buffer.
|
||||
virtual const DownsampledRenderBuffer& GetDownsampledRenderBuffer() const = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_DELAY_BUFFER_H_
|
||||
@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/render_delay_buffer.h"
|
||||
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "webrtc/rtc_base/random.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
std::string ProduceDebugText(int sample_rate_hz) {
|
||||
std::ostringstream ss;
|
||||
ss << "Sample rate: " << sample_rate_hz;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies that the buffer overflow is correctly reported.
|
||||
TEST(RenderDelayBuffer, BufferOverflow) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::vector<std::vector<float>> block_to_insert(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
for (size_t k = 0; k < kMaxApiCallsJitterBlocks; ++k) {
|
||||
EXPECT_TRUE(delay_buffer->Insert(block_to_insert));
|
||||
}
|
||||
EXPECT_FALSE(delay_buffer->Insert(block_to_insert));
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the check for available block works.
|
||||
TEST(RenderDelayBuffer, AvailableBlock) {
|
||||
constexpr size_t kNumBands = 1;
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(
|
||||
RenderDelayBuffer::Create(kNumBands));
|
||||
std::vector<std::vector<float>> input_block(
|
||||
kNumBands, std::vector<float>(kBlockSize, 1.f));
|
||||
EXPECT_TRUE(delay_buffer->Insert(input_block));
|
||||
delay_buffer->UpdateBuffers();
|
||||
}
|
||||
|
||||
// Verifies the SetDelay method.
|
||||
TEST(RenderDelayBuffer, SetDelay) {
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(1));
|
||||
EXPECT_EQ(0u, delay_buffer->Delay());
|
||||
for (size_t delay = 0; delay < 20; ++delay) {
|
||||
delay_buffer->SetDelay(delay);
|
||||
EXPECT_EQ(delay, delay_buffer->Delay());
|
||||
}
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
|
||||
// Verifies the check for feasible delay.
|
||||
// TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
|
||||
// tests on test bots has been fixed.
|
||||
TEST(RenderDelayBuffer, DISABLED_WrongDelay) {
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(3));
|
||||
EXPECT_DEATH(delay_buffer->SetDelay(21), "");
|
||||
}
|
||||
|
||||
// Verifies the check for the number of bands in the inserted blocks.
|
||||
TEST(RenderDelayBuffer, WrongNumberOfBands) {
|
||||
for (auto rate : {16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::vector<std::vector<float>> block_to_insert(
|
||||
NumBandsForRate(rate < 48000 ? rate + 16000 : 16000),
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
EXPECT_DEATH(delay_buffer->Insert(block_to_insert), "");
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies the check of the length of the inserted blocks.
|
||||
TEST(RenderDelayBuffer, WrongBlockLength) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
std::vector<std::vector<float>> block_to_insert(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize - 1, 0.f));
|
||||
EXPECT_DEATH(delay_buffer->Insert(block_to_insert), "");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/render_delay_controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/echo_path_delay_estimator.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_delay_controller_metrics.h"
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
#include "webrtc/rtc_base/atomicops.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
class RenderDelayControllerImpl final : public RenderDelayController {
|
||||
public:
|
||||
RenderDelayControllerImpl(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz);
|
||||
~RenderDelayControllerImpl() override;
|
||||
void Reset() override;
|
||||
void SetDelay(size_t render_delay) override;
|
||||
size_t GetDelay(const DownsampledRenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const float> capture) override;
|
||||
rtc::Optional<size_t> AlignmentHeadroomSamples() const override {
|
||||
return headroom_samples_;
|
||||
}
|
||||
|
||||
private:
|
||||
static int instance_count_;
|
||||
std::unique_ptr<ApmDataDumper> data_dumper_;
|
||||
size_t delay_ = kMinEchoPathDelayBlocks;
|
||||
EchoPathDelayEstimator delay_estimator_;
|
||||
size_t blocks_since_last_delay_estimate_ = 300000;
|
||||
int echo_path_delay_samples_ = kMinEchoPathDelayBlocks * kBlockSize;
|
||||
size_t align_call_counter_ = 0;
|
||||
rtc::Optional<size_t> headroom_samples_;
|
||||
RenderDelayControllerMetrics metrics_;
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayControllerImpl);
|
||||
};
|
||||
|
||||
size_t ComputeNewBufferDelay(size_t current_delay,
|
||||
size_t echo_path_delay_samples) {
|
||||
// The below division is not exact and the truncation is intended.
|
||||
const int echo_path_delay_blocks = echo_path_delay_samples / kBlockSize;
|
||||
constexpr int kDelayHeadroomBlocks = 1;
|
||||
|
||||
// Compute the buffer delay increase required to achieve the desired latency.
|
||||
size_t new_delay = std::max(echo_path_delay_blocks - kDelayHeadroomBlocks, 0);
|
||||
|
||||
// Add hysteresis.
|
||||
if (new_delay == current_delay + 1) {
|
||||
new_delay = current_delay;
|
||||
}
|
||||
|
||||
return new_delay;
|
||||
}
|
||||
|
||||
int RenderDelayControllerImpl::instance_count_ = 0;
|
||||
|
||||
RenderDelayControllerImpl::RenderDelayControllerImpl(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz)
|
||||
: data_dumper_(
|
||||
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
||||
delay_estimator_(data_dumper_.get(), config) {
|
||||
RTC_DCHECK(ValidFullBandRate(sample_rate_hz));
|
||||
}
|
||||
|
||||
RenderDelayControllerImpl::~RenderDelayControllerImpl() = default;
|
||||
|
||||
void RenderDelayControllerImpl::Reset() {
|
||||
delay_ = kMinEchoPathDelayBlocks;
|
||||
blocks_since_last_delay_estimate_ = 300000;
|
||||
echo_path_delay_samples_ = delay_ * kBlockSize;
|
||||
align_call_counter_ = 0;
|
||||
headroom_samples_ = rtc::Optional<size_t>();
|
||||
|
||||
delay_estimator_.Reset();
|
||||
}
|
||||
|
||||
void RenderDelayControllerImpl::SetDelay(size_t render_delay) {
|
||||
if (delay_ != render_delay) {
|
||||
// If a the delay set does not match the actual delay, reset the delay
|
||||
// controller.
|
||||
Reset();
|
||||
delay_ = render_delay;
|
||||
}
|
||||
}
|
||||
|
||||
size_t RenderDelayControllerImpl::GetDelay(
|
||||
const DownsampledRenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const float> capture) {
|
||||
RTC_DCHECK_EQ(kBlockSize, capture.size());
|
||||
|
||||
++align_call_counter_;
|
||||
rtc::Optional<size_t> echo_path_delay_samples =
|
||||
delay_estimator_.EstimateDelay(render_buffer, capture);
|
||||
if (echo_path_delay_samples) {
|
||||
blocks_since_last_delay_estimate_ = 0;
|
||||
echo_path_delay_samples_ = *echo_path_delay_samples;
|
||||
|
||||
// Compute and set new render delay buffer delay.
|
||||
const size_t new_delay =
|
||||
ComputeNewBufferDelay(delay_, echo_path_delay_samples_);
|
||||
if (align_call_counter_ > kNumBlocksPerSecond) {
|
||||
delay_ = new_delay;
|
||||
|
||||
// Update render delay buffer headroom.
|
||||
const int headroom = echo_path_delay_samples_ - delay_ * kBlockSize;
|
||||
RTC_DCHECK_LE(0, headroom);
|
||||
headroom_samples_ = rtc::Optional<size_t>(headroom);
|
||||
}
|
||||
}
|
||||
|
||||
metrics_.Update(echo_path_delay_samples, delay_);
|
||||
|
||||
data_dumper_->DumpRaw("aec3_render_delay_controller_delay", 1,
|
||||
&echo_path_delay_samples_);
|
||||
data_dumper_->DumpRaw("aec3_render_delay_controller_buffer_delay", delay_);
|
||||
|
||||
return delay_;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderDelayController* RenderDelayController::Create(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz) {
|
||||
return new RenderDelayControllerImpl(config, sample_rate_hz);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_DELAY_CONTROLLER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_DELAY_CONTROLLER_H_
|
||||
|
||||
#include "webrtc/api/array_view.h"
|
||||
#include "webrtc/api/optional.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/downsampled_render_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_delay_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Class for aligning the render and capture signal using a RenderDelayBuffer.
|
||||
class RenderDelayController {
|
||||
public:
|
||||
static RenderDelayController* Create(
|
||||
const AudioProcessing::Config::EchoCanceller3& config,
|
||||
int sample_rate_hz);
|
||||
virtual ~RenderDelayController() = default;
|
||||
|
||||
// Resets the delay controller.
|
||||
virtual void Reset() = 0;
|
||||
|
||||
// Receives the externally used delay.
|
||||
virtual void SetDelay(size_t render_delay) = 0;
|
||||
|
||||
// Aligns the render buffer content with the capture signal.
|
||||
virtual size_t GetDelay(const DownsampledRenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const float> capture) = 0;
|
||||
|
||||
// Returns an approximate value for the headroom in the buffer alignment.
|
||||
virtual rtc::Optional<size_t> AlignmentHeadroomSamples() const = 0;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_DELAY_CONTROLLER_H_
|
||||
@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/render_delay_controller_metrics.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
enum class DelayReliabilityCategory {
|
||||
kNone,
|
||||
kPoor,
|
||||
kMedium,
|
||||
kGood,
|
||||
kExcellent,
|
||||
kNumCategories
|
||||
};
|
||||
enum class DelayChangesCategory {
|
||||
kNone,
|
||||
kFew,
|
||||
kSeveral,
|
||||
kMany,
|
||||
kConstant,
|
||||
kNumCategories
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void RenderDelayControllerMetrics::Update(rtc::Optional<size_t> delay_samples,
|
||||
size_t buffer_delay_blocks) {
|
||||
++call_counter_;
|
||||
|
||||
if (!initial_update) {
|
||||
if (delay_samples) {
|
||||
++reliable_delay_estimate_counter_;
|
||||
size_t delay_blocks = (*delay_samples) / kBlockSize;
|
||||
|
||||
if (delay_blocks != delay_blocks_) {
|
||||
++delay_change_counter_;
|
||||
delay_blocks_ = delay_blocks;
|
||||
}
|
||||
}
|
||||
} else if (++initial_call_counter_ == 5 * kNumBlocksPerSecond) {
|
||||
initial_update = false;
|
||||
}
|
||||
|
||||
if (call_counter_ == kMetricsReportingIntervalBlocks) {
|
||||
int value_to_report = static_cast<int>(delay_blocks_);
|
||||
value_to_report = std::min(124, value_to_report);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR("WebRTC.Audio.EchoCanceller.EchoPathDelay",
|
||||
value_to_report, 0, 124, 125);
|
||||
|
||||
value_to_report = static_cast<int>(buffer_delay_blocks);
|
||||
value_to_report = std::min(124, value_to_report);
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR("WebRTC.Audio.EchoCanceller.BufferDelay",
|
||||
value_to_report, 0, 124, 125);
|
||||
|
||||
DelayReliabilityCategory delay_reliability;
|
||||
if (reliable_delay_estimate_counter_ == 0) {
|
||||
delay_reliability = DelayReliabilityCategory::kNone;
|
||||
} else if (reliable_delay_estimate_counter_ > (call_counter_ >> 1)) {
|
||||
delay_reliability = DelayReliabilityCategory::kExcellent;
|
||||
} else if (reliable_delay_estimate_counter_ > 100) {
|
||||
delay_reliability = DelayReliabilityCategory::kGood;
|
||||
} else if (reliable_delay_estimate_counter_ > 10) {
|
||||
delay_reliability = DelayReliabilityCategory::kMedium;
|
||||
} else {
|
||||
delay_reliability = DelayReliabilityCategory::kPoor;
|
||||
}
|
||||
RTC_HISTOGRAM_ENUMERATION(
|
||||
"WebRTC.Audio.EchoCanceller.ReliableDelayEstimates",
|
||||
static_cast<int>(delay_reliability),
|
||||
static_cast<int>(DelayReliabilityCategory::kNumCategories));
|
||||
|
||||
DelayChangesCategory delay_changes;
|
||||
if (delay_change_counter_ == 0) {
|
||||
delay_changes = DelayChangesCategory::kNone;
|
||||
} else if (delay_change_counter_ > 10) {
|
||||
delay_changes = DelayChangesCategory::kConstant;
|
||||
} else if (delay_change_counter_ > 5) {
|
||||
delay_changes = DelayChangesCategory::kMany;
|
||||
} else if (delay_change_counter_ > 2) {
|
||||
delay_changes = DelayChangesCategory::kSeveral;
|
||||
} else {
|
||||
delay_changes = DelayChangesCategory::kFew;
|
||||
}
|
||||
RTC_HISTOGRAM_ENUMERATION(
|
||||
"WebRTC.Audio.EchoCanceller.DelayChanges",
|
||||
static_cast<int>(delay_changes),
|
||||
static_cast<int>(DelayChangesCategory::kNumCategories));
|
||||
|
||||
metrics_reported_ = true;
|
||||
call_counter_ = 0;
|
||||
ResetMetrics();
|
||||
} else {
|
||||
metrics_reported_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderDelayControllerMetrics::ResetMetrics() {
|
||||
delay_change_counter_ = 0;
|
||||
reliable_delay_estimate_counter_ = 0;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_DELAY_CONTROLLER_METRICS_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_DELAY_CONTROLLER_METRICS_H_
|
||||
|
||||
#include "webrtc/api/optional.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Handles the reporting of metrics for the render delay controller.
|
||||
class RenderDelayControllerMetrics {
|
||||
public:
|
||||
RenderDelayControllerMetrics() = default;
|
||||
|
||||
// Updates the metric with new data.
|
||||
void Update(rtc::Optional<size_t> delay_samples, size_t buffer_delay_blocks);
|
||||
|
||||
// Returns true if the metrics have just been reported, otherwise false.
|
||||
bool MetricsReported() { return metrics_reported_; }
|
||||
|
||||
private:
|
||||
// Resets the metrics.
|
||||
void ResetMetrics();
|
||||
|
||||
size_t delay_blocks_ = 0;
|
||||
int reliable_delay_estimate_counter_ = 0;
|
||||
int delay_change_counter_ = 0;
|
||||
int call_counter_ = 0;
|
||||
int initial_call_counter_ = 0;
|
||||
bool metrics_reported_ = false;
|
||||
bool initial_update = true;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(RenderDelayControllerMetrics);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_DELAY_CONTROLLER_METRICS_H_
|
||||
@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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/modules/audio_processing/aec3/render_delay_controller_metrics.h"
|
||||
#include "webrtc/api/optional.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Verify the general functionality of RenderDelayControllerMetrics.
|
||||
TEST(RenderDelayControllerMetrics, NormalUsage) {
|
||||
RenderDelayControllerMetrics metrics;
|
||||
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
for (int k = 0; k < kMetricsReportingIntervalBlocks - 1; ++k) {
|
||||
metrics.Update(rtc::Optional<size_t>(), 0);
|
||||
EXPECT_FALSE(metrics.MetricsReported());
|
||||
}
|
||||
metrics.Update(rtc::Optional<size_t>(), 0);
|
||||
EXPECT_TRUE(metrics.MetricsReported());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user