OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2016 The WebRTC Project Authors. All rights reserved. | 2 * Copyright 2016 The WebRTC Project Authors. All rights reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 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 | 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 | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 */ | 9 */ |
10 | 10 |
11 #import "webrtc/modules/audio_device/ios/objc/RTCAudioSession.h" | 11 #import "webrtc/modules/audio_device/ios/objc/RTCAudioSession.h" |
12 | 12 |
13 #import "webrtc/base/objc/RTCLogging.h" | 13 #import "webrtc/base/objc/RTCLogging.h" |
14 #import "webrtc/modules/audio_device/ios/objc/RTCAudioSession+Private.h" | 14 #import "webrtc/modules/audio_device/ios/objc/RTCAudioSession+Private.h" |
15 #import "webrtc/modules/audio_device/ios/objc/RTCAudioSessionConfiguration.h" | 15 #import "webrtc/modules/audio_device/ios/objc/RTCAudioSessionConfiguration.h" |
16 | 16 |
17 @implementation RTCAudioSession (Configuration) | 17 @implementation RTCAudioSession (Configuration) |
18 | 18 |
| 19 - (BOOL)isConfiguredForWebRTC { |
| 20 return self.savedConfiguration != nil; |
| 21 } |
| 22 |
19 - (BOOL)setConfiguration:(RTCAudioSessionConfiguration *)configuration | 23 - (BOOL)setConfiguration:(RTCAudioSessionConfiguration *)configuration |
20 active:(BOOL)active | 24 active:(BOOL)active |
21 error:(NSError **)outError { | 25 error:(NSError **)outError { |
| 26 NSParameterAssert(configuration); |
| 27 if (outError) { |
| 28 *outError = nil; |
| 29 } |
22 if (![self checkLock:outError]) { | 30 if (![self checkLock:outError]) { |
23 return NO; | 31 return NO; |
24 } | 32 } |
25 | 33 |
26 // Provide an error even if there isn't one so we can log it. We will not | 34 // Provide an error even if there isn't one so we can log it. We will not |
27 // return immediately on error in this function and instead try to set | 35 // return immediately on error in this function and instead try to set |
28 // everything we can. | 36 // everything we can. |
29 NSError *error = nil; | 37 NSError *error = nil; |
30 | 38 |
31 if (self.category != configuration.category || | 39 if (self.category != configuration.category || |
32 self.categoryOptions != configuration.categoryOptions) { | 40 self.categoryOptions != configuration.categoryOptions) { |
33 NSError *categoryError = nil; | 41 NSError *categoryError = nil; |
34 if (![self setCategory:configuration.category | 42 if (![self setCategory:configuration.category |
35 withOptions:configuration.categoryOptions | 43 withOptions:configuration.categoryOptions |
36 error:&categoryError]) { | 44 error:&categoryError]) { |
37 RTCLogError(@"Failed to set category: %@", | 45 RTCLogError(@"Failed to set category: %@", |
38 categoryError.localizedDescription); | 46 categoryError.localizedDescription); |
39 error = categoryError; | 47 error = categoryError; |
| 48 } else { |
| 49 RTCLog(@"Set category to: %@", configuration.category); |
40 } | 50 } |
41 } | 51 } |
42 | 52 |
43 if (self.mode != configuration.mode) { | 53 if (self.mode != configuration.mode) { |
44 NSError *modeError = nil; | 54 NSError *modeError = nil; |
45 if (![self setMode:configuration.mode error:&modeError]) { | 55 if (![self setMode:configuration.mode error:&modeError]) { |
46 RTCLogError(@"Failed to set mode: %@", | 56 RTCLogError(@"Failed to set mode: %@", |
47 modeError.localizedDescription); | 57 modeError.localizedDescription); |
48 error = modeError; | 58 error = modeError; |
| 59 } else { |
| 60 RTCLog(@"Set mode to: %@", configuration.mode); |
49 } | 61 } |
50 } | 62 } |
51 | 63 |
52 // self.sampleRate is accurate only if the audio session is active. | 64 // self.sampleRate is accurate only if the audio session is active. |
53 if (!self.isActive || self.sampleRate != configuration.sampleRate) { | 65 if (!self.isActive || self.sampleRate != configuration.sampleRate) { |
54 NSError *sampleRateError = nil; | 66 NSError *sampleRateError = nil; |
55 if (![self setPreferredSampleRate:configuration.sampleRate | 67 if (![self setPreferredSampleRate:configuration.sampleRate |
56 error:&sampleRateError]) { | 68 error:&sampleRateError]) { |
57 RTCLogError(@"Failed to set preferred sample rate: %@", | 69 RTCLogError(@"Failed to set preferred sample rate: %@", |
58 sampleRateError.localizedDescription); | 70 sampleRateError.localizedDescription); |
59 error = sampleRateError; | 71 error = sampleRateError; |
| 72 } else { |
| 73 RTCLog(@"Set preferred sample rate to: %.2f", |
| 74 configuration.sampleRate); |
60 } | 75 } |
61 } | 76 } |
62 | 77 |
63 // self.IOBufferDuration is accurate only if the audio session is active. | 78 // self.IOBufferDuration is accurate only if the audio session is active. |
64 if (!self.isActive || | 79 if (!self.isActive || |
65 self.IOBufferDuration != configuration.ioBufferDuration) { | 80 self.IOBufferDuration != configuration.ioBufferDuration) { |
66 NSError *bufferDurationError = nil; | 81 NSError *bufferDurationError = nil; |
67 if (![self setPreferredIOBufferDuration:configuration.ioBufferDuration | 82 if (![self setPreferredIOBufferDuration:configuration.ioBufferDuration |
68 error:&bufferDurationError]) { | 83 error:&bufferDurationError]) { |
69 RTCLogError(@"Failed to set preferred IO buffer duration: %@", | 84 RTCLogError(@"Failed to set preferred IO buffer duration: %@", |
70 bufferDurationError.localizedDescription); | 85 bufferDurationError.localizedDescription); |
71 error = bufferDurationError; | 86 error = bufferDurationError; |
| 87 } else { |
| 88 RTCLog(@"Set preferred IO buffer duration to: %f", |
| 89 configuration.ioBufferDuration); |
72 } | 90 } |
73 } | 91 } |
74 | 92 |
75 NSError *activeError = nil; | 93 NSError *activeError = nil; |
76 if (![self setActive:active error:&activeError]) { | 94 if (![self setActive:active error:&activeError]) { |
77 RTCLogError(@"Failed to setActive to %d: %@", | 95 RTCLogError(@"Failed to setActive to %d: %@", |
78 active, activeError.localizedDescription); | 96 active, activeError.localizedDescription); |
79 error = activeError; | 97 error = activeError; |
80 } | 98 } |
81 | 99 |
82 if (self.isActive) { | 100 if (self.isActive && |
| 101 // TODO(tkchin): Figure out which category/mode numChannels is valid for. |
| 102 [self.mode isEqualToString:AVAudioSessionModeVoiceChat]) { |
83 // Try to set the preferred number of hardware audio channels. These calls | 103 // Try to set the preferred number of hardware audio channels. These calls |
84 // must be done after setting the audio session’s category and mode and | 104 // must be done after setting the audio session’s category and mode and |
85 // activating the session. | 105 // activating the session. |
86 NSInteger inputNumberOfChannels = configuration.inputNumberOfChannels; | 106 NSInteger inputNumberOfChannels = configuration.inputNumberOfChannels; |
87 if (self.inputNumberOfChannels != inputNumberOfChannels) { | 107 if (self.inputNumberOfChannels != inputNumberOfChannels) { |
88 NSError *inputChannelsError = nil; | 108 NSError *inputChannelsError = nil; |
89 if (![self setPreferredInputNumberOfChannels:inputNumberOfChannels | 109 if (![self setPreferredInputNumberOfChannels:inputNumberOfChannels |
90 error:&inputChannelsError]) { | 110 error:&inputChannelsError]) { |
91 RTCLogError(@"Failed to set preferred input number of channels: %@", | 111 RTCLogError(@"Failed to set preferred input number of channels: %@", |
92 inputChannelsError.localizedDescription); | 112 inputChannelsError.localizedDescription); |
93 error = inputChannelsError; | 113 error = inputChannelsError; |
| 114 } else { |
| 115 RTCLog(@"Set input number of channels to: %ld", |
| 116 (long)inputNumberOfChannels); |
94 } | 117 } |
95 } | 118 } |
96 NSInteger outputNumberOfChannels = configuration.outputNumberOfChannels; | 119 NSInteger outputNumberOfChannels = configuration.outputNumberOfChannels; |
97 if (self.outputNumberOfChannels != outputNumberOfChannels) { | 120 if (self.outputNumberOfChannels != outputNumberOfChannels) { |
98 NSError *outputChannelsError = nil; | 121 NSError *outputChannelsError = nil; |
99 if (![self setPreferredOutputNumberOfChannels:outputNumberOfChannels | 122 if (![self setPreferredOutputNumberOfChannels:outputNumberOfChannels |
100 error:&outputChannelsError]) { | 123 error:&outputChannelsError]) { |
101 RTCLogError(@"Failed to set preferred output number of channels: %@", | 124 RTCLogError(@"Failed to set preferred output number of channels: %@", |
102 outputChannelsError.localizedDescription); | 125 outputChannelsError.localizedDescription); |
103 error = outputChannelsError; | 126 error = outputChannelsError; |
| 127 } else { |
| 128 RTCLog(@"Set output number of channels to: %ld", |
| 129 (long)outputNumberOfChannels); |
104 } | 130 } |
105 } | 131 } |
106 } | 132 } |
107 | 133 |
108 if (outError) { | 134 if (outError) { |
109 *outError = error; | 135 *outError = error; |
110 } | 136 } |
111 | 137 |
112 return error == nil; | 138 return error == nil; |
113 } | 139 } |
114 | 140 |
115 - (BOOL)configureWebRTCSession:(NSError **)outError { | 141 - (BOOL)configureWebRTCSession:(NSError **)outError { |
| 142 if (outError) { |
| 143 *outError = nil; |
| 144 } |
116 if (![self checkLock:outError]) { | 145 if (![self checkLock:outError]) { |
117 return NO; | 146 return NO; |
118 } | 147 } |
119 RTCLog(@"Configuring audio session for WebRTC."); | 148 RTCLog(@"Configuring audio session for WebRTC."); |
120 | 149 |
| 150 if (self.isConfiguredForWebRTC) { |
| 151 RTCLogError(@"Already configured."); |
| 152 if (outError) { |
| 153 *outError = |
| 154 [self configurationErrorWithDescription:@"Already configured."]; |
| 155 } |
| 156 return NO; |
| 157 } |
| 158 |
| 159 // Configure the AVAudioSession and activate it. |
121 // Provide an error even if there isn't one so we can log it. | 160 // Provide an error even if there isn't one so we can log it. |
122 BOOL hasSucceeded = YES; | |
123 NSError *error = nil; | 161 NSError *error = nil; |
124 RTCAudioSessionConfiguration *currentConfig = | 162 RTCAudioSessionConfiguration *currentConfig = |
125 [RTCAudioSessionConfiguration currentConfiguration]; | 163 [RTCAudioSessionConfiguration currentConfiguration]; |
126 RTCAudioSessionConfiguration *webRTCConfig = | 164 RTCAudioSessionConfiguration *webRTCConfig = |
127 [RTCAudioSessionConfiguration webRTCConfiguration]; | 165 [RTCAudioSessionConfiguration webRTCConfiguration]; |
| 166 self.savedConfiguration = currentConfig; |
128 if (![self setConfiguration:webRTCConfig active:YES error:&error]) { | 167 if (![self setConfiguration:webRTCConfig active:YES error:&error]) { |
129 RTCLogError(@"Failed to set WebRTC audio configuration: %@", | 168 RTCLogError(@"Failed to set WebRTC audio configuration: %@", |
130 error.localizedDescription); | 169 error.localizedDescription); |
131 // Attempt to restore previous state. | 170 [self unconfigureWebRTCSession:nil]; |
132 [self setConfiguration:currentConfig active:NO error:nil]; | 171 if (outError) { |
133 hasSucceeded = NO; | 172 *outError = error; |
134 } else if (![self isConfiguredForWebRTC]) { | 173 } |
135 // Ensure that the active audio session has the correct category and mode. | |
136 // This should never happen - this means that we succeeded earlier but | |
137 // somehow the settings didn't apply. | |
138 RTCLogError(@"Failed to configure audio session."); | |
139 // Attempt to restore previous state. | |
140 [self setConfiguration:currentConfig active:NO error:nil]; | |
141 error = | |
142 [[NSError alloc] initWithDomain:kRTCAudioSessionErrorDomain | |
143 code:kRTCAudioSessionErrorConfiguration | |
144 userInfo:nil]; | |
145 hasSucceeded = NO; | |
146 } | |
147 | |
148 if (outError) { | |
149 *outError = error; | |
150 } | |
151 | |
152 return hasSucceeded; | |
153 } | |
154 | |
155 #pragma mark - Private | |
156 | |
157 - (BOOL)isConfiguredForWebRTC { | |
158 // Ensure that the device currently supports audio input. | |
159 if (!self.inputAvailable) { | |
160 RTCLogError(@"No audio input path is available!"); | |
161 return NO; | 174 return NO; |
162 } | 175 } |
163 | 176 |
164 // Only check a minimal list of requirements for whether we have | 177 // Ensure that the device currently supports audio input. |
165 // what we want. | 178 // TODO(tkchin): Figure out if this is really necessary. |
166 RTCAudioSessionConfiguration *currentConfig = | 179 if (!self.inputAvailable) { |
167 [RTCAudioSessionConfiguration currentConfiguration]; | 180 RTCLogError(@"No audio input path is available!"); |
168 RTCAudioSessionConfiguration *webRTCConfig = | 181 [self unconfigureWebRTCSession:nil]; |
169 [RTCAudioSessionConfiguration webRTCConfiguration]; | 182 if (outError) { |
170 | 183 *outError = [self configurationErrorWithDescription:@"No input path."]; |
171 if (![currentConfig.category isEqualToString:webRTCConfig.category]) { | 184 } |
172 RTCLog(@"Current category %@ does not match %@", | |
173 currentConfig.category, | |
174 webRTCConfig.category); | |
175 return NO; | 185 return NO; |
176 } | 186 } |
177 | 187 |
178 if (![currentConfig.mode isEqualToString:webRTCConfig.mode]) { | 188 // Give delegates a chance to process the event. In particular, the audio |
179 RTCLog(@"Current mode %@ does not match %@", | 189 // devices listening to this event will initialize their audio units. |
180 currentConfig.mode, | 190 [self notifyDidConfigure]; |
181 webRTCConfig.mode); | |
182 return NO; | |
183 } | |
184 | 191 |
185 return YES; | 192 return YES; |
186 } | 193 } |
187 | 194 |
| 195 - (BOOL)unconfigureWebRTCSession:(NSError **)outError { |
| 196 if (outError) { |
| 197 *outError = nil; |
| 198 } |
| 199 if (![self checkLock:outError]) { |
| 200 return NO; |
| 201 } |
| 202 RTCLog(@"Unconfiguring audio session for WebRTC."); |
| 203 |
| 204 if (!self.isConfiguredForWebRTC) { |
| 205 RTCLogError(@"Already unconfigured."); |
| 206 if (outError) { |
| 207 *outError = |
| 208 [self configurationErrorWithDescription:@"Already unconfigured."]; |
| 209 } |
| 210 return NO; |
| 211 } |
| 212 |
| 213 [self setConfiguration:self.savedConfiguration active:NO error:outError]; |
| 214 self.savedConfiguration = nil; |
| 215 |
| 216 [self notifyDidUnconfigure]; |
| 217 |
| 218 return YES; |
| 219 } |
| 220 |
188 @end | 221 @end |
OLD | NEW |