[PATCH 36/37] audio: split AudioMixengBackend code in audio-mixeng-be.c

marcandre.lureau@redhat.com posted 37 patches 2 weeks, 4 days ago
Maintainers: Gerd Hoffmann <kraxel@redhat.com>, Christian Schoenebeck <qemu_oss@crudebyte.com>, "Marc-André Lureau" <marcandre.lureau@redhat.com>, "Philippe Mathieu-Daudé" <philmd@linaro.org>, Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>, Thomas Huth <huth@tuxfamily.org>, Alexandre Ratchov <alex@caoua.org>, Laurent Vivier <laurent@vivier.eu>, Manos Pitsidianakis <manos.pitsidianakis@linaro.org>, "Michael S. Tsirkin" <mst@redhat.com>, Alistair Francis <alistair@alistair23.me>, "Edgar E. Iglesias" <edgar.iglesias@gmail.com>, Peter Maydell <peter.maydell@linaro.org>, Paolo Bonzini <pbonzini@redhat.com>, "Alex Bennée" <alex.bennee@linaro.org>, "Daniel P. Berrangé" <berrange@redhat.com>, Eduardo Habkost <eduardo@habkost.net>, John Snow <jsnow@redhat.com>, Cleber Rosa <crosa@redhat.com>
[PATCH 36/37] audio: split AudioMixengBackend code in audio-mixeng-be.c
Posted by marcandre.lureau@redhat.com 2 weeks, 4 days ago
From: Marc-André Lureau <marcandre.lureau@redhat.com>

Allow to build the audio/ base classes without the
resampling/mixing/queuing code.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_int.h       |    2 -
 include/qemu/audio.h    |    2 +
 audio/audio-mixeng-be.c | 1979 ++++++++++++++++++++++++++++++++++
 audio/audio.c           | 2226 ++++-----------------------------------
 audio/meson.build       |    1 +
 5 files changed, 2159 insertions(+), 2051 deletions(-)
 create mode 100644 audio/audio-mixeng-be.c

diff --git a/audio/audio_int.h b/audio/audio_int.h
index 250fd45238d..6ecd75c4fbf 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -266,8 +266,6 @@ int audio_bug (const char *funcname, int cond);
 
 void audio_run(AudioMixengBackend *s, const char *msg);
 
-const char *audio_application_name(void);
-
 typedef struct RateCtl {
     int64_t start_ticks;
     int64_t bytes_sent;
diff --git a/include/qemu/audio.h b/include/qemu/audio.h
index 42f97f732a6..dfe247ab8c4 100644
--- a/include/qemu/audio.h
+++ b/include/qemu/audio.h
@@ -183,6 +183,8 @@ bool audio_be_set_dbus_server(AudioBackend *be,
                               Error **errp);
 #endif
 
+const char *audio_application_name(void);
+
 #define DEFINE_AUDIO_PROPERTIES(_s, _f)         \
     DEFINE_PROP_AUDIODEV("audiodev", _s, _f)
 
diff --git a/audio/audio-mixeng-be.c b/audio/audio-mixeng-be.c
new file mode 100644
index 00000000000..8ebceb968ce
--- /dev/null
+++ b/audio/audio-mixeng-be.c
@@ -0,0 +1,1979 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * Copyright (c) 2003-2005 Vassili Karpov (malc)
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/audio.h"
+#include "migration/vmstate.h"
+#include "qemu/timer.h"
+#include "qapi/error.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qapi/qapi-visit-audio.h"
+#include "qapi/qapi-commands-audio.h"
+#include "qobject/qdict.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/help_option.h"
+#include "qom/object.h"
+#include "system/system.h"
+#include "system/replay.h"
+#include "system/runstate.h"
+#include "trace.h"
+
+#define AUDIO_CAP "audio"
+#include "audio_int.h"
+
+/* #define DEBUG_OUT */
+/* #define DEBUG_CAPTURE */
+/* #define DEBUG_POLL */
+
+#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown"
+
+const struct mixeng_volume nominal_volume = {
+    .mute = 0,
+#ifdef FLOAT_MIXENG
+    .r = 1.0,
+    .l = 1.0,
+#else
+    .r = 1ULL << 32,
+    .l = 1ULL << 32,
+#endif
+};
+
+int audio_bug (const char *funcname, int cond)
+{
+    if (cond) {
+        static int shown;
+
+        AUD_log (NULL, "A bug was just triggered in %s\n", funcname);
+        if (!shown) {
+            shown = 1;
+            AUD_log (NULL, "Save all your work and restart without audio\n");
+            AUD_log (NULL, "I am sorry\n");
+        }
+        AUD_log (NULL, "Context:\n");
+    }
+
+    return cond;
+}
+
+static inline int audio_bits_to_index (int bits)
+{
+    switch (bits) {
+    case 8:
+        return 0;
+
+    case 16:
+        return 1;
+
+    case 32:
+        return 2;
+
+    default:
+        audio_bug ("bits_to_index", 1);
+        AUD_log (NULL, "invalid bits %d\n", bits);
+        return 0;
+    }
+}
+
+void AUD_vlog (const char *cap, const char *fmt, va_list ap)
+{
+    if (cap) {
+        fprintf(stderr, "%s: ", cap);
+    }
+
+    vfprintf(stderr, fmt, ap);
+}
+
+void AUD_log (const char *cap, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start (ap, fmt);
+    AUD_vlog (cap, fmt, ap);
+    va_end (ap);
+}
+
+static void audio_print_settings (const struct audsettings *as)
+{
+    dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels);
+
+    switch (as->fmt) {
+    case AUDIO_FORMAT_S8:
+        AUD_log (NULL, "S8");
+        break;
+    case AUDIO_FORMAT_U8:
+        AUD_log (NULL, "U8");
+        break;
+    case AUDIO_FORMAT_S16:
+        AUD_log (NULL, "S16");
+        break;
+    case AUDIO_FORMAT_U16:
+        AUD_log (NULL, "U16");
+        break;
+    case AUDIO_FORMAT_S32:
+        AUD_log (NULL, "S32");
+        break;
+    case AUDIO_FORMAT_U32:
+        AUD_log (NULL, "U32");
+        break;
+    case AUDIO_FORMAT_F32:
+        AUD_log (NULL, "F32");
+        break;
+    default:
+        AUD_log (NULL, "invalid(%d)", as->fmt);
+        break;
+    }
+
+    AUD_log (NULL, " endianness=");
+    switch (as->endianness) {
+    case 0:
+        AUD_log (NULL, "little");
+        break;
+    case 1:
+        AUD_log (NULL, "big");
+        break;
+    default:
+        AUD_log (NULL, "invalid");
+        break;
+    }
+    AUD_log (NULL, "\n");
+}
+
+static int audio_validate_settings (const struct audsettings *as)
+{
+    int invalid;
+
+    invalid = as->nchannels < 1;
+    invalid |= as->endianness != 0 && as->endianness != 1;
+
+    switch (as->fmt) {
+    case AUDIO_FORMAT_S8:
+    case AUDIO_FORMAT_U8:
+    case AUDIO_FORMAT_S16:
+    case AUDIO_FORMAT_U16:
+    case AUDIO_FORMAT_S32:
+    case AUDIO_FORMAT_U32:
+    case AUDIO_FORMAT_F32:
+        break;
+    default:
+        invalid = 1;
+        break;
+    }
+
+    invalid |= as->freq <= 0;
+    return invalid ? -1 : 0;
+}
+
+static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct audsettings *as)
+{
+    int bits = 8;
+    bool is_signed = false, is_float = false;
+
+    switch (as->fmt) {
+    case AUDIO_FORMAT_S8:
+        is_signed = true;
+        /* fall through */
+    case AUDIO_FORMAT_U8:
+        break;
+
+    case AUDIO_FORMAT_S16:
+        is_signed = true;
+        /* fall through */
+    case AUDIO_FORMAT_U16:
+        bits = 16;
+        break;
+
+    case AUDIO_FORMAT_F32:
+        is_float = true;
+        /* fall through */
+    case AUDIO_FORMAT_S32:
+        is_signed = true;
+        /* fall through */
+    case AUDIO_FORMAT_U32:
+        bits = 32;
+        break;
+
+    default:
+        abort();
+    }
+    return info->freq == as->freq
+        && info->nchannels == as->nchannels
+        && info->is_signed == is_signed
+        && info->is_float == is_float
+        && info->bits == bits
+        && info->swap_endianness == (as->endianness != HOST_BIG_ENDIAN);
+}
+
+void audio_pcm_init_info (struct audio_pcm_info *info, const struct audsettings *as)
+{
+    int bits = 8, mul;
+    bool is_signed = false, is_float = false;
+
+    switch (as->fmt) {
+    case AUDIO_FORMAT_S8:
+        is_signed = true;
+        /* fall through */
+    case AUDIO_FORMAT_U8:
+        mul = 1;
+        break;
+
+    case AUDIO_FORMAT_S16:
+        is_signed = true;
+        /* fall through */
+    case AUDIO_FORMAT_U16:
+        bits = 16;
+        mul = 2;
+        break;
+
+    case AUDIO_FORMAT_F32:
+        is_float = true;
+        /* fall through */
+    case AUDIO_FORMAT_S32:
+        is_signed = true;
+        /* fall through */
+    case AUDIO_FORMAT_U32:
+        bits = 32;
+        mul = 4;
+        break;
+
+    default:
+        abort();
+    }
+
+    info->freq = as->freq;
+    info->bits = bits;
+    info->is_signed = is_signed;
+    info->is_float = is_float;
+    info->nchannels = as->nchannels;
+    info->bytes_per_frame = as->nchannels * mul;
+    info->bytes_per_second = info->freq * info->bytes_per_frame;
+    info->swap_endianness = (as->endianness != HOST_BIG_ENDIAN);
+}
+
+void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
+{
+    if (!len) {
+        return;
+    }
+
+    if (info->is_signed || info->is_float) {
+        memset(buf, 0x00, len * info->bytes_per_frame);
+    } else {
+        switch (info->bits) {
+        case 8:
+            memset(buf, 0x80, len * info->bytes_per_frame);
+            break;
+
+        case 16:
+            {
+                int i;
+                uint16_t *p = buf;
+                short s = INT16_MAX;
+
+                if (info->swap_endianness) {
+                    s = bswap16 (s);
+                }
+
+                for (i = 0; i < len * info->nchannels; i++) {
+                    p[i] = s;
+                }
+            }
+            break;
+
+        case 32:
+            {
+                int i;
+                uint32_t *p = buf;
+                int32_t s = INT32_MAX;
+
+                if (info->swap_endianness) {
+                    s = bswap32 (s);
+                }
+
+                for (i = 0; i < len * info->nchannels; i++) {
+                    p[i] = s;
+                }
+            }
+            break;
+
+        default:
+            AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n",
+                     info->bits);
+            break;
+        }
+    }
+}
+
+/*
+ * Capture
+ */
+static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioMixengBackend *s,
+                                                        struct audsettings *as)
+{
+    CaptureVoiceOut *cap;
+
+    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
+        if (audio_pcm_info_eq (&cap->hw.info, as)) {
+            return cap;
+        }
+    }
+    return NULL;
+}
+
+static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd)
+{
+    struct capture_callback *cb;
+
+#ifdef DEBUG_CAPTURE
+    dolog ("notification %d sent\n", cmd);
+#endif
+    for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
+        cb->ops.notify (cb->opaque, cmd);
+    }
+}
+
+static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled)
+{
+    if (cap->hw.enabled != enabled) {
+        audcnotification_e cmd;
+        cap->hw.enabled = enabled;
+        cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE;
+        audio_notify_capture (cap, cmd);
+    }
+}
+
+static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap)
+{
+    HWVoiceOut *hw = &cap->hw;
+    SWVoiceOut *sw;
+    bool enabled = false;
+
+    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+        if (sw->active) {
+            enabled = true;
+            break;
+        }
+    }
+    audio_capture_maybe_changed (cap, enabled);
+}
+
+static void audio_detach_capture (HWVoiceOut *hw)
+{
+    SWVoiceCap *sc = hw->cap_head.lh_first;
+
+    while (sc) {
+        SWVoiceCap *sc1 = sc->entries.le_next;
+        SWVoiceOut *sw = &sc->sw;
+        CaptureVoiceOut *cap = sc->cap;
+        int was_active = sw->active;
+
+        if (sw->rate) {
+            st_rate_stop (sw->rate);
+            sw->rate = NULL;
+        }
+
+        QLIST_REMOVE (sw, entries);
+        QLIST_REMOVE (sc, entries);
+        g_free (sc);
+        if (was_active) {
+            /* We have removed soft voice from the capture:
+               this might have changed the overall status of the capture
+               since this might have been the only active voice */
+            audio_recalc_and_notify_capture (cap);
+        }
+        sc = sc1;
+    }
+}
+
+static int audio_attach_capture (HWVoiceOut *hw)
+{
+    AudioMixengBackend *s = hw->s;
+    CaptureVoiceOut *cap;
+
+    audio_detach_capture (hw);
+    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
+        SWVoiceCap *sc;
+        SWVoiceOut *sw;
+        HWVoiceOut *hw_cap = &cap->hw;
+
+        sc = g_malloc0(sizeof(*sc));
+
+        sc->cap = cap;
+        sw = &sc->sw;
+        sw->hw = hw_cap;
+        sw->info = hw->info;
+        sw->empty = true;
+        sw->active = hw->enabled;
+        sw->vol = nominal_volume;
+        sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
+        QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
+        QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);
+#ifdef DEBUG_CAPTURE
+        sw->name = g_strdup_printf ("for %p %d,%d,%d",
+                                    hw, sw->info.freq, sw->info.bits,
+                                    sw->info.nchannels);
+        dolog ("Added %s active = %d\n", sw->name, sw->active);
+#endif
+        if (sw->active) {
+            audio_capture_maybe_changed (cap, 1);
+        }
+    }
+    return 0;
+}
+
+/*
+ * Hard voice (capture)
+ */
+static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)
+{
+    SWVoiceIn *sw;
+    size_t m = hw->total_samples_captured;
+
+    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+        if (sw->active) {
+            m = MIN (m, sw->total_hw_samples_acquired);
+        }
+    }
+    return m;
+}
+
+static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
+{
+    size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
+    if (audio_bug(__func__, live > hw->conv_buf.size)) {
+        dolog("live=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
+        return 0;
+    }
+    return live;
+}
+
+static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
+{
+    size_t conv = 0;
+    STSampleBuffer *conv_buf = &hw->conv_buf;
+
+    while (samples) {
+        uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);
+        size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);
+
+        hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);
+        conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
+        samples -= proc;
+        conv += proc;
+    }
+
+    return conv;
+}
+
+/*
+ * Soft voice (capture)
+ */
+static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
+    size_t frames_in_max, size_t frames_out_max,
+    size_t *total_in, size_t *total_out)
+{
+    HWVoiceIn *hw = sw->hw;
+    struct st_sample *src, *dst;
+    size_t live, rpos, frames_in, frames_out;
+
+    live = hw->total_samples_captured - sw->total_hw_samples_acquired;
+    rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
+
+    /* resample conv_buf from rpos to end of buffer */
+    src = hw->conv_buf.buffer + rpos;
+    frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos);
+    dst = sw->resample_buf.buffer;
+    frames_out = frames_out_max;
+    st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
+    rpos += frames_in;
+    *total_in = frames_in;
+    *total_out = frames_out;
+
+    /* resample conv_buf from start of buffer if there are input frames left */
+    if (frames_in_max - frames_in && rpos == hw->conv_buf.size) {
+        src = hw->conv_buf.buffer;
+        frames_in = frames_in_max - frames_in;
+        dst += frames_out;
+        frames_out = frames_out_max - frames_out;
+        st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
+        *total_in += frames_in;
+        *total_out += frames_out;
+    }
+}
+
+static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
+{
+    HWVoiceIn *hw = sw->hw;
+    size_t live, frames_out_max, total_in, total_out;
+
+    live = hw->total_samples_captured - sw->total_hw_samples_acquired;
+    if (!live) {
+        return 0;
+    }
+    if (audio_bug(__func__, live > hw->conv_buf.size)) {
+        dolog("live_in=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
+        return 0;
+    }
+
+    frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,
+                         sw->resample_buf.size);
+
+    audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);
+
+    if (!hw->pcm_ops->volume_in) {
+        mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);
+    }
+    sw->clip(buf, sw->resample_buf.buffer, total_out);
+
+    sw->total_hw_samples_acquired += total_in;
+    return total_out * sw->info.bytes_per_frame;
+}
+
+/*
+ * Hard voice (playback)
+ */
+static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)
+{
+    SWVoiceOut *sw;
+    size_t m = SIZE_MAX;
+    int nb_live = 0;
+
+    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+        if (sw->active || !sw->empty) {
+            m = MIN (m, sw->total_hw_samples_mixed);
+            nb_live += 1;
+        }
+    }
+
+    *nb_livep = nb_live;
+    return m;
+}
+
+static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)
+{
+    size_t smin;
+    int nb_live1;
+
+    smin = audio_pcm_hw_find_min_out (hw, &nb_live1);
+    if (nb_live) {
+        *nb_live = nb_live1;
+    }
+
+    if (nb_live1) {
+        size_t live = smin;
+
+        if (audio_bug(__func__, live > hw->mix_buf.size)) {
+            dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
+            return 0;
+        }
+        return live;
+    }
+    return 0;
+}
+
+static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)
+{
+    return (hw->pcm_ops->buffer_get_free ? hw->pcm_ops->buffer_get_free(hw) :
+            INT_MAX) / hw->info.bytes_per_frame;
+}
+
+static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
+{
+    size_t clipped = 0;
+    size_t pos = hw->mix_buf.pos;
+
+    while (len) {
+        st_sample *src = hw->mix_buf.buffer + pos;
+        uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame);
+        size_t samples_till_end_of_buf = hw->mix_buf.size - pos;
+        size_t samples_to_clip = MIN(len, samples_till_end_of_buf);
+
+        hw->clip(dst, src, samples_to_clip);
+
+        pos = (pos + samples_to_clip) % hw->mix_buf.size;
+        len -= samples_to_clip;
+        clipped += samples_to_clip;
+    }
+}
+
+/*
+ * Soft voice (playback)
+ */
+static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
+    size_t frames_in_max, size_t frames_out_max,
+    size_t *total_in, size_t *total_out)
+{
+    HWVoiceOut *hw = sw->hw;
+    struct st_sample *src, *dst;
+    size_t live, wpos, frames_in, frames_out;
+
+    live = sw->total_hw_samples_mixed;
+    wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;
+
+    /* write to mix_buf from wpos to end of buffer */
+    src = sw->resample_buf.buffer;
+    frames_in = frames_in_max;
+    dst = hw->mix_buf.buffer + wpos;
+    frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);
+    st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
+    wpos += frames_out;
+    *total_in = frames_in;
+    *total_out = frames_out;
+
+    /* write to mix_buf from start of buffer if there are input frames left */
+    if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {
+        src += frames_in;
+        frames_in = frames_in_max - frames_in;
+        dst = hw->mix_buf.buffer;
+        frames_out = frames_out_max - frames_out;
+        st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
+        *total_in += frames_in;
+        *total_out += frames_out;
+    }
+}
+
+static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
+{
+    HWVoiceOut *hw = sw->hw;
+    size_t live, dead, hw_free, sw_max, fe_max;
+    size_t frames_in_max, frames_out_max, total_in, total_out;
+
+    live = sw->total_hw_samples_mixed;
+    if (audio_bug(__func__, live > hw->mix_buf.size)) {
+        dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
+        return 0;
+    }
+
+    if (live == hw->mix_buf.size) {
+#ifdef DEBUG_OUT
+        dolog ("%s is full %zu\n", sw->name, live);
+#endif
+        return 0;
+    }
+
+    dead = hw->mix_buf.size - live;
+    hw_free = audio_pcm_hw_get_free(hw);
+    hw_free = hw_free > live ? hw_free - live : 0;
+    frames_out_max = MIN(dead, hw_free);
+    sw_max = st_rate_frames_in(sw->rate, frames_out_max);
+    fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,
+                 sw->resample_buf.size);
+    frames_in_max = MIN(sw_max, fe_max);
+
+    if (!frames_in_max) {
+        return 0;
+    }
+
+    if (frames_in_max > sw->resample_buf.pos) {
+        sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,
+                 buf, frames_in_max - sw->resample_buf.pos);
+        if (!sw->hw->pcm_ops->volume_out) {
+            mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,
+                          frames_in_max - sw->resample_buf.pos, &sw->vol);
+        }
+    }
+
+    audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,
+                              &total_in, &total_out);
+
+    sw->total_hw_samples_mixed += total_out;
+    sw->empty = sw->total_hw_samples_mixed == 0;
+
+    /*
+     * Upsampling may leave one audio frame in the resample buffer. Decrement
+     * total_in by one if there was a leftover frame from the previous resample
+     * pass in the resample buffer. Increment total_in by one if the current
+     * resample pass left one frame in the resample buffer.
+     */
+    if (frames_in_max - total_in == 1) {
+        /* copy one leftover audio frame to the beginning of the buffer */
+        *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);
+        total_in += 1 - sw->resample_buf.pos;
+        sw->resample_buf.pos = 1;
+    } else if (total_in >= sw->resample_buf.pos) {
+        total_in -= sw->resample_buf.pos;
+        sw->resample_buf.pos = 0;
+    }
+
+#ifdef DEBUG_OUT
+    dolog (
+        "%s: write size %zu written %zu total mixed %zu\n",
+        SW_NAME(sw),
+        buf_len / sw->info.bytes_per_frame,
+        total_in,
+        sw->total_hw_samples_mixed
+        );
+#endif
+
+    return total_in * sw->info.bytes_per_frame;
+}
+
+#ifdef DEBUG_AUDIO
+static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
+{
+    dolog("%s: bits %d, sign %d, float %d, freq %d, nchan %d\n",
+          cap, info->bits, info->is_signed, info->is_float, info->freq,
+          info->nchannels);
+}
+#endif
+
+#define DAC
+#include "audio_template.h"
+#undef DAC
+#include "audio_template.h"
+
+/*
+ * Timer
+ */
+static int audio_is_timer_needed(AudioMixengBackend *s)
+{
+    HWVoiceIn *hwi = NULL;
+    HWVoiceOut *hwo = NULL;
+
+    while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
+        if (!hwo->poll_mode) {
+            return 1;
+        }
+    }
+    while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
+        if (!hwi->poll_mode) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static void audio_reset_timer(AudioMixengBackend *s)
+{
+    if (audio_is_timer_needed(s)) {
+        timer_mod_anticipate_ns(s->ts,
+            qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
+        if (!s->timer_running) {
+            s->timer_running = true;
+            s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+            trace_audio_timer_start(s->period_ticks / SCALE_MS);
+        }
+    } else {
+        timer_del(s->ts);
+        if (s->timer_running) {
+            s->timer_running = false;
+            trace_audio_timer_stop();
+        }
+    }
+}
+
+static void audio_timer (void *opaque)
+{
+    int64_t now, diff;
+    AudioMixengBackend *s = opaque;
+
+    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    diff = now - s->timer_last;
+    if (diff > s->period_ticks * 3 / 2) {
+        trace_audio_timer_delayed(diff / SCALE_MS);
+    }
+    s->timer_last = now;
+
+    audio_run(s, "timer");
+    audio_reset_timer(s);
+}
+
+/*
+ * Public API
+ */
+static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw,
+                                         void *buf, size_t size)
+{
+    HWVoiceOut *hw;
+
+    if (!sw) {
+        /* XXX: Consider options */
+        return size;
+    }
+    hw = sw->hw;
+
+    if (!hw->enabled) {
+        dolog("Writing to disabled voice %s\n", SW_NAME(sw));
+        return 0;
+    }
+
+    if (audio_get_pdo_out(hw->s->dev)->mixing_engine) {
+        return audio_pcm_sw_write(sw, buf, size);
+    } else {
+        return hw->pcm_ops->write(hw, buf, size);
+    }
+}
+
+static size_t audio_mixeng_backend_read(AudioBackend *be,
+                                        SWVoiceIn *sw, void *buf, size_t size)
+{
+    HWVoiceIn *hw;
+
+    if (!sw) {
+        /* XXX: Consider options */
+        return size;
+    }
+    hw = sw->hw;
+
+    if (!hw->enabled) {
+        dolog("Reading from disabled voice %s\n", SW_NAME(sw));
+        return 0;
+    }
+
+    if (audio_get_pdo_in(hw->s->dev)->mixing_engine) {
+        return audio_pcm_sw_read(sw, buf, size);
+    } else {
+        return hw->pcm_ops->read(hw, buf, size);
+    }
+
+}
+
+static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
+{
+    if (!sw) {
+        return 0;
+    }
+
+    if (audio_get_pdo_out(sw->s->dev)->mixing_engine) {
+        return sw->resample_buf.size * sw->info.bytes_per_frame;
+    }
+
+    return sw->hw->samples * sw->hw->info.bytes_per_frame;
+}
+
+static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceOut *sw,
+                                                bool on)
+{
+    HWVoiceOut *hw;
+
+    if (!sw) {
+        return;
+    }
+
+    hw = sw->hw;
+    if (sw->active != on) {
+        AudioMixengBackend *s = sw->s;
+        SWVoiceOut *temp_sw;
+        SWVoiceCap *sc;
+
+        if (on) {
+            hw->pending_disable = 0;
+            if (!hw->enabled) {
+                hw->enabled = true;
+                if (runstate_is_running()) {
+                    if (hw->pcm_ops->enable_out) {
+                        hw->pcm_ops->enable_out(hw, true);
+                    }
+                    audio_reset_timer (s);
+                }
+            }
+        } else {
+            if (hw->enabled) {
+                int nb_active = 0;
+
+                for (temp_sw = hw->sw_head.lh_first; temp_sw;
+                     temp_sw = temp_sw->entries.le_next) {
+                    nb_active += temp_sw->active != 0;
+                }
+
+                hw->pending_disable = nb_active == 1;
+            }
+        }
+
+        for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
+            sc->sw.active = hw->enabled;
+            if (hw->enabled) {
+                audio_capture_maybe_changed (sc->cap, 1);
+            }
+        }
+        sw->active = on;
+    }
+
+}
+
+static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
+{
+    HWVoiceIn *hw;
+
+    if (!sw) {
+        return;
+    }
+
+    hw = sw->hw;
+    if (sw->active != on) {
+        AudioMixengBackend *s = sw->s;
+        SWVoiceIn *temp_sw;
+
+        if (on) {
+            if (!hw->enabled) {
+                hw->enabled = true;
+                if (runstate_is_running()) {
+                    if (hw->pcm_ops->enable_in) {
+                        hw->pcm_ops->enable_in(hw, true);
+                    }
+                    audio_reset_timer (s);
+                }
+            }
+            sw->total_hw_samples_acquired = hw->total_samples_captured;
+        } else {
+            if (hw->enabled) {
+                int nb_active = 0;
+
+                for (temp_sw = hw->sw_head.lh_first; temp_sw;
+                     temp_sw = temp_sw->entries.le_next) {
+                    nb_active += temp_sw->active != 0;
+                }
+
+                if (nb_active == 1) {
+                    hw->enabled = false;
+                    if (hw->pcm_ops->enable_in) {
+                        hw->pcm_ops->enable_in(hw, false);
+                    }
+                }
+            }
+        }
+        sw->active = on;
+    }
+}
+
+static size_t audio_get_avail(SWVoiceIn *sw)
+{
+    size_t live;
+
+    if (!sw) {
+        return 0;
+    }
+
+    live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
+    if (audio_bug(__func__, live > sw->hw->conv_buf.size)) {
+        dolog("live=%zu sw->hw->conv_buf.size=%zu\n", live,
+              sw->hw->conv_buf.size);
+        return 0;
+    }
+
+    ldebug (
+        "%s: get_avail live %zu frontend frames %u\n",
+        SW_NAME (sw),
+        live, st_rate_frames_out(sw->rate, live)
+        );
+
+    return live;
+}
+
+static size_t audio_get_free(SWVoiceOut *sw)
+{
+    size_t live, dead;
+
+    if (!sw) {
+        return 0;
+    }
+
+    live = sw->total_hw_samples_mixed;
+
+    if (audio_bug(__func__, live > sw->hw->mix_buf.size)) {
+        dolog("live=%zu sw->hw->mix_buf.size=%zu\n", live,
+              sw->hw->mix_buf.size);
+        return 0;
+    }
+
+    dead = sw->hw->mix_buf.size - live;
+
+#ifdef DEBUG_OUT
+    dolog("%s: get_free live %zu dead %zu frontend frames %u\n",
+          SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead));
+#endif
+
+    return dead;
+}
+
+static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
+                                        size_t samples)
+{
+    size_t n;
+
+    if (hw->enabled) {
+        SWVoiceCap *sc;
+
+        for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
+            SWVoiceOut *sw = &sc->sw;
+            size_t rpos2 = rpos;
+
+            n = samples;
+            while (n) {
+                size_t till_end_of_hw = hw->mix_buf.size - rpos2;
+                size_t to_read = MIN(till_end_of_hw, n);
+                size_t live, frames_in, frames_out;
+
+                sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;
+                sw->resample_buf.size = to_read;
+                live = sw->total_hw_samples_mixed;
+
+                audio_pcm_sw_resample_out(sw,
+                                          to_read, sw->hw->mix_buf.size - live,
+                                          &frames_in, &frames_out);
+
+                sw->total_hw_samples_mixed += frames_out;
+                sw->empty = sw->total_hw_samples_mixed == 0;
+
+                if (to_read - frames_in) {
+                    dolog("Could not mix %zu frames into a capture "
+                          "buffer, mixed %zu\n",
+                          to_read, frames_in);
+                    break;
+                }
+                n -= to_read;
+                rpos2 = (rpos2 + to_read) % hw->mix_buf.size;
+            }
+        }
+    }
+
+    n = MIN(samples, hw->mix_buf.size - rpos);
+    mixeng_clear(hw->mix_buf.buffer + rpos, n);
+    mixeng_clear(hw->mix_buf.buffer, samples - n);
+}
+
+static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
+{
+    size_t clipped = 0;
+
+    while (live) {
+        size_t size = live * hw->info.bytes_per_frame;
+        size_t decr, proc;
+        void *buf = hw->pcm_ops->get_buffer_out(hw, &size);
+
+        if (size == 0) {
+            break;
+        }
+
+        decr = MIN(size / hw->info.bytes_per_frame, live);
+        if (buf) {
+            audio_pcm_hw_clip_out(hw, buf, decr);
+        }
+        proc = hw->pcm_ops->put_buffer_out(hw, buf,
+                                           decr * hw->info.bytes_per_frame) /
+            hw->info.bytes_per_frame;
+
+        live -= proc;
+        clipped += proc;
+        hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size;
+
+        if (proc == 0 || proc < decr) {
+            break;
+        }
+    }
+
+    if (hw->pcm_ops->run_buffer_out) {
+        hw->pcm_ops->run_buffer_out(hw);
+    }
+
+    return clipped;
+}
+
+static void audio_run_out(AudioMixengBackend *s)
+{
+    HWVoiceOut *hw = NULL;
+    SWVoiceOut *sw;
+
+    while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
+        size_t played, live, prev_rpos;
+        size_t hw_free = audio_pcm_hw_get_free(hw);
+        int nb_live;
+
+        if (!audio_get_pdo_out(s->dev)->mixing_engine) {
+            /* there is exactly 1 sw for each hw with no mixeng */
+            sw = hw->sw_head.lh_first;
+
+            if (hw->pending_disable) {
+                hw->enabled = false;
+                hw->pending_disable = false;
+                if (hw->pcm_ops->enable_out) {
+                    hw->pcm_ops->enable_out(hw, false);
+                }
+            }
+
+            if (sw->active) {
+                sw->callback.fn(sw->callback.opaque,
+                                hw_free * sw->info.bytes_per_frame);
+            }
+
+            if (hw->pcm_ops->run_buffer_out) {
+                hw->pcm_ops->run_buffer_out(hw);
+            }
+
+            continue;
+        }
+
+        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+            if (sw->active) {
+                size_t sw_free = audio_get_free(sw);
+                size_t free;
+
+                if (hw_free > sw->total_hw_samples_mixed) {
+                    free = st_rate_frames_in(sw->rate,
+                        MIN(sw_free, hw_free - sw->total_hw_samples_mixed));
+                } else {
+                    free = 0;
+                }
+                if (free > sw->resample_buf.pos) {
+                    free = MIN(free, sw->resample_buf.size)
+                           - sw->resample_buf.pos;
+                    sw->callback.fn(sw->callback.opaque,
+                                    free * sw->info.bytes_per_frame);
+                }
+            }
+        }
+
+        live = audio_pcm_hw_get_live_out (hw, &nb_live);
+        if (!nb_live) {
+            live = 0;
+        }
+
+        if (audio_bug(__func__, live > hw->mix_buf.size)) {
+            dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
+            continue;
+        }
+
+        if (hw->pending_disable && !nb_live) {
+            SWVoiceCap *sc;
+#ifdef DEBUG_OUT
+            dolog ("Disabling voice\n");
+#endif
+            hw->enabled = false;
+            hw->pending_disable = false;
+            if (hw->pcm_ops->enable_out) {
+                hw->pcm_ops->enable_out(hw, false);
+            }
+            for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
+                sc->sw.active = false;
+                audio_recalc_and_notify_capture (sc->cap);
+            }
+            continue;
+        }
+
+        if (!live) {
+            if (hw->pcm_ops->run_buffer_out) {
+                hw->pcm_ops->run_buffer_out(hw);
+            }
+            continue;
+        }
+
+        prev_rpos = hw->mix_buf.pos;
+        played = audio_pcm_hw_run_out(hw, live);
+        replay_audio_out(&played);
+        if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) {
+            dolog("hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\n",
+                  hw->mix_buf.pos, hw->mix_buf.size, played);
+            hw->mix_buf.pos = 0;
+        }
+
+#ifdef DEBUG_OUT
+        dolog("played=%zu\n", played);
+#endif
+
+        if (played) {
+            audio_capture_mix_and_clear (hw, prev_rpos, played);
+        }
+
+        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+            if (!sw->active && sw->empty) {
+                continue;
+            }
+
+            if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) {
+                dolog("played=%zu sw->total_hw_samples_mixed=%zu\n",
+                      played, sw->total_hw_samples_mixed);
+                played = sw->total_hw_samples_mixed;
+            }
+
+            sw->total_hw_samples_mixed -= played;
+
+            if (!sw->total_hw_samples_mixed) {
+                sw->empty = true;
+            }
+        }
+    }
+}
+
+static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
+{
+    size_t conv = 0;
+
+    if (hw->pcm_ops->run_buffer_in) {
+        hw->pcm_ops->run_buffer_in(hw);
+    }
+
+    while (samples) {
+        size_t proc;
+        size_t size = samples * hw->info.bytes_per_frame;
+        void *buf = hw->pcm_ops->get_buffer_in(hw, &size);
+
+        assert(size % hw->info.bytes_per_frame == 0);
+        if (size == 0) {
+            break;
+        }
+
+        proc = audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_frame);
+
+        samples -= proc;
+        conv += proc;
+        hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame);
+    }
+
+    return conv;
+}
+
+static void audio_run_in(AudioMixengBackend *s)
+{
+    HWVoiceIn *hw = NULL;
+
+    if (!audio_get_pdo_in(s->dev)->mixing_engine) {
+        while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
+            /* there is exactly 1 sw for each hw with no mixeng */
+            SWVoiceIn *sw = hw->sw_head.lh_first;
+            if (sw->active) {
+                sw->callback.fn(sw->callback.opaque, INT_MAX);
+            }
+        }
+        return;
+    }
+
+    while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
+        SWVoiceIn *sw;
+        size_t captured = 0, min;
+        int pos;
+
+        if (replay_mode != REPLAY_MODE_PLAY) {
+            captured = audio_pcm_hw_run_in(
+                hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw));
+        }
+
+        replay_audio_in_start(&captured);
+        assert(captured <= hw->conv_buf.size);
+        if (replay_mode == REPLAY_MODE_PLAY) {
+            hw->conv_buf.pos = (hw->conv_buf.pos + captured) % hw->conv_buf.size;
+        }
+        for (pos = (hw->conv_buf.pos - captured + hw->conv_buf.size) % hw->conv_buf.size;
+             pos != hw->conv_buf.pos;
+             pos = (pos + 1) % hw->conv_buf.size) {
+                uint64_t left, right;
+
+                if (replay_mode == REPLAY_MODE_RECORD) {
+                    audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left, &right);
+                }
+                replay_audio_in_sample_lr(&left, &right);
+                if (replay_mode == REPLAY_MODE_PLAY) {
+                    audio_sample_from_uint64(hw->conv_buf.buffer, pos, left, right);
+                }
+        }
+        replay_audio_in_finish();
+
+        min = audio_pcm_hw_find_min_in (hw);
+        hw->total_samples_captured += captured - min;
+
+        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+            sw->total_hw_samples_acquired -= min;
+
+            if (sw->active) {
+                size_t sw_avail = audio_get_avail(sw);
+                size_t avail;
+
+                avail = st_rate_frames_out(sw->rate, sw_avail);
+                if (avail > 0) {
+                    avail = MIN(avail, sw->resample_buf.size);
+                    sw->callback.fn(sw->callback.opaque,
+                                    avail * sw->info.bytes_per_frame);
+                }
+            }
+        }
+    }
+}
+
+static void audio_run_capture(AudioMixengBackend *s)
+{
+    CaptureVoiceOut *cap;
+
+    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
+        size_t live, rpos, captured;
+        HWVoiceOut *hw = &cap->hw;
+        SWVoiceOut *sw;
+
+        captured = live = audio_pcm_hw_get_live_out (hw, NULL);
+        rpos = hw->mix_buf.pos;
+        while (live) {
+            size_t left = hw->mix_buf.size - rpos;
+            size_t to_capture = MIN(live, left);
+            struct st_sample *src;
+            struct capture_callback *cb;
+
+            src = hw->mix_buf.buffer + rpos;
+            hw->clip (cap->buf, src, to_capture);
+            mixeng_clear (src, to_capture);
+
+            for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
+                cb->ops.capture (cb->opaque, cap->buf,
+                                 to_capture * hw->info.bytes_per_frame);
+            }
+            rpos = (rpos + to_capture) % hw->mix_buf.size;
+            live -= to_capture;
+        }
+        hw->mix_buf.pos = rpos;
+
+        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+            if (!sw->active && sw->empty) {
+                continue;
+            }
+
+            if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)) {
+                dolog("captured=%zu sw->total_hw_samples_mixed=%zu\n",
+                      captured, sw->total_hw_samples_mixed);
+                captured = sw->total_hw_samples_mixed;
+            }
+
+            sw->total_hw_samples_mixed -= captured;
+            sw->empty = sw->total_hw_samples_mixed == 0;
+        }
+    }
+}
+
+void audio_run(AudioMixengBackend *s, const char *msg)
+{
+    audio_run_out(s);
+    audio_run_in(s);
+    audio_run_capture(s);
+
+#ifdef DEBUG_POLL
+    {
+        static double prevtime;
+        double currtime;
+        struct timeval tv;
+
+        if (gettimeofday (&tv, NULL)) {
+            perror ("audio_run: gettimeofday");
+            return;
+        }
+
+        currtime = tv.tv_sec + tv.tv_usec * 1e-6;
+        dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime);
+        prevtime = currtime;
+    }
+#endif
+}
+
+void audio_generic_run_buffer_in(HWVoiceIn *hw)
+{
+    if (unlikely(!hw->buf_emul)) {
+        hw->size_emul = hw->samples * hw->info.bytes_per_frame;
+        hw->buf_emul = g_malloc(hw->size_emul);
+        hw->pos_emul = hw->pending_emul = 0;
+    }
+
+    while (hw->pending_emul < hw->size_emul) {
+        size_t read_len = MIN(hw->size_emul - hw->pos_emul,
+                              hw->size_emul - hw->pending_emul);
+        size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul,
+                                        read_len);
+        hw->pending_emul += read;
+        hw->pos_emul = (hw->pos_emul + read) % hw->size_emul;
+        if (read < read_len) {
+            break;
+        }
+    }
+}
+
+void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)
+{
+    size_t start;
+
+    start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
+    assert(start < hw->size_emul);
+
+    *size = MIN(*size, hw->pending_emul);
+    *size = MIN(*size, hw->size_emul - start);
+    return hw->buf_emul + start;
+}
+
+void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
+{
+    assert(size <= hw->pending_emul);
+    hw->pending_emul -= size;
+}
+
+size_t audio_generic_buffer_get_free(HWVoiceOut *hw)
+{
+    if (hw->buf_emul) {
+        return hw->size_emul - hw->pending_emul;
+    } else {
+        return hw->samples * hw->info.bytes_per_frame;
+    }
+}
+
+void audio_generic_run_buffer_out(HWVoiceOut *hw)
+{
+    while (hw->pending_emul) {
+        size_t write_len, written, start;
+
+        start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
+        assert(start < hw->size_emul);
+
+        write_len = MIN(hw->pending_emul, hw->size_emul - start);
+
+        written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len);
+        hw->pending_emul -= written;
+
+        if (written < write_len) {
+            break;
+        }
+    }
+}
+
+void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)
+{
+    if (unlikely(!hw->buf_emul)) {
+        hw->size_emul = hw->samples * hw->info.bytes_per_frame;
+        hw->buf_emul = g_malloc(hw->size_emul);
+        hw->pos_emul = hw->pending_emul = 0;
+    }
+
+    *size = MIN(hw->size_emul - hw->pending_emul,
+                hw->size_emul - hw->pos_emul);
+    return hw->buf_emul + hw->pos_emul;
+}
+
+size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
+{
+    assert(buf == hw->buf_emul + hw->pos_emul &&
+           size + hw->pending_emul <= hw->size_emul);
+
+    hw->pending_emul += size;
+    hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;
+
+    return size;
+}
+
+size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size)
+{
+    size_t total = 0;
+
+    if (hw->pcm_ops->buffer_get_free) {
+        size_t free = hw->pcm_ops->buffer_get_free(hw);
+
+        size = MIN(size, free);
+    }
+
+    while (total < size) {
+        size_t dst_size = size - total;
+        size_t copy_size, proc;
+        void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size);
+
+        if (dst_size == 0) {
+            break;
+        }
+
+        copy_size = MIN(size - total, dst_size);
+        if (dst) {
+            memcpy(dst, (char *)buf + total, copy_size);
+        }
+        proc = hw->pcm_ops->put_buffer_out(hw, dst, copy_size);
+        total += proc;
+
+        if (proc == 0 || proc < copy_size) {
+            break;
+        }
+    }
+
+    return total;
+}
+
+size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
+{
+    size_t total = 0;
+
+    if (hw->pcm_ops->run_buffer_in) {
+        hw->pcm_ops->run_buffer_in(hw);
+    }
+
+    while (total < size) {
+        size_t src_size = size - total;
+        void *src = hw->pcm_ops->get_buffer_in(hw, &src_size);
+
+        if (src_size == 0) {
+            break;
+        }
+
+        memcpy((char *)buf + total, src, src_size);
+        hw->pcm_ops->put_buffer_in(hw, src, src_size);
+        total += src_size;
+    }
+
+    return total;
+}
+
+static bool audio_mixeng_backend_realize(AudioBackend *abe,
+                                         Audiodev *dev, Error **errp)
+{
+    AudioMixengBackend *be = AUDIO_MIXENG_BACKEND(abe);
+    audio_driver *drv = AUDIO_MIXENG_BACKEND_GET_CLASS(be)->driver;
+
+    be->dev = dev;
+    be->drv_opaque = drv->init(be->dev, errp);
+    if (!be->drv_opaque) {
+        return false;
+    }
+
+    if (!drv->pcm_ops->get_buffer_in) {
+        drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in;
+        drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in;
+    }
+    if (!drv->pcm_ops->get_buffer_out) {
+        drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out;
+        drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;
+    }
+
+    audio_init_nb_voices_out(be, drv, 1);
+    audio_init_nb_voices_in(be, drv, 0);
+    be->drv = drv;
+
+    if (be->dev->timer_period <= 0) {
+        be->period_ticks = 1;
+    } else {
+        be->period_ticks = be->dev->timer_period * (int64_t)SCALE_US;
+    }
+
+    return true;
+}
+
+static void audio_vm_change_state_handler (void *opaque, bool running,
+                                           RunState state)
+{
+    AudioMixengBackend *s = opaque;
+    HWVoiceOut *hwo = NULL;
+    HWVoiceIn *hwi = NULL;
+
+    while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
+        if (hwo->pcm_ops->enable_out) {
+            hwo->pcm_ops->enable_out(hwo, running);
+        }
+    }
+
+    while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
+        if (hwi->pcm_ops->enable_in) {
+            hwi->pcm_ops->enable_in(hwi, running);
+        }
+    }
+    audio_reset_timer (s);
+}
+
+static const VMStateDescription vmstate_audio;
+
+static const char *audio_mixeng_backend_get_id(AudioBackend *be)
+{
+    return AUDIO_MIXENG_BACKEND(be)->dev->id;
+}
+
+static CaptureVoiceOut *audio_mixeng_backend_add_capture(
+    AudioBackend *be,
+    struct audsettings *as,
+    struct audio_capture_ops *ops,
+    void *cb_opaque);
+
+static void audio_mixeng_backend_del_capture(
+    AudioBackend *be,
+    CaptureVoiceOut *cap,
+    void *cb_opaque);
+
+static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
+                                                Volume *vol);
+static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
+                                               Volume *vol);
+
+static void audio_mixeng_backend_class_init(ObjectClass *klass, const void *data)
+{
+    AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
+
+    be->realize = audio_mixeng_backend_realize;
+    be->get_id = audio_mixeng_backend_get_id;
+    be->open_in = audio_mixeng_backend_open_in;
+    be->open_out = audio_mixeng_backend_open_out;
+    be->close_in = audio_mixeng_backend_close_in;
+    be->close_out = audio_mixeng_backend_close_out;
+    be->is_active_out = audio_mixeng_backend_is_active_out;
+    be->is_active_in = audio_mixeng_backend_is_active_in;
+    be->set_active_out = audio_mixeng_backend_set_active_out;
+    be->set_active_in = audio_mixeng_backend_set_active_in;
+    be->set_volume_out = audio_mixeng_backend_set_volume_out;
+    be->set_volume_in = audio_mixeng_backend_set_volume_in;
+    be->read = audio_mixeng_backend_read;
+    be->write = audio_mixeng_backend_write;
+    be->get_buffer_size_out = audio_mixeng_backend_get_buffer_size_out;
+    be->add_capture = audio_mixeng_backend_add_capture;
+    be->del_capture = audio_mixeng_backend_del_capture;
+}
+
+static void audio_mixeng_backend_init(Object *obj)
+{
+    AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
+
+    QLIST_INIT(&s->hw_head_out);
+    QLIST_INIT(&s->hw_head_in);
+    QLIST_INIT(&s->cap_head);
+    s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
+
+    s->vmse = qemu_add_vm_change_state_handler(audio_vm_change_state_handler, s);
+    assert(s->vmse != NULL);
+
+    vmstate_register_any(NULL, &vmstate_audio, s);
+}
+
+static void audio_mixeng_backend_finalize(Object *obj)
+{
+    AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
+    HWVoiceOut *hwo, *hwon;
+    HWVoiceIn *hwi, *hwin;
+
+    QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) {
+        SWVoiceCap *sc;
+
+        if (hwo->enabled && hwo->pcm_ops->enable_out) {
+            hwo->pcm_ops->enable_out(hwo, false);
+        }
+        hwo->pcm_ops->fini_out (hwo);
+
+        for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) {
+            CaptureVoiceOut *cap = sc->cap;
+            struct capture_callback *cb;
+
+            for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
+                cb->ops.destroy (cb->opaque);
+            }
+        }
+        QLIST_REMOVE(hwo, entries);
+    }
+
+    QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) {
+        if (hwi->enabled && hwi->pcm_ops->enable_in) {
+            hwi->pcm_ops->enable_in(hwi, false);
+        }
+        hwi->pcm_ops->fini_in (hwi);
+        QLIST_REMOVE(hwi, entries);
+    }
+
+    if (s->drv) {
+        s->drv->fini (s->drv_opaque);
+        s->drv = NULL;
+    }
+
+    if (s->dev) {
+        qapi_free_Audiodev(s->dev);
+        s->dev = NULL;
+    }
+
+    if (s->ts) {
+        timer_free(s->ts);
+        s->ts = NULL;
+    }
+
+    if (s->vmse) {
+        qemu_del_vm_change_state_handler(s->vmse);
+        s->vmse = NULL;
+    }
+
+    vmstate_unregister(NULL, &vmstate_audio, s);
+}
+
+static bool vmstate_audio_needed(void *opaque)
+{
+    /*
+     * Never needed, this vmstate only exists in case
+     * an old qemu sends it to us.
+     */
+    return false;
+}
+
+static const VMStateDescription vmstate_audio = {
+    .name = "audio",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .needed = vmstate_audio_needed,
+    .fields = (const VMStateField[]) {
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static struct audio_pcm_ops capture_pcm_ops;
+
+static CaptureVoiceOut *audio_mixeng_backend_add_capture(
+    AudioBackend *be,
+    struct audsettings *as,
+    struct audio_capture_ops *ops,
+    void *cb_opaque)
+{
+    AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(be);
+    CaptureVoiceOut *cap;
+    struct capture_callback *cb;
+
+    if (!s) {
+        error_report("Capturing without setting an audiodev is not supported");
+        abort();
+    }
+
+    if (!audio_get_pdo_out(s->dev)->mixing_engine) {
+        dolog("Can't capture with mixeng disabled\n");
+        return NULL;
+    }
+
+    if (audio_validate_settings (as)) {
+        dolog ("Invalid settings were passed when trying to add capture\n");
+        audio_print_settings (as);
+        return NULL;
+    }
+
+    cb = g_malloc0(sizeof(*cb));
+    cb->ops = *ops;
+    cb->opaque = cb_opaque;
+
+    cap = audio_pcm_capture_find_specific(s, as);
+    if (cap) {
+        QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
+    } else {
+        HWVoiceOut *hw;
+
+        cap = g_malloc0(sizeof(*cap));
+
+        hw = &cap->hw;
+        hw->s = s;
+        hw->pcm_ops = &capture_pcm_ops;
+        QLIST_INIT (&hw->sw_head);
+        QLIST_INIT (&cap->cb_head);
+
+        /* XXX find a more elegant way */
+        hw->samples = 4096 * 4;
+        audio_pcm_hw_alloc_resources_out(hw);
+
+        audio_pcm_init_info (&hw->info, as);
+
+        cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame);
+
+        if (hw->info.is_float) {
+            hw->clip = mixeng_clip_float[hw->info.nchannels == 2]
+                [hw->info.swap_endianness];
+        } else {
+            hw->clip = mixeng_clip
+                [hw->info.nchannels == 2]
+                [hw->info.is_signed]
+                [hw->info.swap_endianness]
+                [audio_bits_to_index(hw->info.bits)];
+        }
+
+        QLIST_INSERT_HEAD (&s->cap_head, cap, entries);
+        QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
+
+        QLIST_FOREACH(hw, &s->hw_head_out, entries) {
+            audio_attach_capture (hw);
+        }
+    }
+
+    return cap;
+}
+
+static void audio_mixeng_backend_del_capture(
+    AudioBackend *be,
+    CaptureVoiceOut *cap,
+    void *cb_opaque)
+{
+    struct capture_callback *cb;
+
+    for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
+        if (cb->opaque == cb_opaque) {
+            cb->ops.destroy (cb_opaque);
+            QLIST_REMOVE (cb, entries);
+            g_free (cb);
+
+            if (!cap->cb_head.lh_first) {
+                SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1;
+
+                while (sw) {
+                    SWVoiceCap *sc = (SWVoiceCap *) sw;
+#ifdef DEBUG_CAPTURE
+                    dolog ("freeing %s\n", sw->name);
+#endif
+
+                    sw1 = sw->entries.le_next;
+                    if (sw->rate) {
+                        st_rate_stop (sw->rate);
+                        sw->rate = NULL;
+                    }
+                    QLIST_REMOVE (sw, entries);
+                    QLIST_REMOVE (sc, entries);
+                    g_free (sc);
+                    sw = sw1;
+                }
+                QLIST_REMOVE (cap, entries);
+                g_free(cap->hw.mix_buf.buffer);
+                g_free (cap->buf);
+                g_free (cap);
+            }
+            return;
+        }
+    }
+}
+
+static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
+                                                Volume *vol)
+{
+    if (sw) {
+        HWVoiceOut *hw = sw->hw;
+
+        sw->vol.mute = vol->mute;
+        sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
+        sw->vol.r = nominal_volume.l * vol->vol[vol->channels > 1 ? 1 : 0] /
+            255;
+
+        if (hw->pcm_ops->volume_out) {
+            hw->pcm_ops->volume_out(hw, vol);
+        }
+    }
+}
+
+static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
+                                               Volume *vol)
+{
+    if (sw) {
+        HWVoiceIn *hw = sw->hw;
+
+        sw->vol.mute = vol->mute;
+        sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
+        sw->vol.r = nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : 0] /
+            255;
+
+        if (hw->pcm_ops->volume_in) {
+            hw->pcm_ops->volume_in(hw, vol);
+        }
+    }
+}
+
+audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo)
+{
+    return (audsettings) {
+        .freq = pdo->frequency,
+        .nchannels = pdo->channels,
+        .fmt = pdo->format,
+        .endianness = HOST_BIG_ENDIAN,
+    };
+}
+
+int audioformat_bytes_per_sample(AudioFormat fmt)
+{
+    switch (fmt) {
+    case AUDIO_FORMAT_U8:
+    case AUDIO_FORMAT_S8:
+        return 1;
+
+    case AUDIO_FORMAT_U16:
+    case AUDIO_FORMAT_S16:
+        return 2;
+
+    case AUDIO_FORMAT_U32:
+    case AUDIO_FORMAT_S32:
+    case AUDIO_FORMAT_F32:
+        return 4;
+
+    case AUDIO_FORMAT__MAX:
+        ;
+    }
+    abort();
+}
+
+
+/* frames = freq * usec / 1e6 */
+int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,
+                        audsettings *as, int def_usecs)
+{
+    uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs;
+    return (as->freq * usecs + 500000) / 1000000;
+}
+
+/* samples = channels * frames = channels * freq * usec / 1e6 */
+int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,
+                         audsettings *as, int def_usecs)
+{
+    return as->nchannels * audio_buffer_frames(pdo, as, def_usecs);
+}
+
+/*
+ * bytes = bytes_per_sample * samples =
+ *     bytes_per_sample * channels * freq * usec / 1e6
+ */
+int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,
+                       audsettings *as, int def_usecs)
+{
+    return audio_buffer_samples(pdo, as, def_usecs) *
+        audioformat_bytes_per_sample(as->fmt);
+}
+
+void audio_rate_start(RateCtl *rate)
+{
+    memset(rate, 0, sizeof(RateCtl));
+    rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+}
+
+size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info)
+{
+    int64_t now;
+    int64_t ticks;
+    int64_t bytes;
+    int64_t frames;
+
+    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    ticks = now - rate->start_ticks;
+    bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);
+    frames = (bytes - rate->bytes_sent) / info->bytes_per_frame;
+    rate->peeked_frames = frames;
+
+    return frames < 0 ? 0 : frames * info->bytes_per_frame;
+}
+
+void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used)
+{
+    if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) {
+        AUD_log(NULL, "Resetting rate control (%" PRId64 " frames)\n",
+                rate->peeked_frames);
+        audio_rate_start(rate);
+    }
+
+    rate->bytes_sent += bytes_used;
+}
+
+size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info,
+                            size_t bytes_avail)
+{
+    size_t bytes;
+
+    bytes = audio_rate_peek_bytes(rate, info);
+    bytes = MIN(bytes, bytes_avail);
+    audio_rate_add_bytes(rate, bytes);
+
+    return bytes;
+}
+
+static const TypeInfo audio_mixeng_backend_info = {
+    .name = TYPE_AUDIO_MIXENG_BACKEND,
+    .parent = TYPE_AUDIO_BACKEND,
+    .instance_size = sizeof(AudioMixengBackend),
+    .instance_init = audio_mixeng_backend_init,
+    .instance_finalize = audio_mixeng_backend_finalize,
+    .abstract = false,
+    .class_size = sizeof(AudioMixengBackendClass),
+    .class_init = audio_mixeng_backend_class_init,
+};
+
+static void register_types(void)
+{
+    type_register_static(&audio_mixeng_backend_info);
+}
+
+type_init(register_types);
diff --git a/audio/audio.c b/audio/audio.c
index 1d948084e80..6f10ee470ee 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1,1750 +1,55 @@
-/*
- * QEMU Audio subsystem
- *
- * Copyright (c) 2003-2005 Vassili Karpov (malc)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-#include "qemu/osdep.h"
-#include "qemu/audio.h"
-#include "migration/vmstate.h"
-#include "qemu/timer.h"
-#include "qapi/error.h"
-#include "qapi/clone-visitor.h"
-#include "qapi/qobject-input-visitor.h"
-#include "qapi/qapi-visit-audio.h"
-#include "qapi/qapi-commands-audio.h"
-#include "qobject/qdict.h"
-#include "qemu/error-report.h"
-#include "qemu/log.h"
-#include "qemu/module.h"
-#include "qemu/help_option.h"
-#include "qom/object.h"
-#include "system/system.h"
-#include "system/replay.h"
-#include "system/runstate.h"
-#include "trace.h"
-
-#define AUDIO_CAP "audio"
-#include "audio_int.h"
-
-/* #define DEBUG_LIVE */
-/* #define DEBUG_OUT */
-/* #define DEBUG_CAPTURE */
-/* #define DEBUG_POLL */
-
-#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown"
-
-
-/* Order of CONFIG_AUDIO_DRIVERS is import.
-   The 1st one is the one used by default, that is the reason
-    that we generate the list.
-*/
-const char *audio_prio_list[] = {
-#ifdef CONFIG_GIO
-    "dbus",
-#endif
-    "spice",
-    CONFIG_AUDIO_DRIVERS
-    "none",
-    NULL
-};
-
-typedef struct AudiodevListEntry {
-    Audiodev *dev;
-    QSIMPLEQ_ENTRY(AudiodevListEntry) next;
-} AudiodevListEntry;
-
-typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;
-
-static AudiodevListHead audiodevs =
-    QSIMPLEQ_HEAD_INITIALIZER(audiodevs);
-static AudiodevListHead default_audiodevs =
-    QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);
-
-static AudioBackendClass *audio_be_class_by_name(const char *name)
-{
-    g_autofree char *tname = g_strconcat("audio-", name, NULL);
-    ObjectClass *oc = module_object_class_by_name(tname);
-
-    if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) {
-        return NULL;
-    }
-
-    return AUDIO_BACKEND_CLASS(oc);
-}
-
-static AudioBackend *default_audio_be;
-
-const struct mixeng_volume nominal_volume = {
-    .mute = 0,
-#ifdef FLOAT_MIXENG
-    .r = 1.0,
-    .l = 1.0,
-#else
-    .r = 1ULL << 32,
-    .l = 1ULL << 32,
-#endif
-};
-
-int audio_bug (const char *funcname, int cond)
-{
-    if (cond) {
-        static int shown;
-
-        AUD_log (NULL, "A bug was just triggered in %s\n", funcname);
-        if (!shown) {
-            shown = 1;
-            AUD_log (NULL, "Save all your work and restart without audio\n");
-            AUD_log (NULL, "I am sorry\n");
-        }
-        AUD_log (NULL, "Context:\n");
-    }
-
-    return cond;
-}
-
-static inline int audio_bits_to_index (int bits)
-{
-    switch (bits) {
-    case 8:
-        return 0;
-
-    case 16:
-        return 1;
-
-    case 32:
-        return 2;
-
-    default:
-        audio_bug ("bits_to_index", 1);
-        AUD_log (NULL, "invalid bits %d\n", bits);
-        return 0;
-    }
-}
-
-void AUD_vlog (const char *cap, const char *fmt, va_list ap)
-{
-    if (cap) {
-        fprintf(stderr, "%s: ", cap);
-    }
-
-    vfprintf(stderr, fmt, ap);
-}
-
-void AUD_log (const char *cap, const char *fmt, ...)
-{
-    va_list ap;
-
-    va_start (ap, fmt);
-    AUD_vlog (cap, fmt, ap);
-    va_end (ap);
-}
-
-static void audio_print_settings (const struct audsettings *as)
-{
-    dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels);
-
-    switch (as->fmt) {
-    case AUDIO_FORMAT_S8:
-        AUD_log (NULL, "S8");
-        break;
-    case AUDIO_FORMAT_U8:
-        AUD_log (NULL, "U8");
-        break;
-    case AUDIO_FORMAT_S16:
-        AUD_log (NULL, "S16");
-        break;
-    case AUDIO_FORMAT_U16:
-        AUD_log (NULL, "U16");
-        break;
-    case AUDIO_FORMAT_S32:
-        AUD_log (NULL, "S32");
-        break;
-    case AUDIO_FORMAT_U32:
-        AUD_log (NULL, "U32");
-        break;
-    case AUDIO_FORMAT_F32:
-        AUD_log (NULL, "F32");
-        break;
-    default:
-        AUD_log (NULL, "invalid(%d)", as->fmt);
-        break;
-    }
-
-    AUD_log (NULL, " endianness=");
-    switch (as->endianness) {
-    case 0:
-        AUD_log (NULL, "little");
-        break;
-    case 1:
-        AUD_log (NULL, "big");
-        break;
-    default:
-        AUD_log (NULL, "invalid");
-        break;
-    }
-    AUD_log (NULL, "\n");
-}
-
-static int audio_validate_settings (const struct audsettings *as)
-{
-    int invalid;
-
-    invalid = as->nchannels < 1;
-    invalid |= as->endianness != 0 && as->endianness != 1;
-
-    switch (as->fmt) {
-    case AUDIO_FORMAT_S8:
-    case AUDIO_FORMAT_U8:
-    case AUDIO_FORMAT_S16:
-    case AUDIO_FORMAT_U16:
-    case AUDIO_FORMAT_S32:
-    case AUDIO_FORMAT_U32:
-    case AUDIO_FORMAT_F32:
-        break;
-    default:
-        invalid = 1;
-        break;
-    }
-
-    invalid |= as->freq <= 0;
-    return invalid ? -1 : 0;
-}
-
-static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct audsettings *as)
-{
-    int bits = 8;
-    bool is_signed = false, is_float = false;
-
-    switch (as->fmt) {
-    case AUDIO_FORMAT_S8:
-        is_signed = true;
-        /* fall through */
-    case AUDIO_FORMAT_U8:
-        break;
-
-    case AUDIO_FORMAT_S16:
-        is_signed = true;
-        /* fall through */
-    case AUDIO_FORMAT_U16:
-        bits = 16;
-        break;
-
-    case AUDIO_FORMAT_F32:
-        is_float = true;
-        /* fall through */
-    case AUDIO_FORMAT_S32:
-        is_signed = true;
-        /* fall through */
-    case AUDIO_FORMAT_U32:
-        bits = 32;
-        break;
-
-    default:
-        abort();
-    }
-    return info->freq == as->freq
-        && info->nchannels == as->nchannels
-        && info->is_signed == is_signed
-        && info->is_float == is_float
-        && info->bits == bits
-        && info->swap_endianness == (as->endianness != HOST_BIG_ENDIAN);
-}
-
-void audio_pcm_init_info (struct audio_pcm_info *info, const struct audsettings *as)
-{
-    int bits = 8, mul;
-    bool is_signed = false, is_float = false;
-
-    switch (as->fmt) {
-    case AUDIO_FORMAT_S8:
-        is_signed = true;
-        /* fall through */
-    case AUDIO_FORMAT_U8:
-        mul = 1;
-        break;
-
-    case AUDIO_FORMAT_S16:
-        is_signed = true;
-        /* fall through */
-    case AUDIO_FORMAT_U16:
-        bits = 16;
-        mul = 2;
-        break;
-
-    case AUDIO_FORMAT_F32:
-        is_float = true;
-        /* fall through */
-    case AUDIO_FORMAT_S32:
-        is_signed = true;
-        /* fall through */
-    case AUDIO_FORMAT_U32:
-        bits = 32;
-        mul = 4;
-        break;
-
-    default:
-        abort();
-    }
-
-    info->freq = as->freq;
-    info->bits = bits;
-    info->is_signed = is_signed;
-    info->is_float = is_float;
-    info->nchannels = as->nchannels;
-    info->bytes_per_frame = as->nchannels * mul;
-    info->bytes_per_second = info->freq * info->bytes_per_frame;
-    info->swap_endianness = (as->endianness != HOST_BIG_ENDIAN);
-}
-
-void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
-{
-    if (!len) {
-        return;
-    }
-
-    if (info->is_signed || info->is_float) {
-        memset(buf, 0x00, len * info->bytes_per_frame);
-    } else {
-        switch (info->bits) {
-        case 8:
-            memset(buf, 0x80, len * info->bytes_per_frame);
-            break;
-
-        case 16:
-            {
-                int i;
-                uint16_t *p = buf;
-                short s = INT16_MAX;
-
-                if (info->swap_endianness) {
-                    s = bswap16 (s);
-                }
-
-                for (i = 0; i < len * info->nchannels; i++) {
-                    p[i] = s;
-                }
-            }
-            break;
-
-        case 32:
-            {
-                int i;
-                uint32_t *p = buf;
-                int32_t s = INT32_MAX;
-
-                if (info->swap_endianness) {
-                    s = bswap32 (s);
-                }
-
-                for (i = 0; i < len * info->nchannels; i++) {
-                    p[i] = s;
-                }
-            }
-            break;
-
-        default:
-            AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n",
-                     info->bits);
-            break;
-        }
-    }
-}
-
-/*
- * Capture
- */
-static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioMixengBackend *s,
-                                                        struct audsettings *as)
-{
-    CaptureVoiceOut *cap;
-
-    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
-        if (audio_pcm_info_eq (&cap->hw.info, as)) {
-            return cap;
-        }
-    }
-    return NULL;
-}
-
-static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd)
-{
-    struct capture_callback *cb;
-
-#ifdef DEBUG_CAPTURE
-    dolog ("notification %d sent\n", cmd);
-#endif
-    for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
-        cb->ops.notify (cb->opaque, cmd);
-    }
-}
-
-static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled)
-{
-    if (cap->hw.enabled != enabled) {
-        audcnotification_e cmd;
-        cap->hw.enabled = enabled;
-        cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE;
-        audio_notify_capture (cap, cmd);
-    }
-}
-
-static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap)
-{
-    HWVoiceOut *hw = &cap->hw;
-    SWVoiceOut *sw;
-    bool enabled = false;
-
-    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
-        if (sw->active) {
-            enabled = true;
-            break;
-        }
-    }
-    audio_capture_maybe_changed (cap, enabled);
-}
-
-static void audio_detach_capture (HWVoiceOut *hw)
-{
-    SWVoiceCap *sc = hw->cap_head.lh_first;
-
-    while (sc) {
-        SWVoiceCap *sc1 = sc->entries.le_next;
-        SWVoiceOut *sw = &sc->sw;
-        CaptureVoiceOut *cap = sc->cap;
-        int was_active = sw->active;
-
-        if (sw->rate) {
-            st_rate_stop (sw->rate);
-            sw->rate = NULL;
-        }
-
-        QLIST_REMOVE (sw, entries);
-        QLIST_REMOVE (sc, entries);
-        g_free (sc);
-        if (was_active) {
-            /* We have removed soft voice from the capture:
-               this might have changed the overall status of the capture
-               since this might have been the only active voice */
-            audio_recalc_and_notify_capture (cap);
-        }
-        sc = sc1;
-    }
-}
-
-static int audio_attach_capture (HWVoiceOut *hw)
-{
-    AudioMixengBackend *s = hw->s;
-    CaptureVoiceOut *cap;
-
-    audio_detach_capture (hw);
-    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
-        SWVoiceCap *sc;
-        SWVoiceOut *sw;
-        HWVoiceOut *hw_cap = &cap->hw;
-
-        sc = g_malloc0(sizeof(*sc));
-
-        sc->cap = cap;
-        sw = &sc->sw;
-        sw->hw = hw_cap;
-        sw->info = hw->info;
-        sw->empty = true;
-        sw->active = hw->enabled;
-        sw->vol = nominal_volume;
-        sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
-        QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
-        QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);
-#ifdef DEBUG_CAPTURE
-        sw->name = g_strdup_printf ("for %p %d,%d,%d",
-                                    hw, sw->info.freq, sw->info.bits,
-                                    sw->info.nchannels);
-        dolog ("Added %s active = %d\n", sw->name, sw->active);
-#endif
-        if (sw->active) {
-            audio_capture_maybe_changed (cap, 1);
-        }
-    }
-    return 0;
-}
-
-/*
- * Hard voice (capture)
- */
-static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)
-{
-    SWVoiceIn *sw;
-    size_t m = hw->total_samples_captured;
-
-    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
-        if (sw->active) {
-            m = MIN (m, sw->total_hw_samples_acquired);
-        }
-    }
-    return m;
-}
-
-static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
-{
-    size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
-    if (audio_bug(__func__, live > hw->conv_buf.size)) {
-        dolog("live=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
-        return 0;
-    }
-    return live;
-}
-
-static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
-{
-    size_t conv = 0;
-    STSampleBuffer *conv_buf = &hw->conv_buf;
-
-    while (samples) {
-        uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);
-        size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);
-
-        hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);
-        conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
-        samples -= proc;
-        conv += proc;
-    }
-
-    return conv;
-}
-
-/*
- * Soft voice (capture)
- */
-static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
-    size_t frames_in_max, size_t frames_out_max,
-    size_t *total_in, size_t *total_out)
-{
-    HWVoiceIn *hw = sw->hw;
-    struct st_sample *src, *dst;
-    size_t live, rpos, frames_in, frames_out;
-
-    live = hw->total_samples_captured - sw->total_hw_samples_acquired;
-    rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
-
-    /* resample conv_buf from rpos to end of buffer */
-    src = hw->conv_buf.buffer + rpos;
-    frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos);
-    dst = sw->resample_buf.buffer;
-    frames_out = frames_out_max;
-    st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
-    rpos += frames_in;
-    *total_in = frames_in;
-    *total_out = frames_out;
-
-    /* resample conv_buf from start of buffer if there are input frames left */
-    if (frames_in_max - frames_in && rpos == hw->conv_buf.size) {
-        src = hw->conv_buf.buffer;
-        frames_in = frames_in_max - frames_in;
-        dst += frames_out;
-        frames_out = frames_out_max - frames_out;
-        st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
-        *total_in += frames_in;
-        *total_out += frames_out;
-    }
-}
-
-static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
-{
-    HWVoiceIn *hw = sw->hw;
-    size_t live, frames_out_max, total_in, total_out;
-
-    live = hw->total_samples_captured - sw->total_hw_samples_acquired;
-    if (!live) {
-        return 0;
-    }
-    if (audio_bug(__func__, live > hw->conv_buf.size)) {
-        dolog("live_in=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
-        return 0;
-    }
-
-    frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,
-                         sw->resample_buf.size);
-
-    audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);
-
-    if (!hw->pcm_ops->volume_in) {
-        mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);
-    }
-    sw->clip(buf, sw->resample_buf.buffer, total_out);
-
-    sw->total_hw_samples_acquired += total_in;
-    return total_out * sw->info.bytes_per_frame;
-}
-
-/*
- * Hard voice (playback)
- */
-static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)
-{
-    SWVoiceOut *sw;
-    size_t m = SIZE_MAX;
-    int nb_live = 0;
-
-    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
-        if (sw->active || !sw->empty) {
-            m = MIN (m, sw->total_hw_samples_mixed);
-            nb_live += 1;
-        }
-    }
-
-    *nb_livep = nb_live;
-    return m;
-}
-
-static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)
-{
-    size_t smin;
-    int nb_live1;
-
-    smin = audio_pcm_hw_find_min_out (hw, &nb_live1);
-    if (nb_live) {
-        *nb_live = nb_live1;
-    }
-
-    if (nb_live1) {
-        size_t live = smin;
-
-        if (audio_bug(__func__, live > hw->mix_buf.size)) {
-            dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
-            return 0;
-        }
-        return live;
-    }
-    return 0;
-}
-
-static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)
-{
-    return (hw->pcm_ops->buffer_get_free ? hw->pcm_ops->buffer_get_free(hw) :
-            INT_MAX) / hw->info.bytes_per_frame;
-}
-
-static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
-{
-    size_t clipped = 0;
-    size_t pos = hw->mix_buf.pos;
-
-    while (len) {
-        st_sample *src = hw->mix_buf.buffer + pos;
-        uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame);
-        size_t samples_till_end_of_buf = hw->mix_buf.size - pos;
-        size_t samples_to_clip = MIN(len, samples_till_end_of_buf);
-
-        hw->clip(dst, src, samples_to_clip);
-
-        pos = (pos + samples_to_clip) % hw->mix_buf.size;
-        len -= samples_to_clip;
-        clipped += samples_to_clip;
-    }
-}
-
-/*
- * Soft voice (playback)
- */
-static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
-    size_t frames_in_max, size_t frames_out_max,
-    size_t *total_in, size_t *total_out)
-{
-    HWVoiceOut *hw = sw->hw;
-    struct st_sample *src, *dst;
-    size_t live, wpos, frames_in, frames_out;
-
-    live = sw->total_hw_samples_mixed;
-    wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;
-
-    /* write to mix_buf from wpos to end of buffer */
-    src = sw->resample_buf.buffer;
-    frames_in = frames_in_max;
-    dst = hw->mix_buf.buffer + wpos;
-    frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);
-    st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
-    wpos += frames_out;
-    *total_in = frames_in;
-    *total_out = frames_out;
-
-    /* write to mix_buf from start of buffer if there are input frames left */
-    if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {
-        src += frames_in;
-        frames_in = frames_in_max - frames_in;
-        dst = hw->mix_buf.buffer;
-        frames_out = frames_out_max - frames_out;
-        st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
-        *total_in += frames_in;
-        *total_out += frames_out;
-    }
-}
-
-static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
-{
-    HWVoiceOut *hw = sw->hw;
-    size_t live, dead, hw_free, sw_max, fe_max;
-    size_t frames_in_max, frames_out_max, total_in, total_out;
-
-    live = sw->total_hw_samples_mixed;
-    if (audio_bug(__func__, live > hw->mix_buf.size)) {
-        dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
-        return 0;
-    }
-
-    if (live == hw->mix_buf.size) {
-#ifdef DEBUG_OUT
-        dolog ("%s is full %zu\n", sw->name, live);
-#endif
-        return 0;
-    }
-
-    dead = hw->mix_buf.size - live;
-    hw_free = audio_pcm_hw_get_free(hw);
-    hw_free = hw_free > live ? hw_free - live : 0;
-    frames_out_max = MIN(dead, hw_free);
-    sw_max = st_rate_frames_in(sw->rate, frames_out_max);
-    fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,
-                 sw->resample_buf.size);
-    frames_in_max = MIN(sw_max, fe_max);
-
-    if (!frames_in_max) {
-        return 0;
-    }
-
-    if (frames_in_max > sw->resample_buf.pos) {
-        sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,
-                 buf, frames_in_max - sw->resample_buf.pos);
-        if (!sw->hw->pcm_ops->volume_out) {
-            mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,
-                          frames_in_max - sw->resample_buf.pos, &sw->vol);
-        }
-    }
-
-    audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,
-                              &total_in, &total_out);
-
-    sw->total_hw_samples_mixed += total_out;
-    sw->empty = sw->total_hw_samples_mixed == 0;
-
-    /*
-     * Upsampling may leave one audio frame in the resample buffer. Decrement
-     * total_in by one if there was a leftover frame from the previous resample
-     * pass in the resample buffer. Increment total_in by one if the current
-     * resample pass left one frame in the resample buffer.
-     */
-    if (frames_in_max - total_in == 1) {
-        /* copy one leftover audio frame to the beginning of the buffer */
-        *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);
-        total_in += 1 - sw->resample_buf.pos;
-        sw->resample_buf.pos = 1;
-    } else if (total_in >= sw->resample_buf.pos) {
-        total_in -= sw->resample_buf.pos;
-        sw->resample_buf.pos = 0;
-    }
-
-#ifdef DEBUG_OUT
-    dolog (
-        "%s: write size %zu written %zu total mixed %zu\n",
-        SW_NAME(sw),
-        buf_len / sw->info.bytes_per_frame,
-        total_in,
-        sw->total_hw_samples_mixed
-        );
-#endif
-
-    return total_in * sw->info.bytes_per_frame;
-}
-
-#ifdef DEBUG_AUDIO
-static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
-{
-    dolog("%s: bits %d, sign %d, float %d, freq %d, nchan %d\n",
-          cap, info->bits, info->is_signed, info->is_float, info->freq,
-          info->nchannels);
-}
-#endif
-
-#define DAC
-#include "audio_template.h"
-#undef DAC
-#include "audio_template.h"
-
-/*
- * Timer
- */
-static int audio_is_timer_needed(AudioMixengBackend *s)
-{
-    HWVoiceIn *hwi = NULL;
-    HWVoiceOut *hwo = NULL;
-
-    while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
-        if (!hwo->poll_mode) {
-            return 1;
-        }
-    }
-    while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
-        if (!hwi->poll_mode) {
-            return 1;
-        }
-    }
-    return 0;
-}
-
-static void audio_reset_timer(AudioMixengBackend *s)
-{
-    if (audio_is_timer_needed(s)) {
-        timer_mod_anticipate_ns(s->ts,
-            qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
-        if (!s->timer_running) {
-            s->timer_running = true;
-            s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
-            trace_audio_timer_start(s->period_ticks / SCALE_MS);
-        }
-    } else {
-        timer_del(s->ts);
-        if (s->timer_running) {
-            s->timer_running = false;
-            trace_audio_timer_stop();
-        }
-    }
-}
-
-static void audio_timer (void *opaque)
-{
-    int64_t now, diff;
-    AudioMixengBackend *s = opaque;
-
-    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
-    diff = now - s->timer_last;
-    if (diff > s->period_ticks * 3 / 2) {
-        trace_audio_timer_delayed(diff / SCALE_MS);
-    }
-    s->timer_last = now;
-
-    audio_run(s, "timer");
-    audio_reset_timer(s);
-}
-
-/*
- * Public API
- */
-static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw,
-                                         void *buf, size_t size)
-{
-    HWVoiceOut *hw;
-
-    if (!sw) {
-        /* XXX: Consider options */
-        return size;
-    }
-    hw = sw->hw;
-
-    if (!hw->enabled) {
-        dolog("Writing to disabled voice %s\n", SW_NAME(sw));
-        return 0;
-    }
-
-    if (audio_get_pdo_out(hw->s->dev)->mixing_engine) {
-        return audio_pcm_sw_write(sw, buf, size);
-    } else {
-        return hw->pcm_ops->write(hw, buf, size);
-    }
-}
-
-static size_t audio_mixeng_backend_read(AudioBackend *be,
-                                        SWVoiceIn *sw, void *buf, size_t size)
-{
-    HWVoiceIn *hw;
-
-    if (!sw) {
-        /* XXX: Consider options */
-        return size;
-    }
-    hw = sw->hw;
-
-    if (!hw->enabled) {
-        dolog("Reading from disabled voice %s\n", SW_NAME(sw));
-        return 0;
-    }
-
-    if (audio_get_pdo_in(hw->s->dev)->mixing_engine) {
-        return audio_pcm_sw_read(sw, buf, size);
-    } else {
-        return hw->pcm_ops->read(hw, buf, size);
-    }
-
-}
-
-static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
-{
-    if (!sw) {
-        return 0;
-    }
-
-    if (audio_get_pdo_out(sw->s->dev)->mixing_engine) {
-        return sw->resample_buf.size * sw->info.bytes_per_frame;
-    }
-
-    return sw->hw->samples * sw->hw->info.bytes_per_frame;
-}
-
-static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceOut *sw,
-                                                bool on)
-{
-    HWVoiceOut *hw;
-
-    if (!sw) {
-        return;
-    }
-
-    hw = sw->hw;
-    if (sw->active != on) {
-        AudioMixengBackend *s = sw->s;
-        SWVoiceOut *temp_sw;
-        SWVoiceCap *sc;
-
-        if (on) {
-            hw->pending_disable = 0;
-            if (!hw->enabled) {
-                hw->enabled = true;
-                if (runstate_is_running()) {
-                    if (hw->pcm_ops->enable_out) {
-                        hw->pcm_ops->enable_out(hw, true);
-                    }
-                    audio_reset_timer (s);
-                }
-            }
-        } else {
-            if (hw->enabled) {
-                int nb_active = 0;
-
-                for (temp_sw = hw->sw_head.lh_first; temp_sw;
-                     temp_sw = temp_sw->entries.le_next) {
-                    nb_active += temp_sw->active != 0;
-                }
-
-                hw->pending_disable = nb_active == 1;
-            }
-        }
-
-        for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
-            sc->sw.active = hw->enabled;
-            if (hw->enabled) {
-                audio_capture_maybe_changed (sc->cap, 1);
-            }
-        }
-        sw->active = on;
-    }
-
-}
-
-static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
-{
-    HWVoiceIn *hw;
-
-    if (!sw) {
-        return;
-    }
-
-    hw = sw->hw;
-    if (sw->active != on) {
-        AudioMixengBackend *s = sw->s;
-        SWVoiceIn *temp_sw;
-
-        if (on) {
-            if (!hw->enabled) {
-                hw->enabled = true;
-                if (runstate_is_running()) {
-                    if (hw->pcm_ops->enable_in) {
-                        hw->pcm_ops->enable_in(hw, true);
-                    }
-                    audio_reset_timer (s);
-                }
-            }
-            sw->total_hw_samples_acquired = hw->total_samples_captured;
-        } else {
-            if (hw->enabled) {
-                int nb_active = 0;
-
-                for (temp_sw = hw->sw_head.lh_first; temp_sw;
-                     temp_sw = temp_sw->entries.le_next) {
-                    nb_active += temp_sw->active != 0;
-                }
-
-                if (nb_active == 1) {
-                    hw->enabled = false;
-                    if (hw->pcm_ops->enable_in) {
-                        hw->pcm_ops->enable_in(hw, false);
-                    }
-                }
-            }
-        }
-        sw->active = on;
-    }
-}
-
-static size_t audio_get_avail(SWVoiceIn *sw)
-{
-    size_t live;
-
-    if (!sw) {
-        return 0;
-    }
-
-    live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
-    if (audio_bug(__func__, live > sw->hw->conv_buf.size)) {
-        dolog("live=%zu sw->hw->conv_buf.size=%zu\n", live,
-              sw->hw->conv_buf.size);
-        return 0;
-    }
-
-    ldebug (
-        "%s: get_avail live %zu frontend frames %u\n",
-        SW_NAME (sw),
-        live, st_rate_frames_out(sw->rate, live)
-        );
-
-    return live;
-}
-
-static size_t audio_get_free(SWVoiceOut *sw)
-{
-    size_t live, dead;
-
-    if (!sw) {
-        return 0;
-    }
-
-    live = sw->total_hw_samples_mixed;
-
-    if (audio_bug(__func__, live > sw->hw->mix_buf.size)) {
-        dolog("live=%zu sw->hw->mix_buf.size=%zu\n", live,
-              sw->hw->mix_buf.size);
-        return 0;
-    }
-
-    dead = sw->hw->mix_buf.size - live;
-
-#ifdef DEBUG_OUT
-    dolog("%s: get_free live %zu dead %zu frontend frames %u\n",
-          SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead));
-#endif
-
-    return dead;
-}
-
-static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
-                                        size_t samples)
-{
-    size_t n;
-
-    if (hw->enabled) {
-        SWVoiceCap *sc;
-
-        for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
-            SWVoiceOut *sw = &sc->sw;
-            size_t rpos2 = rpos;
-
-            n = samples;
-            while (n) {
-                size_t till_end_of_hw = hw->mix_buf.size - rpos2;
-                size_t to_read = MIN(till_end_of_hw, n);
-                size_t live, frames_in, frames_out;
-
-                sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;
-                sw->resample_buf.size = to_read;
-                live = sw->total_hw_samples_mixed;
-
-                audio_pcm_sw_resample_out(sw,
-                                          to_read, sw->hw->mix_buf.size - live,
-                                          &frames_in, &frames_out);
-
-                sw->total_hw_samples_mixed += frames_out;
-                sw->empty = sw->total_hw_samples_mixed == 0;
-
-                if (to_read - frames_in) {
-                    dolog("Could not mix %zu frames into a capture "
-                          "buffer, mixed %zu\n",
-                          to_read, frames_in);
-                    break;
-                }
-                n -= to_read;
-                rpos2 = (rpos2 + to_read) % hw->mix_buf.size;
-            }
-        }
-    }
-
-    n = MIN(samples, hw->mix_buf.size - rpos);
-    mixeng_clear(hw->mix_buf.buffer + rpos, n);
-    mixeng_clear(hw->mix_buf.buffer, samples - n);
-}
-
-static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
-{
-    size_t clipped = 0;
-
-    while (live) {
-        size_t size = live * hw->info.bytes_per_frame;
-        size_t decr, proc;
-        void *buf = hw->pcm_ops->get_buffer_out(hw, &size);
-
-        if (size == 0) {
-            break;
-        }
-
-        decr = MIN(size / hw->info.bytes_per_frame, live);
-        if (buf) {
-            audio_pcm_hw_clip_out(hw, buf, decr);
-        }
-        proc = hw->pcm_ops->put_buffer_out(hw, buf,
-                                           decr * hw->info.bytes_per_frame) /
-            hw->info.bytes_per_frame;
-
-        live -= proc;
-        clipped += proc;
-        hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size;
-
-        if (proc == 0 || proc < decr) {
-            break;
-        }
-    }
-
-    if (hw->pcm_ops->run_buffer_out) {
-        hw->pcm_ops->run_buffer_out(hw);
-    }
-
-    return clipped;
-}
-
-static void audio_run_out(AudioMixengBackend *s)
-{
-    HWVoiceOut *hw = NULL;
-    SWVoiceOut *sw;
-
-    while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
-        size_t played, live, prev_rpos;
-        size_t hw_free = audio_pcm_hw_get_free(hw);
-        int nb_live;
-
-        if (!audio_get_pdo_out(s->dev)->mixing_engine) {
-            /* there is exactly 1 sw for each hw with no mixeng */
-            sw = hw->sw_head.lh_first;
-
-            if (hw->pending_disable) {
-                hw->enabled = false;
-                hw->pending_disable = false;
-                if (hw->pcm_ops->enable_out) {
-                    hw->pcm_ops->enable_out(hw, false);
-                }
-            }
-
-            if (sw->active) {
-                sw->callback.fn(sw->callback.opaque,
-                                hw_free * sw->info.bytes_per_frame);
-            }
-
-            if (hw->pcm_ops->run_buffer_out) {
-                hw->pcm_ops->run_buffer_out(hw);
-            }
-
-            continue;
-        }
-
-        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
-            if (sw->active) {
-                size_t sw_free = audio_get_free(sw);
-                size_t free;
-
-                if (hw_free > sw->total_hw_samples_mixed) {
-                    free = st_rate_frames_in(sw->rate,
-                        MIN(sw_free, hw_free - sw->total_hw_samples_mixed));
-                } else {
-                    free = 0;
-                }
-                if (free > sw->resample_buf.pos) {
-                    free = MIN(free, sw->resample_buf.size)
-                           - sw->resample_buf.pos;
-                    sw->callback.fn(sw->callback.opaque,
-                                    free * sw->info.bytes_per_frame);
-                }
-            }
-        }
-
-        live = audio_pcm_hw_get_live_out (hw, &nb_live);
-        if (!nb_live) {
-            live = 0;
-        }
-
-        if (audio_bug(__func__, live > hw->mix_buf.size)) {
-            dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
-            continue;
-        }
-
-        if (hw->pending_disable && !nb_live) {
-            SWVoiceCap *sc;
-#ifdef DEBUG_OUT
-            dolog ("Disabling voice\n");
-#endif
-            hw->enabled = false;
-            hw->pending_disable = false;
-            if (hw->pcm_ops->enable_out) {
-                hw->pcm_ops->enable_out(hw, false);
-            }
-            for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
-                sc->sw.active = false;
-                audio_recalc_and_notify_capture (sc->cap);
-            }
-            continue;
-        }
-
-        if (!live) {
-            if (hw->pcm_ops->run_buffer_out) {
-                hw->pcm_ops->run_buffer_out(hw);
-            }
-            continue;
-        }
-
-        prev_rpos = hw->mix_buf.pos;
-        played = audio_pcm_hw_run_out(hw, live);
-        replay_audio_out(&played);
-        if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) {
-            dolog("hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\n",
-                  hw->mix_buf.pos, hw->mix_buf.size, played);
-            hw->mix_buf.pos = 0;
-        }
-
-#ifdef DEBUG_OUT
-        dolog("played=%zu\n", played);
-#endif
-
-        if (played) {
-            audio_capture_mix_and_clear (hw, prev_rpos, played);
-        }
-
-        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
-            if (!sw->active && sw->empty) {
-                continue;
-            }
-
-            if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) {
-                dolog("played=%zu sw->total_hw_samples_mixed=%zu\n",
-                      played, sw->total_hw_samples_mixed);
-                played = sw->total_hw_samples_mixed;
-            }
-
-            sw->total_hw_samples_mixed -= played;
-
-            if (!sw->total_hw_samples_mixed) {
-                sw->empty = true;
-            }
-        }
-    }
-}
-
-static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
-{
-    size_t conv = 0;
-
-    if (hw->pcm_ops->run_buffer_in) {
-        hw->pcm_ops->run_buffer_in(hw);
-    }
-
-    while (samples) {
-        size_t proc;
-        size_t size = samples * hw->info.bytes_per_frame;
-        void *buf = hw->pcm_ops->get_buffer_in(hw, &size);
-
-        assert(size % hw->info.bytes_per_frame == 0);
-        if (size == 0) {
-            break;
-        }
-
-        proc = audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_frame);
-
-        samples -= proc;
-        conv += proc;
-        hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame);
-    }
-
-    return conv;
-}
-
-static void audio_run_in(AudioMixengBackend *s)
-{
-    HWVoiceIn *hw = NULL;
-
-    if (!audio_get_pdo_in(s->dev)->mixing_engine) {
-        while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
-            /* there is exactly 1 sw for each hw with no mixeng */
-            SWVoiceIn *sw = hw->sw_head.lh_first;
-            if (sw->active) {
-                sw->callback.fn(sw->callback.opaque, INT_MAX);
-            }
-        }
-        return;
-    }
-
-    while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
-        SWVoiceIn *sw;
-        size_t captured = 0, min;
-        int pos;
-
-        if (replay_mode != REPLAY_MODE_PLAY) {
-            captured = audio_pcm_hw_run_in(
-                hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw));
-        }
-
-        replay_audio_in_start(&captured);
-        assert(captured <= hw->conv_buf.size);
-        if (replay_mode == REPLAY_MODE_PLAY) {
-            hw->conv_buf.pos = (hw->conv_buf.pos + captured) % hw->conv_buf.size;
-        }
-        for (pos = (hw->conv_buf.pos - captured + hw->conv_buf.size) % hw->conv_buf.size;
-             pos != hw->conv_buf.pos;
-             pos = (pos + 1) % hw->conv_buf.size) {
-                uint64_t left, right;
-
-                if (replay_mode == REPLAY_MODE_RECORD) {
-                    audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left, &right);
-                }
-                replay_audio_in_sample_lr(&left, &right);
-                if (replay_mode == REPLAY_MODE_PLAY) {
-                    audio_sample_from_uint64(hw->conv_buf.buffer, pos, left, right);
-                }
-        }
-        replay_audio_in_finish();
-
-        min = audio_pcm_hw_find_min_in (hw);
-        hw->total_samples_captured += captured - min;
-
-        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
-            sw->total_hw_samples_acquired -= min;
-
-            if (sw->active) {
-                size_t sw_avail = audio_get_avail(sw);
-                size_t avail;
-
-                avail = st_rate_frames_out(sw->rate, sw_avail);
-                if (avail > 0) {
-                    avail = MIN(avail, sw->resample_buf.size);
-                    sw->callback.fn(sw->callback.opaque,
-                                    avail * sw->info.bytes_per_frame);
-                }
-            }
-        }
-    }
-}
-
-static void audio_run_capture(AudioMixengBackend *s)
-{
-    CaptureVoiceOut *cap;
-
-    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
-        size_t live, rpos, captured;
-        HWVoiceOut *hw = &cap->hw;
-        SWVoiceOut *sw;
-
-        captured = live = audio_pcm_hw_get_live_out (hw, NULL);
-        rpos = hw->mix_buf.pos;
-        while (live) {
-            size_t left = hw->mix_buf.size - rpos;
-            size_t to_capture = MIN(live, left);
-            struct st_sample *src;
-            struct capture_callback *cb;
-
-            src = hw->mix_buf.buffer + rpos;
-            hw->clip (cap->buf, src, to_capture);
-            mixeng_clear (src, to_capture);
-
-            for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
-                cb->ops.capture (cb->opaque, cap->buf,
-                                 to_capture * hw->info.bytes_per_frame);
-            }
-            rpos = (rpos + to_capture) % hw->mix_buf.size;
-            live -= to_capture;
-        }
-        hw->mix_buf.pos = rpos;
-
-        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
-            if (!sw->active && sw->empty) {
-                continue;
-            }
-
-            if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)) {
-                dolog("captured=%zu sw->total_hw_samples_mixed=%zu\n",
-                      captured, sw->total_hw_samples_mixed);
-                captured = sw->total_hw_samples_mixed;
-            }
-
-            sw->total_hw_samples_mixed -= captured;
-            sw->empty = sw->total_hw_samples_mixed == 0;
-        }
-    }
-}
-
-void audio_run(AudioMixengBackend *s, const char *msg)
-{
-    audio_run_out(s);
-    audio_run_in(s);
-    audio_run_capture(s);
-
-#ifdef DEBUG_POLL
-    {
-        static double prevtime;
-        double currtime;
-        struct timeval tv;
-
-        if (gettimeofday (&tv, NULL)) {
-            perror ("audio_run: gettimeofday");
-            return;
-        }
-
-        currtime = tv.tv_sec + tv.tv_usec * 1e-6;
-        dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime);
-        prevtime = currtime;
-    }
-#endif
-}
-
-void audio_generic_run_buffer_in(HWVoiceIn *hw)
-{
-    if (unlikely(!hw->buf_emul)) {
-        hw->size_emul = hw->samples * hw->info.bytes_per_frame;
-        hw->buf_emul = g_malloc(hw->size_emul);
-        hw->pos_emul = hw->pending_emul = 0;
-    }
-
-    while (hw->pending_emul < hw->size_emul) {
-        size_t read_len = MIN(hw->size_emul - hw->pos_emul,
-                              hw->size_emul - hw->pending_emul);
-        size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul,
-                                        read_len);
-        hw->pending_emul += read;
-        hw->pos_emul = (hw->pos_emul + read) % hw->size_emul;
-        if (read < read_len) {
-            break;
-        }
-    }
-}
-
-void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)
-{
-    size_t start;
-
-    start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
-    assert(start < hw->size_emul);
-
-    *size = MIN(*size, hw->pending_emul);
-    *size = MIN(*size, hw->size_emul - start);
-    return hw->buf_emul + start;
-}
-
-void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
-{
-    assert(size <= hw->pending_emul);
-    hw->pending_emul -= size;
-}
-
-size_t audio_generic_buffer_get_free(HWVoiceOut *hw)
-{
-    if (hw->buf_emul) {
-        return hw->size_emul - hw->pending_emul;
-    } else {
-        return hw->samples * hw->info.bytes_per_frame;
-    }
-}
-
-void audio_generic_run_buffer_out(HWVoiceOut *hw)
-{
-    while (hw->pending_emul) {
-        size_t write_len, written, start;
-
-        start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
-        assert(start < hw->size_emul);
-
-        write_len = MIN(hw->pending_emul, hw->size_emul - start);
-
-        written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len);
-        hw->pending_emul -= written;
-
-        if (written < write_len) {
-            break;
-        }
-    }
-}
-
-void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)
-{
-    if (unlikely(!hw->buf_emul)) {
-        hw->size_emul = hw->samples * hw->info.bytes_per_frame;
-        hw->buf_emul = g_malloc(hw->size_emul);
-        hw->pos_emul = hw->pending_emul = 0;
-    }
-
-    *size = MIN(hw->size_emul - hw->pending_emul,
-                hw->size_emul - hw->pos_emul);
-    return hw->buf_emul + hw->pos_emul;
-}
-
-size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
-{
-    assert(buf == hw->buf_emul + hw->pos_emul &&
-           size + hw->pending_emul <= hw->size_emul);
-
-    hw->pending_emul += size;
-    hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;
-
-    return size;
-}
-
-size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size)
-{
-    size_t total = 0;
-
-    if (hw->pcm_ops->buffer_get_free) {
-        size_t free = hw->pcm_ops->buffer_get_free(hw);
-
-        size = MIN(size, free);
-    }
-
-    while (total < size) {
-        size_t dst_size = size - total;
-        size_t copy_size, proc;
-        void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size);
-
-        if (dst_size == 0) {
-            break;
-        }
-
-        copy_size = MIN(size - total, dst_size);
-        if (dst) {
-            memcpy(dst, (char *)buf + total, copy_size);
-        }
-        proc = hw->pcm_ops->put_buffer_out(hw, dst, copy_size);
-        total += proc;
-
-        if (proc == 0 || proc < copy_size) {
-            break;
-        }
-    }
-
-    return total;
-}
-
-size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
-{
-    size_t total = 0;
-
-    if (hw->pcm_ops->run_buffer_in) {
-        hw->pcm_ops->run_buffer_in(hw);
-    }
-
-    while (total < size) {
-        size_t src_size = size - total;
-        void *src = hw->pcm_ops->get_buffer_in(hw, &src_size);
-
-        if (src_size == 0) {
-            break;
-        }
-
-        memcpy((char *)buf + total, src, src_size);
-        hw->pcm_ops->put_buffer_in(hw, src, src_size);
-        total += src_size;
-    }
-
-    return total;
-}
-
-static bool audio_mixeng_backend_realize(AudioBackend *abe,
-                                         Audiodev *dev, Error **errp)
-{
-    AudioMixengBackend *be = AUDIO_MIXENG_BACKEND(abe);
-    audio_driver *drv = AUDIO_MIXENG_BACKEND_GET_CLASS(be)->driver;
-
-    be->dev = dev;
-    be->drv_opaque = drv->init(be->dev, errp);
-    if (!be->drv_opaque) {
-        return false;
-    }
-
-    if (!drv->pcm_ops->get_buffer_in) {
-        drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in;
-        drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in;
-    }
-    if (!drv->pcm_ops->get_buffer_out) {
-        drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out;
-        drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;
-    }
-
-    audio_init_nb_voices_out(be, drv, 1);
-    audio_init_nb_voices_in(be, drv, 0);
-    be->drv = drv;
-
-    if (be->dev->timer_period <= 0) {
-        be->period_ticks = 1;
-    } else {
-        be->period_ticks = be->dev->timer_period * (int64_t)SCALE_US;
-    }
-
-    return true;
-}
-
-static void audio_vm_change_state_handler (void *opaque, bool running,
-                                           RunState state)
-{
-    AudioMixengBackend *s = opaque;
-    HWVoiceOut *hwo = NULL;
-    HWVoiceIn *hwi = NULL;
-
-    while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
-        if (hwo->pcm_ops->enable_out) {
-            hwo->pcm_ops->enable_out(hwo, running);
-        }
-    }
-
-    while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
-        if (hwi->pcm_ops->enable_in) {
-            hwi->pcm_ops->enable_in(hwi, running);
-        }
-    }
-    audio_reset_timer (s);
-}
-
-static const VMStateDescription vmstate_audio;
-
-static const char *audio_mixeng_backend_get_id(AudioBackend *be)
-{
-    return AUDIO_MIXENG_BACKEND(be)->dev->id;
-}
-
-static CaptureVoiceOut *audio_mixeng_backend_add_capture(
-    AudioBackend *be,
-    struct audsettings *as,
-    struct audio_capture_ops *ops,
-    void *cb_opaque);
-
-static void audio_mixeng_backend_del_capture(
-    AudioBackend *be,
-    CaptureVoiceOut *cap,
-    void *cb_opaque);
-
-static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
-                                                Volume *vol);
-static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
-                                               Volume *vol);
-
-static void audio_mixeng_backend_class_init(ObjectClass *klass, const void *data)
-{
-    AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
-
-    be->realize = audio_mixeng_backend_realize;
-    be->get_id = audio_mixeng_backend_get_id;
-    be->open_in = audio_mixeng_backend_open_in;
-    be->open_out = audio_mixeng_backend_open_out;
-    be->close_in = audio_mixeng_backend_close_in;
-    be->close_out = audio_mixeng_backend_close_out;
-    be->is_active_out = audio_mixeng_backend_is_active_out;
-    be->is_active_in = audio_mixeng_backend_is_active_in;
-    be->set_active_out = audio_mixeng_backend_set_active_out;
-    be->set_active_in = audio_mixeng_backend_set_active_in;
-    be->set_volume_out = audio_mixeng_backend_set_volume_out;
-    be->set_volume_in = audio_mixeng_backend_set_volume_in;
-    be->read = audio_mixeng_backend_read;
-    be->write = audio_mixeng_backend_write;
-    be->get_buffer_size_out = audio_mixeng_backend_get_buffer_size_out;
-    be->add_capture = audio_mixeng_backend_add_capture;
-    be->del_capture = audio_mixeng_backend_del_capture;
-}
-
-static void audio_mixeng_backend_init(Object *obj)
-{
-    AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
-
-    QLIST_INIT(&s->hw_head_out);
-    QLIST_INIT(&s->hw_head_in);
-    QLIST_INIT(&s->cap_head);
-    s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
-
-    s->vmse = qemu_add_vm_change_state_handler(audio_vm_change_state_handler, s);
-    assert(s->vmse != NULL);
-
-    vmstate_register_any(NULL, &vmstate_audio, s);
-}
-
-static void audio_mixeng_backend_finalize(Object *obj)
-{
-    AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
-    HWVoiceOut *hwo, *hwon;
-    HWVoiceIn *hwi, *hwin;
-
-    QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) {
-        SWVoiceCap *sc;
-
-        if (hwo->enabled && hwo->pcm_ops->enable_out) {
-            hwo->pcm_ops->enable_out(hwo, false);
-        }
-        hwo->pcm_ops->fini_out (hwo);
+/* SPDX-License-Identifier: MIT */
 
-        for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) {
-            CaptureVoiceOut *cap = sc->cap;
-            struct capture_callback *cb;
+#include "qemu/osdep.h"
+#include "qemu/audio.h"
+#include "qemu/help_option.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qapi/qapi-visit-audio.h"
+#include "qapi/qapi-commands-audio.h"
+#include "qobject/qdict.h"
+#include "system/system.h"
 
-            for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
-                cb->ops.destroy (cb->opaque);
-            }
-        }
-        QLIST_REMOVE(hwo, entries);
-    }
+/* Order of CONFIG_AUDIO_DRIVERS is import.
+   The 1st one is the one used by default, that is the reason
+    that we generate the list.
+*/
+const char *audio_prio_list[] = {
+#ifdef CONFIG_GIO
+    "dbus",
+#endif
+    "spice",
+    CONFIG_AUDIO_DRIVERS
+    "none",
+    NULL
+};
 
-    QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) {
-        if (hwi->enabled && hwi->pcm_ops->enable_in) {
-            hwi->pcm_ops->enable_in(hwi, false);
-        }
-        hwi->pcm_ops->fini_in (hwi);
-        QLIST_REMOVE(hwi, entries);
-    }
+typedef struct AudiodevListEntry {
+    Audiodev *dev;
+    QSIMPLEQ_ENTRY(AudiodevListEntry) next;
+} AudiodevListEntry;
 
-    if (s->drv) {
-        s->drv->fini (s->drv_opaque);
-        s->drv = NULL;
-    }
+typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;
 
-    if (s->dev) {
-        qapi_free_Audiodev(s->dev);
-        s->dev = NULL;
-    }
+static AudiodevListHead audiodevs =
+    QSIMPLEQ_HEAD_INITIALIZER(audiodevs);
+static AudiodevListHead default_audiodevs =
+    QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);
 
-    if (s->ts) {
-        timer_free(s->ts);
-        s->ts = NULL;
-    }
+static AudioBackendClass *audio_be_class_by_name(const char *name)
+{
+    g_autofree char *tname = g_strconcat("audio-", name, NULL);
+    ObjectClass *oc = module_object_class_by_name(tname);
 
-    if (s->vmse) {
-        qemu_del_vm_change_state_handler(s->vmse);
-        s->vmse = NULL;
+    if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) {
+        return NULL;
     }
 
-    vmstate_unregister(NULL, &vmstate_audio, s);
+    return AUDIO_BACKEND_CLASS(oc);
 }
 
+static AudioBackend *default_audio_be;
+
 static Object *get_audiodevs_root(void)
 {
     return object_get_container("audiodevs");
@@ -1757,25 +62,6 @@ void audio_cleanup(void)
     object_unparent(get_audiodevs_root());
 }
 
-static bool vmstate_audio_needed(void *opaque)
-{
-    /*
-     * Never needed, this vmstate only exists in case
-     * an old qemu sends it to us.
-     */
-    return false;
-}
-
-static const VMStateDescription vmstate_audio = {
-    .name = "audio",
-    .version_id = 1,
-    .minimum_version_id = 1,
-    .needed = vmstate_audio_needed,
-    .fields = (const VMStateField[]) {
-        VMSTATE_END_OF_LIST()
-    }
-};
-
 void audio_create_default_audiodevs(void)
 {
     for (int i = 0; audio_prio_list[i]; i++) {
@@ -1816,15 +102,13 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
         assert(!default_audio_be);
         for (;;) {
             AudiodevListEntry *e = QSIMPLEQ_FIRST(&default_audiodevs);
-
             if (!e) {
                 error_setg(errp, "no default audio driver available");
                 return NULL;
             }
-            dev = e->dev;
             QSIMPLEQ_REMOVE_HEAD(&default_audiodevs, next);
+            be = audio_be_new(e->dev, NULL);
             g_free(e);
-            be = audio_be_new(dev, NULL);
             if (be) {
                 break;
             }
@@ -1855,156 +139,35 @@ AudioBackend *audio_get_default_audio_be(Error **errp)
     return default_audio_be;
 }
 
-static struct audio_pcm_ops capture_pcm_ops;
-
-static CaptureVoiceOut *audio_mixeng_backend_add_capture(
-    AudioBackend *be,
-    struct audsettings *as,
-    struct audio_capture_ops *ops,
-    void *cb_opaque)
-{
-    AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(be);
-    CaptureVoiceOut *cap;
-    struct capture_callback *cb;
-
-    if (!s) {
-        error_report("Capturing without setting an audiodev is not supported");
-        abort();
-    }
-
-    if (!audio_get_pdo_out(s->dev)->mixing_engine) {
-        dolog("Can't capture with mixeng disabled\n");
-        return NULL;
-    }
-
-    if (audio_validate_settings (as)) {
-        dolog ("Invalid settings were passed when trying to add capture\n");
-        audio_print_settings (as);
-        return NULL;
-    }
-
-    cb = g_malloc0(sizeof(*cb));
-    cb->ops = *ops;
-    cb->opaque = cb_opaque;
-
-    cap = audio_pcm_capture_find_specific(s, as);
-    if (cap) {
-        QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
-    } else {
-        HWVoiceOut *hw;
-
-        cap = g_malloc0(sizeof(*cap));
-
-        hw = &cap->hw;
-        hw->s = s;
-        hw->pcm_ops = &capture_pcm_ops;
-        QLIST_INIT (&hw->sw_head);
-        QLIST_INIT (&cap->cb_head);
-
-        /* XXX find a more elegant way */
-        hw->samples = 4096 * 4;
-        audio_pcm_hw_alloc_resources_out(hw);
-
-        audio_pcm_init_info (&hw->info, as);
-
-        cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame);
-
-        if (hw->info.is_float) {
-            hw->clip = mixeng_clip_float[hw->info.nchannels == 2]
-                [hw->info.swap_endianness];
-        } else {
-            hw->clip = mixeng_clip
-                [hw->info.nchannels == 2]
-                [hw->info.is_signed]
-                [hw->info.swap_endianness]
-                [audio_bits_to_index(hw->info.bits)];
-        }
-
-        QLIST_INSERT_HEAD (&s->cap_head, cap, entries);
-        QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
-
-        QLIST_FOREACH(hw, &s->hw_head_out, entries) {
-            audio_attach_capture (hw);
-        }
-    }
-
-    return cap;
-}
-
-static void audio_mixeng_backend_del_capture(
-    AudioBackend *be,
-    CaptureVoiceOut *cap,
-    void *cb_opaque)
+void audio_help(void)
 {
-    struct capture_callback *cb;
-
-    for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
-        if (cb->opaque == cb_opaque) {
-            cb->ops.destroy (cb_opaque);
-            QLIST_REMOVE (cb, entries);
-            g_free (cb);
+    int i;
 
-            if (!cap->cb_head.lh_first) {
-                SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1;
+    printf("Available audio drivers:\n");
 
-                while (sw) {
-                    SWVoiceCap *sc = (SWVoiceCap *) sw;
-#ifdef DEBUG_CAPTURE
-                    dolog ("freeing %s\n", sw->name);
-#endif
+    for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) {
+        const char *name = AudiodevDriver_str(i);
+        AudioBackendClass *be = audio_be_class_by_name(name);
 
-                    sw1 = sw->entries.le_next;
-                    if (sw->rate) {
-                        st_rate_stop (sw->rate);
-                        sw->rate = NULL;
-                    }
-                    QLIST_REMOVE (sw, entries);
-                    QLIST_REMOVE (sc, entries);
-                    g_free (sc);
-                    sw = sw1;
-                }
-                QLIST_REMOVE (cap, entries);
-                g_free(cap->hw.mix_buf.buffer);
-                g_free (cap->buf);
-                g_free (cap);
-            }
-            return;
+        if (be) {
+            printf("%s\n", name);
         }
     }
 }
 
-static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
-                                                Volume *vol)
+void audio_parse_option(const char *opt)
 {
-    if (sw) {
-        HWVoiceOut *hw = sw->hw;
-
-        sw->vol.mute = vol->mute;
-        sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
-        sw->vol.r = nominal_volume.l * vol->vol[vol->channels > 1 ? 1 : 0] /
-            255;
+    Audiodev *dev = NULL;
 
-        if (hw->pcm_ops->volume_out) {
-            hw->pcm_ops->volume_out(hw, vol);
-        }
+    if (is_help_option(opt)) {
+        audio_help();
+        exit(EXIT_SUCCESS);
     }
-}
-
-static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
-                                               Volume *vol)
-{
-    if (sw) {
-        HWVoiceIn *hw = sw->hw;
-
-        sw->vol.mute = vol->mute;
-        sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
-        sw->vol.r = nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : 0] /
-            255;
+    Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
+    visit_type_Audiodev(v, NULL, &dev, &error_fatal);
+    visit_free(v);
 
-        if (hw->pcm_ops->volume_in) {
-            hw->pcm_ops->volume_in(hw, vol);
-        }
-    }
+    audio_add_audiodev(dev);
 }
 
 static void audio_create_pdos(Audiodev *dev)
@@ -2103,6 +266,124 @@ static void audio_validate_per_direction_opts(
     }
 }
 
+static AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev)
+{
+    switch (dev->driver) {
+    case AUDIODEV_DRIVER_NONE:
+        return dev->u.none.out;
+#ifdef CONFIG_AUDIO_ALSA
+    case AUDIODEV_DRIVER_ALSA:
+        return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.out);
+#endif
+#ifdef CONFIG_AUDIO_COREAUDIO
+    case AUDIODEV_DRIVER_COREAUDIO:
+        return qapi_AudiodevCoreaudioPerDirectionOptions_base(
+            dev->u.coreaudio.out);
+#endif
+#ifdef CONFIG_DBUS_DISPLAY
+    case AUDIODEV_DRIVER_DBUS:
+        return dev->u.dbus.out;
+#endif
+#ifdef CONFIG_AUDIO_DSOUND
+    case AUDIODEV_DRIVER_DSOUND:
+        return dev->u.dsound.out;
+#endif
+#ifdef CONFIG_AUDIO_JACK
+    case AUDIODEV_DRIVER_JACK:
+        return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.out);
+#endif
+#ifdef CONFIG_AUDIO_OSS
+    case AUDIODEV_DRIVER_OSS:
+        return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.out);
+#endif
+#ifdef CONFIG_AUDIO_PA
+    case AUDIODEV_DRIVER_PA:
+        return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.out);
+#endif
+#ifdef CONFIG_AUDIO_PIPEWIRE
+    case AUDIODEV_DRIVER_PIPEWIRE:
+        return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.out);
+#endif
+#ifdef CONFIG_AUDIO_SDL
+    case AUDIODEV_DRIVER_SDL:
+        return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.out);
+#endif
+#ifdef CONFIG_AUDIO_SNDIO
+    case AUDIODEV_DRIVER_SNDIO:
+        return dev->u.sndio.out;
+#endif
+#ifdef CONFIG_SPICE
+    case AUDIODEV_DRIVER_SPICE:
+        return dev->u.spice.out;
+#endif
+    case AUDIODEV_DRIVER_WAV:
+        return dev->u.wav.out;
+
+    case AUDIODEV_DRIVER__MAX:
+        break;
+    }
+    abort();
+}
+
+static AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev)
+{
+    switch (dev->driver) {
+    case AUDIODEV_DRIVER_NONE:
+        return dev->u.none.in;
+#ifdef CONFIG_AUDIO_ALSA
+    case AUDIODEV_DRIVER_ALSA:
+        return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.in);
+#endif
+#ifdef CONFIG_AUDIO_COREAUDIO
+    case AUDIODEV_DRIVER_COREAUDIO:
+        return qapi_AudiodevCoreaudioPerDirectionOptions_base(
+            dev->u.coreaudio.in);
+#endif
+#ifdef CONFIG_DBUS_DISPLAY
+    case AUDIODEV_DRIVER_DBUS:
+        return dev->u.dbus.in;
+#endif
+#ifdef CONFIG_AUDIO_DSOUND
+    case AUDIODEV_DRIVER_DSOUND:
+        return dev->u.dsound.in;
+#endif
+#ifdef CONFIG_AUDIO_JACK
+    case AUDIODEV_DRIVER_JACK:
+        return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.in);
+#endif
+#ifdef CONFIG_AUDIO_OSS
+    case AUDIODEV_DRIVER_OSS:
+        return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.in);
+#endif
+#ifdef CONFIG_AUDIO_PA
+    case AUDIODEV_DRIVER_PA:
+        return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.in);
+#endif
+#ifdef CONFIG_AUDIO_PIPEWIRE
+    case AUDIODEV_DRIVER_PIPEWIRE:
+        return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.in);
+#endif
+#ifdef CONFIG_AUDIO_SDL
+    case AUDIODEV_DRIVER_SDL:
+        return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.in);
+#endif
+#ifdef CONFIG_AUDIO_SNDIO
+    case AUDIODEV_DRIVER_SNDIO:
+        return dev->u.sndio.in;
+#endif
+#ifdef CONFIG_SPICE
+    case AUDIODEV_DRIVER_SPICE:
+        return dev->u.spice.in;
+#endif
+    case AUDIODEV_DRIVER_WAV:
+        return dev->u.wav.in;
+
+    case AUDIODEV_DRIVER__MAX:
+        break;
+    }
+    abort();
+}
+
 static void audio_validate_opts(Audiodev *dev, Error **errp)
 {
     Error *err = NULL;
@@ -2127,37 +408,6 @@ static void audio_validate_opts(Audiodev *dev, Error **errp)
     }
 }
 
-void audio_help(void)
-{
-    int i;
-
-    printf("Available audio drivers:\n");
-
-    for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) {
-        const char *name = AudiodevDriver_str(i);
-        AudioBackendClass *be = audio_be_class_by_name(name);
-
-        if (be) {
-            printf("%s\n", name);
-        }
-    }
-}
-
-void audio_parse_option(const char *opt)
-{
-    Audiodev *dev = NULL;
-
-    if (is_help_option(opt)) {
-        audio_help();
-        exit(EXIT_SUCCESS);
-    }
-    Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
-    visit_type_Audiodev(v, NULL, &dev, &error_fatal);
-    visit_free(v);
-
-    audio_add_audiodev(dev);
-}
-
 void audio_add_audiodev(Audiodev *dev)
 {
     AudiodevListEntry *e;
@@ -2189,65 +439,6 @@ void audio_init_audiodevs(void)
     }
 }
 
-audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo)
-{
-    return (audsettings) {
-        .freq = pdo->frequency,
-        .nchannels = pdo->channels,
-        .fmt = pdo->format,
-        .endianness = HOST_BIG_ENDIAN,
-    };
-}
-
-int audioformat_bytes_per_sample(AudioFormat fmt)
-{
-    switch (fmt) {
-    case AUDIO_FORMAT_U8:
-    case AUDIO_FORMAT_S8:
-        return 1;
-
-    case AUDIO_FORMAT_U16:
-    case AUDIO_FORMAT_S16:
-        return 2;
-
-    case AUDIO_FORMAT_U32:
-    case AUDIO_FORMAT_S32:
-    case AUDIO_FORMAT_F32:
-        return 4;
-
-    case AUDIO_FORMAT__MAX:
-        ;
-    }
-    abort();
-}
-
-
-/* frames = freq * usec / 1e6 */
-int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,
-                        audsettings *as, int def_usecs)
-{
-    uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs;
-    return (as->freq * usecs + 500000) / 1000000;
-}
-
-/* samples = channels * frames = channels * freq * usec / 1e6 */
-int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,
-                         audsettings *as, int def_usecs)
-{
-    return as->nchannels * audio_buffer_frames(pdo, as, def_usecs);
-}
-
-/*
- * bytes = bytes_per_sample * samples =
- *     bytes_per_sample * channels * freq * usec / 1e6
- */
-int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,
-                       audsettings *as, int def_usecs)
-{
-    return audio_buffer_samples(pdo, as, def_usecs) *
-        audioformat_bytes_per_sample(as->fmt);
-}
-
 AudioBackend *audio_be_by_name(const char *name, Error **errp)
 {
     Object *obj = object_resolve_path_component(get_audiodevs_root(), name);
@@ -2268,51 +459,6 @@ const char *audio_application_name(void)
     return vm_name ? vm_name : "qemu";
 }
 
-void audio_rate_start(RateCtl *rate)
-{
-    memset(rate, 0, sizeof(RateCtl));
-    rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
-}
-
-size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info)
-{
-    int64_t now;
-    int64_t ticks;
-    int64_t bytes;
-    int64_t frames;
-
-    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
-    ticks = now - rate->start_ticks;
-    bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);
-    frames = (bytes - rate->bytes_sent) / info->bytes_per_frame;
-    rate->peeked_frames = frames;
-
-    return frames < 0 ? 0 : frames * info->bytes_per_frame;
-}
-
-void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used)
-{
-    if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) {
-        AUD_log(NULL, "Resetting rate control (%" PRId64 " frames)\n",
-                rate->peeked_frames);
-        audio_rate_start(rate);
-    }
-
-    rate->bytes_sent += bytes_used;
-}
-
-size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info,
-                            size_t bytes_avail)
-{
-    size_t bytes;
-
-    bytes = audio_rate_peek_bytes(rate, info);
-    bytes = MIN(bytes, bytes_avail);
-    audio_rate_add_bytes(rate, bytes);
-
-    return bytes;
-}
-
 AudiodevList *qmp_query_audiodevs(Error **errp)
 {
     AudiodevList *ret = NULL;
@@ -2322,21 +468,3 @@ AudiodevList *qmp_query_audiodevs(Error **errp)
     }
     return ret;
 }
-
-static const TypeInfo audio_mixeng_backend_info = {
-    .name = TYPE_AUDIO_MIXENG_BACKEND,
-    .parent = TYPE_AUDIO_BACKEND,
-    .instance_size = sizeof(AudioMixengBackend),
-    .instance_init = audio_mixeng_backend_init,
-    .instance_finalize = audio_mixeng_backend_finalize,
-    .abstract = false,
-    .class_size = sizeof(AudioMixengBackendClass),
-    .class_init = audio_mixeng_backend_class_init,
-};
-
-static void register_types(void)
-{
-    type_register_static(&audio_mixeng_backend_info);
-}
-
-type_init(register_types);
diff --git a/audio/meson.build b/audio/meson.build
index 417670bd4c7..d2ff49280ba 100644
--- a/audio/meson.build
+++ b/audio/meson.build
@@ -2,6 +2,7 @@ audio_ss = ss.source_set()
 audio_ss.add(files(
   'audio.c',
   'audio-be.c',
+  'audio-mixeng-be.c',
   'mixeng.c',
   'noaudio.c',
   'wavaudio.c',
-- 
2.52.0


Re: [PATCH 36/37] audio: split AudioMixengBackend code in audio-mixeng-be.c
Posted by Mark Cave-Ayland 5 days, 21 hours ago
On 23/01/2026 07:49, marcandre.lureau@redhat.com wrote:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Allow to build the audio/ base classes without the
> resampling/mixing/queuing code.
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>   audio/audio_int.h       |    2 -
>   include/qemu/audio.h    |    2 +
>   audio/audio-mixeng-be.c | 1979 ++++++++++++++++++++++++++++++++++
>   audio/audio.c           | 2226 ++++-----------------------------------
>   audio/meson.build       |    1 +
>   5 files changed, 2159 insertions(+), 2051 deletions(-)
>   create mode 100644 audio/audio-mixeng-be.c
> 
> diff --git a/audio/audio_int.h b/audio/audio_int.h
> index 250fd45238d..6ecd75c4fbf 100644
> --- a/audio/audio_int.h
> +++ b/audio/audio_int.h
> @@ -266,8 +266,6 @@ int audio_bug (const char *funcname, int cond);
>   
>   void audio_run(AudioMixengBackend *s, const char *msg);
>   
> -const char *audio_application_name(void);
> -
>   typedef struct RateCtl {
>       int64_t start_ticks;
>       int64_t bytes_sent;
> diff --git a/include/qemu/audio.h b/include/qemu/audio.h
> index 42f97f732a6..dfe247ab8c4 100644
> --- a/include/qemu/audio.h
> +++ b/include/qemu/audio.h
> @@ -183,6 +183,8 @@ bool audio_be_set_dbus_server(AudioBackend *be,
>                                 Error **errp);
>   #endif
>   
> +const char *audio_application_name(void);
> +
>   #define DEFINE_AUDIO_PROPERTIES(_s, _f)         \
>       DEFINE_PROP_AUDIODEV("audiodev", _s, _f)
>   
> diff --git a/audio/audio-mixeng-be.c b/audio/audio-mixeng-be.c
> new file mode 100644
> index 00000000000..8ebceb968ce
> --- /dev/null
> +++ b/audio/audio-mixeng-be.c
> @@ -0,0 +1,1979 @@
> +/*
> + * SPDX-License-Identifier: MIT
> + *
> + * Copyright (c) 2003-2005 Vassili Karpov (malc)
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/audio.h"
> +#include "migration/vmstate.h"
> +#include "qemu/timer.h"
> +#include "qapi/error.h"
> +#include "qapi/clone-visitor.h"
> +#include "qapi/qobject-input-visitor.h"
> +#include "qapi/qapi-visit-audio.h"
> +#include "qapi/qapi-commands-audio.h"
> +#include "qobject/qdict.h"
> +#include "qemu/error-report.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +#include "qemu/help_option.h"
> +#include "qom/object.h"
> +#include "system/system.h"
> +#include "system/replay.h"
> +#include "system/runstate.h"
> +#include "trace.h"
> +
> +#define AUDIO_CAP "audio"
> +#include "audio_int.h"
> +
> +/* #define DEBUG_OUT */
> +/* #define DEBUG_CAPTURE */
> +/* #define DEBUG_POLL */
> +
> +#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown"
> +
> +const struct mixeng_volume nominal_volume = {
> +    .mute = 0,
> +#ifdef FLOAT_MIXENG
> +    .r = 1.0,
> +    .l = 1.0,
> +#else
> +    .r = 1ULL << 32,
> +    .l = 1ULL << 32,
> +#endif
> +};
> +
> +int audio_bug (const char *funcname, int cond)
> +{
> +    if (cond) {
> +        static int shown;
> +
> +        AUD_log (NULL, "A bug was just triggered in %s\n", funcname);
> +        if (!shown) {
> +            shown = 1;
> +            AUD_log (NULL, "Save all your work and restart without audio\n");
> +            AUD_log (NULL, "I am sorry\n");
> +        }
> +        AUD_log (NULL, "Context:\n");
> +    }
> +
> +    return cond;
> +}
> +
> +static inline int audio_bits_to_index (int bits)
> +{
> +    switch (bits) {
> +    case 8:
> +        return 0;
> +
> +    case 16:
> +        return 1;
> +
> +    case 32:
> +        return 2;
> +
> +    default:
> +        audio_bug ("bits_to_index", 1);
> +        AUD_log (NULL, "invalid bits %d\n", bits);
> +        return 0;
> +    }
> +}
> +
> +void AUD_vlog (const char *cap, const char *fmt, va_list ap)
> +{
> +    if (cap) {
> +        fprintf(stderr, "%s: ", cap);
> +    }
> +
> +    vfprintf(stderr, fmt, ap);
> +}
> +
> +void AUD_log (const char *cap, const char *fmt, ...)
> +{
> +    va_list ap;
> +
> +    va_start (ap, fmt);
> +    AUD_vlog (cap, fmt, ap);
> +    va_end (ap);
> +}
> +
> +static void audio_print_settings (const struct audsettings *as)
> +{
> +    dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels);
> +
> +    switch (as->fmt) {
> +    case AUDIO_FORMAT_S8:
> +        AUD_log (NULL, "S8");
> +        break;
> +    case AUDIO_FORMAT_U8:
> +        AUD_log (NULL, "U8");
> +        break;
> +    case AUDIO_FORMAT_S16:
> +        AUD_log (NULL, "S16");
> +        break;
> +    case AUDIO_FORMAT_U16:
> +        AUD_log (NULL, "U16");
> +        break;
> +    case AUDIO_FORMAT_S32:
> +        AUD_log (NULL, "S32");
> +        break;
> +    case AUDIO_FORMAT_U32:
> +        AUD_log (NULL, "U32");
> +        break;
> +    case AUDIO_FORMAT_F32:
> +        AUD_log (NULL, "F32");
> +        break;
> +    default:
> +        AUD_log (NULL, "invalid(%d)", as->fmt);
> +        break;
> +    }
> +
> +    AUD_log (NULL, " endianness=");
> +    switch (as->endianness) {
> +    case 0:
> +        AUD_log (NULL, "little");
> +        break;
> +    case 1:
> +        AUD_log (NULL, "big");
> +        break;
> +    default:
> +        AUD_log (NULL, "invalid");
> +        break;
> +    }
> +    AUD_log (NULL, "\n");
> +}
> +
> +static int audio_validate_settings (const struct audsettings *as)
> +{
> +    int invalid;
> +
> +    invalid = as->nchannels < 1;
> +    invalid |= as->endianness != 0 && as->endianness != 1;
> +
> +    switch (as->fmt) {
> +    case AUDIO_FORMAT_S8:
> +    case AUDIO_FORMAT_U8:
> +    case AUDIO_FORMAT_S16:
> +    case AUDIO_FORMAT_U16:
> +    case AUDIO_FORMAT_S32:
> +    case AUDIO_FORMAT_U32:
> +    case AUDIO_FORMAT_F32:
> +        break;
> +    default:
> +        invalid = 1;
> +        break;
> +    }
> +
> +    invalid |= as->freq <= 0;
> +    return invalid ? -1 : 0;
> +}
> +
> +static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct audsettings *as)
> +{
> +    int bits = 8;
> +    bool is_signed = false, is_float = false;
> +
> +    switch (as->fmt) {
> +    case AUDIO_FORMAT_S8:
> +        is_signed = true;
> +        /* fall through */
> +    case AUDIO_FORMAT_U8:
> +        break;
> +
> +    case AUDIO_FORMAT_S16:
> +        is_signed = true;
> +        /* fall through */
> +    case AUDIO_FORMAT_U16:
> +        bits = 16;
> +        break;
> +
> +    case AUDIO_FORMAT_F32:
> +        is_float = true;
> +        /* fall through */
> +    case AUDIO_FORMAT_S32:
> +        is_signed = true;
> +        /* fall through */
> +    case AUDIO_FORMAT_U32:
> +        bits = 32;
> +        break;
> +
> +    default:
> +        abort();
> +    }
> +    return info->freq == as->freq
> +        && info->nchannels == as->nchannels
> +        && info->is_signed == is_signed
> +        && info->is_float == is_float
> +        && info->bits == bits
> +        && info->swap_endianness == (as->endianness != HOST_BIG_ENDIAN);
> +}
> +
> +void audio_pcm_init_info (struct audio_pcm_info *info, const struct audsettings *as)
> +{
> +    int bits = 8, mul;
> +    bool is_signed = false, is_float = false;
> +
> +    switch (as->fmt) {
> +    case AUDIO_FORMAT_S8:
> +        is_signed = true;
> +        /* fall through */
> +    case AUDIO_FORMAT_U8:
> +        mul = 1;
> +        break;
> +
> +    case AUDIO_FORMAT_S16:
> +        is_signed = true;
> +        /* fall through */
> +    case AUDIO_FORMAT_U16:
> +        bits = 16;
> +        mul = 2;
> +        break;
> +
> +    case AUDIO_FORMAT_F32:
> +        is_float = true;
> +        /* fall through */
> +    case AUDIO_FORMAT_S32:
> +        is_signed = true;
> +        /* fall through */
> +    case AUDIO_FORMAT_U32:
> +        bits = 32;
> +        mul = 4;
> +        break;
> +
> +    default:
> +        abort();
> +    }
> +
> +    info->freq = as->freq;
> +    info->bits = bits;
> +    info->is_signed = is_signed;
> +    info->is_float = is_float;
> +    info->nchannels = as->nchannels;
> +    info->bytes_per_frame = as->nchannels * mul;
> +    info->bytes_per_second = info->freq * info->bytes_per_frame;
> +    info->swap_endianness = (as->endianness != HOST_BIG_ENDIAN);
> +}
> +
> +void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
> +{
> +    if (!len) {
> +        return;
> +    }
> +
> +    if (info->is_signed || info->is_float) {
> +        memset(buf, 0x00, len * info->bytes_per_frame);
> +    } else {
> +        switch (info->bits) {
> +        case 8:
> +            memset(buf, 0x80, len * info->bytes_per_frame);
> +            break;
> +
> +        case 16:
> +            {
> +                int i;
> +                uint16_t *p = buf;
> +                short s = INT16_MAX;
> +
> +                if (info->swap_endianness) {
> +                    s = bswap16 (s);
> +                }
> +
> +                for (i = 0; i < len * info->nchannels; i++) {
> +                    p[i] = s;
> +                }
> +            }
> +            break;
> +
> +        case 32:
> +            {
> +                int i;
> +                uint32_t *p = buf;
> +                int32_t s = INT32_MAX;
> +
> +                if (info->swap_endianness) {
> +                    s = bswap32 (s);
> +                }
> +
> +                for (i = 0; i < len * info->nchannels; i++) {
> +                    p[i] = s;
> +                }
> +            }
> +            break;
> +
> +        default:
> +            AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n",
> +                     info->bits);
> +            break;
> +        }
> +    }
> +}
> +
> +/*
> + * Capture
> + */
> +static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioMixengBackend *s,
> +                                                        struct audsettings *as)
> +{
> +    CaptureVoiceOut *cap;
> +
> +    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
> +        if (audio_pcm_info_eq (&cap->hw.info, as)) {
> +            return cap;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd)
> +{
> +    struct capture_callback *cb;
> +
> +#ifdef DEBUG_CAPTURE
> +    dolog ("notification %d sent\n", cmd);
> +#endif
> +    for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> +        cb->ops.notify (cb->opaque, cmd);
> +    }
> +}
> +
> +static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled)
> +{
> +    if (cap->hw.enabled != enabled) {
> +        audcnotification_e cmd;
> +        cap->hw.enabled = enabled;
> +        cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE;
> +        audio_notify_capture (cap, cmd);
> +    }
> +}
> +
> +static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap)
> +{
> +    HWVoiceOut *hw = &cap->hw;
> +    SWVoiceOut *sw;
> +    bool enabled = false;
> +
> +    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> +        if (sw->active) {
> +            enabled = true;
> +            break;
> +        }
> +    }
> +    audio_capture_maybe_changed (cap, enabled);
> +}
> +
> +static void audio_detach_capture (HWVoiceOut *hw)
> +{
> +    SWVoiceCap *sc = hw->cap_head.lh_first;
> +
> +    while (sc) {
> +        SWVoiceCap *sc1 = sc->entries.le_next;
> +        SWVoiceOut *sw = &sc->sw;
> +        CaptureVoiceOut *cap = sc->cap;
> +        int was_active = sw->active;
> +
> +        if (sw->rate) {
> +            st_rate_stop (sw->rate);
> +            sw->rate = NULL;
> +        }
> +
> +        QLIST_REMOVE (sw, entries);
> +        QLIST_REMOVE (sc, entries);
> +        g_free (sc);
> +        if (was_active) {
> +            /* We have removed soft voice from the capture:
> +               this might have changed the overall status of the capture
> +               since this might have been the only active voice */
> +            audio_recalc_and_notify_capture (cap);
> +        }
> +        sc = sc1;
> +    }
> +}
> +
> +static int audio_attach_capture (HWVoiceOut *hw)
> +{
> +    AudioMixengBackend *s = hw->s;
> +    CaptureVoiceOut *cap;
> +
> +    audio_detach_capture (hw);
> +    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
> +        SWVoiceCap *sc;
> +        SWVoiceOut *sw;
> +        HWVoiceOut *hw_cap = &cap->hw;
> +
> +        sc = g_malloc0(sizeof(*sc));
> +
> +        sc->cap = cap;
> +        sw = &sc->sw;
> +        sw->hw = hw_cap;
> +        sw->info = hw->info;
> +        sw->empty = true;
> +        sw->active = hw->enabled;
> +        sw->vol = nominal_volume;
> +        sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
> +        QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
> +        QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);
> +#ifdef DEBUG_CAPTURE
> +        sw->name = g_strdup_printf ("for %p %d,%d,%d",
> +                                    hw, sw->info.freq, sw->info.bits,
> +                                    sw->info.nchannels);
> +        dolog ("Added %s active = %d\n", sw->name, sw->active);
> +#endif
> +        if (sw->active) {
> +            audio_capture_maybe_changed (cap, 1);
> +        }
> +    }
> +    return 0;
> +}
> +
> +/*
> + * Hard voice (capture)
> + */
> +static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)
> +{
> +    SWVoiceIn *sw;
> +    size_t m = hw->total_samples_captured;
> +
> +    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> +        if (sw->active) {
> +            m = MIN (m, sw->total_hw_samples_acquired);
> +        }
> +    }
> +    return m;
> +}
> +
> +static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
> +{
> +    size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
> +    if (audio_bug(__func__, live > hw->conv_buf.size)) {
> +        dolog("live=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
> +        return 0;
> +    }
> +    return live;
> +}
> +
> +static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
> +{
> +    size_t conv = 0;
> +    STSampleBuffer *conv_buf = &hw->conv_buf;
> +
> +    while (samples) {
> +        uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);
> +        size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);
> +
> +        hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);
> +        conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
> +        samples -= proc;
> +        conv += proc;
> +    }
> +
> +    return conv;
> +}
> +
> +/*
> + * Soft voice (capture)
> + */
> +static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
> +    size_t frames_in_max, size_t frames_out_max,
> +    size_t *total_in, size_t *total_out)
> +{
> +    HWVoiceIn *hw = sw->hw;
> +    struct st_sample *src, *dst;
> +    size_t live, rpos, frames_in, frames_out;
> +
> +    live = hw->total_samples_captured - sw->total_hw_samples_acquired;
> +    rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
> +
> +    /* resample conv_buf from rpos to end of buffer */
> +    src = hw->conv_buf.buffer + rpos;
> +    frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos);
> +    dst = sw->resample_buf.buffer;
> +    frames_out = frames_out_max;
> +    st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
> +    rpos += frames_in;
> +    *total_in = frames_in;
> +    *total_out = frames_out;
> +
> +    /* resample conv_buf from start of buffer if there are input frames left */
> +    if (frames_in_max - frames_in && rpos == hw->conv_buf.size) {
> +        src = hw->conv_buf.buffer;
> +        frames_in = frames_in_max - frames_in;
> +        dst += frames_out;
> +        frames_out = frames_out_max - frames_out;
> +        st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
> +        *total_in += frames_in;
> +        *total_out += frames_out;
> +    }
> +}
> +
> +static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
> +{
> +    HWVoiceIn *hw = sw->hw;
> +    size_t live, frames_out_max, total_in, total_out;
> +
> +    live = hw->total_samples_captured - sw->total_hw_samples_acquired;
> +    if (!live) {
> +        return 0;
> +    }
> +    if (audio_bug(__func__, live > hw->conv_buf.size)) {
> +        dolog("live_in=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
> +        return 0;
> +    }
> +
> +    frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,
> +                         sw->resample_buf.size);
> +
> +    audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);
> +
> +    if (!hw->pcm_ops->volume_in) {
> +        mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);
> +    }
> +    sw->clip(buf, sw->resample_buf.buffer, total_out);
> +
> +    sw->total_hw_samples_acquired += total_in;
> +    return total_out * sw->info.bytes_per_frame;
> +}
> +
> +/*
> + * Hard voice (playback)
> + */
> +static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)
> +{
> +    SWVoiceOut *sw;
> +    size_t m = SIZE_MAX;
> +    int nb_live = 0;
> +
> +    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> +        if (sw->active || !sw->empty) {
> +            m = MIN (m, sw->total_hw_samples_mixed);
> +            nb_live += 1;
> +        }
> +    }
> +
> +    *nb_livep = nb_live;
> +    return m;
> +}
> +
> +static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)
> +{
> +    size_t smin;
> +    int nb_live1;
> +
> +    smin = audio_pcm_hw_find_min_out (hw, &nb_live1);
> +    if (nb_live) {
> +        *nb_live = nb_live1;
> +    }
> +
> +    if (nb_live1) {
> +        size_t live = smin;
> +
> +        if (audio_bug(__func__, live > hw->mix_buf.size)) {
> +            dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
> +            return 0;
> +        }
> +        return live;
> +    }
> +    return 0;
> +}
> +
> +static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)
> +{
> +    return (hw->pcm_ops->buffer_get_free ? hw->pcm_ops->buffer_get_free(hw) :
> +            INT_MAX) / hw->info.bytes_per_frame;
> +}
> +
> +static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
> +{
> +    size_t clipped = 0;
> +    size_t pos = hw->mix_buf.pos;
> +
> +    while (len) {
> +        st_sample *src = hw->mix_buf.buffer + pos;
> +        uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame);
> +        size_t samples_till_end_of_buf = hw->mix_buf.size - pos;
> +        size_t samples_to_clip = MIN(len, samples_till_end_of_buf);
> +
> +        hw->clip(dst, src, samples_to_clip);
> +
> +        pos = (pos + samples_to_clip) % hw->mix_buf.size;
> +        len -= samples_to_clip;
> +        clipped += samples_to_clip;
> +    }
> +}
> +
> +/*
> + * Soft voice (playback)
> + */
> +static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
> +    size_t frames_in_max, size_t frames_out_max,
> +    size_t *total_in, size_t *total_out)
> +{
> +    HWVoiceOut *hw = sw->hw;
> +    struct st_sample *src, *dst;
> +    size_t live, wpos, frames_in, frames_out;
> +
> +    live = sw->total_hw_samples_mixed;
> +    wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;
> +
> +    /* write to mix_buf from wpos to end of buffer */
> +    src = sw->resample_buf.buffer;
> +    frames_in = frames_in_max;
> +    dst = hw->mix_buf.buffer + wpos;
> +    frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);
> +    st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
> +    wpos += frames_out;
> +    *total_in = frames_in;
> +    *total_out = frames_out;
> +
> +    /* write to mix_buf from start of buffer if there are input frames left */
> +    if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {
> +        src += frames_in;
> +        frames_in = frames_in_max - frames_in;
> +        dst = hw->mix_buf.buffer;
> +        frames_out = frames_out_max - frames_out;
> +        st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
> +        *total_in += frames_in;
> +        *total_out += frames_out;
> +    }
> +}
> +
> +static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
> +{
> +    HWVoiceOut *hw = sw->hw;
> +    size_t live, dead, hw_free, sw_max, fe_max;
> +    size_t frames_in_max, frames_out_max, total_in, total_out;
> +
> +    live = sw->total_hw_samples_mixed;
> +    if (audio_bug(__func__, live > hw->mix_buf.size)) {
> +        dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
> +        return 0;
> +    }
> +
> +    if (live == hw->mix_buf.size) {
> +#ifdef DEBUG_OUT
> +        dolog ("%s is full %zu\n", sw->name, live);
> +#endif
> +        return 0;
> +    }
> +
> +    dead = hw->mix_buf.size - live;
> +    hw_free = audio_pcm_hw_get_free(hw);
> +    hw_free = hw_free > live ? hw_free - live : 0;
> +    frames_out_max = MIN(dead, hw_free);
> +    sw_max = st_rate_frames_in(sw->rate, frames_out_max);
> +    fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,
> +                 sw->resample_buf.size);
> +    frames_in_max = MIN(sw_max, fe_max);
> +
> +    if (!frames_in_max) {
> +        return 0;
> +    }
> +
> +    if (frames_in_max > sw->resample_buf.pos) {
> +        sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,
> +                 buf, frames_in_max - sw->resample_buf.pos);
> +        if (!sw->hw->pcm_ops->volume_out) {
> +            mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,
> +                          frames_in_max - sw->resample_buf.pos, &sw->vol);
> +        }
> +    }
> +
> +    audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,
> +                              &total_in, &total_out);
> +
> +    sw->total_hw_samples_mixed += total_out;
> +    sw->empty = sw->total_hw_samples_mixed == 0;
> +
> +    /*
> +     * Upsampling may leave one audio frame in the resample buffer. Decrement
> +     * total_in by one if there was a leftover frame from the previous resample
> +     * pass in the resample buffer. Increment total_in by one if the current
> +     * resample pass left one frame in the resample buffer.
> +     */
> +    if (frames_in_max - total_in == 1) {
> +        /* copy one leftover audio frame to the beginning of the buffer */
> +        *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);
> +        total_in += 1 - sw->resample_buf.pos;
> +        sw->resample_buf.pos = 1;
> +    } else if (total_in >= sw->resample_buf.pos) {
> +        total_in -= sw->resample_buf.pos;
> +        sw->resample_buf.pos = 0;
> +    }
> +
> +#ifdef DEBUG_OUT
> +    dolog (
> +        "%s: write size %zu written %zu total mixed %zu\n",
> +        SW_NAME(sw),
> +        buf_len / sw->info.bytes_per_frame,
> +        total_in,
> +        sw->total_hw_samples_mixed
> +        );
> +#endif
> +
> +    return total_in * sw->info.bytes_per_frame;
> +}
> +
> +#ifdef DEBUG_AUDIO
> +static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
> +{
> +    dolog("%s: bits %d, sign %d, float %d, freq %d, nchan %d\n",
> +          cap, info->bits, info->is_signed, info->is_float, info->freq,
> +          info->nchannels);
> +}
> +#endif
> +
> +#define DAC
> +#include "audio_template.h"
> +#undef DAC
> +#include "audio_template.h"
> +
> +/*
> + * Timer
> + */
> +static int audio_is_timer_needed(AudioMixengBackend *s)
> +{
> +    HWVoiceIn *hwi = NULL;
> +    HWVoiceOut *hwo = NULL;
> +
> +    while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
> +        if (!hwo->poll_mode) {
> +            return 1;
> +        }
> +    }
> +    while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
> +        if (!hwi->poll_mode) {
> +            return 1;
> +        }
> +    }
> +    return 0;
> +}
> +
> +static void audio_reset_timer(AudioMixengBackend *s)
> +{
> +    if (audio_is_timer_needed(s)) {
> +        timer_mod_anticipate_ns(s->ts,
> +            qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
> +        if (!s->timer_running) {
> +            s->timer_running = true;
> +            s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> +            trace_audio_timer_start(s->period_ticks / SCALE_MS);
> +        }
> +    } else {
> +        timer_del(s->ts);
> +        if (s->timer_running) {
> +            s->timer_running = false;
> +            trace_audio_timer_stop();
> +        }
> +    }
> +}
> +
> +static void audio_timer (void *opaque)
> +{
> +    int64_t now, diff;
> +    AudioMixengBackend *s = opaque;
> +
> +    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> +    diff = now - s->timer_last;
> +    if (diff > s->period_ticks * 3 / 2) {
> +        trace_audio_timer_delayed(diff / SCALE_MS);
> +    }
> +    s->timer_last = now;
> +
> +    audio_run(s, "timer");
> +    audio_reset_timer(s);
> +}
> +
> +/*
> + * Public API
> + */
> +static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw,
> +                                         void *buf, size_t size)
> +{
> +    HWVoiceOut *hw;
> +
> +    if (!sw) {
> +        /* XXX: Consider options */
> +        return size;
> +    }
> +    hw = sw->hw;
> +
> +    if (!hw->enabled) {
> +        dolog("Writing to disabled voice %s\n", SW_NAME(sw));
> +        return 0;
> +    }
> +
> +    if (audio_get_pdo_out(hw->s->dev)->mixing_engine) {
> +        return audio_pcm_sw_write(sw, buf, size);
> +    } else {
> +        return hw->pcm_ops->write(hw, buf, size);
> +    }
> +}
> +
> +static size_t audio_mixeng_backend_read(AudioBackend *be,
> +                                        SWVoiceIn *sw, void *buf, size_t size)
> +{
> +    HWVoiceIn *hw;
> +
> +    if (!sw) {
> +        /* XXX: Consider options */
> +        return size;
> +    }
> +    hw = sw->hw;
> +
> +    if (!hw->enabled) {
> +        dolog("Reading from disabled voice %s\n", SW_NAME(sw));
> +        return 0;
> +    }
> +
> +    if (audio_get_pdo_in(hw->s->dev)->mixing_engine) {
> +        return audio_pcm_sw_read(sw, buf, size);
> +    } else {
> +        return hw->pcm_ops->read(hw, buf, size);
> +    }
> +
> +}
> +
> +static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
> +{
> +    if (!sw) {
> +        return 0;
> +    }
> +
> +    if (audio_get_pdo_out(sw->s->dev)->mixing_engine) {
> +        return sw->resample_buf.size * sw->info.bytes_per_frame;
> +    }
> +
> +    return sw->hw->samples * sw->hw->info.bytes_per_frame;
> +}
> +
> +static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceOut *sw,
> +                                                bool on)
> +{
> +    HWVoiceOut *hw;
> +
> +    if (!sw) {
> +        return;
> +    }
> +
> +    hw = sw->hw;
> +    if (sw->active != on) {
> +        AudioMixengBackend *s = sw->s;
> +        SWVoiceOut *temp_sw;
> +        SWVoiceCap *sc;
> +
> +        if (on) {
> +            hw->pending_disable = 0;
> +            if (!hw->enabled) {
> +                hw->enabled = true;
> +                if (runstate_is_running()) {
> +                    if (hw->pcm_ops->enable_out) {
> +                        hw->pcm_ops->enable_out(hw, true);
> +                    }
> +                    audio_reset_timer (s);
> +                }
> +            }
> +        } else {
> +            if (hw->enabled) {
> +                int nb_active = 0;
> +
> +                for (temp_sw = hw->sw_head.lh_first; temp_sw;
> +                     temp_sw = temp_sw->entries.le_next) {
> +                    nb_active += temp_sw->active != 0;
> +                }
> +
> +                hw->pending_disable = nb_active == 1;
> +            }
> +        }
> +
> +        for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> +            sc->sw.active = hw->enabled;
> +            if (hw->enabled) {
> +                audio_capture_maybe_changed (sc->cap, 1);
> +            }
> +        }
> +        sw->active = on;
> +    }
> +
> +}
> +
> +static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
> +{
> +    HWVoiceIn *hw;
> +
> +    if (!sw) {
> +        return;
> +    }
> +
> +    hw = sw->hw;
> +    if (sw->active != on) {
> +        AudioMixengBackend *s = sw->s;
> +        SWVoiceIn *temp_sw;
> +
> +        if (on) {
> +            if (!hw->enabled) {
> +                hw->enabled = true;
> +                if (runstate_is_running()) {
> +                    if (hw->pcm_ops->enable_in) {
> +                        hw->pcm_ops->enable_in(hw, true);
> +                    }
> +                    audio_reset_timer (s);
> +                }
> +            }
> +            sw->total_hw_samples_acquired = hw->total_samples_captured;
> +        } else {
> +            if (hw->enabled) {
> +                int nb_active = 0;
> +
> +                for (temp_sw = hw->sw_head.lh_first; temp_sw;
> +                     temp_sw = temp_sw->entries.le_next) {
> +                    nb_active += temp_sw->active != 0;
> +                }
> +
> +                if (nb_active == 1) {
> +                    hw->enabled = false;
> +                    if (hw->pcm_ops->enable_in) {
> +                        hw->pcm_ops->enable_in(hw, false);
> +                    }
> +                }
> +            }
> +        }
> +        sw->active = on;
> +    }
> +}
> +
> +static size_t audio_get_avail(SWVoiceIn *sw)
> +{
> +    size_t live;
> +
> +    if (!sw) {
> +        return 0;
> +    }
> +
> +    live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
> +    if (audio_bug(__func__, live > sw->hw->conv_buf.size)) {
> +        dolog("live=%zu sw->hw->conv_buf.size=%zu\n", live,
> +              sw->hw->conv_buf.size);
> +        return 0;
> +    }
> +
> +    ldebug (
> +        "%s: get_avail live %zu frontend frames %u\n",
> +        SW_NAME (sw),
> +        live, st_rate_frames_out(sw->rate, live)
> +        );
> +
> +    return live;
> +}
> +
> +static size_t audio_get_free(SWVoiceOut *sw)
> +{
> +    size_t live, dead;
> +
> +    if (!sw) {
> +        return 0;
> +    }
> +
> +    live = sw->total_hw_samples_mixed;
> +
> +    if (audio_bug(__func__, live > sw->hw->mix_buf.size)) {
> +        dolog("live=%zu sw->hw->mix_buf.size=%zu\n", live,
> +              sw->hw->mix_buf.size);
> +        return 0;
> +    }
> +
> +    dead = sw->hw->mix_buf.size - live;
> +
> +#ifdef DEBUG_OUT
> +    dolog("%s: get_free live %zu dead %zu frontend frames %u\n",
> +          SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead));
> +#endif
> +
> +    return dead;
> +}
> +
> +static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
> +                                        size_t samples)
> +{
> +    size_t n;
> +
> +    if (hw->enabled) {
> +        SWVoiceCap *sc;
> +
> +        for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> +            SWVoiceOut *sw = &sc->sw;
> +            size_t rpos2 = rpos;
> +
> +            n = samples;
> +            while (n) {
> +                size_t till_end_of_hw = hw->mix_buf.size - rpos2;
> +                size_t to_read = MIN(till_end_of_hw, n);
> +                size_t live, frames_in, frames_out;
> +
> +                sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;
> +                sw->resample_buf.size = to_read;
> +                live = sw->total_hw_samples_mixed;
> +
> +                audio_pcm_sw_resample_out(sw,
> +                                          to_read, sw->hw->mix_buf.size - live,
> +                                          &frames_in, &frames_out);
> +
> +                sw->total_hw_samples_mixed += frames_out;
> +                sw->empty = sw->total_hw_samples_mixed == 0;
> +
> +                if (to_read - frames_in) {
> +                    dolog("Could not mix %zu frames into a capture "
> +                          "buffer, mixed %zu\n",
> +                          to_read, frames_in);
> +                    break;
> +                }
> +                n -= to_read;
> +                rpos2 = (rpos2 + to_read) % hw->mix_buf.size;
> +            }
> +        }
> +    }
> +
> +    n = MIN(samples, hw->mix_buf.size - rpos);
> +    mixeng_clear(hw->mix_buf.buffer + rpos, n);
> +    mixeng_clear(hw->mix_buf.buffer, samples - n);
> +}
> +
> +static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
> +{
> +    size_t clipped = 0;
> +
> +    while (live) {
> +        size_t size = live * hw->info.bytes_per_frame;
> +        size_t decr, proc;
> +        void *buf = hw->pcm_ops->get_buffer_out(hw, &size);
> +
> +        if (size == 0) {
> +            break;
> +        }
> +
> +        decr = MIN(size / hw->info.bytes_per_frame, live);
> +        if (buf) {
> +            audio_pcm_hw_clip_out(hw, buf, decr);
> +        }
> +        proc = hw->pcm_ops->put_buffer_out(hw, buf,
> +                                           decr * hw->info.bytes_per_frame) /
> +            hw->info.bytes_per_frame;
> +
> +        live -= proc;
> +        clipped += proc;
> +        hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size;
> +
> +        if (proc == 0 || proc < decr) {
> +            break;
> +        }
> +    }
> +
> +    if (hw->pcm_ops->run_buffer_out) {
> +        hw->pcm_ops->run_buffer_out(hw);
> +    }
> +
> +    return clipped;
> +}
> +
> +static void audio_run_out(AudioMixengBackend *s)
> +{
> +    HWVoiceOut *hw = NULL;
> +    SWVoiceOut *sw;
> +
> +    while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
> +        size_t played, live, prev_rpos;
> +        size_t hw_free = audio_pcm_hw_get_free(hw);
> +        int nb_live;
> +
> +        if (!audio_get_pdo_out(s->dev)->mixing_engine) {
> +            /* there is exactly 1 sw for each hw with no mixeng */
> +            sw = hw->sw_head.lh_first;
> +
> +            if (hw->pending_disable) {
> +                hw->enabled = false;
> +                hw->pending_disable = false;
> +                if (hw->pcm_ops->enable_out) {
> +                    hw->pcm_ops->enable_out(hw, false);
> +                }
> +            }
> +
> +            if (sw->active) {
> +                sw->callback.fn(sw->callback.opaque,
> +                                hw_free * sw->info.bytes_per_frame);
> +            }
> +
> +            if (hw->pcm_ops->run_buffer_out) {
> +                hw->pcm_ops->run_buffer_out(hw);
> +            }
> +
> +            continue;
> +        }
> +
> +        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> +            if (sw->active) {
> +                size_t sw_free = audio_get_free(sw);
> +                size_t free;
> +
> +                if (hw_free > sw->total_hw_samples_mixed) {
> +                    free = st_rate_frames_in(sw->rate,
> +                        MIN(sw_free, hw_free - sw->total_hw_samples_mixed));
> +                } else {
> +                    free = 0;
> +                }
> +                if (free > sw->resample_buf.pos) {
> +                    free = MIN(free, sw->resample_buf.size)
> +                           - sw->resample_buf.pos;
> +                    sw->callback.fn(sw->callback.opaque,
> +                                    free * sw->info.bytes_per_frame);
> +                }
> +            }
> +        }
> +
> +        live = audio_pcm_hw_get_live_out (hw, &nb_live);
> +        if (!nb_live) {
> +            live = 0;
> +        }
> +
> +        if (audio_bug(__func__, live > hw->mix_buf.size)) {
> +            dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
> +            continue;
> +        }
> +
> +        if (hw->pending_disable && !nb_live) {
> +            SWVoiceCap *sc;
> +#ifdef DEBUG_OUT
> +            dolog ("Disabling voice\n");
> +#endif
> +            hw->enabled = false;
> +            hw->pending_disable = false;
> +            if (hw->pcm_ops->enable_out) {
> +                hw->pcm_ops->enable_out(hw, false);
> +            }
> +            for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> +                sc->sw.active = false;
> +                audio_recalc_and_notify_capture (sc->cap);
> +            }
> +            continue;
> +        }
> +
> +        if (!live) {
> +            if (hw->pcm_ops->run_buffer_out) {
> +                hw->pcm_ops->run_buffer_out(hw);
> +            }
> +            continue;
> +        }
> +
> +        prev_rpos = hw->mix_buf.pos;
> +        played = audio_pcm_hw_run_out(hw, live);
> +        replay_audio_out(&played);
> +        if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) {
> +            dolog("hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\n",
> +                  hw->mix_buf.pos, hw->mix_buf.size, played);
> +            hw->mix_buf.pos = 0;
> +        }
> +
> +#ifdef DEBUG_OUT
> +        dolog("played=%zu\n", played);
> +#endif
> +
> +        if (played) {
> +            audio_capture_mix_and_clear (hw, prev_rpos, played);
> +        }
> +
> +        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> +            if (!sw->active && sw->empty) {
> +                continue;
> +            }
> +
> +            if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) {
> +                dolog("played=%zu sw->total_hw_samples_mixed=%zu\n",
> +                      played, sw->total_hw_samples_mixed);
> +                played = sw->total_hw_samples_mixed;
> +            }
> +
> +            sw->total_hw_samples_mixed -= played;
> +
> +            if (!sw->total_hw_samples_mixed) {
> +                sw->empty = true;
> +            }
> +        }
> +    }
> +}
> +
> +static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
> +{
> +    size_t conv = 0;
> +
> +    if (hw->pcm_ops->run_buffer_in) {
> +        hw->pcm_ops->run_buffer_in(hw);
> +    }
> +
> +    while (samples) {
> +        size_t proc;
> +        size_t size = samples * hw->info.bytes_per_frame;
> +        void *buf = hw->pcm_ops->get_buffer_in(hw, &size);
> +
> +        assert(size % hw->info.bytes_per_frame == 0);
> +        if (size == 0) {
> +            break;
> +        }
> +
> +        proc = audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_frame);
> +
> +        samples -= proc;
> +        conv += proc;
> +        hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame);
> +    }
> +
> +    return conv;
> +}
> +
> +static void audio_run_in(AudioMixengBackend *s)
> +{
> +    HWVoiceIn *hw = NULL;
> +
> +    if (!audio_get_pdo_in(s->dev)->mixing_engine) {
> +        while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
> +            /* there is exactly 1 sw for each hw with no mixeng */
> +            SWVoiceIn *sw = hw->sw_head.lh_first;
> +            if (sw->active) {
> +                sw->callback.fn(sw->callback.opaque, INT_MAX);
> +            }
> +        }
> +        return;
> +    }
> +
> +    while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
> +        SWVoiceIn *sw;
> +        size_t captured = 0, min;
> +        int pos;
> +
> +        if (replay_mode != REPLAY_MODE_PLAY) {
> +            captured = audio_pcm_hw_run_in(
> +                hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw));
> +        }
> +
> +        replay_audio_in_start(&captured);
> +        assert(captured <= hw->conv_buf.size);
> +        if (replay_mode == REPLAY_MODE_PLAY) {
> +            hw->conv_buf.pos = (hw->conv_buf.pos + captured) % hw->conv_buf.size;
> +        }
> +        for (pos = (hw->conv_buf.pos - captured + hw->conv_buf.size) % hw->conv_buf.size;
> +             pos != hw->conv_buf.pos;
> +             pos = (pos + 1) % hw->conv_buf.size) {
> +                uint64_t left, right;
> +
> +                if (replay_mode == REPLAY_MODE_RECORD) {
> +                    audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left, &right);
> +                }
> +                replay_audio_in_sample_lr(&left, &right);
> +                if (replay_mode == REPLAY_MODE_PLAY) {
> +                    audio_sample_from_uint64(hw->conv_buf.buffer, pos, left, right);
> +                }
> +        }
> +        replay_audio_in_finish();
> +
> +        min = audio_pcm_hw_find_min_in (hw);
> +        hw->total_samples_captured += captured - min;
> +
> +        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> +            sw->total_hw_samples_acquired -= min;
> +
> +            if (sw->active) {
> +                size_t sw_avail = audio_get_avail(sw);
> +                size_t avail;
> +
> +                avail = st_rate_frames_out(sw->rate, sw_avail);
> +                if (avail > 0) {
> +                    avail = MIN(avail, sw->resample_buf.size);
> +                    sw->callback.fn(sw->callback.opaque,
> +                                    avail * sw->info.bytes_per_frame);
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +static void audio_run_capture(AudioMixengBackend *s)
> +{
> +    CaptureVoiceOut *cap;
> +
> +    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
> +        size_t live, rpos, captured;
> +        HWVoiceOut *hw = &cap->hw;
> +        SWVoiceOut *sw;
> +
> +        captured = live = audio_pcm_hw_get_live_out (hw, NULL);
> +        rpos = hw->mix_buf.pos;
> +        while (live) {
> +            size_t left = hw->mix_buf.size - rpos;
> +            size_t to_capture = MIN(live, left);
> +            struct st_sample *src;
> +            struct capture_callback *cb;
> +
> +            src = hw->mix_buf.buffer + rpos;
> +            hw->clip (cap->buf, src, to_capture);
> +            mixeng_clear (src, to_capture);
> +
> +            for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> +                cb->ops.capture (cb->opaque, cap->buf,
> +                                 to_capture * hw->info.bytes_per_frame);
> +            }
> +            rpos = (rpos + to_capture) % hw->mix_buf.size;
> +            live -= to_capture;
> +        }
> +        hw->mix_buf.pos = rpos;
> +
> +        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> +            if (!sw->active && sw->empty) {
> +                continue;
> +            }
> +
> +            if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)) {
> +                dolog("captured=%zu sw->total_hw_samples_mixed=%zu\n",
> +                      captured, sw->total_hw_samples_mixed);
> +                captured = sw->total_hw_samples_mixed;
> +            }
> +
> +            sw->total_hw_samples_mixed -= captured;
> +            sw->empty = sw->total_hw_samples_mixed == 0;
> +        }
> +    }
> +}
> +
> +void audio_run(AudioMixengBackend *s, const char *msg)
> +{
> +    audio_run_out(s);
> +    audio_run_in(s);
> +    audio_run_capture(s);
> +
> +#ifdef DEBUG_POLL
> +    {
> +        static double prevtime;
> +        double currtime;
> +        struct timeval tv;
> +
> +        if (gettimeofday (&tv, NULL)) {
> +            perror ("audio_run: gettimeofday");
> +            return;
> +        }
> +
> +        currtime = tv.tv_sec + tv.tv_usec * 1e-6;
> +        dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime);
> +        prevtime = currtime;
> +    }
> +#endif
> +}
> +
> +void audio_generic_run_buffer_in(HWVoiceIn *hw)
> +{
> +    if (unlikely(!hw->buf_emul)) {
> +        hw->size_emul = hw->samples * hw->info.bytes_per_frame;
> +        hw->buf_emul = g_malloc(hw->size_emul);
> +        hw->pos_emul = hw->pending_emul = 0;
> +    }
> +
> +    while (hw->pending_emul < hw->size_emul) {
> +        size_t read_len = MIN(hw->size_emul - hw->pos_emul,
> +                              hw->size_emul - hw->pending_emul);
> +        size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul,
> +                                        read_len);
> +        hw->pending_emul += read;
> +        hw->pos_emul = (hw->pos_emul + read) % hw->size_emul;
> +        if (read < read_len) {
> +            break;
> +        }
> +    }
> +}
> +
> +void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)
> +{
> +    size_t start;
> +
> +    start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
> +    assert(start < hw->size_emul);
> +
> +    *size = MIN(*size, hw->pending_emul);
> +    *size = MIN(*size, hw->size_emul - start);
> +    return hw->buf_emul + start;
> +}
> +
> +void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
> +{
> +    assert(size <= hw->pending_emul);
> +    hw->pending_emul -= size;
> +}
> +
> +size_t audio_generic_buffer_get_free(HWVoiceOut *hw)
> +{
> +    if (hw->buf_emul) {
> +        return hw->size_emul - hw->pending_emul;
> +    } else {
> +        return hw->samples * hw->info.bytes_per_frame;
> +    }
> +}
> +
> +void audio_generic_run_buffer_out(HWVoiceOut *hw)
> +{
> +    while (hw->pending_emul) {
> +        size_t write_len, written, start;
> +
> +        start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
> +        assert(start < hw->size_emul);
> +
> +        write_len = MIN(hw->pending_emul, hw->size_emul - start);
> +
> +        written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len);
> +        hw->pending_emul -= written;
> +
> +        if (written < write_len) {
> +            break;
> +        }
> +    }
> +}
> +
> +void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)
> +{
> +    if (unlikely(!hw->buf_emul)) {
> +        hw->size_emul = hw->samples * hw->info.bytes_per_frame;
> +        hw->buf_emul = g_malloc(hw->size_emul);
> +        hw->pos_emul = hw->pending_emul = 0;
> +    }
> +
> +    *size = MIN(hw->size_emul - hw->pending_emul,
> +                hw->size_emul - hw->pos_emul);
> +    return hw->buf_emul + hw->pos_emul;
> +}
> +
> +size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
> +{
> +    assert(buf == hw->buf_emul + hw->pos_emul &&
> +           size + hw->pending_emul <= hw->size_emul);
> +
> +    hw->pending_emul += size;
> +    hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;
> +
> +    return size;
> +}
> +
> +size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size)
> +{
> +    size_t total = 0;
> +
> +    if (hw->pcm_ops->buffer_get_free) {
> +        size_t free = hw->pcm_ops->buffer_get_free(hw);
> +
> +        size = MIN(size, free);
> +    }
> +
> +    while (total < size) {
> +        size_t dst_size = size - total;
> +        size_t copy_size, proc;
> +        void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size);
> +
> +        if (dst_size == 0) {
> +            break;
> +        }
> +
> +        copy_size = MIN(size - total, dst_size);
> +        if (dst) {
> +            memcpy(dst, (char *)buf + total, copy_size);
> +        }
> +        proc = hw->pcm_ops->put_buffer_out(hw, dst, copy_size);
> +        total += proc;
> +
> +        if (proc == 0 || proc < copy_size) {
> +            break;
> +        }
> +    }
> +
> +    return total;
> +}
> +
> +size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
> +{
> +    size_t total = 0;
> +
> +    if (hw->pcm_ops->run_buffer_in) {
> +        hw->pcm_ops->run_buffer_in(hw);
> +    }
> +
> +    while (total < size) {
> +        size_t src_size = size - total;
> +        void *src = hw->pcm_ops->get_buffer_in(hw, &src_size);
> +
> +        if (src_size == 0) {
> +            break;
> +        }
> +
> +        memcpy((char *)buf + total, src, src_size);
> +        hw->pcm_ops->put_buffer_in(hw, src, src_size);
> +        total += src_size;
> +    }
> +
> +    return total;
> +}
> +
> +static bool audio_mixeng_backend_realize(AudioBackend *abe,
> +                                         Audiodev *dev, Error **errp)
> +{
> +    AudioMixengBackend *be = AUDIO_MIXENG_BACKEND(abe);
> +    audio_driver *drv = AUDIO_MIXENG_BACKEND_GET_CLASS(be)->driver;
> +
> +    be->dev = dev;
> +    be->drv_opaque = drv->init(be->dev, errp);
> +    if (!be->drv_opaque) {
> +        return false;
> +    }
> +
> +    if (!drv->pcm_ops->get_buffer_in) {
> +        drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in;
> +        drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in;
> +    }
> +    if (!drv->pcm_ops->get_buffer_out) {
> +        drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out;
> +        drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;
> +    }
> +
> +    audio_init_nb_voices_out(be, drv, 1);
> +    audio_init_nb_voices_in(be, drv, 0);
> +    be->drv = drv;
> +
> +    if (be->dev->timer_period <= 0) {
> +        be->period_ticks = 1;
> +    } else {
> +        be->period_ticks = be->dev->timer_period * (int64_t)SCALE_US;
> +    }
> +
> +    return true;
> +}
> +
> +static void audio_vm_change_state_handler (void *opaque, bool running,
> +                                           RunState state)
> +{
> +    AudioMixengBackend *s = opaque;
> +    HWVoiceOut *hwo = NULL;
> +    HWVoiceIn *hwi = NULL;
> +
> +    while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
> +        if (hwo->pcm_ops->enable_out) {
> +            hwo->pcm_ops->enable_out(hwo, running);
> +        }
> +    }
> +
> +    while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
> +        if (hwi->pcm_ops->enable_in) {
> +            hwi->pcm_ops->enable_in(hwi, running);
> +        }
> +    }
> +    audio_reset_timer (s);
> +}
> +
> +static const VMStateDescription vmstate_audio;
> +
> +static const char *audio_mixeng_backend_get_id(AudioBackend *be)
> +{
> +    return AUDIO_MIXENG_BACKEND(be)->dev->id;
> +}
> +
> +static CaptureVoiceOut *audio_mixeng_backend_add_capture(
> +    AudioBackend *be,
> +    struct audsettings *as,
> +    struct audio_capture_ops *ops,
> +    void *cb_opaque);
> +
> +static void audio_mixeng_backend_del_capture(
> +    AudioBackend *be,
> +    CaptureVoiceOut *cap,
> +    void *cb_opaque);
> +
> +static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
> +                                                Volume *vol);
> +static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
> +                                               Volume *vol);
> +
> +static void audio_mixeng_backend_class_init(ObjectClass *klass, const void *data)
> +{
> +    AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
> +
> +    be->realize = audio_mixeng_backend_realize;
> +    be->get_id = audio_mixeng_backend_get_id;
> +    be->open_in = audio_mixeng_backend_open_in;
> +    be->open_out = audio_mixeng_backend_open_out;
> +    be->close_in = audio_mixeng_backend_close_in;
> +    be->close_out = audio_mixeng_backend_close_out;
> +    be->is_active_out = audio_mixeng_backend_is_active_out;
> +    be->is_active_in = audio_mixeng_backend_is_active_in;
> +    be->set_active_out = audio_mixeng_backend_set_active_out;
> +    be->set_active_in = audio_mixeng_backend_set_active_in;
> +    be->set_volume_out = audio_mixeng_backend_set_volume_out;
> +    be->set_volume_in = audio_mixeng_backend_set_volume_in;
> +    be->read = audio_mixeng_backend_read;
> +    be->write = audio_mixeng_backend_write;
> +    be->get_buffer_size_out = audio_mixeng_backend_get_buffer_size_out;
> +    be->add_capture = audio_mixeng_backend_add_capture;
> +    be->del_capture = audio_mixeng_backend_del_capture;
> +}
> +
> +static void audio_mixeng_backend_init(Object *obj)
> +{
> +    AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
> +
> +    QLIST_INIT(&s->hw_head_out);
> +    QLIST_INIT(&s->hw_head_in);
> +    QLIST_INIT(&s->cap_head);
> +    s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
> +
> +    s->vmse = qemu_add_vm_change_state_handler(audio_vm_change_state_handler, s);
> +    assert(s->vmse != NULL);
> +
> +    vmstate_register_any(NULL, &vmstate_audio, s);
> +}
> +
> +static void audio_mixeng_backend_finalize(Object *obj)
> +{
> +    AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
> +    HWVoiceOut *hwo, *hwon;
> +    HWVoiceIn *hwi, *hwin;
> +
> +    QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) {
> +        SWVoiceCap *sc;
> +
> +        if (hwo->enabled && hwo->pcm_ops->enable_out) {
> +            hwo->pcm_ops->enable_out(hwo, false);
> +        }
> +        hwo->pcm_ops->fini_out (hwo);
> +
> +        for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> +            CaptureVoiceOut *cap = sc->cap;
> +            struct capture_callback *cb;
> +
> +            for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> +                cb->ops.destroy (cb->opaque);
> +            }
> +        }
> +        QLIST_REMOVE(hwo, entries);
> +    }
> +
> +    QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) {
> +        if (hwi->enabled && hwi->pcm_ops->enable_in) {
> +            hwi->pcm_ops->enable_in(hwi, false);
> +        }
> +        hwi->pcm_ops->fini_in (hwi);
> +        QLIST_REMOVE(hwi, entries);
> +    }
> +
> +    if (s->drv) {
> +        s->drv->fini (s->drv_opaque);
> +        s->drv = NULL;
> +    }
> +
> +    if (s->dev) {
> +        qapi_free_Audiodev(s->dev);
> +        s->dev = NULL;
> +    }
> +
> +    if (s->ts) {
> +        timer_free(s->ts);
> +        s->ts = NULL;
> +    }
> +
> +    if (s->vmse) {
> +        qemu_del_vm_change_state_handler(s->vmse);
> +        s->vmse = NULL;
> +    }
> +
> +    vmstate_unregister(NULL, &vmstate_audio, s);
> +}
> +
> +static bool vmstate_audio_needed(void *opaque)
> +{
> +    /*
> +     * Never needed, this vmstate only exists in case
> +     * an old qemu sends it to us.
> +     */
> +    return false;
> +}
> +
> +static const VMStateDescription vmstate_audio = {
> +    .name = "audio",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .needed = vmstate_audio_needed,
> +    .fields = (const VMStateField[]) {
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static struct audio_pcm_ops capture_pcm_ops;
> +
> +static CaptureVoiceOut *audio_mixeng_backend_add_capture(
> +    AudioBackend *be,
> +    struct audsettings *as,
> +    struct audio_capture_ops *ops,
> +    void *cb_opaque)
> +{
> +    AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(be);
> +    CaptureVoiceOut *cap;
> +    struct capture_callback *cb;
> +
> +    if (!s) {
> +        error_report("Capturing without setting an audiodev is not supported");
> +        abort();
> +    }
> +
> +    if (!audio_get_pdo_out(s->dev)->mixing_engine) {
> +        dolog("Can't capture with mixeng disabled\n");
> +        return NULL;
> +    }
> +
> +    if (audio_validate_settings (as)) {
> +        dolog ("Invalid settings were passed when trying to add capture\n");
> +        audio_print_settings (as);
> +        return NULL;
> +    }
> +
> +    cb = g_malloc0(sizeof(*cb));
> +    cb->ops = *ops;
> +    cb->opaque = cb_opaque;
> +
> +    cap = audio_pcm_capture_find_specific(s, as);
> +    if (cap) {
> +        QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
> +    } else {
> +        HWVoiceOut *hw;
> +
> +        cap = g_malloc0(sizeof(*cap));
> +
> +        hw = &cap->hw;
> +        hw->s = s;
> +        hw->pcm_ops = &capture_pcm_ops;
> +        QLIST_INIT (&hw->sw_head);
> +        QLIST_INIT (&cap->cb_head);
> +
> +        /* XXX find a more elegant way */
> +        hw->samples = 4096 * 4;
> +        audio_pcm_hw_alloc_resources_out(hw);
> +
> +        audio_pcm_init_info (&hw->info, as);
> +
> +        cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame);
> +
> +        if (hw->info.is_float) {
> +            hw->clip = mixeng_clip_float[hw->info.nchannels == 2]
> +                [hw->info.swap_endianness];
> +        } else {
> +            hw->clip = mixeng_clip
> +                [hw->info.nchannels == 2]
> +                [hw->info.is_signed]
> +                [hw->info.swap_endianness]
> +                [audio_bits_to_index(hw->info.bits)];
> +        }
> +
> +        QLIST_INSERT_HEAD (&s->cap_head, cap, entries);
> +        QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
> +
> +        QLIST_FOREACH(hw, &s->hw_head_out, entries) {
> +            audio_attach_capture (hw);
> +        }
> +    }
> +
> +    return cap;
> +}
> +
> +static void audio_mixeng_backend_del_capture(
> +    AudioBackend *be,
> +    CaptureVoiceOut *cap,
> +    void *cb_opaque)
> +{
> +    struct capture_callback *cb;
> +
> +    for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> +        if (cb->opaque == cb_opaque) {
> +            cb->ops.destroy (cb_opaque);
> +            QLIST_REMOVE (cb, entries);
> +            g_free (cb);
> +
> +            if (!cap->cb_head.lh_first) {
> +                SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1;
> +
> +                while (sw) {
> +                    SWVoiceCap *sc = (SWVoiceCap *) sw;
> +#ifdef DEBUG_CAPTURE
> +                    dolog ("freeing %s\n", sw->name);
> +#endif
> +
> +                    sw1 = sw->entries.le_next;
> +                    if (sw->rate) {
> +                        st_rate_stop (sw->rate);
> +                        sw->rate = NULL;
> +                    }
> +                    QLIST_REMOVE (sw, entries);
> +                    QLIST_REMOVE (sc, entries);
> +                    g_free (sc);
> +                    sw = sw1;
> +                }
> +                QLIST_REMOVE (cap, entries);
> +                g_free(cap->hw.mix_buf.buffer);
> +                g_free (cap->buf);
> +                g_free (cap);
> +            }
> +            return;
> +        }
> +    }
> +}
> +
> +static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
> +                                                Volume *vol)
> +{
> +    if (sw) {
> +        HWVoiceOut *hw = sw->hw;
> +
> +        sw->vol.mute = vol->mute;
> +        sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
> +        sw->vol.r = nominal_volume.l * vol->vol[vol->channels > 1 ? 1 : 0] /
> +            255;
> +
> +        if (hw->pcm_ops->volume_out) {
> +            hw->pcm_ops->volume_out(hw, vol);
> +        }
> +    }
> +}
> +
> +static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
> +                                               Volume *vol)
> +{
> +    if (sw) {
> +        HWVoiceIn *hw = sw->hw;
> +
> +        sw->vol.mute = vol->mute;
> +        sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
> +        sw->vol.r = nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : 0] /
> +            255;
> +
> +        if (hw->pcm_ops->volume_in) {
> +            hw->pcm_ops->volume_in(hw, vol);
> +        }
> +    }
> +}
> +
> +audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo)
> +{
> +    return (audsettings) {
> +        .freq = pdo->frequency,
> +        .nchannels = pdo->channels,
> +        .fmt = pdo->format,
> +        .endianness = HOST_BIG_ENDIAN,
> +    };
> +}
> +
> +int audioformat_bytes_per_sample(AudioFormat fmt)
> +{
> +    switch (fmt) {
> +    case AUDIO_FORMAT_U8:
> +    case AUDIO_FORMAT_S8:
> +        return 1;
> +
> +    case AUDIO_FORMAT_U16:
> +    case AUDIO_FORMAT_S16:
> +        return 2;
> +
> +    case AUDIO_FORMAT_U32:
> +    case AUDIO_FORMAT_S32:
> +    case AUDIO_FORMAT_F32:
> +        return 4;
> +
> +    case AUDIO_FORMAT__MAX:
> +        ;
> +    }
> +    abort();
> +}
> +
> +
> +/* frames = freq * usec / 1e6 */
> +int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,
> +                        audsettings *as, int def_usecs)
> +{
> +    uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs;
> +    return (as->freq * usecs + 500000) / 1000000;
> +}
> +
> +/* samples = channels * frames = channels * freq * usec / 1e6 */
> +int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,
> +                         audsettings *as, int def_usecs)
> +{
> +    return as->nchannels * audio_buffer_frames(pdo, as, def_usecs);
> +}
> +
> +/*
> + * bytes = bytes_per_sample * samples =
> + *     bytes_per_sample * channels * freq * usec / 1e6
> + */
> +int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,
> +                       audsettings *as, int def_usecs)
> +{
> +    return audio_buffer_samples(pdo, as, def_usecs) *
> +        audioformat_bytes_per_sample(as->fmt);
> +}
> +
> +void audio_rate_start(RateCtl *rate)
> +{
> +    memset(rate, 0, sizeof(RateCtl));
> +    rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> +}
> +
> +size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info)
> +{
> +    int64_t now;
> +    int64_t ticks;
> +    int64_t bytes;
> +    int64_t frames;
> +
> +    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> +    ticks = now - rate->start_ticks;
> +    bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);
> +    frames = (bytes - rate->bytes_sent) / info->bytes_per_frame;
> +    rate->peeked_frames = frames;
> +
> +    return frames < 0 ? 0 : frames * info->bytes_per_frame;
> +}
> +
> +void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used)
> +{
> +    if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) {
> +        AUD_log(NULL, "Resetting rate control (%" PRId64 " frames)\n",
> +                rate->peeked_frames);
> +        audio_rate_start(rate);
> +    }
> +
> +    rate->bytes_sent += bytes_used;
> +}
> +
> +size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info,
> +                            size_t bytes_avail)
> +{
> +    size_t bytes;
> +
> +    bytes = audio_rate_peek_bytes(rate, info);
> +    bytes = MIN(bytes, bytes_avail);
> +    audio_rate_add_bytes(rate, bytes);
> +
> +    return bytes;
> +}
> +
> +static const TypeInfo audio_mixeng_backend_info = {
> +    .name = TYPE_AUDIO_MIXENG_BACKEND,
> +    .parent = TYPE_AUDIO_BACKEND,
> +    .instance_size = sizeof(AudioMixengBackend),
> +    .instance_init = audio_mixeng_backend_init,
> +    .instance_finalize = audio_mixeng_backend_finalize,
> +    .abstract = false,
> +    .class_size = sizeof(AudioMixengBackendClass),
> +    .class_init = audio_mixeng_backend_class_init,
> +};
> +
> +static void register_types(void)
> +{
> +    type_register_static(&audio_mixeng_backend_info);
> +}
> +
> +type_init(register_types);
> diff --git a/audio/audio.c b/audio/audio.c
> index 1d948084e80..6f10ee470ee 100644
> --- a/audio/audio.c
> +++ b/audio/audio.c
> @@ -1,1750 +1,55 @@
> -/*
> - * QEMU Audio subsystem
> - *
> - * Copyright (c) 2003-2005 Vassili Karpov (malc)
> - *
> - * Permission is hereby granted, free of charge, to any person obtaining a copy
> - * of this software and associated documentation files (the "Software"), to deal
> - * in the Software without restriction, including without limitation the rights
> - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> - * copies of the Software, and to permit persons to whom the Software is
> - * furnished to do so, subject to the following conditions:
> - *
> - * The above copyright notice and this permission notice shall be included in
> - * all copies or substantial portions of the Software.
> - *
> - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> - * THE SOFTWARE.
> - */
> -
> -#include "qemu/osdep.h"
> -#include "qemu/audio.h"
> -#include "migration/vmstate.h"
> -#include "qemu/timer.h"
> -#include "qapi/error.h"
> -#include "qapi/clone-visitor.h"
> -#include "qapi/qobject-input-visitor.h"
> -#include "qapi/qapi-visit-audio.h"
> -#include "qapi/qapi-commands-audio.h"
> -#include "qobject/qdict.h"
> -#include "qemu/error-report.h"
> -#include "qemu/log.h"
> -#include "qemu/module.h"
> -#include "qemu/help_option.h"
> -#include "qom/object.h"
> -#include "system/system.h"
> -#include "system/replay.h"
> -#include "system/runstate.h"
> -#include "trace.h"
> -
> -#define AUDIO_CAP "audio"
> -#include "audio_int.h"
> -
> -/* #define DEBUG_LIVE */
> -/* #define DEBUG_OUT */
> -/* #define DEBUG_CAPTURE */
> -/* #define DEBUG_POLL */
> -
> -#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown"
> -
> -
> -/* Order of CONFIG_AUDIO_DRIVERS is import.
> -   The 1st one is the one used by default, that is the reason
> -    that we generate the list.
> -*/
> -const char *audio_prio_list[] = {
> -#ifdef CONFIG_GIO
> -    "dbus",
> -#endif
> -    "spice",
> -    CONFIG_AUDIO_DRIVERS
> -    "none",
> -    NULL
> -};
> -
> -typedef struct AudiodevListEntry {
> -    Audiodev *dev;
> -    QSIMPLEQ_ENTRY(AudiodevListEntry) next;
> -} AudiodevListEntry;
> -
> -typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;
> -
> -static AudiodevListHead audiodevs =
> -    QSIMPLEQ_HEAD_INITIALIZER(audiodevs);
> -static AudiodevListHead default_audiodevs =
> -    QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);
> -
> -static AudioBackendClass *audio_be_class_by_name(const char *name)
> -{
> -    g_autofree char *tname = g_strconcat("audio-", name, NULL);
> -    ObjectClass *oc = module_object_class_by_name(tname);
> -
> -    if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) {
> -        return NULL;
> -    }
> -
> -    return AUDIO_BACKEND_CLASS(oc);
> -}
> -
> -static AudioBackend *default_audio_be;
> -
> -const struct mixeng_volume nominal_volume = {
> -    .mute = 0,
> -#ifdef FLOAT_MIXENG
> -    .r = 1.0,
> -    .l = 1.0,
> -#else
> -    .r = 1ULL << 32,
> -    .l = 1ULL << 32,
> -#endif
> -};
> -
> -int audio_bug (const char *funcname, int cond)
> -{
> -    if (cond) {
> -        static int shown;
> -
> -        AUD_log (NULL, "A bug was just triggered in %s\n", funcname);
> -        if (!shown) {
> -            shown = 1;
> -            AUD_log (NULL, "Save all your work and restart without audio\n");
> -            AUD_log (NULL, "I am sorry\n");
> -        }
> -        AUD_log (NULL, "Context:\n");
> -    }
> -
> -    return cond;
> -}
> -
> -static inline int audio_bits_to_index (int bits)
> -{
> -    switch (bits) {
> -    case 8:
> -        return 0;
> -
> -    case 16:
> -        return 1;
> -
> -    case 32:
> -        return 2;
> -
> -    default:
> -        audio_bug ("bits_to_index", 1);
> -        AUD_log (NULL, "invalid bits %d\n", bits);
> -        return 0;
> -    }
> -}
> -
> -void AUD_vlog (const char *cap, const char *fmt, va_list ap)
> -{
> -    if (cap) {
> -        fprintf(stderr, "%s: ", cap);
> -    }
> -
> -    vfprintf(stderr, fmt, ap);
> -}
> -
> -void AUD_log (const char *cap, const char *fmt, ...)
> -{
> -    va_list ap;
> -
> -    va_start (ap, fmt);
> -    AUD_vlog (cap, fmt, ap);
> -    va_end (ap);
> -}
> -
> -static void audio_print_settings (const struct audsettings *as)
> -{
> -    dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels);
> -
> -    switch (as->fmt) {
> -    case AUDIO_FORMAT_S8:
> -        AUD_log (NULL, "S8");
> -        break;
> -    case AUDIO_FORMAT_U8:
> -        AUD_log (NULL, "U8");
> -        break;
> -    case AUDIO_FORMAT_S16:
> -        AUD_log (NULL, "S16");
> -        break;
> -    case AUDIO_FORMAT_U16:
> -        AUD_log (NULL, "U16");
> -        break;
> -    case AUDIO_FORMAT_S32:
> -        AUD_log (NULL, "S32");
> -        break;
> -    case AUDIO_FORMAT_U32:
> -        AUD_log (NULL, "U32");
> -        break;
> -    case AUDIO_FORMAT_F32:
> -        AUD_log (NULL, "F32");
> -        break;
> -    default:
> -        AUD_log (NULL, "invalid(%d)", as->fmt);
> -        break;
> -    }
> -
> -    AUD_log (NULL, " endianness=");
> -    switch (as->endianness) {
> -    case 0:
> -        AUD_log (NULL, "little");
> -        break;
> -    case 1:
> -        AUD_log (NULL, "big");
> -        break;
> -    default:
> -        AUD_log (NULL, "invalid");
> -        break;
> -    }
> -    AUD_log (NULL, "\n");
> -}
> -
> -static int audio_validate_settings (const struct audsettings *as)
> -{
> -    int invalid;
> -
> -    invalid = as->nchannels < 1;
> -    invalid |= as->endianness != 0 && as->endianness != 1;
> -
> -    switch (as->fmt) {
> -    case AUDIO_FORMAT_S8:
> -    case AUDIO_FORMAT_U8:
> -    case AUDIO_FORMAT_S16:
> -    case AUDIO_FORMAT_U16:
> -    case AUDIO_FORMAT_S32:
> -    case AUDIO_FORMAT_U32:
> -    case AUDIO_FORMAT_F32:
> -        break;
> -    default:
> -        invalid = 1;
> -        break;
> -    }
> -
> -    invalid |= as->freq <= 0;
> -    return invalid ? -1 : 0;
> -}
> -
> -static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct audsettings *as)
> -{
> -    int bits = 8;
> -    bool is_signed = false, is_float = false;
> -
> -    switch (as->fmt) {
> -    case AUDIO_FORMAT_S8:
> -        is_signed = true;
> -        /* fall through */
> -    case AUDIO_FORMAT_U8:
> -        break;
> -
> -    case AUDIO_FORMAT_S16:
> -        is_signed = true;
> -        /* fall through */
> -    case AUDIO_FORMAT_U16:
> -        bits = 16;
> -        break;
> -
> -    case AUDIO_FORMAT_F32:
> -        is_float = true;
> -        /* fall through */
> -    case AUDIO_FORMAT_S32:
> -        is_signed = true;
> -        /* fall through */
> -    case AUDIO_FORMAT_U32:
> -        bits = 32;
> -        break;
> -
> -    default:
> -        abort();
> -    }
> -    return info->freq == as->freq
> -        && info->nchannels == as->nchannels
> -        && info->is_signed == is_signed
> -        && info->is_float == is_float
> -        && info->bits == bits
> -        && info->swap_endianness == (as->endianness != HOST_BIG_ENDIAN);
> -}
> -
> -void audio_pcm_init_info (struct audio_pcm_info *info, const struct audsettings *as)
> -{
> -    int bits = 8, mul;
> -    bool is_signed = false, is_float = false;
> -
> -    switch (as->fmt) {
> -    case AUDIO_FORMAT_S8:
> -        is_signed = true;
> -        /* fall through */
> -    case AUDIO_FORMAT_U8:
> -        mul = 1;
> -        break;
> -
> -    case AUDIO_FORMAT_S16:
> -        is_signed = true;
> -        /* fall through */
> -    case AUDIO_FORMAT_U16:
> -        bits = 16;
> -        mul = 2;
> -        break;
> -
> -    case AUDIO_FORMAT_F32:
> -        is_float = true;
> -        /* fall through */
> -    case AUDIO_FORMAT_S32:
> -        is_signed = true;
> -        /* fall through */
> -    case AUDIO_FORMAT_U32:
> -        bits = 32;
> -        mul = 4;
> -        break;
> -
> -    default:
> -        abort();
> -    }
> -
> -    info->freq = as->freq;
> -    info->bits = bits;
> -    info->is_signed = is_signed;
> -    info->is_float = is_float;
> -    info->nchannels = as->nchannels;
> -    info->bytes_per_frame = as->nchannels * mul;
> -    info->bytes_per_second = info->freq * info->bytes_per_frame;
> -    info->swap_endianness = (as->endianness != HOST_BIG_ENDIAN);
> -}
> -
> -void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
> -{
> -    if (!len) {
> -        return;
> -    }
> -
> -    if (info->is_signed || info->is_float) {
> -        memset(buf, 0x00, len * info->bytes_per_frame);
> -    } else {
> -        switch (info->bits) {
> -        case 8:
> -            memset(buf, 0x80, len * info->bytes_per_frame);
> -            break;
> -
> -        case 16:
> -            {
> -                int i;
> -                uint16_t *p = buf;
> -                short s = INT16_MAX;
> -
> -                if (info->swap_endianness) {
> -                    s = bswap16 (s);
> -                }
> -
> -                for (i = 0; i < len * info->nchannels; i++) {
> -                    p[i] = s;
> -                }
> -            }
> -            break;
> -
> -        case 32:
> -            {
> -                int i;
> -                uint32_t *p = buf;
> -                int32_t s = INT32_MAX;
> -
> -                if (info->swap_endianness) {
> -                    s = bswap32 (s);
> -                }
> -
> -                for (i = 0; i < len * info->nchannels; i++) {
> -                    p[i] = s;
> -                }
> -            }
> -            break;
> -
> -        default:
> -            AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n",
> -                     info->bits);
> -            break;
> -        }
> -    }
> -}
> -
> -/*
> - * Capture
> - */
> -static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioMixengBackend *s,
> -                                                        struct audsettings *as)
> -{
> -    CaptureVoiceOut *cap;
> -
> -    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
> -        if (audio_pcm_info_eq (&cap->hw.info, as)) {
> -            return cap;
> -        }
> -    }
> -    return NULL;
> -}
> -
> -static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd)
> -{
> -    struct capture_callback *cb;
> -
> -#ifdef DEBUG_CAPTURE
> -    dolog ("notification %d sent\n", cmd);
> -#endif
> -    for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> -        cb->ops.notify (cb->opaque, cmd);
> -    }
> -}
> -
> -static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled)
> -{
> -    if (cap->hw.enabled != enabled) {
> -        audcnotification_e cmd;
> -        cap->hw.enabled = enabled;
> -        cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE;
> -        audio_notify_capture (cap, cmd);
> -    }
> -}
> -
> -static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap)
> -{
> -    HWVoiceOut *hw = &cap->hw;
> -    SWVoiceOut *sw;
> -    bool enabled = false;
> -
> -    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> -        if (sw->active) {
> -            enabled = true;
> -            break;
> -        }
> -    }
> -    audio_capture_maybe_changed (cap, enabled);
> -}
> -
> -static void audio_detach_capture (HWVoiceOut *hw)
> -{
> -    SWVoiceCap *sc = hw->cap_head.lh_first;
> -
> -    while (sc) {
> -        SWVoiceCap *sc1 = sc->entries.le_next;
> -        SWVoiceOut *sw = &sc->sw;
> -        CaptureVoiceOut *cap = sc->cap;
> -        int was_active = sw->active;
> -
> -        if (sw->rate) {
> -            st_rate_stop (sw->rate);
> -            sw->rate = NULL;
> -        }
> -
> -        QLIST_REMOVE (sw, entries);
> -        QLIST_REMOVE (sc, entries);
> -        g_free (sc);
> -        if (was_active) {
> -            /* We have removed soft voice from the capture:
> -               this might have changed the overall status of the capture
> -               since this might have been the only active voice */
> -            audio_recalc_and_notify_capture (cap);
> -        }
> -        sc = sc1;
> -    }
> -}
> -
> -static int audio_attach_capture (HWVoiceOut *hw)
> -{
> -    AudioMixengBackend *s = hw->s;
> -    CaptureVoiceOut *cap;
> -
> -    audio_detach_capture (hw);
> -    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
> -        SWVoiceCap *sc;
> -        SWVoiceOut *sw;
> -        HWVoiceOut *hw_cap = &cap->hw;
> -
> -        sc = g_malloc0(sizeof(*sc));
> -
> -        sc->cap = cap;
> -        sw = &sc->sw;
> -        sw->hw = hw_cap;
> -        sw->info = hw->info;
> -        sw->empty = true;
> -        sw->active = hw->enabled;
> -        sw->vol = nominal_volume;
> -        sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
> -        QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
> -        QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);
> -#ifdef DEBUG_CAPTURE
> -        sw->name = g_strdup_printf ("for %p %d,%d,%d",
> -                                    hw, sw->info.freq, sw->info.bits,
> -                                    sw->info.nchannels);
> -        dolog ("Added %s active = %d\n", sw->name, sw->active);
> -#endif
> -        if (sw->active) {
> -            audio_capture_maybe_changed (cap, 1);
> -        }
> -    }
> -    return 0;
> -}
> -
> -/*
> - * Hard voice (capture)
> - */
> -static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)
> -{
> -    SWVoiceIn *sw;
> -    size_t m = hw->total_samples_captured;
> -
> -    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> -        if (sw->active) {
> -            m = MIN (m, sw->total_hw_samples_acquired);
> -        }
> -    }
> -    return m;
> -}
> -
> -static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
> -{
> -    size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
> -    if (audio_bug(__func__, live > hw->conv_buf.size)) {
> -        dolog("live=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
> -        return 0;
> -    }
> -    return live;
> -}
> -
> -static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
> -{
> -    size_t conv = 0;
> -    STSampleBuffer *conv_buf = &hw->conv_buf;
> -
> -    while (samples) {
> -        uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);
> -        size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);
> -
> -        hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);
> -        conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
> -        samples -= proc;
> -        conv += proc;
> -    }
> -
> -    return conv;
> -}
> -
> -/*
> - * Soft voice (capture)
> - */
> -static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
> -    size_t frames_in_max, size_t frames_out_max,
> -    size_t *total_in, size_t *total_out)
> -{
> -    HWVoiceIn *hw = sw->hw;
> -    struct st_sample *src, *dst;
> -    size_t live, rpos, frames_in, frames_out;
> -
> -    live = hw->total_samples_captured - sw->total_hw_samples_acquired;
> -    rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
> -
> -    /* resample conv_buf from rpos to end of buffer */
> -    src = hw->conv_buf.buffer + rpos;
> -    frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos);
> -    dst = sw->resample_buf.buffer;
> -    frames_out = frames_out_max;
> -    st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
> -    rpos += frames_in;
> -    *total_in = frames_in;
> -    *total_out = frames_out;
> -
> -    /* resample conv_buf from start of buffer if there are input frames left */
> -    if (frames_in_max - frames_in && rpos == hw->conv_buf.size) {
> -        src = hw->conv_buf.buffer;
> -        frames_in = frames_in_max - frames_in;
> -        dst += frames_out;
> -        frames_out = frames_out_max - frames_out;
> -        st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
> -        *total_in += frames_in;
> -        *total_out += frames_out;
> -    }
> -}
> -
> -static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
> -{
> -    HWVoiceIn *hw = sw->hw;
> -    size_t live, frames_out_max, total_in, total_out;
> -
> -    live = hw->total_samples_captured - sw->total_hw_samples_acquired;
> -    if (!live) {
> -        return 0;
> -    }
> -    if (audio_bug(__func__, live > hw->conv_buf.size)) {
> -        dolog("live_in=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
> -        return 0;
> -    }
> -
> -    frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,
> -                         sw->resample_buf.size);
> -
> -    audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);
> -
> -    if (!hw->pcm_ops->volume_in) {
> -        mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);
> -    }
> -    sw->clip(buf, sw->resample_buf.buffer, total_out);
> -
> -    sw->total_hw_samples_acquired += total_in;
> -    return total_out * sw->info.bytes_per_frame;
> -}
> -
> -/*
> - * Hard voice (playback)
> - */
> -static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)
> -{
> -    SWVoiceOut *sw;
> -    size_t m = SIZE_MAX;
> -    int nb_live = 0;
> -
> -    for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> -        if (sw->active || !sw->empty) {
> -            m = MIN (m, sw->total_hw_samples_mixed);
> -            nb_live += 1;
> -        }
> -    }
> -
> -    *nb_livep = nb_live;
> -    return m;
> -}
> -
> -static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)
> -{
> -    size_t smin;
> -    int nb_live1;
> -
> -    smin = audio_pcm_hw_find_min_out (hw, &nb_live1);
> -    if (nb_live) {
> -        *nb_live = nb_live1;
> -    }
> -
> -    if (nb_live1) {
> -        size_t live = smin;
> -
> -        if (audio_bug(__func__, live > hw->mix_buf.size)) {
> -            dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
> -            return 0;
> -        }
> -        return live;
> -    }
> -    return 0;
> -}
> -
> -static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)
> -{
> -    return (hw->pcm_ops->buffer_get_free ? hw->pcm_ops->buffer_get_free(hw) :
> -            INT_MAX) / hw->info.bytes_per_frame;
> -}
> -
> -static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
> -{
> -    size_t clipped = 0;
> -    size_t pos = hw->mix_buf.pos;
> -
> -    while (len) {
> -        st_sample *src = hw->mix_buf.buffer + pos;
> -        uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame);
> -        size_t samples_till_end_of_buf = hw->mix_buf.size - pos;
> -        size_t samples_to_clip = MIN(len, samples_till_end_of_buf);
> -
> -        hw->clip(dst, src, samples_to_clip);
> -
> -        pos = (pos + samples_to_clip) % hw->mix_buf.size;
> -        len -= samples_to_clip;
> -        clipped += samples_to_clip;
> -    }
> -}
> -
> -/*
> - * Soft voice (playback)
> - */
> -static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
> -    size_t frames_in_max, size_t frames_out_max,
> -    size_t *total_in, size_t *total_out)
> -{
> -    HWVoiceOut *hw = sw->hw;
> -    struct st_sample *src, *dst;
> -    size_t live, wpos, frames_in, frames_out;
> -
> -    live = sw->total_hw_samples_mixed;
> -    wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;
> -
> -    /* write to mix_buf from wpos to end of buffer */
> -    src = sw->resample_buf.buffer;
> -    frames_in = frames_in_max;
> -    dst = hw->mix_buf.buffer + wpos;
> -    frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);
> -    st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
> -    wpos += frames_out;
> -    *total_in = frames_in;
> -    *total_out = frames_out;
> -
> -    /* write to mix_buf from start of buffer if there are input frames left */
> -    if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {
> -        src += frames_in;
> -        frames_in = frames_in_max - frames_in;
> -        dst = hw->mix_buf.buffer;
> -        frames_out = frames_out_max - frames_out;
> -        st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
> -        *total_in += frames_in;
> -        *total_out += frames_out;
> -    }
> -}
> -
> -static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
> -{
> -    HWVoiceOut *hw = sw->hw;
> -    size_t live, dead, hw_free, sw_max, fe_max;
> -    size_t frames_in_max, frames_out_max, total_in, total_out;
> -
> -    live = sw->total_hw_samples_mixed;
> -    if (audio_bug(__func__, live > hw->mix_buf.size)) {
> -        dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
> -        return 0;
> -    }
> -
> -    if (live == hw->mix_buf.size) {
> -#ifdef DEBUG_OUT
> -        dolog ("%s is full %zu\n", sw->name, live);
> -#endif
> -        return 0;
> -    }
> -
> -    dead = hw->mix_buf.size - live;
> -    hw_free = audio_pcm_hw_get_free(hw);
> -    hw_free = hw_free > live ? hw_free - live : 0;
> -    frames_out_max = MIN(dead, hw_free);
> -    sw_max = st_rate_frames_in(sw->rate, frames_out_max);
> -    fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,
> -                 sw->resample_buf.size);
> -    frames_in_max = MIN(sw_max, fe_max);
> -
> -    if (!frames_in_max) {
> -        return 0;
> -    }
> -
> -    if (frames_in_max > sw->resample_buf.pos) {
> -        sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,
> -                 buf, frames_in_max - sw->resample_buf.pos);
> -        if (!sw->hw->pcm_ops->volume_out) {
> -            mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,
> -                          frames_in_max - sw->resample_buf.pos, &sw->vol);
> -        }
> -    }
> -
> -    audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,
> -                              &total_in, &total_out);
> -
> -    sw->total_hw_samples_mixed += total_out;
> -    sw->empty = sw->total_hw_samples_mixed == 0;
> -
> -    /*
> -     * Upsampling may leave one audio frame in the resample buffer. Decrement
> -     * total_in by one if there was a leftover frame from the previous resample
> -     * pass in the resample buffer. Increment total_in by one if the current
> -     * resample pass left one frame in the resample buffer.
> -     */
> -    if (frames_in_max - total_in == 1) {
> -        /* copy one leftover audio frame to the beginning of the buffer */
> -        *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);
> -        total_in += 1 - sw->resample_buf.pos;
> -        sw->resample_buf.pos = 1;
> -    } else if (total_in >= sw->resample_buf.pos) {
> -        total_in -= sw->resample_buf.pos;
> -        sw->resample_buf.pos = 0;
> -    }
> -
> -#ifdef DEBUG_OUT
> -    dolog (
> -        "%s: write size %zu written %zu total mixed %zu\n",
> -        SW_NAME(sw),
> -        buf_len / sw->info.bytes_per_frame,
> -        total_in,
> -        sw->total_hw_samples_mixed
> -        );
> -#endif
> -
> -    return total_in * sw->info.bytes_per_frame;
> -}
> -
> -#ifdef DEBUG_AUDIO
> -static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
> -{
> -    dolog("%s: bits %d, sign %d, float %d, freq %d, nchan %d\n",
> -          cap, info->bits, info->is_signed, info->is_float, info->freq,
> -          info->nchannels);
> -}
> -#endif
> -
> -#define DAC
> -#include "audio_template.h"
> -#undef DAC
> -#include "audio_template.h"
> -
> -/*
> - * Timer
> - */
> -static int audio_is_timer_needed(AudioMixengBackend *s)
> -{
> -    HWVoiceIn *hwi = NULL;
> -    HWVoiceOut *hwo = NULL;
> -
> -    while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
> -        if (!hwo->poll_mode) {
> -            return 1;
> -        }
> -    }
> -    while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
> -        if (!hwi->poll_mode) {
> -            return 1;
> -        }
> -    }
> -    return 0;
> -}
> -
> -static void audio_reset_timer(AudioMixengBackend *s)
> -{
> -    if (audio_is_timer_needed(s)) {
> -        timer_mod_anticipate_ns(s->ts,
> -            qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
> -        if (!s->timer_running) {
> -            s->timer_running = true;
> -            s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> -            trace_audio_timer_start(s->period_ticks / SCALE_MS);
> -        }
> -    } else {
> -        timer_del(s->ts);
> -        if (s->timer_running) {
> -            s->timer_running = false;
> -            trace_audio_timer_stop();
> -        }
> -    }
> -}
> -
> -static void audio_timer (void *opaque)
> -{
> -    int64_t now, diff;
> -    AudioMixengBackend *s = opaque;
> -
> -    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> -    diff = now - s->timer_last;
> -    if (diff > s->period_ticks * 3 / 2) {
> -        trace_audio_timer_delayed(diff / SCALE_MS);
> -    }
> -    s->timer_last = now;
> -
> -    audio_run(s, "timer");
> -    audio_reset_timer(s);
> -}
> -
> -/*
> - * Public API
> - */
> -static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw,
> -                                         void *buf, size_t size)
> -{
> -    HWVoiceOut *hw;
> -
> -    if (!sw) {
> -        /* XXX: Consider options */
> -        return size;
> -    }
> -    hw = sw->hw;
> -
> -    if (!hw->enabled) {
> -        dolog("Writing to disabled voice %s\n", SW_NAME(sw));
> -        return 0;
> -    }
> -
> -    if (audio_get_pdo_out(hw->s->dev)->mixing_engine) {
> -        return audio_pcm_sw_write(sw, buf, size);
> -    } else {
> -        return hw->pcm_ops->write(hw, buf, size);
> -    }
> -}
> -
> -static size_t audio_mixeng_backend_read(AudioBackend *be,
> -                                        SWVoiceIn *sw, void *buf, size_t size)
> -{
> -    HWVoiceIn *hw;
> -
> -    if (!sw) {
> -        /* XXX: Consider options */
> -        return size;
> -    }
> -    hw = sw->hw;
> -
> -    if (!hw->enabled) {
> -        dolog("Reading from disabled voice %s\n", SW_NAME(sw));
> -        return 0;
> -    }
> -
> -    if (audio_get_pdo_in(hw->s->dev)->mixing_engine) {
> -        return audio_pcm_sw_read(sw, buf, size);
> -    } else {
> -        return hw->pcm_ops->read(hw, buf, size);
> -    }
> -
> -}
> -
> -static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
> -{
> -    if (!sw) {
> -        return 0;
> -    }
> -
> -    if (audio_get_pdo_out(sw->s->dev)->mixing_engine) {
> -        return sw->resample_buf.size * sw->info.bytes_per_frame;
> -    }
> -
> -    return sw->hw->samples * sw->hw->info.bytes_per_frame;
> -}
> -
> -static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceOut *sw,
> -                                                bool on)
> -{
> -    HWVoiceOut *hw;
> -
> -    if (!sw) {
> -        return;
> -    }
> -
> -    hw = sw->hw;
> -    if (sw->active != on) {
> -        AudioMixengBackend *s = sw->s;
> -        SWVoiceOut *temp_sw;
> -        SWVoiceCap *sc;
> -
> -        if (on) {
> -            hw->pending_disable = 0;
> -            if (!hw->enabled) {
> -                hw->enabled = true;
> -                if (runstate_is_running()) {
> -                    if (hw->pcm_ops->enable_out) {
> -                        hw->pcm_ops->enable_out(hw, true);
> -                    }
> -                    audio_reset_timer (s);
> -                }
> -            }
> -        } else {
> -            if (hw->enabled) {
> -                int nb_active = 0;
> -
> -                for (temp_sw = hw->sw_head.lh_first; temp_sw;
> -                     temp_sw = temp_sw->entries.le_next) {
> -                    nb_active += temp_sw->active != 0;
> -                }
> -
> -                hw->pending_disable = nb_active == 1;
> -            }
> -        }
> -
> -        for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> -            sc->sw.active = hw->enabled;
> -            if (hw->enabled) {
> -                audio_capture_maybe_changed (sc->cap, 1);
> -            }
> -        }
> -        sw->active = on;
> -    }
> -
> -}
> -
> -static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
> -{
> -    HWVoiceIn *hw;
> -
> -    if (!sw) {
> -        return;
> -    }
> -
> -    hw = sw->hw;
> -    if (sw->active != on) {
> -        AudioMixengBackend *s = sw->s;
> -        SWVoiceIn *temp_sw;
> -
> -        if (on) {
> -            if (!hw->enabled) {
> -                hw->enabled = true;
> -                if (runstate_is_running()) {
> -                    if (hw->pcm_ops->enable_in) {
> -                        hw->pcm_ops->enable_in(hw, true);
> -                    }
> -                    audio_reset_timer (s);
> -                }
> -            }
> -            sw->total_hw_samples_acquired = hw->total_samples_captured;
> -        } else {
> -            if (hw->enabled) {
> -                int nb_active = 0;
> -
> -                for (temp_sw = hw->sw_head.lh_first; temp_sw;
> -                     temp_sw = temp_sw->entries.le_next) {
> -                    nb_active += temp_sw->active != 0;
> -                }
> -
> -                if (nb_active == 1) {
> -                    hw->enabled = false;
> -                    if (hw->pcm_ops->enable_in) {
> -                        hw->pcm_ops->enable_in(hw, false);
> -                    }
> -                }
> -            }
> -        }
> -        sw->active = on;
> -    }
> -}
> -
> -static size_t audio_get_avail(SWVoiceIn *sw)
> -{
> -    size_t live;
> -
> -    if (!sw) {
> -        return 0;
> -    }
> -
> -    live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
> -    if (audio_bug(__func__, live > sw->hw->conv_buf.size)) {
> -        dolog("live=%zu sw->hw->conv_buf.size=%zu\n", live,
> -              sw->hw->conv_buf.size);
> -        return 0;
> -    }
> -
> -    ldebug (
> -        "%s: get_avail live %zu frontend frames %u\n",
> -        SW_NAME (sw),
> -        live, st_rate_frames_out(sw->rate, live)
> -        );
> -
> -    return live;
> -}
> -
> -static size_t audio_get_free(SWVoiceOut *sw)
> -{
> -    size_t live, dead;
> -
> -    if (!sw) {
> -        return 0;
> -    }
> -
> -    live = sw->total_hw_samples_mixed;
> -
> -    if (audio_bug(__func__, live > sw->hw->mix_buf.size)) {
> -        dolog("live=%zu sw->hw->mix_buf.size=%zu\n", live,
> -              sw->hw->mix_buf.size);
> -        return 0;
> -    }
> -
> -    dead = sw->hw->mix_buf.size - live;
> -
> -#ifdef DEBUG_OUT
> -    dolog("%s: get_free live %zu dead %zu frontend frames %u\n",
> -          SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead));
> -#endif
> -
> -    return dead;
> -}
> -
> -static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
> -                                        size_t samples)
> -{
> -    size_t n;
> -
> -    if (hw->enabled) {
> -        SWVoiceCap *sc;
> -
> -        for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> -            SWVoiceOut *sw = &sc->sw;
> -            size_t rpos2 = rpos;
> -
> -            n = samples;
> -            while (n) {
> -                size_t till_end_of_hw = hw->mix_buf.size - rpos2;
> -                size_t to_read = MIN(till_end_of_hw, n);
> -                size_t live, frames_in, frames_out;
> -
> -                sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;
> -                sw->resample_buf.size = to_read;
> -                live = sw->total_hw_samples_mixed;
> -
> -                audio_pcm_sw_resample_out(sw,
> -                                          to_read, sw->hw->mix_buf.size - live,
> -                                          &frames_in, &frames_out);
> -
> -                sw->total_hw_samples_mixed += frames_out;
> -                sw->empty = sw->total_hw_samples_mixed == 0;
> -
> -                if (to_read - frames_in) {
> -                    dolog("Could not mix %zu frames into a capture "
> -                          "buffer, mixed %zu\n",
> -                          to_read, frames_in);
> -                    break;
> -                }
> -                n -= to_read;
> -                rpos2 = (rpos2 + to_read) % hw->mix_buf.size;
> -            }
> -        }
> -    }
> -
> -    n = MIN(samples, hw->mix_buf.size - rpos);
> -    mixeng_clear(hw->mix_buf.buffer + rpos, n);
> -    mixeng_clear(hw->mix_buf.buffer, samples - n);
> -}
> -
> -static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
> -{
> -    size_t clipped = 0;
> -
> -    while (live) {
> -        size_t size = live * hw->info.bytes_per_frame;
> -        size_t decr, proc;
> -        void *buf = hw->pcm_ops->get_buffer_out(hw, &size);
> -
> -        if (size == 0) {
> -            break;
> -        }
> -
> -        decr = MIN(size / hw->info.bytes_per_frame, live);
> -        if (buf) {
> -            audio_pcm_hw_clip_out(hw, buf, decr);
> -        }
> -        proc = hw->pcm_ops->put_buffer_out(hw, buf,
> -                                           decr * hw->info.bytes_per_frame) /
> -            hw->info.bytes_per_frame;
> -
> -        live -= proc;
> -        clipped += proc;
> -        hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size;
> -
> -        if (proc == 0 || proc < decr) {
> -            break;
> -        }
> -    }
> -
> -    if (hw->pcm_ops->run_buffer_out) {
> -        hw->pcm_ops->run_buffer_out(hw);
> -    }
> -
> -    return clipped;
> -}
> -
> -static void audio_run_out(AudioMixengBackend *s)
> -{
> -    HWVoiceOut *hw = NULL;
> -    SWVoiceOut *sw;
> -
> -    while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
> -        size_t played, live, prev_rpos;
> -        size_t hw_free = audio_pcm_hw_get_free(hw);
> -        int nb_live;
> -
> -        if (!audio_get_pdo_out(s->dev)->mixing_engine) {
> -            /* there is exactly 1 sw for each hw with no mixeng */
> -            sw = hw->sw_head.lh_first;
> -
> -            if (hw->pending_disable) {
> -                hw->enabled = false;
> -                hw->pending_disable = false;
> -                if (hw->pcm_ops->enable_out) {
> -                    hw->pcm_ops->enable_out(hw, false);
> -                }
> -            }
> -
> -            if (sw->active) {
> -                sw->callback.fn(sw->callback.opaque,
> -                                hw_free * sw->info.bytes_per_frame);
> -            }
> -
> -            if (hw->pcm_ops->run_buffer_out) {
> -                hw->pcm_ops->run_buffer_out(hw);
> -            }
> -
> -            continue;
> -        }
> -
> -        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> -            if (sw->active) {
> -                size_t sw_free = audio_get_free(sw);
> -                size_t free;
> -
> -                if (hw_free > sw->total_hw_samples_mixed) {
> -                    free = st_rate_frames_in(sw->rate,
> -                        MIN(sw_free, hw_free - sw->total_hw_samples_mixed));
> -                } else {
> -                    free = 0;
> -                }
> -                if (free > sw->resample_buf.pos) {
> -                    free = MIN(free, sw->resample_buf.size)
> -                           - sw->resample_buf.pos;
> -                    sw->callback.fn(sw->callback.opaque,
> -                                    free * sw->info.bytes_per_frame);
> -                }
> -            }
> -        }
> -
> -        live = audio_pcm_hw_get_live_out (hw, &nb_live);
> -        if (!nb_live) {
> -            live = 0;
> -        }
> -
> -        if (audio_bug(__func__, live > hw->mix_buf.size)) {
> -            dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
> -            continue;
> -        }
> -
> -        if (hw->pending_disable && !nb_live) {
> -            SWVoiceCap *sc;
> -#ifdef DEBUG_OUT
> -            dolog ("Disabling voice\n");
> -#endif
> -            hw->enabled = false;
> -            hw->pending_disable = false;
> -            if (hw->pcm_ops->enable_out) {
> -                hw->pcm_ops->enable_out(hw, false);
> -            }
> -            for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> -                sc->sw.active = false;
> -                audio_recalc_and_notify_capture (sc->cap);
> -            }
> -            continue;
> -        }
> -
> -        if (!live) {
> -            if (hw->pcm_ops->run_buffer_out) {
> -                hw->pcm_ops->run_buffer_out(hw);
> -            }
> -            continue;
> -        }
> -
> -        prev_rpos = hw->mix_buf.pos;
> -        played = audio_pcm_hw_run_out(hw, live);
> -        replay_audio_out(&played);
> -        if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) {
> -            dolog("hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\n",
> -                  hw->mix_buf.pos, hw->mix_buf.size, played);
> -            hw->mix_buf.pos = 0;
> -        }
> -
> -#ifdef DEBUG_OUT
> -        dolog("played=%zu\n", played);
> -#endif
> -
> -        if (played) {
> -            audio_capture_mix_and_clear (hw, prev_rpos, played);
> -        }
> -
> -        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> -            if (!sw->active && sw->empty) {
> -                continue;
> -            }
> -
> -            if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) {
> -                dolog("played=%zu sw->total_hw_samples_mixed=%zu\n",
> -                      played, sw->total_hw_samples_mixed);
> -                played = sw->total_hw_samples_mixed;
> -            }
> -
> -            sw->total_hw_samples_mixed -= played;
> -
> -            if (!sw->total_hw_samples_mixed) {
> -                sw->empty = true;
> -            }
> -        }
> -    }
> -}
> -
> -static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
> -{
> -    size_t conv = 0;
> -
> -    if (hw->pcm_ops->run_buffer_in) {
> -        hw->pcm_ops->run_buffer_in(hw);
> -    }
> -
> -    while (samples) {
> -        size_t proc;
> -        size_t size = samples * hw->info.bytes_per_frame;
> -        void *buf = hw->pcm_ops->get_buffer_in(hw, &size);
> -
> -        assert(size % hw->info.bytes_per_frame == 0);
> -        if (size == 0) {
> -            break;
> -        }
> -
> -        proc = audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_frame);
> -
> -        samples -= proc;
> -        conv += proc;
> -        hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame);
> -    }
> -
> -    return conv;
> -}
> -
> -static void audio_run_in(AudioMixengBackend *s)
> -{
> -    HWVoiceIn *hw = NULL;
> -
> -    if (!audio_get_pdo_in(s->dev)->mixing_engine) {
> -        while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
> -            /* there is exactly 1 sw for each hw with no mixeng */
> -            SWVoiceIn *sw = hw->sw_head.lh_first;
> -            if (sw->active) {
> -                sw->callback.fn(sw->callback.opaque, INT_MAX);
> -            }
> -        }
> -        return;
> -    }
> -
> -    while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
> -        SWVoiceIn *sw;
> -        size_t captured = 0, min;
> -        int pos;
> -
> -        if (replay_mode != REPLAY_MODE_PLAY) {
> -            captured = audio_pcm_hw_run_in(
> -                hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw));
> -        }
> -
> -        replay_audio_in_start(&captured);
> -        assert(captured <= hw->conv_buf.size);
> -        if (replay_mode == REPLAY_MODE_PLAY) {
> -            hw->conv_buf.pos = (hw->conv_buf.pos + captured) % hw->conv_buf.size;
> -        }
> -        for (pos = (hw->conv_buf.pos - captured + hw->conv_buf.size) % hw->conv_buf.size;
> -             pos != hw->conv_buf.pos;
> -             pos = (pos + 1) % hw->conv_buf.size) {
> -                uint64_t left, right;
> -
> -                if (replay_mode == REPLAY_MODE_RECORD) {
> -                    audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left, &right);
> -                }
> -                replay_audio_in_sample_lr(&left, &right);
> -                if (replay_mode == REPLAY_MODE_PLAY) {
> -                    audio_sample_from_uint64(hw->conv_buf.buffer, pos, left, right);
> -                }
> -        }
> -        replay_audio_in_finish();
> -
> -        min = audio_pcm_hw_find_min_in (hw);
> -        hw->total_samples_captured += captured - min;
> -
> -        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> -            sw->total_hw_samples_acquired -= min;
> -
> -            if (sw->active) {
> -                size_t sw_avail = audio_get_avail(sw);
> -                size_t avail;
> -
> -                avail = st_rate_frames_out(sw->rate, sw_avail);
> -                if (avail > 0) {
> -                    avail = MIN(avail, sw->resample_buf.size);
> -                    sw->callback.fn(sw->callback.opaque,
> -                                    avail * sw->info.bytes_per_frame);
> -                }
> -            }
> -        }
> -    }
> -}
> -
> -static void audio_run_capture(AudioMixengBackend *s)
> -{
> -    CaptureVoiceOut *cap;
> -
> -    for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
> -        size_t live, rpos, captured;
> -        HWVoiceOut *hw = &cap->hw;
> -        SWVoiceOut *sw;
> -
> -        captured = live = audio_pcm_hw_get_live_out (hw, NULL);
> -        rpos = hw->mix_buf.pos;
> -        while (live) {
> -            size_t left = hw->mix_buf.size - rpos;
> -            size_t to_capture = MIN(live, left);
> -            struct st_sample *src;
> -            struct capture_callback *cb;
> -
> -            src = hw->mix_buf.buffer + rpos;
> -            hw->clip (cap->buf, src, to_capture);
> -            mixeng_clear (src, to_capture);
> -
> -            for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> -                cb->ops.capture (cb->opaque, cap->buf,
> -                                 to_capture * hw->info.bytes_per_frame);
> -            }
> -            rpos = (rpos + to_capture) % hw->mix_buf.size;
> -            live -= to_capture;
> -        }
> -        hw->mix_buf.pos = rpos;
> -
> -        for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> -            if (!sw->active && sw->empty) {
> -                continue;
> -            }
> -
> -            if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)) {
> -                dolog("captured=%zu sw->total_hw_samples_mixed=%zu\n",
> -                      captured, sw->total_hw_samples_mixed);
> -                captured = sw->total_hw_samples_mixed;
> -            }
> -
> -            sw->total_hw_samples_mixed -= captured;
> -            sw->empty = sw->total_hw_samples_mixed == 0;
> -        }
> -    }
> -}
> -
> -void audio_run(AudioMixengBackend *s, const char *msg)
> -{
> -    audio_run_out(s);
> -    audio_run_in(s);
> -    audio_run_capture(s);
> -
> -#ifdef DEBUG_POLL
> -    {
> -        static double prevtime;
> -        double currtime;
> -        struct timeval tv;
> -
> -        if (gettimeofday (&tv, NULL)) {
> -            perror ("audio_run: gettimeofday");
> -            return;
> -        }
> -
> -        currtime = tv.tv_sec + tv.tv_usec * 1e-6;
> -        dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime);
> -        prevtime = currtime;
> -    }
> -#endif
> -}
> -
> -void audio_generic_run_buffer_in(HWVoiceIn *hw)
> -{
> -    if (unlikely(!hw->buf_emul)) {
> -        hw->size_emul = hw->samples * hw->info.bytes_per_frame;
> -        hw->buf_emul = g_malloc(hw->size_emul);
> -        hw->pos_emul = hw->pending_emul = 0;
> -    }
> -
> -    while (hw->pending_emul < hw->size_emul) {
> -        size_t read_len = MIN(hw->size_emul - hw->pos_emul,
> -                              hw->size_emul - hw->pending_emul);
> -        size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul,
> -                                        read_len);
> -        hw->pending_emul += read;
> -        hw->pos_emul = (hw->pos_emul + read) % hw->size_emul;
> -        if (read < read_len) {
> -            break;
> -        }
> -    }
> -}
> -
> -void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)
> -{
> -    size_t start;
> -
> -    start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
> -    assert(start < hw->size_emul);
> -
> -    *size = MIN(*size, hw->pending_emul);
> -    *size = MIN(*size, hw->size_emul - start);
> -    return hw->buf_emul + start;
> -}
> -
> -void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
> -{
> -    assert(size <= hw->pending_emul);
> -    hw->pending_emul -= size;
> -}
> -
> -size_t audio_generic_buffer_get_free(HWVoiceOut *hw)
> -{
> -    if (hw->buf_emul) {
> -        return hw->size_emul - hw->pending_emul;
> -    } else {
> -        return hw->samples * hw->info.bytes_per_frame;
> -    }
> -}
> -
> -void audio_generic_run_buffer_out(HWVoiceOut *hw)
> -{
> -    while (hw->pending_emul) {
> -        size_t write_len, written, start;
> -
> -        start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
> -        assert(start < hw->size_emul);
> -
> -        write_len = MIN(hw->pending_emul, hw->size_emul - start);
> -
> -        written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len);
> -        hw->pending_emul -= written;
> -
> -        if (written < write_len) {
> -            break;
> -        }
> -    }
> -}
> -
> -void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)
> -{
> -    if (unlikely(!hw->buf_emul)) {
> -        hw->size_emul = hw->samples * hw->info.bytes_per_frame;
> -        hw->buf_emul = g_malloc(hw->size_emul);
> -        hw->pos_emul = hw->pending_emul = 0;
> -    }
> -
> -    *size = MIN(hw->size_emul - hw->pending_emul,
> -                hw->size_emul - hw->pos_emul);
> -    return hw->buf_emul + hw->pos_emul;
> -}
> -
> -size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
> -{
> -    assert(buf == hw->buf_emul + hw->pos_emul &&
> -           size + hw->pending_emul <= hw->size_emul);
> -
> -    hw->pending_emul += size;
> -    hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;
> -
> -    return size;
> -}
> -
> -size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size)
> -{
> -    size_t total = 0;
> -
> -    if (hw->pcm_ops->buffer_get_free) {
> -        size_t free = hw->pcm_ops->buffer_get_free(hw);
> -
> -        size = MIN(size, free);
> -    }
> -
> -    while (total < size) {
> -        size_t dst_size = size - total;
> -        size_t copy_size, proc;
> -        void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size);
> -
> -        if (dst_size == 0) {
> -            break;
> -        }
> -
> -        copy_size = MIN(size - total, dst_size);
> -        if (dst) {
> -            memcpy(dst, (char *)buf + total, copy_size);
> -        }
> -        proc = hw->pcm_ops->put_buffer_out(hw, dst, copy_size);
> -        total += proc;
> -
> -        if (proc == 0 || proc < copy_size) {
> -            break;
> -        }
> -    }
> -
> -    return total;
> -}
> -
> -size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
> -{
> -    size_t total = 0;
> -
> -    if (hw->pcm_ops->run_buffer_in) {
> -        hw->pcm_ops->run_buffer_in(hw);
> -    }
> -
> -    while (total < size) {
> -        size_t src_size = size - total;
> -        void *src = hw->pcm_ops->get_buffer_in(hw, &src_size);
> -
> -        if (src_size == 0) {
> -            break;
> -        }
> -
> -        memcpy((char *)buf + total, src, src_size);
> -        hw->pcm_ops->put_buffer_in(hw, src, src_size);
> -        total += src_size;
> -    }
> -
> -    return total;
> -}
> -
> -static bool audio_mixeng_backend_realize(AudioBackend *abe,
> -                                         Audiodev *dev, Error **errp)
> -{
> -    AudioMixengBackend *be = AUDIO_MIXENG_BACKEND(abe);
> -    audio_driver *drv = AUDIO_MIXENG_BACKEND_GET_CLASS(be)->driver;
> -
> -    be->dev = dev;
> -    be->drv_opaque = drv->init(be->dev, errp);
> -    if (!be->drv_opaque) {
> -        return false;
> -    }
> -
> -    if (!drv->pcm_ops->get_buffer_in) {
> -        drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in;
> -        drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in;
> -    }
> -    if (!drv->pcm_ops->get_buffer_out) {
> -        drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out;
> -        drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;
> -    }
> -
> -    audio_init_nb_voices_out(be, drv, 1);
> -    audio_init_nb_voices_in(be, drv, 0);
> -    be->drv = drv;
> -
> -    if (be->dev->timer_period <= 0) {
> -        be->period_ticks = 1;
> -    } else {
> -        be->period_ticks = be->dev->timer_period * (int64_t)SCALE_US;
> -    }
> -
> -    return true;
> -}
> -
> -static void audio_vm_change_state_handler (void *opaque, bool running,
> -                                           RunState state)
> -{
> -    AudioMixengBackend *s = opaque;
> -    HWVoiceOut *hwo = NULL;
> -    HWVoiceIn *hwi = NULL;
> -
> -    while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
> -        if (hwo->pcm_ops->enable_out) {
> -            hwo->pcm_ops->enable_out(hwo, running);
> -        }
> -    }
> -
> -    while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
> -        if (hwi->pcm_ops->enable_in) {
> -            hwi->pcm_ops->enable_in(hwi, running);
> -        }
> -    }
> -    audio_reset_timer (s);
> -}
> -
> -static const VMStateDescription vmstate_audio;
> -
> -static const char *audio_mixeng_backend_get_id(AudioBackend *be)
> -{
> -    return AUDIO_MIXENG_BACKEND(be)->dev->id;
> -}
> -
> -static CaptureVoiceOut *audio_mixeng_backend_add_capture(
> -    AudioBackend *be,
> -    struct audsettings *as,
> -    struct audio_capture_ops *ops,
> -    void *cb_opaque);
> -
> -static void audio_mixeng_backend_del_capture(
> -    AudioBackend *be,
> -    CaptureVoiceOut *cap,
> -    void *cb_opaque);
> -
> -static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
> -                                                Volume *vol);
> -static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
> -                                               Volume *vol);
> -
> -static void audio_mixeng_backend_class_init(ObjectClass *klass, const void *data)
> -{
> -    AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
> -
> -    be->realize = audio_mixeng_backend_realize;
> -    be->get_id = audio_mixeng_backend_get_id;
> -    be->open_in = audio_mixeng_backend_open_in;
> -    be->open_out = audio_mixeng_backend_open_out;
> -    be->close_in = audio_mixeng_backend_close_in;
> -    be->close_out = audio_mixeng_backend_close_out;
> -    be->is_active_out = audio_mixeng_backend_is_active_out;
> -    be->is_active_in = audio_mixeng_backend_is_active_in;
> -    be->set_active_out = audio_mixeng_backend_set_active_out;
> -    be->set_active_in = audio_mixeng_backend_set_active_in;
> -    be->set_volume_out = audio_mixeng_backend_set_volume_out;
> -    be->set_volume_in = audio_mixeng_backend_set_volume_in;
> -    be->read = audio_mixeng_backend_read;
> -    be->write = audio_mixeng_backend_write;
> -    be->get_buffer_size_out = audio_mixeng_backend_get_buffer_size_out;
> -    be->add_capture = audio_mixeng_backend_add_capture;
> -    be->del_capture = audio_mixeng_backend_del_capture;
> -}
> -
> -static void audio_mixeng_backend_init(Object *obj)
> -{
> -    AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
> -
> -    QLIST_INIT(&s->hw_head_out);
> -    QLIST_INIT(&s->hw_head_in);
> -    QLIST_INIT(&s->cap_head);
> -    s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
> -
> -    s->vmse = qemu_add_vm_change_state_handler(audio_vm_change_state_handler, s);
> -    assert(s->vmse != NULL);
> -
> -    vmstate_register_any(NULL, &vmstate_audio, s);
> -}
> -
> -static void audio_mixeng_backend_finalize(Object *obj)
> -{
> -    AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
> -    HWVoiceOut *hwo, *hwon;
> -    HWVoiceIn *hwi, *hwin;
> -
> -    QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) {
> -        SWVoiceCap *sc;
> -
> -        if (hwo->enabled && hwo->pcm_ops->enable_out) {
> -            hwo->pcm_ops->enable_out(hwo, false);
> -        }
> -        hwo->pcm_ops->fini_out (hwo);
> +/* SPDX-License-Identifier: MIT */
>   
> -        for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> -            CaptureVoiceOut *cap = sc->cap;
> -            struct capture_callback *cb;
> +#include "qemu/osdep.h"
> +#include "qemu/audio.h"
> +#include "qemu/help_option.h"
> +#include "qapi/clone-visitor.h"
> +#include "qapi/qobject-input-visitor.h"
> +#include "qapi/qapi-visit-audio.h"
> +#include "qapi/qapi-commands-audio.h"
> +#include "qobject/qdict.h"
> +#include "system/system.h"
>   
> -            for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> -                cb->ops.destroy (cb->opaque);
> -            }
> -        }
> -        QLIST_REMOVE(hwo, entries);
> -    }
> +/* Order of CONFIG_AUDIO_DRIVERS is import.
> +   The 1st one is the one used by default, that is the reason
> +    that we generate the list.
> +*/
> +const char *audio_prio_list[] = {
> +#ifdef CONFIG_GIO
> +    "dbus",
> +#endif
> +    "spice",
> +    CONFIG_AUDIO_DRIVERS
> +    "none",
> +    NULL
> +};
>   
> -    QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) {
> -        if (hwi->enabled && hwi->pcm_ops->enable_in) {
> -            hwi->pcm_ops->enable_in(hwi, false);
> -        }
> -        hwi->pcm_ops->fini_in (hwi);
> -        QLIST_REMOVE(hwi, entries);
> -    }
> +typedef struct AudiodevListEntry {
> +    Audiodev *dev;
> +    QSIMPLEQ_ENTRY(AudiodevListEntry) next;
> +} AudiodevListEntry;
>   
> -    if (s->drv) {
> -        s->drv->fini (s->drv_opaque);
> -        s->drv = NULL;
> -    }
> +typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;
>   
> -    if (s->dev) {
> -        qapi_free_Audiodev(s->dev);
> -        s->dev = NULL;
> -    }
> +static AudiodevListHead audiodevs =
> +    QSIMPLEQ_HEAD_INITIALIZER(audiodevs);
> +static AudiodevListHead default_audiodevs =
> +    QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);
>   
> -    if (s->ts) {
> -        timer_free(s->ts);
> -        s->ts = NULL;
> -    }
> +static AudioBackendClass *audio_be_class_by_name(const char *name)
> +{
> +    g_autofree char *tname = g_strconcat("audio-", name, NULL);
> +    ObjectClass *oc = module_object_class_by_name(tname);
>   
> -    if (s->vmse) {
> -        qemu_del_vm_change_state_handler(s->vmse);
> -        s->vmse = NULL;
> +    if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) {
> +        return NULL;
>       }
>   
> -    vmstate_unregister(NULL, &vmstate_audio, s);
> +    return AUDIO_BACKEND_CLASS(oc);
>   }
>   
> +static AudioBackend *default_audio_be;
> +
>   static Object *get_audiodevs_root(void)
>   {
>       return object_get_container("audiodevs");
> @@ -1757,25 +62,6 @@ void audio_cleanup(void)
>       object_unparent(get_audiodevs_root());
>   }
>   
> -static bool vmstate_audio_needed(void *opaque)
> -{
> -    /*
> -     * Never needed, this vmstate only exists in case
> -     * an old qemu sends it to us.
> -     */
> -    return false;
> -}
> -
> -static const VMStateDescription vmstate_audio = {
> -    .name = "audio",
> -    .version_id = 1,
> -    .minimum_version_id = 1,
> -    .needed = vmstate_audio_needed,
> -    .fields = (const VMStateField[]) {
> -        VMSTATE_END_OF_LIST()
> -    }
> -};
> -
>   void audio_create_default_audiodevs(void)
>   {
>       for (int i = 0; audio_prio_list[i]; i++) {
> @@ -1816,15 +102,13 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
>           assert(!default_audio_be);
>           for (;;) {
>               AudiodevListEntry *e = QSIMPLEQ_FIRST(&default_audiodevs);
> -
>               if (!e) {
>                   error_setg(errp, "no default audio driver available");
>                   return NULL;
>               }
> -            dev = e->dev;
>               QSIMPLEQ_REMOVE_HEAD(&default_audiodevs, next);
> +            be = audio_be_new(e->dev, NULL);
>               g_free(e);
> -            be = audio_be_new(dev, NULL);
>               if (be) {
>                   break;
>               }
> @@ -1855,156 +139,35 @@ AudioBackend *audio_get_default_audio_be(Error **errp)
>       return default_audio_be;
>   }
>   
> -static struct audio_pcm_ops capture_pcm_ops;
> -
> -static CaptureVoiceOut *audio_mixeng_backend_add_capture(
> -    AudioBackend *be,
> -    struct audsettings *as,
> -    struct audio_capture_ops *ops,
> -    void *cb_opaque)
> -{
> -    AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(be);
> -    CaptureVoiceOut *cap;
> -    struct capture_callback *cb;
> -
> -    if (!s) {
> -        error_report("Capturing without setting an audiodev is not supported");
> -        abort();
> -    }
> -
> -    if (!audio_get_pdo_out(s->dev)->mixing_engine) {
> -        dolog("Can't capture with mixeng disabled\n");
> -        return NULL;
> -    }
> -
> -    if (audio_validate_settings (as)) {
> -        dolog ("Invalid settings were passed when trying to add capture\n");
> -        audio_print_settings (as);
> -        return NULL;
> -    }
> -
> -    cb = g_malloc0(sizeof(*cb));
> -    cb->ops = *ops;
> -    cb->opaque = cb_opaque;
> -
> -    cap = audio_pcm_capture_find_specific(s, as);
> -    if (cap) {
> -        QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
> -    } else {
> -        HWVoiceOut *hw;
> -
> -        cap = g_malloc0(sizeof(*cap));
> -
> -        hw = &cap->hw;
> -        hw->s = s;
> -        hw->pcm_ops = &capture_pcm_ops;
> -        QLIST_INIT (&hw->sw_head);
> -        QLIST_INIT (&cap->cb_head);
> -
> -        /* XXX find a more elegant way */
> -        hw->samples = 4096 * 4;
> -        audio_pcm_hw_alloc_resources_out(hw);
> -
> -        audio_pcm_init_info (&hw->info, as);
> -
> -        cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame);
> -
> -        if (hw->info.is_float) {
> -            hw->clip = mixeng_clip_float[hw->info.nchannels == 2]
> -                [hw->info.swap_endianness];
> -        } else {
> -            hw->clip = mixeng_clip
> -                [hw->info.nchannels == 2]
> -                [hw->info.is_signed]
> -                [hw->info.swap_endianness]
> -                [audio_bits_to_index(hw->info.bits)];
> -        }
> -
> -        QLIST_INSERT_HEAD (&s->cap_head, cap, entries);
> -        QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
> -
> -        QLIST_FOREACH(hw, &s->hw_head_out, entries) {
> -            audio_attach_capture (hw);
> -        }
> -    }
> -
> -    return cap;
> -}
> -
> -static void audio_mixeng_backend_del_capture(
> -    AudioBackend *be,
> -    CaptureVoiceOut *cap,
> -    void *cb_opaque)
> +void audio_help(void)
>   {
> -    struct capture_callback *cb;
> -
> -    for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> -        if (cb->opaque == cb_opaque) {
> -            cb->ops.destroy (cb_opaque);
> -            QLIST_REMOVE (cb, entries);
> -            g_free (cb);
> +    int i;
>   
> -            if (!cap->cb_head.lh_first) {
> -                SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1;
> +    printf("Available audio drivers:\n");
>   
> -                while (sw) {
> -                    SWVoiceCap *sc = (SWVoiceCap *) sw;
> -#ifdef DEBUG_CAPTURE
> -                    dolog ("freeing %s\n", sw->name);
> -#endif
> +    for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) {
> +        const char *name = AudiodevDriver_str(i);
> +        AudioBackendClass *be = audio_be_class_by_name(name);
>   
> -                    sw1 = sw->entries.le_next;
> -                    if (sw->rate) {
> -                        st_rate_stop (sw->rate);
> -                        sw->rate = NULL;
> -                    }
> -                    QLIST_REMOVE (sw, entries);
> -                    QLIST_REMOVE (sc, entries);
> -                    g_free (sc);
> -                    sw = sw1;
> -                }
> -                QLIST_REMOVE (cap, entries);
> -                g_free(cap->hw.mix_buf.buffer);
> -                g_free (cap->buf);
> -                g_free (cap);
> -            }
> -            return;
> +        if (be) {
> +            printf("%s\n", name);
>           }
>       }
>   }
>   
> -static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
> -                                                Volume *vol)
> +void audio_parse_option(const char *opt)
>   {
> -    if (sw) {
> -        HWVoiceOut *hw = sw->hw;
> -
> -        sw->vol.mute = vol->mute;
> -        sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
> -        sw->vol.r = nominal_volume.l * vol->vol[vol->channels > 1 ? 1 : 0] /
> -            255;
> +    Audiodev *dev = NULL;
>   
> -        if (hw->pcm_ops->volume_out) {
> -            hw->pcm_ops->volume_out(hw, vol);
> -        }
> +    if (is_help_option(opt)) {
> +        audio_help();
> +        exit(EXIT_SUCCESS);
>       }
> -}
> -
> -static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
> -                                               Volume *vol)
> -{
> -    if (sw) {
> -        HWVoiceIn *hw = sw->hw;
> -
> -        sw->vol.mute = vol->mute;
> -        sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
> -        sw->vol.r = nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : 0] /
> -            255;
> +    Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
> +    visit_type_Audiodev(v, NULL, &dev, &error_fatal);
> +    visit_free(v);
>   
> -        if (hw->pcm_ops->volume_in) {
> -            hw->pcm_ops->volume_in(hw, vol);
> -        }
> -    }
> +    audio_add_audiodev(dev);
>   }
>   
>   static void audio_create_pdos(Audiodev *dev)
> @@ -2103,6 +266,124 @@ static void audio_validate_per_direction_opts(
>       }
>   }
>   
> +static AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev)
> +{
> +    switch (dev->driver) {
> +    case AUDIODEV_DRIVER_NONE:
> +        return dev->u.none.out;
> +#ifdef CONFIG_AUDIO_ALSA
> +    case AUDIODEV_DRIVER_ALSA:
> +        return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.out);
> +#endif
> +#ifdef CONFIG_AUDIO_COREAUDIO
> +    case AUDIODEV_DRIVER_COREAUDIO:
> +        return qapi_AudiodevCoreaudioPerDirectionOptions_base(
> +            dev->u.coreaudio.out);
> +#endif
> +#ifdef CONFIG_DBUS_DISPLAY
> +    case AUDIODEV_DRIVER_DBUS:
> +        return dev->u.dbus.out;
> +#endif
> +#ifdef CONFIG_AUDIO_DSOUND
> +    case AUDIODEV_DRIVER_DSOUND:
> +        return dev->u.dsound.out;
> +#endif
> +#ifdef CONFIG_AUDIO_JACK
> +    case AUDIODEV_DRIVER_JACK:
> +        return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.out);
> +#endif
> +#ifdef CONFIG_AUDIO_OSS
> +    case AUDIODEV_DRIVER_OSS:
> +        return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.out);
> +#endif
> +#ifdef CONFIG_AUDIO_PA
> +    case AUDIODEV_DRIVER_PA:
> +        return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.out);
> +#endif
> +#ifdef CONFIG_AUDIO_PIPEWIRE
> +    case AUDIODEV_DRIVER_PIPEWIRE:
> +        return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.out);
> +#endif
> +#ifdef CONFIG_AUDIO_SDL
> +    case AUDIODEV_DRIVER_SDL:
> +        return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.out);
> +#endif
> +#ifdef CONFIG_AUDIO_SNDIO
> +    case AUDIODEV_DRIVER_SNDIO:
> +        return dev->u.sndio.out;
> +#endif
> +#ifdef CONFIG_SPICE
> +    case AUDIODEV_DRIVER_SPICE:
> +        return dev->u.spice.out;
> +#endif
> +    case AUDIODEV_DRIVER_WAV:
> +        return dev->u.wav.out;
> +
> +    case AUDIODEV_DRIVER__MAX:
> +        break;
> +    }
> +    abort();
> +}
> +
> +static AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev)
> +{
> +    switch (dev->driver) {
> +    case AUDIODEV_DRIVER_NONE:
> +        return dev->u.none.in;
> +#ifdef CONFIG_AUDIO_ALSA
> +    case AUDIODEV_DRIVER_ALSA:
> +        return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.in);
> +#endif
> +#ifdef CONFIG_AUDIO_COREAUDIO
> +    case AUDIODEV_DRIVER_COREAUDIO:
> +        return qapi_AudiodevCoreaudioPerDirectionOptions_base(
> +            dev->u.coreaudio.in);
> +#endif
> +#ifdef CONFIG_DBUS_DISPLAY
> +    case AUDIODEV_DRIVER_DBUS:
> +        return dev->u.dbus.in;
> +#endif
> +#ifdef CONFIG_AUDIO_DSOUND
> +    case AUDIODEV_DRIVER_DSOUND:
> +        return dev->u.dsound.in;
> +#endif
> +#ifdef CONFIG_AUDIO_JACK
> +    case AUDIODEV_DRIVER_JACK:
> +        return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.in);
> +#endif
> +#ifdef CONFIG_AUDIO_OSS
> +    case AUDIODEV_DRIVER_OSS:
> +        return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.in);
> +#endif
> +#ifdef CONFIG_AUDIO_PA
> +    case AUDIODEV_DRIVER_PA:
> +        return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.in);
> +#endif
> +#ifdef CONFIG_AUDIO_PIPEWIRE
> +    case AUDIODEV_DRIVER_PIPEWIRE:
> +        return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.in);
> +#endif
> +#ifdef CONFIG_AUDIO_SDL
> +    case AUDIODEV_DRIVER_SDL:
> +        return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.in);
> +#endif
> +#ifdef CONFIG_AUDIO_SNDIO
> +    case AUDIODEV_DRIVER_SNDIO:
> +        return dev->u.sndio.in;
> +#endif
> +#ifdef CONFIG_SPICE
> +    case AUDIODEV_DRIVER_SPICE:
> +        return dev->u.spice.in;
> +#endif
> +    case AUDIODEV_DRIVER_WAV:
> +        return dev->u.wav.in;
> +
> +    case AUDIODEV_DRIVER__MAX:
> +        break;
> +    }
> +    abort();
> +}
> +
>   static void audio_validate_opts(Audiodev *dev, Error **errp)
>   {
>       Error *err = NULL;
> @@ -2127,37 +408,6 @@ static void audio_validate_opts(Audiodev *dev, Error **errp)
>       }
>   }
>   
> -void audio_help(void)
> -{
> -    int i;
> -
> -    printf("Available audio drivers:\n");
> -
> -    for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) {
> -        const char *name = AudiodevDriver_str(i);
> -        AudioBackendClass *be = audio_be_class_by_name(name);
> -
> -        if (be) {
> -            printf("%s\n", name);
> -        }
> -    }
> -}
> -
> -void audio_parse_option(const char *opt)
> -{
> -    Audiodev *dev = NULL;
> -
> -    if (is_help_option(opt)) {
> -        audio_help();
> -        exit(EXIT_SUCCESS);
> -    }
> -    Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
> -    visit_type_Audiodev(v, NULL, &dev, &error_fatal);
> -    visit_free(v);
> -
> -    audio_add_audiodev(dev);
> -}
> -
>   void audio_add_audiodev(Audiodev *dev)
>   {
>       AudiodevListEntry *e;
> @@ -2189,65 +439,6 @@ void audio_init_audiodevs(void)
>       }
>   }
>   
> -audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo)
> -{
> -    return (audsettings) {
> -        .freq = pdo->frequency,
> -        .nchannels = pdo->channels,
> -        .fmt = pdo->format,
> -        .endianness = HOST_BIG_ENDIAN,
> -    };
> -}
> -
> -int audioformat_bytes_per_sample(AudioFormat fmt)
> -{
> -    switch (fmt) {
> -    case AUDIO_FORMAT_U8:
> -    case AUDIO_FORMAT_S8:
> -        return 1;
> -
> -    case AUDIO_FORMAT_U16:
> -    case AUDIO_FORMAT_S16:
> -        return 2;
> -
> -    case AUDIO_FORMAT_U32:
> -    case AUDIO_FORMAT_S32:
> -    case AUDIO_FORMAT_F32:
> -        return 4;
> -
> -    case AUDIO_FORMAT__MAX:
> -        ;
> -    }
> -    abort();
> -}
> -
> -
> -/* frames = freq * usec / 1e6 */
> -int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,
> -                        audsettings *as, int def_usecs)
> -{
> -    uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs;
> -    return (as->freq * usecs + 500000) / 1000000;
> -}
> -
> -/* samples = channels * frames = channels * freq * usec / 1e6 */
> -int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,
> -                         audsettings *as, int def_usecs)
> -{
> -    return as->nchannels * audio_buffer_frames(pdo, as, def_usecs);
> -}
> -
> -/*
> - * bytes = bytes_per_sample * samples =
> - *     bytes_per_sample * channels * freq * usec / 1e6
> - */
> -int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,
> -                       audsettings *as, int def_usecs)
> -{
> -    return audio_buffer_samples(pdo, as, def_usecs) *
> -        audioformat_bytes_per_sample(as->fmt);
> -}
> -
>   AudioBackend *audio_be_by_name(const char *name, Error **errp)
>   {
>       Object *obj = object_resolve_path_component(get_audiodevs_root(), name);
> @@ -2268,51 +459,6 @@ const char *audio_application_name(void)
>       return vm_name ? vm_name : "qemu";
>   }
>   
> -void audio_rate_start(RateCtl *rate)
> -{
> -    memset(rate, 0, sizeof(RateCtl));
> -    rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> -}
> -
> -size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info)
> -{
> -    int64_t now;
> -    int64_t ticks;
> -    int64_t bytes;
> -    int64_t frames;
> -
> -    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> -    ticks = now - rate->start_ticks;
> -    bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);
> -    frames = (bytes - rate->bytes_sent) / info->bytes_per_frame;
> -    rate->peeked_frames = frames;
> -
> -    return frames < 0 ? 0 : frames * info->bytes_per_frame;
> -}
> -
> -void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used)
> -{
> -    if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) {
> -        AUD_log(NULL, "Resetting rate control (%" PRId64 " frames)\n",
> -                rate->peeked_frames);
> -        audio_rate_start(rate);
> -    }
> -
> -    rate->bytes_sent += bytes_used;
> -}
> -
> -size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info,
> -                            size_t bytes_avail)
> -{
> -    size_t bytes;
> -
> -    bytes = audio_rate_peek_bytes(rate, info);
> -    bytes = MIN(bytes, bytes_avail);
> -    audio_rate_add_bytes(rate, bytes);
> -
> -    return bytes;
> -}
> -
>   AudiodevList *qmp_query_audiodevs(Error **errp)
>   {
>       AudiodevList *ret = NULL;
> @@ -2322,21 +468,3 @@ AudiodevList *qmp_query_audiodevs(Error **errp)
>       }
>       return ret;
>   }
> -
> -static const TypeInfo audio_mixeng_backend_info = {
> -    .name = TYPE_AUDIO_MIXENG_BACKEND,
> -    .parent = TYPE_AUDIO_BACKEND,
> -    .instance_size = sizeof(AudioMixengBackend),
> -    .instance_init = audio_mixeng_backend_init,
> -    .instance_finalize = audio_mixeng_backend_finalize,
> -    .abstract = false,
> -    .class_size = sizeof(AudioMixengBackendClass),
> -    .class_init = audio_mixeng_backend_class_init,
> -};
> -
> -static void register_types(void)
> -{
> -    type_register_static(&audio_mixeng_backend_info);
> -}
> -
> -type_init(register_types);
> diff --git a/audio/meson.build b/audio/meson.build
> index 417670bd4c7..d2ff49280ba 100644
> --- a/audio/meson.build
> +++ b/audio/meson.build
> @@ -2,6 +2,7 @@ audio_ss = ss.source_set()
>   audio_ss.add(files(
>     'audio.c',
>     'audio-be.c',
> +  'audio-mixeng-be.c',
>     'mixeng.c',
>     'noaudio.c',
>     'wavaudio.c',

Reviewed-by: Mark Cave-Ayland <mark.caveayland@nutanix.com>


ATB,

Mark.