Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1331)

Side by Side Diff: webrtc/modules/audio_device/ios/objc/RTCAudioSession.mm

Issue 1709853002: Add RTCAudioSession proxy class. (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 #include "webrtc/base/checks.h"
14
15 #import "webrtc/base/objc/RTCLogging.h"
16 #import "webrtc/modules/audio_device/ios/objc/RTCAudioSession+Private.h"
17
18 NSString * const kRTCAudioSessionErrorDomain = @"org.webrtc.RTCAudioSession";
19 NSInteger const kRTCAudioSessionErrorLockRequired = -1;
20
21 // This class needs to be thread-safe because it is accessed from many threads.
22 // TODO(tkchin): Consider more granular locking. We're not expecting a lot of
23 // lock contention so coarse locks should be fine for now.
24 @implementation RTCAudioSession {
25 AVAudioSession *_session;
26 NSHashTable *_delegates;
27 NSInteger _activationCount;
28 BOOL _isActive;
29 BOOL _isLocked;
30 }
31
32 @synthesize session = _session;
33 @synthesize lock = _lock;
34
35 + (instancetype)sharedInstance {
36 static dispatch_once_t onceToken;
37 static RTCAudioSession *sharedInstance = nil;
38 dispatch_once(&onceToken, ^{
39 sharedInstance = [[RTCAudioSession alloc] init];
40 });
41 return sharedInstance;
42 }
43
44 - (instancetype)init {
45 if (self = [super init]) {
46 _session = [AVAudioSession sharedInstance];
47 _delegates = [NSHashTable weakObjectsHashTable];
48 _lock = [[NSRecursiveLock alloc] init];
49 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
50 [center addObserver:self
51 selector:@selector(handleInterruptionNotification:)
52 name:AVAudioSessionInterruptionNotification
53 object:nil];
54 [center addObserver:self
55 selector:@selector(handleRouteChangeNotification:)
56 name:AVAudioSessionRouteChangeNotification
57 object:nil];
58 // TODO(tkchin): Maybe listen to SilenceSecondaryAudioHintNotification.
59 [center addObserver:self
60 selector:@selector(handleMediaServicesWereLost:)
61 name:AVAudioSessionMediaServicesWereLostNotification
62 object:nil];
63 [center addObserver:self
64 selector:@selector(handleMediaServicesWereReset:)
65 name:AVAudioSessionMediaServicesWereResetNotification
66 object:nil];
67 }
68 return self;
69 }
70
71 - (void)dealloc {
72 [[NSNotificationCenter defaultCenter] removeObserver:self];
73 }
74
75 - (void)setIsActive:(BOOL)isActive {
76 @synchronized(self) {
77 _isActive = isActive;
78 }
79 }
80
81 - (BOOL)isActive {
82 @synchronized(self) {
83 return _isActive;
84 }
85 }
86
87 - (BOOL)isLocked {
88 @synchronized(self) {
89 return _isLocked;
90 }
91 }
92
93 - (void)addDelegate:(id<RTCAudioSessionDelegate>)delegate {
94 @synchronized(self) {
95 [_delegates addObject:delegate];
96 }
97 }
98
99 - (void)removeDelegate:(id<RTCAudioSessionDelegate>)delegate {
100 @synchronized(self) {
101 [_delegates removeObject:delegate];
102 }
103 }
104
105 - (void)lockForConfiguration {
106 [_lock lock];
107 @synchronized(self) {
108 _isLocked = YES;
109 }
110 }
111
112 - (void)unlockForConfiguration {
113 // Don't let threads other than the one that called lockForConfiguration
114 // unlock.
115 if ([_lock tryLock]) {
116 @synchronized(self) {
117 _isLocked = NO;
118 }
119 // One unlock for the tryLock, and another one to actually unlock. If this
120 // was called without anyone calling lock, the underlying NSRecursiveLock
121 // should spit out an error.
122 [_lock unlock];
123 [_lock unlock];
124 }
125 }
126
127 #pragma mark - AVAudioSession proxy methods
128
129 - (NSString *)category {
130 return self.session.category;
131 }
132
133 - (AVAudioSessionCategoryOptions)categoryOptions {
134 return self.session.categoryOptions;
135 }
136
137 - (NSString *)mode {
138 return self.session.mode;
139 }
140
141 - (BOOL)secondaryAudioShouldBeSilencedHint {
142 return self.session.secondaryAudioShouldBeSilencedHint;
143 }
144
145 - (AVAudioSessionRouteDescription *)currentRoute {
146 return self.session.currentRoute;
147 }
148
149 - (NSInteger)maximumInputNumberOfChannels {
150 return self.session.maximumInputNumberOfChannels;
151 }
152
153 - (NSInteger)maximumOutputNumberOfChannels {
154 return self.session.maximumOutputNumberOfChannels;
155 }
156
157 - (float)inputGain {
158 return self.session.inputGain;
159 }
160
161 - (BOOL)inputGainSettable {
162 return self.session.inputGainSettable;
163 }
164
165 - (BOOL)inputAvailable {
166 return self.session.inputAvailable;
167 }
168
169 - (NSArray<AVAudioSessionDataSourceDescription *> *)inputDataSources {
170 return self.session.inputDataSources;
171 }
172
173 - (AVAudioSessionDataSourceDescription *)inputDataSource {
174 return self.session.inputDataSource;
175 }
176
177 - (NSArray<AVAudioSessionDataSourceDescription *> *)outputDataSources {
178 return self.session.outputDataSources;
179 }
180
181 - (AVAudioSessionDataSourceDescription *)outputDataSource {
182 return self.session.outputDataSource;
183 }
184
185 - (double)sampleRate {
186 return self.session.sampleRate;
187 }
188
189 - (NSInteger)inputNumberOfChannels {
190 return self.session.inputNumberOfChannels;
191 }
192
193 - (NSInteger)outputNumberOfChannels {
194 return self.session.outputNumberOfChannels;
195 }
196
197 - (float)outputVolume {
198 return self.session.outputVolume;
199 }
200
201 - (NSTimeInterval)inputLatency {
202 return self.session.inputLatency;
203 }
204
205 - (NSTimeInterval)outputLatency {
206 return self.session.outputLatency;
207 }
208
209 - (NSTimeInterval)IOBufferDuration {
210 return self.session.IOBufferDuration;
211 }
212
213 - (BOOL)setActive:(BOOL)active
214 error:(NSError **)outError {
215 if (![self checkLock:outError]) {
216 return NO;
217 }
218 NSInteger activationCount = self.activationCount;
219 if (!active && activationCount == 0) {
220 RTCLogWarning(@"Attempting to deactivate without prior activation.");
221 }
222 BOOL success = YES;
223 BOOL isActive = self.isActive;
224 // Keep a local error so we can log it.
225 NSError *error = nil;
226 BOOL shouldSetActive =
227 (active && !isActive) || (!active && isActive && activationCount == 1);
228 // Attempt to activate if we're not active.
229 // Attempt to deactivate if we're active and it's the last unbalanced call.
230 if (shouldSetActive) {
231 AVAudioSession *session = self.session;
232 // AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation is used to ensure
233 // that other audio sessions that were interrupted by our session can return
234 // to their active state. It is recommended for VoIP apps to use this
235 // option.
236 AVAudioSessionSetActiveOptions options =
237 active ? 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
238 success = [session setActive:active
239 withOptions:options
240 error:&error];
241 if (outError) {
242 *outError = error;
243 }
244 }
245 if (success) {
246 if (shouldSetActive) {
247 self.isActive = active;
248 }
249 if (active) {
250 [self incrementActivationCount];
251 }
252 } else {
253 RTCLogError(@"Failed to setActive:%d. Error: %@", active, error);
254 }
255 // Decrement activation count on deactivation whether or not it succeeded.
256 if (!active) {
257 [self decrementActivationCount];
258 }
259 RTCLog(@"Number of current activations: %ld", (long)self.activationCount);
260 return success;
261 }
262
263 - (BOOL)setCategory:(NSString *)category
264 withOptions:(AVAudioSessionCategoryOptions)options
265 error:(NSError **)outError {
266 if (![self checkLock:outError]) {
267 return NO;
268 }
269 return [self.session setCategory:category withOptions:options error:outError];
270 }
271
272 - (BOOL)setMode:(NSString *)mode error:(NSError **)outError {
273 if (![self checkLock:outError]) {
274 return NO;
275 }
276 return [self.session setMode:mode error:outError];
277 }
278
279 - (BOOL)setInputGain:(float)gain error:(NSError **)outError {
280 if (![self checkLock:outError]) {
281 return NO;
282 }
283 return [self.session setInputGain:gain error:outError];
284 }
285
286 - (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError {
287 if (![self checkLock:outError]) {
288 return NO;
289 }
290 return [self.session setPreferredSampleRate:sampleRate error:outError];
291 }
292
293 - (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration
294 error:(NSError **)outError {
295 if (![self checkLock:outError]) {
296 return NO;
297 }
298 return [self.session setPreferredIOBufferDuration:duration error:outError];
299 }
300
301 - (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count
302 error:(NSError **)outError {
303 if (![self checkLock:outError]) {
304 return NO;
305 }
306 return [self.session setPreferredInputNumberOfChannels:count error:outError];
307 }
308 - (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count
309 error:(NSError **)outError {
310 if (![self checkLock:outError]) {
311 return NO;
312 }
313 return [self.session setPreferredOutputNumberOfChannels:count error:outError];
314 }
315
316 - (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride
317 error:(NSError **)outError {
318 if (![self checkLock:outError]) {
319 return NO;
320 }
321 return [self.session overrideOutputAudioPort:portOverride error:outError];
322 }
323
324 - (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort
325 error:(NSError **)outError {
326 if (![self checkLock:outError]) {
327 return NO;
328 }
329 return [self.session setPreferredInput:inPort error:outError];
330 }
331
332 - (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
333 error:(NSError **)outError {
334 if (![self checkLock:outError]) {
335 return NO;
336 }
337 return [self.session setInputDataSource:dataSource error:outError];
338 }
339
340 - (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource
341 error:(NSError **)outError {
342 if (![self checkLock:outError]) {
343 return NO;
344 }
345 return [self.session setOutputDataSource:dataSource error:outError];
346 }
347
348 #pragma mark - Notifications
349
350 - (void)handleInterruptionNotification:(NSNotification *)notification {
351 NSNumber* typeNumber =
352 notification.userInfo[AVAudioSessionInterruptionTypeKey];
353 AVAudioSessionInterruptionType type =
354 (AVAudioSessionInterruptionType)typeNumber.unsignedIntegerValue;
355 switch (type) {
356 case AVAudioSessionInterruptionTypeBegan:
357 RTCLog(@"Audio session interruption began.");
358 self.isActive = NO;
359 [self notifyDidBeginInterruption];
360 break;
361 case AVAudioSessionInterruptionTypeEnded: {
362 RTCLog(@"Audio session interruption ended.");
363 [self updateAudioSessionAfterEvent];
364 NSNumber *optionsNumber =
365 notification.userInfo[AVAudioSessionInterruptionOptionKey];
366 AVAudioSessionInterruptionOptions options =
367 optionsNumber.unsignedIntegerValue;
368 BOOL shouldResume =
369 options & AVAudioSessionInterruptionOptionShouldResume;
370 [self notifyDidEndInterruptionWithShouldResumeSession:shouldResume];
371 break;
372 }
373 }
374 }
375
376 - (void)handleRouteChangeNotification:(NSNotification *)notification {
377 // Get reason for current route change.
378 NSNumber* reasonNumber =
379 notification.userInfo[AVAudioSessionRouteChangeReasonKey];
380 AVAudioSessionRouteChangeReason reason =
381 (AVAudioSessionRouteChangeReason)reasonNumber.unsignedIntegerValue;
382 RTCLog(@"Audio route changed:");
383 switch (reason) {
384 case AVAudioSessionRouteChangeReasonUnknown:
385 RTCLog(@"Audio route changed: ReasonUnknown");
386 break;
387 case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
388 RTCLog(@"Audio route changed: NewDeviceAvailable");
389 break;
390 case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
391 RTCLog(@"Audio route changed: OldDeviceUnavailable");
392 break;
393 case AVAudioSessionRouteChangeReasonCategoryChange:
394 RTCLog(@"Audio route changed: CategoryChange to :%@",
395 self.session.category);
396 break;
397 case AVAudioSessionRouteChangeReasonOverride:
398 RTCLog(@"Audio route changed: Override");
399 break;
400 case AVAudioSessionRouteChangeReasonWakeFromSleep:
401 RTCLog(@"Audio route changed: WakeFromSleep");
402 break;
403 case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
404 RTCLog(@"Audio route changed: NoSuitableRouteForCategory");
405 break;
406 case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
407 RTCLog(@"Audio route changed: RouteConfigurationChange");
408 break;
409 }
410 AVAudioSessionRouteDescription* previousRoute =
411 notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
412 // Log previous route configuration.
413 RTCLog(@"Previous route: %@\nCurrent route:%@",
414 previousRoute, self.session.currentRoute);
415 [self notifyDidChangeRouteWithReason:reason previousRoute:previousRoute];
416 }
417
418 - (void)handleMediaServicesWereLost:(NSNotification *)notification {
419 RTCLog(@"Media services were lost.");
420 [self updateAudioSessionAfterEvent];
421 [self notifyMediaServicesWereLost];
422 }
423
424 - (void)handleMediaServicesWereReset:(NSNotification *)notification {
425 RTCLog(@"Media services were reset.");
426 [self updateAudioSessionAfterEvent];
427 [self notifyMediaServicesWereReset];
428 }
429
430 #pragma mark - Private
431
432 + (NSError *)lockError {
433 NSDictionary *userInfo = @{
434 NSLocalizedDescriptionKey:
435 @"Must call lockForConfiguration before calling this method."
436 };
437 NSError *error =
438 [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain
439 code:kRTCAudioSessionErrorLockRequired
440 userInfo:userInfo];
441 return error;
442 }
443
444 - (BOOL)checkLock:(NSError **)outError {
445 // Check ivar instead of trying to acquire lock so that we won't accidentally
446 // acquire lock if it hasn't already been called.
447 if (!self.isLocked) {
448 if (outError) {
449 *outError = [RTCAudioSession lockError];
450 }
451 return NO;
452 }
453 return YES;
454 }
455
456 - (NSSet *)delegates {
457 @synchronized(self) {
458 return _delegates.setRepresentation;
459 }
460 }
461
462 - (NSInteger)activationCount {
463 @synchronized(self) {
464 return _activationCount;
465 }
466 }
467
468 - (NSInteger)incrementActivationCount {
469 RTCLog(@"Incrementing activation count.");
470 @synchronized(self) {
471 return ++_activationCount;
472 }
473 }
474
475 - (NSInteger)decrementActivationCount {
476 RTCLog(@"Decrementing activation count.");
477 @synchronized(self) {
478 return --_activationCount;
479 }
480 }
481
482 - (void)updateAudioSessionAfterEvent {
483 BOOL shouldActivate = self.activationCount > 0;
484 AVAudioSessionSetActiveOptions options = shouldActivate ?
485 0 : AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation;
486 NSError *error = nil;
487 if ([self.session setActive:shouldActivate
488 withOptions:options
489 error:&error]) {
490 self.isActive = shouldActivate;
491 } else {
492 RTCLogError(@"Failed to set session active to %d. Error:%@",
493 shouldActivate, error.localizedDescription);
494 }
495 }
496
497 - (void)notifyDidBeginInterruption {
498 for (id<RTCAudioSessionDelegate> delegate in self.delegates) {
499 [delegate audioSessionDidBeginInterruption:self];
500 }
501 }
502
503 - (void)notifyDidEndInterruptionWithShouldResumeSession:
504 (BOOL)shouldResumeSession {
505 for (id<RTCAudioSessionDelegate> delegate in self.delegates) {
506 [delegate audioSessionDidEndInterruption:self
507 shouldResumeSession:shouldResumeSession];
508 }
509
510 }
511
512 - (void)notifyDidChangeRouteWithReason:(AVAudioSessionRouteChangeReason)reason
513 previousRoute:(AVAudioSessionRouteDescription *)previousRoute {
514 for (id<RTCAudioSessionDelegate> delegate in self.delegates) {
515 [delegate audioSessionDidChangeRoute:self
516 reason:reason
517 previousRoute:previousRoute];
518 }
519 }
520
521 - (void)notifyMediaServicesWereLost {
522 for (id<RTCAudioSessionDelegate> delegate in self.delegates) {
523 [delegate audioSessionMediaServicesWereLost:self];
524 }
525 }
526
527 - (void)notifyMediaServicesWereReset {
528 for (id<RTCAudioSessionDelegate> delegate in self.delegates) {
529 [delegate audioSessionMediaServicesWereReset:self];
530 }
531 }
532
533 @end
OLDNEW
« no previous file with comments | « webrtc/modules/audio_device/ios/objc/RTCAudioSession.h ('k') | webrtc/modules/audio_device/ios/objc/RTCAudioSession+Private.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698