Index: webrtc/modules/audio_device/ios/voice_processing_audio_unit.mm |
diff --git a/webrtc/modules/audio_device/ios/voice_processing_audio_unit.mm b/webrtc/modules/audio_device/ios/voice_processing_audio_unit.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4b30909910e5b10bdfb05ddff5ba2b40a80192cc |
--- /dev/null |
+++ b/webrtc/modules/audio_device/ios/voice_processing_audio_unit.mm |
@@ -0,0 +1,359 @@ |
+/* |
+ * Copyright 2016 The WebRTC Project Authors. All rights reserved. |
+ * |
+ * Use of this source code is governed by a BSD-style license |
+ * that can be found in the LICENSE file in the root of the source |
+ * tree. An additional intellectual property rights grant can be found |
+ * in the file PATENTS. All contributing project authors may |
+ * be found in the AUTHORS file in the root of the source tree. |
+ */ |
+ |
+#import "webrtc/modules/audio_device/ios/voice_processing_audio_unit.h" |
+ |
+#include "webrtc/base/checks.h" |
+ |
+#import "webrtc/base/objc/RTCLogging.h" |
+#import "webrtc/modules/audio_device/ios/objc/RTCAudioSessionConfiguration.h" |
+ |
+#if !defined(NDEBUG) |
+static void LogStreamDescription(AudioStreamBasicDescription description) { |
+ char formatIdString[5]; |
+ UInt32 formatId = CFSwapInt32HostToBig(description.mFormatID); |
+ bcopy(&formatId, formatIdString, 4); |
+ formatIdString[4] = '\0'; |
+ RTCLog(@"AudioStreamBasicDescription: {\n" |
+ " mSampleRate: %.2f\n" |
+ " formatIDString: %s\n" |
+ " mFormatFlags: 0x%X\n" |
+ " mBytesPerPacket: %u\n" |
+ " mFramesPerPacket: %u\n" |
+ " mBytesPerFrame: %u\n" |
+ " mChannelsPerFrame: %u\n" |
+ " mBitsPerChannel: %u\n" |
+ " mReserved: %u\n}", |
+ description.mSampleRate, formatIdString, |
+ static_cast<unsigned int>(description.mFormatFlags), |
+ static_cast<unsigned int>(description.mBytesPerPacket), |
+ static_cast<unsigned int>(description.mFramesPerPacket), |
+ static_cast<unsigned int>(description.mBytesPerFrame), |
+ static_cast<unsigned int>(description.mChannelsPerFrame), |
+ static_cast<unsigned int>(description.mBitsPerChannel), |
+ static_cast<unsigned int>(description.mReserved)); |
+} |
+#endif |
+ |
+namespace webrtc { |
+ |
+// Calls to AudioUnitInitialize() can fail if called back-to-back on different |
+// ADM instances. A fall-back solution is to allow multiple sequential calls |
+// with as small delay between each. This factor sets the max number of allowed |
+// initialization attempts. |
+static const int kMaxNumberOfAudioUnitInitializeAttempts = 5; |
+// A VP I/O unit's bus 1 connects to input hardware (microphone). |
+static const AudioUnitElement kInputBus = 1; |
+// A VP I/O unit's bus 0 connects to output hardware (speaker). |
+static const AudioUnitElement kOutputBus = 0; |
+ |
+VoiceProcessingAudioUnit::VoiceProcessingAudioUnit( |
+ VoiceProcessingAudioUnitObserver* observer) |
+ : observer_(observer), vpio_unit_(nullptr) { |
+ RTC_DCHECK(observer); |
+} |
+ |
+VoiceProcessingAudioUnit::~VoiceProcessingAudioUnit() { |
+ DisposeAudioUnit(); |
+} |
+ |
+const UInt32 VoiceProcessingAudioUnit::kBytesPerSample = 2; |
+ |
+bool VoiceProcessingAudioUnit::Init() { |
+ RTC_DCHECK(!vpio_unit_) << "Already called Init()."; |
+ |
+ // Create an audio component description to identify the Voice Processing |
+ // I/O audio unit. |
+ AudioComponentDescription vpio_unit_description; |
+ vpio_unit_description.componentType = kAudioUnitType_Output; |
+ vpio_unit_description.componentSubType = kAudioUnitSubType_VoiceProcessingIO; |
+ vpio_unit_description.componentManufacturer = kAudioUnitManufacturer_Apple; |
+ vpio_unit_description.componentFlags = 0; |
+ vpio_unit_description.componentFlagsMask = 0; |
+ |
+ // Obtain an audio unit instance given the description. |
+ AudioComponent found_vpio_unit_ref = |
+ AudioComponentFindNext(nullptr, &vpio_unit_description); |
+ |
+ // Create a Voice Processing IO audio unit. |
+ OSStatus result = noErr; |
+ result = AudioComponentInstanceNew(found_vpio_unit_ref, &vpio_unit_); |
+ if (result != noErr) { |
+ vpio_unit_ = nullptr; |
+ RTCLogError(@"AudioComponentInstanceNew failed. Error=%ld.", (long)result); |
+ return false; |
+ } |
+ |
+ // Enable input on the input scope of the input element. |
+ UInt32 enable_input = 1; |
+ result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO, |
+ kAudioUnitScope_Input, kInputBus, &enable_input, |
+ sizeof(enable_input)); |
+ if (result != noErr) { |
+ DisposeAudioUnit(); |
+ RTCLogError(@"Failed to enable input on input scope of input element. " |
+ "Error=%ld.", |
+ (long)result); |
+ return false; |
+ } |
+ |
+ // Enable output on the output scope of the output element. |
+ UInt32 enable_output = 1; |
+ result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO, |
+ kAudioUnitScope_Output, kOutputBus, |
+ &enable_output, sizeof(enable_output)); |
+ if (result != noErr) { |
+ DisposeAudioUnit(); |
+ RTCLogError(@"Failed to enable output on output scope of output element. " |
+ "Error=%ld.", |
+ (long)result); |
+ return false; |
+ } |
+ |
+ // Specify the callback function that provides audio samples to the audio |
+ // unit. |
+ AURenderCallbackStruct render_callback; |
+ render_callback.inputProc = OnGetPlayoutData; |
+ render_callback.inputProcRefCon = this; |
+ result = AudioUnitSetProperty( |
+ vpio_unit_, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, |
+ kOutputBus, &render_callback, sizeof(render_callback)); |
+ if (result != noErr) { |
+ DisposeAudioUnit(); |
+ RTCLogError(@"Failed to specify the render callback on the output bus. " |
+ "Error=%ld.", |
+ (long)result); |
+ return false; |
+ } |
+ |
+ // Disable AU buffer allocation for the recorder, we allocate our own. |
+ // TODO(henrika): not sure that it actually saves resource to make this call. |
+ UInt32 flag = 0; |
+ result = AudioUnitSetProperty( |
+ vpio_unit_, kAudioUnitProperty_ShouldAllocateBuffer, |
+ kAudioUnitScope_Output, kInputBus, &flag, sizeof(flag)); |
+ if (result != noErr) { |
+ DisposeAudioUnit(); |
+ RTCLogError(@"Failed to disable buffer allocation on the input bus. " |
+ "Error=%ld.", |
+ (long)result); |
+ return false; |
+ } |
+ |
+ // Specify the callback to be called by the I/O thread to us when input audio |
+ // is available. The recorded samples can then be obtained by calling the |
+ // AudioUnitRender() method. |
+ AURenderCallbackStruct input_callback; |
+ input_callback.inputProc = OnDeliverRecordedData; |
+ input_callback.inputProcRefCon = this; |
+ result = AudioUnitSetProperty(vpio_unit_, |
+ kAudioOutputUnitProperty_SetInputCallback, |
+ kAudioUnitScope_Global, kInputBus, |
+ &input_callback, sizeof(input_callback)); |
+ if (result != noErr) { |
+ DisposeAudioUnit(); |
+ RTCLogError(@"Failed to specify the input callback on the input bus. " |
+ "Error=%ld.", |
+ (long)result); |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+bool VoiceProcessingAudioUnit::Initialize(Float64 sample_rate) { |
+ RTC_DCHECK(vpio_unit_) << "Init() not called."; |
+ RTCLog(@"Initializing audio unit."); |
+ |
+ OSStatus result = noErr; |
+ AudioStreamBasicDescription format = GetFormat(sample_rate); |
+ UInt32 size = sizeof(format); |
+#if !defined(NDEBUG) |
+ LogStreamDescription(format); |
+#endif |
+ |
+ // Set the format on the output scope of the input element/bus. |
+ result = |
+ AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat, |
+ kAudioUnitScope_Output, kInputBus, &format, size); |
+ if (result != noErr) { |
+ RTCLogError(@"Failed to set format on output scope of input bus. " |
+ "Error=%ld.", |
+ (long)result); |
+ return false; |
+ } |
+ |
+ // Set the format on the input scope of the output element/bus. |
+ result = |
+ AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat, |
+ kAudioUnitScope_Input, kOutputBus, &format, size); |
+ if (result != noErr) { |
+ RTCLogError(@"Failed to set format on input scope of output bus. " |
+ "Error=%ld.", |
+ (long)result); |
+ return false; |
+ } |
+ |
+ // Initialize the Voice Processing I/O unit instance. |
+ // Calls to AudioUnitInitialize() can fail if called back-to-back on |
+ // different ADM instances. The error message in this case is -66635 which is |
+ // undocumented. Tests have shown that calling AudioUnitInitialize a second |
+ // time, after a short sleep, avoids this issue. |
+ // See webrtc:5166 for details. |
+ int failed_initalize_attempts = 0; |
+ result = AudioUnitInitialize(vpio_unit_); |
+ while (result != noErr) { |
+ RTCLogError(@"Failed to initialize the Voice Processing I/O unit. " |
+ "Error=%ld.", |
+ (long)result); |
+ ++failed_initalize_attempts; |
+ if (failed_initalize_attempts == kMaxNumberOfAudioUnitInitializeAttempts) { |
+ // Max number of initialization attempts exceeded, hence abort. |
+ RTCLogError(@"Too many initialization attempts."); |
+ return false; |
+ } |
+ RTCLog(@"Pause 100ms and try audio unit initialization again..."); |
+ [NSThread sleepForTimeInterval:0.1f]; |
+ result = AudioUnitInitialize(vpio_unit_); |
+ } |
+ RTCLog(@"Voice Processing I/O unit is now initialized."); |
+ return true; |
+} |
+ |
+bool VoiceProcessingAudioUnit::Start() { |
+ RTC_DCHECK(vpio_unit_) << "Init() not called."; |
+ RTCLog(@"Starting audio unit."); |
+ |
+ OSStatus result = AudioOutputUnitStart(vpio_unit_); |
+ if (result != noErr) { |
+ RTCLogError(@"Failed to start audio unit. Error=%ld", (long)result); |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+bool VoiceProcessingAudioUnit::Stop() { |
+ RTC_DCHECK(vpio_unit_) << "Init() not called."; |
+ RTCLog(@"Stopping audio unit."); |
+ |
+ OSStatus result = AudioOutputUnitStop(vpio_unit_); |
+ if (result != noErr) { |
+ RTCLogError(@"Failed to stop audio unit. Error=%ld", (long)result); |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+bool VoiceProcessingAudioUnit::Uninitialize() { |
+ RTC_DCHECK(vpio_unit_) << "Init() not called."; |
+ RTCLog(@"Unintializing audio unit."); |
+ |
+ OSStatus result = AudioUnitUninitialize(vpio_unit_); |
+ if (result != noErr) { |
+ RTCLogError(@"Failed to uninitialize audio unit. Error=%ld", (long)result); |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+OSStatus VoiceProcessingAudioUnit::Render(AudioUnitRenderActionFlags* flags, |
+ const AudioTimeStamp* time_stamp, |
+ UInt32 output_bus_number, |
+ UInt32 num_frames, |
+ AudioBufferList* io_data) { |
+ RTC_DCHECK(vpio_unit_) << "Init() not called."; |
+ |
+ OSStatus result = AudioUnitRender(vpio_unit_, flags, time_stamp, |
+ output_bus_number, num_frames, io_data); |
+ if (result != noErr) { |
+ RTCLogError(@"Failed to render audio unit. Error=%ld", (long)result); |
+ } |
+ return result; |
+} |
+ |
+OSStatus VoiceProcessingAudioUnit::OnGetPlayoutData( |
+ void* in_ref_con, |
+ AudioUnitRenderActionFlags* flags, |
+ const AudioTimeStamp* time_stamp, |
+ UInt32 bus_number, |
+ UInt32 num_frames, |
+ AudioBufferList* io_data) { |
+ VoiceProcessingAudioUnit* audio_unit = |
+ static_cast<VoiceProcessingAudioUnit*>(in_ref_con); |
+ return audio_unit->NotifyGetPlayoutData(flags, time_stamp, bus_number, |
+ num_frames, io_data); |
+} |
+ |
+OSStatus VoiceProcessingAudioUnit::OnDeliverRecordedData( |
+ void* in_ref_con, |
+ AudioUnitRenderActionFlags* flags, |
+ const AudioTimeStamp* time_stamp, |
+ UInt32 bus_number, |
+ UInt32 num_frames, |
+ AudioBufferList* io_data) { |
+ VoiceProcessingAudioUnit* audio_unit = |
+ static_cast<VoiceProcessingAudioUnit*>(in_ref_con); |
+ return audio_unit->NotifyDeliverRecordedData(flags, time_stamp, bus_number, |
+ num_frames, io_data); |
+} |
+ |
+OSStatus VoiceProcessingAudioUnit::NotifyGetPlayoutData( |
+ AudioUnitRenderActionFlags* flags, |
+ const AudioTimeStamp* time_stamp, |
+ UInt32 bus_number, |
+ UInt32 num_frames, |
+ AudioBufferList* io_data) { |
+ return observer_->OnGetPlayoutData(flags, time_stamp, bus_number, num_frames, |
+ io_data); |
+} |
+ |
+OSStatus VoiceProcessingAudioUnit::NotifyDeliverRecordedData( |
+ AudioUnitRenderActionFlags* flags, |
+ const AudioTimeStamp* time_stamp, |
+ UInt32 bus_number, |
+ UInt32 num_frames, |
+ AudioBufferList* io_data) { |
+ return observer_->OnDeliverRecordedData(flags, time_stamp, bus_number, |
+ num_frames, io_data); |
+} |
+ |
+AudioStreamBasicDescription VoiceProcessingAudioUnit::GetFormat( |
+ Float64 sample_rate) const { |
+ // Set the application formats for input and output: |
+ // - use same format in both directions |
+ // - avoid resampling in the I/O unit by using the hardware sample rate |
+ // - linear PCM => noncompressed audio data format with one frame per packet |
+ // - no need to specify interleaving since only mono is supported |
+ AudioStreamBasicDescription format = {0}; |
+ RTC_DCHECK_EQ(1, kRTCAudioSessionPreferredNumberOfChannels); |
+ format.mSampleRate = sample_rate; |
+ format.mFormatID = kAudioFormatLinearPCM; |
+ format.mFormatFlags = |
+ kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; |
+ format.mBytesPerPacket = kBytesPerSample; |
+ format.mFramesPerPacket = 1; // uncompressed. |
+ format.mBytesPerFrame = kBytesPerSample; |
+ format.mChannelsPerFrame = kRTCAudioSessionPreferredNumberOfChannels; |
+ format.mBitsPerChannel = 8 * kBytesPerSample; |
+ return format; |
+} |
+ |
+void VoiceProcessingAudioUnit::DisposeAudioUnit() { |
+ if (vpio_unit_) { |
+ OSStatus result = AudioComponentInstanceDispose(vpio_unit_); |
+ if (result != noErr) { |
+ RTCLogError(@"AudioComponentInstanceDispose failed. Error=%ld.", |
+ (long)result); |
+ } |
+ vpio_unit_ = nullptr; |
+ } |
+} |
+ |
+} // namespace webrtc |