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:
commit
e9d4246192
@ -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;
|
||||
|
176
audio/audio.c
176
audio/audio.c
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
162
audio/paaudio.c
162
audio/paaudio.c
@ -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
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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' } }
|
||||
|
||||
##
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user