qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [RFC 00/24] audio: add GStreamer backend
@ 2025-12-01 11:22 marcandre.lureau
  2025-12-01 11:22 ` [RFC 01/24] rust: patch thiserror to work with meson marcandre.lureau
                   ` (24 more replies)
  0 siblings, 25 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Hi,

The following patch series provides a GStreamer-based audio backend, which could
ultimately allow QEMU to leverage the framework to support the various audio
subsystems and simplify the audio handling logic (timing, resampling, mixing
etc), as well as allow greater pipeline flexibility and customization.

The preliminary patches consist of additional cleanups started in QEMU 10.2, in
order to make the code more modular and use QOM.

Finally, the last patch introduces the "audio" rust crate that implements the
GStreamer backend. See the TODO list in the commit message for the remaining or
related work items.

Please review and test!

Based-on: https://gitlab.com/bonzini/qemu.git rust-cargo
Requires meson from https://github.com/bonzini/meson.git cargo-object-full

Marc-André Lureau (24):
  rust: patch thiserror to work with meson
  audio: remove obsolete/obscure functions
  audio/dbus: make "dbus" the default backend when using -display dbus
  qemu-options.hx: clarify default audio backend selection
  audio: introduce AudioDriver
  audio: simplify audio_init()
  audio: move object creation to audio_driver_init()
  audio: add QOM module-objects for each backend
  audio: remove set_dbus_server from audio_driver
  audio: lookup "audio-" object types, and realize them
  audio: switch to module-object, drop audio driver registration
  module: remove audio module support
  audio: keep a strong reference on the backend
  audio: make list type declaration private
  audio: make create_pdos() private
  replay: remove dependency on audio/
  audio: make all the backend-specific APIs take the be
  audio: make AudioBackend truely abstract
  audio: split AudioBackend
  audio: AUD_ -> audio_be_
  audio-be: add common pre-conditions
  audio-be: add some state trace
  audio: split AudioDriver code in audio-driver.c
  WIP: rust/audio: add GStreamer backend

 qapi/audio.json                               |   29 +
 ...dio_template.h => audio-driver_template.h} |   76 +-
 audio/audio_int.h                             |   50 +-
 include/qemu/audio-capture.h                  |   24 +-
 include/qemu/audio.h                          |  129 +-
 include/qemu/module.h                         |    3 +-
 include/system/replay.h                       |    8 +-
 replay/replay-internal.h                      |    2 +
 rust/audio/wrapper.h                          |   27 +
 audio/alsaaudio.c                             |   42 +-
 audio/audio-be.c                              |  276 ++
 audio/audio-driver.c                          | 1988 +++++++++++++++
 audio/audio.c                                 | 2248 ++---------------
 audio/dbusaudio.c                             |   57 +-
 audio/dsoundaudio.c                           |   37 +-
 audio/jackaudio.c                             |   37 +-
 audio/noaudio.c                               |   37 +-
 audio/ossaudio.c                              |   41 +-
 audio/paaudio.c                               |   37 +-
 audio/pwaudio.c                               |   37 +-
 audio/sdlaudio.c                              |   37 +-
 audio/sndioaudio.c                            |   37 +-
 audio/spiceaudio.c                            |   37 +-
 audio/wavaudio.c                              |   37 +-
 audio/wavcapture.c                            |    7 +-
 hw/audio/ac97.c                               |   42 +-
 hw/audio/adlib.c                              |   29 +-
 hw/audio/asc.c                                |   20 +-
 hw/audio/cs4231a.c                            |   18 +-
 hw/audio/es1370.c                             |   26 +-
 hw/audio/gus.c                                |   11 +-
 hw/audio/hda-codec.c                          |   26 +-
 hw/audio/lm4549.c                             |   20 +-
 hw/audio/pcspk.c                              |    8 +-
 hw/audio/sb16.c                               |   22 +-
 hw/audio/via-ac97.c                           |   20 +-
 hw/audio/virtio-snd.c                         |   36 +-
 hw/audio/wm8750.c                             |   42 +-
 hw/display/xlnx_dp.c                          |   14 +-
 hw/usb/dev-audio.c                            |   18 +-
 replay/replay-audio.c                         |   51 +-
 replay/replay.c                               |    2 +-
 replay/stubs-system.c                         |    8 +-
 ui/dbus.c                                     |   16 +-
 ui/vnc.c                                      |    4 +-
 Cargo.lock                                    |  572 ++++-
 Cargo.toml                                    |    6 +
 audio/coreaudio.m                             |   37 +-
 audio/meson.build                             |    2 +
 audio/trace-events                            |    9 +
 qemu-options.hx                               |   20 +-
 rust/audio/Cargo.toml                         |   29 +
 rust/audio/build.rs                           |   49 +
 rust/audio/meson.build                        |   75 +
 rust/audio/src/audio.rs                       |  516 ++++
 rust/audio/src/bindings.rs                    |   32 +
 rust/audio/src/gstreamer.rs                   | 1070 ++++++++
 rust/audio/src/lib.rs                         |   99 +
 rust/meson.build                              |    6 +
 .../packagefiles/syn-2-rs/meson/meson.build   |    3 +
 .../thiserror-2.0.17-include.patch            |   14 +
 .../thiserror-impl-2.0.17-include.patch       |   13 +
 subprojects/syn-2-rs.wrap                     |   11 +-
 subprojects/thiserror-2-rs.wrap               |   10 +
 subprojects/thiserror-impl-2-rs.wrap          |   10 +
 65 files changed, 5862 insertions(+), 2494 deletions(-)
 rename audio/{audio_template.h => audio-driver_template.h} (90%)
 create mode 100644 rust/audio/wrapper.h
 create mode 100644 audio/audio-be.c
 create mode 100644 audio/audio-driver.c
 create mode 100644 rust/audio/Cargo.toml
 create mode 100644 rust/audio/build.rs
 create mode 100644 rust/audio/meson.build
 create mode 100644 rust/audio/src/audio.rs
 create mode 100644 rust/audio/src/bindings.rs
 create mode 100644 rust/audio/src/gstreamer.rs
 create mode 100644 rust/audio/src/lib.rs
 create mode 100644 subprojects/packagefiles/syn-2-rs/meson/meson.build
 create mode 100644 subprojects/packagefiles/thiserror-2.0.17-include.patch
 create mode 100644 subprojects/packagefiles/thiserror-impl-2.0.17-include.patch
 create mode 100644 subprojects/thiserror-2-rs.wrap
 create mode 100644 subprojects/thiserror-impl-2-rs.wrap

-- 
2.51.1



^ permalink raw reply	[flat|nested] 51+ messages in thread

* [RFC 01/24] rust: patch thiserror to work with meson
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 02/24] audio: remove obsolete/obscure functions marcandre.lureau
                   ` (23 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Some dependencies introduced in the following patches require thiserror.
Since 2.0.17, it "Use differently named __private module per patch
release", using build.rs time generated code. This is not handled by meson.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 .../packagefiles/syn-2-rs/meson/meson.build        |  3 +++
 .../packagefiles/thiserror-2.0.17-include.patch    | 14 ++++++++++++++
 .../thiserror-impl-2.0.17-include.patch            | 13 +++++++++++++
 subprojects/syn-2-rs.wrap                          | 11 ++++++-----
 subprojects/thiserror-2-rs.wrap                    | 10 ++++++++++
 subprojects/thiserror-impl-2-rs.wrap               | 10 ++++++++++
 6 files changed, 56 insertions(+), 5 deletions(-)
 create mode 100644 subprojects/packagefiles/syn-2-rs/meson/meson.build
 create mode 100644 subprojects/packagefiles/thiserror-2.0.17-include.patch
 create mode 100644 subprojects/packagefiles/thiserror-impl-2.0.17-include.patch
 create mode 100644 subprojects/thiserror-2-rs.wrap
 create mode 100644 subprojects/thiserror-impl-2-rs.wrap

diff --git a/subprojects/packagefiles/syn-2-rs/meson/meson.build b/subprojects/packagefiles/syn-2-rs/meson/meson.build
new file mode 100644
index 0000000000..02439c2b58
--- /dev/null
+++ b/subprojects/packagefiles/syn-2-rs/meson/meson.build
@@ -0,0 +1,3 @@
+extra_args += [
+    '--cfg', 'feature="full"',
+]
diff --git a/subprojects/packagefiles/thiserror-2.0.17-include.patch b/subprojects/packagefiles/thiserror-2.0.17-include.patch
new file mode 100644
index 0000000000..a061aeea57
--- /dev/null
+++ b/subprojects/packagefiles/thiserror-2.0.17-include.patch
@@ -0,0 +1,14 @@
+diff --git a/src/lib.rs b/src/lib.rs
+index 155272d..7683f4c 100644
+--- a/src/lib.rs
++++ b/src/lib.rs
+@@ -288,4 +288,8 @@ pub use thiserror_impl::*;
+ 
+ mod private;
+ 
+-include!(concat!(env!("OUT_DIR"), "/private.rs"));
++#[doc(hidden)]
++pub mod __private_MESON {
++    #[doc(hidden)]
++    pub use crate::private::*;
++}
diff --git a/subprojects/packagefiles/thiserror-impl-2.0.17-include.patch b/subprojects/packagefiles/thiserror-impl-2.0.17-include.patch
new file mode 100644
index 0000000000..372862dffa
--- /dev/null
+++ b/subprojects/packagefiles/thiserror-impl-2.0.17-include.patch
@@ -0,0 +1,13 @@
+diff --git a/impl/src/lib.rs b/impl/src/lib.rs
+index 25890f2..1559a41 100644
+--- a/src/lib.rs
++++ b/src/lib.rs
+@@ -48,7 +48,7 @@ struct private;
+ impl ToTokens for private {
+     fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+         tokens.append(Ident::new(
+-            concat!("__private", env!("CARGO_PKG_VERSION_PATCH")),
++            concat!("__private", "_MESON"),
+             Span::call_site(),
+         ));
+     }
diff --git a/subprojects/syn-2-rs.wrap b/subprojects/syn-2-rs.wrap
index 8ec7fd0052..82675e8723 100644
--- a/subprojects/syn-2-rs.wrap
+++ b/subprojects/syn-2-rs.wrap
@@ -1,9 +1,10 @@
 [wrap-file]
-directory = syn-2.0.104
-source_url = https://crates.io/api/v1/crates/syn/2.0.104/download
-source_filename = syn-2.0.104.0.tar.gz
-source_hash = 17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40
+directory = syn-2.0.110
+source_url = https://crates.io/api/v1/crates/syn/2.0.110/download
+source_filename = syn-2.0.110.tar.gz
+source_hash = a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea
 method = cargo
+patch_directory = syn-2-rs
 
 # bump this version number on every change to meson.build or the patches:
-# v3
+# v4
diff --git a/subprojects/thiserror-2-rs.wrap b/subprojects/thiserror-2-rs.wrap
new file mode 100644
index 0000000000..432d99d937
--- /dev/null
+++ b/subprojects/thiserror-2-rs.wrap
@@ -0,0 +1,10 @@
+[wrap-file]
+directory = thiserror-2.0.17
+source_url = https://crates.io/api/v1/crates/thiserror/2.0.17/download
+source_filename = thiserror-2.0.17.tar.gz
+source_hash = f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8
+method = cargo
+diff_files = thiserror-2.0.17-include.patch
+
+# bump this version number on every change to meson.build or the patches:
+# v2
diff --git a/subprojects/thiserror-impl-2-rs.wrap b/subprojects/thiserror-impl-2-rs.wrap
new file mode 100644
index 0000000000..f0eb263607
--- /dev/null
+++ b/subprojects/thiserror-impl-2-rs.wrap
@@ -0,0 +1,10 @@
+[wrap-file]
+directory = thiserror-impl-2.0.17
+source_url = https://crates.io/api/v1/crates/thiserror-impl/2.0.17/download
+source_filename = thiserror-impl-2.0.17.tar.gz
+source_hash = 3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913
+method = cargo
+diff_files = thiserror-impl-2.0.17-include.patch
+
+# bump this version number on every change to meson.build or the patches:
+# v2
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 02/24] audio: remove obsolete/obscure functions
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
  2025-12-01 11:22 ` [RFC 01/24] rust: patch thiserror to work with meson marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-10 14:02   ` Akihiko Odaki
  2025-12-01 11:22 ` [RFC 03/24] audio/dbus: make "dbus" the default backend when using -display dbus marcandre.lureau
                   ` (22 subsequent siblings)
  24 siblings, 1 reply; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

AUD_init_time_stamp_{in,out} and AUD_get_elapsed_usec_{in,out} are only
used by the adlib device. The result isn't actually being used since
ADLIB_KILL_TIMERS was set some 20y ago. Let's drop this dead code now.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_template.h | 34 ----------------------------------
 include/qemu/audio.h   |  6 ------
 hw/audio/adlib.c       | 18 +-----------------
 3 files changed, 1 insertion(+), 57 deletions(-)

diff --git a/audio/audio_template.h b/audio/audio_template.h
index 7a8c431f2d..1ab3c47fd7 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -569,40 +569,6 @@ bool glue(AUD_is_active_, TYPE)(SW *sw)
     return sw ? sw->active : 0;
 }
 
-void glue (AUD_init_time_stamp_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts)
-{
-    if (!sw) {
-        return;
-    }
-
-    ts->old_ts = sw->hw->ts_helper;
-}
-
-uint64_t glue (AUD_get_elapsed_usec_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts)
-{
-    uint64_t delta, cur_ts, old_ts;
-
-    if (!sw) {
-        return 0;
-    }
-
-    cur_ts = sw->hw->ts_helper;
-    old_ts = ts->old_ts;
-    /* dolog ("cur %" PRId64 " old %" PRId64 "\n", cur_ts, old_ts); */
-
-    if (cur_ts >= old_ts) {
-        delta = cur_ts - old_ts;
-    } else {
-        delta = UINT64_MAX - old_ts + cur_ts;
-    }
-
-    if (!delta) {
-        return 0;
-    }
-
-    return muldiv64 (delta, sw->hw->info.freq, 1000000);
-}
-
 #undef TYPE
 #undef HW
 #undef SW
diff --git a/include/qemu/audio.h b/include/qemu/audio.h
index c56af895d6..2562710bec 100644
--- a/include/qemu/audio.h
+++ b/include/qemu/audio.h
@@ -71,9 +71,6 @@ int  AUD_get_buffer_size_out (SWVoiceOut *sw);
 void AUD_set_active_out(SWVoiceOut *sw, bool on);
 bool AUD_is_active_out(SWVoiceOut *sw);
 
-void     AUD_init_time_stamp_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts);
-uint64_t AUD_get_elapsed_usec_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts);
-
 #define AUDIO_MAX_CHANNELS 16
 typedef struct Volume {
     bool mute;
@@ -112,9 +109,6 @@ size_t AUD_read (SWVoiceIn *sw, void *pcm_buf, size_t size);
 void AUD_set_active_in(SWVoiceIn *sw, bool on);
 bool AUD_is_active_in(SWVoiceIn *sw);
 
-void     AUD_init_time_stamp_in (SWVoiceIn *sw, QEMUAudioTimeStamp *ts);
-uint64_t AUD_get_elapsed_usec_in (SWVoiceIn *sw, QEMUAudioTimeStamp *ts);
-
 void audio_cleanup(void);
 
 typedef struct st_sample st_sample;
diff --git a/hw/audio/adlib.c b/hw/audio/adlib.c
index 19d3a5f128..2a2fe7d04f 100644
--- a/hw/audio/adlib.c
+++ b/hw/audio/adlib.c
@@ -34,8 +34,6 @@
 
 #define DEBUG 0
 
-#define ADLIB_KILL_TIMERS 1
-
 #define ADLIB_DESC "Yamaha YM3812 (OPL2)"
 
 #if DEBUG
@@ -71,7 +69,6 @@ struct AdlibState {
     uint64_t dexp[2];
     SWVoiceOut *voice;
     int left, pos, samples;
-    QEMUAudioTimeStamp ats;
     FM_OPL *opl;
     PortioList port_list;
 };
@@ -88,19 +85,7 @@ static void adlib_kill_timers (AdlibState *s)
 
     for (i = 0; i < 2; ++i) {
         if (s->ticking[i]) {
-            uint64_t delta;
-
-            delta = AUD_get_elapsed_usec_out (s->voice, &s->ats);
-            ldebug (
-                "delta = %f dexp = %f expired => %d",
-                delta / 1000000.0,
-                s->dexp[i] / 1000000.0,
-                delta >= s->dexp[i]
-                );
-            if (ADLIB_KILL_TIMERS || delta >= s->dexp[i]) {
-                adlib_stop_opl_timer (s, i);
-                AUD_init_time_stamp_out (s->voice, &s->ats);
-            }
+            adlib_stop_opl_timer(s, i);
         }
     }
 }
@@ -149,7 +134,6 @@ static void timer_handler (void *opaque, int c, double interval_Sec)
 #endif
 
     s->dexp[n] = interval_Sec * 1000000.0;
-    AUD_init_time_stamp_out (s->voice, &s->ats);
 }
 
 static int write_audio (AdlibState *s, int samples)
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 03/24] audio/dbus: make "dbus" the default backend when using -display dbus
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
  2025-12-01 11:22 ` [RFC 01/24] rust: patch thiserror to work with meson marcandre.lureau
  2025-12-01 11:22 ` [RFC 02/24] audio: remove obsolete/obscure functions marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-10 14:03   ` Akihiko Odaki
  2025-12-01 11:22 ` [RFC 04/24] qemu-options.hx: clarify default audio backend selection marcandre.lureau
                   ` (21 subsequent siblings)
  24 siblings, 1 reply; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Set "using_dbus_display" during early_dbus_init(), so that we can try to
create the "dbus" audio backend by default from audio_prio_list.

This makes dbus audio work by default when using an audio device,
without having to setup and wire up the -audiodev manually.

The added FIXME is addressed in the following commits.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio.c     |  3 +++
 audio/dbusaudio.c |  8 +++++++-
 ui/dbus.c         | 17 +++++++++++++----
 3 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index 86e674410a..0f992a775c 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -57,6 +57,9 @@
     that we generate the list.
 */
 const char *audio_prio_list[] = {
+#ifdef CONFIG_GIO
+    "dbus",
+#endif
     "spice",
     CONFIG_AUDIO_DRIVERS
     "none",
diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c
index d729a810aa..1fe7c4ed64 100644
--- a/audio/dbusaudio.c
+++ b/audio/dbusaudio.c
@@ -32,6 +32,7 @@
 #endif
 
 #include "ui/dbus.h"
+#include "ui/dbus-display.h"
 #include "ui/dbus-display1.h"
 
 #define AUDIO_CAP "dbus"
@@ -408,8 +409,13 @@ dbus_enable_in(HWVoiceIn *hw, bool enable)
 static void *
 dbus_audio_init(Audiodev *dev, Error **errp)
 {
-    DBusAudio *da = g_new0(DBusAudio, 1);
+    DBusAudio *da;
 
+    if (!qemu_using_dbus_display(errp)) {
+        return NULL;
+    }
+
+    da = g_new0(DBusAudio, 1);
     da->dev = dev;
     da->out_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
                                                 g_free, g_object_unref);
diff --git a/ui/dbus.c b/ui/dbus.c
index d2dff33258..45fb3c1aa3 100644
--- a/ui/dbus.c
+++ b/ui/dbus.c
@@ -35,6 +35,7 @@
 #include "ui/egl-context.h"
 #endif
 #include "qemu/audio.h"
+#include "audio/audio_int.h" /* FIXME: use QOM dynamic cast instead of drv->name */
 #include "qapi/error.h"
 #include "trace.h"
 
@@ -218,12 +219,20 @@ dbus_display_complete(UserCreatable *uc, Error **errp)
         return;
     }
 
+    AudioBackend *audio_be = audio_get_default_audio_be(NULL);
+    if (audio_be && !g_str_equal(audio_be->drv->name, "dbus")) {
+        audio_be = NULL;
+    }
     if (dd->audiodev && *dd->audiodev) {
-        AudioBackend *audio_be = audio_be_by_name(dd->audiodev, errp);
-        if (!audio_be || !audio_be_set_dbus_server(audio_be, dd->server, dd->p2p, errp)) {
+        audio_be = audio_be_by_name(dd->audiodev, errp);
+        if (!audio_be) {
             return;
         }
     }
+    if (audio_be && !audio_be_set_dbus_server(audio_be, dd->server, dd->p2p, errp)) {
+        return;
+    }
+
 
     consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
     for (idx = 0;; idx++) {
@@ -475,6 +484,8 @@ early_dbus_init(DisplayOptions *opts)
 #endif
     }
 
+    using_dbus_display = 1;
+
     type_register_static(&dbus_vc_type_info);
 }
 
@@ -488,8 +499,6 @@ dbus_init(DisplayState *ds, DisplayOptions *opts)
         exit(1);
     }
 
-    using_dbus_display = 1;
-
     object_new_with_props(TYPE_DBUS_DISPLAY,
                           object_get_objects_root(),
                           "dbus-display", &error_fatal,
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 04/24] qemu-options.hx: clarify default audio backend selection
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (2 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 03/24] audio/dbus: make "dbus" the default backend when using -display dbus marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 05/24] audio: introduce AudioDriver marcandre.lureau
                   ` (20 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qemu-options.hx | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/qemu-options.hx b/qemu-options.hx
index fca2b7bc74..4aa6c8829c 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -894,11 +894,16 @@ DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev,
     QEMU_ARCH_ALL)
 SRST
 ``-audiodev [driver=]driver,id=id[,prop[=value][,...]]``
-    Adds a new audio backend driver identified by id. There are global
-    and driver specific properties. Some values can be set differently
-    for input and output, they're marked with ``in|out.``. You can set
-    the input's property with ``in.prop`` and the output's property with
-    ``out.prop``. For example:
+    Adds a new audio backend driver identified by id.
+
+    If no audio backend is specified, QEMU will attempt to load a
+    default one. The selected -display option may influence which backend
+    is used.
+
+    There are global and driver specific properties. Some values can be
+    set differently for input and output, they're marked with ``in|out.``.
+    You can set the input's property with ``in.prop`` and the output's
+    property with ``out.prop``. For example:
 
     ::
 
@@ -2170,8 +2175,9 @@ DEF("display", HAS_ARG, QEMU_OPTION_display,
     , QEMU_ARCH_ALL)
 SRST
 ``-display type``
-    Select type of display to use. Use ``-display help`` to list the available
-    display types. Valid values for type are
+    Select type of display to use. It affects the default audio backend selection.
+
+    Use ``-display help`` to list the available display types. Valid values for type are
 
     ``spice-app[,gl=on|off]``
         Start QEMU as a Spice server and launch the default Spice client
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 05/24] audio: introduce AudioDriver
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (3 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 04/24] qemu-options.hx: clarify default audio backend selection marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-11  5:22   ` Akihiko Odaki
  2025-12-01 11:22 ` [RFC 06/24] audio: simplify audio_init() marcandre.lureau
                   ` (19 subsequent siblings)
  24 siblings, 1 reply; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Introduce a sub-class for current "audio_driver" based implementations.
Future AudioBackend implementations can do without it.

Next cleanup will actually remove "audio_driver" struct altogether and
make the subclass proper QOM objects.

Public APIs still rely on backend being an AudioDriver. They will
assert() if not. This will be addressed later to allow other backends.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_int.h      |  25 +++++---
 audio/audio_template.h |  18 +++---
 include/qemu/audio.h   |  17 +++++-
 audio/alsaaudio.c      |   2 +-
 audio/audio.c          | 126 +++++++++++++++++++++++++++++++----------
 audio/dbusaudio.c      |   8 +--
 audio/ossaudio.c       |   4 +-
 ui/dbus.c              |   3 +-
 8 files changed, 142 insertions(+), 61 deletions(-)

diff --git a/audio/audio_int.h b/audio/audio_int.h
index b2b8002477..e1f962875f 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -61,7 +61,7 @@ struct audio_pcm_info {
     int swap_endianness;
 };
 
-typedef struct AudioBackend AudioBackend;
+typedef struct AudioDriver AudioDriver;
 typedef struct SWVoiceCap SWVoiceCap;
 
 typedef struct STSampleBuffer {
@@ -70,7 +70,7 @@ typedef struct STSampleBuffer {
 } STSampleBuffer;
 
 typedef struct HWVoiceOut {
-    AudioBackend *s;
+    AudioDriver *s;
     bool enabled;
     int poll_mode;
     bool pending_disable;
@@ -91,7 +91,7 @@ typedef struct HWVoiceOut {
 } HWVoiceOut;
 
 typedef struct HWVoiceIn {
-    AudioBackend *s;
+    AudioDriver *s;
     bool enabled;
     int poll_mode;
     struct audio_pcm_info info;
@@ -112,7 +112,7 @@ typedef struct HWVoiceIn {
 } HWVoiceIn;
 
 struct SWVoiceOut {
-    AudioBackend *s;
+    AudioDriver *s;
     struct audio_pcm_info info;
     t_sample *conv;
     STSampleBuffer resample_buf;
@@ -128,7 +128,7 @@ struct SWVoiceOut {
 };
 
 struct SWVoiceIn {
-    AudioBackend *s;
+    AudioDriver *s;
     bool active;
     struct audio_pcm_info info;
     void *rate;
@@ -241,8 +241,12 @@ struct SWVoiceCap {
     QLIST_ENTRY (SWVoiceCap) entries;
 };
 
-typedef struct AudioBackend {
-    Object parent;
+struct AudioDriverClass {
+    AudioBackendClass parent_class;
+};
+
+struct AudioDriver {
+    AudioBackend parent_obj;
 
     struct audio_driver *drv;
     Audiodev *dev;
@@ -260,7 +264,7 @@ typedef struct AudioBackend {
     bool timer_running;
     uint64_t timer_last;
     VMChangeStateEntry *vmse;
-} AudioBackend;
+};
 
 extern const struct mixeng_volume nominal_volume;
 
@@ -273,7 +277,7 @@ void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len);
 
 int audio_bug (const char *funcname, int cond);
 
-void audio_run(AudioBackend *s, const char *msg);
+void audio_run(AudioDriver *s, const char *msg);
 
 const char *audio_application_name(void);
 
@@ -326,4 +330,7 @@ void audio_create_pdos(Audiodev *dev);
 AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev);
 AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev);
 
+#define TYPE_AUDIO_DRIVER "audio-driver"
+OBJECT_DECLARE_TYPE(AudioDriver, AudioDriverClass, AUDIO_DRIVER)
+
 #endif /* QEMU_AUDIO_INT_H */
diff --git a/audio/audio_template.h b/audio/audio_template.h
index 1ab3c47fd7..925a878f6d 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -36,7 +36,7 @@
 #define HWBUF hw->conv_buf
 #endif
 
-static void glue(audio_init_nb_voices_, TYPE)(AudioBackend *s,
+static void glue(audio_init_nb_voices_, TYPE)(AudioDriver *s,
                                               struct audio_driver *drv, int min_voices)
 {
     int max_voices = glue (drv->max_voices_, TYPE);
@@ -221,7 +221,7 @@ static void glue (audio_pcm_hw_del_sw_, TYPE) (SW *sw)
 static void glue (audio_pcm_hw_gc_, TYPE) (HW **hwp)
 {
     HW *hw = *hwp;
-    AudioBackend *s = hw->s;
+    AudioDriver *s = hw->s;
 
     if (!hw->sw_head.lh_first) {
 #ifdef DAC
@@ -236,12 +236,12 @@ static void glue (audio_pcm_hw_gc_, TYPE) (HW **hwp)
     }
 }
 
-static HW *glue(audio_pcm_hw_find_any_, TYPE)(AudioBackend *s, HW *hw)
+static HW *glue(audio_pcm_hw_find_any_, TYPE)(AudioDriver *s, HW *hw)
 {
     return hw ? hw->entries.le_next : glue (s->hw_head_, TYPE).lh_first;
 }
 
-static HW *glue(audio_pcm_hw_find_any_enabled_, TYPE)(AudioBackend *s, HW *hw)
+static HW *glue(audio_pcm_hw_find_any_enabled_, TYPE)(AudioDriver *s, HW *hw)
 {
     while ((hw = glue(audio_pcm_hw_find_any_, TYPE)(s, hw))) {
         if (hw->enabled) {
@@ -251,7 +251,7 @@ static HW *glue(audio_pcm_hw_find_any_enabled_, TYPE)(AudioBackend *s, HW *hw)
     return NULL;
 }
 
-static HW *glue(audio_pcm_hw_find_specific_, TYPE)(AudioBackend *s, HW *hw,
+static HW *glue(audio_pcm_hw_find_specific_, TYPE)(AudioDriver *s, HW *hw,
                                                    struct audsettings *as)
 {
     while ((hw = glue(audio_pcm_hw_find_any_, TYPE)(s, hw))) {
@@ -262,7 +262,7 @@ static HW *glue(audio_pcm_hw_find_specific_, TYPE)(AudioBackend *s, HW *hw,
     return NULL;
 }
 
-static HW *glue(audio_pcm_hw_add_new_, TYPE)(AudioBackend *s,
+static HW *glue(audio_pcm_hw_add_new_, TYPE)(AudioDriver *s,
                                              struct audsettings *as)
 {
     HW *hw;
@@ -398,7 +398,7 @@ AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev)
     abort();
 }
 
-static HW *glue(audio_pcm_hw_add_, TYPE)(AudioBackend *s, struct audsettings *as)
+static HW *glue(audio_pcm_hw_add_, TYPE)(AudioDriver *s, struct audsettings *as)
 {
     HW *hw;
     AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev);
@@ -424,7 +424,7 @@ static HW *glue(audio_pcm_hw_add_, TYPE)(AudioBackend *s, struct audsettings *as
 }
 
 static SW *glue(audio_pcm_create_voice_pair_, TYPE)(
-    AudioBackend *s,
+    AudioDriver *s,
     const char *sw_name,
     struct audsettings *as
     )
@@ -494,7 +494,7 @@ SW *glue (AUD_open_, TYPE) (
     struct audsettings *as
     )
 {
-    AudioBackend *s = be;
+    AudioDriver *s = AUDIO_DRIVER(be);
     AudiodevPerDirectionOptions *pdo;
 
     if (audio_bug(__func__, !be || !name || !callback_fn || !as)) {
diff --git a/include/qemu/audio.h b/include/qemu/audio.h
index 2562710bec..f83f8326ab 100644
--- a/include/qemu/audio.h
+++ b/include/qemu/audio.h
@@ -44,11 +44,21 @@ typedef struct audsettings {
 typedef struct SWVoiceOut SWVoiceOut;
 typedef struct SWVoiceIn SWVoiceIn;
 
-struct AudioBackendClass {
+typedef struct AudioBackend {
+    Object parent_obj;
+} AudioBackend;
+
+typedef struct AudioBackendClass {
     ObjectClass parent_class;
-};
 
-typedef struct AudioBackend AudioBackend;
+    const char *(*get_id)(AudioBackend *be);
+#ifdef CONFIG_GIO
+    bool (*set_dbus_server)(AudioBackend *be,
+                            GDBusObjectManagerServer *manager,
+                            bool p2p,
+                            Error **errp);
+#endif
+} AudioBackendClass;
 
 typedef struct QEMUAudioTimeStamp {
     uint64_t old_ts;
@@ -129,6 +139,7 @@ AudioBackend *audio_be_by_name(const char *name, Error **errp);
 AudioBackend *audio_get_default_audio_be(Error **errp);
 const char *audio_be_get_id(AudioBackend *be);
 #ifdef CONFIG_GIO
+bool audio_be_can_set_dbus_server(AudioBackend *be);
 bool audio_be_set_dbus_server(AudioBackend *be,
                               GDBusObjectManagerServer *server,
                               bool p2p,
diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c
index 7d7da576dc..278c74c5de 100644
--- a/audio/alsaaudio.c
+++ b/audio/alsaaudio.c
@@ -41,7 +41,7 @@ struct pollhlp {
     struct pollfd *pfds;
     int count;
     int mask;
-    AudioBackend *s;
+    AudioDriver *s;
 };
 
 typedef struct ALSAVoiceOut {
diff --git a/audio/audio.c b/audio/audio.c
index 0f992a775c..4a43761528 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -36,6 +36,7 @@
 #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"
@@ -383,7 +384,7 @@ void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
 /*
  * Capture
  */
-static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioBackend *s,
+static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioDriver *s,
                                                         struct audsettings *as)
 {
     CaptureVoiceOut *cap;
@@ -463,7 +464,7 @@ static void audio_detach_capture (HWVoiceOut *hw)
 
 static int audio_attach_capture (HWVoiceOut *hw)
 {
-    AudioBackend *s = hw->s;
+    AudioDriver *s = hw->s;
     CaptureVoiceOut *cap;
 
     audio_detach_capture (hw);
@@ -801,7 +802,7 @@ static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
 /*
  * Timer
  */
-static int audio_is_timer_needed(AudioBackend *s)
+static int audio_is_timer_needed(AudioDriver *s)
 {
     HWVoiceIn *hwi = NULL;
     HWVoiceOut *hwo = NULL;
@@ -819,7 +820,7 @@ static int audio_is_timer_needed(AudioBackend *s)
     return 0;
 }
 
-static void audio_reset_timer(AudioBackend *s)
+static void audio_reset_timer(AudioDriver *s)
 {
     if (audio_is_timer_needed(s)) {
         timer_mod_anticipate_ns(s->ts,
@@ -841,7 +842,7 @@ static void audio_reset_timer(AudioBackend *s)
 static void audio_timer (void *opaque)
 {
     int64_t now, diff;
-    AudioBackend *s = opaque;
+    AudioDriver *s = opaque;
 
     now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
     diff = now - s->timer_last;
@@ -924,7 +925,7 @@ void AUD_set_active_out(SWVoiceOut *sw, bool on)
 
     hw = sw->hw;
     if (sw->active != on) {
-        AudioBackend *s = sw->s;
+        AudioDriver *s = sw->s;
         SWVoiceOut *temp_sw;
         SWVoiceCap *sc;
 
@@ -972,7 +973,7 @@ void AUD_set_active_in(SWVoiceIn *sw, bool on)
 
     hw = sw->hw;
     if (sw->active != on) {
-        AudioBackend *s = sw->s;
+        AudioDriver *s = sw->s;
         SWVoiceIn *temp_sw;
 
         if (on) {
@@ -1140,7 +1141,7 @@ static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
     return clipped;
 }
 
-static void audio_run_out(AudioBackend *s)
+static void audio_run_out(AudioDriver *s)
 {
     HWVoiceOut *hw = NULL;
     SWVoiceOut *sw;
@@ -1294,7 +1295,7 @@ static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
     return conv;
 }
 
-static void audio_run_in(AudioBackend *s)
+static void audio_run_in(AudioDriver *s)
 {
     HWVoiceIn *hw = NULL;
 
@@ -1342,7 +1343,7 @@ static void audio_run_in(AudioBackend *s)
     }
 }
 
-static void audio_run_capture(AudioBackend *s)
+static void audio_run_capture(AudioDriver *s)
 {
     CaptureVoiceOut *cap;
 
@@ -1389,7 +1390,7 @@ static void audio_run_capture(AudioBackend *s)
     }
 }
 
-void audio_run(AudioBackend *s, const char *msg)
+void audio_run(AudioDriver *s, const char *msg)
 {
     audio_run_out(s);
     audio_run_in(s);
@@ -1562,8 +1563,8 @@ size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
     return total;
 }
 
-static bool audio_driver_init(AudioBackend *s, struct audio_driver *drv,
-                              Audiodev *dev, Error **errp)
+static bool audio_driver_do_init(AudioDriver *s, struct audio_driver *drv,
+                                 Audiodev *dev, Error **errp)
 {
     s->drv_opaque = drv->init(dev, errp);
     if (!s->drv_opaque) {
@@ -1595,7 +1596,7 @@ static bool audio_driver_init(AudioBackend *s, struct audio_driver *drv,
 static void audio_vm_change_state_handler (void *opaque, bool running,
                                            RunState state)
 {
-    AudioBackend *s = opaque;
+    AudioDriver *s = opaque;
     HWVoiceOut *hwo = NULL;
     HWVoiceIn *hwi = NULL;
 
@@ -1618,7 +1619,47 @@ static const VMStateDescription vmstate_audio;
 
 static void audio_be_init(Object *obj)
 {
-    AudioBackend *s = AUDIO_BACKEND(obj);
+}
+
+static void audio_be_finalize(Object *obj)
+{
+}
+
+static const char *audio_driver_get_id(AudioBackend *be)
+{
+    return AUDIO_DRIVER(be)->dev->id;
+}
+
+#ifdef CONFIG_GIO
+static bool audio_driver_set_dbus_server(AudioBackend *be,
+                                            GDBusObjectManagerServer *manager,
+                                            bool p2p,
+                                            Error **errp)
+{
+    AudioDriver *d = AUDIO_DRIVER(be);
+
+    if (!d->drv->set_dbus_server) {
+        return false;
+    }
+
+    return d->drv->set_dbus_server(be, manager, p2p, errp);
+}
+
+#endif
+
+static void audio_driver_class_init(ObjectClass *klass, const void *data)
+{
+    AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
+
+    be->get_id = audio_driver_get_id;
+#ifdef CONFIG_GIO
+    be->set_dbus_server = audio_driver_set_dbus_server;
+#endif
+}
+
+static void audio_driver_init(Object *obj)
+{
+    AudioDriver *s = AUDIO_DRIVER(obj);
 
     QLIST_INIT(&s->hw_head_out);
     QLIST_INIT(&s->hw_head_in);
@@ -1631,9 +1672,9 @@ static void audio_be_init(Object *obj)
     vmstate_register_any(NULL, &vmstate_audio, s);
 }
 
-static void audio_be_finalize(Object *obj)
+static void audio_driver_finalize(Object *obj)
 {
-    AudioBackend *s = AUDIO_BACKEND(obj);
+    AudioDriver *s = AUDIO_DRIVER(obj);
     HWVoiceOut *hwo, *hwon;
     HWVoiceIn *hwi, *hwin;
 
@@ -1749,10 +1790,10 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
 {
     int done = 0;
     const char *drvname;
-    AudioBackend *s;
+    AudioDriver *s;
     struct audio_driver *driver;
 
-    s = AUDIO_BACKEND(object_new(TYPE_AUDIO_BACKEND));
+    s = AUDIO_DRIVER(object_new(TYPE_AUDIO_DRIVER));
 
     if (dev) {
         /* -audiodev option */
@@ -1760,7 +1801,7 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
         drvname = AudiodevDriver_str(dev->driver);
         driver = audio_driver_lookup(drvname);
         if (driver) {
-            done = audio_driver_init(s, driver, dev, errp);
+            done = audio_driver_do_init(s, driver, dev, errp);
         } else {
             error_setg(errp, "Unknown audio driver `%s'", drvname);
         }
@@ -1780,7 +1821,7 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
             g_free(e);
             drvname = AudiodevDriver_str(dev->driver);
             driver = audio_driver_lookup(drvname);
-            if (audio_driver_init(s, driver, dev, NULL)) {
+            if (audio_driver_do_init(s, driver, dev, NULL)) {
                 break;
             }
             qapi_free_Audiodev(dev);
@@ -1792,7 +1833,7 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
         goto out;
     }
     object_unref(s);
-    return s;
+    return AUDIO_BACKEND(s);
 
 out:
     object_unref(s);
@@ -1831,17 +1872,19 @@ bool AUD_backend_check(AudioBackend **be, Error **errp)
 static struct audio_pcm_ops capture_pcm_ops;
 
 CaptureVoiceOut *AUD_add_capture(
-    AudioBackend *s,
+    AudioBackend *be,
     struct audsettings *as,
     struct audio_capture_ops *ops,
     void *cb_opaque
     )
 {
+    AudioDriver *s = AUDIO_DRIVER(be);
     CaptureVoiceOut *cap;
     struct capture_callback *cb;
 
     if (!s) {
-        error_report("Capturing without setting an audiodev is not supported");
+        /* TODO: implement an interface instead (or drop capture support) */
+        error_report("Capturing without setting an audiodev driver is not supported");
         abort();
     }
 
@@ -2227,27 +2270,36 @@ AudioBackend *audio_be_by_name(const char *name, Error **errp)
 }
 
 #ifdef CONFIG_GIO
+bool audio_be_can_set_dbus_server(AudioBackend *be)
+{
+    /*
+     * AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+     * return klass->set_dbus_server != NULL;
+     */
+     return AUDIO_DRIVER(be)->drv->set_dbus_server != NULL;
+}
+
 bool audio_be_set_dbus_server(AudioBackend *be,
                               GDBusObjectManagerServer *server,
                               bool p2p,
                               Error **errp)
 {
-    assert(be != NULL);
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
-    if (!be->drv->set_dbus_server) {
-        error_setg(errp, "Audiodev '%s' is not compatible with DBus", be->dev->id);
+    if (!audio_be_can_set_dbus_server(be)) {
+        error_setg(errp, "Audiodev '%s' is not compatible with DBus",
+                   audio_be_get_id(be));
         return false;
     }
 
-    return be->drv->set_dbus_server(be, server, p2p, errp);
+    return klass->set_dbus_server(be, server, p2p, errp);
 }
 #endif
 
 const char *audio_be_get_id(AudioBackend *be)
 {
     if (be) {
-        assert(be->dev);
-        return be->dev->id;
+        return AUDIO_BACKEND_GET_CLASS(be)->get_id(be);
     } else {
         return "";
     }
@@ -2322,13 +2374,25 @@ static const TypeInfo audio_be_info = {
     .instance_size = sizeof(AudioBackend),
     .instance_init = audio_be_init,
     .instance_finalize = audio_be_finalize,
-    .abstract = false, /* TODO: subclass drivers and make it abstract */
+    .abstract = true,
     .class_size = sizeof(AudioBackendClass),
 };
 
+static const TypeInfo audio_driver_info = {
+    .name = TYPE_AUDIO_DRIVER,
+    .parent = TYPE_AUDIO_BACKEND,
+    .instance_size = sizeof(AudioDriver),
+    .instance_init = audio_driver_init,
+    .instance_finalize = audio_driver_finalize,
+    .abstract = false,
+    .class_size = sizeof(AudioDriverClass),
+    .class_init = audio_driver_class_init,
+};
+
 static void register_types(void)
 {
     type_register_static(&audio_be_info);
+    type_register_static(&audio_driver_info);
 }
 
 type_init(register_types);
diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c
index 1fe7c4ed64..71b2a6c2cf 100644
--- a/audio/dbusaudio.c
+++ b/audio/dbusaudio.c
@@ -464,7 +464,7 @@ listener_in_vanished_cb(GDBusConnection *connection,
 }
 
 static gboolean
-dbus_audio_register_listener(AudioBackend *s,
+dbus_audio_register_listener(AudioDriver *s,
                              GDBusMethodInvocation *invocation,
 #ifdef G_OS_UNIX
                              GUnixFDList *fd_list,
@@ -621,7 +621,7 @@ dbus_audio_register_listener(AudioBackend *s,
 }
 
 static gboolean
-dbus_audio_register_out_listener(AudioBackend *s,
+dbus_audio_register_out_listener(AudioDriver *s,
                                  GDBusMethodInvocation *invocation,
 #ifdef G_OS_UNIX
                                  GUnixFDList *fd_list,
@@ -637,7 +637,7 @@ dbus_audio_register_out_listener(AudioBackend *s,
 }
 
 static gboolean
-dbus_audio_register_in_listener(AudioBackend *s,
+dbus_audio_register_in_listener(AudioDriver *s,
                                 GDBusMethodInvocation *invocation,
 #ifdef G_OS_UNIX
                                 GUnixFDList *fd_list,
@@ -657,7 +657,7 @@ dbus_audio_set_server(AudioBackend *s,
                       bool p2p,
                       Error **errp)
 {
-    DBusAudio *da = s->drv_opaque;
+    DBusAudio *da = AUDIO_DRIVER(s)->drv_opaque;
 
     g_assert(da);
     g_assert(!da->server);
diff --git a/audio/ossaudio.c b/audio/ossaudio.c
index c6cad47a01..9576cdba51 100644
--- a/audio/ossaudio.c
+++ b/audio/ossaudio.c
@@ -107,13 +107,13 @@ static void oss_anal_close (int *fdp)
 
 static void oss_helper_poll_out (void *opaque)
 {
-    AudioBackend *s = opaque;
+    AudioDriver *s = opaque;
     audio_run(s, "oss_poll_out");
 }
 
 static void oss_helper_poll_in (void *opaque)
 {
-    AudioBackend *s = opaque;
+    AudioDriver *s = opaque;
     audio_run(s, "oss_poll_in");
 }
 
diff --git a/ui/dbus.c b/ui/dbus.c
index 45fb3c1aa3..b0caa9b154 100644
--- a/ui/dbus.c
+++ b/ui/dbus.c
@@ -220,7 +220,7 @@ dbus_display_complete(UserCreatable *uc, Error **errp)
     }
 
     AudioBackend *audio_be = audio_get_default_audio_be(NULL);
-    if (audio_be && !g_str_equal(audio_be->drv->name, "dbus")) {
+    if (audio_be && !audio_be_can_set_dbus_server(audio_be)) {
         audio_be = NULL;
     }
     if (dd->audiodev && *dd->audiodev) {
@@ -233,7 +233,6 @@ dbus_display_complete(UserCreatable *uc, Error **errp)
         return;
     }
 
-
     consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
     for (idx = 0;; idx++) {
         if (!qemu_console_lookup_by_index(idx)) {
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 06/24] audio: simplify audio_init()
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (4 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 05/24] audio: introduce AudioDriver marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 07/24] audio: move object creation to audio_driver_init() marcandre.lureau
                   ` (18 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Factorize looking up the driver in audio_driver_init()

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio.c | 66 ++++++++++++++++++++++-----------------------------
 1 file changed, 29 insertions(+), 37 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index 4a43761528..7b978e862c 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1563,11 +1563,18 @@ size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
     return total;
 }
 
-static bool audio_driver_do_init(AudioDriver *s, struct audio_driver *drv,
-                                 Audiodev *dev, Error **errp)
+static bool audio_driver_do_init(AudioDriver *d, Error **errp)
 {
-    s->drv_opaque = drv->init(dev, errp);
-    if (!s->drv_opaque) {
+    const char *drvname = AudiodevDriver_str(d->dev->driver);
+    struct audio_driver *drv = audio_driver_lookup(drvname);
+
+    if (!drv) {
+        error_setg(errp, "Unknown audio driver `%s'", drvname);
+        return false;
+    }
+
+    d->drv_opaque = drv->init(d->dev, errp);
+    if (!d->drv_opaque) {
         return false;
     }
 
@@ -1580,14 +1587,14 @@ static bool audio_driver_do_init(AudioDriver *s, struct audio_driver *drv,
         drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;
     }
 
-    audio_init_nb_voices_out(s, drv, 1);
-    audio_init_nb_voices_in(s, drv, 0);
-    s->drv = drv;
+    audio_init_nb_voices_out(d, drv, 1);
+    audio_init_nb_voices_in(d, drv, 0);
+    d->drv = drv;
 
-    if (dev->timer_period <= 0) {
-        s->period_ticks = 1;
+    if (d->dev->timer_period <= 0) {
+        d->period_ticks = 1;
     } else {
-        s->period_ticks = dev->timer_period * (int64_t)SCALE_US;
+        d->period_ticks = d->dev->timer_period * (int64_t)SCALE_US;
     }
 
     return true;
@@ -1788,24 +1795,12 @@ void audio_create_default_audiodevs(void)
  */
 static AudioBackend *audio_init(Audiodev *dev, Error **errp)
 {
-    int done = 0;
-    const char *drvname;
-    AudioDriver *s;
-    struct audio_driver *driver;
+    AudioDriver *d;
 
-    s = AUDIO_DRIVER(object_new(TYPE_AUDIO_DRIVER));
-
-    if (dev) {
-        /* -audiodev option */
-        s->dev = dev;
-        drvname = AudiodevDriver_str(dev->driver);
-        driver = audio_driver_lookup(drvname);
-        if (driver) {
-            done = audio_driver_do_init(s, driver, dev, errp);
-        } else {
-            error_setg(errp, "Unknown audio driver `%s'", drvname);
-        }
-        if (!done) {
+    d = AUDIO_DRIVER(object_new(TYPE_AUDIO_DRIVER));
+    d->dev = dev;
+    if (d->dev) {
+        if (!audio_driver_do_init(d, errp)) {
             goto out;
         }
     } else {
@@ -1816,27 +1811,24 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
                 error_setg(errp, "no default audio driver available");
                 goto out;
             }
-            s->dev = dev = e->dev;
+            d->dev = e->dev;
             QSIMPLEQ_REMOVE_HEAD(&default_audiodevs, next);
             g_free(e);
-            drvname = AudiodevDriver_str(dev->driver);
-            driver = audio_driver_lookup(drvname);
-            if (audio_driver_do_init(s, driver, dev, NULL)) {
+            if (audio_driver_do_init(d, NULL)) {
                 break;
             }
-            qapi_free_Audiodev(dev);
-            s->dev = NULL;
         }
     }
 
-    if (!object_property_try_add_child(get_audiodevs_root(), dev->id, OBJECT(s), errp)) {
+    if (!object_property_try_add_child(get_audiodevs_root(),
+                                       d->dev->id, OBJECT(d), errp)) {
         goto out;
     }
-    object_unref(s);
-    return AUDIO_BACKEND(s);
+    object_unref(d);
+    return AUDIO_BACKEND(d);
 
 out:
-    object_unref(s);
+    object_unref(d);
     return NULL;
 }
 
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 07/24] audio: move object creation to audio_driver_init()
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (5 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 06/24] audio: simplify audio_init() marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 08/24] audio: add QOM module-objects for each backend marcandre.lureau
                   ` (17 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

As we will allow other kind of AudioBackend objects to be instantiated,
move the object allocation to a dedicated function.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio.c | 44 ++++++++++++++++++++++----------------------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index 7b978e862c..fab6024207 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1563,19 +1563,23 @@ size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
     return total;
 }
 
-static bool audio_driver_do_init(AudioDriver *d, Error **errp)
+static AudioBackend *audio_be_new(Audiodev *dev, Error **errp)
 {
-    const char *drvname = AudiodevDriver_str(d->dev->driver);
+    const char *drvname = AudiodevDriver_str(dev->driver);
     struct audio_driver *drv = audio_driver_lookup(drvname);
 
     if (!drv) {
         error_setg(errp, "Unknown audio driver `%s'", drvname);
-        return false;
+        return NULL;
     }
 
+    AudioDriver *d = AUDIO_DRIVER(object_new(TYPE_AUDIO_DRIVER));
+    d->dev = dev;
+
     d->drv_opaque = drv->init(d->dev, errp);
     if (!d->drv_opaque) {
-        return false;
+        object_unref(OBJECT(d));
+        return NULL;
     }
 
     if (!drv->pcm_ops->get_buffer_in) {
@@ -1597,7 +1601,7 @@ static bool audio_driver_do_init(AudioDriver *d, Error **errp)
         d->period_ticks = d->dev->timer_period * (int64_t)SCALE_US;
     }
 
-    return true;
+    return AUDIO_BACKEND(d);
 }
 
 static void audio_vm_change_state_handler (void *opaque, bool running,
@@ -1795,13 +1799,12 @@ void audio_create_default_audiodevs(void)
  */
 static AudioBackend *audio_init(Audiodev *dev, Error **errp)
 {
-    AudioDriver *d;
+    AudioBackend *be;
 
-    d = AUDIO_DRIVER(object_new(TYPE_AUDIO_DRIVER));
-    d->dev = dev;
-    if (d->dev) {
-        if (!audio_driver_do_init(d, errp)) {
-            goto out;
+    if (dev) {
+        be = audio_be_new(dev, errp);
+        if (!be) {
+            return NULL;
         }
     } else {
         assert(!default_audio_be);
@@ -1809,27 +1812,24 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
             AudiodevListEntry *e = QSIMPLEQ_FIRST(&default_audiodevs);
             if (!e) {
                 error_setg(errp, "no default audio driver available");
-                goto out;
+                return NULL;
             }
-            d->dev = e->dev;
             QSIMPLEQ_REMOVE_HEAD(&default_audiodevs, next);
+            be = audio_be_new(e->dev, NULL);
             g_free(e);
-            if (audio_driver_do_init(d, NULL)) {
+            if (be) {
                 break;
             }
         }
     }
 
     if (!object_property_try_add_child(get_audiodevs_root(),
-                                       d->dev->id, OBJECT(d), errp)) {
-        goto out;
+                                       audio_be_get_id(be), OBJECT(be), errp)) {
+        object_unref(be);
+        return NULL;
     }
-    object_unref(d);
-    return AUDIO_BACKEND(d);
-
-out:
-    object_unref(d);
-    return NULL;
+    object_unref(be);
+    return be;
 }
 
 AudioBackend *audio_get_default_audio_be(Error **errp)
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 08/24] audio: add QOM module-objects for each backend
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (6 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 07/24] audio: move object creation to audio_driver_init() marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 13:20   ` BALATON Zoltan
  2025-12-01 11:22 ` [RFC 09/24] audio: remove set_dbus_server from audio_driver marcandre.lureau
                   ` (16 subsequent siblings)
  24 siblings, 1 reply; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

This will allow to use QOM and the dynamic object module loading.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_int.h   |  2 ++
 audio/alsaaudio.c   | 39 +++++++++++++++++++++++++++++++++++++++
 audio/dbusaudio.c   | 36 ++++++++++++++++++++++++++++++++++++
 audio/dsoundaudio.c | 36 ++++++++++++++++++++++++++++++++++++
 audio/jackaudio.c   | 36 ++++++++++++++++++++++++++++++++++++
 audio/noaudio.c     | 36 ++++++++++++++++++++++++++++++++++++
 audio/ossaudio.c    | 36 ++++++++++++++++++++++++++++++++++++
 audio/paaudio.c     | 36 ++++++++++++++++++++++++++++++++++++
 audio/pwaudio.c     | 36 ++++++++++++++++++++++++++++++++++++
 audio/sdlaudio.c    | 36 ++++++++++++++++++++++++++++++++++++
 audio/sndioaudio.c  | 36 ++++++++++++++++++++++++++++++++++++
 audio/spiceaudio.c  | 36 ++++++++++++++++++++++++++++++++++++
 audio/wavaudio.c    | 36 ++++++++++++++++++++++++++++++++++++
 audio/coreaudio.m   | 36 ++++++++++++++++++++++++++++++++++++
 14 files changed, 473 insertions(+)

diff --git a/audio/audio_int.h b/audio/audio_int.h
index e1f962875f..44e63318e8 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -243,6 +243,8 @@ struct SWVoiceCap {
 
 struct AudioDriverClass {
     AudioBackendClass parent_class;
+
+    audio_driver *driver;
 };
 
 struct AudioDriver {
diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c
index 278c74c5de..c28ee05b93 100644
--- a/audio/alsaaudio.c
+++ b/audio/alsaaudio.c
@@ -27,6 +27,7 @@
 #include "qemu/main-loop.h"
 #include "qemu/module.h"
 #include "qemu/audio.h"
+#include "qom/object.h"
 #include "trace.h"
 
 #pragma GCC diagnostic ignored "-Waddress"
@@ -36,6 +37,32 @@
 
 #define DEBUG_ALSA 0
 
+#define TYPE_AUDIO_ALSA "audio-alsa"
+OBJECT_DECLARE_TYPE(AudioALSA, AudioBEDriverClass, AUDIO_ALSA)
+
+struct AudioALSA {
+    AudioDriver parent;
+};
+
+static struct audio_driver alsa_audio_driver;
+
+static void audio_alsa_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &alsa_audio_driver;
+}
+
+static void audio_alsa_init(Object *obj)
+{
+
+}
+
+static void audio_alsa_finalize(Object *obj)
+{
+
+}
+
 struct pollhlp {
     snd_pcm_t *handle;
     struct pollfd *pfds;
@@ -945,8 +972,20 @@ static struct audio_driver alsa_audio_driver = {
     .voice_size_in  = sizeof (ALSAVoiceIn)
 };
 
+static const TypeInfo audio_alsa_info = {
+    .name = TYPE_AUDIO_ALSA,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioALSA),
+    .instance_init = audio_alsa_init,
+    .instance_finalize = audio_alsa_finalize,
+    .class_init = audio_alsa_class_init,
+};
+
+
 static void register_audio_alsa(void)
 {
     audio_driver_register(&alsa_audio_driver);
+    type_register_static(&audio_alsa_info);
 }
 type_init(register_audio_alsa);
+module_obj(TYPE_AUDIO_ALSA);
diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c
index 71b2a6c2cf..f0b17be6e8 100644
--- a/audio/dbusaudio.c
+++ b/audio/dbusaudio.c
@@ -26,6 +26,7 @@
 #include "qemu/error-report.h"
 #include "qemu/module.h"
 #include "qemu/dbus.h"
+#include "qom/object.h"
 
 #ifdef G_OS_UNIX
 #include <gio/gunixfdlist.h>
@@ -44,6 +45,30 @@
 
 #define DBUS_DEFAULT_AUDIO_NSAMPLES 480
 
+#define TYPE_AUDIO_DBUS "audio-dbus"
+OBJECT_DECLARE_TYPE(AudioDbus, AudioDriverClass, AUDIO_DBUS)
+
+struct AudioDbus {
+    AudioDriver parent;
+};
+
+static struct audio_driver dbus_audio_driver;
+
+static void audio_dbus_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &dbus_audio_driver;
+}
+
+static void audio_dbus_init(Object *obj)
+{
+}
+
+static void audio_dbus_finalize(Object *obj)
+{
+}
+
 typedef struct DBusAudio {
     Audiodev *dev;
     GDBusObjectManagerServer *server;
@@ -711,10 +736,21 @@ static struct audio_driver dbus_audio_driver = {
     .voice_size_in   = sizeof(DBusVoiceIn)
 };
 
+static const TypeInfo audio_dbus_info = {
+    .name = TYPE_AUDIO_DBUS,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioDbus),
+    .instance_init = audio_dbus_init,
+    .instance_finalize = audio_dbus_finalize,
+    .class_init = audio_dbus_class_init,
+};
+
 static void register_audio_dbus(void)
 {
     audio_driver_register(&dbus_audio_driver);
+    type_register_static(&audio_dbus_info);
 }
 type_init(register_audio_dbus);
 
 module_dep("ui-dbus")
+module_obj(TYPE_AUDIO_DBUS)
diff --git a/audio/dsoundaudio.c b/audio/dsoundaudio.c
index 7a03d1dad8..341df9e6d8 100644
--- a/audio/dsoundaudio.c
+++ b/audio/dsoundaudio.c
@@ -33,6 +33,7 @@
 #include "audio_int.h"
 #include "qemu/module.h"
 #include "qapi/error.h"
+#include "qom/object.h"
 
 #include <windows.h>
 #include <mmsystem.h>
@@ -41,6 +42,30 @@
 
 #include "audio_win_int.h"
 
+#define TYPE_AUDIO_DSOUND "audio-dsound"
+OBJECT_DECLARE_TYPE(AudioDsound, AudioDriverClass, AUDIO_DSOUND)
+
+struct AudioDsound {
+    AudioDriver parent;
+};
+
+static struct audio_driver dsound_audio_driver;
+
+static void audio_dsound_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &dsound_audio_driver;
+}
+
+static void audio_dsound_init(Object *obj)
+{
+}
+
+static void audio_dsound_finalize(Object *obj)
+{
+}
+
 /* #define DEBUG_DSOUND */
 
 typedef struct {
@@ -694,8 +719,19 @@ static struct audio_driver dsound_audio_driver = {
     .voice_size_in  = sizeof (DSoundVoiceIn)
 };
 
+static const TypeInfo audio_dsound_info = {
+    .name = TYPE_AUDIO_DSOUND,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioDsound),
+    .instance_init = audio_dsound_init,
+    .instance_finalize = audio_dsound_finalize,
+    .class_init = audio_dsound_class_init,
+};
+
 static void register_audio_dsound(void)
 {
     audio_driver_register(&dsound_audio_driver);
+    type_register_static(&audio_dsound_info);
 }
 type_init(register_audio_dsound);
+module_obj(TYPE_AUDIO_DSOUND);
diff --git a/audio/jackaudio.c b/audio/jackaudio.c
index 7a3fcaedba..7706fbcb6e 100644
--- a/audio/jackaudio.c
+++ b/audio/jackaudio.c
@@ -27,6 +27,7 @@
 #include "qemu/atomic.h"
 #include "qemu/main-loop.h"
 #include "qemu/audio.h"
+#include "qom/object.h"
 
 #define AUDIO_CAP "jack"
 #include "audio_int.h"
@@ -34,6 +35,30 @@
 #include <jack/jack.h>
 #include <jack/thread.h>
 
+#define TYPE_AUDIO_JACK "audio-jack"
+OBJECT_DECLARE_TYPE(AudioJack, AudioDriverClass, AUDIO_JACK)
+
+struct AudioJack {
+    AudioDriver parent;
+};
+
+static struct audio_driver jack_driver;
+
+static void audio_jack_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &jack_driver;
+}
+
+static void audio_jack_init(Object *obj)
+{
+}
+
+static void audio_jack_finalize(Object *obj)
+{
+}
+
 struct QJack;
 
 typedef enum QJackState {
@@ -691,10 +716,20 @@ static void qjack_info(const char *msg)
     dolog("I: %s\n", msg);
 }
 
+static const TypeInfo audio_jack_info = {
+    .name = TYPE_AUDIO_JACK,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioJack),
+    .instance_init = audio_jack_init,
+    .instance_finalize = audio_jack_finalize,
+    .class_init = audio_jack_class_init,
+};
+
 static void register_audio_jack(void)
 {
     qemu_mutex_init(&qjack_shutdown_lock);
     audio_driver_register(&jack_driver);
+    type_register_static(&audio_jack_info);
 #if !defined(WIN32) && defined(CONFIG_PTHREAD_SETNAME_NP_W_TID)
     jack_set_thread_creator(qjack_thread_creator);
 #endif
@@ -702,3 +737,4 @@ static void register_audio_jack(void)
     jack_set_info_function(qjack_info);
 }
 type_init(register_audio_jack);
+module_obj(TYPE_AUDIO_JACK);
diff --git a/audio/noaudio.c b/audio/noaudio.c
index 4ed9d2156c..6f5af23004 100644
--- a/audio/noaudio.c
+++ b/audio/noaudio.c
@@ -25,10 +25,35 @@
 #include "qemu/osdep.h"
 #include "qemu/module.h"
 #include "qemu/audio.h"
+#include "qom/object.h"
 
 #define AUDIO_CAP "noaudio"
 #include "audio_int.h"
 
+#define TYPE_AUDIO_NONE "audio-none"
+OBJECT_DECLARE_TYPE(AudioNone, AudioDriverClass, AUDIO_NONE)
+
+struct AudioNone {
+    AudioDriver parent;
+};
+
+static struct audio_driver no_audio_driver;
+
+static void audio_none_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &no_audio_driver;
+}
+
+static void audio_none_init(Object *obj)
+{
+}
+
+static void audio_none_finalize(Object *obj)
+{
+}
+
 typedef struct NoVoiceOut {
     HWVoiceOut hw;
     RateCtl rate;
@@ -138,8 +163,19 @@ static struct audio_driver no_audio_driver = {
     .voice_size_in  = sizeof (NoVoiceIn)
 };
 
+static const TypeInfo audio_none_info = {
+    .name = TYPE_AUDIO_NONE,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioNone),
+    .instance_init = audio_none_init,
+    .instance_finalize = audio_none_finalize,
+    .class_init = audio_none_class_init,
+};
+
 static void register_audio_none(void)
 {
     audio_driver_register(&no_audio_driver);
+    type_register_static(&audio_none_info);
 }
 type_init(register_audio_none);
+module_obj(TYPE_AUDIO_NONE);
diff --git a/audio/ossaudio.c b/audio/ossaudio.c
index 9576cdba51..f5ce04928a 100644
--- a/audio/ossaudio.c
+++ b/audio/ossaudio.c
@@ -30,11 +30,36 @@
 #include "qemu/host-utils.h"
 #include "qapi/error.h"
 #include "qemu/audio.h"
+#include "qom/object.h"
 #include "trace.h"
 
 #define AUDIO_CAP "oss"
 #include "audio_int.h"
 
+#define TYPE_AUDIO_OSS "audio-oss"
+OBJECT_DECLARE_TYPE(AudioOss, AudioDriverClass, AUDIO_OSS)
+
+struct AudioOss {
+    AudioDriver parent;
+};
+
+static struct audio_driver oss_audio_driver;
+
+static void audio_oss_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &oss_audio_driver;
+}
+
+static void audio_oss_init(Object *obj)
+{
+}
+
+static void audio_oss_finalize(Object *obj)
+{
+}
+
 #if defined OSS_GETVERSION && defined SNDCTL_DSP_POLICY
 #define USE_DSP_POLICY
 #endif
@@ -772,8 +797,19 @@ static struct audio_driver oss_audio_driver = {
     .voice_size_in  = sizeof (OSSVoiceIn)
 };
 
+static const TypeInfo audio_oss_info = {
+    .name = TYPE_AUDIO_OSS,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioOss),
+    .instance_init = audio_oss_init,
+    .instance_finalize = audio_oss_finalize,
+    .class_init = audio_oss_class_init,
+};
+
 static void register_audio_oss(void)
 {
     audio_driver_register(&oss_audio_driver);
+    type_register_static(&audio_oss_info);
 }
 type_init(register_audio_oss);
+module_obj(TYPE_AUDIO_OSS);
diff --git a/audio/paaudio.c b/audio/paaudio.c
index 0c06a39719..abeb2c2d81 100644
--- a/audio/paaudio.c
+++ b/audio/paaudio.c
@@ -4,12 +4,37 @@
 #include "qemu/module.h"
 #include "qemu/audio.h"
 #include "qapi/error.h"
+#include "qom/object.h"
 
 #include <pulse/pulseaudio.h>
 
 #define AUDIO_CAP "pulseaudio"
 #include "audio_int.h"
 
+#define TYPE_AUDIO_PA "audio-pa"
+OBJECT_DECLARE_TYPE(AudioPa, AudioDriverClass, AUDIO_PA)
+
+struct AudioPa {
+    AudioDriver parent;
+};
+
+static struct audio_driver pa_audio_driver;
+
+static void audio_pa_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &pa_audio_driver;
+}
+
+static void audio_pa_init(Object *obj)
+{
+}
+
+static void audio_pa_finalize(Object *obj)
+{
+}
+
 typedef struct PAConnection {
     char *server;
     int refcount;
@@ -931,8 +956,19 @@ static struct audio_driver pa_audio_driver = {
     .voice_size_in  = sizeof (PAVoiceIn),
 };
 
+static const TypeInfo audio_pa_info = {
+    .name = TYPE_AUDIO_PA,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioPa),
+    .instance_init = audio_pa_init,
+    .instance_finalize = audio_pa_finalize,
+    .class_init = audio_pa_class_init,
+};
+
 static void register_audio_pa(void)
 {
     audio_driver_register(&pa_audio_driver);
+    type_register_static(&audio_pa_info);
 }
 type_init(register_audio_pa);
+module_obj(TYPE_AUDIO_PA);
diff --git a/audio/pwaudio.c b/audio/pwaudio.c
index 30f717ccac..5b51a933ae 100644
--- a/audio/pwaudio.c
+++ b/audio/pwaudio.c
@@ -13,6 +13,7 @@
 #include "qemu/audio.h"
 #include "qemu/error-report.h"
 #include "qapi/error.h"
+#include "qom/object.h"
 #include <spa/param/audio/format-utils.h>
 #include <spa/utils/ringbuffer.h>
 #include <spa/utils/result.h>
@@ -27,6 +28,30 @@
 
 #include "audio_int.h"
 
+#define TYPE_AUDIO_PW "audio-pipewire"
+OBJECT_DECLARE_TYPE(AudioPw, AudioDriverClass, AUDIO_PW)
+
+struct AudioPw {
+    AudioDriver parent;
+};
+
+static struct audio_driver pw_audio_driver;
+
+static void audio_pw_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &pw_audio_driver;
+}
+
+static void audio_pw_init(Object *obj)
+{
+}
+
+static void audio_pw_finalize(Object *obj)
+{
+}
+
 typedef struct pwvolume {
     uint32_t channels;
     float values[SPA_AUDIO_MAX_CHANNELS];
@@ -847,10 +872,21 @@ static struct audio_driver pw_audio_driver = {
     .voice_size_in = sizeof(PWVoiceIn),
 };
 
+static const TypeInfo audio_pw_info = {
+    .name = TYPE_AUDIO_PW,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioPw),
+    .instance_init = audio_pw_init,
+    .instance_finalize = audio_pw_finalize,
+    .class_init = audio_pw_class_init,
+};
+
 static void
 register_audio_pw(void)
 {
     audio_driver_register(&pw_audio_driver);
+    type_register_static(&audio_pw_info);
 }
 
 type_init(register_audio_pw);
+module_obj(TYPE_AUDIO_PW);
diff --git a/audio/sdlaudio.c b/audio/sdlaudio.c
index 707110973a..65fab1427e 100644
--- a/audio/sdlaudio.c
+++ b/audio/sdlaudio.c
@@ -28,6 +28,7 @@
 #include "qemu/module.h"
 #include "qapi/error.h"
 #include "qemu/audio.h"
+#include "qom/object.h"
 
 #ifndef _WIN32
 #ifdef __sun__
@@ -40,6 +41,30 @@
 #define AUDIO_CAP "sdl"
 #include "audio_int.h"
 
+#define TYPE_AUDIO_SDL "audio-sdl"
+OBJECT_DECLARE_TYPE(AudioSdl, AudioDriverClass, AUDIO_SDL)
+
+struct AudioSdl {
+    AudioDriver parent;
+};
+
+static struct audio_driver sdl_audio_driver;
+
+static void audio_sdl_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &sdl_audio_driver;
+}
+
+static void audio_sdl_init(Object *obj)
+{
+}
+
+static void audio_sdl_finalize(Object *obj)
+{
+}
+
 typedef struct SDLVoiceOut {
     HWVoiceOut hw;
     int exit;
@@ -491,8 +516,19 @@ static struct audio_driver sdl_audio_driver = {
     .voice_size_in  = sizeof(SDLVoiceIn),
 };
 
+static const TypeInfo audio_sdl_info = {
+    .name = TYPE_AUDIO_SDL,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioSdl),
+    .instance_init = audio_sdl_init,
+    .instance_finalize = audio_sdl_finalize,
+    .class_init = audio_sdl_class_init,
+};
+
 static void register_audio_sdl(void)
 {
     audio_driver_register(&sdl_audio_driver);
+    type_register_static(&audio_sdl_info);
 }
 type_init(register_audio_sdl);
+module_obj(TYPE_AUDIO_SDL);
diff --git a/audio/sndioaudio.c b/audio/sndioaudio.c
index 8197b8b0b4..1a4912fb60 100644
--- a/audio/sndioaudio.c
+++ b/audio/sndioaudio.c
@@ -20,10 +20,35 @@
 #include "qemu/main-loop.h"
 #include "qemu/audio.h"
 #include "trace.h"
+#include "qom/object.h"
 
 #define AUDIO_CAP "sndio"
 #include "audio_int.h"
 
+#define TYPE_AUDIO_SNDIO "audio-sndio"
+OBJECT_DECLARE_TYPE(AudioSndio, AudioDriverClass, AUDIO_SNDIO)
+
+struct AudioSndio {
+    AudioDriver parent;
+};
+
+static struct audio_driver sndio_audio_driver;
+
+static void audio_sndio_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &sndio_audio_driver;
+}
+
+static void audio_sndio_init(Object *obj)
+{
+}
+
+static void audio_sndio_finalize(Object *obj)
+{
+}
+
 /* default latency in microseconds if no option is set */
 #define SNDIO_LATENCY_US   50000
 
@@ -555,9 +580,20 @@ static struct audio_driver sndio_audio_driver = {
     .voice_size_in  = sizeof(SndioVoice)
 };
 
+static const TypeInfo audio_sndio_info = {
+    .name = TYPE_AUDIO_SNDIO,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioSndio),
+    .instance_init = audio_sndio_init,
+    .instance_finalize = audio_sndio_finalize,
+    .class_init = audio_sndio_class_init,
+};
+
 static void register_audio_sndio(void)
 {
     audio_driver_register(&sndio_audio_driver);
+    type_register_static(&audio_sndio_info);
 }
 
 type_init(register_audio_sndio);
+module_obj(TYPE_AUDIO_SNDIO);
diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c
index 7db2d1f0df..d8451d5877 100644
--- a/audio/spiceaudio.c
+++ b/audio/spiceaudio.c
@@ -24,11 +24,36 @@
 #include "qemu/timer.h"
 #include "qapi/error.h"
 #include "ui/qemu-spice.h"
+#include "qom/object.h"
 
 #define AUDIO_CAP "spice"
 #include "qemu/audio.h"
 #include "audio_int.h"
 
+#define TYPE_AUDIO_SPICE "audio-spice"
+OBJECT_DECLARE_TYPE(AudioSpice, AudioDriverClass, AUDIO_SPICE)
+
+struct AudioSpice {
+    AudioDriver parent;
+};
+
+static struct audio_driver spice_audio_driver;
+
+static void audio_spice_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &spice_audio_driver;
+}
+
+static void audio_spice_init(Object *obj)
+{
+}
+
+static void audio_spice_finalize(Object *obj)
+{
+}
+
 #if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3
 #define LINE_OUT_SAMPLES (480 * 4)
 #else
@@ -325,10 +350,21 @@ static struct audio_driver spice_audio_driver = {
     .voice_size_in  = sizeof (SpiceVoiceIn),
 };
 
+static const TypeInfo audio_spice_info = {
+    .name = TYPE_AUDIO_SPICE,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioSpice),
+    .instance_init = audio_spice_init,
+    .instance_finalize = audio_spice_finalize,
+    .class_init = audio_spice_class_init,
+};
+
 static void register_audio_spice(void)
 {
     audio_driver_register(&spice_audio_driver);
+    type_register_static(&audio_spice_info);
 }
 type_init(register_audio_spice);
+module_obj(TYPE_AUDIO_SPICE);
 
 module_dep("ui-spice-core");
diff --git a/audio/wavaudio.c b/audio/wavaudio.c
index 46460a5d57..3c1f352f11 100644
--- a/audio/wavaudio.c
+++ b/audio/wavaudio.c
@@ -25,10 +25,35 @@
 #include "qemu/osdep.h"
 #include "qemu/module.h"
 #include "qemu/audio.h"
+#include "qom/object.h"
 
 #define AUDIO_CAP "wav"
 #include "audio_int.h"
 
+#define TYPE_AUDIO_WAV "audio-wav"
+OBJECT_DECLARE_TYPE(AudioWav, AudioDriverClass, AUDIO_WAV)
+
+struct AudioWav {
+    AudioDriver parent;
+};
+
+static struct audio_driver wav_audio_driver;
+
+static void audio_wav_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &wav_audio_driver;
+}
+
+static void audio_wav_init(Object *obj)
+{
+}
+
+static void audio_wav_finalize(Object *obj)
+{
+}
+
 typedef struct WAVVoiceOut {
     HWVoiceOut hw;
     FILE *f;
@@ -214,8 +239,19 @@ static struct audio_driver wav_audio_driver = {
     .voice_size_in  = 0
 };
 
+static const TypeInfo audio_wav_info = {
+    .name = TYPE_AUDIO_WAV,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioWav),
+    .instance_init = audio_wav_init,
+    .instance_finalize = audio_wav_finalize,
+    .class_init = audio_wav_class_init,
+};
+
 static void register_audio_wav(void)
 {
     audio_driver_register(&wav_audio_driver);
+    type_register_static(&audio_wav_info);
 }
 type_init(register_audio_wav);
+module_obj(TYPE_AUDIO_WAV);
diff --git a/audio/coreaudio.m b/audio/coreaudio.m
index 997017a1e9..d1a837f4bc 100644
--- a/audio/coreaudio.m
+++ b/audio/coreaudio.m
@@ -29,10 +29,35 @@
 #include "qemu/main-loop.h"
 #include "qemu/module.h"
 #include "qemu/audio.h"
+#include "qom/object.h"
 
 #define AUDIO_CAP "coreaudio"
 #include "audio_int.h"
 
+#define TYPE_AUDIO_COREAUDIO "audio-coreaudio"
+OBJECT_DECLARE_TYPE(AudioCoreaudio, AudioDriverClass, AUDIO_COREAUDIO)
+
+struct AudioCoreaudio {
+    AudioDriver parent;
+};
+
+static struct audio_driver coreaudio_audio_driver;
+
+static void audio_coreaudio_class_init(ObjectClass *klass, const void *data)
+{
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    k->driver = &coreaudio_audio_driver;
+}
+
+static void audio_coreaudio_init(Object *obj)
+{
+}
+
+static void audio_coreaudio_finalize(Object *obj)
+{
+}
+
 typedef struct coreaudioVoiceOut {
     HWVoiceOut hw;
     pthread_mutex_t buf_mutex;
@@ -673,8 +698,19 @@ static void coreaudio_audio_fini (void *opaque)
     .voice_size_in  = 0
 };
 
+static const TypeInfo audio_coreaudio_info = {
+    .name = TYPE_AUDIO_COREAUDIO,
+    .parent = TYPE_AUDIO_DRIVER,
+    .instance_size = sizeof(AudioCoreaudio),
+    .instance_init = audio_coreaudio_init,
+    .instance_finalize = audio_coreaudio_finalize,
+    .class_init = audio_coreaudio_class_init,
+};
+
 static void register_audio_coreaudio(void)
 {
     audio_driver_register(&coreaudio_audio_driver);
+    type_register_static(&audio_coreaudio_info);
 }
 type_init(register_audio_coreaudio);
+module_obj(TYPE_AUDIO_COREAUDIO);
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 09/24] audio: remove set_dbus_server from audio_driver
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (7 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 08/24] audio: add QOM module-objects for each backend marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 10/24] audio: lookup "audio-" object types, and realize them marcandre.lureau
                   ` (15 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Use the QOM class vtable only instead.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_int.h |  6 ------
 audio/audio.c     | 28 +++-------------------------
 audio/dbusaudio.c | 34 ++++++++++++++++++----------------
 3 files changed, 21 insertions(+), 47 deletions(-)

diff --git a/audio/audio_int.h b/audio/audio_int.h
index 44e63318e8..e289ef5f66 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -147,12 +147,6 @@ struct audio_driver {
     const char *name;
     void *(*init) (Audiodev *, Error **);
     void (*fini) (void *);
-#ifdef CONFIG_GIO
-    bool (*set_dbus_server)(AudioBackend *be,
-                            GDBusObjectManagerServer *manager,
-                            bool p2p,
-                            Error **errp);
-#endif
     struct audio_pcm_ops *pcm_ops;
     int max_voices_out;
     int max_voices_in;
diff --git a/audio/audio.c b/audio/audio.c
index fab6024207..1693563c62 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1641,31 +1641,11 @@ static const char *audio_driver_get_id(AudioBackend *be)
     return AUDIO_DRIVER(be)->dev->id;
 }
 
-#ifdef CONFIG_GIO
-static bool audio_driver_set_dbus_server(AudioBackend *be,
-                                            GDBusObjectManagerServer *manager,
-                                            bool p2p,
-                                            Error **errp)
-{
-    AudioDriver *d = AUDIO_DRIVER(be);
-
-    if (!d->drv->set_dbus_server) {
-        return false;
-    }
-
-    return d->drv->set_dbus_server(be, manager, p2p, errp);
-}
-
-#endif
-
 static void audio_driver_class_init(ObjectClass *klass, const void *data)
 {
     AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
 
     be->get_id = audio_driver_get_id;
-#ifdef CONFIG_GIO
-    be->set_dbus_server = audio_driver_set_dbus_server;
-#endif
 }
 
 static void audio_driver_init(Object *obj)
@@ -2264,11 +2244,9 @@ AudioBackend *audio_be_by_name(const char *name, Error **errp)
 #ifdef CONFIG_GIO
 bool audio_be_can_set_dbus_server(AudioBackend *be)
 {
-    /*
-     * AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-     * return klass->set_dbus_server != NULL;
-     */
-     return AUDIO_DRIVER(be)->drv->set_dbus_server != NULL;
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->set_dbus_server != NULL;
 }
 
 bool audio_be_set_dbus_server(AudioBackend *be,
diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c
index f0b17be6e8..edd551a80f 100644
--- a/audio/dbusaudio.c
+++ b/audio/dbusaudio.c
@@ -54,21 +54,6 @@ struct AudioDbus {
 
 static struct audio_driver dbus_audio_driver;
 
-static void audio_dbus_class_init(ObjectClass *klass, const void *data)
-{
-    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
-
-    k->driver = &dbus_audio_driver;
-}
-
-static void audio_dbus_init(Object *obj)
-{
-}
-
-static void audio_dbus_finalize(Object *obj)
-{
-}
-
 typedef struct DBusAudio {
     Audiodev *dev;
     GDBusObjectManagerServer *server;
@@ -728,7 +713,6 @@ static struct audio_driver dbus_audio_driver = {
     .name            = "dbus",
     .init            = dbus_audio_init,
     .fini            = dbus_audio_fini,
-    .set_dbus_server = dbus_audio_set_server,
     .pcm_ops         = &dbus_pcm_ops,
     .max_voices_out  = INT_MAX,
     .max_voices_in   = INT_MAX,
@@ -736,6 +720,24 @@ static struct audio_driver dbus_audio_driver = {
     .voice_size_in   = sizeof(DBusVoiceIn)
 };
 
+static void audio_dbus_class_init(ObjectClass *klass, const void *data)
+{
+    AudioBackendClass *b = AUDIO_BACKEND_CLASS(klass);
+    AudioDriverClass *k = AUDIO_DRIVER_CLASS(klass);
+
+    b->set_dbus_server = dbus_audio_set_server;
+    k->driver = &dbus_audio_driver;
+}
+
+static void audio_dbus_init(Object *obj)
+{
+}
+
+static void audio_dbus_finalize(Object *obj)
+{
+}
+
+
 static const TypeInfo audio_dbus_info = {
     .name = TYPE_AUDIO_DBUS,
     .parent = TYPE_AUDIO_DRIVER,
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 10/24] audio: lookup "audio-" object types, and realize them
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (8 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 09/24] audio: remove set_dbus_server from audio_driver marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 11/24] audio: switch to module-object, drop audio driver registration marcandre.lureau
                   ` (14 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Now "audio_driver" is a detail implementation of AudioBEDriver and not
required to implement an AudioBE.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qemu/audio.h |  1 +
 audio/audio.c        | 38 +++++++++++++++++++++++++-------------
 2 files changed, 26 insertions(+), 13 deletions(-)

diff --git a/include/qemu/audio.h b/include/qemu/audio.h
index f83f8326ab..3abf1037f8 100644
--- a/include/qemu/audio.h
+++ b/include/qemu/audio.h
@@ -51,6 +51,7 @@ typedef struct AudioBackend {
 typedef struct AudioBackendClass {
     ObjectClass parent_class;
 
+    bool (*realize)(AudioBackend *be, Audiodev *dev, Error **errp);
     const char *(*get_id)(AudioBackend *be);
 #ifdef CONFIG_GIO
     bool (*set_dbus_server)(AudioBackend *be,
diff --git a/audio/audio.c b/audio/audio.c
index 1693563c62..31459f9707 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1563,23 +1563,15 @@ size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
     return total;
 }
 
-static AudioBackend *audio_be_new(Audiodev *dev, Error **errp)
+static bool audio_be_driver_realize(AudioBackend *abe, Audiodev *dev, Error **errp)
 {
-    const char *drvname = AudiodevDriver_str(dev->driver);
-    struct audio_driver *drv = audio_driver_lookup(drvname);
-
-    if (!drv) {
-        error_setg(errp, "Unknown audio driver `%s'", drvname);
-        return NULL;
-    }
+    AudioDriver *d = AUDIO_DRIVER(abe);
+    audio_driver *drv = AUDIO_DRIVER_GET_CLASS(d)->driver;
 
-    AudioDriver *d = AUDIO_DRIVER(object_new(TYPE_AUDIO_DRIVER));
     d->dev = dev;
-
     d->drv_opaque = drv->init(d->dev, errp);
     if (!d->drv_opaque) {
-        object_unref(OBJECT(d));
-        return NULL;
+        return false;
     }
 
     if (!drv->pcm_ops->get_buffer_in) {
@@ -1601,7 +1593,26 @@ static AudioBackend *audio_be_new(Audiodev *dev, Error **errp)
         d->period_ticks = d->dev->timer_period * (int64_t)SCALE_US;
     }
 
-    return AUDIO_BACKEND(d);
+    return true;
+}
+
+static AudioBackend *audio_be_new(Audiodev *dev, Error **errp)
+{
+    const char *drvname = AudiodevDriver_str(dev->driver);
+    g_autofree char *type = g_strconcat("audio-", drvname, NULL);
+    AudioBackend *be = AUDIO_BACKEND(object_new(type));
+
+    if (!be) {
+        error_setg(errp, "Unknown audio driver `%s'", drvname);
+        return NULL;
+    }
+
+    if (!AUDIO_BACKEND_GET_CLASS(be)->realize(be, dev, errp)) {
+        object_unref(OBJECT(be));
+        return NULL;
+    }
+
+    return be;
 }
 
 static void audio_vm_change_state_handler (void *opaque, bool running,
@@ -1645,6 +1656,7 @@ static void audio_driver_class_init(ObjectClass *klass, const void *data)
 {
     AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
 
+    be->realize = audio_be_driver_realize;
     be->get_id = audio_driver_get_id;
 }
 
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 11/24] audio: switch to module-object, drop audio driver registration
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (9 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 10/24] audio: lookup "audio-" object types, and realize them marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 12/24] module: remove audio module support marcandre.lureau
                   ` (13 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

module_object_class_by_name() handles loading the module providing the
requested object.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_int.h   |  2 --
 audio/alsaaudio.c   |  1 -
 audio/audio.c       | 43 +++++++++++++------------------------------
 audio/dbusaudio.c   |  1 -
 audio/dsoundaudio.c |  1 -
 audio/jackaudio.c   |  1 -
 audio/noaudio.c     |  1 -
 audio/ossaudio.c    |  1 -
 audio/paaudio.c     |  1 -
 audio/pwaudio.c     |  1 -
 audio/sdlaudio.c    |  1 -
 audio/sndioaudio.c  |  1 -
 audio/spiceaudio.c  |  1 -
 audio/wavaudio.c    |  1 -
 audio/coreaudio.m   |  1 -
 15 files changed, 13 insertions(+), 45 deletions(-)

diff --git a/audio/audio_int.h b/audio/audio_int.h
index e289ef5f66..eaf7838bfe 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -266,8 +266,6 @@ extern const struct mixeng_volume nominal_volume;
 
 extern const char *audio_prio_list[];
 
-void audio_driver_register(audio_driver *drv);
-
 void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as);
 void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len);
 
diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c
index c28ee05b93..1056bdd63d 100644
--- a/audio/alsaaudio.c
+++ b/audio/alsaaudio.c
@@ -984,7 +984,6 @@ static const TypeInfo audio_alsa_info = {
 
 static void register_audio_alsa(void)
 {
-    audio_driver_register(&alsa_audio_driver);
     type_register_static(&audio_alsa_info);
 }
 type_init(register_audio_alsa);
diff --git a/audio/audio.c b/audio/audio.c
index 31459f9707..8523f14daa 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -67,40 +67,21 @@ const char *audio_prio_list[] = {
     NULL
 };
 
-static QLIST_HEAD(, audio_driver) audio_drivers;
 static AudiodevListHead audiodevs =
     QSIMPLEQ_HEAD_INITIALIZER(audiodevs);
 static AudiodevListHead default_audiodevs =
     QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);
 
-
-void audio_driver_register(audio_driver *drv)
-{
-    QLIST_INSERT_HEAD(&audio_drivers, drv, next);
-}
-
-static audio_driver *audio_driver_lookup(const char *name)
+static AudioBackendClass *audio_be_class_by_name(const char *name)
 {
-    struct audio_driver *d;
-    Error *local_err = NULL;
-    int rv;
+    g_autofree char *tname = g_strconcat("audio-", name, NULL);
+    ObjectClass *oc = module_object_class_by_name(tname);
 
-    QLIST_FOREACH(d, &audio_drivers, next) {
-        if (strcmp(name, d->name) == 0) {
-            return d;
-        }
-    }
-    rv = audio_module_load(name, &local_err);
-    if (rv > 0) {
-        QLIST_FOREACH(d, &audio_drivers, next) {
-            if (strcmp(name, d->name) == 0) {
-                return d;
-            }
-        }
-    } else if (rv < 0) {
-        error_report_err(local_err);
+    if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) {
+        return NULL;
     }
-    return NULL;
+
+    return AUDIO_BACKEND_CLASS(oc);
 }
 
 static AudioBackend *default_audio_be;
@@ -1765,7 +1746,7 @@ static const VMStateDescription vmstate_audio = {
 void audio_create_default_audiodevs(void)
 {
     for (int i = 0; audio_prio_list[i]; i++) {
-        if (audio_driver_lookup(audio_prio_list[i])) {
+        if (audio_be_class_by_name(audio_prio_list[i]) != NULL) {
             QDict *dict = qdict_new();
             Audiodev *dev = NULL;
             Visitor *v;
@@ -2129,9 +2110,11 @@ void audio_help(void)
     printf("Available audio drivers:\n");
 
     for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) {
-        audio_driver *driver = audio_driver_lookup(AudiodevDriver_str(i));
-        if (driver) {
-            printf("%s\n", driver->name);
+        const char *name = AudiodevDriver_str(i);
+        AudioBackendClass *be = audio_be_class_by_name(name);
+
+        if (be) {
+            printf("%s\n", name);
         }
     }
 }
diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c
index edd551a80f..187da7fa65 100644
--- a/audio/dbusaudio.c
+++ b/audio/dbusaudio.c
@@ -749,7 +749,6 @@ static const TypeInfo audio_dbus_info = {
 
 static void register_audio_dbus(void)
 {
-    audio_driver_register(&dbus_audio_driver);
     type_register_static(&audio_dbus_info);
 }
 type_init(register_audio_dbus);
diff --git a/audio/dsoundaudio.c b/audio/dsoundaudio.c
index 341df9e6d8..9c6ff93022 100644
--- a/audio/dsoundaudio.c
+++ b/audio/dsoundaudio.c
@@ -730,7 +730,6 @@ static const TypeInfo audio_dsound_info = {
 
 static void register_audio_dsound(void)
 {
-    audio_driver_register(&dsound_audio_driver);
     type_register_static(&audio_dsound_info);
 }
 type_init(register_audio_dsound);
diff --git a/audio/jackaudio.c b/audio/jackaudio.c
index 7706fbcb6e..d472f4e5c6 100644
--- a/audio/jackaudio.c
+++ b/audio/jackaudio.c
@@ -728,7 +728,6 @@ static const TypeInfo audio_jack_info = {
 static void register_audio_jack(void)
 {
     qemu_mutex_init(&qjack_shutdown_lock);
-    audio_driver_register(&jack_driver);
     type_register_static(&audio_jack_info);
 #if !defined(WIN32) && defined(CONFIG_PTHREAD_SETNAME_NP_W_TID)
     jack_set_thread_creator(qjack_thread_creator);
diff --git a/audio/noaudio.c b/audio/noaudio.c
index 6f5af23004..b788bc5f8e 100644
--- a/audio/noaudio.c
+++ b/audio/noaudio.c
@@ -174,7 +174,6 @@ static const TypeInfo audio_none_info = {
 
 static void register_audio_none(void)
 {
-    audio_driver_register(&no_audio_driver);
     type_register_static(&audio_none_info);
 }
 type_init(register_audio_none);
diff --git a/audio/ossaudio.c b/audio/ossaudio.c
index f5ce04928a..60aff632a6 100644
--- a/audio/ossaudio.c
+++ b/audio/ossaudio.c
@@ -808,7 +808,6 @@ static const TypeInfo audio_oss_info = {
 
 static void register_audio_oss(void)
 {
-    audio_driver_register(&oss_audio_driver);
     type_register_static(&audio_oss_info);
 }
 type_init(register_audio_oss);
diff --git a/audio/paaudio.c b/audio/paaudio.c
index abeb2c2d81..02fb29cb2f 100644
--- a/audio/paaudio.c
+++ b/audio/paaudio.c
@@ -967,7 +967,6 @@ static const TypeInfo audio_pa_info = {
 
 static void register_audio_pa(void)
 {
-    audio_driver_register(&pa_audio_driver);
     type_register_static(&audio_pa_info);
 }
 type_init(register_audio_pa);
diff --git a/audio/pwaudio.c b/audio/pwaudio.c
index 5b51a933ae..c42f81b820 100644
--- a/audio/pwaudio.c
+++ b/audio/pwaudio.c
@@ -884,7 +884,6 @@ static const TypeInfo audio_pw_info = {
 static void
 register_audio_pw(void)
 {
-    audio_driver_register(&pw_audio_driver);
     type_register_static(&audio_pw_info);
 }
 
diff --git a/audio/sdlaudio.c b/audio/sdlaudio.c
index 65fab1427e..e52616bec8 100644
--- a/audio/sdlaudio.c
+++ b/audio/sdlaudio.c
@@ -527,7 +527,6 @@ static const TypeInfo audio_sdl_info = {
 
 static void register_audio_sdl(void)
 {
-    audio_driver_register(&sdl_audio_driver);
     type_register_static(&audio_sdl_info);
 }
 type_init(register_audio_sdl);
diff --git a/audio/sndioaudio.c b/audio/sndioaudio.c
index 1a4912fb60..c1468ef2b9 100644
--- a/audio/sndioaudio.c
+++ b/audio/sndioaudio.c
@@ -591,7 +591,6 @@ static const TypeInfo audio_sndio_info = {
 
 static void register_audio_sndio(void)
 {
-    audio_driver_register(&sndio_audio_driver);
     type_register_static(&audio_sndio_info);
 }
 
diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c
index d8451d5877..1165f88400 100644
--- a/audio/spiceaudio.c
+++ b/audio/spiceaudio.c
@@ -361,7 +361,6 @@ static const TypeInfo audio_spice_info = {
 
 static void register_audio_spice(void)
 {
-    audio_driver_register(&spice_audio_driver);
     type_register_static(&audio_spice_info);
 }
 type_init(register_audio_spice);
diff --git a/audio/wavaudio.c b/audio/wavaudio.c
index 3c1f352f11..dbf0e8e00c 100644
--- a/audio/wavaudio.c
+++ b/audio/wavaudio.c
@@ -250,7 +250,6 @@ static const TypeInfo audio_wav_info = {
 
 static void register_audio_wav(void)
 {
-    audio_driver_register(&wav_audio_driver);
     type_register_static(&audio_wav_info);
 }
 type_init(register_audio_wav);
diff --git a/audio/coreaudio.m b/audio/coreaudio.m
index d1a837f4bc..9e4266ee97 100644
--- a/audio/coreaudio.m
+++ b/audio/coreaudio.m
@@ -709,7 +709,6 @@ static void coreaudio_audio_fini (void *opaque)
 
 static void register_audio_coreaudio(void)
 {
-    audio_driver_register(&coreaudio_audio_driver);
     type_register_static(&audio_coreaudio_info);
 }
 type_init(register_audio_coreaudio);
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 12/24] module: remove audio module support
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (10 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 11/24] audio: switch to module-object, drop audio driver registration marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 13/24] audio: keep a strong reference on the backend marcandre.lureau
                   ` (12 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

It relies on dynamic object loading support instead.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qemu/module.h | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/include/qemu/module.h b/include/qemu/module.h
index c37ce74b16..9885ac9afb 100644
--- a/include/qemu/module.h
+++ b/include/qemu/module.h
@@ -63,7 +63,6 @@ typedef enum {
 #define migration_init(function) module_init(function, MODULE_INIT_MIGRATION)
 #define block_module_load(lib, errp) module_load("block-", lib, errp)
 #define ui_module_load(lib, errp) module_load("ui-", lib, errp)
-#define audio_module_load(lib, errp) module_load("audio-", lib, errp)
 
 void register_module_init(void (*fn)(void), module_init_type type);
 void register_dso_module_init(void (*fn)(void), module_init_type type);
@@ -78,7 +77,7 @@ void module_call_init(module_init_type type);
  * - get_relocated_path(CONFIG_QEMU_MODDIR);
  * - /var/run/qemu/${version_dir}
  *
- * prefix:         a subsystem prefix, or the empty string ("audio-", ..., "")
+ * prefix:         a subsystem prefix, or the empty string ("ui-", ..., "")
  * name:           name of the module
  * errp:           error to set in case the module is found, but load failed.
  *
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 13/24] audio: keep a strong reference on the backend
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (11 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 12/24] module: remove audio module support marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 14/24] audio: make list type declaration private marcandre.lureau
                   ` (11 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Since we are going to convert audio_driver-based backends, we need to
properly handle reference counting to allow for a different order of
class finalization (for example, pulse class before base driver class).

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_template.h | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/audio/audio_template.h b/audio/audio_template.h
index 925a878f6d..7d204d25bc 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -231,6 +231,7 @@ static void glue (audio_pcm_hw_gc_, TYPE) (HW **hwp)
         glue(hw->pcm_ops->fini_, TYPE) (hw);
         glue(s->nb_hw_voices_, TYPE) += 1;
         glue(audio_pcm_hw_free_resources_ , TYPE) (hw);
+        object_unref(hw->s);
         g_free(hw);
         *hwp = NULL;
     }
@@ -287,7 +288,7 @@ static HW *glue(audio_pcm_hw_add_new_, TYPE)(AudioDriver *s,
      * is guaranteed to be != 0. See the audio_init_nb_voices_* functions.
      */
     hw = g_malloc0(glue(drv->voice_size_, TYPE));
-    hw->s = s;
+    hw->s = AUDIO_DRIVER(object_ref(s));
     hw->pcm_ops = drv->pcm_ops;
 
     QLIST_INIT (&hw->sw_head);
@@ -335,6 +336,7 @@ static HW *glue(audio_pcm_hw_add_new_, TYPE)(AudioDriver *s,
  err1:
     glue (hw->pcm_ops->fini_, TYPE) (hw);
  err0:
+    object_unref(hw->s);
     g_free (hw);
     return NULL;
 }
@@ -441,7 +443,7 @@ static SW *glue(audio_pcm_create_voice_pair_, TYPE)(
     }
 
     sw = g_new0(SW, 1);
-    sw->s = s;
+    sw->s = AUDIO_DRIVER(object_ref(s));
 
     hw = glue(audio_pcm_hw_add_, TYPE)(s, &hw_as);
     if (!hw) {
@@ -461,6 +463,7 @@ err2:
     glue (audio_pcm_hw_del_sw_, TYPE) (sw);
     glue (audio_pcm_hw_gc_, TYPE) (&hw);
 err1:
+    object_unref(sw->s);
     g_free(sw);
     return NULL;
 }
@@ -470,6 +473,8 @@ static void glue (audio_close_, TYPE) (SW *sw)
     glue (audio_pcm_sw_fini_, TYPE) (sw);
     glue (audio_pcm_hw_del_sw_, TYPE) (sw);
     glue (audio_pcm_hw_gc_, TYPE) (&sw->hw);
+
+    object_unref(sw->s);
     g_free (sw);
 }
 
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 14/24] audio: make list type declaration private
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (12 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 13/24] audio: keep a strong reference on the backend marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 15/24] audio: make create_pdos() private marcandre.lureau
                   ` (10 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

No need to share in a common header.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_int.h | 7 -------
 audio/audio.c     | 7 +++++++
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/audio/audio_int.h b/audio/audio_int.h
index eaf7838bfe..3bf1cf6a99 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -313,13 +313,6 @@ static inline size_t audio_ring_posb(size_t pos, size_t dist, size_t len)
 #define ldebug(fmt, ...) (void)0
 #endif
 
-typedef struct AudiodevListEntry {
-    Audiodev *dev;
-    QSIMPLEQ_ENTRY(AudiodevListEntry) next;
-} AudiodevListEntry;
-
-typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;
-
 void audio_create_pdos(Audiodev *dev);
 AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev);
 AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev);
diff --git a/audio/audio.c b/audio/audio.c
index 8523f14daa..138d7b502a 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -67,6 +67,13 @@ const char *audio_prio_list[] = {
     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 =
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 15/24] audio: make create_pdos() private
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (13 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 14/24] audio: make list type declaration private marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 16/24] replay: remove dependency on audio/ marcandre.lureau
                   ` (9 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Clean up.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_int.h | 1 -
 audio/audio.c     | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/audio/audio_int.h b/audio/audio_int.h
index 3bf1cf6a99..d2d54b924e 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -313,7 +313,6 @@ static inline size_t audio_ring_posb(size_t pos, size_t dist, size_t len)
 #define ldebug(fmt, ...) (void)0
 #endif
 
-void audio_create_pdos(Audiodev *dev);
 AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev);
 AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev);
 
diff --git a/audio/audio.c b/audio/audio.c
index 138d7b502a..7e5fb4ff8a 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1990,7 +1990,7 @@ void AUD_set_volume_in(SWVoiceIn *sw, Volume *vol)
     }
 }
 
-void audio_create_pdos(Audiodev *dev)
+static void audio_create_pdos(Audiodev *dev)
 {
     switch (dev->driver) {
 #define CASE(DRIVER, driver, pdo_name)                              \
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 16/24] replay: remove dependency on audio/
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (14 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 15/24] audio: make create_pdos() private marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 17/24] audio: make all the backend-specific APIs take the be marcandre.lureau
                   ` (8 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau, Paolo Bonzini,
	Alex Bennée

From: Marc-André Lureau <marcandre.lureau@redhat.com>

The replay infrastructure shouldn't depend on internals of audio/.
(st_sample is an internal implementation detail and could be different)

Let audio drive the audio samples recording/replaying.

Notice also we don't need to save & restore the internal ring "wpos",
all replay should care about is the number of samples and the samples.

Bump the replay version.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_int.h        |  5 ++++
 include/qemu/audio.h     |  5 ----
 include/system/replay.h  |  8 +++++--
 replay/replay-internal.h |  2 ++
 audio/audio.c            | 23 ++++++++++++++++--
 replay/replay-audio.c    | 51 +++++++++++++++++++++++-----------------
 replay/replay.c          |  2 +-
 replay/stubs-system.c    |  8 ++++++-
 8 files changed, 72 insertions(+), 32 deletions(-)

diff --git a/audio/audio_int.h b/audio/audio_int.h
index d2d54b924e..79e54fd60a 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -316,6 +316,11 @@ static inline size_t audio_ring_posb(size_t pos, size_t dist, size_t len)
 AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev);
 AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev);
 
+void audio_sample_to_uint64(const st_sample *sample, int pos,
+                            uint64_t *left, uint64_t *right);
+void audio_sample_from_uint64(st_sample *sample, int pos,
+                              uint64_t left, uint64_t right);
+
 #define TYPE_AUDIO_DRIVER "audio-driver"
 OBJECT_DECLARE_TYPE(AudioDriver, AudioDriverClass, AUDIO_DRIVER)
 
diff --git a/include/qemu/audio.h b/include/qemu/audio.h
index 3abf1037f8..07a2e23998 100644
--- a/include/qemu/audio.h
+++ b/include/qemu/audio.h
@@ -124,11 +124,6 @@ void audio_cleanup(void);
 
 typedef struct st_sample st_sample;
 
-void audio_sample_to_uint64(const st_sample *sample, int pos,
-                            uint64_t *left, uint64_t *right);
-void audio_sample_from_uint64(st_sample *sample, int pos,
-                            uint64_t left, uint64_t right);
-
 void audio_add_audiodev(Audiodev *audio);
 void audio_add_default_audiodev(Audiodev *dev, Error **errp);
 void audio_parse_option(const char *opt);
diff --git a/include/system/replay.h b/include/system/replay.h
index 68f91bdfbf..0ee5230586 100644
--- a/include/system/replay.h
+++ b/include/system/replay.h
@@ -165,8 +165,12 @@ void replay_net_packet_event(ReplayNetState *rns, unsigned flags,
 
 /*! Saves/restores number of played samples of audio out operation. */
 void replay_audio_out(size_t *played);
-/*! Saves/restores recorded samples of audio in operation. */
-void replay_audio_in(size_t *recorded, st_sample *samples, size_t *wpos, size_t size);
+/*! Start saves/restores recorded samples of audio in operation, must be called before replay_audio_in_sample_lr(). */
+void replay_audio_in_start(size_t *recorded);
+/*! Saves/restores recorded samples. */
+void replay_audio_in_sample_lr(uint64_t *left, uint64_t *right);
+/*! Finish saves/restores recorded samples. */
+void replay_audio_in_finish(void);
 
 /* VM state operations */
 
diff --git a/replay/replay-internal.h b/replay/replay-internal.h
index 75249b7693..643b357da1 100644
--- a/replay/replay-internal.h
+++ b/replay/replay-internal.h
@@ -85,6 +85,7 @@ enum ReplayEvents {
  * @file_offset: offset into replay log at replay snapshot
  * @block_request_id: current serialised block request id
  * @read_event_id: current async read event id
+ * @n_audio_samples: expected audio samples
  */
 typedef struct ReplayState {
     int64_t cached_clock[REPLAY_CLOCK_COUNT];
@@ -96,6 +97,7 @@ typedef struct ReplayState {
     uint64_t file_offset;
     uint64_t block_request_id;
     uint64_t read_event_id;
+    size_t n_audio_samples;
 } ReplayState;
 extern ReplayState replay_state;
 
diff --git a/audio/audio.c b/audio/audio.c
index 7e5fb4ff8a..f0e31ce4a0 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1301,13 +1301,32 @@ static void audio_run_in(AudioDriver *s)
     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(&captured, hw->conv_buf.buffer, &hw->conv_buf.pos,
-                        hw->conv_buf.size);
+
+        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;
diff --git a/replay/replay-audio.c b/replay/replay-audio.c
index 1b614f4137..7d20ae9110 100644
--- a/replay/replay-audio.c
+++ b/replay/replay-audio.c
@@ -13,7 +13,6 @@
 #include "qemu/error-report.h"
 #include "system/replay.h"
 #include "replay-internal.h"
-#include "qemu/audio.h"
 
 void replay_audio_out(size_t *played)
 {
@@ -35,38 +34,48 @@ void replay_audio_out(size_t *played)
     }
 }
 
-void replay_audio_in(size_t *recorded, st_sample *samples, size_t *wpos, size_t size)
+void replay_audio_in_start(size_t *nsamples)
 {
-    int pos;
-    uint64_t left, right;
     if (replay_mode == REPLAY_MODE_RECORD) {
         g_assert(replay_mutex_locked());
         replay_save_instructions();
         replay_put_event(EVENT_AUDIO_IN);
-        replay_put_qword(*recorded);
-        replay_put_qword(*wpos);
-        for (pos = (*wpos - *recorded + size) % size ; pos != *wpos
-             ; pos = (pos + 1) % size) {
-            audio_sample_to_uint64(samples, pos, &left, &right);
-            replay_put_qword(left);
-            replay_put_qword(right);
-        }
+        replay_put_qword(*nsamples);
+        replay_state.n_audio_samples = *nsamples;
     } else if (replay_mode == REPLAY_MODE_PLAY) {
         g_assert(replay_mutex_locked());
         replay_account_executed_instructions();
         if (replay_next_event_is(EVENT_AUDIO_IN)) {
-            *recorded = replay_get_qword();
-            *wpos = replay_get_qword();
-            for (pos = (*wpos - *recorded + size) % size ; pos != *wpos
-                 ; pos = (pos + 1) % size) {
-                left = replay_get_qword();
-                right = replay_get_qword();
-                audio_sample_from_uint64(samples, pos, left, right);
-            }
-            replay_finish_event();
+            *nsamples = replay_get_qword();
+            replay_state.n_audio_samples = *nsamples;
         } else {
             error_report("Missing audio in event in the replay log");
             abort();
         }
     }
 }
+
+void replay_audio_in_sample_lr(uint64_t *left, uint64_t *right)
+{
+    if (replay_mode == REPLAY_MODE_RECORD) {
+        replay_put_qword(*left);
+        replay_put_qword(*right);
+    } else if (replay_mode == REPLAY_MODE_PLAY) {
+        *left = replay_get_qword();
+        *right = replay_get_qword();
+    } else {
+        return;
+    }
+
+    assert(replay_state.n_audio_samples > 0);
+    replay_state.n_audio_samples--;
+}
+
+void replay_audio_in_finish(void)
+{
+    assert(replay_state.n_audio_samples == 0);
+
+    if (replay_mode == REPLAY_MODE_PLAY) {
+        replay_finish_event();
+    }
+}
diff --git a/replay/replay.c b/replay/replay.c
index b2121788c1..2e5c6fa82e 100644
--- a/replay/replay.c
+++ b/replay/replay.c
@@ -22,7 +22,7 @@
 
 /* Current version of the replay mechanism.
    Increase it when file format changes. */
-#define REPLAY_VERSION              0xe0200c
+#define REPLAY_VERSION              0xe0200d
 /* Size of replay log header */
 #define HEADER_SIZE                 (sizeof(uint32_t) + sizeof(uint64_t))
 
diff --git a/replay/stubs-system.c b/replay/stubs-system.c
index 7f85764936..b2c52bc404 100644
--- a/replay/stubs-system.c
+++ b/replay/stubs-system.c
@@ -15,7 +15,13 @@ void replay_input_sync_event(void)
 void replay_add_blocker(const char *feature)
 {
 }
-void replay_audio_in(size_t *recorded, st_sample *samples, size_t *wpos, size_t size)
+void replay_audio_in_start(size_t *nsamples)
+{
+}
+void replay_audio_in_sample_lr(uint64_t *left, uint64_t *right)
+{
+}
+void replay_audio_in_finish(void)
 {
 }
 void replay_audio_out(size_t *played)
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 17/24] audio: make all the backend-specific APIs take the be
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (15 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 16/24] replay: remove dependency on audio/ marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:22 ` [RFC 18/24] audio: make AudioBackend truely abstract marcandre.lureau
                   ` (7 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau, Laurent Vivier,
	Manos Pitsidianakis, Michael S. Tsirkin, Alistair Francis,
	Edgar E. Iglesias, Peter Maydell, open list:Xilinx ZynqMP and...

From: Marc-André Lureau <marcandre.lureau@redhat.com>

This will allow to dispatch to different implementations next.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_template.h       |  2 +-
 include/qemu/audio-capture.h |  9 ++++++---
 include/qemu/audio.h         | 30 +++++++++++++++---------------
 audio/audio.c                | 18 +++++++++---------
 audio/wavcapture.c           |  7 +++++--
 hw/audio/ac97.c              | 24 ++++++++++++------------
 hw/audio/adlib.c             |  9 +++++----
 hw/audio/asc.c               | 16 ++++++++--------
 hw/audio/cs4231a.c           | 16 ++++++++--------
 hw/audio/es1370.c            | 12 ++++++------
 hw/audio/gus.c               |  9 +++++----
 hw/audio/hda-codec.c         | 16 ++++++++--------
 hw/audio/lm4549.c            | 12 ++++++------
 hw/audio/pcspk.c             |  4 ++--
 hw/audio/sb16.c              | 14 +++++++-------
 hw/audio/via-ac97.c          | 14 +++++++-------
 hw/audio/virtio-snd.c        | 14 ++++++++------
 hw/audio/wm8750.c            | 24 ++++++++++++------------
 hw/display/xlnx_dp.c         | 10 +++++-----
 hw/usb/dev-audio.c           | 12 ++++++------
 ui/vnc.c                     |  2 +-
 21 files changed, 142 insertions(+), 132 deletions(-)

diff --git a/audio/audio_template.h b/audio/audio_template.h
index 7d204d25bc..8d66f9cd40 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -569,7 +569,7 @@ SW *glue (AUD_open_, TYPE) (
     return NULL;
 }
 
-bool glue(AUD_is_active_, TYPE)(SW *sw)
+bool glue(AUD_is_active_, TYPE)(AudioBackend *be, SW *sw)
 {
     return sw ? sw->active : 0;
 }
diff --git a/include/qemu/audio-capture.h b/include/qemu/audio-capture.h
index a07412db85..f500b0a7f8 100644
--- a/include/qemu/audio-capture.h
+++ b/include/qemu/audio-capture.h
@@ -36,8 +36,11 @@ CaptureVoiceOut *AUD_add_capture(
     AudioBackend *be,
     struct audsettings *as,
     struct audio_capture_ops *ops,
-    void *opaque
-    );
-void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque);
+    void *opaque);
+
+void AUD_del_capture(
+    AudioBackend *be,
+    CaptureVoiceOut *cap,
+    void *cb_opaque);
 
 #endif /* QEMU_AUDIO_CAPTURE_H */
diff --git a/include/qemu/audio.h b/include/qemu/audio.h
index 07a2e23998..745ee881b8 100644
--- a/include/qemu/audio.h
+++ b/include/qemu/audio.h
@@ -67,7 +67,7 @@ typedef struct QEMUAudioTimeStamp {
 
 bool AUD_backend_check(AudioBackend **be, Error **errp);
 
-SWVoiceOut *AUD_open_out (
+SWVoiceOut *AUD_open_out(
     AudioBackend *be,
     SWVoiceOut *sw,
     const char *name,
@@ -76,11 +76,11 @@ SWVoiceOut *AUD_open_out (
     struct audsettings *settings
     );
 
-void AUD_close_out (AudioBackend *be, SWVoiceOut *sw);
-size_t AUD_write (SWVoiceOut *sw, void *pcm_buf, size_t size);
-int  AUD_get_buffer_size_out (SWVoiceOut *sw);
-void AUD_set_active_out(SWVoiceOut *sw, bool on);
-bool AUD_is_active_out(SWVoiceOut *sw);
+void AUD_close_out(AudioBackend *be, SWVoiceOut *sw);
+size_t AUD_write(AudioBackend *be, SWVoiceOut *sw, void *pcm_buf, size_t size);
+int  AUD_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw);
+void AUD_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on);
+bool AUD_is_active_out(AudioBackend *be, SWVoiceOut *sw);
 
 #define AUDIO_MAX_CHANNELS 16
 typedef struct Volume {
@@ -89,19 +89,19 @@ typedef struct Volume {
     uint8_t vol[AUDIO_MAX_CHANNELS];
 } Volume;
 
-void AUD_set_volume_out(SWVoiceOut *sw, Volume *vol);
-void AUD_set_volume_in(SWVoiceIn *sw, Volume *vol);
+void AUD_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol);
+void AUD_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol);
 
 static inline void
-AUD_set_volume_out_lr(SWVoiceOut *sw, bool mut, uint8_t lvol, uint8_t rvol) {
-    AUD_set_volume_out(sw, &(Volume) {
+AUD_set_volume_out_lr(AudioBackend *be, SWVoiceOut *sw, bool mut, uint8_t lvol, uint8_t rvol) {
+    AUD_set_volume_out(be, sw, &(Volume) {
         .mute = mut, .channels = 2, .vol = { lvol, rvol }
     });
 }
 
 static inline void
-AUD_set_volume_in_lr(SWVoiceIn *sw, bool mut, uint8_t lvol, uint8_t rvol) {
-    AUD_set_volume_in(sw, &(Volume) {
+AUD_set_volume_in_lr(AudioBackend *be, SWVoiceIn *sw, bool mut, uint8_t lvol, uint8_t rvol) {
+    AUD_set_volume_in(be, sw, &(Volume) {
         .mute = mut, .channels = 2, .vol = { lvol, rvol }
     });
 }
@@ -116,9 +116,9 @@ SWVoiceIn *AUD_open_in(
     );
 
 void AUD_close_in(AudioBackend *be, SWVoiceIn *sw);
-size_t AUD_read (SWVoiceIn *sw, void *pcm_buf, size_t size);
-void AUD_set_active_in(SWVoiceIn *sw, bool on);
-bool AUD_is_active_in(SWVoiceIn *sw);
+size_t AUD_read(AudioBackend *be, SWVoiceIn *sw, void *pcm_buf, size_t size);
+void AUD_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on);
+bool AUD_is_active_in(AudioBackend *be, SWVoiceIn *sw);
 
 void audio_cleanup(void);
 
diff --git a/audio/audio.c b/audio/audio.c
index f0e31ce4a0..cc65153dfa 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -846,7 +846,7 @@ static void audio_timer (void *opaque)
 /*
  * Public API
  */
-size_t AUD_write(SWVoiceOut *sw, void *buf, size_t size)
+size_t AUD_write(AudioBackend *be, SWVoiceOut *sw, void *buf, size_t size)
 {
     HWVoiceOut *hw;
 
@@ -868,7 +868,7 @@ size_t AUD_write(SWVoiceOut *sw, void *buf, size_t size)
     }
 }
 
-size_t AUD_read(SWVoiceIn *sw, void *buf, size_t size)
+size_t AUD_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size)
 {
     HWVoiceIn *hw;
 
@@ -890,7 +890,7 @@ size_t AUD_read(SWVoiceIn *sw, void *buf, size_t size)
     }
 }
 
-int AUD_get_buffer_size_out(SWVoiceOut *sw)
+int AUD_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
 {
     if (!sw) {
         return 0;
@@ -903,7 +903,7 @@ int AUD_get_buffer_size_out(SWVoiceOut *sw)
     return sw->hw->samples * sw->hw->info.bytes_per_frame;
 }
 
-void AUD_set_active_out(SWVoiceOut *sw, bool on)
+void AUD_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
 {
     HWVoiceOut *hw;
 
@@ -951,7 +951,7 @@ void AUD_set_active_out(SWVoiceOut *sw, bool on)
     }
 }
 
-void AUD_set_active_in(SWVoiceIn *sw, bool on)
+void AUD_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
 {
     HWVoiceIn *hw;
 
@@ -996,7 +996,7 @@ void AUD_set_active_in(SWVoiceIn *sw, bool on)
     }
 }
 
-static size_t audio_get_avail (SWVoiceIn *sw)
+static size_t audio_get_avail(SWVoiceIn *sw)
 {
     size_t live;
 
@@ -1938,7 +1938,7 @@ CaptureVoiceOut *AUD_add_capture(
     return cap;
 }
 
-void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque)
+void AUD_del_capture(AudioBackend *be, CaptureVoiceOut *cap, void *cb_opaque)
 {
     struct capture_callback *cb;
 
@@ -1977,7 +1977,7 @@ void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque)
     }
 }
 
-void AUD_set_volume_out(SWVoiceOut *sw, Volume *vol)
+void AUD_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol)
 {
     if (sw) {
         HWVoiceOut *hw = sw->hw;
@@ -1993,7 +1993,7 @@ void AUD_set_volume_out(SWVoiceOut *sw, Volume *vol)
     }
 }
 
-void AUD_set_volume_in(SWVoiceIn *sw, Volume *vol)
+void AUD_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol)
 {
     if (sw) {
         HWVoiceIn *hw = sw->hw;
diff --git a/audio/wavcapture.c b/audio/wavcapture.c
index b33a38ff45..2950ceaf24 100644
--- a/audio/wavcapture.c
+++ b/audio/wavcapture.c
@@ -1,4 +1,5 @@
 #include "qemu/osdep.h"
+#include "qemu/audio.h"
 #include "qemu/qemu-print.h"
 #include "qemu/error-report.h"
 #include "audio_int.h"
@@ -10,6 +11,7 @@ typedef struct {
     int freq;
     int bits;
     int nchannels;
+    AudioBackend *audio_be;
     CaptureVoiceOut *cap;
 } WAVState;
 
@@ -84,7 +86,7 @@ static void wav_capture_destroy (void *opaque)
 {
     WAVState *wav = opaque;
 
-    AUD_del_capture (wav->cap, wav);
+    AUD_del_capture(wav->audio_be, wav->cap, wav);
     g_free (wav);
 }
 
@@ -159,6 +161,7 @@ int wav_start_capture(AudioBackend *state, CaptureState *s, const char *path,
         return -1;
     }
 
+    wav->audio_be = state;
     wav->path = g_strdup (path);
     wav->bits = bits;
     wav->nchannels = nchannels;
@@ -169,7 +172,7 @@ int wav_start_capture(AudioBackend *state, CaptureState *s, const char *path,
         goto error_free;
     }
 
-    cap = AUD_add_capture(state, &as, &ops, wav);
+    cap = AUD_add_capture(wav->audio_be, &as, &ops, wav);
     if (!cap) {
         error_report("Failed to add audio capture");
         goto error_free;
diff --git a/hw/audio/ac97.c b/hw/audio/ac97.c
index 60daa62ea3..e9a1daf92a 100644
--- a/hw/audio/ac97.c
+++ b/hw/audio/ac97.c
@@ -246,15 +246,15 @@ static void voice_set_active(AC97LinkState *s, int bm_index, int on)
 {
     switch (bm_index) {
     case PI_INDEX:
-        AUD_set_active_in(s->voice_pi, on);
+        AUD_set_active_in(s->audio_be, s->voice_pi, on);
         break;
 
     case PO_INDEX:
-        AUD_set_active_out(s->voice_po, on);
+        AUD_set_active_out(s->audio_be, s->voice_po, on);
         break;
 
     case MC_INDEX:
-        AUD_set_active_in(s->voice_mc, on);
+        AUD_set_active_in(s->audio_be, s->voice_mc, on);
         break;
 
     default:
@@ -378,15 +378,15 @@ static void reset_voices(AC97LinkState *s, uint8_t active[LAST_INDEX])
 
     freq = mixer_load(s, AC97_PCM_LR_ADC_Rate);
     open_voice(s, PI_INDEX, freq);
-    AUD_set_active_in(s->voice_pi, active[PI_INDEX]);
+    AUD_set_active_in(s->audio_be, s->voice_pi, active[PI_INDEX]);
 
     freq = mixer_load(s, AC97_PCM_Front_DAC_Rate);
     open_voice(s, PO_INDEX, freq);
-    AUD_set_active_out(s->voice_po, active[PO_INDEX]);
+    AUD_set_active_out(s->audio_be, s->voice_po, active[PO_INDEX]);
 
     freq = mixer_load(s, AC97_MIC_ADC_Rate);
     open_voice(s, MC_INDEX, freq);
-    AUD_set_active_in(s->voice_mc, active[MC_INDEX]);
+    AUD_set_active_in(s->audio_be, s->voice_mc, active[MC_INDEX]);
 }
 
 static void get_volume(uint16_t vol, uint16_t mask, int inverse,
@@ -416,7 +416,7 @@ static void update_combined_volume_out(AC97LinkState *s)
     lvol = (lvol * plvol) / 255;
     rvol = (rvol * prvol) / 255;
 
-    AUD_set_volume_out_lr(s->voice_po, mute, lvol, rvol);
+    AUD_set_volume_out_lr(s->audio_be, s->voice_po, mute, lvol, rvol);
 }
 
 static void update_volume_in(AC97LinkState *s)
@@ -427,7 +427,7 @@ static void update_volume_in(AC97LinkState *s)
     get_volume(mixer_load(s, AC97_Record_Gain_Mute), 0x0f, 0,
                &mute, &lvol, &rvol);
 
-    AUD_set_volume_in_lr(s->voice_pi, mute, lvol, rvol);
+    AUD_set_volume_in_lr(s->audio_be, s->voice_pi, mute, lvol, rvol);
 }
 
 static void set_volume(AC97LinkState *s, int index, uint32_t val)
@@ -904,7 +904,7 @@ static int write_audio(AC97LinkState *s, AC97BusMasterRegs *r,
         int copied;
         to_copy = MIN(temp, sizeof(tmpbuf));
         pci_dma_read(&s->dev, addr, tmpbuf, to_copy);
-        copied = AUD_write(s->voice_po, tmpbuf, to_copy);
+        copied = AUD_write(s->audio_be, s->voice_po, tmpbuf, to_copy);
         dolog("write_audio max=%x to_copy=%x copied=%x",
               max, to_copy, copied);
         if (!copied) {
@@ -948,7 +948,7 @@ static void write_bup(AC97LinkState *s, int elapsed)
     while (elapsed) {
         int temp = MIN(elapsed, sizeof(s->silence));
         while (temp) {
-            int copied = AUD_write(s->voice_po, s->silence, temp);
+            int copied = AUD_write(s->audio_be, s->voice_po, s->silence, temp);
             if (!copied) {
                 return;
             }
@@ -978,7 +978,7 @@ static int read_audio(AC97LinkState *s, AC97BusMasterRegs *r,
     while (temp) {
         int acquired;
         to_copy = MIN(temp, sizeof(tmpbuf));
-        acquired = AUD_read(voice, tmpbuf, to_copy);
+        acquired = AUD_read(s->audio_be, voice, tmpbuf, to_copy);
         if (!acquired) {
             *stop = 1;
             break;
@@ -1275,7 +1275,7 @@ static void ac97_realize(PCIDevice *dev, Error **errp)
     AC97LinkState *s = AC97(dev);
     uint8_t *c = s->dev.config;
 
-    if (!AUD_backend_check (&s->audio_be, errp)) {
+    if (!AUD_backend_check(&s->audio_be, errp)) {
         return;
     }
 
diff --git a/hw/audio/adlib.c b/hw/audio/adlib.c
index 2a2fe7d04f..d3677c1d7f 100644
--- a/hw/audio/adlib.c
+++ b/hw/audio/adlib.c
@@ -96,7 +96,7 @@ static void adlib_write(void *opaque, uint32_t nport, uint32_t val)
     int a = nport & 3;
 
     s->active = 1;
-    AUD_set_active_out (s->voice, 1);
+    AUD_set_active_out(s->audio_be, s->voice, 1);
 
     adlib_kill_timers (s);
 
@@ -145,7 +145,8 @@ static int write_audio (AdlibState *s, int samples)
         int nbytes, wbytes, wsampl;
 
         nbytes = samples << SHIFT;
-        wbytes = AUD_write (
+        wbytes = AUD_write(
+            s->audio_be,
             s->voice,
             s->mixbuf + (pos << (SHIFT - 1)),
             nbytes
@@ -257,7 +258,7 @@ static void adlib_realizefn (DeviceState *dev, Error **errp)
     as.fmt = AUDIO_FORMAT_S16;
     as.endianness = HOST_BIG_ENDIAN;
 
-    s->voice = AUD_open_out (
+    s->voice = AUD_open_out(
         s->audio_be,
         s->voice,
         "adlib",
@@ -271,7 +272,7 @@ static void adlib_realizefn (DeviceState *dev, Error **errp)
         return;
     }
 
-    s->samples = AUD_get_buffer_size_out (s->voice) >> SHIFT;
+    s->samples = AUD_get_buffer_size_out(s->audio_be, s->voice) >> SHIFT;
     s->mixbuf = g_malloc0 (s->samples << SHIFT);
 
     adlib_portio_list[0].offset = s->port;
diff --git a/hw/audio/asc.c b/hw/audio/asc.c
index 0abb106979..c7bb40df83 100644
--- a/hw/audio/asc.c
+++ b/hw/audio/asc.c
@@ -355,12 +355,12 @@ static void asc_out_cb(void *opaque, int free_b)
              * loop because the FIFO has run out of data, and the driver
              * reuses the stale content in its circular audio buffer.
              */
-            AUD_write(s->voice, s->silentbuf, samples << s->shift);
+            AUD_write(s->audio_be, s->voice, s->silentbuf, samples << s->shift);
         }
         return;
     }
 
-    AUD_write(s->voice, s->mixbuf, generated << s->shift);
+    AUD_write(s->audio_be, s->voice, s->mixbuf, generated << s->shift);
 }
 
 static uint64_t asc_fifo_read(void *opaque, hwaddr addr,
@@ -470,9 +470,9 @@ static void asc_write(void *opaque, hwaddr addr, uint64_t value,
             asc_fifo_reset(&s->fifos[1]);
             asc_lower_irq(s);
             if (value != 0) {
-                AUD_set_active_out(s->voice, 1);
+                AUD_set_active_out(s->audio_be, s->voice, 1);
             } else {
-                AUD_set_active_out(s->voice, 0);
+                AUD_set_active_out(s->audio_be, s->voice, 0);
             }
         }
         break;
@@ -489,7 +489,7 @@ static void asc_write(void *opaque, hwaddr addr, uint64_t value,
         {
             int vol = (value & 0xe0);
 
-            AUD_set_volume_out_lr(s->voice, 0, vol, vol);
+            AUD_set_volume_out_lr(s->audio_be, s->voice, 0, vol, vol);
             break;
         }
     }
@@ -545,7 +545,7 @@ static int asc_post_load(void *opaque, int version)
     ASCState *s = ASC(opaque);
 
     if (s->regs[ASC_MODE] != 0) {
-        AUD_set_active_out(s->voice, 1);
+        AUD_set_active_out(s->audio_be, s->voice, 1);
     }
 
     return 0;
@@ -614,7 +614,7 @@ static void asc_reset_hold(Object *obj, ResetType type)
 {
     ASCState *s = ASC(obj);
 
-    AUD_set_active_out(s->voice, 0);
+    AUD_set_active_out(s->audio_be, s->voice, 0);
 
     memset(s->regs, 0, sizeof(s->regs));
     asc_fifo_reset(&s->fifos[0]);
@@ -658,7 +658,7 @@ static void asc_realize(DeviceState *dev, Error **errp)
     }
 
     s->shift = 1;
-    s->samples = AUD_get_buffer_size_out(s->voice) >> s->shift;
+    s->samples = AUD_get_buffer_size_out(s->audio_be, s->voice) >> s->shift;
     s->mixbuf = g_malloc0(s->samples << s->shift);
 
     s->silentbuf = g_malloc(s->samples << s->shift);
diff --git a/hw/audio/cs4231a.c b/hw/audio/cs4231a.c
index 98fdbc5b72..ff68a9ccd9 100644
--- a/hw/audio/cs4231a.c
+++ b/hw/audio/cs4231a.c
@@ -327,7 +327,7 @@ static void cs_reset_voices (CSState *s, uint32_t val)
         goto error;
     }
 
-    s->voice = AUD_open_out (
+    s->voice = AUD_open_out(
         s->audio_be,
         s->voice,
         "cs4231a",
@@ -339,7 +339,7 @@ static void cs_reset_voices (CSState *s, uint32_t val)
     if (s->dregs[Interface_Configuration] & PEN) {
         if (!s->dma_running) {
             k->hold_DREQ(s->isa_dma, s->dma);
-            AUD_set_active_out (s->voice, 1);
+            AUD_set_active_out(s->audio_be, s->voice, 1);
             s->transferred = 0;
         }
         s->dma_running = 1;
@@ -347,7 +347,7 @@ static void cs_reset_voices (CSState *s, uint32_t val)
     else {
         if (s->dma_running) {
             k->release_DREQ(s->isa_dma, s->dma);
-            AUD_set_active_out (s->voice, 0);
+            AUD_set_active_out(s->audio_be, s->voice, 0);
         }
         s->dma_running = 0;
     }
@@ -356,7 +356,7 @@ static void cs_reset_voices (CSState *s, uint32_t val)
  error:
     if (s->dma_running) {
         k->release_DREQ(s->isa_dma, s->dma);
-        AUD_set_active_out (s->voice, 0);
+        AUD_set_active_out(s->audio_be, s->voice, 0);
     }
 }
 
@@ -465,7 +465,7 @@ static void cs_write (void *opaque, hwaddr addr,
                 if (s->dma_running) {
                     IsaDmaClass *k = ISADMA_GET_CLASS(s->isa_dma);
                     k->release_DREQ(s->isa_dma, s->dma);
-                    AUD_set_active_out (s->voice, 0);
+                    AUD_set_active_out(s->audio_be, s->voice, 0);
                     s->dma_running = 0;
                 }
             }
@@ -551,11 +551,11 @@ static int cs_write_audio (CSState *s, int nchan, int dma_pos,
 
             for (i = 0; i < copied; ++i)
                 linbuf[i] = s->tab[tmpbuf[i]];
-            copied = AUD_write (s->voice, linbuf, copied << 1);
+            copied = AUD_write(s->audio_be, s->voice, linbuf, copied << 1);
             copied >>= 1;
         }
         else {
-            copied = AUD_write (s->voice, tmpbuf, copied);
+            copied = AUD_write(s->audio_be, s->voice, tmpbuf, copied);
         }
 
         temp -= copied;
@@ -614,7 +614,7 @@ static int cs4231a_pre_load (void *opaque)
     if (s->dma_running) {
         IsaDmaClass *k = ISADMA_GET_CLASS(s->isa_dma);
         k->release_DREQ(s->isa_dma, s->dma);
-        AUD_set_active_out (s->voice, 0);
+        AUD_set_active_out(s->audio_be, s->voice, 0);
     }
     s->dma_running = 0;
     return 0;
diff --git a/hw/audio/es1370.c b/hw/audio/es1370.c
index 9873ffadab..0422cf9836 100644
--- a/hw/audio/es1370.c
+++ b/hw/audio/es1370.c
@@ -411,7 +411,7 @@ static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl)
 
                 if (i == ADC_CHANNEL) {
                     s->adc_voice =
-                        AUD_open_in (
+                        AUD_open_in(
                             s->audio_be,
                             s->adc_voice,
                             "es1370.adc",
@@ -421,7 +421,7 @@ static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl)
                             );
                 } else {
                     s->dac_voice[i] =
-                        AUD_open_out (
+                        AUD_open_out(
                             s->audio_be,
                             s->dac_voice[i],
                             i ? "es1370.dac2" : "es1370.dac1",
@@ -438,9 +438,9 @@ static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl)
             int on = (ctl & b->ctl_en) && !(sctl & b->sctl_pause);
 
             if (i == ADC_CHANNEL) {
-                AUD_set_active_in (s->adc_voice, on);
+                AUD_set_active_in(s->audio_be, s->adc_voice, on);
             } else {
-                AUD_set_active_out (s->dac_voice[i], on);
+                AUD_set_active_out(s->audio_be, s->dac_voice[i], on);
             }
         }
     }
@@ -627,7 +627,7 @@ static void es1370_transfer_audio (ES1370State *s, struct chan *d, int loop_sel,
             int acquired, to_copy;
 
             to_copy = MIN(to_transfer, sizeof(tmpbuf));
-            acquired = AUD_read (s->adc_voice, tmpbuf, to_copy);
+            acquired = AUD_read(s->audio_be, s->adc_voice, tmpbuf, to_copy);
             if (!acquired) {
                 break;
             }
@@ -646,7 +646,7 @@ static void es1370_transfer_audio (ES1370State *s, struct chan *d, int loop_sel,
 
             to_copy = MIN(to_transfer, sizeof(tmpbuf));
             pci_dma_read (&s->dev, addr, tmpbuf, to_copy);
-            copied = AUD_write (voice, tmpbuf, to_copy);
+            copied = AUD_write(s->audio_be, voice, tmpbuf, to_copy);
             if (!copied) {
                 break;
             }
diff --git a/hw/audio/gus.c b/hw/audio/gus.c
index 68f89e994c..df069a19fc 100644
--- a/hw/audio/gus.c
+++ b/hw/audio/gus.c
@@ -87,7 +87,8 @@ static int write_audio (GUSState *s, int samples)
         int nbytes, wbytes, wsampl;
 
         nbytes = samples << s->shift;
-        wbytes = AUD_write (
+        wbytes = AUD_write(
+            s->audio_be,
             s->voice,
             s->mixbuf + (pos << (s->shift - 1)),
             nbytes
@@ -257,7 +258,7 @@ static void gus_realizefn (DeviceState *dev, Error **errp)
     as.fmt = AUDIO_FORMAT_S16;
     as.endianness = HOST_BIG_ENDIAN;
 
-    s->voice = AUD_open_out (
+    s->voice = AUD_open_out(
         s->audio_be,
         NULL,
         "gus",
@@ -272,7 +273,7 @@ static void gus_realizefn (DeviceState *dev, Error **errp)
     }
 
     s->shift = 2;
-    s->samples = AUD_get_buffer_size_out (s->voice) >> s->shift;
+    s->samples = AUD_get_buffer_size_out(s->audio_be, s->voice) >> s->shift;
     s->mixbuf = g_malloc0 (s->samples << s->shift);
 
     isa_register_portio_list(d, &s->portio_list1, s->port,
@@ -287,7 +288,7 @@ static void gus_realizefn (DeviceState *dev, Error **errp)
     s->emu.opaque = s;
     s->pic = isa_bus_get_irq(bus, s->emu.gusirq);
 
-    AUD_set_active_out (s->voice, 1);
+    AUD_set_active_out(s->audio_be, s->voice, 1);
 }
 
 static const Property gus_properties[] = {
diff --git a/hw/audio/hda-codec.c b/hw/audio/hda-codec.c
index e90c9de046..1daa2c25c5 100644
--- a/hw/audio/hda-codec.c
+++ b/hw/audio/hda-codec.c
@@ -275,7 +275,7 @@ static void hda_audio_input_cb(void *opaque, int avail)
     while (to_transfer) {
         uint32_t start = (uint32_t) (wpos & B_MASK);
         uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
-        uint32_t read = AUD_read(st->voice.in, st->buf + start, chunk);
+        uint32_t read = AUD_read(st->state->audio_be, st->voice.in, st->buf + start, chunk);
         wpos += read;
         to_transfer -= read;
         st->wpos += read;
@@ -354,7 +354,7 @@ static void hda_audio_output_cb(void *opaque, int avail)
     while (to_transfer) {
         uint32_t start = (uint32_t) (rpos & B_MASK);
         uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
-        uint32_t written = AUD_write(st->voice.out, st->buf + start, chunk);
+        uint32_t written = AUD_write(st->state->audio_be, st->voice.out, st->buf + start, chunk);
         rpos += written;
         to_transfer -= written;
         st->rpos += written;
@@ -375,7 +375,7 @@ static void hda_audio_compat_input_cb(void *opaque, int avail)
 
     while (avail - recv >= sizeof(st->compat_buf)) {
         if (st->compat_bpos != sizeof(st->compat_buf)) {
-            len = AUD_read(st->voice.in, st->compat_buf + st->compat_bpos,
+            len = AUD_read(st->state->audio_be, st->voice.in, st->compat_buf + st->compat_bpos,
                            sizeof(st->compat_buf) - st->compat_bpos);
             st->compat_bpos += len;
             recv += len;
@@ -408,7 +408,7 @@ static void hda_audio_compat_output_cb(void *opaque, int avail)
             }
             st->compat_bpos = 0;
         }
-        len = AUD_write(st->voice.out, st->compat_buf + st->compat_bpos,
+        len = AUD_write(st->state->audio_be, st->voice.out, st->compat_buf + st->compat_bpos,
                         sizeof(st->compat_buf) - st->compat_bpos);
         st->compat_bpos += len;
         sent += len;
@@ -440,9 +440,9 @@ static void hda_audio_set_running(HDAAudioStream *st, bool running)
         }
     }
     if (st->output) {
-        AUD_set_active_out(st->voice.out, st->running);
+        AUD_set_active_out(st->state->audio_be, st->voice.out, st->running);
     } else {
-        AUD_set_active_in(st->voice.in, st->running);
+        AUD_set_active_in(st->state->audio_be, st->voice.in, st->running);
     }
 }
 
@@ -466,9 +466,9 @@ static void hda_audio_set_amp(HDAAudioStream *st)
         return;
     }
     if (st->output) {
-        AUD_set_volume_out_lr(st->voice.out, muted, left, right);
+        AUD_set_volume_out_lr(st->state->audio_be, st->voice.out, muted, left, right);
     } else {
-        AUD_set_volume_in_lr(st->voice.in, muted, left, right);
+        AUD_set_volume_in_lr(st->state->audio_be, st->voice.in, muted, left, right);
     }
 }
 
diff --git a/hw/audio/lm4549.c b/hw/audio/lm4549.c
index bf711c49c0..4adf67f967 100644
--- a/hw/audio/lm4549.c
+++ b/hw/audio/lm4549.c
@@ -101,11 +101,11 @@ static void lm4549_audio_transfer(lm4549_state *s)
     uint32_t i;
 
     /* Activate the voice */
-    AUD_set_active_out(s->voice, 1);
+    AUD_set_active_out(s->audio_be, s->voice, 1);
     s->voice_is_active = 1;
 
     /* Try to write the buffer content */
-    written_bytes = AUD_write(s->voice, s->buffer,
+    written_bytes = AUD_write(s->audio_be, s->voice, s->buffer,
                               s->buffer_level * sizeof(uint16_t));
     written_samples = written_bytes >> 1;
 
@@ -129,14 +129,14 @@ static void lm4549_audio_out_callback(void *opaque, int free)
     static uint32_t prev_buffer_level;
 
 #ifdef LM4549_DEBUG
-    int size = AUD_get_buffer_size_out(s->voice);
+    int size = AUD_get_buffer_size_out(s->audio_be, s->voice);
     DPRINTF("audio_out_callback size = %i free = %i\n", size, free);
 #endif
 
     /* Detect that no data are consumed
        => disable the voice */
     if (s->buffer_level == prev_buffer_level) {
-        AUD_set_active_out(s->voice, 0);
+        AUD_set_active_out(s->audio_be, s->voice, 0);
         s->voice_is_active = 0;
     }
     prev_buffer_level = s->buffer_level;
@@ -285,7 +285,7 @@ static int lm4549_post_load(void *opaque, int version_id)
 
     /* Request data */
     if (s->voice_is_active == 1) {
-        lm4549_audio_out_callback(s, AUD_get_buffer_size_out(s->voice));
+        lm4549_audio_out_callback(s, AUD_get_buffer_size_out(s->audio_be, s->voice));
     }
 
     return 0;
@@ -323,7 +323,7 @@ void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque,
         &as
     );
 
-    AUD_set_volume_out_lr(s->voice, 0, 255, 255);
+    AUD_set_volume_out_lr(s->audio_be, s->voice, 0, 255, 255);
 
     s->voice_is_active = 0;
 
diff --git a/hw/audio/pcspk.c b/hw/audio/pcspk.c
index 916c56fa4c..a74a263b37 100644
--- a/hw/audio/pcspk.c
+++ b/hw/audio/pcspk.c
@@ -106,7 +106,7 @@ static void pcspk_callback(void *opaque, int free)
 
     while (free > 0) {
         n = MIN(s->samples - s->play_pos, (unsigned int)free);
-        n = AUD_write(s->voice, &s->sample_buf[s->play_pos], n);
+        n = AUD_write(s->audio_be, s->voice, &s->sample_buf[s->play_pos], n);
         if (!n)
             break;
         s->play_pos = (s->play_pos + n) % s->samples;
@@ -164,7 +164,7 @@ static void pcspk_io_write(void *opaque, hwaddr addr, uint64_t val,
     if (s->voice) {
         if (gate) /* restart */
             s->play_pos = 0;
-        AUD_set_active_out(s->voice, gate & s->data_on);
+        AUD_set_active_out(s->audio_be, s->voice, gate & s->data_on);
     }
 }
 
diff --git a/hw/audio/sb16.c b/hw/audio/sb16.c
index 1e3c4caf5e..47b36dfc69 100644
--- a/hw/audio/sb16.c
+++ b/hw/audio/sb16.c
@@ -185,11 +185,11 @@ static void control (SB16State *s, int hold)
 
     if (hold) {
         k->hold_DREQ(isa_dma, dma);
-        AUD_set_active_out (s->voice, 1);
+        AUD_set_active_out(s->audio_be, s->voice, 1);
     }
     else {
         k->release_DREQ(isa_dma, dma);
-        AUD_set_active_out (s->voice, 0);
+        AUD_set_active_out(s->audio_be, s->voice, 0);
     }
 }
 
@@ -215,7 +215,7 @@ static void continue_dma8 (SB16State *s)
         as.fmt = s->fmt;
         as.endianness = 0;
 
-        s->voice = AUD_open_out (
+        s->voice = AUD_open_out(
             s->audio_be,
             s->voice,
             "sb16",
@@ -378,7 +378,7 @@ static void dma_cmd (SB16State *s, uint8_t cmd, uint8_t d0, int dma_len)
         as.fmt = s->fmt;
         as.endianness = 0;
 
-        s->voice = AUD_open_out (
+        s->voice = AUD_open_out(
             s->audio_be,
             s->voice,
             "sb16",
@@ -879,7 +879,7 @@ static void legacy_reset (SB16State *s)
     as.fmt = AUDIO_FORMAT_U8;
     as.endianness = 0;
 
-    s->voice = AUD_open_out (
+    s->voice = AUD_open_out(
         s->audio_be,
         s->voice,
         "sb16",
@@ -1196,7 +1196,7 @@ static int write_audio (SB16State *s, int nchan, int dma_pos,
         }
 
         copied = k->read_memory(isa_dma, nchan, tmpbuf, dma_pos, to_copy);
-        copied = AUD_write (s->voice, tmpbuf, copied);
+        copied = AUD_write(s->audio_be, s->voice, tmpbuf, copied);
 
         temp -= copied;
         dma_pos = (dma_pos + copied) % dma_len;
@@ -1302,7 +1302,7 @@ static int sb16_post_load (void *opaque, int version_id)
             as.fmt = s->fmt;
             as.endianness = 0;
 
-            s->voice = AUD_open_out (
+            s->voice = AUD_open_out(
                 s->audio_be,
                 s->voice,
                 "sb16",
diff --git a/hw/audio/via-ac97.c b/hw/audio/via-ac97.c
index 019d296853..5b344d7a0f 100644
--- a/hw/audio/via-ac97.c
+++ b/hw/audio/via-ac97.c
@@ -53,7 +53,7 @@ static void codec_volume_set_out(ViaAC97State *s)
     rvol /= 255;
     mute = CODEC_REG(s, AC97_Master_Volume_Mute) >> MUTE_SHIFT;
     mute |= CODEC_REG(s, AC97_PCM_Out_Volume_Mute) >> MUTE_SHIFT;
-    AUD_set_volume_out_lr(s->vo, mute, lvol, rvol);
+    AUD_set_volume_out_lr(s->audio_be, s->vo, mute, lvol, rvol);
 }
 
 static void codec_reset(ViaAC97State *s)
@@ -189,7 +189,7 @@ static void out_cb(void *opaque, int avail)
         while (temp) {
             to_copy = MIN(temp, sizeof(tmpbuf));
             pci_dma_read(&s->dev, c->addr, tmpbuf, to_copy);
-            copied = AUD_write(s->vo, tmpbuf, to_copy);
+            copied = AUD_write(s->audio_be, s->vo, tmpbuf, to_copy);
             if (!copied) {
                 stop = true;
                 break;
@@ -208,7 +208,7 @@ static void out_cb(void *opaque, int avail)
                     c->stat |= STAT_PAUSED;
                 } else {
                     c->stat &= ~STAT_ACTIVE;
-                    AUD_set_active_out(s->vo, 0);
+                    AUD_set_active_out(s->audio_be, s->vo, 0);
                 }
                 if (c->type & STAT_EOL) {
                     via_isa_set_irq(&s->dev, 0, 1);
@@ -317,20 +317,20 @@ static void sgd_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
         break;
     case 1:
         if (val & CNTL_START) {
-            AUD_set_active_out(s->vo, 1);
+            AUD_set_active_out(s->audio_be, s->vo, 1);
             s->aur.stat = STAT_ACTIVE;
         }
         if (val & CNTL_TERM) {
-            AUD_set_active_out(s->vo, 0);
+            AUD_set_active_out(s->audio_be, s->vo, 0);
             s->aur.stat &= ~(STAT_ACTIVE | STAT_PAUSED);
             s->aur.clen = 0;
         }
         if (val & CNTL_PAUSE) {
-            AUD_set_active_out(s->vo, 0);
+            AUD_set_active_out(s->audio_be, s->vo, 0);
             s->aur.stat &= ~STAT_ACTIVE;
             s->aur.stat |= STAT_PAUSED;
         } else if (!(val & CNTL_PAUSE) && (s->aur.stat & STAT_PAUSED)) {
-            AUD_set_active_out(s->vo, 1);
+            AUD_set_active_out(s->audio_be, s->vo, 1);
             s->aur.stat |= STAT_ACTIVE;
             s->aur.stat &= ~STAT_PAUSED;
         }
diff --git a/hw/audio/virtio-snd.c b/hw/audio/virtio-snd.c
index 9101560f38..47fef61af8 100644
--- a/hw/audio/virtio-snd.c
+++ b/hw/audio/virtio-snd.c
@@ -463,7 +463,7 @@ static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id)
                                          stream,
                                          virtio_snd_pcm_out_cb,
                                          &as);
-        AUD_set_volume_out_lr(stream->voice.out, 0, 255, 255);
+        AUD_set_volume_out_lr(s->audio_be, stream->voice.out, 0, 255, 255);
     } else {
         stream->voice.in = AUD_open_in(s->audio_be,
                                         stream->voice.in,
@@ -471,7 +471,7 @@ static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id)
                                         stream,
                                         virtio_snd_pcm_in_cb,
                                         &as);
-        AUD_set_volume_in_lr(stream->voice.in, 0, 255, 255);
+        AUD_set_volume_in_lr(s->audio_be, stream->voice.in, 0, 255, 255);
     }
 
     return cpu_to_le32(VIRTIO_SND_S_OK);
@@ -561,9 +561,9 @@ static void virtio_snd_handle_pcm_start_stop(VirtIOSound *s,
             stream->active = start;
         }
         if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
-            AUD_set_active_out(stream->voice.out, start);
+            AUD_set_active_out(s->audio_be, stream->voice.out, start);
         } else {
-            AUD_set_active_in(stream->voice.in, start);
+            AUD_set_active_in(s->audio_be, stream->voice.in, start);
         }
     } else {
         error_report("Invalid stream id: %"PRIu32, stream_id);
@@ -1166,7 +1166,8 @@ static void virtio_snd_pcm_out_cb(void *data, int available)
                 buffer->populated = true;
             }
             for (;;) {
-                size = AUD_write(stream->voice.out,
+                size = AUD_write(stream->s->audio_be,
+                                 stream->voice.out,
                                  buffer->data + buffer->offset,
                                  MIN(buffer->size, available));
                 assert(size <= MIN(buffer->size, available));
@@ -1258,7 +1259,8 @@ static void virtio_snd_pcm_in_cb(void *data, int available)
                     return_rx_buffer(stream, buffer);
                     break;
                 }
-                size = AUD_read(stream->voice.in,
+                size = AUD_read(stream->s->audio_be,
+                        stream->voice.in,
                         buffer->data + buffer->size,
                         MIN(available, (stream->params.period_bytes -
                                         buffer->size)));
diff --git a/hw/audio/wm8750.c b/hw/audio/wm8750.c
index 336fb6d20b..bc347fe537 100644
--- a/hw/audio/wm8750.c
+++ b/hw/audio/wm8750.c
@@ -72,7 +72,7 @@ static inline void wm8750_in_load(WM8750State *s)
     if (s->idx_in + s->req_in <= sizeof(s->data_in))
         return;
     s->idx_in = MAX(0, (int) sizeof(s->data_in) - s->req_in);
-    AUD_read(*s->in[0], s->data_in + s->idx_in,
+    AUD_read(s->audio_be, *s->in[0], s->data_in + s->idx_in,
              sizeof(s->data_in) - s->idx_in);
 }
 
@@ -80,7 +80,7 @@ static inline void wm8750_out_flush(WM8750State *s)
 {
     int sent = 0;
     while (sent < s->idx_out)
-        sent += AUD_write(*s->out[0], s->data_out + sent, s->idx_out - sent)
+        sent += AUD_write(s->audio_be, *s->out[0], s->data_out + sent, s->idx_out - sent)
                 ?: s->idx_out;
     s->idx_out = 0;
 }
@@ -145,30 +145,30 @@ static void wm8750_vol_update(WM8750State *s)
 {
     /* FIXME: multiply all volumes by s->invol[2], s->invol[3] */
 
-    AUD_set_volume_in_lr(s->adc_voice[0], s->mute,
+    AUD_set_volume_in_lr(s->audio_be, s->adc_voice[0], s->mute,
                     s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]),
                     s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1]));
-    AUD_set_volume_in_lr(s->adc_voice[1], s->mute,
+    AUD_set_volume_in_lr(s->audio_be, s->adc_voice[1], s->mute,
                     s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]),
                     s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1]));
-    AUD_set_volume_in_lr(s->adc_voice[2], s->mute,
+    AUD_set_volume_in_lr(s->audio_be, s->adc_voice[2], s->mute,
                     s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]),
                     s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1]));
 
     /* FIXME: multiply all volumes by s->outvol[0], s->outvol[1] */
 
     /* Speaker: LOUT2VOL ROUT2VOL */
-    AUD_set_volume_out_lr(s->dac_voice[0], s->mute,
+    AUD_set_volume_out_lr(s->audio_be, s->dac_voice[0], s->mute,
                     s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[4]),
                     s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[5]));
 
     /* Headphone: LOUT1VOL ROUT1VOL */
-    AUD_set_volume_out_lr(s->dac_voice[1], s->mute,
+    AUD_set_volume_out_lr(s->audio_be, s->dac_voice[1], s->mute,
                     s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[2]),
                     s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[3]));
 
     /* MONOOUT: MONOVOL MONOVOL */
-    AUD_set_volume_out_lr(s->dac_voice[2], s->mute,
+    AUD_set_volume_out_lr(s->audio_be, s->dac_voice[2], s->mute,
                     s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[6]),
                     s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[6]));
 }
@@ -182,9 +182,9 @@ static void wm8750_set_format(WM8750State *s)
     wm8750_out_flush(s);
 
     if (s->in[0] && *s->in[0])
-        AUD_set_active_in(*s->in[0], 0);
+        AUD_set_active_in(s->audio_be, *s->in[0], 0);
     if (s->out[0] && *s->out[0])
-        AUD_set_active_out(*s->out[0], 0);
+        AUD_set_active_out(s->audio_be, *s->out[0], 0);
 
     for (i = 0; i < IN_PORT_N; i ++)
         if (s->adc_voice[i]) {
@@ -235,9 +235,9 @@ static void wm8750_set_format(WM8750State *s)
      * for mixing or combining paths to different ports, so we
      * connect both channels to where the left channel is routed.  */
     if (s->in[0] && *s->in[0])
-        AUD_set_active_in(*s->in[0], 1);
+        AUD_set_active_in(s->audio_be, *s->in[0], 1);
     if (s->out[0] && *s->out[0])
-        AUD_set_active_out(*s->out[0], 1);
+        AUD_set_active_out(s->audio_be, *s->out[0], 1);
 }
 
 static void wm8750_clk_update(WM8750State *s, int ext)
diff --git a/hw/display/xlnx_dp.c b/hw/display/xlnx_dp.c
index a248b943a5..e89f1ffaf6 100644
--- a/hw/display/xlnx_dp.c
+++ b/hw/display/xlnx_dp.c
@@ -331,7 +331,7 @@ static inline void xlnx_dp_audio_activate(XlnxDPState *s)
 {
     bool activated = ((s->core_registers[DP_TX_AUDIO_CONTROL]
                    & DP_TX_AUD_CTRL) != 0);
-    AUD_set_active_out(s->amixer_output_stream, activated);
+    AUD_set_active_out(s->audio_be, s->amixer_output_stream, activated);
     xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(0),
                                       &s->audio_buffer_0);
     xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(1),
@@ -401,7 +401,7 @@ static void xlnx_dp_audio_callback(void *opaque, int avail)
     /* Send the buffer through the audio. */
     if (s->byte_left <= MAX_QEMU_BUFFER_SIZE) {
         if (s->byte_left != 0) {
-            written = AUD_write(s->amixer_output_stream,
+            written = AUD_write(s->audio_be, s->amixer_output_stream,
                                 &s->out_buffer[s->data_ptr], s->byte_left);
         } else {
              int len_to_copy;
@@ -413,12 +413,12 @@ static void xlnx_dp_audio_callback(void *opaque, int avail)
             while (avail) {
                 len_to_copy = MIN(AUD_CHBUF_MAX_DEPTH, avail);
                 memset(s->out_buffer, 0, len_to_copy);
-                avail -= AUD_write(s->amixer_output_stream, s->out_buffer,
+                avail -= AUD_write(s->audio_be, s->amixer_output_stream, s->out_buffer,
                                    len_to_copy);
             }
         }
     } else {
-        written = AUD_write(s->amixer_output_stream,
+        written = AUD_write(s->audio_be, s->amixer_output_stream,
                             &s->out_buffer[s->data_ptr], MAX_QEMU_BUFFER_SIZE);
     }
     s->byte_left -= written;
@@ -1401,7 +1401,7 @@ static void xlnx_dp_realize(DeviceState *dev, Error **errp)
                                            s,
                                            xlnx_dp_audio_callback,
                                            &as);
-    AUD_set_volume_out_lr(s->amixer_output_stream, 0, 255, 255);
+    AUD_set_volume_out_lr(s->audio_be, s->amixer_output_stream, 0, 255, 255);
     xlnx_dp_audio_activate(s);
     s->vblank = ptimer_init(vblank_hit, s, DP_VBLANK_PTIMER_POLICY);
     ptimer_transaction_begin(s->vblank);
diff --git a/hw/usb/dev-audio.c b/hw/usb/dev-audio.c
index 8dd9d26599..189fd1df26 100644
--- a/hw/usb/dev-audio.c
+++ b/hw/usb/dev-audio.c
@@ -669,7 +669,7 @@ static void output_callback(void *opaque, int avail)
             return;
         }
 
-        written = AUD_write(s->out.voice, data, len);
+        written = AUD_write(s->audio_be, s->out.voice, data, len);
         avail -= written;
         s->out.buf.cons += written;
 
@@ -683,7 +683,7 @@ static int usb_audio_set_output_altset(USBAudioState *s, int altset)
 {
     switch (altset) {
     case ALTSET_OFF:
-        AUD_set_active_out(s->out.voice, false);
+        AUD_set_active_out(s->audio_be, s->out.voice, false);
         break;
     case ALTSET_STEREO:
     case ALTSET_51:
@@ -692,7 +692,7 @@ static int usb_audio_set_output_altset(USBAudioState *s, int altset)
             usb_audio_reinit(USB_DEVICE(s), altset_channels[altset]);
         }
         streambuf_init(&s->out.buf, s->buffer, s->out.channels);
-        AUD_set_active_out(s->out.voice, true);
+        AUD_set_active_out(s->audio_be, s->out.voice, true);
         break;
     default:
         return -1;
@@ -805,7 +805,7 @@ static int usb_audio_set_control(USBAudioState *s, uint8_t attrib,
             }
             fprintf(stderr, "\n");
         }
-        AUD_set_volume_out(s->out.voice, &s->out.vol);
+        AUD_set_volume_out(s->audio_be, s->out.voice, &s->out.vol);
     }
 
     return ret;
@@ -980,8 +980,8 @@ static void usb_audio_reinit(USBDevice *dev, unsigned channels)
 
     s->out.voice = AUD_open_out(s->audio_be, s->out.voice, TYPE_USB_AUDIO,
                                 s, output_callback, &s->out.as);
-    AUD_set_volume_out(s->out.voice, &s->out.vol);
-    AUD_set_active_out(s->out.voice, 0);
+    AUD_set_volume_out(s->audio_be, s->out.voice, &s->out.vol);
+    AUD_set_active_out(s->audio_be, s->out.voice, 0);
 }
 
 static const VMStateDescription vmstate_usb_audio = {
diff --git a/ui/vnc.c b/ui/vnc.c
index 0d499b208b..5c8af65202 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -1286,7 +1286,7 @@ static void audio_add(VncState *vs)
 static void audio_del(VncState *vs)
 {
     if (vs->audio_cap) {
-        AUD_del_capture(vs->audio_cap, vs);
+        AUD_del_capture(vs->vd->audio_be, vs->audio_cap, vs);
         vs->audio_cap = NULL;
     }
 }
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 18/24] audio: make AudioBackend truely abstract
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (16 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 17/24] audio: make all the backend-specific APIs take the be marcandre.lureau
@ 2025-12-01 11:22 ` marcandre.lureau
  2025-12-01 11:23 ` [RFC 19/24] audio: split AudioBackend marcandre.lureau
                   ` (6 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:22 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Add virtual methods to be implemented by concrete classes, like
AudioDriverClass.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_template.h       |   9 +-
 include/qemu/audio-capture.h |  13 ---
 include/qemu/audio.h         |  61 ++++++++--
 audio/audio.c                | 208 ++++++++++++++++++++++++++++++-----
 4 files changed, 237 insertions(+), 54 deletions(-)

diff --git a/audio/audio_template.h b/audio/audio_template.h
index 8d66f9cd40..9dee8575f3 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -478,7 +478,7 @@ static void glue (audio_close_, TYPE) (SW *sw)
     g_free (sw);
 }
 
-void glue(AUD_close_, TYPE)(AudioBackend *be, SW *sw)
+static void glue(audio_driver_close_, TYPE)(AudioBackend *be, SW *sw)
 {
     if (sw) {
         if (audio_bug(__func__, !be)) {
@@ -490,14 +490,13 @@ void glue(AUD_close_, TYPE)(AudioBackend *be, SW *sw)
     }
 }
 
-SW *glue (AUD_open_, TYPE) (
+static SW *glue(audio_driver_open_, TYPE) (
     AudioBackend *be,
     SW *sw,
     const char *name,
     void *callback_opaque ,
     audio_callback_fn callback_fn,
-    struct audsettings *as
-    )
+    struct audsettings *as)
 {
     AudioDriver *s = AUDIO_DRIVER(be);
     AudiodevPerDirectionOptions *pdo;
@@ -569,7 +568,7 @@ SW *glue (AUD_open_, TYPE) (
     return NULL;
 }
 
-bool glue(AUD_is_active_, TYPE)(AudioBackend *be, SW *sw)
+static bool glue(audio_driver_is_active_, TYPE)(AudioBackend *be, SW *sw)
 {
     return sw ? sw->active : 0;
 }
diff --git a/include/qemu/audio-capture.h b/include/qemu/audio-capture.h
index f500b0a7f8..5bfbdd0298 100644
--- a/include/qemu/audio-capture.h
+++ b/include/qemu/audio-capture.h
@@ -8,19 +8,6 @@
 
 #include "audio.h"
 
-typedef struct CaptureVoiceOut CaptureVoiceOut;
-
-typedef enum {
-    AUD_CNOTIFY_ENABLE,
-    AUD_CNOTIFY_DISABLE
-} audcnotification_e;
-
-struct audio_capture_ops {
-    void (*notify) (void *opaque, audcnotification_e cmd);
-    void (*capture) (void *opaque, const void *buf, int size);
-    void (*destroy) (void *opaque);
-};
-
 struct capture_ops {
     void (*info) (void *opaque);
     void (*destroy) (void *opaque);
diff --git a/include/qemu/audio.h b/include/qemu/audio.h
index 745ee881b8..672f9befe5 100644
--- a/include/qemu/audio.h
+++ b/include/qemu/audio.h
@@ -43,6 +43,25 @@ typedef struct audsettings {
 
 typedef struct SWVoiceOut SWVoiceOut;
 typedef struct SWVoiceIn SWVoiceIn;
+typedef struct CaptureVoiceOut CaptureVoiceOut;
+
+typedef enum {
+    AUD_CNOTIFY_ENABLE,
+    AUD_CNOTIFY_DISABLE
+} audcnotification_e;
+
+struct audio_capture_ops {
+    void (*notify) (void *opaque, audcnotification_e cmd);
+    void (*capture) (void *opaque, const void *buf, int size);
+    void (*destroy) (void *opaque);
+};
+
+#define AUDIO_MAX_CHANNELS 16
+typedef struct Volume {
+    bool mute;
+    int channels;
+    uint8_t vol[AUDIO_MAX_CHANNELS];
+} Volume;
 
 typedef struct AudioBackend {
     Object parent_obj;
@@ -53,6 +72,38 @@ typedef struct AudioBackendClass {
 
     bool (*realize)(AudioBackend *be, Audiodev *dev, Error **errp);
     const char *(*get_id)(AudioBackend *be);
+    SWVoiceOut *(*open_out)(
+        AudioBackend *be,
+        SWVoiceOut *sw,
+        const char *name,
+        void *callback_opaque ,
+        audio_callback_fn callback_fn,
+        struct audsettings *as);
+    SWVoiceIn *(*open_in)(
+        AudioBackend *be,
+        SWVoiceIn *sw,
+        const char *name,
+        void *callback_opaque ,
+        audio_callback_fn callback_fn,
+        struct audsettings *as);
+    void (*close_out)(AudioBackend *be, SWVoiceOut *sw);
+    void (*close_in)(AudioBackend *be, SWVoiceIn *sw);
+    bool (*is_active_out)(AudioBackend *be, SWVoiceOut *sw);
+    bool (*is_active_in)(AudioBackend *be, SWVoiceIn *sw);
+    void (*set_active_out)(AudioBackend *be, SWVoiceOut *sw, bool on);
+    void (*set_active_in)(AudioBackend *be, SWVoiceIn *sw, bool on);
+    void (*set_volume_out)(AudioBackend *be, SWVoiceOut *sw, Volume *vol);
+    void (*set_volume_in)(AudioBackend *be, SWVoiceIn *sw, Volume *vol);
+    size_t (*write)(AudioBackend *be, SWVoiceOut *sw, void *buf, size_t size);
+    size_t (*read)(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size);
+    int (*get_buffer_size_out)(AudioBackend *be, SWVoiceOut *sw);
+    CaptureVoiceOut *(*add_capture)(
+        AudioBackend *be,
+        struct audsettings *as,
+        struct audio_capture_ops *ops,
+        void *cb_opaque);
+    void (*del_capture)(AudioBackend *be, CaptureVoiceOut *cap, void *cb_opaque);
+
 #ifdef CONFIG_GIO
     bool (*set_dbus_server)(AudioBackend *be,
                             GDBusObjectManagerServer *manager,
@@ -73,8 +124,7 @@ SWVoiceOut *AUD_open_out(
     const char *name,
     void *callback_opaque,
     audio_callback_fn callback_fn,
-    struct audsettings *settings
-    );
+    struct audsettings *settings);
 
 void AUD_close_out(AudioBackend *be, SWVoiceOut *sw);
 size_t AUD_write(AudioBackend *be, SWVoiceOut *sw, void *pcm_buf, size_t size);
@@ -82,13 +132,6 @@ int  AUD_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw);
 void AUD_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on);
 bool AUD_is_active_out(AudioBackend *be, SWVoiceOut *sw);
 
-#define AUDIO_MAX_CHANNELS 16
-typedef struct Volume {
-    bool mute;
-    int channels;
-    uint8_t vol[AUDIO_MAX_CHANNELS];
-} Volume;
-
 void AUD_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol);
 void AUD_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol);
 
diff --git a/audio/audio.c b/audio/audio.c
index cc65153dfa..d9c338ea91 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -846,29 +846,36 @@ static void audio_timer (void *opaque)
 /*
  * Public API
  */
+ static size_t audio_driver_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);
+     }
+ }
+
 size_t AUD_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;
-    }
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
-    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);
-    }
+    return klass->write(be, sw, buf, size);
 }
 
-size_t AUD_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size)
+static size_t audio_driver_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size)
 {
     HWVoiceIn *hw;
 
@@ -888,9 +895,17 @@ size_t AUD_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size)
     } else {
         return hw->pcm_ops->read(hw, buf, size);
     }
+
 }
 
-int AUD_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
+size_t AUD_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->read(be, sw, buf, size);
+}
+
+static int audio_driver_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
 {
     if (!sw) {
         return 0;
@@ -903,7 +918,14 @@ int AUD_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
     return sw->hw->samples * sw->hw->info.bytes_per_frame;
 }
 
-void AUD_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
+int AUD_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->get_buffer_size_out(be, sw);
+}
+
+static void audio_driver_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
 {
     HWVoiceOut *hw;
 
@@ -949,9 +971,17 @@ void AUD_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
         }
         sw->active = on;
     }
+
 }
 
-void AUD_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
+void AUD_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->set_active_out(be, sw, on);
+}
+
+static void audio_driver_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
 {
     HWVoiceIn *hw;
 
@@ -996,6 +1026,13 @@ void AUD_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
     }
 }
 
+void AUD_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->set_active_in(be, sw, on);
+}
+
 static size_t audio_get_avail(SWVoiceIn *sw)
 {
     size_t live;
@@ -1659,12 +1696,95 @@ static const char *audio_driver_get_id(AudioBackend *be)
     return AUDIO_DRIVER(be)->dev->id;
 }
 
+static CaptureVoiceOut *audio_driver_add_capture(
+    AudioBackend *be,
+    struct audsettings *as,
+    struct audio_capture_ops *ops,
+    void *cb_opaque);
+
+static void audio_driver_del_capture(
+    AudioBackend *be,
+    CaptureVoiceOut *cap,
+    void *cb_opaque);
+
+static void audio_driver_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol);
+static void audio_driver_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol);
+
+SWVoiceOut *AUD_open_out(
+    AudioBackend *be,
+    SWVoiceOut *sw,
+    const char *name,
+    void *callback_opaque ,
+    audio_callback_fn callback_fn,
+    struct audsettings *as)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->open_out(be, sw, name, callback_opaque, callback_fn, as);
+}
+
+SWVoiceIn *AUD_open_in(
+    AudioBackend *be,
+    SWVoiceIn *sw,
+    const char *name,
+    void *callback_opaque ,
+    audio_callback_fn callback_fn,
+    struct audsettings *as)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->open_in(be, sw, name, callback_opaque, callback_fn, as);
+}
+
+void AUD_close_out(AudioBackend *be, SWVoiceOut *sw)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->close_out(be, sw);
+}
+
+void AUD_close_in(AudioBackend *be, SWVoiceIn *sw)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->close_in(be, sw);
+}
+
+bool AUD_is_active_out(AudioBackend *be, SWVoiceOut *sw)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->is_active_out(be, sw);
+}
+
+bool AUD_is_active_in(AudioBackend *be, SWVoiceIn *sw)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->is_active_in(be, sw);
+}
+
 static void audio_driver_class_init(ObjectClass *klass, const void *data)
 {
     AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
 
     be->realize = audio_be_driver_realize;
     be->get_id = audio_driver_get_id;
+    be->open_in = audio_driver_open_in;
+    be->open_out = audio_driver_open_out;
+    be->close_in = audio_driver_close_in;
+    be->close_out = audio_driver_close_out;
+    be->is_active_out = audio_driver_is_active_out;
+    be->is_active_in = audio_driver_is_active_in;
+    be->set_active_out = audio_driver_set_active_out;
+    be->set_active_in = audio_driver_set_active_in;
+    be->set_volume_out = audio_driver_set_volume_out;
+    be->set_volume_in = audio_driver_set_volume_in;
+    be->read = audio_driver_read;
+    be->write = audio_driver_write;
+    be->get_buffer_size_out = audio_driver_get_buffer_size_out;
+    be->add_capture = audio_driver_add_capture;
+    be->del_capture = audio_driver_del_capture;
 }
 
 static void audio_driver_init(Object *obj)
@@ -1862,12 +1982,11 @@ bool AUD_backend_check(AudioBackend **be, Error **errp)
 
 static struct audio_pcm_ops capture_pcm_ops;
 
-CaptureVoiceOut *AUD_add_capture(
+static CaptureVoiceOut *audio_driver_add_capture(
     AudioBackend *be,
     struct audsettings *as,
     struct audio_capture_ops *ops,
-    void *cb_opaque
-    )
+    void *cb_opaque)
 {
     AudioDriver *s = AUDIO_DRIVER(be);
     CaptureVoiceOut *cap;
@@ -1938,7 +2057,21 @@ CaptureVoiceOut *AUD_add_capture(
     return cap;
 }
 
-void AUD_del_capture(AudioBackend *be, CaptureVoiceOut *cap, void *cb_opaque)
+CaptureVoiceOut *AUD_add_capture(
+    AudioBackend *be,
+    struct audsettings *as,
+    struct audio_capture_ops *ops,
+    void *cb_opaque)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->add_capture(be, as, ops, cb_opaque);
+}
+
+static void audio_driver_del_capture(
+    AudioBackend *be,
+    CaptureVoiceOut *cap,
+    void *cb_opaque)
 {
     struct capture_callback *cb;
 
@@ -1977,7 +2110,14 @@ void AUD_del_capture(AudioBackend *be, CaptureVoiceOut *cap, void *cb_opaque)
     }
 }
 
-void AUD_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol)
+void AUD_del_capture(AudioBackend *be, CaptureVoiceOut *cap, void *cb_opaque)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    klass->del_capture(be, cap, cb_opaque);
+}
+
+static void audio_driver_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol)
 {
     if (sw) {
         HWVoiceOut *hw = sw->hw;
@@ -1993,7 +2133,14 @@ void AUD_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol)
     }
 }
 
-void AUD_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol)
+void AUD_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    klass->set_volume_out(be, sw, vol);
+}
+
+static void audio_driver_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol)
 {
     if (sw) {
         HWVoiceIn *hw = sw->hw;
@@ -2009,6 +2156,13 @@ void AUD_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol)
     }
 }
 
+void AUD_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    klass->set_volume_in(be, sw, vol);
+}
+
 static void audio_create_pdos(Audiodev *dev)
 {
     switch (dev->driver) {
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 19/24] audio: split AudioBackend
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (17 preceding siblings ...)
  2025-12-01 11:22 ` [RFC 18/24] audio: make AudioBackend truely abstract marcandre.lureau
@ 2025-12-01 11:23 ` marcandre.lureau
  2025-12-01 11:23 ` [RFC 20/24] audio: AUD_ -> audio_be_ marcandre.lureau
                   ` (5 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:23 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Code clean-up, to allow building bare abstract class separately.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qemu/audio.h |   2 +
 audio/audio-be.c     | 210 +++++++++++++++++++++++++++++++++++++++++++
 audio/audio.c        | 188 --------------------------------------
 audio/meson.build    |   1 +
 4 files changed, 213 insertions(+), 188 deletions(-)
 create mode 100644 audio/audio-be.c

diff --git a/include/qemu/audio.h b/include/qemu/audio.h
index 672f9befe5..649fb89de2 100644
--- a/include/qemu/audio.h
+++ b/include/qemu/audio.h
@@ -118,6 +118,8 @@ typedef struct QEMUAudioTimeStamp {
 
 bool AUD_backend_check(AudioBackend **be, Error **errp);
 
+AudioBackend *audio_be_new(Audiodev *dev, Error **errp);
+
 SWVoiceOut *AUD_open_out(
     AudioBackend *be,
     SWVoiceOut *sw,
diff --git a/audio/audio-be.c b/audio/audio-be.c
new file mode 100644
index 0000000000..565b7a4cc5
--- /dev/null
+++ b/audio/audio-be.c
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: MIT */
+
+#include "qemu/osdep.h"
+#include "qemu/audio.h"
+#include "qemu/audio-capture.h"
+#include "qapi/error.h"
+
+bool AUD_backend_check(AudioBackend **be, Error **errp)
+{
+    assert(be != NULL);
+
+    if (!*be) {
+        *be = audio_get_default_audio_be(errp);
+        if (!*be) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+SWVoiceIn *AUD_open_in(
+    AudioBackend *be,
+    SWVoiceIn *sw,
+    const char *name,
+    void *callback_opaque ,
+    audio_callback_fn callback_fn,
+    struct audsettings *as)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->open_in(be, sw, name, callback_opaque, callback_fn, as);
+}
+
+SWVoiceOut *AUD_open_out(
+    AudioBackend *be,
+    SWVoiceOut *sw,
+    const char *name,
+    void *callback_opaque ,
+    audio_callback_fn callback_fn,
+    struct audsettings *as)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->open_out(be, sw, name, callback_opaque, callback_fn, as);
+}
+
+void AUD_close_out(AudioBackend *be, SWVoiceOut *sw)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->close_out(be, sw);
+}
+
+void AUD_close_in(AudioBackend *be, SWVoiceIn *sw)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->close_in(be, sw);
+}
+
+bool AUD_is_active_out(AudioBackend *be, SWVoiceOut *sw)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->is_active_out(be, sw);
+}
+
+bool AUD_is_active_in(AudioBackend *be, SWVoiceIn *sw)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->is_active_in(be, sw);
+}
+
+size_t AUD_write(AudioBackend *be, SWVoiceOut *sw, void *buf, size_t size)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->write(be, sw, buf, size);
+}
+
+size_t AUD_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->read(be, sw, buf, size);
+}
+
+int AUD_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->get_buffer_size_out(be, sw);
+}
+
+void AUD_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->set_active_out(be, sw, on);
+}
+
+void AUD_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->set_active_in(be, sw, on);
+}
+
+void AUD_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    klass->set_volume_out(be, sw, vol);
+}
+
+void AUD_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    klass->set_volume_in(be, sw, vol);
+}
+
+CaptureVoiceOut *AUD_add_capture(
+    AudioBackend *be,
+    struct audsettings *as,
+    struct audio_capture_ops *ops,
+    void *cb_opaque)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->add_capture(be, as, ops, cb_opaque);
+}
+
+void AUD_del_capture(AudioBackend *be, CaptureVoiceOut *cap, void *cb_opaque)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    klass->del_capture(be, cap, cb_opaque);
+}
+
+#ifdef CONFIG_GIO
+bool audio_be_can_set_dbus_server(AudioBackend *be)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    return klass->set_dbus_server != NULL;
+}
+
+bool audio_be_set_dbus_server(AudioBackend *be,
+                              GDBusObjectManagerServer *server,
+                              bool p2p,
+                              Error **errp)
+{
+    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
+
+    if (!audio_be_can_set_dbus_server(be)) {
+        error_setg(errp, "Audiodev '%s' is not compatible with DBus",
+                   audio_be_get_id(be));
+        return false;
+    }
+
+    return klass->set_dbus_server(be, server, p2p, errp);
+}
+#endif
+
+const char *audio_be_get_id(AudioBackend *be)
+{
+    if (be) {
+        return AUDIO_BACKEND_GET_CLASS(be)->get_id(be);
+    } else {
+        return "";
+    }
+}
+
+AudioBackend *audio_be_new(Audiodev *dev, Error **errp)
+{
+    const char *drvname = AudiodevDriver_str(dev->driver);
+    g_autofree char *type = g_strconcat("audio-", drvname, NULL);
+    AudioBackend *be = AUDIO_BACKEND(object_new(type));
+
+    if (!be) {
+        error_setg(errp, "Unknown audio driver `%s'", drvname);
+        return NULL;
+    }
+
+    if (!AUDIO_BACKEND_GET_CLASS(be)->realize(be, dev, errp)) {
+        object_unref(OBJECT(be));
+        return NULL;
+    }
+
+    return be;
+}
+
+
+static const TypeInfo audio_be_info = {
+    .name = TYPE_AUDIO_BACKEND,
+    .parent = TYPE_OBJECT,
+    .instance_size = sizeof(AudioBackend),
+    .abstract = true,
+    .class_size = sizeof(AudioBackendClass),
+};
+
+static void register_types(void)
+{
+    type_register_static(&audio_be_info);
+}
+
+type_init(register_types);
diff --git a/audio/audio.c b/audio/audio.c
index d9c338ea91..3d1c650cc0 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -868,13 +868,6 @@ static void audio_timer (void *opaque)
      }
  }
 
-size_t AUD_write(AudioBackend *be, SWVoiceOut *sw, void *buf, size_t size)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->write(be, sw, buf, size);
-}
-
 static size_t audio_driver_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size)
 {
     HWVoiceIn *hw;
@@ -898,13 +891,6 @@ static size_t audio_driver_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size
 
 }
 
-size_t AUD_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->read(be, sw, buf, size);
-}
-
 static int audio_driver_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
 {
     if (!sw) {
@@ -918,13 +904,6 @@ static int audio_driver_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
     return sw->hw->samples * sw->hw->info.bytes_per_frame;
 }
 
-int AUD_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->get_buffer_size_out(be, sw);
-}
-
 static void audio_driver_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
 {
     HWVoiceOut *hw;
@@ -974,13 +953,6 @@ static void audio_driver_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool o
 
 }
 
-void AUD_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->set_active_out(be, sw, on);
-}
-
 static void audio_driver_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
 {
     HWVoiceIn *hw;
@@ -1026,13 +998,6 @@ static void audio_driver_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
     }
 }
 
-void AUD_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->set_active_in(be, sw, on);
-}
-
 static size_t audio_get_avail(SWVoiceIn *sw)
 {
     size_t live;
@@ -1683,14 +1648,6 @@ static void audio_vm_change_state_handler (void *opaque, bool running,
 
 static const VMStateDescription vmstate_audio;
 
-static void audio_be_init(Object *obj)
-{
-}
-
-static void audio_be_finalize(Object *obj)
-{
-}
-
 static const char *audio_driver_get_id(AudioBackend *be)
 {
     return AUDIO_DRIVER(be)->dev->id;
@@ -1710,60 +1667,6 @@ static void audio_driver_del_capture(
 static void audio_driver_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol);
 static void audio_driver_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol);
 
-SWVoiceOut *AUD_open_out(
-    AudioBackend *be,
-    SWVoiceOut *sw,
-    const char *name,
-    void *callback_opaque ,
-    audio_callback_fn callback_fn,
-    struct audsettings *as)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->open_out(be, sw, name, callback_opaque, callback_fn, as);
-}
-
-SWVoiceIn *AUD_open_in(
-    AudioBackend *be,
-    SWVoiceIn *sw,
-    const char *name,
-    void *callback_opaque ,
-    audio_callback_fn callback_fn,
-    struct audsettings *as)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->open_in(be, sw, name, callback_opaque, callback_fn, as);
-}
-
-void AUD_close_out(AudioBackend *be, SWVoiceOut *sw)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->close_out(be, sw);
-}
-
-void AUD_close_in(AudioBackend *be, SWVoiceIn *sw)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->close_in(be, sw);
-}
-
-bool AUD_is_active_out(AudioBackend *be, SWVoiceOut *sw)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->is_active_out(be, sw);
-}
-
-bool AUD_is_active_in(AudioBackend *be, SWVoiceIn *sw)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->is_active_in(be, sw);
-}
-
 static void audio_driver_class_init(ObjectClass *klass, const void *data)
 {
     AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
@@ -1966,20 +1869,6 @@ AudioBackend *audio_get_default_audio_be(Error **errp)
     return default_audio_be;
 }
 
-bool AUD_backend_check(AudioBackend **be, Error **errp)
-{
-    assert(be != NULL);
-
-    if (!*be) {
-        *be = audio_get_default_audio_be(errp);
-        if (!*be) {
-            return false;
-        }
-    }
-
-    return true;
-}
-
 static struct audio_pcm_ops capture_pcm_ops;
 
 static CaptureVoiceOut *audio_driver_add_capture(
@@ -2057,17 +1946,6 @@ static CaptureVoiceOut *audio_driver_add_capture(
     return cap;
 }
 
-CaptureVoiceOut *AUD_add_capture(
-    AudioBackend *be,
-    struct audsettings *as,
-    struct audio_capture_ops *ops,
-    void *cb_opaque)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->add_capture(be, as, ops, cb_opaque);
-}
-
 static void audio_driver_del_capture(
     AudioBackend *be,
     CaptureVoiceOut *cap,
@@ -2110,13 +1988,6 @@ static void audio_driver_del_capture(
     }
 }
 
-void AUD_del_capture(AudioBackend *be, CaptureVoiceOut *cap, void *cb_opaque)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    klass->del_capture(be, cap, cb_opaque);
-}
-
 static void audio_driver_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol)
 {
     if (sw) {
@@ -2133,13 +2004,6 @@ static void audio_driver_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume
     }
 }
 
-void AUD_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    klass->set_volume_out(be, sw, vol);
-}
-
 static void audio_driver_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol)
 {
     if (sw) {
@@ -2156,13 +2020,6 @@ static void audio_driver_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *
     }
 }
 
-void AUD_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    klass->set_volume_in(be, sw, vol);
-}
-
 static void audio_create_pdos(Audiodev *dev)
 {
     switch (dev->driver) {
@@ -2416,40 +2273,6 @@ AudioBackend *audio_be_by_name(const char *name, Error **errp)
     }
 }
 
-#ifdef CONFIG_GIO
-bool audio_be_can_set_dbus_server(AudioBackend *be)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    return klass->set_dbus_server != NULL;
-}
-
-bool audio_be_set_dbus_server(AudioBackend *be,
-                              GDBusObjectManagerServer *server,
-                              bool p2p,
-                              Error **errp)
-{
-    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
-
-    if (!audio_be_can_set_dbus_server(be)) {
-        error_setg(errp, "Audiodev '%s' is not compatible with DBus",
-                   audio_be_get_id(be));
-        return false;
-    }
-
-    return klass->set_dbus_server(be, server, p2p, errp);
-}
-#endif
-
-const char *audio_be_get_id(AudioBackend *be)
-{
-    if (be) {
-        return AUDIO_BACKEND_GET_CLASS(be)->get_id(be);
-    } else {
-        return "";
-    }
-}
-
 const char *audio_application_name(void)
 {
     const char *vm_name;
@@ -2513,16 +2336,6 @@ AudiodevList *qmp_query_audiodevs(Error **errp)
     return ret;
 }
 
-static const TypeInfo audio_be_info = {
-    .name = TYPE_AUDIO_BACKEND,
-    .parent = TYPE_OBJECT,
-    .instance_size = sizeof(AudioBackend),
-    .instance_init = audio_be_init,
-    .instance_finalize = audio_be_finalize,
-    .abstract = true,
-    .class_size = sizeof(AudioBackendClass),
-};
-
 static const TypeInfo audio_driver_info = {
     .name = TYPE_AUDIO_DRIVER,
     .parent = TYPE_AUDIO_BACKEND,
@@ -2536,7 +2349,6 @@ static const TypeInfo audio_driver_info = {
 
 static void register_types(void)
 {
-    type_register_static(&audio_be_info);
     type_register_static(&audio_driver_info);
 }
 
diff --git a/audio/meson.build b/audio/meson.build
index b2dca2c640..2450098eb8 100644
--- a/audio/meson.build
+++ b/audio/meson.build
@@ -1,5 +1,6 @@
 system_ss.add(files(
   'audio.c',
+  'audio-be.c',
   'mixeng.c',
   'noaudio.c',
   'wavaudio.c',
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 20/24] audio: AUD_ -> audio_be_
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (18 preceding siblings ...)
  2025-12-01 11:23 ` [RFC 19/24] audio: split AudioBackend marcandre.lureau
@ 2025-12-01 11:23 ` marcandre.lureau
  2025-12-01 11:23 ` [RFC 21/24] audio-be: add common pre-conditions marcandre.lureau
                   ` (4 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:23 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau, Laurent Vivier,
	Manos Pitsidianakis, Michael S. Tsirkin, Alistair Francis,
	Edgar E. Iglesias, Peter Maydell, open list:Xilinx ZynqMP and...

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Use the associate AudioBackend prefix for readability.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio_template.h       |  4 ++--
 include/qemu/audio-capture.h |  4 ++--
 include/qemu/audio.h         | 37 +++++++++++++++----------------
 audio/audio-be.c             | 32 +++++++++++++--------------
 audio/audio.c                | 19 ----------------
 audio/wavcapture.c           |  4 ++--
 hw/audio/ac97.c              | 42 ++++++++++++++++++------------------
 hw/audio/adlib.c             | 10 ++++-----
 hw/audio/asc.c               | 20 ++++++++---------
 hw/audio/cs4231a.c           | 18 ++++++++--------
 hw/audio/es1370.c            | 26 +++++++++++-----------
 hw/audio/gus.c               | 10 ++++-----
 hw/audio/hda-codec.c         | 26 +++++++++++-----------
 hw/audio/lm4549.c            | 20 ++++++++---------
 hw/audio/pcspk.c             |  8 +++----
 hw/audio/sb16.c              | 22 +++++++++----------
 hw/audio/via-ac97.c          | 20 ++++++++---------
 hw/audio/virtio-snd.c        | 34 ++++++++++++++---------------
 hw/audio/wm8750.c            | 42 ++++++++++++++++++------------------
 hw/display/xlnx_dp.c         | 14 ++++++------
 hw/usb/dev-audio.c           | 18 ++++++++--------
 ui/vnc.c                     |  4 ++--
 22 files changed, 208 insertions(+), 226 deletions(-)

diff --git a/audio/audio_template.h b/audio/audio_template.h
index 9dee8575f3..40d1ad9dea 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -527,7 +527,7 @@ static SW *glue(audio_driver_open_, TYPE) (
     }
 
     if (!pdo->fixed_settings && sw) {
-        glue(AUD_close_, TYPE)(be, sw);
+        glue(audio_be_close_, TYPE)(be, sw);
         sw = NULL;
     }
 
@@ -564,7 +564,7 @@ static SW *glue(audio_driver_open_, TYPE) (
     return sw;
 
  fail:
-    glue(AUD_close_, TYPE)(be, sw);
+    glue(audio_be_close_, TYPE)(be, sw);
     return NULL;
 }
 
diff --git a/include/qemu/audio-capture.h b/include/qemu/audio-capture.h
index 5bfbdd0298..d799823224 100644
--- a/include/qemu/audio-capture.h
+++ b/include/qemu/audio-capture.h
@@ -19,13 +19,13 @@ typedef struct CaptureState {
     QLIST_ENTRY(CaptureState) entries;
 } CaptureState;
 
-CaptureVoiceOut *AUD_add_capture(
+CaptureVoiceOut *audio_be_add_capture(
     AudioBackend *be,
     struct audsettings *as,
     struct audio_capture_ops *ops,
     void *opaque);
 
-void AUD_del_capture(
+void audio_be_del_capture(
     AudioBackend *be,
     CaptureVoiceOut *cap,
     void *cb_opaque);
diff --git a/include/qemu/audio.h b/include/qemu/audio.h
index 649fb89de2..1794702c30 100644
--- a/include/qemu/audio.h
+++ b/include/qemu/audio.h
@@ -116,11 +116,11 @@ typedef struct QEMUAudioTimeStamp {
     uint64_t old_ts;
 } QEMUAudioTimeStamp;
 
-bool AUD_backend_check(AudioBackend **be, Error **errp);
+bool audio_be_backend_check(AudioBackend **be, Error **errp);
 
 AudioBackend *audio_be_new(Audiodev *dev, Error **errp);
 
-SWVoiceOut *AUD_open_out(
+SWVoiceOut *audio_be_open_out(
     AudioBackend *be,
     SWVoiceOut *sw,
     const char *name,
@@ -128,30 +128,31 @@ SWVoiceOut *AUD_open_out(
     audio_callback_fn callback_fn,
     struct audsettings *settings);
 
-void AUD_close_out(AudioBackend *be, SWVoiceOut *sw);
-size_t AUD_write(AudioBackend *be, SWVoiceOut *sw, void *pcm_buf, size_t size);
-int  AUD_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw);
-void AUD_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on);
-bool AUD_is_active_out(AudioBackend *be, SWVoiceOut *sw);
+void audio_be_close_out(AudioBackend *be, SWVoiceOut *sw);
+size_t audio_be_write(AudioBackend *be, SWVoiceOut *sw, void *pcm_buf, size_t size);
+int  audio_be_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw);
+void audio_be_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on);
+bool audio_be_is_active_out(AudioBackend *be, SWVoiceOut *sw);
 
-void AUD_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol);
-void AUD_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol);
+
+void audio_be_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol);
+void audio_be_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol);
 
 static inline void
-AUD_set_volume_out_lr(AudioBackend *be, SWVoiceOut *sw, bool mut, uint8_t lvol, uint8_t rvol) {
-    AUD_set_volume_out(be, sw, &(Volume) {
+audio_be_set_volume_out_lr(AudioBackend *be, SWVoiceOut *sw, bool mut, uint8_t lvol, uint8_t rvol) {
+    audio_be_set_volume_out(be, sw, &(Volume) {
         .mute = mut, .channels = 2, .vol = { lvol, rvol }
     });
 }
 
 static inline void
-AUD_set_volume_in_lr(AudioBackend *be, SWVoiceIn *sw, bool mut, uint8_t lvol, uint8_t rvol) {
-    AUD_set_volume_in(be, sw, &(Volume) {
+audio_be_set_volume_in_lr(AudioBackend *be, SWVoiceIn *sw, bool mut, uint8_t lvol, uint8_t rvol) {
+    audio_be_set_volume_in(be, sw, &(Volume) {
         .mute = mut, .channels = 2, .vol = { lvol, rvol }
     });
 }
 
-SWVoiceIn *AUD_open_in(
+SWVoiceIn *audio_be_open_in(
     AudioBackend *be,
     SWVoiceIn *sw,
     const char *name,
@@ -160,10 +161,10 @@ SWVoiceIn *AUD_open_in(
     struct audsettings *settings
     );
 
-void AUD_close_in(AudioBackend *be, SWVoiceIn *sw);
-size_t AUD_read(AudioBackend *be, SWVoiceIn *sw, void *pcm_buf, size_t size);
-void AUD_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on);
-bool AUD_is_active_in(AudioBackend *be, SWVoiceIn *sw);
+void audio_be_close_in(AudioBackend *be, SWVoiceIn *sw);
+size_t audio_be_read(AudioBackend *be, SWVoiceIn *sw, void *pcm_buf, size_t size);
+void audio_be_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on);
+bool audio_be_is_active_in(AudioBackend *be, SWVoiceIn *sw);
 
 void audio_cleanup(void);
 
diff --git a/audio/audio-be.c b/audio/audio-be.c
index 565b7a4cc5..9af4dbe5e9 100644
--- a/audio/audio-be.c
+++ b/audio/audio-be.c
@@ -5,7 +5,7 @@
 #include "qemu/audio-capture.h"
 #include "qapi/error.h"
 
-bool AUD_backend_check(AudioBackend **be, Error **errp)
+bool audio_be_backend_check(AudioBackend **be, Error **errp)
 {
     assert(be != NULL);
 
@@ -19,7 +19,7 @@ bool AUD_backend_check(AudioBackend **be, Error **errp)
     return true;
 }
 
-SWVoiceIn *AUD_open_in(
+SWVoiceIn *audio_be_open_in(
     AudioBackend *be,
     SWVoiceIn *sw,
     const char *name,
@@ -32,7 +32,7 @@ SWVoiceIn *AUD_open_in(
     return klass->open_in(be, sw, name, callback_opaque, callback_fn, as);
 }
 
-SWVoiceOut *AUD_open_out(
+SWVoiceOut *audio_be_open_out(
     AudioBackend *be,
     SWVoiceOut *sw,
     const char *name,
@@ -45,84 +45,84 @@ SWVoiceOut *AUD_open_out(
     return klass->open_out(be, sw, name, callback_opaque, callback_fn, as);
 }
 
-void AUD_close_out(AudioBackend *be, SWVoiceOut *sw)
+void audio_be_close_out(AudioBackend *be, SWVoiceOut *sw)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
     return klass->close_out(be, sw);
 }
 
-void AUD_close_in(AudioBackend *be, SWVoiceIn *sw)
+void audio_be_close_in(AudioBackend *be, SWVoiceIn *sw)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
     return klass->close_in(be, sw);
 }
 
-bool AUD_is_active_out(AudioBackend *be, SWVoiceOut *sw)
+bool audio_be_is_active_out(AudioBackend *be, SWVoiceOut *sw)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
     return klass->is_active_out(be, sw);
 }
 
-bool AUD_is_active_in(AudioBackend *be, SWVoiceIn *sw)
+bool audio_be_is_active_in(AudioBackend *be, SWVoiceIn *sw)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
     return klass->is_active_in(be, sw);
 }
 
-size_t AUD_write(AudioBackend *be, SWVoiceOut *sw, void *buf, size_t size)
+size_t audio_be_write(AudioBackend *be, SWVoiceOut *sw, void *buf, size_t size)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
     return klass->write(be, sw, buf, size);
 }
 
-size_t AUD_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size)
+size_t audio_be_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
     return klass->read(be, sw, buf, size);
 }
 
-int AUD_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
+int audio_be_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
     return klass->get_buffer_size_out(be, sw);
 }
 
-void AUD_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
+void audio_be_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
     return klass->set_active_out(be, sw, on);
 }
 
-void AUD_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
+void audio_be_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
     return klass->set_active_in(be, sw, on);
 }
 
-void AUD_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol)
+void audio_be_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
     klass->set_volume_out(be, sw, vol);
 }
 
-void AUD_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol)
+void audio_be_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
     klass->set_volume_in(be, sw, vol);
 }
 
-CaptureVoiceOut *AUD_add_capture(
+CaptureVoiceOut *audio_be_add_capture(
     AudioBackend *be,
     struct audsettings *as,
     struct audio_capture_ops *ops,
@@ -133,7 +133,7 @@ CaptureVoiceOut *AUD_add_capture(
     return klass->add_capture(be, as, ops, cb_opaque);
 }
 
-void AUD_del_capture(AudioBackend *be, CaptureVoiceOut *cap, void *cb_opaque)
+void audio_be_del_capture(AudioBackend *be, CaptureVoiceOut *cap, void *cb_opaque)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
diff --git a/audio/audio.c b/audio/audio.c
index 3d1c650cc0..da2a4da125 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1605,25 +1605,6 @@ static bool audio_be_driver_realize(AudioBackend *abe, Audiodev *dev, Error **er
     return true;
 }
 
-static AudioBackend *audio_be_new(Audiodev *dev, Error **errp)
-{
-    const char *drvname = AudiodevDriver_str(dev->driver);
-    g_autofree char *type = g_strconcat("audio-", drvname, NULL);
-    AudioBackend *be = AUDIO_BACKEND(object_new(type));
-
-    if (!be) {
-        error_setg(errp, "Unknown audio driver `%s'", drvname);
-        return NULL;
-    }
-
-    if (!AUDIO_BACKEND_GET_CLASS(be)->realize(be, dev, errp)) {
-        object_unref(OBJECT(be));
-        return NULL;
-    }
-
-    return be;
-}
-
 static void audio_vm_change_state_handler (void *opaque, bool running,
                                            RunState state)
 {
diff --git a/audio/wavcapture.c b/audio/wavcapture.c
index 2950ceaf24..69aa91e35f 100644
--- a/audio/wavcapture.c
+++ b/audio/wavcapture.c
@@ -86,7 +86,7 @@ static void wav_capture_destroy (void *opaque)
 {
     WAVState *wav = opaque;
 
-    AUD_del_capture(wav->audio_be, wav->cap, wav);
+    audio_be_del_capture(wav->audio_be, wav->cap, wav);
     g_free (wav);
 }
 
@@ -172,7 +172,7 @@ int wav_start_capture(AudioBackend *state, CaptureState *s, const char *path,
         goto error_free;
     }
 
-    cap = AUD_add_capture(wav->audio_be, &as, &ops, wav);
+    cap = audio_be_add_capture(wav->audio_be, &as, &ops, wav);
     if (!cap) {
         error_report("Failed to add audio capture");
         goto error_free;
diff --git a/hw/audio/ac97.c b/hw/audio/ac97.c
index e9a1daf92a..78f53b977b 100644
--- a/hw/audio/ac97.c
+++ b/hw/audio/ac97.c
@@ -246,15 +246,15 @@ static void voice_set_active(AC97LinkState *s, int bm_index, int on)
 {
     switch (bm_index) {
     case PI_INDEX:
-        AUD_set_active_in(s->audio_be, s->voice_pi, on);
+        audio_be_set_active_in(s->audio_be, s->voice_pi, on);
         break;
 
     case PO_INDEX:
-        AUD_set_active_out(s->audio_be, s->voice_po, on);
+        audio_be_set_active_out(s->audio_be, s->voice_po, on);
         break;
 
     case MC_INDEX:
-        AUD_set_active_in(s->audio_be, s->voice_mc, on);
+        audio_be_set_active_in(s->audio_be, s->voice_mc, on);
         break;
 
     default:
@@ -319,7 +319,7 @@ static void open_voice(AC97LinkState *s, int index, int freq)
         s->invalid_freq[index] = 0;
         switch (index) {
         case PI_INDEX:
-            s->voice_pi = AUD_open_in(
+            s->voice_pi = audio_be_open_in(
                 s->audio_be,
                 s->voice_pi,
                 "ac97.pi",
@@ -330,7 +330,7 @@ static void open_voice(AC97LinkState *s, int index, int freq)
             break;
 
         case PO_INDEX:
-            s->voice_po = AUD_open_out(
+            s->voice_po = audio_be_open_out(
                 s->audio_be,
                 s->voice_po,
                 "ac97.po",
@@ -341,7 +341,7 @@ static void open_voice(AC97LinkState *s, int index, int freq)
             break;
 
         case MC_INDEX:
-            s->voice_mc = AUD_open_in(
+            s->voice_mc = audio_be_open_in(
                 s->audio_be,
                 s->voice_mc,
                 "ac97.mc",
@@ -355,17 +355,17 @@ static void open_voice(AC97LinkState *s, int index, int freq)
         s->invalid_freq[index] = freq;
         switch (index) {
         case PI_INDEX:
-            AUD_close_in(s->audio_be, s->voice_pi);
+            audio_be_close_in(s->audio_be, s->voice_pi);
             s->voice_pi = NULL;
             break;
 
         case PO_INDEX:
-            AUD_close_out(s->audio_be, s->voice_po);
+            audio_be_close_out(s->audio_be, s->voice_po);
             s->voice_po = NULL;
             break;
 
         case MC_INDEX:
-            AUD_close_in(s->audio_be, s->voice_mc);
+            audio_be_close_in(s->audio_be, s->voice_mc);
             s->voice_mc = NULL;
             break;
         }
@@ -378,15 +378,15 @@ static void reset_voices(AC97LinkState *s, uint8_t active[LAST_INDEX])
 
     freq = mixer_load(s, AC97_PCM_LR_ADC_Rate);
     open_voice(s, PI_INDEX, freq);
-    AUD_set_active_in(s->audio_be, s->voice_pi, active[PI_INDEX]);
+    audio_be_set_active_in(s->audio_be, s->voice_pi, active[PI_INDEX]);
 
     freq = mixer_load(s, AC97_PCM_Front_DAC_Rate);
     open_voice(s, PO_INDEX, freq);
-    AUD_set_active_out(s->audio_be, s->voice_po, active[PO_INDEX]);
+    audio_be_set_active_out(s->audio_be, s->voice_po, active[PO_INDEX]);
 
     freq = mixer_load(s, AC97_MIC_ADC_Rate);
     open_voice(s, MC_INDEX, freq);
-    AUD_set_active_in(s->audio_be, s->voice_mc, active[MC_INDEX]);
+    audio_be_set_active_in(s->audio_be, s->voice_mc, active[MC_INDEX]);
 }
 
 static void get_volume(uint16_t vol, uint16_t mask, int inverse,
@@ -416,7 +416,7 @@ static void update_combined_volume_out(AC97LinkState *s)
     lvol = (lvol * plvol) / 255;
     rvol = (rvol * prvol) / 255;
 
-    AUD_set_volume_out_lr(s->audio_be, s->voice_po, mute, lvol, rvol);
+    audio_be_set_volume_out_lr(s->audio_be, s->voice_po, mute, lvol, rvol);
 }
 
 static void update_volume_in(AC97LinkState *s)
@@ -427,7 +427,7 @@ static void update_volume_in(AC97LinkState *s)
     get_volume(mixer_load(s, AC97_Record_Gain_Mute), 0x0f, 0,
                &mute, &lvol, &rvol);
 
-    AUD_set_volume_in_lr(s->audio_be, s->voice_pi, mute, lvol, rvol);
+    audio_be_set_volume_in_lr(s->audio_be, s->voice_pi, mute, lvol, rvol);
 }
 
 static void set_volume(AC97LinkState *s, int index, uint32_t val)
@@ -904,7 +904,7 @@ static int write_audio(AC97LinkState *s, AC97BusMasterRegs *r,
         int copied;
         to_copy = MIN(temp, sizeof(tmpbuf));
         pci_dma_read(&s->dev, addr, tmpbuf, to_copy);
-        copied = AUD_write(s->audio_be, s->voice_po, tmpbuf, to_copy);
+        copied = audio_be_write(s->audio_be, s->voice_po, tmpbuf, to_copy);
         dolog("write_audio max=%x to_copy=%x copied=%x",
               max, to_copy, copied);
         if (!copied) {
@@ -948,7 +948,7 @@ static void write_bup(AC97LinkState *s, int elapsed)
     while (elapsed) {
         int temp = MIN(elapsed, sizeof(s->silence));
         while (temp) {
-            int copied = AUD_write(s->audio_be, s->voice_po, s->silence, temp);
+            int copied = audio_be_write(s->audio_be, s->voice_po, s->silence, temp);
             if (!copied) {
                 return;
             }
@@ -978,7 +978,7 @@ static int read_audio(AC97LinkState *s, AC97BusMasterRegs *r,
     while (temp) {
         int acquired;
         to_copy = MIN(temp, sizeof(tmpbuf));
-        acquired = AUD_read(s->audio_be, voice, tmpbuf, to_copy);
+        acquired = audio_be_read(s->audio_be, voice, tmpbuf, to_copy);
         if (!acquired) {
             *stop = 1;
             break;
@@ -1275,7 +1275,7 @@ static void ac97_realize(PCIDevice *dev, Error **errp)
     AC97LinkState *s = AC97(dev);
     uint8_t *c = s->dev.config;
 
-    if (!AUD_backend_check(&s->audio_be, errp)) {
+    if (!audio_be_backend_check(&s->audio_be, errp)) {
         return;
     }
 
@@ -1301,9 +1301,9 @@ static void ac97_exit(PCIDevice *dev)
 {
     AC97LinkState *s = AC97(dev);
 
-    AUD_close_in(s->audio_be, s->voice_pi);
-    AUD_close_out(s->audio_be, s->voice_po);
-    AUD_close_in(s->audio_be, s->voice_mc);
+    audio_be_close_in(s->audio_be, s->voice_pi);
+    audio_be_close_out(s->audio_be, s->voice_po);
+    audio_be_close_in(s->audio_be, s->voice_mc);
 }
 
 static const Property ac97_properties[] = {
diff --git a/hw/audio/adlib.c b/hw/audio/adlib.c
index d3677c1d7f..b1dd1f7638 100644
--- a/hw/audio/adlib.c
+++ b/hw/audio/adlib.c
@@ -96,7 +96,7 @@ static void adlib_write(void *opaque, uint32_t nport, uint32_t val)
     int a = nport & 3;
 
     s->active = 1;
-    AUD_set_active_out(s->audio_be, s->voice, 1);
+    audio_be_set_active_out(s->audio_be, s->voice, 1);
 
     adlib_kill_timers (s);
 
@@ -145,7 +145,7 @@ static int write_audio (AdlibState *s, int samples)
         int nbytes, wbytes, wsampl;
 
         nbytes = samples << SHIFT;
-        wbytes = AUD_write(
+        wbytes = audio_be_write(
             s->audio_be,
             s->voice,
             s->mixbuf + (pos << (SHIFT - 1)),
@@ -239,7 +239,7 @@ static void adlib_realizefn (DeviceState *dev, Error **errp)
     AdlibState *s = ADLIB(dev);
     struct audsettings as;
 
-    if (!AUD_backend_check(&s->audio_be, errp)) {
+    if (!audio_be_backend_check(&s->audio_be, errp)) {
         return;
     }
 
@@ -258,7 +258,7 @@ static void adlib_realizefn (DeviceState *dev, Error **errp)
     as.fmt = AUDIO_FORMAT_S16;
     as.endianness = HOST_BIG_ENDIAN;
 
-    s->voice = AUD_open_out(
+    s->voice = audio_be_open_out(
         s->audio_be,
         s->voice,
         "adlib",
@@ -272,7 +272,7 @@ static void adlib_realizefn (DeviceState *dev, Error **errp)
         return;
     }
 
-    s->samples = AUD_get_buffer_size_out(s->audio_be, s->voice) >> SHIFT;
+    s->samples = audio_be_get_buffer_size_out(s->audio_be, s->voice) >> SHIFT;
     s->mixbuf = g_malloc0 (s->samples << SHIFT);
 
     adlib_portio_list[0].offset = s->port;
diff --git a/hw/audio/asc.c b/hw/audio/asc.c
index c7bb40df83..82a2990a9a 100644
--- a/hw/audio/asc.c
+++ b/hw/audio/asc.c
@@ -355,12 +355,12 @@ static void asc_out_cb(void *opaque, int free_b)
              * loop because the FIFO has run out of data, and the driver
              * reuses the stale content in its circular audio buffer.
              */
-            AUD_write(s->audio_be, s->voice, s->silentbuf, samples << s->shift);
+            audio_be_write(s->audio_be, s->voice, s->silentbuf, samples << s->shift);
         }
         return;
     }
 
-    AUD_write(s->audio_be, s->voice, s->mixbuf, generated << s->shift);
+    audio_be_write(s->audio_be, s->voice, s->mixbuf, generated << s->shift);
 }
 
 static uint64_t asc_fifo_read(void *opaque, hwaddr addr,
@@ -470,9 +470,9 @@ static void asc_write(void *opaque, hwaddr addr, uint64_t value,
             asc_fifo_reset(&s->fifos[1]);
             asc_lower_irq(s);
             if (value != 0) {
-                AUD_set_active_out(s->audio_be, s->voice, 1);
+                audio_be_set_active_out(s->audio_be, s->voice, 1);
             } else {
-                AUD_set_active_out(s->audio_be, s->voice, 0);
+                audio_be_set_active_out(s->audio_be, s->voice, 0);
             }
         }
         break;
@@ -489,7 +489,7 @@ static void asc_write(void *opaque, hwaddr addr, uint64_t value,
         {
             int vol = (value & 0xe0);
 
-            AUD_set_volume_out_lr(s->audio_be, s->voice, 0, vol, vol);
+            audio_be_set_volume_out_lr(s->audio_be, s->voice, 0, vol, vol);
             break;
         }
     }
@@ -545,7 +545,7 @@ static int asc_post_load(void *opaque, int version)
     ASCState *s = ASC(opaque);
 
     if (s->regs[ASC_MODE] != 0) {
-        AUD_set_active_out(s->audio_be, s->voice, 1);
+        audio_be_set_active_out(s->audio_be, s->voice, 1);
     }
 
     return 0;
@@ -614,7 +614,7 @@ static void asc_reset_hold(Object *obj, ResetType type)
 {
     ASCState *s = ASC(obj);
 
-    AUD_set_active_out(s->audio_be, s->voice, 0);
+    audio_be_set_active_out(s->audio_be, s->voice, 0);
 
     memset(s->regs, 0, sizeof(s->regs));
     asc_fifo_reset(&s->fifos[0]);
@@ -641,7 +641,7 @@ static void asc_realize(DeviceState *dev, Error **errp)
     ASCState *s = ASC(dev);
     struct audsettings as;
 
-    if (!AUD_backend_check(&s->audio_be, errp)) {
+    if (!audio_be_backend_check(&s->audio_be, errp)) {
         return;
     }
 
@@ -650,7 +650,7 @@ static void asc_realize(DeviceState *dev, Error **errp)
     as.fmt = AUDIO_FORMAT_U8;
     as.endianness = HOST_BIG_ENDIAN;
 
-    s->voice = AUD_open_out(s->audio_be, s->voice, "asc.out", s, asc_out_cb,
+    s->voice = audio_be_open_out(s->audio_be, s->voice, "asc.out", s, asc_out_cb,
                             &as);
     if (!s->voice) {
         error_setg(errp, "Initializing audio stream failed");
@@ -658,7 +658,7 @@ static void asc_realize(DeviceState *dev, Error **errp)
     }
 
     s->shift = 1;
-    s->samples = AUD_get_buffer_size_out(s->audio_be, s->voice) >> s->shift;
+    s->samples = audio_be_get_buffer_size_out(s->audio_be, s->voice) >> s->shift;
     s->mixbuf = g_malloc0(s->samples << s->shift);
 
     s->silentbuf = g_malloc(s->samples << s->shift);
diff --git a/hw/audio/cs4231a.c b/hw/audio/cs4231a.c
index ff68a9ccd9..fdfb536468 100644
--- a/hw/audio/cs4231a.c
+++ b/hw/audio/cs4231a.c
@@ -327,7 +327,7 @@ static void cs_reset_voices (CSState *s, uint32_t val)
         goto error;
     }
 
-    s->voice = AUD_open_out(
+    s->voice = audio_be_open_out(
         s->audio_be,
         s->voice,
         "cs4231a",
@@ -339,7 +339,7 @@ static void cs_reset_voices (CSState *s, uint32_t val)
     if (s->dregs[Interface_Configuration] & PEN) {
         if (!s->dma_running) {
             k->hold_DREQ(s->isa_dma, s->dma);
-            AUD_set_active_out(s->audio_be, s->voice, 1);
+            audio_be_set_active_out(s->audio_be, s->voice, 1);
             s->transferred = 0;
         }
         s->dma_running = 1;
@@ -347,7 +347,7 @@ static void cs_reset_voices (CSState *s, uint32_t val)
     else {
         if (s->dma_running) {
             k->release_DREQ(s->isa_dma, s->dma);
-            AUD_set_active_out(s->audio_be, s->voice, 0);
+            audio_be_set_active_out(s->audio_be, s->voice, 0);
         }
         s->dma_running = 0;
     }
@@ -356,7 +356,7 @@ static void cs_reset_voices (CSState *s, uint32_t val)
  error:
     if (s->dma_running) {
         k->release_DREQ(s->isa_dma, s->dma);
-        AUD_set_active_out(s->audio_be, s->voice, 0);
+        audio_be_set_active_out(s->audio_be, s->voice, 0);
     }
 }
 
@@ -465,7 +465,7 @@ static void cs_write (void *opaque, hwaddr addr,
                 if (s->dma_running) {
                     IsaDmaClass *k = ISADMA_GET_CLASS(s->isa_dma);
                     k->release_DREQ(s->isa_dma, s->dma);
-                    AUD_set_active_out(s->audio_be, s->voice, 0);
+                    audio_be_set_active_out(s->audio_be, s->voice, 0);
                     s->dma_running = 0;
                 }
             }
@@ -551,11 +551,11 @@ static int cs_write_audio (CSState *s, int nchan, int dma_pos,
 
             for (i = 0; i < copied; ++i)
                 linbuf[i] = s->tab[tmpbuf[i]];
-            copied = AUD_write(s->audio_be, s->voice, linbuf, copied << 1);
+            copied = audio_be_write(s->audio_be, s->voice, linbuf, copied << 1);
             copied >>= 1;
         }
         else {
-            copied = AUD_write(s->audio_be, s->voice, tmpbuf, copied);
+            copied = audio_be_write(s->audio_be, s->voice, tmpbuf, copied);
         }
 
         temp -= copied;
@@ -614,7 +614,7 @@ static int cs4231a_pre_load (void *opaque)
     if (s->dma_running) {
         IsaDmaClass *k = ISADMA_GET_CLASS(s->isa_dma);
         k->release_DREQ(s->isa_dma, s->dma);
-        AUD_set_active_out(s->audio_be, s->voice, 0);
+        audio_be_set_active_out(s->audio_be, s->voice, 0);
     }
     s->dma_running = 0;
     return 0;
@@ -678,7 +678,7 @@ static void cs4231a_realizefn (DeviceState *dev, Error **errp)
         return;
     }
 
-    if (!AUD_backend_check(&s->audio_be, errp)) {
+    if (!audio_be_backend_check(&s->audio_be, errp)) {
         return;
     }
 
diff --git a/hw/audio/es1370.c b/hw/audio/es1370.c
index 0422cf9836..37e80c6201 100644
--- a/hw/audio/es1370.c
+++ b/hw/audio/es1370.c
@@ -330,10 +330,10 @@ static void es1370_reset (ES1370State *s)
         d->scount = 0;
         d->leftover = 0;
         if (i == ADC_CHANNEL) {
-            AUD_close_in(s->audio_be, s->adc_voice);
+            audio_be_close_in(s->audio_be, s->adc_voice);
             s->adc_voice = NULL;
         } else {
-            AUD_close_out(s->audio_be, s->dac_voice[i]);
+            audio_be_close_out(s->audio_be, s->dac_voice[i]);
             s->dac_voice[i] = NULL;
         }
     }
@@ -411,7 +411,7 @@ static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl)
 
                 if (i == ADC_CHANNEL) {
                     s->adc_voice =
-                        AUD_open_in(
+                        audio_be_open_in(
                             s->audio_be,
                             s->adc_voice,
                             "es1370.adc",
@@ -421,7 +421,7 @@ static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl)
                             );
                 } else {
                     s->dac_voice[i] =
-                        AUD_open_out(
+                        audio_be_open_out(
                             s->audio_be,
                             s->dac_voice[i],
                             i ? "es1370.dac2" : "es1370.dac1",
@@ -438,9 +438,9 @@ static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl)
             int on = (ctl & b->ctl_en) && !(sctl & b->sctl_pause);
 
             if (i == ADC_CHANNEL) {
-                AUD_set_active_in(s->audio_be, s->adc_voice, on);
+                audio_be_set_active_in(s->audio_be, s->adc_voice, on);
             } else {
-                AUD_set_active_out(s->audio_be, s->dac_voice[i], on);
+                audio_be_set_active_out(s->audio_be, s->dac_voice[i], on);
             }
         }
     }
@@ -627,7 +627,7 @@ static void es1370_transfer_audio (ES1370State *s, struct chan *d, int loop_sel,
             int acquired, to_copy;
 
             to_copy = MIN(to_transfer, sizeof(tmpbuf));
-            acquired = AUD_read(s->audio_be, s->adc_voice, tmpbuf, to_copy);
+            acquired = audio_be_read(s->audio_be, s->adc_voice, tmpbuf, to_copy);
             if (!acquired) {
                 break;
             }
@@ -646,7 +646,7 @@ static void es1370_transfer_audio (ES1370State *s, struct chan *d, int loop_sel,
 
             to_copy = MIN(to_transfer, sizeof(tmpbuf));
             pci_dma_read (&s->dev, addr, tmpbuf, to_copy);
-            copied = AUD_write(s->audio_be, voice, tmpbuf, to_copy);
+            copied = audio_be_write(s->audio_be, voice, tmpbuf, to_copy);
             if (!copied) {
                 break;
             }
@@ -784,12 +784,12 @@ static int es1370_post_load (void *opaque, int version_id)
     for (i = 0; i < NB_CHANNELS; ++i) {
         if (i == ADC_CHANNEL) {
             if (s->adc_voice) {
-                AUD_close_in(s->audio_be, s->adc_voice);
+                audio_be_close_in(s->audio_be, s->adc_voice);
                 s->adc_voice = NULL;
             }
         } else {
             if (s->dac_voice[i]) {
-                AUD_close_out(s->audio_be, s->dac_voice[i]);
+                audio_be_close_out(s->audio_be, s->dac_voice[i]);
                 s->dac_voice[i] = NULL;
             }
         }
@@ -833,7 +833,7 @@ static void es1370_realize(PCIDevice *dev, Error **errp)
     ES1370State *s = ES1370(dev);
     uint8_t *c = s->dev.config;
 
-    if (!AUD_backend_check(&s->audio_be, errp)) {
+    if (!audio_be_backend_check(&s->audio_be, errp)) {
         return;
     }
 
@@ -861,10 +861,10 @@ static void es1370_exit(PCIDevice *dev)
     int i;
 
     for (i = 0; i < 2; ++i) {
-        AUD_close_out(s->audio_be, s->dac_voice[i]);
+        audio_be_close_out(s->audio_be, s->dac_voice[i]);
     }
 
-    AUD_close_in(s->audio_be, s->adc_voice);
+    audio_be_close_in(s->audio_be, s->adc_voice);
 }
 
 static const Property es1370_properties[] = {
diff --git a/hw/audio/gus.c b/hw/audio/gus.c
index df069a19fc..ea759ae0f0 100644
--- a/hw/audio/gus.c
+++ b/hw/audio/gus.c
@@ -87,7 +87,7 @@ static int write_audio (GUSState *s, int samples)
         int nbytes, wbytes, wsampl;
 
         nbytes = samples << s->shift;
-        wbytes = AUD_write(
+        wbytes = audio_be_write(
             s->audio_be,
             s->voice,
             s->mixbuf + (pos << (s->shift - 1)),
@@ -243,7 +243,7 @@ static void gus_realizefn (DeviceState *dev, Error **errp)
     IsaDmaClass *k;
     struct audsettings as;
 
-    if (!AUD_backend_check(&s->audio_be, errp)) {
+    if (!audio_be_backend_check(&s->audio_be, errp)) {
         return;
     }
 
@@ -258,7 +258,7 @@ static void gus_realizefn (DeviceState *dev, Error **errp)
     as.fmt = AUDIO_FORMAT_S16;
     as.endianness = HOST_BIG_ENDIAN;
 
-    s->voice = AUD_open_out(
+    s->voice = audio_be_open_out(
         s->audio_be,
         NULL,
         "gus",
@@ -273,7 +273,7 @@ static void gus_realizefn (DeviceState *dev, Error **errp)
     }
 
     s->shift = 2;
-    s->samples = AUD_get_buffer_size_out(s->audio_be, s->voice) >> s->shift;
+    s->samples = audio_be_get_buffer_size_out(s->audio_be, s->voice) >> s->shift;
     s->mixbuf = g_malloc0 (s->samples << s->shift);
 
     isa_register_portio_list(d, &s->portio_list1, s->port,
@@ -288,7 +288,7 @@ static void gus_realizefn (DeviceState *dev, Error **errp)
     s->emu.opaque = s;
     s->pic = isa_bus_get_irq(bus, s->emu.gusirq);
 
-    AUD_set_active_out(s->audio_be, s->voice, 1);
+    audio_be_set_active_out(s->audio_be, s->voice, 1);
 }
 
 static const Property gus_properties[] = {
diff --git a/hw/audio/hda-codec.c b/hw/audio/hda-codec.c
index 1daa2c25c5..28167c00da 100644
--- a/hw/audio/hda-codec.c
+++ b/hw/audio/hda-codec.c
@@ -275,7 +275,7 @@ static void hda_audio_input_cb(void *opaque, int avail)
     while (to_transfer) {
         uint32_t start = (uint32_t) (wpos & B_MASK);
         uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
-        uint32_t read = AUD_read(st->state->audio_be, st->voice.in, st->buf + start, chunk);
+        uint32_t read = audio_be_read(st->state->audio_be, st->voice.in, st->buf + start, chunk);
         wpos += read;
         to_transfer -= read;
         st->wpos += read;
@@ -354,7 +354,7 @@ static void hda_audio_output_cb(void *opaque, int avail)
     while (to_transfer) {
         uint32_t start = (uint32_t) (rpos & B_MASK);
         uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
-        uint32_t written = AUD_write(st->state->audio_be, st->voice.out, st->buf + start, chunk);
+        uint32_t written = audio_be_write(st->state->audio_be, st->voice.out, st->buf + start, chunk);
         rpos += written;
         to_transfer -= written;
         st->rpos += written;
@@ -375,7 +375,7 @@ static void hda_audio_compat_input_cb(void *opaque, int avail)
 
     while (avail - recv >= sizeof(st->compat_buf)) {
         if (st->compat_bpos != sizeof(st->compat_buf)) {
-            len = AUD_read(st->state->audio_be, st->voice.in, st->compat_buf + st->compat_bpos,
+            len = audio_be_read(st->state->audio_be, st->voice.in, st->compat_buf + st->compat_bpos,
                            sizeof(st->compat_buf) - st->compat_bpos);
             st->compat_bpos += len;
             recv += len;
@@ -408,7 +408,7 @@ static void hda_audio_compat_output_cb(void *opaque, int avail)
             }
             st->compat_bpos = 0;
         }
-        len = AUD_write(st->state->audio_be, st->voice.out, st->compat_buf + st->compat_bpos,
+        len = audio_be_write(st->state->audio_be, st->voice.out, st->compat_buf + st->compat_bpos,
                         sizeof(st->compat_buf) - st->compat_bpos);
         st->compat_bpos += len;
         sent += len;
@@ -440,9 +440,9 @@ static void hda_audio_set_running(HDAAudioStream *st, bool running)
         }
     }
     if (st->output) {
-        AUD_set_active_out(st->state->audio_be, st->voice.out, st->running);
+        audio_be_set_active_out(st->state->audio_be, st->voice.out, st->running);
     } else {
-        AUD_set_active_in(st->state->audio_be, st->voice.in, st->running);
+        audio_be_set_active_in(st->state->audio_be, st->voice.in, st->running);
     }
 }
 
@@ -466,9 +466,9 @@ static void hda_audio_set_amp(HDAAudioStream *st)
         return;
     }
     if (st->output) {
-        AUD_set_volume_out_lr(st->state->audio_be, st->voice.out, muted, left, right);
+        audio_be_set_volume_out_lr(st->state->audio_be, st->voice.out, muted, left, right);
     } else {
-        AUD_set_volume_in_lr(st->state->audio_be, st->voice.in, muted, left, right);
+        audio_be_set_volume_in_lr(st->state->audio_be, st->voice.in, muted, left, right);
     }
 }
 
@@ -491,7 +491,7 @@ static void hda_audio_setup(HDAAudioStream *st)
         } else {
             cb = hda_audio_compat_output_cb;
         }
-        st->voice.out = AUD_open_out(st->state->audio_be, st->voice.out,
+        st->voice.out = audio_be_open_out(st->state->audio_be, st->voice.out,
                                      st->node->name, st, cb, &st->as);
     } else {
         if (use_timer) {
@@ -500,7 +500,7 @@ static void hda_audio_setup(HDAAudioStream *st)
         } else {
             cb = hda_audio_compat_input_cb;
         }
-        st->voice.in = AUD_open_in(st->state->audio_be, st->voice.in,
+        st->voice.in = audio_be_open_in(st->state->audio_be, st->voice.in,
                                    st->node->name, st, cb, &st->as);
     }
 }
@@ -696,7 +696,7 @@ static void hda_audio_init(HDACodecDevice *hda,
     const desc_param *param;
     uint32_t i, type;
 
-    if (!AUD_backend_check(&a->audio_be, errp)) {
+    if (!audio_be_backend_check(&a->audio_be, errp)) {
         return;
     }
 
@@ -754,9 +754,9 @@ static void hda_audio_exit(HDACodecDevice *hda)
         }
         timer_free(st->buft);
         if (st->output) {
-            AUD_close_out(a->audio_be, st->voice.out);
+            audio_be_close_out(a->audio_be, st->voice.out);
         } else {
-            AUD_close_in(a->audio_be, st->voice.in);
+            audio_be_close_in(a->audio_be, st->voice.in);
         }
     }
 }
diff --git a/hw/audio/lm4549.c b/hw/audio/lm4549.c
index 4adf67f967..68ee5bdfa9 100644
--- a/hw/audio/lm4549.c
+++ b/hw/audio/lm4549.c
@@ -101,11 +101,11 @@ static void lm4549_audio_transfer(lm4549_state *s)
     uint32_t i;
 
     /* Activate the voice */
-    AUD_set_active_out(s->audio_be, s->voice, 1);
+    audio_be_set_active_out(s->audio_be, s->voice, 1);
     s->voice_is_active = 1;
 
     /* Try to write the buffer content */
-    written_bytes = AUD_write(s->audio_be, s->voice, s->buffer,
+    written_bytes = audio_be_write(s->audio_be, s->voice, s->buffer,
                               s->buffer_level * sizeof(uint16_t));
     written_samples = written_bytes >> 1;
 
@@ -129,14 +129,14 @@ static void lm4549_audio_out_callback(void *opaque, int free)
     static uint32_t prev_buffer_level;
 
 #ifdef LM4549_DEBUG
-    int size = AUD_get_buffer_size_out(s->audio_be, s->voice);
+    int size = audio_be_get_buffer_size_out(s->audio_be, s->voice);
     DPRINTF("audio_out_callback size = %i free = %i\n", size, free);
 #endif
 
     /* Detect that no data are consumed
        => disable the voice */
     if (s->buffer_level == prev_buffer_level) {
-        AUD_set_active_out(s->audio_be, s->voice, 0);
+        audio_be_set_active_out(s->audio_be, s->voice, 0);
         s->voice_is_active = 0;
     }
     prev_buffer_level = s->buffer_level;
@@ -204,7 +204,7 @@ void lm4549_write(lm4549_state *s,
         as.fmt = AUDIO_FORMAT_S16;
         as.endianness = 0;
 
-        s->voice = AUD_open_out(
+        s->voice = audio_be_open_out(
             s->audio_be,
             s->voice,
             "lm4549.out",
@@ -274,7 +274,7 @@ static int lm4549_post_load(void *opaque, int version_id)
     as.fmt = AUDIO_FORMAT_S16;
     as.endianness = 0;
 
-    s->voice = AUD_open_out(
+    s->voice = audio_be_open_out(
         s->audio_be,
         s->voice,
         "lm4549.out",
@@ -285,7 +285,7 @@ static int lm4549_post_load(void *opaque, int version_id)
 
     /* Request data */
     if (s->voice_is_active == 1) {
-        lm4549_audio_out_callback(s, AUD_get_buffer_size_out(s->audio_be, s->voice));
+        lm4549_audio_out_callback(s, audio_be_get_buffer_size_out(s->audio_be, s->voice));
     }
 
     return 0;
@@ -297,7 +297,7 @@ void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque,
     struct audsettings as;
 
     /* Register an audio card */
-    if (!AUD_backend_check(&s->audio_be, errp)) {
+    if (!audio_be_backend_check(&s->audio_be, errp)) {
         return;
     }
 
@@ -314,7 +314,7 @@ void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque,
     as.fmt = AUDIO_FORMAT_S16;
     as.endianness = 0;
 
-    s->voice = AUD_open_out(
+    s->voice = audio_be_open_out(
         s->audio_be,
         s->voice,
         "lm4549.out",
@@ -323,7 +323,7 @@ void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque,
         &as
     );
 
-    AUD_set_volume_out_lr(s->audio_be, s->voice, 0, 255, 255);
+    audio_be_set_volume_out_lr(s->audio_be, s->voice, 0, 255, 255);
 
     s->voice_is_active = 0;
 
diff --git a/hw/audio/pcspk.c b/hw/audio/pcspk.c
index a74a263b37..1787fc2bc5 100644
--- a/hw/audio/pcspk.c
+++ b/hw/audio/pcspk.c
@@ -106,7 +106,7 @@ static void pcspk_callback(void *opaque, int free)
 
     while (free > 0) {
         n = MIN(s->samples - s->play_pos, (unsigned int)free);
-        n = AUD_write(s->audio_be, s->voice, &s->sample_buf[s->play_pos], n);
+        n = audio_be_write(s->audio_be, s->voice, &s->sample_buf[s->play_pos], n);
         if (!n)
             break;
         s->play_pos = (s->play_pos + n) % s->samples;
@@ -123,7 +123,7 @@ static int pcspk_audio_init(PCSpkState *s)
         return 0;
     }
 
-    s->voice = AUD_open_out(s->audio_be, s->voice, s_spk, s, pcspk_callback, &as);
+    s->voice = audio_be_open_out(s->audio_be, s->voice, s_spk, s, pcspk_callback, &as);
     if (!s->voice) {
         error_report("pcspk: Could not open voice");
         return -1;
@@ -164,7 +164,7 @@ static void pcspk_io_write(void *opaque, hwaddr addr, uint64_t val,
     if (s->voice) {
         if (gate) /* restart */
             s->play_pos = 0;
-        AUD_set_active_out(s->audio_be, s->voice, gate & s->data_on);
+        audio_be_set_active_out(s->audio_be, s->voice, gate & s->data_on);
     }
 }
 
@@ -196,7 +196,7 @@ static void pcspk_realizefn(DeviceState *dev, Error **errp)
 
     isa_register_ioport(isadev, &s->ioport, s->iobase);
 
-    if (s->audio_be && AUD_backend_check(&s->audio_be, errp)) {
+    if (s->audio_be && audio_be_backend_check(&s->audio_be, errp)) {
         pcspk_audio_init(s);
         return;
     }
diff --git a/hw/audio/sb16.c b/hw/audio/sb16.c
index 47b36dfc69..270f2075c9 100644
--- a/hw/audio/sb16.c
+++ b/hw/audio/sb16.c
@@ -171,7 +171,7 @@ static void log_dsp (SB16State *dsp)
 static void speaker (SB16State *s, int on)
 {
     s->speaker = on;
-    /* AUD_enable (s->voice, on); */
+    /* audio_be_enable (s->voice, on); */
 }
 
 static void control (SB16State *s, int hold)
@@ -185,11 +185,11 @@ static void control (SB16State *s, int hold)
 
     if (hold) {
         k->hold_DREQ(isa_dma, dma);
-        AUD_set_active_out(s->audio_be, s->voice, 1);
+        audio_be_set_active_out(s->audio_be, s->voice, 1);
     }
     else {
         k->release_DREQ(isa_dma, dma);
-        AUD_set_active_out(s->audio_be, s->voice, 0);
+        audio_be_set_active_out(s->audio_be, s->voice, 0);
     }
 }
 
@@ -215,7 +215,7 @@ static void continue_dma8 (SB16State *s)
         as.fmt = s->fmt;
         as.endianness = 0;
 
-        s->voice = AUD_open_out(
+        s->voice = audio_be_open_out(
             s->audio_be,
             s->voice,
             "sb16",
@@ -378,7 +378,7 @@ static void dma_cmd (SB16State *s, uint8_t cmd, uint8_t d0, int dma_len)
         as.fmt = s->fmt;
         as.endianness = 0;
 
-        s->voice = AUD_open_out(
+        s->voice = audio_be_open_out(
             s->audio_be,
             s->voice,
             "sb16",
@@ -879,7 +879,7 @@ static void legacy_reset (SB16State *s)
     as.fmt = AUDIO_FORMAT_U8;
     as.endianness = 0;
 
-    s->voice = AUD_open_out(
+    s->voice = audio_be_open_out(
         s->audio_be,
         s->voice,
         "sb16",
@@ -889,7 +889,7 @@ static void legacy_reset (SB16State *s)
         );
 
     /* Not sure about that... */
-    /* AUD_set_active_out (s->voice, 1); */
+    /* audio_be_set_active_out (s->voice, 1); */
 }
 
 static void reset (SB16State *s)
@@ -1196,7 +1196,7 @@ static int write_audio (SB16State *s, int nchan, int dma_pos,
         }
 
         copied = k->read_memory(isa_dma, nchan, tmpbuf, dma_pos, to_copy);
-        copied = AUD_write(s->audio_be, s->voice, tmpbuf, copied);
+        copied = audio_be_write(s->audio_be, s->voice, tmpbuf, copied);
 
         temp -= copied;
         dma_pos = (dma_pos + copied) % dma_len;
@@ -1287,7 +1287,7 @@ static int sb16_post_load (void *opaque, int version_id)
     SB16State *s = opaque;
 
     if (s->voice) {
-        AUD_close_out(s->audio_be, s->voice);
+        audio_be_close_out(s->audio_be, s->voice);
         s->voice = NULL;
     }
 
@@ -1302,7 +1302,7 @@ static int sb16_post_load (void *opaque, int version_id)
             as.fmt = s->fmt;
             as.endianness = 0;
 
-            s->voice = AUD_open_out(
+            s->voice = audio_be_open_out(
                 s->audio_be,
                 s->voice,
                 "sb16",
@@ -1401,7 +1401,7 @@ static void sb16_realizefn (DeviceState *dev, Error **errp)
     SB16State *s = SB16 (dev);
     IsaDmaClass *k;
 
-    if (!AUD_backend_check(&s->audio_be, errp)) {
+    if (!audio_be_backend_check(&s->audio_be, errp)) {
         return;
     }
 
diff --git a/hw/audio/via-ac97.c b/hw/audio/via-ac97.c
index 5b344d7a0f..c189322566 100644
--- a/hw/audio/via-ac97.c
+++ b/hw/audio/via-ac97.c
@@ -53,7 +53,7 @@ static void codec_volume_set_out(ViaAC97State *s)
     rvol /= 255;
     mute = CODEC_REG(s, AC97_Master_Volume_Mute) >> MUTE_SHIFT;
     mute |= CODEC_REG(s, AC97_PCM_Out_Volume_Mute) >> MUTE_SHIFT;
-    AUD_set_volume_out_lr(s->audio_be, s->vo, mute, lvol, rvol);
+    audio_be_set_volume_out_lr(s->audio_be, s->vo, mute, lvol, rvol);
 }
 
 static void codec_reset(ViaAC97State *s)
@@ -189,7 +189,7 @@ static void out_cb(void *opaque, int avail)
         while (temp) {
             to_copy = MIN(temp, sizeof(tmpbuf));
             pci_dma_read(&s->dev, c->addr, tmpbuf, to_copy);
-            copied = AUD_write(s->audio_be, s->vo, tmpbuf, to_copy);
+            copied = audio_be_write(s->audio_be, s->vo, tmpbuf, to_copy);
             if (!copied) {
                 stop = true;
                 break;
@@ -208,7 +208,7 @@ static void out_cb(void *opaque, int avail)
                     c->stat |= STAT_PAUSED;
                 } else {
                     c->stat &= ~STAT_ACTIVE;
-                    AUD_set_active_out(s->audio_be, s->vo, 0);
+                    audio_be_set_active_out(s->audio_be, s->vo, 0);
                 }
                 if (c->type & STAT_EOL) {
                     via_isa_set_irq(&s->dev, 0, 1);
@@ -239,7 +239,7 @@ static void open_voice_out(ViaAC97State *s)
         .fmt = s->aur.type & BIT(5) ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_S8,
         .endianness = 0,
     };
-    s->vo = AUD_open_out(s->audio_be, s->vo, "via-ac97.out", s, out_cb, &as);
+    s->vo = audio_be_open_out(s->audio_be, s->vo, "via-ac97.out", s, out_cb, &as);
 }
 
 static uint64_t sgd_read(void *opaque, hwaddr addr, unsigned size)
@@ -317,20 +317,20 @@ static void sgd_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
         break;
     case 1:
         if (val & CNTL_START) {
-            AUD_set_active_out(s->audio_be, s->vo, 1);
+            audio_be_set_active_out(s->audio_be, s->vo, 1);
             s->aur.stat = STAT_ACTIVE;
         }
         if (val & CNTL_TERM) {
-            AUD_set_active_out(s->audio_be, s->vo, 0);
+            audio_be_set_active_out(s->audio_be, s->vo, 0);
             s->aur.stat &= ~(STAT_ACTIVE | STAT_PAUSED);
             s->aur.clen = 0;
         }
         if (val & CNTL_PAUSE) {
-            AUD_set_active_out(s->audio_be, s->vo, 0);
+            audio_be_set_active_out(s->audio_be, s->vo, 0);
             s->aur.stat &= ~STAT_ACTIVE;
             s->aur.stat |= STAT_PAUSED;
         } else if (!(val & CNTL_PAUSE) && (s->aur.stat & STAT_PAUSED)) {
-            AUD_set_active_out(s->audio_be, s->vo, 1);
+            audio_be_set_active_out(s->audio_be, s->vo, 1);
             s->aur.stat |= STAT_ACTIVE;
             s->aur.stat &= ~STAT_PAUSED;
         }
@@ -426,7 +426,7 @@ static void via_ac97_realize(PCIDevice *pci_dev, Error **errp)
     ViaAC97State *s = VIA_AC97(pci_dev);
     Object *o = OBJECT(s);
 
-    if (!AUD_backend_check(&s->audio_be, errp)) {
+    if (!audio_be_backend_check(&s->audio_be, errp)) {
         return;
     }
 
@@ -455,7 +455,7 @@ static void via_ac97_exit(PCIDevice *dev)
 {
     ViaAC97State *s = VIA_AC97(dev);
 
-    AUD_close_out(s->audio_be, s->vo);
+    audio_be_close_out(s->audio_be, s->vo);
 }
 
 static const Property via_ac97_properties[] = {
diff --git a/hw/audio/virtio-snd.c b/hw/audio/virtio-snd.c
index 47fef61af8..19eab3a353 100644
--- a/hw/audio/virtio-snd.c
+++ b/hw/audio/virtio-snd.c
@@ -391,10 +391,10 @@ static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream)
     if (stream) {
         virtio_snd_pcm_flush(stream);
         if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
-            AUD_close_out(stream->pcm->snd->audio_be, stream->voice.out);
+            audio_be_close_out(stream->pcm->snd->audio_be, stream->voice.out);
             stream->voice.out = NULL;
         } else if (stream->info.direction == VIRTIO_SND_D_INPUT) {
-            AUD_close_in(stream->pcm->snd->audio_be, stream->voice.in);
+            audio_be_close_in(stream->pcm->snd->audio_be, stream->voice.in);
             stream->voice.in = NULL;
         }
     }
@@ -457,21 +457,21 @@ static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id)
     stream->as = as;
 
     if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
-        stream->voice.out = AUD_open_out(s->audio_be,
+        stream->voice.out = audio_be_open_out(s->audio_be,
                                          stream->voice.out,
                                          "virtio-sound.out",
                                          stream,
                                          virtio_snd_pcm_out_cb,
                                          &as);
-        AUD_set_volume_out_lr(s->audio_be, stream->voice.out, 0, 255, 255);
+        audio_be_set_volume_out_lr(s->audio_be, stream->voice.out, 0, 255, 255);
     } else {
-        stream->voice.in = AUD_open_in(s->audio_be,
+        stream->voice.in = audio_be_open_in(s->audio_be,
                                         stream->voice.in,
                                         "virtio-sound.in",
                                         stream,
                                         virtio_snd_pcm_in_cb,
                                         &as);
-        AUD_set_volume_in_lr(s->audio_be, stream->voice.in, 0, 255, 255);
+        audio_be_set_volume_in_lr(s->audio_be, stream->voice.in, 0, 255, 255);
     }
 
     return cpu_to_le32(VIRTIO_SND_S_OK);
@@ -561,9 +561,9 @@ static void virtio_snd_handle_pcm_start_stop(VirtIOSound *s,
             stream->active = start;
         }
         if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
-            AUD_set_active_out(s->audio_be, stream->voice.out, start);
+            audio_be_set_active_out(s->audio_be, stream->voice.out, start);
         } else {
-            AUD_set_active_in(s->audio_be, stream->voice.in, start);
+            audio_be_set_active_in(s->audio_be, stream->voice.in, start);
         }
     } else {
         error_report("Invalid stream id: %"PRIu32, stream_id);
@@ -1053,7 +1053,7 @@ static void virtio_snd_realize(DeviceState *dev, Error **errp)
         return;
     }
 
-    if (!AUD_backend_check(&vsnd->audio_be, errp)) {
+    if (!audio_be_backend_check(&vsnd->audio_be, errp)) {
         return;
     }
 
@@ -1135,10 +1135,10 @@ static inline void return_tx_buffer(VirtIOSoundPCMStream *stream,
 }
 
 /*
- * AUD_* output callback.
+ * audio_be_* output callback.
  *
  * @data: VirtIOSoundPCMStream stream
- * @available: number of bytes that can be written with AUD_write()
+ * @available: number of bytes that can be written with audio_be_write()
  */
 static void virtio_snd_pcm_out_cb(void *data, int available)
 {
@@ -1153,7 +1153,7 @@ static void virtio_snd_pcm_out_cb(void *data, int available)
                 return;
             }
             if (!stream->active) {
-                /* Stream has stopped, so do not perform AUD_write. */
+                /* Stream has stopped, so do not perform audio_be_write. */
                 return_tx_buffer(stream, buffer);
                 continue;
             }
@@ -1166,7 +1166,7 @@ static void virtio_snd_pcm_out_cb(void *data, int available)
                 buffer->populated = true;
             }
             for (;;) {
-                size = AUD_write(stream->s->audio_be,
+                size = audio_be_write(stream->s->audio_be,
                                  stream->voice.out,
                                  buffer->data + buffer->offset,
                                  MIN(buffer->size, available));
@@ -1230,10 +1230,10 @@ static inline void return_rx_buffer(VirtIOSoundPCMStream *stream,
 
 
 /*
- * AUD_* input callback.
+ * audio_be_* input callback.
  *
  * @data: VirtIOSoundPCMStream stream
- * @available: number of bytes that can be read with AUD_read()
+ * @available: number of bytes that can be read with audio_be_read()
  */
 static void virtio_snd_pcm_in_cb(void *data, int available)
 {
@@ -1248,7 +1248,7 @@ static void virtio_snd_pcm_in_cb(void *data, int available)
                 return;
             }
             if (!stream->active) {
-                /* Stream has stopped, so do not perform AUD_read. */
+                /* Stream has stopped, so do not perform audio_be_read. */
                 return_rx_buffer(stream, buffer);
                 continue;
             }
@@ -1259,7 +1259,7 @@ static void virtio_snd_pcm_in_cb(void *data, int available)
                     return_rx_buffer(stream, buffer);
                     break;
                 }
-                size = AUD_read(stream->s->audio_be,
+                size = audio_be_read(stream->s->audio_be,
                         stream->voice.in,
                         buffer->data + buffer->size,
                         MIN(available, (stream->params.period_bytes -
diff --git a/hw/audio/wm8750.c b/hw/audio/wm8750.c
index bc347fe537..ee6a212b1f 100644
--- a/hw/audio/wm8750.c
+++ b/hw/audio/wm8750.c
@@ -72,7 +72,7 @@ static inline void wm8750_in_load(WM8750State *s)
     if (s->idx_in + s->req_in <= sizeof(s->data_in))
         return;
     s->idx_in = MAX(0, (int) sizeof(s->data_in) - s->req_in);
-    AUD_read(s->audio_be, *s->in[0], s->data_in + s->idx_in,
+    audio_be_read(s->audio_be, *s->in[0], s->data_in + s->idx_in,
              sizeof(s->data_in) - s->idx_in);
 }
 
@@ -80,7 +80,7 @@ static inline void wm8750_out_flush(WM8750State *s)
 {
     int sent = 0;
     while (sent < s->idx_out)
-        sent += AUD_write(s->audio_be, *s->out[0], s->data_out + sent, s->idx_out - sent)
+        sent += audio_be_write(s->audio_be, *s->out[0], s->data_out + sent, s->idx_out - sent)
                 ?: s->idx_out;
     s->idx_out = 0;
 }
@@ -145,30 +145,30 @@ static void wm8750_vol_update(WM8750State *s)
 {
     /* FIXME: multiply all volumes by s->invol[2], s->invol[3] */
 
-    AUD_set_volume_in_lr(s->audio_be, s->adc_voice[0], s->mute,
+    audio_be_set_volume_in_lr(s->audio_be, s->adc_voice[0], s->mute,
                     s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]),
                     s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1]));
-    AUD_set_volume_in_lr(s->audio_be, s->adc_voice[1], s->mute,
+    audio_be_set_volume_in_lr(s->audio_be, s->adc_voice[1], s->mute,
                     s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]),
                     s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1]));
-    AUD_set_volume_in_lr(s->audio_be, s->adc_voice[2], s->mute,
+    audio_be_set_volume_in_lr(s->audio_be, s->adc_voice[2], s->mute,
                     s->inmute[0] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[0]),
                     s->inmute[1] ? 0 : WM8750_INVOL_TRANSFORM(s->invol[1]));
 
     /* FIXME: multiply all volumes by s->outvol[0], s->outvol[1] */
 
     /* Speaker: LOUT2VOL ROUT2VOL */
-    AUD_set_volume_out_lr(s->audio_be, s->dac_voice[0], s->mute,
+    audio_be_set_volume_out_lr(s->audio_be, s->dac_voice[0], s->mute,
                     s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[4]),
                     s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[5]));
 
     /* Headphone: LOUT1VOL ROUT1VOL */
-    AUD_set_volume_out_lr(s->audio_be, s->dac_voice[1], s->mute,
+    audio_be_set_volume_out_lr(s->audio_be, s->dac_voice[1], s->mute,
                     s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[2]),
                     s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[3]));
 
     /* MONOOUT: MONOVOL MONOVOL */
-    AUD_set_volume_out_lr(s->audio_be, s->dac_voice[2], s->mute,
+    audio_be_set_volume_out_lr(s->audio_be, s->dac_voice[2], s->mute,
                     s->outmute[0] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[6]),
                     s->outmute[1] ? 0 : WM8750_OUTVOL_TRANSFORM(s->outvol[6]));
 }
@@ -182,18 +182,18 @@ static void wm8750_set_format(WM8750State *s)
     wm8750_out_flush(s);
 
     if (s->in[0] && *s->in[0])
-        AUD_set_active_in(s->audio_be, *s->in[0], 0);
+        audio_be_set_active_in(s->audio_be, *s->in[0], 0);
     if (s->out[0] && *s->out[0])
-        AUD_set_active_out(s->audio_be, *s->out[0], 0);
+        audio_be_set_active_out(s->audio_be, *s->out[0], 0);
 
     for (i = 0; i < IN_PORT_N; i ++)
         if (s->adc_voice[i]) {
-            AUD_close_in(s->audio_be, s->adc_voice[i]);
+            audio_be_close_in(s->audio_be, s->adc_voice[i]);
             s->adc_voice[i] = NULL;
         }
     for (i = 0; i < OUT_PORT_N; i ++)
         if (s->dac_voice[i]) {
-            AUD_close_out(s->audio_be, s->dac_voice[i]);
+            audio_be_close_out(s->audio_be, s->dac_voice[i]);
             s->dac_voice[i] = NULL;
         }
 
@@ -206,11 +206,11 @@ static void wm8750_set_format(WM8750State *s)
     in_fmt.freq = s->adc_hz;
     in_fmt.fmt = AUDIO_FORMAT_S16;
 
-    s->adc_voice[0] = AUD_open_in(s->audio_be, s->adc_voice[0],
+    s->adc_voice[0] = audio_be_open_in(s->audio_be, s->adc_voice[0],
                     CODEC ".input1", s, wm8750_audio_in_cb, &in_fmt);
-    s->adc_voice[1] = AUD_open_in(s->audio_be, s->adc_voice[1],
+    s->adc_voice[1] = audio_be_open_in(s->audio_be, s->adc_voice[1],
                     CODEC ".input2", s, wm8750_audio_in_cb, &in_fmt);
-    s->adc_voice[2] = AUD_open_in(s->audio_be, s->adc_voice[2],
+    s->adc_voice[2] = audio_be_open_in(s->audio_be, s->adc_voice[2],
                     CODEC ".input3", s, wm8750_audio_in_cb, &in_fmt);
 
     /* Setup output */
@@ -219,12 +219,12 @@ static void wm8750_set_format(WM8750State *s)
     out_fmt.freq = s->dac_hz;
     out_fmt.fmt = AUDIO_FORMAT_S16;
 
-    s->dac_voice[0] = AUD_open_out(s->audio_be, s->dac_voice[0],
+    s->dac_voice[0] = audio_be_open_out(s->audio_be, s->dac_voice[0],
                     CODEC ".speaker", s, wm8750_audio_out_cb, &out_fmt);
-    s->dac_voice[1] = AUD_open_out(s->audio_be, s->dac_voice[1],
+    s->dac_voice[1] = audio_be_open_out(s->audio_be, s->dac_voice[1],
                     CODEC ".headphone", s, wm8750_audio_out_cb, &out_fmt);
     /* MONOMIX is also in stereo for simplicity */
-    s->dac_voice[2] = AUD_open_out(s->audio_be, s->dac_voice[2],
+    s->dac_voice[2] = audio_be_open_out(s->audio_be, s->dac_voice[2],
                     CODEC ".monomix", s, wm8750_audio_out_cb, &out_fmt);
     /* no sense emulating OUT3 which is a mix of other outputs */
 
@@ -235,9 +235,9 @@ static void wm8750_set_format(WM8750State *s)
      * for mixing or combining paths to different ports, so we
      * connect both channels to where the left channel is routed.  */
     if (s->in[0] && *s->in[0])
-        AUD_set_active_in(s->audio_be, *s->in[0], 1);
+        audio_be_set_active_in(s->audio_be, *s->in[0], 1);
     if (s->out[0] && *s->out[0])
-        AUD_set_active_out(s->audio_be, *s->out[0], 1);
+        audio_be_set_active_out(s->audio_be, *s->out[0], 1);
 }
 
 static void wm8750_clk_update(WM8750State *s, int ext)
@@ -624,7 +624,7 @@ static void wm8750_realize(DeviceState *dev, Error **errp)
 {
     WM8750State *s = WM8750(dev);
 
-    if (!AUD_backend_check(&s->audio_be, errp)) {
+    if (!audio_be_backend_check(&s->audio_be, errp)) {
         return;
     }
 
diff --git a/hw/display/xlnx_dp.c b/hw/display/xlnx_dp.c
index e89f1ffaf6..0e6bd50c9d 100644
--- a/hw/display/xlnx_dp.c
+++ b/hw/display/xlnx_dp.c
@@ -331,7 +331,7 @@ static inline void xlnx_dp_audio_activate(XlnxDPState *s)
 {
     bool activated = ((s->core_registers[DP_TX_AUDIO_CONTROL]
                    & DP_TX_AUD_CTRL) != 0);
-    AUD_set_active_out(s->audio_be, s->amixer_output_stream, activated);
+    audio_be_set_active_out(s->audio_be, s->amixer_output_stream, activated);
     xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(0),
                                       &s->audio_buffer_0);
     xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(1),
@@ -401,7 +401,7 @@ static void xlnx_dp_audio_callback(void *opaque, int avail)
     /* Send the buffer through the audio. */
     if (s->byte_left <= MAX_QEMU_BUFFER_SIZE) {
         if (s->byte_left != 0) {
-            written = AUD_write(s->audio_be, s->amixer_output_stream,
+            written = audio_be_write(s->audio_be, s->amixer_output_stream,
                                 &s->out_buffer[s->data_ptr], s->byte_left);
         } else {
              int len_to_copy;
@@ -413,12 +413,12 @@ static void xlnx_dp_audio_callback(void *opaque, int avail)
             while (avail) {
                 len_to_copy = MIN(AUD_CHBUF_MAX_DEPTH, avail);
                 memset(s->out_buffer, 0, len_to_copy);
-                avail -= AUD_write(s->audio_be, s->amixer_output_stream, s->out_buffer,
+                avail -= audio_be_write(s->audio_be, s->amixer_output_stream, s->out_buffer,
                                    len_to_copy);
             }
         }
     } else {
-        written = AUD_write(s->audio_be, s->amixer_output_stream,
+        written = audio_be_write(s->audio_be, s->amixer_output_stream,
                             &s->out_buffer[s->data_ptr], MAX_QEMU_BUFFER_SIZE);
     }
     s->byte_left -= written;
@@ -1373,7 +1373,7 @@ static void xlnx_dp_realize(DeviceState *dev, Error **errp)
     DisplaySurface *surface;
     struct audsettings as;
 
-    if (!AUD_backend_check(&s->audio_be, errp)) {
+    if (!audio_be_backend_check(&s->audio_be, errp)) {
         return;
     }
 
@@ -1395,13 +1395,13 @@ static void xlnx_dp_realize(DeviceState *dev, Error **errp)
     as.fmt = AUDIO_FORMAT_S16;
     as.endianness = 0;
 
-    s->amixer_output_stream = AUD_open_out(s->audio_be,
+    s->amixer_output_stream = audio_be_open_out(s->audio_be,
                                            s->amixer_output_stream,
                                            "xlnx_dp.audio.out",
                                            s,
                                            xlnx_dp_audio_callback,
                                            &as);
-    AUD_set_volume_out_lr(s->audio_be, s->amixer_output_stream, 0, 255, 255);
+    audio_be_set_volume_out_lr(s->audio_be, s->amixer_output_stream, 0, 255, 255);
     xlnx_dp_audio_activate(s);
     s->vblank = ptimer_init(vblank_hit, s, DP_VBLANK_PTIMER_POLICY);
     ptimer_transaction_begin(s->vblank);
diff --git a/hw/usb/dev-audio.c b/hw/usb/dev-audio.c
index 189fd1df26..42aadcc4d8 100644
--- a/hw/usb/dev-audio.c
+++ b/hw/usb/dev-audio.c
@@ -669,7 +669,7 @@ static void output_callback(void *opaque, int avail)
             return;
         }
 
-        written = AUD_write(s->audio_be, s->out.voice, data, len);
+        written = audio_be_write(s->audio_be, s->out.voice, data, len);
         avail -= written;
         s->out.buf.cons += written;
 
@@ -683,7 +683,7 @@ static int usb_audio_set_output_altset(USBAudioState *s, int altset)
 {
     switch (altset) {
     case ALTSET_OFF:
-        AUD_set_active_out(s->audio_be, s->out.voice, false);
+        audio_be_set_active_out(s->audio_be, s->out.voice, false);
         break;
     case ALTSET_STEREO:
     case ALTSET_51:
@@ -692,7 +692,7 @@ static int usb_audio_set_output_altset(USBAudioState *s, int altset)
             usb_audio_reinit(USB_DEVICE(s), altset_channels[altset]);
         }
         streambuf_init(&s->out.buf, s->buffer, s->out.channels);
-        AUD_set_active_out(s->audio_be, s->out.voice, true);
+        audio_be_set_active_out(s->audio_be, s->out.voice, true);
         break;
     default:
         return -1;
@@ -805,7 +805,7 @@ static int usb_audio_set_control(USBAudioState *s, uint8_t attrib,
             }
             fprintf(stderr, "\n");
         }
-        AUD_set_volume_out(s->audio_be, s->out.voice, &s->out.vol);
+        audio_be_set_volume_out(s->audio_be, s->out.voice, &s->out.vol);
     }
 
     return ret;
@@ -931,7 +931,7 @@ static void usb_audio_unrealize(USBDevice *dev)
     }
 
     usb_audio_set_output_altset(s, ALTSET_OFF);
-    AUD_close_out(s->audio_be, s->out.voice);
+    audio_be_close_out(s->audio_be, s->out.voice);
 
     streambuf_fini(&s->out.buf);
 }
@@ -941,7 +941,7 @@ static void usb_audio_realize(USBDevice *dev, Error **errp)
     USBAudioState *s = USB_AUDIO(dev);
     int i;
 
-    if (!AUD_backend_check(&s->audio_be, errp)) {
+    if (!audio_be_backend_check(&s->audio_be, errp)) {
         return;
     }
 
@@ -978,10 +978,10 @@ static void usb_audio_reinit(USBDevice *dev, unsigned channels)
     s->out.as.endianness = 0;
     streambuf_init(&s->out.buf, s->buffer, s->out.channels);
 
-    s->out.voice = AUD_open_out(s->audio_be, s->out.voice, TYPE_USB_AUDIO,
+    s->out.voice = audio_be_open_out(s->audio_be, s->out.voice, TYPE_USB_AUDIO,
                                 s, output_callback, &s->out.as);
-    AUD_set_volume_out(s->audio_be, s->out.voice, &s->out.vol);
-    AUD_set_active_out(s->audio_be, s->out.voice, 0);
+    audio_be_set_volume_out(s->audio_be, s->out.voice, &s->out.vol);
+    audio_be_set_active_out(s->audio_be, s->out.voice, 0);
 }
 
 static const VMStateDescription vmstate_usb_audio = {
diff --git a/ui/vnc.c b/ui/vnc.c
index 5c8af65202..ee337d729a 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -1277,7 +1277,7 @@ static void audio_add(VncState *vs)
     ops.destroy = audio_capture_destroy;
     ops.capture = audio_capture;
 
-    vs->audio_cap = AUD_add_capture(vs->vd->audio_be, &vs->as, &ops, vs);
+    vs->audio_cap = audio_be_add_capture(vs->vd->audio_be, &vs->as, &ops, vs);
     if (!vs->audio_cap) {
         error_report("Failed to add audio capture");
     }
@@ -1286,7 +1286,7 @@ static void audio_add(VncState *vs)
 static void audio_del(VncState *vs)
 {
     if (vs->audio_cap) {
-        AUD_del_capture(vs->vd->audio_be, vs->audio_cap, vs);
+        audio_be_del_capture(vs->vd->audio_be, vs->audio_cap, vs);
         vs->audio_cap = NULL;
     }
 }
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 21/24] audio-be: add common pre-conditions
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (19 preceding siblings ...)
  2025-12-01 11:23 ` [RFC 20/24] audio: AUD_ -> audio_be_ marcandre.lureau
@ 2025-12-01 11:23 ` marcandre.lureau
  2025-12-01 11:23 ` [RFC 22/24] audio-be: add some state trace marcandre.lureau
                   ` (3 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:23 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

assert() on valid values, and handle acceptable NULL arguments
gracefully.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio-be.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 61 insertions(+)

diff --git a/audio/audio-be.c b/audio/audio-be.c
index 9af4dbe5e9..fde9c20e12 100644
--- a/audio/audio-be.c
+++ b/audio/audio-be.c
@@ -29,6 +29,10 @@ SWVoiceIn *audio_be_open_in(
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    assert(name != NULL);
+    assert(callback_fn != NULL);
+    assert(as != NULL);
+
     return klass->open_in(be, sw, name, callback_opaque, callback_fn, as);
 }
 
@@ -42,6 +46,10 @@ SWVoiceOut *audio_be_open_out(
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    assert(name != NULL);
+    assert(callback_fn != NULL);
+    assert(as != NULL);
+
     return klass->open_out(be, sw, name, callback_opaque, callback_fn, as);
 }
 
@@ -49,6 +57,10 @@ void audio_be_close_out(AudioBackend *be, SWVoiceOut *sw)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    if (!sw) {
+        return;
+    }
+
     return klass->close_out(be, sw);
 }
 
@@ -56,6 +68,10 @@ void audio_be_close_in(AudioBackend *be, SWVoiceIn *sw)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    if (!sw) {
+        return;
+    }
+
     return klass->close_in(be, sw);
 }
 
@@ -63,6 +79,10 @@ bool audio_be_is_active_out(AudioBackend *be, SWVoiceOut *sw)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    if (!sw) {
+        return false;
+    }
+
     return klass->is_active_out(be, sw);
 }
 
@@ -70,6 +90,10 @@ bool audio_be_is_active_in(AudioBackend *be, SWVoiceIn *sw)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    if (!sw) {
+        return false;
+    }
+
     return klass->is_active_in(be, sw);
 }
 
@@ -77,6 +101,10 @@ size_t audio_be_write(AudioBackend *be, SWVoiceOut *sw, void *buf, size_t size)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    if (!sw) {
+        return 0;
+    }
+
     return klass->write(be, sw, buf, size);
 }
 
@@ -84,6 +112,10 @@ size_t audio_be_read(AudioBackend *be, SWVoiceIn *sw, void *buf, size_t size)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    if (!sw) {
+        return 0;
+    }
+
     return klass->read(be, sw, buf, size);
 }
 
@@ -91,6 +123,10 @@ int audio_be_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    if (!sw) {
+        return 0;
+    }
+
     return klass->get_buffer_size_out(be, sw);
 }
 
@@ -98,6 +134,10 @@ void audio_be_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    if (!sw) {
+        return;
+    }
+
     return klass->set_active_out(be, sw, on);
 }
 
@@ -105,6 +145,10 @@ void audio_be_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    if (!sw) {
+        return;
+    }
+
     return klass->set_active_in(be, sw, on);
 }
 
@@ -112,6 +156,10 @@ void audio_be_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    if (!sw) {
+        return;
+    }
+
     klass->set_volume_out(be, sw, vol);
 }
 
@@ -119,6 +167,10 @@ void audio_be_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    if (!sw) {
+        return;
+    }
+
     klass->set_volume_in(be, sw, vol);
 }
 
@@ -130,6 +182,9 @@ CaptureVoiceOut *audio_be_add_capture(
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    assert(as != NULL);
+    assert(ops != NULL);
+
     return klass->add_capture(be, as, ops, cb_opaque);
 }
 
@@ -137,6 +192,10 @@ void audio_be_del_capture(AudioBackend *be, CaptureVoiceOut *cap, void *cb_opaqu
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    if (!cap) {
+        return;
+    }
+
     klass->del_capture(be, cap, cb_opaque);
 }
 
@@ -155,6 +214,8 @@ bool audio_be_set_dbus_server(AudioBackend *be,
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    assert(server != NULL);
+
     if (!audio_be_can_set_dbus_server(be)) {
         error_setg(errp, "Audiodev '%s' is not compatible with DBus",
                    audio_be_get_id(be));
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 22/24] audio-be: add some state trace
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (20 preceding siblings ...)
  2025-12-01 11:23 ` [RFC 21/24] audio-be: add common pre-conditions marcandre.lureau
@ 2025-12-01 11:23 ` marcandre.lureau
  2025-12-01 11:23 ` [RFC 23/24] audio: split AudioDriver code in audio-driver.c marcandre.lureau
                   ` (2 subsequent siblings)
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:23 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 audio/audio-be.c   | 9 +++++++--
 audio/trace-events | 4 ++++
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/audio/audio-be.c b/audio/audio-be.c
index fde9c20e12..7b141566bc 100644
--- a/audio/audio-be.c
+++ b/audio/audio-be.c
@@ -1,9 +1,10 @@
 /* SPDX-License-Identifier: MIT */
 
 #include "qemu/osdep.h"
-#include "qemu/audio.h"
-#include "qemu/audio-capture.h"
 #include "qapi/error.h"
+#include "qemu/audio-capture.h"
+#include "qemu/audio.h"
+#include "trace-audio.h"
 
 bool audio_be_backend_check(AudioBackend **be, Error **errp)
 {
@@ -134,6 +135,8 @@ void audio_be_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    trace_audio_be_set_active_out(sw, on);
+
     if (!sw) {
         return;
     }
@@ -145,6 +148,8 @@ void audio_be_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
 {
     AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
 
+    trace_audio_be_set_active_in(sw, on);
+
     if (!sw) {
         return;
     }
diff --git a/audio/trace-events b/audio/trace-events
index 7e3f1593c8..f7f639d960 100644
--- a/audio/trace-events
+++ b/audio/trace-events
@@ -26,6 +26,10 @@ pw_vol(const char *ret) "set volume: %s"
 pw_period(uint64_t quantum, uint32_t rate) "period =%" PRIu64 "/%u"
 pw_audio_init(void) "Initialize PipeWire context"
 
+# audio-be.c
+audio_be_set_active_in(void *sw, bool on) "sw=%p, on=%d"
+audio_be_set_active_out(void *sw, bool on) "sw=%p, on=%d"
+
 # audio.c
 audio_timer_start(int interval) "interval %d ms"
 audio_timer_stop(void) ""
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 23/24] audio: split AudioDriver code in audio-driver.c
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (21 preceding siblings ...)
  2025-12-01 11:23 ` [RFC 22/24] audio-be: add some state trace marcandre.lureau
@ 2025-12-01 11:23 ` marcandre.lureau
  2025-12-01 11:23 ` [RFC 24/24] WIP: rust/audio: add GStreamer backend marcandre.lureau
  2025-12-01 13:02 ` [RFC 00/24] audio: " BALATON Zoltan
  24 siblings, 0 replies; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:23 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Allow to build the audio base infra without the rest of QEMU
resampling/mixing/queuing code.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 ...dio_template.h => audio-driver_template.h} |    0
 audio/audio_int.h                             |    2 -
 include/qemu/audio.h                          |    2 +
 audio/audio-driver.c                          | 1988 +++++++++++++++
 audio/audio.c                                 | 2218 ++---------------
 audio/meson.build                             |    1 +
 6 files changed, 2167 insertions(+), 2044 deletions(-)
 rename audio/{audio_template.h => audio-driver_template.h} (100%)
 create mode 100644 audio/audio-driver.c

diff --git a/audio/audio_template.h b/audio/audio-driver_template.h
similarity index 100%
rename from audio/audio_template.h
rename to audio/audio-driver_template.h
diff --git a/audio/audio_int.h b/audio/audio_int.h
index 79e54fd60a..e19ca96b01 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -273,8 +273,6 @@ int audio_bug (const char *funcname, int cond);
 
 void audio_run(AudioDriver *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 1794702c30..414dd89f9c 100644
--- a/include/qemu/audio.h
+++ b/include/qemu/audio.h
@@ -188,6 +188,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-driver.c b/audio/audio-driver.c
new file mode 100644
index 0000000000..65e2bf2f38
--- /dev/null
+++ b/audio/audio-driver.c
@@ -0,0 +1,1988 @@
+/*
+ * 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 "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.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"
+
+
+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 (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 (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, 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, 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(AudioDriver *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)
+{
+    AudioDriver *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-driver_template.h"
+#undef DAC
+#include "audio-driver_template.h"
+
+/*
+ * Timer
+ */
+static int audio_is_timer_needed(AudioDriver *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(AudioDriver *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;
+    AudioDriver *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_driver_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_driver_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_driver_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_driver_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
+{
+    HWVoiceOut *hw;
+
+    if (!sw) {
+        return;
+    }
+
+    hw = sw->hw;
+    if (sw->active != on) {
+        AudioDriver *s = sw->s;
+        SWVoiceOut *temp_sw;
+        SWVoiceCap *sc;
+
+        if (on) {
+            hw->pending_disable = 0;
+            if (!hw->enabled) {
+                hw->enabled = true;
+                if (s->vm_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_driver_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
+{
+    HWVoiceIn *hw;
+
+    if (!sw) {
+        return;
+    }
+
+    hw = sw->hw;
+    if (sw->active != on) {
+        AudioDriver *s = sw->s;
+        SWVoiceIn *temp_sw;
+
+        if (on) {
+            if (!hw->enabled) {
+                hw->enabled = true;
+                if (s->vm_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(AudioDriver *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) {
+            hw->ts_helper += 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(AudioDriver *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;
+        hw->ts_helper += captured;
+
+        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(AudioDriver *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(AudioDriver *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_be_driver_realize(AudioBackend *abe, Audiodev *dev, Error **errp)
+{
+    AudioDriver *d = AUDIO_DRIVER(abe);
+    audio_driver *drv = AUDIO_DRIVER_GET_CLASS(d)->driver;
+
+    d->dev = dev;
+    d->drv_opaque = drv->init(d->dev, errp);
+    if (!d->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(d, drv, 1);
+    audio_init_nb_voices_in(d, drv, 0);
+    d->drv = drv;
+
+    if (d->dev->timer_period <= 0) {
+        d->period_ticks = 1;
+    } else {
+        d->period_ticks = d->dev->timer_period * (int64_t)SCALE_US;
+    }
+
+    return true;
+}
+
+static void audio_vm_change_state_handler (void *opaque, bool running,
+                                           RunState state)
+{
+    AudioDriver *s = opaque;
+    HWVoiceOut *hwo = NULL;
+    HWVoiceIn *hwi = NULL;
+
+    s->vm_running = running;
+    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_driver_get_id(AudioBackend *be)
+{
+    return AUDIO_DRIVER(be)->dev->id;
+}
+
+static CaptureVoiceOut *audio_driver_add_capture(
+    AudioBackend *be,
+    struct audsettings *as,
+    struct audio_capture_ops *ops,
+    void *cb_opaque);
+
+static void audio_driver_del_capture(
+    AudioBackend *be,
+    CaptureVoiceOut *cap,
+    void *cb_opaque);
+
+static void audio_driver_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol);
+static void audio_driver_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol);
+
+static void audio_driver_class_init(ObjectClass *klass, const void *data)
+{
+    AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
+
+    be->realize = audio_be_driver_realize;
+    be->get_id = audio_driver_get_id;
+    be->open_in = audio_driver_open_in;
+    be->open_out = audio_driver_open_out;
+    be->close_in = audio_driver_close_in;
+    be->close_out = audio_driver_close_out;
+    be->is_active_out = audio_driver_is_active_out;
+    be->is_active_in = audio_driver_is_active_in;
+    be->set_active_out = audio_driver_set_active_out;
+    be->set_active_in = audio_driver_set_active_in;
+    be->set_volume_out = audio_driver_set_volume_out;
+    be->set_volume_in = audio_driver_set_volume_in;
+    be->read = audio_driver_read;
+    be->write = audio_driver_write;
+    be->get_buffer_size_out = audio_driver_get_buffer_size_out;
+    be->add_capture = audio_driver_add_capture;
+    be->del_capture = audio_driver_del_capture;
+}
+
+static void audio_driver_init(Object *obj)
+{
+    AudioDriver *s = AUDIO_DRIVER(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_driver_finalize(Object *obj)
+{
+    AudioDriver *s = AUDIO_DRIVER(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_driver_add_capture(
+    AudioBackend *be,
+    struct audsettings *as,
+    struct audio_capture_ops *ops,
+    void *cb_opaque)
+{
+    AudioDriver *s = AUDIO_DRIVER(be);
+    CaptureVoiceOut *cap;
+    struct capture_callback *cb;
+
+    if (!s) {
+        /* TODO: implement an interface instead (or drop capture support) */
+        error_report("Capturing without setting an audiodev driver 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_driver_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_driver_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_driver_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_driver_info = {
+    .name = TYPE_AUDIO_DRIVER,
+    .parent = TYPE_AUDIO_BACKEND,
+    .instance_size = sizeof(AudioDriver),
+    .instance_init = audio_driver_init,
+    .instance_finalize = audio_driver_finalize,
+    .abstract = false,
+    .class_size = sizeof(AudioDriverClass),
+    .class_init = audio_driver_class_init,
+};
+
+static void register_types(void)
+{
+    type_register_static(&audio_driver_info);
+}
+
+type_init(register_types);
diff --git a/audio/audio.c b/audio/audio.c
index da2a4da125..ccb16ae3b2 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1,1747 +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 (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 (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, 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, 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(AudioDriver *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)
-{
-    AudioDriver *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(AudioDriver *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(AudioDriver *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;
-    AudioDriver *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_driver_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_driver_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_driver_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_driver_set_active_out(AudioBackend *be, SWVoiceOut *sw, bool on)
-{
-    HWVoiceOut *hw;
-
-    if (!sw) {
-        return;
-    }
-
-    hw = sw->hw;
-    if (sw->active != on) {
-        AudioDriver *s = sw->s;
-        SWVoiceOut *temp_sw;
-        SWVoiceCap *sc;
-
-        if (on) {
-            hw->pending_disable = 0;
-            if (!hw->enabled) {
-                hw->enabled = true;
-                if (s->vm_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_driver_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)
-{
-    HWVoiceIn *hw;
-
-    if (!sw) {
-        return;
-    }
-
-    hw = sw->hw;
-    if (sw->active != on) {
-        AudioDriver *s = sw->s;
-        SWVoiceIn *temp_sw;
-
-        if (on) {
-            if (!hw->enabled) {
-                hw->enabled = true;
-                if (s->vm_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(AudioDriver *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) {
-            hw->ts_helper += 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(AudioDriver *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;
-        hw->ts_helper += captured;
-
-        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(AudioDriver *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(AudioDriver *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_be_driver_realize(AudioBackend *abe, Audiodev *dev, Error **errp)
-{
-    AudioDriver *d = AUDIO_DRIVER(abe);
-    audio_driver *drv = AUDIO_DRIVER_GET_CLASS(d)->driver;
-
-    d->dev = dev;
-    d->drv_opaque = drv->init(d->dev, errp);
-    if (!d->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(d, drv, 1);
-    audio_init_nb_voices_in(d, drv, 0);
-    d->drv = drv;
-
-    if (d->dev->timer_period <= 0) {
-        d->period_ticks = 1;
-    } else {
-        d->period_ticks = d->dev->timer_period * (int64_t)SCALE_US;
-    }
-
-    return true;
-}
-
-static void audio_vm_change_state_handler (void *opaque, bool running,
-                                           RunState state)
-{
-    AudioDriver *s = opaque;
-    HWVoiceOut *hwo = NULL;
-    HWVoiceIn *hwi = NULL;
-
-    s->vm_running = running;
-    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_driver_get_id(AudioBackend *be)
-{
-    return AUDIO_DRIVER(be)->dev->id;
-}
-
-static CaptureVoiceOut *audio_driver_add_capture(
-    AudioBackend *be,
-    struct audsettings *as,
-    struct audio_capture_ops *ops,
-    void *cb_opaque);
-
-static void audio_driver_del_capture(
-    AudioBackend *be,
-    CaptureVoiceOut *cap,
-    void *cb_opaque);
-
-static void audio_driver_set_volume_out(AudioBackend *be, SWVoiceOut *sw, Volume *vol);
-static void audio_driver_set_volume_in(AudioBackend *be, SWVoiceIn *sw, Volume *vol);
-
-static void audio_driver_class_init(ObjectClass *klass, const void *data)
-{
-    AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
-
-    be->realize = audio_be_driver_realize;
-    be->get_id = audio_driver_get_id;
-    be->open_in = audio_driver_open_in;
-    be->open_out = audio_driver_open_out;
-    be->close_in = audio_driver_close_in;
-    be->close_out = audio_driver_close_out;
-    be->is_active_out = audio_driver_is_active_out;
-    be->is_active_in = audio_driver_is_active_in;
-    be->set_active_out = audio_driver_set_active_out;
-    be->set_active_in = audio_driver_set_active_in;
-    be->set_volume_out = audio_driver_set_volume_out;
-    be->set_volume_in = audio_driver_set_volume_in;
-    be->read = audio_driver_read;
-    be->write = audio_driver_write;
-    be->get_buffer_size_out = audio_driver_get_buffer_size_out;
-    be->add_capture = audio_driver_add_capture;
-    be->del_capture = audio_driver_del_capture;
-}
-
-static void audio_driver_init(Object *obj)
-{
-    AudioDriver *s = AUDIO_DRIVER(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);
-}
+/* SPDX-License-Identifier: MIT */
 
-static void audio_driver_finalize(Object *obj)
-{
-    AudioDriver *s = AUDIO_DRIVER(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;
+#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");
@@ -1754,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++) {
@@ -1850,155 +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_driver_add_capture(
-    AudioBackend *be,
-    struct audsettings *as,
-    struct audio_capture_ops *ops,
-    void *cb_opaque)
-{
-    AudioDriver *s = AUDIO_DRIVER(be);
-    CaptureVoiceOut *cap;
-    struct capture_callback *cb;
-
-    if (!s) {
-        /* TODO: implement an interface instead (or drop capture support) */
-        error_report("Capturing without setting an audiodev driver 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_driver_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_driver_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_driver_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)
@@ -2097,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;
@@ -2121,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;
@@ -2183,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);
@@ -2262,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;
@@ -2316,21 +468,3 @@ AudiodevList *qmp_query_audiodevs(Error **errp)
     }
     return ret;
 }
-
-static const TypeInfo audio_driver_info = {
-    .name = TYPE_AUDIO_DRIVER,
-    .parent = TYPE_AUDIO_BACKEND,
-    .instance_size = sizeof(AudioDriver),
-    .instance_init = audio_driver_init,
-    .instance_finalize = audio_driver_finalize,
-    .abstract = false,
-    .class_size = sizeof(AudioDriverClass),
-    .class_init = audio_driver_class_init,
-};
-
-static void register_types(void)
-{
-    type_register_static(&audio_driver_info);
-}
-
-type_init(register_types);
diff --git a/audio/meson.build b/audio/meson.build
index 2450098eb8..5586641d82 100644
--- a/audio/meson.build
+++ b/audio/meson.build
@@ -1,6 +1,7 @@
 system_ss.add(files(
   'audio.c',
   'audio-be.c',
+  'audio-driver.c',
   'mixeng.c',
   'noaudio.c',
   'wavaudio.c',
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* [RFC 24/24] WIP: rust/audio: add GStreamer backend
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (22 preceding siblings ...)
  2025-12-01 11:23 ` [RFC 23/24] audio: split AudioDriver code in audio-driver.c marcandre.lureau
@ 2025-12-01 11:23 ` marcandre.lureau
  2025-12-01 13:12   ` Markus Armbruster
  2025-12-01 13:02 ` [RFC 00/24] audio: " BALATON Zoltan
  24 siblings, 1 reply; 51+ messages in thread
From: marcandre.lureau @ 2025-12-01 11:23 UTC (permalink / raw)
  To: qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Akihiko Odaki, Alexandre Ratchov,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, Marc-André Lureau, Eric Blake,
	Markus Armbruster, Manos Pitsidianakis,
	open list:Rust-related patc...

From: Marc-André Lureau <marcandre.lureau@redhat.com>

This patch introduce a rust/audio crate that replaces QEMU audio/
mixing/resampling code with GStreamer and Rust. It could potentially
remove the need for all the system-specific audio API implementation,
since GStreamer has audio elements for
ALSA/Pipewire/PulseAudio/jack/OSX/WASAPI etc (removing ~10k loc).

TODO:
- test on various system, with various configuration to see if this
  backend can replace the other QEMU audio backends
- add a spicesink/spicesrc to handle spice, or rewrite spice to use
  the capture approach used by VNC code. Or drop capture support, and
  use custom qemusrc/qemusink for both Spice and VNC, lowering the feature
  and behaviour disparity.
- build-sys: make gstreamer optional
- build-sys: loadable module support
- investigate dropping get_buffer_size_out()
- investigate improving emulated devices to not require regular
  timers (appsrc need-data is called once)
- add generic audio backend tests
- more tests for the mixing/liveadder behaviour (synchronization)
- other: replace audio/dbus with a rust implementation (not using gstreamer)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/audio.json               |   29 +
 audio/audio-driver_template.h |    2 +
 rust/audio/wrapper.h          |   27 +
 audio/audio.c                 |    5 +
 Cargo.lock                    |  572 ++++++++++++++++--
 Cargo.toml                    |    6 +
 audio/trace-events            |    5 +
 rust/audio/Cargo.toml         |   29 +
 rust/audio/build.rs           |   49 ++
 rust/audio/meson.build        |   75 +++
 rust/audio/src/audio.rs       |  516 ++++++++++++++++
 rust/audio/src/bindings.rs    |   32 +
 rust/audio/src/gstreamer.rs   | 1070 +++++++++++++++++++++++++++++++++
 rust/audio/src/lib.rs         |   99 +++
 rust/meson.build              |    6 +
 15 files changed, 2467 insertions(+), 55 deletions(-)
 create mode 100644 rust/audio/wrapper.h
 create mode 100644 rust/audio/Cargo.toml
 create mode 100644 rust/audio/build.rs
 create mode 100644 rust/audio/meson.build
 create mode 100644 rust/audio/src/audio.rs
 create mode 100644 rust/audio/src/bindings.rs
 create mode 100644 rust/audio/src/gstreamer.rs
 create mode 100644 rust/audio/src/lib.rs

diff --git a/qapi/audio.json b/qapi/audio.json
index 2df87b9710..76dc7cbfa6 100644
--- a/qapi/audio.json
+++ b/qapi/audio.json
@@ -128,6 +128,33 @@
     '*out':       'AudiodevAlsaPerDirectionOptions',
     '*threshold': 'uint32' } }
 
+    ##
+    # @AudiodevGStreamerOptions:
+    #
+    # Options of the GStreamer audio backend.
+    #
+    # @in: options of the capture stream
+    #
+    # @out: options of the playback stream
+    #
+    # @sink: the name of the GStreamer sink element to use
+    #        (default 'autoaudiosink')
+    #
+    # @source: the name of the GStreamer source element to use
+    #        (default 'autoaudiosrc')
+    #
+    # Since: 11.0
+    ##
+    { 'struct': 'AudiodevGStreamerOptions',
+      'data': {
+        '*in':        'AudiodevPerDirectionOptions',
+        '*out':       'AudiodevPerDirectionOptions',
+        '*sink':      'str',
+        '*source':    'str'
+      }
+    }
+
+
 ##
 # @AudiodevSndioOptions:
 #
@@ -484,6 +511,7 @@
             { 'name': 'sdl', 'if': 'CONFIG_AUDIO_SDL' },
             { 'name': 'sndio', 'if': 'CONFIG_AUDIO_SNDIO' },
             { 'name': 'spice', 'if': 'CONFIG_SPICE' },
+            { 'name': 'gstreamer' },
             'wav' ] }
 
 ##
@@ -530,6 +558,7 @@
                    'if': 'CONFIG_AUDIO_SNDIO' },
     'spice':     { 'type': 'AudiodevGenericOptions',
                    'if': 'CONFIG_SPICE' },
+    'gstreamer': { 'type': 'AudiodevGStreamerOptions' },
     'wav':       'AudiodevWavOptions' } }
 
 ##
diff --git a/audio/audio-driver_template.h b/audio/audio-driver_template.h
index 40d1ad9dea..aa2451ac7f 100644
--- a/audio/audio-driver_template.h
+++ b/audio/audio-driver_template.h
@@ -391,6 +391,8 @@ AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev)
     case AUDIODEV_DRIVER_SPICE:
         return dev->u.spice.TYPE;
 #endif
+    case AUDIODEV_DRIVER_GSTREAMER:
+        abort();
     case AUDIODEV_DRIVER_WAV:
         return dev->u.wav.TYPE;
 
diff --git a/rust/audio/wrapper.h b/rust/audio/wrapper.h
new file mode 100644
index 0000000000..a7960d0acc
--- /dev/null
+++ b/rust/audio/wrapper.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/*
+ * This header file is meant to be used as input to the `bindgen` application
+ * in order to generate C FFI compatible Rust bindings.
+ */
+
+#ifndef __CLANG_STDATOMIC_H
+#define __CLANG_STDATOMIC_H
+/*
+ * Fix potential missing stdatomic.h error in case bindgen does not insert the
+ * correct libclang header paths on its own. We do not use stdatomic.h symbols
+ * in QEMU code, so it's fine to declare dummy types instead.
+ */
+typedef enum memory_order {
+  memory_order_relaxed,
+  memory_order_consume,
+  memory_order_acquire,
+  memory_order_release,
+  memory_order_acq_rel,
+  memory_order_seq_cst,
+} memory_order;
+#endif /* __CLANG_STDATOMIC_H */
+
+#include "qemu/osdep.h"
+
+#include "qemu/audio.h"
diff --git a/audio/audio.c b/audio/audio.c
index ccb16ae3b2..5a11fe60db 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -219,6 +219,7 @@ static void audio_create_pdos(Audiodev *dev)
 #ifdef CONFIG_SPICE
         CASE(SPICE, spice, );
 #endif
+        CASE(GSTREAMER, gstreamer, );
         CASE(WAV, wav, );
 
     case AUDIODEV_DRIVER__MAX:
@@ -316,6 +317,8 @@ static AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev)
     case AUDIODEV_DRIVER_SPICE:
         return dev->u.spice.out;
 #endif
+    case AUDIODEV_DRIVER_GSTREAMER:
+        return dev->u.gstreamer.out;
     case AUDIODEV_DRIVER_WAV:
         return dev->u.wav.out;
 
@@ -375,6 +378,8 @@ static AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev)
     case AUDIODEV_DRIVER_SPICE:
         return dev->u.spice.in;
 #endif
+    case AUDIODEV_DRIVER_GSTREAMER:
+        return dev->u.gstreamer.in;
     case AUDIODEV_DRIVER_WAV:
         return dev->u.wav.in;
 
diff --git a/Cargo.lock b/Cargo.lock
index e6102b258b..274dd1be4a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,15 +4,21 @@ version = 4
 
 [[package]]
 name = "anyhow"
-version = "1.0.98"
+version = "1.0.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
+checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
 
 [[package]]
 name = "arbitrary-int"
-version = "1.2.7"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c84fc003e338a6f69fbd4f7fe9f92b535ff13e9af8997f3b14b6ddff8b1df46d"
+checksum = "825297538d77367557b912770ca3083f778a196054b3ee63b22673c4a3cae0a5"
+
+[[package]]
+name = "atomic_refcell"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c"
 
 [[package]]
 name = "attrs"
@@ -24,6 +30,29 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "audio"
+version = "0.1.0"
+dependencies = [
+ "bql",
+ "common",
+ "futures",
+ "gio-sys",
+ "glib-sys",
+ "gstreamer-app",
+ "gstreamer-audio",
+ "qom",
+ "system",
+ "trace",
+ "util",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
 [[package]]
 name = "bilge"
 version = "0.2.0"
@@ -40,13 +69,19 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "feb11e002038ad243af39c2068c8a72bcf147acf05025dcdb916fcc000adb2d8"
 dependencies = [
- "itertools",
+ "itertools 0.11.0",
  "proc-macro-error",
  "proc-macro2",
  "quote",
  "syn",
 ]
 
+[[package]]
+name = "bitflags"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+
 [[package]]
 name = "bits"
 version = "0.1.0"
@@ -63,14 +98,20 @@ dependencies = [
 
 [[package]]
 name = "cfg-expr"
-version = "0.20.3"
+version = "0.20.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a2c5f3bf25ec225351aa1c8e230d04d880d3bd89dea133537dafad4ae291e5c"
+checksum = "9acd0bdbbf4b2612d09f52ba61da432140cb10930354079d0d53fafc12968726"
 dependencies = [
  "smallvec",
  "target-lexicon",
 ]
 
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
 [[package]]
 name = "chardev"
 version = "0.1.0"
@@ -93,9 +134,9 @@ dependencies = [
 
 [[package]]
 name = "either"
-version = "1.12.0"
+version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
 
 [[package]]
 name = "equivalent"
@@ -105,13 +146,149 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
 
 [[package]]
 name = "foreign"
-version = "0.3.1"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17ca1b5be8c9d320daf386f1809c7acc0cb09accbae795c2001953fa50585846"
+checksum = "bec05eb9c07a3f66653535e5e50eb5eb935eb00d3bb7e06ea26a9d3a1d016182"
 dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "futures"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+ "windows-sys",
+]
+
+[[package]]
+name = "glib"
+version = "0.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b9dbecb1c33e483a98be4acfea2ab369e1c28f517c6eadb674537409c25c4b2"
+dependencies = [
+ "bitflags",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "futures-util",
+ "gio-sys",
+ "glib-macros",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "memchr",
+ "smallvec",
+]
+
+[[package]]
+name = "glib-macros"
+version = "0.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "880e524e0085f3546cfb38532b2c202c0d64741d9977a6e4aa24704bfc9f19fb"
+dependencies = [
+ "heck",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "glib-sys"
 version = "0.21.2"
@@ -122,11 +299,144 @@ dependencies = [
  "system-deps",
 ]
 
+[[package]]
+name = "gobject-sys"
+version = "0.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gstreamer"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69ac2f12970a2f85a681d2ceaa40c32fe86cc202ead315e0dfa2223a1217cd24"
+dependencies = [
+ "cfg-if",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "glib",
+ "gstreamer-sys",
+ "itertools 0.14.0",
+ "kstring",
+ "libc",
+ "muldiv",
+ "num-integer",
+ "num-rational",
+ "option-operations",
+ "pastey",
+ "pin-project-lite",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "gstreamer-app"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0af5d403738faf03494dfd502d223444b4b44feb997ba28ab3f118ee6d40a0b2"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "glib",
+ "gstreamer",
+ "gstreamer-app-sys",
+ "gstreamer-base",
+ "libc",
+]
+
+[[package]]
+name = "gstreamer-app-sys"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aaf1a3af017f9493c34ccc8439cbce5c48f6ddff6ec0514c23996b374ff25f9a"
+dependencies = [
+ "glib-sys",
+ "gstreamer-base-sys",
+ "gstreamer-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gstreamer-audio"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68e540174d060cd0d7ee2c2356f152f05d8262bf102b40a5869ff799377269d8"
+dependencies = [
+ "cfg-if",
+ "glib",
+ "gstreamer",
+ "gstreamer-audio-sys",
+ "gstreamer-base",
+ "libc",
+ "smallvec",
+]
+
+[[package]]
+name = "gstreamer-audio-sys"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "626cd3130bc155a8b6d4ac48cfddc15774b5a6cc76fcb191aab09a2655bad8f5"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "gstreamer-base-sys",
+ "gstreamer-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gstreamer-base"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71ff9b0bbc8041f0c6c8a53b206a6542f86c7d9fa8a7dff3f27d9c374d9f39b4"
+dependencies = [
+ "atomic_refcell",
+ "cfg-if",
+ "glib",
+ "gstreamer",
+ "gstreamer-base-sys",
+ "libc",
+]
+
+[[package]]
+name = "gstreamer-base-sys"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed78852b92db1459b8f4288f86e6530274073c20be2f94ba642cddaca08b00e"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "gstreamer-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gstreamer-sys"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a24ae2930e683665832a19ef02466094b09d1f2da5673f001515ed5486aa9377"
+dependencies = [
+ "cfg-if",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
 [[package]]
 name = "hashbrown"
-version = "0.16.0"
+version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
 
 [[package]]
 name = "heck"
@@ -165,9 +475,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.11.4"
+version = "2.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
+checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
 dependencies = [
  "equivalent",
  "hashbrown",
@@ -182,11 +492,29 @@ dependencies = [
  "either",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "kstring"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1"
+dependencies = [
+ "static_assertions",
+]
+
 [[package]]
 name = "libc"
-version = "0.2.162"
+version = "0.2.177"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
+checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
 
 [[package]]
 name = "memchr"
@@ -205,6 +533,67 @@ dependencies = [
  "util",
 ]
 
+[[package]]
+name = "muldiv"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0"
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "option-operations"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b31ce827892359f23d3cd1cc4c75a6c241772bbd2db17a92dcf27cbefdf52689"
+dependencies = [
+ "pastey",
+]
+
+[[package]]
+name = "pastey"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
 [[package]]
 name = "pkg-config"
 version = "0.3.32"
@@ -236,6 +625,15 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "136558b6e1ebaecc92755d0ffaf9421f519531bed30cc2ad23b22cb00965cc5e"
 
+[[package]]
+name = "proc-macro-crate"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
+dependencies = [
+ "toml_edit",
+]
+
 [[package]]
 name = "proc-macro-error"
 version = "1.0.4"
@@ -261,9 +659,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.95"
+version = "1.0.103"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
+checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
 dependencies = [
  "unicode-ident",
 ]
@@ -292,18 +690,18 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.36"
+version = "1.0.42"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
 name = "serde"
-version = "1.0.226"
+version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
 dependencies = [
  "serde_core",
  "serde_derive",
@@ -311,18 +709,18 @@ dependencies = [
 
 [[package]]
 name = "serde_core"
-version = "1.0.226"
+version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.226"
+version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -331,24 +729,36 @@ dependencies = [
 
 [[package]]
 name = "serde_spanned"
-version = "0.6.9"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
+checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
 dependencies = [
- "serde",
+ "serde_core",
 ]
 
+[[package]]
+name = "slab"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+
 [[package]]
 name = "smallvec"
 version = "1.15.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
 
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
 [[package]]
 name = "syn"
-version = "2.0.104"
+version = "2.0.111"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
+checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -367,9 +777,9 @@ dependencies = [
 
 [[package]]
 name = "system-deps"
-version = "7.0.5"
+version = "7.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb"
+checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f"
 dependencies = [
  "cfg-expr",
  "heck",
@@ -380,9 +790,9 @@ dependencies = [
 
 [[package]]
 name = "target-lexicon"
-version = "0.13.2"
+version = "0.13.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
+checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c"
 
 [[package]]
 name = "tests"
@@ -398,40 +808,77 @@ dependencies = [
  "util",
 ]
 
+[[package]]
+name = "thiserror"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "toml"
-version = "0.8.23"
+version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
+checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
 dependencies = [
- "serde",
+ "indexmap",
+ "serde_core",
  "serde_spanned",
  "toml_datetime",
- "toml_edit",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
 ]
 
 [[package]]
 name = "toml_datetime"
-version = "0.6.11"
+version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
+checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
 dependencies = [
- "serde",
+ "serde_core",
 ]
 
 [[package]]
 name = "toml_edit"
-version = "0.22.27"
+version = "0.23.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
+checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d"
 dependencies = [
  "indexmap",
- "serde",
- "serde_spanned",
  "toml_datetime",
+ "toml_parser",
+ "winnow",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
+dependencies = [
  "winnow",
 ]
 
+[[package]]
+name = "toml_writer"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
+
 [[package]]
 name = "trace"
 version = "0.1.0"
@@ -442,9 +889,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.12"
+version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
 
 [[package]]
 name = "util"
@@ -461,21 +908,36 @@ dependencies = [
 
 [[package]]
 name = "version-compare"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
+checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
 
 [[package]]
 name = "version_check"
-version = "0.9.4"
+version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
 
 [[package]]
 name = "winnow"
-version = "0.7.13"
+version = "0.7.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
+checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
 dependencies = [
  "memchr",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 8e0fbab290..0ced3297e1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,7 @@
 [workspace]
 resolver = "2"
 members = [
+    "rust/audio",
     "rust/hw/char/pl011",
     "rust/hw/timer/hpet",
     "rust/tests",
@@ -18,8 +19,13 @@ authors = ["The QEMU Project Developers <qemu-devel@nongnu.org>"]
 [workspace.dependencies]
 anyhow = "~1.0"
 foreign = "~0.3.1"
+futures = "0.3"
 libc = "0.2.162"
 glib-sys = { version = "0.21.2", features = ["v2_66"] }
+gio-sys = { version = "0.21.2", features = ["v2_66"] }
+gobect-sys = { version = "0.21.2", features = ["v2_66"] }
+gst_app = { package = "gstreamer-app", version = "0.24.2", features = ["v1_24"] }
+gst_audio = { package = "gstreamer-audio", version = "0.24.2" }
 serde = { version = "1.0.226", features = ["derive"] }
 serde_derive = "1.0.226"
 
diff --git a/audio/trace-events b/audio/trace-events
index f7f639d960..d71d85fe01 100644
--- a/audio/trace-events
+++ b/audio/trace-events
@@ -34,3 +34,8 @@ audio_be_set_active_out(void *sw, bool on) "sw=%p, on=%d"
 audio_timer_start(int interval) "interval %d ms"
 audio_timer_stop(void) ""
 audio_timer_delayed(int interval) "interval %d ms"
+
+# gstreamer.rs
+gst_write(size_t bytes_written, uint64_t elapsed_ms, size_t bytes_per_second) "bytes_written=%zu, elapsed_ms=%" PRIu64 ", bytes_per_second=%zu"
+gst_need_data(const char *name, size_t len) "name=%s, len=%zu"
+gst_new_sample(const char *name, size_t len) "name=%s, len=%zu"
diff --git a/rust/audio/Cargo.toml b/rust/audio/Cargo.toml
new file mode 100644
index 0000000000..ceeb1a5b14
--- /dev/null
+++ b/rust/audio/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "audio"
+version = "0.1.0"
+description = "Rust bindings for QEMU/audio"
+resolver = "2"
+publish = false
+
+authors.workspace = true
+edition.workspace = true
+homepage.workspace = true
+license.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+
+[dependencies]
+bql = { path = "../bql" }
+futures = { workspace = true }
+glib-sys = { workspace = true }
+gio-sys = { workspace = true }
+gst_app = { workspace = true }
+gst_audio = { workspace = true }
+common = { path = "../common" }
+qom = { path = "../qom" }
+util = { path = "../util" }
+system = { path = "../system" }
+trace = { path = "../trace" }
+
+[lints]
+workspace = true
diff --git a/rust/audio/build.rs b/rust/audio/build.rs
new file mode 100644
index 0000000000..5654d1d562
--- /dev/null
+++ b/rust/audio/build.rs
@@ -0,0 +1,49 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#[cfg(unix)]
+use std::os::unix::fs::symlink as symlink_file;
+#[cfg(windows)]
+use std::os::windows::fs::symlink_file;
+use std::{env, fs::remove_file, io::Result, path::Path};
+
+fn main() -> Result<()> {
+    let manifest_dir = env!("CARGO_MANIFEST_DIR");
+    let file = if let Ok(root) = env::var("MESON_BUILD_ROOT") {
+        let sub = get_rust_subdir(manifest_dir).unwrap();
+        format!("{root}/{sub}/bindings.inc.rs")
+    } else {
+        // Placing bindings.inc.rs in the source directory is supported
+        // but not documented or encouraged.
+        format!("{manifest_dir}/src/bindings.inc.rs")
+    };
+
+    let file = Path::new(&file);
+    if !Path::new(&file).exists() {
+        panic!(concat!(
+            "\n",
+            "    No generated C bindings found! Maybe you wanted one of\n",
+            "    `make clippy`, `make rustfmt`, `make rustdoc`?\n",
+            "\n",
+            "    For other uses of `cargo`, start a subshell with\n",
+            "    `pyvenv/bin/meson devenv`, or point MESON_BUILD_ROOT to\n",
+            "    the top of the build tree."
+        ));
+    }
+
+    let out_dir = env::var("OUT_DIR").unwrap();
+    let dest_path = format!("{out_dir}/bindings.inc.rs");
+    let dest_path = Path::new(&dest_path);
+    if dest_path.symlink_metadata().is_ok() {
+        remove_file(dest_path)?;
+    }
+    symlink_file(file, dest_path)?;
+
+    println!("cargo:rerun-if-changed=build.rs");
+    Ok(())
+}
+
+fn get_rust_subdir(path: &str) -> Option<&str> {
+    path.find("/rust").map(|index| &path[index + 1..])
+}
diff --git a/rust/audio/meson.build b/rust/audio/meson.build
new file mode 100644
index 0000000000..27519a3e3f
--- /dev/null
+++ b/rust/audio/meson.build
@@ -0,0 +1,75 @@
+c_enums = [
+  'audcnotification_e',
+  'AudioFormat',
+]
+_audio_bindgen_args = []
+foreach enum : c_enums
+  _audio_bindgen_args += ['--rustified-enum', enum]
+endforeach
+
+blocked_type = [
+  'Error',
+  'ObjectClass',
+]
+foreach type: blocked_type
+  _audio_bindgen_args += ['--blocklist-type', type]
+endforeach
+
+
+# TODO: Remove this comment when the clang/libclang mismatch issue is solved.
+#
+# Rust bindings generation with `bindgen` might fail in some cases where the
+# detected `libclang` does not match the expected `clang` version/target. In
+# this case you must pass the path to `clang` and `libclang` to your build
+# command invocation using the environment variables CLANG_PATH and
+# LIBCLANG_PATH
+_audio_bindings_inc_rs = rust.bindgen(
+  input: 'wrapper.h',
+  dependencies: common_ss.all_dependencies(),
+  output: 'bindings.inc.rs',
+  include_directories: bindings_incdir,
+  bindgen_version: ['>=0.60.0'],
+  args: bindgen_args_common + _audio_bindgen_args,
+  c_args: bindgen_c_args,
+)
+
+_audio_rs = static_library(
+  'audio',
+  structured_sources(
+    [
+      'src/lib.rs',
+      'src/bindings.rs',
+      'src/audio.rs',
+      'src/gstreamer.rs',
+    ],
+    {'.': _audio_bindings_inc_rs}
+  ),
+  override_options: ['rust_std=2021', 'build.rust_std=2021'],
+  rust_abi: 'rust',
+  link_with: [
+    _util_rs,
+    _bql_rs,
+    _qom_rs,
+    _trace_rs,
+    _system_rs,
+  ],
+  dependencies: [
+    gio_sys_rs,
+    glib_sys_rs,
+    gst_app_rs,
+    gst_audio_rs,
+    common_rs,
+    futures_rs,
+  ],
+  rust_dependency_map: {
+    'gstreamer_app': 'gst_app',
+    'gstreamer_audio': 'gst_audio',
+  }
+)
+
+audio_rs = declare_dependency(link_with: [_audio_rs])
+
+rust_devices_ss.add(declare_dependency(
+  link_whole: [_audio_rs],
+  variables: {'crate': 'audio'},
+))
diff --git a/rust/audio/src/audio.rs b/rust/audio/src/audio.rs
new file mode 100644
index 0000000000..436298ea17
--- /dev/null
+++ b/rust/audio/src/audio.rs
@@ -0,0 +1,516 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use std::{
+    ffi::{CStr, CString},
+    ptr::NonNull,
+};
+
+use common::Opaque;
+use qom::{prelude::*, ObjectImpl};
+use util::{Error, Result};
+
+use crate::bindings;
+
+/// Called when audio data needs to be processed. The callback receives:
+/// - `available`: Number of bytes/frames available for processing
+pub type AudioCallback = Box<dyn Fn(i32) + Send>;
+
+pub type CaptureNotification = bindings::audcnotification_e;
+
+pub trait AudioCaptureOps {
+    fn notify(&mut self, cmd: CaptureNotification);
+
+    fn capture(&mut self, buf: &[u8]);
+
+    fn destroy(&mut self);
+}
+
+/// A safe wrapper around [`bindings::AudioBackend`].
+#[repr(transparent)]
+#[derive(common::Wrapper)]
+pub struct AudioBackend(Opaque<bindings::AudioBackend>);
+
+pub type AudioBackendClass = bindings::AudioBackendClass;
+
+pub trait AudioBackendImpl: ObjectImpl {
+    type VoiceOut;
+    type VoiceIn;
+    type Capture;
+
+    fn realize(&self, dev: &bindings::Audiodev) -> Result<()>;
+
+    fn id(&self) -> &CString;
+
+    fn open_out(
+        &self,
+        sw: Option<Self::VoiceOut>,
+        name: &CStr,
+        callback_fn: AudioCallback,
+        settings: &bindings::audsettings,
+    ) -> Option<Self::VoiceOut>;
+
+    fn open_in(
+        &self,
+        sw: Option<Self::VoiceIn>,
+        name: &CStr,
+        callback_fn: AudioCallback,
+        settings: &bindings::audsettings,
+    ) -> Option<Self::VoiceIn>;
+
+    fn close_out(&self, sw: Self::VoiceOut);
+
+    fn close_in(&self, sw: Self::VoiceIn);
+
+    fn is_active_out(&self, sw: &Self::VoiceOut) -> bool;
+
+    fn is_active_in(&self, sw: &Self::VoiceIn) -> bool;
+
+    fn set_active_out(&self, sw: &mut Self::VoiceOut, on: bool);
+
+    fn set_active_in(&self, sw: &mut Self::VoiceIn, on: bool);
+
+    fn set_volume_out(&self, sw: &mut Self::VoiceOut, vol: &bindings::Volume);
+
+    fn set_volume_in(&self, sw: &mut Self::VoiceIn, vol: &bindings::Volume);
+
+    fn write(&self, sw: &mut Self::VoiceOut, buf: &[u8]) -> usize;
+
+    fn read(&self, sw: &mut Self::VoiceIn, buf: &mut [u8]) -> usize;
+
+    fn get_buffer_size_out(&self, sw: &mut Self::VoiceOut) -> i32;
+
+    fn add_capture(
+        &self,
+        settings: &bindings::audsettings,
+        ops: Box<dyn AudioCaptureOps + Send>,
+    ) -> Option<Self::Capture>;
+
+    fn del_capture(&self, cap: Self::Capture);
+
+    fn set_dbus_server(
+        &self,
+        manager: *mut gio_sys::GDBusObjectManagerServer,
+        p2p: bool,
+    ) -> Result<()>;
+
+    /// Convert from opaque `SWVoiceOut` pointer to `VoiceOut` using Box
+    ///
+    /// # Safety
+    /// The pointer must be a valid pointer that was previously created by
+    /// `voice_out_to_ptr`
+    unsafe fn ptr_to_voice_out(&self, ptr: NonNull<bindings::SWVoiceOut>) -> Box<Self::VoiceOut> {
+        unsafe { Box::from_raw(ptr.as_ptr().cast::<Self::VoiceOut>()) }
+    }
+
+    /// Convert from `VoiceOut` to opaque `SWVoiceOut` pointer using Box
+    fn voice_out_to_ptr(&self, voice: Box<Self::VoiceOut>) -> NonNull<bindings::SWVoiceOut> {
+        NonNull::new(Box::into_raw(voice).cast::<bindings::SWVoiceOut>()).unwrap()
+    }
+
+    /// Convert from opaque `SWVoiceIn` pointer to `VoiceIn` using Box
+    ///
+    /// # Safety
+    /// The pointer must be a valid pointer that was previously created by
+    /// `voice_in_to_ptr`
+    unsafe fn ptr_to_voice_in(&self, ptr: NonNull<bindings::SWVoiceIn>) -> Box<Self::VoiceIn> {
+        unsafe { Box::from_raw(ptr.as_ptr().cast::<Self::VoiceIn>()) }
+    }
+
+    /// Convert from `VoiceIn` to opaque `SWVoiceIn` pointer using Box
+    fn voice_in_to_ptr(&self, voice: Box<Self::VoiceIn>) -> NonNull<bindings::SWVoiceIn> {
+        NonNull::new(Box::into_raw(voice).cast::<bindings::SWVoiceIn>()).unwrap()
+    }
+
+    /// Convert from opaque `CaptureVoiceOut` pointer to Capture using Box
+    ///
+    /// # Safety
+    /// The pointer must be a valid pointer that was previously created by
+    /// `capture_to_ptr`
+    unsafe fn ptr_to_capture(&self, ptr: NonNull<bindings::CaptureVoiceOut>) -> Box<Self::Capture> {
+        unsafe { Box::from_raw(ptr.as_ptr().cast::<Self::Capture>()) }
+    }
+
+    /// Convert from Capture to opaque `CaptureVoiceOut` pointer using Box
+    fn capture_to_ptr(&self, capture: Self::Capture) -> NonNull<bindings::CaptureVoiceOut> {
+        NonNull::new(Box::into_raw(Box::new(capture)).cast::<bindings::CaptureVoiceOut>()).unwrap()
+    }
+}
+
+unsafe extern "C" fn rust_realize_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    dev: *mut bindings::Audiodev,
+    errp: *mut *mut util::bindings::Error,
+) -> bool {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let dev = NonNull::new(dev).unwrap().cast();
+    let result = unsafe { be.as_ref() }.realize(unsafe { dev.as_ref() });
+    unsafe { Error::bool_or_propagate(result, errp) }
+}
+
+unsafe extern "C" fn rust_get_id_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+) -> *const std::os::raw::c_char {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let id = unsafe { be.as_ref() }.id();
+    id.as_ptr()
+}
+
+unsafe extern "C" fn rust_open_out_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceOut,
+    name: *const std::os::raw::c_char,
+    callback_opaque: *mut std::os::raw::c_void,
+    callback_fn: Option<unsafe extern "C" fn(*mut std::os::raw::c_void, std::os::raw::c_int)>,
+    settings: *mut bindings::audsettings,
+) -> *mut bindings::SWVoiceOut {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let name = unsafe { CStr::from_ptr(name) };
+    let settings = unsafe { settings.as_ref().unwrap() };
+
+    let sw_voice = NonNull::new(sw).map(|ptr| unsafe { be_ref.ptr_to_voice_out(ptr) });
+
+    let rust_callback = c_callback_to_rust(callback_fn, callback_opaque);
+
+    let result = be_ref.open_out(
+        sw_voice.map(|boxed| *boxed),
+        name,
+        rust_callback.unwrap(),
+        settings,
+    );
+
+    result.map_or(std::ptr::null_mut(), |voice| {
+        be_ref.voice_out_to_ptr(Box::new(voice)).as_ptr()
+    })
+}
+
+unsafe extern "C" fn rust_open_in_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceIn,
+    name: *const std::os::raw::c_char,
+    callback_opaque: *mut std::os::raw::c_void,
+    callback_fn: Option<unsafe extern "C" fn(*mut std::os::raw::c_void, std::os::raw::c_int)>,
+    settings: *mut bindings::audsettings,
+) -> *mut bindings::SWVoiceIn {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let name = unsafe { CStr::from_ptr(name) };
+    let settings = unsafe { settings.as_ref().unwrap() };
+
+    let sw_voice = NonNull::new(sw).map(|ptr| unsafe { be_ref.ptr_to_voice_in(ptr) });
+
+    let rust_callback = c_callback_to_rust(callback_fn, callback_opaque);
+
+    let result = be_ref.open_in(
+        sw_voice.map(|boxed| *boxed),
+        name,
+        rust_callback.unwrap(),
+        settings,
+    );
+
+    result.map_or(std::ptr::null_mut(), |voice| {
+        be_ref.voice_in_to_ptr(Box::new(voice)).as_ptr()
+    })
+}
+
+unsafe extern "C" fn rust_close_out_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceOut,
+) {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let sw = NonNull::new(sw).unwrap();
+    let voice = unsafe { be_ref.ptr_to_voice_out(sw) };
+    be_ref.close_out(*voice);
+}
+
+unsafe extern "C" fn rust_close_in_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceIn,
+) {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let sw = NonNull::new(sw).unwrap();
+    let voice = unsafe { be_ref.ptr_to_voice_in(sw) };
+    be_ref.close_in(*voice);
+}
+
+unsafe extern "C" fn rust_is_active_out_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceOut,
+) -> bool {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let sw = NonNull::new(sw).unwrap();
+    let voice = unsafe { be_ref.ptr_to_voice_out(sw) };
+    let result = be_ref.is_active_out(&voice);
+    std::mem::forget(voice);
+    result
+}
+
+unsafe extern "C" fn rust_is_active_in_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceIn,
+) -> bool {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let sw = NonNull::new(sw).unwrap();
+    let voice = unsafe { be_ref.ptr_to_voice_in(sw) };
+    let result = be_ref.is_active_in(&voice);
+    std::mem::forget(voice);
+    result
+}
+
+unsafe extern "C" fn rust_set_active_out_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceOut,
+    on: bool,
+) {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let sw = NonNull::new(sw).unwrap();
+    let mut voice = unsafe { be_ref.ptr_to_voice_out(sw) };
+    be_ref.set_active_out(&mut voice, on);
+    std::mem::forget(voice);
+}
+
+unsafe extern "C" fn rust_set_active_in_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceIn,
+    on: bool,
+) {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let sw = NonNull::new(sw).unwrap();
+    let mut voice = unsafe { be_ref.ptr_to_voice_in(sw) };
+    be_ref.set_active_in(&mut voice, on);
+    std::mem::forget(voice);
+}
+
+unsafe extern "C" fn rust_set_volume_out_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceOut,
+    vol: *mut bindings::Volume,
+) {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let sw = NonNull::new(sw).unwrap();
+    let vol = unsafe { vol.as_ref().unwrap() };
+    let mut voice = unsafe { be_ref.ptr_to_voice_out(sw) };
+    be_ref.set_volume_out(&mut voice, vol);
+    std::mem::forget(voice);
+}
+
+unsafe extern "C" fn rust_set_volume_in_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceIn,
+    vol: *mut bindings::Volume,
+) {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let sw = NonNull::new(sw).unwrap();
+    let vol = unsafe { vol.as_ref().unwrap() };
+    let mut voice = unsafe { be_ref.ptr_to_voice_in(sw) };
+    be_ref.set_volume_in(&mut voice, vol);
+    std::mem::forget(voice);
+}
+
+unsafe extern "C" fn rust_write_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceOut,
+    buf: *mut std::os::raw::c_void,
+    size: usize,
+) -> usize {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let sw = NonNull::new(sw).unwrap();
+    let mut voice = unsafe { be_ref.ptr_to_voice_out(sw) };
+    let buf_slice = unsafe { std::slice::from_raw_parts(buf as *const u8, size) };
+    let result = be_ref.write(&mut voice, buf_slice);
+    // Put the voice back - we need to leak it since we only borrowed it
+    std::mem::forget(voice);
+    result
+}
+
+unsafe extern "C" fn rust_read_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceIn,
+    buf: *mut std::os::raw::c_void,
+    size: usize,
+) -> usize {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let sw = NonNull::new(sw).unwrap();
+    let mut voice = unsafe { be_ref.ptr_to_voice_in(sw) };
+    let buf_slice = unsafe { std::slice::from_raw_parts_mut(buf.cast::<u8>(), size) };
+    let result = be_ref.read(&mut voice, buf_slice);
+    std::mem::forget(voice);
+    result
+}
+
+unsafe extern "C" fn rust_get_buffer_size_out_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    sw: *mut bindings::SWVoiceOut,
+) -> std::os::raw::c_int {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let sw = NonNull::new(sw).unwrap();
+    let mut voice = unsafe { be_ref.ptr_to_voice_out(sw) };
+    let result = be_ref.get_buffer_size_out(&mut voice);
+    std::mem::forget(voice);
+    result
+}
+
+struct CToRustCaptureOpsBridge {
+    notify_fn:
+        Option<unsafe extern "C" fn(*mut std::os::raw::c_void, bindings::audcnotification_e)>,
+    capture_fn: Option<
+        unsafe extern "C" fn(
+            *mut std::os::raw::c_void,
+            *const std::os::raw::c_void,
+            std::os::raw::c_int,
+        ),
+    >,
+    destroy_fn: Option<unsafe extern "C" fn(*mut std::os::raw::c_void)>,
+    opaque: *mut std::os::raw::c_void,
+}
+
+// SAFETY: The bridge is Send because it only contains function pointers and raw
+// pointers. The caller must ensure thread safety when using the opaque pointer.
+unsafe impl Send for CToRustCaptureOpsBridge {}
+
+impl CToRustCaptureOpsBridge {
+    fn new(c_ops: &bindings::audio_capture_ops, opaque: *mut std::os::raw::c_void) -> Self {
+        Self {
+            notify_fn: c_ops.notify,
+            capture_fn: c_ops.capture,
+            destroy_fn: c_ops.destroy,
+            opaque,
+        }
+    }
+}
+
+impl AudioCaptureOps for CToRustCaptureOpsBridge {
+    fn notify(&mut self, cmd: CaptureNotification) {
+        if let Some(notify_fn) = self.notify_fn {
+            unsafe {
+                notify_fn(self.opaque, cmd);
+            }
+        }
+    }
+
+    fn capture(&mut self, buf: &[u8]) {
+        if let Some(capture_fn) = self.capture_fn {
+            unsafe {
+                capture_fn(
+                    self.opaque,
+                    buf.as_ptr().cast::<std::os::raw::c_void>(),
+                    buf.len() as std::os::raw::c_int,
+                );
+            }
+        }
+    }
+
+    fn destroy(&mut self) {
+        if let Some(destroy_fn) = self.destroy_fn {
+            unsafe {
+                destroy_fn(self.opaque);
+            }
+        }
+    }
+}
+
+unsafe extern "C" fn rust_add_capture_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    settings: *mut bindings::audsettings,
+    ops: *mut bindings::audio_capture_ops,
+    cb_opaque: *mut std::os::raw::c_void,
+) -> *mut bindings::CaptureVoiceOut {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let settings = unsafe { settings.as_ref().unwrap() };
+    let ops = unsafe { ops.as_ref().unwrap() };
+
+    let bridge = CToRustCaptureOpsBridge::new(ops, cb_opaque);
+    let rust_ops = Box::new(bridge) as Box<dyn AudioCaptureOps + Send>;
+
+    let result = unsafe { be.as_ref() }.add_capture(settings, rust_ops);
+    result.map_or(std::ptr::null_mut(), |capture| {
+        unsafe { be.as_ref() }.capture_to_ptr(capture).as_ptr()
+    })
+}
+
+unsafe extern "C" fn rust_del_capture_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    cap: *mut bindings::CaptureVoiceOut,
+    _cb_opaque: *mut std::os::raw::c_void,
+) {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let be_ref = unsafe { be.as_ref() };
+    let cap_ptr = NonNull::new(cap).unwrap();
+    let capture = *unsafe { be_ref.ptr_to_capture(cap_ptr) };
+    be_ref.del_capture(capture);
+}
+
+unsafe extern "C" fn rust_set_dbus_server_fn<T: AudioBackendImpl>(
+    be: *mut bindings::AudioBackend,
+    manager: *mut gio_sys::GDBusObjectManagerServer,
+    p2p: bool,
+    errp: *mut *mut util::bindings::Error,
+) -> bool {
+    let be = NonNull::new(be).unwrap().cast::<T>();
+    let result = unsafe { be.as_ref() }.set_dbus_server(manager, p2p);
+    unsafe { Error::bool_or_propagate(result, errp) }
+}
+
+// SAFETY: The C audio callback is expected to handle its own thread safety.
+struct SendCb(
+    unsafe extern "C" fn(*mut std::os::raw::c_void, std::os::raw::c_int),
+    *mut std::os::raw::c_void,
+);
+unsafe impl Send for SendCb {}
+
+impl SendCb {
+    fn call(&self, available: i32) {
+        unsafe { (self.0)(self.1, available) }
+    }
+}
+
+fn c_callback_to_rust(
+    c_fn: Option<unsafe extern "C" fn(*mut std::os::raw::c_void, std::os::raw::c_int)>,
+    opaque: *mut std::os::raw::c_void,
+) -> Option<AudioCallback> {
+    c_fn.map(|f| -> AudioCallback {
+        let cb = SendCb(f, opaque);
+        Box::new(move |available| cb.call(available))
+    })
+}
+
+impl AudioBackendClass {
+    pub(crate) fn class_init<T: AudioBackendImpl>(&mut self) {
+        self.realize = Some(rust_realize_fn::<T>);
+        self.get_id = Some(rust_get_id_fn::<T>);
+        self.open_out = Some(rust_open_out_fn::<T>);
+        self.open_in = Some(rust_open_in_fn::<T>);
+        self.close_out = Some(rust_close_out_fn::<T>);
+        self.close_in = Some(rust_close_in_fn::<T>);
+        self.is_active_out = Some(rust_is_active_out_fn::<T>);
+        self.is_active_in = Some(rust_is_active_in_fn::<T>);
+        self.set_active_out = Some(rust_set_active_out_fn::<T>);
+        self.set_active_in = Some(rust_set_active_in_fn::<T>);
+        self.set_volume_out = Some(rust_set_volume_out_fn::<T>);
+        self.set_volume_in = Some(rust_set_volume_in_fn::<T>);
+        self.write = Some(rust_write_fn::<T>);
+        self.read = Some(rust_read_fn::<T>);
+        self.get_buffer_size_out = Some(rust_get_buffer_size_out_fn::<T>);
+        self.add_capture = Some(rust_add_capture_fn::<T>);
+        self.del_capture = Some(rust_del_capture_fn::<T>);
+        self.set_dbus_server = Some(rust_set_dbus_server_fn::<T>);
+    }
+}
+
+unsafe impl ObjectType for AudioBackend {
+    type Class = AudioBackendClass;
+    const TYPE_NAME: &'static CStr =
+        unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_AUDIO_BACKEND) };
+}
+qom_isa!(AudioBackend: Object);
diff --git a/rust/audio/src/bindings.rs b/rust/audio/src/bindings.rs
new file mode 100644
index 0000000000..aee53473a8
--- /dev/null
+++ b/rust/audio/src/bindings.rs
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#![allow(
+    dead_code,
+    improper_ctypes_definitions,
+    improper_ctypes,
+    non_camel_case_types,
+    non_snake_case,
+    non_upper_case_globals,
+    unnecessary_transmutes,
+    unsafe_op_in_unsafe_fn,
+    clippy::pedantic,
+    clippy::restriction,
+    clippy::style,
+    clippy::missing_const_for_fn,
+    clippy::ptr_offset_with_cast,
+    clippy::useless_transmute,
+    clippy::missing_safety_doc,
+    clippy::too_many_arguments
+)]
+
+use gio_sys::GDBusObjectManagerServer;
+use glib_sys::{GHashTable, GHashTableIter, GPtrArray, GSList};
+use qom::bindings::ObjectClass;
+use util::bindings::Error;
+
+#[cfg(MESON)]
+include!("bindings.inc.rs");
+
+#[cfg(not(MESON))]
+include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs"));
+
+unsafe impl Send for audio_capture_ops {}
diff --git a/rust/audio/src/gstreamer.rs b/rust/audio/src/gstreamer.rs
new file mode 100644
index 0000000000..8b99944c9a
--- /dev/null
+++ b/rust/audio/src/gstreamer.rs
@@ -0,0 +1,1070 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use std::{
+    ffi::{CStr, CString},
+    sync::{
+        atomic::{AtomicBool, Ordering},
+        Arc, Mutex,
+    },
+    time::Instant,
+};
+
+use bql::{BqlCell, BqlRefCell};
+use common::uninit_field_mut;
+use futures::StreamExt;
+use gst::prelude::*;
+use gst_app::{glib, gst};
+use gst_audio::{self, prelude::StreamVolumeExt, StreamVolume};
+use qom::{prelude::*, ObjectImpl, ParentField, ParentInit};
+// Import qom traits explicitly to avoid ambiguity with gst traits
+use qom::{IsA as QomIsA, ObjectType as QomObjectType};
+use util::{log::Log, log_mask_ln, Error, Result};
+
+use crate::{
+    bindings::{self},
+    AudioBackend, AudioBackendImpl, AudioCallback, AudiodevGStreamerOptions,
+    AudiodevPerDirectionOptions, CaptureNotification,
+};
+
+::trace::include_trace!("audio");
+
+#[repr(C)]
+#[derive(qom::Object)]
+pub struct AudioGStreamer {
+    parent_obj: ParentField<AudioBackend>,
+
+    id: BqlCell<CString>,
+    opt: BqlRefCell<Option<AudiodevGStreamerOptions>>,
+    mixer_pipeline: BqlRefCell<Option<MixerPipeline>>,
+}
+
+qom_isa!(AudioGStreamer : AudioBackend, Object);
+
+#[repr(C)]
+pub struct AudioGStreamerClass {
+    parent_class: <AudioBackend as QomObjectType>::Class,
+}
+
+// Shared mixer pipeline for mixing multiple audio voices
+struct MixerPipeline {
+    pipeline: gst::Pipeline,
+    mixer: gst::Element,
+    tee: gst::Element,
+    #[allow(dead_code)] // For RAII reasons
+    bus_watch: gst::bus::BusWatchGuard,
+}
+
+trait AudioGStreamerImpl: AudioBackendImpl + QomIsA<AudioGStreamer> {}
+
+impl AudioGStreamerClass {
+    fn class_init<T: AudioGStreamerImpl>(&mut self) {
+        self.parent_class.class_init::<T>();
+    }
+}
+
+impl AudioGStreamerImpl for AudioGStreamer {}
+
+unsafe impl QomObjectType for AudioGStreamer {
+    type Class = AudioGStreamerClass;
+
+    const TYPE_NAME: &'static CStr = crate::TYPE_AUDIO_GSTREAMER;
+}
+
+impl ObjectImpl for AudioGStreamer {
+    type ParentType = AudioBackend;
+
+    const INSTANCE_INIT: Option<unsafe fn(ParentInit<Self>)> = Some(Self::init);
+    const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>;
+}
+
+enum VoiceOutKind {
+    Mixed,
+    Standalone(gst::bus::BusWatchGuard),
+}
+
+pub struct VoiceOut {
+    kind: VoiceOutKind,
+    pipeline: gst::Pipeline,
+    stream_volume: Option<StreamVolume>,
+    appsrc: gst_app::AppSrc,
+    active: Arc<AtomicBool>,
+    active_time: Option<Instant>,
+    bytes_written: usize,
+    bytes_per_second: usize,
+    offset_time: Option<gst::ClockTime>,
+}
+
+pub struct VoiceIn {
+    pipeline: gst::Pipeline,
+    appsink: gst_app::AppSink,
+    active: bool,
+    pending_data: Arc<Mutex<Vec<u8>>>,
+}
+
+pub struct Capture {
+    bin: gst::Bin,
+    tee_pad: gst::Pad,
+    ops: Arc<Mutex<Box<dyn crate::audio::AudioCaptureOps + Send>>>,
+}
+
+impl AudioBackendImpl for AudioGStreamer {
+    type VoiceOut = VoiceOut;
+    type VoiceIn = VoiceIn;
+    type Capture = Capture;
+
+    fn realize(&self, dev: &bindings::Audiodev) -> Result<()> {
+        if dev.id.is_null() {
+            return Err(Error::msg("dev.id is null"));
+        }
+
+        gst::init()?;
+        let id_cstr = unsafe { CStr::from_ptr(dev.id) };
+        let id = CString::new(id_cstr.to_bytes())?;
+        self.id.set(id);
+
+        // ideally using qapi-rs instead
+        let gst = unsafe { &dev.u.gstreamer };
+        let opt: AudiodevGStreamerOptions = gst.into();
+
+        if opt
+            .out
+            .as_ref()
+            .and_then(|o| o.mixing_engine)
+            .unwrap_or(false)
+        {
+            let mixer_pipeline = create_mixer_pipeline(&opt)?;
+            *self.mixer_pipeline.borrow_mut() = Some(mixer_pipeline);
+        } else {
+            if opt
+                .out
+                .as_ref()
+                .and_then(|o| o.fixed_settings)
+                .unwrap_or(false)
+            {
+                log_mask_ln!(
+                    Log::Unimp,
+                    "Fixed settings aren't supported in non-mixer mode"
+                );
+            }
+            if opt.out.as_ref().and_then(|o| o.buffer_length).is_some() {
+                log_mask_ln!(
+                    Log::Unimp,
+                    "Buffer length isn't supported in non-mixer mode"
+                );
+            }
+        }
+
+        if opt.in_ != Default::default() {
+            log_mask_ln!(Log::Unimp, "Input configuration isn't supported yet");
+        }
+
+        *self.opt.borrow_mut() = Some(opt);
+
+        Ok(())
+    }
+
+    fn id(&self) -> &CString {
+        unsafe { &*self.id.as_ptr() }
+    }
+
+    fn open_out(
+        &self,
+        sw: Option<Self::VoiceOut>,
+        name: &CStr,
+        callback_fn: AudioCallback,
+        settings: &bindings::audsettings,
+    ) -> Option<Self::VoiceOut> {
+        let mut active = false;
+        if let Some(voice) = sw {
+            active = voice.active.load(Ordering::Relaxed);
+            self.close_out(voice);
+        }
+
+        let mixer_pipeline_ref = self.mixer_pipeline.borrow();
+        let mut result = if let Some(mixer_pipeline) = mixer_pipeline_ref.as_ref() {
+            create_mixed_voice(mixer_pipeline, settings, name)
+        } else {
+            create_standalone_voice(settings, name, self.opt.borrow().as_ref().unwrap())
+        };
+
+        if let Ok(voice) = &mut result {
+            let (mut tx, mut rx) = futures::channel::mpsc::channel(1);
+
+            let appsrc_clone = voice.appsrc.clone();
+            let active_clone = voice.active.clone();
+            glib::MainContext::default().spawn(async move {
+                loop {
+                    // TODO: some emulated devices don't call write() after callback (ex: hda)
+                    // we need to call them regularly. Ideally, they should call write() when data is available.
+                    let timeout = glib::timeout_future(std::time::Duration::from_millis(10));
+
+                    match futures::future::select(rx.next(), timeout).await {
+                        futures::future::Either::Left((Some(size), _)) => {
+                            let size = i32::try_from(size).unwrap_or(i32::MAX);
+                            callback_fn(size);
+                        }
+                        futures::future::Either::Left((None, _)) => break,
+                        futures::future::Either::Right(_) => {
+                            if active_clone.load(Ordering::Relaxed)
+                                && appsrc_clone.current_level_bytes() == 0
+                            {
+                                callback_fn(4096);
+                            }
+                        }
+                    }
+                }
+            });
+
+            let name = CString::new(name.to_bytes()).unwrap();
+            #[allow(clippy::shadow_unrelated)]
+            let active_clone = voice.active.clone();
+            voice.appsrc.set_callbacks(
+                gst_app::AppSrcCallbacks::builder()
+                    .need_data(move |_src, size| {
+                        let size = size as usize;
+                        trace::trace_gst_need_data(name.as_c_str(), size);
+
+                        if active_clone.load(Ordering::Relaxed) {
+                            if let Err(err) = tx.try_send(size) {
+                                eprintln!("Failed to send need-data to main loop: {err}");
+                            }
+                        }
+                    })
+                    .build(),
+            );
+
+            self.set_active_out(voice, active);
+        };
+
+        result
+            .inspect_err(|e| eprintln!("Failed to create voice output: {e}"))
+            .ok()
+    }
+
+    fn open_in(
+        &self,
+        sw: Option<Self::VoiceIn>,
+        name: &CStr,
+        callback_fn: AudioCallback,
+        settings: &bindings::audsettings,
+    ) -> Option<Self::VoiceIn> {
+        let mut active = false;
+        if let Some(voice) = sw {
+            active = voice.active;
+            self.close_in(voice);
+        }
+
+        let mut result = create_input_voice(self.opt.borrow().as_ref().unwrap(), settings);
+
+        if let Ok(voice) = &mut result {
+            let (mut tx, mut rx) = futures::channel::mpsc::channel::<gst::Sample>(1);
+            let pending_data = voice.pending_data.clone();
+
+            let name = CString::new(name.to_bytes()).unwrap();
+            glib::MainContext::default().spawn(async move {
+                while let Some(sample) = rx.next().await {
+                    let Some(buffer) = sample.buffer() else {
+                        continue;
+                    };
+                    let size = buffer.size();
+                    trace::trace_gst_new_sample(name.as_c_str(), size);
+                    match buffer.map_readable() {
+                        Ok(buffer) => {
+                            if let Ok(mut data) = pending_data.lock() {
+                                data.extend_from_slice(buffer.as_slice());
+                            }
+                            callback_fn(size.try_into().unwrap());
+                        }
+                        Err(err) => {
+                            eprintln!("Failed to map buffer: {err}");
+                            continue;
+                        }
+                    };
+                }
+            });
+
+            voice.appsink.set_callbacks(
+                gst_app::AppSinkCallbacks::builder()
+                    .new_sample(move |sink| {
+                        // FIXME: with 1.28, we could get "current-level-bytes" instead, and let read() pull
+                        let sample = sink.pull_sample().map_err(|_| gst::FlowError::Eos)?;
+                        tx.try_send(sample).map_err(|_| gst::FlowError::Eos)?;
+                        Ok(gst::FlowSuccess::Ok)
+                    })
+                    .build(),
+            );
+
+            self.set_active_in(voice, active);
+        }
+
+        if let Err(e) = &result {
+            eprintln!("Failed to create voice input: {e}");
+        }
+        result.ok()
+    }
+
+    fn close_out(&self, mut sw: Self::VoiceOut) {
+        match sw.kind {
+            VoiceOutKind::Mixed => {
+                self.set_active_out(&mut sw, false);
+                if let Some(bin) = sw
+                    .appsrc
+                    .parent()
+                    .and_then(|p| p.downcast::<gst::Bin>().ok())
+                {
+                    let _ = bin.set_state(gst::State::Null);
+                    let mp = self.mixer_pipeline.borrow();
+                    if let Some(pad) = bin.sink_pads().first() {
+                        if let Some(peer) = pad.peer() {
+                            let _ = pad.unlink(&peer);
+                            mp.as_ref().unwrap().mixer.release_request_pad(&peer);
+                        }
+                    }
+                    if let Err(err) = mp.as_ref().unwrap().pipeline.remove(&bin) {
+                        eprintln!("Failed to remove voice from pipeline: {err}");
+                    }
+                }
+            }
+            VoiceOutKind::Standalone(_bus_watch_guard) => {
+                if let Err(e) = sw.pipeline.set_state(gst::State::Null) {
+                    eprintln!("Warning: Failed to set to null state: {e}");
+                }
+            }
+        }
+    }
+
+    fn close_in(&self, mut sw: Self::VoiceIn) {
+        self.set_active_in(&mut sw, false);
+        if let Err(e) = sw.pipeline.set_state(gst::State::Null) {
+            eprintln!("Warning: Failed to set to null state: {e}");
+        }
+    }
+
+    fn is_active_out(&self, sw: &Self::VoiceOut) -> bool {
+        sw.active.load(Ordering::Relaxed)
+    }
+
+    fn is_active_in(&self, sw: &Self::VoiceIn) -> bool {
+        sw.active
+    }
+
+    fn set_active_out(&self, sw: &mut Self::VoiceOut, on: bool) {
+        if sw.active.load(Ordering::Relaxed) == on {
+            return;
+        }
+
+        sw.active.store(on, Ordering::Relaxed);
+        sw.bytes_written = 0;
+        sw.active_time = if on { Some(Instant::now()) } else { None };
+        sw.offset_time = None;
+
+        match &sw.kind {
+            VoiceOutKind::Mixed => {
+                let Some(bin) = sw
+                    .appsrc
+                    .parent()
+                    .and_then(|p| p.downcast::<gst::Bin>().ok())
+                else {
+                    eprintln!("Failed to get parent bin for voice");
+                    return;
+                };
+
+                set_playing(bin.upcast_ref(), on);
+            }
+            VoiceOutKind::Standalone(_bus_watch_guard) => {
+                set_playing(sw.pipeline.upcast_ref(), on);
+            }
+        }
+    }
+
+    fn set_active_in(&self, sw: &mut Self::VoiceIn, on: bool) {
+        sw.active = on;
+        set_playing(sw.pipeline.upcast_ref(), on);
+    }
+
+    fn set_volume_out(&self, sw: &mut Self::VoiceOut, vol: &bindings::Volume) {
+        let volume = qemu_volume_to_gst(vol);
+
+        if let Some(ref stream_volume) = sw.stream_volume {
+            stream_volume.set_volume(gst_audio::StreamVolumeFormat::Linear, volume);
+            stream_volume.set_mute(vol.mute);
+        } else if let Some(sink) = find_sink_element(&sw.pipeline) {
+            set_element_volume(sink, volume, vol.mute);
+        } else {
+            eprintln!("Failed to find volume control in pipeline");
+        }
+    }
+
+    fn set_volume_in(&self, sw: &mut Self::VoiceIn, vol: &bindings::Volume) {
+        let volume = qemu_volume_to_gst(vol);
+
+        if let Some(stream_volume) = sw
+            .pipeline
+            .upcast_ref::<gst::Bin>()
+            .by_interface(gst_audio::StreamVolume::static_type())
+            .and_then(|elem| elem.dynamic_cast::<gst_audio::StreamVolume>().ok())
+        {
+            stream_volume.set_volume(gst_audio::StreamVolumeFormat::Linear, volume);
+            stream_volume.set_mute(vol.mute);
+        } else if let Some(source) = find_source_element(&sw.pipeline) {
+            set_element_volume(source, volume, vol.mute)
+        } else {
+            eprintln!("Failed to find volume control in input pipeline");
+        }
+    }
+
+    fn write(&self, sw: &mut Self::VoiceOut, buf: &[u8]) -> usize {
+        if buf.is_empty() || !sw.active.load(Ordering::Relaxed) {
+            return 0;
+        }
+
+        if let Some(timer) = sw.active_time {
+            trace::trace_gst_write(
+                sw.bytes_written,
+                timer.elapsed().as_millis() as u64,
+                sw.bytes_per_second,
+            )
+        }
+
+        let offset_time = sw
+            .offset_time
+            .get_or_insert_with(|| sw.pipeline.current_running_time().unwrap_or_default());
+
+        assert!(sw.bytes_per_second > 0);
+        let pts = gst::ClockTime::from_nseconds(
+            sw.bytes_written as u64 * gst::ClockTime::SECOND.nseconds()
+                / sw.bytes_per_second as u64
+                + offset_time.nseconds(),
+        );
+
+        let mut gst_buffer = match gst::Buffer::with_size(buf.len()) {
+            Ok(buffer) => buffer,
+            Err(e) => {
+                eprintln!("Failed to create GStreamer buffer: {e}");
+                return 0;
+            }
+        };
+
+        {
+            let Some(buffer_ref) = gst_buffer.get_mut() else {
+                eprintln!("Failed to get mutable reference to GStreamer buffer");
+                return 0;
+            };
+
+            let duration = gst::ClockTime::from_nseconds(
+                buf.len() as u64 * gst::ClockTime::SECOND.nseconds() / sw.bytes_per_second as u64,
+            );
+
+            buffer_ref.set_pts(pts);
+            buffer_ref.set_duration(duration);
+
+            let mut mapped_buffer = match buffer_ref.map_writable() {
+                Ok(mapped) => mapped,
+                Err(e) => {
+                    eprintln!("Failed to map GStreamer buffer for writing: {e}");
+                    return 0;
+                }
+            };
+            mapped_buffer.copy_from_slice(buf);
+        }
+
+        sw.bytes_written += buf.len();
+
+        match sw.appsrc.push_buffer(gst_buffer) {
+            Ok(_) => buf.len(),
+            Err(e) => {
+                eprintln!("Failed to push buffer to appsrc: {e}");
+                0
+            }
+        }
+    }
+
+    fn read(&self, sw: &mut Self::VoiceIn, buf: &mut [u8]) -> usize {
+        if buf.is_empty() || !sw.active {
+            return 0;
+        }
+
+        let mut total_read = 0;
+
+        while total_read < buf.len() {
+            // First, try to consume pending data from previous read
+            if let Ok(mut pending) = sw.pending_data.lock() {
+                if !pending.is_empty() {
+                    let bytes_to_copy = pending.len().min(buf.len() - total_read);
+                    buf[total_read..total_read + bytes_to_copy]
+                        .copy_from_slice(&pending[..bytes_to_copy]);
+                    pending.drain(..bytes_to_copy);
+                    total_read += bytes_to_copy;
+                    if total_read >= buf.len() {
+                        break;
+                    }
+                }
+            }
+
+            // Try to pull a new sample from the appsink
+            let sample = match sw.appsink.try_pull_sample(gst::ClockTime::ZERO) {
+                Some(sample) => sample,
+                None => break,
+            };
+
+            let buffer = match sample.buffer() {
+                Some(buffer) => buffer,
+                None => break,
+            };
+
+            let map = match buffer.map_readable() {
+                Ok(map) => map,
+                Err(e) => {
+                    eprintln!("Failed to map input buffer for reading: {e}");
+                    break;
+                }
+            };
+
+            let data = map.as_slice();
+            let needed = buf.len() - total_read;
+            let bytes_to_copy = data.len().min(needed);
+
+            buf[total_read..total_read + bytes_to_copy].copy_from_slice(&data[..bytes_to_copy]);
+            total_read += bytes_to_copy;
+
+            // If there's remaining data, save it for next read
+            if bytes_to_copy < data.len() {
+                if let Ok(mut pending) = sw.pending_data.lock() {
+                    pending.extend_from_slice(&data[bytes_to_copy..]);
+                }
+                break;
+            }
+        }
+
+        total_read
+    }
+
+    fn get_buffer_size_out(&self, sw: &mut Self::VoiceOut) -> i32 {
+        // Maybe it should query the pipeline for latency instead
+        // Tbh, it looks like this API is not very useful in QEMU
+        sw.appsrc.current_level_bytes() as i32
+    }
+
+    fn add_capture(
+        &self,
+        settings: &bindings::audsettings,
+        ops: Box<dyn crate::audio::AudioCaptureOps + Send>,
+    ) -> Option<Self::Capture> {
+        match self.try_add_capture(settings, ops) {
+            Ok(capture) => Some(capture),
+            Err(e) => {
+                eprintln!("Failed to add capture: {e}");
+                None
+            }
+        }
+    }
+
+    fn del_capture(&self, cap: Self::Capture) {
+        if let Ok(mut ops_guard) = cap.ops.lock() {
+            ops_guard.destroy();
+        }
+
+        if let Some(ghost_pad) = cap.bin.static_pad("sink") {
+            let _ = cap.tee_pad.unlink(&ghost_pad);
+        }
+
+        if let Some(tee) = cap.tee_pad.parent_element() {
+            tee.release_request_pad(&cap.tee_pad);
+        }
+
+        if let Err(e) = cap.bin.set_state(gst::State::Null) {
+            eprintln!("Warning: Failed to set capture bin to null state: {e}");
+        }
+
+        if let Some(pipeline) = cap
+            .bin
+            .parent()
+            .and_then(|p| p.downcast::<gst::Pipeline>().ok())
+        {
+            if let Err(e) = pipeline.remove(&cap.bin) {
+                eprintln!("Warning: Failed to remove capture bin from pipeline: {e}");
+            }
+        }
+    }
+
+    fn set_dbus_server(
+        &self,
+        _manager: *mut gio_sys::GDBusObjectManagerServer,
+        _p2p: bool,
+    ) -> Result<()> {
+        Err(Error::msg("DBus not supported by this backend"))
+    }
+}
+
+impl AudioGStreamer {
+    fn try_add_capture(
+        &self,
+        settings: &bindings::audsettings,
+        ops: Box<dyn crate::audio::AudioCaptureOps + Send>,
+    ) -> Result<Capture> {
+        let mixer_pipeline_ref = self.mixer_pipeline.borrow();
+        let mixer_pipeline = mixer_pipeline_ref
+            .as_ref()
+            .ok_or_else(|| Error::msg("Capture is only supported when mixing-engine is enabled"))?;
+
+        let caps = build_audio_caps(settings)?;
+        let appsink = gst_app::AppSink::builder().caps(&caps).sync(false).build();
+
+        let ops = Arc::new(Mutex::new(ops));
+        let ops_clone = ops.clone();
+        // Set up callback to pull samples from appsink
+        appsink.set_callbacks(
+            gst_app::AppSinkCallbacks::builder()
+                .new_sample(move |sink| {
+                    let sample = sink.pull_sample().map_err(|_| gst::FlowError::Eos)?;
+                    let buffer = sample.buffer().ok_or(gst::FlowError::Error)?;
+
+                    let map = buffer.map_readable().map_err(|_| gst::FlowError::Error)?;
+                    let data = map.as_slice();
+
+                    trace::trace_gst_new_sample(c"capture", data.len());
+                    // Forward captured audio to ops
+                    if let Ok(mut ops_guard) = ops_clone.lock() {
+                        ops_guard.notify(CaptureNotification::AUD_CNOTIFY_ENABLE);
+                        ops_guard.capture(data);
+                    }
+
+                    Ok(gst::FlowSuccess::Ok)
+                })
+                .build(),
+        );
+
+        let bin = gst::Bin::new();
+
+        let queue = create_element("queue")?;
+        let audioconvert = create_element("audioconvert")?;
+        let audioresample = create_element("audioresample")?;
+
+        bin.add_many([&queue, &audioconvert, &audioresample, appsink.upcast_ref()])?;
+
+        gst::Element::link_many([&queue, &audioconvert, &audioresample, appsink.upcast_ref()])?;
+
+        let queue_sink_pad = queue
+            .static_pad("sink")
+            .ok_or_else(|| Error::msg("Failed to get queue sink pad"))?;
+        let ghost_pad = gst::GhostPad::with_target(&queue_sink_pad)?;
+        ghost_pad.set_active(true)?;
+        bin.add_pad(&ghost_pad)?;
+
+        mixer_pipeline.pipeline.add(&bin)?;
+
+        let tee_pad = mixer_pipeline
+            .tee
+            .request_pad_simple("src_%u")
+            .ok_or_else(|| Error::msg("Failed to request pad from tee"))?;
+
+        // Link tee → bin (ghost pad)
+        tee_pad.link(&ghost_pad)?;
+
+        bin.sync_state_with_parent()?;
+
+        Ok(Capture { bin, tee_pad, ops })
+    }
+
+    unsafe fn init(mut this: ParentInit<Self>) {
+        uninit_field_mut!(*this, id).write(BqlCell::new(CString::new("invalid-id").unwrap()));
+        uninit_field_mut!(*this, opt).write(BqlRefCell::new(None));
+        uninit_field_mut!(*this, mixer_pipeline).write(BqlRefCell::new(None));
+    }
+}
+
+fn set_playing(elem: &gst::Element, on: bool) {
+    let target_state = if on {
+        gst::State::Playing
+    } else {
+        gst::State::Null
+    };
+
+    if let Err(e) = elem.set_state(target_state) {
+        eprintln!("Failed to set element state to {target_state:?}: {e}");
+    }
+}
+
+/// Build `GStreamer` caps for the given audio settings
+fn build_audio_caps(settings: &bindings::audsettings) -> Result<gst::Caps> {
+    let format = audio_format_to_gst_format(settings.fmt)?;
+
+    Ok(gst_audio::AudioCapsBuilder::new_interleaved()
+        .format(format)
+        .rate(settings.freq)
+        .channels(settings.nchannels)
+        .build())
+}
+
+/// Find the sink element in the pipeline
+fn find_sink_element(pipeline: &gst::Pipeline) -> Option<gst::Element> {
+    pipeline.iterate_sinks().into_iter().flatten().next()
+}
+
+/// Find the source element in the pipeline
+fn find_source_element(pipeline: &gst::Pipeline) -> Option<gst::Element> {
+    pipeline.iterate_sources().into_iter().flatten().next()
+}
+
+fn set_element_volume(elem: gst::Element, volume: f64, mute: bool) {
+    if elem.find_property("volume").is_some() {
+        elem.set_property("volume", volume);
+    }
+    if elem.find_property("mute").is_some() {
+        elem.set_property("mute", mute);
+    }
+}
+
+/// Convert QEMU volume (0-255 per channel) to `GStreamer` volume (0.0-1.0)
+/// Returns the average volume across all channels
+fn qemu_volume_to_gst(vol: &bindings::Volume) -> f64 {
+    let channels = vol.channels.min(bindings::AUDIO_MAX_CHANNELS as i32) as usize;
+
+    if channels == 0 {
+        return 0.0;
+    }
+
+    let total: u32 = vol.vol[..channels].iter().map(|&v| u32::from(v)).sum();
+
+    f64::from(total) / (channels as f64 * 255.0)
+}
+
+/// Build caps from `AudiodevPerDirectionOptions` for fixed settings
+fn build_caps_from_options(opt: &AudiodevPerDirectionOptions) -> Result<gst::Caps> {
+    let format = opt.format.map(audio_format_to_gst_format).transpose()?;
+    let rate = opt.frequency.map(|f| f as i32);
+    let channels = opt.channels.map(|c| c as i32);
+
+    Ok(gst_audio::AudioCapsBuilder::new_interleaved()
+        .format_if_some(format)
+        .rate_if_some(rate)
+        .channels_if_some(channels)
+        .build())
+}
+
+/// Create a shared mixer pipeline for mixing multiple audio voices
+fn create_mixer_pipeline(opt: &AudiodevGStreamerOptions) -> Result<MixerPipeline> {
+    let pipeline = gst::Pipeline::new();
+
+    let mixer = gst::ElementFactory::make("liveadder")
+        .property("ignore-inactive-pads", true)
+        .property("force-live", true)
+        .build()?;
+
+    let audioconvert = create_element("audioconvert")?;
+    let audioresample = create_element("audioresample")?;
+    let tee = create_element("tee")?;
+    let queue = create_element("queue")?;
+    let buffer_time_us = opt.out.as_ref().and_then(|o| o.buffer_length);
+    let audiosink = create_audiosink(&opt.sink, buffer_time_us)?;
+
+    let capsfilter = if opt
+        .out
+        .as_ref()
+        .and_then(|o| o.fixed_settings)
+        .unwrap_or(false)
+    {
+        let cf = create_element("capsfilter")?;
+        let caps = build_caps_from_options(opt.out.as_ref().unwrap())?;
+        cf.set_property("caps", &caps);
+        Some(cf)
+    } else {
+        None
+    };
+
+    let mut elements: Vec<&gst::Element> =
+        vec![&mixer, &audioconvert, &audioresample, &tee, &queue];
+    if let Some(ref cf) = capsfilter {
+        elements.push(cf);
+    }
+    elements.push(&audiosink);
+
+    pipeline.add_many(&elements)?;
+
+    gst::Element::link_many([&mixer, &audioconvert, &audioresample, &tee])?;
+
+    if let Some(ref cf) = capsfilter {
+        gst::Element::link_many([&tee, &queue, cf, &audiosink])?;
+    } else {
+        gst::Element::link_many([&tee, &queue, &audiosink])?;
+    }
+
+    let bus_watch = add_bus_watch(&pipeline)?;
+    set_playing(pipeline.upcast_ref(), true);
+
+    Ok(MixerPipeline {
+        pipeline,
+        mixer,
+        tee,
+        bus_watch,
+    })
+}
+
+/// Create a voice output connected to the shared mixer
+fn create_mixed_voice(
+    mixer_pipeline: &MixerPipeline,
+    settings: &bindings::audsettings,
+    name: &CStr,
+) -> Result<VoiceOut> {
+    // Create a separate bin for this voice
+    let bin = gst::Bin::new();
+
+    let appsrc = create_appsrc(settings, name)?;
+    let volume = create_element("volume")?;
+    let stream_volume = volume
+        .clone()
+        .dynamic_cast::<gst_audio::StreamVolume>()
+        .ok();
+    let identity = create_element("identity")?;
+    identity.set_property("sync", true);
+    let audioconvert = create_element("audioconvert")?;
+    let audioresample = create_element("audioresample")?;
+
+    bin.add_many([
+        appsrc.upcast_ref(),
+        &identity,
+        &volume,
+        &audioconvert,
+        &audioresample,
+    ])?;
+    gst::Element::link_many([
+        appsrc.upcast_ref(),
+        &identity,
+        &volume,
+        &audioconvert,
+        &audioresample,
+    ])?;
+
+    let src_pad = audioresample
+        .static_pad("src")
+        .ok_or_else(|| Error::msg("Failed to get source pad"))?;
+
+    let ghost_pad = gst::GhostPad::with_target(&src_pad)?;
+
+    ghost_pad.set_active(true)?;
+    bin.add_pad(&ghost_pad)?;
+
+    mixer_pipeline
+        .pipeline
+        .add(bin.upcast_ref::<gst::Element>())?;
+
+    bin.link(&mixer_pipeline.mixer)?;
+
+    Ok(VoiceOut {
+        kind: VoiceOutKind::Mixed,
+        pipeline: mixer_pipeline.pipeline.clone(),
+        stream_volume,
+        appsrc,
+        active: Arc::new(AtomicBool::new(false)),
+        active_time: None,
+        bytes_per_second: get_bytes_per_second(settings)?,
+        bytes_written: 0,
+        offset_time: None,
+    })
+}
+
+/// Create a standalone voice output without mixer or capture
+fn create_standalone_voice(
+    settings: &bindings::audsettings,
+    name: &CStr,
+    opt: &AudiodevGStreamerOptions,
+) -> Result<VoiceOut> {
+    let pipeline = gst::Pipeline::new();
+
+    let appsrc = create_appsrc(settings, name)?;
+    let identity = create_element("identity")?;
+    identity.set_property("sync", true);
+    let audioconvert = create_element("audioconvert")?;
+    let audioresample = create_element("audioresample")?;
+    let queue = create_element("queue")?;
+
+    let buffer_time_us = opt.out.as_ref().and_then(|o| o.buffer_length);
+    let audiosink = create_audiosink(&opt.sink, buffer_time_us)?;
+    let stream_volume = audiosink
+        .clone()
+        .dynamic_cast::<gst_audio::StreamVolume>()
+        .ok();
+
+    pipeline.add_many([
+        appsrc.upcast_ref(),
+        &identity,
+        &audioconvert,
+        &audioresample,
+        &queue,
+        &audiosink,
+    ])?;
+
+    gst::Element::link_many([
+        appsrc.upcast_ref(),
+        &identity,
+        &audioconvert,
+        &audioresample,
+        &queue,
+        &audiosink,
+    ])?;
+
+    let bus_watch = add_bus_watch(&pipeline)?;
+
+    Ok(VoiceOut {
+        kind: VoiceOutKind::Standalone(bus_watch),
+        pipeline,
+        stream_volume,
+        appsrc,
+        active: Arc::new(AtomicBool::new(false)),
+        active_time: None,
+        bytes_per_second: get_bytes_per_second(settings)?,
+        bytes_written: 0,
+        offset_time: None,
+    })
+}
+
+fn add_bus_watch(pipeline: &gst::Pipeline) -> Result<gst::bus::BusWatchGuard> {
+    // Add bus message handler to prevent queue overflow
+    let bus = pipeline
+        .bus()
+        .ok_or_else(|| Error::msg("Failed to get pipeline bus"))?;
+    let bus_watch = bus.add_watch(|_bus, msg| {
+        use gst::MessageView;
+        match msg.view() {
+            MessageView::Error(err) => {
+                eprintln!(
+                    "GStreamer error from {:?}: {} ({:?})",
+                    err.src().map(gst::prelude::GstObjectExt::path_string),
+                    err.error(),
+                    err.debug()
+                );
+            }
+            MessageView::Warning(warning) => {
+                eprintln!(
+                    "GStreamer warning from {:?}: {} ({:?})",
+                    warning.src().map(gst::prelude::GstObjectExt::path_string),
+                    warning.error(),
+                    warning.debug()
+                );
+            }
+            _ => {}
+        }
+        glib::ControlFlow::Continue
+    })?;
+
+    Ok(bus_watch)
+}
+
+/// Create an input voice for audio recording
+fn create_input_voice(
+    opt: &AudiodevGStreamerOptions,
+    settings: &bindings::audsettings,
+) -> Result<VoiceIn> {
+    let source_name = opt.source.as_deref().unwrap_or("autoaudiosrc");
+    let pipeline = gst::Pipeline::new();
+
+    let audiosrc = create_element(source_name)?;
+    let audioconvert = create_element("audioconvert")?;
+    let audioresample = create_element("audioresample")?;
+    let caps = build_audio_caps(settings)?;
+    let appsink = gst_app::AppSink::builder()
+        .caps(&caps)
+        .sync(false)
+        .max_time(Some(gst::ClockTime::from_mseconds(10)))
+        .build();
+
+    pipeline.add_many([
+        &audiosrc,
+        &audioconvert,
+        &audioresample,
+        appsink.upcast_ref(),
+    ])?;
+
+    gst::Element::link_many([
+        &audiosrc,
+        &audioconvert,
+        &audioresample,
+        appsink.upcast_ref(),
+    ])?;
+
+    Ok(VoiceIn {
+        pipeline,
+        appsink,
+        active: false,
+        pending_data: Arc::new(Mutex::new(Vec::new())),
+    })
+}
+
+fn create_audiosink(
+    elem_name: &Option<String>,
+    buffer_time_us: Option<u32>,
+) -> Result<gst::Element> {
+    const DEFAULT_BUFFER_TIME_US: u32 = 40_000;
+
+    let elem_name = elem_name.as_deref().unwrap_or("autoaudiosink");
+    let audiosink = create_element(elem_name)?;
+    let buffer_time_us: i64 = buffer_time_us.unwrap_or(DEFAULT_BUFFER_TIME_US).into();
+
+    if let Some(child_proxy) = audiosink.dynamic_cast_ref::<gst::ChildProxy>() {
+        child_proxy.connect_child_added(move |_proxy, child, _child_name| {
+            if let Some(elem) = child.downcast_ref::<gst::Element>() {
+                if elem.has_property("buffer-time") {
+                    elem.set_property("buffer-time", buffer_time_us);
+                } else {
+                    eprintln!("no buffer-time property on the sink");
+                }
+            }
+        });
+    }
+
+    Ok(audiosink)
+}
+
+fn create_appsrc(settings: &bindings::audsettings, name: &CStr) -> Result<gst_app::AppSrc> {
+    let caps = build_audio_caps(settings)?;
+
+    let appsrc = gst_app::AppSrc::builder()
+        .caps(&caps)
+        .is_live(true)
+        .format(gst::Format::Time)
+        // we don't want to block QEMU
+        .block(false)
+        .build();
+    // create a stream-id
+    appsrc.set_uri(&format!("appsrc://{}", name.to_string_lossy()))?;
+    Ok(appsrc)
+}
+
+/// Create a `GStreamer` element with error reporting
+fn create_element(factory_name: &str) -> Result<gst::Element> {
+    gst::ElementFactory::make(factory_name)
+        .build()
+        .map_err(|e| Error::msg(format!("Failed to create element '{factory_name}': {e}")))
+}
+
+fn get_bytes_per_second(settings: &bindings::audsettings) -> Result<usize> {
+    let bytes_per_sample = get_bytes_per_sample(settings.fmt)?;
+
+    Ok(settings.freq as usize * settings.nchannels as usize * bytes_per_sample as usize)
+}
+
+fn get_bytes_per_sample(fmt: bindings::AudioFormat) -> Result<usize> {
+    use bindings::AudioFormat::*;
+    match fmt {
+        AUDIO_FORMAT_U8 | AUDIO_FORMAT_S8 => Ok(1),
+        AUDIO_FORMAT_U16 | AUDIO_FORMAT_S16 => Ok(2),
+        AUDIO_FORMAT_U32 | AUDIO_FORMAT_S32 | AUDIO_FORMAT_F32 => Ok(4),
+        _ => Err(Error::msg(format!("Unsupported audio format: {fmt:?}"))),
+    }
+}
+
+fn audio_format_to_gst_format(fmt: bindings::AudioFormat) -> Result<gst_audio::AudioFormat> {
+    use bindings::AudioFormat::*;
+    use gst_audio::AudioFormat as GAF;
+
+    match fmt {
+        AUDIO_FORMAT_U8 => Ok(GAF::U8),
+        AUDIO_FORMAT_S8 => Ok(GAF::S8),
+        AUDIO_FORMAT_U16 => Ok(GAF::U16le),
+        AUDIO_FORMAT_S16 => Ok(GAF::S16le),
+        AUDIO_FORMAT_U32 => Ok(GAF::U32le),
+        AUDIO_FORMAT_S32 => Ok(GAF::S32le),
+        AUDIO_FORMAT_F32 => Ok(GAF::F32le),
+        _ => Err(Error::msg(format!("Unsupported audio format: {fmt:?}"))),
+    }
+}
diff --git a/rust/audio/src/lib.rs b/rust/audio/src/lib.rs
new file mode 100644
index 0000000000..3e899e2439
--- /dev/null
+++ b/rust/audio/src/lib.rs
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+pub mod bindings;
+
+mod audio;
+use std::ffi::CStr;
+
+pub use audio::*;
+
+mod gstreamer;
+
+pub const TYPE_AUDIO_GSTREAMER: &std::ffi::CStr = c"audio-gstreamer";
+
+#[derive(Default, Debug, PartialEq, Eq)]
+pub struct AudiodevGStreamerOptions {
+    pub in_: Option<AudiodevPerDirectionOptions>,
+    pub out: Option<AudiodevPerDirectionOptions>,
+    pub sink: Option<String>,
+    pub source: Option<String>,
+}
+
+#[derive(Default, Debug, PartialEq, Eq)]
+pub struct AudiodevPerDirectionOptions {
+    pub mixing_engine: Option<bool>,
+    pub fixed_settings: Option<bool>,
+    pub frequency: Option<u32>,
+    pub channels: Option<u32>,
+    pub voices: Option<u32>,
+    pub format: Option<bindings::AudioFormat>,
+    pub buffer_length: Option<u32>,
+}
+
+impl From<&bindings::AudiodevGStreamerOptions> for AudiodevGStreamerOptions {
+    fn from(value: &bindings::AudiodevGStreamerOptions) -> Self {
+        let in_ = (!value.in_.is_null())
+            .then(|| AudiodevPerDirectionOptions::from(unsafe { &*value.in_ }));
+        let out = (!value.out.is_null())
+            .then(|| AudiodevPerDirectionOptions::from(unsafe { &*value.out }));
+        let sink = (!value.sink.is_null()).then(|| {
+            unsafe { CStr::from_ptr(value.sink) }
+                .to_string_lossy()
+                .into_owned()
+        });
+        let source = (!value.source.is_null()).then(|| {
+            unsafe { CStr::from_ptr(value.source) }
+                .to_string_lossy()
+                .into_owned()
+        });
+
+        Self {
+            in_,
+            out,
+            sink,
+            source,
+        }
+    }
+}
+
+impl From<&bindings::AudiodevPerDirectionOptions> for AudiodevPerDirectionOptions {
+    fn from(value: &bindings::AudiodevPerDirectionOptions) -> Self {
+        Self {
+            mixing_engine: if value.has_mixing_engine {
+                Some(value.mixing_engine)
+            } else {
+                None
+            },
+            fixed_settings: if value.has_fixed_settings {
+                Some(value.fixed_settings)
+            } else {
+                None
+            },
+            frequency: if value.has_frequency {
+                Some(value.frequency)
+            } else {
+                None
+            },
+            channels: if value.has_channels {
+                Some(value.channels)
+            } else {
+                None
+            },
+            voices: if value.has_voices {
+                Some(value.voices)
+            } else {
+                None
+            },
+            format: if value.has_format {
+                Some(value.format)
+            } else {
+                None
+            },
+            buffer_length: if value.has_buffer_length {
+                Some(value.buffer_length)
+            } else {
+                None
+            },
+        }
+    }
+}
diff --git a/rust/meson.build b/rust/meson.build
index 9f0ed48481..8c2905a7f0 100644
--- a/rust/meson.build
+++ b/rust/meson.build
@@ -13,6 +13,11 @@ syn_rs_native = cargo_ws.subproject('syn').dependency()
 proc_macro2_rs_native = cargo_ws.subproject('proc-macro2').dependency()
 attrs_rs_native = cargo_ws.subproject('attrs').dependency()
 
+futures_rs = cargo_ws.subproject('futures').dependency()
+gio_sys_rs = cargo_ws.subproject('gio-sys').dependency()
+gst_app_rs = cargo_ws.subproject('gstreamer-app').dependency()
+gst_audio_rs = cargo_ws.subproject('gstreamer-audio').dependency()
+
 genrs = []
 
 subdir('qemu-macros')
@@ -29,6 +34,7 @@ subdir('hw/core')
 subdir('tests')
 subdir('trace')
 subdir('hw')
+subdir('audio')
 
 cargo = find_program('cargo', required: false)
 
-- 
2.51.1



^ permalink raw reply related	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
                   ` (23 preceding siblings ...)
  2025-12-01 11:23 ` [RFC 24/24] WIP: rust/audio: add GStreamer backend marcandre.lureau
@ 2025-12-01 13:02 ` BALATON Zoltan
  2025-12-01 13:41   ` Christian Schoenebeck
  2025-12-01 18:20   ` Marc-André Lureau
  24 siblings, 2 replies; 51+ messages in thread
From: BALATON Zoltan @ 2025-12-01 13:02 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: qemu-devel, Gerd Hoffmann, Thomas Huth, Akihiko Odaki,
	Alexandre Ratchov, dirty.ice.hu, Christian Schoenebeck,
	Philippe Mathieu-Daudé, Volker Rümelin

[-- Attachment #1: Type: text/plain, Size: 7393 bytes --]

On Mon, 1 Dec 2025, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Hi,
>
> The following patch series provides a GStreamer-based audio backend, which could
> ultimately allow QEMU to leverage the framework to support the various audio
> subsystems and simplify the audio handling logic (timing, resampling, mixing
> etc), as well as allow greater pipeline flexibility and customization.

While it's good to have a GStreamer backend to integrate well into systems 
already using that, this should not replace existing audio backends in 
QEMU. The reason is that GStreamer has extensive dependencies that I would 
like to avoid and still be able to use QEMU with just an ALSA or SDL audio 
backend that are much leaner and provide the needed functionality for most 
cases. Also when using jack you'd want to have a QEMU backend for it not 
going through multiple layers. So adding a GStreamer backend has its use 
as another audio backend but not as a replacement for QEMU's audio 
handling logic and backends.

Regards,
BALATON Zoltan

> The preliminary patches consist of additional cleanups started in QEMU 10.2, in
> order to make the code more modular and use QOM.
>
> Finally, the last patch introduces the "audio" rust crate that implements the
> GStreamer backend. See the TODO list in the commit message for the remaining or
> related work items.
>
> Please review and test!
>
> Based-on: https://gitlab.com/bonzini/qemu.git rust-cargo
> Requires meson from https://github.com/bonzini/meson.git cargo-object-full
>
> Marc-André Lureau (24):
>  rust: patch thiserror to work with meson
>  audio: remove obsolete/obscure functions
>  audio/dbus: make "dbus" the default backend when using -display dbus
>  qemu-options.hx: clarify default audio backend selection
>  audio: introduce AudioDriver
>  audio: simplify audio_init()
>  audio: move object creation to audio_driver_init()
>  audio: add QOM module-objects for each backend
>  audio: remove set_dbus_server from audio_driver
>  audio: lookup "audio-" object types, and realize them
>  audio: switch to module-object, drop audio driver registration
>  module: remove audio module support
>  audio: keep a strong reference on the backend
>  audio: make list type declaration private
>  audio: make create_pdos() private
>  replay: remove dependency on audio/
>  audio: make all the backend-specific APIs take the be
>  audio: make AudioBackend truely abstract
>  audio: split AudioBackend
>  audio: AUD_ -> audio_be_
>  audio-be: add common pre-conditions
>  audio-be: add some state trace
>  audio: split AudioDriver code in audio-driver.c
>  WIP: rust/audio: add GStreamer backend
>
> qapi/audio.json                               |   29 +
> ...dio_template.h => audio-driver_template.h} |   76 +-
> audio/audio_int.h                             |   50 +-
> include/qemu/audio-capture.h                  |   24 +-
> include/qemu/audio.h                          |  129 +-
> include/qemu/module.h                         |    3 +-
> include/system/replay.h                       |    8 +-
> replay/replay-internal.h                      |    2 +
> rust/audio/wrapper.h                          |   27 +
> audio/alsaaudio.c                             |   42 +-
> audio/audio-be.c                              |  276 ++
> audio/audio-driver.c                          | 1988 +++++++++++++++
> audio/audio.c                                 | 2248 ++---------------
> audio/dbusaudio.c                             |   57 +-
> audio/dsoundaudio.c                           |   37 +-
> audio/jackaudio.c                             |   37 +-
> audio/noaudio.c                               |   37 +-
> audio/ossaudio.c                              |   41 +-
> audio/paaudio.c                               |   37 +-
> audio/pwaudio.c                               |   37 +-
> audio/sdlaudio.c                              |   37 +-
> audio/sndioaudio.c                            |   37 +-
> audio/spiceaudio.c                            |   37 +-
> audio/wavaudio.c                              |   37 +-
> audio/wavcapture.c                            |    7 +-
> hw/audio/ac97.c                               |   42 +-
> hw/audio/adlib.c                              |   29 +-
> hw/audio/asc.c                                |   20 +-
> hw/audio/cs4231a.c                            |   18 +-
> hw/audio/es1370.c                             |   26 +-
> hw/audio/gus.c                                |   11 +-
> hw/audio/hda-codec.c                          |   26 +-
> hw/audio/lm4549.c                             |   20 +-
> hw/audio/pcspk.c                              |    8 +-
> hw/audio/sb16.c                               |   22 +-
> hw/audio/via-ac97.c                           |   20 +-
> hw/audio/virtio-snd.c                         |   36 +-
> hw/audio/wm8750.c                             |   42 +-
> hw/display/xlnx_dp.c                          |   14 +-
> hw/usb/dev-audio.c                            |   18 +-
> replay/replay-audio.c                         |   51 +-
> replay/replay.c                               |    2 +-
> replay/stubs-system.c                         |    8 +-
> ui/dbus.c                                     |   16 +-
> ui/vnc.c                                      |    4 +-
> Cargo.lock                                    |  572 ++++-
> Cargo.toml                                    |    6 +
> audio/coreaudio.m                             |   37 +-
> audio/meson.build                             |    2 +
> audio/trace-events                            |    9 +
> qemu-options.hx                               |   20 +-
> rust/audio/Cargo.toml                         |   29 +
> rust/audio/build.rs                           |   49 +
> rust/audio/meson.build                        |   75 +
> rust/audio/src/audio.rs                       |  516 ++++
> rust/audio/src/bindings.rs                    |   32 +
> rust/audio/src/gstreamer.rs                   | 1070 ++++++++
> rust/audio/src/lib.rs                         |   99 +
> rust/meson.build                              |    6 +
> .../packagefiles/syn-2-rs/meson/meson.build   |    3 +
> .../thiserror-2.0.17-include.patch            |   14 +
> .../thiserror-impl-2.0.17-include.patch       |   13 +
> subprojects/syn-2-rs.wrap                     |   11 +-
> subprojects/thiserror-2-rs.wrap               |   10 +
> subprojects/thiserror-impl-2-rs.wrap          |   10 +
> 65 files changed, 5862 insertions(+), 2494 deletions(-)
> rename audio/{audio_template.h => audio-driver_template.h} (90%)
> create mode 100644 rust/audio/wrapper.h
> create mode 100644 audio/audio-be.c
> create mode 100644 audio/audio-driver.c
> create mode 100644 rust/audio/Cargo.toml
> create mode 100644 rust/audio/build.rs
> create mode 100644 rust/audio/meson.build
> create mode 100644 rust/audio/src/audio.rs
> create mode 100644 rust/audio/src/bindings.rs
> create mode 100644 rust/audio/src/gstreamer.rs
> create mode 100644 rust/audio/src/lib.rs
> create mode 100644 subprojects/packagefiles/syn-2-rs/meson/meson.build
> create mode 100644 subprojects/packagefiles/thiserror-2.0.17-include.patch
> create mode 100644 subprojects/packagefiles/thiserror-impl-2.0.17-include.patch
> create mode 100644 subprojects/thiserror-2-rs.wrap
> create mode 100644 subprojects/thiserror-impl-2-rs.wrap
>
>

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 24/24] WIP: rust/audio: add GStreamer backend
  2025-12-01 11:23 ` [RFC 24/24] WIP: rust/audio: add GStreamer backend marcandre.lureau
@ 2025-12-01 13:12   ` Markus Armbruster
  2025-12-01 18:26     ` Marc-André Lureau
  0 siblings, 1 reply; 51+ messages in thread
From: Markus Armbruster @ 2025-12-01 13:12 UTC (permalink / raw)
  To: marcandre.lureau
  Cc: qemu-devel, Gerd Hoffmann, Thomas Huth, Akihiko Odaki,
	Alexandre Ratchov, dirty.ice.hu, Christian Schoenebeck,
	Philippe Mathieu-Daudé, Volker Rümelin, Eric Blake,
	Manos Pitsidianakis, open list:Rust-related patc...

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> This patch introduce a rust/audio crate that replaces QEMU audio/
> mixing/resampling code with GStreamer and Rust. It could potentially
> remove the need for all the system-specific audio API implementation,
> since GStreamer has audio elements for
> ALSA/Pipewire/PulseAudio/jack/OSX/WASAPI etc (removing ~10k loc).
>
> TODO:
> - test on various system, with various configuration to see if this
>   backend can replace the other QEMU audio backends
> - add a spicesink/spicesrc to handle spice, or rewrite spice to use
>   the capture approach used by VNC code. Or drop capture support, and
>   use custom qemusrc/qemusink for both Spice and VNC, lowering the feature
>   and behaviour disparity.
> - build-sys: make gstreamer optional
> - build-sys: loadable module support
> - investigate dropping get_buffer_size_out()
> - investigate improving emulated devices to not require regular
>   timers (appsrc need-data is called once)
> - add generic audio backend tests
> - more tests for the mixing/liveadder behaviour (synchronization)
> - other: replace audio/dbus with a rust implementation (not using gstreamer)
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  qapi/audio.json               |   29 +
>  audio/audio-driver_template.h |    2 +
>  rust/audio/wrapper.h          |   27 +
>  audio/audio.c                 |    5 +
>  Cargo.lock                    |  572 ++++++++++++++++--
>  Cargo.toml                    |    6 +
>  audio/trace-events            |    5 +
>  rust/audio/Cargo.toml         |   29 +
>  rust/audio/build.rs           |   49 ++
>  rust/audio/meson.build        |   75 +++
>  rust/audio/src/audio.rs       |  516 ++++++++++++++++
>  rust/audio/src/bindings.rs    |   32 +
>  rust/audio/src/gstreamer.rs   | 1070 +++++++++++++++++++++++++++++++++
>  rust/audio/src/lib.rs         |   99 +++
>  rust/meson.build              |    6 +
>  15 files changed, 2467 insertions(+), 55 deletions(-)
>  create mode 100644 rust/audio/wrapper.h
>  create mode 100644 rust/audio/Cargo.toml
>  create mode 100644 rust/audio/build.rs
>  create mode 100644 rust/audio/meson.build
>  create mode 100644 rust/audio/src/audio.rs
>  create mode 100644 rust/audio/src/bindings.rs
>  create mode 100644 rust/audio/src/gstreamer.rs
>  create mode 100644 rust/audio/src/lib.rs
>
> diff --git a/qapi/audio.json b/qapi/audio.json
> index 2df87b9710..76dc7cbfa6 100644
> --- a/qapi/audio.json
> +++ b/qapi/audio.json
> @@ -128,6 +128,33 @@
>      '*out':       'AudiodevAlsaPerDirectionOptions',
>      '*threshold': 'uint32' } }
>  
> +    ##
> +    # @AudiodevGStreamerOptions:
> +    #
> +    # Options of the GStreamer audio backend.
> +    #
> +    # @in: options of the capture stream
> +    #
> +    # @out: options of the playback stream
> +    #
> +    # @sink: the name of the GStreamer sink element to use
> +    #        (default 'autoaudiosink')
> +    #
> +    # @source: the name of the GStreamer source element to use
> +    #        (default 'autoaudiosrc')

Are 'autoaudiosink' and 'autoaudiosrc' well-known GStreamer names, or
arbitrary?

> +    #
> +    # Since: 11.0
> +    ##
> +    { 'struct': 'AudiodevGStreamerOptions',
> +      'data': {
> +        '*in':        'AudiodevPerDirectionOptions',
> +        '*out':       'AudiodevPerDirectionOptions',
> +        '*sink':      'str',
> +        '*source':    'str'
> +      }
> +    }

Unindent by four, and format the doc comment like this:

##
# @AudiodevGStreamerOptions:
#
# Options of the GStreamer audio backend.
#
# @in: options of the capture stream
#
# @out: options of the playback stream
#
# @sink: the name of the GStreamer sink element to use
#     (default 'autoaudiosink')
#
# @source: the name of the GStreamer source element to use
#     (default 'autoaudiosrc')
#
# Since: 11.0
##


> +
> +
>  ##
>  # @AudiodevSndioOptions:
>  #
> @@ -484,6 +511,7 @@
>              { 'name': 'sdl', 'if': 'CONFIG_AUDIO_SDL' },
>              { 'name': 'sndio', 'if': 'CONFIG_AUDIO_SNDIO' },
>              { 'name': 'spice', 'if': 'CONFIG_SPICE' },
> +            { 'name': 'gstreamer' },

Short form suffices:

               'gstreamer',

>              'wav' ] }
>  
>  ##
> @@ -530,6 +558,7 @@
>                     'if': 'CONFIG_AUDIO_SNDIO' },
>      'spice':     { 'type': 'AudiodevGenericOptions',
>                     'if': 'CONFIG_SPICE' },
> +    'gstreamer': { 'type': 'AudiodevGStreamerOptions' },

Short form suffices:

       'gstreamer': 'AudiodevGStreamerOptions',

>      'wav':       'AudiodevWavOptions' } }
>  
>  ##

[...]



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 08/24] audio: add QOM module-objects for each backend
  2025-12-01 11:22 ` [RFC 08/24] audio: add QOM module-objects for each backend marcandre.lureau
@ 2025-12-01 13:20   ` BALATON Zoltan
  2025-12-01 18:43     ` Marc-André Lureau
  0 siblings, 1 reply; 51+ messages in thread
From: BALATON Zoltan @ 2025-12-01 13:20 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: qemu-devel, Gerd Hoffmann, Thomas Huth, Akihiko Odaki,
	Alexandre Ratchov, dirty.ice.hu, Christian Schoenebeck,
	Philippe Mathieu-Daudé, Volker Rümelin

[-- Attachment #1: Type: text/plain, Size: 1336 bytes --]

On Mon, 1 Dec 2025, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> This will allow to use QOM and the dynamic object module loading.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> audio/audio_int.h   |  2 ++
> audio/alsaaudio.c   | 39 +++++++++++++++++++++++++++++++++++++++
> audio/dbusaudio.c   | 36 ++++++++++++++++++++++++++++++++++++
> audio/dsoundaudio.c | 36 ++++++++++++++++++++++++++++++++++++
> audio/jackaudio.c   | 36 ++++++++++++++++++++++++++++++++++++
> audio/noaudio.c     | 36 ++++++++++++++++++++++++++++++++++++
> audio/ossaudio.c    | 36 ++++++++++++++++++++++++++++++++++++
> audio/paaudio.c     | 36 ++++++++++++++++++++++++++++++++++++
> audio/pwaudio.c     | 36 ++++++++++++++++++++++++++++++++++++
> audio/sdlaudio.c    | 36 ++++++++++++++++++++++++++++++++++++
> audio/sndioaudio.c  | 36 ++++++++++++++++++++++++++++++++++++
> audio/spiceaudio.c  | 36 ++++++++++++++++++++++++++++++++++++
> audio/wavaudio.c    | 36 ++++++++++++++++++++++++++++++++++++
> audio/coreaudio.m   | 36 ++++++++++++++++++++++++++++++++++++
> 14 files changed, 473 insertions(+)

This patch is entirely QOM boiler plate... Are the empty functions really 
needed? Could this use OBJECT_DECLARE_SIMPLE_TYPE instead to cut this 
down?

Regards,
BALATON Zoltan

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-01 13:02 ` [RFC 00/24] audio: " BALATON Zoltan
@ 2025-12-01 13:41   ` Christian Schoenebeck
  2025-12-01 18:20   ` Marc-André Lureau
  1 sibling, 0 replies; 51+ messages in thread
From: Christian Schoenebeck @ 2025-12-01 13:41 UTC (permalink / raw)
  To: Marc-André Lureau, qemu-devel, Gerd Hoffmann
  Cc: Thomas Huth, Akihiko Odaki, Alexandre Ratchov, dirty.ice.hu,
	Philippe Mathieu-Daudé, Volker Rümelin, BALATON Zoltan

On Monday, 1 December 2025 14:02:41 CET BALATON Zoltan wrote:
> On Mon, 1 Dec 2025, marcandre.lureau@redhat.com wrote:
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
[...]
> > The following patch series provides a GStreamer-based audio backend, which
> > could ultimately allow QEMU to leverage the framework to support the
> > various audio subsystems and simplify the audio handling logic (timing,
> > resampling, mixing etc), as well as allow greater pipeline flexibility
> > and customization.
>
> While it's good to have a GStreamer backend to integrate well into systems
> already using that, this should not replace existing audio backends in
> QEMU. The reason is that GStreamer has extensive dependencies that I would
> like to avoid and still be able to use QEMU with just an ALSA or SDL audio
> backend that are much leaner and provide the needed functionality for most
> cases. Also when using jack you'd want to have a QEMU backend for it not
> going through multiple layers. So adding a GStreamer backend has its use
> as another audio backend but not as a replacement for QEMU's audio
> handling logic and backends.
> 
> Regards,
> BALATON Zoltan

I agree with Zoltan on this. GStreamer is nice as new backend, but I would 
also preserve the existing audio backends for the same reasons already 
described by Zoltan.

/Christian




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-01 13:02 ` [RFC 00/24] audio: " BALATON Zoltan
  2025-12-01 13:41   ` Christian Schoenebeck
@ 2025-12-01 18:20   ` Marc-André Lureau
  2025-12-01 19:30     ` BALATON Zoltan
  2025-12-01 20:58     ` Alexandre Ratchov
  1 sibling, 2 replies; 51+ messages in thread
From: Marc-André Lureau @ 2025-12-01 18:20 UTC (permalink / raw)
  To: BALATON Zoltan
  Cc: qemu-devel, Gerd Hoffmann, Thomas Huth, Akihiko Odaki,
	Alexandre Ratchov, dirty.ice.hu, Christian Schoenebeck,
	Philippe Mathieu-Daudé, Volker Rümelin, geoff

[-- Attachment #1: Type: text/plain, Size: 8435 bytes --]

Hi

On Mon, Dec 1, 2025 at 5:03 PM BALATON Zoltan <balaton@eik.bme.hu> wrote:

> On Mon, 1 Dec 2025, marcandre.lureau@redhat.com wrote:
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Hi,
> >
> > The following patch series provides a GStreamer-based audio backend,
> which could
> > ultimately allow QEMU to leverage the framework to support the various
> audio
> > subsystems and simplify the audio handling logic (timing, resampling,
> mixing
> > etc), as well as allow greater pipeline flexibility and customization.
>
> While it's good to have a GStreamer backend to integrate well into systems
> already using that, this should not replace existing audio backends in
> QEMU. The reason is that GStreamer has extensive dependencies that I would
>

GStreamer itself is not so big and doesn't have that many dependencies that
qemu doesn't already have.


> like to avoid and still be able to use QEMU with just an ALSA or SDL audio
> backend that are much leaner and provide the needed functionality for most
>

SDL audio is itself a wrapper for various audio backends, much like
GStreamer in the end, but with arguably less flexibility.


> cases. Also when using jack you'd want to have a QEMU backend for it not
> going through multiple layers. So adding a GStreamer backend has its use
>

I wonder what are the advantages of using JACK compared to ALSA, or
pulse/pipewire, tbh.


> as another audio backend but not as a replacement for QEMU's audio
> handling logic and backends.
>

It would be great if people with very specific or constrained requirements
on qemu audio could check if the GStreamer backend fits their need.


>
> Regards,
> BALATON Zoltan
>
> > The preliminary patches consist of additional cleanups started in QEMU
> 10.2, in
> > order to make the code more modular and use QOM.
> >
> > Finally, the last patch introduces the "audio" rust crate that
> implements the
> > GStreamer backend. See the TODO list in the commit message for the
> remaining or
> > related work items.
> >
> > Please review and test!
> >
> > Based-on: https://gitlab.com/bonzini/qemu.git rust-cargo
> > Requires meson from https://github.com/bonzini/meson.git
> cargo-object-full
> >
> > Marc-André Lureau (24):
> >  rust: patch thiserror to work with meson
> >  audio: remove obsolete/obscure functions
> >  audio/dbus: make "dbus" the default backend when using -display dbus
> >  qemu-options.hx: clarify default audio backend selection
> >  audio: introduce AudioDriver
> >  audio: simplify audio_init()
> >  audio: move object creation to audio_driver_init()
> >  audio: add QOM module-objects for each backend
> >  audio: remove set_dbus_server from audio_driver
> >  audio: lookup "audio-" object types, and realize them
> >  audio: switch to module-object, drop audio driver registration
> >  module: remove audio module support
> >  audio: keep a strong reference on the backend
> >  audio: make list type declaration private
> >  audio: make create_pdos() private
> >  replay: remove dependency on audio/
> >  audio: make all the backend-specific APIs take the be
> >  audio: make AudioBackend truely abstract
> >  audio: split AudioBackend
> >  audio: AUD_ -> audio_be_
> >  audio-be: add common pre-conditions
> >  audio-be: add some state trace
> >  audio: split AudioDriver code in audio-driver.c
> >  WIP: rust/audio: add GStreamer backend
> >
> > qapi/audio.json                               |   29 +
> > ...dio_template.h => audio-driver_template.h} |   76 +-
> > audio/audio_int.h                             |   50 +-
> > include/qemu/audio-capture.h                  |   24 +-
> > include/qemu/audio.h                          |  129 +-
> > include/qemu/module.h                         |    3 +-
> > include/system/replay.h                       |    8 +-
> > replay/replay-internal.h                      |    2 +
> > rust/audio/wrapper.h                          |   27 +
> > audio/alsaaudio.c                             |   42 +-
> > audio/audio-be.c                              |  276 ++
> > audio/audio-driver.c                          | 1988 +++++++++++++++
> > audio/audio.c                                 | 2248 ++---------------
> > audio/dbusaudio.c                             |   57 +-
> > audio/dsoundaudio.c                           |   37 +-
> > audio/jackaudio.c                             |   37 +-
> > audio/noaudio.c                               |   37 +-
> > audio/ossaudio.c                              |   41 +-
> > audio/paaudio.c                               |   37 +-
> > audio/pwaudio.c                               |   37 +-
> > audio/sdlaudio.c                              |   37 +-
> > audio/sndioaudio.c                            |   37 +-
> > audio/spiceaudio.c                            |   37 +-
> > audio/wavaudio.c                              |   37 +-
> > audio/wavcapture.c                            |    7 +-
> > hw/audio/ac97.c                               |   42 +-
> > hw/audio/adlib.c                              |   29 +-
> > hw/audio/asc.c                                |   20 +-
> > hw/audio/cs4231a.c                            |   18 +-
> > hw/audio/es1370.c                             |   26 +-
> > hw/audio/gus.c                                |   11 +-
> > hw/audio/hda-codec.c                          |   26 +-
> > hw/audio/lm4549.c                             |   20 +-
> > hw/audio/pcspk.c                              |    8 +-
> > hw/audio/sb16.c                               |   22 +-
> > hw/audio/via-ac97.c                           |   20 +-
> > hw/audio/virtio-snd.c                         |   36 +-
> > hw/audio/wm8750.c                             |   42 +-
> > hw/display/xlnx_dp.c                          |   14 +-
> > hw/usb/dev-audio.c                            |   18 +-
> > replay/replay-audio.c                         |   51 +-
> > replay/replay.c                               |    2 +-
> > replay/stubs-system.c                         |    8 +-
> > ui/dbus.c                                     |   16 +-
> > ui/vnc.c                                      |    4 +-
> > Cargo.lock                                    |  572 ++++-
> > Cargo.toml                                    |    6 +
> > audio/coreaudio.m                             |   37 +-
> > audio/meson.build                             |    2 +
> > audio/trace-events                            |    9 +
> > qemu-options.hx                               |   20 +-
> > rust/audio/Cargo.toml                         |   29 +
> > rust/audio/build.rs                           |   49 +
> > rust/audio/meson.build                        |   75 +
> > rust/audio/src/audio.rs                       |  516 ++++
> > rust/audio/src/bindings.rs                    |   32 +
> > rust/audio/src/gstreamer.rs                   | 1070 ++++++++
> > rust/audio/src/lib.rs                         |   99 +
> > rust/meson.build                              |    6 +
> > .../packagefiles/syn-2-rs/meson/meson.build   |    3 +
> > .../thiserror-2.0.17-include.patch            |   14 +
> > .../thiserror-impl-2.0.17-include.patch       |   13 +
> > subprojects/syn-2-rs.wrap                     |   11 +-
> > subprojects/thiserror-2-rs.wrap               |   10 +
> > subprojects/thiserror-impl-2-rs.wrap          |   10 +
> > 65 files changed, 5862 insertions(+), 2494 deletions(-)
> > rename audio/{audio_template.h => audio-driver_template.h} (90%)
> > create mode 100644 rust/audio/wrapper.h
> > create mode 100644 audio/audio-be.c
> > create mode 100644 audio/audio-driver.c
> > create mode 100644 rust/audio/Cargo.toml
> > create mode 100644 rust/audio/build.rs
> > create mode 100644 rust/audio/meson.build
> > create mode 100644 rust/audio/src/audio.rs
> > create mode 100644 rust/audio/src/bindings.rs
> > create mode 100644 rust/audio/src/gstreamer.rs
> > create mode 100644 rust/audio/src/lib.rs
> > create mode 100644 subprojects/packagefiles/syn-2-rs/meson/meson.build
> > create mode 100644
> subprojects/packagefiles/thiserror-2.0.17-include.patch
> > create mode 100644
> subprojects/packagefiles/thiserror-impl-2.0.17-include.patch
> > create mode 100644 subprojects/thiserror-2-rs.wrap
> > create mode 100644 subprojects/thiserror-impl-2-rs.wrap
> >
> >

[-- Attachment #2: Type: text/html, Size: 12074 bytes --]

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 24/24] WIP: rust/audio: add GStreamer backend
  2025-12-01 13:12   ` Markus Armbruster
@ 2025-12-01 18:26     ` Marc-André Lureau
  0 siblings, 0 replies; 51+ messages in thread
From: Marc-André Lureau @ 2025-12-01 18:26 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Gerd Hoffmann, Thomas Huth, Akihiko Odaki,
	Alexandre Ratchov, dirty.ice.hu, Christian Schoenebeck,
	Philippe Mathieu-Daudé, Volker Rümelin, Eric Blake,
	Manos Pitsidianakis, open list:Rust-related patc...

Hi Markus

On Mon, Dec 1, 2025 at 5:13 PM Markus Armbruster <armbru@redhat.com> wrote:
>
> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > This patch introduce a rust/audio crate that replaces QEMU audio/
> > mixing/resampling code with GStreamer and Rust. It could potentially
> > remove the need for all the system-specific audio API implementation,
> > since GStreamer has audio elements for
> > ALSA/Pipewire/PulseAudio/jack/OSX/WASAPI etc (removing ~10k loc).
> >
> > TODO:
> > - test on various system, with various configuration to see if this
> >   backend can replace the other QEMU audio backends
> > - add a spicesink/spicesrc to handle spice, or rewrite spice to use
> >   the capture approach used by VNC code. Or drop capture support, and
> >   use custom qemusrc/qemusink for both Spice and VNC, lowering the feature
> >   and behaviour disparity.
> > - build-sys: make gstreamer optional
> > - build-sys: loadable module support
> > - investigate dropping get_buffer_size_out()
> > - investigate improving emulated devices to not require regular
> >   timers (appsrc need-data is called once)
> > - add generic audio backend tests
> > - more tests for the mixing/liveadder behaviour (synchronization)
> > - other: replace audio/dbus with a rust implementation (not using gstreamer)
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> >  qapi/audio.json               |   29 +
> >  audio/audio-driver_template.h |    2 +
> >  rust/audio/wrapper.h          |   27 +
> >  audio/audio.c                 |    5 +
> >  Cargo.lock                    |  572 ++++++++++++++++--
> >  Cargo.toml                    |    6 +
> >  audio/trace-events            |    5 +
> >  rust/audio/Cargo.toml         |   29 +
> >  rust/audio/build.rs           |   49 ++
> >  rust/audio/meson.build        |   75 +++
> >  rust/audio/src/audio.rs       |  516 ++++++++++++++++
> >  rust/audio/src/bindings.rs    |   32 +
> >  rust/audio/src/gstreamer.rs   | 1070 +++++++++++++++++++++++++++++++++
> >  rust/audio/src/lib.rs         |   99 +++
> >  rust/meson.build              |    6 +
> >  15 files changed, 2467 insertions(+), 55 deletions(-)
> >  create mode 100644 rust/audio/wrapper.h
> >  create mode 100644 rust/audio/Cargo.toml
> >  create mode 100644 rust/audio/build.rs
> >  create mode 100644 rust/audio/meson.build
> >  create mode 100644 rust/audio/src/audio.rs
> >  create mode 100644 rust/audio/src/bindings.rs
> >  create mode 100644 rust/audio/src/gstreamer.rs
> >  create mode 100644 rust/audio/src/lib.rs
> >
> > diff --git a/qapi/audio.json b/qapi/audio.json
> > index 2df87b9710..76dc7cbfa6 100644
> > --- a/qapi/audio.json
> > +++ b/qapi/audio.json
> > @@ -128,6 +128,33 @@
> >      '*out':       'AudiodevAlsaPerDirectionOptions',
> >      '*threshold': 'uint32' } }
> >
> > +    ##
> > +    # @AudiodevGStreamerOptions:
> > +    #
> > +    # Options of the GStreamer audio backend.
> > +    #
> > +    # @in: options of the capture stream
> > +    #
> > +    # @out: options of the playback stream
> > +    #
> > +    # @sink: the name of the GStreamer sink element to use
> > +    #        (default 'autoaudiosink')
> > +    #
> > +    # @source: the name of the GStreamer source element to use
> > +    #        (default 'autoaudiosrc')
>
> Are 'autoaudiosink' and 'autoaudiosrc' well-known GStreamer names, or
> arbitrary?

Yes, they are well-known elements from the base plugins.

>
> > +    #
> > +    # Since: 11.0
> > +    ##
> > +    { 'struct': 'AudiodevGStreamerOptions',
> > +      'data': {
> > +        '*in':        'AudiodevPerDirectionOptions',
> > +        '*out':       'AudiodevPerDirectionOptions',
> > +        '*sink':      'str',
> > +        '*source':    'str'
> > +      }
> > +    }
>
> Unindent by four, and format the doc comment like this:
>
> ##
> # @AudiodevGStreamerOptions:
> #
> # Options of the GStreamer audio backend.
> #
> # @in: options of the capture stream
> #
> # @out: options of the playback stream
> #
> # @sink: the name of the GStreamer sink element to use
> #     (default 'autoaudiosink')
> #
> # @source: the name of the GStreamer source element to use
> #     (default 'autoaudiosrc')
> #
> # Since: 11.0
> ##
>
>
> > +
> > +
> >  ##
> >  # @AudiodevSndioOptions:
> >  #
> > @@ -484,6 +511,7 @@
> >              { 'name': 'sdl', 'if': 'CONFIG_AUDIO_SDL' },
> >              { 'name': 'sndio', 'if': 'CONFIG_AUDIO_SNDIO' },
> >              { 'name': 'spice', 'if': 'CONFIG_SPICE' },
> > +            { 'name': 'gstreamer' },
>
> Short form suffices:
>
>                'gstreamer',
>
> >              'wav' ] }
> >
> >  ##
> > @@ -530,6 +558,7 @@
> >                     'if': 'CONFIG_AUDIO_SNDIO' },
> >      'spice':     { 'type': 'AudiodevGenericOptions',
> >                     'if': 'CONFIG_SPICE' },
> > +    'gstreamer': { 'type': 'AudiodevGStreamerOptions' },
>
> Short form suffices:
>
>        'gstreamer': 'AudiodevGStreamerOptions',
>
> >      'wav':       'AudiodevWavOptions' } }
> >
> >  ##
>
> [...]
>
>

Thanks for the early check, I will likely have to update the schema in
future revision, for making GStreamer optional & module to the very
least.

-- 
Marc-André Lureau


^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 08/24] audio: add QOM module-objects for each backend
  2025-12-01 13:20   ` BALATON Zoltan
@ 2025-12-01 18:43     ` Marc-André Lureau
  0 siblings, 0 replies; 51+ messages in thread
From: Marc-André Lureau @ 2025-12-01 18:43 UTC (permalink / raw)
  To: BALATON Zoltan
  Cc: qemu-devel, Gerd Hoffmann, Thomas Huth, Akihiko Odaki,
	Alexandre Ratchov, dirty.ice.hu, Christian Schoenebeck,
	Philippe Mathieu-Daudé, Volker Rümelin

[-- Attachment #1: Type: text/plain, Size: 1644 bytes --]

Hi

On Mon, Dec 1, 2025 at 5:20 PM BALATON Zoltan <balaton@eik.bme.hu> wrote:

> On Mon, 1 Dec 2025, marcandre.lureau@redhat.com wrote:
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > This will allow to use QOM and the dynamic object module loading.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> > audio/audio_int.h   |  2 ++
> > audio/alsaaudio.c   | 39 +++++++++++++++++++++++++++++++++++++++
> > audio/dbusaudio.c   | 36 ++++++++++++++++++++++++++++++++++++
> > audio/dsoundaudio.c | 36 ++++++++++++++++++++++++++++++++++++
> > audio/jackaudio.c   | 36 ++++++++++++++++++++++++++++++++++++
> > audio/noaudio.c     | 36 ++++++++++++++++++++++++++++++++++++
> > audio/ossaudio.c    | 36 ++++++++++++++++++++++++++++++++++++
> > audio/paaudio.c     | 36 ++++++++++++++++++++++++++++++++++++
> > audio/pwaudio.c     | 36 ++++++++++++++++++++++++++++++++++++
> > audio/sdlaudio.c    | 36 ++++++++++++++++++++++++++++++++++++
> > audio/sndioaudio.c  | 36 ++++++++++++++++++++++++++++++++++++
> > audio/spiceaudio.c  | 36 ++++++++++++++++++++++++++++++++++++
> > audio/wavaudio.c    | 36 ++++++++++++++++++++++++++++++++++++
> > audio/coreaudio.m   | 36 ++++++++++++++++++++++++++++++++++++
> > 14 files changed, 473 insertions(+)
>
> This patch is entirely QOM boiler plate... Are the empty functions really
> needed? Could this use OBJECT_DECLARE_SIMPLE_TYPE instead to cut this
> down?
>

Right, I did this patch systematically, and to provide placeholders for the
later patches. But I can now simplify it.

thanks


>
> Regards,
> BALATON Zoltan

[-- Attachment #2: Type: text/html, Size: 2498 bytes --]

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-01 18:20   ` Marc-André Lureau
@ 2025-12-01 19:30     ` BALATON Zoltan
  2025-12-01 19:44       ` Daniel P. Berrangé
  2025-12-01 20:58     ` Alexandre Ratchov
  1 sibling, 1 reply; 51+ messages in thread
From: BALATON Zoltan @ 2025-12-01 19:30 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: qemu-devel, Gerd Hoffmann, Thomas Huth, Akihiko Odaki,
	Alexandre Ratchov, dirty.ice.hu, Christian Schoenebeck,
	Philippe Mathieu-Daudé, Volker Rümelin, geoff

[-- Attachment #1: Type: text/plain, Size: 3092 bytes --]

On Mon, 1 Dec 2025, Marc-André Lureau wrote:
> On Mon, Dec 1, 2025 at 5:03 PM BALATON Zoltan <balaton@eik.bme.hu> wrote:
>> On Mon, 1 Dec 2025, marcandre.lureau@redhat.com wrote:
>>> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>>>
>>> Hi,
>>>
>>> The following patch series provides a GStreamer-based audio backend,
>> which could
>>> ultimately allow QEMU to leverage the framework to support the various
>> audio
>>> subsystems and simplify the audio handling logic (timing, resampling,
>> mixing
>>> etc), as well as allow greater pipeline flexibility and customization.
>>
>> While it's good to have a GStreamer backend to integrate well into systems
>> already using that, this should not replace existing audio backends in
>> QEMU. The reason is that GStreamer has extensive dependencies that I would
>>
>
> GStreamer itself is not so big and doesn't have that many dependencies that
> qemu doesn't already have.

Except that this proposal uses GStreamer from rust so would also pull in 
all the rust dependencies too which is still not needed for QEMU. Saying 
that it's optional but then you lose audio output is also not quite 
acceptable.

>> like to avoid and still be able to use QEMU with just an ALSA or SDL audio
>> backend that are much leaner and provide the needed functionality for most
>>
>
> SDL audio is itself a wrapper for various audio backends, much like
> GStreamer in the end, but with arguably less flexibility.

Yes, but as QEMU has SDL for systems where that's supported it could also 
have GStreamer as another option but not as the sole option replacing 
other backends.

>> cases. Also when using jack you'd want to have a QEMU backend for it not
>> going through multiple layers. So adding a GStreamer backend has its use
>>
>
> I wonder what are the advantages of using JACK compared to ALSA, or
> pulse/pipewire, tbh.

Jack has capability to route between sources and sinks with low latency 
which pulse/pipewire does not have and it allows processing sound better 
than using plain ALSA. Jack is useful for example when running sound tools 
in virtual machines and want to integrate with host sound tools that 
usually support jack. ALSA is useful if you just want to output sound the 
simplest way without adding latency or complexity. The other backends are 
useful to integrate with other apps/environments using those sound 
services.

>> as another audio backend but not as a replacement for QEMU's audio
>> handling logic and backends.
>
> It would be great if people with very specific or constrained requirements
> on qemu audio could check if the GStreamer backend fits their need.

At least one of them already said it wouldn't. Also why somebody not 
running a desktop environment that uses GStreamer would want to add that 
dependency and use a GStreamer plugin to get the sound back to their 
native sound service when it is probably already supported by QEMU 
directly? QEMU also has to support Windows and macOS sound services so 
having a few more Linux/Unix ones does not make it much more complex.

Regards,
BALATON Zoltan

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-01 19:30     ` BALATON Zoltan
@ 2025-12-01 19:44       ` Daniel P. Berrangé
  2025-12-02 12:01         ` BALATON Zoltan
  0 siblings, 1 reply; 51+ messages in thread
From: Daniel P. Berrangé @ 2025-12-01 19:44 UTC (permalink / raw)
  To: BALATON Zoltan
  Cc: Marc-André Lureau, qemu-devel, Gerd Hoffmann, Thomas Huth,
	Akihiko Odaki, Alexandre Ratchov, dirty.ice.hu,
	Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, geoff

On Mon, Dec 01, 2025 at 08:30:26PM +0100, BALATON Zoltan wrote:
> On Mon, 1 Dec 2025, Marc-André Lureau wrote:
> > On Mon, Dec 1, 2025 at 5:03 PM BALATON Zoltan <balaton@eik.bme.hu> wrote:
> > > On Mon, 1 Dec 2025, marcandre.lureau@redhat.com wrote:
> > > > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> > > > 
> > > > Hi,
> > > > 
> > > > The following patch series provides a GStreamer-based audio backend,
> > > which could
> > > > ultimately allow QEMU to leverage the framework to support the various
> > > audio
> > > > subsystems and simplify the audio handling logic (timing, resampling,
> > > mixing
> > > > etc), as well as allow greater pipeline flexibility and customization.
> > > 
> > > While it's good to have a GStreamer backend to integrate well into systems
> > > already using that, this should not replace existing audio backends in
> > > QEMU. The reason is that GStreamer has extensive dependencies that I would
> > > 
> > 
> > GStreamer itself is not so big and doesn't have that many dependencies that
> > qemu doesn't already have.
> 
> Except that this proposal uses GStreamer from rust so would also pull in all
> the rust dependencies too which is still not needed for QEMU. Saying that
> it's optional but then you lose audio output is also not quite acceptable.

In terms of replacing the existing audio backends, it would simply have to
wait until we declare Rust to be a mandatory dependency of QEMU, before
proposing any removal of existing backends.

> > > as another audio backend but not as a replacement for QEMU's audio
> > > handling logic and backends.
> > 
> > It would be great if people with very specific or constrained requirements
> > on qemu audio could check if the GStreamer backend fits their need.
> 
> At least one of them already said it wouldn't. Also why somebody not running
> a desktop environment that uses GStreamer would want to add that dependency
> and use a GStreamer plugin to get the sound back to their native sound
> service when it is probably already supported by QEMU directly? QEMU also
> has to support Windows and macOS sound services so having a few more
> Linux/Unix ones does not make it much more complex.

GStreamer is not merely for desktop environments. It is a general purpose
audio system, and in terms of QEMU, it is already used by the Spice server
for video encoding purposes.  IMHO it is reasonable to consider whether
QEMU could use GStreamer for all audio output regardless of whether it is
running from a desktop session or not.

Personally my main concern with gstreamer is that when things go wrong
it can be very difficult to understand why and thus hard to figure out
how to fix it, unless you're pretty experienced with gstreamer.

If we do consider rationalizing how many backends we have, IMHO, it
would be desirable to retain at least one other QEMU audio backend
that is considered simple & reliable (fool proof) to use & debug.

With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-01 18:20   ` Marc-André Lureau
  2025-12-01 19:30     ` BALATON Zoltan
@ 2025-12-01 20:58     ` Alexandre Ratchov
  2025-12-02  7:55       ` Paolo Bonzini
  1 sibling, 1 reply; 51+ messages in thread
From: Alexandre Ratchov @ 2025-12-01 20:58 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: BALATON Zoltan, qemu-devel, Gerd Hoffmann, Thomas Huth,
	Akihiko Odaki, dirty.ice.hu, Christian Schoenebeck,
	Philippe Mathieu-Daudé, Volker Rümelin, geoff

On Mon, Dec 01, 2025 at 10:20:49PM +0400, Marc-André Lureau wrote:
> > cases. Also when using jack you'd want to have a QEMU backend for it not
> > going through multiple layers. So adding a GStreamer backend has its use
> >
> 
> I wonder what are the advantages of using JACK compared to ALSA, or
> pulse/pipewire, tbh.
> 

Jack can connect programs with deterministic latency. My wild guess is
that it would be to run a synths in the vm.

> > as another audio backend but not as a replacement for QEMU's audio
> > handling logic and backends.
> >
> 
> It would be great if people with very specific or constrained requirements
> on qemu audio could check if the GStreamer backend fits their need.

I'm thinking mainly about their simplicity.

Dropping the system API backends would add an extra sophisticated
layer (GStreamer) between the system and the program. In theory, an
unlimited number of software layers may be stacked in a program, but
the more layers there are, the more fragile the program tends to
be. Based on my limited experience, when things went wrong, the system
backends were simpler to debug and make work than the big frameworks.

IMHO, the system API backends won't hurt GStreamer users, so I see no
reason to remove them.

My 2 cents.


^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-01 20:58     ` Alexandre Ratchov
@ 2025-12-02  7:55       ` Paolo Bonzini
  2025-12-02 12:03         ` BALATON Zoltan
  0 siblings, 1 reply; 51+ messages in thread
From: Paolo Bonzini @ 2025-12-02  7:55 UTC (permalink / raw)
  To: Alexandre Ratchov, Marc-André Lureau
  Cc: BALATON Zoltan, qemu-devel, Gerd Hoffmann, Thomas Huth,
	Akihiko Odaki, dirty.ice.hu, Christian Schoenebeck,
	Philippe Mathieu-Daudé, Volker Rümelin, geoff

On 12/1/25 21:58, Alexandre Ratchov wrote:
> On Mon, Dec 01, 2025 at 10:20:49PM +0400, Marc-André Lureau wrote:
>>> cases. Also when using jack you'd want to have a QEMU backend for it not
>> It would be great if people with very specific or constrained requirements
>> on qemu audio could check if the GStreamer backend fits their need.
> 
> I'm thinking mainly about their simplicity.
> 
> Dropping the system API backends would add an extra sophisticated
> layer (GStreamer) between the system and the program. In theory, an
> unlimited number of software layers may be stacked in a program, but
> the more layers there are, the more fragile the program tends to
> be. Based on my limited experience, when things went wrong, the system
> backends were simpler to debug and make work than the big frameworks.
> 
> IMHO, the system API backends won't hurt GStreamer users, so I see no
> reason to remove them.

I mostly agree.  Perhaps the DirectSound backend could be removed by 
just letting Windows use SDL (unlike macOS, Windows doesn't have a 
"native" GUI layer), and the ALSA backend is also not so useful in my 
opinion.  But all the others have a reason to be there.

Paolo



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-01 19:44       ` Daniel P. Berrangé
@ 2025-12-02 12:01         ` BALATON Zoltan
  0 siblings, 0 replies; 51+ messages in thread
From: BALATON Zoltan @ 2025-12-02 12:01 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Marc-André Lureau, qemu-devel, Gerd Hoffmann, Thomas Huth,
	Akihiko Odaki, Alexandre Ratchov, dirty.ice.hu,
	Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, geoff

[-- Attachment #1: Type: text/plain, Size: 4631 bytes --]

On Mon, 1 Dec 2025, Daniel P. Berrangé wrote:
> On Mon, Dec 01, 2025 at 08:30:26PM +0100, BALATON Zoltan wrote:
>> On Mon, 1 Dec 2025, Marc-André Lureau wrote:
>>> On Mon, Dec 1, 2025 at 5:03 PM BALATON Zoltan <balaton@eik.bme.hu> wrote:
>>>> On Mon, 1 Dec 2025, marcandre.lureau@redhat.com wrote:
>>>>> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>>>>>
>>>>> Hi,
>>>>>
>>>>> The following patch series provides a GStreamer-based audio backend,
>>>> which could
>>>>> ultimately allow QEMU to leverage the framework to support the various
>>>> audio
>>>>> subsystems and simplify the audio handling logic (timing, resampling,
>>>> mixing
>>>>> etc), as well as allow greater pipeline flexibility and customization.
>>>>
>>>> While it's good to have a GStreamer backend to integrate well into systems
>>>> already using that, this should not replace existing audio backends in
>>>> QEMU. The reason is that GStreamer has extensive dependencies that I would
>>>>
>>>
>>> GStreamer itself is not so big and doesn't have that many dependencies that
>>> qemu doesn't already have.
>>
>> Except that this proposal uses GStreamer from rust so would also pull in all
>> the rust dependencies too which is still not needed for QEMU. Saying that
>> it's optional but then you lose audio output is also not quite acceptable.
>
> In terms of replacing the existing audio backends, it would simply have to
> wait until we declare Rust to be a mandatory dependency of QEMU, before
> proposing any removal of existing backends.
>
>>>> as another audio backend but not as a replacement for QEMU's audio
>>>> handling logic and backends.
>>>
>>> It would be great if people with very specific or constrained requirements
>>> on qemu audio could check if the GStreamer backend fits their need.
>>
>> At least one of them already said it wouldn't. Also why somebody not running
>> a desktop environment that uses GStreamer would want to add that dependency
>> and use a GStreamer plugin to get the sound back to their native sound
>> service when it is probably already supported by QEMU directly? QEMU also
>> has to support Windows and macOS sound services so having a few more
>> Linux/Unix ones does not make it much more complex.
>
> GStreamer is not merely for desktop environments. It is a general purpose
> audio system, and in terms of QEMU, it is already used by the Spice server
> for video encoding purposes.  IMHO it is reasonable to consider whether
> QEMU could use GStreamer for all audio output regardless of whether it is
> running from a desktop session or not.

But it's most likely found as dependency in apps that already use other 
libraries from the same family that usually come with a certain desktop 
environment. Also spice does not want to replace all the other display 
backends or SDL does not replace other sound backends so the same way it's 
fine to add a GStreamer as another optional way to output sound but not as 
replacing the existing backends and sound infrastructure in QEMU.

> Personally my main concern with gstreamer is that when things go wrong
> it can be very difficult to understand why and thus hard to figure out
> how to fix it, unless you're pretty experienced with gstreamer.
>
> If we do consider rationalizing how many backends we have, IMHO, it
> would be desirable to retain at least one other QEMU audio backend
> that is considered simple & reliable (fool proof) to use & debug.

That probably means we should retain at least the lowest level output for 
the native sound systems of the OSes where QEMU runs but then we can also 
have other backends as the main complexity is in the audio infrastructure 
and not in the backends. What may be possible is to drop OSS and the 
mixing support that is mainly needed for OSS arguing that ALSA has 
replaced OSS on Linux and sndio replaced it on BSD and these can already 
do resampling and mixing themselves so this could simplify QEMU audio code 
to just pass data to a sound service but I'm not sure this feature isn't 
used by some people in QEMU with other backends or to record to wav for 
example. If we remove that too saying that recording can be done from the 
system native sound service then maybe no need for resampling and mixing 
in QEMU at least for output. But what about input where both the system 
and the emulated cards are limited to some specific samping rates and they 
are not set to use the same? Or do the sound services take care of that 
too and you can ask for arbitrary input rate and will it convert from 
hardware rate or you get back an error and QEMU has to handle this?

Regards,
BALATON Zoltan

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-02  7:55       ` Paolo Bonzini
@ 2025-12-02 12:03         ` BALATON Zoltan
  2025-12-02 12:25           ` Geoffrey McRae
  0 siblings, 1 reply; 51+ messages in thread
From: BALATON Zoltan @ 2025-12-02 12:03 UTC (permalink / raw)
  To: Paolo Bonzini
  Cc: Alexandre Ratchov, Marc-André Lureau, qemu-devel,
	Gerd Hoffmann, Thomas Huth, Akihiko Odaki, dirty.ice.hu,
	Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin, geoff

[-- Attachment #1: Type: text/plain, Size: 1444 bytes --]

On Tue, 2 Dec 2025, Paolo Bonzini wrote:
> On 12/1/25 21:58, Alexandre Ratchov wrote:
>> On Mon, Dec 01, 2025 at 10:20:49PM +0400, Marc-André Lureau wrote:
>>>> cases. Also when using jack you'd want to have a QEMU backend for it not
>>> It would be great if people with very specific or constrained requirements
>>> on qemu audio could check if the GStreamer backend fits their need.
>> 
>> I'm thinking mainly about their simplicity.
>> 
>> Dropping the system API backends would add an extra sophisticated
>> layer (GStreamer) between the system and the program. In theory, an
>> unlimited number of software layers may be stacked in a program, but
>> the more layers there are, the more fragile the program tends to
>> be. Based on my limited experience, when things went wrong, the system
>> backends were simpler to debug and make work than the big frameworks.
>> 
>> IMHO, the system API backends won't hurt GStreamer users, so I see no
>> reason to remove them.
>
> I mostly agree.  Perhaps the DirectSound backend could be removed by just 
> letting Windows use SDL (unlike macOS, Windows doesn't have a "native" GUI 
> layer), and the ALSA backend is also not so useful in my opinion.  But all 
> the others have a reason to be there.

ALSA is also useful as the native sound backend for Linux. I'd say it can 
already do what pulseaudio or pipewire do so those are not so useful in my 
opinion not ALSA. :-)

Regards,
BALATON Zoltan

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-02 12:03         ` BALATON Zoltan
@ 2025-12-02 12:25           ` Geoffrey McRae
  2025-12-02 12:44             ` Marc-André Lureau
  0 siblings, 1 reply; 51+ messages in thread
From: Geoffrey McRae @ 2025-12-02 12:25 UTC (permalink / raw)
  To: BALATON Zoltan
  Cc: Paolo Bonzini, Alexandre Ratchov, Marc-André Lureau,
	qemu-devel, Gerd Hoffmann, Thomas Huth, Akihiko Odaki,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin



On 2025-12-02 23:03, BALATON Zoltan wrote:
> On Tue, 2 Dec 2025, Paolo Bonzini wrote:
>> On 12/1/25 21:58, Alexandre Ratchov wrote:
>>> On Mon, Dec 01, 2025 at 10:20:49PM +0400, Marc-André Lureau wrote:
>>>>> cases. Also when using jack you'd want to have a QEMU backend for 
>>>>> it not
>>>> It would be great if people with very specific or constrained 
>>>> requirements
>>>> on qemu audio could check if the GStreamer backend fits their need.
>>> 
>>> I'm thinking mainly about their simplicity.
>>> 
>>> Dropping the system API backends would add an extra sophisticated
>>> layer (GStreamer) between the system and the program. In theory, an
>>> unlimited number of software layers may be stacked in a program, but
>>> the more layers there are, the more fragile the program tends to
>>> be. Based on my limited experience, when things went wrong, the 
>>> system
>>> backends were simpler to debug and make work than the big frameworks.
>>> 
>>> IMHO, the system API backends won't hurt GStreamer users, so I see no
>>> reason to remove them.
>> 
>> I mostly agree.  Perhaps the DirectSound backend could be removed by 
>> just letting Windows use SDL (unlike macOS, Windows doesn't have a 
>> "native" GUI layer), and the ALSA backend is also not so useful in my 
>> opinion.  But all the others have a reason to be there.
> 
> ALSA is also useful as the native sound backend for Linux. I'd say it 
> can already do what pulseaudio or pipewire do so those are not so 
> useful in my opinion not ALSA. :-)
> 
> Regards,
> BALATON Zoltan

The PipeWire and PulseAudio backends are used by a large number of users 
in the VFIO community. Removing these would be an enormous determent to 
QEMU.

Audio output from QEMU has always been problematic, but with the 
PulseAudio and later, the PipeWire interface, it became much more user 
friendly for those that wanted to configure the VM to output native 
audio into their sound plumbing.

I do not agree that ALSA is as useful as you state it is, it's dependent 
on the host system's audio hardware support. If the sound device doesn't 
support hardware mixing (almost none do anymore), or the bitrate/sample 
rate QEMU wishes to use, your out of luck.

What I do think needs fixing here is the removal of the forced S16 audio 
format, and the resampler which forces all output to 48KHz. This though 
would require changes to the SPICE protocol as currently it is fixed at 
two channel 48KHz S16 also IIRC.

IMHO adding GStreamer is unnecessary, we have the modern PipeWire 
interface which is compatible with everything. I see absolutely no 
reason to add so much complexity to the project for little to no gain.

Regards,
Geoffrey McRae (gnif)


^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-02 12:25           ` Geoffrey McRae
@ 2025-12-02 12:44             ` Marc-André Lureau
  2025-12-02 13:25               ` Geoffrey McRae
  0 siblings, 1 reply; 51+ messages in thread
From: Marc-André Lureau @ 2025-12-02 12:44 UTC (permalink / raw)
  To: Geoffrey McRae
  Cc: BALATON Zoltan, Paolo Bonzini, Alexandre Ratchov, qemu-devel,
	Gerd Hoffmann, Thomas Huth, Akihiko Odaki, dirty.ice.hu,
	Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin

[-- Attachment #1: Type: text/plain, Size: 1578 bytes --]

Hi Geoffrey

On Tue, Dec 2, 2025 at 4:31 PM Geoffrey McRae <geoff@hostfission.com> wrote:

>
> The PipeWire and PulseAudio backends are used by a large number of users
> in the VFIO community. Removing these would be an enormous determent to
> QEMU.
>

They come with GStreamer pulse/pipe elements.


>
> Audio output from QEMU has always been problematic, but with the
> PulseAudio and later, the PipeWire interface, it became much more user
> friendly for those that wanted to configure the VM to output native
> audio into their sound plumbing.
>

Could you be more specific?


> I do not agree that ALSA is as useful as you state it is, it's dependent
> on the host system's audio hardware support. If the sound device doesn't
> support hardware mixing (almost none do anymore), or the bitrate/sample
> rate QEMU wishes to use, your out of luck.
>
> What I do think needs fixing here is the removal of the forced S16 audio
> format, and the resampler which forces all output to 48KHz. This though
> would require changes to the SPICE protocol as currently it is fixed at
> two channel 48KHz S16 also IIRC.
>
>
Why is it a problem that Spice requires 48khz? Afaik, you can't have both
Spice & another backend (unlike VNC which does monitor to capture)


> IMHO adding GStreamer is unnecessary, we have the modern PipeWire
> interface which is compatible with everything. I see absolutely no
> reason to add so much complexity to the project for little to no gain.
>
>
Pipewire alone is not compatible with Windows or OSX, afaik.

[-- Attachment #2: Type: text/html, Size: 2499 bytes --]

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-02 12:44             ` Marc-André Lureau
@ 2025-12-02 13:25               ` Geoffrey McRae
  2025-12-02 14:14                 ` Marc-André Lureau
  0 siblings, 1 reply; 51+ messages in thread
From: Geoffrey McRae @ 2025-12-02 13:25 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: BALATON Zoltan, Paolo Bonzini, Alexandre Ratchov, qemu-devel,
	Gerd Hoffmann, Thomas Huth, Akihiko Odaki, dirty.ice.hu,
	Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin



On 2025-12-02 23:44, Marc-André Lureau wrote:
> Hi Geoffrey
> 
> On Tue, Dec 2, 2025 at 4:31 PM Geoffrey McRae
> <geoff@hostfission.com> wrote:
> 
>> The PipeWire and PulseAudio backends are used by a large number of
>> users
>> in the VFIO community. Removing these would be an enormous determent
>> to
>> QEMU.
> 
> They come with GStreamer pulse/pipe elements.

Yes, but through another layer of abstraction/complexity with no real 
benefit.

> 
>> Audio output from QEMU has always been problematic, but with the
>> PulseAudio and later, the PipeWire interface, it became much more
>> user
>> friendly for those that wanted to configure the VM to output native
>> audio into their sound plumbing.
> 
> Could you be more specific?

There are clock sync/drift issues with the emulated hardware device's 
audio clock and the real hardware audio clock. GStreamer won't solve 
this, it requires a tuned PID loop that resamples the audio to 
compensate for the continual drift between the emulated and hardware 
clocks. Without this, over time, the audio can and does get wildly out 
of sync eventually resulting in xruns.

All you have to do is google for "QEMU Crackling Sound". JACK, PipeWire 
and PulseAudio manage to mostly hide (not sovle) this issue from the 
user, but it still occurs. It's worse for SPICE clients as the audio 
gets buffered in the network stack rather then dropped and can lead to 
many seconds of audio latency.

As for applications, we have a large number of people using QEMU/KVM 
with full GPU pass-through for gaming workloads, many of which route the 
QEMU audio into PipeWire/JACK directly which enables the host's sound 
server to perform DSP and mixing, etc.

Others are streaming the guest via Looking Glass for the video feed, and 
using PipeWire from QEMU to feed into OBS for live streaming setups.

The flexibility that JACK & PipeWire bring to the table can not be 
overstated. From a maintenance point of view, JACK and PipeWire are only 
~800 lines of code each, fully self contained and very easy to debug.

All the audio processing/mixing/resampling/routing (and any user 
configured DSP) is fully offloaded to the host's audio server, where it 
should be.

> 
>> I do not agree that ALSA is as useful as you state it is, it's
>> dependent
>> on the host system's audio hardware support. If the sound device
>> doesn't
>> support hardware mixing (almost none do anymore), or the
>> bitrate/sample
>> rate QEMU wishes to use, your out of luck.
>> 
>> What I do think needs fixing here is the removal of the forced S16
>> audio
>> format, and the resampler which forces all output to 48KHz. This
>> though
>> would require changes to the SPICE protocol as currently it is fixed
>> at
>> two channel 48KHz S16 also IIRC.
> 
> Why is it a problem that Spice requires 48khz? Afaik, you can't have
> both Spice & another backend (unlike VNC which does monitor to
> capture)

For clients like Looking Glass that take the audio via SPICE for 
rendering locally via it's own audio devices where we do additional 
things such as tracking client/host audio clocks and resample to keep 
the audio latency consistent correcting for the clock drift as mentioned 
prior.

There are quite a lot of people also using virt-viewer with Intel GVT-g 
these days too that are also limited to 48khz S16 again due to it using 
SPICE by default.

I digress though, this is a different topic entirely and I should not 
have raised it here.

> 
>> IMHO adding GStreamer is unnecessary, we have the modern PipeWire
>> interface which is compatible with everything. I see absolutely no
>> reason to add so much complexity to the project for little to no
>> gain.
> 
> Pipewire alone is not compatible with Windows or OSX, afaik.

Yes, but there is the DirectSound audio driver for Windows, and 
CoreAudio driver for OSX. While I appreciate that DirectSound is 
deprecated, I really think that effort should be put into implementing a 
WASAPI backend for QEMU.

I really do not think that adding all the complexity of GStreamer to 
QEMU is the right way forward. We should just hand off the audio 
processing to the host system's sound server (as we do already), 
whatever it might be, and let it do the heavy lifting.

Regards,
Geoffrey McRae (gnif)


^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-02 13:25               ` Geoffrey McRae
@ 2025-12-02 14:14                 ` Marc-André Lureau
  2025-12-02 14:33                   ` Neal Gompa
                                     ` (4 more replies)
  0 siblings, 5 replies; 51+ messages in thread
From: Marc-André Lureau @ 2025-12-02 14:14 UTC (permalink / raw)
  To: Geoffrey McRae
  Cc: BALATON Zoltan, Paolo Bonzini, Alexandre Ratchov, qemu-devel,
	Gerd Hoffmann, Thomas Huth, Akihiko Odaki, dirty.ice.hu,
	Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin

Hi

On Tue, Dec 2, 2025 at 5:26 PM Geoffrey McRae <geoff@hostfission.com> wrote:
>
>
>
> On 2025-12-02 23:44, Marc-André Lureau wrote:
> > Hi Geoffrey
> >
> > On Tue, Dec 2, 2025 at 4:31 PM Geoffrey McRae
> > <geoff@hostfission.com> wrote:
> >
> >> The PipeWire and PulseAudio backends are used by a large number of
> >> users
> >> in the VFIO community. Removing these would be an enormous determent
> >> to
> >> QEMU.
> >
> > They come with GStreamer pulse/pipe elements.
>
> Yes, but through another layer of abstraction/complexity with no real
> benefit.

The benefit is that QEMU would not have to maintain 10 backends and
all the audio mixing/resampling. The QEMU code would be simpler and
more maintainable overall. GStreamer has a clear pipeline design,
better suited (optimized code etc) and you can easily modify the
pipeline settings.

>
> >
> >> Audio output from QEMU has always been problematic, but with the
> >> PulseAudio and later, the PipeWire interface, it became much more
> >> user
> >> friendly for those that wanted to configure the VM to output native
> >> audio into their sound plumbing.
> >
> > Could you be more specific?
>
> There are clock sync/drift issues with the emulated hardware device's
> audio clock and the real hardware audio clock. GStreamer won't solve
> this, it requires a tuned PID loop that resamples the audio to
> compensate for the continual drift between the emulated and hardware
> clocks. Without this, over time, the audio can and does get wildly out
> of sync eventually resulting in xruns.

That's indeed a complicated subject and hard to test & measure. Adding
some testing to our audio infra should help identify this better. Not
sure when time permits though.

It seems to me that the current QEMU audio code is using the
virtual/system clock timer to pull the data at a regular pace, which
is not in sync with the actual audio sink clock. The GStreamer
pipeline, otoh, uses the audio sink clock. But there are other
emulated devices related issues (like hda not sending data when asked,
or later, it has to be pulled regularly etc). I need to study this in
more detail, this GStreamer implementation is a bit naive there.


>
> All you have to do is google for "QEMU Crackling Sound". JACK, PipeWire
> and PulseAudio manage to mostly hide (not sovle) this issue from the
> user, but it still occurs. It's worse for SPICE clients as the audio
> gets buffered in the network stack rather then dropped and can lead to
> many seconds of audio latency.

Yes, I think synchronization of audio/video playback for remoting is
another issue, if QEMU has the audio & video frames in sync which can
provide correct timestamps.

>
> As for applications, we have a large number of people using QEMU/KVM
> with full GPU pass-through for gaming workloads, many of which route the
> QEMU audio into PipeWire/JACK directly which enables the host's sound
> server to perform DSP and mixing, etc.
>
> Others are streaming the guest via Looking Glass for the video feed, and
> using PipeWire from QEMU to feed into OBS for live streaming setups.
>
> The flexibility that JACK & PipeWire bring to the table can not be
> overstated. From a maintenance point of view, JACK and PipeWire are only
> ~800 lines of code each, fully self contained and very easy to debug.
>
> All the audio processing/mixing/resampling/routing (and any user
> configured DSP) is fully offloaded to the host's audio server, where it
> should be.

(by default QEMU is still doing resampling & mixing, and adds extra buffering)

A GStreamer backend should not be incompatible with those use cases.

[..]
> deprecated, I really think that effort should be put into implementing a
> WASAPI backend for QEMU.
>
> I really do not think that adding all the complexity of GStreamer to
> QEMU is the right way forward. We should just hand off the audio
> processing to the host system's sound server (as we do already),
> whatever it might be, and let it do the heavy lifting.

I agree with the goal that we should leave most of the work to the
host, and not have to do audio mixing resampling ourself whenever
possible. Imho, GStreamer allows us to do that in less and cleaner
code.

-- 
Marc-André Lureau


^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-02 14:14                 ` Marc-André Lureau
@ 2025-12-02 14:33                   ` Neal Gompa
  2025-12-02 14:43                   ` Geoffrey McRae
                                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 51+ messages in thread
From: Neal Gompa @ 2025-12-02 14:33 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: Geoffrey McRae, BALATON Zoltan, Paolo Bonzini, Alexandre Ratchov,
	qemu-devel, Gerd Hoffmann, Thomas Huth, Akihiko Odaki,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin

On Tue, Dec 2, 2025 at 9:15 AM Marc-André Lureau
<marcandre.lureau@gmail.com> wrote:
>
> Hi
>
> On Tue, Dec 2, 2025 at 5:26 PM Geoffrey McRae <geoff@hostfission.com> wrote:
> >
> >
> >
> > On 2025-12-02 23:44, Marc-André Lureau wrote:
> > > Hi Geoffrey
> > >
> > > On Tue, Dec 2, 2025 at 4:31 PM Geoffrey McRae
> > > <geoff@hostfission.com> wrote:
> > >
> > >> The PipeWire and PulseAudio backends are used by a large number of
> > >> users
> > >> in the VFIO community. Removing these would be an enormous determent
> > >> to
> > >> QEMU.
> > >
> > > They come with GStreamer pulse/pipe elements.
> >
> > Yes, but through another layer of abstraction/complexity with no real
> > benefit.
>
> The benefit is that QEMU would not have to maintain 10 backends and
> all the audio mixing/resampling. The QEMU code would be simpler and
> more maintainable overall. GStreamer has a clear pipeline design,
> better suited (optimized code etc) and you can easily modify the
> pipeline settings.
>
> >
> > >
> > >> Audio output from QEMU has always been problematic, but with the
> > >> PulseAudio and later, the PipeWire interface, it became much more
> > >> user
> > >> friendly for those that wanted to configure the VM to output native
> > >> audio into their sound plumbing.
> > >
> > > Could you be more specific?
> >
> > There are clock sync/drift issues with the emulated hardware device's
> > audio clock and the real hardware audio clock. GStreamer won't solve
> > this, it requires a tuned PID loop that resamples the audio to
> > compensate for the continual drift between the emulated and hardware
> > clocks. Without this, over time, the audio can and does get wildly out
> > of sync eventually resulting in xruns.
>
> That's indeed a complicated subject and hard to test & measure. Adding
> some testing to our audio infra should help identify this better. Not
> sure when time permits though.
>
> It seems to me that the current QEMU audio code is using the
> virtual/system clock timer to pull the data at a regular pace, which
> is not in sync with the actual audio sink clock. The GStreamer
> pipeline, otoh, uses the audio sink clock. But there are other
> emulated devices related issues (like hda not sending data when asked,
> or later, it has to be pulled regularly etc). I need to study this in
> more detail, this GStreamer implementation is a bit naive there.
>
>
> >
> > All you have to do is google for "QEMU Crackling Sound". JACK, PipeWire
> > and PulseAudio manage to mostly hide (not sovle) this issue from the
> > user, but it still occurs. It's worse for SPICE clients as the audio
> > gets buffered in the network stack rather then dropped and can lead to
> > many seconds of audio latency.
>
> Yes, I think synchronization of audio/video playback for remoting is
> another issue, if QEMU has the audio & video frames in sync which can
> provide correct timestamps.
>
> >
> > As for applications, we have a large number of people using QEMU/KVM
> > with full GPU pass-through for gaming workloads, many of which route the
> > QEMU audio into PipeWire/JACK directly which enables the host's sound
> > server to perform DSP and mixing, etc.
> >
> > Others are streaming the guest via Looking Glass for the video feed, and
> > using PipeWire from QEMU to feed into OBS for live streaming setups.
> >
> > The flexibility that JACK & PipeWire bring to the table can not be
> > overstated. From a maintenance point of view, JACK and PipeWire are only
> > ~800 lines of code each, fully self contained and very easy to debug.
> >
> > All the audio processing/mixing/resampling/routing (and any user
> > configured DSP) is fully offloaded to the host's audio server, where it
> > should be.
>
> (by default QEMU is still doing resampling & mixing, and adds extra buffering)
>
> A GStreamer backend should not be incompatible with those use cases.
>

Have you tried to do this with VFIO and do semi-realtime audio (e.g.
AV work, gaming) with this backend?

In my experience, gapless and "crackle-less" semi-realtime audio is
unnecessarily difficult to do well with GStreamer right now.

Don't get me wrong, I *like* GStreamer, but I don't think it's as good
in this case as it is being positioned.

> [..]
> > deprecated, I really think that effort should be put into implementing a
> > WASAPI backend for QEMU.
> >
> > I really do not think that adding all the complexity of GStreamer to
> > QEMU is the right way forward. We should just hand off the audio
> > processing to the host system's sound server (as we do already),
> > whatever it might be, and let it do the heavy lifting.
>
> I agree with the goal that we should leave most of the work to the
> host, and not have to do audio mixing resampling ourself whenever
> possible. Imho, GStreamer allows us to do that in less and cleaner
> code.
>

GStreamer is a painful dependency on non-POSIX platforms, so I don't
think that our non-POSIX consumers would be happy about this.

Personally, I'd keep around at least the SDL Audio and PipeWire
backends too. That gives decent coverage and lets people avoid
problematic build and runtime dependencies.



--
真実はいつも一つ!/ Always, there's only one truth!


^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-02 14:14                 ` Marc-André Lureau
  2025-12-02 14:33                   ` Neal Gompa
@ 2025-12-02 14:43                   ` Geoffrey McRae
  2025-12-02 14:52                   ` Markus Armbruster
                                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 51+ messages in thread
From: Geoffrey McRae @ 2025-12-02 14:43 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: BALATON Zoltan, Paolo Bonzini, Alexandre Ratchov, qemu-devel,
	Gerd Hoffmann, Thomas Huth, Akihiko Odaki, dirty.ice.hu,
	Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin

Hi,

On 2025-12-03 01:14, Marc-André Lureau wrote:
> Hi
> 
> On Tue, Dec 2, 2025 at 5:26 PM Geoffrey McRae <geoff@hostfission.com> 
> wrote:
>> 
>> 
>> 
>> On 2025-12-02 23:44, Marc-André Lureau wrote:
>> > Hi Geoffrey
>> >
>> > On Tue, Dec 2, 2025 at 4:31 PM Geoffrey McRae
>> > <geoff@hostfission.com> wrote:
>> >
>> >> The PipeWire and PulseAudio backends are used by a large number of
>> >> users
>> >> in the VFIO community. Removing these would be an enormous determent
>> >> to
>> >> QEMU.
>> >
>> > They come with GStreamer pulse/pipe elements.
>> 
>> Yes, but through another layer of abstraction/complexity with no real
>> benefit.
> 
> The benefit is that QEMU would not have to maintain 10 backends and
> all the audio mixing/resampling. The QEMU code would be simpler and
> more maintainable overall. GStreamer has a clear pipeline design,
> better suited (optimized code etc) and you can easily modify the
> pipeline settings.

Yes, I suppose I can see it from that point of view provided that the 
advanced features of GStreamer, such as user defined audio graphs, are 
never exposed to the user to configure. I can just imagine the false bug 
report nightmare this would induce (buggy plugins, etc).

> 
>> 
>> >
>> >> Audio output from QEMU has always been problematic, but with the
>> >> PulseAudio and later, the PipeWire interface, it became much more
>> >> user
>> >> friendly for those that wanted to configure the VM to output native
>> >> audio into their sound plumbing.
>> >
>> > Could you be more specific?
>> 
>> There are clock sync/drift issues with the emulated hardware device's
>> audio clock and the real hardware audio clock. GStreamer won't solve
>> this, it requires a tuned PID loop that resamples the audio to
>> compensate for the continual drift between the emulated and hardware
>> clocks. Without this, over time, the audio can and does get wildly out
>> of sync eventually resulting in xruns.
> 
> That's indeed a complicated subject and hard to test & measure. Adding
> some testing to our audio infra should help identify this better. Not
> sure when time permits though.
> 
> It seems to me that the current QEMU audio code is using the
> virtual/system clock timer to pull the data at a regular pace, which
> is not in sync with the actual audio sink clock. The GStreamer
> pipeline, otoh, uses the audio sink clock. But there are other
> emulated devices related issues (like hda not sending data when asked,
> or later, it has to be pulled regularly etc). I need to study this in
> more detail, this GStreamer implementation is a bit naive there.
> 

Yes, this is the primary issue here. I believe that solving this is more 
complicated then it appears however as headless systems that are using 
SPICE for audio wont have an audio clock to sync to.

> 
>> 
>> All you have to do is google for "QEMU Crackling Sound". JACK, 
>> PipeWire
>> and PulseAudio manage to mostly hide (not sovle) this issue from the
>> user, but it still occurs. It's worse for SPICE clients as the audio
>> gets buffered in the network stack rather then dropped and can lead to
>> many seconds of audio latency.
> 
> Yes, I think synchronization of audio/video playback for remoting is
> another issue, if QEMU has the audio & video frames in sync which can
> provide correct timestamps.

Note that I am referring to a SPICE client that only subscribes to the 
audio stream and not video, as is the case for Looking Glass as it uses 
an out of band mechanism to obtain the passthrough GPUs output. This is 
also occurs on the local host via a unix socket.

I do not think this aspect can be solved in QEMU, but rather must be 
solved in the SPICE client

See here if you're interested how we did this:
https://github.com/gnif/LookingGlass/blob/53bfb6547f2b7abd6c183192e13a57068c1677ea/client/src/audio.c

> 
>> 
>> As for applications, we have a large number of people using QEMU/KVM
>> with full GPU pass-through for gaming workloads, many of which route 
>> the
>> QEMU audio into PipeWire/JACK directly which enables the host's sound
>> server to perform DSP and mixing, etc.
>> 
>> Others are streaming the guest via Looking Glass for the video feed, 
>> and
>> using PipeWire from QEMU to feed into OBS for live streaming setups.
>> 
>> The flexibility that JACK & PipeWire bring to the table can not be
>> overstated. From a maintenance point of view, JACK and PipeWire are 
>> only
>> ~800 lines of code each, fully self contained and very easy to debug.
>> 
>> All the audio processing/mixing/resampling/routing (and any user
>> configured DSP) is fully offloaded to the host's audio server, where 
>> it
>> should be.
> 
> (by default QEMU is still doing resampling & mixing, and adds extra 
> buffering)
> 
> A GStreamer backend should not be incompatible with those use cases.
> 

In that case, i'd suggest that if possible the GStreamber back end 
maintains the same port and node names it presents to Jack/PipeWire to 
make the transition from the other audio backends to GStreamer as 
painless as possible.

> [..]
>> deprecated, I really think that effort should be put into implementing 
>> a
>> WASAPI backend for QEMU.
>> 
>> I really do not think that adding all the complexity of GStreamer to
>> QEMU is the right way forward. We should just hand off the audio
>> processing to the host system's sound server (as we do already),
>> whatever it might be, and let it do the heavy lifting.
> 
> I agree with the goal that we should leave most of the work to the
> host, and not have to do audio mixing resampling ourself whenever
> possible. Imho, GStreamer allows us to do that in less and cleaner
> code.

Great, as long as we don't let user's configure GStreamer's more 
advanced features its should be ok then.


^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-02 14:14                 ` Marc-André Lureau
  2025-12-02 14:33                   ` Neal Gompa
  2025-12-02 14:43                   ` Geoffrey McRae
@ 2025-12-02 14:52                   ` Markus Armbruster
  2025-12-03  9:19                     ` Akihiko Odaki
  2025-12-02 15:39                   ` Christian Schoenebeck
  2025-12-03  8:06                   ` Alexandre Ratchov
  4 siblings, 1 reply; 51+ messages in thread
From: Markus Armbruster @ 2025-12-02 14:52 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: Geoffrey McRae, BALATON Zoltan, Paolo Bonzini, Alexandre Ratchov,
	qemu-devel, Gerd Hoffmann, Thomas Huth, Akihiko Odaki,
	dirty.ice.hu, Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin

Marc-André Lureau <marcandre.lureau@gmail.com> writes:

> Hi
>
> On Tue, Dec 2, 2025 at 5:26 PM Geoffrey McRae <geoff@hostfission.com> wrote:
>>
>>
>>
>> On 2025-12-02 23:44, Marc-André Lureau wrote:
>> > Hi Geoffrey
>> >
>> > On Tue, Dec 2, 2025 at 4:31 PM Geoffrey McRae
>> > <geoff@hostfission.com> wrote:
>> >
>> >> The PipeWire and PulseAudio backends are used by a large number of
>> >> users
>> >> in the VFIO community. Removing these would be an enormous determent
>> >> to
>> >> QEMU.
>> >
>> > They come with GStreamer pulse/pipe elements.
>>
>> Yes, but through another layer of abstraction/complexity with no real
>> benefit.
>
> The benefit is that QEMU would not have to maintain 10 backends and

Twelve according to the QAPI schema.

> all the audio mixing/resampling. The QEMU code would be simpler and
> more maintainable overall.

This matters.

The question can't be whether some QEMU feature is useful to somebody
(it basically always is).  It must be whether it is worth its keep.

Maintaining code is not free.  Easy to forget when somebody else does
the actual work quietly and well.

I'm not qualified to judge either utility or maintenance of audio
drivers.  However, I trust our long-serving maintainers there.

>                            GStreamer has a clear pipeline design,
> better suited (optimized code etc) and you can easily modify the
> pipeline settings.

[...]



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-02 14:14                 ` Marc-André Lureau
                                     ` (2 preceding siblings ...)
  2025-12-02 14:52                   ` Markus Armbruster
@ 2025-12-02 15:39                   ` Christian Schoenebeck
  2025-12-03  8:06                   ` Alexandre Ratchov
  4 siblings, 0 replies; 51+ messages in thread
From: Christian Schoenebeck @ 2025-12-02 15:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Geoffrey McRae, BALATON Zoltan, Paolo Bonzini, Alexandre Ratchov,
	qemu-devel, Gerd Hoffmann, Thomas Huth, Akihiko Odaki,
	dirty.ice.hu, Philippe Mathieu-Daudé, Volker Rümelin,
	Marc-André Lureau

On Tuesday, 2 December 2025 15:14:48 CET Marc-André Lureau wrote:
> On Tue, Dec 2, 2025 at 5:26 PM Geoffrey McRae <geoff@hostfission.com> wrote:
> > On 2025-12-02 23:44, Marc-André Lureau wrote:
> > > Hi Geoffrey
> > > 
> > > On Tue, Dec 2, 2025 at 4:31 PM Geoffrey McRae
> > > 
> > > <geoff@hostfission.com> wrote:
> > >> The PipeWire and PulseAudio backends are used by a large number of
> > >> users
> > >> in the VFIO community. Removing these would be an enormous determent
> > >> to
> > >> QEMU.
> > > 
> > > They come with GStreamer pulse/pipe elements.
> > 
> > Yes, but through another layer of abstraction/complexity with no real
> > benefit.
> 
> The benefit is that QEMU would not have to maintain 10 backends and
> all the audio mixing/resampling. The QEMU code would be simpler and
> more maintainable overall. GStreamer has a clear pipeline design,
> better suited (optimized code etc) and you can easily modify the
> pipeline settings.

Nobody questions that it could reduce QEMU code. But it would move complexity 
to users by forcing them to add another layer. Many sound systems allow to be 
layered on top of other sound systems for a long time. But even only one 
additional layer can make it very challenging to find out why you end up with 
a certain undesired setup like unexpected sample rate or bit depth (wrong USB 
profile chosen of external audio device during USB enumeration? some default 
plugin or mixer element of a layer doing resampling?), high latency 
(additional buffers with higher size on some layer? where and why?) or even 
xruns.

/Christian




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-02 14:14                 ` Marc-André Lureau
                                     ` (3 preceding siblings ...)
  2025-12-02 15:39                   ` Christian Schoenebeck
@ 2025-12-03  8:06                   ` Alexandre Ratchov
  4 siblings, 0 replies; 51+ messages in thread
From: Alexandre Ratchov @ 2025-12-03  8:06 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: Geoffrey McRae, BALATON Zoltan, Paolo Bonzini, qemu-devel,
	Gerd Hoffmann, Thomas Huth, Akihiko Odaki, dirty.ice.hu,
	Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin

On Tue, Dec 02, 2025 at 06:14:48PM +0400, Marc-André Lureau wrote:
> Hi
> 
> On Tue, Dec 2, 2025 at 5:26 PM Geoffrey McRae <geoff@hostfission.com> wrote:
> >
> >
> >
> > On 2025-12-02 23:44, Marc-André Lureau wrote:
> > > Hi Geoffrey
> > >
> > > On Tue, Dec 2, 2025 at 4:31 PM Geoffrey McRae
> > > <geoff@hostfission.com> wrote:
> > >
> > >> The PipeWire and PulseAudio backends are used by a large number of
> > >> users
> > >> in the VFIO community. Removing these would be an enormous determent
> > >> to
> > >> QEMU.
> > >
> > > They come with GStreamer pulse/pipe elements.
> >
> > Yes, but through another layer of abstraction/complexity with no real
> > benefit.
> 
> The benefit is that QEMU would not have to maintain 10 backends and
> all the audio mixing/resampling.

Well, for open-source projects it depends on the maintainers: if they
all shift their interrest/efforts toward a single GStreamer backend it
will be a win (many devs maintaining a single backend). If they don't,
we'd end-up with one backend maintained by one person.

> The QEMU code would be simpler and
> more maintainable overall. GStreamer has a clear pipeline design,
> better suited (optimized code etc) and you can easily modify the
> pipeline settings.
> 

I don't know if it would be more maintainable.  The idea is appealing,
but to my experience, fixing audio glitches, timing issues, unexpected
latency, subtle differences between OSes and alike have been painful
compared to fixing similar code that uses the native APIs.

My 2 cents.


^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 00/24] audio: add GStreamer backend
  2025-12-02 14:52                   ` Markus Armbruster
@ 2025-12-03  9:19                     ` Akihiko Odaki
  0 siblings, 0 replies; 51+ messages in thread
From: Akihiko Odaki @ 2025-12-03  9:19 UTC (permalink / raw)
  To: Markus Armbruster, Marc-André Lureau
  Cc: Geoffrey McRae, BALATON Zoltan, Paolo Bonzini, Alexandre Ratchov,
	qemu-devel, Gerd Hoffmann, Thomas Huth, dirty.ice.hu,
	Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin

On 2025/12/02 23:52, Markus Armbruster wrote:
> Marc-André Lureau <marcandre.lureau@gmail.com> writes:
> 
>> Hi
>>
>> On Tue, Dec 2, 2025 at 5:26 PM Geoffrey McRae <geoff@hostfission.com> wrote:
>>>
>>>
>>>
>>> On 2025-12-02 23:44, Marc-André Lureau wrote:
>>>> Hi Geoffrey
>>>>
>>>> On Tue, Dec 2, 2025 at 4:31 PM Geoffrey McRae
>>>> <geoff@hostfission.com> wrote:
>>>>
>>>>> The PipeWire and PulseAudio backends are used by a large number of
>>>>> users
>>>>> in the VFIO community. Removing these would be an enormous determent
>>>>> to
>>>>> QEMU.
>>>>
>>>> They come with GStreamer pulse/pipe elements.
>>>
>>> Yes, but through another layer of abstraction/complexity with no real
>>> benefit.
>>
>> The benefit is that QEMU would not have to maintain 10 backends and
> 
> Twelve according to the QAPI schema.
> 
>> all the audio mixing/resampling. The QEMU code would be simpler and
>> more maintainable overall.
> 
> This matters.
> 
> The question can't be whether some QEMU feature is useful to somebody
> (it basically always is).  It must be whether it is worth its keep.
> 
> Maintaining code is not free.  Easy to forget when somebody else does
> the actual work quietly and well.
> 
> I'm not qualified to judge either utility or maintenance of audio
> drivers.  However, I trust our long-serving maintainers there.

If someone touches the Core Audio backend after GStreamer gets in, my 
first question as a reviewer may be: why don't they use GStreamer instead?

GStreamer has mixing/resampling and Core Audio code that is:
- more optimized (e.g., SIMD resampling),
- better maintained, and
- has more features (GStreamer seems to support microphone/source while 
QEMU's coreaudio backend doesn't).

The reasons _not_ to use GStreamer I can come up with are:
- You don't have GStreamer installed. However I guess it will be 
automatically installed when doing "brew install qemu" once the 
GStreamer backend gets in.
- Bugs in GStreamer or the glue code between QEMU and GStreamer. But of 
course the code that connects QEMU's mixing/resampling has another set 
of bugs.

I think there is a good chance that nobody notices if the coreaudio 
backend breaks once GStreamer becomes the default.

Regards,
Akihiko Odaki


^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 02/24] audio: remove obsolete/obscure functions
  2025-12-01 11:22 ` [RFC 02/24] audio: remove obsolete/obscure functions marcandre.lureau
@ 2025-12-10 14:02   ` Akihiko Odaki
  0 siblings, 0 replies; 51+ messages in thread
From: Akihiko Odaki @ 2025-12-10 14:02 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Alexandre Ratchov, dirty.ice.hu,
	Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin

On 2025/12/01 20:22, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> AUD_init_time_stamp_{in,out} and AUD_get_elapsed_usec_{in,out} are only
> used by the adlib device. The result isn't actually being used since
> ADLIB_KILL_TIMERS was set some 20y ago. Let's drop this dead code now.
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>   audio/audio_template.h | 34 ----------------------------------
>   include/qemu/audio.h   |  6 ------
>   hw/audio/adlib.c       | 18 +-----------------
>   3 files changed, 1 insertion(+), 57 deletions(-)
> 
> diff --git a/audio/audio_template.h b/audio/audio_template.h
> index 7a8c431f2d..1ab3c47fd7 100644
> --- a/audio/audio_template.h
> +++ b/audio/audio_template.h
> @@ -569,40 +569,6 @@ bool glue(AUD_is_active_, TYPE)(SW *sw)
>       return sw ? sw->active : 0;
>   }
>   
> -void glue (AUD_init_time_stamp_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts)
> -{
> -    if (!sw) {
> -        return;
> -    }
> -
> -    ts->old_ts = sw->hw->ts_helper;
> -}
> -
> -uint64_t glue (AUD_get_elapsed_usec_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts)
> -{
> -    uint64_t delta, cur_ts, old_ts;
> -
> -    if (!sw) {
> -        return 0;
> -    }
> -
> -    cur_ts = sw->hw->ts_helper;
> -    old_ts = ts->old_ts;

It seems old_ts of QEMUAudioTimeStamp is no longer used.

Regards,
Akihiko Odaki

> -    /* dolog ("cur %" PRId64 " old %" PRId64 "\n", cur_ts, old_ts); */
> -
> -    if (cur_ts >= old_ts) {
> -        delta = cur_ts - old_ts;
> -    } else {
> -        delta = UINT64_MAX - old_ts + cur_ts;
> -    }
> -
> -    if (!delta) {
> -        return 0;
> -    }
> -
> -    return muldiv64 (delta, sw->hw->info.freq, 1000000);
> -}
> -
>   #undef TYPE
>   #undef HW
>   #undef SW
> diff --git a/include/qemu/audio.h b/include/qemu/audio.h
> index c56af895d6..2562710bec 100644
> --- a/include/qemu/audio.h
> +++ b/include/qemu/audio.h
> @@ -71,9 +71,6 @@ int  AUD_get_buffer_size_out (SWVoiceOut *sw);
>   void AUD_set_active_out(SWVoiceOut *sw, bool on);
>   bool AUD_is_active_out(SWVoiceOut *sw);
>   
> -void     AUD_init_time_stamp_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts);
> -uint64_t AUD_get_elapsed_usec_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts);
> -
>   #define AUDIO_MAX_CHANNELS 16
>   typedef struct Volume {
>       bool mute;
> @@ -112,9 +109,6 @@ size_t AUD_read (SWVoiceIn *sw, void *pcm_buf, size_t size);
>   void AUD_set_active_in(SWVoiceIn *sw, bool on);
>   bool AUD_is_active_in(SWVoiceIn *sw);
>   
> -void     AUD_init_time_stamp_in (SWVoiceIn *sw, QEMUAudioTimeStamp *ts);
> -uint64_t AUD_get_elapsed_usec_in (SWVoiceIn *sw, QEMUAudioTimeStamp *ts);
> -
>   void audio_cleanup(void);
>   
>   typedef struct st_sample st_sample;
> diff --git a/hw/audio/adlib.c b/hw/audio/adlib.c
> index 19d3a5f128..2a2fe7d04f 100644
> --- a/hw/audio/adlib.c
> +++ b/hw/audio/adlib.c
> @@ -34,8 +34,6 @@
>   
>   #define DEBUG 0
>   
> -#define ADLIB_KILL_TIMERS 1
> -
>   #define ADLIB_DESC "Yamaha YM3812 (OPL2)"
>   
>   #if DEBUG
> @@ -71,7 +69,6 @@ struct AdlibState {
>       uint64_t dexp[2];
>       SWVoiceOut *voice;
>       int left, pos, samples;
> -    QEMUAudioTimeStamp ats;
>       FM_OPL *opl;
>       PortioList port_list;
>   };
> @@ -88,19 +85,7 @@ static void adlib_kill_timers (AdlibState *s)
>   
>       for (i = 0; i < 2; ++i) {
>           if (s->ticking[i]) {
> -            uint64_t delta;
> -
> -            delta = AUD_get_elapsed_usec_out (s->voice, &s->ats);
> -            ldebug (
> -                "delta = %f dexp = %f expired => %d",
> -                delta / 1000000.0,
> -                s->dexp[i] / 1000000.0,
> -                delta >= s->dexp[i]
> -                );
> -            if (ADLIB_KILL_TIMERS || delta >= s->dexp[i]) {
> -                adlib_stop_opl_timer (s, i);
> -                AUD_init_time_stamp_out (s->voice, &s->ats);
> -            }
> +            adlib_stop_opl_timer(s, i);
>           }
>       }
>   }
> @@ -149,7 +134,6 @@ static void timer_handler (void *opaque, int c, double interval_Sec)
>   #endif
>   
>       s->dexp[n] = interval_Sec * 1000000.0;
> -    AUD_init_time_stamp_out (s->voice, &s->ats);
>   }
>   
>   static int write_audio (AdlibState *s, int samples)



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 03/24] audio/dbus: make "dbus" the default backend when using -display dbus
  2025-12-01 11:22 ` [RFC 03/24] audio/dbus: make "dbus" the default backend when using -display dbus marcandre.lureau
@ 2025-12-10 14:03   ` Akihiko Odaki
  0 siblings, 0 replies; 51+ messages in thread
From: Akihiko Odaki @ 2025-12-10 14:03 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Alexandre Ratchov, dirty.ice.hu,
	Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin

On 2025/12/01 20:22, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Set "using_dbus_display" during early_dbus_init(), so that we can try to
> create the "dbus" audio backend by default from audio_prio_list.
> 
> This makes dbus audio work by default when using an audio device,
> without having to setup and wire up the -audiodev manually.
> 
> The added FIXME is addressed in the following commits.
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>   audio/audio.c     |  3 +++
>   audio/dbusaudio.c |  8 +++++++-
>   ui/dbus.c         | 17 +++++++++++++----
>   3 files changed, 23 insertions(+), 5 deletions(-)
> 
> diff --git a/audio/audio.c b/audio/audio.c
> index 86e674410a..0f992a775c 100644
> --- a/audio/audio.c
> +++ b/audio/audio.c
> @@ -57,6 +57,9 @@
>       that we generate the list.
>   */
>   const char *audio_prio_list[] = {
> +#ifdef CONFIG_GIO
> +    "dbus",
> +#endif
>       "spice",
>       CONFIG_AUDIO_DRIVERS
>       "none",
> diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c
> index d729a810aa..1fe7c4ed64 100644
> --- a/audio/dbusaudio.c
> +++ b/audio/dbusaudio.c
> @@ -32,6 +32,7 @@
>   #endif
>   
>   #include "ui/dbus.h"
> +#include "ui/dbus-display.h"
>   #include "ui/dbus-display1.h"
>   
>   #define AUDIO_CAP "dbus"
> @@ -408,8 +409,13 @@ dbus_enable_in(HWVoiceIn *hw, bool enable)
>   static void *
>   dbus_audio_init(Audiodev *dev, Error **errp)
>   {
> -    DBusAudio *da = g_new0(DBusAudio, 1);
> +    DBusAudio *da;
>   
> +    if (!qemu_using_dbus_display(errp)) {
> +        return NULL;
> +    }
> +
> +    da = g_new0(DBusAudio, 1);
>       da->dev = dev;
>       da->out_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
>                                                   g_free, g_object_unref);
> diff --git a/ui/dbus.c b/ui/dbus.c
> index d2dff33258..45fb3c1aa3 100644
> --- a/ui/dbus.c
> +++ b/ui/dbus.c
> @@ -35,6 +35,7 @@
>   #include "ui/egl-context.h"
>   #endif
>   #include "qemu/audio.h"
> +#include "audio/audio_int.h" /* FIXME: use QOM dynamic cast instead of drv->name */
>   #include "qapi/error.h"
>   #include "trace.h"
>   
> @@ -218,12 +219,20 @@ dbus_display_complete(UserCreatable *uc, Error **errp)
>           return;
>       }
>   
> +    AudioBackend *audio_be = audio_get_default_audio_be(NULL);

Here is a mixed declaration disallowed by docs/devel/style.rst

Regards,
Akihiko Odaki

> +    if (audio_be && !g_str_equal(audio_be->drv->name, "dbus")) {
> +        audio_be = NULL;
> +    }
>       if (dd->audiodev && *dd->audiodev) {
> -        AudioBackend *audio_be = audio_be_by_name(dd->audiodev, errp);
> -        if (!audio_be || !audio_be_set_dbus_server(audio_be, dd->server, dd->p2p, errp)) {
> +        audio_be = audio_be_by_name(dd->audiodev, errp);
> +        if (!audio_be) {
>               return;
>           }
>       }
> +    if (audio_be && !audio_be_set_dbus_server(audio_be, dd->server, dd->p2p, errp)) {
> +        return;
> +    }
> +
>   
>       consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
>       for (idx = 0;; idx++) {
> @@ -475,6 +484,8 @@ early_dbus_init(DisplayOptions *opts)
>   #endif
>       }
>   
> +    using_dbus_display = 1;
> +
>       type_register_static(&dbus_vc_type_info);
>   }
>   
> @@ -488,8 +499,6 @@ dbus_init(DisplayState *ds, DisplayOptions *opts)
>           exit(1);
>       }
>   
> -    using_dbus_display = 1;
> -
>       object_new_with_props(TYPE_DBUS_DISPLAY,
>                             object_get_objects_root(),
>                             "dbus-display", &error_fatal,



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: [RFC 05/24] audio: introduce AudioDriver
  2025-12-01 11:22 ` [RFC 05/24] audio: introduce AudioDriver marcandre.lureau
@ 2025-12-11  5:22   ` Akihiko Odaki
  0 siblings, 0 replies; 51+ messages in thread
From: Akihiko Odaki @ 2025-12-11  5:22 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel
  Cc: Gerd Hoffmann, Thomas Huth, Alexandre Ratchov, dirty.ice.hu,
	Christian Schoenebeck, Philippe Mathieu-Daudé,
	Volker Rümelin

On 2025/12/01 20:22, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Introduce a sub-class for current "audio_driver" based implementations.
> Future AudioBackend implementations can do without it.
> 
> Next cleanup will actually remove "audio_driver" struct altogether and
> make the subclass proper QOM objects.
> 
> Public APIs still rely on backend being an AudioDriver. They will
> assert() if not. This will be addressed later to allow other backends.

Thank you for this patch. I agree with the overall direction of 
subclassing and cleaning up the old audio_driver struct. However, I have 
a concern about the naming of the new subclass as "AudioDriver".

"Driver" is overloaded with slightly different meanings. Traditionally, 
"driver" means a software component that connects QEMU to a sound 
platform (e.g., "wav driver"). Although audio_driver is going to be 
removed, this is still reflected in the user-facing term AudiodevDriver.

On the other hand, an AudioDriver is an "object" so it contains states 
and there can be multiple instances.

This overloaded semantics of "driver" leads to a confusion; the new 
"gstreamer" is an AudiodevDriver for the user interface but, internally, 
it doesn't inherit AudioDriver.

It also feels strange to refer to multiple instances as several 
"AudioDrivers" when we instinctively think of a driver as a singular 
type (i.e., there is only one "wav" driver).

So I think "AudioDriver" should be renamed to characterize its instances 
instead of static code while differentiating it from GStreamer.

The key difference between the existing implementations (which become 
the new subclass) and GStreamer is that the existing ones essentially 
represent ends of an audio stream. GStreamer calls them sinks or 
sources, but we need one term that represents both.

The idea I came up with is "endpoint" (i.e., "AudioEndpoint"). It is 
natural to say there are several "AudioEnpoints" to express instances, 
and it is clearly distinguished from GStreamer that provides a whole 
graph. You may have a better idea though.

> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>   audio/audio_int.h      |  25 +++++---
>   audio/audio_template.h |  18 +++---
>   include/qemu/audio.h   |  17 +++++-
>   audio/alsaaudio.c      |   2 +-
>   audio/audio.c          | 126 +++++++++++++++++++++++++++++++----------
>   audio/dbusaudio.c      |   8 +--
>   audio/ossaudio.c       |   4 +-
>   ui/dbus.c              |   3 +-
>   8 files changed, 142 insertions(+), 61 deletions(-)
> 
> diff --git a/audio/audio_int.h b/audio/audio_int.h
> index b2b8002477..e1f962875f 100644
> --- a/audio/audio_int.h
> +++ b/audio/audio_int.h
> @@ -61,7 +61,7 @@ struct audio_pcm_info {
>       int swap_endianness;
>   };
>   
> -typedef struct AudioBackend AudioBackend;
> +typedef struct AudioDriver AudioDriver;

You don't have to write typedef your own; OBJECT_DECLARE_TYPE() will 
automatically create them.

Regards,
Akihiko Odaki

>   typedef struct SWVoiceCap SWVoiceCap;
>   
>   typedef struct STSampleBuffer {
> @@ -70,7 +70,7 @@ typedef struct STSampleBuffer {
>   } STSampleBuffer;
>   
>   typedef struct HWVoiceOut {
> -    AudioBackend *s;
> +    AudioDriver *s;
>       bool enabled;
>       int poll_mode;
>       bool pending_disable;
> @@ -91,7 +91,7 @@ typedef struct HWVoiceOut {
>   } HWVoiceOut;
>   
>   typedef struct HWVoiceIn {
> -    AudioBackend *s;
> +    AudioDriver *s;
>       bool enabled;
>       int poll_mode;
>       struct audio_pcm_info info;
> @@ -112,7 +112,7 @@ typedef struct HWVoiceIn {
>   } HWVoiceIn;
>   
>   struct SWVoiceOut {
> -    AudioBackend *s;
> +    AudioDriver *s;
>       struct audio_pcm_info info;
>       t_sample *conv;
>       STSampleBuffer resample_buf;
> @@ -128,7 +128,7 @@ struct SWVoiceOut {
>   };
>   
>   struct SWVoiceIn {
> -    AudioBackend *s;
> +    AudioDriver *s;
>       bool active;
>       struct audio_pcm_info info;
>       void *rate;
> @@ -241,8 +241,12 @@ struct SWVoiceCap {
>       QLIST_ENTRY (SWVoiceCap) entries;
>   };
>   
> -typedef struct AudioBackend {
> -    Object parent;
> +struct AudioDriverClass {
> +    AudioBackendClass parent_class;
> +};
> +
> +struct AudioDriver {
> +    AudioBackend parent_obj;
>   
>       struct audio_driver *drv;
>       Audiodev *dev;
> @@ -260,7 +264,7 @@ typedef struct AudioBackend {
>       bool timer_running;
>       uint64_t timer_last;
>       VMChangeStateEntry *vmse;
> -} AudioBackend;
> +};
>   
>   extern const struct mixeng_volume nominal_volume;
>   
> @@ -273,7 +277,7 @@ void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len);
>   
>   int audio_bug (const char *funcname, int cond);
>   
> -void audio_run(AudioBackend *s, const char *msg);
> +void audio_run(AudioDriver *s, const char *msg);
>   
>   const char *audio_application_name(void);
>   
> @@ -326,4 +330,7 @@ void audio_create_pdos(Audiodev *dev);
>   AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev);
>   AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev);
>   
> +#define TYPE_AUDIO_DRIVER "audio-driver"
> +OBJECT_DECLARE_TYPE(AudioDriver, AudioDriverClass, AUDIO_DRIVER)
> +
>   #endif /* QEMU_AUDIO_INT_H */
> diff --git a/audio/audio_template.h b/audio/audio_template.h
> index 1ab3c47fd7..925a878f6d 100644
> --- a/audio/audio_template.h
> +++ b/audio/audio_template.h
> @@ -36,7 +36,7 @@
>   #define HWBUF hw->conv_buf
>   #endif
>   
> -static void glue(audio_init_nb_voices_, TYPE)(AudioBackend *s,
> +static void glue(audio_init_nb_voices_, TYPE)(AudioDriver *s,
>                                                 struct audio_driver *drv, int min_voices)
>   {
>       int max_voices = glue (drv->max_voices_, TYPE);
> @@ -221,7 +221,7 @@ static void glue (audio_pcm_hw_del_sw_, TYPE) (SW *sw)
>   static void glue (audio_pcm_hw_gc_, TYPE) (HW **hwp)
>   {
>       HW *hw = *hwp;
> -    AudioBackend *s = hw->s;
> +    AudioDriver *s = hw->s;
>   
>       if (!hw->sw_head.lh_first) {
>   #ifdef DAC
> @@ -236,12 +236,12 @@ static void glue (audio_pcm_hw_gc_, TYPE) (HW **hwp)
>       }
>   }
>   
> -static HW *glue(audio_pcm_hw_find_any_, TYPE)(AudioBackend *s, HW *hw)
> +static HW *glue(audio_pcm_hw_find_any_, TYPE)(AudioDriver *s, HW *hw)
>   {
>       return hw ? hw->entries.le_next : glue (s->hw_head_, TYPE).lh_first;
>   }
>   
> -static HW *glue(audio_pcm_hw_find_any_enabled_, TYPE)(AudioBackend *s, HW *hw)
> +static HW *glue(audio_pcm_hw_find_any_enabled_, TYPE)(AudioDriver *s, HW *hw)
>   {
>       while ((hw = glue(audio_pcm_hw_find_any_, TYPE)(s, hw))) {
>           if (hw->enabled) {
> @@ -251,7 +251,7 @@ static HW *glue(audio_pcm_hw_find_any_enabled_, TYPE)(AudioBackend *s, HW *hw)
>       return NULL;
>   }
>   
> -static HW *glue(audio_pcm_hw_find_specific_, TYPE)(AudioBackend *s, HW *hw,
> +static HW *glue(audio_pcm_hw_find_specific_, TYPE)(AudioDriver *s, HW *hw,
>                                                      struct audsettings *as)
>   {
>       while ((hw = glue(audio_pcm_hw_find_any_, TYPE)(s, hw))) {
> @@ -262,7 +262,7 @@ static HW *glue(audio_pcm_hw_find_specific_, TYPE)(AudioBackend *s, HW *hw,
>       return NULL;
>   }
>   
> -static HW *glue(audio_pcm_hw_add_new_, TYPE)(AudioBackend *s,
> +static HW *glue(audio_pcm_hw_add_new_, TYPE)(AudioDriver *s,
>                                                struct audsettings *as)
>   {
>       HW *hw;
> @@ -398,7 +398,7 @@ AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev)
>       abort();
>   }
>   
> -static HW *glue(audio_pcm_hw_add_, TYPE)(AudioBackend *s, struct audsettings *as)
> +static HW *glue(audio_pcm_hw_add_, TYPE)(AudioDriver *s, struct audsettings *as)
>   {
>       HW *hw;
>       AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev);
> @@ -424,7 +424,7 @@ static HW *glue(audio_pcm_hw_add_, TYPE)(AudioBackend *s, struct audsettings *as
>   }
>   
>   static SW *glue(audio_pcm_create_voice_pair_, TYPE)(
> -    AudioBackend *s,
> +    AudioDriver *s,
>       const char *sw_name,
>       struct audsettings *as
>       )
> @@ -494,7 +494,7 @@ SW *glue (AUD_open_, TYPE) (
>       struct audsettings *as
>       )
>   {
> -    AudioBackend *s = be;
> +    AudioDriver *s = AUDIO_DRIVER(be);
>       AudiodevPerDirectionOptions *pdo;
>   
>       if (audio_bug(__func__, !be || !name || !callback_fn || !as)) {
> diff --git a/include/qemu/audio.h b/include/qemu/audio.h
> index 2562710bec..f83f8326ab 100644
> --- a/include/qemu/audio.h
> +++ b/include/qemu/audio.h
> @@ -44,11 +44,21 @@ typedef struct audsettings {
>   typedef struct SWVoiceOut SWVoiceOut;
>   typedef struct SWVoiceIn SWVoiceIn;
>   
> -struct AudioBackendClass {
> +typedef struct AudioBackend {
> +    Object parent_obj;
> +} AudioBackend;
> +
> +typedef struct AudioBackendClass {
>       ObjectClass parent_class;
> -};
>   
> -typedef struct AudioBackend AudioBackend;
> +    const char *(*get_id)(AudioBackend *be);
> +#ifdef CONFIG_GIO
> +    bool (*set_dbus_server)(AudioBackend *be,
> +                            GDBusObjectManagerServer *manager,
> +                            bool p2p,
> +                            Error **errp);
> +#endif
> +} AudioBackendClass;
>   
>   typedef struct QEMUAudioTimeStamp {
>       uint64_t old_ts;
> @@ -129,6 +139,7 @@ AudioBackend *audio_be_by_name(const char *name, Error **errp);
>   AudioBackend *audio_get_default_audio_be(Error **errp);
>   const char *audio_be_get_id(AudioBackend *be);
>   #ifdef CONFIG_GIO
> +bool audio_be_can_set_dbus_server(AudioBackend *be);
>   bool audio_be_set_dbus_server(AudioBackend *be,
>                                 GDBusObjectManagerServer *server,
>                                 bool p2p,
> diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c
> index 7d7da576dc..278c74c5de 100644
> --- a/audio/alsaaudio.c
> +++ b/audio/alsaaudio.c
> @@ -41,7 +41,7 @@ struct pollhlp {
>       struct pollfd *pfds;
>       int count;
>       int mask;
> -    AudioBackend *s;
> +    AudioDriver *s;
>   };
>   
>   typedef struct ALSAVoiceOut {
> diff --git a/audio/audio.c b/audio/audio.c
> index 0f992a775c..4a43761528 100644
> --- a/audio/audio.c
> +++ b/audio/audio.c
> @@ -36,6 +36,7 @@
>   #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"
> @@ -383,7 +384,7 @@ void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
>   /*
>    * Capture
>    */
> -static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioBackend *s,
> +static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioDriver *s,
>                                                           struct audsettings *as)
>   {
>       CaptureVoiceOut *cap;
> @@ -463,7 +464,7 @@ static void audio_detach_capture (HWVoiceOut *hw)
>   
>   static int audio_attach_capture (HWVoiceOut *hw)
>   {
> -    AudioBackend *s = hw->s;
> +    AudioDriver *s = hw->s;
>       CaptureVoiceOut *cap;
>   
>       audio_detach_capture (hw);
> @@ -801,7 +802,7 @@ static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
>   /*
>    * Timer
>    */
> -static int audio_is_timer_needed(AudioBackend *s)
> +static int audio_is_timer_needed(AudioDriver *s)
>   {
>       HWVoiceIn *hwi = NULL;
>       HWVoiceOut *hwo = NULL;
> @@ -819,7 +820,7 @@ static int audio_is_timer_needed(AudioBackend *s)
>       return 0;
>   }
>   
> -static void audio_reset_timer(AudioBackend *s)
> +static void audio_reset_timer(AudioDriver *s)
>   {
>       if (audio_is_timer_needed(s)) {
>           timer_mod_anticipate_ns(s->ts,
> @@ -841,7 +842,7 @@ static void audio_reset_timer(AudioBackend *s)
>   static void audio_timer (void *opaque)
>   {
>       int64_t now, diff;
> -    AudioBackend *s = opaque;
> +    AudioDriver *s = opaque;
>   
>       now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
>       diff = now - s->timer_last;
> @@ -924,7 +925,7 @@ void AUD_set_active_out(SWVoiceOut *sw, bool on)
>   
>       hw = sw->hw;
>       if (sw->active != on) {
> -        AudioBackend *s = sw->s;
> +        AudioDriver *s = sw->s;
>           SWVoiceOut *temp_sw;
>           SWVoiceCap *sc;
>   
> @@ -972,7 +973,7 @@ void AUD_set_active_in(SWVoiceIn *sw, bool on)
>   
>       hw = sw->hw;
>       if (sw->active != on) {
> -        AudioBackend *s = sw->s;
> +        AudioDriver *s = sw->s;
>           SWVoiceIn *temp_sw;
>   
>           if (on) {
> @@ -1140,7 +1141,7 @@ static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
>       return clipped;
>   }
>   
> -static void audio_run_out(AudioBackend *s)
> +static void audio_run_out(AudioDriver *s)
>   {
>       HWVoiceOut *hw = NULL;
>       SWVoiceOut *sw;
> @@ -1294,7 +1295,7 @@ static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
>       return conv;
>   }
>   
> -static void audio_run_in(AudioBackend *s)
> +static void audio_run_in(AudioDriver *s)
>   {
>       HWVoiceIn *hw = NULL;
>   
> @@ -1342,7 +1343,7 @@ static void audio_run_in(AudioBackend *s)
>       }
>   }
>   
> -static void audio_run_capture(AudioBackend *s)
> +static void audio_run_capture(AudioDriver *s)
>   {
>       CaptureVoiceOut *cap;
>   
> @@ -1389,7 +1390,7 @@ static void audio_run_capture(AudioBackend *s)
>       }
>   }
>   
> -void audio_run(AudioBackend *s, const char *msg)
> +void audio_run(AudioDriver *s, const char *msg)
>   {
>       audio_run_out(s);
>       audio_run_in(s);
> @@ -1562,8 +1563,8 @@ size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
>       return total;
>   }
>   
> -static bool audio_driver_init(AudioBackend *s, struct audio_driver *drv,
> -                              Audiodev *dev, Error **errp)
> +static bool audio_driver_do_init(AudioDriver *s, struct audio_driver *drv,
> +                                 Audiodev *dev, Error **errp)
>   {
>       s->drv_opaque = drv->init(dev, errp);
>       if (!s->drv_opaque) {
> @@ -1595,7 +1596,7 @@ static bool audio_driver_init(AudioBackend *s, struct audio_driver *drv,
>   static void audio_vm_change_state_handler (void *opaque, bool running,
>                                              RunState state)
>   {
> -    AudioBackend *s = opaque;
> +    AudioDriver *s = opaque;
>       HWVoiceOut *hwo = NULL;
>       HWVoiceIn *hwi = NULL;
>   
> @@ -1618,7 +1619,47 @@ static const VMStateDescription vmstate_audio;
>   
>   static void audio_be_init(Object *obj)
>   {
> -    AudioBackend *s = AUDIO_BACKEND(obj);
> +}
> +
> +static void audio_be_finalize(Object *obj)
> +{
> +}
> +
> +static const char *audio_driver_get_id(AudioBackend *be)
> +{
> +    return AUDIO_DRIVER(be)->dev->id;
> +}
> +
> +#ifdef CONFIG_GIO
> +static bool audio_driver_set_dbus_server(AudioBackend *be,
> +                                            GDBusObjectManagerServer *manager,
> +                                            bool p2p,
> +                                            Error **errp)
> +{
> +    AudioDriver *d = AUDIO_DRIVER(be);
> +
> +    if (!d->drv->set_dbus_server) {
> +        return false;
> +    }
> +
> +    return d->drv->set_dbus_server(be, manager, p2p, errp);
> +}
> +
> +#endif
> +
> +static void audio_driver_class_init(ObjectClass *klass, const void *data)
> +{
> +    AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);
> +
> +    be->get_id = audio_driver_get_id;
> +#ifdef CONFIG_GIO
> +    be->set_dbus_server = audio_driver_set_dbus_server;
> +#endif
> +}
> +
> +static void audio_driver_init(Object *obj)
> +{
> +    AudioDriver *s = AUDIO_DRIVER(obj);
>   
>       QLIST_INIT(&s->hw_head_out);
>       QLIST_INIT(&s->hw_head_in);
> @@ -1631,9 +1672,9 @@ static void audio_be_init(Object *obj)
>       vmstate_register_any(NULL, &vmstate_audio, s);
>   }
>   
> -static void audio_be_finalize(Object *obj)
> +static void audio_driver_finalize(Object *obj)
>   {
> -    AudioBackend *s = AUDIO_BACKEND(obj);
> +    AudioDriver *s = AUDIO_DRIVER(obj);
>       HWVoiceOut *hwo, *hwon;
>       HWVoiceIn *hwi, *hwin;
>   
> @@ -1749,10 +1790,10 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
>   {
>       int done = 0;
>       const char *drvname;
> -    AudioBackend *s;
> +    AudioDriver *s;
>       struct audio_driver *driver;
>   
> -    s = AUDIO_BACKEND(object_new(TYPE_AUDIO_BACKEND));
> +    s = AUDIO_DRIVER(object_new(TYPE_AUDIO_DRIVER));
>   
>       if (dev) {
>           /* -audiodev option */
> @@ -1760,7 +1801,7 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
>           drvname = AudiodevDriver_str(dev->driver);
>           driver = audio_driver_lookup(drvname);
>           if (driver) {
> -            done = audio_driver_init(s, driver, dev, errp);
> +            done = audio_driver_do_init(s, driver, dev, errp);
>           } else {
>               error_setg(errp, "Unknown audio driver `%s'", drvname);
>           }
> @@ -1780,7 +1821,7 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
>               g_free(e);
>               drvname = AudiodevDriver_str(dev->driver);
>               driver = audio_driver_lookup(drvname);
> -            if (audio_driver_init(s, driver, dev, NULL)) {
> +            if (audio_driver_do_init(s, driver, dev, NULL)) {
>                   break;
>               }
>               qapi_free_Audiodev(dev);
> @@ -1792,7 +1833,7 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)
>           goto out;
>       }
>       object_unref(s);
> -    return s;
> +    return AUDIO_BACKEND(s);
>   
>   out:
>       object_unref(s);
> @@ -1831,17 +1872,19 @@ bool AUD_backend_check(AudioBackend **be, Error **errp)
>   static struct audio_pcm_ops capture_pcm_ops;
>   
>   CaptureVoiceOut *AUD_add_capture(
> -    AudioBackend *s,
> +    AudioBackend *be,
>       struct audsettings *as,
>       struct audio_capture_ops *ops,
>       void *cb_opaque
>       )
>   {
> +    AudioDriver *s = AUDIO_DRIVER(be);
>       CaptureVoiceOut *cap;
>       struct capture_callback *cb;
>   
>       if (!s) {
> -        error_report("Capturing without setting an audiodev is not supported");
> +        /* TODO: implement an interface instead (or drop capture support) */
> +        error_report("Capturing without setting an audiodev driver is not supported");
>           abort();
>       }
>   
> @@ -2227,27 +2270,36 @@ AudioBackend *audio_be_by_name(const char *name, Error **errp)
>   }
>   
>   #ifdef CONFIG_GIO
> +bool audio_be_can_set_dbus_server(AudioBackend *be)
> +{
> +    /*
> +     * AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
> +     * return klass->set_dbus_server != NULL;
> +     */
> +     return AUDIO_DRIVER(be)->drv->set_dbus_server != NULL;
> +}
> +
>   bool audio_be_set_dbus_server(AudioBackend *be,
>                                 GDBusObjectManagerServer *server,
>                                 bool p2p,
>                                 Error **errp)
>   {
> -    assert(be != NULL);
> +    AudioBackendClass *klass = AUDIO_BACKEND_GET_CLASS(be);
>   
> -    if (!be->drv->set_dbus_server) {
> -        error_setg(errp, "Audiodev '%s' is not compatible with DBus", be->dev->id);
> +    if (!audio_be_can_set_dbus_server(be)) {
> +        error_setg(errp, "Audiodev '%s' is not compatible with DBus",
> +                   audio_be_get_id(be));
>           return false;
>       }
>   
> -    return be->drv->set_dbus_server(be, server, p2p, errp);
> +    return klass->set_dbus_server(be, server, p2p, errp);
>   }
>   #endif
>   
>   const char *audio_be_get_id(AudioBackend *be)
>   {
>       if (be) {
> -        assert(be->dev);
> -        return be->dev->id;
> +        return AUDIO_BACKEND_GET_CLASS(be)->get_id(be);
>       } else {
>           return "";
>       }
> @@ -2322,13 +2374,25 @@ static const TypeInfo audio_be_info = {
>       .instance_size = sizeof(AudioBackend),
>       .instance_init = audio_be_init,
>       .instance_finalize = audio_be_finalize,
> -    .abstract = false, /* TODO: subclass drivers and make it abstract */
> +    .abstract = true,
>       .class_size = sizeof(AudioBackendClass),
>   };
>   
> +static const TypeInfo audio_driver_info = {
> +    .name = TYPE_AUDIO_DRIVER,
> +    .parent = TYPE_AUDIO_BACKEND,
> +    .instance_size = sizeof(AudioDriver),
> +    .instance_init = audio_driver_init,
> +    .instance_finalize = audio_driver_finalize,
> +    .abstract = false,
> +    .class_size = sizeof(AudioDriverClass),
> +    .class_init = audio_driver_class_init,
> +};
> +
>   static void register_types(void)
>   {
>       type_register_static(&audio_be_info);
> +    type_register_static(&audio_driver_info);
>   }
>   
>   type_init(register_types);
> diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c
> index 1fe7c4ed64..71b2a6c2cf 100644
> --- a/audio/dbusaudio.c
> +++ b/audio/dbusaudio.c
> @@ -464,7 +464,7 @@ listener_in_vanished_cb(GDBusConnection *connection,
>   }
>   
>   static gboolean
> -dbus_audio_register_listener(AudioBackend *s,
> +dbus_audio_register_listener(AudioDriver *s,
>                                GDBusMethodInvocation *invocation,
>   #ifdef G_OS_UNIX
>                                GUnixFDList *fd_list,
> @@ -621,7 +621,7 @@ dbus_audio_register_listener(AudioBackend *s,
>   }
>   
>   static gboolean
> -dbus_audio_register_out_listener(AudioBackend *s,
> +dbus_audio_register_out_listener(AudioDriver *s,
>                                    GDBusMethodInvocation *invocation,
>   #ifdef G_OS_UNIX
>                                    GUnixFDList *fd_list,
> @@ -637,7 +637,7 @@ dbus_audio_register_out_listener(AudioBackend *s,
>   }
>   
>   static gboolean
> -dbus_audio_register_in_listener(AudioBackend *s,
> +dbus_audio_register_in_listener(AudioDriver *s,
>                                   GDBusMethodInvocation *invocation,
>   #ifdef G_OS_UNIX
>                                   GUnixFDList *fd_list,
> @@ -657,7 +657,7 @@ dbus_audio_set_server(AudioBackend *s,
>                         bool p2p,
>                         Error **errp)
>   {
> -    DBusAudio *da = s->drv_opaque;
> +    DBusAudio *da = AUDIO_DRIVER(s)->drv_opaque;
>   
>       g_assert(da);
>       g_assert(!da->server);
> diff --git a/audio/ossaudio.c b/audio/ossaudio.c
> index c6cad47a01..9576cdba51 100644
> --- a/audio/ossaudio.c
> +++ b/audio/ossaudio.c
> @@ -107,13 +107,13 @@ static void oss_anal_close (int *fdp)
>   
>   static void oss_helper_poll_out (void *opaque)
>   {
> -    AudioBackend *s = opaque;
> +    AudioDriver *s = opaque;
>       audio_run(s, "oss_poll_out");
>   }
>   
>   static void oss_helper_poll_in (void *opaque)
>   {
> -    AudioBackend *s = opaque;
> +    AudioDriver *s = opaque;
>       audio_run(s, "oss_poll_in");
>   }
>   
> diff --git a/ui/dbus.c b/ui/dbus.c
> index 45fb3c1aa3..b0caa9b154 100644
> --- a/ui/dbus.c
> +++ b/ui/dbus.c
> @@ -220,7 +220,7 @@ dbus_display_complete(UserCreatable *uc, Error **errp)
>       }
>   
>       AudioBackend *audio_be = audio_get_default_audio_be(NULL);
> -    if (audio_be && !g_str_equal(audio_be->drv->name, "dbus")) {
> +    if (audio_be && !audio_be_can_set_dbus_server(audio_be)) {
>           audio_be = NULL;
>       }
>       if (dd->audiodev && *dd->audiodev) {
> @@ -233,7 +233,6 @@ dbus_display_complete(UserCreatable *uc, Error **errp)
>           return;
>       }
>   
> -
>       consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
>       for (idx = 0;; idx++) {
>           if (!qemu_console_lookup_by_index(idx)) {



^ permalink raw reply	[flat|nested] 51+ messages in thread

end of thread, other threads:[~2025-12-11  5:23 UTC | newest]

Thread overview: 51+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-01 11:22 [RFC 00/24] audio: add GStreamer backend marcandre.lureau
2025-12-01 11:22 ` [RFC 01/24] rust: patch thiserror to work with meson marcandre.lureau
2025-12-01 11:22 ` [RFC 02/24] audio: remove obsolete/obscure functions marcandre.lureau
2025-12-10 14:02   ` Akihiko Odaki
2025-12-01 11:22 ` [RFC 03/24] audio/dbus: make "dbus" the default backend when using -display dbus marcandre.lureau
2025-12-10 14:03   ` Akihiko Odaki
2025-12-01 11:22 ` [RFC 04/24] qemu-options.hx: clarify default audio backend selection marcandre.lureau
2025-12-01 11:22 ` [RFC 05/24] audio: introduce AudioDriver marcandre.lureau
2025-12-11  5:22   ` Akihiko Odaki
2025-12-01 11:22 ` [RFC 06/24] audio: simplify audio_init() marcandre.lureau
2025-12-01 11:22 ` [RFC 07/24] audio: move object creation to audio_driver_init() marcandre.lureau
2025-12-01 11:22 ` [RFC 08/24] audio: add QOM module-objects for each backend marcandre.lureau
2025-12-01 13:20   ` BALATON Zoltan
2025-12-01 18:43     ` Marc-André Lureau
2025-12-01 11:22 ` [RFC 09/24] audio: remove set_dbus_server from audio_driver marcandre.lureau
2025-12-01 11:22 ` [RFC 10/24] audio: lookup "audio-" object types, and realize them marcandre.lureau
2025-12-01 11:22 ` [RFC 11/24] audio: switch to module-object, drop audio driver registration marcandre.lureau
2025-12-01 11:22 ` [RFC 12/24] module: remove audio module support marcandre.lureau
2025-12-01 11:22 ` [RFC 13/24] audio: keep a strong reference on the backend marcandre.lureau
2025-12-01 11:22 ` [RFC 14/24] audio: make list type declaration private marcandre.lureau
2025-12-01 11:22 ` [RFC 15/24] audio: make create_pdos() private marcandre.lureau
2025-12-01 11:22 ` [RFC 16/24] replay: remove dependency on audio/ marcandre.lureau
2025-12-01 11:22 ` [RFC 17/24] audio: make all the backend-specific APIs take the be marcandre.lureau
2025-12-01 11:22 ` [RFC 18/24] audio: make AudioBackend truely abstract marcandre.lureau
2025-12-01 11:23 ` [RFC 19/24] audio: split AudioBackend marcandre.lureau
2025-12-01 11:23 ` [RFC 20/24] audio: AUD_ -> audio_be_ marcandre.lureau
2025-12-01 11:23 ` [RFC 21/24] audio-be: add common pre-conditions marcandre.lureau
2025-12-01 11:23 ` [RFC 22/24] audio-be: add some state trace marcandre.lureau
2025-12-01 11:23 ` [RFC 23/24] audio: split AudioDriver code in audio-driver.c marcandre.lureau
2025-12-01 11:23 ` [RFC 24/24] WIP: rust/audio: add GStreamer backend marcandre.lureau
2025-12-01 13:12   ` Markus Armbruster
2025-12-01 18:26     ` Marc-André Lureau
2025-12-01 13:02 ` [RFC 00/24] audio: " BALATON Zoltan
2025-12-01 13:41   ` Christian Schoenebeck
2025-12-01 18:20   ` Marc-André Lureau
2025-12-01 19:30     ` BALATON Zoltan
2025-12-01 19:44       ` Daniel P. Berrangé
2025-12-02 12:01         ` BALATON Zoltan
2025-12-01 20:58     ` Alexandre Ratchov
2025-12-02  7:55       ` Paolo Bonzini
2025-12-02 12:03         ` BALATON Zoltan
2025-12-02 12:25           ` Geoffrey McRae
2025-12-02 12:44             ` Marc-André Lureau
2025-12-02 13:25               ` Geoffrey McRae
2025-12-02 14:14                 ` Marc-André Lureau
2025-12-02 14:33                   ` Neal Gompa
2025-12-02 14:43                   ` Geoffrey McRae
2025-12-02 14:52                   ` Markus Armbruster
2025-12-03  9:19                     ` Akihiko Odaki
2025-12-02 15:39                   ` Christian Schoenebeck
2025-12-03  8:06                   ` Alexandre Ratchov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).