From patchwork Sun Oct 13 19:58:05 2019 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: 1175891 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="Xzi6bvSb"; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 46rt7k6jQ5z9sPK for ; Mon, 14 Oct 2019 07:09:20 +1100 (AEDT) Received: from localhost ([::1]:42076 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iJkB3-0007Wm-Gl for incoming@patchwork.ozlabs.org; Sun, 13 Oct 2019 16:09:17 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:37328) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iJk0R-0005Yr-8d for qemu-devel@nongnu.org; Sun, 13 Oct 2019 15:58:21 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1iJk0O-0003Kd-GO for qemu-devel@nongnu.org; Sun, 13 Oct 2019 15:58:19 -0400 Received: from mail-wr1-x443.google.com ([2a00:1450:4864:20::443]:34570) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1iJk0O-0003JR-5S for qemu-devel@nongnu.org; Sun, 13 Oct 2019 15:58:16 -0400 Received: by mail-wr1-x443.google.com with SMTP id j11so17263387wrp.1 for ; Sun, 13 Oct 2019 12:58:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=1xbpHh712sivYhk5HmhmXHkxPur7dIJpK6QrNTW7BR0=; b=Xzi6bvSbYyboubJNp5M8xLxAzCjyMBD+PZnMsSV+M8/dzGR1tA1eObUi+xFox87Aqn tUrj0GEyr/twVt5j6X7PArNbV36i2Eu1CV5AecmjA7h2ci7Ph0ODbPMb/bVeleSrLVX2 VA58plTK2gE0y5LvbkLyqVlG/5ZvSpUEg7uK+QYE2mjdxezE40pmvvfyFNnTQKQ9q+/Y c7Go0MWCgdpzz16itCnSFsH1L/f9ALZljM89HE4ERlAJ3fzTUTjhuoIGTRZvUQtM1otZ NMh+AUjZME6XrOfaMCsxT09xRC/yWNNJo6XVcfMEanyZg7lRo7MBDpSf16xmjyrXLqlM Vcig== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=1xbpHh712sivYhk5HmhmXHkxPur7dIJpK6QrNTW7BR0=; b=Yn+FvB2UgY5EPjDVYXocHdCO89KNafXDU4xu7+hfxrfMSBmxlG5OzsHjYbU8djeRlC njN7FF4zuutTXrtc1FoVzgTZdWHlqiPIhpgbkqyhx2RNpVyNjhAAYLGqW0jDvpQXh9G5 hD6lldpTuJ9JIASUNAXXMfY7gs2YskC/nYDkYMraW01rGBNZoM/E8Qx3SymJas3l+aeo A+indUPtSCogS6vShvtv9faxXBEp/E1wGaGfA+d3bkqCyii5TkUSHVkWk5yhrJFVklcf Mp4C49BnQ9zYe0O5/5GebXvNqxphAW2Vs1XXJ268zm8Nm7SAJewLv8OXG61wcSgGpfxs BQ2Q== X-Gm-Message-State: APjAAAWASuUBAz3KQFEc8Tet1Dsm6yylw9h1A3I/wGt9JYJejiG5k4ph UpXAOp8Y0HnPX/+kIO85Rkd4o5P3 X-Google-Smtp-Source: APXvYqyZDRhjS2zM8XjE0Z71Z3psS2b7+/s8g+mfM52yT5gDz9F0/dbHQLlTP99phxI5VskZjGdORQ== X-Received: by 2002:adf:f68f:: with SMTP id v15mr22182149wrp.234.1570996694744; Sun, 13 Oct 2019 12:58:14 -0700 (PDT) Received: from nullptr.home.dirty-ice.org (2a01-036c-0113-0052-0000-0000-0000-0005.pool6.digikabel.hu. [2a01:36c:113:52::5]) by smtp.gmail.com with ESMTPSA id x129sm29941857wmg.8.2019.10.13.12.58.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 13 Oct 2019 12:58:14 -0700 (PDT) From: "=?UTF-8?q?K=C5=91v=C3=A1g=C3=B3=2C=20Zolt=C3=A1n?=" X-Google-Original-From: =?utf-8?b?S8WRdsOhZ8OzLCBab2x0w6Fu?= To: qemu-devel@nongnu.org Subject: [PATCH v6 08/10] usb-audio: support more than two channels of audio Date: Sun, 13 Oct 2019 21:58:05 +0200 Message-Id: <98e96606228afa907fa238eac26573d5af63434a.1570996490.git.DirtY.iCE.hu@gmail.com> X-Mailer: git-send-email 2.23.0 In-Reply-To: References: MIME-Version: 1.0 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 2a00:1450:4864:20::443 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Gerd Hoffmann Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" This commit adds support for 5.1 and 7.1 audio playback. This commit adds a new property to usb-audio: * multi=on|off Whether to enable the 5.1 and 7.1 audio support. When off (default) it continues to emulate the old stereo-only device. When on, it emulates a slightly different audio device that supports 5.1 and 7.1 audio. Signed-off-by: Kővágó, Zoltán --- Notes: According to the spec the channel order is front left, front right, center, lfe, surround left, surround right for 5.1 sound. But alsa uses front left, front right, surround left, surround right, center, lfe. This means if you simply use an alsa device as -audiodev, the surround and center/lfe channels will be swapped. 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.mixing-engine=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) Pulseaudio allows us to specify channel order, and we default to a compatible order so it should work correctly without extra setup. hw/usb/dev-audio.c | 419 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 366 insertions(+), 53 deletions(-) diff --git a/hw/usb/dev-audio.c b/hw/usb/dev-audio.c index 74c99b1f12..e42bdfbdc1 100644 --- a/hw/usb/dev-audio.c +++ b/hw/usb/dev-audio.c @@ -37,11 +37,15 @@ #include "desc.h" #include "audio/audio.h" +static void usb_audio_reinit(USBDevice *dev, unsigned channels); + #define USBAUDIO_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */ #define USBAUDIO_PRODUCT_NUM 0x0002 #define DEV_CONFIG_VALUE 1 /* The one and only */ +#define USBAUDIO_MAX_CHANNELS(s) (s->multi ? 8 : 2) + /* Descriptor subtypes for AC interfaces */ #define DST_AC_HEADER 1 #define DST_AC_INPUT_TERMINAL 2 @@ -80,6 +84,27 @@ static const USBDescStrings usb_audio_stringtable = { [STRING_REAL_STREAM] = "Audio Output - 48 kHz Stereo", }; +/* + * 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_STEREO = 0x01, /* Single endpoint */ + ALTSET_51 = 0x02, + ALTSET_71 = 0x03, +}; + +static unsigned altset_channels[] = { + [ALTSET_STEREO] = 2, + [ALTSET_51] = 6, + [ALTSET_71] = 8, +}; + #define U16(x) ((x) & 0xff), (((x) >> 8) & 0xff) #define U24(x) U16(x), (((x) >> 16) & 0xff) #define U32(x) U24(x), (((x) >> 24) & 0xff) @@ -87,7 +112,8 @@ 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 @@ -121,7 +147,7 @@ static const USBDescIface desc_iface[] = { 0x01, /* u8 bTerminalID */ U16(0x0101), /* u16 wTerminalType */ 0x00, /* u8 bAssocTerminal */ - 0x02, /* u16 bNrChannels */ + 0x02, /* u8 bNrChannels */ U16(0x0003), /* u16 wChannelConfig */ 0x00, /* u8 iChannelNames */ STRING_INPUT_TERMINAL, /* u8 iTerminal */ @@ -156,14 +182,14 @@ static const USBDescIface desc_iface[] = { }, },{ .bInterfaceNumber = 1, - .bAlternateSetting = 0, + .bAlternateSetting = ALTSET_OFF, .bNumEndpoints = 0, .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, .iInterface = STRING_NULL_STREAM, },{ .bInterfaceNumber = 1, - .bAlternateSetting = 1, + .bAlternateSetting = ALTSET_STEREO, .bNumEndpoints = 1, .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, @@ -199,7 +225,7 @@ static const USBDescIface desc_iface[] = { { .bEndpointAddress = USB_DIR_OUT | 0x01, .bmAttributes = 0x0d, - .wMaxPacketSize = USBAUDIO_PACKET_SIZE, + .wMaxPacketSize = USBAUDIO_PACKET_SIZE(2), .bInterval = 1, .is_audio = 1, /* Stereo Headphone Class-specific @@ -247,17 +273,274 @@ static const USBDesc desc_audio = { .str = usb_audio_stringtable, }; -/* - * 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 */ +/* multi channel compatible desc */ + +static const USBDescIface desc_iface_multi[] = { + { + .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(0x38), /* 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 */ + 0x08, /* u8 bNrChannels */ + U16(0x063f), /* u16 wChannelConfig */ + 0x00, /* u8 iChannelNames */ + STRING_INPUT_TERMINAL, /* u8 iTerminal */ + } + },{ + /* Generic Stereo Feature Unit ID2 Descriptor */ + .data = (uint8_t[]) { + 0x19, /* 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) */ + U16(0x0002), /* u16 bmaControls(3) */ + U16(0x0002), /* u16 bmaControls(4) */ + U16(0x0002), /* u16 bmaControls(5) */ + U16(0x0002), /* u16 bmaControls(6) */ + U16(0x0002), /* u16 bmaControls(7) */ + U16(0x0002), /* u16 bmaControls(8) */ + 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 = ALTSET_OFF, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, + .iInterface = STRING_NULL_STREAM, + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = ALTSET_STEREO, + .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(2), + .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 */ + }, + }, + } + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = ALTSET_51, + .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 */ + 0x06, /* 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(6), + .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 */ + }, + }, + } + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = ALTSET_71, + .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 */ + 0x08, /* 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(8), + .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 const USBDescDevice desc_device_multi = { + .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_multi), + .ifs = desc_iface_multi, + } + }, +}; + +static const USBDesc desc_audio_multi = { + .id = { + .idVendor = USBAUDIO_VENDOR_NUM, + .idProduct = USBAUDIO_PRODUCT_NUM, + .bcdDevice = 0, + .iManufacturer = STRING_MANUFACTURER, + .iProduct = STRING_PRODUCT, + .iSerialNumber = STRING_SERIALNUMBER, + }, + .full = &desc_device_multi, + .str = usb_audio_stringtable, }; /* @@ -300,10 +583,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; @@ -315,21 +599,21 @@ 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; } - if (p->iov.size != USBAUDIO_PACKET_SIZE) { + if (p->iov.size != 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) @@ -357,14 +641,15 @@ typedef struct USBAudioState { enum usb_audio_altset altset; struct audsettings as; SWVoiceOut *voice; - bool mute; - uint8_t vol[2]; + Volume vol; struct streambuf buf; + uint32_t channels; } out; /* properties */ uint32_t debug; - uint32_t buffer; + uint32_t buffer_user, buffer; + bool multi; } USBAudioState; #define TYPE_USB_AUDIO "usb-audio" @@ -397,10 +682,15 @@ 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: + case ALTSET_STEREO: + case ALTSET_51: + case ALTSET_71: + if (s->out.channels != altset_channels[altset]) { + usb_audio_reinit(USB_DEVICE(s), altset_channels[altset]); + } + streambuf_init(&s->out.buf, s->buffer, s->out.channels); AUD_set_active_out(s->out.voice, true); break; default: @@ -431,33 +721,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 < USBAUDIO_MAX_CHANNELS(s)) { + 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 < USBAUDIO_MAX_CHANNELS(s)) { data[0] = 0x01; data[1] = 0x80; ret = 2; } break; case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200): - if (cn < 2) { + if (cn < USBAUDIO_MAX_CHANNELS(s)) { data[0] = 0x00; data[1] = 0x08; ret = 2; } break; case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200): - if (cn < 2) { + if (cn < USBAUDIO_MAX_CHANNELS(s)) { data[0] = 0x88; data[1] = 0x00; ret = 2; @@ -479,16 +769,17 @@ 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 < USBAUDIO_MAX_CHANNELS(s)) { uint16_t vol = data[0] + (data[1] << 8); if (s->debug) { - fprintf(stderr, "usb-audio: vol %04x\n", (uint16_t)vol); + fprintf(stderr, "usb-audio: cn %d vol %04x\n", cn, + (uint16_t)vol); } vol -= 0x8000; @@ -497,7 +788,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; } @@ -506,11 +797,14 @@ 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]); + int i; + fprintf(stderr, "usb-audio: mute %d", s->out.vol.mute); + for (i = 0; i < USBAUDIO_MAX_CHANNELS(s); ++i) { + fprintf(stderr, ", vol[%d] %3d", i, s->out.vol.vol[i]); + } + fprintf(stderr, "\n"); } - 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; @@ -603,7 +897,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->out.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); @@ -645,6 +939,9 @@ static void usb_audio_unrealize(USBDevice *dev, Error **errp) static void usb_audio_realize(USBDevice *dev, Error **errp) { USBAudioState *s = USB_AUDIO(dev); + int i; + + dev->usb_desc = s->multi ? &desc_audio_multi : &desc_audio; usb_desc_create_serial(dev); usb_desc_init(dev); @@ -652,18 +949,35 @@ 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; + for (i = 0; i < USBAUDIO_MAX_CHANNELS(s); ++i) { + s->out.vol.vol[i] = 240; /* 0 dB */ + } + + usb_audio_reinit(dev, 2); +} + +static void usb_audio_reinit(USBDevice *dev, unsigned channels) +{ + USBAudioState *s = USB_AUDIO(dev); + + s->out.channels = channels; + if (!s->buffer_user) { + s->buffer = 32 * USBAUDIO_PACKET_SIZE(s->out.channels); + } else { + s->buffer = s->buffer_user; + } + + s->out.vol.channels = s->out.channels; s->out.as.freq = USBAUDIO_SAMPLE_RATE; - s->out.as.nchannels = 2; + s->out.as.nchannels = s->out.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->out.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); } @@ -675,8 +989,8 @@ 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, - 32 * USBAUDIO_PACKET_SIZE), + DEFINE_PROP_UINT32("buffer", USBAudioState, buffer_user, 0), + DEFINE_PROP_BOOL("multi", USBAudioState, multi, false), DEFINE_PROP_END_OF_LIST(), }; @@ -689,7 +1003,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;