diff --git a/Makefile b/Makefile index 36b582f8f0f..a44c6fbc7d7 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,11 @@ ifeq ($(HAVE_JACK),1) OBJ += audio/jack.o LIBS += -ljack endif +ifeq ($(HAVE_PULSE), 1) + OBJ += audio/pulse.o + LIBS += $(PULSE_LIBS) + DEFINES += $(PULSE_CFLAGS) +endif ifeq ($(HAVE_SDL), 1) OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o diff --git a/README.md b/README.md index 9d250865848..817a89767f2 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ SSNES can utilize these libraries if enabled: - libxml2 (bSNES XML shaders) - libfreetype2 (TTF font rendering on screen) -SSNES needs one of these audio driver libraries: +SSNES needs at least one of these audio driver libraries: - ALSA - OSS @@ -35,6 +35,8 @@ SSNES needs one of these audio driver libraries: - OpenAL - JACK - SDL + - XAudio2 (Win32) + - PulseAudio # Building libsnes diff --git a/audio/pulse.c b/audio/pulse.c new file mode 100644 index 00000000000..79e0fd88224 --- /dev/null +++ b/audio/pulse.c @@ -0,0 +1,191 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2011 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + + +#include "driver.h" +#include "general.h" +#include +#include +#include + +#include + +typedef struct +{ + pa_mainloop *mainloop; + pa_context *context; + pa_stream *stream; + bool nonblock; +} pa_t; + +static void __pulse_free(void *data) +{ + pa_t *pa = data; + if (pa) + { + if (pa->stream) + { + pa_stream_disconnect(pa->stream); + pa_stream_unref(pa->stream); + } + + if (pa->context) + { + pa_context_disconnect(pa->context); + pa_context_unref(pa->context); + } + + if (pa->mainloop) + pa_mainloop_free(pa->mainloop); + + free(pa); + } +} + +static inline uint8_t is_little_endian(void) +{ + union + { + uint16_t x; + uint8_t y[2]; + } u; + + u.x = 1; + return u.y[0]; +} + +static void* __pulse_init(const char* device, int rate, int latency) +{ + pa_t *pa = calloc(1, sizeof(*pa)); + if (!pa) + goto error; + + pa->mainloop = pa_mainloop_new(); + if (!pa->mainloop) + goto error; + + pa->context = pa_context_new(pa_mainloop_get_api(pa->mainloop), "SSNES"); + if (!pa->context) + goto error; + if (pa_context_connect(pa->context, device, PA_CONTEXT_NOFLAGS, NULL) < 0) + goto error; + + pa_context_state_t cstate; + do + { + pa_mainloop_iterate(pa->mainloop, 1, NULL); + cstate = pa_context_get_state(pa->context); + if (!PA_CONTEXT_IS_GOOD(cstate)) goto error; + } while (cstate != PA_CONTEXT_READY); + + pa_sample_spec spec = { + .format = is_little_endian() ? PA_SAMPLE_FLOAT32LE : PA_SAMPLE_FLOAT32BE, + .channels = 2, + .rate = rate + }; + + pa->stream = pa_stream_new(pa->context, "audio", &spec, NULL); + if (!pa->stream) + goto error; + + pa_buffer_attr buffer_attr = { + .maxlength = -1, + .tlength = pa_usec_to_bytes(latency * PA_USEC_PER_MSEC, &spec), + .prebuf = -1, + .minreq = -1, + .fragsize = -1 + }; + + if (pa_stream_connect_playback(pa->stream, NULL, &buffer_attr, PA_STREAM_ADJUST_LATENCY, NULL, NULL) < 0) + goto error; + + pa_stream_state_t sstate; + do + { + pa_mainloop_iterate(pa->mainloop, 1, NULL); + sstate = pa_stream_get_state(pa->stream); + if(!PA_STREAM_IS_GOOD(sstate)) goto error; + } while(sstate != PA_STREAM_READY); + + return pa; + +error: + __pulse_free(pa); + return NULL; +} + +static ssize_t __pulse_write(void* data, const void* buf, size_t size) +{ + pa_t *pa = data; + + unsigned length = pa_stream_writable_size(pa->stream); + while (length < size) + { + pa_mainloop_iterate(pa->mainloop, 1, NULL); + + length = pa_stream_writable_size(pa->stream); + + if (pa->nonblock) + break; + } + + size_t write_size = length < size ? length : size; + + pa_stream_write(pa->stream, buf, write_size, NULL, 0LL, PA_SEEK_RELATIVE); + return write_size; +} + +static bool __pulse_stop(void *data) +{ + (void)data; + return true; +} + +static bool __pulse_start(void *data) +{ + (void)data; + return true; +} + +static void __pulse_set_nonblock_state(void *data, bool state) +{ + pa_t *pa = data; + pa->nonblock = state; +} + +static bool __pulse_use_float(void *data) +{ + (void)data; + return true; +} + +const audio_driver_t audio_pulse = { + .init = __pulse_init, + .write = __pulse_write, + .stop = __pulse_stop, + .start = __pulse_start, + .set_nonblock_state = __pulse_set_nonblock_state, + .use_float = __pulse_use_float, + .free = __pulse_free, + .ident = "pulse" +}; + + + + + + diff --git a/config.def.h b/config.def.h index 62d477085ca..5d2378c8430 100644 --- a/config.def.h +++ b/config.def.h @@ -54,6 +54,7 @@ #define AUDIO_JACK 6 #define AUDIO_SDL 8 #define AUDIO_XAUDIO 9 +#define AUDIO_PULSE 10 //////////////////////// #define INPUT_SDL 7 //////////////////////// @@ -62,6 +63,8 @@ #if defined(HAVE_ALSA) #define AUDIO_DEFAULT_DRIVER AUDIO_ALSA +#elif defined(HAVE_PULSE) +#define AUDIO_DEFAULT_DRIVER AUDIO_PULSE #elif defined(HAVE_OSS) #define AUDIO_DEFAULT_DRIVER AUDIO_OSS #elif defined(HAVE_JACK) diff --git a/driver.c b/driver.c index 4c621e1942e..1b20000d0da 100644 --- a/driver.c +++ b/driver.c @@ -52,6 +52,9 @@ static const audio_driver_t *audio_drivers[] = { #ifdef HAVE_XAUDIO &audio_xa, #endif +#ifdef HAVE_PULSE + &audio_pulse, +#endif }; static const video_driver_t *video_drivers[] = { diff --git a/driver.h b/driver.h index c5792dd26ed..0a0458a5d41 100644 --- a/driver.h +++ b/driver.h @@ -139,6 +139,7 @@ extern const audio_driver_t audio_openal; extern const audio_driver_t audio_jack; extern const audio_driver_t audio_sdl; extern const audio_driver_t audio_xa; +extern const audio_driver_t audio_pulse; extern const video_driver_t video_gl; extern const input_driver_t input_sdl; //////////////////////////////////////////////// diff --git a/qb/config.libs.sh b/qb/config.libs.sh index 2aad11cbb15..19e397bf127 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -21,6 +21,7 @@ check_lib AL -lopenal alcOpenDevice check_lib RSOUND -lrsound rsd_init check_lib ROAR -lroar roar_vs_new check_lib JACK -ljack jack_client_open +check_pkgconf PULSE libpulse check_pkgconf SDL sdl 1.2.10 check_critical SDL "Cannot find SDL library." @@ -46,7 +47,7 @@ check_lib DYNAMIC -ldl dlopen check_pkgconf FREETYPE freetype2 # Creates config.mk and config.h. -VARS="ALSA OSS AL RSOUND ROAR JACK SDL FILTER CG XML DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC CONFIGFILE FREETYPE" +VARS="ALSA OSS AL RSOUND ROAR JACK PULSE SDL FILTER CG XML DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC CONFIGFILE FREETYPE" create_config_make config.mk $VARS create_config_header config.h $VARS diff --git a/qb/config.params.sh b/qb/config.params.sh index 783e67ed5ee..cbf4ecec768 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -20,4 +20,5 @@ add_command_line_enable RSOUND "Enable RSound support" auto add_command_line_enable ROAR "Enable RoarAudio support" auto add_command_line_enable AL "Enable OpenAL support" auto add_command_line_enable JACK "Enable JACK support" auto +add_command_line_enable PULSE "Enable PulseAudio support" auto add_command_line_enable FREETYPE "Enable FreeType support" auto diff --git a/settings.c b/settings.c index dc0397b9c1d..8592993a23a 100644 --- a/settings.c +++ b/settings.c @@ -72,6 +72,9 @@ static void set_defaults(void) case AUDIO_XAUDIO: def_audio = "xaudio"; break; + case AUDIO_PULSE: + def_audio = "pulse"; + break; default: break; } diff --git a/ssnes.cfg b/ssnes.cfg index cce93f740a9..9a497a8f7f5 100644 --- a/ssnes.cfg +++ b/ssnes.cfg @@ -19,7 +19,7 @@ # Video vsync. # video_vsync = true -# Smoothens picture with bilinear filtering. Should be disabled if using Cg shaders. +# Smoothens picture with bilinear filtering. Should be disabled if using pixel shaders. # video_smooth = true # Forces rendering area to stay equal to SNES aspect ratio 4:3 or as defined in video_aspect_ratio. @@ -28,10 +28,10 @@ # A floating point value for video aspect ratio (width / height) # video_aspect_ratio = 1.333 -# Path to Cg shader. If enabled +# Path to Cg shader. # video_cg_shader = "/path/to/cg/shader.cg" -# Path to bSNES-style XML shader. If both Cg shader path and XML shader path are defined, Cg shader will take priority. +# Path to bSNES-style XML shader (GLSL only). If both Cg shader path and XML shader path are defined, Cg shader will take priority. # video_bsnes_shader = "/path/to/bsnes/xml/shader.shader" # CPU-based filter. Valid ones are: hq2x, hq4x, grayscale, bleed, ntsc. @@ -43,7 +43,8 @@ # Size of the TTF font rendered. # video_font_size = 48 -# Offset for where messages will be placed on screen. Values are in range 0.0 to 1.0 for both x and y values. +# Offset for where messages will be placed on screen. Values are in range 0.0 to 1.0 for both x and y values. +# [0.0, 0.0] maps to the lower left corner of the screen. # video_message_pos_x = 0.05 # video_message_pox_y = 0.05 @@ -61,10 +62,10 @@ # but lots of dropped frames. Reasonable values for this is 32000 +/- 100 Hz. # audio_in_rate = 31980 -# Audio driver backend. Depending on configuration possible candidates are: alsa, oss, jack, rsound, roar, openal, sdl and xaudio +# Audio driver backend. Depending on configuration possible candidates are: alsa, pulse, oss, jack, rsound, roar, openal, sdl and xaudio # audio_driver = -# Override the default audio device the audio_driver uses. +# Override the default audio device the audio_driver uses. This is driver dependant. E.g. ALSA wants a PCM device, OSS wants a path (e.g. /dev/dsp), Jack wants portnames (e.g. system:playback1,system:playback_2), and so on ... # audio_device = # Will sync (block) on audio. Recommended. @@ -73,7 +74,7 @@ # Desired audio latency in milliseconds. Might not be honored if driver can't provide given latency. # audio_latency = 64 -# libsamplerate quality. Valid values are from 1 to 5. These values map to zero_order_hold, linear, sinc_fastest, sinc_medium and sinc_best. +# libsamplerate quality. Valid values are from 1 to 5. These values map to zero_order_hold, linear, sinc_fastest, sinc_medium and sinc_best respectively. # audio_src_quality = ### Input @@ -98,11 +99,14 @@ # input_player1_up = up # input_player1_down = down -# If desired, it is possible to override which joypads are being used for player 1 and 2. First joypad available is 0. +# If desired, it is possible to override which joypads are being used for player 1 through 5. First joypad available is 0. # input_player1_joypad_index = 0 # input_player2_joypad_index = 1 +# input_player3_joypad_index = 2 +# input_player4_joypad_index = 3 +# input_player5_joypad_index = 4 -# Joypad buttons. Figure these out by looking at jstest /dev/input/js0 output. +# Joypad buttons. Figure these out by looking at jstest /dev/input/js0 output, or use ssnes-joyconfig. # You can use joypad hats with hnxx, where n is the hat, and xx is a string representing direction. # E.g. "h0up" # input_player1_a_btn = 1 @@ -118,7 +122,7 @@ # input_player1_up_btn = 13 # input_player1_down_btn = 14 -# Axis for DPAD. +# Axis for SNES DPAD. # Needs to be either '+' or '-' in the first character signaling either positive or negative direction of the axis, then the axis number. # Do note that every other input option has the corresponding _btn and _axis binds as well; they are omitted here for clarity. # input_player1_left_axis = -0 @@ -159,6 +163,8 @@ # input_player2_up_axis = -1 # input_player2_down_axis = +1 +# This goes all the way to player 5, but again omitted for clarity. + # Toggles fullscreen. # input_toggle_fullscreen = f # Saves state.