Mercurial > hg > audiostuff
comparison intercom/aec.cpp @ 2:13be24d74cd2
import intercom-0.4.1
author | Peter Meerwald <pmeerw@cosy.sbg.ac.at> |
---|---|
date | Fri, 25 Jun 2010 09:57:52 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
1:9cadc470e3da | 2:13be24d74cd2 |
---|---|
1 /* aec.cpp | |
2 * | |
3 * Copyright (C) DFS Deutsche Flugsicherung (2004, 2005). | |
4 * All Rights Reserved. | |
5 * | |
6 * Acoustic Echo Cancellation NLMS-pw algorithm | |
7 * | |
8 * Version 0.3 filter created with www.dsptutor.freeuk.com | |
9 * Version 0.3.1 Allow change of stability parameter delta | |
10 * Version 0.4 Leaky Normalized LMS - pre whitening algorithm | |
11 */ | |
12 | |
13 #include <stdio.h> | |
14 #include <stdlib.h> | |
15 #include <math.h> | |
16 #include <string.h> | |
17 #include <unistd.h> | |
18 | |
19 #include "oss.h" | |
20 #include "aec.h" | |
21 #include "intercomd.h" | |
22 | |
23 #include "tcp.h" | |
24 | |
25 /* Vector Dot Product */ | |
26 REAL dotp(REAL a[], REAL b[]) | |
27 { | |
28 REAL sum0 = 0.0, sum1 = 0.0; | |
29 int j; | |
30 | |
31 for (j = 0; j < NLMS_LEN; j += 2) { | |
32 // optimize: partial loop unrolling | |
33 sum0 += a[j] * b[j]; | |
34 sum1 += a[j + 1] * b[j + 1]; | |
35 } | |
36 return sum0 + sum1; | |
37 } | |
38 | |
39 | |
40 AEC::AEC() | |
41 { | |
42 hangover = 0; | |
43 memset(x, 0, sizeof(x)); | |
44 memset(xf, 0, sizeof(xf)); | |
45 memset(w, 0, sizeof(w)); | |
46 j = NLMS_EXT; | |
47 delta = 0.0f; | |
48 setambient(NoiseFloor); | |
49 dfast = dslow = M75dB_PCM; | |
50 xfast = xslow = M80dB_PCM; | |
51 gain = 1.0f; | |
52 Fx.init(2000.0f/RATE); | |
53 Fe.init(2000.0f/RATE); | |
54 | |
55 aes_y2 = M0dB; | |
56 | |
57 fdwdisplay = -1; | |
58 dumpcnt = 0; | |
59 memset(ws, 0, sizeof(ws)); | |
60 } | |
61 | |
62 // Adrian soft decision DTD | |
63 // (Dual Average Near-End to Far-End signal Ratio DTD) | |
64 // This algorithm uses exponential smoothing with differnt | |
65 // ageing parameters to get fast and slow near-end and far-end | |
66 // signal averages. The ratio of NFRs term | |
67 // (dfast / xfast) / (dslow / xslow) is used to compute the stepsize | |
68 // A ratio value of 2.5 is mapped to stepsize 0, a ratio of 0 is | |
69 // mapped to 1.0 with a limited linear function. | |
70 inline float AEC::dtd(REAL d, REAL x) | |
71 { | |
72 float stepsize; | |
73 | |
74 // fast near-end and far-end average | |
75 dfast += ALPHAFAST * (fabsf(d) - dfast); | |
76 xfast += ALPHAFAST * (fabsf(x) - xfast); | |
77 | |
78 // slow near-end and far-end average | |
79 dslow += ALPHASLOW * (fabsf(d) - dslow); | |
80 xslow += ALPHASLOW * (fabsf(x) - xslow); | |
81 | |
82 if (xfast < M70dB_PCM) { | |
83 return 0.0; // no Spk signal | |
84 } | |
85 | |
86 if (dfast < M70dB_PCM) { | |
87 return 0.0; // no Mic signal | |
88 } | |
89 | |
90 // ratio of NFRs | |
91 float ratio = (dfast * xslow) / (dslow * xfast); | |
92 | |
93 // begrenzte lineare Kennlinie | |
94 const float M = (STEPY2 - STEPY1) / (STEPX2 - STEPX1); | |
95 if (ratio < STEPX1) { | |
96 stepsize = STEPY1; | |
97 } else if (ratio > STEPX2) { | |
98 stepsize = STEPY2; | |
99 } else { | |
100 // Punktrichtungsform einer Geraden | |
101 stepsize = M * (ratio - STEPX1) + STEPY1; | |
102 } | |
103 | |
104 return stepsize; | |
105 } | |
106 | |
107 | |
108 inline void AEC::leaky() | |
109 // The xfast signal is used to charge the hangover timer to Thold. | |
110 // When hangover expires (no Spk signal for some time) the vector w | |
111 // is erased. This is my implementation of Leaky NLMS. | |
112 { | |
113 if (xfast >= M70dB_PCM) { | |
114 // vector w is valid for hangover Thold time | |
115 hangover = Thold; | |
116 } else { | |
117 if (hangover > 1) { | |
118 --hangover; | |
119 } else if (1 == hangover) { | |
120 --hangover; | |
121 // My Leaky NLMS is to erase vector w when hangover expires | |
122 memset(w, 0, sizeof(w)); | |
123 } | |
124 } | |
125 } | |
126 | |
127 | |
128 void AEC::openwdisplay() { | |
129 // open TCP connection to program wdisplay.tcl | |
130 fdwdisplay = socket_async("127.0.0.1", 50999); | |
131 }; | |
132 | |
133 | |
134 inline REAL AEC::nlms_pw(REAL d, REAL x_, float stepsize) | |
135 { | |
136 x[j] = x_; | |
137 xf[j] = Fx.highpass(x_); // pre-whitening of x | |
138 | |
139 // calculate error value | |
140 // (mic signal - estimated mic signal from spk signal) | |
141 REAL e = d; | |
142 if (hangover > 0) { | |
143 e -= dotp(w, x + j); | |
144 } | |
145 REAL ef = Fe.highpass(e); // pre-whitening of e | |
146 | |
147 // optimize: iterative dotp(xf, xf) | |
148 dotp_xf_xf += (xf[j] * xf[j] - xf[j + NLMS_LEN - 1] * xf[j + NLMS_LEN - 1]); | |
149 | |
150 if (stepsize > 0.0) { | |
151 // calculate variable step size | |
152 REAL mikro_ef = stepsize * ef / dotp_xf_xf; | |
153 | |
154 // update tap weights (filter learning) | |
155 int i; | |
156 for (i = 0; i < NLMS_LEN; i += 2) { | |
157 // optimize: partial loop unrolling | |
158 w[i] += mikro_ef * xf[i + j]; | |
159 w[i + 1] += mikro_ef * xf[i + j + 1]; | |
160 } | |
161 } | |
162 | |
163 if (--j < 0) { | |
164 // optimize: decrease number of memory copies | |
165 j = NLMS_EXT; | |
166 memmove(x + j + 1, x, (NLMS_LEN - 1) * sizeof(REAL)); | |
167 memmove(xf + j + 1, xf, (NLMS_LEN - 1) * sizeof(REAL)); | |
168 } | |
169 | |
170 // Saturation | |
171 if (e > MAXPCM) { | |
172 return MAXPCM; | |
173 } else if (e < -MAXPCM) { | |
174 return -MAXPCM; | |
175 } else { | |
176 return e; | |
177 } | |
178 } | |
179 | |
180 | |
181 int AEC::doAEC(int d_, int x_) | |
182 { | |
183 REAL d = (REAL) d_; | |
184 REAL x = (REAL) x_; | |
185 | |
186 // Mic Highpass Filter - to remove DC | |
187 d = acMic.highpass(d); | |
188 | |
189 // Mic Highpass Filter - cut-off below 300Hz | |
190 d = cutoff.highpass(d); | |
191 | |
192 // Amplify, for e.g. Soundcards with -6dB max. volume | |
193 d *= gain; | |
194 | |
195 // Spk Highpass Filter - to remove DC | |
196 x = acSpk.highpass(x); | |
197 | |
198 // Double Talk Detector | |
199 stepsize = dtd(d, x); | |
200 | |
201 // Leaky (ageing of vector w) | |
202 leaky(); | |
203 | |
204 // Acoustic Echo Cancellation | |
205 d = nlms_pw(d, x, stepsize); | |
206 | |
207 if (fdwdisplay >= 0) { | |
208 if (++dumpcnt >= (WIDEB*8000/10)) { | |
209 // wdisplay creates 10 dumps per seconds = large CPU load! | |
210 dumpcnt = 0; | |
211 write(fdwdisplay, ws, DUMP_LEN*sizeof(float)); | |
212 // we don't check return value. This is not production quality!!! | |
213 memset(ws, 0, sizeof(ws)); | |
214 } else { | |
215 int i; | |
216 for (i = 0; i < DUMP_LEN; i += 2) { | |
217 // optimize: partial loop unrolling | |
218 ws[i] += w[i]; | |
219 ws[i + 1] += w[i + 1]; | |
220 } | |
221 } | |
222 } | |
223 | |
224 return (int) d; | |
225 } |