mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-10-29 05:40:23 -04:00
bluetooth: Handle muting over A2DP Absolute Volume
Muting is a special case that is explicit and separated from volume within PulseAudio, but merged together into a single variable over the A2DP AVRCP connection. Most devices report either 0 or 1 as their lowest volume. Note that capital Volume in the following paragraphs refer to the Volume property on org.bluez.MediaTransport1. This commit deals with the following cases: 1. When the PA stream is muted, notify the peer by setting Volume to 0. While the stream is muted real_volume should _not_ be updated in the callback when Volume changes to <= 1, or unmuting will _not_ return to the original volume. 2. When locally changing stream volume, and muting is enabled, do _not_ send updates to the peer. The peer should stick with Volume = 0. 3. When locally changing stream volume (and sending that to the peer) any resulting updates on the Volume property should _not_ turn on muting. 4. When the peer changes Volume, turn off muting (if enabled) when Volume > 1. 5. When the peer changes Volume, turn on muting when Volume <= 1. Such an implementation matches what happens on an Android device. Muting sets the shared volume to 0, and upping the volume on the peer (in case of headphones which usually only have up/down buttons) will set it a single increment above that. Unmuting the stream from PA however will return the stream back to the original volume, and notify the peer of the same. The discrepancy between merged and separate muting+volume results in a conflict between point 3. and 5.: The peer (and/or dbus API) always responds with the Volume property changing after PA writes it. If PA's stream volume is decreased to a point where the callback triggers with Volume <= 1 the stream is consequently muted, in accordance to point 5. This is especially problematic when decreasing the local volume too far (whether intentionally or not), as muting is not turned off by default when the stream volume is increased afterwards. This case is dealt with by preventing any Volume lower than 2 to be sent to the device at any given point. Only the device itself can return a Volume lower than that to enable implicit muting. However, when point 5. happens, it is important to note that it can only be unmuted by explicitly unmuting the stream from PA, _or_ increasing the volume on the peer.
This commit is contained in:
parent
5af2afba85
commit
3953d44569
3 changed files with 98 additions and 13 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -940,12 +940,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;
|
||||
|
|
@ -982,6 +1006,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 {
|
||||
|
|
@ -1001,6 +1027,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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1177,12 +1204,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;
|
||||
|
|
@ -1224,6 +1275,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 {
|
||||
|
|
@ -1236,6 +1289,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;
|
||||
|
|
@ -2433,12 +2487,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;
|
||||
}
|
||||
|
||||
|
|
@ -2461,6 +2528,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))
|
||||
|
|
@ -2468,6 +2544,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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue