4 * Copyright (c) 2021 Red Hat, Inc.
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 #include "qemu/osdep.h"
26 #include "qemu/error-report.h"
27 #include "qemu/host-utils.h"
28 #include "qemu/module.h"
29 #include "qemu/timer.h"
30 #include "qemu/dbus.h"
32 #include <gio/gunixfdlist.h>
33 #include "ui/dbus-display1.h"
35 #define AUDIO_CAP "dbus"
37 #include "audio_int.h"
40 #define DBUS_DISPLAY1_AUDIO_PATH DBUS_DISPLAY1_ROOT "/Audio"
42 #define DBUS_AUDIO_NSAMPLES 1024 /* could be configured? */
44 typedef struct DBusAudio {
45 GDBusObjectManagerServer *server;
46 GDBusObjectSkeleton *audio;
47 QemuDBusDisplay1Audio *iface;
48 GHashTable *out_listeners;
49 GHashTable *in_listeners;
52 typedef struct DBusVoiceOut {
65 typedef struct DBusVoiceIn {
74 static void *dbus_get_buffer_out(HWVoiceOut *hw, size_t *size)
76 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
79 vo->buf_size = hw->samples * hw->info.bytes_per_frame;
80 vo->buf = g_malloc(vo->buf_size);
84 *size = MIN(vo->buf_size - vo->buf_pos, *size);
85 *size = audio_rate_get_bytes(&vo->rate, &hw->info, *size);
87 return vo->buf + vo->buf_pos;
91 static size_t dbus_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
93 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
94 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
96 QemuDBusDisplay1AudioOutListener *listener = NULL;
97 g_autoptr(GBytes) bytes = NULL;
98 g_autoptr(GVariant) v_data = NULL;
100 assert(buf == vo->buf + vo->buf_pos && vo->buf_pos + size <= vo->buf_size);
103 trace_dbus_audio_put_buffer_out(size);
105 if (vo->buf_pos < vo->buf_size) {
109 bytes = g_bytes_new_take(g_steal_pointer(&vo->buf), vo->buf_size);
110 v_data = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
111 g_variant_ref_sink(v_data);
113 g_hash_table_iter_init(&iter, da->out_listeners);
114 while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
115 qemu_dbus_display1_audio_out_listener_call_write(
119 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
126 #define AUDIO_HOST_BE TRUE
128 #define AUDIO_HOST_BE FALSE
132 dbus_init_out_listener(QemuDBusDisplay1AudioOutListener *listener,
135 qemu_dbus_display1_audio_out_listener_call_init(
143 hw->info.bytes_per_frame,
144 hw->info.bytes_per_second,
145 hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,
146 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
150 dbus_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
152 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
153 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
155 QemuDBusDisplay1AudioOutListener *listener = NULL;
157 audio_pcm_init_info(&hw->info, as);
158 hw->samples = DBUS_AUDIO_NSAMPLES;
159 audio_rate_start(&vo->rate);
161 g_hash_table_iter_init(&iter, da->out_listeners);
162 while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
163 dbus_init_out_listener(listener, hw);
169 dbus_fini_out(HWVoiceOut *hw)
171 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
172 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
174 QemuDBusDisplay1AudioOutListener *listener = NULL;
176 g_hash_table_iter_init(&iter, da->out_listeners);
177 while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
178 qemu_dbus_display1_audio_out_listener_call_fini(
181 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
184 g_clear_pointer(&vo->buf, g_free);
188 dbus_enable_out(HWVoiceOut *hw, bool enable)
190 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
191 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
193 QemuDBusDisplay1AudioOutListener *listener = NULL;
195 vo->enabled = enable;
197 audio_rate_start(&vo->rate);
200 g_hash_table_iter_init(&iter, da->out_listeners);
201 while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
202 qemu_dbus_display1_audio_out_listener_call_set_enabled(
203 listener, (uintptr_t)hw, enable,
204 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
209 dbus_volume_out_listener(HWVoiceOut *hw,
210 QemuDBusDisplay1AudioOutListener *listener)
212 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
213 Volume *vol = &vo->volume;
214 g_autoptr(GBytes) bytes = NULL;
215 GVariant *v_vol = NULL;
217 if (!vo->has_volume) {
221 assert(vol->channels < sizeof(vol->vol));
222 bytes = g_bytes_new(vol->vol, vol->channels);
223 v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
224 qemu_dbus_display1_audio_out_listener_call_set_volume(
225 listener, (uintptr_t)hw, vol->mute, v_vol,
226 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
230 dbus_volume_out(HWVoiceOut *hw, Volume *vol)
232 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
233 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
235 QemuDBusDisplay1AudioOutListener *listener = NULL;
237 vo->has_volume = true;
240 g_hash_table_iter_init(&iter, da->out_listeners);
241 while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
242 dbus_volume_out_listener(hw, listener);
247 dbus_init_in_listener(QemuDBusDisplay1AudioInListener *listener, HWVoiceIn *hw)
249 qemu_dbus_display1_audio_in_listener_call_init(
257 hw->info.bytes_per_frame,
258 hw->info.bytes_per_second,
259 hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,
260 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
264 dbus_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
266 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
267 DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
269 QemuDBusDisplay1AudioInListener *listener = NULL;
271 audio_pcm_init_info(&hw->info, as);
272 hw->samples = DBUS_AUDIO_NSAMPLES;
273 audio_rate_start(&vo->rate);
275 g_hash_table_iter_init(&iter, da->in_listeners);
276 while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
277 dbus_init_in_listener(listener, hw);
283 dbus_fini_in(HWVoiceIn *hw)
285 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
287 QemuDBusDisplay1AudioInListener *listener = NULL;
289 g_hash_table_iter_init(&iter, da->in_listeners);
290 while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
291 qemu_dbus_display1_audio_in_listener_call_fini(
294 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
299 dbus_volume_in_listener(HWVoiceIn *hw,
300 QemuDBusDisplay1AudioInListener *listener)
302 DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
303 Volume *vol = &vo->volume;
304 g_autoptr(GBytes) bytes = NULL;
305 GVariant *v_vol = NULL;
307 if (!vo->has_volume) {
311 assert(vol->channels < sizeof(vol->vol));
312 bytes = g_bytes_new(vol->vol, vol->channels);
313 v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
314 qemu_dbus_display1_audio_in_listener_call_set_volume(
315 listener, (uintptr_t)hw, vol->mute, v_vol,
316 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
320 dbus_volume_in(HWVoiceIn *hw, Volume *vol)
322 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
323 DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
325 QemuDBusDisplay1AudioInListener *listener = NULL;
327 vo->has_volume = true;
330 g_hash_table_iter_init(&iter, da->in_listeners);
331 while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
332 dbus_volume_in_listener(hw, listener);
337 dbus_read(HWVoiceIn *hw, void *buf, size_t size)
339 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
340 /* DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); */
342 QemuDBusDisplay1AudioInListener *listener = NULL;
344 trace_dbus_audio_read(size);
346 /* size = audio_rate_get_bytes(&vo->rate, &hw->info, size); */
348 g_hash_table_iter_init(&iter, da->in_listeners);
349 while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
350 g_autoptr(GVariant) v_data = NULL;
354 if (qemu_dbus_display1_audio_in_listener_call_read_sync(
358 G_DBUS_CALL_FLAGS_NONE, -1,
359 &v_data, NULL, NULL)) {
360 data = g_variant_get_fixed_array(v_data, &n, 1);
361 g_warn_if_fail(n <= size);
363 memcpy(buf, data, size);
372 dbus_enable_in(HWVoiceIn *hw, bool enable)
374 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
375 DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
377 QemuDBusDisplay1AudioInListener *listener = NULL;
379 vo->enabled = enable;
381 audio_rate_start(&vo->rate);
384 g_hash_table_iter_init(&iter, da->in_listeners);
385 while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
386 qemu_dbus_display1_audio_in_listener_call_set_enabled(
387 listener, (uintptr_t)hw, enable,
388 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
393 dbus_audio_init(Audiodev *dev)
395 DBusAudio *da = g_new0(DBusAudio, 1);
397 da->out_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
398 g_free, g_object_unref);
399 da->in_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
400 g_free, g_object_unref);
405 dbus_audio_fini(void *opaque)
407 DBusAudio *da = opaque;
410 g_dbus_object_manager_server_unexport(da->server,
411 DBUS_DISPLAY1_AUDIO_PATH);
413 g_clear_object(&da->audio);
414 g_clear_object(&da->iface);
415 g_clear_pointer(&da->in_listeners, g_hash_table_unref);
416 g_clear_pointer(&da->out_listeners, g_hash_table_unref);
417 g_clear_object(&da->server);
422 listener_out_vanished_cb(GDBusConnection *connection,
423 gboolean remote_peer_vanished,
427 char *name = g_object_get_data(G_OBJECT(connection), "name");
429 g_hash_table_remove(da->out_listeners, name);
433 listener_in_vanished_cb(GDBusConnection *connection,
434 gboolean remote_peer_vanished,
438 char *name = g_object_get_data(G_OBJECT(connection), "name");
440 g_hash_table_remove(da->in_listeners, name);
444 dbus_audio_register_listener(AudioState *s,
445 GDBusMethodInvocation *invocation,
446 GUnixFDList *fd_list,
447 GVariant *arg_listener,
450 DBusAudio *da = s->drv_opaque;
451 const char *sender = g_dbus_method_invocation_get_sender(invocation);
452 g_autoptr(GDBusConnection) listener_conn = NULL;
453 g_autoptr(GError) err = NULL;
454 g_autoptr(GSocket) socket = NULL;
455 g_autoptr(GSocketConnection) socket_conn = NULL;
456 g_autofree char *guid = g_dbus_generate_guid();
457 GHashTable *listeners = out ? da->out_listeners : da->in_listeners;
461 trace_dbus_audio_register(sender, out ? "out" : "in");
463 if (g_hash_table_contains(listeners, sender)) {
464 g_dbus_method_invocation_return_error(invocation,
466 DBUS_DISPLAY_ERROR_INVALID,
467 "`%s` is already registered!",
469 return DBUS_METHOD_INVOCATION_HANDLED;
472 fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);
474 g_dbus_method_invocation_return_error(invocation,
476 DBUS_DISPLAY_ERROR_FAILED,
477 "Couldn't get peer fd: %s",
479 return DBUS_METHOD_INVOCATION_HANDLED;
482 socket = g_socket_new_from_fd(fd, &err);
484 g_dbus_method_invocation_return_error(invocation,
486 DBUS_DISPLAY_ERROR_FAILED,
487 "Couldn't make a socket: %s",
489 return DBUS_METHOD_INVOCATION_HANDLED;
491 socket_conn = g_socket_connection_factory_create_connection(socket);
493 qemu_dbus_display1_audio_complete_register_out_listener(
494 da->iface, invocation, NULL);
496 qemu_dbus_display1_audio_complete_register_in_listener(
497 da->iface, invocation, NULL);
501 g_dbus_connection_new_sync(
502 G_IO_STREAM(socket_conn),
504 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
507 error_report("Failed to setup peer connection: %s", err->message);
508 return DBUS_METHOD_INVOCATION_HANDLED;
512 G_OBJECT(qemu_dbus_display1_audio_out_listener_proxy_new_sync(
514 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
516 "/org/qemu/Display1/AudioOutListener",
519 G_OBJECT(qemu_dbus_display1_audio_in_listener_proxy_new_sync(
521 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
523 "/org/qemu/Display1/AudioInListener",
527 error_report("Failed to setup proxy: %s", err->message);
528 return DBUS_METHOD_INVOCATION_HANDLED;
534 QLIST_FOREACH(hw, &s->hw_head_out, entries) {
535 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
536 QemuDBusDisplay1AudioOutListener *l =
537 QEMU_DBUS_DISPLAY1_AUDIO_OUT_LISTENER(listener);
539 dbus_init_out_listener(l, hw);
540 qemu_dbus_display1_audio_out_listener_call_set_enabled(
541 l, (uintptr_t)hw, vo->enabled,
542 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
547 QLIST_FOREACH(hw, &s->hw_head_in, entries) {
548 DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
549 QemuDBusDisplay1AudioInListener *l =
550 QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener);
552 dbus_init_in_listener(
553 QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener), hw);
554 qemu_dbus_display1_audio_in_listener_call_set_enabled(
555 l, (uintptr_t)hw, vo->enabled,
556 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
560 g_object_set_data_full(G_OBJECT(listener_conn), "name",
561 g_strdup(sender), g_free);
562 g_hash_table_insert(listeners, g_strdup(sender), listener);
563 g_object_connect(listener_conn,
565 out ? listener_out_vanished_cb : listener_in_vanished_cb,
569 return DBUS_METHOD_INVOCATION_HANDLED;
573 dbus_audio_register_out_listener(AudioState *s,
574 GDBusMethodInvocation *invocation,
575 GUnixFDList *fd_list,
576 GVariant *arg_listener)
578 return dbus_audio_register_listener(s, invocation,
579 fd_list, arg_listener, true);
584 dbus_audio_register_in_listener(AudioState *s,
585 GDBusMethodInvocation *invocation,
586 GUnixFDList *fd_list,
587 GVariant *arg_listener)
589 return dbus_audio_register_listener(s, invocation,
590 fd_list, arg_listener, false);
594 dbus_audio_set_server(AudioState *s, GDBusObjectManagerServer *server)
596 DBusAudio *da = s->drv_opaque;
599 g_assert(!da->server);
601 da->server = g_object_ref(server);
603 da->audio = g_dbus_object_skeleton_new(DBUS_DISPLAY1_AUDIO_PATH);
604 da->iface = qemu_dbus_display1_audio_skeleton_new();
605 g_object_connect(da->iface,
606 "swapped-signal::handle-register-in-listener",
607 dbus_audio_register_in_listener, s,
608 "swapped-signal::handle-register-out-listener",
609 dbus_audio_register_out_listener, s,
612 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(da->audio),
613 G_DBUS_INTERFACE_SKELETON(da->iface));
614 g_dbus_object_manager_server_export(da->server, da->audio);
617 static struct audio_pcm_ops dbus_pcm_ops = {
618 .init_out = dbus_init_out,
619 .fini_out = dbus_fini_out,
620 .write = audio_generic_write,
621 .get_buffer_out = dbus_get_buffer_out,
622 .put_buffer_out = dbus_put_buffer_out,
623 .enable_out = dbus_enable_out,
624 .volume_out = dbus_volume_out,
626 .init_in = dbus_init_in,
627 .fini_in = dbus_fini_in,
629 .run_buffer_in = audio_generic_run_buffer_in,
630 .enable_in = dbus_enable_in,
631 .volume_in = dbus_volume_in,
634 static struct audio_driver dbus_audio_driver = {
636 .descr = "Timer based audio exposed with DBus interface",
637 .init = dbus_audio_init,
638 .fini = dbus_audio_fini,
639 .set_dbus_server = dbus_audio_set_server,
640 .pcm_ops = &dbus_pcm_ops,
642 .max_voices_out = INT_MAX,
643 .max_voices_in = INT_MAX,
644 .voice_size_out = sizeof(DBusVoiceOut),
645 .voice_size_in = sizeof(DBusVoiceIn)
648 static void register_audio_dbus(void)
650 audio_driver_register(&dbus_audio_driver);
652 type_init(register_audio_dbus);
654 module_dep("ui-dbus")