diff mbox

[RFC] Add support for a USB audio device model

Message ID 1284155276-14959-1-git-send-email-hpa@linux.intel.com
State New
Headers show

Commit Message

H. Peter Anvin Sept. 10, 2010, 9:47 p.m. UTC
I discovered that none of the audio device models supported by current
Qemu/KVM appear to be supported out of the box on Win7 64 bit (AC97
works fine on 32 bit).  The most logical ways to fix that would be to
add a long-term supportable audio device model.  Intel HD Audio and
USB Audio seemed like the most reasonable options, but I opted for USB
Audio for a few reasons:

a) as an external plugin device, it is more likely that it will be
   supported across a large number of guest operating systems for a
   very long time.
b) as a vendor-independent class device, it is supported by the OS
   developers directly without any of the potential strange issues
   involved with relying on a vendor driver.
c) USB Audio is highly parameterizable, which would make it possible
   to export all kinds of different audio models to the guest.  Note
   that due to the software-scheduled nature of [UOE]HCI, however,
   surround sound support (more than 4 channels) would require a High
   Speed device model with EHCI, or the guest OS will reject it as
   unschedulable (since it will exceed the schedulable bandwidth of a
   Full Speed USB bus.)

This patch adds support for a USB audio device model.  It is based
mostly on the "USB Basic Audio Device Specification", which consists
of a set of profiles for the much more general "USB Audio
Specification".  In accordance with the above profile, it supports
48 kHz, 16-bit stereo only.

It does work, "so far, so good".  However, it doesn't really provide a
fully acceptable audio quality, because it suffers from a fundamental
problem -- the rate of data provided by UHCI may not match the
consumption rate of the sink.  I don't understand the Qemu audio
subsystem well enough to know how to deal with that, which is why I
would like comments and/or help (I really have spent way more time
than I should on this already).  The full USB Audio specification has
an optional concept of synchronization packets (a reverse channel
which carry data rate information to be used for rate matching); this
is specified in section 5.12.4.2 of the USB 2.0 specification (or
5.10.4.2 of USB 1.1).  However, it isn't very clear to me how to get
the relevant information out of the Qemu audio system.

From a device model point of view, a data packet will arrive once per
1 ms of USB time; this can be the time basis.  Presumably this needs
to be used to compare against the number of samples actually consumed
to construct the functional sample rate on the USB clock, as long as
that information can be acquired.

Not-yet-signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
---
 Makefile.objs  |    1 +
 arch_init.c    |   24 ++-
 configure      |    6 +-
 create_config  |    4 +-
 hw/audiodev.h  |    4 +
 hw/pc.c        |   14 +-
 hw/pc_piix.c   |    4 +-
 hw/usb-audio.c |  702 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/usb-net.c   |    3 -
 hw/usb.h       |   13 +-
 qemu-common.h  |    5 +
 sysemu.h       |   11 +-
 12 files changed, 764 insertions(+), 27 deletions(-)
 create mode 100644 hw/usb-audio.c

Comments

malc Sept. 11, 2010, 12:28 a.m. UTC | #1
On Fri, 10 Sep 2010, H. Peter Anvin wrote:

> I discovered that none of the audio device models supported by current
> Qemu/KVM appear to be supported out of the box on Win7 64 bit (AC97
> works fine on 32 bit).  The most logical ways to fix that would be to
> add a long-term supportable audio device model.  Intel HD Audio and
> USB Audio seemed like the most reasonable options, but I opted for USB
> Audio for a few reasons:

I'll look at it tomorow, in the meantime it would be great to know
how to test it (i.e. how to build the kernel which includes the
driver for this thing)

Oh and is the audio output bad when using linux guest too? (I don't
have any windows guests to test this part of the equation)

P.S. The patches have lot's of tabs in them btw.

P.P.S. There's an extension to your Intel HDA Audio option:
       to get the HDA code from VirtualBox and "port" it to QEMU.

[..snip..]
H. Peter Anvin Sept. 11, 2010, 1:08 a.m. UTC | #2
On 09/10/2010 05:28 PM, malc wrote:
> On Fri, 10 Sep 2010, H. Peter Anvin wrote:
> 
>> I discovered that none of the audio device models supported by current
>> Qemu/KVM appear to be supported out of the box on Win7 64 bit (AC97
>> works fine on 32 bit).  The most logical ways to fix that would be to
>> add a long-term supportable audio device model.  Intel HD Audio and
>> USB Audio seemed like the most reasonable options, but I opted for USB
>> Audio for a few reasons:
> 
> I'll look at it tomorow, in the meantime it would be great to know
> how to test it (i.e. how to build the kernel which includes the
> driver for this thing)

Any remotely recent stock distro should have support for it.  I say
"should", because I haven't actually tested it with a Linux guest yet.

I'll try to do that later; I have to leave now.

> Oh and is the audio output bad when using linux guest too? (I don't
> have any windows guests to test this part of the equation)

Yes; I also have an stderr message in the patch

> P.S. The patches have lot's of tabs in them btw.

*Sigh* my tools are tuned to Linux kernel development, I'm afraid.

> P.P.S. There's an extension to your Intel HDA Audio option:
>        to get the HDA code from VirtualBox and "port" it to QEMU.

I know.  Someone else is welcome to do that... since it would require
knowing both the VirtualBox and the Qemu sound subsystem interfaces and
in what ways they are similar or different.

	-hpa
H. Peter Anvin Sept. 11, 2010, 2:47 a.m. UTC | #3
On 09/10/2010 06:08 PM, H. Peter Anvin wrote:
> 
> Any remotely recent stock distro should have support for it.  I say
> "should", because I haven't actually tested it with a Linux guest yet.
> 
> I'll try to do that later; I have to leave now.
> 

Just tested it on a stock Fedora 13 64 bit VM; it behaves exactly the
same way as under Win7.

	-hpa
H. Peter Anvin Sept. 11, 2010, 3:05 a.m. UTC | #4
On 09/10/2010 07:47 PM, H. Peter Anvin wrote:
> On 09/10/2010 06:08 PM, H. Peter Anvin wrote:
>>
>> Any remotely recent stock distro should have support for it.  I say
>> "should", because I haven't actually tested it with a Linux guest yet.
>>
>> I'll try to do that later; I have to leave now.
>>
> 
> Just tested it on a stock Fedora 13 64 bit VM; it behaves exactly the
> same way as under Win7.

Just for the sake of completeness, the Qemu command line was:

~/qemu/git-kvm/x86_64-softmmu/qemu-system-x86_64 -enable-kvm -smp 2 -m
1024 -usb -soundhw usb -hda qemu-fc13-64.img -serial stdio

... and this was with the usb-audio patch applied against top of the the
qemu-kvm git tree (the patch applies against the top of the main qemu
tree too, but because KVM isn't very stable there it was faster to use
the KVM tree.)

	-hpa
Stefan Hajnoczi Sept. 11, 2010, 7:41 a.m. UTC | #5
On Fri, Sep 10, 2010 at 10:47 PM, H. Peter Anvin <hpa@linux.intel.com> wrote:
> diff --git a/hw/usb-audio.c b/hw/usb-audio.c
> new file mode 100644
> index 0000000..d4cf488
> --- /dev/null
> +++ b/hw/usb-audio.c
> @@ -0,0 +1,702 @@
> +/*
> + * QEMU USB Net devices
> + *
> + * Copyright (c) 2006 Thomas Sailer
> + * Copyright (c) 2008 Andrzej Zaborowski

Want to update this for usb-audio?

Stefan
Alexander Graf Sept. 11, 2010, 1:14 p.m. UTC | #6
On 11.09.2010, at 03:08, H. Peter Anvin wrote:

[snip]

> I know.  Someone else is welcome to do that... since it would require
> knowing both the VirtualBox and the Qemu sound subsystem interfaces and
> in what ways they are similar or different.

They should be reasonably close. About 80% of the VBox device model consists of qemu, mangled through an OS2'ifier.

Alex
malc Sept. 11, 2010, 1:53 p.m. UTC | #7
On Fri, 10 Sep 2010, H. Peter Anvin wrote:

> On 09/10/2010 07:47 PM, H. Peter Anvin wrote:
> > On 09/10/2010 06:08 PM, H. Peter Anvin wrote:
> >>
> >> Any remotely recent stock distro should have support for it.  I say
> >> "should", because I haven't actually tested it with a Linux guest yet.
> >>
> >> I'll try to do that later; I have to leave now.
> >>
> > 
> > Just tested it on a stock Fedora 13 64 bit VM; it behaves exactly the
> > same way as under Win7.
> 

Sorry but I have no idea what "stock Fedora 13 64 bit VM" is.

> Just for the sake of completeness, the Qemu command line was:
> 
> ~/qemu/git-kvm/x86_64-softmmu/qemu-system-x86_64 -enable-kvm -smp 2 -m
> 1024 -usb -soundhw usb -hda qemu-fc13-64.img -serial stdio
> 
> ... and this was with the usb-audio patch applied against top of the the
> qemu-kvm git tree (the patch applies against the top of the main qemu
> tree too, but because KVM isn't very stable there it was faster to use
> the KVM tree.)
      ^^^ this doesn't parse, somewhere QEMU was replaced by KVM i think


Anywho, i tried it with linux-test and custom/minimal compiled 2.6.32 [1]

x86_64-softmmu/qemu-system-x86_64 -kernel \
~/x/bld/linux-2.6.32/arch/x86_64/boot/bzImage -append "root=/dev/hda" \
-vnc :0 -soundhw usb ~/x/img/linux-0.2.img -usb [-enable-kvm]
                                                ^^^ this has no consequence [2]

Inside the guest `$ madplay 20thfull.mp2' and things sounded fine with OSS,
with ALSA the story is somewhat different, it stuttered for a while but then
settled and things went back to smooth playback.

So i need a reproduction scenario

[1] .config available on request
[2] Well actually it has - on the speed `-enable-kvm' makes boot sluggish
    for whatever reason
H. Peter Anvin Sept. 11, 2010, 5:09 p.m. UTC | #8
I meant just take the Fedora 13 DVD and install it onto a virtual hard disk.  More later when I'm at a real computer.

"malc" <av1474@comtv.ru> wrote:

>On Fri, 10 Sep 2010, H. Peter Anvin wrote:
>
>> On 09/10/2010 07:47 PM, H. Peter Anvin wrote:
>> > On 09/10/2010 06:08 PM, H. Peter Anvin wrote:
>> >>
>> >> Any remotely recent stock distro should have support for it.  I say
>> >> "should", because I haven't actually tested it with a Linux guest yet.
>> >>
>> >> I'll try to do that later; I have to leave now.
>> >>
>> > 
>> > Just tested it on a stock Fedora 13 64 bit VM; it behaves exactly the
>> > same way as under Win7.
>> 
>
>Sorry but I have no idea what "stock Fedora 13 64 bit VM" is.
>
>> Just for the sake of completeness, the Qemu command line was:
>> 
>> ~/qemu/git-kvm/x86_64-softmmu/qemu-system-x86_64 -enable-kvm -smp 2 -m
>> 1024 -usb -soundhw usb -hda qemu-fc13-64.img -serial stdio
>> 
>> ... and this was with the usb-audio patch applied against top of the the
>> qemu-kvm git tree (the patch applies against the top of the main qemu
>> tree too, but because KVM isn't very stable there it was faster to use
>> the KVM tree.)
>      ^^^ this doesn't parse, somewhere QEMU was replaced by KVM i think
>
>
>Anywho, i tried it with linux-test and custom/minimal compiled 2.6.32 [1]
>
>x86_64-softmmu/qemu-system-x86_64 -kernel \
>~/x/bld/linux-2.6.32/arch/x86_64/boot/bzImage -append "root=/dev/hda" \
>-vnc :0 -soundhw usb ~/x/img/linux-0.2.img -usb [-enable-kvm]
>                                                ^^^ this has no consequence [2]
>
>Inside the guest `$ madplay 20thfull.mp2' and things sounded fine with OSS,
>with ALSA the story is somewhat different, it stuttered for a while but then
>settled and things went back to smooth playback.
>
>So i need a reproduction scenario
>
>[1] .config available on request
>[2] Well actually it has - on the speed `-enable-kvm' makes boot sluggish
>    for whatever reason
>
>-- 
>mailto:av1474@comtv.ru
H. Peter Anvin Sept. 11, 2010, 6:29 p.m. UTC | #9
It would certainly be a worthwhile project.

"Alexander Graf" <agraf@suse.de> wrote:

>
>On 11.09.2010, at 03:08, H. Peter Anvin wrote:
>
>[snip]
>
>> I know.  Someone else is welcome to do that... since it would require
>> knowing both the VirtualBox and the Qemu sound subsystem interfaces and
>> in what ways they are similar or different.
>
>They should be reasonably close. About 80% of the VBox device model consists of qemu, mangled through an OS2'ifier.
>
>Alex
>
>
H. Peter Anvin Sept. 12, 2010, 5:20 a.m. UTC | #10
On 09/11/2010 12:41 AM, Stefan Hajnoczi wrote:
> On Fri, Sep 10, 2010 at 10:47 PM, H. Peter Anvin <hpa@linux.intel.com> wrote:
>> diff --git a/hw/usb-audio.c b/hw/usb-audio.c
>> new file mode 100644
>> index 0000000..d4cf488
>> --- /dev/null
>> +++ b/hw/usb-audio.c
>> @@ -0,0 +1,702 @@
>> +/*
>> + * QEMU USB Net devices
>> + *
>> + * Copyright (c) 2006 Thomas Sailer
>> + * Copyright (c) 2008 Andrzej Zaborowski
> 
> Want to update this for usb-audio?
> 
> Stefan

Yeah, obviously...

	-hpa
Amos Kong Sept. 13, 2010, 8:53 p.m. UTC | #11
On Fri, Sep 10, 2010 at 02:47:56PM -0700, H. Peter Anvin wrote:
> I discovered that none of the audio device models supported by current
> Qemu/KVM appear to be supported out of the box on Win7 64 bit (AC97
> works fine on 32 bit).  The most logical ways to fix that would be to
> add a long-term supportable audio device model.  Intel HD Audio and
> USB Audio seemed like the most reasonable options, but I opted for USB
> Audio for a few reasons:

...
> diff --git a/configure b/configure
> index 8228c1c..4fcb829 100755
> --- a/configure
> +++ b/configure
> @@ -71,8 +71,8 @@ sparc_cpu=""
>  cross_prefix=""
>  cc="gcc"
>  audio_drv_list=""
> -audio_card_list="ac97 es1370 sb16"
> -audio_possible_cards="ac97 es1370 sb16 cs4231a adlib gus"
> +audio_card_list="ac97 es1370 sb16 usb-audio"
> +audio_possible_cards="ac97 es1370 sb16 cs4231a adlib gus usb-audio"
>  block_drv_whitelist=""
>  host_cc="gcc"
>  ar="ar"
> @@ -2414,7 +2414,7 @@ if test "$vde" = "yes" ; then
>  fi
>  for card in $audio_card_list; do
>      def=CONFIG_`echo $card | tr '[:lower:]' '[:upper:]'`
> -    echo "$def=y" >> $config_host_mak
> +    echo ${def//-/_}=y >> $config_host_mak
>  done
>  echo "CONFIG_AUDIO_DRIVERS=$audio_drv_list" >> $config_host_mak
>  for drv in $audio_drv_list; do

# patch -p1 < /tmp/usb-audio.patch
# ./configure
...
...
preadv support    yes
fdatasync         yes
uuid support      no
vhost-net support no
Trace backend     nop
Trace output file trace-<pid>
./configure: 2276: Bad substitution


> diff --git a/create_config b/create_config
> index 0098e68..1caa25b 100755
> --- a/create_config
> +++ b/create_config
> @@ -25,7 +25,7 @@ case $line in
>   CONFIG_AUDIO_DRIVERS=*)
>      drivers=${line#*=}
>      echo "#define CONFIG_AUDIO_DRIVERS \\"
> -    for drv in $drivers; do
> +    for drv in ${drivers//-/_}; do
>        echo "    &${drv}_audio_driver,\\"
>      done
>      echo ""
> @@ -39,10 +39,12 @@ case $line in
>      ;;
>   CONFIG_*=y) # configuration
>      name=${line%=*}
> +    name=${name//-/_}
>      echo "#define $name 1"
>      ;;
>   CONFIG_*=*) # configuration
>      name=${line%=*}
> +    name=${name//-/_}
>      value=${line#*=}
>      echo "#define $name $value"
>      ;;

...
H. Peter Anvin Sept. 13, 2010, 9:04 p.m. UTC | #12
On 09/13/2010 01:53 PM, Amos Kong wrote:
> 
> # patch -p1 < /tmp/usb-audio.patch
> # ./configure
> ...
> ...
> preadv support    yes
> fdatasync         yes
> uuid support      no
> vhost-net support no
> Trace backend     nop
> Trace output file trace-<pid>
> ./configure: 2276: Bad substitution
> 

What shell is your /bin/sh?

	-hpa
H. Peter Anvin Sept. 13, 2010, 9:07 p.m. UTC | #13
On 09/13/2010 01:53 PM, Amos Kong wrote:
> 
> # patch -p1 < /tmp/usb-audio.patch
> # ./configure
> ...
> ...
> preadv support    yes
> fdatasync         yes
> uuid support      no
> vhost-net support no
> Trace backend     nop
> Trace output file trace-<pid>
> ./configure: 2276: Bad substitution
> 
> 
>> diff --git a/create_config b/create_config
>> index 0098e68..1caa25b 100755
>> --- a/create_config
>> +++ b/create_config
>> @@ -25,7 +25,7 @@ case $line in
>>   CONFIG_AUDIO_DRIVERS=*)
>>      drivers=${line#*=}
>>      echo "#define CONFIG_AUDIO_DRIVERS \\"
>> -    for drv in $drivers; do
>> +    for drv in ${drivers//-/_}; do
>>        echo "    &${drv}_audio_driver,\\"
>>      done
>>      echo ""
>> @@ -39,10 +39,12 @@ case $line in
>>      ;;
>>   CONFIG_*=y) # configuration
>>      name=${line%=*}
>> +    name=${name//-/_}
>>      echo "#define $name 1"
>>      ;;
>>   CONFIG_*=*) # configuration
>>      name=${line%=*}
>> +    name=${name//-/_}
>>      value=${line#*=}
>>      echo "#define $name $value"
>>      ;;

Looks like ${.../...} is a bashism.

One can replace it with:

name=`echo "$name" | tr '-' '_'`

and

for drv in `echo "$drivers" | tr '-' '_'`; do

	-hpa
Amos Kong Sept. 14, 2010, 1:37 a.m. UTC | #14
On Mon, Sep 13, 2010 at 02:04:57PM -0700, H. Peter Anvin wrote:
> On 09/13/2010 01:53 PM, Amos Kong wrote:
> > 
> > # patch -p1 < /tmp/usb-audio.patch
> > # ./configure
> > ...
> > ...
> > preadv support    yes
> > fdatasync         yes
> > uuid support      no
> > vhost-net support no
> > Trace backend     nop
> > Trace output file trace-<pid>
> > ./configure: 2276: Bad substitution
> > 

Hello Peter,
 
> What shell is your /bin/sh?

dash,
It's fine when using bash, I suggest to use a common way.

I've heard wonderful music (guest:win7), but mixed with a litte noise, not so fluent.
The following debug msg is normal?

# ./x86_64-softmmu/qemu-system-x86_64 ~/win7-32.qcow2 -m 1024 -vnc :0 -usbdevice tablet -cpu qemu64  -enable-kvm -bios pc-bios/bios.bin -net nic,netdev=idkQlbc8,macaddr=02:BB:3A:D3:b8:29 -netdev tap,id=idkQlbc8,ifname=virtio_0_8000,script=/etc/qemu-ifup-vbr0,downscript=no,vhost=on -snapshot -usb -soundhw usb 
usb_create: no bus specified, using "usb.0" for "usb-audio"
usb-audio: reset
usb-audio: control transaction: request 0x0005 value 0x0001 index 0x0000 length 0x0000
usb-audio: control transaction: request 0x8006 value 0x0100 index 0x0000 length 0x0008
usb-audio: control transaction: request 0x8006 value 0x0200 index 0x0000 length 0x0009
usb-audio: control transaction: request 0x8006 value 0x0200 index 0x0000 length 0x0071
usb-audio: reset
usb-audio: reset
usb-audio: control transaction: request 0x8006 value 0x0100 index 0x0000 length 0x0040
usb-audio: reset
usb-audio: control transaction: request 0x0005 value 0x0001 index 0x0000 length 0x0000
usb-audio: control transaction: request 0x8006 value 0x0100 index 0x0000 length 0x0012
usb-audio: control transaction: request 0x8006 value 0x0200 index 0x0000 length 0x00ff
usb-audio: control transaction: request 0x8006 value 0x03ee index 0x0000 length 0x0012
usb-audio: control transaction: request 0x8006 value 0x0303 index 0x0409 length 0x00ff
usb-audio: control transaction: request 0x8006 value 0x0300 index 0x0000 length 0x00ff
usb-audio: control transaction: request 0x8006 value 0x0302 index 0x0409 length 0x00ff
usb-audio: control transaction: request 0x8006 value 0x0600 index 0x0000 length 0x000a
usb-audio: failed control transaction: request 0x8006 value 0x0600 index 0x0000 length 0x000a
usb-audio: control transaction: request 0x8006 value 0x0100 index 0x0000 length 0x0012
usb-audio: control transaction: request 0x8006 value 0x0200 index 0x0000 length 0x0009
usb-audio: control transaction: request 0x8006 value 0x0200 index 0x0000 length 0x0071
usb-audio: control transaction: request 0x0009 value 0x0001 index 0x0000 length 0x0000
usb-audio: control transaction: request 0x010b value 0x0000 index 0x0001 length 0x0000
usb-audio: control transaction: request 0x8006 value 0x0305 index 0x0409 length 0x0004
usb-audio: control transaction: request 0x8006 value 0x0305 index 0x0409 length 0x002c
usb-audio: control transaction: request 0x8006 value 0x0307 index 0x0409 length 0x004a
usb-audio: control transaction: request 0x8006 value 0x0308 index 0x0409 length 0x0004
usb-audio: control transaction: request 0x8006 value 0x0308 index 0x0409 length 0x003e
usb-audio: control transaction: request 0xa181 value 0x0100 index 0x0200 length 0x0001
usb-audio: control transaction: request 0xa181 value 0x0201 index 0x0200 length 0x0002
usb-audio: control transaction: request 0xa182 value 0x0201 index 0x0200 length 0x0002
usb-audio: control transaction: request 0xa183 value 0x0201 index 0x0200 length 0x0002
usb-audio: control transaction: request 0xa184 value 0x0201 index 0x0200 length 0x0002
usb-audio: control transaction: request 0xa181 value 0x0202 index 0x0200 length 0x0002
usb-audio: control transaction: request 0xa182 value 0x0202 index 0x0200 length 0x0002
usb-audio: control transaction: request 0xa183 value 0x0202 index 0x0200 length 0x0002
usb-audio: control transaction: request 0xa184 value 0x0202 index 0x0200 length 0x0002
usb-audio: control transaction: request 0x010b value 0x0000 index 0x0001 length 0x0000
usb-audio: control transaction: request 0x010b value 0x0001 index 0x0001 length 0x0000
usb-audio: set interface 1
usb-audio: control transaction: request 0x010b value 0x0000 index 0x0001 length 0x0000
usb-audio: set interface 0
usb-audio: control transaction: request 0x010b value 0x0001 index 0x0001 length 0x0000
usb-audio: set interface 1
usb-audio: control transaction: request 0x010b value 0x0000 index 0x0001 length 0x0000
usb-audio: set interface 0
usb-audio: control transaction: request 0x010b value 0x0001 index 0x0001 length 0x0000
usb-audio: set interface 1
usb-audio: control transaction: request 0x010b value 0x0000 index 0x0001 length 0x0000
usb-audio: set interface 0
usb-audio: control transaction: request 0x010b value 0x0001 index 0x0001 length 0x0000
usb-audio: set interface 1
usb-audio: control transaction: request 0x010b value 0x0000 index 0x0001 length 0x0000
usb-audio: set interface 0
usb-audio: control transaction: request 0x010b value 0x0001 index 0x0001 length 0x0000
usb-audio: set interface 1
usb-audio: control transaction: request 0x010b value 0x0000 index 0x0001 length 0x0000
usb-audio: set interface 0
usb-audio: control transaction: request 0x010b value 0x0001 index 0x0001 length 0x0000
usb-audio: set interface 1
usb-audio: control transaction: request 0x010b value 0x0000 index 0x0001 length 0x0000
usb-audio: set interface 0
usb-audio: control transaction: request 0x010b value 0x0001 index 0x0001 length 0x0000
usb-audio: set interface 1
H. Peter Anvin Sept. 14, 2010, 5:56 a.m. UTC | #15
On 09/13/2010 06:37 PM, Amos Kong wrote:
> 
> Hello Peter,
>  
>> What shell is your /bin/sh?
> 
> dash,
> It's fine when using bash, I suggest to use a common way.
> 

Yes, I'll fix it.

> I've heard wonderful music (guest:win7), but mixed with a litte noise, not so fluent.
> The following debug msg is normal?

Yes, all of that is normal.  I talked to malc earlier today, and I think
I have a pretty good idea for how to deal with the rate-matching issues;
I'm going to try to write it up tomorrow.

	-hpa
Mike Snitzer Oct. 14, 2010, 1:51 p.m. UTC | #16
On Tue, Sep 14, 2010 at 1:56 AM, H. Peter Anvin <hpa@zytor.com> wrote:
> On 09/13/2010 06:37 PM, Amos Kong wrote:
>> I've heard wonderful music (guest:win7), but mixed with a litte noise, not so fluent.
>> The following debug msg is normal?
>
> Yes, all of that is normal.  I talked to malc earlier today, and I think
> I have a pretty good idea for how to deal with the rate-matching issues;
> I'm going to try to write it up tomorrow.

Hi,

Was just wondering if you've been able to put some time to the
rate-matching issues?

Has this usb-audio patch evolved and I'm just missing it?

Thanks for doing this work!
Mike
H. Peter Anvin Oct. 14, 2010, 3:40 p.m. UTC | #17
On 10/14/2010 06:51 AM, Mike Snitzer wrote:
> 
> Was just wondering if you've been able to put some time to the
> rate-matching issues?
> 
> Has this usb-audio patch evolved and I'm just missing it?
> 
> Thanks for doing this work!
> Mike
> 

The sad result really is: it doesn't work, and it probably will never work.

	-hpa
Alon Levy Oct. 14, 2010, 4:18 p.m. UTC | #18
----- "H. Peter Anvin" <hpa@zytor.com> wrote:

> On 10/14/2010 06:51 AM, Mike Snitzer wrote:
> > 
> > Was just wondering if you've been able to put some time to the
> > rate-matching issues?
> > 
> > Has this usb-audio patch evolved and I'm just missing it?
> > 
> > Thanks for doing this work!
> > Mike
> > 
> 
> The sad result really is: it doesn't work, and it probably will never
> work.
> 

Can you elaborate?

> 	-hpa
> 
> -- 
> H. Peter Anvin, Intel Open Source Technology Center
> I work for Intel.  I don't speak on their behalf.
H. Peter Anvin Oct. 14, 2010, 5:43 p.m. UTC | #19
On 10/14/2010 09:18 AM, Alon Levy wrote:
> 
> Can you elaborate?
> 

The quality of rate information is too low, and the delays in the system
are too large to enable consistent convergence.

	-hpa
diff mbox

Patch

diff --git a/Makefile.objs b/Makefile.objs
index dbee210..8b5b908 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -245,6 +245,7 @@  sound-obj-$(CONFIG_AC97) += ac97.o
 sound-obj-$(CONFIG_ADLIB) += fmopl.o adlib.o
 sound-obj-$(CONFIG_GUS) += gus.o gusemu_hal.o gusemu_mixer.o
 sound-obj-$(CONFIG_CS4231A) += cs4231a.o
+sound-obj-$(CONFIG_USB_AUDIO) += usb-audio.o
 
 adlib.o fmopl.o: QEMU_CFLAGS += -DBUILD_Y8950=0
 hw-obj-$(CONFIG_SOUND) += $(sound-obj-y)
diff --git a/arch_init.c b/arch_init.c
index e468c0c..68e5643 100644
--- a/arch_init.c
+++ b/arch_init.c
@@ -430,7 +430,7 @@  struct soundhw soundhw[] = {
         "pcspk",
         "PC speaker",
         0,
-        1,
+        BUS_ISA,
         { .init_isa = pcspk_audio_init }
     },
 #endif
@@ -440,7 +440,7 @@  struct soundhw soundhw[] = {
         "sb16",
         "Creative Sound Blaster 16",
         0,
-        1,
+        BUS_ISA,
         { .init_isa = SB16_init }
     },
 #endif
@@ -450,7 +450,7 @@  struct soundhw soundhw[] = {
         "cs4231a",
         "CS4231A",
         0,
-        1,
+        BUS_ISA,
         { .init_isa = cs4231a_init }
     },
 #endif
@@ -464,7 +464,7 @@  struct soundhw soundhw[] = {
         "Yamaha YM3812 (OPL2)",
 #endif
         0,
-        1,
+        BUS_ISA,
         { .init_isa = Adlib_init }
     },
 #endif
@@ -474,7 +474,7 @@  struct soundhw soundhw[] = {
         "gus",
         "Gravis Ultrasound GF1",
         0,
-        1,
+        BUS_ISA,
         { .init_isa = GUS_init }
     },
 #endif
@@ -484,7 +484,7 @@  struct soundhw soundhw[] = {
         "ac97",
         "Intel 82801AA AC97 Audio",
         0,
-        0,
+        BUS_PCI,
         { .init_pci = ac97_init }
     },
 #endif
@@ -494,11 +494,21 @@  struct soundhw soundhw[] = {
         "es1370",
         "ENSONIQ AudioPCI ES1370",
         0,
-        0,
+        BUS_PCI,
         { .init_pci = es1370_init }
     },
 #endif
 
+#ifdef CONFIG_USB_AUDIO
+    {
+        "usb",
+        "USB Audio",
+        0,
+        BUS_USB,
+        { .init_usb = usb_audio_soundhw_init }
+    },
+#endif
+
 #endif /* HAS_AUDIO_CHOICE */
 
     { NULL, NULL, 0, 0, { NULL } }
diff --git a/configure b/configure
index 8228c1c..4fcb829 100755
--- a/configure
+++ b/configure
@@ -71,8 +71,8 @@  sparc_cpu=""
 cross_prefix=""
 cc="gcc"
 audio_drv_list=""
-audio_card_list="ac97 es1370 sb16"
-audio_possible_cards="ac97 es1370 sb16 cs4231a adlib gus"
+audio_card_list="ac97 es1370 sb16 usb-audio"
+audio_possible_cards="ac97 es1370 sb16 cs4231a adlib gus usb-audio"
 block_drv_whitelist=""
 host_cc="gcc"
 ar="ar"
@@ -2414,7 +2414,7 @@  if test "$vde" = "yes" ; then
 fi
 for card in $audio_card_list; do
     def=CONFIG_`echo $card | tr '[:lower:]' '[:upper:]'`
-    echo "$def=y" >> $config_host_mak
+    echo ${def//-/_}=y >> $config_host_mak
 done
 echo "CONFIG_AUDIO_DRIVERS=$audio_drv_list" >> $config_host_mak
 for drv in $audio_drv_list; do
diff --git a/create_config b/create_config
index 0098e68..1caa25b 100755
--- a/create_config
+++ b/create_config
@@ -25,7 +25,7 @@  case $line in
  CONFIG_AUDIO_DRIVERS=*)
     drivers=${line#*=}
     echo "#define CONFIG_AUDIO_DRIVERS \\"
-    for drv in $drivers; do
+    for drv in ${drivers//-/_}; do
       echo "    &${drv}_audio_driver,\\"
     done
     echo ""
@@ -39,10 +39,12 @@  case $line in
     ;;
  CONFIG_*=y) # configuration
     name=${line%=*}
+    name=${name//-/_}
     echo "#define $name 1"
     ;;
  CONFIG_*=*) # configuration
     name=${line%=*}
+    name=${name//-/_}
     value=${line#*=}
     echo "#define $name $value"
     ;;
diff --git a/hw/audiodev.h b/hw/audiodev.h
index 39a729b..24daef4 100644
--- a/hw/audiodev.h
+++ b/hw/audiodev.h
@@ -15,3 +15,7 @@  int ac97_init(PCIBus *buf);
 
 /* cs4231a.c */
 int cs4231a_init(qemu_irq *pic);
+
+/* usb-audio.c */
+int usb_audio_soundhw_init(USBBus *bus);
+
diff --git a/hw/pc.c b/hw/pc.c
index 9c08573..c456189 100644
--- a/hw/pc.c
+++ b/hw/pc.c
@@ -756,13 +756,21 @@  void pc_audio_init (PCIBus *pci_bus, qemu_irq *pic)
 
     for (c = soundhw; c->name; ++c) {
         if (c->enabled) {
-            if (c->isa) {
+	    switch (c->bus) {
+	    case BUS_ISA:
                 c->init.init_isa(pic);
-            } else {
+		break;
+	    case BUS_PCI:
                 if (pci_bus) {
                     c->init.init_pci(pci_bus);
                 }
-            }
+		break;
+	    case BUS_USB:
+		if (usb_enabled) {
+                    c->init.init_usb(NULL /* FIXME */);
+                }
+		break;
+	    }
         }
     }
 }
diff --git a/hw/pc_piix.c b/hw/pc_piix.c
index 3d07ce5..5c98b63 100644
--- a/hw/pc_piix.c
+++ b/hw/pc_piix.c
@@ -167,8 +167,6 @@  static void pc_init1(ram_addr_t ram_size,
         }
     }
 
-    pc_audio_init(pci_enabled ? pci_bus : NULL, isa_irq);
-
     pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device,
                  idebus[0], idebus[1], floppy_controller, rtc_state);
 
@@ -176,6 +174,8 @@  static void pc_init1(ram_addr_t ram_size,
         usb_uhci_piix3_init(pci_bus, piix3_devfn + 2);
     }
 
+    pc_audio_init(pci_enabled ? pci_bus : NULL, isa_irq);
+
     if (pci_enabled && acpi_enabled) {
         uint8_t *eeprom_buf = qemu_mallocz(8 * 256); /* XXX: make this persistent */
         i2c_bus *smbus;
diff --git a/hw/usb-audio.c b/hw/usb-audio.c
new file mode 100644
index 0000000..d4cf488
--- /dev/null
+++ b/hw/usb-audio.c
@@ -0,0 +1,702 @@ 
+/*
+ * QEMU USB Net devices
+ *
+ * Copyright (c) 2006 Thomas Sailer
+ * Copyright (c) 2008 Andrzej Zaborowski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu-common.h"
+#include "usb.h"
+#include "hw.h"
+#include "audiodev.h"
+#include "audio/audio.h"
+
+#define USBAUDIO_VENDOR_NUM	0xabcd
+#define USBAUDIO_PRODUCT_NUM	0x1234
+
+#define DEV_CONFIG_VALUE	1 /* The one and only */
+
+/* Descriptor subtypes for AC interfaces */
+#define DST_AC_HEADER		1
+#define DST_AC_INPUT_TERMINAL	2
+#define DST_AC_OUTPUT_TERMINAL	3
+#define DST_AC_FEATURE_UNIT	6
+/* Descriptor subtypes for AS interfaces */
+#define DST_AS_GENERAL		1
+#define DST_AS_FORMAT_TYPE	2
+/* Descriptor subtypes for endpoints */
+#define DST_EP_GENERAL		1
+
+enum usb_audio_strings {
+    STRING_NULL,
+    STRING_MANUFACTURER,
+    STRING_PRODUCT,
+    STRING_SERIALNUMBER,
+    STRING_CONFIG,
+    STRING_USBAUDIO_CONTROL,
+    STRING_INPUT_TERMINAL,
+    STRING_FEATURE_UNIT,
+    STRING_OUTPUT_TERMINAL,
+    STRING_NULL_STREAM,
+    STRING_REAL_STREAM,
+};
+
+static const char * const usb_audio_stringtable[] = {
+    [STRING_MANUFACTURER]	= "QEMU",
+    [STRING_PRODUCT]		= "QEMU USB Audio",
+    [STRING_SERIALNUMBER]	= "1",
+    [STRING_CONFIG]             = "QEMU USB Audio Configuration",
+    [STRING_USBAUDIO_CONTROL]	= "QEMU USB Audio Device",
+    [STRING_INPUT_TERMINAL]	= "QEMU USB Audio Output Pipe",
+    [STRING_FEATURE_UNIT]       = "QEMU USB Audio Output Volume Control",
+    [STRING_OUTPUT_TERMINAL]    = "QEMU USB Audio Output Terminal",
+    [STRING_NULL_STREAM]        = "QEMU USB Audio Output - Disabled",
+    [STRING_REAL_STREAM]        = "QEMU USB Audio Output - 48 kHz Stereo",
+};
+
+#define U16(x) ((x) & 0xff), (((x) >> 8) & 0xff)
+#define U24(x) U16(x), (((x) >> 16) & 0xff)
+#define U32(x) U24(x), (((x) >> 24) & 0xff)
+
+static const uint8_t qemu_usb_audio_dev_descriptor[] = {
+    0x12,			/*  u8 bLength; */
+    USB_DT_DEVICE,		/*  u8 bDescriptorType; Device */
+    0x00, 0x02,			/*  u16 bcdUSB; v2.0 */
+    0x00,			/*  u8  bDeviceClass; [ interface level ] */
+    0x00,			/*  u8  bDeviceSubClass; */
+    0x00,			/*  u8  bDeviceProtocol; [ low/full only ] */
+    0x40,			/*  u8  bMaxPacketSize0 */
+    U16(USBAUDIO_VENDOR_NUM),	/*  u16 idVendor; */
+    U16(USBAUDIO_PRODUCT_NUM),	/*  u16 idProduct; */
+    0x00, 0x00,			/*  u16 bcdDevice */
+    STRING_MANUFACTURER,	/*  u8  iManufacturer; */
+    STRING_PRODUCT,		/*  u8  iProduct; */
+    STRING_SERIALNUMBER,	/*  u8  iSerialNumber; */
+    0x01,			/*  u8  bNumConfigurations; */
+};
+
+/*
+ * A Basic Audio Device uses these specific values
+ */
+#define USBAUDIO_PACKET_SIZE	192
+#define USBAUDIO_SAMPLE_RATE	48000
+#define USBAUDIO_PACKET_INTERVAL 1
+
+/*
+ * This basically follows a "Basic Audio Device Headphone Type 1",
+ * except the Terminal Type is set to SPEAKER (0x0301) instead
+ * of HEADPHONE (0x0302)
+ */
+static const uint8_t qemu_usb_audio_config_descriptor[] = {
+    /* Configuration Descriptor */
+    0x09,			/*  u8  bLength */
+    USB_DT_CONFIG,		/*  u8  bDescriptorType */
+    U16(0x71),			/*  le16 wTotalLength */
+    0x02,			/*  u8  bNumInterfaces */
+    DEV_CONFIG_VALUE,		/*  u8  bConfigurationValue */
+    STRING_CONFIG,		/*  u8  iConfiguration */
+    0xc0,			/*  u8  bmAttributes */
+    0x32,			/*  u8  bMaxPower */
+    /* USB Basic Headphone AC Interface */
+    0x09,			/*  u8  bLength */
+    USB_DT_INTERFACE,		/*  u8  bDescriptorType */
+    0x00,			/*  u8  bInterfaceNumber */
+    0x00,			/*  u8  bAlternateSetting */
+    0x00,			/*  u8  bNumEndpoints */
+    USB_CLASS_AUDIO,		/*  u8  bInterfaceClass */
+    USB_SUBCLASS_AUDIO_CONTROL,	/*  u8  bInterfaceSubClass */
+    0x04,			/*  u8  bInterfaceProtocol */
+    STRING_USBAUDIO_CONTROL,	/*  u8  iInterface */
+    /* Headphone Class-Specific AC Interface Header Descriptor */
+    0x09,			/*  u8  bLength */
+    USB_DT_CS_INTERFACE,	/*  u8  bDescriptorType */
+    DST_AC_HEADER,		/*  u8  bDescriptorSubtype */
+    U16(0x0100),		/* u16  bcdADC */
+    U16(0x2b),			/* u16  wTotalLength */
+    0x01,			/*  u8  bInCollection */
+    0x01,			/*  u8  baInterfaceNr */
+    /* Generic Stereo Input Terminal ID1 Descriptor */
+    0x0c,			/*  u8  bLength */
+    USB_DT_CS_INTERFACE,	/*  u8  bDescriptorType */
+    DST_AC_INPUT_TERMINAL,	/*  u8  bDescriptorSubtype */
+    0x01,			/*  u8  bTerminalID */
+    U16(0x0101),		/* u16  wTerminalType */
+    0x00,			/*  u8  bAssocTerminal */
+    0x02,			/* u16  bNrChannels */
+    U16(0x0003),		/* u16  wChannelConfig */
+    0x00,			/*  u8  iChannelNames */
+    STRING_INPUT_TERMINAL,	/*  u8  iTerminal */
+    /* Generic Stereo Feature Unit ID2 Descriptor */
+    0x0d,			/*  u8  bLength */
+    USB_DT_CS_INTERFACE,	/*  u8  bDescriptorType */
+    DST_AC_FEATURE_UNIT,	/*  u8  bDescriptorSubtype */
+    0x02,			/*  u8  bUnitID */
+    0x01,			/*  u8  bSourceID */
+    0x02,			/*  u8  bControlSize */
+    U16(0x0001),		/* u16  bmaControls(0) */
+    U16(0x0002),		/* u16  bmaControls(1) */
+    U16(0x0002),		/* u16  bmaControls(2) */
+    STRING_FEATURE_UNIT,	/*  u8  iFeature */
+    /* Headphone Ouptut Terminal ID3 Descriptor */
+    0x09,			/*  u8  bLength */
+    USB_DT_CS_INTERFACE,	/*  u8  bDescriptorType */
+    DST_AC_OUTPUT_TERMINAL,	/*  u8  bDescriptorSubtype */
+    0x03,			/*  u8  bUnitID */
+    U16(0x0301),		/* u16  wTerminalType (SPEAKER) */
+    0x00,			/*  u8  bAssocTerminal */
+    0x02,			/*  u8  bSourceID */
+    STRING_OUTPUT_TERMINAL,	/*  u8  iTerminal */
+    /* Headphone Standard AS Interface Descriptor (Alt Set 0) */
+    0x09,			/*  u8  bLength */
+    USB_DT_INTERFACE,		/*  u8  bDescriptorType */
+    0x01,			/*  u8  bInterfaceNumber */
+    0x00,			/*  u8  bAlternateSetting */
+    0x00,			/*  u8  bNumEndpoints */
+    USB_CLASS_AUDIO,		/*  u8  bInterfaceClass */
+    USB_SUBCLASS_AUDIO_STREAMING, /* u8 bInterfaceSubclass */
+    0x00,			/*  u8  bInterfaceProtocol */
+    STRING_NULL_STREAM,		/*  u8  iInterface */
+    /* Headphone Standard AS Interface Descriptor (Alt Set 1) */
+    0x09,			/*  u8  bLength */
+    USB_DT_INTERFACE,		/*  u8  bDescriptorType */
+    0x01,			/*  u8  bInterfaceNumber */
+    0x01,			/*  u8  bAlternateSetting */
+    0x01,			/*  u8  bNumEndpoins */
+    USB_CLASS_AUDIO,		/*  u8  bInterfaceClass */
+    USB_SUBCLASS_AUDIO_STREAMING, /* u8 bInterfaceSubclass */
+    0x00,			/*  u8  bInterfaceProtocol */
+    STRING_REAL_STREAM,		/*  u8  iInterface */
+    /* Headphone Class-specific AS General Interface Descriptor */
+    0x07,			/*  u8  bLength */
+    USB_DT_CS_INTERFACE,	/*  u8  bDescriptorType */
+    DST_AS_GENERAL,		/*  u8  bDescriptorSubtype */
+    0x01,			/*  u8  bTerminalLink */
+    0x00,			/*  u8  bDelay */
+    0x01, 0x00,			/* u16  wFormatTag */
+    /* Headphone Type I Format Type Descriptor */
+    0x0b,			/*  u8  bLength */
+    USB_DT_CS_INTERFACE,	/*  u8  bDescriptorType */
+    DST_AS_FORMAT_TYPE,		/*  u8  bDescriptorSubtype */
+    0x01,			/*  u8  bFormatType */
+    0x02,			/*  u8  bNrChannels */
+    0x02,			/*  u8  bSubFrameSize */
+    0x10,			/*  u8  bBitResolution */
+    0x01,			/*  u8  bSamFreqType */
+    U24(USBAUDIO_SAMPLE_RATE),	/* u24  tSamFreq */
+    /* Stereo Headphone Standard AS Audio Data Endpoint Descriptor */
+    0x09,			/*  u8  bLength */
+    USB_DT_ENDPOINT,		/*  u8  bDescriptorType */
+    0x01,			/*  u8  bEndpointAddress */
+    0x0d,			/*  u8  bmAttributes */
+    U16(USBAUDIO_PACKET_SIZE),	/* u16  wMaxPacketSize */
+    USBAUDIO_PACKET_INTERVAL,	/*  u8  bInterval */
+    0x00,			/*  u8  bRefresh */
+    0x00,			/*  u8  bSynchAddress */
+    /* Stereo Headphone Class-specific AS Audio Data Endpoint Descriptor */
+    0x07,			/*  u8  bLength */
+    USB_DT_CS_ENDPOINT,		/*  u8  bDescriptorType */
+    DST_EP_GENERAL,		/*  u8  bDescriptorSubtype */
+    0x00,			/*  u8  bmAttributes */
+    0x00,			/*  u8  bLockDelayUnits */
+    U16(0x0000),		/* u16  wLockDelay */
+};
+
+/*
+ * A USB audio device supports an arbitrary number of alternate
+ * interface settings for each interface.  Each corresponds to a block
+ * diagram of parameterized blocks.  This can thus refer to things like
+ * number of channels, data rates, or in fact completely different
+ * block diagrams.  Alternative setting 0 is always the null block diagram,
+ * which is used by a disabled device.
+ */
+enum usb_audio_altset {
+    ALTSET_OFF	= 0x00,		/* No endpoint */
+    ALTSET_ON	= 0x01,		/* Single endpoint */
+};
+
+/*
+ * Class-specific control requests
+ */
+#define CR_SET_CUR	0x01
+#define CR_GET_CUR	0x81
+#define CR_SET_MIN	0x02
+#define CR_GET_MIN	0x82
+#define CR_SET_MAX	0x03
+#define CR_GET_MAX	0x83
+#define CR_SET_RES	0x04
+#define CR_GET_RES	0x84
+#define CR_SET_MEM	0x05
+#define CR_GET_MEM	0x85
+#define CR_GET_STAT	0xff
+
+/*
+ * Feature Unit Control Selectors
+ */
+#define MUTE_CONTROL			0x01
+#define VOLUME_CONTROL			0x02
+#define BASS_CONTROL			0x03
+#define MID_CONTROL			0x04
+#define TREBLE_CONTROL			0x05
+#define GRAPHIC_EQUALIZER_CONTROL	0x06
+#define AUTOMATIC_GAIN_CONTROL		0x07
+#define DELAY_CONTROL			0x08
+#define BASS_BOOST_CONTROL		0x09
+#define LOUDNESS_CONTROL		0x0a
+
+typedef struct USBAudioState {
+    USBDevice dev;
+    QEMUSoundCard card;
+    enum usb_audio_altset outaltset;
+    SWVoiceOut *outvoice;
+    bool mute;
+    uint8_t vol[2];
+} USBAudioState;
+
+static void output_callback(void *opaque, int avail)
+{
+    (void)opaque;
+    (void)avail;
+}
+
+static int usb_audio_set_output_altset(USBAudioState *s, int altset)
+{
+    struct audsettings as;
+
+    if (altset == s->outaltset)
+	return 0;
+
+    switch (altset) {
+    case ALTSET_OFF:
+	as.nchannels = 0;
+	break;
+
+    case ALTSET_ON:
+	as.freq = USBAUDIO_SAMPLE_RATE;
+	as.nchannels = 2;
+	as.fmt = AUD_FMT_S16;
+	as.endianness = 0;
+	break;
+
+    default:
+	return -1;
+    }
+
+    if (s->outvoice) {
+	AUD_close_out (&s->card, s->outvoice);
+	s->outvoice = NULL;
+    }
+
+    if (as.nchannels) {
+	s->outvoice = AUD_open_out (&s->card, s->outvoice, "usb-audio",
+				    s, output_callback, &as);
+	AUD_set_volume_out (s->outvoice, s->mute, s->vol[0], s->vol[1]);
+	AUD_set_active_out (s->outvoice, 1);
+    }
+
+    fprintf(stderr, "usb-audio: set interface %d\n", altset);
+
+    s->outaltset = altset;
+    return 0;
+}
+
+/*
+ * Note: we arbitrarily map the volume control range onto -inf..+8 dB
+ */
+#define ATTRIB_ID(cs, attrib, idif)	\
+    (((cs) << 24) | ((attrib) << 16) | (idif))
+
+static int usb_audio_get_control(USBAudioState *s, uint8_t attrib,
+				 uint16_t cscn, uint16_t idif,
+				 int length, uint8_t *data)
+{
+    uint8_t cs = cscn >> 8;
+    uint8_t cn = cscn - 1;	/* -1 for the non-present master control */
+    uint32_t aid = ATTRIB_ID(cs, attrib, idif);
+    int ret = USB_RET_STALL;
+
+    switch (aid) {
+    case ATTRIB_ID(MUTE_CONTROL, CR_GET_CUR, 0x0200):
+	data[0] = s->mute;
+	ret = 1;
+	break;
+    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200):
+	if (cn < 2) {
+	    uint16_t vol = (s->vol[cn] * 0x8800 + 127) / 255 + 0x8000;
+	    data[0] = vol;
+	    data[1] = vol >> 8;
+	    ret = 2;
+	}
+	break;
+    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MIN, 0x0200):
+	if (cn < 2) {
+	    data[0] = 0x01;
+	    data[1] = 0x80;
+	    ret = 2;
+	}
+	break;
+    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200):
+	if (cn < 2) {
+	    data[0] = 0x00;
+	    data[1] = 0x08;
+	    ret = 2;
+	}
+	break;
+    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200):
+	if (cn < 2) {
+	    data[0] = 0x88;
+	    data[1] = 0x00;
+	    ret = 2;
+	}
+	break;
+    }
+
+    return ret;
+}
+static int usb_audio_set_control(USBAudioState *s, uint8_t attrib,
+				 uint16_t cscn, uint16_t idif,
+				 int length, uint8_t *data)
+{
+    uint8_t cs = cscn >> 8;
+    uint8_t cn = cscn - 1;	/* -1 for the non-present master control */
+    uint32_t aid = ATTRIB_ID(cs, attrib, idif);
+    int ret = USB_RET_STALL;
+    bool set_vol = false;
+
+    switch (aid) {
+    case ATTRIB_ID(MUTE_CONTROL, CR_SET_CUR, 0x0200):
+	s->mute = data[0] & 1;
+	set_vol = true;
+	ret = 0;
+	break;
+    case ATTRIB_ID(VOLUME_CONTROL, CR_SET_CUR, 0x0200):
+	if (cn < 2) {
+	    uint16_t vol = data[0] + (data[1] << 8);
+
+	    fprintf(stderr, "usb-audio: vol %04x\n", (uint16_t)vol);
+
+	    vol -= 0x8000;
+	    vol = (vol * 255 + 0x4400) / 0x8800;
+	    if (vol > 255)
+		vol = 255;
+
+	    s->vol[cn] = vol;
+	    set_vol = true;
+	    ret = 0;
+	}
+	break;
+    }
+
+    if (set_vol) {
+	fprintf(stderr, "usb-audio: mute %d, lvol %3d, rvol %3d\n",
+		s->mute, s->vol[0], s->vol[1]);
+	AUD_set_volume_out (s->outvoice, s->mute, s->vol[0], s->vol[1]);
+    }
+
+    return ret;
+}
+
+static int usb_audio_handle_control(USBDevice *dev, int request, int value,
+                int index, int length, uint8_t *data)
+{
+    USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
+    int ret = 0;
+
+    fprintf(stderr, "usb-audio: control transaction: "
+	    "request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n",
+	    request, value, index, length);
+
+    switch(request) {
+    case DeviceRequest | USB_REQ_GET_STATUS:
+        data[0] = (1 << USB_DEVICE_SELF_POWERED) |
+                (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP);
+        data[1] = 0x00;
+        ret = 2;
+        break;
+
+    case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
+        if (value == USB_DEVICE_REMOTE_WAKEUP) {
+            dev->remote_wakeup = 0;
+        } else {
+            goto fail;
+        }
+        ret = 0;
+        break;
+
+    case DeviceOutRequest | USB_REQ_SET_FEATURE:
+        if (value == USB_DEVICE_REMOTE_WAKEUP) {
+            dev->remote_wakeup = 1;
+        } else {
+            goto fail;
+        }
+        ret = 0;
+        break;
+
+    case DeviceOutRequest | USB_REQ_SET_ADDRESS:
+        dev->addr = value;
+        ret = 0;
+        break;
+
+    case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
+        switch(value >> 8) {
+        case USB_DT_DEVICE:
+            ret = sizeof(qemu_usb_audio_dev_descriptor);
+            memcpy(data, qemu_usb_audio_dev_descriptor, ret);
+            break;
+
+        case USB_DT_CONFIG:
+            switch (value & 0xff) {
+            case 0:
+                ret = sizeof(qemu_usb_audio_config_descriptor);
+                memcpy(data, qemu_usb_audio_config_descriptor, ret);
+                break;
+
+            default:
+                goto fail;
+            }
+
+            data[2] = ret & 0xff;
+            data[3] = ret >> 8;
+            break;
+
+        case USB_DT_STRING:
+            switch (value & 0xff) {
+            case 0:
+                /* language ids */
+                data[0] = 4;
+                data[1] = 3;
+                data[2] = 0x09;
+                data[3] = 0x04;
+                ret = 4;
+                break;
+
+            default:
+                if (usb_audio_stringtable[value & 0xff]) {
+                    ret = set_usb_string(data,
+                                    usb_audio_stringtable[value & 0xff]);
+                    break;
+                }
+
+                goto fail;
+            }
+            break;
+
+        default:
+            goto fail;
+        }
+        break;
+
+    case DeviceRequest | USB_REQ_GET_CONFIGURATION:
+        data[0] = DEV_CONFIG_VALUE;
+        ret = 1;
+        break;
+
+    case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
+        switch (value & 0xff) {
+        case DEV_CONFIG_VALUE:
+            break;
+
+        default:
+            goto fail;
+        }
+        ret = 0;
+        break;
+
+    case DeviceRequest | USB_REQ_GET_INTERFACE:
+        data[0] = 0;
+        ret = 1;
+        break;
+
+    case DeviceOutRequest | USB_REQ_SET_INTERFACE:
+        ret = 0;
+        break;
+
+    case InterfaceRequest | USB_REQ_GET_INTERFACE:
+	if (index != 0x01)
+	    goto fail;
+        data[0] = s->outaltset;
+        ret = 1;
+        break;
+
+    case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
+	if (index != 0x01)
+	    goto fail;
+	if (usb_audio_set_output_altset(s, value))
+	    goto fail;
+
+        ret = 0;
+        break;
+
+    case ClassInterfaceRequest | CR_GET_CUR:
+    case ClassInterfaceRequest | CR_GET_MIN:
+    case ClassInterfaceRequest | CR_GET_MAX:
+    case ClassInterfaceRequest | CR_GET_RES:
+	ret = usb_audio_get_control(s, request & 0xff, value, index,
+				    length, data);
+	if (ret < 0)
+	    goto fail;
+	break;
+
+    case ClassInterfaceOutRequest | CR_SET_CUR:
+    case ClassInterfaceOutRequest | CR_SET_MIN:
+    case ClassInterfaceOutRequest | CR_SET_MAX:
+    case ClassInterfaceOutRequest | CR_SET_RES:
+	ret = usb_audio_set_control(s, request & 0xff, value, index,
+				    length, data);
+	if (ret < 0)
+	    goto fail;
+	break;
+
+    default:
+    fail:
+        fprintf(stderr, "usb-audio: failed control transaction: "
+                        "request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n",
+                        request, value, index, length);
+        ret = USB_RET_STALL;
+        break;
+    }
+    return ret;
+}
+
+
+static void usb_audio_handle_reset(USBDevice *dev)
+{
+    USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
+
+    fprintf(stderr, "usb-audio: reset\n");
+    usb_audio_set_output_altset(s, ALTSET_OFF);
+}
+
+static int usb_audio_handle_dataout(USBAudioState *s, USBPacket *p)
+{
+    int rv;
+
+    /*
+     * Note: allow for the case where the USB HCI model's notion of time
+     * is faster than the audio device, which means we'll eventually overrun
+     * the output buffer.  Drop those samples, sorry.  Can we do better?
+     */
+    rv = AUD_write (s->outvoice, p->data, p->len);
+
+    if (rv < p->len)
+	fprintf(stderr, "usb-audio: dropped %d samples\n", (p->len - rv) >> 2);
+
+    return 0;
+}
+
+static int usb_audio_handle_data(USBDevice *dev, USBPacket *p)
+{
+    USBAudioState *s = (USBAudioState *) dev;
+    int ret = 0;
+
+    switch(p->pid) {
+    case USB_TOKEN_OUT:
+        switch (p->devep) {
+        case 1:
+            ret = usb_audio_handle_dataout(s, p);
+            break;
+
+        default:
+            goto fail;
+        }
+        break;
+
+    default:
+    fail:
+        ret = USB_RET_STALL;
+	break;
+    }
+    if (ret == USB_RET_STALL)
+        fprintf(stderr, "usb-audio: failed data transaction: "
+                        "pid 0x%x ep 0x%x len 0x%x\n",
+                        p->pid, p->devep, p->len);
+    return ret;
+}
+
+static void usb_audio_handle_destroy(USBDevice *dev)
+{
+    USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
+
+    fprintf(stderr, "usb-audio: destroy\n");
+
+    usb_audio_set_output_altset(s, ALTSET_OFF);
+}
+
+static int usb_audio_initfn(USBDevice *dev)
+{
+    USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
+
+    s->dev.speed  = USB_SPEED_FULL;
+    s->outvoice   = NULL;
+    s->outaltset  = ALTSET_OFF;
+    s->mute       = false;
+    s->vol[0]     = 240;	/* 0 dB */
+    s->vol[1]     = 240;	/* 0 dB */
+    return 0;
+}
+
+static USBDevice *usb_audio_init(USBBus *bus)
+{
+    USBDevice *dev;
+    USBAudioState *s;
+
+    dev = usb_create_simple(bus, "usb-audio");
+    s = DO_UPCAST(struct USBAudioState, dev, dev);
+    s->dev.opaque = s;
+
+    AUD_register_card("usb-audio", &s->card);
+
+    return dev;
+}
+
+/* Called by "-soundhw usb" */
+int usb_audio_soundhw_init(USBBus *bus)
+{
+    return usb_audio_init(bus) ? 0 : -1;
+}
+
+/* Called by "-usb audio" */
+static USBDevice *usb_audio_usb_init(const char *cmdline)
+{
+    USBDevice *dev = usb_audio_init(NULL /* FIXME */);
+
+    return dev;
+}
+
+static struct USBDeviceInfo usb_audio_info = {
+    .product_desc   = "QEMU USB Audio Interface",
+    .qdev.name      = "usb-audio",
+    .qdev.size      = sizeof(USBAudioState),
+    .init           = usb_audio_initfn,
+    .handle_packet  = usb_generic_handle_packet,
+    .handle_reset   = usb_audio_handle_reset,
+    .handle_control = usb_audio_handle_control,
+    .handle_data    = usb_audio_handle_data,
+    .handle_destroy = usb_audio_handle_destroy,
+    .usbdevice_name = "audio",
+    .usbdevice_init = usb_audio_usb_init,
+};
+
+static void usb_audio_register_devices(void)
+{
+    usb_qdev_register(&usb_audio_info);
+}
+
+device_init (usb_audio_register_devices)
diff --git a/hw/usb-net.c b/hw/usb-net.c
index 70f9263..61469f3 100644
--- a/hw/usb-net.c
+++ b/hw/usb-net.c
@@ -68,9 +68,6 @@  enum usbstring_idx {
 #define USB_CDC_UNION_TYPE		0x06	/* union_desc */
 #define USB_CDC_ETHERNET_TYPE		0x0f	/* ether_desc */
 
-#define USB_DT_CS_INTERFACE		0x24
-#define USB_DT_CS_ENDPOINT		0x25
-
 #define USB_CDC_SEND_ENCAPSULATED_COMMAND	0x00
 #define USB_CDC_GET_ENCAPSULATED_RESPONSE	0x01
 #define USB_CDC_REQ_SET_LINE_CODING		0x20
diff --git a/hw/usb.h b/hw/usb.h
index 00d2802..40fd9bd 100644
--- a/hw/usb.h
+++ b/hw/usb.h
@@ -67,6 +67,11 @@ 
 #define USB_CLASS_APP_SPEC		0xfe
 #define USB_CLASS_VENDOR_SPEC		0xff
 
+#define USB_SUBCLASS_UNDEFINED		0
+#define USB_SUBCLASS_AUDIO_CONTROL	1
+#define USB_SUBCLASS_AUDIO_STREAMING	2
+#define USB_SUBCLASS_AUDIO_MIDISTREAMING 3
+
 #define USB_DIR_OUT			0
 #define USB_DIR_IN			0x80
 
@@ -116,18 +121,14 @@ 
 #define USB_DT_STRING			0x03
 #define USB_DT_INTERFACE		0x04
 #define USB_DT_ENDPOINT			0x05
+#define USB_DT_CS_INTERFACE		0x24
+#define USB_DT_CS_ENDPOINT		0x25
 
 #define USB_ENDPOINT_XFER_CONTROL	0
 #define USB_ENDPOINT_XFER_ISOC		1
 #define USB_ENDPOINT_XFER_BULK		2
 #define USB_ENDPOINT_XFER_INT		3
 
-typedef struct USBBus USBBus;
-typedef struct USBPort USBPort;
-typedef struct USBDevice USBDevice;
-typedef struct USBDeviceInfo USBDeviceInfo;
-typedef struct USBPacket USBPacket;
-
 /* definition of a USB device */
 struct USBDevice {
     DeviceState qdev;
diff --git a/qemu-common.h b/qemu-common.h
index dfd3dc0..650ab17 100644
--- a/qemu-common.h
+++ b/qemu-common.h
@@ -229,6 +229,11 @@  typedef struct I2SCodec I2SCodec;
 typedef struct SSIBus SSIBus;
 typedef struct EventNotifier EventNotifier;
 typedef struct VirtIODevice VirtIODevice;
+typedef struct USBBus USBBus;
+typedef struct USBPort USBPort;
+typedef struct USBDevice USBDevice;
+typedef struct USBDeviceInfo USBDeviceInfo;
+typedef struct USBPacket USBPacket;
 
 typedef uint64_t pcibus_t;
 
diff --git a/sysemu.h b/sysemu.h
index 98bd47d..4bf8a20 100644
--- a/sysemu.h
+++ b/sysemu.h
@@ -172,14 +172,21 @@  extern CharDriverState *parallel_hds[MAX_PARALLEL_PORTS];
 #define TFR(expr) do { if ((expr) != -1) break; } while (errno == EINTR)
 
 #ifdef HAS_AUDIO
+enum bus_type {
+    BUS_PCI,
+    BUS_ISA,
+    BUS_USB,
+};
+
 struct soundhw {
     const char *name;
     const char *descr;
     int enabled;
-    int isa;
+    enum bus_type bus;
     union {
-        int (*init_isa) (qemu_irq *pic);
         int (*init_pci) (PCIBus *bus);
+        int (*init_isa) (qemu_irq *pic);
+        int (*init_usb) (USBBus *bus);
     } init;
 };