xref: /openbmc/qemu/audio/dbusaudio.c (revision b14df228)
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")
655