Added new audio device functions to set/get the audio device

This commit is contained in:
Marcelo Fernandez 2018-03-25 00:43:51 -03:00
parent fd79de01c2
commit ecc1b34cbc
11 changed files with 780 additions and 196 deletions

View file

@ -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(){

View file

@ -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();

View file

@ -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(){};

View file

@ -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();

View file

@ -37,98 +37,51 @@
#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() {
void AudioDriverPulseAudio::detect_channels() {
pa_mainloop *pa_ml;
pa_mainloop_api *pa_mlapi;
pa_operation *pa_op;
pa_context *pa_ctx;
pa_channels = 2;
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);
return 2;
}
pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready);
// Wait until the pa server is ready
while (pa_ready == 0) {
pa_mainloop_iterate(pa_ml, 1, NULL);
}
// 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;
}
// Get the default output device name
pa_op = pa_context_get_server_info(pa_ctx, &server_info_cb, (void *)default_output);
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);
// 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 (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_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");
}
@ -136,32 +89,50 @@ static unsigned int detect_channels() {
pa_operation_unref(pa_op);
} else {
ERR_PRINT("pa_context_get_sink_info_by_name error");
ERR_PRINT("pa_context_get_server_info error");
}
} else {
ERR_PRINT("pa_context_get_server_info error");
}
pa_context_disconnect(pa_ctx);
pa_context_unref(pa_ctx);
pa_mainloop_free(pa_ml);
char device[1024];
if (device_name == "Default") {
strcpy(device, default_device.utf8().get_data());
} else {
strcpy(device, device_name.utf8().get_data());
}
return channels;
// 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_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_sink_info_by_name error");
}
}
Error AudioDriverPulseAudio::init() {
Error AudioDriverPulseAudio::init_device() {
active = false;
thread_exited = false;
exit_thread = false;
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);
pa_str = pa_stream_new(pa_ctx, "Sound", &spec, NULL);
ERR_FAIL_COND_V(pa_ctx == NULL, ERR_CANT_OPEN);
if (pulse == NULL) {
fprintf(stderr, "PulseAudio ERR: %s\n", pa_strerror(error_code));
ERR_FAIL_COND_V(pulse == 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;

View file

@ -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();

View file

@ -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

View file

@ -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();

View file

@ -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")

View file

@ -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);

View file

@ -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();
};