xref: /qemu/audio/dbusaudio.c (revision 66997c42e02c84481fc162a5b7bd6ad6c643bae2)
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(&vo->rate, &hw->info, *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(&vo->rate, &hw->info, 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