1 //
2 // libtgvoip is free and unencumbered public domain software.
3 // For more information, see http://unlicense.org or the UNLICENSE file
4 // you should have received with this source code distribution.
5 //
6 #include <stdio.h>
7 #include "AudioUnitIO.h"
8 #include "AudioInputAudioUnit.h"
9 #include "AudioOutputAudioUnit.h"
10 #include "../../logging.h"
11 #include "../../VoIPController.h"
12 #include "../../VoIPServerConfig.h"
13 
14 #define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE(msg": OSStatus=%d", (int)res); failed=true; return; }
15 #define BUFFER_SIZE 960 // 20 ms
16 
17 #if TARGET_OS_OSX
18 #define INPUT_BUFFER_SIZE 20480
19 #else
20 #define INPUT_BUFFER_SIZE 10240
21 #endif
22 
23 #define kOutputBus 0
24 #define kInputBus 1
25 
26 #if TARGET_OS_OSX && !defined(TGVOIP_NO_OSX_PRIVATE_API)
27 extern "C" {
28 OSStatus AudioDeviceDuck(AudioDeviceID inDevice,
29                          Float32 inDuckedLevel,
30                          const AudioTimeStamp* __nullable inStartTime,
31                          Float32 inRampDuration) __attribute__((weak_import));
32 }
33 #endif
34 
35 using namespace tgvoip;
36 using namespace tgvoip::audio;
37 
AudioUnitIO(std::string inputDeviceID,std::string outputDeviceID)38 AudioUnitIO::AudioUnitIO(std::string inputDeviceID, std::string outputDeviceID){
39 	input=NULL;
40 	output=NULL;
41 	inputEnabled=false;
42 	outputEnabled=false;
43 	failed=false;
44 	started=false;
45 	inBufferList.mBuffers[0].mData=malloc(INPUT_BUFFER_SIZE);
46 	inBufferList.mBuffers[0].mDataByteSize=INPUT_BUFFER_SIZE;
47 	inBufferList.mNumberBuffers=1;
48 
49 #if TARGET_OS_IPHONE
50 	DarwinSpecific::ConfigureAudioSession();
51 #endif
52 
53 	OSStatus status;
54 	AudioComponentDescription desc;
55 	AudioComponent inputComponent;
56 	desc.componentType = kAudioUnitType_Output;
57 	desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
58 	desc.componentFlags = 0;
59 	desc.componentFlagsMask = 0;
60 	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
61 	inputComponent = AudioComponentFindNext(NULL, &desc);
62 	status = AudioComponentInstanceNew(inputComponent, &unit);
63 
64 	UInt32 flag=1;
65 #if TARGET_OS_IPHONE
66 	status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
67 	CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
68 	status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
69 	CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
70 #endif
71 
72 #if TARGET_OS_IPHONE
73 	flag=ServerConfig::GetSharedInstance()->GetBoolean("use_ios_vpio_agc", true) ? 1 : 0;
74 #else
75 	flag=ServerConfig::GetSharedInstance()->GetBoolean("use_osx_vpio_agc", true) ? 1 : 0;
76 #endif
77 	status=AudioUnitSetProperty(unit, kAUVoiceIOProperty_VoiceProcessingEnableAGC, kAudioUnitScope_Global, kInputBus, &flag, sizeof(flag));
78 	CHECK_AU_ERROR(status, "Error disabling AGC");
79 
80 	AudioStreamBasicDescription audioFormat;
81 	audioFormat.mSampleRate			= 48000;
82 	audioFormat.mFormatID			= kAudioFormatLinearPCM;
83 #if TARGET_OS_IPHONE
84 	audioFormat.mFormatFlags		= kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
85 	audioFormat.mBitsPerChannel		= 16;
86 	audioFormat.mBytesPerPacket		= 2;
87 	audioFormat.mBytesPerFrame		= 2;
88 #else // OS X
89 	audioFormat.mFormatFlags		= kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
90 	audioFormat.mBitsPerChannel		= 32;
91 	audioFormat.mBytesPerPacket		= 4;
92 	audioFormat.mBytesPerFrame		= 4;
93 #endif
94 	audioFormat.mFramesPerPacket	= 1;
95 	audioFormat.mChannelsPerFrame	= 1;
96 
97 	status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, sizeof(audioFormat));
98 	CHECK_AU_ERROR(status, "Error setting output format");
99 	status = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat, sizeof(audioFormat));
100 	CHECK_AU_ERROR(status, "Error setting input format");
101 
102 	AURenderCallbackStruct callbackStruct;
103 
104 	callbackStruct.inputProc = AudioUnitIO::BufferCallback;
105 	callbackStruct.inputProcRefCon = this;
106 	status = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct));
107 	CHECK_AU_ERROR(status, "Error setting output buffer callback");
108 	status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct, sizeof(callbackStruct));
109 	CHECK_AU_ERROR(status, "Error setting input buffer callback");
110 
111 #if TARGET_OS_OSX
112 	CFRunLoopRef theRunLoop = NULL;
113 	AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
114 		kAudioObjectPropertyScopeGlobal,
115 		kAudioObjectPropertyElementMaster };
116 	status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
117 
118 	propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
119 	propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
120 	propertyAddress.mElement = kAudioObjectPropertyElementMaster;
121 	AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
122 	propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
123 	AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
124 
125 
126 #endif
127 
128 
129 	input=new AudioInputAudioUnit(inputDeviceID, this);
130 	output=new AudioOutputAudioUnit(outputDeviceID, this);
131 }
132 
~AudioUnitIO()133 AudioUnitIO::~AudioUnitIO(){
134 #if TARGET_OS_OSX
135 	AudioObjectPropertyAddress propertyAddress;
136 	propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
137 	propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
138 	propertyAddress.mElement = kAudioObjectPropertyElementMaster;
139 	AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
140 	propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
141 	AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioUnitIO::DefaultDeviceChangedCallback, this);
142 #endif
143 	AudioOutputUnitStop(unit);
144 	AudioUnitUninitialize(unit);
145 	AudioComponentInstanceDispose(unit);
146 	free(inBufferList.mBuffers[0].mData);
147 	delete input;
148 	delete output;
149 }
150 
BufferCallback(void * inRefCon,AudioUnitRenderActionFlags * ioActionFlags,const AudioTimeStamp * inTimeStamp,UInt32 inBusNumber,UInt32 inNumberFrames,AudioBufferList * ioData)151 OSStatus AudioUnitIO::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
152 	((AudioUnitIO*)inRefCon)->BufferCallback(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
153 	return noErr;
154 }
155 
BufferCallback(AudioUnitRenderActionFlags * ioActionFlags,const AudioTimeStamp * inTimeStamp,UInt32 bus,UInt32 numFrames,AudioBufferList * ioData)156 void AudioUnitIO::BufferCallback(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 bus, UInt32 numFrames, AudioBufferList *ioData){
157 	if(bus==kOutputBus){
158 		if(output && outputEnabled){
159 			output->HandleBufferCallback(ioData);
160 		}else{
161 			memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize);
162 		}
163 	}else if(bus==kInputBus){
164 		inBufferList.mBuffers[0].mDataByteSize=INPUT_BUFFER_SIZE;
165 		AudioUnitRender(unit, ioActionFlags, inTimeStamp, bus, numFrames, &inBufferList);
166 		if(input && inputEnabled){
167 			input->HandleBufferCallback(&inBufferList);
168 		}
169 	}
170 }
171 
EnableInput(bool enabled)172 void AudioUnitIO::EnableInput(bool enabled){
173 	inputEnabled=enabled;
174 	StartIfNeeded();
175 }
176 
EnableOutput(bool enabled)177 void AudioUnitIO::EnableOutput(bool enabled){
178 	outputEnabled=enabled;
179 	StartIfNeeded();
180 #if TARGET_OS_OSX && !defined(TGVOIP_NO_OSX_PRIVATE_API)
181 	if(actualDuckingEnabled!=duckingEnabled){
182 		actualDuckingEnabled=duckingEnabled;
183     	AudioDeviceDuck(currentOutputDeviceID, duckingEnabled ? 0.177828f : 1.0f, NULL, 0.1f);
184 	}
185 #endif
186 }
187 
StartIfNeeded()188 void AudioUnitIO::StartIfNeeded(){
189 	if(started)
190 		return;
191 	started=true;
192 	OSStatus status = AudioUnitInitialize(unit);
193 	CHECK_AU_ERROR(status, "Error initializing AudioUnit");
194 	status=AudioOutputUnitStart(unit);
195 	CHECK_AU_ERROR(status, "Error starting AudioUnit");
196 }
197 
GetInput()198 AudioInput* AudioUnitIO::GetInput(){
199 	return input;
200 }
201 
GetOutput()202 AudioOutput* AudioUnitIO::GetOutput(){
203 	return output;
204 }
205 
206 #if TARGET_OS_OSX
DefaultDeviceChangedCallback(AudioObjectID inObjectID,UInt32 inNumberAddresses,const AudioObjectPropertyAddress * inAddresses,void * inClientData)207 OSStatus AudioUnitIO::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
208 	AudioUnitIO* self=(AudioUnitIO*)inClientData;
209 	if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultOutputDevice){
210 		LOGV("System default output device changed");
211 		if(self->currentOutputDevice=="default"){
212 			self->SetCurrentDevice(false, self->currentOutputDevice);
213 		}
214 	}else if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultInputDevice){
215 		LOGV("System default input device changed");
216 		if(self->currentInputDevice=="default"){
217 			self->SetCurrentDevice(true, self->currentInputDevice);
218 		}
219 	}
220 	return noErr;
221 }
222 
SetCurrentDevice(bool input,std::string deviceID)223 void AudioUnitIO::SetCurrentDevice(bool input, std::string deviceID){
224 	LOGV("Setting current %sput device: %s", input ? "in" : "out", deviceID.c_str());
225 	if(started){
226 		AudioOutputUnitStop(unit);
227 		AudioUnitUninitialize(unit);
228 	}
229 	UInt32 size=sizeof(AudioDeviceID);
230 	AudioDeviceID device=0;
231 	OSStatus status;
232 
233 	if(deviceID=="default"){
234 		AudioObjectPropertyAddress propertyAddress;
235 		propertyAddress.mSelector = input ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice;
236 		propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
237 		propertyAddress.mElement = kAudioObjectPropertyElementMaster;
238 		UInt32 propsize = sizeof(AudioDeviceID);
239 		status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &device);
240 		CHECK_AU_ERROR(status, "Error getting default device");
241 	}else{
242 		AudioObjectPropertyAddress propertyAddress = {
243 			kAudioHardwarePropertyDevices,
244 			kAudioObjectPropertyScopeGlobal,
245 			kAudioObjectPropertyElementMaster
246 		};
247 		UInt32 dataSize = 0;
248 		status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
249 		CHECK_AU_ERROR(status, "Error getting devices size");
250 		UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
251 		AudioDeviceID audioDevices[deviceCount];
252 		status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
253 		CHECK_AU_ERROR(status, "Error getting device list");
254 		for(UInt32 i = 0; i < deviceCount; ++i) {
255 			// Query device UID
256 			CFStringRef deviceUID = NULL;
257 			dataSize = sizeof(deviceUID);
258 			propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
259 			status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
260 			CHECK_AU_ERROR(status, "Error getting device uid");
261 			char buf[1024];
262 			CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
263 			if(deviceID==buf){
264 				LOGV("Found device for id %s", buf);
265 				device=audioDevices[i];
266 				break;
267 			}
268 		}
269 		if(!device){
270 			LOGW("Requested device not found, using default");
271 			SetCurrentDevice(input, "default");
272 			return;
273 		}
274 	}
275 
276 	status=AudioUnitSetProperty(unit,
277 							  kAudioOutputUnitProperty_CurrentDevice,
278 							  kAudioUnitScope_Global,
279 								input ? kInputBus : kOutputBus,
280 							  &device,
281 							  size);
282 	CHECK_AU_ERROR(status, "Error setting input device");
283 
284 	if(input)
285 		currentInputDevice=deviceID;
286 	else
287 		currentOutputDevice=deviceID;
288 
289 	/*AudioObjectPropertyAddress propertyAddress = {
290 		kAudioDevicePropertyBufferFrameSize,
291 		kAudioObjectPropertyScopeGlobal,
292 		kAudioObjectPropertyElementMaster
293 	};
294 	size=4;
295 	UInt32 bufferFrameSize;
296 	status=AudioObjectGetPropertyData(device, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
297 	if(status==noErr){
298 		estimatedDelay=bufferFrameSize/48;
299 		LOGD("CoreAudio buffer size for device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
300 	}*/
301 	if(started){
302 		started=false;
303 		StartIfNeeded();
304 	}
305 	if(!input){
306 		currentOutputDeviceID=device;
307 	}
308 	LOGV("Set current %sput device done", input ? "in" : "out");
309 }
310 
SetDuckingEnabled(bool enabled)311 void AudioUnitIO::SetDuckingEnabled(bool enabled){
312 	duckingEnabled=enabled;
313 #ifndef TGVOIP_NO_OSX_PRIVATE_API
314 	if(outputEnabled && duckingEnabled!=actualDuckingEnabled){
315 		actualDuckingEnabled=enabled;
316     	AudioDeviceDuck(currentOutputDeviceID, enabled ? 0.177828f : 1.0f, NULL, 0.1f);
317 	}
318 #endif
319 }
320 
321 #endif
322