diff --git a/AEC3.sln b/AEC3.sln
new file mode 100644
index 0000000..4e76bc0
--- /dev/null
+++ b/AEC3.sln
@@ -0,0 +1,62 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "api", "api.vcxproj", "{40294AC8-1111-4B68-8666-D8ABFFC2DAEE}"
+ ProjectSection(ProjectDependencies) = postProject
+ {C0E01E32-A6A3-439D-A3D5-BECB3EFAE67F} = {C0E01E32-A6A3-439D-A3D5-BECB3EFAE67F}
+ {2D2114D9-800B-4C74-A770-119325A6A00B} = {2D2114D9-800B-4C74-A770-119325A6A00B}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AEC3", "AEC3.vcxproj", "{2D2114D9-800B-4C74-A770-119325A6A00B}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "base", "base.vcxproj", "{C0E01E32-A6A3-439D-A3D5-BECB3EFAE67F}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "demo", "demo.vcxproj", "{DF302EC6-FC8F-4BBB-9807-E943E9D0AA90}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {40294AC8-1111-4B68-8666-D8ABFFC2DAEE}.Debug|x64.ActiveCfg = Debug|x64
+ {40294AC8-1111-4B68-8666-D8ABFFC2DAEE}.Debug|x64.Build.0 = Debug|x64
+ {40294AC8-1111-4B68-8666-D8ABFFC2DAEE}.Debug|x86.ActiveCfg = Debug|Win32
+ {40294AC8-1111-4B68-8666-D8ABFFC2DAEE}.Debug|x86.Build.0 = Debug|Win32
+ {40294AC8-1111-4B68-8666-D8ABFFC2DAEE}.Release|x64.ActiveCfg = Release|x64
+ {40294AC8-1111-4B68-8666-D8ABFFC2DAEE}.Release|x64.Build.0 = Release|x64
+ {40294AC8-1111-4B68-8666-D8ABFFC2DAEE}.Release|x86.ActiveCfg = Release|Win32
+ {40294AC8-1111-4B68-8666-D8ABFFC2DAEE}.Release|x86.Build.0 = Release|Win32
+ {2D2114D9-800B-4C74-A770-119325A6A00B}.Debug|x64.ActiveCfg = Debug|x64
+ {2D2114D9-800B-4C74-A770-119325A6A00B}.Debug|x64.Build.0 = Debug|x64
+ {2D2114D9-800B-4C74-A770-119325A6A00B}.Debug|x86.ActiveCfg = Debug|Win32
+ {2D2114D9-800B-4C74-A770-119325A6A00B}.Debug|x86.Build.0 = Debug|Win32
+ {2D2114D9-800B-4C74-A770-119325A6A00B}.Release|x64.ActiveCfg = Release|x64
+ {2D2114D9-800B-4C74-A770-119325A6A00B}.Release|x64.Build.0 = Release|x64
+ {2D2114D9-800B-4C74-A770-119325A6A00B}.Release|x86.ActiveCfg = Release|Win32
+ {2D2114D9-800B-4C74-A770-119325A6A00B}.Release|x86.Build.0 = Release|Win32
+ {C0E01E32-A6A3-439D-A3D5-BECB3EFAE67F}.Debug|x64.ActiveCfg = Debug|x64
+ {C0E01E32-A6A3-439D-A3D5-BECB3EFAE67F}.Debug|x64.Build.0 = Debug|x64
+ {C0E01E32-A6A3-439D-A3D5-BECB3EFAE67F}.Debug|x86.ActiveCfg = Debug|Win32
+ {C0E01E32-A6A3-439D-A3D5-BECB3EFAE67F}.Debug|x86.Build.0 = Debug|Win32
+ {C0E01E32-A6A3-439D-A3D5-BECB3EFAE67F}.Release|x64.ActiveCfg = Release|x64
+ {C0E01E32-A6A3-439D-A3D5-BECB3EFAE67F}.Release|x64.Build.0 = Release|x64
+ {C0E01E32-A6A3-439D-A3D5-BECB3EFAE67F}.Release|x86.ActiveCfg = Release|Win32
+ {C0E01E32-A6A3-439D-A3D5-BECB3EFAE67F}.Release|x86.Build.0 = Release|Win32
+ {DF302EC6-FC8F-4BBB-9807-E943E9D0AA90}.Debug|x64.ActiveCfg = Debug|x64
+ {DF302EC6-FC8F-4BBB-9807-E943E9D0AA90}.Debug|x64.Build.0 = Debug|x64
+ {DF302EC6-FC8F-4BBB-9807-E943E9D0AA90}.Debug|x86.ActiveCfg = Debug|Win32
+ {DF302EC6-FC8F-4BBB-9807-E943E9D0AA90}.Debug|x86.Build.0 = Debug|Win32
+ {DF302EC6-FC8F-4BBB-9807-E943E9D0AA90}.Release|x64.ActiveCfg = Release|x64
+ {DF302EC6-FC8F-4BBB-9807-E943E9D0AA90}.Release|x64.Build.0 = Release|x64
+ {DF302EC6-FC8F-4BBB-9807-E943E9D0AA90}.Release|x86.ActiveCfg = Release|Win32
+ {DF302EC6-FC8F-4BBB-9807-E943E9D0AA90}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/AEC3.vcxproj b/AEC3.vcxproj
new file mode 100644
index 0000000..99745c8
--- /dev/null
+++ b/AEC3.vcxproj
@@ -0,0 +1,308 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {2D2114D9-800B-4C74-A770-119325A6A00B}
+ AEC3
+ 8.1
+
+
+
+ StaticLibrary
+ true
+ v140
+ MultiByte
+
+
+ StaticLibrary
+ false
+ v140
+ true
+ MultiByte
+
+
+ StaticLibrary
+ true
+ v140
+ MultiByte
+
+
+ StaticLibrary
+ false
+ v140
+ true
+ MultiByte
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)output\$(Configuration)_$(Platform)\
+ $(OutDir)$(ProjectName)\
+
+
+ $(SolutionDir)output\$(Configuration)_$(Platform)\
+ $(OutDir)$(ProjectName)\
+
+
+ $(SolutionDir)output\$(Configuration)_$(Platform)\
+ $(OutDir)$(ProjectName)\
+
+
+ $(SolutionDir)output\$(Configuration)_$(Platform)\
+ $(OutDir)$(ProjectName)\
+
+
+
+ Level1
+ Disabled
+ true
+ $(SolutionDir);$(SolutionDir)base;$(SolutionDir)base\abseil;$(SolutionDir)base\jsoncpp\include;$(SolutionDir)audio_processing;$(SolutionDir)audio_processing\aec3
+ WEBRTC_WIN;WIN32;_WINDOWS;_DEBUG;NOMINMAX;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)
+ false
+ true
+ 4005;4068;4180;4244;4267;4503;4800
+
+
+ $(SolutionDir)libs\$(Configuration)_$(Platform)\
+ rtc_base.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+ Disabled
+ true
+ $(SolutionDir);$(SolutionDir)base;$(SolutionDir)base\abseil;$(SolutionDir)base\jsoncpp\include;$(SolutionDir)audio_processing;$(SolutionDir)audio_processing\aec3
+ WEBRTC_WIN;WIN32;_WINDOWS;_DEBUG;NOMINMAX;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)
+ true
+ false
+ 4005;4068;4180;4244;4267;4503;4800
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ $(SolutionDir);$(SolutionDir)base;$(SolutionDir)base\abseil;$(SolutionDir)base\jsoncpp\include;$(SolutionDir)audio_processing;$(SolutionDir)audio_processing\aec3
+ WEBRTC_WIN;WIN32;_WINDOWS;DEBUG;NOMINMAX;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)
+ true
+ 4005;4068;4180;4244;4267;4503;4800
+
+
+ true
+ true
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ $(SolutionDir);$(SolutionDir)base;$(SolutionDir)base\abseil;$(SolutionDir)base\jsoncpp\include;$(SolutionDir)audio_processing;$(SolutionDir)audio_processing\aec3
+ WEBRTC_WIN;WIN32;_WINDOWS;DEBUG;NOMINMAX;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)
+ true
+ 4005;4068;4180;4244;4267;4503;4800
+
+
+ true
+ true
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AEC3.vcxproj.filters b/AEC3.vcxproj.filters
new file mode 100644
index 0000000..874c90c
--- /dev/null
+++ b/AEC3.vcxproj.filters
@@ -0,0 +1,483 @@
+
+
+
+
+ {543ed4ce-93b1-4f37-9f97-85e493542a24}
+
+
+ {5bfa6298-ae53-4dd1-bc35-150a560206f6}
+
+
+ {345fbea9-6bc1-45cb-a86f-f5a95f9823de}
+
+
+ {84332578-0b17-4c77-92fd-e5c4d3e87793}
+
+
+ {45d9278e-7ca9-422e-9cb1-976693b3cd81}
+
+
+ {13b0466b-a3aa-482f-bf43-b4e06665ff62}
+
+
+
+
+ audio_processing
+
+
+ audio_processing
+
+
+ audio_processing
+
+
+ audio_processing\utility
+
+
+ audio_processing\utility
+
+
+ audio_processing\utility
+
+
+ audio_processing\utility
+
+
+ audio_processing\utility
+
+
+ audio_processing\logging
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing
+
+
+ audio_processing
+
+
+ audio_processing
+
+
+ audio_processing\resampler
+
+
+ audio_processing\resampler
+
+
+ audio_processing\resampler
+
+
+ audio_processing\resampler
+
+
+ audio_processing\logging
+
+
+ audio_processing\logging
+
+
+ audio_processing
+
+
+ audio_processing
+
+
+ audio_processing
+
+
+
+
+ audio_processing
+
+
+ audio_processing
+
+
+ audio_processing
+
+
+ audio_processing\utility
+
+
+ audio_processing\utility
+
+
+ audio_processing\utility
+
+
+ audio_processing\utility
+
+
+ audio_processing\logging
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\aec3
+
+
+ audio_processing\include
+
+
+ audio_processing
+
+
+ audio_processing
+
+
+ audio_processing
+
+
+ audio_processing\resampler
+
+
+ audio_processing\resampler
+
+
+ audio_processing\include
+
+
+ audio_processing\logging
+
+
+ audio_processing\logging
+
+
+ audio_processing
+
+
+ audio_processing
+
+
+
\ No newline at end of file
diff --git a/AEC3.vcxproj.user b/AEC3.vcxproj.user
new file mode 100644
index 0000000..abe8dd8
--- /dev/null
+++ b/AEC3.vcxproj.user
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/api.filters b/api.filters
new file mode 100644
index 0000000..03c8628
--- /dev/null
+++ b/api.filters
@@ -0,0 +1,17 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
\ No newline at end of file
diff --git a/api.vcxproj b/api.vcxproj
new file mode 100644
index 0000000..d87476c
--- /dev/null
+++ b/api.vcxproj
@@ -0,0 +1,170 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {2d2114d9-800b-4c74-a770-119325a6a00b}
+
+
+ {c0e01e32-a6a3-439d-a3d5-becb3efae67f}
+
+
+
+ {40294AC8-1111-4B68-8666-D8ABFFC2DAEE}
+ rtc_base
+ 8.1
+
+
+
+ StaticLibrary
+ true
+ v140
+ MultiByte
+
+
+ StaticLibrary
+ false
+ v140
+ true
+ MultiByte
+
+
+ StaticLibrary
+ true
+ v140
+ MultiByte
+
+
+ StaticLibrary
+ false
+ v140
+ true
+ MultiByte
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)output\$(Configuration)_$(Platform)\
+ $(OutDir)$(ProjectName)\
+
+
+ $(SolutionDir)output\$(Configuration)_$(Platform)\
+ $(OutDir)$(ProjectName)\
+
+
+ $(SolutionDir)output\$(Configuration)_$(Platform)\
+ $(OutDir)$(ProjectName)\
+
+
+ $(SolutionDir)output\$(Configuration)_$(Platform)\
+ $(OutDir)$(ProjectName)\
+
+
+
+ Level1
+ Disabled
+ true
+ $(SolutionDir);$(SolutionDir)base\;$(SolutionDir)base\abseil;$(SolutionDir)base\jsoncpp\include;%(AdditionalIncludeDirectories)
+ WEBRTC_WIN;WIN32;_WINDOWS;_DEBUG;NOMINMAX;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)
+ 4005;4068;4180;4244;4267;4503;4800
+
+
+ %(AdditionalLibraryDirectories)
+ winmm.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+ Disabled
+ true
+ $(SolutionDir);$(SolutionDir)base\;$(SolutionDir)base\abseil;$(SolutionDir)base\jsoncpp\include;%(AdditionalIncludeDirectories)
+ WEBRTC_WIN;WIN32;_WINDOWS;_DEBUG;NOMINMAX;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)
+ 4005;4068;4180;4244;4267;4503;4800
+
+
+ winmm.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ $(SolutionDir);$(SolutionDir)base\;$(SolutionDir)base\abseil;$(SolutionDir)base\jsoncpp\include;%(AdditionalIncludeDirectories)
+ WEBRTC_WIN;WIN32;_WINDOWS;DEBUG;NOMINMAX;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)
+ 4005;4068;4180;4244;4267;4503;4800
+
+
+ true
+ true
+ winmm.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ $(SolutionDir);$(SolutionDir)base\;$(SolutionDir)base\abseil;$(SolutionDir)base\jsoncpp\include;%(AdditionalIncludeDirectories)
+ WEBRTC_WIN;WIN32;_WINDOWS;DEBUG;NOMINMAX;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)
+ 4005;4068;4180;4244;4267;4503;4800
+
+
+ true
+ true
+ winmm.lib;%(AdditionalDependencies)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/api.vcxproj.filters b/api.vcxproj.filters
new file mode 100644
index 0000000..081099c
--- /dev/null
+++ b/api.vcxproj.filters
@@ -0,0 +1,33 @@
+
+
+
+
+ {3c7fd86a-aaf5-4b70-9a89-89ecf78fac3f}
+
+
+
+
+ api
+
+
+ api
+
+
+ api
+
+
+
+
+ api
+
+
+ api
+
+
+ api
+
+
+ api
+
+
+
\ No newline at end of file
diff --git a/api/echo_canceller3_config.cc b/api/echo_canceller3_config.cc
new file mode 100644
index 0000000..bde37b0
--- /dev/null
+++ b/api/echo_canceller3_config.cc
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2018 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 "api/echo_canceller3_config.h"
+
+#include
+#include
+
+#include "rtc_base/checks.h"
+#include "rtc_base/numerics/safe_minmax.h"
+
+namespace webrtc {
+namespace {
+bool Limit(float* value, float min, float max) {
+ float clamped = rtc::SafeClamp(*value, min, max);
+ clamped = std::isfinite(clamped) ? clamped : min;
+ bool res = *value == clamped;
+ *value = clamped;
+ return res;
+}
+
+bool Limit(size_t* value, size_t min, size_t max) {
+ size_t clamped = rtc::SafeClamp(*value, min, max);
+ bool res = *value == clamped;
+ *value = clamped;
+ return res;
+}
+
+bool Limit(int* value, int min, int max) {
+ int clamped = rtc::SafeClamp(*value, min, max);
+ bool res = *value == clamped;
+ *value = clamped;
+ return res;
+}
+
+bool FloorLimit(size_t* value, size_t min) {
+ size_t clamped = *value >= min ? *value : min;
+ bool res = *value == clamped;
+ *value = clamped;
+ return res;
+}
+
+} // namespace
+
+EchoCanceller3Config::EchoCanceller3Config() = default;
+EchoCanceller3Config::EchoCanceller3Config(const EchoCanceller3Config& e) =
+ default;
+EchoCanceller3Config& EchoCanceller3Config::operator=(
+ const EchoCanceller3Config& e) = default;
+EchoCanceller3Config::Delay::Delay() = default;
+EchoCanceller3Config::Delay::Delay(const EchoCanceller3Config::Delay& e) =
+ default;
+EchoCanceller3Config::Delay& EchoCanceller3Config::Delay::operator=(
+ const Delay& e) = default;
+
+EchoCanceller3Config::EchoModel::EchoModel() = default;
+EchoCanceller3Config::EchoModel::EchoModel(
+ const EchoCanceller3Config::EchoModel& e) = default;
+EchoCanceller3Config::EchoModel& EchoCanceller3Config::EchoModel::operator=(
+ const EchoModel& e) = default;
+
+EchoCanceller3Config::Suppressor::Suppressor() = default;
+EchoCanceller3Config::Suppressor::Suppressor(
+ const EchoCanceller3Config::Suppressor& e) = default;
+EchoCanceller3Config::Suppressor& EchoCanceller3Config::Suppressor::operator=(
+ const Suppressor& e) = default;
+
+EchoCanceller3Config::Suppressor::MaskingThresholds::MaskingThresholds(
+ float enr_transparent,
+ float enr_suppress,
+ float emr_transparent)
+ : enr_transparent(enr_transparent),
+ enr_suppress(enr_suppress),
+ emr_transparent(emr_transparent) {}
+EchoCanceller3Config::Suppressor::MaskingThresholds::MaskingThresholds(
+ const EchoCanceller3Config::Suppressor::MaskingThresholds& e) = default;
+EchoCanceller3Config::Suppressor::MaskingThresholds&
+EchoCanceller3Config::Suppressor::MaskingThresholds::operator=(
+ const MaskingThresholds& e) = default;
+
+EchoCanceller3Config::Suppressor::Tuning::Tuning(MaskingThresholds mask_lf,
+ MaskingThresholds mask_hf,
+ float max_inc_factor,
+ float max_dec_factor_lf)
+ : mask_lf(mask_lf),
+ mask_hf(mask_hf),
+ max_inc_factor(max_inc_factor),
+ max_dec_factor_lf(max_dec_factor_lf) {}
+EchoCanceller3Config::Suppressor::Tuning::Tuning(
+ const EchoCanceller3Config::Suppressor::Tuning& e) = default;
+EchoCanceller3Config::Suppressor::Tuning&
+EchoCanceller3Config::Suppressor::Tuning::operator=(const Tuning& e) = default;
+
+bool EchoCanceller3Config::Validate(EchoCanceller3Config* config) {
+ RTC_DCHECK(config);
+ EchoCanceller3Config* c = config;
+ bool res = true;
+
+ if (c->delay.down_sampling_factor != 4 &&
+ c->delay.down_sampling_factor != 8) {
+ c->delay.down_sampling_factor = 4;
+ res = false;
+ }
+
+ res = res & Limit(&c->delay.default_delay, 0, 5000);
+ res = res & Limit(&c->delay.num_filters, 0, 5000);
+ res = res & Limit(&c->delay.delay_headroom_samples, 0, 5000);
+ res = res & Limit(&c->delay.hysteresis_limit_blocks, 0, 5000);
+ res = res & Limit(&c->delay.fixed_capture_delay_samples, 0, 5000);
+ res = res & Limit(&c->delay.delay_estimate_smoothing, 0.f, 1.f);
+ res = res & Limit(&c->delay.delay_candidate_detection_threshold, 0.f, 1.f);
+ res = res & Limit(&c->delay.delay_selection_thresholds.initial, 1, 250);
+ res = res & Limit(&c->delay.delay_selection_thresholds.converged, 1, 250);
+
+ res = res & FloorLimit(&c->filter.main.length_blocks, 1);
+ res = res & Limit(&c->filter.main.leakage_converged, 0.f, 1000.f);
+ res = res & Limit(&c->filter.main.leakage_diverged, 0.f, 1000.f);
+ res = res & Limit(&c->filter.main.error_floor, 0.f, 1000.f);
+ res = res & Limit(&c->filter.main.error_ceil, 0.f, 100000000.f);
+ res = res & Limit(&c->filter.main.noise_gate, 0.f, 100000000.f);
+
+ res = res & FloorLimit(&c->filter.main_initial.length_blocks, 1);
+ res = res & Limit(&c->filter.main_initial.leakage_converged, 0.f, 1000.f);
+ res = res & Limit(&c->filter.main_initial.leakage_diverged, 0.f, 1000.f);
+ res = res & Limit(&c->filter.main_initial.error_floor, 0.f, 1000.f);
+ res = res & Limit(&c->filter.main_initial.error_ceil, 0.f, 100000000.f);
+ res = res & Limit(&c->filter.main_initial.noise_gate, 0.f, 100000000.f);
+
+ if (c->filter.main.length_blocks < c->filter.main_initial.length_blocks) {
+ c->filter.main_initial.length_blocks = c->filter.main.length_blocks;
+ res = false;
+ }
+
+ res = res & FloorLimit(&c->filter.shadow.length_blocks, 1);
+ res = res & Limit(&c->filter.shadow.rate, 0.f, 1.f);
+ res = res & Limit(&c->filter.shadow.noise_gate, 0.f, 100000000.f);
+
+ res = res & FloorLimit(&c->filter.shadow_initial.length_blocks, 1);
+ res = res & Limit(&c->filter.shadow_initial.rate, 0.f, 1.f);
+ res = res & Limit(&c->filter.shadow_initial.noise_gate, 0.f, 100000000.f);
+
+ if (c->filter.shadow.length_blocks < c->filter.shadow_initial.length_blocks) {
+ c->filter.shadow_initial.length_blocks = c->filter.shadow.length_blocks;
+ res = false;
+ }
+
+ res = res & Limit(&c->filter.config_change_duration_blocks, 0, 100000);
+ res = res & Limit(&c->filter.initial_state_seconds, 0.f, 100.f);
+
+ res = res & Limit(&c->erle.min, 1.f, 100000.f);
+ res = res & Limit(&c->erle.max_l, 1.f, 100000.f);
+ res = res & Limit(&c->erle.max_h, 1.f, 100000.f);
+ if (c->erle.min > c->erle.max_l || c->erle.min > c->erle.max_h) {
+ c->erle.min = std::min(c->erle.max_l, c->erle.max_h);
+ res = false;
+ }
+ res = res & Limit(&c->erle.num_sections, 1, c->filter.main.length_blocks);
+
+ res = res & Limit(&c->ep_strength.default_gain, 0.f, 1000000.f);
+ res = res & Limit(&c->ep_strength.default_len, -1.f, 1.f);
+
+ res =
+ res & Limit(&c->echo_audibility.low_render_limit, 0.f, 32768.f * 32768.f);
+ res = res &
+ Limit(&c->echo_audibility.normal_render_limit, 0.f, 32768.f * 32768.f);
+ res = res & Limit(&c->echo_audibility.floor_power, 0.f, 32768.f * 32768.f);
+ res = res & Limit(&c->echo_audibility.audibility_threshold_lf, 0.f,
+ 32768.f * 32768.f);
+ res = res & Limit(&c->echo_audibility.audibility_threshold_mf, 0.f,
+ 32768.f * 32768.f);
+ res = res & Limit(&c->echo_audibility.audibility_threshold_hf, 0.f,
+ 32768.f * 32768.f);
+
+ res = res &
+ Limit(&c->render_levels.active_render_limit, 0.f, 32768.f * 32768.f);
+ res = res & Limit(&c->render_levels.poor_excitation_render_limit, 0.f,
+ 32768.f * 32768.f);
+ res = res & Limit(&c->render_levels.poor_excitation_render_limit_ds8, 0.f,
+ 32768.f * 32768.f);
+
+ res = res & Limit(&c->echo_model.noise_floor_hold, 0, 1000);
+ res = res & Limit(&c->echo_model.min_noise_floor_power, 0, 2000000.f);
+ res = res & Limit(&c->echo_model.stationary_gate_slope, 0, 1000000.f);
+ res = res & Limit(&c->echo_model.noise_gate_power, 0, 1000000.f);
+ res = res & Limit(&c->echo_model.noise_gate_slope, 0, 1000000.f);
+ res = res & Limit(&c->echo_model.render_pre_window_size, 0, 100);
+ res = res & Limit(&c->echo_model.render_post_window_size, 0, 100);
+
+ res = res & Limit(&c->suppressor.nearend_average_blocks, 1, 5000);
+
+ res = res &
+ Limit(&c->suppressor.normal_tuning.mask_lf.enr_transparent, 0.f, 100.f);
+ res = res &
+ Limit(&c->suppressor.normal_tuning.mask_lf.enr_suppress, 0.f, 100.f);
+ res = res &
+ Limit(&c->suppressor.normal_tuning.mask_lf.emr_transparent, 0.f, 100.f);
+ res = res &
+ Limit(&c->suppressor.normal_tuning.mask_hf.enr_transparent, 0.f, 100.f);
+ res = res &
+ Limit(&c->suppressor.normal_tuning.mask_hf.enr_suppress, 0.f, 100.f);
+ res = res &
+ Limit(&c->suppressor.normal_tuning.mask_hf.emr_transparent, 0.f, 100.f);
+ res = res & Limit(&c->suppressor.normal_tuning.max_inc_factor, 0.f, 100.f);
+ res = res & Limit(&c->suppressor.normal_tuning.max_dec_factor_lf, 0.f, 100.f);
+
+ res = res & Limit(&c->suppressor.nearend_tuning.mask_lf.enr_transparent, 0.f,
+ 100.f);
+ res = res &
+ Limit(&c->suppressor.nearend_tuning.mask_lf.enr_suppress, 0.f, 100.f);
+ res = res & Limit(&c->suppressor.nearend_tuning.mask_lf.emr_transparent, 0.f,
+ 100.f);
+ res = res & Limit(&c->suppressor.nearend_tuning.mask_hf.enr_transparent, 0.f,
+ 100.f);
+ res = res &
+ Limit(&c->suppressor.nearend_tuning.mask_hf.enr_suppress, 0.f, 100.f);
+ res = res & Limit(&c->suppressor.nearend_tuning.mask_hf.emr_transparent, 0.f,
+ 100.f);
+ res = res & Limit(&c->suppressor.nearend_tuning.max_inc_factor, 0.f, 100.f);
+ res =
+ res & Limit(&c->suppressor.nearend_tuning.max_dec_factor_lf, 0.f, 100.f);
+
+ res = res & Limit(&c->suppressor.dominant_nearend_detection.enr_threshold,
+ 0.f, 1000000.f);
+ res = res & Limit(&c->suppressor.dominant_nearend_detection.snr_threshold,
+ 0.f, 1000000.f);
+ res = res & Limit(&c->suppressor.dominant_nearend_detection.hold_duration, 0,
+ 10000);
+ res = res & Limit(&c->suppressor.dominant_nearend_detection.trigger_threshold,
+ 0, 10000);
+
+ res = res &
+ Limit(&c->suppressor.subband_nearend_detection.nearend_average_blocks,
+ 1, 1024);
+ res =
+ res & Limit(&c->suppressor.subband_nearend_detection.subband1.low, 0, 65);
+ res = res & Limit(&c->suppressor.subband_nearend_detection.subband1.high,
+ c->suppressor.subband_nearend_detection.subband1.low, 65);
+ res =
+ res & Limit(&c->suppressor.subband_nearend_detection.subband2.low, 0, 65);
+ res = res & Limit(&c->suppressor.subband_nearend_detection.subband2.high,
+ c->suppressor.subband_nearend_detection.subband2.low, 65);
+ res = res & Limit(&c->suppressor.subband_nearend_detection.nearend_threshold,
+ 0.f, 1.e24f);
+ res = res & Limit(&c->suppressor.subband_nearend_detection.snr_threshold, 0.f,
+ 1.e24f);
+
+ res = res & Limit(&c->suppressor.high_bands_suppression.enr_threshold, 0.f,
+ 1000000.f);
+ res = res & Limit(&c->suppressor.high_bands_suppression.max_gain_during_echo,
+ 0.f, 1.f);
+ res = res & Limit(&c->suppressor.high_bands_suppression
+ .anti_howling_activation_threshold,
+ 0.f, 32768.f * 32768.f);
+ res = res & Limit(&c->suppressor.high_bands_suppression.anti_howling_gain,
+ 0.f, 1.f);
+
+ res = res & Limit(&c->suppressor.floor_first_increase, 0.f, 1000000.f);
+
+ return res;
+}
+} // namespace webrtc
diff --git a/api/echo_canceller3_config.h b/api/echo_canceller3_config.h
new file mode 100644
index 0000000..a63318f
--- /dev/null
+++ b/api/echo_canceller3_config.h
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2018 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 API_AUDIO_ECHO_CANCELLER3_CONFIG_H_
+#define API_AUDIO_ECHO_CANCELLER3_CONFIG_H_
+
+#include // size_t
+
+#include "rtc_base/system/rtc_export.h"
+
+namespace webrtc {
+
+// Configuration struct for EchoCanceller3
+struct RTC_EXPORT EchoCanceller3Config {
+ // Checks and updates the config parameters to lie within (mostly) reasonable
+ // ranges. Returns true if and only of the config did not need to be changed.
+ static bool Validate(EchoCanceller3Config* config);
+
+ EchoCanceller3Config();
+ EchoCanceller3Config(const EchoCanceller3Config& e);
+ EchoCanceller3Config& operator=(const EchoCanceller3Config& other);
+
+ struct Buffering {
+ size_t excess_render_detection_interval_blocks = 250;
+ size_t max_allowed_excess_render_blocks = 8;
+ } buffering;
+
+ struct Delay {
+ Delay();
+ Delay(const Delay& e);
+ Delay& operator=(const Delay& e);
+ size_t default_delay = 5;
+ size_t down_sampling_factor = 4;
+ size_t num_filters = 5;
+ size_t delay_headroom_samples = 32;
+ size_t hysteresis_limit_blocks = 1;
+ size_t fixed_capture_delay_samples = 0;
+ float delay_estimate_smoothing = 0.7f;
+ float delay_candidate_detection_threshold = 0.2f;
+ struct DelaySelectionThresholds {
+ int initial;
+ int converged;
+ } delay_selection_thresholds = {5, 20};
+ bool use_external_delay_estimator = false;
+ bool log_warning_on_delay_changes = false;
+ struct AlignmentMixing {
+ bool downmix;
+ bool adaptive_selection;
+ float activity_power_threshold;
+ bool prefer_first_two_channels;
+ };
+ AlignmentMixing render_alignment_mixing = {false, true, 10000.f, true};
+ AlignmentMixing capture_alignment_mixing = {false, true, 10000.f, false};
+ } delay;
+
+ struct Filter {
+ struct MainConfiguration {
+ size_t length_blocks;
+ float leakage_converged;
+ float leakage_diverged;
+ float error_floor;
+ float error_ceil;
+ float noise_gate;
+ };
+
+ struct ShadowConfiguration {
+ size_t length_blocks;
+ float rate;
+ float noise_gate;
+ };
+
+ MainConfiguration main = {13, 0.00005f, 0.05f, 0.001f, 2.f, 20075344.f};
+ ShadowConfiguration shadow = {13, 0.7f, 20075344.f};
+
+ MainConfiguration main_initial = {12, 0.005f, 0.5f,
+ 0.001f, 2.f, 20075344.f};
+ ShadowConfiguration shadow_initial = {12, 0.9f, 20075344.f};
+
+ size_t config_change_duration_blocks = 250;
+ float initial_state_seconds = 2.5f;
+ bool conservative_initial_phase = false;
+ bool enable_shadow_filter_output_usage = true;
+ bool use_linear_filter = true;
+ bool export_linear_aec_output = false;
+ } filter;
+
+ struct Erle {
+ float min = 1.f;
+ float max_l = 4.f;
+ float max_h = 1.5f;
+ bool onset_detection = true;
+ size_t num_sections = 1;
+ bool clamp_quality_estimate_to_zero = true;
+ bool clamp_quality_estimate_to_one = true;
+ } erle;
+
+ struct EpStrength {
+ float default_gain = 1.f;
+ float default_len = 0.83f;
+ bool echo_can_saturate = true;
+ bool bounded_erl = false;
+ } ep_strength;
+
+ struct EchoAudibility {
+ float low_render_limit = 4 * 64.f;
+ float normal_render_limit = 64.f;
+ float floor_power = 2 * 64.f;
+ float audibility_threshold_lf = 10;
+ float audibility_threshold_mf = 10;
+ float audibility_threshold_hf = 10;
+ bool use_stationarity_properties = false;
+ bool use_stationarity_properties_at_init = false;
+ } echo_audibility;
+
+ struct RenderLevels {
+ float active_render_limit = 100.f;
+ float poor_excitation_render_limit = 150.f;
+ float poor_excitation_render_limit_ds8 = 20.f;
+ float render_power_gain_db = 0.f;
+ } render_levels;
+
+ struct EchoRemovalControl {
+ bool has_clock_drift = false;
+ bool linear_and_stable_echo_path = false;
+ } echo_removal_control;
+
+ struct EchoModel {
+ EchoModel();
+ EchoModel(const EchoModel& e);
+ EchoModel& operator=(const EchoModel& e);
+ size_t noise_floor_hold = 50;
+ float min_noise_floor_power = 1638400.f;
+ float stationary_gate_slope = 10.f;
+ float noise_gate_power = 27509.42f;
+ float noise_gate_slope = 0.3f;
+ size_t render_pre_window_size = 1;
+ size_t render_post_window_size = 1;
+ } echo_model;
+
+ struct Suppressor {
+ Suppressor();
+ Suppressor(const Suppressor& e);
+ Suppressor& operator=(const Suppressor& e);
+
+ size_t nearend_average_blocks = 4;
+
+ struct MaskingThresholds {
+ MaskingThresholds(float enr_transparent,
+ float enr_suppress,
+ float emr_transparent);
+ MaskingThresholds(const MaskingThresholds& e);
+ MaskingThresholds& operator=(const MaskingThresholds& e);
+ float enr_transparent;
+ float enr_suppress;
+ float emr_transparent;
+ };
+
+ struct Tuning {
+ Tuning(MaskingThresholds mask_lf,
+ MaskingThresholds mask_hf,
+ float max_inc_factor,
+ float max_dec_factor_lf);
+ Tuning(const Tuning& e);
+ Tuning& operator=(const Tuning& e);
+ MaskingThresholds mask_lf;
+ MaskingThresholds mask_hf;
+ float max_inc_factor;
+ float max_dec_factor_lf;
+ };
+
+ Tuning normal_tuning = Tuning(MaskingThresholds(.3f, .4f, .3f),
+ MaskingThresholds(.07f, .1f, .3f),
+ 2.0f,
+ 0.25f);
+ Tuning nearend_tuning = Tuning(MaskingThresholds(1.09f, 1.1f, .3f),
+ MaskingThresholds(.1f, .3f, .3f),
+ 2.0f,
+ 0.25f);
+
+ struct DominantNearendDetection {
+ float enr_threshold = .25f;
+ float enr_exit_threshold = 10.f;
+ float snr_threshold = 30.f;
+ int hold_duration = 50;
+ int trigger_threshold = 12;
+ bool use_during_initial_phase = true;
+ } dominant_nearend_detection;
+
+ struct SubbandNearendDetection {
+ size_t nearend_average_blocks = 1;
+ struct SubbandRegion {
+ size_t low;
+ size_t high;
+ };
+ SubbandRegion subband1 = {1, 1};
+ SubbandRegion subband2 = {1, 1};
+ float nearend_threshold = 1.f;
+ float snr_threshold = 1.f;
+ } subband_nearend_detection;
+
+ bool use_subband_nearend_detection = false;
+
+ struct HighBandsSuppression {
+ float enr_threshold = 1.f;
+ float max_gain_during_echo = 1.f;
+ float anti_howling_activation_threshold = 25.f;
+ float anti_howling_gain = 0.01f;
+ } high_bands_suppression;
+
+ float floor_first_increase = 0.00001f;
+ } suppressor;
+};
+} // namespace webrtc
+
+#endif // API_AUDIO_ECHO_CANCELLER3_CONFIG_H_
diff --git a/api/echo_canceller3_config_json.cc b/api/echo_canceller3_config_json.cc
new file mode 100644
index 0000000..500fab2
--- /dev/null
+++ b/api/echo_canceller3_config_json.cc
@@ -0,0 +1,673 @@
+/*
+ * Copyright (c) 2018 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 "api/echo_canceller3_config_json.h"
+
+#include
+
+#include
+#include
+
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/strings/json.h"
+#include "rtc_base/strings/string_builder.h"
+
+namespace webrtc {
+namespace {
+void ReadParam(const Json::Value& root, std::string param_name, bool* param) {
+ RTC_DCHECK(param);
+ bool v;
+ if (rtc::GetBoolFromJsonObject(root, param_name, &v)) {
+ *param = v;
+ }
+}
+
+void ReadParam(const Json::Value& root, std::string param_name, size_t* param) {
+ RTC_DCHECK(param);
+ int v;
+ if (rtc::GetIntFromJsonObject(root, param_name, &v) && v >= 0) {
+ *param = v;
+ }
+}
+
+void ReadParam(const Json::Value& root, std::string param_name, int* param) {
+ RTC_DCHECK(param);
+ int v;
+ if (rtc::GetIntFromJsonObject(root, param_name, &v)) {
+ *param = v;
+ }
+}
+
+void ReadParam(const Json::Value& root, std::string param_name, float* param) {
+ RTC_DCHECK(param);
+ double v;
+ if (rtc::GetDoubleFromJsonObject(root, param_name, &v)) {
+ *param = static_cast(v);
+ }
+}
+
+void ReadParam(const Json::Value& root,
+ std::string param_name,
+ EchoCanceller3Config::Filter::MainConfiguration* param) {
+ RTC_DCHECK(param);
+ Json::Value json_array;
+ if (rtc::GetValueFromJsonObject(root, param_name, &json_array)) {
+ std::vector v;
+ rtc::JsonArrayToDoubleVector(json_array, &v);
+ if (v.size() != 6) {
+ RTC_LOG(LS_ERROR) << "Incorrect array size for " << param_name;
+ return;
+ }
+ param->length_blocks = static_cast(v[0]);
+ param->leakage_converged = static_cast(v[1]);
+ param->leakage_diverged = static_cast(v[2]);
+ param->error_floor = static_cast(v[3]);
+ param->error_ceil = static_cast(v[4]);
+ param->noise_gate = static_cast(v[5]);
+ }
+}
+
+void ReadParam(const Json::Value& root,
+ std::string param_name,
+ EchoCanceller3Config::Filter::ShadowConfiguration* param) {
+ RTC_DCHECK(param);
+ Json::Value json_array;
+ if (rtc::GetValueFromJsonObject(root, param_name, &json_array)) {
+ std::vector v;
+ rtc::JsonArrayToDoubleVector(json_array, &v);
+ if (v.size() != 3) {
+ RTC_LOG(LS_ERROR) << "Incorrect array size for " << param_name;
+ return;
+ }
+ param->length_blocks = static_cast(v[0]);
+ param->rate = static_cast(v[1]);
+ param->noise_gate = static_cast(v[2]);
+ }
+}
+
+void ReadParam(const Json::Value& root,
+ std::string param_name,
+ EchoCanceller3Config::Delay::AlignmentMixing* param) {
+ RTC_DCHECK(param);
+
+ Json::Value subsection;
+ if (rtc::GetValueFromJsonObject(root, param_name, &subsection)) {
+ ReadParam(subsection, "downmix", ¶m->downmix);
+ ReadParam(subsection, "adaptive_selection", ¶m->adaptive_selection);
+ ReadParam(subsection, "activity_power_threshold",
+ ¶m->activity_power_threshold);
+ ReadParam(subsection, "prefer_first_two_channels",
+ ¶m->prefer_first_two_channels);
+ }
+}
+
+void ReadParam(
+ const Json::Value& root,
+ std::string param_name,
+ EchoCanceller3Config::Suppressor::SubbandNearendDetection::SubbandRegion*
+ param) {
+ RTC_DCHECK(param);
+ Json::Value json_array;
+ if (rtc::GetValueFromJsonObject(root, param_name, &json_array)) {
+ std::vector v;
+ rtc::JsonArrayToIntVector(json_array, &v);
+ if (v.size() != 2) {
+ RTC_LOG(LS_ERROR) << "Incorrect array size for " << param_name;
+ return;
+ }
+ param->low = static_cast(v[0]);
+ param->high = static_cast(v[1]);
+ }
+}
+
+void ReadParam(const Json::Value& root,
+ std::string param_name,
+ EchoCanceller3Config::Suppressor::MaskingThresholds* param) {
+ RTC_DCHECK(param);
+ Json::Value json_array;
+ if (rtc::GetValueFromJsonObject(root, param_name, &json_array)) {
+ std::vector v;
+ rtc::JsonArrayToDoubleVector(json_array, &v);
+ if (v.size() != 3) {
+ RTC_LOG(LS_ERROR) << "Incorrect array size for " << param_name;
+ return;
+ }
+ param->enr_transparent = static_cast(v[0]);
+ param->enr_suppress = static_cast(v[1]);
+ param->emr_transparent = static_cast(v[2]);
+ }
+}
+} // namespace
+
+void Aec3ConfigFromJsonString(absl::string_view json_string,
+ EchoCanceller3Config* config,
+ bool* parsing_successful) {
+ RTC_DCHECK(config);
+ RTC_DCHECK(parsing_successful);
+ EchoCanceller3Config& cfg = *config;
+ cfg = EchoCanceller3Config();
+ *parsing_successful = true;
+
+ Json::Value root;
+ bool success = Json::Reader().parse(std::string(json_string), root);
+ if (!success) {
+ RTC_LOG(LS_ERROR) << "Incorrect JSON format: " << json_string;
+ *parsing_successful = false;
+ return;
+ }
+
+ Json::Value aec3_root;
+ success = rtc::GetValueFromJsonObject(root, "aec3", &aec3_root);
+ if (!success) {
+ RTC_LOG(LS_ERROR) << "Missing AEC3 config field: " << json_string;
+ *parsing_successful = false;
+ return;
+ }
+
+ Json::Value section;
+ if (rtc::GetValueFromJsonObject(aec3_root, "buffering", §ion)) {
+ ReadParam(section, "excess_render_detection_interval_blocks",
+ &cfg.buffering.excess_render_detection_interval_blocks);
+ ReadParam(section, "max_allowed_excess_render_blocks",
+ &cfg.buffering.max_allowed_excess_render_blocks);
+ }
+
+ if (rtc::GetValueFromJsonObject(aec3_root, "delay", §ion)) {
+ ReadParam(section, "default_delay", &cfg.delay.default_delay);
+ ReadParam(section, "down_sampling_factor", &cfg.delay.down_sampling_factor);
+ ReadParam(section, "num_filters", &cfg.delay.num_filters);
+ ReadParam(section, "delay_headroom_samples",
+ &cfg.delay.delay_headroom_samples);
+ ReadParam(section, "hysteresis_limit_blocks",
+ &cfg.delay.hysteresis_limit_blocks);
+ ReadParam(section, "fixed_capture_delay_samples",
+ &cfg.delay.fixed_capture_delay_samples);
+ ReadParam(section, "delay_estimate_smoothing",
+ &cfg.delay.delay_estimate_smoothing);
+ ReadParam(section, "delay_candidate_detection_threshold",
+ &cfg.delay.delay_candidate_detection_threshold);
+
+ Json::Value subsection;
+ if (rtc::GetValueFromJsonObject(section, "delay_selection_thresholds",
+ &subsection)) {
+ ReadParam(subsection, "initial",
+ &cfg.delay.delay_selection_thresholds.initial);
+ ReadParam(subsection, "converged",
+ &cfg.delay.delay_selection_thresholds.converged);
+ }
+
+ ReadParam(section, "use_external_delay_estimator",
+ &cfg.delay.use_external_delay_estimator);
+ ReadParam(section, "log_warning_on_delay_changes",
+ &cfg.delay.log_warning_on_delay_changes);
+
+ ReadParam(section, "render_alignment_mixing",
+ &cfg.delay.render_alignment_mixing);
+ ReadParam(section, "capture_alignment_mixing",
+ &cfg.delay.capture_alignment_mixing);
+ }
+
+ if (rtc::GetValueFromJsonObject(aec3_root, "filter", §ion)) {
+ ReadParam(section, "main", &cfg.filter.main);
+ ReadParam(section, "shadow", &cfg.filter.shadow);
+ ReadParam(section, "main_initial", &cfg.filter.main_initial);
+ ReadParam(section, "shadow_initial", &cfg.filter.shadow_initial);
+ ReadParam(section, "config_change_duration_blocks",
+ &cfg.filter.config_change_duration_blocks);
+ ReadParam(section, "initial_state_seconds",
+ &cfg.filter.initial_state_seconds);
+ ReadParam(section, "conservative_initial_phase",
+ &cfg.filter.conservative_initial_phase);
+ ReadParam(section, "enable_shadow_filter_output_usage",
+ &cfg.filter.enable_shadow_filter_output_usage);
+ ReadParam(section, "use_linear_filter", &cfg.filter.use_linear_filter);
+ ReadParam(section, "export_linear_aec_output",
+ &cfg.filter.export_linear_aec_output);
+ }
+
+ if (rtc::GetValueFromJsonObject(aec3_root, "erle", §ion)) {
+ ReadParam(section, "min", &cfg.erle.min);
+ ReadParam(section, "max_l", &cfg.erle.max_l);
+ ReadParam(section, "max_h", &cfg.erle.max_h);
+ ReadParam(section, "onset_detection", &cfg.erle.onset_detection);
+ ReadParam(section, "num_sections", &cfg.erle.num_sections);
+ ReadParam(section, "clamp_quality_estimate_to_zero",
+ &cfg.erle.clamp_quality_estimate_to_zero);
+ ReadParam(section, "clamp_quality_estimate_to_one",
+ &cfg.erle.clamp_quality_estimate_to_one);
+ }
+
+ if (rtc::GetValueFromJsonObject(aec3_root, "ep_strength", §ion)) {
+ ReadParam(section, "default_gain", &cfg.ep_strength.default_gain);
+ ReadParam(section, "default_len", &cfg.ep_strength.default_len);
+ ReadParam(section, "echo_can_saturate", &cfg.ep_strength.echo_can_saturate);
+ ReadParam(section, "bounded_erl", &cfg.ep_strength.bounded_erl);
+ }
+
+ if (rtc::GetValueFromJsonObject(aec3_root, "echo_audibility", §ion)) {
+ ReadParam(section, "low_render_limit",
+ &cfg.echo_audibility.low_render_limit);
+ ReadParam(section, "normal_render_limit",
+ &cfg.echo_audibility.normal_render_limit);
+
+ ReadParam(section, "floor_power", &cfg.echo_audibility.floor_power);
+ ReadParam(section, "audibility_threshold_lf",
+ &cfg.echo_audibility.audibility_threshold_lf);
+ ReadParam(section, "audibility_threshold_mf",
+ &cfg.echo_audibility.audibility_threshold_mf);
+ ReadParam(section, "audibility_threshold_hf",
+ &cfg.echo_audibility.audibility_threshold_hf);
+ ReadParam(section, "use_stationarity_properties",
+ &cfg.echo_audibility.use_stationarity_properties);
+ ReadParam(section, "use_stationarity_properties_at_init",
+ &cfg.echo_audibility.use_stationarity_properties_at_init);
+ }
+
+ if (rtc::GetValueFromJsonObject(aec3_root, "render_levels", §ion)) {
+ ReadParam(section, "active_render_limit",
+ &cfg.render_levels.active_render_limit);
+ ReadParam(section, "poor_excitation_render_limit",
+ &cfg.render_levels.poor_excitation_render_limit);
+ ReadParam(section, "poor_excitation_render_limit_ds8",
+ &cfg.render_levels.poor_excitation_render_limit_ds8);
+ ReadParam(section, "render_power_gain_db",
+ &cfg.render_levels.render_power_gain_db);
+ }
+
+ if (rtc::GetValueFromJsonObject(aec3_root, "echo_removal_control",
+ §ion)) {
+ ReadParam(section, "has_clock_drift",
+ &cfg.echo_removal_control.has_clock_drift);
+ ReadParam(section, "linear_and_stable_echo_path",
+ &cfg.echo_removal_control.linear_and_stable_echo_path);
+ }
+
+ if (rtc::GetValueFromJsonObject(aec3_root, "echo_model", §ion)) {
+ Json::Value subsection;
+ ReadParam(section, "noise_floor_hold", &cfg.echo_model.noise_floor_hold);
+ ReadParam(section, "min_noise_floor_power",
+ &cfg.echo_model.min_noise_floor_power);
+ ReadParam(section, "stationary_gate_slope",
+ &cfg.echo_model.stationary_gate_slope);
+ ReadParam(section, "noise_gate_power", &cfg.echo_model.noise_gate_power);
+ ReadParam(section, "noise_gate_slope", &cfg.echo_model.noise_gate_slope);
+ ReadParam(section, "render_pre_window_size",
+ &cfg.echo_model.render_pre_window_size);
+ ReadParam(section, "render_post_window_size",
+ &cfg.echo_model.render_post_window_size);
+ }
+
+ Json::Value subsection;
+ if (rtc::GetValueFromJsonObject(aec3_root, "suppressor", §ion)) {
+ ReadParam(section, "nearend_average_blocks",
+ &cfg.suppressor.nearend_average_blocks);
+
+ if (rtc::GetValueFromJsonObject(section, "normal_tuning", &subsection)) {
+ ReadParam(subsection, "mask_lf", &cfg.suppressor.normal_tuning.mask_lf);
+ ReadParam(subsection, "mask_hf", &cfg.suppressor.normal_tuning.mask_hf);
+ ReadParam(subsection, "max_inc_factor",
+ &cfg.suppressor.normal_tuning.max_inc_factor);
+ ReadParam(subsection, "max_dec_factor_lf",
+ &cfg.suppressor.normal_tuning.max_dec_factor_lf);
+ }
+
+ if (rtc::GetValueFromJsonObject(section, "nearend_tuning", &subsection)) {
+ ReadParam(subsection, "mask_lf", &cfg.suppressor.nearend_tuning.mask_lf);
+ ReadParam(subsection, "mask_hf", &cfg.suppressor.nearend_tuning.mask_hf);
+ ReadParam(subsection, "max_inc_factor",
+ &cfg.suppressor.nearend_tuning.max_inc_factor);
+ ReadParam(subsection, "max_dec_factor_lf",
+ &cfg.suppressor.nearend_tuning.max_dec_factor_lf);
+ }
+
+ if (rtc::GetValueFromJsonObject(section, "dominant_nearend_detection",
+ &subsection)) {
+ ReadParam(subsection, "enr_threshold",
+ &cfg.suppressor.dominant_nearend_detection.enr_threshold);
+ ReadParam(subsection, "enr_exit_threshold",
+ &cfg.suppressor.dominant_nearend_detection.enr_exit_threshold);
+ ReadParam(subsection, "snr_threshold",
+ &cfg.suppressor.dominant_nearend_detection.snr_threshold);
+ ReadParam(subsection, "hold_duration",
+ &cfg.suppressor.dominant_nearend_detection.hold_duration);
+ ReadParam(subsection, "trigger_threshold",
+ &cfg.suppressor.dominant_nearend_detection.trigger_threshold);
+ ReadParam(
+ subsection, "use_during_initial_phase",
+ &cfg.suppressor.dominant_nearend_detection.use_during_initial_phase);
+ }
+
+ if (rtc::GetValueFromJsonObject(section, "subband_nearend_detection",
+ &subsection)) {
+ ReadParam(
+ subsection, "nearend_average_blocks",
+ &cfg.suppressor.subband_nearend_detection.nearend_average_blocks);
+ ReadParam(subsection, "subband1",
+ &cfg.suppressor.subband_nearend_detection.subband1);
+ ReadParam(subsection, "subband2",
+ &cfg.suppressor.subband_nearend_detection.subband2);
+ ReadParam(subsection, "nearend_threshold",
+ &cfg.suppressor.subband_nearend_detection.nearend_threshold);
+ ReadParam(subsection, "snr_threshold",
+ &cfg.suppressor.subband_nearend_detection.snr_threshold);
+ }
+
+ ReadParam(section, "use_subband_nearend_detection",
+ &cfg.suppressor.use_subband_nearend_detection);
+
+ if (rtc::GetValueFromJsonObject(section, "high_bands_suppression",
+ &subsection)) {
+ ReadParam(subsection, "enr_threshold",
+ &cfg.suppressor.high_bands_suppression.enr_threshold);
+ ReadParam(subsection, "max_gain_during_echo",
+ &cfg.suppressor.high_bands_suppression.max_gain_during_echo);
+ ReadParam(subsection, "anti_howling_activation_threshold",
+ &cfg.suppressor.high_bands_suppression
+ .anti_howling_activation_threshold);
+ ReadParam(subsection, "anti_howling_gain",
+ &cfg.suppressor.high_bands_suppression.anti_howling_gain);
+ }
+
+ ReadParam(section, "floor_first_increase",
+ &cfg.suppressor.floor_first_increase);
+ }
+}
+
+EchoCanceller3Config Aec3ConfigFromJsonString(absl::string_view json_string) {
+ EchoCanceller3Config cfg;
+ bool not_used;
+ Aec3ConfigFromJsonString(json_string, &cfg, ¬_used);
+ return cfg;
+}
+
+std::string Aec3ConfigToJsonString(const EchoCanceller3Config& config) {
+ rtc::StringBuilder ost;
+ ost << "{";
+ ost << "\"aec3\": {";
+ ost << "\"buffering\": {";
+ ost << "\"excess_render_detection_interval_blocks\": "
+ << config.buffering.excess_render_detection_interval_blocks << ",";
+ ost << "\"max_allowed_excess_render_blocks\": "
+ << config.buffering.max_allowed_excess_render_blocks;
+ ost << "},";
+
+ ost << "\"delay\": {";
+ ost << "\"default_delay\": " << config.delay.default_delay << ",";
+ ost << "\"down_sampling_factor\": " << config.delay.down_sampling_factor
+ << ",";
+ ost << "\"num_filters\": " << config.delay.num_filters << ",";
+ ost << "\"delay_headroom_samples\": " << config.delay.delay_headroom_samples
+ << ",";
+ ost << "\"hysteresis_limit_blocks\": " << config.delay.hysteresis_limit_blocks
+ << ",";
+ ost << "\"fixed_capture_delay_samples\": "
+ << config.delay.fixed_capture_delay_samples << ",";
+ ost << "\"delay_estimate_smoothing\": "
+ << config.delay.delay_estimate_smoothing << ",";
+ ost << "\"delay_candidate_detection_threshold\": "
+ << config.delay.delay_candidate_detection_threshold << ",";
+
+ ost << "\"delay_selection_thresholds\": {";
+ ost << "\"initial\": " << config.delay.delay_selection_thresholds.initial
+ << ",";
+ ost << "\"converged\": " << config.delay.delay_selection_thresholds.converged;
+ ost << "},";
+
+ ost << "\"use_external_delay_estimator\": "
+ << (config.delay.use_external_delay_estimator ? "true" : "false") << ",";
+ ost << "\"log_warning_on_delay_changes\": "
+ << (config.delay.log_warning_on_delay_changes ? "true" : "false") << ",";
+
+ ost << "\"render_alignment_mixing\": {";
+ ost << "\"downmix\": "
+ << (config.delay.render_alignment_mixing.downmix ? "true" : "false")
+ << ",";
+ ost << "\"adaptive_selection\": "
+ << (config.delay.render_alignment_mixing.adaptive_selection ? "true"
+ : "false")
+ << ",";
+ ost << "\"activity_power_threshold\": "
+ << config.delay.render_alignment_mixing.activity_power_threshold << ",";
+ ost << "\"prefer_first_two_channels\": "
+ << (config.delay.render_alignment_mixing.prefer_first_two_channels
+ ? "true"
+ : "false");
+ ost << "},";
+
+ ost << "\"capture_alignment_mixing\": {";
+ ost << "\"downmix\": "
+ << (config.delay.capture_alignment_mixing.downmix ? "true" : "false")
+ << ",";
+ ost << "\"adaptive_selection\": "
+ << (config.delay.capture_alignment_mixing.adaptive_selection ? "true"
+ : "false")
+ << ",";
+ ost << "\"activity_power_threshold\": "
+ << config.delay.capture_alignment_mixing.activity_power_threshold << ",";
+ ost << "\"prefer_first_two_channels\": "
+ << (config.delay.capture_alignment_mixing.prefer_first_two_channels
+ ? "true"
+ : "false");
+ ost << "}";
+ ost << "},";
+
+ ost << "\"filter\": {";
+ ost << "\"main\": [";
+ ost << config.filter.main.length_blocks << ",";
+ ost << config.filter.main.leakage_converged << ",";
+ ost << config.filter.main.leakage_diverged << ",";
+ ost << config.filter.main.error_floor << ",";
+ ost << config.filter.main.error_ceil << ",";
+ ost << config.filter.main.noise_gate;
+ ost << "],";
+
+ ost << "\"shadow\": [";
+ ost << config.filter.shadow.length_blocks << ",";
+ ost << config.filter.shadow.rate << ",";
+ ost << config.filter.shadow.noise_gate;
+ ost << "],";
+
+ ost << "\"main_initial\": [";
+ ost << config.filter.main_initial.length_blocks << ",";
+ ost << config.filter.main_initial.leakage_converged << ",";
+ ost << config.filter.main_initial.leakage_diverged << ",";
+ ost << config.filter.main_initial.error_floor << ",";
+ ost << config.filter.main_initial.error_ceil << ",";
+ ost << config.filter.main_initial.noise_gate;
+ ost << "],";
+
+ ost << "\"shadow_initial\": [";
+ ost << config.filter.shadow_initial.length_blocks << ",";
+ ost << config.filter.shadow_initial.rate << ",";
+ ost << config.filter.shadow_initial.noise_gate;
+ ost << "],";
+
+ ost << "\"config_change_duration_blocks\": "
+ << config.filter.config_change_duration_blocks << ",";
+ ost << "\"initial_state_seconds\": " << config.filter.initial_state_seconds
+ << ",";
+ ost << "\"conservative_initial_phase\": "
+ << (config.filter.conservative_initial_phase ? "true" : "false") << ",";
+ ost << "\"enable_shadow_filter_output_usage\": "
+ << (config.filter.enable_shadow_filter_output_usage ? "true" : "false")
+ << ",";
+ ost << "\"use_linear_filter\": "
+ << (config.filter.use_linear_filter ? "true" : "false") << ",";
+ ost << "\"export_linear_aec_output\": "
+ << (config.filter.export_linear_aec_output ? "true" : "false");
+
+ ost << "},";
+
+ ost << "\"erle\": {";
+ ost << "\"min\": " << config.erle.min << ",";
+ ost << "\"max_l\": " << config.erle.max_l << ",";
+ ost << "\"max_h\": " << config.erle.max_h << ",";
+ ost << "\"onset_detection\": "
+ << (config.erle.onset_detection ? "true" : "false") << ",";
+ ost << "\"num_sections\": " << config.erle.num_sections << ",";
+ ost << "\"clamp_quality_estimate_to_zero\": "
+ << (config.erle.clamp_quality_estimate_to_zero ? "true" : "false") << ",";
+ ost << "\"clamp_quality_estimate_to_one\": "
+ << (config.erle.clamp_quality_estimate_to_one ? "true" : "false");
+ ost << "},";
+
+ ost << "\"ep_strength\": {";
+ ost << "\"default_gain\": " << config.ep_strength.default_gain << ",";
+ ost << "\"default_len\": " << config.ep_strength.default_len << ",";
+ ost << "\"echo_can_saturate\": "
+ << (config.ep_strength.echo_can_saturate ? "true" : "false") << ",";
+ ost << "\"bounded_erl\": "
+ << (config.ep_strength.bounded_erl ? "true" : "false");
+
+ ost << "},";
+
+ ost << "\"echo_audibility\": {";
+ ost << "\"low_render_limit\": " << config.echo_audibility.low_render_limit
+ << ",";
+ ost << "\"normal_render_limit\": "
+ << config.echo_audibility.normal_render_limit << ",";
+ ost << "\"floor_power\": " << config.echo_audibility.floor_power << ",";
+ ost << "\"audibility_threshold_lf\": "
+ << config.echo_audibility.audibility_threshold_lf << ",";
+ ost << "\"audibility_threshold_mf\": "
+ << config.echo_audibility.audibility_threshold_mf << ",";
+ ost << "\"audibility_threshold_hf\": "
+ << config.echo_audibility.audibility_threshold_hf << ",";
+ ost << "\"use_stationarity_properties\": "
+ << (config.echo_audibility.use_stationarity_properties ? "true" : "false")
+ << ",";
+ ost << "\"use_stationarity_properties_at_init\": "
+ << (config.echo_audibility.use_stationarity_properties_at_init ? "true"
+ : "false");
+ ost << "},";
+
+ ost << "\"render_levels\": {";
+ ost << "\"active_render_limit\": " << config.render_levels.active_render_limit
+ << ",";
+ ost << "\"poor_excitation_render_limit\": "
+ << config.render_levels.poor_excitation_render_limit << ",";
+ ost << "\"poor_excitation_render_limit_ds8\": "
+ << config.render_levels.poor_excitation_render_limit_ds8 << ",";
+ ost << "\"render_power_gain_db\": "
+ << config.render_levels.render_power_gain_db;
+ ost << "},";
+
+ ost << "\"echo_removal_control\": {";
+ ost << "\"has_clock_drift\": "
+ << (config.echo_removal_control.has_clock_drift ? "true" : "false")
+ << ",";
+ ost << "\"linear_and_stable_echo_path\": "
+ << (config.echo_removal_control.linear_and_stable_echo_path ? "true"
+ : "false");
+
+ ost << "},";
+
+ ost << "\"echo_model\": {";
+ ost << "\"noise_floor_hold\": " << config.echo_model.noise_floor_hold << ",";
+ ost << "\"min_noise_floor_power\": "
+ << config.echo_model.min_noise_floor_power << ",";
+ ost << "\"stationary_gate_slope\": "
+ << config.echo_model.stationary_gate_slope << ",";
+ ost << "\"noise_gate_power\": " << config.echo_model.noise_gate_power << ",";
+ ost << "\"noise_gate_slope\": " << config.echo_model.noise_gate_slope << ",";
+ ost << "\"render_pre_window_size\": "
+ << config.echo_model.render_pre_window_size << ",";
+ ost << "\"render_post_window_size\": "
+ << config.echo_model.render_post_window_size;
+ ost << "},";
+
+ ost << "\"suppressor\": {";
+ ost << "\"nearend_average_blocks\": "
+ << config.suppressor.nearend_average_blocks << ",";
+ ost << "\"normal_tuning\": {";
+ ost << "\"mask_lf\": [";
+ ost << config.suppressor.normal_tuning.mask_lf.enr_transparent << ",";
+ ost << config.suppressor.normal_tuning.mask_lf.enr_suppress << ",";
+ ost << config.suppressor.normal_tuning.mask_lf.emr_transparent;
+ ost << "],";
+ ost << "\"mask_hf\": [";
+ ost << config.suppressor.normal_tuning.mask_hf.enr_transparent << ",";
+ ost << config.suppressor.normal_tuning.mask_hf.enr_suppress << ",";
+ ost << config.suppressor.normal_tuning.mask_hf.emr_transparent;
+ ost << "],";
+ ost << "\"max_inc_factor\": "
+ << config.suppressor.normal_tuning.max_inc_factor << ",";
+ ost << "\"max_dec_factor_lf\": "
+ << config.suppressor.normal_tuning.max_dec_factor_lf;
+ ost << "},";
+ ost << "\"nearend_tuning\": {";
+ ost << "\"mask_lf\": [";
+ ost << config.suppressor.nearend_tuning.mask_lf.enr_transparent << ",";
+ ost << config.suppressor.nearend_tuning.mask_lf.enr_suppress << ",";
+ ost << config.suppressor.nearend_tuning.mask_lf.emr_transparent;
+ ost << "],";
+ ost << "\"mask_hf\": [";
+ ost << config.suppressor.nearend_tuning.mask_hf.enr_transparent << ",";
+ ost << config.suppressor.nearend_tuning.mask_hf.enr_suppress << ",";
+ ost << config.suppressor.nearend_tuning.mask_hf.emr_transparent;
+ ost << "],";
+ ost << "\"max_inc_factor\": "
+ << config.suppressor.nearend_tuning.max_inc_factor << ",";
+ ost << "\"max_dec_factor_lf\": "
+ << config.suppressor.nearend_tuning.max_dec_factor_lf;
+ ost << "},";
+ ost << "\"dominant_nearend_detection\": {";
+ ost << "\"enr_threshold\": "
+ << config.suppressor.dominant_nearend_detection.enr_threshold << ",";
+ ost << "\"enr_exit_threshold\": "
+ << config.suppressor.dominant_nearend_detection.enr_exit_threshold << ",";
+ ost << "\"snr_threshold\": "
+ << config.suppressor.dominant_nearend_detection.snr_threshold << ",";
+ ost << "\"hold_duration\": "
+ << config.suppressor.dominant_nearend_detection.hold_duration << ",";
+ ost << "\"trigger_threshold\": "
+ << config.suppressor.dominant_nearend_detection.trigger_threshold << ",";
+ ost << "\"use_during_initial_phase\": "
+ << config.suppressor.dominant_nearend_detection.use_during_initial_phase;
+ ost << "},";
+ ost << "\"subband_nearend_detection\": {";
+ ost << "\"nearend_average_blocks\": "
+ << config.suppressor.subband_nearend_detection.nearend_average_blocks
+ << ",";
+ ost << "\"subband1\": [";
+ ost << config.suppressor.subband_nearend_detection.subband1.low << ",";
+ ost << config.suppressor.subband_nearend_detection.subband1.high;
+ ost << "],";
+ ost << "\"subband2\": [";
+ ost << config.suppressor.subband_nearend_detection.subband2.low << ",";
+ ost << config.suppressor.subband_nearend_detection.subband2.high;
+ ost << "],";
+ ost << "\"nearend_threshold\": "
+ << config.suppressor.subband_nearend_detection.nearend_threshold << ",";
+ ost << "\"snr_threshold\": "
+ << config.suppressor.subband_nearend_detection.snr_threshold;
+ ost << "},";
+ ost << "\"use_subband_nearend_detection\": "
+ << config.suppressor.use_subband_nearend_detection << ",";
+ ost << "\"high_bands_suppression\": {";
+ ost << "\"enr_threshold\": "
+ << config.suppressor.high_bands_suppression.enr_threshold << ",";
+ ost << "\"max_gain_during_echo\": "
+ << config.suppressor.high_bands_suppression.max_gain_during_echo << ",";
+ ost << "\"anti_howling_activation_threshold\": "
+ << config.suppressor.high_bands_suppression
+ .anti_howling_activation_threshold
+ << ",";
+ ost << "\"anti_howling_gain\": "
+ << config.suppressor.high_bands_suppression.anti_howling_gain;
+ ost << "},";
+ ost << "\"floor_first_increase\": " << config.suppressor.floor_first_increase;
+ ost << "}";
+ ost << "}";
+ ost << "}";
+
+ return ost.Release();
+}
+} // namespace webrtc
diff --git a/api/echo_canceller3_config_json.h b/api/echo_canceller3_config_json.h
new file mode 100644
index 0000000..bb40ac9
--- /dev/null
+++ b/api/echo_canceller3_config_json.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018 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 API_AUDIO_ECHO_CANCELLER3_CONFIG_JSON_H_
+#define API_AUDIO_ECHO_CANCELLER3_CONFIG_JSON_H_
+
+#include
+
+#include "absl/strings/string_view.h"
+#include "api/echo_canceller3_config.h"
+#include "rtc_base/system/rtc_export.h"
+
+namespace webrtc {
+// Parses a JSON-encoded string into an Aec3 config. Fields corresponds to
+// substruct names, with the addition that there must be a top-level node
+// "aec3". Produces default config values for anything that cannot be parsed
+// from the string. If any error was found in the parsing, parsing_successful is
+// set to false.
+RTC_EXPORT void Aec3ConfigFromJsonString(absl::string_view json_string,
+ EchoCanceller3Config* config,
+ bool* parsing_successful);
+
+// To be deprecated.
+// Parses a JSON-encoded string into an Aec3 config. Fields corresponds to
+// substruct names, with the addition that there must be a top-level node
+// "aec3". Returns default config values for anything that cannot be parsed from
+// the string.
+RTC_EXPORT EchoCanceller3Config
+Aec3ConfigFromJsonString(absl::string_view json_string);
+
+// Encodes an Aec3 config in JSON format. Fields corresponds to substruct names,
+// with the addition that the top-level node is named "aec3".
+RTC_EXPORT std::string Aec3ConfigToJsonString(
+ const EchoCanceller3Config& config);
+
+} // namespace webrtc
+
+#endif // API_AUDIO_ECHO_CANCELLER3_CONFIG_JSON_H_
diff --git a/api/echo_canceller3_factory.cc b/api/echo_canceller3_factory.cc
new file mode 100644
index 0000000..4046d3e
--- /dev/null
+++ b/api/echo_canceller3_factory.cc
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018 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 "api/echo_canceller3_factory.h"
+
+#include
+
+#include "audio_processing/aec3/echo_canceller3.h"
+
+namespace webrtc {
+
+EchoCanceller3Factory::EchoCanceller3Factory() {}
+
+EchoCanceller3Factory::EchoCanceller3Factory(const EchoCanceller3Config& config)
+ : config_(config) {}
+
+std::unique_ptr EchoCanceller3Factory::Create(
+ int sample_rate_hz,
+ int num_render_channels,
+ int num_capture_channels) {
+ return std::make_unique(
+ config_, sample_rate_hz, num_render_channels, num_capture_channels);
+}
+
+} // namespace webrtc
diff --git a/api/echo_canceller3_factory.h b/api/echo_canceller3_factory.h
new file mode 100644
index 0000000..740f34f
--- /dev/null
+++ b/api/echo_canceller3_factory.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018 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 API_AUDIO_ECHO_CANCELLER3_FACTORY_H_
+#define API_AUDIO_ECHO_CANCELLER3_FACTORY_H_
+
+#include
+
+#include "api/echo_canceller3_config.h"
+#include "api/echo_control.h"
+#include "rtc_base/system/rtc_export.h"
+
+namespace webrtc {
+
+class RTC_EXPORT EchoCanceller3Factory : public EchoControlFactory {
+ public:
+ // Factory producing EchoCanceller3 instances with the default configuration.
+ EchoCanceller3Factory();
+
+ // Factory producing EchoCanceller3 instances with the specified
+ // configuration.
+ explicit EchoCanceller3Factory(const EchoCanceller3Config& config);
+
+ // Creates an EchoCanceller3 with a specified channel count and sampling rate.
+ std::unique_ptr Create(int sample_rate_hz,
+ int num_render_channels,
+ int num_capture_channels) override;
+
+ private:
+ const EchoCanceller3Config config_;
+};
+} // namespace webrtc
+
+#endif // API_AUDIO_ECHO_CANCELLER3_FACTORY_H_
diff --git a/api/echo_control.h b/api/echo_control.h
new file mode 100644
index 0000000..8d567bf
--- /dev/null
+++ b/api/echo_control.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018 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 API_AUDIO_ECHO_CONTROL_H_
+#define API_AUDIO_ECHO_CONTROL_H_
+
+#include
+
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+class AudioBuffer;
+
+// Interface for an acoustic echo cancellation (AEC) submodule.
+class EchoControl {
+ public:
+ // Analysis (not changing) of the render signal.
+ virtual void AnalyzeRender(AudioBuffer* render) = 0;
+
+ // Analysis (not changing) of the capture signal.
+ virtual void AnalyzeCapture(AudioBuffer* capture) = 0;
+
+ // Processes the capture signal in order to remove the echo.
+ virtual void ProcessCapture(AudioBuffer* capture, bool level_change) = 0;
+
+ // As above, but also returns the linear filter output.
+ virtual void ProcessCapture(AudioBuffer* capture,
+ AudioBuffer* linear_output,
+ bool level_change) = 0;
+
+ struct Metrics {
+ double echo_return_loss;
+ double echo_return_loss_enhancement;
+ int delay_ms;
+ };
+
+ // Collect current metrics from the echo controller.
+ virtual Metrics GetMetrics() const = 0;
+
+ // Provides an optional external estimate of the audio buffer delay.
+ virtual void SetAudioBufferDelay(int delay_ms) = 0;
+
+ // Returns wheter the signal is altered.
+ virtual bool ActiveProcessing() const = 0;
+
+ virtual ~EchoControl() {}
+};
+
+// Interface for a factory that creates EchoControllers.
+class EchoControlFactory {
+ public:
+ virtual std::unique_ptr Create(int sample_rate_hz,
+ int num_render_channels,
+ int num_capture_channels) = 0;
+
+ virtual ~EchoControlFactory() = default;
+};
+} // namespace webrtc
+
+#endif // API_AUDIO_ECHO_CONTROL_H_
diff --git a/audio_processing/aec3/BUILD.gn b/audio_processing/aec3/BUILD.gn
new file mode 100644
index 0000000..909d49e
--- /dev/null
+++ b/audio_processing/aec3/BUILD.gn
@@ -0,0 +1,238 @@
+# Copyright (c) 2018 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("../../../webrtc.gni")
+
+rtc_library("aec3") {
+ visibility = [ "*" ]
+ configs += [ "..:apm_debug_dump" ]
+ sources = [
+ "adaptive_fir_filter.cc",
+ "adaptive_fir_filter.h",
+ "adaptive_fir_filter_erl.cc",
+ "adaptive_fir_filter_erl.h",
+ "aec3_common.cc",
+ "aec3_common.h",
+ "aec3_fft.cc",
+ "aec3_fft.h",
+ "aec_state.cc",
+ "aec_state.h",
+ "alignment_mixer.cc",
+ "alignment_mixer.h",
+ "api_call_jitter_metrics.cc",
+ "api_call_jitter_metrics.h",
+ "block_buffer.cc",
+ "block_buffer.h",
+ "block_delay_buffer.cc",
+ "block_delay_buffer.h",
+ "block_framer.cc",
+ "block_framer.h",
+ "block_processor.cc",
+ "block_processor.h",
+ "block_processor_metrics.cc",
+ "block_processor_metrics.h",
+ "clockdrift_detector.cc",
+ "clockdrift_detector.h",
+ "comfort_noise_generator.cc",
+ "comfort_noise_generator.h",
+ "decimator.cc",
+ "decimator.h",
+ "delay_estimate.h",
+ "dominant_nearend_detector.cc",
+ "dominant_nearend_detector.h",
+ "downsampled_render_buffer.cc",
+ "downsampled_render_buffer.h",
+ "echo_audibility.cc",
+ "echo_audibility.h",
+ "echo_canceller3.cc",
+ "echo_canceller3.h",
+ "echo_path_delay_estimator.cc",
+ "echo_path_delay_estimator.h",
+ "echo_path_variability.cc",
+ "echo_path_variability.h",
+ "echo_remover.cc",
+ "echo_remover.h",
+ "echo_remover_metrics.cc",
+ "echo_remover_metrics.h",
+ "erl_estimator.cc",
+ "erl_estimator.h",
+ "erle_estimator.cc",
+ "erle_estimator.h",
+ "fft_buffer.cc",
+ "fft_buffer.h",
+ "fft_data.h",
+ "filter_analyzer.cc",
+ "filter_analyzer.h",
+ "frame_blocker.cc",
+ "frame_blocker.h",
+ "fullband_erle_estimator.cc",
+ "fullband_erle_estimator.h",
+ "main_filter_update_gain.cc",
+ "main_filter_update_gain.h",
+ "matched_filter.cc",
+ "matched_filter.h",
+ "matched_filter_lag_aggregator.cc",
+ "matched_filter_lag_aggregator.h",
+ "moving_average.cc",
+ "moving_average.h",
+ "nearend_detector.h",
+ "render_buffer.cc",
+ "render_buffer.h",
+ "render_delay_buffer.cc",
+ "render_delay_buffer.h",
+ "render_delay_controller.cc",
+ "render_delay_controller.h",
+ "render_delay_controller_metrics.cc",
+ "render_delay_controller_metrics.h",
+ "render_signal_analyzer.cc",
+ "render_signal_analyzer.h",
+ "residual_echo_estimator.cc",
+ "residual_echo_estimator.h",
+ "reverb_decay_estimator.cc",
+ "reverb_decay_estimator.h",
+ "reverb_frequency_response.cc",
+ "reverb_frequency_response.h",
+ "reverb_model.cc",
+ "reverb_model.h",
+ "reverb_model_estimator.cc",
+ "reverb_model_estimator.h",
+ "shadow_filter_update_gain.cc",
+ "shadow_filter_update_gain.h",
+ "signal_dependent_erle_estimator.cc",
+ "signal_dependent_erle_estimator.h",
+ "spectrum_buffer.cc",
+ "spectrum_buffer.h",
+ "stationarity_estimator.cc",
+ "stationarity_estimator.h",
+ "subband_erle_estimator.cc",
+ "subband_erle_estimator.h",
+ "subband_nearend_detector.cc",
+ "subband_nearend_detector.h",
+ "subtractor.cc",
+ "subtractor.h",
+ "subtractor_output.cc",
+ "subtractor_output.h",
+ "subtractor_output_analyzer.cc",
+ "subtractor_output_analyzer.h",
+ "suppression_filter.cc",
+ "suppression_filter.h",
+ "suppression_gain.cc",
+ "suppression_gain.h",
+ "vector_math.h",
+ ]
+
+ defines = []
+ if (rtc_build_with_neon && current_cpu != "arm64") {
+ suppressed_configs += [ "//build/config/compiler:compiler_arm_fpu" ]
+ cflags = [ "-mfpu=neon" ]
+ }
+
+ deps = [
+ "..:apm_logging",
+ "..:audio_buffer",
+ "..:high_pass_filter",
+ "../../../api:array_view",
+ "../../../api/audio:aec3_config",
+ "../../../api/audio:echo_control",
+ "../../../common_audio:common_audio_c",
+ "../../../rtc_base:checks",
+ "../../../rtc_base:rtc_base_approved",
+ "../../../rtc_base:safe_minmax",
+ "../../../rtc_base/system:arch",
+ "../../../system_wrappers:cpu_features_api",
+ "../../../system_wrappers:field_trial",
+ "../../../system_wrappers:metrics",
+ "../utility:cascaded_biquad_filter",
+ "../utility:ooura_fft",
+ "//third_party/abseil-cpp/absl/types:optional",
+ ]
+}
+
+if (rtc_include_tests) {
+ rtc_library("aec3_unittests") {
+ testonly = true
+
+ configs += [ "..:apm_debug_dump" ]
+ sources = [
+ "mock/mock_block_processor.cc",
+ "mock/mock_block_processor.h",
+ "mock/mock_echo_remover.cc",
+ "mock/mock_echo_remover.h",
+ "mock/mock_render_delay_buffer.cc",
+ "mock/mock_render_delay_buffer.h",
+ "mock/mock_render_delay_controller.cc",
+ "mock/mock_render_delay_controller.h",
+ ]
+
+ deps = [
+ ":aec3",
+ "..:apm_logging",
+ "..:audio_buffer",
+ "..:audio_processing",
+ "..:audio_processing_unittests",
+ "..:high_pass_filter",
+ "../../../api:array_view",
+ "../../../api/audio:aec3_config",
+ "../../../rtc_base:checks",
+ "../../../rtc_base:rtc_base_approved",
+ "../../../rtc_base:safe_minmax",
+ "../../../rtc_base/system:arch",
+ "../../../system_wrappers:cpu_features_api",
+ "../../../test:test_support",
+ "../utility:cascaded_biquad_filter",
+ "//third_party/abseil-cpp/absl/types:optional",
+ ]
+
+ defines = []
+
+ if (rtc_enable_protobuf) {
+ sources += [
+ "adaptive_fir_filter_erl_unittest.cc",
+ "adaptive_fir_filter_unittest.cc",
+ "aec3_fft_unittest.cc",
+ "aec_state_unittest.cc",
+ "alignment_mixer_unittest.cc",
+ "api_call_jitter_metrics_unittest.cc",
+ "block_delay_buffer_unittest.cc",
+ "block_framer_unittest.cc",
+ "block_processor_metrics_unittest.cc",
+ "block_processor_unittest.cc",
+ "clockdrift_detector_unittest.cc",
+ "comfort_noise_generator_unittest.cc",
+ "decimator_unittest.cc",
+ "echo_canceller3_unittest.cc",
+ "echo_path_delay_estimator_unittest.cc",
+ "echo_path_variability_unittest.cc",
+ "echo_remover_metrics_unittest.cc",
+ "echo_remover_unittest.cc",
+ "erl_estimator_unittest.cc",
+ "erle_estimator_unittest.cc",
+ "fft_data_unittest.cc",
+ "filter_analyzer_unittest.cc",
+ "frame_blocker_unittest.cc",
+ "main_filter_update_gain_unittest.cc",
+ "matched_filter_lag_aggregator_unittest.cc",
+ "matched_filter_unittest.cc",
+ "moving_average_unittest.cc",
+ "render_buffer_unittest.cc",
+ "render_delay_buffer_unittest.cc",
+ "render_delay_controller_metrics_unittest.cc",
+ "render_delay_controller_unittest.cc",
+ "render_signal_analyzer_unittest.cc",
+ "residual_echo_estimator_unittest.cc",
+ "reverb_model_estimator_unittest.cc",
+ "shadow_filter_update_gain_unittest.cc",
+ "signal_dependent_erle_estimator_unittest.cc",
+ "subtractor_unittest.cc",
+ "suppression_filter_unittest.cc",
+ "suppression_gain_unittest.cc",
+ "vector_math_unittest.cc",
+ ]
+ }
+ }
+}
diff --git a/audio_processing/aec3/adaptive_fir_filter.cc b/audio_processing/aec3/adaptive_fir_filter.cc
new file mode 100644
index 0000000..c7afa4b
--- /dev/null
+++ b/audio_processing/aec3/adaptive_fir_filter.cc
@@ -0,0 +1,730 @@
+/*
+ * 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 "audio_processing/aec3/adaptive_fir_filter.h"
+
+// Defines WEBRTC_ARCH_X86_FAMILY, used below.
+#include "rtc_base/system/arch.h"
+
+#if defined(WEBRTC_HAS_NEON)
+#include
+#endif
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+#include
+#endif
+#include
+
+#include
+#include
+
+#include "audio_processing/aec3/fft_data.h"
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+namespace aec3 {
+
+// Computes and stores the frequency response of the filter.
+void ComputeFrequencyResponse(
+ size_t num_partitions,
+ const std::vector>& H,
+ std::vector>* H2) {
+ for (auto& H2_ch : *H2) {
+ H2_ch.fill(0.f);
+ }
+
+ const size_t num_render_channels = H[0].size();
+ RTC_DCHECK_EQ(H.size(), H2->capacity());
+ for (size_t p = 0; p < num_partitions; ++p) {
+ RTC_DCHECK_EQ(kFftLengthBy2Plus1, (*H2)[p].size());
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ for (size_t j = 0; j < kFftLengthBy2Plus1; ++j) {
+ float tmp =
+ H[p][ch].re[j] * H[p][ch].re[j] + H[p][ch].im[j] * H[p][ch].im[j];
+ (*H2)[p][j] = std::max((*H2)[p][j], tmp);
+ }
+ }
+ }
+}
+
+#if defined(WEBRTC_HAS_NEON)
+// Computes and stores the frequency response of the filter.
+void ComputeFrequencyResponse_Neon(
+ size_t num_partitions,
+ const std::vector>& H,
+ std::vector>* H2) {
+ for (auto& H2_ch : *H2) {
+ H2_ch.fill(0.f);
+ }
+
+ const size_t num_render_channels = H[0].size();
+ RTC_DCHECK_EQ(H.size(), H2->capacity());
+ for (size_t p = 0; p < num_partitions; ++p) {
+ RTC_DCHECK_EQ(kFftLengthBy2Plus1, (*H2)[p].size());
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ for (size_t j = 0; j < kFftLengthBy2; j += 4) {
+ const float32x4_t re = vld1q_f32(&H[p][ch].re[j]);
+ const float32x4_t im = vld1q_f32(&H[p][ch].im[j]);
+ float32x4_t H2_new = vmulq_f32(re, re);
+ H2_new = vmlaq_f32(H2_new, im, im);
+ float32x4_t H2_p_j = vld1q_f32(&(*H2)[p][j]);
+ H2_p_j = vmaxq_f32(H2_p_j, H2_new);
+ vst1q_f32(&(*H2)[p][j], H2_p_j);
+ }
+ float H2_new = H[p][ch].re[kFftLengthBy2] * H[p][ch].re[kFftLengthBy2] +
+ H[p][ch].im[kFftLengthBy2] * H[p][ch].im[kFftLengthBy2];
+ (*H2)[p][kFftLengthBy2] = std::max((*H2)[p][kFftLengthBy2], H2_new);
+ }
+ }
+}
+#endif
+
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+// Computes and stores the frequency response of the filter.
+void ComputeFrequencyResponse_Sse2(
+ size_t num_partitions,
+ const std::vector>& H,
+ std::vector>* H2) {
+ for (auto& H2_ch : *H2) {
+ H2_ch.fill(0.f);
+ }
+
+ const size_t num_render_channels = H[0].size();
+ RTC_DCHECK_EQ(H.size(), H2->capacity());
+ // constexpr __mmmask8 kMaxMask = static_cast<__mmmask8>(256u);
+ for (size_t p = 0; p < num_partitions; ++p) {
+ RTC_DCHECK_EQ(kFftLengthBy2Plus1, (*H2)[p].size());
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ for (size_t j = 0; j < kFftLengthBy2; j += 4) {
+ const __m128 re = _mm_loadu_ps(&H[p][ch].re[j]);
+ const __m128 re2 = _mm_mul_ps(re, re);
+ const __m128 im = _mm_loadu_ps(&H[p][ch].im[j]);
+ const __m128 im2 = _mm_mul_ps(im, im);
+ const __m128 H2_new = _mm_add_ps(re2, im2);
+ __m128 H2_k_j = _mm_loadu_ps(&(*H2)[p][j]);
+ H2_k_j = _mm_max_ps(H2_k_j, H2_new);
+ _mm_storeu_ps(&(*H2)[p][j], H2_k_j);
+ }
+ float H2_new = H[p][ch].re[kFftLengthBy2] * H[p][ch].re[kFftLengthBy2] +
+ H[p][ch].im[kFftLengthBy2] * H[p][ch].im[kFftLengthBy2];
+ (*H2)[p][kFftLengthBy2] = std::max((*H2)[p][kFftLengthBy2], H2_new);
+ }
+ }
+}
+#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,
+ size_t num_partitions,
+ std::vector>* H) {
+ rtc::ArrayView> render_buffer_data =
+ render_buffer.GetFftBuffer();
+ size_t index = render_buffer.Position();
+ const size_t num_render_channels = render_buffer_data[index].size();
+ for (size_t p = 0; p < num_partitions; ++p) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ const FftData& X_p_ch = render_buffer_data[index][ch];
+ FftData& H_p_ch = (*H)[p][ch];
+ for (size_t k = 0; k < kFftLengthBy2Plus1; ++k) {
+ H_p_ch.re[k] += X_p_ch.re[k] * G.re[k] + X_p_ch.im[k] * G.im[k];
+ H_p_ch.im[k] += X_p_ch.re[k] * G.im[k] - X_p_ch.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,
+ size_t num_partitions,
+ std::vector>* H) {
+ rtc::ArrayView> render_buffer_data =
+ render_buffer.GetFftBuffer();
+ const size_t num_render_channels = render_buffer_data[0].size();
+ const size_t lim1 = std::min(
+ render_buffer_data.size() - render_buffer.Position(), num_partitions);
+ const size_t lim2 = num_partitions;
+ constexpr size_t kNumFourBinBands = kFftLengthBy2 / 4;
+
+ size_t X_partition = render_buffer.Position();
+ size_t limit = lim1;
+ size_t p = 0;
+ do {
+ for (; p < limit; ++p, ++X_partition) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ FftData& H_p_ch = (*H)[p][ch];
+ const FftData& X = render_buffer_data[X_partition][ch];
+ for (size_t 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_p_ch.re[k]);
+ const float32x4_t H_im = vld1q_f32(&H_p_ch.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_p_ch.re[k], g);
+ vst1q_f32(&H_p_ch.im[k], h);
+ }
+ }
+ }
+
+ X_partition = 0;
+ limit = lim2;
+ } while (p < lim2);
+
+ X_partition = render_buffer.Position();
+ limit = lim1;
+ p = 0;
+ do {
+ for (; p < limit; ++p, ++X_partition) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ FftData& H_p_ch = (*H)[p][ch];
+ const FftData& X = render_buffer_data[X_partition][ch];
+
+ H_p_ch.re[kFftLengthBy2] += X.re[kFftLengthBy2] * G.re[kFftLengthBy2] +
+ X.im[kFftLengthBy2] * G.im[kFftLengthBy2];
+ H_p_ch.im[kFftLengthBy2] += X.re[kFftLengthBy2] * G.im[kFftLengthBy2] -
+ X.im[kFftLengthBy2] * G.re[kFftLengthBy2];
+ }
+ }
+ X_partition = 0;
+ limit = lim2;
+ } while (p < lim2);
+}
+#endif
+
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+// Adapts the filter partitions. (SSE2 variant)
+void AdaptPartitions_Sse2(const RenderBuffer& render_buffer,
+ const FftData& G,
+ size_t num_partitions,
+ std::vector>* H) {
+ rtc::ArrayView> render_buffer_data =
+ render_buffer.GetFftBuffer();
+ const size_t num_render_channels = render_buffer_data[0].size();
+ const size_t lim1 = std::min(
+ render_buffer_data.size() - render_buffer.Position(), num_partitions);
+ const size_t lim2 = num_partitions;
+ constexpr size_t kNumFourBinBands = kFftLengthBy2 / 4;
+
+ size_t X_partition = render_buffer.Position();
+ size_t limit = lim1;
+ size_t p = 0;
+ do {
+ for (; p < limit; ++p, ++X_partition) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ FftData& H_p_ch = (*H)[p][ch];
+ const FftData& X = render_buffer_data[X_partition][ch];
+
+ for (size_t 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]);
+ 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_p_ch.re[k]);
+ const __m128 H_im = _mm_loadu_ps(&H_p_ch.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_p_ch.re[k], g);
+ _mm_storeu_ps(&H_p_ch.im[k], h);
+ }
+ }
+ }
+ X_partition = 0;
+ limit = lim2;
+ } while (p < lim2);
+
+ X_partition = render_buffer.Position();
+ limit = lim1;
+ p = 0;
+ do {
+ for (; p < limit; ++p, ++X_partition) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ FftData& H_p_ch = (*H)[p][ch];
+ const FftData& X = render_buffer_data[X_partition][ch];
+
+ H_p_ch.re[kFftLengthBy2] += X.re[kFftLengthBy2] * G.re[kFftLengthBy2] +
+ X.im[kFftLengthBy2] * G.im[kFftLengthBy2];
+ H_p_ch.im[kFftLengthBy2] += X.re[kFftLengthBy2] * G.im[kFftLengthBy2] -
+ X.im[kFftLengthBy2] * G.re[kFftLengthBy2];
+ }
+ }
+
+ X_partition = 0;
+ limit = lim2;
+ } while (p < lim2);
+}
+#endif
+
+// Produces the filter output.
+void ApplyFilter(const RenderBuffer& render_buffer,
+ size_t num_partitions,
+ const std::vector>& H,
+ FftData* S) {
+ S->re.fill(0.f);
+ S->im.fill(0.f);
+
+ rtc::ArrayView> render_buffer_data =
+ render_buffer.GetFftBuffer();
+ size_t index = render_buffer.Position();
+ const size_t num_render_channels = render_buffer_data[index].size();
+ for (size_t p = 0; p < num_partitions; ++p) {
+ RTC_DCHECK_EQ(num_render_channels, H[p].size());
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ const FftData& X_p_ch = render_buffer_data[index][ch];
+ const FftData& H_p_ch = H[p][ch];
+ for (size_t k = 0; k < kFftLengthBy2Plus1; ++k) {
+ S->re[k] += X_p_ch.re[k] * H_p_ch.re[k] - X_p_ch.im[k] * H_p_ch.im[k];
+ S->im[k] += X_p_ch.re[k] * H_p_ch.im[k] + X_p_ch.im[k] * H_p_ch.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,
+ size_t num_partitions,
+ const std::vector>& H,
+ FftData* S) {
+ // const RenderBuffer& render_buffer,
+ // rtc::ArrayView H,
+ // FftData* S) {
+ RTC_DCHECK_GE(H.size(), H.size() - 1);
+ S->Clear();
+
+ rtc::ArrayView> render_buffer_data =
+ render_buffer.GetFftBuffer();
+ const size_t num_render_channels = render_buffer_data[0].size();
+ const size_t lim1 = std::min(
+ render_buffer_data.size() - render_buffer.Position(), num_partitions);
+ const size_t lim2 = num_partitions;
+ constexpr size_t kNumFourBinBands = kFftLengthBy2 / 4;
+
+ size_t X_partition = render_buffer.Position();
+ size_t p = 0;
+ size_t limit = lim1;
+ do {
+ for (; p < limit; ++p, ++X_partition) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ const FftData& H_p_ch = H[p][ch];
+ const FftData& X = render_buffer_data[X_partition][ch];
+ for (size_t 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_p_ch.re[k]);
+ const float32x4_t H_im = vld1q_f32(&H_p_ch.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_partition = 0;
+ } while (p < lim2);
+
+ X_partition = render_buffer.Position();
+ p = 0;
+ limit = lim1;
+ do {
+ for (; p < limit; ++p, ++X_partition) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ const FftData& H_p_ch = H[p][ch];
+ const FftData& X = render_buffer_data[X_partition][ch];
+ S->re[kFftLengthBy2] += X.re[kFftLengthBy2] * H_p_ch.re[kFftLengthBy2] -
+ X.im[kFftLengthBy2] * H_p_ch.im[kFftLengthBy2];
+ S->im[kFftLengthBy2] += X.re[kFftLengthBy2] * H_p_ch.im[kFftLengthBy2] +
+ X.im[kFftLengthBy2] * H_p_ch.re[kFftLengthBy2];
+ }
+ }
+ limit = lim2;
+ X_partition = 0;
+ } while (p < lim2);
+}
+#endif
+
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+// Produces the filter output (SSE2 variant).
+void ApplyFilter_Sse2(const RenderBuffer& render_buffer,
+ size_t num_partitions,
+ const std::vector>& H,
+ FftData* S) {
+ // const RenderBuffer& render_buffer,
+ // rtc::ArrayView H,
+ // FftData* S) {
+ RTC_DCHECK_GE(H.size(), H.size() - 1);
+ S->re.fill(0.f);
+ S->im.fill(0.f);
+
+ rtc::ArrayView> render_buffer_data =
+ render_buffer.GetFftBuffer();
+ const size_t num_render_channels = render_buffer_data[0].size();
+ const size_t lim1 = std::min(
+ render_buffer_data.size() - render_buffer.Position(), num_partitions);
+ const size_t lim2 = num_partitions;
+ constexpr size_t kNumFourBinBands = kFftLengthBy2 / 4;
+
+ size_t X_partition = render_buffer.Position();
+ size_t p = 0;
+ size_t limit = lim1;
+ do {
+ for (; p < limit; ++p, ++X_partition) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ const FftData& H_p_ch = H[p][ch];
+ const FftData& X = render_buffer_data[X_partition][ch];
+ for (size_t 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_p_ch.re[k]);
+ const __m128 H_im = _mm_loadu_ps(&H_p_ch.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_partition = 0;
+ } while (p < lim2);
+
+ X_partition = render_buffer.Position();
+ p = 0;
+ limit = lim1;
+ do {
+ for (; p < limit; ++p, ++X_partition) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ const FftData& H_p_ch = H[p][ch];
+ const FftData& X = render_buffer_data[X_partition][ch];
+ S->re[kFftLengthBy2] += X.re[kFftLengthBy2] * H_p_ch.re[kFftLengthBy2] -
+ X.im[kFftLengthBy2] * H_p_ch.im[kFftLengthBy2];
+ S->im[kFftLengthBy2] += X.re[kFftLengthBy2] * H_p_ch.im[kFftLengthBy2] +
+ X.im[kFftLengthBy2] * H_p_ch.re[kFftLengthBy2];
+ }
+ }
+ limit = lim2;
+ X_partition = 0;
+ } while (p < lim2);
+}
+#endif
+
+} // namespace aec3
+
+namespace {
+
+// Ensures that the newly added filter partitions after a size increase are set
+// to zero.
+void ZeroFilter(size_t old_size,
+ size_t new_size,
+ std::vector>* H) {
+ RTC_DCHECK_GE(H->size(), old_size);
+ RTC_DCHECK_GE(H->size(), new_size);
+
+ for (size_t p = old_size; p < new_size; ++p) {
+ RTC_DCHECK_EQ((*H)[p].size(), (*H)[0].size());
+ for (size_t ch = 0; ch < (*H)[0].size(); ++ch) {
+ (*H)[p][ch].Clear();
+ }
+ }
+}
+
+} // namespace
+
+AdaptiveFirFilter::AdaptiveFirFilter(size_t max_size_partitions,
+ size_t initial_size_partitions,
+ size_t size_change_duration_blocks,
+ size_t num_render_channels,
+ Aec3Optimization optimization,
+ ApmDataDumper* data_dumper)
+ : data_dumper_(data_dumper),
+ fft_(),
+ optimization_(optimization),
+ num_render_channels_(num_render_channels),
+ max_size_partitions_(max_size_partitions),
+ size_change_duration_blocks_(
+ static_cast(size_change_duration_blocks)),
+ current_size_partitions_(initial_size_partitions),
+ target_size_partitions_(initial_size_partitions),
+ old_target_size_partitions_(initial_size_partitions),
+ H_(max_size_partitions_, std::vector(num_render_channels_)) {
+ RTC_DCHECK(data_dumper_);
+ RTC_DCHECK_GE(max_size_partitions, initial_size_partitions);
+
+ RTC_DCHECK_LT(0, size_change_duration_blocks_);
+ one_by_size_change_duration_blocks_ = 1.f / size_change_duration_blocks_;
+
+ ZeroFilter(0, max_size_partitions_, &H_);
+
+ SetSizePartitions(current_size_partitions_, true);
+}
+
+AdaptiveFirFilter::~AdaptiveFirFilter() = default;
+
+void AdaptiveFirFilter::HandleEchoPathChange() {
+ // TODO(peah): Check the value and purpose of the code below.
+ ZeroFilter(current_size_partitions_, max_size_partitions_, &H_);
+}
+
+void AdaptiveFirFilter::SetSizePartitions(size_t size, bool immediate_effect) {
+ RTC_DCHECK_EQ(max_size_partitions_, H_.capacity());
+ RTC_DCHECK_LE(size, max_size_partitions_);
+
+ target_size_partitions_ = std::min(max_size_partitions_, size);
+ if (immediate_effect) {
+ size_t old_size_partitions_ = current_size_partitions_;
+ current_size_partitions_ = old_target_size_partitions_ =
+ target_size_partitions_;
+ ZeroFilter(old_size_partitions_, current_size_partitions_, &H_);
+
+ partition_to_constrain_ =
+ std::min(partition_to_constrain_, current_size_partitions_ - 1);
+ size_change_counter_ = 0;
+ } else {
+ size_change_counter_ = size_change_duration_blocks_;
+ }
+}
+
+void AdaptiveFirFilter::UpdateSize() {
+ RTC_DCHECK_GE(size_change_duration_blocks_, size_change_counter_);
+ size_t old_size_partitions_ = current_size_partitions_;
+ if (size_change_counter_ > 0) {
+ --size_change_counter_;
+
+ auto average = [](float from, float to, float from_weight) {
+ return from * from_weight + to * (1.f - from_weight);
+ };
+
+ float change_factor =
+ size_change_counter_ * one_by_size_change_duration_blocks_;
+
+ current_size_partitions_ = average(old_target_size_partitions_,
+ target_size_partitions_, change_factor);
+
+ partition_to_constrain_ =
+ std::min(partition_to_constrain_, current_size_partitions_ - 1);
+ } else {
+ current_size_partitions_ = old_target_size_partitions_ =
+ target_size_partitions_;
+ }
+ ZeroFilter(old_size_partitions_, current_size_partitions_, &H_);
+ RTC_DCHECK_LE(0, size_change_counter_);
+}
+
+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, current_size_partitions_, H_, S);
+ break;
+#endif
+#if defined(WEBRTC_HAS_NEON)
+ case Aec3Optimization::kNeon:
+ aec3::ApplyFilter_Neon(render_buffer, current_size_partitions_, H_, S);
+ break;
+#endif
+ default:
+ aec3::ApplyFilter(render_buffer, current_size_partitions_, H_, S);
+ }
+}
+
+void AdaptiveFirFilter::Adapt(const RenderBuffer& render_buffer,
+ const FftData& G) {
+ // Adapt the filter and update the filter size.
+ AdaptAndUpdateSize(render_buffer, G);
+
+ // Constrain the filter partitions in a cyclic manner.
+ Constrain();
+}
+
+void AdaptiveFirFilter::Adapt(const RenderBuffer& render_buffer,
+ const FftData& G,
+ std::vector* impulse_response) {
+ // Adapt the filter and update the filter size.
+ AdaptAndUpdateSize(render_buffer, G);
+
+ // Constrain the filter partitions in a cyclic manner.
+ ConstrainAndUpdateImpulseResponse(impulse_response);
+}
+
+void AdaptiveFirFilter::ComputeFrequencyResponse(
+ std::vector>* H2) const {
+ RTC_DCHECK_GE(max_size_partitions_, H2->capacity());
+
+ H2->resize(current_size_partitions_);
+
+ switch (optimization_) {
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+ case Aec3Optimization::kSse2:
+ aec3::ComputeFrequencyResponse_Sse2(current_size_partitions_, H_, H2);
+ break;
+#endif
+#if defined(WEBRTC_HAS_NEON)
+ case Aec3Optimization::kNeon:
+ aec3::ComputeFrequencyResponse_Neon(current_size_partitions_, H_, H2);
+ break;
+#endif
+ default:
+ aec3::ComputeFrequencyResponse(current_size_partitions_, H_, H2);
+ }
+}
+
+void AdaptiveFirFilter::AdaptAndUpdateSize(const RenderBuffer& render_buffer,
+ const FftData& G) {
+ // Update the filter size if needed.
+ UpdateSize();
+
+ // Adapt the filter.
+ switch (optimization_) {
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+ case Aec3Optimization::kSse2:
+ aec3::AdaptPartitions_Sse2(render_buffer, G, current_size_partitions_,
+ &H_);
+ break;
+#endif
+#if defined(WEBRTC_HAS_NEON)
+ case Aec3Optimization::kNeon:
+ aec3::AdaptPartitions_Neon(render_buffer, G, current_size_partitions_,
+ &H_);
+ break;
+#endif
+ default:
+ aec3::AdaptPartitions(render_buffer, G, current_size_partitions_, &H_);
+ }
+}
+
+// Constrains the partition of the frequency domain filter to be limited in
+// time via setting the relevant time-domain coefficients to zero and updates
+// the corresponding values in an externally stored impulse response estimate.
+void AdaptiveFirFilter::ConstrainAndUpdateImpulseResponse(
+ std::vector* impulse_response) {
+ RTC_DCHECK_EQ(GetTimeDomainLength(max_size_partitions_),
+ impulse_response->capacity());
+ impulse_response->resize(GetTimeDomainLength(current_size_partitions_));
+ std::array h;
+ impulse_response->resize(GetTimeDomainLength(current_size_partitions_));
+ std::fill(
+ impulse_response->begin() + partition_to_constrain_ * kFftLengthBy2,
+ impulse_response->begin() + (partition_to_constrain_ + 1) * kFftLengthBy2,
+ 0.f);
+
+ for (size_t ch = 0; ch < num_render_channels_; ++ch) {
+ fft_.Ifft(H_[partition_to_constrain_][ch], &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);
+
+ if (ch == 0) {
+ std::copy(
+ h.begin(), h.begin() + kFftLengthBy2,
+ impulse_response->begin() + partition_to_constrain_ * kFftLengthBy2);
+ } else {
+ for (size_t k = 0, j = partition_to_constrain_ * kFftLengthBy2;
+ k < kFftLengthBy2; ++k, ++j) {
+ if (fabsf((*impulse_response)[j]) < fabsf(h[k])) {
+ (*impulse_response)[j] = h[k];
+ }
+ }
+ }
+
+ fft_.Fft(&h, &H_[partition_to_constrain_][ch]);
+ }
+
+ partition_to_constrain_ =
+ partition_to_constrain_ < (current_size_partitions_ - 1)
+ ? partition_to_constrain_ + 1
+ : 0;
+}
+
+// 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 h;
+ for (size_t ch = 0; ch < num_render_channels_; ++ch) {
+ fft_.Ifft(H_[partition_to_constrain_][ch], &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);
+
+ fft_.Fft(&h, &H_[partition_to_constrain_][ch]);
+ }
+
+ partition_to_constrain_ =
+ partition_to_constrain_ < (current_size_partitions_ - 1)
+ ? partition_to_constrain_ + 1
+ : 0;
+}
+
+void AdaptiveFirFilter::ScaleFilter(float factor) {
+ for (auto& H_p : H_) {
+ for (auto& H_p_ch : H_p) {
+ for (auto& re : H_p_ch.re) {
+ re *= factor;
+ }
+ for (auto& im : H_p_ch.im) {
+ im *= factor;
+ }
+ }
+ }
+}
+
+// Set the filter coefficients.
+void AdaptiveFirFilter::SetFilter(size_t num_partitions,
+ const std::vector>& H) {
+ const size_t min_num_partitions =
+ std::min(current_size_partitions_, num_partitions);
+ for (size_t p = 0; p < min_num_partitions; ++p) {
+ RTC_DCHECK_EQ(H_[p].size(), H[p].size());
+ RTC_DCHECK_EQ(num_render_channels_, H_[p].size());
+
+ for (size_t ch = 0; ch < num_render_channels_; ++ch) {
+ std::copy(H[p][ch].re.begin(), H[p][ch].re.end(), H_[p][ch].re.begin());
+ std::copy(H[p][ch].im.begin(), H[p][ch].im.end(), H_[p][ch].im.begin());
+ }
+ }
+}
+
+} // namespace webrtc
diff --git a/audio_processing/aec3/adaptive_fir_filter.h b/audio_processing/aec3/adaptive_fir_filter.h
new file mode 100644
index 0000000..6ff36ca
--- /dev/null
+++ b/audio_processing/aec3/adaptive_fir_filter.h
@@ -0,0 +1,176 @@
+/*
+ * 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 MODULES_AUDIO_PROCESSING_AEC3_ADAPTIVE_FIR_FILTER_H_
+#define MODULES_AUDIO_PROCESSING_AEC3_ADAPTIVE_FIR_FILTER_H_
+
+#include
+
+#include
+#include
+
+#include "rtc_base/array_view.h"
+#include "audio_processing/aec3/aec3_common.h"
+#include "audio_processing/aec3/aec3_fft.h"
+#include "audio_processing/aec3/fft_data.h"
+#include "audio_processing/aec3/render_buffer.h"
+#include "audio_processing/logging/apm_data_dumper.h"
+#include "rtc_base/system/arch.h"
+
+namespace webrtc {
+namespace aec3 {
+// Computes and stores the frequency response of the filter.
+void ComputeFrequencyResponse(
+ size_t num_partitions,
+ const std::vector>& H,
+ std::vector>* H2);
+#if defined(WEBRTC_HAS_NEON)
+void ComputeFrequencyResponse_Neon(
+ size_t num_partitions,
+ const std::vector>& H,
+ std::vector>* H2);
+#endif
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+void ComputeFrequencyResponse_Sse2(
+ size_t num_partitions,
+ const std::vector>& H,
+ std::vector>* H2);
+#endif
+
+// Adapts the filter partitions.
+void AdaptPartitions(const RenderBuffer& render_buffer,
+ const FftData& G,
+ size_t num_partitions,
+ std::vector>* H);
+#if defined(WEBRTC_HAS_NEON)
+void AdaptPartitions_Neon(const RenderBuffer& render_buffer,
+ const FftData& G,
+ size_t num_partitions,
+ std::vector>* H);
+#endif
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+void AdaptPartitions_Sse2(const RenderBuffer& render_buffer,
+ const FftData& G,
+ size_t num_partitions,
+ std::vector>* H);
+#endif
+
+// Produces the filter output.
+void ApplyFilter(const RenderBuffer& render_buffer,
+ size_t num_partitions,
+ const std::vector>& H,
+ FftData* S);
+#if defined(WEBRTC_HAS_NEON)
+void ApplyFilter_Neon(const RenderBuffer& render_buffer,
+ size_t num_partitions,
+ const std::vector>& H,
+ FftData* S);
+#endif
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+void ApplyFilter_Sse2(const RenderBuffer& render_buffer,
+ size_t num_partitions,
+ const std::vector>& H,
+ FftData* S);
+#endif
+
+} // namespace aec3
+
+// Provides a frequency domain adaptive filter functionality.
+class AdaptiveFirFilter {
+ public:
+ AdaptiveFirFilter(size_t max_size_partitions,
+ size_t initial_size_partitions,
+ size_t size_change_duration_blocks,
+ size_t num_render_channels,
+ Aec3Optimization optimization,
+ ApmDataDumper* data_dumper);
+
+ ~AdaptiveFirFilter();
+
+ AdaptiveFirFilter(const AdaptiveFirFilter&) = delete;
+ AdaptiveFirFilter& operator=(const AdaptiveFirFilter&) = delete;
+
+ // Produces the output of the filter.
+ void Filter(const RenderBuffer& render_buffer, FftData* S) const;
+
+ // Adapts the filter and updates an externally stored impulse response
+ // estimate.
+ void Adapt(const RenderBuffer& render_buffer,
+ const FftData& G,
+ std::vector* impulse_response);
+
+ // 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 current_size_partitions_; }
+
+ // Sets the filter size.
+ void SetSizePartitions(size_t size, bool immediate_effect);
+
+ // Computes the frequency responses for the filter partitions.
+ void ComputeFrequencyResponse(
+ std::vector>* H2) const;
+
+ // Returns the maximum number of partitions for the filter.
+ size_t max_filter_size_partitions() const { return max_size_partitions_; }
+
+ void DumpFilter(const char* name_frequency_domain) {
+ for (size_t p = 0; p < max_size_partitions_; ++p) {
+ data_dumper_->DumpRaw(name_frequency_domain, H_[p][0].re);
+ data_dumper_->DumpRaw(name_frequency_domain, H_[p][0].im);
+ }
+ }
+
+ // Scale the filter impulse response and spectrum by a factor.
+ void ScaleFilter(float factor);
+
+ // Set the filter coefficients.
+ void SetFilter(size_t num_partitions,
+ const std::vector>& H);
+
+ // Gets the filter coefficients.
+ const std::vector>& GetFilter() const { return H_; }
+
+ private:
+ // Adapts the filter and updates the filter size.
+ void AdaptAndUpdateSize(const RenderBuffer& render_buffer, const FftData& G);
+
+ // Constrain the filter partitions in a cyclic manner.
+ void Constrain();
+ // Constrains the filter in a cyclic manner and updates the corresponding
+ // values in the supplied impulse response.
+ void ConstrainAndUpdateImpulseResponse(std::vector* impulse_response);
+
+ // Gradually Updates the current filter size towards the target size.
+ void UpdateSize();
+
+ ApmDataDumper* const data_dumper_;
+ const Aec3Fft fft_;
+ const Aec3Optimization optimization_;
+ const size_t num_render_channels_;
+ const size_t max_size_partitions_;
+ const int size_change_duration_blocks_;
+ float one_by_size_change_duration_blocks_;
+ size_t current_size_partitions_;
+ size_t target_size_partitions_;
+ size_t old_target_size_partitions_;
+ int size_change_counter_ = 0;
+ std::vector> H_;
+ size_t partition_to_constrain_ = 0;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_AUDIO_PROCESSING_AEC3_ADAPTIVE_FIR_FILTER_H_
diff --git a/audio_processing/aec3/adaptive_fir_filter_erl.cc b/audio_processing/aec3/adaptive_fir_filter_erl.cc
new file mode 100644
index 0000000..648348d
--- /dev/null
+++ b/audio_processing/aec3/adaptive_fir_filter_erl.cc
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2019 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 "audio_processing/aec3/adaptive_fir_filter_erl.h"
+
+#include
+#include
+
+#if defined(WEBRTC_HAS_NEON)
+#include
+#endif
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+#include
+#endif
+
+namespace webrtc {
+
+namespace aec3 {
+
+// Computes and stores the echo return loss estimate of the filter, which is the
+// sum of the partition frequency responses.
+void ErlComputer(const std::vector>& H2,
+ rtc::ArrayView erl) {
+ std::fill(erl.begin(), erl.end(), 0.f);
+ for (auto& H2_j : H2) {
+ std::transform(H2_j.begin(), H2_j.end(), erl.begin(), erl.begin(),
+ std::plus());
+ }
+}
+
+#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 ErlComputer_NEON(
+ const std::vector>& H2,
+ rtc::ArrayView erl) {
+ std::fill(erl.begin(), erl.end(), 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 ErlComputer_SSE2(
+ const std::vector>& H2,
+ rtc::ArrayView erl) {
+ std::fill(erl.begin(), erl.end(), 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
+
+} // namespace aec3
+
+void ComputeErl(const Aec3Optimization& optimization,
+ const std::vector>& H2,
+ rtc::ArrayView erl) {
+ RTC_DCHECK_EQ(kFftLengthBy2Plus1, erl.size());
+ // Update the frequency response and echo return loss for the filter.
+ switch (optimization) {
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+ case Aec3Optimization::kSse2:
+ aec3::ErlComputer_SSE2(H2, erl);
+ break;
+#endif
+#if defined(WEBRTC_HAS_NEON)
+ case Aec3Optimization::kNeon:
+
+ aec3::ErlComputer_NEON(H2, erl);
+ break;
+#endif
+ default:
+ aec3::ErlComputer(H2, erl);
+ }
+}
+
+} // namespace webrtc
diff --git a/audio_processing/aec3/adaptive_fir_filter_erl.h b/audio_processing/aec3/adaptive_fir_filter_erl.h
new file mode 100644
index 0000000..879cefb
--- /dev/null
+++ b/audio_processing/aec3/adaptive_fir_filter_erl.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2019 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 MODULES_AUDIO_PROCESSING_AEC3_ADAPTIVE_FIR_FILTER_ERL_H_
+#define MODULES_AUDIO_PROCESSING_AEC3_ADAPTIVE_FIR_FILTER_ERL_H_
+
+#include
+
+#include
+#include
+
+#include "rtc_base/array_view.h"
+#include "audio_processing/aec3/aec3_common.h"
+#include "rtc_base/system/arch.h"
+
+namespace webrtc {
+namespace aec3 {
+
+// Computes and stores the echo return loss estimate of the filter, which is the
+// sum of the partition frequency responses.
+void ErlComputer(const std::vector>& H2,
+ rtc::ArrayView erl);
+#if defined(WEBRTC_HAS_NEON)
+void ErlComputer_NEON(
+ const std::vector>& H2,
+ rtc::ArrayView erl);
+#endif
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+void ErlComputer_SSE2(
+ const std::vector>& H2,
+ rtc::ArrayView erl);
+#endif
+
+} // namespace aec3
+
+// Computes the echo return loss based on a frequency response.
+void ComputeErl(const Aec3Optimization& optimization,
+ const std::vector>& H2,
+ rtc::ArrayView erl);
+
+} // namespace webrtc
+
+#endif // MODULES_AUDIO_PROCESSING_AEC3_ADAPTIVE_FIR_FILTER_ERL_H_
diff --git a/audio_processing/aec3/adaptive_fir_filter_erl_unittest.cc b/audio_processing/aec3/adaptive_fir_filter_erl_unittest.cc
new file mode 100644
index 0000000..069fc9f
--- /dev/null
+++ b/audio_processing/aec3/adaptive_fir_filter_erl_unittest.cc
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019 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 "modules/audio_processing/aec3/adaptive_fir_filter_erl.h"
+
+#include
+#include
+
+#include "rtc_base/system/arch.h"
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+#include
+#endif
+
+#include "system_wrappers/include/cpu_features_wrapper.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace aec3 {
+
+#if defined(WEBRTC_HAS_NEON)
+// 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> H2(kNumPartitions);
+ std::array erl;
+ std::array 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;
+ }
+ }
+
+ ErlComputer(H2, erl);
+ ErlComputer_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 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> H2(kNumPartitions);
+ std::array erl;
+ std::array 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;
+ }
+ }
+
+ ErlComputer(H2, erl);
+ ErlComputer_SSE2(H2, erl_SSE2);
+
+ for (size_t j = 0; j < erl.size(); ++j) {
+ EXPECT_FLOAT_EQ(erl[j], erl_SSE2[j]);
+ }
+ }
+}
+
+#endif
+
+} // namespace aec3
+} // namespace webrtc
diff --git a/audio_processing/aec3/adaptive_fir_filter_unittest.cc b/audio_processing/aec3/adaptive_fir_filter_unittest.cc
new file mode 100644
index 0000000..e99ff2a
--- /dev/null
+++ b/audio_processing/aec3/adaptive_fir_filter_unittest.cc
@@ -0,0 +1,485 @@
+/*
+ * 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 "modules/audio_processing/aec3/adaptive_fir_filter.h"
+
+// Defines WEBRTC_ARCH_X86_FAMILY, used below.
+#include
+
+#include
+#include
+#include
+
+#include "rtc_base/system/arch.h"
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+#include
+#endif
+
+#include "modules/audio_processing/aec3/adaptive_fir_filter_erl.h"
+#include "modules/audio_processing/aec3/aec3_fft.h"
+#include "modules/audio_processing/aec3/aec_state.h"
+#include "modules/audio_processing/aec3/render_delay_buffer.h"
+#include "modules/audio_processing/aec3/render_signal_analyzer.h"
+#include "modules/audio_processing/aec3/shadow_filter_update_gain.h"
+#include "modules/audio_processing/logging/apm_data_dumper.h"
+#include "modules/audio_processing/test/echo_canceller_test_tools.h"
+#include "modules/audio_processing/utility/cascaded_biquad_filter.h"
+#include "rtc_base/arraysize.h"
+#include "rtc_base/numerics/safe_minmax.h"
+#include "rtc_base/random.h"
+#include "rtc_base/strings/string_builder.h"
+#include "system_wrappers/include/cpu_features_wrapper.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace aec3 {
+namespace {
+
+std::string ProduceDebugText(size_t num_render_channels, size_t delay) {
+ rtc::StringBuilder ss;
+ ss << "delay: " << delay << ", ";
+ ss << "num_render_channels:" << num_render_channels;
+ return ss.Release();
+}
+
+} // namespace
+
+#if defined(WEBRTC_HAS_NEON)
+// Verifies that the optimized methods for filter adaptation are similar to
+// their reference counterparts.
+TEST(AdaptiveFirFilter, FilterAdaptationNeonOptimizations) {
+ for (size_t num_partitions : {2, 5, 12, 30, 50}) {
+ for (size_t num_render_channels : {1, 2, 4, 8}) {
+ constexpr int kSampleRateHz = 48000;
+ constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
+ std::unique_ptr render_delay_buffer(
+ RenderDelayBuffer::Create(EchoCanceller3Config(), kSampleRateHz,
+ num_render_channels));
+ Random random_generator(42U);
+ std::vector>> x(
+ kNumBands,
+ std::vector>(num_render_channels,
+ std::vector(kBlockSize, 0.f)));
+ FftData S_C;
+ FftData S_Neon;
+ FftData G;
+ Aec3Fft fft;
+ std::vector> H_C(
+ num_partitions, std::vector(num_render_channels));
+ std::vector> H_Neon(
+ num_partitions, std::vector(num_render_channels));
+ for (size_t p = 0; p < num_partitions; ++p) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ H_C[p][ch].Clear();
+ H_Neon[p][ch].Clear();
+ }
+ }
+
+ for (size_t k = 0; k < 30; ++k) {
+ for (size_t band = 0; band < x.size(); ++band) {
+ for (size_t ch = 0; ch < x[band].size(); ++ch) {
+ RandomizeSampleVector(&random_generator, x[band][ch]);
+ }
+ }
+ render_delay_buffer->Insert(x);
+ if (k == 0) {
+ render_delay_buffer->Reset();
+ }
+ render_delay_buffer->PrepareCaptureProcessing();
+ }
+ auto* const render_buffer = render_delay_buffer->GetRenderBuffer();
+
+ 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, num_partitions, &H_Neon);
+ AdaptPartitions(*render_buffer, G, num_partitions, &H_C);
+ AdaptPartitions_Neon(*render_buffer, G, num_partitions, &H_Neon);
+ AdaptPartitions(*render_buffer, G, num_partitions, &H_C);
+
+ for (size_t p = 0; p < num_partitions; ++p) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ for (size_t j = 0; j < H_C[p][ch].re.size(); ++j) {
+ EXPECT_FLOAT_EQ(H_C[p][ch].re[j], H_Neon[p][ch].re[j]);
+ EXPECT_FLOAT_EQ(H_C[p][ch].im[j], H_Neon[p][ch].im[j]);
+ }
+ }
+ }
+
+ ApplyFilter_Neon(*render_buffer, num_partitions, H_Neon, &S_Neon);
+ ApplyFilter(*render_buffer, num_partitions, 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, ComputeFrequencyResponseNeonOptimization) {
+ for (size_t num_partitions : {2, 5, 12, 30, 50}) {
+ for (size_t num_render_channels : {1, 2, 4, 8}) {
+ std::vector> H(
+ num_partitions, std::vector(num_render_channels));
+ std::vector> H2(num_partitions);
+ std::vector> H2_Neon(
+ num_partitions);
+
+ for (size_t p = 0; p < num_partitions; ++p) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ for (size_t k = 0; k < H[p][ch].re.size(); ++k) {
+ H[p][ch].re[k] = k + p / 3.f + ch;
+ H[p][ch].im[k] = p + k / 7.f - ch;
+ }
+ }
+ }
+
+ ComputeFrequencyResponse(num_partitions, H, &H2);
+ ComputeFrequencyResponse_Neon(num_partitions, H, &H2_Neon);
+
+ for (size_t p = 0; p < num_partitions; ++p) {
+ for (size_t k = 0; k < H2[p].size(); ++k) {
+ EXPECT_FLOAT_EQ(H2[p][k], H2_Neon[p][k]);
+ }
+ }
+ }
+ }
+}
+#endif
+
+#if defined(WEBRTC_ARCH_X86_FAMILY)
+// Verifies that the optimized methods for filter adaptation are bitexact to
+// their reference counterparts.
+TEST(AdaptiveFirFilter, FilterAdaptationSse2Optimizations) {
+ constexpr int kSampleRateHz = 48000;
+ constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
+ bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0);
+ if (use_sse2) {
+ for (size_t num_partitions : {2, 5, 12, 30, 50}) {
+ for (size_t num_render_channels : {1, 2, 4, 8}) {
+ std::unique_ptr render_delay_buffer(
+ RenderDelayBuffer::Create(EchoCanceller3Config(), kSampleRateHz,
+ num_render_channels));
+ Random random_generator(42U);
+ std::vector>> x(
+ kNumBands,
+ std::vector>(
+ num_render_channels, std::vector(kBlockSize, 0.f)));
+ FftData S_C;
+ FftData S_Sse2;
+ FftData G;
+ Aec3Fft fft;
+ std::vector> H_C(
+ num_partitions, std::vector(num_render_channels));
+ std::vector> H_Sse2(
+ num_partitions, std::vector(num_render_channels));
+ for (size_t p = 0; p < num_partitions; ++p) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ H_C[p][ch].Clear();
+ H_Sse2[p][ch].Clear();
+ }
+ }
+
+ for (size_t k = 0; k < 500; ++k) {
+ for (size_t band = 0; band < x.size(); ++band) {
+ for (size_t ch = 0; ch < x[band].size(); ++ch) {
+ RandomizeSampleVector(&random_generator, x[band][ch]);
+ }
+ }
+ render_delay_buffer->Insert(x);
+ if (k == 0) {
+ render_delay_buffer->Reset();
+ }
+ render_delay_buffer->PrepareCaptureProcessing();
+ auto* const render_buffer = render_delay_buffer->GetRenderBuffer();
+
+ ApplyFilter_Sse2(*render_buffer, num_partitions, H_Sse2, &S_Sse2);
+ ApplyFilter(*render_buffer, num_partitions, 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(); });
+ std::for_each(G.im.begin(), G.im.end(),
+ [&](float& a) { a = random_generator.Rand(); });
+
+ AdaptPartitions_Sse2(*render_buffer, G, num_partitions, &H_Sse2);
+ AdaptPartitions(*render_buffer, G, num_partitions, &H_C);
+
+ for (size_t p = 0; p < num_partitions; ++p) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ for (size_t j = 0; j < H_C[p][ch].re.size(); ++j) {
+ EXPECT_FLOAT_EQ(H_C[p][ch].re[j], H_Sse2[p][ch].re[j]);
+ EXPECT_FLOAT_EQ(H_C[p][ch].im[j], H_Sse2[p][ch].im[j]);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// Verifies that the optimized method for frequency response computation is
+// bitexact to the reference counterpart.
+TEST(AdaptiveFirFilter, ComputeFrequencyResponseSse2Optimization) {
+ bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0);
+ if (use_sse2) {
+ for (size_t num_partitions : {2, 5, 12, 30, 50}) {
+ for (size_t num_render_channels : {1, 2, 4, 8}) {
+ std::vector> H(
+ num_partitions, std::vector(num_render_channels));
+ std::vector> H2(num_partitions);
+ std::vector> H2_Sse2(
+ num_partitions);
+
+ for (size_t p = 0; p < num_partitions; ++p) {
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ for (size_t k = 0; k < H[p][ch].re.size(); ++k) {
+ H[p][ch].re[k] = k + p / 3.f + ch;
+ H[p][ch].im[k] = p + k / 7.f - ch;
+ }
+ }
+ }
+
+ ComputeFrequencyResponse(num_partitions, H, &H2);
+ ComputeFrequencyResponse_Sse2(num_partitions, H, &H2_Sse2);
+
+ for (size_t p = 0; p < num_partitions; ++p) {
+ for (size_t k = 0; k < H2[p].size(); ++k) {
+ EXPECT_FLOAT_EQ(H2[p][k], H2_Sse2[p][k]);
+ }
+ }
+ }
+ }
+ }
+}
+
+#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, 9, 250, 1, DetectOptimization(), nullptr),
+ "");
+}
+
+// Verifies that the check for non-null filter output works.
+TEST(AdaptiveFirFilter, NullFilterOutput) {
+ ApmDataDumper data_dumper(42);
+ AdaptiveFirFilter filter(9, 9, 250, 1, DetectOptimization(), &data_dumper);
+ std::unique_ptr render_delay_buffer(
+ RenderDelayBuffer::Create(EchoCanceller3Config(), 48000, 1));
+ EXPECT_DEATH(filter.Filter(*render_delay_buffer->GetRenderBuffer(), nullptr),
+ "");
+}
+
+#endif
+
+// Verifies that the filter statistics can be accessed when filter statistics
+// are turned on.
+TEST(AdaptiveFirFilter, FilterStatisticsAccess) {
+ ApmDataDumper data_dumper(42);
+ Aec3Optimization optimization = DetectOptimization();
+ AdaptiveFirFilter filter(9, 9, 250, 1, optimization, &data_dumper);
+ std::vector> H2(
+ filter.max_filter_size_partitions(),
+ std::array());
+ for (auto& H2_k : H2) {
+ H2_k.fill(0.f);
+ }
+
+ std::array erl;
+ ComputeErl(optimization, H2, erl);
+ filter.ComputeFrequencyResponse(&H2);
+}
+
+// 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, filter_size, 250, 1,
+ 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 int kSampleRateHz = 48000;
+ constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+ constexpr size_t kNumBlocksToProcessPerRenderChannel = 1000;
+
+ for (size_t num_capture_channels : {1, 2, 4}) {
+ for (size_t num_render_channels : {1, 2, 3, 6, 8}) {
+ ApmDataDumper data_dumper(42);
+ EchoCanceller3Config config;
+
+ if (num_render_channels == 33) {
+ config.filter.main = {13, 0.00005f, 0.0005f, 0.0001f, 2.f, 20075344.f};
+ config.filter.shadow = {13, 0.1f, 20075344.f};
+ config.filter.main_initial = {12, 0.005f, 0.5f,
+ 0.001f, 2.f, 20075344.f};
+ config.filter.shadow_initial = {12, 0.7f, 20075344.f};
+ }
+
+ AdaptiveFirFilter filter(
+ config.filter.main.length_blocks, config.filter.main.length_blocks,
+ config.filter.config_change_duration_blocks, num_render_channels,
+ DetectOptimization(), &data_dumper);
+ std::vector>> H2(
+ num_capture_channels,
+ std::vector>(
+ filter.max_filter_size_partitions(),
+ std::array()));
+ std::vector> h(
+ num_capture_channels,
+ std::vector(
+ GetTimeDomainLength(filter.max_filter_size_partitions()), 0.f));
+ Aec3Fft fft;
+ config.delay.default_delay = 1;
+ std::unique_ptr render_delay_buffer(
+ RenderDelayBuffer::Create(config, kSampleRateHz,
+ num_render_channels));
+ ShadowFilterUpdateGain gain(config.filter.shadow,
+ config.filter.config_change_duration_blocks);
+ Random random_generator(42U);
+ std::vector>> x(
+ kNumBands,
+ std::vector>(num_render_channels,
+ std::vector(kBlockSize, 0.f)));
+ std::vector n(kBlockSize, 0.f);
+ std::vector y(kBlockSize, 0.f);
+ AecState aec_state(EchoCanceller3Config{}, num_capture_channels);
+ RenderSignalAnalyzer render_signal_analyzer(config);
+ absl::optional delay_estimate;
+ std::vector e(kBlockSize, 0.f);
+ std::array s_scratch;
+ std::vector output(num_capture_channels);
+ FftData S;
+ FftData G;
+ FftData E;
+ std::vector> Y2(
+ num_capture_channels);
+ std::vector> E2_main(
+ num_capture_channels);
+ std::array E2_shadow;
+ // [B,A] = butter(2,100/8000,'high')
+ constexpr CascadedBiQuadFilter::BiQuadCoefficients
+ kHighPassFilterCoefficients = {{0.97261f, -1.94523f, 0.97261f},
+ {-1.94448f, 0.94598f}};
+ for (auto& Y2_ch : Y2) {
+ Y2_ch.fill(0.f);
+ }
+ for (auto& E2_main_ch : E2_main) {
+ E2_main_ch.fill(0.f);
+ }
+ E2_shadow.fill(0.f);
+ for (auto& subtractor_output : output) {
+ subtractor_output.Reset();
+ }
+
+ constexpr float kScale = 1.0f / kFftLengthBy2;
+
+ for (size_t delay_samples : {0, 64, 150, 200, 301}) {
+ std::vector> delay_buffer(
+ num_render_channels, DelayBuffer(delay_samples));
+ std::vector> x_hp_filter(
+ num_render_channels);
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ x_hp_filter[ch] = std::make_unique(
+ kHighPassFilterCoefficients, 1);
+ }
+ CascadedBiQuadFilter y_hp_filter(kHighPassFilterCoefficients, 1);
+
+ SCOPED_TRACE(ProduceDebugText(num_render_channels, delay_samples));
+ const size_t num_blocks_to_process =
+ kNumBlocksToProcessPerRenderChannel * num_render_channels;
+ for (size_t j = 0; j < num_blocks_to_process; ++j) {
+ std::fill(y.begin(), y.end(), 0.f);
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ RandomizeSampleVector(&random_generator, x[0][ch]);
+ std::array y_channel;
+ delay_buffer[ch].Delay(x[0][ch], y_channel);
+ for (size_t k = 0; k < y.size(); ++k) {
+ y[k] += y_channel[k] / num_render_channels;
+ }
+ }
+
+ RandomizeSampleVector(&random_generator, n);
+ const float noise_scaling = 1.f / 100.f / num_render_channels;
+ for (size_t k = 0; k < y.size(); ++k) {
+ y[k] += n[k] * noise_scaling;
+ }
+
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ x_hp_filter[ch]->Process(x[0][ch]);
+ }
+ y_hp_filter.Process(y);
+
+ render_delay_buffer->Insert(x);
+ if (j == 0) {
+ render_delay_buffer->Reset();
+ }
+ render_delay_buffer->PrepareCaptureProcessing();
+ auto* const render_buffer = render_delay_buffer->GetRenderBuffer();
+
+ render_signal_analyzer.Update(*render_buffer,
+ aec_state.MinDirectPathFilterDelay());
+
+ 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, Aec3Fft::Window::kRectangular, &E);
+ for (auto& o : output) {
+ for (size_t k = 0; k < kBlockSize; ++k) {
+ o.s_main[k] = kScale * s_scratch[k + kFftLengthBy2];
+ }
+ }
+
+ std::array render_power;
+ render_buffer->SpectralSum(filter.SizePartitions(), &render_power);
+ gain.Compute(render_power, render_signal_analyzer, E,
+ filter.SizePartitions(), false, &G);
+ filter.Adapt(*render_buffer, G, &h[0]);
+ aec_state.HandleEchoPathChange(EchoPathVariability(
+ false, EchoPathVariability::DelayAdjustment::kNone, false));
+
+ filter.ComputeFrequencyResponse(&H2[0]);
+ aec_state.Update(delay_estimate, H2, h, *render_buffer, E2_main, Y2,
+ output);
+ }
+ // 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));
+ }
+ }
+ }
+}
+} // namespace aec3
+} // namespace webrtc
diff --git a/audio_processing/aec3/aec3_common.cc b/audio_processing/aec3/aec3_common.cc
new file mode 100644
index 0000000..95f114a
--- /dev/null
+++ b/audio_processing/aec3/aec3_common.cc
@@ -0,0 +1,56 @@
+/*
+ * 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 "audio_processing/aec3/aec3_common.h"
+
+#include
+
+#include "rtc_base/checks.h"
+#include "rtc_base/system/arch.h"
+#include "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;
+}
+
+float FastApproxLog2f(const float in) {
+ RTC_DCHECK_GT(in, .0f);
+ // Read and interpret float as uint32_t and then cast to float.
+ // This is done to extract the exponent (bits 30 - 23).
+ // "Right shift" of the exponent is then performed by multiplying
+ // with the constant (1/2^23). Finally, we subtract a constant to
+ // remove the bias (https://en.wikipedia.org/wiki/Exponent_bias).
+ union {
+ float dummy;
+ uint32_t a;
+ } x = {in};
+ float out = x.a;
+ out *= 1.1920929e-7f; // 1/2^23
+ out -= 126.942695f; // Remove bias.
+ return out;
+}
+
+float Log2TodB(const float in_log2) {
+ return 3.0102999566398121 * in_log2;
+}
+
+} // namespace webrtc
diff --git a/audio_processing/aec3/aec3_common.h b/audio_processing/aec3/aec3_common.h
new file mode 100644
index 0000000..ed28c88
--- /dev/null
+++ b/audio_processing/aec3/aec3_common.h
@@ -0,0 +1,114 @@
+/*
+ * 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 MODULES_AUDIO_PROCESSING_AEC3_AEC3_COMMON_H_
+#define MODULES_AUDIO_PROCESSING_AEC3_AEC3_COMMON_H_
+
+#include
+
+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 = 11;
+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 size_t kFftLengthBy2Log2 = 6;
+
+constexpr int kRenderTransferQueueSizeFrames = 100;
+
+constexpr size_t kMaxNumBands = 3;
+constexpr size_t kFrameSize = 160;
+constexpr size_t kSubFrameLength = kFrameSize / 2;
+
+constexpr size_t kBlockSize = kFftLengthBy2;
+constexpr size_t kBlockSizeLog2 = kFftLengthBy2Log2;
+
+constexpr size_t kExtendedBlockSize = 2 * kFftLengthBy2;
+constexpr size_t kMatchedFilterWindowSizeSubBlocks = 32;
+constexpr size_t kMatchedFilterAlignmentShiftSizeSubBlocks =
+ kMatchedFilterWindowSizeSubBlocks * 3 / 4;
+
+// TODO(peah): Integrate this with how it is done inside audio_processing_impl.
+constexpr size_t NumBandsForRate(int sample_rate_hz) {
+ return static_cast(sample_rate_hz / 16000);
+}
+
+constexpr bool ValidFullBandRate(int sample_rate_hz) {
+ return sample_rate_hz == 16000 || sample_rate_hz == 32000 ||
+ sample_rate_hz == 48000;
+}
+
+constexpr int GetTimeDomainLength(int filter_length_blocks) {
+ return filter_length_blocks * kFftLengthBy2;
+}
+
+constexpr size_t GetDownSampledBufferSize(size_t down_sampling_factor,
+ size_t num_matched_filters) {
+ return kBlockSize / down_sampling_factor *
+ (kMatchedFilterAlignmentShiftSizeSubBlocks * num_matched_filters +
+ kMatchedFilterWindowSizeSubBlocks + 1);
+}
+
+constexpr size_t GetRenderDelayBufferSize(size_t down_sampling_factor,
+ size_t num_matched_filters,
+ size_t filter_length_blocks) {
+ return GetDownSampledBufferSize(down_sampling_factor, num_matched_filters) /
+ (kBlockSize / down_sampling_factor) +
+ filter_length_blocks + 1;
+}
+
+// Detects what kind of optimizations to use for the code.
+Aec3Optimization DetectOptimization();
+
+// Computes the log2 of the input in a fast an approximate manner.
+float FastApproxLog2f(const float in);
+
+// Returns dB from a power quantity expressed in log2.
+float Log2TodB(const float in_log2);
+
+static_assert(1 << kBlockSizeLog2 == kBlockSize,
+ "Proper number of shifts for blocksize");
+
+static_assert(1 << kFftLengthBy2Log2 == kFftLengthBy2,
+ "Proper number of shifts for the fft length");
+
+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(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 // MODULES_AUDIO_PROCESSING_AEC3_AEC3_COMMON_H_
diff --git a/audio_processing/aec3/aec3_fft.cc b/audio_processing/aec3/aec3_fft.cc
new file mode 100644
index 0000000..ba1477e
--- /dev/null
+++ b/audio_processing/aec3/aec3_fft.cc
@@ -0,0 +1,133 @@
+/*
+ * 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 "audio_processing/aec3/aec3_fft.h"
+
+#include
+#include
+#include
+
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+namespace {
+
+const float kHanning64[kFftLengthBy2] = {
+ 0.f, 0.00248461f, 0.00991376f, 0.0222136f, 0.03926189f,
+ 0.06088921f, 0.08688061f, 0.11697778f, 0.15088159f, 0.1882551f,
+ 0.22872687f, 0.27189467f, 0.31732949f, 0.36457977f, 0.41317591f,
+ 0.46263495f, 0.51246535f, 0.56217185f, 0.61126047f, 0.65924333f,
+ 0.70564355f, 0.75f, 0.79187184f, 0.83084292f, 0.86652594f,
+ 0.89856625f, 0.92664544f, 0.95048443f, 0.96984631f, 0.98453864f,
+ 0.99441541f, 0.99937846f, 0.99937846f, 0.99441541f, 0.98453864f,
+ 0.96984631f, 0.95048443f, 0.92664544f, 0.89856625f, 0.86652594f,
+ 0.83084292f, 0.79187184f, 0.75f, 0.70564355f, 0.65924333f,
+ 0.61126047f, 0.56217185f, 0.51246535f, 0.46263495f, 0.41317591f,
+ 0.36457977f, 0.31732949f, 0.27189467f, 0.22872687f, 0.1882551f,
+ 0.15088159f, 0.11697778f, 0.08688061f, 0.06088921f, 0.03926189f,
+ 0.0222136f, 0.00991376f, 0.00248461f, 0.f};
+
+// Hanning window from Matlab command win = sqrt(hanning(128)).
+const float kSqrtHanning128[kFftLength] = {
+ 0.00000000000000f, 0.02454122852291f, 0.04906767432742f, 0.07356456359967f,
+ 0.09801714032956f, 0.12241067519922f, 0.14673047445536f, 0.17096188876030f,
+ 0.19509032201613f, 0.21910124015687f, 0.24298017990326f, 0.26671275747490f,
+ 0.29028467725446f, 0.31368174039889f, 0.33688985339222f, 0.35989503653499f,
+ 0.38268343236509f, 0.40524131400499f, 0.42755509343028f, 0.44961132965461f,
+ 0.47139673682600f, 0.49289819222978f, 0.51410274419322f, 0.53499761988710f,
+ 0.55557023301960f, 0.57580819141785f, 0.59569930449243f, 0.61523159058063f,
+ 0.63439328416365f, 0.65317284295378f, 0.67155895484702f, 0.68954054473707f,
+ 0.70710678118655f, 0.72424708295147f, 0.74095112535496f, 0.75720884650648f,
+ 0.77301045336274f, 0.78834642762661f, 0.80320753148064f, 0.81758481315158f,
+ 0.83146961230255f, 0.84485356524971f, 0.85772861000027f, 0.87008699110871f,
+ 0.88192126434835f, 0.89322430119552f, 0.90398929312344f, 0.91420975570353f,
+ 0.92387953251129f, 0.93299279883474f, 0.94154406518302f, 0.94952818059304f,
+ 0.95694033573221f, 0.96377606579544f, 0.97003125319454f, 0.97570213003853f,
+ 0.98078528040323f, 0.98527764238894f, 0.98917650996478f, 0.99247953459871f,
+ 0.99518472667220f, 0.99729045667869f, 0.99879545620517f, 0.99969881869620f,
+ 1.00000000000000f, 0.99969881869620f, 0.99879545620517f, 0.99729045667869f,
+ 0.99518472667220f, 0.99247953459871f, 0.98917650996478f, 0.98527764238894f,
+ 0.98078528040323f, 0.97570213003853f, 0.97003125319454f, 0.96377606579544f,
+ 0.95694033573221f, 0.94952818059304f, 0.94154406518302f, 0.93299279883474f,
+ 0.92387953251129f, 0.91420975570353f, 0.90398929312344f, 0.89322430119552f,
+ 0.88192126434835f, 0.87008699110871f, 0.85772861000027f, 0.84485356524971f,
+ 0.83146961230255f, 0.81758481315158f, 0.80320753148064f, 0.78834642762661f,
+ 0.77301045336274f, 0.75720884650648f, 0.74095112535496f, 0.72424708295147f,
+ 0.70710678118655f, 0.68954054473707f, 0.67155895484702f, 0.65317284295378f,
+ 0.63439328416365f, 0.61523159058063f, 0.59569930449243f, 0.57580819141785f,
+ 0.55557023301960f, 0.53499761988710f, 0.51410274419322f, 0.49289819222978f,
+ 0.47139673682600f, 0.44961132965461f, 0.42755509343028f, 0.40524131400499f,
+ 0.38268343236509f, 0.35989503653499f, 0.33688985339222f, 0.31368174039889f,
+ 0.29028467725446f, 0.26671275747490f, 0.24298017990326f, 0.21910124015687f,
+ 0.19509032201613f, 0.17096188876030f, 0.14673047445536f, 0.12241067519922f,
+ 0.09801714032956f, 0.07356456359967f, 0.04906767432742f, 0.02454122852291f};
+
+} // namespace
+
+// TODO(peah): Change x to be std::array once the rest of the code allows this.
+void Aec3Fft::ZeroPaddedFft(rtc::ArrayView x,
+ Window window,
+ FftData* X) const {
+ RTC_DCHECK(X);
+ RTC_DCHECK_EQ(kFftLengthBy2, x.size());
+ std::array fft;
+ std::fill(fft.begin(), fft.begin() + kFftLengthBy2, 0.f);
+ switch (window) {
+ case Window::kRectangular:
+ std::copy(x.begin(), x.end(), fft.begin() + kFftLengthBy2);
+ break;
+ case Window::kHanning:
+ std::transform(x.begin(), x.end(), std::begin(kHanning64),
+ fft.begin() + kFftLengthBy2,
+ [](float a, float b) { return a * b; });
+ break;
+ case Window::kSqrtHanning:
+ RTC_NOTREACHED();
+ break;
+ default:
+ RTC_NOTREACHED();
+ }
+
+ Fft(&fft, X);
+}
+
+void Aec3Fft::PaddedFft(rtc::ArrayView x,
+ rtc::ArrayView x_old,
+ Window window,
+ FftData* X) const {
+ RTC_DCHECK(X);
+ RTC_DCHECK_EQ(kFftLengthBy2, x.size());
+ RTC_DCHECK_EQ(kFftLengthBy2, x_old.size());
+ std::array fft;
+
+ switch (window) {
+ case Window::kRectangular:
+ std::copy(x_old.begin(), x_old.end(), fft.begin());
+ std::copy(x.begin(), x.end(), fft.begin() + x_old.size());
+ break;
+ case Window::kHanning:
+ RTC_NOTREACHED();
+ break;
+ case Window::kSqrtHanning:
+ std::transform(x_old.begin(), x_old.end(), std::begin(kSqrtHanning128),
+ fft.begin(), std::multiplies());
+ std::transform(x.begin(), x.end(),
+ std::begin(kSqrtHanning128) + x_old.size(),
+ fft.begin() + x_old.size(), std::multiplies());
+ break;
+ default:
+ RTC_NOTREACHED();
+ }
+
+ Fft(&fft, X);
+}
+
+} // namespace webrtc
diff --git a/audio_processing/aec3/aec3_fft.h b/audio_processing/aec3/aec3_fft.h
new file mode 100644
index 0000000..5c14c1a
--- /dev/null
+++ b/audio_processing/aec3/aec3_fft.h
@@ -0,0 +1,74 @@
+/*
+ * 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 MODULES_AUDIO_PROCESSING_AEC3_AEC3_FFT_H_
+#define MODULES_AUDIO_PROCESSING_AEC3_AEC3_FFT_H_
+
+#include
+
+#include "rtc_base/array_view.h"
+#include "audio_processing/aec3/aec3_common.h"
+#include "audio_processing/aec3/fft_data.h"
+#include "audio_processing/utility/ooura_fft.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/constructor_magic.h"
+
+namespace webrtc {
+
+// Wrapper class that provides 128 point real valued FFT functionality with the
+// FftData type.
+class Aec3Fft {
+ public:
+ enum class Window { kRectangular, kHanning, kSqrtHanning };
+
+ Aec3Fft() = default;
+ // Computes the FFT. Note that both the input and output are modified.
+ void Fft(std::array* 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* x) const {
+ RTC_DCHECK(x);
+ X.CopyToPackedArray(x);
+ ooura_fft_.InverseFft(x->data());
+ }
+
+ // Windows the input using a Hanning window, and then adds padding of
+ // kFftLengthBy2 initial zeros before computing the Fft.
+ void ZeroPaddedFft(rtc::ArrayView x,
+ Window window,
+ 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 x,
+ rtc::ArrayView x_old,
+ FftData* X) const {
+ PaddedFft(x, x_old, Window::kRectangular, X);
+ }
+
+ // Padded Fft using a time-domain window.
+ void PaddedFft(rtc::ArrayView x,
+ rtc::ArrayView x_old,
+ Window window,
+ FftData* X) const;
+
+ private:
+ const OouraFft ooura_fft_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(Aec3Fft);
+};
+
+} // namespace webrtc
+
+#endif // MODULES_AUDIO_PROCESSING_AEC3_AEC3_FFT_H_
diff --git a/audio_processing/aec3/aec3_fft_unittest.cc b/audio_processing/aec3/aec3_fft_unittest.cc
new file mode 100644
index 0000000..82d6e76
--- /dev/null
+++ b/audio_processing/aec3/aec3_fft_unittest.cc
@@ -0,0 +1,213 @@
+/*
+ * 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 "modules/audio_processing/aec3/aec3_fft.h"
+
+#include
+
+#include "test/gmock.h"
+#include "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 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 x;
+ EXPECT_DEATH(fft.ZeroPaddedFft(x, Aec3Fft::Window::kRectangular, nullptr),
+ "");
+}
+
+// Verifies that the check for input length in ZeroPaddedFft works.
+TEST(Aec3Fft, ZeroPaddedFftWrongInputLength) {
+ Aec3Fft fft;
+ FftData X;
+ std::array x;
+ EXPECT_DEATH(fft.ZeroPaddedFft(x, Aec3Fft::Window::kRectangular, &X), "");
+}
+
+// Verifies that the check for non-null output in PaddedFft works.
+TEST(Aec3Fft, NullPaddedFftOutput) {
+ Aec3Fft fft;
+ std::array x;
+ std::array 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 x;
+ std::array 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 x;
+ std::array 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 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 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 x;
+ std::array 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 x_in;
+ std::array x_ref;
+ std::array 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, Aec3Fft::Window::kRectangular, &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 x_in;
+ std::array x_out;
+ std::array x_old;
+ std::array x_old_ref;
+ std::array 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);
+ std::copy(x_in.begin(), x_in.end(), x_old.begin());
+ 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
diff --git a/audio_processing/aec3/aec_state.cc b/audio_processing/aec3/aec_state.cc
new file mode 100644
index 0000000..d9338d0
--- /dev/null
+++ b/audio_processing/aec3/aec_state.cc
@@ -0,0 +1,522 @@
+/*
+ * 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 "audio_processing/aec3/aec_state.h"
+
+#include
+
+#include
+#include
+#include
+
+#include "absl/types/optional.h"
+#include "rtc_base/array_view.h"
+#include "audio_processing/aec3/aec3_common.h"
+#include "audio_processing/logging/apm_data_dumper.h"
+#include "rtc_base/atomic_ops.h"
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+namespace {
+
+constexpr size_t kBlocksSinceConvergencedFilterInit = 10000;
+constexpr size_t kBlocksSinceConsistentEstimateInit = 10000;
+
+void ComputeAvgRenderReverb(
+ const SpectrumBuffer& spectrum_buffer,
+ int delay_blocks,
+ float reverb_decay,
+ ReverbModel* reverb_model,
+ rtc::ArrayView reverb_power_spectrum) {
+ RTC_DCHECK(reverb_model);
+ const size_t num_render_channels = spectrum_buffer.buffer[0].size();
+ int idx_at_delay =
+ spectrum_buffer.OffsetIndex(spectrum_buffer.read, delay_blocks);
+ int idx_past = spectrum_buffer.IncIndex(idx_at_delay);
+
+ std::array X2_data;
+ rtc::ArrayView X2;
+ if (num_render_channels > 1) {
+ auto average_channels =
+ [](size_t num_render_channels,
+ rtc::ArrayView>
+ spectrum_band_0,
+ rtc::ArrayView render_power) {
+ std::fill(render_power.begin(), render_power.end(), 0.f);
+ for (size_t ch = 0; ch < num_render_channels; ++ch) {
+ for (size_t k = 0; k < kFftLengthBy2Plus1; ++k) {
+ render_power[k] += spectrum_band_0[ch][k];
+ }
+ }
+ const float normalizer = 1.f / num_render_channels;
+ for (size_t k = 0; k < kFftLengthBy2Plus1; ++k) {
+ render_power[k] *= normalizer;
+ }
+ };
+ average_channels(num_render_channels, spectrum_buffer.buffer[idx_past],
+ X2_data);
+ reverb_model->UpdateReverbNoFreqShaping(
+ X2_data, /*power_spectrum_scaling=*/1.0f, reverb_decay);
+
+ average_channels(num_render_channels, spectrum_buffer.buffer[idx_at_delay],
+ X2_data);
+ X2 = X2_data;
+ } else {
+ reverb_model->UpdateReverbNoFreqShaping(
+ spectrum_buffer.buffer[idx_past][/*channel=*/0],
+ /*power_spectrum_scaling=*/1.0f, reverb_decay);
+
+ X2 = spectrum_buffer.buffer[idx_at_delay][/*channel=*/0];
+ }
+
+ rtc::ArrayView reverb_power =
+ reverb_model->reverb();
+ for (size_t k = 0; k < X2.size(); ++k) {
+ reverb_power_spectrum[k] = X2[k] + reverb_power[k];
+ }
+}
+
+} // namespace
+
+int AecState::instance_count_ = 0;
+
+void AecState::GetResidualEchoScaling(
+ rtc::ArrayView residual_scaling) const {
+ bool filter_has_had_time_to_converge;
+ if (config_.filter.conservative_initial_phase) {
+ filter_has_had_time_to_converge =
+ strong_not_saturated_render_blocks_ >= 1.5f * kNumBlocksPerSecond;
+ } else {
+ filter_has_had_time_to_converge =
+ strong_not_saturated_render_blocks_ >= 0.8f * kNumBlocksPerSecond;
+ }
+ echo_audibility_.GetResidualEchoScaling(filter_has_had_time_to_converge,
+ residual_scaling);
+}
+
+absl::optional AecState::ErleUncertainty() const {
+ if (SaturatedEcho()) {
+ return 1.f;
+ }
+
+ return absl::nullopt;
+}
+
+AecState::AecState(const EchoCanceller3Config& config,
+ size_t num_capture_channels)
+ : data_dumper_(
+ new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
+ config_(config),
+ num_capture_channels_(num_capture_channels),
+ initial_state_(config_),
+ delay_state_(config_, num_capture_channels_),
+ transparent_state_(config_),
+ filter_quality_state_(config_, num_capture_channels_),
+ erl_estimator_(2 * kNumBlocksPerSecond),
+ erle_estimator_(2 * kNumBlocksPerSecond, config_, num_capture_channels_),
+ filter_analyzer_(config_, num_capture_channels_),
+ echo_audibility_(
+ config_.echo_audibility.use_stationarity_properties_at_init),
+ reverb_model_estimator_(config_, num_capture_channels_),
+ subtractor_output_analyzer_(num_capture_channels_) {}
+
+AecState::~AecState() = default;
+
+void AecState::HandleEchoPathChange(
+ const EchoPathVariability& echo_path_variability) {
+ const auto full_reset = [&]() {
+ filter_analyzer_.Reset();
+ capture_signal_saturation_ = false;
+ strong_not_saturated_render_blocks_ = 0;
+ blocks_with_active_render_ = 0;
+ initial_state_.Reset();
+ transparent_state_.Reset();
+ erle_estimator_.Reset(true);
+ erl_estimator_.Reset();
+ filter_quality_state_.Reset();
+ };
+
+ // TODO(peah): Refine the reset scheme according to the type of gain and
+ // delay adjustment.
+
+ if (echo_path_variability.delay_change !=
+ EchoPathVariability::DelayAdjustment::kNone) {
+ full_reset();
+ } else if (echo_path_variability.gain_change) {
+ erle_estimator_.Reset(false);
+ }
+ subtractor_output_analyzer_.HandleEchoPathChange();
+}
+
+void AecState::Update(
+ const absl::optional& external_delay,
+ rtc::ArrayView>>
+ adaptive_filter_frequency_responses,
+ rtc::ArrayView> adaptive_filter_impulse_responses,
+ const RenderBuffer& render_buffer,
+ rtc::ArrayView> E2_main,
+ rtc::ArrayView> Y2,
+ rtc::ArrayView subtractor_output) {
+ RTC_DCHECK_EQ(num_capture_channels_, Y2.size());
+ RTC_DCHECK_EQ(num_capture_channels_, subtractor_output.size());
+ RTC_DCHECK_EQ(num_capture_channels_,
+ adaptive_filter_frequency_responses.size());
+ RTC_DCHECK_EQ(num_capture_channels_,
+ adaptive_filter_impulse_responses.size());
+
+ bool any_filter_converged;
+ bool all_filters_diverged;
+ subtractor_output_analyzer_.Update(subtractor_output, &any_filter_converged,
+ &all_filters_diverged);
+
+ bool any_filter_consistent;
+ float max_echo_path_gain;
+ filter_analyzer_.Update(adaptive_filter_impulse_responses, render_buffer,
+ &any_filter_consistent, &max_echo_path_gain);
+
+ if (config_.filter.use_linear_filter) {
+ delay_state_.Update(filter_analyzer_.FilterDelaysBlocks(), external_delay,
+ strong_not_saturated_render_blocks_);
+ }
+
+ const std::vector>& aligned_render_block =
+ render_buffer.Block(-delay_state_.MinDirectPathFilterDelay())[0];
+
+ bool active_render = false;
+ for (size_t ch = 0; ch < aligned_render_block.size(); ++ch) {
+ const float render_energy = std::inner_product(
+ aligned_render_block[ch].begin(), aligned_render_block[ch].end(),
+ aligned_render_block[ch].begin(), 0.f);
+ if (render_energy > (config_.render_levels.active_render_limit *
+ config_.render_levels.active_render_limit) *
+ kFftLengthBy2) {
+ active_render = true;
+ break;
+ }
+ }
+ blocks_with_active_render_ += active_render ? 1 : 0;
+ strong_not_saturated_render_blocks_ +=
+ active_render && !SaturatedCapture() ? 1 : 0;
+
+ std::array avg_render_spectrum_with_reverb;
+
+ ComputeAvgRenderReverb(render_buffer.GetSpectrumBuffer(),
+ delay_state_.MinDirectPathFilterDelay(), ReverbDecay(),
+ &avg_render_reverb_, avg_render_spectrum_with_reverb);
+
+ if (config_.echo_audibility.use_stationarity_properties) {
+ // Update the echo audibility evaluator.
+ echo_audibility_.Update(render_buffer, avg_render_reverb_.reverb(),
+ delay_state_.MinDirectPathFilterDelay(),
+ delay_state_.ExternalDelayReported());
+ }
+
+ if (initial_state_.TransitionTriggered()) {
+ erle_estimator_.Reset(false);
+ }
+
+ erle_estimator_.Update(render_buffer, adaptive_filter_frequency_responses,
+ avg_render_spectrum_with_reverb, Y2, E2_main,
+ subtractor_output_analyzer_.ConvergedFilters());
+
+ erl_estimator_.Update(
+ subtractor_output_analyzer_.ConvergedFilters(),
+ render_buffer.Spectrum(delay_state_.MinDirectPathFilterDelay()), Y2);
+
+ saturation_detector_.Update(aligned_render_block, SaturatedCapture(),
+ UsableLinearEstimate(), subtractor_output,
+ max_echo_path_gain);
+
+ initial_state_.Update(active_render, SaturatedCapture());
+
+ transparent_state_.Update(delay_state_.MinDirectPathFilterDelay(),
+ any_filter_consistent, any_filter_converged,
+ all_filters_diverged, active_render,
+ SaturatedCapture());
+
+ filter_quality_state_.Update(active_render, TransparentMode(),
+ SaturatedCapture(), external_delay,
+ any_filter_converged);
+
+ const bool stationary_block =
+ config_.echo_audibility.use_stationarity_properties &&
+ echo_audibility_.IsBlockStationary();
+
+ reverb_model_estimator_.Update(
+ filter_analyzer_.GetAdjustedFilters(),
+ adaptive_filter_frequency_responses,
+ erle_estimator_.GetInstLinearQualityEstimates(),
+ delay_state_.DirectPathFilterDelays(),
+ filter_quality_state_.UsableLinearFilterOutputs(), stationary_block);
+
+ erle_estimator_.Dump(data_dumper_);
+ reverb_model_estimator_.Dump(data_dumper_.get());
+ data_dumper_->DumpRaw("aec3_erl", Erl());
+ data_dumper_->DumpRaw("aec3_erl_time_domain", ErlTimeDomain());
+ data_dumper_->DumpRaw("aec3_erle", Erle()[0]);
+ data_dumper_->DumpRaw("aec3_usable_linear_estimate", UsableLinearEstimate());
+ data_dumper_->DumpRaw("aec3_transparent_mode", TransparentMode());
+ data_dumper_->DumpRaw("aec3_filter_delay",
+ filter_analyzer_.MinFilterDelayBlocks());
+
+ data_dumper_->DumpRaw("aec3_any_filter_consistent", any_filter_consistent);
+ data_dumper_->DumpRaw("aec3_initial_state",
+ initial_state_.InitialStateActive());
+ data_dumper_->DumpRaw("aec3_capture_saturation", SaturatedCapture());
+ data_dumper_->DumpRaw("aec3_echo_saturation", SaturatedEcho());
+ data_dumper_->DumpRaw("aec3_any_filter_converged", any_filter_converged);
+ data_dumper_->DumpRaw("aec3_all_filters_diverged", all_filters_diverged);
+
+ data_dumper_->DumpRaw("aec3_external_delay_avaliable",
+ external_delay ? 1 : 0);
+ data_dumper_->DumpRaw("aec3_filter_tail_freq_resp_est",
+ GetReverbFrequencyResponse());
+}
+
+AecState::InitialState::InitialState(const EchoCanceller3Config& config)
+ : conservative_initial_phase_(config.filter.conservative_initial_phase),
+ initial_state_seconds_(config.filter.initial_state_seconds) {
+ Reset();
+}
+void AecState::InitialState::InitialState::Reset() {
+ initial_state_ = true;
+ strong_not_saturated_render_blocks_ = 0;
+}
+void AecState::InitialState::InitialState::Update(bool active_render,
+ bool saturated_capture) {
+ strong_not_saturated_render_blocks_ +=
+ active_render && !saturated_capture ? 1 : 0;
+
+ // Flag whether the initial state is still active.
+ bool prev_initial_state = initial_state_;
+ if (conservative_initial_phase_) {
+ initial_state_ =
+ strong_not_saturated_render_blocks_ < 5 * kNumBlocksPerSecond;
+ } else {
+ initial_state_ = strong_not_saturated_render_blocks_ <
+ initial_state_seconds_ * kNumBlocksPerSecond;
+ }
+
+ // Flag whether the transition from the initial state has started.
+ transition_triggered_ = !initial_state_ && prev_initial_state;
+}
+
+AecState::FilterDelay::FilterDelay(const EchoCanceller3Config& config,
+ size_t num_capture_channels)
+ : delay_headroom_samples_(config.delay.delay_headroom_samples),
+ filter_delays_blocks_(num_capture_channels, 0) {}
+
+void AecState::FilterDelay::Update(
+ rtc::ArrayView analyzer_filter_delay_estimates_blocks,
+ const absl::optional& external_delay,
+ size_t blocks_with_proper_filter_adaptation) {
+ // Update the delay based on the external delay.
+ if (external_delay &&
+ (!external_delay_ || external_delay_->delay != external_delay->delay)) {
+ external_delay_ = external_delay;
+ external_delay_reported_ = true;
+ }
+
+ // Override the estimated delay if it is not certain that the filter has had
+ // time to converge.
+ const bool delay_estimator_may_not_have_converged =
+ blocks_with_proper_filter_adaptation < 2 * kNumBlocksPerSecond;
+ if (delay_estimator_may_not_have_converged && external_delay_) {
+ int delay_guess = delay_headroom_samples_ / kBlockSize;
+ std::fill(filter_delays_blocks_.begin(), filter_delays_blocks_.end(),
+ delay_guess);
+ } else {
+ RTC_DCHECK_EQ(filter_delays_blocks_.size(),
+ analyzer_filter_delay_estimates_blocks.size());
+ std::copy(analyzer_filter_delay_estimates_blocks.begin(),
+ analyzer_filter_delay_estimates_blocks.end(),
+ filter_delays_blocks_.begin());
+ }
+
+ min_filter_delay_ = *std::min_element(filter_delays_blocks_.begin(),
+ filter_delays_blocks_.end());
+}
+
+AecState::TransparentMode::TransparentMode(const EchoCanceller3Config& config)
+ : bounded_erl_(config.ep_strength.bounded_erl),
+ linear_and_stable_echo_path_(
+ config.echo_removal_control.linear_and_stable_echo_path),
+ active_blocks_since_sane_filter_(kBlocksSinceConsistentEstimateInit),
+ non_converged_sequence_size_(kBlocksSinceConvergencedFilterInit) {}
+
+void AecState::TransparentMode::Reset() {
+ non_converged_sequence_size_ = kBlocksSinceConvergencedFilterInit;
+ diverged_sequence_size_ = 0;
+ strong_not_saturated_render_blocks_ = 0;
+ if (linear_and_stable_echo_path_) {
+ recent_convergence_during_activity_ = false;
+ }
+}
+
+void AecState::TransparentMode::Update(int filter_delay_blocks,
+ bool any_filter_consistent,
+ bool any_filter_converged,
+ bool all_filters_diverged,
+ bool active_render,
+ bool saturated_capture) {
+ ++capture_block_counter_;
+ strong_not_saturated_render_blocks_ +=
+ active_render && !saturated_capture ? 1 : 0;
+
+ if (any_filter_consistent && filter_delay_blocks < 5) {
+ sane_filter_observed_ = true;
+ active_blocks_since_sane_filter_ = 0;
+ } else if (active_render) {
+ ++active_blocks_since_sane_filter_;
+ }
+
+ bool sane_filter_recently_seen;
+ if (!sane_filter_observed_) {
+ sane_filter_recently_seen =
+ capture_block_counter_ <= 5 * kNumBlocksPerSecond;
+ } else {
+ sane_filter_recently_seen =
+ active_blocks_since_sane_filter_ <= 30 * kNumBlocksPerSecond;
+ }
+
+ if (any_filter_converged) {
+ recent_convergence_during_activity_ = true;
+ active_non_converged_sequence_size_ = 0;
+ non_converged_sequence_size_ = 0;
+ ++num_converged_blocks_;
+ } else {
+ if (++non_converged_sequence_size_ > 20 * kNumBlocksPerSecond) {
+ num_converged_blocks_ = 0;
+ }
+ if (active_render &&
+ ++active_non_converged_sequence_size_ > 60 * kNumBlocksPerSecond) {
+ recent_convergence_during_activity_ = false;
+ }
+ }
+
+ if (!all_filters_diverged) {
+ diverged_sequence_size_ = 0;
+ } else if (++diverged_sequence_size_ >= 60) {
+ // TODO(peah): Change these lines to ensure proper triggering of usable
+ // filter.
+ non_converged_sequence_size_ = kBlocksSinceConvergencedFilterInit;
+ }
+
+ if (active_non_converged_sequence_size_ > 60 * kNumBlocksPerSecond) {
+ finite_erl_recently_detected_ = false;
+ }
+
+ if (num_converged_blocks_ > 50) {
+ finite_erl_recently_detected_ = true;
+ }
+
+ if (bounded_erl_) {
+ transparency_activated_ = false;
+ } else if (finite_erl_recently_detected_) {
+ transparency_activated_ = false;
+ } else if (sane_filter_recently_seen && recent_convergence_during_activity_) {
+ transparency_activated_ = false;
+ } else {
+ const bool filter_should_have_converged =
+ strong_not_saturated_render_blocks_ > 6 * kNumBlocksPerSecond;
+ transparency_activated_ = filter_should_have_converged;
+ }
+}
+
+AecState::FilteringQualityAnalyzer::FilteringQualityAnalyzer(
+ const EchoCanceller3Config& config,
+ size_t num_capture_channels)
+ : use_linear_filter_(config.filter.use_linear_filter),
+ usable_linear_filter_estimates_(num_capture_channels, false) {}
+
+void AecState::FilteringQualityAnalyzer::Reset() {
+ std::fill(usable_linear_filter_estimates_.begin(),
+ usable_linear_filter_estimates_.end(), false);
+ overall_usable_linear_estimates_ = false;
+ filter_update_blocks_since_reset_ = 0;
+}
+
+void AecState::FilteringQualityAnalyzer::Update(
+ bool active_render,
+ bool transparent_mode,
+ bool saturated_capture,
+ const absl::optional& external_delay,
+ bool any_filter_converged) {
+ // Update blocks counter.
+ const bool filter_update = active_render && !saturated_capture;
+ filter_update_blocks_since_reset_ += filter_update ? 1 : 0;
+ filter_update_blocks_since_start_ += filter_update ? 1 : 0;
+
+ // Store convergence flag when observed.
+ convergence_seen_ = convergence_seen_ || any_filter_converged;
+
+ // Verify requirements for achieving a decent filter. The requirements for
+ // filter adaptation at call startup are more restrictive than after an
+ // in-call reset.
+ const bool sufficient_data_to_converge_at_startup =
+ filter_update_blocks_since_start_ > kNumBlocksPerSecond * 0.4f;
+ const bool sufficient_data_to_converge_at_reset =
+ sufficient_data_to_converge_at_startup &&
+ filter_update_blocks_since_reset_ > kNumBlocksPerSecond * 0.2f;
+
+ // The linear filter can only be used if it has had time to converge.
+ overall_usable_linear_estimates_ = sufficient_data_to_converge_at_startup &&
+ sufficient_data_to_converge_at_reset;
+
+ // The linear filter can only be used if an external delay or convergence have
+ // been identified
+ overall_usable_linear_estimates_ =
+ overall_usable_linear_estimates_ && (external_delay || convergence_seen_);
+
+ // If transparent mode is on, deactivate usign the linear filter.
+ overall_usable_linear_estimates_ =
+ overall_usable_linear_estimates_ && !transparent_mode;
+
+ if (use_linear_filter_) {
+ std::fill(usable_linear_filter_estimates_.begin(),
+ usable_linear_filter_estimates_.end(),
+ overall_usable_linear_estimates_);
+ }
+}
+
+void AecState::SaturationDetector::Update(
+ rtc::ArrayView> x,
+ bool saturated_capture,
+ bool usable_linear_estimate,
+ rtc::ArrayView subtractor_output,
+ float echo_path_gain) {
+ saturated_echo_ = false;
+ if (!saturated_capture) {
+ return;
+ }
+
+ if (usable_linear_estimate) {
+ constexpr float kSaturationThreshold = 20000.f;
+ for (size_t ch = 0; ch < subtractor_output.size(); ++ch) {
+ saturated_echo_ =
+ saturated_echo_ ||
+ (subtractor_output[ch].s_main_max_abs > kSaturationThreshold ||
+ subtractor_output[ch].s_shadow_max_abs > kSaturationThreshold);
+ }
+ } else {
+ float max_sample = 0.f;
+ for (auto& channel : x) {
+ for (float sample : channel) {
+ max_sample = std::max(max_sample, fabsf(sample));
+ }
+ }
+
+ const float kMargin = 10.f;
+ float peak_echo_amplitude = max_sample * echo_path_gain * kMargin;
+ saturated_echo_ = saturated_echo_ || peak_echo_amplitude > 32000;
+ }
+}
+
+} // namespace webrtc
diff --git a/audio_processing/aec3/aec_state.h b/audio_processing/aec3/aec_state.h
new file mode 100644
index 0000000..1a5e5fc
--- /dev/null
+++ b/audio_processing/aec3/aec_state.h
@@ -0,0 +1,322 @@
+/*
+ * 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 MODULES_AUDIO_PROCESSING_AEC3_AEC_STATE_H_
+#define MODULES_AUDIO_PROCESSING_AEC3_AEC_STATE_H_
+
+#include
+
+#include
+#include
+#include
+
+#include "absl/types/optional.h"
+#include "rtc_base/array_view.h"
+#include "api/echo_canceller3_config.h"
+#include "audio_processing/aec3/aec3_common.h"
+#include "audio_processing/aec3/delay_estimate.h"
+#include "audio_processing/aec3/echo_audibility.h"
+#include "audio_processing/aec3/echo_path_variability.h"
+#include "audio_processing/aec3/erl_estimator.h"
+#include "audio_processing/aec3/erle_estimator.h"
+#include "audio_processing/aec3/filter_analyzer.h"
+#include "audio_processing/aec3/render_buffer.h"
+#include "audio_processing/aec3/reverb_model_estimator.h"
+#include "audio_processing/aec3/subtractor_output.h"
+#include "audio_processing/aec3/subtractor_output_analyzer.h"
+
+namespace webrtc {
+
+class ApmDataDumper;
+
+// Handles the state and the conditions for the echo removal functionality.
+class AecState {
+ public:
+ AecState(const EchoCanceller3Config& config, size_t num_capture_channels);
+ ~AecState();
+
+ // Returns whether the echo subtractor can be used to determine the residual
+ // echo.
+ bool UsableLinearEstimate() const {
+ return filter_quality_state_.LinearFilterUsable() &&
+ config_.filter.use_linear_filter;
+ }
+
+ // Returns whether the echo subtractor output should be used as output.
+ bool UseLinearFilterOutput() const {
+ return filter_quality_state_.LinearFilterUsable() &&
+ config_.filter.use_linear_filter;
+ }
+
+ // Returns whether the render signal is currently active.
+ bool ActiveRender() const { return blocks_with_active_render_ > 200; }
+
+ // Returns the appropriate scaling of the residual echo to match the
+ // audibility.
+ void GetResidualEchoScaling(rtc::ArrayView residual_scaling) const;
+
+ // Returns whether the stationary properties of the signals are used in the
+ // aec.
+ bool UseStationarityProperties() const {
+ return config_.echo_audibility.use_stationarity_properties;
+ }
+
+ // Returns the ERLE.
+ rtc::ArrayView> Erle() const {
+ return erle_estimator_.Erle();
+ }
+
+ // Returns an offset to apply to the estimation of the residual echo
+ // computation. Returning nullopt means that no offset should be used, while
+ // any other value will be applied as a multiplier to the estimated residual
+ // echo.
+ absl::optional