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
;
47 GDBusObjectSkeleton
*audio
;
48 QemuDBusDisplay1Audio
*iface
;
49 GHashTable
*out_listeners
;
50 GHashTable
*in_listeners
;
53 typedef struct DBusVoiceOut
{
66 typedef struct DBusVoiceIn
{
75 static void *dbus_get_buffer_out(HWVoiceOut
*hw
, size_t *size
)
77 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
80 vo
->buf_size
= hw
->samples
* hw
->info
.bytes_per_frame
;
81 vo
->buf
= g_malloc(vo
->buf_size
);
85 *size
= MIN(vo
->buf_size
- vo
->buf_pos
, *size
);
86 *size
= audio_rate_get_bytes(&vo
->rate
, &hw
->info
, *size
);
88 return vo
->buf
+ vo
->buf_pos
;
92 static size_t dbus_put_buffer_out(HWVoiceOut
*hw
, void *buf
, size_t size
)
94 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
95 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
97 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
98 g_autoptr(GBytes
) bytes
= NULL
;
99 g_autoptr(GVariant
) v_data
= NULL
;
101 assert(buf
== vo
->buf
+ vo
->buf_pos
&& vo
->buf_pos
+ size
<= vo
->buf_size
);
104 trace_dbus_audio_put_buffer_out(size
);
106 if (vo
->buf_pos
< vo
->buf_size
) {
110 bytes
= g_bytes_new_take(g_steal_pointer(&vo
->buf
), vo
->buf_size
);
111 v_data
= g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes
, TRUE
);
112 g_variant_ref_sink(v_data
);
114 g_hash_table_iter_init(&iter
, da
->out_listeners
);
115 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
116 qemu_dbus_display1_audio_out_listener_call_write(
120 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
127 #define AUDIO_HOST_BE TRUE
129 #define AUDIO_HOST_BE FALSE
133 dbus_init_out_listener(QemuDBusDisplay1AudioOutListener
*listener
,
136 qemu_dbus_display1_audio_out_listener_call_init(
144 hw
->info
.bytes_per_frame
,
145 hw
->info
.bytes_per_second
,
146 hw
->info
.swap_endianness
? !AUDIO_HOST_BE
: AUDIO_HOST_BE
,
147 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
151 dbus_init_out(HWVoiceOut
*hw
, struct audsettings
*as
, void *drv_opaque
)
153 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
154 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
156 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
158 audio_pcm_init_info(&hw
->info
, as
);
159 hw
->samples
= DBUS_AUDIO_NSAMPLES
;
160 audio_rate_start(&vo
->rate
);
162 g_hash_table_iter_init(&iter
, da
->out_listeners
);
163 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
164 dbus_init_out_listener(listener
, hw
);
170 dbus_fini_out(HWVoiceOut
*hw
)
172 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
173 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
175 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
177 g_hash_table_iter_init(&iter
, da
->out_listeners
);
178 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
179 qemu_dbus_display1_audio_out_listener_call_fini(
182 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
185 g_clear_pointer(&vo
->buf
, g_free
);
189 dbus_enable_out(HWVoiceOut
*hw
, bool enable
)
191 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
192 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
194 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
196 vo
->enabled
= enable
;
198 audio_rate_start(&vo
->rate
);
201 g_hash_table_iter_init(&iter
, da
->out_listeners
);
202 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
203 qemu_dbus_display1_audio_out_listener_call_set_enabled(
204 listener
, (uintptr_t)hw
, enable
,
205 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
210 dbus_volume_out_listener(HWVoiceOut
*hw
,
211 QemuDBusDisplay1AudioOutListener
*listener
)
213 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
214 Volume
*vol
= &vo
->volume
;
215 g_autoptr(GBytes
) bytes
= NULL
;
216 GVariant
*v_vol
= NULL
;
218 if (!vo
->has_volume
) {
222 assert(vol
->channels
< sizeof(vol
->vol
));
223 bytes
= g_bytes_new(vol
->vol
, vol
->channels
);
224 v_vol
= g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes
, TRUE
);
225 qemu_dbus_display1_audio_out_listener_call_set_volume(
226 listener
, (uintptr_t)hw
, vol
->mute
, v_vol
,
227 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
231 dbus_volume_out(HWVoiceOut
*hw
, Volume
*vol
)
233 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
234 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
236 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
238 vo
->has_volume
= true;
241 g_hash_table_iter_init(&iter
, da
->out_listeners
);
242 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
243 dbus_volume_out_listener(hw
, listener
);
248 dbus_init_in_listener(QemuDBusDisplay1AudioInListener
*listener
, HWVoiceIn
*hw
)
250 qemu_dbus_display1_audio_in_listener_call_init(
258 hw
->info
.bytes_per_frame
,
259 hw
->info
.bytes_per_second
,
260 hw
->info
.swap_endianness
? !AUDIO_HOST_BE
: AUDIO_HOST_BE
,
261 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
265 dbus_init_in(HWVoiceIn
*hw
, struct audsettings
*as
, void *drv_opaque
)
267 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
268 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
270 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
272 audio_pcm_init_info(&hw
->info
, as
);
273 hw
->samples
= DBUS_AUDIO_NSAMPLES
;
274 audio_rate_start(&vo
->rate
);
276 g_hash_table_iter_init(&iter
, da
->in_listeners
);
277 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
278 dbus_init_in_listener(listener
, hw
);
284 dbus_fini_in(HWVoiceIn
*hw
)
286 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
288 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
290 g_hash_table_iter_init(&iter
, da
->in_listeners
);
291 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
292 qemu_dbus_display1_audio_in_listener_call_fini(
295 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
300 dbus_volume_in_listener(HWVoiceIn
*hw
,
301 QemuDBusDisplay1AudioInListener
*listener
)
303 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
304 Volume
*vol
= &vo
->volume
;
305 g_autoptr(GBytes
) bytes
= NULL
;
306 GVariant
*v_vol
= NULL
;
308 if (!vo
->has_volume
) {
312 assert(vol
->channels
< sizeof(vol
->vol
));
313 bytes
= g_bytes_new(vol
->vol
, vol
->channels
);
314 v_vol
= g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes
, TRUE
);
315 qemu_dbus_display1_audio_in_listener_call_set_volume(
316 listener
, (uintptr_t)hw
, vol
->mute
, v_vol
,
317 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
321 dbus_volume_in(HWVoiceIn
*hw
, Volume
*vol
)
323 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
324 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
326 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
328 vo
->has_volume
= true;
331 g_hash_table_iter_init(&iter
, da
->in_listeners
);
332 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
333 dbus_volume_in_listener(hw
, listener
);
338 dbus_read(HWVoiceIn
*hw
, void *buf
, size_t size
)
340 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
341 /* DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); */
343 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
345 trace_dbus_audio_read(size
);
347 /* size = audio_rate_get_bytes(&vo->rate, &hw->info, size); */
349 g_hash_table_iter_init(&iter
, da
->in_listeners
);
350 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
351 g_autoptr(GVariant
) v_data
= NULL
;
355 if (qemu_dbus_display1_audio_in_listener_call_read_sync(
359 G_DBUS_CALL_FLAGS_NONE
, -1,
360 &v_data
, NULL
, NULL
)) {
361 data
= g_variant_get_fixed_array(v_data
, &n
, 1);
362 g_warn_if_fail(n
<= size
);
364 memcpy(buf
, data
, size
);
373 dbus_enable_in(HWVoiceIn
*hw
, bool enable
)
375 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
376 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
378 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
380 vo
->enabled
= enable
;
382 audio_rate_start(&vo
->rate
);
385 g_hash_table_iter_init(&iter
, da
->in_listeners
);
386 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
387 qemu_dbus_display1_audio_in_listener_call_set_enabled(
388 listener
, (uintptr_t)hw
, enable
,
389 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
394 dbus_audio_init(Audiodev
*dev
)
396 DBusAudio
*da
= g_new0(DBusAudio
, 1);
398 da
->out_listeners
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
399 g_free
, g_object_unref
);
400 da
->in_listeners
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
401 g_free
, g_object_unref
);
406 dbus_audio_fini(void *opaque
)
408 DBusAudio
*da
= opaque
;
411 g_dbus_object_manager_server_unexport(da
->server
,
412 DBUS_DISPLAY1_AUDIO_PATH
);
414 g_clear_object(&da
->audio
);
415 g_clear_object(&da
->iface
);
416 g_clear_pointer(&da
->in_listeners
, g_hash_table_unref
);
417 g_clear_pointer(&da
->out_listeners
, g_hash_table_unref
);
418 g_clear_object(&da
->server
);
423 listener_out_vanished_cb(GDBusConnection
*connection
,
424 gboolean remote_peer_vanished
,
428 char *name
= g_object_get_data(G_OBJECT(connection
), "name");
430 g_hash_table_remove(da
->out_listeners
, name
);
434 listener_in_vanished_cb(GDBusConnection
*connection
,
435 gboolean remote_peer_vanished
,
439 char *name
= g_object_get_data(G_OBJECT(connection
), "name");
441 g_hash_table_remove(da
->in_listeners
, name
);
445 dbus_audio_register_listener(AudioState
*s
,
446 GDBusMethodInvocation
*invocation
,
447 GUnixFDList
*fd_list
,
448 GVariant
*arg_listener
,
451 DBusAudio
*da
= s
->drv_opaque
;
453 da
->p2p
? "p2p" : g_dbus_method_invocation_get_sender(invocation
);
454 g_autoptr(GDBusConnection
) listener_conn
= NULL
;
455 g_autoptr(GError
) err
= NULL
;
456 g_autoptr(GSocket
) socket
= NULL
;
457 g_autoptr(GSocketConnection
) socket_conn
= NULL
;
458 g_autofree
char *guid
= g_dbus_generate_guid();
459 GHashTable
*listeners
= out
? da
->out_listeners
: da
->in_listeners
;
463 trace_dbus_audio_register(sender
, out
? "out" : "in");
465 if (g_hash_table_contains(listeners
, sender
)) {
466 g_dbus_method_invocation_return_error(invocation
,
468 DBUS_DISPLAY_ERROR_INVALID
,
469 "`%s` is already registered!",
471 return DBUS_METHOD_INVOCATION_HANDLED
;
474 fd
= g_unix_fd_list_get(fd_list
, g_variant_get_handle(arg_listener
), &err
);
476 g_dbus_method_invocation_return_error(invocation
,
478 DBUS_DISPLAY_ERROR_FAILED
,
479 "Couldn't get peer fd: %s",
481 return DBUS_METHOD_INVOCATION_HANDLED
;
484 socket
= g_socket_new_from_fd(fd
, &err
);
486 g_dbus_method_invocation_return_error(invocation
,
488 DBUS_DISPLAY_ERROR_FAILED
,
489 "Couldn't make a socket: %s",
491 return DBUS_METHOD_INVOCATION_HANDLED
;
493 socket_conn
= g_socket_connection_factory_create_connection(socket
);
495 qemu_dbus_display1_audio_complete_register_out_listener(
496 da
->iface
, invocation
, NULL
);
498 qemu_dbus_display1_audio_complete_register_in_listener(
499 da
->iface
, invocation
, NULL
);
503 g_dbus_connection_new_sync(
504 G_IO_STREAM(socket_conn
),
506 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER
,
509 error_report("Failed to setup peer connection: %s", err
->message
);
510 return DBUS_METHOD_INVOCATION_HANDLED
;
514 G_OBJECT(qemu_dbus_display1_audio_out_listener_proxy_new_sync(
516 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START
,
518 "/org/qemu/Display1/AudioOutListener",
521 G_OBJECT(qemu_dbus_display1_audio_in_listener_proxy_new_sync(
523 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START
,
525 "/org/qemu/Display1/AudioInListener",
529 error_report("Failed to setup proxy: %s", err
->message
);
530 return DBUS_METHOD_INVOCATION_HANDLED
;
536 QLIST_FOREACH(hw
, &s
->hw_head_out
, entries
) {
537 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
538 QemuDBusDisplay1AudioOutListener
*l
=
539 QEMU_DBUS_DISPLAY1_AUDIO_OUT_LISTENER(listener
);
541 dbus_init_out_listener(l
, hw
);
542 qemu_dbus_display1_audio_out_listener_call_set_enabled(
543 l
, (uintptr_t)hw
, vo
->enabled
,
544 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
549 QLIST_FOREACH(hw
, &s
->hw_head_in
, entries
) {
550 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
551 QemuDBusDisplay1AudioInListener
*l
=
552 QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener
);
554 dbus_init_in_listener(
555 QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener
), hw
);
556 qemu_dbus_display1_audio_in_listener_call_set_enabled(
557 l
, (uintptr_t)hw
, vo
->enabled
,
558 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
562 g_object_set_data_full(G_OBJECT(listener_conn
), "name",
563 g_strdup(sender
), g_free
);
564 g_hash_table_insert(listeners
, g_strdup(sender
), listener
);
565 g_object_connect(listener_conn
,
567 out
? listener_out_vanished_cb
: listener_in_vanished_cb
,
571 return DBUS_METHOD_INVOCATION_HANDLED
;
575 dbus_audio_register_out_listener(AudioState
*s
,
576 GDBusMethodInvocation
*invocation
,
577 GUnixFDList
*fd_list
,
578 GVariant
*arg_listener
)
580 return dbus_audio_register_listener(s
, invocation
,
581 fd_list
, arg_listener
, true);
586 dbus_audio_register_in_listener(AudioState
*s
,
587 GDBusMethodInvocation
*invocation
,
588 GUnixFDList
*fd_list
,
589 GVariant
*arg_listener
)
591 return dbus_audio_register_listener(s
, invocation
,
592 fd_list
, arg_listener
, false);
596 dbus_audio_set_server(AudioState
*s
, GDBusObjectManagerServer
*server
, bool p2p
)
598 DBusAudio
*da
= s
->drv_opaque
;
601 g_assert(!da
->server
);
603 da
->server
= g_object_ref(server
);
606 da
->audio
= g_dbus_object_skeleton_new(DBUS_DISPLAY1_AUDIO_PATH
);
607 da
->iface
= qemu_dbus_display1_audio_skeleton_new();
608 g_object_connect(da
->iface
,
609 "swapped-signal::handle-register-in-listener",
610 dbus_audio_register_in_listener
, s
,
611 "swapped-signal::handle-register-out-listener",
612 dbus_audio_register_out_listener
, s
,
615 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(da
->audio
),
616 G_DBUS_INTERFACE_SKELETON(da
->iface
));
617 g_dbus_object_manager_server_export(da
->server
, da
->audio
);
620 static struct audio_pcm_ops dbus_pcm_ops
= {
621 .init_out
= dbus_init_out
,
622 .fini_out
= dbus_fini_out
,
623 .write
= audio_generic_write
,
624 .get_buffer_out
= dbus_get_buffer_out
,
625 .put_buffer_out
= dbus_put_buffer_out
,
626 .enable_out
= dbus_enable_out
,
627 .volume_out
= dbus_volume_out
,
629 .init_in
= dbus_init_in
,
630 .fini_in
= dbus_fini_in
,
632 .run_buffer_in
= audio_generic_run_buffer_in
,
633 .enable_in
= dbus_enable_in
,
634 .volume_in
= dbus_volume_in
,
637 static struct audio_driver dbus_audio_driver
= {
639 .descr
= "Timer based audio exposed with DBus interface",
640 .init
= dbus_audio_init
,
641 .fini
= dbus_audio_fini
,
642 .set_dbus_server
= dbus_audio_set_server
,
643 .pcm_ops
= &dbus_pcm_ops
,
645 .max_voices_out
= INT_MAX
,
646 .max_voices_in
= INT_MAX
,
647 .voice_size_out
= sizeof(DBusVoiceOut
),
648 .voice_size_in
= sizeof(DBusVoiceIn
)
651 static void register_audio_dbus(void)
653 audio_driver_register(&dbus_audio_driver
);
655 type_init(register_audio_dbus
);
657 module_dep("ui-dbus")