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 28 static const size_t kBufSizeSamp = BUF_SIZE_FRAMES * FRAME_LEN; // buffer size (samples) 29 static const int kSampMsNb = 8; // samples per ms in nb 30 // Target suppression levels for nlp modes 31 // log{0.001, 0.00001, 0.00000001} 32 static const int kInitCheck = 42; 33 34 typedef 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) 76 static int WebRtcAecm_EstBufDelay(AecMobile* aecmInst, short msInSndCardBuf); 77 78 // Stuffs the farend buffer if the estimated delay is too large 79 static int WebRtcAecm_DelayComp(AecMobile* aecmInst); 80 81 void* 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 116 void 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 139 int32_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. 197 int32_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 217 int32_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 238 int32_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 436 int32_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 515 int32_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 543 int32_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 570 size_t WebRtcAecm_echo_path_size_bytes() 571 { 572 return (PART_LEN1 * sizeof(int16_t)); 573 } 574 575 576 static 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 625 static 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