diff options
-rw-r--r-- | src/modules/module-filter-apply.c | 27 | ||||
-rw-r--r-- | src/modules/module-filter-heuristics.c | 63 | ||||
-rw-r--r-- | src/pulse/proplist.h | 5 |
3 files changed, 79 insertions, 16 deletions
diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c index c89897112..faa918bf0 100644 --- a/src/modules/module-filter-apply.c +++ b/src/modules/module-filter-apply.c @@ -37,6 +37,7 @@ #include "module-filter-apply-symdef.h" +#define PA_PROP_FILTER_APPLY_MOVING "filter.apply.moving" PA_MODULE_AUTHOR("Colin Guthrie"); PA_MODULE_DESCRIPTION("Load filter sinks automatically when needed"); @@ -64,6 +65,7 @@ struct userdata { pa_hashmap *filters; pa_hook_slot *sink_input_put_slot, + *sink_input_move_finish_slot, *sink_input_proplist_slot, *sink_input_unlink_slot, *sink_unlink_slot; @@ -110,14 +112,14 @@ static void filter_free(struct filter *f) { } static const char* should_filter(pa_sink_input *i) { - const char *want; + const char *apply; /* If the stream doesn't what any filter, then let it be. */ - if ((want = pa_proplist_gets(i->proplist, PA_PROP_FILTER_WANT)) && !pa_streq(want, "")) { + if ((apply = pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY)) && !pa_streq(apply, "")) { const char* suppress = pa_proplist_gets(i->proplist, PA_PROP_FILTER_SUPPRESS); - if (!suppress || !pa_streq(suppress, want)) - return want; + if (!suppress || !pa_streq(suppress, apply)) + return apply; } return NULL; @@ -171,12 +173,16 @@ static void move_input_for_filter(pa_sink_input *i, struct filter* filter, pa_bo pa_assert_se(sink = (restore ? filter->parent_sink : filter->sink)); + pa_proplist_sets(i->proplist, PA_PROP_FILTER_APPLY_MOVING, "1"); + if (pa_sink_input_move_to(i, sink, FALSE) < 0) pa_log_info("Failed to move sink input %u \"%s\" to <%s>.", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), sink->name); else pa_log_info("Sucessfully moved sink input %u \"%s\" to <%s>.", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), sink->name); + + pa_proplist_unset(i->proplist, PA_PROP_FILTER_APPLY_MOVING); } static pa_hook_result_t process(struct userdata *u, pa_sink_input *i) { @@ -281,6 +287,16 @@ static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struc return process(u, i); } +static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING)) + return PA_HOOK_OK; + + return process(u, i); +} + static pa_hook_result_t sink_input_proplist_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { pa_core_assert_ref(core); pa_sink_input_assert_ref(i); @@ -359,6 +375,7 @@ int pa__init(pa_module *m) { u->filters = pa_hashmap_new(filter_hash, filter_compare); u->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_put_cb, u); + u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_finish_cb, u); u->sink_input_proplist_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_proplist_cb, u); u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_unlink_cb, u); u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_unlink_cb, u); @@ -386,6 +403,8 @@ void pa__done(pa_module *m) { if (u->sink_input_put_slot) pa_hook_slot_free(u->sink_input_put_slot); + if (u->sink_input_move_finish_slot) + pa_hook_slot_free(u->sink_input_move_finish_slot); if (u->sink_input_proplist_slot) pa_hook_slot_free(u->sink_input_proplist_slot); if (u->sink_input_unlink_slot) diff --git a/src/modules/module-filter-heuristics.c b/src/modules/module-filter-heuristics.c index a385ff246..20a48355d 100644 --- a/src/modules/module-filter-heuristics.c +++ b/src/modules/module-filter-heuristics.c @@ -33,6 +33,8 @@ #include "module-filter-heuristics-symdef.h" +#define PA_PROP_FILTER_APPLY_MOVING "filter.apply.moving" +#define PA_PROP_FILTER_HEURISTICS "filter.heuristics" PA_MODULE_AUTHOR("Colin Guthrie"); PA_MODULE_DESCRIPTION("Detect when various filters are desirable"); @@ -46,27 +48,63 @@ static const char* const valid_modargs[] = { struct userdata { pa_core *core; pa_hook_slot - *sink_input_put_slot; + *sink_input_put_slot, + *sink_input_move_finish_slot; }; -static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { - const char *sink_role, *si_role; +static pa_hook_result_t process(struct userdata *u, pa_sink_input *i) { + const char *want, *sink_role, *si_role; + + /* If the stream already specifies what it must have, then let it be. */ + if (!pa_proplist_gets(i->proplist, PA_PROP_FILTER_HEURISTICS) && pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY)) + return PA_HOOK_OK; + + want = pa_proplist_gets(i->proplist, PA_PROP_FILTER_WANT); + if (!want) { + /* This is a phone stream, maybe we want echo cancellation */ + if ((si_role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE)) && pa_streq(si_role, "phone")) + want = "echo-cancel"; + } + + /* On phone sinks, make sure we're not applying echo cancellation */ + if ((sink_role = pa_proplist_gets(i->sink->proplist, PA_PROP_DEVICE_INTENDED_ROLES)) && strstr(sink_role, "phone")) { + const char *apply = pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY); + + if (apply && pa_streq(apply, "echo-cancel")) { + pa_proplist_unset(i->proplist, PA_PROP_FILTER_APPLY); + pa_proplist_unset(i->proplist, PA_PROP_FILTER_HEURISTICS); + } + + return PA_HOOK_OK; + } + + if (want) { + /* There's a filter that we want, ask module-filter-apply to apply it, and remember that we're managing filter.apply */ + pa_proplist_sets(i->proplist, PA_PROP_FILTER_APPLY, want); + pa_proplist_sets(i->proplist, PA_PROP_FILTER_HEURISTICS, "1"); + } + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { pa_core_assert_ref(core); pa_sink_input_assert_ref(i); pa_assert(u); - /* If the stream already specifies what it wants, then let it be. */ - if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_WANT)) - return PA_HOOK_OK; + return process(u, i); +} - if ((sink_role = pa_proplist_gets(i->sink->proplist, PA_PROP_DEVICE_INTENDED_ROLES)) && strstr(sink_role, "phone")) - return PA_HOOK_OK; +static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + pa_assert(u); - if ((si_role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE)) && pa_streq(si_role, "phone")) - pa_proplist_sets(i->proplist, PA_PROP_FILTER_WANT, "echo-cancel"); + /* module-filter-apply triggered this move, ignore */ + if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING)) + return PA_HOOK_OK; - return PA_HOOK_OK; + return process(u, i); } int pa__init(pa_module *m) { @@ -85,6 +123,7 @@ int pa__init(pa_module *m) { u->core = m->core; u->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE-1, (pa_hook_cb_t) sink_input_put_cb, u); + u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE-1, (pa_hook_cb_t) sink_input_move_finish_cb, u); pa_modargs_free(ma); @@ -111,6 +150,8 @@ void pa__done(pa_module *m) { if (u->sink_input_put_slot) pa_hook_slot_free(u->sink_input_put_slot); + if (u->sink_input_move_finish_slot) + pa_hook_slot_free(u->sink_input_move_finish_slot); pa_xfree(u); diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h index 4670d61c9..7d0269978 100644 --- a/src/pulse/proplist.h +++ b/src/pulse/proplist.h @@ -62,9 +62,12 @@ PA_C_DECL_BEGIN /** For streams: logic role of this media. One of the strings "video", "music", "game", "event", "phone", "animation", "production", "a11y", "test" */ #define PA_PROP_MEDIA_ROLE "media.role" -/** For streams: the name of a filter that is desired, e.g. "echo-cancel" or "equalizer-sink" \since 1.0 */ +/** For streams: the name of a filter that is desired, e.g. "echo-cancel" or "equalizer-sink". PulseAudio may choose to not apply the filter if it does not make sense (for example, applying echo-cancellation on a Bluetooth headset probably does not make sense. \since 1.0 */ #define PA_PROP_FILTER_WANT "filter.want" +/** For streams: the name of a filter that is desired, e.g. "echo-cancel" or "equalizer-sink". Differs from PA_PROP_FILTER_WANT in that it forces PulseAudio to apply the filter, regardless of whether PulseAudio thinks it makes sense to do so or not. If this is set, PA_PROP_FILTER_WANT is ignored. In other words, you almost certainly do not want to use this. \since 1.0 */ +#define PA_PROP_FILTER_APPLY "filter.apply" + /** For streams: the name of a filter that should specifically suppressed (i.e. overrides PA_PROP_FILTER_WANT). Useful for the times that PA_PROP_FILTER_WANT is automatically added (e.g. echo-cancellation for phone streams when $VOIP_APP does it's own, internal AEC) \since 1.0 */ #define PA_PROP_FILTER_SUPPRESS "filter.suppress" |