mirror of
https://github.com/godotengine/godot
synced 2024-09-05 05:58:29 +00:00
Added new audio device functions to set/get the audio device
This commit is contained in:
parent
fd79de01c2
commit
ecc1b34cbc
|
@ -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(){
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(){};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue