| | |
| | | |
| | | #if SDL_AUDIO_DRIVER_ALSA |
| | | |
| | | #ifndef SDL_ALSA_NON_BLOCKING |
| | | #define SDL_ALSA_NON_BLOCKING 0 |
| | | #endif |
| | | |
| | | /* Allow access to a raw mixing buffer */ |
| | | |
| | | #include <sys/types.h> |
| | |
| | | static int (*ALSA_snd_device_name_hint) (int, const char *, void ***); |
| | | static char* (*ALSA_snd_device_name_get_hint) (const void *, const char *); |
| | | static int (*ALSA_snd_device_name_free_hint) (void **); |
| | | static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *); |
| | | #ifdef SND_CHMAP_API_VERSION |
| | | static snd_pcm_chmap_t* (*ALSA_snd_pcm_get_chmap) (snd_pcm_t *); |
| | | static int (*ALSA_snd_pcm_chmap_print) (const snd_pcm_chmap_t *map, size_t maxlen, char *buf); |
| | |
| | | SDL_ALSA_SYM(snd_device_name_hint); |
| | | SDL_ALSA_SYM(snd_device_name_get_hint); |
| | | SDL_ALSA_SYM(snd_device_name_free_hint); |
| | | SDL_ALSA_SYM(snd_pcm_avail); |
| | | #ifdef SND_CHMAP_API_VERSION |
| | | SDL_ALSA_SYM(snd_pcm_get_chmap); |
| | | SDL_ALSA_SYM(snd_pcm_chmap_print); |
| | |
| | | static void |
| | | ALSA_WaitDevice(_THIS) |
| | | { |
| | | /* We're in blocking mode, so there's nothing to do here */ |
| | | #if SDL_ALSA_NON_BLOCKING |
| | | const snd_pcm_sframes_t needed = (snd_pcm_sframes_t) this->spec.samples; |
| | | while (SDL_AtomicGet(&this->enabled)) { |
| | | const snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(this->hidden->pcm_handle); |
| | | if ((rc < 0) && (rc != -EAGAIN)) { |
| | | /* Hmm, not much we can do - abort */ |
| | | fprintf(stderr, "ALSA snd_pcm_avail failed (unrecoverable): %s\n", |
| | | ALSA_snd_strerror(rc)); |
| | | SDL_OpenedAudioDeviceDisconnected(this); |
| | | return; |
| | | } else if (rc < needed) { |
| | | const Uint32 delay = ((needed - (SDL_max(rc, 0))) * 1000) / this->spec.freq; |
| | | SDL_Delay(SDL_max(delay, 10)); |
| | | } else { |
| | | break; /* ready to go! */ |
| | | } |
| | | } |
| | | #endif |
| | | } |
| | | |
| | | |
| | |
| | | ALSA_CloseDevice(_THIS) |
| | | { |
| | | if (this->hidden->pcm_handle) { |
| | | /* Wait for the submitted audio to drain |
| | | /* Wait for the submitted audio to drain |
| | | ALSA_snd_pcm_drop() can hang, so don't use that. |
| | | */ |
| | | Uint32 delay = ((this->spec.samples * 1000) / this->spec.freq) * 2; |
| | |
| | | } |
| | | |
| | | static int |
| | | ALSA_finalize_hardware(_THIS, snd_pcm_hw_params_t *hwparams, int override) |
| | | ALSA_set_buffer_size(_THIS, snd_pcm_hw_params_t *params) |
| | | { |
| | | int status; |
| | | snd_pcm_hw_params_t *hwparams; |
| | | snd_pcm_uframes_t bufsize; |
| | | snd_pcm_uframes_t persize; |
| | | |
| | | /* Copy the hardware parameters for this setup */ |
| | | snd_pcm_hw_params_alloca(&hwparams); |
| | | ALSA_snd_pcm_hw_params_copy(hwparams, params); |
| | | |
| | | /* Prioritize matching the period size to the requested buffer size */ |
| | | persize = this->spec.samples; |
| | | status = ALSA_snd_pcm_hw_params_set_period_size_near( |
| | | this->hidden->pcm_handle, hwparams, &persize, NULL); |
| | | if ( status < 0 ) { |
| | | return(-1); |
| | | } |
| | | |
| | | /* Next try to restrict the parameters to having only two periods */ |
| | | bufsize = this->spec.samples * 2; |
| | | status = ALSA_snd_pcm_hw_params_set_buffer_size_near( |
| | | this->hidden->pcm_handle, hwparams, &bufsize); |
| | | if ( status < 0 ) { |
| | | return(-1); |
| | | } |
| | | |
| | | /* "set" the hardware with the desired parameters */ |
| | | status = ALSA_snd_pcm_hw_params(this->hidden->pcm_handle, hwparams); |
| | |
| | | return(-1); |
| | | } |
| | | |
| | | /* Get samples for the actual buffer size */ |
| | | status = ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize); |
| | | if ( status < 0 ) { |
| | | return(-1); |
| | | } |
| | | if ( !override && bufsize != this->spec.samples * 2 ) { |
| | | return(-1); |
| | | } |
| | | |
| | | /* !!! FIXME: Is this safe to do? */ |
| | | this->spec.samples = bufsize / 2; |
| | | this->spec.samples = persize; |
| | | |
| | | /* This is useful for debugging */ |
| | | if ( SDL_getenv("SDL_AUDIO_ALSA_DEBUG") ) { |
| | | snd_pcm_uframes_t persize = 0; |
| | | unsigned int periods = 0; |
| | | |
| | | ALSA_snd_pcm_hw_params_get_period_size(hwparams, &persize, NULL); |
| | | ALSA_snd_pcm_hw_params_get_periods(hwparams, &periods, NULL); |
| | | |
| | | fprintf(stderr, |
| | |
| | | } |
| | | |
| | | return(0); |
| | | } |
| | | |
| | | static int |
| | | ALSA_set_period_size(_THIS, snd_pcm_hw_params_t *params, int override) |
| | | { |
| | | const char *env; |
| | | int status; |
| | | snd_pcm_hw_params_t *hwparams; |
| | | snd_pcm_uframes_t frames; |
| | | unsigned int periods; |
| | | |
| | | /* Copy the hardware parameters for this setup */ |
| | | snd_pcm_hw_params_alloca(&hwparams); |
| | | ALSA_snd_pcm_hw_params_copy(hwparams, params); |
| | | |
| | | if ( !override ) { |
| | | env = SDL_getenv("SDL_AUDIO_ALSA_SET_PERIOD_SIZE"); |
| | | if ( env ) { |
| | | override = SDL_atoi(env); |
| | | if ( override == 0 ) { |
| | | return(-1); |
| | | } |
| | | } |
| | | } |
| | | |
| | | frames = this->spec.samples; |
| | | status = ALSA_snd_pcm_hw_params_set_period_size_near( |
| | | this->hidden->pcm_handle, hwparams, &frames, NULL); |
| | | if ( status < 0 ) { |
| | | return(-1); |
| | | } |
| | | |
| | | periods = 2; |
| | | status = ALSA_snd_pcm_hw_params_set_periods_near( |
| | | this->hidden->pcm_handle, hwparams, &periods, NULL); |
| | | if ( status < 0 ) { |
| | | return(-1); |
| | | } |
| | | |
| | | return ALSA_finalize_hardware(this, hwparams, override); |
| | | } |
| | | |
| | | static int |
| | | ALSA_set_buffer_size(_THIS, snd_pcm_hw_params_t *params, int override) |
| | | { |
| | | const char *env; |
| | | int status; |
| | | snd_pcm_hw_params_t *hwparams; |
| | | snd_pcm_uframes_t frames; |
| | | |
| | | /* Copy the hardware parameters for this setup */ |
| | | snd_pcm_hw_params_alloca(&hwparams); |
| | | ALSA_snd_pcm_hw_params_copy(hwparams, params); |
| | | |
| | | if ( !override ) { |
| | | env = SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE"); |
| | | if ( env ) { |
| | | override = SDL_atoi(env); |
| | | if ( override == 0 ) { |
| | | return(-1); |
| | | } |
| | | } |
| | | } |
| | | |
| | | frames = this->spec.samples * 2; |
| | | status = ALSA_snd_pcm_hw_params_set_buffer_size_near( |
| | | this->hidden->pcm_handle, hwparams, &frames); |
| | | if ( status < 0 ) { |
| | | return(-1); |
| | | } |
| | | |
| | | return ALSA_finalize_hardware(this, hwparams, override); |
| | | } |
| | | |
| | | static int |
| | |
| | | this->spec.freq = rate; |
| | | |
| | | /* Set the buffer size, in samples */ |
| | | if ( ALSA_set_period_size(this, hwparams, 0) < 0 && |
| | | ALSA_set_buffer_size(this, hwparams, 0) < 0 ) { |
| | | /* Failed to set desired buffer size, do the best you can... */ |
| | | status = ALSA_set_period_size(this, hwparams, 1); |
| | | if (status < 0) { |
| | | return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status)); |
| | | } |
| | | status = ALSA_set_buffer_size(this, hwparams); |
| | | if (status < 0) { |
| | | return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status)); |
| | | } |
| | | |
| | | /* Set the software parameters */ |
| | | snd_pcm_sw_params_alloca(&swparams); |
| | | status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams); |
| | |
| | | SDL_memset(this->hidden->mixbuf, this->spec.silence, this->hidden->mixlen); |
| | | } |
| | | |
| | | #if !SDL_ALSA_NON_BLOCKING |
| | | if (!iscapture) { |
| | | ALSA_snd_pcm_nonblock(pcm_handle, 0); |
| | | } |
| | | #endif |
| | | |
| | | /* We're ready to rock and roll. :-) */ |
| | | return 0; |