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]; |
} |