mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
bluez5: Add HFP HF support
This commit is contained in:
parent
d6afdda898
commit
0b2d3730b6
2 changed files with 180 additions and 2 deletions
|
|
@ -54,7 +54,7 @@ struct spa_bt_backend {
|
||||||
struct spa_dbus *dbus;
|
struct spa_dbus *dbus;
|
||||||
DBusConnection *conn;
|
DBusConnection *conn;
|
||||||
|
|
||||||
#define DEFAULT_ENABLED_PROFILES (SPA_BT_PROFILE_HEADSET_HEAD_UNIT | SPA_BT_PROFILE_HFP_AG)
|
#define DEFAULT_ENABLED_PROFILES (SPA_BT_PROFILE_HSP_HS | SPA_BT_PROFILE_HFP_AG)
|
||||||
enum spa_bt_profile enabled_profiles;
|
enum spa_bt_profile enabled_profiles;
|
||||||
|
|
||||||
struct spa_source sco;
|
struct spa_source sco;
|
||||||
|
|
@ -67,6 +67,16 @@ struct transport_data {
|
||||||
struct spa_source sco;
|
struct spa_source sco;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum hfp_hf_state {
|
||||||
|
hfp_hf_brsf,
|
||||||
|
hfp_hf_bac,
|
||||||
|
hfp_hf_cind1,
|
||||||
|
hfp_hf_cind2,
|
||||||
|
hfp_hf_cmer,
|
||||||
|
hfp_hf_slc,
|
||||||
|
hfp_hf_bcs
|
||||||
|
};
|
||||||
|
|
||||||
struct rfcomm {
|
struct rfcomm {
|
||||||
struct spa_list link;
|
struct spa_list link;
|
||||||
struct spa_source source;
|
struct spa_source source;
|
||||||
|
|
@ -80,6 +90,8 @@ struct rfcomm {
|
||||||
unsigned int slc_configured:1;
|
unsigned int slc_configured:1;
|
||||||
unsigned int codec_negotiation_supported:1;
|
unsigned int codec_negotiation_supported:1;
|
||||||
unsigned int msbc_supported_by_hfp:1;
|
unsigned int msbc_supported_by_hfp:1;
|
||||||
|
enum hfp_hf_state hf_state;
|
||||||
|
unsigned int codec;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -152,6 +164,22 @@ static void rfcomm_free(struct rfcomm *rfcomm)
|
||||||
free(rfcomm);
|
free(rfcomm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void rfcomm_send_cmd(struct spa_source *source, char *data)
|
||||||
|
{
|
||||||
|
struct rfcomm *rfcomm = source->data;
|
||||||
|
struct spa_bt_backend *backend = rfcomm->backend;
|
||||||
|
char message[256];
|
||||||
|
ssize_t len;
|
||||||
|
|
||||||
|
spa_log_debug(backend->log, NAME": RFCOMM >> %s", data);
|
||||||
|
sprintf(message, "%s\n", data);
|
||||||
|
len = write(source->fd, message, strlen(message));
|
||||||
|
/* we ignore any errors, it's not critical and real errors should
|
||||||
|
* be caught with the HANGUP and ERROR events handled above */
|
||||||
|
if (len < 0)
|
||||||
|
spa_log_error(backend->log, NAME": RFCOMM write error: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
static void rfcomm_send_reply(struct spa_source *source, char *data)
|
static void rfcomm_send_reply(struct spa_source *source, char *data)
|
||||||
{
|
{
|
||||||
struct rfcomm *rfcomm = source->data;
|
struct rfcomm *rfcomm = source->data;
|
||||||
|
|
@ -402,6 +430,95 @@ static bool rfcomm_hfp_ag(struct spa_source *source, char* buf)
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool rfcomm_hfp_hf(struct spa_source *source, char* buf)
|
||||||
|
{
|
||||||
|
struct rfcomm *rfcomm = source->data;
|
||||||
|
struct spa_bt_backend *backend = rfcomm->backend;
|
||||||
|
unsigned int features;
|
||||||
|
unsigned int gain;
|
||||||
|
unsigned int selected_codec;
|
||||||
|
char* token;
|
||||||
|
char separators[] = "\r\n:";
|
||||||
|
|
||||||
|
token = strtok(buf, separators);
|
||||||
|
while (token != NULL)
|
||||||
|
{
|
||||||
|
if (strncmp(token, "+BRSF", 5) == 0) {
|
||||||
|
/* get next token */
|
||||||
|
token = strtok(NULL, separators);
|
||||||
|
features = atoi(token);
|
||||||
|
if (((features & (SPA_BT_HFP_HF_FEATURE_CODEC_NEGOTIATION)) != 0) &&
|
||||||
|
rfcomm->msbc_supported_by_hfp)
|
||||||
|
rfcomm->codec_negotiation_supported = true;
|
||||||
|
} else if (strncmp(token, "+BCS", 4) == 0) {
|
||||||
|
char *cmd;
|
||||||
|
|
||||||
|
/* get next token */
|
||||||
|
token = strtok(NULL, separators);
|
||||||
|
selected_codec = atoi(token);
|
||||||
|
|
||||||
|
/* send codec selection to AG */
|
||||||
|
cmd = spa_aprintf("AT+BCS=%u", selected_codec);
|
||||||
|
rfcomm_send_cmd(source, cmd);
|
||||||
|
free(cmd);
|
||||||
|
rfcomm->hf_state = hfp_hf_bcs;
|
||||||
|
} else if (strncmp(token, "+CIND", 5) == 0) {
|
||||||
|
/* get next token and discard it */
|
||||||
|
token = strtok(NULL, separators);
|
||||||
|
} else if (strncmp(token, "+VGM", 4) == 0) {
|
||||||
|
/* get next token */
|
||||||
|
token = strtok(NULL, separators);
|
||||||
|
gain = atoi(token);
|
||||||
|
/* t->speaker_gain = gain; */
|
||||||
|
} else if (strncmp(token, "+VGS", 4) == 0) {
|
||||||
|
/* get next token */
|
||||||
|
token = strtok(NULL, separators);
|
||||||
|
gain = atoi(token);
|
||||||
|
/* t->microphone_gain = gain; */
|
||||||
|
} else if (strncmp(token, "OK", 5) == 0) {
|
||||||
|
switch(rfcomm->hf_state) {
|
||||||
|
case hfp_hf_brsf:
|
||||||
|
if (rfcomm->codec_negotiation_supported) {
|
||||||
|
rfcomm_send_cmd(source, "AT+BAC=1,2");
|
||||||
|
rfcomm->hf_state = hfp_hf_bac;
|
||||||
|
} else {
|
||||||
|
rfcomm_send_cmd(source, "AT+CIND=?");
|
||||||
|
rfcomm->hf_state = hfp_hf_cind1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case hfp_hf_bac:
|
||||||
|
rfcomm_send_cmd(source, "AT+CIND=?");
|
||||||
|
rfcomm->hf_state = hfp_hf_cind1;
|
||||||
|
break;
|
||||||
|
case hfp_hf_cind1:
|
||||||
|
rfcomm_send_cmd(source, "AT+CIND?");
|
||||||
|
rfcomm->hf_state = hfp_hf_cind2;
|
||||||
|
break;
|
||||||
|
case hfp_hf_cind2:
|
||||||
|
rfcomm_send_cmd(source, "AT+CMER");
|
||||||
|
rfcomm->hf_state = hfp_hf_cmer;
|
||||||
|
break;
|
||||||
|
case hfp_hf_cmer:
|
||||||
|
rfcomm->hf_state = hfp_hf_slc;
|
||||||
|
rfcomm->slc_configured = true;
|
||||||
|
rfcomm->transport = _transport_create(rfcomm);
|
||||||
|
if (rfcomm->transport == NULL) {
|
||||||
|
spa_log_warn(backend->log, NAME": can't create transport: %m");
|
||||||
|
// TODO: We should manage the missing transport
|
||||||
|
}
|
||||||
|
spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* get next token */
|
||||||
|
token = strtok(NULL, separators);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void rfcomm_event(struct spa_source *source)
|
static void rfcomm_event(struct spa_source *source)
|
||||||
|
|
@ -439,6 +556,8 @@ static void rfcomm_event(struct spa_source *source)
|
||||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||||
if (rfcomm->profile == SPA_BT_PROFILE_HFP_HF)
|
if (rfcomm->profile == SPA_BT_PROFILE_HFP_HF)
|
||||||
res = rfcomm_hfp_ag(source, buf);
|
res = rfcomm_hfp_ag(source, buf);
|
||||||
|
else if (rfcomm->profile == SPA_BT_PROFILE_HFP_AG)
|
||||||
|
res = rfcomm_hfp_hf(source, buf);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!res) {
|
if (!res) {
|
||||||
|
|
@ -781,6 +900,8 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag
|
||||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||||
if (strcmp(handler, PROFILE_HFP_AG) == 0)
|
if (strcmp(handler, PROFILE_HFP_AG) == 0)
|
||||||
profile = SPA_BT_PROFILE_HFP_HF;
|
profile = SPA_BT_PROFILE_HFP_HF;
|
||||||
|
else if (strcmp(handler, PROFILE_HFP_HF) == 0)
|
||||||
|
profile = SPA_BT_PROFILE_HFP_AG;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (profile == SPA_BT_PROFILE_NULL) {
|
if (profile == SPA_BT_PROFILE_NULL) {
|
||||||
|
|
@ -827,6 +948,31 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag
|
||||||
spa_bt_device_connect_profile(t->device, profile);
|
spa_bt_device_connect_profile(t->device, profile);
|
||||||
|
|
||||||
spa_log_debug(backend->log, NAME": Transport %s available for profile %s", t->path, handler);
|
spa_log_debug(backend->log, NAME": Transport %s available for profile %s", t->path, handler);
|
||||||
|
} else if (profile == SPA_BT_PROFILE_HFP_AG) {
|
||||||
|
/* Start SLC connection */
|
||||||
|
unsigned int hf_features = SPA_BT_HFP_HF_FEATURE_NONE;
|
||||||
|
char *cmd;
|
||||||
|
|
||||||
|
/* Decide if we want to signal that the HF supports mSBC negotiation
|
||||||
|
This should be done when
|
||||||
|
a) mSBC support is enabled in config file and
|
||||||
|
b) the bluetooth adapter supports the necessary transport mode */
|
||||||
|
if ((backend->msbc_support_enabled_in_config == true) &&
|
||||||
|
(device_supports_required_mSBC_transport_modes(backend, rfcomm->device))) {
|
||||||
|
/* set the feature bit that indicates HF supports codec negotiation */
|
||||||
|
hf_features |= SPA_BT_HFP_HF_FEATURE_CODEC_NEGOTIATION;
|
||||||
|
rfcomm->msbc_supported_by_hfp = true;
|
||||||
|
rfcomm->codec_negotiation_supported = false;
|
||||||
|
} else {
|
||||||
|
rfcomm->msbc_supported_by_hfp = false;
|
||||||
|
rfcomm->codec_negotiation_supported = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* send command to AG with the features supported by Hands-Free */
|
||||||
|
cmd = spa_aprintf("AT+BRSF=%u", hf_features);
|
||||||
|
rfcomm_send_cmd(&rfcomm->source, cmd);
|
||||||
|
free(cmd);
|
||||||
|
rfcomm->hf_state = hfp_hf_brsf;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((r = dbus_message_new_method_return(m)) == NULL)
|
if ((r = dbus_message_new_method_return(m)) == NULL)
|
||||||
|
|
@ -871,6 +1017,8 @@ static DBusHandlerResult profile_request_disconnection(DBusConnection *conn, DBu
|
||||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||||
if (strcmp(handler, PROFILE_HFP_AG) == 0)
|
if (strcmp(handler, PROFILE_HFP_AG) == 0)
|
||||||
profile = SPA_BT_PROFILE_HFP_HF;
|
profile = SPA_BT_PROFILE_HFP_HF;
|
||||||
|
else if (strcmp(handler, PROFILE_HFP_HF) == 0)
|
||||||
|
profile = SPA_BT_PROFILE_HFP_AG;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (profile == SPA_BT_PROFILE_NULL) {
|
if (profile == SPA_BT_PROFILE_NULL) {
|
||||||
|
|
@ -1041,6 +1189,27 @@ static int register_profile(struct spa_bt_backend *backend, const char *profile,
|
||||||
dbus_message_iter_close_container(&it[2], &it[3]);
|
dbus_message_iter_close_container(&it[2], &it[3]);
|
||||||
dbus_message_iter_close_container(&it[1], &it[2]);
|
dbus_message_iter_close_container(&it[1], &it[2]);
|
||||||
|
|
||||||
|
/* HFP version 1.7 */
|
||||||
|
str = "Version";
|
||||||
|
version = 0x0107;
|
||||||
|
dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]);
|
||||||
|
dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str);
|
||||||
|
dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]);
|
||||||
|
dbus_message_iter_append_basic(&it[3], DBUS_TYPE_UINT16, &version);
|
||||||
|
dbus_message_iter_close_container(&it[2], &it[3]);
|
||||||
|
dbus_message_iter_close_container(&it[1], &it[2]);
|
||||||
|
} else if (strcmp(uuid, SPA_BT_UUID_HFP_HF) == 0) {
|
||||||
|
str = "Features";
|
||||||
|
features = SPA_BT_HFP_SDP_HF_FEATURE_NONE;
|
||||||
|
if (backend->msbc_support_enabled_in_config == true)
|
||||||
|
features |= SPA_BT_HFP_SDP_HF_FEATURE_WIDEBAND_SPEECH;
|
||||||
|
dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]);
|
||||||
|
dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str);
|
||||||
|
dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]);
|
||||||
|
dbus_message_iter_append_basic(&it[3], DBUS_TYPE_UINT16, &features);
|
||||||
|
dbus_message_iter_close_container(&it[2], &it[3]);
|
||||||
|
dbus_message_iter_close_container(&it[1], &it[2]);
|
||||||
|
|
||||||
/* HFP version 1.7 */
|
/* HFP version 1.7 */
|
||||||
str = "Version";
|
str = "Version";
|
||||||
version = 0x0107;
|
version = 0x0107;
|
||||||
|
|
@ -1068,6 +1237,7 @@ void backend_native_register_profiles(struct spa_bt_backend *backend)
|
||||||
|
|
||||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||||
register_profile(backend, PROFILE_HFP_AG, SPA_BT_UUID_HFP_AG);
|
register_profile(backend, PROFILE_HFP_AG, SPA_BT_UUID_HFP_AG);
|
||||||
|
register_profile(backend, PROFILE_HFP_HF, SPA_BT_UUID_HFP_HF);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1090,6 +1260,7 @@ void backend_native_free(struct spa_bt_backend *backend)
|
||||||
|
|
||||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||||
dbus_connection_unregister_object_path(backend->conn, PROFILE_HFP_AG);
|
dbus_connection_unregister_object_path(backend->conn, PROFILE_HFP_AG);
|
||||||
|
dbus_connection_unregister_object_path(backend->conn, PROFILE_HFP_HF);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
spa_list_consume(rfcomm, &backend->rfcomm_list, link)
|
spa_list_consume(rfcomm, &backend->rfcomm_list, link)
|
||||||
|
|
@ -1192,6 +1363,12 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
|
||||||
&vtable_profile, backend)) {
|
&vtable_profile, backend)) {
|
||||||
goto fail2;
|
goto fail2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!dbus_connection_register_object_path(backend->conn,
|
||||||
|
PROFILE_HFP_HF,
|
||||||
|
&vtable_profile, backend)) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (backend->enabled_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)
|
if (backend->enabled_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,9 @@ properties = {
|
||||||
#
|
#
|
||||||
# Supported headset roles: hsp_hs (HSP Headset),
|
# Supported headset roles: hsp_hs (HSP Headset),
|
||||||
# hsp_ag (HSP Audio Gateway),
|
# hsp_ag (HSP Audio Gateway),
|
||||||
|
# hfp_hf (HFP Hands-Free),
|
||||||
# hfp_ag (HFP Audio Gateway)
|
# hfp_ag (HFP Audio Gateway)
|
||||||
#bluez5.headset-roles = [ hsp_hs hsp_ag hfp_ag ]
|
#bluez5.headset-roles = [ hsp_hs hsp_ag hfp_hf hfp_ag ]
|
||||||
|
|
||||||
# Enabled A2DP codecs (default: all).
|
# Enabled A2DP codecs (default: all).
|
||||||
#bluez5.codecs = [ sbc aac ldac aptx aptx_hd ]
|
#bluez5.codecs = [ sbc aac ldac aptx aptx_hd ]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue