cubeb.c (19255B)
1 /* 2 * Copyright © 2013 Mozilla Foundation 3 * 4 * This program is made available under an ISC-style license. See the 5 * accompanying file LICENSE for details. 6 */ 7 #undef NDEBUG 8 #include "cubeb/cubeb.h" 9 #include "cubeb-internal.h" 10 #include <assert.h> 11 #include <stddef.h> 12 #include <stdlib.h> 13 #include <string.h> 14 15 #define NELEMS(x) ((int)(sizeof(x) / sizeof(x[0]))) 16 17 struct cubeb { 18 struct cubeb_ops * ops; 19 }; 20 21 struct cubeb_stream { 22 /* 23 * Note: All implementations of cubeb_stream must keep the following 24 * layout. 25 */ 26 struct cubeb * context; 27 void * user_ptr; 28 }; 29 30 #if defined(USE_PULSE) 31 int 32 pulse_init(cubeb ** context, char const * context_name); 33 #endif 34 #if defined(USE_PULSE_RUST) 35 int 36 pulse_rust_init(cubeb ** contet, char const * context_name); 37 #endif 38 #if defined(USE_JACK) 39 int 40 jack_init(cubeb ** context, char const * context_name); 41 #endif 42 #if defined(USE_ALSA) 43 int 44 alsa_init(cubeb ** context, char const * context_name); 45 #endif 46 #if defined(USE_AUDIOUNIT) 47 int 48 audiounit_init(cubeb ** context, char const * context_name); 49 #endif 50 #if defined(USE_AUDIOUNIT_RUST) 51 int 52 audiounit_rust_init(cubeb ** contet, char const * context_name); 53 #endif 54 #if defined(USE_WINMM) 55 int 56 winmm_init(cubeb ** context, char const * context_name); 57 #endif 58 #if defined(USE_WASAPI) 59 int 60 wasapi_init(cubeb ** context, char const * context_name); 61 #endif 62 #if defined(USE_SNDIO) 63 int 64 sndio_init(cubeb ** context, char const * context_name); 65 #endif 66 #if defined(USE_SUN) 67 int 68 sun_init(cubeb ** context, char const * context_name); 69 #endif 70 #if defined(USE_OPENSL) 71 int 72 opensl_init(cubeb ** context, char const * context_name); 73 #endif 74 #if defined(USE_OSS) 75 int 76 oss_init(cubeb ** context, char const * context_name); 77 #endif 78 #if defined(USE_AAUDIO) 79 int 80 aaudio_init(cubeb ** context, char const * context_name); 81 #endif 82 #if defined(USE_AUDIOTRACK) 83 int 84 audiotrack_init(cubeb ** context, char const * context_name); 85 #endif 86 #if defined(USE_KAI) 87 int 88 kai_init(cubeb ** context, char const * context_name); 89 #endif 90 91 static int 92 validate_stream_params(cubeb_stream_params * input_stream_params, 93 cubeb_stream_params * output_stream_params) 94 { 95 XASSERT(input_stream_params || output_stream_params); 96 if (output_stream_params) { 97 if (output_stream_params->rate < 1000 || 98 output_stream_params->rate > 768000 || 99 output_stream_params->channels < 1 || 100 output_stream_params->channels > UINT8_MAX) { 101 return CUBEB_ERROR_INVALID_FORMAT; 102 } 103 } 104 if (input_stream_params) { 105 if (input_stream_params->rate < 1000 || 106 input_stream_params->rate > 768000 || 107 input_stream_params->channels < 1 || 108 input_stream_params->channels > UINT8_MAX) { 109 return CUBEB_ERROR_INVALID_FORMAT; 110 } 111 } 112 // Rate and sample format must be the same for input and output, if using a 113 // duplex stream 114 if (input_stream_params && output_stream_params) { 115 if (input_stream_params->rate != output_stream_params->rate || 116 input_stream_params->format != output_stream_params->format) { 117 return CUBEB_ERROR_INVALID_FORMAT; 118 } 119 } 120 121 cubeb_stream_params * params = 122 input_stream_params ? input_stream_params : output_stream_params; 123 124 switch (params->format) { 125 case CUBEB_SAMPLE_S16LE: 126 case CUBEB_SAMPLE_S16BE: 127 case CUBEB_SAMPLE_FLOAT32LE: 128 case CUBEB_SAMPLE_FLOAT32BE: 129 return CUBEB_OK; 130 } 131 132 return CUBEB_ERROR_INVALID_FORMAT; 133 } 134 135 static int 136 validate_latency(int latency) 137 { 138 if (latency < 1 || latency > 96000) { 139 return CUBEB_ERROR_INVALID_PARAMETER; 140 } 141 return CUBEB_OK; 142 } 143 144 int 145 cubeb_init(cubeb ** context, char const * context_name, 146 char const * backend_name) 147 { 148 int (*init_oneshot)(cubeb **, char const *) = NULL; 149 150 if (backend_name != NULL) { 151 if (!strcmp(backend_name, "pulse")) { 152 #if defined(USE_PULSE) 153 init_oneshot = pulse_init; 154 #endif 155 } else if (!strcmp(backend_name, "pulse-rust")) { 156 #if defined(USE_PULSE_RUST) 157 init_oneshot = pulse_rust_init; 158 #endif 159 } else if (!strcmp(backend_name, "jack")) { 160 #if defined(USE_JACK) 161 init_oneshot = jack_init; 162 #endif 163 } else if (!strcmp(backend_name, "alsa")) { 164 #if defined(USE_ALSA) 165 init_oneshot = alsa_init; 166 #endif 167 } else if (!strcmp(backend_name, "audiounit")) { 168 #if defined(USE_AUDIOUNIT) 169 init_oneshot = audiounit_init; 170 #endif 171 } else if (!strcmp(backend_name, "audiounit-rust")) { 172 #if defined(USE_AUDIOUNIT_RUST) 173 init_oneshot = audiounit_rust_init; 174 #endif 175 } else if (!strcmp(backend_name, "wasapi")) { 176 #if defined(USE_WASAPI) 177 init_oneshot = wasapi_init; 178 #endif 179 } else if (!strcmp(backend_name, "winmm")) { 180 #if defined(USE_WINMM) 181 init_oneshot = winmm_init; 182 #endif 183 } else if (!strcmp(backend_name, "sndio")) { 184 #if defined(USE_SNDIO) 185 init_oneshot = sndio_init; 186 #endif 187 } else if (!strcmp(backend_name, "sun")) { 188 #if defined(USE_SUN) 189 init_oneshot = sun_init; 190 #endif 191 } else if (!strcmp(backend_name, "opensl")) { 192 #if defined(USE_OPENSL) 193 init_oneshot = opensl_init; 194 #endif 195 } else if (!strcmp(backend_name, "oss")) { 196 #if defined(USE_OSS) 197 init_oneshot = oss_init; 198 #endif 199 } else if (!strcmp(backend_name, "aaudio")) { 200 #if defined(USE_AAUDIO) 201 init_oneshot = aaudio_init; 202 #endif 203 } else if (!strcmp(backend_name, "audiotrack")) { 204 #if defined(USE_AUDIOTRACK) 205 init_oneshot = audiotrack_init; 206 #endif 207 } else if (!strcmp(backend_name, "kai")) { 208 #if defined(USE_KAI) 209 init_oneshot = kai_init; 210 #endif 211 } else { 212 /* Already set */ 213 } 214 } 215 216 int (*default_init[])(cubeb **, char const *) = { 217 /* 218 * init_oneshot must be at the top to allow user 219 * to override all other choices 220 */ 221 init_oneshot, 222 #if defined(USE_PULSE_RUST) 223 pulse_rust_init, 224 #endif 225 #if defined(USE_PULSE) 226 pulse_init, 227 #endif 228 #if defined(USE_JACK) 229 jack_init, 230 #endif 231 #if defined(USE_SNDIO) 232 sndio_init, 233 #endif 234 #if defined(USE_ALSA) 235 alsa_init, 236 #endif 237 #if defined(USE_OSS) 238 oss_init, 239 #endif 240 #if defined(USE_AUDIOUNIT_RUST) 241 audiounit_rust_init, 242 #endif 243 #if defined(USE_AUDIOUNIT) 244 audiounit_init, 245 #endif 246 #if defined(USE_WASAPI) 247 wasapi_init, 248 #endif 249 #if defined(USE_WINMM) 250 winmm_init, 251 #endif 252 #if defined(USE_SUN) 253 sun_init, 254 #endif 255 #if defined(USE_AAUDIO) 256 aaudio_init, 257 #endif 258 #if defined(USE_OPENSL) 259 opensl_init, 260 #endif 261 #if defined(USE_AUDIOTRACK) 262 audiotrack_init, 263 #endif 264 #if defined(USE_KAI) 265 kai_init, 266 #endif 267 }; 268 int i; 269 270 if (!context) { 271 return CUBEB_ERROR_INVALID_PARAMETER; 272 } 273 274 #define OK(fn) assert((*context)->ops->fn) 275 for (i = 0; i < NELEMS(default_init); ++i) { 276 if (default_init[i] && default_init[i](context, context_name) == CUBEB_OK) { 277 /* Assert that the minimal API is implemented. */ 278 OK(get_backend_id); 279 OK(destroy); 280 OK(stream_init); 281 OK(stream_destroy); 282 OK(stream_start); 283 OK(stream_stop); 284 OK(stream_get_position); 285 return CUBEB_OK; 286 } 287 } 288 return CUBEB_ERROR; 289 } 290 291 char const * 292 cubeb_get_backend_id(cubeb * context) 293 { 294 if (!context) { 295 return NULL; 296 } 297 298 return context->ops->get_backend_id(context); 299 } 300 301 cubeb_backend_names 302 cubeb_get_backend_names() 303 { 304 static const char * const backend_names[] = { 305 #if defined(USE_PULSE) 306 "pulse", 307 #endif 308 #if defined(USE_PULSE_RUST) 309 "pulse-rust", 310 #endif 311 #if defined(USE_JACK) 312 "jack", 313 #endif 314 #if defined(USE_ALSA) 315 "alsa", 316 #endif 317 #if defined(USE_AUDIOUNIT) 318 "audiounit", 319 #endif 320 #if defined(USE_AUDIOUNIT_RUST) 321 "audiounit-rust", 322 #endif 323 #if defined(USE_WASAPI) 324 "wasapi", 325 #endif 326 #if defined(USE_WINMM) 327 "winmm", 328 #endif 329 #if defined(USE_SNDIO) 330 "sndio", 331 #endif 332 #if defined(USE_SUN) 333 "sun", 334 #endif 335 #if defined(USE_OPENSL) 336 "opensl", 337 #endif 338 #if defined(USE_OSS) 339 "oss", 340 #endif 341 #if defined(USE_AAUDIO) 342 "aaudio", 343 #endif 344 #if defined(USE_AUDIOTRACK) 345 "audiotrack", 346 #endif 347 #if defined(USE_KAI) 348 "kai", 349 #endif 350 }; 351 352 return (cubeb_backend_names){ 353 .names = backend_names, 354 .count = NELEMS(backend_names), 355 }; 356 } 357 358 int 359 cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels) 360 { 361 if (!context || !max_channels) { 362 return CUBEB_ERROR_INVALID_PARAMETER; 363 } 364 365 if (!context->ops->get_max_channel_count) { 366 return CUBEB_ERROR_NOT_SUPPORTED; 367 } 368 369 return context->ops->get_max_channel_count(context, max_channels); 370 } 371 372 int 373 cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params, 374 uint32_t * latency_ms) 375 { 376 if (!context || !params || !latency_ms) { 377 return CUBEB_ERROR_INVALID_PARAMETER; 378 } 379 380 if (!context->ops->get_min_latency) { 381 return CUBEB_ERROR_NOT_SUPPORTED; 382 } 383 384 return context->ops->get_min_latency(context, *params, latency_ms); 385 } 386 387 int 388 cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate) 389 { 390 if (!context || !rate) { 391 return CUBEB_ERROR_INVALID_PARAMETER; 392 } 393 394 if (!context->ops->get_preferred_sample_rate) { 395 return CUBEB_ERROR_NOT_SUPPORTED; 396 } 397 398 return context->ops->get_preferred_sample_rate(context, rate); 399 } 400 401 int 402 cubeb_get_supported_input_processing_params( 403 cubeb * context, cubeb_input_processing_params * params) 404 { 405 if (!context || !params) { 406 return CUBEB_ERROR_INVALID_PARAMETER; 407 } 408 409 if (!context->ops->get_supported_input_processing_params) { 410 return CUBEB_ERROR_NOT_SUPPORTED; 411 } 412 413 return context->ops->get_supported_input_processing_params(context, params); 414 } 415 416 void 417 cubeb_destroy(cubeb * context) 418 { 419 if (!context) { 420 return; 421 } 422 423 context->ops->destroy(context); 424 425 cubeb_set_log_callback(CUBEB_LOG_DISABLED, NULL); 426 } 427 428 int 429 cubeb_stream_init(cubeb * context, cubeb_stream ** stream, 430 char const * stream_name, cubeb_devid input_device, 431 cubeb_stream_params * input_stream_params, 432 cubeb_devid output_device, 433 cubeb_stream_params * output_stream_params, 434 unsigned int latency, cubeb_data_callback data_callback, 435 cubeb_state_callback state_callback, void * user_ptr) 436 { 437 int r; 438 439 if (!context || !stream || !data_callback || !state_callback) { 440 return CUBEB_ERROR_INVALID_PARAMETER; 441 } 442 443 if ((r = validate_stream_params(input_stream_params, output_stream_params)) != 444 CUBEB_OK || 445 (r = validate_latency(latency)) != CUBEB_OK) { 446 return r; 447 } 448 449 r = context->ops->stream_init(context, stream, stream_name, input_device, 450 input_stream_params, output_device, 451 output_stream_params, latency, data_callback, 452 state_callback, user_ptr); 453 454 if (r == CUBEB_ERROR_INVALID_FORMAT) { 455 LOG("Invalid format, %p %p %d %d", output_stream_params, 456 input_stream_params, 457 output_stream_params && output_stream_params->format, 458 input_stream_params && input_stream_params->format); 459 } 460 461 return r; 462 } 463 464 void 465 cubeb_stream_destroy(cubeb_stream * stream) 466 { 467 if (!stream) { 468 return; 469 } 470 471 stream->context->ops->stream_destroy(stream); 472 } 473 474 int 475 cubeb_stream_start(cubeb_stream * stream) 476 { 477 if (!stream) { 478 return CUBEB_ERROR_INVALID_PARAMETER; 479 } 480 481 return stream->context->ops->stream_start(stream); 482 } 483 484 int 485 cubeb_stream_stop(cubeb_stream * stream) 486 { 487 if (!stream) { 488 return CUBEB_ERROR_INVALID_PARAMETER; 489 } 490 491 return stream->context->ops->stream_stop(stream); 492 } 493 494 int 495 cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position) 496 { 497 if (!stream || !position) { 498 return CUBEB_ERROR_INVALID_PARAMETER; 499 } 500 501 return stream->context->ops->stream_get_position(stream, position); 502 } 503 504 int 505 cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency) 506 { 507 if (!stream || !latency) { 508 return CUBEB_ERROR_INVALID_PARAMETER; 509 } 510 511 if (!stream->context->ops->stream_get_latency) { 512 return CUBEB_ERROR_NOT_SUPPORTED; 513 } 514 515 return stream->context->ops->stream_get_latency(stream, latency); 516 } 517 518 int 519 cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency) 520 { 521 if (!stream || !latency) { 522 return CUBEB_ERROR_INVALID_PARAMETER; 523 } 524 525 if (!stream->context->ops->stream_get_input_latency) { 526 return CUBEB_ERROR_NOT_SUPPORTED; 527 } 528 529 return stream->context->ops->stream_get_input_latency(stream, latency); 530 } 531 532 int 533 cubeb_stream_set_volume(cubeb_stream * stream, float volume) 534 { 535 if (!stream || volume > 1.0 || volume < 0.0) { 536 return CUBEB_ERROR_INVALID_PARAMETER; 537 } 538 539 if (!stream->context->ops->stream_set_volume) { 540 return CUBEB_ERROR_NOT_SUPPORTED; 541 } 542 543 return stream->context->ops->stream_set_volume(stream, volume); 544 } 545 546 int 547 cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name) 548 { 549 if (!stream || !stream_name) { 550 return CUBEB_ERROR_INVALID_PARAMETER; 551 } 552 553 if (!stream->context->ops->stream_set_name) { 554 return CUBEB_ERROR_NOT_SUPPORTED; 555 } 556 557 return stream->context->ops->stream_set_name(stream, stream_name); 558 } 559 560 int 561 cubeb_stream_get_current_device(cubeb_stream * stream, 562 cubeb_device ** const device) 563 { 564 if (!stream || !device) { 565 return CUBEB_ERROR_INVALID_PARAMETER; 566 } 567 568 if (!stream->context->ops->stream_get_current_device) { 569 return CUBEB_ERROR_NOT_SUPPORTED; 570 } 571 572 return stream->context->ops->stream_get_current_device(stream, device); 573 } 574 575 int 576 cubeb_stream_set_input_mute(cubeb_stream * stream, int mute) 577 { 578 if (!stream) { 579 return CUBEB_ERROR_INVALID_PARAMETER; 580 } 581 582 if (!stream->context->ops->stream_set_input_mute) { 583 return CUBEB_ERROR_NOT_SUPPORTED; 584 } 585 586 return stream->context->ops->stream_set_input_mute(stream, mute); 587 } 588 589 int 590 cubeb_stream_set_input_processing_params(cubeb_stream * stream, 591 cubeb_input_processing_params params) 592 { 593 if (!stream) { 594 return CUBEB_ERROR_INVALID_PARAMETER; 595 } 596 597 if (!stream->context->ops->stream_set_input_processing_params) { 598 return CUBEB_ERROR_NOT_SUPPORTED; 599 } 600 601 return stream->context->ops->stream_set_input_processing_params(stream, 602 params); 603 } 604 605 int 606 cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * device) 607 { 608 if (!stream || !device) { 609 return CUBEB_ERROR_INVALID_PARAMETER; 610 } 611 612 if (!stream->context->ops->stream_device_destroy) { 613 return CUBEB_ERROR_NOT_SUPPORTED; 614 } 615 616 return stream->context->ops->stream_device_destroy(stream, device); 617 } 618 619 int 620 cubeb_stream_register_device_changed_callback( 621 cubeb_stream * stream, 622 cubeb_device_changed_callback device_changed_callback) 623 { 624 if (!stream) { 625 return CUBEB_ERROR_INVALID_PARAMETER; 626 } 627 628 if (!stream->context->ops->stream_register_device_changed_callback) { 629 return CUBEB_ERROR_NOT_SUPPORTED; 630 } 631 632 return stream->context->ops->stream_register_device_changed_callback( 633 stream, device_changed_callback); 634 } 635 636 void * 637 cubeb_stream_user_ptr(cubeb_stream * stream) 638 { 639 if (!stream) { 640 return NULL; 641 } 642 643 return stream->user_ptr; 644 } 645 646 static void 647 log_device(cubeb_device_info * device_info) 648 { 649 char devfmts[128] = ""; 650 const char *devtype, *devstate, *devdeffmt; 651 652 switch (device_info->type) { 653 case CUBEB_DEVICE_TYPE_INPUT: 654 devtype = "input"; 655 break; 656 case CUBEB_DEVICE_TYPE_OUTPUT: 657 devtype = "output"; 658 break; 659 case CUBEB_DEVICE_TYPE_UNKNOWN: 660 default: 661 devtype = "unknown?"; 662 break; 663 }; 664 665 switch (device_info->state) { 666 case CUBEB_DEVICE_STATE_DISABLED: 667 devstate = "disabled"; 668 break; 669 case CUBEB_DEVICE_STATE_UNPLUGGED: 670 devstate = "unplugged"; 671 break; 672 case CUBEB_DEVICE_STATE_ENABLED: 673 devstate = "enabled"; 674 break; 675 default: 676 devstate = "unknown?"; 677 break; 678 }; 679 680 switch (device_info->default_format) { 681 case CUBEB_DEVICE_FMT_S16LE: 682 devdeffmt = "S16LE"; 683 break; 684 case CUBEB_DEVICE_FMT_S16BE: 685 devdeffmt = "S16BE"; 686 break; 687 case CUBEB_DEVICE_FMT_F32LE: 688 devdeffmt = "F32LE"; 689 break; 690 case CUBEB_DEVICE_FMT_F32BE: 691 devdeffmt = "F32BE"; 692 break; 693 default: 694 devdeffmt = "unknown?"; 695 break; 696 }; 697 698 if (device_info->format & CUBEB_DEVICE_FMT_S16LE) { 699 strcat(devfmts, " S16LE"); 700 } 701 if (device_info->format & CUBEB_DEVICE_FMT_S16BE) { 702 strcat(devfmts, " S16BE"); 703 } 704 if (device_info->format & CUBEB_DEVICE_FMT_F32LE) { 705 strcat(devfmts, " F32LE"); 706 } 707 if (device_info->format & CUBEB_DEVICE_FMT_F32BE) { 708 strcat(devfmts, " F32BE"); 709 } 710 711 LOG("DeviceID: \"%s\"%s\n" 712 "\tName:\t\"%s\"\n" 713 "\tGroup:\t\"%s\"\n" 714 "\tVendor:\t\"%s\"\n" 715 "\tType:\t%s\n" 716 "\tState:\t%s\n" 717 "\tMaximum channels:\t%u\n" 718 "\tFormat:\t%s (0x%x) (default: %s)\n" 719 "\tRate:\t[%u, %u] (default: %u)\n" 720 "\tLatency: lo %u frames, hi %u frames", 721 device_info->device_id, device_info->preferred ? " (PREFERRED)" : "", 722 device_info->friendly_name, device_info->group_id, 723 device_info->vendor_name, devtype, devstate, device_info->max_channels, 724 (devfmts[0] == '\0') ? devfmts : devfmts + 1, 725 (unsigned int)device_info->format, devdeffmt, device_info->min_rate, 726 device_info->max_rate, device_info->default_rate, device_info->latency_lo, 727 device_info->latency_hi); 728 } 729 730 int 731 cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype, 732 cubeb_device_collection * collection) 733 { 734 int rv; 735 if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0) 736 return CUBEB_ERROR_INVALID_PARAMETER; 737 if (context == NULL || collection == NULL) 738 return CUBEB_ERROR_INVALID_PARAMETER; 739 if (!context->ops->enumerate_devices) 740 return CUBEB_ERROR_NOT_SUPPORTED; 741 742 rv = context->ops->enumerate_devices(context, devtype, collection); 743 744 if (cubeb_log_get_callback()) { 745 for (size_t i = 0; i < collection->count; i++) { 746 log_device(&collection->device[i]); 747 } 748 } 749 750 return rv; 751 } 752 753 int 754 cubeb_device_collection_destroy(cubeb * context, 755 cubeb_device_collection * collection) 756 { 757 int r; 758 759 if (context == NULL || collection == NULL) 760 return CUBEB_ERROR_INVALID_PARAMETER; 761 762 if (!context->ops->device_collection_destroy) 763 return CUBEB_ERROR_NOT_SUPPORTED; 764 765 if (!collection->device) 766 return CUBEB_OK; 767 768 r = context->ops->device_collection_destroy(context, collection); 769 if (r == CUBEB_OK) { 770 collection->device = NULL; 771 collection->count = 0; 772 } 773 774 return r; 775 } 776 777 int 778 cubeb_register_device_collection_changed( 779 cubeb * context, cubeb_device_type devtype, 780 cubeb_device_collection_changed_callback callback, void * user_ptr) 781 { 782 if (context == NULL || 783 (devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0) 784 return CUBEB_ERROR_INVALID_PARAMETER; 785 786 if (!context->ops->register_device_collection_changed) { 787 return CUBEB_ERROR_NOT_SUPPORTED; 788 } 789 790 return context->ops->register_device_collection_changed(context, devtype, 791 callback, user_ptr); 792 } 793 794 int 795 cubeb_set_log_callback(cubeb_log_level log_level, 796 cubeb_log_callback log_callback) 797 { 798 if (log_level < CUBEB_LOG_DISABLED || log_level > CUBEB_LOG_VERBOSE) { 799 return CUBEB_ERROR_INVALID_FORMAT; 800 } 801 802 if (!log_callback && log_level != CUBEB_LOG_DISABLED) { 803 return CUBEB_ERROR_INVALID_PARAMETER; 804 } 805 806 if (cubeb_log_get_callback() && log_callback) { 807 return CUBEB_ERROR_NOT_SUPPORTED; 808 } 809 810 cubeb_log_set(log_level, log_callback); 811 812 return CUBEB_OK; 813 }