Mercurial > hg > audiostuff
diff spandsp-0.0.3/spandsp-0.0.3/tests/echo_tests.c @ 5:f762bf195c4b
import spandsp-0.0.3
author | Peter Meerwald <pmeerw@cosy.sbg.ac.at> |
---|---|
date | Fri, 25 Jun 2010 16:00:21 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spandsp-0.0.3/spandsp-0.0.3/tests/echo_tests.c Fri Jun 25 16:00:21 2010 +0200 @@ -0,0 +1,1566 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * echo_tests.c + * + * Written by Steve Underwood <steveu@coppice.org> + * + * Copyright (C) 2001 Steve Underwood + * + * Based on a bit from here, a bit from there, eye of toad, + * ear of bat, etc - plus, of course, my own 2 cents. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: echo_tests.c,v 1.27 2006/11/19 14:07:27 steveu Exp $ + */ + +/*! \page echo_can_tests_page Line echo cancellation for voice tests + +\section echo_can_tests_page_sec_1 What does it do? +The echo cancellation tests test the echo cancellor against the G.168 spec. Not +all the tests in G.168 are fully implemented at this time. + +\section echo_can_tests_page_sec_2 How does it work? + +\section echo_can_tests_page_sec_2 How do I use it? + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(HAVE_FL_FL_H) && defined(HAVE_FL_FL_CARTESIAN_H) && defined(HAVE_FL_FL_AUDIO_METER_H) +#define ENABLE_GUI +#endif + +#define _GNU_SOURCE + +#include <unistd.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#if defined(HAVE_TGMATH_H) +#include <tgmath.h> +#endif +#if defined(HAVE_MATH_H) +#include <math.h> +#endif +#include <assert.h> +#include <audiofile.h> +#include <tiffio.h> + +#define GEN_CONST +#include <math.h> + +#include "spandsp.h" +#include "spandsp/g168models.h" +#if defined(ENABLE_GUI) +#include "echo_monitor.h" +#endif + +#define TEST_EC_TAPS 256 + +#if !defined(NULL) +#define NULL (void *) 0 +#endif + +typedef struct +{ + const char *name; + int max; + int cur; + AFfilehandle handle; + int16_t signal[SAMPLE_RATE]; +} signal_source_t; + +typedef struct +{ + int type; + fir_float_state_t *fir; + float history[35*8]; + int pos; + float factor; + float power; +} level_measurement_device_t; + +signal_source_t local_css; +signal_source_t far_css; + +fir32_state_t line_model; +float model_ki, erl; + +AFfilehandle residuehandle; +int16_t residue_sound[SAMPLE_RATE]; +int residue_cur = 0; +int munge; + +FILE *fdump; + +float clip(float x); +float clip(float x) { + if (x > 32767.0) x = 32767.0; + if (x < -32767.0) x = -32767.0; + + return x; +} +/*- End of function --------------------------------------------------------*/ + +static inline void put_residue(int16_t amp) +{ + int outframes; + + residue_sound[residue_cur++] = amp; + if (residue_cur >= SAMPLE_RATE) { + outframes = afWriteFrames(residuehandle, + AF_DEFAULT_TRACK, + residue_sound, + residue_cur); + if (outframes != residue_cur) + { + fprintf(stderr, " Error writing residue sound\n"); + exit(2); + } + residue_cur = 0; + } +} +/*- End of function --------------------------------------------------------*/ + +static void signal_load(signal_source_t *sig, const char *name) +{ + float x; + + sig->name = name; + if ((sig->handle = afOpenFile(sig->name, "r", 0)) == AF_NULL_FILEHANDLE) + { + fprintf(stderr, " Cannot open wave file '%s'\n", sig->name); + exit(2); + } + if ((x = afGetFrameSize(sig->handle, AF_DEFAULT_TRACK, 1)) != 2.0) + { + fprintf(stderr, " Unexpected frame size in wave file '%s'\n", sig->name); + exit(2); + } + if ((x = afGetRate(sig->handle, AF_DEFAULT_TRACK)) != (float) SAMPLE_RATE) + { + printf(" Unexpected sample rate in wave file '%s'\n", sig->name); + exit(2); + } + if ((x = afGetChannels(sig->handle, AF_DEFAULT_TRACK)) != 1.0) + { + printf(" Unexpected number of channels in wave file '%s'\n", sig->name); + exit(2); + } + sig->max = afReadFrames(sig->handle, AF_DEFAULT_TRACK, sig->signal, SAMPLE_RATE); + if (sig->max < 0) + { + fprintf(stderr, " Error reading sound file '%s'\n", sig->name); + exit(2); + } +} +/*- End of function --------------------------------------------------------*/ + +static AFfilehandle af_file_open_for_read(const char *name) +{ + float x; + AFfilehandle handle; + + if ((handle = afOpenFile(name, "r", 0)) == AF_NULL_FILEHANDLE) + { + fprintf(stderr, " Cannot open wave file '%s'\n", name); + exit(2); + } + if ((x = afGetFrameSize(handle, AF_DEFAULT_TRACK, 1)) != 2.0) + { + fprintf(stderr, " Unexpected frame size in wave file '%s'\n", name); + exit(2); + } + if ((x = afGetRate(handle, AF_DEFAULT_TRACK)) != (float) SAMPLE_RATE) + { + printf(" Unexpected sample rate in wave file '%s'\n", name); + exit(2); + } + if ((x = afGetChannels(handle, AF_DEFAULT_TRACK)) != 1.0) + { + printf(" Unexpected number of channels in wave file '%s'\n", name); + exit(2); + } + + return handle; +} +/*- End of function --------------------------------------------------------*/ + +static AFfilehandle af_file_open_for_write(const char *name) +{ + AFfilesetup setup; + AFfilehandle handle; + + setup = afNewFileSetup(); + if (setup == AF_NULL_FILESETUP) + { + fprintf(stderr, " %s: Failed to create file setup\n", name); + exit(2); + } + afInitSampleFormat(setup, AF_DEFAULT_TRACK, AF_SAMPFMT_TWOSCOMP, 16); + afInitRate(setup, AF_DEFAULT_TRACK, (float) SAMPLE_RATE); + afInitFileFormat(setup, AF_FILE_WAVE); + afInitChannels(setup, AF_DEFAULT_TRACK, 1); + handle = afOpenFile(name, "w", setup); + + if (handle == AF_NULL_FILEHANDLE) + { + fprintf(stderr, " Failed to open result file\n"); + exit(2); + } + afFreeFileSetup(setup); + + return handle; +} +/*- End of function --------------------------------------------------------*/ + +static void signal_restart(signal_source_t *sig) +{ + sig->cur = 0; +} +/*- End of function --------------------------------------------------------*/ + +static int16_t signal_amp(signal_source_t *sig) +{ + int16_t tx; + + tx = sig->signal[sig->cur++]; + if (sig->cur >= sig->max) + sig->cur = 0; + return tx; +} +/*- End of function --------------------------------------------------------*/ + +/* note mu-law used, alaw has big DC Offsets that causes probs with G168 + tests, due to alaw idle values being passed thru when NLP opens for + very low level signals. Probably need a DC blocking filter in e/c +*/ +static inline int16_t codec_munge(int16_t amp) +{ + return ulaw_to_linear(linear_to_ulaw(amp)); +} +/*- End of function --------------------------------------------------------*/ + +static int channel_model_create(int model) +{ + static const int32_t *line_models[] = + { + line_model_d2_coeffs, + line_model_d3_coeffs, + line_model_d4_coeffs, + line_model_d5_coeffs, + line_model_d6_coeffs, + line_model_d7_coeffs, + line_model_d8_coeffs, + line_model_d9_coeffs + }; + + static int line_model_sizes[] = + { + sizeof(line_model_d2_coeffs)/sizeof(int32_t), + sizeof(line_model_d3_coeffs)/sizeof(int32_t), + sizeof(line_model_d4_coeffs)/sizeof(int32_t), + sizeof(line_model_d5_coeffs)/sizeof(int32_t), + sizeof(line_model_d6_coeffs)/sizeof(int32_t), + sizeof(line_model_d7_coeffs)/sizeof(int32_t), + sizeof(line_model_d8_coeffs)/sizeof(int32_t), + sizeof(line_model_d9_coeffs)/sizeof(int32_t) + }; + + static float ki[] = + { + 1.39E-5, 1.44E-5, 1.52E-5, 1.77E-5, 9.33E-6, 1.51E-5, 2.33E-5, 1.33E-5 + }; + + if (model < 1 || model > (int) (sizeof(line_model_sizes)/sizeof(line_model_sizes[0]))) + return -1; + fir32_create(&line_model, line_models[model-1], line_model_sizes[model-1]); + + model_ki = ki[model-1]; + + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static int16_t channel_model(int16_t *new_local, int16_t *new_far, int16_t local, int16_t far) +{ + int16_t echo; + int16_t rx; + + /* Channel modelling is merely simulating the effects of A-law or u-law distortion + and using one of the echo models from G.168. Simulating the codec is very important, + as this is usually the limiting factor in how much echo reduction is achieved. */ + + /* The local tx signal will usually have gone through an A-law munging before + it reached the line's analogue area, where the echo occurs. */ + if (munge == TRUE) + local = codec_munge(local); + /* Now we need to model the echo. We only model a single analogue segment, as per + the G.168 spec. However, there will generally be near end and far end analogue/echoey + segments in the real world, unless an end is purely digital. */ + echo = fir32(&line_model, local*erl*(32768.0*model_ki)); + /* The far end signal will have been through an A-law munging, although + this should not affect things. */ + if (munge == TRUE) + rx = clip(echo + codec_munge(far)); + else + rx = clip(echo + far); + + /* This mixed echo and far end signal will have been through an A-law munging + when it came back into the digital network. */ + if (munge == TRUE) + rx = codec_munge(rx); + if (new_far) + *new_far = rx; + if (new_local) + *new_local = local; + return rx; +} +/*- End of function --------------------------------------------------------*/ + +/* + 250Hz HP filter, designed using this excellent site: + + http://www-users.cs.york.ac.uk/~fisher/mkfilter/ + + Included as preliminary test to see if this sort of filter will help + hum removal from low cost X100P type cards. Unfortunately I couldn't + get thios to work well in fixed point, so had to leave it out of + treh core echo canceller. +*/ + +#define NZEROS 4 +#define NPOLES 4 + +#define FIXED +#ifdef FIXED +#define GAIN 1.293080949e+00 +#define QCONST32(x,bits) ((int)((x)*(1<<(bits)))) + +static int hp_filter(int xv[], int yv[], int x) +{ + xv[0] = xv[1]; xv[1] = xv[2]; xv[2] = xv[3]; xv[3] = xv[4]; + xv[4] = x * (int)((1<<5)/GAIN); + yv[0] = yv[1]; yv[1] = yv[2]; yv[2] = yv[3]; yv[3] = yv[4]; + yv[4] = (xv[0] + xv[4]) - 4 * (xv[1] + xv[3]) + 6 * xv[2]; + yv[4] += (QCONST32(-0.5980652616f, 10) * yv[0]) >> 10; + yv[4] += (QCONST32( 2.6988843913f, 10) * yv[1]) >> 10; + yv[4] += (QCONST32(-4.5892912321f, 10) * yv[2]) >> 10; + yv[4] += (QCONST32( 3.4873077415f, 10) * yv[3]) >> 10; + + return yv[4] >> 5; +} + +#else +#define GAIN 1.293080949e+00 + +static float hp_filter(float xv[], float yv[], float x) +{ + xv[0] = xv[1]; xv[1] = xv[2]; xv[2] = xv[3]; xv[3] = xv[4]; + xv[4] = x / GAIN; + yv[0] = yv[1]; yv[1] = yv[2]; yv[2] = yv[3]; yv[3] = yv[4]; + yv[4] = (xv[0] + xv[4]) - 4 * (xv[1] + xv[3]) + 6 * xv[2] + + ( -0.5980652616 * yv[0]) + ( 2.6988843913 * yv[1]) + + ( -4.5892912321 * yv[2]) + ( 3.4873077415 * yv[3]); + return yv[4]; +} +#endif + +static level_measurement_device_t *level_measurement_device_create(int type) +{ + level_measurement_device_t *dev; + int i; + + dev = (level_measurement_device_t *) malloc(sizeof(level_measurement_device_t)); + dev->fir = (fir_float_state_t *) malloc(sizeof(fir_float_state_t)); + fir_float_create(dev->fir, + level_measurement_bp_coeffs, + sizeof(level_measurement_bp_coeffs)/sizeof(float)); + for (i = 0; i < 35*8; i++) + dev->history[i] = 0.0; + dev->pos = 0; + dev->factor = exp(-1.0/((float) SAMPLE_RATE*0.035)); + dev->power = 0; + dev->type = type; + return dev; +} +/*- End of function --------------------------------------------------------*/ + +static void level_measurement_device_reset(level_measurement_device_t *dev) +{ + int i; + + for (i = 0; i < 35*8; i++) + dev->history[i] = 0.0; + dev->pos = 0; + dev->power = 0; +} +/*- End of function --------------------------------------------------------*/ + +static int level_measurement_device_release(level_measurement_device_t *s) +{ + fir_float_free(s->fir); + free(s->fir); + free(s); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +static float level_measurement_device(level_measurement_device_t *dev, int16_t amp) +{ + float signal; + + signal = fir_float(dev->fir, amp); + signal *= signal; + if (dev->type == 0) + { + dev->power = dev->power*dev->factor + signal*(1.0 - dev->factor); + signal = sqrt(dev->power); + } + else + { + dev->power -= dev->history[dev->pos]; + dev->power += signal; + dev->history[dev->pos++] = signal; + signal = sqrt(dev->power/(35.8*8.0)); + } + if (signal > 0.0) + return DBM0_MAX_POWER + 20.0*log10(signal/32767.0); + else + return -1000.0; +} +/*- End of function --------------------------------------------------------*/ + +/* Globals used for performing tests */ + +echo_can_state_t *ctx; +awgn_state_t sgen_noise_source; +awgn_state_t rin_noise_source; + +level_measurement_device_t *Rin_power_meter; +level_measurement_device_t *Sgen_power_meter; +level_measurement_device_t *Sin_power_meter; +level_measurement_device_t *Sout_power_meter; + +float LRin, maxLRin; +float LSgen, maxLSgen; +float LSin, maxLSin; +float LSout, maxLSout; +float Lres, maxLres; +float test_clock; +float maxHoth; + +float Rin_level, Sgen_level; +FILE *flevel; +int Rin_type, Sgen_type; +int failed; +int verbose, quiet; +float threshold; +int model_number; +char test_name[80]; + +tone_gen_state_t rin_tone_state; +tone_gen_state_t sgen_tone_state; + +/* + Test callback functions are called one for every processed sample + during run_test(). They are user supplied, and return TRUE if the + test is passing or FALSE if a combination of variables mean that + the test has failed (for example Lres exceeding some threshold). + + Different test callback functions are required for each G168 test. +*/ +int (*test_callback)(void); + +/* macros to convert units for run_test */ + +#define MSEC (SAMPLE_RATE/1000) +#define SEC SAMPLE_RATE + +/* Sgen signal generator types */ + +#define NONE 0 +#define CSS 1 +#define HOTH 2 +#define TONE 3 + +/* Experimentally generated constants to normalise levels to dBm0 */ + +#define HOTH_SCALE 2.40 +#define CSS_SCALE 5.60 + +static void reset_all(void) { + echo_can_flush(ctx); + maxLRin = maxLSgen = maxLSin = maxLSout = maxLres = -100.0; + signal_restart(&local_css); + signal_restart(&far_css); + test_callback = NULL; + Rin_type = CSS; + Sgen_type = NONE; + failed = FALSE; + test_clock = 0.0; +} + +static void reset_meter_peaks(void) { + maxLRin = maxLSgen = maxLSin = maxLSout = maxLres = -100.0; +} + +static void install_test_callback(int (*f)(void)) { + test_callback = f; +} + +/* note: maybe we should use absolute levels rather than gain? Need to + normalise levels from various signal types to do this */ + +static void set_Sgen(int source_type, float gain) { + Sgen_type = source_type; + Sgen_level = pow(10.0, gain/20.0); +} + +static void set_Rin(int source_type, float gain) { + Rin_type = source_type; + Rin_level = pow(10.0, gain/20.0); +} + +static void mute_Rin(void) { + Rin_type = NONE; +} + +static void unmute_Rin(void) { + Rin_type = CSS; +} + +static void update_levels(int16_t rin, int16_t sin, int16_t sout, int16_t sgen) +{ + LRin = level_measurement_device(Rin_power_meter, rin); + LSin = level_measurement_device(Sin_power_meter, sin); + LSout = level_measurement_device(Sout_power_meter, sout); + LSgen = level_measurement_device(Sgen_power_meter, sgen); + if (LRin > maxLRin) maxLRin = LRin; + if (LSin > maxLSin) maxLSin = LSin; + if (LSout > maxLSout) maxLSout = LSout; + if (LSgen > maxLSgen) maxLSgen = LSgen; +} + +static void write_log_files(int16_t rout, int16_t sin) +{ + fprintf(flevel, "%f\t%f\t%f\t%f\n",LRin, LSin, LSout, LSgen); + fprintf(fdump, "%d %d %d", ctx->tx, ctx->rx, ctx->clean); + fprintf(fdump, " %d %d %d %d %d %d %d %d %d %d\n", ctx->clean_nlp, ctx->Ltx, + ctx->Lrx, ctx->Lclean, + (ctx->nonupdate_dwell > 0), ctx->adapt, ctx->Lclean_bg, ctx->Pstates, + ctx->Lbgn_upper, ctx->Lbgn); +} + +static void run_test(float time, float units) { + int i; + int samples; + int16_t rout, rin=0, sin; + int16_t sgen=0, sout; + float rin_hoth_noise = 0; + float sgen_hoth_noise = 0; + + samples = time * units; + + for (i = 0; i < samples; i++) { + + switch(Rin_type) { + case NONE: + rin = 0; + break; + case CSS: + rin = clip(Rin_level*signal_amp(&local_css)*CSS_SCALE); + break; + case HOTH: + rin_hoth_noise = rin_hoth_noise*0.625 + awgn(&rin_noise_source)*0.375; + rin = clip(Rin_level*rin_hoth_noise*HOTH_SCALE); + break; + case TONE: + tone_gen(&rin_tone_state, &rin, 1); + break; + } + + switch(Sgen_type) { + case NONE: + sgen = 0; + break; + case CSS: + sgen = clip(Sgen_level*signal_amp(&far_css)*CSS_SCALE); + break; + case HOTH: + sgen_hoth_noise = sgen_hoth_noise*0.625 + awgn(&sgen_noise_source)*0.375; + sgen = clip(Sgen_level*sgen_hoth_noise*HOTH_SCALE); + break; + case TONE: + tone_gen(&sgen_tone_state, &sgen, 1); + break; + } + + rout = echo_can_hpf_tx(ctx, rin); + channel_model(&rout, &sin, rin, sgen); + sout = echo_can_update(ctx, rout, sin); + update_levels(rin, sin, sout, sgen); + write_log_files(rout, sin); + + /* now test for fail condition */ + if (test_callback != NULL) { + if ( (failed == FALSE) && (test_callback() == FALSE)) { + /* test has failed */ + failed = TRUE; + } + } + + /* stop clock on fail - points to time of failure in test */ + + if (failed == FALSE) + test_clock += (float)1/SAMPLE_RATE; + } +} + +static void print_title(const char *title) { + if (quiet == FALSE) + printf(title); +} + +static void print_results(void) { + + if (quiet == FALSE) + printf("test model ERL time Max Rin Max Sin Max Sgen Max Sout Result\n"); + printf("%-4s %-1d %-5.1f%6.2fs%9.2f%9.2f%10.2f%10.2f ", + test_name, model_number, 20.0*log10(erl), + test_clock, maxLRin, maxLSin, maxLSgen, maxLSout); + if (failed == TRUE) + printf("FAIL\n"); + else + printf("PASS\n"); +} + +static int test_2a(void) { + if (LSout > -65.0) + return FALSE; + else + return TRUE; +} + +static int test_2c(void) { + if (LSout > maxHoth) + return FALSE; + else + return TRUE; +} + +static int test_3a(void) { + if (LSout > maxLSgen) + return FALSE; + else + return TRUE; +} + +static int test_3b(void) { + if (LSout > threshold) + return FALSE; + else + return TRUE; +} + +static int test_3c_t2(void) { + if (LSout > maxLSgen) + return FALSE; + else + return TRUE; +} + +static int test_3c_t4t5(void) { + if (LSout > (maxLSgen+6.0)) + return FALSE; + else + return TRUE; +} + +static int test_9(void) { + if (fabs(LSout - LSgen) > 2.0) + return FALSE; + else + return TRUE; +} + +#define N_TESTS 9 +static const char *supported_tests[] = {"ut1", "2aa", "2ca", "3a", "3ba", + "3bb", "3c", "6", "9"}; + +static int is_test_supported(char *test) { + int i; + for(i=0; i<N_TESTS; i++) { + if (!strcasecmp(test, supported_tests[i])) + return TRUE; + } + + return FALSE; +} + +/* dump estimate echo response */ +static void dump_h(void) { + int i; + FILE *f = fopen("h.txt","wt"); + for(i=0; i<TEST_EC_TAPS; i++) { + fprintf(f, "%f\n", (float)ctx->fir_taps16[0][i]/(1<<15)); + } + fclose(f); +} + +int main(int argc, char *argv[]) +{ + //awgn_state_t local_noise_source; + int i; + //int j; + //int k; + //tone_gen_descriptor_t tone_desc; + //tone_gen_state_t tone_state; + //int16_t local_sound[40000]; + //int local_max; + //int local_cur; + int far_cur; + int result_cur; + AFfilehandle txfile, rxfile, ecfile; + time_t now; + int tone_burst_step; + float X_level, Sgen_leveldB; +#ifdef FIXED + int xvrx[NZEROS+1], yvrx[NPOLES+1]; + int xvtx[NZEROS+1], yvtx[NPOLES+1]; +#else + float xvrx[NZEROS+1], yvrx[NPOLES+1]; + float xvtx[NZEROS+1], yvtx[NPOLES+1]; +#endif + + int file_mode; + float tmp; + int cng; + int hpf; + + /* default config ------------------------------------------------*/ + + model_number = 1; + erl = pow(10.0, -10.0/20.0); + verbose = quiet = FALSE; + file_mode = FALSE; + Rin_level = pow(10.0, -15.0/20.0); + Sgen_leveldB = -15.0; + Sgen_level = pow(10.0, Sgen_leveldB/20.0); + X_level = pow(10.0, 6.0/20.0); + tone_burst_step = 0; + txfile = rxfile = ecfile = NULL; + munge = TRUE; + cng = FALSE; + hpf = TRUE; + for(i=0; i<NPOLES+1; i++) { + xvtx[i] = yvtx[i] = xvrx[i] = yvrx[i] = 0.0; + } + + /* Check which tests we should run ----------------------------------------*/ + + if (argc < 2) { + fprintf(stderr, "Usage: echo [2aa] [2ca] [3a] [3ba] [3bb] [3c] [6] [9]\n" + "[-m ChannelModelNumber]\n" + "[-erl ERL_in_dB\n" + "[-file RinInputFile.wav SinInputFile.wav SoutOutputFile.wav\n" + "[-r RinLeveldBm0] [-s SgenLeveldBm0] [-x XLeveldB]\n" + "[-nomunge]\n" + "[-cng]\n" + "[-nohpf] Disable DC block HPF (-file mode)\n"); + + exit(1); + } + + for (i = 1; i < argc; i++) + { + if (is_test_supported(argv[i])) { + } + else if (strcmp(argv[i], "-m") == 0) + { + if (++i < argc) + model_number = atoi(argv[i]); + } + else if (strcmp(argv[i], "-r") == 0) + { + if (++i < argc) + Rin_level = pow(10.0, atof(argv[i])/20.0); + } + else if (strcmp(argv[i], "-s") == 0) + { + if (++i < argc) { + Sgen_leveldB = atof(argv[i]); + Sgen_level = pow(10.0, Sgen_leveldB/20.0); + } + } + else if (strcmp(argv[i], "-x") == 0) + { + if (++i < argc) + X_level = pow(10.0, atof(argv[i])/20.0); + } + else if (strcmp(argv[i], "-erl") == 0) + { + if (++i < argc) { + erl = atof(argv[i]); + if (erl < 0.0) { + printf("ERL must be >= 0.0 dB\n"); + exit(1); + } + erl = pow(10.0, -erl/20.0); + } + } + else if (strcmp(argv[i], "-v") == 0) + { + verbose = TRUE; + } + else if (strcmp(argv[i], "-q") == 0) + { + quiet = TRUE; + } + else if (strcmp(argv[i], "-file") == 0) + { + file_mode = TRUE; + if (argc < (i+3)) { + printf("not enough arguments for --file\n"); + exit(2); + } + txfile = af_file_open_for_read(argv[i+1]); + rxfile = af_file_open_for_read(argv[i+2]); + ecfile = af_file_open_for_write(argv[i+3]); + i += 3; + } + else if (strcmp(argv[i], "-nomunge") == 0) + { + munge = FALSE; + } + else if (strcmp(argv[i], "-cng") == 0) + { + cng = TRUE; + } + else if (strcmp(argv[i], "-nohpf") == 0) + { + hpf = FALSE; + } + else + { + fprintf(stderr, "Unknown test/option '%s' specified\n", argv[i]); + exit(2); + } + } + + /* initialise a bunch of modules we need ------------------------------*/ + + time(&now); + + ctx = echo_can_create(TEST_EC_TAPS, 0); + awgn_init_dbm0(&rin_noise_source, 7162534, 0.0f); + awgn_init_dbm0(&sgen_noise_source, 7162534, 0.0f); + Rin_power_meter = level_measurement_device_create(0); + Sgen_power_meter = level_measurement_device_create(0); + Sin_power_meter = level_measurement_device_create(0); + Sout_power_meter = level_measurement_device_create(0); + if (channel_model_create(model_number)) + { + fprintf(stderr, " Failed to create line model\n"); + exit(2); + } + + far_cur = 0; + result_cur = 0; + + if (verbose == TRUE) { + printf("ERL (linear)......: %6.2f (%5.2f)\n" + "Rin level (linear).: %6.2f (%5.2f)\n" + "Sgen level (linear): %6.2f (%5.2f)\n", + 20.0*log10(erl), erl, + 20.0*log10(Rin_level), Rin_level, + 20.0*log10(Sgen_level), Sgen_level); + } + + fdump = fopen("dump.txt","wt"); + assert(fdump != NULL); + flevel = fopen("level.txt","wt"); + assert(flevel != NULL); + + if (file_mode == TRUE) { + /* process wave files instead of running tests, useful for + testing real world signals */ + int ntx, nrx, nec; + int16_t rin, rout, sin, sout; + int mode; + + mode = ECHO_CAN_USE_ADAPTION | ECHO_CAN_USE_NLP; + if (cng) + mode |= ECHO_CAN_USE_CNG; + else + mode |= ECHO_CAN_USE_CLIP; + if (hpf) { + mode |= ECHO_CAN_USE_TX_HPF; + mode |= ECHO_CAN_USE_RX_HPF; + } + echo_can_adaption_mode(ctx, mode); + do { + ntx = afReadFrames(txfile, AF_DEFAULT_TRACK, &rin, 1); + if (ntx < 0) { + fprintf(stderr, " Error reading tx sound file\n"); + exit(2); + } + nrx = afReadFrames(rxfile, AF_DEFAULT_TRACK, &sin, 1); + if (nrx < 0) { + fprintf(stderr, " Error reading rx sound file\n"); + exit(2); + } + + rout = echo_can_hpf_tx(ctx, rin); + sout = echo_can_update(ctx, rout, sin); + + nec = afWriteFrames(ecfile, AF_DEFAULT_TRACK, &sout, 1); + if (nec != 1) { + fprintf(stderr, " Error writing ec sound file\n"); + exit(2); + } + + update_levels(rin, sin, sout, 0); + write_log_files(rin, sin); + + } while (ntx && nrx); + + dump_h(); + + afCloseFile(txfile); + afCloseFile(rxfile); + afCloseFile(ecfile); + exit(0); + } + + signal_load(&local_css, "sound_c1_8k.wav"); + signal_load(&far_css, "sound_c3_8k.wav"); + + strcpy(test_name, argv[1]); + + /* basic unit test used in e/c dvelopment */ + + if (!strcasecmp(argv[1], "ut1")) { + int16_t rin, sin, rout, sout, sgen; + + print_title("Performing Unit Test 1 - DC inputs\n"); + reset_all(); + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION); + + rout = rin = 2000; + sin = 1000; + sgen = 0; + for(i=0; i<10; i++) { + rout = 2000+2*i; + sout = echo_can_update(ctx, rout, sin); + update_levels(rin, sin, sout, sgen); + write_log_files(rout, sin); + } + dump_h(); + } + + /* Test 1 - Steady state residual and returned echo level test */ + /* This functionality has been merged with test 2 in newer versions of G.168, + so test 1 no longer exists. */ + + /* Test 2 - Convergence and steady state residual and returned echo level test */ + + /* + NOTE: This test is only partially implemented, only the conidtion after + 1s is tested and I am still not sure if LSout == Lres in the part + of the test after 1s. + */ + + if (!strcasecmp(argv[1], "2aa")) { + + print_title("Performing test 2A(a) - Convergence with NLP enabled\n"); + reset_all(); + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION | ECHO_CAN_USE_NLP); + + /* initial zero input as reqd by G168 */ + + mute_Rin(); + run_test(200, MSEC); + unmute_Rin(); + + /* Now test convergence */ + + run_test(1, SEC); + reset_meter_peaks(); + install_test_callback(test_2a); + run_test(10, SEC); + + print_results(); + } + +#ifdef OTHER_TESTS + if ((test_list & PERFORM_TEST_2B)) + { + printf("Performing test 2B - Re-convergence with NLP disabled\n"); + + /* Test 2B - Re-convergence with NLP disabled */ + + echo_can_flush(ctx); + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION); + + /* Converge a canceller */ + + signal_restart(&local_css); + for (i = 0; i < 800*2; i++) + { + clean = echo_can_update(ctx, 0, 0); + put_residue(clean); + } + + for (i = 0; i < SAMPLE_RATE*5; i++) + { + tx = signal_amp(&local_css); + channel_model(&tx, &rx, tx, 0); + clean = echo_can_update(ctx, tx, rx); + put_residue(clean); +#if defined(ENABLE_GUI) + if (use_gui) + echo_can_monitor_can_update(ctx->fir_taps16[ctx->tap_set], TEST_EC_TAPS); +#endif + } +#if defined(ENABLE_GUI) + if (use_gui) + echo_can_monitor_can_update(ctx->fir_taps16[ctx->tap_set], TEST_EC_TAPS); +#endif + } +#endif + + if (!strcasecmp(argv[1], "2ca")) { + float SgenLeveldB; + + print_title("Performing test 2C(a) - Convergence with background noise present\n"); + reset_all(); + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION | ECHO_CAN_USE_NLP); + + /* Converge canceller with background noise */ + + mute_Rin(); + run_test(200, MSEC); + unmute_Rin(); + + SgenLeveldB = 20.0*log10(Rin_level) - 15.0; + if (SgenLeveldB > -30.0) SgenLeveldB = -30.0; + set_Sgen(HOTH, SgenLeveldB); + run_test(1, SEC); + maxHoth = maxLSgen; + + /* After 1 second freeze adaption, switch off noise. */ + + mute_Rin(); + run_test(150, MSEC); + + echo_can_adaption_mode(ctx, ECHO_CAN_USE_NLP); + run_test(1, SEC); + + unmute_Rin(); + set_Sgen(NONE, 0.0); + run_test(500, MSEC); + + /* now measure the echo */ + + reset_meter_peaks(); + maxLSgen = maxHoth; /* keep this peak for print out but reset the rest */ + install_test_callback(test_2c); + set_Sgen(NONE, 0.0); + run_test(5, SEC); + + print_results(); + } + + /* Test 3 - Performance under double talk conditions */ + + if (!strcasecmp(argv[1], "3a")) { + print_title("Performing test 3A - Double talk test with low cancelled-end levels\n"); + reset_all(); + + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION); + set_Sgen(CSS, -15.0 + 20.0*log10(Rin_level)); + run_test(5, SEC); + tmp = maxLSgen; + + /* now freeze adaption */ + + echo_can_adaption_mode(ctx, 0); + set_Sgen(NONE, 0.0); + run_test(500, MSEC); + + /* Now measure the echo */ + + reset_meter_peaks(); + maxLSgen = tmp; + install_test_callback(test_3a); + run_test(5, SEC); + + print_results(); + } + + if (!strcasecmp(argv[1], "3ba")) { + float fig11; + + print_title("Performing test 3B(a) - Double talk stability test with high cancelled-end levels\n"); + reset_all(); + + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION); + run_test(5, SEC); + + /* Apply double talk */ + + set_Sgen(CSS, 20.0*log10(Sgen_level)); + run_test(5, SEC); + tmp = maxLSgen; + + /* freeze adaption and measure echo */ + + mute_Rin(); + run_test(150, MSEC); + + echo_can_adaption_mode(ctx, 0); + run_test(1, SEC); + + unmute_Rin(); + set_Sgen(NONE, 0.0); + run_test(500, MSEC); + + /* Now measure the echo */ + + fig11 = (25.0/30.0)*maxLRin - 30.0; /* pass/fail based on clean level @ tx peak */ + threshold = fig11 + 10.0; + reset_meter_peaks(); + maxLSgen = tmp; + install_test_callback(test_3b); + run_test(5, SEC); + + print_results(); + } + + if (!strcasecmp(argv[1], "3bb")) { + float fig11; + + print_title("Performing test 3B(b) - Double talk stability test with low cancelled-end levels\n"); + reset_all(); + + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION); + run_test(5, SEC); + + /* Apply double talk */ + + set_Sgen(CSS, 20.0*log10(Rin_level) - 20.0*log10(X_level)); + run_test(5, SEC); + tmp = maxLSgen; + + /* freeze adaption and measure echo */ + + mute_Rin(); + run_test(150, MSEC); + + echo_can_adaption_mode(ctx, 0); + run_test(1, SEC); + + unmute_Rin(); + set_Sgen(NONE, 0.0); + run_test(500, MSEC); + + /* Now measure the echo */ + + fig11 = (25.0/30.0)*maxLRin - 30.0; /* pass/fail based on clean level @ tx peak */ + threshold = fig11 + 3.0; + reset_meter_peaks(); + maxLSgen = tmp; + install_test_callback(test_3b); + run_test(5, SEC); + + print_results(); + } + + if (!strcasecmp(argv[1], "3c")) { + print_title("Performing test 3C - Double talk test under simulated conversation\n"); + reset_all(); + + /* t1 (5.6s) - double talk */ + + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION | ECHO_CAN_USE_NLP); + set_Sgen(CSS, Sgen_leveldB); + run_test(5600, MSEC); + + /* t2 (1.4s) - to pass Sout <= Sgen */ + + set_Sgen(NONE, 0.0); + install_test_callback(test_3c_t2); + run_test(1400, MSEC); + + /* t3 - (5s) - single talk to converge e/c */ + + run_test(5000, MSEC); + + /* t4 - (5.6s) - double talk again */ + + install_test_callback(test_3c_t4t5); + set_Sgen(CSS, Sgen_leveldB); + run_test(5600, MSEC); + + /* t5 - (5.6s) - near end single talk */ + + mute_Rin(); + run_test(5600, MSEC); + + print_results(); + } + +#ifdef OTHER_TESTS + if ((test_list & PERFORM_TEST_4)) + { + printf("Performing test 4 - Leak rate test\n"); + /* Test 4 - Leak rate test */ + echo_can_flush(ctx); + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION); + /* Converge a canceller */ + signal_restart(&local_css); + for (i = 0; i < SAMPLE_RATE*5; i++) + { + tx = signal_amp(&local_css); + channel_model(&tx, &rx, tx, 0); + clean = echo_can_update(ctx, tx, rx); + put_residue(clean); + } + /* Put 2 minutes of silence through it */ + for (i = 0; i < SAMPLE_RATE*120; i++) + { + clean = echo_can_update(ctx, 0, 0); + put_residue(clean); + } + /* Now freeze it, and check if it is still well adapted. */ + echo_can_adaption_mode(ctx, 0); + for (i = 0; i < SAMPLE_RATE*5; i++) + { + tx = signal_amp(&local_css); + channel_model(&tx, &rx, tx, 0); + clean = echo_can_update(ctx, tx, rx); + put_residue(clean); + } + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION); +#if defined(ENABLE_GUI) + if (use_gui) + echo_can_monitor_can_update(ctx->fir_taps16[ctx->tap_set], TEST_EC_TAPS); +#endif + } + + if ((test_list & PERFORM_TEST_5)) + { + printf("Performing test 5 - Infinite return loss convergence test\n"); + /* Test 5 - Infinite return loss convergence test */ + echo_can_flush(ctx); + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION); + /* Converge the canceller */ + signal_restart(&local_css); + for (i = 0; i < SAMPLE_RATE*5; i++) + { + tx = signal_amp(&local_css); + channel_model(&tx, &rx, tx, 0); + clean = echo_can_update(ctx, tx, rx); + put_residue(clean); + } + /* Now stop echoing, and see we don't do anything unpleasant as the + echo path is open looped. */ + for (i = 0; i < SAMPLE_RATE*5; i++) + { + tx = signal_amp(&local_css); + rx = 0; + tx = codec_munge(tx); + clean = echo_can_update(ctx, tx, rx); + put_residue(clean); + } +#if defined(ENABLE_GUI) + if (use_gui) + echo_can_monitor_can_update(ctx->fir_taps16[ctx->tap_set], TEST_EC_TAPS); +#endif + } + +#endif + + if (!strcasecmp(argv[1], "6")) + { + int k; + float fig11; + + printf("Performing test 6 - Non-divergence on narrow-band signals\n"); + + reset_all(); + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION); + run_test(5, SEC); + + /* Now put 5s bursts of a list of tones through the converged canceller, and check + that nothing unpleasant happens. */ + + for (k = 0; tones_6_4_2_7[k][0]; k++) + { + tone_gen_descriptor_t tone_desc; + + /* 5 secs of each tone */ + + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION); + set_Rin(TONE, 20.0*log10(Rin_level)); /* level actually set by next func */ + make_tone_gen_descriptor(&tone_desc, + tones_6_4_2_7[k][0], + -11, + tones_6_4_2_7[k][1], + -9, + 1, + 0, + 0, + 0, + 1); + tone_gen_init(&rin_tone_state, &tone_desc); + run_test(5, SEC); + } + + /* disable adaption, back to speech */ + + echo_can_adaption_mode(ctx, 0); + set_Rin(CSS, 20.0*log10(Rin_level)); + run_test(1, SEC); + + /* now test convergence as per test 2 fig 11 */ + + fig11 = (25.0/30.0)*maxLRin - 30.0; /* pass/fail based on clean level @ tx peak */ + threshold = fig11 + 10.0; + reset_meter_peaks(); + install_test_callback(test_3b); + run_test(5, SEC); + + print_results(); + } + +#ifdef OTHER_TESTS + + if ((test_list & PERFORM_TEST_7)) + { + printf("Performing test 7 - Stability\n"); + /* Test 7 - Stability */ + /* Put tones through an unconverged canceller, and check nothing unpleasant + happens. */ + echo_can_flush(ctx); + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION); + make_tone_gen_descriptor(&tone_desc, + tones_6_4_2_7[0][0], + -11, + tones_6_4_2_7[0][1], + -9, + 1, + 0, + 0, + 0, + 1); + tone_gen_init(&tone_state, &tone_desc); + j = 0; + for (i = 0; i < 120; i++) + { + local_max = tone_gen(&tone_state, local_sound, SAMPLE_RATE); + for (j = 0; j < SAMPLE_RATE; j++) + { + tx = local_sound[j]; + channel_model(&tx, &rx, tx, 0); + clean = echo_can_update(ctx, tx, rx); + put_residue(clean); + } +#if defined(ENABLE_GUI) + if (use_gui) + { + echo_can_monitor_can_update(ctx->fir_taps16[ctx->tap_set], TEST_EC_TAPS); + echo_can_monitor_update_display(); + usleep(100000); + } +#endif + } +#if defined(ENABLE_GUI) + if (use_gui) + echo_can_monitor_can_update(ctx->fir_taps16[ctx->tap_set], TEST_EC_TAPS); +#endif + } + + if ((test_list & PERFORM_TEST_8)) + { + printf("Performing test 8 - Non-convergence on No 5, 6, and 7 in-band signalling\n"); + /* Test 8 - Non-convergence on No 5, 6, and 7 in-band signalling */ + fprintf(stderr, "Test 8 not yet implemented\n"); + } +#endif + + if (!strcasecmp(argv[1], "9")) + { + printf("Performing test 9 - Comfort noise test\n"); + + echo_can_flush(ctx); + echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION + | ECHO_CAN_USE_NLP + | ECHO_CAN_USE_CNG); + + /* Test 9 Part 1 - matching */ + + set_Sgen(HOTH, -45.0); + mute_Rin(); + run_test(5, SEC); /* should be 30s but I wanted to speed up sim */ + set_Rin(HOTH, -10.0); + run_test(2, SEC); + + reset_meter_peaks(); + install_test_callback(test_9); + run_test(700, MSEC); + + /* Test 9 Part 2 - adjustment down */ + + install_test_callback(NULL); + set_Sgen(HOTH, -55.0); + mute_Rin(); + run_test(5, SEC); /* should be 10s but I wanted to speed up sim */ + set_Rin(HOTH, -10.0); + run_test(2, SEC); + + reset_meter_peaks(); + install_test_callback(test_9); + run_test(700, MSEC); + + /* Test 9 Part 3 - adjustment up */ + + install_test_callback(NULL); + set_Sgen(HOTH, -45.0); + mute_Rin(); + run_test(5, SEC); /* should be 10s but I wanted to speed up sim */ + set_Rin(HOTH, -10.0); + run_test(2, SEC); + + reset_meter_peaks(); + install_test_callback(test_9); + run_test(700, MSEC); + + print_results(); + } + +#ifdef OTHER_TESTS + /* Test 10 - FAX test during call establishment phase */ + if ((test_list & PERFORM_TEST_10A)) + { + printf("Performing test 10A - Canceller operation on the calling station side\n"); + /* Test 10A - Canceller operation on the calling station side */ + fprintf(stderr, "Test 10A not yet implemented\n"); + } + + if ((test_list & PERFORM_TEST_10B)) + { + printf("Performing test 10B - Canceller operation on the called station side\n"); + /* Test 10B - Canceller operation on the called station side */ + fprintf(stderr, "Test 10B not yet implemented\n"); + } + + if ((test_list & PERFORM_TEST_10C)) + { + printf("Performing test 10C - Canceller operation on the calling station side during page\n" + "transmission and page breaks (for further study)\n"); + /* Test 10C - Canceller operation on the calling station side during page + transmission and page breaks (for further study) */ + fprintf(stderr, "Test 10C not yet implemented\n"); + } + + if ((test_list & PERFORM_TEST_11)) + { + printf("Performing test 11 - Tandem echo canceller test (for further study)\n"); + /* Test 11 - Tandem echo canceller test (for further study) */ + fprintf(stderr, "Test 11 not yet implemented\n"); + } + + if ((test_list & PERFORM_TEST_12)) + { + printf("Performing test 12 - Residual acoustic echo test (for further study)\n"); + /* Test 12 - Residual acoustic echo test (for further study) */ + fprintf(stderr, "Test 12 not yet implemented\n"); + } + + if ((test_list & PERFORM_TEST_13)) + { + printf("Performing test 13 - Performance with ITU-T low-bit rate coders in echo path (Optional, under study)\n"); + /* Test 13 - Performance with ITU-T low-bit rate coders in echo path + (Optional, under study) */ + fprintf(stderr, "Test 13 not yet implemented\n"); + } + + if ((test_list & PERFORM_TEST_14)) + { + printf("Performing test 14 - Performance with V-series low-speed data modems\n"); + /* Test 14 - Performance with V-series low-speed data modems */ + fprintf(stderr, "Test 14 not yet implemented\n"); + } + + if ((test_list & PERFORM_TEST_15)) + { + printf("Performing test 15 - PCM offset test (Optional)\n"); + /* Test 15 - PCM offset test (Optional) */ + fprintf(stderr, "Test 15 not yet implemented\n"); + } + + echo_can_free(ctx); + + signal_free(&local_css); + signal_free(&far_css); + + if (afCloseFile(resulthandle) != 0) + { + fprintf(stderr, " Cannot close speech file '%s'\n", "result_sound.wav"); + exit(2); + } + if (afCloseFile(residuehandle) != 0) + { + fprintf(stderr, " Cannot close speech file '%s'\n", "residue_sound.wav"); + exit(2); + } + afFreeFileSetup(filesetup); + afFreeFileSetup(filesetup2); + +#if defined(XYZZY) + for (j = 0; j < ctx->taps; j++) + { + for (i = 0; i < coeff_index; i++) + fprintf(stderr, "%d ", coeffs[i][j]); + fprintf(stderr, "\n"); + } +#endif +#endif + if (verbose == TRUE) + printf("Run time %lds\n", time(NULL) - now); + +#if defined(ENABLE_GUI) + if (use_gui) + echo_can_monitor_wait_to_end(); +#endif + + + fclose(fdump); + fclose(flevel); + + return 0; +} +/*- End of function --------------------------------------------------------*/ +/*- End of file ------------------------------------------------------------*/