blob: 057f62cf27d47f83589f691c51a3ffe764717d42 [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
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020011#import "RTCAudioSession+Private.h"
Zeke Chinb3fb71c2016-02-18 15:44:07 -080012
tkchin93dd6342016-07-27 10:17:14 -070013#import <UIKit/UIKit.h>
14
Joe Chen0b3a6e32019-12-26 23:01:42 -080015#include <vector>
16
Steve Anton10542f22019-01-11 09:11:00 -080017#include "rtc_base/atomic_ops.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020018#include "rtc_base/checks.h"
Niels Möller072c0082021-02-15 16:30:44 +010019#include "rtc_base/synchronization/mutex.h"
denicija59ee91b2017-06-05 05:48:47 -070020
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020021#import "RTCAudioSessionConfiguration.h"
22#import "base/RTCLogging.h"
denicija59ee91b2017-06-05 05:48:47 -070023
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020024NSString *const kRTCAudioSessionErrorDomain = @"org.webrtc.RTC_OBJC_TYPE(RTCAudioSession)";
Zeke Chinb3fb71c2016-02-18 15:44:07 -080025NSInteger const kRTCAudioSessionErrorLockRequired = -1;
tkchin9f987d32016-03-12 20:06:28 -080026NSInteger const kRTCAudioSessionErrorConfiguration = -2;
jtteh13ae11a2017-05-25 17:52:20 -070027NSString * const kRTCAudioSessionOutputVolumeSelector = @"outputVolume";
Zeke Chinb3fb71c2016-02-18 15:44:07 -080028
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020029@interface RTC_OBJC_TYPE (RTCAudioSession)
30() @property(nonatomic,
31 readonly) std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> > delegates;
Joe Chen0b3a6e32019-12-26 23:01:42 -080032@end
33
Zeke Chinb3fb71c2016-02-18 15:44:07 -080034// This class needs to be thread-safe because it is accessed from many threads.
35// TODO(tkchin): Consider more granular locking. We're not expecting a lot of
36// lock contention so coarse locks should be fine for now.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020037@implementation RTC_OBJC_TYPE (RTCAudioSession) {
Niels Möller072c0082021-02-15 16:30:44 +010038 webrtc::Mutex _mutex;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080039 AVAudioSession *_session;
Tze Kwang Chin307a0922016-03-21 13:57:40 -070040 volatile int _activationCount;
Tze Kwang Chin307a0922016-03-21 13:57:40 -070041 volatile int _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080042 BOOL _isActive;
tkchind2511962016-05-06 18:54:15 -070043 BOOL _useManualAudio;
44 BOOL _isAudioEnabled;
45 BOOL _canPlayOrRecord;
tkchin93dd6342016-07-27 10:17:14 -070046 BOOL _isInterrupted;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080047}
48
49@synthesize session = _session;
tkchine54467f2016-03-15 16:54:03 -070050@synthesize delegates = _delegates;
Joe Chen0c05b1a2019-05-07 10:46:22 -070051@synthesize ignoresPreferredAttributeConfigurationErrors =
52 _ignoresPreferredAttributeConfigurationErrors;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080053
54+ (instancetype)sharedInstance {
55 static dispatch_once_t onceToken;
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020056 static RTC_OBJC_TYPE(RTCAudioSession) *sharedInstance = nil;
Zeke Chinb3fb71c2016-02-18 15:44:07 -080057 dispatch_once(&onceToken, ^{
Tze Kwang Chin307a0922016-03-21 13:57:40 -070058 sharedInstance = [[self alloc] init];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080059 });
60 return sharedInstance;
61}
62
63- (instancetype)init {
Peter Hanspers47217362017-10-05 11:39:15 +020064 return [self initWithAudioSession:[AVAudioSession sharedInstance]];
65}
66
67/** This initializer provides a way for unit tests to inject a fake/mock audio session. */
68- (instancetype)initWithAudioSession:(id)audioSession {
Zeke Chinb3fb71c2016-02-18 15:44:07 -080069 if (self = [super init]) {
Peter Hanspers47217362017-10-05 11:39:15 +020070 _session = audioSession;
tkchin0ce3bf92016-03-12 16:52:04 -080071
Zeke Chinb3fb71c2016-02-18 15:44:07 -080072 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
73 [center addObserver:self
74 selector:@selector(handleInterruptionNotification:)
75 name:AVAudioSessionInterruptionNotification
76 object:nil];
77 [center addObserver:self
78 selector:@selector(handleRouteChangeNotification:)
79 name:AVAudioSessionRouteChangeNotification
80 object:nil];
Zeke Chinb3fb71c2016-02-18 15:44:07 -080081 [center addObserver:self
82 selector:@selector(handleMediaServicesWereLost:)
83 name:AVAudioSessionMediaServicesWereLostNotification
84 object:nil];
85 [center addObserver:self
86 selector:@selector(handleMediaServicesWereReset:)
87 name:AVAudioSessionMediaServicesWereResetNotification
88 object:nil];
henrikaf1363fd2016-09-27 06:06:44 -070089 // Posted on the main thread when the primary audio from other applications
90 // starts and stops. Foreground applications may use this notification as a
91 // hint to enable or disable audio that is secondary.
92 [center addObserver:self
93 selector:@selector(handleSilenceSecondaryAudioHintNotification:)
94 name:AVAudioSessionSilenceSecondaryAudioHintNotification
95 object:nil];
tkchin93dd6342016-07-27 10:17:14 -070096 // Also track foreground event in order to deal with interruption ended situation.
97 [center addObserver:self
98 selector:@selector(handleApplicationDidBecomeActive:)
99 name:UIApplicationDidBecomeActiveNotification
100 object:nil];
jtteh13ae11a2017-05-25 17:52:20 -0700101 [_session addObserver:self
102 forKeyPath:kRTCAudioSessionOutputVolumeSelector
103 options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200104 context:(__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class];
jtteh13ae11a2017-05-25 17:52:20 -0700105
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200106 RTCLog(@"RTC_OBJC_TYPE(RTCAudioSession) (%p): init.", self);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800107 }
108 return self;
109}
110
111- (void)dealloc {
112 [[NSNotificationCenter defaultCenter] removeObserver:self];
Peter Hanspers47217362017-10-05 11:39:15 +0200113 [_session removeObserver:self
114 forKeyPath:kRTCAudioSessionOutputVolumeSelector
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200115 context:(__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class];
116 RTCLog(@"RTC_OBJC_TYPE(RTCAudioSession) (%p): dealloc.", self);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800117}
118
Zeke Chin1300caa2016-03-18 14:39:11 -0700119- (NSString *)description {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200120 NSString *format = @"RTC_OBJC_TYPE(RTCAudioSession): {\n"
121 " category: %@\n"
122 " categoryOptions: %ld\n"
123 " mode: %@\n"
124 " isActive: %d\n"
125 " sampleRate: %.2f\n"
126 " IOBufferDuration: %f\n"
127 " outputNumberOfChannels: %ld\n"
128 " inputNumberOfChannels: %ld\n"
129 " outputLatency: %f\n"
130 " inputLatency: %f\n"
131 " outputVolume: %f\n"
132 "}";
Zeke Chin1300caa2016-03-18 14:39:11 -0700133 NSString *description = [NSString stringWithFormat:format,
tkchind2511962016-05-06 18:54:15 -0700134 self.category, (long)self.categoryOptions, self.mode,
Zeke Chin1300caa2016-03-18 14:39:11 -0700135 self.isActive, self.sampleRate, self.IOBufferDuration,
136 self.outputNumberOfChannels, self.inputNumberOfChannels,
henrikac5aea652016-09-21 07:45:55 -0700137 self.outputLatency, self.inputLatency, self.outputVolume];
Zeke Chin1300caa2016-03-18 14:39:11 -0700138 return description;
139}
140
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800141- (void)setIsActive:(BOOL)isActive {
142 @synchronized(self) {
143 _isActive = isActive;
144 }
145}
146
147- (BOOL)isActive {
148 @synchronized(self) {
149 return _isActive;
150 }
151}
152
tkchind2511962016-05-06 18:54:15 -0700153- (void)setUseManualAudio:(BOOL)useManualAudio {
tkchin9f987d32016-03-12 20:06:28 -0800154 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700155 if (_useManualAudio == useManualAudio) {
tkchin9f987d32016-03-12 20:06:28 -0800156 return;
157 }
tkchind2511962016-05-06 18:54:15 -0700158 _useManualAudio = useManualAudio;
159 }
160 [self updateCanPlayOrRecord];
161}
162
163- (BOOL)useManualAudio {
164 @synchronized(self) {
165 return _useManualAudio;
tkchin9f987d32016-03-12 20:06:28 -0800166 }
167}
168
tkchind2511962016-05-06 18:54:15 -0700169- (void)setIsAudioEnabled:(BOOL)isAudioEnabled {
tkchin9f987d32016-03-12 20:06:28 -0800170 @synchronized(self) {
tkchind2511962016-05-06 18:54:15 -0700171 if (_isAudioEnabled == isAudioEnabled) {
172 return;
173 }
174 _isAudioEnabled = isAudioEnabled;
175 }
176 [self updateCanPlayOrRecord];
177}
178
179- (BOOL)isAudioEnabled {
180 @synchronized(self) {
181 return _isAudioEnabled;
tkchin9f987d32016-03-12 20:06:28 -0800182 }
183}
184
Joe Chen0c05b1a2019-05-07 10:46:22 -0700185- (void)setIgnoresPreferredAttributeConfigurationErrors:
186 (BOOL)ignoresPreferredAttributeConfigurationErrors {
187 @synchronized(self) {
188 if (_ignoresPreferredAttributeConfigurationErrors ==
189 ignoresPreferredAttributeConfigurationErrors) {
190 return;
191 }
192 _ignoresPreferredAttributeConfigurationErrors = ignoresPreferredAttributeConfigurationErrors;
193 }
194}
195
196- (BOOL)ignoresPreferredAttributeConfigurationErrors {
197 @synchronized(self) {
198 return _ignoresPreferredAttributeConfigurationErrors;
199 }
200}
201
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700202// TODO(tkchin): Check for duplicates.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200203- (void)addDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate {
haysc7735b1e2017-03-29 14:53:32 -0700204 RTCLog(@"Adding delegate: (%p)", delegate);
tkchine54467f2016-03-15 16:54:03 -0700205 if (!delegate) {
206 return;
207 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800208 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700209 _delegates.push_back(delegate);
210 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800211 }
212}
213
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200214- (void)removeDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate {
haysc7735b1e2017-03-29 14:53:32 -0700215 RTCLog(@"Removing delegate: (%p)", delegate);
tkchine54467f2016-03-15 16:54:03 -0700216 if (!delegate) {
217 return;
218 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800219 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700220 _delegates.erase(std::remove(_delegates.begin(),
221 _delegates.end(),
tkchinefdd9302016-04-11 12:00:59 -0700222 delegate),
223 _delegates.end());
tkchine54467f2016-03-15 16:54:03 -0700224 [self removeZeroedDelegates];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800225 }
226}
227
kthelgasonee8b8612017-03-31 04:50:27 -0700228#pragma clang diagnostic push
229#pragma clang diagnostic ignored "-Wthread-safety-analysis"
230
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800231- (void)lockForConfiguration {
Niels Möller072c0082021-02-15 16:30:44 +0100232 _mutex.Lock();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800233}
234
235- (void)unlockForConfiguration {
Niels Möller072c0082021-02-15 16:30:44 +0100236 _mutex.Unlock();
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800237}
238
kthelgasonee8b8612017-03-31 04:50:27 -0700239#pragma clang diagnostic pop
240
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800241#pragma mark - AVAudioSession proxy methods
242
243- (NSString *)category {
244 return self.session.category;
245}
246
247- (AVAudioSessionCategoryOptions)categoryOptions {
248 return self.session.categoryOptions;
249}
250
251- (NSString *)mode {
252 return self.session.mode;
253}
254
255- (BOOL)secondaryAudioShouldBeSilencedHint {
256 return self.session.secondaryAudioShouldBeSilencedHint;
257}
258
259- (AVAudioSessionRouteDescription *)currentRoute {
260 return self.session.currentRoute;
261}
262
263- (NSInteger)maximumInputNumberOfChannels {
264 return self.session.maximumInputNumberOfChannels;
265}
266
267- (NSInteger)maximumOutputNumberOfChannels {
268 return self.session.maximumOutputNumberOfChannels;
269}
270
271- (float)inputGain {
272 return self.session.inputGain;
273}
274
275- (BOOL)inputGainSettable {
276 return self.session.inputGainSettable;
277}
278
279- (BOOL)inputAvailable {
280 return self.session.inputAvailable;
281}
282
283- (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
284 return self.session.inputDataSources;
285}
286
287- (AVAudioSessionDataSourceDescription *)inputDataSource {
288 return self.session.inputDataSource;
289}
290
291- (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
292 return self.session.outputDataSources;
293}
294
295- (AVAudioSessionDataSourceDescription *)outputDataSource {
296 return self.session.outputDataSource;
297}
298
299- (double)sampleRate {
300 return self.session.sampleRate;
301}
302
tkchind2511962016-05-06 18:54:15 -0700303- (double)preferredSampleRate {
304 return self.session.preferredSampleRate;
305}
306
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800307- (NSInteger)inputNumberOfChannels {
308 return self.session.inputNumberOfChannels;
309}
310
311- (NSInteger)outputNumberOfChannels {
312 return self.session.outputNumberOfChannels;
313}
314
315- (float)outputVolume {
316 return self.session.outputVolume;
317}
318
319- (NSTimeInterval)inputLatency {
320 return self.session.inputLatency;
321}
322
323- (NSTimeInterval)outputLatency {
324 return self.session.outputLatency;
325}
326
327- (NSTimeInterval)IOBufferDuration {
328 return self.session.IOBufferDuration;
329}
330
tkchind2511962016-05-06 18:54:15 -0700331- (NSTimeInterval)preferredIOBufferDuration {
332 return self.session.preferredIOBufferDuration;
333}
334
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800335- (BOOL)setActive:(BOOL)active
336 error:(NSError **)outError {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700337 int activationCount = _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800338 if (!active && activationCount == 0) {
339 RTCLogWarning(@"Attempting to deactivate without prior activation.");
340 }
JT Tehc1f083d2018-04-25 09:19:35 -0700341 [self notifyWillSetActive:active];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800342 BOOL success = YES;
343 BOOL isActive = self.isActive;
344 // Keep a local error so we can log it.
345 NSError *error = nil;
346 BOOL shouldSetActive =
347 (active && !isActive) || (!active && isActive && activationCount == 1);
348 // Attempt to activate if we're not active.
349 // Attempt to deactivate if we're active and it's the last unbalanced call.
350 if (shouldSetActive) {
351 AVAudioSession *session = self.session;
352 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
353 // that other audio sessions that were interrupted by our session can return
354 // to their active state. It is recommended for VoIP apps to use this
355 // option.
356 AVAudioSessionSetActiveOptions options =
357 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
358 success = [session setActive:active
359 withOptions:options
360 error:&error];
361 if (outError) {
362 *outError = error;
363 }
364 }
365 if (success) {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800366 if (active) {
Abby Yeh31357722021-03-09 01:55:31 +0800367 if (shouldSetActive) {
368 self.isActive = active;
369 if (self.isInterrupted) {
370 self.isInterrupted = NO;
371 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
372 }
373 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800374 [self incrementActivationCount];
Abby Yeh31357722021-03-09 01:55:31 +0800375 [self notifyDidSetActive:active];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800376 }
377 } else {
tkchin9f987d32016-03-12 20:06:28 -0800378 RTCLogError(@"Failed to setActive:%d. Error: %@",
379 active, error.localizedDescription);
JT Tehc1f083d2018-04-25 09:19:35 -0700380 [self notifyFailedToSetActive:active error:error];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800381 }
Abby Yeh31357722021-03-09 01:55:31 +0800382 // Set isActive and decrement activation count on deactivation
383 // whether or not it succeeded.
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800384 if (!active) {
Abby Yeh31357722021-03-09 01:55:31 +0800385 self.isActive = active;
386 [self notifyDidSetActive:active];
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800387 [self decrementActivationCount];
388 }
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700389 RTCLog(@"Number of current activations: %d", _activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800390 return success;
391}
392
393- (BOOL)setCategory:(NSString *)category
394 withOptions:(AVAudioSessionCategoryOptions)options
395 error:(NSError **)outError {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800396 return [self.session setCategory:category withOptions:options error:outError];
397}
398
399- (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800400 return [self.session setMode:mode error:outError];
401}
402
403- (BOOL)setInputGain:(float)gain error:(NSError **)outError {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800404 return [self.session setInputGain:gain error:outError];
405}
406
407- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800408 return [self.session setPreferredSampleRate:sampleRate error:outError];
409}
410
411- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
412 error:(NSError **)outError {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800413 return [self.session setPreferredIOBufferDuration:duration error:outError];
414}
415
416- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
417 error:(NSError **)outError {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800418 return [self.session setPreferredInputNumberOfChannels:count error:outError];
419}
420- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
421 error:(NSError **)outError {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800422 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
423}
424
425- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
426 error:(NSError **)outError {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800427 return [self.session overrideOutputAudioPort:portOverride error:outError];
428}
429
430- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
431 error:(NSError **)outError {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800432 return [self.session setPreferredInput:inPort error:outError];
433}
434
435- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
436 error:(NSError **)outError {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800437 return [self.session setInputDataSource:dataSource error:outError];
438}
439
440- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
441 error:(NSError **)outError {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800442 return [self.session setOutputDataSource:dataSource error:outError];
443}
444
445#pragma mark - Notifications
446
447- (void)handleInterruptionNotification:(NSNotification *)notification {
448 NSNumber* typeNumber =
449 notification.userInfo[AVAudioSessionInterruptionTypeKey];
450 AVAudioSessionInterruptionType type =
451 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
452 switch (type) {
453 case AVAudioSessionInterruptionTypeBegan:
454 RTCLog(@"Audio session interruption began.");
455 self.isActive = NO;
tkchin93dd6342016-07-27 10:17:14 -0700456 self.isInterrupted = YES;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800457 [self notifyDidBeginInterruption];
458 break;
459 case AVAudioSessionInterruptionTypeEnded: {
460 RTCLog(@"Audio session interruption ended.");
tkchin93dd6342016-07-27 10:17:14 -0700461 self.isInterrupted = NO;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800462 [self updateAudioSessionAfterEvent];
463 NSNumber *optionsNumber =
464 notification.userInfo[AVAudioSessionInterruptionOptionKey];
465 AVAudioSessionInterruptionOptions options =
466 optionsNumber.unsignedIntegerValue;
467 BOOL shouldResume =
468 options & AVAudioSessionInterruptionOptionShouldResume;
469 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
470 break;
471 }
472 }
473}
474
475- (void)handleRouteChangeNotification:(NSNotification *)notification {
476 // Get reason for current route change.
477 NSNumber* reasonNumber =
478 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
479 AVAudioSessionRouteChangeReason reason =
480 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
481 RTCLog(@"Audio route changed:");
482 switch (reason) {
483 case AVAudioSessionRouteChangeReasonUnknown:
484 RTCLog(@"Audio route changed: ReasonUnknown");
485 break;
486 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
487 RTCLog(@"Audio route changed: NewDeviceAvailable");
488 break;
489 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
490 RTCLog(@"Audio route changed: OldDeviceUnavailable");
491 break;
492 case AVAudioSessionRouteChangeReasonCategoryChange:
493 RTCLog(@"Audio route changed: CategoryChange to :%@",
494 self.session.category);
495 break;
496 case AVAudioSessionRouteChangeReasonOverride:
497 RTCLog(@"Audio route changed: Override");
498 break;
499 case AVAudioSessionRouteChangeReasonWakeFromSleep:
500 RTCLog(@"Audio route changed: WakeFromSleep");
501 break;
502 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
503 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
504 break;
505 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
506 RTCLog(@"Audio route changed: RouteConfigurationChange");
507 break;
508 }
509 AVAudioSessionRouteDescription* previousRoute =
510 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
511 // Log previous route configuration.
512 RTCLog(@"Previous route: %@\nCurrent route:%@",
513 previousRoute, self.session.currentRoute);
514 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
515}
516
517- (void)handleMediaServicesWereLost:(NSNotification *)notification {
518 RTCLog(@"Media services were lost.");
519 [self updateAudioSessionAfterEvent];
520 [self notifyMediaServicesWereLost];
521}
522
523- (void)handleMediaServicesWereReset:(NSNotification *)notification {
524 RTCLog(@"Media services were reset.");
525 [self updateAudioSessionAfterEvent];
526 [self notifyMediaServicesWereReset];
527}
528
henrikaf1363fd2016-09-27 06:06:44 -0700529- (void)handleSilenceSecondaryAudioHintNotification:(NSNotification *)notification {
530 // TODO(henrika): just adding logs here for now until we know if we are ever
531 // see this notification and might be affected by it or if further actions
532 // are required.
533 NSNumber *typeNumber =
534 notification.userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey];
535 AVAudioSessionSilenceSecondaryAudioHintType type =
536 (AVAudioSessionSilenceSecondaryAudioHintType)typeNumber.unsignedIntegerValue;
537 switch (type) {
538 case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
539 RTCLog(@"Another application's primary audio has started.");
540 break;
541 case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
542 RTCLog(@"Another application's primary audio has stopped.");
543 break;
544 }
545}
546
tkchin93dd6342016-07-27 10:17:14 -0700547- (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
Zeke Chin8280a562018-07-10 13:53:55 -0700548 BOOL isInterrupted = self.isInterrupted;
haysc7735b1e2017-03-29 14:53:32 -0700549 RTCLog(@"Application became active after an interruption. Treating as interruption "
Zeke Chin8280a562018-07-10 13:53:55 -0700550 "end. isInterrupted changed from %d to 0.",
551 isInterrupted);
552 if (isInterrupted) {
tkchin93dd6342016-07-27 10:17:14 -0700553 self.isInterrupted = NO;
554 [self updateAudioSessionAfterEvent];
tkchin93dd6342016-07-27 10:17:14 -0700555 }
haysc7735b1e2017-03-29 14:53:32 -0700556 // Always treat application becoming active as an interruption end event.
557 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
tkchin93dd6342016-07-27 10:17:14 -0700558}
559
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800560#pragma mark - Private
561
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200562- (std::vector<__weak id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)> >)delegates {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800563 @synchronized(self) {
tkchine54467f2016-03-15 16:54:03 -0700564 // Note: this returns a copy.
565 return _delegates;
566 }
567}
568
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700569// TODO(tkchin): check for duplicates.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200570- (void)pushDelegate:(id<RTC_OBJC_TYPE(RTCAudioSessionDelegate)>)delegate {
tkchine54467f2016-03-15 16:54:03 -0700571 @synchronized(self) {
572 _delegates.insert(_delegates.begin(), delegate);
573 }
574}
575
576- (void)removeZeroedDelegates {
577 @synchronized(self) {
tkchinefdd9302016-04-11 12:00:59 -0700578 _delegates.erase(
579 std::remove_if(_delegates.begin(),
580 _delegates.end(),
581 [](id delegate) -> bool { return delegate == nil; }),
582 _delegates.end());
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800583 }
584}
585
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700586- (int)activationCount {
587 return _activationCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800588}
589
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700590- (int)incrementActivationCount {
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800591 RTCLog(@"Incrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700592 return rtc::AtomicOps::Increment(&_activationCount);
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800593}
594
595- (NSInteger)decrementActivationCount {
596 RTCLog(@"Decrementing activation count.");
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700597 return rtc::AtomicOps::Decrement(&_activationCount);
598}
599
600- (int)webRTCSessionCount {
601 return _webRTCSessionCount;
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800602}
603
tkchind2511962016-05-06 18:54:15 -0700604- (BOOL)canPlayOrRecord {
605 return !self.useManualAudio || self.isAudioEnabled;
606}
607
tkchin93dd6342016-07-27 10:17:14 -0700608- (BOOL)isInterrupted {
609 @synchronized(self) {
610 return _isInterrupted;
611 }
612}
613
614- (void)setIsInterrupted:(BOOL)isInterrupted {
615 @synchronized(self) {
616 if (_isInterrupted == isInterrupted) {
617 return;
618 }
619 _isInterrupted = isInterrupted;
620 }
621}
622
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700623- (BOOL)beginWebRTCSession:(NSError **)outError {
624 if (outError) {
625 *outError = nil;
626 }
tkchind2511962016-05-06 18:54:15 -0700627 rtc::AtomicOps::Increment(&_webRTCSessionCount);
628 [self notifyDidStartPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700629 return YES;
630}
631
632- (BOOL)endWebRTCSession:(NSError **)outError {
633 if (outError) {
634 *outError = nil;
635 }
tkchind2511962016-05-06 18:54:15 -0700636 rtc::AtomicOps::Decrement(&_webRTCSessionCount);
637 [self notifyDidStopPlayOrRecord];
638 return YES;
639}
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700640
tkchind2511962016-05-06 18:54:15 -0700641- (BOOL)configureWebRTCSession:(NSError **)outError {
642 if (outError) {
643 *outError = nil;
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;
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200650 RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *webRTCConfig =
651 [RTC_OBJC_TYPE(RTCAudioSessionConfiguration) webRTCConfiguration];
tkchind2511962016-05-06 18:54:15 -0700652 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);
jttehf84c1d62017-04-21 13:56:39 -0700655 // Do not call setActive:NO if setActive:YES failed.
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 }
tkchind2511962016-05-06 18:54:15 -0700705 RTCLog(@"Unconfiguring audio session for WebRTC.");
706 [self setActive:NO error:outError];
707
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700708 return YES;
709}
710
711- (NSError *)configurationErrorWithDescription:(NSString *)description {
712 NSDictionary* userInfo = @{
713 NSLocalizedDescriptionKey: description,
714 };
715 return [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
716 code:kRTCAudioSessionErrorConfiguration
717 userInfo:userInfo];
718}
719
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800720- (void)updateAudioSessionAfterEvent {
721 BOOL shouldActivate = self.activationCount > 0;
722 AVAudioSessionSetActiveOptions options = shouldActivate ?
723 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
724 NSError *error = nil;
725 if ([self.session setActive:shouldActivate
726 withOptions:options
727 error:&error]) {
728 self.isActive = shouldActivate;
729 } else {
730 RTCLogError(@"Failed to set session active to %d. Error:%@",
731 shouldActivate, error.localizedDescription);
732 }
733}
734
tkchind2511962016-05-06 18:54:15 -0700735- (void)updateCanPlayOrRecord {
736 BOOL canPlayOrRecord = NO;
737 BOOL shouldNotify = NO;
738 @synchronized(self) {
739 canPlayOrRecord = !self.useManualAudio || self.isAudioEnabled;
740 if (_canPlayOrRecord == canPlayOrRecord) {
741 return;
742 }
743 _canPlayOrRecord = canPlayOrRecord;
744 shouldNotify = YES;
745 }
746 if (shouldNotify) {
747 [self notifyDidChangeCanPlayOrRecord:canPlayOrRecord];
748 }
749}
750
jtteh3c9a6c02017-04-18 09:09:35 -0700751- (void)audioSessionDidActivate:(AVAudioSession *)session {
752 if (_session != session) {
753 RTCLogError(@"audioSessionDidActivate called on different AVAudioSession");
754 }
Zeke Chin8280a562018-07-10 13:53:55 -0700755 RTCLog(@"Audio session was externally activated.");
jtteh3c9a6c02017-04-18 09:09:35 -0700756 [self incrementActivationCount];
757 self.isActive = YES;
Zeke Chin8280a562018-07-10 13:53:55 -0700758 // When a CallKit call begins, it's possible that we receive an interruption
759 // begin without a corresponding end. Since we know that we have an activated
760 // audio session at this point, just clear any saved interruption flag since
761 // the app may never be foregrounded during the duration of the call.
762 if (self.isInterrupted) {
763 RTCLog(@"Clearing interrupted state due to external activation.");
764 self.isInterrupted = NO;
765 }
766 // Treat external audio session activation as an end interruption event.
767 [self notifyDidEndInterruptionWithShouldResumeSession:YES];
jtteh3c9a6c02017-04-18 09:09:35 -0700768}
769
770- (void)audioSessionDidDeactivate:(AVAudioSession *)session {
771 if (_session != session) {
772 RTCLogError(@"audioSessionDidDeactivate called on different AVAudioSession");
773 }
Zeke Chin8280a562018-07-10 13:53:55 -0700774 RTCLog(@"Audio session was externally deactivated.");
jtteh3c9a6c02017-04-18 09:09:35 -0700775 self.isActive = NO;
776 [self decrementActivationCount];
777}
778
jtteh13ae11a2017-05-25 17:52:20 -0700779- (void)observeValueForKeyPath:(NSString *)keyPath
780 ofObject:(id)object
781 change:(NSDictionary *)change
782 context:(void *)context {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200783 if (context == (__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class) {
Peter Hanspers47217362017-10-05 11:39:15 +0200784 if (object == _session) {
785 NSNumber *newVolume = change[NSKeyValueChangeNewKey];
786 RTCLog(@"OutputVolumeDidChange to %f", newVolume.floatValue);
787 [self notifyDidChangeOutputVolume:newVolume.floatValue];
788 }
jtteh13ae11a2017-05-25 17:52:20 -0700789 } else {
790 [super observeValueForKeyPath:keyPath
791 ofObject:object
792 change:change
793 context:context];
794 }
795}
796
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800797- (void)notifyDidBeginInterruption {
tkchine54467f2016-03-15 16:54:03 -0700798 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700799 SEL sel = @selector(audioSessionDidBeginInterruption:);
800 if ([delegate respondsToSelector:sel]) {
801 [delegate audioSessionDidBeginInterruption:self];
802 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800803 }
804}
805
806- (void)notifyDidEndInterruptionWithShouldResumeSession:
807 (BOOL)shouldResumeSession {
tkchine54467f2016-03-15 16:54:03 -0700808 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700809 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:);
810 if ([delegate respondsToSelector:sel]) {
811 [delegate audioSessionDidEndInterruption:self
812 shouldResumeSession:shouldResumeSession];
813 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800814 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800815}
816
817- (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
818 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
tkchine54467f2016-03-15 16:54:03 -0700819 for (auto delegate : self.delegates) {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700820 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:);
821 if ([delegate respondsToSelector:sel]) {
822 [delegate audioSessionDidChangeRoute:self
823 reason:reason
824 previousRoute:previousRoute];
825 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800826 }
827}
828
829- (void)notifyMediaServicesWereLost {
tkchine54467f2016-03-15 16:54:03 -0700830 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800831 SEL sel = @selector(audioSessionMediaServerTerminated:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700832 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800833 [delegate audioSessionMediaServerTerminated:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700834 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800835 }
836}
837
838- (void)notifyMediaServicesWereReset {
tkchine54467f2016-03-15 16:54:03 -0700839 for (auto delegate : self.delegates) {
kthelgason1634e162017-02-07 02:48:55 -0800840 SEL sel = @selector(audioSessionMediaServerReset:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700841 if ([delegate respondsToSelector:sel]) {
kthelgason1634e162017-02-07 02:48:55 -0800842 [delegate audioSessionMediaServerReset:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700843 }
844 }
845}
846
tkchind2511962016-05-06 18:54:15 -0700847- (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700848 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700849 SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700850 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700851 [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700852 }
853 }
854}
855
tkchind2511962016-05-06 18:54:15 -0700856- (void)notifyDidStartPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700857 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700858 SEL sel = @selector(audioSessionDidStartPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700859 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700860 [delegate audioSessionDidStartPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700861 }
862 }
863}
864
tkchind2511962016-05-06 18:54:15 -0700865- (void)notifyDidStopPlayOrRecord {
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700866 for (auto delegate : self.delegates) {
tkchind2511962016-05-06 18:54:15 -0700867 SEL sel = @selector(audioSessionDidStopPlayOrRecord:);
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700868 if ([delegate respondsToSelector:sel]) {
tkchind2511962016-05-06 18:54:15 -0700869 [delegate audioSessionDidStopPlayOrRecord:self];
Tze Kwang Chin307a0922016-03-21 13:57:40 -0700870 }
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800871 }
872}
873
jtteh13ae11a2017-05-25 17:52:20 -0700874- (void)notifyDidChangeOutputVolume:(float)volume {
875 for (auto delegate : self.delegates) {
876 SEL sel = @selector(audioSession:didChangeOutputVolume:);
877 if ([delegate respondsToSelector:sel]) {
878 [delegate audioSession:self didChangeOutputVolume:volume];
879 }
880 }
881}
882
Anders Carlsson121ea322017-06-26 15:34:47 +0200883- (void)notifyDidDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches {
884 for (auto delegate : self.delegates) {
885 SEL sel = @selector(audioSession:didDetectPlayoutGlitch:);
886 if ([delegate respondsToSelector:sel]) {
887 [delegate audioSession:self didDetectPlayoutGlitch:totalNumberOfGlitches];
888 }
889 }
890}
891
JT Tehc1f083d2018-04-25 09:19:35 -0700892- (void)notifyWillSetActive:(BOOL)active {
893 for (id delegate : self.delegates) {
894 SEL sel = @selector(audioSession:willSetActive:);
895 if ([delegate respondsToSelector:sel]) {
896 [delegate audioSession:self willSetActive:active];
897 }
898 }
899}
900
901- (void)notifyDidSetActive:(BOOL)active {
902 for (id delegate : self.delegates) {
903 SEL sel = @selector(audioSession:didSetActive:);
904 if ([delegate respondsToSelector:sel]) {
905 [delegate audioSession:self didSetActive:active];
906 }
907 }
908}
909
910- (void)notifyFailedToSetActive:(BOOL)active error:(NSError *)error {
911 for (id delegate : self.delegates) {
912 SEL sel = @selector(audioSession:failedToSetActive:error:);
913 if ([delegate respondsToSelector:sel]) {
914 [delegate audioSession:self failedToSetActive:active error:error];
915 }
916 }
917}
918
Zeke Chinb3fb71c2016-02-18 15:44:07 -0800919@end