diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index 2431b3831..ba972e053 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -103,11 +103,7 @@ "" 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; diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h index fff3d573c..0ed501601 100644 --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -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; diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index dc6809ce5..1e31a0e8c 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -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; }