diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index ed82b2f41..d7e75e33d 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -1171,6 +1171,15 @@ in a platform-specific way. See `tests/examples/bt-pinephone.lua` in WirePlumber Do not enable this setting if you don't know what all this means, as it won't work. \endparblock +@PAR@ device-param bluez5.hw-offload-datapath # integer +\parblock +HFP/HSP hardware offload data path ID (default: 0). + +This feature configures the SCO hardware‑offload data path for HFP/HSP using the Bluetooth +SIG–specified procedure. It is intended for advanced setups and vendor integrations. Do not +edit this unless required; incorrect values can disable SCO offload. +\endparblock + @PAR@ monitor-prop bluez5.a2dp.opus.pro.channels = 3 # integer PipeWire Opus Pro audio profile channel count. diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 5c4a2b785..5dde972fc 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -112,6 +112,7 @@ struct impl { int hfp_default_speaker_volume; struct spa_source sco; + unsigned int hfphsp_sco_datapath; const struct spa_bt_quirks *quirks; @@ -297,6 +298,32 @@ static const struct media_codec *codec_list_best(struct impl *backend, struct sp return NULL; } +static int sco_offload_btcodec(struct impl *backend, int sock, bool msbc) +{ + int err; + char buffer[255]; + struct bt_codecs *codecs; + + spa_log_info(backend->log, "%s: sock(%d) msbc(%d)", __func__, sock, msbc); + + memset(buffer, 0, sizeof(buffer)); + codecs = (void *)buffer; + if (msbc) + codecs->codecs[0].id = 0x05; + else + codecs->codecs[0].id = 0x02; + codecs->num_codecs = 1; + codecs->codecs[0].data_path_id = backend->hfphsp_sco_datapath; + codecs->codecs[0].num_caps = 0x00; + + err = setsockopt(sock, SOL_BLUETOOTH, BT_CODEC, codecs, sizeof(buffer)); + if (err < 0) + spa_log_error(backend->log, "%s: ERROR: %s (%d)", __func__, strerror(errno), errno); + else + spa_log_info(backend->log, "%s: set offload codec succeeded", __func__); + return err; +} + static DBusHandlerResult profile_release(DBusConnection *conn, DBusMessage *m, void *userdata) { if (!reply_with_error(conn, m, BLUEZ_PROFILE_INTERFACE ".Error.NotImplemented", "Method not implemented")) @@ -2564,6 +2591,7 @@ static int sco_create_socket(struct impl *backend, struct spa_bt_adapter *adapte struct sockaddr_sco addr; socklen_t len; bdaddr_t src; + uint32_t bt_features; spa_autoclose int sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_NONBLOCK, BTPROTO_SCO); if (sock < 0) { @@ -2595,6 +2623,11 @@ static int sco_create_socket(struct impl *backend, struct spa_bt_adapter *adapte } } + if (backend->quirks && + (spa_bt_quirks_get_features(backend->quirks, NULL, NULL, &bt_features) == 0) && + ((bt_features & (SPA_BT_FEATURE_HW_OFFLOAD)) != 0)) + sco_offload_btcodec(backend, sock, transparent); + return spa_steal_fd(sock); } @@ -4101,6 +4134,18 @@ static void parse_hfp_default_volumes(struct impl *backend, const struct spa_dic backend->hfp_default_speaker_volume = SPA_BT_VOLUME_HS_MAX; } +static void parse_sco_datapath(struct impl *backend, const struct spa_dict *info) +{ + uint32_t tmp; + const char *str; + + backend->hfphsp_sco_datapath = HFP_SCO_DEFAULT_DATAPATH; + + if ((str = spa_dict_lookup(info, "bluez5.hw-offload-datapath")) != NULL && + (tmp = atoi(str)) > 0) + backend->hfphsp_sco_datapath = tmp; +} + static const struct spa_bt_backend_implementation backend_impl = { SPA_VERSION_BT_BACKEND_IMPLEMENTATION, .free = backend_native_free, @@ -4163,6 +4208,7 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor, parse_hfp_disable_nrec(backend, info); parse_hfp_default_volumes(backend, info); parse_hfp_pts(backend, info); + parse_sco_datapath(backend, info); #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE if (!dbus_connection_register_object_path(backend->conn, diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 3efec465a..4bc4fa3bb 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -135,7 +135,8 @@ extern "C" { #define PROFILE_HFP_AG "/Profile/HFPAG" #define PROFILE_HFP_HF "/Profile/HFPHF" -#define HSP_HS_DEFAULT_CHANNEL 3 +#define HSP_HS_DEFAULT_CHANNEL 3 +#define HFP_SCO_DEFAULT_DATAPATH 0 #define SOURCE_ID_BLUETOOTH 0x1 /* Bluetooth SIG */ #define SOURCE_ID_USB 0x2 /* USB Implementer's Forum */ @@ -809,6 +810,7 @@ enum spa_bt_feature { SPA_BT_FEATURE_SBC_XQ = (1 << 5), SPA_BT_FEATURE_FASTSTREAM = (1 << 6), SPA_BT_FEATURE_A2DP_DUPLEX = (1 << 7), + SPA_BT_FEATURE_HW_OFFLOAD = (1 << 8), }; struct spa_bt_quirks; diff --git a/spa/plugins/bluez5/quirks.c b/spa/plugins/bluez5/quirks.c index c4b293e68..2a1c5e860 100644 --- a/spa/plugins/bluez5/quirks.c +++ b/spa/plugins/bluez5/quirks.c @@ -52,6 +52,7 @@ struct spa_bt_quirks { int force_sbc_xq; int force_faststream; int force_a2dp_duplex; + int force_hw_offload; char *device_rules; char *adapter_rules; @@ -69,6 +70,7 @@ static enum spa_bt_feature parse_feature(const char *str) { "sbc-xq", SPA_BT_FEATURE_SBC_XQ }, { "faststream", SPA_BT_FEATURE_FASTSTREAM }, { "a2dp-duplex", SPA_BT_FEATURE_A2DP_DUPLEX }, + { "hw-offload", SPA_BT_FEATURE_HW_OFFLOAD }, }; SPA_FOR_EACH_ELEMENT_VAR(feature_keys, f) { if (spa_streq(str, f->key)) @@ -228,6 +230,7 @@ struct spa_bt_quirks *spa_bt_quirks_create(const struct spa_dict *info, struct s this->force_hw_volume = parse_force_flag(info, "bluez5.enable-hw-volume"); this->force_faststream = parse_force_flag(info, "bluez5.enable-faststream"); this->force_a2dp_duplex = parse_force_flag(info, "bluez5.enable-a2dp-duplex"); + this->force_hw_offload = parse_force_flag(info, "bluez5.hw-offload-sco"); if ((str = spa_dict_lookup(info, "bluez5.hardware-database")) != NULL) { spa_log_debug(this->log, "loading session manager provided data"); @@ -385,6 +388,9 @@ static int get_features(const struct spa_bt_quirks *this, if (this->force_a2dp_duplex != -1) SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_A2DP_DUPLEX, this->force_a2dp_duplex); + if (this->force_hw_offload != -1) + SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_HW_OFFLOAD, this->force_hw_offload); + return 0; }