Date: Sun, 29 Feb 2004 15:14:22 +0200
authorSam Lantinga <slouken@libsdl.org>
Tue, 02 Mar 2004 12:49:16 +0000
changeset 865 92615154bb68
parent 864 0c892e99b65b
child 866 0a45995a7fc3
Date: Sun, 29 Feb 2004 15:14:22 +0200 From: Martin_Storsj Subject: Dynamic loading of ALSA I recently discovered that SDL can dynamically load ESD and aRts, and made a patch which adds this same functionality to ALSA. The update for configure.in isn't too good (it should e.g. look for libasound.so in other directories than /usr/lib), because I'm not too good at shellscripting and autoconf. The reason for using dlfcn.h and dlopen instead of SDL_LoadLibrary and SDL_LoadFunction is that libasound uses versioned symbols, and it is necessary to load the correct version using dlvsym. This isn't probably any real portability issue, because ALSA is linux-only.
configure.in
src/audio/alsa/Makefile.am
src/audio/alsa/SDL_alsa_audio.c
--- a/configure.in	Tue Mar 02 12:45:22 2004 +0000
+++ b/configure.in	Tue Mar 02 12:49:16 2004 +0000
@@ -295,8 +295,22 @@
         AC_CHECK_LIB(asound, snd_pcm_open, have_alsa=yes)
         ])
         if test x$have_alsa = xyes; then
-            CFLAGS="$CFLAGS -DALSA_SUPPORT"
-            SYSTEM_LIBS="$SYSTEM_LIBS -lasound"
+            AC_ARG_ENABLE(alsa-shared,
+[  --enable-alsa-shared     dynamically load ALSA audio support [default=yes]],
+                          , enable_alsa_shared=yes)
+            alsa_lib=`ls /usr/lib/libasound.so.* | head -1 | sed 's/.*\/\(.*\)/\1/'`
+            if test x$use_dlopen != xyes && \
+               test x$enable_alsa_shared = xyes; then
+                AC_MSG_ERROR([You must have dlopen() support and use the --enable-dlopen option])
+            fi
+            if test x$use_dlopen = xyes && \
+               test x$enable_alsa_shared = xyes && test x$alsa_lib != x; then
+                CFLAGS="$CFLAGS -DALSA_SUPPORT -DALSA_DYNAMIC=\$(alsa_lib)"
+                AC_SUBST(alsa_lib)
+            else
+                CFLAGS="$CFLAGS -DALSA_SUPPORT"
+                SYSTEM_LIBS="$SYSTEM_LIBS -lasound"
+            fi
             AUDIO_SUBDIRS="$AUDIO_SUBDIRS alsa"
             AUDIO_DRIVERS="$AUDIO_DRIVERS alsa/libaudio_alsa.la"
         else
--- a/src/audio/alsa/Makefile.am	Tue Mar 02 12:45:22 2004 +0000
+++ b/src/audio/alsa/Makefile.am	Tue Mar 02 12:49:16 2004 +0000
@@ -4,6 +4,8 @@
 noinst_LTLIBRARIES = libaudio_alsa.la
 libaudio_alsa_la_SOURCES = $(SRCS)
 
+alsa_lib = \"@alsa_lib@\"
+
 # The SDL audio driver sources
 SRCS =	SDL_alsa_audio.c	\
 	SDL_alsa_audio.h
--- a/src/audio/alsa/SDL_alsa_audio.c	Tue Mar 02 12:45:22 2004 +0000
+++ b/src/audio/alsa/SDL_alsa_audio.c	Tue Mar 02 12:49:16 2004 +0000
@@ -41,6 +41,16 @@
 #include "SDL_timer.h"
 #include "SDL_alsa_audio.h"
 
+#ifdef ALSA_DYNAMIC
+#define __USE_GNU
+#include <dlfcn.h>
+#include "SDL_name.h"
+#include "SDL_loadso.h"
+#else
+#define SDL_NAME(X)	X
+#endif
+
+
 /* The tag name used by ALSA audio */
 #define DRIVER_NAME         "alsa"
 
@@ -54,6 +64,99 @@
 static Uint8 *ALSA_GetAudioBuf(_THIS);
 static void ALSA_CloseAudio(_THIS);
 
+#ifdef ALSA_DYNAMIC
+
+static const char *alsa_library = ALSA_DYNAMIC;
+static void *alsa_handle = NULL;
+static int alsa_loaded = 0;
+
+static int (*SDL_snd_pcm_open)(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode);
+static int (*SDL_NAME(snd_pcm_open))(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode);
+static int (*SDL_NAME(snd_pcm_close))(snd_pcm_t *pcm);
+static snd_pcm_sframes_t (*SDL_NAME(snd_pcm_writei))(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
+static int (*SDL_NAME(snd_pcm_resume))(snd_pcm_t *pcm);
+static int (*SDL_NAME(snd_pcm_prepare))(snd_pcm_t *pcm);
+static int (*SDL_NAME(snd_pcm_drain))(snd_pcm_t *pcm);
+static const char *(*SDL_NAME(snd_strerror))(int errnum);
+static size_t (*SDL_NAME(snd_pcm_hw_params_sizeof))(void);
+static int (*SDL_NAME(snd_pcm_hw_params_any))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+static int (*SDL_NAME(snd_pcm_hw_params_set_access))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t access);
+static int (*SDL_NAME(snd_pcm_hw_params_set_format))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val);
+static int (*SDL_NAME(snd_pcm_hw_params_set_channels))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
+static int (*SDL_NAME(snd_pcm_hw_params_get_channels))(const snd_pcm_hw_params_t *params);
+static unsigned int (*SDL_NAME(snd_pcm_hw_params_set_rate_near))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int *dir);
+static snd_pcm_uframes_t (*SDL_NAME(snd_pcm_hw_params_set_period_size_near))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val, int *dir);
+static unsigned int (*SDL_NAME(snd_pcm_hw_params_set_periods_near))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int *dir);
+static int (*SDL_NAME(snd_pcm_hw_params))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+static int (*SDL_NAME(snd_pcm_nonblock))(snd_pcm_t *pcm, int nonblock);
+#define snd_pcm_hw_params_sizeof SDL_NAME(snd_pcm_hw_params_sizeof)
+
+static struct {
+	const char *name;
+	void **func;
+} alsa_functions[] = {
+	{ "snd_pcm_open",	(void**)&SDL_NAME(snd_pcm_open)		},
+	{ "snd_pcm_close",	(void**)&SDL_NAME(snd_pcm_close)	},
+	{ "snd_pcm_writei",	(void**)&SDL_NAME(snd_pcm_writei)	},
+	{ "snd_pcm_resume",	(void**)&SDL_NAME(snd_pcm_resume)	},
+	{ "snd_pcm_prepare",	(void**)&SDL_NAME(snd_pcm_prepare)	},
+	{ "snd_pcm_drain",	(void**)&SDL_NAME(snd_pcm_drain)	},
+	{ "snd_strerror",	(void**)&SDL_NAME(snd_strerror)		},
+	{ "snd_pcm_hw_params_sizeof",		(void**)&SDL_NAME(snd_pcm_hw_params_sizeof)		},
+	{ "snd_pcm_hw_params_any",		(void**)&SDL_NAME(snd_pcm_hw_params_any)		},
+	{ "snd_pcm_hw_params_set_access",	(void**)&SDL_NAME(snd_pcm_hw_params_set_access)		},
+	{ "snd_pcm_hw_params_set_format",	(void**)&SDL_NAME(snd_pcm_hw_params_set_format)		},
+	{ "snd_pcm_hw_params_set_channels",	(void**)&SDL_NAME(snd_pcm_hw_params_set_channels)	},
+	{ "snd_pcm_hw_params_get_channels",	(void**)&SDL_NAME(snd_pcm_hw_params_get_channels)	},
+	{ "snd_pcm_hw_params_set_rate_near",	(void**)&SDL_NAME(snd_pcm_hw_params_set_rate_near)	},
+	{ "snd_pcm_hw_params_set_period_size_near",	(void**)&SDL_NAME(snd_pcm_hw_params_set_period_size_near)	},
+	{ "snd_pcm_hw_params_set_periods_near",	(void**)&SDL_NAME(snd_pcm_hw_params_set_periods_near)	},
+	{ "snd_pcm_hw_params",	(void**)&SDL_NAME(snd_pcm_hw_params)	},
+	{ "snd_pcm_nonblock",	(void**)&SDL_NAME(snd_pcm_nonblock)	},
+};
+
+static void UnloadALSALibrary(void) {
+	if (alsa_loaded) {
+/*		SDL_UnloadObject(alsa_handle);*/
+		dlclose(alsa_handle);
+		alsa_handle = NULL;
+		alsa_loaded = 0;
+	}
+}
+
+static int LoadALSALibrary(void) {
+	int i, retval = -1;
+
+/*	alsa_handle = SDL_LoadObject(alsa_library);*/
+	alsa_handle = dlopen(alsa_library,RTLD_NOW);
+	if (alsa_handle) {
+		alsa_loaded = 1;
+		retval = 0;
+		for (i = 0; i < SDL_TABLESIZE(alsa_functions); i++) {
+/*			*alsa_functions[i].func = SDL_LoadFunction(alsa_handle,alsa_functions[i].name);*/
+			*alsa_functions[i].func = dlvsym(alsa_handle,alsa_functions[i].name,"ALSA_0.9");
+			if (!*alsa_functions[i].func) {
+				retval = -1;
+				UnloadALSALibrary();
+				break;
+			}
+		}
+	}
+	return retval;
+}
+
+#else
+
+static void UnloadALSALibrary(void) {
+	return;
+}
+
+static int LoadALSALibrary(void) {
+	return 0;
+}
+
+#endif /* ALSA_DYNAMIC */
+
 static const char *get_audio_device()
 {
 	const char *device;
@@ -74,11 +177,15 @@
 	snd_pcm_t *handle;
 
 	available = 0;
-	status = snd_pcm_open(&handle, get_audio_device(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+	if (LoadALSALibrary() < 0) {
+		return available;
+	}
+	status = SDL_NAME(snd_pcm_open)(&handle, get_audio_device(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
 	if ( status >= 0 ) {
 		available = 1;
-        	snd_pcm_close(handle);
+        	SDL_NAME(snd_pcm_close)(handle);
 	}
+	UnloadALSALibrary();
 	return(available);
 }
 
@@ -86,6 +193,7 @@
 {
 	free(device->hidden);
 	free(device);
+	UnloadALSALibrary();
 }
 
 static SDL_AudioDevice *Audio_CreateDevice(int devindex)
@@ -93,6 +201,7 @@
 	SDL_AudioDevice *this;
 
 	/* Initialize all variables that we clean on shutdown */
+	LoadALSALibrary();
 	this = (SDL_AudioDevice *)malloc(sizeof(SDL_AudioDevice));
 	if ( this ) {
 		memset(this, 0, (sizeof *this));
@@ -150,7 +259,7 @@
 	sample_len = this->spec.samples;
 	sample_buf = (signed short *)mixbuf;
 	while ( sample_len > 0 ) {
-		status = snd_pcm_writei(pcm_handle, sample_buf, sample_len);
+		status = SDL_NAME(snd_pcm_writei)(pcm_handle, sample_buf, sample_len);
 		if ( status < 0 ) {
 			if ( status == -EAGAIN ) {
 				SDL_Delay(1);
@@ -159,11 +268,11 @@
 			if ( status == -ESTRPIPE ) {
 				do {
 					SDL_Delay(1);
-					status = snd_pcm_resume(pcm_handle);
+					status = SDL_NAME(snd_pcm_resume)(pcm_handle);
 				} while ( status == -EAGAIN );
 			}
 			if ( status < 0 ) {
-				status = snd_pcm_prepare(pcm_handle);
+				status = SDL_NAME(snd_pcm_prepare)(pcm_handle);
 			}
 			if ( status < 0 ) {
 				/* Hmm, not much we can do - abort */
@@ -189,8 +298,8 @@
 		mixbuf = NULL;
 	}
 	if ( pcm_handle ) {
-		snd_pcm_drain(pcm_handle);
-		snd_pcm_close(pcm_handle);
+		SDL_NAME(snd_pcm_drain)(pcm_handle);
+		SDL_NAME(snd_pcm_close)(pcm_handle);
 		pcm_handle = NULL;
 	}
 }
@@ -204,25 +313,25 @@
 	Uint16               test_format;
 
 	/* Open the audio device */
-	status = snd_pcm_open(&pcm_handle, get_audio_device(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+	status = SDL_NAME(snd_pcm_open)(&pcm_handle, get_audio_device(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
 	if ( status < 0 ) {
-		SDL_SetError("Couldn't open audio device: %s", snd_strerror(status));
+		SDL_SetError("Couldn't open audio device: %s", SDL_NAME(snd_strerror)(status));
 		return(-1);
 	}
 
 	/* Figure out what the hardware is capable of */
 	snd_pcm_hw_params_alloca(&params);
-	status = snd_pcm_hw_params_any(pcm_handle, params);
+	status = SDL_NAME(snd_pcm_hw_params_any)(pcm_handle, params);
 	if ( status < 0 ) {
-		SDL_SetError("Couldn't get hardware config: %s", snd_strerror(status));
+		SDL_SetError("Couldn't get hardware config: %s", SDL_NAME(snd_strerror)(status));
 		ALSA_CloseAudio(this);
 		return(-1);
 	}
 
 	/* SDL only uses interleaved sample output */
-	status = snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
+	status = SDL_NAME(snd_pcm_hw_params_set_access)(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
 	if ( status < 0 ) {
-		SDL_SetError("Couldn't set interleaved access: %s", snd_strerror(status));
+		SDL_SetError("Couldn't set interleaved access: %s", SDL_NAME(snd_strerror)(status));
 		ALSA_CloseAudio(this);
 		return(-1);
 	}
@@ -255,7 +364,7 @@
 				break;
 		}
 		if ( format != 0 ) {
-			status = snd_pcm_hw_params_set_format(pcm_handle, params, format);
+			status = SDL_NAME(snd_pcm_hw_params_set_format)(pcm_handle, params, format);
 		}
 		if ( status < 0 ) {
 			test_format = SDL_NextAudioFormat();
@@ -269,9 +378,9 @@
 	spec->format = test_format;
 
 	/* Set the number of channels */
-	status = snd_pcm_hw_params_set_channels(pcm_handle, params, spec->channels);
+	status = SDL_NAME(snd_pcm_hw_params_set_channels)(pcm_handle, params, spec->channels);
 	if ( status < 0 ) {
-		status = snd_pcm_hw_params_get_channels(params);
+		status = SDL_NAME(snd_pcm_hw_params_get_channels)(params);
 		if ( (status <= 0) || (status > 2) ) {
 			SDL_SetError("Couldn't set audio channels");
 			ALSA_CloseAudio(this);
@@ -281,9 +390,9 @@
 	}
 
 	/* Set the audio rate */
-	status = snd_pcm_hw_params_set_rate_near(pcm_handle, params, spec->freq, NULL);
+	status = SDL_NAME(snd_pcm_hw_params_set_rate_near)(pcm_handle, params, spec->freq, NULL);
 	if ( status < 0 ) {
-		SDL_SetError("Couldn't set audio frequency: %s", snd_strerror(status));
+		SDL_SetError("Couldn't set audio frequency: %s", SDL_NAME(snd_strerror)(status));
 		ALSA_CloseAudio(this);
 		return(-1);
 	}
@@ -291,14 +400,14 @@
 
 	/* Set the buffer size, in samples */
 	frames = spec->samples;
-	frames = snd_pcm_hw_params_set_period_size_near(pcm_handle, params, frames, NULL);
+	frames = SDL_NAME(snd_pcm_hw_params_set_period_size_near)(pcm_handle, params, frames, NULL);
 	spec->samples = frames;
-	snd_pcm_hw_params_set_periods_near(pcm_handle, params, 2, NULL);
+	SDL_NAME(snd_pcm_hw_params_set_periods_near)(pcm_handle, params, 2, NULL);
 
 	/* "set" the hardware with the desired parameters */
-	status = snd_pcm_hw_params(pcm_handle, params);
+	status = SDL_NAME(snd_pcm_hw_params)(pcm_handle, params);
 	if ( status < 0 ) {
-		SDL_SetError("Couldn't set audio parameters: %s", snd_strerror(status));
+		SDL_SetError("Couldn't set audio parameters: %s", SDL_NAME(snd_strerror)(status));
 		ALSA_CloseAudio(this);
 		return(-1);
 	}
@@ -319,7 +428,7 @@
 	parent = getpid();
 
 	/* Switch to blocking mode for playback */
-	snd_pcm_nonblock(pcm_handle, 0);
+	SDL_NAME(snd_pcm_nonblock)(pcm_handle, 0);
 
 	/* We're ready to rock and roll. :-) */
 	return(0);