diff options
Diffstat (limited to 'modules/alsa/alsa_play.c')
-rw-r--r-- | modules/alsa/alsa_play.c | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/modules/alsa/alsa_play.c b/modules/alsa/alsa_play.c new file mode 100644 index 0000000..856ed96 --- /dev/null +++ b/modules/alsa/alsa_play.c @@ -0,0 +1,155 @@ +/** + * @file alsa_play.c ALSA sound driver - player + * + * Copyright (C) 2010 Creytiv.com + */ +#define _POSIX_SOURCE 1 +#include <sys/types.h> +#include <sys/time.h> +#include <stdlib.h> +#include <unistd.h> +#include <alsa/asoundlib.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "alsa.h" + + +struct auplay_st { + struct auplay *ap; /* inheritance */ + pthread_t thread; + bool run; + snd_pcm_t *write; + struct mbuf *mbw; + auplay_write_h *wh; + void *arg; + struct auplay_prm prm; + char *device; +}; + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + /* Wait for termination of other thread */ + if (st->run) { + st->run = false; + (void)pthread_join(st->thread, NULL); + } + + if (st->write) + snd_pcm_close(st->write); + + mem_deref(st->mbw); + mem_deref(st->ap); + mem_deref(st->device); +} + + +static void *write_thread(void *arg) +{ + struct auplay_st *st = arg; + int n; + int num_frames; + + num_frames = st->prm.srate * st->prm.ptime / 1000; + + while (st->run) { + const int samples = num_frames; + + st->wh(st->mbw->buf, st->mbw->size, st->arg); + + n = snd_pcm_writei(st->write, st->mbw->buf, samples); + if (-EPIPE == n) { + snd_pcm_prepare(st->write); + + n = snd_pcm_writei(st->write, st->mbw->buf, samples); + if (n != samples) { + warning("alsa: write error: %s\n", + snd_strerror(n)); + } + } + else if (n < 0) { + warning("alsa: write error: %s\n", snd_strerror(n)); + } + else if (n != samples) { + warning("alsa: write: wrote %d of %d bytes\n", + n, samples); + } + } + + return NULL; +} + + +int alsa_play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + uint32_t sampc; + int num_frames; + int err; + + if (!stp || !ap || !prm || !wh) + return EINVAL; + if (prm->fmt != AUFMT_S16LE) + return EINVAL; + + if (!str_isset(device)) + device = alsa_dev; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + err = str_dup(&st->device, device); + if (err) + goto out; + + st->prm = *prm; + st->ap = mem_ref(ap); + st->wh = wh; + st->arg = arg; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + num_frames = st->prm.srate * st->prm.ptime / 1000; + + st->mbw = mbuf_alloc(2 * sampc); + if (!st->mbw) { + err = ENOMEM; + goto out; + } + + err = snd_pcm_open(&st->write, st->device, SND_PCM_STREAM_PLAYBACK, 0); + if (err < 0) { + warning("alsa: could not open auplay device '%s' (%s)\n", + st->device, snd_strerror(err)); + goto out; + } + + err = alsa_reset(st->write, st->prm.srate, st->prm.ch, st->prm.fmt, + num_frames); + if (err) { + warning("alsa: could not reset player '%s' (%s)\n", + st->device, snd_strerror(err)); + goto out; + } + + st->run = true; + err = pthread_create(&st->thread, NULL, write_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} |