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")