// // libtgvoip is free and unencumbered public domain software. // For more information, see http://unlicense.org or the UNLICENSE file // you should have received with this source code distribution. // #include "AudioUnitIO.h" #include "../../VoIPController.h" #include "../../VoIPServerConfig.h" #include "../../logging.h" #include "AudioInputAudioUnit.h" #include "AudioOutputAudioUnit.h" #include #define CHECK_AU_ERROR(res, msg) \ if (res != noErr) \ { \ LOGE(msg ": OSStatus=%d", (int)res); \ failed = true; \ return; \ } #define BUFFER_SIZE 960 // 20 ms #if TARGET_OS_OSX #define INPUT_BUFFER_SIZE 20480 #else #define INPUT_BUFFER_SIZE 10240 #endif #define kOutputBus 0 #define kInputBus 1 #if TARGET_OS_OSX && !defined(TGVOIP_NO_OSX_PRIVATE_API) extern "C" { OSStatus AudioDeviceDuck(AudioDeviceID inDevice, Float32 inDuckedLevel, const AudioTimeStamp* __nullable inStartTime, Float32 inRampDuration) __attribute__((weak_import)); } #endif using namespace tgvoip; using namespace tgvoip::audio; AudioUnitIO::AudioUnitIO(std::string inputDeviceID, std::string outputDeviceID) { input = nullptr; output = nullptr; inputEnabled = false; outputEnabled = false; m_failed = false; started = false; inBufferList.mBuffers[0].mData = std::malloc(INPUT_BUFFER_SIZE); inBufferList.mBuffers[0].mDataByteSize = INPUT_BUFFER_SIZE; inBufferList.mNumberBuffers = 1; #if TARGET_OS_IPHONE DarwinSpecific::ConfigureAudioSession(); #endif OSStatus status; AudioComponentDescription desc; AudioComponent inputComponent; desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO; desc.componentFlags = 0; desc.componentFlagsMask = 0; desc.componentManufacturer = kAudioUnitManufacturer_Apple; inputComponent = AudioComponentFindNext(nullptr, &desc); status = AudioComponentInstanceNew(inputComponent, &unit); UInt32 flag = 1; #if TARGET_OS_IPHONE status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag)); CHECK_AU_ERROR(status, "Error enabling AudioUnit output"); status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag)); CHECK_AU_ERROR(status, "Error enabling AudioUnit input"); #endif #if TARGET_OS_IPHONE flag = ServerConfig::GetSharedInstance()->GetBoolean("use_ios_vpio_agc", true) ? 1 : 0; #else flag = ServerConfig::GetSharedInstance()->GetBoolean("use_osx_vpio_agc", true) ? 1 : 0; #endif status = AudioUnitSetProperty(unit, kAUVoiceIOProperty_VoiceProcessingEnableAGC, kAudioUnitScope_Global, kInputBus, &flag, sizeof(flag)); CHECK_AU_ERROR(status, "Error disabling AGC"); AudioStreamBasicDescription audioFormat; audioFormat.mSampleRate = 48000; audioFormat.mFormatID = kAudioFormatLinearPCM; #if TARGET_OS_IPHONE audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; audioFormat.mBitsPerChannel = 16; audioFormat.mBytesPerPacket = 2; audioFormat.mBytesPerFrame = 2; #else // OS X audioFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; audioFormat.mBitsPerChannel = 32; audioFormat.mBytesPerPacket = 4; audioFormat.mBytesPerFrame = 4; #endif audioFormat.mFramesPerPacket = 1; audioFormat.mChannelsPerFrame = 1; status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, sizeof(audioFormat)); CHECK_AU_ERROR(status, "Error setting output format"); status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat, sizeof(audioFormat)); CHECK_AU_ERROR(status, "Error setting input format"); AURenderCallbackStruct callbackStruct; callbackStruct.inputProc = AudioUnitIO::BufferCallback; callbackStruct.inputProcRefCon = this; status = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct)); CHECK_AU_ERROR(status, "Error setting output buffer callback"); status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct, sizeof(callbackStruct)); CHECK_AU_ERROR(status, "Error setting input buffer callback"); #if TARGET_OS_OSX CFRunLoopRef theRunLoop = nullptr; AudioObjectPropertyAddress propertyAddress = {kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr, sizeof(CFRunLoopRef), &theRunLoop); propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; propertyAddress.mElement = kAudioObjectPropertyElementMaster; AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this); propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this); #endif input = new AudioInputAudioUnit(inputDeviceID, this); output = new AudioOutputAudioUnit(outputDeviceID, this); } AudioUnitIO::~AudioUnitIO() { #if TARGET_OS_OSX AudioObjectPropertyAddress propertyAddress; propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; propertyAddress.mElement = kAudioObjectPropertyElementMaster; AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this); propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this); #endif AudioOutputUnitStop(unit); AudioUnitUninitialize(unit); AudioComponentInstanceDispose(unit); std::free(inBufferList.mBuffers[0].mData); delete input; delete output; } OSStatus AudioUnitIO::BufferCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) { (reinterpret_cast(inRefCon)->BufferCallback(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); return noErr; } void AudioUnitIO::BufferCallback(AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 bus, UInt32 numFrames, AudioBufferList* ioData) { if (bus == kOutputBus) { if (output && outputEnabled) { output->HandleBufferCallback(ioData); } else { std::memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize); } } else if (bus == kInputBus) { inBufferList.mBuffers[0].mDataByteSize = INPUT_BUFFER_SIZE; AudioUnitRender(unit, ioActionFlags, inTimeStamp, bus, numFrames, &inBufferList); if (input && inputEnabled) { input->HandleBufferCallback(&inBufferList); } } } void AudioUnitIO::EnableInput(bool enabled) { inputEnabled = enabled; StartIfNeeded(); } void AudioUnitIO::EnableOutput(bool enabled) { outputEnabled = enabled; StartIfNeeded(); #if TARGET_OS_OSX && !defined(TGVOIP_NO_OSX_PRIVATE_API) if (actualDuckingEnabled != duckingEnabled) { actualDuckingEnabled = duckingEnabled; AudioDeviceDuck(currentOutputDeviceID, duckingEnabled ? 0.177828f : 1.0f, nullptr, 0.1f); } #endif } void AudioUnitIO::StartIfNeeded() { if (started) return; started = true; OSStatus status = AudioUnitInitialize(unit); CHECK_AU_ERROR(status, "Error initializing AudioUnit"); status = AudioOutputUnitStart(unit); CHECK_AU_ERROR(status, "Error starting AudioUnit"); } AudioInput* AudioUnitIO::GetInput() { return input; } AudioOutput* AudioUnitIO::GetOutput() { return output; } #if TARGET_OS_OSX OSStatus AudioUnitIO::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress* inAddresses, void* inClientData) { AudioUnitIO* self = (AudioUnitIO*)inClientData; if (inAddresses[0].mSelector == kAudioHardwarePropertyDefaultOutputDevice) { LOGV("System default output device changed"); if (self->currentOutputDevice == "default") { self->SetCurrentDevice(false, self->currentOutputDevice); } } else if (inAddresses[0].mSelector == kAudioHardwarePropertyDefaultInputDevice) { LOGV("System default input device changed"); if (self->currentInputDevice == "default") { self->SetCurrentDevice(true, self->currentInputDevice); } } return noErr; } void AudioUnitIO::SetCurrentDevice(bool input, std::string deviceID) { LOGV("Setting current %sput device: %s", input ? "in" : "out", deviceID.c_str()); if (started) { AudioOutputUnitStop(unit); AudioUnitUninitialize(unit); } UInt32 size = sizeof(AudioDeviceID); AudioDeviceID device = 0; OSStatus status; if (deviceID == "default") { AudioObjectPropertyAddress propertyAddress; propertyAddress.mSelector = input ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice; propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; propertyAddress.mElement = kAudioObjectPropertyElementMaster; UInt32 propsize = sizeof(AudioDeviceID); status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr, &propsize, &device); CHECK_AU_ERROR(status, "Error getting default device"); } else { AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; UInt32 dataSize = 0; status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, nullptr, &dataSize); CHECK_AU_ERROR(status, "Error getting devices size"); UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID)); AudioDeviceID audioDevices[deviceCount]; status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr, &dataSize, audioDevices); CHECK_AU_ERROR(status, "Error getting device list"); for (UInt32 i = 0; i < deviceCount; ++i) { // Query device UID CFStringRef deviceUID = nullptr; dataSize = sizeof(deviceUID); propertyAddress.mSelector = kAudioDevicePropertyDeviceUID; status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, nullptr, &dataSize, &deviceUID); CHECK_AU_ERROR(status, "Error getting device uid"); char buf[1024]; CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8); if (deviceID == buf) { LOGV("Found device for id %s", buf); device = audioDevices[i]; break; } } if (!device) { LOGW("Requested device not found, using default"); SetCurrentDevice(input, "default"); return; } } status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, input ? kInputBus : kOutputBus, &device, size); CHECK_AU_ERROR(status, "Error setting input device"); if (input) currentInputDevice = deviceID; else currentOutputDevice = deviceID; /*AudioObjectPropertyAddress propertyAddress = { kAudioDevicePropertyBufferFrameSize, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; size=4; UInt32 bufferFrameSize; status=AudioObjectGetPropertyData(device, &propertyAddress, 0, nullptr, &size, &bufferFrameSize); if(status==noErr){ estimatedDelay=bufferFrameSize/48; LOGD("CoreAudio buffer size for device is %u frames (%u ms)", bufferFrameSize, estimatedDelay); }*/ if (started) { started = false; StartIfNeeded(); } if (!input) { currentOutputDeviceID = device; } LOGV("Set current %sput device done", input ? "in" : "out"); } void AudioUnitIO::SetDuckingEnabled(bool enabled) { duckingEnabled = enabled; #ifndef TGVOIP_NO_OSX_PRIVATE_API if (outputEnabled && duckingEnabled != actualDuckingEnabled) { actualDuckingEnabled = enabled; AudioDeviceDuck(currentOutputDeviceID, enabled ? 0.177828f : 1.0f, nullptr, 0.1f); } #endif } #endif