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> diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c index cf88126..1c71572 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_HEADSET_HEAD_UNIT; + if (is_hfp) + p = PA_BLUETOOTH_PROFILE_HEADSET_HFP; + else + p = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT; + 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_HEADSET_HFP: + 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_HEADSET_HEAD_UNIT: dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HSP_AG_PROFILE); break; + case PA_BLUETOOTH_PROFILE_HEADSET_HFP: + dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HFP_AG_PROFILE); + break; default: pa_assert_not_reached(); break; @@ -484,6 +504,7 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d backend->discovery = y; profile_init(backend, PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT); + profile_init(backend, PA_BLUETOOTH_PROFILE_HEADSET_HFP); return backend; } @@ -494,6 +515,7 @@ void pa_bluetooth_native_backend_free(pa_bluetooth_backend *backend) { pa_dbus_free_pending_list(&backend->pending); profile_done(backend, PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT); + profile_done(backend, PA_BLUETOOTH_PROFILE_HEADSET_HFP); pa_dbus_connection_unref(backend->connection); diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index 7d63f35..da7184e 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -175,8 +175,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_HEADSET_HEAD_UNIT: - 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_HEADSET_HFP: + return !!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); @@ -1264,6 +1265,8 @@ const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) { return "a2dp_source"; case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT: return "headset_head_unit"; + case PA_BLUETOOTH_PROFILE_HEADSET_HFP: + return "headset_handsfree"; case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY: 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 7f124e2..2300b1f 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_HEADSET_HEAD_UNIT, + PA_BLUETOOTH_PROFILE_HEADSET_HFP, PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY, PA_BLUETOOTH_PROFILE_OFF } pa_bluetooth_profile_t; diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index b8b0493..2b9f7ab 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -246,6 +246,7 @@ static int sco_process_render(struct userdata *u) { pa_assert(u); pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || + u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HFP || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY); pa_assert(u->sink); @@ -306,6 +307,7 @@ static int sco_process_push(struct userdata *u) { pa_assert(u); pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || + u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HFP|| u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY); pa_assert(u->source); pa_assert(u->read_smoother); @@ -766,7 +768,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_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) { + if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT + || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HFP + || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) { u->read_block_size = u->read_link_mtu; u->write_block_size = u->write_link_mtu; } else { @@ -916,7 +920,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_HEADSET_HEAD_UNIT); + pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT + || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HFP); if (u->transport->set_microphone_gain == NULL) return; @@ -951,7 +956,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_HEADSET_HEAD_UNIT) + if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT + || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HFP) pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); connect_ports(u, &data, PA_DIRECTION_INPUT); @@ -964,6 +970,7 @@ static int add_source(struct userdata *u) { break; case PA_BLUETOOTH_PROFILE_A2DP_SINK: case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT: + case PA_BLUETOOTH_PROFILE_HEADSET_HFP: case PA_BLUETOOTH_PROFILE_OFF: pa_assert_not_reached(); break; @@ -979,7 +986,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_HEADSET_HEAD_UNIT) { + if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT + || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HFP) { pa_source_set_set_volume_callback(u->source, source_set_volume_cb); u->source->n_volume_steps = 16; } @@ -1073,7 +1081,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_HEADSET_HEAD_UNIT); + pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT + || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HFP); if (u->transport->set_speaker_gain == NULL) return; @@ -1108,7 +1117,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_HEADSET_HEAD_UNIT) + if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT + || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HFP) pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); connect_ports(u, &data, PA_DIRECTION_OUTPUT); @@ -1122,6 +1132,7 @@ static int add_sink(struct userdata *u) { /* Profile switch should have failed */ case PA_BLUETOOTH_PROFILE_A2DP_SOURCE: case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT: + case PA_BLUETOOTH_PROFILE_HEADSET_HFP: case PA_BLUETOOTH_PROFILE_OFF: pa_assert_not_reached(); break; @@ -1137,7 +1148,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_HEADSET_HEAD_UNIT) { + if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT + || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HFP) { pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb); u->sink->n_volume_steps = 16; } @@ -1146,7 +1158,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_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) { + if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT + || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HFP + || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) { u->sample_spec.format = PA_SAMPLE_S16LE; u->sample_spec.channels = 1; u->sample_spec.rate = 8000; @@ -1292,6 +1306,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_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, + [PA_BLUETOOTH_PROFILE_HEADSET_HFP] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, [PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, [PA_BLUETOOTH_PROFILE_OFF] = 0 }; @@ -1811,7 +1826,20 @@ static pa_card_profile *create_card_profile(struct userdata *u, pa_bluetooth_pro break; case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT: - 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_HEADSET_HFP: + 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 +1925,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_HEADSET_HEAD_UNIT; + else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF)) + *_r = PA_BLUETOOTH_PROFILE_HEADSET_HFP; else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG)) *_r = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY; else