1
/* $Id: echo_suppress.c 3553 2011-05-05 06:14:19Z nanang $ */
3
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
#include <pjmedia/types.h>
21
#include <pjmedia/alaw_ulaw.h>
22
#include <pjmedia/errno.h>
23
#include <pjmedia/silencedet.h>
25
#include <pj/assert.h>
31
#include "echo_internal.h"
33
#define THIS_FILE "echo_suppress.c"
35
/* Maximum float constant */
36
#define MAX_FLOAT (float)1.701411e38
38
/* The effective learn duration (in seconds) before we declare that learning
39
* is complete. The actual learning duration itself may be longer depending
40
* on the conversation pattern (e.g. we can't detect echo if speaker is only
43
#define MAX_CALC_DURATION_SEC 3
45
/* The internal audio segment length, in milliseconds. 10ms shold be good
46
* and no need to change it.
48
#define SEGMENT_PTIME 10
50
/* The length of the template signal in milliseconds. The longer the template,
51
* the better correlation will be found, at the expense of more processing
52
* and longer learning time.
54
#define TEMPLATE_PTIME 200
56
/* How long to look back in the past to see if either mic or speaker is
59
#define SIGNAL_LOOKUP_MSEC 200
61
/* The minimum level value to be considered as talking, in uLaw complement
64
#define MIN_SIGNAL_ULAW 35
66
/* The period (in seconds) on which the ES will analize it's effectiveness,
67
* and it may trigger soft-reset to force recalculation.
69
#define CHECK_PERIOD 30
71
/* Maximum signal level of average echo residue (in uLaw complement). When
72
* the residue value exceeds this value, we force the ES to re-learn.
74
#define MAX_RESIDUE 2.5
78
# define TRACE_(expr) PJ_LOG(5,expr)
83
PJ_INLINE(float) FABS(float val)
92
#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0
93
typedef float pj_ufloat_t;
94
# define pj_ufloat_from_float(f) (f)
95
# define pj_ufloat_mul_u(val1, f) ((val1) * (f))
96
# define pj_ufloat_mul_i(val1, f) ((val1) * (f))
98
typedef pj_uint32_t pj_ufloat_t;
100
pj_ufloat_t pj_ufloat_from_float(float f)
102
return (pj_ufloat_t)(f * 65536);
105
unsigned pj_ufloat_mul_u(unsigned val1, pj_ufloat_t val2)
107
return (val1 * val2) >> 16;
110
int pj_ufloat_mul_i(int val1, pj_ufloat_t val2)
112
return (val1 * (pj_int32_t)val2) >> 16;
117
/* Conversation state */
118
typedef enum talk_state
127
const char *state_names[] =
139
The echo suppressor tries to find the position of echoed signal by looking
140
at the correlation between signal played to the speaker (played signal)
141
and the signal captured from the microphone (recorded signal).
143
To do this, it first divides the frames (from mic and speaker) into
144
segments, calculate the audio level of the segment, and save the level
145
information in the playback and record history (play_hist and rec_hist
148
In the history, the newest element (depicted as "t0" in the diagram belo)
149
is put in the last position of the array.
151
The record history size is as large as the template size (tmpl_cnt), since
152
we will use the record history as the template to find the best matching
153
position in the playback history.
155
Here is the record history buffer:
163
As you can see, the newest frame ("t0") is put as the last element in the
166
The playback history size is larger than record history, since we need to
167
find the matching pattern in the past. The playback history size is
168
"templ_cnt + tail_cnt", where "tail_cnt" is the number of segments equal
169
to the maximum tail length. The maximum tail length is set when the ES
172
Here is the playback history buffer:
174
<-----tail_cnt-----> <--templ_cnt-->
175
+-------------------+--------------+
177
+-------------------+--------------+
178
t-play_hist_cnt...t-templ_cnt.......t0
184
During the processing, the ES calculates the following values:
185
- the correlation value, that is how similar the playback signal compared
186
to the mic signal. The lower the correlation value the better (i.e. more
187
similar) the signal is. The correlation value is done over the template
189
- the gain scaling factor, that is the ratio between mic signal and
190
speaker signal. The ES calculates both the minimum and average ratios.
192
The ES calculates both the values above for every tail position in the
193
playback history. The values are saved in arrays below:
196
+-------------------+
198
+-------------------+
200
+-------------------+
202
+-------------------+
204
At the end of processing, the ES iterates through the correlation array and
205
picks the tail index with the lowest corr_sum value. This is the position
206
where echo is most likely to be found.
211
Once learning is done, the ES will change the level of the mic signal
212
depending on the state of the conversation and according to the ratio that
213
has been found in the learning phase above.
218
* The simple echo suppresor state
220
typedef struct echo_supp
222
unsigned clock_rate; /* Clock rate. */
223
pj_uint16_t samples_per_frame; /* Frame length in samples */
224
pj_uint16_t samples_per_segment;/* Segment length in samples */
225
pj_uint16_t tail_ms; /* Tail length in milliseconds */
226
pj_uint16_t tail_samples; /* Tail length in samples. */
228
pj_bool_t learning; /* Are we still learning yet? */
229
talk_state_t talk_state; /* Current talking state */
230
int tail_index; /* Echo location, -1 if not found */
232
unsigned max_calc; /* # of calc before learning complete.
233
(see MAX_CALC_DURATION_SEC) */
234
unsigned calc_cnt; /* Number of calculations so far */
236
unsigned update_cnt; /* # of updates */
237
unsigned templ_cnt; /* Template length, in # of segments */
238
unsigned tail_cnt; /* Tail length, in # of segments */
239
unsigned play_hist_cnt; /* # of segments in play_hist */
240
pj_uint16_t *play_hist; /* Array of playback levels */
241
pj_uint16_t *rec_hist; /* Array of rec levels */
243
float *corr_sum; /* Array of corr for each tail pos. */
244
float *tmp_corr; /* Temporary corr array calculation */
245
float best_corr; /* Best correlation so far. */
247
unsigned sum_rec_level; /* Running sum of level in rec_hist */
248
float rec_corr; /* Running corr in rec_hist. */
250
unsigned sum_play_level0; /* Running sum of level for first pos */
251
float play_corr0; /* Running corr for first pos . */
253
float *min_factor; /* Array of minimum scaling factor */
254
float *avg_factor; /* Array of average scaling factor */
255
float *tmp_factor; /* Array to store provisional result */
257
unsigned running_cnt; /* Running duration in # of frames */
258
float residue; /* Accummulated echo residue. */
259
float last_factor; /* Last factor applied to mic signal */
267
PJ_DEF(pj_status_t) echo_supp_create( pj_pool_t *pool,
269
unsigned channel_count,
270
unsigned samples_per_frame,
277
PJ_UNUSED_ARG(channel_count);
278
PJ_UNUSED_ARG(options);
280
PJ_ASSERT_RETURN(samples_per_frame >= SEGMENT_PTIME * clock_rate / 1000,
283
ec = PJ_POOL_ZALLOC_T(pool, struct echo_supp);
284
ec->clock_rate = clock_rate;
285
ec->samples_per_frame = (pj_uint16_t)samples_per_frame;
286
ec->samples_per_segment = (pj_uint16_t)(SEGMENT_PTIME * clock_rate / 1000);
287
ec->tail_ms = (pj_uint16_t)tail_ms;
288
ec->tail_samples = (pj_uint16_t)(tail_ms * clock_rate / 1000);
290
ec->templ_cnt = TEMPLATE_PTIME / SEGMENT_PTIME;
291
ec->tail_cnt = (pj_uint16_t)(tail_ms / SEGMENT_PTIME);
292
ec->play_hist_cnt = (pj_uint16_t)(ec->tail_cnt+ec->templ_cnt);
294
ec->max_calc = (pj_uint16_t)(MAX_CALC_DURATION_SEC * clock_rate /
295
ec->samples_per_segment);
297
ec->rec_hist = (pj_uint16_t*)
298
pj_pool_alloc(pool, ec->templ_cnt *
299
sizeof(ec->rec_hist[0]));
301
/* Note: play history has twice number of elements */
302
ec->play_hist = (pj_uint16_t*)
303
pj_pool_alloc(pool, ec->play_hist_cnt *
304
sizeof(ec->play_hist[0]));
306
ec->corr_sum = (float*)
307
pj_pool_alloc(pool, ec->tail_cnt *
308
sizeof(ec->corr_sum[0]));
309
ec->tmp_corr = (float*)
310
pj_pool_alloc(pool, ec->tail_cnt *
311
sizeof(ec->tmp_corr[0]));
312
ec->min_factor = (float*)
313
pj_pool_alloc(pool, ec->tail_cnt *
314
sizeof(ec->min_factor[0]));
315
ec->avg_factor = (float*)
316
pj_pool_alloc(pool, ec->tail_cnt *
317
sizeof(ec->avg_factor[0]));
318
ec->tmp_factor = (float*)
319
pj_pool_alloc(pool, ec->tail_cnt *
320
sizeof(ec->tmp_factor[0]));
331
PJ_DEF(pj_status_t) echo_supp_destroy(void *state)
333
PJ_UNUSED_ARG(state);
341
PJ_DEF(void) echo_supp_reset(void *state)
344
echo_supp *ec = (echo_supp*) state;
346
pj_bzero(ec->rec_hist, ec->templ_cnt * sizeof(ec->rec_hist[0]));
347
pj_bzero(ec->play_hist, ec->play_hist_cnt * sizeof(ec->play_hist[0]));
349
for (i=0; i<ec->tail_cnt; ++i) {
350
ec->corr_sum[i] = ec->avg_factor[i] = 0;
351
ec->min_factor[i] = MAX_FLOAT;
356
ec->learning = PJ_TRUE;
358
ec->best_corr = MAX_FLOAT;
359
ec->talk_state = ST_NULL;
360
ec->last_factor = 1.0;
363
ec->sum_rec_level = ec->sum_play_level0 = 0;
364
ec->rec_corr = ec->play_corr0 = 0;
368
* Soft reset to force the EC to re-learn without having to discard all
369
* rec and playback history.
371
PJ_DEF(void) echo_supp_soft_reset(void *state)
375
echo_supp *ec = (echo_supp*) state;
377
for (i=0; i<ec->tail_cnt; ++i) {
383
ec->learning = PJ_TRUE;
384
ec->best_corr = MAX_FLOAT;
387
ec->sum_rec_level = ec->sum_play_level0 = 0;
388
ec->rec_corr = ec->play_corr0 = 0;
390
PJ_LOG(4,(THIS_FILE, "Echo suppressor soft reset. Re-learning.."));
395
static void echo_supp_set_state(echo_supp *ec, talk_state_t state,
398
PJ_UNUSED_ARG(level);
400
if (state != ec->talk_state) {
401
TRACE_((THIS_FILE, "[%03d.%03d] %s --> %s, level=%u",
402
(ec->update_cnt * SEGMENT_PTIME / 1000),
403
((ec->update_cnt * SEGMENT_PTIME) % 1000),
404
state_names[ec->talk_state],
405
state_names[state], level));
406
ec->talk_state = state;
413
static void echo_supp_update(echo_supp *ec, pj_int16_t *rec_frm,
414
const pj_int16_t *play_frm)
417
unsigned i, j, frm_level, sum_play_level, ulaw;
418
pj_uint16_t old_rec_frm_level, old_play_frm_level;
422
if (ec->update_cnt > 0x7FFFFFFF)
423
ec->update_cnt = 0x7FFFFFFF; /* Detect overflow */
425
/* Calculate current play frame level */
426
frm_level = pjmedia_calc_avg_signal(play_frm, ec->samples_per_segment);
427
++frm_level; /* to avoid division by zero */
429
/* Save the oldest frame level for later */
430
old_play_frm_level = ec->play_hist[0];
432
/* Push current frame level to the back of the play history */
433
pj_array_erase(ec->play_hist, sizeof(pj_uint16_t), ec->play_hist_cnt, 0);
434
ec->play_hist[ec->play_hist_cnt-1] = (pj_uint16_t) frm_level;
436
/* Calculate level of current mic frame */
437
frm_level = pjmedia_calc_avg_signal(rec_frm, ec->samples_per_segment);
438
++frm_level; /* to avoid division by zero */
440
/* Save the oldest frame level for later */
441
old_rec_frm_level = ec->rec_hist[0];
443
/* Push to the back of the rec history */
444
pj_array_erase(ec->rec_hist, sizeof(pj_uint16_t), ec->templ_cnt, 0);
445
ec->rec_hist[ec->templ_cnt-1] = (pj_uint16_t) frm_level;
448
/* Can't do the calc until the play history is full. */
449
if (ec->update_cnt < ec->play_hist_cnt)
452
/* Skip if learning is done */
457
/* Calculate rec signal pattern */
458
if (ec->sum_rec_level == 0) {
459
/* Buffer has just been filled up, do full calculation */
461
ec->sum_rec_level = 0;
462
for (i=0; i < ec->templ_cnt-1; ++i) {
464
corr = (float)ec->rec_hist[i+1] / ec->rec_hist[i];
465
ec->rec_corr += corr;
466
ec->sum_rec_level += ec->rec_hist[i];
468
ec->sum_rec_level += ec->rec_hist[i];
470
/* Update from previous calculation */
471
ec->sum_rec_level = ec->sum_rec_level - old_rec_frm_level +
472
ec->rec_hist[ec->templ_cnt-1];
473
ec->rec_corr = ec->rec_corr - ((float)ec->rec_hist[0] /
475
((float)ec->rec_hist[ec->templ_cnt-1] /
476
ec->rec_hist[ec->templ_cnt-2]);
479
/* Iterate through the play history and calculate the signal correlation
480
* for every tail position in the play_hist. Save the result in temporary
481
* array since we may bail out early if the conversation state is not good
485
* First phase: do full calculation for the first position
487
if (ec->sum_play_level0 == 0) {
488
/* Buffer has just been filled up, do full calculation */
491
for (j=0; j<ec->templ_cnt-1; ++j) {
493
corr = (float)ec->play_hist[j+1] / ec->play_hist[j];
495
sum_play_level += ec->play_hist[j];
497
sum_play_level += ec->play_hist[j];
498
ec->sum_play_level0 = sum_play_level;
499
ec->play_corr0 = play_corr;
501
/* Update from previous calculation */
502
ec->sum_play_level0 = ec->sum_play_level0 - old_play_frm_level +
503
ec->play_hist[ec->templ_cnt-1];
504
ec->play_corr0 = ec->play_corr0 - ((float)ec->play_hist[0] /
505
old_play_frm_level) +
506
((float)ec->play_hist[ec->templ_cnt-1] /
507
ec->play_hist[ec->templ_cnt-2]);
508
sum_play_level = ec->sum_play_level0;
509
play_corr = ec->play_corr0;
511
ec->tmp_corr[0] = FABS(play_corr - ec->rec_corr);
512
ec->tmp_factor[0] = (float)ec->sum_rec_level / sum_play_level;
514
/* Bail out if remote isn't talking */
515
ulaw = pjmedia_linear2ulaw(sum_play_level/ec->templ_cnt) ^ 0xFF;
516
if (ulaw < MIN_SIGNAL_ULAW) {
517
echo_supp_set_state(ec, ST_REM_SILENT, ulaw);
520
/* Bail out if local user is talking */
521
if (ec->sum_rec_level >= sum_play_level) {
522
echo_supp_set_state(ec, ST_LOCAL_TALK, ulaw);
527
* Second phase: do incremental calculation for the rest of positions
529
for (i=1; i < ec->tail_cnt; ++i) {
532
end = i + ec->templ_cnt;
534
sum_play_level = sum_play_level - ec->play_hist[i-1] +
535
ec->play_hist[end-1];
536
play_corr = play_corr - ((float)ec->play_hist[i]/ec->play_hist[i-1]) +
537
((float)ec->play_hist[end-1]/ec->play_hist[end-2]);
539
/* Bail out if remote isn't talking */
540
ulaw = pjmedia_linear2ulaw(sum_play_level/ec->templ_cnt) ^ 0xFF;
541
if (ulaw < MIN_SIGNAL_ULAW) {
542
echo_supp_set_state(ec, ST_REM_SILENT, ulaw);
546
/* Bail out if local user is talking */
547
if (ec->sum_rec_level >= sum_play_level) {
548
echo_supp_set_state(ec, ST_LOCAL_TALK, ulaw);
553
// disabled: not a good idea if mic throws out loud echo
554
/* Also bail out if we suspect there's a doubletalk */
555
ulaw = pjmedia_linear2ulaw(ec->sum_rec_level/ec->templ_cnt) ^ 0xFF;
556
if (ulaw > MIN_SIGNAL_ULAW) {
557
echo_supp_set_state(ec, ST_DOUBLETALK, ulaw);
562
/* Calculate correlation and save to temporary array */
563
ec->tmp_corr[i] = FABS(play_corr - ec->rec_corr);
565
/* Also calculate the gain factor between mic and speaker level */
566
ec->tmp_factor[i] = (float)ec->sum_rec_level / sum_play_level;
567
pj_assert(ec->tmp_factor[i] < 1);
570
/* We seem to have good signal, we can update the EC state */
571
echo_supp_set_state(ec, ST_REM_TALK, MIN_SIGNAL_ULAW);
573
/* Accummulate the correlation value to the history and at the same
574
* time find the tail index of the best correlation.
576
prev_index = ec->tail_index;
577
for (i=1; i<ec->tail_cnt-1; ++i) {
578
float *p = &ec->corr_sum[i], sum;
580
/* Accummulate correlation value for this tail position */
581
ec->corr_sum[i] += ec->tmp_corr[i];
583
/* Update the min and avg gain factor for this tail position */
584
if (ec->tmp_factor[i] < ec->min_factor[i])
585
ec->min_factor[i] = ec->tmp_factor[i];
586
ec->avg_factor[i] = ((ec->avg_factor[i] * ec->tail_cnt) +
590
/* To get the best correlation, also include the correlation
591
* value of the neighbouring tail locations.
593
sum = *(p-1) + (*p)*2 + *(p+1);
596
/* See if we have better correlation value */
597
if (sum < ec->best_corr) {
603
if (ec->tail_index != prev_index) {
607
duration = ec->update_cnt * SEGMENT_PTIME;
608
imin = (int)(ec->min_factor[ec->tail_index] * 1000);
609
iavg = (int)(ec->avg_factor[ec->tail_index] * 1000);
612
"Echo suppressor updated at t=%03d.%03ds, echo tail=%d msec"
613
", factor min/avg=%d.%03d/%d.%03d",
614
(duration/1000), (duration%1000),
615
(ec->tail_cnt-ec->tail_index) * SEGMENT_PTIME,
616
imin/1000, imin%1000,
617
iavg/1000, iavg%1000));
623
if (ec->calc_cnt > ec->max_calc) {
628
ec->learning = PJ_FALSE;
631
duration = ec->update_cnt * SEGMENT_PTIME;
632
imin = (int)(ec->min_factor[ec->tail_index] * 1000);
633
iavg = (int)(ec->avg_factor[ec->tail_index] * 1000);
636
"Echo suppressor learning done at t=%03d.%03ds, tail=%d ms"
637
", factor min/avg=%d.%03d/%d.%03d",
638
(duration/1000), (duration%1000),
639
(ec->tail_cnt-ec->tail_index) * SEGMENT_PTIME,
640
imin/1000, imin%1000,
641
iavg/1000, iavg%1000));
648
static void amplify_frame(pj_int16_t *frm, unsigned length,
653
for (i=0; i<length; ++i) {
654
frm[i] = (pj_int16_t)pj_ufloat_mul_i(frm[i], factor);
659
* Perform echo cancellation.
661
PJ_DEF(pj_status_t) echo_supp_cancel_echo( void *state,
663
const pj_int16_t *play_frm,
668
echo_supp *ec = (echo_supp*) state;
670
PJ_UNUSED_ARG(options);
671
PJ_UNUSED_ARG(reserved);
673
/* Calculate number of segments. This should be okay even if
674
* samples_per_frame is not a multiply of samples_per_segment, since
675
* we only calculate level.
677
N = ec->samples_per_frame / ec->samples_per_segment;
679
for (i=0; i<N; ++i) {
680
unsigned pos = i * ec->samples_per_segment;
681
echo_supp_update(ec, rec_frm+pos, play_frm+pos);
684
if (ec->tail_index < 0) {
687
unsigned lookup_cnt, rec_level=0, play_level=0;
691
/* How many previous segments to lookup */
692
lookup_cnt = SIGNAL_LOOKUP_MSEC / SEGMENT_PTIME;
693
if (lookup_cnt > ec->templ_cnt)
694
lookup_cnt = ec->templ_cnt;
696
/* Lookup in recording history to get maximum mic level, to see
697
* if local user is currently talking
699
for (i=ec->templ_cnt - lookup_cnt; i < ec->templ_cnt; ++i) {
700
if (ec->rec_hist[i] > rec_level)
701
rec_level = ec->rec_hist[i];
703
rec_level = pjmedia_linear2ulaw(rec_level) ^ 0xFF;
705
/* Calculate the detected tail length, in # of segments */
706
tail_cnt = (ec->tail_cnt - ec->tail_index);
708
/* Lookup in playback history to get max speaker level, to see
709
* if remote user is currently talking
711
for (i=ec->play_hist_cnt -lookup_cnt -tail_cnt;
712
i<ec->play_hist_cnt-tail_cnt; ++i)
714
if (ec->play_hist[i] > play_level)
715
play_level = ec->play_hist[i];
717
play_level = pjmedia_linear2ulaw(play_level) ^ 0xFF;
719
if (rec_level >= MIN_SIGNAL_ULAW) {
720
if (play_level < MIN_SIGNAL_ULAW) {
721
/* Mic is talking, speaker is idle. Let mic signal pass as is.
724
echo_supp_set_state(ec, ST_LOCAL_TALK, rec_level);
725
} else if (rec_level > play_level) {
726
/* Seems that both are talking. Scale the mic signal
727
* down a little bit to reduce echo, while allowing both
728
* parties to talk at the same time.
730
factor = (float)(ec->avg_factor[ec->tail_index] * 2);
731
echo_supp_set_state(ec, ST_DOUBLETALK, rec_level);
733
/* Speaker is active, but we've picked up large signal in
734
* the microphone. Assume that this is an echo, so bring
735
* the level down to minimum too.
737
factor = ec->min_factor[ec->tail_index] / 2;
738
echo_supp_set_state(ec, ST_REM_TALK, play_level);
741
if (play_level < MIN_SIGNAL_ULAW) {
742
/* Both mic and speaker seems to be idle. Also scale the
743
* mic signal down with average factor to reduce low power
746
factor = ec->avg_factor[ec->tail_index] * 3 / 2;
747
echo_supp_set_state(ec, ST_REM_SILENT, rec_level);
749
/* Mic is idle, but there's something playing in speaker.
750
* Scale the mic down to minimum
752
factor = ec->min_factor[ec->tail_index] / 2;
753
echo_supp_set_state(ec, ST_REM_TALK, play_level);
757
/* Smoothen the transition */
758
if (factor >= ec->last_factor)
759
factor = (factor + ec->last_factor) / 2;
761
factor = (factor + ec->last_factor*19) / 20;
764
amplify_frame(rec_frm, ec->samples_per_frame,
765
pj_ufloat_from_float(factor));
766
ec->last_factor = factor;
768
if (ec->talk_state == ST_REM_TALK) {
769
unsigned level, recalc_cnt;
771
/* Get the adjusted frame signal level */
772
level = pjmedia_calc_avg_signal(rec_frm, ec->samples_per_frame);
773
level = pjmedia_linear2ulaw(level) ^ 0xFF;
775
/* Accumulate average echo residue to see the ES effectiveness */
776
ec->residue = ((ec->residue * ec->running_cnt) + level) /
777
(ec->running_cnt + 1);
781
/* Check if we need to re-learn */
782
recalc_cnt = CHECK_PERIOD * ec->clock_rate / ec->samples_per_frame;
783
if (ec->running_cnt > recalc_cnt) {
786
iresidue = (int)(ec->residue*1000);
788
PJ_LOG(5,(THIS_FILE, "Echo suppressor residue = %d.%03d",
789
iresidue/1000, iresidue%1000));
791
if (ec->residue > MAX_RESIDUE && !ec->learning) {
792
echo_supp_soft_reset(ec);