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