filters: Handle stream moves properly

This makes sure that we handle streams moving between sinks properly. To
do this, we change the way the filter.* properties are handled a little
bit.

Firstly, this splits up the "filter.apply" property into two properties
- "filter.want" and "filter.apply". "filter.apply" acts as before - it
bypasses module-filter-heuristics and directly tells module-filter-apply
what filters are to be applied.

"filter.want" is used to tell module-filter-heuristics what filters the
client wants. The module then decides whether to actually apply the
filter or not (for now, this makes sure we don't apply echo-cancellation
even if requested on phone sinks (where it is assumed AEC is taken care
of or is not required).

Next, we also make sure that we track whether the client set
"filter.apply" or module-filter-heuristics did - and in the latter case,
we recalculate "filter.apply" and then have module-filter-apply apply
the filter if required. This introduces some evil in the form of causing
the move_finish callback to possibly trigger another move, but we
protect for this case (with a property) to be doubly sure of not causing
an infinite loop.
This commit is contained in:
Arun Raghavan 2011-04-21 12:52:27 +05:30 committed by Colin Guthrie
parent 42b378b130
commit ceca42f706
3 changed files with 80 additions and 17 deletions

View file

@ -37,6 +37,7 @@
#include "module-filter-apply-symdef.h" #include "module-filter-apply-symdef.h"
#define PA_PROP_FILTER_APPLY_MOVING "filter.apply.moving"
PA_MODULE_AUTHOR("Colin Guthrie"); PA_MODULE_AUTHOR("Colin Guthrie");
PA_MODULE_DESCRIPTION("Load filter sinks automatically when needed"); PA_MODULE_DESCRIPTION("Load filter sinks automatically when needed");
@ -64,6 +65,7 @@ struct userdata {
pa_hashmap *filters; pa_hashmap *filters;
pa_hook_slot pa_hook_slot
*sink_input_put_slot, *sink_input_put_slot,
*sink_input_move_finish_slot,
*sink_input_proplist_slot, *sink_input_proplist_slot,
*sink_input_unlink_slot, *sink_input_unlink_slot,
*sink_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) { 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 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); const char* suppress = pa_proplist_gets(i->proplist, PA_PROP_FILTER_SUPPRESS);
if (!suppress || !pa_streq(suppress, want)) if (!suppress || !pa_streq(suppress, apply))
return want; return apply;
} }
return NULL; 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_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) 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_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); pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), sink->name);
else else
pa_log_info("Sucessfully moved sink input %u \"%s\" to <%s>.", i->index, 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_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) { 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); 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) { 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_core_assert_ref(core);
pa_sink_input_assert_ref(i); 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->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_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_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_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); 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) if (u->sink_input_put_slot)
pa_hook_slot_free(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) if (u->sink_input_proplist_slot)
pa_hook_slot_free(u->sink_input_proplist_slot); pa_hook_slot_free(u->sink_input_proplist_slot);
if (u->sink_input_unlink_slot) if (u->sink_input_unlink_slot)

View file

@ -33,6 +33,8 @@
#include "module-filter-heuristics-symdef.h" #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_AUTHOR("Colin Guthrie");
PA_MODULE_DESCRIPTION("Detect when various filters are desirable"); PA_MODULE_DESCRIPTION("Detect when various filters are desirable");
@ -46,27 +48,63 @@ static const char* const valid_modargs[] = {
struct userdata { struct userdata {
pa_core *core; pa_core *core;
pa_hook_slot 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) { static pa_hook_result_t process(struct userdata *u, pa_sink_input *i) {
const char *sink_role, *si_role; 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_core_assert_ref(core);
pa_sink_input_assert_ref(i); pa_sink_input_assert_ref(i);
pa_assert(u); pa_assert(u);
/* If the stream already specifies what it wants, then let it be. */ return process(u, i);
if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_WANT)) }
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);
/* 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;
if ((sink_role = pa_proplist_gets(i->sink->proplist, PA_PROP_DEVICE_INTENDED_ROLES)) && strstr(sink_role, "phone")) return process(u, i);
return PA_HOOK_OK;
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");
return PA_HOOK_OK;
} }
int pa__init(pa_module *m) { int pa__init(pa_module *m) {
@ -85,6 +123,7 @@ int pa__init(pa_module *m) {
u->core = m->core; 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_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); pa_modargs_free(ma);
@ -111,6 +150,8 @@ void pa__done(pa_module *m) {
if (u->sink_input_put_slot) if (u->sink_input_put_slot)
pa_hook_slot_free(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); pa_xfree(u);

View file

@ -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" */ /** 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" #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" #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 */ /** 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" #define PA_PROP_FILTER_SUPPRESS "filter.suppress"