xref: /qemu/audio/dbusaudio.c (revision 370ed600)
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")
658