]> git.proxmox.com Git - mirror_qemu.git/blob - audio/dbusaudio.c
block: use bdrv_co_debug_event in coroutine context
[mirror_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 bool p2p;
47 GDBusObjectSkeleton *audio;
48 QemuDBusDisplay1Audio *iface;
49 GHashTable *out_listeners;
50 GHashTable *in_listeners;
51 } DBusAudio;
52
53 typedef 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
66 typedef struct DBusVoiceIn {
67 HWVoiceIn hw;
68 bool enabled;
69 RateCtl rate;
70
71 bool has_volume;
72 Volume volume;
73 } DBusVoiceIn;
74
75 static 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
92 static 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
132 static void
133 dbus_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
150 static int
151 dbus_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
169 static void
170 dbus_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
188 static void
189 dbus_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
209 static void
210 dbus_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
230 static void
231 dbus_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
247 static void
248 dbus_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
264 static int
265 dbus_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
283 static void
284 dbus_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
299 static void
300 dbus_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
320 static void
321 dbus_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
337 static size_t
338 dbus_read(HWVoiceIn *hw, void *buf, size_t size)
339 {
340 DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
341 /* DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); */
342 GHashTableIter iter;
343 QemuDBusDisplay1AudioInListener *listener = NULL;
344
345 trace_dbus_audio_read(size);
346
347 /* size = audio_rate_get_bytes(&vo->rate, &hw->info, size); */
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
372 static void
373 dbus_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
393 static void *
394 dbus_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
405 static void
406 dbus_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
422 static void
423 listener_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
433 static void
434 listener_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
444 static gboolean
445 dbus_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
574 static gboolean
575 dbus_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
585 static gboolean
586 dbus_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
595 static void
596 dbus_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
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,
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
637 static 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
651 static void register_audio_dbus(void)
652 {
653 audio_driver_register(&dbus_audio_driver);
654 }
655 type_init(register_audio_dbus);
656
657 module_dep("ui-dbus")