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