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 0e127c9dc28d99578ffb9600b977096131586a6a..8f6fb4d9b635f480fb699a17a6529950146cf555 100644 |
--- a/webrtc/modules/audio_device/ios/audio_device_ios.mm |
+++ b/webrtc/modules/audio_device/ios/audio_device_ios.mm |
@@ -61,6 +61,13 @@ namespace webrtc { |
const UInt16 kFixedPlayoutDelayEstimate = 30; |
const UInt16 kFixedRecordDelayEstimate = 30; |
+enum AudioDeviceMessageType : uint32_t { |
+ kMessageTypeInterruptionBegin, |
+ kMessageTypeInterruptionEnd, |
+ kMessageTypeValidRouteChange, |
+ kMessageTypeCanPlayOrRecordChange, |
+}; |
+ |
using ios::CheckAndLogError; |
#if !defined(NDEBUG) |
@@ -85,15 +92,15 @@ static void LogDeviceInfo() { |
#endif // !defined(NDEBUG) |
AudioDeviceIOS::AudioDeviceIOS() |
- : async_invoker_(new rtc::AsyncInvoker()), |
- audio_device_buffer_(nullptr), |
+ : audio_device_buffer_(nullptr), |
audio_unit_(nullptr), |
recording_(0), |
playing_(0), |
initialized_(false), |
rec_is_initialized_(false), |
play_is_initialized_(false), |
- is_interrupted_(false) { |
+ is_interrupted_(false), |
+ has_configured_session_(false) { |
LOGI() << "ctor" << ios::GetCurrentThreadDescription(); |
thread_ = rtc::Thread::Current(); |
audio_session_observer_ = |
@@ -191,6 +198,7 @@ int32_t AudioDeviceIOS::StartPlayout() { |
RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
RTC_DCHECK(play_is_initialized_); |
RTC_DCHECK(!playing_); |
+ RTC_DCHECK(audio_unit_); |
if (fine_audio_buffer_) { |
fine_audio_buffer_->ResetPlayout(); |
} |
@@ -209,7 +217,11 @@ int32_t AudioDeviceIOS::StartPlayout() { |
int32_t AudioDeviceIOS::StopPlayout() { |
LOGI() << "StopPlayout"; |
RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
- if (!play_is_initialized_ || !playing_) { |
+ if (!play_is_initialized_) { |
+ return 0; |
+ } |
+ if (!playing_) { |
+ play_is_initialized_ = false; |
return 0; |
} |
if (!recording_) { |
@@ -225,6 +237,7 @@ int32_t AudioDeviceIOS::StartRecording() { |
RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
RTC_DCHECK(rec_is_initialized_); |
RTC_DCHECK(!recording_); |
+ RTC_DCHECK(audio_unit_); |
if (fine_audio_buffer_) { |
fine_audio_buffer_->ResetRecord(); |
} |
@@ -243,7 +256,11 @@ int32_t AudioDeviceIOS::StartRecording() { |
int32_t AudioDeviceIOS::StopRecording() { |
LOGI() << "StopRecording"; |
RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
- if (!rec_is_initialized_ || !recording_) { |
+ if (!rec_is_initialized_) { |
+ return 0; |
+ } |
+ if (!recording_) { |
+ rec_is_initialized_ = false; |
return 0; |
} |
if (!playing_) { |
@@ -318,51 +335,24 @@ int AudioDeviceIOS::GetRecordAudioParameters(AudioParameters* params) const { |
} |
void AudioDeviceIOS::OnInterruptionBegin() { |
- RTC_DCHECK(async_invoker_); |
RTC_DCHECK(thread_); |
- if (thread_->IsCurrent()) { |
- HandleInterruptionBegin(); |
- return; |
- } |
- async_invoker_->AsyncInvoke<void>( |
- thread_, |
- rtc::Bind(&webrtc::AudioDeviceIOS::HandleInterruptionBegin, this)); |
+ thread_->Post(this, kMessageTypeInterruptionBegin); |
} |
void AudioDeviceIOS::OnInterruptionEnd() { |
- RTC_DCHECK(async_invoker_); |
RTC_DCHECK(thread_); |
- if (thread_->IsCurrent()) { |
- HandleInterruptionEnd(); |
- return; |
- } |
- async_invoker_->AsyncInvoke<void>( |
- thread_, |
- rtc::Bind(&webrtc::AudioDeviceIOS::HandleInterruptionEnd, this)); |
+ thread_->Post(this, kMessageTypeInterruptionEnd); |
} |
void AudioDeviceIOS::OnValidRouteChange() { |
- RTC_DCHECK(async_invoker_); |
RTC_DCHECK(thread_); |
- if (thread_->IsCurrent()) { |
- HandleValidRouteChange(); |
- return; |
- } |
- async_invoker_->AsyncInvoke<void>( |
- thread_, |
- rtc::Bind(&webrtc::AudioDeviceIOS::HandleValidRouteChange, this)); |
+ thread_->Post(this, kMessageTypeValidRouteChange); |
} |
-void AudioDeviceIOS::OnConfiguredForWebRTC() { |
- RTC_DCHECK(async_invoker_); |
+void AudioDeviceIOS::OnCanPlayOrRecordChange(bool can_play_or_record) { |
RTC_DCHECK(thread_); |
- if (thread_->IsCurrent()) { |
- HandleValidRouteChange(); |
- return; |
- } |
- async_invoker_->AsyncInvoke<void>( |
- thread_, |
- rtc::Bind(&webrtc::AudioDeviceIOS::HandleConfiguredForWebRTC, this)); |
+ thread_->Post(this, kMessageTypeCanPlayOrRecordChange, |
+ new rtc::TypedMessageData<bool>(can_play_or_record)); |
} |
OSStatus AudioDeviceIOS::OnDeliverRecordedData( |
@@ -385,6 +375,9 @@ OSStatus AudioDeviceIOS::OnDeliverRecordedData( |
RTCLogWarning(@"Expected %u frames but got %u", |
static_cast<unsigned int>(frames_per_buffer), |
static_cast<unsigned int>(num_frames)); |
+ |
+ RTCAudioSession *session = [RTCAudioSession sharedInstance]; |
+ RTCLogWarning(@"Session:\n %@", session); |
return result; |
} |
@@ -447,12 +440,36 @@ OSStatus AudioDeviceIOS::OnGetPlayoutData(AudioUnitRenderActionFlags* flags, |
return noErr; |
} |
+void AudioDeviceIOS::OnMessage(rtc::Message *msg) { |
+ switch (msg->message_id) { |
+ case kMessageTypeInterruptionBegin: |
+ HandleInterruptionBegin(); |
+ break; |
+ case kMessageTypeInterruptionEnd: |
+ HandleInterruptionEnd(); |
+ break; |
+ case kMessageTypeValidRouteChange: |
+ HandleValidRouteChange(); |
+ break; |
+ case kMessageTypeCanPlayOrRecordChange: { |
+ rtc::TypedMessageData<bool>* data = |
+ static_cast<rtc::TypedMessageData<bool>*>(msg->pdata); |
+ HandleCanPlayOrRecordChange(data->data()); |
+ delete data; |
+ break; |
+ } |
+ } |
+} |
+ |
void AudioDeviceIOS::HandleInterruptionBegin() { |
RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
- RTCLog(@"Stopping the audio unit due to interruption begin."); |
- if (!audio_unit_->Stop()) { |
- RTCLogError(@"Failed to stop the audio unit."); |
+ if (audio_unit_ && |
+ audio_unit_->GetState() == VoiceProcessingAudioUnit::kStarted) { |
+ RTCLog(@"Stopping the audio unit due to interruption begin."); |
+ if (!audio_unit_->Stop()) { |
+ RTCLogError(@"Failed to stop the audio unit for interruption begin."); |
+ } |
} |
is_interrupted_ = true; |
} |
@@ -460,66 +477,95 @@ void AudioDeviceIOS::HandleInterruptionBegin() { |
void AudioDeviceIOS::HandleInterruptionEnd() { |
RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
- RTCLog(@"Starting the audio unit due to interruption end."); |
- if (!audio_unit_->Start()) { |
- RTCLogError(@"Failed to start the audio unit."); |
- } |
is_interrupted_ = false; |
+ RTCLog(@"Interruption ended. Updating audio unit state."); |
+ UpdateAudioUnit([RTCAudioSession sharedInstance].canPlayOrRecord); |
} |
void AudioDeviceIOS::HandleValidRouteChange() { |
RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
+ RTCAudioSession* session = [RTCAudioSession sharedInstance]; |
+ HandleSampleRateChange(session.sampleRate); |
+} |
+ |
+void AudioDeviceIOS::HandleCanPlayOrRecordChange(bool can_play_or_record) { |
+ RTCLog(@"Handling CanPlayOrRecord change to: %d", can_play_or_record); |
+ UpdateAudioUnit(can_play_or_record); |
+} |
+ |
+void AudioDeviceIOS::HandleSampleRateChange(float sample_rate) { |
+ RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
+ RTCLog(@"Handling sample rate change to %f.", sample_rate); |
+ |
// Don't do anything if we're interrupted. |
if (is_interrupted_) { |
+ RTCLog(@"Ignoring sample rate change to %f due to interruption.", |
+ sample_rate); |
return; |
} |
- // Only restart audio for a valid route change if the session sample rate |
- // has changed. |
- RTCAudioSession* session = [RTCAudioSession sharedInstance]; |
- const double current_sample_rate = playout_parameters_.sample_rate(); |
- const double session_sample_rate = session.sampleRate; |
- if (current_sample_rate != session_sample_rate) { |
- RTCLog(@"Route changed caused sample rate to change from %f to %f. " |
- "Restarting audio unit.", current_sample_rate, session_sample_rate); |
- if (!RestartAudioUnit(session_sample_rate)) { |
- RTCLogError(@"Audio restart failed."); |
- } |
+ // If we don't have an audio unit yet, or the audio unit is uninitialized, |
+ // there is no work to do. |
+ if (!audio_unit_ || |
+ audio_unit_->GetState() < VoiceProcessingAudioUnit::kInitialized) { |
+ return; |
} |
-} |
-void AudioDeviceIOS::HandleConfiguredForWebRTC() { |
- RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
- |
- // If we're not initialized we don't need to do anything. Audio unit will |
- // be initialized on initialization. |
- if (!rec_is_initialized_ && !play_is_initialized_) |
+ // The audio unit is already initialized or started. |
+ // Check to see if the sample rate or buffer size has changed. |
+ RTCAudioSession* session = [RTCAudioSession sharedInstance]; |
+ const double session_sample_rate = session.sampleRate; |
+ const NSTimeInterval session_buffer_duration = session.IOBufferDuration; |
+ const size_t session_frames_per_buffer = |
+ static_cast<size_t>(session_sample_rate * session_buffer_duration + .5); |
+ const double current_sample_rate = playout_parameters_.sample_rate(); |
+ const size_t current_frames_per_buffer = |
+ playout_parameters_.frames_per_buffer(); |
+ RTCLog(@"Handling playout sample rate change to: %f\n" |
+ " Session sample rate: %f frames_per_buffer: %lu\n" |
+ " ADM sample rate: %f frames_per_buffer: %lu", |
+ sample_rate, |
+ session_sample_rate, (unsigned long)session_frames_per_buffer, |
+ current_sample_rate, (unsigned long)current_frames_per_buffer);; |
+ |
+ // Sample rate and buffer size are the same, no work to do. |
+ if (abs(current_sample_rate - session_sample_rate) <= DBL_EPSILON && |
+ current_frames_per_buffer == session_frames_per_buffer) { |
return; |
+ } |
- // If we're initialized, we must have an audio unit. |
- RTC_DCHECK(audio_unit_); |
+ // We need to adjust our format and buffer sizes. |
+ // The stream format is about to be changed and it requires that we first |
+ // stop and uninitialize the audio unit to deallocate its resources. |
+ RTCLog(@"Stopping and uninitializing audio unit to adjust buffers."); |
+ bool restart_audio_unit = false; |
+ if (audio_unit_->GetState() == VoiceProcessingAudioUnit::kStarted) { |
+ audio_unit_->Stop(); |
+ restart_audio_unit = true; |
+ } |
+ if (audio_unit_->GetState() == VoiceProcessingAudioUnit::kInitialized) { |
+ audio_unit_->Uninitialize(); |
+ } |
- // Use configured audio session's settings to set up audio device buffer. |
- // TODO(tkchin): Use RTCAudioSessionConfiguration to pick up settings and |
- // pass it along. |
+ // Allocate new buffers given the new stream format. |
SetupAudioBuffersForActiveAudioSession(); |
- // Initialize the audio unit. This will affect any existing audio playback. |
- if (!audio_unit_->Initialize(playout_parameters_.sample_rate())) { |
- RTCLogError(@"Failed to initialize audio unit after configuration."); |
+ // Initialize the audio unit again with the new sample rate. |
+ RTC_DCHECK_EQ(playout_parameters_.sample_rate(), session_sample_rate); |
+ if (!audio_unit_->Initialize(session_sample_rate)) { |
+ RTCLogError(@"Failed to initialize the audio unit with sample rate: %f", |
+ session_sample_rate); |
return; |
} |
- // If we haven't started playing or recording there's nothing more to do. |
- if (!playing_ && !recording_) |
- return; |
- |
- // We are in a play or record state, start the audio unit. |
- if (!audio_unit_->Start()) { |
- RTCLogError(@"Failed to start audio unit after configuration."); |
+ // Restart the audio unit if it was already running. |
+ if (restart_audio_unit && !audio_unit_->Start()) { |
+ RTCLogError(@"Failed to start audio unit with sample rate: %f", |
+ session_sample_rate); |
return; |
} |
+ RTCLog(@"Successfully handled sample rate change."); |
} |
void AudioDeviceIOS::UpdateAudioDeviceBuffer() { |
@@ -597,6 +643,7 @@ void AudioDeviceIOS::SetupAudioBuffersForActiveAudioSession() { |
// at each input callback when calling AudioUnitRender(). |
const int data_byte_size = record_parameters_.GetBytesPerBuffer(); |
record_audio_buffer_.reset(new SInt8[data_byte_size]); |
+ memset(record_audio_buffer_.get(), 0, data_byte_size); |
audio_record_buffer_list_.mNumberBuffers = 1; |
AudioBuffer* audio_buffer = &audio_record_buffer_list_.mBuffers[0]; |
audio_buffer->mNumberChannels = record_parameters_.channels(); |
@@ -616,46 +663,117 @@ bool AudioDeviceIOS::CreateAudioUnit() { |
return true; |
} |
-bool AudioDeviceIOS::RestartAudioUnit(float sample_rate) { |
- RTCLog(@"Restarting audio unit with new sample rate: %f", sample_rate); |
+void AudioDeviceIOS::UpdateAudioUnit(bool can_play_or_record) { |
+ RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
+ RTCLog(@"Updating audio unit state. CanPlayOrRecord=%d IsInterrupted=%d", |
+ can_play_or_record, is_interrupted_); |
- // Stop the active audio unit. |
- if (!audio_unit_->Stop()) { |
- RTCLogError(@"Failed to stop the audio unit."); |
- return false; |
+ if (is_interrupted_) { |
+ RTCLog(@"Ignoring audio unit update due to interruption."); |
+ return; |
} |
- // The stream format is about to be changed and it requires that we first |
- // uninitialize it to deallocate its resources. |
- if (!audio_unit_->Uninitialize()) { |
- RTCLogError(@"Failed to uninitialize the audio unit."); |
- return false; |
+ // If we're not initialized we don't need to do anything. Audio unit will |
+ // be initialized on initialization. |
+ if (!rec_is_initialized_ && !play_is_initialized_) |
+ return; |
+ |
+ // If we're initialized, we must have an audio unit. |
+ RTC_DCHECK(audio_unit_); |
+ |
+ bool should_initialize_audio_unit = false; |
+ bool should_uninitialize_audio_unit = false; |
+ bool should_start_audio_unit = false; |
+ bool should_stop_audio_unit = false; |
+ |
+ switch (audio_unit_->GetState()) { |
+ case VoiceProcessingAudioUnit::kInitRequired: |
+ RTC_NOTREACHED(); |
+ break; |
+ case VoiceProcessingAudioUnit::kUninitialized: |
+ should_initialize_audio_unit = can_play_or_record; |
+ should_start_audio_unit = should_initialize_audio_unit && |
+ (playing_ || recording_); |
+ break; |
+ case VoiceProcessingAudioUnit::kInitialized: |
+ should_start_audio_unit = |
+ can_play_or_record && (playing_ || recording_); |
+ should_uninitialize_audio_unit = !can_play_or_record; |
+ break; |
+ case VoiceProcessingAudioUnit::kStarted: |
+ RTC_DCHECK(playing_ || recording_); |
+ should_stop_audio_unit = !can_play_or_record; |
+ should_uninitialize_audio_unit = should_stop_audio_unit; |
+ break; |
+ } |
+ |
+ if (should_initialize_audio_unit) { |
+ RTCLog(@"Initializing audio unit for UpdateAudioUnit"); |
+ ConfigureAudioSession(); |
+ SetupAudioBuffersForActiveAudioSession(); |
+ if (!audio_unit_->Initialize(playout_parameters_.sample_rate())) { |
+ RTCLogError(@"Failed to initialize audio unit."); |
+ return; |
+ } |
} |
- // Allocate new buffers given the new stream format. |
- SetupAudioBuffersForActiveAudioSession(); |
+ if (should_start_audio_unit) { |
+ RTCLog(@"Starting audio unit for UpdateAudioUnit"); |
+ if (!audio_unit_->Start()) { |
+ RTCLogError(@"Failed to start audio unit."); |
+ return; |
+ } |
+ } |
- // Initialize the audio unit again with the new sample rate. |
- RTC_DCHECK_EQ(playout_parameters_.sample_rate(), sample_rate); |
- if (!audio_unit_->Initialize(sample_rate)) { |
- RTCLogError(@"Failed to initialize the audio unit with sample rate: %f", |
- sample_rate); |
- return false; |
+ if (should_stop_audio_unit) { |
+ RTCLog(@"Stopping audio unit for UpdateAudioUnit"); |
+ if (!audio_unit_->Stop()) { |
+ RTCLogError(@"Failed to stop audio unit."); |
+ return; |
+ } |
} |
- // Restart the audio unit. |
- if (!audio_unit_->Start()) { |
- RTCLogError(@"Failed to start audio unit."); |
- return false; |
+ if (should_uninitialize_audio_unit) { |
+ RTCLog(@"Uninitializing audio unit for UpdateAudioUnit"); |
+ audio_unit_->Uninitialize(); |
+ UnconfigureAudioSession(); |
} |
- RTCLog(@"Successfully restarted audio unit."); |
+} |
- return true; |
+void AudioDeviceIOS::ConfigureAudioSession() { |
+ RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
+ RTCLog(@"Configuring audio session."); |
+ if (has_configured_session_) { |
+ RTCLogWarning(@"Audio session already configured."); |
+ return; |
+ } |
+ RTCAudioSession* session = [RTCAudioSession sharedInstance]; |
+ [session lockForConfiguration]; |
+ [session configureWebRTCSession:nil]; |
+ [session unlockForConfiguration]; |
+ has_configured_session_ = true; |
+ RTCLog(@"Configured audio session."); |
+} |
+ |
+void AudioDeviceIOS::UnconfigureAudioSession() { |
+ RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
+ RTCLog(@"Unconfiguring audio session."); |
+ if (!has_configured_session_) { |
+ RTCLogWarning(@"Audio session already unconfigured."); |
+ return; |
+ } |
+ RTCAudioSession* session = [RTCAudioSession sharedInstance]; |
+ [session lockForConfiguration]; |
+ [session unconfigureWebRTCSession:nil]; |
+ [session unlockForConfiguration]; |
+ has_configured_session_ = false; |
+ RTCLog(@"Unconfigured audio session."); |
} |
bool AudioDeviceIOS::InitPlayOrRecord() { |
LOGI() << "InitPlayOrRecord"; |
+ // There should be no audio unit at this point. |
if (!CreateAudioUnit()) { |
return false; |
} |
@@ -674,14 +792,11 @@ bool AudioDeviceIOS::InitPlayOrRecord() { |
return false; |
} |
- // If we are already configured properly, we can initialize the audio unit. |
- if (session.isConfiguredForWebRTC) { |
- [session unlockForConfiguration]; |
+ // If we are ready to play or record, initialize the audio unit. |
+ if (session.canPlayOrRecord) { |
+ ConfigureAudioSession(); |
SetupAudioBuffersForActiveAudioSession(); |
- // Audio session has been marked ready for WebRTC so we can initialize the |
- // audio unit now. |
audio_unit_->Initialize(playout_parameters_.sample_rate()); |
- return true; |
} |
// Release the lock. |
@@ -694,9 +809,7 @@ void AudioDeviceIOS::ShutdownPlayOrRecord() { |
LOGI() << "ShutdownPlayOrRecord"; |
// Close and delete the voice-processing I/O unit. |
- if (audio_unit_) { |
- audio_unit_.reset(); |
- } |
+ audio_unit_.reset(); |
// Remove audio session notification observers. |
RTCAudioSession* session = [RTCAudioSession sharedInstance]; |
@@ -705,6 +818,7 @@ void AudioDeviceIOS::ShutdownPlayOrRecord() { |
// All I/O should be stopped or paused prior to deactivating the audio |
// session, hence we deactivate as last action. |
[session lockForConfiguration]; |
+ UnconfigureAudioSession(); |
[session endWebRTCSession:nil]; |
[session unlockForConfiguration]; |
} |