Chromium Code Reviews| 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..3b566adad13e2c6f9d0ef0fea6641f1e5fbd3805 100644 |
| --- a/webrtc/modules/audio_device/ios/audio_device_ios.mm |
| +++ b/webrtc/modules/audio_device/ios/audio_device_ios.mm |
| @@ -61,6 +61,14 @@ namespace webrtc { |
| const UInt16 kFixedPlayoutDelayEstimate = 30; |
| const UInt16 kFixedRecordDelayEstimate = 30; |
| +enum AudioDeviceMessageType : uint32_t { |
| + kMessageTypeInterruptionBegin, |
| + kMessageTypeInterruptionEnd, |
| + kMessageTypeValidRouteChange, |
| + kMessageTypeCanPlayOrRecordChange, |
| + kMessageTypeSampleRateChange, |
| +}; |
| + |
| using ios::CheckAndLogError; |
| #if !defined(NDEBUG) |
| @@ -85,15 +93,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_ = |
| @@ -318,51 +326,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 +366,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,6 +431,40 @@ OSStatus AudioDeviceIOS::OnGetPlayoutData(AudioUnitRenderActionFlags* flags, |
| return noErr; |
| } |
| +void AudioDeviceIOS::OnSampleRateChange(float sample_rate) { |
| + RTC_DCHECK(thread_); |
| + thread_->Post(this, kMessageTypeSampleRateChange, |
| + new rtc::TypedMessageData<float>(sample_rate)); |
| +} |
| + |
| +void AudioDeviceIOS::OnMessage(rtc::Message *msg) { |
|
henrika_webrtc
2016/05/04 12:33:07
nice!
tkchin_webrtc
2016/05/05 23:23:27
Acknowledged.
|
| + 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; |
| + } |
| + case kMessageTypeSampleRateChange: { |
| + rtc::TypedMessageData<float>* data = |
| + static_cast<rtc::TypedMessageData<float>*>(msg->pdata); |
| + HandleSampleRateChange(data->data()); |
| + delete data; |
| + break; |
| + } |
| + } |
| +} |
| + |
| void AudioDeviceIOS::HandleInterruptionBegin() { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| @@ -470,27 +488,13 @@ void AudioDeviceIOS::HandleInterruptionEnd() { |
| void AudioDeviceIOS::HandleValidRouteChange() { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| - // Don't do anything if we're interrupted. |
| - if (is_interrupted_) { |
| - 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."); |
| - } |
| - } |
| + HandleSampleRateChange(session.sampleRate); |
| } |
| -void AudioDeviceIOS::HandleConfiguredForWebRTC() { |
| +void AudioDeviceIOS::HandleCanPlayOrRecordChange(bool can_play_or_record) { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| + RTCLog(@"Handling CanPlayOrRecordChange to %d", can_play_or_record); |
| // If we're not initialized we don't need to do anything. Audio unit will |
| // be initialized on initialization. |
| @@ -500,26 +504,111 @@ void AudioDeviceIOS::HandleConfiguredForWebRTC() { |
| // If we're initialized, we must have an audio unit. |
| RTC_DCHECK(audio_unit_); |
| - // Use configured audio session's settings to set up audio device buffer. |
| - // TODO(tkchin): Use RTCAudioSessionConfiguration to pick up settings and |
| - // pass it along. |
| - SetupAudioBuffersForActiveAudioSession(); |
| + 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 CanPlayOrRecordChange"); |
| + // Mad hack: Sometimes the audio session is already active because the app |
|
henrika_webrtc
2016/05/04 12:33:07
Wow. Do you have a case where you can reliably tes
tkchin_webrtc
2016/05/05 23:23:27
Yup. With my BT headset. I'm backing out the chang
henrika_webrtc
2016/05/06 11:22:16
Acknowledged.
|
| + // activated it. In this condition, the audio session reports an inaccurate |
| + // IOBufferDuration until the audio unit is started, but this causes audio |
| + // artifacts. To work around it, deactivate it forcibly and reactivate it. |
| + // This only works if the app has only activated the session once. |
| + RTCAudioSession *session = [RTCAudioSession sharedInstance]; |
| + BOOL refreshAudioSession = session.isActive; |
| + if (refreshAudioSession) { |
| + [session lockForConfiguration]; |
| + [session setActive:NO error:nil]; |
| + [session unlockForConfiguration]; |
| + } |
| + ConfigureAudioSession(); |
| + if (refreshAudioSession) { |
| + [session lockForConfiguration]; |
| + [session setActive:YES error:nil]; |
| + [session unlockForConfiguration]; |
| + } |
| + SetupAudioBuffersForActiveAudioSession(); |
| + if (!audio_unit_->Initialize(playout_parameters_.sample_rate())) { |
| + RTCLogError(@"Failed to initialize audio unit."); |
| + return; |
| + } |
| + } |
| - // 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."); |
| - return; |
| + if (should_start_audio_unit) { |
| + RTCLog(@"Starting audio unit for CanPlayOrRecordChange"); |
| + if (!audio_unit_->Start()) { |
| + RTCLogError(@"Failed to start audio unit."); |
| + return; |
| + } |
| } |
| - // If we haven't started playing or recording there's nothing more to do. |
| - if (!playing_ && !recording_) |
| - return; |
| + if (should_stop_audio_unit) { |
| + RTCLog(@"Stopping audio unit for CanPlayOrRecordChange"); |
| + if (!audio_unit_->Stop()) { |
| + RTCLogError(@"Failed to stop audio unit."); |
| + 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."); |
| + if (should_uninitialize_audio_unit) { |
| + RTCLog(@"Uninitializing audio unit for CanPlayOrRecordChange"); |
| + audio_unit_->Uninitialize(); |
| + UnconfigureAudioSession(); |
| + } |
| +} |
| + |
| +void AudioDeviceIOS::HandleSampleRateChange(float sample_rate) { |
| + RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| + |
| + // Don't do anything if we're interrupted. |
| + if (is_interrupted_) { |
| return; |
| } |
| + |
| + 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);; |
| + |
| + if (current_sample_rate != session_sample_rate || |
| + current_frames_per_buffer != session_frames_per_buffer) { |
| + RTCLog(@"Restarting audio unit due to frames per buffer change."); |
| + if (!RestartAudioUnit(sample_rate)) { |
| + RTCLogError(@"Audio restart failed."); |
| + } |
| + } |
| } |
| void AudioDeviceIOS::UpdateAudioDeviceBuffer() { |
| @@ -597,6 +686,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(); |
| @@ -653,9 +743,40 @@ bool AudioDeviceIOS::RestartAudioUnit(float sample_rate) { |
| 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 +795,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,17 +812,16 @@ 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]; |
| + // Remove audio session notification observers. |
| [session removeDelegate:audio_session_observer_]; |
| // 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]; |
| } |