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}
diff --git a/webrtc/modules/audio_device/ios/audio_device_ios.mm b/webrtc/modules/audio_device/ios/audio_device_ios.mm
index e094ebd..b0d26be 100644
--- a/webrtc/modules/audio_device/ios/audio_device_ios.mm
+++ b/webrtc/modules/audio_device/ios/audio_device_ios.mm
@@ -86,6 +86,12 @@
// 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 @@
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 @@
}
// 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;
}
diff --git a/webrtc/modules/audio_device/ios/audio_device_unittest_ios.cc b/webrtc/modules/audio_device/ios/audio_device_unittest_ios.cc
index b892f28..8993ace 100644
--- a/webrtc/modules/audio_device/ios/audio_device_unittest_ios.cc
+++ b/webrtc/modules/audio_device/ios/audio_device_unittest_ios.cc
@@ -636,6 +636,55 @@
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) {