summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/alsa/audio_driver_alsa.cpp143
-rw-r--r--drivers/alsa/audio_driver_alsa.h13
-rw-r--r--drivers/coreaudio/audio_driver_coreaudio.cpp212
-rw-r--r--drivers/coreaudio/audio_driver_coreaudio.h20
-rw-r--r--drivers/pulseaudio/audio_driver_pulseaudio.cpp382
-rw-r--r--drivers/pulseaudio/audio_driver_pulseaudio.h26
-rw-r--r--drivers/wasapi/audio_driver_wasapi.cpp131
-rw-r--r--drivers/wasapi/audio_driver_wasapi.h6
-rw-r--r--platform/x11/detect.py4
-rw-r--r--servers/audio_server.cpp30
-rw-r--r--servers/audio_server.h7
11 files changed, 779 insertions, 195 deletions
diff --git a/drivers/alsa/audio_driver_alsa.cpp b/drivers/alsa/audio_driver_alsa.cpp
index 0bb03d23ea..1e17e72532 100644
--- a/drivers/alsa/audio_driver_alsa.cpp
+++ b/drivers/alsa/audio_driver_alsa.cpp
@@ -37,19 +37,20 @@
#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);
speaker_mode = SPEAKER_MODE_STEREO;
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;
snd_pcm_hw_params_t *hwparams;
snd_pcm_sw_params_t *swparams;
@@ -65,7 +66,16 @@ Error AudioDriverALSA::init() {
//6 chans - "plug:surround51"
//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);
@@ -129,15 +139,28 @@ Error AudioDriverALSA::init() {
status = snd_pcm_sw_params(pcm_handle, swparams);
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);
- mutex = Mutex::create();
- thread = Thread::create(AudioDriverALSA::thread_func, this);
-
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) {
@@ -152,7 +175,7 @@ void AudioDriverALSA::thread_func(void *p_udata) {
} else {
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();
@@ -167,7 +190,7 @@ void AudioDriverALSA::thread_func(void *p_udata) {
while (todo) {
if (ad->exit_thread)
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);
if (wrote < 0) {
@@ -193,6 +216,26 @@ void AudioDriverALSA::thread_func(void *p_udata) {
total += 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;
@@ -213,6 +256,49 @@ AudioDriver::SpeakerMode AudioDriverALSA::get_speaker_mode() const {
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() {
if (!thread || !mutex)
@@ -227,6 +313,14 @@ void AudioDriverALSA::unlock() {
mutex->unlock();
};
+void AudioDriverALSA::finish_device() {
+
+ if (pcm_open) {
+ snd_pcm_close(pcm_handle);
+ pcm_open = NULL;
+ }
+}
+
void AudioDriverALSA::finish() {
if (!thread)
@@ -235,17 +329,13 @@ void AudioDriverALSA::finish() {
exit_thread = true;
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);
- if (mutex)
+ if (mutex) {
memdelete(mutex);
+ mutex = NULL;
+ }
thread = NULL;
};
@@ -254,6 +344,9 @@ AudioDriverALSA::AudioDriverALSA() {
mutex = NULL;
thread = NULL;
pcm_handle = NULL;
+
+ device_name = "Default";
+ new_device = "Default";
};
AudioDriverALSA::~AudioDriverALSA(){
diff --git a/drivers/alsa/audio_driver_alsa.h b/drivers/alsa/audio_driver_alsa.h
index 8ed60dfdc7..2878e100a2 100644
--- a/drivers/alsa/audio_driver_alsa.h
+++ b/drivers/alsa/audio_driver_alsa.h
@@ -44,8 +44,14 @@ class AudioDriverALSA : public AudioDriver {
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);
@@ -71,6 +77,9 @@ public:
virtual void start();
virtual int get_mix_rate() 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 unlock();
virtual void finish();
diff --git a/drivers/coreaudio/audio_driver_coreaudio.cpp b/drivers/coreaudio/audio_driver_coreaudio.cpp
index 0b39f9ebc3..c84469f26f 100644
--- a/drivers/coreaudio/audio_driver_coreaudio.cpp
+++ b/drivers/coreaudio/audio_driver_coreaudio.cpp
@@ -37,16 +37,22 @@
#define kOutputBus 0
#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;
- 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;
}
#endif
-Error AudioDriverCoreAudio::initDevice() {
+Error AudioDriverCoreAudio::init_device() {
AudioComponentDescription desc;
zeromem(&desc, sizeof(desc));
desc.componentType = kAudioUnitType_Output;
@@ -129,7 +135,7 @@ Error AudioDriverCoreAudio::initDevice() {
return OK;
}
-Error AudioDriverCoreAudio::finishDevice() {
+Error AudioDriverCoreAudio::finish_device() {
OSStatus result;
if (active) {
@@ -153,49 +159,18 @@ Error AudioDriverCoreAudio::init() {
channels = 2;
#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);
#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,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
@@ -257,6 +232,150 @@ AudioDriver::SpeakerMode AudioDriverCoreAudio::get_speaker_mode() const {
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() {
if (mutex)
mutex->lock();
@@ -276,10 +395,15 @@ bool AudioDriverCoreAudio::try_lock() {
void AudioDriverCoreAudio::finish() {
OSStatus result;
- finishDevice();
+ finish_device();
#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) {
ERR_PRINT("AudioObjectRemovePropertyListener failed");
}
@@ -309,6 +433,8 @@ AudioDriverCoreAudio::AudioDriverCoreAudio() {
buffer_frames = 0;
samples_in.clear();
+
+ device_name = "Default";
};
AudioDriverCoreAudio::~AudioDriverCoreAudio(){};
diff --git a/drivers/coreaudio/audio_driver_coreaudio.h b/drivers/coreaudio/audio_driver_coreaudio.h
index 51256085d8..1ef474ac19 100644
--- a/drivers/coreaudio/audio_driver_coreaudio.h
+++ b/drivers/coreaudio/audio_driver_coreaudio.h
@@ -43,12 +43,12 @@
class AudioDriverCoreAudio : public AudioDriver {
AudioComponentInstance audio_unit;
-#ifdef OSX_ENABLED
- AudioObjectPropertyAddress outputDeviceAddress;
-#endif
+
bool active;
Mutex *mutex;
+ String device_name;
+
int mix_rate;
unsigned int channels;
unsigned int buffer_frames;
@@ -56,14 +56,18 @@ class AudioDriverCoreAudio : public AudioDriver {
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,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData);
- Error initDevice();
- Error finishDevice();
+ Error init_device();
+ Error finish_device();
public:
const char *get_name() const {
@@ -74,12 +78,16 @@ public:
virtual void start();
virtual int get_mix_rate() 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 unlock();
virtual void finish();
bool try_lock();
- Error reopen();
AudioDriverCoreAudio();
~AudioDriverCoreAudio();
diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.cpp b/drivers/pulseaudio/audio_driver_pulseaudio.cpp
index 9639f06345..5dddb1a900 100644
--- a/drivers/pulseaudio/audio_driver_pulseaudio.cpp
+++ b/drivers/pulseaudio/audio_driver_pulseaudio.cpp
@@ -37,131 +37,102 @@
#include "os/os.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:
- *pa_ready = 2;
+ case PA_CONTEXT_FAILED:
+ ad->pa_ready = -1;
break;
case PA_CONTEXT_READY:
- *pa_ready = 1;
+ ad->pa_ready = 1;
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 > 0) {
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) {
- 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) {
ERR_PRINT("pa_mainloop_iterate error");
}
}
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 {
- 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
// 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
// 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) {
case 1: // Mono
case 3: // Surround 2.1
@@ -205,27 +176,76 @@ Error AudioDriverPulseAudio::init() {
attr.maxlength = (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_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;
}
@@ -233,9 +253,24 @@ Error AudioDriverPulseAudio::init() {
float AudioDriverPulseAudio::get_latency() {
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;
@@ -278,17 +313,57 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
}
}
- // pa_simple_write always consumes the entire buffer
-
int error_code;
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;
@@ -309,6 +384,61 @@ AudioDriver::SpeakerMode AudioDriverPulseAudio::get_speaker_mode() const {
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() {
if (!thread || !mutex)
@@ -323,6 +453,15 @@ void AudioDriverPulseAudio::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() {
if (!thread)
@@ -331,9 +470,17 @@ void AudioDriverPulseAudio::finish() {
exit_thread = true;
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);
@@ -347,9 +494,16 @@ void AudioDriverPulseAudio::finish() {
AudioDriverPulseAudio::AudioDriverPulseAudio() {
+ pa_ml = NULL;
+ pa_ctx = NULL;
+ pa_str = NULL;
+
mutex = NULL;
thread = NULL;
- pulse = NULL;
+
+ device_name = "Default";
+ new_device = "Default";
+ default_device = "";
samples_in.clear();
samples_out.clear();
@@ -359,6 +513,8 @@ AudioDriverPulseAudio::AudioDriverPulseAudio() {
pa_buffer_size = 0;
channels = 0;
pa_channels = 0;
+ pa_ready = 0;
+ pa_status = 0;
active = false;
thread_exited = false;
diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.h b/drivers/pulseaudio/audio_driver_pulseaudio.h
index 5737f24314..934031e2a5 100644
--- a/drivers/pulseaudio/audio_driver_pulseaudio.h
+++ b/drivers/pulseaudio/audio_driver_pulseaudio.h
@@ -37,14 +37,20 @@
#include "core/os/thread.h"
#include "servers/audio_server.h"
-#include <pulse/simple.h>
+#include <pulse/pulseaudio.h>
class AudioDriverPulseAudio : public AudioDriver {
Thread *thread;
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<int16_t> samples_out;
@@ -54,6 +60,9 @@ class AudioDriverPulseAudio : public AudioDriver {
unsigned int pa_buffer_size;
int channels;
int pa_channels;
+ int pa_ready;
+ int pa_status;
+ Array pa_devices;
bool active;
bool thread_exited;
@@ -61,6 +70,16 @@ class AudioDriverPulseAudio : public AudioDriver {
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);
public:
@@ -72,6 +91,9 @@ public:
virtual void start();
virtual int get_mix_rate() 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 unlock();
virtual void finish();
diff --git a/drivers/wasapi/audio_driver_wasapi.cpp b/drivers/wasapi/audio_driver_wasapi.cpp
index 36acfb10d1..fb5e355d63 100644
--- a/drivers/wasapi/audio_driver_wasapi.cpp
+++ b/drivers/wasapi/audio_driver_wasapi.cpp
@@ -35,6 +35,8 @@
#include "os/os.h"
#include "project_settings.h"
+#include <functiondiscoverykeys.h>
+
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
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);
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) {
// 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.
@@ -285,6 +341,64 @@ AudioDriver::SpeakerMode AudioDriverWASAPI::get_speaker_mode() const {
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) {
if (ad->format_tag == WAVE_FORMAT_PCM) {
switch (ad->bits_per_sample) {
@@ -400,7 +514,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();
if (err != OK) {
ERR_PRINT("WASAPI: finish_device error");
@@ -409,6 +524,15 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
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) {
Error err = ad->init_device(true);
if (err == OK) {
@@ -483,6 +607,9 @@ AudioDriverWASAPI::AudioDriverWASAPI() {
thread_exited = false;
exit_thread = false;
active = false;
+
+ device_name = "Default";
+ new_device = "Default";
}
#endif
diff --git a/drivers/wasapi/audio_driver_wasapi.h b/drivers/wasapi/audio_driver_wasapi.h
index 2b19f0cca1..c97f4c288c 100644
--- a/drivers/wasapi/audio_driver_wasapi.h
+++ b/drivers/wasapi/audio_driver_wasapi.h
@@ -49,6 +49,9 @@ class AudioDriverWASAPI : public AudioDriver {
Mutex *mutex;
Thread *thread;
+ String device_name;
+ String new_device;
+
WORD format_tag;
WORD bits_per_sample;
@@ -80,6 +83,9 @@ public:
virtual void start();
virtual int get_mix_rate() 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 unlock();
virtual void finish();
diff --git a/platform/x11/detect.py b/platform/x11/detect.py
index da2b0701b6..5820a926e9 100644
--- a/platform/x11/detect.py
+++ b/platform/x11/detect.py
@@ -234,10 +234,10 @@ def configure(env):
print("ALSA libraries not found, disabling driver")
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")
env.Append(CPPFLAGS=["-DPULSEAUDIO_ENABLED"])
- env.ParseConfig('pkg-config --cflags --libs libpulse-simple')
+ env.ParseConfig('pkg-config --cflags --libs libpulse')
else:
print("PulseAudio development libraries not found, disabling driver")
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index c3b0de6d9a..b08e41301a 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -101,6 +101,18 @@ int AudioDriver::get_total_channels_by_speaker_mode(AudioDriver::SpeakerMode p_m
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() {
_last_mix_time = 0;
@@ -1108,6 +1120,21 @@ Ref<AudioBusLayout> AudioServer::generate_bus_layout() const {
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() {
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_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("generate_bus_layout"), &AudioServer::generate_bus_layout);
diff --git a/servers/audio_server.h b/servers/audio_server.h
index a8be48b4c3..af2668b69e 100644
--- a/servers/audio_server.h
+++ b/servers/audio_server.h
@@ -70,6 +70,9 @@ public:
virtual void start() = 0;
virtual int get_mix_rate() 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 unlock() = 0;
virtual void finish() = 0;
@@ -300,6 +303,10 @@ public:
void set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout);
Ref<AudioBusLayout> generate_bus_layout() const;
+ Array get_device_list();
+ String get_device();
+ void set_device(String device);
+
AudioServer();
virtual ~AudioServer();
};