All of lore.kernel.org
 help / color / mirror / Atom feed
From: marcandre.lureau@redhat.com
To: qemu-devel@nongnu.org
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>,
	"Gerd Hoffmann" <kraxel@redhat.com>
Subject: [PATCH 15/27] audio: add dbusaudio backend
Date: Fri, 12 Mar 2021 14:00:56 +0400	[thread overview]
Message-ID: <20210312100108.2706195-16-marcandre.lureau@redhat.com> (raw)
In-Reply-To: <20210312100108.2706195-1-marcandre.lureau@redhat.com>

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

Add a new -audio backend that accepts DBus clients/listeners to handle
playback & recording, to be exported via the -display dbus.

Example usage:
-audiodev dbus,in.mixing-engine=off,out.mixing-engine=off,id=dbus
-display dbus,audiodev=dbus

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/audio.json        |   3 +-
 qapi/ui.json           |   7 +-
 audio/audio_int.h      |   7 +
 audio/audio_template.h |   2 +
 ui/dbus.h              |   1 +
 audio/audio.c          |   1 +
 audio/dbusaudio.c      | 649 +++++++++++++++++++++++++++++++++++++++++
 ui/dbus.c              |  35 +++
 util/module.c          |   1 +
 audio/meson.build      |   3 +-
 audio/trace-events     |   5 +
 qemu-options.hx        |   3 +
 ui/dbus-display1.xml   |  82 ++++++
 13 files changed, 795 insertions(+), 4 deletions(-)
 create mode 100644 audio/dbusaudio.c

diff --git a/qapi/audio.json b/qapi/audio.json
index 9cba0df8a4..693e327c6b 100644
--- a/qapi/audio.json
+++ b/qapi/audio.json
@@ -386,7 +386,7 @@
 # Since: 4.0
 ##
 { 'enum': 'AudiodevDriver',
-  'data': [ 'none', 'alsa', 'coreaudio', 'dsound', 'jack', 'oss', 'pa',
+  'data': [ 'none', 'alsa', 'coreaudio', 'dbus', 'dsound', 'jack', 'oss', 'pa',
             'sdl', 'spice', 'wav' ] }
 
 ##
@@ -412,6 +412,7 @@
     'none':      'AudiodevGenericOptions',
     'alsa':      'AudiodevAlsaOptions',
     'coreaudio': 'AudiodevCoreaudioOptions',
+    'dbus':      'AudiodevGenericOptions',
     'dsound':    'AudiodevDsoundOptions',
     'jack':      'AudiodevJackOptions',
     'oss':       'AudiodevOssOptions',
diff --git a/qapi/ui.json b/qapi/ui.json
index bdfab800c0..0344f75f69 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -1078,12 +1078,15 @@
 # @rendernode: Which DRM render node should be used. Default is the first
 #              available node on the host.
 #
+# @audiodev: Use the specified DBus audiodev to export audio.
+#
 # Since: X.X
 #
 ##
 { 'struct'  : 'DisplayDBus',
-  'data'    : { '*rendernode' : 'str',
-                '*addr': 'str' } }
+  'data'    : { '*rendernode': 'str',
+                '*addr': 'str',
+                '*audiodev': 'str' } }
 
  ##
  # @DisplayGLMode:
diff --git a/audio/audio_int.h b/audio/audio_int.h
index 06f0913835..294c7f2342 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -31,6 +31,10 @@
 #endif
 #include "mixeng.h"
 
+#ifdef CONFIG_GIO
+#include <gio/gio.h>
+#endif
+
 struct audio_pcm_ops;
 
 struct audio_callback {
@@ -140,6 +144,9 @@ struct audio_driver {
     const char *descr;
     void *(*init) (Audiodev *);
     void (*fini) (void *);
+#ifdef CONFIG_GIO
+    void (*set_dbus_server) (AudioState *s, GDBusObjectManagerServer *manager);
+#endif
     struct audio_pcm_ops *pcm_ops;
     int can_be_default;
     int max_voices_out;
diff --git a/audio/audio_template.h b/audio/audio_template.h
index c6714946aa..d2d348638b 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -327,6 +327,8 @@ AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev)
     case AUDIODEV_DRIVER_COREAUDIO:
         return qapi_AudiodevCoreaudioPerDirectionOptions_base(
             dev->u.coreaudio.TYPE);
+    case AUDIODEV_DRIVER_DBUS:
+        return dev->u.dbus.TYPE;
     case AUDIODEV_DRIVER_DSOUND:
         return dev->u.dsound.TYPE;
     case AUDIODEV_DRIVER_JACK:
diff --git a/ui/dbus.h b/ui/dbus.h
index f554084a27..10a019564f 100644
--- a/ui/dbus.h
+++ b/ui/dbus.h
@@ -35,6 +35,7 @@ struct DBusDisplay {
 
     DisplayGLMode gl_mode;
     char *dbus_addr;
+    char *audiodev;
     DisplayGLCtx glctx;
 
     GDBusConnection *bus;
diff --git a/audio/audio.c b/audio/audio.c
index 6734c8af70..fbcf676e78 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1989,6 +1989,7 @@ void audio_create_pdos(Audiodev *dev)
         CASE(NONE, none, );
         CASE(ALSA, alsa, Alsa);
         CASE(COREAUDIO, coreaudio, Coreaudio);
+        CASE(DBUS, dbus, );
         CASE(DSOUND, dsound, );
         CASE(JACK, jack, Jack);
         CASE(OSS, oss, Oss);
diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c
new file mode 100644
index 0000000000..4f1e51d951
--- /dev/null
+++ b/audio/dbusaudio.c
@@ -0,0 +1,649 @@
+/*
+ * QEMU DBus audio
+ *
+ * Copyright (c) 2021 Red Hat, Inc.
+ *
+ * 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/error-report.h"
+#include "qemu/host-utils.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qemu/dbus.h"
+
+#include <gio/gunixfdlist.h>
+#include "ui/dbus-display1.h"
+
+#define AUDIO_CAP "dbus"
+#include "audio.h"
+#include "audio_int.h"
+#include "trace.h"
+
+#define DBUS_DISPLAY1_AUDIO DBUS_DISPLAY1_ROOT "/Audio"
+
+#define DBUS_AUDIO_NSAMPLES 1024 /* could be configured? */
+
+typedef struct DBusAudio {
+    GDBusObjectManagerServer *server;
+    GDBusObjectSkeleton *audio;
+    DBusDisplayDisplay1Audio *iface;
+    GHashTable *out_listeners;
+    GHashTable *in_listeners;
+} DBusAudio;
+
+typedef struct DBusVoiceOut {
+    HWVoiceOut hw;
+    bool enabled;
+    RateCtl rate;
+
+    void *buf;
+    size_t buf_pos;
+    size_t buf_size;
+
+    bool has_volume;
+    Volume volume;
+} DBusVoiceOut;
+
+typedef struct DBusVoiceIn {
+    HWVoiceIn hw;
+    bool enabled;
+    RateCtl rate;
+
+    bool has_volume;
+    Volume volume;
+} DBusVoiceIn;
+
+static void *dbus_get_buffer_out(HWVoiceOut *hw, size_t *size)
+{
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+
+    if (!vo->buf) {
+        vo->buf_size = hw->samples * hw->info.bytes_per_frame;
+        vo->buf = g_malloc(vo->buf_size);
+        vo->buf_pos = 0;
+    }
+
+    *size = MIN(vo->buf_size - vo->buf_pos, *size);
+    *size = audio_rate_get_bytes(&hw->info, &vo->rate, *size);
+
+    return vo->buf + vo->buf_pos;
+
+}
+
+static size_t dbus_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+    GHashTableIter iter;
+    DBusDisplayDisplay1AudioOutListener *listener = NULL;
+    g_autoptr(GBytes) bytes = NULL;
+    g_autoptr(GVariant) v_data = NULL;
+
+    assert(buf == vo->buf + vo->buf_pos && vo->buf_pos + size <= vo->buf_size);
+    vo->buf_pos += size;
+
+    trace_dbus_put_buffer_out(size);
+
+    if (vo->buf_pos < vo->buf_size) {
+        return size;
+    }
+
+    bytes = g_bytes_new_take(g_steal_pointer(&vo->buf), vo->buf_size);
+    v_data = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
+    g_variant_ref_sink(v_data);
+
+    g_hash_table_iter_init(&iter, da->out_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_display_display1_audio_out_listener_call_write(
+            listener,
+            (uintptr_t)hw,
+            v_data,
+            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+    }
+
+    return size;
+}
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define AUDIO_HOST_BE TRUE
+#else
+#define AUDIO_HOST_BE FALSE
+#endif
+
+static void
+dbus_init_out_listener(DBusDisplayDisplay1AudioOutListener *listener, HWVoiceOut *hw)
+{
+    dbus_display_display1_audio_out_listener_call_init(
+        listener,
+        (uintptr_t)hw,
+        hw->info.bits,
+        hw->info.is_signed,
+        hw->info.is_float,
+        hw->info.freq,
+        hw->info.nchannels,
+        hw->info.bytes_per_frame,
+        hw->info.bytes_per_second,
+        hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,
+        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static int
+dbus_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+    GHashTableIter iter;
+    DBusDisplayDisplay1AudioOutListener *listener = NULL;
+
+    audio_pcm_init_info(&hw->info, as);
+    hw->samples = DBUS_AUDIO_NSAMPLES;
+    audio_rate_start(&vo->rate);
+
+    g_hash_table_iter_init(&iter, da->out_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_init_out_listener(listener, hw);
+    }
+    return 0;
+}
+
+static void
+dbus_fini_out(HWVoiceOut *hw)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+    GHashTableIter iter;
+    DBusDisplayDisplay1AudioOutListener *listener = NULL;
+
+    g_hash_table_iter_init(&iter, da->out_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_display_display1_audio_out_listener_call_fini(
+            listener,
+            (uintptr_t)hw,
+            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+    }
+
+    g_clear_pointer(&vo->buf, g_free);
+}
+
+static void
+dbus_enable_out(HWVoiceOut *hw, bool enable)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+    GHashTableIter iter;
+    DBusDisplayDisplay1AudioOutListener *listener = NULL;
+
+    vo->enabled = enable;
+    if (enable) {
+        audio_rate_start(&vo->rate);
+    }
+
+    g_hash_table_iter_init(&iter, da->out_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_display_display1_audio_out_listener_call_set_enabled(
+            listener, (uintptr_t)hw, enable,
+            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+    }
+}
+
+static void
+dbus_volume_out_listener(HWVoiceOut *hw,
+                         DBusDisplayDisplay1AudioOutListener *listener)
+{
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+    Volume *vol = &vo->volume;
+    g_autoptr(GBytes) bytes = NULL;
+    GVariant *v_vol = NULL;
+
+    if (!vo->has_volume) {
+        return;
+    }
+
+    assert(vol->channels < sizeof(vol->vol));
+    bytes = g_bytes_new(vol->vol, vol->channels);
+    v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
+    dbus_display_display1_audio_out_listener_call_set_volume(
+        listener, (uintptr_t)hw, vol->mute, v_vol,
+        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void
+dbus_volume_out(HWVoiceOut *hw, Volume *vol)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+    GHashTableIter iter;
+    DBusDisplayDisplay1AudioOutListener *listener = NULL;
+
+    vo->has_volume = true;
+    vo->volume = *vol;
+
+    g_hash_table_iter_init(&iter, da->out_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_volume_out_listener(hw, listener);
+    }
+}
+
+static void
+dbus_init_in_listener(DBusDisplayDisplay1AudioInListener *listener, HWVoiceIn *hw)
+{
+    dbus_display_display1_audio_in_listener_call_init(
+        listener,
+        (uintptr_t)hw,
+        hw->info.bits,
+        hw->info.is_signed,
+        hw->info.is_float,
+        hw->info.freq,
+        hw->info.nchannels,
+        hw->info.bytes_per_frame,
+        hw->info.bytes_per_second,
+        hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,
+        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static int
+dbus_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+    GHashTableIter iter;
+    DBusDisplayDisplay1AudioInListener *listener = NULL;
+
+    audio_pcm_init_info(&hw->info, as);
+    hw->samples = DBUS_AUDIO_NSAMPLES;
+    audio_rate_start(&vo->rate);
+
+    g_hash_table_iter_init(&iter, da->in_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_init_in_listener(listener, hw);
+    }
+    return 0;
+}
+
+static void
+dbus_fini_in(HWVoiceIn *hw)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    GHashTableIter iter;
+    DBusDisplayDisplay1AudioInListener *listener = NULL;
+
+    g_hash_table_iter_init(&iter, da->in_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_display_display1_audio_in_listener_call_fini(
+            listener,
+            (uintptr_t)hw,
+            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+    }
+}
+
+static void
+dbus_volume_in_listener(HWVoiceIn *hw,
+                         DBusDisplayDisplay1AudioInListener *listener)
+{
+    DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+    Volume *vol = &vo->volume;
+    g_autoptr(GBytes) bytes = NULL;
+    GVariant *v_vol = NULL;
+
+    if (!vo->has_volume) {
+        return;
+    }
+
+    assert(vol->channels < sizeof(vol->vol));
+    bytes = g_bytes_new(vol->vol, vol->channels);
+    v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
+    dbus_display_display1_audio_in_listener_call_set_volume(
+        listener, (uintptr_t)hw, vol->mute, v_vol,
+        G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void
+dbus_volume_in(HWVoiceIn *hw, Volume *vol)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+    GHashTableIter iter;
+    DBusDisplayDisplay1AudioInListener *listener = NULL;
+
+    vo->has_volume = true;
+    vo->volume = *vol;
+
+    g_hash_table_iter_init(&iter, da->in_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_volume_in_listener(hw, listener);
+    }
+}
+
+static size_t
+dbus_read(HWVoiceIn *hw, void *buf, size_t size)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    /* DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); */
+    GHashTableIter iter;
+    DBusDisplayDisplay1AudioInListener *listener = NULL;
+
+    trace_dbus_read(size);
+
+    /* size = audio_rate_get_bytes(&hw->info, &vo->rate, size); */
+
+    g_hash_table_iter_init(&iter, da->in_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        g_autoptr(GVariant) v_data = NULL;
+        const char *data;
+        gsize n = 0;
+
+        if (dbus_display_display1_audio_in_listener_call_read_sync(
+                listener,
+                (uintptr_t)hw,
+                size,
+                G_DBUS_CALL_FLAGS_NONE, -1,
+                &v_data, NULL, NULL)) {
+            data = g_variant_get_fixed_array(v_data, &n, 1);
+            g_warn_if_fail(n <= size);
+            size = MIN(n, size);
+            memcpy(buf, data, size);
+            break;
+        }
+    }
+
+    return size;
+}
+
+static void
+dbus_enable_in(HWVoiceIn *hw, bool enable)
+{
+    DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+    DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+    GHashTableIter iter;
+    DBusDisplayDisplay1AudioInListener *listener = NULL;
+
+    vo->enabled = enable;
+    if (enable) {
+        audio_rate_start(&vo->rate);
+    }
+
+    g_hash_table_iter_init(&iter, da->in_listeners);
+    while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+        dbus_display_display1_audio_in_listener_call_set_enabled(
+            listener, (uintptr_t)hw, enable,
+            G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+    }
+}
+
+static void *
+dbus_audio_init(Audiodev *dev)
+{
+    DBusAudio *self = g_new0(DBusAudio, 1);
+
+    self->out_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                                g_free, g_object_unref);
+    self->in_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                               g_free, g_object_unref);
+    return self;
+}
+
+static void
+dbus_audio_fini(void *opaque)
+{
+    DBusAudio *self = opaque;
+
+    if (self->server) {
+        g_dbus_object_manager_server_unexport(self->server, DBUS_DISPLAY1_AUDIO);
+    }
+    g_clear_object(&self->audio);
+    g_clear_object(&self->iface);
+    g_clear_pointer(&self->in_listeners, g_hash_table_unref);
+    g_clear_pointer(&self->out_listeners, g_hash_table_unref);
+    g_clear_object(&self->server);
+    g_free(self);
+}
+
+static void
+listener_out_vanished_cb(GDBusConnection *connection,
+                         gboolean remote_peer_vanished,
+                         GError *error,
+                         DBusAudio *self)
+{
+    char *name = g_object_get_data(G_OBJECT(connection), "name");
+
+    g_hash_table_remove(self->out_listeners, name);
+}
+
+static void
+listener_in_vanished_cb(GDBusConnection *connection,
+                        gboolean remote_peer_vanished,
+                        GError *error,
+                        DBusAudio *self)
+{
+    char *name = g_object_get_data(G_OBJECT(connection), "name");
+
+    g_hash_table_remove(self->in_listeners, name);
+}
+
+static gboolean
+dbus_audio_register_listener(AudioState *s,
+                             GDBusMethodInvocation *invocation,
+                             GUnixFDList *fd_list,
+                             GVariant *arg_listener,
+                             bool out)
+{
+    DBusAudio *self = s->drv_opaque;
+    const char *sender = g_dbus_method_invocation_get_sender(invocation);
+    g_autoptr(GDBusConnection) listener_conn = NULL;
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GSocket) socket = NULL;
+    g_autoptr(GSocketConnection) socket_conn = NULL;
+    g_autofree char *guid = g_dbus_generate_guid();
+    GHashTable *listeners = out ? self->out_listeners : self->in_listeners;
+    GObject *listener;
+    int fd;
+
+    trace_dbus_register(sender, out ? "out" : "in");
+
+    if (g_hash_table_contains(listeners, sender)) {
+        g_dbus_method_invocation_return_error(invocation,
+                                              DBUS_DISPLAY_ERROR,
+                                              DBUS_DISPLAY_ERROR_INVALID,
+                                              "`%s` is already registered!",
+                                              sender);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);
+    if (err) {
+        g_dbus_method_invocation_return_error(invocation,
+                                              DBUS_DISPLAY_ERROR,
+                                              DBUS_DISPLAY_ERROR_FAILED,
+                                              "Couldn't get peer fd: %s",
+                                              err->message);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    socket = g_socket_new_from_fd(fd, &err);
+    if (err) {
+        g_dbus_method_invocation_return_error(invocation,
+                                              DBUS_DISPLAY_ERROR,
+                                              DBUS_DISPLAY_ERROR_FAILED,
+                                              "Couldn't make a socket: %s",
+                                              err->message);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+    socket_conn = g_socket_connection_factory_create_connection(socket);
+    if (out) {
+        dbus_display_display1_audio_complete_register_out_listener(
+            self->iface, invocation, NULL);
+    } else {
+        dbus_display_display1_audio_complete_register_in_listener(
+            self->iface, invocation, NULL);
+    }
+
+    listener_conn =
+        g_dbus_connection_new_sync(
+            G_IO_STREAM(socket_conn),
+            guid,
+            G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+            NULL, NULL, &err);
+    if (err) {
+        error_report("Failed to setup peer connection: %s", err->message);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    listener = out ?
+        G_OBJECT(dbus_display_display1_audio_out_listener_proxy_new_sync(
+            listener_conn,
+            G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+            NULL,
+            "/org/qemu/Display1/AudioOutListener",
+            NULL,
+            &err)) :
+        G_OBJECT(dbus_display_display1_audio_in_listener_proxy_new_sync(
+            listener_conn,
+            G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+            NULL,
+            "/org/qemu/Display1/AudioInListener",
+            NULL,
+            &err));
+    if (!listener) {
+        error_report("Failed to setup proxy: %s", err->message);
+        return DBUS_METHOD_INVOCATION_HANDLED;
+    }
+
+    if (out) {
+        HWVoiceOut *hw;
+
+        QLIST_FOREACH(hw, &s->hw_head_out, entries) {
+            DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+            DBusDisplayDisplay1AudioOutListener *l =
+                DBUS_DISPLAY_DISPLAY1_AUDIO_OUT_LISTENER(listener);
+
+            dbus_init_out_listener(l, hw);
+            dbus_display_display1_audio_out_listener_call_set_enabled(
+                l, (uintptr_t)hw, vo->enabled,
+                G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+        }
+    } else {
+        HWVoiceIn *hw;
+
+        QLIST_FOREACH(hw, &s->hw_head_in, entries) {
+            DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+            DBusDisplayDisplay1AudioInListener *l =
+                DBUS_DISPLAY_DISPLAY1_AUDIO_IN_LISTENER(listener);
+
+            dbus_init_in_listener(DBUS_DISPLAY_DISPLAY1_AUDIO_IN_LISTENER(listener), hw);
+            dbus_display_display1_audio_in_listener_call_set_enabled(
+                l, (uintptr_t)hw, vo->enabled,
+                G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+        }
+    }
+
+    g_object_set_data_full(G_OBJECT(listener_conn), "name",
+                           g_strdup(sender), g_free);
+    g_hash_table_insert(listeners, g_strdup(sender), listener);
+    g_object_connect(listener_conn,
+                     "signal::closed",
+                     out ? listener_out_vanished_cb : listener_in_vanished_cb,
+                     self,
+                     NULL);
+
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_audio_register_out_listener(AudioState *s,
+                                 GDBusMethodInvocation *invocation,
+                                 GUnixFDList *fd_list,
+                                 GVariant *arg_listener)
+{
+    return dbus_audio_register_listener(s, invocation,
+                                        fd_list, arg_listener, true);
+
+}
+
+static gboolean
+dbus_audio_register_in_listener(AudioState *s,
+                                GDBusMethodInvocation *invocation,
+                                GUnixFDList *fd_list,
+                                GVariant *arg_listener)
+{
+    return dbus_audio_register_listener(s, invocation,
+                                        fd_list, arg_listener, false);
+}
+
+static void
+dbus_audio_set_server(AudioState *s, GDBusObjectManagerServer *server)
+{
+    DBusAudio *self = s->drv_opaque;
+
+    g_assert(self);
+    g_assert(!self->server);
+
+    self->server = g_object_ref(server);
+
+    self->audio = g_dbus_object_skeleton_new(DBUS_DISPLAY1_AUDIO);
+    self->iface = g_object_new(DBUS_DISPLAY_TYPE_DISPLAY1_AUDIO_SKELETON, NULL);
+    g_object_connect(self->iface,
+                     "swapped-signal::handle-register-in-listener",
+                     dbus_audio_register_in_listener, s,
+                     "swapped-signal::handle-register-out-listener",
+                     dbus_audio_register_out_listener, s,
+                     NULL);
+
+    g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(self->audio),
+                                         G_DBUS_INTERFACE_SKELETON(self->iface));
+    g_dbus_object_manager_server_export(self->server, self->audio);
+}
+
+static struct audio_pcm_ops dbus_pcm_ops = {
+    .init_out = dbus_init_out,
+    .fini_out = dbus_fini_out,
+    .write    = audio_generic_write,
+    .get_buffer_out = dbus_get_buffer_out,
+    .put_buffer_out = dbus_put_buffer_out,
+    .enable_out = dbus_enable_out,
+    .volume_out = dbus_volume_out,
+
+    .init_in  = dbus_init_in,
+    .fini_in  = dbus_fini_in,
+    .read     = dbus_read,
+    .run_buffer_in = audio_generic_run_buffer_in,
+    .enable_in = dbus_enable_in,
+    .volume_in = dbus_volume_in,
+};
+
+static struct audio_driver dbus_audio_driver = {
+    .name            = "dbus",
+    .descr           = "Timer based audio exposed with DBus interface",
+    .init            = dbus_audio_init,
+    .fini            = dbus_audio_fini,
+    .set_dbus_server = dbus_audio_set_server,
+    .pcm_ops         = &dbus_pcm_ops,
+    .can_be_default  = 1,
+    .max_voices_out  = INT_MAX,
+    .max_voices_in   = INT_MAX,
+    .voice_size_out  = sizeof(DBusVoiceOut),
+    .voice_size_in   = sizeof(DBusVoiceIn)
+};
+
+static void register_audio_dbus(void)
+{
+    audio_driver_register(&dbus_audio_driver);
+}
+type_init(register_audio_dbus);
diff --git a/ui/dbus.c b/ui/dbus.c
index 089a92cedf..cc0d0665f0 100644
--- a/ui/dbus.c
+++ b/ui/dbus.c
@@ -28,6 +28,8 @@
 #include "sysemu/sysemu.h"
 #include "ui/egl-helpers.h"
 #include "ui/egl-context.h"
+#include "audio/audio.h"
+#include "audio/audio_int.h"
 #include "qapi/error.h"
 #include "trace.h"
 
@@ -75,6 +77,7 @@ dbus_display_finalize(Object *o)
     g_clear_object(&self->bus);
     g_clear_object(&self->iface);
     g_free(self->dbus_addr);
+    g_free(self->audiodev);
 }
 
 static bool
@@ -127,6 +130,18 @@ dbus_display_complete(UserCreatable *uc, Error **errp)
         return;
     }
 
+    if (self->audiodev && *self->audiodev) {
+        AudioState *audio_state = audio_state_by_name(self->audiodev);
+        if (!audio_state) {
+            error_setg(errp, "Audiodev '%s' not found", self->audiodev);
+            return;
+        }
+        if (!g_str_equal(audio_state->drv->name, "dbus")) {
+            error_setg(errp, "Audiodev '%s' is not compatible with DBus", self->audiodev);
+            return;
+        }
+        audio_state->drv->set_dbus_server(audio_state, self->server);
+    }
 
     consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
     for (idx = 0;; idx++) {
@@ -171,6 +186,24 @@ set_dbus_addr(Object *o, const char *str, Error **errp)
     self->dbus_addr = g_strdup(str);
 }
 
+static char *
+get_audiodev(Object *o, Error **errp)
+{
+    DBusDisplay *self = DBUS_DISPLAY(o);
+
+    return g_strdup(self->audiodev);
+}
+
+static void
+set_audiodev(Object *o, const char *str, Error **errp)
+{
+    DBusDisplay *self = DBUS_DISPLAY(o);
+
+    g_free(self->audiodev);
+    self->audiodev = g_strdup(str);
+}
+
+
 static int
 get_gl_mode(Object *o, Error **errp)
 {
@@ -194,6 +227,7 @@ dbus_display_class_init(ObjectClass *oc, void *data)
 
     ucc->complete = dbus_display_complete;
     object_class_property_add_str(oc, "addr", get_dbus_addr, set_dbus_addr);
+    object_class_property_add_str(oc, "audiodev", get_audiodev, set_audiodev);
     object_class_property_add_enum(oc, "gl-mode",
                                    "DisplayGLMode", &DisplayGLMode_lookup,
                                    get_gl_mode, set_gl_mode);
@@ -223,6 +257,7 @@ dbus_init(DisplayState *ds, DisplayOptions *opts)
                           object_get_objects_root(),
                           "dbus-display", &error_fatal,
                           "addr", opts->u.dbus.addr ?: "",
+                          "audiodev", opts->u.dbus.audiodev ?: "",
                           "gl-mode", DisplayGLMode_str(mode),
                           NULL);
 }
diff --git a/util/module.c b/util/module.c
index f228067d0d..858f13e86f 100644
--- a/util/module.c
+++ b/util/module.c
@@ -176,6 +176,7 @@ static const struct {
     const char *name;
     const char *dep;
 } module_deps[] = {
+    { "audio-dbus",     "ui-dbus" },
     { "audio-spice",    "ui-spice-core" },
     { "chardev-spice",  "ui-spice-core" },
     { "hw-display-qxl", "ui-spice-core" },
diff --git a/audio/meson.build b/audio/meson.build
index 7d53b0f920..5eca175779 100644
--- a/audio/meson.build
+++ b/audio/meson.build
@@ -18,7 +18,8 @@ foreach m : [
   ['CONFIG_AUDIO_PA', 'pa', pulse, 'paaudio.c'],
   ['CONFIG_AUDIO_SDL', 'sdl', sdl, 'sdlaudio.c'],
   ['CONFIG_AUDIO_JACK', 'jack', jack, 'jackaudio.c'],
-  ['CONFIG_SPICE', 'spice', spice, 'spiceaudio.c']
+  ['CONFIG_SPICE', 'spice', spice, 'spiceaudio.c'],
+  ['CONFIG_UI_DBUS', 'dbus', gio, 'dbusaudio.c'],
 ]
   if config_host.has_key(m[0])
     module_ss = ss.source_set()
diff --git a/audio/trace-events b/audio/trace-events
index 6aec535763..02d6d70ed3 100644
--- a/audio/trace-events
+++ b/audio/trace-events
@@ -13,6 +13,11 @@ alsa_resume_out(void) "Resuming suspended output stream"
 # ossaudio.c
 oss_version(int version) "OSS version = 0x%x"
 
+# dbusaudio.c
+dbus_register(const char *s, const char *dir) "sender = %s, dir = %s"
+dbus_put_buffer_out(size_t len) "len = %zu"
+dbus_read(size_t len) "len = %zu"
+
 # audio.c
 audio_timer_start(int interval) "interval %d ms"
 audio_timer_stop(void) ""
diff --git a/qemu-options.hx b/qemu-options.hx
index 9b42f15f53..826dfc503d 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -616,6 +616,9 @@ DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev,
 #endif
 #ifdef CONFIG_SPICE
     "-audiodev spice,id=id[,prop[=value][,...]]\n"
+#endif
+#ifdef CONFIG_UI_DBUS
+    "-audiodev dbus,id=id[,prop[=value][,...]]\n"
 #endif
     "-audiodev wav,id=id[,prop[=value][,...]]\n"
     "                path= path of wav file to record\n",
diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml
index df2ffcea07..61afbed727 100644
--- a/ui/dbus-display1.xml
+++ b/ui/dbus-display1.xml
@@ -120,4 +120,86 @@
       </arg>
     </method>
   </interface>
+
+  <!-- on /org/qemu/Display1/Audio -->
+  <interface name="org.qemu.Display1.Audio">
+    <method name="RegisterOutListener">
+      <arg type="h" name="listener" direction="in"/>
+    </method>
+    <method name="RegisterInListener">
+      <arg type="h" name="listener" direction="in"/>
+    </method>
+  </interface>
+
+  <!-- on client /org/qemu/Display1/AudioOutListener -->
+  <interface name="org.qemu.Display1.AudioOutListener">
+    <method name="Init">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="bits" type="y" direction="in"/>
+      <arg name="is_signed" type="b" direction="in"/>
+      <arg name="is_float" type="b" direction="in"/>
+      <arg name="freq" type="u" direction="in"/>
+      <arg name="nchannels" type="y" direction="in"/>
+      <arg name="bytes_per_frame" type="u" direction="in"/>
+      <arg name="bytes_per_second" type="u" direction="in"/>
+      <arg name="be" type="b" direction="in"/>
+    </method>
+    <method name="Fini">
+      <arg name="id" type="t" direction="in"/>
+    </method>
+    <method name="SetEnabled">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="enabled" type="b" direction="in"/>
+    </method>
+    <method name="SetVolume">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="mute" type="b" direction="in"/>
+      <arg name="volume" type="ay" direction="in">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+      </arg>
+    </method>
+    <method name="Write">
+      <arg name="id" type="t" direction="in"/>
+      <arg type="ay" name="data" direction="in">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+      </arg>
+    </method>
+  </interface>
+
+  <!-- on client /org/qemu/Display1/AudioInListener -->
+  <interface name="org.qemu.Display1.AudioInListener">
+    <method name="Init">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="bits" type="y" direction="in"/>
+      <arg name="is_signed" type="b" direction="in"/>
+      <arg name="is_float" type="b" direction="in"/>
+      <arg name="freq" type="u" direction="in"/>
+      <arg name="nchannels" type="y" direction="in"/>
+      <arg name="bytes_per_frame" type="u" direction="in"/>
+      <arg name="bytes_per_second" type="u" direction="in"/>
+      <arg name="be" type="b" direction="in"/>
+    </method>
+    <method name="Fini">
+      <arg name="id" type="t" direction="in"/>
+    </method>
+    <method name="SetEnabled">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="enabled" type="b" direction="in"/>
+    </method>
+    <method name="SetVolume">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="mute" type="b" direction="in"/>
+      <arg name="volume" type="ay" direction="in">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+      </arg>
+    </method>
+    <method name="Read">
+      <arg name="id" type="t" direction="in"/>
+      <arg name="size" type="t" direction="in"/>
+      <arg type="ay" name="data" direction="out">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+      </arg>
+    </method>
+  </interface>
+
 </node>
-- 
2.29.0



  parent reply	other threads:[~2021-03-12 10:13 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-03-12 10:00 [PATCH 00/27] Add D-Bus display backend marcandre.lureau
2021-03-12 10:00 ` [PATCH 01/27] ui: fold qemu_alloc_display in only caller marcandre.lureau
2021-03-12 10:00 ` [PATCH 02/27] vhost-user-gpu: glFlush before notifying clients marcandre.lureau
2021-03-12 10:00 ` [PATCH 03/27] vhost-user-gpu: fix vugbm_device_init fallback marcandre.lureau
2021-03-12 10:00 ` [PATCH 04/27] vhost-user-gpu: fix cursor move/update marcandre.lureau
2021-03-12 10:00 ` [PATCH 05/27] ui: factor out qemu_console_set_display_gl_ctx() marcandre.lureau
2021-03-12 15:34   ` Philippe Mathieu-Daudé
2021-03-12 10:00 ` [PATCH 06/27] ui: associate GL context outside of display listener registration marcandre.lureau
2021-03-12 10:00 ` [PATCH 07/27] ui: make gl_block use a counter marcandre.lureau
2021-03-12 10:12   ` Philippe Mathieu-Daudé
2021-03-12 11:21     ` Marc-André Lureau
2021-03-12 10:00 ` [PATCH 08/27] ui: add a gl-unblock warning timer marcandre.lureau
2021-03-12 10:00 ` [PATCH 09/27] ui: simplify gl unblock & flush marcandre.lureau
2021-03-12 10:00 ` [PATCH 10/27] ui: dispatch GL events to all listeners marcandre.lureau
2021-03-12 10:00 ` [PATCH 11/27] ui: split the GL context in a different object marcandre.lureau
2021-03-12 10:00 ` [PATCH 12/27] ui: move qemu_spice_fill_device_address to ui/util.c marcandre.lureau
2021-03-12 10:00 ` [PATCH 13/27] console: save current scanout details marcandre.lureau
2021-03-12 10:00 ` [PATCH 14/27] ui: add a D-Bus display backend marcandre.lureau
2021-03-12 11:03   ` Gerd Hoffmann
2021-03-12 11:27     ` Marc-André Lureau
2021-03-12 15:16       ` Gerd Hoffmann
2021-03-12 10:00 ` marcandre.lureau [this message]
2021-03-12 10:00 ` [PATCH 16/27] vhost-user-gpu: add vg_send_disable_scanout() marcandre.lureau
2021-03-12 10:00 ` [PATCH 17/27] vhost-user-gpu: add vg_send_scanout_dmabuf() marcandre.lureau
2021-03-12 10:00 ` [PATCH 18/27] vhost-user-gpu: add vg_send_dmabuf_update() marcandre.lureau
2021-03-12 10:01 ` [PATCH 19/27] vhost-user-gpu: add vg_send_scanout() marcandre.lureau
2021-03-12 10:01 ` [PATCH 20/27] vhost-user-gpu: add vg_send_cursor_update() marcandre.lureau
2021-03-12 10:01 ` [PATCH 21/27] vhost-user-gpu: add vg_send_cursor_pos() marcandre.lureau
2021-03-12 10:01 ` [PATCH 22/27] vhost-user-gpu: add vg_send_update() marcandre.lureau
2021-03-12 10:01 ` [PATCH 23/27] vhost-user: add VHOST_USER_GPU_QEMU_DBUS_LISTENER marcandre.lureau
2021-03-12 10:01 ` [PATCH 24/27] ui: add GraphicHwOps.register_dbus_listener() marcandre.lureau
2021-03-12 10:01 ` [PATCH 25/27] vhost-user-gpu: implement register_dbus_listener() marcandre.lureau
2021-03-12 10:01 ` [PATCH 26/27] vhost-user-gpu: check the PIXMAN format is supported marcandre.lureau
2021-03-12 10:01 ` [PATCH 27/27] vhost-user-gpu: implement GPU_QEMU_DBUS_LISTENER marcandre.lureau
2021-03-12 11:16 ` [PATCH 00/27] Add D-Bus display backend no-reply
2021-03-12 11:18 ` Gerd Hoffmann
2021-03-12 11:29   ` Marc-André Lureau
2021-03-12 14:37     ` Gerd Hoffmann
2021-03-12 14:57       ` Marc-André Lureau

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210312100108.2706195-16-marcandre.lureau@redhat.com \
    --to=marcandre.lureau@redhat.com \
    --cc=kraxel@redhat.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.