diff mbox

[5/6] Add support for a USB audio device model

Message ID 1288195475-3807-6-git-send-email-kraxel@redhat.com
State New
Headers show

Commit Message

Gerd Hoffmann Oct. 27, 2010, 4:04 p.m. UTC
From: H. Peter Anvin <hpa@linux.intel.com>

This brings a usb audio device to qemu.  Output only, fixed at
16bit stereo @ 480000 Hz.  Based on a patch from
H. Peter Anvin <hpa@linux.intel.com>

Usage: add '-device usb-audio' to your qemu command line.

Works sorta ok on a idle machine.  Known issues:

 * Is *very* sensitive to latencies: when the uhci emulation misses one
   of the usb frame rate wakeups (1k/sec!) you'll loose/delay data from
   the audio stream, resulting in dropouts.
 * Also seems to not play very well with the usb tablet (and/or usb hub,
   to be investigated).  Best try this as the only device on a usb bus.
 * Burns quite some CPU due to usb polling.

In short:  It brings the qemu usb emulation to its limits.  Enjoy!

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 Makefile.objs  |    2 +-
 hw/usb-audio.c |  758 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/usb-net.c   |    3 -
 hw/usb.h       |    7 +
 4 files changed, 766 insertions(+), 4 deletions(-)
 create mode 100644 hw/usb-audio.c

Comments

H. Peter Anvin Oct. 27, 2010, 5:13 p.m. UTC | #1
On 10/27/2010 9:04 AM, Gerd Hoffmann wrote:
> From: H. Peter Anvin<hpa@linux.intel.com>
>
> This brings a usb audio device to qemu.  Output only, fixed at
> 16bit stereo @ 480000 Hz.  Based on a patch from
> H. Peter Anvin<hpa@linux.intel.com>
>
> Usage: add '-device usb-audio' to your qemu command line.
>
> Works sorta ok on a idle machine.  Known issues:
>
>   * Is *very* sensitive to latencies: when the uhci emulation misses one
>     of the usb frame rate wakeups (1k/sec!) you'll loose/delay data from
>     the audio stream, resulting in dropouts.
>   * Also seems to not play very well with the usb tablet (and/or usb hub,
>     to be investigated).  Best try this as the only device on a usb bus.
>   * Burns quite some CPU due to usb polling.
>
> In short:  It brings the qemu usb emulation to its limits.  Enjoy!
>
> Signed-off-by: Gerd Hoffmann<kraxel@redhat.com>

Please don't apply this.  This patch is outdated and has several glaring 
bugs.  I can send a better patch later (I'm in an all-day meeting with 
insanely bad networking.)

	-hpa
Gerd Hoffmann Oct. 28, 2010, 8:01 a.m. UTC | #2
On 10/27/10 19:13, H. Peter Anvin wrote:
> On 10/27/2010 9:04 AM, Gerd Hoffmann wrote:

>> This brings a usb audio device to qemu. Output only, fixed at
>> 16bit stereo @ 480000 Hz. Based on a patch from
>> H. Peter Anvin<hpa@linux.intel.com>

> Please don't apply this. This patch is outdated and has several glaring
> bugs. I can send a better patch later (I'm in an all-day meeting with
> insanely bad networking.)

Oh, I thought you've abandoned the project ...

Note that I've update the patch too and fixed at least some of those 
bugs.  Nevertheless I'll be happy to see your latest bits and try to 
merge the best of both versions.

cheers,
   Gerd
H. Peter Anvin Oct. 28, 2010, 3:23 p.m. UTC | #3
On 10/28/2010 1:01 AM, Gerd Hoffmann wrote:
> On 10/27/10 19:13, H. Peter Anvin wrote:
>> On 10/27/2010 9:04 AM, Gerd Hoffmann wrote:
>
>>> This brings a usb audio device to qemu. Output only, fixed at
>>> 16bit stereo @ 480000 Hz. Based on a patch from
>>> H. Peter Anvin<hpa@linux.intel.com>
>
>> Please don't apply this. This patch is outdated and has several glaring
>> bugs. I can send a better patch later (I'm in an all-day meeting with
>> insanely bad networking.)
>
> Oh, I thought you've abandoned the project ...
>
> Note that I've update the patch too and fixed at least some of those
> bugs. Nevertheless I'll be happy to see your latest bits and try to
> merge the best of both versions.
>

I have a git tree on kernel.org with the latest version.  However, the 
rate-matching code needs to be ripped out since it just plain doesn't 
work, and tends to produce worse results than no rate matching:

http://git.kernel.org/?p=virt/qemu/hpa/qemu-usb-audio-wip.git;a=shortlog;h=refs/heads/usb-audio

	-hpa
H. Peter Anvin Oct. 28, 2010, 3:28 p.m. UTC | #4
On 10/28/2010 1:01 AM, Gerd Hoffmann wrote:
> On 10/27/10 19:13, H. Peter Anvin wrote:
>> On 10/27/2010 9:04 AM, Gerd Hoffmann wrote:
>
>>> This brings a usb audio device to qemu. Output only, fixed at
>>> 16bit stereo @ 480000 Hz. Based on a patch from
>>> H. Peter Anvin<hpa@linux.intel.com>
>
>> Please don't apply this. This patch is outdated and has several glaring
>> bugs. I can send a better patch later (I'm in an all-day meeting with
>> insanely bad networking.)
>
> Oh, I thought you've abandoned the project ...
>
> Note that I've update the patch too and fixed at least some of those
> bugs. Nevertheless I'll be happy to see your latest bits and try to
> merge the best of both versions.
>
> cheers,
> Gerd

Grmf ... looks like I have multiple pieces of code in multiple places... 
I need to reconcile the best version I have by hand first.
	-hpa
H. Peter Anvin Oct. 28, 2010, 3:29 p.m. UTC | #5
On 10/28/2010 1:01 AM, Gerd Hoffmann wrote:
> On 10/27/10 19:13, H. Peter Anvin wrote:
>> On 10/27/2010 9:04 AM, Gerd Hoffmann wrote:
>
>>> This brings a usb audio device to qemu. Output only, fixed at
>>> 16bit stereo @ 480000 Hz. Based on a patch from
>>> H. Peter Anvin<hpa@linux.intel.com>
>
>> Please don't apply this. This patch is outdated and has several glaring
>> bugs. I can send a better patch later (I'm in an all-day meeting with
>> insanely bad networking.)
>
> Oh, I thought you've abandoned the project ...
>
> Note that I've update the patch too and fixed at least some of those
> bugs. Nevertheless I'll be happy to see your latest bits and try to
> merge the best of both versions.

I'm going to have to look at this later... I'm in a F2F meeting most of 
the day today.

	-hpa
Gerd Hoffmann Oct. 28, 2010, 4:13 p.m. UTC | #6
Hi,

> I have a git tree on kernel.org with the latest version.

Thanks, I'll have a look when I find some time.

> However, the
> rate-matching code needs to be ripped out since it just plain doesn't
> work, and tends to produce worse results than no rate matching:

Doesn't surprise me.  There are a bunch of more serve issues which have 
to be fixed before rate-matching has a chance to produce useful results.

cheers,
   Gerd
diff mbox

Patch

diff --git a/Makefile.objs b/Makefile.objs
index f07fb01..06dfcd0 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -73,7 +73,7 @@  common-obj-y += eeprom93xx.o
 common-obj-y += scsi-disk.o cdrom.o
 common-obj-y += scsi-generic.o scsi-bus.o
 common-obj-y += usb.o usb-hub.o usb-$(HOST_USB).o usb-hid.o usb-msd.o usb-wacom.o
-common-obj-y += usb-serial.o usb-net.o usb-bus.o
+common-obj-y += usb-serial.o usb-net.o usb-bus.o usb-audio.o
 common-obj-$(CONFIG_SSI) += ssi.o
 common-obj-$(CONFIG_SSI_SD) += ssi-sd.o
 common-obj-$(CONFIG_SD) += sd.o
diff --git a/hw/usb-audio.c b/hw/usb-audio.c
new file mode 100644
index 0000000..8fb00b4
--- /dev/null
+++ b/hw/usb-audio.c
@@ -0,0 +1,758 @@ 
+/*
+ * 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[256] = {
+    [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(0),			/*  le16 wTotalLength, filled at runtime */
+    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
+
+/*
+ * buffering
+ */
+
+struct streambuf {
+    uint8_t *data;
+    uint32_t size;
+    uint32_t prod;
+    uint32_t cons;
+};
+
+static void streambuf_init(struct streambuf *buf, uint32_t size)
+{
+    qemu_free(buf->data);
+    buf->size = size - (size % USBAUDIO_PACKET_SIZE);
+    buf->data = qemu_malloc(buf->size);
+    buf->prod = 0;
+    buf->cons = 0;
+}
+
+static void streambuf_fini(struct streambuf *buf)
+{
+    qemu_free(buf->data);
+    buf->data = NULL;
+}
+
+static int streambuf_put(struct streambuf *buf, uint8_t *data)
+{
+    uint32_t free = buf->size - (buf->prod - buf->cons);
+
+    if (!free)
+        return 0;
+    assert(free >= USBAUDIO_PACKET_SIZE);
+    memcpy(buf->data + (buf->prod % buf->size), data, USBAUDIO_PACKET_SIZE);
+    buf->prod += USBAUDIO_PACKET_SIZE;
+    return USBAUDIO_PACKET_SIZE;
+}
+
+static uint8_t *streambuf_get(struct streambuf *buf)
+{
+    uint32_t used = buf->prod - buf->cons;
+    uint8_t *data;
+
+    if (!used)
+        return NULL;
+    assert(used >= USBAUDIO_PACKET_SIZE);
+    data = buf->data + (buf->cons % buf->size);
+    buf->cons += USBAUDIO_PACKET_SIZE;
+    return data;
+}
+
+typedef struct USBAudioState {
+    /* qemu interfaces */
+    USBDevice dev;
+    QEMUSoundCard card;
+
+    /* state */
+    struct {
+        enum usb_audio_altset altset;
+        struct audsettings as;
+        SWVoiceOut *voice;
+        bool mute;
+        uint8_t vol[2];
+        struct streambuf buf;
+    } out;
+
+    /* properties */
+    uint32_t debug;
+    uint32_t buffer;
+} USBAudioState;
+
+static void output_callback(void *opaque, int avail)
+{
+    USBAudioState *s = opaque;
+    uint8_t *data;
+
+    for (;;) {
+        if (avail < USBAUDIO_PACKET_SIZE)
+            return;
+        data = streambuf_get(&s->out.buf);
+        if (NULL == data) {
+            return;
+        }
+        AUD_write(s->out.voice, data, USBAUDIO_PACKET_SIZE);
+        avail -= USBAUDIO_PACKET_SIZE;
+    }
+}
+
+static int usb_audio_set_output_altset(USBAudioState *s, int altset)
+{
+    switch (altset) {
+    case ALTSET_OFF:
+        streambuf_init(&s->out.buf, s->buffer);
+        AUD_set_active_out(s->out.voice, false);
+	break;
+    case ALTSET_ON:
+        AUD_set_active_out(s->out.voice, true);
+	break;
+    default:
+	return -1;
+    }
+
+    if (s->debug)
+        fprintf(stderr, "usb-audio: set interface %d\n", altset);
+    s->out.altset = 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->out.mute;
+	ret = 1;
+	break;
+    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200):
+	if (cn < 2) {
+	    uint16_t vol = (s->out.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->out.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);
+
+            if (s->debug)
+                fprintf(stderr, "usb-audio: vol %04x\n", (uint16_t)vol);
+
+	    vol -= 0x8000;
+	    vol = (vol * 255 + 0x4400) / 0x8800;
+	    if (vol > 255)
+		vol = 255;
+
+	    s->out.vol[cn] = vol;
+	    set_vol = true;
+	    ret = 0;
+	}
+	break;
+    }
+
+    if (set_vol) {
+        if (s->debug)
+            fprintf(stderr, "usb-audio: mute %d, lvol %3d, rvol %3d\n",
+                    s->out.mute, s->out.vol[0], s->out.vol[1]);
+	AUD_set_volume_out(s->out.voice, s->out.mute, s->out.vol[0], s->out.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;
+
+    if (s->debug)
+        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);
+                data[2] = ret & 0xff;
+                data[3] = ret >> 8;
+                break;
+            default:
+                goto fail;
+            }
+            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;
+                }
+                if (s->debug)
+                    fprintf(stderr, "usb-audio: fail: string 0x%x doesn't exist\n",
+                            value & 0xff);
+                goto fail;
+            }
+            break;
+        default:
+            if (s->debug)
+                fprintf(stderr, "usb-audio: fail: unknown descriptor 0x%x\n",
+                        value >> 8);
+            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->out.altset;
+        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) {
+            if (s->debug)
+                fprintf(stderr, "usb-audio: fail: get control\n");
+	    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) {
+            if (s->debug)
+                fprintf(stderr, "usb-audio: fail: set control\n");
+	    goto fail;
+        }
+	break;
+
+    default:
+    fail:
+        if (s->debug)
+            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);
+
+    if (s->debug)
+        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 rc;
+
+    if (s->out.altset == ALTSET_OFF)
+        return USB_RET_STALL;
+
+    rc = streambuf_put(&s->out.buf, p->data);
+    if (rc < p->len && s->debug > 1) {
+        fprintf(stderr, "usb-audio: output overrun (%d bytes)\n", p->len - rc);
+    }
+
+    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 && s->debug)
+        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);
+
+    if (s->debug)
+        fprintf(stderr, "usb-audio: destroy\n");
+
+    usb_audio_set_output_altset(s, ALTSET_OFF);
+    AUD_close_out(&s->card, s->out.voice);
+    AUD_remove_card(&s->card);
+
+    streambuf_fini(&s->out.buf);
+}
+
+static int usb_audio_initfn(USBDevice *dev)
+{
+    USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
+
+    s->dev.speed  = USB_SPEED_FULL;
+    s->dev.opaque = s;
+    AUD_register_card("usb-audio", &s->card);
+
+    s->out.altset        = ALTSET_OFF;
+    s->out.mute          = false;
+    s->out.vol[0]        = 240;	/* 0 dB */
+    s->out.vol[1]        = 240;	/* 0 dB */
+    s->out.as.freq       = USBAUDIO_SAMPLE_RATE;
+    s->out.as.nchannels  = 2;
+    s->out.as.fmt        = AUD_FMT_S16;
+    s->out.as.endianness = 0;
+    streambuf_init(&s->out.buf, s->buffer);
+
+    s->out.voice = AUD_open_out(&s->card, s->out.voice, "usb-audio",
+                                s, output_callback, &s->out.as);
+    AUD_set_volume_out(s->out.voice, s->out.mute, s->out.vol[0], s->out.vol[1]);
+    AUD_set_active_out(s->out.voice, 0);
+    return 0;
+}
+
+static struct USBDeviceInfo usb_audio_info = {
+    .product_desc   = "QEMU USB Audio Interface",
+    .usbdevice_name = "audio",
+    .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,
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_UINT32("debug", USBAudioState, debug, 0),
+        DEFINE_PROP_UINT32("buffer", USBAudioState, buffer, 8 * USBAUDIO_PACKET_SIZE),
+        DEFINE_PROP_END_OF_LIST(),
+    }
+};
+
+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..111aa39 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,6 +121,8 @@ 
 #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