From e126a783e8fcf3b80d7f712793b4d88c88c89431 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 2 Mar 2004 12:49:16 +0000 Subject: [PATCH] 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 | 18 +++- src/audio/alsa/Makefile.am | 2 + src/audio/alsa/SDL_alsa_audio.c | 155 +++++++++++++++++++++++++++----- 3 files changed, 150 insertions(+), 25 deletions(-) diff --git a/configure.in b/configure.in index 317c6caa4..6d315e6fd 100644 --- a/configure.in +++ b/configure.in @@ -295,8 +295,22 @@ CheckALSA() 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 diff --git a/src/audio/alsa/Makefile.am b/src/audio/alsa/Makefile.am index f9e5280d6..4f30d158f 100644 --- a/src/audio/alsa/Makefile.am +++ b/src/audio/alsa/Makefile.am @@ -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 diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c index e0aae5fb2..60d8fbadc 100644 --- a/src/audio/alsa/SDL_alsa_audio.c +++ b/src/audio/alsa/SDL_alsa_audio.c @@ -41,6 +41,16 @@ #include "SDL_timer.h" #include "SDL_alsa_audio.h" +#ifdef ALSA_DYNAMIC +#define __USE_GNU +#include +#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 void ALSA_PlayAudio(_THIS); 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 @@ static int Audio_Available(void) 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 @@ static void Audio_DeleteDevice(SDL_AudioDevice *device) { free(device->hidden); free(device); + UnloadALSALibrary(); } static SDL_AudioDevice *Audio_CreateDevice(int devindex) @@ -93,6 +201,7 @@ static SDL_AudioDevice *Audio_CreateDevice(int devindex) 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 @@ static void ALSA_PlayAudio(_THIS) 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 @@ static void ALSA_PlayAudio(_THIS) 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 @@ static void ALSA_CloseAudio(_THIS) 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 @@ static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec) 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(¶ms); - 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 @@ static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec) 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 @@ static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec) 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 @@ static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec) } /* 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 @@ static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec) /* 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 @@ static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec) 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);