1/* 2 * Copyright 2018 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#import <XCTest/XCTest.h> 12 13#if defined(WEBRTC_IOS) 14#import "sdk/objc/native/api/audio_device_module.h" 15#endif 16 17#include "api/scoped_refptr.h" 18 19typedef int32_t(^NeedMorePlayDataBlock)(const size_t nSamples, 20 const size_t nBytesPerSample, 21 const size_t nChannels, 22 const uint32_t samplesPerSec, 23 void* audioSamples, 24 size_t& nSamplesOut, 25 int64_t* elapsed_time_ms, 26 int64_t* ntp_time_ms); 27 28typedef int32_t(^RecordedDataIsAvailableBlock)(const void* audioSamples, 29 const size_t nSamples, 30 const size_t nBytesPerSample, 31 const size_t nChannels, 32 const uint32_t samplesPerSec, 33 const uint32_t totalDelayMS, 34 const int32_t clockDrift, 35 const uint32_t currentMicLevel, 36 const bool keyPressed, 37 uint32_t& newMicLevel); 38 39 40// This class implements the AudioTransport API and forwards all methods to the appropriate blocks. 41class MockAudioTransport : public webrtc::AudioTransport { 42public: 43 MockAudioTransport() {} 44 ~MockAudioTransport() override {} 45 46 void expectNeedMorePlayData(NeedMorePlayDataBlock block) { 47 needMorePlayDataBlock = block; 48 } 49 50 void expectRecordedDataIsAvailable(RecordedDataIsAvailableBlock block) { 51 recordedDataIsAvailableBlock = block; 52 } 53 54 int32_t NeedMorePlayData(const size_t nSamples, 55 const size_t nBytesPerSample, 56 const size_t nChannels, 57 const uint32_t samplesPerSec, 58 void* audioSamples, 59 size_t& nSamplesOut, 60 int64_t* elapsed_time_ms, 61 int64_t* ntp_time_ms) override { 62 return needMorePlayDataBlock(nSamples, 63 nBytesPerSample, 64 nChannels, 65 samplesPerSec, 66 audioSamples, 67 nSamplesOut, 68 elapsed_time_ms, 69 ntp_time_ms); 70 } 71 72 int32_t RecordedDataIsAvailable(const void* audioSamples, 73 const size_t nSamples, 74 const size_t nBytesPerSample, 75 const size_t nChannels, 76 const uint32_t samplesPerSec, 77 const uint32_t totalDelayMS, 78 const int32_t clockDrift, 79 const uint32_t currentMicLevel, 80 const bool keyPressed, 81 uint32_t& newMicLevel) override { 82 return recordedDataIsAvailableBlock(audioSamples, 83 nSamples, 84 nBytesPerSample, 85 nChannels, 86 samplesPerSec, 87 totalDelayMS, 88 clockDrift, 89 currentMicLevel, 90 keyPressed, 91 newMicLevel); 92 } 93 94 void PullRenderData(int bits_per_sample, 95 int sample_rate, 96 size_t number_of_channels, 97 size_t number_of_frames, 98 void* audio_data, 99 int64_t* elapsed_time_ms, 100 int64_t* ntp_time_ms) override {} 101 102 private: 103 NeedMorePlayDataBlock needMorePlayDataBlock; 104 RecordedDataIsAvailableBlock recordedDataIsAvailableBlock; 105}; 106 107// Number of callbacks (input or output) the tests waits for before we set 108// an event indicating that the test was OK. 109static const NSUInteger kNumCallbacks = 10; 110// Max amount of time we wait for an event to be set while counting callbacks. 111static const NSTimeInterval kTestTimeOutInSec = 20.0; 112// Number of bits per PCM audio sample. 113static const NSUInteger kBitsPerSample = 16; 114// Number of bytes per PCM audio sample. 115static const NSUInteger kBytesPerSample = kBitsPerSample / 8; 116// Average number of audio callbacks per second assuming 10ms packet size. 117static const NSUInteger kNumCallbacksPerSecond = 100; 118// Play out a test file during this time (unit is in seconds). 119static const NSUInteger kFilePlayTimeInSec = 15; 120// Run the full-duplex test during this time (unit is in seconds). 121// Note that first |kNumIgnoreFirstCallbacks| are ignored. 122static const NSUInteger kFullDuplexTimeInSec = 10; 123// Wait for the callback sequence to stabilize by ignoring this amount of the 124// initial callbacks (avoids initial FIFO access). 125// Only used in the RunPlayoutAndRecordingInFullDuplex test. 126static const NSUInteger kNumIgnoreFirstCallbacks = 50; 127 128@interface RTCAudioDeviceModuleTests : XCTestCase { 129 rtc::scoped_refptr<webrtc::AudioDeviceModule> audioDeviceModule; 130 MockAudioTransport mock; 131} 132 133@property(nonatomic, assign) webrtc::AudioParameters playoutParameters; 134@property(nonatomic, assign) webrtc::AudioParameters recordParameters; 135 136@end 137 138@implementation RTCAudioDeviceModuleTests 139 140@synthesize playoutParameters; 141@synthesize recordParameters; 142 143- (void)setUp { 144 [super setUp]; 145 audioDeviceModule = webrtc::CreateAudioDeviceModule(); 146 XCTAssertEqual(0, audioDeviceModule->Init()); 147 XCTAssertEqual(0, audioDeviceModule->GetPlayoutAudioParameters(&playoutParameters)); 148 XCTAssertEqual(0, audioDeviceModule->GetRecordAudioParameters(&recordParameters)); 149} 150 151- (void)tearDown { 152 XCTAssertEqual(0, audioDeviceModule->Terminate()); 153 audioDeviceModule = nullptr; 154 [super tearDown]; 155} 156 157- (void)startPlayout { 158 XCTAssertFalse(audioDeviceModule->Playing()); 159 XCTAssertEqual(0, audioDeviceModule->InitPlayout()); 160 XCTAssertTrue(audioDeviceModule->PlayoutIsInitialized()); 161 XCTAssertEqual(0, audioDeviceModule->StartPlayout()); 162 XCTAssertTrue(audioDeviceModule->Playing()); 163} 164 165- (void)stopPlayout { 166 XCTAssertEqual(0, audioDeviceModule->StopPlayout()); 167 XCTAssertFalse(audioDeviceModule->Playing()); 168} 169 170- (void)startRecording{ 171 XCTAssertFalse(audioDeviceModule->Recording()); 172 XCTAssertEqual(0, audioDeviceModule->InitRecording()); 173 XCTAssertTrue(audioDeviceModule->RecordingIsInitialized()); 174 XCTAssertEqual(0, audioDeviceModule->StartRecording()); 175 XCTAssertTrue(audioDeviceModule->Recording()); 176} 177 178- (void)stopRecording{ 179 XCTAssertEqual(0, audioDeviceModule->StopRecording()); 180 XCTAssertFalse(audioDeviceModule->Recording()); 181} 182 183- (NSURL*)fileURLForSampleRate:(int)sampleRate { 184 XCTAssertTrue(sampleRate == 48000 || sampleRate == 44100 || sampleRate == 16000); 185 NSString *filename = [NSString stringWithFormat:@"audio_short%d", sampleRate / 1000]; 186 NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:@"pcm"]; 187 XCTAssertNotNil(url); 188 189 return url; 190} 191 192#pragma mark - Tests 193 194- (void)testConstructDestruct { 195 // Using the test fixture to create and destruct the audio device module. 196} 197 198- (void)testInitTerminate { 199 // Initialization is part of the test fixture. 200 XCTAssertTrue(audioDeviceModule->Initialized()); 201 XCTAssertEqual(0, audioDeviceModule->Terminate()); 202 XCTAssertFalse(audioDeviceModule->Initialized()); 203} 204 205// Tests that playout can be initiated, started and stopped. No audio callback 206// is registered in this test. 207- (void)testStartStopPlayout { 208 [self startPlayout]; 209 [self stopPlayout]; 210 [self startPlayout]; 211 [self stopPlayout]; 212} 213 214// Tests that recording can be initiated, started and stopped. No audio callback 215// is registered in this test. 216- (void)testStartStopRecording { 217 [self startRecording]; 218 [self stopRecording]; 219 [self startRecording]; 220 [self stopRecording]; 221} 222// Verify that calling StopPlayout() will leave us in an uninitialized state 223// which will require a new call to InitPlayout(). This test does not call 224// StartPlayout() while being uninitialized since doing so will hit a 225// RTC_DCHECK. 226- (void)testStopPlayoutRequiresInitToRestart { 227 XCTAssertEqual(0, audioDeviceModule->InitPlayout()); 228 XCTAssertEqual(0, audioDeviceModule->StartPlayout()); 229 XCTAssertEqual(0, audioDeviceModule->StopPlayout()); 230 XCTAssertFalse(audioDeviceModule->PlayoutIsInitialized()); 231} 232 233// Verify that we can create two ADMs and start playing on the second ADM. 234// Only the first active instance shall activate an audio session and the 235// last active instance shall deactivate the audio session. The test does not 236// explicitly verify correct audio session calls but instead focuses on 237// ensuring that audio starts for both ADMs. 238- (void)testStartPlayoutOnTwoInstances { 239 // Create and initialize a second/extra ADM instance. The default ADM is 240 // created by the test harness. 241 rtc::scoped_refptr<webrtc::AudioDeviceModule> secondAudioDeviceModule = 242 webrtc::CreateAudioDeviceModule(); 243 XCTAssertNotEqual(secondAudioDeviceModule.get(), nullptr); 244 XCTAssertEqual(0, secondAudioDeviceModule->Init()); 245 246 // Start playout for the default ADM but don't wait here. Instead use the 247 // upcoming second stream for that. We set the same expectation on number 248 // of callbacks as for the second stream. 249 mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, 250 const size_t nBytesPerSample, 251 const size_t nChannels, 252 const uint32_t samplesPerSec, 253 void *audioSamples, 254 size_t &nSamplesOut, 255 int64_t *elapsed_time_ms, 256 int64_t *ntp_time_ms) { 257 nSamplesOut = nSamples; 258 XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer()); 259 XCTAssertEqual(nBytesPerSample, kBytesPerSample); 260 XCTAssertEqual(nChannels, self.playoutParameters.channels()); 261 XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate()); 262 XCTAssertNotEqual((void*)NULL, audioSamples); 263 264 return 0; 265 }); 266 267 XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); 268 [self startPlayout]; 269 270 // Initialize playout for the second ADM. If all is OK, the second ADM shall 271 // reuse the audio session activated when the first ADM started playing. 272 // This call will also ensure that we avoid a problem related to initializing 273 // two different audio unit instances back to back (see webrtc:5166 for 274 // details). 275 XCTAssertEqual(0, secondAudioDeviceModule->InitPlayout()); 276 XCTAssertTrue(secondAudioDeviceModule->PlayoutIsInitialized()); 277 278 // Start playout for the second ADM and verify that it starts as intended. 279 // Passing this test ensures that initialization of the second audio unit 280 // has been done successfully and that there is no conflict with the already 281 // playing first ADM. 282 XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; 283 __block int num_callbacks = 0; 284 285 MockAudioTransport mock2; 286 mock2.expectNeedMorePlayData(^int32_t(const size_t nSamples, 287 const size_t nBytesPerSample, 288 const size_t nChannels, 289 const uint32_t samplesPerSec, 290 void *audioSamples, 291 size_t &nSamplesOut, 292 int64_t *elapsed_time_ms, 293 int64_t *ntp_time_ms) { 294 nSamplesOut = nSamples; 295 XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer()); 296 XCTAssertEqual(nBytesPerSample, kBytesPerSample); 297 XCTAssertEqual(nChannels, self.playoutParameters.channels()); 298 XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate()); 299 XCTAssertNotEqual((void*)NULL, audioSamples); 300 if (++num_callbacks == kNumCallbacks) { 301 [playoutExpectation fulfill]; 302 } 303 304 return 0; 305 }); 306 307 XCTAssertEqual(0, secondAudioDeviceModule->RegisterAudioCallback(&mock2)); 308 XCTAssertEqual(0, secondAudioDeviceModule->StartPlayout()); 309 XCTAssertTrue(secondAudioDeviceModule->Playing()); 310 [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil]; 311 [self stopPlayout]; 312 XCTAssertEqual(0, secondAudioDeviceModule->StopPlayout()); 313 XCTAssertFalse(secondAudioDeviceModule->Playing()); 314 XCTAssertFalse(secondAudioDeviceModule->PlayoutIsInitialized()); 315 316 XCTAssertEqual(0, secondAudioDeviceModule->Terminate()); 317} 318 319// Start playout and verify that the native audio layer starts asking for real 320// audio samples to play out using the NeedMorePlayData callback. 321- (void)testStartPlayoutVerifyCallbacks { 322 323 XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; 324 __block int num_callbacks = 0; 325 mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, 326 const size_t nBytesPerSample, 327 const size_t nChannels, 328 const uint32_t samplesPerSec, 329 void *audioSamples, 330 size_t &nSamplesOut, 331 int64_t *elapsed_time_ms, 332 int64_t *ntp_time_ms) { 333 nSamplesOut = nSamples; 334 XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer()); 335 XCTAssertEqual(nBytesPerSample, kBytesPerSample); 336 XCTAssertEqual(nChannels, self.playoutParameters.channels()); 337 XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate()); 338 XCTAssertNotEqual((void*)NULL, audioSamples); 339 if (++num_callbacks == kNumCallbacks) { 340 [playoutExpectation fulfill]; 341 } 342 return 0; 343 }); 344 345 XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); 346 347 [self startPlayout]; 348 [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil]; 349 [self stopPlayout]; 350} 351 352// Start recording and verify that the native audio layer starts feeding real 353// audio samples via the RecordedDataIsAvailable callback. 354- (void)testStartRecordingVerifyCallbacks { 355 XCTestExpectation *recordExpectation = 356 [self expectationWithDescription:@"RecordedDataIsAvailable"]; 357 __block int num_callbacks = 0; 358 359 mock.expectRecordedDataIsAvailable(^(const void* audioSamples, 360 const size_t nSamples, 361 const size_t nBytesPerSample, 362 const size_t nChannels, 363 const uint32_t samplesPerSec, 364 const uint32_t totalDelayMS, 365 const int32_t clockDrift, 366 const uint32_t currentMicLevel, 367 const bool keyPressed, 368 uint32_t& newMicLevel) { 369 XCTAssertNotEqual((void*)NULL, audioSamples); 370 XCTAssertEqual(nSamples, self.recordParameters.frames_per_10ms_buffer()); 371 XCTAssertEqual(nBytesPerSample, kBytesPerSample); 372 XCTAssertEqual(nChannels, self.recordParameters.channels()); 373 XCTAssertEqual((int)samplesPerSec, self.recordParameters.sample_rate()); 374 XCTAssertEqual(0, clockDrift); 375 XCTAssertEqual(0u, currentMicLevel); 376 XCTAssertFalse(keyPressed); 377 if (++num_callbacks == kNumCallbacks) { 378 [recordExpectation fulfill]; 379 } 380 381 return 0; 382 }); 383 384 XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); 385 [self startRecording]; 386 [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil]; 387 [self stopRecording]; 388} 389 390// Start playout and recording (full-duplex audio) and verify that audio is 391// active in both directions. 392- (void)testStartPlayoutAndRecordingVerifyCallbacks { 393 XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; 394 __block NSUInteger callbackCount = 0; 395 396 XCTestExpectation *recordExpectation = 397 [self expectationWithDescription:@"RecordedDataIsAvailable"]; 398 recordExpectation.expectedFulfillmentCount = kNumCallbacks; 399 400 mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, 401 const size_t nBytesPerSample, 402 const size_t nChannels, 403 const uint32_t samplesPerSec, 404 void *audioSamples, 405 size_t &nSamplesOut, 406 int64_t *elapsed_time_ms, 407 int64_t *ntp_time_ms) { 408 nSamplesOut = nSamples; 409 XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer()); 410 XCTAssertEqual(nBytesPerSample, kBytesPerSample); 411 XCTAssertEqual(nChannels, self.playoutParameters.channels()); 412 XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate()); 413 XCTAssertNotEqual((void*)NULL, audioSamples); 414 if (callbackCount++ >= kNumCallbacks) { 415 [playoutExpectation fulfill]; 416 } 417 418 return 0; 419 }); 420 421 mock.expectRecordedDataIsAvailable(^(const void* audioSamples, 422 const size_t nSamples, 423 const size_t nBytesPerSample, 424 const size_t nChannels, 425 const uint32_t samplesPerSec, 426 const uint32_t totalDelayMS, 427 const int32_t clockDrift, 428 const uint32_t currentMicLevel, 429 const bool keyPressed, 430 uint32_t& newMicLevel) { 431 XCTAssertNotEqual((void*)NULL, audioSamples); 432 XCTAssertEqual(nSamples, self.recordParameters.frames_per_10ms_buffer()); 433 XCTAssertEqual(nBytesPerSample, kBytesPerSample); 434 XCTAssertEqual(nChannels, self.recordParameters.channels()); 435 XCTAssertEqual((int)samplesPerSec, self.recordParameters.sample_rate()); 436 XCTAssertEqual(0, clockDrift); 437 XCTAssertEqual(0u, currentMicLevel); 438 XCTAssertFalse(keyPressed); 439 [recordExpectation fulfill]; 440 441 return 0; 442 }); 443 444 XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); 445 [self startPlayout]; 446 [self startRecording]; 447 [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil]; 448 [self stopRecording]; 449 [self stopPlayout]; 450} 451 452// Start playout and read audio from an external PCM file when the audio layer 453// asks for data to play out. Real audio is played out in this test but it does 454// not contain any explicit verification that the audio quality is perfect. 455- (void)testRunPlayoutWithFileAsSource { 456 XCTAssertEqual(1u, playoutParameters.channels()); 457 458 // Using XCTestExpectation to count callbacks is very slow. 459 XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; 460 const int expectedCallbackCount = kFilePlayTimeInSec * kNumCallbacksPerSecond; 461 __block int callbackCount = 0; 462 463 NSURL *fileURL = [self fileURLForSampleRate:playoutParameters.sample_rate()]; 464 NSInputStream *inputStream = [[NSInputStream alloc] initWithURL:fileURL]; 465 466 mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, 467 const size_t nBytesPerSample, 468 const size_t nChannels, 469 const uint32_t samplesPerSec, 470 void *audioSamples, 471 size_t &nSamplesOut, 472 int64_t *elapsed_time_ms, 473 int64_t *ntp_time_ms) { 474 [inputStream read:(uint8_t *)audioSamples maxLength:nSamples*nBytesPerSample*nChannels]; 475 nSamplesOut = nSamples; 476 if (callbackCount++ == expectedCallbackCount) { 477 [playoutExpectation fulfill]; 478 } 479 480 return 0; 481 }); 482 483 XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); 484 [self startPlayout]; 485 NSTimeInterval waitTimeout = kFilePlayTimeInSec * 2.0; 486 [self waitForExpectationsWithTimeout:waitTimeout handler:nil]; 487 [self stopPlayout]; 488} 489 490- (void)testDevices { 491 // Device enumeration is not supported. Verify fixed values only. 492 XCTAssertEqual(1, audioDeviceModule->PlayoutDevices()); 493 XCTAssertEqual(1, audioDeviceModule->RecordingDevices()); 494} 495 496// Start playout and recording and store recorded data in an intermediate FIFO 497// buffer from which the playout side then reads its samples in the same order 498// as they were stored. Under ideal circumstances, a callback sequence would 499// look like: ...+-+-+-+-+-+-+-..., where '+' means 'packet recorded' and '-' 500// means 'packet played'. Under such conditions, the FIFO would only contain 501// one packet on average. However, under more realistic conditions, the size 502// of the FIFO will vary more due to an unbalance between the two sides. 503// This test tries to verify that the device maintains a balanced callback- 504// sequence by running in loopback for ten seconds while measuring the size 505// (max and average) of the FIFO. The size of the FIFO is increased by the 506// recording side and decreased by the playout side. 507// TODO(henrika): tune the final test parameters after running tests on several 508// different devices. 509- (void)testRunPlayoutAndRecordingInFullDuplex { 510 XCTAssertEqual(recordParameters.channels(), playoutParameters.channels()); 511 XCTAssertEqual(recordParameters.sample_rate(), playoutParameters.sample_rate()); 512 513 XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; 514 __block NSUInteger playoutCallbacks = 0; 515 NSUInteger expectedPlayoutCallbacks = kFullDuplexTimeInSec * kNumCallbacksPerSecond; 516 517 // FIFO queue and measurements 518 NSMutableArray *fifoBuffer = [NSMutableArray arrayWithCapacity:20]; 519 __block NSUInteger fifoMaxSize = 0; 520 __block NSUInteger fifoTotalWrittenElements = 0; 521 __block NSUInteger fifoWriteCount = 0; 522 523 mock.expectRecordedDataIsAvailable(^(const void* audioSamples, 524 const size_t nSamples, 525 const size_t nBytesPerSample, 526 const size_t nChannels, 527 const uint32_t samplesPerSec, 528 const uint32_t totalDelayMS, 529 const int32_t clockDrift, 530 const uint32_t currentMicLevel, 531 const bool keyPressed, 532 uint32_t& newMicLevel) { 533 if (fifoWriteCount++ < kNumIgnoreFirstCallbacks) { 534 return 0; 535 } 536 537 NSData *data = [NSData dataWithBytes:audioSamples length:nSamples*nBytesPerSample*nChannels]; 538 @synchronized(fifoBuffer) { 539 [fifoBuffer addObject:data]; 540 fifoMaxSize = MAX(fifoMaxSize, fifoBuffer.count); 541 fifoTotalWrittenElements += fifoBuffer.count; 542 } 543 544 return 0; 545 }); 546 547 mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, 548 const size_t nBytesPerSample, 549 const size_t nChannels, 550 const uint32_t samplesPerSec, 551 void *audioSamples, 552 size_t &nSamplesOut, 553 int64_t *elapsed_time_ms, 554 int64_t *ntp_time_ms) { 555 nSamplesOut = nSamples; 556 NSData *data; 557 @synchronized(fifoBuffer) { 558 data = fifoBuffer.firstObject; 559 if (data) { 560 [fifoBuffer removeObjectAtIndex:0]; 561 } 562 } 563 564 if (data) { 565 memcpy(audioSamples, (char*) data.bytes, data.length); 566 } else { 567 memset(audioSamples, 0, nSamples*nBytesPerSample*nChannels); 568 } 569 570 if (playoutCallbacks++ == expectedPlayoutCallbacks) { 571 [playoutExpectation fulfill]; 572 } 573 return 0; 574 }); 575 576 XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); 577 [self startRecording]; 578 [self startPlayout]; 579 NSTimeInterval waitTimeout = kFullDuplexTimeInSec * 2.0; 580 [self waitForExpectationsWithTimeout:waitTimeout handler:nil]; 581 582 size_t fifoAverageSize = 583 (fifoTotalWrittenElements == 0) 584 ? 0.0 585 : 0.5 + (double)fifoTotalWrittenElements / (fifoWriteCount - kNumIgnoreFirstCallbacks); 586 587 [self stopPlayout]; 588 [self stopRecording]; 589 XCTAssertLessThan(fifoAverageSize, 10u); 590 XCTAssertLessThan(fifoMaxSize, 20u); 591} 592 593@end 594