bluetooth: add wideband audio codec negotiation to HFP

The HFP protocol supports the ability to negotiate codecs if that is
supported by both AG and HF.  This patch adds advertising of codec
negotiation support and the ability to negotiate a codec change.  The
only currently supported extra codec (as of HF 1.7.1) is mSBC.  mSBC
requires that the transmission be done over an eSCO link with
Transparent Data.  The linux kernel ensures the former, but we have to
manually set the socket to transparent data.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>
This commit is contained in:
James Bottomley 2021-02-17 09:44:35 +03:00 committed by PulseAudio Marge Bot
parent f22cfa8f81
commit 4444ecad6f
3 changed files with 72 additions and 5 deletions

View file

@ -35,6 +35,7 @@
#include <bluetooth/sco.h>
#include "bluez5-util.h"
#include "bt-codec-msbc.h"
#define HSP_MAX_GAIN 15
@ -59,6 +60,7 @@ struct transport_data {
struct hfp_config {
uint32_t capabilities;
int state;
bool support_msbc;
};
/*
@ -91,7 +93,7 @@ enum hfp_ag_features {
/* gateway features we support, which is as little as we can get away with */
static uint32_t hfp_features =
/* HFP 1.6 requires this */
(1 << HFP_AG_ESTATUS );
(1 << HFP_AG_ESTATUS ) | (1 << HFP_AG_CODECS);
#define HSP_AG_PROFILE "/Profile/HSPAGProfile"
#define HFP_AG_PROFILE "/Profile/HFPAGProfile"
@ -219,6 +221,20 @@ static void rfcomm_write_response(int fd, const char *fmt, ...)
va_end(ap);
}
static int sco_setsockopt_enable_bt_voice(pa_bluetooth_transport *t, int fd) {
/* the mSBC codec requires a special transparent eSCO connection */
struct bt_voice voice;
memset(&voice, 0, sizeof(voice));
voice.setting = BT_VOICE_TRANSPARENT;
if (setsockopt(fd, SOL_BLUETOOTH, BT_VOICE, &voice, sizeof(voice)) < 0) {
pa_log_error("sockopt(): %s", pa_cstrerror(errno));
return -1;
}
pa_log_info("Enabled BT_VOICE_TRANSPARENT connection for mSBC");
return 0;
}
static int sco_do_connect(pa_bluetooth_transport *t) {
pa_bluetooth_device *d = t->device;
struct sockaddr_sco addr;
@ -254,6 +270,9 @@ static int sco_do_connect(pa_bluetooth_transport *t) {
goto fail_close;
}
if (t->setsockopt && t->setsockopt(t, sock) < 0)
goto fail_close;
memset(&addr, 0, len);
addr.sco_family = AF_BLUETOOTH;
bacpy(&addr.sco_bdaddr, &dst);
@ -323,6 +342,11 @@ static int sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu
}
}
/* read/decode machinery only works if we get at most one MSBC encoded packet at a time
* when it is fixed to process stream of packets, lift this assertion */
pa_assert(*imtu <= MSBC_PACKET_SIZE);
pa_assert(*omtu <= MSBC_PACKET_SIZE);
return sock;
fail:
@ -526,6 +550,7 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
{
struct hfp_config *c = t->config;
int val;
char str[5];
/* stateful negotiation */
if (c->state == 0 && sscanf(buf, "AT+BRSF=%d", &val) == 1) {
@ -534,6 +559,13 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
rfcomm_write_response(fd, "+BRSF: %d", hfp_features);
c->state = 1;
return true;
} else if (c->state == 1 && sscanf(buf, "AT+BAC=%3s", str) == 1) {
if (strncmp(str, "1,2", 3) == 0)
c->support_msbc = true;
else
c->support_msbc = false;
return true;
} else if (c->state == 1 && pa_startswith(buf, "AT+CIND=?")) {
/* we declare minimal no indicators */
@ -543,21 +575,52 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
"(\"call\",(0-1)),"
"(\"callheld\",(0-2))");
c->state = 2;
return true;
} else if (c->state == 2 && pa_startswith(buf, "AT+CIND?")) {
rfcomm_write_response(fd, "+CIND: 0,0");
c->state = 3;
return true;
} else if ((c->state == 2 || c->state == 3) && pa_startswith(buf, "AT+CMER=")) {
rfcomm_write_response(fd, "OK");
c->state = 4;
t->bt_codec = pa_bluetooth_get_hf_codec("CVSD");
transport_put(t);
if (c->support_msbc) {
rfcomm_write_response(fd, "+BCS:2");
c->state = 4;
} else {
c->state = 5;
t->bt_codec = pa_bluetooth_get_hf_codec("CVSD");
t->setsockopt = NULL;
transport_put(t);
}
return false;
} else if (sscanf(buf, "AT+BCS=%d", &val)) {
if (val == 1) {
t->bt_codec = pa_bluetooth_get_hf_codec("CVSD");
t->setsockopt = NULL;
} else if (val == 2) {
t->bt_codec = pa_bluetooth_get_hf_codec("mSBC");
t->setsockopt = sco_setsockopt_enable_bt_voice;
} else
pa_assert_not_reached();
if (c->state == 4) {
c->state = 5;
pa_log_info("HFP negotiated codec %s", t->bt_codec->name);
transport_put(t);
}
return true;
} if (c->state == 4) {
/* the ack for the codec setting may take a while. we need
* to reply OK to everything else until then */
return true;
}
/* if we get here, negotiation should be complete */
if (c->state != 4) {
if (c->state != 5) {
pa_log_error("HFP negotiation failed in state %d with inbound %s\n",
c->state, buf);
rfcomm_write_response(fd, "ERROR");