The implemented logic is copied from module-alsa-card. Now the same logic works across all card implementations, not just ALSA. The policy in short: the goal is to keep streams connected to the same card before and after a profile switch. --- src/modules/module-skoa-router.c | 84 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/src/modules/module-skoa-router.c b/src/modules/module-skoa-router.c index 9406fef..32dafda 100644 --- a/src/modules/module-skoa-router.c +++ b/src/modules/module-skoa-router.c @@ -25,6 +25,7 @@ #include "module-skoa-router-symdef.h" +#include <pulsecore/device-prototype.h> #include <pulsecore/i18n.h> #include <pulsecore/module.h> #include <pulsecore/router.h> @@ -38,6 +39,87 @@ struct userdata { pa_router *router; }; +static int router_set_card_profile_cb(pa_router *router, pa_card *card, pa_card_profile *profile, bool save) { + pa_device_prototype *prototype; + void *state; + pa_queue *sink_inputs = NULL; + pa_queue *source_outputs = NULL; + int r; + + pa_assert(router); + pa_assert(card); + pa_assert(profile); + + /* When the profile changes, the sinks and sources of the old profile are + * removed (at least if they aren't part of the new profile too), and this + * raises the question what happens to streams that are connected to the + * removed devices. Here we implement a policy to move those streams to the + * devices of the new profile if possible. It's unclear whether this is + * actually a good policy, but that's what we have historically been doing + * (previously in the ALSA card code), and nothing better has been + * proposed. */ + + PA_HASHMAP_FOREACH(prototype, card->active_profile->device_prototypes, state) { + if (!card->recreate_devices_on_profile_switch && pa_hashmap_get(profile->device_prototypes, prototype)) + /* This device is a part of both the old and new profile, so + * there's no need to rescue any streams from this device. */ + continue; + + if (prototype->sink) { + sink_inputs = pa_sink_move_all_start(prototype->sink, sink_inputs); + + if (!pa_queue_isempty(sink_inputs)) + pa_log_debug("Started to move sink inputs away from sink %s.", prototype->sink->name); + } + + if (prototype->source) { + source_outputs = pa_source_move_all_start(prototype->source, source_outputs); + + if (!pa_queue_isempty(source_outputs)) + pa_log_debug("Started to move source outputs away from source %s.", prototype->source->name); + } + } + + r = pa_card_set_profile(card, profile, save, true); + + if (r >= 0 && (sink_inputs || source_outputs)) { + PA_HASHMAP_FOREACH(prototype, profile->device_prototypes, state) { + if (prototype->sink && sink_inputs && !pa_queue_isempty(sink_inputs)) { + pa_sink_move_all_finish(prototype->sink, sink_inputs, false); + pa_log_debug("Finished moving sink inputs to sink %s.", prototype->sink->name); + sink_inputs = NULL; + } + + if (prototype->source && source_outputs && !pa_queue_isempty(source_outputs)) { + pa_source_move_all_finish(prototype->source, source_outputs, false); + pa_log_debug("Finished moving source outputs to source %s.", prototype->source->name); + source_outputs = NULL; + } + + if (!sink_inputs && !source_outputs) + break; + } + } + + if (sink_inputs) { + if (!pa_queue_isempty(sink_inputs)) + pa_log_debug("Profile change failed or there are no sinks in the new profile, " + "have to fail the sink input rescue operation."); + + pa_sink_move_all_fail(sink_inputs); + } + + if (source_outputs) { + if (!pa_queue_isempty(source_outputs)) + pa_log_debug("Profile change failed or there are no sources in the new profile, " + "have to fail the source output rescue operation."); + + pa_source_move_all_fail(source_outputs); + } + + return r; +} + int pa__init(pa_module *module) { struct userdata *u; int r; @@ -50,6 +132,8 @@ int pa__init(pa_module *module) { if (!u->router) goto fail; + u->router->set_card_profile = router_set_card_profile_cb; + r = pa_router_put(u->router); if (r < 0) -- 1.8.3.1