diff options
| -rw-r--r-- | LICENSE | 4 | ||||
| -rw-r--r-- | src/Makefile.am | 5 | ||||
| -rw-r--r-- | src/modules/echo-cancel/adrian-aec.c | 233 | ||||
| -rw-r--r-- | src/modules/echo-cancel/adrian-aec.h | 370 | ||||
| -rw-r--r-- | src/modules/echo-cancel/adrian-license.txt | 17 | ||||
| -rw-r--r-- | src/modules/echo-cancel/adrian.c | 121 | ||||
| -rw-r--r-- | src/modules/echo-cancel/adrian.h | 31 | ||||
| -rw-r--r-- | src/modules/echo-cancel/echo-cancel.h | 14 | ||||
| -rw-r--r-- | src/modules/echo-cancel/module-echo-cancel.c | 8 | 
9 files changed, 802 insertions, 1 deletions
| @@ -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 <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; +} 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 <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; +} 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 <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_); 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 <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); 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) | 
