source "drivers/misc/vmw_vmci/Kconfig"
source "drivers/misc/mic/Kconfig"
source "drivers/misc/genwqe/Kconfig"
+source "drivers/misc/echo/Kconfig"
endmenu
obj-$(CONFIG_SRAM) += sram.o
obj-y += mic/
obj-$(CONFIG_GENWQE) += genwqe/
+obj-$(CONFIG_ECHO) += echo/
--- /dev/null
+config ECHO
+ tristate "Line Echo Canceller support"
+ default n
+ ---help---
+ This driver provides line echo cancelling support for mISDN and
+ Zaptel drivers.
+
+ To compile this driver as a module, choose M here. The module
+ will be called echo.
--- /dev/null
+obj-$(CONFIG_ECHO) += echo.o
--- /dev/null
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * echo.c - A line echo canceller. This code is being developed
+ * against and partially complies with G168.
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ * and David Rowe <david_at_rowetel_dot_com>
+ *
+ * Copyright (C) 2001, 2003 Steve Underwood, 2007 David Rowe
+ *
+ * Based on a bit from here, a bit from there, eye of toad, ear of
+ * bat, 15 years of failed attempts by David and a few fried brain
+ * cells.
+ *
+ * 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.
+ */
+
+/*! \file */
+
+/* Implementation Notes
+ David Rowe
+ April 2007
+
+ This code started life as Steve's NLMS algorithm with a tap
+ rotation algorithm to handle divergence during double talk. I
+ added a Geigel Double Talk Detector (DTD) [2] and performed some
+ G168 tests. However I had trouble meeting the G168 requirements,
+ especially for double talk - there were always cases where my DTD
+ failed, for example where near end speech was under the 6dB
+ threshold required for declaring double talk.
+
+ So I tried a two path algorithm [1], which has so far given better
+ results. The original tap rotation/Geigel algorithm is available
+ in SVN http://svn.rowetel.com/software/oslec/tags/before_16bit.
+ It's probably possible to make it work if some one wants to put some
+ serious work into it.
+
+ At present no special treatment is provided for tones, which
+ generally cause NLMS algorithms to diverge. Initial runs of a
+ subset of the G168 tests for tones (e.g ./echo_test 6) show the
+ current algorithm is passing OK, which is kind of surprising. The
+ full set of tests needs to be performed to confirm this result.
+
+ One other interesting change is that I have managed to get the NLMS
+ code to work with 16 bit coefficients, rather than the original 32
+ bit coefficents. This reduces the MIPs and storage required.
+ I evaulated the 16 bit port using g168_tests.sh and listening tests
+ on 4 real-world samples.
+
+ I also attempted the implementation of a block based NLMS update
+ [2] but although this passes g168_tests.sh it didn't converge well
+ on the real-world samples. I have no idea why, perhaps a scaling
+ problem. The block based code is also available in SVN
+ http://svn.rowetel.com/software/oslec/tags/before_16bit. If this
+ code can be debugged, it will lead to further reduction in MIPS, as
+ the block update code maps nicely onto DSP instruction sets (it's a
+ dot product) compared to the current sample-by-sample update.
+
+ Steve also has some nice notes on echo cancellers in echo.h
+
+ References:
+
+ [1] Ochiai, Areseki, and Ogihara, "Echo Canceller with Two Echo
+ Path Models", IEEE Transactions on communications, COM-25,
+ No. 6, June
+ 1977.
+ http://www.rowetel.com/images/echo/dual_path_paper.pdf
+
+ [2] The classic, very useful paper that tells you how to
+ actually build a real world echo canceller:
+ Messerschmitt, Hedberg, Cole, Haoui, Winship, "Digital Voice
+ Echo Canceller with a TMS320020,
+ http://www.rowetel.com/images/echo/spra129.pdf
+
+ [3] I have written a series of blog posts on this work, here is
+ Part 1: http://www.rowetel.com/blog/?p=18
+
+ [4] The source code http://svn.rowetel.com/software/oslec/
+
+ [5] A nice reference on LMS filters:
+ http://en.wikipedia.org/wiki/Least_mean_squares_filter
+
+ Credits:
+
+ Thanks to Steve Underwood, Jean-Marc Valin, and Ramakrishnan
+ Muthukrishnan for their suggestions and email discussions. Thanks
+ also to those people who collected echo samples for me such as
+ Mark, Pawel, and Pavel.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "echo.h"
+
+#define MIN_TX_POWER_FOR_ADAPTION 64
+#define MIN_RX_POWER_FOR_ADAPTION 64
+#define DTD_HANGOVER 600 /* 600 samples, or 75ms */
+#define DC_LOG2BETA 3 /* log2() of DC filter Beta */
+
+/* adapting coeffs using the traditional stochastic descent (N)LMS algorithm */
+
+#ifdef __bfin__
+static inline void lms_adapt_bg(struct oslec_state *ec, int clean, int shift)
+{
+ int i;
+ int offset1;
+ int offset2;
+ int factor;
+ int exp;
+ int16_t *phist;
+ int n;
+
+ if (shift > 0)
+ factor = clean << shift;
+ else
+ factor = clean >> -shift;
+
+ /* Update the FIR taps */
+
+ offset2 = ec->curr_pos;
+ offset1 = ec->taps - offset2;
+ phist = &ec->fir_state_bg.history[offset2];
+
+ /* st: and en: help us locate the assembler in echo.s */
+
+ /* asm("st:"); */
+ n = ec->taps;
+ for (i = 0; i < n; i++) {
+ exp = *phist++ * factor;
+ ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15);
+ }
+ /* asm("en:"); */
+
+ /* Note the asm for the inner loop above generated by Blackfin gcc
+ 4.1.1 is pretty good (note even parallel instructions used):
+
+ R0 = W [P0++] (X);
+ R0 *= R2;
+ R0 = R0 + R3 (NS) ||
+ R1 = W [P1] (X) ||
+ nop;
+ R0 >>>= 15;
+ R0 = R0 + R1;
+ W [P1++] = R0;
+
+ A block based update algorithm would be much faster but the
+ above can't be improved on much. Every instruction saved in
+ the loop above is 2 MIPs/ch! The for loop above is where the
+ Blackfin spends most of it's time - about 17 MIPs/ch measured
+ with speedtest.c with 256 taps (32ms). Write-back and
+ Write-through cache gave about the same performance.
+ */
+}
+
+/*
+ IDEAS for further optimisation of lms_adapt_bg():
+
+ 1/ The rounding is quite costly. Could we keep as 32 bit coeffs
+ then make filter pluck the MS 16-bits of the coeffs when filtering?
+ However this would lower potential optimisation of filter, as I
+ think the dual-MAC architecture requires packed 16 bit coeffs.
+
+ 2/ Block based update would be more efficient, as per comments above,
+ could use dual MAC architecture.
+
+ 3/ Look for same sample Blackfin LMS code, see if we can get dual-MAC
+ packing.
+
+ 4/ Execute the whole e/c in a block of say 20ms rather than sample
+ by sample. Processing a few samples every ms is inefficient.
+*/
+
+#else
+static inline void lms_adapt_bg(struct oslec_state *ec, int clean, int shift)
+{
+ int i;
+
+ int offset1;
+ int offset2;
+ int factor;
+ int exp;
+
+ if (shift > 0)
+ factor = clean << shift;
+ else
+ factor = clean >> -shift;
+
+ /* Update the FIR taps */
+
+ offset2 = ec->curr_pos;
+ offset1 = ec->taps - offset2;
+
+ for (i = ec->taps - 1; i >= offset1; i--) {
+ exp = (ec->fir_state_bg.history[i - offset1] * factor);
+ ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15);
+ }
+ for (; i >= 0; i--) {
+ exp = (ec->fir_state_bg.history[i + offset2] * factor);
+ ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15);
+ }
+}
+#endif
+
+static inline int top_bit(unsigned int bits)
+{
+ if (bits == 0)
+ return -1;
+ else
+ return (int)fls((int32_t) bits) - 1;
+}
+
+struct oslec_state *oslec_create(int len, int adaption_mode)
+{
+ struct oslec_state *ec;
+ int i;
+ const int16_t *history;
+
+ ec = kzalloc(sizeof(*ec), GFP_KERNEL);
+ if (!ec)
+ return NULL;
+
+ ec->taps = len;
+ ec->log2taps = top_bit(len);
+ ec->curr_pos = ec->taps - 1;
+
+ ec->fir_taps16[0] =
+ kcalloc(ec->taps, sizeof(int16_t), GFP_KERNEL);
+ if (!ec->fir_taps16[0])
+ goto error_oom_0;
+
+ ec->fir_taps16[1] =
+ kcalloc(ec->taps, sizeof(int16_t), GFP_KERNEL);
+ if (!ec->fir_taps16[1])
+ goto error_oom_1;
+
+ history = fir16_create(&ec->fir_state, ec->fir_taps16[0], ec->taps);
+ if (!history)
+ goto error_state;
+ history = fir16_create(&ec->fir_state_bg, ec->fir_taps16[1], ec->taps);
+ if (!history)
+ goto error_state_bg;
+
+ for (i = 0; i < 5; i++)
+ ec->xvtx[i] = ec->yvtx[i] = ec->xvrx[i] = ec->yvrx[i] = 0;
+
+ ec->cng_level = 1000;
+ oslec_adaption_mode(ec, adaption_mode);
+
+ ec->snapshot = kcalloc(ec->taps, sizeof(int16_t), GFP_KERNEL);
+ if (!ec->snapshot)
+ goto error_snap;
+
+ ec->cond_met = 0;
+ ec->pstates = 0;
+ ec->ltxacc = ec->lrxacc = ec->lcleanacc = ec->lclean_bgacc = 0;
+ ec->ltx = ec->lrx = ec->lclean = ec->lclean_bg = 0;
+ ec->tx_1 = ec->tx_2 = ec->rx_1 = ec->rx_2 = 0;
+ ec->lbgn = ec->lbgn_acc = 0;
+ ec->lbgn_upper = 200;
+ ec->lbgn_upper_acc = ec->lbgn_upper << 13;
+
+ return ec;
+
+error_snap:
+ fir16_free(&ec->fir_state_bg);
+error_state_bg:
+ fir16_free(&ec->fir_state);
+error_state:
+ kfree(ec->fir_taps16[1]);
+error_oom_1:
+ kfree(ec->fir_taps16[0]);
+error_oom_0:
+ kfree(ec);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(oslec_create);
+
+void oslec_free(struct oslec_state *ec)
+{
+ int i;
+
+ fir16_free(&ec->fir_state);
+ fir16_free(&ec->fir_state_bg);
+ for (i = 0; i < 2; i++)
+ kfree(ec->fir_taps16[i]);
+ kfree(ec->snapshot);
+ kfree(ec);
+}
+EXPORT_SYMBOL_GPL(oslec_free);
+
+void oslec_adaption_mode(struct oslec_state *ec, int adaption_mode)
+{
+ ec->adaption_mode = adaption_mode;
+}
+EXPORT_SYMBOL_GPL(oslec_adaption_mode);
+
+void oslec_flush(struct oslec_state *ec)
+{
+ int i;
+
+ ec->ltxacc = ec->lrxacc = ec->lcleanacc = ec->lclean_bgacc = 0;
+ ec->ltx = ec->lrx = ec->lclean = ec->lclean_bg = 0;
+ ec->tx_1 = ec->tx_2 = ec->rx_1 = ec->rx_2 = 0;
+
+ ec->lbgn = ec->lbgn_acc = 0;
+ ec->lbgn_upper = 200;
+ ec->lbgn_upper_acc = ec->lbgn_upper << 13;
+
+ ec->nonupdate_dwell = 0;
+
+ fir16_flush(&ec->fir_state);
+ fir16_flush(&ec->fir_state_bg);
+ ec->fir_state.curr_pos = ec->taps - 1;
+ ec->fir_state_bg.curr_pos = ec->taps - 1;
+ for (i = 0; i < 2; i++)
+ memset(ec->fir_taps16[i], 0, ec->taps * sizeof(int16_t));
+
+ ec->curr_pos = ec->taps - 1;
+ ec->pstates = 0;
+}
+EXPORT_SYMBOL_GPL(oslec_flush);
+
+void oslec_snapshot(struct oslec_state *ec)
+{
+ memcpy(ec->snapshot, ec->fir_taps16[0], ec->taps * sizeof(int16_t));
+}
+EXPORT_SYMBOL_GPL(oslec_snapshot);
+
+/* Dual Path Echo Canceller */
+
+int16_t oslec_update(struct oslec_state *ec, int16_t tx, int16_t rx)
+{
+ int32_t echo_value;
+ int clean_bg;
+ int tmp;
+ int tmp1;
+
+ /*
+ * Input scaling was found be required to prevent problems when tx
+ * starts clipping. Another possible way to handle this would be the
+ * filter coefficent scaling.
+ */
+
+ ec->tx = tx;
+ ec->rx = rx;
+ tx >>= 1;
+ rx >>= 1;
+
+ /*
+ * Filter DC, 3dB point is 160Hz (I think), note 32 bit precision
+ * required otherwise values do not track down to 0. Zero at DC, Pole
+ * at (1-Beta) on real axis. Some chip sets (like Si labs) don't
+ * need this, but something like a $10 X100P card does. Any DC really
+ * slows down convergence.
+ *
+ * Note: removes some low frequency from the signal, this reduces the
+ * speech quality when listening to samples through headphones but may
+ * not be obvious through a telephone handset.
+ *
+ * Note that the 3dB frequency in radians is approx Beta, e.g. for Beta
+ * = 2^(-3) = 0.125, 3dB freq is 0.125 rads = 159Hz.
+ */
+
+ if (ec->adaption_mode & ECHO_CAN_USE_RX_HPF) {
+ tmp = rx << 15;
+
+ /*
+ * Make sure the gain of the HPF is 1.0. This can still
+ * saturate a little under impulse conditions, and it might
+ * roll to 32768 and need clipping on sustained peak level
+ * signals. However, the scale of such clipping is small, and
+ * the error due to any saturation should not markedly affect
+ * the downstream processing.
+ */
+ tmp -= (tmp >> 4);
+
+ ec->rx_1 += -(ec->rx_1 >> DC_LOG2BETA) + tmp - ec->rx_2;
+
+ /*
+ * hard limit filter to prevent clipping. Note that at this
+ * stage rx should be limited to +/- 16383 due to right shift
+ * above
+ */
+ tmp1 = ec->rx_1 >> 15;
+ if (tmp1 > 16383)
+ tmp1 = 16383;
+ if (tmp1 < -16383)
+ tmp1 = -16383;
+ rx = tmp1;
+ ec->rx_2 = tmp;
+ }
+
+ /* Block average of power in the filter states. Used for
+ adaption power calculation. */
+
+ {
+ int new, old;
+
+ /* efficient "out with the old and in with the new" algorithm so
+ we don't have to recalculate over the whole block of
+ samples. */
+ new = (int)tx * (int)tx;
+ old = (int)ec->fir_state.history[ec->fir_state.curr_pos] *
+ (int)ec->fir_state.history[ec->fir_state.curr_pos];
+ ec->pstates +=
+ ((new - old) + (1 << (ec->log2taps - 1))) >> ec->log2taps;
+ if (ec->pstates < 0)
+ ec->pstates = 0;
+ }
+
+ /* Calculate short term average levels using simple single pole IIRs */
+
+ ec->ltxacc += abs(tx) - ec->ltx;
+ ec->ltx = (ec->ltxacc + (1 << 4)) >> 5;
+ ec->lrxacc += abs(rx) - ec->lrx;
+ ec->lrx = (ec->lrxacc + (1 << 4)) >> 5;
+
+ /* Foreground filter */
+
+ ec->fir_state.coeffs = ec->fir_taps16[0];
+ echo_value = fir16(&ec->fir_state, tx);
+ ec->clean = rx - echo_value;
+ ec->lcleanacc += abs(ec->clean) - ec->lclean;
+ ec->lclean = (ec->lcleanacc + (1 << 4)) >> 5;
+
+ /* Background filter */
+
+ echo_value = fir16(&ec->fir_state_bg, tx);
+ clean_bg = rx - echo_value;
+ ec->lclean_bgacc += abs(clean_bg) - ec->lclean_bg;
+ ec->lclean_bg = (ec->lclean_bgacc + (1 << 4)) >> 5;
+
+ /* Background Filter adaption */
+
+ /* Almost always adap bg filter, just simple DT and energy
+ detection to minimise adaption in cases of strong double talk.
+ However this is not critical for the dual path algorithm.
+ */
+ ec->factor = 0;
+ ec->shift = 0;
+ if ((ec->nonupdate_dwell == 0)) {
+ int p, logp, shift;
+
+ /* Determine:
+
+ f = Beta * clean_bg_rx/P ------ (1)
+
+ where P is the total power in the filter states.
+
+ The Boffins have shown that if we obey (1) we converge
+ quickly and avoid instability.
+
+ The correct factor f must be in Q30, as this is the fixed
+ point format required by the lms_adapt_bg() function,
+ therefore the scaled version of (1) is:
+
+ (2^30) * f = (2^30) * Beta * clean_bg_rx/P
+ factor = (2^30) * Beta * clean_bg_rx/P ----- (2)
+
+ We have chosen Beta = 0.25 by experiment, so:
+
+ factor = (2^30) * (2^-2) * clean_bg_rx/P
+
+ (30 - 2 - log2(P))
+ factor = clean_bg_rx 2 ----- (3)
+
+ To avoid a divide we approximate log2(P) as top_bit(P),
+ which returns the position of the highest non-zero bit in
+ P. This approximation introduces an error as large as a
+ factor of 2, but the algorithm seems to handle it OK.
+
+ Come to think of it a divide may not be a big deal on a
+ modern DSP, so its probably worth checking out the cycles
+ for a divide versus a top_bit() implementation.
+ */
+
+ p = MIN_TX_POWER_FOR_ADAPTION + ec->pstates;
+ logp = top_bit(p) + ec->log2taps;
+ shift = 30 - 2 - logp;
+ ec->shift = shift;
+
+ lms_adapt_bg(ec, clean_bg, shift);
+ }
+
+ /* very simple DTD to make sure we dont try and adapt with strong
+ near end speech */
+
+ ec->adapt = 0;
+ if ((ec->lrx > MIN_RX_POWER_FOR_ADAPTION) && (ec->lrx > ec->ltx))
+ ec->nonupdate_dwell = DTD_HANGOVER;
+ if (ec->nonupdate_dwell)
+ ec->nonupdate_dwell--;
+
+ /* Transfer logic */
+
+ /* These conditions are from the dual path paper [1], I messed with
+ them a bit to improve performance. */
+
+ if ((ec->adaption_mode & ECHO_CAN_USE_ADAPTION) &&
+ (ec->nonupdate_dwell == 0) &&
+ /* (ec->Lclean_bg < 0.875*ec->Lclean) */
+ (8 * ec->lclean_bg < 7 * ec->lclean) &&
+ /* (ec->Lclean_bg < 0.125*ec->Ltx) */
+ (8 * ec->lclean_bg < ec->ltx)) {
+ if (ec->cond_met == 6) {
+ /*
+ * BG filter has had better results for 6 consecutive
+ * samples
+ */
+ ec->adapt = 1;
+ memcpy(ec->fir_taps16[0], ec->fir_taps16[1],
+ ec->taps * sizeof(int16_t));
+ } else
+ ec->cond_met++;
+ } else
+ ec->cond_met = 0;
+
+ /* Non-Linear Processing */
+
+ ec->clean_nlp = ec->clean;
+ if (ec->adaption_mode & ECHO_CAN_USE_NLP) {
+ /*
+ * Non-linear processor - a fancy way to say "zap small
+ * signals, to avoid residual echo due to (uLaw/ALaw)
+ * non-linearity in the channel.".
+ */
+
+ if ((16 * ec->lclean < ec->ltx)) {
+ /*
+ * Our e/c has improved echo by at least 24 dB (each
+ * factor of 2 is 6dB, so 2*2*2*2=16 is the same as
+ * 6+6+6+6=24dB)
+ */
+ if (ec->adaption_mode & ECHO_CAN_USE_CNG) {
+ ec->cng_level = ec->lbgn;
+
+ /*
+ * Very elementary comfort noise generation.
+ * Just random numbers rolled off very vaguely
+ * Hoth-like. DR: This noise doesn't sound
+ * quite right to me - I suspect there are some
+ * overflow issues in the filtering as it's too
+ * "crackly".
+ * TODO: debug this, maybe just play noise at
+ * high level or look at spectrum.
+ */
+
+ ec->cng_rndnum =
+ 1664525U * ec->cng_rndnum + 1013904223U;
+ ec->cng_filter =
+ ((ec->cng_rndnum & 0xFFFF) - 32768 +
+ 5 * ec->cng_filter) >> 3;
+ ec->clean_nlp =
+ (ec->cng_filter * ec->cng_level * 8) >> 14;
+
+ } else if (ec->adaption_mode & ECHO_CAN_USE_CLIP) {
+ /* This sounds much better than CNG */
+ if (ec->clean_nlp > ec->lbgn)
+ ec->clean_nlp = ec->lbgn;
+ if (ec->clean_nlp < -ec->lbgn)
+ ec->clean_nlp = -ec->lbgn;
+ } else {
+ /*
+ * just mute the residual, doesn't sound very
+ * good, used mainly in G168 tests
+ */
+ ec->clean_nlp = 0;
+ }
+ } else {
+ /*
+ * Background noise estimator. I tried a few
+ * algorithms here without much luck. This very simple
+ * one seems to work best, we just average the level
+ * using a slow (1 sec time const) filter if the
+ * current level is less than a (experimentally
+ * derived) constant. This means we dont include high
+ * level signals like near end speech. When combined
+ * with CNG or especially CLIP seems to work OK.
+ */
+ if (ec->lclean < 40) {
+ ec->lbgn_acc += abs(ec->clean) - ec->lbgn;
+ ec->lbgn = (ec->lbgn_acc + (1 << 11)) >> 12;
+ }
+ }
+ }
+
+ /* Roll around the taps buffer */
+ if (ec->curr_pos <= 0)
+ ec->curr_pos = ec->taps;
+ ec->curr_pos--;
+
+ if (ec->adaption_mode & ECHO_CAN_DISABLE)
+ ec->clean_nlp = rx;
+
+ /* Output scaled back up again to match input scaling */
+
+ return (int16_t) ec->clean_nlp << 1;
+}
+EXPORT_SYMBOL_GPL(oslec_update);
+
+/* This function is separated from the echo canceller is it is usually called
+ as part of the tx process. See rx HP (DC blocking) filter above, it's
+ the same design.
+
+ Some soft phones send speech signals with a lot of low frequency
+ energy, e.g. down to 20Hz. This can make the hybrid non-linear
+ which causes the echo canceller to fall over. This filter can help
+ by removing any low frequency before it gets to the tx port of the
+ hybrid.
+
+ It can also help by removing and DC in the tx signal. DC is bad
+ for LMS algorithms.
+
+ This is one of the classic DC removal filters, adjusted to provide
+ sufficient bass rolloff to meet the above requirement to protect hybrids
+ from things that upset them. The difference between successive samples
+ produces a lousy HPF, and then a suitably placed pole flattens things out.
+ The final result is a nicely rolled off bass end. The filtering is
+ implemented with extended fractional precision, which noise shapes things,
+ giving very clean DC removal.
+*/
+
+int16_t oslec_hpf_tx(struct oslec_state *ec, int16_t tx)
+{
+ int tmp;
+ int tmp1;
+
+ if (ec->adaption_mode & ECHO_CAN_USE_TX_HPF) {
+ tmp = tx << 15;
+
+ /*
+ * Make sure the gain of the HPF is 1.0. The first can still
+ * saturate a little under impulse conditions, and it might
+ * roll to 32768 and need clipping on sustained peak level
+ * signals. However, the scale of such clipping is small, and
+ * the error due to any saturation should not markedly affect
+ * the downstream processing.
+ */
+ tmp -= (tmp >> 4);
+
+ ec->tx_1 += -(ec->tx_1 >> DC_LOG2BETA) + tmp - ec->tx_2;
+ tmp1 = ec->tx_1 >> 15;
+ if (tmp1 > 32767)
+ tmp1 = 32767;
+ if (tmp1 < -32767)
+ tmp1 = -32767;
+ tx = tmp1;
+ ec->tx_2 = tmp;
+ }
+
+ return tx;
+}
+EXPORT_SYMBOL_GPL(oslec_hpf_tx);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Rowe");
+MODULE_DESCRIPTION("Open Source Line Echo Canceller");
+MODULE_VERSION("0.3.0");
--- /dev/null
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * echo.c - A line echo canceller. This code is being developed
+ * against and partially complies with G168.
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ * and David Rowe <david_at_rowetel_dot_com>
+ *
+ * Copyright (C) 2001 Steve Underwood and 2007 David Rowe
+ *
+ * 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.
+ */
+
+#ifndef __ECHO_H
+#define __ECHO_H
+
+/*
+Line echo cancellation for voice
+
+What does it do?
+
+This module aims to provide G.168-2002 compliant echo cancellation, to remove
+electrical echoes (e.g. from 2-4 wire hybrids) from voice calls.
+
+How does it work?
+
+The heart of the echo cancellor is FIR filter. This is adapted to match the
+echo impulse response of the telephone line. It must be long enough to
+adequately cover the duration of that impulse response. The signal transmitted
+to the telephone line is passed through the FIR filter. Once the FIR is
+properly adapted, the resulting output is an estimate of the echo signal
+received from the line. This is subtracted from the received signal. The result
+is an estimate of the signal which originated at the far end of the line, free
+from echos of our own transmitted signal.
+
+The least mean squares (LMS) algorithm is attributed to Widrow and Hoff, and
+was introduced in 1960. It is the commonest form of filter adaption used in
+things like modem line equalisers and line echo cancellers. There it works very
+well. However, it only works well for signals of constant amplitude. It works
+very poorly for things like speech echo cancellation, where the signal level
+varies widely. This is quite easy to fix. If the signal level is normalised -
+similar to applying AGC - LMS can work as well for a signal of varying
+amplitude as it does for a modem signal. This normalised least mean squares
+(NLMS) algorithm is the commonest one used for speech echo cancellation. Many
+other algorithms exist - e.g. RLS (essentially the same as Kalman filtering),
+FAP, etc. Some perform significantly better than NLMS. However, factors such
+as computational complexity and patents favour the use of NLMS.
+
+A simple refinement to NLMS can improve its performance with speech. NLMS tends
+to adapt best to the strongest parts of a signal. If the signal is white noise,
+the NLMS algorithm works very well. However, speech has more low frequency than
+high frequency content. Pre-whitening (i.e. filtering the signal to flatten its
+spectrum) the echo signal improves the adapt rate for speech, and ensures the
+final residual signal is not heavily biased towards high frequencies. A very
+low complexity filter is adequate for this, so pre-whitening adds little to the
+compute requirements of the echo canceller.
+
+An FIR filter adapted using pre-whitened NLMS performs well, provided certain
+conditions are met:
+
+ - The transmitted signal has poor self-correlation.
+ - There is no signal being generated within the environment being
+ cancelled.
+
+The difficulty is that neither of these can be guaranteed.
+
+If the adaption is performed while transmitting noise (or something fairly
+noise like, such as voice) the adaption works very well. If the adaption is
+performed while transmitting something highly correlative (typically narrow
+band energy such as signalling tones or DTMF), the adaption can go seriously
+wrong. The reason is there is only one solution for the adaption on a near
+random signal - the impulse response of the line. For a repetitive signal,
+there are any number of solutions which converge the adaption, and nothing
+guides the adaption to choose the generalised one. Allowing an untrained
+canceller to converge on this kind of narrowband energy probably a good thing,
+since at least it cancels the tones. Allowing a well converged canceller to
+continue converging on such energy is just a way to ruin its generalised
+adaption. A narrowband detector is needed, so adapation can be suspended at
+appropriate times.
+
+The adaption process is based on trying to eliminate the received signal. When
+there is any signal from within the environment being cancelled it may upset
+the adaption process. Similarly, if the signal we are transmitting is small,
+noise may dominate and disturb the adaption process. If we can ensure that the
+adaption is only performed when we are transmitting a significant signal level,
+and the environment is not, things will be OK. Clearly, it is easy to tell when
+we are sending a significant signal. Telling, if the environment is generating
+a significant signal, and doing it with sufficient speed that the adaption will
+not have diverged too much more we stop it, is a little harder.
+
+The key problem in detecting when the environment is sourcing significant
+energy is that we must do this very quickly. Given a reasonably long sample of
+the received signal, there are a number of strategies which may be used to
+assess whether that signal contains a strong far end component. However, by the
+time that assessment is complete the far end signal will have already caused
+major mis-convergence in the adaption process. An assessment algorithm is
+needed which produces a fairly accurate result from a very short burst of far
+end energy.
+
+How do I use it?
+
+The echo cancellor processes both the transmit and receive streams sample by
+sample. The processing function is not declared inline. Unfortunately,
+cancellation requires many operations per sample, so the call overhead is only
+a minor burden.
+*/
+
+#include "fir.h"
+#include "oslec.h"
+
+/*
+ G.168 echo canceller descriptor. This defines the working state for a line
+ echo canceller.
+*/
+struct oslec_state {
+ int16_t tx;
+ int16_t rx;
+ int16_t clean;
+ int16_t clean_nlp;
+
+ int nonupdate_dwell;
+ int curr_pos;
+ int taps;
+ int log2taps;
+ int adaption_mode;
+
+ int cond_met;
+ int32_t pstates;
+ int16_t adapt;
+ int32_t factor;
+ int16_t shift;
+
+ /* Average levels and averaging filter states */
+ int ltxacc;
+ int lrxacc;
+ int lcleanacc;
+ int lclean_bgacc;
+ int ltx;
+ int lrx;
+ int lclean;
+ int lclean_bg;
+ int lbgn;
+ int lbgn_acc;
+ int lbgn_upper;
+ int lbgn_upper_acc;
+
+ /* foreground and background filter states */
+ struct fir16_state_t fir_state;
+ struct fir16_state_t fir_state_bg;
+ int16_t *fir_taps16[2];
+
+ /* DC blocking filter states */
+ int tx_1;
+ int tx_2;
+ int rx_1;
+ int rx_2;
+
+ /* optional High Pass Filter states */
+ int32_t xvtx[5];
+ int32_t yvtx[5];
+ int32_t xvrx[5];
+ int32_t yvrx[5];
+
+ /* Parameters for the optional Hoth noise generator */
+ int cng_level;
+ int cng_rndnum;
+ int cng_filter;
+
+ /* snapshot sample of coeffs used for development */
+ int16_t *snapshot;
+};
+
+#endif /* __ECHO_H */
--- /dev/null
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * fir.h - General telephony FIR routines
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2002 Steve Underwood
+ *
+ * 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.
+ */
+
+#if !defined(_FIR_H_)
+#define _FIR_H_
+
+/*
+ Blackfin NOTES & IDEAS:
+
+ A simple dot product function is used to implement the filter. This performs
+ just one MAC/cycle which is inefficient but was easy to implement as a first
+ pass. The current Blackfin code also uses an unrolled form of the filter
+ history to avoid 0 length hardware loop issues. This is wasteful of
+ memory.
+
+ Ideas for improvement:
+
+ 1/ Rewrite filter for dual MAC inner loop. The issue here is handling
+ history sample offsets that are 16 bit aligned - the dual MAC needs
+ 32 bit aligmnent. There are some good examples in libbfdsp.
+
+ 2/ Use the hardware circular buffer facility tohalve memory usage.
+
+ 3/ Consider using internal memory.
+
+ Using less memory might also improve speed as cache misses will be
+ reduced. A drop in MIPs and memory approaching 50% should be
+ possible.
+
+ The foreground and background filters currenlty use a total of
+ about 10 MIPs/ch as measured with speedtest.c on a 256 TAP echo
+ can.
+*/
+
+/*
+ * 16 bit integer FIR descriptor. This defines the working state for a single
+ * instance of an FIR filter using 16 bit integer coefficients.
+ */
+struct fir16_state_t {
+ int taps;
+ int curr_pos;
+ const int16_t *coeffs;
+ int16_t *history;
+};
+
+/*
+ * 32 bit integer FIR descriptor. This defines the working state for a single
+ * instance of an FIR filter using 32 bit integer coefficients, and filtering
+ * 16 bit integer data.
+ */
+struct fir32_state_t {
+ int taps;
+ int curr_pos;
+ const int32_t *coeffs;
+ int16_t *history;
+};
+
+/*
+ * Floating point FIR descriptor. This defines the working state for a single
+ * instance of an FIR filter using floating point coefficients and data.
+ */
+struct fir_float_state_t {
+ int taps;
+ int curr_pos;
+ const float *coeffs;
+ float *history;
+};
+
+static inline const int16_t *fir16_create(struct fir16_state_t *fir,
+ const int16_t *coeffs, int taps)
+{
+ fir->taps = taps;
+ fir->curr_pos = taps - 1;
+ fir->coeffs = coeffs;
+#if defined(__bfin__)
+ fir->history = kcalloc(2 * taps, sizeof(int16_t), GFP_KERNEL);
+#else
+ fir->history = kcalloc(taps, sizeof(int16_t), GFP_KERNEL);
+#endif
+ return fir->history;
+}
+
+static inline void fir16_flush(struct fir16_state_t *fir)
+{
+#if defined(__bfin__)
+ memset(fir->history, 0, 2 * fir->taps * sizeof(int16_t));
+#else
+ memset(fir->history, 0, fir->taps * sizeof(int16_t));
+#endif
+}
+
+static inline void fir16_free(struct fir16_state_t *fir)
+{
+ kfree(fir->history);
+}
+
+#ifdef __bfin__
+static inline int32_t dot_asm(short *x, short *y, int len)
+{
+ int dot;
+
+ len--;
+
+ __asm__("I0 = %1;\n\t"
+ "I1 = %2;\n\t"
+ "A0 = 0;\n\t"
+ "R0.L = W[I0++] || R1.L = W[I1++];\n\t"
+ "LOOP dot%= LC0 = %3;\n\t"
+ "LOOP_BEGIN dot%=;\n\t"
+ "A0 += R0.L * R1.L (IS) || R0.L = W[I0++] || R1.L = W[I1++];\n\t"
+ "LOOP_END dot%=;\n\t"
+ "A0 += R0.L*R1.L (IS);\n\t"
+ "R0 = A0;\n\t"
+ "%0 = R0;\n\t"
+ : "=&d"(dot)
+ : "a"(x), "a"(y), "a"(len)
+ : "I0", "I1", "A1", "A0", "R0", "R1"
+ );
+
+ return dot;
+}
+#endif
+
+static inline int16_t fir16(struct fir16_state_t *fir, int16_t sample)
+{
+ int32_t y;
+#if defined(__bfin__)
+ fir->history[fir->curr_pos] = sample;
+ fir->history[fir->curr_pos + fir->taps] = sample;
+ y = dot_asm((int16_t *) fir->coeffs, &fir->history[fir->curr_pos],
+ fir->taps);
+#else
+ int i;
+ int offset1;
+ int offset2;
+
+ fir->history[fir->curr_pos] = sample;
+
+ offset2 = fir->curr_pos;
+ offset1 = fir->taps - offset2;
+ y = 0;
+ for (i = fir->taps - 1; i >= offset1; i--)
+ y += fir->coeffs[i] * fir->history[i - offset1];
+ for (; i >= 0; i--)
+ y += fir->coeffs[i] * fir->history[i + offset2];
+#endif
+ if (fir->curr_pos <= 0)
+ fir->curr_pos = fir->taps;
+ fir->curr_pos--;
+ return (int16_t) (y >> 15);
+}
+
+static inline const int16_t *fir32_create(struct fir32_state_t *fir,
+ const int32_t *coeffs, int taps)
+{
+ fir->taps = taps;
+ fir->curr_pos = taps - 1;
+ fir->coeffs = coeffs;
+ fir->history = kcalloc(taps, sizeof(int16_t), GFP_KERNEL);
+ return fir->history;
+}
+
+static inline void fir32_flush(struct fir32_state_t *fir)
+{
+ memset(fir->history, 0, fir->taps * sizeof(int16_t));
+}
+
+static inline void fir32_free(struct fir32_state_t *fir)
+{
+ kfree(fir->history);
+}
+
+static inline int16_t fir32(struct fir32_state_t *fir, int16_t sample)
+{
+ int i;
+ int32_t y;
+ int offset1;
+ int offset2;
+
+ fir->history[fir->curr_pos] = sample;
+ offset2 = fir->curr_pos;
+ offset1 = fir->taps - offset2;
+ y = 0;
+ for (i = fir->taps - 1; i >= offset1; i--)
+ y += fir->coeffs[i] * fir->history[i - offset1];
+ for (; i >= 0; i--)
+ y += fir->coeffs[i] * fir->history[i + offset2];
+ if (fir->curr_pos <= 0)
+ fir->curr_pos = fir->taps;
+ fir->curr_pos--;
+ return (int16_t) (y >> 15);
+}
+
+#endif
--- /dev/null
+/*
+ * OSLEC - A line echo canceller. This code is being developed
+ * against and partially complies with G168. Using code from SpanDSP
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ * and David Rowe <david_at_rowetel_dot_com>
+ *
+ * Copyright (C) 2001 Steve Underwood and 2007-2008 David Rowe
+ *
+ * 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.
+ *
+ */
+
+#ifndef __OSLEC_H
+#define __OSLEC_H
+
+/* Mask bits for the adaption mode */
+#define ECHO_CAN_USE_ADAPTION 0x01
+#define ECHO_CAN_USE_NLP 0x02
+#define ECHO_CAN_USE_CNG 0x04
+#define ECHO_CAN_USE_CLIP 0x08
+#define ECHO_CAN_USE_TX_HPF 0x10
+#define ECHO_CAN_USE_RX_HPF 0x20
+#define ECHO_CAN_DISABLE 0x40
+
+/**
+ * oslec_state: G.168 echo canceller descriptor.
+ *
+ * This defines the working state for a line echo canceller.
+ */
+struct oslec_state;
+
+/**
+ * oslec_create - Create a voice echo canceller context.
+ * @len: The length of the canceller, in samples.
+ * @return: The new canceller context, or NULL if the canceller could not be
+ * created.
+ */
+struct oslec_state *oslec_create(int len, int adaption_mode);
+
+/**
+ * oslec_free - Free a voice echo canceller context.
+ * @ec: The echo canceller context.
+ */
+void oslec_free(struct oslec_state *ec);
+
+/**
+ * oslec_flush - Flush (reinitialise) a voice echo canceller context.
+ * @ec: The echo canceller context.
+ */
+void oslec_flush(struct oslec_state *ec);
+
+/**
+ * oslec_adaption_mode - set the adaption mode of a voice echo canceller context.
+ * @ec The echo canceller context.
+ * @adaption_mode: The mode.
+ */
+void oslec_adaption_mode(struct oslec_state *ec, int adaption_mode);
+
+void oslec_snapshot(struct oslec_state *ec);
+
+/**
+ * oslec_update: Process a sample through a voice echo canceller.
+ * @ec: The echo canceller context.
+ * @tx: The transmitted audio sample.
+ * @rx: The received audio sample.
+ *
+ * The return value is the clean (echo cancelled) received sample.
+ */
+int16_t oslec_update(struct oslec_state *ec, int16_t tx, int16_t rx);
+
+/**
+ * oslec_hpf_tx: Process to high pass filter the tx signal.
+ * @ec: The echo canceller context.
+ * @tx: The transmitted auio sample.
+ *
+ * The return value is the HP filtered transmit sample, send this to your D/A.
+ */
+int16_t oslec_hpf_tx(struct oslec_state *ec, int16_t tx);
+
+#endif /* __OSLEC_H */
source "drivers/staging/wlan-ng/Kconfig"
-source "drivers/staging/echo/Kconfig"
-
source "drivers/staging/comedi/Kconfig"
source "drivers/staging/olpc_dcon/Kconfig"
obj-$(CONFIG_USBIP_CORE) += usbip/
obj-$(CONFIG_W35UND) += winbond/
obj-$(CONFIG_PRISM2_USB) += wlan-ng/
-obj-$(CONFIG_ECHO) += echo/
obj-$(CONFIG_COMEDI) += comedi/
obj-$(CONFIG_FB_OLPC_DCON) += olpc_dcon/
obj-$(CONFIG_PANEL) += panel/
+++ /dev/null
-config ECHO
- tristate "Line Echo Canceller support"
- default n
- ---help---
- This driver provides line echo cancelling support for mISDN and
- Zaptel drivers.
-
- To compile this driver as a module, choose M here. The module
- will be called echo.
+++ /dev/null
-obj-$(CONFIG_ECHO) += echo.o
+++ /dev/null
-TODO:
- - send to lkml for review
-
-Please send patches to Greg Kroah-Hartman <greg@kroah.com> and Cc: Steve
-Underwood <steveu@coppice.org> and David Rowe <david@rowetel.com>
+++ /dev/null
-/*
- * SpanDSP - a series of DSP components for telephony
- *
- * echo.c - A line echo canceller. This code is being developed
- * against and partially complies with G168.
- *
- * Written by Steve Underwood <steveu@coppice.org>
- * and David Rowe <david_at_rowetel_dot_com>
- *
- * Copyright (C) 2001, 2003 Steve Underwood, 2007 David Rowe
- *
- * Based on a bit from here, a bit from there, eye of toad, ear of
- * bat, 15 years of failed attempts by David and a few fried brain
- * cells.
- *
- * 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.
- */
-
-/*! \file */
-
-/* Implementation Notes
- David Rowe
- April 2007
-
- This code started life as Steve's NLMS algorithm with a tap
- rotation algorithm to handle divergence during double talk. I
- added a Geigel Double Talk Detector (DTD) [2] and performed some
- G168 tests. However I had trouble meeting the G168 requirements,
- especially for double talk - there were always cases where my DTD
- failed, for example where near end speech was under the 6dB
- threshold required for declaring double talk.
-
- So I tried a two path algorithm [1], which has so far given better
- results. The original tap rotation/Geigel algorithm is available
- in SVN http://svn.rowetel.com/software/oslec/tags/before_16bit.
- It's probably possible to make it work if some one wants to put some
- serious work into it.
-
- At present no special treatment is provided for tones, which
- generally cause NLMS algorithms to diverge. Initial runs of a
- subset of the G168 tests for tones (e.g ./echo_test 6) show the
- current algorithm is passing OK, which is kind of surprising. The
- full set of tests needs to be performed to confirm this result.
-
- One other interesting change is that I have managed to get the NLMS
- code to work with 16 bit coefficients, rather than the original 32
- bit coefficents. This reduces the MIPs and storage required.
- I evaulated the 16 bit port using g168_tests.sh and listening tests
- on 4 real-world samples.
-
- I also attempted the implementation of a block based NLMS update
- [2] but although this passes g168_tests.sh it didn't converge well
- on the real-world samples. I have no idea why, perhaps a scaling
- problem. The block based code is also available in SVN
- http://svn.rowetel.com/software/oslec/tags/before_16bit. If this
- code can be debugged, it will lead to further reduction in MIPS, as
- the block update code maps nicely onto DSP instruction sets (it's a
- dot product) compared to the current sample-by-sample update.
-
- Steve also has some nice notes on echo cancellers in echo.h
-
- References:
-
- [1] Ochiai, Areseki, and Ogihara, "Echo Canceller with Two Echo
- Path Models", IEEE Transactions on communications, COM-25,
- No. 6, June
- 1977.
- http://www.rowetel.com/images/echo/dual_path_paper.pdf
-
- [2] The classic, very useful paper that tells you how to
- actually build a real world echo canceller:
- Messerschmitt, Hedberg, Cole, Haoui, Winship, "Digital Voice
- Echo Canceller with a TMS320020,
- http://www.rowetel.com/images/echo/spra129.pdf
-
- [3] I have written a series of blog posts on this work, here is
- Part 1: http://www.rowetel.com/blog/?p=18
-
- [4] The source code http://svn.rowetel.com/software/oslec/
-
- [5] A nice reference on LMS filters:
- http://en.wikipedia.org/wiki/Least_mean_squares_filter
-
- Credits:
-
- Thanks to Steve Underwood, Jean-Marc Valin, and Ramakrishnan
- Muthukrishnan for their suggestions and email discussions. Thanks
- also to those people who collected echo samples for me such as
- Mark, Pawel, and Pavel.
-*/
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-
-#include "echo.h"
-
-#define MIN_TX_POWER_FOR_ADAPTION 64
-#define MIN_RX_POWER_FOR_ADAPTION 64
-#define DTD_HANGOVER 600 /* 600 samples, or 75ms */
-#define DC_LOG2BETA 3 /* log2() of DC filter Beta */
-
-/* adapting coeffs using the traditional stochastic descent (N)LMS algorithm */
-
-#ifdef __bfin__
-static inline void lms_adapt_bg(struct oslec_state *ec, int clean, int shift)
-{
- int i;
- int offset1;
- int offset2;
- int factor;
- int exp;
- int16_t *phist;
- int n;
-
- if (shift > 0)
- factor = clean << shift;
- else
- factor = clean >> -shift;
-
- /* Update the FIR taps */
-
- offset2 = ec->curr_pos;
- offset1 = ec->taps - offset2;
- phist = &ec->fir_state_bg.history[offset2];
-
- /* st: and en: help us locate the assembler in echo.s */
-
- /* asm("st:"); */
- n = ec->taps;
- for (i = 0; i < n; i++) {
- exp = *phist++ * factor;
- ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15);
- }
- /* asm("en:"); */
-
- /* Note the asm for the inner loop above generated by Blackfin gcc
- 4.1.1 is pretty good (note even parallel instructions used):
-
- R0 = W [P0++] (X);
- R0 *= R2;
- R0 = R0 + R3 (NS) ||
- R1 = W [P1] (X) ||
- nop;
- R0 >>>= 15;
- R0 = R0 + R1;
- W [P1++] = R0;
-
- A block based update algorithm would be much faster but the
- above can't be improved on much. Every instruction saved in
- the loop above is 2 MIPs/ch! The for loop above is where the
- Blackfin spends most of it's time - about 17 MIPs/ch measured
- with speedtest.c with 256 taps (32ms). Write-back and
- Write-through cache gave about the same performance.
- */
-}
-
-/*
- IDEAS for further optimisation of lms_adapt_bg():
-
- 1/ The rounding is quite costly. Could we keep as 32 bit coeffs
- then make filter pluck the MS 16-bits of the coeffs when filtering?
- However this would lower potential optimisation of filter, as I
- think the dual-MAC architecture requires packed 16 bit coeffs.
-
- 2/ Block based update would be more efficient, as per comments above,
- could use dual MAC architecture.
-
- 3/ Look for same sample Blackfin LMS code, see if we can get dual-MAC
- packing.
-
- 4/ Execute the whole e/c in a block of say 20ms rather than sample
- by sample. Processing a few samples every ms is inefficient.
-*/
-
-#else
-static inline void lms_adapt_bg(struct oslec_state *ec, int clean, int shift)
-{
- int i;
-
- int offset1;
- int offset2;
- int factor;
- int exp;
-
- if (shift > 0)
- factor = clean << shift;
- else
- factor = clean >> -shift;
-
- /* Update the FIR taps */
-
- offset2 = ec->curr_pos;
- offset1 = ec->taps - offset2;
-
- for (i = ec->taps - 1; i >= offset1; i--) {
- exp = (ec->fir_state_bg.history[i - offset1] * factor);
- ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15);
- }
- for (; i >= 0; i--) {
- exp = (ec->fir_state_bg.history[i + offset2] * factor);
- ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15);
- }
-}
-#endif
-
-static inline int top_bit(unsigned int bits)
-{
- if (bits == 0)
- return -1;
- else
- return (int)fls((int32_t) bits) - 1;
-}
-
-struct oslec_state *oslec_create(int len, int adaption_mode)
-{
- struct oslec_state *ec;
- int i;
- const int16_t *history;
-
- ec = kzalloc(sizeof(*ec), GFP_KERNEL);
- if (!ec)
- return NULL;
-
- ec->taps = len;
- ec->log2taps = top_bit(len);
- ec->curr_pos = ec->taps - 1;
-
- ec->fir_taps16[0] =
- kcalloc(ec->taps, sizeof(int16_t), GFP_KERNEL);
- if (!ec->fir_taps16[0])
- goto error_oom_0;
-
- ec->fir_taps16[1] =
- kcalloc(ec->taps, sizeof(int16_t), GFP_KERNEL);
- if (!ec->fir_taps16[1])
- goto error_oom_1;
-
- history = fir16_create(&ec->fir_state, ec->fir_taps16[0], ec->taps);
- if (!history)
- goto error_state;
- history = fir16_create(&ec->fir_state_bg, ec->fir_taps16[1], ec->taps);
- if (!history)
- goto error_state_bg;
-
- for (i = 0; i < 5; i++)
- ec->xvtx[i] = ec->yvtx[i] = ec->xvrx[i] = ec->yvrx[i] = 0;
-
- ec->cng_level = 1000;
- oslec_adaption_mode(ec, adaption_mode);
-
- ec->snapshot = kcalloc(ec->taps, sizeof(int16_t), GFP_KERNEL);
- if (!ec->snapshot)
- goto error_snap;
-
- ec->cond_met = 0;
- ec->pstates = 0;
- ec->ltxacc = ec->lrxacc = ec->lcleanacc = ec->lclean_bgacc = 0;
- ec->ltx = ec->lrx = ec->lclean = ec->lclean_bg = 0;
- ec->tx_1 = ec->tx_2 = ec->rx_1 = ec->rx_2 = 0;
- ec->lbgn = ec->lbgn_acc = 0;
- ec->lbgn_upper = 200;
- ec->lbgn_upper_acc = ec->lbgn_upper << 13;
-
- return ec;
-
-error_snap:
- fir16_free(&ec->fir_state_bg);
-error_state_bg:
- fir16_free(&ec->fir_state);
-error_state:
- kfree(ec->fir_taps16[1]);
-error_oom_1:
- kfree(ec->fir_taps16[0]);
-error_oom_0:
- kfree(ec);
- return NULL;
-}
-EXPORT_SYMBOL_GPL(oslec_create);
-
-void oslec_free(struct oslec_state *ec)
-{
- int i;
-
- fir16_free(&ec->fir_state);
- fir16_free(&ec->fir_state_bg);
- for (i = 0; i < 2; i++)
- kfree(ec->fir_taps16[i]);
- kfree(ec->snapshot);
- kfree(ec);
-}
-EXPORT_SYMBOL_GPL(oslec_free);
-
-void oslec_adaption_mode(struct oslec_state *ec, int adaption_mode)
-{
- ec->adaption_mode = adaption_mode;
-}
-EXPORT_SYMBOL_GPL(oslec_adaption_mode);
-
-void oslec_flush(struct oslec_state *ec)
-{
- int i;
-
- ec->ltxacc = ec->lrxacc = ec->lcleanacc = ec->lclean_bgacc = 0;
- ec->ltx = ec->lrx = ec->lclean = ec->lclean_bg = 0;
- ec->tx_1 = ec->tx_2 = ec->rx_1 = ec->rx_2 = 0;
-
- ec->lbgn = ec->lbgn_acc = 0;
- ec->lbgn_upper = 200;
- ec->lbgn_upper_acc = ec->lbgn_upper << 13;
-
- ec->nonupdate_dwell = 0;
-
- fir16_flush(&ec->fir_state);
- fir16_flush(&ec->fir_state_bg);
- ec->fir_state.curr_pos = ec->taps - 1;
- ec->fir_state_bg.curr_pos = ec->taps - 1;
- for (i = 0; i < 2; i++)
- memset(ec->fir_taps16[i], 0, ec->taps * sizeof(int16_t));
-
- ec->curr_pos = ec->taps - 1;
- ec->pstates = 0;
-}
-EXPORT_SYMBOL_GPL(oslec_flush);
-
-void oslec_snapshot(struct oslec_state *ec)
-{
- memcpy(ec->snapshot, ec->fir_taps16[0], ec->taps * sizeof(int16_t));
-}
-EXPORT_SYMBOL_GPL(oslec_snapshot);
-
-/* Dual Path Echo Canceller */
-
-int16_t oslec_update(struct oslec_state *ec, int16_t tx, int16_t rx)
-{
- int32_t echo_value;
- int clean_bg;
- int tmp;
- int tmp1;
-
- /*
- * Input scaling was found be required to prevent problems when tx
- * starts clipping. Another possible way to handle this would be the
- * filter coefficent scaling.
- */
-
- ec->tx = tx;
- ec->rx = rx;
- tx >>= 1;
- rx >>= 1;
-
- /*
- * Filter DC, 3dB point is 160Hz (I think), note 32 bit precision
- * required otherwise values do not track down to 0. Zero at DC, Pole
- * at (1-Beta) on real axis. Some chip sets (like Si labs) don't
- * need this, but something like a $10 X100P card does. Any DC really
- * slows down convergence.
- *
- * Note: removes some low frequency from the signal, this reduces the
- * speech quality when listening to samples through headphones but may
- * not be obvious through a telephone handset.
- *
- * Note that the 3dB frequency in radians is approx Beta, e.g. for Beta
- * = 2^(-3) = 0.125, 3dB freq is 0.125 rads = 159Hz.
- */
-
- if (ec->adaption_mode & ECHO_CAN_USE_RX_HPF) {
- tmp = rx << 15;
-
- /*
- * Make sure the gain of the HPF is 1.0. This can still
- * saturate a little under impulse conditions, and it might
- * roll to 32768 and need clipping on sustained peak level
- * signals. However, the scale of such clipping is small, and
- * the error due to any saturation should not markedly affect
- * the downstream processing.
- */
- tmp -= (tmp >> 4);
-
- ec->rx_1 += -(ec->rx_1 >> DC_LOG2BETA) + tmp - ec->rx_2;
-
- /*
- * hard limit filter to prevent clipping. Note that at this
- * stage rx should be limited to +/- 16383 due to right shift
- * above
- */
- tmp1 = ec->rx_1 >> 15;
- if (tmp1 > 16383)
- tmp1 = 16383;
- if (tmp1 < -16383)
- tmp1 = -16383;
- rx = tmp1;
- ec->rx_2 = tmp;
- }
-
- /* Block average of power in the filter states. Used for
- adaption power calculation. */
-
- {
- int new, old;
-
- /* efficient "out with the old and in with the new" algorithm so
- we don't have to recalculate over the whole block of
- samples. */
- new = (int)tx * (int)tx;
- old = (int)ec->fir_state.history[ec->fir_state.curr_pos] *
- (int)ec->fir_state.history[ec->fir_state.curr_pos];
- ec->pstates +=
- ((new - old) + (1 << (ec->log2taps - 1))) >> ec->log2taps;
- if (ec->pstates < 0)
- ec->pstates = 0;
- }
-
- /* Calculate short term average levels using simple single pole IIRs */
-
- ec->ltxacc += abs(tx) - ec->ltx;
- ec->ltx = (ec->ltxacc + (1 << 4)) >> 5;
- ec->lrxacc += abs(rx) - ec->lrx;
- ec->lrx = (ec->lrxacc + (1 << 4)) >> 5;
-
- /* Foreground filter */
-
- ec->fir_state.coeffs = ec->fir_taps16[0];
- echo_value = fir16(&ec->fir_state, tx);
- ec->clean = rx - echo_value;
- ec->lcleanacc += abs(ec->clean) - ec->lclean;
- ec->lclean = (ec->lcleanacc + (1 << 4)) >> 5;
-
- /* Background filter */
-
- echo_value = fir16(&ec->fir_state_bg, tx);
- clean_bg = rx - echo_value;
- ec->lclean_bgacc += abs(clean_bg) - ec->lclean_bg;
- ec->lclean_bg = (ec->lclean_bgacc + (1 << 4)) >> 5;
-
- /* Background Filter adaption */
-
- /* Almost always adap bg filter, just simple DT and energy
- detection to minimise adaption in cases of strong double talk.
- However this is not critical for the dual path algorithm.
- */
- ec->factor = 0;
- ec->shift = 0;
- if ((ec->nonupdate_dwell == 0)) {
- int p, logp, shift;
-
- /* Determine:
-
- f = Beta * clean_bg_rx/P ------ (1)
-
- where P is the total power in the filter states.
-
- The Boffins have shown that if we obey (1) we converge
- quickly and avoid instability.
-
- The correct factor f must be in Q30, as this is the fixed
- point format required by the lms_adapt_bg() function,
- therefore the scaled version of (1) is:
-
- (2^30) * f = (2^30) * Beta * clean_bg_rx/P
- factor = (2^30) * Beta * clean_bg_rx/P ----- (2)
-
- We have chosen Beta = 0.25 by experiment, so:
-
- factor = (2^30) * (2^-2) * clean_bg_rx/P
-
- (30 - 2 - log2(P))
- factor = clean_bg_rx 2 ----- (3)
-
- To avoid a divide we approximate log2(P) as top_bit(P),
- which returns the position of the highest non-zero bit in
- P. This approximation introduces an error as large as a
- factor of 2, but the algorithm seems to handle it OK.
-
- Come to think of it a divide may not be a big deal on a
- modern DSP, so its probably worth checking out the cycles
- for a divide versus a top_bit() implementation.
- */
-
- p = MIN_TX_POWER_FOR_ADAPTION + ec->pstates;
- logp = top_bit(p) + ec->log2taps;
- shift = 30 - 2 - logp;
- ec->shift = shift;
-
- lms_adapt_bg(ec, clean_bg, shift);
- }
-
- /* very simple DTD to make sure we dont try and adapt with strong
- near end speech */
-
- ec->adapt = 0;
- if ((ec->lrx > MIN_RX_POWER_FOR_ADAPTION) && (ec->lrx > ec->ltx))
- ec->nonupdate_dwell = DTD_HANGOVER;
- if (ec->nonupdate_dwell)
- ec->nonupdate_dwell--;
-
- /* Transfer logic */
-
- /* These conditions are from the dual path paper [1], I messed with
- them a bit to improve performance. */
-
- if ((ec->adaption_mode & ECHO_CAN_USE_ADAPTION) &&
- (ec->nonupdate_dwell == 0) &&
- /* (ec->Lclean_bg < 0.875*ec->Lclean) */
- (8 * ec->lclean_bg < 7 * ec->lclean) &&
- /* (ec->Lclean_bg < 0.125*ec->Ltx) */
- (8 * ec->lclean_bg < ec->ltx)) {
- if (ec->cond_met == 6) {
- /*
- * BG filter has had better results for 6 consecutive
- * samples
- */
- ec->adapt = 1;
- memcpy(ec->fir_taps16[0], ec->fir_taps16[1],
- ec->taps * sizeof(int16_t));
- } else
- ec->cond_met++;
- } else
- ec->cond_met = 0;
-
- /* Non-Linear Processing */
-
- ec->clean_nlp = ec->clean;
- if (ec->adaption_mode & ECHO_CAN_USE_NLP) {
- /*
- * Non-linear processor - a fancy way to say "zap small
- * signals, to avoid residual echo due to (uLaw/ALaw)
- * non-linearity in the channel.".
- */
-
- if ((16 * ec->lclean < ec->ltx)) {
- /*
- * Our e/c has improved echo by at least 24 dB (each
- * factor of 2 is 6dB, so 2*2*2*2=16 is the same as
- * 6+6+6+6=24dB)
- */
- if (ec->adaption_mode & ECHO_CAN_USE_CNG) {
- ec->cng_level = ec->lbgn;
-
- /*
- * Very elementary comfort noise generation.
- * Just random numbers rolled off very vaguely
- * Hoth-like. DR: This noise doesn't sound
- * quite right to me - I suspect there are some
- * overflow issues in the filtering as it's too
- * "crackly".
- * TODO: debug this, maybe just play noise at
- * high level or look at spectrum.
- */
-
- ec->cng_rndnum =
- 1664525U * ec->cng_rndnum + 1013904223U;
- ec->cng_filter =
- ((ec->cng_rndnum & 0xFFFF) - 32768 +
- 5 * ec->cng_filter) >> 3;
- ec->clean_nlp =
- (ec->cng_filter * ec->cng_level * 8) >> 14;
-
- } else if (ec->adaption_mode & ECHO_CAN_USE_CLIP) {
- /* This sounds much better than CNG */
- if (ec->clean_nlp > ec->lbgn)
- ec->clean_nlp = ec->lbgn;
- if (ec->clean_nlp < -ec->lbgn)
- ec->clean_nlp = -ec->lbgn;
- } else {
- /*
- * just mute the residual, doesn't sound very
- * good, used mainly in G168 tests
- */
- ec->clean_nlp = 0;
- }
- } else {
- /*
- * Background noise estimator. I tried a few
- * algorithms here without much luck. This very simple
- * one seems to work best, we just average the level
- * using a slow (1 sec time const) filter if the
- * current level is less than a (experimentally
- * derived) constant. This means we dont include high
- * level signals like near end speech. When combined
- * with CNG or especially CLIP seems to work OK.
- */
- if (ec->lclean < 40) {
- ec->lbgn_acc += abs(ec->clean) - ec->lbgn;
- ec->lbgn = (ec->lbgn_acc + (1 << 11)) >> 12;
- }
- }
- }
-
- /* Roll around the taps buffer */
- if (ec->curr_pos <= 0)
- ec->curr_pos = ec->taps;
- ec->curr_pos--;
-
- if (ec->adaption_mode & ECHO_CAN_DISABLE)
- ec->clean_nlp = rx;
-
- /* Output scaled back up again to match input scaling */
-
- return (int16_t) ec->clean_nlp << 1;
-}
-EXPORT_SYMBOL_GPL(oslec_update);
-
-/* This function is separated from the echo canceller is it is usually called
- as part of the tx process. See rx HP (DC blocking) filter above, it's
- the same design.
-
- Some soft phones send speech signals with a lot of low frequency
- energy, e.g. down to 20Hz. This can make the hybrid non-linear
- which causes the echo canceller to fall over. This filter can help
- by removing any low frequency before it gets to the tx port of the
- hybrid.
-
- It can also help by removing and DC in the tx signal. DC is bad
- for LMS algorithms.
-
- This is one of the classic DC removal filters, adjusted to provide
- sufficient bass rolloff to meet the above requirement to protect hybrids
- from things that upset them. The difference between successive samples
- produces a lousy HPF, and then a suitably placed pole flattens things out.
- The final result is a nicely rolled off bass end. The filtering is
- implemented with extended fractional precision, which noise shapes things,
- giving very clean DC removal.
-*/
-
-int16_t oslec_hpf_tx(struct oslec_state *ec, int16_t tx)
-{
- int tmp;
- int tmp1;
-
- if (ec->adaption_mode & ECHO_CAN_USE_TX_HPF) {
- tmp = tx << 15;
-
- /*
- * Make sure the gain of the HPF is 1.0. The first can still
- * saturate a little under impulse conditions, and it might
- * roll to 32768 and need clipping on sustained peak level
- * signals. However, the scale of such clipping is small, and
- * the error due to any saturation should not markedly affect
- * the downstream processing.
- */
- tmp -= (tmp >> 4);
-
- ec->tx_1 += -(ec->tx_1 >> DC_LOG2BETA) + tmp - ec->tx_2;
- tmp1 = ec->tx_1 >> 15;
- if (tmp1 > 32767)
- tmp1 = 32767;
- if (tmp1 < -32767)
- tmp1 = -32767;
- tx = tmp1;
- ec->tx_2 = tmp;
- }
-
- return tx;
-}
-EXPORT_SYMBOL_GPL(oslec_hpf_tx);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("David Rowe");
-MODULE_DESCRIPTION("Open Source Line Echo Canceller");
-MODULE_VERSION("0.3.0");
+++ /dev/null
-/*
- * SpanDSP - a series of DSP components for telephony
- *
- * echo.c - A line echo canceller. This code is being developed
- * against and partially complies with G168.
- *
- * Written by Steve Underwood <steveu@coppice.org>
- * and David Rowe <david_at_rowetel_dot_com>
- *
- * Copyright (C) 2001 Steve Underwood and 2007 David Rowe
- *
- * 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.
- */
-
-#ifndef __ECHO_H
-#define __ECHO_H
-
-/*
-Line echo cancellation for voice
-
-What does it do?
-
-This module aims to provide G.168-2002 compliant echo cancellation, to remove
-electrical echoes (e.g. from 2-4 wire hybrids) from voice calls.
-
-How does it work?
-
-The heart of the echo cancellor is FIR filter. This is adapted to match the
-echo impulse response of the telephone line. It must be long enough to
-adequately cover the duration of that impulse response. The signal transmitted
-to the telephone line is passed through the FIR filter. Once the FIR is
-properly adapted, the resulting output is an estimate of the echo signal
-received from the line. This is subtracted from the received signal. The result
-is an estimate of the signal which originated at the far end of the line, free
-from echos of our own transmitted signal.
-
-The least mean squares (LMS) algorithm is attributed to Widrow and Hoff, and
-was introduced in 1960. It is the commonest form of filter adaption used in
-things like modem line equalisers and line echo cancellers. There it works very
-well. However, it only works well for signals of constant amplitude. It works
-very poorly for things like speech echo cancellation, where the signal level
-varies widely. This is quite easy to fix. If the signal level is normalised -
-similar to applying AGC - LMS can work as well for a signal of varying
-amplitude as it does for a modem signal. This normalised least mean squares
-(NLMS) algorithm is the commonest one used for speech echo cancellation. Many
-other algorithms exist - e.g. RLS (essentially the same as Kalman filtering),
-FAP, etc. Some perform significantly better than NLMS. However, factors such
-as computational complexity and patents favour the use of NLMS.
-
-A simple refinement to NLMS can improve its performance with speech. NLMS tends
-to adapt best to the strongest parts of a signal. If the signal is white noise,
-the NLMS algorithm works very well. However, speech has more low frequency than
-high frequency content. Pre-whitening (i.e. filtering the signal to flatten its
-spectrum) the echo signal improves the adapt rate for speech, and ensures the
-final residual signal is not heavily biased towards high frequencies. A very
-low complexity filter is adequate for this, so pre-whitening adds little to the
-compute requirements of the echo canceller.
-
-An FIR filter adapted using pre-whitened NLMS performs well, provided certain
-conditions are met:
-
- - The transmitted signal has poor self-correlation.
- - There is no signal being generated within the environment being
- cancelled.
-
-The difficulty is that neither of these can be guaranteed.
-
-If the adaption is performed while transmitting noise (or something fairly
-noise like, such as voice) the adaption works very well. If the adaption is
-performed while transmitting something highly correlative (typically narrow
-band energy such as signalling tones or DTMF), the adaption can go seriously
-wrong. The reason is there is only one solution for the adaption on a near
-random signal - the impulse response of the line. For a repetitive signal,
-there are any number of solutions which converge the adaption, and nothing
-guides the adaption to choose the generalised one. Allowing an untrained
-canceller to converge on this kind of narrowband energy probably a good thing,
-since at least it cancels the tones. Allowing a well converged canceller to
-continue converging on such energy is just a way to ruin its generalised
-adaption. A narrowband detector is needed, so adapation can be suspended at
-appropriate times.
-
-The adaption process is based on trying to eliminate the received signal. When
-there is any signal from within the environment being cancelled it may upset
-the adaption process. Similarly, if the signal we are transmitting is small,
-noise may dominate and disturb the adaption process. If we can ensure that the
-adaption is only performed when we are transmitting a significant signal level,
-and the environment is not, things will be OK. Clearly, it is easy to tell when
-we are sending a significant signal. Telling, if the environment is generating
-a significant signal, and doing it with sufficient speed that the adaption will
-not have diverged too much more we stop it, is a little harder.
-
-The key problem in detecting when the environment is sourcing significant
-energy is that we must do this very quickly. Given a reasonably long sample of
-the received signal, there are a number of strategies which may be used to
-assess whether that signal contains a strong far end component. However, by the
-time that assessment is complete the far end signal will have already caused
-major mis-convergence in the adaption process. An assessment algorithm is
-needed which produces a fairly accurate result from a very short burst of far
-end energy.
-
-How do I use it?
-
-The echo cancellor processes both the transmit and receive streams sample by
-sample. The processing function is not declared inline. Unfortunately,
-cancellation requires many operations per sample, so the call overhead is only
-a minor burden.
-*/
-
-#include "fir.h"
-#include "oslec.h"
-
-/*
- G.168 echo canceller descriptor. This defines the working state for a line
- echo canceller.
-*/
-struct oslec_state {
- int16_t tx;
- int16_t rx;
- int16_t clean;
- int16_t clean_nlp;
-
- int nonupdate_dwell;
- int curr_pos;
- int taps;
- int log2taps;
- int adaption_mode;
-
- int cond_met;
- int32_t pstates;
- int16_t adapt;
- int32_t factor;
- int16_t shift;
-
- /* Average levels and averaging filter states */
- int ltxacc;
- int lrxacc;
- int lcleanacc;
- int lclean_bgacc;
- int ltx;
- int lrx;
- int lclean;
- int lclean_bg;
- int lbgn;
- int lbgn_acc;
- int lbgn_upper;
- int lbgn_upper_acc;
-
- /* foreground and background filter states */
- struct fir16_state_t fir_state;
- struct fir16_state_t fir_state_bg;
- int16_t *fir_taps16[2];
-
- /* DC blocking filter states */
- int tx_1;
- int tx_2;
- int rx_1;
- int rx_2;
-
- /* optional High Pass Filter states */
- int32_t xvtx[5];
- int32_t yvtx[5];
- int32_t xvrx[5];
- int32_t yvrx[5];
-
- /* Parameters for the optional Hoth noise generator */
- int cng_level;
- int cng_rndnum;
- int cng_filter;
-
- /* snapshot sample of coeffs used for development */
- int16_t *snapshot;
-};
-
-#endif /* __ECHO_H */
+++ /dev/null
-/*
- * SpanDSP - a series of DSP components for telephony
- *
- * fir.h - General telephony FIR routines
- *
- * Written by Steve Underwood <steveu@coppice.org>
- *
- * Copyright (C) 2002 Steve Underwood
- *
- * 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.
- */
-
-#if !defined(_FIR_H_)
-#define _FIR_H_
-
-/*
- Blackfin NOTES & IDEAS:
-
- A simple dot product function is used to implement the filter. This performs
- just one MAC/cycle which is inefficient but was easy to implement as a first
- pass. The current Blackfin code also uses an unrolled form of the filter
- history to avoid 0 length hardware loop issues. This is wasteful of
- memory.
-
- Ideas for improvement:
-
- 1/ Rewrite filter for dual MAC inner loop. The issue here is handling
- history sample offsets that are 16 bit aligned - the dual MAC needs
- 32 bit aligmnent. There are some good examples in libbfdsp.
-
- 2/ Use the hardware circular buffer facility tohalve memory usage.
-
- 3/ Consider using internal memory.
-
- Using less memory might also improve speed as cache misses will be
- reduced. A drop in MIPs and memory approaching 50% should be
- possible.
-
- The foreground and background filters currenlty use a total of
- about 10 MIPs/ch as measured with speedtest.c on a 256 TAP echo
- can.
-*/
-
-/*
- * 16 bit integer FIR descriptor. This defines the working state for a single
- * instance of an FIR filter using 16 bit integer coefficients.
- */
-struct fir16_state_t {
- int taps;
- int curr_pos;
- const int16_t *coeffs;
- int16_t *history;
-};
-
-/*
- * 32 bit integer FIR descriptor. This defines the working state for a single
- * instance of an FIR filter using 32 bit integer coefficients, and filtering
- * 16 bit integer data.
- */
-struct fir32_state_t {
- int taps;
- int curr_pos;
- const int32_t *coeffs;
- int16_t *history;
-};
-
-/*
- * Floating point FIR descriptor. This defines the working state for a single
- * instance of an FIR filter using floating point coefficients and data.
- */
-struct fir_float_state_t {
- int taps;
- int curr_pos;
- const float *coeffs;
- float *history;
-};
-
-static inline const int16_t *fir16_create(struct fir16_state_t *fir,
- const int16_t *coeffs, int taps)
-{
- fir->taps = taps;
- fir->curr_pos = taps - 1;
- fir->coeffs = coeffs;
-#if defined(__bfin__)
- fir->history = kcalloc(2 * taps, sizeof(int16_t), GFP_KERNEL);
-#else
- fir->history = kcalloc(taps, sizeof(int16_t), GFP_KERNEL);
-#endif
- return fir->history;
-}
-
-static inline void fir16_flush(struct fir16_state_t *fir)
-{
-#if defined(__bfin__)
- memset(fir->history, 0, 2 * fir->taps * sizeof(int16_t));
-#else
- memset(fir->history, 0, fir->taps * sizeof(int16_t));
-#endif
-}
-
-static inline void fir16_free(struct fir16_state_t *fir)
-{
- kfree(fir->history);
-}
-
-#ifdef __bfin__
-static inline int32_t dot_asm(short *x, short *y, int len)
-{
- int dot;
-
- len--;
-
- __asm__("I0 = %1;\n\t"
- "I1 = %2;\n\t"
- "A0 = 0;\n\t"
- "R0.L = W[I0++] || R1.L = W[I1++];\n\t"
- "LOOP dot%= LC0 = %3;\n\t"
- "LOOP_BEGIN dot%=;\n\t"
- "A0 += R0.L * R1.L (IS) || R0.L = W[I0++] || R1.L = W[I1++];\n\t"
- "LOOP_END dot%=;\n\t"
- "A0 += R0.L*R1.L (IS);\n\t"
- "R0 = A0;\n\t"
- "%0 = R0;\n\t"
- : "=&d"(dot)
- : "a"(x), "a"(y), "a"(len)
- : "I0", "I1", "A1", "A0", "R0", "R1"
- );
-
- return dot;
-}
-#endif
-
-static inline int16_t fir16(struct fir16_state_t *fir, int16_t sample)
-{
- int32_t y;
-#if defined(__bfin__)
- fir->history[fir->curr_pos] = sample;
- fir->history[fir->curr_pos + fir->taps] = sample;
- y = dot_asm((int16_t *) fir->coeffs, &fir->history[fir->curr_pos],
- fir->taps);
-#else
- int i;
- int offset1;
- int offset2;
-
- fir->history[fir->curr_pos] = sample;
-
- offset2 = fir->curr_pos;
- offset1 = fir->taps - offset2;
- y = 0;
- for (i = fir->taps - 1; i >= offset1; i--)
- y += fir->coeffs[i] * fir->history[i - offset1];
- for (; i >= 0; i--)
- y += fir->coeffs[i] * fir->history[i + offset2];
-#endif
- if (fir->curr_pos <= 0)
- fir->curr_pos = fir->taps;
- fir->curr_pos--;
- return (int16_t) (y >> 15);
-}
-
-static inline const int16_t *fir32_create(struct fir32_state_t *fir,
- const int32_t *coeffs, int taps)
-{
- fir->taps = taps;
- fir->curr_pos = taps - 1;
- fir->coeffs = coeffs;
- fir->history = kcalloc(taps, sizeof(int16_t), GFP_KERNEL);
- return fir->history;
-}
-
-static inline void fir32_flush(struct fir32_state_t *fir)
-{
- memset(fir->history, 0, fir->taps * sizeof(int16_t));
-}
-
-static inline void fir32_free(struct fir32_state_t *fir)
-{
- kfree(fir->history);
-}
-
-static inline int16_t fir32(struct fir32_state_t *fir, int16_t sample)
-{
- int i;
- int32_t y;
- int offset1;
- int offset2;
-
- fir->history[fir->curr_pos] = sample;
- offset2 = fir->curr_pos;
- offset1 = fir->taps - offset2;
- y = 0;
- for (i = fir->taps - 1; i >= offset1; i--)
- y += fir->coeffs[i] * fir->history[i - offset1];
- for (; i >= 0; i--)
- y += fir->coeffs[i] * fir->history[i + offset2];
- if (fir->curr_pos <= 0)
- fir->curr_pos = fir->taps;
- fir->curr_pos--;
- return (int16_t) (y >> 15);
-}
-
-#endif
+++ /dev/null
-/*
- * OSLEC - A line echo canceller. This code is being developed
- * against and partially complies with G168. Using code from SpanDSP
- *
- * Written by Steve Underwood <steveu@coppice.org>
- * and David Rowe <david_at_rowetel_dot_com>
- *
- * Copyright (C) 2001 Steve Underwood and 2007-2008 David Rowe
- *
- * 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.
- *
- */
-
-#ifndef __OSLEC_H
-#define __OSLEC_H
-
-/* Mask bits for the adaption mode */
-#define ECHO_CAN_USE_ADAPTION 0x01
-#define ECHO_CAN_USE_NLP 0x02
-#define ECHO_CAN_USE_CNG 0x04
-#define ECHO_CAN_USE_CLIP 0x08
-#define ECHO_CAN_USE_TX_HPF 0x10
-#define ECHO_CAN_USE_RX_HPF 0x20
-#define ECHO_CAN_DISABLE 0x40
-
-/**
- * oslec_state: G.168 echo canceller descriptor.
- *
- * This defines the working state for a line echo canceller.
- */
-struct oslec_state;
-
-/**
- * oslec_create - Create a voice echo canceller context.
- * @len: The length of the canceller, in samples.
- * @return: The new canceller context, or NULL if the canceller could not be
- * created.
- */
-struct oslec_state *oslec_create(int len, int adaption_mode);
-
-/**
- * oslec_free - Free a voice echo canceller context.
- * @ec: The echo canceller context.
- */
-void oslec_free(struct oslec_state *ec);
-
-/**
- * oslec_flush - Flush (reinitialise) a voice echo canceller context.
- * @ec: The echo canceller context.
- */
-void oslec_flush(struct oslec_state *ec);
-
-/**
- * oslec_adaption_mode - set the adaption mode of a voice echo canceller context.
- * @ec The echo canceller context.
- * @adaption_mode: The mode.
- */
-void oslec_adaption_mode(struct oslec_state *ec, int adaption_mode);
-
-void oslec_snapshot(struct oslec_state *ec);
-
-/**
- * oslec_update: Process a sample through a voice echo canceller.
- * @ec: The echo canceller context.
- * @tx: The transmitted audio sample.
- * @rx: The received audio sample.
- *
- * The return value is the clean (echo cancelled) received sample.
- */
-int16_t oslec_update(struct oslec_state *ec, int16_t tx, int16_t rx);
-
-/**
- * oslec_hpf_tx: Process to high pass filter the tx signal.
- * @ec: The echo canceller context.
- * @tx: The transmitted auio sample.
- *
- * The return value is the HP filtered transmit sample, send this to your D/A.
- */
-int16_t oslec_hpf_tx(struct oslec_state *ec, int16_t tx);
-
-#endif /* __OSLEC_H */