1/*** 2 This file is part of PulseAudio. 3 4 Copyright 2004-2006 Lennart Poettering 5 6 PulseAudio is free software; you can redistribute it and/or modify 7 it under the terms of the GNU Lesser General Public License as published 8 by the Free Software Foundation; either version 2.1 of the License, 9 or (at your option) any later version. 10 11 PulseAudio is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public License 17 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 18***/ 19 20#ifdef HAVE_CONFIG_H 21#include <config.h> 22#endif 23 24#include <stdlib.h> 25#include <stdio.h> 26#include <errno.h> 27 28#include <pulse/xmalloc.h> 29#include <pulse/timeval.h> 30 31#include <pulsecore/sink-input.h> 32#include <pulsecore/source-output.h> 33#include <pulsecore/client.h> 34#include <pulsecore/sample-util.h> 35#include <pulsecore/namereg.h> 36#include <pulsecore/log.h> 37#include <pulsecore/core-error.h> 38#include <pulsecore/atomic.h> 39#include <pulsecore/thread-mq.h> 40#include <pulsecore/core-util.h> 41#include <pulsecore/shared.h> 42 43#include "protocol-simple.h" 44 45/* Don't allow more than this many concurrent connections */ 46#define MAX_CONNECTIONS 10 47 48typedef struct connection { 49 pa_msgobject parent; 50 pa_simple_protocol *protocol; 51 pa_simple_options *options; 52 pa_iochannel *io; 53 pa_sink_input *sink_input; 54 pa_source_output *source_output; 55 pa_client *client; 56 pa_memblockq *input_memblockq, *output_memblockq; 57 58 bool dead; 59 60 struct { 61 pa_memblock *current_memblock; 62 size_t memblock_index; 63 pa_atomic_t missing; 64 bool underrun; 65 } playback; 66} connection; 67 68PA_DEFINE_PRIVATE_CLASS(connection, pa_msgobject); 69#define CONNECTION(o) (connection_cast(o)) 70 71struct pa_simple_protocol { 72 PA_REFCNT_DECLARE; 73 74 pa_core *core; 75 pa_idxset *connections; 76}; 77 78enum { 79 SINK_INPUT_MESSAGE_POST_DATA = PA_SINK_INPUT_MESSAGE_MAX, /* data from main loop to sink input */ 80 SINK_INPUT_MESSAGE_DISABLE_PREBUF /* disabled prebuf, get playback started. */ 81}; 82 83enum { 84 CONNECTION_MESSAGE_REQUEST_DATA, /* data requested from sink input from the main loop */ 85 CONNECTION_MESSAGE_POST_DATA, /* data from source output to main loop */ 86 CONNECTION_MESSAGE_UNLINK_CONNECTION /* Please drop the connection now */ 87}; 88 89#define PLAYBACK_BUFFER_SECONDS (.5) 90#define PLAYBACK_BUFFER_FRAGMENTS (10) 91#define RECORD_BUFFER_SECONDS (5) 92#define DEFAULT_SINK_LATENCY (300*PA_USEC_PER_MSEC) 93#define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC) 94 95static void connection_unlink(connection *c) { 96 pa_assert(c); 97 98 if (!c->protocol) 99 return; 100 101 if (c->options) { 102 pa_simple_options_unref(c->options); 103 c->options = NULL; 104 } 105 106 if (c->sink_input) { 107 pa_sink_input_unlink(c->sink_input); 108 pa_sink_input_unref(c->sink_input); 109 c->sink_input = NULL; 110 } 111 112 if (c->source_output) { 113 pa_source_output_unlink(c->source_output); 114 pa_source_output_unref(c->source_output); 115 c->source_output = NULL; 116 } 117 118 if (c->client) { 119 pa_client_free(c->client); 120 c->client = NULL; 121 } 122 123 if (c->io) { 124 pa_iochannel_free(c->io); 125 c->io = NULL; 126 } 127 128 pa_idxset_remove_by_data(c->protocol->connections, c, NULL); 129 c->protocol = NULL; 130 connection_unref(c); 131} 132 133static void connection_free(pa_object *o) { 134 connection *c = CONNECTION(o); 135 pa_assert(c); 136 137 if (c->playback.current_memblock) 138 pa_memblock_unref(c->playback.current_memblock); 139 140 if (c->input_memblockq) 141 pa_memblockq_free(c->input_memblockq); 142 if (c->output_memblockq) 143 pa_memblockq_free(c->output_memblockq); 144 145 pa_xfree(c); 146} 147 148static int do_read(connection *c) { 149 pa_memchunk chunk; 150 ssize_t r; 151 size_t l; 152 void *p; 153 size_t space = 0; 154 155 connection_assert_ref(c); 156 157 if (!c->sink_input || (l = (size_t) pa_atomic_load(&c->playback.missing)) <= 0) 158 return 0; 159 160 if (c->playback.current_memblock) { 161 162 space = pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index; 163 164 if (space <= 0) { 165 pa_memblock_unref(c->playback.current_memblock); 166 c->playback.current_memblock = NULL; 167 } 168 } 169 170 if (!c->playback.current_memblock) { 171 pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, (size_t) -1)); 172 c->playback.memblock_index = 0; 173 174 space = pa_memblock_get_length(c->playback.current_memblock); 175 } 176 177 if (l > space) 178 l = space; 179 180 p = pa_memblock_acquire(c->playback.current_memblock); 181 r = pa_iochannel_read(c->io, (uint8_t*) p + c->playback.memblock_index, l); 182 pa_memblock_release(c->playback.current_memblock); 183 184 if (r <= 0) { 185 186 if (r < 0 && errno == EAGAIN) 187 return 0; 188 189 pa_log_debug("read(): %s", r == 0 ? "EOF" : pa_cstrerror(errno)); 190 return -1; 191 } 192 193 chunk.memblock = c->playback.current_memblock; 194 chunk.index = c->playback.memblock_index; 195 chunk.length = (size_t) r; 196 197 c->playback.memblock_index += (size_t) r; 198 199 pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, &chunk, NULL); 200 pa_atomic_sub(&c->playback.missing, (int) r); 201 202 return 0; 203} 204 205static int do_write(connection *c) { 206 pa_memchunk chunk; 207 ssize_t r; 208 void *p; 209 210 connection_assert_ref(c); 211 212 if (!c->source_output) 213 return 0; 214 215 if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) { 216/* pa_log("peek failed"); */ 217 return 0; 218 } 219 220 pa_assert(chunk.memblock); 221 pa_assert(chunk.length); 222 223 p = pa_memblock_acquire(chunk.memblock); 224 r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length); 225 pa_memblock_release(chunk.memblock); 226 227 pa_memblock_unref(chunk.memblock); 228 229 if (r < 0) { 230 pa_log("write(): %s", pa_cstrerror(errno)); 231 return -1; 232 } 233 234 pa_memblockq_drop(c->output_memblockq, (size_t) r); 235 236 return 1; 237} 238 239static void do_work(connection *c) { 240 connection_assert_ref(c); 241 242 if (c->dead) 243 return; 244 245 if (pa_iochannel_is_readable(c->io)) 246 if (do_read(c) < 0) 247 goto fail; 248 249 if (!c->sink_input && pa_iochannel_is_hungup(c->io)) 250 goto fail; 251 252 while (pa_iochannel_is_writable(c->io)) { 253 int r = do_write(c); 254 if (r < 0) 255 goto fail; 256 if (r == 0) 257 break; 258 } 259 260 return; 261 262fail: 263 264 if (c->sink_input) { 265 266 /* If there is a sink input, we first drain what we already have read before shutting down the connection */ 267 c->dead = true; 268 269 pa_iochannel_free(c->io); 270 c->io = NULL; 271 272 pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_DISABLE_PREBUF, NULL, 0, NULL, NULL); 273 } else 274 connection_unlink(c); 275} 276 277static int connection_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { 278 connection *c = CONNECTION(o); 279 connection_assert_ref(c); 280 281 if (!c->protocol) 282 return -1; 283 284 switch (code) { 285 case CONNECTION_MESSAGE_REQUEST_DATA: 286 do_work(c); 287 break; 288 289 case CONNECTION_MESSAGE_POST_DATA: 290/* pa_log("got data %u", chunk->length); */ 291 pa_memblockq_push_align(c->output_memblockq, chunk); 292 do_work(c); 293 break; 294 295 case CONNECTION_MESSAGE_UNLINK_CONNECTION: 296 connection_unlink(c); 297 break; 298 } 299 300 return 0; 301} 302 303/*** sink_input callbacks ***/ 304 305/* Called from thread context */ 306static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { 307 pa_sink_input *i = PA_SINK_INPUT(o); 308 connection*c; 309 310 pa_sink_input_assert_ref(i); 311 c = CONNECTION(i->userdata); 312 connection_assert_ref(c); 313 314 switch (code) { 315 316 case SINK_INPUT_MESSAGE_POST_DATA: { 317 pa_assert(chunk); 318 319 /* New data from the main loop */ 320 pa_memblockq_push_align(c->input_memblockq, chunk); 321 322 if (pa_memblockq_is_readable(c->input_memblockq) && c->playback.underrun) { 323 pa_log_debug("Requesting rewind due to end of underrun."); 324 pa_sink_input_request_rewind(c->sink_input, 0, false, true, false); 325 } 326 327/* pa_log("got data, %u", pa_memblockq_get_length(c->input_memblockq)); */ 328 329 return 0; 330 } 331 332 case SINK_INPUT_MESSAGE_DISABLE_PREBUF: 333 pa_memblockq_prebuf_disable(c->input_memblockq); 334 return 0; 335 336 case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { 337 pa_usec_t *r = userdata; 338 339 /* The default handler will add in the extra latency added by the resampler.*/ 340 *r = pa_bytes_to_usec(pa_memblockq_get_length(c->input_memblockq), &c->sink_input->sample_spec); 341 } 342 /* Fall through. */ 343 344 default: 345 return pa_sink_input_process_msg(o, code, userdata, offset, chunk); 346 } 347} 348 349/* Called from thread context */ 350static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { 351 connection *c; 352 353 pa_sink_input_assert_ref(i); 354 c = CONNECTION(i->userdata); 355 connection_assert_ref(c); 356 pa_assert(chunk); 357 358 if (pa_memblockq_peek(c->input_memblockq, chunk) < 0) { 359 360 c->playback.underrun = true; 361 362 if (c->dead && pa_sink_input_safe_to_remove(i)) 363 pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL); 364 365 return -1; 366 } else { 367 size_t m; 368 369 chunk->length = PA_MIN(length, chunk->length); 370 371 c->playback.underrun = false; 372 373 pa_memblockq_drop(c->input_memblockq, chunk->length); 374 m = pa_memblockq_pop_missing(c->input_memblockq); 375 376 if (m > 0) 377 if (pa_atomic_add(&c->playback.missing, (int) m) <= 0) 378 pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); 379 380 return 0; 381 } 382} 383 384/* Called from thread context */ 385static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { 386 connection *c; 387 388 pa_sink_input_assert_ref(i); 389 c = CONNECTION(i->userdata); 390 connection_assert_ref(c); 391 392 /* If we are in an underrun, then we don't rewind */ 393 if (i->thread_info.underrun_for > 0) 394 return; 395 396 pa_memblockq_rewind(c->input_memblockq, nbytes); 397} 398 399/* Called from thread context */ 400static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { 401 connection *c; 402 403 pa_sink_input_assert_ref(i); 404 c = CONNECTION(i->userdata); 405 connection_assert_ref(c); 406 407 pa_memblockq_set_maxrewind(c->input_memblockq, nbytes); 408} 409 410/* Called from main context */ 411static void sink_input_kill_cb(pa_sink_input *i) { 412 pa_sink_input_assert_ref(i); 413 414 connection_unlink(CONNECTION(i->userdata)); 415} 416 417/*** source_output callbacks ***/ 418 419/* Called from thread context */ 420static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { 421 connection *c; 422 423 pa_source_output_assert_ref(o); 424 c = CONNECTION(o->userdata); 425 pa_assert(c); 426 pa_assert(chunk); 427 428 pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); 429} 430 431/* Called from main context */ 432static void source_output_kill_cb(pa_source_output *o) { 433 pa_source_output_assert_ref(o); 434 435 connection_unlink(CONNECTION(o->userdata)); 436} 437 438/* Called from main context */ 439static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { 440 connection*c; 441 442 pa_source_output_assert_ref(o); 443 c = CONNECTION(o->userdata); 444 pa_assert(c); 445 446 return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec); 447} 448 449/*** client callbacks ***/ 450 451static void client_kill_cb(pa_client *client) { 452 connection*c; 453 454 pa_assert(client); 455 c = CONNECTION(client->userdata); 456 pa_assert(c); 457 458 connection_unlink(c); 459} 460 461/*** pa_iochannel callbacks ***/ 462 463static void io_callback(pa_iochannel*io, void *userdata) { 464 connection *c = CONNECTION(userdata); 465 466 connection_assert_ref(c); 467 pa_assert(io); 468 469 do_work(c); 470} 471 472/*** socket_server callbacks ***/ 473 474void pa_simple_protocol_connect(pa_simple_protocol *p, pa_iochannel *io, pa_simple_options *o) { 475 connection *c = NULL; 476 char pname[128]; 477 pa_client_new_data client_data; 478 479 pa_assert(p); 480 pa_assert(io); 481 pa_assert(o); 482 483 if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) { 484 pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS); 485 pa_iochannel_free(io); 486 return; 487 } 488 489 c = pa_msgobject_new(connection); 490 c->parent.parent.free = connection_free; 491 c->parent.process_msg = connection_process_msg; 492 c->io = io; 493 pa_iochannel_set_callback(c->io, io_callback, c); 494 495 c->sink_input = NULL; 496 c->source_output = NULL; 497 c->input_memblockq = c->output_memblockq = NULL; 498 c->protocol = p; 499 c->options = pa_simple_options_ref(o); 500 c->playback.current_memblock = NULL; 501 c->playback.memblock_index = 0; 502 c->dead = false; 503 c->playback.underrun = true; 504 pa_atomic_store(&c->playback.missing, 0); 505 506 pa_client_new_data_init(&client_data); 507 client_data.module = o->module; 508 client_data.driver = __FILE__; 509 pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname)); 510 pa_proplist_setf(client_data.proplist, PA_PROP_APPLICATION_NAME, "Simple client (%s)", pname); 511 pa_proplist_sets(client_data.proplist, "simple-protocol.peer", pname); 512 c->client = pa_client_new(p->core, &client_data); 513 pa_client_new_data_done(&client_data); 514 515 if (!c->client) 516 goto fail; 517 518 c->client->kill = client_kill_cb; 519 c->client->userdata = c; 520 521 if (o->playback) { 522 pa_sink_input_new_data data; 523 pa_memchunk silence; 524 size_t l; 525 pa_sink *sink; 526 527 if (!(sink = pa_namereg_get(c->protocol->core, o->default_sink, PA_NAMEREG_SINK))) { 528 pa_log("Failed to get sink."); 529 goto fail; 530 } 531 532 pa_sink_input_new_data_init(&data); 533 data.driver = __FILE__; 534 data.module = o->module; 535 data.client = c->client; 536 pa_sink_input_new_data_set_sink(&data, sink, false, true); 537 pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); 538 pa_sink_input_new_data_set_sample_spec(&data, &o->sample_spec); 539 540 pa_sink_input_new(&c->sink_input, p->core, &data); 541 pa_sink_input_new_data_done(&data); 542 543 if (!c->sink_input) { 544 pa_log("Failed to create sink input."); 545 goto fail; 546 } 547 548 c->sink_input->parent.process_msg = sink_input_process_msg; 549 c->sink_input->pop = sink_input_pop_cb; 550 c->sink_input->process_rewind = sink_input_process_rewind_cb; 551 c->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; 552 c->sink_input->kill = sink_input_kill_cb; 553 c->sink_input->userdata = c; 554 555 pa_sink_input_set_requested_latency(c->sink_input, DEFAULT_SINK_LATENCY); 556 557 l = (size_t) ((double) pa_bytes_per_second(&o->sample_spec)*PLAYBACK_BUFFER_SECONDS); 558 pa_sink_input_get_silence(c->sink_input, &silence); 559 c->input_memblockq = pa_memblockq_new( 560 "simple protocol connection input_memblockq", 561 0, 562 l, 563 l, 564 &o->sample_spec, 565 (size_t) -1, 566 l/PLAYBACK_BUFFER_FRAGMENTS, 567 0, 568 &silence); 569 pa_memblock_unref(silence.memblock); 570 571 pa_iochannel_socket_set_rcvbuf(io, l); 572 573 pa_atomic_store(&c->playback.missing, (int) pa_memblockq_pop_missing(c->input_memblockq)); 574 575 pa_sink_input_put(c->sink_input); 576 } 577 578 if (o->record) { 579 pa_source_output_new_data data; 580 size_t l; 581 pa_source *source; 582 583 if (!(source = pa_namereg_get(c->protocol->core, o->default_source, PA_NAMEREG_SOURCE))) { 584 pa_log("Failed to get source."); 585 goto fail; 586 } 587 588 pa_source_output_new_data_init(&data); 589 data.driver = __FILE__; 590 data.module = o->module; 591 data.client = c->client; 592 pa_source_output_new_data_set_source(&data, source, false, true); 593 pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); 594 pa_source_output_new_data_set_sample_spec(&data, &o->sample_spec); 595 596 pa_source_output_new(&c->source_output, p->core, &data); 597 pa_source_output_new_data_done(&data); 598 599 if (!c->source_output) { 600 pa_log("Failed to create source output."); 601 goto fail; 602 } 603 c->source_output->push = source_output_push_cb; 604 c->source_output->kill = source_output_kill_cb; 605 c->source_output->get_latency = source_output_get_latency_cb; 606 c->source_output->userdata = c; 607 608 pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY); 609 610 l = (size_t) (pa_bytes_per_second(&o->sample_spec)*RECORD_BUFFER_SECONDS); 611 c->output_memblockq = pa_memblockq_new( 612 "simple protocol connection output_memblockq", 613 0, 614 l, 615 0, 616 &o->sample_spec, 617 1, 618 0, 619 0, 620 NULL); 621 pa_iochannel_socket_set_sndbuf(io, l); 622 623 pa_source_output_put(c->source_output); 624 } 625 626 pa_idxset_put(p->connections, c, NULL); 627 628 return; 629 630fail: 631 connection_unlink(c); 632} 633 634void pa_simple_protocol_disconnect(pa_simple_protocol *p, pa_module *m) { 635 connection *c; 636 void *state = NULL; 637 638 pa_assert(p); 639 pa_assert(m); 640 641 while ((c = pa_idxset_iterate(p->connections, &state, NULL))) 642 if (c->options->module == m) 643 connection_unlink(c); 644} 645 646static pa_simple_protocol* simple_protocol_new(pa_core *c) { 647 pa_simple_protocol *p; 648 649 pa_assert(c); 650 651 p = pa_xnew(pa_simple_protocol, 1); 652 PA_REFCNT_INIT(p); 653 p->core = c; 654 p->connections = pa_idxset_new(NULL, NULL); 655 656 pa_assert_se(pa_shared_set(c, "simple-protocol", p) >= 0); 657 658 return p; 659} 660 661pa_simple_protocol* pa_simple_protocol_get(pa_core *c) { 662 pa_simple_protocol *p; 663 664 if ((p = pa_shared_get(c, "simple-protocol"))) 665 return pa_simple_protocol_ref(p); 666 667 return simple_protocol_new(c); 668} 669 670pa_simple_protocol* pa_simple_protocol_ref(pa_simple_protocol *p) { 671 pa_assert(p); 672 pa_assert(PA_REFCNT_VALUE(p) >= 1); 673 674 PA_REFCNT_INC(p); 675 676 return p; 677} 678 679void pa_simple_protocol_unref(pa_simple_protocol *p) { 680 connection *c; 681 pa_assert(p); 682 pa_assert(PA_REFCNT_VALUE(p) >= 1); 683 684 if (PA_REFCNT_DEC(p) > 0) 685 return; 686 687 while ((c = pa_idxset_first(p->connections, NULL))) 688 connection_unlink(c); 689 690 pa_idxset_free(p->connections, NULL); 691 692 pa_assert_se(pa_shared_remove(p->core, "simple-protocol") >= 0); 693 694 pa_xfree(p); 695} 696 697pa_simple_options* pa_simple_options_new(void) { 698 pa_simple_options *o; 699 700 o = pa_xnew0(pa_simple_options, 1); 701 PA_REFCNT_INIT(o); 702 703 o->record = false; 704 o->playback = true; 705 706 return o; 707} 708 709pa_simple_options* pa_simple_options_ref(pa_simple_options *o) { 710 pa_assert(o); 711 pa_assert(PA_REFCNT_VALUE(o) >= 1); 712 713 PA_REFCNT_INC(o); 714 715 return o; 716} 717 718void pa_simple_options_unref(pa_simple_options *o) { 719 pa_assert(o); 720 pa_assert(PA_REFCNT_VALUE(o) >= 1); 721 722 if (PA_REFCNT_DEC(o) > 0) 723 return; 724 725 pa_xfree(o->default_sink); 726 pa_xfree(o->default_source); 727 728 pa_xfree(o); 729} 730 731int pa_simple_options_parse(pa_simple_options *o, pa_core *c, pa_modargs *ma) { 732 bool enabled; 733 734 pa_assert(o); 735 pa_assert(PA_REFCNT_VALUE(o) >= 1); 736 pa_assert(ma); 737 738 o->sample_spec = c->default_sample_spec; 739 if (pa_modargs_get_sample_spec_and_channel_map(ma, &o->sample_spec, &o->channel_map, PA_CHANNEL_MAP_DEFAULT) < 0) { 740 pa_log("Failed to parse sample type specification."); 741 return -1; 742 } 743 744 pa_xfree(o->default_source); 745 o->default_source = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL)); 746 747 pa_xfree(o->default_sink); 748 o->default_sink = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); 749 750 enabled = o->record; 751 if (pa_modargs_get_value_boolean(ma, "record", &enabled) < 0) { 752 pa_log("record= expects a boolean argument."); 753 return -1; 754 } 755 o->record = enabled; 756 757 enabled = o->playback; 758 if (pa_modargs_get_value_boolean(ma, "playback", &enabled) < 0) { 759 pa_log("playback= expects a boolean argument."); 760 return -1; 761 } 762 o->playback = enabled; 763 764 if (!o->playback && !o->record) { 765 pa_log("neither playback nor recording enabled for protocol."); 766 return -1; 767 } 768 769 return 0; 770} 771