blob: b17c601270c09bb9703de7c3002c9391692c1ff3 [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"
104 "}";
105 NSString *description = [NSString stringWithFormat:format,
tkchind2511962016-05-06 18:54:15 -0700106 self.category, (long)self.categoryOptions, self.mode,
Zeke Chin1300caa2016-03-18 14:39:11 -0700107 self.isActive, self.sampleRate, self.IOBufferDuration,
108 self.outputNumberOfChannels, self.inputNumberOfChannels,
109 self.outputLatency, self.inputLatency];
110 return description;
111}
112
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800113- (void)setIsActive:(BOOL)isActive {
114 @synchronized(self) {
115 _isActive = isActive;
116 }
117}
118
119- (BOOL)isActive {
120 @synchronized(self) {
121 return _isActive;
122 }
123}
124
125- (BOOL)isLocked {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700126 return _lockRecursionCount > 0;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800127}
128
tkchind2511962016-05-06 18:54:15 -0700129- (void)setUseManualAudio:(BOOL)useManualAudio {
tkchin9f987d32016-03-12 20:06:28 -0800130 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700131 if (_useManualAudio == useManualAudio) {
tkchin9f987d32016-03-12 20:06:28 -0800132 return;
133 }
tkchind2511962016-05-06 18:54:15 -0700134 _useManualAudio = useManualAudio;
135 }
136 [self updateCanPlayOrRecord];
137}
138
139- (BOOL)useManualAudio {
140 @synchronized(self) {
141 return _useManualAudio;
tkchin9f987d32016-03-12 20:06:28 -0800142 }
143}
144
tkchind2511962016-05-06 18:54:15 -0700145- (void)setIsAudioEnabled:(BOOL)isAudioEnabled {
tkchin9f987d32016-03-12 20:06:28 -0800146 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700147 if (_isAudioEnabled == isAudioEnabled) {
148 return;
149 }
150 _isAudioEnabled = isAudioEnabled;
151 }
152 [self updateCanPlayOrRecord];
153}
154
155- (BOOL)isAudioEnabled {
156 @synchronized(self) {
157 return _isAudioEnabled;
tkchin9f987d32016-03-12 20:06:28 -0800158 }
159}
160
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700161// TODO(tkchin): Check for duplicates.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800162- (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate {
tkchine54467f2016-03-15 16:54:03 -0700163 if (!delegate) {
164 return;
165 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800166 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700167 _delegates.push_back(delegate);
168 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800169 }
170}
171
172- (void)removeDelegate:(id<RTCAudioSessionDelegate>)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.erase(std::remove(_delegates.begin(),
178 _delegates.end(),
tkchinefdd9302016-04-11 12:00:59 -0700179 delegate),
180 _delegates.end());
tkchine54467f2016-03-15 16:54:03 -0700181 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800182 }
183}
184
185- (void)lockForConfiguration {
tkchin0ce3bf92016-03-12 16:52:04 -0800186 _crit.Enter();
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700187 rtc::AtomicOps::Increment(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800188}
189
190- (void)unlockForConfiguration {
191 // Don't let threads other than the one that called lockForConfiguration
192 // unlock.
tkchin0ce3bf92016-03-12 16:52:04 -0800193 if (_crit.TryEnter()) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700194 rtc::AtomicOps::Decrement(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800195 // One unlock for the tryLock, and another one to actually unlock. If this
tkchin0ce3bf92016-03-12 16:52:04 -0800196 // was called without anyone calling lock, we will hit an assertion.
197 _crit.Leave();
198 _crit.Leave();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800199 }
200}
201
202#pragma mark - AVAudioSession proxy methods
203
204- (NSString *)category {
205 return self.session.category;
206}
207
208- (AVAudioSessionCategoryOptions)categoryOptions {
209 return self.session.categoryOptions;
210}
211
212- (NSString *)mode {
213 return self.session.mode;
214}
215
216- (BOOL)secondaryAudioShouldBeSilencedHint {
217 return self.session.secondaryAudioShouldBeSilencedHint;
218}
219
220- (AVAudioSessionRouteDescription *)currentRoute {
221 return self.session.currentRoute;
222}
223
224- (NSInteger)maximumInputNumberOfChannels {
225 return self.session.maximumInputNumberOfChannels;
226}
227
228- (NSInteger)maximumOutputNumberOfChannels {
229 return self.session.maximumOutputNumberOfChannels;
230}
231
232- (float)inputGain {
233 return self.session.inputGain;
234}
235
236- (BOOL)inputGainSettable {
237 return self.session.inputGainSettable;
238}
239
240- (BOOL)inputAvailable {
241 return self.session.inputAvailable;
242}
243
244- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
245 return self.session.inputDataSources;
246}
247
248- (AVAudioSessionDataSourceDescription *)inputDataSource {
249 return self.session.inputDataSource;
250}
251
252- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
253 return self.session.outputDataSources;
254}
255
256- (AVAudioSessionDataSourceDescription *)outputDataSource {
257 return self.session.outputDataSource;
258}
259
260- (double)sampleRate {
261 return self.session.sampleRate;
262}
263
tkchind2511962016-05-06 18:54:15 -0700264- (double)preferredSampleRate {
265 return self.session.preferredSampleRate;
266}
267
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800268- (NSInteger)inputNumberOfChannels {
269 return self.session.inputNumberOfChannels;
270}
271
272- (NSInteger)outputNumberOfChannels {
273 return self.session.outputNumberOfChannels;
274}
275
276- (float)outputVolume {
277 return self.session.outputVolume;
278}
279
280- (NSTimeInterval)inputLatency {
281 return self.session.inputLatency;
282}
283
284- (NSTimeInterval)outputLatency {
285 return self.session.outputLatency;
286}
287
288- (NSTimeInterval)IOBufferDuration {
289 return self.session.IOBufferDuration;
290}
291
tkchind2511962016-05-06 18:54:15 -0700292- (NSTimeInterval)preferredIOBufferDuration {
293 return self.session.preferredIOBufferDuration;
294}
295
tkchine54467f2016-03-15 16:54:03 -0700296// TODO(tkchin): Simplify the amount of locking happening here. Likely that we
297// can just do atomic increments / decrements.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800298- (BOOL)setActive:(BOOL)active
299 error:(NSError **)outError {
300 if (![self checkLock:outError]) {
301 return NO;
302 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700303 int activationCount = _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800304 if (!active && activationCount == 0) {
305 RTCLogWarning(@"Attempting to deactivate without prior activation.");
306 }
307 BOOL success = YES;
308 BOOL isActive = self.isActive;
309 // Keep a local error so we can log it.
310 NSError *error = nil;
311 BOOL shouldSetActive =
312 (active && !isActive) || (!active && isActive && activationCount == 1);
313 // Attempt to activate if we're not active.
314 // Attempt to deactivate if we're active and it's the last unbalanced call.
315 if (shouldSetActive) {
316 AVAudioSession *session = self.session;
317 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
318 // that other audio sessions that were interrupted by our session can return
319 // to their active state. It is recommended for VoIP apps to use this
320 // option.
321 AVAudioSessionSetActiveOptions options =
322 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
323 success = [session setActive:active
324 withOptions:options
325 error:&error];
326 if (outError) {
327 *outError = error;
328 }
329 }
330 if (success) {
331 if (shouldSetActive) {
332 self.isActive = active;
333 }
334 if (active) {
335 [self incrementActivationCount];
336 }
337 } else {
tkchin9f987d32016-03-12 20:06:28 -0800338 RTCLogError(@"Failed to setActive:%d. Error: %@",
339 active, error.localizedDescription);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800340 }
341 // Decrement activation count on deactivation whether or not it succeeded.
342 if (!active) {
343 [self decrementActivationCount];
344 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700345 RTCLog(@"Number of current activations: %d", _activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800346 return success;
347}
348
349- (BOOL)setCategory:(NSString *)category
350 withOptions:(AVAudioSessionCategoryOptions)options
351 error:(NSError **)outError {
352 if (![self checkLock:outError]) {
353 return NO;
354 }
355 return [self.session setCategory:category withOptions:options error:outError];
356}
357
358- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
359 if (![self checkLock:outError]) {
360 return NO;
361 }
362 return [self.session setMode:mode error:outError];
363}
364
365- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
366 if (![self checkLock:outError]) {
367 return NO;
368 }
369 return [self.session setInputGain:gain error:outError];
370}
371
372- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
373 if (![self checkLock:outError]) {
374 return NO;
375 }
376 return [self.session setPreferredSampleRate:sampleRate error:outError];
377}
378
379- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
380 error:(NSError **)outError {
381 if (![self checkLock:outError]) {
382 return NO;
383 }
384 return [self.session setPreferredIOBufferDuration:duration error:outError];
385}
386
387- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
388 error:(NSError **)outError {
389 if (![self checkLock:outError]) {
390 return NO;
391 }
392 return [self.session setPreferredInputNumberOfChannels:count error:outError];
393}
394- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
395 error:(NSError **)outError {
396 if (![self checkLock:outError]) {
397 return NO;
398 }
399 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
400}
401
402- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
403 error:(NSError **)outError {
404 if (![self checkLock:outError]) {
405 return NO;
406 }
407 return [self.session overrideOutputAudioPort:portOverride error:outError];
408}
409
410- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
411 error:(NSError **)outError {
412 if (![self checkLock:outError]) {
413 return NO;
414 }
415 return [self.session setPreferredInput:inPort error:outError];
416}
417
418- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
419 error:(NSError **)outError {
420 if (![self checkLock:outError]) {
421 return NO;
422 }
423 return [self.session setInputDataSource:dataSource error:outError];
424}
425
426- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
427 error:(NSError **)outError {
428 if (![self checkLock:outError]) {
429 return NO;
430 }
431 return [self.session setOutputDataSource:dataSource error:outError];
432}
433
434#pragma mark - Notifications
435
436- (void)handleInterruptionNotification:(NSNotification *)notification {
437 NSNumber* typeNumber =
438 notification.userInfo[AVAudioSessionInterruptionTypeKey];
439 AVAudioSessionInterruptionType type =
440 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
441 switch (type) {
442 case AVAudioSessionInterruptionTypeBegan:
443 RTCLog(@"Audio session interruption began.");
444 self.isActive = NO;
tkchin93dd6342016-07-27 10:17:14 -0700445 self.isInterrupted = YES;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800446 [self notifyDidBeginInterruption];
447 break;
448 case AVAudioSessionInterruptionTypeEnded: {
449 RTCLog(@"Audio session interruption ended.");
tkchin93dd6342016-07-27 10:17:14 -0700450 self.isInterrupted = NO;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800451 [self updateAudioSessionAfterEvent];
452 NSNumber *optionsNumber =
453 notification.userInfo[AVAudioSessionInterruptionOptionKey];
454 AVAudioSessionInterruptionOptions options =
455 optionsNumber.unsignedIntegerValue;
456 BOOL shouldResume =
457 options & AVAudioSessionInterruptionOptionShouldResume;
458 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
459 break;
460 }
461 }
462}
463
464- (void)handleRouteChangeNotification:(NSNotification *)notification {
465 // Get reason for current route change.
466 NSNumber* reasonNumber =
467 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
468 AVAudioSessionRouteChangeReason reason =
469 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
470 RTCLog(@"Audio route changed:");
471 switch (reason) {
472 case AVAudioSessionRouteChangeReasonUnknown:
473 RTCLog(@"Audio route changed: ReasonUnknown");
474 break;
475 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
476 RTCLog(@"Audio route changed: NewDeviceAvailable");
477 break;
478 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
479 RTCLog(@"Audio route changed: OldDeviceUnavailable");
480 break;
481 case AVAudioSessionRouteChangeReasonCategoryChange:
482 RTCLog(@"Audio route changed: CategoryChange to :%@",
483 self.session.category);
484 break;
485 case AVAudioSessionRouteChangeReasonOverride:
486 RTCLog(@"Audio route changed: Override");
487 break;
488 case AVAudioSessionRouteChangeReasonWakeFromSleep:
489 RTCLog(@"Audio route changed: WakeFromSleep");
490 break;
491 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
492 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
493 break;
494 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
495 RTCLog(@"Audio route changed: RouteConfigurationChange");
496 break;
497 }
498 AVAudioSessionRouteDescription* previousRoute =
499 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
500 // Log previous route configuration.
501 RTCLog(@"Previous route: %@\nCurrent route:%@",
502 previousRoute, self.session.currentRoute);
503 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
504}
505
506- (void)handleMediaServicesWereLost:(NSNotification *)notification {
507 RTCLog(@"Media services were lost.");
508 [self updateAudioSessionAfterEvent];
509 [self notifyMediaServicesWereLost];
510}
511
512- (void)handleMediaServicesWereReset:(NSNotification *)notification {
513 RTCLog(@"Media services were reset.");
514 [self updateAudioSessionAfterEvent];
515 [self notifyMediaServicesWereReset];
516}
517
tkchin93dd6342016-07-27 10:17:14 -0700518- (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
519 if (self.isInterrupted) {
520 RTCLog(@"Application became active after an interruption. Treating as interruption end.");
521 self.isInterrupted = NO;
522 [self updateAudioSessionAfterEvent];
523 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
524 }
525}
526
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800527#pragma mark - Private
528
529+ (NSError *)lockError {
530 NSDictionary *userInfo = @{
531 NSLocalizedDescriptionKey:
532 @"Must call lockForConfiguration before calling this method."
533 };
534 NSError *error =
535 [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
536 code:kRTCAudioSessionErrorLockRequired
537 userInfo:userInfo];
538 return error;
539}
540
tkchine54467f2016-03-15 16:54:03 -0700541- (std::vector<__weak id<RTCAudioSessionDelegate> >)delegates {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800542 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700543 // Note: this returns a copy.
544 return _delegates;
545 }
546}
547
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700548// TODO(tkchin): check for duplicates.
tkchine54467f2016-03-15 16:54:03 -0700549- (void)pushDelegate:(id<RTCAudioSessionDelegate>)delegate {
550 @synchronized(self) {
551 _delegates.insert(_delegates.begin(), delegate);
552 }
553}
554
555- (void)removeZeroedDelegates {
556 @synchronized(self) {
tkchinefdd9302016-04-11 12:00:59 -0700557 _delegates.erase(
558 std::remove_if(_delegates.begin(),
559 _delegates.end(),
560 [](id delegate) -> bool { return delegate == nil; }),
561 _delegates.end());
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800562 }
563}
564
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700565- (int)activationCount {
566 return _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800567}
568
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700569- (int)incrementActivationCount {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800570 RTCLog(@"Incrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700571 return rtc::AtomicOps::Increment(&_activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800572}
573
574- (NSInteger)decrementActivationCount {
575 RTCLog(@"Decrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700576 return rtc::AtomicOps::Decrement(&_activationCount);
577}
578
579- (int)webRTCSessionCount {
580 return _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800581}
582
tkchind2511962016-05-06 18:54:15 -0700583- (BOOL)canPlayOrRecord {
584 return !self.useManualAudio || self.isAudioEnabled;
585}
586
tkchin93dd6342016-07-27 10:17:14 -0700587- (BOOL)isInterrupted {
588 @synchronized(self) {
589 return _isInterrupted;
590 }
591}
592
593- (void)setIsInterrupted:(BOOL)isInterrupted {
594 @synchronized(self) {
595 if (_isInterrupted == isInterrupted) {
596 return;
597 }
598 _isInterrupted = isInterrupted;
599 }
600}
601
tkchin9f987d32016-03-12 20:06:28 -0800602- (BOOL)checkLock:(NSError **)outError {
603 // Check ivar instead of trying to acquire lock so that we won't accidentally
604 // acquire lock if it hasn't already been called.
605 if (!self.isLocked) {
606 if (outError) {
607 *outError = [RTCAudioSession lockError];
608 }
609 return NO;
610 }
611 return YES;
612}
613
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700614- (BOOL)beginWebRTCSession:(NSError **)outError {
615 if (outError) {
616 *outError = nil;
617 }
618 if (![self checkLock:outError]) {
619 return NO;
620 }
tkchind2511962016-05-06 18:54:15 -0700621 rtc::AtomicOps::Increment(&_webRTCSessionCount);
622 [self notifyDidStartPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700623 return YES;
624}
625
626- (BOOL)endWebRTCSession:(NSError **)outError {
627 if (outError) {
628 *outError = nil;
629 }
630 if (![self checkLock:outError]) {
631 return NO;
632 }
tkchind2511962016-05-06 18:54:15 -0700633 rtc::AtomicOps::Decrement(&_webRTCSessionCount);
634 [self notifyDidStopPlayOrRecord];
635 return YES;
636}
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700637
tkchind2511962016-05-06 18:54:15 -0700638- (BOOL)configureWebRTCSession:(NSError **)outError {
639 if (outError) {
640 *outError = nil;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700641 }
tkchind2511962016-05-06 18:54:15 -0700642 if (![self checkLock:outError]) {
643 return NO;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700644 }
tkchind2511962016-05-06 18:54:15 -0700645 RTCLog(@"Configuring audio session for WebRTC.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700646
tkchind2511962016-05-06 18:54:15 -0700647 // Configure the AVAudioSession and activate it.
648 // Provide an error even if there isn't one so we can log it.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700649 NSError *error = nil;
tkchind2511962016-05-06 18:54:15 -0700650 RTCAudioSessionConfiguration *webRTCConfig =
651 [RTCAudioSessionConfiguration webRTCConfiguration];
652 if (![self setConfiguration:webRTCConfig active:YES error:&error]) {
653 RTCLogError(@"Failed to set WebRTC audio configuration: %@",
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700654 error.localizedDescription);
tkchind2511962016-05-06 18:54:15 -0700655 [self unconfigureWebRTCSession:nil];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700656 if (outError) {
657 *outError = error;
658 }
659 return NO;
660 }
661
tkchind2511962016-05-06 18:54:15 -0700662 // Ensure that the device currently supports audio input.
663 // TODO(tkchin): Figure out if this is really necessary.
664 if (!self.inputAvailable) {
665 RTCLogError(@"No audio input path is available!");
666 [self unconfigureWebRTCSession:nil];
667 if (outError) {
668 *outError = [self configurationErrorWithDescription:@"No input path."];
669 }
670 return NO;
671 }
672
henrika2d014be2016-06-16 14:26:55 +0200673 // It can happen (e.g. in combination with BT devices) that the attempt to set
674 // the preferred sample rate for WebRTC (48kHz) fails. If so, make a new
675 // configuration attempt using the sample rate that worked using the active
676 // audio session. A typical case is that only 8 or 16kHz can be set, e.g. in
677 // combination with BT headsets. Using this "trick" seems to avoid a state
678 // where Core Audio asks for a different number of audio frames than what the
679 // session's I/O buffer duration corresponds to.
680 // TODO(henrika): this fix resolves bugs.webrtc.org/6004 but it has only been
681 // tested on a limited set of iOS devices and BT devices.
682 double sessionSampleRate = self.sampleRate;
683 double preferredSampleRate = webRTCConfig.sampleRate;
684 if (sessionSampleRate != preferredSampleRate) {
685 RTCLogWarning(
686 @"Current sample rate (%.2f) is not the preferred rate (%.2f)",
687 sessionSampleRate, preferredSampleRate);
688 if (![self setPreferredSampleRate:sessionSampleRate
689 error:&error]) {
690 RTCLogError(@"Failed to set preferred sample rate: %@",
691 error.localizedDescription);
692 if (outError) {
693 *outError = error;
694 }
695 }
696 }
697
tkchind2511962016-05-06 18:54:15 -0700698 return YES;
699}
700
701- (BOOL)unconfigureWebRTCSession:(NSError **)outError {
702 if (outError) {
703 *outError = nil;
704 }
705 if (![self checkLock:outError]) {
706 return NO;
707 }
708 RTCLog(@"Unconfiguring audio session for WebRTC.");
709 [self setActive:NO error:outError];
710
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700711 return YES;
712}
713
714- (NSError *)configurationErrorWithDescription:(NSString *)description {
715 NSDictionary* userInfo = @{
716 NSLocalizedDescriptionKey: description,
717 };
718 return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
719 code:kRTCAudioSessionErrorConfiguration
720 userInfo:userInfo];
721}
722
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800723- (void)updateAudioSessionAfterEvent {
724 BOOL shouldActivate = self.activationCount > 0;
725 AVAudioSessionSetActiveOptions options = shouldActivate ?
726 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
727 NSError *error = nil;
728 if ([self.session setActive:shouldActivate
729 withOptions:options
730 error:&error]) {
731 self.isActive = shouldActivate;
732 } else {
733 RTCLogError(@"Failed to set session active to %d. Error:%@",
734 shouldActivate, error.localizedDescription);
735 }
736}
737
tkchind2511962016-05-06 18:54:15 -0700738- (void)updateCanPlayOrRecord {
739 BOOL canPlayOrRecord = NO;
740 BOOL shouldNotify = NO;
741 @synchronized(self) {
742 canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled;
743 if (_canPlayOrRecord == canPlayOrRecord) {
744 return;
745 }
746 _canPlayOrRecord = canPlayOrRecord;
747 shouldNotify = YES;
748 }
749 if (shouldNotify) {
750 [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord];
751 }
752}
753
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800754- (void)notifyDidBeginInterruption {
tkchine54467f2016-03-15 16:54:03 -0700755 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700756 SEL sel = @selector(audioSessionDidBeginInterruption:);
757 if ([delegate respondsToSelector:sel]) {
758 [delegate audioSessionDidBeginInterruption:self];
759 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800760 }
761}
762
763- (void)notifyDidEndInterruptionWithShouldResumeSession:
764 (BOOL)shouldResumeSession {
tkchine54467f2016-03-15 16:54:03 -0700765 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700766 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
767 if ([delegate respondsToSelector:sel]) {
768 [delegate audioSessionDidEndInterruption:self
769 shouldResumeSession:shouldResumeSession];
770 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800771 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800772}
773
774- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
775 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -0700776 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700777 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
778 if ([delegate respondsToSelector:sel]) {
779 [delegate audioSessionDidChangeRoute:self
780 reason:reason
781 previousRoute:previousRoute];
782 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800783 }
784}
785
786- (void)notifyMediaServicesWereLost {
tkchine54467f2016-03-15 16:54:03 -0700787 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700788 SEL sel = @selector(audioSessionMediaServicesWereLost:);
789 if ([delegate respondsToSelector:sel]) {
790 [delegate audioSessionMediaServicesWereLost:self];
791 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800792 }
793}
794
795- (void)notifyMediaServicesWereReset {
tkchine54467f2016-03-15 16:54:03 -0700796 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700797 SEL sel = @selector(audioSessionMediaServicesWereReset:);
798 if ([delegate respondsToSelector:sel]) {
799 [delegate audioSessionMediaServicesWereReset:self];
800 }
801 }
802}
803
tkchind2511962016-05-06 18:54:15 -0700804- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700805 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700806 SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700807 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700808 [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700809 }
810 }
811}
812
tkchind2511962016-05-06 18:54:15 -0700813- (void)notifyDidStartPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700814 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700815 SEL sel = @selector(audioSessionDidStartPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700816 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700817 [delegate audioSessionDidStartPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700818 }
819 }
820}
821
tkchind2511962016-05-06 18:54:15 -0700822- (void)notifyDidStopPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700823 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700824 SEL sel = @selector(audioSessionDidStopPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700825 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700826 [delegate audioSessionDidStopPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700827 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800828 }
829}
830
831@end