bluetooth: Move HSP gain conversions into backend-native

For the upcoming A2DP AVRCP Absolute Volume feature the code in BlueZ5
has to be generic to be reusable. Move this conversion so that it
becomes possible to implement A2DP volume - which uses different values
- on top without duplicating existing callback functionality.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/521>
This commit is contained in:
Marijn Suijten 2020-06-07 16:07:00 +02:00
parent cefee393fb
commit a575006aa8
3 changed files with 82 additions and 87 deletions

View file

@ -36,6 +36,8 @@
#include "bluez5-util.h" #include "bluez5-util.h"
#define HSP_MAX_GAIN 15
struct pa_bluetooth_backend { struct pa_bluetooth_backend {
pa_core *core; pa_core *core;
pa_dbus_connection *connection; pa_dbus_connection *connection;
@ -121,6 +123,25 @@ static uint32_t hfp_features =
" </interface>" \ " </interface>" \
"</node>" "</node>"
static pa_volume_t hsp_gain_to_volume(uint16_t gain) {
pa_volume_t volume = (pa_volume_t) (gain * PA_VOLUME_NORM / HSP_MAX_GAIN);
/* increment volume by one to correct rounding errors */
if (volume < PA_VOLUME_NORM)
volume++;
return volume;
}
static uint16_t volume_to_hsp_gain(pa_volume_t volume) {
uint16_t gain = volume * HSP_MAX_GAIN / PA_VOLUME_NORM;
if (gain > HSP_MAX_GAIN)
gain = HSP_MAX_GAIN;
return gain;
}
static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m, static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m,
DBusPendingCallNotifyFunction func, void *call_data) { DBusPendingCallNotifyFunction func, void *call_data) {
@ -516,13 +537,13 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
* RING: Sent by AG to HS to notify of an incoming call. It can safely be ignored because * RING: Sent by AG to HS to notify of an incoming call. It can safely be ignored because
* it does not expect a reply. */ * it does not expect a reply. */
if (sscanf(buf, "AT+VGS=%d", &gain) == 1 || sscanf(buf, "\r\n+VGM=%d\r\n", &gain) == 1) { if (sscanf(buf, "AT+VGS=%d", &gain) == 1 || sscanf(buf, "\r\n+VGM=%d\r\n", &gain) == 1) {
t->speaker_gain = gain; t->speaker_volume = hsp_gain_to_volume(gain);
pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED), t); pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_VOLUME_CHANGED), t);
do_reply = true; do_reply = true;
} else if (sscanf(buf, "AT+VGM=%d", &gain) == 1 || sscanf(buf, "\r\n+VGS=%d\r\n", &gain) == 1) { } else if (sscanf(buf, "AT+VGM=%d", &gain) == 1 || sscanf(buf, "\r\n+VGS=%d\r\n", &gain) == 1) {
t->microphone_gain = gain; t->microphone_volume = hsp_gain_to_volume(gain);
pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), t); pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_VOLUME_CHANGED), t);
do_reply = true; do_reply = true;
} else if (sscanf(buf, "AT+CKPD=%d", &dummy) == 1) { } else if (sscanf(buf, "AT+CKPD=%d", &dummy) == 1) {
do_reply = true; do_reply = true;
@ -559,13 +580,17 @@ static void transport_destroy(pa_bluetooth_transport *t) {
pa_xfree(trd); pa_xfree(trd);
} }
static void set_speaker_gain(pa_bluetooth_transport *t, uint16_t gain) { static pa_volume_t set_speaker_volume(pa_bluetooth_transport *t, pa_volume_t volume) {
struct transport_data *trd = t->userdata; struct transport_data *trd = t->userdata;
uint16_t gain = volume_to_hsp_gain(volume);
if (t->speaker_gain == gain) /* Propagate rounding and bound checks */
return; volume = hsp_gain_to_volume(gain);
t->speaker_gain = gain; if (t->speaker_volume == volume)
return volume;
t->speaker_volume = volume;
/* If we are in the AG role, we send a command to the head set to change /* If we are in the AG role, we send a command to the head set to change
* the speaker gain. In the HS role, source and sink are swapped, so * the speaker gain. In the HS role, source and sink are swapped, so
@ -575,15 +600,21 @@ static void set_speaker_gain(pa_bluetooth_transport *t, uint16_t gain) {
} else { } else {
rfcomm_write_command(trd->rfcomm_fd, "AT+VGM=%d", gain); rfcomm_write_command(trd->rfcomm_fd, "AT+VGM=%d", gain);
} }
return volume;
} }
static void set_microphone_gain(pa_bluetooth_transport *t, uint16_t gain) { static pa_volume_t set_microphone_volume(pa_bluetooth_transport *t, pa_volume_t volume) {
struct transport_data *trd = t->userdata; struct transport_data *trd = t->userdata;
uint16_t gain = volume_to_hsp_gain(volume);
if (t->microphone_gain == gain) /* Propagate rounding and bound checks */
return; volume = hsp_gain_to_volume(gain);
t->microphone_gain = gain; if (t->microphone_volume == volume)
return volume;
t->microphone_volume = volume;
/* If we are in the AG role, we send a command to the head set to change /* If we are in the AG role, we send a command to the head set to change
* the microphone gain. In the HS role, source and sink are swapped, so * the microphone gain. In the HS role, source and sink are swapped, so
@ -593,6 +624,8 @@ static void set_microphone_gain(pa_bluetooth_transport *t, uint16_t gain) {
} else { } else {
rfcomm_write_command(trd->rfcomm_fd, "AT+VGS=%d", gain); rfcomm_write_command(trd->rfcomm_fd, "AT+VGS=%d", gain);
} }
return volume;
} }
static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, void *userdata) { static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, void *userdata) {
@ -662,8 +695,8 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
t->acquire = sco_acquire_cb; t->acquire = sco_acquire_cb;
t->release = sco_release_cb; t->release = sco_release_cb;
t->destroy = transport_destroy; t->destroy = transport_destroy;
t->set_speaker_gain = set_speaker_gain; t->set_speaker_volume = set_speaker_volume;
t->set_microphone_gain = set_microphone_gain; t->set_microphone_volume = set_microphone_volume;
trd = pa_xnew0(struct transport_data, 1); trd = pa_xnew0(struct transport_data, 1);
trd->rfcomm_fd = fd; trd->rfcomm_fd = fd;

View file

@ -59,11 +59,11 @@ typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
typedef struct pa_bluetooth_backend pa_bluetooth_backend; typedef struct pa_bluetooth_backend pa_bluetooth_backend;
typedef enum pa_bluetooth_hook { typedef enum pa_bluetooth_hook {
PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */ PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */
PA_BLUETOOTH_HOOK_DEVICE_UNLINK, /* Call data: pa_bluetooth_device */ PA_BLUETOOTH_HOOK_DEVICE_UNLINK, /* Call data: pa_bluetooth_device */
PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */ PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */ PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_VOLUME_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */ PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_VOLUME_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_MAX PA_BLUETOOTH_HOOK_MAX
} pa_bluetooth_hook_t; } pa_bluetooth_hook_t;
@ -87,8 +87,8 @@ typedef enum pa_bluetooth_transport_state {
typedef int (*pa_bluetooth_transport_acquire_cb)(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu); typedef int (*pa_bluetooth_transport_acquire_cb)(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu);
typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t); typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
typedef void (*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t); typedef void (*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t);
typedef void (*pa_bluetooth_transport_set_speaker_gain_cb)(pa_bluetooth_transport *t, uint16_t gain); typedef pa_volume_t (*pa_bluetooth_transport_set_speaker_volume_cb)(pa_bluetooth_transport *t, pa_volume_t volume);
typedef void (*pa_bluetooth_transport_set_microphone_gain_cb)(pa_bluetooth_transport *t, uint16_t gain); typedef pa_volume_t (*pa_bluetooth_transport_set_microphone_volume_cb)(pa_bluetooth_transport *t, pa_volume_t volume);
struct pa_bluetooth_transport { struct pa_bluetooth_transport {
pa_bluetooth_device *device; pa_bluetooth_device *device;
@ -103,16 +103,16 @@ struct pa_bluetooth_transport {
const pa_a2dp_codec *a2dp_codec; const pa_a2dp_codec *a2dp_codec;
uint16_t microphone_gain; pa_volume_t microphone_volume;
uint16_t speaker_gain; pa_volume_t speaker_volume;
pa_bluetooth_transport_state_t state; pa_bluetooth_transport_state_t state;
pa_bluetooth_transport_acquire_cb acquire; pa_bluetooth_transport_acquire_cb acquire;
pa_bluetooth_transport_release_cb release; pa_bluetooth_transport_release_cb release;
pa_bluetooth_transport_destroy_cb destroy; pa_bluetooth_transport_destroy_cb destroy;
pa_bluetooth_transport_set_speaker_gain_cb set_speaker_gain; pa_bluetooth_transport_set_speaker_volume_cb set_speaker_volume;
pa_bluetooth_transport_set_microphone_gain_cb set_microphone_gain; pa_bluetooth_transport_set_microphone_volume_cb set_microphone_volume;
void *userdata; void *userdata;
}; };

View file

@ -67,8 +67,6 @@ PA_MODULE_USAGE(
#define FIXED_LATENCY_RECORD_A2DP (25 * PA_USEC_PER_MSEC) #define FIXED_LATENCY_RECORD_A2DP (25 * PA_USEC_PER_MSEC)
#define FIXED_LATENCY_RECORD_SCO (25 * PA_USEC_PER_MSEC) #define FIXED_LATENCY_RECORD_SCO (25 * PA_USEC_PER_MSEC)
#define HSP_MAX_GAIN 15
static const char* const valid_modargs[] = { static const char* const valid_modargs[] = {
"path", "path",
"autodetect_mtu", "autodetect_mtu",
@ -104,8 +102,8 @@ struct userdata {
pa_hook_slot *device_connection_changed_slot; pa_hook_slot *device_connection_changed_slot;
pa_hook_slot *transport_state_changed_slot; pa_hook_slot *transport_state_changed_slot;
pa_hook_slot *transport_speaker_gain_changed_slot; pa_hook_slot *transport_speaker_volume_changed_slot;
pa_hook_slot *transport_microphone_gain_changed_slot; pa_hook_slot *transport_microphone_volume_changed_slot;
pa_bluetooth_discovery *discovery; pa_bluetooth_discovery *discovery;
pa_bluetooth_device *device; pa_bluetooth_device *device;
@ -962,7 +960,6 @@ static int source_set_state_in_io_thread_cb(pa_source *s, pa_source_state_t new_
/* Run from main thread */ /* Run from main thread */
static void source_set_volume_cb(pa_source *s) { static void source_set_volume_cb(pa_source *s) {
uint16_t gain;
pa_volume_t volume; pa_volume_t volume;
struct userdata *u; struct userdata *u;
@ -974,30 +971,19 @@ static void source_set_volume_cb(pa_source *s) {
pa_assert(u); pa_assert(u);
pa_assert(u->source == s); pa_assert(u->source == s);
if (u->transport->set_microphone_gain == NULL) if (u->transport->set_microphone_volume == NULL)
return; return;
gain = (pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN) / PA_VOLUME_NORM; /* If we are in the AG role, we send a command to the head set to change
* the microphone gain. In the HS role, source and sink are swapped, so
if (gain > HSP_MAX_GAIN) * in this case we notify the AG that the speaker gain has changed */
gain = HSP_MAX_GAIN; volume = u->transport->set_microphone_volume(u->transport, pa_cvolume_max(&s->real_volume));
volume = (pa_volume_t) (gain * PA_VOLUME_NORM / HSP_MAX_GAIN);
/* increment volume by one to correct rounding errors */
if (volume < PA_VOLUME_NORM)
volume++;
pa_cvolume_set(&s->real_volume, u->decoder_sample_spec.channels, volume); pa_cvolume_set(&s->real_volume, u->decoder_sample_spec.channels, volume);
/* Set soft volume when in headset role */ /* Set soft volume when in headset role */
if (u->profile == PA_BLUETOOTH_PROFILE_HFP_AG || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG) if (u->profile == PA_BLUETOOTH_PROFILE_HFP_AG || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG)
pa_cvolume_set(&s->soft_volume, u->decoder_sample_spec.channels, volume); pa_cvolume_set(&s->soft_volume, u->decoder_sample_spec.channels, volume);
/* If we are in the AG role, we send a command to the head set to change
* the microphone gain. In the HS role, source and sink are swapped, so
* in this case we notify the AG that the speaker gain has changed */
u->transport->set_microphone_gain(u->transport, gain);
} }
/* Run from main thread */ /* Run from main thread */
@ -1154,7 +1140,6 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state,
/* Run from main thread */ /* Run from main thread */
static void sink_set_volume_cb(pa_sink *s) { static void sink_set_volume_cb(pa_sink *s) {
uint16_t gain;
pa_volume_t volume; pa_volume_t volume;
struct userdata *u; struct userdata *u;
@ -1166,30 +1151,19 @@ static void sink_set_volume_cb(pa_sink *s) {
pa_assert(u); pa_assert(u);
pa_assert(u->sink == s); pa_assert(u->sink == s);
if (u->transport->set_speaker_gain == NULL) if (u->transport->set_speaker_volume == NULL)
return; return;
gain = (pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN) / PA_VOLUME_NORM; /* If we are in the AG role, we send a command to the head set to change
* the speaker gain. In the HS role, source and sink are swapped, so
if (gain > HSP_MAX_GAIN) * in this case we notify the AG that the microphone gain has changed */
gain = HSP_MAX_GAIN; volume = u->transport->set_speaker_volume(u->transport, pa_cvolume_max(&s->real_volume));
volume = (pa_volume_t) (gain * PA_VOLUME_NORM / HSP_MAX_GAIN);
/* increment volume by one to correct rounding errors */
if (volume < PA_VOLUME_NORM)
volume++;
pa_cvolume_set(&s->real_volume, u->encoder_sample_spec.channels, volume); pa_cvolume_set(&s->real_volume, u->encoder_sample_spec.channels, volume);
/* Set soft volume when in headset role */ /* Set soft volume when in headset role */
if (u->profile == PA_BLUETOOTH_PROFILE_HFP_AG || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG) if (u->profile == PA_BLUETOOTH_PROFILE_HFP_AG || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG)
pa_cvolume_set(&s->soft_volume, u->encoder_sample_spec.channels, volume); pa_cvolume_set(&s->soft_volume, u->encoder_sample_spec.channels, volume);
/* If we are in the AG role, we send a command to the head set to change
* the speaker gain. In the HS role, source and sink are swapped, so
* in this case we notify the AG that the microphone gain has changed */
u->transport->set_speaker_gain(u->transport, gain);
} }
/* Run from main thread */ /* Run from main thread */
@ -2270,10 +2244,9 @@ static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa
return PA_HOOK_OK; return PA_HOOK_OK;
} }
static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) { static pa_hook_result_t transport_speaker_volume_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
pa_volume_t volume; pa_volume_t volume;
pa_cvolume v; pa_cvolume v;
uint16_t gain;
pa_assert(t); pa_assert(t);
pa_assert(u); pa_assert(u);
@ -2281,12 +2254,7 @@ static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluetooth_discovery
if (t != u->transport) if (t != u->transport)
return PA_HOOK_OK; return PA_HOOK_OK;
gain = t->speaker_gain; volume = t->speaker_volume;
volume = (pa_volume_t) (gain * PA_VOLUME_NORM / HSP_MAX_GAIN);
/* increment volume by one to correct rounding errors */
if (volume < PA_VOLUME_NORM)
volume++;
pa_cvolume_set(&v, u->encoder_sample_spec.channels, volume); pa_cvolume_set(&v, u->encoder_sample_spec.channels, volume);
if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS || t->profile == PA_BLUETOOTH_PROFILE_HFP_HF) if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS || t->profile == PA_BLUETOOTH_PROFILE_HFP_HF)
@ -2297,10 +2265,9 @@ static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluetooth_discovery
return PA_HOOK_OK; return PA_HOOK_OK;
} }
static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) { static pa_hook_result_t transport_microphone_volume_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
pa_volume_t volume; pa_volume_t volume;
pa_cvolume v; pa_cvolume v;
uint16_t gain;
pa_assert(t); pa_assert(t);
pa_assert(u); pa_assert(u);
@ -2308,12 +2275,7 @@ static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov
if (t != u->transport) if (t != u->transport)
return PA_HOOK_OK; return PA_HOOK_OK;
gain = t->microphone_gain; volume = t->microphone_volume;
volume = (pa_volume_t) (gain * PA_VOLUME_NORM / HSP_MAX_GAIN);
/* increment volume by one to correct rounding errors */
if (volume < PA_VOLUME_NORM)
volume++;
pa_cvolume_set(&v, u->decoder_sample_spec.channels, volume); pa_cvolume_set(&v, u->decoder_sample_spec.channels, volume);
@ -2620,11 +2582,11 @@ int pa__init(pa_module* m) {
pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED), pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED),
PA_HOOK_NORMAL, (pa_hook_cb_t) transport_state_changed_cb, u); PA_HOOK_NORMAL, (pa_hook_cb_t) transport_state_changed_cb, u);
u->transport_speaker_gain_changed_slot = u->transport_speaker_volume_changed_slot =
pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_speaker_gain_changed_cb, u); pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_VOLUME_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_speaker_volume_changed_cb, u);
u->transport_microphone_gain_changed_slot = u->transport_microphone_volume_changed_slot =
pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_microphone_gain_changed_cb, u); pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_VOLUME_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_microphone_volume_changed_cb, u);
if (add_card(u) < 0) if (add_card(u) < 0)
goto fail; goto fail;
@ -2695,11 +2657,11 @@ void pa__done(pa_module *m) {
if (u->transport_state_changed_slot) if (u->transport_state_changed_slot)
pa_hook_slot_free(u->transport_state_changed_slot); pa_hook_slot_free(u->transport_state_changed_slot);
if (u->transport_speaker_gain_changed_slot) if (u->transport_speaker_volume_changed_slot)
pa_hook_slot_free(u->transport_speaker_gain_changed_slot); pa_hook_slot_free(u->transport_speaker_volume_changed_slot);
if (u->transport_microphone_gain_changed_slot) if (u->transport_microphone_volume_changed_slot)
pa_hook_slot_free(u->transport_microphone_gain_changed_slot); pa_hook_slot_free(u->transport_microphone_volume_changed_slot);
if (u->encoder_buffer) if (u->encoder_buffer)
pa_xfree(u->encoder_buffer); pa_xfree(u->encoder_buffer);