Merge branch 'bluetooth-muting' into 'master'

Draft: bluetooth: Handle muting over A2DP Absolute Volume

See merge request pulseaudio/pulseaudio!533
This commit is contained in:
Marijn Suijten 2025-10-01 03:51:43 +00:00
commit 5c0d45342e
3 changed files with 98 additions and 13 deletions

View file

@ -103,11 +103,7 @@
"</node>"
static pa_volume_t a2dp_gain_to_volume(uint16_t gain) {
pa_volume_t volume = (pa_volume_t) ((
gain * PA_VOLUME_NORM
/* Round to closest by adding half the denominator */
+ A2DP_MAX_GAIN / 2
) / A2DP_MAX_GAIN);
pa_volume_t volume = A2DP_GAIN_TO_VOLUME(gain);
if (volume > PA_VOLUME_NORM)
volume = PA_VOLUME_NORM;
@ -116,11 +112,7 @@ static pa_volume_t a2dp_gain_to_volume(uint16_t gain) {
}
static uint16_t volume_to_a2dp_gain(pa_volume_t volume) {
uint16_t gain = (uint16_t) ((
volume * A2DP_MAX_GAIN
/* Round to closest by adding half the denominator */
+ PA_VOLUME_NORM / 2
) / PA_VOLUME_NORM);
uint16_t gain = VOLUME_TO_A2DP_GAIN(volume);
if (gain > A2DP_MAX_GAIN)
gain = A2DP_MAX_GAIN;

View file

@ -54,9 +54,22 @@
#define PA_BLUETOOTH_UUID_HFP_HF "0000111e-0000-1000-8000-00805f9b34fb"
#define PA_BLUETOOTH_UUID_HFP_AG "0000111f-0000-1000-8000-00805f9b34fb"
#define A2DP_MAX_GAIN 127
#define HSP_MAX_GAIN 15
#define A2DP_MAX_GAIN 127
/* Some devices only go as low as 1 */
#define A2DP_MUTE_GAIN 1
#define A2DP_MIN_GAIN (A2DP_MUTE_GAIN + 1)
/* Round to closest by adding half the denominator */
#define A2DP_GAIN_TO_VOLUME(gain) \
((pa_volume_t)(((gain)*PA_VOLUME_NORM + A2DP_MAX_GAIN / 2) / A2DP_MAX_GAIN))
#define VOLUME_TO_A2DP_GAIN(volume) \
((uint16_t)(((volume)*A2DP_MAX_GAIN + PA_VOLUME_NORM / 2) / PA_VOLUME_NORM))
#define A2DP_MUTE_VOLUME A2DP_GAIN_TO_VOLUME(A2DP_MUTE_GAIN)
#define A2DP_MIN_VOLUME A2DP_GAIN_TO_VOLUME(A2DP_MIN_GAIN)
typedef struct pa_bluetooth_transport pa_bluetooth_transport;
typedef struct pa_bluetooth_device pa_bluetooth_device;
typedef struct pa_bluetooth_adapter pa_bluetooth_adapter;

View file

@ -945,12 +945,36 @@ static void source_set_volume_cb(pa_source *s) {
pa_assert(u->transport);
pa_assert(u->transport->set_source_volume);
volume = pa_cvolume_max(&s->real_volume);
/* Prevent setting a gain below A2DP_MIN_GAIN, this is used to detect muting */
if (volume < A2DP_MIN_VOLUME)
volume = A2DP_MIN_VOLUME;
/* In the AG role, send a command to change microphone gain on the HS/HF */
volume = u->transport->set_source_volume(u->transport, pa_cvolume_max(&s->real_volume));
if (!s->muted)
volume = u->transport->set_source_volume(u->transport, volume);
pa_cvolume_set(&s->real_volume, u->decoder_sample_spec.channels, volume);
}
static void source_set_mute_cb(pa_source *s) {
struct userdata *u;
pa_assert(s);
u = s->userdata;
pa_assert(u);
pa_assert(u->source == s);
pa_assert(!pa_bluetooth_profile_should_attenuate_volume(u->profile));
pa_assert(u->transport);
pa_assert(u->transport->set_source_volume);
if (s->muted)
u->transport->set_source_volume(u->transport, 0);
else
source_set_volume_cb(s);
}
/* Run from main thread */
static void source_setup_volume_callback(pa_source *s) {
struct userdata *u;
@ -993,6 +1017,8 @@ static void source_setup_volume_callback(pa_source *s) {
u->source_volume_changed_slot = pa_hook_connect(&s->core->hooks[PA_CORE_HOOK_SOURCE_VOLUME_CHANGED],
PA_HOOK_NORMAL, sink_source_volume_changed_cb, u);
// TODO: Mute hook!
/* Send initial volume to peer, signalling support for volume control */
u->transport->set_source_volume(u->transport, pa_cvolume_max(&s->real_volume));
} else {
@ -1012,6 +1038,7 @@ static void source_setup_volume_callback(pa_source *s) {
pa_source_set_soft_volume(s, NULL);
pa_source_set_set_volume_callback(s, source_set_volume_cb);
pa_source_set_set_mute_callback(s, source_set_mute_cb);
s->n_volume_steps = HSP_MAX_GAIN + 1;
}
}
@ -1193,12 +1220,36 @@ static void sink_set_volume_cb(pa_sink *s) {
pa_assert(u->transport);
pa_assert(u->transport->set_sink_volume);
volume = pa_cvolume_max(&s->real_volume);
/* Prevent setting a gain below A2DP_MIN_GAIN, this is used to detect muting */
if (volume < A2DP_MIN_VOLUME)
volume = A2DP_MIN_VOLUME;
/* In the AG role, send a command to change speaker gain on the HS/HF */
volume = u->transport->set_sink_volume(u->transport, pa_cvolume_max(&s->real_volume));
if (!s->muted)
volume = u->transport->set_sink_volume(u->transport, volume);
pa_cvolume_set(&s->real_volume, u->encoder_sample_spec.channels, volume);
}
static void sink_set_mute_cb(pa_sink *s) {
struct userdata *u;
pa_assert(s);
u = s->userdata;
pa_assert(u);
pa_assert(u->sink == s);
pa_assert(!pa_bluetooth_profile_should_attenuate_volume(u->profile));
pa_assert(u->transport);
pa_assert(u->transport->set_sink_volume);
if (s->muted)
u->transport->set_sink_volume(u->transport, 0);
else
sink_set_volume_cb(s);
}
/* Run from main thread */
static void sink_setup_volume_callback(pa_sink *s) {
struct userdata *u;
@ -1246,6 +1297,8 @@ static void sink_setup_volume_callback(pa_sink *s) {
u->sink_volume_changed_slot = pa_hook_connect(&s->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED],
PA_HOOK_NORMAL, sink_source_volume_changed_cb, u);
// TODO: Hook up mute callback!
/* Send initial volume to peer, signalling support for volume control */
u->transport->set_sink_volume(u->transport, pa_cvolume_max(&s->real_volume));
} else {
@ -1258,6 +1311,7 @@ static void sink_setup_volume_callback(pa_sink *s) {
pa_sink_set_soft_volume(s, NULL);
pa_sink_set_set_volume_callback(s, sink_set_volume_cb);
pa_sink_set_set_mute_callback(s, sink_set_mute_cb);
if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK)
s->n_volume_steps = A2DP_MAX_GAIN + 1;
@ -2467,12 +2521,25 @@ static pa_hook_result_t transport_sink_volume_changed_cb(pa_bluetooth_discovery
sink_setup_volume_callback(u->sink);
if (t->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
// TODO: Apply to HSP too
if (volume <= A2DP_MUTE_VOLUME) {
pa_sink_mute_changed(u->sink, true);
/* Do not update local volume; unmute should jump back to previous */
return PA_HOOK_OK;
}
}
pa_cvolume_set(&v, u->encoder_sample_spec.channels, volume);
if (pa_bluetooth_profile_should_attenuate_volume(t->profile))
pa_sink_set_volume(u->sink, &v, true, true);
else
pa_sink_volume_changed(u->sink, &v);
/* Unmute _after_ reflecting peer volume */
if(u->sink->muted)
pa_sink_mute_changed(u->sink, false);
return PA_HOOK_OK;
}
@ -2495,6 +2562,15 @@ static pa_hook_result_t transport_source_volume_changed_cb(pa_bluetooth_discover
source_setup_volume_callback(u->source);
if (t->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
// TODO: Apply to HSP too
if (volume <= A2DP_MUTE_VOLUME) {
pa_source_mute_changed(u->source, true);
/* Do not update local volume; unmute should jump back to previous */
return PA_HOOK_OK;
}
}
pa_cvolume_set(&v, u->decoder_sample_spec.channels, volume);
if (pa_bluetooth_profile_should_attenuate_volume(t->profile))
@ -2502,6 +2578,10 @@ static pa_hook_result_t transport_source_volume_changed_cb(pa_bluetooth_discover
else
pa_source_volume_changed(u->source, &v);
/* Unmute _after_ reflecting peer volume */
if(u->source->muted)
pa_source_mute_changed(u->source, false);
return PA_HOOK_OK;
}