blob: 69cfa22aa8914d1c8fd9ace0c9d5bffd4954ecd0 [file] [log] [blame]
Zeke Chinb3fb71c2016-02-18 15:44:07 -08001/*
2 * Copyright 2016 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
denicija59ee91b2017-06-05 05:48:47 -070011#import "WebRTC/RTCAudioSession.h"
Zeke Chinb3fb71c2016-02-18 15:44:07 -080012
tkchin93dd6342016-07-27 10:17:14 -070013#import <UIKit/UIKit.h>
14
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020015#include "rtc_base/atomicops.h"
16#include "rtc_base/checks.h"
17#include "rtc_base/criticalsection.h"
denicija59ee91b2017-06-05 05:48:47 -070018
19#import "WebRTC/RTCAudioSessionConfiguration.h"
tkchin9eeb6242016-04-27 01:54:20 -070020#import "WebRTC/RTCLogging.h"
denicija59ee91b2017-06-05 05:48:47 -070021
22#import "RTCAudioSession+Private.h"
23
Zeke Chinb3fb71c2016-02-18 15:44:07 -080024
25NSString * const kRTCAudioSessionErrorDomain = @"org.webrtc.RTCAudioSession";
26NSInteger const kRTCAudioSessionErrorLockRequired = -1;
tkchin9f987d32016-03-12 20:06:28 -080027NSInteger const kRTCAudioSessionErrorConfiguration = -2;
jtteh13ae11a2017-05-25 17:52:20 -070028NSString * const kRTCAudioSessionOutputVolumeSelector = @"outputVolume";
Zeke Chinb3fb71c2016-02-18 15:44:07 -080029
30// This class needs to be thread-safe because it is accessed from many threads.
31// TODO(tkchin): Consider more granular locking. We're not expecting a lot of
32// lock contention so coarse locks should be fine for now.
33@implementation RTCAudioSession {
tkchin0ce3bf92016-03-12 16:52:04 -080034 rtc::CriticalSection _crit;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080035 AVAudioSession *_session;
Tze Kwang Chin307a0922016-03-21 13:57:40 -070036 volatile int _activationCount;
37 volatile int _lockRecursionCount;
38 volatile int _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080039 BOOL _isActive;
tkchind2511962016-05-06 18:54:15 -070040 BOOL _useManualAudio;
41 BOOL _isAudioEnabled;
42 BOOL _canPlayOrRecord;
tkchin93dd6342016-07-27 10:17:14 -070043 BOOL _isInterrupted;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080044}
45
46@synthesize session = _session;
tkchine54467f2016-03-15 16:54:03 -070047@synthesize delegates = _delegates;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080048
49+ (instancetype)sharedInstance {
50 static dispatch_once_t onceToken;
51 static RTCAudioSession *sharedInstance = nil;
52 dispatch_once(&onceToken, ^{
Tze Kwang Chin307a0922016-03-21 13:57:40 -070053 sharedInstance = [[self alloc] init];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080054 });
55 return sharedInstance;
56}
57
58- (instancetype)init {
Peter Hanspers47217362017-10-05 11:39:15 +020059 return [self initWithAudioSession:[AVAudioSession sharedInstance]];
60}
61
62/** This initializer provides a way for unit tests to inject a fake/mock audio session. */
63- (instancetype)initWithAudioSession:(id)audioSession {
Zeke Chinb3fb71c2016-02-18 15:44:07 -080064 if (self = [super init]) {
Peter Hanspers47217362017-10-05 11:39:15 +020065 _session = audioSession;
tkchin0ce3bf92016-03-12 16:52:04 -080066
Zeke Chinb3fb71c2016-02-18 15:44:07 -080067 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
68 [center addObserver:self
69 selector:@selector(handleInterruptionNotification:)
70 name:AVAudioSessionInterruptionNotification
71 object:nil];
72 [center addObserver:self
73 selector:@selector(handleRouteChangeNotification:)
74 name:AVAudioSessionRouteChangeNotification
75 object:nil];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080076 [center addObserver:self
77 selector:@selector(handleMediaServicesWereLost:)
78 name:AVAudioSessionMediaServicesWereLostNotification
79 object:nil];
80 [center addObserver:self
81 selector:@selector(handleMediaServicesWereReset:)
82 name:AVAudioSessionMediaServicesWereResetNotification
83 object:nil];
henrikaf1363fd2016-09-27 06:06:44 -070084 // Posted on the main thread when the primary audio from other applications
85 // starts and stops. Foreground applications may use this notification as a
86 // hint to enable or disable audio that is secondary.
87 [center addObserver:self
88 selector:@selector(handleSilenceSecondaryAudioHintNotification:)
89 name:AVAudioSessionSilenceSecondaryAudioHintNotification
90 object:nil];
tkchin93dd6342016-07-27 10:17:14 -070091 // Also track foreground event in order to deal with interruption ended situation.
92 [center addObserver:self
93 selector:@selector(handleApplicationDidBecomeActive:)
94 name:UIApplicationDidBecomeActiveNotification
95 object:nil];
jtteh13ae11a2017-05-25 17:52:20 -070096 [_session addObserver:self
97 forKeyPath:kRTCAudioSessionOutputVolumeSelector
98 options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
Peter Hanspers47217362017-10-05 11:39:15 +020099 context:(__bridge void*)RTCAudioSession.class];
jtteh13ae11a2017-05-25 17:52:20 -0700100
haysc7735b1e2017-03-29 14:53:32 -0700101 RTCLog(@"RTCAudioSession (%p): init.", self);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800102 }
103 return self;
104}
105
106- (void)dealloc {
107 [[NSNotificationCenter defaultCenter] removeObserver:self];
Peter Hanspers47217362017-10-05 11:39:15 +0200108 [_session removeObserver:self
109 forKeyPath:kRTCAudioSessionOutputVolumeSelector
110 context:(__bridge void*)RTCAudioSession.class];
haysc7735b1e2017-03-29 14:53:32 -0700111 RTCLog(@"RTCAudioSession (%p): dealloc.", self);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800112}
113
Zeke Chin1300caa2016-03-18 14:39:11 -0700114- (NSString *)description {
115 NSString *format =
116 @"RTCAudioSession: {\n"
tkchind2511962016-05-06 18:54:15 -0700117 " category: %@\n"
118 " categoryOptions: %ld\n"
119 " mode: %@\n"
Zeke Chin1300caa2016-03-18 14:39:11 -0700120 " isActive: %d\n"
121 " sampleRate: %.2f\n"
122 " IOBufferDuration: %f\n"
123 " outputNumberOfChannels: %ld\n"
124 " inputNumberOfChannels: %ld\n"
125 " outputLatency: %f\n"
126 " inputLatency: %f\n"
henrikac5aea652016-09-21 07:45:55 -0700127 " outputVolume: %f\n"
Zeke Chin1300caa2016-03-18 14:39:11 -0700128 "}";
129 NSString *description = [NSString stringWithFormat:format,
tkchind2511962016-05-06 18:54:15 -0700130 self.category, (long)self.categoryOptions, self.mode,
Zeke Chin1300caa2016-03-18 14:39:11 -0700131 self.isActive, self.sampleRate, self.IOBufferDuration,
132 self.outputNumberOfChannels, self.inputNumberOfChannels,
henrikac5aea652016-09-21 07:45:55 -0700133 self.outputLatency, self.inputLatency, self.outputVolume];
Zeke Chin1300caa2016-03-18 14:39:11 -0700134 return description;
135}
136
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800137- (void)setIsActive:(BOOL)isActive {
138 @synchronized(self) {
139 _isActive = isActive;
140 }
141}
142
143- (BOOL)isActive {
144 @synchronized(self) {
145 return _isActive;
146 }
147}
148
149- (BOOL)isLocked {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700150 return _lockRecursionCount > 0;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800151}
152
tkchind2511962016-05-06 18:54:15 -0700153- (void)setUseManualAudio:(BOOL)useManualAudio {
tkchin9f987d32016-03-12 20:06:28 -0800154 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700155 if (_useManualAudio == useManualAudio) {
tkchin9f987d32016-03-12 20:06:28 -0800156 return;
157 }
tkchind2511962016-05-06 18:54:15 -0700158 _useManualAudio = useManualAudio;
159 }
160 [self updateCanPlayOrRecord];
161}
162
163- (BOOL)useManualAudio {
164 @synchronized(self) {
165 return _useManualAudio;
tkchin9f987d32016-03-12 20:06:28 -0800166 }
167}
168
tkchind2511962016-05-06 18:54:15 -0700169- (void)setIsAudioEnabled:(BOOL)isAudioEnabled {
tkchin9f987d32016-03-12 20:06:28 -0800170 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700171 if (_isAudioEnabled == isAudioEnabled) {
172 return;
173 }
174 _isAudioEnabled = isAudioEnabled;
175 }
176 [self updateCanPlayOrRecord];
177}
178
179- (BOOL)isAudioEnabled {
180 @synchronized(self) {
181 return _isAudioEnabled;
tkchin9f987d32016-03-12 20:06:28 -0800182 }
183}
184
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700185// TODO(tkchin): Check for duplicates.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800186- (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate {
haysc7735b1e2017-03-29 14:53:32 -0700187 RTCLog(@"Adding delegate: (%p)", delegate);
tkchine54467f2016-03-15 16:54:03 -0700188 if (!delegate) {
189 return;
190 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800191 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700192 _delegates.push_back(delegate);
193 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800194 }
195}
196
197- (void)removeDelegate:(id<RTCAudioSessionDelegate>)delegate {
haysc7735b1e2017-03-29 14:53:32 -0700198 RTCLog(@"Removing delegate: (%p)", delegate);
tkchine54467f2016-03-15 16:54:03 -0700199 if (!delegate) {
200 return;
201 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800202 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700203 _delegates.erase(std::remove(_delegates.begin(),
204 _delegates.end(),
tkchinefdd9302016-04-11 12:00:59 -0700205 delegate),
206 _delegates.end());
tkchine54467f2016-03-15 16:54:03 -0700207 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800208 }
209}
210
kthelgasonee8b8612017-03-31 04:50:27 -0700211#pragma clang diagnostic push
212#pragma clang diagnostic ignored "-Wthread-safety-analysis"
213
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800214- (void)lockForConfiguration {
tkchin0ce3bf92016-03-12 16:52:04 -0800215 _crit.Enter();
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700216 rtc::AtomicOps::Increment(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800217}
218
219- (void)unlockForConfiguration {
220 // Don't let threads other than the one that called lockForConfiguration
221 // unlock.
tkchin0ce3bf92016-03-12 16:52:04 -0800222 if (_crit.TryEnter()) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700223 rtc::AtomicOps::Decrement(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800224 // One unlock for the tryLock, and another one to actually unlock. If this
tkchin0ce3bf92016-03-12 16:52:04 -0800225 // was called without anyone calling lock, we will hit an assertion.
226 _crit.Leave();
227 _crit.Leave();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800228 }
229}
230
kthelgasonee8b8612017-03-31 04:50:27 -0700231#pragma clang diagnostic pop
232
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800233#pragma mark - AVAudioSession proxy methods
234
235- (NSString *)category {
236 return self.session.category;
237}
238
239- (AVAudioSessionCategoryOptions)categoryOptions {
240 return self.session.categoryOptions;
241}
242
243- (NSString *)mode {
244 return self.session.mode;
245}
246
247- (BOOL)secondaryAudioShouldBeSilencedHint {
248 return self.session.secondaryAudioShouldBeSilencedHint;
249}
250
251- (AVAudioSessionRouteDescription *)currentRoute {
252 return self.session.currentRoute;
253}
254
255- (NSInteger)maximumInputNumberOfChannels {
256 return self.session.maximumInputNumberOfChannels;
257}
258
259- (NSInteger)maximumOutputNumberOfChannels {
260 return self.session.maximumOutputNumberOfChannels;
261}
262
263- (float)inputGain {
264 return self.session.inputGain;
265}
266
267- (BOOL)inputGainSettable {
268 return self.session.inputGainSettable;
269}
270
271- (BOOL)inputAvailable {
272 return self.session.inputAvailable;
273}
274
275- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
276 return self.session.inputDataSources;
277}
278
279- (AVAudioSessionDataSourceDescription *)inputDataSource {
280 return self.session.inputDataSource;
281}
282
283- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
284 return self.session.outputDataSources;
285}
286
287- (AVAudioSessionDataSourceDescription *)outputDataSource {
288 return self.session.outputDataSource;
289}
290
291- (double)sampleRate {
292 return self.session.sampleRate;
293}
294
tkchind2511962016-05-06 18:54:15 -0700295- (double)preferredSampleRate {
296 return self.session.preferredSampleRate;
297}
298
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800299- (NSInteger)inputNumberOfChannels {
300 return self.session.inputNumberOfChannels;
301}
302
303- (NSInteger)outputNumberOfChannels {
304 return self.session.outputNumberOfChannels;
305}
306
307- (float)outputVolume {
308 return self.session.outputVolume;
309}
310
311- (NSTimeInterval)inputLatency {
312 return self.session.inputLatency;
313}
314
315- (NSTimeInterval)outputLatency {
316 return self.session.outputLatency;
317}
318
319- (NSTimeInterval)IOBufferDuration {
320 return self.session.IOBufferDuration;
321}
322
tkchind2511962016-05-06 18:54:15 -0700323- (NSTimeInterval)preferredIOBufferDuration {
324 return self.session.preferredIOBufferDuration;
325}
326
tkchine54467f2016-03-15 16:54:03 -0700327// TODO(tkchin): Simplify the amount of locking happening here. Likely that we
328// can just do atomic increments / decrements.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800329- (BOOL)setActive:(BOOL)active
330 error:(NSError **)outError {
331 if (![self checkLock:outError]) {
332 return NO;
333 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700334 int activationCount = _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800335 if (!active && activationCount == 0) {
336 RTCLogWarning(@"Attempting to deactivate without prior activation.");
337 }
JT Tehc1f083d2018-04-25 09:19:35 -0700338 [self notifyWillSetActive:active];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800339 BOOL success = YES;
340 BOOL isActive = self.isActive;
341 // Keep a local error so we can log it.
342 NSError *error = nil;
343 BOOL shouldSetActive =
344 (active && !isActive) || (!active && isActive && activationCount == 1);
345 // Attempt to activate if we're not active.
346 // Attempt to deactivate if we're active and it's the last unbalanced call.
347 if (shouldSetActive) {
348 AVAudioSession *session = self.session;
349 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
350 // that other audio sessions that were interrupted by our session can return
351 // to their active state. It is recommended for VoIP apps to use this
352 // option.
353 AVAudioSessionSetActiveOptions options =
354 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
355 success = [session setActive:active
356 withOptions:options
357 error:&error];
358 if (outError) {
359 *outError = error;
360 }
361 }
362 if (success) {
363 if (shouldSetActive) {
364 self.isActive = active;
365 }
366 if (active) {
367 [self incrementActivationCount];
368 }
JT Tehc1f083d2018-04-25 09:19:35 -0700369 [self notifyDidSetActive:active];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800370 } else {
tkchin9f987d32016-03-12 20:06:28 -0800371 RTCLogError(@"Failed to setActive:%d. Error: %@",
372 active, error.localizedDescription);
JT Tehc1f083d2018-04-25 09:19:35 -0700373 [self notifyFailedToSetActive:active error:error];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800374 }
375 // Decrement activation count on deactivation whether or not it succeeded.
376 if (!active) {
377 [self decrementActivationCount];
378 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700379 RTCLog(@"Number of current activations: %d", _activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800380 return success;
381}
382
383- (BOOL)setCategory:(NSString *)category
384 withOptions:(AVAudioSessionCategoryOptions)options
385 error:(NSError **)outError {
386 if (![self checkLock:outError]) {
387 return NO;
388 }
389 return [self.session setCategory:category withOptions:options error:outError];
390}
391
392- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
393 if (![self checkLock:outError]) {
394 return NO;
395 }
396 return [self.session setMode:mode error:outError];
397}
398
399- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
400 if (![self checkLock:outError]) {
401 return NO;
402 }
403 return [self.session setInputGain:gain error:outError];
404}
405
406- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
407 if (![self checkLock:outError]) {
408 return NO;
409 }
410 return [self.session setPreferredSampleRate:sampleRate error:outError];
411}
412
413- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
414 error:(NSError **)outError {
415 if (![self checkLock:outError]) {
416 return NO;
417 }
418 return [self.session setPreferredIOBufferDuration:duration error:outError];
419}
420
421- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
422 error:(NSError **)outError {
423 if (![self checkLock:outError]) {
424 return NO;
425 }
426 return [self.session setPreferredInputNumberOfChannels:count error:outError];
427}
428- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
429 error:(NSError **)outError {
430 if (![self checkLock:outError]) {
431 return NO;
432 }
433 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
434}
435
436- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
437 error:(NSError **)outError {
438 if (![self checkLock:outError]) {
439 return NO;
440 }
441 return [self.session overrideOutputAudioPort:portOverride error:outError];
442}
443
444- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
445 error:(NSError **)outError {
446 if (![self checkLock:outError]) {
447 return NO;
448 }
449 return [self.session setPreferredInput:inPort error:outError];
450}
451
452- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
453 error:(NSError **)outError {
454 if (![self checkLock:outError]) {
455 return NO;
456 }
457 return [self.session setInputDataSource:dataSource error:outError];
458}
459
460- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
461 error:(NSError **)outError {
462 if (![self checkLock:outError]) {
463 return NO;
464 }
465 return [self.session setOutputDataSource:dataSource error:outError];
466}
467
468#pragma mark - Notifications
469
470- (void)handleInterruptionNotification:(NSNotification *)notification {
471 NSNumber* typeNumber =
472 notification.userInfo[AVAudioSessionInterruptionTypeKey];
473 AVAudioSessionInterruptionType type =
474 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
475 switch (type) {
476 case AVAudioSessionInterruptionTypeBegan:
477 RTCLog(@"Audio session interruption began.");
478 self.isActive = NO;
tkchin93dd6342016-07-27 10:17:14 -0700479 self.isInterrupted = YES;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800480 [self notifyDidBeginInterruption];
481 break;
482 case AVAudioSessionInterruptionTypeEnded: {
483 RTCLog(@"Audio session interruption ended.");
tkchin93dd6342016-07-27 10:17:14 -0700484 self.isInterrupted = NO;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800485 [self updateAudioSessionAfterEvent];
486 NSNumber *optionsNumber =
487 notification.userInfo[AVAudioSessionInterruptionOptionKey];
488 AVAudioSessionInterruptionOptions options =
489 optionsNumber.unsignedIntegerValue;
490 BOOL shouldResume =
491 options & AVAudioSessionInterruptionOptionShouldResume;
492 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
493 break;
494 }
495 }
496}
497
498- (void)handleRouteChangeNotification:(NSNotification *)notification {
499 // Get reason for current route change.
500 NSNumber* reasonNumber =
501 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
502 AVAudioSessionRouteChangeReason reason =
503 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
504 RTCLog(@"Audio route changed:");
505 switch (reason) {
506 case AVAudioSessionRouteChangeReasonUnknown:
507 RTCLog(@"Audio route changed: ReasonUnknown");
508 break;
509 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
510 RTCLog(@"Audio route changed: NewDeviceAvailable");
511 break;
512 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
513 RTCLog(@"Audio route changed: OldDeviceUnavailable");
514 break;
515 case AVAudioSessionRouteChangeReasonCategoryChange:
516 RTCLog(@"Audio route changed: CategoryChange to :%@",
517 self.session.category);
518 break;
519 case AVAudioSessionRouteChangeReasonOverride:
520 RTCLog(@"Audio route changed: Override");
521 break;
522 case AVAudioSessionRouteChangeReasonWakeFromSleep:
523 RTCLog(@"Audio route changed: WakeFromSleep");
524 break;
525 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
526 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
527 break;
528 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
529 RTCLog(@"Audio route changed: RouteConfigurationChange");
530 break;
531 }
532 AVAudioSessionRouteDescription* previousRoute =
533 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
534 // Log previous route configuration.
535 RTCLog(@"Previous route: %@\nCurrent route:%@",
536 previousRoute, self.session.currentRoute);
537 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
538}
539
540- (void)handleMediaServicesWereLost:(NSNotification *)notification {
541 RTCLog(@"Media services were lost.");
542 [self updateAudioSessionAfterEvent];
543 [self notifyMediaServicesWereLost];
544}
545
546- (void)handleMediaServicesWereReset:(NSNotification *)notification {
547 RTCLog(@"Media services were reset.");
548 [self updateAudioSessionAfterEvent];
549 [self notifyMediaServicesWereReset];
550}
551
henrikaf1363fd2016-09-27 06:06:44 -0700552- (void)handleSilenceSecondaryAudioHintNotification:(NSNotification *)notification {
553 // TODO(henrika): just adding logs here for now until we know if we are ever
554 // see this notification and might be affected by it or if further actions
555 // are required.
556 NSNumber *typeNumber =
557 notification.userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey];
558 AVAudioSessionSilenceSecondaryAudioHintType type =
559 (AVAudioSessionSilenceSecondaryAudioHintType)typeNumber.unsignedIntegerValue;
560 switch (type) {
561 case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
562 RTCLog(@"Another application's primary audio has started.");
563 break;
564 case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
565 RTCLog(@"Another application's primary audio has stopped.");
566 break;
567 }
568}
569
tkchin93dd6342016-07-27 10:17:14 -0700570- (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
haysc7735b1e2017-03-29 14:53:32 -0700571 RTCLog(@"Application became active after an interruption. Treating as interruption "
572 " end. isInterrupted changed from %d to 0.", self.isInterrupted);
tkchin93dd6342016-07-27 10:17:14 -0700573 if (self.isInterrupted) {
tkchin93dd6342016-07-27 10:17:14 -0700574 self.isInterrupted = NO;
575 [self updateAudioSessionAfterEvent];
tkchin93dd6342016-07-27 10:17:14 -0700576 }
haysc7735b1e2017-03-29 14:53:32 -0700577 // Always treat application becoming active as an interruption end event.
578 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
tkchin93dd6342016-07-27 10:17:14 -0700579}
580
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800581#pragma mark - Private
582
583+ (NSError *)lockError {
584 NSDictionary *userInfo = @{
585 NSLocalizedDescriptionKey:
586 @"Must call lockForConfiguration before calling this method."
587 };
588 NSError *error =
589 [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
590 code:kRTCAudioSessionErrorLockRequired
591 userInfo:userInfo];
592 return error;
593}
594
tkchine54467f2016-03-15 16:54:03 -0700595- (std::vector<__weak id<RTCAudioSessionDelegate> >)delegates {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800596 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700597 // Note: this returns a copy.
598 return _delegates;
599 }
600}
601
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700602// TODO(tkchin): check for duplicates.
tkchine54467f2016-03-15 16:54:03 -0700603- (void)pushDelegate:(id<RTCAudioSessionDelegate>)delegate {
604 @synchronized(self) {
605 _delegates.insert(_delegates.begin(), delegate);
606 }
607}
608
609- (void)removeZeroedDelegates {
610 @synchronized(self) {
tkchinefdd9302016-04-11 12:00:59 -0700611 _delegates.erase(
612 std::remove_if(_delegates.begin(),
613 _delegates.end(),
614 [](id delegate) -> bool { return delegate == nil; }),
615 _delegates.end());
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800616 }
617}
618
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700619- (int)activationCount {
620 return _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800621}
622
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700623- (int)incrementActivationCount {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800624 RTCLog(@"Incrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700625 return rtc::AtomicOps::Increment(&_activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800626}
627
628- (NSInteger)decrementActivationCount {
629 RTCLog(@"Decrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700630 return rtc::AtomicOps::Decrement(&_activationCount);
631}
632
633- (int)webRTCSessionCount {
634 return _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800635}
636
tkchind2511962016-05-06 18:54:15 -0700637- (BOOL)canPlayOrRecord {
638 return !self.useManualAudio || self.isAudioEnabled;
639}
640
tkchin93dd6342016-07-27 10:17:14 -0700641- (BOOL)isInterrupted {
642 @synchronized(self) {
643 return _isInterrupted;
644 }
645}
646
647- (void)setIsInterrupted:(BOOL)isInterrupted {
648 @synchronized(self) {
649 if (_isInterrupted == isInterrupted) {
650 return;
651 }
652 _isInterrupted = isInterrupted;
653 }
654}
655
tkchin9f987d32016-03-12 20:06:28 -0800656- (BOOL)checkLock:(NSError **)outError {
657 // Check ivar instead of trying to acquire lock so that we won't accidentally
658 // acquire lock if it hasn't already been called.
659 if (!self.isLocked) {
660 if (outError) {
661 *outError = [RTCAudioSession lockError];
662 }
663 return NO;
664 }
665 return YES;
666}
667
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700668- (BOOL)beginWebRTCSession:(NSError **)outError {
669 if (outError) {
670 *outError = nil;
671 }
672 if (![self checkLock:outError]) {
673 return NO;
674 }
tkchind2511962016-05-06 18:54:15 -0700675 rtc::AtomicOps::Increment(&_webRTCSessionCount);
676 [self notifyDidStartPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700677 return YES;
678}
679
680- (BOOL)endWebRTCSession:(NSError **)outError {
681 if (outError) {
682 *outError = nil;
683 }
684 if (![self checkLock:outError]) {
685 return NO;
686 }
tkchind2511962016-05-06 18:54:15 -0700687 rtc::AtomicOps::Decrement(&_webRTCSessionCount);
688 [self notifyDidStopPlayOrRecord];
689 return YES;
690}
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700691
tkchind2511962016-05-06 18:54:15 -0700692- (BOOL)configureWebRTCSession:(NSError **)outError {
693 if (outError) {
694 *outError = nil;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700695 }
tkchind2511962016-05-06 18:54:15 -0700696 if (![self checkLock:outError]) {
697 return NO;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700698 }
tkchind2511962016-05-06 18:54:15 -0700699 RTCLog(@"Configuring audio session for WebRTC.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700700
tkchind2511962016-05-06 18:54:15 -0700701 // Configure the AVAudioSession and activate it.
702 // Provide an error even if there isn't one so we can log it.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700703 NSError *error = nil;
tkchind2511962016-05-06 18:54:15 -0700704 RTCAudioSessionConfiguration *webRTCConfig =
705 [RTCAudioSessionConfiguration webRTCConfiguration];
706 if (![self setConfiguration:webRTCConfig active:YES error:&error]) {
707 RTCLogError(@"Failed to set WebRTC audio configuration: %@",
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700708 error.localizedDescription);
jttehf84c1d62017-04-21 13:56:39 -0700709 // Do not call setActive:NO if setActive:YES failed.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700710 if (outError) {
711 *outError = error;
712 }
713 return NO;
714 }
715
tkchind2511962016-05-06 18:54:15 -0700716 // Ensure that the device currently supports audio input.
717 // TODO(tkchin): Figure out if this is really necessary.
718 if (!self.inputAvailable) {
719 RTCLogError(@"No audio input path is available!");
720 [self unconfigureWebRTCSession:nil];
721 if (outError) {
722 *outError = [self configurationErrorWithDescription:@"No input path."];
723 }
724 return NO;
725 }
726
henrika2d014be2016-06-16 14:26:55 +0200727 // It can happen (e.g. in combination with BT devices) that the attempt to set
728 // the preferred sample rate for WebRTC (48kHz) fails. If so, make a new
729 // configuration attempt using the sample rate that worked using the active
730 // audio session. A typical case is that only 8 or 16kHz can be set, e.g. in
731 // combination with BT headsets. Using this "trick" seems to avoid a state
732 // where Core Audio asks for a different number of audio frames than what the
733 // session's I/O buffer duration corresponds to.
734 // TODO(henrika): this fix resolves bugs.webrtc.org/6004 but it has only been
735 // tested on a limited set of iOS devices and BT devices.
736 double sessionSampleRate = self.sampleRate;
737 double preferredSampleRate = webRTCConfig.sampleRate;
738 if (sessionSampleRate != preferredSampleRate) {
739 RTCLogWarning(
740 @"Current sample rate (%.2f) is not the preferred rate (%.2f)",
741 sessionSampleRate, preferredSampleRate);
742 if (![self setPreferredSampleRate:sessionSampleRate
743 error:&error]) {
744 RTCLogError(@"Failed to set preferred sample rate: %@",
745 error.localizedDescription);
746 if (outError) {
747 *outError = error;
748 }
749 }
750 }
751
tkchind2511962016-05-06 18:54:15 -0700752 return YES;
753}
754
755- (BOOL)unconfigureWebRTCSession:(NSError **)outError {
756 if (outError) {
757 *outError = nil;
758 }
759 if (![self checkLock:outError]) {
760 return NO;
761 }
762 RTCLog(@"Unconfiguring audio session for WebRTC.");
763 [self setActive:NO error:outError];
764
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700765 return YES;
766}
767
768- (NSError *)configurationErrorWithDescription:(NSString *)description {
769 NSDictionary* userInfo = @{
770 NSLocalizedDescriptionKey: description,
771 };
772 return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
773 code:kRTCAudioSessionErrorConfiguration
774 userInfo:userInfo];
775}
776
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800777- (void)updateAudioSessionAfterEvent {
778 BOOL shouldActivate = self.activationCount > 0;
779 AVAudioSessionSetActiveOptions options = shouldActivate ?
780 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
781 NSError *error = nil;
782 if ([self.session setActive:shouldActivate
783 withOptions:options
784 error:&error]) {
785 self.isActive = shouldActivate;
786 } else {
787 RTCLogError(@"Failed to set session active to %d. Error:%@",
788 shouldActivate, error.localizedDescription);
789 }
790}
791
tkchind2511962016-05-06 18:54:15 -0700792- (void)updateCanPlayOrRecord {
793 BOOL canPlayOrRecord = NO;
794 BOOL shouldNotify = NO;
795 @synchronized(self) {
796 canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled;
797 if (_canPlayOrRecord == canPlayOrRecord) {
798 return;
799 }
800 _canPlayOrRecord = canPlayOrRecord;
801 shouldNotify = YES;
802 }
803 if (shouldNotify) {
804 [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord];
805 }
806}
807
jtteh3c9a6c02017-04-18 09:09:35 -0700808- (void)audioSessionDidActivate:(AVAudioSession *)session {
809 if (_session != session) {
810 RTCLogError(@"audioSessionDidActivate called on different AVAudioSession");
811 }
812 [self incrementActivationCount];
813 self.isActive = YES;
814}
815
816- (void)audioSessionDidDeactivate:(AVAudioSession *)session {
817 if (_session != session) {
818 RTCLogError(@"audioSessionDidDeactivate called on different AVAudioSession");
819 }
820 self.isActive = NO;
821 [self decrementActivationCount];
822}
823
jtteh13ae11a2017-05-25 17:52:20 -0700824- (void)observeValueForKeyPath:(NSString *)keyPath
825 ofObject:(id)object
826 change:(NSDictionary *)change
827 context:(void *)context {
Peter Hanspers47217362017-10-05 11:39:15 +0200828 if (context == (__bridge void*)RTCAudioSession.class) {
829 if (object == _session) {
830 NSNumber *newVolume = change[NSKeyValueChangeNewKey];
831 RTCLog(@"OutputVolumeDidChange to %f", newVolume.floatValue);
832 [self notifyDidChangeOutputVolume:newVolume.floatValue];
833 }
jtteh13ae11a2017-05-25 17:52:20 -0700834 } else {
835 [super observeValueForKeyPath:keyPath
836 ofObject:object
837 change:change
838 context:context];
839 }
840}
841
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800842- (void)notifyDidBeginInterruption {
tkchine54467f2016-03-15 16:54:03 -0700843 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700844 SEL sel = @selector(audioSessionDidBeginInterruption:);
845 if ([delegate respondsToSelector:sel]) {
846 [delegate audioSessionDidBeginInterruption:self];
847 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800848 }
849}
850
851- (void)notifyDidEndInterruptionWithShouldResumeSession:
852 (BOOL)shouldResumeSession {
tkchine54467f2016-03-15 16:54:03 -0700853 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700854 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
855 if ([delegate respondsToSelector:sel]) {
856 [delegate audioSessionDidEndInterruption:self
857 shouldResumeSession:shouldResumeSession];
858 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800859 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800860}
861
862- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
863 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -0700864 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700865 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
866 if ([delegate respondsToSelector:sel]) {
867 [delegate audioSessionDidChangeRoute:self
868 reason:reason
869 previousRoute:previousRoute];
870 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800871 }
872}
873
874- (void)notifyMediaServicesWereLost {
tkchine54467f2016-03-15 16:54:03 -0700875 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800876 SEL sel = @selector(audioSessionMediaServerTerminated:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700877 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800878 [delegate audioSessionMediaServerTerminated:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700879 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800880 }
881}
882
883- (void)notifyMediaServicesWereReset {
tkchine54467f2016-03-15 16:54:03 -0700884 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800885 SEL sel = @selector(audioSessionMediaServerReset:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700886 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800887 [delegate audioSessionMediaServerReset:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700888 }
889 }
890}
891
tkchind2511962016-05-06 18:54:15 -0700892- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700893 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700894 SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700895 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700896 [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700897 }
898 }
899}
900
tkchind2511962016-05-06 18:54:15 -0700901- (void)notifyDidStartPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700902 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700903 SEL sel = @selector(audioSessionDidStartPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700904 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700905 [delegate audioSessionDidStartPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700906 }
907 }
908}
909
tkchind2511962016-05-06 18:54:15 -0700910- (void)notifyDidStopPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700911 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700912 SEL sel = @selector(audioSessionDidStopPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700913 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700914 [delegate audioSessionDidStopPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700915 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800916 }
917}
918
jtteh13ae11a2017-05-25 17:52:20 -0700919- (void)notifyDidChangeOutputVolume:(float)volume {
920 for (auto delegate : self.delegates) {
921 SEL sel = @selector(audioSession:didChangeOutputVolume:);
922 if ([delegate respondsToSelector:sel]) {
923 [delegate audioSession:self didChangeOutputVolume:volume];
924 }
925 }
926}
927
Anders Carlsson121ea322017-06-26 15:34:47 +0200928- (void)notifyDidDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches {
929 for (auto delegate : self.delegates) {
930 SEL sel = @selector(audioSession:didDetectPlayoutGlitch:);
931 if ([delegate respondsToSelector:sel]) {
932 [delegate audioSession:self didDetectPlayoutGlitch:totalNumberOfGlitches];
933 }
934 }
935}
936
JT Tehc1f083d2018-04-25 09:19:35 -0700937- (void)notifyWillSetActive:(BOOL)active {
938 for (id delegate : self.delegates) {
939 SEL sel = @selector(audioSession:willSetActive:);
940 if ([delegate respondsToSelector:sel]) {
941 [delegate audioSession:self willSetActive:active];
942 }
943 }
944}
945
946- (void)notifyDidSetActive:(BOOL)active {
947 for (id delegate : self.delegates) {
948 SEL sel = @selector(audioSession:didSetActive:);
949 if ([delegate respondsToSelector:sel]) {
950 [delegate audioSession:self didSetActive:active];
951 }
952 }
953}
954
955- (void)notifyFailedToSetActive:(BOOL)active error:(NSError *)error {
956 for (id delegate : self.delegates) {
957 SEL sel = @selector(audioSession:failedToSetActive:error:);
958 if ([delegate respondsToSelector:sel]) {
959 [delegate audioSession:self failedToSetActive:active error:error];
960 }
961 }
962}
963
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800964@end