Commit 526277c9 authored by Arun Raghavan's avatar Arun Raghavan

echo-cancel: Add alternative echo-cancellation implementation

This adds Andre Adrian's AEC implementation from his intercom project
(http://andreadrian.de/intercom/) as an alternative to the speex echo
cancellation routines. Since the implementation was in C++ and not in
the form of a library, I have converted the code to C and made a local
copy of the implementation.

The implementation actually works on floating point data, so we can
tweak it to work with both integer and floating point samples (currently
we just use S16LE).
parent 126e1336
......@@ -10,4 +10,8 @@ LGPL licensed and the server part ('libpulsecore') as being GPL licensed. Since
the PulseAudio daemon and the modules link to 'libpulsecore' they are of course
also GPL licensed.
Andre Adrian's echo cancellation implementation is licensed under a less
restrictive license - see src/modules/echo-cancel/adrian-license.txt for
details.
-- Lennart Poettering, April 20th, 2006.
......@@ -1702,7 +1702,10 @@ module_suspend_on_idle_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO
module_suspend_on_idle_la_CFLAGS = $(AM_CFLAGS)
# echo-cancel module
module_echo_cancel_la_SOURCES = modules/echo-cancel/module-echo-cancel.c modules/echo-cancel/speex.c
module_echo_cancel_la_SOURCES = modules/echo-cancel/module-echo-cancel.c \
modules/echo-cancel/speex.c \
modules/echo-cancel/adrian-aec.c \
modules/echo-cancel/adrian.c
module_echo_cancel_la_LDFLAGS = $(MODULE_LDFLAGS)
module_echo_cancel_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la $(LIBSPEEX_LIBS)
module_echo_cancel_la_CFLAGS = $(AM_CFLAGS) $(LIBSPEEX_CFLAGS)
......
/* aec.cpp
*
* Copyright (C) DFS Deutsche Flugsicherung (2004, 2005).
* All Rights Reserved.
*
* Acoustic Echo Cancellation NLMS-pw algorithm
*
* Version 0.3 filter created with www.dsptutor.freeuk.com
* Version 0.3.1 Allow change of stability parameter delta
* Version 0.4 Leaky Normalized LMS - pre whitening algorithm
*/
#include <math.h>
#include <string.h>
#include <pulse/xmalloc.h>
#include "adrian-aec.h"
/* Vector Dot Product */
static REAL dotp(REAL a[], REAL b[])
{
REAL sum0 = 0.0, sum1 = 0.0;
int j;
for (j = 0; j < NLMS_LEN; j += 2) {
// optimize: partial loop unrolling
sum0 += a[j] * b[j];
sum1 += a[j + 1] * b[j + 1];
}
return sum0 + sum1;
}
AEC* AEC_init(int RATE)
{
AEC *a = pa_xnew(AEC, 1);
a->hangover = 0;
memset(a->x, 0, sizeof(a->x));
memset(a->xf, 0, sizeof(a->xf));
memset(a->w, 0, sizeof(a->w));
a->j = NLMS_EXT;
a->delta = 0.0f;
AEC_setambient(a, NoiseFloor);
a->dfast = a->dslow = M75dB_PCM;
a->xfast = a->xslow = M80dB_PCM;
a->gain = 1.0f;
a->Fx = IIR1_init(2000.0f/RATE);
a->Fe = IIR1_init(2000.0f/RATE);
a->cutoff = FIR_HP_300Hz_init();
a->acMic = IIR_HP_init();
a->acSpk = IIR_HP_init();
a->aes_y2 = M0dB;
a->fdwdisplay = -1;
a->dumpcnt = 0;
memset(a->ws, 0, sizeof(a->ws));
return a;
}
// Adrian soft decision DTD
// (Dual Average Near-End to Far-End signal Ratio DTD)
// This algorithm uses exponential smoothing with differnt
// ageing parameters to get fast and slow near-end and far-end
// signal averages. The ratio of NFRs term
// (dfast / xfast) / (dslow / xslow) is used to compute the stepsize
// A ratio value of 2.5 is mapped to stepsize 0, a ratio of 0 is
// mapped to 1.0 with a limited linear function.
static float AEC_dtd(AEC *a, REAL d, REAL x)
{
float stepsize;
float ratio, M;
// fast near-end and far-end average
a->dfast += ALPHAFAST * (fabsf(d) - a->dfast);
a->xfast += ALPHAFAST * (fabsf(x) - a->xfast);
// slow near-end and far-end average
a->dslow += ALPHASLOW * (fabsf(d) - a->dslow);
a->xslow += ALPHASLOW * (fabsf(x) - a->xslow);
if (a->xfast < M70dB_PCM) {
return 0.0; // no Spk signal
}
if (a->dfast < M70dB_PCM) {
return 0.0; // no Mic signal
}
// ratio of NFRs
ratio = (a->dfast * a->xslow) / (a->dslow * a->xfast);
// begrenzte lineare Kennlinie
M = (STEPY2 - STEPY1) / (STEPX2 - STEPX1);
if (ratio < STEPX1) {
stepsize = STEPY1;
} else if (ratio > STEPX2) {
stepsize = STEPY2;
} else {
// Punktrichtungsform einer Geraden
stepsize = M * (ratio - STEPX1) + STEPY1;
}
return stepsize;
}
static void AEC_leaky(AEC *a)
// The xfast signal is used to charge the hangover timer to Thold.
// When hangover expires (no Spk signal for some time) the vector w
// is erased. This is my implementation of Leaky NLMS.
{
if (a->xfast >= M70dB_PCM) {
// vector w is valid for hangover Thold time
a->hangover = Thold;
} else {
if (a->hangover > 1) {
--(a->hangover);
} else if (1 == a->hangover) {
--(a->hangover);
// My Leaky NLMS is to erase vector w when hangover expires
memset(a->w, 0, sizeof(a->w));
}
}
}
#if 0
void AEC::openwdisplay() {
// open TCP connection to program wdisplay.tcl
fdwdisplay = socket_async("127.0.0.1", 50999);
};
#endif
static REAL AEC_nlms_pw(AEC *a, REAL d, REAL x_, float stepsize)
{
REAL e;
REAL ef;
a->x[a->j] = x_;
a->xf[a->j] = IIR1_highpass(a->Fx, x_); // pre-whitening of x
// calculate error value
// (mic signal - estimated mic signal from spk signal)
e = d;
if (a->hangover > 0) {
e -= dotp(a->w, a->x + a->j);
}
ef = IIR1_highpass(a->Fe, e); // pre-whitening of e
// optimize: iterative dotp(xf, xf)
a->dotp_xf_xf += (a->xf[a->j] * a->xf[a->j] - a->xf[a->j + NLMS_LEN - 1] * a->xf[a->j + NLMS_LEN - 1]);
if (stepsize > 0.0) {
// calculate variable step size
REAL mikro_ef = stepsize * ef / a->dotp_xf_xf;
// update tap weights (filter learning)
int i;
for (i = 0; i < NLMS_LEN; i += 2) {
// optimize: partial loop unrolling
a->w[i] += mikro_ef * a->xf[i + a->j];
a->w[i + 1] += mikro_ef * a->xf[i + a->j + 1];
}
}
if (--(a->j) < 0) {
// optimize: decrease number of memory copies
a->j = NLMS_EXT;
memmove(a->x + a->j + 1, a->x, (NLMS_LEN - 1) * sizeof(REAL));
memmove(a->xf + a->j + 1, a->xf, (NLMS_LEN - 1) * sizeof(REAL));
}
// Saturation
if (e > MAXPCM) {
return MAXPCM;
} else if (e < -MAXPCM) {
return -MAXPCM;
} else {
return e;
}
}
int AEC_doAEC(AEC *a, int d_, int x_)
{
REAL d = (REAL) d_;
REAL x = (REAL) x_;
// Mic Highpass Filter - to remove DC
d = IIR_HP_highpass(a->acMic, d);
// Mic Highpass Filter - cut-off below 300Hz
d = FIR_HP_300Hz_highpass(a->cutoff, d);
// Amplify, for e.g. Soundcards with -6dB max. volume
d *= a->gain;
// Spk Highpass Filter - to remove DC
x = IIR_HP_highpass(a->acSpk, x);
// Double Talk Detector
a->stepsize = AEC_dtd(a, d, x);
// Leaky (ageing of vector w)
AEC_leaky(a);
// Acoustic Echo Cancellation
d = AEC_nlms_pw(a, d, x, a->stepsize);
#if 0
if (fdwdisplay >= 0) {
if (++dumpcnt >= (WIDEB*RATE/10)) {
// wdisplay creates 10 dumps per seconds = large CPU load!
dumpcnt = 0;
write(fdwdisplay, ws, DUMP_LEN*sizeof(float));
// we don't check return value. This is not production quality!!!
memset(ws, 0, sizeof(ws));
} else {
int i;
for (i = 0; i < DUMP_LEN; i += 2) {
// optimize: partial loop unrolling
ws[i] += w[i];
ws[i + 1] += w[i + 1];
}
}
}
#endif
return (int) d;
}
This diff is collapsed.
Copyright (C) DFS Deutsche Flugsicherung (2004). All Rights Reserved.
You are allowed to use this source code in any open source or closed
source software you want. You are allowed to use the algorithms for a
hardware solution. You are allowed to modify the source code.
You are not allowed to remove the name of the author from this memo or
from the source code files. You are not allowed to monopolize the
source code or the algorithms behind the source code as your
intellectual property. This source code is free of royalty and comes
with no warranty.
--- The following does not apply to the PulseAudio module ---
Please see g711/gen-lic.txt for the ITU-T G.711 codec copyright.
Please see gsm/gen-lic.txt for the ITU-T GSM codec copyright.
Please see ilbc/COPYRIGHT and ilbc/NOTICE for the IETF iLBC codec
copyright.
/***
This file is part of PulseAudio.
Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk>
Contributor: Wim Taymans <wim.taymans@gmail.com>
The actual implementation is taken from the sources at
http://andreadrian.de/intercom/ - for the license, look for
adrian-license.txt in the same directory as this file.
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 <pulsecore/modargs.h>
#include "echo-cancel.h"
/* should be between 10-20 ms */
#define DEFAULT_FRAME_SIZE_MS 20
static const char* const valid_modargs[] = {
"frame_size_ms",
NULL
};
static void pa_adrian_ec_fixate_spec(pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map)
{
source_ss->format = PA_SAMPLE_S16LE;
source_ss->channels = 1;
pa_channel_map_init_mono(source_map);
*sink_ss = *source_ss;
*sink_map = *source_map;
}
pa_bool_t pa_adrian_ec_init(pa_echo_canceller *ec,
pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map,
const char *args)
{
int framelen, rate;
uint32_t frame_size_ms;
pa_modargs *ma;
if (!(ma = pa_modargs_new(args, valid_modargs))) {
pa_log("Failed to parse submodule arguments.");
goto fail;
}
frame_size_ms = DEFAULT_FRAME_SIZE_MS;
if (pa_modargs_get_value_u32(ma, "frame_size_ms", &frame_size_ms) < 0 || frame_size_ms < 1 || frame_size_ms > 200) {
pa_log("Invalid frame_size_ms specification");
goto fail;
}
pa_adrian_ec_fixate_spec(source_ss, source_map, sink_ss, sink_map);
rate = source_ss->rate;
framelen = (rate * frame_size_ms) / 1000;
ec->params.priv.adrian.blocksize = framelen * pa_frame_size (source_ss);
pa_log_debug ("Using framelen %d, blocksize %lld, channels %d, rate %d", framelen, (long long) ec->params.priv.adrian.blocksize, source_ss->channels, source_ss->rate);
ec->params.priv.adrian.aec = AEC_init(rate);
if (!ec->params.priv.adrian.aec)
goto fail;
pa_modargs_free(ma);
return TRUE;
fail:
if (ma)
pa_modargs_free(ma);
return FALSE;
}
void pa_adrian_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out)
{
unsigned int i;
for (i = 0; i < ec->params.priv.adrian.blocksize; i += 2) {
/* We know it's S16LE mono data */
int r = (((int8_t) rec[i + 1]) << 8) | rec[i];
int p = (((int8_t) play[i + 1]) << 8) | play[i];
int res;
res = AEC_doAEC(ec->params.priv.adrian.aec, r, p);
out[i] = (uint8_t) (res & 0xff);
out[i + 1] = (uint8_t) ((res >> 8) & 0xff);
}
}
void pa_adrian_ec_done(pa_echo_canceller *ec)
{
pa_xfree(ec->params.priv.adrian.aec);
ec->params.priv.adrian.aec = NULL;
}
uint32_t pa_adrian_ec_get_block_size(pa_echo_canceller *ec)
{
return ec->params.priv.adrian.blocksize;
}
/***
This file is part of PulseAudio.
Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk>
The actual implementation is taken from the sources at
http://andreadrian.de/intercom/ - for the license, look for
adrian-license.txt in the same directory as this file.
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.
***/
/* Forward declarations */
typedef struct AEC AEC;
AEC* AEC_init(int RATE);
int AEC_doAEC(AEC *a, int d_, int x_);
......@@ -28,6 +28,7 @@
#include <pulsecore/macro.h>
#include <speex/speex_echo.h>
#include "adrian.h"
/* Common data structures */
......@@ -39,6 +40,10 @@ struct pa_echo_canceller_params {
uint32_t blocksize;
SpeexEchoState *state;
} speex;
struct {
uint32_t blocksize;
AEC *aec;
} adrian;
/* each canceller-specific structure goes here */
} priv;
};
......@@ -67,3 +72,12 @@ pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec,
void pa_speex_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
void pa_speex_ec_done(pa_echo_canceller *ec);
uint32_t pa_speex_ec_get_block_size(pa_echo_canceller *ec);
/* Adrian Andre's echo canceller */
pa_bool_t pa_adrian_ec_init(pa_echo_canceller *ec,
pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map,
const char *args);
void pa_adrian_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
void pa_adrian_ec_done(pa_echo_canceller *ec);
uint32_t pa_adrian_ec_get_block_size(pa_echo_canceller *ec);
......@@ -82,6 +82,7 @@ PA_MODULE_USAGE(
/* NOTE: Make sure the enum and ec_table are maintained in the correct order */
enum {
PA_ECHO_CANCELLER_SPEEX,
PA_ECHO_CANCELLER_ADRIAN,
};
#define DEFAULT_ECHO_CANCELLER PA_ECHO_CANCELLER_SPEEX
......@@ -94,6 +95,13 @@ static const pa_echo_canceller ec_table[] = {
.done = pa_speex_ec_done,
.get_block_size = pa_speex_ec_get_block_size,
},
{
/* Adrian Andre's NLMS implementation */
.init = pa_adrian_ec_init,
.run = pa_adrian_ec_run,
.done = pa_adrian_ec_done,
.get_block_size = pa_adrian_ec_get_block_size,
},
};
#define DEFAULT_ADJUST_TIME_USEC (1*PA_USEC_PER_SEC)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment