OLD | NEW |
| (Empty) |
1 /* | |
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 | |
13 #import <UIKit/UIKit.h> | |
14 | |
15 #include "webrtc/base/atomicops.h" | |
16 #include "webrtc/base/checks.h" | |
17 #include "webrtc/base/criticalsection.h" | |
18 #include "webrtc/modules/audio_device/ios/audio_device_ios.h" | |
19 | |
20 #import "WebRTC/RTCLogging.h" | |
21 #import "webrtc/modules/audio_device/ios/objc/RTCAudioSession+Private.h" | |
22 #import "webrtc/modules/audio_device/ios/objc/RTCAudioSessionConfiguration.h" | |
23 | |
24 NSString * const kRTCAudioSessionErrorDomain = @"org.webrtc.RTCAudioSession"; | |
25 NSInteger const kRTCAudioSessionErrorLockRequired = -1; | |
26 NSInteger const kRTCAudioSessionErrorConfiguration = -2; | |
27 | |
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 { | |
32 rtc::CriticalSection _crit; | |
33 AVAudioSession *_session; | |
34 volatile int _activationCount; | |
35 volatile int _lockRecursionCount; | |
36 volatile int _webRTCSessionCount; | |
37 BOOL _isActive; | |
38 BOOL _useManualAudio; | |
39 BOOL _isAudioEnabled; | |
40 BOOL _canPlayOrRecord; | |
41 BOOL _isInterrupted; | |
42 } | |
43 | |
44 @synthesize session = _session; | |
45 @synthesize delegates = _delegates; | |
46 | |
47 + (instancetype)sharedInstance { | |
48 static dispatch_once_t onceToken; | |
49 static RTCAudioSession *sharedInstance = nil; | |
50 dispatch_once(&onceToken, ^{ | |
51 sharedInstance = [[self alloc] init]; | |
52 }); | |
53 return sharedInstance; | |
54 } | |
55 | |
56 - (instancetype)init { | |
57 if (self = [super init]) { | |
58 _session = [AVAudioSession sharedInstance]; | |
59 | |
60 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 [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]; | |
77 // 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]; | |
84 // Also track foreground event in order to deal with interruption ended situ
ation. | |
85 [center addObserver:self | |
86 selector:@selector(handleApplicationDidBecomeActive:) | |
87 name:UIApplicationDidBecomeActiveNotification | |
88 object:nil]; | |
89 RTCLog(@"RTCAudioSession (%p): init.", self); | |
90 } | |
91 return self; | |
92 } | |
93 | |
94 - (void)dealloc { | |
95 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
96 RTCLog(@"RTCAudioSession (%p): dealloc.", self); | |
97 } | |
98 | |
99 - (NSString *)description { | |
100 NSString *format = | |
101 @"RTCAudioSession: {\n" | |
102 " category: %@\n" | |
103 " categoryOptions: %ld\n" | |
104 " mode: %@\n" | |
105 " 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" | |
112 " outputVolume: %f\n" | |
113 "}"; | |
114 NSString *description = [NSString stringWithFormat:format, | |
115 self.category, (long)self.categoryOptions, self.mode, | |
116 self.isActive, self.sampleRate, self.IOBufferDuration, | |
117 self.outputNumberOfChannels, self.inputNumberOfChannels, | |
118 self.outputLatency, self.inputLatency, self.outputVolume]; | |
119 return description; | |
120 } | |
121 | |
122 - (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 { | |
135 return _lockRecursionCount > 0; | |
136 } | |
137 | |
138 - (void)setUseManualAudio:(BOOL)useManualAudio { | |
139 @synchronized(self) { | |
140 if (_useManualAudio == useManualAudio) { | |
141 return; | |
142 } | |
143 _useManualAudio = useManualAudio; | |
144 } | |
145 [self updateCanPlayOrRecord]; | |
146 } | |
147 | |
148 - (BOOL)useManualAudio { | |
149 @synchronized(self) { | |
150 return _useManualAudio; | |
151 } | |
152 } | |
153 | |
154 - (void)setIsAudioEnabled:(BOOL)isAudioEnabled { | |
155 @synchronized(self) { | |
156 if (_isAudioEnabled == isAudioEnabled) { | |
157 return; | |
158 } | |
159 _isAudioEnabled = isAudioEnabled; | |
160 } | |
161 [self updateCanPlayOrRecord]; | |
162 } | |
163 | |
164 - (BOOL)isAudioEnabled { | |
165 @synchronized(self) { | |
166 return _isAudioEnabled; | |
167 } | |
168 } | |
169 | |
170 // TODO(tkchin): Check for duplicates. | |
171 - (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate { | |
172 RTCLog(@"Adding delegate: (%p)", delegate); | |
173 if (!delegate) { | |
174 return; | |
175 } | |
176 @synchronized(self) { | |
177 _delegates.push_back(delegate); | |
178 [self removeZeroedDelegates]; | |
179 } | |
180 } | |
181 | |
182 - (void)removeDelegate:(id<RTCAudioSessionDelegate>)delegate { | |
183 RTCLog(@"Removing delegate: (%p)", delegate); | |
184 if (!delegate) { | |
185 return; | |
186 } | |
187 @synchronized(self) { | |
188 _delegates.erase(std::remove(_delegates.begin(), | |
189 _delegates.end(), | |
190 delegate), | |
191 _delegates.end()); | |
192 [self removeZeroedDelegates]; | |
193 } | |
194 } | |
195 | |
196 #pragma clang diagnostic push | |
197 #pragma clang diagnostic ignored "-Wthread-safety-analysis" | |
198 | |
199 - (void)lockForConfiguration { | |
200 _crit.Enter(); | |
201 rtc::AtomicOps::Increment(&_lockRecursionCount); | |
202 } | |
203 | |
204 - (void)unlockForConfiguration { | |
205 // Don't let threads other than the one that called lockForConfiguration | |
206 // unlock. | |
207 if (_crit.TryEnter()) { | |
208 rtc::AtomicOps::Decrement(&_lockRecursionCount); | |
209 // One unlock for the tryLock, and another one to actually unlock. If this | |
210 // was called without anyone calling lock, we will hit an assertion. | |
211 _crit.Leave(); | |
212 _crit.Leave(); | |
213 } | |
214 } | |
215 | |
216 #pragma clang diagnostic pop | |
217 | |
218 #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 | |
280 - (double)preferredSampleRate { | |
281 return self.session.preferredSampleRate; | |
282 } | |
283 | |
284 - (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 | |
308 - (NSTimeInterval)preferredIOBufferDuration { | |
309 return self.session.preferredIOBufferDuration; | |
310 } | |
311 | |
312 // TODO(tkchin): Simplify the amount of locking happening here. Likely that we | |
313 // can just do atomic increments / decrements. | |
314 - (BOOL)setActive:(BOOL)active | |
315 error:(NSError **)outError { | |
316 if (![self checkLock:outError]) { | |
317 return NO; | |
318 } | |
319 int activationCount = _activationCount; | |
320 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 { | |
354 RTCLogError(@"Failed to setActive:%d. Error: %@", | |
355 active, error.localizedDescription); | |
356 } | |
357 // Decrement activation count on deactivation whether or not it succeeded. | |
358 if (!active) { | |
359 [self decrementActivationCount]; | |
360 } | |
361 RTCLog(@"Number of current activations: %d", _activationCount); | |
362 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; | |
461 self.isInterrupted = YES; | |
462 [self notifyDidBeginInterruption]; | |
463 break; | |
464 case AVAudioSessionInterruptionTypeEnded: { | |
465 RTCLog(@"Audio session interruption ended."); | |
466 self.isInterrupted = NO; | |
467 [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 | |
534 - (void)handleSilenceSecondaryAudioHintNotification:(NSNotification *)notificati
on { | |
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.unsignedIntegerVal
ue; | |
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 | |
552 - (void)handleApplicationDidBecomeActive:(NSNotification *)notification { | |
553 RTCLog(@"Application became active after an interruption. Treating as interrup
tion " | |
554 " end. isInterrupted changed from %d to 0.", self.isInterrupted); | |
555 if (self.isInterrupted) { | |
556 self.isInterrupted = NO; | |
557 [self updateAudioSessionAfterEvent]; | |
558 } | |
559 // Always treat application becoming active as an interruption end event. | |
560 [self notifyDidEndInterruptionWithShouldResumeSession:YES]; | |
561 } | |
562 | |
563 #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 | |
577 - (std::vector<__weak id<RTCAudioSessionDelegate> >)delegates { | |
578 @synchronized(self) { | |
579 // Note: this returns a copy. | |
580 return _delegates; | |
581 } | |
582 } | |
583 | |
584 // TODO(tkchin): check for duplicates. | |
585 - (void)pushDelegate:(id<RTCAudioSessionDelegate>)delegate { | |
586 @synchronized(self) { | |
587 _delegates.insert(_delegates.begin(), delegate); | |
588 } | |
589 } | |
590 | |
591 - (void)removeZeroedDelegates { | |
592 @synchronized(self) { | |
593 _delegates.erase( | |
594 std::remove_if(_delegates.begin(), | |
595 _delegates.end(), | |
596 [](id delegate) -> bool { return delegate == nil; }), | |
597 _delegates.end()); | |
598 } | |
599 } | |
600 | |
601 - (int)activationCount { | |
602 return _activationCount; | |
603 } | |
604 | |
605 - (int)incrementActivationCount { | |
606 RTCLog(@"Incrementing activation count."); | |
607 return rtc::AtomicOps::Increment(&_activationCount); | |
608 } | |
609 | |
610 - (NSInteger)decrementActivationCount { | |
611 RTCLog(@"Decrementing activation count."); | |
612 return rtc::AtomicOps::Decrement(&_activationCount); | |
613 } | |
614 | |
615 - (int)webRTCSessionCount { | |
616 return _webRTCSessionCount; | |
617 } | |
618 | |
619 - (BOOL)canPlayOrRecord { | |
620 return !self.useManualAudio || self.isAudioEnabled; | |
621 } | |
622 | |
623 - (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 | |
638 - (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 | |
650 - (BOOL)beginWebRTCSession:(NSError **)outError { | |
651 if (outError) { | |
652 *outError = nil; | |
653 } | |
654 if (![self checkLock:outError]) { | |
655 return NO; | |
656 } | |
657 rtc::AtomicOps::Increment(&_webRTCSessionCount); | |
658 [self notifyDidStartPlayOrRecord]; | |
659 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 } | |
669 rtc::AtomicOps::Decrement(&_webRTCSessionCount); | |
670 [self notifyDidStopPlayOrRecord]; | |
671 return YES; | |
672 } | |
673 | |
674 - (BOOL)configureWebRTCSession:(NSError **)outError { | |
675 if (outError) { | |
676 *outError = nil; | |
677 } | |
678 if (![self checkLock:outError]) { | |
679 return NO; | |
680 } | |
681 RTCLog(@"Configuring audio session for WebRTC."); | |
682 | |
683 // Configure the AVAudioSession and activate it. | |
684 // Provide an error even if there isn't one so we can log it. | |
685 NSError *error = nil; | |
686 RTCAudioSessionConfiguration *webRTCConfig = | |
687 [RTCAudioSessionConfiguration webRTCConfiguration]; | |
688 if (![self setConfiguration:webRTCConfig active:YES error:&error]) { | |
689 RTCLogError(@"Failed to set WebRTC audio configuration: %@", | |
690 error.localizedDescription); | |
691 // Do not call setActive:NO if setActive:YES failed. | |
692 if (outError) { | |
693 *outError = error; | |
694 } | |
695 return NO; | |
696 } | |
697 | |
698 // 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 | |
709 // 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 | |
734 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 | |
747 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 | |
759 - (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 | |
774 - (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 | |
790 - (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 | |
806 - (void)notifyDidBeginInterruption { | |
807 for (auto delegate : self.delegates) { | |
808 SEL sel = @selector(audioSessionDidBeginInterruption:); | |
809 if ([delegate respondsToSelector:sel]) { | |
810 [delegate audioSessionDidBeginInterruption:self]; | |
811 } | |
812 } | |
813 } | |
814 | |
815 - (void)notifyDidEndInterruptionWithShouldResumeSession: | |
816 (BOOL)shouldResumeSession { | |
817 for (auto delegate : self.delegates) { | |
818 SEL sel = @selector(audioSessionDidEndInterruption:shouldResumeSession:); | |
819 if ([delegate respondsToSelector:sel]) { | |
820 [delegate audioSessionDidEndInterruption:self | |
821 shouldResumeSession:shouldResumeSession]; | |
822 } | |
823 } | |
824 } | |
825 | |
826 - (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason | |
827 previousRoute:(AVAudioSessionRouteDescription *)previousRoute { | |
828 for (auto delegate : self.delegates) { | |
829 SEL sel = @selector(audioSessionDidChangeRoute:reason:previousRoute:); | |
830 if ([delegate respondsToSelector:sel]) { | |
831 [delegate audioSessionDidChangeRoute:self | |
832 reason:reason | |
833 previousRoute:previousRoute]; | |
834 } | |
835 } | |
836 } | |
837 | |
838 - (void)notifyMediaServicesWereLost { | |
839 for (auto delegate : self.delegates) { | |
840 SEL sel = @selector(audioSessionMediaServerTerminated:); | |
841 if ([delegate respondsToSelector:sel]) { | |
842 [delegate audioSessionMediaServerTerminated:self]; | |
843 } | |
844 } | |
845 } | |
846 | |
847 - (void)notifyMediaServicesWereReset { | |
848 for (auto delegate : self.delegates) { | |
849 SEL sel = @selector(audioSessionMediaServerReset:); | |
850 if ([delegate respondsToSelector:sel]) { | |
851 [delegate audioSessionMediaServerReset:self]; | |
852 } | |
853 } | |
854 } | |
855 | |
856 - (void)notifyDidChangeCanPlayOrRecord:(BOOL)canPlayOrRecord { | |
857 for (auto delegate : self.delegates) { | |
858 SEL sel = @selector(audioSession:didChangeCanPlayOrRecord:); | |
859 if ([delegate respondsToSelector:sel]) { | |
860 [delegate audioSession:self didChangeCanPlayOrRecord:canPlayOrRecord]; | |
861 } | |
862 } | |
863 } | |
864 | |
865 - (void)notifyDidStartPlayOrRecord { | |
866 for (auto delegate : self.delegates) { | |
867 SEL sel = @selector(audioSessionDidStartPlayOrRecord:); | |
868 if ([delegate respondsToSelector:sel]) { | |
869 [delegate audioSessionDidStartPlayOrRecord:self]; | |
870 } | |
871 } | |
872 } | |
873 | |
874 - (void)notifyDidStopPlayOrRecord { | |
875 for (auto delegate : self.delegates) { | |
876 SEL sel = @selector(audioSessionDidStopPlayOrRecord:); | |
877 if ([delegate respondsToSelector:sel]) { | |
878 [delegate audioSessionDidStopPlayOrRecord:self]; | |
879 } | |
880 } | |
881 } | |
882 | |
883 @end | |
OLD | NEW |