blob: 55d48c28ba71e93f03f9deb7873f54113c0c1ea4 [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)
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020014#import "sdk/objc/native/api/audio_device_module.h"
Peter Hanspers8d95e3b2018-05-15 10:22:36 +020015#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() {}
Mirko Bonadei17aff352018-07-26 12:20:40 +020046 ~MockAudioTransport() override {}
Peter Hanspers8d95e3b2018-05-15 10:22:36 +020047
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,
Mirko Bonadei17aff352018-07-26 12:20:40 +020063 int64_t* ntp_time_ms) override {
Peter Hanspers8d95e3b2018-05-15 10:22:36 +020064 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,
Mirko Bonadei17aff352018-07-26 12:20:40 +020083 uint32_t& newMicLevel) override {
Peter Hanspers8d95e3b2018-05-15 10:22:36 +020084 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,
Mirko Bonadei17aff352018-07-26 12:20:40 +0200102 int64_t* ntp_time_ms) override {}
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200103
Mirko Bonadei17aff352018-07-26 12:20:40 +0200104 private:
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200105 NeedMorePlayDataBlock needMorePlayDataBlock;
106 RecordedDataIsAvailableBlock recordedDataIsAvailableBlock;
107};
108
109// Number of callbacks (input or output) the tests waits for before we set
110// an event indicating that the test was OK.
111static const NSUInteger kNumCallbacks = 10;
112// Max amount of time we wait for an event to be set while counting callbacks.
113static const NSTimeInterval kTestTimeOutInSec = 20.0;
114// Number of bits per PCM audio sample.
115static const NSUInteger kBitsPerSample = 16;
116// Number of bytes per PCM audio sample.
117static const NSUInteger kBytesPerSample = kBitsPerSample / 8;
118// Average number of audio callbacks per second assuming 10ms packet size.
119static const NSUInteger kNumCallbacksPerSecond = 100;
120// Play out a test file during this time (unit is in seconds).
121static const NSUInteger kFilePlayTimeInSec = 15;
122// Run the full-duplex test during this time (unit is in seconds).
123// Note that first |kNumIgnoreFirstCallbacks| are ignored.
124static const NSUInteger kFullDuplexTimeInSec = 10;
125// Wait for the callback sequence to stabilize by ignoring this amount of the
126// initial callbacks (avoids initial FIFO access).
127// Only used in the RunPlayoutAndRecordingInFullDuplex test.
128static const NSUInteger kNumIgnoreFirstCallbacks = 50;
129
130@interface RTCAudioDeviceModuleTests : XCTestCase {
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200131 rtc::scoped_refptr<webrtc::AudioDeviceModule> audioDeviceModule;
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200132 MockAudioTransport mock;
133}
134
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800135@property(nonatomic, assign) webrtc::AudioParameters playoutParameters;
136@property(nonatomic, assign) webrtc::AudioParameters recordParameters;
137
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200138@end
139
140@implementation RTCAudioDeviceModuleTests
141
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800142@synthesize playoutParameters;
143@synthesize recordParameters;
144
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200145- (void)setUp {
146 [super setUp];
147 audioDeviceModule = webrtc::CreateAudioDeviceModule();
148 XCTAssertEqual(0, audioDeviceModule->Init());
149 XCTAssertEqual(0, audioDeviceModule->GetPlayoutAudioParameters(&playoutParameters));
150 XCTAssertEqual(0, audioDeviceModule->GetRecordAudioParameters(&recordParameters));
151}
152
153- (void)tearDown {
154 XCTAssertEqual(0, audioDeviceModule->Terminate());
155 audioDeviceModule = nullptr;
156 [super tearDown];
157}
158
159- (void)startPlayout {
160 XCTAssertFalse(audioDeviceModule->Playing());
161 XCTAssertEqual(0, audioDeviceModule->InitPlayout());
162 XCTAssertTrue(audioDeviceModule->PlayoutIsInitialized());
163 XCTAssertEqual(0, audioDeviceModule->StartPlayout());
164 XCTAssertTrue(audioDeviceModule->Playing());
165}
166
167- (void)stopPlayout {
168 XCTAssertEqual(0, audioDeviceModule->StopPlayout());
169 XCTAssertFalse(audioDeviceModule->Playing());
170}
171
172- (void)startRecording{
173 XCTAssertFalse(audioDeviceModule->Recording());
174 XCTAssertEqual(0, audioDeviceModule->InitRecording());
175 XCTAssertTrue(audioDeviceModule->RecordingIsInitialized());
176 XCTAssertEqual(0, audioDeviceModule->StartRecording());
177 XCTAssertTrue(audioDeviceModule->Recording());
178}
179
180- (void)stopRecording{
181 XCTAssertEqual(0, audioDeviceModule->StopRecording());
182 XCTAssertFalse(audioDeviceModule->Recording());
183}
184
185- (NSURL*)fileURLForSampleRate:(int)sampleRate {
186 XCTAssertTrue(sampleRate == 48000 || sampleRate == 44100 || sampleRate == 16000);
187 NSString *filename = [NSString stringWithFormat:@"audio_short%d", sampleRate / 1000];
188 NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:@"pcm"];
189 XCTAssertNotNil(url);
190
191 return url;
192}
193
194#pragma mark - Tests
195
196- (void)testConstructDestruct {
197 // Using the test fixture to create and destruct the audio device module.
198}
199
200- (void)testInitTerminate {
201 // Initialization is part of the test fixture.
202 XCTAssertTrue(audioDeviceModule->Initialized());
203 XCTAssertEqual(0, audioDeviceModule->Terminate());
204 XCTAssertFalse(audioDeviceModule->Initialized());
205}
206
207// Tests that playout can be initiated, started and stopped. No audio callback
208// is registered in this test.
Kári Tristan Helgasondb543c92018-09-06 13:49:37 +0200209- (void)testStartStopPlayout {
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200210 [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.
Kári Tristan Helgasondb543c92018-09-06 13:49:37 +0200218- (void)testStartStopRecording {
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200219 [self startRecording];
220 [self stopRecording];
221 [self startRecording];
222 [self stopRecording];
223}
224// Verify that calling StopPlayout() will leave us in an uninitialized state
225// which will require a new call to InitPlayout(). This test does not call
226// StartPlayout() while being uninitialized since doing so will hit a
227// RTC_DCHECK.
228- (void)testStopPlayoutRequiresInitToRestart {
229 XCTAssertEqual(0, audioDeviceModule->InitPlayout());
230 XCTAssertEqual(0, audioDeviceModule->StartPlayout());
231 XCTAssertEqual(0, audioDeviceModule->StopPlayout());
232 XCTAssertFalse(audioDeviceModule->PlayoutIsInitialized());
233}
234
235// Verify that we can create two ADMs and start playing on the second ADM.
236// Only the first active instance shall activate an audio session and the
237// last active instance shall deactivate the audio session. The test does not
238// explicitly verify correct audio session calls but instead focuses on
239// ensuring that audio starts for both ADMs.
Kári Tristan Helgasondb543c92018-09-06 13:49:37 +0200240- (void)testStartPlayoutOnTwoInstances {
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200241 // Create and initialize a second/extra ADM instance. The default ADM is
242 // created by the test harness.
243 rtc::scoped_refptr<webrtc::AudioDeviceModule> secondAudioDeviceModule =
244 webrtc::CreateAudioDeviceModule();
245 XCTAssertNotEqual(secondAudioDeviceModule.get(), nullptr);
246 XCTAssertEqual(0, secondAudioDeviceModule->Init());
247
248 // Start playout for the default ADM but don't wait here. Instead use the
249 // upcoming second stream for that. We set the same expectation on number
250 // of callbacks as for the second stream.
251 mock.expectNeedMorePlayData(^int32_t(const size_t nSamples,
252 const size_t nBytesPerSample,
253 const size_t nChannels,
254 const uint32_t samplesPerSec,
255 void *audioSamples,
256 size_t &nSamplesOut,
257 int64_t *elapsed_time_ms,
258 int64_t *ntp_time_ms) {
259 nSamplesOut = nSamples;
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800260 XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer());
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200261 XCTAssertEqual(nBytesPerSample, kBytesPerSample);
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800262 XCTAssertEqual(nChannels, self.playoutParameters.channels());
263 XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate());
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200264 XCTAssertNotEqual((void*)NULL, audioSamples);
265
266 return 0;
267 });
268
269 XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock));
270 [self startPlayout];
271
272 // Initialize playout for the second ADM. If all is OK, the second ADM shall
273 // reuse the audio session activated when the first ADM started playing.
274 // This call will also ensure that we avoid a problem related to initializing
275 // two different audio unit instances back to back (see webrtc:5166 for
276 // details).
277 XCTAssertEqual(0, secondAudioDeviceModule->InitPlayout());
278 XCTAssertTrue(secondAudioDeviceModule->PlayoutIsInitialized());
279
280 // Start playout for the second ADM and verify that it starts as intended.
281 // Passing this test ensures that initialization of the second audio unit
282 // has been done successfully and that there is no conflict with the already
283 // playing first ADM.
284 XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"];
Kári Tristan Helgasondb543c92018-09-06 13:49:37 +0200285 __block int num_callbacks = 0;
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200286
287 MockAudioTransport mock2;
288 mock2.expectNeedMorePlayData(^int32_t(const size_t nSamples,
289 const size_t nBytesPerSample,
290 const size_t nChannels,
291 const uint32_t samplesPerSec,
292 void *audioSamples,
293 size_t &nSamplesOut,
294 int64_t *elapsed_time_ms,
295 int64_t *ntp_time_ms) {
296 nSamplesOut = nSamples;
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800297 XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer());
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200298 XCTAssertEqual(nBytesPerSample, kBytesPerSample);
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800299 XCTAssertEqual(nChannels, self.playoutParameters.channels());
300 XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate());
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200301 XCTAssertNotEqual((void*)NULL, audioSamples);
Kári Tristan Helgasondb543c92018-09-06 13:49:37 +0200302 if (++num_callbacks == kNumCallbacks) {
303 [playoutExpectation fulfill];
304 }
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200305
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];
Kári Tristan Helgasondb543c92018-09-06 13:49:37 +0200313 [self stopPlayout];
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200314 XCTAssertEqual(0, secondAudioDeviceModule->StopPlayout());
315 XCTAssertFalse(secondAudioDeviceModule->Playing());
316 XCTAssertFalse(secondAudioDeviceModule->PlayoutIsInitialized());
317
318 XCTAssertEqual(0, secondAudioDeviceModule->Terminate());
319}
320
321// Start playout and verify that the native audio layer starts asking for real
322// audio samples to play out using the NeedMorePlayData callback.
323- (void)testStartPlayoutVerifyCallbacks {
324
325 XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"];
Kári Tristan Helgasondb543c92018-09-06 13:49:37 +0200326 __block int num_callbacks = 0;
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200327 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;
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800336 XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer());
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200337 XCTAssertEqual(nBytesPerSample, kBytesPerSample);
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800338 XCTAssertEqual(nChannels, self.playoutParameters.channels());
339 XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate());
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200340 XCTAssertNotEqual((void*)NULL, audioSamples);
Kári Tristan Helgasondb543c92018-09-06 13:49:37 +0200341 if (++num_callbacks == kNumCallbacks) {
342 [playoutExpectation fulfill];
343 }
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200344 return 0;
345 });
346
347 XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock));
348
349 [self startPlayout];
350 [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil];
351 [self stopPlayout];
352}
353
354// Start recording and verify that the native audio layer starts feeding real
355// audio samples via the RecordedDataIsAvailable callback.
356- (void)testStartRecordingVerifyCallbacks {
357 XCTestExpectation *recordExpectation =
358 [self expectationWithDescription:@"RecordedDataIsAvailable"];
Kári Tristan Helgasondb543c92018-09-06 13:49:37 +0200359 __block int num_callbacks = 0;
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200360
361 mock.expectRecordedDataIsAvailable(^(const void* audioSamples,
362 const size_t nSamples,
363 const size_t nBytesPerSample,
364 const size_t nChannels,
365 const uint32_t samplesPerSec,
366 const uint32_t totalDelayMS,
367 const int32_t clockDrift,
368 const uint32_t currentMicLevel,
369 const bool keyPressed,
370 uint32_t& newMicLevel) {
371 XCTAssertNotEqual((void*)NULL, audioSamples);
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800372 XCTAssertEqual(nSamples, self.recordParameters.frames_per_10ms_buffer());
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200373 XCTAssertEqual(nBytesPerSample, kBytesPerSample);
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800374 XCTAssertEqual(nChannels, self.recordParameters.channels());
375 XCTAssertEqual((int)samplesPerSec, self.recordParameters.sample_rate());
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200376 XCTAssertEqual(0, clockDrift);
377 XCTAssertEqual(0u, currentMicLevel);
378 XCTAssertFalse(keyPressed);
Kári Tristan Helgasondb543c92018-09-06 13:49:37 +0200379 if (++num_callbacks == kNumCallbacks) {
380 [recordExpectation fulfill];
381 }
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200382
383 return 0;
384 });
385
386 XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock));
387 [self startRecording];
388 [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil];
389 [self stopRecording];
390}
391
392// Start playout and recording (full-duplex audio) and verify that audio is
393// active in both directions.
394- (void)testStartPlayoutAndRecordingVerifyCallbacks {
395 XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"];
396 __block NSUInteger callbackCount = 0;
397
398 XCTestExpectation *recordExpectation =
399 [self expectationWithDescription:@"RecordedDataIsAvailable"];
400 recordExpectation.expectedFulfillmentCount = kNumCallbacks;
401
402 mock.expectNeedMorePlayData(^int32_t(const size_t nSamples,
403 const size_t nBytesPerSample,
404 const size_t nChannels,
405 const uint32_t samplesPerSec,
406 void *audioSamples,
407 size_t &nSamplesOut,
408 int64_t *elapsed_time_ms,
409 int64_t *ntp_time_ms) {
410 nSamplesOut = nSamples;
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800411 XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer());
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200412 XCTAssertEqual(nBytesPerSample, kBytesPerSample);
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800413 XCTAssertEqual(nChannels, self.playoutParameters.channels());
414 XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate());
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200415 XCTAssertNotEqual((void*)NULL, audioSamples);
416 if (callbackCount++ >= kNumCallbacks) {
417 [playoutExpectation fulfill];
418 }
419
420 return 0;
421 });
422
423 mock.expectRecordedDataIsAvailable(^(const void* audioSamples,
424 const size_t nSamples,
425 const size_t nBytesPerSample,
426 const size_t nChannels,
427 const uint32_t samplesPerSec,
428 const uint32_t totalDelayMS,
429 const int32_t clockDrift,
430 const uint32_t currentMicLevel,
431 const bool keyPressed,
432 uint32_t& newMicLevel) {
433 XCTAssertNotEqual((void*)NULL, audioSamples);
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800434 XCTAssertEqual(nSamples, self.recordParameters.frames_per_10ms_buffer());
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200435 XCTAssertEqual(nBytesPerSample, kBytesPerSample);
Jiawei Ou4aeb35b2018-11-09 13:55:45 -0800436 XCTAssertEqual(nChannels, self.recordParameters.channels());
437 XCTAssertEqual((int)samplesPerSec, self.recordParameters.sample_rate());
Peter Hanspers8d95e3b2018-05-15 10:22:36 +0200438 XCTAssertEqual(0, clockDrift);
439 XCTAssertEqual(0u, currentMicLevel);
440 XCTAssertFalse(keyPressed);
441 [recordExpectation fulfill];
442
443 return 0;
444 });
445
446 XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock));
447 [self startPlayout];
448 [self startRecording];
449 [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil];
450 [self stopRecording];
451 [self stopPlayout];
452}
453
454// Start playout and read audio from an external PCM file when the audio layer
455// asks for data to play out. Real audio is played out in this test but it does
456// not contain any explicit verification that the audio quality is perfect.
457- (void)testRunPlayoutWithFileAsSource {
458 XCTAssertEqual(1u, playoutParameters.channels());
459
460 // Using XCTestExpectation to count callbacks is very slow.
461 XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"];
462 const int expectedCallbackCount = kFilePlayTimeInSec * kNumCallbacksPerSecond;
463 __block int callbackCount = 0;
464
465 NSURL *fileURL = [self fileURLForSampleRate:playoutParameters.sample_rate()];
466 NSInputStream *inputStream = [[NSInputStream alloc] initWithURL:fileURL];
467
468 mock.expectNeedMorePlayData(^int32_t(const size_t nSamples,
469 const size_t nBytesPerSample,
470 const size_t nChannels,
471 const uint32_t samplesPerSec,
472 void *audioSamples,
473 size_t &nSamplesOut,
474 int64_t *elapsed_time_ms,
475 int64_t *ntp_time_ms) {
476 [inputStream read:(uint8_t *)audioSamples maxLength:nSamples*nBytesPerSample*nChannels];
477 nSamplesOut = nSamples;
478 if (callbackCount++ == expectedCallbackCount) {
479 [playoutExpectation fulfill];
480 }
481
482 return 0;
483 });
484
485 XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock));
486 [self startPlayout];
487 NSTimeInterval waitTimeout = kFilePlayTimeInSec * 2.0;
488 [self waitForExpectationsWithTimeout:waitTimeout handler:nil];
489 [self stopPlayout];
490}
491
492- (void)testDevices {
493 // Device enumeration is not supported. Verify fixed values only.
494 XCTAssertEqual(1, audioDeviceModule->PlayoutDevices());
495 XCTAssertEqual(1, audioDeviceModule->RecordingDevices());
496}
497
498// Start playout and recording and store recorded data in an intermediate FIFO
499// buffer from which the playout side then reads its samples in the same order
500// as they were stored. Under ideal circumstances, a callback sequence would
501// look like: ...+-+-+-+-+-+-+-..., where '+' means 'packet recorded' and '-'
502// means 'packet played'. Under such conditions, the FIFO would only contain
503// one packet on average. However, under more realistic conditions, the size
504// of the FIFO will vary more due to an unbalance between the two sides.
505// This test tries to verify that the device maintains a balanced callback-
506// sequence by running in loopback for ten seconds while measuring the size
507// (max and average) of the FIFO. The size of the FIFO is increased by the
508// recording side and decreased by the playout side.
509// TODO(henrika): tune the final test parameters after running tests on several
510// different devices.
511- (void)testRunPlayoutAndRecordingInFullDuplex {
512 XCTAssertEqual(recordParameters.channels(), playoutParameters.channels());
513 XCTAssertEqual(recordParameters.sample_rate(), playoutParameters.sample_rate());
514
515 XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"];
516 __block NSUInteger playoutCallbacks = 0;
517 NSUInteger expectedPlayoutCallbacks = kFullDuplexTimeInSec * kNumCallbacksPerSecond;
518
519 // FIFO queue and measurements
520 NSMutableArray *fifoBuffer = [NSMutableArray arrayWithCapacity:20];
521 __block NSUInteger fifoMaxSize = 0;
522 __block NSUInteger fifoTotalWrittenElements = 0;
523 __block NSUInteger fifoWriteCount = 0;
524
525 mock.expectRecordedDataIsAvailable(^(const void* audioSamples,
526 const size_t nSamples,
527 const size_t nBytesPerSample,
528 const size_t nChannels,
529 const uint32_t samplesPerSec,
530 const uint32_t totalDelayMS,
531 const int32_t clockDrift,
532 const uint32_t currentMicLevel,
533 const bool keyPressed,
534 uint32_t& newMicLevel) {
535 if (fifoWriteCount++ < kNumIgnoreFirstCallbacks) {
536 return 0;
537 }
538
539 NSData *data = [NSData dataWithBytes:audioSamples length:nSamples*nBytesPerSample*nChannels];
540 @synchronized(fifoBuffer) {
541 [fifoBuffer addObject:data];
542 fifoMaxSize = MAX(fifoMaxSize, fifoBuffer.count);
543 fifoTotalWrittenElements += fifoBuffer.count;
544 }
545
546 return 0;
547 });
548
549 mock.expectNeedMorePlayData(^int32_t(const size_t nSamples,
550 const size_t nBytesPerSample,
551 const size_t nChannels,
552 const uint32_t samplesPerSec,
553 void *audioSamples,
554 size_t &nSamplesOut,
555 int64_t *elapsed_time_ms,
556 int64_t *ntp_time_ms) {
557 nSamplesOut = nSamples;
558 NSData *data;
559 @synchronized(fifoBuffer) {
560 data = fifoBuffer.firstObject;
561 if (data) {
562 [fifoBuffer removeObjectAtIndex:0];
563 }
564 }
565
566 if (data) {
567 memcpy(audioSamples, (char*) data.bytes, data.length);
568 } else {
569 memset(audioSamples, 0, nSamples*nBytesPerSample*nChannels);
570 }
571
572 if (playoutCallbacks++ == expectedPlayoutCallbacks) {
573 [playoutExpectation fulfill];
574 }
575 return 0;
576 });
577
578 XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock));
579 [self startRecording];
580 [self startPlayout];
581 NSTimeInterval waitTimeout = kFullDuplexTimeInSec * 2.0;
582 [self waitForExpectationsWithTimeout:waitTimeout handler:nil];
583
584 size_t fifoAverageSize =
585 (fifoTotalWrittenElements == 0)
586 ? 0.0
587 : 0.5 + (double)fifoTotalWrittenElements / (fifoWriteCount - kNumIgnoreFirstCallbacks);
588
589 [self stopPlayout];
590 [self stopRecording];
591 XCTAssertLessThan(fifoAverageSize, 10u);
592 XCTAssertLessThan(fifoMaxSize, 20u);
593}
594
595@end