dahdi-linux/drivers/dahdi/dahdi_echocan_kb1.c
Kinsey Moore 7a7d4000a0 dahdi-base, dahdi_echocan_*, wcb4xxp, wct4xxp, wctdm24xxp, wcte12xp, kernel: Allow name of EC factory to vary based on channel
Changed the echocan factory name to a function (get_name) called to get the
name.  This allows a factory to return a different name when being called in
reference to a channel such as in the case of hardware echo cancellers.  To
further accommodate this change for HWEC, a new echocan_name function was
added to the span ops struct and is used in hwec_factory in dahdi-base for
all cards that support hardware echo cancellation.

Signed-off-by: Kinsey Moore <kmoore@digium.com>
Acked-by: Shaun Ruffell <sruffell@digium.com>

git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@9524 a0bf4364-ded3-4de4-8d8a-66a801d63aff
2010-12-09 20:19:26 +00:00

747 lines
23 KiB
C

/*
* ECHO_CAN_KB1
*
* by Kris Boutilier
*
* Based upon mec2.h
*
* Copyright (C) 2002, Digium, Inc.
*
* Additional background on the techniques used in this code can be found in:
*
* Messerschmitt, David; Hedberg, David; Cole, Christopher; Haoui, Amine;
* Winship, Peter; "Digital Voice Echo Canceller with a TMS32020,"
* in Digital Signal Processing Applications with the TMS320 Family,
* pp. 415-437, Texas Instruments, Inc., 1986.
*
* A pdf of which is available by searching on the document title at http://www.ti.com/
*
*/
/*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2 as published by the
* Free Software Foundation. See the LICENSE file included with
* this program for more details.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/ctype.h>
#include <linux/moduleparam.h>
#include <dahdi/kernel.h>
static int debug;
static int aggressive;
#define module_printk(level, fmt, args...) printk(level "%s: " fmt, THIS_MODULE->name, ## args)
#define debug_printk(level, fmt, args...) if (debug >= level) printk(KERN_DEBUG "%s (%s): " fmt, THIS_MODULE->name, __FUNCTION__, ## args)
/* Uncomment to provide summary statistics for overall echo can performance every 4000 samples */
/* #define MEC2_STATS 4000 */
/* Uncomment to generate per-sample statistics - this will severely degrade system performance and audio quality */
/* #define MEC2_STATS_DETAILED */
/* Get optimized routines for math */
#include "arith.h"
/*
Important constants for tuning kb1 echo can
*/
/* Convergence (aka. adaptation) speed -- higher means slower */
#define DEFAULT_BETA1_I 2048
/* Constants for various power computations */
#define DEFAULT_SIGMA_LY_I 7
#define DEFAULT_SIGMA_LU_I 7
#define DEFAULT_ALPHA_ST_I 5 /* near-end speech detection sensitivity factor */
#define DEFAULT_ALPHA_YT_I 5
#define DEFAULT_CUTOFF_I 128
/* Define the near-end speech hangover counter: if near-end speech
* is declared, hcntr is set equal to hangt (see pg. 432)
*/
#define DEFAULT_HANGT 600 /* in samples, so 600 samples = 75ms */
/* define the residual error suppression threshold */
#define DEFAULT_SUPPR_I 16 /* 16 = -24db */
/* This is the minimum reference signal power estimate level
* that will result in filter adaptation.
* If this is too low then background noise will cause the filter
* coefficients to constantly be updated.
*/
#define MIN_UPDATE_THRESH_I 4096
/* The number of samples used to update coefficients using the
* the block update method (M). It should be related back to the
* length of the echo can.
* ie. it only updates coefficients when (sample number MOD default_m) = 0
*
* Getting this wrong may cause an oops. Consider yourself warned!
*/
#define DEFAULT_M 16 /* every 16th sample */
/* If AGGRESSIVE supression is enabled, then we start cancelling residual
* echos again even while there is potentially the very end of a near-side
* signal present.
* This defines how many samples of DEFAULT_HANGT can remain before we
* kick back in
*/
#define AGGRESSIVE_HCNTR 160 /* in samples, so 160 samples = 20ms */
/***************************************************************/
/* The following knobs are not implemented in the current code */
/* we need a dynamic level of suppression varying with the ratio of the
power of the echo to the power of the reference signal this is
done so that we have a smoother background.
we have a higher suppression when the power ratio is closer to
suppr_ceil and reduces logarithmically as we approach suppr_floor.
*/
#define SUPPR_FLOOR -64
#define SUPPR_CEIL -24
/* in a second departure, we calculate the residual error suppression
* as a percentage of the reference signal energy level. The threshold
* is defined in terms of dB below the reference signal.
*/
#define RES_SUPR_FACTOR -20
#ifndef NULL
#define NULL 0
#endif
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif
/* Generic circular buffer definition */
typedef struct {
/* Pointer to the relative 'start' of the buffer */
int idx_d;
/* The absolute size of the buffer */
int size_d;
/* The actual sample - twice as large as we need, however we do store values at idx_d and idx_d+size_d */
short *buf_d;
} echo_can_cb_s;
static int echo_can_create(struct dahdi_chan *chan, struct dahdi_echocanparams *ecp,
struct dahdi_echocanparam *p, struct dahdi_echocan_state **ec);
static void echo_can_free(struct dahdi_chan *chan, struct dahdi_echocan_state *ec);
static void echo_can_process(struct dahdi_echocan_state *ec, short *isig, const short *iref, u32 size);
static int echo_can_traintap(struct dahdi_echocan_state *ec, int pos, short val);
static void echocan_NLP_toggle(struct dahdi_echocan_state *ec, unsigned int enable);
static const char *name = "KB1";
static const char *ec_name(const struct dahdi_chan *chan) { return name; }
static const struct dahdi_echocan_factory my_factory = {
.get_name = ec_name,
.owner = THIS_MODULE,
.echocan_create = echo_can_create,
};
static const struct dahdi_echocan_features my_features = {
.NLP_toggle = 1,
};
static const struct dahdi_echocan_ops my_ops = {
.echocan_free = echo_can_free,
.echocan_process = echo_can_process,
.echocan_traintap = echo_can_traintap,
.echocan_NLP_toggle = echocan_NLP_toggle,
};
struct ec_pvt {
struct dahdi_echocan_state dahdi;
/* an arbitrary ID for this echo can - this really should be settable from the calling channel... */
int id;
/* absolute time - aka. sample number index - essentially the number of samples since this can was init'ed */
int i_d;
/* Pre-computed constants */
/* ---------------------- */
/* Number of filter coefficents */
int N_d;
/* Rate of adaptation of filter */
int beta2_i;
/* Accumulators for power computations */
/* ----------------------------------- */
/* reference signal power estimate - aka. Average absolute value of y(k) */
int Ly_i;
/* ... */
int Lu_i;
/* Accumulators for signal detectors */
/* --------------------------------- */
/* Power estimate of the recent past of the near-end hybrid signal - aka. Short-time average of: 2 x |s(i)| */
int s_tilde_i;
/* Power estimate of the recent past of the far-end receive signal - aka. Short-time average of: |y(i)| */
int y_tilde_i;
/* Near end speech detection counter - stores Hangover counter time remaining, in samples */
int HCNTR_d;
/* Circular buffers and coefficients */
/* --------------------------------- */
/* ... */
int *a_i;
/* ... */
short *a_s;
/* Reference samples of far-end receive signal */
echo_can_cb_s y_s;
/* Reference samples of near-end signal */
echo_can_cb_s s_s;
/* Reference samples of near-end signal minus echo estimate */
echo_can_cb_s u_s;
/* Reference samples of far-end receive signal used to calculate short-time average */
echo_can_cb_s y_tilde_s;
/* Peak far-end receive signal */
/* --------------------------- */
/* Highest y_tilde value in the sample buffer */
short max_y_tilde;
/* Index of the sample containing the max_y_tilde value */
int max_y_tilde_pos;
#ifdef MEC2_STATS
/* Storage for performance statistics */
int cntr_nearend_speech_frames;
int cntr_residualcorrected_frames;
int cntr_residualcorrected_framesskipped;
int cntr_coeff_updates;
int cntr_coeff_missedupdates;
int avg_Lu_i_toolow;
int avg_Lu_i_ok;
#endif
unsigned int aggressive:1;
int use_nlp;
};
#define dahdi_to_pvt(a) container_of(a, struct ec_pvt, dahdi)
static inline void init_cb_s(echo_can_cb_s *cb, int len, void *where)
{
cb->buf_d = (short *)where;
cb->idx_d = 0;
cb->size_d = len;
}
static inline void add_cc_s(echo_can_cb_s *cb, short newval)
{
/* Can't use modulus because N+M isn't a power of two (generally) */
cb->idx_d--;
if (cb->idx_d < (int)0)
/* Whoops - the pointer to the 'start' wrapped around so reset it to the top of the buffer */
cb->idx_d += cb->size_d;
/* Load two copies into memory */
cb->buf_d[cb->idx_d] = newval;
cb->buf_d[cb->idx_d + cb->size_d] = newval;
}
static inline short get_cc_s(echo_can_cb_s *cb, int pos)
{
/* Load two copies into memory */
return cb->buf_d[cb->idx_d + pos];
}
static inline void init_cc(struct ec_pvt *pvt, int N, int maxy, int maxu)
{
char *ptr = (char *) pvt;
unsigned long tmp;
/* Double-word align past end of state */
ptr += sizeof(*pvt);
tmp = (unsigned long)ptr;
tmp += 3;
tmp &= ~3L;
ptr = (void *)tmp;
/* Reset parameters */
pvt->N_d = N;
pvt->beta2_i = DEFAULT_BETA1_I;
/* Allocate coefficient memory */
pvt->a_i = (int *) ptr;
ptr += (sizeof(int) * pvt->N_d);
pvt->a_s = (short *) ptr;
ptr += (sizeof(short) * pvt->N_d);
/* Reset Y circular buffer (short version) */
init_cb_s(&pvt->y_s, maxy, ptr);
ptr += (sizeof(short) * (maxy) * 2);
/* Reset Sigma circular buffer (short version for FIR filter) */
init_cb_s(&pvt->s_s, (1 << DEFAULT_ALPHA_ST_I), ptr);
ptr += (sizeof(short) * (1 << DEFAULT_ALPHA_ST_I) * 2);
init_cb_s(&pvt->u_s, maxu, ptr);
ptr += (sizeof(short) * maxu * 2);
/* Allocate a buffer for the reference signal power computation */
init_cb_s(&pvt->y_tilde_s, pvt->N_d, ptr);
/* Reset the absolute time index */
pvt->i_d = (int)0;
/* Reset the power computations (for y and u) */
pvt->Ly_i = DEFAULT_CUTOFF_I;
pvt->Lu_i = DEFAULT_CUTOFF_I;
#ifdef MEC2_STATS
/* set the identity */
pvt->id = (int)&ptr;
/* Reset performance stats */
pvt->cntr_nearend_speech_frames = (int)0;
pvt->cntr_residualcorrected_frames = (int)0;
pvt->cntr_residualcorrected_framesskipped = (int)0;
pvt->cntr_coeff_updates = (int)0;
pvt->cntr_coeff_missedupdates = (int)0;
pvt->avg_Lu_i_toolow = (int)0;
pvt->avg_Lu_i_ok = (int)0;
#endif
/* Reset the near-end speech detector */
pvt->s_tilde_i = (int)0;
pvt->y_tilde_i = (int)0;
pvt->HCNTR_d = (int)0;
}
static void echo_can_free(struct dahdi_chan *chan, struct dahdi_echocan_state *ec)
{
struct ec_pvt *pvt = dahdi_to_pvt(ec);
kfree(pvt);
}
static inline short sample_update(struct ec_pvt *pvt, short iref, short isig)
{
/* Declare local variables that are used more than once */
/* ... */
int k;
/* ... */
int rs;
/* ... */
short u;
/* ... */
int Py_i;
/* ... */
int two_beta_i;
/* flow A on pg. 428 */
/* eq. (16): high-pass filter the input to generate the next value;
* push the current value into the circular buffer
*
* sdc_im1_d = sdc_d;
* sdc_d = sig;
* s_i_d = sdc_d;
* s_d = s_i_d;
* s_i_d = (float)(1.0 - gamma_d) * s_i_d
* + (float)(0.5 * (1.0 - gamma_d)) * (sdc_d - sdc_im1_d);
*/
/* Update the Far-end receive signal circular buffers and accumulators */
/* ------------------------------------------------------------------- */
/* Delete the oldest sample from the power estimate accumulator */
pvt->y_tilde_i -= abs(get_cc_s(&pvt->y_s, (1 << DEFAULT_ALPHA_YT_I) - 1)) >> DEFAULT_ALPHA_YT_I;
/* Add the new sample to the power estimate accumulator */
pvt->y_tilde_i += abs(iref) >> DEFAULT_ALPHA_ST_I;
/* Push a copy of the new sample into its circular buffer */
add_cc_s(&pvt->y_s, iref);
/* eq. (2): compute r in fixed-point */
rs = CONVOLVE2(pvt->a_s,
pvt->y_s.buf_d + pvt->y_s.idx_d,
pvt->N_d);
rs >>= 15;
/* eq. (3): compute the output value (see figure 3) and the error
* note: the error is the same as the output signal when near-end
* speech is not present
*/
u = isig - rs;
/* Push a copy of the output value sample into its circular buffer */
add_cc_s(&pvt->u_s, u);
/* Update the Near-end hybrid signal circular buffers and accumulators */
/* ------------------------------------------------------------------- */
/* Delete the oldest sample from the power estimate accumulator */
pvt->s_tilde_i -= abs(get_cc_s(&pvt->s_s, (1 << DEFAULT_ALPHA_ST_I) - 1));
/* Add the new sample to the power estimate accumulator */
pvt->s_tilde_i += abs(isig);
/* Push a copy of the new sample into it's circular buffer */
add_cc_s(&pvt->s_s, isig);
/* Push a copy of the current short-time average of the far-end receive signal into it's circular buffer */
add_cc_s(&pvt->y_tilde_s, pvt->y_tilde_i);
/* flow B on pg. 428 */
/* If the hangover timer isn't running then compute the new convergence factor, otherwise set Py_i to 32768 */
if (!pvt->HCNTR_d) {
Py_i = (pvt->Ly_i >> DEFAULT_SIGMA_LY_I) * (pvt->Ly_i >> DEFAULT_SIGMA_LY_I);
Py_i >>= 15;
} else {
Py_i = (1 << 15);
}
#if 0
/* Vary rate of adaptation depending on position in the file
* Do not do this for the first (DEFAULT_UPDATE_TIME) secs after speech
* has begun of the file to allow the echo cancellor to estimate the
* channel accurately
* Still needs conversion!
*/
if (pvt->start_speech_d != 0) {
if (pvt->i_d > (DEFAULT_T0 + pvt->start_speech_d)*(SAMPLE_FREQ)) {
pvt->beta2_d = max_cc_float(MIN_BETA, DEFAULT_BETA1 * exp((-1/DEFAULT_TAU)*((pvt->i_d/(float)SAMPLE_FREQ) - DEFAULT_T0 - pvt->start_speech_d)));
}
} else {
pvt->beta2_d = DEFAULT_BETA1;
}
#endif
/* Fixed point, inverted */
pvt->beta2_i = DEFAULT_BETA1_I;
/* Fixed point version, inverted */
two_beta_i = (pvt->beta2_i * Py_i) >> 15;
if (!two_beta_i)
two_beta_i++;
/* Update the Suppressed signal power estimate accumulator */
/* ------------------------------------------------------- */
/* Delete the oldest sample from the power estimate accumulator */
pvt->Lu_i -= abs(get_cc_s(&pvt->u_s, (1 << DEFAULT_SIGMA_LU_I) - 1));
/* Add the new sample to the power estimate accumulator */
pvt->Lu_i += abs(u);
/* Update the Far-end reference signal power estimate accumulator */
/* -------------------------------------------------------------- */
/* eq. (10): update power estimate of the reference */
/* Delete the oldest sample from the power estimate accumulator */
pvt->Ly_i -= abs(get_cc_s(&pvt->y_s, (1 << DEFAULT_SIGMA_LY_I) - 1)) ;
/* Add the new sample to the power estimate accumulator */
pvt->Ly_i += abs(iref);
if (pvt->Ly_i < DEFAULT_CUTOFF_I)
pvt->Ly_i = DEFAULT_CUTOFF_I;
/* Update the Peak far-end receive signal detected */
/* ----------------------------------------------- */
if (pvt->y_tilde_i > pvt->max_y_tilde) {
/* New highest y_tilde with full life */
pvt->max_y_tilde = pvt->y_tilde_i;
pvt->max_y_tilde_pos = pvt->N_d - 1;
} else if (--pvt->max_y_tilde_pos < 0) {
/* Time to find new max y tilde... */
pvt->max_y_tilde = MAX16(pvt->y_tilde_s.buf_d + pvt->y_tilde_s.idx_d, pvt->N_d, &pvt->max_y_tilde_pos);
}
/* Determine if near end speech was detected in this sample */
/* -------------------------------------------------------- */
if (((pvt->s_tilde_i >> (DEFAULT_ALPHA_ST_I - 1)) > pvt->max_y_tilde)
&& (pvt->max_y_tilde > 0)) {
/* Then start the Hangover counter */
pvt->HCNTR_d = DEFAULT_HANGT;
#ifdef MEC2_STATS_DETAILED
printk(KERN_INFO "Reset near end speech timer with: s_tilde_i %d, stmnt %d, max_y_tilde %d\n", pvt->s_tilde_i, (pvt->s_tilde_i >> (DEFAULT_ALPHA_ST_I - 1)), pvt->max_y_tilde);
#endif
#ifdef MEC2_STATS
++pvt->cntr_nearend_speech_frames;
#endif
} else if (pvt->HCNTR_d > (int)0) {
/* otherwise, if it's still non-zero, decrement the Hangover counter by one sample */
#ifdef MEC2_STATS
++pvt->cntr_nearend_speech_frames;
#endif
pvt->HCNTR_d--;
}
/* Update coefficients if no near-end speech in this sample (ie. HCNTR_d = 0)
* and we have enough signal to bother trying to update.
* --------------------------------------------------------------------------
*/
if (!pvt->HCNTR_d && /* no near-end speech present */
!(pvt->i_d % DEFAULT_M)) { /* we only update on every DEFAULM_M'th sample from the stream */
if (pvt->Lu_i > MIN_UPDATE_THRESH_I) { /* there is sufficient energy above the noise floor to contain meaningful data */
/* so loop over all the filter coefficients */
#ifdef MEC2_STATS_DETAILED
printk(KERN_INFO "updating coefficients with: pvt->Lu_i %9d\n", pvt->Lu_i);
#endif
#ifdef MEC2_STATS
pvt->avg_Lu_i_ok = pvt->avg_Lu_i_ok + pvt->Lu_i;
++pvt->cntr_coeff_updates;
#endif
for (k = 0; k < pvt->N_d; k++) {
/* eq. (7): compute an expectation over M_d samples */
int grad2;
grad2 = CONVOLVE2(pvt->u_s.buf_d + pvt->u_s.idx_d,
pvt->y_s.buf_d + pvt->y_s.idx_d + k,
DEFAULT_M);
/* eq. (7): update the coefficient */
pvt->a_i[k] += grad2 / two_beta_i;
pvt->a_s[k] = pvt->a_i[k] >> 16;
}
} else {
#ifdef MEC2_STATS_DETAILED
printk(KERN_INFO "insufficient signal to update coefficients pvt->Lu_i %5d < %5d\n", pvt->Lu_i, MIN_UPDATE_THRESH_I);
#endif
#ifdef MEC2_STATS
pvt->avg_Lu_i_toolow = pvt->avg_Lu_i_toolow + pvt->Lu_i;
++pvt->cntr_coeff_missedupdates;
#endif
}
}
/* paragraph below eq. (15): if no near-end speech in the sample and
* the reference signal power estimate > cutoff threshold
* then perform residual error suppression
*/
#ifdef MEC2_STATS_DETAILED
if (pvt->HCNTR_d == 0)
printk(KERN_INFO "possibly correcting frame with pvt->Ly_i %9d pvt->Lu_i %9d and expression %d\n", pvt->Ly_i, pvt->Lu_i, (pvt->Ly_i/(pvt->Lu_i + 1)));
#endif
#ifndef NO_ECHO_SUPPRESSOR
if (pvt->use_nlp) {
if (pvt->aggressive) {
if ((pvt->HCNTR_d < AGGRESSIVE_HCNTR) && (pvt->Ly_i > (pvt->Lu_i << 1))) {
for (k = 0; k < 2; k++) {
u = u * (pvt->Lu_i >> DEFAULT_SIGMA_LU_I) / ((pvt->Ly_i >> (DEFAULT_SIGMA_LY_I)) + 1);
}
#ifdef MEC2_STATS_DETAILED
printk(KERN_INFO "aggresively correcting frame with pvt->Ly_i %9d pvt->Lu_i %9d expression %d\n", pvt->Ly_i, pvt->Lu_i, (pvt->Ly_i/(pvt->Lu_i + 1)));
#endif
#ifdef MEC2_STATS
++pvt->cntr_residualcorrected_frames;
#endif
}
} else {
if (pvt->HCNTR_d == 0) {
if ((pvt->Ly_i/(pvt->Lu_i + 1)) > DEFAULT_SUPPR_I) {
for (k = 0; k < 1; k++) {
u = u * (pvt->Lu_i >> DEFAULT_SIGMA_LU_I) / ((pvt->Ly_i >> (DEFAULT_SIGMA_LY_I + 2)) + 1);
}
#ifdef MEC2_STATS_DETAILED
printk(KERN_INFO "correcting frame with pvt->Ly_i %9d pvt->Lu_i %9d expression %d\n", pvt->Ly_i, pvt->Lu_i, (pvt->Ly_i/(pvt->Lu_i + 1)));
#endif
#ifdef MEC2_STATS
++pvt->cntr_residualcorrected_frames;
#endif
}
#ifdef MEC2_STATS
else {
++pvt->cntr_residualcorrected_framesskipped;
}
#endif
}
}
}
#endif
#if 0
/* This will generate a non-linear supression factor, once converted */
if ((pvt->HCNTR_d == 0) &&
((pvt->Lu_d/pvt->Ly_d) < DEFAULT_SUPPR) &&
(pvt->Lu_d/pvt->Ly_d > EC_MIN_DB_VALUE)) {
suppr_factor = (10 / (float)(SUPPR_FLOOR - SUPPR_CEIL)) * log(pvt->Lu_d/pvt->Ly_d)
- SUPPR_CEIL / (float)(SUPPR_FLOOR - SUPPR_CEIL);
u_suppr = pow(10.0, (suppr_factor) * RES_SUPR_FACTOR / 10.0) * u_suppr;
}
#endif
#ifdef MEC2_STATS
/* Periodically dump performance stats */
if ((pvt->i_d % MEC2_STATS) == 0) {
/* make sure to avoid div0's! */
if (pvt->cntr_coeff_missedupdates > 0)
pvt->avg_Lu_i_toolow = (int)(pvt->avg_Lu_i_toolow / pvt->cntr_coeff_missedupdates);
else
pvt->avg_Lu_i_toolow = -1;
if (pvt->cntr_coeff_updates > 0)
pvt->avg_Lu_i_ok = (pvt->avg_Lu_i_ok / pvt->cntr_coeff_updates);
else
pvt->avg_Lu_i_ok = -1;
printk( KERN_INFO "%d: Near end speech: %5d Residuals corrected/skipped: %5d/%5d Coefficients updated ok/low sig: %3d/%3d Lu_i avg ok/low sig %6d/%5d\n",
pvt->id,
pvt->cntr_nearend_speech_frames,
pvt->cntr_residualcorrected_frames, pvt->cntr_residualcorrected_framesskipped,
pvt->cntr_coeff_updates, pvt->cntr_coeff_missedupdates,
pvt->avg_Lu_i_ok, pvt->avg_Lu_i_toolow);
pvt->cntr_nearend_speech_frames = 0;
pvt->cntr_residualcorrected_frames = 0;
pvt->cntr_residualcorrected_framesskipped = 0;
pvt->cntr_coeff_updates = 0;
pvt->cntr_coeff_missedupdates = 0;
pvt->avg_Lu_i_ok = 0;
pvt->avg_Lu_i_toolow = 0;
}
#endif
/* Increment the sample index and return the corrected sample */
pvt->i_d++;
return u;
}
static void echo_can_process(struct dahdi_echocan_state *ec, short *isig, const short *iref, u32 size)
{
struct ec_pvt *pvt = dahdi_to_pvt(ec);
u32 x;
short result;
for (x = 0; x < size; x++) {
result = sample_update(pvt, *iref, *isig);
*isig++ = result;
++iref;
}
}
static int echo_can_create(struct dahdi_chan *chan, struct dahdi_echocanparams *ecp,
struct dahdi_echocanparam *p, struct dahdi_echocan_state **ec)
{
int maxy;
int maxu;
size_t size;
unsigned int x;
char *c;
struct ec_pvt *pvt;
maxy = ecp->tap_length + DEFAULT_M;
maxu = DEFAULT_M;
if (maxy < (1 << DEFAULT_ALPHA_YT_I))
maxy = (1 << DEFAULT_ALPHA_YT_I);
if (maxy < (1 << DEFAULT_SIGMA_LY_I))
maxy = (1 << DEFAULT_SIGMA_LY_I);
if (maxu < (1 << DEFAULT_SIGMA_LU_I))
maxu = (1 << DEFAULT_SIGMA_LU_I);
size = sizeof(*ec) +
4 + /* align */
sizeof(int) * ecp->tap_length + /* a_i */
sizeof(short) * ecp->tap_length + /* a_s */
2 * sizeof(short) * (maxy) + /* y_s */
2 * sizeof(short) * (1 << DEFAULT_ALPHA_ST_I) + /* s_s */
2 * sizeof(short) * (maxu) + /* u_s */
2 * sizeof(short) * ecp->tap_length; /* y_tilde_s */
pvt = kzalloc(size, GFP_KERNEL);
if (!pvt)
return -ENOMEM;
pvt->dahdi.ops = &my_ops;
pvt->aggressive = aggressive;
pvt->dahdi.features = my_features;
for (x = 0; x < ecp->param_count; x++) {
for (c = p[x].name; *c; c++)
*c = tolower(*c);
if (!strcmp(p[x].name, "aggressive")) {
pvt->aggressive = p[x].value ? 1 : 0;
} else {
printk(KERN_WARNING "Unknown parameter supplied to KB1 echo canceler: '%s'\n", p[x].name);
kfree(pvt);
return -EINVAL;
}
}
init_cc(pvt, ecp->tap_length, maxy, maxu);
/* Non-linear processor - a fancy way to say "zap small signals, to avoid
accumulating noise". */
pvt->use_nlp = TRUE;
*ec = &pvt->dahdi;
return 0;
}
static int echo_can_traintap(struct dahdi_echocan_state *ec, int pos, short val)
{
struct ec_pvt *pvt = dahdi_to_pvt(ec);
/* Set the hangover counter to the length of the can to
* avoid adjustments occuring immediately after initial forced training
*/
pvt->HCNTR_d = pvt->N_d << 1;
if (pos >= pvt->N_d)
return 1;
pvt->a_i[pos] = val << 17;
pvt->a_s[pos] = val << 1;
if (++pos >= pvt->N_d)
return 1;
return 0;
}
static void echocan_NLP_toggle(struct dahdi_echocan_state *ec, unsigned int enable)
{
struct ec_pvt *pvt = dahdi_to_pvt(ec);
pvt->use_nlp = enable ? 1 : 0;
}
static int __init mod_init(void)
{
if (dahdi_register_echocan_factory(&my_factory)) {
module_printk(KERN_ERR, "could not register with DAHDI core\n");
return -EPERM;
}
module_printk(KERN_NOTICE, "Registered echo canceler '%s'\n",
my_factory.get_name(NULL));
return 0;
}
static void __exit mod_exit(void)
{
dahdi_unregister_echocan_factory(&my_factory);
}
module_param(debug, int, S_IRUGO | S_IWUSR);
module_param(aggressive, int, S_IRUGO | S_IWUSR);
MODULE_DESCRIPTION("DAHDI 'KB1' Echo Canceler");
MODULE_AUTHOR("Kris Boutilier");
MODULE_LICENSE("GPL v2");
module_init(mod_init);
module_exit(mod_exit);