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"
33 #include <gio/gunixfdlist.h>
36 #include "ui/dbus-display1.h"
38 #define AUDIO_CAP "dbus"
40 #include "audio_int.h"
43 #define DBUS_DISPLAY1_AUDIO_PATH DBUS_DISPLAY1_ROOT "/Audio"
45 #define DBUS_AUDIO_NSAMPLES 1024 /* could be configured? */
47 typedef struct DBusAudio
{
48 GDBusObjectManagerServer
*server
;
50 GDBusObjectSkeleton
*audio
;
51 QemuDBusDisplay1Audio
*iface
;
52 GHashTable
*out_listeners
;
53 GHashTable
*in_listeners
;
56 typedef struct DBusVoiceOut
{
69 typedef struct DBusVoiceIn
{
78 static void *dbus_get_buffer_out(HWVoiceOut
*hw
, size_t *size
)
80 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
83 vo
->buf_size
= hw
->samples
* hw
->info
.bytes_per_frame
;
84 vo
->buf
= g_malloc(vo
->buf_size
);
88 *size
= MIN(vo
->buf_size
- vo
->buf_pos
, *size
);
89 *size
= audio_rate_get_bytes(&vo
->rate
, &hw
->info
, *size
);
91 return vo
->buf
+ vo
->buf_pos
;
95 static size_t dbus_put_buffer_out(HWVoiceOut
*hw
, void *buf
, size_t size
)
97 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
98 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
100 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
101 g_autoptr(GBytes
) bytes
= NULL
;
102 g_autoptr(GVariant
) v_data
= NULL
;
104 assert(buf
== vo
->buf
+ vo
->buf_pos
&& vo
->buf_pos
+ size
<= vo
->buf_size
);
107 trace_dbus_audio_put_buffer_out(size
);
109 if (vo
->buf_pos
< vo
->buf_size
) {
113 bytes
= g_bytes_new_take(g_steal_pointer(&vo
->buf
), vo
->buf_size
);
114 v_data
= g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes
, TRUE
);
115 g_variant_ref_sink(v_data
);
117 g_hash_table_iter_init(&iter
, da
->out_listeners
);
118 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
119 qemu_dbus_display1_audio_out_listener_call_write(
123 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
130 #define AUDIO_HOST_BE TRUE
132 #define AUDIO_HOST_BE FALSE
136 dbus_init_out_listener(QemuDBusDisplay1AudioOutListener
*listener
,
139 qemu_dbus_display1_audio_out_listener_call_init(
147 hw
->info
.bytes_per_frame
,
148 hw
->info
.bytes_per_second
,
149 hw
->info
.swap_endianness
? !AUDIO_HOST_BE
: AUDIO_HOST_BE
,
150 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
154 dbus_init_out(HWVoiceOut
*hw
, struct audsettings
*as
, void *drv_opaque
)
156 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
157 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
159 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
161 audio_pcm_init_info(&hw
->info
, as
);
162 hw
->samples
= DBUS_AUDIO_NSAMPLES
;
163 audio_rate_start(&vo
->rate
);
165 g_hash_table_iter_init(&iter
, da
->out_listeners
);
166 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
167 dbus_init_out_listener(listener
, hw
);
173 dbus_fini_out(HWVoiceOut
*hw
)
175 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
176 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
178 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
180 g_hash_table_iter_init(&iter
, da
->out_listeners
);
181 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
182 qemu_dbus_display1_audio_out_listener_call_fini(
185 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
188 g_clear_pointer(&vo
->buf
, g_free
);
192 dbus_enable_out(HWVoiceOut
*hw
, bool enable
)
194 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
195 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
197 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
199 vo
->enabled
= enable
;
201 audio_rate_start(&vo
->rate
);
204 g_hash_table_iter_init(&iter
, da
->out_listeners
);
205 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
206 qemu_dbus_display1_audio_out_listener_call_set_enabled(
207 listener
, (uintptr_t)hw
, enable
,
208 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
213 dbus_volume_out_listener(HWVoiceOut
*hw
,
214 QemuDBusDisplay1AudioOutListener
*listener
)
216 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
217 Volume
*vol
= &vo
->volume
;
218 g_autoptr(GBytes
) bytes
= NULL
;
219 GVariant
*v_vol
= NULL
;
221 if (!vo
->has_volume
) {
225 assert(vol
->channels
< sizeof(vol
->vol
));
226 bytes
= g_bytes_new(vol
->vol
, vol
->channels
);
227 v_vol
= g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes
, TRUE
);
228 qemu_dbus_display1_audio_out_listener_call_set_volume(
229 listener
, (uintptr_t)hw
, vol
->mute
, v_vol
,
230 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
234 dbus_volume_out(HWVoiceOut
*hw
, Volume
*vol
)
236 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
237 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
239 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
241 vo
->has_volume
= true;
244 g_hash_table_iter_init(&iter
, da
->out_listeners
);
245 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
246 dbus_volume_out_listener(hw
, listener
);
251 dbus_init_in_listener(QemuDBusDisplay1AudioInListener
*listener
, HWVoiceIn
*hw
)
253 qemu_dbus_display1_audio_in_listener_call_init(
261 hw
->info
.bytes_per_frame
,
262 hw
->info
.bytes_per_second
,
263 hw
->info
.swap_endianness
? !AUDIO_HOST_BE
: AUDIO_HOST_BE
,
264 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
268 dbus_init_in(HWVoiceIn
*hw
, struct audsettings
*as
, void *drv_opaque
)
270 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
271 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
273 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
275 audio_pcm_init_info(&hw
->info
, as
);
276 hw
->samples
= DBUS_AUDIO_NSAMPLES
;
277 audio_rate_start(&vo
->rate
);
279 g_hash_table_iter_init(&iter
, da
->in_listeners
);
280 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
281 dbus_init_in_listener(listener
, hw
);
287 dbus_fini_in(HWVoiceIn
*hw
)
289 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
291 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
293 g_hash_table_iter_init(&iter
, da
->in_listeners
);
294 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
295 qemu_dbus_display1_audio_in_listener_call_fini(
298 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
303 dbus_volume_in_listener(HWVoiceIn
*hw
,
304 QemuDBusDisplay1AudioInListener
*listener
)
306 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
307 Volume
*vol
= &vo
->volume
;
308 g_autoptr(GBytes
) bytes
= NULL
;
309 GVariant
*v_vol
= NULL
;
311 if (!vo
->has_volume
) {
315 assert(vol
->channels
< sizeof(vol
->vol
));
316 bytes
= g_bytes_new(vol
->vol
, vol
->channels
);
317 v_vol
= g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes
, TRUE
);
318 qemu_dbus_display1_audio_in_listener_call_set_volume(
319 listener
, (uintptr_t)hw
, vol
->mute
, v_vol
,
320 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
324 dbus_volume_in(HWVoiceIn
*hw
, Volume
*vol
)
326 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
327 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
329 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
331 vo
->has_volume
= true;
334 g_hash_table_iter_init(&iter
, da
->in_listeners
);
335 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
336 dbus_volume_in_listener(hw
, listener
);
341 dbus_read(HWVoiceIn
*hw
, void *buf
, size_t size
)
343 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
344 /* DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); */
346 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
348 trace_dbus_audio_read(size
);
350 /* size = audio_rate_get_bytes(&vo->rate, &hw->info, size); */
352 g_hash_table_iter_init(&iter
, da
->in_listeners
);
353 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
354 g_autoptr(GVariant
) v_data
= NULL
;
358 if (qemu_dbus_display1_audio_in_listener_call_read_sync(
362 G_DBUS_CALL_FLAGS_NONE
, -1,
363 &v_data
, NULL
, NULL
)) {
364 data
= g_variant_get_fixed_array(v_data
, &n
, 1);
365 g_warn_if_fail(n
<= size
);
367 memcpy(buf
, data
, size
);
376 dbus_enable_in(HWVoiceIn
*hw
, bool enable
)
378 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
379 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
381 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
383 vo
->enabled
= enable
;
385 audio_rate_start(&vo
->rate
);
388 g_hash_table_iter_init(&iter
, da
->in_listeners
);
389 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
390 qemu_dbus_display1_audio_in_listener_call_set_enabled(
391 listener
, (uintptr_t)hw
, enable
,
392 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
397 dbus_audio_init(Audiodev
*dev
)
399 DBusAudio
*da
= g_new0(DBusAudio
, 1);
401 da
->out_listeners
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
402 g_free
, g_object_unref
);
403 da
->in_listeners
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
404 g_free
, g_object_unref
);
409 dbus_audio_fini(void *opaque
)
411 DBusAudio
*da
= opaque
;
414 g_dbus_object_manager_server_unexport(da
->server
,
415 DBUS_DISPLAY1_AUDIO_PATH
);
417 g_clear_object(&da
->audio
);
418 g_clear_object(&da
->iface
);
419 g_clear_pointer(&da
->in_listeners
, g_hash_table_unref
);
420 g_clear_pointer(&da
->out_listeners
, g_hash_table_unref
);
421 g_clear_object(&da
->server
);
427 listener_out_vanished_cb(GDBusConnection
*connection
,
428 gboolean remote_peer_vanished
,
432 char *name
= g_object_get_data(G_OBJECT(connection
), "name");
434 g_hash_table_remove(da
->out_listeners
, name
);
438 listener_in_vanished_cb(GDBusConnection
*connection
,
439 gboolean remote_peer_vanished
,
443 char *name
= g_object_get_data(G_OBJECT(connection
), "name");
445 g_hash_table_remove(da
->in_listeners
, name
);
449 dbus_audio_register_listener(AudioState
*s
,
450 GDBusMethodInvocation
*invocation
,
451 GUnixFDList
*fd_list
,
452 GVariant
*arg_listener
,
455 DBusAudio
*da
= s
->drv_opaque
;
457 da
->p2p
? "p2p" : g_dbus_method_invocation_get_sender(invocation
);
458 g_autoptr(GDBusConnection
) listener_conn
= NULL
;
459 g_autoptr(GError
) err
= NULL
;
460 g_autoptr(GSocket
) socket
= NULL
;
461 g_autoptr(GSocketConnection
) socket_conn
= NULL
;
462 g_autofree
char *guid
= g_dbus_generate_guid();
463 GHashTable
*listeners
= out
? da
->out_listeners
: da
->in_listeners
;
467 trace_dbus_audio_register(sender
, out
? "out" : "in");
469 if (g_hash_table_contains(listeners
, sender
)) {
470 g_dbus_method_invocation_return_error(invocation
,
472 DBUS_DISPLAY_ERROR_INVALID
,
473 "`%s` is already registered!",
475 return DBUS_METHOD_INVOCATION_HANDLED
;
478 fd
= g_unix_fd_list_get(fd_list
, g_variant_get_handle(arg_listener
), &err
);
480 g_dbus_method_invocation_return_error(invocation
,
482 DBUS_DISPLAY_ERROR_FAILED
,
483 "Couldn't get peer fd: %s",
485 return DBUS_METHOD_INVOCATION_HANDLED
;
488 socket
= g_socket_new_from_fd(fd
, &err
);
490 g_dbus_method_invocation_return_error(invocation
,
492 DBUS_DISPLAY_ERROR_FAILED
,
493 "Couldn't make a socket: %s",
495 return DBUS_METHOD_INVOCATION_HANDLED
;
497 socket_conn
= g_socket_connection_factory_create_connection(socket
);
499 qemu_dbus_display1_audio_complete_register_out_listener(
500 da
->iface
, invocation
, NULL
);
502 qemu_dbus_display1_audio_complete_register_in_listener(
503 da
->iface
, invocation
, NULL
);
507 g_dbus_connection_new_sync(
508 G_IO_STREAM(socket_conn
),
510 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER
,
513 error_report("Failed to setup peer connection: %s", err
->message
);
514 return DBUS_METHOD_INVOCATION_HANDLED
;
518 G_OBJECT(qemu_dbus_display1_audio_out_listener_proxy_new_sync(
520 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START
,
522 "/org/qemu/Display1/AudioOutListener",
525 G_OBJECT(qemu_dbus_display1_audio_in_listener_proxy_new_sync(
527 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START
,
529 "/org/qemu/Display1/AudioInListener",
533 error_report("Failed to setup proxy: %s", err
->message
);
534 return DBUS_METHOD_INVOCATION_HANDLED
;
540 QLIST_FOREACH(hw
, &s
->hw_head_out
, entries
) {
541 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
542 QemuDBusDisplay1AudioOutListener
*l
=
543 QEMU_DBUS_DISPLAY1_AUDIO_OUT_LISTENER(listener
);
545 dbus_init_out_listener(l
, hw
);
546 qemu_dbus_display1_audio_out_listener_call_set_enabled(
547 l
, (uintptr_t)hw
, vo
->enabled
,
548 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
553 QLIST_FOREACH(hw
, &s
->hw_head_in
, entries
) {
554 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
555 QemuDBusDisplay1AudioInListener
*l
=
556 QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener
);
558 dbus_init_in_listener(
559 QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener
), hw
);
560 qemu_dbus_display1_audio_in_listener_call_set_enabled(
561 l
, (uintptr_t)hw
, vo
->enabled
,
562 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
566 g_object_set_data_full(G_OBJECT(listener_conn
), "name",
567 g_strdup(sender
), g_free
);
568 g_hash_table_insert(listeners
, g_strdup(sender
), listener
);
569 g_object_connect(listener_conn
,
571 out
? listener_out_vanished_cb
: listener_in_vanished_cb
,
575 return DBUS_METHOD_INVOCATION_HANDLED
;
579 dbus_audio_register_out_listener(AudioState
*s
,
580 GDBusMethodInvocation
*invocation
,
581 GUnixFDList
*fd_list
,
582 GVariant
*arg_listener
)
584 return dbus_audio_register_listener(s
, invocation
,
585 fd_list
, arg_listener
, true);
590 dbus_audio_register_in_listener(AudioState
*s
,
591 GDBusMethodInvocation
*invocation
,
592 GUnixFDList
*fd_list
,
593 GVariant
*arg_listener
)
595 return dbus_audio_register_listener(s
, invocation
,
596 fd_list
, arg_listener
, false);
601 dbus_audio_set_server(AudioState
*s
, GDBusObjectManagerServer
*server
, bool p2p
)
603 DBusAudio
*da
= s
->drv_opaque
;
606 g_assert(!da
->server
);
608 da
->server
= g_object_ref(server
);
611 da
->audio
= g_dbus_object_skeleton_new(DBUS_DISPLAY1_AUDIO_PATH
);
612 da
->iface
= qemu_dbus_display1_audio_skeleton_new();
614 g_object_connect(da
->iface
,
615 "swapped-signal::handle-register-in-listener",
616 dbus_audio_register_in_listener
, s
,
617 "swapped-signal::handle-register-out-listener",
618 dbus_audio_register_out_listener
, s
,
622 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(da
->audio
),
623 G_DBUS_INTERFACE_SKELETON(da
->iface
));
624 g_dbus_object_manager_server_export(da
->server
, da
->audio
);
627 static struct audio_pcm_ops dbus_pcm_ops
= {
628 .init_out
= dbus_init_out
,
629 .fini_out
= dbus_fini_out
,
630 .write
= audio_generic_write
,
631 .get_buffer_out
= dbus_get_buffer_out
,
632 .put_buffer_out
= dbus_put_buffer_out
,
633 .enable_out
= dbus_enable_out
,
634 .volume_out
= dbus_volume_out
,
636 .init_in
= dbus_init_in
,
637 .fini_in
= dbus_fini_in
,
639 .run_buffer_in
= audio_generic_run_buffer_in
,
640 .enable_in
= dbus_enable_in
,
641 .volume_in
= dbus_volume_in
,
644 static struct audio_driver dbus_audio_driver
= {
646 .descr
= "Timer based audio exposed with DBus interface",
647 .init
= dbus_audio_init
,
648 .fini
= dbus_audio_fini
,
649 .set_dbus_server
= dbus_audio_set_server
,
650 .pcm_ops
= &dbus_pcm_ops
,
652 .max_voices_out
= INT_MAX
,
653 .max_voices_in
= INT_MAX
,
654 .voice_size_out
= sizeof(DBusVoiceOut
),
655 .voice_size_in
= sizeof(DBusVoiceIn
)
658 static void register_audio_dbus(void)
660 audio_driver_register(&dbus_audio_driver
);
662 type_init(register_audio_dbus
);
664 module_dep("ui-dbus")