diff options
Diffstat (limited to 'drivers/pulseaudio/audio_driver_pulseaudio.cpp')
-rw-r--r-- | drivers/pulseaudio/audio_driver_pulseaudio.cpp | 190 |
1 files changed, 165 insertions, 25 deletions
diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.cpp b/drivers/pulseaudio/audio_driver_pulseaudio.cpp index 356b1ad958..e4c8641a3b 100644 --- a/drivers/pulseaudio/audio_driver_pulseaudio.cpp +++ b/drivers/pulseaudio/audio_driver_pulseaudio.cpp @@ -31,34 +31,162 @@ #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", 44100); - speaker_mode = SPEAKER_MODE_STEREO; - channels = 2; + mix_rate = GLOBAL_DEF("audio/mix_rate", DEFAULT_MIX_RATE); + 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; spec.channels = channels; spec.rate = mix_rate; - int latency = GLOBAL_DEF("audio/output_latency", 25); - buffer_size = closest_power_of_2(latency * mix_rate / 1000); + int latency = GLOBAL_DEF("audio/output_latency", DEFAULT_OUTPUT_LATENCY); + buffer_frames = closest_power_of_2(latency * mix_rate / 1000); + buffer_size = buffer_frames * channels; + + if (OS::get_singleton()->is_stdout_verbose()) { + 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; - // set to appropriate buffer size from global settings - attr.tlength = buffer_size; + // set to appropriate buffer length (in bytes) from global settings + attr.tlength = buffer_size * sizeof(int16_t); // set them to be automatically chosen attr.prebuf = (uint32_t)-1; attr.maxlength = (uint32_t)-1; @@ -80,8 +208,8 @@ Error AudioDriverPulseAudio::init() { ERR_FAIL_COND_V(pulse == NULL, ERR_CANT_OPEN); } - samples_in = memnew_arr(int32_t, buffer_size * channels); - samples_out = memnew_arr(int16_t, buffer_size * channels); + samples_in.resize(buffer_size); + samples_out.resize(buffer_size); mutex = Mutex::create(); thread = Thread::create(AudioDriverPulseAudio::thread_func, this); @@ -106,18 +234,18 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) { while (!ad->exit_thread) { if (!ad->active) { - for (unsigned int i = 0; i < ad->buffer_size * ad->channels; i++) { + for (unsigned int i = 0; i < ad->buffer_size; i++) { ad->samples_out[i] = 0; } } else { ad->lock(); - ad->audio_server_process(ad->buffer_size, ad->samples_in); + ad->audio_server_process(ad->buffer_frames, ad->samples_in.ptr()); ad->unlock(); - for (unsigned int i = 0; i < ad->buffer_size * ad->channels; i++) { + for (unsigned int i = 0; i < ad->buffer_size; i++) { ad->samples_out[i] = ad->samples_in[i] >> 16; } } @@ -125,8 +253,8 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) { // pa_simple_write always consumes the entire buffer int error_code; - int byte_size = ad->buffer_size * sizeof(int16_t) * ad->channels; - if (pa_simple_write(ad->pulse, ad->samples_out, byte_size, &error_code) < 0) { + int byte_size = ad->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; @@ -150,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() { @@ -175,13 +303,10 @@ void AudioDriverPulseAudio::finish() { exit_thread = true; Thread::wait_to_finish(thread); - if (pulse) + if (pulse) { pa_simple_free(pulse); - - if (samples_in) { - memdelete_arr(samples_in); - memdelete_arr(samples_out); - }; + pulse = NULL; + } memdelete(thread); if (mutex) { @@ -197,7 +322,22 @@ AudioDriverPulseAudio::AudioDriverPulseAudio() { 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; + channels = 0; } AudioDriverPulseAudio::~AudioDriverPulseAudio() { |