mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-12-16 08:56:45 -05:00
bluez5: implement HFP codec switching
Implement switching HFP codecs in bluez5-devices, currently only for backend-native. Codecs are exposed via profiles similarly as for A2DP. Some hardware appears to not properly reply to the +BCS message. Catch these cases with a timeout, in which case we fall back to previously existing transports.
This commit is contained in:
parent
e18df4e344
commit
79e098bdf2
4 changed files with 348 additions and 71 deletions
|
|
@ -47,6 +47,8 @@
|
|||
|
||||
#define PROP_KEY_HEADSET_ROLES "bluez5.headset-roles"
|
||||
|
||||
#define HFP_CODEC_SWITCH_TIMEOUT_MSEC 5000
|
||||
|
||||
struct impl {
|
||||
struct spa_bt_backend this;
|
||||
|
||||
|
|
@ -54,6 +56,7 @@ struct impl {
|
|||
|
||||
struct spa_log *log;
|
||||
struct spa_loop *main_loop;
|
||||
struct spa_system *main_system;
|
||||
struct spa_dbus *dbus;
|
||||
DBusConnection *conn;
|
||||
|
||||
|
|
@ -88,12 +91,14 @@ struct rfcomm {
|
|||
struct spa_bt_transport *transport;
|
||||
struct spa_hook transport_listener;
|
||||
enum spa_bt_profile profile;
|
||||
struct spa_source timer;
|
||||
char* path;
|
||||
#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;
|
||||
enum hfp_hf_state hf_state;
|
||||
unsigned int codec;
|
||||
#endif
|
||||
|
|
@ -157,8 +162,11 @@ finish:
|
|||
return t;
|
||||
}
|
||||
|
||||
static int codec_switch_stop_timer(struct rfcomm *rfcomm);
|
||||
|
||||
static void rfcomm_free(struct rfcomm *rfcomm)
|
||||
{
|
||||
codec_switch_stop_timer(rfcomm);
|
||||
spa_list_remove(&rfcomm->link);
|
||||
if (rfcomm->path)
|
||||
free(rfcomm->path);
|
||||
|
|
@ -185,7 +193,7 @@ static void rfcomm_send_cmd(struct spa_source *source, char *data)
|
|||
spa_log_error(backend->log, NAME": RFCOMM write error: %s", strerror(errno));
|
||||
}
|
||||
|
||||
static void rfcomm_send_reply(struct spa_source *source, char *data)
|
||||
static int rfcomm_send_reply(struct spa_source *source, char *data)
|
||||
{
|
||||
struct rfcomm *rfcomm = source->data;
|
||||
struct impl *backend = rfcomm->backend;
|
||||
|
|
@ -199,6 +207,7 @@ static void rfcomm_send_reply(struct spa_source *source, char *data)
|
|||
* be caught with the HANGUP and ERROR events handled above */
|
||||
if (len < 0)
|
||||
spa_log_error(backend->log, NAME": RFCOMM write error: %s", strerror(errno));
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE
|
||||
|
|
@ -425,28 +434,41 @@ static bool rfcomm_hfp_ag(struct spa_source *source, char* buf)
|
|||
return false;
|
||||
} else if (sscanf(buf, "AT+BCS=%u", &selected_codec) == 1) {
|
||||
/* parse BCS(=Bluetooth Codec Selection) reply */
|
||||
bool was_switching_codec = rfcomm->hfp_ag_switching_codec && (rfcomm->device != NULL);
|
||||
rfcomm->hfp_ag_switching_codec = false;
|
||||
codec_switch_stop_timer(rfcomm);
|
||||
|
||||
if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC) {
|
||||
spa_log_warn(backend->log, NAME": unsupported codec negotiation: %d", selected_codec);
|
||||
rfcomm_send_reply(source, "ERROR");
|
||||
if (was_switching_codec)
|
||||
spa_bt_device_emit_codec_switched(rfcomm->device, -EIO);
|
||||
return true;
|
||||
}
|
||||
|
||||
spa_log_debug(backend->log, NAME": RFCOMM selected_codec = %i", selected_codec);
|
||||
if (!rfcomm->transport || (rfcomm->transport->codec != selected_codec) ) {
|
||||
if (rfcomm->transport)
|
||||
spa_bt_transport_free(rfcomm->transport);
|
||||
rfcomm->codec = selected_codec;
|
||||
|
||||
rfcomm->transport = _transport_create(rfcomm);
|
||||
if (rfcomm->transport == NULL) {
|
||||
spa_log_warn(backend->log, NAME": can't create transport: %m");
|
||||
// TODO: We should manage the missing transport
|
||||
rfcomm_send_reply(source, "ERROR");
|
||||
return true;
|
||||
}
|
||||
rfcomm->transport->codec = selected_codec;
|
||||
spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile);
|
||||
spa_log_debug(backend->log, NAME": RFCOMM selected_codec = %i", selected_codec);
|
||||
|
||||
/* Recreate transport, since previous connection may now be invalid */
|
||||
if (rfcomm->transport)
|
||||
spa_bt_transport_free(rfcomm->transport);
|
||||
|
||||
rfcomm->transport = _transport_create(rfcomm);
|
||||
if (rfcomm->transport == NULL) {
|
||||
spa_log_warn(backend->log, NAME": can't create transport: %m");
|
||||
// TODO: We should manage the missing transport
|
||||
rfcomm_send_reply(source, "ERROR");
|
||||
if (was_switching_codec)
|
||||
spa_bt_device_emit_codec_switched(rfcomm->device, -ENOMEM);
|
||||
return true;
|
||||
}
|
||||
rfcomm->transport->codec = selected_codec;
|
||||
spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile);
|
||||
|
||||
rfcomm_send_reply(source, "OK");
|
||||
if (was_switching_codec)
|
||||
spa_bt_device_emit_codec_switched(rfcomm->device, 0);
|
||||
} else if (sscanf(buf, "AT+VGM=%u", &gain) == 1) {
|
||||
if (gain <= 15) {
|
||||
/* t->microphone_gain = gain; */
|
||||
|
|
@ -983,6 +1005,136 @@ static const struct spa_bt_transport_implementation sco_transport_impl = {
|
|||
.release = sco_release_cb,
|
||||
};
|
||||
|
||||
static struct rfcomm *device_find_rfcomm(struct impl *backend, struct spa_bt_device *device)
|
||||
{
|
||||
struct rfcomm *rfcomm;
|
||||
spa_list_for_each(rfcomm, &backend->rfcomm_list, link) {
|
||||
if (rfcomm->device == device)
|
||||
return rfcomm;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int backend_native_supports_codec(void *data, struct spa_bt_device *device, unsigned int codec)
|
||||
{
|
||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||
struct impl *backend = data;
|
||||
struct rfcomm *rfcomm;
|
||||
|
||||
rfcomm = device_find_rfcomm(backend, device);
|
||||
if (rfcomm == NULL || rfcomm->profile != SPA_BT_PROFILE_HFP_HF)
|
||||
return -ENOTSUP;
|
||||
|
||||
if (codec == HFP_AUDIO_CODEC_CVSD)
|
||||
return 1;
|
||||
|
||||
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
|
||||
return -ENOTSUP;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int codec_switch_stop_timer(struct rfcomm *rfcomm)
|
||||
{
|
||||
struct impl *backend = rfcomm->backend;
|
||||
struct itimerspec ts;
|
||||
|
||||
if (rfcomm->timer.data == NULL)
|
||||
return 0;
|
||||
|
||||
spa_loop_remove_source(backend->main_loop, &rfcomm->timer);
|
||||
ts.it_value.tv_sec = 0;
|
||||
ts.it_value.tv_nsec = 0;
|
||||
ts.it_interval.tv_sec = 0;
|
||||
ts.it_interval.tv_nsec = 0;
|
||||
spa_system_timerfd_settime(backend->main_system, rfcomm->timer.fd, 0, &ts, NULL);
|
||||
spa_system_close(backend->main_system, rfcomm->timer.fd);
|
||||
rfcomm->timer.data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void codec_switch_timer_event(struct spa_source *source)
|
||||
{
|
||||
struct rfcomm *rfcomm = source->data;
|
||||
struct impl *backend = rfcomm->backend;
|
||||
uint64_t exp;
|
||||
|
||||
if (spa_system_timerfd_read(backend->main_system, source->fd, &exp) < 0)
|
||||
spa_log_warn(backend->log, "error reading timerfd: %s", strerror(errno));
|
||||
|
||||
codec_switch_stop_timer(rfcomm);
|
||||
|
||||
spa_log_debug(backend->log, "rfcomm %p: codec switch timeout", rfcomm);
|
||||
|
||||
if (rfcomm->hfp_ag_switching_codec) {
|
||||
rfcomm->hfp_ag_switching_codec = false;
|
||||
if (rfcomm->device)
|
||||
spa_bt_device_emit_codec_switched(rfcomm->device, -EIO);
|
||||
}
|
||||
}
|
||||
|
||||
static int codec_switch_start_timer(struct rfcomm *rfcomm, int timeout_msec)
|
||||
{
|
||||
struct impl *backend = rfcomm->backend;
|
||||
struct itimerspec ts;
|
||||
|
||||
spa_log_debug(backend->log, "rfcomm %p: start timer", rfcomm);
|
||||
if (rfcomm->timer.data == NULL) {
|
||||
rfcomm->timer.data = rfcomm;
|
||||
rfcomm->timer.func = codec_switch_timer_event;
|
||||
rfcomm->timer.fd = spa_system_timerfd_create(backend->main_system,
|
||||
CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
|
||||
rfcomm->timer.mask = SPA_IO_IN;
|
||||
rfcomm->timer.rmask = 0;
|
||||
spa_loop_add_source(backend->main_loop, &rfcomm->timer);
|
||||
}
|
||||
ts.it_value.tv_sec = timeout_msec / SPA_MSEC_PER_SEC;
|
||||
ts.it_value.tv_nsec = (timeout_msec % SPA_MSEC_PER_SEC) * SPA_NSEC_PER_MSEC;
|
||||
ts.it_interval.tv_sec = 0;
|
||||
ts.it_interval.tv_nsec = 0;
|
||||
spa_system_timerfd_settime(backend->main_system, rfcomm->timer.fd, 0, &ts, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int backend_native_ensure_codec(void *data, struct spa_bt_device *device, unsigned int codec)
|
||||
{
|
||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||
struct impl *backend = data;
|
||||
struct rfcomm *rfcomm;
|
||||
int res;
|
||||
char msg[16];
|
||||
|
||||
res = backend_native_supports_codec(data, device, codec);
|
||||
if (res <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
rfcomm = device_find_rfcomm(backend, device);
|
||||
if (rfcomm == NULL)
|
||||
return -ENOTSUP;
|
||||
|
||||
if (rfcomm->codec == codec) {
|
||||
spa_bt_device_emit_codec_switched(device, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
snprintf(msg, sizeof(msg), "+BCS: %d", codec);
|
||||
if ((res = rfcomm_send_reply(&rfcomm->source, msg)) < 0)
|
||||
return res;
|
||||
|
||||
rfcomm->hfp_ag_switching_codec = true;
|
||||
codec_switch_start_timer(rfcomm, HFP_CODEC_SWITCH_TIMEOUT_MSEC);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -ENOTSUP;
|
||||
#endif
|
||||
}
|
||||
|
||||
static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessage *m, void *userdata)
|
||||
{
|
||||
struct impl *backend = userdata;
|
||||
|
|
@ -1502,6 +1654,8 @@ static const struct spa_bt_backend_implementation backend_impl = {
|
|||
.free = backend_native_free,
|
||||
.register_profiles = backend_native_register_profiles,
|
||||
.unregister_profiles = backend_native_unregister_profiles,
|
||||
.ensure_codec = backend_native_ensure_codec,
|
||||
.supports_codec = backend_native_supports_codec,
|
||||
};
|
||||
|
||||
struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
|
||||
|
|
@ -1526,6 +1680,7 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
|
|||
backend->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
|
||||
backend->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus);
|
||||
backend->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop);
|
||||
backend->main_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System);
|
||||
backend->conn = dbus_connection;
|
||||
backend->sco.fd = -1;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue