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