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