From 47e4dd1ec43298f4d5b8165b772b07f95589966e Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 7 Sep 2010 13:43:58 +0530 Subject: 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). --- LICENSE | 4 + src/Makefile.am | 5 +- src/modules/echo-cancel/adrian-aec.c | 233 +++++++++++++++++ src/modules/echo-cancel/adrian-aec.h | 370 +++++++++++++++++++++++++++ src/modules/echo-cancel/adrian-license.txt | 17 ++ src/modules/echo-cancel/adrian.c | 121 +++++++++ src/modules/echo-cancel/adrian.h | 31 +++ src/modules/echo-cancel/echo-cancel.h | 14 + src/modules/echo-cancel/module-echo-cancel.c | 8 + 9 files changed, 802 insertions(+), 1 deletion(-) create mode 100644 src/modules/echo-cancel/adrian-aec.c create mode 100644 src/modules/echo-cancel/adrian-aec.h create mode 100644 src/modules/echo-cancel/adrian-license.txt create mode 100644 src/modules/echo-cancel/adrian.c create mode 100644 src/modules/echo-cancel/adrian.h diff --git a/LICENSE b/LICENSE index 612c2341..3a82749e 100644 --- a/LICENSE +++ b/LICENSE @@ -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. diff --git a/src/Makefile.am b/src/Makefile.am index 242532ca..09804886 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1605,7 +1605,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) diff --git a/src/modules/echo-cancel/adrian-aec.c b/src/modules/echo-cancel/adrian-aec.c new file mode 100644 index 00000000..69107c75 --- /dev/null +++ b/src/modules/echo-cancel/adrian-aec.c @@ -0,0 +1,233 @@ +/* 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 +#include + +#include + +#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; +} diff --git a/src/modules/echo-cancel/adrian-aec.h b/src/modules/echo-cancel/adrian-aec.h new file mode 100644 index 00000000..1f5b090a --- /dev/null +++ b/src/modules/echo-cancel/adrian-aec.h @@ -0,0 +1,370 @@ +/* aec.h + * + * Copyright (C) DFS Deutsche Flugsicherung (2004, 2005). + * All Rights Reserved. + * Author: Andre Adrian + * + * Acoustic Echo Cancellation Leaky 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 + */ + +#ifndef _AEC_H /* include only once */ + +#define WIDEB 2 + +// use double if your CPU does software-emulation of float +typedef float REAL; + +/* dB Values */ +#define M0dB 1.0f +#define M3dB 0.71f +#define M6dB 0.50f +#define M9dB 0.35f +#define M12dB 0.25f +#define M18dB 0.125f +#define M24dB 0.063f + +/* dB values for 16bit PCM */ +/* MxdB_PCM = 32767 * 10 ^(x / 20) */ +#define M10dB_PCM 10362.0f +#define M20dB_PCM 3277.0f +#define M25dB_PCM 1843.0f +#define M30dB_PCM 1026.0f +#define M35dB_PCM 583.0f +#define M40dB_PCM 328.0f +#define M45dB_PCM 184.0f +#define M50dB_PCM 104.0f +#define M55dB_PCM 58.0f +#define M60dB_PCM 33.0f +#define M65dB_PCM 18.0f +#define M70dB_PCM 10.0f +#define M75dB_PCM 6.0f +#define M80dB_PCM 3.0f +#define M85dB_PCM 2.0f +#define M90dB_PCM 1.0f + +#define MAXPCM 32767.0f + +/* Design constants (Change to fine tune the algorithms */ + +/* The following values are for hardware AEC and studio quality + * microphone */ + +/* NLMS filter length in taps (samples). A longer filter length gives + * better Echo Cancellation, but maybe slower convergence speed and + * needs more CPU power (Order of NLMS is linear) */ +#define NLMS_LEN (100*WIDEB*8) + +/* Vector w visualization length in taps (samples). + * Must match argv value for wdisplay.tcl */ +#define DUMP_LEN (40*WIDEB*8) + +/* minimum energy in xf. Range: M70dB_PCM to M50dB_PCM. Should be equal + * to microphone ambient Noise level */ +#define NoiseFloor M55dB_PCM + +/* Leaky hangover in taps. + */ +#define Thold (60 * WIDEB * 8) + +// Adrian soft decision DTD +// left point. X is ratio, Y is stepsize +#define STEPX1 1.0 +#define STEPY1 1.0 +// right point. STEPX2=2.0 is good double talk, 3.0 is good single talk. +#define STEPX2 2.5 +#define STEPY2 0 +#define ALPHAFAST (1.0f / 100.0f) +#define ALPHASLOW (1.0f / 20000.0f) + + + +/* Ageing multiplier for LMS memory vector w */ +#define Leaky 0.9999f + +/* Double Talk Detector Speaker/Microphone Threshold. Range <=1 + * Large value (M0dB) is good for Single-Talk Echo cancellation, + * small value (M12dB) is good for Doulbe-Talk AEC */ +#define GeigelThreshold M6dB + +/* for Non Linear Processor. Range >0 to 1. Large value (M0dB) is good + * for Double-Talk, small value (M12dB) is good for Single-Talk */ +#define NLPAttenuation M12dB + +/* Below this line there are no more design constants */ + +typedef struct IIR_HP IIR_HP; + +/* Exponential Smoothing or IIR Infinite Impulse Response Filter */ +struct IIR_HP { + REAL x; +}; + +static IIR_HP* IIR_HP_init(void) { + IIR_HP *i = pa_xnew(IIR_HP, 1); + i->x = 0.0f; + return i; + } + +static REAL IIR_HP_highpass(IIR_HP *i, REAL in) { + const REAL a0 = 0.01f; /* controls Transfer Frequency */ + /* Highpass = Signal - Lowpass. Lowpass = Exponential Smoothing */ + i->x += a0 * (in - i->x); + return in - i->x; + }; + +typedef struct FIR_HP_300Hz FIR_HP_300Hz; + +#if WIDEB==1 +/* 17 taps FIR Finite Impulse Response filter + * Coefficients calculated with + * www.dsptutor.freeuk.com/KaiserFilterDesign/KaiserFilterDesign.html + */ +class FIR_HP_300Hz { + REAL z[18]; + +public: + FIR_HP_300Hz() { + memset(this, 0, sizeof(FIR_HP_300Hz)); + } + + REAL highpass(REAL in) { + const REAL a[18] = { + // Kaiser Window FIR Filter, Filter type: High pass + // Passband: 300.0 - 4000.0 Hz, Order: 16 + // Transition band: 75.0 Hz, Stopband attenuation: 10.0 dB + -0.034870606, -0.039650206, -0.044063766, -0.04800318, + -0.051370874, -0.054082647, -0.056070227, -0.057283327, + 0.8214126, -0.057283327, -0.056070227, -0.054082647, + -0.051370874, -0.04800318, -0.044063766, -0.039650206, + -0.034870606, 0.0 + }; + memmove(z + 1, z, 17 * sizeof(REAL)); + z[0] = in; + REAL sum0 = 0.0, sum1 = 0.0; + int j; + + for (j = 0; j < 18; j += 2) { + // optimize: partial loop unrolling + sum0 += a[j] * z[j]; + sum1 += a[j + 1] * z[j + 1]; + } + return sum0 + sum1; + } +}; + +#else + +/* 35 taps FIR Finite Impulse Response filter + * Passband 150Hz to 4kHz for 8kHz sample rate, 300Hz to 8kHz for 16kHz + * sample rate. + * Coefficients calculated with + * www.dsptutor.freeuk.com/KaiserFilterDesign/KaiserFilterDesign.html + */ +struct FIR_HP_300Hz { + REAL z[36]; +}; + +static FIR_HP_300Hz* FIR_HP_300Hz_init(void) { + FIR_HP_300Hz *ret = pa_xnew(FIR_HP_300Hz, 1); + memset(ret, 0, sizeof(FIR_HP_300Hz)); + return ret; + } + +static REAL FIR_HP_300Hz_highpass(FIR_HP_300Hz *f, REAL in) { + REAL sum0 = 0.0, sum1 = 0.0; + int j; + const REAL a[36] = { + // Kaiser Window FIR Filter, Filter type: High pass + // Passband: 150.0 - 4000.0 Hz, Order: 34 + // Transition band: 34.0 Hz, Stopband attenuation: 10.0 dB + -0.016165324, -0.017454365, -0.01871232, -0.019931411, + -0.021104068, -0.022222936, -0.02328091, -0.024271343, + -0.025187887, -0.02602462, -0.026776174, -0.027437767, + -0.028004972, -0.028474221, -0.028842418, -0.029107114, + -0.02926664, 0.8524841, -0.02926664, -0.029107114, + -0.028842418, -0.028474221, -0.028004972, -0.027437767, + -0.026776174, -0.02602462, -0.025187887, -0.024271343, + -0.02328091, -0.022222936, -0.021104068, -0.019931411, + -0.01871232, -0.017454365, -0.016165324, 0.0 + }; + memmove(f->z + 1, f->z, 35 * sizeof(REAL)); + f->z[0] = in; + + for (j = 0; j < 36; j += 2) { + // optimize: partial loop unrolling + sum0 += a[j] * f->z[j]; + sum1 += a[j + 1] * f->z[j + 1]; + } + return sum0 + sum1; + } +#endif + +typedef struct IIR1 IIR1; + +/* Recursive single pole IIR Infinite Impulse response High-pass filter + * + * Reference: The Scientist and Engineer's Guide to Digital Processing + * + * output[N] = A0 * input[N] + A1 * input[N-1] + B1 * output[N-1] + * + * X = exp(-2.0 * pi * Fc) + * A0 = (1 + X) / 2 + * A1 = -(1 + X) / 2 + * B1 = X + * Fc = cutoff freq / sample rate + */ +struct IIR1 { + REAL in0, out0; + REAL a0, a1, b1; +}; + +#if 0 + IIR1() { + memset(this, 0, sizeof(IIR1)); + } +#endif + +static IIR1* IIR1_init(REAL Fc) { + IIR1 *i = pa_xnew(IIR1, 1); + i->b1 = expf(-2.0f * M_PI * Fc); + i->a0 = (1.0f + i->b1) / 2.0f; + i->a1 = -(i->a0); + i->in0 = 0.0f; + i->out0 = 0.0f; + return i; + } + +static REAL IIR1_highpass(IIR1 *i, REAL in) { + REAL out = i->a0 * in + i->a1 * i->in0 + i->b1 * i->out0; + i->in0 = in; + i->out0 = out; + return out; + } + + +#if 0 +/* Recursive two pole IIR Infinite Impulse Response filter + * Coefficients calculated with + * http://www.dsptutor.freeuk.com/IIRFilterDesign/IIRFiltDes102.html + */ +class IIR2 { + REAL x[2], y[2]; + +public: + IIR2() { + memset(this, 0, sizeof(IIR2)); + } + + REAL highpass(REAL in) { + // Butterworth IIR filter, Filter type: HP + // Passband: 2000 - 4000.0 Hz, Order: 2 + const REAL a[] = { 0.29289323f, -0.58578646f, 0.29289323f }; + const REAL b[] = { 1.3007072E-16f, 0.17157288f }; + REAL out = + a[0] * in + a[1] * x[0] + a[2] * x[1] - b[0] * y[0] - b[1] * y[1]; + + x[1] = x[0]; + x[0] = in; + y[1] = y[0]; + y[0] = out; + return out; + } +}; +#endif + + +// Extention in taps to reduce mem copies +#define NLMS_EXT (10*8) + +// block size in taps to optimize DTD calculation +#define DTD_LEN 16 + +typedef struct AEC AEC; + +struct AEC { + // Time domain Filters + IIR_HP *acMic, *acSpk; // DC-level remove Highpass) + FIR_HP_300Hz *cutoff; // 150Hz cut-off Highpass + REAL gain; // Mic signal amplify + IIR1 *Fx, *Fe; // pre-whitening Highpass for x, e + + // Adrian soft decision DTD (Double Talk Detector) + REAL dfast, xfast; + REAL dslow, xslow; + + // NLMS-pw + REAL x[NLMS_LEN + NLMS_EXT]; // tap delayed loudspeaker signal + REAL xf[NLMS_LEN + NLMS_EXT]; // pre-whitening tap delayed signal + REAL w[NLMS_LEN]; // tap weights + int j; // optimize: less memory copies + double dotp_xf_xf; // double to avoid loss of precision + float delta; // noise floor to stabilize NLMS + + // AES + float aes_y2; // not in use! + + // w vector visualization + REAL ws[DUMP_LEN]; // tap weights sums + int fdwdisplay; // TCP file descriptor + int dumpcnt; // wdisplay output counter + + // variables are public for visualization + int hangover; + float stepsize; +}; + +/* Double-Talk Detector + * + * in d: microphone sample (PCM as REALing point value) + * in x: loudspeaker sample (PCM as REALing point value) + * return: from 0 for doubletalk to 1.0 for single talk + */ +static float AEC_dtd(AEC *a, REAL d, REAL x); + +static void AEC_leaky(AEC *a); + +/* Normalized Least Mean Square Algorithm pre-whitening (NLMS-pw) + * The LMS algorithm was developed by Bernard Widrow + * book: Haykin, Adaptive Filter Theory, 4. edition, Prentice Hall, 2002 + * + * in d: microphone sample (16bit PCM value) + * in x_: loudspeaker sample (16bit PCM value) + * in stepsize: NLMS adaptation variable + * return: echo cancelled microphone sample + */ +static REAL AEC_nlms_pw(AEC *a, REAL d, REAL x_, float stepsize); + + AEC* AEC_init(int RATE); + +/* Acoustic Echo Cancellation and Suppression of one sample + * in d: microphone signal with echo + * in x: loudspeaker signal + * return: echo cancelled microphone signal + */ + int AEC_doAEC(AEC *a, int d_, int x_); + +static float AEC_getambient(AEC *a) { + return a->dfast; + }; +static void AEC_setambient(AEC *a, float Min_xf) { + a->dotp_xf_xf -= a->delta; // subtract old delta + a->delta = (NLMS_LEN-1) * Min_xf * Min_xf; + a->dotp_xf_xf += a->delta; // add new delta + }; +static void AEC_setgain(AEC *a, float gain_) { + a->gain = gain_; + }; +#if 0 + void AEC_openwdisplay(AEC *a); +#endif +static void AEC_setaes(AEC *a, float aes_y2_) { + a->aes_y2 = aes_y2_; + }; +static double AEC_max_dotp_xf_xf(AEC *a, double u); + +#define _AEC_H +#endif diff --git a/src/modules/echo-cancel/adrian-license.txt b/src/modules/echo-cancel/adrian-license.txt new file mode 100644 index 00000000..7c06efd0 --- /dev/null +++ b/src/modules/echo-cancel/adrian-license.txt @@ -0,0 +1,17 @@ + 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. diff --git a/src/modules/echo-cancel/adrian.c b/src/modules/echo-cancel/adrian.c new file mode 100644 index 00000000..86c22cb3 --- /dev/null +++ b/src/modules/echo-cancel/adrian.c @@ -0,0 +1,121 @@ +/*** + This file is part of PulseAudio. + + Copyright 2010 Arun Raghavan + + Contributor: Wim Taymans + + 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 +#endif + +#include +#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; +} diff --git a/src/modules/echo-cancel/adrian.h b/src/modules/echo-cancel/adrian.h new file mode 100644 index 00000000..d02e934d --- /dev/null +++ b/src/modules/echo-cancel/adrian.h @@ -0,0 +1,31 @@ +/*** + This file is part of PulseAudio. + + Copyright 2010 Arun Raghavan + + 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_); diff --git a/src/modules/echo-cancel/echo-cancel.h b/src/modules/echo-cancel/echo-cancel.h index 205c4d14..65e0e240 100644 --- a/src/modules/echo-cancel/echo-cancel.h +++ b/src/modules/echo-cancel/echo-cancel.h @@ -28,6 +28,7 @@ #include #include +#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); diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c index 6a88167b..75f74d34 100644 --- a/src/modules/echo-cancel/module-echo-cancel.c +++ b/src/modules/echo-cancel/module-echo-cancel.c @@ -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) -- cgit v1.2.3