qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH 10/25] audio: -audiodev command line option


From: Marc-André Lureau
Subject: Re: [Qemu-devel] [PATCH 10/25] audio: -audiodev command line option
Date: Thu, 20 Aug 2015 19:17:38 +0200

Hi

On Thu, Aug 6, 2015 at 8:28 PM, Kővágó, Zoltán <address@hidden> wrote:
> This patch adds an -audiodev command line option, and deprecates the
> QEMU_* environment variables for audio backend configuration.  It's

"its" or simply "the"

> syntax is similar to existing options (-netdev, -device, etc): -audiodev
> driver_name,property=value,...
>
> Audio drivers now get an Audiodev * as config paramters, instead of the
> global audio_option structs.  There is some code in audio/audio_legacy.c
> that converts the old environment variables to audiodev options (this
> way backends do not have to worry about legacy options).  It also
> contains a replacement of -audio-help, which prints out the equivalent
> -audiodev based config of the currently specified environment variables.

I guess the option should be deprecated though, perhaps not even
visible in -help.

>
> Although now it's possible to specify multiple -audiodev options on
> command line, multiple audio backends are not supported yet.
>
> Signed-off-by: Kővágó, Zoltán <address@hidden>
> ---
>  audio/Makefile.objs     |   2 +-
>  audio/alsaaudio.c       | 311 ++++++--------------
>  audio/audio.c           | 760 
> ++++++++++++++----------------------------------
>  audio/audio.h           |  23 +-
>  audio/audio_int.h       |  23 +-
>  audio/audio_legacy.c    | 328 +++++++++++++++++++++
>  audio/audio_template.h  |  13 +-
>  audio/coreaudio.c       |  49 +---
>  audio/dsound_template.h |   6 +-
>  audio/dsoundaudio.c     |  60 ++--
>  audio/noaudio.c         |   3 +-
>  audio/ossaudio.c        | 155 +++-------
>  audio/paaudio.c         |  82 ++----
>  audio/sdlaudio.c        |  24 +-
>  audio/spiceaudio.c      |   7 +-
>  audio/wavaudio.c        |  60 +---
>  qemu-options.hx         | 226 +++++++++++++-
>  vl.c                    |  10 +-
>  18 files changed, 1015 insertions(+), 1127 deletions(-)
>  create mode 100644 audio/audio_legacy.c
>

Quite a large patch, perhaps it could be splitted?

> 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 2b28b99..cfe4aec 100644
> --- a/audio/alsaaudio.c
> +++ b/audio/alsaaudio.c
> @@ -22,6 +22,7 @@
>   * THE SOFTWARE.
>   */
>  #include <alsa/asoundlib.h>
> +#include "qapi-visit.h"
>  #include "qemu-common.h"
>  #include "qemu/main-loop.h"
>  #include "audio.h"
> @@ -34,28 +35,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 +49,7 @@ typedef struct ALSAVoiceOut {
>      void *pcm_buf;
>      snd_pcm_t *handle;
>      struct pollhlp pollhlp;
> +    Audiodev *dev;
>  } ALSAVoiceOut;
>
>  typedef struct ALSAVoiceIn {
> @@ -74,16 +57,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 +389,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 +398,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_len ? pdo->buffer_len : 0,
> +           pdo->has_buffer_len ? pdo->buffer_len : 0);

buffer size & buffer count are both buffer_len here, you should fix this

>      dolog ("obtained: samples %ld\n", obt->samples);
>  }
>
> @@ -452,23 +434,25 @@ 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->alsa;
> +    AudiodevAlsaPerDirectionOptions *apdo =
> +        in ? aopts->alsa_in : aopts->alsa_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_len) {
> +            int64_t req = pdo->buffer_len * 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_len) {
> +        int dir = 0;
> +        unsigned int ptime = pdo->buffer_len;
>
> -        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_len);
>              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_len && ptime != pdo->buffer_len) {
> +            dolog("Requested period time %" PRId64 " was rejected, using 
> %d\n",
> +                  pdo->buffer_len, ptime);
> +        }
>      }
>
>      err = snd_pcm_hw_params (handle, hw_params);
> @@ -632,33 +586,10 @@ static int alsa_open (int in, struct alsa_params_req 
> *req,
>          goto err;
>      }
>
> -    if (!in && conf->threshold) {
> -        snd_pcm_uframes_t threshold;
> -        int bytes_per_sec;
> -
> -        bytes_per_sec = freq << (nchannels == 2);
> -
> -        switch (obt->fmt) {
> -        case AUDIO_FORMAT_S8:
> -        case AUDIO_FORMAT_U8:
> -            break;
> -
> -        case AUDIO_FORMAT_S16:
> -        case AUDIO_FORMAT_U16:
> -            bytes_per_sec <<= 1;
> -            break;
> -
> -        case AUDIO_FORMAT_S32:
> -        case AUDIO_FORMAT_U32:
> -            bytes_per_sec <<= 2;
> -            break;
> -
> -        default:
> -            abort();
> -        }
> -
> -        threshold = (conf->threshold * bytes_per_sec) / 1000;
> -        alsa_set_threshold (handle, threshold);
> +    if (!in && aopts->has_threshold && aopts->threshold) {
> +        struct audsettings as = { .freq = freq };
> +        alsa_set_threshold(handle,
> +                           audio_buffer_frames(pdo, &as, aopts->threshold));
>      }
>
>      obt->nchannels = nchannels;
> @@ -671,11 +602,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 +732,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 +759,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 +799,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->alsa->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 +833,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 +860,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 +1002,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->alsa->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 +1030,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->kind == AUDIODEV_DRIVER_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_len) {
> +        dev->out->buffer_len = 23219; /* 1024 frames assuming 44100Hz */
> +    }

how did you compute that? Would be worth leaving that in the code.

> +
> +    /* 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_len) {
> +        dev->in->buffer_len = 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 +1076,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 334c935..08ac15e 100644
> --- a/audio/audio.c
> +++ b/audio/audio.c
> @@ -24,7 +24,10 @@
>  #include "hw/hw.h"
>  #include "audio.h"
>  #include "monitor/monitor.h"
> +#include "qapi-visit.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 +45,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 +71,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 +78,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 address@hidden");
>              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 +142,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";
> -
> -    default:
> -        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 +160,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 +780,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 +856,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 +901,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 +1218,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 +1245,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 +1288,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 +1301,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 = AudiodevDriver_lookup[dev->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 +1342,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 +1357,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 +1370,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 +1396,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 +1416,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 +1591,156 @@ 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 = "driver",
> +    .desc = {
> +        /*
> +         * no elements => accept any params
> +         * sanity checking will happen later
> +         */
> +        { /* end of list */ }
> +    },
> +};
> +
> +static void validate_per_direction_opts(AudiodevPerDirectionOptions *pdo,
> +                                        Error **errp)
> +{
> +    if (!pdo->has_fixed_settings) {
> +        pdo->has_fixed_settings = true;
> +        pdo->fixed_settings = true;
> +    }
> +    if (!pdo->fixed_settings &&
> +        (pdo->has_frequency || pdo->has_channels || pdo->has_format)) {
> +        error_setg(errp,
> +                   "You can't use frequency, channels or format with 
> fixed-settings=off");
> +        return;
> +    }
> +
> +    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) {
> +        goto err2;
> +    }
> +
> +    validate_per_direction_opts(dev->in, &local_err);
> +    if (local_err) {
> +        goto err;
> +    }
> +    validate_per_direction_opts(dev->out, &local_err);
> +    if (local_err) {
> +        goto err;
> +    }
> +
> +    if (!dev->has_timer_period) {
> +        dev->has_timer_period = true;
> +        dev->timer_period = 10000; /* 100Hz -> 10ms */
> +    }
> +
> +    return dev;
> +
> +err:
> +    qapi_free_Audiodev(dev);
> +err2:
> +    error_propagate(errp, local_err);
> +    return NULL;
> +}
> +
> +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_len ? pdo->buffer_len : 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..59b2362 100644
> --- a/audio/audio_int.h
> +++ b/audio/audio_int.h
> @@ -32,22 +32,6 @@
>
>  struct audio_pcm_ops;
>
> -typedef enum {
> -    AUD_OPT_INT,
> -    AUD_OPT_FMT,
> -    AUD_OPT_STR,
> -    AUD_OPT_BOOL
> -} audio_option_tag_e;
> -
> -struct audio_option {
> -    const char *name;
> -    audio_option_tag_e tag;
> -    void *valp;
> -    const char *descr;
> -    int *overriddenp;
> -    int overridden;
> -};
> -
>  struct audio_callback {
>      void *opaque;
>      audio_callback_fn fn;
> @@ -143,8 +127,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 +173,7 @@ struct SWVoiceCap {
>
>  struct AudioState {
>      struct audio_driver *drv;
> +    Audiodev *dev;
>      void *drv_opaque;
>
>      QEMUTimer *ts;
> @@ -200,6 +184,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 +198,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..88f577d
> --- /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", "alsa-out.try-poll", ENV_TRANSFORM_BOOL },
> +    { "QEMU_AUDIO_ADC_TRY_POLL", "alsa-in.try-poll", ENV_TRANSFORM_BOOL },
> +
> +    { "QEMU_ALSA_THRESHOLD", "threshold", ENV_TRANSFORM_MILLIS_TO_USECS },
> +    { "QEMU_ALSA_DAC_DEV", "alsa-out.dev" },
> +    { "QEMU_ALSA_ADC_DEV", "alsa-in.dev" },
> +
> +    { /* End of list */ }
> +};
> +
> +SimpleEnvMap coreaudio_map[] = {
> +    { "QEMU_COREAUDIO_BUFFER_SIZE", "buffer-len",
> +      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-len",
> +      ENV_TRANSFORM_BYTES_TO_USECS_OUT },
> +    { "QEMU_DSOUND_BUFSIZE_IN", "in.buffer-len",
> +      ENV_TRANSFORM_BYTES_TO_USECS_IN },
> +
> +    { /* End of list */ }
> +};
> +
> +SimpleEnvMap oss_map[] = {
> +    { "QEMU_AUDIO_DAC_TRY_POLL", "oss-out.try-poll", ENV_TRANSFORM_BOOL },
> +    { "QEMU_AUDIO_ADC_TRY_POLL", "oss-in.try-poll", ENV_TRANSFORM_BOOL },
> +
> +    { "QEMU_OSS_FRAGSIZE", "buffer-len", ENV_TRANSFORM_BYTES_TO_USECS_OUT },
> +    { "QEMU_OSS_NFRAGS", "buffer-count" },
> +    { "QEMU_OSS_MMAP", "try-mmap", ENV_TRANSFORM_BOOL },
> +    { "QEMU_OSS_DAC_DEV", "oss-out.dev" },
> +    { "QEMU_OSS_ADC_DEV", "oss-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-len", 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);

integer?

> +        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];

sizeof(int) ?

> +    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(usec_env);
> +    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-len", "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-len", "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, "driver", 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..7e7b3f2 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->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->kind == AUDIODEV_DRIVER_DSOUND);
> +    s->dev = dev;
> +    dso = dev->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 3ea7d27..a5e7f7c 100644
> --- a/audio/ossaudio.c
> +++ b/audio/ossaudio.c
> @@ -29,6 +29,7 @@
>  #include "qemu-common.h"
>  #include "qemu/main-loop.h"
>  #include "qemu/host-utils.h"
> +#include "qapi-visit.h"
>  #include "audio.h"
>  #include "trace.h"
>
> @@ -39,16 +40,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 +49,7 @@ typedef struct OSSVoiceOut {
>      int fragsize;
>      int mmapped;
>      int pending;
> -    OSSConf *conf;
> +    Audiodev *dev;
>  } OSSVoiceOut;
>
>  typedef struct OSSVoiceIn {
> @@ -67,12 +58,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 +255,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->oss;
> +    AudiodevOssPerDirectionOptions *opdo = in ? oopts->oss_in : 
> oopts->oss_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_try_mmap && oopts->try_mmap) ?
> +        O_RDWR : (in ? O_RDONLY : O_WRONLY);
>
>      fd = open (dspname, oflags | O_NONBLOCK);
>      if (-1 == fd) {
> @@ -287,6 +285,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 +310,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 +504,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->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 +540,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_try_mmap && oopts->try_mmap) {
>          oss->pcm_buf = mmap (
>              NULL,
>              hw->samples << hw->info.shift,
> @@ -601,7 +600,7 @@ static int oss_init_out(HWVoiceOut *hw, struct 
> audsettings *as,
>      }
>
>      oss->fd = fd;
> -    oss->conf = conf;
> +    oss->dev = dev;
>      return 0;
>  }
>
> @@ -609,16 +608,12 @@ static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...)
>  {
>      int trig;
>      OSSVoiceOut *oss = (OSSVoiceOut *) hw;
> +    AudiodevOssPerDirectionOptions *opdo = oss->dev->oss->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 +668,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 +709,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 +800,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->oss->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,82 +825,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->kind == AUDIODEV_DRIVER_OSS);
>
> -    if (access(conf->devpath_in, R_OK | W_OK) < 0 ||
> -        access(conf->devpath_out, R_OK | W_OK) < 0) {
> -        g_free(conf);
> +    oopts = dev->oss;
> +    if (access(oopts->oss_in->has_dev ? oopts->oss_in->dev : "/dev/dsp",
> +               R_OK | W_OK) < 0 ||
> +        access(oopts->oss_out->has_dev ? oopts->oss_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,
> @@ -929,7 +861,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..a53aaf6 100644
> --- a/audio/paaudio.c
> +++ b/audio/paaudio.c
> @@ -1,6 +1,7 @@
>  /* public domain */
>  #include "qemu-common.h"
>  #include "audio.h"
> +#include "qapi-visit.h"
>
>  #include <pulse/pulseaudio.h>
>
> @@ -9,14 +10,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 +25,7 @@ typedef struct {
>      void *pcm_buf;
>      struct audio_pt pt;
>      paaudio *g;
> +    int samples;
>  } PAVoiceOut;
>
>  typedef struct {
> @@ -45,6 +40,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 +222,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 +314,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 +541,8 @@ 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->pa;
> +    AudiodevPaPerDirectionOptions *ppdo = popts->sink;
>
>      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,
> +        ppdo->has_name ? ppdo->name : 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,8 @@ 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->pa;
> +    AudiodevPaPerDirectionOptions *ppdo = popts->source;
>
>      ss.format = audfmt_to_pa (as->fmt, as->endianness);
>      ss.channels = as->nchannels;
> @@ -622,7 +623,7 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings 
> *as, void *drv_opaque)
>          g,
>          "qemu",
>          PA_STREAM_RECORD,
> -        g->conf.source,
> +        ppdo->has_name ? ppdo->name : NULL,
>          &ss,
>          NULL,                   /* channel map */
>          NULL,                   /* buffering attributes */
> @@ -634,7 +635,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 +810,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->kind == AUDIODEV_DRIVER_PA);
> +
> +    g = g_malloc(sizeof(paaudio));
> +    popts = dev->pa;
> +    server = popts->has_server ? popts->server : NULL;
> +
> +    g->dev = dev;
>      g->mainloop = NULL;
>      g->context = NULL;
>
> @@ -825,14 +832,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 +902,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 +919,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 141fd8d..a9b9a1d 100644
> --- a/audio/spiceaudio.c
> +++ b/audio/spiceaudio.c
> @@ -75,7 +75,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;
> @@ -371,10 +371,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,
> @@ -392,7 +388,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 81250e6..1af6d23 100644
> --- a/audio/wavaudio.c
> +++ b/audio/wavaudio.c
> @@ -23,6 +23,7 @@
>   */
>  #include "hw/hw.h"
>  #include "qemu/timer.h"
> +#include "qapi-visit.h"
>  #include "audio.h"
>
>  #define AUDIO_CAP "wav"
> @@ -36,11 +37,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 +107,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->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 +151,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 +222,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->kind == AUDIODEV_DRIVER_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 +244,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 77f5853..efd57e6 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -185,8 +185,8 @@ Set default value of @var{driver}'s property @var{prop} 
> to @var{value}, e.g.:
>  qemu-system-i386 -global ide-drive.physical_block_size=4096 -drive 
> file=file,if=ide,index=0,media=disk
>  @end example
>
> -In particular, you can use this to set driver properties for devices which 
> are
> -created automatically by the machine model. To create a device which is not
> +In particular, you can use this to set driver properties for devices which 
> are
> +created automatically by the machine model. To create a device which is not
>  created automatically and set properties on it, use address@hidden
>
>  -global @address@hidden@var{value} is shorthand for -global
> @@ -313,14 +313,230 @@ 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",

by current, you mean "old" or "deprecated" perhaps?

>      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=]driver,id=id[,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-len= size of buffer in microseconds\n"
> +    "                in|out.buffer-count= number of buffers\n"
> +    "-audiodev none,id=id,[,prop[=value][,...]]\n"
> +    "                dummy driver that discards all output\n"
> +#ifdef CONFIG_ALSA
> +    "-audiodev alsa,id=id[,prop[=value][,...]]\n"
> +    "                alsa-in|alsa-out.dev= name of the audio device to use\n"
> +    "                alsa-in|alsa-out.try-poll= attempt to use poll mode\n"
> +    "                threshold= threshold (in microseconds) when playback 
> starts\n"
> +#endif
> +#ifdef CONFIG_COREAUDIO
> +    "-audiodev coreaudio,id=id[,prop[=value][,...]]\n"
> +#endif
> +#ifdef CONFIG_DSOUND
> +    "-audiodev dsound,id=id[,prop[=value][,...]]\n"
> +    "                latency= add extra latency to playback in 
> microseconds\n"
> +#endif
> +#ifdef CONFIG_OSS
> +    "-audiodev oss,id=id[,prop[=value][,...]]\n"
> +    "                oss-in|oss-out.dev= path of the audio device to use\n"
> +    "                oss-in|oss-out.try-poll= attempt to use poll mode\n"
> +    "                try-mmap= try using memory mapped access\n"
> +    "                exclusive= open device in exclusive mode\n"
> +    "                dsp-policy= set timing policy (0..10), -1 to use 
> fragment mode\n"
> +#endif
> +#ifdef CONFIG_PA
> +    "-audiodev pa,id=id[,prop[=value][,...]]\n"
> +    "                server= PulseAudio server address\n"
> +    "                sink|source.name= sink/source device name\n"
> +#endif
> +#ifdef CONFIG_SDL
> +    "-audiodev sdl,id=id[,prop[=value][,...]]\n"
> +#endif
> +#ifdef CONFIG_SPICE
> +    "-audiodev spice,id=id[,prop[=value][,...]]\n"
> +#endif
> +    "-audiodev wav,id=id[,prop[=value][,...]]\n"
> +    "                path= path of wav file to record\n",
> +    QEMU_ARCH_ALL)
> +STEXI
> address@hidden -audiodev address@hidden,address@hidden,@address@hidden,...]]
> address@hidden -audiodev
> +Adds a new audio backend @var{driver} identified by @var{id}.  There are
> +global and driver specific properties.  Some values can be set
> +differently for input and output, they're marked with @code{in|out.}.
> +You can set the input's property with @address@hidden and the
> +output's property with @address@hidden For example:
> address@hidden
> +-audiodev alsa,in.frequency=44110,out.frequency=8000
> +-audiodev alsa,out.channels=1 # leaves in.channels unspecified
> address@hidden example
> +
> +Valid global options are:
> +
> address@hidden @option
> address@hidden address@hidden
> +Identifies the audio backend.
> +
> address@hidden address@hidden
> +Sets the timer @var{period} used by the audio subsystem in microseconds.
> +Default is 10000 (10 ms).
> +
> address@hidden 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.  In this case you must not specify
> address@hidden, @var{channels} or @var{format}.  Default is on.
> +
> address@hidden in|address@hidden
> +Specify the @var{frequency} to use when using @var{fixed-settings}.
> +Default is 44100Hz.
> +
> address@hidden in|address@hidden
> +Specify the number of @var{channels} to use when using
> address@hidden Default is 2 (stereo).
> +
> address@hidden in|address@hidden
> +Specify the sample @var{format} to use when using @var{fixed-settings}.
> +Valid values are: @code{s8}, @code{s16}, @code{s32}, @code{u8},
> address@hidden, @code{u32}. Default is @code{s16}.
> +
> address@hidden in|address@hidden
> +Specify the number of @var{voices} to use.  Default is 1.
> +
> address@hidden in|address@hidden
> +Sets the size of the buffer in microseconds.
> +
> address@hidden in|address@hidden
> +Sets the @var{count} of the buffers.
> +
> address@hidden table
> +
> address@hidden -audiodev none,address@hidden,@address@hidden,...]]
> +Creates a dummy backend that discards all outputs.  This backend has no
> +backend specific properties.
> +
> address@hidden -audiodev alsa,address@hidden,@address@hidden,...]]
> +Creates backend using the ALSA.  This backend is only available on
> +Linux.
> +
> +ALSA specific options are:
> +
> address@hidden @option
> address@hidden alsa-in|address@hidden
> +Specify the ALSA @var{device} to use for input and/or output.  Default
> +is @code{default}.
> +
> address@hidden alsa-in|alsa-out.try-poll=on|off
> +Attempt to use poll mode with the device.  Default is on.
> +
> address@hidden address@hidden
> +Threshold (in microseconds) when playback starts.  Default is 0.
> +
> address@hidden table
> +
> address@hidden -audiodev coreaudio,address@hidden,@address@hidden,...]]
> +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.
> +
> address@hidden -audiodev dsound,address@hidden,@address@hidden,...]]
> +Creates a backend using Microsoft's DirectSound.  This backend is only
> +available on Windows and only supports playback.
> +
> +Backend specific options are:
> +
> address@hidden @option
> +
> address@hidden address@hidden
> +Add extra @var{usecs} microseconds latency to playback.  Default is
> +10000 (10 ms).
> +
> address@hidden table
> +
> address@hidden -audiodev oss,address@hidden,@address@hidden,...]]
> +Creates a backend using OSS.  This backend is available on most
> +Unix-like systems.
> +
> +OSS specific options are:
> +
> address@hidden @option
> +
> address@hidden oss-in|address@hidden
> +Specify the file name of the OSS @var{device} to use.  Default is
> address@hidden/dev/dsp}.
> +
> address@hidden oss-in|oss-out.try-poll=on|of
> +Attempt to use poll mode with the device.  Default is on.
> +
> address@hidden try-mmap=on|off
> +Try using memory mapped device access.  Default is off.
> +
> address@hidden exclusive=on|off
> +Open the device in exclusive mode (vmix won't work in this case).
> +Default is off.
> +
> address@hidden address@hidden
> +Sets the timing policy (between 0 and 10, where smaller number means
> +smaller latency but higher CPU usage).  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. Default is 5.
> +
> address@hidden table
> +
> address@hidden -audiodev pa,address@hidden,@address@hidden,...]]
> +Creates a backend using PulseAudio.  This backend is available on most
> +systems.
> +
> +PulseAudio specific options are:
> +
> address@hidden @option
> +
> address@hidden address@hidden
> +Sets the PulseAudio @var{server} to connect to.
> +
> address@hidden sink|address@hidden
> +Use the specified sink/source for playback/recording.
> +
> address@hidden table
> +
> address@hidden -audiodev sdl,address@hidden,@address@hidden,...]]
> +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.
> +
> address@hidden -audiodev spice,address@hidden,@address@hidden,...]]
> +Creates a backend that sends audio through SPICE.  This backend requires
> address@hidden and automatically selected in that case, so usually you
> +can ignore this option.  This backend has no backend specific
> +properties.
> +
> address@hidden -audiodev wav,address@hidden,@address@hidden,...]]
> +Creates a backend that writes audio to a WAV file.
> +
> +Backend specific options are:
> +
> address@hidden @option
> +
> address@hidden address@hidden
> +Write recorded audio into the specified file.  Default is
> address@hidden
> +
> address@hidden table
>  ETEXI
>
>  DEF("soundhw", HAS_ARG, QEMU_OPTION_soundhw,
> diff --git a/vl.c b/vl.c
> index 0adbbd6..f1aa1df 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -3004,6 +3004,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);
> @@ -3309,9 +3310,15 @@ 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_noisily(qemu_find_opts("audiodev"),
> +                                             optarg, true)) {
> +                    exit(1);
> +                }
> +                break;
>              case QEMU_OPTION_soundhw:
>                  select_soundhw (optarg);
>                  break;
> @@ -4511,6 +4518,7 @@ int main(int argc, char **argv, char **envp)
>
>      realtime_init();
>
> +    audio_set_options();
>      audio_init();
>
>      cpu_synchronize_all_post_init();
> --
> 2.4.5
>
>

overall looks good to me, but it will require testing.

Tbh, I wonder why qemu has so many audio backends and options...
(linking with gstreamer would be just one more lib of 1mb after all,
with a lot more flexibility, and it could be made a module..) :)

-- 
Marc-André Lureau



reply via email to

[Prev in Thread] Current Thread [Next in Thread]