| 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 | 
|---|