// // 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 #include #include "AudioUnitIO.h" #include "AudioInputAudioUnit.h" #include "AudioOutputAudioUnit.h" #include "../../logging.h" #include "../../VoIPController.h" #define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE(msg": OSStatus=%d", (int)res); return; } #define BUFFER_SIZE 960 // 20 ms #define kOutputBus 0 #define kInputBus 1 using namespace tgvoip; using namespace tgvoip::audio; int AudioUnitIO::refCount=0; AudioUnitIO* AudioUnitIO::sharedInstance=NULL; bool AudioUnitIO::haveAudioSession=false; AudioUnitIO::AudioUnitIO(){ input=NULL; output=NULL; configured=false; inputEnabled=false; outputEnabled=false; inBufferList.mBuffers[0].mData=malloc(10240); inBufferList.mBuffers[0].mDataByteSize=10240; inBufferList.mNumberBuffers=1; if(haveAudioSession) ProcessAudioSessionAcquired(); } AudioUnitIO::~AudioUnitIO(){ if(runFakeIO){ runFakeIO=false; join_thread(fakeIOThread); } AudioOutputUnitStop(unit); AudioUnitUninitialize(unit); AudioComponentInstanceDispose(unit); free(inBufferList.mBuffers[0].mData); haveAudioSession=false; } void AudioUnitIO::ProcessAudioSessionAcquired(){ OSStatus status; AudioComponentDescription desc; AudioComponent inputComponent; desc.componentType = kAudioUnitType_Output; #if TARGET_OS_IPHONE //desc.componentSubType = kAudioUnitSubType_RemoteIO; desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO; #else desc.componentSubType = kAudioUnitSubType_HALOutput; #endif desc.componentFlags = 0; desc.componentFlagsMask = 0; desc.componentManufacturer = kAudioUnitManufacturer_Apple; inputComponent = AudioComponentFindNext(NULL, &desc); status = AudioComponentInstanceNew(inputComponent, &unit); if(configured) ActuallyConfigure(cfgSampleRate, cfgBitsPerSample, cfgChannels); } AudioUnitIO* AudioUnitIO::Get(){ if(refCount==0){ sharedInstance=new AudioUnitIO(); } refCount++; assert(refCount>0); return sharedInstance; } void AudioUnitIO::Release(){ refCount--; assert(refCount>=0); if(refCount==0){ delete sharedInstance; sharedInstance=NULL; } } void AudioUnitIO::AudioSessionAcquired(){ haveAudioSession=true; if(sharedInstance) sharedInstance->ProcessAudioSessionAcquired(); } void AudioUnitIO::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){ if(configured) return; runFakeIO=true; start_thread(fakeIOThread, AudioUnitIO::StartFakeIOThread, this); set_thread_priority(fakeIOThread, get_thread_max_priority()); if(haveAudioSession){ ActuallyConfigure(sampleRate, bitsPerSample, channels); }else{ cfgSampleRate=sampleRate; cfgBitsPerSample=bitsPerSample; cfgChannels=channels; } configured=true; } void AudioUnitIO::ActuallyConfigure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){ UInt32 flag=1; OSStatus 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"); flag=0; status=AudioUnitSetProperty(unit, kAUVoiceIOProperty_VoiceProcessingEnableAGC, kAudioUnitScope_Global, kInputBus, &flag, sizeof(flag)); CHECK_AU_ERROR(status, "Error disabling AGC"); AudioStreamBasicDescription audioFormat; audioFormat.mSampleRate = sampleRate; audioFormat.mFormatID = kAudioFormatLinearPCM; audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; audioFormat.mFramesPerPacket = 1; audioFormat.mChannelsPerFrame = channels; audioFormat.mBitsPerChannel = bitsPerSample*channels; audioFormat.mBytesPerPacket = bitsPerSample/8*channels; audioFormat.mBytesPerFrame = bitsPerSample/8*channels; 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"); status = AudioUnitInitialize(unit); CHECK_AU_ERROR(status, "Error initializing AudioUnit"); status=AudioOutputUnitStart(unit); CHECK_AU_ERROR(status, "Error starting AudioUnit"); } OSStatus AudioUnitIO::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){ ((AudioUnitIO*)inRefCon)->BufferCallback(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); return noErr; } void AudioUnitIO::BufferCallback(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 bus, UInt32 numFrames, AudioBufferList *ioData){ runFakeIO=false; if(bus==kOutputBus){ if(output && outputEnabled){ output->HandleBufferCallback(ioData); }else{ memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize); } }else if(bus==kInputBus){ if(input && inputEnabled){ inBufferList.mBuffers[0].mDataByteSize=10240; AudioUnitRender(unit, ioActionFlags, inTimeStamp, bus, numFrames, &inBufferList); input->HandleBufferCallback(&inBufferList); } } } void AudioUnitIO::AttachInput(AudioInputAudioUnit *i){ assert(input==NULL); input=i; } void AudioUnitIO::AttachOutput(AudioOutputAudioUnit *o){ assert(output==NULL); output=o; } void AudioUnitIO::DetachInput(){ assert(input!=NULL); input=NULL; inputEnabled=false; } void AudioUnitIO::DetachOutput(){ assert(output!=NULL); output=NULL; outputEnabled=false; } void AudioUnitIO::EnableInput(bool enabled){ inputEnabled=enabled; } void AudioUnitIO::EnableOutput(bool enabled){ outputEnabled=enabled; } void* AudioUnitIO::StartFakeIOThread(void *arg){ ((AudioUnitIO*)arg)->RunFakeIOThread(); return NULL; } void AudioUnitIO::RunFakeIOThread(){ double neededDataDuration=0; double prevTime=VoIPController::GetCurrentTime(); while(runFakeIO){ double t=VoIPController::GetCurrentTime(); neededDataDuration+=t-prevTime; prevTime=t; while(neededDataDuration>=0.020){ if(output && outputEnabled){ inBufferList.mBuffers[0].mDataByteSize=960*2; output->HandleBufferCallback(&inBufferList); } if((output && outputEnabled) || (input && inputEnabled)){ memset(inBufferList.mBuffers[0].mData, 0, 960*2); } if(input && inputEnabled){ inBufferList.mBuffers[0].mDataByteSize=960*2; input->HandleBufferCallback(&inBufferList); } neededDataDuration-=0.020; } usleep(5000); } LOGD("FakeIO thread exiting"); }