blob: f4530167f943928e7fe4ef5fb2ada88a273edc52 [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
kthelgasonee8b8612017-03-31 04:50:27 -0700196#pragma clang diagnostic push
197#pragma clang diagnostic ignored "-Wthread-safety-analysis"
198
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800199- (void)lockForConfiguration {
tkchin0ce3bf92016-03-12 16:52:04 -0800200 _crit.Enter();
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700201 rtc::AtomicOps::Increment(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800202}
203
204- (void)unlockForConfiguration {
205 // Don't let threads other than the one that called lockForConfiguration
206 // unlock.
tkchin0ce3bf92016-03-12 16:52:04 -0800207 if (_crit.TryEnter()) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700208 rtc::AtomicOps::Decrement(&_lockRecursionCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800209 // One unlock for the tryLock, and another one to actually unlock. If this
tkchin0ce3bf92016-03-12 16:52:04 -0800210 // was called without anyone calling lock, we will hit an assertion.
211 _crit.Leave();
212 _crit.Leave();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800213 }
214}
215
kthelgasonee8b8612017-03-31 04:50:27 -0700216#pragma clang diagnostic pop
217
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800218#pragma mark - AVAudioSession proxy methods
219
220- (NSString *)category {
221 return self.session.category;
222}
223
224- (AVAudioSessionCategoryOptions)categoryOptions {
225 return self.session.categoryOptions;
226}
227
228- (NSString *)mode {
229 return self.session.mode;
230}
231
232- (BOOL)secondaryAudioShouldBeSilencedHint {
233 return self.session.secondaryAudioShouldBeSilencedHint;
234}
235
236- (AVAudioSessionRouteDescription *)currentRoute {
237 return self.session.currentRoute;
238}
239
240- (NSInteger)maximumInputNumberOfChannels {
241 return self.session.maximumInputNumberOfChannels;
242}
243
244- (NSInteger)maximumOutputNumberOfChannels {
245 return self.session.maximumOutputNumberOfChannels;
246}
247
248- (float)inputGain {
249 return self.session.inputGain;
250}
251
252- (BOOL)inputGainSettable {
253 return self.session.inputGainSettable;
254}
255
256- (BOOL)inputAvailable {
257 return self.session.inputAvailable;
258}
259
260- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
261 return self.session.inputDataSources;
262}
263
264- (AVAudioSessionDataSourceDescription *)inputDataSource {
265 return self.session.inputDataSource;
266}
267
268- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
269 return self.session.outputDataSources;
270}
271
272- (AVAudioSessionDataSourceDescription *)outputDataSource {
273 return self.session.outputDataSource;
274}
275
276- (double)sampleRate {
277 return self.session.sampleRate;
278}
279
tkchind2511962016-05-06 18:54:15 -0700280- (double)preferredSampleRate {
281 return self.session.preferredSampleRate;
282}
283
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800284- (NSInteger)inputNumberOfChannels {
285 return self.session.inputNumberOfChannels;
286}
287
288- (NSInteger)outputNumberOfChannels {
289 return self.session.outputNumberOfChannels;
290}
291
292- (float)outputVolume {
293 return self.session.outputVolume;
294}
295
296- (NSTimeInterval)inputLatency {
297 return self.session.inputLatency;
298}
299
300- (NSTimeInterval)outputLatency {
301 return self.session.outputLatency;
302}
303
304- (NSTimeInterval)IOBufferDuration {
305 return self.session.IOBufferDuration;
306}
307
tkchind2511962016-05-06 18:54:15 -0700308- (NSTimeInterval)preferredIOBufferDuration {
309 return self.session.preferredIOBufferDuration;
310}
311
tkchine54467f2016-03-15 16:54:03 -0700312// TODO(tkchin): Simplify the amount of locking happening here. Likely that we
313// can just do atomic increments / decrements.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800314- (BOOL)setActive:(BOOL)active
315 error:(NSError **)outError {
316 if (![self checkLock:outError]) {
317 return NO;
318 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700319 int activationCount = _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800320 if (!active && activationCount == 0) {
321 RTCLogWarning(@"Attempting to deactivate without prior activation.");
322 }
323 BOOL success = YES;
324 BOOL isActive = self.isActive;
325 // Keep a local error so we can log it.
326 NSError *error = nil;
327 BOOL shouldSetActive =
328 (active && !isActive) || (!active && isActive && activationCount == 1);
329 // Attempt to activate if we're not active.
330 // Attempt to deactivate if we're active and it's the last unbalanced call.
331 if (shouldSetActive) {
332 AVAudioSession *session = self.session;
333 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
334 // that other audio sessions that were interrupted by our session can return
335 // to their active state. It is recommended for VoIP apps to use this
336 // option.
337 AVAudioSessionSetActiveOptions options =
338 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
339 success = [session setActive:active
340 withOptions:options
341 error:&error];
342 if (outError) {
343 *outError = error;
344 }
345 }
346 if (success) {
347 if (shouldSetActive) {
348 self.isActive = active;
349 }
350 if (active) {
351 [self incrementActivationCount];
352 }
353 } else {
tkchin9f987d32016-03-12 20:06:28 -0800354 RTCLogError(@"Failed to setActive:%d. Error: %@",
355 active, error.localizedDescription);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800356 }
357 // Decrement activation count on deactivation whether or not it succeeded.
358 if (!active) {
359 [self decrementActivationCount];
360 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700361 RTCLog(@"Number of current activations: %d", _activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800362 return success;
363}
364
365- (BOOL)setCategory:(NSString *)category
366 withOptions:(AVAudioSessionCategoryOptions)options
367 error:(NSError **)outError {
368 if (![self checkLock:outError]) {
369 return NO;
370 }
371 return [self.session setCategory:category withOptions:options error:outError];
372}
373
374- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
375 if (![self checkLock:outError]) {
376 return NO;
377 }
378 return [self.session setMode:mode error:outError];
379}
380
381- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
382 if (![self checkLock:outError]) {
383 return NO;
384 }
385 return [self.session setInputGain:gain error:outError];
386}
387
388- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
389 if (![self checkLock:outError]) {
390 return NO;
391 }
392 return [self.session setPreferredSampleRate:sampleRate error:outError];
393}
394
395- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
396 error:(NSError **)outError {
397 if (![self checkLock:outError]) {
398 return NO;
399 }
400 return [self.session setPreferredIOBufferDuration:duration error:outError];
401}
402
403- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
404 error:(NSError **)outError {
405 if (![self checkLock:outError]) {
406 return NO;
407 }
408 return [self.session setPreferredInputNumberOfChannels:count error:outError];
409}
410- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
411 error:(NSError **)outError {
412 if (![self checkLock:outError]) {
413 return NO;
414 }
415 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
416}
417
418- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
419 error:(NSError **)outError {
420 if (![self checkLock:outError]) {
421 return NO;
422 }
423 return [self.session overrideOutputAudioPort:portOverride error:outError];
424}
425
426- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
427 error:(NSError **)outError {
428 if (![self checkLock:outError]) {
429 return NO;
430 }
431 return [self.session setPreferredInput:inPort error:outError];
432}
433
434- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
435 error:(NSError **)outError {
436 if (![self checkLock:outError]) {
437 return NO;
438 }
439 return [self.session setInputDataSource:dataSource error:outError];
440}
441
442- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
443 error:(NSError **)outError {
444 if (![self checkLock:outError]) {
445 return NO;
446 }
447 return [self.session setOutputDataSource:dataSource error:outError];
448}
449
450#pragma mark - Notifications
451
452- (void)handleInterruptionNotification:(NSNotification *)notification {
453 NSNumber* typeNumber =
454 notification.userInfo[AVAudioSessionInterruptionTypeKey];
455 AVAudioSessionInterruptionType type =
456 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
457 switch (type) {
458 case AVAudioSessionInterruptionTypeBegan:
459 RTCLog(@"Audio session interruption began.");
460 self.isActive = NO;
tkchin93dd6342016-07-27 10:17:14 -0700461 self.isInterrupted = YES;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800462 [self notifyDidBeginInterruption];
463 break;
464 case AVAudioSessionInterruptionTypeEnded: {
465 RTCLog(@"Audio session interruption ended.");
tkchin93dd6342016-07-27 10:17:14 -0700466 self.isInterrupted = NO;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800467 [self updateAudioSessionAfterEvent];
468 NSNumber *optionsNumber =
469 notification.userInfo[AVAudioSessionInterruptionOptionKey];
470 AVAudioSessionInterruptionOptions options =
471 optionsNumber.unsignedIntegerValue;
472 BOOL shouldResume =
473 options & AVAudioSessionInterruptionOptionShouldResume;
474 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
475 break;
476 }
477 }
478}
479
480- (void)handleRouteChangeNotification:(NSNotification *)notification {
481 // Get reason for current route change.
482 NSNumber* reasonNumber =
483 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
484 AVAudioSessionRouteChangeReason reason =
485 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
486 RTCLog(@"Audio route changed:");
487 switch (reason) {
488 case AVAudioSessionRouteChangeReasonUnknown:
489 RTCLog(@"Audio route changed: ReasonUnknown");
490 break;
491 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
492 RTCLog(@"Audio route changed: NewDeviceAvailable");
493 break;
494 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
495 RTCLog(@"Audio route changed: OldDeviceUnavailable");
496 break;
497 case AVAudioSessionRouteChangeReasonCategoryChange:
498 RTCLog(@"Audio route changed: CategoryChange to :%@",
499 self.session.category);
500 break;
501 case AVAudioSessionRouteChangeReasonOverride:
502 RTCLog(@"Audio route changed: Override");
503 break;
504 case AVAudioSessionRouteChangeReasonWakeFromSleep:
505 RTCLog(@"Audio route changed: WakeFromSleep");
506 break;
507 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
508 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
509 break;
510 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
511 RTCLog(@"Audio route changed: RouteConfigurationChange");
512 break;
513 }
514 AVAudioSessionRouteDescription* previousRoute =
515 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
516 // Log previous route configuration.
517 RTCLog(@"Previous route: %@\nCurrent route:%@",
518 previousRoute, self.session.currentRoute);
519 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
520}
521
522- (void)handleMediaServicesWereLost:(NSNotification *)notification {
523 RTCLog(@"Media services were lost.");
524 [self updateAudioSessionAfterEvent];
525 [self notifyMediaServicesWereLost];
526}
527
528- (void)handleMediaServicesWereReset:(NSNotification *)notification {
529 RTCLog(@"Media services were reset.");
530 [self updateAudioSessionAfterEvent];
531 [self notifyMediaServicesWereReset];
532}
533
henrikaf1363fd2016-09-27 06:06:44 -0700534- (void)handleSilenceSecondaryAudioHintNotification:(NSNotification *)notification {
535 // TODO(henrika): just adding logs here for now until we know if we are ever
536 // see this notification and might be affected by it or if further actions
537 // are required.
538 NSNumber *typeNumber =
539 notification.userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey];
540 AVAudioSessionSilenceSecondaryAudioHintType type =
541 (AVAudioSessionSilenceSecondaryAudioHintType)typeNumber.unsignedIntegerValue;
542 switch (type) {
543 case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
544 RTCLog(@"Another application's primary audio has started.");
545 break;
546 case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
547 RTCLog(@"Another application's primary audio has stopped.");
548 break;
549 }
550}
551
tkchin93dd6342016-07-27 10:17:14 -0700552- (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
haysc7735b1e2017-03-29 14:53:32 -0700553 RTCLog(@"Application became active after an interruption. Treating as interruption "
554 " end. isInterrupted changed from %d to 0.", self.isInterrupted);
tkchin93dd6342016-07-27 10:17:14 -0700555 if (self.isInterrupted) {
tkchin93dd6342016-07-27 10:17:14 -0700556 self.isInterrupted = NO;
557 [self updateAudioSessionAfterEvent];
tkchin93dd6342016-07-27 10:17:14 -0700558 }
haysc7735b1e2017-03-29 14:53:32 -0700559 // Always treat application becoming active as an interruption end event.
560 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
tkchin93dd6342016-07-27 10:17:14 -0700561}
562
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800563#pragma mark - Private
564
565+ (NSError *)lockError {
566 NSDictionary *userInfo = @{
567 NSLocalizedDescriptionKey:
568 @"Must call lockForConfiguration before calling this method."
569 };
570 NSError *error =
571 [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
572 code:kRTCAudioSessionErrorLockRequired
573 userInfo:userInfo];
574 return error;
575}
576
tkchine54467f2016-03-15 16:54:03 -0700577- (std::vector<__weak id<RTCAudioSessionDelegate> >)delegates {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800578 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700579 // Note: this returns a copy.
580 return _delegates;
581 }
582}
583
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700584// TODO(tkchin): check for duplicates.
tkchine54467f2016-03-15 16:54:03 -0700585- (void)pushDelegate:(id<RTCAudioSessionDelegate>)delegate {
586 @synchronized(self) {
587 _delegates.insert(_delegates.begin(), delegate);
588 }
589}
590
591- (void)removeZeroedDelegates {
592 @synchronized(self) {
tkchinefdd9302016-04-11 12:00:59 -0700593 _delegates.erase(
594 std::remove_if(_delegates.begin(),
595 _delegates.end(),
596 [](id delegate) -> bool { return delegate == nil; }),
597 _delegates.end());
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800598 }
599}
600
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700601- (int)activationCount {
602 return _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800603}
604
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700605- (int)incrementActivationCount {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800606 RTCLog(@"Incrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700607 return rtc::AtomicOps::Increment(&_activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800608}
609
610- (NSInteger)decrementActivationCount {
611 RTCLog(@"Decrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700612 return rtc::AtomicOps::Decrement(&_activationCount);
613}
614
615- (int)webRTCSessionCount {
616 return _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800617}
618
tkchind2511962016-05-06 18:54:15 -0700619- (BOOL)canPlayOrRecord {
620 return !self.useManualAudio || self.isAudioEnabled;
621}
622
tkchin93dd6342016-07-27 10:17:14 -0700623- (BOOL)isInterrupted {
624 @synchronized(self) {
625 return _isInterrupted;
626 }
627}
628
629- (void)setIsInterrupted:(BOOL)isInterrupted {
630 @synchronized(self) {
631 if (_isInterrupted == isInterrupted) {
632 return;
633 }
634 _isInterrupted = isInterrupted;
635 }
636}
637
tkchin9f987d32016-03-12 20:06:28 -0800638- (BOOL)checkLock:(NSError **)outError {
639 // Check ivar instead of trying to acquire lock so that we won't accidentally
640 // acquire lock if it hasn't already been called.
641 if (!self.isLocked) {
642 if (outError) {
643 *outError = [RTCAudioSession lockError];
644 }
645 return NO;
646 }
647 return YES;
648}
649
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700650- (BOOL)beginWebRTCSession:(NSError **)outError {
651 if (outError) {
652 *outError = nil;
653 }
654 if (![self checkLock:outError]) {
655 return NO;
656 }
tkchind2511962016-05-06 18:54:15 -0700657 rtc::AtomicOps::Increment(&_webRTCSessionCount);
658 [self notifyDidStartPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700659 return YES;
660}
661
662- (BOOL)endWebRTCSession:(NSError **)outError {
663 if (outError) {
664 *outError = nil;
665 }
666 if (![self checkLock:outError]) {
667 return NO;
668 }
tkchind2511962016-05-06 18:54:15 -0700669 rtc::AtomicOps::Decrement(&_webRTCSessionCount);
670 [self notifyDidStopPlayOrRecord];
671 return YES;
672}
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700673
tkchind2511962016-05-06 18:54:15 -0700674- (BOOL)configureWebRTCSession:(NSError **)outError {
675 if (outError) {
676 *outError = nil;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700677 }
tkchind2511962016-05-06 18:54:15 -0700678 if (![self checkLock:outError]) {
679 return NO;
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700680 }
tkchind2511962016-05-06 18:54:15 -0700681 RTCLog(@"Configuring audio session for WebRTC.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700682
tkchind2511962016-05-06 18:54:15 -0700683 // Configure the AVAudioSession and activate it.
684 // Provide an error even if there isn't one so we can log it.
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700685 NSError *error = nil;
tkchind2511962016-05-06 18:54:15 -0700686 RTCAudioSessionConfiguration *webRTCConfig =
687 [RTCAudioSessionConfiguration webRTCConfiguration];
688 if (![self setConfiguration:webRTCConfig active:YES error:&error]) {
689 RTCLogError(@"Failed to set WebRTC audio configuration: %@",
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700690 error.localizedDescription);
tkchind2511962016-05-06 18:54:15 -0700691 [self unconfigureWebRTCSession:nil];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700692 if (outError) {
693 *outError = error;
694 }
695 return NO;
696 }
697
tkchind2511962016-05-06 18:54:15 -0700698 // Ensure that the device currently supports audio input.
699 // TODO(tkchin): Figure out if this is really necessary.
700 if (!self.inputAvailable) {
701 RTCLogError(@"No audio input path is available!");
702 [self unconfigureWebRTCSession:nil];
703 if (outError) {
704 *outError = [self configurationErrorWithDescription:@"No input path."];
705 }
706 return NO;
707 }
708
henrika2d014be2016-06-16 14:26:55 +0200709 // It can happen (e.g. in combination with BT devices) that the attempt to set
710 // the preferred sample rate for WebRTC (48kHz) fails. If so, make a new
711 // configuration attempt using the sample rate that worked using the active
712 // audio session. A typical case is that only 8 or 16kHz can be set, e.g. in
713 // combination with BT headsets. Using this "trick" seems to avoid a state
714 // where Core Audio asks for a different number of audio frames than what the
715 // session's I/O buffer duration corresponds to.
716 // TODO(henrika): this fix resolves bugs.webrtc.org/6004 but it has only been
717 // tested on a limited set of iOS devices and BT devices.
718 double sessionSampleRate = self.sampleRate;
719 double preferredSampleRate = webRTCConfig.sampleRate;
720 if (sessionSampleRate != preferredSampleRate) {
721 RTCLogWarning(
722 @"Current sample rate (%.2f) is not the preferred rate (%.2f)",
723 sessionSampleRate, preferredSampleRate);
724 if (![self setPreferredSampleRate:sessionSampleRate
725 error:&error]) {
726 RTCLogError(@"Failed to set preferred sample rate: %@",
727 error.localizedDescription);
728 if (outError) {
729 *outError = error;
730 }
731 }
732 }
733
tkchind2511962016-05-06 18:54:15 -0700734 return YES;
735}
736
737- (BOOL)unconfigureWebRTCSession:(NSError **)outError {
738 if (outError) {
739 *outError = nil;
740 }
741 if (![self checkLock:outError]) {
742 return NO;
743 }
744 RTCLog(@"Unconfiguring audio session for WebRTC.");
745 [self setActive:NO error:outError];
746
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700747 return YES;
748}
749
750- (NSError *)configurationErrorWithDescription:(NSString *)description {
751 NSDictionary* userInfo = @{
752 NSLocalizedDescriptionKey: description,
753 };
754 return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
755 code:kRTCAudioSessionErrorConfiguration
756 userInfo:userInfo];
757}
758
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800759- (void)updateAudioSessionAfterEvent {
760 BOOL shouldActivate = self.activationCount > 0;
761 AVAudioSessionSetActiveOptions options = shouldActivate ?
762 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
763 NSError *error = nil;
764 if ([self.session setActive:shouldActivate
765 withOptions:options
766 error:&error]) {
767 self.isActive = shouldActivate;
768 } else {
769 RTCLogError(@"Failed to set session active to %d. Error:%@",
770 shouldActivate, error.localizedDescription);
771 }
772}
773
tkchind2511962016-05-06 18:54:15 -0700774- (void)updateCanPlayOrRecord {
775 BOOL canPlayOrRecord = NO;
776 BOOL shouldNotify = NO;
777 @synchronized(self) {
778 canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled;
779 if (_canPlayOrRecord == canPlayOrRecord) {
780 return;
781 }
782 _canPlayOrRecord = canPlayOrRecord;
783 shouldNotify = YES;
784 }
785 if (shouldNotify) {
786 [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord];
787 }
788}
789
jtteh3c9a6c02017-04-18 09:09:35 -0700790- (void)audioSessionDidActivate:(AVAudioSession *)session {
791 if (_session != session) {
792 RTCLogError(@"audioSessionDidActivate called on different AVAudioSession");
793 }
794 [self incrementActivationCount];
795 self.isActive = YES;
796}
797
798- (void)audioSessionDidDeactivate:(AVAudioSession *)session {
799 if (_session != session) {
800 RTCLogError(@"audioSessionDidDeactivate called on different AVAudioSession");
801 }
802 self.isActive = NO;
803 [self decrementActivationCount];
804}
805
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800806- (void)notifyDidBeginInterruption {
tkchine54467f2016-03-15 16:54:03 -0700807 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700808 SEL sel = @selector(audioSessionDidBeginInterruption:);
809 if ([delegate respondsToSelector:sel]) {
810 [delegate audioSessionDidBeginInterruption:self];
811 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800812 }
813}
814
815- (void)notifyDidEndInterruptionWithShouldResumeSession:
816 (BOOL)shouldResumeSession {
tkchine54467f2016-03-15 16:54:03 -0700817 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700818 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
819 if ([delegate respondsToSelector:sel]) {
820 [delegate audioSessionDidEndInterruption:self
821 shouldResumeSession:shouldResumeSession];
822 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800823 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800824}
825
826- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
827 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -0700828 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700829 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
830 if ([delegate respondsToSelector:sel]) {
831 [delegate audioSessionDidChangeRoute:self
832 reason:reason
833 previousRoute:previousRoute];
834 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800835 }
836}
837
838- (void)notifyMediaServicesWereLost {
tkchine54467f2016-03-15 16:54:03 -0700839 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800840 SEL sel = @selector(audioSessionMediaServerTerminated:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700841 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800842 [delegate audioSessionMediaServerTerminated:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700843 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800844 }
845}
846
847- (void)notifyMediaServicesWereReset {
tkchine54467f2016-03-15 16:54:03 -0700848 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800849 SEL sel = @selector(audioSessionMediaServerReset:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700850 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800851 [delegate audioSessionMediaServerReset:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700852 }
853 }
854}
855
tkchind2511962016-05-06 18:54:15 -0700856- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700857 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700858 SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700859 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700860 [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700861 }
862 }
863}
864
tkchind2511962016-05-06 18:54:15 -0700865- (void)notifyDidStartPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700866 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700867 SEL sel = @selector(audioSessionDidStartPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700868 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700869 [delegate audioSessionDidStartPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700870 }
871 }
872}
873
tkchind2511962016-05-06 18:54:15 -0700874- (void)notifyDidStopPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700875 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700876 SEL sel = @selector(audioSessionDidStopPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700877 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700878 [delegate audioSessionDidStopPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700879 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800880 }
881}
882
883@end