blob: 43575b965229bb93328659c5198924e3969cf9c2 [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 {
59 if (self = [super init]) {
60 _session = [AVAudioSession sharedInstance];
tkchin0ce3bf92016-03-12 16:52:04 -080061
Zeke Chinb3fb71c2016-02-18 15:44:07 -080062 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
63 [center addObserver:self
64 selector:@selector(handleInterruptionNotification:)
65 name:AVAudioSessionInterruptionNotification
66 object:nil];
67 [center addObserver:self
68 selector:@selector(handleRouteChangeNotification:)
69 name:AVAudioSessionRouteChangeNotification
70 object:nil];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080071 [center addObserver:self
72 selector:@selector(handleMediaServicesWereLost:)
73 name:AVAudioSessionMediaServicesWereLostNotification
74 object:nil];
75 [center addObserver:self
76 selector:@selector(handleMediaServicesWereReset:)
77 name:AVAudioSessionMediaServicesWereResetNotification
78 object:nil];
henrikaf1363fd2016-09-27 06:06:44 -070079 // Posted on the main thread when the primary audio from other applications
80 // starts and stops. Foreground applications may use this notification as a
81 // hint to enable or disable audio that is secondary.
82 [center addObserver:self
83 selector:@selector(handleSilenceSecondaryAudioHintNotification:)
84 name:AVAudioSessionSilenceSecondaryAudioHintNotification
85 object:nil];
tkchin93dd6342016-07-27 10:17:14 -070086 // Also track foreground event in order to deal with interruption ended situation.
87 [center addObserver:self
88 selector:@selector(handleApplicationDidBecomeActive:)
89 name:UIApplicationDidBecomeActiveNotification
90 object:nil];
jtteh13ae11a2017-05-25 17:52:20 -070091 [_session addObserver:self
92 forKeyPath:kRTCAudioSessionOutputVolumeSelector
93 options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
94 context:nil];
95
haysc7735b1e2017-03-29 14:53:32 -070096 RTCLog(@"RTCAudioSession (%p): init.", self);
Zeke Chinb3fb71c2016-02-18 15:44:07 -080097 }
98 return self;
99}
100
101- (void)dealloc {
102 [[NSNotificationCenter defaultCenter] removeObserver:self];
jtteh13ae11a2017-05-25 17:52:20 -0700103 [_session removeObserver:self forKeyPath:kRTCAudioSessionOutputVolumeSelector context:nil];
haysc7735b1e2017-03-29 14:53:32 -0700104 RTCLog(@"RTCAudioSession (%p): dealloc.", self);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800105}
106
Zeke Chin1300caa2016-03-18 14:39:11 -0700107- (NSString *)description {
108 NSString *format =
109 @"RTCAudioSession: {\n"
tkchind2511962016-05-06 18:54:15 -0700110 " category: %@\n"
111 " categoryOptions: %ld\n"
112 " mode: %@\n"
Zeke Chin1300caa2016-03-18 14:39:11 -0700113 " isActive: %d\n"
114 " sampleRate: %.2f\n"
115 " IOBufferDuration: %f\n"
116 " outputNumberOfChannels: %ld\n"
117 " inputNumberOfChannels: %ld\n"
118 " outputLatency: %f\n"
119 " inputLatency: %f\n"
henrikac5aea652016-09-21 07:45:55 -0700120 " outputVolume: %f\n"
Zeke Chin1300caa2016-03-18 14:39:11 -0700121 "}";
122 NSString *description = [NSString stringWithFormat:format,
tkchind2511962016-05-06 18:54:15 -0700123 self.category, (long)self.categoryOptions, self.mode,
Zeke Chin1300caa2016-03-18 14:39:11 -0700124 self.isActive, self.sampleRate, self.IOBufferDuration,
125 self.outputNumberOfChannels, self.inputNumberOfChannels,
henrikac5aea652016-09-21 07:45:55 -0700126 self.outputLatency, self.inputLatency, self.outputVolume];
Zeke Chin1300caa2016-03-18 14:39:11 -0700127 return description;
128}
129
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800130- (void)setIsActive:(BOOL)isActive {
131 @synchronized(self) {
132 _isActive = isActive;
133 }
134}
135
136- (BOOL)isActive {
137 @synchronized(self) {
138 return _isActive;
139 }
140}
141
142- (BOOL)isLocked {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700143 return _lockRecursionCount > 0;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800144}
145
tkchind2511962016-05-06 18:54:15 -0700146- (void)setUseManualAudio:(BOOL)useManualAudio {
tkchin9f987d32016-03-12 20:06:28 -0800147 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700148 if (_useManualAudio == useManualAudio) {
tkchin9f987d32016-03-12 20:06:28 -0800149 return;
150 }
tkchind2511962016-05-06 18:54:15 -0700151 _useManualAudio = useManualAudio;
152 }
153 [self updateCanPlayOrRecord];
154}
155
156- (BOOL)useManualAudio {
157 @synchronized(self) {
158 return _useManualAudio;
tkchin9f987d32016-03-12 20:06:28 -0800159 }
160}
161
tkchind2511962016-05-06 18:54:15 -0700162- (void)setIsAudioEnabled:(BOOL)isAudioEnabled {
tkchin9f987d32016-03-12 20:06:28 -0800163 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700164 if (_isAudioEnabled == isAudioEnabled) {
165 return;
166 }
167 _isAudioEnabled = isAudioEnabled;
168 }
169 [self updateCanPlayOrRecord];
170}
171
172- (BOOL)isAudioEnabled {
173 @synchronized(self) {
174 return _isAudioEnabled;
tkchin9f987d32016-03-12 20:06:28 -0800175 }
176}
177
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700178// TODO(tkchin): Check for duplicates.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800179- (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate {
haysc7735b1e2017-03-29 14:53:32 -0700180 RTCLog(@"Adding delegate: (%p)", delegate);
tkchine54467f2016-03-15 16:54:03 -0700181 if (!delegate) {
182 return;
183 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800184 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700185 _delegates.push_back(delegate);
186 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800187 }
188}
189
190- (void)removeDelegate:(id<RTCAudioSessionDelegate>)delegate {
haysc7735b1e2017-03-29 14:53:32 -0700191 RTCLog(@"Removing delegate: (%p)", delegate);
tkchine54467f2016-03-15 16:54:03 -0700192 if (!delegate) {
193 return;
194 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800195 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700196 _delegates.erase(std::remove(_delegates.begin(),
197 _delegates.end(),
tkchinefdd9302016-04-11 12:00:59 -0700198 delegate),
199 _delegates.end());
tkchine54467f2016-03-15 16:54:03 -0700200 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800201 }
202}
203
kthelgasonee8b8612017-03-31 04:50:27 -0700204#pragma clang diagnostic push
205#pragma clang diagnostic ignored "-Wthread-safety-analysis"
206
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800207- (void)lockForConfiguration {
tkchin0ce3bf92016-03-12 16:52:04 -0800208 _crit.Enter();
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700209 rtc::AtomicOps::Increment(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800210}
211
212- (void)unlockForConfiguration {
213 // Don't let threads other than the one that called lockForConfiguration
214 // unlock.
tkchin0ce3bf92016-03-12 16:52:04 -0800215 if (_crit.TryEnter()) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700216 rtc::AtomicOps::Decrement(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800217 // One unlock for the tryLock, and another one to actually unlock. If this
tkchin0ce3bf92016-03-12 16:52:04 -0800218 // was called without anyone calling lock, we will hit an assertion.
219 _crit.Leave();
220 _crit.Leave();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800221 }
222}
223
kthelgasonee8b8612017-03-31 04:50:27 -0700224#pragma clang diagnostic pop
225
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800226#pragma mark - AVAudioSession proxy methods
227
228- (NSString *)category {
229 return self.session.category;
230}
231
232- (AVAudioSessionCategoryOptions)categoryOptions {
233 return self.session.categoryOptions;
234}
235
236- (NSString *)mode {
237 return self.session.mode;
238}
239
240- (BOOL)secondaryAudioShouldBeSilencedHint {
241 return self.session.secondaryAudioShouldBeSilencedHint;
242}
243
244- (AVAudioSessionRouteDescription *)currentRoute {
245 return self.session.currentRoute;
246}
247
248- (NSInteger)maximumInputNumberOfChannels {
249 return self.session.maximumInputNumberOfChannels;
250}
251
252- (NSInteger)maximumOutputNumberOfChannels {
253 return self.session.maximumOutputNumberOfChannels;
254}
255
256- (float)inputGain {
257 return self.session.inputGain;
258}
259
260- (BOOL)inputGainSettable {
261 return self.session.inputGainSettable;
262}
263
264- (BOOL)inputAvailable {
265 return self.session.inputAvailable;
266}
267
268- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
269 return self.session.inputDataSources;
270}
271
272- (AVAudioSessionDataSourceDescription *)inputDataSource {
273 return self.session.inputDataSource;
274}
275
276- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
277 return self.session.outputDataSources;
278}
279
280- (AVAudioSessionDataSourceDescription *)outputDataSource {
281 return self.session.outputDataSource;
282}
283
284- (double)sampleRate {
285 return self.session.sampleRate;
286}
287
tkchind2511962016-05-06 18:54:15 -0700288- (double)preferredSampleRate {
289 return self.session.preferredSampleRate;
290}
291
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800292- (NSInteger)inputNumberOfChannels {
293 return self.session.inputNumberOfChannels;
294}
295
296- (NSInteger)outputNumberOfChannels {
297 return self.session.outputNumberOfChannels;
298}
299
300- (float)outputVolume {
301 return self.session.outputVolume;
302}
303
304- (NSTimeInterval)inputLatency {
305 return self.session.inputLatency;
306}
307
308- (NSTimeInterval)outputLatency {
309 return self.session.outputLatency;
310}
311
312- (NSTimeInterval)IOBufferDuration {
313 return self.session.IOBufferDuration;
314}
315
tkchind2511962016-05-06 18:54:15 -0700316- (NSTimeInterval)preferredIOBufferDuration {
317 return self.session.preferredIOBufferDuration;
318}
319
tkchine54467f2016-03-15 16:54:03 -0700320// TODO(tkchin): Simplify the amount of locking happening here. Likely that we
321// can just do atomic increments / decrements.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800322- (BOOL)setActive:(BOOL)active
323 error:(NSError **)outError {
324 if (![self checkLock:outError]) {
325 return NO;
326 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700327 int activationCount = _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800328 if (!active && activationCount == 0) {
329 RTCLogWarning(@"Attempting to deactivate without prior activation.");
330 }
331 BOOL success = YES;
332 BOOL isActive = self.isActive;
333 // Keep a local error so we can log it.
334 NSError *error = nil;
335 BOOL shouldSetActive =
336 (active && !isActive) || (!active && isActive && activationCount == 1);
337 // Attempt to activate if we're not active.
338 // Attempt to deactivate if we're active and it's the last unbalanced call.
339 if (shouldSetActive) {
340 AVAudioSession *session = self.session;
341 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
342 // that other audio sessions that were interrupted by our session can return
343 // to their active state. It is recommended for VoIP apps to use this
344 // option.
345 AVAudioSessionSetActiveOptions options =
346 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
347 success = [session setActive:active
348 withOptions:options
349 error:&error];
350 if (outError) {
351 *outError = error;
352 }
353 }
354 if (success) {
355 if (shouldSetActive) {
356 self.isActive = active;
357 }
358 if (active) {
359 [self incrementActivationCount];
360 }
361 } else {
tkchin9f987d32016-03-12 20:06:28 -0800362 RTCLogError(@"Failed to setActive:%d. Error: %@",
363 active, error.localizedDescription);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800364 }
365 // Decrement activation count on deactivation whether or not it succeeded.
366 if (!active) {
367 [self decrementActivationCount];
368 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700369 RTCLog(@"Number of current activations: %d", _activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800370 return success;
371}
372
373- (BOOL)setCategory:(NSString *)category
374 withOptions:(AVAudioSessionCategoryOptions)options
375 error:(NSError **)outError {
376 if (![self checkLock:outError]) {
377 return NO;
378 }
379 return [self.session setCategory:category withOptions:options error:outError];
380}
381
382- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
383 if (![self checkLock:outError]) {
384 return NO;
385 }
386 return [self.session setMode:mode error:outError];
387}
388
389- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
390 if (![self checkLock:outError]) {
391 return NO;
392 }
393 return [self.session setInputGain:gain error:outError];
394}
395
396- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
397 if (![self checkLock:outError]) {
398 return NO;
399 }
400 return [self.session setPreferredSampleRate:sampleRate error:outError];
401}
402
403- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
404 error:(NSError **)outError {
405 if (![self checkLock:outError]) {
406 return NO;
407 }
408 return [self.session setPreferredIOBufferDuration:duration error:outError];
409}
410
411- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
412 error:(NSError **)outError {
413 if (![self checkLock:outError]) {
414 return NO;
415 }
416 return [self.session setPreferredInputNumberOfChannels:count error:outError];
417}
418- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
419 error:(NSError **)outError {
420 if (![self checkLock:outError]) {
421 return NO;
422 }
423 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
424}
425
426- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
427 error:(NSError **)outError {
428 if (![self checkLock:outError]) {
429 return NO;
430 }
431 return [self.session overrideOutputAudioPort:portOverride error:outError];
432}
433
434- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
435 error:(NSError **)outError {
436 if (![self checkLock:outError]) {
437 return NO;
438 }
439 return [self.session setPreferredInput:inPort error:outError];
440}
441
442- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
443 error:(NSError **)outError {
444 if (![self checkLock:outError]) {
445 return NO;
446 }
447 return [self.session setInputDataSource:dataSource error:outError];
448}
449
450- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
451 error:(NSError **)outError {
452 if (![self checkLock:outError]) {
453 return NO;
454 }
455 return [self.session setOutputDataSource:dataSource error:outError];
456}
457
458#pragma mark - Notifications
459
460- (void)handleInterruptionNotification:(NSNotification *)notification {
461 NSNumber* typeNumber =
462 notification.userInfo[AVAudioSessionInterruptionTypeKey];
463 AVAudioSessionInterruptionType type =
464 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
465 switch (type) {
466 case AVAudioSessionInterruptionTypeBegan:
467 RTCLog(@"Audio session interruption began.");
468 self.isActive = NO;
tkchin93dd6342016-07-27 10:17:14 -0700469 self.isInterrupted = YES;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800470 [self notifyDidBeginInterruption];
471 break;
472 case AVAudioSessionInterruptionTypeEnded: {
473 RTCLog(@"Audio session interruption ended.");
tkchin93dd6342016-07-27 10:17:14 -0700474 self.isInterrupted = NO;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800475 [self updateAudioSessionAfterEvent];
476 NSNumber *optionsNumber =
477 notification.userInfo[AVAudioSessionInterruptionOptionKey];
478 AVAudioSessionInterruptionOptions options =
479 optionsNumber.unsignedIntegerValue;
480 BOOL shouldResume =
481 options & AVAudioSessionInterruptionOptionShouldResume;
482 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
483 break;
484 }
485 }
486}
487
488- (void)handleRouteChangeNotification:(NSNotification *)notification {
489 // Get reason for current route change.
490 NSNumber* reasonNumber =
491 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
492 AVAudioSessionRouteChangeReason reason =
493 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
494 RTCLog(@"Audio route changed:");
495 switch (reason) {
496 case AVAudioSessionRouteChangeReasonUnknown:
497 RTCLog(@"Audio route changed: ReasonUnknown");
498 break;
499 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
500 RTCLog(@"Audio route changed: NewDeviceAvailable");
501 break;
502 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
503 RTCLog(@"Audio route changed: OldDeviceUnavailable");
504 break;
505 case AVAudioSessionRouteChangeReasonCategoryChange:
506 RTCLog(@"Audio route changed: CategoryChange to :%@",
507 self.session.category);
508 break;
509 case AVAudioSessionRouteChangeReasonOverride:
510 RTCLog(@"Audio route changed: Override");
511 break;
512 case AVAudioSessionRouteChangeReasonWakeFromSleep:
513 RTCLog(@"Audio route changed: WakeFromSleep");
514 break;
515 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
516 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
517 break;
518 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
519 RTCLog(@"Audio route changed: RouteConfigurationChange");
520 break;
521 }
522 AVAudioSessionRouteDescription* previousRoute =
523 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
524 // Log previous route configuration.
525 RTCLog(@"Previous route: %@\nCurrent route:%@",
526 previousRoute, self.session.currentRoute);
527 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
528}
529
530- (void)handleMediaServicesWereLost:(NSNotification *)notification {
531 RTCLog(@"Media services were lost.");
532 [self updateAudioSessionAfterEvent];
533 [self notifyMediaServicesWereLost];
534}
535
536- (void)handleMediaServicesWereReset:(NSNotification *)notification {
537 RTCLog(@"Media services were reset.");
538 [self updateAudioSessionAfterEvent];
539 [self notifyMediaServicesWereReset];
540}
541
henrikaf1363fd2016-09-27 06:06:44 -0700542- (void)handleSilenceSecondaryAudioHintNotification:(NSNotification *)notification {
543 // TODO(henrika): just adding logs here for now until we know if we are ever
544 // see this notification and might be affected by it or if further actions
545 // are required.
546 NSNumber *typeNumber =
547 notification.userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey];
548 AVAudioSessionSilenceSecondaryAudioHintType type =
549 (AVAudioSessionSilenceSecondaryAudioHintType)typeNumber.unsignedIntegerValue;
550 switch (type) {
551 case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
552 RTCLog(@"Another application's primary audio has started.");
553 break;
554 case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
555 RTCLog(@"Another application's primary audio has stopped.");
556 break;
557 }
558}
559
tkchin93dd6342016-07-27 10:17:14 -0700560- (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
haysc7735b1e2017-03-29 14:53:32 -0700561 RTCLog(@"Application became active after an interruption. Treating as interruption "
562 " end. isInterrupted changed from %d to 0.", self.isInterrupted);
tkchin93dd6342016-07-27 10:17:14 -0700563 if (self.isInterrupted) {
tkchin93dd6342016-07-27 10:17:14 -0700564 self.isInterrupted = NO;
565 [self updateAudioSessionAfterEvent];
tkchin93dd6342016-07-27 10:17:14 -0700566 }
haysc7735b1e2017-03-29 14:53:32 -0700567 // Always treat application becoming active as an interruption end event.
568 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
tkchin93dd6342016-07-27 10:17:14 -0700569}
570
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800571#pragma mark - Private
572
573+ (NSError *)lockError {
574 NSDictionary *userInfo = @{
575 NSLocalizedDescriptionKey:
576 @"Must call lockForConfiguration before calling this method."
577 };
578 NSError *error =
579 [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
580 code:kRTCAudioSessionErrorLockRequired
581 userInfo:userInfo];
582 return error;
583}
584
tkchine54467f2016-03-15 16:54:03 -0700585- (std::vector<__weak id<RTCAudioSessionDelegate> >)delegates {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800586 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700587 // Note: this returns a copy.
588 return _delegates;
589 }
590}
591
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700592// TODO(tkchin): check for duplicates.
tkchine54467f2016-03-15 16:54:03 -0700593- (void)pushDelegate:(id<RTCAudioSessionDelegate>)delegate {
594 @synchronized(self) {
595 _delegates.insert(_delegates.begin(), delegate);
596 }
597}
598
599- (void)removeZeroedDelegates {
600 @synchronized(self) {
tkchinefdd9302016-04-11 12:00:59 -0700601 _delegates.erase(
602 std::remove_if(_delegates.begin(),
603 _delegates.end(),
604 [](id delegate) -> bool { return delegate == nil; }),
605 _delegates.end());
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800606 }
607}
608
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700609- (int)activationCount {
610 return _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800611}
612
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700613- (int)incrementActivationCount {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800614 RTCLog(@"Incrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700615 return rtc::AtomicOps::Increment(&_activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800616}
617
618- (NSInteger)decrementActivationCount {
619 RTCLog(@"Decrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700620 return rtc::AtomicOps::Decrement(&_activationCount);
621}
622
623- (int)webRTCSessionCount {
624 return _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800625}
626
tkchind2511962016-05-06 18:54:15 -0700627- (BOOL)canPlayOrRecord {
628 return !self.useManualAudio || self.isAudioEnabled;
629}
630
tkchin93dd6342016-07-27 10:17:14 -0700631- (BOOL)isInterrupted {
632 @synchronized(self) {
633 return _isInterrupted;
634 }
635}
636
637- (void)setIsInterrupted:(BOOL)isInterrupted {
638 @synchronized(self) {
639 if (_isInterrupted == isInterrupted) {
640 return;
641 }
642 _isInterrupted = isInterrupted;
643 }
644}
645
tkchin9f987d32016-03-12 20:06:28 -0800646- (BOOL)checkLock:(NSError **)outError {
647 // Check ivar instead of trying to acquire lock so that we won't accidentally
648 // acquire lock if it hasn't already been called.
649 if (!self.isLocked) {
650 if (outError) {
651 *outError = [RTCAudioSession lockError];
652 }
653 return NO;
654 }
655 return YES;
656}
657
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700658- (BOOL)beginWebRTCSession:(NSError **)outError {
659 if (outError) {
660 *outError = nil;
661 }
662 if (![self checkLock:outError]) {
663 return NO;
664 }
tkchind2511962016-05-06 18:54:15 -0700665 rtc::AtomicOps::Increment(&_webRTCSessionCount);
666 [self notifyDidStartPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700667 return YES;
668}
669
670- (BOOL)endWebRTCSession:(NSError **)outError {
671 if (outError) {
672 *outError = nil;
673 }
674 if (![self checkLock:outError]) {
675 return NO;
676 }
tkchind2511962016-05-06 18:54:15 -0700677 rtc::AtomicOps::Decrement(&_webRTCSessionCount);
678 [self notifyDidStopPlayOrRecord];
679 return YES;
680}
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700681
tkchind2511962016-05-06 18:54:15 -0700682- (BOOL)configureWebRTCSession:(NSError **)outError {
683 if (outError) {
684 *outError = nil;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700685 }
tkchind2511962016-05-06 18:54:15 -0700686 if (![self checkLock:outError]) {
687 return NO;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700688 }
tkchind2511962016-05-06 18:54:15 -0700689 RTCLog(@"Configuring audio session for WebRTC.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700690
tkchind2511962016-05-06 18:54:15 -0700691 // Configure the AVAudioSession and activate it.
692 // Provide an error even if there isn't one so we can log it.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700693 NSError *error = nil;
tkchind2511962016-05-06 18:54:15 -0700694 RTCAudioSessionConfiguration *webRTCConfig =
695 [RTCAudioSessionConfiguration webRTCConfiguration];
696 if (![self setConfiguration:webRTCConfig active:YES error:&error]) {
697 RTCLogError(@"Failed to set WebRTC audio configuration: %@",
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700698 error.localizedDescription);
jttehf84c1d62017-04-21 13:56:39 -0700699 // Do not call setActive:NO if setActive:YES failed.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700700 if (outError) {
701 *outError = error;
702 }
703 return NO;
704 }
705
tkchind2511962016-05-06 18:54:15 -0700706 // Ensure that the device currently supports audio input.
707 // TODO(tkchin): Figure out if this is really necessary.
708 if (!self.inputAvailable) {
709 RTCLogError(@"No audio input path is available!");
710 [self unconfigureWebRTCSession:nil];
711 if (outError) {
712 *outError = [self configurationErrorWithDescription:@"No input path."];
713 }
714 return NO;
715 }
716
henrika2d014be2016-06-16 14:26:55 +0200717 // It can happen (e.g. in combination with BT devices) that the attempt to set
718 // the preferred sample rate for WebRTC (48kHz) fails. If so, make a new
719 // configuration attempt using the sample rate that worked using the active
720 // audio session. A typical case is that only 8 or 16kHz can be set, e.g. in
721 // combination with BT headsets. Using this "trick" seems to avoid a state
722 // where Core Audio asks for a different number of audio frames than what the
723 // session's I/O buffer duration corresponds to.
724 // TODO(henrika): this fix resolves bugs.webrtc.org/6004 but it has only been
725 // tested on a limited set of iOS devices and BT devices.
726 double sessionSampleRate = self.sampleRate;
727 double preferredSampleRate = webRTCConfig.sampleRate;
728 if (sessionSampleRate != preferredSampleRate) {
729 RTCLogWarning(
730 @"Current sample rate (%.2f) is not the preferred rate (%.2f)",
731 sessionSampleRate, preferredSampleRate);
732 if (![self setPreferredSampleRate:sessionSampleRate
733 error:&error]) {
734 RTCLogError(@"Failed to set preferred sample rate: %@",
735 error.localizedDescription);
736 if (outError) {
737 *outError = error;
738 }
739 }
740 }
741
tkchind2511962016-05-06 18:54:15 -0700742 return YES;
743}
744
745- (BOOL)unconfigureWebRTCSession:(NSError **)outError {
746 if (outError) {
747 *outError = nil;
748 }
749 if (![self checkLock:outError]) {
750 return NO;
751 }
752 RTCLog(@"Unconfiguring audio session for WebRTC.");
753 [self setActive:NO error:outError];
754
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700755 return YES;
756}
757
758- (NSError *)configurationErrorWithDescription:(NSString *)description {
759 NSDictionary* userInfo = @{
760 NSLocalizedDescriptionKey: description,
761 };
762 return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
763 code:kRTCAudioSessionErrorConfiguration
764 userInfo:userInfo];
765}
766
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800767- (void)updateAudioSessionAfterEvent {
768 BOOL shouldActivate = self.activationCount > 0;
769 AVAudioSessionSetActiveOptions options = shouldActivate ?
770 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
771 NSError *error = nil;
772 if ([self.session setActive:shouldActivate
773 withOptions:options
774 error:&error]) {
775 self.isActive = shouldActivate;
776 } else {
777 RTCLogError(@"Failed to set session active to %d. Error:%@",
778 shouldActivate, error.localizedDescription);
779 }
780}
781
tkchind2511962016-05-06 18:54:15 -0700782- (void)updateCanPlayOrRecord {
783 BOOL canPlayOrRecord = NO;
784 BOOL shouldNotify = NO;
785 @synchronized(self) {
786 canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled;
787 if (_canPlayOrRecord == canPlayOrRecord) {
788 return;
789 }
790 _canPlayOrRecord = canPlayOrRecord;
791 shouldNotify = YES;
792 }
793 if (shouldNotify) {
794 [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord];
795 }
796}
797
jtteh3c9a6c02017-04-18 09:09:35 -0700798- (void)audioSessionDidActivate:(AVAudioSession *)session {
799 if (_session != session) {
800 RTCLogError(@"audioSessionDidActivate called on different AVAudioSession");
801 }
802 [self incrementActivationCount];
803 self.isActive = YES;
804}
805
806- (void)audioSessionDidDeactivate:(AVAudioSession *)session {
807 if (_session != session) {
808 RTCLogError(@"audioSessionDidDeactivate called on different AVAudioSession");
809 }
810 self.isActive = NO;
811 [self decrementActivationCount];
812}
813
jtteh13ae11a2017-05-25 17:52:20 -0700814- (void)observeValueForKeyPath:(NSString *)keyPath
815 ofObject:(id)object
816 change:(NSDictionary *)change
817 context:(void *)context {
818 if (object == _session) {
819 NSNumber *newVolume = change[NSKeyValueChangeNewKey];
820 RTCLog(@"OutputVolumeDidChange to %f", newVolume.floatValue);
821 [self notifyDidChangeOutputVolume:newVolume.floatValue];
822 } else {
823 [super observeValueForKeyPath:keyPath
824 ofObject:object
825 change:change
826 context:context];
827 }
828}
829
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800830- (void)notifyDidBeginInterruption {
tkchine54467f2016-03-15 16:54:03 -0700831 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700832 SEL sel = @selector(audioSessionDidBeginInterruption:);
833 if ([delegate respondsToSelector:sel]) {
834 [delegate audioSessionDidBeginInterruption:self];
835 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800836 }
837}
838
839- (void)notifyDidEndInterruptionWithShouldResumeSession:
840 (BOOL)shouldResumeSession {
tkchine54467f2016-03-15 16:54:03 -0700841 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700842 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
843 if ([delegate respondsToSelector:sel]) {
844 [delegate audioSessionDidEndInterruption:self
845 shouldResumeSession:shouldResumeSession];
846 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800847 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800848}
849
850- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
851 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -0700852 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700853 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
854 if ([delegate respondsToSelector:sel]) {
855 [delegate audioSessionDidChangeRoute:self
856 reason:reason
857 previousRoute:previousRoute];
858 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800859 }
860}
861
862- (void)notifyMediaServicesWereLost {
tkchine54467f2016-03-15 16:54:03 -0700863 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800864 SEL sel = @selector(audioSessionMediaServerTerminated:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700865 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800866 [delegate audioSessionMediaServerTerminated:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700867 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800868 }
869}
870
871- (void)notifyMediaServicesWereReset {
tkchine54467f2016-03-15 16:54:03 -0700872 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800873 SEL sel = @selector(audioSessionMediaServerReset:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700874 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800875 [delegate audioSessionMediaServerReset:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700876 }
877 }
878}
879
tkchind2511962016-05-06 18:54:15 -0700880- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700881 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700882 SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700883 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700884 [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700885 }
886 }
887}
888
tkchind2511962016-05-06 18:54:15 -0700889- (void)notifyDidStartPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700890 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700891 SEL sel = @selector(audioSessionDidStartPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700892 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700893 [delegate audioSessionDidStartPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700894 }
895 }
896}
897
tkchind2511962016-05-06 18:54:15 -0700898- (void)notifyDidStopPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700899 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700900 SEL sel = @selector(audioSessionDidStopPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700901 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700902 [delegate audioSessionDidStopPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700903 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800904 }
905}
906
jtteh13ae11a2017-05-25 17:52:20 -0700907- (void)notifyDidChangeOutputVolume:(float)volume {
908 for (auto delegate : self.delegates) {
909 SEL sel = @selector(audioSession:didChangeOutputVolume:);
910 if ([delegate respondsToSelector:sel]) {
911 [delegate audioSession:self didChangeOutputVolume:volume];
912 }
913 }
914}
915
Anders Carlsson121ea322017-06-26 15:34:47 +0200916- (void)notifyDidDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches {
917 for (auto delegate : self.delegates) {
918 SEL sel = @selector(audioSession:didDetectPlayoutGlitch:);
919 if ([delegate respondsToSelector:sel]) {
920 [delegate audioSession:self didDetectPlayoutGlitch:totalNumberOfGlitches];
921 }
922 }
923}
924
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800925@end