bluez5: use separate routes for HFP and A2DP

The A2DP and HFP profiles may have different volume curves, so trying to
convert volumes between the two can produce undesirable volume spikes.
For example, when one of them is using hardware volume and the other
software.

Fix by separating HFP and A2DP routes.
This commit is contained in:
Pauli Virtanen 2022-03-05 18:31:41 +02:00 committed by Wim Taymans
parent bb2f054d43
commit 276c4bc554

View file

@ -1301,16 +1301,17 @@ static bool validate_profile(struct impl *this, uint32_t profile,
}
static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
uint32_t id, uint32_t port, uint32_t dev, uint32_t profile)
uint32_t id, uint32_t port, uint32_t profile)
{
struct spa_bt_device *device = this->bt_dev;
struct spa_pod_frame f[2];
enum spa_direction direction;
const char *name_prefix, *description, *port_type;
const char *name_prefix, *description, *hfp_description, *port_type;
enum spa_bt_form_factor ff;
enum spa_bluetooth_audio_codec codec;
char name[128];
uint32_t i, j, mask, next;
uint32_t dev = SPA_ID_INVALID, enum_dev;
ff = spa_bt_form_factor_from_class(device->bluetooth_class);
@ -1318,52 +1319,62 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
case SPA_BT_FORM_FACTOR_HEADSET:
name_prefix = "headset";
description = _("Headset");
hfp_description = _("Handsfree");
port_type = "headset";
break;
case SPA_BT_FORM_FACTOR_HANDSFREE:
name_prefix = "handsfree";
description = _("Handsfree");
hfp_description = _("Handsfree (HFP)");
port_type = "handsfree";
break;
case SPA_BT_FORM_FACTOR_MICROPHONE:
name_prefix = "microphone";
description = _("Microphone");
hfp_description = _("Handsfree");
port_type = "mic";
break;
case SPA_BT_FORM_FACTOR_SPEAKER:
name_prefix = "speaker";
description = _("Speaker");
hfp_description = _("Handsfree");
port_type = "speaker";
break;
case SPA_BT_FORM_FACTOR_HEADPHONE:
name_prefix = "headphone";
description = _("Headphone");
hfp_description = _("Handsfree");
port_type = "headphones";
break;
case SPA_BT_FORM_FACTOR_PORTABLE:
name_prefix = "portable";
description = _("Portable");
hfp_description = _("Handsfree");
port_type = "portable";
break;
case SPA_BT_FORM_FACTOR_CAR:
name_prefix = "car";
description = _("Car");
hfp_description = _("Handsfree");
port_type = "car";
break;
case SPA_BT_FORM_FACTOR_HIFI:
name_prefix = "hifi";
description = _("HiFi");
hfp_description = _("Handsfree");
port_type = "hifi";
break;
case SPA_BT_FORM_FACTOR_PHONE:
name_prefix = "phone";
description = _("Phone");
hfp_description = _("Handsfree");
port_type = "phone";
break;
case SPA_BT_FORM_FACTOR_UNKNOWN:
default:
name_prefix = "bluetooth";
description = _("Bluetooth");
hfp_description = _("Bluetooth (HFP)");
port_type = "bluetooth";
break;
}
@ -1372,16 +1383,51 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
case 0:
direction = SPA_DIRECTION_INPUT;
snprintf(name, sizeof(name), "%s-input", name_prefix);
enum_dev = DEVICE_ID_SOURCE;
if (profile == DEVICE_PROFILE_A2DP)
dev = enum_dev;
else if (profile != SPA_ID_INVALID)
enum_dev = SPA_ID_INVALID;
break;
case 1:
direction = SPA_DIRECTION_OUTPUT;
snprintf(name, sizeof(name), "%s-output", name_prefix);
enum_dev = DEVICE_ID_SINK;
if (profile == DEVICE_PROFILE_A2DP)
dev = enum_dev;
else if (profile != SPA_ID_INVALID)
enum_dev = SPA_ID_INVALID;
break;
case 2:
direction = SPA_DIRECTION_INPUT;
snprintf(name, sizeof(name), "%s-hf-input", name_prefix);
description = hfp_description;
enum_dev = DEVICE_ID_SOURCE;
if (profile == DEVICE_PROFILE_HSP_HFP)
dev = enum_dev;
else if (profile != SPA_ID_INVALID)
enum_dev = SPA_ID_INVALID;
break;
case 3:
direction = SPA_DIRECTION_OUTPUT;
snprintf(name, sizeof(name), "%s-hf-output", name_prefix);
description = hfp_description;
enum_dev = DEVICE_ID_SINK;
if (profile == DEVICE_PROFILE_HSP_HFP)
dev = enum_dev;
else if (profile != SPA_ID_INVALID)
enum_dev = SPA_ID_INVALID;
break;
default:
errno = EINVAL;
return NULL;
}
if (enum_dev == SPA_ID_INVALID) {
errno = EINVAL;
return NULL;
}
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamRoute, id);
spa_pod_builder_add(b,
SPA_PARAM_ROUTE_index, SPA_POD_Int(port),
@ -1406,6 +1452,11 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
for (i = 1; (j = get_profile_from_index(this, i, &next, &codec)) != SPA_ID_INVALID; i = next) {
uint32_t profile_mask;
if (j == DEVICE_PROFILE_A2DP && !(port == 0 || port == 1))
continue;
if (j == DEVICE_PROFILE_HSP_HFP && !(port == 2 || port == 3))
continue;
profile_mask = profile_direction_mask(this, j, codec);
if (!(profile_mask & (1 << direction)))
continue;
@ -1472,8 +1523,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0);
spa_pod_builder_push_array(b, &f[1]);
/* port and device indexes are the same, 0=source, 1=sink */
spa_pod_builder_int(b, port);
spa_pod_builder_int(b, enum_dev);
spa_pod_builder_pop(b, &f[1]);
if (profile != SPA_ID_INVALID) {
@ -1640,9 +1690,8 @@ static int impl_enum_params(void *object, int seq,
case SPA_PARAM_EnumRoute:
{
switch (result.index) {
case 0: case 1:
param = build_route(this, &b, id, result.index,
SPA_ID_INVALID, SPA_ID_INVALID);
case 0: case 1: case 2: case 3:
param = build_route(this, &b, id, result.index, SPA_ID_INVALID);
if (param == NULL)
goto next;
break;
@ -1654,9 +1703,8 @@ static int impl_enum_params(void *object, int seq,
case SPA_PARAM_Route:
{
switch (result.index) {
case 0: case 1:
param = build_route(this, &b, id, result.index,
result.index, this->profile);
case 0: case 1: case 2: case 3:
param = build_route(this, &b, id, result.index, this->profile);
if (param == NULL)
goto next;
break;