Mercurial > sdl-ios-xcode
diff src/audio/alsa/SDL_alsa_audio.c @ 354:30935e76acb5
Updated ALSA audio support for ALSA 0.9
author | Sam Lantinga <slouken@libsdl.org> |
---|---|
date | Mon, 15 Apr 2002 07:38:54 +0000 |
parents | f6ffac90895c |
children | a1e54d1ba16f |
line wrap: on
line diff
--- a/src/audio/alsa/SDL_alsa_audio.c Mon Apr 15 04:53:41 2002 +0000 +++ b/src/audio/alsa/SDL_alsa_audio.c Mon Apr 15 07:38:54 2002 +0000 @@ -44,96 +44,41 @@ /* The tag name used by ALSA audio */ #define DRIVER_NAME "alsa" -/* default card and device numbers as listed in dev/snd */ -static int card_no = 0; -static int device_no = 0; - -/* default channel communication parameters */ -#define DEFAULT_CPARAMS_RATE 22050 -#define DEFAULT_CPARAMS_VOICES 1 -#define DEFAULT_CPARAMS_FRAG_SIZE 512 -#define DEFAULT_CPARAMS_FRAGS_MIN 1 -#define DEFAULT_CPARAMS_FRAGS_MAX -1 - -/* Open the audio device for playback, and don't block if busy */ -#define OPEN_FLAGS (SND_PCM_OPEN_PLAYBACK|SND_PCM_OPEN_NONBLOCK) +/* The default ALSA audio driver */ +#define DEFAULT_DEVICE "plughw:0,0" /* Audio driver functions */ -static int PCM_OpenAudio(_THIS, SDL_AudioSpec *spec); -static void PCM_WaitAudio(_THIS); -static void PCM_PlayAudio(_THIS); -static Uint8 *PCM_GetAudioBuf(_THIS); -static void PCM_CloseAudio(_THIS); - -/* PCM transfer channel parameters initialize function */ -static void init_pcm_cparams(snd_pcm_channel_params_t* cparams) -{ - memset(cparams,0,sizeof(snd_pcm_channel_params_t)); +static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec); +static void ALSA_WaitAudio(_THIS); +static void ALSA_PlayAudio(_THIS); +static Uint8 *ALSA_GetAudioBuf(_THIS); +static void ALSA_CloseAudio(_THIS); - cparams->channel = SND_PCM_CHANNEL_PLAYBACK; - cparams->mode = SND_PCM_MODE_BLOCK; - cparams->start_mode = SND_PCM_START_DATA; //_FULL - cparams->stop_mode = SND_PCM_STOP_STOP; - cparams->format.format = SND_PCM_SFMT_S16_LE; - cparams->format.interleave = 1; - cparams->format.rate = DEFAULT_CPARAMS_RATE; - cparams->format.voices = DEFAULT_CPARAMS_VOICES; - cparams->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE; - cparams->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN; - cparams->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX; +static const char *get_audio_device() +{ + const char *device; + + device = getenv("AUDIODEV"); /* Is there a standard variable name? */ + if ( device == NULL ) { + device = DEFAULT_DEVICE; + } + return device; } /* Audio driver bootstrap functions */ static int Audio_Available(void) -/* - See if we can open a nonblocking channel. - Return value '1' means we can. - Return value '0' means we cannot. -*/ { int available; - int rval; + int status; snd_pcm_t *handle; - snd_pcm_channel_params_t cparams; -#ifdef DEBUG_AUDIO - snd_pcm_channel_status_t cstatus; -#endif available = 0; - handle = NULL; - - init_pcm_cparams(&cparams); - - rval = snd_pcm_open(&handle, card_no, device_no, OPEN_FLAGS); - if (rval >= 0) - { - rval = snd_pcm_plugin_params(handle, &cparams); - -#ifdef DEBUG_AUDIO - snd_pcm_plugin_status(handle, &cstatus); - printf("status after snd_pcm_plugin_params call = %d\n",cstatus.status); -#endif - if (rval >= 0) - { - available = 1; - } - else - { - SDL_SetError("snd_pcm_channel_params failed: %s\n", snd_strerror (rval)); - } - - if ((rval = snd_pcm_close(handle)) < 0) - { - SDL_SetError("snd_pcm_close failed: %s\n",snd_strerror(rval)); - available = 0; - } + status = snd_pcm_open(&handle, get_audio_device(), SND_PCM_STREAM_PLAYBACK, 0); + if ( status >= 0 ) { + available = 1; + snd_pcm_close(handle); } - else - { - SDL_SetError("snd_pcm_open failed: %s\n", snd_strerror(rval)); - } - return(available); } @@ -162,14 +107,13 @@ return(0); } memset(this->hidden, 0, (sizeof *this->hidden)); - audio_handle = NULL; /* Set the function pointers */ - this->OpenAudio = PCM_OpenAudio; - this->WaitAudio = PCM_WaitAudio; - this->PlayAudio = PCM_PlayAudio; - this->GetAudioBuf = PCM_GetAudioBuf; - this->CloseAudio = PCM_CloseAudio; + this->OpenAudio = ALSA_OpenAudio; + this->WaitAudio = ALSA_WaitAudio; + this->PlayAudio = ALSA_PlayAudio; + this->GetAudioBuf = ALSA_GetAudioBuf; + this->CloseAudio = ALSA_CloseAudio; this->free = Audio_DeleteDevice; @@ -177,14 +121,13 @@ } AudioBootStrap ALSA_bootstrap = { - DRIVER_NAME, "ALSA PCM audio", + DRIVER_NAME, "ALSA 0.9 PCM audio", Audio_Available, Audio_CreateDevice }; /* This function waits until it is possible to write a full sound buffer */ -static void PCM_WaitAudio(_THIS) +static void ALSA_WaitAudio(_THIS) { - /* Check to see if the thread-parent process is still alive */ { static int cnt = 0; /* Note that this only works with thread implementations @@ -196,322 +139,177 @@ } } } - - /* See if we need to use timed audio synchronization */ - if ( frame_ticks ) - { - /* Use timer for general audio synchronization */ - Sint32 ticks; - - ticks = ((Sint32)(next_frame - SDL_GetTicks()))-FUDGE_TICKS; - if ( ticks > 0 ) - { - SDL_Delay(ticks); - } - } - else - { - /* Use select() for audio synchronization */ - fd_set fdset; - struct timeval timeout; - FD_ZERO(&fdset); - FD_SET(audio_fd, &fdset); - timeout.tv_sec = 10; - timeout.tv_usec = 0; -#ifdef DEBUG_AUDIO - fprintf(stderr, "Waiting for audio to get ready\n"); -#endif - if ( select(audio_fd+1, NULL, &fdset, NULL, &timeout) <= 0 ) - { - const char *message = - "Audio timeout - buggy audio driver? (disabled)"; - /* In general we should never print to the screen, - but in this case we have no other way of letting - the user know what happened. - */ - fprintf(stderr, "SDL: %s\n", message); - this->enabled = 0; - /* Don't try to close - may hang */ - audio_fd = -1; -#ifdef DEBUG_AUDIO - fprintf(stderr, "Done disabling audio\n"); -#endif - } -#ifdef DEBUG_AUDIO - fprintf(stderr, "Ready!\n"); -#endif - } } -static snd_pcm_channel_status_t cstatus; - -static void PCM_PlayAudio(_THIS) +static void ALSA_PlayAudio(_THIS) { - int written, rval; - - /* Write the audio data, checking for EAGAIN (buffer full) and underrun */ - do { - written = snd_pcm_plugin_write(audio_handle, pcm_buf, pcm_len); -#ifdef DEBUG_AUDIO - fprintf(stderr, "written = %d pcm_len = %d\n",written,pcm_len); -#endif - if (written != pcm_len) - { - if (errno == EAGAIN) - { - SDL_Delay(1); /* Let a little CPU time go by and try to write again */ -#ifdef DEBUG_AUDIO - fprintf(stderr, "errno == EAGAIN\n"); -#endif - } - else - { - if( (rval = snd_pcm_plugin_status(audio_handle, &cstatus)) < 0 ) - { - SDL_SetError("snd_pcm_plugin_status failed: %s\n", snd_strerror(rval)); - return; - } - if ( (cstatus.status == SND_PCM_STATUS_UNDERRUN) - ||(cstatus.status == SND_PCM_STATUS_READY) ) - { -#ifdef DEBUG_AUDIO - fprintf(stderr, "buffer underrun\n"); -#endif - if ( (rval = snd_pcm_plugin_prepare (audio_handle,SND_PCM_CHANNEL_PLAYBACK)) < 0 ) - { - SDL_SetError("snd_pcm_plugin_prepare failed: %s\n",snd_strerror(rval) ); - return; - } - /* if we reach here, try to write again */ - } + int status; + int sample_len; + signed short *sample_buf; + + sample_len = this->spec.samples; + sample_buf = (signed short *)mixbuf; + while ( sample_len > 0 ) { + status = snd_pcm_writei(pcm_handle, sample_buf, sample_len); + if ( status < 0 ) { + if ( status == -EAGAIN ) { + continue; + } + if ( status == -ESTRPIPE ) { + do { + status = snd_pcm_resume(pcm_handle); + } while ( status == -EAGAIN ); + } + if ( status < 0 ) { + status = snd_pcm_prepare(pcm_handle); + } + if ( status < 0 ) { + /* Hmm, not much we can do - abort */ + this->enabled = 0; + return; } } - } while ( (written < 0) && ((errno == 0) || (errno == EAGAIN)) ); - - /* Set the next write frame */ - if ( frame_ticks ) { - next_frame += frame_ticks; + sample_buf += status * this->spec.channels; + sample_len -= status; } - - /* If we couldn't write, assume fatal error for now */ - if ( written < 0 ) { - this->enabled = 0; - } - return; } -static Uint8 *PCM_GetAudioBuf(_THIS) +static Uint8 *ALSA_GetAudioBuf(_THIS) { - return(pcm_buf); + return(mixbuf); } -static void PCM_CloseAudio(_THIS) +static void ALSA_CloseAudio(_THIS) { - int rval; - - if ( pcm_buf != NULL ) { - free(pcm_buf); - pcm_buf = NULL; + if ( mixbuf != NULL ) { + SDL_FreeAudioMem(mixbuf); + mixbuf = NULL; } - if ( audio_handle != NULL ) { - if ((rval = snd_pcm_plugin_flush(audio_handle,SND_PCM_CHANNEL_PLAYBACK)) < 0) - { - SDL_SetError("snd_pcm_plugin_flush failed: %s\n",snd_strerror(rval)); - return; - } - if ((rval = snd_pcm_close(audio_handle)) < 0) - { - SDL_SetError("snd_pcm_close failed: %s\n",snd_strerror(rval)); - return; - } - audio_handle = NULL; + if ( pcm_handle ) { + snd_pcm_close(pcm_handle); + pcm_handle = NULL; } } -static int PCM_OpenAudio(_THIS, SDL_AudioSpec *spec) +static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec) { - int rval; - snd_pcm_channel_params_t cparams; - snd_pcm_channel_setup_t csetup; - int format; - Uint16 test_format; - int twidth; - - /* initialize channel transfer parameters to default */ - init_pcm_cparams(&cparams); - - /* Reset the timer synchronization flag */ - frame_ticks = 0.0; + int status; + snd_pcm_hw_params_t *params; + snd_pcm_format_t format; + snd_pcm_uframes_t frames; + Uint16 test_format; /* Open the audio device */ - - rval = snd_pcm_open(&audio_handle, card_no, device_no, OPEN_FLAGS); - if ( rval < 0 ) { - SDL_SetError("snd_pcm_open failed: %s\n", snd_strerror(rval)); + status = snd_pcm_open(&pcm_handle, get_audio_device(), SND_PCM_STREAM_PLAYBACK, 0); + if ( status < 0 ) { + SDL_SetError("Couldn't open audio device: %s", 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); + if ( status < 0 ) { + SDL_SetError("Couldn't get hardware config: %s", 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); + if ( status < 0 ) { + SDL_SetError("Couldn't set interleaved access: %s", snd_strerror(status)); + ALSA_CloseAudio(this); return(-1); } -#ifdef PLUGIN_DISABLE_MMAP /* This is gone in newer versions of ALSA? */ - /* disable count status parameter */ - if ((rval = snd_plugin_set_disable(audio_handle, PLUGIN_DISABLE_MMAP))<0) - { - SDL_SetError("snd_plugin_set_disable failed: %s\n", snd_strerror(rval)); - return(-1); - } -#endif - - pcm_buf = NULL; - /* Try for a closest match on audio format */ - format = 0; + status = -1; for ( test_format = SDL_FirstAudioFormat(spec->format); - ! format && test_format; ) - { -#ifdef DEBUG_AUDIO - fprintf(stderr, "Trying format 0x%4.4x spec->samples %d\n", test_format,spec->samples); -#endif - /* if match found set format to equivalent ALSA format */ - switch ( test_format ) { + test_format && (status < 0); ) { + switch ( test_format ) { case AUDIO_U8: - format = SND_PCM_SFMT_U8; - cparams.buf.block.frag_size = spec->samples * spec->channels; + format = SND_PCM_FORMAT_U8; break; case AUDIO_S8: - format = SND_PCM_SFMT_S8; - cparams.buf.block.frag_size = spec->samples * spec->channels; + format = SND_PCM_FORMAT_S8; break; case AUDIO_S16LSB: - format = SND_PCM_SFMT_S16_LE; - cparams.buf.block.frag_size = spec->samples*2 * spec->channels; + format = SND_PCM_FORMAT_S16_LE; break; case AUDIO_S16MSB: - format = SND_PCM_SFMT_S16_BE; - cparams.buf.block.frag_size = spec->samples*2 * spec->channels; + format = SND_PCM_FORMAT_S16_BE; break; case AUDIO_U16LSB: - format = SND_PCM_SFMT_U16_LE; - cparams.buf.block.frag_size = spec->samples*2 * spec->channels; + format = SND_PCM_FORMAT_U16_LE; break; case AUDIO_U16MSB: - format = SND_PCM_SFMT_U16_BE; - cparams.buf.block.frag_size = spec->samples*2 * spec->channels; + format = SND_PCM_FORMAT_U16_BE; break; default: + format = 0; break; } - if ( ! format ) { + if ( format != 0 ) { + status = snd_pcm_hw_params_set_format(pcm_handle, params, format); + } + if ( status < 0 ) { test_format = SDL_NextAudioFormat(); } } - if ( format == 0 ) { + if ( status < 0 ) { SDL_SetError("Couldn't find any hardware audio formats"); + ALSA_CloseAudio(this); return(-1); } spec->format = test_format; - /* Set the audio format */ - cparams.format.format = format; + /* Set the number of channels */ + status = snd_pcm_hw_params_set_channels(pcm_handle, params, spec->channels); + if ( status < 0 ) { + status = snd_pcm_hw_params_get_channels(params); + if ( (status <= 0) || (status > 2) ) { + SDL_SetError("Couldn't set audio channels"); + ALSA_CloseAudio(this); + return(-1); + } + spec->channels = status; + } - /* Set mono or stereo audio (currently only two channels supported) */ - cparams.format.voices = spec->channels; - - #ifdef DEBUG_AUDIO - printf("intializing channels %d\n", cparams.format.voices); - #endif - - /* Set rate */ - cparams.format.rate = spec->freq ; + /* Set the audio rate */ + status = 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)); + ALSA_CloseAudio(this); + return(-1); + } + spec->freq = status; - /* Setup the transfer parameters according to cparams */ - rval = snd_pcm_plugin_params(audio_handle, &cparams); - if (rval < 0) { - SDL_SetError("snd_pcm_channel_params failed: %s\n", snd_strerror (rval)); + /* Set the buffer size, in samples */ + frames = spec->samples; + frames = 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); + + /* "set" the hardware with the desired parameters */ + status = snd_pcm_hw_params(pcm_handle, params); + if ( status < 0 ) { + SDL_SetError("Couldn't set audio parameters: %s", snd_strerror(status)); + ALSA_CloseAudio(this); return(-1); } - /* Make sure channel is setup right one last time */ - memset( &csetup, 0, sizeof( csetup ) ); - csetup.channel = SND_PCM_CHANNEL_PLAYBACK; - if ( snd_pcm_plugin_setup( audio_handle, &csetup ) < 0 ) - { - SDL_SetError("Unable to setup playback channel\n" ); - return(-1); - } - -#ifdef DEBUG_AUDIO - else - { - fprintf(stderr,"requested format: %d\n",cparams.format.format); - fprintf(stderr,"requested frag size: %d\n",cparams.buf.block.frag_size); - fprintf(stderr,"requested max frags: %d\n\n",cparams.buf.block.frags_max); - - fprintf(stderr,"real format: %d\n", csetup.format.format ); - fprintf(stderr,"real frag size : %d\n", csetup.buf.block.frag_size ); - fprintf(stderr,"real max frags : %d\n", csetup.buf.block.frags_max ); - } -#endif // DEBUG_AUDIO + /* Calculate the final parameters for this audio specification */ + SDL_CalculateAudioSpec(spec); - /* Allocate memory to the audio buffer and initialize with silence - (Note that buffer size must be a multiple of fragment size, so find closest multiple) - */ - - twidth = snd_pcm_format_width(format); - if (twidth < 0) { - printf("snd_pcm_format_width failed\n"); - twidth = 0; - } -#ifdef DEBUG_AUDIO - printf("format is %d bits wide\n",twidth); -#endif - - pcm_len = csetup.buf.block.frag_size * (twidth/8) * csetup.format.voices ; - -#ifdef DEBUG_AUDIO - printf("pcm_len set to %d\n", pcm_len); -#endif - - if (pcm_len == 0) - { - pcm_len = csetup.buf.block.frag_size; - } - - pcm_buf = (Uint8*)malloc(pcm_len); - if (pcm_buf == NULL) { - SDL_SetError("pcm_buf malloc failed\n"); - return(-1); - } - memset(pcm_buf,spec->silence,pcm_len); - -#ifdef DEBUG_AUDIO - fprintf(stderr,"pcm_buf malloced and silenced.\n"); -#endif - - /* get the file descriptor */ - if( (audio_fd = snd_pcm_file_descriptor(audio_handle, device_no)) < 0) - { - fprintf(stderr, "snd_pcm_file_descriptor failed with error code: %d\n", audio_fd); - } - - /* Trigger audio playback */ - rval = snd_pcm_plugin_prepare( audio_handle, SND_PCM_CHANNEL_PLAYBACK); - if (rval < 0) { - SDL_SetError("snd_pcm_plugin_prepare failed: %s\n", snd_strerror (rval)); - return(-1); + /* Allocate mixing buffer */ + mixlen = spec->size; + mixbuf = (Uint8 *)SDL_AllocAudioMem(mixlen); + if ( mixbuf == NULL ) { + ALSA_CloseAudio(this); + return(-1); } - rval = snd_pcm_playback_go(audio_handle); - if (rval < 0) { - SDL_SetError("snd_pcm_playback_go failed: %s\n", snd_strerror (rval)); - return(-1); - } - - /* Check to see if we need to use select() workaround */ - { char *workaround; - workaround = getenv("SDL_DSP_NOSELECT"); - if ( workaround ) { - frame_ticks = (float)(spec->samples*1000)/spec->freq; - next_frame = SDL_GetTicks()+frame_ticks; - } - } + memset(mixbuf, spec->silence, spec->size); /* Get the parent process id (we're the parent of the audio thread) */ parent = getpid();