Mercurial > hg > audiostuff
comparison spandsp-0.0.6pre17/tests/modem_echo_tests.c @ 4:26cd8f1ef0b1
import spandsp-0.0.6pre17
| author | Peter Meerwald <pmeerw@cosy.sbg.ac.at> |
|---|---|
| date | Fri, 25 Jun 2010 15:50:58 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 3:c6c5a16ce2f2 | 4:26cd8f1ef0b1 |
|---|---|
| 1 /* | |
| 2 * SpanDSP - a series of DSP components for telephony | |
| 3 * | |
| 4 * modem_echo_tests.c | |
| 5 * | |
| 6 * Written by Steve Underwood <steveu@coppice.org> | |
| 7 * | |
| 8 * Copyright (C) 2004 Steve Underwood | |
| 9 * | |
| 10 * All rights reserved. | |
| 11 * | |
| 12 * This program is free software; you can redistribute it and/or modify | |
| 13 * it under the terms of the GNU General Public License version 2, as | |
| 14 * published by the Free Software Foundation. | |
| 15 * | |
| 16 * This program is distributed in the hope that it will be useful, | |
| 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 19 * GNU General Public License for more details. | |
| 20 * | |
| 21 * You should have received a copy of the GNU General Public License | |
| 22 * along with this program; if not, write to the Free Software | |
| 23 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
| 24 * | |
| 25 * $Id: modem_echo_tests.c,v 1.32 2009/05/30 15:23:14 steveu Exp $ | |
| 26 */ | |
| 27 | |
| 28 /*! \page modem_echo_can_tests_page Line echo cancellation for modems tests | |
| 29 \section modem_echo_can_tests_page_sec_1 What does it do? | |
| 30 Currently the echo cancellation tests only provide simple exercising of the | |
| 31 cancellor in the way it might be used for line echo cancellation. The test code | |
| 32 is in echotests.c. | |
| 33 | |
| 34 The goal is to test the echo cancellor again the G.16X specs. Clearly, that also | |
| 35 means the goal for the cancellor itself is to comply with those specs. Right | |
| 36 now, the only aspect of these tests implemented is the line impulse response | |
| 37 models in g168tests.c. | |
| 38 | |
| 39 \section modem_echo_can_tests_page_sec_2 How does it work? | |
| 40 The current test consists of feeding an audio file of real speech to the echo | |
| 41 cancellor as the transmit signal. A very simple model of a telephone line is | |
| 42 used to simulate a simple echo from the transmit signal. A second audio file of | |
| 43 real speech is also used to simulate a signal received form the far end of the | |
| 44 line. This is gated so it is only placed for one second every 10 seconds, | |
| 45 simulating the double talk condition. The resulting echo cancelled signal can | |
| 46 either be store in a file for further analysis, or played back as the data is | |
| 47 processed. | |
| 48 | |
| 49 A number of modified versions of this test have been performed. The signal level | |
| 50 of the two speech sources has been varied. Several simple models of the | |
| 51 telephone line have been used. Although the current cancellor design has known | |
| 52 limitations, it seems stable for all these test conditions. No instability has | |
| 53 been observed in the current version due to arithmetic overflow when the speech | |
| 54 is very loud (with earlier versions, well, ....:) ). The lack of saturating | |
| 55 arithmetic in general purpose CPUs is a huge disadvantage here, as software | |
| 56 saturation logic would cause a major slow down. Floating point would be good, | |
| 57 but is not usable in the Linux kernel. Anyway, the bottom line seems to be the | |
| 58 current design is genuinely useful, if imperfect. | |
| 59 | |
| 60 \section modem_echo_can_tests_page_sec_2 How do I use it? | |
| 61 | |
| 62 Build the tests with the command "./build". Currently there is no proper make | |
| 63 setup, or way to build individual tests. "./build" will built all the tests | |
| 64 which currently exist for the DSP functions. The echo cancellation test assumes | |
| 65 there are two audio files containing mono, 16 bit signed PCM speech data, sampled | |
| 66 at 8kHz. These should be called local_sound.wav and far_sound.wav. A third wave | |
| 67 file will be produced. This very crudely starts with the first 256 bytes from | |
| 68 the local_sound.wav file, followed by the results of the echo cancellation. The | |
| 69 resulting audio is also played to the /dev/dsp device. A printf near the end of | |
| 70 echo_tests.c is commented out with a \#if. If this is enabled, detailed | |
| 71 information about the results of the echo cancellation will be written to | |
| 72 stdout. By saving this into a file, Grace (recommended), GnuPlot, or some other | |
| 73 plotting package may be used to graphically display the functioning of the | |
| 74 cancellor. | |
| 75 */ | |
| 76 | |
| 77 #if defined(HAVE_CONFIG_H) | |
| 78 #include "config.h" | |
| 79 #endif | |
| 80 | |
| 81 #if defined(HAVE_FL_FL_H) && defined(HAVE_FL_FL_CARTESIAN_H) && defined(HAVE_FL_FL_AUDIO_METER_H) | |
| 82 #define ENABLE_GUI | |
| 83 #endif | |
| 84 | |
| 85 #include <stdlib.h> | |
| 86 #include <stdio.h> | |
| 87 #include <fcntl.h> | |
| 88 #include <string.h> | |
| 89 #include <time.h> | |
| 90 #include <sndfile.h> | |
| 91 #if defined(HAVE_MATH_H) | |
| 92 #define GEN_CONST | |
| 93 #endif | |
| 94 | |
| 95 //#if defined(WITH_SPANDSP_INTERNALS) | |
| 96 #define SPANDSP_EXPOSE_INTERNAL_STRUCTURES | |
| 97 //#endif | |
| 98 | |
| 99 #include "spandsp.h" | |
| 100 #include "spandsp/g168models.h" | |
| 101 #include "spandsp-sim.h" | |
| 102 | |
| 103 #if defined(ENABLE_GUI) | |
| 104 #include "echo_monitor.h" | |
| 105 #endif | |
| 106 | |
| 107 #if !defined(NULL) | |
| 108 #define NULL (void *) 0 | |
| 109 #endif | |
| 110 | |
| 111 typedef struct | |
| 112 { | |
| 113 const char *name; | |
| 114 int max; | |
| 115 int cur; | |
| 116 SNDFILE *handle; | |
| 117 int16_t signal[8000]; | |
| 118 } signal_source_t; | |
| 119 | |
| 120 signal_source_t local_css; | |
| 121 | |
| 122 fir32_state_t line_model; | |
| 123 | |
| 124 SNDFILE *resulthandle; | |
| 125 int16_t residue_sound[8000]; | |
| 126 int residue_cur = 0; | |
| 127 int do_codec_munge = TRUE; | |
| 128 int use_gui = FALSE; | |
| 129 | |
| 130 static const int16_t tone_1khz[] = {10362, 7327, 0, -7327, -10362, -7327, 0, 7327}; | |
| 131 | |
| 132 static inline void put_residue(int16_t tx, int16_t residue) | |
| 133 { | |
| 134 int outframes; | |
| 135 | |
| 136 residue_sound[residue_cur++] = tx; | |
| 137 residue_sound[residue_cur++] = residue; | |
| 138 if (residue_cur >= 8000) | |
| 139 { | |
| 140 residue_cur >>= 1; | |
| 141 outframes = sf_writef_short(resulthandle, residue_sound, residue_cur); | |
| 142 if (outframes != residue_cur) | |
| 143 { | |
| 144 fprintf(stderr, " Error writing residue sound\n"); | |
| 145 exit(2); | |
| 146 } | |
| 147 residue_cur = 0; | |
| 148 } | |
| 149 } | |
| 150 /*- End of function --------------------------------------------------------*/ | |
| 151 | |
| 152 static void signal_load(signal_source_t *sig, const char *name) | |
| 153 { | |
| 154 sig->name = name; | |
| 155 if ((sig->handle = sf_open_telephony_read(sig->name, 1)) == NULL) | |
| 156 { | |
| 157 fprintf(stderr, " Cannot open sound file '%s'\n", sig->name); | |
| 158 exit(2); | |
| 159 } | |
| 160 sig->max = sf_readf_short(sig->handle, sig->signal, 8000); | |
| 161 if (sig->max < 0) | |
| 162 { | |
| 163 fprintf(stderr, " Error reading sound file '%s'\n", sig->name); | |
| 164 exit(2); | |
| 165 } | |
| 166 } | |
| 167 /*- End of function --------------------------------------------------------*/ | |
| 168 | |
| 169 static void signal_free(signal_source_t *sig) | |
| 170 { | |
| 171 if (sf_close(sig->handle) != 0) | |
| 172 { | |
| 173 fprintf(stderr, " Cannot close sound file '%s'\n", sig->name); | |
| 174 exit(2); | |
| 175 } | |
| 176 } | |
| 177 /*- End of function --------------------------------------------------------*/ | |
| 178 | |
| 179 static void signal_restart(signal_source_t *sig) | |
| 180 { | |
| 181 sig->cur = 0; | |
| 182 } | |
| 183 /*- End of function --------------------------------------------------------*/ | |
| 184 | |
| 185 static int16_t signal_amp(signal_source_t *sig) | |
| 186 { | |
| 187 int16_t tx; | |
| 188 | |
| 189 tx = sig->signal[sig->cur++]; | |
| 190 if (sig->cur >= sig->max) | |
| 191 sig->cur = 0; | |
| 192 return tx; | |
| 193 } | |
| 194 /*- End of function --------------------------------------------------------*/ | |
| 195 | |
| 196 static inline int16_t codec_munger(int16_t amp) | |
| 197 { | |
| 198 if (do_codec_munge) | |
| 199 return alaw_to_linear(linear_to_alaw(amp)); | |
| 200 return amp; | |
| 201 } | |
| 202 /*- End of function --------------------------------------------------------*/ | |
| 203 | |
| 204 static void channel_model_create(int model) | |
| 205 { | |
| 206 static const int32_t *line_models[] = | |
| 207 { | |
| 208 line_model_d2_coeffs, | |
| 209 line_model_d3_coeffs, | |
| 210 line_model_d4_coeffs, | |
| 211 line_model_d5_coeffs, | |
| 212 line_model_d6_coeffs, | |
| 213 line_model_d7_coeffs, | |
| 214 line_model_d8_coeffs, | |
| 215 line_model_d9_coeffs | |
| 216 }; | |
| 217 | |
| 218 static int line_model_sizes[] = | |
| 219 { | |
| 220 sizeof(line_model_d2_coeffs)/sizeof(int32_t), | |
| 221 sizeof(line_model_d3_coeffs)/sizeof(int32_t), | |
| 222 sizeof(line_model_d4_coeffs)/sizeof(int32_t), | |
| 223 sizeof(line_model_d5_coeffs)/sizeof(int32_t), | |
| 224 sizeof(line_model_d6_coeffs)/sizeof(int32_t), | |
| 225 sizeof(line_model_d7_coeffs)/sizeof(int32_t), | |
| 226 sizeof(line_model_d8_coeffs)/sizeof(int32_t), | |
| 227 sizeof(line_model_d9_coeffs)/sizeof(int32_t) | |
| 228 }; | |
| 229 | |
| 230 fir32_create(&line_model, line_models[model], line_model_sizes[model]); | |
| 231 } | |
| 232 /*- End of function --------------------------------------------------------*/ | |
| 233 | |
| 234 static int16_t channel_model(int16_t local, int16_t far) | |
| 235 { | |
| 236 int16_t echo; | |
| 237 int16_t rx; | |
| 238 | |
| 239 /* Channel modelling is merely simulating the effects of A-law distortion | |
| 240 and using one of the echo models from G.168 */ | |
| 241 | |
| 242 /* The local tx signal will have gone through an A-law munging before | |
| 243 it reached the line's analogue area where the echo occurs. */ | |
| 244 echo = fir32(&line_model, codec_munger(local/8)); | |
| 245 /* The far end signal will have been through an A-law munging, although | |
| 246 this should not affect things. */ | |
| 247 rx = echo + codec_munger(far); | |
| 248 /* This mixed echo and far end signal will have been through an A-law munging when it came back into | |
| 249 the digital network. */ | |
| 250 rx = codec_munger(rx); | |
| 251 return rx; | |
| 252 } | |
| 253 /*- End of function --------------------------------------------------------*/ | |
| 254 | |
| 255 int main(int argc, char *argv[]) | |
| 256 { | |
| 257 modem_echo_can_state_t *ctx; | |
| 258 //awgn_state_t local_noise_source; | |
| 259 awgn_state_t far_noise_source; | |
| 260 int i; | |
| 261 int clean; | |
| 262 int16_t rx; | |
| 263 int16_t tx; | |
| 264 int local_cur; | |
| 265 int far_cur; | |
| 266 int result_cur; | |
| 267 int line_model_no; | |
| 268 time_t now; | |
| 269 power_meter_t power_before; | |
| 270 power_meter_t power_after; | |
| 271 float unadapted_output_power; | |
| 272 float unadapted_echo_power; | |
| 273 float adapted_output_power; | |
| 274 float adapted_echo_power; | |
| 275 #if defined(ENABLE_GUI) | |
| 276 int16_t amp[2]; | |
| 277 #endif | |
| 278 | |
| 279 line_model_no = 0; | |
| 280 use_gui = FALSE; | |
| 281 for (i = 1; i < argc; i++) | |
| 282 { | |
| 283 if (strcmp(argv[i], "-g") == 0) | |
| 284 { | |
| 285 use_gui = TRUE; | |
| 286 continue; | |
| 287 } | |
| 288 line_model_no = atoi(argv[1]); | |
| 289 } | |
| 290 time(&now); | |
| 291 ctx = modem_echo_can_create(256); | |
| 292 awgn_init_dbm0(&far_noise_source, 7162534, -50.0f); | |
| 293 | |
| 294 signal_load(&local_css, "sound_c1_8k.wav"); | |
| 295 | |
| 296 if ((resulthandle = sf_open_telephony_write("modem_echo.wav", 2)) == NULL) | |
| 297 { | |
| 298 fprintf(stderr, " Failed to open result file\n"); | |
| 299 exit(2); | |
| 300 } | |
| 301 | |
| 302 #if defined(ENABLE_GUI) | |
| 303 if (use_gui) | |
| 304 start_echo_can_monitor(256); | |
| 305 #endif | |
| 306 | |
| 307 local_cur = 0; | |
| 308 far_cur = 0; | |
| 309 result_cur = 0; | |
| 310 channel_model_create(line_model_no); | |
| 311 #if defined(ENABLE_GUI) | |
| 312 if (use_gui) | |
| 313 echo_can_monitor_line_model_update(line_model.coeffs, line_model.taps); | |
| 314 #endif | |
| 315 | |
| 316 modem_echo_can_flush(ctx); | |
| 317 | |
| 318 power_meter_init(&power_before, 5); | |
| 319 power_meter_init(&power_after, 5); | |
| 320 | |
| 321 /* Measure the echo power before adaption */ | |
| 322 modem_echo_can_adaption_mode(ctx, FALSE); | |
| 323 for (i = 0; i < 8000*5; i++) | |
| 324 { | |
| 325 tx = tone_1khz[i & 7]; | |
| 326 rx = channel_model(tx, 0); | |
| 327 clean = modem_echo_can_update(ctx, tx, rx); | |
| 328 power_meter_update(&power_before, rx); | |
| 329 power_meter_update(&power_after, clean); | |
| 330 } | |
| 331 unadapted_output_power = power_meter_current_dbm0(&power_before); | |
| 332 unadapted_echo_power = power_meter_current_dbm0(&power_after); | |
| 333 printf("Pre-adaption: output power %10.5fdBm0, echo power %10.5fdBm0\n", unadapted_output_power, unadapted_echo_power); | |
| 334 | |
| 335 /* Converge the canceller */ | |
| 336 signal_restart(&local_css); | |
| 337 modem_echo_can_adaption_mode(ctx, TRUE); | |
| 338 for (i = 0; i < 800*2; i++) | |
| 339 { | |
| 340 clean = modem_echo_can_update(ctx, 0, 0); | |
| 341 put_residue(0, clean); | |
| 342 } | |
| 343 | |
| 344 for (i = 0; i < 8000*50; i++) | |
| 345 { | |
| 346 tx = signal_amp(&local_css); | |
| 347 rx = channel_model(tx, 0); | |
| 348 clean = modem_echo_can_update(ctx, tx, rx); | |
| 349 power_meter_update(&power_before, rx); | |
| 350 power_meter_update(&power_after, clean); | |
| 351 #if 0 | |
| 352 if (i%800 == 0) | |
| 353 printf("Powers %10.5fdBm0 %10.5fdBm0\n", power_meter_current_dbm0(&power_before), power_meter_current_dbm0(&power_after)); | |
| 354 #endif | |
| 355 put_residue(tx, clean); | |
| 356 #if defined(ENABLE_GUI) | |
| 357 if (use_gui) | |
| 358 { | |
| 359 echo_can_monitor_can_update(ctx->fir_taps16, 256); | |
| 360 amp[0] = tx; | |
| 361 echo_can_monitor_line_spectrum_update(amp, 1); | |
| 362 } | |
| 363 #endif | |
| 364 } | |
| 365 | |
| 366 /* Now lets see how well adapted we are */ | |
| 367 modem_echo_can_adaption_mode(ctx, FALSE); | |
| 368 for (i = 0; i < 8000*5; i++) | |
| 369 { | |
| 370 tx = tone_1khz[i & 7]; | |
| 371 rx = channel_model(tx, 0); | |
| 372 clean = modem_echo_can_update(ctx, tx, rx); | |
| 373 power_meter_update(&power_before, rx); | |
| 374 power_meter_update(&power_after, clean); | |
| 375 } | |
| 376 adapted_output_power = power_meter_current_dbm0(&power_before); | |
| 377 adapted_echo_power = power_meter_current_dbm0(&power_after); | |
| 378 printf("Post-adaption: output power %10.5fdBm0, echo power %10.5fdBm0\n", adapted_output_power, adapted_echo_power); | |
| 379 | |
| 380 if (fabsf(adapted_output_power - unadapted_output_power) > 0.1f | |
| 381 || | |
| 382 adapted_echo_power > unadapted_echo_power - 30.0f) | |
| 383 { | |
| 384 printf("Tests failed.\n"); | |
| 385 exit(2); | |
| 386 } | |
| 387 | |
| 388 modem_echo_can_free(ctx); | |
| 389 signal_free(&local_css); | |
| 390 | |
| 391 if (sf_close(resulthandle) != 0) | |
| 392 { | |
| 393 fprintf(stderr, " Cannot close speech file '%s'\n", "result_sound.wav"); | |
| 394 exit(2); | |
| 395 } | |
| 396 | |
| 397 #if defined(ENABLE_GUI) | |
| 398 if (use_gui) | |
| 399 echo_can_monitor_wait_to_end(); | |
| 400 #endif | |
| 401 | |
| 402 printf("Tests passed.\n"); | |
| 403 return 0; | |
| 404 } | |
| 405 /*- End of function --------------------------------------------------------*/ | |
| 406 /*- End of file ------------------------------------------------------------*/ |
