echo-cancel: Add the WebRTC echo canceller

This adds the WebRTC echo canceller as another module-echo-cancel
backend. We're exposing both the full echo canceller as well as the
mobile echo control version as modargs.

Pending items:

1. The mobile canceller doesn't seem to work at the moment.

2. We still need to add bits to hook in drift compensation (to support
   sink and source from different devices).

The most controversial part of this patch would probably be the
mandatory build-time dependency on a C++ compiler. If the optional
--enable-webrtc-aec is set, then there's also a dependency on libstdc++.
This commit is contained in:
Arun Raghavan 2011-09-19 13:41:13 +05:30
parent dbe8f2e595
commit 6df6eb959e
5 changed files with 297 additions and 0 deletions

View file

@ -78,6 +78,9 @@ AC_PROG_MKDIR_P
AC_PROG_CC
AC_PROG_CC_C99
AM_PROG_CC_C_O
# Only required if you want the WebRTC canceller -- no runtime dep on
# libstdc++ otherwise
AC_PROG_CXX
AC_PROG_GCC_TRADITIONAL
AC_USE_SYSTEM_EXTENSIONS
@ -1139,6 +1142,20 @@ if test "x$os_is_darwin" = "x1" ; then
fi
fi
AC_ARG_ENABLE([webrtc-aec],
AS_HELP_STRING([--enable-webrtc-aec], [Enable the optional WebRTC-based echo canceller]))
AS_IF([test "x$enable_webrtc_aec" != "xno"],
[PKG_CHECK_MODULES(WEBRTC, [ webrtc-audio-processing ], [HAVE_WEBRTC=1], [HAVE_WEBRTC=0])],
[HAVE_WEBRTC=0])
AS_IF([test "x$enable_webrtc_aec" = "xyes" && test "x$HAVE_WEBRTC" = "x0"],
[AC_MSG_ERROR([*** webrtc-audio-processing library not found])])
AC_SUBST(WEBRTC_CFLAGS)
AC_SUBST(WEBRTC_LIBS)
AM_CONDITIONAL([HAVE_WEBRTC], [test "x$HAVE_WEBRTC" = "x1"])
###################################
# Output #
@ -1275,6 +1292,7 @@ AS_IF([test "x$HAVE_IPV6" = "x1"], ENABLE_IPV6=yes, ENABLE_IPV6=no)
AS_IF([test "x$HAVE_OPENSSL" = "x1"], ENABLE_OPENSSL=yes, ENABLE_OPENSSL=no)
AS_IF([test "x$HAVE_FFTW" = "x1"], ENABLE_FFTW=yes, ENABLE_FFTW=no)
AS_IF([test "x$HAVE_ORC" = "xyes"], ENABLE_ORC=yes, ENABLE_ORC=no)
AS_IF([test "x$HAVE_WEBRTC" = "x1"], ENABLE_WEBRTC=yes, ENABLE_WEBRTC=no)
AS_IF([test "x$HAVE_TDB" = "x1"], ENABLE_TDB=yes, ENABLE_TDB=no)
AS_IF([test "x$HAVE_GDBM" = "x1"], ENABLE_GDBM=yes, ENABLE_GDBM=no)
AS_IF([test "x$HAVE_SIMPLEDB" = "x1"], ENABLE_SIMPLEDB=yes, ENABLE_SIMPLEDB=no)
@ -1321,6 +1339,7 @@ echo "
Enable OpenSSL (for Airtunes): ${ENABLE_OPENSSL}
Enable fftw: ${ENABLE_FFTW}
Enable orc: ${ENABLE_ORC}
Enable WebRTC echo canceller: ${ENABLE_WEBRTC}
Database
tdb: ${ENABLE_TDB}
gdbm: ${ENABLE_GDBM}

View file

@ -48,6 +48,7 @@ AM_CFLAGS = \
$(PTHREAD_CFLAGS) \
-DPA_ALSA_PATHS_DIR=\"$(alsapathsdir)\" \
-DPA_ALSA_PROFILE_SETS_DIR=\"$(alsaprofilesetsdir)\"
AM_CXXFLAGS = $(AM_CFLAGS)
SERVER_CFLAGS = -D__INCLUDED_FROM_PULSE_AUDIO
AM_LIBADD = $(PTHREAD_LIBS) $(INTLLIBS)
@ -523,6 +524,7 @@ echo_cancel_test_SOURCES = $(module_echo_cancel_la_SOURCES)
nodist_echo_cancel_test_SOURCES = $(nodist_module_echo_cancel_la_SOURCES)
echo_cancel_test_LDADD = $(module_echo_cancel_la_LIBADD)
echo_cancel_test_CFLAGS = $(module_echo_cancel_la_CFLAGS) -DECHO_CANCEL_TEST=1
echo_cancel_test_CXXFLAGS = $(module_echo_cancel_la_CXXFLAGS) -DECHO_CANCEL_TEST=1
echo_cancel_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
###################################
@ -1753,6 +1755,12 @@ nodist_module_echo_cancel_la_SOURCES = \
module_echo_cancel_la_LIBADD += $(ORC_LIBS)
module_echo_cancel_la_CFLAGS += $(ORC_CFLAGS) -I$(top_builddir)/src/modules/echo-cancel
endif
if HAVE_WEBRTC
module_echo_cancel_la_SOURCES += modules/echo-cancel/webrtc.cc
module_echo_cancel_la_CFLAGS += -DHAVE_WEBRTC=1
module_echo_cancel_la_CXXFLAGS = $(AM_CXXFLAGS) $(SERVER_CFLAGS) $(WEBRTC_CFLAGS) -DHAVE_WEBRTC=1
module_echo_cancel_la_LIBADD += $(WEBRTC_LIBS)
endif
# RTP modules
module_rtp_send_la_SOURCES = modules/rtp/module-rtp-send.c

View file

@ -49,6 +49,15 @@ struct pa_echo_canceller_params {
uint32_t blocksize;
AEC *aec;
} adrian;
#ifdef HAVE_WEBRTC
struct {
/* This is a void* so that we don't have to convert this whole file
* to C++ linkage. apm is a pointer to an AudioProcessing object */
void *apm;
uint32_t blocksize;
pa_sample_spec sample_spec;
} webrtc;
#endif
/* each canceller-specific structure goes here */
} priv;
};
@ -86,4 +95,16 @@ pa_bool_t pa_adrian_ec_init(pa_core *c, pa_echo_canceller *ec,
void pa_adrian_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out);
void pa_adrian_ec_done(pa_echo_canceller *ec);
#ifdef HAVE_WEBRTC
/* WebRTC canceller functions */
PA_C_DECL_BEGIN
pa_bool_t pa_webrtc_ec_init(pa_core *c, pa_echo_canceller *ec,
pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map,
uint32_t *blocksize, const char *args);
void pa_webrtc_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out);
void pa_webrtc_ec_done(pa_echo_canceller *ec);
PA_C_DECL_END
#endif
#endif /* fooechocancelhfoo */

View file

@ -83,6 +83,9 @@ typedef enum {
PA_ECHO_CANCELLER_INVALID = -1,
PA_ECHO_CANCELLER_SPEEX = 0,
PA_ECHO_CANCELLER_ADRIAN,
#ifdef HAVE_WEBRTC
PA_ECHO_CANCELLER_WEBRTC,
#endif
} pa_echo_canceller_method_t;
#define DEFAULT_ECHO_CANCELLER "speex"
@ -100,6 +103,14 @@ static const pa_echo_canceller ec_table[] = {
.run = pa_adrian_ec_run,
.done = pa_adrian_ec_done,
},
#ifdef HAVE_WEBRTC
{
/* WebRTC's audio processing engine */
.init = pa_webrtc_ec_init,
.run = pa_webrtc_ec_run,
.done = pa_webrtc_ec_done,
},
#endif
};
#define DEFAULT_RATE 32000
@ -1340,6 +1351,10 @@ static pa_echo_canceller_method_t get_ec_method_from_string(const char *method)
return PA_ECHO_CANCELLER_SPEEX;
else if (pa_streq(method, "adrian"))
return PA_ECHO_CANCELLER_ADRIAN;
#ifdef HAVE_WEBRTC
else if (pa_streq(method, "webrtc"))
return PA_ECHO_CANCELLER_WEBRTC;
#endif
else
return PA_ECHO_CANCELLER_INVALID;
}

View file

@ -0,0 +1,234 @@
/***
This file is part of PulseAudio.
Copyright 2011 Collabora Ltd.
Contributor: Arun Raghavan <arun.raghavan@collabora.co.uk>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2.1 of the License,
or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with PulseAudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pulse/cdecl.h>
PA_C_DECL_BEGIN
#include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulse/timeval.h>
#include "echo-cancel.h"
PA_C_DECL_END
#include <audio_processing.h>
#include <module_common_types.h>
#define BLOCK_SIZE_US 10000
#define DEFAULT_HIGH_PASS_FILTER TRUE
#define DEFAULT_NOISE_SUPPRESSION TRUE
#define DEFAULT_ANALOG_GAIN_CONTROL FALSE
#define DEFAULT_DIGITAL_GAIN_CONTROL TRUE
#define DEFAULT_MOBILE FALSE
#define DEFAULT_ROUTING_MODE "speakerphone"
#define DEFAULT_COMFORT_NOISE TRUE
static const char* const valid_modargs[] = {
"high_pass_filter",
"noise_suppression",
"analog_gain_control",
"digital_gain_control",
"mobile",
"routing_mode",
"comfort_noise",
NULL
};
static int routing_mode_from_string(const char *rmode) {
if (pa_streq(rmode, "quiet-earpiece-or-headset"))
return webrtc::EchoControlMobile::kQuietEarpieceOrHeadset;
else if (pa_streq(rmode, "earpiece"))
return webrtc::EchoControlMobile::kEarpiece;
else if (pa_streq(rmode, "loud-earpiece"))
return webrtc::EchoControlMobile::kLoudEarpiece;
else if (pa_streq(rmode, "speakerphone"))
return webrtc::EchoControlMobile::kSpeakerphone;
else if (pa_streq(rmode, "loud-speakerphone"))
return webrtc::EchoControlMobile::kLoudSpeakerphone;
else
return -1;
}
pa_bool_t pa_webrtc_ec_init(pa_core *c, pa_echo_canceller *ec,
pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map,
uint32_t *blocksize, const char *args)
{
webrtc::AudioProcessing *apm = NULL;
pa_bool_t hpf, ns, agc, dgc, mobile, cn;
int rm;
pa_modargs *ma;
if (!(ma = pa_modargs_new(args, valid_modargs))) {
pa_log("Failed to parse submodule arguments.");
goto fail;
}
hpf = DEFAULT_HIGH_PASS_FILTER;
if (pa_modargs_get_value_boolean(ma, "high_pass_filter", &hpf) < 0) {
pa_log("Failed to parse high_pass_filter value");
goto fail;
}
ns = DEFAULT_NOISE_SUPPRESSION;
if (pa_modargs_get_value_boolean(ma, "noise_suppression", &ns) < 0) {
pa_log("Failed to parse noise_suppression value");
goto fail;
}
agc = DEFAULT_ANALOG_GAIN_CONTROL;
if (pa_modargs_get_value_boolean(ma, "analog_gain_control", &agc) < 0) {
pa_log("Failed to parse analog_gain_control value");
goto fail;
}
dgc = DEFAULT_DIGITAL_GAIN_CONTROL;
if (pa_modargs_get_value_boolean(ma, "analog_gain_control", &dgc) < 0) {
pa_log("Failed to parse digital_gain_control value");
goto fail;
}
if (agc && dgc) {
pa_log("You must pick only one between analog and digital gain control");
goto fail;
}
mobile = DEFAULT_MOBILE;
if (pa_modargs_get_value_boolean(ma, "mobile", &mobile) < 0) {
pa_log("Failed to parse mobile value");
goto fail;
}
if (mobile) {
if ((rm = routing_mode_from_string(pa_modargs_get_value(ma, "routing_mode", DEFAULT_ROUTING_MODE))) < 0) {
pa_log("Failed to parse routing_mode value");
goto fail;
}
cn = DEFAULT_COMFORT_NOISE;
if (pa_modargs_get_value_boolean(ma, "comfort_noise", &cn) < 0) {
pa_log("Failed to parse cn value");
goto fail;
}
} else {
if (pa_modargs_get_value(ma, "comfort_noise", NULL) || pa_modargs_get_value(ma, "routing_mode", NULL)) {
pa_log("The routing_mode and comfort_noise options are only valid with mobile=true");
goto fail;
}
}
apm = webrtc::AudioProcessing::Create(0);
source_ss->format = PA_SAMPLE_S16NE;
*sink_ss = *source_ss;
/* FIXME: the implementation actually allows a different number of
* source/sink channels. Do we want to support that? */
*sink_map = *source_map;
apm->set_sample_rate_hz(source_ss->rate);
apm->set_num_channels(source_ss->channels, source_ss->channels);
apm->set_num_reverse_channels(sink_ss->channels);
if (hpf)
apm->high_pass_filter()->Enable(true);
if (!mobile) {
apm->echo_cancellation()->enable_drift_compensation(false);
apm->echo_cancellation()->Enable(true);
} else {
apm->echo_control_mobile()->set_routing_mode(static_cast<webrtc::EchoControlMobile::RoutingMode>(rm));
apm->echo_control_mobile()->enable_comfort_noise(cn);
apm->echo_control_mobile()->Enable(true);
}
if (ns) {
apm->noise_suppression()->set_level(webrtc::NoiseSuppression::kHigh);
apm->noise_suppression()->Enable(true);
}
if (agc || dgc) {
if (mobile && rm <= webrtc::EchoControlMobile::kEarpiece)
/* Maybe this should be a knob, but we've got a lot of knobs already */
apm->gain_control()->set_mode(webrtc::GainControl::kFixedDigital);
else if (dgc)
apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveDigital);
else {
/* FIXME: Hook up for analog AGC */
pa_log("Analog gain control isn't implemented yet -- using ditital gain control.");
apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveDigital);
}
}
apm->voice_detection()->Enable(true);
ec->params.priv.webrtc.apm = apm;
ec->params.priv.webrtc.sample_spec = *source_ss;
ec->params.priv.webrtc.blocksize = *blocksize = (uint64_t)pa_bytes_per_second(source_ss) * BLOCK_SIZE_US / PA_USEC_PER_SEC;
pa_modargs_free(ma);
return TRUE;
fail:
if (ma)
pa_modargs_free(ma);
if (apm)
webrtc::AudioProcessing::Destroy(apm);
return FALSE;
}
void pa_webrtc_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) {
webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm;
webrtc::AudioFrame play_frame, out_frame;
const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec;
play_frame._audioChannel = ss->channels;
play_frame._frequencyInHz = ss->rate;
play_frame._payloadDataLengthInSamples = ec->params.priv.webrtc.blocksize / pa_frame_size(ss);
memcpy(play_frame._payloadData, play, ec->params.priv.webrtc.blocksize);
out_frame._audioChannel = ss->channels;
out_frame._frequencyInHz = ss->rate;
out_frame._payloadDataLengthInSamples = ec->params.priv.webrtc.blocksize / pa_frame_size(ss);
memcpy(out_frame._payloadData, rec, ec->params.priv.webrtc.blocksize);
apm->AnalyzeReverseStream(&play_frame);
apm->set_stream_delay_ms(0);
apm->ProcessStream(&out_frame);
memcpy(out, out_frame._payloadData, ec->params.priv.webrtc.blocksize);
}
void pa_webrtc_ec_done(pa_echo_canceller *ec) {
if (ec->params.priv.webrtc.apm) {
webrtc::AudioProcessing::Destroy((webrtc::AudioProcessing*)ec->params.priv.webrtc.apm);
ec->params.priv.webrtc.apm = NULL;
}
}