blob: 87fa5b6cdd3dae8762822702f480c831353dcfc9 [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];
69 // TODO(tkchin): Maybe listen to SilenceSecondaryAudioHintNotification.
70 [center addObserver:self
71 selector:@selector(handleMediaServicesWereLost:)
72 name:AVAudioSessionMediaServicesWereLostNotification
73 object:nil];
74 [center addObserver:self
75 selector:@selector(handleMediaServicesWereReset:)
76 name:AVAudioSessionMediaServicesWereResetNotification
77 object:nil];
tkchin93dd6342016-07-27 10:17:14 -070078 // Also track foreground event in order to deal with interruption ended situation.
79 [center addObserver:self
80 selector:@selector(handleApplicationDidBecomeActive:)
81 name:UIApplicationDidBecomeActiveNotification
82 object:nil];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080083 }
84 return self;
85}
86
87- (void)dealloc {
88 [[NSNotificationCenter defaultCenter] removeObserver:self];
89}
90
Zeke Chin1300caa2016-03-18 14:39:11 -070091- (NSString *)description {
92 NSString *format =
93 @"RTCAudioSession: {\n"
tkchind2511962016-05-06 18:54:15 -070094 " category: %@\n"
95 " categoryOptions: %ld\n"
96 " mode: %@\n"
Zeke Chin1300caa2016-03-18 14:39:11 -070097 " isActive: %d\n"
98 " sampleRate: %.2f\n"
99 " IOBufferDuration: %f\n"
100 " outputNumberOfChannels: %ld\n"
101 " inputNumberOfChannels: %ld\n"
102 " outputLatency: %f\n"
103 " inputLatency: %f\n"
henrikac5aea652016-09-21 07:45:55 -0700104 " outputVolume: %f\n"
Zeke Chin1300caa2016-03-18 14:39:11 -0700105 "}";
106 NSString *description = [NSString stringWithFormat:format,
tkchind2511962016-05-06 18:54:15 -0700107 self.category, (long)self.categoryOptions, self.mode,
Zeke Chin1300caa2016-03-18 14:39:11 -0700108 self.isActive, self.sampleRate, self.IOBufferDuration,
109 self.outputNumberOfChannels, self.inputNumberOfChannels,
henrikac5aea652016-09-21 07:45:55 -0700110 self.outputLatency, self.inputLatency, self.outputVolume];
Zeke Chin1300caa2016-03-18 14:39:11 -0700111 return description;
112}
113
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800114- (void)setIsActive:(BOOL)isActive {
115 @synchronized(self) {
116 _isActive = isActive;
117 }
118}
119
120- (BOOL)isActive {
121 @synchronized(self) {
122 return _isActive;
123 }
124}
125
126- (BOOL)isLocked {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700127 return _lockRecursionCount > 0;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800128}
129
tkchind2511962016-05-06 18:54:15 -0700130- (void)setUseManualAudio:(BOOL)useManualAudio {
tkchin9f987d32016-03-12 20:06:28 -0800131 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700132 if (_useManualAudio == useManualAudio) {
tkchin9f987d32016-03-12 20:06:28 -0800133 return;
134 }
tkchind2511962016-05-06 18:54:15 -0700135 _useManualAudio = useManualAudio;
136 }
137 [self updateCanPlayOrRecord];
138}
139
140- (BOOL)useManualAudio {
141 @synchronized(self) {
142 return _useManualAudio;
tkchin9f987d32016-03-12 20:06:28 -0800143 }
144}
145
tkchind2511962016-05-06 18:54:15 -0700146- (void)setIsAudioEnabled:(BOOL)isAudioEnabled {
tkchin9f987d32016-03-12 20:06:28 -0800147 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700148 if (_isAudioEnabled == isAudioEnabled) {
149 return;
150 }
151 _isAudioEnabled = isAudioEnabled;
152 }
153 [self updateCanPlayOrRecord];
154}
155
156- (BOOL)isAudioEnabled {
157 @synchronized(self) {
158 return _isAudioEnabled;
tkchin9f987d32016-03-12 20:06:28 -0800159 }
160}
161
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700162// TODO(tkchin): Check for duplicates.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800163- (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate {
tkchine54467f2016-03-15 16:54:03 -0700164 if (!delegate) {
165 return;
166 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800167 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700168 _delegates.push_back(delegate);
169 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800170 }
171}
172
173- (void)removeDelegate:(id<RTCAudioSessionDelegate>)delegate {
tkchine54467f2016-03-15 16:54:03 -0700174 if (!delegate) {
175 return;
176 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800177 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700178 _delegates.erase(std::remove(_delegates.begin(),
179 _delegates.end(),
tkchinefdd9302016-04-11 12:00:59 -0700180 delegate),
181 _delegates.end());
tkchine54467f2016-03-15 16:54:03 -0700182 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800183 }
184}
185
186- (void)lockForConfiguration {
tkchin0ce3bf92016-03-12 16:52:04 -0800187 _crit.Enter();
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700188 rtc::AtomicOps::Increment(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800189}
190
191- (void)unlockForConfiguration {
192 // Don't let threads other than the one that called lockForConfiguration
193 // unlock.
tkchin0ce3bf92016-03-12 16:52:04 -0800194 if (_crit.TryEnter()) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700195 rtc::AtomicOps::Decrement(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800196 // One unlock for the tryLock, and another one to actually unlock. If this
tkchin0ce3bf92016-03-12 16:52:04 -0800197 // was called without anyone calling lock, we will hit an assertion.
198 _crit.Leave();
199 _crit.Leave();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800200 }
201}
202
203#pragma mark - AVAudioSession proxy methods
204
205- (NSString *)category {
206 return self.session.category;
207}
208
209- (AVAudioSessionCategoryOptions)categoryOptions {
210 return self.session.categoryOptions;
211}
212
213- (NSString *)mode {
214 return self.session.mode;
215}
216
217- (BOOL)secondaryAudioShouldBeSilencedHint {
218 return self.session.secondaryAudioShouldBeSilencedHint;
219}
220
221- (AVAudioSessionRouteDescription *)currentRoute {
222 return self.session.currentRoute;
223}
224
225- (NSInteger)maximumInputNumberOfChannels {
226 return self.session.maximumInputNumberOfChannels;
227}
228
229- (NSInteger)maximumOutputNumberOfChannels {
230 return self.session.maximumOutputNumberOfChannels;
231}
232
233- (float)inputGain {
234 return self.session.inputGain;
235}
236
237- (BOOL)inputGainSettable {
238 return self.session.inputGainSettable;
239}
240
241- (BOOL)inputAvailable {
242 return self.session.inputAvailable;
243}
244
245- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
246 return self.session.inputDataSources;
247}
248
249- (AVAudioSessionDataSourceDescription *)inputDataSource {
250 return self.session.inputDataSource;
251}
252
253- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
254 return self.session.outputDataSources;
255}
256
257- (AVAudioSessionDataSourceDescription *)outputDataSource {
258 return self.session.outputDataSource;
259}
260
261- (double)sampleRate {
262 return self.session.sampleRate;
263}
264
tkchind2511962016-05-06 18:54:15 -0700265- (double)preferredSampleRate {
266 return self.session.preferredSampleRate;
267}
268
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800269- (NSInteger)inputNumberOfChannels {
270 return self.session.inputNumberOfChannels;
271}
272
273- (NSInteger)outputNumberOfChannels {
274 return self.session.outputNumberOfChannels;
275}
276
277- (float)outputVolume {
278 return self.session.outputVolume;
279}
280
281- (NSTimeInterval)inputLatency {
282 return self.session.inputLatency;
283}
284
285- (NSTimeInterval)outputLatency {
286 return self.session.outputLatency;
287}
288
289- (NSTimeInterval)IOBufferDuration {
290 return self.session.IOBufferDuration;
291}
292
tkchind2511962016-05-06 18:54:15 -0700293- (NSTimeInterval)preferredIOBufferDuration {
294 return self.session.preferredIOBufferDuration;
295}
296
tkchine54467f2016-03-15 16:54:03 -0700297// TODO(tkchin): Simplify the amount of locking happening here. Likely that we
298// can just do atomic increments / decrements.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800299- (BOOL)setActive:(BOOL)active
300 error:(NSError **)outError {
301 if (![self checkLock:outError]) {
302 return NO;
303 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700304 int activationCount = _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800305 if (!active && activationCount == 0) {
306 RTCLogWarning(@"Attempting to deactivate without prior activation.");
307 }
308 BOOL success = YES;
309 BOOL isActive = self.isActive;
310 // Keep a local error so we can log it.
311 NSError *error = nil;
312 BOOL shouldSetActive =
313 (active && !isActive) || (!active && isActive && activationCount == 1);
314 // Attempt to activate if we're not active.
315 // Attempt to deactivate if we're active and it's the last unbalanced call.
316 if (shouldSetActive) {
317 AVAudioSession *session = self.session;
318 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
319 // that other audio sessions that were interrupted by our session can return
320 // to their active state. It is recommended for VoIP apps to use this
321 // option.
322 AVAudioSessionSetActiveOptions options =
323 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
324 success = [session setActive:active
325 withOptions:options
326 error:&error];
327 if (outError) {
328 *outError = error;
329 }
330 }
331 if (success) {
332 if (shouldSetActive) {
333 self.isActive = active;
334 }
335 if (active) {
336 [self incrementActivationCount];
337 }
338 } else {
tkchin9f987d32016-03-12 20:06:28 -0800339 RTCLogError(@"Failed to setActive:%d. Error: %@",
340 active, error.localizedDescription);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800341 }
342 // Decrement activation count on deactivation whether or not it succeeded.
343 if (!active) {
344 [self decrementActivationCount];
345 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700346 RTCLog(@"Number of current activations: %d", _activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800347 return success;
348}
349
350- (BOOL)setCategory:(NSString *)category
351 withOptions:(AVAudioSessionCategoryOptions)options
352 error:(NSError **)outError {
353 if (![self checkLock:outError]) {
354 return NO;
355 }
356 return [self.session setCategory:category withOptions:options error:outError];
357}
358
359- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
360 if (![self checkLock:outError]) {
361 return NO;
362 }
363 return [self.session setMode:mode error:outError];
364}
365
366- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
367 if (![self checkLock:outError]) {
368 return NO;
369 }
370 return [self.session setInputGain:gain error:outError];
371}
372
373- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
374 if (![self checkLock:outError]) {
375 return NO;
376 }
377 return [self.session setPreferredSampleRate:sampleRate error:outError];
378}
379
380- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
381 error:(NSError **)outError {
382 if (![self checkLock:outError]) {
383 return NO;
384 }
385 return [self.session setPreferredIOBufferDuration:duration error:outError];
386}
387
388- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
389 error:(NSError **)outError {
390 if (![self checkLock:outError]) {
391 return NO;
392 }
393 return [self.session setPreferredInputNumberOfChannels:count error:outError];
394}
395- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
396 error:(NSError **)outError {
397 if (![self checkLock:outError]) {
398 return NO;
399 }
400 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
401}
402
403- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
404 error:(NSError **)outError {
405 if (![self checkLock:outError]) {
406 return NO;
407 }
408 return [self.session overrideOutputAudioPort:portOverride error:outError];
409}
410
411- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
412 error:(NSError **)outError {
413 if (![self checkLock:outError]) {
414 return NO;
415 }
416 return [self.session setPreferredInput:inPort error:outError];
417}
418
419- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
420 error:(NSError **)outError {
421 if (![self checkLock:outError]) {
422 return NO;
423 }
424 return [self.session setInputDataSource:dataSource error:outError];
425}
426
427- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
428 error:(NSError **)outError {
429 if (![self checkLock:outError]) {
430 return NO;
431 }
432 return [self.session setOutputDataSource:dataSource error:outError];
433}
434
435#pragma mark - Notifications
436
437- (void)handleInterruptionNotification:(NSNotification *)notification {
438 NSNumber* typeNumber =
439 notification.userInfo[AVAudioSessionInterruptionTypeKey];
440 AVAudioSessionInterruptionType type =
441 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
442 switch (type) {
443 case AVAudioSessionInterruptionTypeBegan:
444 RTCLog(@"Audio session interruption began.");
445 self.isActive = NO;
tkchin93dd6342016-07-27 10:17:14 -0700446 self.isInterrupted = YES;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800447 [self notifyDidBeginInterruption];
448 break;
449 case AVAudioSessionInterruptionTypeEnded: {
450 RTCLog(@"Audio session interruption ended.");
tkchin93dd6342016-07-27 10:17:14 -0700451 self.isInterrupted = NO;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800452 [self updateAudioSessionAfterEvent];
453 NSNumber *optionsNumber =
454 notification.userInfo[AVAudioSessionInterruptionOptionKey];
455 AVAudioSessionInterruptionOptions options =
456 optionsNumber.unsignedIntegerValue;
457 BOOL shouldResume =
458 options & AVAudioSessionInterruptionOptionShouldResume;
459 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
460 break;
461 }
462 }
463}
464
465- (void)handleRouteChangeNotification:(NSNotification *)notification {
466 // Get reason for current route change.
467 NSNumber* reasonNumber =
468 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
469 AVAudioSessionRouteChangeReason reason =
470 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
471 RTCLog(@"Audio route changed:");
472 switch (reason) {
473 case AVAudioSessionRouteChangeReasonUnknown:
474 RTCLog(@"Audio route changed: ReasonUnknown");
475 break;
476 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
477 RTCLog(@"Audio route changed: NewDeviceAvailable");
478 break;
479 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
480 RTCLog(@"Audio route changed: OldDeviceUnavailable");
481 break;
482 case AVAudioSessionRouteChangeReasonCategoryChange:
483 RTCLog(@"Audio route changed: CategoryChange to :%@",
484 self.session.category);
485 break;
486 case AVAudioSessionRouteChangeReasonOverride:
487 RTCLog(@"Audio route changed: Override");
488 break;
489 case AVAudioSessionRouteChangeReasonWakeFromSleep:
490 RTCLog(@"Audio route changed: WakeFromSleep");
491 break;
492 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
493 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
494 break;
495 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
496 RTCLog(@"Audio route changed: RouteConfigurationChange");
497 break;
498 }
499 AVAudioSessionRouteDescription* previousRoute =
500 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
501 // Log previous route configuration.
502 RTCLog(@"Previous route: %@\nCurrent route:%@",
503 previousRoute, self.session.currentRoute);
504 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
505}
506
507- (void)handleMediaServicesWereLost:(NSNotification *)notification {
508 RTCLog(@"Media services were lost.");
509 [self updateAudioSessionAfterEvent];
510 [self notifyMediaServicesWereLost];
511}
512
513- (void)handleMediaServicesWereReset:(NSNotification *)notification {
514 RTCLog(@"Media services were reset.");
515 [self updateAudioSessionAfterEvent];
516 [self notifyMediaServicesWereReset];
517}
518
tkchin93dd6342016-07-27 10:17:14 -0700519- (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
520 if (self.isInterrupted) {
521 RTCLog(@"Application became active after an interruption. Treating as interruption end.");
522 self.isInterrupted = NO;
523 [self updateAudioSessionAfterEvent];
524 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
525 }
526}
527
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800528#pragma mark - Private
529
530+ (NSError *)lockError {
531 NSDictionary *userInfo = @{
532 NSLocalizedDescriptionKey:
533 @"Must call lockForConfiguration before calling this method."
534 };
535 NSError *error =
536 [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
537 code:kRTCAudioSessionErrorLockRequired
538 userInfo:userInfo];
539 return error;
540}
541
tkchine54467f2016-03-15 16:54:03 -0700542- (std::vector<__weak id<RTCAudioSessionDelegate> >)delegates {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800543 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700544 // Note: this returns a copy.
545 return _delegates;
546 }
547}
548
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700549// TODO(tkchin): check for duplicates.
tkchine54467f2016-03-15 16:54:03 -0700550- (void)pushDelegate:(id<RTCAudioSessionDelegate>)delegate {
551 @synchronized(self) {
552 _delegates.insert(_delegates.begin(), delegate);
553 }
554}
555
556- (void)removeZeroedDelegates {
557 @synchronized(self) {
tkchinefdd9302016-04-11 12:00:59 -0700558 _delegates.erase(
559 std::remove_if(_delegates.begin(),
560 _delegates.end(),
561 [](id delegate) -> bool { return delegate == nil; }),
562 _delegates.end());
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800563 }
564}
565
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700566- (int)activationCount {
567 return _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800568}
569
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700570- (int)incrementActivationCount {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800571 RTCLog(@"Incrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700572 return rtc::AtomicOps::Increment(&_activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800573}
574
575- (NSInteger)decrementActivationCount {
576 RTCLog(@"Decrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700577 return rtc::AtomicOps::Decrement(&_activationCount);
578}
579
580- (int)webRTCSessionCount {
581 return _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800582}
583
tkchind2511962016-05-06 18:54:15 -0700584- (BOOL)canPlayOrRecord {
585 return !self.useManualAudio || self.isAudioEnabled;
586}
587
tkchin93dd6342016-07-27 10:17:14 -0700588- (BOOL)isInterrupted {
589 @synchronized(self) {
590 return _isInterrupted;
591 }
592}
593
594- (void)setIsInterrupted:(BOOL)isInterrupted {
595 @synchronized(self) {
596 if (_isInterrupted == isInterrupted) {
597 return;
598 }
599 _isInterrupted = isInterrupted;
600 }
601}
602
tkchin9f987d32016-03-12 20:06:28 -0800603- (BOOL)checkLock:(NSError **)outError {
604 // Check ivar instead of trying to acquire lock so that we won't accidentally
605 // acquire lock if it hasn't already been called.
606 if (!self.isLocked) {
607 if (outError) {
608 *outError = [RTCAudioSession lockError];
609 }
610 return NO;
611 }
612 return YES;
613}
614
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700615- (BOOL)beginWebRTCSession:(NSError **)outError {
616 if (outError) {
617 *outError = nil;
618 }
619 if (![self checkLock:outError]) {
620 return NO;
621 }
tkchind2511962016-05-06 18:54:15 -0700622 rtc::AtomicOps::Increment(&_webRTCSessionCount);
623 [self notifyDidStartPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700624 return YES;
625}
626
627- (BOOL)endWebRTCSession:(NSError **)outError {
628 if (outError) {
629 *outError = nil;
630 }
631 if (![self checkLock:outError]) {
632 return NO;
633 }
tkchind2511962016-05-06 18:54:15 -0700634 rtc::AtomicOps::Decrement(&_webRTCSessionCount);
635 [self notifyDidStopPlayOrRecord];
636 return YES;
637}
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700638
tkchind2511962016-05-06 18:54:15 -0700639- (BOOL)configureWebRTCSession:(NSError **)outError {
640 if (outError) {
641 *outError = nil;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700642 }
tkchind2511962016-05-06 18:54:15 -0700643 if (![self checkLock:outError]) {
644 return NO;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700645 }
tkchind2511962016-05-06 18:54:15 -0700646 RTCLog(@"Configuring audio session for WebRTC.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700647
tkchind2511962016-05-06 18:54:15 -0700648 // Configure the AVAudioSession and activate it.
649 // Provide an error even if there isn't one so we can log it.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700650 NSError *error = nil;
tkchind2511962016-05-06 18:54:15 -0700651 RTCAudioSessionConfiguration *webRTCConfig =
652 [RTCAudioSessionConfiguration webRTCConfiguration];
653 if (![self setConfiguration:webRTCConfig active:YES error:&error]) {
654 RTCLogError(@"Failed to set WebRTC audio configuration: %@",
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700655 error.localizedDescription);
tkchind2511962016-05-06 18:54:15 -0700656 [self unconfigureWebRTCSession:nil];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700657 if (outError) {
658 *outError = error;
659 }
660 return NO;
661 }
662
tkchind2511962016-05-06 18:54:15 -0700663 // Ensure that the device currently supports audio input.
664 // TODO(tkchin): Figure out if this is really necessary.
665 if (!self.inputAvailable) {
666 RTCLogError(@"No audio input path is available!");
667 [self unconfigureWebRTCSession:nil];
668 if (outError) {
669 *outError = [self configurationErrorWithDescription:@"No input path."];
670 }
671 return NO;
672 }
673
henrika2d014be2016-06-16 14:26:55 +0200674 // It can happen (e.g. in combination with BT devices) that the attempt to set
675 // the preferred sample rate for WebRTC (48kHz) fails. If so, make a new
676 // configuration attempt using the sample rate that worked using the active
677 // audio session. A typical case is that only 8 or 16kHz can be set, e.g. in
678 // combination with BT headsets. Using this "trick" seems to avoid a state
679 // where Core Audio asks for a different number of audio frames than what the
680 // session's I/O buffer duration corresponds to.
681 // TODO(henrika): this fix resolves bugs.webrtc.org/6004 but it has only been
682 // tested on a limited set of iOS devices and BT devices.
683 double sessionSampleRate = self.sampleRate;
684 double preferredSampleRate = webRTCConfig.sampleRate;
685 if (sessionSampleRate != preferredSampleRate) {
686 RTCLogWarning(
687 @"Current sample rate (%.2f) is not the preferred rate (%.2f)",
688 sessionSampleRate, preferredSampleRate);
689 if (![self setPreferredSampleRate:sessionSampleRate
690 error:&error]) {
691 RTCLogError(@"Failed to set preferred sample rate: %@",
692 error.localizedDescription);
693 if (outError) {
694 *outError = error;
695 }
696 }
697 }
698
tkchind2511962016-05-06 18:54:15 -0700699 return YES;
700}
701
702- (BOOL)unconfigureWebRTCSession:(NSError **)outError {
703 if (outError) {
704 *outError = nil;
705 }
706 if (![self checkLock:outError]) {
707 return NO;
708 }
709 RTCLog(@"Unconfiguring audio session for WebRTC.");
710 [self setActive:NO error:outError];
711
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700712 return YES;
713}
714
715- (NSError *)configurationErrorWithDescription:(NSString *)description {
716 NSDictionary* userInfo = @{
717 NSLocalizedDescriptionKey: description,
718 };
719 return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
720 code:kRTCAudioSessionErrorConfiguration
721 userInfo:userInfo];
722}
723
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800724- (void)updateAudioSessionAfterEvent {
725 BOOL shouldActivate = self.activationCount > 0;
726 AVAudioSessionSetActiveOptions options = shouldActivate ?
727 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
728 NSError *error = nil;
729 if ([self.session setActive:shouldActivate
730 withOptions:options
731 error:&error]) {
732 self.isActive = shouldActivate;
733 } else {
734 RTCLogError(@"Failed to set session active to %d. Error:%@",
735 shouldActivate, error.localizedDescription);
736 }
737}
738
tkchind2511962016-05-06 18:54:15 -0700739- (void)updateCanPlayOrRecord {
740 BOOL canPlayOrRecord = NO;
741 BOOL shouldNotify = NO;
742 @synchronized(self) {
743 canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled;
744 if (_canPlayOrRecord == canPlayOrRecord) {
745 return;
746 }
747 _canPlayOrRecord = canPlayOrRecord;
748 shouldNotify = YES;
749 }
750 if (shouldNotify) {
751 [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord];
752 }
753}
754
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800755- (void)notifyDidBeginInterruption {
tkchine54467f2016-03-15 16:54:03 -0700756 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700757 SEL sel = @selector(audioSessionDidBeginInterruption:);
758 if ([delegate respondsToSelector:sel]) {
759 [delegate audioSessionDidBeginInterruption:self];
760 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800761 }
762}
763
764- (void)notifyDidEndInterruptionWithShouldResumeSession:
765 (BOOL)shouldResumeSession {
tkchine54467f2016-03-15 16:54:03 -0700766 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700767 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
768 if ([delegate respondsToSelector:sel]) {
769 [delegate audioSessionDidEndInterruption:self
770 shouldResumeSession:shouldResumeSession];
771 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800772 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800773}
774
775- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
776 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -0700777 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700778 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
779 if ([delegate respondsToSelector:sel]) {
780 [delegate audioSessionDidChangeRoute:self
781 reason:reason
782 previousRoute:previousRoute];
783 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800784 }
785}
786
787- (void)notifyMediaServicesWereLost {
tkchine54467f2016-03-15 16:54:03 -0700788 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700789 SEL sel = @selector(audioSessionMediaServicesWereLost:);
790 if ([delegate respondsToSelector:sel]) {
791 [delegate audioSessionMediaServicesWereLost:self];
792 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800793 }
794}
795
796- (void)notifyMediaServicesWereReset {
tkchine54467f2016-03-15 16:54:03 -0700797 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700798 SEL sel = @selector(audioSessionMediaServicesWereReset:);
799 if ([delegate respondsToSelector:sel]) {
800 [delegate audioSessionMediaServicesWereReset:self];
801 }
802 }
803}
804
tkchind2511962016-05-06 18:54:15 -0700805- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700806 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700807 SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700808 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700809 [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700810 }
811 }
812}
813
tkchind2511962016-05-06 18:54:15 -0700814- (void)notifyDidStartPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700815 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700816 SEL sel = @selector(audioSessionDidStartPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700817 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700818 [delegate audioSessionDidStartPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700819 }
820 }
821}
822
tkchind2511962016-05-06 18:54:15 -0700823- (void)notifyDidStopPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700824 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700825 SEL sel = @selector(audioSessionDidStopPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700826 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700827 [delegate audioSessionDidStopPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700828 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800829 }
830}
831
832@end