summaryrefslogtreecommitdiff
path: root/drivers/pulseaudio/audio_driver_pulseaudio.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pulseaudio/audio_driver_pulseaudio.cpp')
-rw-r--r--drivers/pulseaudio/audio_driver_pulseaudio.cpp170
1 files changed, 146 insertions, 24 deletions
diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.cpp b/drivers/pulseaudio/audio_driver_pulseaudio.cpp
index 1798c84d85..e4c8641a3b 100644
--- a/drivers/pulseaudio/audio_driver_pulseaudio.cpp
+++ b/drivers/pulseaudio/audio_driver_pulseaudio.cpp
@@ -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;