From 5e0b63b149559154a6164dbc064aefc7e773c03a Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 13 Jun 2021 21:52:41 +0300 Subject: [PATCH] bluez5: backend-native: use quirks + usb adapter caps for checking msbc Use the quirks database to check whether to enable MSBC codec for each device. If quirks don't allow ALT1 mode for an USB adapter, check whether the adapter has an usable ALT6 mode and disable MSBC if not. --- meson.build | 5 + meson_options.txt | 4 + spa/plugins/bluez5/backend-native.c | 167 ++++++++++++++++++++++++---- spa/plugins/bluez5/meson.build | 3 + 4 files changed, 157 insertions(+), 22 deletions(-) diff --git a/meson.build b/meson.build index cb3c5bb6b..33e8bf404 100644 --- a/meson.build +++ b/meson.build @@ -323,6 +323,11 @@ sndfile_dep = dependency('sndfile', version : '>= 1.0.20', required : get_option pulseaudio_dep = dependency('libpulse', required : get_option('libpulse')) avahi_dep = dependency('avahi-client', required : get_option('avahi')) +libusb_dep = dependency('libusb-1.0', required : get_option('libusb')) +if libusb_dep.found() + cdata.set('HAVE_LIBUSB', 1) +endif + cap_lib = dependency('libcap', required : false) if cap_lib.found() cdata.set('HAVE_LIBCAP', 1) diff --git a/meson_options.txt b/meson_options.txt index 583fe21c9..8a50f42dc 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -191,6 +191,10 @@ option('echo-cancel-webrtc', description : 'Enable WebRTC-based echo canceller', type : 'feature', value : 'auto') +option('libusb', + description: 'Enable code that depends on libusb', + type: 'feature', + value: 'auto') option('media-session', description: 'Build and install pipewire-media-session', type: 'feature', diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 61508bf88..26e5ed882 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -25,6 +25,9 @@ #include #include +#include +#include +#include #include #include @@ -44,6 +47,10 @@ #include "defs.h" +#ifdef HAVE_LIBUSB +#include +#endif + #define NAME "native" #define PROP_KEY_HEADSET_ROLES "bluez5.headset-roles" @@ -125,7 +132,6 @@ struct rfcomm { #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE unsigned int slc_configured:1; unsigned int codec_negotiation_supported:1; - unsigned int msbc_support_enabled_in_config:1; unsigned int msbc_supported_by_hfp:1; unsigned int hfp_ag_switching_codec:1; unsigned int hfp_ag_initial_codec_setup:2; @@ -413,24 +419,135 @@ static bool rfcomm_hsp_hs(struct spa_source *source, char* buf) } #endif +#ifdef HAVE_LIBUSB +static bool check_usb_altsetting_6(struct impl *backend, uint16_t vendor_id, uint16_t product_id) +{ + libusb_context *ctx = NULL; + struct libusb_config_descriptor *cfg = NULL; + libusb_device **devices = NULL; + + ssize_t ndev, idev; + int res; + bool ok = false; + + if ((res = libusb_init(&ctx)) < 0) { + ctx = NULL; + goto fail; + } + + if ((ndev = libusb_get_device_list(ctx, &devices)) < 0) { + res = ndev; + devices = NULL; + goto fail; + } + + for (idev = 0; idev < ndev; ++idev) { + libusb_device *dev = devices[idev]; + struct libusb_device_descriptor desc; + int icfg; + + libusb_get_device_descriptor(dev, &desc); + if (vendor_id != desc.idVendor || product_id != desc.idProduct) + continue; + + /* Check the device has Bluetooth isoch. altsetting 6 interface */ + + for (icfg = 0; icfg < desc.bNumConfigurations; ++icfg) { + int iiface; + + if ((res = libusb_get_config_descriptor(dev, icfg, &cfg)) != 0) { + cfg = NULL; + goto fail; + } + + for (iiface = 0; iiface < cfg->bNumInterfaces; ++iiface) { + const struct libusb_interface *iface = &cfg->interface[iiface]; + int ialt; + + for (ialt = 0; ialt < iface->num_altsetting; ++ialt) { + const struct libusb_interface_descriptor *idesc = &iface->altsetting[ialt]; + int iep; + + if (idesc->bInterfaceClass != LIBUSB_CLASS_WIRELESS || + idesc->bInterfaceSubClass != 1 /* RF */ || + idesc->bInterfaceProtocol != 1 /* Bluetooth */ || + idesc->bAlternateSetting != 6) + continue; + + for (iep = 0; iep < idesc->bNumEndpoints; ++iep) { + const struct libusb_endpoint_descriptor *ep = &idesc->endpoint[iep]; + if ((ep->bmAttributes & 0x3) == LIBUSB_ENDPOINT_TRANSFER_TYPE_ISOCHRONOUS) { + ok = true; + goto done; + } + } + } + } + + libusb_free_config_descriptor(cfg); + cfg = NULL; + } + } + +done: + if (cfg) + libusb_free_config_descriptor(cfg); + if (devices) + libusb_free_device_list(devices, 0); + if (ctx) + libusb_exit(ctx); + return ok; + +fail: + spa_log_info(backend->log, NAME": failed to acquire USB device info: %d (%s)", + res, libusb_strerror(res)); + ok = false; + goto done; +} +#endif + #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE static bool device_supports_required_mSBC_transport_modes( struct impl *backend, struct spa_bt_device *device) { bdaddr_t src; uint8_t features[8], max_page = 0; + struct hci_dev_info di; int device_id; int sock; + bool msbc_ok, msbc_alt1_ok; + uint32_t bt_features; if (device->adapter == NULL) return false; spa_log_debug(backend->log, NAME": Entering function"); + if (backend->quirks && spa_bt_quirks_get_features(backend->quirks, device->adapter, device, &bt_features) == 0) { + msbc_ok = bt_features & SPA_BT_FEATURE_MSBC; + msbc_alt1_ok = bt_features & (SPA_BT_FEATURE_MSBC_ALT1 | SPA_BT_FEATURE_MSBC_ALT1_RTL); + } else { + msbc_ok = true; + msbc_alt1_ok = true; + } + + spa_log_info(backend->log, + NAME": bluez-monitor/hardware.conf: msbc:%d msbc-alt1:%d", (int)msbc_ok, (int)msbc_alt1_ok); + + if (!msbc_ok && !msbc_alt1_ok) + return false; + str2ba(device->adapter->address, &src); device_id = hci_get_route(&src); + + if (hci_devinfo(device_id, &di) < 0) { + spa_log_error(backend->log, NAME": Error getting device info for hci%d: %s (%d)\n", + device_id, strerror(errno), errno); + return false; + } + sock = hci_open_dev(device_id); - if (sock < 0) { + if (sock < 0) { spa_log_error(backend->log, NAME": Error opening device hci%d: %s (%d)\n", device_id, strerror(errno), errno); return false; @@ -459,11 +576,29 @@ static bool device_supports_required_mSBC_transport_modes( } 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; + if ((di.type & 0x0f) == HCI_USB && !msbc_alt1_ok && msbc_ok) { + /* Check if USB ALT6 is really available on the device */ +#if HAVE_LIBUSB + if (device->adapter->source_id == SOURCE_ID_USB) { + msbc_ok = check_usb_altsetting_6(backend, device->adapter->vendor_id, + device->adapter->product_id); + } else { + msbc_ok = false; + } + if (!msbc_ok) + spa_log_info(backend->log, NAME": bluetooth host adapter does not support USB ALT6"); +#else + spa_log_info(backend->log, + NAME": compiled without libusb; can't check if bluetooth adapter has USB ALT6"); + msbc_ok = false; +#endif + } + if ((di.type & 0x0f) != HCI_USB) + msbc_alt1_ok = false; + + return msbc_ok || msbc_alt1_ok; } static int codec_switch_start_timer(struct rfcomm *rfcomm, int timeout_msec); @@ -489,11 +624,8 @@ static bool rfcomm_hfp_ag(struct spa_source *source, char* buf) rfcomm->has_volume = (features & SPA_BT_HFP_HF_FEATURE_REMOTE_VOLUME_CONTROL); /* 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 ((rfcomm->msbc_support_enabled_in_config == true) && - (device_supports_required_mSBC_transport_modes(backend, rfcomm->device))) { + This should be done when the computers bluetooth adapter supports the necessary transport mode */ + if (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; @@ -1265,7 +1397,6 @@ static int backend_native_supports_codec(void *data, struct spa_bt_device *devic return (codec == HFP_AUDIO_CODEC_MSBC && (rfcomm->profile == SPA_BT_PROFILE_HFP_AG || rfcomm->profile == SPA_BT_PROFILE_HFP_HF) && - rfcomm->msbc_support_enabled_in_config && rfcomm->msbc_supported_by_hfp && rfcomm->codec_negotiation_supported) ? 1 : 0; #else @@ -1413,7 +1544,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag struct impl *backend = userdata; DBusMessage *r; DBusMessageIter it[5]; - const char *handler, *path, *str; + const char *handler, *path; enum spa_bt_profile profile = SPA_BT_PROFILE_NULL; struct rfcomm *rfcomm; struct spa_bt_device *d; @@ -1483,11 +1614,6 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag spa_loop_add_source(backend->main_loop, &rfcomm->source); spa_list_append(&backend->rfcomm_list, &rfcomm->link); - if (d->settings && (str = spa_dict_lookup(d->settings, "bluez5.msbc-support"))) - rfcomm->msbc_support_enabled_in_config = spa_atob(str); - else - rfcomm->msbc_support_enabled_in_config = false; - if (profile == SPA_BT_PROFILE_HSP_HS || profile == SPA_BT_PROFILE_HSP_AG) { t = _transport_create(rfcomm); if (t == NULL) { @@ -1510,11 +1636,8 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag 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 ((rfcomm->msbc_support_enabled_in_config == true) && - (device_supports_required_mSBC_transport_modes(backend, rfcomm->device))) { + This should be done when the bluetooth adapter supports the necessary transport mode */ + if (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; diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 5a5df437d..247fd973c 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -55,6 +55,9 @@ if fdk_aac_dep.found() endif if not get_option('bluez5-backend-hsp-native').disabled() or not get_option('bluez5-backend-hfp-native').disabled() + if libusb_dep.found() + bluez5_deps += libusb_dep + endif bluez5_sources += ['backend-native.c'] endif