summaryrefslogtreecommitdiffstats
path: root/src/modules/bluetooth/module-bluetooth-device.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/bluetooth/module-bluetooth-device.c')
-rw-r--r--src/modules/bluetooth/module-bluetooth-device.c246
1 files changed, 131 insertions, 115 deletions
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index dc09ffca..55610546 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -58,6 +58,9 @@
#define MAX_BITPOOL 64
#define MIN_BITPOOL 2U
+#define BITPOOL_DEC_LIMIT 32
+#define BITPOOL_DEC_STEP 5
+
PA_MODULE_AUTHOR("Joao Paulo Rechi Vita");
PA_MODULE_DESCRIPTION("Bluetooth audio sink and source");
PA_MODULE_VERSION(PACKAGE_VERSION);
@@ -117,6 +120,8 @@ struct a2dp_info {
size_t buffer_size; /* Size of the buffer */
uint16_t seq_num; /* Cumulative packet sequence */
+ uint8_t min_bitpool;
+ uint8_t max_bitpool;
};
struct hsp_info {
@@ -574,7 +579,7 @@ static int setup_a2dp(struct userdata *u) {
}
/* Run from main thread */
-static void setup_sbc(struct a2dp_info *a2dp) {
+static void setup_sbc(struct a2dp_info *a2dp, enum profile p) {
sbc_capabilities_t *active_capabilities;
pa_assert(a2dp);
@@ -660,7 +665,11 @@ static void setup_sbc(struct a2dp_info *a2dp) {
pa_assert_not_reached();
}
- a2dp->sbc.bitpool = active_capabilities->max_bitpool;
+ a2dp->min_bitpool = active_capabilities->min_bitpool;
+ a2dp->max_bitpool = active_capabilities->max_bitpool;
+
+ /* Set minimum bitpool for source to get the maximum possible block_size */
+ a2dp->sbc.bitpool = p == PROFILE_A2DP ? a2dp->max_bitpool : a2dp->min_bitpool;
a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
}
@@ -728,7 +737,7 @@ static int set_conf(struct userdata *u) {
/* setup SBC encoder now we agree on parameters */
if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) {
- setup_sbc(&u->a2dp);
+ setup_sbc(&u->a2dp, u->profile);
u->block_size =
((u->link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
@@ -743,6 +752,39 @@ static int set_conf(struct userdata *u) {
return 0;
}
+/* from IO thread */
+static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool)
+{
+ struct a2dp_info *a2dp;
+
+ pa_assert(u);
+
+ a2dp = &u->a2dp;
+
+ if (a2dp->sbc.bitpool == bitpool)
+ return;
+
+ if (bitpool > a2dp->max_bitpool)
+ bitpool = a2dp->max_bitpool;
+ else if (bitpool < a2dp->min_bitpool)
+ bitpool = a2dp->min_bitpool;
+
+ a2dp->sbc.bitpool = bitpool;
+
+ a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
+ a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
+
+ pa_log_debug("Bitpool has changed to %u", a2dp->sbc.bitpool);
+
+ u->block_size =
+ (u->link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+ / a2dp->frame_length * a2dp->codesize;
+
+ pa_sink_set_max_request_within_thread(u->sink, u->block_size);
+ pa_sink_set_fixed_latency_within_thread(u->sink,
+ FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->block_size, &u->sample_spec));
+}
+
/* from IO thread, except in SCO over PCM */
static int setup_stream(struct userdata *u) {
@@ -758,6 +800,9 @@ static int setup_stream(struct userdata *u) {
pa_log_debug("Stream properly set up, we're ready to roll!");
+ if (u->profile == PROFILE_A2DP)
+ a2dp_set_bitpool(u, u->a2dp.max_bitpool);
+
u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
pollfd->fd = u->stream_fd;
@@ -857,8 +902,7 @@ static int stop_stream_fd(struct userdata *u) {
return r;
}
-static void bt_transport_release(struct userdata *u)
-{
+static void bt_transport_release(struct userdata *u) {
const char *accesstype = "rw";
const pa_bluetooth_transport *t;
@@ -891,8 +935,7 @@ static void bt_transport_release(struct userdata *u)
}
}
-static int bt_transport_acquire(struct userdata *u, pa_bool_t start)
-{
+static int bt_transport_acquire(struct userdata *u, pa_bool_t start) {
const char *accesstype = "rw";
const pa_bluetooth_transport *t;
@@ -912,7 +955,8 @@ static int bt_transport_acquire(struct userdata *u, pa_bool_t start)
return -1;
}
- u->stream_fd = pa_bluetooth_transport_acquire(t, accesstype);
+ /* FIXME: Handle in/out MTU properly when unix socket is not longer supported */
+ u->stream_fd = pa_bluetooth_transport_acquire(t, accesstype, NULL, &u->link_mtu);
if (u->stream_fd < 0)
return -1;
@@ -1351,7 +1395,7 @@ static int a2dp_process_render(struct userdata *u) {
break;
pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
- ret = -1;
+ ret = -1;
break;
}
@@ -1443,7 +1487,7 @@ static int a2dp_process_push(struct userdata *u) {
d = pa_memblock_acquire(memchunk.memblock);
to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock);
- while (PA_LIKELY(to_decode > 0 && to_write > 0)) {
+ while (PA_LIKELY(to_decode > 0)) {
size_t written;
ssize_t decoded;
@@ -1462,10 +1506,12 @@ static int a2dp_process_push(struct userdata *u) {
/* pa_log_debug("SBC: decoded: %lu; written: %lu", (unsigned long) decoded, (unsigned long) written); */
/* pa_log_debug("SBC: frame_length: %lu; codesize: %lu", (unsigned long) a2dp->frame_length, (unsigned long) a2dp->codesize); */
+ /* Reset frame length, it can be changed due to bitpool change */
+ a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
+
pa_assert_fp((size_t) decoded <= to_decode);
pa_assert_fp((size_t) decoded == a2dp->frame_length);
- pa_assert_fp((size_t) written <= to_write);
pa_assert_fp((size_t) written == a2dp->codesize);
p = (const uint8_t*) p + decoded;
@@ -1477,6 +1523,8 @@ static int a2dp_process_push(struct userdata *u) {
frame_count++;
}
+ memchunk.length -= to_write;
+
pa_memblock_release(memchunk.memblock);
pa_source_post(u->source, &memchunk);
@@ -1490,6 +1538,27 @@ static int a2dp_process_push(struct userdata *u) {
return ret;
}
+static void a2dp_reduce_bitpool(struct userdata *u)
+{
+ struct a2dp_info *a2dp;
+ uint8_t bitpool;
+
+ pa_assert(u);
+
+ a2dp = &u->a2dp;
+
+ /* Check if bitpool is already at its limit */
+ if (a2dp->sbc.bitpool <= BITPOOL_DEC_LIMIT)
+ return;
+
+ bitpool = a2dp->sbc.bitpool - BITPOOL_DEC_STEP;
+
+ if (bitpool < BITPOOL_DEC_LIMIT)
+ bitpool = BITPOOL_DEC_LIMIT;
+
+ a2dp_set_bitpool(u, bitpool);
+}
+
static void thread_func(void *userdata) {
struct userdata *u = userdata;
unsigned do_write = 0;
@@ -1581,6 +1650,9 @@ static void thread_func(void *userdata) {
pa_sink_render_full(u->sink, skip_bytes, &tmp);
pa_memblock_unref(tmp.memblock);
u->write_index += skip_bytes;
+
+ if (u->profile == PROFILE_A2DP)
+ a2dp_reduce_bitpool(u);
}
}
@@ -1679,8 +1751,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
dbus_message_get_path(m),
dbus_message_get_member(m));
- if (!dbus_message_has_path(m, u->path))
- goto fail;
+ if (!dbus_message_has_path(m, u->path) && !dbus_message_has_path(m, u->transport))
+ goto fail;
if (dbus_message_is_signal(m, "org.bluez.Headset", "SpeakerGainChanged") ||
dbus_message_is_signal(m, "org.bluez.Headset", "MicrophoneGainChanged")) {
@@ -1705,6 +1777,28 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
pa_source_volume_changed(u->source, &v);
}
}
+ } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) {
+ DBusMessageIter arg_i;
+ pa_bluetooth_transport *t;
+ pa_bool_t nrec;
+
+ t = (pa_bluetooth_transport *) pa_bluetooth_discovery_get_transport(u->discovery, u->transport);
+ pa_assert(t);
+
+ if (!dbus_message_iter_init(m, &arg_i)) {
+ pa_log("Failed to parse PropertyChanged: %s", err.message);
+ goto fail;
+ }
+
+ nrec = t->nrec;
+
+ if (pa_bluetooth_transport_parse_property(t, &arg_i) < 0)
+ goto fail;
+
+ if (nrec != t->nrec) {
+ pa_log_debug("dbus: property 'NREC' changed to value '%s'", t->nrec ? "True" : "False");
+ pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0");
+ }
}
fail:
@@ -1946,6 +2040,7 @@ static int add_source(struct userdata *u) {
pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP_SOURCE ? "a2dp_source" : "hsp");
if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW))
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
+
data.card = u->card;
data.name = get_name("source", u->modargs, u->address, &b);
data.namereg_fail = b;
@@ -1972,8 +2067,15 @@ static int add_source(struct userdata *u) {
pa_bytes_to_usec(u->block_size, &u->sample_spec));
}
- if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW)
- pa_proplist_sets(u->source->proplist, "bluetooth.nrec", (u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC) ? "1" : "0");
+ if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) {
+ if (u->transport) {
+ const pa_bluetooth_transport *t;
+ t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport);
+ pa_assert(t);
+ pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0");
+ } else
+ pa_proplist_sets(u->source->proplist, "bluetooth.nrec", (u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC) ? "1" : "0");
+ }
if (u->profile == PROFILE_HSP) {
u->source->set_volume = source_set_volume_cb;
@@ -2007,8 +2109,7 @@ static void shutdown_bt(struct userdata *u) {
}
}
-static int bt_transport_config_a2dp(struct userdata *u)
-{
+static int bt_transport_config_a2dp(struct userdata *u) {
const pa_bluetooth_transport *t;
struct a2dp_info *a2dp = &u->a2dp;
sbc_capabilities_raw_t *config;
@@ -2097,7 +2198,11 @@ static int bt_transport_config_a2dp(struct userdata *u)
pa_assert_not_reached();
}
- a2dp->sbc.bitpool = config->max_bitpool;
+ a2dp->min_bitpool = config->min_bitpool;
+ a2dp->max_bitpool = config->max_bitpool;
+
+ /* Set minimum bitpool for source to get the maximum possible block_size */
+ a2dp->sbc.bitpool = u->profile == PROFILE_A2DP ? a2dp->max_bitpool : a2dp->min_bitpool;
a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
@@ -2112,8 +2217,7 @@ static int bt_transport_config_a2dp(struct userdata *u)
return 0;
}
-static int bt_transport_config(struct userdata *u)
-{
+static int bt_transport_config(struct userdata *u) {
if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) {
u->block_size = u->link_mtu;
return 0;
@@ -2122,101 +2226,12 @@ static int bt_transport_config(struct userdata *u)
return bt_transport_config_a2dp(u);
}
-static int parse_transport_property(struct userdata *u, DBusMessageIter *i)
-{
- const char *key;
- DBusMessageIter variant_i;
-
- pa_assert(u);
- pa_assert(i);
-
- if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
- pa_log("Property name not a string.");
- return -1;
- }
-
- dbus_message_iter_get_basic(i, &key);
-
- if (!dbus_message_iter_next(i)) {
- pa_log("Property value missing");
- return -1;
- }
-
- if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
- pa_log("Property value not a variant.");
- return -1;
- }
-
- dbus_message_iter_recurse(i, &variant_i);
-
- switch (dbus_message_iter_get_arg_type(&variant_i)) {
-
- case DBUS_TYPE_UINT16: {
-
- uint16_t value;
- dbus_message_iter_get_basic(&variant_i, &value);
-
- if (pa_streq(key, "OMTU"))
- u->link_mtu = value;
-
- break;
- }
-
- }
-
- return 0;
-}
-
/* Run from main thread */
-static int bt_transport_open(struct userdata *u)
-{
- DBusMessage *m, *r;
- DBusMessageIter arg_i, element_i;
- DBusError err;
-
+static int bt_transport_open(struct userdata *u) {
if (bt_transport_acquire(u, FALSE) < 0)
return -1;
- dbus_error_init(&err);
-
- pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->transport, "org.bluez.MediaTransport", "GetProperties"));
- r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &err);
-
- if (dbus_error_is_set(&err) || !r) {
- pa_log("Failed to get transport properties: %s", err.message);
- goto fail;
- }
-
- if (!dbus_message_iter_init(r, &arg_i)) {
- pa_log("GetProperties reply has no arguments.");
- goto fail;
- }
-
- if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
- pa_log("GetProperties argument is not an array.");
- goto fail;
- }
-
- dbus_message_iter_recurse(&arg_i, &element_i);
- while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) {
-
- if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
- DBusMessageIter dict_i;
-
- dbus_message_iter_recurse(&element_i, &dict_i);
-
- parse_transport_property(u, &dict_i);
- }
-
- if (!dbus_message_iter_next(&element_i))
- break;
- }
-
return bt_transport_config(u);
-
-fail:
- dbus_message_unref(r);
- return -1;
}
/* Run from main thread */
@@ -2696,7 +2711,7 @@ int pa__init(pa_module* m) {
struct userdata *u;
const char *address, *path;
DBusError err;
- char *mike, *speaker;
+ char *mike, *speaker, *transport;
const pa_bluetooth_device *device;
pa_assert(m);
@@ -2775,15 +2790,18 @@ int pa__init(pa_module* m) {
speaker = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='SpeakerGainChanged',path='%s'", u->path);
mike = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='MicrophoneGainChanged',path='%s'", u->path);
+ transport = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'");
if (pa_dbus_add_matches(
pa_dbus_connection_get(u->connection), &err,
speaker,
mike,
+ transport,
NULL) < 0) {
pa_xfree(speaker);
pa_xfree(mike);
+ pa_xfree(transport);
pa_log("Failed to add D-Bus matches: %s", err.message);
goto fail;
@@ -2791,6 +2809,7 @@ int pa__init(pa_module* m) {
pa_xfree(speaker);
pa_xfree(mike);
+ pa_xfree(transport);
/* Connect to the BT service */
init_bt(u);
@@ -2855,10 +2874,7 @@ void pa__done(pa_module *m) {
speaker = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='SpeakerGainChanged',path='%s'", u->path);
mike = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='MicrophoneGainChanged',path='%s'", u->path);
- pa_dbus_remove_matches(pa_dbus_connection_get(u->connection),
- speaker,
- mike,
- NULL);
+ pa_dbus_remove_matches(pa_dbus_connection_get(u->connection), speaker, mike, NULL);
pa_xfree(speaker);
pa_xfree(mike);