/* * (C) 2023 by Andreas Eversberg * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ /* This Echo Suppressor is based on ITU-T G.164, but it does not conform with it! * * The 'receive' signal (received by the interface) will cause an echo what is * included in the 'send' signal (sent by the interface). To remove echo, the * 'send' signal is muted when speech is received by the interface. * * Figure 6 / G,164 shows a diagram with the states in this code: * * The 'silence' state is depicted as X. It is entered when both sides do not * talk. * * The 'suppression' state is depticted as Z. It is entered when speech is * received by the interface. * * The 'break-in' state is depicted as W. It is entered when speech is sent to * the interface. * * The depiced area V is implemented as a 6 dB loss, so that a transition from * 'break-in' state to 'suppession' state requires 6 dB more level in the * receive path. * * The input is based on an int16_t that has a range of +3 dBm. * * Timers are used to enter state (operate) or leave state (hangover). * * Six filters are used, to detect the speech level, three in each direction: * * A low-pass filter and a high-pass filter are used to limit the band within * the 500-3400 Hz range. * * The the signal is rectified, amplified and filtered with a low-pass filter. * The resulting level represents the average volume of the input. (The peak * level of a sine wave at the input will result in same level on the output.) * * In the 'suppression' state, the send signal is muted. * * In the 'break-in' state, the receive signal is NOT changed. The loss is only * inserted in the processing, not is the actual signal. */ #include #include #include #include "echo_suppress.h" #define db2level(db) pow(10, (double)db / 20.0) #define level2db(level) (20 * log10(level)) #define SUPPRESS_THRESHOLD -31.0 #define RELEASE_SUPPRESS -31.0 #define INSERTION_LOSS 6.0 #define SUPPRESS_OPERATE 0.000 #define SUPPRESS_HANGOVER 0.050 #define BREAK_IN_OPERATE 0.030 #define BREAK_IN_HANGOVER 0.050 #define LP_FREQUENCY 3400.0 #define HP_FREQUENCY 500.0 #define ENV_FREQUENCY 500.0 //#define DEBUG_ES /* * IIR filter */ static inline void iir_lowpass_init(iir_filter_t *filter, float frequency, int samplerate) { float Fc, Q, K, norm; Q = pow(sqrt(0.5), 1.0); /* 0.7071 */ Fc = frequency / (float)samplerate; K = tan(M_PI * Fc); norm = 1 / (1 + K / Q + K * K); filter->a0 = K * K * norm; filter->a1 = 2 * filter->a0; filter->a2 = filter->a0; filter->b1 = 2 * (K * K - 1) * norm; filter->b2 = (1 - K / Q + K * K) * norm; } static inline void iir_highpass_init(iir_filter_t *filter, float frequency, int samplerate) { float Fc, Q, K, norm; Q = pow(sqrt(0.5), 1.0); /* 0.7071 */ Fc = frequency / (float)samplerate; K = tan(M_PI * Fc); norm = 1 / (1 + K / Q + K * K); filter->a0 = 1 * norm; filter->a1 = -2 * filter->a0; filter->a2 = filter->a0; filter->b1 = 2 * (K * K - 1) * norm; filter->b2 = (1 - K / Q + K * K) * norm; } static inline void iir_process(iir_filter_t *filter, float *samples, int length) { float a0, a1, a2, b1, b2; float z1, z2; float in, out; int i; /* get states */ a0 = filter->a0; a1 = filter->a1; a2 = filter->a2; b1 = filter->b1; b2 = filter->b2; z1 = filter->z1; z2 = filter->z2; /* process filter */ for (i = 0; i < length; i++) { /* add a small value, otherwise this loop will perform really bad on 0-samples */ in = *samples + 0.000000001; out = in * a0 + z1; z1 = in * a1 + z2 - b1 * out; z2 = in * a2 - b2 * out; in = out; *samples++ = in; } filter->z1 = z1; filter->z2 = z2; } static inline void iir_reset(iir_filter_t *filter) { filter->z1 = 0.0; filter->z2 = 0.0; } /* * echo suppressor */ echo_sup_state_t *echo_sup_create(void *ctx, int samplerate) { echo_sup_state_t *es; es = talloc_zero(ctx, struct echo_sup_state); if (!es) return NULL; es->suppress_threshold = db2level(SUPPRESS_THRESHOLD); es->release_suppress = db2level(RELEASE_SUPPRESS); es->insertion_loss = db2level(INSERTION_LOSS); es->suppress_operate = floor(SUPPRESS_OPERATE * (double)samplerate); es->suppress_hangover = floor(SUPPRESS_HANGOVER * (double)samplerate); es->break_in_operate = floor(BREAK_IN_OPERATE * (double)samplerate); es->break_in_hangover = floor(BREAK_IN_HANGOVER * (double)samplerate); iir_lowpass_init(&es->lp_send, LP_FREQUENCY, samplerate); iir_lowpass_init(&es->lp_receive, LP_FREQUENCY, samplerate); iir_highpass_init(&es->hp_send, HP_FREQUENCY, samplerate); iir_highpass_init(&es->hp_receive, HP_FREQUENCY, samplerate); iir_lowpass_init(&es->env_send, ENV_FREQUENCY, samplerate); iir_lowpass_init(&es->env_receive, ENV_FREQUENCY, samplerate); return es; } void echo_sup_free(echo_sup_state_t *es) { talloc_free(es); } /* rx is what we received from user interface, aka 'send' */ /* tx is what we sent to user interface, aka 'receive' */ int16_t echo_sup_update(echo_sup_state_t *es, int16_t tx, int16_t rx) { float send, receive; /* convert from integer samples to float (@ 0 dBm scale) */ receive = (float)tx / 23196.0; /* towards user interface */ send = (float)rx / 23196.0; /* from user interface */ /* filter band */ iir_process(&es->lp_send, &send, 1); iir_process(&es->lp_receive, &receive, 1); iir_process(&es->hp_send, &send, 1); iir_process(&es->hp_receive, &receive, 1); /* get absolute value (rectifying with 3 dB gain) */ send = fabsf(send) * 1.4142136; receive = fabsf(receive) * 1.4142136; /* filter envelope */ iir_process(&es->env_send, &send, 1); iir_process(&es->env_receive, &receive, 1); #ifdef DEBUG_ES static int debug_interval = 0; float send_max = 0, receive_max = 0; if (send > send_max) send_max = send; if (receive > receive_max) receive_max = receive; if (++debug_interval == 1000) { printf("Level (Ls=%.0f Lr=%.0f)\n", level2db(send_max), level2db(receive_max)); debug_interval = 0; send_max = 0; receive_max = 0; } #endif switch (es->state) { case SUP_STATE_SILENCE: if (receive <= es->suppress_threshold && send <= es->suppress_threshold) { es->timer = 0; break; } if (receive < send) { if (++es->timer < es->break_in_operate) break; #ifdef DEBUG_ES printf("Change from silence to break-in\n"); #endif es->state = SUP_STATE_BREAK_IN; } else { if (++es->timer < es->suppress_operate) break; #ifdef DEBUG_ES printf("Change from silence to suppression\n"); #endif es->state = SUP_STATE_SUPPRESSION; } es->timer = 0; break; case SUP_STATE_SUPPRESSION: if (receive <= es->release_suppress && send <= es->release_suppress) { if (++es->timer < es->suppress_hangover) break; #ifdef DEBUG_ES printf("Change from suppression to silence\n"); #endif es->state = SUP_STATE_SILENCE; es->timer = 0; break; } if (receive < send) { if (++es->timer < es->break_in_operate) break; #ifdef DEBUG_ES printf("Change from suppression to break-in\n"); #endif es->state = SUP_STATE_BREAK_IN; } es->timer = 0; break; case SUP_STATE_BREAK_IN: if (receive <= es->suppress_threshold && send <= es->suppress_threshold) { if (++es->timer < es->break_in_hangover) break; #ifdef DEBUG_ES printf("Change from break-in to silence\n"); #endif es->state = SUP_STATE_SILENCE; es->timer = 0; break; } /* insert loss, so that receive level must be 6 dB higher than send level */ if (receive / es->insertion_loss > send) { if (++es->timer < es->break_in_hangover) break; #ifdef DEBUG_ES printf("Change from break-in to suppression\n"); #endif es->state = SUP_STATE_SUPPRESSION; } es->timer = 0; break; } if (es->state == SUP_STATE_SUPPRESSION) return 0; return rx; } void echo_sup_flush(echo_sup_state_t *es) { es->state = SUP_STATE_SILENCE; es->timer = 0; iir_reset(&es->lp_send); iir_reset(&es->lp_receive); iir_reset(&es->hp_send); iir_reset(&es->hp_receive); iir_reset(&es->env_send); iir_reset(&es->env_receive); }