From: Marc-André Lureau <[email protected]>

Allow to build the audio base infra without the rest of QEMU
resampling/mixing/queuing code.

Signed-off-by: Marc-André Lureau <[email protected]>
---
 ...dio_template.h => audio-driver_template.h} |    0
 audio/audio_int.h                             |    2 -
 include/qemu/audio.h                          |    2 +
 audio/audio-driver.c                          | 1988 +++++++++++++++
 audio/audio.c                                 | 2218 ++---------------
 audio/meson.build                             |    1 +
 6 files changed, 2167 insertions(+), 2044 deletions(-)
 rename audio/{audio_template.h => audio-driver_template.h} (100%)
 create mode 100644 audio/audio-driver.c

diff --git a/audio/audio_template.h b/audio/audio-driver_template.h
similarity index 100%
rename from audio/audio_template.h
rename to audio/audio-driver_template.h
diff --git a/audio/audio_int.h b/audio/audio_int.h
index 79e54fd60a..e19ca96b01 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -273,8 +273,6 @@ int audio_bug (const char *funcname, int cond);
 
 void audio_run(AudioDriver *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 1794702c30..414dd89f9c 100644
--- a/include/qemu/audio.h
+++ b/include/qemu/audio.h
@@ -188,6 +188,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-driver.c b/audio/audio-driver.c
new file mode 100644
index 0000000000..65e2bf2f38
--- /dev/null
+++ b/audio/audio-driver.c
@@ -0,0 +1,1988 @@
+/*
+ * 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 "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.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"
+
+
+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 (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 (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, 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, 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(AudioDriver *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)
+{
+    AudioDriver *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-driver_template.h"
+#undef DAC
+#include "audio-driver_template.h"
+
+/*
+ * Timer
+ */
+static int audio_is_timer_needed(AudioDriver *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(AudioDriver *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;
+    AudioDriver *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_driver_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_driver_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_driver_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_driver_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool 
on)
+{
+    HWVoiceOut *hw;
+
+    if (!sw) {
+        return;
+    }
+
+    hw = sw->hw;
+    if (sw->active != on) {
+        AudioDriver *s = sw->s;
+        SWVoiceOut *temp_sw;
+        SWVoiceCap *sc;
+
+        if (on) {
+            hw->pending_disable = 0;
+            if (!hw->enabled) {
+                hw->enabled = true;
+                if (s->vm_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_driver_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool 
on)
+{
+    HWVoiceIn *hw;
+
+    if (!sw) {
+        return;
+    }
+
+    hw = sw->hw;
+    if (sw->active != on) {
+        AudioDriver *s = sw->s;
+        SWVoiceIn *temp_sw;
+
+        if (on) {
+            if (!hw->enabled) {
+                hw->enabled = true;
+                if (s->vm_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(AudioDriver *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) {
+            hw->ts_helper += 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(AudioDriver *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;
+        hw->ts_helper += captured;
+
+        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(AudioDriver *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(AudioDriver *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_be_driver_realize(AudioBackend *abe, Audiodev *dev, Error 
**errp)
+{
+    AudioDriver *d = AUDIO_DRIVER(abe);
+    audio_driver *drv = AUDIO_DRIVER_GET_CLASS(d)->driver;
+
+    d->dev = dev;
+    d->drv_opaque = drv->init(d->dev, errp);
+    if (!d->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(d, drv, 1);
+    audio_init_nb_voices_in(d, drv, 0);
+    d->drv = drv;
+
+    if (d->dev->timer_period <= 0) {
+        d->period_ticks = 1;
+    } else {
+        d->period_ticks = d->dev->timer_period * (int64_t)SCALE_US;
+    }
+
+    return true;
+}
+
+static void audio_vm_change_state_handler (void *opaque, bool running,
+                                           RunState state)
+{
+    AudioDriver *s = opaque;
+    HWVoiceOut *hwo = NULL;
+    HWVoiceIn *hwi = NULL;
+
+    s->vm_running = running;
+    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_driver_get_id(AudioBackend *be)
+{
+    return AUDIO_DRIVER(be)->dev->id;
+}
+
+static CaptureVoiceOut *audio_driver_add_capture(
+    AudioBackend *be,
+    struct audsettings *as,
+    struct audio_capture_ops *ops,
+    void *cb_opaque);
+
+static void audio_driver_del_capture(
+    AudioBackend *be,
+    CaptureVoiceOut *cap,
+    void *cb_opaque);
+
+static void audio_driver_set_volume_out(AudioBackend *be, SWVoiceOut *sw, 
Volume *vol);
+static void audio_driver_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume 
*vol);
+
+static void audio_driver_class_init(ObjectClass *klass, const void *data)
+{
+    AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
+
+    be->realize = audio_be_driver_realize;
+    be->get_id = audio_driver_get_id;
+    be->open_in = audio_driver_open_in;
+    be->open_out = audio_driver_open_out;
+    be->close_in = audio_driver_close_in;
+    be->close_out = audio_driver_close_out;
+    be->is_active_out = audio_driver_is_active_out;
+    be->is_active_in = audio_driver_is_active_in;
+    be->set_active_out = audio_driver_set_active_out;
+    be->set_active_in = audio_driver_set_active_in;
+    be->set_volume_out = audio_driver_set_volume_out;
+    be->set_volume_in = audio_driver_set_volume_in;
+    be->read = audio_driver_read;
+    be->write = audio_driver_write;
+    be->get_buffer_size_out = audio_driver_get_buffer_size_out;
+    be->add_capture = audio_driver_add_capture;
+    be->del_capture = audio_driver_del_capture;
+}
+
+static void audio_driver_init(Object *obj)
+{
+    AudioDriver *s = AUDIO_DRIVER(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_driver_finalize(Object *obj)
+{
+    AudioDriver *s = AUDIO_DRIVER(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_driver_add_capture(
+    AudioBackend *be,
+    struct audsettings *as,
+    struct audio_capture_ops *ops,
+    void *cb_opaque)
+{
+    AudioDriver *s = AUDIO_DRIVER(be);
+    CaptureVoiceOut *cap;
+    struct capture_callback *cb;
+
+    if (!s) {
+        /* TODO: implement an interface instead (or drop capture support) */
+        error_report("Capturing without setting an audiodev driver 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_driver_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_driver_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_driver_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_driver_info = {
+    .name = TYPE_AUDIO_DRIVER,
+    .parent = TYPE_AUDIO_BACKEND,
+    .instance_size = sizeof(AudioDriver),
+    .instance_init = audio_driver_init,
+    .instance_finalize = audio_driver_finalize,
+    .abstract = false,
+    .class_size = sizeof(AudioDriverClass),
+    .class_init = audio_driver_class_init,
+};
+
+static void register_types(void)
+{
+    type_register_static(&audio_driver_info);
+}
+
+type_init(register_types);
diff --git a/audio/audio.c b/audio/audio.c
index da2a4da125..ccb16ae3b2 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1,1747 +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 (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 (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, 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, 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(AudioDriver *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)
-{
-    AudioDriver *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(AudioDriver *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(AudioDriver *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;
-    AudioDriver *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_driver_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_driver_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_driver_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_driver_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool 
on)
-{
-    HWVoiceOut *hw;
-
-    if (!sw) {
-        return;
-    }
-
-    hw = sw->hw;
-    if (sw->active != on) {
-        AudioDriver *s = sw->s;
-        SWVoiceOut *temp_sw;
-        SWVoiceCap *sc;
-
-        if (on) {
-            hw->pending_disable = 0;
-            if (!hw->enabled) {
-                hw->enabled = true;
-                if (s->vm_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_driver_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool 
on)
-{
-    HWVoiceIn *hw;
-
-    if (!sw) {
-        return;
-    }
-
-    hw = sw->hw;
-    if (sw->active != on) {
-        AudioDriver *s = sw->s;
-        SWVoiceIn *temp_sw;
-
-        if (on) {
-            if (!hw->enabled) {
-                hw->enabled = true;
-                if (s->vm_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(AudioDriver *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) {
-            hw->ts_helper += 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(AudioDriver *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;
-        hw->ts_helper += captured;
-
-        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(AudioDriver *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(AudioDriver *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_be_driver_realize(AudioBackend *abe, Audiodev *dev, Error 
**errp)
-{
-    AudioDriver *d = AUDIO_DRIVER(abe);
-    audio_driver *drv = AUDIO_DRIVER_GET_CLASS(d)->driver;
-
-    d->dev = dev;
-    d->drv_opaque = drv->init(d->dev, errp);
-    if (!d->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(d, drv, 1);
-    audio_init_nb_voices_in(d, drv, 0);
-    d->drv = drv;
-
-    if (d->dev->timer_period <= 0) {
-        d->period_ticks = 1;
-    } else {
-        d->period_ticks = d->dev->timer_period * (int64_t)SCALE_US;
-    }
-
-    return true;
-}
-
-static void audio_vm_change_state_handler (void *opaque, bool running,
-                                           RunState state)
-{
-    AudioDriver *s = opaque;
-    HWVoiceOut *hwo = NULL;
-    HWVoiceIn *hwi = NULL;
-
-    s->vm_running = running;
-    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_driver_get_id(AudioBackend *be)
-{
-    return AUDIO_DRIVER(be)->dev->id;
-}
-
-static CaptureVoiceOut *audio_driver_add_capture(
-    AudioBackend *be,
-    struct audsettings *as,
-    struct audio_capture_ops *ops,
-    void *cb_opaque);
-
-static void audio_driver_del_capture(
-    AudioBackend *be,
-    CaptureVoiceOut *cap,
-    void *cb_opaque);
-
-static void audio_driver_set_volume_out(AudioBackend *be, SWVoiceOut *sw, 
Volume *vol);
-static void audio_driver_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume 
*vol);
-
-static void audio_driver_class_init(ObjectClass *klass, const void *data)
-{
-    AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
-
-    be->realize = audio_be_driver_realize;
-    be->get_id = audio_driver_get_id;
-    be->open_in = audio_driver_open_in;
-    be->open_out = audio_driver_open_out;
-    be->close_in = audio_driver_close_in;
-    be->close_out = audio_driver_close_out;
-    be->is_active_out = audio_driver_is_active_out;
-    be->is_active_in = audio_driver_is_active_in;
-    be->set_active_out = audio_driver_set_active_out;
-    be->set_active_in = audio_driver_set_active_in;
-    be->set_volume_out = audio_driver_set_volume_out;
-    be->set_volume_in = audio_driver_set_volume_in;
-    be->read = audio_driver_read;
-    be->write = audio_driver_write;
-    be->get_buffer_size_out = audio_driver_get_buffer_size_out;
-    be->add_capture = audio_driver_add_capture;
-    be->del_capture = audio_driver_del_capture;
-}
-
-static void audio_driver_init(Object *obj)
-{
-    AudioDriver *s = AUDIO_DRIVER(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);
-}
+/* SPDX-License-Identifier: MIT */
 
-static void audio_driver_finalize(Object *obj)
-{
-    AudioDriver *s = AUDIO_DRIVER(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;
+#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");
@@ -1754,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++) {
@@ -1850,155 +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_driver_add_capture(
-    AudioBackend *be,
-    struct audsettings *as,
-    struct audio_capture_ops *ops,
-    void *cb_opaque)
-{
-    AudioDriver *s = AUDIO_DRIVER(be);
-    CaptureVoiceOut *cap;
-    struct capture_callback *cb;
-
-    if (!s) {
-        /* TODO: implement an interface instead (or drop capture support) */
-        error_report("Capturing without setting an audiodev driver 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_driver_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_driver_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_driver_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)
@@ -2097,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;
@@ -2121,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;
@@ -2183,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);
@@ -2262,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;
@@ -2316,21 +468,3 @@ AudiodevList *qmp_query_audiodevs(Error **errp)
     }
     return ret;
 }
-
-static const TypeInfo audio_driver_info = {
-    .name = TYPE_AUDIO_DRIVER,
-    .parent = TYPE_AUDIO_BACKEND,
-    .instance_size = sizeof(AudioDriver),
-    .instance_init = audio_driver_init,
-    .instance_finalize = audio_driver_finalize,
-    .abstract = false,
-    .class_size = sizeof(AudioDriverClass),
-    .class_init = audio_driver_class_init,
-};
-
-static void register_types(void)
-{
-    type_register_static(&audio_driver_info);
-}
-
-type_init(register_types);
diff --git a/audio/meson.build b/audio/meson.build
index 2450098eb8..5586641d82 100644
--- a/audio/meson.build
+++ b/audio/meson.build
@@ -1,6 +1,7 @@
 system_ss.add(files(
   'audio.c',
   'audio-be.c',
+  'audio-driver.c',
   'mixeng.c',
   'noaudio.c',
   'wavaudio.c',
-- 
2.51.1



Reply via email to