--- src/modules/module-combine-sink.c | 92 +++++++++++++++++++++++++++++++++++++++ src/pulsecore/cli-command.c | 78 +++++++++++++++++++++++++++++++++ src/pulsecore/sink.c | 32 ++++++++++++++ src/pulsecore/sink.h | 8 ++++ 4 files changed, 210 insertions(+) diff --git a/src/modules/module-combine-sink.c b/src/modules/module-combine-sink.c index b6322c6..4a91901 100644 --- a/src/modules/module-combine-sink.c +++ b/src/modules/module-combine-sink.c @@ -1249,6 +1249,95 @@ static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struc return PA_HOOK_OK; } +void update_slaves_prop(pa_sink *combine_sink, struct userdata *u) { + + uint32_t idx; + char *t; + bool first = true; + struct output *o; + pa_proplist *pl; + + PA_IDXSET_FOREACH(o, u->outputs, idx) { + char *e; + if (first) { + e = pa_sprintf_malloc("%s", pa_strnull(o->sink->name)); + first = false; + } else { + e = pa_sprintf_malloc("%s, %s", t, pa_strnull(o->sink->name)); + pa_xfree(t); + } + t = e; + } + /* if still first we have no outputs in the list */ + if (first) { + t = pa_sprintf_malloc(""); + } + + pl = pa_proplist_new(); + pa_proplist_setf(pl, "combine.slaves", t); + pa_xfree(t); + pa_sink_update_proplist(combine_sink, PA_UPDATE_REPLACE, pl); + pa_proplist_free(pl); +} + +/* Called from main context on native API call */ +static int add_output(pa_sink *combine_sink, pa_sink *slave_sink) { + struct userdata *u; + struct output *o; + + pa_sink_assert_ref(combine_sink); + pa_sink_assert_ref(slave_sink); + pa_assert_se(u = combine_sink->userdata); + + if (u->automatic) { + pa_log("combine sink is automatic, cannot add: '%s'.", slave_sink->name); + return -2; + } + + if ((o = find_output(u, slave_sink))) { + pa_log("Sink already on combine sink: '%s'.", slave_sink->name); + return -3; + } + + if (!(o = output_new(u, slave_sink))) { + pa_log("Failed to add sink to combine sink: '%s'.", slave_sink->name); + return -4; + } + output_verify(o); + update_slaves_prop(combine_sink, u); + + pa_log("Add output sink"); + return 0; +} + +/* Called from main context on native API call */ +static int del_output(pa_sink *combine_sink, pa_sink *slave_sink) { + struct userdata *u; + struct output *o; + + pa_sink_assert_ref(combine_sink); + pa_sink_assert_ref(slave_sink); + pa_assert_se(u = combine_sink->userdata); + + if (u->automatic) { + pa_log("combine sink is automatic, cannot del: '%s'.", slave_sink->name); + return -2; + } + + if (!(o = find_output(u, slave_sink))) { + pa_log("Could not remove %s from combine sink, output was not found", + slave_sink->name); + return -3; + } + + pa_idxset_remove_by_data(u->outputs, o, NULL); + output_free(o); + update_description(u); + update_slaves_prop(combine_sink, u); + pa_log("Del output sink"); + return 0; +} + int pa__init(pa_module*m) { struct userdata *u; pa_modargs *ma = NULL; @@ -1401,6 +1490,9 @@ int pa__init(pa_module*m) { u->sink->set_state = sink_set_state; u->sink->update_requested_latency = sink_update_requested_latency; u->sink->userdata = u; + u->sink->module = m; + u->sink->combine_add_output = add_output; + u->sink->combine_del_output = del_output; pa_sink_set_rtpoll(u->sink, u->rtpoll); pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c index 9a73605..ba168d5 100644 --- a/src/pulsecore/cli-command.c +++ b/src/pulsecore/cli-command.c @@ -135,6 +135,8 @@ static int pa_cli_command_sink_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, static int pa_cli_command_source_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); static int pa_cli_command_port_offset(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); static int pa_cli_command_dump_volumes(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_combine_sink_add_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_combine_sink_del_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); /* A method table for all available commands */ @@ -191,6 +193,8 @@ static const struct command commands[] = { { "set-log-meta", pa_cli_command_log_meta, "Show source code location in log messages (args: bool)", 2}, { "set-log-time", pa_cli_command_log_time, "Show timestamps in log messages (args: bool)", 2}, { "set-log-backtrace", pa_cli_command_log_backtrace, "Show backtrace in log messages (args: frames)", 2}, + { "combine-sink-add-output", pa_cli_command_combine_sink_add_output, "Add a output sink to the specified combine sink (args: ?, ?", 3}, + { "combine-sink-del-output", pa_cli_command_combine_sink_del_output, "Delete a output sink from the specified combine sink (args: ?, ?", 3}, { "play-file", pa_cli_command_play_file, "Play a sound file (args: filename, sink|index)", 3}, { "dump", pa_cli_command_dump, "Dump daemon configuration", 1}, { "dump-volumes", pa_cli_command_dump_volumes, "Debug: Show the state of all volumes", 1 }, @@ -1992,6 +1996,80 @@ static int pa_cli_command_dump_volumes(pa_core *c, pa_tokenizer *t, pa_strbuf *b return 0; } +int pa_cli_command_combine_sink_add_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *cs, *os; + pa_sink *combine_sink, *out_sink; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(cs = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a combine sink either by its name or its index.\n"); + return -1; + } + + if (!(os = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify an output sink either by its name or its index.\n"); + return -1; + } + + if (!(combine_sink = pa_namereg_get(c, cs, PA_NAMEREG_SINK))) { + pa_strbuf_puts(buf, "No combine sink found by this name or index.\n"); + return -1; + } + + if (!(out_sink = pa_namereg_get(c, os, PA_NAMEREG_SINK))) { + pa_strbuf_puts(buf, "No output sink found by this name or index.\n"); + return -1; + } + + if (pa_sink_combine_add_output(combine_sink, out_sink) < 0) { + pa_strbuf_puts(buf, "Add to combine sink failed!\n"); + return -1; + } + + return 0; +} + +int pa_cli_command_combine_sink_del_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *cs, *os; + pa_sink *combine_sink, *out_sink; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(cs = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a combine sink either by its name or its index.\n"); + return -1; + } + + if (!(os = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify an output sink either by its name or its index.\n"); + return -1; + } + + if (!(combine_sink = pa_namereg_get(c, cs, PA_NAMEREG_SINK))) { + pa_strbuf_puts(buf, "No combine sink found by this name or index.\n"); + return -1; + } + + if (!(out_sink = pa_namereg_get(c, os, PA_NAMEREG_SINK))) { + pa_strbuf_puts(buf, "No output sink found by this name or index.\n"); + return -1; + } + + if (pa_sink_combine_del_output(combine_sink, out_sink) < 0) { + pa_strbuf_puts(buf, "Delete from combine sink failed!\n"); + return -1; + } + + return 0; +} + int pa_cli_command_execute_line_stateful(pa_core *c, const char *s, pa_strbuf *buf, bool *fail, int *ifstate) { const char *cs; diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 5c6a9c6..6379504 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -3835,3 +3835,35 @@ void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume) { pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], s); } + +int pa_sink_combine_add_output(pa_sink *combine_sink, pa_sink *slave_sink) { + int r; + + pa_assert_ctl_context(); + pa_assert(combine_sink); + pa_assert(slave_sink); + + if (combine_sink->combine_add_output != NULL) { + r = combine_sink->combine_add_output(combine_sink, slave_sink); + return r; + } else { + return -1; + } + + return 0; +} + +int pa_sink_combine_del_output(pa_sink *combine_sink, pa_sink *slave_sink) { + int r; + + pa_assert_ctl_context(); + pa_assert(combine_sink); + pa_assert(slave_sink); + + if (combine_sink->combine_del_output != NULL) { + r = combine_sink->combine_del_output(combine_sink, slave_sink); + return r; + } else { + return -1; + } +} diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index c549869..756a0f6 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -248,6 +248,11 @@ struct pa_sink { * main thread. */ int (*update_rate)(pa_sink *s, uint32_t rate); + /* Called for manual combine sink change. Must not be NULL for a combine + * sink. Can be NULL for all other sink types. */ + int (*combine_add_output)(pa_sink *combine_sink, pa_sink *slave_sink); + int (*combine_del_output)(pa_sink *combine_sink, pa_sink *slave_sink); + /* Contains copies of the above data so that the real-time worker * thread can work without access locking */ struct { @@ -528,6 +533,9 @@ pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s); * s->reference_volume and fires change notifications. */ void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume); +int pa_sink_combine_add_output(pa_sink *combine_sink, pa_sink *slave_sink); +int pa_sink_combine_del_output(pa_sink *combine_sink, pa_sink *slave_sink); + /* Verify that we called in IO context (aka 'thread context), or that * the sink is not yet set up, i.e. the thread not set up yet. See * pa_assert_io_context() in thread-mq.h for more information. */ -- 1.9.1