Committed PulseAudio driver. Thanks, Stephan! SDL-1.2
authorRyan C. Gordon <icculus@icculus.org>
Sun, 13 May 2007 23:12:46 +0000
branchSDL-1.2
changeset 3939 42e83d81224b
parent 3938 345908fb5442
child 3940 cb04355ffec4
Committed PulseAudio driver. Thanks, Stephan!
configure.in
include/SDL_config.h.in
src/audio/SDL_audio.c
src/audio/SDL_sysaudio.h
src/audio/pulse/SDL_pulseaudio.c
src/audio/pulse/SDL_pulseaudio.h
--- a/configure.in	Tue Apr 17 09:09:48 2007 +0000
+++ b/configure.in	Sun May 13 23:12:46 2007 +0000
@@ -459,6 +459,63 @@
     fi
 }
 
+dnl Find PulseAudio
+CheckPulseAudio()
+{
+    AC_ARG_ENABLE(pulseaudio,
+AC_HELP_STRING([--enable-pulseaudio], [use PulseAudio [[default=yes]]]),
+                  , enable_pulse=yes)
+    if test x$enable_audio = xyes -a x$enable_pulse = xyes; then
+        audio_pulse=no
+
+        PULSE_REQUIRED_VERSION=0.9
+
+        AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
+        AC_MSG_CHECKING(for PulseAudio $PULSE_REQUIRED_VERSION support)
+        if test x$PKG_CONFIG != xno; then
+        if $PKG_CONFIG --atleast-pkgconfig-version 0.7 && $PKG_CONFIG --atleast-version $PULSE_REQUIRED_VERSION libpulse-simple; then
+                PULSE_CFLAGS=`$PKG_CONFIG --cflags libpulse-simple`
+                PULSE_LIBS=`$PKG_CONFIG --libs libpulse-simple`
+                audio_pulse=yes
+        fi
+        fi
+        AC_MSG_RESULT($audio_pulse)
+
+        if test x$audio_pulse = xyes; then
+            AC_ARG_ENABLE(pulseaudio-shared,
+AC_HELP_STRING([--enable-pulseaudio-shared], [dynamically load PulseAudio support [[default=yes]]]),
+                          , enable_pulse_shared=yes)
+            if test "x`echo $PULSE_LIBS | grep -- -L`" = "x"; then
+                if test "x`ls /lib/libpulse-simple.so.* 2> /dev/null`" != "x"; then
+                    PULSE_LIBS="-L/lib $PULSE_LIBS"
+                elif test "x`ls /usr/lib/libpulse-simple.so.* 2> /dev/null`" != "x"; then
+                    PULSE_LIBS="-L/usr/lib $PULSE_LIBS"
+                elif test "x`ls /usr/local/lib/libpulse-simple.so.* 2> /dev/null`" != "x"; then
+                    PULSE_LIBS="-L/usr/local/lib $PULSE_LIBS"
+                fi
+            fi
+            pulse_lib_spec=`echo $PULSE_LIBS | sed 's/.*-L\([[^ ]]*\).*/\1\/libpulse-simple.so.*/'`
+            pulse_lib=`ls -- $pulse_lib_spec | sed 's/.*\/\(.*\)/\1/; q'`
+            echo "-- $pulse_lib_spec -> $pulse_lib"
+
+            AC_DEFINE(SDL_AUDIO_DRIVER_PULSE)
+            SOURCES="$SOURCES $srcdir/src/audio/pulse/*.c"
+            EXTRA_CFLAGS="$EXTRA_CFLAGS $PULSE_CFLAGS"
+            if test x$have_loadso != xyes && \
+               test x$enable_pulse_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_pulse_shared = xyes && test x$pulse_lib != x; then
+                AC_DEFINE_UNQUOTED(SDL_AUDIO_DRIVER_PULSE_DYNAMIC, "$pulse_lib")
+            else
+                EXTRA_LDFLAGS="$EXTRA_LDFLAGS $PULSE_LIBS"
+            fi
+            have_audio=yes
+        fi
+    fi
+}
+
 CheckARTSC()
 {
     AC_ARG_ENABLE(arts,
@@ -2150,6 +2207,7 @@
         CheckALSA
         CheckARTSC
         CheckESD
+        CheckPulseAudio
         CheckNAS
         CheckX11
         CheckNANOX
--- a/include/SDL_config.h.in	Tue Apr 17 09:09:48 2007 +0000
+++ b/include/SDL_config.h.in	Sun May 13 23:12:46 2007 +0000
@@ -163,6 +163,8 @@
 #undef SDL_AUDIO_DRIVER_DUMMY
 #undef SDL_AUDIO_DRIVER_DMEDIA
 #undef SDL_AUDIO_DRIVER_DSOUND
+#undef SDL_AUDIO_DRIVER_PULSE
+#undef SDL_AUDIO_DRIVER_PULSE_DYNAMIC
 #undef SDL_AUDIO_DRIVER_ESD
 #undef SDL_AUDIO_DRIVER_ESD_DYNAMIC
 #undef SDL_AUDIO_DRIVER_MINT
--- a/src/audio/SDL_audio.c	Tue Apr 17 09:09:48 2007 +0000
+++ b/src/audio/SDL_audio.c	Sun May 13 23:12:46 2007 +0000
@@ -46,6 +46,9 @@
 #if SDL_AUDIO_DRIVER_ALSA
 	&ALSA_bootstrap,
 #endif
+#if SDL_AUDIO_DRIVER_PULSE
+	&PULSE_bootstrap,
+#endif
 #if SDL_AUDIO_DRIVER_QNXNTO
 	&QNXNTOAUDIO_bootstrap,
 #endif
--- a/src/audio/SDL_sysaudio.h	Tue Apr 17 09:09:48 2007 +0000
+++ b/src/audio/SDL_sysaudio.h	Sun May 13 23:12:46 2007 +0000
@@ -103,6 +103,9 @@
 #if SDL_AUDIO_DRIVER_BSD
 extern AudioBootStrap BSD_AUDIO_bootstrap;
 #endif
+#if SDL_AUDIO_DRIVER_PULSE
+extern AudioBootStrap PULSE_bootstrap;
+#endif
 #if SDL_AUDIO_DRIVER_OSS
 extern AudioBootStrap DSP_bootstrap;
 extern AudioBootStrap DMA_bootstrap;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/audio/pulse/SDL_pulseaudio.c	Sun May 13 23:12:46 2007 +0000
@@ -0,0 +1,377 @@
+/*
+    SDL - Simple DirectMedia Layer
+    Copyright (C) 1997-2007 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
+
+    Stéphan Kochen
+    stephan@kochen.nl
+    
+    Based on parts of the ALSA and ESounD output drivers.
+*/
+#include "SDL_config.h"
+
+/* Allow access to an PulseAudio network stream mixing buffer */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <pulse/simple.h>
+
+#include "SDL_timer.h"
+#include "SDL_audio.h"
+#include "../SDL_audiomem.h"
+#include "../SDL_audio_c.h"
+#include "../SDL_audiodev_c.h"
+#include "SDL_pulseaudio.h"
+
+#ifdef SDL_AUDIO_DRIVER_PULSE_DYNAMIC
+#include "SDL_name.h"
+#include "SDL_loadso.h"
+#else
+#define SDL_NAME(X)	X
+#endif
+
+/* The tag name used by the driver */
+#define PULSE_DRIVER_NAME	"pulse"
+
+/* Audio driver functions */
+static int PULSE_OpenAudio(_THIS, SDL_AudioSpec *spec);
+static void PULSE_WaitAudio(_THIS);
+static void PULSE_PlayAudio(_THIS);
+static Uint8 *PULSE_GetAudioBuf(_THIS);
+static void PULSE_CloseAudio(_THIS);
+
+#ifdef SDL_AUDIO_DRIVER_PULSE_DYNAMIC
+
+static const char *pulse_library = SDL_AUDIO_DRIVER_PULSE_DYNAMIC;
+static void *pulse_handle = NULL;
+static int pulse_loaded = 0;
+
+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
+);
+	
+
+static struct {
+	const char *name;
+	void **func;
+} pulse_functions[] = {
+	{ "pa_simple_new",
+		(void **)&SDL_NAME(pa_simple_new)		},
+	{ "pa_simple_free",
+		(void **)&SDL_NAME(pa_simple_free)		},
+	{ "pa_simple_drain",
+		(void **)&SDL_NAME(pa_simple_drain)		},
+	{ "pa_simple_write",
+		(void **)&SDL_NAME(pa_simple_write)		},
+	{ "pa_channel_map_init_auto",
+		(void **)&SDL_NAME(pa_channel_map_init_auto)	},
+};
+
+static void UnloadPulseLibrary()
+{
+	if ( pulse_loaded ) {
+		SDL_UnloadObject(pulse_handle);
+		pulse_handle = NULL;
+		pulse_loaded = 0;
+	}
+}
+
+static int LoadPulseLibrary(void)
+{
+	int i, retval = -1;
+
+	pulse_handle = SDL_LoadObject(pulse_library);
+	if ( pulse_handle ) {
+		pulse_loaded = 1;
+		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_PULSE_DYNAMIC */
+
+/* Audio driver bootstrap functions */
+
+static int Audio_Available(void)
+{
+	pa_sample_spec paspec;
+	pa_simple *connection;
+	int available;
+
+	available = 0;
+	if ( LoadPulseLibrary() < 0 ) {
+		return available;
+	}
+	
+	/* Connect with a dummy format. */
+	paspec.format = PA_SAMPLE_U8;
+	paspec.rate = 11025;
+	paspec.channels = 1;
+	connection = SDL_NAME(pa_simple_new)(
+		SDL_getenv("PASERVER"),      /* server */
+		"Test stream",               /* application name */
+		PA_STREAM_PLAYBACK,          /* playback mode */
+		SDL_getenv("PADEVICE"),      /* device on the server */
+		"Simple DirectMedia Layer",  /* stream description */
+		&paspec,                     /* sample format spec */
+		NULL,                        /* channel map */
+		NULL,                        /* buffering attributes */
+		NULL                         /* error code */
+	);
+	if ( connection != NULL ) {
+		available = 1;
+		SDL_NAME(pa_simple_free)(connection);
+	}
+	
+	UnloadPulseLibrary();
+	return(available);
+}
+
+static void Audio_DeleteDevice(SDL_AudioDevice *device)
+{
+	SDL_free(device->hidden);
+	SDL_free(device);
+	UnloadPulseLibrary();
+}
+
+static SDL_AudioDevice *Audio_CreateDevice(int devindex)
+{
+	SDL_AudioDevice *this;
+
+	/* Initialize all variables that we clean on shutdown */
+	LoadPulseLibrary();
+	this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice));
+	if ( this ) {
+		SDL_memset(this, 0, (sizeof *this));
+		this->hidden = (struct SDL_PrivateAudioData *)
+				SDL_malloc((sizeof *this->hidden));
+	}
+	if ( (this == NULL) || (this->hidden == NULL) ) {
+		SDL_OutOfMemory();
+		if ( this ) {
+			SDL_free(this);
+		}
+		return(0);
+	}
+	SDL_memset(this->hidden, 0, (sizeof *this->hidden));
+
+	/* Set the function pointers */
+	this->OpenAudio = PULSE_OpenAudio;
+	this->WaitAudio = PULSE_WaitAudio;
+	this->PlayAudio = PULSE_PlayAudio;
+	this->GetAudioBuf = PULSE_GetAudioBuf;
+	this->CloseAudio = PULSE_CloseAudio;
+
+	this->free = Audio_DeleteDevice;
+
+	return this;
+}
+
+AudioBootStrap PULSE_bootstrap = {
+	PULSE_DRIVER_NAME, "PulseAudio",
+	Audio_Available, Audio_CreateDevice
+};
+
+/* This function waits until it is possible to write a full sound buffer */
+static void PULSE_WaitAudio(_THIS)
+{
+	/* 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.
+		*/
+		if (parent && (((++cnt)%10) == 0)) { /* Check every 10 loops */
+			if ( kill(parent, 0) < 0 ) {
+				this->enabled = 0;
+			}
+		}
+	}
+}
+
+static void PULSE_PlayAudio(_THIS)
+{
+	/* Write the audio data */
+	if ( SDL_NAME(pa_simple_write)(stream, mixbuf, mixlen, NULL) != 0 )
+	{
+		this->enabled = 0;
+	}
+}
+
+static Uint8 *PULSE_GetAudioBuf(_THIS)
+{
+	return(mixbuf);
+}
+
+static void PULSE_CloseAudio(_THIS)
+{
+	if ( mixbuf != NULL ) {
+		SDL_FreeAudioMem(mixbuf);
+		mixbuf = NULL;
+	}
+	if ( stream != NULL ) {
+		SDL_NAME(pa_simple_drain)(stream, NULL);
+		SDL_NAME(pa_simple_free)(stream);
+		stream = NULL;
+	}
+}
+
+/* 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 PULSE_OpenAudio(_THIS, SDL_AudioSpec *spec)
+{
+	Uint16          test_format;
+	pa_sample_spec  paspec;
+	pa_buffer_attr  paattr;
+	pa_channel_map  pacmap;
+	
+	paspec.format = PA_SAMPLE_INVALID;
+	for ( test_format = SDL_FirstAudioFormat(spec->format); test_format; ) {
+		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;
+		}
+		if ( paspec.format != PA_SAMPLE_INVALID )
+			break;
+	}
+	if (paspec.format == PA_SAMPLE_INVALID ) {
+		SDL_SetError("Couldn't find any suitable audio formats");
+		return(-1);
+	}
+	spec->format = test_format;
+	
+	paspec.channels = spec->channels;
+	paspec.rate = spec->freq;
+
+	/* Calculate the final parameters for this audio specification */
+	SDL_CalculateAudioSpec(spec);
+
+	/* Allocate mixing buffer */
+	mixlen = spec->size;
+	mixbuf = (Uint8 *)SDL_AllocAudioMem(mixlen);
+	if ( mixbuf == NULL ) {
+		return(-1);
+	}
+	SDL_memset(mixbuf, spec->silence, spec->size);
+	
+	/* Reduced prebuffering compared to the defaults. */
+	paattr.tlength = mixlen;
+	paattr.minreq = mixlen;
+	paattr.fragsize = mixlen;
+	paattr.prebuf = mixlen;
+	paattr.maxlength = 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, spec->channels, PA_CHANNEL_MAP_WAVEEX);
+	
+	/* Connect to the PulseAudio server */
+	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 ( stream == NULL ) {
+		PULSE_CloseAudio(this);
+		SDL_SetError("Could not connect to PulseAudio");
+		return(-1);
+	}
+
+	/* Get the parent process id (we're the parent of the audio thread) */
+	parent = getpid();
+	
+	return(0);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/audio/pulse/SDL_pulseaudio.h	Sun May 13 23:12:46 2007 +0000
@@ -0,0 +1,53 @@
+/*
+    SDL - Simple DirectMedia Layer
+    Copyright (C) 1997-2007 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
+
+    Stéphan Kochen
+    stephan@kochen.nl
+    
+    Based on parts of the ALSA and ESounD output drivers.
+*/
+#include "SDL_config.h"
+
+#ifndef _SDL_pulseaudio_h
+#define _SDL_pulseaudio_h
+
+#include "../SDL_sysaudio.h"
+
+/* Hidden "this" pointer for the video 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;
+};
+
+/* Old variable names */
+#define stream			(this->hidden->stream)
+#define parent			(this->hidden->parent)
+#define mixbuf			(this->hidden->mixbuf)
+#define mixlen			(this->hidden->mixlen)
+
+#endif /* _SDL_pulseaudio_h */
+