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