Resolves issue with multiple calls to audio unit initialization

BUG=webrtc:5166
R=tkchin@webrtc.org

Review URL: https://codereview.webrtc.org/1472833002 .

Cr-Commit-Position: refs/heads/master@{#10865}
This commit is contained in:
henrika
2015-12-02 10:46:46 +01:00
parent 40455d6f37
commit c729032b1b
2 changed files with 74 additions and 7 deletions

View File

@ -86,6 +86,12 @@ const UInt32 kBytesPerSample = 2;
// Can most likely be removed.
const UInt16 kFixedPlayoutDelayEstimate = 30;
const UInt16 kFixedRecordDelayEstimate = 30;
// Calls to AudioUnitInitialize() can fail if called back-to-back on different
// ADM instances. A fall-back solution is to allow multiple sequential calls
// with as small delay between each. This factor sets the max number of allowed
// initialization attempts.
const int kMaxNumberOfAudioUnitInitializeAttempts = 5;
using ios::CheckAndLogError;
@ -741,6 +747,7 @@ bool AudioDeviceIOS::SetupAndInitializeVoiceProcessingAudioUnit() {
vpio_unit_description.componentManufacturer = kAudioUnitManufacturer_Apple;
vpio_unit_description.componentFlags = 0;
vpio_unit_description.componentFlagsMask = 0;
// Obtain an audio unit instance given the description.
AudioComponent found_vpio_unit_ref =
AudioComponentFindNext(nullptr, &vpio_unit_description);
@ -876,17 +883,28 @@ bool AudioDeviceIOS::SetupAndInitializeVoiceProcessingAudioUnit() {
}
// Initialize the Voice-Processing I/O unit instance.
// Calls to AudioUnitInitialize() can fail if called back-to-back on
// different ADM instances. The error message in this case is -66635 which is
// undocumented. Tests have shown that calling AudioUnitInitialize a second
// time, after a short sleep, avoids this issue.
// See webrtc:5166 for details.
int failed_initalize_attempts = 0;
result = AudioUnitInitialize(vpio_unit_);
if (result != noErr) {
result = AudioUnitUninitialize(vpio_unit_);
if (result != noErr) {
LOG_F(LS_ERROR) << "AudioUnitUninitialize failed: " << result;
}
DisposeAudioUnit();
while (result != noErr) {
LOG(LS_ERROR) << "Failed to initialize the Voice-Processing I/O unit: "
<< result;
return false;
++failed_initalize_attempts;
if (failed_initalize_attempts == kMaxNumberOfAudioUnitInitializeAttempts) {
// Max number of initialization attempts exceeded, hence abort.
LOG(LS_WARNING) << "Too many initialization attempts";
DisposeAudioUnit();
return false;
}
LOG(LS_INFO) << "pause 100ms and try audio unit initialization again...";
[NSThread sleepForTimeInterval:0.1f];
result = AudioUnitInitialize(vpio_unit_);
}
LOG(LS_INFO) << "Voice-Processing I/O unit is now initialized";
return true;
}

View File

@ -636,6 +636,55 @@ TEST_F(AudioDeviceTest, StopPlayoutRequiresInitToRestart) {
EXPECT_FALSE(audio_device()->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 instace shall deactivate the audio session.
TEST_F(AudioDeviceTest, StartPlayoutOnTwoInstances) {
// Create and initialize a second/extra ADM instance. The default ADM is
// created by the test harness.
rtc::scoped_refptr<AudioDeviceModule> second_audio_device =
CreateAudioDevice(AudioDeviceModule::kPlatformDefaultAudio);
EXPECT_NE(second_audio_device.get(), nullptr);
EXPECT_EQ(0, second_audio_device->Init());
// Start playout for the default ADM. Ignore the callback sequence.
NiceMock<MockAudioTransport> mock(kPlayout);
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock));
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).
EXPECT_EQ(0, second_audio_device->InitPlayout());
EXPECT_TRUE(second_audio_device->PlayoutIsInitialized());
// Stop playout for the default ADM. The audio session shall not be
// deactivated since it is used by the second ADM.
StopPlayout();
// 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.
MockAudioTransport mock2(kPlayout);
mock2.HandleCallbacks(test_is_done_.get(), nullptr, kNumCallbacks);
EXPECT_CALL(
mock2, NeedMorePlayData(playout_frames_per_10ms_buffer(), kBytesPerSample,
playout_channels(), playout_sample_rate(),
NotNull(), _, _, _))
.Times(AtLeast(kNumCallbacks));
EXPECT_EQ(0, second_audio_device->RegisterAudioCallback(&mock2));
EXPECT_EQ(0, second_audio_device->StartPlayout());
EXPECT_TRUE(second_audio_device->Playing());
test_is_done_->Wait(kTestTimeOutInMilliseconds);
EXPECT_EQ(0, second_audio_device->StopPlayout());
EXPECT_FALSE(second_audio_device->Playing());
EXPECT_FALSE(second_audio_device->PlayoutIsInitialized());
EXPECT_EQ(0, second_audio_device->Terminate());
}
// Start playout and verify that the native audio layer starts asking for real
// audio samples to play out using the NeedMorePlayData callback.
TEST_F(AudioDeviceTest, StartPlayoutVerifyCallbacks) {