From 1d5aa72cb0a7bb02134ff627d114f83d93b2a77c Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 20 Aug 2007 01:02:37 +0000 Subject: [PATCH] Ported PulseAudio target from 1.2 to 1.3 interfaces, and added it to the trunk. Fixes Bugzilla #439. --- configure.in | 58 ++++ include/SDL_config.h.in | 2 + src/audio/SDL_audio.c | 4 + src/audio/pulseaudio/SDL_pulseaudio.c | 389 ++++++++++++++++++++++++++ src/audio/pulseaudio/SDL_pulseaudio.h | 54 ++++ 5 files changed, 507 insertions(+) create mode 100644 src/audio/pulseaudio/SDL_pulseaudio.c create mode 100644 src/audio/pulseaudio/SDL_pulseaudio.h diff --git a/configure.in b/configure.in index bb40c729d..c3440d56c 100644 --- a/configure.in +++ b/configure.in @@ -644,6 +644,63 @@ AC_HELP_STRING([--enable-esd-shared], [dynamically load ESD audio support [[defa fi } +dnl Find PulseAudio +CheckPulseAudio() +{ + AC_ARG_ENABLE(pulseaudio, +AC_HELP_STRING([--enable-pulseaudio], [use PulseAudio [[default=yes]]]), + , enable_pulseaudio=yes) + if test x$enable_audio = xyes -a x$enable_pulseaudio = xyes; then + audio_pulseaudio=no + + PULSEAUDIO_REQUIRED_VERSION=0.9 + + AC_PATH_PROG(PKG_CONFIG, pkg-config, no) + AC_MSG_CHECKING(for PulseAudio $PULSEAUDIO_REQUIRED_VERSION support) + if test x$PKG_CONFIG != xno; then + if $PKG_CONFIG --atleast-pkgconfig-version 0.7 && $PKG_CONFIG --atleast-version $PULSEAUDIO_REQUIRED_VERSION libpulse-simple; then + PULSEAUDIO_CFLAGS=`$PKG_CONFIG --cflags libpulse-simple` + PULSEAUDIO_LIBS=`$PKG_CONFIG --libs libpulse-simple` + audio_pulseaudio=yes + fi + fi + AC_MSG_RESULT($audio_pulseaudio) + + if test x$audio_pulseaudio = xyes; then + AC_ARG_ENABLE(pulseaudio-shared, +AC_HELP_STRING([--enable-pulseaudio-shared], [dynamically load PulseAudio support [[default=yes]]]), + , enable_pulseaudio_shared=yes) + if test "x`echo $PULSEAUDIO_LIBS | grep -- -L`" = "x"; then + if test "x`ls /lib/libpulse-simple.so.* 2> /dev/null`" != "x"; then + PULSEAUDIO_LIBS="-L/lib $PULSEAUDIO_LIBS" + elif test "x`ls /usr/lib/libpulse-simple.so.* 2> /dev/null`" != "x"; then + PULSEAUDIO_LIBS="-L/usr/lib $PULSEAUDIO_LIBS" + elif test "x`ls /usr/local/lib/libpulse-simple.so.* 2> /dev/null`" != "x"; then + PULSEAUDIO_LIBS="-L/usr/local/lib $PULSEAUDIO_LIBS" + fi + fi + pulseaudio_lib_spec=`echo $PULSEAUDIO_LIBS | sed 's/.*-L\([[^ ]]*\).*/\1\/libpulse-simple.so.*/'` + pulseaudio_lib=`ls -- $pulseaudio_lib_spec | sed 's/.*\/\(.*\)/\1/; q'` + echo "-- $pulseaudio_lib_spec -> $pulseaudio_lib" + + AC_DEFINE(SDL_AUDIO_DRIVER_PULSEAUDIO) + SOURCES="$SOURCES $srcdir/src/audio/pulseaudio/*.c" + EXTRA_CFLAGS="$EXTRA_CFLAGS $PULSEAUDIO_CFLAGS" + if test x$have_loadso != xyes && \ + test x$enable_pulseaudio_shared = xyes; then + AC_MSG_WARN([You must have SDL_LoadObject() support for dynamic PulseAudio loading]) + fi + if test x$have_loadso = xyes && \ + test x$enable_pulseaudio_shared = xyes && test x$pulseaudio_lib != x; then + AC_DEFINE_UNQUOTED(SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC, "$pulseaudio_lib") + else + EXTRA_LDFLAGS="$EXTRA_LDFLAGS $PULSEAUDIO_LIBS" + fi + have_audio=yes + fi + fi +} + CheckARTSC() { AC_ARG_ENABLE(arts, @@ -2065,6 +2122,7 @@ case "$host" in CheckDMEDIA CheckMME CheckALSA + CheckPulseAudio CheckARTSC CheckESD CheckNAS diff --git a/include/SDL_config.h.in b/include/SDL_config.h.in index effd02fb3..295e5153e 100644 --- a/include/SDL_config.h.in +++ b/include/SDL_config.h.in @@ -159,6 +159,8 @@ #undef SDL_AUDIO_DRIVER_ALSA_DYNAMIC #undef SDL_AUDIO_DRIVER_ARTS #undef SDL_AUDIO_DRIVER_ARTS_DYNAMIC +#undef SDL_AUDIO_DRIVER_PULSEAUDIO +#undef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC #undef SDL_AUDIO_DRIVER_BEOSAUDIO #undef SDL_AUDIO_DRIVER_BSD #undef SDL_AUDIO_DRIVER_COREAUDIO diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 4eeadd2ee..855ab9cba 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -47,6 +47,7 @@ extern AudioBootStrap BSD_AUDIO_bootstrap; extern AudioBootStrap DSP_bootstrap; extern AudioBootStrap DMA_bootstrap; extern AudioBootStrap ALSA_bootstrap; +extern AudioBootStrap PULSEAUDIO_bootstrap; extern AudioBootStrap QNXNTOAUDIO_bootstrap; extern AudioBootStrap SUNAUDIO_bootstrap; extern AudioBootStrap DMEDIA_bootstrap; @@ -83,6 +84,9 @@ static AudioBootStrap *bootstrap[] = { #if SDL_AUDIO_DRIVER_ALSA &ALSA_bootstrap, #endif +#if SDL_AUDIO_DRIVER_PULSEAUDIO + &PULSEAUDIO_bootstrap, +#endif #if SDL_AUDIO_DRIVER_QNXNTO &QNXNTOAUDIO_bootstrap, #endif diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c new file mode 100644 index 000000000..f2fb716f9 --- /dev/null +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -0,0 +1,389 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2006 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/* + The PulseAudio target for SDL 1.3 is based on the 1.3 arts target, with + the appropriate parts replaced with the 1.2 PulseAudio target code. This + was the cleanest way to move it to 1.3. The 1.2 target was written by + Stéphan Kochen: stephan .a.t. kochen.nl +*/ + +#include "SDL_config.h" + +/* Allow access to a raw mixing buffer */ + +#ifdef HAVE_SIGNAL_H +#include +#endif +#include +#include +#include +#include + +#include "SDL_timer.h" +#include "SDL_audio.h" +#include "../SDL_audiomem.h" +#include "../SDL_audio_c.h" +#include "SDL_pulseaudio.h" + +#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC +#include "SDL_name.h" +#include "SDL_loadso.h" +#else +#define SDL_NAME(X) X +#endif + +/* The tag name used by pulse audio */ +#define PULSEAUDIO_DRIVER_NAME "pulseaudio" + +#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC + +static const char *pulse_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC; +static void *pulse_handle = NULL; + +/* !!! FIXME: I hate this SDL_NAME clutter...it makes everything so messy! */ +static pa_simple* (*SDL_NAME(pa_simple_new))( + const char *server, + const char *name, + pa_stream_direction_t dir, + const char *dev, + const char *stream_name, + const pa_sample_spec *ss, + const pa_channel_map *map, + const pa_buffer_attr *attr, + int *error +); +static void (*SDL_NAME(pa_simple_free))(pa_simple *s); +static int (*SDL_NAME(pa_simple_drain))(pa_simple *s, int *error); +static int (*SDL_NAME(pa_simple_write))( + pa_simple *s, + const void *data, + size_t length, + int *error +); +static pa_channel_map* (*SDL_NAME(pa_channel_map_init_auto))( + pa_channel_map *m, + unsigned channels, + pa_channel_map_def_t def +); + + +#define SDL_PULSEAUDIO_SYM(x) { #x, (void **) (char *) &SDL_NAME(x) } +static struct { + const char *name; + void **func; +} pulse_functions[] = { +/* *INDENT-OFF* */ + SDL_PULSEAUDIO_SYM(pa_simple_new), + SDL_PULSEAUDIO_SYM(pa_simple_free), + SDL_PULSEAUDIO_SYM(pa_simple_drain), + SDL_PULSEAUDIO_SYM(pa_simple_write), + SDL_PULSEAUDIO_SYM(pa_channel_map_init_auto), +/* *INDENT-ON* */ +}; +#undef SDL_PULSEAUDIO_SYM + +static void +UnloadPulseLibrary() +{ + if (pulse_handle != NULL) { + SDL_UnloadObject(pulse_handle); + pulse_handle = NULL; + } +} + +static int +LoadPulseLibrary(void) +{ + int i, retval = -1; + + if (pulse_handle == NULL) { + pulse_handle = SDL_LoadObject(pulse_library); + if (pulse_handle != NULL) { + retval = 0; + for (i = 0; i < SDL_arraysize(pulse_functions); ++i) { + *pulse_functions[i].func = + SDL_LoadFunction(pulse_handle, pulse_functions[i].name); + if (!*pulse_functions[i].func) { + retval = -1; + UnloadPulseLibrary(); + break; + } + } + } + } + + return retval; +} + +#else + +static void +UnloadPulseLibrary() +{ + return; +} + +static int +LoadPulseLibrary(void) +{ + return 0; +} + +#endif /* SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC */ + +/* This function waits until it is possible to write a full sound buffer */ +static void +PULSEAUDIO_WaitDevice(_THIS) +{ + Sint32 ticks; + + /* Check to see if the thread-parent process is still alive */ + { + static int cnt = 0; + /* Note that this only works with thread implementations + that use a different process id for each thread. + */ + /* Check every 10 loops */ + if (this->hidden->parent && (((++cnt) % 10) == 0)) { + if (kill(this->hidden->parent, 0) < 0) { + this->enabled = 0; + } + } + } + + /* Use timer for general audio synchronization */ + ticks = + ((Sint32) (this->hidden->next_frame - SDL_GetTicks())) - FUDGE_TICKS; + if (ticks > 0) { + SDL_Delay(ticks); + } +} + +static void +PULSEAUDIO_PlayDevice(_THIS) +{ + /* Write the audio data */ + if ( SDL_NAME(pa_simple_write)(this->hidden->stream, this->hidden->mixbuf, + this->hidden->mixlen, NULL) != 0 ) + { + this->enabled = 0; + } +} + +static void +PULSEAUDIO_WaitDone(_THIS) +{ + SDL_NAME(pa_simple_drain)(this->hidden->stream, NULL); +} + + +static Uint8 * +PULSEAUDIO_GetDeviceBuf(_THIS) +{ + return (this->hidden->mixbuf); +} + + +static void +PULSEAUDIO_CloseDevice(_THIS) +{ + if (this->hidden != NULL) { + if (this->hidden->mixbuf != NULL) { + SDL_FreeAudioMem(this->hidden->mixbuf); + this->hidden->mixbuf = NULL; + } + if (this->hidden->stream) { + SDL_NAME(pa_simple_drain)(this->hidden->stream, NULL); + SDL_NAME(pa_simple_free)(this->hidden->stream); + this->hidden->stream = NULL; + } + SDL_free(this->hidden); + this->hidden = NULL; + } +} + + +/* !!! FIXME: this could probably be expanded. */ +/* Try to get the name of the program */ +static char *get_progname(void) +{ + char *progname = NULL; +#ifdef __LINUX__ + FILE *fp; + static char temp[BUFSIZ]; + + SDL_snprintf(temp, SDL_arraysize(temp), "/proc/%d/cmdline", getpid()); + fp = fopen(temp, "r"); + if ( fp != NULL ) { + if ( fgets(temp, sizeof(temp)-1, fp) ) { + progname = SDL_strrchr(temp, '/'); + if ( progname == NULL ) { + progname = temp; + } else { + progname = progname+1; + } + } + fclose(fp); + } +#endif + return(progname); +} + + +static int +PULSEAUDIO_OpenDevice(_THIS, const char *devname, int iscapture) +{ + Uint16 test_format; + pa_sample_spec paspec; + pa_buffer_attr paattr; + pa_channel_map pacmap; + + /* Initialize all variables that we clean on shutdown */ + this->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc((sizeof *this->hidden)); + if (this->hidden == NULL) { + SDL_OutOfMemory(); + return 0; + } + SDL_memset(this->hidden, 0, (sizeof *this->hidden)); + + paspec.format = PA_SAMPLE_INVALID; + + /* Try for a closest match on audio format */ + for (test_format = SDL_FirstAudioFormat(this->spec.format); + (paspec.format == PA_SAMPLE_INVALID) && test_format;) { +#ifdef DEBUG_AUDIO + fprintf(stderr, "Trying format 0x%4.4x\n", test_format); +#endif + switch ( test_format ) { + case AUDIO_U8: + paspec.format = PA_SAMPLE_U8; + break; + case AUDIO_S16LSB: + paspec.format = PA_SAMPLE_S16LE; + break; + case AUDIO_S16MSB: + paspec.format = PA_SAMPLE_S16BE; + break; + default: + paspec.format = PA_SAMPLE_INVALID; + break; + } + if (paspec.format == PA_SAMPLE_INVALID) { + test_format = SDL_NextAudioFormat(); + } + } + if (paspec.format == PA_SAMPLE_INVALID) { + PULSEAUDIO_CloseDevice(this); + SDL_SetError("Couldn't find any hardware audio formats"); + return 0; + } + this->spec.format = test_format; + + /* Calculate the final parameters for this audio specification */ + SDL_CalculateAudioSpec(&this->spec); + + /* Allocate mixing buffer */ + this->hidden->mixlen = this->spec.size; + this->hidden->mixbuf = (Uint8 *) SDL_AllocAudioMem(this->hidden->mixlen); + if (this->hidden->mixbuf == NULL) { + PULSEAUDIO_CloseDevice(this); + SDL_OutOfMemory(); + return 0; + } + SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size); + + paspec.channels = this->spec.channels; + paspec.rate = this->spec.freq; + + /* Reduced prebuffering compared to the defaults. */ + paattr.tlength = this->hidden->mixlen; + paattr.minreq = this->hidden->mixlen; + paattr.fragsize = this->hidden->mixlen; + paattr.prebuf = this->hidden->mixlen; + paattr.maxlength = this->hidden->mixlen * 4; + + /* The SDL ALSA output hints us that we use Windows' channel mapping */ + /* http://bugzilla.libsdl.org/show_bug.cgi?id=110 */ + SDL_NAME(pa_channel_map_init_auto)( + &pacmap, this->spec.channels, PA_CHANNEL_MAP_WAVEEX); + + /* Connect to the PulseAudio server */ + this->hidden->stream = SDL_NAME(pa_simple_new)( + SDL_getenv("PASERVER"), /* server */ + get_progname(), /* application name */ + PA_STREAM_PLAYBACK, /* playback mode */ + SDL_getenv("PADEVICE"), /* device on the server */ + "Simple DirectMedia Layer", /* stream description */ + &paspec, /* sample format spec */ + &pacmap, /* channel map */ + &paattr, /* buffering attributes */ + NULL /* error code */ + ); + if ( this->hidden->stream == NULL ) { + PULSEAUDIO_CloseDevice(this); + SDL_SetError("Could not connect to PulseAudio"); + return(-1); + } + + /* Get the parent process id (we're the parent of the audio thread) */ + this->hidden->parent = getpid(); + + /* We're ready to rock and roll. :-) */ + return 1; +} + + +static void +PULSEAUDIO_Deinitialize(void) +{ + UnloadPulseLibrary(); +} + + +static int +PULSEAUDIO_Init(SDL_AudioDriverImpl * impl) +{ + if (LoadPulseLibrary() < 0) { + return 0; + } + + /* Set the function pointers */ + impl->OpenDevice = PULSEAUDIO_OpenDevice; + impl->PlayDevice = PULSEAUDIO_PlayDevice; + impl->WaitDevice = PULSEAUDIO_WaitDevice; + impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf; + impl->CloseDevice = PULSEAUDIO_CloseDevice; + impl->WaitDone = PULSEAUDIO_WaitDone; + impl->Deinitialize = PULSEAUDIO_Deinitialize; + impl->OnlyHasDefaultOutputDevice = 1; + + return 1; +} + + +AudioBootStrap PULSEAUDIO_bootstrap = { + PULSEAUDIO_DRIVER_NAME, "PulseAudio", PULSEAUDIO_Init, 0 +}; + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/audio/pulseaudio/SDL_pulseaudio.h b/src/audio/pulseaudio/SDL_pulseaudio.h new file mode 100644 index 000000000..a893d8354 --- /dev/null +++ b/src/audio/pulseaudio/SDL_pulseaudio.h @@ -0,0 +1,54 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2006 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ +#include "SDL_config.h" + +#ifndef _SDL_pulseaudio_h +#define _SDL_pulseaudio_h + +#include + +#include "../SDL_sysaudio.h" + +/* Hidden "this" pointer for the audio functions */ +#define _THIS SDL_AudioDevice *this + +struct SDL_PrivateAudioData +{ + /* The audio stream handle */ + pa_simple *stream; + + /* The parent process id, to detect when application quits */ + pid_t parent; + + /* Raw mixing buffer */ + Uint8 *mixbuf; + int mixlen; + + /* Support for audio timing using a timer, in addition to select() */ + float frame_ticks; + float next_frame; +}; +#define FUDGE_TICKS 10 /* The scheduler overhead ticks per frame */ + +#endif /* _SDL_pulseaudio_h */ + +/* vi: set ts=4 sw=4 expandtab: */