audio: multi channel audio support
cover audio: multi channel audio support
This patch series adds support to more than two channels of audio (5.1, 7.1, etc.). Currently only usb-audio frontend and alsa and pa backends are updated. Using more than two channels requires turning off mixeng (-audiodev backend,id=foo,out.mixeng=off,...).
Currently you have to specify the number of channels you want to use when creating the usb-audio device, see the last commit for details. To do this I currently dynamically allocate the whole USBDesc structure, which looks ugly. If there's a better way, please let me know!
These patches requires my previous -audiodev patches:
https://lists.gnu.org/archive/html/qemu-devel/2015-08/msg00827.html
Please review.
Kővágó, Zoltán (5):
audio: replace shift in audio_pcm_info with bytes_per_frame
audio: basic support for multichannel audio
paaudio: channel-map option
usb-audio: do not count on avail bytes actually available
usb-audio: support more than two channels of audio
audio/alsaaudio.c | 17 +- audio/audio.c | 70 ++++---- audio/audio_int.h | 3 +- audio/coreaudio.c | 4 +- audio/dsound_template.h | 10 +- audio/dsoundaudio.c | 4 +- audio/noaudio.c | 2 +- audio/ossaudio.c | 14 +- audio/paaudio.c | 18 +- audio/wavaudio.c | 6 +- hw/usb/dev-audio.c | 441 +++++++++++++++++++++++++++--------------------- qapi/audio.json | 5 +- qemu-options.hx | 9 + 13 files changed, 330 insertions(+), 273 deletions(-)
Signature · 1 line
2.5.0
PATCH 1/5 audio: replace shift in audio_pcm_info with bytes_per_frame
The bit shifting trick worked because the number of bytes per frame was always a power-of-two (since QEMU only supports mono, stereo and 8, 16 and 32 bit samples). But if we want to add support for surround sound, this no longer holds true.
--- audio/alsaaudio.c | 10 ++++---- audio/audio.c | 68 ++++++++++++++++++++++++------------------------- audio/audio_int.h | 3 +-- audio/coreaudio.c | 4 +-- audio/dsound_template.h | 10 ++++---- audio/dsoundaudio.c | 4 +-- audio/noaudio.c | 2 +- audio/ossaudio.c | 14 +++++----- audio/wavaudio.c | 6 ++--- 9 files changed, 60 insertions(+), 61 deletions(-)
audio/alsaaudio.c +5 -5
@@ -613,7 +613,7 @@ static size_t alsa_write(HWVoiceOut *hw, void *buf, size_t len) {ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;size_t pos =0 ; -size_t len_frames = len >> hw->info.shift; +size_t len_frames = len / hw->info.bytes_per_frame;while (len_frames) {char *src =advance (buf, pos);
@@ -657,7 +657,7 @@ static size_t alsa_write(HWVoiceOut *hw, void *buf, size_t len) } } - pos += written << hw->info.shift; + pos += written * hw->info.bytes_per_frame;if (written < len_frames) {break ; }
@@ -823,7 +823,7 @@ static size_t alsa_read(HWVoiceIn *hw, void *buf, size_t len)void *dst =advance (buf, pos);snd_pcm_sframes_t nread; - nread =snd_pcm_readi (alsa->handle, dst, len >> hw->info.shift); + nread =snd_pcm_readi (alsa->handle, dst, len / hw->info.bytes_per_frame);if (nread <=0 ) {switch (nread) {
@@ -849,8 +849,8 @@ static size_t alsa_read(HWVoiceIn *hw, void *buf, size_t len) } } - pos += nread << hw->info.shift; - len -= nread << hw->info.shift; + pos += nread * hw->info.bytes_per_frame; + len -= nread * hw->info.bytes_per_frame; }return pos;
audio/audio.c +34 -34
240 lines not on this page · raw message
audio/audio_int.h +1 -2
@@ -42,8 +42,7 @@ struct audio_pcm_info {int sign;int freq;int nchannels; -int align; -int shift; +int bytes_per_frame;int bytes_per_second;int swap_endianness; };
audio/coreaudio.c +2 -2
@@ -225,7 +225,7 @@ static OSStatus audioDeviceIOProc( } frameCount = core->audioDevicePropertyBufferFrameSize; - pending_frames = hw->pending_emul >> hw->info.shift; + pending_frames = hw->pending_emul / hw->info.bytes_per_frame;/* if there are not enough samples, set signal and return */ if (pending_frames < frameCount) {
@@ -234,7 +234,7 @@ static OSStatus audioDeviceIOProc(return 0 ; } - len = frameCount << hw->info.shift; + len = frameCount * hw->info.bytes_per_frame;while (len) {size_t write_len;ssize_t start = ((ssize_t ) hw->pos_emul) - hw->pending_emul;
audio/dsound_template.h +5 -5
@@ -98,8 +98,8 @@ static int glue (dsound_lock_, TYPE) ( goto fail; } -if ((p1p && *p1p && (*blen1p & info->align)) || - (p2p && *p2p && (*blen2p & info->align))) { +if ((p1 && (blen1 % info->bytes_per_frame)) || + (p2 && (blen2 % info->bytes_per_frame))) {dolog ("DirectSound returned misaligned buffer %ld %ld\n" , *blen1p, *blen2p); glue (dsound_unlock_, TYPE) (buf, *p1p, p2p ? *p2p : NULL, *blen1p,
@@ -247,14 +247,14 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as, obt_as.endianness =0 ;audio_pcm_init_info (&hw->info, &obt_as); -if (bc.dwBufferBytes & hw->info.align) { +if (bc.dwBufferBytes % hw->info.bytes_per_frame) {dolog ("GetCaps returned misaligned buffer size %ld, alignment %d\n" , - bc.dwBufferBytes, hw->info.align +1 + bc.dwBufferBytes, hw->info.bytes_per_frame ); } hw->size_emul = bc.dwBufferBytes; - ds->samples = bc.dwBufferBytes >> hw->info.shift; + ds->samples = bc.dwBufferBytes / hw->info.bytes_per_frame; ds->s = s;#ifdef DEBUG_DSOUND
audio/dsoundaudio.c +2 -2
@@ -320,8 +320,8 @@ static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb,return ; } - len1 = blen1 >> hw->info.shift; - len2 = blen2 >> hw->info.shift; + len1 = blen1 / hw->info.bytes_per_frame; + len2 = blen2 / hw->info.bytes_per_frame;#ifdef DEBUG_DSOUND dolog ("clear %p,%ld,%ld %p,%ld,%ld\n",
audio/noaudio.c +1 -1
@@ -86,7 +86,7 @@ static size_t no_read(HWVoiceIn *hw, void *buf, size_t size)NoVoiceIn *no = (NoVoiceIn *) hw;int64_t bytes =audio_rate_get_bytes (&hw->info, &no->rate, size); -audio_pcm_info_clear_buf (&hw->info, buf, bytes >> hw->info.shift); +audio_pcm_info_clear_buf (&hw->info, buf, bytes / hw->info.bytes_per_frame);return bytes; }
audio/ossaudio.c +7 -7
@@ -511,16 +511,16 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as, oss->nfrags = obt.nfrags; oss->fragsize = obt.fragsize; -if (obt.nfrags * obt.fragsize & hw->info.align) { +if (obt.nfrags * obt.fragsize % hw->info.bytes_per_frame) {dolog ("warning: Misaligned DAC buffer, size %d, alignment %d\n" , - obt.nfrags * obt.fragsize, hw->info.align +1 ); + obt.nfrags * obt.fragsize, hw->info.bytes_per_frame); } - oss->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift; + oss->samples = (obt.nfrags * obt.fragsize) / hw->info.bytes_per_frame; oss->mmapped =0 ;if (oopts->has_try_mmap && oopts->try_mmap) { - hw->size_emul = oss->samples << hw->info.shift; + hw->size_emul = oss->samples * hw->info.bytes_per_frame; hw->buf_emul = mmap ( NULL, hw->size_emul,
@@ -655,12 +655,12 @@ static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) oss->nfrags = obt.nfrags; oss->fragsize = obt.fragsize; -if (obt.nfrags * obt.fragsize & hw->info.align) { +if (obt.nfrags * obt.fragsize % hw->info.bytes_per_frame) {dolog ("warning: Misaligned ADC buffer, size %d, alignment %d\n" , - obt.nfrags * obt.fragsize, hw->info.align +1 ); + obt.nfrags * obt.fragsize, hw->info.bytes_per_frame); } - oss->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift; + oss->samples = (obt.nfrags * obt.fragsize) / hw->info.bytes_per_frame; oss->fd = fd; oss->dev = dev;
audio/wavaudio.c +3 -3
@@ -40,14 +40,14 @@ static size_t wav_write_out(HWVoiceOut *hw, void *buf, size_t len) {WAVVoiceOut *wav = (WAVVoiceOut *) hw;int64_t bytes =audio_rate_get_bytes (&hw->info, &wav->rate, len); -assert (bytes >> hw->info.shift << hw->info.shift == bytes); +assert (bytes % hw->info.bytes_per_frame ==0 );if (bytes &&fwrite (buf, bytes,1 , wav->f) !=1 ) {dolog ("wav_write_out: fwrite of %zu bytes failed\nReaons: %s\n" , bytes,strerror (errno)); } - wav->total_samples += bytes >> hw->info.shift; + wav->total_samples += bytes / hw->info.bytes_per_frame;return bytes; }
@@ -130,7 +130,7 @@ static void wav_fini_out (HWVoiceOut *hw)WAVVoiceOut *wav = (WAVVoiceOut *) hw;uint8_t rlen[4 ];uint8_t dlen[4 ]; -uint32_t datalen = wav->total_samples << hw->info.shift; +uint32_t datalen = wav->total_samples * hw->info.bytes_per_frame;uint32_t rifflen = datalen +36 ;if (!wav->f) {
Signature · 1 line
2.5.0
PATCH 2/5 audio: basic support for multichannel audio
In reply to Kővágó, ZoltánWhich currently only means removing some checks. Old code won't require more than two channels, but new code will need it.
--- audio/alsaaudio.c | 7 ------- audio/audio.c | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-)
audio/alsaaudio.c +0 -7
@@ -499,13 +499,6 @@ static int alsa_open(bool in, struct alsa_params_req *req, goto err; } -if (nchannels !=1 && nchannels !=2 ) { -alsa_logerr2 (err, typ, -"Can not handle obtained number of channels %d\n" , - nchannels); - goto err; - } -if (pdo->buffer_count) {if (pdo->buffer_len) {int64_t req = pdo->buffer_len * pdo->buffer_count;
audio/audio.c +1 -1
@@ -210,7 +210,7 @@ static int audio_validate_settings (struct audsettings *as) {int invalid; - invalid = as->nchannels !=1 && as->nchannels !=2 ; + invalid = as->nchannels <1 ; invalid |= as->endianness !=0 && as->endianness !=1 ;switch (as->fmt) {
Signature · 1 line
2.5.0
PATCH 3/5 paaudio: channel-map option
In reply to Kővágó, ZoltánAdd an option to change the channel map used by pulseaudio. If not specified, falls back to an ALSA compatible channel map.
--- audio/paaudio.c | 18 +++++++++++------- qapi/audio.json | 5 ++++- qemu-options.hx | 9 +++++++++ 3 files changed, 24 insertions(+), 8 deletions(-)
audio/paaudio.c +11 -7
@@ -336,23 +336,27 @@ static pa_stream *qpa_simple_new (pa_stream_direction_t dir, const char *dev, const pa_sample_spec *ss, - const pa_channel_map *map, + const char *map, const pa_buffer_attr *attr, int *rerror) { int r;pa_stream *stream;pa_stream_flags_t flags; -pa_channel_map my_map; +pa_channel_map pa_map;pa_threaded_mainloop_lock (c->mainloop); +if (map && !pa_channel_map_parse (&pa_map, map)) { +dolog ("Invalid channel map specified: '%s'\n" , map); + map =NULL ; + }if (!map) { - map =pa_channel_map_init_extend (&my_map, ss->channels, -PA_CHANNEL_MAP_ALSA ); +pa_channel_map_init_extend (&pa_map, ss->channels, +PA_CHANNEL_MAP_ALSA ); } - stream =pa_stream_new (c->context, name, ss, map); + stream =pa_stream_new (c->context, name, ss, &pa_map);if (!stream) { goto fail; }
@@ -428,7 +432,7 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as,PA_STREAM_PLAYBACK , ppdo->has_name ? ppdo->name :NULL , &ss, -NULL ,/* channel map */ + ppdo->has_channel_map ? ppdo->channel_map :NULL , &ba,/* buffering attributes */ &error );
@@ -476,7 +480,7 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)PA_STREAM_RECORD , ppdo->has_name ? ppdo->name :NULL , &ss, -NULL ,/* channel map */ + ppdo->has_channel_map ? ppdo->channel_map :NULL ,NULL ,/* buffering attributes */ &error );
qapi/audio.json +4 -1
@@ -119,11 +119,14 @@ # # @name: #optional name of the sink/source to use # +# @channel-map: #optional channel map to use (default: ALSA compatible map) +# # Since: 2.5 ## { 'struct': 'AudiodevPaPerDirectionOptions', 'data': { - '*name': 'str' } } + '*name': 'str', + '*channel-map': 'str' } } ## # @AudiodevPaOptions
qemu-options.hx +9 -0
@@ -365,6 +365,7 @@ DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev, "-audiodev pa,id=id[,prop[=value][,...]]\n" " server= PulseAudio server address\n" " sink|source.name= sink/source device name\n" + " sink|source.channel-map= channel map to use\n" #endif #ifdef CONFIG_SDL "-audiodev sdl,id=id[,prop[=value][,...]]\n"
@@ -518,6 +519,14 @@ Sets the PulseAudio @var{server} to connect to. @item sink|source.name=@var{sink} Use the specified sink/source for playback/recording. +@item sink|source.channel-map=@var{map} +Use the specified channel map. The default is an ALSA compatible +channel map. Do not forget to escape commas inside the map: + +@example +-audiodev pa,id=example,sink.channel-map=front-left,,front-right +@end example + @end table @item -audiodev sdl,id=@var{id}[,@var{prop}[=@var{value}][,...]]
Signature · 1 line
2.5.0
PATCH 4/5 usb-audio: do not count on avail bytes actually available
In reply to Kővágó, ZoltánThis assumption is no longer true when mixeng is turned off.
--- hw/usb/dev-audio.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-)
hw/usb/dev-audio.c +18 -12
@@ -317,27 +317,28 @@ static int streambuf_put(struct streambuf *buf, USBPacket *p) {uint32_t free = buf->size - (buf->prod - buf->cons); -if (!free) { +if (free <USBAUDIO_PACKET_SIZE ) {return 0 ; } -assert (free >=USBAUDIO_PACKET_SIZE ); +usb_packet_copy (p, buf->data + (buf->prod % buf->size),USBAUDIO_PACKET_SIZE ); buf->prod +=USBAUDIO_PACKET_SIZE ;return USBAUDIO_PACKET_SIZE ; } -static uint8_t *streambuf_get (struct streambuf *buf) +static uint8_t *streambuf_get (struct streambuf *buf,size_t *len) {uint32_t used = buf->prod - buf->cons;uint8_t *data;if (!used) { + *len =0 ;return NULL ; } -assert (used >=USBAUDIO_PACKET_SIZE ); data = buf->data + (buf->cons % buf->size); - buf->cons +=USBAUDIO_PACKET_SIZE ; + *len =audio_MIN (buf->prod - buf->cons, + buf->size - (buf->cons % buf->size));return data; }
@@ -369,16 +370,21 @@ 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); +while (avail) { +size_t written, len; + + data =streambuf_get (&s->out.buf, &len);if (!data) {return ; } -AUD_write (s->out.voice, data,USBAUDIO_PACKET_SIZE ); - avail -=USBAUDIO_PACKET_SIZE ; + + written =AUD_write (s->out.voice, data, len); + avail -= written; + s->out.buf.cons += written; + +if (written < len) { +return ; + } } }
Signature · 1 line
2.5.0
PATCH 5/5 usb-audio: support more than two channels of audio
In reply to Kővágó, ZoltánThis 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)
---
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(-)
hw/usb/dev-audio.c +228 -185
@@ -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;
Signature · 1 line
2.5.0
Hi,
Quoted 4 lines
Currently you have to specify the number of channels you want to use when creating the usb-audio device, see the last commit for details. To do this I currently dynamically allocate the whole USBDesc structure, which looks ugly. If there's a better way, please let me know!
You can have multiple USBDesc stuctures defined and pick one at device initialization time (see hw/usb/dev-hid.c, there are usb 1.1 and usb 2.0 versions of all hid devices).
For usb-audio I think we need two: the current one (stereo) and a new one with stereo + 5.1 and 7.1 support. Then for the new one have 4 alternative interface configurations (off, stereo, 5.1, 7.1) instead of only two (off, stereo). Or possibly we need 3 interfaces (stereo, 5.1, 7.1) with on and off alternatives for each of them. Didn't check usb-audio spec.
cheers,
Gerd