src/audio/alsa/SDL_alsa_audio.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 14 Dec 2001 12:30:25 +0000
changeset 249 e3d0d44f6f2e
parent 0 74212992fb08
child 252 e8157fcb3114
permissions -rw-r--r--
*** empty log message ***
     1 /*
     2     SDL - Simple DirectMedia Layer
     3     Copyright (C) 1997, 1998, 1999, 2000, 2001  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@devolution.com
    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 /* The tag name used by ALSA audio */
    45 #define DRIVER_NAME         "alsa"
    46 
    47 /* default card and device numbers as listed in dev/snd */
    48 static int card_no = 0;
    49 static int device_no = 0;
    50 
    51 /* default channel communication parameters */
    52 #define DEFAULT_CPARAMS_RATE 22050
    53 #define DEFAULT_CPARAMS_VOICES 1
    54 #define DEFAULT_CPARAMS_FRAG_SIZE 512
    55 #define DEFAULT_CPARAMS_FRAGS_MIN 1
    56 #define DEFAULT_CPARAMS_FRAGS_MAX -1
    57 
    58 /* Open the audio device for playback, and don't block if busy */
    59 #define OPEN_FLAGS	(SND_PCM_OPEN_PLAYBACK|SND_PCM_OPEN_NONBLOCK)
    60 
    61 /* Audio driver functions */
    62 static int PCM_OpenAudio(_THIS, SDL_AudioSpec *spec);
    63 static void PCM_WaitAudio(_THIS);
    64 static void PCM_PlayAudio(_THIS);
    65 static Uint8 *PCM_GetAudioBuf(_THIS);
    66 static void PCM_CloseAudio(_THIS);
    67 
    68 /* PCM transfer channel parameters initialize function */
    69 static void init_pcm_cparams(snd_pcm_channel_params_t* cparams)
    70 {
    71 	memset(cparams,0,sizeof(snd_pcm_channel_params_t));
    72 
    73 	cparams->channel = SND_PCM_CHANNEL_PLAYBACK;
    74 	cparams->mode = SND_PCM_MODE_BLOCK;
    75 	cparams->start_mode = SND_PCM_START_DATA; //_FULL
    76 	cparams->stop_mode  = SND_PCM_STOP_STOP;
    77 	cparams->format.format = SND_PCM_SFMT_S16_LE;
    78 	cparams->format.interleave = 1;
    79 	cparams->format.rate = DEFAULT_CPARAMS_RATE;
    80 	cparams->format.voices = DEFAULT_CPARAMS_VOICES;
    81 	cparams->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE;
    82 	cparams->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN;
    83 	cparams->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX;
    84 }
    85 
    86 /* Audio driver bootstrap functions */
    87 
    88 static int Audio_Available(void)
    89 /*
    90 	See if we can open a nonblocking channel.
    91 	Return value '1' means we can.
    92 	Return value '0' means we cannot.
    93 */
    94 {
    95 	int available;
    96 	int rval;
    97 	snd_pcm_t *handle;
    98 	snd_pcm_channel_params_t cparams;
    99 #ifdef DEBUG_AUDIO
   100 	snd_pcm_channel_status_t cstatus;
   101 #endif
   102 
   103 	available = 0;
   104 	handle = NULL;
   105 
   106 	init_pcm_cparams(&cparams);
   107 	
   108 	rval = snd_pcm_open(&handle, card_no, device_no, OPEN_FLAGS);
   109 	if (rval >= 0)
   110 	{
   111 		rval = snd_pcm_plugin_params(handle, &cparams);
   112 
   113 #ifdef DEBUG_AUDIO
   114 		snd_pcm_plugin_status(handle, &cstatus);
   115 		printf("status after snd_pcm_plugin_params call = %d\n",cstatus.status);
   116 #endif
   117 		if (rval >= 0)
   118 		{
   119 			available = 1;
   120 		}
   121 		else
   122 		{
   123 	        	SDL_SetError("snd_pcm_channel_params failed: %s\n", snd_strerror (rval));
   124 		}
   125 
   126         if ((rval = snd_pcm_close(handle)) < 0)
   127         {
   128             SDL_SetError("snd_pcm_close failed: %s\n",snd_strerror(rval));
   129 			available = 0;
   130         }
   131 	}
   132 	else
   133 	{
   134        SDL_SetError("snd_pcm_open failed: %s\n", snd_strerror(rval));
   135 	}
   136 
   137 	return(available);
   138 }
   139 
   140 static void Audio_DeleteDevice(SDL_AudioDevice *device)
   141 {
   142 	free(device->hidden);
   143 	free(device);
   144 }
   145 
   146 static SDL_AudioDevice *Audio_CreateDevice(int devindex)
   147 {
   148 	SDL_AudioDevice *this;
   149 
   150 	/* Initialize all variables that we clean on shutdown */
   151 	this = (SDL_AudioDevice *)malloc(sizeof(SDL_AudioDevice));
   152 	if ( this ) {
   153 		memset(this, 0, (sizeof *this));
   154 		this->hidden = (struct SDL_PrivateAudioData *)
   155 				malloc((sizeof *this->hidden));
   156 	}
   157 	if ( (this == NULL) || (this->hidden == NULL) ) {
   158 		SDL_OutOfMemory();
   159 		if ( this ) {
   160 			free(this);
   161 		}
   162 		return(0);
   163 	}
   164 	memset(this->hidden, 0, (sizeof *this->hidden));
   165 	audio_handle = NULL;
   166 
   167 	/* Set the function pointers */
   168 	this->OpenAudio = PCM_OpenAudio;
   169 	this->WaitAudio = PCM_WaitAudio;
   170 	this->PlayAudio = PCM_PlayAudio;
   171 	this->GetAudioBuf = PCM_GetAudioBuf;
   172 	this->CloseAudio = PCM_CloseAudio;
   173 
   174 	this->free = Audio_DeleteDevice;
   175 
   176 	return this;
   177 }
   178 
   179 AudioBootStrap ALSA_bootstrap = {
   180 	DRIVER_NAME, "ALSA PCM audio",
   181 	Audio_Available, Audio_CreateDevice
   182 };
   183 
   184 /* This function waits until it is possible to write a full sound buffer */
   185 static void PCM_WaitAudio(_THIS)
   186 {
   187 
   188 	/* Check to see if the thread-parent process is still alive */
   189 	{ static int cnt = 0;
   190 		/* Note that this only works with thread implementations 
   191 		   that use a different process id for each thread.
   192 		*/
   193 		if (parent && (((++cnt)%10) == 0)) { /* Check every 10 loops */
   194 			if ( kill(parent, 0) < 0 ) {
   195 				this->enabled = 0;
   196 			}
   197 		}
   198 	}
   199 
   200 	/* See if we need to use timed audio synchronization */
   201 	if ( frame_ticks ) 
   202 	{
   203 		/* Use timer for general audio synchronization */
   204 		Sint32 ticks;
   205 
   206 		ticks = ((Sint32)(next_frame - SDL_GetTicks()))-FUDGE_TICKS;
   207 		if ( ticks > 0 ) 
   208 		{
   209 			SDL_Delay(ticks);
   210 		}
   211 	}
   212     else 
   213 	{
   214     	/* Use select() for audio synchronization */
   215 		fd_set fdset;
   216 	    struct timeval timeout;
   217     	FD_ZERO(&fdset);
   218 	    FD_SET(audio_fd, &fdset);
   219     	timeout.tv_sec = 10;
   220 	    timeout.tv_usec = 0;
   221 #ifdef DEBUG_AUDIO
   222     	fprintf(stderr, "Waiting for audio to get ready\n");
   223 #endif
   224 	    if ( select(audio_fd+1, NULL, &fdset, NULL, &timeout) <= 0 ) 
   225 		{
   226             const char *message =
   227             "Audio timeout - buggy audio driver? (disabled)";
   228 	        /* In general we should never print to the screen,
   229        	       but in this case we have no other way of letting
   230                the user know what happened.
   231             */
   232        	    fprintf(stderr, "SDL: %s\n", message);
   233    	        this->enabled = 0;
   234             /* Don't try to close - may hang */
   235             audio_fd = -1;
   236 #ifdef DEBUG_AUDIO
   237        	    fprintf(stderr, "Done disabling audio\n");
   238 #endif
   239     	    }
   240 #ifdef DEBUG_AUDIO
   241         fprintf(stderr, "Ready!\n");
   242 #endif
   243     }
   244 }
   245 
   246 static snd_pcm_channel_status_t cstatus;
   247 
   248 static void PCM_PlayAudio(_THIS)
   249 {
   250     int written, rval;
   251 
   252     /* Write the audio data, checking for EAGAIN (buffer full) and underrun */
   253     do {
   254 		written = snd_pcm_plugin_write(audio_handle, pcm_buf, pcm_len);
   255 #ifdef DEBUG_AUDIO
   256 		fprintf(stderr, "written = %d pcm_len = %d\n",written,pcm_len);
   257 #endif
   258 		if (written != pcm_len)
   259 		{
   260 	        if (errno == EAGAIN) 
   261 			{
   262             	SDL_Delay(1);   /* Let a little CPU time go by and try to write again */
   263 #ifdef DEBUG_AUDIO
   264 				fprintf(stderr, "errno == EAGAIN\n");
   265 #endif
   266         	}
   267 			else
   268 			{
   269 		        if( (rval = snd_pcm_plugin_status(audio_handle, &cstatus)) < 0 )
   270         		{
   271 		            SDL_SetError("snd_pcm_plugin_status failed: %s\n", snd_strerror(rval));
   272         		    return;
   273 		        }
   274 				if ( (cstatus.status == SND_PCM_STATUS_UNDERRUN)
   275 					||(cstatus.status == SND_PCM_STATUS_READY) )
   276 				{
   277 #ifdef DEBUG_AUDIO
   278 					fprintf(stderr, "buffer underrun\n");
   279 #endif
   280 					if ( (rval = snd_pcm_plugin_prepare (audio_handle,SND_PCM_CHANNEL_PLAYBACK)) < 0 )
   281 					{
   282 						SDL_SetError("snd_pcm_plugin_prepare failed: %s\n",snd_strerror(rval) );
   283 						return;
   284 					}
   285 					/* if we reach here, try to write again */
   286 				}
   287 			}
   288 		}
   289     } while ( (written < 0) && ((errno == 0) || (errno == EAGAIN)) );
   290 
   291     /* Set the next write frame */
   292    if ( frame_ticks ) {
   293 	    next_frame += frame_ticks;
   294 	}
   295 
   296     /* If we couldn't write, assume fatal error for now */
   297     if ( written < 0 ) {
   298         this->enabled = 0;
   299     }
   300 	return;
   301 }
   302 
   303 static Uint8 *PCM_GetAudioBuf(_THIS)
   304 {
   305 	return(pcm_buf);
   306 }
   307 
   308 static void PCM_CloseAudio(_THIS)
   309 {
   310 	int rval;
   311 
   312 	if ( pcm_buf != NULL ) {
   313 		free(pcm_buf);
   314 		pcm_buf = NULL;
   315 	}
   316 	if ( audio_handle != NULL ) {
   317 		if ((rval = snd_pcm_plugin_flush(audio_handle,SND_PCM_CHANNEL_PLAYBACK)) < 0)
   318 		{
   319         	SDL_SetError("snd_pcm_plugin_flush failed: %s\n",snd_strerror(rval));
   320 			return;
   321 		}
   322 		if ((rval = snd_pcm_close(audio_handle)) < 0)
   323 		{
   324 			SDL_SetError("snd_pcm_close failed: %s\n",snd_strerror(rval));
   325 			return;
   326 		}
   327 		audio_handle = NULL;
   328 	}
   329 }
   330 
   331 static int PCM_OpenAudio(_THIS, SDL_AudioSpec *spec)
   332 {
   333 	int rval;
   334 	snd_pcm_channel_params_t cparams;
   335 	snd_pcm_channel_setup_t  csetup;
   336 	int format;
   337 	Uint16 test_format;
   338 	int twidth;
   339 
   340 	/* initialize channel transfer parameters to default */
   341 	init_pcm_cparams(&cparams);
   342 
   343 	/* Reset the timer synchronization flag */
   344 	frame_ticks = 0.0;
   345 
   346 	/* Open the audio device */
   347 	
   348 	rval = snd_pcm_open(&audio_handle, card_no, device_no, OPEN_FLAGS);
   349 	if ( rval < 0 ) {
   350 		SDL_SetError("snd_pcm_open failed: %s\n", snd_strerror(rval));
   351 		return(-1);
   352 	}
   353 
   354 #ifdef PLUGIN_DISABLE_MMAP /* This is gone in newer versions of ALSA? */
   355     /* disable count status parameter */
   356     if ((rval = snd_plugin_set_disable(audio_handle, PLUGIN_DISABLE_MMAP))<0)
   357     {
   358         SDL_SetError("snd_plugin_set_disable failed: %s\n", snd_strerror(rval));
   359         return(-1);
   360     }
   361 #endif
   362 
   363 	pcm_buf = NULL;
   364 
   365 	/* Try for a closest match on audio format */
   366 	format = 0;
   367 	for ( test_format = SDL_FirstAudioFormat(spec->format);
   368 						! format && test_format; ) 
   369 	{
   370 #ifdef DEBUG_AUDIO
   371 		fprintf(stderr, "Trying format 0x%4.4x spec->samples %d\n", test_format,spec->samples);
   372 #endif
   373 			/* if match found set format to equivalent ALSA format */
   374         switch ( test_format ) {
   375 			case AUDIO_U8:
   376 				format = SND_PCM_SFMT_U8;
   377 				cparams.buf.block.frag_size = spec->samples * spec->channels;
   378 				break;
   379 			case AUDIO_S8:
   380 				format = SND_PCM_SFMT_S8;
   381 				cparams.buf.block.frag_size = spec->samples * spec->channels;
   382 				break;
   383 			case AUDIO_S16LSB:
   384 				format = SND_PCM_SFMT_S16_LE;
   385 				cparams.buf.block.frag_size = spec->samples*2 * spec->channels;
   386 				break;
   387 			case AUDIO_S16MSB:
   388 				format = SND_PCM_SFMT_S16_BE;
   389 				cparams.buf.block.frag_size = spec->samples*2 * spec->channels;
   390 				break;
   391 			case AUDIO_U16LSB:
   392 				format = SND_PCM_SFMT_U16_LE;
   393 				cparams.buf.block.frag_size = spec->samples*2 * spec->channels;
   394 				break;
   395 			case AUDIO_U16MSB:
   396 				format = SND_PCM_SFMT_U16_BE;
   397 				cparams.buf.block.frag_size = spec->samples*2 * spec->channels;
   398 				break;
   399 			default:
   400 				break;
   401 		}
   402 		if ( ! format ) {
   403 			test_format = SDL_NextAudioFormat();
   404 		}
   405 	}
   406 	if ( format == 0 ) {
   407 		SDL_SetError("Couldn't find any hardware audio formats");
   408 		return(-1);
   409 	}
   410 	spec->format = test_format;
   411 
   412 	/* Set the audio format */
   413 	cparams.format.format = format;
   414 
   415 	/* Set mono or stereo audio (currently only two channels supported) */
   416 	cparams.format.voices = spec->channels;
   417 	
   418 	#ifdef DEBUG_AUDIO
   419 	printf("intializing channels %d\n", cparams.format.voices);
   420 	#endif
   421 	
   422 	/* Set rate */
   423 	cparams.format.rate = spec->freq ;
   424 
   425 	/* Setup the transfer parameters according to cparams */
   426 	rval = snd_pcm_plugin_params(audio_handle, &cparams);
   427 	if (rval < 0) {
   428 		SDL_SetError("snd_pcm_channel_params failed: %s\n", snd_strerror (rval));
   429 		return(-1);
   430 	}
   431 
   432     /*  Make sure channel is setup right one last time */
   433     memset( &csetup, 0, sizeof( csetup ) );
   434     csetup.channel = SND_PCM_CHANNEL_PLAYBACK;
   435     if ( snd_pcm_plugin_setup( audio_handle, &csetup ) < 0 )
   436     {
   437         SDL_SetError("Unable to setup playback channel\n" );
   438         return(-1);
   439     }
   440 
   441 #ifdef DEBUG_AUDIO
   442     else
   443     {
   444         fprintf(stderr,"requested format: %d\n",cparams.format.format);
   445         fprintf(stderr,"requested frag size: %d\n",cparams.buf.block.frag_size);
   446         fprintf(stderr,"requested max frags: %d\n\n",cparams.buf.block.frags_max);
   447 
   448         fprintf(stderr,"real format: %d\n", csetup.format.format );
   449         fprintf(stderr,"real frag size : %d\n", csetup.buf.block.frag_size );
   450 		fprintf(stderr,"real max frags : %d\n", csetup.buf.block.frags_max );
   451     }
   452 #endif // DEBUG_AUDIO
   453 
   454     /*  Allocate memory to the audio buffer and initialize with silence
   455         (Note that buffer size must be a multiple of fragment size, so find closest multiple)
   456     */
   457     
   458     twidth = snd_pcm_format_width(format);
   459     if (twidth < 0) {
   460         printf("snd_pcm_format_width failed\n");
   461         twidth = 0;
   462     }
   463 #ifdef DEBUG_AUDIO
   464     printf("format is %d bits wide\n",twidth);
   465 #endif      
   466     
   467     pcm_len = csetup.buf.block.frag_size * (twidth/8) * csetup.format.voices ;
   468     
   469 #ifdef DEBUG_AUDIO    
   470     printf("pcm_len set to %d\n", pcm_len);
   471 #endif
   472     
   473     if (pcm_len == 0)
   474     {
   475         pcm_len = csetup.buf.block.frag_size;
   476     }
   477     
   478     pcm_buf = (Uint8*)malloc(pcm_len);
   479     if (pcm_buf == NULL) {
   480         SDL_SetError("pcm_buf malloc failed\n");
   481         return(-1);
   482     }
   483     memset(pcm_buf,spec->silence,pcm_len);
   484 
   485 #ifdef DEBUG_AUDIO
   486 	fprintf(stderr,"pcm_buf malloced and silenced.\n");
   487 #endif
   488 
   489     /* get the file descriptor */
   490     if( (audio_fd = snd_pcm_file_descriptor(audio_handle, device_no)) < 0)
   491     {
   492        fprintf(stderr, "snd_pcm_file_descriptor failed with error code: %d\n", audio_fd);
   493     }
   494 
   495 	/* Trigger audio playback */
   496 	rval = snd_pcm_plugin_prepare( audio_handle, SND_PCM_CHANNEL_PLAYBACK);
   497 	if (rval < 0) {
   498        SDL_SetError("snd_pcm_plugin_prepare failed: %s\n", snd_strerror (rval));
   499        return(-1);
   500 	}
   501 	rval =  snd_pcm_playback_go(audio_handle);
   502     if (rval < 0) {
   503        SDL_SetError("snd_pcm_playback_go failed: %s\n", snd_strerror (rval));
   504        return(-1);
   505     }
   506 
   507     /* Check to see if we need to use select() workaround */
   508     { char *workaround;
   509         workaround = getenv("SDL_DSP_NOSELECT");
   510         if ( workaround ) {
   511             frame_ticks = (float)(spec->samples*1000)/spec->freq;
   512             next_frame = SDL_GetTicks()+frame_ticks;
   513         }
   514     }
   515 
   516 	/* Get the parent process id (we're the parent of the audio thread) */
   517 	parent = getpid();
   518 
   519 	/* We're ready to rock and roll. :-) */
   520 	return(0);
   521 }