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