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