Mercurial > pmdwin
diff alsa_pcm_api.c @ 0:c55ea9478c80
Hello Gensokyo!
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Tue, 21 May 2013 10:29:21 +0200 |
parents | |
children |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/alsa_pcm_api.c @@ -0,0 +1,417 @@ +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include "alsa_pcm_api.h" +int snd_pcm_hw_params_any(int snd_fd, snd_pcm_hw_params_t *params); +int snd_pcm_hw_params_set_access(int snd_fd, snd_pcm_hw_params_t *params, snd_pcm_access_t access); +int snd_pcm_hw_params_set_format(int snd_fd, snd_pcm_hw_params_t *params, snd_pcm_format_t format); +int snd_pcm_hw_params_set_channels(int snd_fd, snd_pcm_hw_params_t *params, unsigned int val); +int snd_pcm_hw_params_set_rate(int snd_fd, snd_pcm_hw_params_t *params, unsigned int val, int dir); +int snd_pcm_hw_params_set_buffer_size(int snd_fd, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val); +#define MASK_SIZE (SND_MASK_MAX / 32) +#define MASK_OFS(i) ((i) >> 5) +#define MASK_BIT(i) (1U << ((i) & 31)) + +static inline +snd_interval_t *hw_param_interval(snd_pcm_hw_params_t *params, int var) +{ + return ¶ms->intervals[var - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]; +} + +static inline +const snd_interval_t *hw_param_interval_c(const snd_pcm_hw_params_t *params, int var) +{ + return (const snd_interval_t *)hw_param_interval((snd_pcm_hw_params_t*) params, var); +} + +size_t snd_pcm_hw_params_sizeof(void) +{ + return sizeof(snd_pcm_hw_params_t); +} + +static void snd_interval_any(snd_interval_t *i) +{ + i->min = 0; + i->openmin = 0; + i->max = -1; + i->openmax = 0; + i->integer = 0; + i->empty = 0; +} + +static inline int snd_interval_empty(const snd_interval_t *i) +{ + return i->empty; +} + +static inline int snd_interval_checkempty(const snd_interval_t *i) +{ + return (i->min > i->max || + (i->min == i->max && (i->openmin || i->openmax))); +} + +/* Return the minimum value for field PAR. */ +int _snd_pcm_hw_param_get_min(const snd_pcm_hw_params_t *params, + int var, unsigned int *val, int *dir) +{ + const snd_interval_t *i = hw_param_interval_c(params, var); + if (dir) + *dir = i->openmin; + if (val) + *val = i->min; + return 0; +} + +/* Return the maximum value for field PAR. */ +int _snd_pcm_hw_param_get_max(const snd_pcm_hw_params_t *params, + int var, unsigned int *val, int *dir) +{ + const snd_interval_t *i = hw_param_interval_c(params, var); + if (dir) + *dir = - (int) i->openmax; + if (val) + *val = i->max; + return 0; +} + +static void _snd_pcm_hw_param_any(snd_pcm_hw_params_t *params, int var) +{ + snd_interval_any(hw_param_interval(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; +} + +int snd_pcm_hw_params_any(int snd_fd, snd_pcm_hw_params_t *params) +{ + unsigned int k; + memset(params, 0, sizeof(*params)); + for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; + k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) { + snd_mask_t *mask = ¶ms->masks[k - SNDRV_PCM_HW_PARAM_FIRST_MASK]; + memset(mask, 0xff, sizeof(*mask)); + params->cmask |= 1 << k; + params->rmask |= 1 << k; + } + for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; + k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) + _snd_pcm_hw_param_any(params, k); + params->rmask = ~0U; + params->cmask = 0; + params->info = ~0U; + return(ioctl(snd_fd, SNDRV_PCM_IOCTL_HW_REFINE, params)); +} + +int snd_pcm_hw_params_store(int snd_fd, snd_pcm_hw_params_t *params) +{ + ioctl(snd_fd, SNDRV_PCM_IOCTL_HW_REFINE, params); + if (ioctl(snd_fd, SNDRV_PCM_IOCTL_HW_PARAMS, params) < 0) + return -1; + if (ioctl(snd_fd, SNDRV_PCM_IOCTL_PREPARE) < 0) + return -1; //-errno; + return 0; +} + +snd_pcm_sframes_t snd_pcm_writei(int snd_fd, const void *buffer, snd_pcm_uframes_t size) +{ + struct snd_xferi xferi; + xferi.result = 0; + xferi.buf = (char*) buffer; + xferi.frames = size; + if (ioctl(snd_fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &xferi) < 0) + return -1; + return xferi.result; +} + +snd_pcm_sframes_t snd_pcm_writen(int snd_fd, void **bufs, snd_pcm_uframes_t size) +{ + struct snd_xfern xfern; + xfern.result = 0; + xfern.bufs = bufs; + xfern.frames = size; + if (ioctl(snd_fd, SNDRV_PCM_IOCTL_WRITEN_FRAMES, &xfern) < 0) + return -1; + return xfern.result; +} + +int snd_pcm_prepare(int snd_fd) +{ + return ioctl(snd_fd, SNDRV_PCM_IOCTL_PREPARE); +} + +int snd_pcm_recover(int snd_fd, int err, int silent) +{ + if (err > 0) + err = -err; + switch (err) { + case -EINTR: + return 0; + case -EPIPE: + if (ioctl(snd_fd, SNDRV_PCM_IOCTL_PREPARE) < 0) + return -1; //-errno; + return 0; + } + return err; +} + +int snd_pcm_status(int snd_fd, snd_pcm_status_t *status) +{ + if (ioctl(snd_fd, SNDRV_PCM_IOCTL_STATUS, status) < 0) + return -1; + return 0; +} + +static int hw_param_update_var(int snd_fd, snd_pcm_hw_params_t *params, + int var, int changed) +{ + if (changed < 0) + return changed; + if (changed) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + if (params->rmask) { + changed = ioctl(snd_fd, SNDRV_PCM_IOCTL_HW_REFINE, params); + if (changed < 0) + return changed; + if (snd_interval_empty(hw_param_interval_c(params, var))) + return -ENOENT; + } + return 0; +} + +static int snd_interval_refine(snd_interval_t *i, const snd_interval_t *v) +{ + int changed = 0; + if (snd_interval_empty(i)) + return -ENOENT; + if (i->min < v->min) { + i->min = v->min; + i->openmin = v->openmin; + changed = 1; + } else if (i->min == v->min && !i->openmin && v->openmin) { + i->openmin = 1; + changed = 1; + } + if (i->max > v->max) { + i->max = v->max; + i->openmax = v->openmax; + changed = 1; + } else if (i->max == v->max && !i->openmax && v->openmax) { + i->openmax = 1; + changed = 1; + } + if (!i->integer && v->integer) { + i->integer = 1; + changed = 1; + } + if (i->integer) { + if (i->openmin) { + i->min++; + i->openmin = 0; + } + if (i->openmax) { + i->max--; + i->openmax = 0; + } + } else if (!i->openmin && !i->openmax && i->min == i->max) + i->integer = 1; + if (snd_interval_checkempty(i)) { + i->empty = 1; + return -EINVAL; + } + return changed; +} + +int _snd_pcm_hw_param_get(const snd_pcm_hw_params_t *params, + int var, unsigned int *val, int *dir) +{ + const snd_interval_t *i = hw_param_interval_c(params, var); + if (snd_interval_empty(i) || !(i->min == i->max || ((i->min + 1 == i->max) && i->openmax))) + return -EINVAL; + if (dir) + *dir = i->openmin; + if (val) + *val = i->min; + return 0; +} + +int _snd_pcm_hw_param_set(int snd_fd, snd_pcm_hw_params_t *params, int var, + unsigned int val, int dir) +{ + int err = 0; + snd_pcm_hw_params_t save = *params; + snd_interval_t *i = hw_param_interval(params, var); + snd_interval_t t; + t.empty = 0; + t.min = t.max = var; + t.openmin = t.openmax = 0; + t.integer = 1; + err = snd_interval_refine(i, &t); + err = hw_param_update_var(snd_fd, params, var, err); + if (err) + *params = save; + return err; +} + +static inline int _snd_pcm_hw_param_get64(const snd_pcm_hw_params_t *params, int type, unsigned long *val, int *dir) +{ + unsigned int _val; + int err = _snd_pcm_hw_param_get(params, type, &_val, dir); + *val = _val; + return err; +} + +#if 0 +int snd_pcm_hw_params_get_channels(const snd_pcm_hw_params_t *params, unsigned int *val) +{ + return _snd_pcm_hw_param_get(params, SNDRV_PCM_HW_PARAM_CHANNELS, val, NULL); +} + +int snd_pcm_hw_params_set_channels(int snd_fd, snd_pcm_hw_params_t *params, unsigned int val) +{ + return _snd_pcm_hw_param_set(snd_fd, params, SNDRV_PCM_HW_PARAM_CHANNELS, val, 0); +} + +int snd_pcm_hw_params_get_rate(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) +{ + return _snd_pcm_hw_param_get(params, SNDRV_PCM_HW_PARAM_RATE, val, dir); +} + +int snd_pcm_hw_params_set_rate(int snd_fd, snd_pcm_hw_params_t *params, unsigned int val, int dir) +{ + return _snd_pcm_hw_param_set(snd_fd, params, SNDRV_PCM_HW_PARAM_RATE, val, dir); +} + +int snd_pcm_hw_params_get_period_time(const snd_pcm_hw_params_t *params, + unsigned int *val, int *dir) +{ + return _snd_pcm_hw_param_get(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME, val, dir); +} + +int snd_pcm_hw_params_get_period_size(const snd_pcm_hw_params_t *params, + snd_pcm_uframes_t *val, int *dir) +{ + return _snd_pcm_hw_param_get64(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, val, dir); +} + +int snd_pcm_hw_params_get_buffer_size(const snd_pcm_hw_params_t *params, + snd_pcm_uframes_t *val) +{ + return _snd_pcm_hw_param_get64(params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, val, NULL); +} + +int snd_pcm_hw_params_set_buffer_size(int snd_fd, snd_pcm_hw_params_t *params, + snd_pcm_uframes_t val) +{ + return _snd_pcm_hw_param_set(snd_fd, params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, val, 0); +} +#endif + +static inline unsigned int popcount32(uint32_t mask) +{ + register unsigned int y; + y = (mask >> 1) &033333333333; + y = mask - y - ((y >>1) & 033333333333); + return (((y + (y >> 3)) & 030707070707) % 077); +} + +int snd_pcm_hw_params_set_access(int pcm_fd, snd_pcm_hw_params_t *params, + snd_pcm_access_t access) +{ + snd_pcm_hw_params_t save = *params; + snd_mask_t *param = (snd_mask_t*)¶ms->masks[SNDRV_PCM_HW_PARAM_ACCESS - SNDRV_PCM_HW_PARAM_FIRST_MASK]; + uint32_t i, v = 0; + int err = 0, changed; + for (i = 0; i < MASK_SIZE; i++) { + v += popcount32(param->bits[i]); + } + changed = (v != 1); + v = param->bits[MASK_OFS(access)] & MASK_BIT(access); + memset(param, 0, sizeof(*param)); + param->bits[MASK_OFS(access)] = v; + if (changed) { + params->cmask |= 1 << SNDRV_PCM_HW_PARAM_ACCESS; + params->rmask |= 1 << SNDRV_PCM_HW_PARAM_ACCESS; + } + if (params->rmask) { + err = ioctl(pcm_fd, SNDRV_PCM_IOCTL_HW_REFINE, params); + if (err) + *params = save; + } + return err; +} + +int snd_pcm_hw_params_set_format(int pcm_fd, snd_pcm_hw_params_t *params, + snd_pcm_format_t format) +{ + snd_pcm_hw_params_t save = *params; + snd_mask_t *param = (snd_mask_t*)¶ms->masks[SNDRV_PCM_HW_PARAM_FORMAT - SNDRV_PCM_HW_PARAM_FIRST_MASK]; + uint32_t i, v = 0; + int err = 0, changed; + for (i = 0; i < MASK_SIZE; i++) { + v += popcount32(param->bits[i]); + } + changed = (v != 1); + v = param->bits[MASK_OFS(format)] & MASK_BIT(format); + memset(param, 0, sizeof(*param)); + param->bits[MASK_OFS(format)] = v; + if (changed) { + params->cmask |= 1 << SNDRV_PCM_HW_PARAM_ACCESS; + params->rmask |= 1 << SNDRV_PCM_HW_PARAM_ACCESS; + } + if (params->rmask) { + err = ioctl(pcm_fd, SNDRV_PCM_IOCTL_HW_REFINE, params); + if (err) + *params = save; + } + return err; +} + +int alsa_audio_close(int snd_fd) +{ + ioctl(snd_fd, SNDRV_PCM_IOCTL_HW_FREE); + return close(snd_fd); +} + +int alsa_audio_open(uint32_t *rate, uint8_t channels, uint32_t *_ver, int mode) +{ + uint32_t actual_channels; + snd_pcm_hw_params_t *hwparams; + *&hwparams = (snd_pcm_hw_params_t *) alloca(snd_pcm_hw_params_sizeof()); + int ver, snd_fd = open("/dev/snd/pcmC0D0p", O_WRONLY|mode); + if (snd_fd < 0) + return -1; + + if (ioctl(snd_fd, SNDRV_PCM_IOCTL_PVERSION, &ver) < 0) { + if (snd_fd >= 0) + close(snd_fd); + return -1; + } + if(_ver) *_ver = ver; + + // Initialize hwparams structure. + snd_pcm_hw_params_any(snd_fd, hwparams); + + // this *must* be set, or else the writei (actually an ioctl(), wtf?) below will fail with -EBADF + snd_pcm_hw_params_set_access(snd_fd, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(snd_fd, hwparams, SND_PCM_FORMAT_S16_LE); + + _snd_pcm_hw_param_set(snd_fd, hwparams, SNDRV_PCM_HW_PARAM_CHANNELS, channels, 0); + _snd_pcm_hw_param_get(hwparams, SNDRV_PCM_HW_PARAM_CHANNELS, &actual_channels, NULL); + if ((channels == 1) && (actual_channels == 2)) { + // This is an offensively dumb hack that works around the fact that the ALSA devs erroneously believe that HDAudio chipsets cannot playback mono. + // On any other chipset, it's a terrible idea. In general, it's awful. I'm just lazy. That, and I don't use ALSA myself. :P + *rate >>= 1; + } + _snd_pcm_hw_param_set(snd_fd, hwparams, SNDRV_PCM_HW_PARAM_RATE, *rate, 0); + snd_pcm_hw_params_store(snd_fd, hwparams); + _snd_pcm_hw_param_get(hwparams, SNDRV_PCM_HW_PARAM_RATE, rate, NULL); + if ((channels == 1) && (actual_channels == 2)) { + *rate <<= 1; + } + return snd_fd; +} +