diff mbox

[v2,6/6] audio: -audiodev command line option

Message ID 34d6db2f41b9101490b3f3549a9f1c5a85be03c8.1434458391.git.DirtY.iCE.hu@gmail.com
State New
Headers show

Commit Message

=?UTF-8?B?Wm9sdMOhbiBLxZF2w6Fnw7M=?= June 16, 2015, 12:49 p.m. UTC
This patch adds an -audiodev command line option, and deprecates the QEMU_*
environment variables for audio backend configuration. It's 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, also print out them with -audio-help, to ease
migrating to -audiodev).

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 <DirtY.iCE.hu@gmail.com>

---

Changes from v1:
* updated to everything usecs without suffix
* better rounding when converting between usecs and frames/samples/bytes
* bugfixes in audio_legacy.c
* fixed code broken by qemu_opts changes by Markus

 audio/Makefile.objs     |   2 +-
 audio/alsaaudio.c       | 284 ++++++------------
 audio/audio.c           | 747 +++++++++++++-----------------------------------
 audio/audio.h           |  23 +-
 audio/audio_int.h       |   7 +-
 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         |  81 ++----
 audio/sdlaudio.c        |  24 +-
 audio/spiceaudio.c      |   7 +-
 audio/wavaudio.c        |  61 +---
 qemu-options.hx         | 218 +++++++++++++-
 vl.c                    |   9 +-
 18 files changed, 994 insertions(+), 1083 deletions(-)
 create mode 100644 audio/audio_legacy.c

Comments

Markus Armbruster June 17, 2015, 8:13 a.m. UTC | #1
"Kővágó, Zoltán" <dirty.ice.hu@gmail.com> writes:

> This patch adds an -audiodev command line option, and deprecates the QEMU_*
> environment variables for audio backend configuration. It's syntax is similar to
> existing options (-netdev, -device, etc):
>  -audiodev driver_name,property=value,...

Sounds really good.

Please wrap your commit message lines a bit earlier, around column 70.

> 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, also print out them with -audio-help, to ease
> migrating to -audiodev).

The parenthesis isn't as clear as the rest of your message, probably
because it deals with two separate things.  Suggest to move out the bit
about help into its own paragraph.

> Although now it's possible to specify multiple -audiodev options on command
> line, multiple audio backends are not supported yet.

What happens when I specify multiple -audiodev?

How should the command line look like when multiple audio backends are
supported?

Do we have a clear backward-compatible path from here to there?

[...]
>  18 files changed, 994 insertions(+), 1083 deletions(-)
>  create mode 100644 audio/audio_legacy.c

Very nice delta, but the size is a bit intimidating :)

[...]
=?UTF-8?B?Wm9sdMOhbiBLxZF2w6Fnw7M=?= June 17, 2015, 11:18 a.m. UTC | #2
2015-06-17 10:13 keltezéssel, Markus Armbruster írta:
> "Kővágó, Zoltán" <dirty.ice.hu@gmail.com> writes:
>
>> This patch adds an -audiodev command line option, and deprecates the QEMU_*
>> environment variables for audio backend configuration. It's syntax is similar to
>> existing options (-netdev, -device, etc):
>>   -audiodev driver_name,property=value,...
>
> Sounds really good.
>
> Please wrap your commit message lines a bit earlier, around column 70.
>
>> 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, also print out them with -audio-help, to ease
>> migrating to -audiodev).
>
> The parenthesis isn't as clear as the rest of your message, probably
> because it deals with two separate things.  Suggest to move out the bit
> about help into its own paragraph.
>
>> Although now it's possible to specify multiple -audiodev options on command
>> line, multiple audio backends are not supported yet.
>
> What happens when I specify multiple -audiodev?

You get an error and qemu terminates.

> How should the command line look like when multiple audio backends are
> supported?

There's an id property of audiodev, so you can identify them:
-audiodev alsa,id=foo,... -audiodev pa,id=bar,...
and audio devices should get an extra parameter, like audiodev or 
something like that:
-device usb-audio,audiodev=foo -device usb-audio,audiodev=bar
And you have two cards, one connected to the alsa device and the other 
connected to pulseaudio.

>
> Do we have a clear backward-compatible path from here to there?

Currently if you specify an -audiodev option, the environment variables 
are completely ignored, and it will create an audio backend using the 
specified options. If you do not provide an -audiodev, it will 
initialize the audio subsystem using the old environment variables when 
you add the first sound card (so no -audiodev and no sound device means 
no audio subsystem, just like the old times).

About multiple backends: if the user does not specify the id of the 
backend when creating the sound card, just use the first -audiodev 
specified on the command line (or the legacy config, if there's no 
-audiodev). This way we stay backward-compatible (there won't be 
multiple -audiodevs in legacy configs).

>
> [...]
>>   18 files changed, 994 insertions(+), 1083 deletions(-)
>>   create mode 100644 audio/audio_legacy.c
>
> Very nice delta, but the size is a bit intimidating :)
>
> [...]
>
Markus Armbruster June 17, 2015, 12:27 p.m. UTC | #3
"Kővágó Zoltán" <dirty.ice.hu@gmail.com> writes:

> 2015-06-17 10:13 keltezéssel, Markus Armbruster írta:
>> "Kővágó, Zoltán" <dirty.ice.hu@gmail.com> writes:
>>
>>> This patch adds an -audiodev command line option, and deprecates the QEMU_*
>>> environment variables for audio backend configuration. It's syntax
>>> is similar to
>>> existing options (-netdev, -device, etc):
>>>   -audiodev driver_name,property=value,...
>>
>> Sounds really good.
>>
>> Please wrap your commit message lines a bit earlier, around column 70.
>>
>>> 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, also print out them with -audio-help, to ease
>>> migrating to -audiodev).
>>
>> The parenthesis isn't as clear as the rest of your message, probably
>> because it deals with two separate things.  Suggest to move out the bit
>> about help into its own paragraph.
>>
>>> Although now it's possible to specify multiple -audiodev options on command
>>> line, multiple audio backends are not supported yet.
>>
>> What happens when I specify multiple -audiodev?
>
> You get an error and qemu terminates.

Unlikely to create backward compatibility trouble.  Works for me.

>> How should the command line look like when multiple audio backends are
>> supported?
>
> There's an id property of audiodev, so you can identify them:
> -audiodev alsa,id=foo,... -audiodev pa,id=bar,...
> and audio devices should get an extra parameter, like audiodev or
> something like that:
> -device usb-audio,audiodev=foo -device usb-audio,audiodev=bar
> And you have two cards, one connected to the alsa device and the other
> connected to pulseaudio.

Good, because it's consistent with how we connect other kinds of
frontends/backends.

Multiple audio frontends (device models) can connect to the same audio
backend, can't they?

Is backend property "id" mandatory?  Hmm, judging from PATCH 1 it isn't.
It generally is for other kinds of backends, e.g. -netdev, -chardev and
(in the future) -blockdev.  It's optional with -drive only because
-drive is crazy.

>> Do we have a clear backward-compatible path from here to there?
>
> Currently if you specify an -audiodev option, the environment
> variables are completely ignored, and it will create an audio backend
> using the specified options. If you do not provide an -audiodev, it
> will initialize the audio subsystem using the old environment
> variables when you add the first sound card (so no -audiodev and no
> sound device means no audio subsystem, just like the old times).

Should we document this?  Where?

> About multiple backends: if the user does not specify the id of the
> backend when creating the sound card, just use the first -audiodev
> specified on the command line (or the legacy config, if there's no
> -audiodev). This way we stay backward-compatible (there won't be
> multiple -audiodevs in legacy configs).

Old way (before your patch): there is only one audio backend, and it's
configuration comes from a bunch of environment variables.

New way (we're not there yet): you can have multiple audio backends, and
you connect frontends to backends using backend ID as usual, i.e. id=ID
on the backend, audiodev=ID on the frontend.

Required backward compatibility: the old way still works, i.e. when
there's no new-way backend, and no frontend has an audiodev=..., then we
create a backend configured from the environment.

Anything beyond required backward compatibility should be carefully
judged on its UI merits.

If I understand your description correctly, the plan is to default a
frontend's audiodev to the first backend, and if there is none, create
one configured from the environment.  That's definitely beyond required.
Is it a good user interface?

[...]
=?UTF-8?B?Wm9sdMOhbiBLxZF2w6Fnw7M=?= June 17, 2015, 1:25 p.m. UTC | #4
2015-06-17 14:27 keltezéssel, Markus Armbruster írta:
> "Kővágó Zoltán" <dirty.ice.hu@gmail.com> writes:
>
>> 2015-06-17 10:13 keltezéssel, Markus Armbruster írta:
>>> "Kővágó, Zoltán" <dirty.ice.hu@gmail.com> writes:
>>>
>>>> This patch adds an -audiodev command line option, and deprecates the QEMU_*
>>>> environment variables for audio backend configuration. It's syntax
>>>> is similar to
>>>> existing options (-netdev, -device, etc):
>>>>    -audiodev driver_name,property=value,...
>>>
>>> Sounds really good.
>>>
>>> Please wrap your commit message lines a bit earlier, around column 70.
>>>
>>>> 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, also print out them with -audio-help, to ease
>>>> migrating to -audiodev).
>>>
>>> The parenthesis isn't as clear as the rest of your message, probably
>>> because it deals with two separate things.  Suggest to move out the bit
>>> about help into its own paragraph.
>>>
>>>> Although now it's possible to specify multiple -audiodev options on command
>>>> line, multiple audio backends are not supported yet.
>>>
>>> What happens when I specify multiple -audiodev?
>>
>> You get an error and qemu terminates.
>
> Unlikely to create backward compatibility trouble.  Works for me.
>
>>> How should the command line look like when multiple audio backends are
>>> supported?
>>
>> There's an id property of audiodev, so you can identify them:
>> -audiodev alsa,id=foo,... -audiodev pa,id=bar,...
>> and audio devices should get an extra parameter, like audiodev or
>> something like that:
>> -device usb-audio,audiodev=foo -device usb-audio,audiodev=bar
>> And you have two cards, one connected to the alsa device and the other
>> connected to pulseaudio.
>
> Good, because it's consistent with how we connect other kinds of
> frontends/backends.
>
> Multiple audio frontends (device models) can connect to the same audio
> backend, can't they?

Yes. (It's mandatory, since currently all audio frontends connect to a 
single backend.)

> Is backend property "id" mandatory?  Hmm, judging from PATCH 1 it isn't.
> It generally is for other kinds of backends, e.g. -netdev, -chardev and
> (in the future) -blockdev.  It's optional with -drive only because
> -drive is crazy.
It's optional right now (because I thought specifying an id when you 
have a single backend is pretty pointless), but it's probably better 
then if I change it to mandatory.

>
>>> Do we have a clear backward-compatible path from here to there?
>>
>> Currently if you specify an -audiodev option, the environment
>> variables are completely ignored, and it will create an audio backend
>> using the specified options. If you do not provide an -audiodev, it
>> will initialize the audio subsystem using the old environment
>> variables when you add the first sound card (so no -audiodev and no
>> sound device means no audio subsystem, just like the old times).
>
> Should we document this?  Where?

Maybe adding a phrase to -audiodev documentation like "If you specify 
this option, the deprecated environment variables will be ignored".

>> About multiple backends: if the user does not specify the id of the
>> backend when creating the sound card, just use the first -audiodev
>> specified on the command line (or the legacy config, if there's no
>> -audiodev). This way we stay backward-compatible (there won't be
>> multiple -audiodevs in legacy configs).
>
> Old way (before your patch): there is only one audio backend, and it's
> configuration comes from a bunch of environment variables.
>
> New way (we're not there yet): you can have multiple audio backends, and
> you connect frontends to backends using backend ID as usual, i.e. id=ID
> on the backend, audiodev=ID on the frontend.
>
> Required backward compatibility: the old way still works, i.e. when
> there's no new-way backend, and no frontend has an audiodev=..., then we
> create a backend configured from the environment.
>
> Anything beyond required backward compatibility should be carefully
> judged on its UI merits.
>
> If I understand your description correctly, the plan is to default a
> frontend's audiodev to the first backend, and if there is none, create
> one configured from the environment.  That's definitely beyond required.
> Is it a good user interface?

The "create one configured from the environment" is needed for backward 
compatibility. Specifying an audiodev=... on frontend makes no sense 
without an -audiodev, so that's not allowed. The only question is what 
happens with an -audiodev, but no audiodev= on the frontend. We can't 
make audiodev= required because that would break compatibility. So we 
either a) make audiodev= required but only when there's an -audiodev, or 
b) try to find a sensible default when audiodev= is not specified. Imho 
b is better, because having a parameter that's sometimes required, 
sometimes forbidden is a bit confusing.
Markus Armbruster June 17, 2015, 4:13 p.m. UTC | #5
"Kővágó Zoltán" <dirty.ice.hu@gmail.com> writes:

> 2015-06-17 14:27 keltezéssel, Markus Armbruster írta:
>> "Kővágó Zoltán" <dirty.ice.hu@gmail.com> writes:
>>
>>> 2015-06-17 10:13 keltezéssel, Markus Armbruster írta:
>>>> "Kővágó, Zoltán" <dirty.ice.hu@gmail.com> writes:
>>>>
>>>>> This patch adds an -audiodev command line option, and deprecates the QEMU_*
>>>>> environment variables for audio backend configuration. It's syntax
>>>>> is similar to
>>>>> existing options (-netdev, -device, etc):
>>>>>    -audiodev driver_name,property=value,...
>>>>
>>>> Sounds really good.
>>>>
>>>> Please wrap your commit message lines a bit earlier, around column 70.
>>>>
>>>>> 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, also print out them with
>>>>> -audio-help, to ease
>>>>> migrating to -audiodev).
>>>>
>>>> The parenthesis isn't as clear as the rest of your message, probably
>>>> because it deals with two separate things.  Suggest to move out the bit
>>>> about help into its own paragraph.
>>>>
>>>>> Although now it's possible to specify multiple -audiodev options on command
>>>>> line, multiple audio backends are not supported yet.
>>>>
>>>> What happens when I specify multiple -audiodev?
>>>
>>> You get an error and qemu terminates.
>>
>> Unlikely to create backward compatibility trouble.  Works for me.
>>
>>>> How should the command line look like when multiple audio backends are
>>>> supported?
>>>
>>> There's an id property of audiodev, so you can identify them:
>>> -audiodev alsa,id=foo,... -audiodev pa,id=bar,...
>>> and audio devices should get an extra parameter, like audiodev or
>>> something like that:
>>> -device usb-audio,audiodev=foo -device usb-audio,audiodev=bar
>>> And you have two cards, one connected to the alsa device and the other
>>> connected to pulseaudio.
>>
>> Good, because it's consistent with how we connect other kinds of
>> frontends/backends.
>>
>> Multiple audio frontends (device models) can connect to the same audio
>> backend, can't they?
>
> Yes. (It's mandatory, since currently all audio frontends connect to a
> single backend.)

Understood.

>> Is backend property "id" mandatory?  Hmm, judging from PATCH 1 it isn't.
>> It generally is for other kinds of backends, e.g. -netdev, -chardev and
>> (in the future) -blockdev.  It's optional with -drive only because
>> -drive is crazy.
> It's optional right now (because I thought specifying an id when you
> have a single backend is pretty pointless), but it's probably better
> then if I change it to mandatory.

Let's make it mandatory for now.  We can always change mandatory to
optional, but not the other way round.

>>>> Do we have a clear backward-compatible path from here to there?
>>>
>>> Currently if you specify an -audiodev option, the environment
>>> variables are completely ignored, and it will create an audio backend
>>> using the specified options. If you do not provide an -audiodev, it
>>> will initialize the audio subsystem using the old environment
>>> variables when you add the first sound card (so no -audiodev and no
>>> sound device means no audio subsystem, just like the old times).
>>
>> Should we document this?  Where?
>
> Maybe adding a phrase to -audiodev documentation like "If you specify
> this option, the deprecated environment variables will be ignored".

Works for me.

>>> About multiple backends: if the user does not specify the id of the
>>> backend when creating the sound card, just use the first -audiodev
>>> specified on the command line (or the legacy config, if there's no
>>> -audiodev). This way we stay backward-compatible (there won't be
>>> multiple -audiodevs in legacy configs).
>>
>> Old way (before your patch): there is only one audio backend, and it's
>> configuration comes from a bunch of environment variables.
>>
>> New way (we're not there yet): you can have multiple audio backends, and
>> you connect frontends to backends using backend ID as usual, i.e. id=ID
>> on the backend, audiodev=ID on the frontend.
>>
>> Required backward compatibility: the old way still works, i.e. when
>> there's no new-way backend, and no frontend has an audiodev=..., then we
>> create a backend configured from the environment.
>>
>> Anything beyond required backward compatibility should be carefully
>> judged on its UI merits.
>>
>> If I understand your description correctly, the plan is to default a
>> frontend's audiodev to the first backend, and if there is none, create
>> one configured from the environment.  That's definitely beyond required.
>> Is it a good user interface?
>
> The "create one configured from the environment" is needed for
> backward compatibility. Specifying an audiodev=... on frontend makes
> no sense without an -audiodev, so that's not allowed. The only
> question is what happens with an -audiodev, but no audiodev= on the
> frontend. We can't make audiodev= required because that would break
> compatibility. So we either a) make audiodev= required but only when
> there's an -audiodev, or b) try to find a sensible default when
> audiodev= is not specified. Imho b is better, because having a
> parameter that's sometimes required, sometimes forbidden is a bit
> confusing.

If we didn't have to do backwards compatibility, we'd make audiodev
mandatory, wouldn't we?

Here's how I'd add back-compat to that baseline.  audiodev becomes
optional, but omitting it is deprecated.  If you do, you implicitly use
the legacy backend that takes its configuration from the environment.
The legacy backend gets created when a frontend is using it.

If you think you got a better way to do it, the person to convince is
Gerd.

I'll try to find time to experiment with flattening your QAPI schema,
perhaps we can make things a bit simpler.

I'm glad you're taking care of turning our bizarro audio UI into
something that actually fits in with the rest.  Much appreciated!
Gerd Hoffmann June 18, 2015, 6:54 a.m. UTC | #6
Hi,

> Here's how I'd add back-compat to that baseline.  audiodev becomes
> optional, but omitting it is deprecated.  If you do, you implicitly use
> the legacy backend that takes its configuration from the environment.
> The legacy backend gets created when a frontend is using it.
> 
> If you think you got a better way to do it, the person to convince is
> Gerd.

Approach makes sense to me.  Either the old config scheme is active, for
compatibility, or the new one.  Allowing to mix old+new (use -audiodev,
but don't specify the name to -device soundcard,audiodev=) is not
needed.

cheers,
  Gerd
diff mbox

Patch

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