/* * (C) notaz, 2013 * * This work is licensed under the terms of any of these licenses * (at your option): * - GNU GPL, version 2 or later. * - GNU LGPL, version 2.1 or later. * - MAME license. * See the COPYING file in the top-level directory. */ #include #include #include #include "sndout_alsa.h" #define PFX "sndout_alsa: " static snd_pcm_t *handle; static snd_pcm_uframes_t buffer_size, period_size; static void *silent_period; static unsigned int channels; static int failure_counter; int sndout_alsa_init(void) { int ret; ret = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0); if (ret != 0) return -1; return 0; } int sndout_alsa_start(int rate_, int stereo) { snd_pcm_hw_params_t *hwparams = NULL; unsigned int rate = rate_; int samples, shift; int ret; samples = rate * 40 / 1000; for (shift = 8; (1 << shift) < samples; shift++) ; period_size = 1 << shift; buffer_size = 8 * period_size; snd_pcm_hw_params_alloca(&hwparams); ret = snd_pcm_hw_params_any(handle, hwparams); ret |= snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED); ret |= snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16); ret |= snd_pcm_hw_params_set_channels(handle, hwparams, stereo ? 2 : 1); ret |= snd_pcm_hw_params_set_rate_near(handle, hwparams, &rate, 0); ret |= snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, &buffer_size); ret |= snd_pcm_hw_params_set_period_size_near(handle, hwparams, &period_size, NULL); if (ret != 0) { fprintf(stderr, PFX "failed to set hwparams\n"); goto fail; } ret = snd_pcm_hw_params(handle, hwparams); if (ret != 0) { fprintf(stderr, PFX "failed to apply hwparams: %d\n", ret); goto fail; } snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size); snd_pcm_hw_params_get_period_size(hwparams, &period_size, NULL); snd_pcm_hw_params_get_channels(hwparams, &channels); silent_period = calloc(period_size * channels, 2); ret = snd_pcm_prepare(handle); if (ret != 0) { fprintf(stderr, PFX "snd_pcm_prepare failed: %d\n", ret); goto fail; } ret = snd_pcm_start(handle); if (ret != 0) { fprintf(stderr, PFX "snd_pcm_start failed: %d\n", ret); goto fail; } failure_counter = 0; return 0; fail: // to flush out redirected logs fflush(stdout); fflush(stderr); return -1; } void sndout_alsa_stop(void) { int ret = snd_pcm_drop(handle); if (ret != 0) fprintf(stderr, PFX "snd_pcm_drop failed: %d\n", ret); free(silent_period); silent_period = NULL; } void sndout_alsa_wait(void) { snd_pcm_sframes_t left; while (1) { left = snd_pcm_avail(handle); if (left < 0 || left >= buffer_size / 2) break; usleep(4000); } } int sndout_alsa_write_nb(const void *samples, int len) { snd_pcm_sframes_t left; int ret; len /= 2; if (channels == 2) len /= 2; left = snd_pcm_avail(handle); if (left >= 0 && left < len) return 0; ret = snd_pcm_writei(handle, samples, len); if (ret < 0) { ret = snd_pcm_recover(handle, ret, 1); if (ret != 0 && failure_counter++ < 5) fprintf(stderr, PFX "snd_pcm_recover: %d\n", ret); if (silent_period) snd_pcm_writei(handle, silent_period, period_size); snd_pcm_writei(handle, samples, len); } return len; } void sndout_alsa_exit(void) { snd_pcm_close(handle); handle = NULL; }