mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-12-16 08:56:45 -05:00
bluez5: support LC3-SWB in HFP RFCOMM & add codec id for it
HFP 1.9 adds LC3 as a possible codec in addition to CVSD & mSBC. E.g. Pixel Buds Pro latest firmware supports it. Add the RFCOMM side and codec selection for it.
This commit is contained in:
parent
237d282c05
commit
fe412784a4
6 changed files with 130 additions and 53 deletions
|
|
@ -39,6 +39,7 @@ enum spa_bluetooth_audio_codec {
|
||||||
/* HFP */
|
/* HFP */
|
||||||
SPA_BLUETOOTH_AUDIO_CODEC_CVSD = 0x100,
|
SPA_BLUETOOTH_AUDIO_CODEC_CVSD = 0x100,
|
||||||
SPA_BLUETOOTH_AUDIO_CODEC_MSBC,
|
SPA_BLUETOOTH_AUDIO_CODEC_MSBC,
|
||||||
|
SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB,
|
||||||
|
|
||||||
/* BAP */
|
/* BAP */
|
||||||
SPA_BLUETOOTH_AUDIO_CODEC_LC3 = 0x200,
|
SPA_BLUETOOTH_AUDIO_CODEC_LC3 = 0x200,
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ static const struct spa_type_info spa_type_bluetooth_audio_codec[] = {
|
||||||
|
|
||||||
{ SPA_BLUETOOTH_AUDIO_CODEC_CVSD, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "cvsd", NULL },
|
{ SPA_BLUETOOTH_AUDIO_CODEC_CVSD, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "cvsd", NULL },
|
||||||
{ SPA_BLUETOOTH_AUDIO_CODEC_MSBC, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "msbc", NULL },
|
{ SPA_BLUETOOTH_AUDIO_CODEC_MSBC, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "msbc", NULL },
|
||||||
|
{ SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3_swb", NULL },
|
||||||
|
|
||||||
{ SPA_BLUETOOTH_AUDIO_CODEC_LC3, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3", NULL },
|
{ SPA_BLUETOOTH_AUDIO_CODEC_LC3, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3", NULL },
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ if get_option('spa-plugins').allowed()
|
||||||
mm_dep = dependency('ModemManager', version : '>= 1.10.0', required : get_option('bluez5-backend-native-mm'))
|
mm_dep = dependency('ModemManager', version : '>= 1.10.0', required : get_option('bluez5-backend-native-mm'))
|
||||||
summary({'ModemManager': mm_dep.found()}, bool_yn: true, section: 'Bluetooth backends')
|
summary({'ModemManager': mm_dep.found()}, bool_yn: true, section: 'Bluetooth backends')
|
||||||
endif
|
endif
|
||||||
|
cdata.set('HAVE_LC3', get_option('bluez5-codec-lc3').allowed() and lc3_dep.found())
|
||||||
endif
|
endif
|
||||||
jack_dep = dependency('jack', version : '>= 1.9.10', required: get_option('jack'))
|
jack_dep = dependency('jack', version : '>= 1.9.10', required: get_option('jack'))
|
||||||
summary({'JACK2': jack_dep.found()}, bool_yn: true, section: 'Backend')
|
summary({'JACK2': jack_dep.found()}, bool_yn: true, section: 'Backend')
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ struct rfcomm {
|
||||||
unsigned int slc_configured:1;
|
unsigned int slc_configured:1;
|
||||||
unsigned int codec_negotiation_supported:1;
|
unsigned int codec_negotiation_supported:1;
|
||||||
unsigned int msbc_supported_by_hfp:1;
|
unsigned int msbc_supported_by_hfp:1;
|
||||||
|
unsigned int lc3_supported_by_hfp:1;
|
||||||
unsigned int hfp_ag_switching_codec:1;
|
unsigned int hfp_ag_switching_codec:1;
|
||||||
unsigned int hfp_ag_initial_codec_setup:2;
|
unsigned int hfp_ag_initial_codec_setup:2;
|
||||||
unsigned int cind_call_active:1;
|
unsigned int cind_call_active:1;
|
||||||
|
|
@ -615,28 +616,48 @@ fail:
|
||||||
|
|
||||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||||
|
|
||||||
static bool device_supports_required_mSBC_transport_modes(
|
static bool device_supports_codec(struct impl *backend, struct spa_bt_device *device, int codec)
|
||||||
struct impl *backend, struct spa_bt_device *device)
|
|
||||||
{
|
{
|
||||||
int res;
|
int res;
|
||||||
bool msbc_ok, msbc_alt1_ok;
|
bool alt6_ok = true, alt1_ok = true;
|
||||||
|
bool msbc_alt6_ok = true, msbc_alt1_ok = true;
|
||||||
uint32_t bt_features;
|
uint32_t bt_features;
|
||||||
|
|
||||||
if (device->adapter == NULL)
|
if (device->adapter == NULL)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (backend->quirks && spa_bt_quirks_get_features(backend->quirks, device->adapter, device, &bt_features) == 0) {
|
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));
|
||||||
msbc_alt1_ok = bt_features & (SPA_BT_FEATURE_MSBC_ALT1 | SPA_BT_FEATURE_MSBC_ALT1_RTL);
|
msbc_alt6_ok = (bt_features & SPA_BT_FEATURE_MSBC);
|
||||||
} else {
|
}
|
||||||
msbc_ok = true;
|
|
||||||
msbc_alt1_ok = true;
|
switch (codec) {
|
||||||
|
case HFP_AUDIO_CODEC_CVSD:
|
||||||
|
return true;
|
||||||
|
case HFP_AUDIO_CODEC_MSBC:
|
||||||
|
alt1_ok = msbc_alt1_ok;
|
||||||
|
alt6_ok = msbc_alt6_ok;
|
||||||
|
break;
|
||||||
|
case HFP_AUDIO_CODEC_LC3:
|
||||||
|
#ifdef HAVE_LC3
|
||||||
|
/* LC3-SWB has same transport requirements as msbc.
|
||||||
|
* However, ALT1/ALT5 modes don't appear to work, seem
|
||||||
|
* to lose frame sync so output is garbled.
|
||||||
|
*/
|
||||||
|
alt1_ok = false;
|
||||||
|
alt6_ok = msbc_alt6_ok;
|
||||||
|
break;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
spa_log_info(backend->log,
|
spa_log_info(backend->log,
|
||||||
"bluez-monitor/hardware.conf: msbc:%d msbc-alt1:%d", (int)msbc_ok, (int)msbc_alt1_ok);
|
"bluez-monitor/hardware.conf: alt6:%d alt1/5:%d", (int)alt6_ok, (int)alt1_ok);
|
||||||
|
|
||||||
if (!msbc_ok && !msbc_alt1_ok)
|
if (!alt6_ok && !alt1_ok)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
res = spa_bt_adapter_has_msbc(device->adapter);
|
res = spa_bt_adapter_has_msbc(device->adapter);
|
||||||
|
|
@ -656,26 +677,26 @@ static bool device_supports_required_mSBC_transport_modes(
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if USB ALT6 is really available on the device */
|
/* Check if USB ALT6 is really available on the device */
|
||||||
if (device->adapter->bus_type == BUS_TYPE_USB && !msbc_alt1_ok && msbc_ok) {
|
if (device->adapter->bus_type == BUS_TYPE_USB && !alt1_ok && alt6_ok) {
|
||||||
#ifdef HAVE_LIBUSB
|
#ifdef HAVE_LIBUSB
|
||||||
if (device->adapter->source_id == SOURCE_ID_USB) {
|
if (device->adapter->source_id == SOURCE_ID_USB) {
|
||||||
msbc_ok = check_usb_altsetting_6(backend, device->adapter->vendor_id,
|
alt6_ok = check_usb_altsetting_6(backend, device->adapter->vendor_id,
|
||||||
device->adapter->product_id);
|
device->adapter->product_id);
|
||||||
} else {
|
} else {
|
||||||
msbc_ok = false;
|
alt6_ok = false;
|
||||||
}
|
}
|
||||||
if (!msbc_ok)
|
if (!alt6_ok)
|
||||||
spa_log_info(backend->log, "bluetooth host adapter does not support USB ALT6");
|
spa_log_info(backend->log, "bluetooth host adapter does not support USB ALT6");
|
||||||
#else
|
#else
|
||||||
spa_log_info(backend->log,
|
spa_log_info(backend->log,
|
||||||
"compiled without libusb; can't check if bluetooth adapter has USB ALT6");
|
"compiled without libusb; can't check if bluetooth adapter has USB ALT6, assuming no");
|
||||||
msbc_ok = false;
|
alt6_ok = false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
if (device->adapter->bus_type != BUS_TYPE_USB)
|
if (device->adapter->bus_type != BUS_TYPE_USB)
|
||||||
msbc_alt1_ok = false;
|
alt1_ok = false;
|
||||||
|
|
||||||
return msbc_ok || msbc_alt1_ok;
|
return alt6_ok || alt1_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int codec_switch_start_timer(struct rfcomm *rfcomm, int timeout_msec);
|
static int codec_switch_start_timer(struct rfcomm *rfcomm, int timeout_msec);
|
||||||
|
|
@ -779,6 +800,8 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf)
|
||||||
|
|
||||||
if (sscanf(buf, "AT+BRSF=%u", &features) == 1) {
|
if (sscanf(buf, "AT+BRSF=%u", &features) == 1) {
|
||||||
unsigned int ag_features = SPA_BT_HFP_AG_FEATURE_NONE;
|
unsigned int ag_features = SPA_BT_HFP_AG_FEATURE_NONE;
|
||||||
|
bool codecs = device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_MSBC) ||
|
||||||
|
device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_LC3);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determine device volume control. Some headsets only support control of
|
* Determine device volume control. Some headsets only support control of
|
||||||
|
|
@ -787,10 +810,9 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf)
|
||||||
*/
|
*/
|
||||||
rfcomm->has_volume = (features & SPA_BT_HFP_HF_FEATURE_REMOTE_VOLUME_CONTROL);
|
rfcomm->has_volume = (features & SPA_BT_HFP_HF_FEATURE_REMOTE_VOLUME_CONTROL);
|
||||||
|
|
||||||
/* Decide if we want to signal that the computer supports mSBC negotiation
|
/* Decide if we want to signal that the computer supports codec negotiation
|
||||||
This should be done when the computers bluetooth adapter supports the necessary transport mode */
|
This should be done when the computers bluetooth adapter supports the necessary transport mode */
|
||||||
if (device_supports_required_mSBC_transport_modes(backend, rfcomm->device)) {
|
if (codecs) {
|
||||||
|
|
||||||
/* set the feature bit that indicates AG (=computer) supports codec negotiation */
|
/* set the feature bit that indicates AG (=computer) supports codec negotiation */
|
||||||
ag_features |= SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION;
|
ag_features |= SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION;
|
||||||
|
|
||||||
|
|
@ -802,6 +824,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf)
|
||||||
/* Prepare reply: Audio Gateway (=computer) supports codec negotiation */
|
/* Prepare reply: Audio Gateway (=computer) supports codec negotiation */
|
||||||
rfcomm->codec_negotiation_supported = true;
|
rfcomm->codec_negotiation_supported = true;
|
||||||
rfcomm->msbc_supported_by_hfp = false;
|
rfcomm->msbc_supported_by_hfp = false;
|
||||||
|
rfcomm->lc3_supported_by_hfp = false;
|
||||||
} else {
|
} else {
|
||||||
/* Codec negotiation not supported */
|
/* Codec negotiation not supported */
|
||||||
spa_log_debug(backend->log,
|
spa_log_debug(backend->log,
|
||||||
|
|
@ -810,6 +833,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf)
|
||||||
|
|
||||||
rfcomm->codec_negotiation_supported = false;
|
rfcomm->codec_negotiation_supported = false;
|
||||||
rfcomm->msbc_supported_by_hfp = false;
|
rfcomm->msbc_supported_by_hfp = false;
|
||||||
|
rfcomm->lc3_supported_by_hfp = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -832,14 +856,22 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf)
|
||||||
/* skip token 0 i.e. the "AT+BAC=" part */
|
/* skip token 0 i.e. the "AT+BAC=" part */
|
||||||
if (cntr > 0 && sscanf(token, "%u", &codec_id) == 1) {
|
if (cntr > 0 && sscanf(token, "%u", &codec_id) == 1) {
|
||||||
spa_log_debug(backend->log, "RFCOMM AT+BAC found codec %u", codec_id);
|
spa_log_debug(backend->log, "RFCOMM AT+BAC found codec %u", codec_id);
|
||||||
if (codec_id == HFP_AUDIO_CODEC_MSBC) {
|
|
||||||
rfcomm->msbc_supported_by_hfp = true;
|
if (codec_id == HFP_AUDIO_CODEC_MSBC)
|
||||||
spa_log_debug(backend->log, "RFCOMM headset supports mSBC codec");
|
rfcomm->msbc_supported_by_hfp =
|
||||||
}
|
device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_MSBC);
|
||||||
|
else if (codec_id == HFP_AUDIO_CODEC_LC3)
|
||||||
|
rfcomm->lc3_supported_by_hfp =
|
||||||
|
device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_LC3);
|
||||||
}
|
}
|
||||||
cntr++;
|
cntr++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rfcomm->msbc_supported_by_hfp)
|
||||||
|
spa_log_debug(backend->log, "mSBC codec is supported");
|
||||||
|
if (rfcomm->lc3_supported_by_hfp)
|
||||||
|
spa_log_debug(backend->log, "LC3 codec is supported");
|
||||||
|
|
||||||
rfcomm_send_reply(rfcomm, "OK");
|
rfcomm_send_reply(rfcomm, "OK");
|
||||||
} else if (spa_strstartswith(buf, "AT+CIND=?")) {
|
} else if (spa_strstartswith(buf, "AT+CIND=?")) {
|
||||||
rfcomm_send_reply(rfcomm, "+CIND:%s", CIND_INDICATORS);
|
rfcomm_send_reply(rfcomm, "+CIND:%s", CIND_INDICATORS);
|
||||||
|
|
@ -851,6 +883,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf)
|
||||||
rfcomm_send_reply(rfcomm, "OK");
|
rfcomm_send_reply(rfcomm, "OK");
|
||||||
} else if (spa_strstartswith(buf, "AT+CMER")) {
|
} else if (spa_strstartswith(buf, "AT+CMER")) {
|
||||||
int mode, keyp, disp, ind;
|
int mode, keyp, disp, ind;
|
||||||
|
bool have_codecs = rfcomm->msbc_supported_by_hfp || rfcomm->lc3_supported_by_hfp;
|
||||||
|
|
||||||
rfcomm->slc_configured = true;
|
rfcomm->slc_configured = true;
|
||||||
rfcomm_send_reply(rfcomm, "OK");
|
rfcomm_send_reply(rfcomm, "OK");
|
||||||
|
|
@ -862,10 +895,13 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf)
|
||||||
rfcomm->cind_call_notify = false;
|
rfcomm->cind_call_notify = false;
|
||||||
|
|
||||||
/* switch codec to mSBC by sending unsolicited +BCS message */
|
/* switch codec to mSBC by sending unsolicited +BCS message */
|
||||||
if (rfcomm->codec_negotiation_supported && rfcomm->msbc_supported_by_hfp) {
|
if (rfcomm->codec_negotiation_supported && have_codecs) {
|
||||||
spa_log_debug(backend->log, "RFCOMM initial codec setup");
|
spa_log_debug(backend->log, "RFCOMM initial codec setup");
|
||||||
rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_SEND;
|
rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_SEND;
|
||||||
rfcomm_send_reply(rfcomm, "+BCS: 2");
|
if (rfcomm->lc3_supported_by_hfp)
|
||||||
|
rfcomm_send_reply(rfcomm, "+BCS: 3");
|
||||||
|
else
|
||||||
|
rfcomm_send_reply(rfcomm, "+BCS: 2");
|
||||||
codec_switch_start_timer(rfcomm, HFP_CODEC_SWITCH_INITIAL_TIMEOUT_MSEC);
|
codec_switch_start_timer(rfcomm, HFP_CODEC_SWITCH_INITIAL_TIMEOUT_MSEC);
|
||||||
} else {
|
} else {
|
||||||
rfcomm->transport = _transport_create(rfcomm);
|
rfcomm->transport = _transport_create(rfcomm);
|
||||||
|
|
@ -898,7 +934,8 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf)
|
||||||
codec_switch_stop_timer(rfcomm);
|
codec_switch_stop_timer(rfcomm);
|
||||||
volume_sync_stop_timer(rfcomm);
|
volume_sync_stop_timer(rfcomm);
|
||||||
|
|
||||||
if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC) {
|
if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC &&
|
||||||
|
selected_codec != HFP_AUDIO_CODEC_LC3) {
|
||||||
spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec);
|
spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec);
|
||||||
rfcomm_send_error(rfcomm, CMEE_AG_FAILURE);
|
rfcomm_send_error(rfcomm, CMEE_AG_FAILURE);
|
||||||
if (was_switching_codec)
|
if (was_switching_codec)
|
||||||
|
|
@ -1179,10 +1216,11 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
|
||||||
|
|
||||||
if (sscanf(token, "+BRSF:%u", &features) == 1) {
|
if (sscanf(token, "+BRSF:%u", &features) == 1) {
|
||||||
if (((features & (SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION)) != 0) &&
|
if (((features & (SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION)) != 0) &&
|
||||||
rfcomm->msbc_supported_by_hfp)
|
(rfcomm->msbc_supported_by_hfp || rfcomm->lc3_supported_by_hfp))
|
||||||
rfcomm->codec_negotiation_supported = true;
|
rfcomm->codec_negotiation_supported = true;
|
||||||
} else if (sscanf(token, "+BCS:%u", &selected_codec) == 1 && rfcomm->codec_negotiation_supported) {
|
} else if (sscanf(token, "+BCS:%u", &selected_codec) == 1 && rfcomm->codec_negotiation_supported) {
|
||||||
if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC) {
|
if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC &&
|
||||||
|
selected_codec != HFP_AUDIO_CODEC_LC3) {
|
||||||
spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec);
|
spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec);
|
||||||
} else {
|
} else {
|
||||||
spa_log_debug(backend->log, "RFCOMM selected_codec = %i", selected_codec);
|
spa_log_debug(backend->log, "RFCOMM selected_codec = %i", selected_codec);
|
||||||
|
|
@ -1263,7 +1301,17 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
|
||||||
switch(rfcomm->hf_state) {
|
switch(rfcomm->hf_state) {
|
||||||
case hfp_hf_brsf:
|
case hfp_hf_brsf:
|
||||||
if (rfcomm->codec_negotiation_supported) {
|
if (rfcomm->codec_negotiation_supported) {
|
||||||
rfcomm_send_cmd(rfcomm, "AT+BAC=1,2");
|
char buf[64];
|
||||||
|
struct spa_strbuf str;
|
||||||
|
|
||||||
|
spa_strbuf_init(&str, buf, sizeof(buf));
|
||||||
|
spa_strbuf_append(&str, "1");
|
||||||
|
if (rfcomm->msbc_supported_by_hfp)
|
||||||
|
spa_strbuf_append(&str, ",2");
|
||||||
|
if (rfcomm->lc3_supported_by_hfp)
|
||||||
|
spa_strbuf_append(&str, ",3");
|
||||||
|
|
||||||
|
rfcomm_send_cmd(rfcomm, "AT+BAC=%s", buf);
|
||||||
rfcomm->hf_state = hfp_hf_bac;
|
rfcomm->hf_state = hfp_hf_bac;
|
||||||
} else {
|
} else {
|
||||||
rfcomm_send_cmd(rfcomm, "AT+CIND=?");
|
rfcomm_send_cmd(rfcomm, "AT+CIND=?");
|
||||||
|
|
@ -1395,7 +1443,7 @@ static void rfcomm_event(struct spa_source *source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sco_create_socket(struct impl *backend, struct spa_bt_adapter *adapter, bool msbc)
|
static int sco_create_socket(struct impl *backend, struct spa_bt_adapter *adapter, bool transparent)
|
||||||
{
|
{
|
||||||
struct sockaddr_sco addr;
|
struct sockaddr_sco addr;
|
||||||
socklen_t len;
|
socklen_t len;
|
||||||
|
|
@ -1419,9 +1467,9 @@ static int sco_create_socket(struct impl *backend, struct spa_bt_adapter *adapte
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
spa_log_debug(backend->log, "msbc=%d", (int)msbc);
|
spa_log_debug(backend->log, "transparent=%d", (int)transparent);
|
||||||
if (msbc) {
|
if (transparent) {
|
||||||
/* set correct socket options for mSBC */
|
/* set correct socket options for mSBC/LC3 */
|
||||||
struct bt_voice voice_config;
|
struct bt_voice voice_config;
|
||||||
memset(&voice_config, 0, sizeof(voice_config));
|
memset(&voice_config, 0, sizeof(voice_config));
|
||||||
voice_config.setting = BT_VOICE_TRANSPARENT;
|
voice_config.setting = BT_VOICE_TRANSPARENT;
|
||||||
|
|
@ -1455,7 +1503,8 @@ static int sco_do_connect(struct spa_bt_transport *t)
|
||||||
str2ba(d->address, &addr.sco_bdaddr);
|
str2ba(d->address, &addr.sco_bdaddr);
|
||||||
|
|
||||||
for (int retry = 2;;) {
|
for (int retry = 2;;) {
|
||||||
spa_autoclose int sock = sco_create_socket(backend, d->adapter, (t->codec == HFP_AUDIO_CODEC_MSBC));
|
bool transparent = (t->codec == HFP_AUDIO_CODEC_MSBC || t->codec == HFP_AUDIO_CODEC_LC3);
|
||||||
|
spa_autoclose int sock = sco_create_socket(backend, d->adapter, transparent);
|
||||||
if (sock < 0)
|
if (sock < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
|
@ -1468,12 +1517,13 @@ static int sco_do_connect(struct spa_bt_transport *t)
|
||||||
} else if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) {
|
} else if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) {
|
||||||
spa_log_error(backend->log, "connect(): %s", strerror(errno));
|
spa_log_error(backend->log, "connect(): %s", strerror(errno));
|
||||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||||
if (errno == EOPNOTSUPP && t->codec == HFP_AUDIO_CODEC_MSBC &&
|
if (errno == EOPNOTSUPP && t->codec != HFP_AUDIO_CODEC_CVSD &&
|
||||||
td->rfcomm->msbc_supported_by_hfp) {
|
td->rfcomm->codec_negotiation_supported) {
|
||||||
/* Adapter doesn't support msbc. Renegotiate. */
|
/* Adapter doesn't support msbc/lc3. Renegotiate. */
|
||||||
d->adapter->msbc_probed = true;
|
d->adapter->msbc_probed = true;
|
||||||
d->adapter->has_msbc = false;
|
d->adapter->has_msbc = false;
|
||||||
td->rfcomm->msbc_supported_by_hfp = false;
|
td->rfcomm->msbc_supported_by_hfp = false;
|
||||||
|
td->rfcomm->lc3_supported_by_hfp = false;
|
||||||
if (t->profile == SPA_BT_PROFILE_HFP_HF) {
|
if (t->profile == SPA_BT_PROFILE_HFP_HF) {
|
||||||
td->rfcomm->hfp_ag_switching_codec = true;
|
td->rfcomm->hfp_ag_switching_codec = true;
|
||||||
rfcomm_send_reply(td->rfcomm, "+BCS: 1");
|
rfcomm_send_reply(td->rfcomm, "+BCS: 1");
|
||||||
|
|
@ -1784,8 +1834,8 @@ static void sco_listen_event(struct spa_source *source)
|
||||||
* accepted socket. */
|
* accepted socket. */
|
||||||
char buff;
|
char buff;
|
||||||
|
|
||||||
if (t->codec == HFP_AUDIO_CODEC_MSBC) {
|
if (t->codec == HFP_AUDIO_CODEC_MSBC || t->codec == HFP_AUDIO_CODEC_LC3) {
|
||||||
/* set correct socket options for mSBC */
|
/* set correct socket options for mSBC/LC3 */
|
||||||
struct bt_voice voice_config;
|
struct bt_voice voice_config;
|
||||||
memset(&voice_config, 0, sizeof(voice_config));
|
memset(&voice_config, 0, sizeof(voice_config));
|
||||||
voice_config.setting = BT_VOICE_TRANSPARENT;
|
voice_config.setting = BT_VOICE_TRANSPARENT;
|
||||||
|
|
@ -1956,11 +2006,19 @@ static int backend_native_supports_codec(void *data, struct spa_bt_device *devic
|
||||||
if (codec == HFP_AUDIO_CODEC_CVSD)
|
if (codec == HFP_AUDIO_CODEC_CVSD)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
return (codec == HFP_AUDIO_CODEC_MSBC &&
|
if (rfcomm->profile != SPA_BT_PROFILE_HFP_AG &&
|
||||||
(rfcomm->profile == SPA_BT_PROFILE_HFP_AG ||
|
rfcomm->profile != SPA_BT_PROFILE_HFP_HF)
|
||||||
rfcomm->profile == SPA_BT_PROFILE_HFP_HF) &&
|
return 0;
|
||||||
rfcomm->msbc_supported_by_hfp &&
|
|
||||||
rfcomm->codec_negotiation_supported) ? 1 : 0;
|
if (!rfcomm->codec_negotiation_supported)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (codec == HFP_AUDIO_CODEC_MSBC)
|
||||||
|
return rfcomm->msbc_supported_by_hfp;
|
||||||
|
else if (codec == HFP_AUDIO_CODEC_LC3)
|
||||||
|
return rfcomm->lc3_supported_by_hfp;
|
||||||
|
|
||||||
|
return 0;
|
||||||
#else
|
#else
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -2261,16 +2319,20 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag
|
||||||
} else if (profile == SPA_BT_PROFILE_HFP_AG) {
|
} else if (profile == SPA_BT_PROFILE_HFP_AG) {
|
||||||
/* Start SLC connection */
|
/* Start SLC connection */
|
||||||
unsigned int hf_features = SPA_BT_HFP_HF_FEATURE_NONE;
|
unsigned int hf_features = SPA_BT_HFP_HF_FEATURE_NONE;
|
||||||
|
bool has_msbc = device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_MSBC);
|
||||||
|
bool has_lc3 = device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_LC3);
|
||||||
|
|
||||||
/* Decide if we want to signal that the HF supports mSBC negotiation
|
/* Decide if we want to signal that the HF supports mSBC/LC3 negotiation
|
||||||
This should be done when the bluetooth adapter supports the necessary transport mode */
|
This should be done when the bluetooth adapter supports the necessary transport mode */
|
||||||
if (device_supports_required_mSBC_transport_modes(backend, rfcomm->device)) {
|
if (has_msbc || has_lc3) {
|
||||||
/* set the feature bit that indicates HF supports codec negotiation */
|
/* set the feature bit that indicates HF supports codec negotiation */
|
||||||
hf_features |= SPA_BT_HFP_HF_FEATURE_CODEC_NEGOTIATION;
|
hf_features |= SPA_BT_HFP_HF_FEATURE_CODEC_NEGOTIATION;
|
||||||
rfcomm->msbc_supported_by_hfp = true;
|
rfcomm->msbc_supported_by_hfp = has_msbc;
|
||||||
|
rfcomm->lc3_supported_by_hfp = has_lc3;
|
||||||
rfcomm->codec_negotiation_supported = false;
|
rfcomm->codec_negotiation_supported = false;
|
||||||
} else {
|
} else {
|
||||||
rfcomm->msbc_supported_by_hfp = false;
|
rfcomm->msbc_supported_by_hfp = false;
|
||||||
|
rfcomm->lc3_supported_by_hfp = false;
|
||||||
rfcomm->codec_negotiation_supported = false;
|
rfcomm->codec_negotiation_supported = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,8 @@ static unsigned int get_hfp_codec(enum spa_bluetooth_audio_codec id)
|
||||||
return HFP_AUDIO_CODEC_CVSD;
|
return HFP_AUDIO_CODEC_CVSD;
|
||||||
case SPA_BLUETOOTH_AUDIO_CODEC_MSBC:
|
case SPA_BLUETOOTH_AUDIO_CODEC_MSBC:
|
||||||
return HFP_AUDIO_CODEC_MSBC;
|
return HFP_AUDIO_CODEC_MSBC;
|
||||||
|
case SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB:
|
||||||
|
return HFP_AUDIO_CODEC_LC3;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -257,6 +259,8 @@ static enum spa_bluetooth_audio_codec get_hfp_codec_id(unsigned int codec)
|
||||||
switch (codec) {
|
switch (codec) {
|
||||||
case HFP_AUDIO_CODEC_MSBC:
|
case HFP_AUDIO_CODEC_MSBC:
|
||||||
return SPA_BLUETOOTH_AUDIO_CODEC_MSBC;
|
return SPA_BLUETOOTH_AUDIO_CODEC_MSBC;
|
||||||
|
case HFP_AUDIO_CODEC_LC3:
|
||||||
|
return SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB;
|
||||||
case HFP_AUDIO_CODEC_CVSD:
|
case HFP_AUDIO_CODEC_CVSD:
|
||||||
return SPA_BLUETOOTH_AUDIO_CODEC_CVSD;
|
return SPA_BLUETOOTH_AUDIO_CODEC_CVSD;
|
||||||
}
|
}
|
||||||
|
|
@ -268,6 +272,8 @@ static const char *get_hfp_codec_description(unsigned int codec)
|
||||||
switch (codec) {
|
switch (codec) {
|
||||||
case HFP_AUDIO_CODEC_MSBC:
|
case HFP_AUDIO_CODEC_MSBC:
|
||||||
return "mSBC";
|
return "mSBC";
|
||||||
|
case HFP_AUDIO_CODEC_LC3:
|
||||||
|
return "LC3-SWB";
|
||||||
case HFP_AUDIO_CODEC_CVSD:
|
case HFP_AUDIO_CODEC_CVSD:
|
||||||
return "CVSD";
|
return "CVSD";
|
||||||
}
|
}
|
||||||
|
|
@ -279,6 +285,8 @@ static const char *get_hfp_codec_name(unsigned int codec)
|
||||||
switch (codec) {
|
switch (codec) {
|
||||||
case HFP_AUDIO_CODEC_MSBC:
|
case HFP_AUDIO_CODEC_MSBC:
|
||||||
return "msbc";
|
return "msbc";
|
||||||
|
case HFP_AUDIO_CODEC_LC3:
|
||||||
|
return "lc3_swb";
|
||||||
case HFP_AUDIO_CODEC_CVSD:
|
case HFP_AUDIO_CODEC_CVSD:
|
||||||
return "cvsd";
|
return "cvsd";
|
||||||
}
|
}
|
||||||
|
|
@ -1900,7 +1908,7 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *
|
||||||
desc_and_codec = spa_aprintf(_("Headset Head Unit (HSP/HFP, codec %s)"),
|
desc_and_codec = spa_aprintf(_("Headset Head Unit (HSP/HFP, codec %s)"),
|
||||||
get_hfp_codec_description(hfp_codec));
|
get_hfp_codec_description(hfp_codec));
|
||||||
desc = desc_and_codec;
|
desc = desc_and_codec;
|
||||||
priority = 1 + hfp_codec; /* prefer msbc over cvsd */
|
priority = 1 + hfp_codec; /* prefer lc3_swb > msbc > cvsd */
|
||||||
} else {
|
} else {
|
||||||
desc = _("Headset Head Unit (HSP/HFP)");
|
desc = _("Headset Head Unit (HSP/HFP)");
|
||||||
priority = 1;
|
priority = 1;
|
||||||
|
|
@ -2256,7 +2264,7 @@ static struct spa_pod *build_prop_info_codec(struct impl *this, struct spa_pod_b
|
||||||
#define FOR_EACH_MEDIA_CODEC(j, codec) \
|
#define FOR_EACH_MEDIA_CODEC(j, codec) \
|
||||||
for (j = -1; iterate_supported_media_codecs(this, &j, &codec);)
|
for (j = -1; iterate_supported_media_codecs(this, &j, &codec);)
|
||||||
#define FOR_EACH_HFP_CODEC(j) \
|
#define FOR_EACH_HFP_CODEC(j) \
|
||||||
for (j = HFP_AUDIO_CODEC_MSBC; j >= HFP_AUDIO_CODEC_CVSD; --j) \
|
for (j = HFP_AUDIO_CODEC_LC3; j >= HFP_AUDIO_CODEC_CVSD; --j) \
|
||||||
if (spa_bt_device_supports_hfp_codec(this->bt_dev, j) == 1)
|
if (spa_bt_device_supports_hfp_codec(this->bt_dev, j) == 1)
|
||||||
|
|
||||||
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id);
|
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id);
|
||||||
|
|
@ -2739,6 +2747,9 @@ static int impl_set_param(void *object,
|
||||||
} else if (codec_id == SPA_BLUETOOTH_AUDIO_CODEC_MSBC &&
|
} else if (codec_id == SPA_BLUETOOTH_AUDIO_CODEC_MSBC &&
|
||||||
spa_bt_device_supports_hfp_codec(this->bt_dev, HFP_AUDIO_CODEC_MSBC) == 1) {
|
spa_bt_device_supports_hfp_codec(this->bt_dev, HFP_AUDIO_CODEC_MSBC) == 1) {
|
||||||
return set_profile(this, this->profile, codec_id, true);
|
return set_profile(this, this->profile, codec_id, true);
|
||||||
|
} else if (codec_id == SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB &&
|
||||||
|
spa_bt_device_supports_hfp_codec(this->bt_dev, HFP_AUDIO_CODEC_LC3) == 1) {
|
||||||
|
return set_profile(this, this->profile, codec_id, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
|
||||||
|
|
@ -141,8 +141,9 @@ extern "C" {
|
||||||
#define BUS_TYPE_USB 1
|
#define BUS_TYPE_USB 1
|
||||||
#define BUS_TYPE_OTHER 255
|
#define BUS_TYPE_OTHER 255
|
||||||
|
|
||||||
#define HFP_AUDIO_CODEC_CVSD 0x01
|
#define HFP_AUDIO_CODEC_CVSD 0x01
|
||||||
#define HFP_AUDIO_CODEC_MSBC 0x02
|
#define HFP_AUDIO_CODEC_MSBC 0x02
|
||||||
|
#define HFP_AUDIO_CODEC_LC3 0x03
|
||||||
|
|
||||||
#define A2DP_OBJECT_MANAGER_PATH "/MediaEndpoint"
|
#define A2DP_OBJECT_MANAGER_PATH "/MediaEndpoint"
|
||||||
#define A2DP_SINK_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSink"
|
#define A2DP_SINK_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSink"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue