src/audio/alsa/SDL_alsa_audio.c
author Sam Lantinga <slouken@libsdl.org>
Tue, 02 Mar 2004 12:49:16 +0000
changeset 865 92615154bb68
parent 769 b8d311d90021
child 939 c7c04f811994
permissions -rw-r--r--
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.
     1 /*
     2     SDL - Simple DirectMedia Layer
     3     Copyright (C) 1997-2004 Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Library General Public
     7     License as published by the Free Software Foundation; either
     8     version 2 of the License, or (at your option) any later version.
     9 
    10     This library is distributed in the hope that it will be useful,
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13     Library General Public License for more details.
    14 
    15     You should have received a copy of the GNU Library General Public
    16     License along with this library; if not, write to the Free
    17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    18 
    19     Sam Lantinga
    20     slouken@libsdl.org
    21 */
    22 
    23 
    24 
    25 /* Allow access to a raw mixing buffer */
    26 
    27 #include <stdlib.h>
    28 #include <stdio.h>
    29 #include <string.h>
    30 #include <errno.h>
    31 #include <unistd.h>
    32 #include <fcntl.h>
    33 #include <signal.h>
    34 #include <sys/types.h>
    35 #include <sys/time.h>
    36 
    37 #include "SDL_audio.h"
    38 #include "SDL_error.h"
    39 #include "SDL_audiomem.h"
    40 #include "SDL_audio_c.h"
    41 #include "SDL_timer.h"
    42 #include "SDL_alsa_audio.h"
    43 
    44 #ifdef ALSA_DYNAMIC
    45 #define __USE_GNU
    46 #include <dlfcn.h>
    47 #include "SDL_name.h"
    48 #include "SDL_loadso.h"
    49 #else
    50 #define SDL_NAME(X)	X
    51 #endif
    52 
    53 
    54 /* The tag name used by ALSA audio */
    55 #define DRIVER_NAME         "alsa"
    56 
    57 /* The default ALSA audio driver */
    58 #define DEFAULT_DEVICE	"default"
    59 
    60 /* Audio driver functions */
    61 static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec);
    62 static void ALSA_WaitAudio(_THIS);
    63 static void ALSA_PlayAudio(_THIS);
    64 static Uint8 *ALSA_GetAudioBuf(_THIS);
    65 static void ALSA_CloseAudio(_THIS);
    66 
    67 #ifdef ALSA_DYNAMIC
    68 
    69 static const char *alsa_library = ALSA_DYNAMIC;
    70 static void *alsa_handle = NULL;
    71 static int alsa_loaded = 0;
    72 
    73 static int (*SDL_snd_pcm_open)(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode);
    74 static int (*SDL_NAME(snd_pcm_open))(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode);
    75 static int (*SDL_NAME(snd_pcm_close))(snd_pcm_t *pcm);
    76 static snd_pcm_sframes_t (*SDL_NAME(snd_pcm_writei))(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
    77 static int (*SDL_NAME(snd_pcm_resume))(snd_pcm_t *pcm);
    78 static int (*SDL_NAME(snd_pcm_prepare))(snd_pcm_t *pcm);
    79 static int (*SDL_NAME(snd_pcm_drain))(snd_pcm_t *pcm);
    80 static const char *(*SDL_NAME(snd_strerror))(int errnum);
    81 static size_t (*SDL_NAME(snd_pcm_hw_params_sizeof))(void);
    82 static int (*SDL_NAME(snd_pcm_hw_params_any))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
    83 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);
    84 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);
    85 static int (*SDL_NAME(snd_pcm_hw_params_set_channels))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
    86 static int (*SDL_NAME(snd_pcm_hw_params_get_channels))(const snd_pcm_hw_params_t *params);
    87 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);
    88 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);
    89 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);
    90 static int (*SDL_NAME(snd_pcm_hw_params))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
    91 static int (*SDL_NAME(snd_pcm_nonblock))(snd_pcm_t *pcm, int nonblock);
    92 #define snd_pcm_hw_params_sizeof SDL_NAME(snd_pcm_hw_params_sizeof)
    93 
    94 static struct {
    95 	const char *name;
    96 	void **func;
    97 } alsa_functions[] = {
    98 	{ "snd_pcm_open",	(void**)&SDL_NAME(snd_pcm_open)		},
    99 	{ "snd_pcm_close",	(void**)&SDL_NAME(snd_pcm_close)	},
   100 	{ "snd_pcm_writei",	(void**)&SDL_NAME(snd_pcm_writei)	},
   101 	{ "snd_pcm_resume",	(void**)&SDL_NAME(snd_pcm_resume)	},
   102 	{ "snd_pcm_prepare",	(void**)&SDL_NAME(snd_pcm_prepare)	},
   103 	{ "snd_pcm_drain",	(void**)&SDL_NAME(snd_pcm_drain)	},
   104 	{ "snd_strerror",	(void**)&SDL_NAME(snd_strerror)		},
   105 	{ "snd_pcm_hw_params_sizeof",		(void**)&SDL_NAME(snd_pcm_hw_params_sizeof)		},
   106 	{ "snd_pcm_hw_params_any",		(void**)&SDL_NAME(snd_pcm_hw_params_any)		},
   107 	{ "snd_pcm_hw_params_set_access",	(void**)&SDL_NAME(snd_pcm_hw_params_set_access)		},
   108 	{ "snd_pcm_hw_params_set_format",	(void**)&SDL_NAME(snd_pcm_hw_params_set_format)		},
   109 	{ "snd_pcm_hw_params_set_channels",	(void**)&SDL_NAME(snd_pcm_hw_params_set_channels)	},
   110 	{ "snd_pcm_hw_params_get_channels",	(void**)&SDL_NAME(snd_pcm_hw_params_get_channels)	},
   111 	{ "snd_pcm_hw_params_set_rate_near",	(void**)&SDL_NAME(snd_pcm_hw_params_set_rate_near)	},
   112 	{ "snd_pcm_hw_params_set_period_size_near",	(void**)&SDL_NAME(snd_pcm_hw_params_set_period_size_near)	},
   113 	{ "snd_pcm_hw_params_set_periods_near",	(void**)&SDL_NAME(snd_pcm_hw_params_set_periods_near)	},
   114 	{ "snd_pcm_hw_params",	(void**)&SDL_NAME(snd_pcm_hw_params)	},
   115 	{ "snd_pcm_nonblock",	(void**)&SDL_NAME(snd_pcm_nonblock)	},
   116 };
   117 
   118 static void UnloadALSALibrary(void) {
   119 	if (alsa_loaded) {
   120 /*		SDL_UnloadObject(alsa_handle);*/
   121 		dlclose(alsa_handle);
   122 		alsa_handle = NULL;
   123 		alsa_loaded = 0;
   124 	}
   125 }
   126 
   127 static int LoadALSALibrary(void) {
   128 	int i, retval = -1;
   129 
   130 /*	alsa_handle = SDL_LoadObject(alsa_library);*/
   131 	alsa_handle = dlopen(alsa_library,RTLD_NOW);
   132 	if (alsa_handle) {
   133 		alsa_loaded = 1;
   134 		retval = 0;
   135 		for (i = 0; i < SDL_TABLESIZE(alsa_functions); i++) {
   136 /*			*alsa_functions[i].func = SDL_LoadFunction(alsa_handle,alsa_functions[i].name);*/
   137 			*alsa_functions[i].func = dlvsym(alsa_handle,alsa_functions[i].name,"ALSA_0.9");
   138 			if (!*alsa_functions[i].func) {
   139 				retval = -1;
   140 				UnloadALSALibrary();
   141 				break;
   142 			}
   143 		}
   144 	}
   145 	return retval;
   146 }
   147 
   148 #else
   149 
   150 static void UnloadALSALibrary(void) {
   151 	return;
   152 }
   153 
   154 static int LoadALSALibrary(void) {
   155 	return 0;
   156 }
   157 
   158 #endif /* ALSA_DYNAMIC */
   159 
   160 static const char *get_audio_device()
   161 {
   162 	const char *device;
   163 	
   164 	device = getenv("AUDIODEV");	/* Is there a standard variable name? */
   165 	if ( device == NULL ) {
   166 		device = DEFAULT_DEVICE;
   167 	}
   168 	return device;
   169 }
   170 
   171 /* Audio driver bootstrap functions */
   172 
   173 static int Audio_Available(void)
   174 {
   175 	int available;
   176 	int status;
   177 	snd_pcm_t *handle;
   178 
   179 	available = 0;
   180 	if (LoadALSALibrary() < 0) {
   181 		return available;
   182 	}
   183 	status = SDL_NAME(snd_pcm_open)(&handle, get_audio_device(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
   184 	if ( status >= 0 ) {
   185 		available = 1;
   186         	SDL_NAME(snd_pcm_close)(handle);
   187 	}
   188 	UnloadALSALibrary();
   189 	return(available);
   190 }
   191 
   192 static void Audio_DeleteDevice(SDL_AudioDevice *device)
   193 {
   194 	free(device->hidden);
   195 	free(device);
   196 	UnloadALSALibrary();
   197 }
   198 
   199 static SDL_AudioDevice *Audio_CreateDevice(int devindex)
   200 {
   201 	SDL_AudioDevice *this;
   202 
   203 	/* Initialize all variables that we clean on shutdown */
   204 	LoadALSALibrary();
   205 	this = (SDL_AudioDevice *)malloc(sizeof(SDL_AudioDevice));
   206 	if ( this ) {
   207 		memset(this, 0, (sizeof *this));
   208 		this->hidden = (struct SDL_PrivateAudioData *)
   209 				malloc((sizeof *this->hidden));
   210 	}
   211 	if ( (this == NULL) || (this->hidden == NULL) ) {
   212 		SDL_OutOfMemory();
   213 		if ( this ) {
   214 			free(this);
   215 		}
   216 		return(0);
   217 	}
   218 	memset(this->hidden, 0, (sizeof *this->hidden));
   219 
   220 	/* Set the function pointers */
   221 	this->OpenAudio = ALSA_OpenAudio;
   222 	this->WaitAudio = ALSA_WaitAudio;
   223 	this->PlayAudio = ALSA_PlayAudio;
   224 	this->GetAudioBuf = ALSA_GetAudioBuf;
   225 	this->CloseAudio = ALSA_CloseAudio;
   226 
   227 	this->free = Audio_DeleteDevice;
   228 
   229 	return this;
   230 }
   231 
   232 AudioBootStrap ALSA_bootstrap = {
   233 	DRIVER_NAME, "ALSA 0.9 PCM audio",
   234 	Audio_Available, Audio_CreateDevice
   235 };
   236 
   237 /* This function waits until it is possible to write a full sound buffer */
   238 static void ALSA_WaitAudio(_THIS)
   239 {
   240 	/* Check to see if the thread-parent process is still alive */
   241 	{ static int cnt = 0;
   242 		/* Note that this only works with thread implementations 
   243 		   that use a different process id for each thread.
   244 		*/
   245 		if (parent && (((++cnt)%10) == 0)) { /* Check every 10 loops */
   246 			if ( kill(parent, 0) < 0 ) {
   247 				this->enabled = 0;
   248 			}
   249 		}
   250 	}
   251 }
   252 
   253 static void ALSA_PlayAudio(_THIS)
   254 {
   255 	int           status;
   256 	int           sample_len;
   257 	signed short *sample_buf;
   258 
   259 	sample_len = this->spec.samples;
   260 	sample_buf = (signed short *)mixbuf;
   261 	while ( sample_len > 0 ) {
   262 		status = SDL_NAME(snd_pcm_writei)(pcm_handle, sample_buf, sample_len);
   263 		if ( status < 0 ) {
   264 			if ( status == -EAGAIN ) {
   265 				SDL_Delay(1);
   266 				continue;
   267 			}
   268 			if ( status == -ESTRPIPE ) {
   269 				do {
   270 					SDL_Delay(1);
   271 					status = SDL_NAME(snd_pcm_resume)(pcm_handle);
   272 				} while ( status == -EAGAIN );
   273 			}
   274 			if ( status < 0 ) {
   275 				status = SDL_NAME(snd_pcm_prepare)(pcm_handle);
   276 			}
   277 			if ( status < 0 ) {
   278 				/* Hmm, not much we can do - abort */
   279 				this->enabled = 0;
   280 				return;
   281 			}
   282 			continue;
   283 		}
   284 		sample_buf += status * this->spec.channels;
   285 		sample_len -= status;
   286 	}
   287 }
   288 
   289 static Uint8 *ALSA_GetAudioBuf(_THIS)
   290 {
   291 	return(mixbuf);
   292 }
   293 
   294 static void ALSA_CloseAudio(_THIS)
   295 {
   296 	if ( mixbuf != NULL ) {
   297 		SDL_FreeAudioMem(mixbuf);
   298 		mixbuf = NULL;
   299 	}
   300 	if ( pcm_handle ) {
   301 		SDL_NAME(snd_pcm_drain)(pcm_handle);
   302 		SDL_NAME(snd_pcm_close)(pcm_handle);
   303 		pcm_handle = NULL;
   304 	}
   305 }
   306 
   307 static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec)
   308 {
   309 	int                  status;
   310 	snd_pcm_hw_params_t *params;
   311 	snd_pcm_format_t     format;
   312 	snd_pcm_uframes_t    frames;
   313 	Uint16               test_format;
   314 
   315 	/* Open the audio device */
   316 	status = SDL_NAME(snd_pcm_open)(&pcm_handle, get_audio_device(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
   317 	if ( status < 0 ) {
   318 		SDL_SetError("Couldn't open audio device: %s", SDL_NAME(snd_strerror)(status));
   319 		return(-1);
   320 	}
   321 
   322 	/* Figure out what the hardware is capable of */
   323 	snd_pcm_hw_params_alloca(&params);
   324 	status = SDL_NAME(snd_pcm_hw_params_any)(pcm_handle, params);
   325 	if ( status < 0 ) {
   326 		SDL_SetError("Couldn't get hardware config: %s", SDL_NAME(snd_strerror)(status));
   327 		ALSA_CloseAudio(this);
   328 		return(-1);
   329 	}
   330 
   331 	/* SDL only uses interleaved sample output */
   332 	status = SDL_NAME(snd_pcm_hw_params_set_access)(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
   333 	if ( status < 0 ) {
   334 		SDL_SetError("Couldn't set interleaved access: %s", SDL_NAME(snd_strerror)(status));
   335 		ALSA_CloseAudio(this);
   336 		return(-1);
   337 	}
   338 
   339 	/* Try for a closest match on audio format */
   340 	status = -1;
   341 	for ( test_format = SDL_FirstAudioFormat(spec->format);
   342 	      test_format && (status < 0); ) {
   343 		switch ( test_format ) {
   344 			case AUDIO_U8:
   345 				format = SND_PCM_FORMAT_U8;
   346 				break;
   347 			case AUDIO_S8:
   348 				format = SND_PCM_FORMAT_S8;
   349 				break;
   350 			case AUDIO_S16LSB:
   351 				format = SND_PCM_FORMAT_S16_LE;
   352 				break;
   353 			case AUDIO_S16MSB:
   354 				format = SND_PCM_FORMAT_S16_BE;
   355 				break;
   356 			case AUDIO_U16LSB:
   357 				format = SND_PCM_FORMAT_U16_LE;
   358 				break;
   359 			case AUDIO_U16MSB:
   360 				format = SND_PCM_FORMAT_U16_BE;
   361 				break;
   362 			default:
   363 				format = 0;
   364 				break;
   365 		}
   366 		if ( format != 0 ) {
   367 			status = SDL_NAME(snd_pcm_hw_params_set_format)(pcm_handle, params, format);
   368 		}
   369 		if ( status < 0 ) {
   370 			test_format = SDL_NextAudioFormat();
   371 		}
   372 	}
   373 	if ( status < 0 ) {
   374 		SDL_SetError("Couldn't find any hardware audio formats");
   375 		ALSA_CloseAudio(this);
   376 		return(-1);
   377 	}
   378 	spec->format = test_format;
   379 
   380 	/* Set the number of channels */
   381 	status = SDL_NAME(snd_pcm_hw_params_set_channels)(pcm_handle, params, spec->channels);
   382 	if ( status < 0 ) {
   383 		status = SDL_NAME(snd_pcm_hw_params_get_channels)(params);
   384 		if ( (status <= 0) || (status > 2) ) {
   385 			SDL_SetError("Couldn't set audio channels");
   386 			ALSA_CloseAudio(this);
   387 			return(-1);
   388 		}
   389 		spec->channels = status;
   390 	}
   391 
   392 	/* Set the audio rate */
   393 	status = SDL_NAME(snd_pcm_hw_params_set_rate_near)(pcm_handle, params, spec->freq, NULL);
   394 	if ( status < 0 ) {
   395 		SDL_SetError("Couldn't set audio frequency: %s", SDL_NAME(snd_strerror)(status));
   396 		ALSA_CloseAudio(this);
   397 		return(-1);
   398 	}
   399 	spec->freq = status;
   400 
   401 	/* Set the buffer size, in samples */
   402 	frames = spec->samples;
   403 	frames = SDL_NAME(snd_pcm_hw_params_set_period_size_near)(pcm_handle, params, frames, NULL);
   404 	spec->samples = frames;
   405 	SDL_NAME(snd_pcm_hw_params_set_periods_near)(pcm_handle, params, 2, NULL);
   406 
   407 	/* "set" the hardware with the desired parameters */
   408 	status = SDL_NAME(snd_pcm_hw_params)(pcm_handle, params);
   409 	if ( status < 0 ) {
   410 		SDL_SetError("Couldn't set audio parameters: %s", SDL_NAME(snd_strerror)(status));
   411 		ALSA_CloseAudio(this);
   412 		return(-1);
   413 	}
   414 
   415 	/* Calculate the final parameters for this audio specification */
   416 	SDL_CalculateAudioSpec(spec);
   417 
   418 	/* Allocate mixing buffer */
   419 	mixlen = spec->size;
   420 	mixbuf = (Uint8 *)SDL_AllocAudioMem(mixlen);
   421 	if ( mixbuf == NULL ) {
   422 		ALSA_CloseAudio(this);
   423 		return(-1);
   424 	}
   425 	memset(mixbuf, spec->silence, spec->size);
   426 
   427 	/* Get the parent process id (we're the parent of the audio thread) */
   428 	parent = getpid();
   429 
   430 	/* Switch to blocking mode for playback */
   431 	SDL_NAME(snd_pcm_nonblock)(pcm_handle, 0);
   432 
   433 	/* We're ready to rock and roll. :-) */
   434 	return(0);
   435 }