diff options
Diffstat (limited to 'galsa.c')
-rw-r--r-- | galsa.c | 160 |
1 files changed, 160 insertions, 0 deletions
@@ -0,0 +1,160 @@ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <ctype.h> + +#include "galsa.h" + +struct device *device_open(const char *path, unsigned rate, int chan, size_t pktsize) +{ + struct device *dev; + snd_pcm_hw_params_t *hw_params = NULL; + snd_pcm_t *hdl = NULL; + char errmsg[256]; + +#define E(x) do { int err; if ((err = x) < 0) { \ + snprintf(errmsg, sizeof(errmsg), "%s:%d/%s(): %s() failed: %s (%d)\n",\ + __FILE__, __LINE__, __func__, #x, snd_strerror(err), err); \ + goto out_err; \ + } } while (0) + + printf("Opening alsa://%s with %d ch %dHz...\n", path, chan, rate); + E(snd_pcm_open(&hdl, path, SND_PCM_STREAM_CAPTURE, 0)); + E(snd_pcm_hw_params_malloc(&hw_params)); + E(snd_pcm_hw_params_any(hdl, hw_params)); + E(snd_pcm_hw_params_set_access(hdl, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)); + E(snd_pcm_hw_params_set_format(hdl, hw_params, SND_PCM_FORMAT_S16_LE)); + E(snd_pcm_hw_params_set_rate_near(hdl, hw_params, &rate, 0)); + E(snd_pcm_hw_params_set_channels(hdl, hw_params, chan)); + E(snd_pcm_hw_params(hdl, hw_params)); + snd_pcm_hw_params_free(hw_params); + hw_params = NULL; + + E(snd_pcm_prepare(hdl)); + printf("... Ok.\n"); + + dev = (struct device *)malloc(sizeof(struct device)); + dev->path = strdup(path); + dev->rate = rate; + dev->chan = chan; + dev->handle = hdl; + dev->thread = NULL; + dev->queue = g_async_queue_new(); + dev->state = DEV_OPEN; + dev->pktsize = pktsize; + return dev; + +out_err: + if (hw_params) + snd_pcm_hw_params_free(hw_params); + if (hdl) + snd_pcm_close(hdl); + return NULL; +} + +static gpointer threadf(gpointer param) +{ + struct device *dev = (struct device *)param; + struct qe *ent, *entx; + int pcmreturn; + ptrdiff_t pos; + + do { + ent = malloc(sizeof(struct qe) + + dev->pktsize * sizeof(sample) * dev->chan); + ent->message = QE_DATA; + ent->size = dev->pktsize; + pos = 0; + + do { + while ((pcmreturn = snd_pcm_readi(dev->handle, + ent->samples + pos, ent->size - pos)) < 0) { + + entx = calloc(sizeof(struct qe), 1); + entx->message = QE_ERROR; + entx->errcode = pcmreturn; + asprintf(&entx->errp, + "PCM read failed: %s (%d)", + snd_strerror(pcmreturn), pcmreturn); + g_async_queue_push(dev->queue, entx); + + if (dev->state != DEV_RUNNING) { + free(ent); + goto out; + } + + snd_pcm_prepare(dev->handle); + } + pos += pcmreturn; + } while (pos < ent->size); + g_async_queue_push(dev->queue, ent); + } while (dev->state == DEV_RUNNING); + +out: + entx = calloc(sizeof(struct qe), 1); + entx->message = QE_STOPPED; + g_async_queue_push(dev->queue, entx); + + return NULL; +} + +void device_start(struct device *dev) +{ + GError *error = NULL; + if (dev->thread) + return; + dev->state = DEV_RUNNING; + dev->thread = g_thread_create(threadf, dev, 0, &error); +} + +void device_stop(struct device *dev) +{ + if (dev->state == DEV_RUNNING) + dev->state = DEV_STOPREQ; +} + +void device_stop_wait(struct device *dev) +{ + int got_stop = 0, iter = 0; + struct qe *qe; + + if (dev->state == DEV_OPEN) + return; + + device_stop(dev); + + while (!got_stop && iter++ < 10) { + qe = (struct qe *)g_async_queue_timeout_pop(dev->queue, 250000); + switch (qe->message) { + case QE_DATA: + break; + case QE_ERROR: + free(qe->errp); + break; + case QE_STOPPED: + got_stop = 1; + break; + } + free(qe); + } + if (!got_stop) + fprintf(stderr, "failed to terminate recording thread!\n"); + dev->state = DEV_OPEN; +} + +void device_stop_done(struct device *dev) +{ + dev->state = DEV_OPEN; +} + +void device_close(struct device *dev) +{ + device_stop_wait(dev); + + snd_pcm_close(dev->handle); + free(dev->path); + free(dev); +} + + |