summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWim Taymans <wim.taymans@gmail.com>2014-10-24 09:56:52 +0200
committerArun Raghavan <arun@accosted.net>2014-10-25 12:43:30 +0530
commitf9b8d17e689991b784f673933a1530ea0c5834e0 (patch)
tree09330490204a0410b52958bc5081448102e18832
parenta11e03f10d47f1d2137ccabc78d49b80a8bdc1e4 (diff)
backend-native: implement volume controlhsp-review
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.
-rw-r--r--src/modules/bluetooth/backend-native.c55
-rw-r--r--src/modules/bluetooth/bluez5-util.h9
-rw-r--r--src/modules/bluetooth/module-bluez5-device.c140
3 files changed, 203 insertions, 1 deletions
diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c
index b363652e3..d937e3cd3 100644
--- a/src/modules/bluetooth/backend-native.c
+++ b/src/modules/bluetooth/backend-native.c
@@ -114,6 +114,7 @@ static int bluez5_sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_
src_addr = d->adapter->address;
dst_addr = d->address;
+ /* don't use ba2str to avoid -lbluetooth */
for (i = 5; i >= 0; i--, src_addr += 3)
src.b[i] = strtol(src_addr, NULL, 16);
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) {
char buf[512];
ssize_t len;
+ int gain;
len = read (fd, buf, 511);
buf[len] = 0;
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");
- 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
* be caught with the HANGUP and ERROR events handled above */
if (len < 0)
@@ -262,6 +275,44 @@ static void transport_destroy(pa_bluetooth_transport *t) {
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) {
pa_bluetooth_backend *b = userdata;
pa_bluetooth_device *d;
@@ -308,6 +359,8 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
t->acquire = bluez5_sco_acquire_cb;
t->release = bluez5_sco_release_cb;
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->rfcomm_fd = fd;
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index 1a8a364da..3ef0ac837 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -40,6 +40,8 @@ typedef struct pa_bluetooth_backend pa_bluetooth_backend;
typedef enum pa_bluetooth_hook {
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_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_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 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_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 {
pa_bluetooth_device *device;
@@ -73,11 +77,16 @@ struct pa_bluetooth_transport {
uint8_t *config;
size_t config_size;
+ uint16_t microphone_gain;
+ uint16_t speaker_gain;
+
pa_bluetooth_transport_state_t state;
pa_bluetooth_transport_acquire_cb acquire;
pa_bluetooth_transport_release_cb release;
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;
};
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 15731d147..d07716169 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -66,6 +66,7 @@ PA_MODULE_USAGE("path=<device object path>");
#define BITPOOL_DEC_LIMIT 32
#define BITPOOL_DEC_STEP 5
+#define HSP_MAX_GAIN 15
static const char* const valid_modargs[] = {
"path",
@@ -103,6 +104,8 @@ struct userdata {
pa_hook_slot *device_connection_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_device *device;
@@ -903,6 +906,40 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
}
/* 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 */
static int add_source(struct userdata *u) {
pa_source_new_data data;
@@ -944,6 +981,10 @@ static int add_source(struct userdata *u) {
u->source->userdata = u;
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;
}
@@ -1022,6 +1063,40 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
}
/* 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 */
static int add_sink(struct userdata *u) {
pa_sink_new_data data;
@@ -1064,6 +1139,10 @@ static int add_sink(struct userdata *u) {
u->sink->userdata = u;
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;
}
@@ -1975,6 +2054,54 @@ static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa
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 */
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);
@@ -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_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)
goto fail;
@@ -2091,6 +2225,12 @@ void pa__done(pa_module *m) {
if (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)
pa_xfree(u->sbc_info.buffer);