mirror of
				https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
				synced 2025-11-03 09:01:50 -05:00 
			
		
		
		
	bluetooth: Support A2DP codec switching via messaging API
This uses the messaging API to initiate a codec switch.
While a particular codec might be applicable only for a particular
profile, for eg. aptX can only be applicable for A2DP sink or source
and not for let's say HSP, the codec switching logic has not been
tied to the logic for switching profiles.
Codec can be switched by running the following on the command line.
pacmd send-message /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez switch-codec{"ldac_hq"}
pacmd send-message /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez switch-codec {"ldac_mq"}
pacmd send-message /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez switch-codec {"ldac_sq"}
pacmd send-message /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez switch-codec {"aptx_hd"}
pacmd send-message /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez switch-codec {"aptx"}
pacmd send-message /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez switch-codec {"sbc"}
Codec name passed above is matched against pa_a2dp_codec->name. Note that
the match is case sensitive. XX_XX_XX_XX_XX_XX needs to be substituted with
the actual bluetooth device id.
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/440>
			
			
This commit is contained in:
		
							parent
							
								
									5284b450e7
								
							
						
					
					
						commit
						3447335da9
					
				
					 4 changed files with 500 additions and 4 deletions
				
			
		| 
						 | 
					@ -287,6 +287,123 @@ static void device_start_waiting_for_profiles(pa_bluetooth_device *device) {
 | 
				
			||||||
                                                         wait_for_profiles_cb, 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) {
 | 
					void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_transport_state_t state) {
 | 
				
			||||||
    bool old_any_connected;
 | 
					    bool old_any_connected;
 | 
				
			||||||
    unsigned n_disconnected_profiles;
 | 
					    unsigned n_disconnected_profiles;
 | 
				
			||||||
| 
						 | 
					@ -537,6 +654,59 @@ static int parse_transport_properties(pa_bluetooth_transport *t, DBusMessageIter
 | 
				
			||||||
    return 0;
 | 
					    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) {
 | 
					static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char *path) {
 | 
				
			||||||
    pa_bluetooth_device *d;
 | 
					    pa_bluetooth_device *d;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -547,6 +717,8 @@ static pa_bluetooth_device* device_create(pa_bluetooth_discovery *y, const char
 | 
				
			||||||
    d->discovery = y;
 | 
					    d->discovery = y;
 | 
				
			||||||
    d->path = pa_xstrdup(path);
 | 
					    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->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);
 | 
					    pa_hashmap_put(y->devices, d->path, d);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -602,6 +774,10 @@ static void device_free(pa_bluetooth_device *d) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (d->uuids)
 | 
					    if (d->uuids)
 | 
				
			||||||
        pa_hashmap_free(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->path);
 | 
				
			||||||
    pa_xfree(d->alias);
 | 
					    pa_xfree(d->alias);
 | 
				
			||||||
| 
						 | 
					@ -996,6 +1172,7 @@ finish:
 | 
				
			||||||
        register_legacy_sbc_endpoint(y, a2dp_codec_sbc, path, A2DP_SOURCE_ENDPOINT "/sbc",
 | 
					        register_legacy_sbc_endpoint(y, a2dp_codec_sbc, path, A2DP_SOURCE_ENDPOINT "/sbc",
 | 
				
			||||||
                PA_BLUETOOTH_UUID_A2DP_SOURCE);
 | 
					                PA_BLUETOOTH_UUID_A2DP_SOURCE);
 | 
				
			||||||
        pa_log_warn("Only SBC codec is available for A2DP profiles");
 | 
					        pa_log_warn("Only SBC codec is available for A2DP profiles");
 | 
				
			||||||
 | 
					        a->application_registered = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pa_xfree(path);
 | 
					    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));
 | 
					    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) {
 | 
					static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessageIter *dict_i) {
 | 
				
			||||||
    DBusMessageIter element_i;
 | 
					    DBusMessageIter element_i;
 | 
				
			||||||
    const char *path;
 | 
					    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);
 | 
					            pa_log_debug("Device %s found", d->path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            parse_device_properties(d, &iface_i);
 | 
					            parse_device_properties(d, &iface_i);
 | 
				
			||||||
 | 
					        } else if (pa_streq(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE)) {
 | 
				
			||||||
 | 
					            parse_remote_endpoint_properties(y, path, &iface_i);
 | 
				
			||||||
        } else
 | 
					        } else
 | 
				
			||||||
            pa_log_debug("Unknown interface %s found, skipping", interface);
 | 
					            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);
 | 
					                device_remove(y, p);
 | 
				
			||||||
            else if (pa_streq(iface, BLUEZ_ADAPTER_INTERFACE))
 | 
					            else if (pa_streq(iface, BLUEZ_ADAPTER_INTERFACE))
 | 
				
			||||||
                adapter_remove(y, p);
 | 
					                adapter_remove(y, p);
 | 
				
			||||||
 | 
					            else if (pa_streq(iface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
 | 
				
			||||||
 | 
					                remote_endpoint_remove(y, p);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            dbus_message_iter_next(&element_i);
 | 
					            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;
 | 
					                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            parse_transport_properties(t, &arg_i);
 | 
					            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;
 | 
					        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -109,6 +109,7 @@ struct pa_bluetooth_device {
 | 
				
			||||||
    bool tried_to_link_with_adapter;
 | 
					    bool tried_to_link_with_adapter;
 | 
				
			||||||
    bool valid;
 | 
					    bool valid;
 | 
				
			||||||
    bool autodetect_mtu;
 | 
					    bool autodetect_mtu;
 | 
				
			||||||
 | 
					    bool codec_switching_in_progress;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* Device information */
 | 
					    /* Device information */
 | 
				
			||||||
    char *path;
 | 
					    char *path;
 | 
				
			||||||
| 
						 | 
					@ -117,6 +118,9 @@ struct pa_bluetooth_device {
 | 
				
			||||||
    char *address;
 | 
					    char *address;
 | 
				
			||||||
    uint32_t class_of_device;
 | 
					    uint32_t class_of_device;
 | 
				
			||||||
    pa_hashmap *uuids; /* char* -> char* (hashmap-as-a-set) */
 | 
					    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];
 | 
					    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);
 | 
					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);
 | 
					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) {
 | 
					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);
 | 
					    return pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS_ALT);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,11 +31,13 @@
 | 
				
			||||||
#include <pulse/timeval.h>
 | 
					#include <pulse/timeval.h>
 | 
				
			||||||
#include <pulse/utf8.h>
 | 
					#include <pulse/utf8.h>
 | 
				
			||||||
#include <pulse/util.h>
 | 
					#include <pulse/util.h>
 | 
				
			||||||
 | 
					#include <pulse/message-params.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <pulsecore/core-error.h>
 | 
					#include <pulsecore/core-error.h>
 | 
				
			||||||
#include <pulsecore/core-rtclock.h>
 | 
					#include <pulsecore/core-rtclock.h>
 | 
				
			||||||
#include <pulsecore/core-util.h>
 | 
					#include <pulsecore/core-util.h>
 | 
				
			||||||
#include <pulsecore/i18n.h>
 | 
					#include <pulsecore/i18n.h>
 | 
				
			||||||
 | 
					#include <pulsecore/message-handler.h>
 | 
				
			||||||
#include <pulsecore/module.h>
 | 
					#include <pulsecore/module.h>
 | 
				
			||||||
#include <pulsecore/modargs.h>
 | 
					#include <pulsecore/modargs.h>
 | 
				
			||||||
#include <pulsecore/poll.h>
 | 
					#include <pulsecore/poll.h>
 | 
				
			||||||
| 
						 | 
					@ -1312,7 +1314,10 @@ static int init_profile(struct userdata *u) {
 | 
				
			||||||
    pa_assert(u);
 | 
					    pa_assert(u);
 | 
				
			||||||
    pa_assert(u->profile != PA_BLUETOOTH_PROFILE_OFF);
 | 
					    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;
 | 
					        return -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pa_assert(u->transport);
 | 
					    pa_assert(u->transport);
 | 
				
			||||||
| 
						 | 
					@ -2079,6 +2084,11 @@ 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)));
 | 
					    pa_assert_se(cp = pa_hashmap_get(u->card->profiles, pa_bluetooth_profile_to_string(t->profile)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    oldavail = cp->available;
 | 
					    oldavail = cp->available;
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					     * 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));
 | 
					        pa_card_profile_set_available(cp, transport_state_to_availability(t->state));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* Update port availability */
 | 
					    /* Update port availability */
 | 
				
			||||||
| 
						 | 
					@ -2149,7 +2159,7 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
 | 
				
			||||||
    pa_assert(d);
 | 
					    pa_assert(d);
 | 
				
			||||||
    pa_assert(u);
 | 
					    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;
 | 
					        return PA_HOOK_OK;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pa_log_debug("Unloading module for device %s", d->path);
 | 
					    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;
 | 
					    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 */
 | 
					/* Run from main thread context */
 | 
				
			||||||
static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
 | 
					static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
 | 
				
			||||||
    struct bluetooth_msg *m = BLUETOOTH_MSG(obj);
 | 
					    struct bluetooth_msg *m = BLUETOOTH_MSG(obj);
 | 
				
			||||||
| 
						 | 
					@ -2264,6 +2412,7 @@ int pa__init(pa_module* m) {
 | 
				
			||||||
    const char *path;
 | 
					    const char *path;
 | 
				
			||||||
    pa_modargs *ma;
 | 
					    pa_modargs *ma;
 | 
				
			||||||
    bool autodetect_mtu;
 | 
					    bool autodetect_mtu;
 | 
				
			||||||
 | 
					    char *message_handler_path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pa_assert(m);
 | 
					    pa_assert(m);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2335,6 +2484,12 @@ int pa__init(pa_module* m) {
 | 
				
			||||||
        if (start_thread(u) < 0)
 | 
					        if (start_thread(u) < 0)
 | 
				
			||||||
            goto off;
 | 
					            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;
 | 
					    return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
off:
 | 
					off:
 | 
				
			||||||
| 
						 | 
					@ -2357,6 +2512,7 @@ fail:
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void pa__done(pa_module *m) {
 | 
					void pa__done(pa_module *m) {
 | 
				
			||||||
 | 
					    char *message_handler_path;
 | 
				
			||||||
    struct userdata *u;
 | 
					    struct userdata *u;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pa_assert(m);
 | 
					    pa_assert(m);
 | 
				
			||||||
| 
						 | 
					@ -2364,6 +2520,10 @@ void pa__done(pa_module *m) {
 | 
				
			||||||
    if (!(u = m->userdata))
 | 
					    if (!(u = m->userdata))
 | 
				
			||||||
        return;
 | 
					        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);
 | 
					    stop_thread(u);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (u->device_connection_changed_slot)
 | 
					    if (u->device_connection_changed_slot)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
					    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 */
 | 
					        /* disconnection, the module unloads itself */
 | 
				
			||||||
        pa_log_debug("Unregistering module for %s", d->path);
 | 
					        pa_log_debug("Unregistering module for %s", d->path);
 | 
				
			||||||
        pa_hashmap_remove(u->loaded_device_paths, d->path);
 | 
					        pa_hashmap_remove(u->loaded_device_paths, d->path);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue