Merge pull request #17742 from marcelofg55/audio_device_list
Added new audio device functions to set/get the audio device
This commit is contained in:
commit
1570a72eee
|
@ -37,19 +37,20 @@
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
Error AudioDriverALSA::init() {
|
Error AudioDriverALSA::init_device() {
|
||||||
|
|
||||||
active = false;
|
|
||||||
thread_exited = false;
|
|
||||||
exit_thread = false;
|
|
||||||
pcm_open = false;
|
|
||||||
samples_in = NULL;
|
|
||||||
samples_out = NULL;
|
|
||||||
|
|
||||||
mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE);
|
mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE);
|
||||||
speaker_mode = SPEAKER_MODE_STEREO;
|
speaker_mode = SPEAKER_MODE_STEREO;
|
||||||
channels = 2;
|
channels = 2;
|
||||||
|
|
||||||
|
// If there is a specified device check that it is really present
|
||||||
|
if (device_name != "Default") {
|
||||||
|
Array list = get_device_list();
|
||||||
|
if (list.find(device_name) == -1) {
|
||||||
|
device_name = "Default";
|
||||||
|
new_device = "Default";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int status;
|
int status;
|
||||||
snd_pcm_hw_params_t *hwparams;
|
snd_pcm_hw_params_t *hwparams;
|
||||||
snd_pcm_sw_params_t *swparams;
|
snd_pcm_sw_params_t *swparams;
|
||||||
|
@ -65,7 +66,16 @@ Error AudioDriverALSA::init() {
|
||||||
//6 chans - "plug:surround51"
|
//6 chans - "plug:surround51"
|
||||||
//4 chans - "plug:surround40";
|
//4 chans - "plug:surround40";
|
||||||
|
|
||||||
status = snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
if (device_name == "Default") {
|
||||||
|
status = snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
||||||
|
} else {
|
||||||
|
String device = device_name;
|
||||||
|
int pos = device.find(";");
|
||||||
|
if (pos != -1) {
|
||||||
|
device = device.substr(0, pos);
|
||||||
|
}
|
||||||
|
status = snd_pcm_open(&pcm_handle, device.utf8().get_data(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
ERR_FAIL_COND_V(status < 0, ERR_CANT_OPEN);
|
ERR_FAIL_COND_V(status < 0, ERR_CANT_OPEN);
|
||||||
|
|
||||||
|
@ -129,15 +139,28 @@ Error AudioDriverALSA::init() {
|
||||||
status = snd_pcm_sw_params(pcm_handle, swparams);
|
status = snd_pcm_sw_params(pcm_handle, swparams);
|
||||||
CHECK_FAIL(status < 0);
|
CHECK_FAIL(status < 0);
|
||||||
|
|
||||||
samples_in = memnew_arr(int32_t, period_size * channels);
|
samples_in.resize(period_size * channels);
|
||||||
samples_out = memnew_arr(int16_t, period_size * channels);
|
samples_out.resize(period_size * channels);
|
||||||
|
|
||||||
snd_pcm_nonblock(pcm_handle, 0);
|
snd_pcm_nonblock(pcm_handle, 0);
|
||||||
|
|
||||||
mutex = Mutex::create();
|
|
||||||
thread = Thread::create(AudioDriverALSA::thread_func, this);
|
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error AudioDriverALSA::init() {
|
||||||
|
|
||||||
|
active = false;
|
||||||
|
thread_exited = false;
|
||||||
|
exit_thread = false;
|
||||||
|
pcm_open = false;
|
||||||
|
|
||||||
|
Error err = init_device();
|
||||||
|
if (err == OK) {
|
||||||
|
mutex = Mutex::create();
|
||||||
|
thread = Thread::create(AudioDriverALSA::thread_func, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
};
|
};
|
||||||
|
|
||||||
void AudioDriverALSA::thread_func(void *p_udata) {
|
void AudioDriverALSA::thread_func(void *p_udata) {
|
||||||
|
@ -152,7 +175,7 @@ void AudioDriverALSA::thread_func(void *p_udata) {
|
||||||
} else {
|
} else {
|
||||||
ad->lock();
|
ad->lock();
|
||||||
|
|
||||||
ad->audio_server_process(ad->period_size, ad->samples_in);
|
ad->audio_server_process(ad->period_size, ad->samples_in.ptrw());
|
||||||
|
|
||||||
ad->unlock();
|
ad->unlock();
|
||||||
|
|
||||||
|
@ -167,7 +190,7 @@ void AudioDriverALSA::thread_func(void *p_udata) {
|
||||||
while (todo) {
|
while (todo) {
|
||||||
if (ad->exit_thread)
|
if (ad->exit_thread)
|
||||||
break;
|
break;
|
||||||
uint8_t *src = (uint8_t *)ad->samples_out;
|
uint8_t *src = (uint8_t *)ad->samples_out.ptr();
|
||||||
int wrote = snd_pcm_writei(ad->pcm_handle, (void *)(src + (total * ad->channels)), todo);
|
int wrote = snd_pcm_writei(ad->pcm_handle, (void *)(src + (total * ad->channels)), todo);
|
||||||
|
|
||||||
if (wrote < 0) {
|
if (wrote < 0) {
|
||||||
|
@ -193,6 +216,26 @@ void AudioDriverALSA::thread_func(void *p_udata) {
|
||||||
total += wrote;
|
total += wrote;
|
||||||
todo -= wrote;
|
todo -= wrote;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// User selected a new device, finish the current one so we'll init the new device
|
||||||
|
if (ad->device_name != ad->new_device) {
|
||||||
|
ad->device_name = ad->new_device;
|
||||||
|
ad->finish_device();
|
||||||
|
|
||||||
|
Error err = ad->init_device();
|
||||||
|
if (err != OK) {
|
||||||
|
ERR_PRINT("ALSA: init_device error");
|
||||||
|
ad->device_name = "Default";
|
||||||
|
ad->new_device = "Default";
|
||||||
|
|
||||||
|
err = ad->init_device();
|
||||||
|
if (err != OK) {
|
||||||
|
ad->active = false;
|
||||||
|
ad->exit_thread = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ad->thread_exited = true;
|
ad->thread_exited = true;
|
||||||
|
@ -213,6 +256,49 @@ AudioDriver::SpeakerMode AudioDriverALSA::get_speaker_mode() const {
|
||||||
return speaker_mode;
|
return speaker_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Array AudioDriverALSA::get_device_list() {
|
||||||
|
|
||||||
|
Array list;
|
||||||
|
|
||||||
|
list.push_back("Default");
|
||||||
|
|
||||||
|
void **hints;
|
||||||
|
|
||||||
|
if (snd_device_name_hint(-1, "pcm", &hints) < 0)
|
||||||
|
return list;
|
||||||
|
|
||||||
|
for (void **n = hints; *n != NULL; n++) {
|
||||||
|
char *name = snd_device_name_get_hint(*n, "NAME");
|
||||||
|
char *desc = snd_device_name_get_hint(*n, "DESC");
|
||||||
|
|
||||||
|
if (name != NULL && !strncmp(name, "plughw", 6)) {
|
||||||
|
if (desc) {
|
||||||
|
list.push_back(String(name) + ";" + String(desc));
|
||||||
|
} else {
|
||||||
|
list.push_back(String(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desc != NULL)
|
||||||
|
free(desc);
|
||||||
|
if (name != NULL)
|
||||||
|
free(name);
|
||||||
|
}
|
||||||
|
snd_device_name_free_hint(hints);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AudioDriverALSA::get_device() {
|
||||||
|
|
||||||
|
return device_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDriverALSA::set_device(String device) {
|
||||||
|
|
||||||
|
new_device = device;
|
||||||
|
}
|
||||||
|
|
||||||
void AudioDriverALSA::lock() {
|
void AudioDriverALSA::lock() {
|
||||||
|
|
||||||
if (!thread || !mutex)
|
if (!thread || !mutex)
|
||||||
|
@ -227,6 +313,14 @@ void AudioDriverALSA::unlock() {
|
||||||
mutex->unlock();
|
mutex->unlock();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void AudioDriverALSA::finish_device() {
|
||||||
|
|
||||||
|
if (pcm_open) {
|
||||||
|
snd_pcm_close(pcm_handle);
|
||||||
|
pcm_open = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AudioDriverALSA::finish() {
|
void AudioDriverALSA::finish() {
|
||||||
|
|
||||||
if (!thread)
|
if (!thread)
|
||||||
|
@ -235,17 +329,13 @@ void AudioDriverALSA::finish() {
|
||||||
exit_thread = true;
|
exit_thread = true;
|
||||||
Thread::wait_to_finish(thread);
|
Thread::wait_to_finish(thread);
|
||||||
|
|
||||||
if (pcm_open)
|
finish_device();
|
||||||
snd_pcm_close(pcm_handle);
|
|
||||||
|
|
||||||
if (samples_in) {
|
|
||||||
memdelete_arr(samples_in);
|
|
||||||
memdelete_arr(samples_out);
|
|
||||||
};
|
|
||||||
|
|
||||||
memdelete(thread);
|
memdelete(thread);
|
||||||
if (mutex)
|
if (mutex) {
|
||||||
memdelete(mutex);
|
memdelete(mutex);
|
||||||
|
mutex = NULL;
|
||||||
|
}
|
||||||
thread = NULL;
|
thread = NULL;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -254,6 +344,9 @@ AudioDriverALSA::AudioDriverALSA() {
|
||||||
mutex = NULL;
|
mutex = NULL;
|
||||||
thread = NULL;
|
thread = NULL;
|
||||||
pcm_handle = NULL;
|
pcm_handle = NULL;
|
||||||
|
|
||||||
|
device_name = "Default";
|
||||||
|
new_device = "Default";
|
||||||
};
|
};
|
||||||
|
|
||||||
AudioDriverALSA::~AudioDriverALSA(){
|
AudioDriverALSA::~AudioDriverALSA(){
|
||||||
|
|
|
@ -44,8 +44,14 @@ class AudioDriverALSA : public AudioDriver {
|
||||||
|
|
||||||
snd_pcm_t *pcm_handle;
|
snd_pcm_t *pcm_handle;
|
||||||
|
|
||||||
int32_t *samples_in;
|
String device_name;
|
||||||
int16_t *samples_out;
|
String new_device;
|
||||||
|
|
||||||
|
Vector<int32_t> samples_in;
|
||||||
|
Vector<int16_t> samples_out;
|
||||||
|
|
||||||
|
Error init_device();
|
||||||
|
void finish_device();
|
||||||
|
|
||||||
static void thread_func(void *p_udata);
|
static void thread_func(void *p_udata);
|
||||||
|
|
||||||
|
@ -71,6 +77,9 @@ public:
|
||||||
virtual void start();
|
virtual void start();
|
||||||
virtual int get_mix_rate() const;
|
virtual int get_mix_rate() const;
|
||||||
virtual SpeakerMode get_speaker_mode() const;
|
virtual SpeakerMode get_speaker_mode() const;
|
||||||
|
virtual Array get_device_list();
|
||||||
|
virtual String get_device();
|
||||||
|
virtual void set_device(String device);
|
||||||
virtual void lock();
|
virtual void lock();
|
||||||
virtual void unlock();
|
virtual void unlock();
|
||||||
virtual void finish();
|
virtual void finish();
|
||||||
|
|
|
@ -37,16 +37,22 @@
|
||||||
#define kOutputBus 0
|
#define kOutputBus 0
|
||||||
|
|
||||||
#ifdef OSX_ENABLED
|
#ifdef OSX_ENABLED
|
||||||
static OSStatus outputDeviceAddressCB(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData) {
|
OSStatus AudioDriverCoreAudio::output_device_address_cb(AudioObjectID inObjectID,
|
||||||
|
UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses,
|
||||||
|
void *inClientData) {
|
||||||
AudioDriverCoreAudio *driver = (AudioDriverCoreAudio *)inClientData;
|
AudioDriverCoreAudio *driver = (AudioDriverCoreAudio *)inClientData;
|
||||||
|
|
||||||
driver->reopen();
|
// If our selected device is the Default call set_device to update the
|
||||||
|
// kAudioOutputUnitProperty_CurrentDevice property
|
||||||
|
if (driver->device_name == "Default") {
|
||||||
|
driver->set_device("Default");
|
||||||
|
}
|
||||||
|
|
||||||
return noErr;
|
return noErr;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Error AudioDriverCoreAudio::initDevice() {
|
Error AudioDriverCoreAudio::init_device() {
|
||||||
AudioComponentDescription desc;
|
AudioComponentDescription desc;
|
||||||
zeromem(&desc, sizeof(desc));
|
zeromem(&desc, sizeof(desc));
|
||||||
desc.componentType = kAudioUnitType_Output;
|
desc.componentType = kAudioUnitType_Output;
|
||||||
|
@ -129,7 +135,7 @@ Error AudioDriverCoreAudio::initDevice() {
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error AudioDriverCoreAudio::finishDevice() {
|
Error AudioDriverCoreAudio::finish_device() {
|
||||||
OSStatus result;
|
OSStatus result;
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
|
@ -153,49 +159,18 @@ Error AudioDriverCoreAudio::init() {
|
||||||
channels = 2;
|
channels = 2;
|
||||||
|
|
||||||
#ifdef OSX_ENABLED
|
#ifdef OSX_ENABLED
|
||||||
outputDeviceAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
AudioObjectPropertyAddress prop;
|
||||||
outputDeviceAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
prop.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||||
outputDeviceAddress.mElement = kAudioObjectPropertyElementMaster;
|
prop.mScope = kAudioObjectPropertyScopeGlobal;
|
||||||
|
prop.mElement = kAudioObjectPropertyElementMaster;
|
||||||
|
|
||||||
result = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &outputDeviceAddress, &outputDeviceAddressCB, this);
|
result = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &prop, &output_device_address_cb, this);
|
||||||
ERR_FAIL_COND_V(result != noErr, FAILED);
|
ERR_FAIL_COND_V(result != noErr, FAILED);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return initDevice();
|
return init_device();
|
||||||
};
|
};
|
||||||
|
|
||||||
Error AudioDriverCoreAudio::reopen() {
|
|
||||||
bool restart = false;
|
|
||||||
|
|
||||||
lock();
|
|
||||||
|
|
||||||
if (active) {
|
|
||||||
restart = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Error err = finishDevice();
|
|
||||||
if (err != OK) {
|
|
||||||
ERR_PRINT("finishDevice failed");
|
|
||||||
unlock();
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = initDevice();
|
|
||||||
if (err != OK) {
|
|
||||||
ERR_PRINT("initDevice failed");
|
|
||||||
unlock();
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (restart) {
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
|
|
||||||
unlock();
|
|
||||||
|
|
||||||
return OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus AudioDriverCoreAudio::output_callback(void *inRefCon,
|
OSStatus AudioDriverCoreAudio::output_callback(void *inRefCon,
|
||||||
AudioUnitRenderActionFlags *ioActionFlags,
|
AudioUnitRenderActionFlags *ioActionFlags,
|
||||||
const AudioTimeStamp *inTimeStamp,
|
const AudioTimeStamp *inTimeStamp,
|
||||||
|
@ -257,6 +232,150 @@ AudioDriver::SpeakerMode AudioDriverCoreAudio::get_speaker_mode() const {
|
||||||
return get_speaker_mode_by_total_channels(channels);
|
return get_speaker_mode_by_total_channels(channels);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef OSX_ENABLED
|
||||||
|
|
||||||
|
Array AudioDriverCoreAudio::get_device_list() {
|
||||||
|
|
||||||
|
Array list;
|
||||||
|
|
||||||
|
list.push_back("Default");
|
||||||
|
|
||||||
|
AudioObjectPropertyAddress prop;
|
||||||
|
|
||||||
|
prop.mSelector = kAudioHardwarePropertyDevices;
|
||||||
|
prop.mScope = kAudioObjectPropertyScopeGlobal;
|
||||||
|
prop.mElement = kAudioObjectPropertyElementMaster;
|
||||||
|
|
||||||
|
UInt32 size = 0;
|
||||||
|
AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, NULL, &size);
|
||||||
|
AudioDeviceID *audioDevices = (AudioDeviceID *)malloc(size);
|
||||||
|
AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &size, audioDevices);
|
||||||
|
|
||||||
|
UInt32 deviceCount = size / sizeof(AudioDeviceID);
|
||||||
|
for (UInt32 i = 0; i < deviceCount; i++) {
|
||||||
|
prop.mScope = kAudioDevicePropertyScopeOutput;
|
||||||
|
prop.mSelector = kAudioDevicePropertyStreamConfiguration;
|
||||||
|
|
||||||
|
AudioObjectGetPropertyDataSize(audioDevices[i], &prop, 0, NULL, &size);
|
||||||
|
AudioBufferList *bufferList = (AudioBufferList *)malloc(size);
|
||||||
|
AudioObjectGetPropertyData(audioDevices[i], &prop, 0, NULL, &size, bufferList);
|
||||||
|
|
||||||
|
UInt32 outputChannelCount = 0;
|
||||||
|
for (UInt32 j = 0; j < bufferList->mNumberBuffers; j++)
|
||||||
|
outputChannelCount += bufferList->mBuffers[j].mNumberChannels;
|
||||||
|
|
||||||
|
free(bufferList);
|
||||||
|
|
||||||
|
if (outputChannelCount >= 1) {
|
||||||
|
CFStringRef cfname;
|
||||||
|
|
||||||
|
size = sizeof(CFStringRef);
|
||||||
|
prop.mSelector = kAudioObjectPropertyName;
|
||||||
|
|
||||||
|
AudioObjectGetPropertyData(audioDevices[i], &prop, 0, NULL, &size, &cfname);
|
||||||
|
|
||||||
|
CFIndex length = CFStringGetLength(cfname);
|
||||||
|
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
|
||||||
|
char *buffer = (char *)malloc(maxSize);
|
||||||
|
if (CFStringGetCString(cfname, buffer, maxSize, kCFStringEncodingUTF8)) {
|
||||||
|
// Append the ID to the name in case we have devices with duplicate name
|
||||||
|
list.push_back(String(buffer) + " (" + itos(audioDevices[i]) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(audioDevices);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AudioDriverCoreAudio::get_device() {
|
||||||
|
|
||||||
|
return device_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDriverCoreAudio::set_device(String device) {
|
||||||
|
|
||||||
|
device_name = device;
|
||||||
|
if (!active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioDeviceID deviceId;
|
||||||
|
bool found = false;
|
||||||
|
if (device_name != "Default") {
|
||||||
|
AudioObjectPropertyAddress prop;
|
||||||
|
|
||||||
|
prop.mSelector = kAudioHardwarePropertyDevices;
|
||||||
|
prop.mScope = kAudioObjectPropertyScopeGlobal;
|
||||||
|
prop.mElement = kAudioObjectPropertyElementMaster;
|
||||||
|
|
||||||
|
UInt32 size = 0;
|
||||||
|
AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, NULL, &size);
|
||||||
|
AudioDeviceID *audioDevices = (AudioDeviceID *)malloc(size);
|
||||||
|
AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &size, audioDevices);
|
||||||
|
|
||||||
|
UInt32 deviceCount = size / sizeof(AudioDeviceID);
|
||||||
|
for (UInt32 i = 0; i < deviceCount && !found; i++) {
|
||||||
|
prop.mScope = kAudioDevicePropertyScopeOutput;
|
||||||
|
prop.mSelector = kAudioDevicePropertyStreamConfiguration;
|
||||||
|
|
||||||
|
AudioObjectGetPropertyDataSize(audioDevices[i], &prop, 0, NULL, &size);
|
||||||
|
AudioBufferList *bufferList = (AudioBufferList *)malloc(size);
|
||||||
|
AudioObjectGetPropertyData(audioDevices[i], &prop, 0, NULL, &size, bufferList);
|
||||||
|
|
||||||
|
UInt32 outputChannelCount = 0;
|
||||||
|
for (UInt32 j = 0; j < bufferList->mNumberBuffers; j++)
|
||||||
|
outputChannelCount += bufferList->mBuffers[j].mNumberChannels;
|
||||||
|
|
||||||
|
free(bufferList);
|
||||||
|
|
||||||
|
if (outputChannelCount >= 1) {
|
||||||
|
CFStringRef cfname;
|
||||||
|
|
||||||
|
size = sizeof(CFStringRef);
|
||||||
|
prop.mSelector = kAudioObjectPropertyName;
|
||||||
|
|
||||||
|
AudioObjectGetPropertyData(audioDevices[i], &prop, 0, NULL, &size, &cfname);
|
||||||
|
|
||||||
|
CFIndex length = CFStringGetLength(cfname);
|
||||||
|
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
|
||||||
|
char *buffer = (char *)malloc(maxSize);
|
||||||
|
if (CFStringGetCString(cfname, buffer, maxSize, kCFStringEncodingUTF8)) {
|
||||||
|
String name = String(buffer) + " (" + itos(audioDevices[i]) + ")";
|
||||||
|
if (name == device_name) {
|
||||||
|
deviceId = audioDevices[i];
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(audioDevices);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
UInt32 size = sizeof(AudioDeviceID);
|
||||||
|
AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
|
||||||
|
|
||||||
|
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property, 0, NULL, &size, &deviceId);
|
||||||
|
ERR_FAIL_COND(result != noErr);
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
OSStatus result = AudioUnitSetProperty(audio_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &deviceId, sizeof(AudioDeviceID));
|
||||||
|
ERR_FAIL_COND(result != noErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
void AudioDriverCoreAudio::lock() {
|
void AudioDriverCoreAudio::lock() {
|
||||||
if (mutex)
|
if (mutex)
|
||||||
mutex->lock();
|
mutex->lock();
|
||||||
|
@ -276,10 +395,15 @@ bool AudioDriverCoreAudio::try_lock() {
|
||||||
void AudioDriverCoreAudio::finish() {
|
void AudioDriverCoreAudio::finish() {
|
||||||
OSStatus result;
|
OSStatus result;
|
||||||
|
|
||||||
finishDevice();
|
finish_device();
|
||||||
|
|
||||||
#ifdef OSX_ENABLED
|
#ifdef OSX_ENABLED
|
||||||
result = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &outputDeviceAddress, &outputDeviceAddressCB, this);
|
AudioObjectPropertyAddress prop;
|
||||||
|
prop.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||||
|
prop.mScope = kAudioObjectPropertyScopeGlobal;
|
||||||
|
prop.mElement = kAudioObjectPropertyElementMaster;
|
||||||
|
|
||||||
|
result = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &prop, &output_device_address_cb, this);
|
||||||
if (result != noErr) {
|
if (result != noErr) {
|
||||||
ERR_PRINT("AudioObjectRemovePropertyListener failed");
|
ERR_PRINT("AudioObjectRemovePropertyListener failed");
|
||||||
}
|
}
|
||||||
|
@ -309,6 +433,8 @@ AudioDriverCoreAudio::AudioDriverCoreAudio() {
|
||||||
buffer_frames = 0;
|
buffer_frames = 0;
|
||||||
|
|
||||||
samples_in.clear();
|
samples_in.clear();
|
||||||
|
|
||||||
|
device_name = "Default";
|
||||||
};
|
};
|
||||||
|
|
||||||
AudioDriverCoreAudio::~AudioDriverCoreAudio(){};
|
AudioDriverCoreAudio::~AudioDriverCoreAudio(){};
|
||||||
|
|
|
@ -43,12 +43,12 @@
|
||||||
class AudioDriverCoreAudio : public AudioDriver {
|
class AudioDriverCoreAudio : public AudioDriver {
|
||||||
|
|
||||||
AudioComponentInstance audio_unit;
|
AudioComponentInstance audio_unit;
|
||||||
#ifdef OSX_ENABLED
|
|
||||||
AudioObjectPropertyAddress outputDeviceAddress;
|
|
||||||
#endif
|
|
||||||
bool active;
|
bool active;
|
||||||
Mutex *mutex;
|
Mutex *mutex;
|
||||||
|
|
||||||
|
String device_name;
|
||||||
|
|
||||||
int mix_rate;
|
int mix_rate;
|
||||||
unsigned int channels;
|
unsigned int channels;
|
||||||
unsigned int buffer_frames;
|
unsigned int buffer_frames;
|
||||||
|
@ -56,14 +56,18 @@ class AudioDriverCoreAudio : public AudioDriver {
|
||||||
|
|
||||||
Vector<int32_t> samples_in;
|
Vector<int32_t> samples_in;
|
||||||
|
|
||||||
|
static OSStatus output_device_address_cb(AudioObjectID inObjectID,
|
||||||
|
UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses,
|
||||||
|
void *inClientData);
|
||||||
|
|
||||||
static OSStatus output_callback(void *inRefCon,
|
static OSStatus output_callback(void *inRefCon,
|
||||||
AudioUnitRenderActionFlags *ioActionFlags,
|
AudioUnitRenderActionFlags *ioActionFlags,
|
||||||
const AudioTimeStamp *inTimeStamp,
|
const AudioTimeStamp *inTimeStamp,
|
||||||
UInt32 inBusNumber, UInt32 inNumberFrames,
|
UInt32 inBusNumber, UInt32 inNumberFrames,
|
||||||
AudioBufferList *ioData);
|
AudioBufferList *ioData);
|
||||||
|
|
||||||
Error initDevice();
|
Error init_device();
|
||||||
Error finishDevice();
|
Error finish_device();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const char *get_name() const {
|
const char *get_name() const {
|
||||||
|
@ -74,12 +78,16 @@ public:
|
||||||
virtual void start();
|
virtual void start();
|
||||||
virtual int get_mix_rate() const;
|
virtual int get_mix_rate() const;
|
||||||
virtual SpeakerMode get_speaker_mode() const;
|
virtual SpeakerMode get_speaker_mode() const;
|
||||||
|
#ifdef OSX_ENABLED
|
||||||
|
virtual Array get_device_list();
|
||||||
|
virtual String get_device();
|
||||||
|
virtual void set_device(String device);
|
||||||
|
#endif
|
||||||
virtual void lock();
|
virtual void lock();
|
||||||
virtual void unlock();
|
virtual void unlock();
|
||||||
virtual void finish();
|
virtual void finish();
|
||||||
|
|
||||||
bool try_lock();
|
bool try_lock();
|
||||||
Error reopen();
|
|
||||||
|
|
||||||
AudioDriverCoreAudio();
|
AudioDriverCoreAudio();
|
||||||
~AudioDriverCoreAudio();
|
~AudioDriverCoreAudio();
|
||||||
|
|
|
@ -37,98 +37,51 @@
|
||||||
#include "os/os.h"
|
#include "os/os.h"
|
||||||
#include "project_settings.h"
|
#include "project_settings.h"
|
||||||
|
|
||||||
void pa_state_cb(pa_context *c, void *userdata) {
|
void AudioDriverPulseAudio::pa_state_cb(pa_context *c, void *userdata) {
|
||||||
pa_context_state_t state;
|
AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata;
|
||||||
int *pa_ready = (int *)userdata;
|
|
||||||
|
|
||||||
state = pa_context_get_state(c);
|
switch (pa_context_get_state(c)) {
|
||||||
switch (state) {
|
|
||||||
case PA_CONTEXT_FAILED:
|
|
||||||
case PA_CONTEXT_TERMINATED:
|
case PA_CONTEXT_TERMINATED:
|
||||||
*pa_ready = 2;
|
case PA_CONTEXT_FAILED:
|
||||||
|
ad->pa_ready = -1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PA_CONTEXT_READY:
|
case PA_CONTEXT_READY:
|
||||||
*pa_ready = 1;
|
ad->pa_ready = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sink_info_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) {
|
void AudioDriverPulseAudio::pa_sink_info_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) {
|
||||||
unsigned int *channels = (unsigned int *)userdata;
|
AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata;
|
||||||
|
|
||||||
// If eol is set to a positive number, you're at the end of the list
|
// If eol is set to a positive number, you're at the end of the list
|
||||||
if (eol > 0) {
|
if (eol > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
*channels = l->channel_map.channels;
|
ad->pa_channels = l->channel_map.channels;
|
||||||
|
ad->pa_status++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void server_info_cb(pa_context *c, const pa_server_info *i, void *userdata) {
|
void AudioDriverPulseAudio::pa_server_info_cb(pa_context *c, const pa_server_info *i, void *userdata) {
|
||||||
char *default_output = (char *)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_channels = 2;
|
||||||
pa_mainloop_api *pa_mlapi;
|
|
||||||
pa_operation *pa_op;
|
|
||||||
pa_context *pa_ctx;
|
|
||||||
|
|
||||||
int state = 0;
|
if (device_name == "Default") {
|
||||||
int pa_ready = 0;
|
// Get the default output device name
|
||||||
|
pa_status = 0;
|
||||||
char default_output[1024];
|
pa_operation *pa_op = pa_context_get_server_info(pa_ctx, &AudioDriverPulseAudio::pa_server_info_cb, (void *)this);
|
||||||
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 (pa_op) {
|
if (pa_op) {
|
||||||
while (pa_operation_get_state(pa_op) == PA_OPERATION_RUNNING) {
|
while (pa_status == 0) {
|
||||||
ret = pa_mainloop_iterate(pa_ml, 1, NULL);
|
int ret = pa_mainloop_iterate(pa_ml, 1, NULL);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
ERR_PRINT("pa_mainloop_iterate error");
|
ERR_PRINT("pa_mainloop_iterate error");
|
||||||
}
|
}
|
||||||
|
@ -136,32 +89,50 @@ static unsigned int detect_channels() {
|
||||||
|
|
||||||
pa_operation_unref(pa_op);
|
pa_operation_unref(pa_op);
|
||||||
} else {
|
} 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);
|
char device[1024];
|
||||||
pa_context_unref(pa_ctx);
|
if (device_name == "Default") {
|
||||||
pa_mainloop_free(pa_ml);
|
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;
|
// If there is a specified device check that it is really present
|
||||||
thread_exited = false;
|
if (device_name != "Default") {
|
||||||
exit_thread = false;
|
Array list = get_device_list();
|
||||||
|
if (list.find(device_name) == -1) {
|
||||||
mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE);
|
device_name = "Default";
|
||||||
|
new_device = "Default";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Detect the amount of channels PulseAudio is using
|
// Detect the amount of channels PulseAudio is using
|
||||||
// Note: If using an even amount of channels (2, 4, etc) channels and pa_channels will be equal,
|
// Note: If using an even amount of channels (2, 4, etc) channels and pa_channels will be equal,
|
||||||
// if not then pa_channels will have the real amount of channels PulseAudio is using and channels
|
// if not then pa_channels will have the real amount of channels PulseAudio is using and channels
|
||||||
// will have the amount of channels Godot is using (in this case it's pa_channels + 1)
|
// will have the amount of channels Godot is using (in this case it's pa_channels + 1)
|
||||||
pa_channels = detect_channels();
|
detect_channels();
|
||||||
switch (pa_channels) {
|
switch (pa_channels) {
|
||||||
case 1: // Mono
|
case 1: // Mono
|
||||||
case 3: // Surround 2.1
|
case 3: // Surround 2.1
|
||||||
|
@ -205,27 +176,76 @@ Error AudioDriverPulseAudio::init() {
|
||||||
attr.maxlength = (uint32_t)-1;
|
attr.maxlength = (uint32_t)-1;
|
||||||
attr.minreq = (uint32_t)-1;
|
attr.minreq = (uint32_t)-1;
|
||||||
|
|
||||||
int error_code;
|
pa_str = pa_stream_new(pa_ctx, "Sound", &spec, NULL);
|
||||||
pulse = pa_simple_new(NULL, // default server
|
ERR_FAIL_COND_V(pa_ctx == NULL, ERR_CANT_OPEN);
|
||||||
"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) {
|
const char *dev = device_name == "Default" ? NULL : device_name.utf8().get_data();
|
||||||
fprintf(stderr, "PulseAudio ERR: %s\n", pa_strerror(error_code));
|
pa_stream_flags flags = pa_stream_flags(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE);
|
||||||
ERR_FAIL_COND_V(pulse == NULL, ERR_CANT_OPEN);
|
int error_code = pa_stream_connect_playback(pa_str, dev, &attr, flags, NULL, NULL);
|
||||||
}
|
ERR_FAIL_COND_V(error_code < 0, ERR_CANT_OPEN);
|
||||||
|
|
||||||
samples_in.resize(buffer_frames * channels);
|
samples_in.resize(buffer_frames * channels);
|
||||||
samples_out.resize(pa_buffer_size);
|
samples_out.resize(pa_buffer_size);
|
||||||
|
|
||||||
mutex = Mutex::create();
|
return OK;
|
||||||
thread = Thread::create(AudioDriverPulseAudio::thread_func, this);
|
}
|
||||||
|
|
||||||
|
Error AudioDriverPulseAudio::init() {
|
||||||
|
|
||||||
|
active = false;
|
||||||
|
thread_exited = false;
|
||||||
|
exit_thread = false;
|
||||||
|
|
||||||
|
mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE);
|
||||||
|
|
||||||
|
pa_ml = pa_mainloop_new();
|
||||||
|
ERR_FAIL_COND_V(pa_ml == NULL, ERR_CANT_OPEN);
|
||||||
|
|
||||||
|
pa_ctx = pa_context_new(pa_mainloop_get_api(pa_ml), "Godot");
|
||||||
|
ERR_FAIL_COND_V(pa_ctx == NULL, ERR_CANT_OPEN);
|
||||||
|
|
||||||
|
pa_ready = 0;
|
||||||
|
pa_context_set_state_callback(pa_ctx, pa_state_cb, (void *)this);
|
||||||
|
|
||||||
|
int ret = pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (pa_ctx) {
|
||||||
|
pa_context_unref(pa_ctx);
|
||||||
|
pa_ctx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pa_ml) {
|
||||||
|
pa_mainloop_free(pa_ml);
|
||||||
|
pa_ml = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERR_CANT_OPEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (pa_ready == 0) {
|
||||||
|
pa_mainloop_iterate(pa_ml, 1, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pa_ready < 0) {
|
||||||
|
if (pa_ctx) {
|
||||||
|
pa_context_disconnect(pa_ctx);
|
||||||
|
pa_context_unref(pa_ctx);
|
||||||
|
pa_ctx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pa_ml) {
|
||||||
|
pa_mainloop_free(pa_ml);
|
||||||
|
pa_ml = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERR_CANT_OPEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error err = init_device();
|
||||||
|
if (err == OK) {
|
||||||
|
mutex = Mutex::create();
|
||||||
|
thread = Thread::create(AudioDriverPulseAudio::thread_func, this);
|
||||||
|
}
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
@ -233,9 +253,24 @@ Error AudioDriverPulseAudio::init() {
|
||||||
float AudioDriverPulseAudio::get_latency() {
|
float AudioDriverPulseAudio::get_latency() {
|
||||||
|
|
||||||
if (latency == 0) { //only do this once since it's approximate anyway
|
if (latency == 0) { //only do this once since it's approximate anyway
|
||||||
int error_code;
|
lock();
|
||||||
pa_usec_t palat = pa_simple_get_latency(pulse, &error_code);
|
|
||||||
latency = double(palat) / 1000000.0;
|
pa_usec_t palat = 0;
|
||||||
|
if (pa_stream_get_state(pa_str) == PA_STREAM_READY) {
|
||||||
|
int negative = 0;
|
||||||
|
|
||||||
|
if (pa_stream_get_latency(pa_str, &palat, &negative) >= 0) {
|
||||||
|
if (negative) {
|
||||||
|
palat = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (palat > 0) {
|
||||||
|
latency = double(palat) / 1000000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
return latency;
|
return latency;
|
||||||
|
@ -278,17 +313,57 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pa_simple_write always consumes the entire buffer
|
|
||||||
|
|
||||||
int error_code;
|
int error_code;
|
||||||
int byte_size = ad->pa_buffer_size * sizeof(int16_t);
|
int byte_size = ad->pa_buffer_size * sizeof(int16_t);
|
||||||
if (pa_simple_write(ad->pulse, ad->samples_out.ptr(), byte_size, &error_code) < 0) {
|
|
||||||
// can't recover here
|
ad->lock();
|
||||||
fprintf(stderr, "PulseAudio failed and can't recover: %s\n", pa_strerror(error_code));
|
|
||||||
ad->active = false;
|
int ret;
|
||||||
ad->exit_thread = true;
|
do {
|
||||||
break;
|
ret = pa_mainloop_iterate(ad->pa_ml, 0, NULL);
|
||||||
|
} while (ret > 0);
|
||||||
|
|
||||||
|
if (pa_stream_get_state(ad->pa_str) == PA_STREAM_READY) {
|
||||||
|
const void *ptr = ad->samples_out.ptr();
|
||||||
|
while (byte_size > 0) {
|
||||||
|
size_t bytes = pa_stream_writable_size(ad->pa_str);
|
||||||
|
if (bytes > 0) {
|
||||||
|
if (bytes > byte_size) {
|
||||||
|
bytes = byte_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = pa_stream_write(ad->pa_str, ptr, bytes, NULL, 0LL, PA_SEEK_RELATIVE);
|
||||||
|
if (ret >= 0) {
|
||||||
|
byte_size -= bytes;
|
||||||
|
ptr = (const char *)ptr + bytes;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pa_mainloop_iterate(ad->pa_ml, 1, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User selected a new device, finish the current one so we'll init the new device
|
||||||
|
if (ad->device_name != ad->new_device) {
|
||||||
|
ad->device_name = ad->new_device;
|
||||||
|
ad->finish_device();
|
||||||
|
|
||||||
|
Error err = ad->init_device();
|
||||||
|
if (err != OK) {
|
||||||
|
ERR_PRINT("PulseAudio: init_device error");
|
||||||
|
ad->device_name = "Default";
|
||||||
|
ad->new_device = "Default";
|
||||||
|
|
||||||
|
err = ad->init_device();
|
||||||
|
if (err != OK) {
|
||||||
|
ad->active = false;
|
||||||
|
ad->exit_thread = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ad->unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
ad->thread_exited = true;
|
ad->thread_exited = true;
|
||||||
|
@ -309,6 +384,61 @@ AudioDriver::SpeakerMode AudioDriverPulseAudio::get_speaker_mode() const {
|
||||||
return get_speaker_mode_by_total_channels(channels);
|
return get_speaker_mode_by_total_channels(channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioDriverPulseAudio::pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) {
|
||||||
|
AudioDriverPulseAudio *ad = (AudioDriverPulseAudio *)userdata;
|
||||||
|
int ctr = 0;
|
||||||
|
|
||||||
|
// If eol is set to a positive number, you're at the end of the list
|
||||||
|
if (eol > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ad->pa_devices.push_back(l->name);
|
||||||
|
ad->pa_status++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array AudioDriverPulseAudio::get_device_list() {
|
||||||
|
|
||||||
|
pa_devices.clear();
|
||||||
|
pa_devices.push_back("Default");
|
||||||
|
|
||||||
|
if (pa_ctx == NULL) {
|
||||||
|
return pa_devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock();
|
||||||
|
|
||||||
|
// Get the device list
|
||||||
|
pa_status = 0;
|
||||||
|
pa_operation *pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, (void *)this);
|
||||||
|
if (pa_op) {
|
||||||
|
while (pa_status == 0) {
|
||||||
|
int ret = pa_mainloop_iterate(pa_ml, 1, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
ERR_PRINT("pa_mainloop_iterate error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pa_operation_unref(pa_op);
|
||||||
|
} else {
|
||||||
|
ERR_PRINT("pa_context_get_server_info error");
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock();
|
||||||
|
|
||||||
|
return pa_devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AudioDriverPulseAudio::get_device() {
|
||||||
|
|
||||||
|
return device_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDriverPulseAudio::set_device(String device) {
|
||||||
|
|
||||||
|
new_device = device;
|
||||||
|
}
|
||||||
|
|
||||||
void AudioDriverPulseAudio::lock() {
|
void AudioDriverPulseAudio::lock() {
|
||||||
|
|
||||||
if (!thread || !mutex)
|
if (!thread || !mutex)
|
||||||
|
@ -323,6 +453,15 @@ void AudioDriverPulseAudio::unlock() {
|
||||||
mutex->unlock();
|
mutex->unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioDriverPulseAudio::finish_device() {
|
||||||
|
|
||||||
|
if (pa_str) {
|
||||||
|
pa_stream_disconnect(pa_str);
|
||||||
|
pa_stream_unref(pa_str);
|
||||||
|
pa_str = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AudioDriverPulseAudio::finish() {
|
void AudioDriverPulseAudio::finish() {
|
||||||
|
|
||||||
if (!thread)
|
if (!thread)
|
||||||
|
@ -331,9 +470,17 @@ void AudioDriverPulseAudio::finish() {
|
||||||
exit_thread = true;
|
exit_thread = true;
|
||||||
Thread::wait_to_finish(thread);
|
Thread::wait_to_finish(thread);
|
||||||
|
|
||||||
if (pulse) {
|
finish_device();
|
||||||
pa_simple_free(pulse);
|
|
||||||
pulse = NULL;
|
if (pa_ctx) {
|
||||||
|
pa_context_disconnect(pa_ctx);
|
||||||
|
pa_context_unref(pa_ctx);
|
||||||
|
pa_ctx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pa_ml) {
|
||||||
|
pa_mainloop_free(pa_ml);
|
||||||
|
pa_ml = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
memdelete(thread);
|
memdelete(thread);
|
||||||
|
@ -347,9 +494,16 @@ void AudioDriverPulseAudio::finish() {
|
||||||
|
|
||||||
AudioDriverPulseAudio::AudioDriverPulseAudio() {
|
AudioDriverPulseAudio::AudioDriverPulseAudio() {
|
||||||
|
|
||||||
|
pa_ml = NULL;
|
||||||
|
pa_ctx = NULL;
|
||||||
|
pa_str = NULL;
|
||||||
|
|
||||||
mutex = NULL;
|
mutex = NULL;
|
||||||
thread = NULL;
|
thread = NULL;
|
||||||
pulse = NULL;
|
|
||||||
|
device_name = "Default";
|
||||||
|
new_device = "Default";
|
||||||
|
default_device = "";
|
||||||
|
|
||||||
samples_in.clear();
|
samples_in.clear();
|
||||||
samples_out.clear();
|
samples_out.clear();
|
||||||
|
@ -359,6 +513,8 @@ AudioDriverPulseAudio::AudioDriverPulseAudio() {
|
||||||
pa_buffer_size = 0;
|
pa_buffer_size = 0;
|
||||||
channels = 0;
|
channels = 0;
|
||||||
pa_channels = 0;
|
pa_channels = 0;
|
||||||
|
pa_ready = 0;
|
||||||
|
pa_status = 0;
|
||||||
|
|
||||||
active = false;
|
active = false;
|
||||||
thread_exited = false;
|
thread_exited = false;
|
||||||
|
|
|
@ -37,14 +37,20 @@
|
||||||
#include "core/os/thread.h"
|
#include "core/os/thread.h"
|
||||||
#include "servers/audio_server.h"
|
#include "servers/audio_server.h"
|
||||||
|
|
||||||
#include <pulse/simple.h>
|
#include <pulse/pulseaudio.h>
|
||||||
|
|
||||||
class AudioDriverPulseAudio : public AudioDriver {
|
class AudioDriverPulseAudio : public AudioDriver {
|
||||||
|
|
||||||
Thread *thread;
|
Thread *thread;
|
||||||
Mutex *mutex;
|
Mutex *mutex;
|
||||||
|
|
||||||
pa_simple *pulse;
|
pa_mainloop *pa_ml;
|
||||||
|
pa_context *pa_ctx;
|
||||||
|
pa_stream *pa_str;
|
||||||
|
|
||||||
|
String device_name;
|
||||||
|
String new_device;
|
||||||
|
String default_device;
|
||||||
|
|
||||||
Vector<int32_t> samples_in;
|
Vector<int32_t> samples_in;
|
||||||
Vector<int16_t> samples_out;
|
Vector<int16_t> samples_out;
|
||||||
|
@ -54,6 +60,9 @@ class AudioDriverPulseAudio : public AudioDriver {
|
||||||
unsigned int pa_buffer_size;
|
unsigned int pa_buffer_size;
|
||||||
int channels;
|
int channels;
|
||||||
int pa_channels;
|
int pa_channels;
|
||||||
|
int pa_ready;
|
||||||
|
int pa_status;
|
||||||
|
Array pa_devices;
|
||||||
|
|
||||||
bool active;
|
bool active;
|
||||||
bool thread_exited;
|
bool thread_exited;
|
||||||
|
@ -61,6 +70,16 @@ class AudioDriverPulseAudio : public AudioDriver {
|
||||||
|
|
||||||
float latency;
|
float latency;
|
||||||
|
|
||||||
|
static void pa_state_cb(pa_context *c, void *userdata);
|
||||||
|
static void pa_sink_info_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata);
|
||||||
|
static void pa_server_info_cb(pa_context *c, const pa_server_info *i, void *userdata);
|
||||||
|
static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata);
|
||||||
|
|
||||||
|
Error init_device();
|
||||||
|
void finish_device();
|
||||||
|
|
||||||
|
void detect_channels();
|
||||||
|
|
||||||
static void thread_func(void *p_udata);
|
static void thread_func(void *p_udata);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -72,6 +91,9 @@ public:
|
||||||
virtual void start();
|
virtual void start();
|
||||||
virtual int get_mix_rate() const;
|
virtual int get_mix_rate() const;
|
||||||
virtual SpeakerMode get_speaker_mode() const;
|
virtual SpeakerMode get_speaker_mode() const;
|
||||||
|
virtual Array get_device_list();
|
||||||
|
virtual String get_device();
|
||||||
|
virtual void set_device(String device);
|
||||||
virtual void lock();
|
virtual void lock();
|
||||||
virtual void unlock();
|
virtual void unlock();
|
||||||
virtual void finish();
|
virtual void finish();
|
||||||
|
|
|
@ -35,6 +35,8 @@
|
||||||
#include "os/os.h"
|
#include "os/os.h"
|
||||||
#include "project_settings.h"
|
#include "project_settings.h"
|
||||||
|
|
||||||
|
#include <functiondiscoverykeys.h>
|
||||||
|
|
||||||
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
||||||
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
||||||
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
||||||
|
@ -121,7 +123,61 @@ Error AudioDriverWASAPI::init_device(bool reinit) {
|
||||||
HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator);
|
HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator);
|
||||||
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
|
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
|
||||||
|
|
||||||
hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
|
if (device_name == "Default") {
|
||||||
|
hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
|
||||||
|
} else {
|
||||||
|
IMMDeviceCollection *devices = NULL;
|
||||||
|
|
||||||
|
hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices);
|
||||||
|
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
|
||||||
|
|
||||||
|
LPWSTR strId = NULL;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
UINT count = 0;
|
||||||
|
hr = devices->GetCount(&count);
|
||||||
|
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
|
||||||
|
|
||||||
|
for (ULONG i = 0; i < count && !found; i++) {
|
||||||
|
IMMDevice *device = NULL;
|
||||||
|
|
||||||
|
hr = devices->Item(i, &device);
|
||||||
|
ERR_BREAK(hr != S_OK);
|
||||||
|
|
||||||
|
IPropertyStore *props = NULL;
|
||||||
|
hr = device->OpenPropertyStore(STGM_READ, &props);
|
||||||
|
ERR_BREAK(hr != S_OK);
|
||||||
|
|
||||||
|
PROPVARIANT propvar;
|
||||||
|
PropVariantInit(&propvar);
|
||||||
|
|
||||||
|
hr = props->GetValue(PKEY_Device_FriendlyName, &propvar);
|
||||||
|
ERR_BREAK(hr != S_OK);
|
||||||
|
|
||||||
|
if (device_name == String(propvar.pwszVal)) {
|
||||||
|
hr = device->GetId(&strId);
|
||||||
|
ERR_BREAK(hr != S_OK);
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropVariantClear(&propvar);
|
||||||
|
props->Release();
|
||||||
|
device->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
hr = enumerator->GetDevice(strId, &device);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strId) {
|
||||||
|
CoTaskMemFree(strId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device == NULL) {
|
||||||
|
hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (reinit) {
|
if (reinit) {
|
||||||
// In case we're trying to re-initialize the device prevent throwing this error on the console,
|
// In case we're trying to re-initialize the device prevent throwing this error on the console,
|
||||||
// otherwise if there is currently no device available this will spam the console.
|
// otherwise if there is currently no device available this will spam the console.
|
||||||
|
@ -294,6 +350,64 @@ AudioDriver::SpeakerMode AudioDriverWASAPI::get_speaker_mode() const {
|
||||||
return get_speaker_mode_by_total_channels(channels);
|
return get_speaker_mode_by_total_channels(channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Array AudioDriverWASAPI::get_device_list() {
|
||||||
|
|
||||||
|
Array list;
|
||||||
|
IMMDeviceCollection *devices = NULL;
|
||||||
|
IMMDeviceEnumerator *enumerator = NULL;
|
||||||
|
|
||||||
|
list.push_back(String("Default"));
|
||||||
|
|
||||||
|
CoInitialize(NULL);
|
||||||
|
|
||||||
|
HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator);
|
||||||
|
ERR_FAIL_COND_V(hr != S_OK, Array());
|
||||||
|
|
||||||
|
hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices);
|
||||||
|
ERR_FAIL_COND_V(hr != S_OK, Array());
|
||||||
|
|
||||||
|
UINT count = 0;
|
||||||
|
hr = devices->GetCount(&count);
|
||||||
|
ERR_FAIL_COND_V(hr != S_OK, Array());
|
||||||
|
|
||||||
|
for (ULONG i = 0; i < count; i++) {
|
||||||
|
IMMDevice *device = NULL;
|
||||||
|
|
||||||
|
hr = devices->Item(i, &device);
|
||||||
|
ERR_BREAK(hr != S_OK);
|
||||||
|
|
||||||
|
IPropertyStore *props = NULL;
|
||||||
|
hr = device->OpenPropertyStore(STGM_READ, &props);
|
||||||
|
ERR_BREAK(hr != S_OK);
|
||||||
|
|
||||||
|
PROPVARIANT propvar;
|
||||||
|
PropVariantInit(&propvar);
|
||||||
|
|
||||||
|
hr = props->GetValue(PKEY_Device_FriendlyName, &propvar);
|
||||||
|
ERR_BREAK(hr != S_OK);
|
||||||
|
|
||||||
|
list.push_back(String(propvar.pwszVal));
|
||||||
|
|
||||||
|
PropVariantClear(&propvar);
|
||||||
|
props->Release();
|
||||||
|
device->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
devices->Release();
|
||||||
|
enumerator->Release();
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AudioDriverWASAPI::get_device() {
|
||||||
|
|
||||||
|
return device_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDriverWASAPI::set_device(String device) {
|
||||||
|
|
||||||
|
new_device = device;
|
||||||
|
}
|
||||||
|
|
||||||
void AudioDriverWASAPI::write_sample(AudioDriverWASAPI *ad, BYTE *buffer, int i, int32_t sample) {
|
void AudioDriverWASAPI::write_sample(AudioDriverWASAPI *ad, BYTE *buffer, int i, int32_t sample) {
|
||||||
if (ad->format_tag == WAVE_FORMAT_PCM) {
|
if (ad->format_tag == WAVE_FORMAT_PCM) {
|
||||||
switch (ad->bits_per_sample) {
|
switch (ad->bits_per_sample) {
|
||||||
|
@ -409,7 +523,8 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (default_device_changed) {
|
// If we're using the Default device and it changed finish it so we'll re-init the device
|
||||||
|
if (ad->device_name == "Default" && default_device_changed) {
|
||||||
Error err = ad->finish_device();
|
Error err = ad->finish_device();
|
||||||
if (err != OK) {
|
if (err != OK) {
|
||||||
ERR_PRINT("WASAPI: finish_device error");
|
ERR_PRINT("WASAPI: finish_device error");
|
||||||
|
@ -418,6 +533,15 @@ void AudioDriverWASAPI::thread_func(void *p_udata) {
|
||||||
default_device_changed = false;
|
default_device_changed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User selected a new device, finish the current one so we'll init the new device
|
||||||
|
if (ad->device_name != ad->new_device) {
|
||||||
|
ad->device_name = ad->new_device;
|
||||||
|
Error err = ad->finish_device();
|
||||||
|
if (err != OK) {
|
||||||
|
ERR_PRINT("WASAPI: finish_device error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!ad->audio_client) {
|
if (!ad->audio_client) {
|
||||||
Error err = ad->init_device(true);
|
Error err = ad->init_device(true);
|
||||||
if (err == OK) {
|
if (err == OK) {
|
||||||
|
@ -492,6 +616,9 @@ AudioDriverWASAPI::AudioDriverWASAPI() {
|
||||||
thread_exited = false;
|
thread_exited = false;
|
||||||
exit_thread = false;
|
exit_thread = false;
|
||||||
active = false;
|
active = false;
|
||||||
|
|
||||||
|
device_name = "Default";
|
||||||
|
new_device = "Default";
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -49,6 +49,9 @@ class AudioDriverWASAPI : public AudioDriver {
|
||||||
Mutex *mutex;
|
Mutex *mutex;
|
||||||
Thread *thread;
|
Thread *thread;
|
||||||
|
|
||||||
|
String device_name;
|
||||||
|
String new_device;
|
||||||
|
|
||||||
WORD format_tag;
|
WORD format_tag;
|
||||||
WORD bits_per_sample;
|
WORD bits_per_sample;
|
||||||
|
|
||||||
|
@ -80,6 +83,9 @@ public:
|
||||||
virtual void start();
|
virtual void start();
|
||||||
virtual int get_mix_rate() const;
|
virtual int get_mix_rate() const;
|
||||||
virtual SpeakerMode get_speaker_mode() const;
|
virtual SpeakerMode get_speaker_mode() const;
|
||||||
|
virtual Array get_device_list();
|
||||||
|
virtual String get_device();
|
||||||
|
virtual void set_device(String device);
|
||||||
virtual void lock();
|
virtual void lock();
|
||||||
virtual void unlock();
|
virtual void unlock();
|
||||||
virtual void finish();
|
virtual void finish();
|
||||||
|
|
|
@ -234,10 +234,10 @@ def configure(env):
|
||||||
print("ALSA libraries not found, disabling driver")
|
print("ALSA libraries not found, disabling driver")
|
||||||
|
|
||||||
if env['pulseaudio']:
|
if env['pulseaudio']:
|
||||||
if (os.system("pkg-config --exists libpulse-simple") == 0): # 0 means found
|
if (os.system("pkg-config --exists libpulse") == 0): # 0 means found
|
||||||
print("Enabling PulseAudio")
|
print("Enabling PulseAudio")
|
||||||
env.Append(CPPFLAGS=["-DPULSEAUDIO_ENABLED"])
|
env.Append(CPPFLAGS=["-DPULSEAUDIO_ENABLED"])
|
||||||
env.ParseConfig('pkg-config --cflags --libs libpulse-simple')
|
env.ParseConfig('pkg-config --cflags --libs libpulse')
|
||||||
else:
|
else:
|
||||||
print("PulseAudio development libraries not found, disabling driver")
|
print("PulseAudio development libraries not found, disabling driver")
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,18 @@ int AudioDriver::get_total_channels_by_speaker_mode(AudioDriver::SpeakerMode p_m
|
||||||
ERR_FAIL_V(2);
|
ERR_FAIL_V(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Array AudioDriver::get_device_list() {
|
||||||
|
Array list;
|
||||||
|
|
||||||
|
list.push_back("Default");
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AudioDriver::get_device() {
|
||||||
|
return "Default";
|
||||||
|
}
|
||||||
|
|
||||||
AudioDriver::AudioDriver() {
|
AudioDriver::AudioDriver() {
|
||||||
|
|
||||||
_last_mix_time = 0;
|
_last_mix_time = 0;
|
||||||
|
@ -1108,6 +1120,21 @@ Ref<AudioBusLayout> AudioServer::generate_bus_layout() const {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Array AudioServer::get_device_list() {
|
||||||
|
|
||||||
|
return AudioDriver::get_singleton()->get_device_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
String AudioServer::get_device() {
|
||||||
|
|
||||||
|
return AudioDriver::get_singleton()->get_device();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioServer::set_device(String device) {
|
||||||
|
|
||||||
|
AudioDriver::get_singleton()->set_device(device);
|
||||||
|
}
|
||||||
|
|
||||||
void AudioServer::_bind_methods() {
|
void AudioServer::_bind_methods() {
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("set_bus_count", "amount"), &AudioServer::set_bus_count);
|
ClassDB::bind_method(D_METHOD("set_bus_count", "amount"), &AudioServer::set_bus_count);
|
||||||
|
@ -1154,6 +1181,9 @@ void AudioServer::_bind_methods() {
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("get_speaker_mode"), &AudioServer::get_speaker_mode);
|
ClassDB::bind_method(D_METHOD("get_speaker_mode"), &AudioServer::get_speaker_mode);
|
||||||
ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioServer::get_mix_rate);
|
ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioServer::get_mix_rate);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_device_list"), &AudioServer::get_device_list);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_device"), &AudioServer::get_device);
|
||||||
|
ClassDB::bind_method(D_METHOD("set_device"), &AudioServer::set_device);
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("set_bus_layout", "bus_layout"), &AudioServer::set_bus_layout);
|
ClassDB::bind_method(D_METHOD("set_bus_layout", "bus_layout"), &AudioServer::set_bus_layout);
|
||||||
ClassDB::bind_method(D_METHOD("generate_bus_layout"), &AudioServer::generate_bus_layout);
|
ClassDB::bind_method(D_METHOD("generate_bus_layout"), &AudioServer::generate_bus_layout);
|
||||||
|
|
|
@ -70,6 +70,9 @@ public:
|
||||||
virtual void start() = 0;
|
virtual void start() = 0;
|
||||||
virtual int get_mix_rate() const = 0;
|
virtual int get_mix_rate() const = 0;
|
||||||
virtual SpeakerMode get_speaker_mode() const = 0;
|
virtual SpeakerMode get_speaker_mode() const = 0;
|
||||||
|
virtual Array get_device_list();
|
||||||
|
virtual String get_device();
|
||||||
|
virtual void set_device(String device) {}
|
||||||
virtual void lock() = 0;
|
virtual void lock() = 0;
|
||||||
virtual void unlock() = 0;
|
virtual void unlock() = 0;
|
||||||
virtual void finish() = 0;
|
virtual void finish() = 0;
|
||||||
|
@ -300,6 +303,10 @@ public:
|
||||||
void set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout);
|
void set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout);
|
||||||
Ref<AudioBusLayout> generate_bus_layout() const;
|
Ref<AudioBusLayout> generate_bus_layout() const;
|
||||||
|
|
||||||
|
Array get_device_list();
|
||||||
|
String get_device();
|
||||||
|
void set_device(String device);
|
||||||
|
|
||||||
AudioServer();
|
AudioServer();
|
||||||
virtual ~AudioServer();
|
virtual ~AudioServer();
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue