diff options
Diffstat (limited to 'src/modules/bluetooth')
-rw-r--r-- | src/modules/bluetooth/bluetooth-util.c | 666 | ||||
-rw-r--r-- | src/modules/bluetooth/bluetooth-util.h | 26 | ||||
-rw-r--r-- | src/modules/bluetooth/ipc.h | 28 | ||||
-rw-r--r-- | src/modules/bluetooth/module-bluetooth-device.c | 461 |
4 files changed, 1109 insertions, 72 deletions
diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c index c3b08ea3..bce27663 100644 --- a/src/modules/bluetooth/bluetooth-util.c +++ b/src/modules/bluetooth/bluetooth-util.c @@ -28,6 +28,38 @@ #include <pulsecore/dbus-shared.h> #include "bluetooth-util.h" +#include "ipc.h" + +#define HFP_AG_ENDPOINT "/MediaEndpoint/HFPAG" +#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource" +#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink" + +#define ENDPOINT_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>" \ + " <interface name=\"org.bluez.MediaEndpoint\">" \ + " <method name=\"SetConfiguration\">" \ + " <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \ + " <arg name=\"configuration\" direction=\"in\" type=\"ay\"/>" \ + " </method>" \ + " <method name=\"SelectConfiguration\">" \ + " <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>" \ + " <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>" \ + " </method>" \ + " <method name=\"ClearConfiguration\">" \ + " </method>" \ + " <method name=\"Release\">" \ + " </method>" \ + " </interface>" \ + " <interface name=\"org.freedesktop.DBus.Introspectable\">" \ + " <method name=\"Introspect\">" \ + " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \ + " </method>" \ + " </interface>" \ + "</node>" + +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2U struct pa_bluetooth_discovery { PA_REFCNT_DECLARE; @@ -86,6 +118,7 @@ static pa_bluetooth_device* device_new(const char *path) { d->name = NULL; d->path = pa_xstrdup(path); + d->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); d->paired = -1; d->alias = NULL; d->device_connected = -1; @@ -103,11 +136,25 @@ static pa_bluetooth_device* device_new(const char *path) { return d; } +static void transport_free(pa_bluetooth_transport *t) { + pa_assert(t); + + pa_xfree(t->path); + pa_xfree(t->config); + pa_xfree(t); +} + static void device_free(pa_bluetooth_device *d) { pa_bluetooth_uuid *u; + pa_bluetooth_transport *t; pa_assert(d); + while ((t = pa_hashmap_steal_first(d->transports))) + transport_free(t); + + pa_hashmap_free(d->transports, NULL, NULL); + while ((u = d->uuids)) { PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u); uuid_free(u); @@ -331,6 +378,29 @@ static void remove_all_devices(pa_bluetooth_discovery *y) { } } +static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path) { + DBusMessage *m; + pa_bluetooth_device *d; + + pa_assert(y); + pa_assert(path); + + d = pa_hashmap_get(y->devices, path); + if (d) + return d; + + d = device_new(path); + + pa_hashmap_put(y->devices, d->path, d); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties")); + send_and_add_to_pending(y, d, m, get_properties_reply); + + /* Before we read the other properties (Audio, AudioSink, AudioSource, + * Headset) we wait that the UUID is read */ + return d; +} + static void get_properties_reply(DBusPendingCall *pending, void *userdata) { DBusMessage *r; DBusMessageIter arg_i, element_i; @@ -415,6 +485,7 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) { } else if (dbus_message_has_interface(p->message, "org.bluez.HandsfreeGateway")) { if (parse_audio_property(y, &d->hfgw_state, &arg_i) < 0) goto finish; + } } @@ -448,25 +519,36 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bl return p; } -static void found_device(pa_bluetooth_discovery *y, const char* path) { - DBusMessage *m; - pa_bluetooth_device *d; +static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) { + DBusError e; + DBusMessage *r; + pa_dbus_pending *p; + pa_bluetooth_discovery *y; - pa_assert(y); - pa_assert(path); + pa_assert(pending); - if (pa_hashmap_get(y->devices, path)) - return; + dbus_error_init(&e); - d = device_new(path); + pa_assert_se(p = userdata); + pa_assert_se(y = p->context_data); + pa_assert_se(r = dbus_pending_call_steal_reply(pending)); - pa_hashmap_put(y->devices, d->path, d); + if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) { + pa_log_debug("Bluetooth daemon is apparently not available."); + remove_all_devices(y); + goto finish; + } - pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties")); - send_and_add_to_pending(y, d, m, get_properties_reply); + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + pa_log("Error from RegisterEndpoint reply: %s", dbus_message_get_error_name(r)); + goto finish; + } - /* Before we read the other properties (Audio, AudioSink, AudioSource, - * Headset) we wait that the UUID is read */ +finish: + dbus_message_unref(r); + + PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p); + pa_dbus_pending_free(p); } static void list_devices_reply(DBusPendingCall *pending, void *userdata) { @@ -516,11 +598,126 @@ finish: pa_dbus_pending_free(p); } +static void append_variant(DBusMessageIter *iter, int type, const void *val) +{ + DBusMessageIter value; + char sig[2] = { type, '\0' }; + + dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, sig, &value); + + dbus_message_iter_append_basic(&value, type, val); + + dbus_message_iter_close_container(iter, &value); +} + +static void append_array_variant(DBusMessageIter *iter, int type, const void *val, int n_elements) +{ + DBusMessageIter variant, array; + char type_sig[2] = { type, '\0' }; + char array_sig[3] = { DBUS_TYPE_ARRAY, type, '\0' }; + + dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, array_sig, &variant); + + dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, type_sig, &array); + + dbus_message_iter_append_fixed_array(&array, type, val, n_elements); + + dbus_message_iter_close_container(&variant, &array); + + dbus_message_iter_close_container(iter, &variant); +} + +static void dict_append_entry(DBusMessageIter *dict, const char *key, int type, void *val) +{ + DBusMessageIter entry; + + if (type == DBUS_TYPE_STRING) { + const char *str = *((const char **) val); + if (str == NULL) + return; + } + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + append_variant(&entry, type, val); + + dbus_message_iter_close_container(dict, &entry); +} + +static void dict_append_array(DBusMessageIter *dict, const char *key, int type, void *val, int n_elements) +{ + DBusMessageIter entry; + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + append_array_variant(&entry, type, val, n_elements); + + dbus_message_iter_close_container(dict, &entry); +} + +static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) +{ + DBusMessage *m; + DBusMessageIter i, d; + uint8_t codec = 0; + + pa_log_debug("Registering %s on adapter %s.", endpoint, path); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Media", "RegisterEndpoint")); + + dbus_message_iter_init_append(m, &i); + + dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint); + + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &d); + + dict_append_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid); + + dict_append_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec); + + if (pa_streq(uuid, HFP_AG_UUID)) { + uint8_t *caps = NULL; + dict_append_array(&d, "Capabilities", DBUS_TYPE_BYTE, &caps, 0); + } else { + sbc_capabilities_raw_t capabilities; + uint8_t *caps = (uint8_t *) &capabilities; + + capabilities.channel_mode = BT_A2DP_CHANNEL_MODE_MONO | BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL | + BT_A2DP_CHANNEL_MODE_STEREO | BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + capabilities.frequency = BT_SBC_SAMPLING_FREQ_16000 | BT_SBC_SAMPLING_FREQ_32000 | + BT_SBC_SAMPLING_FREQ_44100 | BT_SBC_SAMPLING_FREQ_48000; + capabilities.allocation_method = BT_A2DP_ALLOCATION_SNR | BT_A2DP_ALLOCATION_LOUDNESS; + capabilities.subbands = BT_A2DP_SUBBANDS_4 | BT_A2DP_SUBBANDS_8; + capabilities.block_length = BT_A2DP_BLOCK_LENGTH_4 | BT_A2DP_BLOCK_LENGTH_8 | + BT_A2DP_BLOCK_LENGTH_12 | BT_A2DP_BLOCK_LENGTH_16; + capabilities.min_bitpool = MIN_BITPOOL; + capabilities.max_bitpool = MAX_BITPOOL; + + dict_append_array(&d, "Capabilities", DBUS_TYPE_BYTE, &caps, sizeof(capabilities)); + } + + dbus_message_iter_close_container(&i, &d); + + send_and_add_to_pending(y, NULL, m, register_endpoint_reply); +} + static void found_adapter(pa_bluetooth_discovery *y, const char *path) { DBusMessage *m; pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "ListDevices")); send_and_add_to_pending(y, NULL, m, list_devices_reply); + +#ifdef DBUS_TYPE_UNIX_FD + register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID); + register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID); + register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID); +#endif } static void list_adapters_reply(DBusPendingCall *pending, void *userdata) { @@ -769,6 +966,87 @@ const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_disco return NULL; } +const pa_bluetooth_transport* pa_bluetooth_discovery_get_transport(pa_bluetooth_discovery *y, const char *path) { + pa_bluetooth_device *d; + pa_bluetooth_transport *t; + void *state = NULL; + + pa_assert(y); + pa_assert(PA_REFCNT_VALUE(y) > 0); + pa_assert(path); + + while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) + if ((t = pa_hashmap_get(d->transports, path))) + return t; + + return NULL; +} + +const pa_bluetooth_transport* pa_bluetooth_device_get_transport(const pa_bluetooth_device *d, enum profile profile) { + pa_bluetooth_transport *t; + void *state = NULL; + + pa_assert(d); + + while ((t = pa_hashmap_iterate(d->transports, &state, NULL))) + if (t->profile == profile) + return t; + + return NULL; +} + +int pa_bluetooth_transport_acquire(const pa_bluetooth_transport *t, const char *accesstype) { + DBusMessage *m, *r; + DBusError err; + int ret; + + pa_assert(t); + pa_assert(t->y); + + dbus_error_init(&err); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", t->path, "org.bluez.MediaTransport", "Acquire")); + pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID)); + r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->y->connection), m, -1, &err); + + if (dbus_error_is_set(&err) || !r) { + pa_log("Failed to acquire transport fd: %s", err.message); + dbus_error_free(&err); + return -1; + } + + if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_INVALID)) { + pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err.message); + ret = -1; + dbus_error_free(&err); + goto fail; + } + +fail: + dbus_message_unref(r); + return ret; +} + +void pa_bluetooth_transport_release(const pa_bluetooth_transport *t, const char *accesstype) { + DBusMessage *m; + DBusError err; + + pa_assert(t); + pa_assert(t->y); + + dbus_error_init(&err); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", t->path, "org.bluez.MediaTransport", "Release")); + pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID)); + dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->y->connection), m, -1, &err); + + if (dbus_error_is_set(&err)) { + pa_log("Failed to release transport %s: %s", t->path, err.message); + dbus_error_free(&err); + } else + pa_log_info("Transport %s released", t->path); +} + static int setup_dbus(pa_bluetooth_discovery *y) { DBusError err; @@ -785,9 +1063,358 @@ static int setup_dbus(pa_bluetooth_discovery *y) { return 0; } +static pa_bluetooth_transport *transport_new(pa_bluetooth_discovery *y, const char *path, enum profile p, const uint8_t *config, int size) { + pa_bluetooth_transport *t; + + t = pa_xnew0(pa_bluetooth_transport, 1); + t->y = y; + t->path = pa_xstrdup(path); + t->profile = p; + t->config_size = size; + + if (size > 0) { + t->config = pa_xnew(uint8_t, size); + memcpy(t->config, config, size); + } + + return t; +} + +static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) { + pa_bluetooth_discovery *y = userdata; + pa_bluetooth_device *d; + pa_bluetooth_transport *t; + const char *path, *dev_path = NULL, *uuid = NULL; + uint8_t *config = NULL; + int size = 0; + enum profile p; + DBusMessageIter args, props; + DBusMessage *r; + + dbus_message_iter_init(m, &args); + + dbus_message_iter_get_basic(&args, &path); + if (!dbus_message_iter_next(&args)) + goto fail; + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) + goto fail; + + /* Read transport properties */ + while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(&props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + if (strcasecmp(key, "UUID") == 0) { + if (var != DBUS_TYPE_STRING) + goto fail; + dbus_message_iter_get_basic(&value, &uuid); + } else if (strcasecmp(key, "Device") == 0) { + if (var != DBUS_TYPE_OBJECT_PATH) + goto fail; + dbus_message_iter_get_basic(&value, &dev_path); + } else if (strcasecmp(key, "Configuration") == 0) { + DBusMessageIter array; + if (var != DBUS_TYPE_ARRAY) + goto fail; + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_fixed_array(&array, &config, &size); + } + + dbus_message_iter_next(&props); + } + + d = found_device(y, dev_path); + if (!d) + goto fail; + + if (dbus_message_has_path(m, HFP_AG_ENDPOINT)) + p = PROFILE_HSP; + else if (dbus_message_has_path(m, A2DP_SOURCE_ENDPOINT)) + p = PROFILE_A2DP; + else + p = PROFILE_A2DP_SOURCE; + + t = transport_new(y, path, p, config, size); + pa_hashmap_put(d->transports, t->path, t); + + pa_log_debug("Transport %s profile %d available", t->path, t->profile); + + pa_assert_se(r = dbus_message_new_method_return(m)); + + return r; + +fail: + pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments"); + pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", + "Unable to set configuration"))); + return r; +} + +static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage *m, void *userdata) { + pa_bluetooth_discovery *y = userdata; + pa_bluetooth_device *d; + pa_bluetooth_transport *t; + void *state = NULL; + DBusMessage *r; + DBusError e; + const char *path; + + dbus_error_init(&e); + + if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e.message); + dbus_error_free(&e); + goto fail; + } + + while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) { + if ((t = pa_hashmap_get(d->transports, path))) { + pa_log_debug("Clearing transport %s profile %d", t->path, t->profile); + pa_hashmap_remove(d->transports, t->path); + transport_free(t); + break; + } + } + + pa_assert_se(r = dbus_message_new_method_return(m)); + + return r; + +fail: + pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", + "Unable to clear configuration"))); + return r; +} + +static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) { + + switch (freq) { + case BT_SBC_SAMPLING_FREQ_16000: + case BT_SBC_SAMPLING_FREQ_32000: + return 53; + + case BT_SBC_SAMPLING_FREQ_44100: + + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 53; + + default: + pa_log_warn("Invalid channel mode %u", mode); + return 53; + } + + case BT_SBC_SAMPLING_FREQ_48000: + + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 51; + + default: + pa_log_warn("Invalid channel mode %u", mode); + return 51; + } + + default: + pa_log_warn("Invalid sampling freq %u", freq); + return 53; + } +} + +static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) { + pa_bluetooth_discovery *y = userdata; + sbc_capabilities_raw_t *cap, config; + uint8_t *pconf = (uint8_t *) &config; + int i, size; + DBusMessage *r; + DBusError e; + + static const struct { + uint32_t rate; + uint8_t cap; + } freq_table[] = { + { 16000U, BT_SBC_SAMPLING_FREQ_16000 }, + { 32000U, BT_SBC_SAMPLING_FREQ_32000 }, + { 44100U, BT_SBC_SAMPLING_FREQ_44100 }, + { 48000U, BT_SBC_SAMPLING_FREQ_48000 } + }; + + dbus_error_init(&e); + + if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) { + pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message); + dbus_error_free(&e); + goto fail; + } + + if (dbus_message_has_path(m, HFP_AG_ENDPOINT)) + goto done; + + pa_assert(size == sizeof(config)); + + memset(&config, 0, sizeof(config)); + + /* Find the lowest freq that is at least as high as the requested + * sampling rate */ + for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) + if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) { + config.frequency = freq_table[i].cap; + break; + } + + if ((unsigned) i == PA_ELEMENTSOF(freq_table)) { + for (--i; i >= 0; i--) { + if (cap->frequency & freq_table[i].cap) { + config.frequency = freq_table[i].cap; + break; + } + } + + if (i < 0) { + pa_log("Not suitable sample rate"); + goto fail; + } + } + + pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table)); + + if (y->core->default_sample_spec.channels <= 1) { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + config.channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + } + + if (y->core->default_sample_spec.channels >= 2) { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) + config.channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) + config.channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) + config.channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) { + config.channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + } else { + pa_log("No supported channel modes"); + goto fail; + } + } + + if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16) + config.block_length = BT_A2DP_BLOCK_LENGTH_16; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12) + config.block_length = BT_A2DP_BLOCK_LENGTH_12; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8) + config.block_length = BT_A2DP_BLOCK_LENGTH_8; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4) + config.block_length = BT_A2DP_BLOCK_LENGTH_4; + else { + pa_log_error("No supported block lengths"); + goto fail; + } + + if (cap->subbands & BT_A2DP_SUBBANDS_8) + config.subbands = BT_A2DP_SUBBANDS_8; + else if (cap->subbands & BT_A2DP_SUBBANDS_4) + config.subbands = BT_A2DP_SUBBANDS_4; + else { + pa_log_error("No supported subbands"); + goto fail; + } + + if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) + config.allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; + else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR) + config.allocation_method = BT_A2DP_ALLOCATION_SNR; + + config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool); + config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool); + +done: + pa_assert_se(r = dbus_message_new_method_return(m)); + + pa_assert_se(dbus_message_append_args( + r, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, + DBUS_TYPE_INVALID)); + + return r; + +fail: + pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", + "Unable to select configuration"))); + return r; +} + +static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) { + struct pa_bluetooth_discovery *y = userdata; + DBusMessage *r = NULL; + DBusError e; + const char *path; + + pa_assert(y); + + pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", + dbus_message_get_interface(m), + dbus_message_get_path(m), + dbus_message_get_member(m)); + + path = dbus_message_get_path(m); + dbus_error_init(&e); + + if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT)) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + const char *xml = ENDPOINT_INTROSPECT_XML; + + pa_assert_se(r = dbus_message_new_method_return(m)); + pa_assert_se(dbus_message_append_args( + r, + DBUS_TYPE_STRING, &xml, + DBUS_TYPE_INVALID)); + + } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SetConfiguration")) { + r = endpoint_set_configuration(c, m, userdata); + } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SelectConfiguration")) { + r = endpoint_select_configuration(c, m, userdata); + } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "ClearConfiguration")) + r = endpoint_clear_configuration(c, m, userdata); + else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (r) { + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL)); + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) { DBusError err; pa_bluetooth_discovery *y; + static const DBusObjectPathVTable vtable_endpoint = { + .message_function = endpoint_handler, + }; pa_assert(c); @@ -832,6 +1459,12 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) { goto fail; } +#ifdef DBUS_TYPE_UNIX_FD + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT, &vtable_endpoint, y)); + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y)); + pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT, &vtable_endpoint, y)); +#endif + list_adapters(y); return y; @@ -870,6 +1503,11 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) { } if (y->connection) { +#ifdef DBUS_TYPE_UNIX_FD + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT); + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT); + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT); +#endif pa_dbus_remove_matches(pa_dbus_connection_get(y->connection), "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'", "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'", @@ -888,6 +1526,8 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) { if (y->filter_added) dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y); + dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y); + pa_dbus_connection_unref(y->connection); } diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h index 9cee3de3..ce551967 100644 --- a/src/modules/bluetooth/bluetooth-util.h +++ b/src/modules/bluetooth/bluetooth-util.h @@ -25,6 +25,7 @@ #include <dbus/dbus.h> #include <pulsecore/llist.h> +#include <pulsecore/strlist.h> #include <pulsecore/macro.h> #include <pulsecore/core-util.h> @@ -45,6 +46,7 @@ typedef struct pa_bluetooth_uuid pa_bluetooth_uuid; typedef struct pa_bluetooth_device pa_bluetooth_device; typedef struct pa_bluetooth_discovery pa_bluetooth_discovery; +typedef struct pa_bluetooth_transport pa_bluetooth_transport; struct userdata; @@ -53,6 +55,23 @@ struct pa_bluetooth_uuid { PA_LLIST_FIELDS(pa_bluetooth_uuid); }; +enum profile { + PROFILE_A2DP, + PROFILE_A2DP_SOURCE, + PROFILE_HSP, + PROFILE_HFGW, + PROFILE_OFF +}; + +struct pa_bluetooth_transport { + pa_bluetooth_discovery *y; + char *path; + enum profile profile; + uint8_t codec; + uint8_t *config; + int config_size; +}; + /* This enum is shared among Audio, Headset, AudioSink, and AudioSource, although not all values are acceptable in all profiles */ typedef enum pa_bt_audio_state { PA_BT_AUDIO_STATE_INVALID = -1, @@ -70,6 +89,7 @@ struct pa_bluetooth_device { /* Device information */ char *name; char *path; + pa_hashmap *transports; int paired; char *alias; int device_connected; @@ -103,6 +123,12 @@ void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *d); const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *d, const char* path); const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *d, const char* address); +const pa_bluetooth_transport* pa_bluetooth_discovery_get_transport(pa_bluetooth_discovery *y, const char *path); +const pa_bluetooth_transport* pa_bluetooth_device_get_transport(const pa_bluetooth_device *d, enum profile profile); + +int pa_bluetooth_transport_acquire(const pa_bluetooth_transport *t, const char *accesstype); +void pa_bluetooth_transport_release(const pa_bluetooth_transport *t, const char *accesstype); + pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *d); const char* pa_bluetooth_get_form_factor(uint32_t class); diff --git a/src/modules/bluetooth/ipc.h b/src/modules/bluetooth/ipc.h index 2e170f50..9537886b 100644 --- a/src/modules/bluetooth/ipc.h +++ b/src/modules/bluetooth/ipc.h @@ -201,6 +201,34 @@ typedef struct { uint8_t max_bitpool; } __attribute__ ((packed)) sbc_capabilities_t; +#if __BYTE_ORDER == __LITTLE_ENDIAN + +typedef struct { + uint8_t channel_mode:4; + uint8_t frequency:4; + uint8_t allocation_method:2; + uint8_t subbands:2; + uint8_t block_length:4; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) sbc_capabilities_raw_t; + +#elif __BYTE_ORDER == __BIG_ENDIAN + +typedef struct { + uint8_t frequency:4; + uint8_t channel_mode:4; + uint8_t block_length:4; + uint8_t subbands:2; + uint8_t allocation_method:2; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) sbc_capabilities_raw_t; + +#else +#error "Unknown byte order" +#endif + typedef struct { codec_capabilities_t capability; uint8_t channel_mode; diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index 61fe3697..c7ac7fa6 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -48,6 +48,7 @@ #include <pulsecore/time-smoother.h> #include <pulsecore/namereg.h> #include <pulsecore/dbus-shared.h> +#include <pulsecore/llist.h> #include "module-bluetooth-device-symdef.h" #include "ipc.h" @@ -129,20 +130,15 @@ struct hsp_info { pa_hook_slot *source_state_changed_slot; }; -enum profile { - PROFILE_A2DP, - PROFILE_A2DP_SOURCE, - PROFILE_HSP, - PROFILE_HFGW, - PROFILE_OFF -}; - struct userdata { pa_core *core; pa_module *module; char *address; char *path; + char *transport; + char *accesstype; + pa_bluetooth_discovery *discovery; pa_bool_t auto_connect; @@ -202,10 +198,14 @@ static int service_send(struct userdata *u, const bt_audio_msg_header_t *msg) { ssize_t r; pa_assert(u); - pa_assert(u->service_fd >= 0); pa_assert(msg); pa_assert(msg->length > 0); + if (u->service_fd < 0) { + pa_log_warn("Service not connected"); + return -1; + } + pa_log_debug("Sending %s -> %s", pa_strnull(bt_audio_strtype(msg->type)), pa_strnull(bt_audio_strname(msg->name))); @@ -745,6 +745,41 @@ static int set_conf(struct userdata *u) { } /* from IO thread, except in SCO over PCM */ + +static int setup_stream(struct userdata *u) { + struct pollfd *pollfd; + int one; + + pa_make_fd_nonblock(u->stream_fd); + pa_make_socket_low_delay(u->stream_fd); + + one = 1; + if (setsockopt(u->stream_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) + pa_log_warn("Failed to enable SO_TIMESTAMP: %s", pa_cstrerror(errno)); + + pa_log_debug("Stream properly set up, we're ready to roll!"); + + 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; + pollfd->events = pollfd->revents = 0; + + u->read_index = u->write_index = 0; + u->started_at = 0; + + if (u->source) + u->read_smoother = pa_smoother_new( + PA_USEC_PER_SEC, + PA_USEC_PER_SEC*2, + TRUE, + TRUE, + 10, + pa_rtclock_now(), + TRUE); + + return 0; +} + static int start_stream_fd(struct userdata *u) { union { bt_audio_msg_header_t rsp; @@ -754,8 +789,6 @@ static int start_stream_fd(struct userdata *u) { bt_audio_error_t error; uint8_t buf[BT_SUGGESTED_BUFFER_SIZE]; } msg; - struct pollfd *pollfd; - int one; pa_assert(u); pa_assert(u->rtpoll); @@ -781,34 +814,7 @@ static int start_stream_fd(struct userdata *u) { return -1; } - pa_make_fd_nonblock(u->stream_fd); - pa_make_socket_low_delay(u->stream_fd); - - one = 1; - if (setsockopt(u->stream_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) - pa_log_warn("Failed to enable SO_TIMESTAMP: %s", pa_cstrerror(errno)); - - pa_log_debug("Stream properly set up, we're ready to roll!"); - - 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; - pollfd->events = pollfd->revents = 0; - - u->read_index = u->write_index = 0; - u->started_at = 0; - - if (u->source) - u->read_smoother = pa_smoother_new( - PA_USEC_PER_SEC, - PA_USEC_PER_SEC*2, - TRUE, - TRUE, - 10, - pa_rtclock_now(), - TRUE); - - return 0; + return setup_stream(u); } /* from IO thread */ @@ -852,6 +858,76 @@ static int stop_stream_fd(struct userdata *u) { return r; } +static void bt_transport_release(struct userdata *u) +{ + const char *accesstype = "rw"; + const pa_bluetooth_transport *t; + + /* Ignore if already released */ + if (!u->accesstype) + return; + + pa_log_debug("Releasing transport %s", u->transport); + + t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport); + if (t) + pa_bluetooth_transport_release(t, accesstype); + + pa_xfree(u->accesstype); + u->accesstype = NULL; + + if (u->rtpoll_item) { + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + + if (u->stream_fd >= 0) { + pa_close(u->stream_fd); + u->stream_fd = -1; + } + + if (u->read_smoother) { + pa_smoother_free(u->read_smoother); + u->read_smoother = NULL; + } +} + +static int bt_transport_acquire(struct userdata *u, pa_bool_t start) +{ + const char *accesstype = "rw"; + const pa_bluetooth_transport *t; + + if (u->accesstype) { + if (start) + goto done; + return 0; + } + + pa_log_debug("Acquiring transport %s", u->transport); + + t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport); + if (!t) { + pa_log("Transport %s no longer available", u->transport); + pa_xfree(u->transport); + u->transport = NULL; + return -1; + } + + u->stream_fd = pa_bluetooth_transport_acquire(t, accesstype); + if (u->stream_fd < 0) + return -1; + + u->accesstype = pa_xstrdup(accesstype); + pa_log_info("Transport %s acquired: fd %d", u->transport, u->stream_fd); + + if (!start) + return 0; + +done: + pa_log_info("Transport %s resuming", u->transport); + return setup_stream(u); +} + /* Run from IO thread */ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SINK(o)->userdata; @@ -870,11 +946,15 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); /* Stop the device if the source is suspended as well */ - if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) + if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) { /* We deliberately ignore whether stopping * actually worked. Since the stream_fd is * closed it doesn't really matter */ - stop_stream_fd(u); + if (u->transport) + bt_transport_release(u); + else + stop_stream_fd(u); + } break; @@ -884,9 +964,13 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse break; /* Resume the device if the source was suspended as well */ - if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) - if (start_stream_fd(u) < 0) + if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) { + if (u->transport) { + if (bt_transport_acquire(u, TRUE) < 0) + failed = TRUE; + } else if (start_stream_fd(u) < 0) failed = TRUE; + } break; case PA_SINK_UNLINKED: @@ -942,8 +1026,12 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state)); /* Stop the device if the sink is suspended as well */ - if (!u->sink || u->sink->state == PA_SINK_SUSPENDED) - stop_stream_fd(u); + if (!u->sink || u->sink->state == PA_SINK_SUSPENDED) { + if (u->transport) + bt_transport_release(u); + else + stop_stream_fd(u); + } if (u->read_smoother) pa_smoother_pause(u->read_smoother, pa_rtclock_now()); @@ -955,10 +1043,13 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off break; /* Resume the device if the sink was suspended as well */ - if (!u->sink || u->sink->thread_info.state == PA_SINK_SUSPENDED) - if (start_stream_fd(u) < 0) + if (!u->sink || u->sink->thread_info.state == PA_SINK_SUSPENDED) { + if (u->transport) { + if (bt_transport_acquire(u, TRUE) < 0) failed = TRUE; - + } else if (start_stream_fd(u) < 0) + failed = TRUE; + } /* We don't resume the smoother here. Instead we * wait until the first packet arrives */ break; @@ -1414,7 +1505,10 @@ static void thread_func(void *userdata) { pa_thread_mq_install(&u->thread_mq); - if (start_stream_fd(u) < 0) + if (u->transport) { + if (bt_transport_acquire(u, TRUE) < 0) + goto fail; + } else if (start_stream_fd(u) < 0) goto fail; for (;;) { @@ -1709,17 +1803,25 @@ static void sco_over_pcm_state_update(struct userdata *u) { if (u->service_fd >= 0) return; + init_bt(u); + pa_log_debug("Resuming SCO over PCM"); - if ((init_bt(u) < 0) || (init_profile(u) < 0)) + if (init_profile(u) < 0) pa_log("Can't resume SCO over PCM"); - start_stream_fd(u); + if (u->transport) + bt_transport_acquire(u, TRUE); + else + start_stream_fd(u); } else { if (u->service_fd < 0) return; - stop_stream_fd(u); + if (u->transport) + bt_transport_release(u); + else + stop_stream_fd(u); pa_log_debug("Closing SCO over PCM"); pa_close(u->service_fd); @@ -1906,6 +2008,218 @@ static void shutdown_bt(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; + + t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport); + pa_assert(t); + + config = (sbc_capabilities_raw_t *) t->config; + + if (a2dp->sbc_initialized) + sbc_reinit(&a2dp->sbc, 0); + else + sbc_init(&a2dp->sbc, 0); + a2dp->sbc_initialized = TRUE; + + switch (config->frequency) { + case BT_SBC_SAMPLING_FREQ_16000: + a2dp->sbc.frequency = SBC_FREQ_16000; + break; + case BT_SBC_SAMPLING_FREQ_32000: + a2dp->sbc.frequency = SBC_FREQ_32000; + break; + case BT_SBC_SAMPLING_FREQ_44100: + a2dp->sbc.frequency = SBC_FREQ_44100; + break; + case BT_SBC_SAMPLING_FREQ_48000: + a2dp->sbc.frequency = SBC_FREQ_48000; + break; + default: + pa_assert_not_reached(); + } + + switch (config->channel_mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + a2dp->sbc.mode = SBC_MODE_MONO; + break; + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL; + break; + case BT_A2DP_CHANNEL_MODE_STEREO: + a2dp->sbc.mode = SBC_MODE_STEREO; + break; + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + a2dp->sbc.mode = SBC_MODE_JOINT_STEREO; + break; + default: + pa_assert_not_reached(); + } + + switch (config->allocation_method) { + case BT_A2DP_ALLOCATION_SNR: + a2dp->sbc.allocation = SBC_AM_SNR; + break; + case BT_A2DP_ALLOCATION_LOUDNESS: + a2dp->sbc.allocation = SBC_AM_LOUDNESS; + break; + default: + pa_assert_not_reached(); + } + + switch (config->subbands) { + case BT_A2DP_SUBBANDS_4: + a2dp->sbc.subbands = SBC_SB_4; + break; + case BT_A2DP_SUBBANDS_8: + a2dp->sbc.subbands = SBC_SB_8; + break; + default: + pa_assert_not_reached(); + } + + switch (config->block_length) { + case BT_A2DP_BLOCK_LENGTH_4: + a2dp->sbc.blocks = SBC_BLK_4; + break; + case BT_A2DP_BLOCK_LENGTH_8: + a2dp->sbc.blocks = SBC_BLK_8; + break; + case BT_A2DP_BLOCK_LENGTH_12: + a2dp->sbc.blocks = SBC_BLK_12; + break; + case BT_A2DP_BLOCK_LENGTH_16: + a2dp->sbc.blocks = SBC_BLK_16; + break; + default: + pa_assert_not_reached(); + } + + a2dp->sbc.bitpool = config->max_bitpool; + a2dp->codesize = sbc_get_codesize(&a2dp->sbc); + a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); + + u->block_size = + ((u->link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) + / a2dp->frame_length + * a2dp->codesize); + + pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n", + a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks, a2dp->sbc.bitpool); + + return 0; +} + +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; + } + + 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; + + 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 */ static int init_bt(struct userdata *u) { pa_assert(u); @@ -1917,7 +2231,7 @@ static int init_bt(struct userdata *u) { u->service_read_type = 0; if ((u->service_fd = bt_audio_service_open()) < 0) { - pa_log_error("Couldn't connect to bluetooth audio service"); + pa_log_warn("Bluetooth audio service not available"); return -1; } @@ -1928,8 +2242,30 @@ static int init_bt(struct userdata *u) { /* Run from main thread */ static int setup_bt(struct userdata *u) { + const pa_bluetooth_device *d; + const pa_bluetooth_transport *t; + pa_assert(u); + if (!(d = pa_bluetooth_discovery_get_by_path(u->discovery, u->path))) { + pa_log_error("Failed to get device object."); + return -1; + } + + /* release transport if exist */ + if (u->transport) { + bt_transport_release(u); + pa_xfree(u->transport); + u->transport = NULL; + } + + /* check if profile has a transport */ + t = pa_bluetooth_device_get_transport(d, u->profile); + if (t) { + u->transport = pa_xstrdup(t->path); + return bt_transport_open(u); + } + if (get_caps(u, 0) < 0) return -1; @@ -2036,7 +2372,10 @@ static int start_thread(struct userdata *u) { #ifdef NOKIA if (USE_SCO_OVER_PCM(u)) { - if (start_stream_fd(u) < 0) + if (u->transport) { + if (bt_transport_acquire(u, TRUE) < 0) + return -1; + } else if (start_stream_fd(u) < 0) return -1; pa_sink_ref(u->sink); @@ -2316,7 +2655,7 @@ static const pa_bluetooth_device* find_device(struct userdata *u, const char *ad } if (address && !(pa_streq(d->address, address))) { - pa_log_error("Passed path %s and address %s don't match.", path, address); + pa_log_error("Passed path %s address %s != %s don't match.", path, d->address, address); return NULL; } @@ -2429,10 +2768,6 @@ int pa__init(pa_module* m) { if (add_card(u, device) < 0) goto fail; - /* Connect to the BT service and query capabilities */ - if (init_bt(u) < 0) - goto fail; - if (!dbus_connection_add_filter(pa_dbus_connection_get(u->connection), filter_cb, u, NULL)) { pa_log_error("Failed to add filter function"); goto fail; @@ -2458,6 +2793,9 @@ int pa__init(pa_module* m) { pa_xfree(speaker); pa_xfree(mike); + /* Connect to the BT service */ + init_bt(u); + if (u->profile != PROFILE_OFF) if (init_profile(u) < 0) goto fail; @@ -2552,6 +2890,11 @@ void pa__done(pa_module *m) { pa_xfree(u->address); pa_xfree(u->path); + if (u->transport) { + bt_transport_release(u); + pa_xfree(u->transport); + } + if (u->discovery) pa_bluetooth_discovery_unref(u->discovery); |