Browse Source

Merge pull request #17742 from marcelofg55/audio_device_list

Added new audio device functions to set/get the audio device
Juan Linietsky 7 years ago
parent
commit
1570a72eee

+ 118 - 25
drivers/alsa/audio_driver_alsa.cpp

@@ -37,19 +37,20 @@
 
 
 #include <errno.h>
 #include <errno.h>
 
 
-Error AudioDriverALSA::init() {
-
-	active = false;
-	thread_exited = false;
-	exit_thread = false;
-	pcm_open = false;
-	samples_in = NULL;
-	samples_out = NULL;
-
+Error AudioDriverALSA::init_device() {
 	mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE);
 	mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE);
 	speaker_mode = SPEAKER_MODE_STEREO;
 	speaker_mode = SPEAKER_MODE_STEREO;
 	channels = 2;
 	channels = 2;
 
 
+	// If there is a specified device check that it is really present
+	if (device_name != "Default") {
+		Array list = get_device_list();
+		if (list.find(device_name) == -1) {
+			device_name = "Default";
+			new_device = "Default";
+		}
+	}
+
 	int status;
 	int status;
 	snd_pcm_hw_params_t *hwparams;
 	snd_pcm_hw_params_t *hwparams;
 	snd_pcm_sw_params_t *swparams;
 	snd_pcm_sw_params_t *swparams;
@@ -65,7 +66,16 @@ Error AudioDriverALSA::init() {
 	//6 chans - "plug:surround51"
 	//6 chans - "plug:surround51"
 	//4 chans - "plug:surround40";
 	//4 chans - "plug:surround40";
 
 
-	status = snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+	if (device_name == "Default") {
+		status = snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+	} else {
+		String device = device_name;
+		int pos = device.find(";");
+		if (pos != -1) {
+			device = device.substr(0, pos);
+		}
+		status = snd_pcm_open(&pcm_handle, device.utf8().get_data(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+	}
 
 
 	ERR_FAIL_COND_V(status < 0, ERR_CANT_OPEN);
 	ERR_FAIL_COND_V(status < 0, ERR_CANT_OPEN);
 
 
@@ -129,15 +139,28 @@ Error AudioDriverALSA::init() {
 	status = snd_pcm_sw_params(pcm_handle, swparams);
 	status = snd_pcm_sw_params(pcm_handle, swparams);
 	CHECK_FAIL(status < 0);
 	CHECK_FAIL(status < 0);
 
 
-	samples_in = memnew_arr(int32_t, period_size * channels);
-	samples_out = memnew_arr(int16_t, period_size * channels);
+	samples_in.resize(period_size * channels);
+	samples_out.resize(period_size * channels);
 
 
 	snd_pcm_nonblock(pcm_handle, 0);
 	snd_pcm_nonblock(pcm_handle, 0);
 
 
-	mutex = Mutex::create();
-	thread = Thread::create(AudioDriverALSA::thread_func, this);
-
 	return OK;
 	return OK;
+}
+
+Error AudioDriverALSA::init() {
+
+	active = false;
+	thread_exited = false;
+	exit_thread = false;
+	pcm_open = false;
+
+	Error err = init_device();
+	if (err == OK) {
+		mutex = Mutex::create();
+		thread = Thread::create(AudioDriverALSA::thread_func, this);
+	}
+
+	return err;
 };
 };
 
 
 void AudioDriverALSA::thread_func(void *p_udata) {
 void AudioDriverALSA::thread_func(void *p_udata) {
@@ -152,7 +175,7 @@ void AudioDriverALSA::thread_func(void *p_udata) {
 		} else {
 		} else {
 			ad->lock();
 			ad->lock();
 
 
-			ad->audio_server_process(ad->period_size, ad->samples_in);
+			ad->audio_server_process(ad->period_size, ad->samples_in.ptrw());
 
 
 			ad->unlock();
 			ad->unlock();
 
 
@@ -167,7 +190,7 @@ void AudioDriverALSA::thread_func(void *p_udata) {
 		while (todo) {
 		while (todo) {
 			if (ad->exit_thread)
 			if (ad->exit_thread)
 				break;
 				break;
-			uint8_t *src = (uint8_t *)ad->samples_out;
+			uint8_t *src = (uint8_t *)ad->samples_out.ptr();
 			int wrote = snd_pcm_writei(ad->pcm_handle, (void *)(src + (total * ad->channels)), todo);
 			int wrote = snd_pcm_writei(ad->pcm_handle, (void *)(src + (total * ad->channels)), todo);
 
 
 			if (wrote < 0) {
 			if (wrote < 0) {
@@ -193,6 +216,26 @@ void AudioDriverALSA::thread_func(void *p_udata) {
 			total += wrote;
 			total += wrote;
 			todo -= wrote;
 			todo -= wrote;
 		};
 		};
+
+		// User selected a new device, finish the current one so we'll init the new device
+		if (ad->device_name != ad->new_device) {
+			ad->device_name = ad->new_device;
+			ad->finish_device();
+
+			Error err = ad->init_device();
+			if (err != OK) {
+				ERR_PRINT("ALSA: init_device error");
+				ad->device_name = "Default";
+				ad->new_device = "Default";
+
+				err = ad->init_device();
+				if (err != OK) {
+					ad->active = false;
+					ad->exit_thread = true;
+					break;
+				}
+			}
+		}
 	};
 	};
 
 
 	ad->thread_exited = true;
 	ad->thread_exited = true;
@@ -213,6 +256,49 @@ AudioDriver::SpeakerMode AudioDriverALSA::get_speaker_mode() const {
 	return speaker_mode;
 	return speaker_mode;
 };
 };
 
 
+Array AudioDriverALSA::get_device_list() {
+
+	Array list;
+
+	list.push_back("Default");
+
+	void **hints;
+
+	if (snd_device_name_hint(-1, "pcm", &hints) < 0)
+		return list;
+
+	for (void **n = hints; *n != NULL; n++) {
+		char *name = snd_device_name_get_hint(*n, "NAME");
+		char *desc = snd_device_name_get_hint(*n, "DESC");
+
+		if (name != NULL && !strncmp(name, "plughw", 6)) {
+			if (desc) {
+				list.push_back(String(name) + ";" + String(desc));
+			} else {
+				list.push_back(String(name));
+			}
+		}
+
+		if (desc != NULL)
+			free(desc);
+		if (name != NULL)
+			free(name);
+	}
+	snd_device_name_free_hint(hints);
+
+	return list;
+}
+
+String AudioDriverALSA::get_device() {
+
+	return device_name;
+}
+
+void AudioDriverALSA::set_device(String device) {
+
+	new_device = device;
+}
+
 void AudioDriverALSA::lock() {
 void AudioDriverALSA::lock() {
 
 
 	if (!thread || !mutex)
 	if (!thread || !mutex)
@@ -227,6 +313,14 @@ void AudioDriverALSA::unlock() {
 	mutex->unlock();
 	mutex->unlock();
 };
 };
 
 
+void AudioDriverALSA::finish_device() {
+
+	if (pcm_open) {
+		snd_pcm_close(pcm_handle);
+		pcm_open = NULL;
+	}
+}
+
 void AudioDriverALSA::finish() {
 void AudioDriverALSA::finish() {
 
 
 	if (!thread)
 	if (!thread)
@@ -235,17 +329,13 @@ void AudioDriverALSA::finish() {
 	exit_thread = true;
 	exit_thread = true;
 	Thread::wait_to_finish(thread);
 	Thread::wait_to_finish(thread);
 
 
-	if (pcm_open)
-		snd_pcm_close(pcm_handle);
-
-	if (samples_in) {
-		memdelete_arr(samples_in);
-		memdelete_arr(samples_out);
-	};
+	finish_device();
 
 
 	memdelete(thread);
 	memdelete(thread);
-	if (mutex)
+	if (mutex) {
 		memdelete(mutex);
 		memdelete(mutex);
+		mutex = NULL;
+	}
 	thread = NULL;
 	thread = NULL;
 };
 };
 
 
@@ -254,6 +344,9 @@ AudioDriverALSA::AudioDriverALSA() {
 	mutex = NULL;
 	mutex = NULL;
 	thread = NULL;
 	thread = NULL;
 	pcm_handle = NULL;
 	pcm_handle = NULL;
+
+	device_name = "Default";
+	new_device = "Default";
 };
 };
 
 
 AudioDriverALSA::~AudioDriverALSA(){
 AudioDriverALSA::~AudioDriverALSA(){

+ 11 - 2
drivers/alsa/audio_driver_alsa.h

@@ -44,8 +44,14 @@ class AudioDriverALSA : public AudioDriver {
 
 
 	snd_pcm_t *pcm_handle;
 	snd_pcm_t *pcm_handle;
 
 
-	int32_t *samples_in;
-	int16_t *samples_out;
+	String device_name;
+	String new_device;
+
+	Vector<int32_t> samples_in;
+	Vector<int16_t> samples_out;
+
+	Error init_device();
+	void finish_device();
 
 
 	static void thread_func(void *p_udata);
 	static void thread_func(void *p_udata);
 
 
@@ -71,6 +77,9 @@ public:
 	virtual void start();
 	virtual void start();
 	virtual int get_mix_rate() const;
 	virtual int get_mix_rate() const;
 	virtual SpeakerMode get_speaker_mode() const;
 	virtual SpeakerMode get_speaker_mode() const;
+	virtual Array get_device_list();
+	virtual String get_device();
+	virtual void set_device(String device);
 	virtual void lock();
 	virtual void lock();
 	virtual void unlock();
 	virtual void unlock();
 	virtual void finish();
 	virtual void finish();

+ 169 - 43
drivers/coreaudio/audio_driver_coreaudio.cpp

@@ -37,16 +37,22 @@
 #define kOutputBus 0
 #define kOutputBus 0
 
 
 #ifdef OSX_ENABLED
 #ifdef OSX_ENABLED
-static OSStatus outputDeviceAddressCB(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData) {
+OSStatus AudioDriverCoreAudio::output_device_address_cb(AudioObjectID inObjectID,
+		UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses,
+		void *inClientData) {
 	AudioDriverCoreAudio *driver = (AudioDriverCoreAudio *)inClientData;
 	AudioDriverCoreAudio *driver = (AudioDriverCoreAudio *)inClientData;
 
 
-	driver->reopen();
+	// If our selected device is the Default call set_device to update the
+	// kAudioOutputUnitProperty_CurrentDevice property
+	if (driver->device_name == "Default") {
+		driver->set_device("Default");
+	}
 
 
 	return noErr;
 	return noErr;
 }
 }
 #endif
 #endif
 
 
-Error AudioDriverCoreAudio::initDevice() {
+Error AudioDriverCoreAudio::init_device() {
 	AudioComponentDescription desc;
 	AudioComponentDescription desc;
 	zeromem(&desc, sizeof(desc));
 	zeromem(&desc, sizeof(desc));
 	desc.componentType = kAudioUnitType_Output;
 	desc.componentType = kAudioUnitType_Output;
@@ -129,7 +135,7 @@ Error AudioDriverCoreAudio::initDevice() {
 	return OK;
 	return OK;
 }
 }
 
 
-Error AudioDriverCoreAudio::finishDevice() {
+Error AudioDriverCoreAudio::finish_device() {
 	OSStatus result;
 	OSStatus result;
 
 
 	if (active) {
 	if (active) {
@@ -153,49 +159,18 @@ Error AudioDriverCoreAudio::init() {
 	channels = 2;
 	channels = 2;
 
 
 #ifdef OSX_ENABLED
 #ifdef OSX_ENABLED
-	outputDeviceAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
-	outputDeviceAddress.mScope = kAudioObjectPropertyScopeGlobal;
-	outputDeviceAddress.mElement = kAudioObjectPropertyElementMaster;
+	AudioObjectPropertyAddress prop;
+	prop.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+	prop.mScope = kAudioObjectPropertyScopeGlobal;
+	prop.mElement = kAudioObjectPropertyElementMaster;
 
 
-	result = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &outputDeviceAddress, &outputDeviceAddressCB, this);
+	result = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &prop, &output_device_address_cb, this);
 	ERR_FAIL_COND_V(result != noErr, FAILED);
 	ERR_FAIL_COND_V(result != noErr, FAILED);
 #endif
 #endif
 
 
-	return initDevice();
+	return init_device();
 };
 };
 
 
-Error AudioDriverCoreAudio::reopen() {
-	bool restart = false;
-
-	lock();
-
-	if (active) {
-		restart = true;
-	}
-
-	Error err = finishDevice();
-	if (err != OK) {
-		ERR_PRINT("finishDevice failed");
-		unlock();
-		return err;
-	}
-
-	err = initDevice();
-	if (err != OK) {
-		ERR_PRINT("initDevice failed");
-		unlock();
-		return err;
-	}
-
-	if (restart) {
-		start();
-	}
-
-	unlock();
-
-	return OK;
-}
-
 OSStatus AudioDriverCoreAudio::output_callback(void *inRefCon,
 OSStatus AudioDriverCoreAudio::output_callback(void *inRefCon,
 		AudioUnitRenderActionFlags *ioActionFlags,
 		AudioUnitRenderActionFlags *ioActionFlags,
 		const AudioTimeStamp *inTimeStamp,
 		const AudioTimeStamp *inTimeStamp,
@@ -257,6 +232,150 @@ AudioDriver::SpeakerMode AudioDriverCoreAudio::get_speaker_mode() const {
 	return get_speaker_mode_by_total_channels(channels);
 	return get_speaker_mode_by_total_channels(channels);
 };
 };
 
 
+#ifdef OSX_ENABLED
+
+Array AudioDriverCoreAudio::get_device_list() {
+
+	Array list;
+
+	list.push_back("Default");
+
+	AudioObjectPropertyAddress prop;
+
+	prop.mSelector = kAudioHardwarePropertyDevices;
+	prop.mScope = kAudioObjectPropertyScopeGlobal;
+	prop.mElement = kAudioObjectPropertyElementMaster;
+
+	UInt32 size = 0;
+	AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, NULL, &size);
+	AudioDeviceID *audioDevices = (AudioDeviceID *)malloc(size);
+	AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &size, audioDevices);
+
+	UInt32 deviceCount = size / sizeof(AudioDeviceID);
+	for (UInt32 i = 0; i < deviceCount; i++) {
+		prop.mScope = kAudioDevicePropertyScopeOutput;
+		prop.mSelector = kAudioDevicePropertyStreamConfiguration;
+
+		AudioObjectGetPropertyDataSize(audioDevices[i], &prop, 0, NULL, &size);
+		AudioBufferList *bufferList = (AudioBufferList *)malloc(size);
+		AudioObjectGetPropertyData(audioDevices[i], &prop, 0, NULL, &size, bufferList);
+
+		UInt32 outputChannelCount = 0;
+		for (UInt32 j = 0; j < bufferList->mNumberBuffers; j++)
+			outputChannelCount += bufferList->mBuffers[j].mNumberChannels;
+
+		free(bufferList);
+
+		if (outputChannelCount >= 1) {
+			CFStringRef cfname;
+
+			size = sizeof(CFStringRef);
+			prop.mSelector = kAudioObjectPropertyName;
+
+			AudioObjectGetPropertyData(audioDevices[i], &prop, 0, NULL, &size, &cfname);
+
+			CFIndex length = CFStringGetLength(cfname);
+			CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
+			char *buffer = (char *)malloc(maxSize);
+			if (CFStringGetCString(cfname, buffer, maxSize, kCFStringEncodingUTF8)) {
+				// Append the ID to the name in case we have devices with duplicate name
+				list.push_back(String(buffer) + " (" + itos(audioDevices[i]) + ")");
+			}
+
+			free(buffer);
+		}
+	}
+
+	free(audioDevices);
+
+	return list;
+}
+
+String AudioDriverCoreAudio::get_device() {
+
+	return device_name;
+}
+
+void AudioDriverCoreAudio::set_device(String device) {
+
+	device_name = device;
+	if (!active) {
+		return;
+	}
+
+	AudioDeviceID deviceId;
+	bool found = false;
+	if (device_name != "Default") {
+		AudioObjectPropertyAddress prop;
+
+		prop.mSelector = kAudioHardwarePropertyDevices;
+		prop.mScope = kAudioObjectPropertyScopeGlobal;
+		prop.mElement = kAudioObjectPropertyElementMaster;
+
+		UInt32 size = 0;
+		AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, NULL, &size);
+		AudioDeviceID *audioDevices = (AudioDeviceID *)malloc(size);
+		AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &size, audioDevices);
+
+		UInt32 deviceCount = size / sizeof(AudioDeviceID);
+		for (UInt32 i = 0; i < deviceCount && !found; i++) {
+			prop.mScope = kAudioDevicePropertyScopeOutput;
+			prop.mSelector = kAudioDevicePropertyStreamConfiguration;
+
+			AudioObjectGetPropertyDataSize(audioDevices[i], &prop, 0, NULL, &size);
+			AudioBufferList *bufferList = (AudioBufferList *)malloc(size);
+			AudioObjectGetPropertyData(audioDevices[i], &prop, 0, NULL, &size, bufferList);
+
+			UInt32 outputChannelCount = 0;
+			for (UInt32 j = 0; j < bufferList->mNumberBuffers; j++)
+				outputChannelCount += bufferList->mBuffers[j].mNumberChannels;
+
+			free(bufferList);
+
+			if (outputChannelCount >= 1) {
+				CFStringRef cfname;
+
+				size = sizeof(CFStringRef);
+				prop.mSelector = kAudioObjectPropertyName;
+
+				AudioObjectGetPropertyData(audioDevices[i], &prop, 0, NULL, &size, &cfname);
+
+				CFIndex length = CFStringGetLength(cfname);
+				CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
+				char *buffer = (char *)malloc(maxSize);
+				if (CFStringGetCString(cfname, buffer, maxSize, kCFStringEncodingUTF8)) {
+					String name = String(buffer) + " (" + itos(audioDevices[i]) + ")";
+					if (name == device_name) {
+						deviceId = audioDevices[i];
+						found = true;
+					}
+				}
+
+				free(buffer);
+			}
+		}
+
+		free(audioDevices);
+	}
+
+	if (!found) {
+		UInt32 size = sizeof(AudioDeviceID);
+		AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
+
+		OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property, 0, NULL, &size, &deviceId);
+		ERR_FAIL_COND(result != noErr);
+
+		found = true;
+	}
+
+	if (found) {
+		OSStatus result = AudioUnitSetProperty(audio_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &deviceId, sizeof(AudioDeviceID));
+		ERR_FAIL_COND(result != noErr);
+	}
+}
+
+#endif
+
 void AudioDriverCoreAudio::lock() {
 void AudioDriverCoreAudio::lock() {
 	if (mutex)
 	if (mutex)
 		mutex->lock();
 		mutex->lock();
@@ -276,10 +395,15 @@ bool AudioDriverCoreAudio::try_lock() {
 void AudioDriverCoreAudio::finish() {
 void AudioDriverCoreAudio::finish() {
 	OSStatus result;
 	OSStatus result;
 
 
-	finishDevice();
+	finish_device();
 
 
 #ifdef OSX_ENABLED
 #ifdef OSX_ENABLED
-	result = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &outputDeviceAddress, &outputDeviceAddressCB, this);
+	AudioObjectPropertyAddress prop;
+	prop.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+	prop.mScope = kAudioObjectPropertyScopeGlobal;
+	prop.mElement = kAudioObjectPropertyElementMaster;
+
+	result = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &prop, &output_device_address_cb, this);
 	if (result != noErr) {
 	if (result != noErr) {
 		ERR_PRINT("AudioObjectRemovePropertyListener failed");
 		ERR_PRINT("AudioObjectRemovePropertyListener failed");
 	}
 	}
@@ -309,6 +433,8 @@ AudioDriverCoreAudio::AudioDriverCoreAudio() {
 	buffer_frames = 0;
 	buffer_frames = 0;
 
 
 	samples_in.clear();
 	samples_in.clear();
+
+	device_name = "Default";
 };
 };
 
 
 AudioDriverCoreAudio::~AudioDriverCoreAudio(){};
 AudioDriverCoreAudio::~AudioDriverCoreAudio(){};

+ 14 - 6
drivers/coreaudio/audio_driver_coreaudio.h

@@ -43,12 +43,12 @@
 class AudioDriverCoreAudio : public AudioDriver {
 class AudioDriverCoreAudio : public AudioDriver {
 
 
 	AudioComponentInstance audio_unit;
 	AudioComponentInstance audio_unit;
-#ifdef OSX_ENABLED
-	AudioObjectPropertyAddress outputDeviceAddress;
-#endif
+
 	bool active;
 	bool active;
 	Mutex *mutex;
 	Mutex *mutex;
 
 
+	String device_name;
+
 	int mix_rate;
 	int mix_rate;
 	unsigned int channels;
 	unsigned int channels;
 	unsigned int buffer_frames;
 	unsigned int buffer_frames;
@@ -56,14 +56,18 @@ class AudioDriverCoreAudio : public AudioDriver {
 
 
 	Vector<int32_t> samples_in;
 	Vector<int32_t> samples_in;
 
 
+	static OSStatus output_device_address_cb(AudioObjectID inObjectID,
+			UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses,
+			void *inClientData);
+
 	static OSStatus output_callback(void *inRefCon,
 	static OSStatus output_callback(void *inRefCon,
 			AudioUnitRenderActionFlags *ioActionFlags,
 			AudioUnitRenderActionFlags *ioActionFlags,
 			const AudioTimeStamp *inTimeStamp,
 			const AudioTimeStamp *inTimeStamp,
 			UInt32 inBusNumber, UInt32 inNumberFrames,
 			UInt32 inBusNumber, UInt32 inNumberFrames,
 			AudioBufferList *ioData);
 			AudioBufferList *ioData);
 
 
-	Error initDevice();
-	Error finishDevice();
+	Error init_device();
+	Error finish_device();
 
 
 public:
 public:
 	const char *get_name() const {
 	const char *get_name() const {
@@ -74,12 +78,16 @@ public:
 	virtual void start();
 	virtual void start();
 	virtual int get_mix_rate() const;
 	virtual int get_mix_rate() const;
 	virtual SpeakerMode get_speaker_mode() const;
 	virtual SpeakerMode get_speaker_mode() const;
+#ifdef OSX_ENABLED
+	virtual Array get_device_list();
+	virtual String get_device();
+	virtual void set_device(String device);
+#endif
 	virtual void lock();
 	virtual void lock();
 	virtual void unlock();
 	virtual void unlock();
 	virtual void finish();
 	virtual void finish();
 
 
 	bool try_lock();
 	bool try_lock();
-	Error reopen();
 
 
 	AudioDriverCoreAudio();
 	AudioDriverCoreAudio();
 	~AudioDriverCoreAudio();
 	~AudioDriverCoreAudio();

+ 269 - 113
drivers/pulseaudio/audio_driver_pulseaudio.cpp

@@ -37,131 +37,102 @@
 #include "os/os.h"
 #include "os/os.h"
 #include "project_settings.h"
 #include "project_settings.h"
 
 
-void pa_state_cb(pa_context *c, void *userdata) {
-	pa_context_state_t state;
-	int *pa_ready = (int *)userdata;
+void AudioDriverPulseAudio::pa_state_cb(pa_context *c, void *userdata) {
+	AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata;
 
 
-	state = pa_context_get_state(c);
-	switch (state) {
-		case PA_CONTEXT_FAILED:
+	switch (pa_context_get_state(c)) {
 		case PA_CONTEXT_TERMINATED:
 		case PA_CONTEXT_TERMINATED:
-			*pa_ready = 2;
+		case PA_CONTEXT_FAILED:
+			ad->pa_ready = -1;
 			break;
 			break;
 
 
 		case PA_CONTEXT_READY:
 		case PA_CONTEXT_READY:
-			*pa_ready = 1;
+			ad->pa_ready = 1;
 			break;
 			break;
 	}
 	}
 }
 }
 
 
-void sink_info_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) {
-	unsigned int *channels = (unsigned int *)userdata;
+void AudioDriverPulseAudio::pa_sink_info_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) {
+	AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata;
 
 
 	// If eol is set to a positive number, you're at the end of the list
 	// If eol is set to a positive number, you're at the end of the list
 	if (eol > 0) {
 	if (eol > 0) {
 		return;
 		return;
 	}
 	}
 
 
-	*channels = l->channel_map.channels;
+	ad->pa_channels = l->channel_map.channels;
+	ad->pa_status++;
 }
 }
 
 
-void server_info_cb(pa_context *c, const pa_server_info *i, void *userdata) {
-	char *default_output = (char *)userdata;
+void AudioDriverPulseAudio::pa_server_info_cb(pa_context *c, const pa_server_info *i, void *userdata) {
+	AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata;
 
 
-	strncpy(default_output, i->default_sink_name, 1024);
+	ad->default_device = i->default_sink_name;
+	ad->pa_status++;
 }
 }
 
 
-static unsigned int detect_channels() {
-
-	pa_mainloop *pa_ml;
-	pa_mainloop_api *pa_mlapi;
-	pa_operation *pa_op;
-	pa_context *pa_ctx;
-
-	int state = 0;
-	int pa_ready = 0;
-
-	char default_output[1024];
-	unsigned int channels = 2;
-
-	pa_ml = pa_mainloop_new();
-	pa_mlapi = pa_mainloop_get_api(pa_ml);
-	pa_ctx = pa_context_new(pa_mlapi, "Godot");
-
-	int ret = pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
-	if (ret < 0) {
-		pa_context_unref(pa_ctx);
-		pa_mainloop_free(pa_ml);
+void AudioDriverPulseAudio::detect_channels() {
 
 
-		return 2;
-	}
+	pa_channels = 2;
 
 
-	pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready);
+	if (device_name == "Default") {
+		// Get the default output device name
+		pa_status = 0;
+		pa_operation *pa_op = pa_context_get_server_info(pa_ctx, &AudioDriverPulseAudio::pa_server_info_cb, (void *)this);
+		if (pa_op) {
+			while (pa_status == 0) {
+				int ret = pa_mainloop_iterate(pa_ml, 1, NULL);
+				if (ret < 0) {
+					ERR_PRINT("pa_mainloop_iterate error");
+				}
+			}
 
 
-	// Wait until the pa server is ready
-	while (pa_ready == 0) {
-		pa_mainloop_iterate(pa_ml, 1, NULL);
+			pa_operation_unref(pa_op);
+		} else {
+			ERR_PRINT("pa_context_get_server_info error");
+		}
 	}
 	}
 
 
-	// Check if there was an error connecting to the pa server
-	if (pa_ready == 2) {
-		pa_context_disconnect(pa_ctx);
-		pa_context_unref(pa_ctx);
-		pa_mainloop_free(pa_ml);
-
-		return 2;
+	char device[1024];
+	if (device_name == "Default") {
+		strcpy(device, default_device.utf8().get_data());
+	} else {
+		strcpy(device, device_name.utf8().get_data());
 	}
 	}
 
 
-	// Get the default output device name
-	pa_op = pa_context_get_server_info(pa_ctx, &server_info_cb, (void *)default_output);
+	// Now using the device name get the amount of channels
+	pa_status = 0;
+	pa_operation *pa_op = pa_context_get_sink_info_by_name(pa_ctx, device, &AudioDriverPulseAudio::pa_sink_info_cb, (void *)this);
 	if (pa_op) {
 	if (pa_op) {
-		while (pa_operation_get_state(pa_op) == PA_OPERATION_RUNNING) {
-			ret = pa_mainloop_iterate(pa_ml, 1, NULL);
+		while (pa_status == 0) {
+			int ret = pa_mainloop_iterate(pa_ml, 1, NULL);
 			if (ret < 0) {
 			if (ret < 0) {
 				ERR_PRINT("pa_mainloop_iterate error");
 				ERR_PRINT("pa_mainloop_iterate error");
 			}
 			}
 		}
 		}
 
 
 		pa_operation_unref(pa_op);
 		pa_operation_unref(pa_op);
-
-		// Now using the device name get the amount of channels
-		pa_op = pa_context_get_sink_info_by_name(pa_ctx, default_output, &sink_info_cb, (void *)&channels);
-		if (pa_op) {
-			while (pa_operation_get_state(pa_op) == PA_OPERATION_RUNNING) {
-				ret = pa_mainloop_iterate(pa_ml, 1, NULL);
-				if (ret < 0) {
-					ERR_PRINT("pa_mainloop_iterate error");
-				}
-			}
-
-			pa_operation_unref(pa_op);
-		} else {
-			ERR_PRINT("pa_context_get_sink_info_by_name error");
-		}
 	} else {
 	} else {
-		ERR_PRINT("pa_context_get_server_info error");
+		ERR_PRINT("pa_context_get_sink_info_by_name error");
 	}
 	}
-
-	pa_context_disconnect(pa_ctx);
-	pa_context_unref(pa_ctx);
-	pa_mainloop_free(pa_ml);
-
-	return channels;
 }
 }
 
 
-Error AudioDriverPulseAudio::init() {
-
-	active = false;
-	thread_exited = false;
-	exit_thread = false;
+Error AudioDriverPulseAudio::init_device() {
 
 
-	mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE);
+	// If there is a specified device check that it is really present
+	if (device_name != "Default") {
+		Array list = get_device_list();
+		if (list.find(device_name) == -1) {
+			device_name = "Default";
+			new_device = "Default";
+		}
+	}
 
 
 	// Detect the amount of channels PulseAudio is using
 	// Detect the amount of channels PulseAudio is using
 	// Note: If using an even amount of channels (2, 4, etc) channels and pa_channels will be equal,
 	// Note: If using an even amount of channels (2, 4, etc) channels and pa_channels will be equal,
 	// if not then pa_channels will have the real amount of channels PulseAudio is using and channels
 	// if not then pa_channels will have the real amount of channels PulseAudio is using and channels
 	// will have the amount of channels Godot is using (in this case it's pa_channels + 1)
 	// will have the amount of channels Godot is using (in this case it's pa_channels + 1)
-	pa_channels = detect_channels();
+	detect_channels();
 	switch (pa_channels) {
 	switch (pa_channels) {
 		case 1: // Mono
 		case 1: // Mono
 		case 3: // Surround 2.1
 		case 3: // Surround 2.1
@@ -205,27 +176,76 @@ Error AudioDriverPulseAudio::init() {
 	attr.maxlength = (uint32_t)-1;
 	attr.maxlength = (uint32_t)-1;
 	attr.minreq = (uint32_t)-1;
 	attr.minreq = (uint32_t)-1;
 
 
-	int error_code;
-	pulse = pa_simple_new(NULL, // default server
-			"Godot", // application name
-			PA_STREAM_PLAYBACK,
-			NULL, // default device
-			"Sound", // stream description
-			&spec,
-			NULL, // use default channel map
-			&attr, // use buffering attributes from above
-			&error_code);
-
-	if (pulse == NULL) {
-		fprintf(stderr, "PulseAudio ERR: %s\n", pa_strerror(error_code));
-		ERR_FAIL_COND_V(pulse == NULL, ERR_CANT_OPEN);
-	}
+	pa_str = pa_stream_new(pa_ctx, "Sound", &spec, NULL);
+	ERR_FAIL_COND_V(pa_ctx == NULL, ERR_CANT_OPEN);
+
+	const char *dev = device_name == "Default" ? NULL : device_name.utf8().get_data();
+	pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE);
+	int error_code = pa_stream_connect_playback(pa_str, dev, &attr, flags, NULL, NULL);
+	ERR_FAIL_COND_V(error_code < 0, ERR_CANT_OPEN);
 
 
 	samples_in.resize(buffer_frames * channels);
 	samples_in.resize(buffer_frames * channels);
 	samples_out.resize(pa_buffer_size);
 	samples_out.resize(pa_buffer_size);
 
 
-	mutex = Mutex::create();
-	thread = Thread::create(AudioDriverPulseAudio::thread_func, this);
+	return OK;
+}
+
+Error AudioDriverPulseAudio::init() {
+
+	active = false;
+	thread_exited = false;
+	exit_thread = false;
+
+	mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE);
+
+	pa_ml = pa_mainloop_new();
+	ERR_FAIL_COND_V(pa_ml == NULL, ERR_CANT_OPEN);
+
+	pa_ctx = pa_context_new(pa_mainloop_get_api(pa_ml), "Godot");
+	ERR_FAIL_COND_V(pa_ctx == NULL, ERR_CANT_OPEN);
+
+	pa_ready = 0;
+	pa_context_set_state_callback(pa_ctx, pa_state_cb, (void *)this);
+
+	int ret = pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
+	if (ret < 0) {
+		if (pa_ctx) {
+			pa_context_unref(pa_ctx);
+			pa_ctx = NULL;
+		}
+
+		if (pa_ml) {
+			pa_mainloop_free(pa_ml);
+			pa_ml = NULL;
+		}
+
+		return ERR_CANT_OPEN;
+	}
+
+	while (pa_ready == 0) {
+		pa_mainloop_iterate(pa_ml, 1, NULL);
+	}
+
+	if (pa_ready < 0) {
+		if (pa_ctx) {
+			pa_context_disconnect(pa_ctx);
+			pa_context_unref(pa_ctx);
+			pa_ctx = NULL;
+		}
+
+		if (pa_ml) {
+			pa_mainloop_free(pa_ml);
+			pa_ml = NULL;
+		}
+
+		return ERR_CANT_OPEN;
+	}
+
+	Error err = init_device();
+	if (err == OK) {
+		mutex = Mutex::create();
+		thread = Thread::create(AudioDriverPulseAudio::thread_func, this);
+	}
 
 
 	return OK;
 	return OK;
 }
 }
@@ -233,9 +253,24 @@ Error AudioDriverPulseAudio::init() {
 float AudioDriverPulseAudio::get_latency() {
 float AudioDriverPulseAudio::get_latency() {
 
 
 	if (latency == 0) { //only do this once since it's approximate anyway
 	if (latency == 0) { //only do this once since it's approximate anyway
-		int error_code;
-		pa_usec_t palat = pa_simple_get_latency(pulse, &error_code);
-		latency = double(palat) / 1000000.0;
+		lock();
+
+		pa_usec_t palat = 0;
+		if (pa_stream_get_state(pa_str) == PA_STREAM_READY) {
+			int negative = 0;
+
+			if (pa_stream_get_latency(pa_str, &palat, &negative) >= 0) {
+				if (negative) {
+					palat = 0;
+				}
+			}
+		}
+
+		if (palat > 0) {
+			latency = double(palat) / 1000000.0;
+		}
+
+		unlock();
 	}
 	}
 
 
 	return latency;
 	return latency;
@@ -278,17 +313,57 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
 			}
 			}
 		}
 		}
 
 
-		// pa_simple_write always consumes the entire buffer
-
 		int error_code;
 		int error_code;
 		int byte_size = ad->pa_buffer_size * sizeof(int16_t);
 		int byte_size = ad->pa_buffer_size * sizeof(int16_t);
-		if (pa_simple_write(ad->pulse, ad->samples_out.ptr(), byte_size, &error_code) < 0) {
-			// can't recover here
-			fprintf(stderr, "PulseAudio failed and can't recover: %s\n", pa_strerror(error_code));
-			ad->active = false;
-			ad->exit_thread = true;
-			break;
+
+		ad->lock();
+
+		int ret;
+		do {
+			ret = pa_mainloop_iterate(ad->pa_ml, 0, NULL);
+		} while (ret > 0);
+
+		if (pa_stream_get_state(ad->pa_str) == PA_STREAM_READY) {
+			const void *ptr = ad->samples_out.ptr();
+			while (byte_size > 0) {
+				size_t bytes = pa_stream_writable_size(ad->pa_str);
+				if (bytes > 0) {
+					if (bytes > byte_size) {
+						bytes = byte_size;
+					}
+
+					int ret = pa_stream_write(ad->pa_str, ptr, bytes, NULL, 0LL, PA_SEEK_RELATIVE);
+					if (ret >= 0) {
+						byte_size -= bytes;
+						ptr = (const char *)ptr + bytes;
+					}
+				} else {
+					pa_mainloop_iterate(ad->pa_ml, 1, NULL);
+				}
+			}
 		}
 		}
+
+		// User selected a new device, finish the current one so we'll init the new device
+		if (ad->device_name != ad->new_device) {
+			ad->device_name = ad->new_device;
+			ad->finish_device();
+
+			Error err = ad->init_device();
+			if (err != OK) {
+				ERR_PRINT("PulseAudio: init_device error");
+				ad->device_name = "Default";
+				ad->new_device = "Default";
+
+				err = ad->init_device();
+				if (err != OK) {
+					ad->active = false;
+					ad->exit_thread = true;
+					break;
+				}
+			}
+		}
+
+		ad->unlock();
 	}
 	}
 
 
 	ad->thread_exited = true;
 	ad->thread_exited = true;
@@ -309,6 +384,61 @@ AudioDriver::SpeakerMode AudioDriverPulseAudio::get_speaker_mode() const {
 	return get_speaker_mode_by_total_channels(channels);
 	return get_speaker_mode_by_total_channels(channels);
 }
 }
 
 
+void AudioDriverPulseAudio::pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) {
+	AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata;
+	int ctr = 0;
+
+	// If eol is set to a positive number, you're at the end of the list
+	if (eol > 0) {
+		return;
+	}
+
+	ad->pa_devices.push_back(l->name);
+	ad->pa_status++;
+}
+
+Array AudioDriverPulseAudio::get_device_list() {
+
+	pa_devices.clear();
+	pa_devices.push_back("Default");
+
+	if (pa_ctx == NULL) {
+		return pa_devices;
+	}
+
+	lock();
+
+	// Get the device list
+	pa_status = 0;
+	pa_operation *pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, (void *)this);
+	if (pa_op) {
+		while (pa_status == 0) {
+			int ret = pa_mainloop_iterate(pa_ml, 1, NULL);
+			if (ret < 0) {
+				ERR_PRINT("pa_mainloop_iterate error");
+			}
+		}
+
+		pa_operation_unref(pa_op);
+	} else {
+		ERR_PRINT("pa_context_get_server_info error");
+	}
+
+	unlock();
+
+	return pa_devices;
+}
+
+String AudioDriverPulseAudio::get_device() {
+
+	return device_name;
+}
+
+void AudioDriverPulseAudio::set_device(String device) {
+
+	new_device = device;
+}
+
 void AudioDriverPulseAudio::lock() {
 void AudioDriverPulseAudio::lock() {
 
 
 	if (!thread || !mutex)
 	if (!thread || !mutex)
@@ -323,6 +453,15 @@ void AudioDriverPulseAudio::unlock() {
 	mutex->unlock();
 	mutex->unlock();
 }
 }
 
 
+void AudioDriverPulseAudio::finish_device() {
+
+	if (pa_str) {
+		pa_stream_disconnect(pa_str);
+		pa_stream_unref(pa_str);
+		pa_str = NULL;
+	}
+}
+
 void AudioDriverPulseAudio::finish() {
 void AudioDriverPulseAudio::finish() {
 
 
 	if (!thread)
 	if (!thread)
@@ -331,9 +470,17 @@ void AudioDriverPulseAudio::finish() {
 	exit_thread = true;
 	exit_thread = true;
 	Thread::wait_to_finish(thread);
 	Thread::wait_to_finish(thread);
 
 
-	if (pulse) {
-		pa_simple_free(pulse);
-		pulse = NULL;
+	finish_device();
+
+	if (pa_ctx) {
+		pa_context_disconnect(pa_ctx);
+		pa_context_unref(pa_ctx);
+		pa_ctx = NULL;
+	}
+
+	if (pa_ml) {
+		pa_mainloop_free(pa_ml);
+		pa_ml = NULL;
 	}
 	}
 
 
 	memdelete(thread);
 	memdelete(thread);
@@ -347,9 +494,16 @@ void AudioDriverPulseAudio::finish() {
 
 
 AudioDriverPulseAudio::AudioDriverPulseAudio() {
 AudioDriverPulseAudio::AudioDriverPulseAudio() {
 
 
+	pa_ml = NULL;
+	pa_ctx = NULL;
+	pa_str = NULL;
+
 	mutex = NULL;
 	mutex = NULL;
 	thread = NULL;
 	thread = NULL;
-	pulse = NULL;
+
+	device_name = "Default";
+	new_device = "Default";
+	default_device = "";
 
 
 	samples_in.clear();
 	samples_in.clear();
 	samples_out.clear();
 	samples_out.clear();
@@ -359,6 +513,8 @@ AudioDriverPulseAudio::AudioDriverPulseAudio() {
 	pa_buffer_size = 0;
 	pa_buffer_size = 0;
 	channels = 0;
 	channels = 0;
 	pa_channels = 0;
 	pa_channels = 0;
+	pa_ready = 0;
+	pa_status = 0;
 
 
 	active = false;
 	active = false;
 	thread_exited = false;
 	thread_exited = false;

+ 24 - 2
drivers/pulseaudio/audio_driver_pulseaudio.h

@@ -37,14 +37,20 @@
 #include "core/os/thread.h"
 #include "core/os/thread.h"
 #include "servers/audio_server.h"
 #include "servers/audio_server.h"
 
 
-#include <pulse/simple.h>
+#include <pulse/pulseaudio.h>
 
 
 class AudioDriverPulseAudio : public AudioDriver {
 class AudioDriverPulseAudio : public AudioDriver {
 
 
 	Thread *thread;
 	Thread *thread;
 	Mutex *mutex;
 	Mutex *mutex;
 
 
-	pa_simple *pulse;
+	pa_mainloop *pa_ml;
+	pa_context *pa_ctx;
+	pa_stream *pa_str;
+
+	String device_name;
+	String new_device;
+	String default_device;
 
 
 	Vector<int32_t> samples_in;
 	Vector<int32_t> samples_in;
 	Vector<int16_t> samples_out;
 	Vector<int16_t> samples_out;
@@ -54,6 +60,9 @@ class AudioDriverPulseAudio : public AudioDriver {
 	unsigned int pa_buffer_size;
 	unsigned int pa_buffer_size;
 	int channels;
 	int channels;
 	int pa_channels;
 	int pa_channels;
+	int pa_ready;
+	int pa_status;
+	Array pa_devices;
 
 
 	bool active;
 	bool active;
 	bool thread_exited;
 	bool thread_exited;
@@ -61,6 +70,16 @@ class AudioDriverPulseAudio : public AudioDriver {
 
 
 	float latency;
 	float latency;
 
 
+	static void pa_state_cb(pa_context *c, void *userdata);
+	static void pa_sink_info_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata);
+	static void pa_server_info_cb(pa_context *c, const pa_server_info *i, void *userdata);
+	static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata);
+
+	Error init_device();
+	void finish_device();
+
+	void detect_channels();
+
 	static void thread_func(void *p_udata);
 	static void thread_func(void *p_udata);
 
 
 public:
 public:
@@ -72,6 +91,9 @@ public:
 	virtual void start();
 	virtual void start();
 	virtual int get_mix_rate() const;
 	virtual int get_mix_rate() const;
 	virtual SpeakerMode get_speaker_mode() const;
 	virtual SpeakerMode get_speaker_mode() const;
+	virtual Array get_device_list();
+	virtual String get_device();
+	virtual void set_device(String device);
 	virtual void lock();
 	virtual void lock();
 	virtual void unlock();
 	virtual void unlock();
 	virtual void finish();
 	virtual void finish();

+ 129 - 2
drivers/wasapi/audio_driver_wasapi.cpp

@@ -35,6 +35,8 @@
 #include "os/os.h"
 #include "os/os.h"
 #include "project_settings.h"
 #include "project_settings.h"
 
 
+#include <functiondiscoverykeys.h>
+
 const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
 const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
 const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
 const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
 const IID IID_IAudioClient = __uuidof(IAudioClient);
 const IID IID_IAudioClient = __uuidof(IAudioClient);
@@ -121,7 +123,61 @@ Error AudioDriverWASAPI::init_device(bool reinit) {
 	HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator);
 	HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator);
 	ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
 	ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
 
 
-	hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
+	if (device_name == "Default") {
+		hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
+	} else {
+		IMMDeviceCollection *devices = NULL;
+
+		hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices);
+		ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
+
+		LPWSTR strId = NULL;
+		bool found = false;
+
+		UINT count = 0;
+		hr = devices->GetCount(&count);
+		ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
+
+		for (ULONG i = 0; i < count && !found; i++) {
+			IMMDevice *device = NULL;
+
+			hr = devices->Item(i, &device);
+			ERR_BREAK(hr != S_OK);
+
+			IPropertyStore *props = NULL;
+			hr = device->OpenPropertyStore(STGM_READ, &props);
+			ERR_BREAK(hr != S_OK);
+
+			PROPVARIANT propvar;
+			PropVariantInit(&propvar);
+
+			hr = props->GetValue(PKEY_Device_FriendlyName, &propvar);
+			ERR_BREAK(hr != S_OK);
+
+			if (device_name == String(propvar.pwszVal)) {
+				hr = device->GetId(&strId);
+				ERR_BREAK(hr != S_OK);
+
+				found = true;
+			}
+
+			PropVariantClear(&propvar);
+			props->Release();
+			device->Release();
+		}
+
+		if (found) {
+			hr = enumerator->GetDevice(strId, &device);
+		}
+
+		if (strId) {
+			CoTaskMemFree(strId);
+		}
+
+		if (device == NULL) {
+			hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
+		}
+	}
 	if (reinit) {
 	if (reinit) {
 		// In case we're trying to re-initialize the device prevent throwing this error on the console,
 		// In case we're trying to re-initialize the device prevent throwing this error on the console,
 		// otherwise if there is currently no device available this will spam the console.
 		// otherwise if there is currently no device available this will spam the console.
@@ -294,6 +350,64 @@ AudioDriver::SpeakerMode AudioDriverWASAPI::get_speaker_mode() const {
 	return get_speaker_mode_by_total_channels(channels);
 	return get_speaker_mode_by_total_channels(channels);
 }
 }
 
 
+Array AudioDriverWASAPI::get_device_list() {
+
+	Array list;
+	IMMDeviceCollection *devices = NULL;
+	IMMDeviceEnumerator *enumerator = NULL;
+
+	list.push_back(String("Default"));
+
+	CoInitialize(NULL);
+
+	HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator);
+	ERR_FAIL_COND_V(hr != S_OK, Array());
+
+	hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices);
+	ERR_FAIL_COND_V(hr != S_OK, Array());
+
+	UINT count = 0;
+	hr = devices->GetCount(&count);
+	ERR_FAIL_COND_V(hr != S_OK, Array());
+
+	for (ULONG i = 0; i < count; i++) {
+		IMMDevice *device = NULL;
+
+		hr = devices->Item(i, &device);
+		ERR_BREAK(hr != S_OK);
+
+		IPropertyStore *props = NULL;
+		hr = device->OpenPropertyStore(STGM_READ, &props);
+		ERR_BREAK(hr != S_OK);
+
+		PROPVARIANT propvar;
+		PropVariantInit(&propvar);
+
+		hr = props->GetValue(PKEY_Device_FriendlyName, &propvar);
+		ERR_BREAK(hr != S_OK);
+
+		list.push_back(String(propvar.pwszVal));
+
+		PropVariantClear(&propvar);
+		props->Release();
+		device->Release();
+	}
+
+	devices->Release();
+	enumerator->Release();
+	return list;
+}
+
+String AudioDriverWASAPI::get_device() {
+
+	return device_name;
+}
+
+void AudioDriverWASAPI::set_device(String device) {
+
+	new_device = device;
+}
+
 void AudioDriverWASAPI::write_sample(AudioDriverWASAPI *ad, BYTE *buffer, int i, int32_t sample) {
 void AudioDriverWASAPI::write_sample(AudioDriverWASAPI *ad, BYTE *buffer, int i, int32_t sample) {
 	if (ad->format_tag == WAVE_FORMAT_PCM) {
 	if (ad->format_tag == WAVE_FORMAT_PCM) {
 		switch (ad->bits_per_sample) {
 		switch (ad->bits_per_sample) {
@@ -409,7 +523,8 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
 			}
 			}
 		}
 		}
 
 
-		if (default_device_changed) {
+		// If we're using the Default device and it changed finish it so we'll re-init the device
+		if (ad->device_name == "Default" && default_device_changed) {
 			Error err = ad->finish_device();
 			Error err = ad->finish_device();
 			if (err != OK) {
 			if (err != OK) {
 				ERR_PRINT("WASAPI: finish_device error");
 				ERR_PRINT("WASAPI: finish_device error");
@@ -418,6 +533,15 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
 			default_device_changed = false;
 			default_device_changed = false;
 		}
 		}
 
 
+		// User selected a new device, finish the current one so we'll init the new device
+		if (ad->device_name != ad->new_device) {
+			ad->device_name = ad->new_device;
+			Error err = ad->finish_device();
+			if (err != OK) {
+				ERR_PRINT("WASAPI: finish_device error");
+			}
+		}
+
 		if (!ad->audio_client) {
 		if (!ad->audio_client) {
 			Error err = ad->init_device(true);
 			Error err = ad->init_device(true);
 			if (err == OK) {
 			if (err == OK) {
@@ -492,6 +616,9 @@ AudioDriverWASAPI::AudioDriverWASAPI() {
 	thread_exited = false;
 	thread_exited = false;
 	exit_thread = false;
 	exit_thread = false;
 	active = false;
 	active = false;
+
+	device_name = "Default";
+	new_device = "Default";
 }
 }
 
 
 #endif
 #endif

+ 6 - 0
drivers/wasapi/audio_driver_wasapi.h

@@ -49,6 +49,9 @@ class AudioDriverWASAPI : public AudioDriver {
 	Mutex *mutex;
 	Mutex *mutex;
 	Thread *thread;
 	Thread *thread;
 
 
+	String device_name;
+	String new_device;
+
 	WORD format_tag;
 	WORD format_tag;
 	WORD bits_per_sample;
 	WORD bits_per_sample;
 
 
@@ -80,6 +83,9 @@ public:
 	virtual void start();
 	virtual void start();
 	virtual int get_mix_rate() const;
 	virtual int get_mix_rate() const;
 	virtual SpeakerMode get_speaker_mode() const;
 	virtual SpeakerMode get_speaker_mode() const;
+	virtual Array get_device_list();
+	virtual String get_device();
+	virtual void set_device(String device);
 	virtual void lock();
 	virtual void lock();
 	virtual void unlock();
 	virtual void unlock();
 	virtual void finish();
 	virtual void finish();

+ 2 - 2
platform/x11/detect.py

@@ -234,10 +234,10 @@ def configure(env):
         print("ALSA libraries not found, disabling driver")
         print("ALSA libraries not found, disabling driver")
 
 
     if env['pulseaudio']:
     if env['pulseaudio']:
-        if (os.system("pkg-config --exists libpulse-simple") == 0): # 0 means found
+        if (os.system("pkg-config --exists libpulse") == 0): # 0 means found
             print("Enabling PulseAudio")
             print("Enabling PulseAudio")
             env.Append(CPPFLAGS=["-DPULSEAUDIO_ENABLED"])
             env.Append(CPPFLAGS=["-DPULSEAUDIO_ENABLED"])
-            env.ParseConfig('pkg-config --cflags --libs libpulse-simple')
+            env.ParseConfig('pkg-config --cflags --libs libpulse')
         else:
         else:
             print("PulseAudio development libraries not found, disabling driver")
             print("PulseAudio development libraries not found, disabling driver")
 
 

+ 30 - 0
servers/audio_server.cpp

@@ -101,6 +101,18 @@ int AudioDriver::get_total_channels_by_speaker_mode(AudioDriver::SpeakerMode p_m
 	ERR_FAIL_V(2);
 	ERR_FAIL_V(2);
 }
 }
 
 
+Array AudioDriver::get_device_list() {
+	Array list;
+
+	list.push_back("Default");
+
+	return list;
+}
+
+String AudioDriver::get_device() {
+	return "Default";
+}
+
 AudioDriver::AudioDriver() {
 AudioDriver::AudioDriver() {
 
 
 	_last_mix_time = 0;
 	_last_mix_time = 0;
@@ -1108,6 +1120,21 @@ Ref<AudioBusLayout> AudioServer::generate_bus_layout() const {
 	return state;
 	return state;
 }
 }
 
 
+Array AudioServer::get_device_list() {
+
+	return AudioDriver::get_singleton()->get_device_list();
+}
+
+String AudioServer::get_device() {
+
+	return AudioDriver::get_singleton()->get_device();
+}
+
+void AudioServer::set_device(String device) {
+
+	AudioDriver::get_singleton()->set_device(device);
+}
+
 void AudioServer::_bind_methods() {
 void AudioServer::_bind_methods() {
 
 
 	ClassDB::bind_method(D_METHOD("set_bus_count", "amount"), &AudioServer::set_bus_count);
 	ClassDB::bind_method(D_METHOD("set_bus_count", "amount"), &AudioServer::set_bus_count);
@@ -1154,6 +1181,9 @@ void AudioServer::_bind_methods() {
 
 
 	ClassDB::bind_method(D_METHOD("get_speaker_mode"), &AudioServer::get_speaker_mode);
 	ClassDB::bind_method(D_METHOD("get_speaker_mode"), &AudioServer::get_speaker_mode);
 	ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioServer::get_mix_rate);
 	ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioServer::get_mix_rate);
+	ClassDB::bind_method(D_METHOD("get_device_list"), &AudioServer::get_device_list);
+	ClassDB::bind_method(D_METHOD("get_device"), &AudioServer::get_device);
+	ClassDB::bind_method(D_METHOD("set_device"), &AudioServer::set_device);
 
 
 	ClassDB::bind_method(D_METHOD("set_bus_layout", "bus_layout"), &AudioServer::set_bus_layout);
 	ClassDB::bind_method(D_METHOD("set_bus_layout", "bus_layout"), &AudioServer::set_bus_layout);
 	ClassDB::bind_method(D_METHOD("generate_bus_layout"), &AudioServer::generate_bus_layout);
 	ClassDB::bind_method(D_METHOD("generate_bus_layout"), &AudioServer::generate_bus_layout);

+ 7 - 0
servers/audio_server.h

@@ -70,6 +70,9 @@ public:
 	virtual void start() = 0;
 	virtual void start() = 0;
 	virtual int get_mix_rate() const = 0;
 	virtual int get_mix_rate() const = 0;
 	virtual SpeakerMode get_speaker_mode() const = 0;
 	virtual SpeakerMode get_speaker_mode() const = 0;
+	virtual Array get_device_list();
+	virtual String get_device();
+	virtual void set_device(String device) {}
 	virtual void lock() = 0;
 	virtual void lock() = 0;
 	virtual void unlock() = 0;
 	virtual void unlock() = 0;
 	virtual void finish() = 0;
 	virtual void finish() = 0;
@@ -300,6 +303,10 @@ public:
 	void set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout);
 	void set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout);
 	Ref<AudioBusLayout> generate_bus_layout() const;
 	Ref<AudioBusLayout> generate_bus_layout() const;
 
 
+	Array get_device_list();
+	String get_device();
+	void set_device(String device);
+
 	AudioServer();
 	AudioServer();
 	virtual ~AudioServer();
 	virtual ~AudioServer();
 };
 };