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