From patchwork Mon Aug 17 18:11:58 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?UTF-8?B?Wm9sdMOhbiBLxZF2w6Fnw7M=?= X-Patchwork-Id: 508180 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id DB430140293 for ; Tue, 18 Aug 2015 15:23:09 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=cSL5dGLG; dkim-atps=neutral Received: from localhost ([::1]:56219 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZROtt-0001Wl-SQ for incoming@patchwork.ozlabs.org; Mon, 17 Aug 2015 14:12:49 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:58462) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZROtC-00014b-4r for qemu-devel@nongnu.org; Mon, 17 Aug 2015 14:12:08 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1ZROt6-0008Dc-LM for qemu-devel@nongnu.org; Mon, 17 Aug 2015 14:12:06 -0400 Received: from mail-wi0-x231.google.com ([2a00:1450:400c:c05::231]:34827) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZROt6-0008D2-5k for qemu-devel@nongnu.org; Mon, 17 Aug 2015 14:12:00 -0400 Received: by wicne3 with SMTP id ne3so76943168wic.0 for ; Mon, 17 Aug 2015 11:11:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-type:content-transfer-encoding; bh=gFBFqtcr3SIolgqke2PFRWTzeHlsuKQkld+CXq/yu7o=; b=cSL5dGLGTM20Ly2fvOnUyQGT8FKd6N1BqmqBVbDcAlde7YUdOC7uSdtf8f9XHccqqT Ra8r8X5rUV4pIAhE528ZupNbsx0kcg5+l3pUM1IcmpLgD9dJ+7YOuFTh6YRq6vmO8gpy ya2X2S739cLe+MSP1zEcsqvgbYnpjSfGJ9cU5WiTxVX8ijSZld0jbFzitKl/aAm2h8xo idDXT12fxQA0B7pESyBXdwI5E5D3voKpa0ifgpX1fs+uEbJE3uHkW3tWZ15vgT079mrh LsernfYMIKiTMYVx8zDflZGqDqXQrYSW4NhZZoyS6u5dnUCucc7FsxW+hkMok6Lz4wDG ejrw== X-Received: by 10.194.84.211 with SMTP id b19mr5230235wjz.120.1439835119586; Mon, 17 Aug 2015 11:11:59 -0700 (PDT) Received: from nullptr.home.dirty-ice.org (178-164-172-216.pool.digikabel.hu. [178.164.172.216]) by smtp.gmail.com with ESMTPSA id mc18sm17752876wic.23.2015.08.17.11.11.58 (version=TLSv1/SSLv3 cipher=OTHER); Mon, 17 Aug 2015 11:11:59 -0700 (PDT) From: "=?UTF-8?q?K=C5=91v=C3=A1g=C3=B3=2C=20Zolt=C3=A1n?=" X-Google-Original-From: =?UTF-8?q?K=C5=91v=C3=A1g=C3=B3=2C=20Zolt=C3=A1n?= To: qemu-devel@nongnu.org Date: Mon, 17 Aug 2015 20:11:58 +0200 Message-Id: <35f2c9de35bccec7e135974e656235a68e832f73.1439821260.git.DirtY.iCE.hu@gmail.com> X-Mailer: git-send-email 2.5.0 In-Reply-To: References: MIME-Version: 1.0 X-detected-operating-system: by eggs.gnu.org: Error: Malformed IPv6 address (bad octet value). X-Received-From: 2a00:1450:400c:c05::231 Cc: Gerd Hoffmann Subject: [Qemu-devel] [PATCH 5/5] usb-audio: support more than two channels of audio X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org This commit adds support for non stereo audio playback. This commit adds two new properties to usb-audio: * channels = the number of channels * channel-config = the channel config to use. Should be 3 for stereo, 0x3f for 5.1 and 0x63f for 7.1 audio. See USB Device Class Definition for Audio Devices for other possible values (http://www.usb.org/developers/docs/devclass_docs/audio10.pdf, p34) Signed-off-by: Kővágó, Zoltán --- According to the spec the channel order is front left, front right, center, lfe, surround left, surround right for 5.1 sound. But Linux with alsa seems to use front left, front right, surround left, surround right, center, lfe, while Windows uses the order in the specification. The default pulseaudio channel map currently is an ALSA compatible, which means by default Linux guests will have correct audio while Windows guests will have surround and center/lfe swapped. I could change the pulseaudio default to OSS like, but in that case Linux guest would be wrong and Windows ok. With alsa there is not much to do sort of writing a mini mixeng or something like that, but you can easily add a new device to /etc/asound.conf that swaps the channels: pcm.swap { type route slave.pcm "default" # or whatever slave.channels 6 ttable.0.0 1 ttable.1.1 1 ttable.2.4 1 ttable.3.5 1 ttable.4.2 1 ttable.5.3 1 } and use -audiodev alsa,id=foo,out.mixeng=off,alsa-out.dev=swap,... (due to how usb and usb-audio works, you'll probably need alsa-out.try-poll=off and some playing with threshold) hw/usb/dev-audio.c | 413 +++++++++++++++++++++++++++++------------------------ 1 file changed, 228 insertions(+), 185 deletions(-) diff --git a/hw/usb/dev-audio.c b/hw/usb/dev-audio.c index f916ccc..c0637a5 100644 --- a/hw/usb/dev-audio.c +++ b/hw/usb/dev-audio.c @@ -85,165 +85,190 @@ static const USBDescStrings usb_audio_stringtable = { /* * A Basic Audio Device uses these specific values */ -#define USBAUDIO_PACKET_SIZE 192 +#define USBAUDIO_PACKET_SIZE_BASE 96 +#define USBAUDIO_PACKET_SIZE(channels) (USBAUDIO_PACKET_SIZE_BASE * channels) #define USBAUDIO_SAMPLE_RATE 48000 #define USBAUDIO_PACKET_INTERVAL 1 -static const USBDescIface desc_iface[] = { - { - .bInterfaceNumber = 0, - .bNumEndpoints = 0, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL, - .bInterfaceProtocol = 0x04, - .iInterface = STRING_USBAUDIO_CONTROL, - .ndesc = 4, - .descs = (USBDescOther[]) { - { - /* Headphone Class-Specific AC Interface Header Descriptor */ - .data = (uint8_t[]) { - 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 */ - .data = (uint8_t[]) { - 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 */ - .data = (uint8_t[]) { - 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 */ - .data = (uint8_t[]) { - 0x09, /* u8 bLength */ - USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ - DST_AC_OUTPUT_TERMINAL, /* u8 bDescriptorSubtype */ - 0x03, /* u8 bUnitID */ - U16(0x0301), /* u16 wTerminalType (SPK) */ - 0x00, /* u8 bAssocTerminal */ - 0x02, /* u8 bSourceID */ - STRING_OUTPUT_TERMINAL, /* u8 iTerminal */ - } - } - }, - },{ - .bInterfaceNumber = 1, - .bAlternateSetting = 0, - .bNumEndpoints = 0, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, - .iInterface = STRING_NULL_STREAM, - },{ - .bInterfaceNumber = 1, - .bAlternateSetting = 1, - .bNumEndpoints = 1, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, - .iInterface = STRING_REAL_STREAM, - .ndesc = 2, - .descs = (USBDescOther[]) { - { - /* Headphone Class-specific AS General Interface Descriptor */ - .data = (uint8_t[]) { - 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 */ - .data = (uint8_t[]) { - 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 */ - } - } - }, - .eps = (USBDescEndpoint[]) { - { - .bEndpointAddress = USB_DIR_OUT | 0x01, - .bmAttributes = 0x0d, - .wMaxPacketSize = USBAUDIO_PACKET_SIZE, - .bInterval = 1, - .is_audio = 1, - /* Stereo Headphone Class-specific - AS Audio Data Endpoint Descriptor */ - .extra = (uint8_t[]) { - 0x07, /* u8 bLength */ - USB_DT_CS_ENDPOINT, /* u8 bDescriptorType */ - DST_EP_GENERAL, /* u8 bDescriptorSubtype */ - 0x00, /* u8 bmAttributes */ - 0x00, /* u8 bLockDelayUnits */ - U16(0x0000), /* u16 wLockDelay */ - }, - }, +static void *memdup(void *src, size_t len) +{ + void *ret = g_malloc(len); + memcpy(ret, src, len); + return ret; +} + +static USBDesc *alloc_desc(uint32_t channels, uint32_t channel_config) +{ + USBDescIface *desc_iface; + USBDescDevice *desc_device; + USBDescConfig *desc_config; + USBDesc *desc_audio; + + desc_iface = g_malloc0(sizeof(USBDescIface) * 3); + + desc_iface[0].bInterfaceNumber = 0; + desc_iface[0].bNumEndpoints = 0; + desc_iface[0].bInterfaceClass = USB_CLASS_AUDIO; + desc_iface[0].bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL; + desc_iface[0].bInterfaceProtocol = 0x04; + desc_iface[0].iInterface = STRING_USBAUDIO_CONTROL; + desc_iface[0].ndesc = 4; + desc_iface[0].descs = g_malloc0(sizeof(USBDescOther) * 4); + desc_iface[0].descs[0].data = memdup( + /* Headphone Class-Specific AC Interface Header Descriptor */ + (uint8_t[]) { + 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 */ + }, 9); + desc_iface[0].descs[1].data = memdup( + /* Generic Stereo Input Terminal ID1 Descriptor */ + (uint8_t[]) { + 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 */ + channels, /* u16 bNrChannels */ + U16(channel_config), /* u16 wChannelConfig */ + 0x00, /* u8 iChannelNames */ + STRING_INPUT_TERMINAL, /* u8 iTerminal */ + }, 12); + desc_iface[0].descs[2].data = memdup( + /* Generic Stereo Feature Unit ID2 Descriptor */ + (uint8_t[]) { + 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 */ + }, 13); + desc_iface[0].descs[3].data = memdup( + /* Headphone Ouptut Terminal ID3 Descriptor */ + (uint8_t[]) { + 0x09, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + DST_AC_OUTPUT_TERMINAL, /* u8 bDescriptorSubtype */ + 0x03, /* u8 bUnitID */ + U16(0x0301), /* u16 wTerminalType (SPK) */ + 0x00, /* u8 bAssocTerminal */ + 0x02, /* u8 bSourceID */ + STRING_OUTPUT_TERMINAL, /* u8 iTerminal */ + }, 9); + + desc_iface[1].bInterfaceNumber = 1; + desc_iface[1].bAlternateSetting = 0; + desc_iface[1].bNumEndpoints = 0; + desc_iface[1].bInterfaceClass = USB_CLASS_AUDIO; + desc_iface[1].bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING; + desc_iface[1].iInterface = STRING_NULL_STREAM; + + desc_iface[2].bInterfaceNumber = 1; + desc_iface[2].bAlternateSetting = 1; + desc_iface[2].bNumEndpoints = 1; + desc_iface[2].bInterfaceClass = USB_CLASS_AUDIO; + desc_iface[2].bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING; + desc_iface[2].iInterface = STRING_REAL_STREAM; + desc_iface[2].ndesc = 2; + desc_iface[2].descs = g_malloc0(sizeof(USBDescOther) * 2); + desc_iface[2].descs[0].data = memdup( + /* Headphone Class-specific AS General Interface Descriptor */ + (uint8_t[]) { + 0x07, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + DST_AS_GENERAL, /* u8 bDescriptorSubtype */ + 0x01, /* u8 bTerminalLink */ + 0x00, /* u8 bDelay */ + 0x01, 0x00, /* u16 wFormatTag */ + }, 7); + desc_iface[2].descs[1].data = memdup( + /* Headphone Type I Format Type Descriptor */ + (uint8_t[]) { + 0x0b, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + DST_AS_FORMAT_TYPE, /* u8 bDescriptorSubtype */ + 0x01, /* u8 bFormatType */ + channels, /* u8 bNrChannels */ + 0x02, /* u8 bSubFrameSize */ + 0x10, /* u8 bBitResolution */ + 0x01, /* u8 bSamFreqType */ + U24(USBAUDIO_SAMPLE_RATE), /* u24 tSamFreq */ + }, 11); + desc_iface[2].eps = g_malloc0(sizeof(USBDescEndpoint)); + desc_iface[2].eps[0].bEndpointAddress = USB_DIR_OUT | 0x01; + desc_iface[2].eps[0].bmAttributes = 0x0d; + desc_iface[2].eps[0].wMaxPacketSize = USBAUDIO_PACKET_SIZE(channels); + desc_iface[2].eps[0].bInterval = 1; + desc_iface[2].eps[0].is_audio = 1; + /* Stereo Headphone Class-specific + AS Audio Data Endpoint Descriptor */ + desc_iface[2].eps[0].extra = memdup((uint8_t[]) { + 0x07, /* u8 bLength */ + USB_DT_CS_ENDPOINT, /* u8 bDescriptorType */ + DST_EP_GENERAL, /* u8 bDescriptorSubtype */ + 0x00, /* u8 bmAttributes */ + 0x00, /* u8 bLockDelayUnits */ + U16(0x0000), /* u16 wLockDelay */ + }, 7); + + desc_device = g_malloc0(sizeof(USBDescDevice)); + desc_device->bcdUSB = 0x0100; + desc_device->bMaxPacketSize0 = 64; + desc_device->bNumConfigurations = 1; + desc_config = g_malloc0(sizeof(USBDescConfig)); + desc_device->confs = desc_config; + desc_config->bNumInterfaces = 2; + desc_config->bConfigurationValue = DEV_CONFIG_VALUE; + desc_config->iConfiguration = STRING_CONFIG; + desc_config->bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER; + desc_config->bMaxPower = 0x32; + desc_config->nif = 3; + desc_config->ifs = desc_iface; + + desc_audio = g_malloc0(sizeof(USBDesc)); + desc_audio->id.idVendor = USBAUDIO_VENDOR_NUM; + desc_audio->id.idProduct = USBAUDIO_PRODUCT_NUM; + desc_audio->id.bcdDevice = 0; + desc_audio->id.iManufacturer = STRING_MANUFACTURER; + desc_audio->id.iProduct = STRING_PRODUCT; + desc_audio->id.iSerialNumber = STRING_SERIALNUMBER; + desc_audio->full = desc_device; + desc_audio->str = usb_audio_stringtable; + + return desc_audio; +}; + +static void free_desc(USBDesc *desc) +{ + int i, j; + USBDescIface *desc_iface = (USBDescIface *) desc->full->confs->ifs; + + for (i = 0; i < desc->full->confs->nif; ++i) { + for (j = 0; j < desc_iface[i].ndesc; ++j) { + g_free((void *) desc_iface[i].descs[j].data); + } + g_free(desc_iface[i].descs); + if (desc_iface[i].eps) { + g_free(desc_iface[i].eps->extra); + g_free(desc_iface[i].eps); } } -}; + g_free(desc_iface); -static const USBDescDevice desc_device = { - .bcdUSB = 0x0100, - .bMaxPacketSize0 = 64, - .bNumConfigurations = 1, - .confs = (USBDescConfig[]) { - { - .bNumInterfaces = 2, - .bConfigurationValue = DEV_CONFIG_VALUE, - .iConfiguration = STRING_CONFIG, - .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, - .bMaxPower = 0x32, - .nif = ARRAY_SIZE(desc_iface), - .ifs = desc_iface, - }, - }, -}; - -static const USBDesc desc_audio = { - .id = { - .idVendor = USBAUDIO_VENDOR_NUM, - .idProduct = USBAUDIO_PRODUCT_NUM, - .bcdDevice = 0, - .iManufacturer = STRING_MANUFACTURER, - .iProduct = STRING_PRODUCT, - .iSerialNumber = STRING_SERIALNUMBER, - }, - .full = &desc_device, - .str = usb_audio_stringtable, -}; + g_free((void *) desc->full->confs); + g_free((void *) desc->full); + g_free(desc); +} /* * A USB audio device supports an arbitrary number of alternate @@ -298,10 +323,11 @@ struct streambuf { uint32_t cons; }; -static void streambuf_init(struct streambuf *buf, uint32_t size) +static void streambuf_init(struct streambuf *buf, uint32_t size, + uint32_t channels) { g_free(buf->data); - buf->size = size - (size % USBAUDIO_PACKET_SIZE); + buf->size = size - (size % USBAUDIO_PACKET_SIZE(channels)); buf->data = g_malloc(buf->size); buf->prod = 0; buf->cons = 0; @@ -313,18 +339,18 @@ static void streambuf_fini(struct streambuf *buf) buf->data = NULL; } -static int streambuf_put(struct streambuf *buf, USBPacket *p) +static int streambuf_put(struct streambuf *buf, USBPacket *p, uint32_t channels) { uint32_t free = buf->size - (buf->prod - buf->cons); - if (free < USBAUDIO_PACKET_SIZE) { + if (free < USBAUDIO_PACKET_SIZE(channels)) { return 0; } usb_packet_copy(p, buf->data + (buf->prod % buf->size), - USBAUDIO_PACKET_SIZE); - buf->prod += USBAUDIO_PACKET_SIZE; - return USBAUDIO_PACKET_SIZE; + USBAUDIO_PACKET_SIZE(channels)); + buf->prod += USBAUDIO_PACKET_SIZE(channels); + return USBAUDIO_PACKET_SIZE(channels); } static uint8_t *streambuf_get(struct streambuf *buf, size_t *len) @@ -352,14 +378,14 @@ typedef struct USBAudioState { enum usb_audio_altset altset; struct audsettings as; SWVoiceOut *voice; - bool mute; - uint8_t vol[2]; + Volume vol; struct streambuf buf; } out; /* properties */ uint32_t debug; uint32_t buffer; + uint32_t channels, channel_config; } USBAudioState; #define TYPE_USB_AUDIO "usb-audio" @@ -392,7 +418,7 @@ static int usb_audio_set_output_altset(USBAudioState *s, int altset) { switch (altset) { case ALTSET_OFF: - streambuf_init(&s->out.buf, s->buffer); + streambuf_init(&s->out.buf, s->buffer, s->channels); AUD_set_active_out(s->out.voice, false); break; case ALTSET_ON: @@ -426,33 +452,33 @@ static int usb_audio_get_control(USBAudioState *s, uint8_t attrib, switch (aid) { case ATTRIB_ID(MUTE_CONTROL, CR_GET_CUR, 0x0200): - data[0] = s->out.mute; + data[0] = s->out.vol.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; + if (cn < s->channels) { + uint16_t vol = (s->out.vol.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) { + if (cn < s->channels) { data[0] = 0x01; data[1] = 0x80; ret = 2; } break; case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200): - if (cn < 2) { + if (cn < s->channels) { data[0] = 0x00; data[1] = 0x08; ret = 2; } break; case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200): - if (cn < 2) { + if (cn < s->channels) { data[0] = 0x88; data[1] = 0x00; ret = 2; @@ -474,12 +500,12 @@ static int usb_audio_set_control(USBAudioState *s, uint8_t attrib, switch (aid) { case ATTRIB_ID(MUTE_CONTROL, CR_SET_CUR, 0x0200): - s->out.mute = data[0] & 1; + s->out.vol.mute = data[0] & 1; set_vol = true; ret = 0; break; case ATTRIB_ID(VOLUME_CONTROL, CR_SET_CUR, 0x0200): - if (cn < 2) { + if (cn < s->channels) { uint16_t vol = data[0] + (data[1] << 8); if (s->debug) { @@ -492,7 +518,7 @@ static int usb_audio_set_control(USBAudioState *s, uint8_t attrib, vol = 255; } - s->out.vol[cn] = vol; + s->out.vol.vol[cn] = vol; set_vol = true; ret = 0; } @@ -502,10 +528,9 @@ static int usb_audio_set_control(USBAudioState *s, uint8_t attrib, 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]); + s->out.vol.mute, s->out.vol.vol[0], s->out.vol.vol[1]); } - AUD_set_volume_out(s->out.voice, s->out.mute, - s->out.vol[0], s->out.vol[1]); + audio_set_volume_out(s->out.voice, &s->out.vol); } return ret; @@ -598,7 +623,7 @@ static void usb_audio_handle_dataout(USBAudioState *s, USBPacket *p) return; } - streambuf_put(&s->out.buf, p); + streambuf_put(&s->out.buf, p, s->channels); if (p->actual_length < p->iov.size && s->debug > 1) { fprintf(stderr, "usb-audio: output overrun (%zd bytes)\n", p->iov.size - p->actual_length); @@ -635,11 +660,27 @@ static void usb_audio_handle_destroy(USBDevice *dev) AUD_remove_card(&s->card); streambuf_fini(&s->out.buf); + + free_desc((USBDesc *) dev->usb_desc); } static void usb_audio_realize(USBDevice *dev, Error **errp) { USBAudioState *s = USB_AUDIO(dev); + int i; + + /* validate user options */ + if (s->channels > AUDIO_MAX_CHANNELS) { + fprintf(stderr, "usb-audio: only %d channels supported\n", + AUDIO_MAX_CHANNELS); + s->channels = AUDIO_MAX_CHANNELS; + } + if (!s->buffer) { + /* 1 packet -> 1ms audio, the default audio timer period is 10ms */ + s->buffer = 12 * USBAUDIO_PACKET_SIZE(s->channels); + } + + dev->usb_desc = alloc_desc(s->channels, s->channel_config); usb_desc_create_serial(dev); usb_desc_init(dev); @@ -647,18 +688,20 @@ static void usb_audio_realize(USBDevice *dev, Error **errp) AUD_register_card(TYPE_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.vol.mute = false; + s->out.vol.channels = s->channels; + for (i = 0; i < s->channels; ++i) { + s->out.vol.vol[i] = 240; /* 0 dB */ + } s->out.as.freq = USBAUDIO_SAMPLE_RATE; - s->out.as.nchannels = 2; + s->out.as.nchannels = s->channels; s->out.as.fmt = AUDIO_FORMAT_S16; s->out.as.endianness = 0; - streambuf_init(&s->out.buf, s->buffer); + streambuf_init(&s->out.buf, s->buffer, s->channels); s->out.voice = AUD_open_out(&s->card, s->out.voice, TYPE_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]); + audio_set_volume_out(s->out.voice, &s->out.vol); AUD_set_active_out(s->out.voice, 0); } @@ -670,8 +713,9 @@ static const VMStateDescription vmstate_usb_audio = { static Property usb_audio_properties[] = { DEFINE_AUDIO_PROPERTIES(USBAudioState, card), DEFINE_PROP_UINT32("debug", USBAudioState, debug, 0), - DEFINE_PROP_UINT32("buffer", USBAudioState, buffer, - 8 * USBAUDIO_PACKET_SIZE), + DEFINE_PROP_UINT32("buffer", USBAudioState, buffer, 0), + DEFINE_PROP_UINT32("channels", USBAudioState, channels, 2), + DEFINE_PROP_UINT32("channel-config", USBAudioState, channel_config, 3), DEFINE_PROP_END_OF_LIST(), }; @@ -684,7 +728,6 @@ static void usb_audio_class_init(ObjectClass *klass, void *data) dc->props = usb_audio_properties; set_bit(DEVICE_CATEGORY_SOUND, dc->categories); k->product_desc = "QEMU USB Audio Interface"; - k->usb_desc = &desc_audio; k->realize = usb_audio_realize; k->handle_reset = usb_audio_handle_reset; k->handle_control = usb_audio_handle_control;