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