mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-02 09:01:50 -05:00
bluez5: Added mSBC support in bluze5 backend native, including check to make sure the computer's bluetooth adapter
supports the transport modes required for mSBC
This commit is contained in:
parent
8aec26f5bd
commit
8ac5a89583
3 changed files with 167 additions and 8 deletions
|
|
@ -28,6 +28,8 @@
|
|||
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/sco.h>
|
||||
#include <bluetooth/hci.h>
|
||||
#include <bluetooth/hci_lib.h>
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
|
||||
|
|
@ -50,6 +52,7 @@ struct spa_bt_backend {
|
|||
DBusConnection *conn;
|
||||
|
||||
struct spa_list rfcomm_list;
|
||||
unsigned int msbc_support_enabled_in_config:1;
|
||||
};
|
||||
|
||||
struct transport_data {
|
||||
|
|
@ -66,6 +69,8 @@ struct rfcomm {
|
|||
char* path;
|
||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||
unsigned int slc_configured:1;
|
||||
unsigned int codec_negotiation_supported:1;
|
||||
unsigned int msbc_supported_by_hfp:1;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
@ -151,11 +156,11 @@ static bool rfcomm_hsp(struct spa_source *source, char* buf)
|
|||
* it does not expect a reply. */
|
||||
if (sscanf(buf, "AT+VGS=%d", &gain) == 1 ||
|
||||
sscanf(buf, "\r\n+VGM=%d\r\n", &gain) == 1) {
|
||||
// t->speaker_gain = gain;
|
||||
/* t->speaker_gain = gain; */
|
||||
rfcomm_send_reply(source, "OK");
|
||||
} else if (sscanf(buf, "AT+VGM=%d", &gain) == 1 ||
|
||||
sscanf(buf, "\r\n+VGS=%d\r\n", &gain) == 1) {
|
||||
// t->microphone_gain = gain;
|
||||
/* t->microphone_gain = gain; */
|
||||
rfcomm_send_reply(source, "OK");
|
||||
} else if (sscanf(buf, "AT+CKPD=%d", &dummy) == 1) {
|
||||
rfcomm_send_reply(source, "OK");
|
||||
|
|
@ -168,24 +173,137 @@ static bool rfcomm_hsp(struct spa_source *source, char* buf)
|
|||
#endif
|
||||
|
||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||
static bool device_supports_required_mSBC_transport_modes(
|
||||
struct spa_bt_backend *backend, struct spa_bt_device *device) {
|
||||
|
||||
const char *src_addr;
|
||||
bdaddr_t src;
|
||||
int i;
|
||||
uint8_t features[8], max_page = 0;
|
||||
int device_id;
|
||||
int sock;
|
||||
|
||||
if (device->adapter == NULL)
|
||||
return false;
|
||||
|
||||
spa_log_debug(backend->log, NAME": Entering function");
|
||||
|
||||
src_addr = device->adapter->address;
|
||||
|
||||
/* don't use ba2str to avoid -lbluetooth */
|
||||
for (i = 5; i >= 0; i--, src_addr += 3)
|
||||
src.b[i] = strtol(src_addr, NULL, 16);
|
||||
|
||||
device_id = hci_get_route(&src);
|
||||
sock = hci_open_dev(device_id);
|
||||
if (sock < 0) {
|
||||
spa_log_error(backend->log, NAME": Error opening device hci%d: %s (%d)\n",
|
||||
device_id, strerror(errno), errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hci_read_local_ext_features(sock, 0, &max_page, features, 1000) < 0) {
|
||||
spa_log_error(backend->log, NAME": Error reading extended features hci%d: %s (%d)\n",
|
||||
device_id, strerror(errno), errno);
|
||||
hci_close_dev(sock);
|
||||
return false;
|
||||
}
|
||||
hci_close_dev(sock);
|
||||
|
||||
if (!(features[2] & LMP_TRSP_SCO)) {
|
||||
/* When adapater support, then the LMP_TRSP_SCO bit in features[2] is set*/
|
||||
spa_log_info(backend->log,
|
||||
NAME": bluetooth host adapter not capable of Transparent SCO LMP_TRSP_SCO" );
|
||||
return false;
|
||||
|
||||
} else if (!(features[3] & LMP_ESCO)) {
|
||||
/* When adapater support, then the LMP_ESCO bit in features[3] is set*/
|
||||
spa_log_info(backend->log,
|
||||
NAME": bluetooth host adapter not capable of eSCO link mode (LMP_ESCO)" );
|
||||
return false;
|
||||
|
||||
} else {
|
||||
spa_log_info(backend->log,
|
||||
NAME": bluetooth host adapter supports eSCO link and Transparent Data mode" );
|
||||
return true;
|
||||
}
|
||||
|
||||
spa_log_debug(backend->log, NAME": Fallthrough - we should not be here");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool rfcomm_hfp_ag(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;
|
||||
|
||||
|
||||
if (sscanf(buf, "AT+BRSF=%u", &features) == 1) {
|
||||
|
||||
unsigned int ag_features = SPA_BT_HFP_AG_FEATURE_NONE;
|
||||
char *cmd;
|
||||
|
||||
/* TODO: retrieve HF supported features */
|
||||
/* Decide if we want to signal that the computer supports mSBC negotiation
|
||||
This should be done when
|
||||
a) mSBC support is enabled in config file and
|
||||
b) the computers 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 AG (=computer) supports codec negotiation */
|
||||
ag_features |= SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION;
|
||||
|
||||
/* let's see if the headset supports codec negotiation */
|
||||
if ((features & (SPA_BT_HFP_HF_FEATURE_CODEC_NEGOTIATION)) != 0) {
|
||||
spa_log_debug(backend->log,
|
||||
NAME": RFCOMM features = %i, codec negotiation supported by headset",
|
||||
features);
|
||||
/* Prepare reply: Audio Gateway (=computer) supports codec negotiation */
|
||||
rfcomm->codec_negotiation_supported = true;
|
||||
} else {
|
||||
/* Codec negotiation not supported */
|
||||
spa_log_debug(backend->log,
|
||||
NAME": RFCOMM features = %i, codec negotiation NOT supported by headset",
|
||||
features);
|
||||
|
||||
rfcomm->codec_negotiation_supported = false;
|
||||
rfcomm->msbc_supported_by_hfp = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* send reply to HF with the features supported by Audio Gateway (=computer) */
|
||||
cmd = spa_aprintf("+BRSF: %d", ag_features);
|
||||
rfcomm_send_reply(source, cmd);
|
||||
free(cmd);
|
||||
rfcomm_send_reply(source, "OK");
|
||||
} else if (strncmp(buf, "AT+BAC=", 7) == 0) {
|
||||
/* TODO: retrieve supported codecs */
|
||||
/* retrieve supported codecs */
|
||||
/* response has the form AT+BAC=<codecID1>,<codecID2>,<codecIDx>
|
||||
strategy: split the string into tokens */
|
||||
char* token;
|
||||
char seperators[] = "=,";
|
||||
int cntr = 0;
|
||||
token = strtok (buf, seperators);
|
||||
while (token != NULL)
|
||||
{
|
||||
/* skip token 0 i.e. the "AT+BAC=" part */
|
||||
if (cntr > 0) {
|
||||
int codec_id;
|
||||
sscanf (token, "%u", &codec_id);
|
||||
spa_log_debug(backend->log, NAME": RFCOMM AT+BAC found codec %u", codec_id);
|
||||
if (codec_id == HFP_AUDIO_CODEC_MSBC) {
|
||||
rfcomm->msbc_supported_by_hfp = true;
|
||||
spa_log_debug(backend->log, NAME": RFCOMM headset supports mSBC codec");
|
||||
}
|
||||
}
|
||||
/* get next token */
|
||||
token = strtok (NULL, seperators);
|
||||
cntr++;
|
||||
}
|
||||
|
||||
rfcomm_send_reply(source, "OK");
|
||||
} else if (strncmp(buf, "AT+CIND=?", 9) == 0) {
|
||||
rfcomm_send_reply(source, "+CIND:(\"service\",(0-1)),(\"call\",(0-1)),(\"callsetup\",(0-3)),(\"callheld\",(0-2))");
|
||||
|
|
@ -195,6 +313,13 @@ static bool rfcomm_hfp_ag(struct spa_source *source, char* buf)
|
|||
rfcomm_send_reply(source, "OK");
|
||||
} else if (strncmp(buf, "AT+CMER", 7) == 0) {
|
||||
rfcomm->slc_configured = true;
|
||||
rfcomm_send_reply(source, "OK");
|
||||
|
||||
/* switch codec to mSBC by sending unsolicited +BCS message */
|
||||
if (rfcomm->msbc_supported_by_hfp) {
|
||||
spa_log_debug(backend->log, NAME": RFCOMM switching codec to mSBC");
|
||||
rfcomm_send_reply(source, "+BCS: 2");
|
||||
}
|
||||
|
||||
rfcomm->transport = _transport_create(rfcomm);
|
||||
if (rfcomm->transport == NULL) {
|
||||
|
|
@ -203,16 +328,23 @@ static bool rfcomm_hfp_ag(struct spa_source *source, char* buf)
|
|||
}
|
||||
spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile);
|
||||
|
||||
rfcomm_send_reply(source, "OK");
|
||||
} else if (!rfcomm->slc_configured) {
|
||||
spa_log_warn(backend->log, NAME": RFCOMM receive command before SLC completed: %s", buf);
|
||||
rfcomm_send_reply(source, "ERROR");
|
||||
return false;
|
||||
} else if (sscanf(buf, "AT+BCS=%u", &selected_codec) == 1) {
|
||||
/* parse BCS(=Bluetooth Codec Selection) reply */
|
||||
if (selected_codec == HFP_AUDIO_CODEC_MSBC) {
|
||||
spa_log_debug(backend->log,
|
||||
NAME": RFCOMM selected_codec = %i, mSBC codec successfully negotiated", selected_codec);
|
||||
rfcomm->transport->codec = HFP_AUDIO_CODEC_MSBC;
|
||||
}
|
||||
rfcomm_send_reply(source, "OK");
|
||||
} else if (sscanf(buf, "AT+VGM=%u", &gain) == 1) {
|
||||
//t->microphone_gain = gain;
|
||||
/* t->microphone_gain = gain; */
|
||||
rfcomm_send_reply(source, "OK");
|
||||
} else if (sscanf(buf, "AT+VGS=%u", &gain) == 1) {
|
||||
//t->speaker_gain = gain;
|
||||
/* t->speaker_gain = gain; */
|
||||
rfcomm_send_reply(source, "OK");
|
||||
} else {
|
||||
return false;
|
||||
|
|
@ -301,6 +433,8 @@ static int sco_do_connect(struct spa_bt_transport *t)
|
|||
bdaddr_t dst;
|
||||
const char *src_addr, *dst_addr;
|
||||
|
||||
spa_log_debug(backend->log, NAME": transport %p: enter sco_do_connect", t);
|
||||
|
||||
if (d->adapter == NULL)
|
||||
return -EIO;
|
||||
|
||||
|
|
@ -333,6 +467,18 @@ static int sco_do_connect(struct spa_bt_transport *t)
|
|||
addr.sco_family = AF_BLUETOOTH;
|
||||
bacpy(&addr.sco_bdaddr, &dst);
|
||||
|
||||
spa_log_debug(backend->log, NAME": transport %p: codec=%u", t, t->codec);
|
||||
if (t->codec == HFP_AUDIO_CODEC_MSBC) {
|
||||
/* set correct socket options for mSBC */
|
||||
struct bt_voice voice_config;
|
||||
memset(&voice_config, 0, sizeof(voice_config));
|
||||
voice_config.setting = BT_VOICE_TRANSPARENT;
|
||||
if (setsockopt(sock, SOL_BLUETOOTH, BT_VOICE, &voice_config, sizeof(voice_config)) < 0) {
|
||||
spa_log_error(backend->log, NAME": setsockopt(): %s", strerror(errno));
|
||||
goto fail_close;
|
||||
}
|
||||
}
|
||||
|
||||
spa_log_debug(backend->log, NAME": transport %p: doing connect", t);
|
||||
err = connect(sock, (struct sockaddr *) &addr, len);
|
||||
if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) {
|
||||
|
|
@ -354,6 +500,8 @@ static int sco_acquire_cb(void *data, bool optional)
|
|||
int sock;
|
||||
socklen_t len;
|
||||
|
||||
spa_log_debug(backend->log, NAME": transport %p: enter sco_acquire_cb", t);
|
||||
|
||||
if (optional)
|
||||
sock = sco_do_accept(t);
|
||||
else
|
||||
|
|
@ -382,6 +530,7 @@ static int sco_acquire_cb(void *data, bool optional)
|
|||
t->write_mtu = sco_opt.mtu;
|
||||
}
|
||||
}
|
||||
spa_log_debug(backend->log, NAME": transport %p: read_mtu=%u, write_mtu=%u", t, t->read_mtu, t->write_mtu);
|
||||
|
||||
return 0;
|
||||
|
||||
|
|
@ -833,10 +982,13 @@ void backend_native_free(struct spa_bt_backend *backend)
|
|||
|
||||
struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
|
||||
void *dbus_connection,
|
||||
const struct spa_dict *info,
|
||||
const struct spa_support *support,
|
||||
uint32_t n_support)
|
||||
{
|
||||
struct spa_bt_backend *backend;
|
||||
const char *str;
|
||||
|
||||
static const DBusObjectPathVTable vtable_profile = {
|
||||
.message_function = profile_handler,
|
||||
};
|
||||
|
|
@ -853,6 +1005,11 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
|
|||
|
||||
spa_list_init(&backend->rfcomm_list);
|
||||
|
||||
if (info && (str = spa_dict_lookup(info, "bluez5.msbc-support")))
|
||||
backend->msbc_support_enabled_in_config = strcmp(str, "true") == 0 || atoi(str) == 1;
|
||||
else
|
||||
backend->msbc_support_enabled_in_config = false;
|
||||
|
||||
#ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE
|
||||
if (!dbus_connection_register_object_path(backend->conn,
|
||||
PROFILE_HSP_AG,
|
||||
|
|
|
|||
|
|
@ -2213,7 +2213,7 @@ impl_init(const struct spa_handle_factory *factory,
|
|||
this->enable_sbc_xq = true;
|
||||
}
|
||||
|
||||
this->backend_native = backend_native_new(this, this->conn, support, n_support);
|
||||
this->backend_native = backend_native_new(this, this->conn, info, support, n_support);
|
||||
this->backend_ofono = backend_ofono_new(this, this->conn, info, support, n_support);
|
||||
this->backend_hsphfpd = backend_hsphfpd_new(this, this->conn, info, support, n_support);
|
||||
|
||||
|
|
|
|||
|
|
@ -463,6 +463,7 @@ static inline enum spa_bt_transport_state spa_bt_transport_state_from_string(con
|
|||
#ifdef HAVE_BLUEZ_5_BACKEND_NATIVE
|
||||
struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
|
||||
void *dbus_connection,
|
||||
const struct spa_dict *info,
|
||||
const struct spa_support *support,
|
||||
uint32_t n_support);
|
||||
void backend_native_free(struct spa_bt_backend *backend);
|
||||
|
|
@ -470,6 +471,7 @@ void backend_native_register_profiles(struct spa_bt_backend *backend);
|
|||
#else
|
||||
static inline struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
|
||||
void *dbus_connection,
|
||||
const struct spa_dict *info,
|
||||
const struct spa_support *support,
|
||||
uint32_t n_support) {
|
||||
return NULL;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue