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