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 &params->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 = &params->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*)&params->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*)&params->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;
+}
+