blob: 1d099397f3844c3bd5a73b4c4f7bcf9a9965f0df [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
11#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession.h"
12
tkchin93dd6342016-07-27 10:17:14 -070013#import <UIKit/UIKit.h>
14
Tze Kwang Chin307a0922016-03-21 13:57:40 -070015#include "webrtc/base/atomicops.h"
Zeke Chinb3fb71c2016-02-18 15:44:07 -080016#include "webrtc/base/checks.h"
tkchin0ce3bf92016-03-12 16:52:04 -080017#include "webrtc/base/criticalsection.h"
tkchine54467f2016-03-15 16:54:03 -070018#include "webrtc/modules/audio_device/ios/audio_device_ios.h"
Zeke Chinb3fb71c2016-02-18 15:44:07 -080019
tkchin9eeb6242016-04-27 01:54:20 -070020#import "WebRTC/RTCLogging.h"
Zeke Chinb3fb71c2016-02-18 15:44:07 -080021#import "webrtc/modules/audio_device/ios/objc/RTCAudioSession+Private.h"
tkchind2511962016-05-06 18:54:15 -070022#import "webrtc/modules/audio_device/ios/objc/RTCAudioSessionConfiguration.h"
Zeke Chinb3fb71c2016-02-18 15:44:07 -080023
24NSString * const kRTCAudioSessionErrorDomain = @"org.webrtc.RTCAudioSession";
25NSInteger const kRTCAudioSessionErrorLockRequired = -1;
tkchin9f987d32016-03-12 20:06:28 -080026NSInteger const kRTCAudioSessionErrorConfiguration = -2;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080027
28// This class needs to be thread-safe because it is accessed from many threads.
29// TODO(tkchin): Consider more granular locking. We're not expecting a lot of
30// lock contention so coarse locks should be fine for now.
31@implementation RTCAudioSession {
tkchin0ce3bf92016-03-12 16:52:04 -080032 rtc::CriticalSection _crit;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080033 AVAudioSession *_session;
Tze Kwang Chin307a0922016-03-21 13:57:40 -070034 volatile int _activationCount;
35 volatile int _lockRecursionCount;
36 volatile int _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080037 BOOL _isActive;
tkchind2511962016-05-06 18:54:15 -070038 BOOL _useManualAudio;
39 BOOL _isAudioEnabled;
40 BOOL _canPlayOrRecord;
tkchin93dd6342016-07-27 10:17:14 -070041 BOOL _isInterrupted;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080042}
43
44@synthesize session = _session;
tkchine54467f2016-03-15 16:54:03 -070045@synthesize delegates = _delegates;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080046
47+ (instancetype)sharedInstance {
48 static dispatch_once_t onceToken;
49 static RTCAudioSession *sharedInstance = nil;
50 dispatch_once(&onceToken, ^{
Tze Kwang Chin307a0922016-03-21 13:57:40 -070051 sharedInstance = [[self alloc] init];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080052 });
53 return sharedInstance;
54}
55
56- (instancetype)init {
57 if (self = [super init]) {
58 _session = [AVAudioSession sharedInstance];
tkchin0ce3bf92016-03-12 16:52:04 -080059
Zeke Chinb3fb71c2016-02-18 15:44:07 -080060 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
61 [center addObserver:self
62 selector:@selector(handleInterruptionNotification:)
63 name:AVAudioSessionInterruptionNotification
64 object:nil];
65 [center addObserver:self
66 selector:@selector(handleRouteChangeNotification:)
67 name:AVAudioSessionRouteChangeNotification
68 object:nil];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080069 [center addObserver:self
70 selector:@selector(handleMediaServicesWereLost:)
71 name:AVAudioSessionMediaServicesWereLostNotification
72 object:nil];
73 [center addObserver:self
74 selector:@selector(handleMediaServicesWereReset:)
75 name:AVAudioSessionMediaServicesWereResetNotification
76 object:nil];
henrikaf1363fd2016-09-27 06:06:44 -070077 // Posted on the main thread when the primary audio from other applications
78 // starts and stops. Foreground applications may use this notification as a
79 // hint to enable or disable audio that is secondary.
80 [center addObserver:self
81 selector:@selector(handleSilenceSecondaryAudioHintNotification:)
82 name:AVAudioSessionSilenceSecondaryAudioHintNotification
83 object:nil];
tkchin93dd6342016-07-27 10:17:14 -070084 // Also track foreground event in order to deal with interruption ended situation.
85 [center addObserver:self
86 selector:@selector(handleApplicationDidBecomeActive:)
87 name:UIApplicationDidBecomeActiveNotification
88 object:nil];
haysc7735b1e2017-03-29 14:53:32 -070089 RTCLog(@"RTCAudioSession (%p): init.", self);
Zeke Chinb3fb71c2016-02-18 15:44:07 -080090 }
91 return self;
92}
93
94- (void)dealloc {
95 [[NSNotificationCenter defaultCenter] removeObserver:self];
haysc7735b1e2017-03-29 14:53:32 -070096 RTCLog(@"RTCAudioSession (%p): dealloc.", self);
Zeke Chinb3fb71c2016-02-18 15:44:07 -080097}
98
Zeke Chin1300caa2016-03-18 14:39:11 -070099- (NSString *)description {
100 NSString *format =
101 @"RTCAudioSession: {\n"
tkchind2511962016-05-06 18:54:15 -0700102 " category: %@\n"
103 " categoryOptions: %ld\n"
104 " mode: %@\n"
Zeke Chin1300caa2016-03-18 14:39:11 -0700105 " isActive: %d\n"
106 " sampleRate: %.2f\n"
107 " IOBufferDuration: %f\n"
108 " outputNumberOfChannels: %ld\n"
109 " inputNumberOfChannels: %ld\n"
110 " outputLatency: %f\n"
111 " inputLatency: %f\n"
henrikac5aea652016-09-21 07:45:55 -0700112 " outputVolume: %f\n"
Zeke Chin1300caa2016-03-18 14:39:11 -0700113 "}";
114 NSString *description = [NSString stringWithFormat:format,
tkchind2511962016-05-06 18:54:15 -0700115 self.category, (long)self.categoryOptions, self.mode,
Zeke Chin1300caa2016-03-18 14:39:11 -0700116 self.isActive, self.sampleRate, self.IOBufferDuration,
117 self.outputNumberOfChannels, self.inputNumberOfChannels,
henrikac5aea652016-09-21 07:45:55 -0700118 self.outputLatency, self.inputLatency, self.outputVolume];
Zeke Chin1300caa2016-03-18 14:39:11 -0700119 return description;
120}
121
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800122- (void)setIsActive:(BOOL)isActive {
123 @synchronized(self) {
124 _isActive = isActive;
125 }
126}
127
128- (BOOL)isActive {
129 @synchronized(self) {
130 return _isActive;
131 }
132}
133
134- (BOOL)isLocked {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700135 return _lockRecursionCount > 0;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800136}
137
tkchind2511962016-05-06 18:54:15 -0700138- (void)setUseManualAudio:(BOOL)useManualAudio {
tkchin9f987d32016-03-12 20:06:28 -0800139 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700140 if (_useManualAudio == useManualAudio) {
tkchin9f987d32016-03-12 20:06:28 -0800141 return;
142 }
tkchind2511962016-05-06 18:54:15 -0700143 _useManualAudio = useManualAudio;
144 }
145 [self updateCanPlayOrRecord];
146}
147
148- (BOOL)useManualAudio {
149 @synchronized(self) {
150 return _useManualAudio;
tkchin9f987d32016-03-12 20:06:28 -0800151 }
152}
153
tkchind2511962016-05-06 18:54:15 -0700154- (void)setIsAudioEnabled:(BOOL)isAudioEnabled {
tkchin9f987d32016-03-12 20:06:28 -0800155 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700156 if (_isAudioEnabled == isAudioEnabled) {
157 return;
158 }
159 _isAudioEnabled = isAudioEnabled;
160 }
161 [self updateCanPlayOrRecord];
162}
163
164- (BOOL)isAudioEnabled {
165 @synchronized(self) {
166 return _isAudioEnabled;
tkchin9f987d32016-03-12 20:06:28 -0800167 }
168}
169
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700170// TODO(tkchin): Check for duplicates.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800171- (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate {
haysc7735b1e2017-03-29 14:53:32 -0700172 RTCLog(@"Adding delegate: (%p)", delegate);
tkchine54467f2016-03-15 16:54:03 -0700173 if (!delegate) {
174 return;
175 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800176 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700177 _delegates.push_back(delegate);
178 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800179 }
180}
181
182- (void)removeDelegate:(id<RTCAudioSessionDelegate>)delegate {
haysc7735b1e2017-03-29 14:53:32 -0700183 RTCLog(@"Removing delegate: (%p)", delegate);
tkchine54467f2016-03-15 16:54:03 -0700184 if (!delegate) {
185 return;
186 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800187 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700188 _delegates.erase(std::remove(_delegates.begin(),
189 _delegates.end(),
tkchinefdd9302016-04-11 12:00:59 -0700190 delegate),
191 _delegates.end());
tkchine54467f2016-03-15 16:54:03 -0700192 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800193 }
194}
195
196- (void)lockForConfiguration {
tkchin0ce3bf92016-03-12 16:52:04 -0800197 _crit.Enter();
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700198 rtc::AtomicOps::Increment(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800199}
200
201- (void)unlockForConfiguration {
202 // Don't let threads other than the one that called lockForConfiguration
203 // unlock.
tkchin0ce3bf92016-03-12 16:52:04 -0800204 if (_crit.TryEnter()) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700205 rtc::AtomicOps::Decrement(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800206 // One unlock for the tryLock, and another one to actually unlock. If this
tkchin0ce3bf92016-03-12 16:52:04 -0800207 // was called without anyone calling lock, we will hit an assertion.
208 _crit.Leave();
209 _crit.Leave();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800210 }
211}
212
213#pragma mark - AVAudioSession proxy methods
214
215- (NSString *)category {
216 return self.session.category;
217}
218
219- (AVAudioSessionCategoryOptions)categoryOptions {
220 return self.session.categoryOptions;
221}
222
223- (NSString *)mode {
224 return self.session.mode;
225}
226
227- (BOOL)secondaryAudioShouldBeSilencedHint {
228 return self.session.secondaryAudioShouldBeSilencedHint;
229}
230
231- (AVAudioSessionRouteDescription *)currentRoute {
232 return self.session.currentRoute;
233}
234
235- (NSInteger)maximumInputNumberOfChannels {
236 return self.session.maximumInputNumberOfChannels;
237}
238
239- (NSInteger)maximumOutputNumberOfChannels {
240 return self.session.maximumOutputNumberOfChannels;
241}
242
243- (float)inputGain {
244 return self.session.inputGain;
245}
246
247- (BOOL)inputGainSettable {
248 return self.session.inputGainSettable;
249}
250
251- (BOOL)inputAvailable {
252 return self.session.inputAvailable;
253}
254
255- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
256 return self.session.inputDataSources;
257}
258
259- (AVAudioSessionDataSourceDescription *)inputDataSource {
260 return self.session.inputDataSource;
261}
262
263- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
264 return self.session.outputDataSources;
265}
266
267- (AVAudioSessionDataSourceDescription *)outputDataSource {
268 return self.session.outputDataSource;
269}
270
271- (double)sampleRate {
272 return self.session.sampleRate;
273}
274
tkchind2511962016-05-06 18:54:15 -0700275- (double)preferredSampleRate {
276 return self.session.preferredSampleRate;
277}
278
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800279- (NSInteger)inputNumberOfChannels {
280 return self.session.inputNumberOfChannels;
281}
282
283- (NSInteger)outputNumberOfChannels {
284 return self.session.outputNumberOfChannels;
285}
286
287- (float)outputVolume {
288 return self.session.outputVolume;
289}
290
291- (NSTimeInterval)inputLatency {
292 return self.session.inputLatency;
293}
294
295- (NSTimeInterval)outputLatency {
296 return self.session.outputLatency;
297}
298
299- (NSTimeInterval)IOBufferDuration {
300 return self.session.IOBufferDuration;
301}
302
tkchind2511962016-05-06 18:54:15 -0700303- (NSTimeInterval)preferredIOBufferDuration {
304 return self.session.preferredIOBufferDuration;
305}
306
tkchine54467f2016-03-15 16:54:03 -0700307// TODO(tkchin): Simplify the amount of locking happening here. Likely that we
308// can just do atomic increments / decrements.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800309- (BOOL)setActive:(BOOL)active
310 error:(NSError **)outError {
311 if (![self checkLock:outError]) {
312 return NO;
313 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700314 int activationCount = _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800315 if (!active && activationCount == 0) {
316 RTCLogWarning(@"Attempting to deactivate without prior activation.");
317 }
318 BOOL success = YES;
319 BOOL isActive = self.isActive;
320 // Keep a local error so we can log it.
321 NSError *error = nil;
322 BOOL shouldSetActive =
323 (active && !isActive) || (!active && isActive && activationCount == 1);
324 // Attempt to activate if we're not active.
325 // Attempt to deactivate if we're active and it's the last unbalanced call.
326 if (shouldSetActive) {
327 AVAudioSession *session = self.session;
328 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
329 // that other audio sessions that were interrupted by our session can return
330 // to their active state. It is recommended for VoIP apps to use this
331 // option.
332 AVAudioSessionSetActiveOptions options =
333 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
334 success = [session setActive:active
335 withOptions:options
336 error:&error];
337 if (outError) {
338 *outError = error;
339 }
340 }
341 if (success) {
342 if (shouldSetActive) {
343 self.isActive = active;
344 }
345 if (active) {
346 [self incrementActivationCount];
347 }
348 } else {
tkchin9f987d32016-03-12 20:06:28 -0800349 RTCLogError(@"Failed to setActive:%d. Error: %@",
350 active, error.localizedDescription);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800351 }
352 // Decrement activation count on deactivation whether or not it succeeded.
353 if (!active) {
354 [self decrementActivationCount];
355 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700356 RTCLog(@"Number of current activations: %d", _activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800357 return success;
358}
359
360- (BOOL)setCategory:(NSString *)category
361 withOptions:(AVAudioSessionCategoryOptions)options
362 error:(NSError **)outError {
363 if (![self checkLock:outError]) {
364 return NO;
365 }
366 return [self.session setCategory:category withOptions:options error:outError];
367}
368
369- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
370 if (![self checkLock:outError]) {
371 return NO;
372 }
373 return [self.session setMode:mode error:outError];
374}
375
376- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
377 if (![self checkLock:outError]) {
378 return NO;
379 }
380 return [self.session setInputGain:gain error:outError];
381}
382
383- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
384 if (![self checkLock:outError]) {
385 return NO;
386 }
387 return [self.session setPreferredSampleRate:sampleRate error:outError];
388}
389
390- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
391 error:(NSError **)outError {
392 if (![self checkLock:outError]) {
393 return NO;
394 }
395 return [self.session setPreferredIOBufferDuration:duration error:outError];
396}
397
398- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
399 error:(NSError **)outError {
400 if (![self checkLock:outError]) {
401 return NO;
402 }
403 return [self.session setPreferredInputNumberOfChannels:count error:outError];
404}
405- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
406 error:(NSError **)outError {
407 if (![self checkLock:outError]) {
408 return NO;
409 }
410 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
411}
412
413- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
414 error:(NSError **)outError {
415 if (![self checkLock:outError]) {
416 return NO;
417 }
418 return [self.session overrideOutputAudioPort:portOverride error:outError];
419}
420
421- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
422 error:(NSError **)outError {
423 if (![self checkLock:outError]) {
424 return NO;
425 }
426 return [self.session setPreferredInput:inPort error:outError];
427}
428
429- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
430 error:(NSError **)outError {
431 if (![self checkLock:outError]) {
432 return NO;
433 }
434 return [self.session setInputDataSource:dataSource error:outError];
435}
436
437- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
438 error:(NSError **)outError {
439 if (![self checkLock:outError]) {
440 return NO;
441 }
442 return [self.session setOutputDataSource:dataSource error:outError];
443}
444
445#pragma mark - Notifications
446
447- (void)handleInterruptionNotification:(NSNotification *)notification {
448 NSNumber* typeNumber =
449 notification.userInfo[AVAudioSessionInterruptionTypeKey];
450 AVAudioSessionInterruptionType type =
451 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
452 switch (type) {
453 case AVAudioSessionInterruptionTypeBegan:
454 RTCLog(@"Audio session interruption began.");
455 self.isActive = NO;
tkchin93dd6342016-07-27 10:17:14 -0700456 self.isInterrupted = YES;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800457 [self notifyDidBeginInterruption];
458 break;
459 case AVAudioSessionInterruptionTypeEnded: {
460 RTCLog(@"Audio session interruption ended.");
tkchin93dd6342016-07-27 10:17:14 -0700461 self.isInterrupted = NO;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800462 [self updateAudioSessionAfterEvent];
463 NSNumber *optionsNumber =
464 notification.userInfo[AVAudioSessionInterruptionOptionKey];
465 AVAudioSessionInterruptionOptions options =
466 optionsNumber.unsignedIntegerValue;
467 BOOL shouldResume =
468 options & AVAudioSessionInterruptionOptionShouldResume;
469 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
470 break;
471 }
472 }
473}
474
475- (void)handleRouteChangeNotification:(NSNotification *)notification {
476 // Get reason for current route change.
477 NSNumber* reasonNumber =
478 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
479 AVAudioSessionRouteChangeReason reason =
480 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
481 RTCLog(@"Audio route changed:");
482 switch (reason) {
483 case AVAudioSessionRouteChangeReasonUnknown:
484 RTCLog(@"Audio route changed: ReasonUnknown");
485 break;
486 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
487 RTCLog(@"Audio route changed: NewDeviceAvailable");
488 break;
489 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
490 RTCLog(@"Audio route changed: OldDeviceUnavailable");
491 break;
492 case AVAudioSessionRouteChangeReasonCategoryChange:
493 RTCLog(@"Audio route changed: CategoryChange to :%@",
494 self.session.category);
495 break;
496 case AVAudioSessionRouteChangeReasonOverride:
497 RTCLog(@"Audio route changed: Override");
498 break;
499 case AVAudioSessionRouteChangeReasonWakeFromSleep:
500 RTCLog(@"Audio route changed: WakeFromSleep");
501 break;
502 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
503 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
504 break;
505 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
506 RTCLog(@"Audio route changed: RouteConfigurationChange");
507 break;
508 }
509 AVAudioSessionRouteDescription* previousRoute =
510 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
511 // Log previous route configuration.
512 RTCLog(@"Previous route: %@\nCurrent route:%@",
513 previousRoute, self.session.currentRoute);
514 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
515}
516
517- (void)handleMediaServicesWereLost:(NSNotification *)notification {
518 RTCLog(@"Media services were lost.");
519 [self updateAudioSessionAfterEvent];
520 [self notifyMediaServicesWereLost];
521}
522
523- (void)handleMediaServicesWereReset:(NSNotification *)notification {
524 RTCLog(@"Media services were reset.");
525 [self updateAudioSessionAfterEvent];
526 [self notifyMediaServicesWereReset];
527}
528
henrikaf1363fd2016-09-27 06:06:44 -0700529- (void)handleSilenceSecondaryAudioHintNotification:(NSNotification *)notification {
530 // TODO(henrika): just adding logs here for now until we know if we are ever
531 // see this notification and might be affected by it or if further actions
532 // are required.
533 NSNumber *typeNumber =
534 notification.userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey];
535 AVAudioSessionSilenceSecondaryAudioHintType type =
536 (AVAudioSessionSilenceSecondaryAudioHintType)typeNumber.unsignedIntegerValue;
537 switch (type) {
538 case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
539 RTCLog(@"Another application's primary audio has started.");
540 break;
541 case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
542 RTCLog(@"Another application's primary audio has stopped.");
543 break;
544 }
545}
546
tkchin93dd6342016-07-27 10:17:14 -0700547- (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
haysc7735b1e2017-03-29 14:53:32 -0700548 RTCLog(@"Application became active after an interruption. Treating as interruption "
549 " end. isInterrupted changed from %d to 0.", self.isInterrupted);
tkchin93dd6342016-07-27 10:17:14 -0700550 if (self.isInterrupted) {
tkchin93dd6342016-07-27 10:17:14 -0700551 self.isInterrupted = NO;
552 [self updateAudioSessionAfterEvent];
tkchin93dd6342016-07-27 10:17:14 -0700553 }
haysc7735b1e2017-03-29 14:53:32 -0700554 // Always treat application becoming active as an interruption end event.
555 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
tkchin93dd6342016-07-27 10:17:14 -0700556}
557
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800558#pragma mark - Private
559
560+ (NSError *)lockError {
561 NSDictionary *userInfo = @{
562 NSLocalizedDescriptionKey:
563 @"Must call lockForConfiguration before calling this method."
564 };
565 NSError *error =
566 [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
567 code:kRTCAudioSessionErrorLockRequired
568 userInfo:userInfo];
569 return error;
570}
571
tkchine54467f2016-03-15 16:54:03 -0700572- (std::vector<__weak id<RTCAudioSessionDelegate> >)delegates {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800573 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700574 // Note: this returns a copy.
575 return _delegates;
576 }
577}
578
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700579// TODO(tkchin): check for duplicates.
tkchine54467f2016-03-15 16:54:03 -0700580- (void)pushDelegate:(id<RTCAudioSessionDelegate>)delegate {
581 @synchronized(self) {
582 _delegates.insert(_delegates.begin(), delegate);
583 }
584}
585
586- (void)removeZeroedDelegates {
587 @synchronized(self) {
tkchinefdd9302016-04-11 12:00:59 -0700588 _delegates.erase(
589 std::remove_if(_delegates.begin(),
590 _delegates.end(),
591 [](id delegate) -> bool { return delegate == nil; }),
592 _delegates.end());
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800593 }
594}
595
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700596- (int)activationCount {
597 return _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800598}
599
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700600- (int)incrementActivationCount {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800601 RTCLog(@"Incrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700602 return rtc::AtomicOps::Increment(&_activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800603}
604
605- (NSInteger)decrementActivationCount {
606 RTCLog(@"Decrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700607 return rtc::AtomicOps::Decrement(&_activationCount);
608}
609
610- (int)webRTCSessionCount {
611 return _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800612}
613
tkchind2511962016-05-06 18:54:15 -0700614- (BOOL)canPlayOrRecord {
615 return !self.useManualAudio || self.isAudioEnabled;
616}
617
tkchin93dd6342016-07-27 10:17:14 -0700618- (BOOL)isInterrupted {
619 @synchronized(self) {
620 return _isInterrupted;
621 }
622}
623
624- (void)setIsInterrupted:(BOOL)isInterrupted {
625 @synchronized(self) {
626 if (_isInterrupted == isInterrupted) {
627 return;
628 }
629 _isInterrupted = isInterrupted;
630 }
631}
632
tkchin9f987d32016-03-12 20:06:28 -0800633- (BOOL)checkLock:(NSError **)outError {
634 // Check ivar instead of trying to acquire lock so that we won't accidentally
635 // acquire lock if it hasn't already been called.
636 if (!self.isLocked) {
637 if (outError) {
638 *outError = [RTCAudioSession lockError];
639 }
640 return NO;
641 }
642 return YES;
643}
644
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700645- (BOOL)beginWebRTCSession:(NSError **)outError {
646 if (outError) {
647 *outError = nil;
648 }
649 if (![self checkLock:outError]) {
650 return NO;
651 }
tkchind2511962016-05-06 18:54:15 -0700652 rtc::AtomicOps::Increment(&_webRTCSessionCount);
653 [self notifyDidStartPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700654 return YES;
655}
656
657- (BOOL)endWebRTCSession:(NSError **)outError {
658 if (outError) {
659 *outError = nil;
660 }
661 if (![self checkLock:outError]) {
662 return NO;
663 }
tkchind2511962016-05-06 18:54:15 -0700664 rtc::AtomicOps::Decrement(&_webRTCSessionCount);
665 [self notifyDidStopPlayOrRecord];
666 return YES;
667}
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700668
tkchind2511962016-05-06 18:54:15 -0700669- (BOOL)configureWebRTCSession:(NSError **)outError {
670 if (outError) {
671 *outError = nil;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700672 }
tkchind2511962016-05-06 18:54:15 -0700673 if (![self checkLock:outError]) {
674 return NO;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700675 }
tkchind2511962016-05-06 18:54:15 -0700676 RTCLog(@"Configuring audio session for WebRTC.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700677
tkchind2511962016-05-06 18:54:15 -0700678 // Configure the AVAudioSession and activate it.
679 // Provide an error even if there isn't one so we can log it.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700680 NSError *error = nil;
tkchind2511962016-05-06 18:54:15 -0700681 RTCAudioSessionConfiguration *webRTCConfig =
682 [RTCAudioSessionConfiguration webRTCConfiguration];
683 if (![self setConfiguration:webRTCConfig active:YES error:&error]) {
684 RTCLogError(@"Failed to set WebRTC audio configuration: %@",
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700685 error.localizedDescription);
tkchind2511962016-05-06 18:54:15 -0700686 [self unconfigureWebRTCSession:nil];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700687 if (outError) {
688 *outError = error;
689 }
690 return NO;
691 }
692
tkchind2511962016-05-06 18:54:15 -0700693 // Ensure that the device currently supports audio input.
694 // TODO(tkchin): Figure out if this is really necessary.
695 if (!self.inputAvailable) {
696 RTCLogError(@"No audio input path is available!");
697 [self unconfigureWebRTCSession:nil];
698 if (outError) {
699 *outError = [self configurationErrorWithDescription:@"No input path."];
700 }
701 return NO;
702 }
703
henrika2d014be2016-06-16 14:26:55 +0200704 // It can happen (e.g. in combination with BT devices) that the attempt to set
705 // the preferred sample rate for WebRTC (48kHz) fails. If so, make a new
706 // configuration attempt using the sample rate that worked using the active
707 // audio session. A typical case is that only 8 or 16kHz can be set, e.g. in
708 // combination with BT headsets. Using this "trick" seems to avoid a state
709 // where Core Audio asks for a different number of audio frames than what the
710 // session's I/O buffer duration corresponds to.
711 // TODO(henrika): this fix resolves bugs.webrtc.org/6004 but it has only been
712 // tested on a limited set of iOS devices and BT devices.
713 double sessionSampleRate = self.sampleRate;
714 double preferredSampleRate = webRTCConfig.sampleRate;
715 if (sessionSampleRate != preferredSampleRate) {
716 RTCLogWarning(
717 @"Current sample rate (%.2f) is not the preferred rate (%.2f)",
718 sessionSampleRate, preferredSampleRate);
719 if (![self setPreferredSampleRate:sessionSampleRate
720 error:&error]) {
721 RTCLogError(@"Failed to set preferred sample rate: %@",
722 error.localizedDescription);
723 if (outError) {
724 *outError = error;
725 }
726 }
727 }
728
tkchind2511962016-05-06 18:54:15 -0700729 return YES;
730}
731
732- (BOOL)unconfigureWebRTCSession:(NSError **)outError {
733 if (outError) {
734 *outError = nil;
735 }
736 if (![self checkLock:outError]) {
737 return NO;
738 }
739 RTCLog(@"Unconfiguring audio session for WebRTC.");
740 [self setActive:NO error:outError];
741
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700742 return YES;
743}
744
745- (NSError *)configurationErrorWithDescription:(NSString *)description {
746 NSDictionary* userInfo = @{
747 NSLocalizedDescriptionKey: description,
748 };
749 return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
750 code:kRTCAudioSessionErrorConfiguration
751 userInfo:userInfo];
752}
753
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800754- (void)updateAudioSessionAfterEvent {
755 BOOL shouldActivate = self.activationCount > 0;
756 AVAudioSessionSetActiveOptions options = shouldActivate ?
757 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
758 NSError *error = nil;
759 if ([self.session setActive:shouldActivate
760 withOptions:options
761 error:&error]) {
762 self.isActive = shouldActivate;
763 } else {
764 RTCLogError(@"Failed to set session active to %d. Error:%@",
765 shouldActivate, error.localizedDescription);
766 }
767}
768
tkchind2511962016-05-06 18:54:15 -0700769- (void)updateCanPlayOrRecord {
770 BOOL canPlayOrRecord = NO;
771 BOOL shouldNotify = NO;
772 @synchronized(self) {
773 canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled;
774 if (_canPlayOrRecord == canPlayOrRecord) {
775 return;
776 }
777 _canPlayOrRecord = canPlayOrRecord;
778 shouldNotify = YES;
779 }
780 if (shouldNotify) {
781 [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord];
782 }
783}
784
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800785- (void)notifyDidBeginInterruption {
tkchine54467f2016-03-15 16:54:03 -0700786 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700787 SEL sel = @selector(audioSessionDidBeginInterruption:);
788 if ([delegate respondsToSelector:sel]) {
789 [delegate audioSessionDidBeginInterruption:self];
790 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800791 }
792}
793
794- (void)notifyDidEndInterruptionWithShouldResumeSession:
795 (BOOL)shouldResumeSession {
tkchine54467f2016-03-15 16:54:03 -0700796 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700797 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
798 if ([delegate respondsToSelector:sel]) {
799 [delegate audioSessionDidEndInterruption:self
800 shouldResumeSession:shouldResumeSession];
801 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800802 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800803}
804
805- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
806 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -0700807 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700808 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
809 if ([delegate respondsToSelector:sel]) {
810 [delegate audioSessionDidChangeRoute:self
811 reason:reason
812 previousRoute:previousRoute];
813 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800814 }
815}
816
817- (void)notifyMediaServicesWereLost {
tkchine54467f2016-03-15 16:54:03 -0700818 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800819 SEL sel = @selector(audioSessionMediaServerTerminated:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700820 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800821 [delegate audioSessionMediaServerTerminated:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700822 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800823 }
824}
825
826- (void)notifyMediaServicesWereReset {
tkchine54467f2016-03-15 16:54:03 -0700827 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800828 SEL sel = @selector(audioSessionMediaServerReset:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700829 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800830 [delegate audioSessionMediaServerReset:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700831 }
832 }
833}
834
tkchind2511962016-05-06 18:54:15 -0700835- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700836 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700837 SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700838 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700839 [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700840 }
841 }
842}
843
tkchind2511962016-05-06 18:54:15 -0700844- (void)notifyDidStartPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700845 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700846 SEL sel = @selector(audioSessionDidStartPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700847 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700848 [delegate audioSessionDidStartPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700849 }
850 }
851}
852
tkchind2511962016-05-06 18:54:15 -0700853- (void)notifyDidStopPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700854 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700855 SEL sel = @selector(audioSessionDidStopPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700856 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700857 [delegate audioSessionDidStopPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700858 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800859 }
860}
861
862@end