diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c index 4c2a77612..f46ad5c8e 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -287,6 +287,123 @@ static void device_start_waiting_for_profiles(pa_bluetooth_device *device) { wait_for_profiles_cb, device); } +struct switch_codec_data { + char *pa_endpoint; + char *device_path; + pa_bluetooth_profile_t profile; + void (*cb)(bool, pa_bluetooth_profile_t profile, void *); + void *userdata; +}; + +static void pa_bluetooth_switch_codec_reply(DBusPendingCall *pending, void *userdata) { + DBusMessage *r; + pa_dbus_pending *p; + pa_bluetooth_discovery *y; + pa_bluetooth_device *device; + struct switch_codec_data *data; + + pa_assert(pending); + pa_assert_se(p = userdata); + pa_assert_se(y = p->context_data); + pa_assert_se(data = p->call_data); + pa_assert_se(r = dbus_pending_call_steal_reply(pending)); + + PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p); + pa_dbus_pending_free(p); + + device = pa_hashmap_get(y->devices, data->device_path); + if (!device) { + pa_log_error("Changing codec for device %s with profile %s failed. Device is not connected anymore", + data->device_path, pa_bluetooth_profile_to_string(data->profile)); + data->cb(false, data->profile, data->userdata); + } else if (dbus_message_get_type(r) != DBUS_MESSAGE_TYPE_ERROR) { + pa_log_info("Changing codec for device %s with profile %s succeeded", + data->device_path, pa_bluetooth_profile_to_string(data->profile)); + data->cb(true, data->profile, data->userdata); + } else if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + pa_log_error("Changing codec for device %s with profile %s failed. Error: %s", + data->device_path, pa_bluetooth_profile_to_string(data->profile), + dbus_message_get_error_name(r)); + } + + dbus_message_unref(r); + + pa_xfree(data->pa_endpoint); + pa_xfree(data->device_path); + pa_xfree(data); + + device->codec_switching_in_progress = false; +} + +bool pa_bluetooth_switch_codec(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, + pa_hashmap *capabilities_hashmap, const pa_a2dp_codec *a2dp_codec, + void (*codec_switch_cb)(bool, pa_bluetooth_profile_t profile, void *), void *userdata) { + DBusMessageIter iter, dict; + DBusMessage *m; + struct switch_codec_data *data; + pa_a2dp_codec_capabilities *capabilities; + uint8_t config[MAX_A2DP_CAPS_SIZE]; + uint8_t config_size; + bool is_a2dp_sink; + pa_hashmap *all_endpoints; + char *pa_endpoint; + const char *endpoint; + + pa_assert(device); + pa_assert(capabilities_hashmap); + pa_assert(a2dp_codec); + + if (device->codec_switching_in_progress) { + pa_log_error("Codec switching operation already in progress"); + return false; + } + + is_a2dp_sink = profile == PA_BLUETOOTH_PROFILE_A2DP_SINK; + + all_endpoints = NULL; + all_endpoints = pa_hashmap_get(is_a2dp_sink ? device->a2dp_sink_endpoints : device->a2dp_source_endpoints, + &a2dp_codec->id); + pa_assert(all_endpoints); + + pa_assert_se(endpoint = a2dp_codec->choose_remote_endpoint(capabilities_hashmap, &device->discovery->core->default_sample_spec, is_a2dp_sink)); + pa_assert_se(capabilities = pa_hashmap_get(all_endpoints, endpoint)); + + config_size = a2dp_codec->fill_preferred_configuration(&device->discovery->core->default_sample_spec, + capabilities->buffer, capabilities->size, config); + if (config_size == 0) + return false; + + pa_endpoint = pa_sprintf_malloc("%s/%s", is_a2dp_sink ? A2DP_SOURCE_ENDPOINT : A2DP_SINK_ENDPOINT, + a2dp_codec->name); + + pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, endpoint, + BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SetConfiguration")); + + dbus_message_iter_init_append(m, &iter); + pa_assert_se(dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &pa_endpoint)); + dbus_message_iter_open_container(&iter, 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, + &dict); + pa_dbus_append_basic_array_variant_dict_entry(&dict, "Capabilities", DBUS_TYPE_BYTE, &config, config_size); + dbus_message_iter_close_container(&iter, &dict); + + device->codec_switching_in_progress = true; + + data = pa_xnew0(struct switch_codec_data, 1); + data->pa_endpoint = pa_endpoint; + data->device_path = pa_xstrdup(device->path); + data->profile = profile; + data->cb = codec_switch_cb; + data->userdata = userdata; + + send_and_add_to_pending(device->discovery, m, pa_bluetooth_switch_codec_reply, data); + + return true; +} + void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_transport_state_t state) { bool old_any_connected; unsigned n_disconnected_profiles; @@ -537,6 +654,59 @@ static int parse_transport_properties(pa_bluetooth_transport *t, DBusMessageIter return 0; } +static unsigned pa_a2dp_codec_id_hash_func(const void *_p) { + unsigned hash; + const pa_a2dp_codec_id *p = _p; + + hash = p->codec_id; + hash = 31 * hash + ((p->vendor_id >> 0) & 0xFF); + hash = 31 * hash + ((p->vendor_id >> 8) & 0xFF); + hash = 31 * hash + ((p->vendor_id >> 16) & 0xFF); + hash = 31 * hash + ((p->vendor_id >> 24) & 0xFF); + hash = 31 * hash + ((p->vendor_codec_id >> 0) & 0xFF); + hash = 31 * hash + ((p->vendor_codec_id >> 8) & 0xFF); + return hash; +} + +static int pa_a2dp_codec_id_compare_func(const void *_a, const void *_b) { + const pa_a2dp_codec_id *a = _a; + const pa_a2dp_codec_id *b = _b; + + if (a->codec_id < b->codec_id) + return -1; + if (a->codec_id > b->codec_id) + return 1; + + if (a->vendor_id < b->vendor_id) + return -1; + if (a->vendor_id > b->vendor_id) + return 1; + + if (a->vendor_codec_id < b->vendor_codec_id) + return -1; + if (a->vendor_codec_id > b->vendor_codec_id) + return 1; + + return 0; +} + +static void remote_endpoint_remove(pa_bluetooth_discovery *y, const char *path) { + pa_bluetooth_device *device; + pa_hashmap *endpoints; + void *devices_state; + void *state; + + PA_HASHMAP_FOREACH(device, y->devices, devices_state) { + PA_HASHMAP_FOREACH(endpoints, device->a2dp_sink_endpoints, state) + pa_hashmap_remove_and_free(endpoints, path); + + PA_HASHMAP_FOREACH(endpoints, device->a2dp_source_endpoints, state) + pa_hashmap_remove_and_free(endpoints, path); + } + + pa_log_debug("Remote endpoint %s was removed", path); +} + static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char *path) { pa_bluetooth_device *d; @@ -547,6 +717,8 @@ static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char d->discovery = y; d->path = pa_xstrdup(path); d->uuids = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree); + d->a2dp_sink_endpoints = pa_hashmap_new_full(pa_a2dp_codec_id_hash_func, pa_a2dp_codec_id_compare_func, pa_xfree, (pa_free_cb_t)pa_hashmap_free); + d->a2dp_source_endpoints = pa_hashmap_new_full(pa_a2dp_codec_id_hash_func, pa_a2dp_codec_id_compare_func, pa_xfree, (pa_free_cb_t)pa_hashmap_free); pa_hashmap_put(y->devices, d->path, d); @@ -602,6 +774,10 @@ static void device_free(pa_bluetooth_device *d) { if (d->uuids) pa_hashmap_free(d->uuids); + if (d->a2dp_sink_endpoints) + pa_hashmap_free(d->a2dp_sink_endpoints); + if (d->a2dp_source_endpoints) + pa_hashmap_free(d->a2dp_source_endpoints); pa_xfree(d->path); pa_xfree(d->alias); @@ -996,6 +1172,7 @@ finish: register_legacy_sbc_endpoint(y, a2dp_codec_sbc, path, A2DP_SOURCE_ENDPOINT "/sbc", PA_BLUETOOTH_UUID_A2DP_SOURCE); pa_log_warn("Only SBC codec is available for A2DP profiles"); + a->application_registered = false; } pa_xfree(path); @@ -1029,6 +1206,150 @@ static void register_application(pa_bluetooth_adapter *a) { send_and_add_to_pending(a->discovery, m, register_application_reply, pa_xstrdup(a->path)); } +static void parse_remote_endpoint_properties(pa_bluetooth_discovery *y, const char *endpoint, DBusMessageIter *i) { + DBusMessageIter element_i; + pa_bluetooth_device *device; + pa_hashmap *codec_endpoints; + pa_hashmap *endpoints; + pa_a2dp_codec_id *a2dp_codec_id; + pa_a2dp_codec_capabilities *a2dp_codec_capabilities; + const char *uuid = NULL; + const char *device_path = NULL; + uint8_t codec_id = 0; + bool have_codec_id = false; + const uint8_t *capabilities = NULL; + int capabilities_size = 0; + + pa_log_debug("Parsing remote endpoint %s", endpoint); + + dbus_message_iter_recurse(i, &element_i); + + while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter dict_i, variant_i; + const char *key; + + dbus_message_iter_recurse(&element_i, &dict_i); + + key = check_variant_property(&dict_i); + if (key == NULL) { + pa_log_error("Received invalid property for remote endpoint %s", endpoint); + return; + } + + dbus_message_iter_recurse(&dict_i, &variant_i); + + if (pa_streq(key, "UUID")) { + if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_STRING) { + pa_log_warn("Remote endpoint %s property 'UUID' is not string, ignoring", endpoint); + return; + } + + dbus_message_iter_get_basic(&variant_i, &uuid); + } else if (pa_streq(key, "Codec")) { + if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_BYTE) { + pa_log_warn("Remote endpoint %s property 'Codec' is not byte, ignoring", endpoint); + return; + } + + dbus_message_iter_get_basic(&variant_i, &codec_id); + have_codec_id = true; + } else if (pa_streq(key, "Capabilities")) { + DBusMessageIter array; + + if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_ARRAY) { + pa_log_warn("Remote endpoint %s property 'Capabilities' is not array, ignoring", endpoint); + return; + } + + dbus_message_iter_recurse(&variant_i, &array); + if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE) { + pa_log_warn("Remote endpoint %s property 'Capabilities' is not array of bytes, ignoring", endpoint); + return; + } + + dbus_message_iter_get_fixed_array(&array, &capabilities, &capabilities_size); + } else if (pa_streq(key, "Device")) { + if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_OBJECT_PATH) { + pa_log_warn("Remote endpoint %s property 'Device' is not path, ignoring", endpoint); + return; + } + + dbus_message_iter_get_basic(&variant_i, &device_path); + } + + dbus_message_iter_next(&element_i); + } + + if (!uuid) { + pa_log_warn("Remote endpoint %s does not have property 'UUID', ignoring", endpoint); + return; + } + + if (!have_codec_id) { + pa_log_warn("Remote endpoint %s does not have property 'Codec', ignoring", endpoint); + return; + } + + if (!capabilities || !capabilities_size) { + pa_log_warn("Remote endpoint %s does not have property 'Capabilities', ignoring", endpoint); + return; + } + + if (!device_path) { + pa_log_warn("Remote endpoint %s does not have property 'Device', ignoring", endpoint); + return; + } + + device = pa_hashmap_get(y->devices, device_path); + if (!device) { + pa_log_warn("Device for remote endpoint %s was not found", endpoint); + return; + } + + if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK)) { + codec_endpoints = device->a2dp_sink_endpoints; + } else if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE)) { + codec_endpoints = device->a2dp_source_endpoints; + } else { + pa_log_warn("Remote endpoint %s does not have valid property 'UUID', ignoring", endpoint); + return; + } + + if (capabilities_size < 0 || capabilities_size > MAX_A2DP_CAPS_SIZE) { + pa_log_warn("Remote endpoint %s does not have valid property 'Capabilities', ignoring", endpoint); + return; + } + + a2dp_codec_id = pa_xmalloc0(sizeof(*a2dp_codec_id)); + a2dp_codec_id->codec_id = codec_id; + if (codec_id == A2DP_CODEC_VENDOR) { + if ((size_t)capabilities_size < sizeof(a2dp_vendor_codec_t)) { + pa_log_warn("Remote endpoint %s does not have valid property 'Capabilities', ignoring", endpoint); + pa_xfree(a2dp_codec_id); + return; + } + a2dp_codec_id->vendor_id = A2DP_GET_VENDOR_ID(*(a2dp_vendor_codec_t *)capabilities); + a2dp_codec_id->vendor_codec_id = A2DP_GET_CODEC_ID(*(a2dp_vendor_codec_t *)capabilities); + } else { + a2dp_codec_id->vendor_id = 0; + a2dp_codec_id->vendor_codec_id = 0; + } + + a2dp_codec_capabilities = pa_xmalloc0(sizeof(*a2dp_codec_capabilities) + capabilities_size); + a2dp_codec_capabilities->size = capabilities_size; + memcpy(a2dp_codec_capabilities->buffer, capabilities, capabilities_size); + + endpoints = pa_hashmap_get(codec_endpoints, a2dp_codec_id); + if (!endpoints) { + endpoints = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, pa_xfree); + pa_hashmap_put(codec_endpoints, a2dp_codec_id, endpoints); + } + + if (pa_hashmap_remove_and_free(endpoints, endpoint) >= 0) + pa_log_debug("Replacing existing remote endpoint %s", endpoint); + pa_hashmap_put(endpoints, pa_xstrdup(endpoint), a2dp_codec_capabilities); +} + static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) { DBusMessageIter element_i; const char *path; @@ -1085,6 +1406,8 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa pa_log_debug("Device %s found", d->path); parse_device_properties(d, &iface_i); + } else if (pa_streq(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE)) { + parse_remote_endpoint_properties(y, path, &iface_i); } else pa_log_debug("Unknown interface %s found, skipping", interface); @@ -1292,6 +1615,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us device_remove(y, p); else if (pa_streq(iface, BLUEZ_ADAPTER_INTERFACE)) adapter_remove(y, p); + else if (pa_streq(iface, BLUEZ_MEDIA_ENDPOINT_INTERFACE)) + remote_endpoint_remove(y, p); dbus_message_iter_next(&element_i); } @@ -1350,6 +1675,10 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; parse_transport_properties(t, &arg_i); + } else if (pa_streq(iface, BLUEZ_MEDIA_ENDPOINT_INTERFACE)) { + pa_log_info("Properties changed in remote endpoint %s", dbus_message_get_path(m)); + + parse_remote_endpoint_properties(y, dbus_message_get_path(m), &arg_i); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h index e1f0578a3..e994a8d9a 100644 --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -109,6 +109,7 @@ struct pa_bluetooth_device { bool tried_to_link_with_adapter; bool valid; bool autodetect_mtu; + bool codec_switching_in_progress; /* Device information */ char *path; @@ -117,6 +118,9 @@ struct pa_bluetooth_device { char *address; uint32_t class_of_device; pa_hashmap *uuids; /* char* -> char* (hashmap-as-a-set) */ + /* pa_a2dp_codec_id* -> pa_hashmap ( char* (remote endpoint) -> struct a2dp_codec_capabilities* ) */ + pa_hashmap *a2dp_sink_endpoints; + pa_hashmap *a2dp_source_endpoints; pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT]; @@ -170,6 +174,7 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_d pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook); const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile); +bool pa_bluetooth_switch_codec(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, pa_hashmap *capabilities_hashmap, const pa_a2dp_codec *a2dp_codec, void (*codec_switch_cb)(bool, pa_bluetooth_profile_t profile, void *), void *userdata); static inline bool pa_bluetooth_uuid_is_hsp_hs(const char *uuid) { return pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS_ALT); diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c index 9440084a6..e243f0b65 100644 --- a/src/modules/bluetooth/module-bluez5-device.c +++ b/src/modules/bluetooth/module-bluez5-device.c @@ -31,11 +31,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -1312,7 +1314,10 @@ static int init_profile(struct userdata *u) { pa_assert(u); pa_assert(u->profile != PA_BLUETOOTH_PROFILE_OFF); - if (setup_transport(u) < 0) + r = setup_transport(u); + if (r == -EINPROGRESS) + return 0; + else if (r < 0) return -1; pa_assert(u->transport); @@ -2079,7 +2084,12 @@ static void handle_transport_state_change(struct userdata *u, struct pa_bluetoot pa_assert_se(cp = pa_hashmap_get(u->card->profiles, pa_bluetooth_profile_to_string(t->profile))); oldavail = cp->available; - pa_card_profile_set_available(cp, transport_state_to_availability(t->state)); + /* + * If codec switching is in progress, transport state change should not + * make profile unavailable. + */ + if (!t->device->codec_switching_in_progress) + pa_card_profile_set_available(cp, transport_state_to_availability(t->state)); /* Update port availability */ pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name)); @@ -2149,7 +2159,7 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, pa_assert(d); pa_assert(u); - if (d != u->device || pa_bluetooth_device_any_transport_connected(d)) + if (d != u->device || pa_bluetooth_device_any_transport_connected(d) || d->codec_switching_in_progress) return PA_HOOK_OK; pa_log_debug("Unloading module for device %s", d->path); @@ -2227,6 +2237,144 @@ static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov return PA_HOOK_OK; } +static char* make_message_handler_path(const char *name) { + return pa_sprintf_malloc("/card/%s/bluez", name); +} + +static void switch_codec_cb_handler(bool success, pa_bluetooth_profile_t profile, void *userdata) +{ + struct userdata *u = (struct userdata *) userdata; + + if (!success) + goto off; + + u->profile = profile; + + if (init_profile(u) < 0) { + pa_log_info("Failed to initialise profile after codec switching"); + goto off; + } + + if (u->sink || u->source) + if (start_thread(u) < 0) { + pa_log_info("Failed to start thread after codec switching"); + goto off; + } + + pa_log_info("Codec successfully switched to %s with profile: %s", + u->a2dp_codec->name, pa_bluetooth_profile_to_string(u->profile)); + + return; + +off: + pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0); +} + +static int bluez5_device_message_handler(const char *object_path, const char *message, char *message_parameters, char **response, void *userdata) { + char *message_handler_path; + pa_hashmap *capabilities_hashmap; + pa_bluetooth_profile_t profile; + const pa_a2dp_codec *codec; + const char *codec_name; + struct userdata *u; + bool is_a2dp_sink; + void *state = NULL; + int err; + + pa_assert(u = (struct userdata *)userdata); + pa_assert(message); + pa_assert(response); + + message_handler_path = make_message_handler_path(u->card->name); + + if (!object_path || !pa_streq(object_path, message_handler_path)) { + pa_xfree(message_handler_path); + return -PA_ERR_NOENTITY; + } + + pa_xfree(message_handler_path); + + if (u->device->codec_switching_in_progress) { + pa_log_info("Codec switching operation already in progress"); + return -PA_ERR_INVALID; + } + + if (!u->device->adapter->application_registered) { + pa_log_info("Old BlueZ version was detected, only SBC codec supported."); + return -PA_ERR_NOTIMPLEMENTED; + } + + if (u->profile == PA_BLUETOOTH_PROFILE_OFF) { + pa_log_info("Bluetooth profile is off. Message cannot be handled."); + return -PA_ERR_INVALID; + } else if (u->profile != PA_BLUETOOTH_PROFILE_A2DP_SINK && + u->profile != PA_BLUETOOTH_PROFILE_A2DP_SOURCE) { + pa_log_info("Codec switching only allowed for A2DP sink or source"); + return -PA_ERR_INVALID; + } + + if (pa_streq(message, "switch-codec")) { + err = pa_message_params_read_string(message_parameters, &codec_name, &state); + if (err < 0) + return err; + + if (u->a2dp_codec && pa_streq(codec_name, u->a2dp_codec->name)) { + pa_log_info("Requested codec is currently selected codec"); + return -PA_ERR_INVALID; + } + + codec = pa_bluetooth_get_a2dp_codec(codec_name); + if (codec == NULL) { + pa_log_info("Invalid codec %s specified for switching", codec_name); + return -PA_ERR_INVALID; + } + + is_a2dp_sink = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK; + + /* + * We need to check if we have valid sink or source endpoints which + * were registered during the negotiation process. If we do, then we + * check if the specified codec is present among the codecs supported + * by the remote endpoint. + */ + if (pa_hashmap_isempty(is_a2dp_sink ? u->device->a2dp_sink_endpoints : u->device->a2dp_source_endpoints)) { + pa_log_info("No device endpoints found. Codec switching not allowed."); + return -PA_ERR_INVALID; + } + + capabilities_hashmap = pa_hashmap_get(is_a2dp_sink ? u->device->a2dp_sink_endpoints : u->device->a2dp_source_endpoints, &codec->id); + if (!capabilities_hashmap) { + pa_log_info("No remote endpoint found for %s codec. Codec not supported by remote endpoint.", + codec->name); + return -PA_ERR_INVALID; + } + + pa_log_info("Initiating codec switching process to %s", codec->name); + + /* + * The current profile needs to be saved before we stop the thread and + * initiate the switch. u->profile will be changed in other places + * depending on the state of transport and port availability. + */ + profile = u->profile; + + stop_thread(u); + + if (!pa_bluetooth_switch_codec(u->device, profile, capabilities_hashmap, codec, switch_codec_cb_handler, userdata) + && !u->device->codec_switching_in_progress) + goto profile_off; + + return PA_OK; + } + + return -PA_ERR_NOTIMPLEMENTED; + +profile_off: + pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0); + + return -PA_ERR_IO; +} + /* 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); @@ -2264,6 +2412,7 @@ int pa__init(pa_module* m) { const char *path; pa_modargs *ma; bool autodetect_mtu; + char *message_handler_path; pa_assert(m); @@ -2335,6 +2484,12 @@ int pa__init(pa_module* m) { if (start_thread(u) < 0) goto off; + message_handler_path = make_message_handler_path(u->card->name); + pa_message_handler_register(m->core, message_handler_path, "Bluez5 device message handler", + bluez5_device_message_handler, (void *) u); + pa_log_info("Bluez5 device message handler registered at path: %s", message_handler_path); + pa_xfree(message_handler_path); + return 0; off: @@ -2357,6 +2512,7 @@ fail: } void pa__done(pa_module *m) { + char *message_handler_path; struct userdata *u; pa_assert(m); @@ -2364,6 +2520,10 @@ void pa__done(pa_module *m) { if (!(u = m->userdata)) return; + message_handler_path = make_message_handler_path(u->card->name); + pa_message_handler_unregister(m->core, message_handler_path); + pa_xfree(message_handler_path); + stop_thread(u); if (u->device_connection_changed_slot) diff --git a/src/modules/bluetooth/module-bluez5-discover.c b/src/modules/bluetooth/module-bluez5-discover.c index 47b576103..f38031d13 100644 --- a/src/modules/bluetooth/module-bluez5-discover.c +++ b/src/modules/bluetooth/module-bluez5-discover.c @@ -62,7 +62,9 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, module_loaded = pa_hashmap_get(u->loaded_device_paths, d->path) ? true : false; - if (module_loaded && !pa_bluetooth_device_any_transport_connected(d)) { + /* When changing A2DP codec there is no transport connected, ensure that no module is unloaded */ + if (module_loaded && !pa_bluetooth_device_any_transport_connected(d) && + !d->codec_switching_in_progress) { /* disconnection, the module unloads itself */ pa_log_debug("Unregistering module for %s", d->path); pa_hashmap_remove(u->loaded_device_paths, d->path);