From: Marc-André Lureau <marcandre.lureau@redhat.com>
Allow to build the audio/ base classes without the
resampling/mixing/queuing code.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
audio/audio_int.h | 2 -
include/qemu/audio.h | 2 +
audio/audio-mixeng-be.c | 1979 ++++++++++++++++++++++++++++++++++
audio/audio.c | 2226 ++++-----------------------------------
audio/meson.build | 1 +
5 files changed, 2159 insertions(+), 2051 deletions(-)
create mode 100644 audio/audio-mixeng-be.c
diff --git a/audio/audio_int.h b/audio/audio_int.h
index 250fd45238d..6ecd75c4fbf 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -266,8 +266,6 @@ int audio_bug (const char *funcname, int cond);
void audio_run(AudioMixengBackend *s, const char *msg);
-const char *audio_application_name(void);
-
typedef struct RateCtl {
int64_t start_ticks;
int64_t bytes_sent;
diff --git a/include/qemu/audio.h b/include/qemu/audio.h
index 42f97f732a6..dfe247ab8c4 100644
--- a/include/qemu/audio.h
+++ b/include/qemu/audio.h
@@ -183,6 +183,8 @@ bool audio_be_set_dbus_server(AudioBackend *be,
Error **errp);
#endif
+const char *audio_application_name(void);
+
#define DEFINE_AUDIO_PROPERTIES(_s, _f) \
DEFINE_PROP_AUDIODEV("audiodev", _s, _f)
diff --git a/audio/audio-mixeng-be.c b/audio/audio-mixeng-be.c
new file mode 100644
index 00000000000..8ebceb968ce
--- /dev/null
+++ b/audio/audio-mixeng-be.c
@@ -0,0 +1,1979 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * Copyright (c) 2003-2005 Vassili Karpov (malc)
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/audio.h"
+#include "migration/vmstate.h"
+#include "qemu/timer.h"
+#include "qapi/error.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qapi/qapi-visit-audio.h"
+#include "qapi/qapi-commands-audio.h"
+#include "qobject/qdict.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/help_option.h"
+#include "qom/object.h"
+#include "system/system.h"
+#include "system/replay.h"
+#include "system/runstate.h"
+#include "trace.h"
+
+#define AUDIO_CAP "audio"
+#include "audio_int.h"
+
+/* #define DEBUG_OUT */
+/* #define DEBUG_CAPTURE */
+/* #define DEBUG_POLL */
+
+#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown"
+
+const struct mixeng_volume nominal_volume = {
+ .mute = 0,
+#ifdef FLOAT_MIXENG
+ .r = 1.0,
+ .l = 1.0,
+#else
+ .r = 1ULL << 32,
+ .l = 1ULL << 32,
+#endif
+};
+
+int audio_bug (const char *funcname, int cond)
+{
+ if (cond) {
+ static int shown;
+
+ AUD_log (NULL, "A bug was just triggered in %s\n", funcname);
+ if (!shown) {
+ shown = 1;
+ AUD_log (NULL, "Save all your work and restart without audio\n");
+ AUD_log (NULL, "I am sorry\n");
+ }
+ AUD_log (NULL, "Context:\n");
+ }
+
+ return cond;
+}
+
+static inline int audio_bits_to_index (int bits)
+{
+ switch (bits) {
+ case 8:
+ return 0;
+
+ case 16:
+ return 1;
+
+ case 32:
+ return 2;
+
+ default:
+ audio_bug ("bits_to_index", 1);
+ AUD_log (NULL, "invalid bits %d\n", bits);
+ return 0;
+ }
+}
+
+void AUD_vlog (const char *cap, const char *fmt, va_list ap)
+{
+ if (cap) {
+ fprintf(stderr, "%s: ", cap);
+ }
+
+ vfprintf(stderr, fmt, ap);
+}
+
+void AUD_log (const char *cap, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start (ap, fmt);
+ AUD_vlog (cap, fmt, ap);
+ va_end (ap);
+}
+
+static void audio_print_settings (const struct audsettings *as)
+{
+ dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels);
+
+ switch (as->fmt) {
+ case AUDIO_FORMAT_S8:
+ AUD_log (NULL, "S8");
+ break;
+ case AUDIO_FORMAT_U8:
+ AUD_log (NULL, "U8");
+ break;
+ case AUDIO_FORMAT_S16:
+ AUD_log (NULL, "S16");
+ break;
+ case AUDIO_FORMAT_U16:
+ AUD_log (NULL, "U16");
+ break;
+ case AUDIO_FORMAT_S32:
+ AUD_log (NULL, "S32");
+ break;
+ case AUDIO_FORMAT_U32:
+ AUD_log (NULL, "U32");
+ break;
+ case AUDIO_FORMAT_F32:
+ AUD_log (NULL, "F32");
+ break;
+ default:
+ AUD_log (NULL, "invalid(%d)", as->fmt);
+ break;
+ }
+
+ AUD_log (NULL, " endianness=");
+ switch (as->endianness) {
+ case 0:
+ AUD_log (NULL, "little");
+ break;
+ case 1:
+ AUD_log (NULL, "big");
+ break;
+ default:
+ AUD_log (NULL, "invalid");
+ break;
+ }
+ AUD_log (NULL, "\n");
+}
+
+static int audio_validate_settings (const struct audsettings *as)
+{
+ int invalid;
+
+ invalid = as->nchannels < 1;
+ invalid |= as->endianness != 0 && as->endianness != 1;
+
+ switch (as->fmt) {
+ case AUDIO_FORMAT_S8:
+ case AUDIO_FORMAT_U8:
+ case AUDIO_FORMAT_S16:
+ case AUDIO_FORMAT_U16:
+ case AUDIO_FORMAT_S32:
+ case AUDIO_FORMAT_U32:
+ case AUDIO_FORMAT_F32:
+ break;
+ default:
+ invalid = 1;
+ break;
+ }
+
+ invalid |= as->freq <= 0;
+ return invalid ? -1 : 0;
+}
+
+static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct audsettings *as)
+{
+ int bits = 8;
+ bool is_signed = false, is_float = false;
+
+ switch (as->fmt) {
+ case AUDIO_FORMAT_S8:
+ is_signed = true;
+ /* fall through */
+ case AUDIO_FORMAT_U8:
+ break;
+
+ case AUDIO_FORMAT_S16:
+ is_signed = true;
+ /* fall through */
+ case AUDIO_FORMAT_U16:
+ bits = 16;
+ break;
+
+ case AUDIO_FORMAT_F32:
+ is_float = true;
+ /* fall through */
+ case AUDIO_FORMAT_S32:
+ is_signed = true;
+ /* fall through */
+ case AUDIO_FORMAT_U32:
+ bits = 32;
+ break;
+
+ default:
+ abort();
+ }
+ return info->freq == as->freq
+ && info->nchannels == as->nchannels
+ && info->is_signed == is_signed
+ && info->is_float == is_float
+ && info->bits == bits
+ && info->swap_endianness == (as->endianness != HOST_BIG_ENDIAN);
+}
+
+void audio_pcm_init_info (struct audio_pcm_info *info, const struct audsettings *as)
+{
+ int bits = 8, mul;
+ bool is_signed = false, is_float = false;
+
+ switch (as->fmt) {
+ case AUDIO_FORMAT_S8:
+ is_signed = true;
+ /* fall through */
+ case AUDIO_FORMAT_U8:
+ mul = 1;
+ break;
+
+ case AUDIO_FORMAT_S16:
+ is_signed = true;
+ /* fall through */
+ case AUDIO_FORMAT_U16:
+ bits = 16;
+ mul = 2;
+ break;
+
+ case AUDIO_FORMAT_F32:
+ is_float = true;
+ /* fall through */
+ case AUDIO_FORMAT_S32:
+ is_signed = true;
+ /* fall through */
+ case AUDIO_FORMAT_U32:
+ bits = 32;
+ mul = 4;
+ break;
+
+ default:
+ abort();
+ }
+
+ info->freq = as->freq;
+ info->bits = bits;
+ info->is_signed = is_signed;
+ info->is_float = is_float;
+ info->nchannels = as->nchannels;
+ info->bytes_per_frame = as->nchannels * mul;
+ info->bytes_per_second = info->freq * info->bytes_per_frame;
+ info->swap_endianness = (as->endianness != HOST_BIG_ENDIAN);
+}
+
+void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
+{
+ if (!len) {
+ return;
+ }
+
+ if (info->is_signed || info->is_float) {
+ memset(buf, 0x00, len * info->bytes_per_frame);
+ } else {
+ switch (info->bits) {
+ case 8:
+ memset(buf, 0x80, len * info->bytes_per_frame);
+ break;
+
+ case 16:
+ {
+ int i;
+ uint16_t *p = buf;
+ short s = INT16_MAX;
+
+ if (info->swap_endianness) {
+ s = bswap16 (s);
+ }
+
+ for (i = 0; i < len * info->nchannels; i++) {
+ p[i] = s;
+ }
+ }
+ break;
+
+ case 32:
+ {
+ int i;
+ uint32_t *p = buf;
+ int32_t s = INT32_MAX;
+
+ if (info->swap_endianness) {
+ s = bswap32 (s);
+ }
+
+ for (i = 0; i < len * info->nchannels; i++) {
+ p[i] = s;
+ }
+ }
+ break;
+
+ default:
+ AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n",
+ info->bits);
+ break;
+ }
+ }
+}
+
+/*
+ * Capture
+ */
+static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioMixengBackend *s,
+ struct audsettings *as)
+{
+ CaptureVoiceOut *cap;
+
+ for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
+ if (audio_pcm_info_eq (&cap->hw.info, as)) {
+ return cap;
+ }
+ }
+ return NULL;
+}
+
+static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd)
+{
+ struct capture_callback *cb;
+
+#ifdef DEBUG_CAPTURE
+ dolog ("notification %d sent\n", cmd);
+#endif
+ for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
+ cb->ops.notify (cb->opaque, cmd);
+ }
+}
+
+static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled)
+{
+ if (cap->hw.enabled != enabled) {
+ audcnotification_e cmd;
+ cap->hw.enabled = enabled;
+ cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE;
+ audio_notify_capture (cap, cmd);
+ }
+}
+
+static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap)
+{
+ HWVoiceOut *hw = &cap->hw;
+ SWVoiceOut *sw;
+ bool enabled = false;
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (sw->active) {
+ enabled = true;
+ break;
+ }
+ }
+ audio_capture_maybe_changed (cap, enabled);
+}
+
+static void audio_detach_capture (HWVoiceOut *hw)
+{
+ SWVoiceCap *sc = hw->cap_head.lh_first;
+
+ while (sc) {
+ SWVoiceCap *sc1 = sc->entries.le_next;
+ SWVoiceOut *sw = &sc->sw;
+ CaptureVoiceOut *cap = sc->cap;
+ int was_active = sw->active;
+
+ if (sw->rate) {
+ st_rate_stop (sw->rate);
+ sw->rate = NULL;
+ }
+
+ QLIST_REMOVE (sw, entries);
+ QLIST_REMOVE (sc, entries);
+ g_free (sc);
+ if (was_active) {
+ /* We have removed soft voice from the capture:
+ this might have changed the overall status of the capture
+ since this might have been the only active voice */
+ audio_recalc_and_notify_capture (cap);
+ }
+ sc = sc1;
+ }
+}
+
+static int audio_attach_capture (HWVoiceOut *hw)
+{
+ AudioMixengBackend *s = hw->s;
+ CaptureVoiceOut *cap;
+
+ audio_detach_capture (hw);
+ for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
+ SWVoiceCap *sc;
+ SWVoiceOut *sw;
+ HWVoiceOut *hw_cap = &cap->hw;
+
+ sc = g_malloc0(sizeof(*sc));
+
+ sc->cap = cap;
+ sw = &sc->sw;
+ sw->hw = hw_cap;
+ sw->info = hw->info;
+ sw->empty = true;
+ sw->active = hw->enabled;
+ sw->vol = nominal_volume;
+ sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
+ QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
+ QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);
+#ifdef DEBUG_CAPTURE
+ sw->name = g_strdup_printf ("for %p %d,%d,%d",
+ hw, sw->info.freq, sw->info.bits,
+ sw->info.nchannels);
+ dolog ("Added %s active = %d\n", sw->name, sw->active);
+#endif
+ if (sw->active) {
+ audio_capture_maybe_changed (cap, 1);
+ }
+ }
+ return 0;
+}
+
+/*
+ * Hard voice (capture)
+ */
+static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)
+{
+ SWVoiceIn *sw;
+ size_t m = hw->total_samples_captured;
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (sw->active) {
+ m = MIN (m, sw->total_hw_samples_acquired);
+ }
+ }
+ return m;
+}
+
+static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
+{
+ size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
+ if (audio_bug(__func__, live > hw->conv_buf.size)) {
+ dolog("live=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
+ return 0;
+ }
+ return live;
+}
+
+static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
+{
+ size_t conv = 0;
+ STSampleBuffer *conv_buf = &hw->conv_buf;
+
+ while (samples) {
+ uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);
+ size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);
+
+ hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);
+ conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
+ samples -= proc;
+ conv += proc;
+ }
+
+ return conv;
+}
+
+/*
+ * Soft voice (capture)
+ */
+static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
+ size_t frames_in_max, size_t frames_out_max,
+ size_t *total_in, size_t *total_out)
+{
+ HWVoiceIn *hw = sw->hw;
+ struct st_sample *src, *dst;
+ size_t live, rpos, frames_in, frames_out;
+
+ live = hw->total_samples_captured - sw->total_hw_samples_acquired;
+ rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
+
+ /* resample conv_buf from rpos to end of buffer */
+ src = hw->conv_buf.buffer + rpos;
+ frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos);
+ dst = sw->resample_buf.buffer;
+ frames_out = frames_out_max;
+ st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
+ rpos += frames_in;
+ *total_in = frames_in;
+ *total_out = frames_out;
+
+ /* resample conv_buf from start of buffer if there are input frames left */
+ if (frames_in_max - frames_in && rpos == hw->conv_buf.size) {
+ src = hw->conv_buf.buffer;
+ frames_in = frames_in_max - frames_in;
+ dst += frames_out;
+ frames_out = frames_out_max - frames_out;
+ st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
+ *total_in += frames_in;
+ *total_out += frames_out;
+ }
+}
+
+static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
+{
+ HWVoiceIn *hw = sw->hw;
+ size_t live, frames_out_max, total_in, total_out;
+
+ live = hw->total_samples_captured - sw->total_hw_samples_acquired;
+ if (!live) {
+ return 0;
+ }
+ if (audio_bug(__func__, live > hw->conv_buf.size)) {
+ dolog("live_in=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
+ return 0;
+ }
+
+ frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,
+ sw->resample_buf.size);
+
+ audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);
+
+ if (!hw->pcm_ops->volume_in) {
+ mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);
+ }
+ sw->clip(buf, sw->resample_buf.buffer, total_out);
+
+ sw->total_hw_samples_acquired += total_in;
+ return total_out * sw->info.bytes_per_frame;
+}
+
+/*
+ * Hard voice (playback)
+ */
+static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)
+{
+ SWVoiceOut *sw;
+ size_t m = SIZE_MAX;
+ int nb_live = 0;
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (sw->active || !sw->empty) {
+ m = MIN (m, sw->total_hw_samples_mixed);
+ nb_live += 1;
+ }
+ }
+
+ *nb_livep = nb_live;
+ return m;
+}
+
+static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)
+{
+ size_t smin;
+ int nb_live1;
+
+ smin = audio_pcm_hw_find_min_out (hw, &nb_live1);
+ if (nb_live) {
+ *nb_live = nb_live1;
+ }
+
+ if (nb_live1) {
+ size_t live = smin;
+
+ if (audio_bug(__func__, live > hw->mix_buf.size)) {
+ dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
+ return 0;
+ }
+ return live;
+ }
+ return 0;
+}
+
+static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)
+{
+ return (hw->pcm_ops->buffer_get_free ? hw->pcm_ops->buffer_get_free(hw) :
+ INT_MAX) / hw->info.bytes_per_frame;
+}
+
+static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
+{
+ size_t clipped = 0;
+ size_t pos = hw->mix_buf.pos;
+
+ while (len) {
+ st_sample *src = hw->mix_buf.buffer + pos;
+ 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);
+
+ hw->clip(dst, src, samples_to_clip);
+
+ pos = (pos + samples_to_clip) % hw->mix_buf.size;
+ len -= samples_to_clip;
+ clipped += samples_to_clip;
+ }
+}
+
+/*
+ * Soft voice (playback)
+ */
+static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
+ size_t frames_in_max, size_t frames_out_max,
+ size_t *total_in, size_t *total_out)
+{
+ HWVoiceOut *hw = sw->hw;
+ struct st_sample *src, *dst;
+ size_t live, wpos, frames_in, frames_out;
+
+ live = sw->total_hw_samples_mixed;
+ wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;
+
+ /* write to mix_buf from wpos to end of buffer */
+ src = sw->resample_buf.buffer;
+ frames_in = frames_in_max;
+ dst = hw->mix_buf.buffer + wpos;
+ frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);
+ st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
+ wpos += frames_out;
+ *total_in = frames_in;
+ *total_out = frames_out;
+
+ /* write to mix_buf from start of buffer if there are input frames left */
+ if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {
+ src += frames_in;
+ frames_in = frames_in_max - frames_in;
+ dst = hw->mix_buf.buffer;
+ frames_out = frames_out_max - frames_out;
+ st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
+ *total_in += frames_in;
+ *total_out += frames_out;
+ }
+}
+
+static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
+{
+ HWVoiceOut *hw = sw->hw;
+ size_t live, dead, hw_free, sw_max, fe_max;
+ size_t frames_in_max, frames_out_max, total_in, total_out;
+
+ live = sw->total_hw_samples_mixed;
+ if (audio_bug(__func__, live > hw->mix_buf.size)) {
+ dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
+ return 0;
+ }
+
+ if (live == hw->mix_buf.size) {
+#ifdef DEBUG_OUT
+ dolog ("%s is full %zu\n", sw->name, live);
+#endif
+ return 0;
+ }
+
+ dead = hw->mix_buf.size - live;
+ hw_free = audio_pcm_hw_get_free(hw);
+ hw_free = hw_free > live ? hw_free - live : 0;
+ frames_out_max = MIN(dead, hw_free);
+ sw_max = st_rate_frames_in(sw->rate, frames_out_max);
+ fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,
+ sw->resample_buf.size);
+ frames_in_max = MIN(sw_max, fe_max);
+
+ if (!frames_in_max) {
+ return 0;
+ }
+
+ if (frames_in_max > sw->resample_buf.pos) {
+ sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,
+ buf, frames_in_max - sw->resample_buf.pos);
+ if (!sw->hw->pcm_ops->volume_out) {
+ mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,
+ frames_in_max - sw->resample_buf.pos, &sw->vol);
+ }
+ }
+
+ audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,
+ &total_in, &total_out);
+
+ sw->total_hw_samples_mixed += total_out;
+ sw->empty = sw->total_hw_samples_mixed == 0;
+
+ /*
+ * Upsampling may leave one audio frame in the resample buffer. Decrement
+ * total_in by one if there was a leftover frame from the previous resample
+ * pass in the resample buffer. Increment total_in by one if the current
+ * resample pass left one frame in the resample buffer.
+ */
+ if (frames_in_max - total_in == 1) {
+ /* copy one leftover audio frame to the beginning of the buffer */
+ *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);
+ total_in += 1 - sw->resample_buf.pos;
+ sw->resample_buf.pos = 1;
+ } else if (total_in >= sw->resample_buf.pos) {
+ total_in -= sw->resample_buf.pos;
+ sw->resample_buf.pos = 0;
+ }
+
+#ifdef DEBUG_OUT
+ dolog (
+ "%s: write size %zu written %zu total mixed %zu\n",
+ SW_NAME(sw),
+ buf_len / sw->info.bytes_per_frame,
+ total_in,
+ sw->total_hw_samples_mixed
+ );
+#endif
+
+ return total_in * sw->info.bytes_per_frame;
+}
+
+#ifdef DEBUG_AUDIO
+static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
+{
+ dolog("%s: bits %d, sign %d, float %d, freq %d, nchan %d\n",
+ cap, info->bits, info->is_signed, info->is_float, info->freq,
+ info->nchannels);
+}
+#endif
+
+#define DAC
+#include "audio_template.h"
+#undef DAC
+#include "audio_template.h"
+
+/*
+ * Timer
+ */
+static int audio_is_timer_needed(AudioMixengBackend *s)
+{
+ HWVoiceIn *hwi = NULL;
+ HWVoiceOut *hwo = NULL;
+
+ while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
+ if (!hwo->poll_mode) {
+ return 1;
+ }
+ }
+ while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
+ if (!hwi->poll_mode) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void audio_reset_timer(AudioMixengBackend *s)
+{
+ if (audio_is_timer_needed(s)) {
+ timer_mod_anticipate_ns(s->ts,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
+ if (!s->timer_running) {
+ s->timer_running = true;
+ s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ trace_audio_timer_start(s->period_ticks / SCALE_MS);
+ }
+ } else {
+ timer_del(s->ts);
+ if (s->timer_running) {
+ s->timer_running = false;
+ trace_audio_timer_stop();
+ }
+ }
+}
+
+static void audio_timer (void *opaque)
+{
+ int64_t now, diff;
+ AudioMixengBackend *s = opaque;
+
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ diff = now - s->timer_last;
+ if (diff > s->period_ticks * 3 / 2) {
+ trace_audio_timer_delayed(diff / SCALE_MS);
+ }
+ s->timer_last = now;
+
+ audio_run(s, "timer");
+ audio_reset_timer(s);
+}
+
+/*
+ * Public API
+ */
+static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw,
+ void *buf, size_t size)
+{
+ HWVoiceOut *hw;
+
+ if (!sw) {
+ /* XXX: Consider options */
+ return size;
+ }
+ hw = sw->hw;
+
+ if (!hw->enabled) {
+ dolog("Writing to disabled voice %s\n", SW_NAME(sw));
+ return 0;
+ }
+
+ 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);
+ }
+}
+
+static size_t audio_mixeng_backend_read(AudioBackend *be,
+ SWVoiceIn *sw, void *buf, size_t size)
+{
+ HWVoiceIn *hw;
+
+ if (!sw) {
+ /* XXX: Consider options */
+ return size;
+ }
+ hw = sw->hw;
+
+ if (!hw->enabled) {
+ dolog("Reading from disabled voice %s\n", SW_NAME(sw));
+ return 0;
+ }
+
+ 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);
+ }
+
+}
+
+static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
+{
+ if (!sw) {
+ return 0;
+ }
+
+ if (audio_get_pdo_out(sw->s->dev)->mixing_engine) {
+ return sw->resample_buf.size * sw->info.bytes_per_frame;
+ }
+
+ return sw->hw->samples * sw->hw->info.bytes_per_frame;
+}
+
+static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceOut *sw,
+ bool on)
+{
+ HWVoiceOut *hw;
+
+ if (!sw) {
+ return;
+ }
+
+ hw = sw->hw;
+ if (sw->active != on) {
+ AudioMixengBackend *s = sw->s;
+ SWVoiceOut *temp_sw;
+ SWVoiceCap *sc;
+
+ if (on) {
+ hw->pending_disable = 0;
+ if (!hw->enabled) {
+ hw->enabled = true;
+ if (runstate_is_running()) {
+ if (hw->pcm_ops->enable_out) {
+ hw->pcm_ops->enable_out(hw, true);
+ }
+ audio_reset_timer (s);
+ }
+ }
+ } else {
+ if (hw->enabled) {
+ int nb_active = 0;
+
+ for (temp_sw = hw->sw_head.lh_first; temp_sw;
+ temp_sw = temp_sw->entries.le_next) {
+ nb_active += temp_sw->active != 0;
+ }
+
+ hw->pending_disable = nb_active == 1;
+ }
+ }
+
+ for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
+ sc->sw.active = hw->enabled;
+ if (hw->enabled) {
+ audio_capture_maybe_changed (sc->cap, 1);
+ }
+ }
+ sw->active = on;
+ }
+
+}
+
+static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
+{
+ HWVoiceIn *hw;
+
+ if (!sw) {
+ return;
+ }
+
+ hw = sw->hw;
+ if (sw->active != on) {
+ AudioMixengBackend *s = sw->s;
+ SWVoiceIn *temp_sw;
+
+ if (on) {
+ if (!hw->enabled) {
+ hw->enabled = true;
+ if (runstate_is_running()) {
+ if (hw->pcm_ops->enable_in) {
+ hw->pcm_ops->enable_in(hw, true);
+ }
+ audio_reset_timer (s);
+ }
+ }
+ sw->total_hw_samples_acquired = hw->total_samples_captured;
+ } else {
+ if (hw->enabled) {
+ int nb_active = 0;
+
+ for (temp_sw = hw->sw_head.lh_first; temp_sw;
+ temp_sw = temp_sw->entries.le_next) {
+ nb_active += temp_sw->active != 0;
+ }
+
+ if (nb_active == 1) {
+ hw->enabled = false;
+ if (hw->pcm_ops->enable_in) {
+ hw->pcm_ops->enable_in(hw, false);
+ }
+ }
+ }
+ }
+ sw->active = on;
+ }
+}
+
+static size_t audio_get_avail(SWVoiceIn *sw)
+{
+ size_t live;
+
+ if (!sw) {
+ return 0;
+ }
+
+ live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
+ if (audio_bug(__func__, live > sw->hw->conv_buf.size)) {
+ dolog("live=%zu sw->hw->conv_buf.size=%zu\n", live,
+ sw->hw->conv_buf.size);
+ return 0;
+ }
+
+ ldebug (
+ "%s: get_avail live %zu frontend frames %u\n",
+ SW_NAME (sw),
+ live, st_rate_frames_out(sw->rate, live)
+ );
+
+ return live;
+}
+
+static size_t audio_get_free(SWVoiceOut *sw)
+{
+ size_t live, dead;
+
+ if (!sw) {
+ return 0;
+ }
+
+ live = sw->total_hw_samples_mixed;
+
+ if (audio_bug(__func__, live > sw->hw->mix_buf.size)) {
+ dolog("live=%zu sw->hw->mix_buf.size=%zu\n", live,
+ sw->hw->mix_buf.size);
+ return 0;
+ }
+
+ dead = sw->hw->mix_buf.size - live;
+
+#ifdef DEBUG_OUT
+ dolog("%s: get_free live %zu dead %zu frontend frames %u\n",
+ SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead));
+#endif
+
+ return dead;
+}
+
+static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
+ size_t samples)
+{
+ size_t n;
+
+ if (hw->enabled) {
+ SWVoiceCap *sc;
+
+ for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
+ SWVoiceOut *sw = &sc->sw;
+ size_t rpos2 = rpos;
+
+ n = samples;
+ while (n) {
+ size_t till_end_of_hw = hw->mix_buf.size - rpos2;
+ size_t to_read = MIN(till_end_of_hw, n);
+ size_t live, frames_in, frames_out;
+
+ sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;
+ sw->resample_buf.size = to_read;
+ live = sw->total_hw_samples_mixed;
+
+ audio_pcm_sw_resample_out(sw,
+ to_read, sw->hw->mix_buf.size - live,
+ &frames_in, &frames_out);
+
+ sw->total_hw_samples_mixed += frames_out;
+ sw->empty = sw->total_hw_samples_mixed == 0;
+
+ if (to_read - frames_in) {
+ dolog("Could not mix %zu frames into a capture "
+ "buffer, mixed %zu\n",
+ to_read, frames_in);
+ break;
+ }
+ n -= to_read;
+ rpos2 = (rpos2 + to_read) % hw->mix_buf.size;
+ }
+ }
+ }
+
+ n = MIN(samples, hw->mix_buf.size - rpos);
+ mixeng_clear(hw->mix_buf.buffer + rpos, n);
+ mixeng_clear(hw->mix_buf.buffer, samples - n);
+}
+
+static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
+{
+ size_t clipped = 0;
+
+ while (live) {
+ size_t size = live * hw->info.bytes_per_frame;
+ size_t decr, proc;
+ void *buf = hw->pcm_ops->get_buffer_out(hw, &size);
+
+ if (size == 0) {
+ break;
+ }
+
+ decr = MIN(size / hw->info.bytes_per_frame, live);
+ if (buf) {
+ audio_pcm_hw_clip_out(hw, buf, decr);
+ }
+ proc = hw->pcm_ops->put_buffer_out(hw, buf,
+ decr * hw->info.bytes_per_frame) /
+ hw->info.bytes_per_frame;
+
+ live -= proc;
+ clipped += proc;
+ hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size;
+
+ if (proc == 0 || proc < decr) {
+ break;
+ }
+ }
+
+ if (hw->pcm_ops->run_buffer_out) {
+ hw->pcm_ops->run_buffer_out(hw);
+ }
+
+ return clipped;
+}
+
+static void audio_run_out(AudioMixengBackend *s)
+{
+ HWVoiceOut *hw = NULL;
+ SWVoiceOut *sw;
+
+ while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
+ size_t played, live, prev_rpos;
+ size_t hw_free = audio_pcm_hw_get_free(hw);
+ int nb_live;
+
+ if (!audio_get_pdo_out(s->dev)->mixing_engine) {
+ /* there is exactly 1 sw for each hw with no mixeng */
+ sw = hw->sw_head.lh_first;
+
+ if (hw->pending_disable) {
+ hw->enabled = false;
+ hw->pending_disable = false;
+ if (hw->pcm_ops->enable_out) {
+ hw->pcm_ops->enable_out(hw, false);
+ }
+ }
+
+ if (sw->active) {
+ sw->callback.fn(sw->callback.opaque,
+ hw_free * sw->info.bytes_per_frame);
+ }
+
+ if (hw->pcm_ops->run_buffer_out) {
+ hw->pcm_ops->run_buffer_out(hw);
+ }
+
+ continue;
+ }
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (sw->active) {
+ size_t sw_free = audio_get_free(sw);
+ size_t free;
+
+ if (hw_free > sw->total_hw_samples_mixed) {
+ free = st_rate_frames_in(sw->rate,
+ MIN(sw_free, hw_free - sw->total_hw_samples_mixed));
+ } else {
+ free = 0;
+ }
+ if (free > sw->resample_buf.pos) {
+ free = MIN(free, sw->resample_buf.size)
+ - sw->resample_buf.pos;
+ sw->callback.fn(sw->callback.opaque,
+ free * sw->info.bytes_per_frame);
+ }
+ }
+ }
+
+ live = audio_pcm_hw_get_live_out (hw, &nb_live);
+ if (!nb_live) {
+ live = 0;
+ }
+
+ if (audio_bug(__func__, live > hw->mix_buf.size)) {
+ dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
+ continue;
+ }
+
+ if (hw->pending_disable && !nb_live) {
+ SWVoiceCap *sc;
+#ifdef DEBUG_OUT
+ dolog ("Disabling voice\n");
+#endif
+ hw->enabled = false;
+ hw->pending_disable = false;
+ if (hw->pcm_ops->enable_out) {
+ hw->pcm_ops->enable_out(hw, false);
+ }
+ for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
+ sc->sw.active = false;
+ audio_recalc_and_notify_capture (sc->cap);
+ }
+ continue;
+ }
+
+ if (!live) {
+ if (hw->pcm_ops->run_buffer_out) {
+ hw->pcm_ops->run_buffer_out(hw);
+ }
+ continue;
+ }
+
+ prev_rpos = hw->mix_buf.pos;
+ played = audio_pcm_hw_run_out(hw, live);
+ replay_audio_out(&played);
+ if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) {
+ dolog("hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\n",
+ hw->mix_buf.pos, hw->mix_buf.size, played);
+ hw->mix_buf.pos = 0;
+ }
+
+#ifdef DEBUG_OUT
+ dolog("played=%zu\n", played);
+#endif
+
+ if (played) {
+ audio_capture_mix_and_clear (hw, prev_rpos, played);
+ }
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (!sw->active && sw->empty) {
+ continue;
+ }
+
+ if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) {
+ dolog("played=%zu sw->total_hw_samples_mixed=%zu\n",
+ played, sw->total_hw_samples_mixed);
+ played = sw->total_hw_samples_mixed;
+ }
+
+ sw->total_hw_samples_mixed -= played;
+
+ if (!sw->total_hw_samples_mixed) {
+ sw->empty = true;
+ }
+ }
+ }
+}
+
+static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
+{
+ size_t conv = 0;
+
+ if (hw->pcm_ops->run_buffer_in) {
+ hw->pcm_ops->run_buffer_in(hw);
+ }
+
+ while (samples) {
+ size_t proc;
+ size_t size = samples * hw->info.bytes_per_frame;
+ void *buf = hw->pcm_ops->get_buffer_in(hw, &size);
+
+ assert(size % hw->info.bytes_per_frame == 0);
+ if (size == 0) {
+ break;
+ }
+
+ proc = audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_frame);
+
+ samples -= proc;
+ conv += proc;
+ hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame);
+ }
+
+ return conv;
+}
+
+static void audio_run_in(AudioMixengBackend *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;
+ int pos;
+
+ if (replay_mode != REPLAY_MODE_PLAY) {
+ captured = audio_pcm_hw_run_in(
+ hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw));
+ }
+
+ replay_audio_in_start(&captured);
+ assert(captured <= hw->conv_buf.size);
+ if (replay_mode == REPLAY_MODE_PLAY) {
+ hw->conv_buf.pos = (hw->conv_buf.pos + captured) % hw->conv_buf.size;
+ }
+ for (pos = (hw->conv_buf.pos - captured + hw->conv_buf.size) % hw->conv_buf.size;
+ pos != hw->conv_buf.pos;
+ pos = (pos + 1) % hw->conv_buf.size) {
+ uint64_t left, right;
+
+ if (replay_mode == REPLAY_MODE_RECORD) {
+ audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left, &right);
+ }
+ replay_audio_in_sample_lr(&left, &right);
+ if (replay_mode == REPLAY_MODE_PLAY) {
+ audio_sample_from_uint64(hw->conv_buf.buffer, pos, left, right);
+ }
+ }
+ replay_audio_in_finish();
+
+ min = audio_pcm_hw_find_min_in (hw);
+ hw->total_samples_captured += captured - min;
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ sw->total_hw_samples_acquired -= min;
+
+ if (sw->active) {
+ size_t sw_avail = audio_get_avail(sw);
+ size_t avail;
+
+ avail = st_rate_frames_out(sw->rate, sw_avail);
+ if (avail > 0) {
+ avail = MIN(avail, sw->resample_buf.size);
+ sw->callback.fn(sw->callback.opaque,
+ avail * sw->info.bytes_per_frame);
+ }
+ }
+ }
+ }
+}
+
+static void audio_run_capture(AudioMixengBackend *s)
+{
+ CaptureVoiceOut *cap;
+
+ for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
+ size_t live, rpos, captured;
+ HWVoiceOut *hw = &cap->hw;
+ SWVoiceOut *sw;
+
+ captured = live = audio_pcm_hw_get_live_out (hw, NULL);
+ rpos = hw->mix_buf.pos;
+ while (live) {
+ size_t left = hw->mix_buf.size - rpos;
+ size_t to_capture = MIN(live, left);
+ struct st_sample *src;
+ struct capture_callback *cb;
+
+ src = hw->mix_buf.buffer + rpos;
+ hw->clip (cap->buf, src, to_capture);
+ mixeng_clear (src, to_capture);
+
+ for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
+ cb->ops.capture (cb->opaque, cap->buf,
+ to_capture * hw->info.bytes_per_frame);
+ }
+ rpos = (rpos + to_capture) % hw->mix_buf.size;
+ live -= to_capture;
+ }
+ hw->mix_buf.pos = rpos;
+
+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
+ if (!sw->active && sw->empty) {
+ continue;
+ }
+
+ if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)) {
+ dolog("captured=%zu sw->total_hw_samples_mixed=%zu\n",
+ captured, sw->total_hw_samples_mixed);
+ captured = sw->total_hw_samples_mixed;
+ }
+
+ sw->total_hw_samples_mixed -= captured;
+ sw->empty = sw->total_hw_samples_mixed == 0;
+ }
+ }
+}
+
+void audio_run(AudioMixengBackend *s, const char *msg)
+{
+ audio_run_out(s);
+ audio_run_in(s);
+ audio_run_capture(s);
+
+#ifdef DEBUG_POLL
+ {
+ static double prevtime;
+ double currtime;
+ struct timeval tv;
+
+ if (gettimeofday (&tv, NULL)) {
+ perror ("audio_run: gettimeofday");
+ return;
+ }
+
+ currtime = tv.tv_sec + tv.tv_usec * 1e-6;
+ dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime);
+ prevtime = currtime;
+ }
+#endif
+}
+
+void audio_generic_run_buffer_in(HWVoiceIn *hw)
+{
+ if (unlikely(!hw->buf_emul)) {
+ hw->size_emul = hw->samples * hw->info.bytes_per_frame;
+ hw->buf_emul = g_malloc(hw->size_emul);
+ hw->pos_emul = hw->pending_emul = 0;
+ }
+
+ while (hw->pending_emul < hw->size_emul) {
+ size_t read_len = MIN(hw->size_emul - hw->pos_emul,
+ hw->size_emul - hw->pending_emul);
+ size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul,
+ read_len);
+ hw->pending_emul += read;
+ hw->pos_emul = (hw->pos_emul + read) % hw->size_emul;
+ if (read < read_len) {
+ break;
+ }
+ }
+}
+
+void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)
+{
+ size_t start;
+
+ start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
+ assert(start < hw->size_emul);
+
+ *size = MIN(*size, hw->pending_emul);
+ *size = MIN(*size, hw->size_emul - start);
+ return hw->buf_emul + start;
+}
+
+void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
+{
+ assert(size <= hw->pending_emul);
+ hw->pending_emul -= size;
+}
+
+size_t audio_generic_buffer_get_free(HWVoiceOut *hw)
+{
+ if (hw->buf_emul) {
+ return hw->size_emul - hw->pending_emul;
+ } else {
+ return hw->samples * hw->info.bytes_per_frame;
+ }
+}
+
+void audio_generic_run_buffer_out(HWVoiceOut *hw)
+{
+ while (hw->pending_emul) {
+ size_t write_len, written, start;
+
+ start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
+ assert(start < hw->size_emul);
+
+ write_len = MIN(hw->pending_emul, hw->size_emul - start);
+
+ written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len);
+ hw->pending_emul -= written;
+
+ if (written < write_len) {
+ break;
+ }
+ }
+}
+
+void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)
+{
+ if (unlikely(!hw->buf_emul)) {
+ hw->size_emul = hw->samples * hw->info.bytes_per_frame;
+ hw->buf_emul = g_malloc(hw->size_emul);
+ hw->pos_emul = hw->pending_emul = 0;
+ }
+
+ *size = MIN(hw->size_emul - hw->pending_emul,
+ hw->size_emul - hw->pos_emul);
+ return hw->buf_emul + hw->pos_emul;
+}
+
+size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
+{
+ assert(buf == hw->buf_emul + hw->pos_emul &&
+ size + hw->pending_emul <= hw->size_emul);
+
+ hw->pending_emul += size;
+ hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;
+
+ return size;
+}
+
+size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size)
+{
+ size_t total = 0;
+
+ if (hw->pcm_ops->buffer_get_free) {
+ size_t free = hw->pcm_ops->buffer_get_free(hw);
+
+ size = MIN(size, free);
+ }
+
+ while (total < size) {
+ size_t dst_size = size - total;
+ size_t copy_size, proc;
+ void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size);
+
+ if (dst_size == 0) {
+ break;
+ }
+
+ copy_size = MIN(size - total, dst_size);
+ if (dst) {
+ memcpy(dst, (char *)buf + total, copy_size);
+ }
+ proc = hw->pcm_ops->put_buffer_out(hw, dst, copy_size);
+ total += proc;
+
+ if (proc == 0 || proc < copy_size) {
+ break;
+ }
+ }
+
+ return total;
+}
+
+size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
+{
+ size_t total = 0;
+
+ if (hw->pcm_ops->run_buffer_in) {
+ hw->pcm_ops->run_buffer_in(hw);
+ }
+
+ while (total < size) {
+ size_t src_size = size - total;
+ void *src = hw->pcm_ops->get_buffer_in(hw, &src_size);
+
+ if (src_size == 0) {
+ break;
+ }
+
+ memcpy((char *)buf + total, src, src_size);
+ hw->pcm_ops->put_buffer_in(hw, src, src_size);
+ total += src_size;
+ }
+
+ return total;
+}
+
+static bool audio_mixeng_backend_realize(AudioBackend *abe,
+ Audiodev *dev, Error **errp)
+{
+ AudioMixengBackend *be = AUDIO_MIXENG_BACKEND(abe);
+ audio_driver *drv = AUDIO_MIXENG_BACKEND_GET_CLASS(be)->driver;
+
+ be->dev = dev;
+ be->drv_opaque = drv->init(be->dev, errp);
+ if (!be->drv_opaque) {
+ return false;
+ }
+
+ if (!drv->pcm_ops->get_buffer_in) {
+ drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in;
+ drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in;
+ }
+ if (!drv->pcm_ops->get_buffer_out) {
+ drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out;
+ drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;
+ }
+
+ audio_init_nb_voices_out(be, drv, 1);
+ audio_init_nb_voices_in(be, drv, 0);
+ be->drv = drv;
+
+ if (be->dev->timer_period <= 0) {
+ be->period_ticks = 1;
+ } else {
+ be->period_ticks = be->dev->timer_period * (int64_t)SCALE_US;
+ }
+
+ return true;
+}
+
+static void audio_vm_change_state_handler (void *opaque, bool running,
+ RunState state)
+{
+ AudioMixengBackend *s = opaque;
+ HWVoiceOut *hwo = NULL;
+ HWVoiceIn *hwi = NULL;
+
+ while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
+ if (hwo->pcm_ops->enable_out) {
+ hwo->pcm_ops->enable_out(hwo, running);
+ }
+ }
+
+ while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
+ if (hwi->pcm_ops->enable_in) {
+ hwi->pcm_ops->enable_in(hwi, running);
+ }
+ }
+ audio_reset_timer (s);
+}
+
+static const VMStateDescription vmstate_audio;
+
+static const char *audio_mixeng_backend_get_id(AudioBackend *be)
+{
+ return AUDIO_MIXENG_BACKEND(be)->dev->id;
+}
+
+static CaptureVoiceOut *audio_mixeng_backend_add_capture(
+ AudioBackend *be,
+ struct audsettings *as,
+ struct audio_capture_ops *ops,
+ void *cb_opaque);
+
+static void audio_mixeng_backend_del_capture(
+ AudioBackend *be,
+ CaptureVoiceOut *cap,
+ void *cb_opaque);
+
+static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
+ Volume *vol);
+static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
+ Volume *vol);
+
+static void audio_mixeng_backend_class_init(ObjectClass *klass, const void *data)
+{
+ AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
+
+ be->realize = audio_mixeng_backend_realize;
+ be->get_id = audio_mixeng_backend_get_id;
+ be->open_in = audio_mixeng_backend_open_in;
+ be->open_out = audio_mixeng_backend_open_out;
+ be->close_in = audio_mixeng_backend_close_in;
+ be->close_out = audio_mixeng_backend_close_out;
+ be->is_active_out = audio_mixeng_backend_is_active_out;
+ be->is_active_in = audio_mixeng_backend_is_active_in;
+ be->set_active_out = audio_mixeng_backend_set_active_out;
+ be->set_active_in = audio_mixeng_backend_set_active_in;
+ be->set_volume_out = audio_mixeng_backend_set_volume_out;
+ be->set_volume_in = audio_mixeng_backend_set_volume_in;
+ be->read = audio_mixeng_backend_read;
+ be->write = audio_mixeng_backend_write;
+ be->get_buffer_size_out = audio_mixeng_backend_get_buffer_size_out;
+ be->add_capture = audio_mixeng_backend_add_capture;
+ be->del_capture = audio_mixeng_backend_del_capture;
+}
+
+static void audio_mixeng_backend_init(Object *obj)
+{
+ AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
+
+ QLIST_INIT(&s->hw_head_out);
+ QLIST_INIT(&s->hw_head_in);
+ QLIST_INIT(&s->cap_head);
+ s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
+
+ s->vmse = qemu_add_vm_change_state_handler(audio_vm_change_state_handler, s);
+ assert(s->vmse != NULL);
+
+ vmstate_register_any(NULL, &vmstate_audio, s);
+}
+
+static void audio_mixeng_backend_finalize(Object *obj)
+{
+ AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
+ HWVoiceOut *hwo, *hwon;
+ HWVoiceIn *hwi, *hwin;
+
+ QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) {
+ SWVoiceCap *sc;
+
+ if (hwo->enabled && hwo->pcm_ops->enable_out) {
+ hwo->pcm_ops->enable_out(hwo, false);
+ }
+ hwo->pcm_ops->fini_out (hwo);
+
+ for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) {
+ CaptureVoiceOut *cap = sc->cap;
+ struct capture_callback *cb;
+
+ for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
+ cb->ops.destroy (cb->opaque);
+ }
+ }
+ QLIST_REMOVE(hwo, entries);
+ }
+
+ QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) {
+ if (hwi->enabled && hwi->pcm_ops->enable_in) {
+ hwi->pcm_ops->enable_in(hwi, false);
+ }
+ hwi->pcm_ops->fini_in (hwi);
+ QLIST_REMOVE(hwi, entries);
+ }
+
+ if (s->drv) {
+ s->drv->fini (s->drv_opaque);
+ s->drv = NULL;
+ }
+
+ if (s->dev) {
+ qapi_free_Audiodev(s->dev);
+ s->dev = NULL;
+ }
+
+ if (s->ts) {
+ timer_free(s->ts);
+ s->ts = NULL;
+ }
+
+ if (s->vmse) {
+ qemu_del_vm_change_state_handler(s->vmse);
+ s->vmse = NULL;
+ }
+
+ vmstate_unregister(NULL, &vmstate_audio, s);
+}
+
+static bool vmstate_audio_needed(void *opaque)
+{
+ /*
+ * Never needed, this vmstate only exists in case
+ * an old qemu sends it to us.
+ */
+ return false;
+}
+
+static const VMStateDescription vmstate_audio = {
+ .name = "audio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = vmstate_audio_needed,
+ .fields = (const VMStateField[]) {
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static struct audio_pcm_ops capture_pcm_ops;
+
+static CaptureVoiceOut *audio_mixeng_backend_add_capture(
+ AudioBackend *be,
+ struct audsettings *as,
+ struct audio_capture_ops *ops,
+ void *cb_opaque)
+{
+ AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(be);
+ CaptureVoiceOut *cap;
+ struct capture_callback *cb;
+
+ if (!s) {
+ error_report("Capturing without setting an audiodev is not supported");
+ abort();
+ }
+
+ 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);
+ return NULL;
+ }
+
+ cb = g_malloc0(sizeof(*cb));
+ cb->ops = *ops;
+ cb->opaque = cb_opaque;
+
+ cap = audio_pcm_capture_find_specific(s, as);
+ if (cap) {
+ QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
+ } else {
+ HWVoiceOut *hw;
+
+ cap = g_malloc0(sizeof(*cap));
+
+ hw = &cap->hw;
+ hw->s = s;
+ hw->pcm_ops = &capture_pcm_ops;
+ QLIST_INIT (&hw->sw_head);
+ QLIST_INIT (&cap->cb_head);
+
+ /* XXX find a more elegant way */
+ hw->samples = 4096 * 4;
+ audio_pcm_hw_alloc_resources_out(hw);
+
+ audio_pcm_init_info (&hw->info, as);
+
+ cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame);
+
+ if (hw->info.is_float) {
+ hw->clip = mixeng_clip_float[hw->info.nchannels == 2]
+ [hw->info.swap_endianness];
+ } else {
+ hw->clip = mixeng_clip
+ [hw->info.nchannels == 2]
+ [hw->info.is_signed]
+ [hw->info.swap_endianness]
+ [audio_bits_to_index(hw->info.bits)];
+ }
+
+ QLIST_INSERT_HEAD (&s->cap_head, cap, entries);
+ QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
+
+ QLIST_FOREACH(hw, &s->hw_head_out, entries) {
+ audio_attach_capture (hw);
+ }
+ }
+
+ return cap;
+}
+
+static void audio_mixeng_backend_del_capture(
+ AudioBackend *be,
+ CaptureVoiceOut *cap,
+ void *cb_opaque)
+{
+ struct capture_callback *cb;
+
+ for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
+ if (cb->opaque == cb_opaque) {
+ cb->ops.destroy (cb_opaque);
+ QLIST_REMOVE (cb, entries);
+ g_free (cb);
+
+ if (!cap->cb_head.lh_first) {
+ SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1;
+
+ while (sw) {
+ SWVoiceCap *sc = (SWVoiceCap *) sw;
+#ifdef DEBUG_CAPTURE
+ dolog ("freeing %s\n", sw->name);
+#endif
+
+ sw1 = sw->entries.le_next;
+ if (sw->rate) {
+ st_rate_stop (sw->rate);
+ sw->rate = NULL;
+ }
+ QLIST_REMOVE (sw, entries);
+ QLIST_REMOVE (sc, entries);
+ g_free (sc);
+ sw = sw1;
+ }
+ QLIST_REMOVE (cap, entries);
+ g_free(cap->hw.mix_buf.buffer);
+ g_free (cap->buf);
+ g_free (cap);
+ }
+ return;
+ }
+ }
+}
+
+static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
+ Volume *vol)
+{
+ if (sw) {
+ HWVoiceOut *hw = sw->hw;
+
+ 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, vol);
+ }
+ }
+}
+
+static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
+ Volume *vol)
+{
+ if (sw) {
+ HWVoiceIn *hw = sw->hw;
+
+ 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, vol);
+ }
+ }
+}
+
+audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo)
+{
+ return (audsettings) {
+ .freq = pdo->frequency,
+ .nchannels = pdo->channels,
+ .fmt = pdo->format,
+ .endianness = HOST_BIG_ENDIAN,
+ };
+}
+
+int audioformat_bytes_per_sample(AudioFormat fmt)
+{
+ switch (fmt) {
+ case AUDIO_FORMAT_U8:
+ case AUDIO_FORMAT_S8:
+ return 1;
+
+ case AUDIO_FORMAT_U16:
+ case AUDIO_FORMAT_S16:
+ return 2;
+
+ case AUDIO_FORMAT_U32:
+ case AUDIO_FORMAT_S32:
+ case AUDIO_FORMAT_F32:
+ return 4;
+
+ case AUDIO_FORMAT__MAX:
+ ;
+ }
+ abort();
+}
+
+
+/* frames = freq * usec / 1e6 */
+int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,
+ audsettings *as, int def_usecs)
+{
+ uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs;
+ return (as->freq * usecs + 500000) / 1000000;
+}
+
+/* samples = channels * frames = channels * freq * usec / 1e6 */
+int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,
+ audsettings *as, int def_usecs)
+{
+ return as->nchannels * audio_buffer_frames(pdo, as, def_usecs);
+}
+
+/*
+ * bytes = bytes_per_sample * samples =
+ * bytes_per_sample * channels * freq * usec / 1e6
+ */
+int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,
+ audsettings *as, int def_usecs)
+{
+ return audio_buffer_samples(pdo, as, def_usecs) *
+ audioformat_bytes_per_sample(as->fmt);
+}
+
+void audio_rate_start(RateCtl *rate)
+{
+ memset(rate, 0, sizeof(RateCtl));
+ rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+}
+
+size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info)
+{
+ int64_t now;
+ int64_t ticks;
+ int64_t bytes;
+ int64_t frames;
+
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ ticks = now - rate->start_ticks;
+ bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);
+ frames = (bytes - rate->bytes_sent) / info->bytes_per_frame;
+ rate->peeked_frames = frames;
+
+ return frames < 0 ? 0 : frames * info->bytes_per_frame;
+}
+
+void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used)
+{
+ if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) {
+ AUD_log(NULL, "Resetting rate control (%" PRId64 " frames)\n",
+ rate->peeked_frames);
+ audio_rate_start(rate);
+ }
+
+ rate->bytes_sent += bytes_used;
+}
+
+size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info,
+ size_t bytes_avail)
+{
+ size_t bytes;
+
+ bytes = audio_rate_peek_bytes(rate, info);
+ bytes = MIN(bytes, bytes_avail);
+ audio_rate_add_bytes(rate, bytes);
+
+ return bytes;
+}
+
+static const TypeInfo audio_mixeng_backend_info = {
+ .name = TYPE_AUDIO_MIXENG_BACKEND,
+ .parent = TYPE_AUDIO_BACKEND,
+ .instance_size = sizeof(AudioMixengBackend),
+ .instance_init = audio_mixeng_backend_init,
+ .instance_finalize = audio_mixeng_backend_finalize,
+ .abstract = false,
+ .class_size = sizeof(AudioMixengBackendClass),
+ .class_init = audio_mixeng_backend_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&audio_mixeng_backend_info);
+}
+
+type_init(register_types);
diff --git a/audio/audio.c b/audio/audio.c
index 1d948084e80..6f10ee470ee 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1,1750 +1,55 @@
-/*
- * QEMU Audio subsystem
- *
- * Copyright (c) 2003-2005 Vassili Karpov (malc)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-#include "qemu/osdep.h"
-#include "qemu/audio.h"
-#include "migration/vmstate.h"
-#include "qemu/timer.h"
-#include "qapi/error.h"
-#include "qapi/clone-visitor.h"
-#include "qapi/qobject-input-visitor.h"
-#include "qapi/qapi-visit-audio.h"
-#include "qapi/qapi-commands-audio.h"
-#include "qobject/qdict.h"
-#include "qemu/error-report.h"
-#include "qemu/log.h"
-#include "qemu/module.h"
-#include "qemu/help_option.h"
-#include "qom/object.h"
-#include "system/system.h"
-#include "system/replay.h"
-#include "system/runstate.h"
-#include "trace.h"
-
-#define AUDIO_CAP "audio"
-#include "audio_int.h"
-
-/* #define DEBUG_LIVE */
-/* #define DEBUG_OUT */
-/* #define DEBUG_CAPTURE */
-/* #define DEBUG_POLL */
-
-#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown"
-
-
-/* Order of CONFIG_AUDIO_DRIVERS is import.
- The 1st one is the one used by default, that is the reason
- that we generate the list.
-*/
-const char *audio_prio_list[] = {
-#ifdef CONFIG_GIO
- "dbus",
-#endif
- "spice",
- CONFIG_AUDIO_DRIVERS
- "none",
- NULL
-};
-
-typedef struct AudiodevListEntry {
- Audiodev *dev;
- QSIMPLEQ_ENTRY(AudiodevListEntry) next;
-} AudiodevListEntry;
-
-typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;
-
-static AudiodevListHead audiodevs =
- QSIMPLEQ_HEAD_INITIALIZER(audiodevs);
-static AudiodevListHead default_audiodevs =
- QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);
-
-static AudioBackendClass *audio_be_class_by_name(const char *name)
-{
- g_autofree char *tname = g_strconcat("audio-", name, NULL);
- ObjectClass *oc = module_object_class_by_name(tname);
-
- if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) {
- return NULL;
- }
-
- return AUDIO_BACKEND_CLASS(oc);
-}
-
-static AudioBackend *default_audio_be;
-
-const struct mixeng_volume nominal_volume = {
- .mute = 0,
-#ifdef FLOAT_MIXENG
- .r = 1.0,
- .l = 1.0,
-#else
- .r = 1ULL << 32,
- .l = 1ULL << 32,
-#endif
-};
-
-int audio_bug (const char *funcname, int cond)
-{
- if (cond) {
- static int shown;
-
- AUD_log (NULL, "A bug was just triggered in %s\n", funcname);
- if (!shown) {
- shown = 1;
- AUD_log (NULL, "Save all your work and restart without audio\n");
- AUD_log (NULL, "I am sorry\n");
- }
- AUD_log (NULL, "Context:\n");
- }
-
- return cond;
-}
-
-static inline int audio_bits_to_index (int bits)
-{
- switch (bits) {
- case 8:
- return 0;
-
- case 16:
- return 1;
-
- case 32:
- return 2;
-
- default:
- audio_bug ("bits_to_index", 1);
- AUD_log (NULL, "invalid bits %d\n", bits);
- return 0;
- }
-}
-
-void AUD_vlog (const char *cap, const char *fmt, va_list ap)
-{
- if (cap) {
- fprintf(stderr, "%s: ", cap);
- }
-
- vfprintf(stderr, fmt, ap);
-}
-
-void AUD_log (const char *cap, const char *fmt, ...)
-{
- va_list ap;
-
- va_start (ap, fmt);
- AUD_vlog (cap, fmt, ap);
- va_end (ap);
-}
-
-static void audio_print_settings (const struct audsettings *as)
-{
- dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels);
-
- switch (as->fmt) {
- case AUDIO_FORMAT_S8:
- AUD_log (NULL, "S8");
- break;
- case AUDIO_FORMAT_U8:
- AUD_log (NULL, "U8");
- break;
- case AUDIO_FORMAT_S16:
- AUD_log (NULL, "S16");
- break;
- case AUDIO_FORMAT_U16:
- AUD_log (NULL, "U16");
- break;
- case AUDIO_FORMAT_S32:
- AUD_log (NULL, "S32");
- break;
- case AUDIO_FORMAT_U32:
- AUD_log (NULL, "U32");
- break;
- case AUDIO_FORMAT_F32:
- AUD_log (NULL, "F32");
- break;
- default:
- AUD_log (NULL, "invalid(%d)", as->fmt);
- break;
- }
-
- AUD_log (NULL, " endianness=");
- switch (as->endianness) {
- case 0:
- AUD_log (NULL, "little");
- break;
- case 1:
- AUD_log (NULL, "big");
- break;
- default:
- AUD_log (NULL, "invalid");
- break;
- }
- AUD_log (NULL, "\n");
-}
-
-static int audio_validate_settings (const struct audsettings *as)
-{
- int invalid;
-
- invalid = as->nchannels < 1;
- invalid |= as->endianness != 0 && as->endianness != 1;
-
- switch (as->fmt) {
- case AUDIO_FORMAT_S8:
- case AUDIO_FORMAT_U8:
- case AUDIO_FORMAT_S16:
- case AUDIO_FORMAT_U16:
- case AUDIO_FORMAT_S32:
- case AUDIO_FORMAT_U32:
- case AUDIO_FORMAT_F32:
- break;
- default:
- invalid = 1;
- break;
- }
-
- invalid |= as->freq <= 0;
- return invalid ? -1 : 0;
-}
-
-static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct audsettings *as)
-{
- int bits = 8;
- bool is_signed = false, is_float = false;
-
- switch (as->fmt) {
- case AUDIO_FORMAT_S8:
- is_signed = true;
- /* fall through */
- case AUDIO_FORMAT_U8:
- break;
-
- case AUDIO_FORMAT_S16:
- is_signed = true;
- /* fall through */
- case AUDIO_FORMAT_U16:
- bits = 16;
- break;
-
- case AUDIO_FORMAT_F32:
- is_float = true;
- /* fall through */
- case AUDIO_FORMAT_S32:
- is_signed = true;
- /* fall through */
- case AUDIO_FORMAT_U32:
- bits = 32;
- break;
-
- default:
- abort();
- }
- return info->freq == as->freq
- && info->nchannels == as->nchannels
- && info->is_signed == is_signed
- && info->is_float == is_float
- && info->bits == bits
- && info->swap_endianness == (as->endianness != HOST_BIG_ENDIAN);
-}
-
-void audio_pcm_init_info (struct audio_pcm_info *info, const struct audsettings *as)
-{
- int bits = 8, mul;
- bool is_signed = false, is_float = false;
-
- switch (as->fmt) {
- case AUDIO_FORMAT_S8:
- is_signed = true;
- /* fall through */
- case AUDIO_FORMAT_U8:
- mul = 1;
- break;
-
- case AUDIO_FORMAT_S16:
- is_signed = true;
- /* fall through */
- case AUDIO_FORMAT_U16:
- bits = 16;
- mul = 2;
- break;
-
- case AUDIO_FORMAT_F32:
- is_float = true;
- /* fall through */
- case AUDIO_FORMAT_S32:
- is_signed = true;
- /* fall through */
- case AUDIO_FORMAT_U32:
- bits = 32;
- mul = 4;
- break;
-
- default:
- abort();
- }
-
- info->freq = as->freq;
- info->bits = bits;
- info->is_signed = is_signed;
- info->is_float = is_float;
- info->nchannels = as->nchannels;
- info->bytes_per_frame = as->nchannels * mul;
- info->bytes_per_second = info->freq * info->bytes_per_frame;
- info->swap_endianness = (as->endianness != HOST_BIG_ENDIAN);
-}
-
-void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
-{
- if (!len) {
- return;
- }
-
- if (info->is_signed || info->is_float) {
- memset(buf, 0x00, len * info->bytes_per_frame);
- } else {
- switch (info->bits) {
- case 8:
- memset(buf, 0x80, len * info->bytes_per_frame);
- break;
-
- case 16:
- {
- int i;
- uint16_t *p = buf;
- short s = INT16_MAX;
-
- if (info->swap_endianness) {
- s = bswap16 (s);
- }
-
- for (i = 0; i < len * info->nchannels; i++) {
- p[i] = s;
- }
- }
- break;
-
- case 32:
- {
- int i;
- uint32_t *p = buf;
- int32_t s = INT32_MAX;
-
- if (info->swap_endianness) {
- s = bswap32 (s);
- }
-
- for (i = 0; i < len * info->nchannels; i++) {
- p[i] = s;
- }
- }
- break;
-
- default:
- AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n",
- info->bits);
- break;
- }
- }
-}
-
-/*
- * Capture
- */
-static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioMixengBackend *s,
- struct audsettings *as)
-{
- CaptureVoiceOut *cap;
-
- for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
- if (audio_pcm_info_eq (&cap->hw.info, as)) {
- return cap;
- }
- }
- return NULL;
-}
-
-static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd)
-{
- struct capture_callback *cb;
-
-#ifdef DEBUG_CAPTURE
- dolog ("notification %d sent\n", cmd);
-#endif
- for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
- cb->ops.notify (cb->opaque, cmd);
- }
-}
-
-static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled)
-{
- if (cap->hw.enabled != enabled) {
- audcnotification_e cmd;
- cap->hw.enabled = enabled;
- cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE;
- audio_notify_capture (cap, cmd);
- }
-}
-
-static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap)
-{
- HWVoiceOut *hw = &cap->hw;
- SWVoiceOut *sw;
- bool enabled = false;
-
- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
- if (sw->active) {
- enabled = true;
- break;
- }
- }
- audio_capture_maybe_changed (cap, enabled);
-}
-
-static void audio_detach_capture (HWVoiceOut *hw)
-{
- SWVoiceCap *sc = hw->cap_head.lh_first;
-
- while (sc) {
- SWVoiceCap *sc1 = sc->entries.le_next;
- SWVoiceOut *sw = &sc->sw;
- CaptureVoiceOut *cap = sc->cap;
- int was_active = sw->active;
-
- if (sw->rate) {
- st_rate_stop (sw->rate);
- sw->rate = NULL;
- }
-
- QLIST_REMOVE (sw, entries);
- QLIST_REMOVE (sc, entries);
- g_free (sc);
- if (was_active) {
- /* We have removed soft voice from the capture:
- this might have changed the overall status of the capture
- since this might have been the only active voice */
- audio_recalc_and_notify_capture (cap);
- }
- sc = sc1;
- }
-}
-
-static int audio_attach_capture (HWVoiceOut *hw)
-{
- AudioMixengBackend *s = hw->s;
- CaptureVoiceOut *cap;
-
- audio_detach_capture (hw);
- for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
- SWVoiceCap *sc;
- SWVoiceOut *sw;
- HWVoiceOut *hw_cap = &cap->hw;
-
- sc = g_malloc0(sizeof(*sc));
-
- sc->cap = cap;
- sw = &sc->sw;
- sw->hw = hw_cap;
- sw->info = hw->info;
- sw->empty = true;
- sw->active = hw->enabled;
- sw->vol = nominal_volume;
- sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
- QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
- QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);
-#ifdef DEBUG_CAPTURE
- sw->name = g_strdup_printf ("for %p %d,%d,%d",
- hw, sw->info.freq, sw->info.bits,
- sw->info.nchannels);
- dolog ("Added %s active = %d\n", sw->name, sw->active);
-#endif
- if (sw->active) {
- audio_capture_maybe_changed (cap, 1);
- }
- }
- return 0;
-}
-
-/*
- * Hard voice (capture)
- */
-static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)
-{
- SWVoiceIn *sw;
- size_t m = hw->total_samples_captured;
-
- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
- if (sw->active) {
- m = MIN (m, sw->total_hw_samples_acquired);
- }
- }
- return m;
-}
-
-static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
-{
- size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
- if (audio_bug(__func__, live > hw->conv_buf.size)) {
- dolog("live=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
- return 0;
- }
- return live;
-}
-
-static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
-{
- size_t conv = 0;
- STSampleBuffer *conv_buf = &hw->conv_buf;
-
- while (samples) {
- uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);
- size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);
-
- hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);
- conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
- samples -= proc;
- conv += proc;
- }
-
- return conv;
-}
-
-/*
- * Soft voice (capture)
- */
-static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
- size_t frames_in_max, size_t frames_out_max,
- size_t *total_in, size_t *total_out)
-{
- HWVoiceIn *hw = sw->hw;
- struct st_sample *src, *dst;
- size_t live, rpos, frames_in, frames_out;
-
- live = hw->total_samples_captured - sw->total_hw_samples_acquired;
- rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
-
- /* resample conv_buf from rpos to end of buffer */
- src = hw->conv_buf.buffer + rpos;
- frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos);
- dst = sw->resample_buf.buffer;
- frames_out = frames_out_max;
- st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
- rpos += frames_in;
- *total_in = frames_in;
- *total_out = frames_out;
-
- /* resample conv_buf from start of buffer if there are input frames left */
- if (frames_in_max - frames_in && rpos == hw->conv_buf.size) {
- src = hw->conv_buf.buffer;
- frames_in = frames_in_max - frames_in;
- dst += frames_out;
- frames_out = frames_out_max - frames_out;
- st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
- *total_in += frames_in;
- *total_out += frames_out;
- }
-}
-
-static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
-{
- HWVoiceIn *hw = sw->hw;
- size_t live, frames_out_max, total_in, total_out;
-
- live = hw->total_samples_captured - sw->total_hw_samples_acquired;
- if (!live) {
- return 0;
- }
- if (audio_bug(__func__, live > hw->conv_buf.size)) {
- dolog("live_in=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
- return 0;
- }
-
- frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,
- sw->resample_buf.size);
-
- audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);
-
- if (!hw->pcm_ops->volume_in) {
- mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);
- }
- sw->clip(buf, sw->resample_buf.buffer, total_out);
-
- sw->total_hw_samples_acquired += total_in;
- return total_out * sw->info.bytes_per_frame;
-}
-
-/*
- * Hard voice (playback)
- */
-static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)
-{
- SWVoiceOut *sw;
- size_t m = SIZE_MAX;
- int nb_live = 0;
-
- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
- if (sw->active || !sw->empty) {
- m = MIN (m, sw->total_hw_samples_mixed);
- nb_live += 1;
- }
- }
-
- *nb_livep = nb_live;
- return m;
-}
-
-static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)
-{
- size_t smin;
- int nb_live1;
-
- smin = audio_pcm_hw_find_min_out (hw, &nb_live1);
- if (nb_live) {
- *nb_live = nb_live1;
- }
-
- if (nb_live1) {
- size_t live = smin;
-
- if (audio_bug(__func__, live > hw->mix_buf.size)) {
- dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
- return 0;
- }
- return live;
- }
- return 0;
-}
-
-static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)
-{
- return (hw->pcm_ops->buffer_get_free ? hw->pcm_ops->buffer_get_free(hw) :
- INT_MAX) / hw->info.bytes_per_frame;
-}
-
-static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
-{
- size_t clipped = 0;
- size_t pos = hw->mix_buf.pos;
-
- while (len) {
- st_sample *src = hw->mix_buf.buffer + pos;
- 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);
-
- hw->clip(dst, src, samples_to_clip);
-
- pos = (pos + samples_to_clip) % hw->mix_buf.size;
- len -= samples_to_clip;
- clipped += samples_to_clip;
- }
-}
-
-/*
- * Soft voice (playback)
- */
-static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
- size_t frames_in_max, size_t frames_out_max,
- size_t *total_in, size_t *total_out)
-{
- HWVoiceOut *hw = sw->hw;
- struct st_sample *src, *dst;
- size_t live, wpos, frames_in, frames_out;
-
- live = sw->total_hw_samples_mixed;
- wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;
-
- /* write to mix_buf from wpos to end of buffer */
- src = sw->resample_buf.buffer;
- frames_in = frames_in_max;
- dst = hw->mix_buf.buffer + wpos;
- frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);
- st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
- wpos += frames_out;
- *total_in = frames_in;
- *total_out = frames_out;
-
- /* write to mix_buf from start of buffer if there are input frames left */
- if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {
- src += frames_in;
- frames_in = frames_in_max - frames_in;
- dst = hw->mix_buf.buffer;
- frames_out = frames_out_max - frames_out;
- st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
- *total_in += frames_in;
- *total_out += frames_out;
- }
-}
-
-static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
-{
- HWVoiceOut *hw = sw->hw;
- size_t live, dead, hw_free, sw_max, fe_max;
- size_t frames_in_max, frames_out_max, total_in, total_out;
-
- live = sw->total_hw_samples_mixed;
- if (audio_bug(__func__, live > hw->mix_buf.size)) {
- dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
- return 0;
- }
-
- if (live == hw->mix_buf.size) {
-#ifdef DEBUG_OUT
- dolog ("%s is full %zu\n", sw->name, live);
-#endif
- return 0;
- }
-
- dead = hw->mix_buf.size - live;
- hw_free = audio_pcm_hw_get_free(hw);
- hw_free = hw_free > live ? hw_free - live : 0;
- frames_out_max = MIN(dead, hw_free);
- sw_max = st_rate_frames_in(sw->rate, frames_out_max);
- fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,
- sw->resample_buf.size);
- frames_in_max = MIN(sw_max, fe_max);
-
- if (!frames_in_max) {
- return 0;
- }
-
- if (frames_in_max > sw->resample_buf.pos) {
- sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,
- buf, frames_in_max - sw->resample_buf.pos);
- if (!sw->hw->pcm_ops->volume_out) {
- mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,
- frames_in_max - sw->resample_buf.pos, &sw->vol);
- }
- }
-
- audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,
- &total_in, &total_out);
-
- sw->total_hw_samples_mixed += total_out;
- sw->empty = sw->total_hw_samples_mixed == 0;
-
- /*
- * Upsampling may leave one audio frame in the resample buffer. Decrement
- * total_in by one if there was a leftover frame from the previous resample
- * pass in the resample buffer. Increment total_in by one if the current
- * resample pass left one frame in the resample buffer.
- */
- if (frames_in_max - total_in == 1) {
- /* copy one leftover audio frame to the beginning of the buffer */
- *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);
- total_in += 1 - sw->resample_buf.pos;
- sw->resample_buf.pos = 1;
- } else if (total_in >= sw->resample_buf.pos) {
- total_in -= sw->resample_buf.pos;
- sw->resample_buf.pos = 0;
- }
-
-#ifdef DEBUG_OUT
- dolog (
- "%s: write size %zu written %zu total mixed %zu\n",
- SW_NAME(sw),
- buf_len / sw->info.bytes_per_frame,
- total_in,
- sw->total_hw_samples_mixed
- );
-#endif
-
- return total_in * sw->info.bytes_per_frame;
-}
-
-#ifdef DEBUG_AUDIO
-static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
-{
- dolog("%s: bits %d, sign %d, float %d, freq %d, nchan %d\n",
- cap, info->bits, info->is_signed, info->is_float, info->freq,
- info->nchannels);
-}
-#endif
-
-#define DAC
-#include "audio_template.h"
-#undef DAC
-#include "audio_template.h"
-
-/*
- * Timer
- */
-static int audio_is_timer_needed(AudioMixengBackend *s)
-{
- HWVoiceIn *hwi = NULL;
- HWVoiceOut *hwo = NULL;
-
- while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
- if (!hwo->poll_mode) {
- return 1;
- }
- }
- while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
- if (!hwi->poll_mode) {
- return 1;
- }
- }
- return 0;
-}
-
-static void audio_reset_timer(AudioMixengBackend *s)
-{
- if (audio_is_timer_needed(s)) {
- timer_mod_anticipate_ns(s->ts,
- qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
- if (!s->timer_running) {
- s->timer_running = true;
- s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- trace_audio_timer_start(s->period_ticks / SCALE_MS);
- }
- } else {
- timer_del(s->ts);
- if (s->timer_running) {
- s->timer_running = false;
- trace_audio_timer_stop();
- }
- }
-}
-
-static void audio_timer (void *opaque)
-{
- int64_t now, diff;
- AudioMixengBackend *s = opaque;
-
- now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- diff = now - s->timer_last;
- if (diff > s->period_ticks * 3 / 2) {
- trace_audio_timer_delayed(diff / SCALE_MS);
- }
- s->timer_last = now;
-
- audio_run(s, "timer");
- audio_reset_timer(s);
-}
-
-/*
- * Public API
- */
-static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw,
- void *buf, size_t size)
-{
- HWVoiceOut *hw;
-
- if (!sw) {
- /* XXX: Consider options */
- return size;
- }
- hw = sw->hw;
-
- if (!hw->enabled) {
- dolog("Writing to disabled voice %s\n", SW_NAME(sw));
- return 0;
- }
-
- 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);
- }
-}
-
-static size_t audio_mixeng_backend_read(AudioBackend *be,
- SWVoiceIn *sw, void *buf, size_t size)
-{
- HWVoiceIn *hw;
-
- if (!sw) {
- /* XXX: Consider options */
- return size;
- }
- hw = sw->hw;
-
- if (!hw->enabled) {
- dolog("Reading from disabled voice %s\n", SW_NAME(sw));
- return 0;
- }
-
- 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);
- }
-
-}
-
-static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
-{
- if (!sw) {
- return 0;
- }
-
- if (audio_get_pdo_out(sw->s->dev)->mixing_engine) {
- return sw->resample_buf.size * sw->info.bytes_per_frame;
- }
-
- return sw->hw->samples * sw->hw->info.bytes_per_frame;
-}
-
-static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceOut *sw,
- bool on)
-{
- HWVoiceOut *hw;
-
- if (!sw) {
- return;
- }
-
- hw = sw->hw;
- if (sw->active != on) {
- AudioMixengBackend *s = sw->s;
- SWVoiceOut *temp_sw;
- SWVoiceCap *sc;
-
- if (on) {
- hw->pending_disable = 0;
- if (!hw->enabled) {
- hw->enabled = true;
- if (runstate_is_running()) {
- if (hw->pcm_ops->enable_out) {
- hw->pcm_ops->enable_out(hw, true);
- }
- audio_reset_timer (s);
- }
- }
- } else {
- if (hw->enabled) {
- int nb_active = 0;
-
- for (temp_sw = hw->sw_head.lh_first; temp_sw;
- temp_sw = temp_sw->entries.le_next) {
- nb_active += temp_sw->active != 0;
- }
-
- hw->pending_disable = nb_active == 1;
- }
- }
-
- for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
- sc->sw.active = hw->enabled;
- if (hw->enabled) {
- audio_capture_maybe_changed (sc->cap, 1);
- }
- }
- sw->active = on;
- }
-
-}
-
-static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
-{
- HWVoiceIn *hw;
-
- if (!sw) {
- return;
- }
-
- hw = sw->hw;
- if (sw->active != on) {
- AudioMixengBackend *s = sw->s;
- SWVoiceIn *temp_sw;
-
- if (on) {
- if (!hw->enabled) {
- hw->enabled = true;
- if (runstate_is_running()) {
- if (hw->pcm_ops->enable_in) {
- hw->pcm_ops->enable_in(hw, true);
- }
- audio_reset_timer (s);
- }
- }
- sw->total_hw_samples_acquired = hw->total_samples_captured;
- } else {
- if (hw->enabled) {
- int nb_active = 0;
-
- for (temp_sw = hw->sw_head.lh_first; temp_sw;
- temp_sw = temp_sw->entries.le_next) {
- nb_active += temp_sw->active != 0;
- }
-
- if (nb_active == 1) {
- hw->enabled = false;
- if (hw->pcm_ops->enable_in) {
- hw->pcm_ops->enable_in(hw, false);
- }
- }
- }
- }
- sw->active = on;
- }
-}
-
-static size_t audio_get_avail(SWVoiceIn *sw)
-{
- size_t live;
-
- if (!sw) {
- return 0;
- }
-
- live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
- if (audio_bug(__func__, live > sw->hw->conv_buf.size)) {
- dolog("live=%zu sw->hw->conv_buf.size=%zu\n", live,
- sw->hw->conv_buf.size);
- return 0;
- }
-
- ldebug (
- "%s: get_avail live %zu frontend frames %u\n",
- SW_NAME (sw),
- live, st_rate_frames_out(sw->rate, live)
- );
-
- return live;
-}
-
-static size_t audio_get_free(SWVoiceOut *sw)
-{
- size_t live, dead;
-
- if (!sw) {
- return 0;
- }
-
- live = sw->total_hw_samples_mixed;
-
- if (audio_bug(__func__, live > sw->hw->mix_buf.size)) {
- dolog("live=%zu sw->hw->mix_buf.size=%zu\n", live,
- sw->hw->mix_buf.size);
- return 0;
- }
-
- dead = sw->hw->mix_buf.size - live;
-
-#ifdef DEBUG_OUT
- dolog("%s: get_free live %zu dead %zu frontend frames %u\n",
- SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead));
-#endif
-
- return dead;
-}
-
-static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
- size_t samples)
-{
- size_t n;
-
- if (hw->enabled) {
- SWVoiceCap *sc;
-
- for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
- SWVoiceOut *sw = &sc->sw;
- size_t rpos2 = rpos;
-
- n = samples;
- while (n) {
- size_t till_end_of_hw = hw->mix_buf.size - rpos2;
- size_t to_read = MIN(till_end_of_hw, n);
- size_t live, frames_in, frames_out;
-
- sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;
- sw->resample_buf.size = to_read;
- live = sw->total_hw_samples_mixed;
-
- audio_pcm_sw_resample_out(sw,
- to_read, sw->hw->mix_buf.size - live,
- &frames_in, &frames_out);
-
- sw->total_hw_samples_mixed += frames_out;
- sw->empty = sw->total_hw_samples_mixed == 0;
-
- if (to_read - frames_in) {
- dolog("Could not mix %zu frames into a capture "
- "buffer, mixed %zu\n",
- to_read, frames_in);
- break;
- }
- n -= to_read;
- rpos2 = (rpos2 + to_read) % hw->mix_buf.size;
- }
- }
- }
-
- n = MIN(samples, hw->mix_buf.size - rpos);
- mixeng_clear(hw->mix_buf.buffer + rpos, n);
- mixeng_clear(hw->mix_buf.buffer, samples - n);
-}
-
-static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
-{
- size_t clipped = 0;
-
- while (live) {
- size_t size = live * hw->info.bytes_per_frame;
- size_t decr, proc;
- void *buf = hw->pcm_ops->get_buffer_out(hw, &size);
-
- if (size == 0) {
- break;
- }
-
- decr = MIN(size / hw->info.bytes_per_frame, live);
- if (buf) {
- audio_pcm_hw_clip_out(hw, buf, decr);
- }
- proc = hw->pcm_ops->put_buffer_out(hw, buf,
- decr * hw->info.bytes_per_frame) /
- hw->info.bytes_per_frame;
-
- live -= proc;
- clipped += proc;
- hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size;
-
- if (proc == 0 || proc < decr) {
- break;
- }
- }
-
- if (hw->pcm_ops->run_buffer_out) {
- hw->pcm_ops->run_buffer_out(hw);
- }
-
- return clipped;
-}
-
-static void audio_run_out(AudioMixengBackend *s)
-{
- HWVoiceOut *hw = NULL;
- SWVoiceOut *sw;
-
- while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
- size_t played, live, prev_rpos;
- size_t hw_free = audio_pcm_hw_get_free(hw);
- int nb_live;
-
- if (!audio_get_pdo_out(s->dev)->mixing_engine) {
- /* there is exactly 1 sw for each hw with no mixeng */
- sw = hw->sw_head.lh_first;
-
- if (hw->pending_disable) {
- hw->enabled = false;
- hw->pending_disable = false;
- if (hw->pcm_ops->enable_out) {
- hw->pcm_ops->enable_out(hw, false);
- }
- }
-
- if (sw->active) {
- sw->callback.fn(sw->callback.opaque,
- hw_free * sw->info.bytes_per_frame);
- }
-
- if (hw->pcm_ops->run_buffer_out) {
- hw->pcm_ops->run_buffer_out(hw);
- }
-
- continue;
- }
-
- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
- if (sw->active) {
- size_t sw_free = audio_get_free(sw);
- size_t free;
-
- if (hw_free > sw->total_hw_samples_mixed) {
- free = st_rate_frames_in(sw->rate,
- MIN(sw_free, hw_free - sw->total_hw_samples_mixed));
- } else {
- free = 0;
- }
- if (free > sw->resample_buf.pos) {
- free = MIN(free, sw->resample_buf.size)
- - sw->resample_buf.pos;
- sw->callback.fn(sw->callback.opaque,
- free * sw->info.bytes_per_frame);
- }
- }
- }
-
- live = audio_pcm_hw_get_live_out (hw, &nb_live);
- if (!nb_live) {
- live = 0;
- }
-
- if (audio_bug(__func__, live > hw->mix_buf.size)) {
- dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
- continue;
- }
-
- if (hw->pending_disable && !nb_live) {
- SWVoiceCap *sc;
-#ifdef DEBUG_OUT
- dolog ("Disabling voice\n");
-#endif
- hw->enabled = false;
- hw->pending_disable = false;
- if (hw->pcm_ops->enable_out) {
- hw->pcm_ops->enable_out(hw, false);
- }
- for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
- sc->sw.active = false;
- audio_recalc_and_notify_capture (sc->cap);
- }
- continue;
- }
-
- if (!live) {
- if (hw->pcm_ops->run_buffer_out) {
- hw->pcm_ops->run_buffer_out(hw);
- }
- continue;
- }
-
- prev_rpos = hw->mix_buf.pos;
- played = audio_pcm_hw_run_out(hw, live);
- replay_audio_out(&played);
- if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) {
- dolog("hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\n",
- hw->mix_buf.pos, hw->mix_buf.size, played);
- hw->mix_buf.pos = 0;
- }
-
-#ifdef DEBUG_OUT
- dolog("played=%zu\n", played);
-#endif
-
- if (played) {
- audio_capture_mix_and_clear (hw, prev_rpos, played);
- }
-
- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
- if (!sw->active && sw->empty) {
- continue;
- }
-
- if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) {
- dolog("played=%zu sw->total_hw_samples_mixed=%zu\n",
- played, sw->total_hw_samples_mixed);
- played = sw->total_hw_samples_mixed;
- }
-
- sw->total_hw_samples_mixed -= played;
-
- if (!sw->total_hw_samples_mixed) {
- sw->empty = true;
- }
- }
- }
-}
-
-static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
-{
- size_t conv = 0;
-
- if (hw->pcm_ops->run_buffer_in) {
- hw->pcm_ops->run_buffer_in(hw);
- }
-
- while (samples) {
- size_t proc;
- size_t size = samples * hw->info.bytes_per_frame;
- void *buf = hw->pcm_ops->get_buffer_in(hw, &size);
-
- assert(size % hw->info.bytes_per_frame == 0);
- if (size == 0) {
- break;
- }
-
- proc = audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_frame);
-
- samples -= proc;
- conv += proc;
- hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame);
- }
-
- return conv;
-}
-
-static void audio_run_in(AudioMixengBackend *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;
- int pos;
-
- if (replay_mode != REPLAY_MODE_PLAY) {
- captured = audio_pcm_hw_run_in(
- hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw));
- }
-
- replay_audio_in_start(&captured);
- assert(captured <= hw->conv_buf.size);
- if (replay_mode == REPLAY_MODE_PLAY) {
- hw->conv_buf.pos = (hw->conv_buf.pos + captured) % hw->conv_buf.size;
- }
- for (pos = (hw->conv_buf.pos - captured + hw->conv_buf.size) % hw->conv_buf.size;
- pos != hw->conv_buf.pos;
- pos = (pos + 1) % hw->conv_buf.size) {
- uint64_t left, right;
-
- if (replay_mode == REPLAY_MODE_RECORD) {
- audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left, &right);
- }
- replay_audio_in_sample_lr(&left, &right);
- if (replay_mode == REPLAY_MODE_PLAY) {
- audio_sample_from_uint64(hw->conv_buf.buffer, pos, left, right);
- }
- }
- replay_audio_in_finish();
-
- min = audio_pcm_hw_find_min_in (hw);
- hw->total_samples_captured += captured - min;
-
- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
- sw->total_hw_samples_acquired -= min;
-
- if (sw->active) {
- size_t sw_avail = audio_get_avail(sw);
- size_t avail;
-
- avail = st_rate_frames_out(sw->rate, sw_avail);
- if (avail > 0) {
- avail = MIN(avail, sw->resample_buf.size);
- sw->callback.fn(sw->callback.opaque,
- avail * sw->info.bytes_per_frame);
- }
- }
- }
- }
-}
-
-static void audio_run_capture(AudioMixengBackend *s)
-{
- CaptureVoiceOut *cap;
-
- for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
- size_t live, rpos, captured;
- HWVoiceOut *hw = &cap->hw;
- SWVoiceOut *sw;
-
- captured = live = audio_pcm_hw_get_live_out (hw, NULL);
- rpos = hw->mix_buf.pos;
- while (live) {
- size_t left = hw->mix_buf.size - rpos;
- size_t to_capture = MIN(live, left);
- struct st_sample *src;
- struct capture_callback *cb;
-
- src = hw->mix_buf.buffer + rpos;
- hw->clip (cap->buf, src, to_capture);
- mixeng_clear (src, to_capture);
-
- for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
- cb->ops.capture (cb->opaque, cap->buf,
- to_capture * hw->info.bytes_per_frame);
- }
- rpos = (rpos + to_capture) % hw->mix_buf.size;
- live -= to_capture;
- }
- hw->mix_buf.pos = rpos;
-
- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
- if (!sw->active && sw->empty) {
- continue;
- }
-
- if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)) {
- dolog("captured=%zu sw->total_hw_samples_mixed=%zu\n",
- captured, sw->total_hw_samples_mixed);
- captured = sw->total_hw_samples_mixed;
- }
-
- sw->total_hw_samples_mixed -= captured;
- sw->empty = sw->total_hw_samples_mixed == 0;
- }
- }
-}
-
-void audio_run(AudioMixengBackend *s, const char *msg)
-{
- audio_run_out(s);
- audio_run_in(s);
- audio_run_capture(s);
-
-#ifdef DEBUG_POLL
- {
- static double prevtime;
- double currtime;
- struct timeval tv;
-
- if (gettimeofday (&tv, NULL)) {
- perror ("audio_run: gettimeofday");
- return;
- }
-
- currtime = tv.tv_sec + tv.tv_usec * 1e-6;
- dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime);
- prevtime = currtime;
- }
-#endif
-}
-
-void audio_generic_run_buffer_in(HWVoiceIn *hw)
-{
- if (unlikely(!hw->buf_emul)) {
- hw->size_emul = hw->samples * hw->info.bytes_per_frame;
- hw->buf_emul = g_malloc(hw->size_emul);
- hw->pos_emul = hw->pending_emul = 0;
- }
-
- while (hw->pending_emul < hw->size_emul) {
- size_t read_len = MIN(hw->size_emul - hw->pos_emul,
- hw->size_emul - hw->pending_emul);
- size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul,
- read_len);
- hw->pending_emul += read;
- hw->pos_emul = (hw->pos_emul + read) % hw->size_emul;
- if (read < read_len) {
- break;
- }
- }
-}
-
-void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)
-{
- size_t start;
-
- start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
- assert(start < hw->size_emul);
-
- *size = MIN(*size, hw->pending_emul);
- *size = MIN(*size, hw->size_emul - start);
- return hw->buf_emul + start;
-}
-
-void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
-{
- assert(size <= hw->pending_emul);
- hw->pending_emul -= size;
-}
-
-size_t audio_generic_buffer_get_free(HWVoiceOut *hw)
-{
- if (hw->buf_emul) {
- return hw->size_emul - hw->pending_emul;
- } else {
- return hw->samples * hw->info.bytes_per_frame;
- }
-}
-
-void audio_generic_run_buffer_out(HWVoiceOut *hw)
-{
- while (hw->pending_emul) {
- size_t write_len, written, start;
-
- start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
- assert(start < hw->size_emul);
-
- write_len = MIN(hw->pending_emul, hw->size_emul - start);
-
- written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len);
- hw->pending_emul -= written;
-
- if (written < write_len) {
- break;
- }
- }
-}
-
-void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)
-{
- if (unlikely(!hw->buf_emul)) {
- hw->size_emul = hw->samples * hw->info.bytes_per_frame;
- hw->buf_emul = g_malloc(hw->size_emul);
- hw->pos_emul = hw->pending_emul = 0;
- }
-
- *size = MIN(hw->size_emul - hw->pending_emul,
- hw->size_emul - hw->pos_emul);
- return hw->buf_emul + hw->pos_emul;
-}
-
-size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
-{
- assert(buf == hw->buf_emul + hw->pos_emul &&
- size + hw->pending_emul <= hw->size_emul);
-
- hw->pending_emul += size;
- hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;
-
- return size;
-}
-
-size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size)
-{
- size_t total = 0;
-
- if (hw->pcm_ops->buffer_get_free) {
- size_t free = hw->pcm_ops->buffer_get_free(hw);
-
- size = MIN(size, free);
- }
-
- while (total < size) {
- size_t dst_size = size - total;
- size_t copy_size, proc;
- void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size);
-
- if (dst_size == 0) {
- break;
- }
-
- copy_size = MIN(size - total, dst_size);
- if (dst) {
- memcpy(dst, (char *)buf + total, copy_size);
- }
- proc = hw->pcm_ops->put_buffer_out(hw, dst, copy_size);
- total += proc;
-
- if (proc == 0 || proc < copy_size) {
- break;
- }
- }
-
- return total;
-}
-
-size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
-{
- size_t total = 0;
-
- if (hw->pcm_ops->run_buffer_in) {
- hw->pcm_ops->run_buffer_in(hw);
- }
-
- while (total < size) {
- size_t src_size = size - total;
- void *src = hw->pcm_ops->get_buffer_in(hw, &src_size);
-
- if (src_size == 0) {
- break;
- }
-
- memcpy((char *)buf + total, src, src_size);
- hw->pcm_ops->put_buffer_in(hw, src, src_size);
- total += src_size;
- }
-
- return total;
-}
-
-static bool audio_mixeng_backend_realize(AudioBackend *abe,
- Audiodev *dev, Error **errp)
-{
- AudioMixengBackend *be = AUDIO_MIXENG_BACKEND(abe);
- audio_driver *drv = AUDIO_MIXENG_BACKEND_GET_CLASS(be)->driver;
-
- be->dev = dev;
- be->drv_opaque = drv->init(be->dev, errp);
- if (!be->drv_opaque) {
- return false;
- }
-
- if (!drv->pcm_ops->get_buffer_in) {
- drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in;
- drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in;
- }
- if (!drv->pcm_ops->get_buffer_out) {
- drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out;
- drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;
- }
-
- audio_init_nb_voices_out(be, drv, 1);
- audio_init_nb_voices_in(be, drv, 0);
- be->drv = drv;
-
- if (be->dev->timer_period <= 0) {
- be->period_ticks = 1;
- } else {
- be->period_ticks = be->dev->timer_period * (int64_t)SCALE_US;
- }
-
- return true;
-}
-
-static void audio_vm_change_state_handler (void *opaque, bool running,
- RunState state)
-{
- AudioMixengBackend *s = opaque;
- HWVoiceOut *hwo = NULL;
- HWVoiceIn *hwi = NULL;
-
- while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
- if (hwo->pcm_ops->enable_out) {
- hwo->pcm_ops->enable_out(hwo, running);
- }
- }
-
- while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
- if (hwi->pcm_ops->enable_in) {
- hwi->pcm_ops->enable_in(hwi, running);
- }
- }
- audio_reset_timer (s);
-}
-
-static const VMStateDescription vmstate_audio;
-
-static const char *audio_mixeng_backend_get_id(AudioBackend *be)
-{
- return AUDIO_MIXENG_BACKEND(be)->dev->id;
-}
-
-static CaptureVoiceOut *audio_mixeng_backend_add_capture(
- AudioBackend *be,
- struct audsettings *as,
- struct audio_capture_ops *ops,
- void *cb_opaque);
-
-static void audio_mixeng_backend_del_capture(
- AudioBackend *be,
- CaptureVoiceOut *cap,
- void *cb_opaque);
-
-static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
- Volume *vol);
-static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
- Volume *vol);
-
-static void audio_mixeng_backend_class_init(ObjectClass *klass, const void *data)
-{
- AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
-
- be->realize = audio_mixeng_backend_realize;
- be->get_id = audio_mixeng_backend_get_id;
- be->open_in = audio_mixeng_backend_open_in;
- be->open_out = audio_mixeng_backend_open_out;
- be->close_in = audio_mixeng_backend_close_in;
- be->close_out = audio_mixeng_backend_close_out;
- be->is_active_out = audio_mixeng_backend_is_active_out;
- be->is_active_in = audio_mixeng_backend_is_active_in;
- be->set_active_out = audio_mixeng_backend_set_active_out;
- be->set_active_in = audio_mixeng_backend_set_active_in;
- be->set_volume_out = audio_mixeng_backend_set_volume_out;
- be->set_volume_in = audio_mixeng_backend_set_volume_in;
- be->read = audio_mixeng_backend_read;
- be->write = audio_mixeng_backend_write;
- be->get_buffer_size_out = audio_mixeng_backend_get_buffer_size_out;
- be->add_capture = audio_mixeng_backend_add_capture;
- be->del_capture = audio_mixeng_backend_del_capture;
-}
-
-static void audio_mixeng_backend_init(Object *obj)
-{
- AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
-
- QLIST_INIT(&s->hw_head_out);
- QLIST_INIT(&s->hw_head_in);
- QLIST_INIT(&s->cap_head);
- s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
-
- s->vmse = qemu_add_vm_change_state_handler(audio_vm_change_state_handler, s);
- assert(s->vmse != NULL);
-
- vmstate_register_any(NULL, &vmstate_audio, s);
-}
-
-static void audio_mixeng_backend_finalize(Object *obj)
-{
- AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
- HWVoiceOut *hwo, *hwon;
- HWVoiceIn *hwi, *hwin;
-
- QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) {
- SWVoiceCap *sc;
-
- if (hwo->enabled && hwo->pcm_ops->enable_out) {
- hwo->pcm_ops->enable_out(hwo, false);
- }
- hwo->pcm_ops->fini_out (hwo);
+/* SPDX-License-Identifier: MIT */
- for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) {
- CaptureVoiceOut *cap = sc->cap;
- struct capture_callback *cb;
+#include "qemu/osdep.h"
+#include "qemu/audio.h"
+#include "qemu/help_option.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qapi/qapi-visit-audio.h"
+#include "qapi/qapi-commands-audio.h"
+#include "qobject/qdict.h"
+#include "system/system.h"
- for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
- cb->ops.destroy (cb->opaque);
- }
- }
- QLIST_REMOVE(hwo, entries);
- }
+/* Order of CONFIG_AUDIO_DRIVERS is import.
+ The 1st one is the one used by default, that is the reason
+ that we generate the list.
+*/
+const char *audio_prio_list[] = {
+#ifdef CONFIG_GIO
+ "dbus",
+#endif
+ "spice",
+ CONFIG_AUDIO_DRIVERS
+ "none",
+ NULL
+};
- QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) {
- if (hwi->enabled && hwi->pcm_ops->enable_in) {
- hwi->pcm_ops->enable_in(hwi, false);
- }
- hwi->pcm_ops->fini_in (hwi);
- QLIST_REMOVE(hwi, entries);
- }
+typedef struct AudiodevListEntry {
+ Audiodev *dev;
+ QSIMPLEQ_ENTRY(AudiodevListEntry) next;
+} AudiodevListEntry;
- if (s->drv) {
- s->drv->fini (s->drv_opaque);
- s->drv = NULL;
- }
+typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;
- if (s->dev) {
- qapi_free_Audiodev(s->dev);
- s->dev = NULL;
- }
+static AudiodevListHead audiodevs =
+ QSIMPLEQ_HEAD_INITIALIZER(audiodevs);
+static AudiodevListHead default_audiodevs =
+ QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);
- if (s->ts) {
- timer_free(s->ts);
- s->ts = NULL;
- }
+static AudioBackendClass *audio_be_class_by_name(const char *name)
+{
+ g_autofree char *tname = g_strconcat("audio-", name, NULL);
+ ObjectClass *oc = module_object_class_by_name(tname);
- if (s->vmse) {
- qemu_del_vm_change_state_handler(s->vmse);
- s->vmse = NULL;
+ if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) {
+ return NULL;
}
- vmstate_unregister(NULL, &vmstate_audio, s);
+ return AUDIO_BACKEND_CLASS(oc);
}
+static AudioBackend *default_audio_be;
+
static Object *get_audiodevs_root(void)
{
return object_get_container("audiodevs");
@@ -1757,25 +62,6 @@ void audio_cleanup(void)
object_unparent(get_audiodevs_root());
}
-static bool vmstate_audio_needed(void *opaque)
-{
- /*
- * Never needed, this vmstate only exists in case
- * an old qemu sends it to us.
- */
- return false;
-}
-
-static const VMStateDescription vmstate_audio = {
- .name = "audio",
- .version_id = 1,
- .minimum_version_id = 1,
- .needed = vmstate_audio_needed,
- .fields = (const VMStateField[]) {
- VMSTATE_END_OF_LIST()
- }
-};
-
void audio_create_default_audiodevs(void)
{
for (int i = 0; audio_prio_list[i]; i++) {
@@ -1816,15 +102,13 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
assert(!default_audio_be);
for (;;) {
AudiodevListEntry *e = QSIMPLEQ_FIRST(&default_audiodevs);
-
if (!e) {
error_setg(errp, "no default audio driver available");
return NULL;
}
- dev = e->dev;
QSIMPLEQ_REMOVE_HEAD(&default_audiodevs, next);
+ be = audio_be_new(e->dev, NULL);
g_free(e);
- be = audio_be_new(dev, NULL);
if (be) {
break;
}
@@ -1855,156 +139,35 @@ AudioBackend *audio_get_default_audio_be(Error **errp)
return default_audio_be;
}
-static struct audio_pcm_ops capture_pcm_ops;
-
-static CaptureVoiceOut *audio_mixeng_backend_add_capture(
- AudioBackend *be,
- struct audsettings *as,
- struct audio_capture_ops *ops,
- void *cb_opaque)
-{
- AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(be);
- CaptureVoiceOut *cap;
- struct capture_callback *cb;
-
- if (!s) {
- error_report("Capturing without setting an audiodev is not supported");
- abort();
- }
-
- 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);
- return NULL;
- }
-
- cb = g_malloc0(sizeof(*cb));
- cb->ops = *ops;
- cb->opaque = cb_opaque;
-
- cap = audio_pcm_capture_find_specific(s, as);
- if (cap) {
- QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
- } else {
- HWVoiceOut *hw;
-
- cap = g_malloc0(sizeof(*cap));
-
- hw = &cap->hw;
- hw->s = s;
- hw->pcm_ops = &capture_pcm_ops;
- QLIST_INIT (&hw->sw_head);
- QLIST_INIT (&cap->cb_head);
-
- /* XXX find a more elegant way */
- hw->samples = 4096 * 4;
- audio_pcm_hw_alloc_resources_out(hw);
-
- audio_pcm_init_info (&hw->info, as);
-
- cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame);
-
- if (hw->info.is_float) {
- hw->clip = mixeng_clip_float[hw->info.nchannels == 2]
- [hw->info.swap_endianness];
- } else {
- hw->clip = mixeng_clip
- [hw->info.nchannels == 2]
- [hw->info.is_signed]
- [hw->info.swap_endianness]
- [audio_bits_to_index(hw->info.bits)];
- }
-
- QLIST_INSERT_HEAD (&s->cap_head, cap, entries);
- QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
-
- QLIST_FOREACH(hw, &s->hw_head_out, entries) {
- audio_attach_capture (hw);
- }
- }
-
- return cap;
-}
-
-static void audio_mixeng_backend_del_capture(
- AudioBackend *be,
- CaptureVoiceOut *cap,
- void *cb_opaque)
+void audio_help(void)
{
- struct capture_callback *cb;
-
- for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
- if (cb->opaque == cb_opaque) {
- cb->ops.destroy (cb_opaque);
- QLIST_REMOVE (cb, entries);
- g_free (cb);
+ int i;
- if (!cap->cb_head.lh_first) {
- SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1;
+ printf("Available audio drivers:\n");
- while (sw) {
- SWVoiceCap *sc = (SWVoiceCap *) sw;
-#ifdef DEBUG_CAPTURE
- dolog ("freeing %s\n", sw->name);
-#endif
+ for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) {
+ const char *name = AudiodevDriver_str(i);
+ AudioBackendClass *be = audio_be_class_by_name(name);
- sw1 = sw->entries.le_next;
- if (sw->rate) {
- st_rate_stop (sw->rate);
- sw->rate = NULL;
- }
- QLIST_REMOVE (sw, entries);
- QLIST_REMOVE (sc, entries);
- g_free (sc);
- sw = sw1;
- }
- QLIST_REMOVE (cap, entries);
- g_free(cap->hw.mix_buf.buffer);
- g_free (cap->buf);
- g_free (cap);
- }
- return;
+ if (be) {
+ printf("%s\n", name);
}
}
}
-static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
- Volume *vol)
+void audio_parse_option(const char *opt)
{
- if (sw) {
- HWVoiceOut *hw = sw->hw;
-
- 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;
+ Audiodev *dev = NULL;
- if (hw->pcm_ops->volume_out) {
- hw->pcm_ops->volume_out(hw, vol);
- }
+ if (is_help_option(opt)) {
+ audio_help();
+ exit(EXIT_SUCCESS);
}
-}
-
-static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
- Volume *vol)
-{
- if (sw) {
- HWVoiceIn *hw = sw->hw;
-
- 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;
+ Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
+ visit_type_Audiodev(v, NULL, &dev, &error_fatal);
+ visit_free(v);
- if (hw->pcm_ops->volume_in) {
- hw->pcm_ops->volume_in(hw, vol);
- }
- }
+ audio_add_audiodev(dev);
}
static void audio_create_pdos(Audiodev *dev)
@@ -2103,6 +266,124 @@ static void audio_validate_per_direction_opts(
}
}
+static AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev)
+{
+ switch (dev->driver) {
+ case AUDIODEV_DRIVER_NONE:
+ return dev->u.none.out;
+#ifdef CONFIG_AUDIO_ALSA
+ case AUDIODEV_DRIVER_ALSA:
+ return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.out);
+#endif
+#ifdef CONFIG_AUDIO_COREAUDIO
+ case AUDIODEV_DRIVER_COREAUDIO:
+ return qapi_AudiodevCoreaudioPerDirectionOptions_base(
+ dev->u.coreaudio.out);
+#endif
+#ifdef CONFIG_DBUS_DISPLAY
+ case AUDIODEV_DRIVER_DBUS:
+ return dev->u.dbus.out;
+#endif
+#ifdef CONFIG_AUDIO_DSOUND
+ case AUDIODEV_DRIVER_DSOUND:
+ return dev->u.dsound.out;
+#endif
+#ifdef CONFIG_AUDIO_JACK
+ case AUDIODEV_DRIVER_JACK:
+ return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.out);
+#endif
+#ifdef CONFIG_AUDIO_OSS
+ case AUDIODEV_DRIVER_OSS:
+ return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.out);
+#endif
+#ifdef CONFIG_AUDIO_PA
+ case AUDIODEV_DRIVER_PA:
+ return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.out);
+#endif
+#ifdef CONFIG_AUDIO_PIPEWIRE
+ case AUDIODEV_DRIVER_PIPEWIRE:
+ return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.out);
+#endif
+#ifdef CONFIG_AUDIO_SDL
+ case AUDIODEV_DRIVER_SDL:
+ return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.out);
+#endif
+#ifdef CONFIG_AUDIO_SNDIO
+ case AUDIODEV_DRIVER_SNDIO:
+ return dev->u.sndio.out;
+#endif
+#ifdef CONFIG_SPICE
+ case AUDIODEV_DRIVER_SPICE:
+ return dev->u.spice.out;
+#endif
+ case AUDIODEV_DRIVER_WAV:
+ return dev->u.wav.out;
+
+ case AUDIODEV_DRIVER__MAX:
+ break;
+ }
+ abort();
+}
+
+static AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev)
+{
+ switch (dev->driver) {
+ case AUDIODEV_DRIVER_NONE:
+ return dev->u.none.in;
+#ifdef CONFIG_AUDIO_ALSA
+ case AUDIODEV_DRIVER_ALSA:
+ return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.in);
+#endif
+#ifdef CONFIG_AUDIO_COREAUDIO
+ case AUDIODEV_DRIVER_COREAUDIO:
+ return qapi_AudiodevCoreaudioPerDirectionOptions_base(
+ dev->u.coreaudio.in);
+#endif
+#ifdef CONFIG_DBUS_DISPLAY
+ case AUDIODEV_DRIVER_DBUS:
+ return dev->u.dbus.in;
+#endif
+#ifdef CONFIG_AUDIO_DSOUND
+ case AUDIODEV_DRIVER_DSOUND:
+ return dev->u.dsound.in;
+#endif
+#ifdef CONFIG_AUDIO_JACK
+ case AUDIODEV_DRIVER_JACK:
+ return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.in);
+#endif
+#ifdef CONFIG_AUDIO_OSS
+ case AUDIODEV_DRIVER_OSS:
+ return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.in);
+#endif
+#ifdef CONFIG_AUDIO_PA
+ case AUDIODEV_DRIVER_PA:
+ return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.in);
+#endif
+#ifdef CONFIG_AUDIO_PIPEWIRE
+ case AUDIODEV_DRIVER_PIPEWIRE:
+ return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.in);
+#endif
+#ifdef CONFIG_AUDIO_SDL
+ case AUDIODEV_DRIVER_SDL:
+ return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.in);
+#endif
+#ifdef CONFIG_AUDIO_SNDIO
+ case AUDIODEV_DRIVER_SNDIO:
+ return dev->u.sndio.in;
+#endif
+#ifdef CONFIG_SPICE
+ case AUDIODEV_DRIVER_SPICE:
+ return dev->u.spice.in;
+#endif
+ case AUDIODEV_DRIVER_WAV:
+ return dev->u.wav.in;
+
+ case AUDIODEV_DRIVER__MAX:
+ break;
+ }
+ abort();
+}
+
static void audio_validate_opts(Audiodev *dev, Error **errp)
{
Error *err = NULL;
@@ -2127,37 +408,6 @@ static void audio_validate_opts(Audiodev *dev, Error **errp)
}
}
-void audio_help(void)
-{
- int i;
-
- printf("Available audio drivers:\n");
-
- for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) {
- const char *name = AudiodevDriver_str(i);
- AudioBackendClass *be = audio_be_class_by_name(name);
-
- if (be) {
- printf("%s\n", name);
- }
- }
-}
-
-void audio_parse_option(const char *opt)
-{
- Audiodev *dev = NULL;
-
- if (is_help_option(opt)) {
- audio_help();
- exit(EXIT_SUCCESS);
- }
- Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
- visit_type_Audiodev(v, NULL, &dev, &error_fatal);
- visit_free(v);
-
- audio_add_audiodev(dev);
-}
-
void audio_add_audiodev(Audiodev *dev)
{
AudiodevListEntry *e;
@@ -2189,65 +439,6 @@ void audio_init_audiodevs(void)
}
}
-audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo)
-{
- return (audsettings) {
- .freq = pdo->frequency,
- .nchannels = pdo->channels,
- .fmt = pdo->format,
- .endianness = HOST_BIG_ENDIAN,
- };
-}
-
-int audioformat_bytes_per_sample(AudioFormat fmt)
-{
- switch (fmt) {
- case AUDIO_FORMAT_U8:
- case AUDIO_FORMAT_S8:
- return 1;
-
- case AUDIO_FORMAT_U16:
- case AUDIO_FORMAT_S16:
- return 2;
-
- case AUDIO_FORMAT_U32:
- case AUDIO_FORMAT_S32:
- case AUDIO_FORMAT_F32:
- return 4;
-
- case AUDIO_FORMAT__MAX:
- ;
- }
- abort();
-}
-
-
-/* frames = freq * usec / 1e6 */
-int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,
- audsettings *as, int def_usecs)
-{
- uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs;
- return (as->freq * usecs + 500000) / 1000000;
-}
-
-/* samples = channels * frames = channels * freq * usec / 1e6 */
-int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,
- audsettings *as, int def_usecs)
-{
- return as->nchannels * audio_buffer_frames(pdo, as, def_usecs);
-}
-
-/*
- * bytes = bytes_per_sample * samples =
- * bytes_per_sample * channels * freq * usec / 1e6
- */
-int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,
- audsettings *as, int def_usecs)
-{
- return audio_buffer_samples(pdo, as, def_usecs) *
- audioformat_bytes_per_sample(as->fmt);
-}
-
AudioBackend *audio_be_by_name(const char *name, Error **errp)
{
Object *obj = object_resolve_path_component(get_audiodevs_root(), name);
@@ -2268,51 +459,6 @@ const char *audio_application_name(void)
return vm_name ? vm_name : "qemu";
}
-void audio_rate_start(RateCtl *rate)
-{
- memset(rate, 0, sizeof(RateCtl));
- rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
-}
-
-size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info)
-{
- int64_t now;
- int64_t ticks;
- int64_t bytes;
- int64_t frames;
-
- now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- ticks = now - rate->start_ticks;
- bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);
- frames = (bytes - rate->bytes_sent) / info->bytes_per_frame;
- rate->peeked_frames = frames;
-
- return frames < 0 ? 0 : frames * info->bytes_per_frame;
-}
-
-void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used)
-{
- if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) {
- AUD_log(NULL, "Resetting rate control (%" PRId64 " frames)\n",
- rate->peeked_frames);
- audio_rate_start(rate);
- }
-
- rate->bytes_sent += bytes_used;
-}
-
-size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info,
- size_t bytes_avail)
-{
- size_t bytes;
-
- bytes = audio_rate_peek_bytes(rate, info);
- bytes = MIN(bytes, bytes_avail);
- audio_rate_add_bytes(rate, bytes);
-
- return bytes;
-}
-
AudiodevList *qmp_query_audiodevs(Error **errp)
{
AudiodevList *ret = NULL;
@@ -2322,21 +468,3 @@ AudiodevList *qmp_query_audiodevs(Error **errp)
}
return ret;
}
-
-static const TypeInfo audio_mixeng_backend_info = {
- .name = TYPE_AUDIO_MIXENG_BACKEND,
- .parent = TYPE_AUDIO_BACKEND,
- .instance_size = sizeof(AudioMixengBackend),
- .instance_init = audio_mixeng_backend_init,
- .instance_finalize = audio_mixeng_backend_finalize,
- .abstract = false,
- .class_size = sizeof(AudioMixengBackendClass),
- .class_init = audio_mixeng_backend_class_init,
-};
-
-static void register_types(void)
-{
- type_register_static(&audio_mixeng_backend_info);
-}
-
-type_init(register_types);
diff --git a/audio/meson.build b/audio/meson.build
index 417670bd4c7..d2ff49280ba 100644
--- a/audio/meson.build
+++ b/audio/meson.build
@@ -2,6 +2,7 @@ audio_ss = ss.source_set()
audio_ss.add(files(
'audio.c',
'audio-be.c',
+ 'audio-mixeng-be.c',
'mixeng.c',
'noaudio.c',
'wavaudio.c',
--
2.52.0
On 23/01/2026 07:49, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Allow to build the audio/ base classes without the
> resampling/mixing/queuing code.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> audio/audio_int.h | 2 -
> include/qemu/audio.h | 2 +
> audio/audio-mixeng-be.c | 1979 ++++++++++++++++++++++++++++++++++
> audio/audio.c | 2226 ++++-----------------------------------
> audio/meson.build | 1 +
> 5 files changed, 2159 insertions(+), 2051 deletions(-)
> create mode 100644 audio/audio-mixeng-be.c
>
> diff --git a/audio/audio_int.h b/audio/audio_int.h
> index 250fd45238d..6ecd75c4fbf 100644
> --- a/audio/audio_int.h
> +++ b/audio/audio_int.h
> @@ -266,8 +266,6 @@ int audio_bug (const char *funcname, int cond);
>
> void audio_run(AudioMixengBackend *s, const char *msg);
>
> -const char *audio_application_name(void);
> -
> typedef struct RateCtl {
> int64_t start_ticks;
> int64_t bytes_sent;
> diff --git a/include/qemu/audio.h b/include/qemu/audio.h
> index 42f97f732a6..dfe247ab8c4 100644
> --- a/include/qemu/audio.h
> +++ b/include/qemu/audio.h
> @@ -183,6 +183,8 @@ bool audio_be_set_dbus_server(AudioBackend *be,
> Error **errp);
> #endif
>
> +const char *audio_application_name(void);
> +
> #define DEFINE_AUDIO_PROPERTIES(_s, _f) \
> DEFINE_PROP_AUDIODEV("audiodev", _s, _f)
>
> diff --git a/audio/audio-mixeng-be.c b/audio/audio-mixeng-be.c
> new file mode 100644
> index 00000000000..8ebceb968ce
> --- /dev/null
> +++ b/audio/audio-mixeng-be.c
> @@ -0,0 +1,1979 @@
> +/*
> + * SPDX-License-Identifier: MIT
> + *
> + * Copyright (c) 2003-2005 Vassili Karpov (malc)
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/audio.h"
> +#include "migration/vmstate.h"
> +#include "qemu/timer.h"
> +#include "qapi/error.h"
> +#include "qapi/clone-visitor.h"
> +#include "qapi/qobject-input-visitor.h"
> +#include "qapi/qapi-visit-audio.h"
> +#include "qapi/qapi-commands-audio.h"
> +#include "qobject/qdict.h"
> +#include "qemu/error-report.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +#include "qemu/help_option.h"
> +#include "qom/object.h"
> +#include "system/system.h"
> +#include "system/replay.h"
> +#include "system/runstate.h"
> +#include "trace.h"
> +
> +#define AUDIO_CAP "audio"
> +#include "audio_int.h"
> +
> +/* #define DEBUG_OUT */
> +/* #define DEBUG_CAPTURE */
> +/* #define DEBUG_POLL */
> +
> +#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown"
> +
> +const struct mixeng_volume nominal_volume = {
> + .mute = 0,
> +#ifdef FLOAT_MIXENG
> + .r = 1.0,
> + .l = 1.0,
> +#else
> + .r = 1ULL << 32,
> + .l = 1ULL << 32,
> +#endif
> +};
> +
> +int audio_bug (const char *funcname, int cond)
> +{
> + if (cond) {
> + static int shown;
> +
> + AUD_log (NULL, "A bug was just triggered in %s\n", funcname);
> + if (!shown) {
> + shown = 1;
> + AUD_log (NULL, "Save all your work and restart without audio\n");
> + AUD_log (NULL, "I am sorry\n");
> + }
> + AUD_log (NULL, "Context:\n");
> + }
> +
> + return cond;
> +}
> +
> +static inline int audio_bits_to_index (int bits)
> +{
> + switch (bits) {
> + case 8:
> + return 0;
> +
> + case 16:
> + return 1;
> +
> + case 32:
> + return 2;
> +
> + default:
> + audio_bug ("bits_to_index", 1);
> + AUD_log (NULL, "invalid bits %d\n", bits);
> + return 0;
> + }
> +}
> +
> +void AUD_vlog (const char *cap, const char *fmt, va_list ap)
> +{
> + if (cap) {
> + fprintf(stderr, "%s: ", cap);
> + }
> +
> + vfprintf(stderr, fmt, ap);
> +}
> +
> +void AUD_log (const char *cap, const char *fmt, ...)
> +{
> + va_list ap;
> +
> + va_start (ap, fmt);
> + AUD_vlog (cap, fmt, ap);
> + va_end (ap);
> +}
> +
> +static void audio_print_settings (const struct audsettings *as)
> +{
> + dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels);
> +
> + switch (as->fmt) {
> + case AUDIO_FORMAT_S8:
> + AUD_log (NULL, "S8");
> + break;
> + case AUDIO_FORMAT_U8:
> + AUD_log (NULL, "U8");
> + break;
> + case AUDIO_FORMAT_S16:
> + AUD_log (NULL, "S16");
> + break;
> + case AUDIO_FORMAT_U16:
> + AUD_log (NULL, "U16");
> + break;
> + case AUDIO_FORMAT_S32:
> + AUD_log (NULL, "S32");
> + break;
> + case AUDIO_FORMAT_U32:
> + AUD_log (NULL, "U32");
> + break;
> + case AUDIO_FORMAT_F32:
> + AUD_log (NULL, "F32");
> + break;
> + default:
> + AUD_log (NULL, "invalid(%d)", as->fmt);
> + break;
> + }
> +
> + AUD_log (NULL, " endianness=");
> + switch (as->endianness) {
> + case 0:
> + AUD_log (NULL, "little");
> + break;
> + case 1:
> + AUD_log (NULL, "big");
> + break;
> + default:
> + AUD_log (NULL, "invalid");
> + break;
> + }
> + AUD_log (NULL, "\n");
> +}
> +
> +static int audio_validate_settings (const struct audsettings *as)
> +{
> + int invalid;
> +
> + invalid = as->nchannels < 1;
> + invalid |= as->endianness != 0 && as->endianness != 1;
> +
> + switch (as->fmt) {
> + case AUDIO_FORMAT_S8:
> + case AUDIO_FORMAT_U8:
> + case AUDIO_FORMAT_S16:
> + case AUDIO_FORMAT_U16:
> + case AUDIO_FORMAT_S32:
> + case AUDIO_FORMAT_U32:
> + case AUDIO_FORMAT_F32:
> + break;
> + default:
> + invalid = 1;
> + break;
> + }
> +
> + invalid |= as->freq <= 0;
> + return invalid ? -1 : 0;
> +}
> +
> +static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct audsettings *as)
> +{
> + int bits = 8;
> + bool is_signed = false, is_float = false;
> +
> + switch (as->fmt) {
> + case AUDIO_FORMAT_S8:
> + is_signed = true;
> + /* fall through */
> + case AUDIO_FORMAT_U8:
> + break;
> +
> + case AUDIO_FORMAT_S16:
> + is_signed = true;
> + /* fall through */
> + case AUDIO_FORMAT_U16:
> + bits = 16;
> + break;
> +
> + case AUDIO_FORMAT_F32:
> + is_float = true;
> + /* fall through */
> + case AUDIO_FORMAT_S32:
> + is_signed = true;
> + /* fall through */
> + case AUDIO_FORMAT_U32:
> + bits = 32;
> + break;
> +
> + default:
> + abort();
> + }
> + return info->freq == as->freq
> + && info->nchannels == as->nchannels
> + && info->is_signed == is_signed
> + && info->is_float == is_float
> + && info->bits == bits
> + && info->swap_endianness == (as->endianness != HOST_BIG_ENDIAN);
> +}
> +
> +void audio_pcm_init_info (struct audio_pcm_info *info, const struct audsettings *as)
> +{
> + int bits = 8, mul;
> + bool is_signed = false, is_float = false;
> +
> + switch (as->fmt) {
> + case AUDIO_FORMAT_S8:
> + is_signed = true;
> + /* fall through */
> + case AUDIO_FORMAT_U8:
> + mul = 1;
> + break;
> +
> + case AUDIO_FORMAT_S16:
> + is_signed = true;
> + /* fall through */
> + case AUDIO_FORMAT_U16:
> + bits = 16;
> + mul = 2;
> + break;
> +
> + case AUDIO_FORMAT_F32:
> + is_float = true;
> + /* fall through */
> + case AUDIO_FORMAT_S32:
> + is_signed = true;
> + /* fall through */
> + case AUDIO_FORMAT_U32:
> + bits = 32;
> + mul = 4;
> + break;
> +
> + default:
> + abort();
> + }
> +
> + info->freq = as->freq;
> + info->bits = bits;
> + info->is_signed = is_signed;
> + info->is_float = is_float;
> + info->nchannels = as->nchannels;
> + info->bytes_per_frame = as->nchannels * mul;
> + info->bytes_per_second = info->freq * info->bytes_per_frame;
> + info->swap_endianness = (as->endianness != HOST_BIG_ENDIAN);
> +}
> +
> +void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
> +{
> + if (!len) {
> + return;
> + }
> +
> + if (info->is_signed || info->is_float) {
> + memset(buf, 0x00, len * info->bytes_per_frame);
> + } else {
> + switch (info->bits) {
> + case 8:
> + memset(buf, 0x80, len * info->bytes_per_frame);
> + break;
> +
> + case 16:
> + {
> + int i;
> + uint16_t *p = buf;
> + short s = INT16_MAX;
> +
> + if (info->swap_endianness) {
> + s = bswap16 (s);
> + }
> +
> + for (i = 0; i < len * info->nchannels; i++) {
> + p[i] = s;
> + }
> + }
> + break;
> +
> + case 32:
> + {
> + int i;
> + uint32_t *p = buf;
> + int32_t s = INT32_MAX;
> +
> + if (info->swap_endianness) {
> + s = bswap32 (s);
> + }
> +
> + for (i = 0; i < len * info->nchannels; i++) {
> + p[i] = s;
> + }
> + }
> + break;
> +
> + default:
> + AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n",
> + info->bits);
> + break;
> + }
> + }
> +}
> +
> +/*
> + * Capture
> + */
> +static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioMixengBackend *s,
> + struct audsettings *as)
> +{
> + CaptureVoiceOut *cap;
> +
> + for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
> + if (audio_pcm_info_eq (&cap->hw.info, as)) {
> + return cap;
> + }
> + }
> + return NULL;
> +}
> +
> +static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd)
> +{
> + struct capture_callback *cb;
> +
> +#ifdef DEBUG_CAPTURE
> + dolog ("notification %d sent\n", cmd);
> +#endif
> + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> + cb->ops.notify (cb->opaque, cmd);
> + }
> +}
> +
> +static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled)
> +{
> + if (cap->hw.enabled != enabled) {
> + audcnotification_e cmd;
> + cap->hw.enabled = enabled;
> + cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE;
> + audio_notify_capture (cap, cmd);
> + }
> +}
> +
> +static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap)
> +{
> + HWVoiceOut *hw = &cap->hw;
> + SWVoiceOut *sw;
> + bool enabled = false;
> +
> + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> + if (sw->active) {
> + enabled = true;
> + break;
> + }
> + }
> + audio_capture_maybe_changed (cap, enabled);
> +}
> +
> +static void audio_detach_capture (HWVoiceOut *hw)
> +{
> + SWVoiceCap *sc = hw->cap_head.lh_first;
> +
> + while (sc) {
> + SWVoiceCap *sc1 = sc->entries.le_next;
> + SWVoiceOut *sw = &sc->sw;
> + CaptureVoiceOut *cap = sc->cap;
> + int was_active = sw->active;
> +
> + if (sw->rate) {
> + st_rate_stop (sw->rate);
> + sw->rate = NULL;
> + }
> +
> + QLIST_REMOVE (sw, entries);
> + QLIST_REMOVE (sc, entries);
> + g_free (sc);
> + if (was_active) {
> + /* We have removed soft voice from the capture:
> + this might have changed the overall status of the capture
> + since this might have been the only active voice */
> + audio_recalc_and_notify_capture (cap);
> + }
> + sc = sc1;
> + }
> +}
> +
> +static int audio_attach_capture (HWVoiceOut *hw)
> +{
> + AudioMixengBackend *s = hw->s;
> + CaptureVoiceOut *cap;
> +
> + audio_detach_capture (hw);
> + for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
> + SWVoiceCap *sc;
> + SWVoiceOut *sw;
> + HWVoiceOut *hw_cap = &cap->hw;
> +
> + sc = g_malloc0(sizeof(*sc));
> +
> + sc->cap = cap;
> + sw = &sc->sw;
> + sw->hw = hw_cap;
> + sw->info = hw->info;
> + sw->empty = true;
> + sw->active = hw->enabled;
> + sw->vol = nominal_volume;
> + sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
> + QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
> + QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);
> +#ifdef DEBUG_CAPTURE
> + sw->name = g_strdup_printf ("for %p %d,%d,%d",
> + hw, sw->info.freq, sw->info.bits,
> + sw->info.nchannels);
> + dolog ("Added %s active = %d\n", sw->name, sw->active);
> +#endif
> + if (sw->active) {
> + audio_capture_maybe_changed (cap, 1);
> + }
> + }
> + return 0;
> +}
> +
> +/*
> + * Hard voice (capture)
> + */
> +static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)
> +{
> + SWVoiceIn *sw;
> + size_t m = hw->total_samples_captured;
> +
> + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> + if (sw->active) {
> + m = MIN (m, sw->total_hw_samples_acquired);
> + }
> + }
> + return m;
> +}
> +
> +static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
> +{
> + size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
> + if (audio_bug(__func__, live > hw->conv_buf.size)) {
> + dolog("live=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
> + return 0;
> + }
> + return live;
> +}
> +
> +static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
> +{
> + size_t conv = 0;
> + STSampleBuffer *conv_buf = &hw->conv_buf;
> +
> + while (samples) {
> + uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);
> + size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);
> +
> + hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);
> + conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
> + samples -= proc;
> + conv += proc;
> + }
> +
> + return conv;
> +}
> +
> +/*
> + * Soft voice (capture)
> + */
> +static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
> + size_t frames_in_max, size_t frames_out_max,
> + size_t *total_in, size_t *total_out)
> +{
> + HWVoiceIn *hw = sw->hw;
> + struct st_sample *src, *dst;
> + size_t live, rpos, frames_in, frames_out;
> +
> + live = hw->total_samples_captured - sw->total_hw_samples_acquired;
> + rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
> +
> + /* resample conv_buf from rpos to end of buffer */
> + src = hw->conv_buf.buffer + rpos;
> + frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos);
> + dst = sw->resample_buf.buffer;
> + frames_out = frames_out_max;
> + st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
> + rpos += frames_in;
> + *total_in = frames_in;
> + *total_out = frames_out;
> +
> + /* resample conv_buf from start of buffer if there are input frames left */
> + if (frames_in_max - frames_in && rpos == hw->conv_buf.size) {
> + src = hw->conv_buf.buffer;
> + frames_in = frames_in_max - frames_in;
> + dst += frames_out;
> + frames_out = frames_out_max - frames_out;
> + st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
> + *total_in += frames_in;
> + *total_out += frames_out;
> + }
> +}
> +
> +static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
> +{
> + HWVoiceIn *hw = sw->hw;
> + size_t live, frames_out_max, total_in, total_out;
> +
> + live = hw->total_samples_captured - sw->total_hw_samples_acquired;
> + if (!live) {
> + return 0;
> + }
> + if (audio_bug(__func__, live > hw->conv_buf.size)) {
> + dolog("live_in=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
> + return 0;
> + }
> +
> + frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,
> + sw->resample_buf.size);
> +
> + audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);
> +
> + if (!hw->pcm_ops->volume_in) {
> + mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);
> + }
> + sw->clip(buf, sw->resample_buf.buffer, total_out);
> +
> + sw->total_hw_samples_acquired += total_in;
> + return total_out * sw->info.bytes_per_frame;
> +}
> +
> +/*
> + * Hard voice (playback)
> + */
> +static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)
> +{
> + SWVoiceOut *sw;
> + size_t m = SIZE_MAX;
> + int nb_live = 0;
> +
> + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> + if (sw->active || !sw->empty) {
> + m = MIN (m, sw->total_hw_samples_mixed);
> + nb_live += 1;
> + }
> + }
> +
> + *nb_livep = nb_live;
> + return m;
> +}
> +
> +static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)
> +{
> + size_t smin;
> + int nb_live1;
> +
> + smin = audio_pcm_hw_find_min_out (hw, &nb_live1);
> + if (nb_live) {
> + *nb_live = nb_live1;
> + }
> +
> + if (nb_live1) {
> + size_t live = smin;
> +
> + if (audio_bug(__func__, live > hw->mix_buf.size)) {
> + dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
> + return 0;
> + }
> + return live;
> + }
> + return 0;
> +}
> +
> +static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)
> +{
> + return (hw->pcm_ops->buffer_get_free ? hw->pcm_ops->buffer_get_free(hw) :
> + INT_MAX) / hw->info.bytes_per_frame;
> +}
> +
> +static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
> +{
> + size_t clipped = 0;
> + size_t pos = hw->mix_buf.pos;
> +
> + while (len) {
> + st_sample *src = hw->mix_buf.buffer + pos;
> + 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);
> +
> + hw->clip(dst, src, samples_to_clip);
> +
> + pos = (pos + samples_to_clip) % hw->mix_buf.size;
> + len -= samples_to_clip;
> + clipped += samples_to_clip;
> + }
> +}
> +
> +/*
> + * Soft voice (playback)
> + */
> +static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
> + size_t frames_in_max, size_t frames_out_max,
> + size_t *total_in, size_t *total_out)
> +{
> + HWVoiceOut *hw = sw->hw;
> + struct st_sample *src, *dst;
> + size_t live, wpos, frames_in, frames_out;
> +
> + live = sw->total_hw_samples_mixed;
> + wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;
> +
> + /* write to mix_buf from wpos to end of buffer */
> + src = sw->resample_buf.buffer;
> + frames_in = frames_in_max;
> + dst = hw->mix_buf.buffer + wpos;
> + frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);
> + st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
> + wpos += frames_out;
> + *total_in = frames_in;
> + *total_out = frames_out;
> +
> + /* write to mix_buf from start of buffer if there are input frames left */
> + if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {
> + src += frames_in;
> + frames_in = frames_in_max - frames_in;
> + dst = hw->mix_buf.buffer;
> + frames_out = frames_out_max - frames_out;
> + st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
> + *total_in += frames_in;
> + *total_out += frames_out;
> + }
> +}
> +
> +static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
> +{
> + HWVoiceOut *hw = sw->hw;
> + size_t live, dead, hw_free, sw_max, fe_max;
> + size_t frames_in_max, frames_out_max, total_in, total_out;
> +
> + live = sw->total_hw_samples_mixed;
> + if (audio_bug(__func__, live > hw->mix_buf.size)) {
> + dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
> + return 0;
> + }
> +
> + if (live == hw->mix_buf.size) {
> +#ifdef DEBUG_OUT
> + dolog ("%s is full %zu\n", sw->name, live);
> +#endif
> + return 0;
> + }
> +
> + dead = hw->mix_buf.size - live;
> + hw_free = audio_pcm_hw_get_free(hw);
> + hw_free = hw_free > live ? hw_free - live : 0;
> + frames_out_max = MIN(dead, hw_free);
> + sw_max = st_rate_frames_in(sw->rate, frames_out_max);
> + fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,
> + sw->resample_buf.size);
> + frames_in_max = MIN(sw_max, fe_max);
> +
> + if (!frames_in_max) {
> + return 0;
> + }
> +
> + if (frames_in_max > sw->resample_buf.pos) {
> + sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,
> + buf, frames_in_max - sw->resample_buf.pos);
> + if (!sw->hw->pcm_ops->volume_out) {
> + mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,
> + frames_in_max - sw->resample_buf.pos, &sw->vol);
> + }
> + }
> +
> + audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,
> + &total_in, &total_out);
> +
> + sw->total_hw_samples_mixed += total_out;
> + sw->empty = sw->total_hw_samples_mixed == 0;
> +
> + /*
> + * Upsampling may leave one audio frame in the resample buffer. Decrement
> + * total_in by one if there was a leftover frame from the previous resample
> + * pass in the resample buffer. Increment total_in by one if the current
> + * resample pass left one frame in the resample buffer.
> + */
> + if (frames_in_max - total_in == 1) {
> + /* copy one leftover audio frame to the beginning of the buffer */
> + *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);
> + total_in += 1 - sw->resample_buf.pos;
> + sw->resample_buf.pos = 1;
> + } else if (total_in >= sw->resample_buf.pos) {
> + total_in -= sw->resample_buf.pos;
> + sw->resample_buf.pos = 0;
> + }
> +
> +#ifdef DEBUG_OUT
> + dolog (
> + "%s: write size %zu written %zu total mixed %zu\n",
> + SW_NAME(sw),
> + buf_len / sw->info.bytes_per_frame,
> + total_in,
> + sw->total_hw_samples_mixed
> + );
> +#endif
> +
> + return total_in * sw->info.bytes_per_frame;
> +}
> +
> +#ifdef DEBUG_AUDIO
> +static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
> +{
> + dolog("%s: bits %d, sign %d, float %d, freq %d, nchan %d\n",
> + cap, info->bits, info->is_signed, info->is_float, info->freq,
> + info->nchannels);
> +}
> +#endif
> +
> +#define DAC
> +#include "audio_template.h"
> +#undef DAC
> +#include "audio_template.h"
> +
> +/*
> + * Timer
> + */
> +static int audio_is_timer_needed(AudioMixengBackend *s)
> +{
> + HWVoiceIn *hwi = NULL;
> + HWVoiceOut *hwo = NULL;
> +
> + while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
> + if (!hwo->poll_mode) {
> + return 1;
> + }
> + }
> + while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
> + if (!hwi->poll_mode) {
> + return 1;
> + }
> + }
> + return 0;
> +}
> +
> +static void audio_reset_timer(AudioMixengBackend *s)
> +{
> + if (audio_is_timer_needed(s)) {
> + timer_mod_anticipate_ns(s->ts,
> + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
> + if (!s->timer_running) {
> + s->timer_running = true;
> + s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> + trace_audio_timer_start(s->period_ticks / SCALE_MS);
> + }
> + } else {
> + timer_del(s->ts);
> + if (s->timer_running) {
> + s->timer_running = false;
> + trace_audio_timer_stop();
> + }
> + }
> +}
> +
> +static void audio_timer (void *opaque)
> +{
> + int64_t now, diff;
> + AudioMixengBackend *s = opaque;
> +
> + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> + diff = now - s->timer_last;
> + if (diff > s->period_ticks * 3 / 2) {
> + trace_audio_timer_delayed(diff / SCALE_MS);
> + }
> + s->timer_last = now;
> +
> + audio_run(s, "timer");
> + audio_reset_timer(s);
> +}
> +
> +/*
> + * Public API
> + */
> +static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw,
> + void *buf, size_t size)
> +{
> + HWVoiceOut *hw;
> +
> + if (!sw) {
> + /* XXX: Consider options */
> + return size;
> + }
> + hw = sw->hw;
> +
> + if (!hw->enabled) {
> + dolog("Writing to disabled voice %s\n", SW_NAME(sw));
> + return 0;
> + }
> +
> + 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);
> + }
> +}
> +
> +static size_t audio_mixeng_backend_read(AudioBackend *be,
> + SWVoiceIn *sw, void *buf, size_t size)
> +{
> + HWVoiceIn *hw;
> +
> + if (!sw) {
> + /* XXX: Consider options */
> + return size;
> + }
> + hw = sw->hw;
> +
> + if (!hw->enabled) {
> + dolog("Reading from disabled voice %s\n", SW_NAME(sw));
> + return 0;
> + }
> +
> + 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);
> + }
> +
> +}
> +
> +static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
> +{
> + if (!sw) {
> + return 0;
> + }
> +
> + if (audio_get_pdo_out(sw->s->dev)->mixing_engine) {
> + return sw->resample_buf.size * sw->info.bytes_per_frame;
> + }
> +
> + return sw->hw->samples * sw->hw->info.bytes_per_frame;
> +}
> +
> +static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceOut *sw,
> + bool on)
> +{
> + HWVoiceOut *hw;
> +
> + if (!sw) {
> + return;
> + }
> +
> + hw = sw->hw;
> + if (sw->active != on) {
> + AudioMixengBackend *s = sw->s;
> + SWVoiceOut *temp_sw;
> + SWVoiceCap *sc;
> +
> + if (on) {
> + hw->pending_disable = 0;
> + if (!hw->enabled) {
> + hw->enabled = true;
> + if (runstate_is_running()) {
> + if (hw->pcm_ops->enable_out) {
> + hw->pcm_ops->enable_out(hw, true);
> + }
> + audio_reset_timer (s);
> + }
> + }
> + } else {
> + if (hw->enabled) {
> + int nb_active = 0;
> +
> + for (temp_sw = hw->sw_head.lh_first; temp_sw;
> + temp_sw = temp_sw->entries.le_next) {
> + nb_active += temp_sw->active != 0;
> + }
> +
> + hw->pending_disable = nb_active == 1;
> + }
> + }
> +
> + for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> + sc->sw.active = hw->enabled;
> + if (hw->enabled) {
> + audio_capture_maybe_changed (sc->cap, 1);
> + }
> + }
> + sw->active = on;
> + }
> +
> +}
> +
> +static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
> +{
> + HWVoiceIn *hw;
> +
> + if (!sw) {
> + return;
> + }
> +
> + hw = sw->hw;
> + if (sw->active != on) {
> + AudioMixengBackend *s = sw->s;
> + SWVoiceIn *temp_sw;
> +
> + if (on) {
> + if (!hw->enabled) {
> + hw->enabled = true;
> + if (runstate_is_running()) {
> + if (hw->pcm_ops->enable_in) {
> + hw->pcm_ops->enable_in(hw, true);
> + }
> + audio_reset_timer (s);
> + }
> + }
> + sw->total_hw_samples_acquired = hw->total_samples_captured;
> + } else {
> + if (hw->enabled) {
> + int nb_active = 0;
> +
> + for (temp_sw = hw->sw_head.lh_first; temp_sw;
> + temp_sw = temp_sw->entries.le_next) {
> + nb_active += temp_sw->active != 0;
> + }
> +
> + if (nb_active == 1) {
> + hw->enabled = false;
> + if (hw->pcm_ops->enable_in) {
> + hw->pcm_ops->enable_in(hw, false);
> + }
> + }
> + }
> + }
> + sw->active = on;
> + }
> +}
> +
> +static size_t audio_get_avail(SWVoiceIn *sw)
> +{
> + size_t live;
> +
> + if (!sw) {
> + return 0;
> + }
> +
> + live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
> + if (audio_bug(__func__, live > sw->hw->conv_buf.size)) {
> + dolog("live=%zu sw->hw->conv_buf.size=%zu\n", live,
> + sw->hw->conv_buf.size);
> + return 0;
> + }
> +
> + ldebug (
> + "%s: get_avail live %zu frontend frames %u\n",
> + SW_NAME (sw),
> + live, st_rate_frames_out(sw->rate, live)
> + );
> +
> + return live;
> +}
> +
> +static size_t audio_get_free(SWVoiceOut *sw)
> +{
> + size_t live, dead;
> +
> + if (!sw) {
> + return 0;
> + }
> +
> + live = sw->total_hw_samples_mixed;
> +
> + if (audio_bug(__func__, live > sw->hw->mix_buf.size)) {
> + dolog("live=%zu sw->hw->mix_buf.size=%zu\n", live,
> + sw->hw->mix_buf.size);
> + return 0;
> + }
> +
> + dead = sw->hw->mix_buf.size - live;
> +
> +#ifdef DEBUG_OUT
> + dolog("%s: get_free live %zu dead %zu frontend frames %u\n",
> + SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead));
> +#endif
> +
> + return dead;
> +}
> +
> +static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
> + size_t samples)
> +{
> + size_t n;
> +
> + if (hw->enabled) {
> + SWVoiceCap *sc;
> +
> + for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> + SWVoiceOut *sw = &sc->sw;
> + size_t rpos2 = rpos;
> +
> + n = samples;
> + while (n) {
> + size_t till_end_of_hw = hw->mix_buf.size - rpos2;
> + size_t to_read = MIN(till_end_of_hw, n);
> + size_t live, frames_in, frames_out;
> +
> + sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;
> + sw->resample_buf.size = to_read;
> + live = sw->total_hw_samples_mixed;
> +
> + audio_pcm_sw_resample_out(sw,
> + to_read, sw->hw->mix_buf.size - live,
> + &frames_in, &frames_out);
> +
> + sw->total_hw_samples_mixed += frames_out;
> + sw->empty = sw->total_hw_samples_mixed == 0;
> +
> + if (to_read - frames_in) {
> + dolog("Could not mix %zu frames into a capture "
> + "buffer, mixed %zu\n",
> + to_read, frames_in);
> + break;
> + }
> + n -= to_read;
> + rpos2 = (rpos2 + to_read) % hw->mix_buf.size;
> + }
> + }
> + }
> +
> + n = MIN(samples, hw->mix_buf.size - rpos);
> + mixeng_clear(hw->mix_buf.buffer + rpos, n);
> + mixeng_clear(hw->mix_buf.buffer, samples - n);
> +}
> +
> +static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
> +{
> + size_t clipped = 0;
> +
> + while (live) {
> + size_t size = live * hw->info.bytes_per_frame;
> + size_t decr, proc;
> + void *buf = hw->pcm_ops->get_buffer_out(hw, &size);
> +
> + if (size == 0) {
> + break;
> + }
> +
> + decr = MIN(size / hw->info.bytes_per_frame, live);
> + if (buf) {
> + audio_pcm_hw_clip_out(hw, buf, decr);
> + }
> + proc = hw->pcm_ops->put_buffer_out(hw, buf,
> + decr * hw->info.bytes_per_frame) /
> + hw->info.bytes_per_frame;
> +
> + live -= proc;
> + clipped += proc;
> + hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size;
> +
> + if (proc == 0 || proc < decr) {
> + break;
> + }
> + }
> +
> + if (hw->pcm_ops->run_buffer_out) {
> + hw->pcm_ops->run_buffer_out(hw);
> + }
> +
> + return clipped;
> +}
> +
> +static void audio_run_out(AudioMixengBackend *s)
> +{
> + HWVoiceOut *hw = NULL;
> + SWVoiceOut *sw;
> +
> + while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
> + size_t played, live, prev_rpos;
> + size_t hw_free = audio_pcm_hw_get_free(hw);
> + int nb_live;
> +
> + if (!audio_get_pdo_out(s->dev)->mixing_engine) {
> + /* there is exactly 1 sw for each hw with no mixeng */
> + sw = hw->sw_head.lh_first;
> +
> + if (hw->pending_disable) {
> + hw->enabled = false;
> + hw->pending_disable = false;
> + if (hw->pcm_ops->enable_out) {
> + hw->pcm_ops->enable_out(hw, false);
> + }
> + }
> +
> + if (sw->active) {
> + sw->callback.fn(sw->callback.opaque,
> + hw_free * sw->info.bytes_per_frame);
> + }
> +
> + if (hw->pcm_ops->run_buffer_out) {
> + hw->pcm_ops->run_buffer_out(hw);
> + }
> +
> + continue;
> + }
> +
> + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> + if (sw->active) {
> + size_t sw_free = audio_get_free(sw);
> + size_t free;
> +
> + if (hw_free > sw->total_hw_samples_mixed) {
> + free = st_rate_frames_in(sw->rate,
> + MIN(sw_free, hw_free - sw->total_hw_samples_mixed));
> + } else {
> + free = 0;
> + }
> + if (free > sw->resample_buf.pos) {
> + free = MIN(free, sw->resample_buf.size)
> + - sw->resample_buf.pos;
> + sw->callback.fn(sw->callback.opaque,
> + free * sw->info.bytes_per_frame);
> + }
> + }
> + }
> +
> + live = audio_pcm_hw_get_live_out (hw, &nb_live);
> + if (!nb_live) {
> + live = 0;
> + }
> +
> + if (audio_bug(__func__, live > hw->mix_buf.size)) {
> + dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
> + continue;
> + }
> +
> + if (hw->pending_disable && !nb_live) {
> + SWVoiceCap *sc;
> +#ifdef DEBUG_OUT
> + dolog ("Disabling voice\n");
> +#endif
> + hw->enabled = false;
> + hw->pending_disable = false;
> + if (hw->pcm_ops->enable_out) {
> + hw->pcm_ops->enable_out(hw, false);
> + }
> + for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> + sc->sw.active = false;
> + audio_recalc_and_notify_capture (sc->cap);
> + }
> + continue;
> + }
> +
> + if (!live) {
> + if (hw->pcm_ops->run_buffer_out) {
> + hw->pcm_ops->run_buffer_out(hw);
> + }
> + continue;
> + }
> +
> + prev_rpos = hw->mix_buf.pos;
> + played = audio_pcm_hw_run_out(hw, live);
> + replay_audio_out(&played);
> + if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) {
> + dolog("hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\n",
> + hw->mix_buf.pos, hw->mix_buf.size, played);
> + hw->mix_buf.pos = 0;
> + }
> +
> +#ifdef DEBUG_OUT
> + dolog("played=%zu\n", played);
> +#endif
> +
> + if (played) {
> + audio_capture_mix_and_clear (hw, prev_rpos, played);
> + }
> +
> + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> + if (!sw->active && sw->empty) {
> + continue;
> + }
> +
> + if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) {
> + dolog("played=%zu sw->total_hw_samples_mixed=%zu\n",
> + played, sw->total_hw_samples_mixed);
> + played = sw->total_hw_samples_mixed;
> + }
> +
> + sw->total_hw_samples_mixed -= played;
> +
> + if (!sw->total_hw_samples_mixed) {
> + sw->empty = true;
> + }
> + }
> + }
> +}
> +
> +static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
> +{
> + size_t conv = 0;
> +
> + if (hw->pcm_ops->run_buffer_in) {
> + hw->pcm_ops->run_buffer_in(hw);
> + }
> +
> + while (samples) {
> + size_t proc;
> + size_t size = samples * hw->info.bytes_per_frame;
> + void *buf = hw->pcm_ops->get_buffer_in(hw, &size);
> +
> + assert(size % hw->info.bytes_per_frame == 0);
> + if (size == 0) {
> + break;
> + }
> +
> + proc = audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_frame);
> +
> + samples -= proc;
> + conv += proc;
> + hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame);
> + }
> +
> + return conv;
> +}
> +
> +static void audio_run_in(AudioMixengBackend *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;
> + int pos;
> +
> + if (replay_mode != REPLAY_MODE_PLAY) {
> + captured = audio_pcm_hw_run_in(
> + hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw));
> + }
> +
> + replay_audio_in_start(&captured);
> + assert(captured <= hw->conv_buf.size);
> + if (replay_mode == REPLAY_MODE_PLAY) {
> + hw->conv_buf.pos = (hw->conv_buf.pos + captured) % hw->conv_buf.size;
> + }
> + for (pos = (hw->conv_buf.pos - captured + hw->conv_buf.size) % hw->conv_buf.size;
> + pos != hw->conv_buf.pos;
> + pos = (pos + 1) % hw->conv_buf.size) {
> + uint64_t left, right;
> +
> + if (replay_mode == REPLAY_MODE_RECORD) {
> + audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left, &right);
> + }
> + replay_audio_in_sample_lr(&left, &right);
> + if (replay_mode == REPLAY_MODE_PLAY) {
> + audio_sample_from_uint64(hw->conv_buf.buffer, pos, left, right);
> + }
> + }
> + replay_audio_in_finish();
> +
> + min = audio_pcm_hw_find_min_in (hw);
> + hw->total_samples_captured += captured - min;
> +
> + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> + sw->total_hw_samples_acquired -= min;
> +
> + if (sw->active) {
> + size_t sw_avail = audio_get_avail(sw);
> + size_t avail;
> +
> + avail = st_rate_frames_out(sw->rate, sw_avail);
> + if (avail > 0) {
> + avail = MIN(avail, sw->resample_buf.size);
> + sw->callback.fn(sw->callback.opaque,
> + avail * sw->info.bytes_per_frame);
> + }
> + }
> + }
> + }
> +}
> +
> +static void audio_run_capture(AudioMixengBackend *s)
> +{
> + CaptureVoiceOut *cap;
> +
> + for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
> + size_t live, rpos, captured;
> + HWVoiceOut *hw = &cap->hw;
> + SWVoiceOut *sw;
> +
> + captured = live = audio_pcm_hw_get_live_out (hw, NULL);
> + rpos = hw->mix_buf.pos;
> + while (live) {
> + size_t left = hw->mix_buf.size - rpos;
> + size_t to_capture = MIN(live, left);
> + struct st_sample *src;
> + struct capture_callback *cb;
> +
> + src = hw->mix_buf.buffer + rpos;
> + hw->clip (cap->buf, src, to_capture);
> + mixeng_clear (src, to_capture);
> +
> + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> + cb->ops.capture (cb->opaque, cap->buf,
> + to_capture * hw->info.bytes_per_frame);
> + }
> + rpos = (rpos + to_capture) % hw->mix_buf.size;
> + live -= to_capture;
> + }
> + hw->mix_buf.pos = rpos;
> +
> + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> + if (!sw->active && sw->empty) {
> + continue;
> + }
> +
> + if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)) {
> + dolog("captured=%zu sw->total_hw_samples_mixed=%zu\n",
> + captured, sw->total_hw_samples_mixed);
> + captured = sw->total_hw_samples_mixed;
> + }
> +
> + sw->total_hw_samples_mixed -= captured;
> + sw->empty = sw->total_hw_samples_mixed == 0;
> + }
> + }
> +}
> +
> +void audio_run(AudioMixengBackend *s, const char *msg)
> +{
> + audio_run_out(s);
> + audio_run_in(s);
> + audio_run_capture(s);
> +
> +#ifdef DEBUG_POLL
> + {
> + static double prevtime;
> + double currtime;
> + struct timeval tv;
> +
> + if (gettimeofday (&tv, NULL)) {
> + perror ("audio_run: gettimeofday");
> + return;
> + }
> +
> + currtime = tv.tv_sec + tv.tv_usec * 1e-6;
> + dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime);
> + prevtime = currtime;
> + }
> +#endif
> +}
> +
> +void audio_generic_run_buffer_in(HWVoiceIn *hw)
> +{
> + if (unlikely(!hw->buf_emul)) {
> + hw->size_emul = hw->samples * hw->info.bytes_per_frame;
> + hw->buf_emul = g_malloc(hw->size_emul);
> + hw->pos_emul = hw->pending_emul = 0;
> + }
> +
> + while (hw->pending_emul < hw->size_emul) {
> + size_t read_len = MIN(hw->size_emul - hw->pos_emul,
> + hw->size_emul - hw->pending_emul);
> + size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul,
> + read_len);
> + hw->pending_emul += read;
> + hw->pos_emul = (hw->pos_emul + read) % hw->size_emul;
> + if (read < read_len) {
> + break;
> + }
> + }
> +}
> +
> +void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)
> +{
> + size_t start;
> +
> + start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
> + assert(start < hw->size_emul);
> +
> + *size = MIN(*size, hw->pending_emul);
> + *size = MIN(*size, hw->size_emul - start);
> + return hw->buf_emul + start;
> +}
> +
> +void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
> +{
> + assert(size <= hw->pending_emul);
> + hw->pending_emul -= size;
> +}
> +
> +size_t audio_generic_buffer_get_free(HWVoiceOut *hw)
> +{
> + if (hw->buf_emul) {
> + return hw->size_emul - hw->pending_emul;
> + } else {
> + return hw->samples * hw->info.bytes_per_frame;
> + }
> +}
> +
> +void audio_generic_run_buffer_out(HWVoiceOut *hw)
> +{
> + while (hw->pending_emul) {
> + size_t write_len, written, start;
> +
> + start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
> + assert(start < hw->size_emul);
> +
> + write_len = MIN(hw->pending_emul, hw->size_emul - start);
> +
> + written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len);
> + hw->pending_emul -= written;
> +
> + if (written < write_len) {
> + break;
> + }
> + }
> +}
> +
> +void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)
> +{
> + if (unlikely(!hw->buf_emul)) {
> + hw->size_emul = hw->samples * hw->info.bytes_per_frame;
> + hw->buf_emul = g_malloc(hw->size_emul);
> + hw->pos_emul = hw->pending_emul = 0;
> + }
> +
> + *size = MIN(hw->size_emul - hw->pending_emul,
> + hw->size_emul - hw->pos_emul);
> + return hw->buf_emul + hw->pos_emul;
> +}
> +
> +size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
> +{
> + assert(buf == hw->buf_emul + hw->pos_emul &&
> + size + hw->pending_emul <= hw->size_emul);
> +
> + hw->pending_emul += size;
> + hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;
> +
> + return size;
> +}
> +
> +size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size)
> +{
> + size_t total = 0;
> +
> + if (hw->pcm_ops->buffer_get_free) {
> + size_t free = hw->pcm_ops->buffer_get_free(hw);
> +
> + size = MIN(size, free);
> + }
> +
> + while (total < size) {
> + size_t dst_size = size - total;
> + size_t copy_size, proc;
> + void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size);
> +
> + if (dst_size == 0) {
> + break;
> + }
> +
> + copy_size = MIN(size - total, dst_size);
> + if (dst) {
> + memcpy(dst, (char *)buf + total, copy_size);
> + }
> + proc = hw->pcm_ops->put_buffer_out(hw, dst, copy_size);
> + total += proc;
> +
> + if (proc == 0 || proc < copy_size) {
> + break;
> + }
> + }
> +
> + return total;
> +}
> +
> +size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
> +{
> + size_t total = 0;
> +
> + if (hw->pcm_ops->run_buffer_in) {
> + hw->pcm_ops->run_buffer_in(hw);
> + }
> +
> + while (total < size) {
> + size_t src_size = size - total;
> + void *src = hw->pcm_ops->get_buffer_in(hw, &src_size);
> +
> + if (src_size == 0) {
> + break;
> + }
> +
> + memcpy((char *)buf + total, src, src_size);
> + hw->pcm_ops->put_buffer_in(hw, src, src_size);
> + total += src_size;
> + }
> +
> + return total;
> +}
> +
> +static bool audio_mixeng_backend_realize(AudioBackend *abe,
> + Audiodev *dev, Error **errp)
> +{
> + AudioMixengBackend *be = AUDIO_MIXENG_BACKEND(abe);
> + audio_driver *drv = AUDIO_MIXENG_BACKEND_GET_CLASS(be)->driver;
> +
> + be->dev = dev;
> + be->drv_opaque = drv->init(be->dev, errp);
> + if (!be->drv_opaque) {
> + return false;
> + }
> +
> + if (!drv->pcm_ops->get_buffer_in) {
> + drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in;
> + drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in;
> + }
> + if (!drv->pcm_ops->get_buffer_out) {
> + drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out;
> + drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;
> + }
> +
> + audio_init_nb_voices_out(be, drv, 1);
> + audio_init_nb_voices_in(be, drv, 0);
> + be->drv = drv;
> +
> + if (be->dev->timer_period <= 0) {
> + be->period_ticks = 1;
> + } else {
> + be->period_ticks = be->dev->timer_period * (int64_t)SCALE_US;
> + }
> +
> + return true;
> +}
> +
> +static void audio_vm_change_state_handler (void *opaque, bool running,
> + RunState state)
> +{
> + AudioMixengBackend *s = opaque;
> + HWVoiceOut *hwo = NULL;
> + HWVoiceIn *hwi = NULL;
> +
> + while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
> + if (hwo->pcm_ops->enable_out) {
> + hwo->pcm_ops->enable_out(hwo, running);
> + }
> + }
> +
> + while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
> + if (hwi->pcm_ops->enable_in) {
> + hwi->pcm_ops->enable_in(hwi, running);
> + }
> + }
> + audio_reset_timer (s);
> +}
> +
> +static const VMStateDescription vmstate_audio;
> +
> +static const char *audio_mixeng_backend_get_id(AudioBackend *be)
> +{
> + return AUDIO_MIXENG_BACKEND(be)->dev->id;
> +}
> +
> +static CaptureVoiceOut *audio_mixeng_backend_add_capture(
> + AudioBackend *be,
> + struct audsettings *as,
> + struct audio_capture_ops *ops,
> + void *cb_opaque);
> +
> +static void audio_mixeng_backend_del_capture(
> + AudioBackend *be,
> + CaptureVoiceOut *cap,
> + void *cb_opaque);
> +
> +static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
> + Volume *vol);
> +static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
> + Volume *vol);
> +
> +static void audio_mixeng_backend_class_init(ObjectClass *klass, const void *data)
> +{
> + AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
> +
> + be->realize = audio_mixeng_backend_realize;
> + be->get_id = audio_mixeng_backend_get_id;
> + be->open_in = audio_mixeng_backend_open_in;
> + be->open_out = audio_mixeng_backend_open_out;
> + be->close_in = audio_mixeng_backend_close_in;
> + be->close_out = audio_mixeng_backend_close_out;
> + be->is_active_out = audio_mixeng_backend_is_active_out;
> + be->is_active_in = audio_mixeng_backend_is_active_in;
> + be->set_active_out = audio_mixeng_backend_set_active_out;
> + be->set_active_in = audio_mixeng_backend_set_active_in;
> + be->set_volume_out = audio_mixeng_backend_set_volume_out;
> + be->set_volume_in = audio_mixeng_backend_set_volume_in;
> + be->read = audio_mixeng_backend_read;
> + be->write = audio_mixeng_backend_write;
> + be->get_buffer_size_out = audio_mixeng_backend_get_buffer_size_out;
> + be->add_capture = audio_mixeng_backend_add_capture;
> + be->del_capture = audio_mixeng_backend_del_capture;
> +}
> +
> +static void audio_mixeng_backend_init(Object *obj)
> +{
> + AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
> +
> + QLIST_INIT(&s->hw_head_out);
> + QLIST_INIT(&s->hw_head_in);
> + QLIST_INIT(&s->cap_head);
> + s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
> +
> + s->vmse = qemu_add_vm_change_state_handler(audio_vm_change_state_handler, s);
> + assert(s->vmse != NULL);
> +
> + vmstate_register_any(NULL, &vmstate_audio, s);
> +}
> +
> +static void audio_mixeng_backend_finalize(Object *obj)
> +{
> + AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
> + HWVoiceOut *hwo, *hwon;
> + HWVoiceIn *hwi, *hwin;
> +
> + QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) {
> + SWVoiceCap *sc;
> +
> + if (hwo->enabled && hwo->pcm_ops->enable_out) {
> + hwo->pcm_ops->enable_out(hwo, false);
> + }
> + hwo->pcm_ops->fini_out (hwo);
> +
> + for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> + CaptureVoiceOut *cap = sc->cap;
> + struct capture_callback *cb;
> +
> + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> + cb->ops.destroy (cb->opaque);
> + }
> + }
> + QLIST_REMOVE(hwo, entries);
> + }
> +
> + QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) {
> + if (hwi->enabled && hwi->pcm_ops->enable_in) {
> + hwi->pcm_ops->enable_in(hwi, false);
> + }
> + hwi->pcm_ops->fini_in (hwi);
> + QLIST_REMOVE(hwi, entries);
> + }
> +
> + if (s->drv) {
> + s->drv->fini (s->drv_opaque);
> + s->drv = NULL;
> + }
> +
> + if (s->dev) {
> + qapi_free_Audiodev(s->dev);
> + s->dev = NULL;
> + }
> +
> + if (s->ts) {
> + timer_free(s->ts);
> + s->ts = NULL;
> + }
> +
> + if (s->vmse) {
> + qemu_del_vm_change_state_handler(s->vmse);
> + s->vmse = NULL;
> + }
> +
> + vmstate_unregister(NULL, &vmstate_audio, s);
> +}
> +
> +static bool vmstate_audio_needed(void *opaque)
> +{
> + /*
> + * Never needed, this vmstate only exists in case
> + * an old qemu sends it to us.
> + */
> + return false;
> +}
> +
> +static const VMStateDescription vmstate_audio = {
> + .name = "audio",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .needed = vmstate_audio_needed,
> + .fields = (const VMStateField[]) {
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static struct audio_pcm_ops capture_pcm_ops;
> +
> +static CaptureVoiceOut *audio_mixeng_backend_add_capture(
> + AudioBackend *be,
> + struct audsettings *as,
> + struct audio_capture_ops *ops,
> + void *cb_opaque)
> +{
> + AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(be);
> + CaptureVoiceOut *cap;
> + struct capture_callback *cb;
> +
> + if (!s) {
> + error_report("Capturing without setting an audiodev is not supported");
> + abort();
> + }
> +
> + 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);
> + return NULL;
> + }
> +
> + cb = g_malloc0(sizeof(*cb));
> + cb->ops = *ops;
> + cb->opaque = cb_opaque;
> +
> + cap = audio_pcm_capture_find_specific(s, as);
> + if (cap) {
> + QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
> + } else {
> + HWVoiceOut *hw;
> +
> + cap = g_malloc0(sizeof(*cap));
> +
> + hw = &cap->hw;
> + hw->s = s;
> + hw->pcm_ops = &capture_pcm_ops;
> + QLIST_INIT (&hw->sw_head);
> + QLIST_INIT (&cap->cb_head);
> +
> + /* XXX find a more elegant way */
> + hw->samples = 4096 * 4;
> + audio_pcm_hw_alloc_resources_out(hw);
> +
> + audio_pcm_init_info (&hw->info, as);
> +
> + cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame);
> +
> + if (hw->info.is_float) {
> + hw->clip = mixeng_clip_float[hw->info.nchannels == 2]
> + [hw->info.swap_endianness];
> + } else {
> + hw->clip = mixeng_clip
> + [hw->info.nchannels == 2]
> + [hw->info.is_signed]
> + [hw->info.swap_endianness]
> + [audio_bits_to_index(hw->info.bits)];
> + }
> +
> + QLIST_INSERT_HEAD (&s->cap_head, cap, entries);
> + QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
> +
> + QLIST_FOREACH(hw, &s->hw_head_out, entries) {
> + audio_attach_capture (hw);
> + }
> + }
> +
> + return cap;
> +}
> +
> +static void audio_mixeng_backend_del_capture(
> + AudioBackend *be,
> + CaptureVoiceOut *cap,
> + void *cb_opaque)
> +{
> + struct capture_callback *cb;
> +
> + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> + if (cb->opaque == cb_opaque) {
> + cb->ops.destroy (cb_opaque);
> + QLIST_REMOVE (cb, entries);
> + g_free (cb);
> +
> + if (!cap->cb_head.lh_first) {
> + SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1;
> +
> + while (sw) {
> + SWVoiceCap *sc = (SWVoiceCap *) sw;
> +#ifdef DEBUG_CAPTURE
> + dolog ("freeing %s\n", sw->name);
> +#endif
> +
> + sw1 = sw->entries.le_next;
> + if (sw->rate) {
> + st_rate_stop (sw->rate);
> + sw->rate = NULL;
> + }
> + QLIST_REMOVE (sw, entries);
> + QLIST_REMOVE (sc, entries);
> + g_free (sc);
> + sw = sw1;
> + }
> + QLIST_REMOVE (cap, entries);
> + g_free(cap->hw.mix_buf.buffer);
> + g_free (cap->buf);
> + g_free (cap);
> + }
> + return;
> + }
> + }
> +}
> +
> +static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
> + Volume *vol)
> +{
> + if (sw) {
> + HWVoiceOut *hw = sw->hw;
> +
> + 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, vol);
> + }
> + }
> +}
> +
> +static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
> + Volume *vol)
> +{
> + if (sw) {
> + HWVoiceIn *hw = sw->hw;
> +
> + 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, vol);
> + }
> + }
> +}
> +
> +audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo)
> +{
> + return (audsettings) {
> + .freq = pdo->frequency,
> + .nchannels = pdo->channels,
> + .fmt = pdo->format,
> + .endianness = HOST_BIG_ENDIAN,
> + };
> +}
> +
> +int audioformat_bytes_per_sample(AudioFormat fmt)
> +{
> + switch (fmt) {
> + case AUDIO_FORMAT_U8:
> + case AUDIO_FORMAT_S8:
> + return 1;
> +
> + case AUDIO_FORMAT_U16:
> + case AUDIO_FORMAT_S16:
> + return 2;
> +
> + case AUDIO_FORMAT_U32:
> + case AUDIO_FORMAT_S32:
> + case AUDIO_FORMAT_F32:
> + return 4;
> +
> + case AUDIO_FORMAT__MAX:
> + ;
> + }
> + abort();
> +}
> +
> +
> +/* frames = freq * usec / 1e6 */
> +int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,
> + audsettings *as, int def_usecs)
> +{
> + uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs;
> + return (as->freq * usecs + 500000) / 1000000;
> +}
> +
> +/* samples = channels * frames = channels * freq * usec / 1e6 */
> +int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,
> + audsettings *as, int def_usecs)
> +{
> + return as->nchannels * audio_buffer_frames(pdo, as, def_usecs);
> +}
> +
> +/*
> + * bytes = bytes_per_sample * samples =
> + * bytes_per_sample * channels * freq * usec / 1e6
> + */
> +int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,
> + audsettings *as, int def_usecs)
> +{
> + return audio_buffer_samples(pdo, as, def_usecs) *
> + audioformat_bytes_per_sample(as->fmt);
> +}
> +
> +void audio_rate_start(RateCtl *rate)
> +{
> + memset(rate, 0, sizeof(RateCtl));
> + rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> +}
> +
> +size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info)
> +{
> + int64_t now;
> + int64_t ticks;
> + int64_t bytes;
> + int64_t frames;
> +
> + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> + ticks = now - rate->start_ticks;
> + bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);
> + frames = (bytes - rate->bytes_sent) / info->bytes_per_frame;
> + rate->peeked_frames = frames;
> +
> + return frames < 0 ? 0 : frames * info->bytes_per_frame;
> +}
> +
> +void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used)
> +{
> + if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) {
> + AUD_log(NULL, "Resetting rate control (%" PRId64 " frames)\n",
> + rate->peeked_frames);
> + audio_rate_start(rate);
> + }
> +
> + rate->bytes_sent += bytes_used;
> +}
> +
> +size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info,
> + size_t bytes_avail)
> +{
> + size_t bytes;
> +
> + bytes = audio_rate_peek_bytes(rate, info);
> + bytes = MIN(bytes, bytes_avail);
> + audio_rate_add_bytes(rate, bytes);
> +
> + return bytes;
> +}
> +
> +static const TypeInfo audio_mixeng_backend_info = {
> + .name = TYPE_AUDIO_MIXENG_BACKEND,
> + .parent = TYPE_AUDIO_BACKEND,
> + .instance_size = sizeof(AudioMixengBackend),
> + .instance_init = audio_mixeng_backend_init,
> + .instance_finalize = audio_mixeng_backend_finalize,
> + .abstract = false,
> + .class_size = sizeof(AudioMixengBackendClass),
> + .class_init = audio_mixeng_backend_class_init,
> +};
> +
> +static void register_types(void)
> +{
> + type_register_static(&audio_mixeng_backend_info);
> +}
> +
> +type_init(register_types);
> diff --git a/audio/audio.c b/audio/audio.c
> index 1d948084e80..6f10ee470ee 100644
> --- a/audio/audio.c
> +++ b/audio/audio.c
> @@ -1,1750 +1,55 @@
> -/*
> - * QEMU Audio subsystem
> - *
> - * Copyright (c) 2003-2005 Vassili Karpov (malc)
> - *
> - * Permission is hereby granted, free of charge, to any person obtaining a copy
> - * of this software and associated documentation files (the "Software"), to deal
> - * in the Software without restriction, including without limitation the rights
> - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> - * copies of the Software, and to permit persons to whom the Software is
> - * furnished to do so, subject to the following conditions:
> - *
> - * The above copyright notice and this permission notice shall be included in
> - * all copies or substantial portions of the Software.
> - *
> - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> - * THE SOFTWARE.
> - */
> -
> -#include "qemu/osdep.h"
> -#include "qemu/audio.h"
> -#include "migration/vmstate.h"
> -#include "qemu/timer.h"
> -#include "qapi/error.h"
> -#include "qapi/clone-visitor.h"
> -#include "qapi/qobject-input-visitor.h"
> -#include "qapi/qapi-visit-audio.h"
> -#include "qapi/qapi-commands-audio.h"
> -#include "qobject/qdict.h"
> -#include "qemu/error-report.h"
> -#include "qemu/log.h"
> -#include "qemu/module.h"
> -#include "qemu/help_option.h"
> -#include "qom/object.h"
> -#include "system/system.h"
> -#include "system/replay.h"
> -#include "system/runstate.h"
> -#include "trace.h"
> -
> -#define AUDIO_CAP "audio"
> -#include "audio_int.h"
> -
> -/* #define DEBUG_LIVE */
> -/* #define DEBUG_OUT */
> -/* #define DEBUG_CAPTURE */
> -/* #define DEBUG_POLL */
> -
> -#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown"
> -
> -
> -/* Order of CONFIG_AUDIO_DRIVERS is import.
> - The 1st one is the one used by default, that is the reason
> - that we generate the list.
> -*/
> -const char *audio_prio_list[] = {
> -#ifdef CONFIG_GIO
> - "dbus",
> -#endif
> - "spice",
> - CONFIG_AUDIO_DRIVERS
> - "none",
> - NULL
> -};
> -
> -typedef struct AudiodevListEntry {
> - Audiodev *dev;
> - QSIMPLEQ_ENTRY(AudiodevListEntry) next;
> -} AudiodevListEntry;
> -
> -typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;
> -
> -static AudiodevListHead audiodevs =
> - QSIMPLEQ_HEAD_INITIALIZER(audiodevs);
> -static AudiodevListHead default_audiodevs =
> - QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);
> -
> -static AudioBackendClass *audio_be_class_by_name(const char *name)
> -{
> - g_autofree char *tname = g_strconcat("audio-", name, NULL);
> - ObjectClass *oc = module_object_class_by_name(tname);
> -
> - if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) {
> - return NULL;
> - }
> -
> - return AUDIO_BACKEND_CLASS(oc);
> -}
> -
> -static AudioBackend *default_audio_be;
> -
> -const struct mixeng_volume nominal_volume = {
> - .mute = 0,
> -#ifdef FLOAT_MIXENG
> - .r = 1.0,
> - .l = 1.0,
> -#else
> - .r = 1ULL << 32,
> - .l = 1ULL << 32,
> -#endif
> -};
> -
> -int audio_bug (const char *funcname, int cond)
> -{
> - if (cond) {
> - static int shown;
> -
> - AUD_log (NULL, "A bug was just triggered in %s\n", funcname);
> - if (!shown) {
> - shown = 1;
> - AUD_log (NULL, "Save all your work and restart without audio\n");
> - AUD_log (NULL, "I am sorry\n");
> - }
> - AUD_log (NULL, "Context:\n");
> - }
> -
> - return cond;
> -}
> -
> -static inline int audio_bits_to_index (int bits)
> -{
> - switch (bits) {
> - case 8:
> - return 0;
> -
> - case 16:
> - return 1;
> -
> - case 32:
> - return 2;
> -
> - default:
> - audio_bug ("bits_to_index", 1);
> - AUD_log (NULL, "invalid bits %d\n", bits);
> - return 0;
> - }
> -}
> -
> -void AUD_vlog (const char *cap, const char *fmt, va_list ap)
> -{
> - if (cap) {
> - fprintf(stderr, "%s: ", cap);
> - }
> -
> - vfprintf(stderr, fmt, ap);
> -}
> -
> -void AUD_log (const char *cap, const char *fmt, ...)
> -{
> - va_list ap;
> -
> - va_start (ap, fmt);
> - AUD_vlog (cap, fmt, ap);
> - va_end (ap);
> -}
> -
> -static void audio_print_settings (const struct audsettings *as)
> -{
> - dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels);
> -
> - switch (as->fmt) {
> - case AUDIO_FORMAT_S8:
> - AUD_log (NULL, "S8");
> - break;
> - case AUDIO_FORMAT_U8:
> - AUD_log (NULL, "U8");
> - break;
> - case AUDIO_FORMAT_S16:
> - AUD_log (NULL, "S16");
> - break;
> - case AUDIO_FORMAT_U16:
> - AUD_log (NULL, "U16");
> - break;
> - case AUDIO_FORMAT_S32:
> - AUD_log (NULL, "S32");
> - break;
> - case AUDIO_FORMAT_U32:
> - AUD_log (NULL, "U32");
> - break;
> - case AUDIO_FORMAT_F32:
> - AUD_log (NULL, "F32");
> - break;
> - default:
> - AUD_log (NULL, "invalid(%d)", as->fmt);
> - break;
> - }
> -
> - AUD_log (NULL, " endianness=");
> - switch (as->endianness) {
> - case 0:
> - AUD_log (NULL, "little");
> - break;
> - case 1:
> - AUD_log (NULL, "big");
> - break;
> - default:
> - AUD_log (NULL, "invalid");
> - break;
> - }
> - AUD_log (NULL, "\n");
> -}
> -
> -static int audio_validate_settings (const struct audsettings *as)
> -{
> - int invalid;
> -
> - invalid = as->nchannels < 1;
> - invalid |= as->endianness != 0 && as->endianness != 1;
> -
> - switch (as->fmt) {
> - case AUDIO_FORMAT_S8:
> - case AUDIO_FORMAT_U8:
> - case AUDIO_FORMAT_S16:
> - case AUDIO_FORMAT_U16:
> - case AUDIO_FORMAT_S32:
> - case AUDIO_FORMAT_U32:
> - case AUDIO_FORMAT_F32:
> - break;
> - default:
> - invalid = 1;
> - break;
> - }
> -
> - invalid |= as->freq <= 0;
> - return invalid ? -1 : 0;
> -}
> -
> -static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct audsettings *as)
> -{
> - int bits = 8;
> - bool is_signed = false, is_float = false;
> -
> - switch (as->fmt) {
> - case AUDIO_FORMAT_S8:
> - is_signed = true;
> - /* fall through */
> - case AUDIO_FORMAT_U8:
> - break;
> -
> - case AUDIO_FORMAT_S16:
> - is_signed = true;
> - /* fall through */
> - case AUDIO_FORMAT_U16:
> - bits = 16;
> - break;
> -
> - case AUDIO_FORMAT_F32:
> - is_float = true;
> - /* fall through */
> - case AUDIO_FORMAT_S32:
> - is_signed = true;
> - /* fall through */
> - case AUDIO_FORMAT_U32:
> - bits = 32;
> - break;
> -
> - default:
> - abort();
> - }
> - return info->freq == as->freq
> - && info->nchannels == as->nchannels
> - && info->is_signed == is_signed
> - && info->is_float == is_float
> - && info->bits == bits
> - && info->swap_endianness == (as->endianness != HOST_BIG_ENDIAN);
> -}
> -
> -void audio_pcm_init_info (struct audio_pcm_info *info, const struct audsettings *as)
> -{
> - int bits = 8, mul;
> - bool is_signed = false, is_float = false;
> -
> - switch (as->fmt) {
> - case AUDIO_FORMAT_S8:
> - is_signed = true;
> - /* fall through */
> - case AUDIO_FORMAT_U8:
> - mul = 1;
> - break;
> -
> - case AUDIO_FORMAT_S16:
> - is_signed = true;
> - /* fall through */
> - case AUDIO_FORMAT_U16:
> - bits = 16;
> - mul = 2;
> - break;
> -
> - case AUDIO_FORMAT_F32:
> - is_float = true;
> - /* fall through */
> - case AUDIO_FORMAT_S32:
> - is_signed = true;
> - /* fall through */
> - case AUDIO_FORMAT_U32:
> - bits = 32;
> - mul = 4;
> - break;
> -
> - default:
> - abort();
> - }
> -
> - info->freq = as->freq;
> - info->bits = bits;
> - info->is_signed = is_signed;
> - info->is_float = is_float;
> - info->nchannels = as->nchannels;
> - info->bytes_per_frame = as->nchannels * mul;
> - info->bytes_per_second = info->freq * info->bytes_per_frame;
> - info->swap_endianness = (as->endianness != HOST_BIG_ENDIAN);
> -}
> -
> -void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
> -{
> - if (!len) {
> - return;
> - }
> -
> - if (info->is_signed || info->is_float) {
> - memset(buf, 0x00, len * info->bytes_per_frame);
> - } else {
> - switch (info->bits) {
> - case 8:
> - memset(buf, 0x80, len * info->bytes_per_frame);
> - break;
> -
> - case 16:
> - {
> - int i;
> - uint16_t *p = buf;
> - short s = INT16_MAX;
> -
> - if (info->swap_endianness) {
> - s = bswap16 (s);
> - }
> -
> - for (i = 0; i < len * info->nchannels; i++) {
> - p[i] = s;
> - }
> - }
> - break;
> -
> - case 32:
> - {
> - int i;
> - uint32_t *p = buf;
> - int32_t s = INT32_MAX;
> -
> - if (info->swap_endianness) {
> - s = bswap32 (s);
> - }
> -
> - for (i = 0; i < len * info->nchannels; i++) {
> - p[i] = s;
> - }
> - }
> - break;
> -
> - default:
> - AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n",
> - info->bits);
> - break;
> - }
> - }
> -}
> -
> -/*
> - * Capture
> - */
> -static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioMixengBackend *s,
> - struct audsettings *as)
> -{
> - CaptureVoiceOut *cap;
> -
> - for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
> - if (audio_pcm_info_eq (&cap->hw.info, as)) {
> - return cap;
> - }
> - }
> - return NULL;
> -}
> -
> -static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd)
> -{
> - struct capture_callback *cb;
> -
> -#ifdef DEBUG_CAPTURE
> - dolog ("notification %d sent\n", cmd);
> -#endif
> - for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> - cb->ops.notify (cb->opaque, cmd);
> - }
> -}
> -
> -static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled)
> -{
> - if (cap->hw.enabled != enabled) {
> - audcnotification_e cmd;
> - cap->hw.enabled = enabled;
> - cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE;
> - audio_notify_capture (cap, cmd);
> - }
> -}
> -
> -static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap)
> -{
> - HWVoiceOut *hw = &cap->hw;
> - SWVoiceOut *sw;
> - bool enabled = false;
> -
> - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> - if (sw->active) {
> - enabled = true;
> - break;
> - }
> - }
> - audio_capture_maybe_changed (cap, enabled);
> -}
> -
> -static void audio_detach_capture (HWVoiceOut *hw)
> -{
> - SWVoiceCap *sc = hw->cap_head.lh_first;
> -
> - while (sc) {
> - SWVoiceCap *sc1 = sc->entries.le_next;
> - SWVoiceOut *sw = &sc->sw;
> - CaptureVoiceOut *cap = sc->cap;
> - int was_active = sw->active;
> -
> - if (sw->rate) {
> - st_rate_stop (sw->rate);
> - sw->rate = NULL;
> - }
> -
> - QLIST_REMOVE (sw, entries);
> - QLIST_REMOVE (sc, entries);
> - g_free (sc);
> - if (was_active) {
> - /* We have removed soft voice from the capture:
> - this might have changed the overall status of the capture
> - since this might have been the only active voice */
> - audio_recalc_and_notify_capture (cap);
> - }
> - sc = sc1;
> - }
> -}
> -
> -static int audio_attach_capture (HWVoiceOut *hw)
> -{
> - AudioMixengBackend *s = hw->s;
> - CaptureVoiceOut *cap;
> -
> - audio_detach_capture (hw);
> - for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
> - SWVoiceCap *sc;
> - SWVoiceOut *sw;
> - HWVoiceOut *hw_cap = &cap->hw;
> -
> - sc = g_malloc0(sizeof(*sc));
> -
> - sc->cap = cap;
> - sw = &sc->sw;
> - sw->hw = hw_cap;
> - sw->info = hw->info;
> - sw->empty = true;
> - sw->active = hw->enabled;
> - sw->vol = nominal_volume;
> - sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
> - QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
> - QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);
> -#ifdef DEBUG_CAPTURE
> - sw->name = g_strdup_printf ("for %p %d,%d,%d",
> - hw, sw->info.freq, sw->info.bits,
> - sw->info.nchannels);
> - dolog ("Added %s active = %d\n", sw->name, sw->active);
> -#endif
> - if (sw->active) {
> - audio_capture_maybe_changed (cap, 1);
> - }
> - }
> - return 0;
> -}
> -
> -/*
> - * Hard voice (capture)
> - */
> -static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)
> -{
> - SWVoiceIn *sw;
> - size_t m = hw->total_samples_captured;
> -
> - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> - if (sw->active) {
> - m = MIN (m, sw->total_hw_samples_acquired);
> - }
> - }
> - return m;
> -}
> -
> -static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
> -{
> - size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
> - if (audio_bug(__func__, live > hw->conv_buf.size)) {
> - dolog("live=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
> - return 0;
> - }
> - return live;
> -}
> -
> -static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
> -{
> - size_t conv = 0;
> - STSampleBuffer *conv_buf = &hw->conv_buf;
> -
> - while (samples) {
> - uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);
> - size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);
> -
> - hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);
> - conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
> - samples -= proc;
> - conv += proc;
> - }
> -
> - return conv;
> -}
> -
> -/*
> - * Soft voice (capture)
> - */
> -static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
> - size_t frames_in_max, size_t frames_out_max,
> - size_t *total_in, size_t *total_out)
> -{
> - HWVoiceIn *hw = sw->hw;
> - struct st_sample *src, *dst;
> - size_t live, rpos, frames_in, frames_out;
> -
> - live = hw->total_samples_captured - sw->total_hw_samples_acquired;
> - rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
> -
> - /* resample conv_buf from rpos to end of buffer */
> - src = hw->conv_buf.buffer + rpos;
> - frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos);
> - dst = sw->resample_buf.buffer;
> - frames_out = frames_out_max;
> - st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
> - rpos += frames_in;
> - *total_in = frames_in;
> - *total_out = frames_out;
> -
> - /* resample conv_buf from start of buffer if there are input frames left */
> - if (frames_in_max - frames_in && rpos == hw->conv_buf.size) {
> - src = hw->conv_buf.buffer;
> - frames_in = frames_in_max - frames_in;
> - dst += frames_out;
> - frames_out = frames_out_max - frames_out;
> - st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
> - *total_in += frames_in;
> - *total_out += frames_out;
> - }
> -}
> -
> -static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
> -{
> - HWVoiceIn *hw = sw->hw;
> - size_t live, frames_out_max, total_in, total_out;
> -
> - live = hw->total_samples_captured - sw->total_hw_samples_acquired;
> - if (!live) {
> - return 0;
> - }
> - if (audio_bug(__func__, live > hw->conv_buf.size)) {
> - dolog("live_in=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
> - return 0;
> - }
> -
> - frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,
> - sw->resample_buf.size);
> -
> - audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);
> -
> - if (!hw->pcm_ops->volume_in) {
> - mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);
> - }
> - sw->clip(buf, sw->resample_buf.buffer, total_out);
> -
> - sw->total_hw_samples_acquired += total_in;
> - return total_out * sw->info.bytes_per_frame;
> -}
> -
> -/*
> - * Hard voice (playback)
> - */
> -static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)
> -{
> - SWVoiceOut *sw;
> - size_t m = SIZE_MAX;
> - int nb_live = 0;
> -
> - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> - if (sw->active || !sw->empty) {
> - m = MIN (m, sw->total_hw_samples_mixed);
> - nb_live += 1;
> - }
> - }
> -
> - *nb_livep = nb_live;
> - return m;
> -}
> -
> -static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)
> -{
> - size_t smin;
> - int nb_live1;
> -
> - smin = audio_pcm_hw_find_min_out (hw, &nb_live1);
> - if (nb_live) {
> - *nb_live = nb_live1;
> - }
> -
> - if (nb_live1) {
> - size_t live = smin;
> -
> - if (audio_bug(__func__, live > hw->mix_buf.size)) {
> - dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
> - return 0;
> - }
> - return live;
> - }
> - return 0;
> -}
> -
> -static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)
> -{
> - return (hw->pcm_ops->buffer_get_free ? hw->pcm_ops->buffer_get_free(hw) :
> - INT_MAX) / hw->info.bytes_per_frame;
> -}
> -
> -static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
> -{
> - size_t clipped = 0;
> - size_t pos = hw->mix_buf.pos;
> -
> - while (len) {
> - st_sample *src = hw->mix_buf.buffer + pos;
> - 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);
> -
> - hw->clip(dst, src, samples_to_clip);
> -
> - pos = (pos + samples_to_clip) % hw->mix_buf.size;
> - len -= samples_to_clip;
> - clipped += samples_to_clip;
> - }
> -}
> -
> -/*
> - * Soft voice (playback)
> - */
> -static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
> - size_t frames_in_max, size_t frames_out_max,
> - size_t *total_in, size_t *total_out)
> -{
> - HWVoiceOut *hw = sw->hw;
> - struct st_sample *src, *dst;
> - size_t live, wpos, frames_in, frames_out;
> -
> - live = sw->total_hw_samples_mixed;
> - wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;
> -
> - /* write to mix_buf from wpos to end of buffer */
> - src = sw->resample_buf.buffer;
> - frames_in = frames_in_max;
> - dst = hw->mix_buf.buffer + wpos;
> - frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);
> - st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
> - wpos += frames_out;
> - *total_in = frames_in;
> - *total_out = frames_out;
> -
> - /* write to mix_buf from start of buffer if there are input frames left */
> - if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {
> - src += frames_in;
> - frames_in = frames_in_max - frames_in;
> - dst = hw->mix_buf.buffer;
> - frames_out = frames_out_max - frames_out;
> - st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
> - *total_in += frames_in;
> - *total_out += frames_out;
> - }
> -}
> -
> -static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
> -{
> - HWVoiceOut *hw = sw->hw;
> - size_t live, dead, hw_free, sw_max, fe_max;
> - size_t frames_in_max, frames_out_max, total_in, total_out;
> -
> - live = sw->total_hw_samples_mixed;
> - if (audio_bug(__func__, live > hw->mix_buf.size)) {
> - dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
> - return 0;
> - }
> -
> - if (live == hw->mix_buf.size) {
> -#ifdef DEBUG_OUT
> - dolog ("%s is full %zu\n", sw->name, live);
> -#endif
> - return 0;
> - }
> -
> - dead = hw->mix_buf.size - live;
> - hw_free = audio_pcm_hw_get_free(hw);
> - hw_free = hw_free > live ? hw_free - live : 0;
> - frames_out_max = MIN(dead, hw_free);
> - sw_max = st_rate_frames_in(sw->rate, frames_out_max);
> - fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,
> - sw->resample_buf.size);
> - frames_in_max = MIN(sw_max, fe_max);
> -
> - if (!frames_in_max) {
> - return 0;
> - }
> -
> - if (frames_in_max > sw->resample_buf.pos) {
> - sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,
> - buf, frames_in_max - sw->resample_buf.pos);
> - if (!sw->hw->pcm_ops->volume_out) {
> - mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,
> - frames_in_max - sw->resample_buf.pos, &sw->vol);
> - }
> - }
> -
> - audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,
> - &total_in, &total_out);
> -
> - sw->total_hw_samples_mixed += total_out;
> - sw->empty = sw->total_hw_samples_mixed == 0;
> -
> - /*
> - * Upsampling may leave one audio frame in the resample buffer. Decrement
> - * total_in by one if there was a leftover frame from the previous resample
> - * pass in the resample buffer. Increment total_in by one if the current
> - * resample pass left one frame in the resample buffer.
> - */
> - if (frames_in_max - total_in == 1) {
> - /* copy one leftover audio frame to the beginning of the buffer */
> - *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);
> - total_in += 1 - sw->resample_buf.pos;
> - sw->resample_buf.pos = 1;
> - } else if (total_in >= sw->resample_buf.pos) {
> - total_in -= sw->resample_buf.pos;
> - sw->resample_buf.pos = 0;
> - }
> -
> -#ifdef DEBUG_OUT
> - dolog (
> - "%s: write size %zu written %zu total mixed %zu\n",
> - SW_NAME(sw),
> - buf_len / sw->info.bytes_per_frame,
> - total_in,
> - sw->total_hw_samples_mixed
> - );
> -#endif
> -
> - return total_in * sw->info.bytes_per_frame;
> -}
> -
> -#ifdef DEBUG_AUDIO
> -static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
> -{
> - dolog("%s: bits %d, sign %d, float %d, freq %d, nchan %d\n",
> - cap, info->bits, info->is_signed, info->is_float, info->freq,
> - info->nchannels);
> -}
> -#endif
> -
> -#define DAC
> -#include "audio_template.h"
> -#undef DAC
> -#include "audio_template.h"
> -
> -/*
> - * Timer
> - */
> -static int audio_is_timer_needed(AudioMixengBackend *s)
> -{
> - HWVoiceIn *hwi = NULL;
> - HWVoiceOut *hwo = NULL;
> -
> - while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
> - if (!hwo->poll_mode) {
> - return 1;
> - }
> - }
> - while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
> - if (!hwi->poll_mode) {
> - return 1;
> - }
> - }
> - return 0;
> -}
> -
> -static void audio_reset_timer(AudioMixengBackend *s)
> -{
> - if (audio_is_timer_needed(s)) {
> - timer_mod_anticipate_ns(s->ts,
> - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
> - if (!s->timer_running) {
> - s->timer_running = true;
> - s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> - trace_audio_timer_start(s->period_ticks / SCALE_MS);
> - }
> - } else {
> - timer_del(s->ts);
> - if (s->timer_running) {
> - s->timer_running = false;
> - trace_audio_timer_stop();
> - }
> - }
> -}
> -
> -static void audio_timer (void *opaque)
> -{
> - int64_t now, diff;
> - AudioMixengBackend *s = opaque;
> -
> - now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> - diff = now - s->timer_last;
> - if (diff > s->period_ticks * 3 / 2) {
> - trace_audio_timer_delayed(diff / SCALE_MS);
> - }
> - s->timer_last = now;
> -
> - audio_run(s, "timer");
> - audio_reset_timer(s);
> -}
> -
> -/*
> - * Public API
> - */
> -static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw,
> - void *buf, size_t size)
> -{
> - HWVoiceOut *hw;
> -
> - if (!sw) {
> - /* XXX: Consider options */
> - return size;
> - }
> - hw = sw->hw;
> -
> - if (!hw->enabled) {
> - dolog("Writing to disabled voice %s\n", SW_NAME(sw));
> - return 0;
> - }
> -
> - 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);
> - }
> -}
> -
> -static size_t audio_mixeng_backend_read(AudioBackend *be,
> - SWVoiceIn *sw, void *buf, size_t size)
> -{
> - HWVoiceIn *hw;
> -
> - if (!sw) {
> - /* XXX: Consider options */
> - return size;
> - }
> - hw = sw->hw;
> -
> - if (!hw->enabled) {
> - dolog("Reading from disabled voice %s\n", SW_NAME(sw));
> - return 0;
> - }
> -
> - 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);
> - }
> -
> -}
> -
> -static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
> -{
> - if (!sw) {
> - return 0;
> - }
> -
> - if (audio_get_pdo_out(sw->s->dev)->mixing_engine) {
> - return sw->resample_buf.size * sw->info.bytes_per_frame;
> - }
> -
> - return sw->hw->samples * sw->hw->info.bytes_per_frame;
> -}
> -
> -static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceOut *sw,
> - bool on)
> -{
> - HWVoiceOut *hw;
> -
> - if (!sw) {
> - return;
> - }
> -
> - hw = sw->hw;
> - if (sw->active != on) {
> - AudioMixengBackend *s = sw->s;
> - SWVoiceOut *temp_sw;
> - SWVoiceCap *sc;
> -
> - if (on) {
> - hw->pending_disable = 0;
> - if (!hw->enabled) {
> - hw->enabled = true;
> - if (runstate_is_running()) {
> - if (hw->pcm_ops->enable_out) {
> - hw->pcm_ops->enable_out(hw, true);
> - }
> - audio_reset_timer (s);
> - }
> - }
> - } else {
> - if (hw->enabled) {
> - int nb_active = 0;
> -
> - for (temp_sw = hw->sw_head.lh_first; temp_sw;
> - temp_sw = temp_sw->entries.le_next) {
> - nb_active += temp_sw->active != 0;
> - }
> -
> - hw->pending_disable = nb_active == 1;
> - }
> - }
> -
> - for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> - sc->sw.active = hw->enabled;
> - if (hw->enabled) {
> - audio_capture_maybe_changed (sc->cap, 1);
> - }
> - }
> - sw->active = on;
> - }
> -
> -}
> -
> -static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
> -{
> - HWVoiceIn *hw;
> -
> - if (!sw) {
> - return;
> - }
> -
> - hw = sw->hw;
> - if (sw->active != on) {
> - AudioMixengBackend *s = sw->s;
> - SWVoiceIn *temp_sw;
> -
> - if (on) {
> - if (!hw->enabled) {
> - hw->enabled = true;
> - if (runstate_is_running()) {
> - if (hw->pcm_ops->enable_in) {
> - hw->pcm_ops->enable_in(hw, true);
> - }
> - audio_reset_timer (s);
> - }
> - }
> - sw->total_hw_samples_acquired = hw->total_samples_captured;
> - } else {
> - if (hw->enabled) {
> - int nb_active = 0;
> -
> - for (temp_sw = hw->sw_head.lh_first; temp_sw;
> - temp_sw = temp_sw->entries.le_next) {
> - nb_active += temp_sw->active != 0;
> - }
> -
> - if (nb_active == 1) {
> - hw->enabled = false;
> - if (hw->pcm_ops->enable_in) {
> - hw->pcm_ops->enable_in(hw, false);
> - }
> - }
> - }
> - }
> - sw->active = on;
> - }
> -}
> -
> -static size_t audio_get_avail(SWVoiceIn *sw)
> -{
> - size_t live;
> -
> - if (!sw) {
> - return 0;
> - }
> -
> - live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
> - if (audio_bug(__func__, live > sw->hw->conv_buf.size)) {
> - dolog("live=%zu sw->hw->conv_buf.size=%zu\n", live,
> - sw->hw->conv_buf.size);
> - return 0;
> - }
> -
> - ldebug (
> - "%s: get_avail live %zu frontend frames %u\n",
> - SW_NAME (sw),
> - live, st_rate_frames_out(sw->rate, live)
> - );
> -
> - return live;
> -}
> -
> -static size_t audio_get_free(SWVoiceOut *sw)
> -{
> - size_t live, dead;
> -
> - if (!sw) {
> - return 0;
> - }
> -
> - live = sw->total_hw_samples_mixed;
> -
> - if (audio_bug(__func__, live > sw->hw->mix_buf.size)) {
> - dolog("live=%zu sw->hw->mix_buf.size=%zu\n", live,
> - sw->hw->mix_buf.size);
> - return 0;
> - }
> -
> - dead = sw->hw->mix_buf.size - live;
> -
> -#ifdef DEBUG_OUT
> - dolog("%s: get_free live %zu dead %zu frontend frames %u\n",
> - SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead));
> -#endif
> -
> - return dead;
> -}
> -
> -static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
> - size_t samples)
> -{
> - size_t n;
> -
> - if (hw->enabled) {
> - SWVoiceCap *sc;
> -
> - for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> - SWVoiceOut *sw = &sc->sw;
> - size_t rpos2 = rpos;
> -
> - n = samples;
> - while (n) {
> - size_t till_end_of_hw = hw->mix_buf.size - rpos2;
> - size_t to_read = MIN(till_end_of_hw, n);
> - size_t live, frames_in, frames_out;
> -
> - sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;
> - sw->resample_buf.size = to_read;
> - live = sw->total_hw_samples_mixed;
> -
> - audio_pcm_sw_resample_out(sw,
> - to_read, sw->hw->mix_buf.size - live,
> - &frames_in, &frames_out);
> -
> - sw->total_hw_samples_mixed += frames_out;
> - sw->empty = sw->total_hw_samples_mixed == 0;
> -
> - if (to_read - frames_in) {
> - dolog("Could not mix %zu frames into a capture "
> - "buffer, mixed %zu\n",
> - to_read, frames_in);
> - break;
> - }
> - n -= to_read;
> - rpos2 = (rpos2 + to_read) % hw->mix_buf.size;
> - }
> - }
> - }
> -
> - n = MIN(samples, hw->mix_buf.size - rpos);
> - mixeng_clear(hw->mix_buf.buffer + rpos, n);
> - mixeng_clear(hw->mix_buf.buffer, samples - n);
> -}
> -
> -static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
> -{
> - size_t clipped = 0;
> -
> - while (live) {
> - size_t size = live * hw->info.bytes_per_frame;
> - size_t decr, proc;
> - void *buf = hw->pcm_ops->get_buffer_out(hw, &size);
> -
> - if (size == 0) {
> - break;
> - }
> -
> - decr = MIN(size / hw->info.bytes_per_frame, live);
> - if (buf) {
> - audio_pcm_hw_clip_out(hw, buf, decr);
> - }
> - proc = hw->pcm_ops->put_buffer_out(hw, buf,
> - decr * hw->info.bytes_per_frame) /
> - hw->info.bytes_per_frame;
> -
> - live -= proc;
> - clipped += proc;
> - hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size;
> -
> - if (proc == 0 || proc < decr) {
> - break;
> - }
> - }
> -
> - if (hw->pcm_ops->run_buffer_out) {
> - hw->pcm_ops->run_buffer_out(hw);
> - }
> -
> - return clipped;
> -}
> -
> -static void audio_run_out(AudioMixengBackend *s)
> -{
> - HWVoiceOut *hw = NULL;
> - SWVoiceOut *sw;
> -
> - while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {
> - size_t played, live, prev_rpos;
> - size_t hw_free = audio_pcm_hw_get_free(hw);
> - int nb_live;
> -
> - if (!audio_get_pdo_out(s->dev)->mixing_engine) {
> - /* there is exactly 1 sw for each hw with no mixeng */
> - sw = hw->sw_head.lh_first;
> -
> - if (hw->pending_disable) {
> - hw->enabled = false;
> - hw->pending_disable = false;
> - if (hw->pcm_ops->enable_out) {
> - hw->pcm_ops->enable_out(hw, false);
> - }
> - }
> -
> - if (sw->active) {
> - sw->callback.fn(sw->callback.opaque,
> - hw_free * sw->info.bytes_per_frame);
> - }
> -
> - if (hw->pcm_ops->run_buffer_out) {
> - hw->pcm_ops->run_buffer_out(hw);
> - }
> -
> - continue;
> - }
> -
> - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> - if (sw->active) {
> - size_t sw_free = audio_get_free(sw);
> - size_t free;
> -
> - if (hw_free > sw->total_hw_samples_mixed) {
> - free = st_rate_frames_in(sw->rate,
> - MIN(sw_free, hw_free - sw->total_hw_samples_mixed));
> - } else {
> - free = 0;
> - }
> - if (free > sw->resample_buf.pos) {
> - free = MIN(free, sw->resample_buf.size)
> - - sw->resample_buf.pos;
> - sw->callback.fn(sw->callback.opaque,
> - free * sw->info.bytes_per_frame);
> - }
> - }
> - }
> -
> - live = audio_pcm_hw_get_live_out (hw, &nb_live);
> - if (!nb_live) {
> - live = 0;
> - }
> -
> - if (audio_bug(__func__, live > hw->mix_buf.size)) {
> - dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
> - continue;
> - }
> -
> - if (hw->pending_disable && !nb_live) {
> - SWVoiceCap *sc;
> -#ifdef DEBUG_OUT
> - dolog ("Disabling voice\n");
> -#endif
> - hw->enabled = false;
> - hw->pending_disable = false;
> - if (hw->pcm_ops->enable_out) {
> - hw->pcm_ops->enable_out(hw, false);
> - }
> - for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> - sc->sw.active = false;
> - audio_recalc_and_notify_capture (sc->cap);
> - }
> - continue;
> - }
> -
> - if (!live) {
> - if (hw->pcm_ops->run_buffer_out) {
> - hw->pcm_ops->run_buffer_out(hw);
> - }
> - continue;
> - }
> -
> - prev_rpos = hw->mix_buf.pos;
> - played = audio_pcm_hw_run_out(hw, live);
> - replay_audio_out(&played);
> - if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) {
> - dolog("hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\n",
> - hw->mix_buf.pos, hw->mix_buf.size, played);
> - hw->mix_buf.pos = 0;
> - }
> -
> -#ifdef DEBUG_OUT
> - dolog("played=%zu\n", played);
> -#endif
> -
> - if (played) {
> - audio_capture_mix_and_clear (hw, prev_rpos, played);
> - }
> -
> - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> - if (!sw->active && sw->empty) {
> - continue;
> - }
> -
> - if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) {
> - dolog("played=%zu sw->total_hw_samples_mixed=%zu\n",
> - played, sw->total_hw_samples_mixed);
> - played = sw->total_hw_samples_mixed;
> - }
> -
> - sw->total_hw_samples_mixed -= played;
> -
> - if (!sw->total_hw_samples_mixed) {
> - sw->empty = true;
> - }
> - }
> - }
> -}
> -
> -static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
> -{
> - size_t conv = 0;
> -
> - if (hw->pcm_ops->run_buffer_in) {
> - hw->pcm_ops->run_buffer_in(hw);
> - }
> -
> - while (samples) {
> - size_t proc;
> - size_t size = samples * hw->info.bytes_per_frame;
> - void *buf = hw->pcm_ops->get_buffer_in(hw, &size);
> -
> - assert(size % hw->info.bytes_per_frame == 0);
> - if (size == 0) {
> - break;
> - }
> -
> - proc = audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_frame);
> -
> - samples -= proc;
> - conv += proc;
> - hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame);
> - }
> -
> - return conv;
> -}
> -
> -static void audio_run_in(AudioMixengBackend *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;
> - int pos;
> -
> - if (replay_mode != REPLAY_MODE_PLAY) {
> - captured = audio_pcm_hw_run_in(
> - hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw));
> - }
> -
> - replay_audio_in_start(&captured);
> - assert(captured <= hw->conv_buf.size);
> - if (replay_mode == REPLAY_MODE_PLAY) {
> - hw->conv_buf.pos = (hw->conv_buf.pos + captured) % hw->conv_buf.size;
> - }
> - for (pos = (hw->conv_buf.pos - captured + hw->conv_buf.size) % hw->conv_buf.size;
> - pos != hw->conv_buf.pos;
> - pos = (pos + 1) % hw->conv_buf.size) {
> - uint64_t left, right;
> -
> - if (replay_mode == REPLAY_MODE_RECORD) {
> - audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left, &right);
> - }
> - replay_audio_in_sample_lr(&left, &right);
> - if (replay_mode == REPLAY_MODE_PLAY) {
> - audio_sample_from_uint64(hw->conv_buf.buffer, pos, left, right);
> - }
> - }
> - replay_audio_in_finish();
> -
> - min = audio_pcm_hw_find_min_in (hw);
> - hw->total_samples_captured += captured - min;
> -
> - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> - sw->total_hw_samples_acquired -= min;
> -
> - if (sw->active) {
> - size_t sw_avail = audio_get_avail(sw);
> - size_t avail;
> -
> - avail = st_rate_frames_out(sw->rate, sw_avail);
> - if (avail > 0) {
> - avail = MIN(avail, sw->resample_buf.size);
> - sw->callback.fn(sw->callback.opaque,
> - avail * sw->info.bytes_per_frame);
> - }
> - }
> - }
> - }
> -}
> -
> -static void audio_run_capture(AudioMixengBackend *s)
> -{
> - CaptureVoiceOut *cap;
> -
> - for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
> - size_t live, rpos, captured;
> - HWVoiceOut *hw = &cap->hw;
> - SWVoiceOut *sw;
> -
> - captured = live = audio_pcm_hw_get_live_out (hw, NULL);
> - rpos = hw->mix_buf.pos;
> - while (live) {
> - size_t left = hw->mix_buf.size - rpos;
> - size_t to_capture = MIN(live, left);
> - struct st_sample *src;
> - struct capture_callback *cb;
> -
> - src = hw->mix_buf.buffer + rpos;
> - hw->clip (cap->buf, src, to_capture);
> - mixeng_clear (src, to_capture);
> -
> - for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> - cb->ops.capture (cb->opaque, cap->buf,
> - to_capture * hw->info.bytes_per_frame);
> - }
> - rpos = (rpos + to_capture) % hw->mix_buf.size;
> - live -= to_capture;
> - }
> - hw->mix_buf.pos = rpos;
> -
> - for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
> - if (!sw->active && sw->empty) {
> - continue;
> - }
> -
> - if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)) {
> - dolog("captured=%zu sw->total_hw_samples_mixed=%zu\n",
> - captured, sw->total_hw_samples_mixed);
> - captured = sw->total_hw_samples_mixed;
> - }
> -
> - sw->total_hw_samples_mixed -= captured;
> - sw->empty = sw->total_hw_samples_mixed == 0;
> - }
> - }
> -}
> -
> -void audio_run(AudioMixengBackend *s, const char *msg)
> -{
> - audio_run_out(s);
> - audio_run_in(s);
> - audio_run_capture(s);
> -
> -#ifdef DEBUG_POLL
> - {
> - static double prevtime;
> - double currtime;
> - struct timeval tv;
> -
> - if (gettimeofday (&tv, NULL)) {
> - perror ("audio_run: gettimeofday");
> - return;
> - }
> -
> - currtime = tv.tv_sec + tv.tv_usec * 1e-6;
> - dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime);
> - prevtime = currtime;
> - }
> -#endif
> -}
> -
> -void audio_generic_run_buffer_in(HWVoiceIn *hw)
> -{
> - if (unlikely(!hw->buf_emul)) {
> - hw->size_emul = hw->samples * hw->info.bytes_per_frame;
> - hw->buf_emul = g_malloc(hw->size_emul);
> - hw->pos_emul = hw->pending_emul = 0;
> - }
> -
> - while (hw->pending_emul < hw->size_emul) {
> - size_t read_len = MIN(hw->size_emul - hw->pos_emul,
> - hw->size_emul - hw->pending_emul);
> - size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul,
> - read_len);
> - hw->pending_emul += read;
> - hw->pos_emul = (hw->pos_emul + read) % hw->size_emul;
> - if (read < read_len) {
> - break;
> - }
> - }
> -}
> -
> -void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)
> -{
> - size_t start;
> -
> - start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
> - assert(start < hw->size_emul);
> -
> - *size = MIN(*size, hw->pending_emul);
> - *size = MIN(*size, hw->size_emul - start);
> - return hw->buf_emul + start;
> -}
> -
> -void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
> -{
> - assert(size <= hw->pending_emul);
> - hw->pending_emul -= size;
> -}
> -
> -size_t audio_generic_buffer_get_free(HWVoiceOut *hw)
> -{
> - if (hw->buf_emul) {
> - return hw->size_emul - hw->pending_emul;
> - } else {
> - return hw->samples * hw->info.bytes_per_frame;
> - }
> -}
> -
> -void audio_generic_run_buffer_out(HWVoiceOut *hw)
> -{
> - while (hw->pending_emul) {
> - size_t write_len, written, start;
> -
> - start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);
> - assert(start < hw->size_emul);
> -
> - write_len = MIN(hw->pending_emul, hw->size_emul - start);
> -
> - written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len);
> - hw->pending_emul -= written;
> -
> - if (written < write_len) {
> - break;
> - }
> - }
> -}
> -
> -void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)
> -{
> - if (unlikely(!hw->buf_emul)) {
> - hw->size_emul = hw->samples * hw->info.bytes_per_frame;
> - hw->buf_emul = g_malloc(hw->size_emul);
> - hw->pos_emul = hw->pending_emul = 0;
> - }
> -
> - *size = MIN(hw->size_emul - hw->pending_emul,
> - hw->size_emul - hw->pos_emul);
> - return hw->buf_emul + hw->pos_emul;
> -}
> -
> -size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
> -{
> - assert(buf == hw->buf_emul + hw->pos_emul &&
> - size + hw->pending_emul <= hw->size_emul);
> -
> - hw->pending_emul += size;
> - hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;
> -
> - return size;
> -}
> -
> -size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size)
> -{
> - size_t total = 0;
> -
> - if (hw->pcm_ops->buffer_get_free) {
> - size_t free = hw->pcm_ops->buffer_get_free(hw);
> -
> - size = MIN(size, free);
> - }
> -
> - while (total < size) {
> - size_t dst_size = size - total;
> - size_t copy_size, proc;
> - void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size);
> -
> - if (dst_size == 0) {
> - break;
> - }
> -
> - copy_size = MIN(size - total, dst_size);
> - if (dst) {
> - memcpy(dst, (char *)buf + total, copy_size);
> - }
> - proc = hw->pcm_ops->put_buffer_out(hw, dst, copy_size);
> - total += proc;
> -
> - if (proc == 0 || proc < copy_size) {
> - break;
> - }
> - }
> -
> - return total;
> -}
> -
> -size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
> -{
> - size_t total = 0;
> -
> - if (hw->pcm_ops->run_buffer_in) {
> - hw->pcm_ops->run_buffer_in(hw);
> - }
> -
> - while (total < size) {
> - size_t src_size = size - total;
> - void *src = hw->pcm_ops->get_buffer_in(hw, &src_size);
> -
> - if (src_size == 0) {
> - break;
> - }
> -
> - memcpy((char *)buf + total, src, src_size);
> - hw->pcm_ops->put_buffer_in(hw, src, src_size);
> - total += src_size;
> - }
> -
> - return total;
> -}
> -
> -static bool audio_mixeng_backend_realize(AudioBackend *abe,
> - Audiodev *dev, Error **errp)
> -{
> - AudioMixengBackend *be = AUDIO_MIXENG_BACKEND(abe);
> - audio_driver *drv = AUDIO_MIXENG_BACKEND_GET_CLASS(be)->driver;
> -
> - be->dev = dev;
> - be->drv_opaque = drv->init(be->dev, errp);
> - if (!be->drv_opaque) {
> - return false;
> - }
> -
> - if (!drv->pcm_ops->get_buffer_in) {
> - drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in;
> - drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in;
> - }
> - if (!drv->pcm_ops->get_buffer_out) {
> - drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out;
> - drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;
> - }
> -
> - audio_init_nb_voices_out(be, drv, 1);
> - audio_init_nb_voices_in(be, drv, 0);
> - be->drv = drv;
> -
> - if (be->dev->timer_period <= 0) {
> - be->period_ticks = 1;
> - } else {
> - be->period_ticks = be->dev->timer_period * (int64_t)SCALE_US;
> - }
> -
> - return true;
> -}
> -
> -static void audio_vm_change_state_handler (void *opaque, bool running,
> - RunState state)
> -{
> - AudioMixengBackend *s = opaque;
> - HWVoiceOut *hwo = NULL;
> - HWVoiceIn *hwi = NULL;
> -
> - while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
> - if (hwo->pcm_ops->enable_out) {
> - hwo->pcm_ops->enable_out(hwo, running);
> - }
> - }
> -
> - while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
> - if (hwi->pcm_ops->enable_in) {
> - hwi->pcm_ops->enable_in(hwi, running);
> - }
> - }
> - audio_reset_timer (s);
> -}
> -
> -static const VMStateDescription vmstate_audio;
> -
> -static const char *audio_mixeng_backend_get_id(AudioBackend *be)
> -{
> - return AUDIO_MIXENG_BACKEND(be)->dev->id;
> -}
> -
> -static CaptureVoiceOut *audio_mixeng_backend_add_capture(
> - AudioBackend *be,
> - struct audsettings *as,
> - struct audio_capture_ops *ops,
> - void *cb_opaque);
> -
> -static void audio_mixeng_backend_del_capture(
> - AudioBackend *be,
> - CaptureVoiceOut *cap,
> - void *cb_opaque);
> -
> -static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
> - Volume *vol);
> -static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
> - Volume *vol);
> -
> -static void audio_mixeng_backend_class_init(ObjectClass *klass, const void *data)
> -{
> - AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
> -
> - be->realize = audio_mixeng_backend_realize;
> - be->get_id = audio_mixeng_backend_get_id;
> - be->open_in = audio_mixeng_backend_open_in;
> - be->open_out = audio_mixeng_backend_open_out;
> - be->close_in = audio_mixeng_backend_close_in;
> - be->close_out = audio_mixeng_backend_close_out;
> - be->is_active_out = audio_mixeng_backend_is_active_out;
> - be->is_active_in = audio_mixeng_backend_is_active_in;
> - be->set_active_out = audio_mixeng_backend_set_active_out;
> - be->set_active_in = audio_mixeng_backend_set_active_in;
> - be->set_volume_out = audio_mixeng_backend_set_volume_out;
> - be->set_volume_in = audio_mixeng_backend_set_volume_in;
> - be->read = audio_mixeng_backend_read;
> - be->write = audio_mixeng_backend_write;
> - be->get_buffer_size_out = audio_mixeng_backend_get_buffer_size_out;
> - be->add_capture = audio_mixeng_backend_add_capture;
> - be->del_capture = audio_mixeng_backend_del_capture;
> -}
> -
> -static void audio_mixeng_backend_init(Object *obj)
> -{
> - AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
> -
> - QLIST_INIT(&s->hw_head_out);
> - QLIST_INIT(&s->hw_head_in);
> - QLIST_INIT(&s->cap_head);
> - s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
> -
> - s->vmse = qemu_add_vm_change_state_handler(audio_vm_change_state_handler, s);
> - assert(s->vmse != NULL);
> -
> - vmstate_register_any(NULL, &vmstate_audio, s);
> -}
> -
> -static void audio_mixeng_backend_finalize(Object *obj)
> -{
> - AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);
> - HWVoiceOut *hwo, *hwon;
> - HWVoiceIn *hwi, *hwin;
> -
> - QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) {
> - SWVoiceCap *sc;
> -
> - if (hwo->enabled && hwo->pcm_ops->enable_out) {
> - hwo->pcm_ops->enable_out(hwo, false);
> - }
> - hwo->pcm_ops->fini_out (hwo);
> +/* SPDX-License-Identifier: MIT */
>
> - for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) {
> - CaptureVoiceOut *cap = sc->cap;
> - struct capture_callback *cb;
> +#include "qemu/osdep.h"
> +#include "qemu/audio.h"
> +#include "qemu/help_option.h"
> +#include "qapi/clone-visitor.h"
> +#include "qapi/qobject-input-visitor.h"
> +#include "qapi/qapi-visit-audio.h"
> +#include "qapi/qapi-commands-audio.h"
> +#include "qobject/qdict.h"
> +#include "system/system.h"
>
> - for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> - cb->ops.destroy (cb->opaque);
> - }
> - }
> - QLIST_REMOVE(hwo, entries);
> - }
> +/* Order of CONFIG_AUDIO_DRIVERS is import.
> + The 1st one is the one used by default, that is the reason
> + that we generate the list.
> +*/
> +const char *audio_prio_list[] = {
> +#ifdef CONFIG_GIO
> + "dbus",
> +#endif
> + "spice",
> + CONFIG_AUDIO_DRIVERS
> + "none",
> + NULL
> +};
>
> - QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) {
> - if (hwi->enabled && hwi->pcm_ops->enable_in) {
> - hwi->pcm_ops->enable_in(hwi, false);
> - }
> - hwi->pcm_ops->fini_in (hwi);
> - QLIST_REMOVE(hwi, entries);
> - }
> +typedef struct AudiodevListEntry {
> + Audiodev *dev;
> + QSIMPLEQ_ENTRY(AudiodevListEntry) next;
> +} AudiodevListEntry;
>
> - if (s->drv) {
> - s->drv->fini (s->drv_opaque);
> - s->drv = NULL;
> - }
> +typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;
>
> - if (s->dev) {
> - qapi_free_Audiodev(s->dev);
> - s->dev = NULL;
> - }
> +static AudiodevListHead audiodevs =
> + QSIMPLEQ_HEAD_INITIALIZER(audiodevs);
> +static AudiodevListHead default_audiodevs =
> + QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);
>
> - if (s->ts) {
> - timer_free(s->ts);
> - s->ts = NULL;
> - }
> +static AudioBackendClass *audio_be_class_by_name(const char *name)
> +{
> + g_autofree char *tname = g_strconcat("audio-", name, NULL);
> + ObjectClass *oc = module_object_class_by_name(tname);
>
> - if (s->vmse) {
> - qemu_del_vm_change_state_handler(s->vmse);
> - s->vmse = NULL;
> + if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) {
> + return NULL;
> }
>
> - vmstate_unregister(NULL, &vmstate_audio, s);
> + return AUDIO_BACKEND_CLASS(oc);
> }
>
> +static AudioBackend *default_audio_be;
> +
> static Object *get_audiodevs_root(void)
> {
> return object_get_container("audiodevs");
> @@ -1757,25 +62,6 @@ void audio_cleanup(void)
> object_unparent(get_audiodevs_root());
> }
>
> -static bool vmstate_audio_needed(void *opaque)
> -{
> - /*
> - * Never needed, this vmstate only exists in case
> - * an old qemu sends it to us.
> - */
> - return false;
> -}
> -
> -static const VMStateDescription vmstate_audio = {
> - .name = "audio",
> - .version_id = 1,
> - .minimum_version_id = 1,
> - .needed = vmstate_audio_needed,
> - .fields = (const VMStateField[]) {
> - VMSTATE_END_OF_LIST()
> - }
> -};
> -
> void audio_create_default_audiodevs(void)
> {
> for (int i = 0; audio_prio_list[i]; i++) {
> @@ -1816,15 +102,13 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
> assert(!default_audio_be);
> for (;;) {
> AudiodevListEntry *e = QSIMPLEQ_FIRST(&default_audiodevs);
> -
> if (!e) {
> error_setg(errp, "no default audio driver available");
> return NULL;
> }
> - dev = e->dev;
> QSIMPLEQ_REMOVE_HEAD(&default_audiodevs, next);
> + be = audio_be_new(e->dev, NULL);
> g_free(e);
> - be = audio_be_new(dev, NULL);
> if (be) {
> break;
> }
> @@ -1855,156 +139,35 @@ AudioBackend *audio_get_default_audio_be(Error **errp)
> return default_audio_be;
> }
>
> -static struct audio_pcm_ops capture_pcm_ops;
> -
> -static CaptureVoiceOut *audio_mixeng_backend_add_capture(
> - AudioBackend *be,
> - struct audsettings *as,
> - struct audio_capture_ops *ops,
> - void *cb_opaque)
> -{
> - AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(be);
> - CaptureVoiceOut *cap;
> - struct capture_callback *cb;
> -
> - if (!s) {
> - error_report("Capturing without setting an audiodev is not supported");
> - abort();
> - }
> -
> - 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);
> - return NULL;
> - }
> -
> - cb = g_malloc0(sizeof(*cb));
> - cb->ops = *ops;
> - cb->opaque = cb_opaque;
> -
> - cap = audio_pcm_capture_find_specific(s, as);
> - if (cap) {
> - QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
> - } else {
> - HWVoiceOut *hw;
> -
> - cap = g_malloc0(sizeof(*cap));
> -
> - hw = &cap->hw;
> - hw->s = s;
> - hw->pcm_ops = &capture_pcm_ops;
> - QLIST_INIT (&hw->sw_head);
> - QLIST_INIT (&cap->cb_head);
> -
> - /* XXX find a more elegant way */
> - hw->samples = 4096 * 4;
> - audio_pcm_hw_alloc_resources_out(hw);
> -
> - audio_pcm_init_info (&hw->info, as);
> -
> - cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame);
> -
> - if (hw->info.is_float) {
> - hw->clip = mixeng_clip_float[hw->info.nchannels == 2]
> - [hw->info.swap_endianness];
> - } else {
> - hw->clip = mixeng_clip
> - [hw->info.nchannels == 2]
> - [hw->info.is_signed]
> - [hw->info.swap_endianness]
> - [audio_bits_to_index(hw->info.bits)];
> - }
> -
> - QLIST_INSERT_HEAD (&s->cap_head, cap, entries);
> - QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
> -
> - QLIST_FOREACH(hw, &s->hw_head_out, entries) {
> - audio_attach_capture (hw);
> - }
> - }
> -
> - return cap;
> -}
> -
> -static void audio_mixeng_backend_del_capture(
> - AudioBackend *be,
> - CaptureVoiceOut *cap,
> - void *cb_opaque)
> +void audio_help(void)
> {
> - struct capture_callback *cb;
> -
> - for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
> - if (cb->opaque == cb_opaque) {
> - cb->ops.destroy (cb_opaque);
> - QLIST_REMOVE (cb, entries);
> - g_free (cb);
> + int i;
>
> - if (!cap->cb_head.lh_first) {
> - SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1;
> + printf("Available audio drivers:\n");
>
> - while (sw) {
> - SWVoiceCap *sc = (SWVoiceCap *) sw;
> -#ifdef DEBUG_CAPTURE
> - dolog ("freeing %s\n", sw->name);
> -#endif
> + for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) {
> + const char *name = AudiodevDriver_str(i);
> + AudioBackendClass *be = audio_be_class_by_name(name);
>
> - sw1 = sw->entries.le_next;
> - if (sw->rate) {
> - st_rate_stop (sw->rate);
> - sw->rate = NULL;
> - }
> - QLIST_REMOVE (sw, entries);
> - QLIST_REMOVE (sc, entries);
> - g_free (sc);
> - sw = sw1;
> - }
> - QLIST_REMOVE (cap, entries);
> - g_free(cap->hw.mix_buf.buffer);
> - g_free (cap->buf);
> - g_free (cap);
> - }
> - return;
> + if (be) {
> + printf("%s\n", name);
> }
> }
> }
>
> -static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,
> - Volume *vol)
> +void audio_parse_option(const char *opt)
> {
> - if (sw) {
> - HWVoiceOut *hw = sw->hw;
> -
> - 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;
> + Audiodev *dev = NULL;
>
> - if (hw->pcm_ops->volume_out) {
> - hw->pcm_ops->volume_out(hw, vol);
> - }
> + if (is_help_option(opt)) {
> + audio_help();
> + exit(EXIT_SUCCESS);
> }
> -}
> -
> -static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,
> - Volume *vol)
> -{
> - if (sw) {
> - HWVoiceIn *hw = sw->hw;
> -
> - 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;
> + Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
> + visit_type_Audiodev(v, NULL, &dev, &error_fatal);
> + visit_free(v);
>
> - if (hw->pcm_ops->volume_in) {
> - hw->pcm_ops->volume_in(hw, vol);
> - }
> - }
> + audio_add_audiodev(dev);
> }
>
> static void audio_create_pdos(Audiodev *dev)
> @@ -2103,6 +266,124 @@ static void audio_validate_per_direction_opts(
> }
> }
>
> +static AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev)
> +{
> + switch (dev->driver) {
> + case AUDIODEV_DRIVER_NONE:
> + return dev->u.none.out;
> +#ifdef CONFIG_AUDIO_ALSA
> + case AUDIODEV_DRIVER_ALSA:
> + return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.out);
> +#endif
> +#ifdef CONFIG_AUDIO_COREAUDIO
> + case AUDIODEV_DRIVER_COREAUDIO:
> + return qapi_AudiodevCoreaudioPerDirectionOptions_base(
> + dev->u.coreaudio.out);
> +#endif
> +#ifdef CONFIG_DBUS_DISPLAY
> + case AUDIODEV_DRIVER_DBUS:
> + return dev->u.dbus.out;
> +#endif
> +#ifdef CONFIG_AUDIO_DSOUND
> + case AUDIODEV_DRIVER_DSOUND:
> + return dev->u.dsound.out;
> +#endif
> +#ifdef CONFIG_AUDIO_JACK
> + case AUDIODEV_DRIVER_JACK:
> + return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.out);
> +#endif
> +#ifdef CONFIG_AUDIO_OSS
> + case AUDIODEV_DRIVER_OSS:
> + return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.out);
> +#endif
> +#ifdef CONFIG_AUDIO_PA
> + case AUDIODEV_DRIVER_PA:
> + return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.out);
> +#endif
> +#ifdef CONFIG_AUDIO_PIPEWIRE
> + case AUDIODEV_DRIVER_PIPEWIRE:
> + return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.out);
> +#endif
> +#ifdef CONFIG_AUDIO_SDL
> + case AUDIODEV_DRIVER_SDL:
> + return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.out);
> +#endif
> +#ifdef CONFIG_AUDIO_SNDIO
> + case AUDIODEV_DRIVER_SNDIO:
> + return dev->u.sndio.out;
> +#endif
> +#ifdef CONFIG_SPICE
> + case AUDIODEV_DRIVER_SPICE:
> + return dev->u.spice.out;
> +#endif
> + case AUDIODEV_DRIVER_WAV:
> + return dev->u.wav.out;
> +
> + case AUDIODEV_DRIVER__MAX:
> + break;
> + }
> + abort();
> +}
> +
> +static AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev)
> +{
> + switch (dev->driver) {
> + case AUDIODEV_DRIVER_NONE:
> + return dev->u.none.in;
> +#ifdef CONFIG_AUDIO_ALSA
> + case AUDIODEV_DRIVER_ALSA:
> + return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.in);
> +#endif
> +#ifdef CONFIG_AUDIO_COREAUDIO
> + case AUDIODEV_DRIVER_COREAUDIO:
> + return qapi_AudiodevCoreaudioPerDirectionOptions_base(
> + dev->u.coreaudio.in);
> +#endif
> +#ifdef CONFIG_DBUS_DISPLAY
> + case AUDIODEV_DRIVER_DBUS:
> + return dev->u.dbus.in;
> +#endif
> +#ifdef CONFIG_AUDIO_DSOUND
> + case AUDIODEV_DRIVER_DSOUND:
> + return dev->u.dsound.in;
> +#endif
> +#ifdef CONFIG_AUDIO_JACK
> + case AUDIODEV_DRIVER_JACK:
> + return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.in);
> +#endif
> +#ifdef CONFIG_AUDIO_OSS
> + case AUDIODEV_DRIVER_OSS:
> + return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.in);
> +#endif
> +#ifdef CONFIG_AUDIO_PA
> + case AUDIODEV_DRIVER_PA:
> + return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.in);
> +#endif
> +#ifdef CONFIG_AUDIO_PIPEWIRE
> + case AUDIODEV_DRIVER_PIPEWIRE:
> + return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.in);
> +#endif
> +#ifdef CONFIG_AUDIO_SDL
> + case AUDIODEV_DRIVER_SDL:
> + return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.in);
> +#endif
> +#ifdef CONFIG_AUDIO_SNDIO
> + case AUDIODEV_DRIVER_SNDIO:
> + return dev->u.sndio.in;
> +#endif
> +#ifdef CONFIG_SPICE
> + case AUDIODEV_DRIVER_SPICE:
> + return dev->u.spice.in;
> +#endif
> + case AUDIODEV_DRIVER_WAV:
> + return dev->u.wav.in;
> +
> + case AUDIODEV_DRIVER__MAX:
> + break;
> + }
> + abort();
> +}
> +
> static void audio_validate_opts(Audiodev *dev, Error **errp)
> {
> Error *err = NULL;
> @@ -2127,37 +408,6 @@ static void audio_validate_opts(Audiodev *dev, Error **errp)
> }
> }
>
> -void audio_help(void)
> -{
> - int i;
> -
> - printf("Available audio drivers:\n");
> -
> - for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) {
> - const char *name = AudiodevDriver_str(i);
> - AudioBackendClass *be = audio_be_class_by_name(name);
> -
> - if (be) {
> - printf("%s\n", name);
> - }
> - }
> -}
> -
> -void audio_parse_option(const char *opt)
> -{
> - Audiodev *dev = NULL;
> -
> - if (is_help_option(opt)) {
> - audio_help();
> - exit(EXIT_SUCCESS);
> - }
> - Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
> - visit_type_Audiodev(v, NULL, &dev, &error_fatal);
> - visit_free(v);
> -
> - audio_add_audiodev(dev);
> -}
> -
> void audio_add_audiodev(Audiodev *dev)
> {
> AudiodevListEntry *e;
> @@ -2189,65 +439,6 @@ void audio_init_audiodevs(void)
> }
> }
>
> -audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo)
> -{
> - return (audsettings) {
> - .freq = pdo->frequency,
> - .nchannels = pdo->channels,
> - .fmt = pdo->format,
> - .endianness = HOST_BIG_ENDIAN,
> - };
> -}
> -
> -int audioformat_bytes_per_sample(AudioFormat fmt)
> -{
> - switch (fmt) {
> - case AUDIO_FORMAT_U8:
> - case AUDIO_FORMAT_S8:
> - return 1;
> -
> - case AUDIO_FORMAT_U16:
> - case AUDIO_FORMAT_S16:
> - return 2;
> -
> - case AUDIO_FORMAT_U32:
> - case AUDIO_FORMAT_S32:
> - case AUDIO_FORMAT_F32:
> - return 4;
> -
> - case AUDIO_FORMAT__MAX:
> - ;
> - }
> - abort();
> -}
> -
> -
> -/* frames = freq * usec / 1e6 */
> -int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,
> - audsettings *as, int def_usecs)
> -{
> - uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs;
> - return (as->freq * usecs + 500000) / 1000000;
> -}
> -
> -/* samples = channels * frames = channels * freq * usec / 1e6 */
> -int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,
> - audsettings *as, int def_usecs)
> -{
> - return as->nchannels * audio_buffer_frames(pdo, as, def_usecs);
> -}
> -
> -/*
> - * bytes = bytes_per_sample * samples =
> - * bytes_per_sample * channels * freq * usec / 1e6
> - */
> -int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,
> - audsettings *as, int def_usecs)
> -{
> - return audio_buffer_samples(pdo, as, def_usecs) *
> - audioformat_bytes_per_sample(as->fmt);
> -}
> -
> AudioBackend *audio_be_by_name(const char *name, Error **errp)
> {
> Object *obj = object_resolve_path_component(get_audiodevs_root(), name);
> @@ -2268,51 +459,6 @@ const char *audio_application_name(void)
> return vm_name ? vm_name : "qemu";
> }
>
> -void audio_rate_start(RateCtl *rate)
> -{
> - memset(rate, 0, sizeof(RateCtl));
> - rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> -}
> -
> -size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info)
> -{
> - int64_t now;
> - int64_t ticks;
> - int64_t bytes;
> - int64_t frames;
> -
> - now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> - ticks = now - rate->start_ticks;
> - bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);
> - frames = (bytes - rate->bytes_sent) / info->bytes_per_frame;
> - rate->peeked_frames = frames;
> -
> - return frames < 0 ? 0 : frames * info->bytes_per_frame;
> -}
> -
> -void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used)
> -{
> - if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) {
> - AUD_log(NULL, "Resetting rate control (%" PRId64 " frames)\n",
> - rate->peeked_frames);
> - audio_rate_start(rate);
> - }
> -
> - rate->bytes_sent += bytes_used;
> -}
> -
> -size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info,
> - size_t bytes_avail)
> -{
> - size_t bytes;
> -
> - bytes = audio_rate_peek_bytes(rate, info);
> - bytes = MIN(bytes, bytes_avail);
> - audio_rate_add_bytes(rate, bytes);
> -
> - return bytes;
> -}
> -
> AudiodevList *qmp_query_audiodevs(Error **errp)
> {
> AudiodevList *ret = NULL;
> @@ -2322,21 +468,3 @@ AudiodevList *qmp_query_audiodevs(Error **errp)
> }
> return ret;
> }
> -
> -static const TypeInfo audio_mixeng_backend_info = {
> - .name = TYPE_AUDIO_MIXENG_BACKEND,
> - .parent = TYPE_AUDIO_BACKEND,
> - .instance_size = sizeof(AudioMixengBackend),
> - .instance_init = audio_mixeng_backend_init,
> - .instance_finalize = audio_mixeng_backend_finalize,
> - .abstract = false,
> - .class_size = sizeof(AudioMixengBackendClass),
> - .class_init = audio_mixeng_backend_class_init,
> -};
> -
> -static void register_types(void)
> -{
> - type_register_static(&audio_mixeng_backend_info);
> -}
> -
> -type_init(register_types);
> diff --git a/audio/meson.build b/audio/meson.build
> index 417670bd4c7..d2ff49280ba 100644
> --- a/audio/meson.build
> +++ b/audio/meson.build
> @@ -2,6 +2,7 @@ audio_ss = ss.source_set()
> audio_ss.add(files(
> 'audio.c',
> 'audio-be.c',
> + 'audio-mixeng-be.c',
> 'mixeng.c',
> 'noaudio.c',
> 'wavaudio.c',
Reviewed-by: Mark Cave-Ayland <mark.caveayland@nutanix.com>
ATB,
Mark.
© 2016 - 2026 Red Hat, Inc.