aboutsummaryrefslogtreecommitdiff
path: root/webrtc/modules/audio_device/ios/audio_device_ios.mm
blob: f26e9f1cc7463657a2646aa28f4789cc9bbe73b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

#import <AVFoundation/AVFoundation.h>
#import <Foundation/Foundation.h>

#include "webrtc/modules/audio_device/ios/audio_device_ios.h"

#include "webrtc/base/atomicops.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
#include "webrtc/modules/audio_device/fine_audio_buffer.h"
#include "webrtc/modules/utility/interface/helpers_ios.h"

namespace webrtc {

#define LOGI() LOG(LS_INFO) << "AudioDeviceIOS::"

#define LOG_AND_RETURN_IF_ERROR(error, message) \
  do {                                          \
    OSStatus err = error;                       \
    if (err) {                                  \
      LOG(LS_ERROR) << message << ": " << err;  \
      return false;                             \
    }                                           \
  } while (0)

#define LOG_IF_ERROR(error, message)           \
  do {                                         \
    OSStatus err = error;                      \
    if (err) {                                 \
      LOG(LS_ERROR) << message << ": " << err; \
    }                                          \
  } while (0)

// Preferred hardware sample rate (unit is in Hertz). The client sample rate
// will be set to this value as well to avoid resampling the the audio unit's
// format converter. Note that, some devices, e.g. BT headsets, only supports
// 8000Hz as native sample rate.
const double kPreferredSampleRate = 48000.0;
// Use a hardware I/O buffer size (unit is in seconds) that matches the 10ms
// size used by WebRTC. The exact actual size will differ between devices.
// Example: using 48kHz on iPhone 6 results in a native buffer size of
// ~10.6667ms or 512 audio frames per buffer. The FineAudioBuffer instance will
// take care of any buffering required to convert between native buffers and
// buffers used by WebRTC. It is beneficial for the performance if the native
// size is as close to 10ms as possible since it results in "clean" callback
// sequence without bursts of callbacks back to back.
const double kPreferredIOBufferDuration = 0.01;
// Try to use mono to save resources. Also avoids channel format conversion
// in the I/O audio unit. Initial tests have shown that it is possible to use
// mono natively for built-in microphones and for BT headsets but not for
// wired headsets. Wired headsets only support stereo as native channel format
// but it is a low cost operation to do a format conversion to mono in the
// audio unit. Hence, we will not hit a RTC_CHECK in
// VerifyAudioParametersForActiveAudioSession() for a mismatch between the
// preferred number of channels and the actual number of channels.
const int kPreferredNumberOfChannels = 1;
// Number of bytes per audio sample for 16-bit signed integer representation.
const UInt32 kBytesPerSample = 2;
// Hardcoded delay estimates based on real measurements.
// TODO(henrika): these value is not used in combination with built-in AEC.
// Can most likely be removed.
const UInt16 kFixedPlayoutDelayEstimate = 30;
const UInt16 kFixedRecordDelayEstimate = 30;

using ios::CheckAndLogError;

// 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) {
  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;
    }

    // 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
    // audio sessions which are also nonmixable.
    if (session.category != AVAudioSessionCategoryPlayAndRecord) {
      error = nil;
      success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
                         withOptions:AVAudioSessionCategoryOptionAllowBluetooth
                               error:&error];
      RTC_DCHECK(CheckAndLogError(success, error));
    }

    // Specify mode for two-way voice communication (e.g. VoIP).
    if (session.mode != AVAudioSessionModeVoiceChat) {
      error = nil;
      success = [session setMode:AVAudioSessionModeVoiceChat error:&error];
      RTC_DCHECK(CheckAndLogError(success, error));
    }

    // Set the session's sample rate or the hardware sample rate.
    // It is essential that we use the same sample rate as stream format
    // to ensure that the I/O unit does not have to do sample rate conversion.
    error = nil;
    success =
        [session setPreferredSampleRate:kPreferredSampleRate error:&error];
    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];
    RTC_DCHECK(CheckAndLogError(success, error));

    // Activate the audio session. Activation can fail if another active audio
    // 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!";

    // Ensure that category and mode are actually activated.
    RTC_DCHECK(
        [session.category isEqualToString:AVAudioSessionCategoryPlayAndRecord]);
    RTC_DCHECK([session.mode isEqualToString:AVAudioSessionModeVoiceChat]);

    // 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
    // activating the session.
    // We try to use mono in both directions to save resources and format
    // conversions in the audio unit. Some devices does only support stereo;
    // e.g. wired headset on iPhone 6.
    // TODO(henrika): add support for stereo if needed.
    error = nil;
    success =
        [session setPreferredInputNumberOfChannels:kPreferredNumberOfChannels
                                             error:&error];
    RTC_DCHECK(CheckAndLogError(success, error));
    error = nil;
    success =
        [session setPreferredOutputNumberOfChannels:kPreferredNumberOfChannels
                                              error:&error];
    RTC_DCHECK(CheckAndLogError(success, error));
  }
}

#if !defined(NDEBUG)
// Helper method for printing out an AudioStreamBasicDescription structure.
static void LogABSD(AudioStreamBasicDescription absd) {
  char formatIDString[5];
  UInt32 formatID = CFSwapInt32HostToBig(absd.mFormatID);
  bcopy(&formatID, formatIDString, 4);
  formatIDString[4] = '\0';
  LOG(LS_INFO) << "LogABSD";
  LOG(LS_INFO) << " sample rate: " << absd.mSampleRate;
  LOG(LS_INFO) << " format ID: " << formatIDString;
  LOG(LS_INFO) << " format flags: " << std::hex << absd.mFormatFlags;
  LOG(LS_INFO) << " bytes per packet: " << absd.mBytesPerPacket;
  LOG(LS_INFO) << " frames per packet: " << absd.mFramesPerPacket;
  LOG(LS_INFO) << " bytes per frame: " << absd.mBytesPerFrame;
  LOG(LS_INFO) << " channels per packet: " << absd.mChannelsPerFrame;
  LOG(LS_INFO) << " bits per channel: " << absd.mBitsPerChannel;
  LOG(LS_INFO) << " reserved: " << absd.mReserved;
}

// Helper method that logs essential device information strings.
static void LogDeviceInfo() {
  LOG(LS_INFO) << "LogDeviceInfo";
  @autoreleasepool {
    LOG(LS_INFO) << " system name: " << ios::GetSystemName();
    LOG(LS_INFO) << " system version: " << ios::GetSystemVersion();
    LOG(LS_INFO) << " device type: " << ios::GetDeviceType();
    LOG(LS_INFO) << " device name: " << ios::GetDeviceName();
  }
}
#endif  // !defined(NDEBUG)

AudioDeviceIOS::AudioDeviceIOS()
    : audio_device_buffer_(nullptr),
      vpio_unit_(nullptr),
      recording_(0),
      playing_(0),
      initialized_(false),
      rec_is_initialized_(false),
      play_is_initialized_(false),
      audio_interruption_observer_(nullptr) {
  LOGI() << "ctor" << ios::GetCurrentThreadDescription();
}

AudioDeviceIOS::~AudioDeviceIOS() {
  LOGI() << "~dtor";
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  Terminate();
}

void AudioDeviceIOS::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
  LOGI() << "AttachAudioBuffer";
  RTC_DCHECK(audioBuffer);
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  audio_device_buffer_ = audioBuffer;
}

int32_t AudioDeviceIOS::Init() {
  LOGI() << "Init";
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  if (initialized_) {
    return 0;
  }
#if !defined(NDEBUG)
  LogDeviceInfo();
#endif
  // Store the preferred sample rate and preferred number of channels already
  // here. They have not been set and confirmed yet since ActivateAudioSession()
  // is not called until audio is about to start. However, it makes sense to
  // store the parameters now and then verify at a later stage.
  playout_parameters_.reset(kPreferredSampleRate, kPreferredNumberOfChannels);
  record_parameters_.reset(kPreferredSampleRate, kPreferredNumberOfChannels);
  // Ensure that the audio device buffer (ADB) knows about the internal audio
  // parameters. Note that, even if we are unable to get a mono audio session,
  // we will always tell the I/O audio unit to do a channel format conversion
  // to guarantee mono on the "input side" of the audio unit.
  UpdateAudioDeviceBuffer();
  initialized_ = true;
  return 0;
}

int32_t AudioDeviceIOS::Terminate() {
  LOGI() << "Terminate";
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  if (!initialized_) {
    return 0;
  }
  ShutdownPlayOrRecord();
  initialized_ = false;
  return 0;
}

int32_t AudioDeviceIOS::InitPlayout() {
  LOGI() << "InitPlayout";
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  RTC_DCHECK(initialized_);
  RTC_DCHECK(!play_is_initialized_);
  RTC_DCHECK(!playing_);
  if (!rec_is_initialized_) {
    if (!InitPlayOrRecord()) {
      LOG_F(LS_ERROR) << "InitPlayOrRecord failed!";
      return -1;
    }
  }
  play_is_initialized_ = true;
  return 0;
}

int32_t AudioDeviceIOS::InitRecording() {
  LOGI() << "InitRecording";
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  RTC_DCHECK(initialized_);
  RTC_DCHECK(!rec_is_initialized_);
  RTC_DCHECK(!recording_);
  if (!play_is_initialized_) {
    if (!InitPlayOrRecord()) {
      LOG_F(LS_ERROR) << "InitPlayOrRecord failed!";
      return -1;
    }
  }
  rec_is_initialized_ = true;
  return 0;
}

int32_t AudioDeviceIOS::StartPlayout() {
  LOGI() << "StartPlayout";
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  RTC_DCHECK(play_is_initialized_);
  RTC_DCHECK(!playing_);
  fine_audio_buffer_->ResetPlayout();
  if (!recording_) {
    OSStatus result = AudioOutputUnitStart(vpio_unit_);
    if (result != noErr) {
      LOG_F(LS_ERROR) << "AudioOutputUnitStart failed: " << result;
      return -1;
    }
  }
  rtc::AtomicOps::ReleaseStore(&playing_, 1);
  return 0;
}

int32_t AudioDeviceIOS::StopPlayout() {
  LOGI() << "StopPlayout";
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  if (!play_is_initialized_ || !playing_) {
    return 0;
  }
  if (!recording_) {
    ShutdownPlayOrRecord();
  }
  play_is_initialized_ = false;
  rtc::AtomicOps::ReleaseStore(&playing_, 0);
  return 0;
}

int32_t AudioDeviceIOS::StartRecording() {
  LOGI() << "StartRecording";
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  RTC_DCHECK(rec_is_initialized_);
  RTC_DCHECK(!recording_);
  fine_audio_buffer_->ResetRecord();
  if (!playing_) {
    OSStatus result = AudioOutputUnitStart(vpio_unit_);
    if (result != noErr) {
      LOG_F(LS_ERROR) << "AudioOutputUnitStart failed: " << result;
      return -1;
    }
  }
  rtc::AtomicOps::ReleaseStore(&recording_, 1);
  return 0;
}

int32_t AudioDeviceIOS::StopRecording() {
  LOGI() << "StopRecording";
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  if (!rec_is_initialized_ || !recording_) {
    return 0;
  }
  if (!playing_) {
    ShutdownPlayOrRecord();
  }
  rec_is_initialized_ = false;
  rtc::AtomicOps::ReleaseStore(&recording_, 0);
  return 0;
}

// Change the default receiver playout route to speaker.
int32_t AudioDeviceIOS::SetLoudspeakerStatus(bool enable) {
  LOGI() << "SetLoudspeakerStatus(" << enable << ")";

  AVAudioSession* session = [AVAudioSession sharedInstance];
  NSString* category = session.category;
  AVAudioSessionCategoryOptions options = session.categoryOptions;
  // Respect old category options if category is
  // AVAudioSessionCategoryPlayAndRecord. Otherwise reset it since old options
  // might not be valid for this category.
  if ([category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) {
    if (enable) {
      options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
    } else {
      options &= ~AVAudioSessionCategoryOptionDefaultToSpeaker;
    }
  } else {
    options = AVAudioSessionCategoryOptionDefaultToSpeaker;
  }
  NSError* error = nil;
  BOOL success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
                          withOptions:options
                                error:&error];
  ios::CheckAndLogError(success, error);
  return (error == nil) ? 0 : -1;
}

int32_t AudioDeviceIOS::GetLoudspeakerStatus(bool& enabled) const {
  LOGI() << "GetLoudspeakerStatus";
  AVAudioSession* session = [AVAudioSession sharedInstance];
  AVAudioSessionCategoryOptions options = session.categoryOptions;
  enabled = options & AVAudioSessionCategoryOptionDefaultToSpeaker;
  return 0;
}

int32_t AudioDeviceIOS::PlayoutDelay(uint16_t& delayMS) const {
  delayMS = kFixedPlayoutDelayEstimate;
  return 0;
}

int32_t AudioDeviceIOS::RecordingDelay(uint16_t& delayMS) const {
  delayMS = kFixedRecordDelayEstimate;
  return 0;
}

int AudioDeviceIOS::GetPlayoutAudioParameters(AudioParameters* params) const {
  LOGI() << "GetPlayoutAudioParameters";
  RTC_DCHECK(playout_parameters_.is_valid());
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  *params = playout_parameters_;
  return 0;
}

int AudioDeviceIOS::GetRecordAudioParameters(AudioParameters* params) const {
  LOGI() << "GetRecordAudioParameters";
  RTC_DCHECK(record_parameters_.is_valid());
  RTC_DCHECK(thread_checker_.CalledOnValidThread());
  *params = record_parameters_;
  return 0;
}

void AudioDeviceIOS::UpdateAudioDeviceBuffer() {
  LOGI() << "UpdateAudioDevicebuffer";
  // AttachAudioBuffer() is called at construction by the main class but check
  // just in case.
  RTC_DCHECK(audio_device_buffer_) << "AttachAudioBuffer must be called first";
  // Inform the audio device buffer (ADB) about the new audio format.
  audio_device_buffer_->SetPlayoutSampleRate(playout_parameters_.sample_rate());
  audio_device_buffer_->SetPlayoutChannels(playout_parameters_.channels());
  audio_device_buffer_->SetRecordingSampleRate(
      record_parameters_.sample_rate());
  audio_device_buffer_->SetRecordingChannels(record_parameters_.channels());
}

void AudioDeviceIOS::RegisterNotificationObservers() {
  LOGI() << "RegisterNotificationObservers";
  // This code block will be called when AVAudioSessionInterruptionNotification
  // is observed.
  void (^interrupt_block)(NSNotification*) = ^(NSNotification* notification) {
    NSNumber* type_number =
        notification.userInfo[AVAudioSessionInterruptionTypeKey];
    AVAudioSessionInterruptionType type =
        (AVAudioSessionInterruptionType)type_number.unsignedIntegerValue;
    LOG(LS_INFO) << "Audio session interruption:";
    switch (type) {
      case AVAudioSessionInterruptionTypeBegan:
        // The system has deactivated our audio session.
        // Stop the active audio unit.
        LOG(LS_INFO) << " Began => stopping the audio unit";
        LOG_IF_ERROR(AudioOutputUnitStop(vpio_unit_),
                     "Failed to stop the the Voice-Processing I/O unit");
        break;
      case AVAudioSessionInterruptionTypeEnded:
        // The interruption has ended. Restart the audio session and start the
        // initialized audio unit again.
        LOG(LS_INFO) << " Ended => restarting audio session and audio unit";
        NSError* error = nil;
        BOOL success = NO;
        AVAudioSession* session = [AVAudioSession sharedInstance];
        success = [session setActive:YES error:&error];
        if (CheckAndLogError(success, error)) {
          LOG_IF_ERROR(AudioOutputUnitStart(vpio_unit_),
                       "Failed to start the the Voice-Processing I/O unit");
        }
        break;
    }
  };

  // This code block will be called when AVAudioSessionRouteChangeNotification
  // is observed.
  void (^route_change_block)(NSNotification*) =
      ^(NSNotification* notification) {
        // Get reason for current route change.
        NSNumber* reason_number =
            notification.userInfo[AVAudioSessionRouteChangeReasonKey];
        AVAudioSessionRouteChangeReason reason =
            (AVAudioSessionRouteChangeReason)reason_number.unsignedIntegerValue;
        bool valid_route_change = true;
        LOG(LS_INFO) << "Route change:";
        switch (reason) {
          case AVAudioSessionRouteChangeReasonUnknown:
            LOG(LS_INFO) << " ReasonUnknown";
            break;
          case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
            LOG(LS_INFO) << " NewDeviceAvailable";
            break;
          case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
            LOG(LS_INFO) << " OldDeviceUnavailable";
            break;
          case AVAudioSessionRouteChangeReasonCategoryChange:
            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";
            break;
          case AVAudioSessionRouteChangeReasonWakeFromSleep:
            LOG(LS_INFO) << " WakeFromSleep";
            break;
          case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
            LOG(LS_INFO) << " NoSuitableRouteForCategory";
            break;
          case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
            // Ignore this type of route change since we are focusing
            // on detecting headset changes.
            LOG(LS_INFO) << " RouteConfigurationChange";
            valid_route_change = false;
            break;
        }

        if (valid_route_change) {
          // Log previous route configuration.
          AVAudioSessionRouteDescription* prev_route =
              notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
          LOG(LS_INFO) << "Previous route:";
          LOG(LS_INFO) << ios::StdStringFromNSString(
              [NSString stringWithFormat:@"%@", prev_route]);

          // Only restart audio for a valid route change and if the
          // session sample rate has changed.
          AVAudioSession* session = [AVAudioSession sharedInstance];
          const double session_sample_rate = session.sampleRate;
          LOG(LS_INFO) << "session sample rate: " << session_sample_rate;
          if (playout_parameters_.sample_rate() != session_sample_rate) {
            if (!RestartAudioUnitWithNewFormat(session_sample_rate)) {
              LOG(LS_ERROR) << "Audio restart failed";
            }
          }
        }
      };

  // Get the default notification center of the current process.
  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];

  // Add AVAudioSessionInterruptionNotification observer.
  id interruption_observer =
      [center addObserverForName:AVAudioSessionInterruptionNotification
                          object:nil
                           queue:[NSOperationQueue mainQueue]
                      usingBlock:interrupt_block];
  // Add AVAudioSessionRouteChangeNotification observer.
  id route_change_observer =
      [center addObserverForName:AVAudioSessionRouteChangeNotification
                          object:nil
                           queue:[NSOperationQueue mainQueue]
                      usingBlock:route_change_block];

  // Increment refcount on observers using ARC bridge. Instance variable is a
  // void* instead of an id because header is included in other pure C++
  // files.
  audio_interruption_observer_ = (__bridge_retained void*)interruption_observer;
  route_change_observer_ = (__bridge_retained void*)route_change_observer;
}

void AudioDeviceIOS::UnregisterNotificationObservers() {
  LOGI() << "UnregisterNotificationObservers";
  // Transfer ownership of observer back to ARC, which will deallocate the
  // observer once it exits this scope.
  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  if (audio_interruption_observer_ != nullptr) {
    id observer = (__bridge_transfer id)audio_interruption_observer_;
    [center removeObserver:observer];
    audio_interruption_observer_ = nullptr;
  }
  if (route_change_observer_ != nullptr) {
    id observer = (__bridge_transfer id)route_change_observer_;
    [center removeObserver:observer];
    route_change_observer_ = nullptr;
  }
}

void AudioDeviceIOS::SetupAudioBuffersForActiveAudioSession() {
  LOGI() << "SetupAudioBuffersForActiveAudioSession";
  // Verify the current values once the audio session has been activated.
  AVAudioSession* session = [AVAudioSession sharedInstance];
  LOG(LS_INFO) << " sample rate: " << session.sampleRate;
  LOG(LS_INFO) << " IO buffer duration: " << session.IOBufferDuration;
  LOG(LS_INFO) << " output channels: " << session.outputNumberOfChannels;
  LOG(LS_INFO) << " input channels: " << session.inputNumberOfChannels;
  LOG(LS_INFO) << " output latency: " << session.outputLatency;
  LOG(LS_INFO) << " input latency: " << session.inputLatency;

  // Log a warning message for the case when we are unable to set the preferred
  // hardware sample rate but continue and use the non-ideal sample rate after
  // reinitializing the audio parameters. Most BT headsets only support 8kHz or
  // 16kHz.
  if (session.sampleRate != kPreferredSampleRate) {
    LOG(LS_WARNING) << "Unable to set the preferred sample rate";
  }

  // At this stage, we also know the exact IO buffer duration and can add
  // that info to the existing audio parameters where it is converted into
  // number of audio frames.
  // Example: IO buffer size = 0.008 seconds <=> 128 audio frames at 16kHz.
  // Hence, 128 is the size we expect to see in upcoming render callbacks.
  playout_parameters_.reset(session.sampleRate, playout_parameters_.channels(),
                            session.IOBufferDuration);
  RTC_DCHECK(playout_parameters_.is_complete());
  record_parameters_.reset(session.sampleRate, record_parameters_.channels(),
                           session.IOBufferDuration);
  RTC_DCHECK(record_parameters_.is_complete());
  LOG(LS_INFO) << " frames per I/O buffer: "
               << playout_parameters_.frames_per_buffer();
  LOG(LS_INFO) << " bytes per I/O buffer: "
               << playout_parameters_.GetBytesPerBuffer();
  RTC_DCHECK_EQ(playout_parameters_.GetBytesPerBuffer(),
                record_parameters_.GetBytesPerBuffer());

  // Update the ADB parameters since the sample rate might have changed.
  UpdateAudioDeviceBuffer();

  // Create a modified audio buffer class which allows us to ask for,
  // or deliver, any number of samples (and not only multiple of 10ms) to match
  // the native audio unit buffer size.
  RTC_DCHECK(audio_device_buffer_);
  fine_audio_buffer_.reset(new FineAudioBuffer(
      audio_device_buffer_, playout_parameters_.GetBytesPerBuffer(),
      playout_parameters_.sample_rate()));

  // The extra/temporary playoutbuffer must be of this size to avoid
  // unnecessary memcpy while caching data between successive callbacks.
  const int required_playout_buffer_size =
      fine_audio_buffer_->RequiredPlayoutBufferSizeBytes();
  LOG(LS_INFO) << " required playout buffer size: "
               << required_playout_buffer_size;
  playout_audio_buffer_.reset(new SInt8[required_playout_buffer_size]);

  // Allocate AudioBuffers to be used as storage for the received audio.
  // The AudioBufferList structure works as a placeholder for the
  // AudioBuffer structure, which holds a pointer to the actual data buffer
  // in |record_audio_buffer_|. Recorded audio will be rendered into this memory
  // at each input callback when calling AudioUnitRender().
  const int data_byte_size = record_parameters_.GetBytesPerBuffer();
  record_audio_buffer_.reset(new SInt8[data_byte_size]);
  audio_record_buffer_list_.mNumberBuffers = 1;
  AudioBuffer* audio_buffer = &audio_record_buffer_list_.mBuffers[0];
  audio_buffer->mNumberChannels = record_parameters_.channels();
  audio_buffer->mDataByteSize = data_byte_size;
  audio_buffer->mData = record_audio_buffer_.get();
}

bool AudioDeviceIOS::SetupAndInitializeVoiceProcessingAudioUnit() {
  LOGI() << "SetupAndInitializeVoiceProcessingAudioUnit";
  RTC_DCHECK(!vpio_unit_);
  // Create an audio component description to identify the Voice-Processing
  // I/O audio unit.
  AudioComponentDescription vpio_unit_description;
  vpio_unit_description.componentType = kAudioUnitType_Output;
  vpio_unit_description.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
  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");

  // 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");

  // 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");

  // Set the application formats for input and output:
  // - use same format in both directions
  // - avoid resampling in the I/O unit by using the hardware sample rate
  // - linear PCM => noncompressed audio data format with one frame per packet
  // - no need to specify interleaving since only mono is supported
  AudioStreamBasicDescription application_format = {0};
  UInt32 size = sizeof(application_format);
  RTC_DCHECK_EQ(playout_parameters_.sample_rate(),
                record_parameters_.sample_rate());
  RTC_DCHECK_EQ(1, kPreferredNumberOfChannels);
  application_format.mSampleRate = playout_parameters_.sample_rate();
  application_format.mFormatID = kAudioFormatLinearPCM;
  application_format.mFormatFlags =
      kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
  application_format.mBytesPerPacket = kBytesPerSample;
  application_format.mFramesPerPacket = 1;  // uncompressed
  application_format.mBytesPerFrame = kBytesPerSample;
  application_format.mChannelsPerFrame = kPreferredNumberOfChannels;
  application_format.mBitsPerChannel = 8 * kBytesPerSample;
  // Store the new format.
  application_format_ = application_format;
#if !defined(NDEBUG)
  LogABSD(application_format_);
#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");

  // 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");

  // 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");

  // 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");

  // 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
  // AudioUnitRender() method.
  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");

  // 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");
  return true;
}

bool AudioDeviceIOS::RestartAudioUnitWithNewFormat(float sample_rate) {
  LOGI() << "RestartAudioUnitWithNewFormat(sample_rate=" << sample_rate << ")";
  // Stop the active audio unit.
  LOG_AND_RETURN_IF_ERROR(AudioOutputUnitStop(vpio_unit_),
                          "Failed to stop the the Voice-Processing I/O unit");

  // The stream format is about to be changed and it requires that we first
  // uninitialize it to deallocate its resources.
  LOG_AND_RETURN_IF_ERROR(
      AudioUnitUninitialize(vpio_unit_),
      "Failed to uninitialize the the Voice-Processing I/O unit");

  // Allocate new buffers given the new stream format.
  SetupAudioBuffersForActiveAudioSession();

  // Update the existing application format using the new sample rate.
  application_format_.mSampleRate = playout_parameters_.sample_rate();
  UInt32 size = sizeof(application_format_);
  AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
                       kAudioUnitScope_Output, 1, &application_format_, size);
  AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,
                       kAudioUnitScope_Input, 0, &application_format_, size);

  // 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");

  // Start rendering audio using the new format.
  LOG_AND_RETURN_IF_ERROR(AudioOutputUnitStart(vpio_unit_),
                          "Failed to start the Voice-Processing I/O unit");
  return true;
}

bool AudioDeviceIOS::InitPlayOrRecord() {
  LOGI() << "InitPlayOrRecord";
  AVAudioSession* session = [AVAudioSession sharedInstance];
  // Activate the audio session and ask for a set of preferred audio parameters.
  ActivateAudioSession(session, true);

  // Start observing audio session interruptions and route changes.
  RegisterNotificationObservers();

  // Ensure that we got what what we asked for in our active audio session.
  SetupAudioBuffersForActiveAudioSession();

  // Create, setup and initialize a new Voice-Processing I/O unit.
  if (!SetupAndInitializeVoiceProcessingAudioUnit()) {
    return false;
  }
  return true;
}

bool 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_) {
    result = AudioOutputUnitStop(vpio_unit_);
    if (result != noErr) {
      LOG_F(LS_ERROR) << "AudioOutputUnitStop failed: " << result;
    }
    result = AudioUnitUninitialize(vpio_unit_);
    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;
  }

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

OSStatus AudioDeviceIOS::RecordedDataIsAvailable(
    void* in_ref_con,
    AudioUnitRenderActionFlags* io_action_flags,
    const AudioTimeStamp* in_time_stamp,
    UInt32 in_bus_number,
    UInt32 in_number_frames,
    AudioBufferList* io_data) {
  RTC_DCHECK_EQ(1u, in_bus_number);
  RTC_DCHECK(
      !io_data);  // no buffer should be allocated for input at this stage
  AudioDeviceIOS* audio_device_ios = static_cast<AudioDeviceIOS*>(in_ref_con);
  return audio_device_ios->OnRecordedDataIsAvailable(
      io_action_flags, in_time_stamp, in_bus_number, in_number_frames);
}

OSStatus AudioDeviceIOS::OnRecordedDataIsAvailable(
    AudioUnitRenderActionFlags* io_action_flags,
    const AudioTimeStamp* in_time_stamp,
    UInt32 in_bus_number,
    UInt32 in_number_frames) {
  OSStatus result = noErr;
  // Simply return if recording is not enabled.
  if (!rtc::AtomicOps::AcquireLoad(&recording_))
    return result;
  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.
    LOG(LS_WARNING) << "in_number_frames (" << in_number_frames
                    << ") != " << record_parameters_.frames_per_buffer();
  }
  // Obtain the recorded audio samples by initiating a rendering cycle.
  // Since it happens on the input bus, the |io_data| parameter is a reference
  // to the preallocated audio buffer list that the audio unit renders into.
  // TODO(henrika): should error handling be improved?
  AudioBufferList* io_data = &audio_record_buffer_list_;
  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;
    return result;
  }
  // Get a pointer to the recorded audio and send it to the WebRTC ADB.
  // Use the FineAudioBuffer instance to convert between native buffer size
  // and the 10ms buffer size used by WebRTC.
  const UInt32 data_size_in_bytes = io_data->mBuffers[0].mDataByteSize;
  RTC_CHECK_EQ(data_size_in_bytes / kBytesPerSample, in_number_frames);
  SInt8* data = static_cast<SInt8*>(io_data->mBuffers[0].mData);
  fine_audio_buffer_->DeliverRecordedData(data, data_size_in_bytes,
                                          kFixedPlayoutDelayEstimate,
                                          kFixedRecordDelayEstimate);
  return noErr;
}

OSStatus AudioDeviceIOS::GetPlayoutData(
    void* in_ref_con,
    AudioUnitRenderActionFlags* io_action_flags,
    const AudioTimeStamp* in_time_stamp,
    UInt32 in_bus_number,
    UInt32 in_number_frames,
    AudioBufferList* io_data) {
  RTC_DCHECK_EQ(0u, in_bus_number);
  RTC_DCHECK(io_data);
  AudioDeviceIOS* audio_device_ios = static_cast<AudioDeviceIOS*>(in_ref_con);
  return audio_device_ios->OnGetPlayoutData(io_action_flags, in_number_frames,
                                            io_data);
}

OSStatus AudioDeviceIOS::OnGetPlayoutData(
    AudioUnitRenderActionFlags* io_action_flags,
    UInt32 in_number_frames,
    AudioBufferList* io_data) {
  // Verify 16-bit, noninterleaved mono PCM signal format.
  RTC_DCHECK_EQ(1u, io_data->mNumberBuffers);
  RTC_DCHECK_EQ(1u, io_data->mBuffers[0].mNumberChannels);
  // Get pointer to internal audio buffer to which new audio data shall be
  // written.
  const UInt32 dataSizeInBytes = io_data->mBuffers[0].mDataByteSize;
  RTC_CHECK_EQ(dataSizeInBytes / kBytesPerSample, in_number_frames);
  SInt8* destination = static_cast<SInt8*>(io_data->mBuffers[0].mData);
  // Produce silence and give audio unit a hint about it if playout is not
  // activated.
  if (!rtc::AtomicOps::AcquireLoad(&playing_)) {
    *io_action_flags |= kAudioUnitRenderAction_OutputIsSilence;
    memset(destination, 0, dataSizeInBytes);
    return noErr;
  }
  // Read decoded 16-bit PCM samples from WebRTC (using a size that matches
  // the native I/O audio unit) to a preallocated intermediate buffer and
  // copy the result to the audio buffer in the |io_data| destination.
  SInt8* source = playout_audio_buffer_.get();
  fine_audio_buffer_->GetPlayoutData(source);
  memcpy(destination, source, dataSizeInBytes);
  return noErr;
}

}  // namespace webrtc