diff spandsp-0.0.6pre17/tests/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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spandsp-0.0.6pre17/tests/echo_tests.c	Fri Jun 25 15:50:58 2010 +0200
@@ -0,0 +1,1732 @@
+/*
+ * 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.39 2009/05/30 15:23:13 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?
+
+*/
+
+#if defined(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
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <sndfile.h>
+
+#define GEN_CONST
+#include <math.h>
+
+//#if defined(WITH_SPANDSP_INTERNALS)
+#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES
+//#endif
+
+#include "spandsp.h"
+#include "spandsp/g168models.h"
+#include "spandsp-sim.h"
+#if defined(ENABLE_GUI)
+#include "echo_monitor.h"
+#endif
+
+#if !defined(NULL)
+#define NULL (void *) 0
+#endif
+
+#define TEST_EC_TAPS            256
+
+#define RESIDUE_FILE_NAME       "residue_sound.wav"
+
+/*
+    The key signal names, as defined in G.168
+
+          +--------------+       +------------+
+          |              |  Sin  |            |
+Sgen -->--|   Echoey     |--->---|  Echo      |-->-- Sout
+          |              |       |            |
+          |   World      |  Rout |  Canceller |
+     --<--|              |---<---|            |--<-- Rin
+          |              |       |            |
+          +--------------+       +------------+
+
+    Echoey world model. Munge means linear->PCM->linear distortion.
+          +-------------------------+
+          |                         |  Sin
+Sgen -->--|-->munge--->sum-->munge--|--->---
+          |        +-->             |
+          |       FIR               |  Rout
+     --<--|--------+--------munge<--|---<---
+          |                         |
+          +-------------------------+
+*/
+
+typedef struct
+{
+    const char *name;
+    int max;
+    int cur;
+    float gain;
+    SNDFILE *handle;
+    int16_t signal[SAMPLE_RATE];
+} signal_source_t;
+
+/* Level measurement device, specified in G.168 section 6.4.1.2.1 */
+typedef struct
+{
+    int type;
+    fir_float_state_t *fir;
+    float history[35*8];
+    int pos;
+    float factor; 
+    float power;
+    float peak;
+} level_measurement_device_t;
+
+typedef struct
+{
+    int model_no;
+    float erl;
+    fir32_state_t impulse;
+    float gain;
+    int munging_codec;
+} channel_model_state_t;
+
+channel_model_state_t chan_model;
+
+signal_source_t local_css;
+signal_source_t far_css;
+awgn_state_t local_noise_source;
+awgn_state_t far_noise_source;
+
+SNDFILE *residue_handle;
+int16_t residue_sound[SAMPLE_RATE];
+int residue_cur = 0;
+
+level_measurement_device_t *power_meter_1;
+level_measurement_device_t *power_meter_2;
+
+int line_model_no;
+int supp_line_model_no;
+int munger;
+
+level_measurement_device_t *rin_power_meter;    /* Also known as Lrin */
+level_measurement_device_t *rout_power_meter;
+level_measurement_device_t *sin_power_meter;
+level_measurement_device_t *sout_power_meter;   /* Also known as Lret (pre NLP value is known as Lres) */
+level_measurement_device_t *sgen_power_meter;
+
+#define RESULT_CHANNELS 7
+SNDFILE *result_handle;
+int16_t result_sound[SAMPLE_RATE*RESULT_CHANNELS];
+int result_cur;
+
+const char *test_name;
+int quiet;
+int use_gui;
+
+float erl;
+
+/* Dump estimated echo response */
+static void dump_ec_state(echo_can_state_t *ctx)
+{
+    int i;
+    FILE *f;
+
+    if ((f = fopen("echo_tests_state.txt", "wt")) == NULL)
+        return;
+    for (i = 0;  i < TEST_EC_TAPS;  i++)
+        fprintf(f, "%f\n", (float) ctx->fir_taps16[0][i]/(1 << 15));
+    fclose(f);
+}
+/*- End of function --------------------------------------------------------*/
+
+static inline void put_residue(int16_t amp)
+{
+    int outframes;
+
+    residue_sound[residue_cur++] = amp;
+    if (residue_cur >= SAMPLE_RATE)
+    {
+        outframes = sf_writef_short(residue_handle, 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)
+{
+    sig->handle = sf_open_telephony_read(name, 1);
+    sig->name = name;
+    sig->max = sf_readf_short(sig->handle, sig->signal, SAMPLE_RATE);
+    if (sig->max < 0)
+    {
+        fprintf(stderr, "    Error reading sound file '%s'\n", sig->name);
+        exit(2);
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void signal_free(signal_source_t *sig)
+{
+    if (sf_close(sig->handle) != 0)
+    {
+        fprintf(stderr, "    Cannot close sound file '%s'\n", sig->name);
+        exit(2);
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void signal_restart(signal_source_t *sig, float gain)
+{
+    sig->cur = 0;
+    sig->gain = powf(10.0f, gain/20.0f);
+}
+/*- End of function --------------------------------------------------------*/
+
+static int16_t signal_amp(signal_source_t *sig)
+{
+    int16_t tx;
+
+    tx = sig->signal[sig->cur++]*sig->gain;
+    if (sig->cur >= sig->max)
+        sig->cur = 0;
+    return tx;
+}
+/*- End of function --------------------------------------------------------*/
+
+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.0f;
+    dev->pos = 0;
+    dev->factor = expf(-1.0f/((float) SAMPLE_RATE*0.035f));
+    dev->power = 0;
+    dev->type = type;
+    return  dev;
+}
+/*- End of function --------------------------------------------------------*/
+
+#if 0
+static void level_measurement_device_reset(level_measurement_device_t *dev)
+{
+    int i;
+
+    for (i = 0;  i < 35*8;  i++)
+        dev->history[i] = 0.0f;
+    dev->pos = 0;
+    dev->power = 0;
+    dev->peak = 0.0f;
+}
+/*- 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 --------------------------------------------------------*/
+#endif
+
+static float level_measurement_device_get_peak(level_measurement_device_t *dev)
+{
+    return dev->peak;
+}
+/*- End of function --------------------------------------------------------*/
+
+static float level_measurement_device_reset_peak(level_measurement_device_t *dev)
+{
+    float power;
+
+    power = dev->peak;
+    dev->peak = -99.0f;
+    return power;
+}
+/*- End of function --------------------------------------------------------*/
+
+static float level_measurement_device(level_measurement_device_t *dev, int16_t amp)
+{
+    float signal;
+    float power;
+
+    /* Level measurement device(s), specified in G.168 section 6.4.1.2.1 and 6.4.1.2.2 */
+    signal = fir_float(dev->fir, amp);
+    signal *= signal;
+    if (dev->type == 0)
+    {
+        /* Level measurement device, specified in G.168 section 6.4.1.2.1 -
+           level measurement device. This version uses a single pole
+           estimator.*/
+        dev->power = dev->power*dev->factor + signal*(1.0f - dev->factor);
+        signal = sqrtf(dev->power);
+    }
+    else
+    {
+        /* Level measurement device, specified in G.168 section 6.4.1.2.2 -
+           level measurement device for peaks. This version uses a sliding
+           window estimator. */
+        dev->power += (signal - dev->history[dev->pos]);
+        dev->history[dev->pos++] = signal;
+        signal = sqrtf(dev->power/(35.8f*8.0f));
+    }
+    if (signal <= 0.0f)
+        return -99.0f;
+    power = DBM0_MAX_POWER + 20.0f*log10f(signal/32767.0f);
+    if (power > dev->peak)
+        dev->peak = power;
+    return power;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void level_measurements_create(int type)
+{
+    rin_power_meter = level_measurement_device_create(type);
+    rout_power_meter = level_measurement_device_create(type);
+    sin_power_meter = level_measurement_device_create(type);
+    sout_power_meter = level_measurement_device_create(type);
+    sgen_power_meter = level_measurement_device_create(type);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void level_measurements_update(int16_t rin, int16_t sin, int16_t rout, int16_t sout, int16_t sgen)
+{
+    level_measurement_device(rin_power_meter, rin);
+    level_measurement_device(rout_power_meter, rout);
+    level_measurement_device(sin_power_meter, sin);
+    level_measurement_device(sout_power_meter, sout);
+    level_measurement_device(sgen_power_meter, sgen);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void level_measurements_reset_peaks(void)
+{
+    level_measurement_device_reset_peak(rin_power_meter);
+    level_measurement_device_reset_peak(rout_power_meter);
+    level_measurement_device_reset_peak(sin_power_meter);
+    level_measurement_device_reset_peak(sout_power_meter);
+    level_measurement_device_reset_peak(sgen_power_meter);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void print_results(void)
+{
+    if (!quiet)
+        printf("test  model  ERL   time     Max Rin  Max Rout Max Sgen Max Sin  Max Sout\n");
+    printf("%-4s  %-1d      %-5.1f%6.2fs%9.2f%9.2f%9.2f%9.2f%9.2f\n", 
+           test_name,
+           chan_model.model_no,
+           20.0f*log10f(-chan_model.erl), 
+           0.0f, //test_clock,
+           level_measurement_device_get_peak(rin_power_meter),
+           level_measurement_device_get_peak(rout_power_meter),
+           level_measurement_device_get_peak(sgen_power_meter),
+           level_measurement_device_get_peak(sin_power_meter),
+           level_measurement_device_get_peak(sout_power_meter));
+}
+/*- End of function --------------------------------------------------------*/
+
+static int channel_model_create(channel_model_state_t *chan, int model, float erl, int codec)
+{
+    static const int32_t line_model_clear_coeffs[] =
+    {
+        32768
+    };
+    static const int32_t *line_models[] =
+    {
+        line_model_clear_coeffs,
+        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 const int line_model_sizes[] =
+    {
+        sizeof(line_model_clear_coeffs)/sizeof(line_model_clear_coeffs[0]),
+        sizeof(line_model_d2_coeffs)/sizeof(line_model_d2_coeffs[0]),
+        sizeof(line_model_d3_coeffs)/sizeof(line_model_d3_coeffs[0]),
+        sizeof(line_model_d4_coeffs)/sizeof(line_model_d4_coeffs[0]),
+        sizeof(line_model_d5_coeffs)/sizeof(line_model_d5_coeffs[0]),
+        sizeof(line_model_d6_coeffs)/sizeof(line_model_d6_coeffs[0]),
+        sizeof(line_model_d7_coeffs)/sizeof(line_model_d7_coeffs[0]),
+        sizeof(line_model_d8_coeffs)/sizeof(line_model_d8_coeffs[0]),
+        sizeof(line_model_d9_coeffs)/sizeof(line_model_d9_coeffs[0])
+    };
+    static const float ki[] = 
+    {
+        3.05e-5f,
+        LINE_MODEL_D2_GAIN,
+        LINE_MODEL_D3_GAIN,
+        LINE_MODEL_D4_GAIN,
+        LINE_MODEL_D5_GAIN,
+        LINE_MODEL_D6_GAIN,
+        LINE_MODEL_D7_GAIN,
+        LINE_MODEL_D8_GAIN,
+        LINE_MODEL_D9_GAIN
+    };
+
+    if (model < 0  ||  model >= (int) (sizeof(line_model_sizes)/sizeof(line_model_sizes[0])))
+        return -1;
+    fir32_create(&chan->impulse, line_models[model], line_model_sizes[model]);
+    chan->gain = 32768.0f*powf(10.0f, erl/20.0f)*ki[model];
+    chan->munging_codec = codec;
+    chan->model_no = model;
+    chan->erl = erl;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int16_t channel_model(channel_model_state_t *chan, int16_t rout, int16_t sgen)
+{
+    int16_t echo;
+    int16_t sin;
+
+    /* 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 far end signal will have been through codec munging. */
+    switch (chan->munging_codec)
+    {
+    case G711_ALAW:
+        sgen = alaw_to_linear(linear_to_alaw(sgen));
+        break;
+    case G711_ULAW:
+        sgen = ulaw_to_linear(linear_to_ulaw(sgen));
+        break;
+    }
+
+    /* The local tx signal will usually have gone through codec munging before
+       it reached the line's analogue area, where the echo occurs. */
+    switch (chan->munging_codec)
+    {
+    case G711_ALAW:
+        rout = alaw_to_linear(linear_to_alaw(rout));
+        break;
+    case G711_ULAW:
+        rout = ulaw_to_linear(linear_to_ulaw(rout));
+        break;
+    }
+    /* 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(&chan->impulse, rout*chan->gain);
+    sin = saturate(echo + sgen);
+
+    /* This mixed echo and far end signal will have been through codec munging
+       when it came back into the digital network. */
+    switch (chan->munging_codec)
+    {
+    case G711_ALAW:
+        sin = alaw_to_linear(linear_to_alaw(sin));
+        break;
+    case G711_ULAW:
+        sin = ulaw_to_linear(linear_to_ulaw(sin));
+        break;
+    }
+    return sin;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void write_log_files(int16_t rout, int16_t sin)
+{
+#if 0
+    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);
+#endif
+}
+/*- End of function --------------------------------------------------------*/
+
+static int16_t silence(void)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int16_t local_css_signal(void)
+{
+    return signal_amp(&local_css);
+}
+/*- End of function --------------------------------------------------------*/
+
+static int16_t far_css_signal(void)
+{
+    return signal_amp(&far_css);
+}
+/*- End of function --------------------------------------------------------*/
+
+static int16_t local_noise_signal(void)
+{
+    return awgn(&local_noise_source);
+}
+/*- End of function --------------------------------------------------------*/
+
+static int16_t far_noise_signal(void)
+{
+    return awgn(&far_noise_source);
+}
+/*- End of function --------------------------------------------------------*/
+
+#if 0
+static int16_t local_hoth_noise_signal(void)
+{
+    static float hoth_noise = 0.0;
+
+    hoth_noise = hoth_noise*0.625 + awgn(&local_noise_source)*0.375;
+    return (int16_t) hoth_noise;
+}
+/*- End of function --------------------------------------------------------*/
+#endif
+
+static int16_t far_hoth_noise_signal(void)
+{
+    static float hoth_noise = 0.0;
+
+    hoth_noise = hoth_noise*0.625 + awgn(&far_noise_source)*0.375;
+    return (int16_t) hoth_noise;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void run_test(echo_can_state_t *ctx, int16_t (*tx_source)(void), int16_t (*rx_source)(void), int period)
+{
+    int i;
+    int16_t rin;
+    int16_t rout;
+    int16_t sin;
+    int16_t sout;
+    int16_t sgen;
+    int outframes;
+
+    for (i = 0;  i < period*SAMPLE_RATE/1000;  i++)
+    {
+        rin = tx_source();
+        sgen = rx_source();
+
+        rout = echo_can_hpf_tx(ctx, rin);
+        sin = channel_model(&chan_model, rout, sgen);
+        sout = echo_can_update(ctx, rout, sin);
+
+        level_measurements_update(rin, sin, rout, sout, sgen);
+        //residue = 100.0f*pp1/pp2;
+        //put_residue(residue);
+
+        //put_residue(clean - rx);
+#if defined(ENABLE_GUI)
+        if (use_gui)
+            echo_can_monitor_can_update(ctx->fir_taps16[0], TEST_EC_TAPS);
+#endif
+        result_sound[result_cur++] = rin;
+        result_sound[result_cur++] = sgen;
+        result_sound[result_cur++] = sin;
+        result_sound[result_cur++] = rout;
+        result_sound[result_cur++] = sout;
+        result_sound[result_cur++] = sout - sgen;
+        result_sound[result_cur++] = 0; // TODO: insert the EC's internal status here
+        if (result_cur >= RESULT_CHANNELS*SAMPLE_RATE)
+        {
+            outframes = sf_writef_short(result_handle, result_sound, result_cur/RESULT_CHANNELS);
+            if (outframes != result_cur/RESULT_CHANNELS)
+            {
+                fprintf(stderr, "    Error writing result sound\n");
+                exit(2);
+            }
+            result_cur = 0;
+        }
+    }
+#if defined(ENABLE_GUI)
+    if (use_gui)
+        echo_can_monitor_can_update(ctx->fir_taps16[0], TEST_EC_TAPS);
+#endif
+    if (result_cur >= 0)
+    {
+        outframes = sf_writef_short(result_handle, result_sound, result_cur/RESULT_CHANNELS);
+        if (outframes != result_cur/RESULT_CHANNELS)
+        {
+            fprintf(stderr, "    Error writing result sound\n");
+            exit(2);
+        }
+        result_cur = 0;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void print_test_title(const char *title)
+{
+    if (quiet == FALSE) 
+        printf(title);
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_sanity(void)
+{
+    echo_can_state_t *ctx;
+    int i;
+    int16_t rx;
+    int16_t tx;
+    int16_t clean;
+    int far_tx;
+    int16_t far_sound[SAMPLE_RATE];
+    int16_t result_sound[64000];
+    int result_cur;
+    int outframes;
+    int local_cur;
+    int far_cur;
+    //int32_t coeffs[200][128];
+    //int coeff_index;
+
+    print_test_title("Performing basic sanity test\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    local_cur = 0;
+    far_cur = 0;
+    result_cur = 0;
+
+    echo_can_flush(ctx);
+    /* Converge the canceller */
+    signal_restart(&local_css, 0.0f);
+    run_test(ctx, silence, silence, 200);
+    run_test(ctx, local_css_signal, silence, 5000);
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION | ECHO_CAN_USE_NLP | ECHO_CAN_USE_CNG);
+    run_test(ctx, local_css_signal, silence, 5000);
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+    
+    for (i = 0;  i < SAMPLE_RATE*10;  i++)
+    {
+        tx = local_css_signal();
+#if 0
+        if ((i/10000)%10 == 9)
+        {
+            /* Inject a burst of far sound */
+            if (far_cur >= far_max)
+            {
+                far_max = sf_readf_short(farhandle, far_sound, SAMPLE_RATE);
+                if (far_max < 0)
+                {
+                    fprintf(stderr, "    Error reading far sound\n");
+                    exit(2);
+                }
+                if (far_max == 0)
+                    break;
+                far_cur = 0;
+            }
+            far_tx = far_sound[far_cur++];
+        }
+        else
+        {
+            far_tx = 0;
+        }
+#else
+        far_sound[0] = 0;
+        far_tx = 0;
+#endif
+        rx = channel_model(&chan_model, tx, far_tx);
+        //rx += awgn(&far_noise_source);
+        //tx += awgn(&far_noise_source);
+        clean = echo_can_update(ctx, tx, rx);
+
+#if defined(XYZZY)
+        if (i%SAMPLE_RATE == 0)
+        {
+            if (coeff_index < 200)
+            {
+                for (j = 0;  j < ctx->taps;  j++)
+                    coeffs[coeff_index][j] = ctx->fir_taps32[j];
+                coeff_index++;
+            }
+        }
+#endif
+        result_sound[result_cur++] = tx;
+        result_sound[result_cur++] = rx;
+        result_sound[result_cur++] = clean - far_tx;
+        //result_sound[result_cur++] = ctx->tx_power[2];
+        //result_sound[result_cur++] = ctx->tx_power[1];
+        ////result_sound[result_cur++] = (ctx->tx_power[1] > 64)  ?  SAMPLE_RATE  :  -SAMPLE_RATE;
+        //result_sound[result_cur++] = ctx->tap_set*SAMPLE_RATE;
+        //result_sound[result_cur++] = (ctx->nonupdate_dwell > 0)  ?  SAMPLE_RATE  :  -SAMPLE_RATE;
+        //result_sound[result_cur++] = ctx->latest_correction >> 8;
+        //result_sound[result_cur++] = level_measurement_device(tx)/(16.0*65536.0);
+        //result_sound[result_cur++] = level_measurement_device(tx)/4096.0;
+        ////result_sound[result_cur++] = (ctx->tx_power[1] > ctx->rx_power[0])  ?  SAMPLE_RATE  :  -SAMPLE_RATE;
+        //result_sound[result_cur++] = (ctx->tx_power[1] > ctx->rx_power[0])  ?  SAMPLE_RATE  :  -SAMPLE_RATE;
+        //result_sound[result_cur++] = (ctx->narrowband_score)*5; //  ?  SAMPLE_RATE  :  -SAMPLE_RATE;
+        //result_sound[result_cur++] = ctx->tap_rotate_counter*10;
+        ////result_sound[result_cur++] = ctx->vad;
+        
+        put_residue(clean - far_tx);
+        if (result_cur >= RESULT_CHANNELS*SAMPLE_RATE)
+        {
+            outframes = sf_writef_short(result_handle, result_sound, result_cur/RESULT_CHANNELS);
+            if (outframes != result_cur/RESULT_CHANNELS)
+            {
+                fprintf(stderr, "    Error writing result sound\n");
+                exit(2);
+            }
+            result_cur = 0;
+        }
+    }
+    if (result_cur > 0)
+    {
+        outframes = sf_writef_short(result_handle, result_sound, result_cur/RESULT_CHANNELS);
+        if (outframes != result_cur/RESULT_CHANNELS)
+        {
+            fprintf(stderr, "    Error writing result sound\n");
+            exit(2);
+        }
+    }
+#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
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_2a(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 2 - Convergence and steady state residual and returned echo level test */
+    /* Test 2A - Convergence and reconvergence test with NLP enabled */
+    print_test_title("Performing test 2A - Convergence and reconvergence test with NLP enabled\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    echo_can_flush(ctx);
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION | ECHO_CAN_USE_NLP);
+
+    /* Test 2A (a) - Convergence test with NLP enabled */
+
+    /* Converge the canceller. */
+    run_test(ctx, silence, silence, 200);
+    signal_restart(&local_css, 0.0f);
+    run_test(ctx, local_css_signal, silence, 1000);
+    level_measurements_reset_peaks();
+    run_test(ctx, local_css_signal, silence, 9000);
+    print_results();
+    if (level_measurement_device_get_peak(sout_power_meter) > -65.0f)
+        printf("Test failed\n");
+    else
+        printf("Test passed\n");
+
+    /* Test 2A (b) - Reconvergence test with NLP enabled */
+
+    /* Make an abrupt change of channel characteristic, to another of the channel echo models. */
+    if (channel_model_create(&chan_model, supp_line_model_no, erl, munger))
+    {
+        fprintf(stderr, "    Failed to create line model\n");
+        exit(2);
+    }
+    signal_restart(&local_css, 0.0f);
+    run_test(ctx, local_css_signal, silence, 1000);
+    level_measurements_reset_peaks();
+    run_test(ctx, local_css_signal, silence, 9000);
+    print_results();
+    if (level_measurement_device_get_peak(sout_power_meter) > -65.0f)
+        printf("Test failed\n");
+    else
+        printf("Test passed\n");
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_2b(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 2 - Convergence and steady state residual and returned echo level test */
+    /* Test 2B - Convergence and reconverge with NLP disabled */
+    print_test_title("Performing test 2B - Convergence and reconverge with NLP disabled\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    echo_can_flush(ctx);
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+    signal_restart(&local_css, 0.0f);
+    
+    /* Test 2B (a) - Convergence test with NLP disabled */
+
+    /* Converge the canceller */
+    run_test(ctx, silence, silence, 200);
+    run_test(ctx, local_css_signal, silence, 1000);
+    level_measurements_reset_peaks();
+    run_test(ctx, local_css_signal, silence, 9000);
+    print_results();
+    level_measurements_reset_peaks();
+    run_test(ctx, local_css_signal, silence, 170000);
+    print_results();
+    if (level_measurement_device_get_peak(sout_power_meter) > -65.0)
+        printf("Test failed\n");
+    else
+        printf("Test passed\n");
+
+    /* Test 2B (b) - Reconvergence test with NLP disabled */
+
+    /* Make an abrupt change of channel characteristic, to another of the channel echo models. */
+    if (channel_model_create(&chan_model, supp_line_model_no, erl, munger))
+    {
+        fprintf(stderr, "    Failed to create line model\n");
+        exit(2);
+    }
+    run_test(ctx, local_css_signal, silence, 1000);
+    level_measurements_reset_peaks();
+    run_test(ctx, local_css_signal, silence, 9000);
+    print_results();
+    level_measurements_reset_peaks();
+    run_test(ctx, local_css_signal, silence, 170000);
+    print_results();
+    if (level_measurement_device_get_peak(sout_power_meter) > -65.0)
+        printf("Test failed\n");
+    else
+        printf("Test passed\n");
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_2ca(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 2 - Convergence and steady state residual and returned echo level test */
+    /* Test 2C(a) - Convergence with background noise present */
+    print_test_title("Performing test 2C(a) - Convergence with background noise present\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+    awgn_init_dbm0(&far_noise_source, 7162534, -50.0f);
+    
+    echo_can_flush(ctx);
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+    
+    /* Converge a canceller */
+    signal_restart(&local_css, 0.0f);
+    run_test(ctx, silence, silence, 200);
+    
+    awgn_init_dbm0(&far_noise_source, 7162534, -40.0f);
+    run_test(ctx, local_css_signal, far_hoth_noise_signal, 5000);
+    
+    /* Now freeze adaption, and measure the echo. */
+    echo_can_adaption_mode(ctx, 0);
+    level_measurements_reset_peaks();
+    run_test(ctx, local_css_signal, silence, 5000);
+    print_results();
+    if (level_measurement_device_get_peak(sout_power_meter) > level_measurement_device_get_peak(sgen_power_meter))
+        printf("Test failed\n");
+    else
+        printf("Test passed\n");
+
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_3a(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 3 - Performance under double talk conditions */
+    /* Test 3A - Double talk test with low cancelled-end levels */
+    print_test_title("Performing test 3A - Double talk test with low cancelled-end levels\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    echo_can_flush(ctx);
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+    
+    run_test(ctx, silence, silence, 200);
+    signal_restart(&local_css, 0.0f);
+    signal_restart(&far_css, -20.0f);
+
+    /* Apply double talk, with a weak far end signal */
+    run_test(ctx, local_css_signal, far_css_signal, 5000);
+    
+    /* Now freeze adaption. */
+    echo_can_adaption_mode(ctx, 0);
+    run_test(ctx, local_css_signal, silence, 500);
+    
+    /* Now measure the echo */
+    level_measurements_reset_peaks();
+    run_test(ctx, local_css_signal, silence, 5000);
+    print_results();
+    if (level_measurement_device_get_peak(sout_power_meter) > level_measurement_device_get_peak(sgen_power_meter))
+        printf("Test failed\n");
+    else
+        printf("Test passed\n");
+
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_3ba(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 3 - Performance under double talk conditions */
+    /* Test 3B(a) - Double talk stability test with high cancelled-end levels */
+    print_test_title("Performing test 3B(b) - Double talk stability test with high cancelled-end levels\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    echo_can_flush(ctx);
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+
+    run_test(ctx, silence, silence, 200);
+    signal_restart(&local_css, 0.0f);
+    signal_restart(&far_css, 0.0f);
+    
+    /* Converge the canceller */
+    run_test(ctx, local_css_signal, silence, 5000);
+    
+    /* Apply double talk */
+    run_test(ctx, local_css_signal, far_css_signal, 5000);
+    
+    /* Now freeze adaption. */
+    echo_can_adaption_mode(ctx, 0);
+    run_test(ctx, local_css_signal, far_css_signal, 1000);
+    
+    /* Turn off the double talk. */
+    run_test(ctx, local_css_signal, silence, 500);
+    
+    /* Now measure the echo */
+    level_measurements_reset_peaks();
+    run_test(ctx, local_css_signal, silence, 5000);
+    print_results();
+
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_3bb(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 3 - Performance under double talk conditions */
+    /* Test 3B(b) - Double talk stability test with low cancelled-end levels */
+    print_test_title("Performing test 3B(b) - Double talk stability test with low cancelled-end levels\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    echo_can_flush(ctx);
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+
+    run_test(ctx, silence, silence, 200);
+    signal_restart(&local_css, 0.0f);
+    signal_restart(&far_css, -15.0f);
+    
+    /* Converge the canceller */
+    run_test(ctx, local_css_signal, silence, 5000);
+    
+    /* Apply double talk */
+    run_test(ctx, local_css_signal, far_css_signal, 5000);
+    
+    /* Now freeze adaption. */
+    echo_can_adaption_mode(ctx, 0);
+    run_test(ctx, local_css_signal, silence, 1000);
+    
+    /* Turn off the double talk. */
+    run_test(ctx, local_css_signal, silence, 500);
+    
+    /* Now measure the echo */
+    level_measurements_reset_peaks();
+    run_test(ctx, local_css_signal, silence, 5000);
+    print_results();
+
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_3c(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 3 - Performance under double talk conditions */
+    /* Test 3C - Double talk test with simulated conversation */
+    print_test_title("Performing test 3C - Double talk test with simulated conversation\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    echo_can_flush(ctx);
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+    run_test(ctx, silence, silence, 200);
+
+    signal_restart(&local_css, 0.0f);
+    signal_restart(&far_css, -15.0f);
+
+    /* Apply double talk */
+    run_test(ctx, local_css_signal, far_css_signal, 5600);
+
+    /* Stop the far signal, and measure the echo. */
+    level_measurements_reset_peaks();
+    run_test(ctx, local_css_signal, silence, 1400);
+    print_results();
+
+    /* Continue measuring the resulting echo */
+    run_test(ctx, local_css_signal, silence, 5000);
+
+    /* Reapply double talk */
+    signal_restart(&far_css, 0.0f);
+    run_test(ctx, local_css_signal, far_css_signal, 5600);
+
+    /* Now the far signal only */
+    run_test(ctx, silence, far_css_signal, 5600);
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_4(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 4 - Leak rate test */
+    print_test_title("Performing test 4 - Leak rate test\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    echo_can_flush(ctx);
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+
+    run_test(ctx, silence, silence, 200);
+
+    /* Converge the canceller */
+    signal_restart(&local_css, 0.0f);
+    run_test(ctx, local_css_signal, silence, 5000);
+
+    /* Put 2 minutes of silence through it */
+    run_test(ctx, silence, silence, 120000);
+
+    /* Now freeze it, and check if it is still well adapted. */
+    echo_can_adaption_mode(ctx, 0);
+    level_measurements_reset_peaks();
+    run_test(ctx, local_css_signal, silence, 5000);
+    print_results();
+
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_5(void)
+{
+    echo_can_state_t *ctx;
+    
+    /* Test 5 - Infinite return loss convergence test */
+    print_test_title("Performing test 5 - Infinite return loss convergence test\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    echo_can_flush(ctx);
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+
+    /* Converge the canceller */
+    signal_restart(&local_css, 0.0f);
+    run_test(ctx, local_css_signal, silence, 5000);
+
+    /* Now stop echoing, and see we don't do anything unpleasant as the
+       echo path is open looped. */
+    run_test(ctx, local_css_signal, silence, 5000);
+    print_results();
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_6(void)
+{
+    echo_can_state_t *ctx;
+    int i;
+    int j;
+    int k;
+    int16_t rx;
+    int16_t tx;
+    int16_t clean;
+    int local_max;
+    tone_gen_descriptor_t tone_desc;
+    tone_gen_state_t tone_state;
+    int16_t local_sound[40000];
+
+    /* Test 6 - Non-divergence on narrow-band signals */
+    print_test_title("Performing test 6 - Non-divergence on narrow-band signals\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    echo_can_flush(ctx);
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION);
+
+    /* Converge the canceller */
+    signal_restart(&local_css, 0.0f);
+    run_test(ctx, local_css_signal, silence, 5000);
+
+    /* 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++)
+    {
+        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(&tone_state, &tone_desc);
+        j = 0;
+        for (i = 0;  i < 5;  i++)
+        {
+            local_max = tone_gen(&tone_state, local_sound, SAMPLE_RATE);
+            for (j = 0;  j < SAMPLE_RATE;  j++)
+            {
+                tx = local_sound[j];
+                rx = channel_model(&chan_model, 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[0], 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[0], TEST_EC_TAPS);
+#endif
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_7(void)
+{
+    echo_can_state_t *ctx;
+    int i;
+    int j;
+    int16_t rx;
+    int16_t tx;
+    int16_t clean;
+    int local_max;
+    tone_gen_descriptor_t tone_desc;
+    tone_gen_state_t tone_state;
+    int16_t local_sound[40000];
+
+    /* Test 7 - Stability */
+    print_test_title("Performing test 7 - Stability\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    /* 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];
+            rx = channel_model(&chan_model, 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[0], 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[0], TEST_EC_TAPS);
+#endif
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_8(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 8 - Non-convergence on No 5, 6, and 7 in-band signalling */
+    print_test_title("Performing test 8 - Non-convergence on No 5, 6, and 7 in-band signalling\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    fprintf(stderr, "Test 8 not yet implemented\n");
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_9(void)
+{
+    echo_can_state_t *ctx;
+    awgn_state_t local_noise_source;
+    awgn_state_t far_noise_source;
+
+    /* Test 9 - Comfort noise test */
+    print_test_title("Performing test 9 - Comfort noise test\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+    awgn_init_dbm0(&far_noise_source, 7162534, -50.0f);
+
+    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 */
+    /* Converge the canceller */
+    signal_restart(&local_css, 0.0f);
+    run_test(ctx, local_css_signal, silence, 5000);
+
+    echo_can_adaption_mode(ctx, ECHO_CAN_USE_ADAPTION | ECHO_CAN_USE_NLP | ECHO_CAN_USE_CNG);
+    awgn_init_dbm0(&far_noise_source, 7162534, -45.0f);
+    run_test(ctx, silence, far_noise_signal, 30000);
+
+    awgn_init_dbm0(&local_noise_source, 1234567, -10.0f);
+    run_test(ctx, local_noise_signal, far_noise_signal, 2000);
+
+    /* Test 9 part 2 - adjust down */
+    awgn_init_dbm0(&far_noise_source, 7162534, -55.0f);
+    run_test(ctx, silence, far_noise_signal, 10000);
+    run_test(ctx, local_noise_signal, far_noise_signal, 2000);
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_10a(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 10 - FAX test during call establishment phase */
+    /* Test 10A - Canceller operation on the calling station side */
+    print_test_title("Performing test 10A - Canceller operation on the calling station side\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    fprintf(stderr, "Test 10A not yet implemented\n");
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_10b(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 10 - FAX test during call establishment phase */
+    /* Test 10B - Canceller operation on the called station side */
+    print_test_title("Performing test 10B - Canceller operation on the called station side\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    fprintf(stderr, "Test 10B not yet implemented\n");
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_10c(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 10 - FAX test during call establishment phase */
+    /* Test 10C - Canceller operation on the calling station side during page
+                  transmission and page breaks (for further study) */
+    print_test_title("Performing test 10C - Canceller operation on the calling station side during page\n"
+                     "transmission and page breaks (for further study)\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    fprintf(stderr, "Test 10C not yet implemented\n");
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_11(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 11 - Tandem echo canceller test (for further study) */
+    print_test_title("Performing test 11 - Tandem echo canceller test (for further study)\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    fprintf(stderr, "Test 11 not yet implemented\n");
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_12(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 12 - Residual acoustic echo test (for further study) */
+    print_test_title("Performing test 12 - Residual acoustic echo test (for further study)\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    fprintf(stderr, "Test 12 not yet implemented\n");
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_13(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 13 - Performance with ITU-T low-bit rate coders in echo path
+                 (Optional, under study) */
+    print_test_title("Performing test 13 - Performance with ITU-T low-bit rate coders in echo path (Optional, under study)\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    fprintf(stderr, "Test 13 not yet implemented\n");
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_14(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 14 - Performance with V-series low-speed data modems */
+    print_test_title("Performing test 14 - Performance with V-series low-speed data modems\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    fprintf(stderr, "Test 14 not yet implemented\n");
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int perform_test_15(void)
+{
+    echo_can_state_t *ctx;
+
+    /* Test 15 - PCM offset test (Optional) */
+    print_test_title("Performing test 15 - PCM offset test (Optional)\n");
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+
+    fprintf(stderr, "Test 15 not yet implemented\n");
+
+    echo_can_free(ctx);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static int match_test_name(const char *name)
+{
+    const struct
+    {
+        const char *name;
+        int (*func)(void);
+    } tests[] =
+    {
+        {"sanity", perform_test_sanity},
+        {"2a", perform_test_2a},
+        {"2b", perform_test_2b},
+        {"2ca", perform_test_2ca},
+        {"3a", perform_test_3a},
+        {"3ba", perform_test_3ba},
+        {"3bb", perform_test_3bb},
+        {"3c", perform_test_3c},
+        {"4", perform_test_4},
+        {"5", perform_test_5},
+        {"6", perform_test_6},
+        {"7", perform_test_7},
+        {"8", perform_test_8},
+        {"9", perform_test_9},
+        {"10a", perform_test_10a},
+        {"10b", perform_test_10b},
+        {"10c", perform_test_10c},
+        {"11", perform_test_11},
+        {"12", perform_test_12},
+        {"13", perform_test_13},
+        {"14", perform_test_14},
+        {"15", perform_test_15},
+        {NULL, NULL}
+    };
+    int i;
+
+    for (i = 0;  tests[i].name;  i++)
+    {
+        if (strcasecmp(name, tests[i].name) == 0)
+        {
+            test_name = name;
+            tests[i].func();
+            return 0;
+        }
+    }
+    printf("Unknown test name '%s' specified. The known test names are ", name);
+    for (i = 0;  tests[i].name;  i++)
+    {
+        printf("%s", tests[i].name);
+        if (tests[i + 1].name)
+            printf(", ");
+    }
+    printf("\n");
+    return -1;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void simulate_ec(char *argv[], int two_channel_file, int mode)
+{
+    echo_can_state_t *ctx;
+    SNDFILE *txfile;
+    SNDFILE *rxfile;
+    SNDFILE *rxtxfile;
+    SNDFILE *ecfile;
+    int ntx;
+    int nrx;
+    int nec;
+    int16_t buf[2];
+    int16_t rin;
+    int16_t rout;
+    int16_t sin;
+    int16_t sout;
+    int32_t samples;
+
+    mode |= ECHO_CAN_USE_ADAPTION;
+    txfile = NULL;
+    rxfile = NULL;
+    rxtxfile = NULL;
+    ecfile = NULL;
+    if (two_channel_file)
+    {
+        txfile = sf_open_telephony_read(argv[0], 1);
+        rxfile = sf_open_telephony_read(argv[1], 1);      
+        ecfile = sf_open_telephony_write(argv[2], 1);
+    }
+    else
+    {
+        rxtxfile = sf_open_telephony_read(argv[0], 2);
+        ecfile = sf_open_telephony_write(argv[1], 1);
+    }
+
+    ctx = echo_can_init(TEST_EC_TAPS, 0);
+    echo_can_adaption_mode(ctx, mode);
+    samples = 0;
+    do
+    {
+        if (two_channel_file)
+        {
+            if ((ntx = sf_readf_short(rxtxfile, buf, 1)) < 0)
+            {
+                fprintf(stderr, "    Error reading tx sound file\n");
+                exit(2);
+            }
+            rin = buf[0];
+            sin = buf[1];
+            nrx = ntx;
+        }
+        else
+        {
+            if ((ntx = sf_readf_short(txfile, &rin, 1)) < 0)
+            {
+                fprintf(stderr, "    Error reading tx sound file\n");
+                exit(2);
+            }
+            if ((nrx = sf_readf_short(rxfile, &sin, 1)) < 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);
+
+        if ((nec = sf_writef_short(ecfile, &sout, 1)) != 1)
+        {
+            fprintf(stderr, "    Error writing ec sound file\n");
+            exit(2);
+        }
+        level_measurements_update(rin, sin, rout, sout, 0);
+        write_log_files(rin, sin);
+#if defined(ENABLE_GUI)
+        if (use_gui)
+            echo_can_monitor_can_update(ctx->fir_taps16[0], TEST_EC_TAPS);
+#endif
+    }
+    while (ntx  &&  nrx);
+    dump_ec_state(ctx);
+
+    echo_can_free(ctx);
+
+    if (two_channel_file)
+    {
+        sf_close(rxtxfile);
+    }
+    else
+    {
+        sf_close(txfile);
+        sf_close(rxfile);
+    }
+    sf_close(ecfile);
+}
+/*- End of function --------------------------------------------------------*/
+
+int main(int argc, char *argv[])
+{
+    int i;
+    time_t now;
+    int simulate;
+    int cng;
+    int hpf;
+    int two_channel_file;
+    int opt;
+    int mode;
+
+    /* Check which tests we should run */
+    if (argc < 2)
+        fprintf(stderr, "Usage: echo tests [-g] [-m <model number>] [-s] <list of test numbers>\n");
+    line_model_no = 0;
+    supp_line_model_no = 0;
+    cng = FALSE;
+    hpf = FALSE;
+    use_gui = FALSE;
+    simulate = FALSE;
+    munger = -1;
+    two_channel_file = FALSE;
+    erl = -12.0f;
+
+    while ((opt = getopt(argc, argv, "2ace:ghm:M:su")) != -1)
+    {
+        switch (opt)
+        {
+        case '2':
+            two_channel_file = TRUE;
+            break;
+        case 'a':
+            munger = G711_ALAW;
+            break;
+        case 'c':
+            cng = TRUE;
+            break;
+        case 'e':
+            /* Allow for ERL being entered as x or -x */
+            erl = -fabs(atof(optarg));
+            break;
+        case 'g':
+#if defined(ENABLE_GUI)
+            use_gui = TRUE;
+#else
+            fprintf(stderr, "Graphical monitoring not available\n");
+            exit(2);
+#endif
+            break;
+        case 'h':
+            hpf = TRUE;
+            break;
+        case 'm':
+            line_model_no = atoi(optarg);
+            break;
+        case 'M':
+            supp_line_model_no = atoi(optarg);
+            break;
+        case 's':
+            simulate = TRUE;
+            break;
+        case 'u':
+            munger = G711_ULAW;
+            break;
+        default:
+            //usage();
+            exit(2);
+            break;
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+#if defined(ENABLE_GUI)
+    if (use_gui)
+        start_echo_can_monitor(TEST_EC_TAPS);
+#endif
+    if (simulate)
+    {
+        /* Process a pair of transmitted and received audio files, and produce
+           an echo cancelled audio file. */
+        if (argc < ((two_channel_file)  ?  2  :  3))
+        {
+            printf("not enough arguments for a simulation\n");
+            exit(2);
+        }
+        mode = ECHO_CAN_USE_NLP;
+        mode |= ((cng)  ?  ECHO_CAN_USE_CNG  :  ECHO_CAN_USE_CLIP);
+        if (hpf)
+        {
+            mode |= ECHO_CAN_USE_TX_HPF;
+            mode |= ECHO_CAN_USE_RX_HPF;
+        }
+        simulate_ec(argv, two_channel_file, mode);
+    }
+    else
+    {
+        /* Run some G.168 tests */
+#if defined(ENABLE_GUI)
+        if (use_gui)
+            echo_can_monitor_line_model_update(chan_model.impulse.coeffs, chan_model.impulse.taps);
+#endif
+        signal_load(&local_css, "sound_c1_8k.wav");
+        signal_load(&far_css, "sound_c3_8k.wav");
+
+        if ((residue_handle = sf_open_telephony_write(RESIDUE_FILE_NAME, 1)) == NULL)
+        {
+            fprintf(stderr, "    Failed to open '%s'\n", RESIDUE_FILE_NAME);
+            exit(2);
+        }
+        if (argc <= 0)
+        {
+            printf("No tests specified\n");
+        }
+        else
+        {
+            time(&now);
+            if ((result_handle = sf_open_telephony_write("echo_tests_result.wav", RESULT_CHANNELS)) == NULL)
+            {
+                fprintf(stderr, "    Failed to open result file\n");
+                exit(2);
+            }
+            result_cur = 0;
+            level_measurements_create(0);
+            for (i = 0;  i < argc;  i++)
+            {
+                if (channel_model_create(&chan_model, line_model_no, erl, munger))
+                {
+                    fprintf(stderr, "    Failed to create line model\n");
+                    exit(2);
+                }
+                match_test_name(argv[i]);
+            }
+            if (sf_close(result_handle) != 0)
+            {
+                fprintf(stderr, "    Cannot close speech file '%s'\n", "result_sound.wav");
+                exit(2);
+            }
+            printf("Run time %lds\n", time(NULL) - now);
+        }
+        signal_free(&local_css);
+        signal_free(&far_css);
+        if (sf_close(residue_handle) != 0)
+        {
+            fprintf(stderr, "    Cannot close speech file '%s'\n", RESIDUE_FILE_NAME);
+            exit(2);
+        }
+    }
+#if defined(ENABLE_GUI)
+    if (use_gui)
+        echo_can_monitor_wait_to_end();
+#endif
+
+    printf("Tests passed.\n");
+    return  0;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

Repositories maintained by Peter Meerwald, pmeerw@pmeerw.net.