1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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"
31
32#include <gio/gunixfdlist.h>
33#include "ui/dbus-display1.h"
34
35#define AUDIO_CAP "dbus"
36#include "audio.h"
37#include "audio_int.h"
38#include "trace.h"
39
40#define DBUS_DISPLAY1_AUDIO_PATH DBUS_DISPLAY1_ROOT "/Audio"
41
42#define DBUS_AUDIO_NSAMPLES 1024
43
44typedef struct DBusAudio {
45 GDBusObjectManagerServer *server;
46 bool p2p;
47 GDBusObjectSkeleton *audio;
48 QemuDBusDisplay1Audio *iface;
49 GHashTable *out_listeners;
50 GHashTable *in_listeners;
51} DBusAudio;
52
53typedef struct DBusVoiceOut {
54 HWVoiceOut hw;
55 bool enabled;
56 RateCtl rate;
57
58 void *buf;
59 size_t buf_pos;
60 size_t buf_size;
61
62 bool has_volume;
63 Volume volume;
64} DBusVoiceOut;
65
66typedef struct DBusVoiceIn {
67 HWVoiceIn hw;
68 bool enabled;
69 RateCtl rate;
70
71 bool has_volume;
72 Volume volume;
73} DBusVoiceIn;
74
75static void *dbus_get_buffer_out(HWVoiceOut *hw, size_t *size)
76{
77 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
78
79 if (!vo->buf) {
80 vo->buf_size = hw->samples * hw->info.bytes_per_frame;
81 vo->buf = g_malloc(vo->buf_size);
82 vo->buf_pos = 0;
83 }
84
85 *size = MIN(vo->buf_size - vo->buf_pos, *size);
86 *size = audio_rate_get_bytes(&vo->rate, &hw->info, *size);
87
88 return vo->buf + vo->buf_pos;
89
90}
91
92static size_t dbus_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
93{
94 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
95 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
96 GHashTableIter iter;
97 QemuDBusDisplay1AudioOutListener *listener = NULL;
98 g_autoptr(GBytes) bytes = NULL;
99 g_autoptr(GVariant) v_data = NULL;
100
101 assert(buf == vo->buf + vo->buf_pos && vo->buf_pos + size <= vo->buf_size);
102 vo->buf_pos += size;
103
104 trace_dbus_audio_put_buffer_out(size);
105
106 if (vo->buf_pos < vo->buf_size) {
107 return size;
108 }
109
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);
113
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(
117 listener,
118 (uintptr_t)hw,
119 v_data,
120 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
121 }
122
123 return size;
124}
125
126#if HOST_BIG_ENDIAN
127#define AUDIO_HOST_BE TRUE
128#else
129#define AUDIO_HOST_BE FALSE
130#endif
131
132static void
133dbus_init_out_listener(QemuDBusDisplay1AudioOutListener *listener,
134 HWVoiceOut *hw)
135{
136 qemu_dbus_display1_audio_out_listener_call_init(
137 listener,
138 (uintptr_t)hw,
139 hw->info.bits,
140 hw->info.is_signed,
141 hw->info.is_float,
142 hw->info.freq,
143 hw->info.nchannels,
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);
148}
149
150static int
151dbus_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
152{
153 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
154 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
155 GHashTableIter iter;
156 QemuDBusDisplay1AudioOutListener *listener = NULL;
157
158 audio_pcm_init_info(&hw->info, as);
159 hw->samples = DBUS_AUDIO_NSAMPLES;
160 audio_rate_start(&vo->rate);
161
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);
165 }
166 return 0;
167}
168
169static void
170dbus_fini_out(HWVoiceOut *hw)
171{
172 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
173 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
174 GHashTableIter iter;
175 QemuDBusDisplay1AudioOutListener *listener = NULL;
176
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(
180 listener,
181 (uintptr_t)hw,
182 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
183 }
184
185 g_clear_pointer(&vo->buf, g_free);
186}
187
188static void
189dbus_enable_out(HWVoiceOut *hw, bool enable)
190{
191 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
192 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
193 GHashTableIter iter;
194 QemuDBusDisplay1AudioOutListener *listener = NULL;
195
196 vo->enabled = enable;
197 if (enable) {
198 audio_rate_start(&vo->rate);
199 }
200
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);
206 }
207}
208
209static void
210dbus_volume_out_listener(HWVoiceOut *hw,
211 QemuDBusDisplay1AudioOutListener *listener)
212{
213 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
214 Volume *vol = &vo->volume;
215 g_autoptr(GBytes) bytes = NULL;
216 GVariant *v_vol = NULL;
217
218 if (!vo->has_volume) {
219 return;
220 }
221
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);
228}
229
230static void
231dbus_volume_out(HWVoiceOut *hw, Volume *vol)
232{
233 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
234 DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
235 GHashTableIter iter;
236 QemuDBusDisplay1AudioOutListener *listener = NULL;
237
238 vo->has_volume = true;
239 vo->volume = *vol;
240
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);
244 }
245}
246
247static void
248dbus_init_in_listener(QemuDBusDisplay1AudioInListener *listener, HWVoiceIn *hw)
249{
250 qemu_dbus_display1_audio_in_listener_call_init(
251 listener,
252 (uintptr_t)hw,
253 hw->info.bits,
254 hw->info.is_signed,
255 hw->info.is_float,
256 hw->info.freq,
257 hw->info.nchannels,
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);
262}
263
264static int
265dbus_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
266{
267 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
268 DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
269 GHashTableIter iter;
270 QemuDBusDisplay1AudioInListener *listener = NULL;
271
272 audio_pcm_init_info(&hw->info, as);
273 hw->samples = DBUS_AUDIO_NSAMPLES;
274 audio_rate_start(&vo->rate);
275
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);
279 }
280 return 0;
281}
282
283static void
284dbus_fini_in(HWVoiceIn *hw)
285{
286 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
287 GHashTableIter iter;
288 QemuDBusDisplay1AudioInListener *listener = NULL;
289
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(
293 listener,
294 (uintptr_t)hw,
295 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
296 }
297}
298
299static void
300dbus_volume_in_listener(HWVoiceIn *hw,
301 QemuDBusDisplay1AudioInListener *listener)
302{
303 DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
304 Volume *vol = &vo->volume;
305 g_autoptr(GBytes) bytes = NULL;
306 GVariant *v_vol = NULL;
307
308 if (!vo->has_volume) {
309 return;
310 }
311
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);
318}
319
320static void
321dbus_volume_in(HWVoiceIn *hw, Volume *vol)
322{
323 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
324 DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
325 GHashTableIter iter;
326 QemuDBusDisplay1AudioInListener *listener = NULL;
327
328 vo->has_volume = true;
329 vo->volume = *vol;
330
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);
334 }
335}
336
337static size_t
338dbus_read(HWVoiceIn *hw, void *buf, size_t size)
339{
340 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
341
342 GHashTableIter iter;
343 QemuDBusDisplay1AudioInListener *listener = NULL;
344
345 trace_dbus_audio_read(size);
346
347
348
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;
352 const char *data;
353 gsize n = 0;
354
355 if (qemu_dbus_display1_audio_in_listener_call_read_sync(
356 listener,
357 (uintptr_t)hw,
358 size,
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);
363 size = MIN(n, size);
364 memcpy(buf, data, size);
365 break;
366 }
367 }
368
369 return size;
370}
371
372static void
373dbus_enable_in(HWVoiceIn *hw, bool enable)
374{
375 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
376 DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
377 GHashTableIter iter;
378 QemuDBusDisplay1AudioInListener *listener = NULL;
379
380 vo->enabled = enable;
381 if (enable) {
382 audio_rate_start(&vo->rate);
383 }
384
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);
390 }
391}
392
393static void *
394dbus_audio_init(Audiodev *dev)
395{
396 DBusAudio *da = g_new0(DBusAudio, 1);
397
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);
402 return da;
403}
404
405static void
406dbus_audio_fini(void *opaque)
407{
408 DBusAudio *da = opaque;
409
410 if (da->server) {
411 g_dbus_object_manager_server_unexport(da->server,
412 DBUS_DISPLAY1_AUDIO_PATH);
413 }
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);
419 g_free(da);
420}
421
422static void
423listener_out_vanished_cb(GDBusConnection *connection,
424 gboolean remote_peer_vanished,
425 GError *error,
426 DBusAudio *da)
427{
428 char *name = g_object_get_data(G_OBJECT(connection), "name");
429
430 g_hash_table_remove(da->out_listeners, name);
431}
432
433static void
434listener_in_vanished_cb(GDBusConnection *connection,
435 gboolean remote_peer_vanished,
436 GError *error,
437 DBusAudio *da)
438{
439 char *name = g_object_get_data(G_OBJECT(connection), "name");
440
441 g_hash_table_remove(da->in_listeners, name);
442}
443
444static gboolean
445dbus_audio_register_listener(AudioState *s,
446 GDBusMethodInvocation *invocation,
447 GUnixFDList *fd_list,
448 GVariant *arg_listener,
449 bool out)
450{
451 DBusAudio *da = s->drv_opaque;
452 const char *sender =
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;
460 GObject *listener;
461 int fd;
462
463 trace_dbus_audio_register(sender, out ? "out" : "in");
464
465 if (g_hash_table_contains(listeners, sender)) {
466 g_dbus_method_invocation_return_error(invocation,
467 DBUS_DISPLAY_ERROR,
468 DBUS_DISPLAY_ERROR_INVALID,
469 "`%s` is already registered!",
470 sender);
471 return DBUS_METHOD_INVOCATION_HANDLED;
472 }
473
474 fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);
475 if (err) {
476 g_dbus_method_invocation_return_error(invocation,
477 DBUS_DISPLAY_ERROR,
478 DBUS_DISPLAY_ERROR_FAILED,
479 "Couldn't get peer fd: %s",
480 err->message);
481 return DBUS_METHOD_INVOCATION_HANDLED;
482 }
483
484 socket = g_socket_new_from_fd(fd, &err);
485 if (err) {
486 g_dbus_method_invocation_return_error(invocation,
487 DBUS_DISPLAY_ERROR,
488 DBUS_DISPLAY_ERROR_FAILED,
489 "Couldn't make a socket: %s",
490 err->message);
491 return DBUS_METHOD_INVOCATION_HANDLED;
492 }
493 socket_conn = g_socket_connection_factory_create_connection(socket);
494 if (out) {
495 qemu_dbus_display1_audio_complete_register_out_listener(
496 da->iface, invocation, NULL);
497 } else {
498 qemu_dbus_display1_audio_complete_register_in_listener(
499 da->iface, invocation, NULL);
500 }
501
502 listener_conn =
503 g_dbus_connection_new_sync(
504 G_IO_STREAM(socket_conn),
505 guid,
506 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
507 NULL, NULL, &err);
508 if (err) {
509 error_report("Failed to setup peer connection: %s", err->message);
510 return DBUS_METHOD_INVOCATION_HANDLED;
511 }
512
513 listener = out ?
514 G_OBJECT(qemu_dbus_display1_audio_out_listener_proxy_new_sync(
515 listener_conn,
516 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
517 NULL,
518 "/org/qemu/Display1/AudioOutListener",
519 NULL,
520 &err)) :
521 G_OBJECT(qemu_dbus_display1_audio_in_listener_proxy_new_sync(
522 listener_conn,
523 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
524 NULL,
525 "/org/qemu/Display1/AudioInListener",
526 NULL,
527 &err));
528 if (!listener) {
529 error_report("Failed to setup proxy: %s", err->message);
530 return DBUS_METHOD_INVOCATION_HANDLED;
531 }
532
533 if (out) {
534 HWVoiceOut *hw;
535
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);
540
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);
545 }
546 } else {
547 HWVoiceIn *hw;
548
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);
553
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);
559 }
560 }
561
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,
566 "signal::closed",
567 out ? listener_out_vanished_cb : listener_in_vanished_cb,
568 da,
569 NULL);
570
571 return DBUS_METHOD_INVOCATION_HANDLED;
572}
573
574static gboolean
575dbus_audio_register_out_listener(AudioState *s,
576 GDBusMethodInvocation *invocation,
577 GUnixFDList *fd_list,
578 GVariant *arg_listener)
579{
580 return dbus_audio_register_listener(s, invocation,
581 fd_list, arg_listener, true);
582
583}
584
585static gboolean
586dbus_audio_register_in_listener(AudioState *s,
587 GDBusMethodInvocation *invocation,
588 GUnixFDList *fd_list,
589 GVariant *arg_listener)
590{
591 return dbus_audio_register_listener(s, invocation,
592 fd_list, arg_listener, false);
593}
594
595static void
596dbus_audio_set_server(AudioState *s, GDBusObjectManagerServer *server, bool p2p)
597{
598 DBusAudio *da = s->drv_opaque;
599
600 g_assert(da);
601 g_assert(!da->server);
602
603 da->server = g_object_ref(server);
604 da->p2p = p2p;
605
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,
613 NULL);
614
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);
618}
619
620static 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,
628
629 .init_in = dbus_init_in,
630 .fini_in = dbus_fini_in,
631 .read = dbus_read,
632 .run_buffer_in = audio_generic_run_buffer_in,
633 .enable_in = dbus_enable_in,
634 .volume_in = dbus_volume_in,
635};
636
637static struct audio_driver dbus_audio_driver = {
638 .name = "dbus",
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,
644 .can_be_default = 1,
645 .max_voices_out = INT_MAX,
646 .max_voices_in = INT_MAX,
647 .voice_size_out = sizeof(DBusVoiceOut),
648 .voice_size_in = sizeof(DBusVoiceIn)
649};
650
651static void register_audio_dbus(void)
652{
653 audio_driver_register(&dbus_audio_driver);
654}
655type_init(register_audio_dbus);
656
657module_dep("ui-dbus")
658