1/*
2 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "webrtc/modules/audio_processing/aecm/echo_control_mobile.h"
12
13#ifdef AEC_DEBUG
14#include <stdio.h>
15#endif
16#include <stdlib.h>
17
18#include "webrtc/common_audio/ring_buffer.h"
19#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h"
20#include "webrtc/modules/audio_processing/aecm/aecm_core.h"
21
22#define BUF_SIZE_FRAMES 50 // buffer size (frames)
23// Maximum length of resampled signal. Must be an integer multiple of frames
24// (ceil(1/(1 + MIN_SKEW)*2) + 1)*FRAME_LEN
25// The factor of 2 handles wb, and the + 1 is as a safety margin
26#define MAX_RESAMP_LEN (5 * FRAME_LEN)
27
28static const size_t kBufSizeSamp = BUF_SIZE_FRAMES * FRAME_LEN; // buffer size (samples)
29static const int kSampMsNb = 8; // samples per ms in nb
30// Target suppression levels for nlp modes
31// log{0.001, 0.00001, 0.00000001}
32static const int kInitCheck = 42;
33
34typedef struct
35{
36    int sampFreq;
37    int scSampFreq;
38    short bufSizeStart;
39    int knownDelay;
40
41    // Stores the last frame added to the farend buffer
42    short farendOld[2][FRAME_LEN];
43    short initFlag; // indicates if AEC has been initialized
44
45    // Variables used for averaging far end buffer size
46    short counter;
47    short sum;
48    short firstVal;
49    short checkBufSizeCtr;
50
51    // Variables used for delay shifts
52    short msInSndCardBuf;
53    short filtDelay;
54    int timeForDelayChange;
55    int ECstartup;
56    int checkBuffSize;
57    int delayChange;
58    short lastDelayDiff;
59
60    int16_t echoMode;
61
62#ifdef AEC_DEBUG
63    FILE *bufFile;
64    FILE *delayFile;
65    FILE *preCompFile;
66    FILE *postCompFile;
67#endif // AEC_DEBUG
68    // Structures
69    RingBuffer *farendBuf;
70
71    AecmCore* aecmCore;
72} AecMobile;
73
74// Estimates delay to set the position of the farend buffer read pointer
75// (controlled by knownDelay)
76static int WebRtcAecm_EstBufDelay(AecMobile* aecmInst, short msInSndCardBuf);
77
78// Stuffs the farend buffer if the estimated delay is too large
79static int WebRtcAecm_DelayComp(AecMobile* aecmInst);
80
81void* WebRtcAecm_Create() {
82    AecMobile* aecm = malloc(sizeof(AecMobile));
83
84    WebRtcSpl_Init();
85
86    aecm->aecmCore = WebRtcAecm_CreateCore();
87    if (!aecm->aecmCore) {
88        WebRtcAecm_Free(aecm);
89        return NULL;
90    }
91
92    aecm->farendBuf = WebRtc_CreateBuffer(kBufSizeSamp,
93                                          sizeof(int16_t));
94    if (!aecm->farendBuf)
95    {
96        WebRtcAecm_Free(aecm);
97        return NULL;
98    }
99
100    aecm->initFlag = 0;
101
102#ifdef AEC_DEBUG
103    aecm->aecmCore->farFile = fopen("aecFar.pcm","wb");
104    aecm->aecmCore->nearFile = fopen("aecNear.pcm","wb");
105    aecm->aecmCore->outFile = fopen("aecOut.pcm","wb");
106    //aecm->aecmCore->outLpFile = fopen("aecOutLp.pcm","wb");
107
108    aecm->bufFile = fopen("aecBuf.dat", "wb");
109    aecm->delayFile = fopen("aecDelay.dat", "wb");
110    aecm->preCompFile = fopen("preComp.pcm", "wb");
111    aecm->postCompFile = fopen("postComp.pcm", "wb");
112#endif // AEC_DEBUG
113    return aecm;
114}
115
116void WebRtcAecm_Free(void* aecmInst) {
117  AecMobile* aecm = aecmInst;
118
119    if (aecm == NULL) {
120      return;
121    }
122
123#ifdef AEC_DEBUG
124    fclose(aecm->aecmCore->farFile);
125    fclose(aecm->aecmCore->nearFile);
126    fclose(aecm->aecmCore->outFile);
127    //fclose(aecm->aecmCore->outLpFile);
128
129    fclose(aecm->bufFile);
130    fclose(aecm->delayFile);
131    fclose(aecm->preCompFile);
132    fclose(aecm->postCompFile);
133#endif // AEC_DEBUG
134    WebRtcAecm_FreeCore(aecm->aecmCore);
135    WebRtc_FreeBuffer(aecm->farendBuf);
136    free(aecm);
137}
138
139int32_t WebRtcAecm_Init(void *aecmInst, int32_t sampFreq)
140{
141  AecMobile* aecm = aecmInst;
142    AecmConfig aecConfig;
143
144    if (aecm == NULL)
145    {
146        return -1;
147    }
148
149    if (sampFreq != 8000 && sampFreq != 16000)
150    {
151        return AECM_BAD_PARAMETER_ERROR;
152    }
153    aecm->sampFreq = sampFreq;
154
155    // Initialize AECM core
156    if (WebRtcAecm_InitCore(aecm->aecmCore, aecm->sampFreq) == -1)
157    {
158        return AECM_UNSPECIFIED_ERROR;
159    }
160
161    // Initialize farend buffer
162    WebRtc_InitBuffer(aecm->farendBuf);
163
164    aecm->initFlag = kInitCheck; // indicates that initialization has been done
165
166    aecm->delayChange = 1;
167
168    aecm->sum = 0;
169    aecm->counter = 0;
170    aecm->checkBuffSize = 1;
171    aecm->firstVal = 0;
172
173    aecm->ECstartup = 1;
174    aecm->bufSizeStart = 0;
175    aecm->checkBufSizeCtr = 0;
176    aecm->filtDelay = 0;
177    aecm->timeForDelayChange = 0;
178    aecm->knownDelay = 0;
179    aecm->lastDelayDiff = 0;
180
181    memset(&aecm->farendOld[0][0], 0, 160);
182
183    // Default settings.
184    aecConfig.cngMode = AecmTrue;
185    aecConfig.echoMode = 3;
186
187    if (WebRtcAecm_set_config(aecm, aecConfig) == -1)
188    {
189        return AECM_UNSPECIFIED_ERROR;
190    }
191
192    return 0;
193}
194
195// Returns any error that is caused when buffering the
196// farend signal.
197int32_t WebRtcAecm_GetBufferFarendError(void *aecmInst, const int16_t *farend,
198                                size_t nrOfSamples) {
199  AecMobile* aecm = aecmInst;
200
201  if (aecm == NULL)
202    return -1;
203
204  if (farend == NULL)
205    return AECM_NULL_POINTER_ERROR;
206
207  if (aecm->initFlag != kInitCheck)
208    return AECM_UNINITIALIZED_ERROR;
209
210  if (nrOfSamples != 80 && nrOfSamples != 160)
211    return AECM_BAD_PARAMETER_ERROR;
212
213  return 0;
214}
215
216
217int32_t WebRtcAecm_BufferFarend(void *aecmInst, const int16_t *farend,
218                                size_t nrOfSamples) {
219  AecMobile* aecm = aecmInst;
220
221  const int32_t err =
222      WebRtcAecm_GetBufferFarendError(aecmInst, farend, nrOfSamples);
223
224  if (err != 0)
225    return err;
226
227  // TODO(unknown): Is this really a good idea?
228  if (!aecm->ECstartup)
229  {
230    WebRtcAecm_DelayComp(aecm);
231  }
232
233  WebRtc_WriteBuffer(aecm->farendBuf, farend, nrOfSamples);
234
235  return 0;
236}
237
238int32_t WebRtcAecm_Process(void *aecmInst, const int16_t *nearendNoisy,
239                           const int16_t *nearendClean, int16_t *out,
240                           size_t nrOfSamples, int16_t msInSndCardBuf)
241{
242  AecMobile* aecm = aecmInst;
243    int32_t retVal = 0;
244    size_t i;
245    short nmbrOfFilledBuffers;
246    size_t nBlocks10ms;
247    size_t nFrames;
248#ifdef AEC_DEBUG
249    short msInAECBuf;
250#endif
251
252    if (aecm == NULL)
253    {
254        return -1;
255    }
256
257    if (nearendNoisy == NULL)
258    {
259        return AECM_NULL_POINTER_ERROR;
260    }
261
262    if (out == NULL)
263    {
264        return AECM_NULL_POINTER_ERROR;
265    }
266
267    if (aecm->initFlag != kInitCheck)
268    {
269        return AECM_UNINITIALIZED_ERROR;
270    }
271
272    if (nrOfSamples != 80 && nrOfSamples != 160)
273    {
274        return AECM_BAD_PARAMETER_ERROR;
275    }
276
277    if (msInSndCardBuf < 0)
278    {
279        msInSndCardBuf = 0;
280        retVal = AECM_BAD_PARAMETER_WARNING;
281    } else if (msInSndCardBuf > 500)
282    {
283        msInSndCardBuf = 500;
284        retVal = AECM_BAD_PARAMETER_WARNING;
285    }
286    msInSndCardBuf += 10;
287    aecm->msInSndCardBuf = msInSndCardBuf;
288
289    nFrames = nrOfSamples / FRAME_LEN;
290    nBlocks10ms = nFrames / aecm->aecmCore->mult;
291
292    if (aecm->ECstartup)
293    {
294        if (nearendClean == NULL)
295        {
296            if (out != nearendNoisy)
297            {
298                memcpy(out, nearendNoisy, sizeof(short) * nrOfSamples);
299            }
300        } else if (out != nearendClean)
301        {
302            memcpy(out, nearendClean, sizeof(short) * nrOfSamples);
303        }
304
305        nmbrOfFilledBuffers =
306            (short) WebRtc_available_read(aecm->farendBuf) / FRAME_LEN;
307        // The AECM is in the start up mode
308        // AECM is disabled until the soundcard buffer and farend buffers are OK
309
310        // Mechanism to ensure that the soundcard buffer is reasonably stable.
311        if (aecm->checkBuffSize)
312        {
313            aecm->checkBufSizeCtr++;
314            // Before we fill up the far end buffer we require the amount of data on the
315            // sound card to be stable (+/-8 ms) compared to the first value. This
316            // comparison is made during the following 4 consecutive frames. If it seems
317            // to be stable then we start to fill up the far end buffer.
318
319            if (aecm->counter == 0)
320            {
321                aecm->firstVal = aecm->msInSndCardBuf;
322                aecm->sum = 0;
323            }
324
325            if (abs(aecm->firstVal - aecm->msInSndCardBuf)
326                    < WEBRTC_SPL_MAX(0.2 * aecm->msInSndCardBuf, kSampMsNb))
327            {
328                aecm->sum += aecm->msInSndCardBuf;
329                aecm->counter++;
330            } else
331            {
332                aecm->counter = 0;
333            }
334
335            if (aecm->counter * nBlocks10ms >= 6)
336            {
337                // The farend buffer size is determined in blocks of 80 samples
338                // Use 75% of the average value of the soundcard buffer
339                aecm->bufSizeStart
340                        = WEBRTC_SPL_MIN((3 * aecm->sum
341                                        * aecm->aecmCore->mult) / (aecm->counter * 40), BUF_SIZE_FRAMES);
342                // buffersize has now been determined
343                aecm->checkBuffSize = 0;
344            }
345
346            if (aecm->checkBufSizeCtr * nBlocks10ms > 50)
347            {
348                // for really bad sound cards, don't disable echocanceller for more than 0.5 sec
349                aecm->bufSizeStart = WEBRTC_SPL_MIN((3 * aecm->msInSndCardBuf
350                                * aecm->aecmCore->mult) / 40, BUF_SIZE_FRAMES);
351                aecm->checkBuffSize = 0;
352            }
353        }
354
355        // if checkBuffSize changed in the if-statement above
356        if (!aecm->checkBuffSize)
357        {
358            // soundcard buffer is now reasonably stable
359            // When the far end buffer is filled with approximately the same amount of
360            // data as the amount on the sound card we end the start up phase and start
361            // to cancel echoes.
362
363            if (nmbrOfFilledBuffers == aecm->bufSizeStart)
364            {
365                aecm->ECstartup = 0; // Enable the AECM
366            } else if (nmbrOfFilledBuffers > aecm->bufSizeStart)
367            {
368                WebRtc_MoveReadPtr(aecm->farendBuf,
369                                   (int) WebRtc_available_read(aecm->farendBuf)
370                                   - (int) aecm->bufSizeStart * FRAME_LEN);
371                aecm->ECstartup = 0;
372            }
373        }
374
375    } else
376    {
377        // AECM is enabled
378
379        // Note only 1 block supported for nb and 2 blocks for wb
380        for (i = 0; i < nFrames; i++)
381        {
382            int16_t farend[FRAME_LEN];
383            const int16_t* farend_ptr = NULL;
384
385            nmbrOfFilledBuffers =
386                (short) WebRtc_available_read(aecm->farendBuf) / FRAME_LEN;
387
388            // Check that there is data in the far end buffer
389            if (nmbrOfFilledBuffers > 0)
390            {
391                // Get the next 80 samples from the farend buffer
392                WebRtc_ReadBuffer(aecm->farendBuf, (void**) &farend_ptr, farend,
393                                  FRAME_LEN);
394
395                // Always store the last frame for use when we run out of data
396                memcpy(&(aecm->farendOld[i][0]), farend_ptr,
397                       FRAME_LEN * sizeof(short));
398            } else
399            {
400                // We have no data so we use the last played frame
401                memcpy(farend, &(aecm->farendOld[i][0]), FRAME_LEN * sizeof(short));
402                farend_ptr = farend;
403            }
404
405            // Call buffer delay estimator when all data is extracted,
406            // i,e. i = 0 for NB and i = 1 for WB
407            if ((i == 0 && aecm->sampFreq == 8000) || (i == 1 && aecm->sampFreq == 16000))
408            {
409                WebRtcAecm_EstBufDelay(aecm, aecm->msInSndCardBuf);
410            }
411
412            // Call the AECM
413            /*WebRtcAecm_ProcessFrame(aecm->aecmCore, farend, &nearend[FRAME_LEN * i],
414             &out[FRAME_LEN * i], aecm->knownDelay);*/
415            if (WebRtcAecm_ProcessFrame(aecm->aecmCore,
416                                        farend_ptr,
417                                        &nearendNoisy[FRAME_LEN * i],
418                                        (nearendClean
419                                         ? &nearendClean[FRAME_LEN * i]
420                                         : NULL),
421                                        &out[FRAME_LEN * i]) == -1)
422                return -1;
423        }
424    }
425
426#ifdef AEC_DEBUG
427    msInAECBuf = (short) WebRtc_available_read(aecm->farendBuf) /
428        (kSampMsNb * aecm->aecmCore->mult);
429    fwrite(&msInAECBuf, 2, 1, aecm->bufFile);
430    fwrite(&(aecm->knownDelay), sizeof(aecm->knownDelay), 1, aecm->delayFile);
431#endif
432
433    return retVal;
434}
435
436int32_t WebRtcAecm_set_config(void *aecmInst, AecmConfig config)
437{
438  AecMobile* aecm = aecmInst;
439
440    if (aecm == NULL)
441    {
442        return -1;
443    }
444
445    if (aecm->initFlag != kInitCheck)
446    {
447        return AECM_UNINITIALIZED_ERROR;
448    }
449
450    if (config.cngMode != AecmFalse && config.cngMode != AecmTrue)
451    {
452        return AECM_BAD_PARAMETER_ERROR;
453    }
454    aecm->aecmCore->cngMode = config.cngMode;
455
456    if (config.echoMode < 0 || config.echoMode > 4)
457    {
458        return AECM_BAD_PARAMETER_ERROR;
459    }
460    aecm->echoMode = config.echoMode;
461
462    if (aecm->echoMode == 0)
463    {
464        aecm->aecmCore->supGain = SUPGAIN_DEFAULT >> 3;
465        aecm->aecmCore->supGainOld = SUPGAIN_DEFAULT >> 3;
466        aecm->aecmCore->supGainErrParamA = SUPGAIN_ERROR_PARAM_A >> 3;
467        aecm->aecmCore->supGainErrParamD = SUPGAIN_ERROR_PARAM_D >> 3;
468        aecm->aecmCore->supGainErrParamDiffAB = (SUPGAIN_ERROR_PARAM_A >> 3)
469                - (SUPGAIN_ERROR_PARAM_B >> 3);
470        aecm->aecmCore->supGainErrParamDiffBD = (SUPGAIN_ERROR_PARAM_B >> 3)
471                - (SUPGAIN_ERROR_PARAM_D >> 3);
472    } else if (aecm->echoMode == 1)
473    {
474        aecm->aecmCore->supGain = SUPGAIN_DEFAULT >> 2;
475        aecm->aecmCore->supGainOld = SUPGAIN_DEFAULT >> 2;
476        aecm->aecmCore->supGainErrParamA = SUPGAIN_ERROR_PARAM_A >> 2;
477        aecm->aecmCore->supGainErrParamD = SUPGAIN_ERROR_PARAM_D >> 2;
478        aecm->aecmCore->supGainErrParamDiffAB = (SUPGAIN_ERROR_PARAM_A >> 2)
479                - (SUPGAIN_ERROR_PARAM_B >> 2);
480        aecm->aecmCore->supGainErrParamDiffBD = (SUPGAIN_ERROR_PARAM_B >> 2)
481                - (SUPGAIN_ERROR_PARAM_D >> 2);
482    } else if (aecm->echoMode == 2)
483    {
484        aecm->aecmCore->supGain = SUPGAIN_DEFAULT >> 1;
485        aecm->aecmCore->supGainOld = SUPGAIN_DEFAULT >> 1;
486        aecm->aecmCore->supGainErrParamA = SUPGAIN_ERROR_PARAM_A >> 1;
487        aecm->aecmCore->supGainErrParamD = SUPGAIN_ERROR_PARAM_D >> 1;
488        aecm->aecmCore->supGainErrParamDiffAB = (SUPGAIN_ERROR_PARAM_A >> 1)
489                - (SUPGAIN_ERROR_PARAM_B >> 1);
490        aecm->aecmCore->supGainErrParamDiffBD = (SUPGAIN_ERROR_PARAM_B >> 1)
491                - (SUPGAIN_ERROR_PARAM_D >> 1);
492    } else if (aecm->echoMode == 3)
493    {
494        aecm->aecmCore->supGain = SUPGAIN_DEFAULT;
495        aecm->aecmCore->supGainOld = SUPGAIN_DEFAULT;
496        aecm->aecmCore->supGainErrParamA = SUPGAIN_ERROR_PARAM_A;
497        aecm->aecmCore->supGainErrParamD = SUPGAIN_ERROR_PARAM_D;
498        aecm->aecmCore->supGainErrParamDiffAB = SUPGAIN_ERROR_PARAM_A - SUPGAIN_ERROR_PARAM_B;
499        aecm->aecmCore->supGainErrParamDiffBD = SUPGAIN_ERROR_PARAM_B - SUPGAIN_ERROR_PARAM_D;
500    } else if (aecm->echoMode == 4)
501    {
502        aecm->aecmCore->supGain = SUPGAIN_DEFAULT << 1;
503        aecm->aecmCore->supGainOld = SUPGAIN_DEFAULT << 1;
504        aecm->aecmCore->supGainErrParamA = SUPGAIN_ERROR_PARAM_A << 1;
505        aecm->aecmCore->supGainErrParamD = SUPGAIN_ERROR_PARAM_D << 1;
506        aecm->aecmCore->supGainErrParamDiffAB = (SUPGAIN_ERROR_PARAM_A << 1)
507                - (SUPGAIN_ERROR_PARAM_B << 1);
508        aecm->aecmCore->supGainErrParamDiffBD = (SUPGAIN_ERROR_PARAM_B << 1)
509                - (SUPGAIN_ERROR_PARAM_D << 1);
510    }
511
512    return 0;
513}
514
515int32_t WebRtcAecm_InitEchoPath(void* aecmInst,
516                                const void* echo_path,
517                                size_t size_bytes)
518{
519  AecMobile* aecm = aecmInst;
520    const int16_t* echo_path_ptr = echo_path;
521
522    if (aecmInst == NULL) {
523      return -1;
524    }
525    if (echo_path == NULL) {
526      return AECM_NULL_POINTER_ERROR;
527    }
528    if (size_bytes != WebRtcAecm_echo_path_size_bytes())
529    {
530        // Input channel size does not match the size of AECM
531        return AECM_BAD_PARAMETER_ERROR;
532    }
533    if (aecm->initFlag != kInitCheck)
534    {
535        return AECM_UNINITIALIZED_ERROR;
536    }
537
538    WebRtcAecm_InitEchoPathCore(aecm->aecmCore, echo_path_ptr);
539
540    return 0;
541}
542
543int32_t WebRtcAecm_GetEchoPath(void* aecmInst,
544                               void* echo_path,
545                               size_t size_bytes)
546{
547  AecMobile* aecm = aecmInst;
548    int16_t* echo_path_ptr = echo_path;
549
550    if (aecmInst == NULL) {
551      return -1;
552    }
553    if (echo_path == NULL) {
554      return AECM_NULL_POINTER_ERROR;
555    }
556    if (size_bytes != WebRtcAecm_echo_path_size_bytes())
557    {
558        // Input channel size does not match the size of AECM
559        return AECM_BAD_PARAMETER_ERROR;
560    }
561    if (aecm->initFlag != kInitCheck)
562    {
563        return AECM_UNINITIALIZED_ERROR;
564    }
565
566    memcpy(echo_path_ptr, aecm->aecmCore->channelStored, size_bytes);
567    return 0;
568}
569
570size_t WebRtcAecm_echo_path_size_bytes()
571{
572    return (PART_LEN1 * sizeof(int16_t));
573}
574
575
576static int WebRtcAecm_EstBufDelay(AecMobile* aecm, short msInSndCardBuf) {
577    short delayNew, nSampSndCard;
578    short nSampFar = (short) WebRtc_available_read(aecm->farendBuf);
579    short diff;
580
581    nSampSndCard = msInSndCardBuf * kSampMsNb * aecm->aecmCore->mult;
582
583    delayNew = nSampSndCard - nSampFar;
584
585    if (delayNew < FRAME_LEN)
586    {
587        WebRtc_MoveReadPtr(aecm->farendBuf, FRAME_LEN);
588        delayNew += FRAME_LEN;
589    }
590
591    aecm->filtDelay = WEBRTC_SPL_MAX(0, (8 * aecm->filtDelay + 2 * delayNew) / 10);
592
593    diff = aecm->filtDelay - aecm->knownDelay;
594    if (diff > 224)
595    {
596        if (aecm->lastDelayDiff < 96)
597        {
598            aecm->timeForDelayChange = 0;
599        } else
600        {
601            aecm->timeForDelayChange++;
602        }
603    } else if (diff < 96 && aecm->knownDelay > 0)
604    {
605        if (aecm->lastDelayDiff > 224)
606        {
607            aecm->timeForDelayChange = 0;
608        } else
609        {
610            aecm->timeForDelayChange++;
611        }
612    } else
613    {
614        aecm->timeForDelayChange = 0;
615    }
616    aecm->lastDelayDiff = diff;
617
618    if (aecm->timeForDelayChange > 25)
619    {
620        aecm->knownDelay = WEBRTC_SPL_MAX((int)aecm->filtDelay - 160, 0);
621    }
622    return 0;
623}
624
625static int WebRtcAecm_DelayComp(AecMobile* aecm) {
626    int nSampFar = (int) WebRtc_available_read(aecm->farendBuf);
627    int nSampSndCard, delayNew, nSampAdd;
628    const int maxStuffSamp = 10 * FRAME_LEN;
629
630    nSampSndCard = aecm->msInSndCardBuf * kSampMsNb * aecm->aecmCore->mult;
631    delayNew = nSampSndCard - nSampFar;
632
633    if (delayNew > FAR_BUF_LEN - FRAME_LEN * aecm->aecmCore->mult)
634    {
635        // The difference of the buffer sizes is larger than the maximum
636        // allowed known delay. Compensate by stuffing the buffer.
637        nSampAdd = (int)(WEBRTC_SPL_MAX(((nSampSndCard >> 1) - nSampFar),
638                FRAME_LEN));
639        nSampAdd = WEBRTC_SPL_MIN(nSampAdd, maxStuffSamp);
640
641        WebRtc_MoveReadPtr(aecm->farendBuf, -nSampAdd);
642        aecm->delayChange = 1; // the delay needs to be updated
643    }
644
645    return 0;
646}
647