123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- /*
- * SpanDSP - a series of DSP components for telephony
- *
- * echo.c - An echo cancellor, suitable for electrical and acoustic
- * cancellation. This code does not currently comply with
- * any relevant standards (e.g. G.164/5/7/8). One day....
- *
- * 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.
- *
- */
- /*
- * 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.
- */
- /* TODO:
- Finish the echo suppressor option, however nasty suppression may be
- Add an option to reintroduce side tone at -24dB under appropriate conditions.
- Improve double talk detector (iterative!)
- */
- #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;
- #include "fir.h"
- #ifndef NULL
- #define NULL 0
- #endif
- #ifndef FALSE
- #define FALSE 0
- #endif
- #ifndef TRUE
- #define TRUE (!FALSE)
- #endif
- #define NONUPDATE_DWELL_TIME 600 /* 600 samples, or 75ms */
- /*
- * According to Jim...
- */
- #define MIN_TX_POWER_FOR_ADAPTION 512
- #define MIN_RX_POWER_FOR_ADAPTION 64
- /*
- * According to Steve...
- */
- /* #define MIN_TX_POWER_FOR_ADAPTION 4096
- #define MIN_RX_POWER_FOR_ADAPTION 64 */
- 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 const char *name = "SEC2";
- 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_ops my_ops = {
- .echocan_free = echo_can_free,
- .echocan_process = echo_can_process,
- .echocan_traintap = echo_can_traintap,
- };
- struct ec_pvt {
- struct dahdi_echocan_state dahdi;
- int tx_power;
- int rx_power;
- int clean_rx_power;
- int rx_power_threshold;
- int nonupdate_dwell;
- fir16_state_t fir_state;
- int16_t *fir_taps16; /* 16-bit version of FIR taps */
- int32_t *fir_taps32; /* 32-bit version of FIR taps */
- int curr_pos;
- int taps;
- int tap_mask;
- int use_nlp;
- int use_suppressor;
- int32_t supp_test1;
- int32_t supp_test2;
- int32_t supp1;
- int32_t supp2;
- int32_t latest_correction; /* Indication of the magnitude of the latest
- adaption, or a code to indicate why adaption
- was skipped, for test purposes */
- };
- #define dahdi_to_pvt(a) container_of(a, struct ec_pvt, dahdi)
- static int echo_can_create(struct dahdi_chan *chan, struct dahdi_echocanparams *ecp,
- struct dahdi_echocanparam *p, struct dahdi_echocan_state **ec)
- {
- struct ec_pvt *pvt;
- size_t size;
- if (ecp->param_count > 0) {
- printk(KERN_WARNING "SEC2 does not support parameters; failing request\n");
- return -EINVAL;
- }
- size = sizeof(*pvt) + ecp->tap_length * sizeof(int32_t) + ecp->tap_length * 3 * sizeof(int16_t);
-
- pvt = kzalloc(size, GFP_KERNEL);
- if (!pvt)
- return -ENOMEM;
- pvt->dahdi.ops = &my_ops;
- if (ecp->param_count > 0) {
- printk(KERN_WARNING "SEC-2 echo canceler does not support parameters; failing request\n");
- return -EINVAL;
- }
- pvt->taps = ecp->tap_length;
- pvt->curr_pos = ecp->tap_length - 1;
- pvt->tap_mask = ecp->tap_length - 1;
- pvt->fir_taps32 = (int32_t *) (pvt + sizeof(*pvt));
- pvt->fir_taps16 = (int16_t *) (pvt + sizeof(*pvt) + ecp->tap_length * sizeof(int32_t));
- /* Create FIR filter */
- fir16_create(&pvt->fir_state, pvt->fir_taps16, pvt->taps);
- pvt->rx_power_threshold = 10000000;
- pvt->use_suppressor = FALSE;
- /* Non-linear processor - a fancy way to say "zap small signals, to avoid
- accumulating noise". */
- pvt->use_nlp = FALSE;
- *ec = &pvt->dahdi;
- return 0;
- }
- static void echo_can_free(struct dahdi_chan *chan, struct dahdi_echocan_state *ec)
- {
- struct ec_pvt *pvt = dahdi_to_pvt(ec);
- fir16_free(&pvt->fir_state);
- kfree(pvt);
- }
- static inline int16_t sample_update(struct ec_pvt *pvt, int16_t tx, int16_t rx)
- {
- int offset1;
- int offset2;
- int32_t echo_value;
- int clean_rx;
- int nsuppr;
- int i;
- int correction;
- /* Evaluate the echo - i.e. apply the FIR filter */
- /* Assume the gain of the FIR does not exceed unity. Exceeding unity
- would seem like a rather poor thing for an echo cancellor to do :)
- This means we can compute the result with a total disregard for
- overflows. 16bits x 16bits -> 31bits, so no overflow can occur in
- any multiply. While accumulating we may overflow and underflow the
- 32 bit scale often. However, if the gain does not exceed unity,
- everything should work itself out, and the final result will be
- OK, without any saturation logic. */
- /* Overflow is very much possible here, and we do nothing about it because
- of the compute costs */
- /* 16 bit coeffs for the LMS give lousy results (maths good, actual sound
- bad!), but 32 bit coeffs require some shifting. On balance 32 bit seems
- best */
- echo_value = fir16 (&pvt->fir_state, tx);
- /* And the answer is..... */
- clean_rx = rx - echo_value;
- /* That was the easy part. Now we need to adapt! */
- if (pvt->nonupdate_dwell > 0)
- pvt->nonupdate_dwell--;
- /* If there is very little being transmitted, any attempt to train is
- futile. We would either be training on the far end's noise or signal,
- the channel's own noise, or our noise. Either way, this is hardly good
- training, so don't do it (avoid trouble). */
- /* If the received power is very low, either we are sending very little or
- we are already well adapted. There is little point in trying to improve
- the adaption under these circumstanceson, so don't do it (reduce the
- compute load). */
- if (pvt->tx_power > MIN_TX_POWER_FOR_ADAPTION && pvt->rx_power > MIN_RX_POWER_FOR_ADAPTION) {
- /* This is a really crude piece of decision logic, but it does OK
- for now. */
- if (pvt->tx_power > 2*pvt->rx_power) {
- /* There is no far-end speech detected */
- if (pvt->nonupdate_dwell == 0) {
- /* ... and we are not in the dwell time from previous speech. */
- /* nsuppr = saturate((clean_rx << 16)/pvt->tx_power); */
- nsuppr = clean_rx >> 3;
- /* Update the FIR taps */
- offset2 = pvt->curr_pos + 1;
- offset1 = pvt->taps - offset2;
- pvt->latest_correction = 0;
- for (i = pvt->taps - 1; i >= offset1; i--) {
- correction = pvt->fir_state.history[i - offset1]*nsuppr;
- /* Leak to avoid false training on signals with multiple
- strong correlations. */
- pvt->fir_taps32[i] -= (pvt->fir_taps32[i] >> 12);
- pvt->fir_taps32[i] += correction;
- pvt->fir_state.coeffs[i] = pvt->fir_taps32[i] >> 15;
- pvt->latest_correction += abs(correction);
- }
- for ( ; i >= 0; i--) {
- correction = pvt->fir_state.history[i + offset2]*nsuppr;
- /* Leak to avoid false training on signals with multiple
- strong correlations. */
- pvt->fir_taps32[i] -= (pvt->fir_taps32[i] >> 12);
- pvt->fir_taps32[i] += correction;
- pvt->fir_state.coeffs[i] = pvt->fir_taps32[i] >> 15;
- pvt->latest_correction += abs(correction);
- }
- } else {
- pvt->latest_correction = -1;
- }
- } else {
- pvt->nonupdate_dwell = NONUPDATE_DWELL_TIME;
- pvt->latest_correction = -2;
- }
- } else {
- pvt->nonupdate_dwell = 0;
- pvt->latest_correction = -3;
- }
- /* Calculate short term power levels using very simple single pole IIRs */
- /* TODO: Is the nasty modulus approach the fastest, or would a real
- tx*tx power calculation actually be faster? */
- pvt->tx_power += ((abs(tx) - pvt->tx_power) >> 5);
- pvt->rx_power += ((abs(rx) - pvt->rx_power) >> 5);
- pvt->clean_rx_power += ((abs(clean_rx) - pvt->clean_rx_power) >> 5);
- #if defined(XYZZY)
- if (pvt->use_suppressor) {
- pvt->supp_test1 += (pvt->fir_state.history[pvt->curr_pos] - pvt->fir_state.history[(pvt->curr_pos - 7) & pvt->tap_mask]);
- pvt->supp_test2 += (pvt->fir_state.history[(pvt->curr_pos - 24) & pvt->tap_mask] - pvt->fir_state.history[(pvt->curr_pos - 31) & pvt->tap_mask]);
- if (pvt->supp_test1 > 42 && pvt->supp_test2 > 42)
- supp_change = 25;
- else
- supp_change = 50;
- supp = supp_change + k1*pvt->supp1 + k2*pvt->supp2;
- pvt->supp2 = pvt->supp1;
- pvt->supp1 = supp;
- clean_rx *= (1 - supp);
- }
- #endif
- if (pvt->use_nlp && pvt->rx_power < 32)
- clean_rx = 0;
- /* Roll around the rolling buffer */
- if (pvt->curr_pos <= 0)
- pvt->curr_pos = pvt->taps;
- pvt->curr_pos--;
- return clean_rx;
- }
- 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_traintap(struct dahdi_echocan_state *ec, int pos, short val)
- {
- struct ec_pvt *pvt = dahdi_to_pvt(ec);
- /* Reset hang counter to avoid adjustments after
- initial forced training */
- pvt->nonupdate_dwell = pvt->taps << 1;
- if (pos >= pvt->taps)
- return 1;
- pvt->fir_taps32[pos] = val << 17;
- pvt->fir_taps16[pos] = val << 1;
- if (++pos >= pvt->taps)
- return 1;
- else
- return 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_DESCRIPTION("DAHDI 'SEC2' Echo Canceler");
- MODULE_AUTHOR("Steve Underwood <steveu@coppice.org>");
- MODULE_LICENSE("GPL");
- module_init(mod_init);
- module_exit(mod_exit);
|