bluez5/backend-native: Add HFP/HSP hardware offload datapath configuration

Add support for configuring the SCO hardware offload data path for
HFP/HSP profiles using the Bluetooth SIG-specified procedure. This
enables vendor-specific SCO offload integrations.

Changes:
- Add `bluez5.hw-offload-datapath` configuration property (default: 0)
- Implement `sco_offload_btcodec()` to set BT_CODEC socket option
- Add `SPA_BT_FEATURE_HW_OFFLOAD` quirk feature flag
- Apply offload configuration when creating SCO sockets if quirk enabled
- Document new property in pipewire-props.7.md

The datapath ID is configurable via device parameters and only applied
when the hardware offload feature flag is set in quirks, allowing
platform-specific SCO offload implementations.
This commit is contained in:
Mengshi Wu 2026-01-16 16:16:22 +08:00 committed by Wim Taymans
parent 35817c0d85
commit db7c74a042
4 changed files with 64 additions and 1 deletions

View File

@ -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 hardwareoffload data path for HFP/HSP using the Bluetooth
SIGspecified 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.

View File

@ -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,

View File

@ -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;

View File

@ -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;
}