mirror of https://github.com/procxx/kepka.git
				
				
				
			
		
			
				
	
	
		
			369 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			369 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
| //
 | |
| // 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 <stdlib.h>
 | |
| #include <stdio.h>
 | |
| #include <sys/sysctl.h>
 | |
| #include "AudioOutputAudioUnitOSX.h"
 | |
| #include "../../logging.h"
 | |
| #include "../../VoIPController.h"
 | |
| 
 | |
| #define BUFFER_SIZE 960
 | |
| #define CHECK_AU_ERROR(res, msg) if(res!=noErr){ LOGE("output: " msg": OSStatus=%d", (int)res); return; }
 | |
| 
 | |
| #define kOutputBus 0
 | |
| #define kInputBus 1
 | |
| 
 | |
| using namespace tgvoip;
 | |
| using namespace tgvoip::audio;
 | |
| 
 | |
| AudioOutputAudioUnitLegacy::AudioOutputAudioUnitLegacy(std::string deviceID){
 | |
| 	remainingDataSize=0;
 | |
| 	isPlaying=false;
 | |
| 	sysDevID=NULL;
 | |
| 	
 | |
| 	OSStatus status;
 | |
| 	AudioComponentDescription inputDesc={
 | |
| 		.componentType = kAudioUnitType_Output, .componentSubType = kAudioUnitSubType_HALOutput, .componentFlags = 0, .componentFlagsMask = 0,
 | |
| 		.componentManufacturer = kAudioUnitManufacturer_Apple
 | |
| 	};
 | |
| 	AudioComponent component=AudioComponentFindNext(NULL, &inputDesc);
 | |
| 	status=AudioComponentInstanceNew(component, &unit);
 | |
| 	CHECK_AU_ERROR(status, "Error creating AudioUnit");
 | |
| 	
 | |
| 	UInt32 flag=1;
 | |
| 	status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
 | |
| 	CHECK_AU_ERROR(status, "Error enabling AudioUnit output");
 | |
| 	flag=0;
 | |
| 	status = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
 | |
| 	CHECK_AU_ERROR(status, "Error enabling AudioUnit input");
 | |
| 	
 | |
| 	char model[128];
 | |
| 	memset(model, 0, sizeof(model));
 | |
| 	size_t msize=sizeof(model);
 | |
| 	int mres=sysctlbyname("hw.model", model, &msize, NULL, 0);
 | |
| 	if(mres==0){
 | |
| 		LOGV("Mac model: %s", model);
 | |
| 		isMacBookPro=(strncmp("MacBookPro", model, 10)==0);
 | |
| 	}
 | |
| 	
 | |
| 	SetCurrentDevice(deviceID);
 | |
| 	
 | |
| 	CFRunLoopRef theRunLoop = NULL;
 | |
| 	AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop,
 | |
| 		kAudioObjectPropertyScopeGlobal,
 | |
| 		kAudioObjectPropertyElementMaster };
 | |
| 	status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
 | |
| 	
 | |
| 	propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
 | |
| 	propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
 | |
| 	propertyAddress.mElement = kAudioObjectPropertyElementMaster;
 | |
| 	AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
 | |
| 	
 | |
| 	AudioStreamBasicDescription desiredFormat={
 | |
| 		.mSampleRate=/*hardwareFormat.mSampleRate*/48000, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
 | |
| 		.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
 | |
| 	};
 | |
| 	
 | |
| 	status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &desiredFormat, sizeof(desiredFormat));
 | |
| 	CHECK_AU_ERROR(status, "Error setting format");
 | |
| 	
 | |
| 	AURenderCallbackStruct callbackStruct;
 | |
| 	callbackStruct.inputProc = AudioOutputAudioUnitLegacy::BufferCallback;
 | |
| 	callbackStruct.inputProcRefCon=this;
 | |
| 	status = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct));
 | |
| 	CHECK_AU_ERROR(status, "Error setting input buffer callback");
 | |
| 	status=AudioUnitInitialize(unit);
 | |
| 	CHECK_AU_ERROR(status, "Error initializing unit");
 | |
| }
 | |
| 
 | |
| AudioOutputAudioUnitLegacy::~AudioOutputAudioUnitLegacy(){
 | |
| 	AudioObjectPropertyAddress propertyAddress;
 | |
| 	propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
 | |
| 	propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
 | |
| 	propertyAddress.mElement = kAudioObjectPropertyElementMaster;
 | |
| 	AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propertyAddress, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
 | |
| 	
 | |
| 	AudioObjectPropertyAddress dataSourceProp={
 | |
| 		kAudioDevicePropertyDataSource,
 | |
| 		kAudioDevicePropertyScopeOutput,
 | |
| 		kAudioObjectPropertyElementMaster
 | |
| 	};
 | |
| 	if(isMacBookPro && sysDevID && AudioObjectHasProperty(sysDevID, &dataSourceProp)){
 | |
| 		AudioObjectRemovePropertyListener(sysDevID, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
 | |
| 	}
 | |
| 	
 | |
| 	AudioUnitUninitialize(unit);
 | |
| 	AudioComponentInstanceDispose(unit);
 | |
| }
 | |
| 
 | |
| void AudioOutputAudioUnitLegacy::Configure(uint32_t sampleRate, uint32_t bitsPerSample, uint32_t channels){
 | |
| }
 | |
| 
 | |
| void AudioOutputAudioUnitLegacy::Start(){
 | |
| 	isPlaying=true;
 | |
| 	OSStatus status=AudioOutputUnitStart(unit);
 | |
| 	CHECK_AU_ERROR(status, "Error starting AudioUnit");
 | |
| }
 | |
| 
 | |
| void AudioOutputAudioUnitLegacy::Stop(){
 | |
| 	isPlaying=false;
 | |
| 	OSStatus status=AudioOutputUnitStart(unit);
 | |
| 	CHECK_AU_ERROR(status, "Error stopping AudioUnit");
 | |
| }
 | |
| 
 | |
| OSStatus AudioOutputAudioUnitLegacy::BufferCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
 | |
| 	AudioOutputAudioUnitLegacy* input=(AudioOutputAudioUnitLegacy*) inRefCon;
 | |
| 	input->HandleBufferCallback(ioData);
 | |
| 	return noErr;
 | |
| }
 | |
| 
 | |
| bool AudioOutputAudioUnitLegacy::IsPlaying(){
 | |
| 	return isPlaying;
 | |
| }
 | |
| 
 | |
| void AudioOutputAudioUnitLegacy::HandleBufferCallback(AudioBufferList *ioData){
 | |
| 	int i;
 | |
| 	unsigned int k;
 | |
| 	int16_t absVal=0;
 | |
| 	for(i=0;i<ioData->mNumberBuffers;i++){
 | |
| 		AudioBuffer buf=ioData->mBuffers[i];
 | |
| 		if(!isPlaying){
 | |
| 			memset(buf.mData, 0, buf.mDataByteSize);
 | |
| 			return;
 | |
| 		}
 | |
| 		while(remainingDataSize<buf.mDataByteSize){
 | |
| 			assert(remainingDataSize+BUFFER_SIZE*2<10240);
 | |
| 			InvokeCallback(remainingData+remainingDataSize, BUFFER_SIZE*2);
 | |
| 			remainingDataSize+=BUFFER_SIZE*2;
 | |
| 		}
 | |
| 		memcpy(buf.mData, remainingData, buf.mDataByteSize);
 | |
| 		remainingDataSize-=buf.mDataByteSize;
 | |
| 		memmove(remainingData, remainingData+buf.mDataByteSize, remainingDataSize);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| void AudioOutputAudioUnitLegacy::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
 | |
| 	AudioObjectPropertyAddress propertyAddress = {
 | |
| 		kAudioHardwarePropertyDevices,
 | |
| 		kAudioObjectPropertyScopeGlobal,
 | |
| 		kAudioObjectPropertyElementMaster
 | |
| 	};
 | |
| 	
 | |
| 	UInt32 dataSize = 0;
 | |
| 	OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
 | |
| 	if(kAudioHardwareNoError != status) {
 | |
| 		LOGE("AudioObjectGetPropertyDataSize (kAudioHardwarePropertyDevices) failed: %i", status);
 | |
| 		return;
 | |
| 	}
 | |
| 	
 | |
| 	UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
 | |
| 	
 | |
| 	
 | |
| 	AudioDeviceID *audioDevices = (AudioDeviceID*)(malloc(dataSize));
 | |
| 	
 | |
| 	status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
 | |
| 	if(kAudioHardwareNoError != status) {
 | |
| 		LOGE("AudioObjectGetPropertyData (kAudioHardwarePropertyDevices) failed: %i", status);
 | |
| 		free(audioDevices);
 | |
| 		audioDevices = NULL;
 | |
| 		return;
 | |
| 	}
 | |
| 	
 | |
| 	
 | |
| 	// Iterate through all the devices and determine which are input-capable
 | |
| 	propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
 | |
| 	for(UInt32 i = 0; i < deviceCount; ++i) {
 | |
| 		// Query device UID
 | |
| 		CFStringRef deviceUID = NULL;
 | |
| 		dataSize = sizeof(deviceUID);
 | |
| 		propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
 | |
| 		status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceUID);
 | |
| 		if(kAudioHardwareNoError != status) {
 | |
| 			LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID) failed: %i", status);
 | |
| 			continue;
 | |
| 		}
 | |
| 		
 | |
| 		// Query device name
 | |
| 		CFStringRef deviceName = NULL;
 | |
| 		dataSize = sizeof(deviceName);
 | |
| 		propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
 | |
| 		status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, &deviceName);
 | |
| 		if(kAudioHardwareNoError != status) {
 | |
| 			LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceNameCFString) failed: %i", status);
 | |
| 			continue;
 | |
| 		}
 | |
| 		
 | |
| 		// Determine if the device is an input device (it is an input device if it has input channels)
 | |
| 		dataSize = 0;
 | |
| 		propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
 | |
| 		status = AudioObjectGetPropertyDataSize(audioDevices[i], &propertyAddress, 0, NULL, &dataSize);
 | |
| 		if(kAudioHardwareNoError != status) {
 | |
| 			LOGE("AudioObjectGetPropertyDataSize (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
 | |
| 			continue;
 | |
| 		}
 | |
| 		
 | |
| 		AudioBufferList *bufferList = (AudioBufferList*)(malloc(dataSize));
 | |
| 		
 | |
| 		status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &dataSize, bufferList);
 | |
| 		if(kAudioHardwareNoError != status || 0 == bufferList->mNumberBuffers) {
 | |
| 			if(kAudioHardwareNoError != status)
 | |
| 				LOGE("AudioObjectGetPropertyData (kAudioDevicePropertyStreamConfiguration) failed: %i", status);
 | |
| 			free(bufferList);
 | |
| 			bufferList = NULL;
 | |
| 			continue;
 | |
| 		}
 | |
| 		
 | |
| 		free(bufferList);
 | |
| 		bufferList = NULL;
 | |
| 		
 | |
| 		AudioOutputDevice dev;
 | |
| 		char buf[1024];
 | |
| 		CFStringGetCString(deviceName, buf, 1024, kCFStringEncodingUTF8);
 | |
| 		dev.displayName=std::string(buf);
 | |
| 		CFStringGetCString(deviceUID, buf, 1024, kCFStringEncodingUTF8);
 | |
| 		dev.id=std::string(buf);
 | |
| 		devs.push_back(dev);
 | |
| 	}
 | |
| 	
 | |
| 	free(audioDevices);
 | |
| 	audioDevices = NULL;
 | |
| }
 | |
| 
 | |
| void AudioOutputAudioUnitLegacy::SetCurrentDevice(std::string deviceID){
 | |
| 	UInt32 size=sizeof(AudioDeviceID);
 | |
| 	AudioDeviceID outputDevice=NULL;
 | |
| 	OSStatus status;
 | |
| 	AudioObjectPropertyAddress dataSourceProp={
 | |
| 		kAudioDevicePropertyDataSource,
 | |
| 		kAudioDevicePropertyScopeOutput,
 | |
| 		kAudioObjectPropertyElementMaster
 | |
| 	};
 | |
| 	
 | |
| 	if(isMacBookPro && sysDevID && AudioObjectHasProperty(sysDevID, &dataSourceProp)){
 | |
| 		AudioObjectRemovePropertyListener(sysDevID, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
 | |
| 	}
 | |
| 	
 | |
| 	if(deviceID=="default"){
 | |
| 		AudioObjectPropertyAddress propertyAddress;
 | |
| 		propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
 | |
| 		propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
 | |
| 		propertyAddress.mElement = kAudioObjectPropertyElementMaster;
 | |
| 		UInt32 propsize = sizeof(AudioDeviceID);
 | |
| 		status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &outputDevice);
 | |
| 		CHECK_AU_ERROR(status, "Error getting default input device");
 | |
| 	}else{
 | |
| 		AudioObjectPropertyAddress propertyAddress = {
 | |
| 			kAudioHardwarePropertyDevices,
 | |
| 			kAudioObjectPropertyScopeGlobal,
 | |
| 			kAudioObjectPropertyElementMaster
 | |
| 		};
 | |
| 		UInt32 dataSize = 0;
 | |
| 		status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
 | |
| 		CHECK_AU_ERROR(status, "Error getting devices size");
 | |
| 		UInt32 deviceCount = (UInt32)(dataSize / sizeof(AudioDeviceID));
 | |
| 		AudioDeviceID audioDevices[deviceCount];
 | |
| 		status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
 | |
| 		CHECK_AU_ERROR(status, "Error getting device list");
 | |
| 		for(UInt32 i = 0; i < deviceCount; ++i) {
 | |
| 			// Query device UID
 | |
| 			CFStringRef deviceUID = NULL;
 | |
| 			dataSize = sizeof(deviceUID);
 | |
| 			propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
 | |
| 			status = AudioObjectGetPropertyData(audioDevices[i], &propertyAddress, 0, NULL, &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);
 | |
| 				outputDevice=audioDevices[i];
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		if(!outputDevice){
 | |
| 			LOGW("Requested device not found, using default");
 | |
| 			SetCurrentDevice("default");
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
|  
 | |
| 	status =AudioUnitSetProperty(unit,
 | |
| 							  kAudioOutputUnitProperty_CurrentDevice,
 | |
| 							  kAudioUnitScope_Global,
 | |
| 							  kOutputBus,
 | |
| 							  &outputDevice,
 | |
| 							  size);
 | |
| 	CHECK_AU_ERROR(status, "Error setting output device");
 | |
| 	
 | |
| 	AudioStreamBasicDescription hardwareFormat;
 | |
| 	size=sizeof(hardwareFormat);
 | |
| 	status=AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kOutputBus, &hardwareFormat, &size);
 | |
| 	CHECK_AU_ERROR(status, "Error getting hardware format");
 | |
| 	hardwareSampleRate=hardwareFormat.mSampleRate;
 | |
| 	
 | |
| 	AudioStreamBasicDescription desiredFormat={
 | |
| 		.mSampleRate=48000, .mFormatID=kAudioFormatLinearPCM, .mFormatFlags=kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian,
 | |
| 		.mFramesPerPacket=1, .mChannelsPerFrame=1, .mBitsPerChannel=16, .mBytesPerPacket=2, .mBytesPerFrame=2
 | |
| 	};
 | |
| 	
 | |
| 	status=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &desiredFormat, sizeof(desiredFormat));
 | |
| 	CHECK_AU_ERROR(status, "Error setting format");
 | |
| 	
 | |
| 	LOGD("Switched playback device, new sample rate %d", hardwareSampleRate);
 | |
| 	
 | |
| 	this->currentDevice=deviceID;
 | |
| 	sysDevID=outputDevice;
 | |
| 	
 | |
| 	AudioObjectPropertyAddress propertyAddress = {
 | |
| 		kAudioDevicePropertyBufferFrameSize,
 | |
| 		kAudioObjectPropertyScopeGlobal,
 | |
| 		kAudioObjectPropertyElementMaster
 | |
| 	};
 | |
| 	size=4;
 | |
| 	UInt32 bufferFrameSize;
 | |
| 	status=AudioObjectGetPropertyData(outputDevice, &propertyAddress, 0, NULL, &size, &bufferFrameSize);
 | |
| 	if(status==noErr){
 | |
| 		estimatedDelay=bufferFrameSize/48;
 | |
| 		LOGD("CoreAudio buffer size for output device is %u frames (%u ms)", bufferFrameSize, estimatedDelay);
 | |
| 	}
 | |
| 	
 | |
| 	if(isMacBookPro){
 | |
| 		if(AudioObjectHasProperty(outputDevice, &dataSourceProp)){
 | |
| 			UInt32 dataSource;
 | |
| 			size=4;
 | |
| 			AudioObjectGetPropertyData(outputDevice, &dataSourceProp, 0, NULL, &size, &dataSource);
 | |
| 			SetPanRight(dataSource=='ispk');
 | |
| 			AudioObjectAddPropertyListener(outputDevice, &dataSourceProp, AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback, this);
 | |
| 		}else{
 | |
| 			SetPanRight(false);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void AudioOutputAudioUnitLegacy::SetPanRight(bool panRight){
 | |
| 	LOGI("%sabling pan right on macbook pro", panRight ? "En" : "Dis");
 | |
| 	int32_t channelMap[]={panRight ? -1 : 0, 0};
 | |
| 	OSStatus status=AudioUnitSetProperty(unit, kAudioOutputUnitProperty_ChannelMap, kAudioUnitScope_Global, kOutputBus, channelMap, sizeof(channelMap));
 | |
| 	CHECK_AU_ERROR(status, "Error setting channel map");
 | |
| }
 | |
| 
 | |
| OSStatus AudioOutputAudioUnitLegacy::DefaultDeviceChangedCallback(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData){
 | |
| 	AudioOutputAudioUnitLegacy* self=(AudioOutputAudioUnitLegacy*)inClientData;
 | |
| 	if(inAddresses[0].mSelector==kAudioHardwarePropertyDefaultOutputDevice){
 | |
| 		LOGV("System default input device changed");
 | |
| 		if(self->currentDevice=="default"){
 | |
| 			self->SetCurrentDevice(self->currentDevice);
 | |
| 		}
 | |
| 	}else if(inAddresses[0].mSelector==kAudioDevicePropertyDataSource){
 | |
| 		UInt32 dataSource;
 | |
| 		UInt32 size=4;
 | |
| 		AudioObjectGetPropertyData(inObjectID, inAddresses, 0, NULL, &size, &dataSource);
 | |
| 		self->SetPanRight(dataSource=='ispk');
 | |
| 	}
 | |
| 	return noErr;
 | |
| }
 |