diff --git a/audio/Makefile.objs b/audio/Makefile.objs
index 481d1aa..9d8f579 100644
--- a/audio/Makefile.objs
+++ b/audio/Makefile.objs
@@ -1,4 +1,4 @@
-common-obj-y = audio.o noaudio.o wavaudio.o mixeng.o
+common-obj-y = audio.o audio_legacy.o noaudio.o wavaudio.o mixeng.o
 common-obj-$(CONFIG_SDL) += sdlaudio.o
 common-obj-$(CONFIG_OSS) += ossaudio.o
 common-obj-$(CONFIG_SPICE) += spiceaudio.o
diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c
index 4d38f5d..a660ae1 100644
--- a/audio/alsaaudio.c
+++ b/audio/alsaaudio.c
@@ -22,6 +22,8 @@
  * THE SOFTWARE.
  */
 #include <alsa/asoundlib.h>
+#include "qapi/alloc-visitor.h"
+#include "qapi-visit.h"
 #include "qemu-common.h"
 #include "qemu/main-loop.h"
 #include "audio.h"
@@ -34,28 +36,9 @@
 #define AUDIO_CAP "alsa"
 #include "audio_int.h"
 
-typedef struct ALSAConf {
-    int size_in_usec_in;
-    int size_in_usec_out;
-    const char *pcm_name_in;
-    const char *pcm_name_out;
-    unsigned int buffer_size_in;
-    unsigned int period_size_in;
-    unsigned int buffer_size_out;
-    unsigned int period_size_out;
-    unsigned int threshold;
-
-    int buffer_size_in_overridden;
-    int period_size_in_overridden;
-
-    int buffer_size_out_overridden;
-    int period_size_out_overridden;
-} ALSAConf;
-
 struct pollhlp {
     snd_pcm_t *handle;
     struct pollfd *pfds;
-    ALSAConf *conf;
     int count;
     int mask;
 };
@@ -67,6 +50,7 @@ typedef struct ALSAVoiceOut {
     void *pcm_buf;
     snd_pcm_t *handle;
     struct pollhlp pollhlp;
+    Audiodev *dev;
 } ALSAVoiceOut;
 
 typedef struct ALSAVoiceIn {
@@ -74,16 +58,13 @@ typedef struct ALSAVoiceIn {
     snd_pcm_t *handle;
     void *pcm_buf;
     struct pollhlp pollhlp;
+    Audiodev *dev;
 } ALSAVoiceIn;
 
 struct alsa_params_req {
     int freq;
     snd_pcm_format_t fmt;
     int nchannels;
-    int size_in_usec;
-    int override_mask;
-    unsigned int buffer_size;
-    unsigned int period_size;
 };
 
 struct alsa_params_obt {
@@ -409,7 +390,8 @@ static int alsa_to_audfmt (snd_pcm_format_t alsafmt, AudioFormat *fmt,
 
 static void alsa_dump_info (struct alsa_params_req *req,
                             struct alsa_params_obt *obt,
-                            snd_pcm_format_t obtfmt)
+                            snd_pcm_format_t obtfmt,
+                            AudiodevPerDirectionOptions *pdo)
 {
     dolog ("parameter | requested value | obtained value\n");
     dolog ("format    |      %10d |     %10d\n", req->fmt, obtfmt);
@@ -417,8 +399,9 @@ static void alsa_dump_info (struct alsa_params_req *req,
            req->nchannels, obt->nchannels);
     dolog ("frequency |      %10d |     %10d\n", req->freq, obt->freq);
     dolog ("============================================\n");
-    dolog ("requested: buffer size %d period size %d\n",
-           req->buffer_size, req->period_size);
+    dolog ("requested: buffer size %" PRId64 " buffer count %" PRId64 "\n",
+           pdo->has_buffer ? pdo->buffer : 0,
+           pdo->has_buffer ? pdo->buffer : 0);
     dolog ("obtained: samples %ld\n", obt->samples);
 }
 
@@ -452,23 +435,24 @@ static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold)
     }
 }
 
-static int alsa_open (int in, struct alsa_params_req *req,
-                      struct alsa_params_obt *obt, snd_pcm_t **handlep,
-                      ALSAConf *conf)
+static int alsa_open(bool in, struct alsa_params_req *req,
+                     struct alsa_params_obt *obt, snd_pcm_t **handlep,
+                     Audiodev *dev)
 {
+    AudiodevPerDirectionOptions *pdo = in ? dev->in : dev->out;
+    AudiodevAlsaOptions *aopts = dev->opts->alsa;
+    AudiodevAlsaPerDirectionOptions *apdo = in ? aopts->in : aopts->out;
     snd_pcm_t *handle;
     snd_pcm_hw_params_t *hw_params;
     int err;
-    int size_in_usec;
     unsigned int freq, nchannels;
-    const char *pcm_name = in ? conf->pcm_name_in : conf->pcm_name_out;
+    const char *pcm_name = apdo->has_dev ? apdo->dev : "default";
     snd_pcm_uframes_t obt_buffer_size;
     const char *typ = in ? "ADC" : "DAC";
     snd_pcm_format_t obtfmt;
 
     freq = req->freq;
     nchannels = req->nchannels;
-    size_in_usec = req->size_in_usec;
 
     snd_pcm_hw_params_alloca (&hw_params);
 
@@ -528,79 +512,49 @@ static int alsa_open (int in, struct alsa_params_req *req,
         goto err;
     }
 
-    if (req->buffer_size) {
-        unsigned long obt;
+    if (pdo->buffer_count) {
+        if (pdo->buffer) {
+            int64_t req = pdo->buffer * pdo->buffer_count;
 
-        if (size_in_usec) {
             int dir = 0;
-            unsigned int btime = req->buffer_size;
+            unsigned int btime = req;
 
-            err = snd_pcm_hw_params_set_buffer_time_near (
-                handle,
-                hw_params,
-                &btime,
-                &dir
-                );
-            obt = btime;
-        }
-        else {
-            snd_pcm_uframes_t bsize = req->buffer_size;
+            err = snd_pcm_hw_params_set_buffer_time_near(
+                handle, hw_params, &btime, &dir);
 
-            err = snd_pcm_hw_params_set_buffer_size_near (
-                handle,
-                hw_params,
-                &bsize
-                );
-            obt = bsize;
-        }
-        if (err < 0) {
-            alsa_logerr2 (err, typ, "Failed to set buffer %s to %d\n",
-                          size_in_usec ? "time" : "size", req->buffer_size);
-            goto err;
-        }
+            if (err < 0) {
+                alsa_logerr2(err, typ,
+                             "Failed to set buffer time to %" PRId64 "\n",
+                             req);
+                goto err;
+            }
 
-        if ((req->override_mask & 2) && (obt - req->buffer_size))
-            dolog ("Requested buffer %s %u was rejected, using %lu\n",
-                   size_in_usec ? "time" : "size", req->buffer_size, obt);
+            if (pdo->has_buffer_count && btime != req) {
+                dolog("Requested buffer time %" PRId64
+                      " was rejected, using %u\n", req, btime);
+            }
+        } else {
+            dolog("Can't set buffer_count without buffer_size!\n");
+        }
     }
 
-    if (req->period_size) {
-        unsigned long obt;
+    if (pdo->buffer) {
+        int dir = 0;
+        unsigned int ptime = pdo->buffer;
 
-        if (size_in_usec) {
-            int dir = 0;
-            unsigned int ptime = req->period_size;
-
-            err = snd_pcm_hw_params_set_period_time_near (
-                handle,
-                hw_params,
-                &ptime,
-                &dir
-                );
-            obt = ptime;
-        }
-        else {
-            int dir = 0;
-            snd_pcm_uframes_t psize = req->period_size;
-
-            err = snd_pcm_hw_params_set_period_size_near (
-                handle,
-                hw_params,
-                &psize,
-                &dir
-                );
-            obt = psize;
-        }
+        err = snd_pcm_hw_params_set_period_time_near(handle, hw_params, &ptime,
+                                                     &dir);
 
         if (err < 0) {
-            alsa_logerr2 (err, typ, "Failed to set period %s to %d\n",
-                          size_in_usec ? "time" : "size", req->period_size);
+            alsa_logerr2(err, typ, "Failed to set period time to %" PRId64 "\n",
+                         pdo->buffer);
             goto err;
         }
 
-        if (((req->override_mask & 1) && (obt - req->period_size)))
-            dolog ("Requested period %s %u was rejected, using %lu\n",
-                   size_in_usec ? "time" : "size", req->period_size, obt);
+        if (pdo->has_buffer && ptime != pdo->buffer) {
+            dolog("Requested period time %" PRId64 " was rejected, using %d\n",
+                  pdo->buffer, ptime);
+        }
     }
 
     err = snd_pcm_hw_params (handle, hw_params);
@@ -632,7 +586,7 @@ static int alsa_open (int in, struct alsa_params_req *req,
         goto err;
     }
 
-    if (!in && conf->threshold) {
+    if (!in && aopts->has_threshold && aopts->threshold) {
         snd_pcm_uframes_t threshold;
         int bytes_per_sec;
 
@@ -657,7 +611,7 @@ static int alsa_open (int in, struct alsa_params_req *req,
             break;
         }
 
-        threshold = (conf->threshold * bytes_per_sec) / 1000;
+        threshold = (aopts->threshold * bytes_per_sec) / 1000;
         alsa_set_threshold (handle, threshold);
     }
 
@@ -671,11 +625,11 @@ static int alsa_open (int in, struct alsa_params_req *req,
          obt->nchannels != req->nchannels ||
          obt->freq != req->freq) {
         dolog ("Audio parameters for %s\n", typ);
-        alsa_dump_info (req, obt, obtfmt);
+        alsa_dump_info (req, obt, obtfmt, pdo);
     }
 
 #ifdef DEBUG
-    alsa_dump_info (req, obt, obtfmt);
+    alsa_dump_info (req, obt, obtfmt, pdo);
 #endif
     return 0;
 
@@ -801,19 +755,13 @@ static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as,
     struct alsa_params_obt obt;
     snd_pcm_t *handle;
     struct audsettings obt_as;
-    ALSAConf *conf = drv_opaque;
+    Audiodev *dev = drv_opaque;
 
     req.fmt = aud_to_alsafmt (as->fmt, as->endianness);
     req.freq = as->freq;
     req.nchannels = as->nchannels;
-    req.period_size = conf->period_size_out;
-    req.buffer_size = conf->buffer_size_out;
-    req.size_in_usec = conf->size_in_usec_out;
-    req.override_mask =
-        (conf->period_size_out_overridden ? 1 : 0) |
-        (conf->buffer_size_out_overridden ? 2 : 0);
 
-    if (alsa_open (0, &req, &obt, &handle, conf)) {
+    if (alsa_open (0, &req, &obt, &handle, dev)) {
         return -1;
     }
 
@@ -834,7 +782,7 @@ static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as,
     }
 
     alsa->handle = handle;
-    alsa->pollhlp.conf = conf;
+    alsa->dev = dev;
     return 0;
 }
 
@@ -874,16 +822,12 @@ static int alsa_voice_ctl (snd_pcm_t *handle, const char *typ, int ctl)
 static int alsa_ctl_out (HWVoiceOut *hw, int cmd, ...)
 {
     ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
+    AudiodevAlsaPerDirectionOptions *apdo = alsa->dev->opts->alsa->out;
 
     switch (cmd) {
     case VOICE_ENABLE:
         {
-            va_list ap;
-            int poll_mode;
-
-            va_start (ap, cmd);
-            poll_mode = va_arg (ap, int);
-            va_end (ap);
+            bool poll_mode = !apdo->has_try_poll || apdo->try_poll;
 
             ldebug ("enabling voice\n");
             if (poll_mode && alsa_poll_out (hw)) {
@@ -912,19 +856,13 @@ static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
     struct alsa_params_obt obt;
     snd_pcm_t *handle;
     struct audsettings obt_as;
-    ALSAConf *conf = drv_opaque;
+    Audiodev *dev = drv_opaque;
 
     req.fmt = aud_to_alsafmt (as->fmt, as->endianness);
     req.freq = as->freq;
     req.nchannels = as->nchannels;
-    req.period_size = conf->period_size_in;
-    req.buffer_size = conf->buffer_size_in;
-    req.size_in_usec = conf->size_in_usec_in;
-    req.override_mask =
-        (conf->period_size_in_overridden ? 1 : 0) |
-        (conf->buffer_size_in_overridden ? 2 : 0);
 
-    if (alsa_open (1, &req, &obt, &handle, conf)) {
+    if (alsa_open (1, &req, &obt, &handle, dev)) {
         return -1;
     }
 
@@ -945,7 +883,7 @@ static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
     }
 
     alsa->handle = handle;
-    alsa->pollhlp.conf = conf;
+    alsa->dev = dev;
     return 0;
 }
 
@@ -1087,16 +1025,12 @@ static int alsa_read (SWVoiceIn *sw, void *buf, int size)
 static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...)
 {
     ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw;
+    AudiodevAlsaPerDirectionOptions *apdo = alsa->dev->opts->alsa->in;
 
     switch (cmd) {
     case VOICE_ENABLE:
         {
-            va_list ap;
-            int poll_mode;
-
-            va_start (ap, cmd);
-            poll_mode = va_arg (ap, int);
-            va_end (ap);
+            bool poll_mode = !apdo->has_try_poll || apdo->try_poll;
 
             ldebug ("enabling voice\n");
             if (poll_mode && alsa_poll_in (hw)) {
@@ -1119,88 +1053,35 @@ static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...)
     return -1;
 }
 
-static ALSAConf glob_conf = {
-    .buffer_size_out = 4096,
-    .period_size_out = 1024,
-    .pcm_name_out = "default",
-    .pcm_name_in = "default",
-};
-
-static void *alsa_audio_init (void)
+static void *alsa_audio_init(Audiodev *dev)
 {
-    ALSAConf *conf = g_malloc(sizeof(ALSAConf));
-    *conf = glob_conf;
-    return conf;
+    assert(dev->opts->kind == AUDIODEV_BACKEND_OPTIONS_KIND_ALSA);
+
+    /* need to define them, as otherwise alsa produces no sound
+     * doesn't set has_* so alsa_open can identify it wasn't set by the user */
+    if (!dev->out->has_buffer_count) {
+        dev->out->buffer_count = 4;
+    }
+    if (!dev->out->has_buffer) {
+        dev->out->buffer = 23219; /* 1024 frames assuming 44100Hz */
+    }
+
+    /* OptsVisitor sets unspecified optional fields to zero, but do not depend
+     * on it... */
+    if (!dev->in->has_buffer_count) {
+        dev->in->buffer_count = 0;
+    }
+    if (!dev->in->has_buffer) {
+        dev->in->buffer = 0;
+    }
+
+    return dev;
 }
 
 static void alsa_audio_fini (void *opaque)
 {
-    g_free(opaque);
 }
 
-static struct audio_option alsa_options[] = {
-    {
-        .name        = "DAC_SIZE_IN_USEC",
-        .tag         = AUD_OPT_BOOL,
-        .valp        = &glob_conf.size_in_usec_out,
-        .descr       = "DAC period/buffer size in microseconds (otherwise in frames)"
-    },
-    {
-        .name        = "DAC_PERIOD_SIZE",
-        .tag         = AUD_OPT_INT,
-        .valp        = &glob_conf.period_size_out,
-        .descr       = "DAC period size (0 to go with system default)",
-        .overriddenp = &glob_conf.period_size_out_overridden
-    },
-    {
-        .name        = "DAC_BUFFER_SIZE",
-        .tag         = AUD_OPT_INT,
-        .valp        = &glob_conf.buffer_size_out,
-        .descr       = "DAC buffer size (0 to go with system default)",
-        .overriddenp = &glob_conf.buffer_size_out_overridden
-    },
-    {
-        .name        = "ADC_SIZE_IN_USEC",
-        .tag         = AUD_OPT_BOOL,
-        .valp        = &glob_conf.size_in_usec_in,
-        .descr       =
-        "ADC period/buffer size in microseconds (otherwise in frames)"
-    },
-    {
-        .name        = "ADC_PERIOD_SIZE",
-        .tag         = AUD_OPT_INT,
-        .valp        = &glob_conf.period_size_in,
-        .descr       = "ADC period size (0 to go with system default)",
-        .overriddenp = &glob_conf.period_size_in_overridden
-    },
-    {
-        .name        = "ADC_BUFFER_SIZE",
-        .tag         = AUD_OPT_INT,
-        .valp        = &glob_conf.buffer_size_in,
-        .descr       = "ADC buffer size (0 to go with system default)",
-        .overriddenp = &glob_conf.buffer_size_in_overridden
-    },
-    {
-        .name        = "THRESHOLD",
-        .tag         = AUD_OPT_INT,
-        .valp        = &glob_conf.threshold,
-        .descr       = "(undocumented)"
-    },
-    {
-        .name        = "DAC_DEV",
-        .tag         = AUD_OPT_STR,
-        .valp        = &glob_conf.pcm_name_out,
-        .descr       = "DAC device name (for instance dmix)"
-    },
-    {
-        .name        = "ADC_DEV",
-        .tag         = AUD_OPT_STR,
-        .valp        = &glob_conf.pcm_name_in,
-        .descr       = "ADC device name"
-    },
-    { /* End of list */ }
-};
-
 static struct audio_pcm_ops alsa_pcm_ops = {
     .init_out = alsa_init_out,
     .fini_out = alsa_fini_out,
@@ -1218,7 +1099,6 @@ static struct audio_pcm_ops alsa_pcm_ops = {
 struct audio_driver alsa_audio_driver = {
     .name           = "alsa",
     .descr          = "ALSA http://www.alsa-project.org",
-    .options        = alsa_options,
     .init           = alsa_audio_init,
     .fini           = alsa_audio_fini,
     .pcm_ops        = &alsa_pcm_ops,
diff --git a/audio/audio.c b/audio/audio.c
index 112b57b..daee117 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -24,7 +24,11 @@
 #include "hw/hw.h"
 #include "audio.h"
 #include "monitor/monitor.h"
+#include "qapi-visit.h"
+#include "qapi/alloc-visitor.h"
+#include "qapi/opts-visitor.h"
 #include "qemu/timer.h"
+#include "qemu/config-file.h"
 #include "sysemu/sysemu.h"
 
 #define AUDIO_CAP "audio"
@@ -42,59 +46,14 @@
    The 1st one is the one used by default, that is the reason
     that we generate the list.
 */
-static struct audio_driver *drvtab[] = {
+struct audio_driver *drvtab[] = {
 #ifdef CONFIG_SPICE
     &spice_audio_driver,
 #endif
     CONFIG_AUDIO_DRIVERS
     &no_audio_driver,
-    &wav_audio_driver
-};
-
-struct fixed_settings {
-    int enabled;
-    int nb_voices;
-    int greedy;
-    struct audsettings settings;
-};
-
-static struct {
-    struct fixed_settings fixed_out;
-    struct fixed_settings fixed_in;
-    union {
-        int hertz;
-        int64_t ticks;
-    } period;
-    int try_poll_in;
-    int try_poll_out;
-} conf = {
-    .fixed_out = { /* DAC fixed settings */
-        .enabled = 1,
-        .nb_voices = 1,
-        .greedy = 1,
-        .settings = {
-            .freq = 44100,
-            .nchannels = 2,
-            .fmt = AUDIO_FORMAT_S16,
-            .endianness =  AUDIO_HOST_ENDIANNESS,
-        }
-    },
-
-    .fixed_in = { /* ADC fixed settings */
-        .enabled = 1,
-        .nb_voices = 1,
-        .greedy = 1,
-        .settings = {
-            .freq = 44100,
-            .nchannels = 2,
-            .fmt = AUDIO_FORMAT_S16,
-            .endianness = AUDIO_HOST_ENDIANNESS,
-        }
-    },
-
-    .period = { .hertz = 100 },
-    .try_poll_in = 1,
-    .try_poll_out = 1,
+    &wav_audio_driver,
+    NULL
 };
 
 static AudioState glob_audio_state;
@@ -113,9 +72,6 @@ const struct mixeng_volume nominal_volume = {
 #ifdef AUDIO_IS_FLAWLESS_AND_NO_CHECKS_ARE_REQURIED
 #error No its not
 #else
-static void audio_print_options (const char *prefix,
-                                 struct audio_option *opt);
-
 int audio_bug (const char *funcname, int cond)
 {
     if (cond) {
@@ -123,16 +79,9 @@ int audio_bug (const char *funcname, int cond)
 
         AUD_log (NULL, "A bug was just triggered in %s\n", funcname);
         if (!shown) {
-            struct audio_driver *d;
-
             shown = 1;
             AUD_log (NULL, "Save all your work and restart without audio\n");
-            AUD_log (NULL, "Please send bug report to av1474@comtv.ru\n");
             AUD_log (NULL, "I am sorry\n");
-            d = glob_audio_state.drv;
-            if (d) {
-                audio_print_options (d->name, d->options);
-            }
         }
         AUD_log (NULL, "Context:\n");
 
@@ -194,139 +143,6 @@ void *audio_calloc (const char *funcname, int nmemb, size_t size)
     return g_malloc0 (len);
 }
 
-static char *audio_alloc_prefix (const char *s)
-{
-    const char qemu_prefix[] = "QEMU_";
-    size_t len, i;
-    char *r, *u;
-
-    if (!s) {
-        return NULL;
-    }
-
-    len = strlen (s);
-    r = g_malloc (len + sizeof (qemu_prefix));
-
-    u = r + sizeof (qemu_prefix) - 1;
-
-    pstrcpy (r, len + sizeof (qemu_prefix), qemu_prefix);
-    pstrcat (r, len + sizeof (qemu_prefix), s);
-
-    for (i = 0; i < len; ++i) {
-        u[i] = qemu_toupper(u[i]);
-    }
-
-    return r;
-}
-
-static const char *audio_audfmt_to_string (AudioFormat fmt)
-{
-    switch (fmt) {
-    case AUDIO_FORMAT_U8:
-        return "U8";
-
-    case AUDIO_FORMAT_U16:
-        return "U16";
-
-    case AUDIO_FORMAT_S8:
-        return "S8";
-
-    case AUDIO_FORMAT_S16:
-        return "S16";
-
-    case AUDIO_FORMAT_U32:
-        return "U32";
-
-    case AUDIO_FORMAT_S32:
-        return "S32";
-
-    case AUDIO_FORMAT_MAX:
-        abort();
-    }
-
-    dolog ("Bogus audfmt %d returning S16\n", fmt);
-    return "S16";
-}
-
-static AudioFormat audio_string_to_audfmt (const char *s, AudioFormat defval,
-                                        int *defaultp)
-{
-    if (!strcasecmp (s, "u8")) {
-        *defaultp = 0;
-        return AUDIO_FORMAT_U8;
-    }
-    else if (!strcasecmp (s, "u16")) {
-        *defaultp = 0;
-        return AUDIO_FORMAT_U16;
-    }
-    else if (!strcasecmp (s, "u32")) {
-        *defaultp = 0;
-        return AUDIO_FORMAT_U32;
-    }
-    else if (!strcasecmp (s, "s8")) {
-        *defaultp = 0;
-        return AUDIO_FORMAT_S8;
-    }
-    else if (!strcasecmp (s, "s16")) {
-        *defaultp = 0;
-        return AUDIO_FORMAT_S16;
-    }
-    else if (!strcasecmp (s, "s32")) {
-        *defaultp = 0;
-        return AUDIO_FORMAT_S32;
-    }
-    else {
-        dolog ("Bogus audio format `%s' using %s\n",
-               s, audio_audfmt_to_string (defval));
-        *defaultp = 1;
-        return defval;
-    }
-}
-
-static AudioFormat audio_get_conf_fmt (const char *envname,
-                                    AudioFormat defval,
-                                    int *defaultp)
-{
-    const char *var = getenv (envname);
-    if (!var) {
-        *defaultp = 1;
-        return defval;
-    }
-    return audio_string_to_audfmt (var, defval, defaultp);
-}
-
-static int audio_get_conf_int (const char *key, int defval, int *defaultp)
-{
-    int val;
-    char *strval;
-
-    strval = getenv (key);
-    if (strval) {
-        *defaultp = 0;
-        val = atoi (strval);
-        return val;
-    }
-    else {
-        *defaultp = 1;
-        return defval;
-    }
-}
-
-static const char *audio_get_conf_str (const char *key,
-                                       const char *defval,
-                                       int *defaultp)
-{
-    const char *val = getenv (key);
-    if (!val) {
-        *defaultp = 1;
-        return defval;
-    }
-    else {
-        *defaultp = 0;
-        return val;
-    }
-}
-
 void AUD_vlog (const char *cap, const char *fmt, va_list ap)
 {
     if (cap) {
@@ -345,161 +161,6 @@ void AUD_log (const char *cap, const char *fmt, ...)
     va_end (ap);
 }
 
-static void audio_print_options (const char *prefix,
-                                 struct audio_option *opt)
-{
-    char *uprefix;
-
-    if (!prefix) {
-        dolog ("No prefix specified\n");
-        return;
-    }
-
-    if (!opt) {
-        dolog ("No options\n");
-        return;
-    }
-
-    uprefix = audio_alloc_prefix (prefix);
-
-    for (; opt->name; opt++) {
-        const char *state = "default";
-        printf ("  %s_%s: ", uprefix, opt->name);
-
-        if (opt->overriddenp && *opt->overriddenp) {
-            state = "current";
-        }
-
-        switch (opt->tag) {
-        case AUD_OPT_BOOL:
-            {
-                int *intp = opt->valp;
-                printf ("boolean, %s = %d\n", state, *intp ? 1 : 0);
-            }
-            break;
-
-        case AUD_OPT_INT:
-            {
-                int *intp = opt->valp;
-                printf ("integer, %s = %d\n", state, *intp);
-            }
-            break;
-
-        case AUD_OPT_FMT:
-            {
-                AudioFormat *fmtp = opt->valp;
-                printf (
-                    "format, %s = %s, (one of: U8 S8 U16 S16 U32 S32)\n",
-                    state,
-                    audio_audfmt_to_string (*fmtp)
-                    );
-            }
-            break;
-
-        case AUD_OPT_STR:
-            {
-                const char **strp = opt->valp;
-                printf ("string, %s = %s\n",
-                        state,
-                        *strp ? *strp : "(not set)");
-            }
-            break;
-
-        default:
-            printf ("???\n");
-            dolog ("Bad value tag for option %s_%s %d\n",
-                   uprefix, opt->name, opt->tag);
-            break;
-        }
-        printf ("    %s\n", opt->descr);
-    }
-
-    g_free (uprefix);
-}
-
-static void audio_process_options (const char *prefix,
-                                   struct audio_option *opt)
-{
-    char *optname;
-    const char qemu_prefix[] = "QEMU_";
-    size_t preflen, optlen;
-
-    if (audio_bug (AUDIO_FUNC, !prefix)) {
-        dolog ("prefix = NULL\n");
-        return;
-    }
-
-    if (audio_bug (AUDIO_FUNC, !opt)) {
-        dolog ("opt = NULL\n");
-        return;
-    }
-
-    preflen = strlen (prefix);
-
-    for (; opt->name; opt++) {
-        size_t len, i;
-        int def;
-
-        if (!opt->valp) {
-            dolog ("Option value pointer for `%s' is not set\n",
-                   opt->name);
-            continue;
-        }
-
-        len = strlen (opt->name);
-        /* len of opt->name + len of prefix + size of qemu_prefix
-         * (includes trailing zero) + zero + underscore (on behalf of
-         * sizeof) */
-        optlen = len + preflen + sizeof (qemu_prefix) + 1;
-        optname = g_malloc (optlen);
-
-        pstrcpy (optname, optlen, qemu_prefix);
-
-        /* copy while upper-casing, including trailing zero */
-        for (i = 0; i <= preflen; ++i) {
-            optname[i + sizeof (qemu_prefix) - 1] = qemu_toupper(prefix[i]);
-        }
-        pstrcat (optname, optlen, "_");
-        pstrcat (optname, optlen, opt->name);
-
-        def = 1;
-        switch (opt->tag) {
-        case AUD_OPT_BOOL:
-        case AUD_OPT_INT:
-            {
-                int *intp = opt->valp;
-                *intp = audio_get_conf_int (optname, *intp, &def);
-            }
-            break;
-
-        case AUD_OPT_FMT:
-            {
-                AudioFormat *fmtp = opt->valp;
-                *fmtp = audio_get_conf_fmt (optname, *fmtp, &def);
-            }
-            break;
-
-        case AUD_OPT_STR:
-            {
-                const char **strp = opt->valp;
-                *strp = audio_get_conf_str (optname, *strp, &def);
-            }
-            break;
-
-        default:
-            dolog ("Bad value tag for option `%s' - %d\n",
-                   optname, opt->tag);
-            break;
-        }
-
-        if (!opt->overriddenp) {
-            opt->overriddenp = &opt->overridden;
-        }
-        *opt->overriddenp = !def;
-        g_free (optname);
-    }
-}
-
 static void audio_print_settings (struct audsettings *as)
 {
     dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels);
@@ -1120,7 +781,7 @@ static void audio_reset_timer (AudioState *s)
 {
     if (audio_is_timer_needed ()) {
         timer_mod (s->ts,
-            qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + conf.period.ticks);
+            qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
     }
     else {
         timer_del (s->ts);
@@ -1196,7 +857,7 @@ void AUD_set_active_out (SWVoiceOut *sw, int on)
             if (!hw->enabled) {
                 hw->enabled = 1;
                 if (s->vm_running) {
-                    hw->pcm_ops->ctl_out (hw, VOICE_ENABLE, conf.try_poll_out);
+                    hw->pcm_ops->ctl_out (hw, VOICE_ENABLE);
                     audio_reset_timer (s);
                 }
             }
@@ -1241,7 +902,7 @@ void AUD_set_active_in (SWVoiceIn *sw, int on)
             if (!hw->enabled) {
                 hw->enabled = 1;
                 if (s->vm_running) {
-                    hw->pcm_ops->ctl_in (hw, VOICE_ENABLE, conf.try_poll_in);
+                    hw->pcm_ops->ctl_in (hw, VOICE_ENABLE);
                     audio_reset_timer (s);
                 }
             }
@@ -1558,168 +1219,10 @@ void audio_run (const char *msg)
 #endif
 }
 
-static struct audio_option audio_options[] = {
-    /* DAC */
-    {
-        .name  = "DAC_FIXED_SETTINGS",
-        .tag   = AUD_OPT_BOOL,
-        .valp  = &conf.fixed_out.enabled,
-        .descr = "Use fixed settings for host DAC"
-    },
-    {
-        .name  = "DAC_FIXED_FREQ",
-        .tag   = AUD_OPT_INT,
-        .valp  = &conf.fixed_out.settings.freq,
-        .descr = "Frequency for fixed host DAC"
-    },
-    {
-        .name  = "DAC_FIXED_FMT",
-        .tag   = AUD_OPT_FMT,
-        .valp  = &conf.fixed_out.settings.fmt,
-        .descr = "Format for fixed host DAC"
-    },
-    {
-        .name  = "DAC_FIXED_CHANNELS",
-        .tag   = AUD_OPT_INT,
-        .valp  = &conf.fixed_out.settings.nchannels,
-        .descr = "Number of channels for fixed DAC (1 - mono, 2 - stereo)"
-    },
-    {
-        .name  = "DAC_VOICES",
-        .tag   = AUD_OPT_INT,
-        .valp  = &conf.fixed_out.nb_voices,
-        .descr = "Number of voices for DAC"
-    },
-    {
-        .name  = "DAC_TRY_POLL",
-        .tag   = AUD_OPT_BOOL,
-        .valp  = &conf.try_poll_out,
-        .descr = "Attempt using poll mode for DAC"
-    },
-    /* ADC */
-    {
-        .name  = "ADC_FIXED_SETTINGS",
-        .tag   = AUD_OPT_BOOL,
-        .valp  = &conf.fixed_in.enabled,
-        .descr = "Use fixed settings for host ADC"
-    },
-    {
-        .name  = "ADC_FIXED_FREQ",
-        .tag   = AUD_OPT_INT,
-        .valp  = &conf.fixed_in.settings.freq,
-        .descr = "Frequency for fixed host ADC"
-    },
-    {
-        .name  = "ADC_FIXED_FMT",
-        .tag   = AUD_OPT_FMT,
-        .valp  = &conf.fixed_in.settings.fmt,
-        .descr = "Format for fixed host ADC"
-    },
-    {
-        .name  = "ADC_FIXED_CHANNELS",
-        .tag   = AUD_OPT_INT,
-        .valp  = &conf.fixed_in.settings.nchannels,
-        .descr = "Number of channels for fixed ADC (1 - mono, 2 - stereo)"
-    },
-    {
-        .name  = "ADC_VOICES",
-        .tag   = AUD_OPT_INT,
-        .valp  = &conf.fixed_in.nb_voices,
-        .descr = "Number of voices for ADC"
-    },
-    {
-        .name  = "ADC_TRY_POLL",
-        .tag   = AUD_OPT_BOOL,
-        .valp  = &conf.try_poll_in,
-        .descr = "Attempt using poll mode for ADC"
-    },
-    /* Misc */
-    {
-        .name  = "TIMER_PERIOD",
-        .tag   = AUD_OPT_INT,
-        .valp  = &conf.period.hertz,
-        .descr = "Timer period in HZ (0 - use lowest possible)"
-    },
-    { /* End of list */ }
-};
-
-static void audio_pp_nb_voices (const char *typ, int nb)
+static int audio_driver_init(AudioState *s, struct audio_driver *drv,
+                             Audiodev *dev)
 {
-    switch (nb) {
-    case 0:
-        printf ("Does not support %s\n", typ);
-        break;
-    case 1:
-        printf ("One %s voice\n", typ);
-        break;
-    case INT_MAX:
-        printf ("Theoretically supports many %s voices\n", typ);
-        break;
-    default:
-        printf ("Theoretically supports up to %d %s voices\n", nb, typ);
-        break;
-    }
-
-}
-
-void AUD_help (void)
-{
-    size_t i;
-
-    audio_process_options ("AUDIO", audio_options);
-    for (i = 0; i < ARRAY_SIZE (drvtab); i++) {
-        struct audio_driver *d = drvtab[i];
-        if (d->options) {
-            audio_process_options (d->name, d->options);
-        }
-    }
-
-    printf ("Audio options:\n");
-    audio_print_options ("AUDIO", audio_options);
-    printf ("\n");
-
-    printf ("Available drivers:\n");
-
-    for (i = 0; i < ARRAY_SIZE (drvtab); i++) {
-        struct audio_driver *d = drvtab[i];
-
-        printf ("Name: %s\n", d->name);
-        printf ("Description: %s\n", d->descr);
-
-        audio_pp_nb_voices ("playback", d->max_voices_out);
-        audio_pp_nb_voices ("capture", d->max_voices_in);
-
-        if (d->options) {
-            printf ("Options:\n");
-            audio_print_options (d->name, d->options);
-        }
-        else {
-            printf ("No options\n");
-        }
-        printf ("\n");
-    }
-
-    printf (
-        "Options are settable through environment variables.\n"
-        "Example:\n"
-#ifdef _WIN32
-        "  set QEMU_AUDIO_DRV=wav\n"
-        "  set QEMU_WAV_PATH=c:\\tune.wav\n"
-#else
-        "  export QEMU_AUDIO_DRV=wav\n"
-        "  export QEMU_WAV_PATH=$HOME/tune.wav\n"
-        "(for csh replace export with setenv in the above)\n"
-#endif
-        "  qemu ...\n\n"
-        );
-}
-
-static int audio_driver_init (AudioState *s, struct audio_driver *drv)
-{
-    if (drv->options) {
-        audio_process_options (drv->name, drv->options);
-    }
-    s->drv_opaque = drv->init ();
+    s->drv_opaque = drv->init(dev);
 
     if (s->drv_opaque) {
         audio_init_nb_voices_out (drv);
@@ -1743,11 +1246,11 @@ static void audio_vm_change_state_handler (void *opaque, int running,
 
     s->vm_running = running;
     while ((hwo = audio_pcm_hw_find_any_enabled_out (hwo))) {
-        hwo->pcm_ops->ctl_out (hwo, op, conf.try_poll_out);
+        hwo->pcm_ops->ctl_out (hwo, op);
     }
 
     while ((hwi = audio_pcm_hw_find_any_enabled_in (hwi))) {
-        hwi->pcm_ops->ctl_in (hwi, op, conf.try_poll_in);
+        hwi->pcm_ops->ctl_in (hwi, op);
     }
     audio_reset_timer (s);
 }
@@ -1786,6 +1289,8 @@ static void audio_atexit (void)
     if (s->drv) {
         s->drv->fini (s->drv_opaque);
     }
+
+    qapi_free_Audiodev(s->dev);
 }
 
 static const VMStateDescription vmstate_audio = {
@@ -1797,18 +1302,37 @@ static const VMStateDescription vmstate_audio = {
     }
 };
 
-static void audio_init (void)
+static Audiodev *parse_option(QemuOpts *opts, Error **errp);
+static int audio_init(Audiodev *dev)
 {
     size_t i;
     int done = 0;
-    const char *drvname;
+    const char *drvname = NULL;
     VMChangeStateEntry *e;
     AudioState *s = &glob_audio_state;
+    QemuOptsList *list = NULL; /* silence gcc warning about uninitialized
+                                * variable */
 
     if (s->drv) {
-        return;
+        if (dev) {
+            dolog("Cannot create more than one audio backend, sorry\n");
+            qapi_free_Audiodev(dev);
+        }
+        return -1;
     }
 
+    if (dev) {
+        drvname = AudiodevBackendOptionsKind_lookup[dev->opts->kind];
+    } else {
+        audio_handle_legacy_opts();
+        list = qemu_find_opts("audiodev");
+        dev = parse_option(QTAILQ_FIRST(&list->head), &error_abort);
+        if (!dev) {
+            exit(1);
+        }
+    }
+    s->dev = dev;
+
     QLIST_INIT (&s->hw_head_out);
     QLIST_INIT (&s->hw_head_in);
     QLIST_INIT (&s->cap_head);
@@ -1819,10 +1343,8 @@ static void audio_init (void)
         hw_error("Could not create audio timer\n");
     }
 
-    audio_process_options ("AUDIO", audio_options);
-
-    s->nb_hw_voices_out = conf.fixed_out.nb_voices;
-    s->nb_hw_voices_in = conf.fixed_in.nb_voices;
+    s->nb_hw_voices_out = dev->out->voices;
+    s->nb_hw_voices_in = dev->in->voices;
 
     if (s->nb_hw_voices_out <= 0) {
         dolog ("Bogus number of playback voices %d, setting to 1\n",
@@ -1836,17 +1358,12 @@ static void audio_init (void)
         s->nb_hw_voices_in = 0;
     }
 
-    {
-        int def;
-        drvname = audio_get_conf_str ("QEMU_AUDIO_DRV", NULL, &def);
-    }
-
     if (drvname) {
         int found = 0;
 
-        for (i = 0; i < ARRAY_SIZE (drvtab); i++) {
+        for (i = 0; drvtab[i]; i++) {
             if (!strcmp (drvname, drvtab[i]->name)) {
-                done = !audio_driver_init (s, drvtab[i]);
+                done = !audio_driver_init (s, drvtab[i], dev);
                 found = 1;
                 break;
             }
@@ -1854,20 +1371,24 @@ static void audio_init (void)
 
         if (!found) {
             dolog ("Unknown audio driver `%s'\n", drvname);
-            dolog ("Run with -audio-help to list available drivers\n");
         }
-    }
-
-    if (!done) {
-        for (i = 0; !done && i < ARRAY_SIZE (drvtab); i++) {
-            if (drvtab[i]->can_be_default) {
-                done = !audio_driver_init (s, drvtab[i]);
+    } else {
+        for (i = 0; !done && drvtab[i]; i++) {
+            QemuOpts *opts = qemu_opts_find(list, drvtab[i]->name);
+            if (opts) {
+                qapi_free_Audiodev(dev);
+                dev = parse_option(opts, &error_abort);
+                if (!dev) {
+                    exit(1);
+                }
+                s->dev = dev;
+                done = !audio_driver_init(s, drvtab[i], dev);
             }
         }
     }
 
     if (!done) {
-        done = !audio_driver_init (s, &no_audio_driver);
+        done = !audio_driver_init (s, &no_audio_driver, dev);
         if (!done) {
             hw_error("Could not initialize audio subsystem\n");
         }
@@ -1876,16 +1397,16 @@ static void audio_init (void)
         }
     }
 
-    if (conf.period.hertz <= 0) {
-        if (conf.period.hertz < 0) {
-            dolog ("warning: Timer period is negative - %d "
-                   "treating as zero\n",
-                   conf.period.hertz);
+    if (dev->timer_period <= 0) {
+        if (dev->timer_period < 0) {
+            dolog ("warning: Timer period is negative - %" PRId64
+                   " treating as zero\n",
+                   dev->timer_period);
         }
-        conf.period.ticks = 1;
+        s->period_ticks = 1;
     } else {
-        conf.period.ticks =
-            muldiv64 (1, get_ticks_per_sec (), conf.period.hertz);
+        s->period_ticks =
+            muldiv64(dev->timer_period, get_ticks_per_sec(), 1000000);
     }
 
     e = qemu_add_vm_change_state_handler (audio_vm_change_state_handler, s);
@@ -1896,11 +1417,12 @@ static void audio_init (void)
 
     QLIST_INIT (&s->card_head);
     vmstate_register (NULL, 0, &vmstate_audio, s);
+    return 0;
 }
 
 void AUD_register_card (const char *name, QEMUSoundCard *card)
 {
-    audio_init ();
+    audio_init(NULL);
     card->name = g_strdup (name);
     memset (&card->entries, 0, sizeof (card->entries));
     QLIST_INSERT_HEAD (&glob_audio_state.card_head, card, entries);
@@ -2070,3 +1592,142 @@ void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol)
         }
     }
 }
+
+QemuOptsList qemu_audiodev_opts = {
+    .name = "audiodev",
+    .head = QTAILQ_HEAD_INITIALIZER(qemu_audiodev_opts.head),
+    .implied_opt_name = "type",
+    .desc = {
+        /*
+         * no elements => accept any params
+         * sanity checking will happen later
+         */
+        { /* end of list */ }
+    },
+};
+
+static void set_per_direction_defaults(AudiodevPerDirectionOptions *pdo)
+{
+    if (!pdo->has_fixed_settings) {
+        pdo->has_fixed_settings = true;
+        pdo->fixed_settings = true;
+    }
+    if (!pdo->has_frequency) {
+        pdo->has_frequency = true;
+        pdo->frequency = 44100;
+    }
+    if (!pdo->has_channels) {
+        pdo->has_channels = true;
+        pdo->channels = 2;
+    }
+    if (!pdo->has_voices) {
+        pdo->has_voices = true;
+        pdo->voices = 1;
+    }
+    if (!pdo->has_format) {
+        pdo->has_format = true;
+        pdo->format = AUDIO_FORMAT_S16;
+    }
+}
+
+static Audiodev *parse_option(QemuOpts *opts, Error **errp)
+{
+    Error *local_err = NULL;
+    OptsVisitor *ov = opts_visitor_new(opts);
+    Audiodev *dev = NULL;
+    visit_type_Audiodev(opts_get_visitor(ov), &dev, NULL, &local_err);
+    opts_visitor_cleanup(ov);
+
+    if (local_err) {
+        error_propagate(errp, local_err);
+        return NULL;
+    }
+
+    if (!dev->has_id) {
+        dev->has_id = true;
+        dev->id = g_strdup("default");
+    }
+
+    set_per_direction_defaults(dev->in);
+    set_per_direction_defaults(dev->out);
+
+    if (!dev->has_timer_period) {
+        dev->has_timer_period = true;
+        dev->timer_period = 10000; /* 100Hz -> 10ms */
+    }
+
+    return dev;
+}
+
+static int each_option(void *opaque, QemuOpts *opts, Error **errp)
+{
+    Audiodev *dev = parse_option(opts, errp);
+    if (!dev) {
+        return -1;
+    }
+    return audio_init(dev);
+}
+
+void audio_set_options(void)
+{
+    if (qemu_opts_foreach(qemu_find_opts("audiodev"), each_option, NULL,
+                          &error_abort)) {
+        exit(1);
+    }
+}
+
+audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo)
+{
+    return (audsettings) {
+        .freq = pdo->frequency,
+        .nchannels = pdo->channels,
+        .fmt = pdo->format,
+        .endianness = AUDIO_HOST_ENDIANNESS,
+    };
+}
+
+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:
+        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 ? pdo->buffer : 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);
+}
diff --git a/audio/audio.h b/audio/audio.h
index e300511..177a673 100644
--- a/audio/audio.h
+++ b/audio/audio.h
@@ -24,7 +24,10 @@
 #ifndef QEMU_AUDIO_H
 #define QEMU_AUDIO_H
 
+#include <stdarg.h>
 #include "config-host.h"
+#include "qapi-types.h"
+#include "qemu/option.h"
 #include "qemu/queue.h"
 
 typedef void (*audio_callback_fn) (void *opaque, int avail);
@@ -35,12 +38,21 @@ typedef void (*audio_callback_fn) (void *opaque, int avail);
 #define AUDIO_HOST_ENDIANNESS 0
 #endif
 
-struct audsettings {
+typedef struct audsettings {
     int freq;
     int nchannels;
     AudioFormat fmt;
     int endianness;
-};
+} audsettings;
+
+audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo);
+int audioformat_bytes_per_sample(AudioFormat fmt);
+int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,
+                        audsettings *as, int def_usecs);
+int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,
+                         audsettings *as, int def_usecs);
+int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,
+                       audsettings *as, int def_usecs);
 
 typedef enum {
     AUD_CNOTIFY_ENABLE,
@@ -77,10 +89,11 @@ typedef struct QEMUAudioTimeStamp {
     uint64_t old_ts;
 } QEMUAudioTimeStamp;
 
+extern QemuOptsList qemu_audiodev_opts;
+
 void AUD_vlog (const char *cap, const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0);
 void AUD_log (const char *cap, const char *fmt, ...) GCC_FMT_ATTR(2, 3);
 
-void AUD_help (void);
 void AUD_register_card (const char *name, QEMUSoundCard *card);
 void AUD_remove_card (QEMUSoundCard *card);
 CaptureVoiceOut *AUD_add_capture (
@@ -154,4 +167,8 @@ static inline void *advance (void *p, int incr)
 int wav_start_capture (CaptureState *s, const char *path, int freq,
                        int bits, int nchannels);
 
+void audio_set_options(void);
+void audio_handle_legacy_opts(void);
+void audio_legacy_help(void);
+
 #endif  /* audio.h */
diff --git a/audio/audio_int.h b/audio/audio_int.h
index 566df5e..c4539e7 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -143,8 +143,7 @@ struct SWVoiceIn {
 struct audio_driver {
     const char *name;
     const char *descr;
-    struct audio_option *options;
-    void *(*init) (void);
+    void *(*init) (Audiodev *);
     void (*fini) (void *);
     struct audio_pcm_ops *pcm_ops;
     int can_be_default;
@@ -190,6 +189,7 @@ struct SWVoiceCap {
 
 struct AudioState {
     struct audio_driver *drv;
+    Audiodev *dev;
     void *drv_opaque;
 
     QEMUTimer *ts;
@@ -200,6 +200,7 @@ struct AudioState {
     int nb_hw_voices_out;
     int nb_hw_voices_in;
     int vm_running;
+    int64_t period_ticks;
 };
 
 extern struct audio_driver no_audio_driver;
@@ -213,6 +214,8 @@ extern struct audio_driver pa_audio_driver;
 extern struct audio_driver spice_audio_driver;
 extern const struct mixeng_volume nominal_volume;
 
+extern struct audio_driver *drvtab[];
+
 void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as);
 void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len);
 
diff --git a/audio/audio_legacy.c b/audio/audio_legacy.c
new file mode 100644
index 0000000..4987427
--- /dev/null
+++ b/audio/audio_legacy.c
@@ -0,0 +1,328 @@
+#include "audio.h"
+#include "qemu-common.h"
+#include "qemu/config-file.h"
+
+#define AUDIO_CAP "audio-legacy"
+#include "audio_int.h"
+
+typedef enum EnvTransform {
+    ENV_TRANSFORM_NONE,
+    ENV_TRANSFORM_BOOL,
+    ENV_TRANSFORM_FMT,
+    ENV_TRANSFORM_FRAMES_TO_USECS_IN,
+    ENV_TRANSFORM_FRAMES_TO_USECS_OUT,
+    ENV_TRANSFORM_SAMPLES_TO_USECS_IN,
+    ENV_TRANSFORM_SAMPLES_TO_USECS_OUT,
+    ENV_TRANSFORM_BYTES_TO_USECS_IN,
+    ENV_TRANSFORM_BYTES_TO_USECS_OUT,
+    ENV_TRANSFORM_MILLIS_TO_USECS,
+    ENV_TRANSFORM_HZ_TO_USECS,
+} EnvTransform;
+
+typedef struct SimpleEnvMap {
+    const char *name;
+    const char *option;
+    EnvTransform transform;
+} SimpleEnvMap;
+
+SimpleEnvMap global_map[] = {
+    /* DAC/out settings */
+    { "QEMU_AUDIO_DAC_FIXED_SETTINGS", "out.fixed-settings",
+      ENV_TRANSFORM_BOOL },
+    { "QEMU_AUDIO_DAC_FIXED_FREQ", "out.frequency" },
+    { "QEMU_AUDIO_DAC_FIXED_FMT", "out.format", ENV_TRANSFORM_FMT },
+    { "QEMU_AUDIO_DAC_FIXED_CHANNELS", "out.channels" },
+    { "QEMU_AUDIO_DAC_VOICES", "out.voices" },
+
+    /* ADC/in settings */
+    { "QEMU_AUDIO_ADC_FIXED_SETTINGS", "in.fixed-settings",
+      ENV_TRANSFORM_BOOL },
+    { "QEMU_AUDIO_ADC_FIXED_FREQ", "in.frequency" },
+    { "QEMU_AUDIO_ADC_FIXED_FMT", "in.format", ENV_TRANSFORM_FMT },
+    { "QEMU_AUDIO_ADC_FIXED_CHANNELS", "in.channels" },
+    { "QEMU_AUDIO_ADC_VOICES", "in.voices" },
+
+    /* general */
+    { "QEMU_AUDIO_TIMER_PERIOD", "timer-period", ENV_TRANSFORM_HZ_TO_USECS },
+    { /* End of list */ }
+};
+
+SimpleEnvMap alsa_map[] = {
+    { "QEMU_AUDIO_DAC_TRY_POLL", "out.try-poll", ENV_TRANSFORM_BOOL },
+    { "QEMU_AUDIO_ADC_TRY_POLL", "in.try-poll", ENV_TRANSFORM_BOOL },
+
+    { "QEMU_ALSA_THRESHOLD", "threshold" },
+    { "QEMU_ALSA_DAC_DEV", "out.dev" },
+    { "QEMU_ALSA_ADC_DEV", "in.dev" },
+
+    { /* End of list */ }
+};
+
+SimpleEnvMap coreaudio_map[] = {
+    { "QEMU_COREAUDIO_BUFFER_SIZE", "buffer",
+      ENV_TRANSFORM_FRAMES_TO_USECS_OUT },
+    { "QEMU_COREAUDIO_BUFFER_COUNT", "buffer-count" },
+
+    { /* End of list */ }
+};
+
+SimpleEnvMap dsound_map[] = {
+    { "QEMU_DSOUND_LATENCY_MILLIS", "latency", ENV_TRANSFORM_MILLIS_TO_USECS },
+    { "QEMU_DSOUND_BUFSIZE_OUT", "out.buffer",
+      ENV_TRANSFORM_BYTES_TO_USECS_OUT },
+    { "QEMU_DSOUND_BUFSIZE_IN", "in.buffer",
+      ENV_TRANSFORM_BYTES_TO_USECS_IN },
+
+    { /* End of list */ }
+};
+
+SimpleEnvMap oss_map[] = {
+    { "QEMU_AUDIO_DAC_TRY_POLL", "out.try-poll", ENV_TRANSFORM_BOOL },
+    { "QEMU_AUDIO_ADC_TRY_POLL", "in.try-poll", ENV_TRANSFORM_BOOL },
+
+    { "QEMU_OSS_FRAGSIZE", "buffer", ENV_TRANSFORM_BYTES_TO_USECS_OUT },
+    { "QEMU_OSS_NFRAGS", "buffer-count" },
+    { "QEMU_OSS_MMAP", "mmap", ENV_TRANSFORM_BOOL },
+    { "QEMU_OSS_DAC_DEV", "out.dev" },
+    { "QEMU_OSS_ADC_DEV", "in.dev" },
+    { "QEMU_OSS_EXCLUSIVE", "exclusive", ENV_TRANSFORM_BOOL },
+    { "QEMU_OSS_POLICY", "dsp-policy" },
+
+    { /* End of list */ }
+};
+
+SimpleEnvMap pa_map[] = {
+    { "QEMU_PA_SAMPLES", "buffer", ENV_TRANSFORM_SAMPLES_TO_USECS_OUT },
+    { "QEMU_PA_SERVER", "server" },
+    { "QEMU_PA_SINK", "sink" },
+    { "QEMU_PA_SOURCE", "source" },
+
+    { /* End of list */ }
+};
+
+SimpleEnvMap sdl_map[] = {
+    { "QEMU_SDL_SAMPLES", "buffer", ENV_TRANSFORM_SAMPLES_TO_USECS_OUT },
+    { /* End of list */ }
+};
+
+SimpleEnvMap wav_map[] = {
+    { "QEMU_WAV_FREQUENCY", "out.frequency" },
+    { "QEMU_WAV_FORMAT", "out.format", ENV_TRANSFORM_FMT },
+    { "QEMU_WAV_DAC_FIXED_CHANNELS", "out.channels" },
+    { "QEMU_WAV_PATH", "path" },
+    { /* End of list */ }
+};
+
+static unsigned long long toull(const char *str)
+{
+    unsigned long long ret;
+    if (parse_uint_full(str, &ret, 10)) {
+        dolog("Invalid boolean value `%s'\n", str);
+        exit(1);
+    }
+    return ret;
+}
+
+/* non reentrant typesafe or anything, but enough in this small c file */
+static const char *tostr(unsigned long long val)
+{
+    #define LEN ((CHAR_BIT * sizeof(int) - 1) / 3 + 2)
+    static char ret[LEN];
+    snprintf(ret, LEN, "%llu", val);
+    return ret;
+}
+
+static uint64_t frames_to_usecs(QemuOpts *opts, uint64_t frames, bool in)
+{
+    const char *opt = in ? "in.frequency" : "out.frequency";
+    uint64_t freq = qemu_opt_get_number(opts, opt, 44100);
+    return (frames * 1000000 + freq/2) / freq;
+}
+
+static uint64_t samples_to_usecs(QemuOpts *opts, uint64_t samples, bool in)
+{
+    const char *opt = in ? "in.channels" : "out.channels";
+    uint64_t channels = qemu_opt_get_number(opts, opt, 2);
+    return frames_to_usecs(opts, samples/channels, in);
+}
+
+static uint64_t bytes_to_usecs(QemuOpts *opts, uint64_t bytes, bool in)
+{
+    const char *opt = in ? "in.format" : "out.format";
+    const char *val = qemu_opt_get(opts, opt);
+    uint64_t bytes_per_sample = (val ? toull(val) : 16) / 8;
+    return samples_to_usecs(opts, bytes * bytes_per_sample, in);
+}
+
+static const char *transform_val(QemuOpts *opts, const char *val,
+                                 EnvTransform transform)
+{
+    switch (transform) {
+    case ENV_TRANSFORM_NONE:
+        return val;
+
+    case ENV_TRANSFORM_BOOL:
+        return toull(val) ? "on" : "off";
+
+    case ENV_TRANSFORM_FMT:
+        if (strcasecmp(val, "u8") == 0) {
+            return "u8";
+        } else if (strcasecmp(val, "u16") == 0) {
+            return "u16";
+        } else if (strcasecmp(val, "u32") == 0) {
+            return "u32";
+        } else if (strcasecmp(val, "s8") == 0) {
+            return "s8";
+        } else if (strcasecmp(val, "s16") == 0) {
+            return "s16";
+        } else if (strcasecmp(val, "s32") == 0) {
+            return "s32";
+        } else {
+            dolog("Invalid audio format `%s'\n", val);
+            exit(1);
+        }
+
+    case ENV_TRANSFORM_FRAMES_TO_USECS_IN:
+        return tostr(frames_to_usecs(opts, toull(val), true));
+    case ENV_TRANSFORM_FRAMES_TO_USECS_OUT:
+        return tostr(frames_to_usecs(opts, toull(val), false));
+
+    case ENV_TRANSFORM_SAMPLES_TO_USECS_IN:
+        return tostr(samples_to_usecs(opts, toull(val), true));
+    case ENV_TRANSFORM_SAMPLES_TO_USECS_OUT:
+        return tostr(samples_to_usecs(opts, toull(val), false));
+
+    case ENV_TRANSFORM_BYTES_TO_USECS_IN:
+        return tostr(bytes_to_usecs(opts, toull(val), true));
+    case ENV_TRANSFORM_BYTES_TO_USECS_OUT:
+        return tostr(bytes_to_usecs(opts, toull(val), false));
+
+    case ENV_TRANSFORM_MILLIS_TO_USECS:
+        return tostr(toull(val) * 1000);
+
+    case ENV_TRANSFORM_HZ_TO_USECS:
+        return tostr(1000000 / toull(val));
+    }
+
+    abort(); /* it's unreachable, gcc */
+}
+
+static void handle_env_opts(QemuOpts *opts, SimpleEnvMap *map)
+{
+    while (map->name) {
+        const char *val = getenv(map->name);
+
+        if (val) {
+            qemu_opt_set(opts, map->option,
+                         transform_val(opts, val, map->transform),
+                         &error_abort);
+        }
+
+        ++map;
+    }
+}
+
+static void handle_alsa_side(QemuOpts *opts, int period, int buffer,
+                             const char *usec_env, const char *period_env,
+                             const char *buffer_env, const char *usec_opt,
+                             const char *count_opt, bool in)
+{
+    char *usec_s, *period_s, *buffer_s;
+    bool usec = false;
+
+    usec_s = getenv("QEMU_ALSA_DAC_SIZE_IN_USEC");
+    if (usec_s) {
+        usec = toull(usec_s);
+    }
+
+    period_s = getenv(period_env);
+    if (period_s) {
+        period = toull(period_s);
+    }
+    if (!usec) {
+        period = frames_to_usecs(opts, period, in);
+    }
+    if (period_s) {
+        qemu_opt_set(opts, usec_opt, tostr(period), &error_abort);
+    }
+
+    buffer_s = getenv(buffer_env);
+    if (buffer_s) {
+        buffer = toull(buffer_s);
+        if (!usec) {
+            buffer = frames_to_usecs(opts, buffer, in);
+        }
+        printf("buffer %d period %d\n", buffer, period);
+        qemu_opt_set(opts, count_opt, tostr((buffer+period/2)/period),
+                     &error_abort);
+    }
+}
+
+static void handle_alsa(QemuOpts *opts)
+{
+    handle_alsa_side(opts, 1024, 4096,
+                     "QEMU_ALSA_DAC_SIZE_IN_USEC", "QEMU_ALSA_DAC_PERIOD_SIZE",
+                     "QEMU_ALSA_DAC_BUFFER_SIZE",
+                     "out.buffer", "out.buffer-count", false);
+    handle_alsa_side(opts, 0, 0,
+                     "QEMU_ALSA_ADC_SIZE_IN_USEC", "QEMU_ALSA_ADC_PERIOD_SIZE",
+                     "QEMU_ALSA_ADC_BUFFER_SIZE",
+                     "in.buffer", "in.buffer-count", true);
+}
+
+static void legacy_opt(const char *drv)
+{
+    QemuOpts *opts;
+    opts = qemu_opts_create(qemu_find_opts("audiodev"), drv, true,
+                            &error_abort);
+    qemu_opt_set(opts, "type", drv, &error_abort);
+
+    handle_env_opts(opts, global_map);
+
+    if (strcmp(drv, "alsa") == 0) {
+        handle_env_opts(opts, alsa_map);
+        handle_alsa(opts);
+    } else if (strcmp(drv, "oss") == 0) {
+        handle_env_opts(opts, oss_map);
+    } else if (strcmp(drv, "pa") == 0) {
+        handle_env_opts(opts, pa_map);
+    } else if (strcmp(drv, "sdl") == 0) {
+        handle_env_opts(opts, sdl_map);
+    } else if (strcmp(drv, "wav") == 0) {
+        handle_env_opts(opts, wav_map);
+    }
+}
+
+void audio_handle_legacy_opts(void)
+{
+    const char *drv = getenv("QEMU_AUDIO_DRV");
+
+    if (drv) {
+        legacy_opt(drv);
+    } else {
+        struct audio_driver **drv;
+        for (drv = drvtab; *drv; ++drv) {
+            if ((*drv)->can_be_default) {
+                legacy_opt((*drv)->name);
+            }
+        }
+    }
+}
+
+static int legacy_help_each(void *opaque, QemuOpts *opts, Error **errp)
+{
+    printf("-audiodev ");
+    qemu_opts_print(opts, ",");
+    printf("\n");
+    return 0;
+}
+
+void audio_legacy_help(void)
+{
+    printf("Environment variable based configuration deprecated.\n");
+    printf("Please use the new -audiodev option.\n");
+
+    audio_handle_legacy_opts();
+    printf("\nEquivalent -audiodev to your current environment variables:\n");
+    qemu_opts_foreach(qemu_find_opts("audiodev"), legacy_help_each, NULL, NULL);
+}
diff --git a/audio/audio_template.h b/audio/audio_template.h
index 99b27b2..096b2b3 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -302,8 +302,10 @@ static HW *glue (audio_pcm_hw_add_new_, TYPE) (struct audsettings *as)
 static HW *glue (audio_pcm_hw_add_, TYPE) (struct audsettings *as)
 {
     HW *hw;
+    AudioState *s = &glob_audio_state;
+    AudiodevPerDirectionOptions *pdo = s->dev->TYPE;
 
-    if (glue (conf.fixed_, TYPE).enabled && glue (conf.fixed_, TYPE).greedy) {
+    if (pdo->fixed_settings) {
         hw = glue (audio_pcm_hw_add_new_, TYPE) (as);
         if (hw) {
             return hw;
@@ -331,9 +333,11 @@ static SW *glue (audio_pcm_create_voice_pair_, TYPE) (
     SW *sw;
     HW *hw;
     struct audsettings hw_as;
+    AudioState *s = &glob_audio_state;
+    AudiodevPerDirectionOptions *pdo = s->dev->TYPE;
 
-    if (glue (conf.fixed_, TYPE).enabled) {
-        hw_as = glue (conf.fixed_, TYPE).settings;
+    if (pdo->fixed_settings) {
+        hw_as = audiodev_to_audsettings(pdo);
     }
     else {
         hw_as = *as;
@@ -398,6 +402,7 @@ SW *glue (AUD_open_, TYPE) (
     )
 {
     AudioState *s = &glob_audio_state;
+    AudiodevPerDirectionOptions *pdo = s->dev->TYPE;
 
     if (audio_bug (AUDIO_FUNC, !card || !name || !callback_fn || !as)) {
         dolog ("card=%p name=%p callback_fn=%p as=%p\n",
@@ -422,7 +427,7 @@ SW *glue (AUD_open_, TYPE) (
         return sw;
     }
 
-    if (!glue (conf.fixed_, TYPE).enabled && sw) {
+    if (!pdo->fixed_settings && sw) {
         glue (AUD_close_, TYPE) (card, sw);
         sw = NULL;
     }
diff --git a/audio/coreaudio.c b/audio/coreaudio.c
index 6dfd63e..dfa5e79 100644
--- a/audio/coreaudio.c
+++ b/audio/coreaudio.c
@@ -34,11 +34,6 @@
 
 static int isAtexit;
 
-typedef struct {
-    int buffer_frames;
-    int nbuffers;
-} CoreaudioConf;
-
 typedef struct coreaudioVoiceOut {
     HWVoiceOut hw;
     pthread_mutex_t mutex;
@@ -292,7 +287,9 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
     int err;
     const char *typ = "playback";
     AudioValueRange frameRange;
-    CoreaudioConf *conf = drv_opaque;
+    Audiodev *dev = drv_opaque;
+    AudiodevPerDirectionOptions *pdo = dev->out;
+    int frames;
 
     /* create mutex */
     err = pthread_mutex_init(&core->mutex, NULL);
@@ -334,16 +331,17 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
         return -1;
     }
 
-    if (frameRange.mMinimum > conf->buffer_frames) {
+    frames = audio_buffer_frames(pdo, as, 11610);
+    if (frameRange.mMinimum > frames) {
         core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum;
         dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum);
     }
-    else if (frameRange.mMaximum < conf->buffer_frames) {
+    else if (frameRange.mMaximum < frames) {
         core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum;
         dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum);
     }
     else {
-        core->audioDevicePropertyBufferFrameSize = conf->buffer_frames;
+        core->audioDevicePropertyBufferFrameSize = frames;
     }
 
     /* set Buffer Frame Size */
@@ -377,7 +375,8 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
                            "Could not get device buffer frame size\n");
         return -1;
     }
-    hw->samples = conf->nbuffers * core->audioDevicePropertyBufferFrameSize;
+    hw->samples = (pdo->has_buffer_count ? pdo->buffer_count : 4) *
+        core->audioDevicePropertyBufferFrameSize;
 
     /* get StreamFormat */
     propertySize = sizeof(core->outputStreamBasicDescription);
@@ -497,41 +496,16 @@ static int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...)
     return 0;
 }
 
-static CoreaudioConf glob_conf = {
-    .buffer_frames = 512,
-    .nbuffers = 4,
-};
-
-static void *coreaudio_audio_init (void)
+static void *coreaudio_audio_init(Audiodev *dev)
 {
-    CoreaudioConf *conf = g_malloc(sizeof(CoreaudioConf));
-    *conf = glob_conf;
-
     atexit(coreaudio_atexit);
-    return conf;
+    return dev;
 }
 
 static void coreaudio_audio_fini (void *opaque)
 {
-    g_free(opaque);
 }
 
-static struct audio_option coreaudio_options[] = {
-    {
-        .name  = "BUFFER_SIZE",
-        .tag   = AUD_OPT_INT,
-        .valp  = &glob_conf.buffer_frames,
-        .descr = "Size of the buffer in frames"
-    },
-    {
-        .name  = "BUFFER_COUNT",
-        .tag   = AUD_OPT_INT,
-        .valp  = &glob_conf.nbuffers,
-        .descr = "Number of buffers"
-    },
-    { /* End of list */ }
-};
-
 static struct audio_pcm_ops coreaudio_pcm_ops = {
     .init_out = coreaudio_init_out,
     .fini_out = coreaudio_fini_out,
@@ -543,7 +517,6 @@ static struct audio_pcm_ops coreaudio_pcm_ops = {
 struct audio_driver coreaudio_audio_driver = {
     .name           = "coreaudio",
     .descr          = "CoreAudio http://developer.apple.com/audio/coreaudio.html",
-    .options        = coreaudio_options,
     .init           = coreaudio_audio_init,
     .fini           = coreaudio_audio_fini,
     .pcm_ops        = &coreaudio_pcm_ops,
diff --git a/audio/dsound_template.h b/audio/dsound_template.h
index b439f33..96181ef 100644
--- a/audio/dsound_template.h
+++ b/audio/dsound_template.h
@@ -167,17 +167,18 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as,
     dsound *s = drv_opaque;
     WAVEFORMATEX wfx;
     struct audsettings obt_as;
-    DSoundConf *conf = &s->conf;
 #ifdef DSBTYPE_IN
     const char *typ = "ADC";
     DSoundVoiceIn *ds = (DSoundVoiceIn *) hw;
     DSCBUFFERDESC bd;
     DSCBCAPS bc;
+    AudiodevPerDirectionOptions *pdo = s->dev->in;
 #else
     const char *typ = "DAC";
     DSoundVoiceOut *ds = (DSoundVoiceOut *) hw;
     DSBUFFERDESC bd;
     DSBCAPS bc;
+    AudiodevPerDirectionOptions *pdo = s->dev->out;
 #endif
 
     if (!s->FIELD2) {
@@ -193,8 +194,8 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as,
     memset (&bd, 0, sizeof (bd));
     bd.dwSize = sizeof (bd);
     bd.lpwfxFormat = &wfx;
+    bd.dwBufferBytes = audio_buffer_bytes(pdo, as, 92880);
 #ifdef DSBTYPE_IN
-    bd.dwBufferBytes = conf->bufsize_in;
     hr = IDirectSoundCapture_CreateCaptureBuffer (
         s->dsound_capture,
         &bd,
@@ -203,7 +204,6 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as,
         );
 #else
     bd.dwFlags = DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2;
-    bd.dwBufferBytes = conf->bufsize_out;
     hr = IDirectSound_CreateSoundBuffer (
         s->dsound,
         &bd,
diff --git a/audio/dsoundaudio.c b/audio/dsoundaudio.c
index e9472c1..fdbf2e6 100644
--- a/audio/dsoundaudio.c
+++ b/audio/dsoundaudio.c
@@ -42,16 +42,10 @@
 /* #define DEBUG_DSOUND */
 
 typedef struct {
-    int bufsize_in;
-    int bufsize_out;
-    int latency_millis;
-} DSoundConf;
-
-typedef struct {
     LPDIRECTSOUND dsound;
     LPDIRECTSOUNDCAPTURE dsound_capture;
     struct audsettings settings;
-    DSoundConf conf;
+    Audiodev *dev;
 } dsound;
 
 typedef struct {
@@ -247,9 +241,9 @@ static void GCC_FMT_ATTR (3, 4) dsound_logerr2 (
     dsound_log_hresult (hr);
 }
 
-static DWORD millis_to_bytes (struct audio_pcm_info *info, DWORD millis)
+static uint64_t usecs_to_bytes(struct audio_pcm_info *info, uint32_t usecs)
 {
-    return (millis * info->bytes_per_second) / 1000;
+    return muldiv64(usecs, info->bytes_per_second, 1000000);
 }
 
 #ifdef DEBUG_DSOUND
@@ -477,7 +471,7 @@ static int dsound_run_out (HWVoiceOut *hw, int live)
     LPVOID p1, p2;
     int bufsize;
     dsound *s = ds->s;
-    DSoundConf *conf = &s->conf;
+    AudiodevDsoundOptions *dso = s->dev->opts->dsound;
 
     if (!dsb) {
         dolog ("Attempt to run empty with playback buffer\n");
@@ -500,14 +494,14 @@ static int dsound_run_out (HWVoiceOut *hw, int live)
     len = live << hwshift;
 
     if (ds->first_time) {
-        if (conf->latency_millis) {
+        if (dso->latency) {
             DWORD cur_blat;
 
             cur_blat = audio_ring_dist (wpos, ppos, bufsize);
             ds->first_time = 0;
             old_pos = wpos;
             old_pos +=
-                millis_to_bytes (&hw->info, conf->latency_millis) - cur_blat;
+                usecs_to_bytes(&hw->info, dso->latency) - cur_blat;
             old_pos %= bufsize;
             old_pos &= ~hw->info.align;
         }
@@ -746,12 +740,6 @@ static int dsound_run_in (HWVoiceIn *hw)
     return decr;
 }
 
-static DSoundConf glob_conf = {
-    .bufsize_in         = 16384,
-    .bufsize_out        = 16384,
-    .latency_millis     = 10
-};
-
 static void dsound_audio_fini (void *opaque)
 {
     HRESULT hr;
@@ -782,13 +770,22 @@ static void dsound_audio_fini (void *opaque)
     g_free(s);
 }
 
-static void *dsound_audio_init (void)
+static void *dsound_audio_init(Audiodev *dev)
 {
     int err;
     HRESULT hr;
     dsound *s = g_malloc0(sizeof(dsound));
+    AudiodevDsoundOptions *dso;
+
+    assert(dev->opts->kind == AUDIODEV_BACKEND_OPTIONS_KIND_DSOUND);
+    s->dev = dev;
+    dso = dev->opts->dsound;
+
+    if (!dso->has_latency) {
+        dso->has_latency = true;
+        dso->latency = 10000; /* 10 ms */
+    }
 
-    s->conf = glob_conf;
     hr = CoInitialize (NULL);
     if (FAILED (hr)) {
         dsound_logerr (hr, "Could not initialize COM\n");
@@ -853,28 +850,6 @@ static void *dsound_audio_init (void)
     return s;
 }
 
-static struct audio_option dsound_options[] = {
-    {
-        .name  = "LATENCY_MILLIS",
-        .tag   = AUD_OPT_INT,
-        .valp  = &glob_conf.latency_millis,
-        .descr = "(undocumented)"
-    },
-    {
-        .name  = "BUFSIZE_OUT",
-        .tag   = AUD_OPT_INT,
-        .valp  = &glob_conf.bufsize_out,
-        .descr = "(undocumented)"
-    },
-    {
-        .name  = "BUFSIZE_IN",
-        .tag   = AUD_OPT_INT,
-        .valp  = &glob_conf.bufsize_in,
-        .descr = "(undocumented)"
-    },
-    { /* End of list */ }
-};
-
 static struct audio_pcm_ops dsound_pcm_ops = {
     .init_out = dsound_init_out,
     .fini_out = dsound_fini_out,
@@ -892,7 +867,6 @@ static struct audio_pcm_ops dsound_pcm_ops = {
 struct audio_driver dsound_audio_driver = {
     .name           = "dsound",
     .descr          = "DirectSound http://wikipedia.org/wiki/DirectSound",
-    .options        = dsound_options,
     .init           = dsound_audio_init,
     .fini           = dsound_audio_fini,
     .pcm_ops        = &dsound_pcm_ops,
diff --git a/audio/noaudio.c b/audio/noaudio.c
index 50db1f3..4c94a26 100644
--- a/audio/noaudio.c
+++ b/audio/noaudio.c
@@ -134,7 +134,7 @@ static int no_ctl_in (HWVoiceIn *hw, int cmd, ...)
     return 0;
 }
 
-static void *no_audio_init (void)
+static void *no_audio_init (Audiodev *dev)
 {
     return &no_audio_init;
 }
@@ -161,7 +161,6 @@ static struct audio_pcm_ops no_pcm_ops = {
 struct audio_driver no_audio_driver = {
     .name           = "none",
     .descr          = "Timer based audio emulation",
-    .options        = NULL,
     .init           = no_audio_init,
     .fini           = no_audio_fini,
     .pcm_ops        = &no_pcm_ops,
diff --git a/audio/ossaudio.c b/audio/ossaudio.c
index 02a3a95..3cc04a7 100644
--- a/audio/ossaudio.c
+++ b/audio/ossaudio.c
@@ -29,6 +29,8 @@
 #include "qemu-common.h"
 #include "qemu/main-loop.h"
 #include "qemu/host-utils.h"
+#include "qapi/alloc-visitor.h"
+#include "qapi-visit.h"
 #include "audio.h"
 #include "trace.h"
 
@@ -39,16 +41,6 @@
 #define USE_DSP_POLICY
 #endif
 
-typedef struct OSSConf {
-    int try_mmap;
-    int nfrags;
-    int fragsize;
-    const char *devpath_out;
-    const char *devpath_in;
-    int exclusive;
-    int policy;
-} OSSConf;
-
 typedef struct OSSVoiceOut {
     HWVoiceOut hw;
     void *pcm_buf;
@@ -58,7 +50,7 @@ typedef struct OSSVoiceOut {
     int fragsize;
     int mmapped;
     int pending;
-    OSSConf *conf;
+    Audiodev *dev;
 } OSSVoiceOut;
 
 typedef struct OSSVoiceIn {
@@ -67,12 +59,12 @@ typedef struct OSSVoiceIn {
     int fd;
     int nfrags;
     int fragsize;
-    OSSConf *conf;
+    Audiodev *dev;
 } OSSVoiceIn;
 
 struct oss_params {
     int freq;
-    AudioFormat fmt;
+    int fmt;
     int nchannels;
     int nfrags;
     int fragsize;
@@ -264,19 +256,26 @@ static int oss_get_version (int fd, int *version, const char *typ)
 }
 #endif
 
-static int oss_open (int in, struct oss_params *req,
-                     struct oss_params *obt, int *pfd, OSSConf* conf)
+static int oss_open(int in, struct oss_params *req, audsettings *as,
+                    struct oss_params *obt, int *pfd, Audiodev *dev)
 {
+    AudiodevOssOptions *oopts = dev->opts->oss;
+    AudiodevOssPerDirectionOptions *opdo = in ? oopts->in : oopts->out;
+    AudiodevPerDirectionOptions *pdo = in ? dev->in : dev->out;
     int fd;
-    int oflags = conf->exclusive ? O_EXCL : 0;
+    int oflags = (oopts->has_exclusive && oopts->exclusive) ? O_EXCL : 0;
     audio_buf_info abinfo;
     int fmt, freq, nchannels;
     int setfragment = 1;
-    const char *dspname = in ? conf->devpath_in : conf->devpath_out;
+    const char *dspname = opdo->has_dev ? opdo->dev : "/dev/dsp";
     const char *typ = in ? "ADC" : "DAC";
+#ifdef USE_DSP_POLICY
+    int policy = oopts->has_dsp_policy ? oopts->dsp_policy : 5;
+#endif
 
     /* Kludge needed to have working mmap on Linux */
-    oflags |= conf->try_mmap ? O_RDWR : (in ? O_RDONLY : O_WRONLY);
+    oflags |= (oopts->has_mmap && oopts->mmap) ?
+        O_RDWR : (in ? O_RDONLY : O_WRONLY);
 
     fd = open (dspname, oflags | O_NONBLOCK);
     if (-1 == fd) {
@@ -287,6 +286,8 @@ static int oss_open (int in, struct oss_params *req,
     freq = req->freq;
     nchannels = req->nchannels;
     fmt = req->fmt;
+    req->nfrags = pdo->has_buffer_count ? pdo->buffer_count : 4;
+    req->fragsize = audio_buffer_bytes(pdo, as, 23220);
 
     if (ioctl (fd, SNDCTL_DSP_SAMPLESIZE, &fmt)) {
         oss_logerr2 (errno, typ, "Failed to set sample size %d\n", req->fmt);
@@ -310,18 +311,18 @@ static int oss_open (int in, struct oss_params *req,
     }
 
 #ifdef USE_DSP_POLICY
-    if (conf->policy >= 0) {
+    if (policy >= 0) {
         int version;
 
         if (!oss_get_version (fd, &version, typ)) {
             trace_oss_version(version);
 
             if (version >= 0x040000) {
-                int policy = conf->policy;
-                if (ioctl (fd, SNDCTL_DSP_POLICY, &policy)) {
+                int policy2 = policy;
+                if (ioctl (fd, SNDCTL_DSP_POLICY, &policy2)) {
                     oss_logerr2 (errno, typ,
                                  "Failed to set timing policy to %d\n",
-                                 conf->policy);
+                                 policy);
                     goto err;
                 }
                 setfragment = 0;
@@ -504,17 +505,16 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as,
     int fd;
     AudioFormat effective_fmt;
     struct audsettings obt_as;
-    OSSConf *conf = drv_opaque;
+    Audiodev *dev = drv_opaque;
+    AudiodevOssOptions *oopts = dev->opts->oss;
 
     oss->fd = -1;
 
     req.fmt = aud_to_ossfmt (as->fmt, as->endianness);
     req.freq = as->freq;
     req.nchannels = as->nchannels;
-    req.fragsize = conf->fragsize;
-    req.nfrags = conf->nfrags;
 
-    if (oss_open (0, &req, &obt, &fd, conf)) {
+    if (oss_open(0, &req, as, &obt, &fd, dev)) {
         return -1;
     }
 
@@ -541,7 +541,7 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as,
     hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift;
 
     oss->mmapped = 0;
-    if (conf->try_mmap) {
+    if (oopts->has_mmap && oopts->mmap) {
         oss->pcm_buf = mmap (
             NULL,
             hw->samples << hw->info.shift,
@@ -601,7 +601,7 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as,
     }
 
     oss->fd = fd;
-    oss->conf = conf;
+    oss->dev = dev;
     return 0;
 }
 
@@ -609,16 +609,12 @@ static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...)
 {
     int trig;
     OSSVoiceOut *oss = (OSSVoiceOut *) hw;
+    AudiodevOssPerDirectionOptions *opdo = oss->dev->opts->oss->out;
 
     switch (cmd) {
     case VOICE_ENABLE:
         {
-            va_list ap;
-            int poll_mode;
-
-            va_start (ap, cmd);
-            poll_mode = va_arg (ap, int);
-            va_end (ap);
+            bool poll_mode = !opdo->has_try_poll || opdo->try_poll;
 
             ldebug ("enabling voice\n");
             if (poll_mode) {
@@ -673,16 +669,14 @@ static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
     int fd;
     AudioFormat effective_fmt;
     struct audsettings obt_as;
-    OSSConf *conf = drv_opaque;
+    Audiodev *dev = drv_opaque;
 
     oss->fd = -1;
 
     req.fmt = aud_to_ossfmt (as->fmt, as->endianness);
     req.freq = as->freq;
     req.nchannels = as->nchannels;
-    req.fragsize = conf->fragsize;
-    req.nfrags = conf->nfrags;
-    if (oss_open (1, &req, &obt, &fd, conf)) {
+    if (oss_open(1, &req, as, &obt, &fd, dev)) {
         return -1;
     }
 
@@ -716,7 +710,7 @@ static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
     }
 
     oss->fd = fd;
-    oss->conf = conf;
+    oss->dev = dev;
     return 0;
 }
 
@@ -807,16 +801,12 @@ static int oss_read (SWVoiceIn *sw, void *buf, int size)
 static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...)
 {
     OSSVoiceIn *oss = (OSSVoiceIn *) hw;
+    AudiodevOssPerDirectionOptions *opdo = oss->dev->opts->oss->out;
 
     switch (cmd) {
     case VOICE_ENABLE:
         {
-            va_list ap;
-            int poll_mode;
-
-            va_start (ap, cmd);
-            poll_mode = va_arg (ap, int);
-            va_end (ap);
+            bool poll_mode = !opdo->has_try_poll || opdo->try_poll;
 
             if (poll_mode) {
                 oss_poll_in (hw);
@@ -836,81 +826,25 @@ static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...)
     return 0;
 }
 
-static OSSConf glob_conf = {
-    .try_mmap = 0,
-    .nfrags = 4,
-    .fragsize = 4096,
-    .devpath_out = "/dev/dsp",
-    .devpath_in = "/dev/dsp",
-    .exclusive = 0,
-    .policy = 5
-};
-
-static void *oss_audio_init (void)
+static void *oss_audio_init(Audiodev *dev)
 {
-    OSSConf *conf = g_malloc(sizeof(OSSConf));
-    *conf = glob_conf;
+    AudiodevOssOptions *oopts;
+    assert(dev->opts->kind == AUDIODEV_BACKEND_OPTIONS_KIND_OSS);
 
-    if (access(conf->devpath_in, R_OK | W_OK) < 0 ||
-        access(conf->devpath_out, R_OK | W_OK) < 0) {
+    oopts = dev->opts->oss;
+    if (access(oopts->in->has_dev ? oopts->in->dev : "/dev/dsp",
+               R_OK | W_OK) < 0 ||
+        access(oopts->out->has_dev ? oopts->out->dev : "/dev/dsp",
+               R_OK | W_OK) < 0) {
         return NULL;
     }
-    return conf;
+    return dev;
 }
 
 static void oss_audio_fini (void *opaque)
 {
-    g_free(opaque);
 }
 
-static struct audio_option oss_options[] = {
-    {
-        .name  = "FRAGSIZE",
-        .tag   = AUD_OPT_INT,
-        .valp  = &glob_conf.fragsize,
-        .descr = "Fragment size in bytes"
-    },
-    {
-        .name  = "NFRAGS",
-        .tag   = AUD_OPT_INT,
-        .valp  = &glob_conf.nfrags,
-        .descr = "Number of fragments"
-    },
-    {
-        .name  = "MMAP",
-        .tag   = AUD_OPT_BOOL,
-        .valp  = &glob_conf.try_mmap,
-        .descr = "Try using memory mapped access"
-    },
-    {
-        .name  = "DAC_DEV",
-        .tag   = AUD_OPT_STR,
-        .valp  = &glob_conf.devpath_out,
-        .descr = "Path to DAC device"
-    },
-    {
-        .name  = "ADC_DEV",
-        .tag   = AUD_OPT_STR,
-        .valp  = &glob_conf.devpath_in,
-        .descr = "Path to ADC device"
-    },
-    {
-        .name  = "EXCLUSIVE",
-        .tag   = AUD_OPT_BOOL,
-        .valp  = &glob_conf.exclusive,
-        .descr = "Open device in exclusive mode (vmix wont work)"
-    },
-#ifdef USE_DSP_POLICY
-    {
-        .name  = "POLICY",
-        .tag   = AUD_OPT_INT,
-        .valp  = &glob_conf.policy,
-        .descr = "Set the timing policy of the device, -1 to use fragment mode",
-    },
-#endif
-    { /* End of list */ }
-};
-
 static struct audio_pcm_ops oss_pcm_ops = {
     .init_out = oss_init_out,
     .fini_out = oss_fini_out,
@@ -928,7 +862,6 @@ static struct audio_pcm_ops oss_pcm_ops = {
 struct audio_driver oss_audio_driver = {
     .name           = "oss",
     .descr          = "OSS http://www.opensound.com",
-    .options        = oss_options,
     .init           = oss_audio_init,
     .fini           = oss_audio_fini,
     .pcm_ops        = &oss_pcm_ops,
diff --git a/audio/paaudio.c b/audio/paaudio.c
index cfdbdc6..60afb04 100644
--- a/audio/paaudio.c
+++ b/audio/paaudio.c
@@ -1,6 +1,8 @@
 /* public domain */
 #include "qemu-common.h"
 #include "audio.h"
+#include "qapi/alloc-visitor.h"
+#include "qapi-visit.h"
 
 #include <pulse/pulseaudio.h>
 
@@ -9,14 +11,7 @@
 #include "audio_pt_int.h"
 
 typedef struct {
-    int samples;
-    char *server;
-    char *sink;
-    char *source;
-} PAConf;
-
-typedef struct {
-    PAConf conf;
+    Audiodev *dev;
     pa_threaded_mainloop *mainloop;
     pa_context *context;
 } paaudio;
@@ -31,6 +26,7 @@ typedef struct {
     void *pcm_buf;
     struct audio_pt pt;
     paaudio *g;
+    int samples;
 } PAVoiceOut;
 
 typedef struct {
@@ -45,6 +41,7 @@ typedef struct {
     const void *read_data;
     size_t read_index, read_length;
     paaudio *g;
+    int samples;
 } PAVoiceIn;
 
 static void qpa_audio_fini(void *opaque);
@@ -226,7 +223,7 @@ static void *qpa_thread_out (void *arg)
             }
         }
 
-        decr = to_mix = audio_MIN (pa->live, pa->g->conf.samples >> 2);
+        decr = to_mix = audio_MIN (pa->live, pa->samples >> 2);
         rpos = pa->rpos;
 
         if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) {
@@ -318,7 +315,7 @@ static void *qpa_thread_in (void *arg)
             }
         }
 
-        incr = to_grab = audio_MIN (pa->dead, pa->g->conf.samples >> 2);
+        incr = to_grab = audio_MIN (pa->dead, pa->samples >> 2);
         wpos = pa->wpos;
 
         if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) {
@@ -545,6 +542,7 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as,
     struct audsettings obt_as = *as;
     PAVoiceOut *pa = (PAVoiceOut *) hw;
     paaudio *g = pa->g = drv_opaque;
+    AudiodevPaOptions *popts = g->dev->opts->pa;
 
     ss.format = audfmt_to_pa (as->fmt, as->endianness);
     ss.channels = as->nchannels;
@@ -565,7 +563,7 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as,
         g,
         "qemu",
         PA_STREAM_PLAYBACK,
-        g->conf.sink,
+        popts->has_sink ? popts->sink : NULL,
         &ss,
         NULL,                   /* channel map */
         &ba,                    /* buffering attributes */
@@ -577,7 +575,8 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as,
     }
 
     audio_pcm_init_info (&hw->info, &obt_as);
-    hw->samples = g->conf.samples;
+    hw->samples = pa->samples = audio_buffer_samples(g->dev->out, &obt_as,
+                                                     46440);
     pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
     pa->rpos = hw->rpos;
     if (!pa->pcm_buf) {
@@ -611,6 +610,7 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
     struct audsettings obt_as = *as;
     PAVoiceIn *pa = (PAVoiceIn *) hw;
     paaudio *g = pa->g = drv_opaque;
+    AudiodevPaOptions *popts = g->dev->opts->pa;
 
     ss.format = audfmt_to_pa (as->fmt, as->endianness);
     ss.channels = as->nchannels;
@@ -622,7 +622,7 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
         g,
         "qemu",
         PA_STREAM_RECORD,
-        g->conf.source,
+        popts->has_source ? popts->source : NULL,
         &ss,
         NULL,                   /* channel map */
         NULL,                   /* buffering attributes */
@@ -634,7 +634,8 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
     }
 
     audio_pcm_init_info (&hw->info, &obt_as);
-    hw->samples = g->conf.samples;
+    hw->samples = pa->samples = audio_buffer_samples(g->dev->in, &obt_as,
+                                                     46440);
     pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
     pa->wpos = hw->wpos;
     if (!pa->pcm_buf) {
@@ -808,14 +809,19 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...)
 }
 
 /* common */
-static PAConf glob_conf = {
-    .samples = 4096,
-};
-
-static void *qpa_audio_init (void)
+static void *qpa_audio_init(Audiodev *dev)
 {
-    paaudio *g = g_malloc(sizeof(paaudio));
-    g->conf = glob_conf;
+    paaudio *g;
+    AudiodevPaOptions *popts;
+    const char *server;
+
+    assert(dev->opts->kind == AUDIODEV_BACKEND_OPTIONS_KIND_PA);
+
+    g = g_malloc(sizeof(paaudio));
+    popts = dev->opts->pa;
+    server = popts->has_server ? popts->server : NULL;
+
+    g->dev = dev;
     g->mainloop = NULL;
     g->context = NULL;
 
@@ -825,14 +831,14 @@ static void *qpa_audio_init (void)
     }
 
     g->context = pa_context_new (pa_threaded_mainloop_get_api (g->mainloop),
-                                 g->conf.server);
+                                 server);
     if (!g->context) {
         goto fail;
     }
 
     pa_context_set_state_callback (g->context, context_state_cb, g);
 
-    if (pa_context_connect (g->context, g->conf.server, 0, NULL) < 0) {
+    if (pa_context_connect (g->context, server, 0, NULL) < 0) {
         qpa_logerr (pa_context_errno (g->context),
                     "pa_context_connect() failed\n");
         goto fail;
@@ -895,34 +901,6 @@ static void qpa_audio_fini (void *opaque)
     g_free(g);
 }
 
-struct audio_option qpa_options[] = {
-    {
-        .name  = "SAMPLES",
-        .tag   = AUD_OPT_INT,
-        .valp  = &glob_conf.samples,
-        .descr = "buffer size in samples"
-    },
-    {
-        .name  = "SERVER",
-        .tag   = AUD_OPT_STR,
-        .valp  = &glob_conf.server,
-        .descr = "server address"
-    },
-    {
-        .name  = "SINK",
-        .tag   = AUD_OPT_STR,
-        .valp  = &glob_conf.sink,
-        .descr = "sink device name"
-    },
-    {
-        .name  = "SOURCE",
-        .tag   = AUD_OPT_STR,
-        .valp  = &glob_conf.source,
-        .descr = "source device name"
-    },
-    { /* End of list */ }
-};
-
 static struct audio_pcm_ops qpa_pcm_ops = {
     .init_out = qpa_init_out,
     .fini_out = qpa_fini_out,
@@ -940,7 +918,6 @@ static struct audio_pcm_ops qpa_pcm_ops = {
 struct audio_driver pa_audio_driver = {
     .name           = "pa",
     .descr          = "http://www.pulseaudio.org/",
-    .options        = qpa_options,
     .init           = qpa_audio_init,
     .fini           = qpa_audio_fini,
     .pcm_ops        = &qpa_pcm_ops,
diff --git a/audio/sdlaudio.c b/audio/sdlaudio.c
index db0f95a..796238a 100644
--- a/audio/sdlaudio.c
+++ b/audio/sdlaudio.c
@@ -44,18 +44,13 @@ typedef struct SDLVoiceOut {
     int decr;
 } SDLVoiceOut;
 
-static struct {
-    int nb_samples;
-} conf = {
-    .nb_samples = 1024
-};
-
 static struct SDLAudioState {
     int exit;
     SDL_mutex *mutex;
     SDL_sem *sem;
     int initialized;
     bool driver_created;
+    Audiodev *dev;
 } glob_sdl;
 typedef struct SDLAudioState SDLAudioState;
 
@@ -347,7 +342,7 @@ static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as,
     req.freq = as->freq;
     req.format = aud_to_sdlfmt (as->fmt);
     req.channels = as->nchannels;
-    req.samples = conf.nb_samples;
+    req.samples = audio_buffer_samples(s->dev->out, as, 11610);
     req.callback = sdl_callback;
     req.userdata = sdl;
 
@@ -391,7 +386,7 @@ static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...)
     return 0;
 }
 
-static void *sdl_audio_init (void)
+static void *sdl_audio_init(Audiodev *dev)
 {
     SDLAudioState *s = &glob_sdl;
     if (s->driver_created) {
@@ -420,6 +415,7 @@ static void *sdl_audio_init (void)
     }
 
     s->driver_created = true;
+    s->dev = dev;
     return s;
 }
 
@@ -431,18 +427,9 @@ static void sdl_audio_fini (void *opaque)
     SDL_DestroyMutex (s->mutex);
     SDL_QuitSubSystem (SDL_INIT_AUDIO);
     s->driver_created = false;
+    s->dev = NULL;
 }
 
-static struct audio_option sdl_options[] = {
-    {
-        .name  = "SAMPLES",
-        .tag   = AUD_OPT_INT,
-        .valp  = &conf.nb_samples,
-        .descr = "Size of SDL buffer in samples"
-    },
-    { /* End of list */ }
-};
-
 static struct audio_pcm_ops sdl_pcm_ops = {
     .init_out = sdl_init_out,
     .fini_out = sdl_fini_out,
@@ -454,7 +441,6 @@ static struct audio_pcm_ops sdl_pcm_ops = {
 struct audio_driver sdl_audio_driver = {
     .name           = "sdl",
     .descr          = "SDL http://www.libsdl.org",
-    .options        = sdl_options,
     .init           = sdl_audio_init,
     .fini           = sdl_audio_fini,
     .pcm_ops        = &sdl_pcm_ops,
diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c
index f556b3b..441fbcb 100644
--- a/audio/spiceaudio.c
+++ b/audio/spiceaudio.c
@@ -74,7 +74,7 @@ static const SpiceRecordInterface record_sif = {
     .base.minor_version = SPICE_INTERFACE_RECORD_MINOR,
 };
 
-static void *spice_audio_init (void)
+static void *spice_audio_init(Audiodev *dev)
 {
     if (!using_spice) {
         return NULL;
@@ -370,10 +370,6 @@ static int line_in_ctl (HWVoiceIn *hw, int cmd, ...)
     return 0;
 }
 
-static struct audio_option audio_options[] = {
-    { /* end of list */ },
-};
-
 static struct audio_pcm_ops audio_callbacks = {
     .init_out = line_out_init,
     .fini_out = line_out_fini,
@@ -391,7 +387,6 @@ static struct audio_pcm_ops audio_callbacks = {
 struct audio_driver spice_audio_driver = {
     .name           = "spice",
     .descr          = "spice audio driver",
-    .options        = audio_options,
     .init           = spice_audio_init,
     .fini           = spice_audio_fini,
     .pcm_ops        = &audio_callbacks,
diff --git a/audio/wavaudio.c b/audio/wavaudio.c
index 62017de..fa807b9 100644
--- a/audio/wavaudio.c
+++ b/audio/wavaudio.c
@@ -23,6 +23,8 @@
  */
 #include "hw/hw.h"
 #include "qemu/timer.h"
+#include "qapi/alloc-visitor.h"
+#include "qapi-visit.h"
 #include "audio.h"
 
 #define AUDIO_CAP "wav"
@@ -36,11 +38,6 @@ typedef struct WAVVoiceOut {
     int total_samples;
 } WAVVoiceOut;
 
-typedef struct {
-    struct audsettings settings;
-    const char *wav_path;
-} WAVConf;
-
 static int wav_run_out (HWVoiceOut *hw, int live)
 {
     WAVVoiceOut *wav = (WAVVoiceOut *) hw;
@@ -111,8 +108,10 @@ static int wav_init_out(HWVoiceOut *hw, struct audsettings *as,
         0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04,
         0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00
     };
-    WAVConf *conf = drv_opaque;
-    struct audsettings wav_as = conf->settings;
+    Audiodev *dev = drv_opaque;
+    AudiodevWavOptions *wopts = dev->opts->wav;
+    struct audsettings wav_as = audiodev_to_audsettings(dev->out);
+    const char *wav_path = wopts->has_path ? wopts->path : "qemu.wav";
 
     stereo = wav_as.nchannels == 2;
     switch (wav_as.fmt) {
@@ -153,10 +152,10 @@ static int wav_init_out(HWVoiceOut *hw, struct audsettings *as,
     le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4);
     le_store (hdr + 32, 1 << (bits16 + stereo), 2);
 
-    wav->f = fopen (conf->wav_path, "wb");
+    wav->f = fopen(wav_path, "wb");
     if (!wav->f) {
         dolog ("Failed to open wave file `%s'\nReason: %s\n",
-               conf->wav_path, strerror (errno));
+               wav_path, strerror(errno));
         g_free (wav->pcm_buf);
         wav->pcm_buf = NULL;
         return -1;
@@ -224,54 +223,17 @@ static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...)
     return 0;
 }
 
-static WAVConf glob_conf = {
-    .settings.freq      = 44100,
-    .settings.nchannels = 2,
-    .settings.fmt       = AUDIO_FORMAT_S16,
-    .wav_path           = "qemu.wav"
-};
-
-static void *wav_audio_init (void)
+static void *wav_audio_init(Audiodev *dev)
 {
-    WAVConf *conf = g_malloc(sizeof(WAVConf));
-    *conf = glob_conf;
-    return conf;
+    assert(dev->opts->kind == AUDIODEV_BACKEND_OPTIONS_KIND_WAV);
+    return dev;
 }
 
 static void wav_audio_fini (void *opaque)
 {
     ldebug ("wav_fini");
-    g_free(opaque);
 }
 
-static struct audio_option wav_options[] = {
-    {
-        .name  = "FREQUENCY",
-        .tag   = AUD_OPT_INT,
-        .valp  = &glob_conf.settings.freq,
-        .descr = "Frequency"
-    },
-    {
-        .name  = "FORMAT",
-        .tag   = AUD_OPT_FMT,
-        .valp  = &glob_conf.settings.fmt,
-        .descr = "Format"
-    },
-    {
-        .name  = "DAC_FIXED_CHANNELS",
-        .tag   = AUD_OPT_INT,
-        .valp  = &glob_conf.settings.nchannels,
-        .descr = "Number of channels (1 - mono, 2 - stereo)"
-    },
-    {
-        .name  = "PATH",
-        .tag   = AUD_OPT_STR,
-        .valp  = &glob_conf.wav_path,
-        .descr = "Path to wave file"
-    },
-    { /* End of list */ }
-};
-
 static struct audio_pcm_ops wav_pcm_ops = {
     .init_out = wav_init_out,
     .fini_out = wav_fini_out,
@@ -283,7 +245,6 @@ static struct audio_pcm_ops wav_pcm_ops = {
 struct audio_driver wav_audio_driver = {
     .name           = "wav",
     .descr          = "WAV renderer http://wikipedia.org/wiki/WAV",
-    .options        = wav_options,
     .init           = wav_audio_init,
     .fini           = wav_audio_fini,
     .pcm_ops        = &wav_pcm_ops,
diff --git a/qemu-options.hx b/qemu-options.hx
index 5438f98..2889ea4 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -312,14 +312,226 @@ The default is @code{en-us}.
 ETEXI
 
 
+HXCOMM Deprecated by -audiodev
 DEF("audio-help", 0, QEMU_OPTION_audio_help,
-    "-audio-help     print list of audio drivers and their options\n",
+    "-audio-help     show -audiodev equivalent of the current audio settings\n",
     QEMU_ARCH_ALL)
 STEXI
 @item -audio-help
 @findex -audio-help
-Will show the audio subsystem help: list of drivers, tunable
-parameters.
+Will show the -audiodev equivalent of the currently specified
+(deprecated) environment variables.
+ETEXI
+
+DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev,
+    "-audiodev driver[,prop[=value][,...]]\n"
+    "                specifies the audio backend to use\n"
+    "                id= identifier of the backend\n"
+    "                timer-period= timer period in microseconds\n"
+    "                [in.|out.]fixed-settings= use fixed settings for host audio\n"
+    "                [in.|out.]frequency= frequency to use with fixed settings\n"
+    "                [in.|out.]channels= number of channels to use with fixed settings\n"
+    "                [in.|out.]format= sample format to use with fixed settings\n"
+    "                valid values: s8, s16, s32, u8, u16, u32\n"
+    "                [in.|out.]voices= number of voices to use\n"
+    "                [in.|out.]buffer= size of buffer in microseconds\n"
+    "                [in.|out.]buffer-count= number of buffers\n"
+    "-audiodev none[,prop[=value][,...]]\n"
+    "                dummy driver that discards all output\n"
+#ifdef CONFIG_ALSA
+    "-audiodev alsa[,prop[=value][,...]]\n"
+    "                [in.|out]dev= name of the audio device to use\n"
+    "                [in.|out]try-poll= attempt to use poll mode\n"
+    "                threshold= threshold (in frames) when playback starts\n"
+#endif
+#ifdef CONFIG_COREAUDIO
+    "-audiodev coreaudio[,prop[=value][,...]]\n"
+#endif
+#ifdef CONFIG_DSOUND
+    "-audiodev dsound[,prop[=value][,...]]\n"
+    "                latency= add extra latency to playback in microseconds\n"
+#endif
+#ifdef CONFIG_OSS
+    "-audiodev oss[,prop[=value][,...]]\n"
+    "                [in.|out]dev= path of the audio device to use\n"
+    "                [in.|out]try-poll= attempt to use poll mode\n"
+    "                mmap= try using memory mapped access\n"
+    "                exclusive= open device in exclusive mode\n"
+    "                dsp-policy= set timing policy, -1 to use fragment mode\n"
+#endif
+#ifdef CONFIG_PA
+    "-audiodev pa[,prop[=value][,...]]\n"
+    "                server= PulseAudio server address\n"
+    "                sink= sink device name\n"
+    "                source= source device name\n"
+#endif
+#ifdef CONFIG_SDL
+    "-audiodev sdl[,prop[=value][,...]]\n"
+#endif
+#ifdef CONFIG_SPICE
+    "-audiodev spice[,prop[=value][,...]]\n"
+#endif
+    "-audiodev wav[,prop[=value][,...]]\n"
+    "                path= path of wav file to record\n",
+    QEMU_ARCH_ALL)
+STEXI
+@item -audiodev @var{driver}[,@var{prop}[=@var{value}][,...]]
+@findex -audiodev
+Adds a new audio backend @var{driver}.  There are global and driver
+specific properties.  Some values can be set differently for input and
+output, they're marked with @code{[in.|out.]}.  To only set the input's
+option prefix the property with @code{in.}, to only set the output
+prefix it with @code{out.}, or leave them to specify both.  Thus the two
+examples are equal:
+@example
+-audiodev alsa,in.frequency=44110,out.frequency=44110
+-audiodev alsa,frequency=44100
+@end example
+
+Valid global options are:
+
+@table @option
+@item id=@var{identifier}
+Identifies the audio backend.
+
+@item timer-period=@var{period}
+Sets the timer @var{period} used by the audio subsystem in microseconds.
+
+@item [in.|out.]fixed-settings=on|off
+Use fixed settings for host audio.  When off, it will change based on
+how the guest opens the sound card.
+
+@item [in.|out.]frequency=@var{frequency}
+Specify the @var{frequency} to use when using @var{fixed-settings}.
+
+@item [in.|out.]channels=@var{channels}
+Specify the number of @var{channels} to use when using
+@var{fixed-settings}.
+
+@item [in.|out.]format=@var{format}
+Specify the sample @var{format} to use when using @var{fixed-settings}.
+Valid values are: @code{s8}, @code{s16}, @code{s32}, @code{u8},
+@code{u16}, @code{u32}.
+
+@item [in.|out.]voices=@var{voices}
+Specify the number of @var{voices} to use.
+
+@item [in.|out.]buffer=@var{usecs}
+Sets the size of the buffer in microseconds.
+
+@item [in.|out.]buffer-count=@var{count}
+Sets the @var{count} of the buffers.
+
+@end table
+
+@item -audiodev none[,@var{prop}[=@var{value}][,...]]
+Creates a dummy backend that discards all outputs.  This backend has no
+backend specific properties.
+
+@item -audiodev alsa[,@var{prop}[=@var{value}][,...]]
+Creates backend using the ALSA.  This backend is only available on
+Linux.
+
+ALSA specific options are:
+
+@table @option
+@item [in.|out.]dev=@var{device}
+Specify the ALSA @var{device} to use for input and/or output.
+
+@item [in.|out.]try-poll=on|of
+Attempt to use poll mode with the device.
+
+@item threshold=@var{threshold}
+Threshold (in frames) when playback starts.
+
+@end table
+
+@item -audiodev coreaudio[,@var{prop}[=@var{value}][,...]]
+Creates a backend using Apple's Core Audio.  This backend is only
+available on Mac OS and only supports playback.  This backend has no
+backend specific properties.
+
+@item -audiodev dsound[,@var{prop}[=@var{value}][,...]]
+Creates a backend using Microsoft's DirectSound.  This backend is only
+available on Windows and only supports playback.
+
+Backend specific options are:
+
+@table @option
+
+@item latency=@var{usecs}
+Add extra @var{usecs} microseconds latency to playback.
+
+@end table
+
+@item -audiodev oss[,@var{prop}[=@var{value}][,...]]
+Creates a backend using OSS.  This backend is available on most
+Unix-like systems.
+
+OSS specific options are:
+
+@table @option
+
+@item [in.|out.]dev=@var{path}
+Specify the @var{path} of the OSS device to use.
+
+@item [in.|out.]try-poll=on|of
+Attempt to use poll mode with the device.
+
+@item mmap=on|off
+Try using memory mapped device access.
+
+@item exclusive=on|off
+Open the device in exclusive mode (vmix won't work in this case).
+
+@item dsp-policy=@var{policy}
+Sets the timing policy (between 0 and 10).  Use -1 to use buffer sizes
+specified by @code{buffer} and @code{buffer-count}.  This option is
+ignored if you do not have OSS 4.
+
+@end table
+
+@item -audiodev pa[,@var{prop}[=@var{value}][,...]]
+Creates a backend using PulseAudio.  This backend is available on most
+systems.
+
+PulseAudio specific options are:
+
+@table @option
+
+@item server=@var{server}
+Sets the PulseAudio @var{server} to connect to.
+
+@item sink=@var{sink}
+Use the specified @var{sink} for playback.
+
+@item source=@var{source}
+Use the specified @var{source} for recording.
+
+@end table
+
+@item -audiodev sdl[,@var{prop}[=@var{value}][,...]]
+Creates a backend using SDL.  This backend is available on most systems,
+but you should use your platform's native backend if possible.  This
+backend has no backend specific properties.
+
+@item -audiodev spice[,@var{prop}[=@var{value}][,...]]
+Creates a backend that sends audio through SPICE.  This backend requires
+@code{-spice} and automatically selected in that case, so usually you
+can ignore this option.  This backend has no backend specific
+properties.
+
+@item -audiodev wav[,@var{prop}[=@var{value}][,...]]
+Creates a backend that writes audio to a WAV file.
+
+Backend specific options are:
+
+@table @option
+
+@item path=@var{path}
+Write recorded audio into the specified file.
+
+@end table
 ETEXI
 
 DEF("soundhw", HAS_ARG, QEMU_OPTION_soundhw,
diff --git a/vl.c b/vl.c
index 2201e27..c3d6ef9 100644
--- a/vl.c
+++ b/vl.c
@@ -2873,6 +2873,7 @@ int main(int argc, char **argv, char **envp)
     qemu_add_opts(&qemu_trace_opts);
     qemu_add_opts(&qemu_option_rom_opts);
     qemu_add_opts(&qemu_machine_opts);
+    qemu_add_opts(&qemu_audiodev_opts);
     qemu_add_opts(&qemu_mem_opts);
     qemu_add_opts(&qemu_smp_opts);
     qemu_add_opts(&qemu_boot_opts);
@@ -3170,9 +3171,14 @@ int main(int argc, char **argv, char **envp)
                 add_device_config(DEV_BT, optarg);
                 break;
             case QEMU_OPTION_audio_help:
-                AUD_help ();
+                audio_legacy_help();
                 exit (0);
                 break;
+            case QEMU_OPTION_audiodev:
+                if (!qemu_opts_parse(qemu_find_opts("audiodev"), optarg, 1)) {
+                    exit(1);
+                }
+                break;
             case QEMU_OPTION_soundhw:
                 select_soundhw (optarg);
                 break;
@@ -4339,6 +4345,7 @@ int main(int argc, char **argv, char **envp)
 
     realtime_init();
 
+    audio_set_options();
     audio_init();
 
     cpu_synchronize_all_post_init();
