bluetooth: Modular API for A2DP codecs

This patch introduce new modular API for bluetooth A2DP codecs. Its
benefits are:

* bluez5-util and module-bluez5-device does not contain any codec specific
  code, they are codec independent.

* For adding new A2DP codec it is needed just to adjust one table in
  a2dp-codec-util.c file. All codec specific functions are in separate
  codec file.

* Support for backchannel (microphone voice). Some A2DP codecs (like
  FastStream or aptX Low Latency) are bi-directional and can be used for
  both music playback and audio call.

* Support for more configurations per codec. This allows to implement low
  quality mode of some codec together with high quality.

Current SBC codec implementation was moved from bluez5-util and
module-bluez5-device to its own file and converted to this new A2DP API.
This commit is contained in:
Pali Rohár 2019-04-06 11:15:58 +02:00 committed by Tanu Kaskinen
parent e8c4638f79
commit 106aa91477
8 changed files with 1127 additions and 622 deletions

View file

@ -2,6 +2,7 @@
This file is part of PulseAudio.
Copyright 2008-2013 João Paulo Rechi Vita
Copyrigth 2018-2019 Pali Rohár <pali.rohar@gmail.com>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@ -33,6 +34,7 @@
#include <pulsecore/refcnt.h>
#include <pulsecore/shared.h>
#include "a2dp-codec-util.h"
#include "a2dp-codecs.h"
#include "bluez5-util.h"
@ -885,13 +887,19 @@ finish:
pa_xfree(endpoint);
}
static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) {
static void register_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_codec *a2dp_codec, const char *path, const char *endpoint, const char *uuid) {
DBusMessage *m;
DBusMessageIter i, d;
uint8_t codec = 0;
uint8_t capabilities[MAX_A2DP_CAPS_SIZE];
size_t capabilities_size;
uint8_t codec_id;
pa_log_debug("Registering %s on adapter %s", endpoint, path);
codec_id = a2dp_codec->id.codec_id;
capabilities_size = a2dp_codec->fill_capabilities(capabilities);
pa_assert(capabilities_size != 0);
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, path, BLUEZ_MEDIA_INTERFACE, "RegisterEndpoint"));
dbus_message_iter_init_append(m, &i);
@ -899,23 +907,8 @@ static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const
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);
pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid);
pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec);
if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE) || pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK)) {
a2dp_sbc_t capabilities;
capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO |
SBC_CHANNEL_MODE_JOINT_STEREO;
capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 |
SBC_SAMPLING_FREQ_48000;
capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS;
capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8;
capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16;
capabilities.min_bitpool = SBC_MIN_BITPOOL;
capabilities.max_bitpool = 64;
pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities));
}
pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec_id);
pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, capabilities_size);
dbus_message_iter_close_container(&i, &d);
@ -950,6 +943,7 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
if (pa_streq(interface, BLUEZ_ADAPTER_INTERFACE)) {
pa_bluetooth_adapter *a;
unsigned a2dp_codec_i;
if ((a = pa_hashmap_get(y->adapters, path))) {
pa_log_error("Found duplicated D-Bus path for adapter %s", path);
@ -964,8 +958,20 @@ static void parse_interfaces_and_properties(pa_bluetooth_discovery *y, DBusMessa
if (!a->valid)
return;
register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SOURCE);
register_endpoint(y, path, A2DP_SINK_ENDPOINT, PA_BLUETOOTH_UUID_A2DP_SINK);
/* Order is important. bluez prefers endpoints registered earlier.
* And codec with higher number has higher priority. So iterate in reverse order. */
for (a2dp_codec_i = pa_bluetooth_a2dp_codec_count(); a2dp_codec_i > 0; a2dp_codec_i--) {
const pa_a2dp_codec *a2dp_codec = pa_bluetooth_a2dp_codec_iter(a2dp_codec_i-1);
char *endpoint;
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
register_endpoint(y, a2dp_codec, path, endpoint, PA_BLUETOOTH_UUID_A2DP_SINK);
pa_xfree(endpoint);
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
register_endpoint(y, a2dp_codec, path, endpoint, PA_BLUETOOTH_UUID_A2DP_SOURCE);
pa_xfree(endpoint);
}
} else if (pa_streq(interface, BLUEZ_DEVICE_INTERFACE)) {
@ -1257,48 +1263,6 @@ fail:
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
/* These bitpool values were chosen based on the A2DP spec recommendation */
switch (freq) {
case SBC_SAMPLING_FREQ_16000:
case SBC_SAMPLING_FREQ_32000:
return 53;
case SBC_SAMPLING_FREQ_44100:
switch (mode) {
case SBC_CHANNEL_MODE_MONO:
case SBC_CHANNEL_MODE_DUAL_CHANNEL:
return 31;
case SBC_CHANNEL_MODE_STEREO:
case SBC_CHANNEL_MODE_JOINT_STEREO:
return 53;
}
pa_log_warn("Invalid channel mode %u", mode);
return 53;
case SBC_SAMPLING_FREQ_48000:
switch (mode) {
case SBC_CHANNEL_MODE_MONO:
case SBC_CHANNEL_MODE_DUAL_CHANNEL:
return 29;
case SBC_CHANNEL_MODE_STEREO:
case SBC_CHANNEL_MODE_JOINT_STEREO:
return 51;
}
pa_log_warn("Invalid channel mode %u", mode);
return 51;
}
pa_log_warn("Invalid sampling freq %u", freq);
return 53;
}
const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
switch(profile) {
case PA_BLUETOOTH_PROFILE_A2DP_SINK:
@ -1316,10 +1280,24 @@ const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
return NULL;
}
static const pa_a2dp_codec *a2dp_endpoint_to_a2dp_codec(const char *endpoint) {
const char *codec_name;
if (pa_startswith(endpoint, A2DP_SINK_ENDPOINT "/"))
codec_name = endpoint + strlen(A2DP_SINK_ENDPOINT "/");
else if (pa_startswith(endpoint, A2DP_SOURCE_ENDPOINT "/"))
codec_name = endpoint + strlen(A2DP_SOURCE_ENDPOINT "/");
else
return NULL;
return pa_bluetooth_get_a2dp_codec(codec_name);
}
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 pa_a2dp_codec *a2dp_codec = NULL;
const char *sender, *path, *endpoint_path, *dev_path = NULL, *uuid = NULL;
const uint8_t *config = NULL;
int size = 0;
@ -1345,6 +1323,8 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
goto fail;
endpoint_path = dbus_message_get_path(m);
/* Read transport properties */
while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) {
const char *key;
@ -1367,16 +1347,13 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
dbus_message_iter_get_basic(&value, &uuid);
endpoint_path = dbus_message_get_path(m);
if (pa_streq(endpoint_path, A2DP_SOURCE_ENDPOINT)) {
if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
p = PA_BLUETOOTH_PROFILE_A2DP_SINK;
} else if (pa_streq(endpoint_path, A2DP_SINK_ENDPOINT)) {
if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK))
p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
}
if (pa_startswith(endpoint_path, A2DP_SINK_ENDPOINT "/"))
p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
else if (pa_startswith(endpoint_path, A2DP_SOURCE_ENDPOINT "/"))
p = PA_BLUETOOTH_PROFILE_A2DP_SINK;
if (p == PA_BLUETOOTH_PROFILE_OFF) {
if ((pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE) && p != PA_BLUETOOTH_PROFILE_A2DP_SINK) ||
(pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SINK) && p != PA_BLUETOOTH_PROFILE_A2DP_SOURCE)) {
pa_log_error("UUID %s of transport %s incompatible with endpoint %s", uuid, path, endpoint_path);
goto fail;
}
@ -1389,7 +1366,6 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
dbus_message_iter_get_basic(&value, &dev_path);
} else if (pa_streq(key, "Configuration")) {
DBusMessageIter array;
a2dp_sbc_t *c;
if (var != DBUS_TYPE_ARRAY) {
pa_log_error("Property %s of wrong type %c", key, (char)var);
@ -1404,45 +1380,20 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
}
dbus_message_iter_get_fixed_array(&array, &config, &size);
if (size != sizeof(a2dp_sbc_t)) {
pa_log_error("Configuration array of invalid size");
goto fail;
}
c = (a2dp_sbc_t *) config;
a2dp_codec = a2dp_endpoint_to_a2dp_codec(endpoint_path);
pa_assert(a2dp_codec);
if (c->frequency != SBC_SAMPLING_FREQ_16000 && c->frequency != SBC_SAMPLING_FREQ_32000 &&
c->frequency != SBC_SAMPLING_FREQ_44100 && c->frequency != SBC_SAMPLING_FREQ_48000) {
pa_log_error("Invalid sampling frequency in configuration");
if (!a2dp_codec->is_configuration_valid(config, size))
goto fail;
}
if (c->channel_mode != SBC_CHANNEL_MODE_MONO && c->channel_mode != SBC_CHANNEL_MODE_DUAL_CHANNEL &&
c->channel_mode != SBC_CHANNEL_MODE_STEREO && c->channel_mode != SBC_CHANNEL_MODE_JOINT_STEREO) {
pa_log_error("Invalid channel mode in configuration");
goto fail;
}
if (c->allocation_method != SBC_ALLOCATION_SNR && c->allocation_method != SBC_ALLOCATION_LOUDNESS) {
pa_log_error("Invalid allocation method in configuration");
goto fail;
}
if (c->subbands != SBC_SUBBANDS_4 && c->subbands != SBC_SUBBANDS_8) {
pa_log_error("Invalid SBC subbands in configuration");
goto fail;
}
if (c->block_length != SBC_BLOCK_LENGTH_4 && c->block_length != SBC_BLOCK_LENGTH_8 &&
c->block_length != SBC_BLOCK_LENGTH_12 && c->block_length != SBC_BLOCK_LENGTH_16) {
pa_log_error("Invalid block length in configuration");
goto fail;
}
}
dbus_message_iter_next(&props);
}
if (!a2dp_codec)
goto fail2;
if ((d = pa_hashmap_get(y->devices, dev_path))) {
if (!d->valid) {
pa_log_error("Information about device %s is invalid", dev_path);
@ -1466,6 +1417,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
dbus_message_unref(r);
t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
t->a2dp_codec = a2dp_codec;
t->acquire = bluez5_transport_acquire_cb;
t->release = bluez5_transport_release_cb;
pa_bluetooth_transport_put(t);
@ -1484,21 +1436,17 @@ fail2:
static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
pa_bluetooth_discovery *y = userdata;
a2dp_sbc_t *cap, config;
uint8_t *pconf = (uint8_t *) &config;
int i, size;
const char *endpoint_path;
uint8_t *cap;
int size;
const pa_a2dp_codec *a2dp_codec;
uint8_t config[MAX_A2DP_CAPS_SIZE];
uint8_t *config_ptr = config;
size_t config_size;
DBusMessage *r;
DBusError err;
static const struct {
uint32_t rate;
uint8_t cap;
} freq_table[] = {
{ 16000U, SBC_SAMPLING_FREQ_16000 },
{ 32000U, SBC_SAMPLING_FREQ_32000 },
{ 44100U, SBC_SAMPLING_FREQ_44100 },
{ 48000U, SBC_SAMPLING_FREQ_48000 }
};
endpoint_path = dbus_message_get_path(m);
dbus_error_init(&err);
@ -1508,101 +1456,15 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMess
goto fail;
}
if (size != sizeof(config)) {
pa_log_error("Capabilities array has invalid size");
goto fail;
}
a2dp_codec = a2dp_endpoint_to_a2dp_codec(endpoint_path);
pa_assert(a2dp_codec);
pa_zero(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_error("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 & SBC_CHANNEL_MODE_MONO)
config.channel_mode = SBC_CHANNEL_MODE_MONO;
else if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
config.channel_mode = SBC_CHANNEL_MODE_STEREO;
else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
else {
pa_log_error("No supported channel modes");
goto fail;
}
}
if (y->core->default_sample_spec.channels >= 2) {
if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO)
config.channel_mode = SBC_CHANNEL_MODE_STEREO;
else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO)
config.channel_mode = SBC_CHANNEL_MODE_MONO;
else {
pa_log_error("No supported channel modes");
goto fail;
}
}
if (cap->block_length & SBC_BLOCK_LENGTH_16)
config.block_length = SBC_BLOCK_LENGTH_16;
else if (cap->block_length & SBC_BLOCK_LENGTH_12)
config.block_length = SBC_BLOCK_LENGTH_12;
else if (cap->block_length & SBC_BLOCK_LENGTH_8)
config.block_length = SBC_BLOCK_LENGTH_8;
else if (cap->block_length & SBC_BLOCK_LENGTH_4)
config.block_length = SBC_BLOCK_LENGTH_4;
else {
pa_log_error("No supported block lengths");
goto fail;
}
if (cap->subbands & SBC_SUBBANDS_8)
config.subbands = SBC_SUBBANDS_8;
else if (cap->subbands & SBC_SUBBANDS_4)
config.subbands = SBC_SUBBANDS_4;
else {
pa_log_error("No supported subbands");
goto fail;
}
if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS)
config.allocation_method = SBC_ALLOCATION_LOUDNESS;
else if (cap->allocation_method & SBC_ALLOCATION_SNR)
config.allocation_method = SBC_ALLOCATION_SNR;
config.min_bitpool = (uint8_t) PA_MAX(SBC_MIN_BITPOOL, cap->min_bitpool);
config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool);
if (config.min_bitpool > config.max_bitpool)
config_size = a2dp_codec->fill_preferred_configuration(&y->core->default_sample_spec, cap, size, config);
if (config_size == 0)
goto fail;
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));
pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &config_ptr, config_size, DBUS_TYPE_INVALID));
return r;
@ -1677,7 +1539,7 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT))
if (!a2dp_endpoint_to_a2dp_codec(path))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
@ -1705,49 +1567,32 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
return DBUS_HANDLER_RESULT_HANDLED;
}
static void endpoint_init(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile) {
static void endpoint_init(pa_bluetooth_discovery *y, const char *endpoint) {
static const DBusObjectPathVTable vtable_endpoint = {
.message_function = endpoint_handler,
};
pa_assert(y);
pa_assert(endpoint);
switch(profile) {
case PA_BLUETOOTH_PROFILE_A2DP_SINK:
pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT,
&vtable_endpoint, y));
break;
case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT,
&vtable_endpoint, y));
break;
default:
pa_assert_not_reached();
break;
}
pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), endpoint,
&vtable_endpoint, y));
}
static void endpoint_done(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile) {
static void endpoint_done(pa_bluetooth_discovery *y, const char *endpoint) {
pa_assert(y);
pa_assert(endpoint);
switch(profile) {
case PA_BLUETOOTH_PROFILE_A2DP_SINK:
dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT);
break;
case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT);
break;
default:
pa_assert_not_reached();
break;
}
dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), endpoint);
}
pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend) {
pa_bluetooth_discovery *y;
DBusError err;
DBusConnection *conn;
unsigned i;
unsigned i, count;
const pa_a2dp_codec *a2dp_codec;
char *endpoint;
y = pa_xnew0(pa_bluetooth_discovery, 1);
PA_REFCNT_INIT(y);
@ -1799,8 +1644,18 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
}
y->matches_added = true;
endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
count = pa_bluetooth_a2dp_codec_count();
for (i = 0; i < count; i++) {
a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
endpoint_init(y, endpoint);
pa_xfree(endpoint);
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
endpoint_init(y, endpoint);
pa_xfree(endpoint);
}
get_managed_objects(y);
@ -1823,6 +1678,10 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
}
void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
unsigned i, count;
const pa_a2dp_codec *a2dp_codec;
char *endpoint;
pa_assert(y);
pa_assert(PA_REFCNT_VALUE(y) > 0);
@ -1868,8 +1727,18 @@ 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);
endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
endpoint_done(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
count = pa_bluetooth_a2dp_codec_count();
for (i = 0; i < count; i++) {
a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
endpoint_done(y, endpoint);
pa_xfree(endpoint);
endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
endpoint_done(y, endpoint);
pa_xfree(endpoint);
}
pa_dbus_connection_unref(y->connection);
}