|
@@ -31,23 +31,144 @@
|
|
|
|
|
|
#ifdef PULSEAUDIO_ENABLED
|
|
|
|
|
|
-#include <pulse/error.h>
|
|
|
+#include <pulse/pulseaudio.h>
|
|
|
|
|
|
#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;
|
|
|
+
|
|
|
+ state = pa_context_get_state(c);
|
|
|
+ switch (state) {
|
|
|
+ case PA_CONTEXT_FAILED:
|
|
|
+ case PA_CONTEXT_TERMINATED:
|
|
|
+ *pa_ready = 2;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PA_CONTEXT_READY:
|
|
|
+ *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;
|
|
|
+
|
|
|
+ // 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;
|
|
|
+}
|
|
|
+
|
|
|
+void server_info_cb(pa_context *c, const pa_server_info *i, void *userdata) {
|
|
|
+ char *default_output = (char *)userdata;
|
|
|
+
|
|
|
+ strncpy(default_output, i->default_sink_name, 1024);
|
|
|
+}
|
|
|
+
|
|
|
+static unsigned int detect_channels() {
|
|
|
+
|
|
|
+ pa_mainloop *pa_ml;
|
|
|
+ pa_mainloop_api *pa_mlapi;
|
|
|
+ pa_operation *pa_op;
|
|
|
+ pa_context *pa_ctx;
|
|
|
+
|
|
|
+ int state = 0;
|
|
|
+ int pa_ready = 0;
|
|
|
+
|
|
|
+ char default_output[1024];
|
|
|
+ unsigned int channels = 2;
|
|
|
+
|
|
|
+ pa_ml = pa_mainloop_new();
|
|
|
+ pa_mlapi = pa_mainloop_get_api(pa_ml);
|
|
|
+ pa_ctx = pa_context_new(pa_mlapi, "Godot");
|
|
|
+
|
|
|
+ int ret = pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
|
|
|
+ if (ret < 0) {
|
|
|
+ pa_context_unref(pa_ctx);
|
|
|
+ pa_mainloop_free(pa_ml);
|
|
|
+
|
|
|
+ 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) {
|
|
|
+ while (pa_operation_get_state(pa_op) == PA_OPERATION_RUNNING) {
|
|
|
+ ret = pa_mainloop_iterate(pa_ml, 1, NULL);
|
|
|
+ if (ret < 0) {
|
|
|
+ ERR_PRINT("pa_mainloop_iterate error");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pa_operation_unref(pa_op);
|
|
|
+ } else {
|
|
|
+ ERR_PRINT("pa_context_get_sink_info_by_name error");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ ERR_PRINT("pa_context_get_server_info error");
|
|
|
+ }
|
|
|
+
|
|
|
+ pa_context_disconnect(pa_ctx);
|
|
|
+ pa_context_unref(pa_ctx);
|
|
|
+ pa_mainloop_free(pa_ml);
|
|
|
+
|
|
|
+ return channels;
|
|
|
+}
|
|
|
+
|
|
|
Error AudioDriverPulseAudio::init() {
|
|
|
|
|
|
active = false;
|
|
|
thread_exited = false;
|
|
|
exit_thread = false;
|
|
|
- pcm_open = false;
|
|
|
- samples_in = NULL;
|
|
|
- samples_out = NULL;
|
|
|
|
|
|
mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE);
|
|
|
- speaker_mode = SPEAKER_MODE_STEREO;
|
|
|
- channels = 2;
|
|
|
+ channels = detect_channels();
|
|
|
+
|
|
|
+ switch (channels) {
|
|
|
+ case 2: // Stereo
|
|
|
+ case 4: // Surround 3.1
|
|
|
+ case 6: // Surround 5.1
|
|
|
+ case 8: // Surround 7.1
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ ERR_PRINTS("PulseAudio: Unsupported number of channels: " + itos(channels));
|
|
|
+ ERR_FAIL_V(ERR_CANT_OPEN);
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
pa_sample_spec spec;
|
|
|
spec.format = PA_SAMPLE_S16LE;
|
|
@@ -59,7 +180,8 @@ Error AudioDriverPulseAudio::init() {
|
|
|
buffer_size = buffer_frames * channels;
|
|
|
|
|
|
if (OS::get_singleton()->is_stdout_verbose()) {
|
|
|
- print_line("audio buffer frames: " + itos(buffer_frames) + " calculated latency: " + itos(buffer_frames * 1000 / mix_rate) + "ms");
|
|
|
+ print_line("PulseAudio: detected " + itos(channels) + " channels");
|
|
|
+ print_line("PulseAudio: audio buffer frames: " + itos(buffer_frames) + " calculated latency: " + itos(buffer_frames * 1000 / mix_rate) + "ms");
|
|
|
}
|
|
|
|
|
|
pa_buffer_attr attr;
|
|
@@ -86,8 +208,8 @@ Error AudioDriverPulseAudio::init() {
|
|
|
ERR_FAIL_COND_V(pulse == NULL, ERR_CANT_OPEN);
|
|
|
}
|
|
|
|
|
|
- samples_in = memnew_arr(int32_t, buffer_size);
|
|
|
- samples_out = memnew_arr(int16_t, buffer_size);
|
|
|
+ samples_in.resize(buffer_size);
|
|
|
+ samples_out.resize(buffer_size);
|
|
|
|
|
|
mutex = Mutex::create();
|
|
|
thread = Thread::create(AudioDriverPulseAudio::thread_func, this);
|
|
@@ -119,7 +241,7 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
|
|
|
} else {
|
|
|
ad->lock();
|
|
|
|
|
|
- ad->audio_server_process(ad->buffer_frames, ad->samples_in);
|
|
|
+ ad->audio_server_process(ad->buffer_frames, ad->samples_in.ptr());
|
|
|
|
|
|
ad->unlock();
|
|
|
|
|
@@ -132,7 +254,7 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
|
|
|
|
|
|
int error_code;
|
|
|
int byte_size = ad->buffer_size * sizeof(int16_t);
|
|
|
- if (pa_simple_write(ad->pulse, ad->samples_out, byte_size, &error_code) < 0) {
|
|
|
+ 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;
|
|
@@ -156,7 +278,7 @@ int AudioDriverPulseAudio::get_mix_rate() const {
|
|
|
|
|
|
AudioDriver::SpeakerMode AudioDriverPulseAudio::get_speaker_mode() const {
|
|
|
|
|
|
- return speaker_mode;
|
|
|
+ return get_speaker_mode_by_total_channels(channels);
|
|
|
}
|
|
|
|
|
|
void AudioDriverPulseAudio::lock() {
|
|
@@ -186,16 +308,6 @@ void AudioDriverPulseAudio::finish() {
|
|
|
pulse = NULL;
|
|
|
}
|
|
|
|
|
|
- if (samples_in) {
|
|
|
- memdelete_arr(samples_in);
|
|
|
- samples_in = NULL;
|
|
|
- }
|
|
|
-
|
|
|
- if (samples_out) {
|
|
|
- memdelete_arr(samples_out);
|
|
|
- samples_out = NULL;
|
|
|
- }
|
|
|
-
|
|
|
memdelete(thread);
|
|
|
if (mutex) {
|
|
|
memdelete(mutex);
|
|
@@ -207,11 +319,21 @@ void AudioDriverPulseAudio::finish() {
|
|
|
|
|
|
AudioDriverPulseAudio::AudioDriverPulseAudio() {
|
|
|
|
|
|
- samples_in = NULL;
|
|
|
- samples_out = NULL;
|
|
|
mutex = NULL;
|
|
|
thread = NULL;
|
|
|
pulse = NULL;
|
|
|
+
|
|
|
+ samples_in.clear();
|
|
|
+ samples_out.clear();
|
|
|
+
|
|
|
+ mix_rate = 0;
|
|
|
+ buffer_size = 0;
|
|
|
+ channels = 0;
|
|
|
+
|
|
|
+ active = false;
|
|
|
+ thread_exited = false;
|
|
|
+ exit_thread = false;
|
|
|
+
|
|
|
latency = 0;
|
|
|
buffer_frames = 0;
|
|
|
buffer_size = 0;
|