Currently works on bluetooth cards based on existing and incoming streams' media roles. --- src/Makefile.am | 7 ++ src/daemon/default.pa.in | 3 + src/modules/module-profile-switcher.c | 192 +++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 0 deletions(-) create mode 100644 src/modules/module-profile-switcher.c diff --git a/src/Makefile.am b/src/Makefile.am index cc3e13b..58135a0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -990,6 +990,7 @@ modlibexec_LTLIBRARIES += \ module-always-sink.la \ module-rescue-streams.la \ module-intended-roles.la \ + module-profile-switcher.la \ module-suspend-on-idle.la \ module-echo-cancel.la \ module-http-protocol-tcp.la \ @@ -1286,6 +1287,7 @@ SYMDEF_FILES = \ module-always-sink-symdef.h \ module-rescue-streams-symdef.h \ module-intended-roles-symdef.h \ + module-profile-switcher-symdef.h \ module-suspend-on-idle-symdef.h \ module-echo-cancel-symdef.h \ module-hal-detect-symdef.h \ @@ -1734,6 +1736,11 @@ module_intended_roles_la_LDFLAGS = $(MODULE_LDFLAGS) module_intended_roles_la_LIBADD = $(MODULE_LIBADD) module_intended_roles_la_CFLAGS = $(AM_CFLAGS) +# Switch profiles based on (role) heuristics +module_profile_switcher_la_SOURCES = modules/module-profile-switcher.c +module_profile_switcher_la_LDFLAGS = $(MODULE_LDFLAGS) +module_profile_switcher_la_LIBADD = $(MODULE_LIBADD) + # Suspend-on-idle module module_suspend_on_idle_la_SOURCES = modules/module-suspend-on-idle.c module_suspend_on_idle_la_LDFLAGS = $(MODULE_LDFLAGS) diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in index f2f22e8..7cf2416 100755 --- a/src/daemon/default.pa.in +++ b/src/daemon/default.pa.in @@ -163,6 +163,9 @@ load-module module-role-cork ### loading modules and rerouting streams. load-module module-filter-heuristics load-module module-filter-apply + +### Module to switch profiles based on heuristics +load-module module-profile-switcher ])dnl ifelse(@HAVE_DBUS@, 1, [dnl diff --git a/src/modules/module-profile-switcher.c b/src/modules/module-profile-switcher.c new file mode 100644 index 0000000..baccf67 --- /dev/null +++ b/src/modules/module-profile-switcher.c @@ -0,0 +1,192 @@ +/*** + This file is part of PulseAudio. + + Copyright 2011 Arun Raghavan + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/idxset.h> +#include <pulsecore/core.h> +#include <pulsecore/sink.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> + +#include "module-profile-switcher-symdef.h" + +PA_MODULE_AUTHOR("Arun Raghavan"); +PA_MODULE_DESCRIPTION("Switch profiles for modules based on certain heuristics"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +#define GET_ROLE(i) (pa_proplist_gets((i)->proplist, PA_PROP_MEDIA_ROLE)) + +static const char* const valid_modargs[] = { + NULL, +}; + +struct userdata { + pa_hook_slot + *sink_input_put_slot, + *sink_input_state_changed_slot, + *sink_input_unlink_slot; +}; + +static pa_hook_result_t sink_input_put_cb(pa_core *c, pa_sink_input *i, void *userdata) { + pa_card *card; + const char *role; + uint32_t idx1, idx2, idx3; + + pa_assert(c); + pa_assert(i); + + role = GET_ROLE(i); + + if (!pa_streq(role, "phone") && !pa_streq(role, "music")) + return PA_HOOK_OK; + + /* This is basically implementing a per-module policy. It would be nicer to + * be able to specify all this in some configuration file and have a policy + * manager to handle the actual implementation, but until that's done, this + * is the simplest way. We don't want to do this in the actual driver + * module itself to keep the policy bits independent. */ + + PA_IDXSET_FOREACH(card, c->cards, idx1) { + const char *device_api = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_API); + + if (device_api && pa_streq(device_api, "bluez")) { + pa_sink *sink; + pa_bool_t use_a2dp = TRUE; + + PA_IDXSET_FOREACH(sink, card->sinks, idx2) { + pa_sink_input *input; + + PA_IDXSET_FOREACH(input, sink->inputs, idx3) { + if (pa_streq(GET_ROLE(input), "phone")) { + /* There's a phone stream already attached, don't + * switch to A2DP */ + use_a2dp = FALSE; + } + } + } + + if (pa_streq(role, "phone")) { + pa_log_info("Got a phone stream, switching card '%s' to HSP/HFP profile", card->name); + pa_card_set_profile(card, "hsp", FALSE); + } else if (use_a2dp && pa_streq(role, "music")) { + pa_log_info("Got a music stream, switching card '%s' to A2DP profile", card->name); + pa_card_set_profile(card, "a2dp", FALSE); + } + } + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_state_changed_cb(pa_core *c, pa_sink_input *i, void *userdata) { + return sink_input_put_cb(c, i, userdata); +} + +static pa_hook_result_t sink_input_unlink_cb(pa_core *c, pa_sink_input *i, void *userdata) { + const char *role; + uint32_t idx; + + pa_assert(c); + pa_assert(i); + + role = GET_ROLE(i); + + if (!pa_streq(role, "phone")) + return PA_HOOK_OK; + + if (i->sink && i->sink->card) { + const char *device_api = pa_proplist_gets(i->sink->card->proplist, PA_PROP_DEVICE_API); + + if (device_api && pa_streq(device_api, "bluez")) { + pa_sink_input *input; + pa_bool_t use_a2dp = FALSE; + + PA_IDXSET_FOREACH(input, i->sink->inputs, idx) { + if (input != i && pa_streq(GET_ROLE(input), "phone")) { + /* There's a phone stream attaached, don't switch */ + use_a2dp = FALSE; + break; + } + if (pa_streq(GET_ROLE(input), "music")) { + /* There's a music stream attached, we'd like to switch to + * A2DP */ + use_a2dp = TRUE; + } + } + + if (use_a2dp) { + pa_log_info("Lost all phone streams, switching card '%s' to A2DP profile", i->sink->card->name); + pa_card_set_profile(i->sink->card, "a2dp", FALSE); + } + } + } + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma; + struct userdata *u; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + return -1; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + + /* Set these all to EARLY so we can trigger profile changes asap */ + u->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_put_cb, u); + u->sink_input_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_state_changed_cb, u); + u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_unlink_cb, u); + + pa_modargs_free(ma); + return 0; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_input_put_slot) + pa_hook_slot_free(u->sink_input_put_slot); + if (u->sink_input_state_changed_slot) + pa_hook_slot_free(u->sink_input_state_changed_slot); + if (u->sink_input_unlink_slot) + pa_hook_slot_free(u->sink_input_unlink_slot); + + + pa_xfree(u); +} -- 1.7.7.1