1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
|
https://bugs.gentoo.org/913693
https://gitlab.freedesktop.org/pipewire/pipewire/-/commit/1f1c308c9766312e684f0b53fc2d1422c7414d31
From 1f1c308c9766312e684f0b53fc2d1422c7414d31 Mon Sep 17 00:00:00 2001
From: Wim Taymans <wtaymans@redhat.com>
Date: Thu, 14 Sep 2023 15:35:40 +0200
Subject: [PATCH] aec: support both webrtc versions
Version 1 does not seem to be packaged in many distros and so they would
need to revert the patch or disable AEC. Enabling both allows for things
to move forwards gracefully.
--- a/meson.build
+++ b/meson.build
@@ -377,9 +377,17 @@ cdata.set('HAVE_GSTREAMER_DEVICE_PROVIDER', get_option('gstreamer-device-provide
webrtc_dep = dependency('webrtc-audio-processing-1',
version : ['>= 1.2' ],
- required : get_option('echo-cancel-webrtc'))
-summary({'WebRTC Echo Canceling': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies')
-cdata.set('HAVE_WEBRTC', webrtc_dep.found())
+ required : false)
+cdata.set('HAVE_WEBRTC1', webrtc_dep.found())
+if webrtc_dep.found()
+ summary({'WebRTC Echo Canceling >= 1.2': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies')
+else
+ webrtc_dep = dependency('webrtc-audio-processing',
+ version : ['>= 0.2', '< 1.0'],
+ required : get_option('echo-cancel-webrtc'))
+ cdata.set('HAVE_WEBRTC', webrtc_dep.found())
+ summary({'WebRTC Echo Canceling < 1.0': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies')
+endif
# On FreeBSD and MidnightBSD, epoll-shim library is required for eventfd() and timerfd()
epoll_shim_dep = (host_machine.system() == 'freebsd' or host_machine.system() == 'midnightbsd'
--- a/spa/plugins/aec/aec-webrtc.cpp
+++ b/spa/plugins/aec/aec-webrtc.cpp
@@ -3,6 +3,8 @@
/* SPDX-FileCopyrightText: Copyright © 2021 Arun Raghavan <arun@asymptotic.io> */
/* SPDX-License-Identifier: MIT */
+#include "config.h"
+
#include <memory>
#include <utility>
@@ -13,7 +15,13 @@
#include <spa/utils/json.h>
#include <spa/support/plugin.h>
+#ifdef HAVE_WEBRTC
+#include <webrtc/modules/audio_processing/include/audio_processing.h>
+#include <webrtc/modules/interface/module_common_types.h>
+#include <webrtc/system_wrappers/include/trace.h>
+#else
#include <modules/audio_processing/include/audio_processing.h>
+#endif
struct impl_data {
struct spa_handle handle;
@@ -39,6 +47,54 @@ static bool webrtc_get_spa_bool(const struct spa_dict *args, const char *key, bo
return default_value;
}
+#ifdef HAVE_WEBRTC
+/* [ f0 f1 f2 ] */
+static int parse_point(struct spa_json *it, float (&f)[3])
+{
+ struct spa_json arr;
+ int i, res;
+
+ if (spa_json_enter_array(it, &arr) <= 0)
+ return -EINVAL;
+
+ for (i = 0; i < 3; i++) {
+ if ((res = spa_json_get_float(&arr, &f[i])) <= 0)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* [ point1 point2 ... ] */
+static int parse_mic_geometry(struct impl_data *impl, const char *mic_geometry,
+ std::vector<webrtc::Point>& geometry)
+{
+ int res;
+ size_t i;
+ struct spa_json it[2];
+
+ spa_json_init(&it[0], mic_geometry, strlen(mic_geometry));
+ if (spa_json_enter_array(&it[0], &it[1]) <= 0) {
+ spa_log_error(impl->log, "Error: webrtc.mic-geometry expects an array");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < geometry.size(); i++) {
+ float f[3];
+
+ if ((res = parse_point(&it[1], f)) < 0) {
+ spa_log_error(impl->log, "Error: can't parse webrtc.mic-geometry points: %d", res);
+ return res;
+ }
+
+ spa_log_info(impl->log, "mic %zd position: (%g %g %g)", i, f[0], f[1], f[2]);
+ geometry[i].c[0] = f[0];
+ geometry[i].c[1] = f[1];
+ geometry[i].c[2] = f[2];
+ }
+ return 0;
+}
+#endif
+
static int webrtc_init2(void *object, const struct spa_dict *args,
struct spa_audio_info_raw *rec_info, struct spa_audio_info_raw *out_info,
struct spa_audio_info_raw *play_info)
@@ -48,9 +104,18 @@ static int webrtc_init2(void *object, const struct spa_dict *args,
bool high_pass_filter = webrtc_get_spa_bool(args, "webrtc.high_pass_filter", true);
bool noise_suppression = webrtc_get_spa_bool(args, "webrtc.noise_suppression", true);
- bool transient_suppression = webrtc_get_spa_bool(args, "webrtc.transient_suppression", true);
bool voice_detection = webrtc_get_spa_bool(args, "webrtc.voice_detection", true);
-
+#ifdef HAVE_WEBRTC
+ bool extended_filter = webrtc_get_spa_bool(args, "webrtc.extended_filter", true);
+ bool delay_agnostic = webrtc_get_spa_bool(args, "webrtc.delay_agnostic", true);
+ // Disable experimental flags by default
+ bool experimental_agc = webrtc_get_spa_bool(args, "webrtc.experimental_agc", false);
+ bool experimental_ns = webrtc_get_spa_bool(args, "webrtc.experimental_ns", false);
+
+ bool beamforming = webrtc_get_spa_bool(args, "webrtc.beamforming", false);
+#else
+ bool transient_suppression = webrtc_get_spa_bool(args, "webrtc.transient_suppression", true);
+#endif
// Note: AGC seems to mess up with Agnostic Delay Detection, especially with speech,
// result in very poor performance, disable by default
bool gain_control = webrtc_get_spa_bool(args, "webrtc.gain_control", false);
@@ -59,6 +124,51 @@ static int webrtc_init2(void *object, const struct spa_dict *args,
// This filter will modify playback buffer (when calling ProcessReverseStream), but now
// playback buffer modifications are discarded.
+#ifdef HAVE_WEBRTC
+ webrtc::Config config;
+ config.Set<webrtc::ExtendedFilter>(new webrtc::ExtendedFilter(extended_filter));
+ config.Set<webrtc::DelayAgnostic>(new webrtc::DelayAgnostic(delay_agnostic));
+ config.Set<webrtc::ExperimentalAgc>(new webrtc::ExperimentalAgc(experimental_agc));
+ config.Set<webrtc::ExperimentalNs>(new webrtc::ExperimentalNs(experimental_ns));
+
+ if (beamforming) {
+ std::vector<webrtc::Point> geometry(rec_info->channels);
+ const char *mic_geometry, *target_direction;
+
+ /* The beamformer gives a single mono channel */
+ out_info->channels = 1;
+ out_info->position[0] = SPA_AUDIO_CHANNEL_MONO;
+
+ if ((mic_geometry = spa_dict_lookup(args, "webrtc.mic-geometry")) == NULL) {
+ spa_log_error(impl->log, "Error: webrtc.beamforming requires webrtc.mic-geometry");
+ return -EINVAL;
+ }
+
+ if ((res = parse_mic_geometry(impl, mic_geometry, geometry)) < 0)
+ return res;
+
+ if ((target_direction = spa_dict_lookup(args, "webrtc.target-direction")) != NULL) {
+ webrtc::SphericalPointf direction(0.0f, 0.0f, 0.0f);
+ struct spa_json it;
+ float f[3];
+
+ spa_json_init(&it, target_direction, strlen(target_direction));
+ if (parse_point(&it, f) < 0) {
+ spa_log_error(impl->log, "Error: can't parse target-direction %s",
+ target_direction);
+ return -EINVAL;
+ }
+
+ direction.s[0] = f[0];
+ direction.s[1] = f[1];
+ direction.s[2] = f[2];
+
+ config.Set<webrtc::Beamforming>(new webrtc::Beamforming(true, geometry, direction));
+ } else {
+ config.Set<webrtc::Beamforming>(new webrtc::Beamforming(true, geometry));
+ }
+ }
+#else
webrtc::AudioProcessing::Config config;
config.echo_canceller.enabled = true;
// FIXME: Example code enables both gain controllers, but that seems sus
@@ -73,6 +183,7 @@ static int webrtc_init2(void *object, const struct spa_dict *args,
// FIXME: expose pre/postamp gain
config.transient_suppression.enabled = transient_suppression;
config.voice_detection.enabled = voice_detection;
+#endif
webrtc::ProcessingConfig pconfig = {{
webrtc::StreamConfig(rec_info->rate, rec_info->channels, false), /* input stream */
@@ -81,15 +192,35 @@ static int webrtc_init2(void *object, const struct spa_dict *args,
webrtc::StreamConfig(play_info->rate, play_info->channels, false), /* reverse output stream */
}};
+#ifdef HAVE_WEBRTC
+ auto apm = std::unique_ptr<webrtc::AudioProcessing>(webrtc::AudioProcessing::Create(config));
+#else
auto apm = std::unique_ptr<webrtc::AudioProcessing>(webrtc::AudioProcessingBuilder().Create());
apm->ApplyConfig(config);
+#endif
if ((res = apm->Initialize(pconfig)) != webrtc::AudioProcessing::kNoError) {
spa_log_error(impl->log, "Error initialising webrtc audio processing module: %d", res);
return -EINVAL;
}
+#ifdef HAVE_WEBRTC
+ apm->high_pass_filter()->Enable(high_pass_filter);
+ // Always disable drift compensation since PipeWire will already do
+ // drift compensation on all sinks and sources linked to this echo-canceler
+ apm->echo_cancellation()->enable_drift_compensation(false);
+ apm->echo_cancellation()->Enable(true);
+ // TODO: wire up supression levels to args
+ apm->echo_cancellation()->set_suppression_level(webrtc::EchoCancellation::kHighSuppression);
+ apm->noise_suppression()->set_level(webrtc::NoiseSuppression::kHigh);
+ apm->noise_suppression()->Enable(noise_suppression);
+ apm->voice_detection()->Enable(voice_detection);
+ // TODO: wire up AGC parameters to args
+ apm->gain_control()->set_analog_level_limits(0, 255);
+ apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveDigital);
+ apm->gain_control()->Enable(gain_control);
+#endif
impl->apm = std::move(apm);
impl->rec_info = *rec_info;
impl->out_info = *out_info;
--
GitLab
|