audio: bugfixes, pa connection and stream naming.

audio: 5.1/7.1 support for alsa, pa and usb-audio.
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2.0.22 (GNU/Linux)
 
 iQIcBAABAgAGBQJdqWymAAoJEEy22O7T6HE4xkwP/RMd2YrNeTpeBHOII6DF/o0S
 AFY7V8L1sEsvjBpEGwcOWs5Jf9ByRbxkq41w6g5RqzKIHp5kT5dtlRQ1oc4Efg+x
 AK/UGbyNTyp2hmQY6FO6MDMgq8ZgqNaBLvlTr6ZxxIrIoKUWN3jE3wwjnQsuDSv7
 RcD8L891km4W4yxhpAVsT2a/6yOAsqKR0h3lgVWvsF+ugsIa3Ip72FehCL95VbGs
 TbdyWIv75ibkZXIZxTIQjxAEHrv9tkQU8slSNFZKZDamCSrlPyVQBfaHksV+4DQV
 SWana1NB1xu2ZLsBcbDnsWWqsBtK0zWmzJ6Y+Gurfsq2981BAP8yw0xZ90g3bFFB
 vaDcDly+b/4v7UNLKtl2ArzC3vjQS4djuOExgs5s6rcmWfYHw9fYor4MzIvTDnD3
 KkPUrmxOCKD8MtL5XAXadxUS9GK3A5MD1UOIoe8WI/cNQAZhWLM1ofEh//341VAP
 tvkQfCF4mqW/1Rz+p6Vp0Ra3FG5+wVXTL9M8yW6ztD7GPUaw9ANoSXk9xTJa5EY5
 760l5WrNVdFMQxxl+M82aI3/GT3O/bE3Gz5FCjf7JoigIZKG6gKphNFxJySsypxV
 xXoMmE+6P4VoT3g7FfJ5yf9rksCt6Pn5ryx//AQPSuyGF21vF/zNfH98XycXf6ma
 H5JDtM9kEphMQrLxE5LN
 =pQ+6
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kraxel/tags/audio-20191018-pull-request' into staging

audio: bugfixes, pa connection and stream naming.
audio: 5.1/7.1 support for alsa, pa and usb-audio.

# gpg: Signature made Fri 18 Oct 2019 08:41:26 BST
# gpg:                using RSA key 4CB6D8EED3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" [full]
# gpg:                 aka "Gerd Hoffmann <gerd@kraxel.org>" [full]
# gpg:                 aka "Gerd Hoffmann (private) <kraxel@gmail.com>" [full]
# Primary key fingerprint: A032 8CFF B93A 17A7 9901  FE7D 4CB6 D8EE D3E8 7138

* remotes/kraxel/tags/audio-20191018-pull-request:
  paaudio: fix channel order for usb-audio 5.1 and 7.1 streams
  usbaudio: change playback counters to 64 bit
  usb-audio: support more than two channels of audio
  usb-audio: do not count on avail bytes actually available
  audio: basic support for multichannel audio
  audio: replace shift in audio_pcm_info with bytes_per_frame
  audio: support more than two channels in volume setting
  paaudio: get/put_buffer functions
  audio: make mixeng optional
  audio: add mixing-engine option (documentation)
  audio: paaudio: ability to specify stream name
  audio: paaudio: fix connection and stream name
  audio: fix parameter dereference before NULL check

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2019-10-18 14:13:11 +01:00
commit e9d4246192
16 changed files with 758 additions and 191 deletions

View File

@ -493,13 +493,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 (apdo->buffer_length) {
int dir = 0;
unsigned int btime = apdo->buffer_length;
@ -602,7 +595,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);
@ -648,7 +641,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;
}
@ -802,7 +795,8 @@ 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) {
@ -828,8 +822,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;

View File

@ -242,7 +242,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) {
@ -299,12 +299,13 @@ static int audio_pcm_info_eq (struct audio_pcm_info *info, struct audsettings *a
void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as)
{
int bits = 8, sign = 0, shift = 0;
int bits = 8, sign = 0, mul;
switch (as->fmt) {
case AUDIO_FORMAT_S8:
sign = 1;
case AUDIO_FORMAT_U8:
mul = 1;
break;
case AUDIO_FORMAT_S16:
@ -312,7 +313,7 @@ void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as)
/* fall through */
case AUDIO_FORMAT_U16:
bits = 16;
shift = 1;
mul = 2;
break;
case AUDIO_FORMAT_S32:
@ -320,7 +321,7 @@ void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as)
/* fall through */
case AUDIO_FORMAT_U32:
bits = 32;
shift = 2;
mul = 4;
break;
default:
@ -331,9 +332,8 @@ void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as)
info->bits = bits;
info->sign = sign;
info->nchannels = as->nchannels;
info->shift = (as->nchannels == 2) + shift;
info->align = (1 << info->shift) - 1;
info->bytes_per_second = info->freq << info->shift;
info->bytes_per_frame = as->nchannels * mul;
info->bytes_per_second = info->freq * info->bytes_per_frame;
info->swap_endianness = (as->endianness != AUDIO_HOST_ENDIANNESS);
}
@ -344,26 +344,25 @@ void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
}
if (info->sign) {
memset (buf, 0x00, len << info->shift);
memset(buf, 0x00, len * info->bytes_per_frame);
}
else {
switch (info->bits) {
case 8:
memset (buf, 0x80, len << info->shift);
memset(buf, 0x80, len * info->bytes_per_frame);
break;
case 16:
{
int i;
uint16_t *p = buf;
int shift = info->nchannels - 1;
short s = INT16_MAX;
if (info->swap_endianness) {
s = bswap16 (s);
}
for (i = 0; i < len << shift; i++) {
for (i = 0; i < len * info->nchannels; i++) {
p[i] = s;
}
}
@ -373,14 +372,13 @@ void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
{
int i;
uint32_t *p = buf;
int shift = info->nchannels - 1;
int32_t s = INT32_MAX;
if (info->swap_endianness) {
s = bswap32 (s);
}
for (i = 0; i < len << shift; i++) {
for (i = 0; i < len * info->nchannels; i++) {
p[i] = s;
}
}
@ -558,7 +556,7 @@ static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
while (len) {
st_sample *src = hw->mix_buf->samples + pos;
uint8_t *dst = advance(pcm_buf, clipped << hw->info.shift);
uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame);
size_t samples_till_end_of_buf = hw->mix_buf->size - pos;
size_t samples_to_clip = MIN(len, samples_till_end_of_buf);
@ -607,7 +605,7 @@ static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
return 0;
}
samples = size >> sw->info.shift;
samples = size / sw->info.bytes_per_frame;
if (!live) {
return 0;
}
@ -642,7 +640,7 @@ static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
sw->clip (buf, sw->buf, ret);
sw->total_hw_samples_acquired += total;
return ret << sw->info.shift;
return ret * sw->info.bytes_per_frame;
}
/*
@ -715,7 +713,7 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
}
wpos = (sw->hw->mix_buf->pos + live) % hwsamples;
samples = size >> sw->info.shift;
samples = size / sw->info.bytes_per_frame;
dead = hwsamples - live;
swlim = ((int64_t) dead << 32) / sw->ratio;
@ -759,13 +757,13 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
dolog (
"%s: write size %zu ret %zu total sw %zu\n",
SW_NAME (sw),
size >> sw->info.shift,
size / sw->info.bytes_per_frame,
ret,
sw->total_hw_samples_mixed
);
#endif
return ret << sw->info.shift;
return ret * sw->info.bytes_per_frame;
}
#ifdef DEBUG_AUDIO
@ -838,37 +836,51 @@ static void audio_timer (void *opaque)
*/
size_t AUD_write(SWVoiceOut *sw, void *buf, size_t size)
{
HWVoiceOut *hw;
if (!sw) {
/* XXX: Consider options */
return size;
}
hw = sw->hw;
if (!sw->hw->enabled) {
if (!hw->enabled) {
dolog ("Writing to disabled voice %s\n", SW_NAME (sw));
return 0;
}
return audio_pcm_sw_write(sw, buf, size);
if (audio_get_pdo_out(hw->s->dev)->mixing_engine) {
return audio_pcm_sw_write(sw, buf, size);
} else {
return hw->pcm_ops->write(hw, buf, size);
}
}
size_t AUD_read(SWVoiceIn *sw, void *buf, size_t size)
{
HWVoiceIn *hw;
if (!sw) {
/* XXX: Consider options */
return size;
}
hw = sw->hw;
if (!sw->hw->enabled) {
if (!hw->enabled) {
dolog ("Reading from disabled voice %s\n", SW_NAME (sw));
return 0;
}
return audio_pcm_sw_read(sw, buf, size);
if (audio_get_pdo_in(hw->s->dev)->mixing_engine) {
return audio_pcm_sw_read(sw, buf, size);
} else {
return hw->pcm_ops->read(hw, buf, size);
}
}
int AUD_get_buffer_size_out (SWVoiceOut *sw)
{
return sw->hw->mix_buf->size << sw->hw->info.shift;
return sw->hw->mix_buf->size * sw->hw->info.bytes_per_frame;
}
void AUD_set_active_out (SWVoiceOut *sw, int on)
@ -984,10 +996,10 @@ static size_t audio_get_avail (SWVoiceIn *sw)
ldebug (
"%s: get_avail live %d ret %" PRId64 "\n",
SW_NAME (sw),
live, (((int64_t) live << 32) / sw->ratio) << sw->info.shift
live, (((int64_t) live << 32) / sw->ratio) * sw->info.bytes_per_frame
);
return (((int64_t) live << 32) / sw->ratio) << sw->info.shift;
return (((int64_t) live << 32) / sw->ratio) * sw->info.bytes_per_frame;
}
static size_t audio_get_free(SWVoiceOut *sw)
@ -1011,10 +1023,11 @@ static size_t audio_get_free(SWVoiceOut *sw)
#ifdef DEBUG_OUT
dolog ("%s: get_free live %d dead %d ret %" PRId64 "\n",
SW_NAME (sw),
live, dead, (((int64_t) dead << 32) / sw->ratio) << sw->info.shift);
live, dead, (((int64_t) dead << 32) / sw->ratio) *
sw->info.bytes_per_frame);
#endif
return (((int64_t) dead << 32) / sw->ratio) << sw->info.shift;
return (((int64_t) dead << 32) / sw->ratio) * sw->info.bytes_per_frame;
}
static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
@ -1033,7 +1046,7 @@ static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
while (n) {
size_t till_end_of_hw = hw->mix_buf->size - rpos2;
size_t to_write = MIN(till_end_of_hw, n);
size_t bytes = to_write << hw->info.shift;
size_t bytes = to_write * hw->info.bytes_per_frame;
size_t written;
sw->buf = hw->mix_buf->samples + rpos2;
@ -1068,10 +1081,11 @@ static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
return clipped + live;
}
decr = MIN(size >> hw->info.shift, live);
decr = MIN(size / hw->info.bytes_per_frame, live);
audio_pcm_hw_clip_out(hw, buf, decr);
proc = hw->pcm_ops->put_buffer_out(hw, buf, decr << hw->info.shift) >>
hw->info.shift;
proc = hw->pcm_ops->put_buffer_out(hw, buf,
decr * hw->info.bytes_per_frame) /
hw->info.bytes_per_frame;
live -= proc;
clipped += proc;
@ -1090,6 +1104,26 @@ static void audio_run_out (AudioState *s)
HWVoiceOut *hw = NULL;
SWVoiceOut *sw;
if (!audio_get_pdo_out(s->dev)->mixing_engine) {
while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
/* there is exactly 1 sw for each hw with no mixeng */
sw = hw->sw_head.lh_first;
if (hw->pending_disable) {
hw->enabled = 0;
hw->pending_disable = 0;
if (hw->pcm_ops->enable_out) {
hw->pcm_ops->enable_out(hw, false);
}
}
if (sw->active) {
sw->callback.fn(sw->callback.opaque, INT_MAX);
}
}
return;
}
while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
size_t played, live, prev_rpos, free;
int nb_live, cleanup_required;
@ -1200,16 +1234,16 @@ static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
while (samples) {
size_t proc;
size_t size = samples << hw->info.shift;
size_t size = samples * hw->info.bytes_per_frame;
void *buf = hw->pcm_ops->get_buffer_in(hw, &size);
assert((size & hw->info.align) == 0);
assert(size % hw->info.bytes_per_frame == 0);
if (size == 0) {
hw->pcm_ops->put_buffer_in(hw, buf, size);
break;
}
proc = MIN(size >> hw->info.shift,
proc = MIN(size / hw->info.bytes_per_frame,
conv_buf->size - conv_buf->pos);
hw->conv(conv_buf->samples + conv_buf->pos, buf, proc);
@ -1217,7 +1251,7 @@ static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
samples -= proc;
conv += proc;
hw->pcm_ops->put_buffer_in(hw, buf, proc << hw->info.shift);
hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame);
}
return conv;
@ -1227,6 +1261,17 @@ static void audio_run_in (AudioState *s)
{
HWVoiceIn *hw = NULL;
if (!audio_get_pdo_in(s->dev)->mixing_engine) {
while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
/* there is exactly 1 sw for each hw with no mixeng */
SWVoiceIn *sw = hw->sw_head.lh_first;
if (sw->active) {
sw->callback.fn(sw->callback.opaque, INT_MAX);
}
}
return;
}
while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
SWVoiceIn *sw;
size_t captured = 0, min;
@ -1280,7 +1325,7 @@ static void audio_run_capture (AudioState *s)
for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
cb->ops.capture (cb->opaque, cap->buf,
to_capture << hw->info.shift);
to_capture * hw->info.bytes_per_frame);
}
rpos = (rpos + to_capture) % hw->mix_buf->size;
live -= to_capture;
@ -1333,7 +1378,7 @@ void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)
ssize_t start;
if (unlikely(!hw->buf_emul)) {
size_t calc_size = hw->conv_buf->size << hw->info.shift;
size_t calc_size = hw->conv_buf->size * hw->info.bytes_per_frame;
hw->buf_emul = g_malloc(calc_size);
hw->size_emul = calc_size;
hw->pos_emul = hw->pending_emul = 0;
@ -1369,7 +1414,7 @@ void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)
{
if (unlikely(!hw->buf_emul)) {
size_t calc_size = hw->mix_buf->size << hw->info.shift;
size_t calc_size = hw->mix_buf->size * hw->info.bytes_per_frame;
hw->buf_emul = g_malloc(calc_size);
hw->size_emul = calc_size;
@ -1751,6 +1796,11 @@ CaptureVoiceOut *AUD_add_capture(
s = audio_init(NULL, NULL);
}
if (!audio_get_pdo_out(s->dev)->mixing_engine) {
dolog("Can't capture with mixeng disabled\n");
return NULL;
}
if (audio_validate_settings (as)) {
dolog ("Invalid settings were passed when trying to add capture\n");
audio_print_settings (as);
@ -1783,7 +1833,7 @@ CaptureVoiceOut *AUD_add_capture(
audio_pcm_init_info (&hw->info, as);
cap->buf = g_malloc0_n(hw->mix_buf->size, 1 << hw->info.shift);
cap->buf = g_malloc0_n(hw->mix_buf->size, hw->info.bytes_per_frame);
hw->clip = mixeng_clip
[hw->info.nchannels == 2]
@ -1841,31 +1891,45 @@ void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque)
}
void AUD_set_volume_out (SWVoiceOut *sw, int mute, uint8_t lvol, uint8_t rvol)
{
Volume vol = { .mute = mute, .channels = 2, .vol = { lvol, rvol } };
audio_set_volume_out(sw, &vol);
}
void audio_set_volume_out(SWVoiceOut *sw, Volume *vol)
{
if (sw) {
HWVoiceOut *hw = sw->hw;
sw->vol.mute = mute;
sw->vol.l = nominal_volume.l * lvol / 255;
sw->vol.r = nominal_volume.r * rvol / 255;
sw->vol.mute = vol->mute;
sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
sw->vol.r = nominal_volume.l * vol->vol[vol->channels > 1 ? 1 : 0] /
255;
if (hw->pcm_ops->volume_out) {
hw->pcm_ops->volume_out(hw, &sw->vol);
hw->pcm_ops->volume_out(hw, vol);
}
}
}
void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol)
{
Volume vol = { .mute = mute, .channels = 2, .vol = { lvol, rvol } };
audio_set_volume_in(sw, &vol);
}
void audio_set_volume_in(SWVoiceIn *sw, Volume *vol)
{
if (sw) {
HWVoiceIn *hw = sw->hw;
sw->vol.mute = mute;
sw->vol.l = nominal_volume.l * lvol / 255;
sw->vol.r = nominal_volume.r * rvol / 255;
sw->vol.mute = vol->mute;
sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
sw->vol.r = nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : 0] /
255;
if (hw->pcm_ops->volume_in) {
hw->pcm_ops->volume_in(hw, &sw->vol);
hw->pcm_ops->volume_in(hw, vol);
}
}
}
@ -1905,9 +1969,13 @@ void audio_create_pdos(Audiodev *dev)
static void audio_validate_per_direction_opts(
AudiodevPerDirectionOptions *pdo, Error **errp)
{
if (!pdo->has_mixing_engine) {
pdo->has_mixing_engine = true;
pdo->mixing_engine = true;
}
if (!pdo->has_fixed_settings) {
pdo->has_fixed_settings = true;
pdo->fixed_settings = true;
pdo->fixed_settings = pdo->mixing_engine;
}
if (!pdo->fixed_settings &&
(pdo->has_frequency || pdo->has_channels || pdo->has_format)) {
@ -1915,6 +1983,10 @@ static void audio_validate_per_direction_opts(
"You can't use frequency, channels or format with fixed-settings=off");
return;
}
if (!pdo->mixing_engine && pdo->fixed_settings) {
error_setg(errp, "You can't use fixed-settings without mixeng");
return;
}
if (!pdo->has_frequency) {
pdo->has_frequency = true;
@ -1926,7 +1998,7 @@ static void audio_validate_per_direction_opts(
}
if (!pdo->has_voices) {
pdo->has_voices = true;
pdo->voices = 1;
pdo->voices = pdo->mixing_engine ? 1 : INT_MAX;
}
if (!pdo->has_format) {
pdo->has_format = true;
@ -2081,14 +2153,14 @@ size_t audio_rate_get_bytes(struct audio_pcm_info *info, RateCtl *rate,
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
ticks = now - rate->start_ticks;
bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);
samples = (bytes - rate->bytes_sent) >> info->shift;
samples = (bytes - rate->bytes_sent) / info->bytes_per_frame;
if (samples < 0 || samples > 65536) {
AUD_log(NULL, "Resetting rate control (%" PRId64 " samples)\n", samples);
audio_rate_start(rate);
samples = 0;
}
ret = MIN(samples << info->shift, bytes_avail);
ret = MIN(samples * info->bytes_per_frame, bytes_avail);
rate->bytes_sent += ret;
return ret;
}

View File

@ -124,6 +124,16 @@ uint64_t AUD_get_elapsed_usec_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts);
void AUD_set_volume_out (SWVoiceOut *sw, int mute, uint8_t lvol, uint8_t rvol);
void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol);
#define AUDIO_MAX_CHANNELS 16
typedef struct Volume {
bool mute;
int channels;
uint8_t vol[AUDIO_MAX_CHANNELS];
} Volume;
void audio_set_volume_out(SWVoiceOut *sw, Volume *vol);
void audio_set_volume_in(SWVoiceIn *sw, Volume *vol);
SWVoiceIn *AUD_open_in (
QEMUSoundCard *card,
SWVoiceIn *sw,

View File

@ -43,8 +43,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;
};
@ -166,7 +165,7 @@ struct audio_pcm_ops {
*/
size_t (*put_buffer_out)(HWVoiceOut *hw, void *buf, size_t size);
void (*enable_out)(HWVoiceOut *hw, bool enable);
void (*volume_out)(HWVoiceOut *hw, struct mixeng_volume *vol);
void (*volume_out)(HWVoiceOut *hw, Volume *vol);
int (*init_in) (HWVoiceIn *hw, audsettings *as, void *drv_opaque);
void (*fini_in) (HWVoiceIn *hw);
@ -174,7 +173,7 @@ struct audio_pcm_ops {
void *(*get_buffer_in)(HWVoiceIn *hw, size_t *size);
void (*put_buffer_in)(HWVoiceIn *hw, void *buf, size_t size);
void (*enable_in)(HWVoiceIn *hw, bool enable);
void (*volume_in)(HWVoiceIn *hw, struct mixeng_volume *vol);
void (*volume_in)(HWVoiceIn *hw, Volume *vol);
};
void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size);

View File

@ -78,13 +78,17 @@ static void glue (audio_pcm_hw_free_resources_, TYPE) (HW *hw)
static void glue(audio_pcm_hw_alloc_resources_, TYPE)(HW *hw)
{
size_t samples = hw->samples;
if (audio_bug(__func__, samples == 0)) {
dolog("Attempted to allocate empty buffer\n");
}
if (glue(audio_get_pdo_, TYPE)(hw->s->dev)->mixing_engine) {
size_t samples = hw->samples;
if (audio_bug(__func__, samples == 0)) {
dolog("Attempted to allocate empty buffer\n");
}
HWBUF = g_malloc0(sizeof(STSampleBuffer) + sizeof(st_sample) * samples);
HWBUF->size = samples;
HWBUF = g_malloc0(sizeof(STSampleBuffer) + sizeof(st_sample) * samples);
HWBUF->size = samples;
} else {
HWBUF = NULL;
}
}
static void glue (audio_pcm_sw_free_resources_, TYPE) (SW *sw)
@ -103,6 +107,10 @@ static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw)
{
int samples;
if (!glue(audio_get_pdo_, TYPE)(sw->s->dev)->mixing_engine) {
return 0;
}
samples = ((int64_t) sw->HWBUF->size << 32) / sw->ratio;
sw->buf = audio_calloc(__func__, samples, sizeof(struct st_sample));
@ -328,9 +336,9 @@ static HW *glue(audio_pcm_hw_add_, TYPE)(AudioState *s, struct audsettings *as)
HW *hw;
AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev);
if (pdo->fixed_settings) {
if (!pdo->mixing_engine || pdo->fixed_settings) {
hw = glue(audio_pcm_hw_add_new_, TYPE)(s, as);
if (hw) {
if (!pdo->mixing_engine || hw) {
return hw;
}
}
@ -425,8 +433,8 @@ SW *glue (AUD_open_, TYPE) (
struct audsettings *as
)
{
AudioState *s = card->state;
AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev);
AudioState *s;
AudiodevPerDirectionOptions *pdo;
if (audio_bug(__func__, !card || !name || !callback_fn || !as)) {
dolog ("card=%p name=%p callback_fn=%p as=%p\n",
@ -434,6 +442,9 @@ SW *glue (AUD_open_, TYPE) (
goto fail;
}
s = card->state;
pdo = glue(audio_get_pdo_, TYPE)(s->dev);
ldebug ("open %s, freq %d, nchannels %d, fmt %d\n",
name, as->freq, as->nchannels, as->fmt);

View File

@ -440,7 +440,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) {
@ -449,7 +449,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;

View File

@ -98,8 +98,8 @@ static int glue (dsound_lock_, TYPE) (
goto fail;
}
if ((p1p && *p1p && (*blen1p & info->align)) ||
(p2p && *p2p && (*blen2p & info->align))) {
if ((p1p && *p1p && (*blen1p % info->bytes_per_frame)) ||
(p2p && *p2p && (*blen2p % 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;
hw->samples = bc.dwBufferBytes >> hw->info.shift;
hw->samples = bc.dwBufferBytes / hw->info.bytes_per_frame;
ds->s = s;
#ifdef DEBUG_DSOUND

View File

@ -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",

View File

@ -91,7 +91,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;
}

View File

@ -506,16 +506,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);
}
hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift;
hw->samples = (obt.nfrags * obt.fragsize) / hw->info.bytes_per_frame;
oss->mmapped = 0;
if (oopts->has_try_mmap && oopts->try_mmap) {
hw->size_emul = hw->samples << hw->info.shift;
hw->size_emul = hw->samples * hw->info.bytes_per_frame;
hw->buf_emul = mmap(
NULL,
hw->size_emul,
@ -644,12 +644,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);
}
hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift;
hw->samples = (obt.nfrags * obt.fragsize) / hw->info.bytes_per_frame;
oss->fd = fd;
oss->dev = dev;

View File

@ -2,6 +2,7 @@
#include "qemu/osdep.h"
#include "qemu/module.h"
#include "qemu-common.h"
#include "audio.h"
#include "qapi/opts-visitor.h"
@ -98,6 +99,59 @@ static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x)
} \
} while (0)
static void *qpa_get_buffer_in(HWVoiceIn *hw, size_t *size)
{
PAVoiceIn *p = (PAVoiceIn *) hw;
PAConnection *c = p->g->conn;
int r;
pa_threaded_mainloop_lock(c->mainloop);
CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
"pa_threaded_mainloop_lock failed\n");
if (!p->read_length) {
r = pa_stream_peek(p->stream, &p->read_data, &p->read_length);
CHECK_SUCCESS_GOTO(c, r == 0, unlock_and_fail,
"pa_stream_peek failed\n");
}
*size = MIN(p->read_length, *size);
pa_threaded_mainloop_unlock(c->mainloop);
return (void *) p->read_data;
unlock_and_fail:
pa_threaded_mainloop_unlock(c->mainloop);
*size = 0;
return NULL;
}
static void qpa_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
{
PAVoiceIn *p = (PAVoiceIn *) hw;
PAConnection *c = p->g->conn;
int r;
pa_threaded_mainloop_lock(c->mainloop);
CHECK_DEAD_GOTO(c, p->stream, unlock,
"pa_threaded_mainloop_lock failed\n");
assert(buf == p->read_data && size <= p->read_length);
p->read_data += size;
p->read_length -= size;
if (size && !p->read_length) {
r = pa_stream_drop(p->stream);
CHECK_SUCCESS_GOTO(c, r == 0, unlock, "pa_stream_drop failed\n");
}
unlock:
pa_threaded_mainloop_unlock(c->mainloop);
}
static size_t qpa_read(HWVoiceIn *hw, void *data, size_t length)
{
PAVoiceIn *p = (PAVoiceIn *) hw;
@ -136,6 +190,32 @@ unlock_and_fail:
return 0;
}
static void *qpa_get_buffer_out(HWVoiceOut *hw, size_t *size)
{
PAVoiceOut *p = (PAVoiceOut *) hw;
PAConnection *c = p->g->conn;
void *ret;
int r;
pa_threaded_mainloop_lock(c->mainloop);
CHECK_DEAD_GOTO(c, p->stream, unlock_and_fail,
"pa_threaded_mainloop_lock failed\n");
*size = -1;
r = pa_stream_begin_write(p->stream, &ret, size);
CHECK_SUCCESS_GOTO(c, r >= 0, unlock_and_fail,
"pa_stream_begin_write failed\n");
pa_threaded_mainloop_unlock(c->mainloop);
return ret;
unlock_and_fail:
pa_threaded_mainloop_unlock(c->mainloop);
*size = 0;
return NULL;
}
static size_t qpa_write(HWVoiceOut *hw, void *data, size_t length)
{
PAVoiceOut *p = (PAVoiceOut *) hw;
@ -259,17 +339,59 @@ 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 pa_buffer_attr *attr,
int *rerror)
{
int r;
pa_stream *stream;
pa_stream *stream = NULL;
pa_stream_flags_t flags;
pa_channel_map map;
pa_threaded_mainloop_lock(c->mainloop);
stream = pa_stream_new(c->context, name, ss, map);
pa_channel_map_init(&map);
map.channels = ss->channels;
/*
* TODO: This currently expects the only frontend supporting more than 2
* channels is the usb-audio. We will need some means to set channel
* order when a new frontend gains multi-channel support.
*/
switch (ss->channels) {
case 1:
map.map[0] = PA_CHANNEL_POSITION_MONO;
break;
case 2:
map.map[0] = PA_CHANNEL_POSITION_LEFT;
map.map[1] = PA_CHANNEL_POSITION_RIGHT;
break;
case 6:
map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
map.map[2] = PA_CHANNEL_POSITION_CENTER;
map.map[3] = PA_CHANNEL_POSITION_LFE;
map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
break;
case 8:
map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
map.map[2] = PA_CHANNEL_POSITION_CENTER;
map.map[3] = PA_CHANNEL_POSITION_LFE;
map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;
map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;
default:
dolog("Internal error: unsupported channel count %d\n", ss->channels);
goto fail;
}
stream = pa_stream_new(c->context, name, ss, &map);
if (!stream) {
goto fail;
}
@ -338,11 +460,10 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as,
pa->stream = qpa_simple_new (
c,
"qemu",
ppdo->has_stream_name ? ppdo->stream_name : g->dev->id,
PA_STREAM_PLAYBACK,
ppdo->has_name ? ppdo->name : NULL,
&ss,
NULL, /* channel map */
&ba, /* buffering attributes */
&error
);
@ -387,11 +508,10 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
pa->stream = qpa_simple_new (
c,
"qemu",
ppdo->has_stream_name ? ppdo->stream_name : g->dev->id,
PA_STREAM_RECORD,
ppdo->has_name ? ppdo->name : NULL,
&ss,
NULL, /* channel map */
&ba, /* buffering attributes */
&error
);
@ -452,20 +572,22 @@ static void qpa_fini_in (HWVoiceIn *hw)
}
}
static void qpa_volume_out(HWVoiceOut *hw, struct mixeng_volume *vol)
static void qpa_volume_out(HWVoiceOut *hw, Volume *vol)
{
PAVoiceOut *pa = (PAVoiceOut *) hw;
pa_operation *op;
pa_cvolume v;
PAConnection *c = pa->g->conn;
int i;
#ifdef PA_CHECK_VERSION /* macro is present in 0.9.16+ */
pa_cvolume_init (&v); /* function is present in 0.9.13+ */
#endif
v.channels = 2;
v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->l) / UINT32_MAX;
v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->r) / UINT32_MAX;
v.channels = vol->channels;
for (i = 0; i < vol->channels; ++i) {
v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255;
}
pa_threaded_mainloop_lock(c->mainloop);
@ -492,20 +614,22 @@ static void qpa_volume_out(HWVoiceOut *hw, struct mixeng_volume *vol)
pa_threaded_mainloop_unlock(c->mainloop);
}
static void qpa_volume_in(HWVoiceIn *hw, struct mixeng_volume *vol)
static void qpa_volume_in(HWVoiceIn *hw, Volume *vol)
{
PAVoiceIn *pa = (PAVoiceIn *) hw;
pa_operation *op;
pa_cvolume v;
PAConnection *c = pa->g->conn;
int i;
#ifdef PA_CHECK_VERSION
pa_cvolume_init (&v);
#endif
v.channels = 2;
v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->l) / UINT32_MAX;
v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->r) / UINT32_MAX;
v.channels = vol->channels;
for (i = 0; i < vol->channels; ++i) {
v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255;
}
pa_threaded_mainloop_lock(c->mainloop);
@ -549,6 +673,7 @@ static int qpa_validate_per_direction_opts(Audiodev *dev,
/* common */
static void *qpa_conn_init(const char *server)
{
const char *vm_name;
PAConnection *c = g_malloc0(sizeof(PAConnection));
QTAILQ_INSERT_TAIL(&pa_conns, c, list);
@ -557,8 +682,9 @@ static void *qpa_conn_init(const char *server)
goto fail;
}
vm_name = qemu_get_vm_name();
c->context = pa_context_new(pa_threaded_mainloop_get_api(c->mainloop),
server);
vm_name ? vm_name : "qemu");
if (!c->context) {
goto fail;
}
@ -698,11 +824,15 @@ static struct audio_pcm_ops qpa_pcm_ops = {
.init_out = qpa_init_out,
.fini_out = qpa_fini_out,
.write = qpa_write,
.get_buffer_out = qpa_get_buffer_out,
.put_buffer_out = qpa_write, /* pa handles it */
.volume_out = qpa_volume_out,
.init_in = qpa_init_in,
.fini_in = qpa_fini_in,
.read = qpa_read,
.get_buffer_in = qpa_get_buffer_in,
.put_buffer_in = qpa_put_buffer_in,
.volume_in = qpa_volume_in
};

View File

@ -131,7 +131,8 @@ static void *line_out_get_buffer(HWVoiceOut *hw, size_t *size)
if (out->frame) {
*size = audio_rate_get_bytes(
&hw->info, &out->rate, (out->fsize - out->fpos) << hw->info.shift);
&hw->info, &out->rate,
(out->fsize - out->fpos) * hw->info.bytes_per_frame);
} else {
audio_rate_start(&out->rate);
}
@ -179,13 +180,14 @@ static void line_out_enable(HWVoiceOut *hw, bool enable)
}
#if ((SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && (SPICE_INTERFACE_PLAYBACK_MINOR >= 2))
static void line_out_volume(HWVoiceOut *hw, struct mixeng_volume *vol)
static void line_out_volume(HWVoiceOut *hw, Volume *vol)
{
SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
uint16_t svol[2];
svol[0] = vol->l / ((1ULL << 16) + 1);
svol[1] = vol->r / ((1ULL << 16) + 1);
assert(vol->channels == 2);
svol[0] = vol->vol[0] * 257;
svol[1] = vol->vol[1] * 257;
spice_server_playback_set_volume(&out->sin, 2, svol);
spice_server_playback_set_mute(&out->sin, vol->mute);
}
@ -262,13 +264,14 @@ static void line_in_enable(HWVoiceIn *hw, bool enable)
}
#if ((SPICE_INTERFACE_RECORD_MAJOR >= 2) && (SPICE_INTERFACE_RECORD_MINOR >= 2))
static void line_in_volume(HWVoiceIn *hw, struct mixeng_volume *vol)
static void line_in_volume(HWVoiceIn *hw, Volume *vol)
{
SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw);
uint16_t svol[2];
svol[0] = vol->l / ((1ULL << 16) + 1);
svol[1] = vol->r / ((1ULL << 16) + 1);
assert(vol->channels == 2);
svol[0] = vol->vol[0] * 257;
svol[1] = vol->vol[1] * 257;
spice_server_record_set_volume(&in->sin, 2, svol);
spice_server_record_set_mute(&in->sin, vol->mute);
}

View File

@ -43,14 +43,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 %" PRId64 " bytes failed\nReason: %s\n",
bytes, strerror(errno));
}
wav->total_samples += bytes >> hw->info.shift;
wav->total_samples += bytes / hw->info.bytes_per_frame;
return bytes;
}
@ -134,7 +134,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) {

View File

@ -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,
};
/*
@ -295,15 +578,16 @@ enum usb_audio_altset {
struct streambuf {
uint8_t *data;
uint32_t size;
uint32_t prod;
uint32_t cons;
size_t size;
uint64_t prod;
uint64_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,34 +599,37 @@ 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);
int64_t free = buf->size - (buf->prod - buf->cons);
if (!free) {
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;
}
assert(free >= USBAUDIO_PACKET_SIZE);
/* can happen if prod overflows */
assert(buf->prod % USBAUDIO_PACKET_SIZE(channels) == 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)
static uint8_t *streambuf_get(struct streambuf *buf, size_t *len)
{
uint32_t used = buf->prod - buf->cons;
int64_t used = buf->prod - buf->cons;
uint8_t *data;
if (!used) {
if (used <= 0) {
*len = 0;
return NULL;
}
assert(used >= USBAUDIO_PACKET_SIZE);
data = buf->data + (buf->cons % buf->size);
buf->cons += USBAUDIO_PACKET_SIZE;
*len = MIN(buf->prod - buf->cons,
buf->size - (buf->cons % buf->size));
return data;
}
@ -356,14 +643,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"
@ -374,16 +662,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;
}
}
}
@ -391,10 +684,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:
@ -425,33 +723,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;
@ -473,16 +771,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;
@ -491,7 +790,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;
}
@ -500,11 +799,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;
@ -597,7 +899,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);
@ -639,6 +941,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);
@ -646,18 +951,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);
}
@ -669,8 +991,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(),
};
@ -683,7 +1005,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;

View File

@ -11,6 +11,11 @@
# General audio backend options that are used for both playback and
# recording.
#
# @mixing-engine: use QEMU's mixing engine to mix all streams inside QEMU and
# convert audio formats when not supported by the backend. When
# set to off, fixed-settings must be also off (default on,
# since 4.2)
#
# @fixed-settings: use fixed settings for host input/output. When off,
# frequency, channels and format must not be
# specified (default true)
@ -31,6 +36,7 @@
##
{ 'struct': 'AudiodevPerDirectionOptions',
'data': {
'*mixing-engine': 'bool',
'*fixed-settings': 'bool',
'*frequency': 'uint32',
'*channels': 'uint32',
@ -206,6 +212,11 @@
#
# @name: name of the sink/source to use
#
# @stream-name: name of the PulseAudio stream created by qemu. Can be
# used to identify the stream in PulseAudio when you
# create multiple PulseAudio devices or run multiple qemu
# instances (default: audiodev's id, since 4.2)
#
# @latency: latency you want PulseAudio to achieve in microseconds
# (default 15000)
#
@ -215,6 +226,7 @@
'base': 'AudiodevPerDirectionOptions',
'data': {
'*name': 'str',
'*stream-name': 'str',
'*latency': 'uint32' } }
##

View File

@ -433,6 +433,7 @@ DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev,
" specifies the audio backend to use\n"
" id= identifier of the backend\n"
" timer-period= timer period in microseconds\n"
" in|out.mixing-engine= use mixing engine to mix streams inside QEMU\n"
" in|out.fixed-settings= use fixed settings for host audio\n"
" in|out.frequency= frequency to use with fixed settings\n"
" in|out.channels= number of channels to use with fixed settings\n"
@ -493,6 +494,10 @@ output's property with @code{out.@var{prop}}. For example:
-audiodev alsa,id=example,out.channels=1 # leaves in.channels unspecified
@end example
NOTE: parameter validation is known to be incomplete, in many cases
specifying an invalid option causes QEMU to print an error message and
continue emulation without sound.
Valid global options are:
@table @option
@ -503,6 +508,16 @@ Identifies the audio backend.
Sets the timer @var{period} used by the audio subsystem in microseconds.
Default is 10000 (10 ms).
@item in|out.mixing-engine=on|off
Use QEMU's mixing engine to mix all streams inside QEMU and convert
audio formats when not supported by the backend. When off,
@var{fixed-settings} must be off too. Note that disabling this option
means that the selected backend must support multiple streams and the
audio formats used by the virtual cards, otherwise you'll get no sound.
It's not recommended to disable this option unless you want to use 5.1
or 7.1 audio, as mixing engine only supports mono and stereo audio.
Default is on.
@item in|out.fixed-settings=on|off
Use fixed settings for host audio. When off, it will change based on
how the guest opens the sound card. In this case you must not specify