[pulseaudio-discuss] [PATCH v3 1/2] bluetooth: Add support for automatic switch between hsp and a2dp profiles
Pali Rohár
pali.rohar at gmail.com
Fri May 22 12:56:30 PDT 2015
On Friday 31 October 2014 23:49:47 Pali Rohár wrote:
> With this patch module-bluetooth-policy automatically switch from
> a2dp profile to hsp profile if some VOIP application with
> media.role=phone wants to start recording audio.
>
> By default a2dp profile is used for listening music, but for VOIP
> calls is needed profile with microphone support (hsp). So this patch
> will switch to hsp profile if some application want to use
> microphone (and specify it in media.role as "phone). After recording
> is stopped profile is switched back to a2dp. So this patch allows to
> use bluetooth microphone for VOIP applications with media.role=phone
> automatically without need of user interaction.
>
> Signed-off-by: Pali Rohár <pali.rohar at gmail.com>
> ---
> src/modules/bluetooth/module-bluetooth-policy.c | 200
> ++++++++++++++++++++++- 1 file changed, 198 insertions(+), 2
> deletions(-)
>
> diff --git a/src/modules/bluetooth/module-bluetooth-policy.c
> b/src/modules/bluetooth/module-bluetooth-policy.c index
> 860868f..200fc84 100644
> --- a/src/modules/bluetooth/module-bluetooth-policy.c
> +++ b/src/modules/bluetooth/module-bluetooth-policy.c
> @@ -35,16 +35,18 @@
>
> #include "module-bluetooth-policy-symdef.h"
>
> -PA_MODULE_AUTHOR("Frédéric Dalleau");
> -PA_MODULE_DESCRIPTION("When a bluetooth sink or source is added,
> load module-loopback"); +PA_MODULE_AUTHOR("Frédéric Dalleau, Pali
> Rohár");
> +PA_MODULE_DESCRIPTION("Automatically switch between bluetooth hsp
> and a2dp profiles and automatically load module-loopback when a
> bluetooth sink (ag) or source (ag, a2dp_source) is added");
> PA_MODULE_VERSION(PACKAGE_VERSION);
> PA_MODULE_LOAD_ONCE(true);
> PA_MODULE_USAGE(
> + "switch=<Switch between hsp and a2dp profile?> "
> "a2dp_source=<Handle a2dp_source card profile (sink role)?>
> " "ag=<Handle headset_audio_gateway card profile (headset role)?> "
> "hfgw=<Handle hfgw card profile (headset role)?> DEPRECATED");
>
> static const char* const valid_modargs[] = {
> + "switch",
> "a2dp_source",
> "ag",
> "hfgw",
> @@ -56,6 +58,10 @@ struct userdata {
> bool enable_ag;
> pa_hook_slot *source_put_slot;
> pa_hook_slot *sink_put_slot;
> + pa_hook_slot *source_output_put_slot;
> + pa_hook_slot *source_output_unlink_slot;
> + pa_hook_slot *card_new_slot;
> + pa_hook_slot *card_unlink_slot;
> pa_hook_slot *profile_available_changed_slot;
> };
>
> @@ -139,6 +145,163 @@ static pa_hook_result_t
> sink_put_hook_callback(pa_core *c, pa_sink *sink, void * return
> PA_HOOK_OK;
> }
>
> +/* Switch profile for one card */
> +static void switch_profile(pa_card *card, bool revert) {
> + const char *from_profile;
> + const char *to_profile;
> + pa_card_profile *profile;
> + const char *s;
> + void *state;
> +
> + if (revert) {
> + from_profile = "hsp";
> + to_profile = "a2dp";
> + } else {
> + from_profile = "a2dp";
> + to_profile = "hsp";
> + }
> +
> + /* Only consider bluetooth cards */
> + s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS);
> + if (!s || !pa_streq(s, "bluetooth"))
> + return;
> +
> + if (revert) {
> + /* In revert phase only consider cards with revert flag */
> + s = pa_proplist_gets(card->proplist, "bluez-revert");
> + if (!s || !pa_streq(s, "true"))
> + return;
> +
> + /* Remove revert flag */
> + pa_proplist_unset(card->proplist, "bluez-revert");
> + }
> +
> + /* Skip card if does not have active from_profile */
> + if (!pa_streq(card->active_profile->name, from_profile))
> + return;
> +
> + /* Skip card if already has active profile to_profile */
> + if (pa_streq(card->active_profile->name, to_profile))
> + return;
> +
> + /* Find available to_profile and activate it */
> + PA_HASHMAP_FOREACH(profile, card->profiles, state) {
> + if (!pa_streq(profile->name, to_profile))
> + continue;
> +
> + if (profile->available == PA_AVAILABLE_NO)
> + continue;
> +
> + pa_log_debug("Setting card '%s' to profile '%s'",
> card->name, to_profile); +
> + if (pa_card_set_profile(card, profile, false) != 0) {
> + pa_log_warn("Could not set profile '%s'", to_profile);
> + continue;
> + }
> +
> + /* When we are not in revert phase flag this card for revert
> */ + if (!revert)
> + pa_proplist_sets(card->proplist, "bluez-revert",
> "true"); +
> + break;
> + }
> +}
> +
> +/* Return true if we should ignore this source output */
> +static bool ignore_output(pa_source_output *source_output) {
> + const char *s;
> +
> + /* New applications could set media.role for identifing streams
> */ + /* We are iterested only in media.role=phone */
> + s = pa_proplist_gets(source_output->proplist,
> PA_PROP_MEDIA_ROLE); + if (s)
> + return !pa_streq(s, "phone");
> +
> + return true;
> +}
> +
> +static unsigned source_output_count(pa_core *c) {
> + pa_source_output *source_output;
> + uint32_t idx;
> + unsigned count = 0;
> +
> + PA_IDXSET_FOREACH(source_output, c->source_outputs, idx)
> + if (!ignore_output(source_output))
> + ++count;
> +
> + return count;
> +}
> +
> +/* Switch profile for all cards */
> +static void switch_profile_all(pa_idxset *cards, bool revert) {
> + pa_card *card;
> + uint32_t idx;
> +
> + PA_IDXSET_FOREACH(card, cards, idx)
> + switch_profile(card, revert);
> +}
> +
> +/* When a source output is created, switch profile a2dp to profile
> hsp */ +static pa_hook_result_t
> source_output_put_hook_callback(pa_core *c, pa_source_output
> *source_output, void *userdata) { + pa_assert(c);
> + pa_assert(source_output);
> +
> + if (ignore_output(source_output))
> + return PA_HOOK_OK;
> +
> + switch_profile_all(c->cards, false);
> + return PA_HOOK_OK;
> +}
> +
> +/* When all sources are unlinked, switch profile hsp back back to
> profile a2dp */ +static pa_hook_result_t
> source_output_unlink_hook_callback(pa_core *c, pa_source_output
> *source_output, void *userdata) { + pa_assert(c);
> + pa_assert(source_output);
> +
> + if (ignore_output(source_output))
> + return PA_HOOK_OK;
> +
> + /* If there are still some source outputs do nothing (count is
> with *this* source_output, so +1) */ + if (source_output_count(c)
> > 1)
> + return PA_HOOK_OK;
> +
> + switch_profile_all(c->cards, true);
> + return PA_HOOK_OK;
> +}
> +
> +static pa_hook_result_t card_new_hook_callback(pa_core *c,
> pa_card_new_data *new_data, void *userdata) { + const char *s;
> +
> + pa_assert(c);
> + pa_assert(new_data);
> +
> + if (source_output_count(c) == 0)
> + return PA_HOOK_OK;
> +
> + /* Only consider bluetooth cards */
> + s = pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_BUS);
> + if (!s || !pa_streq(s, "bluetooth"))
> + return PA_HOOK_OK;
> +
> + /* Ignore card if has already set other initial profile than
> a2dp */ + if (new_data->active_profile &&
> !pa_streq(new_data->active_profile, "a2dp")) + return
> PA_HOOK_OK;
> +
> + /* Set initial profile to hsp */
> + pa_card_new_data_set_profile(new_data, "hsp");
> +
> + /* Flag this card for revert */
> + pa_proplist_sets(new_data->proplist, "bluez-revert", "true");
> + return PA_HOOK_OK;
> +}
> +
> +static pa_hook_result_t card_unlink_hook_callback(pa_core *c,
> pa_card *card, void *userdata) { + pa_assert(c);
> + pa_assert(card);
> + switch_profile(card, true);
> + return PA_HOOK_OK;
> +}
> +
> static pa_card_profile *find_best_profile(pa_card *card) {
> void *state;
> pa_card_profile *profile;
> @@ -222,6 +385,7 @@ static void handle_all_profiles(pa_core *core) {
> int pa__init(pa_module *m) {
> pa_modargs *ma;
> struct userdata *u;
> + bool auto_switch;
>
> pa_assert(m);
>
> @@ -232,6 +396,12 @@ int pa__init(pa_module *m) {
>
> m->userdata = u = pa_xnew0(struct userdata, 1);
>
> + auto_switch = true;
> + if (pa_modargs_get_value_boolean(ma, "switch", &auto_switch) <
> 0) { + pa_log("Failed to parse switch argument.");
> + goto fail;
> + }
> +
> u->enable_a2dp_source = true;
> if (pa_modargs_get_value_boolean(ma, "a2dp_source",
> &u->enable_a2dp_source) < 0) { pa_log("Failed to parse a2dp_source
> argument.");
> @@ -254,6 +424,20 @@ int pa__init(pa_module *m) {
> u->sink_put_slot =
> pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT],
> PA_HOOK_NORMAL, (pa_hook_cb_t) sink_put_hook_callback, u);
>
> + if (auto_switch) {
> + u->source_output_put_slot =
> pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT],
> PA_HOOK_NORMAL, +
> (pa_hook_cb_t) source_output_put_hook_callback, u); +
> + u->source_output_unlink_slot =
> pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK],
> PA_HOOK_NORMAL, +
> (pa_hook_cb_t) source_output_unlink_hook_callback, u); +
> + u->card_new_slot =
> pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_NEW],
> PA_HOOK_NORMAL, +
> (pa_hook_cb_t) card_new_hook_callback, u); +
> + u->card_unlink_slot =
> pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_UNLINK],
> PA_HOOK_NORMAL, +
> (pa_hook_cb_t) card_unlink_hook_callback, u); + }
> +
> u->profile_available_changed_slot =
> pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_
> CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t)
> profile_available_hook_callback, u);
>
> @@ -281,6 +465,18 @@ void pa__done(pa_module *m) {
> if (u->sink_put_slot)
> pa_hook_slot_free(u->sink_put_slot);
>
> + if (u->source_output_put_slot)
> + pa_hook_slot_free(u->source_output_put_slot);
> +
> + if (u->source_output_unlink_slot)
> + pa_hook_slot_free(u->source_output_unlink_slot);
> +
> + if (u->card_new_slot)
> + pa_hook_slot_free(u->card_new_slot);
> +
> + if (u->card_unlink_slot)
> + pa_hook_slot_free(u->card_unlink_slot);
> +
> if (u->profile_available_changed_slot)
> pa_hook_slot_free(u->profile_available_changed_slot);
Ping, any review/comments for this (first only) patch?
--
Pali Rohár
pali.rohar at gmail.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 198 bytes
Desc: This is a digitally signed message part.
URL: <http://lists.freedesktop.org/archives/pulseaudio-discuss/attachments/20150522/9d461150/attachment-0001.sig>
More information about the pulseaudio-discuss
mailing list