aboutsummaryrefslogtreecommitdiff
path: root/webrtc/modules/audio_device/ios/audio_device_ios.mm
diff options
context:
space:
mode:
Diffstat (limited to 'webrtc/modules/audio_device/ios/audio_device_ios.mm')
-rw-r--r--webrtc/modules/audio_device/ios/audio_device_ios.mm350
1 files changed, 268 insertions, 82 deletions
diff --git a/webrtc/modules/audio_device/ios/audio_device_ios.mm b/webrtc/modules/audio_device/ios/audio_device_ios.mm
index f26e9f1cc7..f6dee5b3cf 100644
--- a/webrtc/modules/audio_device/ios/audio_device_ios.mm
+++ b/webrtc/modules/audio_device/ios/audio_device_ios.mm
@@ -19,12 +19,24 @@
#include "webrtc/base/atomicops.h"
#include "webrtc/base/checks.h"
+#include "webrtc/base/criticalsection.h"
#include "webrtc/base/logging.h"
+#include "webrtc/base/thread_annotations.h"
#include "webrtc/modules/audio_device/fine_audio_buffer.h"
-#include "webrtc/modules/utility/interface/helpers_ios.h"
+#include "webrtc/modules/utility/include/helpers_ios.h"
namespace webrtc {
+// Protects |g_audio_session_users|.
+static rtc::GlobalLockPod g_lock;
+
+// Counts number of users (=instances of this object) who needs an active
+// audio session. This variable is used to ensure that we only activate an audio
+// session for the first user and deactivate it for the last.
+// Member is static to ensure that the value is counted for all instances
+// and not per instance.
+static int g_audio_session_users GUARDED_BY(g_lock) = 0;
+
#define LOGI() LOG(LS_INFO) << "AudioDeviceIOS::"
#define LOG_AND_RETURN_IF_ERROR(error, message) \
@@ -74,25 +86,62 @@ 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;
+// Verifies that the current audio session supports input audio and that the
+// required category and mode are enabled.
+static bool VerifyAudioSession(AVAudioSession* session) {
+ LOG(LS_INFO) << "VerifyAudioSession";
+ // 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;
+ }
+ return true;
+}
+
// 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)
+ EXCLUSIVE_LOCKS_REQUIRED(g_lock) {
LOG(LS_INFO) << "ActivateAudioSession(" << activate << ")";
@autoreleasepool {
NSError* error = nil;
BOOL success = NO;
- // Deactivate the audio session and return if |activate| is false.
if (!activate) {
- success = [session setActive:NO error:&error];
- RTC_DCHECK(CheckAndLogError(success, error));
- return;
+ // Deactivate the audio session using an extra option and then return.
+ // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to
+ // ensure that other audio sessions that were interrupted by our session
+ // can return to their active state. It is recommended for VoIP apps to
+ // use this option.
+ success = [session
+ setActive:NO
+ withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
+ error:&error];
+ return CheckAndLogError(success, error);
}
+ // Go ahead and active our own audio session since |activate| is true.
// Use a category which supports simultaneous recording and playback.
// By default, using this category implies that our app’s audio is
// nonmixable, hence activating the session will interrupt any other
@@ -121,7 +170,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];
@@ -131,13 +179,15 @@ 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 active audio session has the correct category and mode.
+ if (!VerifyAudioSession(session)) {
+ LOG(LS_ERROR) << "Failed to verify audio session category and mode";
+ 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
@@ -156,7 +206,52 @@ static void ActivateAudioSession(AVAudioSession* session, bool activate) {
[session setPreferredOutputNumberOfChannels:kPreferredNumberOfChannels
error:&error];
RTC_DCHECK(CheckAndLogError(success, error));
+ return 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).
+static bool ActivateAudioSession() {
+ LOGI() << "ActivateAudioSession";
+ rtc::GlobalLockScope ls(&g_lock);
+ if (g_audio_session_users == 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)) {
+ LOG(LS_ERROR) << "Failed to activate the audio session";
+ return false;
+ }
+ LOG(LS_INFO) << "The audio session is now activated";
+ }
+ ++g_audio_session_users;
+ LOG(LS_INFO) << "Number of audio session users: " << g_audio_session_users;
+ return true;
+}
+
+// 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".
+static bool DeactivateAudioSession() {
+ LOGI() << "DeactivateAudioSession";
+ rtc::GlobalLockScope ls(&g_lock);
+ if (g_audio_session_users == 1) {
+ AVAudioSession* session = [AVAudioSession sharedInstance];
+ if (!ActivateAudioSession(session, false)) {
+ LOG(LS_ERROR) << "Failed to deactivate the audio session";
+ return false;
+ }
+ LOG(LS_INFO) << "Our audio session is now deactivated";
}
+ --g_audio_session_users;
+ LOG(LS_INFO) << "Number of audio session users: " << g_audio_session_users;
+ return true;
}
#if !defined(NDEBUG)
@@ -198,12 +293,13 @@ AudioDeviceIOS::AudioDeviceIOS()
initialized_(false),
rec_is_initialized_(false),
play_is_initialized_(false),
- audio_interruption_observer_(nullptr) {
+ audio_interruption_observer_(nullptr),
+ route_change_observer_(nullptr) {
LOGI() << "ctor" << ios::GetCurrentThreadDescription();
}
AudioDeviceIOS::~AudioDeviceIOS() {
- LOGI() << "~dtor";
+ LOGI() << "~dtor" << ios::GetCurrentThreadDescription();
RTC_DCHECK(thread_checker_.CalledOnValidThread());
Terminate();
}
@@ -245,8 +341,16 @@ int32_t AudioDeviceIOS::Terminate() {
if (!initialized_) {
return 0;
}
- ShutdownPlayOrRecord();
+ StopPlayout();
+ StopRecording();
initialized_ = false;
+ {
+ rtc::GlobalLockScope ls(&g_lock);
+ if (g_audio_session_users != 0) {
+ LOG(LS_WARNING) << "Object is destructed with an active audio session";
+ }
+ RTC_DCHECK_GE(g_audio_session_users, 0);
+ }
return 0;
}
@@ -258,7 +362,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;
}
}
@@ -274,7 +378,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;
}
}
@@ -291,9 +395,11 @@ int32_t AudioDeviceIOS::StartPlayout() {
if (!recording_) {
OSStatus result = AudioOutputUnitStart(vpio_unit_);
if (result != noErr) {
- LOG_F(LS_ERROR) << "AudioOutputUnitStart failed: " << result;
+ LOG_F(LS_ERROR) << "AudioOutputUnitStart failed for StartPlayout: "
+ << result;
return -1;
}
+ LOG(LS_INFO) << "Voice-Processing I/O audio unit is now started";
}
rtc::AtomicOps::ReleaseStore(&playing_, 1);
return 0;
@@ -322,9 +428,11 @@ int32_t AudioDeviceIOS::StartRecording() {
if (!playing_) {
OSStatus result = AudioOutputUnitStart(vpio_unit_);
if (result != noErr) {
- LOG_F(LS_ERROR) << "AudioOutputUnitStart failed: " << result;
+ LOG_F(LS_ERROR) << "AudioOutputUnitStart failed for StartRecording: "
+ << result;
return -1;
}
+ LOG(LS_INFO) << "Voice-Processing I/O audio unit is now started";
}
rtc::AtomicOps::ReleaseStore(&recording_, 1);
return 0;
@@ -474,11 +582,12 @@ void AudioDeviceIOS::RegisterNotificationObservers() {
LOG(LS_INFO) << " OldDeviceUnavailable";
break;
case AVAudioSessionRouteChangeReasonCategoryChange:
+ // It turns out that we see this notification (at least in iOS 9.2)
+ // when making a switch from a BT device to e.g. Speaker using the
+ // iOS Control Center and that we therefore must check if the sample
+ // rate has changed. And if so is the case, restart the audio unit.
LOG(LS_INFO) << " CategoryChange";
LOG(LS_INFO) << " New category: " << ios::GetAudioSessionCategory();
- // Don't see this as route change since it can be triggered in
- // combination with session interruptions as well.
- valid_route_change = false;
break;
case AVAudioSessionRouteChangeReasonOverride:
LOG(LS_INFO) << " Override";
@@ -490,9 +599,11 @@ void AudioDeviceIOS::RegisterNotificationObservers() {
LOG(LS_INFO) << " NoSuitableRouteForCategory";
break;
case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
- // Ignore this type of route change since we are focusing
+ // The set of input and output ports has not changed, but their
+ // configuration has, e.g., a port’s selected data source has
+ // changed. Ignore this type of route change since we are focusing
// on detecting headset changes.
- LOG(LS_INFO) << " RouteConfigurationChange";
+ LOG(LS_INFO) << " RouteConfigurationChange (ignored)";
valid_route_change = false;
break;
}
@@ -630,7 +741,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;
@@ -639,34 +750,48 @@ 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);
// 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
@@ -694,38 +819,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
@@ -733,16 +875,39 @@ 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");
+ // 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_);
+ while (result != noErr) {
+ LOG(LS_ERROR) << "Failed to initialize the Voice-Processing I/O unit: "
+ << result;
+ ++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;
}
@@ -772,18 +937,29 @@ bool AudioDeviceIOS::RestartAudioUnitWithNewFormat(float sample_rate) {
// Prepare the audio unit to render audio again.
LOG_AND_RETURN_IF_ERROR(AudioUnitInitialize(vpio_unit_),
"Failed to initialize the Voice-Processing I/O unit");
+ LOG(LS_INFO) << "Voice-Processing I/O unit is now reinitialized";
// Start rendering audio using the new format.
LOG_AND_RETURN_IF_ERROR(AudioOutputUnitStart(vpio_unit_),
"Failed to start the Voice-Processing I/O unit");
+ LOG(LS_INFO) << "Voice-Processing I/O unit is now restarted";
return true;
}
bool AudioDeviceIOS::InitPlayOrRecord() {
LOGI() << "InitPlayOrRecord";
+ // Activate the audio session if not already activated.
+ if (!ActivateAudioSession()) {
+ return false;
+ }
+
+ // Ensure that the active audio session has the correct category and mode.
AVAudioSession* session = [AVAudioSession sharedInstance];
- // Activate the audio session and ask for a set of preferred audio parameters.
- ActivateAudioSession(session, true);
+ if (!VerifyAudioSession(session)) {
+ DeactivateAudioSession();
+ LOG(LS_ERROR) << "Failed to verify audio session category and mode";
+ return false;
+ }
// Start observing audio session interruptions and route changes.
RegisterNotificationObservers();
@@ -793,16 +969,16 @@ bool AudioDeviceIOS::InitPlayOrRecord() {
// Create, setup and initialize a new Voice-Processing I/O unit.
if (!SetupAndInitializeVoiceProcessingAudioUnit()) {
+ // Reduce usage count for the audio session and possibly deactivate it if
+ // this object is the only user.
+ DeactivateAudioSession();
return false;
}
return true;
}
-bool AudioDeviceIOS::ShutdownPlayOrRecord() {
+void AudioDeviceIOS::ShutdownPlayOrRecord() {
LOGI() << "ShutdownPlayOrRecord";
- // Remove audio session notification observers.
- UnregisterNotificationObservers();
-
// Close and delete the voice-processing I/O unit.
OSStatus result = -1;
if (nullptr != vpio_unit_) {
@@ -814,18 +990,25 @@ 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();
}
+ // Remove audio session notification observers.
+ UnregisterNotificationObservers();
+
// 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);
- return true;
+ DeactivateAudioSession();
+}
+
+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(
@@ -855,8 +1038,11 @@ OSStatus AudioDeviceIOS::OnRecordedDataIsAvailable(
if (in_number_frames != record_parameters_.frames_per_buffer()) {
// We have seen short bursts (1-2 frames) where |in_number_frames| changes.
// Add a log to keep track of longer sequences if that should ever happen.
+ // Also return since calling AudioUnitRender in this state will only result
+ // in kAudio_ParamError (-50) anyhow.
LOG(LS_WARNING) << "in_number_frames (" << in_number_frames
<< ") != " << record_parameters_.frames_per_buffer();
+ return noErr;
}
// Obtain the recorded audio samples by initiating a rendering cycle.
// Since it happens on the input bus, the |io_data| parameter is a reference
@@ -866,7 +1052,7 @@ OSStatus AudioDeviceIOS::OnRecordedDataIsAvailable(
result = AudioUnitRender(vpio_unit_, io_action_flags, in_time_stamp,
in_bus_number, in_number_frames, io_data);
if (result != noErr) {
- LOG_F(LS_ERROR) << "AudioOutputUnitStart failed: " << result;
+ LOG_F(LS_ERROR) << "AudioUnitRender failed: " << result;
return result;
}
// Get a pointer to the recorded audio and send it to the WebRTC ADB.