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:
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user