Here's an attempt to add audio playback and recording support to
qemu. It should allow guest operating systems with no sndio support to
use sndio audio interfaces of the host system.

There's some work remaining to synchronize play and record directions,
but the code is usable enough imho.

With a lot of help from brad@.

OK?

Index: Makefile
===================================================================
RCS file: /cvs/ports/emulators/qemu/Makefile,v
retrieving revision 1.183
diff -u -p -u -p -r1.183 Makefile
--- Makefile    19 Dec 2018 17:57:06 -0000      1.183
+++ Makefile    28 Apr 2019 12:42:14 -0000
@@ -7,6 +7,7 @@ ONLY_FOR_ARCHS= aarch64 amd64 arm i386 p
 COMMENT=       multi system emulator
 
 DISTNAME=      qemu-3.1.0
+REVISION=      0
 CATEGORIES=    emulators
 MASTER_SITES=  https://download.qemu.org/
 EXTRACT_SUFX=  .tar.xz
@@ -22,8 +23,8 @@ WANTLIB=      SDL2 X11 atk-1.0 bz2 c cairo ca
                epoxy fdt gbm gdk-3 gdk_pixbuf-2.0 gio-2.0 glib-2.0 gnutls \
                gobject-2.0 gthread-2.0 gtk-3 intl iscsi jpeg lzo2 m \
                curses nettle nfs pango-1.0 pangocairo-1.0 pcre2-8 \
-               pixman-1 png ssh2 usb-1.0 util vte-2.91 xkbcommon xml2 z \
-               ${COMPILER_LIBCXX}
+               pixman-1 png sndio ssh2 usb-1.0 util vte-2.91 xkbcommon \
+               xml2 z ${COMPILER_LIBCXX}
 
 # Using TLS emulation layer
 COMPILER =     base-clang ports-gcc
Index: patches/patch-audio_Makefile_objs
===================================================================
RCS file: patches/patch-audio_Makefile_objs
diff -N patches/patch-audio_Makefile_objs
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ patches/patch-audio_Makefile_objs   28 Apr 2019 12:42:14 -0000
@@ -0,0 +1,16 @@
+$OpenBSD$
+
+sndio module
+
+Index: audio/Makefile.objs
+--- audio/Makefile.objs.orig
++++ audio/Makefile.objs
+@@ -29,3 +29,8 @@ common-obj-$(CONFIG_AUDIO_SDL) += sdl.mo
+ sdl.mo-objs = sdlaudio.o
+ sdl.mo-cflags := $(SDL_CFLAGS)
+ sdl.mo-libs := $(SDL_LIBS)
++
++# sndio module
++common-obj-$(CONFIG_AUDIO_SNDIO) += sndio.mo
++sndio.mo-objs = sndioaudio.o
++sndio.mo-libs := $(SNDIO_LIBS)
Index: patches/patch-audio_sndioaudio_c
===================================================================
RCS file: patches/patch-audio_sndioaudio_c
diff -N patches/patch-audio_sndioaudio_c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ patches/patch-audio_sndioaudio_c    28 Apr 2019 12:42:14 -0000
@@ -0,0 +1,552 @@
+$OpenBSD$
+
+sndio module
+
+Index: audio/sndioaudio.c
+--- audio/sndioaudio.c.orig
++++ audio/sndioaudio.c
+@@ -0,0 +1,544 @@
++/*
++ * Copyright (c) 2019 Alexandre Ratchov <a...@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"
++
++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 SndioConf glob_conf = {
++    .devname = SIO_DEVANY,
++    .latency = 50
++};
++
++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, SndioConf *cf)
++{
++    struct sio_par req;
++    unsigned int nch;
++    int i, nfds;
++
++    self->hdl = sio_open (cf->devname, mode, 1);
++    if (self->hdl == NULL) {
++        dolog ("failed to open device\n");
++        return -1;
++    }
++
++    self->mode = mode;
++
++    sio_initpar(&req);
++
++    switch (as->fmt) {
++    case AUD_FMT_S8:
++        req.bits = 8;
++        req.sig = 1;
++        break;
++    case AUD_FMT_U8:
++        req.bits = 8;
++        req.sig = 0;
++        break;
++    case AUD_FMT_S16:
++        req.bits = 16;
++        req.sig = 1;
++        break;
++    case AUD_FMT_U16:
++        req.bits = 16;
++        req.sig = 0;
++        break;
++    case AUD_FMT_S32:
++        req.bits = 32;
++        req.sig = 1;
++        break;
++    case AUD_FMT_U32:
++        req.bits = 32;
++        req.sig = 0;
++    }
++
++    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 * cf->latency / 1000;
++
++    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 (void)
++{
++    SndioConf *conf;
++
++    conf = g_malloc(sizeof(SndioConf));
++    if (conf == NULL)
++        return NULL;
++
++    *conf = glob_conf;
++
++    return conf;
++}
++
++static void sndio_audio_fini (void *opaque)
++{
++    g_free(opaque);
++}
++
++static struct audio_option sndio_options[] = {
++    {
++        .name  = "LATENCY",
++        .tag   = AUD_OPT_INT,
++        .valp  = &glob_conf.latency,
++        .descr = "Desired latency in milliseconds"
++    },
++    {
++        .name  = "DEVICE",
++        .tag   = AUD_OPT_STR,
++        .valp  = &glob_conf.devname,
++        .descr = "Device name"
++    },
++    { /* End of list */ }
++};
++
++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";,
++    .options        = sndio_options,
++    .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);
Index: patches/patch-configure
===================================================================
RCS file: /cvs/ports/emulators/qemu/patches/patch-configure,v
retrieving revision 1.59
diff -u -p -u -p -r1.59 patch-configure
--- patches/patch-configure     19 Dec 2018 17:57:06 -0000      1.59
+++ patches/patch-configure     28 Apr 2019 12:42:14 -0000
@@ -1,11 +1,36 @@
 $OpenBSD: patch-configure,v 1.59 2018/12/19 17:57:06 sthen Exp $
 
-Fix curses test to work on OpenBSD
+- sndio module
+- Fix curses test to work on OpenBSD
 
 Index: configure
 --- configure.orig
 +++ configure
-@@ -3370,6 +3370,7 @@ if test "$curses" != "no" ; then
+@@ -819,8 +819,8 @@ NetBSD)
+ OpenBSD)
+   bsd="yes"
+   make="${MAKE-gmake}"
+-  audio_drv_list="sdl"
+-  audio_possible_drivers="sdl"
++  audio_drv_list="sndio"
++  audio_possible_drivers="sndio"
+   HOST_VARIANT_DIR="openbsd"
+   supported_os="yes"
+ ;;
+@@ -3312,6 +3312,12 @@ for drv in $audio_drv_list; do
+     fi
+     ;;
+ 
++    sndio)
++    audio_drv_probe $drv sndio.h "-lsndio" \
++        "sio_open(0, SIO_PLAY, 0); return 0;"
++    sndio_libs="-lsndio"
++    ;;
++
+     coreaudio)
+       coreaudio_libs="-framework CoreAudio"
+     ;;
+@@ -3370,6 +3376,7 @@ if test "$curses" != "no" ; then
    fi
    curses_found=no
    cat > $TMPC << EOF
@@ -13,7 +38,7 @@ Index: configure
  #include <locale.h>
  #include <curses.h>
  #include <wchar.h>
-@@ -5631,10 +5632,6 @@ write_c_skeleton
+@@ -5631,10 +5638,6 @@ write_c_skeleton
  if test "$gcov" = "yes" ; then
    CFLAGS="-fprofile-arcs -ftest-coverage -g $CFLAGS"
    LDFLAGS="-fprofile-arcs -ftest-coverage $LDFLAGS"
@@ -24,3 +49,20 @@ Index: configure
  fi
  
  if test "$have_asan" = "yes"; then
+@@ -6261,7 +6264,7 @@ echo "CONFIG_AUDIO_DRIVERS=$audio_drv_list" >> $config
+ for drv in $audio_drv_list; do
+     def=CONFIG_AUDIO_$(echo $drv | LC_ALL=C tr '[a-z]' '[A-Z]')
+     case "$drv" in
+-      alsa | oss | pa | sdl)
++      alsa | oss | pa | sdl | sndio)
+           echo "$def=m" >> $config_host_mak ;;
+       *)
+           echo "$def=y" >> $config_host_mak ;;
+@@ -6275,6 +6278,7 @@ echo "OSS_LIBS=$oss_libs" >> $config_host_mak
+ if test "$audio_pt_int" = "yes" ; then
+   echo "CONFIG_AUDIO_PT_INT=y" >> $config_host_mak
+ fi
++echo "SNDIO_LIBS=$sndio_libs" >> $config_host_mak
+ if test "$audio_win_int" = "yes" ; then
+   echo "CONFIG_AUDIO_WIN_INT=y" >> $config_host_mak
+ fi

Reply via email to