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>
37 #include "ui/dbus-display1.h"
39 #define AUDIO_CAP "dbus"
41 #include "audio_int.h"
44 #define DBUS_DISPLAY1_AUDIO_PATH DBUS_DISPLAY1_ROOT "/Audio"
46 #define DBUS_AUDIO_NSAMPLES 1024 /* could be configured? */
48 typedef struct DBusAudio
{
49 GDBusObjectManagerServer
*server
;
51 GDBusObjectSkeleton
*audio
;
52 QemuDBusDisplay1Audio
*iface
;
53 GHashTable
*out_listeners
;
54 GHashTable
*in_listeners
;
57 typedef struct DBusVoiceOut
{
70 typedef struct DBusVoiceIn
{
79 static void *dbus_get_buffer_out(HWVoiceOut
*hw
, size_t *size
)
81 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
84 vo
->buf_size
= hw
->samples
* hw
->info
.bytes_per_frame
;
85 vo
->buf
= g_malloc(vo
->buf_size
);
89 *size
= MIN(vo
->buf_size
- vo
->buf_pos
, *size
);
90 *size
= audio_rate_get_bytes(&vo
->rate
, &hw
->info
, *size
);
92 return vo
->buf
+ vo
->buf_pos
;
96 static size_t dbus_put_buffer_out(HWVoiceOut
*hw
, void *buf
, size_t size
)
98 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
99 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
101 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
102 g_autoptr(GBytes
) bytes
= NULL
;
103 g_autoptr(GVariant
) v_data
= NULL
;
105 assert(buf
== vo
->buf
+ vo
->buf_pos
&& vo
->buf_pos
+ size
<= vo
->buf_size
);
108 trace_dbus_audio_put_buffer_out(size
);
110 if (vo
->buf_pos
< vo
->buf_size
) {
114 bytes
= g_bytes_new_take(g_steal_pointer(&vo
->buf
), vo
->buf_size
);
115 v_data
= g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes
, TRUE
);
116 g_variant_ref_sink(v_data
);
118 g_hash_table_iter_init(&iter
, da
->out_listeners
);
119 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
120 qemu_dbus_display1_audio_out_listener_call_write(
124 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
131 #define AUDIO_HOST_BE TRUE
133 #define AUDIO_HOST_BE FALSE
137 dbus_init_out_listener(QemuDBusDisplay1AudioOutListener
*listener
,
140 qemu_dbus_display1_audio_out_listener_call_init(
148 hw
->info
.bytes_per_frame
,
149 hw
->info
.bytes_per_second
,
150 hw
->info
.swap_endianness
? !AUDIO_HOST_BE
: AUDIO_HOST_BE
,
151 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
155 dbus_init_out(HWVoiceOut
*hw
, struct audsettings
*as
, void *drv_opaque
)
157 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
158 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
160 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
162 audio_pcm_init_info(&hw
->info
, as
);
163 hw
->samples
= DBUS_AUDIO_NSAMPLES
;
164 audio_rate_start(&vo
->rate
);
166 g_hash_table_iter_init(&iter
, da
->out_listeners
);
167 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
168 dbus_init_out_listener(listener
, hw
);
174 dbus_fini_out(HWVoiceOut
*hw
)
176 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
177 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
179 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
181 g_hash_table_iter_init(&iter
, da
->out_listeners
);
182 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
183 qemu_dbus_display1_audio_out_listener_call_fini(
186 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
189 g_clear_pointer(&vo
->buf
, g_free
);
193 dbus_enable_out(HWVoiceOut
*hw
, bool enable
)
195 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
196 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
198 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
200 vo
->enabled
= enable
;
202 audio_rate_start(&vo
->rate
);
205 g_hash_table_iter_init(&iter
, da
->out_listeners
);
206 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
207 qemu_dbus_display1_audio_out_listener_call_set_enabled(
208 listener
, (uintptr_t)hw
, enable
,
209 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
214 dbus_volume_out_listener(HWVoiceOut
*hw
,
215 QemuDBusDisplay1AudioOutListener
*listener
)
217 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
218 Volume
*vol
= &vo
->volume
;
219 g_autoptr(GBytes
) bytes
= NULL
;
220 GVariant
*v_vol
= NULL
;
222 if (!vo
->has_volume
) {
226 assert(vol
->channels
< sizeof(vol
->vol
));
227 bytes
= g_bytes_new(vol
->vol
, vol
->channels
);
228 v_vol
= g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes
, TRUE
);
229 qemu_dbus_display1_audio_out_listener_call_set_volume(
230 listener
, (uintptr_t)hw
, vol
->mute
, v_vol
,
231 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
235 dbus_volume_out(HWVoiceOut
*hw
, Volume
*vol
)
237 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
238 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
240 QemuDBusDisplay1AudioOutListener
*listener
= NULL
;
242 vo
->has_volume
= true;
245 g_hash_table_iter_init(&iter
, da
->out_listeners
);
246 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
247 dbus_volume_out_listener(hw
, listener
);
252 dbus_init_in_listener(QemuDBusDisplay1AudioInListener
*listener
, HWVoiceIn
*hw
)
254 qemu_dbus_display1_audio_in_listener_call_init(
262 hw
->info
.bytes_per_frame
,
263 hw
->info
.bytes_per_second
,
264 hw
->info
.swap_endianness
? !AUDIO_HOST_BE
: AUDIO_HOST_BE
,
265 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
269 dbus_init_in(HWVoiceIn
*hw
, struct audsettings
*as
, void *drv_opaque
)
271 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
272 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
274 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
276 audio_pcm_init_info(&hw
->info
, as
);
277 hw
->samples
= DBUS_AUDIO_NSAMPLES
;
278 audio_rate_start(&vo
->rate
);
280 g_hash_table_iter_init(&iter
, da
->in_listeners
);
281 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
282 dbus_init_in_listener(listener
, hw
);
288 dbus_fini_in(HWVoiceIn
*hw
)
290 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
292 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
294 g_hash_table_iter_init(&iter
, da
->in_listeners
);
295 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
296 qemu_dbus_display1_audio_in_listener_call_fini(
299 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
304 dbus_volume_in_listener(HWVoiceIn
*hw
,
305 QemuDBusDisplay1AudioInListener
*listener
)
307 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
308 Volume
*vol
= &vo
->volume
;
309 g_autoptr(GBytes
) bytes
= NULL
;
310 GVariant
*v_vol
= NULL
;
312 if (!vo
->has_volume
) {
316 assert(vol
->channels
< sizeof(vol
->vol
));
317 bytes
= g_bytes_new(vol
->vol
, vol
->channels
);
318 v_vol
= g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes
, TRUE
);
319 qemu_dbus_display1_audio_in_listener_call_set_volume(
320 listener
, (uintptr_t)hw
, vol
->mute
, v_vol
,
321 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
325 dbus_volume_in(HWVoiceIn
*hw
, Volume
*vol
)
327 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
328 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
330 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
332 vo
->has_volume
= true;
335 g_hash_table_iter_init(&iter
, da
->in_listeners
);
336 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
337 dbus_volume_in_listener(hw
, listener
);
342 dbus_read(HWVoiceIn
*hw
, void *buf
, size_t size
)
344 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
345 /* DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); */
347 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
349 trace_dbus_audio_read(size
);
351 /* size = audio_rate_get_bytes(&vo->rate, &hw->info, size); */
353 g_hash_table_iter_init(&iter
, da
->in_listeners
);
354 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
355 g_autoptr(GVariant
) v_data
= NULL
;
359 if (qemu_dbus_display1_audio_in_listener_call_read_sync(
363 G_DBUS_CALL_FLAGS_NONE
, -1,
364 &v_data
, NULL
, NULL
)) {
365 data
= g_variant_get_fixed_array(v_data
, &n
, 1);
366 g_warn_if_fail(n
<= size
);
368 memcpy(buf
, data
, size
);
377 dbus_enable_in(HWVoiceIn
*hw
, bool enable
)
379 DBusAudio
*da
= (DBusAudio
*)hw
->s
->drv_opaque
;
380 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
382 QemuDBusDisplay1AudioInListener
*listener
= NULL
;
384 vo
->enabled
= enable
;
386 audio_rate_start(&vo
->rate
);
389 g_hash_table_iter_init(&iter
, da
->in_listeners
);
390 while (g_hash_table_iter_next(&iter
, NULL
, (void **)&listener
)) {
391 qemu_dbus_display1_audio_in_listener_call_set_enabled(
392 listener
, (uintptr_t)hw
, enable
,
393 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
398 dbus_audio_init(Audiodev
*dev
)
400 DBusAudio
*da
= g_new0(DBusAudio
, 1);
402 da
->out_listeners
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
403 g_free
, g_object_unref
);
404 da
->in_listeners
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
405 g_free
, g_object_unref
);
410 dbus_audio_fini(void *opaque
)
412 DBusAudio
*da
= opaque
;
415 g_dbus_object_manager_server_unexport(da
->server
,
416 DBUS_DISPLAY1_AUDIO_PATH
);
418 g_clear_object(&da
->audio
);
419 g_clear_object(&da
->iface
);
420 g_clear_pointer(&da
->in_listeners
, g_hash_table_unref
);
421 g_clear_pointer(&da
->out_listeners
, g_hash_table_unref
);
422 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
,
452 GUnixFDList
*fd_list
,
454 GVariant
*arg_listener
,
457 DBusAudio
*da
= s
->drv_opaque
;
459 da
->p2p
? "p2p" : g_dbus_method_invocation_get_sender(invocation
);
460 g_autoptr(GDBusConnection
) listener_conn
= NULL
;
461 g_autoptr(GError
) err
= NULL
;
462 g_autoptr(GSocket
) socket
= NULL
;
463 g_autoptr(GSocketConnection
) socket_conn
= NULL
;
464 g_autofree
char *guid
= g_dbus_generate_guid();
465 GHashTable
*listeners
= out
? da
->out_listeners
: da
->in_listeners
;
469 trace_dbus_audio_register(sender
, out
? "out" : "in");
471 if (g_hash_table_contains(listeners
, sender
)) {
472 g_dbus_method_invocation_return_error(invocation
,
474 DBUS_DISPLAY_ERROR_INVALID
,
475 "`%s` is already registered!",
477 return DBUS_METHOD_INVOCATION_HANDLED
;
481 if (!dbus_win32_import_socket(invocation
, arg_listener
, &fd
)) {
482 return DBUS_METHOD_INVOCATION_HANDLED
;
485 fd
= g_unix_fd_list_get(fd_list
, g_variant_get_handle(arg_listener
), &err
);
487 g_dbus_method_invocation_return_error(invocation
,
489 DBUS_DISPLAY_ERROR_FAILED
,
490 "Couldn't get peer fd: %s",
492 return DBUS_METHOD_INVOCATION_HANDLED
;
496 socket
= g_socket_new_from_fd(fd
, &err
);
498 g_dbus_method_invocation_return_error(invocation
,
500 DBUS_DISPLAY_ERROR_FAILED
,
501 "Couldn't make a socket: %s",
508 return DBUS_METHOD_INVOCATION_HANDLED
;
510 socket_conn
= g_socket_connection_factory_create_connection(socket
);
512 qemu_dbus_display1_audio_complete_register_out_listener(
513 da
->iface
, invocation
519 qemu_dbus_display1_audio_complete_register_in_listener(
520 da
->iface
, invocation
528 g_dbus_connection_new_sync(
529 G_IO_STREAM(socket_conn
),
531 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER
,
534 error_report("Failed to setup peer connection: %s", err
->message
);
535 return DBUS_METHOD_INVOCATION_HANDLED
;
539 G_OBJECT(qemu_dbus_display1_audio_out_listener_proxy_new_sync(
541 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START
,
543 "/org/qemu/Display1/AudioOutListener",
546 G_OBJECT(qemu_dbus_display1_audio_in_listener_proxy_new_sync(
548 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START
,
550 "/org/qemu/Display1/AudioInListener",
554 error_report("Failed to setup proxy: %s", err
->message
);
555 return DBUS_METHOD_INVOCATION_HANDLED
;
561 QLIST_FOREACH(hw
, &s
->hw_head_out
, entries
) {
562 DBusVoiceOut
*vo
= container_of(hw
, DBusVoiceOut
, hw
);
563 QemuDBusDisplay1AudioOutListener
*l
=
564 QEMU_DBUS_DISPLAY1_AUDIO_OUT_LISTENER(listener
);
566 dbus_init_out_listener(l
, hw
);
567 qemu_dbus_display1_audio_out_listener_call_set_enabled(
568 l
, (uintptr_t)hw
, vo
->enabled
,
569 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
574 QLIST_FOREACH(hw
, &s
->hw_head_in
, entries
) {
575 DBusVoiceIn
*vo
= container_of(hw
, DBusVoiceIn
, hw
);
576 QemuDBusDisplay1AudioInListener
*l
=
577 QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener
);
579 dbus_init_in_listener(
580 QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener
), hw
);
581 qemu_dbus_display1_audio_in_listener_call_set_enabled(
582 l
, (uintptr_t)hw
, vo
->enabled
,
583 G_DBUS_CALL_FLAGS_NONE
, -1, NULL
, NULL
, NULL
);
587 g_object_set_data_full(G_OBJECT(listener_conn
), "name",
588 g_strdup(sender
), g_free
);
589 g_hash_table_insert(listeners
, g_strdup(sender
), listener
);
590 g_object_connect(listener_conn
,
592 out
? listener_out_vanished_cb
: listener_in_vanished_cb
,
596 return DBUS_METHOD_INVOCATION_HANDLED
;
600 dbus_audio_register_out_listener(AudioState
*s
,
601 GDBusMethodInvocation
*invocation
,
603 GUnixFDList
*fd_list
,
605 GVariant
*arg_listener
)
607 return dbus_audio_register_listener(s
, invocation
,
616 dbus_audio_register_in_listener(AudioState
*s
,
617 GDBusMethodInvocation
*invocation
,
619 GUnixFDList
*fd_list
,
621 GVariant
*arg_listener
)
623 return dbus_audio_register_listener(s
, invocation
,
627 arg_listener
, false);
631 dbus_audio_set_server(AudioState
*s
, GDBusObjectManagerServer
*server
, bool p2p
)
633 DBusAudio
*da
= s
->drv_opaque
;
636 g_assert(!da
->server
);
638 da
->server
= g_object_ref(server
);
641 da
->audio
= g_dbus_object_skeleton_new(DBUS_DISPLAY1_AUDIO_PATH
);
642 da
->iface
= qemu_dbus_display1_audio_skeleton_new();
643 g_object_connect(da
->iface
,
644 "swapped-signal::handle-register-in-listener",
645 dbus_audio_register_in_listener
, s
,
646 "swapped-signal::handle-register-out-listener",
647 dbus_audio_register_out_listener
, s
,
650 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(da
->audio
),
651 G_DBUS_INTERFACE_SKELETON(da
->iface
));
652 g_dbus_object_manager_server_export(da
->server
, da
->audio
);
655 static struct audio_pcm_ops dbus_pcm_ops
= {
656 .init_out
= dbus_init_out
,
657 .fini_out
= dbus_fini_out
,
658 .write
= audio_generic_write
,
659 .get_buffer_out
= dbus_get_buffer_out
,
660 .put_buffer_out
= dbus_put_buffer_out
,
661 .enable_out
= dbus_enable_out
,
662 .volume_out
= dbus_volume_out
,
664 .init_in
= dbus_init_in
,
665 .fini_in
= dbus_fini_in
,
667 .run_buffer_in
= audio_generic_run_buffer_in
,
668 .enable_in
= dbus_enable_in
,
669 .volume_in
= dbus_volume_in
,
672 static struct audio_driver dbus_audio_driver
= {
674 .descr
= "Timer based audio exposed with DBus interface",
675 .init
= dbus_audio_init
,
676 .fini
= dbus_audio_fini
,
677 .set_dbus_server
= dbus_audio_set_server
,
678 .pcm_ops
= &dbus_pcm_ops
,
680 .max_voices_out
= INT_MAX
,
681 .max_voices_in
= INT_MAX
,
682 .voice_size_out
= sizeof(DBusVoiceOut
),
683 .voice_size_in
= sizeof(DBusVoiceIn
)
686 static void register_audio_dbus(void)
688 audio_driver_register(&dbus_audio_driver
);
690 type_init(register_audio_dbus
);
692 module_dep("ui-dbus")