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; 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) { if (apdo->buffer_length) {
int dir = 0; int dir = 0;
unsigned int btime = apdo->buffer_length; 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; ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
size_t pos = 0; 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) { while (len_frames) {
char *src = advance(buf, pos); 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) { if (written < len_frames) {
break; break;
} }
@ -802,7 +795,8 @@ static size_t alsa_read(HWVoiceIn *hw, void *buf, size_t len)
void *dst = advance(buf, pos); void *dst = advance(buf, pos);
snd_pcm_sframes_t nread; 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) { if (nread <= 0) {
switch (nread) { switch (nread) {
@ -828,8 +822,8 @@ static size_t alsa_read(HWVoiceIn *hw, void *buf, size_t len)
} }
} }
pos += nread << hw->info.shift; pos += nread * hw->info.bytes_per_frame;
len -= nread << hw->info.shift; len -= nread * hw->info.bytes_per_frame;
} }
return pos; return pos;

View File

@ -242,7 +242,7 @@ static int audio_validate_settings (struct audsettings *as)
{ {
int invalid; int invalid;
invalid = as->nchannels != 1 && as->nchannels != 2; invalid = as->nchannels < 1;
invalid |= as->endianness != 0 && as->endianness != 1; invalid |= as->endianness != 0 && as->endianness != 1;
switch (as->fmt) { 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) 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) { switch (as->fmt) {
case AUDIO_FORMAT_S8: case AUDIO_FORMAT_S8:
sign = 1; sign = 1;
case AUDIO_FORMAT_U8: case AUDIO_FORMAT_U8:
mul = 1;
break; break;
case AUDIO_FORMAT_S16: case AUDIO_FORMAT_S16:
@ -312,7 +313,7 @@ void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as)
/* fall through */ /* fall through */
case AUDIO_FORMAT_U16: case AUDIO_FORMAT_U16:
bits = 16; bits = 16;
shift = 1; mul = 2;
break; break;
case AUDIO_FORMAT_S32: case AUDIO_FORMAT_S32:
@ -320,7 +321,7 @@ void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as)
/* fall through */ /* fall through */
case AUDIO_FORMAT_U32: case AUDIO_FORMAT_U32:
bits = 32; bits = 32;
shift = 2; mul = 4;
break; break;
default: default:
@ -331,9 +332,8 @@ void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as)
info->bits = bits; info->bits = bits;
info->sign = sign; info->sign = sign;
info->nchannels = as->nchannels; info->nchannels = as->nchannels;
info->shift = (as->nchannels == 2) + shift; info->bytes_per_frame = as->nchannels * mul;
info->align = (1 << info->shift) - 1; info->bytes_per_second = info->freq * info->bytes_per_frame;
info->bytes_per_second = info->freq << info->shift;
info->swap_endianness = (as->endianness != AUDIO_HOST_ENDIANNESS); 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) { if (info->sign) {
memset (buf, 0x00, len << info->shift); memset(buf, 0x00, len * info->bytes_per_frame);
} }
else { else {
switch (info->bits) { switch (info->bits) {
case 8: case 8:
memset (buf, 0x80, len << info->shift); memset(buf, 0x80, len * info->bytes_per_frame);
break; break;
case 16: case 16:
{ {
int i; int i;
uint16_t *p = buf; uint16_t *p = buf;
int shift = info->nchannels - 1;
short s = INT16_MAX; short s = INT16_MAX;
if (info->swap_endianness) { if (info->swap_endianness) {
s = bswap16 (s); s = bswap16 (s);
} }
for (i = 0; i < len << shift; i++) { for (i = 0; i < len * info->nchannels; i++) {
p[i] = s; p[i] = s;
} }
} }
@ -373,14 +372,13 @@ void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
{ {
int i; int i;
uint32_t *p = buf; uint32_t *p = buf;
int shift = info->nchannels - 1;
int32_t s = INT32_MAX; int32_t s = INT32_MAX;
if (info->swap_endianness) { if (info->swap_endianness) {
s = bswap32 (s); s = bswap32 (s);
} }
for (i = 0; i < len << shift; i++) { for (i = 0; i < len * info->nchannels; i++) {
p[i] = s; p[i] = s;
} }
} }
@ -558,7 +556,7 @@ static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
while (len) { while (len) {
st_sample *src = hw->mix_buf->samples + pos; 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_till_end_of_buf = hw->mix_buf->size - pos;
size_t samples_to_clip = MIN(len, samples_till_end_of_buf); 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; return 0;
} }
samples = size >> sw->info.shift; samples = size / sw->info.bytes_per_frame;
if (!live) { if (!live) {
return 0; 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->clip (buf, sw->buf, ret);
sw->total_hw_samples_acquired += total; 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; wpos = (sw->hw->mix_buf->pos + live) % hwsamples;
samples = size >> sw->info.shift; samples = size / sw->info.bytes_per_frame;
dead = hwsamples - live; dead = hwsamples - live;
swlim = ((int64_t) dead << 32) / sw->ratio; 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 ( dolog (
"%s: write size %zu ret %zu total sw %zu\n", "%s: write size %zu ret %zu total sw %zu\n",
SW_NAME (sw), SW_NAME (sw),
size >> sw->info.shift, size / sw->info.bytes_per_frame,
ret, ret,
sw->total_hw_samples_mixed sw->total_hw_samples_mixed
); );
#endif #endif
return ret << sw->info.shift; return ret * sw->info.bytes_per_frame;
} }
#ifdef DEBUG_AUDIO #ifdef DEBUG_AUDIO
@ -838,37 +836,51 @@ static void audio_timer (void *opaque)
*/ */
size_t AUD_write(SWVoiceOut *sw, void *buf, size_t size) size_t AUD_write(SWVoiceOut *sw, void *buf, size_t size)
{ {
HWVoiceOut *hw;
if (!sw) { if (!sw) {
/* XXX: Consider options */ /* XXX: Consider options */
return size; return size;
} }
hw = sw->hw;
if (!sw->hw->enabled) { if (!hw->enabled) {
dolog ("Writing to disabled voice %s\n", SW_NAME (sw)); dolog ("Writing to disabled voice %s\n", SW_NAME (sw));
return 0; 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) size_t AUD_read(SWVoiceIn *sw, void *buf, size_t size)
{ {
HWVoiceIn *hw;
if (!sw) { if (!sw) {
/* XXX: Consider options */ /* XXX: Consider options */
return size; return size;
} }
hw = sw->hw;
if (!sw->hw->enabled) { if (!hw->enabled) {
dolog ("Reading from disabled voice %s\n", SW_NAME (sw)); dolog ("Reading from disabled voice %s\n", SW_NAME (sw));
return 0; 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) 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) void AUD_set_active_out (SWVoiceOut *sw, int on)
@ -984,10 +996,10 @@ static size_t audio_get_avail (SWVoiceIn *sw)
ldebug ( ldebug (
"%s: get_avail live %d ret %" PRId64 "\n", "%s: get_avail live %d ret %" PRId64 "\n",
SW_NAME (sw), 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) static size_t audio_get_free(SWVoiceOut *sw)
@ -1011,10 +1023,11 @@ static size_t audio_get_free(SWVoiceOut *sw)
#ifdef DEBUG_OUT #ifdef DEBUG_OUT
dolog ("%s: get_free live %d dead %d ret %" PRId64 "\n", dolog ("%s: get_free live %d dead %d ret %" PRId64 "\n",
SW_NAME (sw), 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 #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, 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) { while (n) {
size_t till_end_of_hw = hw->mix_buf->size - rpos2; size_t till_end_of_hw = hw->mix_buf->size - rpos2;
size_t to_write = MIN(till_end_of_hw, n); 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; size_t written;
sw->buf = hw->mix_buf->samples + rpos2; 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; 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); audio_pcm_hw_clip_out(hw, buf, decr);
proc = hw->pcm_ops->put_buffer_out(hw, buf, decr << hw->info.shift) >> proc = hw->pcm_ops->put_buffer_out(hw, buf,
hw->info.shift; decr * hw->info.bytes_per_frame) /
hw->info.bytes_per_frame;
live -= proc; live -= proc;
clipped += proc; clipped += proc;
@ -1090,6 +1104,26 @@ static void audio_run_out (AudioState *s)
HWVoiceOut *hw = NULL; HWVoiceOut *hw = NULL;
SWVoiceOut *sw; 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))) { while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
size_t played, live, prev_rpos, free; size_t played, live, prev_rpos, free;
int nb_live, cleanup_required; int nb_live, cleanup_required;
@ -1200,16 +1234,16 @@ static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
while (samples) { while (samples) {
size_t proc; 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); 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) { if (size == 0) {
hw->pcm_ops->put_buffer_in(hw, buf, size); hw->pcm_ops->put_buffer_in(hw, buf, size);
break; break;
} }
proc = MIN(size >> hw->info.shift, proc = MIN(size / hw->info.bytes_per_frame,
conv_buf->size - conv_buf->pos); conv_buf->size - conv_buf->pos);
hw->conv(conv_buf->samples + conv_buf->pos, buf, proc); 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; samples -= proc;
conv += 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; return conv;
@ -1227,6 +1261,17 @@ static void audio_run_in (AudioState *s)
{ {
HWVoiceIn *hw = NULL; 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))) { while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {
SWVoiceIn *sw; SWVoiceIn *sw;
size_t captured = 0, min; 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) { for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
cb->ops.capture (cb->opaque, cap->buf, 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; rpos = (rpos + to_capture) % hw->mix_buf->size;
live -= to_capture; live -= to_capture;
@ -1333,7 +1378,7 @@ void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)
ssize_t start; ssize_t start;
if (unlikely(!hw->buf_emul)) { 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->buf_emul = g_malloc(calc_size);
hw->size_emul = calc_size; hw->size_emul = calc_size;
hw->pos_emul = hw->pending_emul = 0; 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) void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)
{ {
if (unlikely(!hw->buf_emul)) { 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->buf_emul = g_malloc(calc_size);
hw->size_emul = calc_size; hw->size_emul = calc_size;
@ -1751,6 +1796,11 @@ CaptureVoiceOut *AUD_add_capture(
s = audio_init(NULL, NULL); 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)) { if (audio_validate_settings (as)) {
dolog ("Invalid settings were passed when trying to add capture\n"); dolog ("Invalid settings were passed when trying to add capture\n");
audio_print_settings (as); audio_print_settings (as);
@ -1783,7 +1833,7 @@ CaptureVoiceOut *AUD_add_capture(
audio_pcm_init_info (&hw->info, as); 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->clip = mixeng_clip
[hw->info.nchannels == 2] [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) 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) { if (sw) {
HWVoiceOut *hw = sw->hw; HWVoiceOut *hw = sw->hw;
sw->vol.mute = mute; sw->vol.mute = vol->mute;
sw->vol.l = nominal_volume.l * lvol / 255; sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
sw->vol.r = nominal_volume.r * rvol / 255; sw->vol.r = nominal_volume.l * vol->vol[vol->channels > 1 ? 1 : 0] /
255;
if (hw->pcm_ops->volume_out) { 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) 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) { if (sw) {
HWVoiceIn *hw = sw->hw; HWVoiceIn *hw = sw->hw;
sw->vol.mute = mute; sw->vol.mute = vol->mute;
sw->vol.l = nominal_volume.l * lvol / 255; sw->vol.l = nominal_volume.l * vol->vol[0] / 255;
sw->vol.r = nominal_volume.r * rvol / 255; sw->vol.r = nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : 0] /
255;
if (hw->pcm_ops->volume_in) { 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( static void audio_validate_per_direction_opts(
AudiodevPerDirectionOptions *pdo, Error **errp) AudiodevPerDirectionOptions *pdo, Error **errp)
{ {
if (!pdo->has_mixing_engine) {
pdo->has_mixing_engine = true;
pdo->mixing_engine = true;
}
if (!pdo->has_fixed_settings) { if (!pdo->has_fixed_settings) {
pdo->has_fixed_settings = true; pdo->has_fixed_settings = true;
pdo->fixed_settings = true; pdo->fixed_settings = pdo->mixing_engine;
} }
if (!pdo->fixed_settings && if (!pdo->fixed_settings &&
(pdo->has_frequency || pdo->has_channels || pdo->has_format)) { (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"); "You can't use frequency, channels or format with fixed-settings=off");
return; return;
} }
if (!pdo->mixing_engine && pdo->fixed_settings) {
error_setg(errp, "You can't use fixed-settings without mixeng");
return;
}
if (!pdo->has_frequency) { if (!pdo->has_frequency) {
pdo->has_frequency = true; pdo->has_frequency = true;
@ -1926,7 +1998,7 @@ static void audio_validate_per_direction_opts(
} }
if (!pdo->has_voices) { if (!pdo->has_voices) {
pdo->has_voices = true; pdo->has_voices = true;
pdo->voices = 1; pdo->voices = pdo->mixing_engine ? 1 : INT_MAX;
} }
if (!pdo->has_format) { if (!pdo->has_format) {
pdo->has_format = true; 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); now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
ticks = now - rate->start_ticks; ticks = now - rate->start_ticks;
bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND); 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) { if (samples < 0 || samples > 65536) {
AUD_log(NULL, "Resetting rate control (%" PRId64 " samples)\n", samples); AUD_log(NULL, "Resetting rate control (%" PRId64 " samples)\n", samples);
audio_rate_start(rate); audio_rate_start(rate);
samples = 0; samples = 0;
} }
ret = MIN(samples << info->shift, bytes_avail); ret = MIN(samples * info->bytes_per_frame, bytes_avail);
rate->bytes_sent += ret; rate->bytes_sent += ret;
return 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_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); 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 ( SWVoiceIn *AUD_open_in (
QEMUSoundCard *card, QEMUSoundCard *card,
SWVoiceIn *sw, SWVoiceIn *sw,

View File

@ -43,8 +43,7 @@ struct audio_pcm_info {
int sign; int sign;
int freq; int freq;
int nchannels; int nchannels;
int align; int bytes_per_frame;
int shift;
int bytes_per_second; int bytes_per_second;
int swap_endianness; int swap_endianness;
}; };
@ -166,7 +165,7 @@ struct audio_pcm_ops {
*/ */
size_t (*put_buffer_out)(HWVoiceOut *hw, void *buf, size_t size); size_t (*put_buffer_out)(HWVoiceOut *hw, void *buf, size_t size);
void (*enable_out)(HWVoiceOut *hw, bool enable); 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); int (*init_in) (HWVoiceIn *hw, audsettings *as, void *drv_opaque);
void (*fini_in) (HWVoiceIn *hw); void (*fini_in) (HWVoiceIn *hw);
@ -174,7 +173,7 @@ struct audio_pcm_ops {
void *(*get_buffer_in)(HWVoiceIn *hw, size_t *size); void *(*get_buffer_in)(HWVoiceIn *hw, size_t *size);
void (*put_buffer_in)(HWVoiceIn *hw, void *buf, size_t size); void (*put_buffer_in)(HWVoiceIn *hw, void *buf, size_t size);
void (*enable_in)(HWVoiceIn *hw, bool enable); 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); 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) static void glue(audio_pcm_hw_alloc_resources_, TYPE)(HW *hw)
{ {
size_t samples = hw->samples; if (glue(audio_get_pdo_, TYPE)(hw->s->dev)->mixing_engine) {
if (audio_bug(__func__, samples == 0)) { size_t samples = hw->samples;
dolog("Attempted to allocate empty buffer\n"); if (audio_bug(__func__, samples == 0)) {
} dolog("Attempted to allocate empty buffer\n");
}
HWBUF = g_malloc0(sizeof(STSampleBuffer) + sizeof(st_sample) * samples); HWBUF = g_malloc0(sizeof(STSampleBuffer) + sizeof(st_sample) * samples);
HWBUF->size = samples; HWBUF->size = samples;
} else {
HWBUF = NULL;
}
} }
static void glue (audio_pcm_sw_free_resources_, TYPE) (SW *sw) 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; int samples;
if (!glue(audio_get_pdo_, TYPE)(sw->s->dev)->mixing_engine) {
return 0;
}
samples = ((int64_t) sw->HWBUF->size << 32) / sw->ratio; samples = ((int64_t) sw->HWBUF->size << 32) / sw->ratio;
sw->buf = audio_calloc(__func__, samples, sizeof(struct st_sample)); 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; HW *hw;
AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev); 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); hw = glue(audio_pcm_hw_add_new_, TYPE)(s, as);
if (hw) { if (!pdo->mixing_engine || hw) {
return hw; return hw;
} }
} }
@ -425,8 +433,8 @@ SW *glue (AUD_open_, TYPE) (
struct audsettings *as struct audsettings *as
) )
{ {
AudioState *s = card->state; AudioState *s;
AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev); AudiodevPerDirectionOptions *pdo;
if (audio_bug(__func__, !card || !name || !callback_fn || !as)) { if (audio_bug(__func__, !card || !name || !callback_fn || !as)) {
dolog ("card=%p name=%p callback_fn=%p as=%p\n", dolog ("card=%p name=%p callback_fn=%p as=%p\n",
@ -434,6 +442,9 @@ SW *glue (AUD_open_, TYPE) (
goto fail; goto fail;
} }
s = card->state;
pdo = glue(audio_get_pdo_, TYPE)(s->dev);
ldebug ("open %s, freq %d, nchannels %d, fmt %d\n", ldebug ("open %s, freq %d, nchannels %d, fmt %d\n",
name, as->freq, as->nchannels, as->fmt); name, as->freq, as->nchannels, as->fmt);

View File

@ -440,7 +440,7 @@ static OSStatus audioDeviceIOProc(
} }
frameCount = core->audioDevicePropertyBufferFrameSize; 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 there are not enough samples, set signal and return */
if (pending_frames < frameCount) { if (pending_frames < frameCount) {
@ -449,7 +449,7 @@ static OSStatus audioDeviceIOProc(
return 0; return 0;
} }
len = frameCount << hw->info.shift; len = frameCount * hw->info.bytes_per_frame;
while (len) { while (len) {
size_t write_len; size_t write_len;
ssize_t start = ((ssize_t) hw->pos_emul) - hw->pending_emul; 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; goto fail;
} }
if ((p1p && *p1p && (*blen1p & info->align)) || if ((p1p && *p1p && (*blen1p % info->bytes_per_frame)) ||
(p2p && *p2p && (*blen2p & info->align))) { (p2p && *p2p && (*blen2p % info->bytes_per_frame))) {
dolog("DirectSound returned misaligned buffer %ld %ld\n", dolog("DirectSound returned misaligned buffer %ld %ld\n",
*blen1p, *blen2p); *blen1p, *blen2p);
glue(dsound_unlock_, TYPE)(buf, *p1p, p2p ? *p2p : NULL, *blen1p, 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; obt_as.endianness = 0;
audio_pcm_init_info (&hw->info, &obt_as); audio_pcm_init_info (&hw->info, &obt_as);
if (bc.dwBufferBytes & hw->info.align) { if (bc.dwBufferBytes % hw->info.bytes_per_frame) {
dolog ( dolog (
"GetCaps returned misaligned buffer size %ld, alignment %d\n", "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->size_emul = bc.dwBufferBytes;
hw->samples = bc.dwBufferBytes >> hw->info.shift; hw->samples = bc.dwBufferBytes / hw->info.bytes_per_frame;
ds->s = s; ds->s = s;
#ifdef DEBUG_DSOUND #ifdef DEBUG_DSOUND

View File

@ -320,8 +320,8 @@ static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb,
return; return;
} }
len1 = blen1 >> hw->info.shift; len1 = blen1 / hw->info.bytes_per_frame;
len2 = blen2 >> hw->info.shift; len2 = blen2 / hw->info.bytes_per_frame;
#ifdef DEBUG_DSOUND #ifdef DEBUG_DSOUND
dolog ("clear %p,%ld,%ld %p,%ld,%ld\n", 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; NoVoiceIn *no = (NoVoiceIn *) hw;
int64_t bytes = audio_rate_get_bytes(&hw->info, &no->rate, size); 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; return bytes;
} }

View File

@ -506,16 +506,16 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as,
oss->nfrags = obt.nfrags; oss->nfrags = obt.nfrags;
oss->fragsize = obt.fragsize; 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", 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; oss->mmapped = 0;
if (oopts->has_try_mmap && oopts->try_mmap) { 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( hw->buf_emul = mmap(
NULL, NULL,
hw->size_emul, 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->nfrags = obt.nfrags;
oss->fragsize = obt.fragsize; 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", 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->fd = fd;
oss->dev = dev; oss->dev = dev;

View File

@ -2,6 +2,7 @@
#include "qemu/osdep.h" #include "qemu/osdep.h"
#include "qemu/module.h" #include "qemu/module.h"
#include "qemu-common.h"
#include "audio.h" #include "audio.h"
#include "qapi/opts-visitor.h" #include "qapi/opts-visitor.h"
@ -98,6 +99,59 @@ static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x)
} \ } \
} while (0) } 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) static size_t qpa_read(HWVoiceIn *hw, void *data, size_t length)
{ {
PAVoiceIn *p = (PAVoiceIn *) hw; PAVoiceIn *p = (PAVoiceIn *) hw;
@ -136,6 +190,32 @@ unlock_and_fail:
return 0; 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) static size_t qpa_write(HWVoiceOut *hw, void *data, size_t length)
{ {
PAVoiceOut *p = (PAVoiceOut *) hw; PAVoiceOut *p = (PAVoiceOut *) hw;
@ -259,17 +339,59 @@ static pa_stream *qpa_simple_new (
pa_stream_direction_t dir, pa_stream_direction_t dir,
const char *dev, const char *dev,
const pa_sample_spec *ss, const pa_sample_spec *ss,
const pa_channel_map *map,
const pa_buffer_attr *attr, const pa_buffer_attr *attr,
int *rerror) int *rerror)
{ {
int r; int r;
pa_stream *stream; pa_stream *stream = NULL;
pa_stream_flags_t flags; pa_stream_flags_t flags;
pa_channel_map map;
pa_threaded_mainloop_lock(c->mainloop); 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) { if (!stream) {
goto fail; goto fail;
} }
@ -338,11 +460,10 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as,
pa->stream = qpa_simple_new ( pa->stream = qpa_simple_new (
c, c,
"qemu", ppdo->has_stream_name ? ppdo->stream_name : g->dev->id,
PA_STREAM_PLAYBACK, PA_STREAM_PLAYBACK,
ppdo->has_name ? ppdo->name : NULL, ppdo->has_name ? ppdo->name : NULL,
&ss, &ss,
NULL, /* channel map */
&ba, /* buffering attributes */ &ba, /* buffering attributes */
&error &error
); );
@ -387,11 +508,10 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
pa->stream = qpa_simple_new ( pa->stream = qpa_simple_new (
c, c,
"qemu", ppdo->has_stream_name ? ppdo->stream_name : g->dev->id,
PA_STREAM_RECORD, PA_STREAM_RECORD,
ppdo->has_name ? ppdo->name : NULL, ppdo->has_name ? ppdo->name : NULL,
&ss, &ss,
NULL, /* channel map */
&ba, /* buffering attributes */ &ba, /* buffering attributes */
&error &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; PAVoiceOut *pa = (PAVoiceOut *) hw;
pa_operation *op; pa_operation *op;
pa_cvolume v; pa_cvolume v;
PAConnection *c = pa->g->conn; PAConnection *c = pa->g->conn;
int i;
#ifdef PA_CHECK_VERSION /* macro is present in 0.9.16+ */ #ifdef PA_CHECK_VERSION /* macro is present in 0.9.16+ */
pa_cvolume_init (&v); /* function is present in 0.9.13+ */ pa_cvolume_init (&v); /* function is present in 0.9.13+ */
#endif #endif
v.channels = 2; v.channels = vol->channels;
v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->l) / UINT32_MAX; for (i = 0; i < vol->channels; ++i) {
v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->r) / UINT32_MAX; v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255;
}
pa_threaded_mainloop_lock(c->mainloop); 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); 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; PAVoiceIn *pa = (PAVoiceIn *) hw;
pa_operation *op; pa_operation *op;
pa_cvolume v; pa_cvolume v;
PAConnection *c = pa->g->conn; PAConnection *c = pa->g->conn;
int i;
#ifdef PA_CHECK_VERSION #ifdef PA_CHECK_VERSION
pa_cvolume_init (&v); pa_cvolume_init (&v);
#endif #endif
v.channels = 2; v.channels = vol->channels;
v.values[0] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->l) / UINT32_MAX; for (i = 0; i < vol->channels; ++i) {
v.values[1] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->r) / UINT32_MAX; v.values[i] = ((PA_VOLUME_NORM - PA_VOLUME_MUTED) * vol->vol[i]) / 255;
}
pa_threaded_mainloop_lock(c->mainloop); pa_threaded_mainloop_lock(c->mainloop);
@ -549,6 +673,7 @@ static int qpa_validate_per_direction_opts(Audiodev *dev,
/* common */ /* common */
static void *qpa_conn_init(const char *server) static void *qpa_conn_init(const char *server)
{ {
const char *vm_name;
PAConnection *c = g_malloc0(sizeof(PAConnection)); PAConnection *c = g_malloc0(sizeof(PAConnection));
QTAILQ_INSERT_TAIL(&pa_conns, c, list); QTAILQ_INSERT_TAIL(&pa_conns, c, list);
@ -557,8 +682,9 @@ static void *qpa_conn_init(const char *server)
goto fail; goto fail;
} }
vm_name = qemu_get_vm_name();
c->context = pa_context_new(pa_threaded_mainloop_get_api(c->mainloop), c->context = pa_context_new(pa_threaded_mainloop_get_api(c->mainloop),
server); vm_name ? vm_name : "qemu");
if (!c->context) { if (!c->context) {
goto fail; goto fail;
} }
@ -698,11 +824,15 @@ static struct audio_pcm_ops qpa_pcm_ops = {
.init_out = qpa_init_out, .init_out = qpa_init_out,
.fini_out = qpa_fini_out, .fini_out = qpa_fini_out,
.write = qpa_write, .write = qpa_write,
.get_buffer_out = qpa_get_buffer_out,
.put_buffer_out = qpa_write, /* pa handles it */
.volume_out = qpa_volume_out, .volume_out = qpa_volume_out,
.init_in = qpa_init_in, .init_in = qpa_init_in,
.fini_in = qpa_fini_in, .fini_in = qpa_fini_in,
.read = qpa_read, .read = qpa_read,
.get_buffer_in = qpa_get_buffer_in,
.put_buffer_in = qpa_put_buffer_in,
.volume_in = qpa_volume_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) { if (out->frame) {
*size = audio_rate_get_bytes( *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 { } else {
audio_rate_start(&out->rate); 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)) #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); SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
uint16_t svol[2]; uint16_t svol[2];
svol[0] = vol->l / ((1ULL << 16) + 1); assert(vol->channels == 2);
svol[1] = vol->r / ((1ULL << 16) + 1); 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_volume(&out->sin, 2, svol);
spice_server_playback_set_mute(&out->sin, vol->mute); 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)) #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); SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw);
uint16_t svol[2]; uint16_t svol[2];
svol[0] = vol->l / ((1ULL << 16) + 1); assert(vol->channels == 2);
svol[1] = vol->r / ((1ULL << 16) + 1); 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_volume(&in->sin, 2, svol);
spice_server_record_set_mute(&in->sin, vol->mute); 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; WAVVoiceOut *wav = (WAVVoiceOut *) hw;
int64_t bytes = audio_rate_get_bytes(&hw->info, &wav->rate, len); 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) { if (bytes && fwrite(buf, bytes, 1, wav->f) != 1) {
dolog("wav_write_out: fwrite of %" PRId64 " bytes failed\nReason: %s\n", dolog("wav_write_out: fwrite of %" PRId64 " bytes failed\nReason: %s\n",
bytes, strerror(errno)); bytes, strerror(errno));
} }
wav->total_samples += bytes >> hw->info.shift; wav->total_samples += bytes / hw->info.bytes_per_frame;
return bytes; return bytes;
} }
@ -134,7 +134,7 @@ static void wav_fini_out (HWVoiceOut *hw)
WAVVoiceOut *wav = (WAVVoiceOut *) hw; WAVVoiceOut *wav = (WAVVoiceOut *) hw;
uint8_t rlen[4]; uint8_t rlen[4];
uint8_t dlen[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; uint32_t rifflen = datalen + 36;
if (!wav->f) { if (!wav->f) {

View File

@ -37,11 +37,15 @@
#include "desc.h" #include "desc.h"
#include "audio/audio.h" #include "audio/audio.h"
static void usb_audio_reinit(USBDevice *dev, unsigned channels);
#define USBAUDIO_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */ #define USBAUDIO_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */
#define USBAUDIO_PRODUCT_NUM 0x0002 #define USBAUDIO_PRODUCT_NUM 0x0002
#define DEV_CONFIG_VALUE 1 /* The one and only */ #define DEV_CONFIG_VALUE 1 /* The one and only */
#define USBAUDIO_MAX_CHANNELS(s) (s->multi ? 8 : 2)
/* Descriptor subtypes for AC interfaces */ /* Descriptor subtypes for AC interfaces */
#define DST_AC_HEADER 1 #define DST_AC_HEADER 1
#define DST_AC_INPUT_TERMINAL 2 #define DST_AC_INPUT_TERMINAL 2
@ -80,6 +84,27 @@ static const USBDescStrings usb_audio_stringtable = {
[STRING_REAL_STREAM] = "Audio Output - 48 kHz Stereo", [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 U16(x) ((x) & 0xff), (((x) >> 8) & 0xff)
#define U24(x) U16(x), (((x) >> 16) & 0xff) #define U24(x) U16(x), (((x) >> 16) & 0xff)
#define U32(x) U24(x), (((x) >> 24) & 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 * 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_SAMPLE_RATE 48000
#define USBAUDIO_PACKET_INTERVAL 1 #define USBAUDIO_PACKET_INTERVAL 1
@ -121,7 +147,7 @@ static const USBDescIface desc_iface[] = {
0x01, /* u8 bTerminalID */ 0x01, /* u8 bTerminalID */
U16(0x0101), /* u16 wTerminalType */ U16(0x0101), /* u16 wTerminalType */
0x00, /* u8 bAssocTerminal */ 0x00, /* u8 bAssocTerminal */
0x02, /* u16 bNrChannels */ 0x02, /* u8 bNrChannels */
U16(0x0003), /* u16 wChannelConfig */ U16(0x0003), /* u16 wChannelConfig */
0x00, /* u8 iChannelNames */ 0x00, /* u8 iChannelNames */
STRING_INPUT_TERMINAL, /* u8 iTerminal */ STRING_INPUT_TERMINAL, /* u8 iTerminal */
@ -156,14 +182,14 @@ static const USBDescIface desc_iface[] = {
}, },
},{ },{
.bInterfaceNumber = 1, .bInterfaceNumber = 1,
.bAlternateSetting = 0, .bAlternateSetting = ALTSET_OFF,
.bNumEndpoints = 0, .bNumEndpoints = 0,
.bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceClass = USB_CLASS_AUDIO,
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING,
.iInterface = STRING_NULL_STREAM, .iInterface = STRING_NULL_STREAM,
},{ },{
.bInterfaceNumber = 1, .bInterfaceNumber = 1,
.bAlternateSetting = 1, .bAlternateSetting = ALTSET_STEREO,
.bNumEndpoints = 1, .bNumEndpoints = 1,
.bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceClass = USB_CLASS_AUDIO,
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING,
@ -199,7 +225,7 @@ static const USBDescIface desc_iface[] = {
{ {
.bEndpointAddress = USB_DIR_OUT | 0x01, .bEndpointAddress = USB_DIR_OUT | 0x01,
.bmAttributes = 0x0d, .bmAttributes = 0x0d,
.wMaxPacketSize = USBAUDIO_PACKET_SIZE, .wMaxPacketSize = USBAUDIO_PACKET_SIZE(2),
.bInterval = 1, .bInterval = 1,
.is_audio = 1, .is_audio = 1,
/* Stereo Headphone Class-specific /* Stereo Headphone Class-specific
@ -247,17 +273,274 @@ static const USBDesc desc_audio = {
.str = usb_audio_stringtable, .str = usb_audio_stringtable,
}; };
/* /* multi channel compatible desc */
* A USB audio device supports an arbitrary number of alternate
* interface settings for each interface. Each corresponds to a block static const USBDescIface desc_iface_multi[] = {
* diagram of parameterized blocks. This can thus refer to things like {
* number of channels, data rates, or in fact completely different .bInterfaceNumber = 0,
* block diagrams. Alternative setting 0 is always the null block diagram, .bNumEndpoints = 0,
* which is used by a disabled device. .bInterfaceClass = USB_CLASS_AUDIO,
*/ .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL,
enum usb_audio_altset { .bInterfaceProtocol = 0x04,
ALTSET_OFF = 0x00, /* No endpoint */ .iInterface = STRING_USBAUDIO_CONTROL,
ALTSET_ON = 0x01, /* Single endpoint */ .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 { struct streambuf {
uint8_t *data; uint8_t *data;
uint32_t size; size_t size;
uint32_t prod; uint64_t prod;
uint32_t cons; 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); 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->data = g_malloc(buf->size);
buf->prod = 0; buf->prod = 0;
buf->cons = 0; buf->cons = 0;
@ -315,34 +599,37 @@ static void streambuf_fini(struct streambuf *buf)
buf->data = NULL; 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; return 0;
} }
if (p->iov.size != USBAUDIO_PACKET_SIZE) { if (p->iov.size != USBAUDIO_PACKET_SIZE(channels)) {
return 0; 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), usb_packet_copy(p, buf->data + (buf->prod % buf->size),
USBAUDIO_PACKET_SIZE); USBAUDIO_PACKET_SIZE(channels));
buf->prod += USBAUDIO_PACKET_SIZE; buf->prod += USBAUDIO_PACKET_SIZE(channels);
return USBAUDIO_PACKET_SIZE; 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; uint8_t *data;
if (!used) { if (used <= 0) {
*len = 0;
return NULL; return NULL;
} }
assert(used >= USBAUDIO_PACKET_SIZE);
data = buf->data + (buf->cons % buf->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; return data;
} }
@ -356,14 +643,15 @@ typedef struct USBAudioState {
enum usb_audio_altset altset; enum usb_audio_altset altset;
struct audsettings as; struct audsettings as;
SWVoiceOut *voice; SWVoiceOut *voice;
bool mute; Volume vol;
uint8_t vol[2];
struct streambuf buf; struct streambuf buf;
uint32_t channels;
} out; } out;
/* properties */ /* properties */
uint32_t debug; uint32_t debug;
uint32_t buffer; uint32_t buffer_user, buffer;
bool multi;
} USBAudioState; } USBAudioState;
#define TYPE_USB_AUDIO "usb-audio" #define TYPE_USB_AUDIO "usb-audio"
@ -374,16 +662,21 @@ static void output_callback(void *opaque, int avail)
USBAudioState *s = opaque; USBAudioState *s = opaque;
uint8_t *data; uint8_t *data;
for (;;) { while (avail) {
if (avail < USBAUDIO_PACKET_SIZE) { size_t written, len;
return;
} data = streambuf_get(&s->out.buf, &len);
data = streambuf_get(&s->out.buf);
if (!data) { if (!data) {
return; 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) { switch (altset) {
case ALTSET_OFF: case ALTSET_OFF:
streambuf_init(&s->out.buf, s->buffer);
AUD_set_active_out(s->out.voice, false); AUD_set_active_out(s->out.voice, false);
break; 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); AUD_set_active_out(s->out.voice, true);
break; break;
default: default:
@ -425,33 +723,33 @@ static int usb_audio_get_control(USBAudioState *s, uint8_t attrib,
switch (aid) { switch (aid) {
case ATTRIB_ID(MUTE_CONTROL, CR_GET_CUR, 0x0200): case ATTRIB_ID(MUTE_CONTROL, CR_GET_CUR, 0x0200):
data[0] = s->out.mute; data[0] = s->out.vol.mute;
ret = 1; ret = 1;
break; break;
case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200): case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200):
if (cn < 2) { if (cn < USBAUDIO_MAX_CHANNELS(s)) {
uint16_t vol = (s->out.vol[cn] * 0x8800 + 127) / 255 + 0x8000; uint16_t vol = (s->out.vol.vol[cn] * 0x8800 + 127) / 255 + 0x8000;
data[0] = vol; data[0] = vol;
data[1] = vol >> 8; data[1] = vol >> 8;
ret = 2; ret = 2;
} }
break; break;
case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MIN, 0x0200): case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MIN, 0x0200):
if (cn < 2) { if (cn < USBAUDIO_MAX_CHANNELS(s)) {
data[0] = 0x01; data[0] = 0x01;
data[1] = 0x80; data[1] = 0x80;
ret = 2; ret = 2;
} }
break; break;
case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200): case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200):
if (cn < 2) { if (cn < USBAUDIO_MAX_CHANNELS(s)) {
data[0] = 0x00; data[0] = 0x00;
data[1] = 0x08; data[1] = 0x08;
ret = 2; ret = 2;
} }
break; break;
case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200): case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200):
if (cn < 2) { if (cn < USBAUDIO_MAX_CHANNELS(s)) {
data[0] = 0x88; data[0] = 0x88;
data[1] = 0x00; data[1] = 0x00;
ret = 2; ret = 2;
@ -473,16 +771,17 @@ static int usb_audio_set_control(USBAudioState *s, uint8_t attrib,
switch (aid) { switch (aid) {
case ATTRIB_ID(MUTE_CONTROL, CR_SET_CUR, 0x0200): 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; set_vol = true;
ret = 0; ret = 0;
break; break;
case ATTRIB_ID(VOLUME_CONTROL, CR_SET_CUR, 0x0200): 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); uint16_t vol = data[0] + (data[1] << 8);
if (s->debug) { 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; vol -= 0x8000;
@ -491,7 +790,7 @@ static int usb_audio_set_control(USBAudioState *s, uint8_t attrib,
vol = 255; vol = 255;
} }
s->out.vol[cn] = vol; s->out.vol.vol[cn] = vol;
set_vol = true; set_vol = true;
ret = 0; ret = 0;
} }
@ -500,11 +799,14 @@ static int usb_audio_set_control(USBAudioState *s, uint8_t attrib,
if (set_vol) { if (set_vol) {
if (s->debug) { if (s->debug) {
fprintf(stderr, "usb-audio: mute %d, lvol %3d, rvol %3d\n", int i;
s->out.mute, s->out.vol[0], s->out.vol[1]); 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, audio_set_volume_out(s->out.voice, &s->out.vol);
s->out.vol[0], s->out.vol[1]);
} }
return ret; return ret;
@ -597,7 +899,7 @@ static void usb_audio_handle_dataout(USBAudioState *s, USBPacket *p)
return; 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) { if (p->actual_length < p->iov.size && s->debug > 1) {
fprintf(stderr, "usb-audio: output overrun (%zd bytes)\n", fprintf(stderr, "usb-audio: output overrun (%zd bytes)\n",
p->iov.size - p->actual_length); 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) static void usb_audio_realize(USBDevice *dev, Error **errp)
{ {
USBAudioState *s = USB_AUDIO(dev); USBAudioState *s = USB_AUDIO(dev);
int i;
dev->usb_desc = s->multi ? &desc_audio_multi : &desc_audio;
usb_desc_create_serial(dev); usb_desc_create_serial(dev);
usb_desc_init(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); AUD_register_card(TYPE_USB_AUDIO, &s->card);
s->out.altset = ALTSET_OFF; s->out.altset = ALTSET_OFF;
s->out.mute = false; s->out.vol.mute = false;
s->out.vol[0] = 240; /* 0 dB */ for (i = 0; i < USBAUDIO_MAX_CHANNELS(s); ++i) {
s->out.vol[1] = 240; /* 0 dB */ 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.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.fmt = AUDIO_FORMAT_S16;
s->out.as.endianness = 0; 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->out.voice = AUD_open_out(&s->card, s->out.voice, TYPE_USB_AUDIO,
s, output_callback, &s->out.as); 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); AUD_set_active_out(s->out.voice, 0);
} }
@ -669,8 +991,8 @@ static const VMStateDescription vmstate_usb_audio = {
static Property usb_audio_properties[] = { static Property usb_audio_properties[] = {
DEFINE_AUDIO_PROPERTIES(USBAudioState, card), DEFINE_AUDIO_PROPERTIES(USBAudioState, card),
DEFINE_PROP_UINT32("debug", USBAudioState, debug, 0), DEFINE_PROP_UINT32("debug", USBAudioState, debug, 0),
DEFINE_PROP_UINT32("buffer", USBAudioState, buffer, DEFINE_PROP_UINT32("buffer", USBAudioState, buffer_user, 0),
32 * USBAUDIO_PACKET_SIZE), DEFINE_PROP_BOOL("multi", USBAudioState, multi, false),
DEFINE_PROP_END_OF_LIST(), DEFINE_PROP_END_OF_LIST(),
}; };
@ -683,7 +1005,6 @@ static void usb_audio_class_init(ObjectClass *klass, void *data)
dc->props = usb_audio_properties; dc->props = usb_audio_properties;
set_bit(DEVICE_CATEGORY_SOUND, dc->categories); set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
k->product_desc = "QEMU USB Audio Interface"; k->product_desc = "QEMU USB Audio Interface";
k->usb_desc = &desc_audio;
k->realize = usb_audio_realize; k->realize = usb_audio_realize;
k->handle_reset = usb_audio_handle_reset; k->handle_reset = usb_audio_handle_reset;
k->handle_control = usb_audio_handle_control; k->handle_control = usb_audio_handle_control;

View File

@ -11,6 +11,11 @@
# General audio backend options that are used for both playback and # General audio backend options that are used for both playback and
# recording. # 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, # @fixed-settings: use fixed settings for host input/output. When off,
# frequency, channels and format must not be # frequency, channels and format must not be
# specified (default true) # specified (default true)
@ -31,6 +36,7 @@
## ##
{ 'struct': 'AudiodevPerDirectionOptions', { 'struct': 'AudiodevPerDirectionOptions',
'data': { 'data': {
'*mixing-engine': 'bool',
'*fixed-settings': 'bool', '*fixed-settings': 'bool',
'*frequency': 'uint32', '*frequency': 'uint32',
'*channels': 'uint32', '*channels': 'uint32',
@ -206,6 +212,11 @@
# #
# @name: name of the sink/source to use # @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 # @latency: latency you want PulseAudio to achieve in microseconds
# (default 15000) # (default 15000)
# #
@ -215,6 +226,7 @@
'base': 'AudiodevPerDirectionOptions', 'base': 'AudiodevPerDirectionOptions',
'data': { 'data': {
'*name': 'str', '*name': 'str',
'*stream-name': 'str',
'*latency': 'uint32' } } '*latency': 'uint32' } }
## ##

View File

@ -433,6 +433,7 @@ DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev,
" specifies the audio backend to use\n" " specifies the audio backend to use\n"
" id= identifier of the backend\n" " id= identifier of the backend\n"
" timer-period= timer period in microseconds\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.fixed-settings= use fixed settings for host audio\n"
" in|out.frequency= frequency to use with fixed settings\n" " in|out.frequency= frequency to use with fixed settings\n"
" in|out.channels= number of channels 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 -audiodev alsa,id=example,out.channels=1 # leaves in.channels unspecified
@end example @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: Valid global options are:
@table @option @table @option
@ -503,6 +508,16 @@ Identifies the audio backend.
Sets the timer @var{period} used by the audio subsystem in microseconds. Sets the timer @var{period} used by the audio subsystem in microseconds.
Default is 10000 (10 ms). 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 @item in|out.fixed-settings=on|off
Use fixed settings for host audio. When off, it will change based on 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 how the guest opens the sound card. In this case you must not specify