parent
3285bf896d
commit
0f0ada9200
@ -0,0 +1,316 @@
|
||||
/*
|
||||
* (C) 2023 by Andreas Eversberg <andreas@eversberg.eu>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* 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 <osmocom/core/talloc.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#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);
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
|
||||
typedef struct iir_filter {
|
||||
float a0, a1, a2, b1, b2;
|
||||
float z1, z2;
|
||||
} iir_filter_t;
|
||||
|
||||
enum sup_state {
|
||||
SUP_STATE_SILENCE,
|
||||
SUP_STATE_SUPPRESSION,
|
||||
SUP_STATE_BREAK_IN,
|
||||
};
|
||||
|
||||
typedef struct echo_sup_state {
|
||||
float suppress_threshold; /* linear value of suppression th */
|
||||
float release_suppress; /* linear value of release suppression th */
|
||||
float insertion_loss; /* linear value of insertion loss */
|
||||
int suppress_operate; /* number of sample until transition */
|
||||
int suppress_hangover;
|
||||
int break_in_operate;
|
||||
int break_in_hangover;
|
||||
enum sup_state state; /* current state */
|
||||
int timer; /* timer to delay transition */
|
||||
iir_filter_t lp_send, lp_receive; /* filter to cut off high frequencies */
|
||||
iir_filter_t hp_send, hp_receive; /* filter to cut off low frequencies */
|
||||
iir_filter_t env_send, env_receive; /* filter to get the envelope after rectifying */
|
||||
} echo_sup_state_t;
|
||||
|
||||
echo_sup_state_t *echo_sup_create(void *ctx, int samplerate);
|
||||
void echo_sup_free(echo_sup_state_t *es);
|
||||
int16_t echo_sup_update(echo_sup_state_t *es, int16_t tx, int16_t rx);
|
||||
void echo_sup_flush(echo_sup_state_t *es);
|
Loading…
Reference in new issue