When all headsets supported both HSP and HFP, life was good and we only needed to implement HSP in the native backend. Unfortunately some headsets have started supporting HFP only. Unfortuantely, we can't simply switch to HFP only because that might break older HSP only headsets meaning we need to support both HSP and HFP separately. This patch separates them from a joint profile to being two separate ones. The older one retains the headset_head_unit name, meaning any saved parameters will still select this (keeping us backward compatible). It also introduces a new headset_handsfree. For headsets that support both HSP and HFP, the two profiles will become separately visible and selectable. This will only matter once we start adding features to HFP that HSP can't support (like wideband audio). Signed-off-by: <James.Bottomley at HansenPartnership.com> --- v2: - fold in review feedback - add global disable option for not registering HFP --- src/modules/bluetooth/backend-native.c | 29 ++++++++- src/modules/bluetooth/bluez5-util.c | 9 ++- src/modules/bluetooth/bluez5-util.h | 3 + src/modules/bluetooth/module-bluetooth-discover.c | 3 +- src/modules/bluetooth/module-bluetooth-policy.c | 3 +- src/modules/bluetooth/module-bluez5-device.c | 73 +++++++++++++++++++---- src/modules/bluetooth/module-bluez5-discover.c | 13 +++- 7 files changed, 113 insertions(+), 20 deletions(-) diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c index 005363b..f7453a4 100644 --- a/src/modules/bluetooth/backend-native.c +++ b/src/modules/bluetooth/backend-native.c @@ -59,6 +59,7 @@ struct transport_rfcomm { #define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1" #define HSP_AG_PROFILE "/Profile/HSPAGProfile" +#define HFP_AG_PROFILE "/Profile/HFPAGProfile" #define PROFILE_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ @@ -324,6 +325,7 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, DBusMessageIter arg_i; char *pathfd; struct transport_rfcomm *trfc; + bool is_hfp; if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oha{sv}")) { pa_log_error("Invalid signature found in NewConnection"); @@ -331,7 +333,13 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, } handler = dbus_message_get_path(m); - pa_assert(pa_streq(handler, HSP_AG_PROFILE)); + + if (pa_streq(handler, HFP_AG_PROFILE)) + is_hfp = true; + else if (pa_streq(handler, HSP_AG_PROFILE)) + is_hfp = false; + else + pa_assert_not_reached(); pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_OBJECT_PATH); dbus_message_iter_get_basic(&arg_i, &path); @@ -351,7 +359,11 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, sender = dbus_message_get_sender(m); - p = PA_BLUETOOTH_PROFILE_HSP_HS; + if (is_hfp) + p = PA_BLUETOOTH_PROFILE_HFP_HF; + else + p = PA_BLUETOOTH_PROFILE_HSP_HS; + pathfd = pa_sprintf_malloc ("%s/fd%d", path, fd); t = pa_bluetooth_transport_new(d, sender, pathfd, p, NULL, 0); pa_xfree(pathfd); @@ -403,7 +415,8 @@ static DBusHandlerResult profile_handler(DBusConnection *c, DBusMessage *m, void pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member); - if (!pa_streq(path, HSP_AG_PROFILE)) + if (!pa_streq(path, HSP_AG_PROFILE) + && !pa_streq(path, HFP_AG_PROFILE)) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { @@ -442,6 +455,10 @@ static void profile_init(pa_bluetooth_backend *b, pa_bluetooth_profile_t profile object_name = HSP_AG_PROFILE; uuid = PA_BLUETOOTH_UUID_HSP_AG; break; + case PA_BLUETOOTH_PROFILE_HFP_HF: + object_name = HFP_AG_PROFILE; + uuid = PA_BLUETOOTH_UUID_HFP_AG; + break; default: pa_assert_not_reached(); break; @@ -458,6 +475,9 @@ static void profile_done(pa_bluetooth_backend *b, pa_bluetooth_profile_t profile case PA_BLUETOOTH_PROFILE_HSP_HS: dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HSP_AG_PROFILE); break; + case PA_BLUETOOTH_PROFILE_HFP_HF: + dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HFP_AG_PROFILE); + break; default: pa_assert_not_reached(); break; @@ -484,6 +504,8 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d backend->discovery = y; profile_init(backend, PA_BLUETOOTH_PROFILE_HSP_HS); + if (!disable_profile_hfp) + profile_init(backend, PA_BLUETOOTH_PROFILE_HFP_HF); return backend; } @@ -494,6 +516,7 @@ void pa_bluetooth_native_backend_free(pa_bluetooth_backend *backend) { pa_dbus_free_pending_list(&backend->pending); profile_done(backend, PA_BLUETOOTH_PROFILE_HSP_HS); + profile_done(backend, PA_BLUETOOTH_PROFILE_HFP_HF); pa_dbus_connection_unref(backend->connection); diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index 959d447..e4a50a7 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -76,6 +76,8 @@ " </interface>" \ "</node>" +bool disable_profile_hfp = false; + struct pa_bluetooth_discovery { PA_REFCNT_DECLARE; @@ -175,8 +177,9 @@ static bool device_supports_profile(pa_bluetooth_device *device, pa_bluetooth_pr case PA_BLUETOOTH_PROFILE_A2DP_SOURCE: return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE); case PA_BLUETOOTH_PROFILE_HSP_HS: - return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS) - || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_HF); + return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS); + case PA_BLUETOOTH_PROFILE_HFP_HF: + return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_HF); case PA_BLUETOOTH_PROFILE_HFP_AG: return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_AG) || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_AG); @@ -1264,6 +1267,8 @@ const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) { return "a2dp_source"; case PA_BLUETOOTH_PROFILE_HSP_HS: return "headset_head_unit"; + case PA_BLUETOOTH_PROFILE_HFP_HF: + return "headset_handsfree"; case PA_BLUETOOTH_PROFILE_HFP_AG: return "headset_audio_gateway"; case PA_BLUETOOTH_PROFILE_OFF: diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h index badc999..73fe598 100644 --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -47,6 +47,7 @@ typedef enum profile { PA_BLUETOOTH_PROFILE_A2DP_SINK, PA_BLUETOOTH_PROFILE_A2DP_SOURCE, PA_BLUETOOTH_PROFILE_HSP_HS, + PA_BLUETOOTH_PROFILE_HFP_HF, PA_BLUETOOTH_PROFILE_HFP_AG, PA_BLUETOOTH_PROFILE_OFF } pa_bluetooth_profile_t; @@ -117,6 +118,8 @@ struct pa_bluetooth_adapter { bool valid; }; +extern bool disable_profile_hfp; /* globally disable HFP */ + #ifdef HAVE_BLUEZ_5_OFONO_HEADSET pa_bluetooth_backend *pa_bluetooth_ofono_backend_new(pa_core *c, pa_bluetooth_discovery *y); void pa_bluetooth_ofono_backend_free(pa_bluetooth_backend *b); diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c index fe8aec9..b5a0693 100644 --- a/src/modules/bluetooth/module-bluetooth-discover.c +++ b/src/modules/bluetooth/module-bluetooth-discover.c @@ -32,7 +32,8 @@ PA_MODULE_DESCRIPTION("Detect available Bluetooth daemon and load the correspond PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); PA_MODULE_USAGE( - "headset=ofono|native|auto (bluez 5 only)" + "headset=ofono|native|auto (bluez 5 only) " + "disable_profile_hfp=<don't register HFP, only HSP> (bluez 5 only)" ); struct userdata { diff --git a/src/modules/bluetooth/module-bluetooth-policy.c b/src/modules/bluetooth/module-bluetooth-policy.c index 68c8ab4..71489ed 100644 --- a/src/modules/bluetooth/module-bluetooth-policy.c +++ b/src/modules/bluetooth/module-bluetooth-policy.c @@ -341,7 +341,8 @@ static pa_hook_result_t profile_available_hook_callback(pa_core *c, pa_card_prof /* Do not automatically switch profiles for headsets, just in case */ /* TODO: remove a2dp and hsp when we remove BlueZ 4 support */ if (pa_streq(profile->name, "hsp") || pa_streq(profile->name, "a2dp") || pa_streq(profile->name, "a2dp_sink") || - pa_streq(profile->name, "headset_head_unit")) + pa_streq(profile->name, "headset_head_unit") || + pa_streq(profile->name, "headset_handsfree")) return PA_HOOK_OK; is_active_profile = card->active_profile == profile; diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index 4a59f4b..6a8fd75 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -54,7 +54,10 @@ PA_MODULE_AUTHOR("João Paulo Rechi Vita"); PA_MODULE_DESCRIPTION("BlueZ 5 Bluetooth audio sink and source"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(false); -PA_MODULE_USAGE("path=<device object path>"); +PA_MODULE_USAGE( + "path=<device object path> " + "disable_profile_hfp=<if true only register HSP?>" +); #define MAX_PLAYBACK_CATCH_UP_USEC (100 * PA_USEC_PER_MSEC) #define FIXED_LATENCY_PLAYBACK_A2DP (25 * PA_USEC_PER_MSEC) @@ -68,6 +71,7 @@ PA_MODULE_USAGE("path=<device object path>"); static const char* const valid_modargs[] = { "path", + "disable_profile_hfp", NULL }; @@ -246,6 +250,7 @@ static int sco_process_render(struct userdata *u) { pa_assert(u); pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS || + u->profile == PA_BLUETOOTH_PROFILE_HFP_HF || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG); pa_assert(u->sink); @@ -306,6 +311,7 @@ static int sco_process_push(struct userdata *u) { pa_assert(u); pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS || + u->profile == PA_BLUETOOTH_PROFILE_HFP_HF|| u->profile == PA_BLUETOOTH_PROFILE_HFP_AG); pa_assert(u->source); pa_assert(u->read_smoother); @@ -766,7 +772,9 @@ static void transport_release(struct userdata *u) { /* Run from I/O thread */ static void transport_config_mtu(struct userdata *u) { - if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) { + if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF + || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) { u->read_block_size = u->read_link_mtu; u->write_block_size = u->write_link_mtu; } else { @@ -916,7 +924,8 @@ static void source_set_volume_cb(pa_source *s) { pa_assert(u); pa_assert(u->source == s); - pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS); + pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF); if (u->transport->set_microphone_gain == NULL) return; @@ -951,7 +960,8 @@ static int add_source(struct userdata *u) { data.namereg_fail = false; pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile)); pa_source_new_data_set_sample_spec(&data, &u->sample_spec); - if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS) + if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF) pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); connect_ports(u, &data, PA_DIRECTION_INPUT); @@ -964,6 +974,7 @@ static int add_source(struct userdata *u) { break; case PA_BLUETOOTH_PROFILE_A2DP_SINK: case PA_BLUETOOTH_PROFILE_HSP_HS: + case PA_BLUETOOTH_PROFILE_HFP_HF: case PA_BLUETOOTH_PROFILE_OFF: pa_assert_not_reached(); break; @@ -979,7 +990,8 @@ static int add_source(struct userdata *u) { u->source->userdata = u; u->source->parent.process_msg = source_process_msg; - if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS) { + if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF) { pa_source_set_set_volume_callback(u->source, source_set_volume_cb); u->source->n_volume_steps = 16; } @@ -1073,7 +1085,8 @@ static void sink_set_volume_cb(pa_sink *s) { pa_assert(u); pa_assert(u->sink == s); - pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS); + pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF); if (u->transport->set_speaker_gain == NULL) return; @@ -1108,7 +1121,8 @@ static int add_sink(struct userdata *u) { data.namereg_fail = false; pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile)); pa_sink_new_data_set_sample_spec(&data, &u->sample_spec); - if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS) + if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF) pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); connect_ports(u, &data, PA_DIRECTION_OUTPUT); @@ -1122,6 +1136,7 @@ static int add_sink(struct userdata *u) { /* Profile switch should have failed */ case PA_BLUETOOTH_PROFILE_A2DP_SOURCE: case PA_BLUETOOTH_PROFILE_HSP_HS: + case PA_BLUETOOTH_PROFILE_HFP_HF: case PA_BLUETOOTH_PROFILE_OFF: pa_assert_not_reached(); break; @@ -1137,7 +1152,8 @@ static int add_sink(struct userdata *u) { u->sink->userdata = u; u->sink->parent.process_msg = sink_process_msg; - if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS) { + if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF) { pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb); u->sink->n_volume_steps = 16; } @@ -1146,7 +1162,9 @@ static int add_sink(struct userdata *u) { /* Run from main thread */ static void transport_config(struct userdata *u) { - if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) { + if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS + || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF + || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) { u->sample_spec.format = PA_SAMPLE_S16LE; u->sample_spec.channels = 1; u->sample_spec.rate = 8000; @@ -1292,6 +1310,7 @@ static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) { [PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT, [PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT, [PA_BLUETOOTH_PROFILE_HSP_HS] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, + [PA_BLUETOOTH_PROFILE_HFP_HF] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, [PA_BLUETOOTH_PROFILE_HFP_AG] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, [PA_BLUETOOTH_PROFILE_OFF] = 0 }; @@ -1811,7 +1830,20 @@ static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_pro break; case PA_BLUETOOTH_PROFILE_HSP_HS: - cp = pa_card_profile_new(name, _("Headset Head Unit (HSP/HFP)"), sizeof(pa_bluetooth_profile_t)); + cp = pa_card_profile_new(name, _("Headset Head Unit (HSP)"), sizeof(pa_bluetooth_profile_t)); + cp->priority = 20; + cp->n_sinks = 1; + cp->n_sources = 1; + cp->max_sink_channels = 1; + cp->max_source_channels = 1; + pa_hashmap_put(input_port->profiles, cp->name, cp); + pa_hashmap_put(output_port->profiles, cp->name, cp); + + p = PA_CARD_PROFILE_DATA(cp); + break; + + case PA_BLUETOOTH_PROFILE_HFP_HF: + cp = pa_card_profile_new(name, _("Headset Handsfree (HFP)"), sizeof(pa_bluetooth_profile_t)); cp->priority = 20; cp->n_sinks = 1; cp->n_sources = 1; @@ -1897,8 +1929,10 @@ static int uuid_to_profile(const char *uuid, pa_bluetooth_profile_t *_r) { *_r = PA_BLUETOOTH_PROFILE_A2DP_SINK; else if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE)) *_r = PA_BLUETOOTH_PROFILE_A2DP_SOURCE; - else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF)) + else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS)) *_r = PA_BLUETOOTH_PROFILE_HSP_HS; + else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF)) + *_r = PA_BLUETOOTH_PROFILE_HFP_HF; else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG)) *_r = PA_BLUETOOTH_PROFILE_HFP_AG; else @@ -1917,6 +1951,7 @@ static int add_card(struct userdata *u) { pa_bluetooth_profile_t *p; const char *uuid; void *state; + bool has_both; pa_assert(u); pa_assert(u->device); @@ -1947,9 +1982,20 @@ static int add_card(struct userdata *u) { create_card_ports(u, data.ports); + has_both = !disable_profile_hfp && pa_hashmap_get(d->uuids, PA_BLUETOOTH_UUID_HFP_HF) && pa_hashmap_get(d->uuids, PA_BLUETOOTH_UUID_HSP_HS); PA_HASHMAP_FOREACH(uuid, d->uuids, state) { pa_bluetooth_profile_t profile; + if (disable_profile_hfp && pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF)) { + pa_log_info("device supports HFP but disabling profile as requested"); + continue; + } + + if (has_both && pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS)) { + pa_log_info("device support HSP and HFP, selecting HFP only"); + continue; + } + if (uuid_to_profile(uuid, &profile) < 0) continue; @@ -2170,6 +2216,11 @@ int pa__init(pa_module* m) { goto fail_free_modargs; } + if (pa_modargs_get_value_boolean(ma, "disable_profile_hfp", &disable_profile_hfp) < 0) { + pa_log_error("disable_profile_hfp must be either true or false"); + goto fail_free_modargs; + } + if ((u->discovery = pa_shared_get(u->core, "bluetooth-discovery"))) pa_bluetooth_discovery_ref(u->discovery); else { diff --git a/src/modules/bluetooth/module-bluez5-discover.c b/src/modules/bluetooth/module-bluez5-discover.c index 080e5d0..5b81380 100644 --- a/src/modules/bluetooth/module-bluez5-discover.c +++ b/src/modules/bluetooth/module-bluez5-discover.c @@ -37,11 +37,13 @@ PA_MODULE_DESCRIPTION("Detect available BlueZ 5 Bluetooth audio devices and load PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); PA_MODULE_USAGE( - "headset=ofono|native|auto" + "headset=ofono|native|auto " + "disable_profile_hfp=<disable registration of HFP?>" ); static const char* const valid_modargs[] = { "headset", + "disable_profile_hfp", NULL }; @@ -51,6 +53,7 @@ struct userdata { pa_hashmap *loaded_device_paths; pa_hook_slot *device_connection_changed_slot; pa_bluetooth_discovery *discovery; + bool disable_profile_hfp; }; static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) { @@ -71,7 +74,7 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, if (!module_loaded && pa_bluetooth_device_any_transport_connected(d)) { /* a new device has been connected */ pa_module *m; - char *args = pa_sprintf_malloc("path=%s", d->path); + char *args = pa_sprintf_malloc("path=%s disable_profile_hfp=%s", d->path, u->disable_profile_hfp ? "true" : "false"); pa_log_debug("Loading module-bluez5-device %s", args); m = pa_module_load(u->module->core, "module-bluez5-device", args); @@ -126,6 +129,12 @@ int pa__init(pa_module *m) { u->core = m->core; u->loaded_device_paths = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + u->disable_profile_hfp = false; + if (pa_modargs_get_value_boolean(ma, "disable_profile_hfp", &u->disable_profile_hfp) < 0) { + pa_log("disable_profile_hfp must be either true or false"); + goto fail; + } + if (!(u->discovery = pa_bluetooth_discovery_get(u->core, headset_backend))) goto fail; -- 2.6.6