[pulseaudio-discuss] [PATCH] bluetooth: Add support for automatic switch between hsp and a2dp profiles

Alexander E. Patrakov patrakov at gmail.com
Wed Sep 24 12:36:50 PDT 2014


24.09.2014 23:08, Pali Rohár wrote:
> With this patch module-bluetooth-policy automatically switch from a2dp profile
> to hsp profile if some application want to start recording.
>
> 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 after it release
> it profile is switched back to a2dp. So this patch allows to use bluetooth
> microphone automatically without need of user interaction.

Summary from IRC:

This module switches the headset to the hsp profile when any application 
starts recording anything, except pavucontrol. While the need for 
ignoring pavucontrol is valid, the test should likely not be done via 
PA_PROP_APPLICATION_ID.

Unfortunately, I cannot suggest an exhaustive list of source outputs 
that should be ignored. The following candidates to ignore have been 
suggested so far:

  * pavucontrol (obviously), both when it is recording from the 
microphone and when it is recording from the monitor source
  * all desktop-specific equivalents of pavucontrol - e.g. 
mate-volume-control. Maybe we can figure them out by resampler == peaks?
  * anything that records from any monitor source
  * virtual streams, like the one from module-echo-cancel, as long as 
nothing records from the corresponding virtual sources

>
> Signed-off-by: Pali Rohár <pali.rohar at gmail.com>
> ---
>   src/modules/bluetooth/module-bluetooth-policy.c |  177 ++++++++++++++++++++++-
>   1 file changed, 175 insertions(+), 2 deletions(-)
>
> diff --git a/src/modules/bluetooth/module-bluetooth-policy.c b/src/modules/bluetooth/module-bluetooth-policy.c
> index 860868f..c98f372 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_put_slot;
> +    pa_hook_slot *card_unlink_slot;
>       pa_hook_slot *profile_available_changed_slot;
>   };
>
> @@ -139,6 +145,142 @@ 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 source output is pavucontrol peak */
> +static bool is_pc_peak(pa_source_output *source_output) {
> +    const char *s = pa_proplist_gets(source_output->proplist, PA_PROP_APPLICATION_ID);
> +    if (!s || !pa_streq(s, "org.PulseAudio.pavucontrol"))
> +        return false;
> +    else
> +        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 (!is_pc_peak(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);
> +
> +    /* Ignore pavucontrol */
> +    if (is_pc_peak(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);
> +
> +    /* Ignore pavucontrol */
> +    if (is_pc_peak(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_put_hook_callback(pa_core *c, pa_card *card, void *userdata) {
> +    pa_assert(c);
> +    pa_assert(card);
> +    switch_profile(card, false);
> +    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 +364,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 +375,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 +403,18 @@ 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_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_PUT], PA_HOOK_NORMAL,
> +                                           (pa_hook_cb_t) card_put_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 +442,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_put_slot)
> +        pa_hook_slot_free(u->card_put_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);
>
>

-- 
Alexander E. Patrakov


More information about the pulseaudio-discuss mailing list