| /* |
| * Copyright 2018 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #import <XCTest/XCTest.h> |
| |
| #if defined(WEBRTC_IOS) |
| #import "sdk/objc/native/api/audio_device_module.h" |
| #endif |
| |
| #include "api/scoped_refptr.h" |
| |
| typedef int32_t(^NeedMorePlayDataBlock)(const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| void* audioSamples, |
| size_t& nSamplesOut, |
| int64_t* elapsed_time_ms, |
| int64_t* ntp_time_ms); |
| |
| typedef int32_t(^RecordedDataIsAvailableBlock)(const void* audioSamples, |
| const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| const uint32_t totalDelayMS, |
| const int32_t clockDrift, |
| const uint32_t currentMicLevel, |
| const bool keyPressed, |
| uint32_t& newMicLevel); |
| |
| |
| // This class implements the AudioTransport API and forwards all methods to the appropriate blocks. |
| class MockAudioTransport : public webrtc::AudioTransport { |
| public: |
| MockAudioTransport() {} |
| ~MockAudioTransport() override {} |
| |
| void expectNeedMorePlayData(NeedMorePlayDataBlock block) { |
| needMorePlayDataBlock = block; |
| } |
| |
| void expectRecordedDataIsAvailable(RecordedDataIsAvailableBlock block) { |
| recordedDataIsAvailableBlock = block; |
| } |
| |
| int32_t NeedMorePlayData(const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| void* audioSamples, |
| size_t& nSamplesOut, |
| int64_t* elapsed_time_ms, |
| int64_t* ntp_time_ms) override { |
| return needMorePlayDataBlock(nSamples, |
| nBytesPerSample, |
| nChannels, |
| samplesPerSec, |
| audioSamples, |
| nSamplesOut, |
| elapsed_time_ms, |
| ntp_time_ms); |
| } |
| |
| int32_t RecordedDataIsAvailable(const void* audioSamples, |
| const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| const uint32_t totalDelayMS, |
| const int32_t clockDrift, |
| const uint32_t currentMicLevel, |
| const bool keyPressed, |
| uint32_t& newMicLevel) override { |
| return recordedDataIsAvailableBlock(audioSamples, |
| nSamples, |
| nBytesPerSample, |
| nChannels, |
| samplesPerSec, |
| totalDelayMS, |
| clockDrift, |
| currentMicLevel, |
| keyPressed, |
| newMicLevel); |
| } |
| |
| void PullRenderData(int bits_per_sample, |
| int sample_rate, |
| size_t number_of_channels, |
| size_t number_of_frames, |
| void* audio_data, |
| int64_t* elapsed_time_ms, |
| int64_t* ntp_time_ms) override {} |
| |
| private: |
| NeedMorePlayDataBlock needMorePlayDataBlock; |
| RecordedDataIsAvailableBlock recordedDataIsAvailableBlock; |
| }; |
| |
| // Number of callbacks (input or output) the tests waits for before we set |
| // an event indicating that the test was OK. |
| static const NSUInteger kNumCallbacks = 10; |
| // Max amount of time we wait for an event to be set while counting callbacks. |
| static const NSTimeInterval kTestTimeOutInSec = 20.0; |
| // Number of bits per PCM audio sample. |
| static const NSUInteger kBitsPerSample = 16; |
| // Number of bytes per PCM audio sample. |
| static const NSUInteger kBytesPerSample = kBitsPerSample / 8; |
| // Average number of audio callbacks per second assuming 10ms packet size. |
| static const NSUInteger kNumCallbacksPerSecond = 100; |
| // Play out a test file during this time (unit is in seconds). |
| static const NSUInteger kFilePlayTimeInSec = 15; |
| // Run the full-duplex test during this time (unit is in seconds). |
| // Note that first `kNumIgnoreFirstCallbacks` are ignored. |
| static const NSUInteger kFullDuplexTimeInSec = 10; |
| // Wait for the callback sequence to stabilize by ignoring this amount of the |
| // initial callbacks (avoids initial FIFO access). |
| // Only used in the RunPlayoutAndRecordingInFullDuplex test. |
| static const NSUInteger kNumIgnoreFirstCallbacks = 50; |
| |
| @interface RTCAudioDeviceModuleTests : XCTestCase { |
| rtc::scoped_refptr<webrtc::AudioDeviceModule> audioDeviceModule; |
| MockAudioTransport mock; |
| } |
| |
| @property(nonatomic, assign) webrtc::AudioParameters playoutParameters; |
| @property(nonatomic, assign) webrtc::AudioParameters recordParameters; |
| |
| @end |
| |
| @implementation RTCAudioDeviceModuleTests |
| |
| @synthesize playoutParameters; |
| @synthesize recordParameters; |
| |
| - (void)setUp { |
| [super setUp]; |
| audioDeviceModule = webrtc::CreateAudioDeviceModule(); |
| XCTAssertEqual(0, audioDeviceModule->Init()); |
| XCTAssertEqual(0, audioDeviceModule->GetPlayoutAudioParameters(&playoutParameters)); |
| XCTAssertEqual(0, audioDeviceModule->GetRecordAudioParameters(&recordParameters)); |
| } |
| |
| - (void)tearDown { |
| XCTAssertEqual(0, audioDeviceModule->Terminate()); |
| audioDeviceModule = nullptr; |
| [super tearDown]; |
| } |
| |
| - (void)startPlayout { |
| XCTAssertFalse(audioDeviceModule->Playing()); |
| XCTAssertEqual(0, audioDeviceModule->InitPlayout()); |
| XCTAssertTrue(audioDeviceModule->PlayoutIsInitialized()); |
| XCTAssertEqual(0, audioDeviceModule->StartPlayout()); |
| XCTAssertTrue(audioDeviceModule->Playing()); |
| } |
| |
| - (void)stopPlayout { |
| XCTAssertEqual(0, audioDeviceModule->StopPlayout()); |
| XCTAssertFalse(audioDeviceModule->Playing()); |
| } |
| |
| - (void)startRecording{ |
| XCTAssertFalse(audioDeviceModule->Recording()); |
| XCTAssertEqual(0, audioDeviceModule->InitRecording()); |
| XCTAssertTrue(audioDeviceModule->RecordingIsInitialized()); |
| XCTAssertEqual(0, audioDeviceModule->StartRecording()); |
| XCTAssertTrue(audioDeviceModule->Recording()); |
| } |
| |
| - (void)stopRecording{ |
| XCTAssertEqual(0, audioDeviceModule->StopRecording()); |
| XCTAssertFalse(audioDeviceModule->Recording()); |
| } |
| |
| - (NSURL*)fileURLForSampleRate:(int)sampleRate { |
| XCTAssertTrue(sampleRate == 48000 || sampleRate == 44100 || sampleRate == 16000); |
| NSString *filename = [NSString stringWithFormat:@"audio_short%d", sampleRate / 1000]; |
| NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:@"pcm"]; |
| XCTAssertNotNil(url); |
| |
| return url; |
| } |
| |
| #pragma mark - Tests |
| |
| - (void)testConstructDestruct { |
| // Using the test fixture to create and destruct the audio device module. |
| } |
| |
| - (void)testInitTerminate { |
| // Initialization is part of the test fixture. |
| XCTAssertTrue(audioDeviceModule->Initialized()); |
| XCTAssertEqual(0, audioDeviceModule->Terminate()); |
| XCTAssertFalse(audioDeviceModule->Initialized()); |
| } |
| |
| // Tests that playout can be initiated, started and stopped. No audio callback |
| // is registered in this test. |
| - (void)testStartStopPlayout { |
| [self startPlayout]; |
| [self stopPlayout]; |
| [self startPlayout]; |
| [self stopPlayout]; |
| } |
| |
| // Tests that recording can be initiated, started and stopped. No audio callback |
| // is registered in this test. |
| - (void)testStartStopRecording { |
| [self startRecording]; |
| [self stopRecording]; |
| [self startRecording]; |
| [self stopRecording]; |
| } |
| // Verify that calling StopPlayout() will leave us in an uninitialized state |
| // which will require a new call to InitPlayout(). This test does not call |
| // StartPlayout() while being uninitialized since doing so will hit a |
| // RTC_DCHECK. |
| - (void)testStopPlayoutRequiresInitToRestart { |
| XCTAssertEqual(0, audioDeviceModule->InitPlayout()); |
| XCTAssertEqual(0, audioDeviceModule->StartPlayout()); |
| XCTAssertEqual(0, audioDeviceModule->StopPlayout()); |
| XCTAssertFalse(audioDeviceModule->PlayoutIsInitialized()); |
| } |
| |
| // Verify that we can create two ADMs and start playing on the second ADM. |
| // Only the first active instance shall activate an audio session and the |
| // last active instance shall deactivate the audio session. The test does not |
| // explicitly verify correct audio session calls but instead focuses on |
| // ensuring that audio starts for both ADMs. |
| - (void)testStartPlayoutOnTwoInstances { |
| // Create and initialize a second/extra ADM instance. The default ADM is |
| // created by the test harness. |
| rtc::scoped_refptr<webrtc::AudioDeviceModule> secondAudioDeviceModule = |
| webrtc::CreateAudioDeviceModule(); |
| XCTAssertNotEqual(secondAudioDeviceModule.get(), nullptr); |
| XCTAssertEqual(0, secondAudioDeviceModule->Init()); |
| |
| // Start playout for the default ADM but don't wait here. Instead use the |
| // upcoming second stream for that. We set the same expectation on number |
| // of callbacks as for the second stream. |
| mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| void *audioSamples, |
| size_t &nSamplesOut, |
| int64_t *elapsed_time_ms, |
| int64_t *ntp_time_ms) { |
| nSamplesOut = nSamples; |
| XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer()); |
| XCTAssertEqual(nBytesPerSample, kBytesPerSample); |
| XCTAssertEqual(nChannels, self.playoutParameters.channels()); |
| XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate()); |
| XCTAssertNotEqual((void*)NULL, audioSamples); |
| |
| return 0; |
| }); |
| |
| XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); |
| [self startPlayout]; |
| |
| // Initialize playout for the second ADM. If all is OK, the second ADM shall |
| // reuse the audio session activated when the first ADM started playing. |
| // This call will also ensure that we avoid a problem related to initializing |
| // two different audio unit instances back to back (see webrtc:5166 for |
| // details). |
| XCTAssertEqual(0, secondAudioDeviceModule->InitPlayout()); |
| XCTAssertTrue(secondAudioDeviceModule->PlayoutIsInitialized()); |
| |
| // Start playout for the second ADM and verify that it starts as intended. |
| // Passing this test ensures that initialization of the second audio unit |
| // has been done successfully and that there is no conflict with the already |
| // playing first ADM. |
| XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; |
| __block int num_callbacks = 0; |
| |
| MockAudioTransport mock2; |
| mock2.expectNeedMorePlayData(^int32_t(const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| void *audioSamples, |
| size_t &nSamplesOut, |
| int64_t *elapsed_time_ms, |
| int64_t *ntp_time_ms) { |
| nSamplesOut = nSamples; |
| XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer()); |
| XCTAssertEqual(nBytesPerSample, kBytesPerSample); |
| XCTAssertEqual(nChannels, self.playoutParameters.channels()); |
| XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate()); |
| XCTAssertNotEqual((void*)NULL, audioSamples); |
| if (++num_callbacks == kNumCallbacks) { |
| [playoutExpectation fulfill]; |
| } |
| |
| return 0; |
| }); |
| |
| XCTAssertEqual(0, secondAudioDeviceModule->RegisterAudioCallback(&mock2)); |
| XCTAssertEqual(0, secondAudioDeviceModule->StartPlayout()); |
| XCTAssertTrue(secondAudioDeviceModule->Playing()); |
| [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil]; |
| [self stopPlayout]; |
| XCTAssertEqual(0, secondAudioDeviceModule->StopPlayout()); |
| XCTAssertFalse(secondAudioDeviceModule->Playing()); |
| XCTAssertFalse(secondAudioDeviceModule->PlayoutIsInitialized()); |
| |
| XCTAssertEqual(0, secondAudioDeviceModule->Terminate()); |
| } |
| |
| // Start playout and verify that the native audio layer starts asking for real |
| // audio samples to play out using the NeedMorePlayData callback. |
| - (void)testStartPlayoutVerifyCallbacks { |
| |
| XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; |
| __block int num_callbacks = 0; |
| mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| void *audioSamples, |
| size_t &nSamplesOut, |
| int64_t *elapsed_time_ms, |
| int64_t *ntp_time_ms) { |
| nSamplesOut = nSamples; |
| XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer()); |
| XCTAssertEqual(nBytesPerSample, kBytesPerSample); |
| XCTAssertEqual(nChannels, self.playoutParameters.channels()); |
| XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate()); |
| XCTAssertNotEqual((void*)NULL, audioSamples); |
| if (++num_callbacks == kNumCallbacks) { |
| [playoutExpectation fulfill]; |
| } |
| return 0; |
| }); |
| |
| XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); |
| |
| [self startPlayout]; |
| [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil]; |
| [self stopPlayout]; |
| } |
| |
| // Start recording and verify that the native audio layer starts feeding real |
| // audio samples via the RecordedDataIsAvailable callback. |
| - (void)testStartRecordingVerifyCallbacks { |
| XCTestExpectation *recordExpectation = |
| [self expectationWithDescription:@"RecordedDataIsAvailable"]; |
| __block int num_callbacks = 0; |
| |
| mock.expectRecordedDataIsAvailable(^(const void* audioSamples, |
| const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| const uint32_t totalDelayMS, |
| const int32_t clockDrift, |
| const uint32_t currentMicLevel, |
| const bool keyPressed, |
| uint32_t& newMicLevel) { |
| XCTAssertNotEqual((void*)NULL, audioSamples); |
| XCTAssertEqual(nSamples, self.recordParameters.frames_per_10ms_buffer()); |
| XCTAssertEqual(nBytesPerSample, kBytesPerSample); |
| XCTAssertEqual(nChannels, self.recordParameters.channels()); |
| XCTAssertEqual((int)samplesPerSec, self.recordParameters.sample_rate()); |
| XCTAssertEqual(0, clockDrift); |
| XCTAssertEqual(0u, currentMicLevel); |
| XCTAssertFalse(keyPressed); |
| if (++num_callbacks == kNumCallbacks) { |
| [recordExpectation fulfill]; |
| } |
| |
| return 0; |
| }); |
| |
| XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); |
| [self startRecording]; |
| [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil]; |
| [self stopRecording]; |
| } |
| |
| // Start playout and recording (full-duplex audio) and verify that audio is |
| // active in both directions. |
| - (void)testStartPlayoutAndRecordingVerifyCallbacks { |
| XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; |
| __block NSUInteger callbackCount = 0; |
| |
| XCTestExpectation *recordExpectation = |
| [self expectationWithDescription:@"RecordedDataIsAvailable"]; |
| recordExpectation.expectedFulfillmentCount = kNumCallbacks; |
| |
| mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| void *audioSamples, |
| size_t &nSamplesOut, |
| int64_t *elapsed_time_ms, |
| int64_t *ntp_time_ms) { |
| nSamplesOut = nSamples; |
| XCTAssertEqual(nSamples, self.playoutParameters.frames_per_10ms_buffer()); |
| XCTAssertEqual(nBytesPerSample, kBytesPerSample); |
| XCTAssertEqual(nChannels, self.playoutParameters.channels()); |
| XCTAssertEqual((int)samplesPerSec, self.playoutParameters.sample_rate()); |
| XCTAssertNotEqual((void*)NULL, audioSamples); |
| if (callbackCount++ >= kNumCallbacks) { |
| [playoutExpectation fulfill]; |
| } |
| |
| return 0; |
| }); |
| |
| mock.expectRecordedDataIsAvailable(^(const void* audioSamples, |
| const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| const uint32_t totalDelayMS, |
| const int32_t clockDrift, |
| const uint32_t currentMicLevel, |
| const bool keyPressed, |
| uint32_t& newMicLevel) { |
| XCTAssertNotEqual((void*)NULL, audioSamples); |
| XCTAssertEqual(nSamples, self.recordParameters.frames_per_10ms_buffer()); |
| XCTAssertEqual(nBytesPerSample, kBytesPerSample); |
| XCTAssertEqual(nChannels, self.recordParameters.channels()); |
| XCTAssertEqual((int)samplesPerSec, self.recordParameters.sample_rate()); |
| XCTAssertEqual(0, clockDrift); |
| XCTAssertEqual(0u, currentMicLevel); |
| XCTAssertFalse(keyPressed); |
| [recordExpectation fulfill]; |
| |
| return 0; |
| }); |
| |
| XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); |
| [self startPlayout]; |
| [self startRecording]; |
| [self waitForExpectationsWithTimeout:kTestTimeOutInSec handler:nil]; |
| [self stopRecording]; |
| [self stopPlayout]; |
| } |
| |
| // Start playout and read audio from an external PCM file when the audio layer |
| // asks for data to play out. Real audio is played out in this test but it does |
| // not contain any explicit verification that the audio quality is perfect. |
| - (void)testRunPlayoutWithFileAsSource { |
| XCTAssertEqual(1u, playoutParameters.channels()); |
| |
| // Using XCTestExpectation to count callbacks is very slow. |
| XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; |
| const int expectedCallbackCount = kFilePlayTimeInSec * kNumCallbacksPerSecond; |
| __block int callbackCount = 0; |
| |
| NSURL *fileURL = [self fileURLForSampleRate:playoutParameters.sample_rate()]; |
| NSInputStream *inputStream = [[NSInputStream alloc] initWithURL:fileURL]; |
| |
| mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| void *audioSamples, |
| size_t &nSamplesOut, |
| int64_t *elapsed_time_ms, |
| int64_t *ntp_time_ms) { |
| [inputStream read:(uint8_t *)audioSamples maxLength:nSamples*nBytesPerSample*nChannels]; |
| nSamplesOut = nSamples; |
| if (callbackCount++ == expectedCallbackCount) { |
| [playoutExpectation fulfill]; |
| } |
| |
| return 0; |
| }); |
| |
| XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); |
| [self startPlayout]; |
| NSTimeInterval waitTimeout = kFilePlayTimeInSec * 2.0; |
| [self waitForExpectationsWithTimeout:waitTimeout handler:nil]; |
| [self stopPlayout]; |
| } |
| |
| - (void)testDevices { |
| // Device enumeration is not supported. Verify fixed values only. |
| XCTAssertEqual(1, audioDeviceModule->PlayoutDevices()); |
| XCTAssertEqual(1, audioDeviceModule->RecordingDevices()); |
| } |
| |
| // Start playout and recording and store recorded data in an intermediate FIFO |
| // buffer from which the playout side then reads its samples in the same order |
| // as they were stored. Under ideal circumstances, a callback sequence would |
| // look like: ...+-+-+-+-+-+-+-..., where '+' means 'packet recorded' and '-' |
| // means 'packet played'. Under such conditions, the FIFO would only contain |
| // one packet on average. However, under more realistic conditions, the size |
| // of the FIFO will vary more due to an unbalance between the two sides. |
| // This test tries to verify that the device maintains a balanced callback- |
| // sequence by running in loopback for ten seconds while measuring the size |
| // (max and average) of the FIFO. The size of the FIFO is increased by the |
| // recording side and decreased by the playout side. |
| // TODO(henrika): tune the final test parameters after running tests on several |
| // different devices. |
| - (void)testRunPlayoutAndRecordingInFullDuplex { |
| XCTAssertEqual(recordParameters.channels(), playoutParameters.channels()); |
| XCTAssertEqual(recordParameters.sample_rate(), playoutParameters.sample_rate()); |
| |
| XCTestExpectation *playoutExpectation = [self expectationWithDescription:@"NeedMorePlayoutData"]; |
| __block NSUInteger playoutCallbacks = 0; |
| NSUInteger expectedPlayoutCallbacks = kFullDuplexTimeInSec * kNumCallbacksPerSecond; |
| |
| // FIFO queue and measurements |
| NSMutableArray *fifoBuffer = [NSMutableArray arrayWithCapacity:20]; |
| __block NSUInteger fifoMaxSize = 0; |
| __block NSUInteger fifoTotalWrittenElements = 0; |
| __block NSUInteger fifoWriteCount = 0; |
| |
| mock.expectRecordedDataIsAvailable(^(const void* audioSamples, |
| const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| const uint32_t totalDelayMS, |
| const int32_t clockDrift, |
| const uint32_t currentMicLevel, |
| const bool keyPressed, |
| uint32_t& newMicLevel) { |
| if (fifoWriteCount++ < kNumIgnoreFirstCallbacks) { |
| return 0; |
| } |
| |
| NSData *data = [NSData dataWithBytes:audioSamples length:nSamples*nBytesPerSample*nChannels]; |
| @synchronized(fifoBuffer) { |
| [fifoBuffer addObject:data]; |
| fifoMaxSize = MAX(fifoMaxSize, fifoBuffer.count); |
| fifoTotalWrittenElements += fifoBuffer.count; |
| } |
| |
| return 0; |
| }); |
| |
| mock.expectNeedMorePlayData(^int32_t(const size_t nSamples, |
| const size_t nBytesPerSample, |
| const size_t nChannels, |
| const uint32_t samplesPerSec, |
| void *audioSamples, |
| size_t &nSamplesOut, |
| int64_t *elapsed_time_ms, |
| int64_t *ntp_time_ms) { |
| nSamplesOut = nSamples; |
| NSData *data; |
| @synchronized(fifoBuffer) { |
| data = fifoBuffer.firstObject; |
| if (data) { |
| [fifoBuffer removeObjectAtIndex:0]; |
| } |
| } |
| |
| if (data) { |
| memcpy(audioSamples, (char*) data.bytes, data.length); |
| } else { |
| memset(audioSamples, 0, nSamples*nBytesPerSample*nChannels); |
| } |
| |
| if (playoutCallbacks++ == expectedPlayoutCallbacks) { |
| [playoutExpectation fulfill]; |
| } |
| return 0; |
| }); |
| |
| XCTAssertEqual(0, audioDeviceModule->RegisterAudioCallback(&mock)); |
| [self startRecording]; |
| [self startPlayout]; |
| NSTimeInterval waitTimeout = kFullDuplexTimeInSec * 2.0; |
| [self waitForExpectationsWithTimeout:waitTimeout handler:nil]; |
| |
| size_t fifoAverageSize = |
| (fifoTotalWrittenElements == 0) |
| ? 0.0 |
| : 0.5 + (double)fifoTotalWrittenElements / (fifoWriteCount - kNumIgnoreFirstCallbacks); |
| |
| [self stopPlayout]; |
| [self stopRecording]; |
| XCTAssertLessThan(fifoAverageSize, 10u); |
| XCTAssertLessThan(fifoMaxSize, 20u); |
| } |
| |
| @end |