mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
bluez5: boost A2DP duplex microphone volume by 20 dB
There doesn't seem to be a way to control the A2DP duplex microphone HW volume gain, and devices sometimes have very low mic volumes. Work around this by boosting the software volume scale by +20 dB. If it causes clipping, the user can just reduce the volume to bring SW gain below 1.0.
This commit is contained in:
parent
e08cdf27d5
commit
ab5fcf4a66
3 changed files with 102 additions and 14 deletions
|
|
@ -473,6 +473,20 @@ static int msbc_enum_config(const struct a2dp_codec *codec,
|
||||||
return *param == NULL ? -EIO : 1;
|
return *param == NULL ? -EIO : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int msbc_validate_config(const struct a2dp_codec *codec, uint32_t flags,
|
||||||
|
const void *caps, size_t caps_size,
|
||||||
|
struct spa_audio_info *info)
|
||||||
|
{
|
||||||
|
spa_zero(*info);
|
||||||
|
info->media_type = SPA_MEDIA_TYPE_audio;
|
||||||
|
info->media_subtype = SPA_MEDIA_SUBTYPE_raw;
|
||||||
|
info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE;
|
||||||
|
info->info.raw.channels = 1;
|
||||||
|
info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO;
|
||||||
|
info->info.raw.rate = 16000;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int msbc_reduce_bitpool(void *data)
|
static int msbc_reduce_bitpool(void *data)
|
||||||
{
|
{
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
|
|
@ -679,6 +693,7 @@ static const struct a2dp_codec aptx_ll_msbc = {
|
||||||
.fill_caps = codec_fill_caps,
|
.fill_caps = codec_fill_caps,
|
||||||
.select_config = codec_select_config_ll,
|
.select_config = codec_select_config_ll,
|
||||||
.enum_config = msbc_enum_config,
|
.enum_config = msbc_enum_config,
|
||||||
|
.validate_config = msbc_validate_config,
|
||||||
.init = msbc_init,
|
.init = msbc_init,
|
||||||
.deinit = msbc_deinit,
|
.deinit = msbc_deinit,
|
||||||
.get_block_size = msbc_get_block_size,
|
.get_block_size = msbc_get_block_size,
|
||||||
|
|
|
||||||
|
|
@ -409,6 +409,21 @@ static int duplex_enum_config(const struct a2dp_codec *codec,
|
||||||
return *param == NULL ? -EIO : 1;
|
return *param == NULL ? -EIO : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int duplex_validate_config(const struct a2dp_codec *codec, uint32_t flags,
|
||||||
|
const void *caps, size_t caps_size,
|
||||||
|
struct spa_audio_info *info)
|
||||||
|
{
|
||||||
|
spa_zero(*info);
|
||||||
|
info->media_type = SPA_MEDIA_TYPE_audio;
|
||||||
|
info->media_subtype = SPA_MEDIA_SUBTYPE_raw;
|
||||||
|
info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE;
|
||||||
|
info->info.raw.channels = 2;
|
||||||
|
info->info.raw.position[0] = SPA_AUDIO_CHANNEL_FL;
|
||||||
|
info->info.raw.position[1] = SPA_AUDIO_CHANNEL_FR;
|
||||||
|
info->info.raw.rate = 16000;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int duplex_reduce_bitpool(void *data)
|
static int duplex_reduce_bitpool(void *data)
|
||||||
{
|
{
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
|
|
@ -567,6 +582,7 @@ static const struct a2dp_codec duplex_codec = {
|
||||||
.fill_caps = codec_fill_caps,
|
.fill_caps = codec_fill_caps,
|
||||||
.select_config = codec_select_config,
|
.select_config = codec_select_config,
|
||||||
.enum_config = duplex_enum_config,
|
.enum_config = duplex_enum_config,
|
||||||
|
.validate_config = duplex_validate_config,
|
||||||
.init = duplex_init,
|
.init = duplex_init,
|
||||||
.deinit = duplex_deinit,
|
.deinit = duplex_deinit,
|
||||||
.get_block_size = duplex_get_block_size,
|
.get_block_size = duplex_get_block_size,
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ struct node {
|
||||||
unsigned int active:1;
|
unsigned int active:1;
|
||||||
unsigned int mute:1;
|
unsigned int mute:1;
|
||||||
unsigned int save:1;
|
unsigned int save:1;
|
||||||
|
unsigned int a2dp_duplex:1;
|
||||||
uint32_t n_channels;
|
uint32_t n_channels;
|
||||||
int64_t latency_offset;
|
int64_t latency_offset;
|
||||||
uint32_t channels[SPA_AUDIO_MAX_CHANNELS];
|
uint32_t channels[SPA_AUDIO_MAX_CHANNELS];
|
||||||
|
|
@ -290,6 +291,26 @@ static void emit_volume(struct impl *this, struct node *node)
|
||||||
|
|
||||||
static void emit_info(struct impl *this, bool full);
|
static void emit_info(struct impl *this, bool full);
|
||||||
|
|
||||||
|
static float get_soft_volume_boost(struct node *node)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* For A2DP duplex, the duplex microphone channel sometimes does not appear
|
||||||
|
* to have hardware gain, and input volume is very low.
|
||||||
|
*
|
||||||
|
* Work around this by boosting the software volume level, i.e. adjust
|
||||||
|
* the scale on the user-visible volume control to something more sensible.
|
||||||
|
* If this causes clipping, the user can just reduce the mic volume to
|
||||||
|
* bring SW gain below 1.
|
||||||
|
*/
|
||||||
|
if (node->a2dp_duplex && node->transport &&
|
||||||
|
node->id == DEVICE_ID_SOURCE &&
|
||||||
|
!node->transport->volumes[SPA_BT_VOLUME_ID_RX].active)
|
||||||
|
return 10.0f; /* 20 dB boost */
|
||||||
|
|
||||||
|
/* In all other cases, no boost */
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
static float node_get_hw_volume(struct node *node)
|
static float node_get_hw_volume(struct node *node)
|
||||||
{
|
{
|
||||||
uint32_t i;
|
uint32_t i;
|
||||||
|
|
@ -350,6 +371,33 @@ static const struct spa_bt_transport_events transport_events = {
|
||||||
.volume_changed = volume_changed,
|
.volume_changed = volume_changed,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t *n_channels, uint32_t *channels)
|
||||||
|
{
|
||||||
|
const struct a2dp_codec *codec;
|
||||||
|
struct spa_audio_info info = { 0 };
|
||||||
|
|
||||||
|
if (!a2dp_duplex || !t->a2dp_codec || !t->a2dp_codec->duplex_codec) {
|
||||||
|
*n_channels = t->n_channels;
|
||||||
|
memcpy(channels, t->channels, t->n_channels * sizeof(uint32_t));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec = t->a2dp_codec->duplex_codec;
|
||||||
|
|
||||||
|
if (!codec->validate_config ||
|
||||||
|
codec->validate_config(codec, 0,
|
||||||
|
t->configuration, t->configuration_len,
|
||||||
|
&info) < 0) {
|
||||||
|
*n_channels = 1;
|
||||||
|
channels[0] = SPA_AUDIO_CHANNEL_MONO;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*n_channels = info.info.raw.channels;
|
||||||
|
memcpy(channels, info.info.raw.position,
|
||||||
|
info.info.raw.channels * sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
|
||||||
static void emit_node(struct impl *this, struct spa_bt_transport *t,
|
static void emit_node(struct impl *this, struct spa_bt_transport *t,
|
||||||
uint32_t id, const char *factory_name, bool a2dp_duplex)
|
uint32_t id, const char *factory_name, bool a2dp_duplex)
|
||||||
{
|
{
|
||||||
|
|
@ -391,27 +439,35 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t,
|
||||||
spa_device_emit_object_info(&this->hooks, id, &info);
|
spa_device_emit_object_info(&this->hooks, id, &info);
|
||||||
|
|
||||||
if (!is_dyn_node) {
|
if (!is_dyn_node) {
|
||||||
if (this->nodes[id].n_channels > 0) {
|
uint32_t prev_channels = this->nodes[id].n_channels;
|
||||||
size_t i;
|
float boost;
|
||||||
|
|
||||||
/*
|
|
||||||
* Spread mono volume to all channels, if we had switched HFP -> A2DP.
|
|
||||||
* XXX: we should also use different route for hfp and a2dp
|
|
||||||
*/
|
|
||||||
for (i = this->nodes[id].n_channels; i < t->n_channels; ++i)
|
|
||||||
this->nodes[id].volumes[i] = this->nodes[id].volumes[i % this->nodes[id].n_channels];
|
|
||||||
}
|
|
||||||
|
|
||||||
this->nodes[id].impl = this;
|
this->nodes[id].impl = this;
|
||||||
this->nodes[id].active = true;
|
this->nodes[id].active = true;
|
||||||
this->nodes[id].n_channels = t->n_channels;
|
this->nodes[id].a2dp_duplex = a2dp_duplex;
|
||||||
memcpy(this->nodes[id].channels, t->channels,
|
get_channels(t, a2dp_duplex, &this->nodes[id].n_channels, this->nodes[id].channels);
|
||||||
t->n_channels * sizeof(uint32_t));
|
|
||||||
if (this->nodes[id].transport)
|
if (this->nodes[id].transport)
|
||||||
spa_hook_remove(&this->nodes[id].transport_listener);
|
spa_hook_remove(&this->nodes[id].transport_listener);
|
||||||
this->nodes[id].transport = t;
|
this->nodes[id].transport = t;
|
||||||
spa_bt_transport_add_listener(t, &this->nodes[id].transport_listener, &transport_events, &this->nodes[id]);
|
spa_bt_transport_add_listener(t, &this->nodes[id].transport_listener, &transport_events, &this->nodes[id]);
|
||||||
|
|
||||||
|
if (prev_channels > 0) {
|
||||||
|
size_t i;
|
||||||
|
/*
|
||||||
|
* Spread mono volume to all channels, if we had switched HFP -> A2DP.
|
||||||
|
* XXX: we should also use different route for hfp and a2dp
|
||||||
|
*/
|
||||||
|
for (i = prev_channels; i < this->nodes[id].n_channels; ++i)
|
||||||
|
this->nodes[id].volumes[i] = this->nodes[id].volumes[i % prev_channels];
|
||||||
|
}
|
||||||
|
|
||||||
|
boost = get_soft_volume_boost(&this->nodes[id]);
|
||||||
|
if (boost != 1.0f) {
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < this->nodes[id].n_channels; ++i)
|
||||||
|
this->nodes[id].soft_volumes[i] = this->nodes[id].volumes[i] * boost;
|
||||||
|
}
|
||||||
|
|
||||||
emit_node_props(this, &this->nodes[id], true);
|
emit_node_props(this, &this->nodes[id], true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1628,8 +1684,9 @@ static int node_set_volume(struct impl *this, struct node *node, float volumes[]
|
||||||
node_update_soft_volumes(node, hw_volume);
|
node_update_soft_volumes(node, hw_volume);
|
||||||
spa_bt_transport_set_volume(node->transport, node->id, hw_volume);
|
spa_bt_transport_set_volume(node->transport, node->id, hw_volume);
|
||||||
} else {
|
} else {
|
||||||
|
float boost = get_soft_volume_boost(node);
|
||||||
for (uint32_t i = 0; i < node->n_channels; ++i)
|
for (uint32_t i = 0; i < node->n_channels; ++i)
|
||||||
node->soft_volumes[i] = node->volumes[i];
|
node->soft_volumes[i] = node->volumes[i] * boost;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit_volume(this, node);
|
emit_volume(this, node);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue