$OpenBSD: patch-audio_sndioaudio_c,v 1.2 2019/07/04 17:11:05 sthen Exp $

sndio module

Index: audio/sndioaudio.c
--- audio/sndioaudio.c.orig
+++ audio/sndioaudio.c
@@ -0,0 +1,526 @@
+/*
+ * Copyright (c) 2019 Alexandre Ratchov <alex@caoua.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * TODO :
+ *
+ * - Store device handle in a global variable and open it in full-duplex
+ *   rather than opening it twice. This is the only way to ensure
+ *   playback doesn't drift with respect to recording, which
+ *   is what guest systems expect.
+ *
+ * - Clarify whether hw->wpos can wrap, see sndio_run_in(). As we only
+ *   transfer one block at a time, hw->hwpos can't wrap, unless upper
+ *   layer skip samples.
+ */
+
+#include <poll.h>
+#include <sndio.h>
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/main-loop.h"
+#include "audio.h"
+#include "trace.h"
+
+#define AUDIO_CAP "sndio"
+#include "audio_int.h"
+
+#define SNDIO_LATENCY_US   50000
+
+typedef struct SndioVoice {
+    union {
+        HWVoiceOut out;
+        HWVoiceIn in;
+    } hw;
+    struct sio_par par;
+    struct sio_hdl *hdl;
+    struct pollfd *pfds;
+    struct pollindex {
+        struct SndioVoice *self;
+        int index;
+    } *pindexes;
+    unsigned char *buf;
+    unsigned int bpf;
+    unsigned int sndio_pos;
+    unsigned int qemu_pos;
+    unsigned int mode;
+    unsigned int nfds;
+} SndioVoice;
+
+typedef struct SndioConf {
+    const char *devname;
+    unsigned int latency;
+} SndioConf;
+
+/* needed for forward reference */
+static void sndio_poll_in(void *arg);
+static void sndio_poll_out(void *arg);
+
+static void sndio_poll_clear (SndioVoice *self)
+{
+    struct pollfd *pfd;
+    int i;
+
+    for (i = 0; i < self->nfds; i++) {
+        pfd = &self->pfds[i];
+        qemu_set_fd_handler (pfd->fd, NULL, NULL, NULL);
+    }
+
+    self->nfds = 0;
+}
+
+static void sndio_poll_wait(SndioVoice *self, int events)
+{
+    struct pollfd *pfd;
+    int i;
+
+    /*
+     * fill the given array of descriptors with the events sndio
+     * wants, they are different from our 'event' variable because
+     * sndio may use descriptors internally.
+     */
+    self->nfds = sio_pollfd (self->hdl, self->pfds, events);
+
+    for (i = 0; i < self->nfds; i++) {
+        pfd = &self->pfds[i];
+        if (pfd->fd < 0)
+                continue;
+        qemu_set_fd_handler (pfd->fd,
+                (pfd->events & POLLIN) ? sndio_poll_in : NULL,
+                (pfd->events & POLLOUT) ? sndio_poll_out : NULL,
+                &self->pindexes[i]);
+        pfd->revents = 0;
+    }
+}
+
+static void sndio_write_do(SndioVoice *self)
+{
+    int todo, n;
+
+    todo = (self->qemu_pos * self->bpf) - self->sndio_pos;
+
+    /*
+     * transfer data to device, until it blocks
+     */    
+    while (todo > 0) {
+        n = sio_write (self->hdl, self->buf + self->sndio_pos, todo);
+        if (n == 0)
+            break;
+        self->sndio_pos += n;
+        todo -= n;
+    }
+
+    /*
+     * did we complete the block ?
+     */
+    if (todo == 0 && self->qemu_pos == self->par.round) {
+        self->sndio_pos = 0;
+        self->qemu_pos = 0;
+    }
+
+    audio_run ("sndio_out");
+}
+
+static void sndio_read_do(SndioVoice *self)
+{
+    int todo, n;
+
+    todo = (self->par.round * self->bpf) - self->sndio_pos;
+
+    /*
+     * transfer data from the device, until it blocks
+     */    
+    while (todo > 0) {
+        n = sio_read (self->hdl, self->buf + self->sndio_pos, todo);
+        if (n == 0)
+            break;
+        self->sndio_pos += n;
+        todo -= n;
+    }
+
+    audio_run ("sndio_in");
+}
+
+static void sndio_poll_event (SndioVoice *self, int index, int event)
+{
+    int revents, todo;
+
+    /*
+     * ensure we're not called twice this cycle
+     */
+    sndio_poll_clear (self);
+
+    /*
+     * make self->pfds[] look as we're returning from poll syscal,
+     * this is how sio_revents expects events to be.
+     */
+    self->pfds[index].revents = event;
+
+    /*
+     * tell sndio to handle events and return whether we can read or
+     * write without blocking.
+     */
+    revents = sio_revents (self->hdl, self->pfds);
+    if (self->mode == SIO_PLAY) {
+        if (revents & POLLOUT)
+            sndio_write_do (self);
+
+        todo = (self->qemu_pos * self->bpf) - self->sndio_pos;
+        sndio_poll_wait (self, todo > 0 ? POLLOUT : 0);
+
+    } else {
+        if (revents & POLLIN)
+            sndio_read_do (self);
+
+        todo = (self->par.round * self->bpf) - self->sndio_pos;
+        sndio_poll_wait (self, todo > 0 ? POLLIN : 0);
+    }
+}
+
+static void sndio_poll_in(void *arg)
+{
+    struct pollindex *pindex = (struct pollindex *) arg;
+
+    sndio_poll_event (pindex->self, pindex->index, POLLIN);
+}
+
+static void sndio_poll_out(void *arg)
+{
+    struct pollindex *pindex = (struct pollindex *) arg;
+
+    sndio_poll_event (pindex->self, pindex->index, POLLOUT);
+}
+
+static void sndio_fini (SndioVoice *self)
+{
+    if (self->hdl) {
+        sio_close (self->hdl);
+        self->hdl = NULL;
+    }
+
+    if (self->pfds) {
+        g_free (self->pfds);
+        self->pfds = NULL;
+    }
+
+    if (self->pindexes) {
+        g_free (self->pindexes);
+        self->pindexes = NULL;
+    }
+
+    if (self->buf) {
+        g_free (self->buf);
+        self->buf = NULL;
+    }
+}
+
+static int sndio_init (SndioVoice *self,
+                       struct audsettings *as, int mode, Audiodev *dev)
+{
+    AudiodevSndioOptions *opts = &dev->u.sndio;
+    unsigned long long latency;
+    const char *dev_name;
+    struct sio_par req;
+    unsigned int nch;
+    int i, nfds;
+
+    dev_name = opts->has_dev ? opts->dev : SIO_DEVANY;
+    latency = opts->has_latency ? opts->latency : SNDIO_LATENCY_US;
+
+    self->hdl = sio_open (dev_name, mode, 1);
+    if (self->hdl == NULL) {
+        dolog ("failed to open device\n");
+        return -1;
+    }
+
+    self->mode = mode;
+
+    sio_initpar(&req);
+
+    switch (as->fmt) {
+    case AUDIO_FORMAT_S8:
+        req.bits = 8;
+        req.sig = 1;
+        break;
+    case AUDIO_FORMAT_U8:
+        req.bits = 8;
+        req.sig = 0;
+        break;
+    case AUDIO_FORMAT_S16:
+        req.bits = 16;
+        req.sig = 1;
+        break;
+    case AUDIO_FORMAT_U16:
+        req.bits = 16;
+        req.sig = 0;
+        break;
+    case AUDIO_FORMAT_S32:
+        req.bits = 32;
+        req.sig = 1;
+        break;
+    case AUDIO_FORMAT_U32:
+        req.bits = 32;
+        req.sig = 0;
+    default:
+        dolog ("unknown audio sample format\n");
+        return -1;
+    }
+
+    if (req.bits > 8)
+        req.le = as->endianness ? 0 : 1;
+
+    req.rate = as->freq;
+    if (mode == SIO_PLAY)
+            req.pchan = as->nchannels;
+    else
+            req.rchan = as->nchannels;
+
+    /* set on-device buffer size */
+    req.appbufsz = req.rate * latency / 1000000;
+
+    if (!sio_setpar (self->hdl, &req)) {
+        dolog ("failed set audio params\n");
+        goto fail;
+    }
+
+    if (!sio_getpar (self->hdl, &self->par)) {
+        dolog ("failed get audio params\n");
+        goto fail;
+    }
+
+    nch = (mode == SIO_PLAY) ? self->par.pchan : self->par.rchan;
+
+    if (self->par.bits != req.bits || self->par.bps != req.bits / 8 ||
+        self->par.sig != req.sig || (req.bits > 8 && self->par.le != req.le) ||
+        self->par.rate != as->freq || nch != as->nchannels) {
+        dolog ("unsupported audio params\n");
+        goto fail;
+    }
+
+    self->bpf = self->par.bps * nch;
+
+    self->buf = audio_calloc (__func__, self->par.round, self->bpf);
+    if (self->buf == NULL) {
+        dolog ("failed to allocate audio buffer\n");
+        goto fail;
+    }
+
+    nfds = sio_nfds (self->hdl);
+
+    self->pfds = g_malloc_n (nfds, sizeof(struct pollfd));
+    if (self->pfds == NULL) {
+        dolog ("failed to allocate pollfd structures\n");
+        goto fail;
+    }
+
+    self->pindexes = g_malloc_n (nfds, sizeof(struct pollindex));
+    if (self->pindexes == NULL) {
+        dolog ("failed to allocate pollindex structures\n");
+        goto fail;
+    }
+
+    for (i = 0; i < nfds; i++) {
+        self->pindexes[i].self = self;
+        self->pindexes[i].index = i;
+    }
+
+    return 0;
+fail:
+    sndio_fini (self);
+    return -1;
+}
+
+static int sndio_run_out (HWVoiceOut *hw, int live)
+{
+    SndioVoice *self = (SndioVoice *) hw;
+    int decr, todo;
+
+    decr = hw->samples - self->qemu_pos;
+    if (decr > live)
+        decr = live;
+
+    decr = audio_pcm_hw_clip_out (hw, self->buf, decr, self->qemu_pos);
+
+    self->qemu_pos += decr; 
+
+    todo = (self->qemu_pos * self->bpf) - self->sndio_pos;
+    sndio_poll_wait (self, todo > 0 ? POLLOUT : 0);
+
+    return decr;
+}
+
+static int sndio_run_in (HWVoiceIn *hw)
+{
+    SndioVoice *self = (SndioVoice *) hw;
+    int decr, todo, avail;
+
+    avail = (self->sndio_pos / self->bpf) - self->qemu_pos;
+
+    decr = hw->samples - audio_pcm_hw_get_live_in (hw);
+    if (decr > avail)
+        decr = avail;
+
+    if (hw->wpos + decr > hw->samples) {
+        dolog ("%s: overrun: wpos = %d, decr = %d\n", __func__, hw->wpos, decr);
+        abort();
+    }
+
+    hw->conv (hw->conv_buf + hw->wpos,
+              self->buf + (hw->wpos * self->bpf),
+              decr);
+ 
+    self->qemu_pos += decr;
+
+    /* did we complete the block ? */
+    if (self->qemu_pos == self->par.round) {
+        self->qemu_pos = 0;
+        self->sndio_pos = 0;
+    }
+
+    todo = (self->par.round * self->bpf) - self->sndio_pos;
+    sndio_poll_wait (self, todo > 0 ? POLLIN : 0);
+
+    hw->wpos += decr;
+    if (hw->wpos == hw->samples)
+        hw->wpos = 0;
+
+    return decr;
+}
+
+static int sndio_ctl (SndioVoice *self, int cmd)
+{
+    switch (cmd) {
+    case VOICE_ENABLE:
+        sio_start (self->hdl);
+        sndio_poll_wait (self, 0);
+        return 0;
+
+    case VOICE_DISABLE:
+        sndio_poll_clear (self);
+        sio_stop (self->hdl);
+        return 0;
+    }
+
+    return -1;
+}
+
+static int sndio_write (SWVoiceOut *sw, void *buf, int len)
+{
+    return audio_pcm_sw_write (sw, buf, len);
+}
+
+static int sndio_read (SWVoiceIn *sw, void *buf, int len)
+{
+    return audio_pcm_sw_read (sw, buf, len);
+}
+
+static int sndio_ctl_out (HWVoiceOut *hw, int cmd, ...)
+{
+    SndioVoice *self = (SndioVoice *) hw;
+
+    return sndio_ctl (self, cmd);
+}
+
+static int sndio_ctl_in (HWVoiceIn *hw, int cmd, ...)
+{
+    SndioVoice *self = (SndioVoice *) hw;
+
+    return sndio_ctl (self, cmd);
+}
+
+static int sndio_init_out (HWVoiceOut *hw, struct audsettings *as, void *opaque)
+{
+    SndioVoice *self = (SndioVoice *) hw;
+
+    if (sndio_init (self, as, SIO_PLAY, opaque) == -1)
+        return -1;
+
+    audio_pcm_init_info (&hw->info, as);
+    hw->samples = self->par.round;
+
+    return 0;
+}
+
+static int sndio_init_in (HWVoiceIn *hw, struct audsettings *as, void *opaque)
+{
+    SndioVoice *self = (SndioVoice *) hw;
+
+    if (sndio_init (self, as, SIO_REC, opaque) == -1)
+        return -1;
+
+    audio_pcm_init_info (&hw->info, as);
+    hw->samples = self->par.round;
+
+    return 0;
+}
+
+static void sndio_fini_out (HWVoiceOut *hw)
+{
+    SndioVoice *self = (SndioVoice *) hw;
+
+    return sndio_fini(self);
+}
+
+static void sndio_fini_in (HWVoiceIn *hw)
+{
+    SndioVoice *self = (SndioVoice *) hw;
+
+    return sndio_fini(self);
+}
+
+static void *sndio_audio_init (Audiodev *dev)
+{
+    assert(dev->driver == AUDIODEV_DRIVER_SNDIO);
+    return dev;
+}
+
+static void sndio_audio_fini (void *opaque)
+{
+}
+
+static struct audio_pcm_ops sndio_pcm_ops = {
+    .init_out = sndio_init_out,
+    .fini_out = sndio_fini_out,
+    .run_out  = sndio_run_out,
+    .write    = sndio_write,
+    .ctl_out  = sndio_ctl_out,
+
+    .init_in  = sndio_init_in,
+    .fini_in  = sndio_fini_in,
+    .run_in   = sndio_run_in,
+    .read     = sndio_read,
+    .ctl_in   = sndio_ctl_in,
+};
+
+static struct audio_driver sndio_audio_driver = {
+    .name           = "sndio",
+    .descr          = "https://man.openbsd.org/sndio",
+    .init           = sndio_audio_init,
+    .fini           = sndio_audio_fini,
+    .pcm_ops        = &sndio_pcm_ops,
+    .can_be_default = 1,
+    .max_voices_out = INT_MAX,
+    .max_voices_in  = INT_MAX,
+    .voice_size_out = sizeof (SndioVoice),
+    .voice_size_in  = sizeof (SndioVoice)
+};
+
+static void register_audio_sndio(void)
+{
+    audio_driver_register(&sndio_audio_driver);
+}
+
+type_init(register_audio_sndio);
