Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/1571492/?format=api
{ "id": 1571492, "url": "http://patchwork.ozlabs.org/api/patches/1571492/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20211221065855.142578-30-marcandre.lureau@redhat.com/", "project": { "id": 14, "url": "http://patchwork.ozlabs.org/api/projects/14/?format=api", "name": "QEMU Development", "link_name": "qemu-devel", "list_id": "qemu-devel.nongnu.org", "list_email": "qemu-devel@nongnu.org", "web_url": "", "scm_url": "", "webscm_url": "", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20211221065855.142578-30-marcandre.lureau@redhat.com>", "list_archive_url": null, "date": "2021-12-21T06:58:48", "name": "[PULL,v2,29/36] audio: add \"dbus\" audio backend", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "f5583701252c83b96799c4eaffeee1dd83de6910", "submitter": { "id": 66774, "url": "http://patchwork.ozlabs.org/api/people/66774/?format=api", "name": "Marc-André Lureau", "email": "marcandre.lureau@redhat.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20211221065855.142578-30-marcandre.lureau@redhat.com/mbox/", "series": [ { "id": 277865, "url": "http://patchwork.ozlabs.org/api/series/277865/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/list/?series=277865", "date": "2021-12-21T06:58:19", "name": "[PULL,v2,01/36] ui/vdagent: add CHECK_SPICE_PROTOCOL_VERSION", "version": 2, "mbox": "http://patchwork.ozlabs.org/series/277865/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/1571492/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/1571492/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>", "X-Original-To": "incoming@patchwork.ozlabs.org", "Delivered-To": "patchwork-incoming@bilbo.ozlabs.org", "Authentication-Results": [ "bilbo.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=b08Jk3+E;\n\tdkim-atps=neutral", "ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=<UNKNOWN>)", "relay.mimecast.com;\n auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=marcandre.lureau@redhat.com" ], "Received": [ "from lists.gnu.org (lists.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby bilbo.ozlabs.org (Postfix) with ESMTPS id 4JJ7jz4TmQz9s3q\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 21 Dec 2021 18:43:35 +1100 (AEDT)", "from localhost ([::1]:55086 helo=lists1p.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>)\n\tid 1mzZo5-00013A-Du\n\tfor incoming@patchwork.ozlabs.org; Tue, 21 Dec 2021 02:43:33 -0500", "from eggs.gnu.org ([209.51.188.92]:59150)\n by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <marcandre.lureau@redhat.com>)\n id 1mzZFI-0002S0-O4\n for qemu-devel@nongnu.org; Tue, 21 Dec 2021 02:07:37 -0500", "from us-smtp-delivery-124.mimecast.com ([170.10.129.124]:25983)\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <marcandre.lureau@redhat.com>)\n id 1mzZFF-0002Ub-9q\n for qemu-devel@nongnu.org; Tue, 21 Dec 2021 02:07:36 -0500", "from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com\n [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS\n (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id\n us-mta-65-Ybsm6vWLPCyKzOr4D5FHIQ-1; Tue, 21 Dec 2021 02:07:30 -0500", "from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com\n [10.5.11.22])\n (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))\n (No client certificate requested)\n by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 71EC48042E2;\n Tue, 21 Dec 2021 07:07:29 +0000 (UTC)", "from localhost (unknown [10.39.208.37])\n by smtp.corp.redhat.com (Postfix) with ESMTP id 69A79105B20B;\n Tue, 21 Dec 2021 07:07:02 +0000 (UTC)" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1640070452;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding:\n in-reply-to:in-reply-to:references:references;\n bh=uZmHqPJwF3GSSjNUX5ebugjGeisqMy1vI7MNbYpokPw=;\n b=b08Jk3+Ex3GJoMbuunpT9BCZkLyJitK6i9DaWi4E0BgErtF3aFxOGQizVDW0/5okWdvWSm\n D9RkQ74L1b7Gxhe9ujEt9uCjj4x663C5nHxsaBm+Vi9MSM6eIzUrlo8CuAVqQNIxqcnViQ\n o1oKbtJwxlhngvBnaDdyHdOxEQIMH1w=", "X-MC-Unique": "Ybsm6vWLPCyKzOr4D5FHIQ-1", "From": "marcandre.lureau@redhat.com", "To": "qemu-devel@nongnu.org", "Subject": "[PULL v2 29/36] audio: add \"dbus\" audio backend", "Date": "Tue, 21 Dec 2021 10:58:48 +0400", "Message-Id": "<20211221065855.142578-30-marcandre.lureau@redhat.com>", "In-Reply-To": "<20211221065855.142578-1-marcandre.lureau@redhat.com>", "References": "<20211221065855.142578-1-marcandre.lureau@redhat.com>", "MIME-Version": "1.0", "X-Scanned-By": "MIMEDefang 2.84 on 10.5.11.22", "X-Mimecast-Spam-Score": "0", "X-Mimecast-Originator": "redhat.com", "Content-Type": "text/plain; charset=UTF-8", "Content-Transfer-Encoding": "8bit", "Received-SPF": "pass client-ip=170.10.129.124;\n envelope-from=marcandre.lureau@redhat.com;\n helo=us-smtp-delivery-124.mimecast.com", "X-Spam_score_int": "-29", "X-Spam_score": "-3.0", "X-Spam_bar": "---", "X-Spam_report": "(-3.0 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.203,\n DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,\n RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001,\n SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no", "X-Spam_action": "no action", "X-BeenThere": "qemu-devel@nongnu.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "<qemu-devel.nongnu.org>", "List-Unsubscribe": "<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>", "List-Archive": "<https://lists.nongnu.org/archive/html/qemu-devel>", "List-Post": "<mailto:qemu-devel@nongnu.org>", "List-Help": "<mailto:qemu-devel-request@nongnu.org?subject=help>", "List-Subscribe": "<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>", "Cc": "peter.maydell@linaro.org, richard.henderson@linaro.org, =?utf-8?q?Marc-A?=\n\t=?utf-8?q?ndr=C3=A9_Lureau?= <marcandre.lureau@redhat.com>", "Errors-To": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org", "Sender": "\"Qemu-devel\"\n <qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>" }, "content": "From: Marc-André Lureau <marcandre.lureau@redhat.com>\n\nAdd a new -audio backend that accepts D-Bus clients/listeners to handle\nplayback & recording, to be exported via the -display dbus.\n\nExample usage:\n-audiodev dbus,in.mixing-engine=off,out.mixing-engine=off,id=dbus\n-display dbus,audiodev=dbus\n\nSigned-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>\nAcked-by: Gerd Hoffmann <kraxel@redhat.com>\n---\n qapi/audio.json | 3 +-\n qapi/ui.json | 5 +-\n audio/audio_int.h | 7 +\n audio/audio_template.h | 2 +\n ui/dbus.h | 1 +\n audio/audio.c | 1 +\n audio/dbusaudio.c | 654 +++++++++++++++++++++++++++++++++++++++++\n ui/dbus.c | 35 +++\n audio/meson.build | 6 +\n audio/trace-events | 5 +\n qemu-options.hx | 3 +\n ui/dbus-display1.xml | 211 +++++++++++++\n 12 files changed, 931 insertions(+), 2 deletions(-)\n create mode 100644 audio/dbusaudio.c", "diff": "diff --git a/qapi/audio.json b/qapi/audio.json\nindex 9cba0df8a4e9..693e327c6b6a 100644\n--- a/qapi/audio.json\n+++ b/qapi/audio.json\n@@ -386,7 +386,7 @@\n # Since: 4.0\n ##\n { 'enum': 'AudiodevDriver',\n- 'data': [ 'none', 'alsa', 'coreaudio', 'dsound', 'jack', 'oss', 'pa',\n+ 'data': [ 'none', 'alsa', 'coreaudio', 'dbus', 'dsound', 'jack', 'oss', 'pa',\n 'sdl', 'spice', 'wav' ] }\n \n ##\n@@ -412,6 +412,7 @@\n 'none': 'AudiodevGenericOptions',\n 'alsa': 'AudiodevAlsaOptions',\n 'coreaudio': 'AudiodevCoreaudioOptions',\n+ 'dbus': 'AudiodevGenericOptions',\n 'dsound': 'AudiodevDsoundOptions',\n 'jack': 'AudiodevJackOptions',\n 'oss': 'AudiodevOssOptions',\ndiff --git a/qapi/ui.json b/qapi/ui.json\nindex d435e9472264..2b4371da3777 100644\n--- a/qapi/ui.json\n+++ b/qapi/ui.json\n@@ -1134,13 +1134,16 @@\n # @p2p: Whether to use peer-to-peer connections (accepted through\n # ``add_client``).\n #\n+# @audiodev: Use the specified DBus audiodev to export audio.\n+#\n # Since: 7.0\n #\n ##\n { 'struct' : 'DisplayDBus',\n 'data' : { '*rendernode' : 'str',\n '*addr': 'str',\n- '*p2p': 'bool' } }\n+ '*p2p': 'bool',\n+ '*audiodev': 'str' } }\n \n ##\n # @DisplayGLMode:\ndiff --git a/audio/audio_int.h b/audio/audio_int.h\nindex 6d685e24a388..428a091d05e5 100644\n--- a/audio/audio_int.h\n+++ b/audio/audio_int.h\n@@ -31,6 +31,10 @@\n #endif\n #include \"mixeng.h\"\n \n+#ifdef CONFIG_GIO\n+#include <gio/gio.h>\n+#endif\n+\n struct audio_pcm_ops;\n \n struct audio_callback {\n@@ -140,6 +144,9 @@ struct audio_driver {\n const char *descr;\n void *(*init) (Audiodev *);\n void (*fini) (void *);\n+#ifdef CONFIG_GIO\n+ void (*set_dbus_server) (AudioState *s, GDBusObjectManagerServer *manager);\n+#endif\n struct audio_pcm_ops *pcm_ops;\n int can_be_default;\n int max_voices_out;\ndiff --git a/audio/audio_template.h b/audio/audio_template.h\nindex c6714946aaed..d2d348638b8c 100644\n--- a/audio/audio_template.h\n+++ b/audio/audio_template.h\n@@ -327,6 +327,8 @@ AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev)\n case AUDIODEV_DRIVER_COREAUDIO:\n return qapi_AudiodevCoreaudioPerDirectionOptions_base(\n dev->u.coreaudio.TYPE);\n+ case AUDIODEV_DRIVER_DBUS:\n+ return dev->u.dbus.TYPE;\n case AUDIODEV_DRIVER_DSOUND:\n return dev->u.dsound.TYPE;\n case AUDIODEV_DRIVER_JACK:\ndiff --git a/ui/dbus.h b/ui/dbus.h\nindex 4698d324632e..ca1f0f4ab94f 100644\n--- a/ui/dbus.h\n+++ b/ui/dbus.h\n@@ -36,6 +36,7 @@ struct DBusDisplay {\n DisplayGLMode gl_mode;\n bool p2p;\n char *dbus_addr;\n+ char *audiodev;\n DisplayGLCtx glctx;\n \n GDBusConnection *bus;\ndiff --git a/audio/audio.c b/audio/audio.c\nindex 54a153c0ef07..dc28685d226d 100644\n--- a/audio/audio.c\n+++ b/audio/audio.c\n@@ -2000,6 +2000,7 @@ void audio_create_pdos(Audiodev *dev)\n CASE(NONE, none, );\n CASE(ALSA, alsa, Alsa);\n CASE(COREAUDIO, coreaudio, Coreaudio);\n+ CASE(DBUS, dbus, );\n CASE(DSOUND, dsound, );\n CASE(JACK, jack, Jack);\n CASE(OSS, oss, Oss);\ndiff --git a/audio/dbusaudio.c b/audio/dbusaudio.c\nnew file mode 100644\nindex 000000000000..f178b47deec1\n--- /dev/null\n+++ b/audio/dbusaudio.c\n@@ -0,0 +1,654 @@\n+/*\n+ * QEMU DBus audio\n+ *\n+ * Copyright (c) 2021 Red Hat, Inc.\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \"Software\"), to deal\n+ * in the Software without restriction, including without limitation the rights\n+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+ * copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n+ * THE SOFTWARE.\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/error-report.h\"\n+#include \"qemu/host-utils.h\"\n+#include \"qemu/module.h\"\n+#include \"qemu/timer.h\"\n+#include \"qemu/dbus.h\"\n+\n+#include <gio/gunixfdlist.h>\n+#include \"ui/dbus-display1.h\"\n+\n+#define AUDIO_CAP \"dbus\"\n+#include \"audio.h\"\n+#include \"audio_int.h\"\n+#include \"trace.h\"\n+\n+#define DBUS_DISPLAY1_AUDIO_PATH DBUS_DISPLAY1_ROOT \"/Audio\"\n+\n+#define DBUS_AUDIO_NSAMPLES 1024 /* could be configured? */\n+\n+typedef struct DBusAudio {\n+ GDBusObjectManagerServer *server;\n+ GDBusObjectSkeleton *audio;\n+ QemuDBusDisplay1Audio *iface;\n+ GHashTable *out_listeners;\n+ GHashTable *in_listeners;\n+} DBusAudio;\n+\n+typedef struct DBusVoiceOut {\n+ HWVoiceOut hw;\n+ bool enabled;\n+ RateCtl rate;\n+\n+ void *buf;\n+ size_t buf_pos;\n+ size_t buf_size;\n+\n+ bool has_volume;\n+ Volume volume;\n+} DBusVoiceOut;\n+\n+typedef struct DBusVoiceIn {\n+ HWVoiceIn hw;\n+ bool enabled;\n+ RateCtl rate;\n+\n+ bool has_volume;\n+ Volume volume;\n+} DBusVoiceIn;\n+\n+static void *dbus_get_buffer_out(HWVoiceOut *hw, size_t *size)\n+{\n+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);\n+\n+ if (!vo->buf) {\n+ vo->buf_size = hw->samples * hw->info.bytes_per_frame;\n+ vo->buf = g_malloc(vo->buf_size);\n+ vo->buf_pos = 0;\n+ }\n+\n+ *size = MIN(vo->buf_size - vo->buf_pos, *size);\n+ *size = audio_rate_get_bytes(&hw->info, &vo->rate, *size);\n+\n+ return vo->buf + vo->buf_pos;\n+\n+}\n+\n+static size_t dbus_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)\n+{\n+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;\n+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);\n+ GHashTableIter iter;\n+ QemuDBusDisplay1AudioOutListener *listener = NULL;\n+ g_autoptr(GBytes) bytes = NULL;\n+ g_autoptr(GVariant) v_data = NULL;\n+\n+ assert(buf == vo->buf + vo->buf_pos && vo->buf_pos + size <= vo->buf_size);\n+ vo->buf_pos += size;\n+\n+ trace_dbus_audio_put_buffer_out(size);\n+\n+ if (vo->buf_pos < vo->buf_size) {\n+ return size;\n+ }\n+\n+ bytes = g_bytes_new_take(g_steal_pointer(&vo->buf), vo->buf_size);\n+ v_data = g_variant_new_from_bytes(G_VARIANT_TYPE(\"ay\"), bytes, TRUE);\n+ g_variant_ref_sink(v_data);\n+\n+ g_hash_table_iter_init(&iter, da->out_listeners);\n+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {\n+ qemu_dbus_display1_audio_out_listener_call_write(\n+ listener,\n+ (uintptr_t)hw,\n+ v_data,\n+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);\n+ }\n+\n+ return size;\n+}\n+\n+#ifdef HOST_WORDS_BIGENDIAN\n+#define AUDIO_HOST_BE TRUE\n+#else\n+#define AUDIO_HOST_BE FALSE\n+#endif\n+\n+static void\n+dbus_init_out_listener(QemuDBusDisplay1AudioOutListener *listener,\n+ HWVoiceOut *hw)\n+{\n+ qemu_dbus_display1_audio_out_listener_call_init(\n+ listener,\n+ (uintptr_t)hw,\n+ hw->info.bits,\n+ hw->info.is_signed,\n+ hw->info.is_float,\n+ hw->info.freq,\n+ hw->info.nchannels,\n+ hw->info.bytes_per_frame,\n+ hw->info.bytes_per_second,\n+ hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,\n+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);\n+}\n+\n+static int\n+dbus_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)\n+{\n+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;\n+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);\n+ GHashTableIter iter;\n+ QemuDBusDisplay1AudioOutListener *listener = NULL;\n+\n+ audio_pcm_init_info(&hw->info, as);\n+ hw->samples = DBUS_AUDIO_NSAMPLES;\n+ audio_rate_start(&vo->rate);\n+\n+ g_hash_table_iter_init(&iter, da->out_listeners);\n+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {\n+ dbus_init_out_listener(listener, hw);\n+ }\n+ return 0;\n+}\n+\n+static void\n+dbus_fini_out(HWVoiceOut *hw)\n+{\n+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;\n+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);\n+ GHashTableIter iter;\n+ QemuDBusDisplay1AudioOutListener *listener = NULL;\n+\n+ g_hash_table_iter_init(&iter, da->out_listeners);\n+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {\n+ qemu_dbus_display1_audio_out_listener_call_fini(\n+ listener,\n+ (uintptr_t)hw,\n+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);\n+ }\n+\n+ g_clear_pointer(&vo->buf, g_free);\n+}\n+\n+static void\n+dbus_enable_out(HWVoiceOut *hw, bool enable)\n+{\n+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;\n+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);\n+ GHashTableIter iter;\n+ QemuDBusDisplay1AudioOutListener *listener = NULL;\n+\n+ vo->enabled = enable;\n+ if (enable) {\n+ audio_rate_start(&vo->rate);\n+ }\n+\n+ g_hash_table_iter_init(&iter, da->out_listeners);\n+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {\n+ qemu_dbus_display1_audio_out_listener_call_set_enabled(\n+ listener, (uintptr_t)hw, enable,\n+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);\n+ }\n+}\n+\n+static void\n+dbus_volume_out_listener(HWVoiceOut *hw,\n+ QemuDBusDisplay1AudioOutListener *listener)\n+{\n+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);\n+ Volume *vol = &vo->volume;\n+ g_autoptr(GBytes) bytes = NULL;\n+ GVariant *v_vol = NULL;\n+\n+ if (!vo->has_volume) {\n+ return;\n+ }\n+\n+ assert(vol->channels < sizeof(vol->vol));\n+ bytes = g_bytes_new(vol->vol, vol->channels);\n+ v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE(\"ay\"), bytes, TRUE);\n+ qemu_dbus_display1_audio_out_listener_call_set_volume(\n+ listener, (uintptr_t)hw, vol->mute, v_vol,\n+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);\n+}\n+\n+static void\n+dbus_volume_out(HWVoiceOut *hw, Volume *vol)\n+{\n+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;\n+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);\n+ GHashTableIter iter;\n+ QemuDBusDisplay1AudioOutListener *listener = NULL;\n+\n+ vo->has_volume = true;\n+ vo->volume = *vol;\n+\n+ g_hash_table_iter_init(&iter, da->out_listeners);\n+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {\n+ dbus_volume_out_listener(hw, listener);\n+ }\n+}\n+\n+static void\n+dbus_init_in_listener(QemuDBusDisplay1AudioInListener *listener, HWVoiceIn *hw)\n+{\n+ qemu_dbus_display1_audio_in_listener_call_init(\n+ listener,\n+ (uintptr_t)hw,\n+ hw->info.bits,\n+ hw->info.is_signed,\n+ hw->info.is_float,\n+ hw->info.freq,\n+ hw->info.nchannels,\n+ hw->info.bytes_per_frame,\n+ hw->info.bytes_per_second,\n+ hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,\n+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);\n+}\n+\n+static int\n+dbus_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)\n+{\n+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;\n+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);\n+ GHashTableIter iter;\n+ QemuDBusDisplay1AudioInListener *listener = NULL;\n+\n+ audio_pcm_init_info(&hw->info, as);\n+ hw->samples = DBUS_AUDIO_NSAMPLES;\n+ audio_rate_start(&vo->rate);\n+\n+ g_hash_table_iter_init(&iter, da->in_listeners);\n+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {\n+ dbus_init_in_listener(listener, hw);\n+ }\n+ return 0;\n+}\n+\n+static void\n+dbus_fini_in(HWVoiceIn *hw)\n+{\n+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;\n+ GHashTableIter iter;\n+ QemuDBusDisplay1AudioInListener *listener = NULL;\n+\n+ g_hash_table_iter_init(&iter, da->in_listeners);\n+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {\n+ qemu_dbus_display1_audio_in_listener_call_fini(\n+ listener,\n+ (uintptr_t)hw,\n+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);\n+ }\n+}\n+\n+static void\n+dbus_volume_in_listener(HWVoiceIn *hw,\n+ QemuDBusDisplay1AudioInListener *listener)\n+{\n+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);\n+ Volume *vol = &vo->volume;\n+ g_autoptr(GBytes) bytes = NULL;\n+ GVariant *v_vol = NULL;\n+\n+ if (!vo->has_volume) {\n+ return;\n+ }\n+\n+ assert(vol->channels < sizeof(vol->vol));\n+ bytes = g_bytes_new(vol->vol, vol->channels);\n+ v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE(\"ay\"), bytes, TRUE);\n+ qemu_dbus_display1_audio_in_listener_call_set_volume(\n+ listener, (uintptr_t)hw, vol->mute, v_vol,\n+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);\n+}\n+\n+static void\n+dbus_volume_in(HWVoiceIn *hw, Volume *vol)\n+{\n+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;\n+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);\n+ GHashTableIter iter;\n+ QemuDBusDisplay1AudioInListener *listener = NULL;\n+\n+ vo->has_volume = true;\n+ vo->volume = *vol;\n+\n+ g_hash_table_iter_init(&iter, da->in_listeners);\n+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {\n+ dbus_volume_in_listener(hw, listener);\n+ }\n+}\n+\n+static size_t\n+dbus_read(HWVoiceIn *hw, void *buf, size_t size)\n+{\n+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;\n+ /* DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); */\n+ GHashTableIter iter;\n+ QemuDBusDisplay1AudioInListener *listener = NULL;\n+\n+ trace_dbus_audio_read(size);\n+\n+ /* size = audio_rate_get_bytes(&hw->info, &vo->rate, size); */\n+\n+ g_hash_table_iter_init(&iter, da->in_listeners);\n+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {\n+ g_autoptr(GVariant) v_data = NULL;\n+ const char *data;\n+ gsize n = 0;\n+\n+ if (qemu_dbus_display1_audio_in_listener_call_read_sync(\n+ listener,\n+ (uintptr_t)hw,\n+ size,\n+ G_DBUS_CALL_FLAGS_NONE, -1,\n+ &v_data, NULL, NULL)) {\n+ data = g_variant_get_fixed_array(v_data, &n, 1);\n+ g_warn_if_fail(n <= size);\n+ size = MIN(n, size);\n+ memcpy(buf, data, size);\n+ break;\n+ }\n+ }\n+\n+ return size;\n+}\n+\n+static void\n+dbus_enable_in(HWVoiceIn *hw, bool enable)\n+{\n+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;\n+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);\n+ GHashTableIter iter;\n+ QemuDBusDisplay1AudioInListener *listener = NULL;\n+\n+ vo->enabled = enable;\n+ if (enable) {\n+ audio_rate_start(&vo->rate);\n+ }\n+\n+ g_hash_table_iter_init(&iter, da->in_listeners);\n+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {\n+ qemu_dbus_display1_audio_in_listener_call_set_enabled(\n+ listener, (uintptr_t)hw, enable,\n+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);\n+ }\n+}\n+\n+static void *\n+dbus_audio_init(Audiodev *dev)\n+{\n+ DBusAudio *da = g_new0(DBusAudio, 1);\n+\n+ da->out_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,\n+ g_free, g_object_unref);\n+ da->in_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,\n+ g_free, g_object_unref);\n+ return da;\n+}\n+\n+static void\n+dbus_audio_fini(void *opaque)\n+{\n+ DBusAudio *da = opaque;\n+\n+ if (da->server) {\n+ g_dbus_object_manager_server_unexport(da->server,\n+ DBUS_DISPLAY1_AUDIO_PATH);\n+ }\n+ g_clear_object(&da->audio);\n+ g_clear_object(&da->iface);\n+ g_clear_pointer(&da->in_listeners, g_hash_table_unref);\n+ g_clear_pointer(&da->out_listeners, g_hash_table_unref);\n+ g_clear_object(&da->server);\n+ g_free(da);\n+}\n+\n+static void\n+listener_out_vanished_cb(GDBusConnection *connection,\n+ gboolean remote_peer_vanished,\n+ GError *error,\n+ DBusAudio *da)\n+{\n+ char *name = g_object_get_data(G_OBJECT(connection), \"name\");\n+\n+ g_hash_table_remove(da->out_listeners, name);\n+}\n+\n+static void\n+listener_in_vanished_cb(GDBusConnection *connection,\n+ gboolean remote_peer_vanished,\n+ GError *error,\n+ DBusAudio *da)\n+{\n+ char *name = g_object_get_data(G_OBJECT(connection), \"name\");\n+\n+ g_hash_table_remove(da->in_listeners, name);\n+}\n+\n+static gboolean\n+dbus_audio_register_listener(AudioState *s,\n+ GDBusMethodInvocation *invocation,\n+ GUnixFDList *fd_list,\n+ GVariant *arg_listener,\n+ bool out)\n+{\n+ DBusAudio *da = s->drv_opaque;\n+ const char *sender = g_dbus_method_invocation_get_sender(invocation);\n+ g_autoptr(GDBusConnection) listener_conn = NULL;\n+ g_autoptr(GError) err = NULL;\n+ g_autoptr(GSocket) socket = NULL;\n+ g_autoptr(GSocketConnection) socket_conn = NULL;\n+ g_autofree char *guid = g_dbus_generate_guid();\n+ GHashTable *listeners = out ? da->out_listeners : da->in_listeners;\n+ GObject *listener;\n+ int fd;\n+\n+ trace_dbus_audio_register(sender, out ? \"out\" : \"in\");\n+\n+ if (g_hash_table_contains(listeners, sender)) {\n+ g_dbus_method_invocation_return_error(invocation,\n+ DBUS_DISPLAY_ERROR,\n+ DBUS_DISPLAY_ERROR_INVALID,\n+ \"`%s` is already registered!\",\n+ sender);\n+ return DBUS_METHOD_INVOCATION_HANDLED;\n+ }\n+\n+ fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);\n+ if (err) {\n+ g_dbus_method_invocation_return_error(invocation,\n+ DBUS_DISPLAY_ERROR,\n+ DBUS_DISPLAY_ERROR_FAILED,\n+ \"Couldn't get peer fd: %s\",\n+ err->message);\n+ return DBUS_METHOD_INVOCATION_HANDLED;\n+ }\n+\n+ socket = g_socket_new_from_fd(fd, &err);\n+ if (err) {\n+ g_dbus_method_invocation_return_error(invocation,\n+ DBUS_DISPLAY_ERROR,\n+ DBUS_DISPLAY_ERROR_FAILED,\n+ \"Couldn't make a socket: %s\",\n+ err->message);\n+ return DBUS_METHOD_INVOCATION_HANDLED;\n+ }\n+ socket_conn = g_socket_connection_factory_create_connection(socket);\n+ if (out) {\n+ qemu_dbus_display1_audio_complete_register_out_listener(\n+ da->iface, invocation, NULL);\n+ } else {\n+ qemu_dbus_display1_audio_complete_register_in_listener(\n+ da->iface, invocation, NULL);\n+ }\n+\n+ listener_conn =\n+ g_dbus_connection_new_sync(\n+ G_IO_STREAM(socket_conn),\n+ guid,\n+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,\n+ NULL, NULL, &err);\n+ if (err) {\n+ error_report(\"Failed to setup peer connection: %s\", err->message);\n+ return DBUS_METHOD_INVOCATION_HANDLED;\n+ }\n+\n+ listener = out ?\n+ G_OBJECT(qemu_dbus_display1_audio_out_listener_proxy_new_sync(\n+ listener_conn,\n+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,\n+ NULL,\n+ \"/org/qemu/Display1/AudioOutListener\",\n+ NULL,\n+ &err)) :\n+ G_OBJECT(qemu_dbus_display1_audio_in_listener_proxy_new_sync(\n+ listener_conn,\n+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,\n+ NULL,\n+ \"/org/qemu/Display1/AudioInListener\",\n+ NULL,\n+ &err));\n+ if (!listener) {\n+ error_report(\"Failed to setup proxy: %s\", err->message);\n+ return DBUS_METHOD_INVOCATION_HANDLED;\n+ }\n+\n+ if (out) {\n+ HWVoiceOut *hw;\n+\n+ QLIST_FOREACH(hw, &s->hw_head_out, entries) {\n+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);\n+ QemuDBusDisplay1AudioOutListener *l =\n+ QEMU_DBUS_DISPLAY1_AUDIO_OUT_LISTENER(listener);\n+\n+ dbus_init_out_listener(l, hw);\n+ qemu_dbus_display1_audio_out_listener_call_set_enabled(\n+ l, (uintptr_t)hw, vo->enabled,\n+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);\n+ }\n+ } else {\n+ HWVoiceIn *hw;\n+\n+ QLIST_FOREACH(hw, &s->hw_head_in, entries) {\n+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);\n+ QemuDBusDisplay1AudioInListener *l =\n+ QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener);\n+\n+ dbus_init_in_listener(\n+ QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener), hw);\n+ qemu_dbus_display1_audio_in_listener_call_set_enabled(\n+ l, (uintptr_t)hw, vo->enabled,\n+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);\n+ }\n+ }\n+\n+ g_object_set_data_full(G_OBJECT(listener_conn), \"name\",\n+ g_strdup(sender), g_free);\n+ g_hash_table_insert(listeners, g_strdup(sender), listener);\n+ g_object_connect(listener_conn,\n+ \"signal::closed\",\n+ out ? listener_out_vanished_cb : listener_in_vanished_cb,\n+ da,\n+ NULL);\n+\n+ return DBUS_METHOD_INVOCATION_HANDLED;\n+}\n+\n+static gboolean\n+dbus_audio_register_out_listener(AudioState *s,\n+ GDBusMethodInvocation *invocation,\n+ GUnixFDList *fd_list,\n+ GVariant *arg_listener)\n+{\n+ return dbus_audio_register_listener(s, invocation,\n+ fd_list, arg_listener, true);\n+\n+}\n+\n+static gboolean\n+dbus_audio_register_in_listener(AudioState *s,\n+ GDBusMethodInvocation *invocation,\n+ GUnixFDList *fd_list,\n+ GVariant *arg_listener)\n+{\n+ return dbus_audio_register_listener(s, invocation,\n+ fd_list, arg_listener, false);\n+}\n+\n+static void\n+dbus_audio_set_server(AudioState *s, GDBusObjectManagerServer *server)\n+{\n+ DBusAudio *da = s->drv_opaque;\n+\n+ g_assert(da);\n+ g_assert(!da->server);\n+\n+ da->server = g_object_ref(server);\n+\n+ da->audio = g_dbus_object_skeleton_new(DBUS_DISPLAY1_AUDIO_PATH);\n+ da->iface = qemu_dbus_display1_audio_skeleton_new();\n+ g_object_connect(da->iface,\n+ \"swapped-signal::handle-register-in-listener\",\n+ dbus_audio_register_in_listener, s,\n+ \"swapped-signal::handle-register-out-listener\",\n+ dbus_audio_register_out_listener, s,\n+ NULL);\n+\n+ g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(da->audio),\n+ G_DBUS_INTERFACE_SKELETON(da->iface));\n+ g_dbus_object_manager_server_export(da->server, da->audio);\n+}\n+\n+static struct audio_pcm_ops dbus_pcm_ops = {\n+ .init_out = dbus_init_out,\n+ .fini_out = dbus_fini_out,\n+ .write = audio_generic_write,\n+ .get_buffer_out = dbus_get_buffer_out,\n+ .put_buffer_out = dbus_put_buffer_out,\n+ .enable_out = dbus_enable_out,\n+ .volume_out = dbus_volume_out,\n+\n+ .init_in = dbus_init_in,\n+ .fini_in = dbus_fini_in,\n+ .read = dbus_read,\n+ .run_buffer_in = audio_generic_run_buffer_in,\n+ .enable_in = dbus_enable_in,\n+ .volume_in = dbus_volume_in,\n+};\n+\n+static struct audio_driver dbus_audio_driver = {\n+ .name = \"dbus\",\n+ .descr = \"Timer based audio exposed with DBus interface\",\n+ .init = dbus_audio_init,\n+ .fini = dbus_audio_fini,\n+ .set_dbus_server = dbus_audio_set_server,\n+ .pcm_ops = &dbus_pcm_ops,\n+ .can_be_default = 1,\n+ .max_voices_out = INT_MAX,\n+ .max_voices_in = INT_MAX,\n+ .voice_size_out = sizeof(DBusVoiceOut),\n+ .voice_size_in = sizeof(DBusVoiceIn)\n+};\n+\n+static void register_audio_dbus(void)\n+{\n+ audio_driver_register(&dbus_audio_driver);\n+}\n+type_init(register_audio_dbus);\n+\n+module_dep(\"ui-dbus\")\ndiff --git a/ui/dbus.c b/ui/dbus.c\nindex 847a66782116..d24f704d4662 100644\n--- a/ui/dbus.c\n+++ b/ui/dbus.c\n@@ -30,6 +30,8 @@\n #include \"ui/dbus-module.h\"\n #include \"ui/egl-helpers.h\"\n #include \"ui/egl-context.h\"\n+#include \"audio/audio.h\"\n+#include \"audio/audio_int.h\"\n #include \"qapi/error.h\"\n #include \"trace.h\"\n \n@@ -84,6 +86,7 @@ dbus_display_finalize(Object *o)\n g_clear_object(&dd->bus);\n g_clear_object(&dd->iface);\n g_free(dd->dbus_addr);\n+ g_free(dd->audiodev);\n dbus_display = NULL;\n }\n \n@@ -140,6 +143,19 @@ dbus_display_complete(UserCreatable *uc, Error **errp)\n return;\n }\n \n+ if (dd->audiodev && *dd->audiodev) {\n+ AudioState *audio_state = audio_state_by_name(dd->audiodev);\n+ if (!audio_state) {\n+ error_setg(errp, \"Audiodev '%s' not found\", dd->audiodev);\n+ return;\n+ }\n+ if (!g_str_equal(audio_state->drv->name, \"dbus\")) {\n+ error_setg(errp, \"Audiodev '%s' is not compatible with DBus\",\n+ dd->audiodev);\n+ return;\n+ }\n+ audio_state->drv->set_dbus_server(audio_state, dd->server);\n+ }\n \n consoles = g_array_new(FALSE, FALSE, sizeof(guint32));\n for (idx = 0;; idx++) {\n@@ -261,6 +277,23 @@ set_dbus_addr(Object *o, const char *str, Error **errp)\n dd->dbus_addr = g_strdup(str);\n }\n \n+static char *\n+get_audiodev(Object *o, Error **errp)\n+{\n+ DBusDisplay *dd = DBUS_DISPLAY(o);\n+\n+ return g_strdup(dd->audiodev);\n+}\n+\n+static void\n+set_audiodev(Object *o, const char *str, Error **errp)\n+{\n+ DBusDisplay *dd = DBUS_DISPLAY(o);\n+\n+ g_free(dd->audiodev);\n+ dd->audiodev = g_strdup(str);\n+}\n+\n static int\n get_gl_mode(Object *o, Error **errp)\n {\n@@ -285,6 +318,7 @@ dbus_display_class_init(ObjectClass *oc, void *data)\n ucc->complete = dbus_display_complete;\n object_class_property_add_bool(oc, \"p2p\", get_dbus_p2p, set_dbus_p2p);\n object_class_property_add_str(oc, \"addr\", get_dbus_addr, set_dbus_addr);\n+ object_class_property_add_str(oc, \"audiodev\", get_audiodev, set_audiodev);\n object_class_property_add_enum(oc, \"gl-mode\",\n \"DisplayGLMode\", &DisplayGLMode_lookup,\n get_gl_mode, set_gl_mode);\n@@ -321,6 +355,7 @@ dbus_init(DisplayState *ds, DisplayOptions *opts)\n object_get_objects_root(),\n \"dbus-display\", &error_fatal,\n \"addr\", opts->u.dbus.addr ?: \"\",\n+ \"audiodev\", opts->u.dbus.audiodev ?: \"\",\n \"gl-mode\", DisplayGLMode_str(mode),\n \"p2p\", yes_no(opts->u.dbus.p2p),\n NULL);\ndiff --git a/audio/meson.build b/audio/meson.build\nindex 462533bb8c22..0ac3791d0bd1 100644\n--- a/audio/meson.build\n+++ b/audio/meson.build\n@@ -26,4 +26,10 @@ foreach m : [\n endif\n endforeach\n \n+if dbus_display\n+ module_ss = ss.source_set()\n+ module_ss.add(when: gio, if_true: files('dbusaudio.c'))\n+ audio_modules += {'dbus': module_ss}\n+endif\n+\n modules += {'audio': audio_modules}\ndiff --git a/audio/trace-events b/audio/trace-events\nindex 957c92337bee..e1ab643add39 100644\n--- a/audio/trace-events\n+++ b/audio/trace-events\n@@ -13,6 +13,11 @@ alsa_resume_out(void) \"Resuming suspended output stream\"\n # ossaudio.c\n oss_version(int version) \"OSS version = 0x%x\"\n \n+# dbusaudio.c\n+dbus_audio_register(const char *s, const char *dir) \"sender = %s, dir = %s\"\n+dbus_audio_put_buffer_out(size_t len) \"len = %zu\"\n+dbus_audio_read(size_t len) \"len = %zu\"\n+\n # audio.c\n audio_timer_start(int interval) \"interval %d ms\"\n audio_timer_stop(void) \"\"\ndiff --git a/qemu-options.hx b/qemu-options.hx\nindex 977e0873a12c..7d4751094783 100644\n--- a/qemu-options.hx\n+++ b/qemu-options.hx\n@@ -659,6 +659,9 @@ DEF(\"audiodev\", HAS_ARG, QEMU_OPTION_audiodev,\n #endif\n #ifdef CONFIG_SPICE\n \"-audiodev spice,id=id[,prop[=value][,...]]\\n\"\n+#endif\n+#ifdef CONFIG_DBUS_DISPLAY\n+ \"-audiodev dbus,id=id[,prop[=value][,...]]\\n\"\n #endif\n \"-audiodev wav,id=id[,prop[=value][,...]]\\n\"\n \" path= path of wav file to record\\n\",\ndiff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml\nindex 0f0ae92e4d57..aff645220c88 100644\n--- a/ui/dbus-display1.xml\n+++ b/ui/dbus-display1.xml\n@@ -375,4 +375,215 @@\n </arg>\n </method>\n </interface>\n+\n+ <!--\n+ org.qemu.Display1.Audio:\n+\n+ Audio backend may be available on ``/org/qemu/Display1/Audio``.\n+ -->\n+ <interface name=\"org.qemu.Display1.Audio\">\n+ <!--\n+ RegisterOutListener:\n+ @listener: a Unix socket FD, for peer-to-peer D-Bus communication.\n+\n+ Register an audio backend playback handler.\n+\n+ Multiple listeners may be registered simultaneously.\n+\n+ The listener is expected to implement the\n+ :dbus:iface:`org.qemu.Display1.AudioOutListener` interface.\n+ -->\n+ <method name=\"RegisterOutListener\">\n+ <arg type=\"h\" name=\"listener\" direction=\"in\"/>\n+ </method>\n+\n+ <!--\n+ RegisterInListener:\n+ @listener: a Unix socket FD, for peer-to-peer D-Bus communication.\n+\n+ Register an audio backend record handler.\n+\n+ Multiple listeners may be registered simultaneously.\n+\n+ The listener is expected to implement the\n+ :dbus:iface:`org.qemu.Display1.AudioInListener` interface.\n+ -->\n+ <method name=\"RegisterInListener\">\n+ <arg type=\"h\" name=\"listener\" direction=\"in\"/>\n+ </method>\n+ </interface>\n+\n+ <!--\n+ org.qemu.Display1.AudioOutListener:\n+\n+ This client-side interface must be available on\n+ ``/org/qemu/Display1/AudioOutListener`` when registering the peer-to-peer\n+ connection with :dbus:meth:`~org.qemu.Display1.Audio.RegisterOutListener`.\n+ -->\n+ <interface name=\"org.qemu.Display1.AudioOutListener\">\n+ <!--\n+ Init:\n+ @id: the stream ID.\n+ @bits: PCM bits per sample.\n+ @is_signed: whether the PCM data is signed.\n+ @is_float: PCM floating point format.\n+ @freq: the PCM frequency in Hz.\n+ @nchannels: the number of channels.\n+ @bytes_per_frame: the bytes per frame.\n+ @bytes_per_second: the bytes per second.\n+ @be: whether using big-endian format.\n+\n+ Initializes a PCM playback stream.\n+ -->\n+ <method name=\"Init\">\n+ <arg name=\"id\" type=\"t\" direction=\"in\"/>\n+ <arg name=\"bits\" type=\"y\" direction=\"in\"/>\n+ <arg name=\"is_signed\" type=\"b\" direction=\"in\"/>\n+ <arg name=\"is_float\" type=\"b\" direction=\"in\"/>\n+ <arg name=\"freq\" type=\"u\" direction=\"in\"/>\n+ <arg name=\"nchannels\" type=\"y\" direction=\"in\"/>\n+ <arg name=\"bytes_per_frame\" type=\"u\" direction=\"in\"/>\n+ <arg name=\"bytes_per_second\" type=\"u\" direction=\"in\"/>\n+ <arg name=\"be\" type=\"b\" direction=\"in\"/>\n+ </method>\n+\n+ <!--\n+ Fini:\n+ @id: the stream ID.\n+\n+ Finish & close a playback stream.\n+ -->\n+ <method name=\"Fini\">\n+ <arg name=\"id\" type=\"t\" direction=\"in\"/>\n+ </method>\n+\n+ <!--\n+ SetEnabled:\n+ @id: the stream ID.\n+\n+ Resume or suspend the playback stream.\n+ -->\n+ <method name=\"SetEnabled\">\n+ <arg name=\"id\" type=\"t\" direction=\"in\"/>\n+ <arg name=\"enabled\" type=\"b\" direction=\"in\"/>\n+ </method>\n+\n+ <!--\n+ SetVolume:\n+ @id: the stream ID.\n+ @mute: whether the stream is muted.\n+ @volume: the volume per-channel.\n+\n+ Set the stream volume and mute state (volume without unit, 0-255).\n+ -->\n+ <method name=\"SetVolume\">\n+ <arg name=\"id\" type=\"t\" direction=\"in\"/>\n+ <arg name=\"mute\" type=\"b\" direction=\"in\"/>\n+ <arg name=\"volume\" type=\"ay\" direction=\"in\">\n+ <annotation name=\"org.gtk.GDBus.C.ForceGVariant\" value=\"true\"/>\n+ </arg>\n+ </method>\n+\n+ <!--\n+ Write:\n+ @id: the stream ID.\n+ @data: the PCM data.\n+\n+ PCM stream to play.\n+ -->\n+ <method name=\"Write\">\n+ <arg name=\"id\" type=\"t\" direction=\"in\"/>\n+ <arg type=\"ay\" name=\"data\" direction=\"in\">\n+ <annotation name=\"org.gtk.GDBus.C.ForceGVariant\" value=\"true\"/>\n+ </arg>\n+ </method>\n+ </interface>\n+\n+ <!--\n+ org.qemu.Display1.AudioInListener:\n+\n+ This client-side interface must be available on\n+ ``/org/qemu/Display1/AudioInListener`` when registering the peer-to-peer\n+ connection with :dbus:meth:`~org.qemu.Display1.Audio.RegisterInListener`.\n+ -->\n+ <interface name=\"org.qemu.Display1.AudioInListener\">\n+ <!--\n+ Init:\n+ @id: the stream ID.\n+ @bits: PCM bits per sample.\n+ @is_signed: whether the PCM data is signed.\n+ @is_float: PCM floating point format.\n+ @freq: the PCM frequency in Hz.\n+ @nchannels: the number of channels.\n+ @bytes_per_frame: the bytes per frame.\n+ @bytes_per_second: the bytes per second.\n+ @be: whether using big-endian format.\n+\n+ Initializes a PCM record stream.\n+ -->\n+ <method name=\"Init\">\n+ <arg name=\"id\" type=\"t\" direction=\"in\"/>\n+ <arg name=\"bits\" type=\"y\" direction=\"in\"/>\n+ <arg name=\"is_signed\" type=\"b\" direction=\"in\"/>\n+ <arg name=\"is_float\" type=\"b\" direction=\"in\"/>\n+ <arg name=\"freq\" type=\"u\" direction=\"in\"/>\n+ <arg name=\"nchannels\" type=\"y\" direction=\"in\"/>\n+ <arg name=\"bytes_per_frame\" type=\"u\" direction=\"in\"/>\n+ <arg name=\"bytes_per_second\" type=\"u\" direction=\"in\"/>\n+ <arg name=\"be\" type=\"b\" direction=\"in\"/>\n+ </method>\n+\n+ <!--\n+ Fini:\n+ @id: the stream ID.\n+\n+ Finish & close a record stream.\n+ -->\n+ <method name=\"Fini\">\n+ <arg name=\"id\" type=\"t\" direction=\"in\"/>\n+ </method>\n+\n+ <!--\n+ SetEnabled:\n+ @id: the stream ID.\n+\n+ Resume or suspend the record stream.\n+ -->\n+ <method name=\"SetEnabled\">\n+ <arg name=\"id\" type=\"t\" direction=\"in\"/>\n+ <arg name=\"enabled\" type=\"b\" direction=\"in\"/>\n+ </method>\n+\n+ <!--\n+ SetVolume:\n+ @id: the stream ID.\n+ @mute: whether the stream is muted.\n+ @volume: the volume per-channel.\n+\n+ Set the stream volume and mute state (volume without unit, 0-255).\n+ -->\n+ <method name=\"SetVolume\">\n+ <arg name=\"id\" type=\"t\" direction=\"in\"/>\n+ <arg name=\"mute\" type=\"b\" direction=\"in\"/>\n+ <arg name=\"volume\" type=\"ay\" direction=\"in\">\n+ <annotation name=\"org.gtk.GDBus.C.ForceGVariant\" value=\"true\"/>\n+ </arg>\n+ </method>\n+\n+ <!--\n+ Read:\n+ @id: the stream ID.\n+ @size: the amount to read, in bytes.\n+ @data: the recorded data (which may be less than requested).\n+\n+ Read \"size\" bytes from the record stream.\n+ -->\n+ <method name=\"Read\">\n+ <arg name=\"id\" type=\"t\" direction=\"in\"/>\n+ <arg name=\"size\" type=\"t\" direction=\"in\"/>\n+ <arg type=\"ay\" name=\"data\" direction=\"out\">\n+ <annotation name=\"org.gtk.GDBus.C.ForceGVariant\" value=\"true\"/>\n+ </arg>\n+ </method>\n+ </interface>\n </node>\n", "prefixes": [ "PULL", "v2", "29/36" ] }