mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
bluez5: basic codec-switching framework
This commit is contained in:
parent
0908588d0c
commit
e860f2bb4e
7 changed files with 537 additions and 14 deletions
|
|
@ -346,6 +346,14 @@ struct a2dp_codec {
|
|||
const void *caps, size_t caps_size,
|
||||
struct spa_audio_info *info);
|
||||
|
||||
/** qsort comparison sorting caps in order of preference for the codec.
|
||||
* Used in codec switching to select best remote endpoints.
|
||||
* The caps handed in correspond to this codec_id, but are
|
||||
* otherwise not checked beforehand.
|
||||
*/
|
||||
int (*caps_preference_cmp) (const struct a2dp_codec *codec, const void *caps1, size_t caps1_size,
|
||||
const void *caps2, size_t caps2_size);
|
||||
|
||||
void *(*init) (const struct a2dp_codec *codec, uint32_t flags, void *config, size_t config_size,
|
||||
const struct spa_audio_info *info, const struct spa_dict *settings, size_t mtu);
|
||||
void (*deinit) (void *data);
|
||||
|
|
|
|||
|
|
@ -1118,6 +1118,7 @@ static DBusHandlerResult hsphfpd_parse_endpoint_properties(struct spa_bt_backend
|
|||
else
|
||||
t->codec = HFP_AUDIO_CODEC_CVSD;
|
||||
|
||||
t->enabled = true;
|
||||
spa_bt_device_connect_profile(t->device, t->profile);
|
||||
|
||||
spa_log_debug(backend->log, NAME": Transport %s available for hsphfpd", endpoint->path);
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ static struct spa_bt_transport *_transport_create(struct rfcomm *rfcomm)
|
|||
spa_list_append(&t->device->transport_list, &t->device_link);
|
||||
t->profile = rfcomm->profile;
|
||||
t->backend = backend;
|
||||
t->enabled = true;
|
||||
|
||||
spa_bt_transport_add_listener(t, &rfcomm->transport_listener, &transport_events, rfcomm);
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ static struct spa_bt_transport *_transport_create(struct spa_bt_backend *backend
|
|||
t->backend = backend;
|
||||
t->profile = profile;
|
||||
t->codec = codec;
|
||||
t->enabled = true;
|
||||
|
||||
finish:
|
||||
return t;
|
||||
|
|
|
|||
|
|
@ -69,6 +69,10 @@ struct spa_bt_monitor {
|
|||
uint32_t count;
|
||||
uint32_t id;
|
||||
|
||||
/*
|
||||
* Lists of BlueZ objects, kept up-to-date by following DBus events
|
||||
* initiated by BlueZ. Object lifetime is also determined by that.
|
||||
*/
|
||||
struct spa_list adapter_list;
|
||||
struct spa_list device_list;
|
||||
struct spa_list remote_endpoint_list;
|
||||
|
|
@ -101,6 +105,34 @@ struct spa_bt_remote_endpoint {
|
|||
bool delay_reporting;
|
||||
};
|
||||
|
||||
/*
|
||||
* Codec switching tries various codec/remote endpoint combinations
|
||||
* in order, until an acceptable one is found. This triggers BlueZ
|
||||
* to initiate DBus calls that result to the creation of a transport
|
||||
* with the desired capabilities.
|
||||
* The codec switch struct tracks candidates still to be tried.
|
||||
*/
|
||||
struct spa_bt_a2dp_codec_switch {
|
||||
struct spa_bt_device *device;
|
||||
struct spa_list device_link;
|
||||
|
||||
DBusPendingCall *pending;
|
||||
|
||||
uint32_t profile;
|
||||
|
||||
/*
|
||||
* Called asynchronously, so endpoint paths instead of pointers (which may be
|
||||
* invalidated in the meantime).
|
||||
*/
|
||||
const struct a2dp_codec **codecs;
|
||||
char **paths;
|
||||
|
||||
const struct a2dp_codec **codec_iter; /**< outer iterator over codecs */
|
||||
char **path_iter; /**< inner iterator over endpoint paths */
|
||||
|
||||
size_t num_paths;
|
||||
};
|
||||
|
||||
/*
|
||||
* SCO socket connect may fail with ECONNABORTED if it is done too soon after
|
||||
* previous close. To avoid this in cases where nodes are toggled between
|
||||
|
|
@ -389,6 +421,9 @@ static struct spa_bt_device *device_create(struct spa_bt_monitor *monitor, const
|
|||
d->path = strdup(path);
|
||||
spa_list_init(&d->remote_endpoint_list);
|
||||
spa_list_init(&d->transport_list);
|
||||
spa_list_init(&d->codec_switch_list);
|
||||
|
||||
spa_hook_list_init(&d->listener_list);
|
||||
|
||||
spa_list_prepend(&monitor->device_list, &d->link);
|
||||
|
||||
|
|
@ -397,9 +432,12 @@ static struct spa_bt_device *device_create(struct spa_bt_monitor *monitor, const
|
|||
|
||||
static int device_stop_timer(struct spa_bt_device *device);
|
||||
|
||||
static void a2dp_codec_switch_free(struct spa_bt_a2dp_codec_switch *sw);
|
||||
|
||||
static void device_free(struct spa_bt_device *device)
|
||||
{
|
||||
struct spa_bt_remote_endpoint *ep;
|
||||
struct spa_bt_a2dp_codec_switch *sw;
|
||||
struct spa_bt_transport *t;
|
||||
struct spa_bt_monitor *monitor = device->monitor;
|
||||
|
||||
|
|
@ -419,6 +457,10 @@ static void device_free(struct spa_bt_device *device)
|
|||
t->device = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
spa_list_consume(sw, &device->codec_switch_list, device_link)
|
||||
a2dp_codec_switch_free(sw);
|
||||
|
||||
spa_list_remove(&device->link);
|
||||
free(device->path);
|
||||
free(device->alias);
|
||||
|
|
@ -1335,6 +1377,362 @@ static const struct spa_bt_transport_implementation transport_impl = {
|
|||
.release = transport_release,
|
||||
};
|
||||
|
||||
static void append_basic_array_variant_dict_entry(DBusMessageIter *dict, int key_type_int, void* key, const char* variant_type_str, const char* array_type_str, int array_type_int, void* data, int data_size);
|
||||
|
||||
static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *userdata);
|
||||
|
||||
static int a2dp_codec_switch_cmp(const void *a, const void *b);
|
||||
|
||||
static struct spa_bt_a2dp_codec_switch *a2dp_codec_switch_cmp_sw; /* global for qsort */
|
||||
|
||||
static void a2dp_codec_switch_free(struct spa_bt_a2dp_codec_switch *sw)
|
||||
{
|
||||
char **p;
|
||||
|
||||
if (sw->pending != NULL) {
|
||||
dbus_pending_call_cancel(sw->pending);
|
||||
dbus_pending_call_unref(sw->pending);
|
||||
}
|
||||
|
||||
if (sw->device != NULL)
|
||||
spa_list_remove(&sw->device_link);
|
||||
|
||||
if (sw->paths != NULL)
|
||||
for (p = sw->paths; *p != NULL; ++p)
|
||||
free(*p);
|
||||
|
||||
free(sw->paths);
|
||||
free(sw->codecs);
|
||||
free(sw);
|
||||
}
|
||||
|
||||
static void a2dp_codec_switch_next(struct spa_bt_a2dp_codec_switch *sw)
|
||||
{
|
||||
spa_assert(*sw->codec_iter != NULL && *sw->path_iter != NULL);
|
||||
|
||||
++sw->path_iter;
|
||||
if (*sw->path_iter == NULL) {
|
||||
++sw->codec_iter;
|
||||
sw->path_iter = sw->paths;
|
||||
}
|
||||
}
|
||||
|
||||
static bool a2dp_codec_switch_process_current(struct spa_bt_a2dp_codec_switch *sw)
|
||||
{
|
||||
struct spa_bt_remote_endpoint *ep;
|
||||
const struct a2dp_codec *codec;
|
||||
uint8_t config[A2DP_MAX_CAPS_SIZE];
|
||||
char *local_endpoint_base;
|
||||
char *local_endpoint = NULL;
|
||||
int res, config_size;
|
||||
dbus_bool_t dbus_ret;
|
||||
const char *str;
|
||||
DBusMessage *m;
|
||||
DBusMessageIter iter, d;
|
||||
int i;
|
||||
|
||||
/* Try setting configuration for current codec on current endpoint in list */
|
||||
|
||||
codec = *sw->codec_iter;
|
||||
|
||||
spa_log_debug(sw->device->monitor->log, NAME": a2dp codec switch %p: consider codec %s for remote endpoint %s",
|
||||
sw, (*sw->codec_iter)->name, *sw->path_iter);
|
||||
|
||||
ep = device_remote_endpoint_find(sw->device, *sw->path_iter);
|
||||
|
||||
if (ep == NULL || ep->capabilities == NULL || ep->uuid == NULL) {
|
||||
spa_log_debug(sw->device->monitor->log, NAME": a2dp codec switch %p: endpoint %s not valid, try next",
|
||||
*sw->path_iter);
|
||||
goto next;
|
||||
}
|
||||
|
||||
/* Setup and check compatible configuration */
|
||||
if (ep->codec != codec->codec_id) {
|
||||
spa_log_debug(sw->device->monitor->log, NAME": a2dp codec switch %p: different codec, try next", sw);
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (!(sw->profile & spa_bt_profile_from_uuid(ep->uuid))) {
|
||||
spa_log_debug(sw->device->monitor->log, NAME": a2dp codec switch %p: wrong uuid (%s) for profile, try next",
|
||||
sw, ep->uuid);
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (sw->profile & SPA_BT_PROFILE_A2DP_SINK) {
|
||||
local_endpoint_base = A2DP_SOURCE_ENDPOINT;
|
||||
} else if (sw->profile & SPA_BT_PROFILE_A2DP_SOURCE) {
|
||||
local_endpoint_base = A2DP_SINK_ENDPOINT;
|
||||
} else {
|
||||
spa_log_debug(sw->device->monitor->log, NAME": a2dp codec switch %p: bad profile (%d), try next",
|
||||
sw, sw->profile);
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (a2dp_codec_to_endpoint(codec, local_endpoint_base, &local_endpoint) < 0) {
|
||||
spa_log_debug(sw->device->monitor->log, NAME": a2dp codec switch %p: no endpoint for codec %s, try next",
|
||||
sw, codec->name);
|
||||
goto next;
|
||||
}
|
||||
|
||||
res = codec->select_config(codec, 0, ep->capabilities, ep->capabilities_len, NULL, config);
|
||||
if (res < 0) {
|
||||
spa_log_debug(sw->device->monitor->log, NAME": a2dp codec switch %p: incompatible capabilities (%d), try next",
|
||||
sw, res);
|
||||
goto next;
|
||||
}
|
||||
config_size = res;
|
||||
|
||||
spa_log_debug(sw->device->monitor->log, NAME": a2dp codec switch %p: configuration %d", sw, config_size);
|
||||
for (i = 0; i < config_size; i++)
|
||||
spa_log_debug(sw->device->monitor->log, NAME": a2dp codec switch %p: %d: %02x", sw, i, config[i]);
|
||||
|
||||
/* org.bluez.MediaEndpoint1.SetConfiguration on remote endpoint */
|
||||
m = dbus_message_new_method_call(BLUEZ_SERVICE, ep->path, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SetConfiguration");
|
||||
if (m == NULL) {
|
||||
spa_log_debug(sw->device->monitor->log, NAME": a2dp codec switch %p: dbus allocation failure, try next", sw);
|
||||
goto next;
|
||||
}
|
||||
|
||||
spa_log_info(sw->device->monitor->log, NAME": a2dp codec switch %p: trying codec %s for endpoint %s, local endpoint %s",
|
||||
sw, codec->name, ep->path, local_endpoint);
|
||||
|
||||
dbus_message_iter_init_append(m, &iter);
|
||||
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &local_endpoint);
|
||||
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &d);
|
||||
str = "Capabilities";
|
||||
append_basic_array_variant_dict_entry(&d, DBUS_TYPE_STRING, &str, "ay", "y", DBUS_TYPE_BYTE, config, config_size);
|
||||
dbus_message_iter_close_container(&iter, &d);
|
||||
|
||||
spa_assert(sw->pending == NULL);
|
||||
dbus_ret = dbus_connection_send_with_reply(sw->device->monitor->conn, m, &sw->pending, -1);
|
||||
|
||||
if (!dbus_ret || sw->pending == NULL) {
|
||||
spa_log_error(sw->device->monitor->log, NAME": a2dp codec switch %p: dbus call failure, try next", sw);
|
||||
dbus_message_unref(m);
|
||||
goto next;
|
||||
}
|
||||
|
||||
dbus_ret = dbus_pending_call_set_notify(sw->pending, a2dp_codec_switch_reply, sw, NULL);
|
||||
dbus_message_unref(m);
|
||||
|
||||
if (!dbus_ret) {
|
||||
spa_log_error(sw->device->monitor->log, NAME": a2dp codec switch %p: dbus set notify failure", sw);
|
||||
goto next;
|
||||
}
|
||||
|
||||
free(local_endpoint);
|
||||
return true;
|
||||
|
||||
next:
|
||||
free(local_endpoint);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void a2dp_codec_switch_process(struct spa_bt_a2dp_codec_switch *sw)
|
||||
{
|
||||
while (*sw->codec_iter != NULL && *sw->path_iter != NULL) {
|
||||
if (sw->path_iter == sw->paths && (*sw->codec_iter)->caps_preference_cmp) {
|
||||
/* Sort endpoints according to codec preference, when at a new codec. */
|
||||
a2dp_codec_switch_cmp_sw = sw;
|
||||
qsort(sw->paths, sw->num_paths, sizeof(char *), a2dp_codec_switch_cmp);
|
||||
}
|
||||
|
||||
if (a2dp_codec_switch_process_current(sw)) {
|
||||
/* Wait for dbus reply */
|
||||
return;
|
||||
}
|
||||
|
||||
a2dp_codec_switch_next(sw);
|
||||
};
|
||||
|
||||
/* Didn't find any suitable endpoint. Report failure. */
|
||||
spa_log_info(sw->device->monitor->log, NAME": a2dp codec switch %p: failed to get an endpoint", sw);
|
||||
sw->device->active_codec_switch = NULL;
|
||||
spa_bt_device_emit_codec_switched(sw->device, -ENODEV);
|
||||
a2dp_codec_switch_free(sw);
|
||||
}
|
||||
|
||||
static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *user_data)
|
||||
{
|
||||
struct spa_bt_a2dp_codec_switch *sw = user_data;
|
||||
struct spa_bt_device *device = sw->device;
|
||||
DBusMessage *r;
|
||||
|
||||
r = dbus_pending_call_steal_reply(pending);
|
||||
|
||||
spa_assert(sw->pending == pending);
|
||||
dbus_pending_call_unref(pending);
|
||||
sw->pending = NULL;
|
||||
|
||||
if (sw->device->active_codec_switch != sw) {
|
||||
/* This codec switch has been canceled. Switch to the new one. */
|
||||
spa_log_debug(sw->device->monitor->log, NAME": a2dp codec switch %p: canceled, go to new switch", sw);
|
||||
a2dp_codec_switch_free(sw);
|
||||
|
||||
if (r != NULL)
|
||||
dbus_message_unref(r);
|
||||
|
||||
spa_assert(device->active_codec_switch != NULL);
|
||||
a2dp_codec_switch_process(device->active_codec_switch);
|
||||
return;
|
||||
}
|
||||
|
||||
if (r == NULL) {
|
||||
spa_log_error(sw->device->monitor->log,
|
||||
NAME": a2dp codec switch %p: empty reply from dbus, stopping",
|
||||
sw);
|
||||
a2dp_codec_switch_free(sw);
|
||||
device->active_codec_switch = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
|
||||
spa_log_debug(sw->device->monitor->log,
|
||||
NAME": a2dp codec switch %p: failed (%s), trying next",
|
||||
sw, dbus_message_get_error_name(r));
|
||||
dbus_message_unref(r);
|
||||
goto next;
|
||||
}
|
||||
|
||||
dbus_message_unref(r);
|
||||
|
||||
/* Success */
|
||||
spa_log_info(sw->device->monitor->log, NAME": a2dp codec switch %p: success", sw);
|
||||
device->active_codec_switch = NULL;
|
||||
spa_bt_device_emit_codec_switched(sw->device, 0);
|
||||
a2dp_codec_switch_free(sw);
|
||||
return;
|
||||
|
||||
next:
|
||||
a2dp_codec_switch_next(sw);
|
||||
a2dp_codec_switch_process(sw);
|
||||
return;
|
||||
}
|
||||
|
||||
static int a2dp_codec_switch_cmp(const void *a, const void *b)
|
||||
{
|
||||
struct spa_bt_a2dp_codec_switch *sw = a2dp_codec_switch_cmp_sw;
|
||||
const struct a2dp_codec *codec = *sw->codec_iter;
|
||||
const char *path1 = a, *path2 = b;
|
||||
struct spa_bt_remote_endpoint *ep1, *ep2;
|
||||
|
||||
ep1 = device_remote_endpoint_find(sw->device, path1);
|
||||
ep2 = device_remote_endpoint_find(sw->device, path2);
|
||||
|
||||
if (ep1 != NULL && (ep1->uuid == NULL || ep1->codec != codec->codec_id || ep1->capabilities == NULL))
|
||||
ep1 = NULL;
|
||||
if (ep2 != NULL && (ep2->uuid == NULL || ep2->codec != codec->codec_id || ep2->capabilities == NULL))
|
||||
ep2 = NULL;
|
||||
|
||||
if (ep1 == NULL && ep2 == NULL)
|
||||
return 0;
|
||||
else if (ep1 == NULL)
|
||||
return 1;
|
||||
else if (ep2 == NULL)
|
||||
return -1;
|
||||
|
||||
return codec->caps_preference_cmp(codec, ep1->capabilities, ep1->capabilities_len,
|
||||
ep2->capabilities, ep2->capabilities_len);
|
||||
}
|
||||
|
||||
/* Ensure there's a transport for at least one of the listed codecs */
|
||||
int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec **codecs)
|
||||
{
|
||||
struct spa_bt_a2dp_codec_switch *sw;
|
||||
struct spa_bt_remote_endpoint *ep;
|
||||
struct spa_bt_transport *t;
|
||||
size_t i, num_codecs, num_eps;
|
||||
|
||||
if (!device->adapter->application_registered) {
|
||||
/* Codec switching not supported */
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (codecs[0] == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
/* Check if we already have an enabled transport for the most preferred codec.
|
||||
* However, if there already was a codec switch running, these transports may
|
||||
* disapper soon. In that case, we have to do the full thing.
|
||||
*/
|
||||
if (device->active_codec_switch == NULL) {
|
||||
spa_list_for_each(t, &device->transport_list, device_link) {
|
||||
if (t->a2dp_codec != codecs[0] || !t->enabled)
|
||||
continue;
|
||||
|
||||
if ((device->connected_profiles & t->profile) != t->profile)
|
||||
continue;
|
||||
|
||||
spa_bt_device_emit_codec_switched(device, 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup and start iteration */
|
||||
|
||||
sw = calloc(1, sizeof(struct spa_bt_a2dp_codec_switch));
|
||||
if (sw == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
num_eps = 0;
|
||||
spa_list_for_each(ep, &device->remote_endpoint_list, device_link)
|
||||
++num_eps;
|
||||
|
||||
num_codecs = 0;
|
||||
while (codecs[num_codecs] != NULL)
|
||||
++num_codecs;
|
||||
|
||||
sw->codecs = calloc(num_codecs + 1, sizeof(const struct a2dp_codec *));
|
||||
sw->paths = calloc(num_eps + 1, sizeof(char *));
|
||||
sw->num_paths = num_eps;
|
||||
|
||||
if (sw->codecs == NULL || sw->paths == NULL) {
|
||||
a2dp_codec_switch_free(sw);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memcpy(sw->codecs, codecs, num_codecs * sizeof(const struct a2dp_codec *));
|
||||
|
||||
i = 0;
|
||||
spa_list_for_each(ep, &device->remote_endpoint_list, device_link) {
|
||||
sw->paths[i] = strdup(ep->path);
|
||||
if (sw->paths[i] == NULL) {
|
||||
a2dp_codec_switch_free(sw);
|
||||
return -ENOMEM;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
sw->codecs[num_codecs] = NULL;
|
||||
sw->paths[num_eps] = NULL;
|
||||
|
||||
sw->codec_iter = sw->codecs;
|
||||
sw->path_iter = sw->paths;
|
||||
|
||||
sw->profile = device->connected_profiles;
|
||||
|
||||
sw->device = device;
|
||||
spa_list_append(&device->codec_switch_list, &sw->device_link);
|
||||
|
||||
if (device->active_codec_switch != NULL) {
|
||||
/*
|
||||
* There's a codec switch already running. BlueZ does not appear to allow
|
||||
* calling dbus_pending_call_cancel on an active request, so we have to
|
||||
* wait for the reply to arrive first, and only then start processing this
|
||||
* request.
|
||||
*/
|
||||
spa_log_debug(sw->device->monitor->log,
|
||||
NAME": a2dp codec switch: already in progress, canceling previous");
|
||||
spa_assert(device->active_codec_switch->pending != NULL);
|
||||
device->active_codec_switch = sw;
|
||||
} else {
|
||||
device->active_codec_switch = sw;
|
||||
a2dp_codec_switch_process(sw);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn,
|
||||
const char *path, DBusMessage *m, void *userdata)
|
||||
{
|
||||
|
|
@ -1402,6 +1800,8 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn,
|
|||
spa_log_info(monitor->log, "%p: %s validate conf channels:%d",
|
||||
monitor, path, transport->n_channels);
|
||||
|
||||
transport->enabled = true;
|
||||
|
||||
spa_bt_device_connect_profile(transport->device, transport->profile);
|
||||
|
||||
if ((r = dbus_message_new_method_return(m)) == NULL)
|
||||
|
|
|
|||
|
|
@ -92,8 +92,11 @@ struct impl {
|
|||
struct props props;
|
||||
|
||||
struct spa_bt_device *bt_dev;
|
||||
struct spa_hook bt_dev_listener;
|
||||
|
||||
uint32_t profile;
|
||||
const struct a2dp_codec *selected_a2dp_codec; /**< Codec wanted. NULL means any. */
|
||||
|
||||
struct node nodes[2];
|
||||
};
|
||||
|
||||
|
|
@ -138,16 +141,33 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t,
|
|||
t->n_channels * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
static struct spa_bt_transport *find_transport(struct impl *this, int profile)
|
||||
static struct spa_bt_transport *find_transport(struct impl *this, int profile, const struct a2dp_codec *a2dp_codec)
|
||||
{
|
||||
struct spa_bt_device *device = this->bt_dev;
|
||||
struct spa_bt_transport *t;
|
||||
const struct a2dp_codec **codecs;
|
||||
size_t i, num_codecs;
|
||||
|
||||
spa_list_for_each(t, &device->transport_list, device_link) {
|
||||
if (t->profile & device->connected_profiles &&
|
||||
(t->profile & profile) == t->profile)
|
||||
return t;
|
||||
codecs = &a2dp_codec;
|
||||
num_codecs = 1;
|
||||
|
||||
if (a2dp_codec == NULL && (profile == SPA_BT_PROFILE_A2DP_SOURCE || profile == SPA_BT_PROFILE_A2DP_SINK)) {
|
||||
codecs = a2dp_codecs;
|
||||
num_codecs = 0;
|
||||
while (codecs[num_codecs] != NULL)
|
||||
++num_codecs;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_codecs; ++i) {
|
||||
spa_list_for_each(t, &device->transport_list, device_link) {
|
||||
if (t->enabled &&
|
||||
(t->profile & device->connected_profiles) &&
|
||||
(t->profile & profile) == t->profile &&
|
||||
(codecs[i] == NULL || t->a2dp_codec == codecs[i]))
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -160,13 +180,13 @@ static int emit_nodes(struct impl *this)
|
|||
break;
|
||||
case 1:
|
||||
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) {
|
||||
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE);
|
||||
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, this->selected_a2dp_codec);
|
||||
if (t)
|
||||
emit_node(this, t, 0, SPA_NAME_API_BLUEZ5_A2DP_SOURCE);
|
||||
}
|
||||
|
||||
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) {
|
||||
t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK);
|
||||
t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK, this->selected_a2dp_codec);
|
||||
if (t)
|
||||
emit_node(this, t, 1, SPA_NAME_API_BLUEZ5_A2DP_SINK);
|
||||
}
|
||||
|
|
@ -177,7 +197,7 @@ static int emit_nodes(struct impl *this)
|
|||
int i;
|
||||
|
||||
for (i = SPA_BT_PROFILE_HSP_HS ; i <= SPA_BT_PROFILE_HFP_AG ; i <<= 1) {
|
||||
t = find_transport(this, i);
|
||||
t = find_transport(this, i, NULL);
|
||||
if (t)
|
||||
break;
|
||||
}
|
||||
|
|
@ -211,21 +231,50 @@ static void emit_info(struct impl *this, bool full)
|
|||
}
|
||||
}
|
||||
|
||||
static int set_profile(struct impl *this, uint32_t profile)
|
||||
static void emit_remove_nodes(struct impl *this)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
if (this->profile == profile)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (this->nodes[i].active) {
|
||||
spa_device_emit_object_info(&this->hooks, i, NULL);
|
||||
this->nodes[i].active = false;
|
||||
}
|
||||
}
|
||||
this->profile = profile;
|
||||
}
|
||||
|
||||
static int set_profile(struct impl *this, uint32_t profile, const struct a2dp_codec *a2dp_codec)
|
||||
{
|
||||
if (this->profile == profile && a2dp_codec == this->selected_a2dp_codec)
|
||||
return 0;
|
||||
|
||||
emit_remove_nodes(this);
|
||||
|
||||
this->profile = profile;
|
||||
this->selected_a2dp_codec = a2dp_codec;
|
||||
|
||||
if (profile == 1) {
|
||||
int ret;
|
||||
const struct a2dp_codec *codec_list[2], **codecs;
|
||||
|
||||
/* A2DP: ensure there's a transport with the selected codec (NULL means any) */
|
||||
if (a2dp_codec == NULL) {
|
||||
codecs = a2dp_codecs;
|
||||
} else {
|
||||
codec_list[0] = a2dp_codec;
|
||||
codec_list[1] = NULL;
|
||||
codecs = codec_list;
|
||||
}
|
||||
|
||||
ret = spa_bt_device_ensure_a2dp_codec(this->bt_dev, codecs);
|
||||
if (ret < 0)
|
||||
spa_log_error(this->log, NAME": failed to switch codec (%d), setting basic profile", ret);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
this->switching_codec = false;
|
||||
this->selected_a2dp_codec = NULL;
|
||||
emit_nodes(this);
|
||||
|
||||
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
|
||||
|
|
@ -237,6 +286,37 @@ static int set_profile(struct impl *this, uint32_t profile)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void codec_switched(void *userdata, int status)
|
||||
{
|
||||
struct impl *this = userdata;
|
||||
|
||||
spa_log_debug(this->log, NAME": codec switched (status %d)", status);
|
||||
|
||||
if (status < 0) {
|
||||
/* Failed to switch: return to a fallback profile */
|
||||
spa_log_error(this->log, NAME": failed to switch codec (%d), setting fallback profile", status);
|
||||
if (this->selected_a2dp_codec != NULL) {
|
||||
this->selected_a2dp_codec = NULL;
|
||||
} else {
|
||||
this->profile = 0;
|
||||
}
|
||||
}
|
||||
|
||||
emit_remove_nodes(this);
|
||||
emit_nodes(this);
|
||||
|
||||
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
|
||||
this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||
this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||
this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||
emit_info(this, false);
|
||||
}
|
||||
|
||||
static const struct spa_bt_device_events bt_dev_events = {
|
||||
SPA_VERSION_BT_DEVICE_EVENTS,
|
||||
.codec_switched = codec_switched,
|
||||
};
|
||||
|
||||
static int impl_add_listener(void *object,
|
||||
struct spa_hook *listener,
|
||||
const struct spa_device_events *events,
|
||||
|
|
@ -762,7 +842,7 @@ static int impl_set_param(void *object,
|
|||
spa_debug_pod(0, NULL, param);
|
||||
return res;
|
||||
}
|
||||
set_profile(this, id);
|
||||
set_profile(this, id, NULL);
|
||||
break;
|
||||
}
|
||||
case SPA_PARAM_Route:
|
||||
|
|
@ -825,6 +905,9 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void
|
|||
|
||||
static int impl_clear(struct spa_handle *handle)
|
||||
{
|
||||
struct impl *this = (struct impl *) handle;
|
||||
if (this->bt_dev)
|
||||
spa_hook_remove(&this->bt_dev_listener);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -885,6 +968,8 @@ impl_init(const struct spa_handle_factory *factory,
|
|||
this->info.params = this->params;
|
||||
this->info.n_params = 4;
|
||||
|
||||
spa_bt_device_add_listener(this->bt_dev, &this->bt_dev_listener, &bt_dev_events, this);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -326,6 +326,17 @@ static inline enum spa_bt_form_factor spa_bt_form_factor_from_class(uint32_t blu
|
|||
return SPA_BT_FORM_FACTOR_UNKNOWN;
|
||||
}
|
||||
|
||||
struct spa_bt_a2dp_codec_switch;
|
||||
struct spa_bt_transport;
|
||||
|
||||
struct spa_bt_device_events {
|
||||
#define SPA_VERSION_BT_DEVICE_EVENTS 0
|
||||
uint32_t version;
|
||||
|
||||
/** Codec switching completed */
|
||||
void (*codec_switched) (void *data, int status);
|
||||
};
|
||||
|
||||
struct spa_bt_device {
|
||||
struct spa_list link;
|
||||
struct spa_bt_monitor *monitor;
|
||||
|
|
@ -349,13 +360,27 @@ struct spa_bt_device {
|
|||
struct spa_source timer;
|
||||
struct spa_list remote_endpoint_list;
|
||||
struct spa_list transport_list;
|
||||
struct spa_list codec_switch_list;
|
||||
struct spa_bt_a2dp_codec_switch *active_codec_switch;
|
||||
|
||||
struct spa_hook_list listener_list;
|
||||
bool added;
|
||||
};
|
||||
|
||||
struct a2dp_codec;
|
||||
|
||||
struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path);
|
||||
struct spa_bt_device *spa_bt_device_find_by_address(struct spa_bt_monitor *monitor, const char *remote_address, const char *local_address);
|
||||
int spa_bt_device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile);
|
||||
int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force);
|
||||
int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec **codecs);
|
||||
|
||||
#define spa_bt_device_emit(d,m,v,...) spa_hook_list_call(&(d)->listener_list, \
|
||||
struct spa_bt_device_events, \
|
||||
m, v, ##__VA_ARGS__)
|
||||
#define spa_bt_device_emit_codec_switched(d,...) spa_bt_device_emit(d, codec_switched, 0, __VA_ARGS__)
|
||||
#define spa_bt_device_add_listener(d,listener,events,data) \
|
||||
spa_hook_list_append(&(d)->listener_list, listener, events, data)
|
||||
|
||||
struct spa_bt_sco_io;
|
||||
|
||||
|
|
@ -403,6 +428,8 @@ struct spa_bt_transport {
|
|||
void *configuration;
|
||||
int configuration_len;
|
||||
|
||||
unsigned int enabled:1; /**< Transport ready for use in sink/source */
|
||||
|
||||
uint32_t n_channels;
|
||||
uint32_t channels[64];
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue