You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
594 lines
25 KiB
594 lines
25 KiB
/*
|
|
* 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
|