提交 e9d42461 编写于 作者: P Peter Maydell

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: NPeter Maydell <peter.maydell@linaro.org>
......@@ -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;
......
......@@ -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;
......
......@@ -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) {
......
此差异已折叠。
......@@ -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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册