Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(218)

Unified Diff: webrtc/modules/audio_device/ios/audio_device_ios.mm

Issue 1435293003: Improved error handling in iOS ADM to avoid race during init (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Adding rtc::GlobalLockPod and counts number of active audio sessions at Start/Stop Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: webrtc/modules/audio_device/ios/audio_device_ios.mm
diff --git a/webrtc/modules/audio_device/ios/audio_device_ios.mm b/webrtc/modules/audio_device/ios/audio_device_ios.mm
index d7668f7894df500d385a50d959499ffa7911c091..56fa418c87d4c38aca8492bacc7205d35ce6465c 100644
--- a/webrtc/modules/audio_device/ios/audio_device_ios.mm
+++ b/webrtc/modules/audio_device/ios/audio_device_ios.mm
@@ -25,6 +25,10 @@
namespace webrtc {
+// Initialization of static members. See audio_device_ios.h for details.
pbos-webrtc 2015/11/18 12:54:22 Feel free to remove this comment (or put why it's
henrika_webrtc 2015/11/18 16:05:32 Removed.
+int AudioDeviceIOS::audio_session_activation_count_ = 0;
+rtc::GlobalLockPod AudioDeviceIOS::lock_;
+
#define LOGI() LOG(LS_INFO) << "AudioDeviceIOS::"
#define LOG_AND_RETURN_IF_ERROR(error, message) \
@@ -80,7 +84,7 @@ using ios::CheckAndLogError;
// Activates an audio session suitable for full duplex VoIP sessions when
// |activate| is true. Also sets the preferred sample rate and IO buffer
// duration. Deactivates an active audio session if |activate| is set to false.
-static void ActivateAudioSession(AVAudioSession* session, bool activate) {
+static bool ActivateAudioSession(AVAudioSession* session, bool activate) {
pbos-webrtc 2015/11/18 12:54:22 EXCLUSIVE_LOCKS_REQUIRED(lock_) (before {)
henrika_webrtc 2015/11/18 16:05:32 Can't do that actually since this is not a member
LOG(LS_INFO) << "ActivateAudioSession(" << activate << ")";
@autoreleasepool {
NSError* error = nil;
@@ -96,8 +100,7 @@ static void ActivateAudioSession(AVAudioSession* session, bool activate) {
setActive:NO
withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
error:&error];
- RTC_DCHECK(CheckAndLogError(success, error));
- return;
+ return CheckAndLogError(success, error);
}
// Go ahead and active our own audio session since |activate| is true.
@@ -129,7 +132,6 @@ static void ActivateAudioSession(AVAudioSession* session, bool activate) {
RTC_DCHECK(CheckAndLogError(success, error));
// Set the preferred audio I/O buffer duration, in seconds.
- // TODO(henrika): add more comments here.
error = nil;
success = [session setPreferredIOBufferDuration:kPreferredIOBufferDuration
error:&error];
@@ -139,13 +141,27 @@ static void ActivateAudioSession(AVAudioSession* session, bool activate) {
// session (e.g. phone call) has higher priority than ours.
error = nil;
success = [session setActive:YES error:&error];
- RTC_DCHECK(CheckAndLogError(success, error));
- RTC_CHECK(session.isInputAvailable) << "No input path is available!";
+ if (!CheckAndLogError(success, error)) {
+ return false;
+ }
- // Ensure that category and mode are actually activated.
- RTC_DCHECK(
- [session.category isEqualToString:AVAudioSessionCategoryPlayAndRecord]);
- RTC_DCHECK([session.mode isEqualToString:AVAudioSessionModeVoiceChat]);
+ // Ensure that the device currently supports audio input.
+ if (!session.isInputAvailable) {
+ LOG(LS_ERROR) << "No audio input path is available!";
+ return false;
+ }
+
+ // Ensure that the required category and mode are actually activated.
+ if (![session.category
+ isEqualToString:AVAudioSessionCategoryPlayAndRecord]) {
+ LOG(LS_ERROR)
+ << "Failed to set category to AVAudioSessionCategoryPlayAndRecord";
+ return false;
+ }
+ if (![session.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
+ LOG(LS_ERROR) << "Failed to set mode to AVAudioSessionModeVoiceChat";
+ return false;
+ }
// Try to set the preferred number of hardware audio channels. These calls
// must be done after setting the audio session’s category and mode and
@@ -164,6 +180,7 @@ static void ActivateAudioSession(AVAudioSession* session, bool activate) {
[session setPreferredOutputNumberOfChannels:kPreferredNumberOfChannels
error:&error];
RTC_DCHECK(CheckAndLogError(success, error));
+ return true;
}
}
@@ -212,7 +229,7 @@ AudioDeviceIOS::AudioDeviceIOS()
}
AudioDeviceIOS::~AudioDeviceIOS() {
- LOGI() << "~dtor";
+ LOGI() << "~dtor" << ios::GetCurrentThreadDescription();
RTC_DCHECK(thread_checker_.CalledOnValidThread());
Terminate();
}
@@ -267,7 +284,7 @@ int32_t AudioDeviceIOS::InitPlayout() {
RTC_DCHECK(!playing_);
if (!rec_is_initialized_) {
if (!InitPlayOrRecord()) {
- LOG_F(LS_ERROR) << "InitPlayOrRecord failed!";
+ LOG_F(LS_ERROR) << "InitPlayOrRecord failed for InitPlayout!";
return -1;
}
}
@@ -283,7 +300,7 @@ int32_t AudioDeviceIOS::InitRecording() {
RTC_DCHECK(!recording_);
if (!play_is_initialized_) {
if (!InitPlayOrRecord()) {
- LOG_F(LS_ERROR) << "InitPlayOrRecord failed!";
+ LOG_F(LS_ERROR) << "InitPlayOrRecord failed for InitRecording!";
return -1;
}
}
@@ -639,7 +656,7 @@ void AudioDeviceIOS::SetupAudioBuffersForActiveAudioSession() {
bool AudioDeviceIOS::SetupAndInitializeVoiceProcessingAudioUnit() {
LOGI() << "SetupAndInitializeVoiceProcessingAudioUnit";
- RTC_DCHECK(!vpio_unit_);
+ RTC_DCHECK(!vpio_unit_) << "VoiceProcessingIO audio unit already exists";
// Create an audio component description to identify the Voice-Processing
// I/O audio unit.
AudioComponentDescription vpio_unit_description;
@@ -653,29 +670,42 @@ bool AudioDeviceIOS::SetupAndInitializeVoiceProcessingAudioUnit() {
AudioComponentFindNext(nullptr, &vpio_unit_description);
// Create a Voice-Processing IO audio unit.
- LOG_AND_RETURN_IF_ERROR(
- AudioComponentInstanceNew(found_vpio_unit_ref, &vpio_unit_),
- "Failed to create a VoiceProcessingIO audio unit");
+ OSStatus result = noErr;
+ result = AudioComponentInstanceNew(found_vpio_unit_ref, &vpio_unit_);
+ if (result != noErr) {
+ vpio_unit_ = nullptr;
+ LOG(LS_ERROR) << "AudioComponentInstanceNew failed: " << result;
+ return false;
+ }
// A VP I/O unit's bus 1 connects to input hardware (microphone). Enable
// input on the input scope of the input element.
AudioUnitElement input_bus = 1;
UInt32 enable_input = 1;
- LOG_AND_RETURN_IF_ERROR(
- AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Input, input_bus, &enable_input,
- sizeof(enable_input)),
- "Failed to enable input on input scope of input element");
+ result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Input, input_bus, &enable_input,
+ sizeof(enable_input));
+ if (result != noErr) {
+ DisposeAudioUnit();
+ LOG(LS_ERROR) << "Failed to enable input on input scope of input element: "
+ << result;
+ return false;
+ }
// A VP I/O unit's bus 0 connects to output hardware (speaker). Enable
// output on the output scope of the output element.
AudioUnitElement output_bus = 0;
UInt32 enable_output = 1;
- LOG_AND_RETURN_IF_ERROR(
- AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Output, output_bus, &enable_output,
- sizeof(enable_output)),
- "Failed to enable output on output scope of output element");
+ result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Output, output_bus,
+ &enable_output, sizeof(enable_output));
+ if (result != noErr) {
+ DisposeAudioUnit();
+ LOG(LS_ERROR)
+ << "Failed to enable output on output scope of output element: "
+ << result;
+ return false;
+ }
// Set the application formats for input and output:
// - use same format in both directions
@@ -703,38 +733,55 @@ bool AudioDeviceIOS::SetupAndInitializeVoiceProcessingAudioUnit() {
#endif
// Set the application format on the output scope of the input element/bus.
- LOG_AND_RETURN_IF_ERROR(
- AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Output, input_bus,
- &application_format, size),
- "Failed to set application format on output scope of input element");
+ result = AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output, input_bus,
+ &application_format, size);
+ if (result != noErr) {
+ DisposeAudioUnit();
+ LOG(LS_ERROR)
+ << "Failed to set application format on output scope of input bus: "
+ << result;
+ return false;
+ }
// Set the application format on the input scope of the output element/bus.
- LOG_AND_RETURN_IF_ERROR(
- AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input, output_bus,
- &application_format, size),
- "Failed to set application format on input scope of output element");
+ result = AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, output_bus,
+ &application_format, size);
+ if (result != noErr) {
+ DisposeAudioUnit();
+ LOG(LS_ERROR)
+ << "Failed to set application format on input scope of output bus: "
+ << result;
+ return false;
+ }
// Specify the callback function that provides audio samples to the audio
// unit.
AURenderCallbackStruct render_callback;
render_callback.inputProc = GetPlayoutData;
render_callback.inputProcRefCon = this;
- LOG_AND_RETURN_IF_ERROR(
- AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Input, output_bus, &render_callback,
- sizeof(render_callback)),
- "Failed to specify the render callback on the output element");
+ result = AudioUnitSetProperty(
+ vpio_unit_, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
+ output_bus, &render_callback, sizeof(render_callback));
+ if (result != noErr) {
+ DisposeAudioUnit();
+ LOG(LS_ERROR) << "Failed to specify the render callback on the output bus: "
+ << result;
+ return false;
+ }
// Disable AU buffer allocation for the recorder, we allocate our own.
// TODO(henrika): not sure that it actually saves resource to make this call.
UInt32 flag = 0;
- LOG_AND_RETURN_IF_ERROR(
- AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_ShouldAllocateBuffer,
- kAudioUnitScope_Output, input_bus, &flag,
- sizeof(flag)),
- "Failed to disable buffer allocation on the input element");
+ result = AudioUnitSetProperty(
+ vpio_unit_, kAudioUnitProperty_ShouldAllocateBuffer,
+ kAudioUnitScope_Output, input_bus, &flag, sizeof(flag));
+ if (result != noErr) {
+ DisposeAudioUnit();
+ LOG(LS_ERROR) << "Failed to disable buffer allocation on the input bus: "
+ << result;
+ }
// Specify the callback to be called by the I/O thread to us when input audio
// is available. The recorded samples can then be obtained by calling the
@@ -742,16 +789,24 @@ bool AudioDeviceIOS::SetupAndInitializeVoiceProcessingAudioUnit() {
AURenderCallbackStruct input_callback;
input_callback.inputProc = RecordedDataIsAvailable;
input_callback.inputProcRefCon = this;
- LOG_AND_RETURN_IF_ERROR(
- AudioUnitSetProperty(vpio_unit_,
- kAudioOutputUnitProperty_SetInputCallback,
- kAudioUnitScope_Global, input_bus, &input_callback,
- sizeof(input_callback)),
- "Failed to specify the input callback on the input element");
+ result = AudioUnitSetProperty(vpio_unit_,
+ kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global, input_bus,
+ &input_callback, sizeof(input_callback));
+ if (result != noErr) {
+ DisposeAudioUnit();
+ LOG(LS_ERROR) << "Failed to specify the input callback on the input bus: "
+ << result;
+ }
// Initialize the Voice-Processing I/O unit instance.
- LOG_AND_RETURN_IF_ERROR(AudioUnitInitialize(vpio_unit_),
- "Failed to initialize the Voice-Processing I/O unit");
+ result = AudioUnitInitialize(vpio_unit_);
+ if (result != noErr) {
+ DisposeAudioUnit();
+ LOG(LS_ERROR) << "Failed to initialize the Voice-Processing I/O unit: "
+ << result;
+ return false;
+ }
return true;
}
@@ -790,9 +845,26 @@ bool AudioDeviceIOS::RestartAudioUnitWithNewFormat(float sample_rate) {
bool AudioDeviceIOS::InitPlayOrRecord() {
LOGI() << "InitPlayOrRecord";
- AVAudioSession* session = [AVAudioSession sharedInstance];
- // Activate the audio session and ask for a set of preferred audio parameters.
- ActivateAudioSession(session, true);
+ {
+ // An application can create more than one ADM and start audio streaming
+ // for all of them. It is essential that we only activate the app's audio
+ // session once (for the first one) and deactivate it once (for the last).
+ rtc::GlobalLockScope ls(&lock_);
+ if (audio_session_activation_count_ == 0) {
+ // The system provides an audio session object upon launch of an
+ // application. However, we must initialize the session in order to
+ // handle interruptions. Implicit initialization occurs when obtaining
+ // a reference to the AVAudioSession object.
+ AVAudioSession* session = [AVAudioSession sharedInstance];
+ // Try to activate the audio session and ask for a set of preferred audio
+ // parameters.
+ if (!ActivateAudioSession(session, true)) {
+ return false;
pbos-webrtc 2015/11/18 12:54:22 LOG with LS_ERROR here?
henrika_webrtc 2015/11/18 16:05:32 Done.
+ }
+ ++audio_session_activation_count_;
+ LOG(LS_INFO) << "Our audio session is now activated";
+ }
+ }
// Start observing audio session interruptions and route changes.
RegisterNotificationObservers();
@@ -802,6 +874,7 @@ bool AudioDeviceIOS::InitPlayOrRecord() {
// Create, setup and initialize a new Voice-Processing I/O unit.
if (!SetupAndInitializeVoiceProcessingAudioUnit()) {
+ LOG(LS_ERROR) << "SetupAndInitializeVoiceProcessingAudioUnit failed";
return false;
}
return true;
@@ -823,20 +896,38 @@ bool AudioDeviceIOS::ShutdownPlayOrRecord() {
if (result != noErr) {
LOG_F(LS_ERROR) << "AudioUnitUninitialize failed: " << result;
}
- result = AudioComponentInstanceDispose(vpio_unit_);
- if (result != noErr) {
- LOG_F(LS_ERROR) << "AudioComponentInstanceDispose failed: " << result;
- }
- vpio_unit_ = nullptr;
+ DisposeAudioUnit();
}
// All I/O should be stopped or paused prior to deactivating the audio
- // session, hence we deactivate as last action.
- AVAudioSession* session = [AVAudioSession sharedInstance];
- ActivateAudioSession(session, false);
+ // session, hence we deactivate as last action. However, if more than one
+ // object is using the audio session, ensure that only the last object
+ // deactivates. Apple recommends: "activate your audio session only as needed
+ // and deactivate it when you are not using audio".
+ {
+ rtc::GlobalLockScope ls(&lock_);
+ if (audio_session_activation_count_ == 1) {
pbos-webrtc 2015/11/18 12:54:22 pref: --audio_session_activation_count_ == 0 (and
henrika_webrtc 2015/11/18 16:07:33 Solved in a different manner. Hope you are OK with
+ AVAudioSession* session = [AVAudioSession sharedInstance];
+ if (!ActivateAudioSession(session, false)) {
pbos-webrtc 2015/11/18 12:54:22 I'm wondering whether this should be a CHECK, or a
henrika_webrtc 2015/11/18 16:05:32 We have strived to avoid crashing the app in these
+ return false;
+ }
+ --audio_session_activation_count_;
+ LOG(LS_INFO) << "Our audio session is now deactivated";
+ }
+ }
return true;
}
+void AudioDeviceIOS::DisposeAudioUnit() {
+ if (nullptr == vpio_unit_)
+ return;
+ OSStatus result = AudioComponentInstanceDispose(vpio_unit_);
+ if (result != noErr) {
+ LOG(LS_ERROR) << "AudioComponentInstanceDispose failed:" << result;
+ }
+ vpio_unit_ = nullptr;
+}
+
OSStatus AudioDeviceIOS::RecordedDataIsAvailable(
void* in_ref_con,
AudioUnitRenderActionFlags* io_action_flags,

Powered by Google App Engine
This is Rietveld 408576698