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/voice_processing_audio_unit.h" |
| 12 |
| 13 #include "webrtc/base/checks.h" |
| 14 |
| 15 #import "webrtc/base/objc/RTCLogging.h" |
| 16 #import "webrtc/modules/audio_device/ios/objc/RTCAudioSessionConfiguration.h" |
| 17 |
| 18 #if !defined(NDEBUG) |
| 19 static void LogStreamDescription(AudioStreamBasicDescription description) { |
| 20 char formatIdString[5]; |
| 21 UInt32 formatId = CFSwapInt32HostToBig(description.mFormatID); |
| 22 bcopy(&formatId, formatIdString, 4); |
| 23 formatIdString[4] = '\0'; |
| 24 RTCLog(@"AudioStreamBasicDescription: {\n" |
| 25 " mSampleRate: %.2f\n" |
| 26 " formatIDString: %s\n" |
| 27 " mFormatFlags: 0x%X\n" |
| 28 " mBytesPerPacket: %u\n" |
| 29 " mFramesPerPacket: %u\n" |
| 30 " mBytesPerFrame: %u\n" |
| 31 " mChannelsPerFrame: %u\n" |
| 32 " mBitsPerChannel: %u\n" |
| 33 " mReserved: %u\n}", |
| 34 description.mSampleRate, formatIdString, |
| 35 static_cast<unsigned int>(description.mFormatFlags), |
| 36 static_cast<unsigned int>(description.mBytesPerPacket), |
| 37 static_cast<unsigned int>(description.mFramesPerPacket), |
| 38 static_cast<unsigned int>(description.mBytesPerFrame), |
| 39 static_cast<unsigned int>(description.mChannelsPerFrame), |
| 40 static_cast<unsigned int>(description.mBitsPerChannel), |
| 41 static_cast<unsigned int>(description.mReserved)); |
| 42 } |
| 43 #endif |
| 44 |
| 45 namespace webrtc { |
| 46 |
| 47 // Calls to AudioUnitInitialize() can fail if called back-to-back on different |
| 48 // ADM instances. A fall-back solution is to allow multiple sequential calls |
| 49 // with as small delay between each. This factor sets the max number of allowed |
| 50 // initialization attempts. |
| 51 static const int kMaxNumberOfAudioUnitInitializeAttempts = 5; |
| 52 // A VP I/O unit's bus 1 connects to input hardware (microphone). |
| 53 static const AudioUnitElement kInputBus = 1; |
| 54 // A VP I/O unit's bus 0 connects to output hardware (speaker). |
| 55 static const AudioUnitElement kOutputBus = 0; |
| 56 |
| 57 VoiceProcessingAudioUnit::VoiceProcessingAudioUnit( |
| 58 VoiceProcessingAudioUnitObserver* observer) |
| 59 : observer_(observer), vpio_unit_(nullptr) { |
| 60 RTC_DCHECK(observer); |
| 61 } |
| 62 |
| 63 VoiceProcessingAudioUnit::~VoiceProcessingAudioUnit() { |
| 64 DisposeAudioUnit(); |
| 65 } |
| 66 |
| 67 const UInt32 VoiceProcessingAudioUnit::kBytesPerSample = 2; |
| 68 |
| 69 bool VoiceProcessingAudioUnit::Init() { |
| 70 RTC_DCHECK(!vpio_unit_) << "Already called Init()."; |
| 71 |
| 72 // Create an audio component description to identify the Voice Processing |
| 73 // I/O audio unit. |
| 74 AudioComponentDescription vpio_unit_description; |
| 75 vpio_unit_description.componentType = kAudioUnitType_Output; |
| 76 vpio_unit_description.componentSubType = kAudioUnitSubType_VoiceProcessingIO; |
| 77 vpio_unit_description.componentManufacturer = kAudioUnitManufacturer_Apple; |
| 78 vpio_unit_description.componentFlags = 0; |
| 79 vpio_unit_description.componentFlagsMask = 0; |
| 80 |
| 81 // Obtain an audio unit instance given the description. |
| 82 AudioComponent found_vpio_unit_ref = |
| 83 AudioComponentFindNext(nullptr, &vpio_unit_description); |
| 84 |
| 85 // Create a Voice Processing IO audio unit. |
| 86 OSStatus result = noErr; |
| 87 result = AudioComponentInstanceNew(found_vpio_unit_ref, &vpio_unit_); |
| 88 if (result != noErr) { |
| 89 vpio_unit_ = nullptr; |
| 90 RTCLogError(@"AudioComponentInstanceNew failed. Error=%ld.", (long)result); |
| 91 return false; |
| 92 } |
| 93 |
| 94 // Enable input on the input scope of the input element. |
| 95 UInt32 enable_input = 1; |
| 96 result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO, |
| 97 kAudioUnitScope_Input, kInputBus, &enable_input, |
| 98 sizeof(enable_input)); |
| 99 if (result != noErr) { |
| 100 DisposeAudioUnit(); |
| 101 RTCLogError(@"Failed to enable input on input scope of input element. " |
| 102 "Error=%ld.", |
| 103 (long)result); |
| 104 return false; |
| 105 } |
| 106 |
| 107 // Enable output on the output scope of the output element. |
| 108 UInt32 enable_output = 1; |
| 109 result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO, |
| 110 kAudioUnitScope_Output, kOutputBus, |
| 111 &enable_output, sizeof(enable_output)); |
| 112 if (result != noErr) { |
| 113 DisposeAudioUnit(); |
| 114 RTCLogError(@"Failed to enable output on output scope of output element. " |
| 115 "Error=%ld.", |
| 116 (long)result); |
| 117 return false; |
| 118 } |
| 119 |
| 120 // Specify the callback function that provides audio samples to the audio |
| 121 // unit. |
| 122 AURenderCallbackStruct render_callback; |
| 123 render_callback.inputProc = OnGetPlayoutData; |
| 124 render_callback.inputProcRefCon = this; |
| 125 result = AudioUnitSetProperty( |
| 126 vpio_unit_, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, |
| 127 kOutputBus, &render_callback, sizeof(render_callback)); |
| 128 if (result != noErr) { |
| 129 DisposeAudioUnit(); |
| 130 RTCLogError(@"Failed to specify the render callback on the output bus. " |
| 131 "Error=%ld.", |
| 132 (long)result); |
| 133 return false; |
| 134 } |
| 135 |
| 136 // Disable AU buffer allocation for the recorder, we allocate our own. |
| 137 // TODO(henrika): not sure that it actually saves resource to make this call. |
| 138 UInt32 flag = 0; |
| 139 result = AudioUnitSetProperty( |
| 140 vpio_unit_, kAudioUnitProperty_ShouldAllocateBuffer, |
| 141 kAudioUnitScope_Output, kInputBus, &flag, sizeof(flag)); |
| 142 if (result != noErr) { |
| 143 DisposeAudioUnit(); |
| 144 RTCLogError(@"Failed to disable buffer allocation on the input bus. " |
| 145 "Error=%ld.", |
| 146 (long)result); |
| 147 return false; |
| 148 } |
| 149 |
| 150 // Specify the callback to be called by the I/O thread to us when input audio |
| 151 // is available. The recorded samples can then be obtained by calling the |
| 152 // AudioUnitRender() method. |
| 153 AURenderCallbackStruct input_callback; |
| 154 input_callback.inputProc = OnDeliverRecordedData; |
| 155 input_callback.inputProcRefCon = this; |
| 156 result = AudioUnitSetProperty(vpio_unit_, |
| 157 kAudioOutputUnitProperty_SetInputCallback, |
| 158 kAudioUnitScope_Global, kInputBus, |
| 159 &input_callback, sizeof(input_callback)); |
| 160 if (result != noErr) { |
| 161 DisposeAudioUnit(); |
| 162 RTCLogError(@"Failed to specify the input callback on the input bus. " |
| 163 "Error=%ld.", |
| 164 (long)result); |
| 165 return false; |
| 166 } |
| 167 |
| 168 return true; |
| 169 } |
| 170 |
| 171 bool VoiceProcessingAudioUnit::Initialize(Float64 sample_rate) { |
| 172 RTC_DCHECK(vpio_unit_) << "Init() not called."; |
| 173 RTCLog(@"Initializing audio unit."); |
| 174 |
| 175 OSStatus result = noErr; |
| 176 AudioStreamBasicDescription format = GetFormat(sample_rate); |
| 177 UInt32 size = sizeof(format); |
| 178 #if !defined(NDEBUG) |
| 179 LogStreamDescription(format); |
| 180 #endif |
| 181 |
| 182 // Set the format on the output scope of the input element/bus. |
| 183 result = |
| 184 AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat, |
| 185 kAudioUnitScope_Output, kInputBus, &format, size); |
| 186 if (result != noErr) { |
| 187 RTCLogError(@"Failed to set format on output scope of input bus. " |
| 188 "Error=%ld.", |
| 189 (long)result); |
| 190 return false; |
| 191 } |
| 192 |
| 193 // Set the format on the input scope of the output element/bus. |
| 194 result = |
| 195 AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat, |
| 196 kAudioUnitScope_Input, kOutputBus, &format, size); |
| 197 if (result != noErr) { |
| 198 RTCLogError(@"Failed to set format on input scope of output bus. " |
| 199 "Error=%ld.", |
| 200 (long)result); |
| 201 return false; |
| 202 } |
| 203 |
| 204 // Initialize the Voice Processing I/O unit instance. |
| 205 // Calls to AudioUnitInitialize() can fail if called back-to-back on |
| 206 // different ADM instances. The error message in this case is -66635 which is |
| 207 // undocumented. Tests have shown that calling AudioUnitInitialize a second |
| 208 // time, after a short sleep, avoids this issue. |
| 209 // See webrtc:5166 for details. |
| 210 int failed_initalize_attempts = 0; |
| 211 result = AudioUnitInitialize(vpio_unit_); |
| 212 while (result != noErr) { |
| 213 RTCLogError(@"Failed to initialize the Voice Processing I/O unit. " |
| 214 "Error=%ld.", |
| 215 (long)result); |
| 216 ++failed_initalize_attempts; |
| 217 if (failed_initalize_attempts == kMaxNumberOfAudioUnitInitializeAttempts) { |
| 218 // Max number of initialization attempts exceeded, hence abort. |
| 219 RTCLogError(@"Too many initialization attempts."); |
| 220 return false; |
| 221 } |
| 222 RTCLog(@"Pause 100ms and try audio unit initialization again..."); |
| 223 [NSThread sleepForTimeInterval:0.1f]; |
| 224 result = AudioUnitInitialize(vpio_unit_); |
| 225 } |
| 226 RTCLog(@"Voice Processing I/O unit is now initialized."); |
| 227 return true; |
| 228 } |
| 229 |
| 230 bool VoiceProcessingAudioUnit::Start() { |
| 231 RTC_DCHECK(vpio_unit_) << "Init() not called."; |
| 232 RTCLog(@"Starting audio unit."); |
| 233 |
| 234 OSStatus result = AudioOutputUnitStart(vpio_unit_); |
| 235 if (result != noErr) { |
| 236 RTCLogError(@"Failed to start audio unit. Error=%ld", (long)result); |
| 237 return false; |
| 238 } |
| 239 return true; |
| 240 } |
| 241 |
| 242 bool VoiceProcessingAudioUnit::Stop() { |
| 243 RTC_DCHECK(vpio_unit_) << "Init() not called."; |
| 244 RTCLog(@"Stopping audio unit."); |
| 245 |
| 246 OSStatus result = AudioOutputUnitStop(vpio_unit_); |
| 247 if (result != noErr) { |
| 248 RTCLogError(@"Failed to stop audio unit. Error=%ld", (long)result); |
| 249 return false; |
| 250 } |
| 251 return true; |
| 252 } |
| 253 |
| 254 bool VoiceProcessingAudioUnit::Uninitialize() { |
| 255 RTC_DCHECK(vpio_unit_) << "Init() not called."; |
| 256 RTCLog(@"Unintializing audio unit."); |
| 257 |
| 258 OSStatus result = AudioUnitUninitialize(vpio_unit_); |
| 259 if (result != noErr) { |
| 260 RTCLogError(@"Failed to uninitialize audio unit. Error=%ld", (long)result); |
| 261 return false; |
| 262 } |
| 263 return true; |
| 264 } |
| 265 |
| 266 OSStatus VoiceProcessingAudioUnit::Render(AudioUnitRenderActionFlags* flags, |
| 267 const AudioTimeStamp* time_stamp, |
| 268 UInt32 output_bus_number, |
| 269 UInt32 num_frames, |
| 270 AudioBufferList* io_data) { |
| 271 RTC_DCHECK(vpio_unit_) << "Init() not called."; |
| 272 |
| 273 OSStatus result = AudioUnitRender(vpio_unit_, flags, time_stamp, |
| 274 output_bus_number, num_frames, io_data); |
| 275 if (result != noErr) { |
| 276 RTCLogError(@"Failed to render audio unit. Error=%ld", (long)result); |
| 277 } |
| 278 return result; |
| 279 } |
| 280 |
| 281 OSStatus VoiceProcessingAudioUnit::OnGetPlayoutData( |
| 282 void* in_ref_con, |
| 283 AudioUnitRenderActionFlags* flags, |
| 284 const AudioTimeStamp* time_stamp, |
| 285 UInt32 bus_number, |
| 286 UInt32 num_frames, |
| 287 AudioBufferList* io_data) { |
| 288 VoiceProcessingAudioUnit* audio_unit = |
| 289 static_cast<VoiceProcessingAudioUnit*>(in_ref_con); |
| 290 return audio_unit->NotifyGetPlayoutData(flags, time_stamp, bus_number, |
| 291 num_frames, io_data); |
| 292 } |
| 293 |
| 294 OSStatus VoiceProcessingAudioUnit::OnDeliverRecordedData( |
| 295 void* in_ref_con, |
| 296 AudioUnitRenderActionFlags* flags, |
| 297 const AudioTimeStamp* time_stamp, |
| 298 UInt32 bus_number, |
| 299 UInt32 num_frames, |
| 300 AudioBufferList* io_data) { |
| 301 VoiceProcessingAudioUnit* audio_unit = |
| 302 static_cast<VoiceProcessingAudioUnit*>(in_ref_con); |
| 303 return audio_unit->NotifyDeliverRecordedData(flags, time_stamp, bus_number, |
| 304 num_frames, io_data); |
| 305 } |
| 306 |
| 307 OSStatus VoiceProcessingAudioUnit::NotifyGetPlayoutData( |
| 308 AudioUnitRenderActionFlags* flags, |
| 309 const AudioTimeStamp* time_stamp, |
| 310 UInt32 bus_number, |
| 311 UInt32 num_frames, |
| 312 AudioBufferList* io_data) { |
| 313 return observer_->OnGetPlayoutData(flags, time_stamp, bus_number, num_frames, |
| 314 io_data); |
| 315 } |
| 316 |
| 317 OSStatus VoiceProcessingAudioUnit::NotifyDeliverRecordedData( |
| 318 AudioUnitRenderActionFlags* flags, |
| 319 const AudioTimeStamp* time_stamp, |
| 320 UInt32 bus_number, |
| 321 UInt32 num_frames, |
| 322 AudioBufferList* io_data) { |
| 323 return observer_->OnDeliverRecordedData(flags, time_stamp, bus_number, |
| 324 num_frames, io_data); |
| 325 } |
| 326 |
| 327 AudioStreamBasicDescription VoiceProcessingAudioUnit::GetFormat( |
| 328 Float64 sample_rate) const { |
| 329 // Set the application formats for input and output: |
| 330 // - use same format in both directions |
| 331 // - avoid resampling in the I/O unit by using the hardware sample rate |
| 332 // - linear PCM => noncompressed audio data format with one frame per packet |
| 333 // - no need to specify interleaving since only mono is supported |
| 334 AudioStreamBasicDescription format = {0}; |
| 335 RTC_DCHECK_EQ(1, kRTCAudioSessionPreferredNumberOfChannels); |
| 336 format.mSampleRate = sample_rate; |
| 337 format.mFormatID = kAudioFormatLinearPCM; |
| 338 format.mFormatFlags = |
| 339 kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; |
| 340 format.mBytesPerPacket = kBytesPerSample; |
| 341 format.mFramesPerPacket = 1; // uncompressed. |
| 342 format.mBytesPerFrame = kBytesPerSample; |
| 343 format.mChannelsPerFrame = kRTCAudioSessionPreferredNumberOfChannels; |
| 344 format.mBitsPerChannel = 8 * kBytesPerSample; |
| 345 return format; |
| 346 } |
| 347 |
| 348 void VoiceProcessingAudioUnit::DisposeAudioUnit() { |
| 349 if (vpio_unit_) { |
| 350 OSStatus result = AudioComponentInstanceDispose(vpio_unit_); |
| 351 if (result != noErr) { |
| 352 RTCLogError(@"AudioComponentInstanceDispose failed. Error=%ld.", |
| 353 (long)result); |
| 354 } |
| 355 vpio_unit_ = nullptr; |
| 356 } |
| 357 } |
| 358 |
| 359 } // namespace webrtc |
OLD | NEW |