backend-native: implement volume control

Parse the gain changed AT commands from the headset and fire 2 new
hooks as a result. The device will connect to those hooks and change the
source/sink volumes.

When the source/sink volume changes, set the gain on the microphone or
speaker respectively. Make sure we do nothing if the transport can not
handle the gain changes.
This commit is contained in:
Wim Taymans 2014-10-24 09:56:52 +02:00 committed by Arun Raghavan
parent 7d4a497b3d
commit 34a5c754a9
3 changed files with 203 additions and 1 deletions

View file

@ -114,6 +114,7 @@ static int bluez5_sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_
src_addr = d->adapter->address; src_addr = d->adapter->address;
dst_addr = d->address; dst_addr = d->address;
/* don't use ba2str to avoid -lbluetooth */
for (i = 5; i >= 0; i--, src_addr += 3) for (i = 5; i >= 0; i--, src_addr += 3)
src.b[i] = strtol(src_addr, NULL, 16); src.b[i] = strtol(src_addr, NULL, 16);
for (i = 5; i >= 0; i--, dst_addr += 3) for (i = 5; i >= 0; i--, dst_addr += 3)
@ -230,13 +231,25 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
if (events & PA_IO_EVENT_INPUT) { if (events & PA_IO_EVENT_INPUT) {
char buf[512]; char buf[512];
ssize_t len; ssize_t len;
int gain;
len = read(fd, buf, 511); len = read(fd, buf, 511);
buf[len] = 0; buf[len] = 0;
pa_log_debug("RFCOMM << %s", buf); pa_log_debug("RFCOMM << %s", buf);
if (sscanf(buf, "AT+VGS=%d", &gain) == 1) {
t->speaker_gain = gain;
pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED), t);
} else if (sscanf(buf, "AT+VGM=%d", &gain) == 1) {
t->microphone_gain = gain;
pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), t);
}
pa_log_debug("RFCOMM >> OK"); pa_log_debug("RFCOMM >> OK");
len = write (fd, "\r\nOK\r\n", 5);
len = write(fd, "\r\nOK\r\n", 5);
/* we ignore any errors, it's not critical and real errors should /* we ignore any errors, it's not critical and real errors should
* be caught with the HANGUP and ERROR events handled above */ * be caught with the HANGUP and ERROR events handled above */
if (len < 0) if (len < 0)
@ -262,6 +275,44 @@ static void transport_destroy(pa_bluetooth_transport *t) {
pa_xfree(trfc); pa_xfree(trfc);
} }
static void set_speaker_gain(pa_bluetooth_transport *t, uint16_t gain) {
struct transport_rfcomm *trfc = t->userdata;
char buf[512];
ssize_t len, written;
if (t->speaker_gain == gain)
return;
t->speaker_gain = gain;
len = sprintf(buf, "+VGS=%d\r\n", gain);
pa_log_debug("RFCOMM >> +VGS=%d", gain);
written = write(trfc->rfcomm_fd, buf, len);
if (written != len)
pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
}
static void set_microphone_gain(pa_bluetooth_transport *t, uint16_t gain) {
struct transport_rfcomm *trfc = t->userdata;
char buf[512];
ssize_t len, written;
if (t->microphone_gain == gain)
return;
t->microphone_gain = gain;
len = sprintf(buf, "+VGM=%d\r\n", gain);
pa_log_debug("RFCOMM >> +VGM=%d", gain);
written = write (trfc->rfcomm_fd, buf, len);
if (written != len)
pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
}
static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, void *userdata) { static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, void *userdata) {
pa_bluetooth_backend *b = userdata; pa_bluetooth_backend *b = userdata;
pa_bluetooth_device *d; pa_bluetooth_device *d;
@ -308,6 +359,8 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
t->acquire = bluez5_sco_acquire_cb; t->acquire = bluez5_sco_acquire_cb;
t->release = bluez5_sco_release_cb; t->release = bluez5_sco_release_cb;
t->destroy = transport_destroy; t->destroy = transport_destroy;
t->set_speaker_gain = set_speaker_gain;
t->set_microphone_gain = set_microphone_gain;
trfc = pa_xnew0(struct transport_rfcomm, 1); trfc = pa_xnew0(struct transport_rfcomm, 1);
trfc->rfcomm_fd = fd; trfc->rfcomm_fd = fd;

View file

@ -40,6 +40,8 @@ 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_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_SPEAKER_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_MAX PA_BLUETOOTH_HOOK_MAX
} pa_bluetooth_hook_t; } pa_bluetooth_hook_t;
@ -61,6 +63,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 void (*pa_bluetooth_transport_set_microphone_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
struct pa_bluetooth_transport { struct pa_bluetooth_transport {
pa_bluetooth_device *device; pa_bluetooth_device *device;
@ -73,11 +77,16 @@ struct pa_bluetooth_transport {
uint8_t *config; uint8_t *config;
size_t config_size; size_t config_size;
uint16_t microphone_gain;
uint16_t speaker_gain;
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_microphone_gain_cb set_microphone_gain;
void *userdata; void *userdata;
}; };

View file

@ -66,6 +66,7 @@ PA_MODULE_USAGE("path=<device object path>");
#define BITPOOL_DEC_LIMIT 32 #define BITPOOL_DEC_LIMIT 32
#define BITPOOL_DEC_STEP 5 #define BITPOOL_DEC_STEP 5
#define HSP_MAX_GAIN 15
static const char* const valid_modargs[] = { static const char* const valid_modargs[] = {
"path", "path",
@ -103,6 +104,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_microphone_gain_changed_slot;
pa_bluetooth_discovery *discovery; pa_bluetooth_discovery *discovery;
pa_bluetooth_device *device; pa_bluetooth_device *device;
@ -902,6 +905,40 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
return (r < 0 || !failed) ? r : -1; return (r < 0 || !failed) ? r : -1;
} }
/* Run from main thread */
static void source_set_volume_cb(pa_source *s) {
uint16_t gain;
pa_volume_t volume;
struct userdata *u;
pa_assert(s);
pa_assert(s->core);
u = s->userdata;
pa_assert(u);
pa_assert(u->source == s);
pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT);
if (u->transport->set_microphone_gain == NULL)
return;
gain = (pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN) / PA_VOLUME_NORM;
if (gain > HSP_MAX_GAIN)
gain = HSP_MAX_GAIN;
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->sample_spec.channels, volume);
u->transport->set_microphone_gain(u->transport, gain);
}
/* Run from main thread */ /* Run from main thread */
static int add_source(struct userdata *u) { static int add_source(struct userdata *u) {
pa_source_new_data data; pa_source_new_data data;
@ -944,6 +981,10 @@ static int add_source(struct userdata *u) {
u->source->userdata = u; u->source->userdata = u;
u->source->parent.process_msg = source_process_msg; u->source->parent.process_msg = source_process_msg;
if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT) {
pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
u->source->n_volume_steps = 16;
}
return 0; return 0;
} }
@ -1021,6 +1062,40 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
return (r < 0 || !failed) ? r : -1; return (r < 0 || !failed) ? r : -1;
} }
/* Run from main thread */
static void sink_set_volume_cb(pa_sink *s) {
uint16_t gain;
pa_volume_t volume;
struct userdata *u;
pa_assert(s);
pa_assert(s->core);
u = s->userdata;
pa_assert(u);
pa_assert(u->sink == s);
pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT);
if (u->transport->set_speaker_gain == NULL)
return;
gain = (pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN) / PA_VOLUME_NORM;
if (gain > HSP_MAX_GAIN)
gain = HSP_MAX_GAIN;
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->sample_spec.channels, volume);
u->transport->set_speaker_gain(u->transport, gain);
}
/* Run from main thread */ /* Run from main thread */
static int add_sink(struct userdata *u) { static int add_sink(struct userdata *u) {
pa_sink_new_data data; pa_sink_new_data data;
@ -1064,6 +1139,10 @@ static int add_sink(struct userdata *u) {
u->sink->userdata = u; u->sink->userdata = u;
u->sink->parent.process_msg = sink_process_msg; u->sink->parent.process_msg = sink_process_msg;
if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT) {
pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
u->sink->n_volume_steps = 16;
}
return 0; return 0;
} }
@ -1975,6 +2054,54 @@ 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) {
pa_volume_t volume;
pa_cvolume v;
uint16_t gain;
pa_assert(t);
pa_assert(u);
if (t != u->transport)
return PA_HOOK_OK;
gain = t->speaker_gain;
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->sample_spec.channels, volume);
pa_sink_volume_changed(u->sink, &v);
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) {
pa_volume_t volume;
pa_cvolume v;
uint16_t gain;
pa_assert(t);
pa_assert(u);
if (t != u->transport)
return PA_HOOK_OK;
gain = t->microphone_gain;
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->sample_spec.channels, volume);
pa_source_volume_changed(u->source, &v);
return PA_HOOK_OK;
}
/* Run from main thread context */ /* Run from main thread context */
static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct bluetooth_msg *m = BLUETOOTH_MSG(obj); struct bluetooth_msg *m = BLUETOOTH_MSG(obj);
@ -2039,6 +2166,13 @@ 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 =
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);
u->transport_microphone_gain_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);
if (add_card(u) < 0) if (add_card(u) < 0)
goto fail; goto fail;
@ -2091,6 +2225,12 @@ 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)
pa_hook_slot_free(u->transport_speaker_gain_changed_slot);
if (u->transport_microphone_gain_changed_slot)
pa_hook_slot_free(u->transport_microphone_gain_changed_slot);
if (u->sbc_info.buffer) if (u->sbc_info.buffer)
pa_xfree(u->sbc_info.buffer); pa_xfree(u->sbc_info.buffer);