| /* Copyright (C) 2003-2006 Jean-Marc Valin |
| |
| File: mdf.c |
| Echo canceller based on the MDF algorithm (see below) |
| |
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the following conditions are |
| met: |
| |
| 1. Redistributions of source code must retain the above copyright notice, |
| this list of conditions and the following disclaimer. |
| |
| 2. Redistributions in binary form must reproduce the above copyright |
| notice, this list of conditions and the following disclaimer in the |
| documentation and/or other materials provided with the distribution. |
| |
| 3. The name of the author may not be used to endorse or promote products |
| derived from this software without specific prior written permission. |
| |
| THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, |
| INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
| ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /* |
| The echo canceller is based on the MDF algorithm described in: |
| |
| J. S. Soo, K. K. Pang Multidelay block frequency adaptive filter, |
| IEEE Trans. Acoust. Speech Signal Process., Vol. ASSP-38, No. 2, |
| February 1990. |
| |
| We use the Alternatively Updated MDF (AUMDF) variant. Robustness to |
| double-talk is achieved using a variable learning rate as described in: |
| |
| Valin, J.-M., On Adjusting the Learning Rate in Frequency Domain Echo |
| Cancellation With Double-Talk. Submitted to IEEE Transactions on Speech |
| and Audio Processing, 2006. |
| |
| There is no explicit double-talk detection, but a continuous variation |
| in the learning rate based on residual echo, double-talk and background |
| noise. |
| |
| About the fixed-point version: |
| All the signals are represented with 16-bit words. The filter weights |
| are represented with 32-bit words, but only the top 16 bits are used |
| in most cases. The lower 16 bits are completely unreliable (due to the |
| fact that the update is done only on the top bits), but help in the |
| adaptation -- probably by removing a "threshold effect" due to |
| quantization (rounding going to zero) when the gradient is small. |
| |
| Another kludge that seems to work good: when performing the weight |
| update, we only move half the way toward the "goal" this seems to |
| reduce the effect of quantization noise in the update phase. This |
| can be seen as applying a gradient descent on a "soft constraint" |
| instead of having a hard constraint. |
| |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "misc.h" |
| #include "speex/speex_echo.h" |
| #include "fftwrap.h" |
| #include "pseudofloat.h" |
| #include "math_approx.h" |
| |
| #ifndef M_PI |
| #define M_PI 3.14159265358979323846 |
| #endif |
| |
| #define min(a,b) ((a)<(b) ? (a) : (b)) |
| #define max(a,b) ((a)>(b) ? (a) : (b)) |
| |
| #ifdef FIXED_POINT |
| #define WEIGHT_SHIFT 11 |
| #define NORMALIZE_SCALEDOWN 5 |
| #define NORMALIZE_SCALEUP 3 |
| #else |
| #define WEIGHT_SHIFT 0 |
| #endif |
| |
| #ifdef FIXED_POINT |
| static const spx_float_t MIN_LEAK = ((spx_float_t){16777, -24}); |
| #define TOP16(x) ((x)>>16) |
| #else |
| static const spx_float_t MIN_LEAK = .001f; |
| #define TOP16(x) (x) |
| #endif |
| |
| |
| /** Speex echo cancellation state. */ |
| struct SpeexEchoState_ { |
| int frame_size; /**< Number of samples processed each time */ |
| int window_size; |
| int M; |
| int cancel_count; |
| int adapted; |
| spx_int32_t sampling_rate; |
| spx_word16_t spec_average; |
| spx_word16_t beta0; |
| spx_word16_t beta_max; |
| spx_word32_t sum_adapt; |
| spx_word16_t *e; |
| spx_word16_t *x; |
| spx_word16_t *X; |
| spx_word16_t *d; |
| spx_word16_t *y; |
| spx_word16_t *last_y; |
| spx_word32_t *Yps; |
| spx_word16_t *Y; |
| spx_word16_t *E; |
| spx_word32_t *PHI; |
| spx_word32_t *W; |
| spx_word32_t *power; |
| spx_float_t *power_1; |
| spx_word16_t *wtmp; |
| #ifdef FIXED_POINT |
| spx_word16_t *wtmp2; |
| #endif |
| spx_word32_t *Rf; |
| spx_word32_t *Yf; |
| spx_word32_t *Xf; |
| spx_word32_t *Eh; |
| spx_word32_t *Yh; |
| spx_float_t Pey; |
| spx_float_t Pyy; |
| spx_word16_t *window; |
| void *fft_table; |
| spx_word16_t memX, memD, memE; |
| spx_word16_t preemph; |
| spx_word16_t notch_radius; |
| spx_mem_t notch_mem[2]; |
| }; |
| |
| static inline void filter_dc_notch16(spx_int16_t *in, spx_word16_t radius, spx_word16_t *out, int len, spx_mem_t *mem) |
| { |
| int i; |
| spx_word16_t den2; |
| #ifdef FIXED_POINT |
| den2 = MULT16_16_Q15(radius,radius) + MULT16_16_Q15(QCONST16(.7,15),MULT16_16_Q15(32767-radius,32767-radius)); |
| #else |
| den2 = radius*radius + .7*(1-radius)*(1-radius); |
| #endif |
| /*printf ("%d %d %d %d %d %d\n", num[0], num[1], num[2], den[0], den[1], den[2]);*/ |
| for (i=0;i<len;i++) |
| { |
| spx_word16_t vin = in[i]; |
| spx_word32_t vout = mem[0] + SHL32(EXTEND32(vin),15); |
| #ifdef FIXED_POINT |
| mem[0] = mem[1] + SHL32(SHL32(-EXTEND32(vin),15) + MULT16_32_Q15(radius,vout),1); |
| #else |
| mem[0] = mem[1] + 2*(-vin + radius*vout); |
| #endif |
| mem[1] = SHL32(EXTEND32(vin),15) - MULT16_32_Q15(den2,vout); |
| out[i] = SATURATE32(PSHR32(MULT16_32_Q15(radius,vout),15),32767); |
| } |
| } |
| |
| static inline spx_word32_t inner_prod(const spx_word16_t *x, const spx_word16_t *y, int len) |
| { |
| spx_word32_t sum=0; |
| len >>= 2; |
| while(len--) |
| { |
| spx_word32_t part=0; |
| part = MAC16_16(part,*x++,*y++); |
| part = MAC16_16(part,*x++,*y++); |
| part = MAC16_16(part,*x++,*y++); |
| part = MAC16_16(part,*x++,*y++); |
| /* HINT: If you had a 40-bit accumulator, you could shift only at the end */ |
| sum = ADD32(sum,SHR32(part,6)); |
| } |
| return sum; |
| } |
| |
| /** Compute power spectrum of a half-complex (packed) vector */ |
| static inline void power_spectrum(spx_word16_t *X, spx_word32_t *ps, int N) |
| { |
| int i, j; |
| ps[0]=MULT16_16(X[0],X[0]); |
| for (i=1,j=1;i<N-1;i+=2,j++) |
| { |
| ps[j] = MULT16_16(X[i],X[i]) + MULT16_16(X[i+1],X[i+1]); |
| } |
| ps[j]=MULT16_16(X[i],X[i]); |
| } |
| |
| /** Compute cross-power spectrum of a half-complex (packed) vectors and add to acc */ |
| #ifdef FIXED_POINT |
| static inline void spectral_mul_accum(spx_word16_t *X, spx_word32_t *Y, spx_word16_t *acc, int N, int M) |
| { |
| int i,j; |
| spx_word32_t tmp1=0,tmp2=0; |
| for (j=0;j<M;j++) |
| { |
| tmp1 = MAC16_16(tmp1, X[j*N],TOP16(Y[j*N])); |
| } |
| acc[0] = PSHR32(tmp1,WEIGHT_SHIFT); |
| for (i=1;i<N-1;i+=2) |
| { |
| tmp1 = tmp2 = 0; |
| for (j=0;j<M;j++) |
| { |
| tmp1 = SUB32(MAC16_16(tmp1, X[j*N+i],TOP16(Y[j*N+i])), MULT16_16(X[j*N+i+1],TOP16(Y[j*N+i+1]))); |
| tmp2 = MAC16_16(MAC16_16(tmp2, X[j*N+i+1],TOP16(Y[j*N+i])), X[j*N+i], TOP16(Y[j*N+i+1])); |
| } |
| acc[i] = PSHR32(tmp1,WEIGHT_SHIFT); |
| acc[i+1] = PSHR32(tmp2,WEIGHT_SHIFT); |
| } |
| tmp1 = tmp2 = 0; |
| for (j=0;j<M;j++) |
| { |
| tmp1 = MAC16_16(tmp1, X[(j+1)*N-1],TOP16(Y[(j+1)*N-1])); |
| } |
| acc[N-1] = PSHR32(tmp1,WEIGHT_SHIFT); |
| } |
| #else |
| static inline void spectral_mul_accum(spx_word16_t *X, spx_word32_t *Y, spx_word16_t *acc, int N, int M) |
| { |
| int i,j; |
| for (i=0;i<N;i++) |
| acc[i] = 0; |
| for (j=0;j<M;j++) |
| { |
| acc[0] += X[0]*Y[0]; |
| for (i=1;i<N-1;i+=2) |
| { |
| acc[i] += (X[i]*Y[i] - X[i+1]*Y[i+1]); |
| acc[i+1] += (X[i+1]*Y[i] + X[i]*Y[i+1]); |
| } |
| acc[i] += X[i]*Y[i]; |
| X += N; |
| Y += N; |
| } |
| } |
| #endif |
| |
| /** Compute weighted cross-power spectrum of a half-complex (packed) vector with conjugate */ |
| static inline void weighted_spectral_mul_conj(spx_float_t *w, spx_word16_t *X, spx_word16_t *Y, spx_word32_t *prod, int N) |
| { |
| int i, j; |
| prod[0] = FLOAT_MUL32(w[0],MULT16_16(X[0],Y[0])); |
| for (i=1,j=1;i<N-1;i+=2,j++) |
| { |
| prod[i] = FLOAT_MUL32(w[j],MAC16_16(MULT16_16(X[i],Y[i]), X[i+1],Y[i+1])); |
| prod[i+1] = FLOAT_MUL32(w[j],MAC16_16(MULT16_16(-X[i+1],Y[i]), X[i],Y[i+1])); |
| } |
| prod[i] = FLOAT_MUL32(w[j],MULT16_16(X[i],Y[i])); |
| } |
| |
| |
| /** Creates a new echo canceller state */ |
| SpeexEchoState *speex_echo_state_init(int frame_size, int filter_length) |
| { |
| int i,N,M; |
| SpeexEchoState *st = (SpeexEchoState *)speex_alloc(sizeof(SpeexEchoState)); |
| |
| st->frame_size = frame_size; |
| st->window_size = 2*frame_size; |
| N = st->window_size; |
| M = st->M = (filter_length+st->frame_size-1)/frame_size; |
| st->cancel_count=0; |
| st->sum_adapt = 0; |
| /* FIXME: Make that an init option (new API call?) */ |
| st->sampling_rate = 8000; |
| st->spec_average = DIV32_16(SHL32(st->frame_size, 15), st->sampling_rate); |
| #ifdef FIXED_POINT |
| st->beta0 = DIV32_16(SHL32(st->frame_size, 16), st->sampling_rate); |
| st->beta_max = DIV32_16(SHL32(st->frame_size, 14), st->sampling_rate); |
| #else |
| st->beta0 = (2.0f*st->frame_size)/st->sampling_rate; |
| st->beta_max = (.5f*st->frame_size)/st->sampling_rate; |
| #endif |
| |
| st->fft_table = spx_fft_init(N); |
| |
| st->e = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); |
| st->x = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); |
| st->d = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); |
| st->y = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); |
| st->Yps = (spx_word32_t*)speex_alloc(N*sizeof(spx_word32_t)); |
| st->last_y = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); |
| st->Yf = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); |
| st->Rf = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); |
| st->Xf = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); |
| st->Yh = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); |
| st->Eh = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); |
| |
| st->X = (spx_word16_t*)speex_alloc(M*N*sizeof(spx_word16_t)); |
| st->Y = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); |
| st->E = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); |
| st->W = (spx_word32_t*)speex_alloc(M*N*sizeof(spx_word32_t)); |
| st->PHI = (spx_word32_t*)speex_alloc(M*N*sizeof(spx_word32_t)); |
| st->power = (spx_word32_t*)speex_alloc((frame_size+1)*sizeof(spx_word32_t)); |
| st->power_1 = (spx_float_t*)speex_alloc((frame_size+1)*sizeof(spx_float_t)); |
| st->window = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); |
| st->wtmp = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); |
| #ifdef FIXED_POINT |
| st->wtmp2 = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); |
| for (i=0;i<N>>1;i++) |
| { |
| st->window[i] = (16383-SHL16(spx_cos(DIV32_16(MULT16_16(25736,i<<1),N)),1)); |
| st->window[N-i-1] = st->window[i]; |
| } |
| #else |
| for (i=0;i<N;i++) |
| st->window[i] = .5-.5*cos(2*M_PI*i/N); |
| #endif |
| for (i=0;i<N*M;i++) |
| { |
| st->W[i] = st->PHI[i] = 0; |
| } |
| st->memX=st->memD=st->memE=0; |
| st->preemph = QCONST16(.9,15); |
| if (st->sampling_rate<12000) |
| st->notch_radius = QCONST16(.9, 15); |
| else if (st->sampling_rate<24000) |
| st->notch_radius = QCONST16(.982, 15); |
| else |
| st->notch_radius = QCONST16(.992, 15); |
| |
| st->notch_mem[0] = st->notch_mem[1] = 0; |
| st->adapted = 0; |
| st->Pey = st->Pyy = FLOAT_ONE; |
| return st; |
| } |
| |
| /** Resets echo canceller state */ |
| void speex_echo_state_reset(SpeexEchoState *st) |
| { |
| int i, M, N; |
| st->cancel_count=0; |
| N = st->window_size; |
| M = st->M; |
| for (i=0;i<N*M;i++) |
| { |
| st->W[i] = 0; |
| st->X[i] = 0; |
| } |
| for (i=0;i<=st->frame_size;i++) |
| st->power[i] = 0; |
| |
| st->adapted = 0; |
| st->sum_adapt = 0; |
| st->Pey = st->Pyy = FLOAT_ONE; |
| |
| } |
| |
| /** Destroys an echo canceller state */ |
| void speex_echo_state_destroy(SpeexEchoState *st) |
| { |
| spx_fft_destroy(st->fft_table); |
| |
| speex_free(st->e); |
| speex_free(st->x); |
| speex_free(st->d); |
| speex_free(st->y); |
| speex_free(st->last_y); |
| speex_free(st->Yps); |
| speex_free(st->Yf); |
| speex_free(st->Rf); |
| speex_free(st->Xf); |
| speex_free(st->Yh); |
| speex_free(st->Eh); |
| |
| speex_free(st->X); |
| speex_free(st->Y); |
| speex_free(st->E); |
| speex_free(st->W); |
| speex_free(st->PHI); |
| speex_free(st->power); |
| speex_free(st->power_1); |
| speex_free(st->window); |
| speex_free(st->wtmp); |
| #ifdef FIXED_POINT |
| speex_free(st->wtmp2); |
| #endif |
| speex_free(st); |
| } |
| |
| extern int fixed_point; |
| /** Performs echo cancellation on a frame */ |
| void speex_echo_cancel(SpeexEchoState *st, short *ref, short *echo, short *out, spx_int32_t *Yout) |
| { |
| int i,j; |
| int N,M; |
| spx_word32_t Syy,See; |
| spx_word16_t leak_estimate; |
| spx_word16_t ss, ss_1; |
| spx_float_t Pey = FLOAT_ONE, Pyy=FLOAT_ONE; |
| spx_float_t alpha, alpha_1; |
| spx_word16_t RER; |
| spx_word32_t tmp32; |
| spx_word16_t M_1; |
| |
| N = st->window_size; |
| M = st->M; |
| st->cancel_count++; |
| #ifdef FIXED_POINT |
| ss=DIV32_16(11469,M); |
| ss_1 = SUB16(32767,ss); |
| M_1 = DIV32_16(32767,M); |
| #else |
| ss=.35/M; |
| ss_1 = 1-ss; |
| M_1 = 1.f/M; |
| #endif |
| |
| filter_dc_notch16(ref, st->notch_radius, st->d, st->frame_size, st->notch_mem); |
| /* Copy input data to buffer */ |
| for (i=0;i<st->frame_size;i++) |
| { |
| spx_word16_t tmp; |
| st->x[i] = st->x[i+st->frame_size]; |
| st->x[i+st->frame_size] = SUB16(echo[i], MULT16_16_P15(st->preemph, st->memX)); |
| st->memX = echo[i]; |
| |
| tmp = st->d[i]; |
| st->d[i] = st->d[i+st->frame_size]; |
| st->d[i+st->frame_size] = SUB16(tmp, MULT16_16_P15(st->preemph, st->memD)); |
| st->memD = tmp; |
| } |
| |
| /* Shift memory: this could be optimized eventually*/ |
| for (i=0;i<N*(M-1);i++) |
| st->X[i]=st->X[i+N]; |
| |
| /* Convert x (echo input) to frequency domain */ |
| spx_fft(st->fft_table, st->x, &st->X[(M-1)*N]); |
| |
| /* Compute filter response Y */ |
| spectral_mul_accum(st->X, st->W, st->Y, N, M); |
| |
| spx_ifft(st->fft_table, st->Y, st->y); |
| |
| #if 1 |
| spectral_mul_accum(st->X, st->PHI, st->Y, N, M); |
| spx_ifft(st->fft_table, st->Y, st->e); |
| #endif |
| |
| /* Compute error signal (for the output with de-emphasis) */ |
| for (i=0;i<st->frame_size;i++) |
| { |
| spx_word32_t tmp_out; |
| #if 1 |
| spx_word16_t y = MULT16_16_Q15(st->window[i+st->frame_size],st->e[i+st->frame_size]) + MULT16_16_Q15(st->window[i],st->y[i+st->frame_size]); |
| tmp_out = SUB32(EXTEND32(st->d[i+st->frame_size]), EXTEND32(y)); |
| #else |
| tmp_out = SUB32(EXTEND32(st->d[i+st->frame_size]), EXTEND32(st->y[i+st->frame_size])); |
| #endif |
| |
| /* Saturation */ |
| if (tmp_out>32767) |
| tmp_out = 32767; |
| else if (tmp_out<-32768) |
| tmp_out = -32768; |
| tmp_out = ADD32(tmp_out, EXTEND32(MULT16_16_P15(st->preemph, st->memE))); |
| out[i] = tmp_out; |
| st->memE = tmp_out; |
| } |
| |
| /* Compute error signal (filter update version) */ |
| for (i=0;i<st->frame_size;i++) |
| { |
| st->e[i] = 0; |
| st->e[i+st->frame_size] = st->d[i+st->frame_size] - st->y[i+st->frame_size]; |
| } |
| |
| /* Compute a bunch of correlations */ |
| See = inner_prod(st->e+st->frame_size, st->e+st->frame_size, st->frame_size); |
| See = ADD32(See, SHR32(10000,6)); |
| Syy = inner_prod(st->y+st->frame_size, st->y+st->frame_size, st->frame_size); |
| |
| /* Convert error to frequency domain */ |
| spx_fft(st->fft_table, st->e, st->E); |
| for (i=0;i<st->frame_size;i++) |
| st->y[i] = 0; |
| spx_fft(st->fft_table, st->y, st->Y); |
| |
| /* Compute power spectrum of echo (X), error (E) and filter response (Y) */ |
| power_spectrum(st->E, st->Rf, N); |
| power_spectrum(st->Y, st->Yf, N); |
| power_spectrum(&st->X[(M-1)*N], st->Xf, N); |
| |
| /* Smooth echo energy estimate over time */ |
| for (j=0;j<=st->frame_size;j++) |
| st->power[j] = MULT16_32_Q15(ss_1,st->power[j]) + 1 + MULT16_32_Q15(ss,st->Xf[j]); |
| |
| /* Enable this to compute the power based only on the tail (would need to compute more |
| efficiently to make this really useful */ |
| if (0) |
| { |
| float scale2 = .5f/M; |
| for (j=0;j<=st->frame_size;j++) |
| st->power[j] = 0; |
| for (i=0;i<M;i++) |
| { |
| power_spectrum(&st->X[i*N], st->Xf, N); |
| for (j=0;j<=st->frame_size;j++) |
| st->power[j] += scale2*st->Xf[j]; |
| } |
| } |
| |
| /* Compute filtered spectra and (cross-)correlations */ |
| for (j=st->frame_size;j>=0;j--) |
| { |
| spx_float_t Eh, Yh; |
| Eh = PSEUDOFLOAT(st->Rf[j] - st->Eh[j]); |
| Yh = PSEUDOFLOAT(st->Yf[j] - st->Yh[j]); |
| Pey = FLOAT_ADD(Pey,FLOAT_MULT(Eh,Yh)); |
| Pyy = FLOAT_ADD(Pyy,FLOAT_MULT(Yh,Yh)); |
| #ifdef FIXED_POINT |
| st->Eh[j] = MAC16_32_Q15(MULT16_32_Q15(SUB16(32767,st->spec_average),st->Eh[j]), st->spec_average, st->Rf[j]); |
| st->Yh[j] = MAC16_32_Q15(MULT16_32_Q15(SUB16(32767,st->spec_average),st->Yh[j]), st->spec_average, st->Yf[j]); |
| #else |
| st->Eh[j] = (1-st->spec_average)*st->Eh[j] + st->spec_average*st->Rf[j]; |
| st->Yh[j] = (1-st->spec_average)*st->Yh[j] + st->spec_average*st->Yf[j]; |
| #endif |
| } |
| |
| /* Compute correlation updatete rate */ |
| tmp32 = MULT16_32_Q15(st->beta0,Syy); |
| if (tmp32 > MULT16_32_Q15(st->beta_max,See)) |
| tmp32 = MULT16_32_Q15(st->beta_max,See); |
| alpha = FLOAT_DIV32(tmp32, See); |
| alpha_1 = FLOAT_SUB(FLOAT_ONE, alpha); |
| /* Update correlations (recursive average) */ |
| st->Pey = FLOAT_ADD(FLOAT_MULT(alpha_1,st->Pey) , FLOAT_MULT(alpha,Pey)); |
| st->Pyy = FLOAT_ADD(FLOAT_MULT(alpha_1,st->Pyy) , FLOAT_MULT(alpha,Pyy)); |
| if (FLOAT_LT(st->Pyy, FLOAT_ONE)) |
| st->Pyy = FLOAT_ONE; |
| /* We don't really hope to get better than 33 dB (MIN_LEAK-3dB) attenuation anyway */ |
| if (FLOAT_LT(st->Pey, FLOAT_MULT(MIN_LEAK,st->Pyy))) |
| st->Pey = FLOAT_MULT(MIN_LEAK,st->Pyy); |
| if (FLOAT_GT(st->Pey, st->Pyy)) |
| st->Pey = st->Pyy; |
| /* leak_estimate is the limear regression result */ |
| leak_estimate = FLOAT_EXTRACT16(FLOAT_SHL(FLOAT_DIVU(st->Pey, st->Pyy),14)); |
| if (leak_estimate > 16383) |
| leak_estimate = 32767; |
| else |
| leak_estimate = SHL16(leak_estimate,1); |
| /*printf ("%f\n", leak_estimate);*/ |
| |
| /* Compute Residual to Error Ratio */ |
| #ifdef FIXED_POINT |
| tmp32 = MULT16_32_Q15(leak_estimate,Syy); |
| tmp32 = ADD32(tmp32, SHL32(tmp32,1)); |
| if (tmp32 > SHR32(See,1)) |
| tmp32 = SHR32(See,1); |
| RER = FLOAT_EXTRACT16(FLOAT_SHL(FLOAT_DIV32(tmp32,See),15)); |
| #else |
| RER = 3.*MULT16_32_Q15(leak_estimate,Syy) / See; |
| if (RER > .5) |
| RER = .5; |
| #endif |
| |
| /* We consider that the filter has had minimal adaptation if the following is true*/ |
| if (!st->adapted && st->sum_adapt > QCONST32(1,15)) |
| { |
| st->adapted = 1; |
| } |
| |
| if (st->adapted) |
| { |
| for (i=0;i<=st->frame_size;i++) |
| { |
| spx_word32_t r, e; |
| /* Compute frequency-domain adaptation mask */ |
| r = MULT16_32_Q15(leak_estimate,SHL32(st->Yf[i],3)); |
| e = SHL32(st->Rf[i],3)+1; |
| #ifdef FIXED_POINT |
| if (r>SHR32(e,1)) |
| r = SHR32(e,1); |
| #else |
| if (r>.5*e) |
| r = .5*e; |
| #endif |
| r = MULT16_32_Q15(QCONST16(.8,15),r) + MULT16_32_Q15(QCONST16(.2,15),(spx_word32_t)(MULT16_32_Q15(RER,e))); |
| /*st->power_1[i] = adapt_rate*r/(e*(1+st->power[i]));*/ |
| st->power_1[i] = FLOAT_SHL(FLOAT_DIV32_FLOAT(MULT16_32_Q15(M_1,r),FLOAT_MUL32U(e,st->power[i]+10)),WEIGHT_SHIFT+16); |
| } |
| } else { |
| spx_word32_t Sxx; |
| spx_word16_t adapt_rate=0; |
| |
| Sxx = inner_prod(st->x+st->frame_size, st->x+st->frame_size, st->frame_size); |
| /* Temporary adaption rate if filter is not adapted correctly */ |
| |
| tmp32 = MULT16_32_Q15(QCONST16(.15f, 15), Sxx); |
| #ifdef FIXED_POINT |
| if (Sxx > SHR32(See,2)) |
| Sxx = SHR32(See,2); |
| #else |
| if (Sxx > .25*See) |
| Sxx = .25*See; |
| #endif |
| adapt_rate = FLOAT_EXTRACT16(FLOAT_SHL(FLOAT_DIV32(MULT16_32_Q15(M_1,Sxx), See),15)); |
| |
| for (i=0;i<=st->frame_size;i++) |
| st->power_1[i] = FLOAT_SHL(FLOAT_DIV32(EXTEND32(adapt_rate),ADD32(st->power[i],10)),WEIGHT_SHIFT+1); |
| |
| |
| /* How much have we adapted so far? */ |
| st->sum_adapt = ADD32(st->sum_adapt,adapt_rate); |
| } |
| /* Compute weight gradient */ |
| for (j=0;j<M;j++) |
| { |
| weighted_spectral_mul_conj(st->power_1, &st->X[j*N], st->E, st->PHI+N*j, N); |
| } |
| |
| /* Gradient descent */ |
| for (i=0;i<M*N;i++) |
| { |
| st->W[i] += st->PHI[i]; |
| /* Old value of W in PHI */ |
| st->PHI[i] = st->W[i] - st->PHI[i]; |
| } |
| |
| /* Update weight to prevent circular convolution (MDF / AUMDF) */ |
| for (j=0;j<M;j++) |
| { |
| /* This is a variant of the Alternatively Updated MDF (AUMDF) */ |
| /* Remove the "if" to make this an MDF filter */ |
| if (j==M-1 || st->cancel_count%(M-1) == j) |
| { |
| #ifdef FIXED_POINT |
| for (i=0;i<N;i++) |
| st->wtmp2[i] = PSHR32(st->W[j*N+i],NORMALIZE_SCALEDOWN+16); |
| spx_ifft(st->fft_table, st->wtmp2, st->wtmp); |
| for (i=0;i<st->frame_size;i++) |
| { |
| st->wtmp[i]=0; |
| } |
| for (i=st->frame_size;i<N;i++) |
| { |
| st->wtmp[i]=SHL(st->wtmp[i],NORMALIZE_SCALEUP); |
| } |
| spx_fft(st->fft_table, st->wtmp, st->wtmp2); |
| /* The "-1" in the shift is a sort of kludge that trades less efficient update speed for decrease noise */ |
| for (i=0;i<N;i++) |
| st->W[j*N+i] -= SHL32(st->wtmp2[i],16+NORMALIZE_SCALEDOWN-NORMALIZE_SCALEUP-1); |
| #else |
| spx_ifft(st->fft_table, &st->W[j*N], st->wtmp); |
| for (i=st->frame_size;i<N;i++) |
| { |
| st->wtmp[i]=0; |
| } |
| spx_fft(st->fft_table, st->wtmp, &st->W[j*N]); |
| #endif |
| } |
| } |
| |
| /* Compute spectrum of estimated echo for use in an echo post-filter (if necessary)*/ |
| if (Yout) |
| { |
| spx_word16_t leak2; |
| if (st->adapted) |
| { |
| /* If the filter is adapted, take the filtered echo */ |
| for (i=0;i<st->frame_size;i++) |
| st->last_y[i] = st->last_y[st->frame_size+i]; |
| for (i=0;i<st->frame_size;i++) |
| st->last_y[st->frame_size+i] = ref[i]-out[i]; |
| } else { |
| /* If filter isn't adapted yet, all we can do is take the echo signal directly */ |
| for (i=0;i<N;i++) |
| st->last_y[i] = st->x[i]; |
| } |
| |
| /* Apply hanning window (should pre-compute it)*/ |
| for (i=0;i<N;i++) |
| st->y[i] = MULT16_16_Q15(st->window[i],st->last_y[i]); |
| |
| /* Compute power spectrum of the echo */ |
| spx_fft(st->fft_table, st->y, st->Y); |
| power_spectrum(st->Y, st->Yps, N); |
| |
| #ifdef FIXED_POINT |
| if (leak_estimate > 16383) |
| leak2 = 32767; |
| else |
| leak2 = SHL16(leak_estimate, 1); |
| #else |
| if (leak_estimate>.5) |
| leak2 = 1; |
| else |
| leak2 = 2*leak_estimate; |
| #endif |
| /* Estimate residual echo */ |
| for (i=0;i<=st->frame_size;i++) |
| Yout[i] = MULT16_32_Q15(leak2,st->Yps[i]); |
| } |
| } |
| |
| |
| int speex_echo_ctl(SpeexEchoState *st, int request, void *ptr) |
| { |
| switch(request) |
| { |
| |
| case SPEEX_ECHO_GET_FRAME_SIZE: |
| (*(int*)ptr) = st->frame_size; |
| break; |
| case SPEEX_ECHO_SET_SAMPLING_RATE: |
| st->sampling_rate = (*(int*)ptr); |
| st->spec_average = DIV32_16(SHL32(st->frame_size, 15), st->sampling_rate); |
| #ifdef FIXED_POINT |
| st->beta0 = DIV32_16(SHL32(st->frame_size, 16), st->sampling_rate); |
| st->beta_max = DIV32_16(SHL32(st->frame_size, 14), st->sampling_rate); |
| #else |
| st->beta0 = (2.0f*st->frame_size)/st->sampling_rate; |
| st->beta_max = (.5f*st->frame_size)/st->sampling_rate; |
| #endif |
| if (st->sampling_rate<12000) |
| st->notch_radius = QCONST16(.9, 15); |
| else if (st->sampling_rate<24000) |
| st->notch_radius = QCONST16(.982, 15); |
| else |
| st->notch_radius = QCONST16(.992, 15); |
| break; |
| case SPEEX_ECHO_GET_SAMPLING_RATE: |
| (*(int*)ptr) = st->sampling_rate; |
| break; |
| default: |
| speex_warning_int("Unknown speex_echo_ctl request: ", request); |
| return -1; |
| } |
| return 0; |
| } |