From f2843209a5439eb6bf8a6382e8c8ef378175c111 Mon Sep 17 00:00:00 2001 From: Alexander Stillich Date: Fri, 19 Dec 2014 13:44:34 +0100 Subject: [PATCH] Implemented PulseAudio backend and fixed audio driver selection on X11 --- drivers/SCsub | 1 + drivers/pulseaudio/SCsub | 5 + .../pulseaudio/audio_driver_pulseaudio.cpp | 194 ++++++++++++++++++ drivers/pulseaudio/audio_driver_pulseaudio.h | 79 +++++++ main/main.cpp | 1 + platform/x11/detect.py | 8 + platform/x11/os_x11.cpp | 16 ++ platform/x11/os_x11.h | 10 +- 8 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 drivers/pulseaudio/SCsub create mode 100644 drivers/pulseaudio/audio_driver_pulseaudio.cpp create mode 100644 drivers/pulseaudio/audio_driver_pulseaudio.h diff --git a/drivers/SCsub b/drivers/SCsub index bfd35344eab..46334468ba7 100644 --- a/drivers/SCsub +++ b/drivers/SCsub @@ -7,6 +7,7 @@ Export('env') SConscript('unix/SCsub'); SConscript('alsa/SCsub'); +SConscript('pulseaudio/SCsub'); SConscript('windows/SCsub'); SConscript('gles2/SCsub'); SConscript('gles1/SCsub'); diff --git a/drivers/pulseaudio/SCsub b/drivers/pulseaudio/SCsub new file mode 100644 index 00000000000..9fbb467baa1 --- /dev/null +++ b/drivers/pulseaudio/SCsub @@ -0,0 +1,5 @@ +Import('env') + +env.add_source_files(env.drivers_sources,"*.cpp") + +Export('env') diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.cpp b/drivers/pulseaudio/audio_driver_pulseaudio.cpp new file mode 100644 index 00000000000..dfe9ddc55f8 --- /dev/null +++ b/drivers/pulseaudio/audio_driver_pulseaudio.cpp @@ -0,0 +1,194 @@ +/*************************************************************************/ +/* audio_driver_alsa.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "audio_driver_pulseaudio.h" + +#ifdef PULSEAUDIO_ENABLED + +#include + +#include "globals.h" + +Error AudioDriverPulseAudio::init() { + + active = false; + thread_exited = false; + exit_thread = false; + pcm_open = false; + samples_in = NULL; + samples_out = NULL; + + mix_rate = 44100; + output_format = OUTPUT_STEREO; + channels = 2; + + pa_sample_spec spec; + spec.format = PA_SAMPLE_S16LE; + spec.channels = channels; + spec.rate = mix_rate; + + 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 + NULL, // use default buffering attributes + &error_code + ); + + if (pulse == NULL) { + + fprintf(stderr, "PulseAudio ERR: %s\n", pa_strerror(error_code));\ + ERR_FAIL_COND_V(pulse == NULL, ERR_CANT_OPEN); + } + + int latency = GLOBAL_DEF("audio/output_latency", 25); + buffer_size = nearest_power_of_2(latency * mix_rate / 1000); + + samples_in = memnew_arr(int32_t, buffer_size * channels); + samples_out = memnew_arr(int16_t, buffer_size * channels); + + mutex = Mutex::create(); + thread = Thread::create(AudioDriverPulseAudio::thread_func, this); + + return OK; +} + +void AudioDriverPulseAudio::thread_func(void* p_udata) { + + AudioDriverPulseAudio* ad = (AudioDriverPulseAudio*)p_udata; + + while (!ad->exit_thread) { + + if (!ad->active) { + + for (unsigned int i=0; i < ad->buffer_size * ad->channels; i++) { + + ad->samples_out[i] = 0; + } + + } else { + + ad->lock(); + + ad->audio_server_process(ad->buffer_size, ad->samples_in); + + ad->unlock(); + + for (unsigned int i=0; i < ad->buffer_size * ad->channels;i ++) { + + ad->samples_out[i] = ad->samples_in[i] >> 16; + } + } + + // 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) { + + // 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->thread_exited = true; +} + +void AudioDriverPulseAudio::start() { + + active = true; +} + +int AudioDriverPulseAudio::get_mix_rate() const { + + return mix_rate; +} + +AudioDriverSW::OutputFormat AudioDriverPulseAudio::get_output_format() const { + + return output_format; +} + +void AudioDriverPulseAudio::lock() { + + if (!thread || !mutex) + return; + mutex->lock(); +} + +void AudioDriverPulseAudio::unlock() { + + if (!thread || !mutex) + return; + mutex->unlock(); +} + +void AudioDriverPulseAudio::finish() { + + if (!thread) + return; + + exit_thread = true; + Thread::wait_to_finish(thread); + + if (pulse) + pa_simple_free(pulse); + + if (samples_in) { + memdelete_arr(samples_in); + memdelete_arr(samples_out); + }; + + memdelete(thread); + if (mutex) { + memdelete(mutex); + mutex = NULL; + } + + thread = NULL; +} + +AudioDriverPulseAudio::AudioDriverPulseAudio() { + + mutex = NULL; + thread = NULL; + pulse = NULL; +} + +AudioDriverPulseAudio::~AudioDriverPulseAudio() { + +} + +#endif diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.h b/drivers/pulseaudio/audio_driver_pulseaudio.h new file mode 100644 index 00000000000..e82e0c24bea --- /dev/null +++ b/drivers/pulseaudio/audio_driver_pulseaudio.h @@ -0,0 +1,79 @@ +/*************************************************************************/ +/* audio_driver_pulseaudio.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "servers/audio/audio_server_sw.h" + +#ifdef PULSEAUDIO_ENABLED + +#include "core/os/thread.h" +#include "core/os/mutex.h" + +#include + +class AudioDriverPulseAudio : public AudioDriverSW { + + Thread* thread; + Mutex* mutex; + + pa_simple* pulse; + + int32_t* samples_in; + int16_t* samples_out; + + static void thread_func(void* p_udata); + + unsigned int mix_rate; + OutputFormat output_format; + + unsigned int buffer_size; + int channels; + + bool active; + bool thread_exited; + mutable bool exit_thread; + bool pcm_open; + +public: + + const char* get_name() const { + return "PulseAudio"; + }; + + virtual Error init(); + virtual void start(); + virtual int get_mix_rate() const; + virtual OutputFormat get_output_format() const; + virtual void lock(); + virtual void unlock(); + virtual void finish(); + + AudioDriverPulseAudio(); + ~AudioDriverPulseAudio(); +}; + +#endif diff --git a/main/main.cpp b/main/main.cpp index 116dbb1d0d3..f0e376a045e 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -147,6 +147,7 @@ void Main::print_help(const char* p_binary) { OS::get_singleton()->print(", "); OS::get_singleton()->print("%s",OS::get_singleton()->get_audio_driver_name(i)); } + OS::get_singleton()->print(")\n"); OS::get_singleton()->print("\t-rthread \t : Render Thread Mode ('unsafe', 'safe', 'separate)."); OS::get_singleton()->print(")\n"); OS::get_singleton()->print("\t-s,-script [script] : Run a script.\n"); diff --git a/platform/x11/detect.py b/platform/x11/detect.py index 1c8d231d4a1..dd5fa827ffb 100644 --- a/platform/x11/detect.py +++ b/platform/x11/detect.py @@ -114,6 +114,14 @@ def configure(env): env.Append(CPPFLAGS=['-DOPENGL_ENABLED','-DGLEW_ENABLED']) env.Append(CPPFLAGS=["-DALSA_ENABLED"]) + + if not os.system("pkg-config --exists libpulse-simple"): + print("Enabling PulseAudio") + env.Append(CPPFLAGS=["-DPULSEAUDIO_ENABLED"]) + env.ParseConfig('pkg-config --cflags --libs libpulse-simple') + else: + print("PulseAudio development libraries not found, disabling driver") + env.Append(CPPFLAGS=['-DX11_ENABLED','-DUNIX_ENABLED','-DGLES2_ENABLED','-DGLES1_ENABLED','-DGLES_OVER_GL']) env.Append(LIBS=['GL', 'GLU', 'pthread','asound','z']) #TODO detect linux/BSD! #env.Append(CPPFLAGS=['-DMPC_FIXED_POINT']) diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 04669843596..804809a87dc 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -74,6 +74,18 @@ OS::VideoMode OS_X11::get_default_video_mode() const { return OS::VideoMode(800,600,false); } +int OS_X11::get_audio_driver_count() const { + + return AudioDriverManagerSW::get_driver_count(); +} + +const char *OS_X11::get_audio_driver_name(int p_driver) const { + + AudioDriverSW* driver = AudioDriverManagerSW::get_driver(p_driver); + ERR_FAIL_COND_V( !driver, "" ); + return AudioDriverManagerSW::get_driver(p_driver)->get_name(); +} + void OS_X11::initialize(const VideoMode& p_desired,int p_video_driver,int p_audio_driver) { last_button_state=0; @@ -1450,6 +1462,10 @@ OS_X11::OS_X11() { AudioDriverManagerSW::add_driver(&driver_alsa); #endif +#ifdef PULSEAUDIO_ENABLED + AudioDriverManagerSW::add_driver(&driver_pulseaudio); +#endif + minimized = false; xim_style=NULL; mouse_mode=MOUSE_MODE_VISIBLE; diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index 67772894fa3..dd2476ec1b7 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -43,6 +43,7 @@ #include "servers/spatial_sound_2d/spatial_sound_2d_server_sw.h" #include "drivers/rtaudio/audio_driver_rtaudio.h" #include "drivers/alsa/audio_driver_alsa.h" +#include "drivers/pulseaudio/audio_driver_pulseaudio.h" #include "servers/physics_2d/physics_2d_server_sw.h" #include @@ -129,6 +130,10 @@ class OS_X11 : public OS_Unix { AudioDriverALSA driver_alsa; #endif +#ifdef PULSEAUDIO_ENABLED + AudioDriverPulseAudio driver_pulseaudio; +#endif + enum { JOYSTICKS_MAX = 8, MAX_JOY_AXIS = 32768, // I've no idea @@ -160,7 +165,10 @@ protected: virtual int get_video_driver_count() const; virtual const char * get_video_driver_name(int p_driver) const; virtual VideoMode get_default_video_mode() const; - + + virtual int get_audio_driver_count() const; + virtual const char * get_audio_driver_name(int p_driver) const; + virtual void initialize(const VideoMode& p_desired,int p_video_driver,int p_audio_driver); virtual void finalize();