1/*
2 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#if !defined(__has_feature) || !__has_feature(objc_arc)
12#error "This file requires ARC support."
13#endif
14
15#import <AVFoundation/AVFoundation.h>
16#import <Foundation/Foundation.h>
17
18#include "webrtc/modules/audio_device/ios/audio_device_ios.h"
19
20#include "webrtc/base/atomicops.h"
21#include "webrtc/base/bind.h"
22#include "webrtc/base/checks.h"
23#include "webrtc/base/criticalsection.h"
24#include "webrtc/base/logging.h"
25#include "webrtc/base/thread.h"
26#include "webrtc/base/thread_annotations.h"
27#include "webrtc/modules/audio_device/fine_audio_buffer.h"
28#include "webrtc/sdk/objc/Framework/Classes/helpers.h"
29
30#import "WebRTC/RTCLogging.h"
31#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession.h"
32#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession+Private.h"
33#import "webrtc/modules/audio_device/ios/objc/RTCAudioSessionConfiguration.h"
34#import "webrtc/modules/audio_device/ios/objc/RTCAudioSessionDelegateAdapter.h"
35
36namespace webrtc {
37
38#define LOGI() LOG(LS_INFO) << "AudioDeviceIOS::"
39
40#define LOG_AND_RETURN_IF_ERROR(error, message) \
41  do {                                          \
42    OSStatus err = error;                       \
43    if (err) {                                  \
44      LOG(LS_ERROR) << message << ": " << err;  \
45      return false;                             \
46    }                                           \
47  } while (0)
48
49#define LOG_IF_ERROR(error, message)           \
50  do {                                         \
51    OSStatus err = error;                      \
52    if (err) {                                 \
53      LOG(LS_ERROR) << message << ": " << err; \
54    }                                          \
55  } while (0)
56
57
58// Hardcoded delay estimates based on real measurements.
59// TODO(henrika): these value is not used in combination with built-in AEC.
60// Can most likely be removed.
61const UInt16 kFixedPlayoutDelayEstimate = 30;
62const UInt16 kFixedRecordDelayEstimate = 30;
63
64enum AudioDeviceMessageType : uint32_t {
65  kMessageTypeInterruptionBegin,
66  kMessageTypeInterruptionEnd,
67  kMessageTypeValidRouteChange,
68  kMessageTypeCanPlayOrRecordChange,
69};
70
71using ios::CheckAndLogError;
72
73#if !defined(NDEBUG)
74// Helper method that logs essential device information strings.
75static void LogDeviceInfo() {
76  LOG(LS_INFO) << "LogDeviceInfo";
77  @autoreleasepool {
78    LOG(LS_INFO) << " system name: " << ios::GetSystemName();
79    LOG(LS_INFO) << " system version 1(2): " << ios::GetSystemVersionAsString();
80    LOG(LS_INFO) << " system version 2(2): " << ios::GetSystemVersion();
81    LOG(LS_INFO) << " device type: " << ios::GetDeviceType();
82    LOG(LS_INFO) << " device name: " << ios::GetDeviceName();
83    LOG(LS_INFO) << " process name: " << ios::GetProcessName();
84    LOG(LS_INFO) << " process ID: " << ios::GetProcessID();
85    LOG(LS_INFO) << " OS version: " << ios::GetOSVersionString();
86    LOG(LS_INFO) << " processing cores: " << ios::GetProcessorCount();
87#if defined(__IPHONE_9_0) && defined(__IPHONE_OS_VERSION_MAX_ALLOWED) \
88    && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0
89    LOG(LS_INFO) << " low power mode: " << ios::GetLowPowerModeEnabled();
90#endif
91  }
92}
93#endif  // !defined(NDEBUG)
94
95AudioDeviceIOS::AudioDeviceIOS()
96    : audio_device_buffer_(nullptr),
97      audio_unit_(nullptr),
98      recording_(0),
99      playing_(0),
100      initialized_(false),
101      audio_is_initialized_(false),
102      is_interrupted_(false),
103      has_configured_session_(false) {
104  LOGI() << "ctor" << ios::GetCurrentThreadDescription();
105  thread_ = rtc::Thread::Current();
106  audio_session_observer_ =
107      [[RTCAudioSessionDelegateAdapter alloc] initWithObserver:this];
108}
109
110AudioDeviceIOS::~AudioDeviceIOS() {
111  LOGI() << "~dtor" << ios::GetCurrentThreadDescription();
112  audio_session_observer_ = nil;
113  RTC_DCHECK(thread_checker_.CalledOnValidThread());
114  Terminate();
115}
116
117void AudioDeviceIOS::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
118  LOGI() << "AttachAudioBuffer";
119  RTC_DCHECK(audioBuffer);
120  RTC_DCHECK(thread_checker_.CalledOnValidThread());
121  audio_device_buffer_ = audioBuffer;
122}
123
124AudioDeviceGeneric::InitStatus AudioDeviceIOS::Init() {
125  LOGI() << "Init";
126  RTC_DCHECK(thread_checker_.CalledOnValidThread());
127  if (initialized_) {
128    return InitStatus::OK;
129  }
130#if !defined(NDEBUG)
131  LogDeviceInfo();
132#endif
133  // Store the preferred sample rate and preferred number of channels already
134  // here. They have not been set and confirmed yet since configureForWebRTC
135  // is not called until audio is about to start. However, it makes sense to
136  // store the parameters now and then verify at a later stage.
137  RTCAudioSessionConfiguration* config =
138      [RTCAudioSessionConfiguration webRTCConfiguration];
139  playout_parameters_.reset(config.sampleRate,
140                            config.outputNumberOfChannels);
141  record_parameters_.reset(config.sampleRate,
142                           config.inputNumberOfChannels);
143  // Ensure that the audio device buffer (ADB) knows about the internal audio
144  // parameters. Note that, even if we are unable to get a mono audio session,
145  // we will always tell the I/O audio unit to do a channel format conversion
146  // to guarantee mono on the "input side" of the audio unit.
147  UpdateAudioDeviceBuffer();
148  initialized_ = true;
149  return InitStatus::OK;
150}
151
152int32_t AudioDeviceIOS::Terminate() {
153  LOGI() << "Terminate";
154  RTC_DCHECK(thread_checker_.CalledOnValidThread());
155  if (!initialized_) {
156    return 0;
157  }
158  StopPlayout();
159  StopRecording();
160  initialized_ = false;
161  return 0;
162}
163
164int32_t AudioDeviceIOS::InitPlayout() {
165  LOGI() << "InitPlayout";
166  RTC_DCHECK(thread_checker_.CalledOnValidThread());
167  RTC_DCHECK(initialized_);
168  RTC_DCHECK(!audio_is_initialized_);
169  RTC_DCHECK(!playing_);
170  if (!audio_is_initialized_) {
171    if (!InitPlayOrRecord()) {
172      LOG_F(LS_ERROR) << "InitPlayOrRecord failed for InitPlayout!";
173      return -1;
174    }
175  }
176  audio_is_initialized_ = true;
177  return 0;
178}
179
180int32_t AudioDeviceIOS::InitRecording() {
181  LOGI() << "InitRecording";
182  RTC_DCHECK(thread_checker_.CalledOnValidThread());
183  RTC_DCHECK(initialized_);
184  RTC_DCHECK(!audio_is_initialized_);
185  RTC_DCHECK(!recording_);
186  if (!audio_is_initialized_) {
187    if (!InitPlayOrRecord()) {
188      LOG_F(LS_ERROR) << "InitPlayOrRecord failed for InitRecording!";
189      return -1;
190    }
191  }
192  audio_is_initialized_ = true;
193  return 0;
194}
195
196int32_t AudioDeviceIOS::StartPlayout() {
197  LOGI() << "StartPlayout";
198  RTC_DCHECK(thread_checker_.CalledOnValidThread());
199  RTC_DCHECK(audio_is_initialized_);
200  RTC_DCHECK(!playing_);
201  RTC_DCHECK(audio_unit_);
202  if (fine_audio_buffer_) {
203    fine_audio_buffer_->ResetPlayout();
204  }
205  if (!recording_ &&
206      audio_unit_->GetState() == VoiceProcessingAudioUnit::kInitialized) {
207    if (!audio_unit_->Start()) {
208      RTCLogError(@"StartPlayout failed to start audio unit.");
209      return -1;
210    }
211    LOG(LS_INFO) << "Voice-Processing I/O audio unit is now started";
212  }
213  rtc::AtomicOps::ReleaseStore(&playing_, 1);
214  return 0;
215}
216
217int32_t AudioDeviceIOS::StopPlayout() {
218  LOGI() << "StopPlayout";
219  RTC_DCHECK(thread_checker_.CalledOnValidThread());
220  if (!audio_is_initialized_ || !playing_) {
221    return 0;
222  }
223  if (!recording_) {
224    ShutdownPlayOrRecord();
225    audio_is_initialized_ = false;
226  }
227  rtc::AtomicOps::ReleaseStore(&playing_, 0);
228  return 0;
229}
230
231int32_t AudioDeviceIOS::StartRecording() {
232  LOGI() << "StartRecording";
233  RTC_DCHECK(thread_checker_.CalledOnValidThread());
234  RTC_DCHECK(audio_is_initialized_);
235  RTC_DCHECK(!recording_);
236  RTC_DCHECK(audio_unit_);
237  if (fine_audio_buffer_) {
238    fine_audio_buffer_->ResetRecord();
239  }
240  if (!playing_ &&
241      audio_unit_->GetState() == VoiceProcessingAudioUnit::kInitialized) {
242    if (!audio_unit_->Start()) {
243      RTCLogError(@"StartRecording failed to start audio unit.");
244      return -1;
245    }
246    LOG(LS_INFO) << "Voice-Processing I/O audio unit is now started";
247  }
248  rtc::AtomicOps::ReleaseStore(&recording_, 1);
249  return 0;
250}
251
252int32_t AudioDeviceIOS::StopRecording() {
253  LOGI() << "StopRecording";
254  RTC_DCHECK(thread_checker_.CalledOnValidThread());
255  if (!audio_is_initialized_ || !recording_) {
256    return 0;
257  }
258  if (!playing_) {
259    ShutdownPlayOrRecord();
260    audio_is_initialized_ = false;
261  }
262  rtc::AtomicOps::ReleaseStore(&recording_, 0);
263  return 0;
264}
265
266// Change the default receiver playout route to speaker.
267int32_t AudioDeviceIOS::SetLoudspeakerStatus(bool enable) {
268  LOGI() << "SetLoudspeakerStatus(" << enable << ")";
269
270  RTCAudioSession* session = [RTCAudioSession sharedInstance];
271  [session lockForConfiguration];
272  NSString* category = session.category;
273  AVAudioSessionCategoryOptions options = session.categoryOptions;
274  // Respect old category options if category is
275  // AVAudioSessionCategoryPlayAndRecord. Otherwise reset it since old options
276  // might not be valid for this category.
277  if ([category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) {
278    if (enable) {
279      options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
280    } else {
281      options &= ~AVAudioSessionCategoryOptionDefaultToSpeaker;
282    }
283  } else {
284    options = AVAudioSessionCategoryOptionDefaultToSpeaker;
285  }
286  NSError* error = nil;
287  BOOL success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
288                          withOptions:options
289                                error:&error];
290  ios::CheckAndLogError(success, error);
291  [session unlockForConfiguration];
292  return (error == nil) ? 0 : -1;
293}
294
295int32_t AudioDeviceIOS::GetLoudspeakerStatus(bool& enabled) const {
296  LOGI() << "GetLoudspeakerStatus";
297  RTCAudioSession* session = [RTCAudioSession sharedInstance];
298  AVAudioSessionCategoryOptions options = session.categoryOptions;
299  enabled = options & AVAudioSessionCategoryOptionDefaultToSpeaker;
300  return 0;
301}
302
303int32_t AudioDeviceIOS::PlayoutDelay(uint16_t& delayMS) const {
304  delayMS = kFixedPlayoutDelayEstimate;
305  return 0;
306}
307
308int32_t AudioDeviceIOS::RecordingDelay(uint16_t& delayMS) const {
309  delayMS = kFixedRecordDelayEstimate;
310  return 0;
311}
312
313int AudioDeviceIOS::GetPlayoutAudioParameters(AudioParameters* params) const {
314  LOGI() << "GetPlayoutAudioParameters";
315  RTC_DCHECK(playout_parameters_.is_valid());
316  RTC_DCHECK(thread_checker_.CalledOnValidThread());
317  *params = playout_parameters_;
318  return 0;
319}
320
321int AudioDeviceIOS::GetRecordAudioParameters(AudioParameters* params) const {
322  LOGI() << "GetRecordAudioParameters";
323  RTC_DCHECK(record_parameters_.is_valid());
324  RTC_DCHECK(thread_checker_.CalledOnValidThread());
325  *params = record_parameters_;
326  return 0;
327}
328
329void AudioDeviceIOS::OnInterruptionBegin() {
330  RTC_DCHECK(thread_);
331  thread_->Post(RTC_FROM_HERE, this, kMessageTypeInterruptionBegin);
332}
333
334void AudioDeviceIOS::OnInterruptionEnd() {
335  RTC_DCHECK(thread_);
336  thread_->Post(RTC_FROM_HERE, this, kMessageTypeInterruptionEnd);
337}
338
339void AudioDeviceIOS::OnValidRouteChange() {
340  RTC_DCHECK(thread_);
341  thread_->Post(RTC_FROM_HERE, this, kMessageTypeValidRouteChange);
342}
343
344void AudioDeviceIOS::OnCanPlayOrRecordChange(bool can_play_or_record) {
345  RTC_DCHECK(thread_);
346  thread_->Post(RTC_FROM_HERE, this, kMessageTypeCanPlayOrRecordChange,
347                new rtc::TypedMessageData<bool>(can_play_or_record));
348}
349
350OSStatus AudioDeviceIOS::OnDeliverRecordedData(
351    AudioUnitRenderActionFlags* flags,
352    const AudioTimeStamp* time_stamp,
353    UInt32 bus_number,
354    UInt32 num_frames,
355    AudioBufferList* /* io_data */) {
356  OSStatus result = noErr;
357  // Simply return if recording is not enabled.
358  if (!rtc::AtomicOps::AcquireLoad(&recording_))
359    return result;
360
361  size_t frames_per_buffer = record_parameters_.frames_per_buffer();
362  if (num_frames != frames_per_buffer) {
363    // We have seen short bursts (1-2 frames) where |in_number_frames| changes.
364    // Add a log to keep track of longer sequences if that should ever happen.
365    // Also return since calling AudioUnitRender in this state will only result
366    // in kAudio_ParamError (-50) anyhow.
367    RTCLogWarning(@"Expected %u frames but got %u",
368                  static_cast<unsigned int>(frames_per_buffer),
369                  static_cast<unsigned int>(num_frames));
370
371    RTCAudioSession *session = [RTCAudioSession sharedInstance];
372    RTCLogWarning(@"Session:\n %@", session);
373    return result;
374  }
375
376  // Obtain the recorded audio samples by initiating a rendering cycle.
377  // Since it happens on the input bus, the |io_data| parameter is a reference
378  // to the preallocated audio buffer list that the audio unit renders into.
379  // We can make the audio unit provide a buffer instead in io_data, but we
380  // currently just use our own.
381  // TODO(henrika): should error handling be improved?
382  AudioBufferList* io_data = &audio_record_buffer_list_;
383  result =
384      audio_unit_->Render(flags, time_stamp, bus_number, num_frames, io_data);
385  if (result != noErr) {
386    RTCLogError(@"Failed to render audio.");
387    return result;
388  }
389
390  // Get a pointer to the recorded audio and send it to the WebRTC ADB.
391  // Use the FineAudioBuffer instance to convert between native buffer size
392  // and the 10ms buffer size used by WebRTC.
393  AudioBuffer* audio_buffer = &io_data->mBuffers[0];
394  const size_t size_in_bytes = audio_buffer->mDataByteSize;
395  RTC_CHECK_EQ(size_in_bytes / VoiceProcessingAudioUnit::kBytesPerSample,
396               num_frames);
397  int8_t* data = static_cast<int8_t*>(audio_buffer->mData);
398  fine_audio_buffer_->DeliverRecordedData(data, size_in_bytes,
399                                          kFixedPlayoutDelayEstimate,
400                                          kFixedRecordDelayEstimate);
401  return noErr;
402}
403
404OSStatus AudioDeviceIOS::OnGetPlayoutData(AudioUnitRenderActionFlags* flags,
405                                          const AudioTimeStamp* time_stamp,
406                                          UInt32 bus_number,
407                                          UInt32 num_frames,
408                                          AudioBufferList* io_data) {
409  // Verify 16-bit, noninterleaved mono PCM signal format.
410  RTC_DCHECK_EQ(1, io_data->mNumberBuffers);
411  AudioBuffer* audio_buffer = &io_data->mBuffers[0];
412  RTC_DCHECK_EQ(1, audio_buffer->mNumberChannels);
413  // Get pointer to internal audio buffer to which new audio data shall be
414  // written.
415  const size_t size_in_bytes = audio_buffer->mDataByteSize;
416  RTC_CHECK_EQ(size_in_bytes / VoiceProcessingAudioUnit::kBytesPerSample,
417               num_frames);
418  int8_t* destination = reinterpret_cast<int8_t*>(audio_buffer->mData);
419  // Produce silence and give audio unit a hint about it if playout is not
420  // activated.
421  if (!rtc::AtomicOps::AcquireLoad(&playing_)) {
422    *flags |= kAudioUnitRenderAction_OutputIsSilence;
423    memset(destination, 0, size_in_bytes);
424    return noErr;
425  }
426  // Produce silence and log a warning message for the case when Core Audio is
427  // asking for an invalid number of audio frames. I don't expect this to happen
428  // but it is done as a safety measure to avoid bad audio if such as case would
429  // ever be triggered e.g. in combination with BT devices.
430  const size_t frames_per_buffer = playout_parameters_.frames_per_buffer();
431  if (num_frames != frames_per_buffer) {
432    RTCLogWarning(@"Expected %u frames but got %u",
433                  static_cast<unsigned int>(frames_per_buffer),
434                  static_cast<unsigned int>(num_frames));
435    *flags |= kAudioUnitRenderAction_OutputIsSilence;
436    memset(destination, 0, size_in_bytes);
437    return noErr;
438  }
439
440  // Read decoded 16-bit PCM samples from WebRTC (using a size that matches
441  // the native I/O audio unit) to a preallocated intermediate buffer and
442  // copy the result to the audio buffer in the |io_data| destination.
443  int8_t* source = playout_audio_buffer_.get();
444  fine_audio_buffer_->GetPlayoutData(source);
445  memcpy(destination, source, size_in_bytes);
446  return noErr;
447}
448
449void AudioDeviceIOS::OnMessage(rtc::Message *msg) {
450  switch (msg->message_id) {
451    case kMessageTypeInterruptionBegin:
452      HandleInterruptionBegin();
453      break;
454    case kMessageTypeInterruptionEnd:
455      HandleInterruptionEnd();
456      break;
457    case kMessageTypeValidRouteChange:
458      HandleValidRouteChange();
459      break;
460    case kMessageTypeCanPlayOrRecordChange: {
461      rtc::TypedMessageData<bool>* data =
462          static_cast<rtc::TypedMessageData<bool>*>(msg->pdata);
463      HandleCanPlayOrRecordChange(data->data());
464      delete data;
465      break;
466    }
467  }
468}
469
470void AudioDeviceIOS::HandleInterruptionBegin() {
471  RTC_DCHECK(thread_checker_.CalledOnValidThread());
472
473  if (audio_unit_ &&
474      audio_unit_->GetState() == VoiceProcessingAudioUnit::kStarted) {
475    RTCLog(@"Stopping the audio unit due to interruption begin.");
476    if (!audio_unit_->Stop()) {
477      RTCLogError(@"Failed to stop the audio unit for interruption begin.");
478    }
479  }
480  is_interrupted_ = true;
481}
482
483void AudioDeviceIOS::HandleInterruptionEnd() {
484  RTC_DCHECK(thread_checker_.CalledOnValidThread());
485
486  is_interrupted_ = false;
487  RTCLog(@"Interruption ended. Updating audio unit state.");
488  UpdateAudioUnit([RTCAudioSession sharedInstance].canPlayOrRecord);
489}
490
491void AudioDeviceIOS::HandleValidRouteChange() {
492  RTC_DCHECK(thread_checker_.CalledOnValidThread());
493
494  RTCAudioSession* session = [RTCAudioSession sharedInstance];
495  RTCLog(@"%@", session);
496  HandleSampleRateChange(session.sampleRate);
497}
498
499void AudioDeviceIOS::HandleCanPlayOrRecordChange(bool can_play_or_record) {
500  RTCLog(@"Handling CanPlayOrRecord change to: %d", can_play_or_record);
501  UpdateAudioUnit(can_play_or_record);
502}
503
504void AudioDeviceIOS::HandleSampleRateChange(float sample_rate) {
505  RTC_DCHECK(thread_checker_.CalledOnValidThread());
506  RTCLog(@"Handling sample rate change to %f.", sample_rate);
507
508  // Don't do anything if we're interrupted.
509  if (is_interrupted_) {
510    RTCLog(@"Ignoring sample rate change to %f due to interruption.",
511           sample_rate);
512    return;
513  }
514
515  // If we don't have an audio unit yet, or the audio unit is uninitialized,
516  // there is no work to do.
517  if (!audio_unit_ ||
518      audio_unit_->GetState() < VoiceProcessingAudioUnit::kInitialized) {
519    return;
520  }
521
522  // The audio unit is already initialized or started.
523  // Check to see if the sample rate or buffer size has changed.
524  RTCAudioSession* session = [RTCAudioSession sharedInstance];
525  const double session_sample_rate = session.sampleRate;
526  const NSTimeInterval session_buffer_duration = session.IOBufferDuration;
527  const size_t session_frames_per_buffer =
528      static_cast<size_t>(session_sample_rate * session_buffer_duration + .5);
529  const double current_sample_rate = playout_parameters_.sample_rate();
530  const size_t current_frames_per_buffer =
531      playout_parameters_.frames_per_buffer();
532  RTCLog(@"Handling playout sample rate change to: %f\n"
533          "  Session sample rate: %f frames_per_buffer: %lu\n"
534          "  ADM sample rate: %f frames_per_buffer: %lu",
535         sample_rate,
536         session_sample_rate, (unsigned long)session_frames_per_buffer,
537         current_sample_rate, (unsigned long)current_frames_per_buffer);;
538
539  // Sample rate and buffer size are the same, no work to do.
540  if (std::abs(current_sample_rate - session_sample_rate) <= DBL_EPSILON &&
541      current_frames_per_buffer == session_frames_per_buffer) {
542    return;
543  }
544
545  // We need to adjust our format and buffer sizes.
546  // The stream format is about to be changed and it requires that we first
547  // stop and uninitialize the audio unit to deallocate its resources.
548  RTCLog(@"Stopping and uninitializing audio unit to adjust buffers.");
549  bool restart_audio_unit = false;
550  if (audio_unit_->GetState() == VoiceProcessingAudioUnit::kStarted) {
551    audio_unit_->Stop();
552    restart_audio_unit = true;
553  }
554  if (audio_unit_->GetState() == VoiceProcessingAudioUnit::kInitialized) {
555    audio_unit_->Uninitialize();
556  }
557
558  // Allocate new buffers given the new stream format.
559  SetupAudioBuffersForActiveAudioSession();
560
561  // Initialize the audio unit again with the new sample rate.
562  RTC_DCHECK_EQ(playout_parameters_.sample_rate(), session_sample_rate);
563  if (!audio_unit_->Initialize(session_sample_rate)) {
564    RTCLogError(@"Failed to initialize the audio unit with sample rate: %f",
565                session_sample_rate);
566    return;
567  }
568
569  // Restart the audio unit if it was already running.
570  if (restart_audio_unit && !audio_unit_->Start()) {
571    RTCLogError(@"Failed to start audio unit with sample rate: %f",
572                session_sample_rate);
573    return;
574  }
575  RTCLog(@"Successfully handled sample rate change.");
576}
577
578void AudioDeviceIOS::UpdateAudioDeviceBuffer() {
579  LOGI() << "UpdateAudioDevicebuffer";
580  // AttachAudioBuffer() is called at construction by the main class but check
581  // just in case.
582  RTC_DCHECK(audio_device_buffer_) << "AttachAudioBuffer must be called first";
583  // Inform the audio device buffer (ADB) about the new audio format.
584  audio_device_buffer_->SetPlayoutSampleRate(playout_parameters_.sample_rate());
585  audio_device_buffer_->SetPlayoutChannels(playout_parameters_.channels());
586  audio_device_buffer_->SetRecordingSampleRate(
587      record_parameters_.sample_rate());
588  audio_device_buffer_->SetRecordingChannels(record_parameters_.channels());
589}
590
591void AudioDeviceIOS::SetupAudioBuffersForActiveAudioSession() {
592  LOGI() << "SetupAudioBuffersForActiveAudioSession";
593  // Verify the current values once the audio session has been activated.
594  RTCAudioSession* session = [RTCAudioSession sharedInstance];
595  double sample_rate = session.sampleRate;
596  NSTimeInterval io_buffer_duration = session.IOBufferDuration;
597  RTCLog(@"%@", session);
598
599  // Log a warning message for the case when we are unable to set the preferred
600  // hardware sample rate but continue and use the non-ideal sample rate after
601  // reinitializing the audio parameters. Most BT headsets only support 8kHz or
602  // 16kHz.
603  RTCAudioSessionConfiguration* webRTCConfig =
604      [RTCAudioSessionConfiguration webRTCConfiguration];
605  if (sample_rate != webRTCConfig.sampleRate) {
606    LOG(LS_WARNING) << "Unable to set the preferred sample rate";
607  }
608
609  // At this stage, we also know the exact IO buffer duration and can add
610  // that info to the existing audio parameters where it is converted into
611  // number of audio frames.
612  // Example: IO buffer size = 0.008 seconds <=> 128 audio frames at 16kHz.
613  // Hence, 128 is the size we expect to see in upcoming render callbacks.
614  playout_parameters_.reset(sample_rate, playout_parameters_.channels(),
615                            io_buffer_duration);
616  RTC_DCHECK(playout_parameters_.is_complete());
617  record_parameters_.reset(sample_rate, record_parameters_.channels(),
618                           io_buffer_duration);
619  RTC_DCHECK(record_parameters_.is_complete());
620  LOG(LS_INFO) << " frames per I/O buffer: "
621               << playout_parameters_.frames_per_buffer();
622  LOG(LS_INFO) << " bytes per I/O buffer: "
623               << playout_parameters_.GetBytesPerBuffer();
624  RTC_DCHECK_EQ(playout_parameters_.GetBytesPerBuffer(),
625                record_parameters_.GetBytesPerBuffer());
626
627  // Update the ADB parameters since the sample rate might have changed.
628  UpdateAudioDeviceBuffer();
629
630  // Create a modified audio buffer class which allows us to ask for,
631  // or deliver, any number of samples (and not only multiple of 10ms) to match
632  // the native audio unit buffer size.
633  RTC_DCHECK(audio_device_buffer_);
634  fine_audio_buffer_.reset(new FineAudioBuffer(
635      audio_device_buffer_, playout_parameters_.GetBytesPerBuffer(),
636      playout_parameters_.sample_rate()));
637
638  // The extra/temporary playoutbuffer must be of this size to avoid
639  // unnecessary memcpy while caching data between successive callbacks.
640  const int required_playout_buffer_size =
641      fine_audio_buffer_->RequiredPlayoutBufferSizeBytes();
642  LOG(LS_INFO) << " required playout buffer size: "
643               << required_playout_buffer_size;
644  playout_audio_buffer_.reset(new SInt8[required_playout_buffer_size]);
645
646  // Allocate AudioBuffers to be used as storage for the received audio.
647  // The AudioBufferList structure works as a placeholder for the
648  // AudioBuffer structure, which holds a pointer to the actual data buffer
649  // in |record_audio_buffer_|. Recorded audio will be rendered into this memory
650  // at each input callback when calling AudioUnitRender().
651  const int data_byte_size = record_parameters_.GetBytesPerBuffer();
652  record_audio_buffer_.reset(new SInt8[data_byte_size]);
653  memset(record_audio_buffer_.get(), 0, data_byte_size);
654  audio_record_buffer_list_.mNumberBuffers = 1;
655  AudioBuffer* audio_buffer = &audio_record_buffer_list_.mBuffers[0];
656  audio_buffer->mNumberChannels = record_parameters_.channels();
657  audio_buffer->mDataByteSize = data_byte_size;
658  audio_buffer->mData = record_audio_buffer_.get();
659}
660
661bool AudioDeviceIOS::CreateAudioUnit() {
662  RTC_DCHECK(!audio_unit_);
663
664  audio_unit_.reset(new VoiceProcessingAudioUnit(this));
665  if (!audio_unit_->Init()) {
666    audio_unit_.reset();
667    return false;
668  }
669
670  return true;
671}
672
673void AudioDeviceIOS::UpdateAudioUnit(bool can_play_or_record) {
674  RTC_DCHECK(thread_checker_.CalledOnValidThread());
675  RTCLog(@"Updating audio unit state. CanPlayOrRecord=%d IsInterrupted=%d",
676         can_play_or_record, is_interrupted_);
677
678  if (is_interrupted_) {
679    RTCLog(@"Ignoring audio unit update due to interruption.");
680    return;
681  }
682
683  // If we're not initialized we don't need to do anything. Audio unit will
684  // be initialized on initialization.
685  if (!audio_is_initialized_)
686    return;
687
688  // If we're initialized, we must have an audio unit.
689  RTC_DCHECK(audio_unit_);
690
691  bool should_initialize_audio_unit = false;
692  bool should_uninitialize_audio_unit = false;
693  bool should_start_audio_unit = false;
694  bool should_stop_audio_unit = false;
695
696  switch (audio_unit_->GetState()) {
697    case VoiceProcessingAudioUnit::kInitRequired:
698      RTCLog(@"VPAU state: InitRequired");
699      RTC_NOTREACHED();
700      break;
701    case VoiceProcessingAudioUnit::kUninitialized:
702      RTCLog(@"VPAU state: Uninitialized");
703      should_initialize_audio_unit = can_play_or_record;
704      should_start_audio_unit = should_initialize_audio_unit &&
705          (playing_ || recording_);
706      break;
707    case VoiceProcessingAudioUnit::kInitialized:
708      RTCLog(@"VPAU state: Initialized");
709      should_start_audio_unit =
710          can_play_or_record && (playing_ || recording_);
711      should_uninitialize_audio_unit = !can_play_or_record;
712      break;
713    case VoiceProcessingAudioUnit::kStarted:
714      RTCLog(@"VPAU state: Started");
715      RTC_DCHECK(playing_ || recording_);
716      should_stop_audio_unit = !can_play_or_record;
717      should_uninitialize_audio_unit = should_stop_audio_unit;
718      break;
719  }
720
721  if (should_initialize_audio_unit) {
722    RTCLog(@"Initializing audio unit for UpdateAudioUnit");
723    ConfigureAudioSession();
724    SetupAudioBuffersForActiveAudioSession();
725    if (!audio_unit_->Initialize(playout_parameters_.sample_rate())) {
726      RTCLogError(@"Failed to initialize audio unit.");
727      return;
728    }
729  }
730
731  if (should_start_audio_unit) {
732    RTCLog(@"Starting audio unit for UpdateAudioUnit");
733    // Log session settings before trying to start audio streaming.
734    RTCAudioSession* session = [RTCAudioSession sharedInstance];
735    RTCLog(@"%@", session);
736    if (!audio_unit_->Start()) {
737      RTCLogError(@"Failed to start audio unit.");
738      return;
739    }
740  }
741
742  if (should_stop_audio_unit) {
743    RTCLog(@"Stopping audio unit for UpdateAudioUnit");
744    if (!audio_unit_->Stop()) {
745      RTCLogError(@"Failed to stop audio unit.");
746      return;
747    }
748  }
749
750  if (should_uninitialize_audio_unit) {
751    RTCLog(@"Uninitializing audio unit for UpdateAudioUnit");
752    audio_unit_->Uninitialize();
753    UnconfigureAudioSession();
754  }
755}
756
757void AudioDeviceIOS::ConfigureAudioSession() {
758  RTC_DCHECK(thread_checker_.CalledOnValidThread());
759  RTCLog(@"Configuring audio session.");
760  if (has_configured_session_) {
761    RTCLogWarning(@"Audio session already configured.");
762    return;
763  }
764  RTCAudioSession* session = [RTCAudioSession sharedInstance];
765  [session lockForConfiguration];
766  [session configureWebRTCSession:nil];
767  [session unlockForConfiguration];
768  has_configured_session_ = true;
769  RTCLog(@"Configured audio session.");
770}
771
772void AudioDeviceIOS::UnconfigureAudioSession() {
773  RTC_DCHECK(thread_checker_.CalledOnValidThread());
774  RTCLog(@"Unconfiguring audio session.");
775  if (!has_configured_session_) {
776    RTCLogWarning(@"Audio session already unconfigured.");
777    return;
778  }
779  RTCAudioSession* session = [RTCAudioSession sharedInstance];
780  [session lockForConfiguration];
781  [session unconfigureWebRTCSession:nil];
782  [session unlockForConfiguration];
783  has_configured_session_ = false;
784  RTCLog(@"Unconfigured audio session.");
785}
786
787bool AudioDeviceIOS::InitPlayOrRecord() {
788  LOGI() << "InitPlayOrRecord";
789
790  // There should be no audio unit at this point.
791  if (!CreateAudioUnit()) {
792    return false;
793  }
794
795  RTCAudioSession* session = [RTCAudioSession sharedInstance];
796  // Subscribe to audio session events.
797  [session pushDelegate:audio_session_observer_];
798
799  // Lock the session to make configuration changes.
800  [session lockForConfiguration];
801  NSError* error = nil;
802  if (![session beginWebRTCSession:&error]) {
803    [session unlockForConfiguration];
804    RTCLogError(@"Failed to begin WebRTC session: %@",
805                error.localizedDescription);
806    return false;
807  }
808
809  // If we are ready to play or record, initialize the audio unit.
810  if (session.canPlayOrRecord) {
811    ConfigureAudioSession();
812    SetupAudioBuffersForActiveAudioSession();
813    audio_unit_->Initialize(playout_parameters_.sample_rate());
814  }
815
816  // Release the lock.
817  [session unlockForConfiguration];
818
819  return true;
820}
821
822void AudioDeviceIOS::ShutdownPlayOrRecord() {
823  LOGI() << "ShutdownPlayOrRecord";
824
825  // Stop the audio unit to prevent any additional audio callbacks.
826  audio_unit_->Stop();
827
828  // Close and delete the voice-processing I/O unit.
829  audio_unit_.reset();
830
831  // Remove audio session notification observers.
832  RTCAudioSession* session = [RTCAudioSession sharedInstance];
833  [session removeDelegate:audio_session_observer_];
834
835  // All I/O should be stopped or paused prior to deactivating the audio
836  // session, hence we deactivate as last action.
837  [session lockForConfiguration];
838  UnconfigureAudioSession();
839  [session endWebRTCSession:nil];
840  [session unlockForConfiguration];
841}
842
843}  // namespace webrtc
844