diff -Nru pulseaudio-8.0/debian/changelog pulseaudio-8.0/debian/changelog --- pulseaudio-8.0/debian/changelog 2016-12-08 11:16:59.000000000 +0800 +++ pulseaudio-8.0/debian/changelog 2017-05-24 10:57:40.000000000 +0800 @@ -1,3 +1,33 @@ +pulseaudio (1:8.0-0ubuntu3.3) xenial; urgency=medium + + [Luke Yelavich, Konrad Zapałowicz] + * Fixed multiple interrelated problems with using Bluetooth audio (A2DP), + where users would experience some combination of: + - Bluetooth headset/speakers listed but not selectable in Sound settings + (LP: #1283003) + - [regression] Bluetooth audio no longer supports A2DP (stuck in HSP/HFP + mode) (LP: #1438510) + - [xenial] Bluetooth device doesn't play any sound in A2DP mode unless set + to HSP/HFP first (LP: #1582213) + * Specific patches from upstream used to address the above problems: + - 0103-bluetooth-Add-support-for-automatic-switch-between-h.patch + - 0104-bluetooth-Add-support-for-automatic-switch-bluez5.patch + - 0106-bluetooth-Add-optional-heuristic-for-switching-betwe.patch + . Backport from upstream to fix a bug in Xenial where an incorrect + audio profile is applied for a headset connected over Bluetooth + making using it impossible. + - 0105-bluetooth-policy-do-A2DP-profile-restoring-a-bit-lat.patch + . Fix a crash that happens if the BT headset is the only non-monitor + source in the system and the last "phone" stream dies. + - 0700-pulsecore-add-new-card-profile-hook.patch + . Backport from upstream (commit 7b6260140149) to allow for correct + profile selection. + - 0701-bluetooth-bluez5-wait-for-all-profiles-to-connect.patch + . Backport from upstream waiting for all profiles to connect before + creating a card. + + -- Daniel van Vugt Tue, 23 May 2017 16:24:14 +0800 + pulseaudio (1:8.0-0ubuntu3.2) xenial; urgency=medium * 0101-card-add-preferred-input-output-port.patch, diff -Nru pulseaudio-8.0/debian/patches/0103-bluetooth-Add-support-for-automatic-switch-between-h.patch pulseaudio-8.0/debian/patches/0103-bluetooth-Add-support-for-automatic-switch-between-h.patch --- pulseaudio-8.0/debian/patches/0103-bluetooth-Add-support-for-automatic-switch-between-h.patch 1970-01-01 08:00:00.000000000 +0800 +++ pulseaudio-8.0/debian/patches/0103-bluetooth-Add-support-for-automatic-switch-between-h.patch 2017-02-21 23:44:49.000000000 +0800 @@ -0,0 +1,307 @@ +From bde2ff8794a9363b1f7f2c683b160268ce371770 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sat, 2 Jul 2016 22:26:17 +0200 +Subject: [PATCH] bluetooth: Add support for automatic switch between hsp and + a2dp profiles +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +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 +--- + src/modules/bluetooth/module-bluetooth-policy.c | 209 +++++++++++++++++++++++- + 1 file changed, 207 insertions(+), 2 deletions(-) + +diff --git a/src/modules/bluetooth/module-bluetooth-policy.c b/src/modules/bluetooth/module-bluetooth-policy.c +index 4e51846..68c8ab4 100644 +--- a/src/modules/bluetooth/module-bluetooth-policy.c ++++ b/src/modules/bluetooth/module-bluetooth-policy.c +@@ -33,16 +33,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("Policy module to make using bluetooth devices out-of-the-box easier"); + PA_MODULE_VERSION(PACKAGE_VERSION); + PA_MODULE_LOAD_ONCE(true); + PA_MODULE_USAGE( ++ "auto_switch= " + "a2dp_source= " + "ag= " + "hfgw= DEPRECATED"); + + static const char* const valid_modargs[] = { ++ "auto_switch", + "a2dp_source", + "ag", + "hfgw", +@@ -54,7 +56,12 @@ 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_init_profile_slot; ++ pa_hook_slot *card_unlink_slot; + pa_hook_slot *profile_available_changed_slot; ++ pa_hashmap *will_need_revert_card_map; + }; + + /* When a source is created, loopback it to default sink */ +@@ -137,6 +144,167 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void * + return PA_HOOK_OK; + } + ++static void card_set_profile(struct userdata *u, pa_card *card, const char *to_profile, bool revert_to_a2dp) ++{ ++ pa_card_profile *profile; ++ void *state; ++ ++ /* 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_to_a2dp phase flag this card for will_need_revert */ ++ if (!revert_to_a2dp) ++ pa_hashmap_put(u->will_need_revert_card_map, card, PA_INT_TO_PTR(1)); ++ ++ break; ++ } ++} ++ ++/* Switch profile for one card */ ++static void switch_profile(pa_card *card, bool revert_to_a2dp, void *userdata) { ++ struct userdata *u = userdata; ++ const char *from_profile; ++ const char *to_profile; ++ const char *s; ++ ++ if (revert_to_a2dp) { ++ 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_to_a2dp) { ++ /* In revert_to_a2dp phase only consider cards with will_need_revert flag and remove it */ ++ if (!pa_hashmap_remove(u->will_need_revert_card_map, card)) ++ return; ++ } ++ ++ /* 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; ++ ++ card_set_profile(u, card, to_profile, revert_to_a2dp); ++} ++ ++/* 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 identifying streams */ ++ /* We are interested 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_to_a2dp, void *userdata) { ++ pa_card *card; ++ uint32_t idx; ++ ++ PA_IDXSET_FOREACH(card, cards, idx) ++ switch_profile(card, revert_to_a2dp, userdata); ++} ++ ++/* 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, userdata); ++ return PA_HOOK_OK; ++} ++ ++/* When all source outputs 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, userdata); ++ return PA_HOOK_OK; ++} ++ ++static pa_hook_result_t card_init_profile_hook_callback(pa_core *c, pa_card *card, void *userdata) { ++ struct userdata *u = userdata; ++ const char *s; ++ ++ pa_assert(c); ++ pa_assert(card); ++ ++ if (source_output_count(c) == 0) ++ return PA_HOOK_OK; ++ ++ /* Only consider bluetooth cards */ ++ s = pa_proplist_gets(card->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 (card->active_profile && !pa_streq(card->active_profile->name, "a2dp")) ++ return PA_HOOK_OK; ++ ++ /* Set initial profile to hsp */ ++ card_set_profile(u, card, "hsp", false); ++ ++ /* Flag this card for will_need_revert */ ++ pa_hashmap_put(u->will_need_revert_card_map, card, PA_INT_TO_PTR(1)); ++ 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, userdata); ++ return PA_HOOK_OK; ++} ++ + static pa_card_profile *find_best_profile(pa_card *card) { + void *state; + pa_card_profile *profile; +@@ -220,6 +388,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); + +@@ -230,6 +399,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, "auto_switch", &auto_switch) < 0) { ++ pa_log("Failed to parse auto_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."); +@@ -246,12 +421,28 @@ int pa__init(pa_module *m) { + goto fail; + } + ++ u->will_need_revert_card_map = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); ++ + u->source_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL, + (pa_hook_cb_t) source_put_hook_callback, u); + + 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_init_profile_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE], PA_HOOK_NORMAL, ++ (pa_hook_cb_t) card_init_profile_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); + +@@ -280,8 +471,22 @@ 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_init_profile_slot) ++ pa_hook_slot_free(u->card_init_profile_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); + ++ pa_hashmap_free(u->will_need_revert_card_map); ++ + pa_xfree(u); + } +-- +2.7.4 + diff -Nru pulseaudio-8.0/debian/patches/0104-bluetooth-Add-support-for-automatic-switch-bluez5.patch pulseaudio-8.0/debian/patches/0104-bluetooth-Add-support-for-automatic-switch-bluez5.patch --- pulseaudio-8.0/debian/patches/0104-bluetooth-Add-support-for-automatic-switch-bluez5.patch 1970-01-01 08:00:00.000000000 +0800 +++ pulseaudio-8.0/debian/patches/0104-bluetooth-Add-support-for-automatic-switch-bluez5.patch 2017-02-21 23:44:49.000000000 +0800 @@ -0,0 +1,130 @@ +From e32a462cc4201d9c3c7305d0f806d16a09f08875 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 11 Sep 2016 17:16:38 +0200 +Subject: [PATCH] bluetooth: Add support for automatic switch between hsp and + a2dp profiles also for bluez5 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Bluez5 uses different profile names as bluez4, so we need to check for +a2dp_sink and headset_head_unit too for bluez5 support. + +Signed-off-by: Pali Rohár +--- + src/modules/bluetooth/module-bluetooth-policy.c | 60 ++++++++++++++----------- + 1 file changed, 33 insertions(+), 27 deletions(-) + +diff --git a/src/modules/bluetooth/module-bluetooth-policy.c b/src/modules/bluetooth/module-bluetooth-policy.c +index 68c8ab4..e62a114 100644 +--- a/src/modules/bluetooth/module-bluetooth-policy.c ++++ b/src/modules/bluetooth/module-bluetooth-policy.c +@@ -144,23 +144,29 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void * + return PA_HOOK_OK; + } + +-static void card_set_profile(struct userdata *u, pa_card *card, const char *to_profile, bool revert_to_a2dp) ++static void card_set_profile(struct userdata *u, pa_card *card, bool revert_to_a2dp) + { + pa_card_profile *profile; + void *state; + +- /* Find available to_profile and activate it */ ++ /* Find available 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); ++ /* Check for correct profile based on revert_to_a2dp */ ++ if (revert_to_a2dp) { ++ if (!pa_streq(profile->name, "a2dp") && !pa_streq(profile->name, "a2dp_sink")) ++ continue; ++ } else { ++ if (!pa_streq(profile->name, "hsp") && !pa_streq(profile->name, "headset_head_unit")) ++ continue; ++ } ++ ++ pa_log_debug("Setting card '%s' to profile '%s'", card->name, profile->name); + + if (pa_card_set_profile(card, profile, false) != 0) { +- pa_log_warn("Could not set profile '%s'", to_profile); ++ pa_log_warn("Could not set profile '%s'", profile->name); + continue; + } + +@@ -175,18 +181,8 @@ static void card_set_profile(struct userdata *u, pa_card *card, const char *to_p + /* Switch profile for one card */ + static void switch_profile(pa_card *card, bool revert_to_a2dp, void *userdata) { + struct userdata *u = userdata; +- const char *from_profile; +- const char *to_profile; + const char *s; + +- if (revert_to_a2dp) { +- 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")) +@@ -196,17 +192,25 @@ static void switch_profile(pa_card *card, bool revert_to_a2dp, void *userdata) { + /* In revert_to_a2dp phase only consider cards with will_need_revert flag and remove it */ + if (!pa_hashmap_remove(u->will_need_revert_card_map, card)) + return; +- } + +- /* Skip card if does not have active from_profile */ +- if (!pa_streq(card->active_profile->name, from_profile)) +- return; ++ /* Skip card if does not have active hsp profile */ ++ if (!pa_streq(card->active_profile->name, "hsp") && !pa_streq(card->active_profile->name, "headset_head_unit")) ++ return; + +- /* Skip card if already has active profile to_profile */ +- if (pa_streq(card->active_profile->name, to_profile)) +- return; ++ /* Skip card if already has active a2dp profile */ ++ if (pa_streq(card->active_profile->name, "a2dp") || pa_streq(card->active_profile->name, "a2dp_sink")) ++ return; ++ } else { ++ /* Skip card if does not have active a2dp profile */ ++ if (!pa_streq(card->active_profile->name, "a2dp") && !pa_streq(card->active_profile->name, "a2dp_sink")) ++ return; ++ ++ /* Skip card if already has active hsp profile */ ++ if (pa_streq(card->active_profile->name, "hsp") || pa_streq(card->active_profile->name, "headset_head_unit")) ++ return; ++ } + +- card_set_profile(u, card, to_profile, revert_to_a2dp); ++ card_set_profile(u, card, revert_to_a2dp); + } + + /* Return true if we should ignore this source output */ +@@ -287,11 +291,13 @@ static pa_hook_result_t card_init_profile_hook_callback(pa_core *c, pa_card *car + return PA_HOOK_OK; + + /* Ignore card if has already set other initial profile than a2dp */ +- if (card->active_profile && !pa_streq(card->active_profile->name, "a2dp")) ++ if (card->active_profile && ++ !pa_streq(card->active_profile->name, "a2dp") && ++ !pa_streq(card->active_profile->name, "a2dp_sink")) + return PA_HOOK_OK; + + /* Set initial profile to hsp */ +- card_set_profile(u, card, "hsp", false); ++ card_set_profile(u, card, false); + + /* Flag this card for will_need_revert */ + pa_hashmap_put(u->will_need_revert_card_map, card, PA_INT_TO_PTR(1)); +-- +2.7.4 + diff -Nru pulseaudio-8.0/debian/patches/0105-bluetooth-policy-do-A2DP-profile-restoring-a-bit-lat.patch pulseaudio-8.0/debian/patches/0105-bluetooth-policy-do-A2DP-profile-restoring-a-bit-lat.patch --- pulseaudio-8.0/debian/patches/0105-bluetooth-policy-do-A2DP-profile-restoring-a-bit-lat.patch 1970-01-01 08:00:00.000000000 +0800 +++ pulseaudio-8.0/debian/patches/0105-bluetooth-policy-do-A2DP-profile-restoring-a-bit-lat.patch 2017-02-21 23:44:49.000000000 +0800 @@ -0,0 +1,65 @@ +From 2250dbfd6968bd9ce295fc7bef8595b2c6ef6a44 Mon Sep 17 00:00:00 2001 +From: Tanu Kaskinen +Date: Wed, 12 Oct 2016 17:20:39 +0300 +Subject: [PATCH] bluetooth-policy: do A2DP profile restoring a bit later + +This fixes a crash that happens if the bluetooth headset is the only +non-monitor source in the system and the last "phone" stream dies. +When the stream dies, the native protocol calls pa_source_output_unlink() +and would call pa_source_output_unref() next, but without this patch, +things happen during the unlinking, and the unreffing ends up being +performed on a stream that is already freed. + +pa_source_output_unlink() fires the "unlink" hook before doing anything +else. module-bluetooth-policy then switches the headset profile from HSP +to A2DP within that hook. The HSP source gets removed, and at this point +the dying stream is still connected to it, and needs to be rescued. +Rescuing fails, because there are no other sources in the system, so the +stream gets killed. The native protocol has a kill callback, which again +calls pa_source_output_unlink() and pa_source_output_unref(). This is +the point where the native protocol drops its own reference to the +stream, but another unref call is waiting to be executed once we return +from the original unlink call. + +I first tried to avoid the double unreffing by making it safe to do +unlinking recursively, but I found out that there's code that assumes +that once unlink() returns, unlinking has actually occurred (a +reasonable assumption), and at least with my implementation this was not +guaranteed. I now think that we must avoid situations where unlinking +happens recursively. It's just too hairy to deal with. This patch moves +the bluetooth profile switch to happen at a time when the dead stream +isn't any more connected to the source, so it doesn't have to be +rescued or killed. + +BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=97906 +--- + src/modules/bluetooth/module-bluetooth-policy.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/modules/bluetooth/module-bluetooth-policy.c b/src/modules/bluetooth/module-bluetooth-policy.c +index e62a114..df702cc 100644 +--- a/src/modules/bluetooth/module-bluetooth-policy.c ++++ b/src/modules/bluetooth/module-bluetooth-policy.c +@@ -267,8 +267,8 @@ static pa_hook_result_t source_output_unlink_hook_callback(pa_core *c, pa_source + 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) ++ /* If there are still some source outputs do nothing. */ ++ if (source_output_count(c) > 0) + return PA_HOOK_OK; + + switch_profile_all(c->cards, true, userdata); +@@ -439,7 +439,7 @@ int pa__init(pa_module *m) { + 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, ++ u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], PA_HOOK_NORMAL, + (pa_hook_cb_t) source_output_unlink_hook_callback, u); + + u->card_init_profile_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE], PA_HOOK_NORMAL, +-- +2.7.4 + diff -Nru pulseaudio-8.0/debian/patches/0106-bluetooth-Add-optional-heuristic-for-switching-betwe.patch pulseaudio-8.0/debian/patches/0106-bluetooth-Add-optional-heuristic-for-switching-betwe.patch --- pulseaudio-8.0/debian/patches/0106-bluetooth-Add-optional-heuristic-for-switching-betwe.patch 1970-01-01 08:00:00.000000000 +0800 +++ pulseaudio-8.0/debian/patches/0106-bluetooth-Add-optional-heuristic-for-switching-betwe.patch 2017-02-21 23:44:49.000000000 +0800 @@ -0,0 +1,169 @@ +From b245fb41e17a4c74a2226f9202788aa883a340e9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 11 Sep 2016 16:41:02 +0200 +Subject: [PATCH] bluetooth: Add optional heuristic for switching between hsp + and a2dp profiles +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Not all VOIP applications (specially those which use alsa) set media.role to +phone. This means we need some heuristic to determinate if we want to switch +from a2dp to hsp profile based on number and types of source output (recording) +streams. + +And also some people want to use their bluetooth headset (with microphone) as +their default recording device but some do not want to because of low quality. + +This patch implements optional heuristic which is disabled by default. It is +disabled by default to not break experience of current pulseaudio users because +heuristic cannot be optimal. Heuristic is implemented in module-bluetooth-policy +module and decide if pulseaudio should switch to a hsp profile or not. It checks +if there is some source output with pass all these conditions: + +* does not have set media.role +* does not use peak resample method (which is used by desktop volume programs) +* has assigned client/application (non virtual stream) +* does not record from monitor of sink + +And if yes it switch to hsp profile. + +By default this heuristic is disabled and can be enabled when loading module +module-bluetooth-policy with specifying parameter auto_switch=2 + +Because it is disabled by default nobody will be affected by this change unless +manually change auto_switch parameter. + +Signed-off-by: Pali Rohár +--- + src/modules/bluetooth/module-bluetooth-policy.c | 43 +++++++++++++++++-------- + 1 file changed, 30 insertions(+), 13 deletions(-) + +Index: pulseaudio.git-2/src/modules/bluetooth/module-bluetooth-policy.c +=================================================================== +--- pulseaudio.git-2.orig/src/modules/bluetooth/module-bluetooth-policy.c ++++ pulseaudio.git-2/src/modules/bluetooth/module-bluetooth-policy.c +@@ -38,7 +38,7 @@ PA_MODULE_DESCRIPTION("Policy module to + PA_MODULE_VERSION(PACKAGE_VERSION); + PA_MODULE_LOAD_ONCE(true); + PA_MODULE_USAGE( +- "auto_switch= " ++ "auto_switch= " + "a2dp_source= " + "ag= " + "hfgw= DEPRECATED"); +@@ -52,6 +52,7 @@ static const char* const valid_modargs[] + }; + + struct userdata { ++ uint32_t auto_switch; + bool enable_a2dp_source; + bool enable_ag; + pa_hook_slot *source_put_slot; +@@ -214,7 +215,8 @@ static void switch_profile(pa_card *card + } + + /* Return true if we should ignore this source output */ +-static bool ignore_output(pa_source_output *source_output) { ++static bool ignore_output(pa_source_output *source_output, void *userdata) { ++ struct userdata *u = userdata; + const char *s; + + /* New applications could set media.role for identifying streams */ +@@ -223,16 +225,32 @@ static bool ignore_output(pa_source_outp + if (s) + return !pa_streq(s, "phone"); + +- return true; ++ /* If media.role is not set use some heuristic (if enabled) */ ++ if (u->auto_switch != 2) ++ return true; ++ ++ /* Ignore if resample method is peaks (used by desktop volume programs) */ ++ if (pa_source_output_get_resample_method(source_output) == PA_RESAMPLER_PEAKS) ++ return true; ++ ++ /* Ignore if there is no client/application assigned (used by virtual stream) */ ++ if (!source_output->client) ++ return true; ++ ++ /* Ignore if recording from monitor of sink */ ++ if (source_output->direct_on_input) ++ return true; ++ ++ return false; + } + +-static unsigned source_output_count(pa_core *c) { ++static unsigned source_output_count(pa_core *c, void *userdata) { + 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)) ++ if (!ignore_output(source_output, userdata)) + ++count; + + return count; +@@ -252,7 +270,7 @@ static pa_hook_result_t source_output_pu + pa_assert(c); + pa_assert(source_output); + +- if (ignore_output(source_output)) ++ if (ignore_output(source_output, userdata)) + return PA_HOOK_OK; + + switch_profile_all(c->cards, false, userdata); +@@ -264,11 +282,11 @@ static pa_hook_result_t source_output_un + pa_assert(c); + pa_assert(source_output); + +- if (ignore_output(source_output)) ++ if (ignore_output(source_output, userdata)) + return PA_HOOK_OK; + +- /* If there are still some source outputs do nothing. */ +- if (source_output_count(c) > 0) ++ /* If there are still some source outputs do nothing (count is with *this* source_output, so +1) */ ++ if (source_output_count(c, userdata) > 1) + return PA_HOOK_OK; + + switch_profile_all(c->cards, true, userdata); +@@ -282,7 +300,7 @@ static pa_hook_result_t card_init_profil + pa_assert(c); + pa_assert(card); + +- if (source_output_count(c) == 0) ++ if (source_output_count(c, userdata) == 0) + return PA_HOOK_OK; + + /* Only consider bluetooth cards */ +@@ -394,7 +412,6 @@ static void handle_all_profiles(pa_core + int pa__init(pa_module *m) { + pa_modargs *ma; + struct userdata *u; +- bool auto_switch; + + pa_assert(m); + +@@ -405,8 +422,8 @@ int pa__init(pa_module *m) { + + m->userdata = u = pa_xnew0(struct userdata, 1); + +- auto_switch = true; +- if (pa_modargs_get_value_boolean(ma, "auto_switch", &auto_switch) < 0) { ++ u->auto_switch = 1; ++ if (pa_modargs_get_value_u32(ma, "auto_switch", &u->auto_switch) < 0) { + pa_log("Failed to parse auto_switch argument."); + goto fail; + } +@@ -435,7 +452,7 @@ 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) { ++ if (u->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); + diff -Nru pulseaudio-8.0/debian/patches/0700-pulsecore-add-new-card-profile-hook.patch pulseaudio-8.0/debian/patches/0700-pulsecore-add-new-card-profile-hook.patch --- pulseaudio-8.0/debian/patches/0700-pulsecore-add-new-card-profile-hook.patch 1970-01-01 08:00:00.000000000 +0800 +++ pulseaudio-8.0/debian/patches/0700-pulsecore-add-new-card-profile-hook.patch 2017-02-21 23:44:49.000000000 +0800 @@ -0,0 +1,324 @@ +Index: pulseaudio.git/src/pulsecore/core.h +=================================================================== +--- pulseaudio.git.orig/src/pulsecore/core.h ++++ pulseaudio.git/src/pulsecore/core.h +@@ -119,6 +119,7 @@ typedef enum pa_core_hook { + PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED, + PA_CORE_HOOK_CLIENT_SEND_EVENT, + PA_CORE_HOOK_CARD_NEW, ++ PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE, + PA_CORE_HOOK_CARD_PUT, + PA_CORE_HOOK_CARD_UNLINK, + PA_CORE_HOOK_CARD_PREFERRED_PORT_CHANGED, +Index: pulseaudio.git/src/pulsecore/card.h +=================================================================== +--- pulseaudio.git.orig/src/pulsecore/card.h ++++ pulseaudio.git/src/pulsecore/card.h +@@ -84,6 +84,8 @@ struct pa_card { + + bool save_profile:1; + ++ bool linked; ++ + void *userdata; + + int (*set_profile)(pa_card *c, pa_card_profile *profile); +@@ -126,6 +128,13 @@ void pa_card_new_data_set_preferred_port + void pa_card_new_data_done(pa_card_new_data *data); + + pa_card *pa_card_new(pa_core *c, pa_card_new_data *data); ++ ++/* Select the initial card profile acording to the configured policies. This ++ * must be called between pa_card_new() and pa_card_put(), after the port and ++ * profile availabilities have been initialized. */ ++void pa_card_choose_initial_profile(pa_card *card); ++ ++void pa_card_put(pa_card *c); + void pa_card_free(pa_card *c); + + void pa_card_add_profile(pa_card *c, pa_card_profile *profile); +Index: pulseaudio.git/src/modules/bluetooth/module-bluez5-device.c +=================================================================== +--- pulseaudio.git.orig/src/modules/bluetooth/module-bluez5-device.c ++++ pulseaudio.git/src/modules/bluetooth/module-bluez5-device.c +@@ -2194,6 +2194,8 @@ static int add_card(struct userdata *u) + + u->card->userdata = u; + u->card->set_profile = set_profile_cb; ++ pa_card_choose_initial_profile(u->card); ++ pa_card_put(u->card); + + p = PA_CARD_PROFILE_DATA(u->card->active_profile); + +Index: pulseaudio.git/src/modules/macosx/module-coreaudio-device.c +=================================================================== +--- pulseaudio.git.orig/src/modules/macosx/module-coreaudio-device.c ++++ pulseaudio.git/src/modules/macosx/module-coreaudio-device.c +@@ -807,6 +807,8 @@ int pa__init(pa_module *m) { + pa_card_new_data_done(&card_new_data); + u->card->userdata = u; + u->card->set_profile = card_set_profile; ++ pa_card_choose_initial_profile(u->card); ++ pa_card_put(u->card); + + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); +Index: pulseaudio.git/src/pulsecore/card.c +=================================================================== +--- pulseaudio.git.orig/src/pulsecore/card.c ++++ pulseaudio.git/src/pulsecore/card.c +@@ -181,38 +181,59 @@ pa_card *pa_card_new(pa_core *core, pa_c + c->preferred_input_port = data->preferred_input_port; + c->preferred_output_port = data->preferred_output_port; + +- if (data->active_profile) +- if ((c->active_profile = pa_hashmap_get(c->profiles, data->active_profile))) +- c->save_profile = data->save_profile; +- +- if (!c->active_profile) { +- PA_HASHMAP_FOREACH(profile, c->profiles, state) { +- if (profile->available == PA_AVAILABLE_NO) +- continue; ++ pa_device_init_description(c->proplist, c); ++ pa_device_init_icon(c->proplist, true); ++ pa_device_init_intended_roles(c->proplist); + +- if (!c->active_profile || profile->priority > c->active_profile->priority) +- c->active_profile = profile; +- } +- /* If all profiles are not available, then we still need to pick one */ +- if (!c->active_profile) { +- PA_HASHMAP_FOREACH(profile, c->profiles, state) +- if (!c->active_profile || profile->priority > c->active_profile->priority) +- c->active_profile = profile; ++ return c; ++} ++ ++void pa_card_choose_initial_profile(pa_card *card) { ++ pa_card_profile *profile; ++ void *state; ++ pa_card_profile *best = NULL; ++ ++ pa_assert(card); ++ ++ /* By default, pick the highest priority that is not unavailable, ++ * or if all profiles are unavailable, pick the profile with the ++ * highest priority regardless of its availability. ++ */ ++ ++ PA_HASHMAP_FOREACH(profile, card->profiles, state) { ++ if (profile->available == PA_AVAILABLE_NO) ++ continue; ++ ++ if (!best || profile->priority > best->priority) ++ best = profile; ++ } ++ ++ if (!best) { ++ PA_HASHMAP_FOREACH(profile, card->profiles, state) { ++ if (!best || profile->priority > best->priority) ++ best = profile; + } +- pa_assert(c->active_profile); + } ++ pa_assert(best); + +- pa_device_init_description(c->proplist, c); +- pa_device_init_icon(c->proplist, true); +- pa_device_init_intended_roles(c->proplist); ++ card->active_profile = best; ++ card->save_profile = false; + +- pa_assert_se(pa_idxset_put(core->cards, c, &c->index) >= 0); ++ /* Let policy modules override the default. */ ++ pa_hook_fire(&card->core->hooks[PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE], ++ card); ++} + +- pa_log_info("Created %u \"%s\"", c->index, c->name); +- pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_NEW, c->index); ++void pa_card_put(pa_card *card) { ++ pa_assert(card); + +- pa_hook_fire(&core->hooks[PA_CORE_HOOK_CARD_PUT], c); +- return c; ++ pa_assert_se(pa_idxset_put(card->core->cards, card, &card->index) >= 0); ++ card->linked = true; ++ ++ pa_log_info("Created %u \"%s\"", card->index, card->name); ++ pa_hook_fire(&card->core->hooks[PA_CORE_HOOK_CARD_PUT], card); ++ pa_subscription_post(card->core, ++ PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_NEW, card->index); + } + + void pa_card_free(pa_card *c) { +@@ -223,16 +244,16 @@ void pa_card_free(pa_card *c) { + + core = c->core; + +- pa_hook_fire(&core->hooks[PA_CORE_HOOK_CARD_UNLINK], c); ++ if (c->linked) { ++ pa_hook_fire(&core->hooks[PA_CORE_HOOK_CARD_UNLINK], c); ++ pa_idxset_remove_by_data(c->core->cards, c, NULL); ++ pa_log_info("Freed %u \"%s\"", c->index, c->name); ++ pa_subscription_post(c->core, ++ PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_REMOVE, c->index); ++ } + + pa_namereg_unregister(core, c->name); + +- pa_idxset_remove_by_data(c->core->cards, c, NULL); +- +- pa_log_info("Freed %u \"%s\"", c->index, c->name); +- +- pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_REMOVE, c->index); +- + pa_assert(pa_idxset_isempty(c->sinks)); + pa_idxset_free(c->sinks, NULL); + pa_assert(pa_idxset_isempty(c->sources)); +@@ -305,20 +326,27 @@ int pa_card_set_profile(pa_card *c, pa_c + + pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGING], profile); + +- if ((r = c->set_profile(c, profile)) < 0) ++ /* If we're setting the initial profile, we shouldn't call set_profile(), ++ * because the implementation don't expect that (for historical reasons). ++ * We should just set c->active_profile, and the implementation will ++ * properly set up that profile after pa_card_put() has returned. It would ++ * be probably good to change this so that also the initial profile can be ++ * set up in set_profile(), but if set_profile() fails, that would need ++ * some better handling than what we do here currently. */ ++ if (c->linked && (r = c->set_profile(c, profile)) < 0) + return r; + +- pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, c->index); +- +- pa_log_info("Changed profile of card %u \"%s\" to %s", c->index, c->name, profile->name); +- + c->active_profile = profile; + c->save_profile = save; + + if (save) + update_port_preferred_profile(c); + +- pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGED], c); ++ if (c->linked) { ++ pa_log_info("Changed profile of card %u \"%s\" to %s", c->index, c->name, profile->name); ++ pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGED], c); ++ pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, c->index); ++ } + + return 0; + } +Index: pulseaudio.git/src/modules/module-card-restore.c +=================================================================== +--- pulseaudio.git.orig/src/modules/module-card-restore.c ++++ pulseaudio.git/src/modules/module-card-restore.c +@@ -526,16 +526,6 @@ static pa_hook_result_t card_new_hook_ca + if (!(e = entry_read(u, new_data->name))) + return PA_HOOK_OK; + +- if (e->profile[0]) { +- if (!new_data->active_profile) { +- pa_card_new_data_set_profile(new_data, e->profile); +- pa_log_info("Restored profile '%s' for card %s.", new_data->active_profile, new_data->name); +- new_data->save_profile = true; +- +- } else +- pa_log_debug("Not restoring profile for card %s, because already set.", new_data->name); +- } +- + /* Always restore the latency offsets because their + * initial value is always 0 */ + +@@ -552,6 +542,31 @@ static pa_hook_result_t card_new_hook_ca + return PA_HOOK_OK; + } + ++static pa_hook_result_t card_choose_initial_profile_callback(pa_core *core, pa_card *card, struct userdata *u) { ++ struct entry *e; ++ ++ if (!(e = entry_read(u, card->name))) ++ return PA_HOOK_OK; ++ ++ if (e->profile[0]) { ++ pa_card_profile *profile; ++ ++ profile = pa_hashmap_get(card->profiles, e->profile); ++ if (profile) { ++ pa_log_info("Restoring profile '%s' for card %s.", card->active_profile->name, card->name); ++ pa_card_set_profile(card, profile, true); ++ } else { ++ pa_log_debug("Tried to restore profile %s for card %s, but the card doesn't have such profile.", ++ e->profile, card->name); ++ } ++ } ++ ++ entry_free(e); ++ ++ ++ return PA_HOOK_OK; ++} ++ + int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; +@@ -569,6 +584,8 @@ int pa__init(pa_module*m) { + u->module = m; + + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) card_new_hook_callback, u); ++ pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE], PA_HOOK_NORMAL, ++ (pa_hook_cb_t) card_choose_initial_profile_callback, u); + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PUT], PA_HOOK_NORMAL, (pa_hook_cb_t) card_put_hook_callback, u); + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) card_profile_changed_callback, u); + pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_ADDED], PA_HOOK_NORMAL, (pa_hook_cb_t) card_profile_added_callback, u); +Index: pulseaudio.git/src/modules/alsa/module-alsa-card.c +=================================================================== +--- pulseaudio.git.orig/src/modules/alsa/module-alsa-card.c ++++ pulseaudio.git/src/modules/alsa/module-alsa-card.c +@@ -642,7 +642,7 @@ int pa__init(pa_module *m) { + struct userdata *u; + pa_reserve_wrapper *reserve = NULL; + const char *description; +- const char *profile = NULL; ++ const char *profile_str = NULL; + char *fn = NULL; + bool namereg_fail = false; + +@@ -770,9 +770,6 @@ int pa__init(pa_module *m) { + goto fail; + } + +- if ((profile = pa_modargs_get_value(u->modargs, "profile", NULL))) +- pa_card_new_data_set_profile(&data, profile); +- + u->card = pa_card_new(m->core, &data); + pa_card_new_data_done(&data); + +@@ -783,6 +780,26 @@ int pa__init(pa_module *m) { + u->card->set_profile = card_set_profile; + + init_jacks(u); ++ ++ pa_card_choose_initial_profile(u->card); ++ ++ /* If the "profile" modarg is given, we have to override whatever the usual ++ * policy chose in pa_card_choose_initial_profile(). */ ++ profile_str = pa_modargs_get_value(u->modargs, "profile", NULL); ++ if (profile_str) { ++ pa_card_profile *profile; ++ ++ profile = pa_hashmap_get(u->card->profiles, profile_str); ++ if (!profile) { ++ pa_log("No such profile: %s", profile_str); ++ goto fail; ++ } ++ ++ pa_card_set_profile(u->card, profile, false); ++ } ++ ++ pa_card_put(u->card); ++ + init_profile(u); + init_eld_ctls(u); + diff -Nru pulseaudio-8.0/debian/patches/0701-bluetooth-bluez5-wait-for-all-profiles-to-connect.patch pulseaudio-8.0/debian/patches/0701-bluetooth-bluez5-wait-for-all-profiles-to-connect.patch --- pulseaudio-8.0/debian/patches/0701-bluetooth-bluez5-wait-for-all-profiles-to-connect.patch 1970-01-01 08:00:00.000000000 +0800 +++ pulseaudio-8.0/debian/patches/0701-bluetooth-bluez5-wait-for-all-profiles-to-connect.patch 2017-02-21 23:44:49.000000000 +0800 @@ -0,0 +1,170 @@ +Index: pulseaudio.git/src/modules/bluetooth/bluez5-util.h +=================================================================== +--- pulseaudio.git.orig/src/modules/bluetooth/bluez5-util.h ++++ pulseaudio.git/src/modules/bluetooth/bluez5-util.h +@@ -105,6 +105,8 @@ struct pa_bluetooth_device { + pa_hashmap *uuids; + + pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT]; ++ ++ pa_time_event *wait_for_profiles_timer; + }; + + struct pa_bluetooth_adapter { +Index: pulseaudio.git/src/modules/bluetooth/bluez5-util.c +=================================================================== +--- pulseaudio.git.orig/src/modules/bluetooth/bluez5-util.c ++++ pulseaudio.git/src/modules/bluetooth/bluez5-util.c +@@ -21,6 +21,8 @@ + #include + #endif + ++#include ++#include + #include + + #include +@@ -35,6 +37,8 @@ + + #include "bluez5-util.h" + ++#define WAIT_FOR_PROFILES_TIMEOUT_USEC (3 * PA_USEC_PER_SEC) ++ + #define BLUEZ_SERVICE "org.bluez" + #define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1" + #define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1" +@@ -164,6 +168,95 @@ static const char *transport_state_to_st + return "invalid"; + } + ++static bool device_supports_profile(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) { ++ switch (profile) { ++ case PA_BLUETOOTH_PROFILE_A2DP_SINK: ++ return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SINK); ++ case PA_BLUETOOTH_PROFILE_A2DP_SOURCE: ++ return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE); ++ case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT: ++ return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS) ++ || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_HF); ++ case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY: ++ return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_AG) ++ || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_AG); ++ case PA_BLUETOOTH_PROFILE_OFF: ++ pa_assert_not_reached(); ++ } ++ ++ pa_assert_not_reached(); ++} ++ ++static bool device_is_profile_connected(pa_bluetooth_device *device, pa_bluetooth_profile_t profile) { ++ if (device->transports[profile] && device->transports[profile]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) ++ return true; ++ else ++ return false; ++} ++ ++static unsigned device_count_disconnected_profiles(pa_bluetooth_device *device) { ++ pa_bluetooth_profile_t profile; ++ unsigned count = 0; ++ ++ for (profile = 0; profile < PA_BLUETOOTH_PROFILE_COUNT; profile++) { ++ if (!device_supports_profile(device, profile)) ++ continue; ++ ++ if (!device_is_profile_connected(device, profile)) ++ count++; ++ } ++ ++ return count; ++} ++ ++static void device_stop_waiting_for_profiles(pa_bluetooth_device *device) { ++ if (!device->wait_for_profiles_timer) ++ return; ++ ++ device->discovery->core->mainloop->time_free(device->wait_for_profiles_timer); ++ device->wait_for_profiles_timer = NULL; ++} ++ ++static void wait_for_profiles_cb(pa_mainloop_api *api, pa_time_event *event, const struct timeval *tv, void *userdata) { ++ pa_bluetooth_device *device = userdata; ++ pa_strbuf *buf; ++ pa_bluetooth_profile_t profile; ++ bool first = true; ++ char *profiles_str; ++ ++ device_stop_waiting_for_profiles(device); ++ ++ buf = pa_strbuf_new(); ++ ++ for (profile = 0; profile < PA_BLUETOOTH_PROFILE_COUNT; profile++) { ++ if (device_is_profile_connected(device, profile)) ++ continue; ++ ++ if (!device_supports_profile(device, profile)) ++ continue; ++ ++ if (first) ++ first = false; ++ else ++ pa_strbuf_puts(buf, ", "); ++ ++ pa_strbuf_puts(buf, pa_bluetooth_profile_to_string(profile)); ++ } ++ ++ profiles_str = pa_strbuf_to_string_free(buf); ++ pa_log_debug("Timeout expired, and device %s still has disconnected profiles: %s", ++ device->path, profiles_str); ++ pa_xfree(profiles_str); ++ pa_hook_fire(&device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], device); ++} ++ ++static void device_start_waiting_for_profiles(pa_bluetooth_device *device) { ++ pa_assert(!device->wait_for_profiles_timer); ++ device->wait_for_profiles_timer = pa_core_rttime_new(device->discovery->core, ++ pa_rtclock_now() + WAIT_FOR_PROFILES_TIMEOUT_USEC, ++ wait_for_profiles_cb, device); ++} ++ + void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_transport_state_t state) { + bool old_any_connected; + +@@ -181,8 +274,27 @@ void pa_bluetooth_transport_set_state(pa + + pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t); + +- if (old_any_connected != pa_bluetooth_device_any_transport_connected(t->device)) +- pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], t->device); ++ if (old_any_connected != pa_bluetooth_device_any_transport_connected(t->device)) { ++ unsigned n_disconnected_profiles; ++ ++ /* If there are profiles that are expected to get conneced soon (based ++ * on the UUID list), we wait for a bit before announcing the new ++ * device, so that all profiles have time to get connected before the ++ * card object is created. If we didn't wait, the card would always ++ * have only one profile marked as available in the initial state, ++ * which would prevent module-card-restore from restoring initial ++ * profile properly. */ ++ ++ n_disconnected_profiles = device_count_disconnected_profiles(t->device); ++ ++ if (n_disconnected_profiles == 0) ++ device_stop_waiting_for_profiles(t->device); ++ ++ if (!old_any_connected && n_disconnected_profiles > 0) ++ device_start_waiting_for_profiles(t->device); ++ else ++ pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], t->device); ++ } + } + + void pa_bluetooth_transport_put(pa_bluetooth_transport *t) { +@@ -422,6 +534,8 @@ static void device_free(pa_bluetooth_dev + + pa_assert(d); + ++ device_stop_waiting_for_profiles(d); ++ + for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) { + pa_bluetooth_transport *t; + diff -Nru pulseaudio-8.0/debian/patches/series pulseaudio-8.0/debian/patches/series --- pulseaudio-8.0/debian/patches/series 2016-12-08 10:47:46.000000000 +0800 +++ pulseaudio-8.0/debian/patches/series 2017-02-21 23:44:49.000000000 +0800 @@ -10,6 +10,10 @@ 0100-switch-on-port-available-Switch-from-HDMI-to-analog-.patch 0101-card-add-preferred-input-output-port.patch 0102-switch-on-port-available-prefer-ports-that-have-been.patch +0103-bluetooth-Add-support-for-automatic-switch-between-h.patch +0104-bluetooth-Add-support-for-automatic-switch-bluez5.patch +0105-bluetooth-policy-do-A2DP-profile-restoring-a-bit-lat.patch +0106-bluetooth-Add-optional-heuristic-for-switching-betwe.patch # Ubuntu touch stuff 0202-dont-probe-ucm.patch @@ -46,3 +50,6 @@ 0601-droid-alternative-hw-module-id.patch 0602-droid-inputstream-config-parameters.pach 0603-droid-port-priority-and-availability.patch + +0700-pulsecore-add-new-card-profile-hook.patch +0701-bluetooth-bluez5-wait-for-all-profiles-to-connect.patch