mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
bluez5: make room for a2dp duplex channel
Some non-standard A2DP codecs (FastStream/aptX-LL) have "voice duplex channel" that can be used to provide an A2DP duplex mode. Add support for duplex channels, accounting for the fact that the two directions may be encoded with different actual codecs.
This commit is contained in:
parent
4ab8ecfe06
commit
eca37b58a6
3 changed files with 82 additions and 28 deletions
|
|
@ -355,6 +355,8 @@ struct a2dp_codec {
|
||||||
|
|
||||||
const size_t send_buf_size;
|
const size_t send_buf_size;
|
||||||
|
|
||||||
|
const struct a2dp_codec *duplex_codec; /**< Codec for non-standard A2DP duplex channel */
|
||||||
|
|
||||||
int (*fill_caps) (const struct a2dp_codec *codec, uint32_t flags,
|
int (*fill_caps) (const struct a2dp_codec *codec, uint32_t flags,
|
||||||
uint8_t caps[A2DP_MAX_CAPS_SIZE]);
|
uint8_t caps[A2DP_MAX_CAPS_SIZE]);
|
||||||
int (*select_config) (const struct a2dp_codec *codec, uint32_t flags,
|
int (*select_config) (const struct a2dp_codec *codec, uint32_t flags,
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ struct impl {
|
||||||
uint64_t skip_count;
|
uint64_t skip_count;
|
||||||
|
|
||||||
bool is_input;
|
bool is_input;
|
||||||
|
bool is_duplex;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define NAME "a2dp-source"
|
#define NAME "a2dp-source"
|
||||||
|
|
@ -465,6 +466,8 @@ static void a2dp_on_ready_read(struct spa_source *source)
|
||||||
goto stop;
|
goto stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spa_log_trace(this->log, "socket poll");
|
||||||
|
|
||||||
/* update the current pts */
|
/* update the current pts */
|
||||||
spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &this->now);
|
spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &this->now);
|
||||||
|
|
||||||
|
|
@ -667,7 +670,8 @@ static int do_start(struct impl *this)
|
||||||
|
|
||||||
spa_return_val_if_fail(this->transport != NULL, -EIO);
|
spa_return_val_if_fail(this->transport != NULL, -EIO);
|
||||||
|
|
||||||
if (this->transport->state >= SPA_BT_TRANSPORT_STATE_PENDING)
|
if (this->transport->state >= SPA_BT_TRANSPORT_STATE_PENDING ||
|
||||||
|
this->is_duplex)
|
||||||
res = transport_start(this);
|
res = transport_start(this);
|
||||||
|
|
||||||
this->started = true;
|
this->started = true;
|
||||||
|
|
@ -1377,6 +1381,8 @@ impl_init(const struct spa_handle_factory *factory,
|
||||||
sscanf(str, "pointer:%p", &this->transport);
|
sscanf(str, "pointer:%p", &this->transport);
|
||||||
if ((str = spa_dict_lookup(info, "bluez5.a2dp-source-role")) != NULL)
|
if ((str = spa_dict_lookup(info, "bluez5.a2dp-source-role")) != NULL)
|
||||||
this->is_input = spa_streq(str, "input");
|
this->is_input = spa_streq(str, "input");
|
||||||
|
if ((str = spa_dict_lookup(info, "api.bluez5.a2dp-duplex")) != NULL)
|
||||||
|
this->is_duplex = spa_atob(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->transport == NULL) {
|
if (this->transport == NULL) {
|
||||||
|
|
@ -1388,6 +1394,16 @@ impl_init(const struct spa_handle_factory *factory,
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
this->codec = this->transport->a2dp_codec;
|
this->codec = this->transport->a2dp_codec;
|
||||||
|
|
||||||
|
if (this->is_duplex) {
|
||||||
|
if (!this->codec->duplex_codec) {
|
||||||
|
spa_log_error(this->log, "transport codec doesn't support duplex");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
this->codec = this->codec->duplex_codec;
|
||||||
|
this->is_input = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (this->codec->init_props != NULL)
|
if (this->codec->init_props != NULL)
|
||||||
this->codec_props = this->codec->init_props(this->codec,
|
this->codec_props = this->codec->init_props(this->codec,
|
||||||
this->transport->device->settings);
|
this->transport->device->settings);
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ struct dynamic_node
|
||||||
struct spa_hook transport_listener;
|
struct spa_hook transport_listener;
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
const char *factory_name;
|
const char *factory_name;
|
||||||
|
bool a2dp_duplex;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct impl {
|
struct impl {
|
||||||
|
|
@ -139,6 +140,7 @@ struct impl {
|
||||||
size_t supported_codec_count;
|
size_t supported_codec_count;
|
||||||
|
|
||||||
struct dynamic_node dyn_a2dp_source;
|
struct dynamic_node dyn_a2dp_source;
|
||||||
|
struct dynamic_node dyn_a2dp_sink;
|
||||||
struct dynamic_node dyn_sco_source;
|
struct dynamic_node dyn_sco_source;
|
||||||
struct dynamic_node dyn_sco_sink;
|
struct dynamic_node dyn_sco_sink;
|
||||||
|
|
||||||
|
|
@ -232,10 +234,13 @@ static const char *get_hfp_codec_name(unsigned int codec)
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *get_codec_name(struct spa_bt_transport *t)
|
static const char *get_codec_name(struct spa_bt_transport *t, bool a2dp_duplex)
|
||||||
{
|
{
|
||||||
if (t->a2dp_codec != NULL)
|
if (t->a2dp_codec != NULL) {
|
||||||
|
if (a2dp_duplex && t->a2dp_codec->duplex_codec)
|
||||||
|
return t->a2dp_codec->duplex_codec->name;
|
||||||
return t->a2dp_codec->name;
|
return t->a2dp_codec->name;
|
||||||
|
}
|
||||||
return get_hfp_codec_name(t->codec);
|
return get_hfp_codec_name(t->codec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -346,11 +351,11 @@ static const struct spa_bt_transport_events transport_events = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static void emit_node(struct impl *this, struct spa_bt_transport *t,
|
static void emit_node(struct impl *this, struct spa_bt_transport *t,
|
||||||
uint32_t id, const char *factory_name)
|
uint32_t id, const char *factory_name, bool a2dp_duplex)
|
||||||
{
|
{
|
||||||
struct spa_bt_device *device = this->bt_dev;
|
struct spa_bt_device *device = this->bt_dev;
|
||||||
struct spa_device_object_info info;
|
struct spa_device_object_info info;
|
||||||
struct spa_dict_item items[7];
|
struct spa_dict_item items[8];
|
||||||
uint32_t n_items = 0;
|
uint32_t n_items = 0;
|
||||||
char transport[32], str_id[32];
|
char transport[32], str_id[32];
|
||||||
bool is_dyn_node = SPA_FLAG_IS_SET(id, DYNAMIC_NODE_ID_FLAG);
|
bool is_dyn_node = SPA_FLAG_IS_SET(id, DYNAMIC_NODE_ID_FLAG);
|
||||||
|
|
@ -358,7 +363,7 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t,
|
||||||
snprintf(transport, sizeof(transport), "pointer:%p", t);
|
snprintf(transport, sizeof(transport), "pointer:%p", t);
|
||||||
items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_TRANSPORT, transport);
|
items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_TRANSPORT, transport);
|
||||||
items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PROFILE, spa_bt_profile_name(t->profile));
|
items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PROFILE, spa_bt_profile_name(t->profile));
|
||||||
items[2] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CODEC, get_codec_name(t));
|
items[2] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CODEC, get_codec_name(t, a2dp_duplex));
|
||||||
items[3] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ADDRESS, device->address);
|
items[3] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ADDRESS, device->address);
|
||||||
items[4] = SPA_DICT_ITEM_INIT("device.routes", "1");
|
items[4] = SPA_DICT_ITEM_INIT("device.routes", "1");
|
||||||
n_items = 5;
|
n_items = 5;
|
||||||
|
|
@ -371,6 +376,10 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t,
|
||||||
items[n_items] = SPA_DICT_ITEM_INIT("device.intended-roles", "Communication");
|
items[n_items] = SPA_DICT_ITEM_INIT("device.intended-roles", "Communication");
|
||||||
n_items++;
|
n_items++;
|
||||||
}
|
}
|
||||||
|
if (a2dp_duplex) {
|
||||||
|
items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.a2dp-duplex", "true");
|
||||||
|
n_items++;
|
||||||
|
}
|
||||||
|
|
||||||
info = SPA_DEVICE_OBJECT_INFO_INIT();
|
info = SPA_DEVICE_OBJECT_INFO_INIT();
|
||||||
info.type = SPA_TYPE_INTERFACE_Node;
|
info.type = SPA_TYPE_INTERFACE_Node;
|
||||||
|
|
@ -446,7 +455,7 @@ static void dynamic_node_transport_state_changed(void *data,
|
||||||
if (state >= SPA_BT_TRANSPORT_STATE_PENDING && old < SPA_BT_TRANSPORT_STATE_PENDING) {
|
if (state >= SPA_BT_TRANSPORT_STATE_PENDING && old < SPA_BT_TRANSPORT_STATE_PENDING) {
|
||||||
if (!SPA_FLAG_IS_SET(this->id, DYNAMIC_NODE_ID_FLAG)) {
|
if (!SPA_FLAG_IS_SET(this->id, DYNAMIC_NODE_ID_FLAG)) {
|
||||||
SPA_FLAG_SET(this->id, DYNAMIC_NODE_ID_FLAG);
|
SPA_FLAG_SET(this->id, DYNAMIC_NODE_ID_FLAG);
|
||||||
emit_node(impl, t, this->id, this->factory_name);
|
emit_node(impl, t, this->id, this->factory_name, this->a2dp_duplex);
|
||||||
}
|
}
|
||||||
} else if (state < SPA_BT_TRANSPORT_STATE_PENDING && old >= SPA_BT_TRANSPORT_STATE_PENDING) {
|
} else if (state < SPA_BT_TRANSPORT_STATE_PENDING && old >= SPA_BT_TRANSPORT_STATE_PENDING) {
|
||||||
if (SPA_FLAG_IS_SET(this->id, DYNAMIC_NODE_ID_FLAG)) {
|
if (SPA_FLAG_IS_SET(this->id, DYNAMIC_NODE_ID_FLAG)) {
|
||||||
|
|
@ -511,7 +520,7 @@ static const struct spa_bt_transport_events dynamic_node_transport_events = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static void emit_dynamic_node(struct dynamic_node *this, struct impl *impl,
|
static void emit_dynamic_node(struct dynamic_node *this, struct impl *impl,
|
||||||
struct spa_bt_transport *t, uint32_t id, const char *factory_name)
|
struct spa_bt_transport *t, uint32_t id, const char *factory_name, bool a2dp_duplex)
|
||||||
{
|
{
|
||||||
spa_log_debug(impl->log, NAME": dynamic node, transport: %p->%p id: %08x->%08x",
|
spa_log_debug(impl->log, NAME": dynamic node, transport: %p->%p id: %08x->%08x",
|
||||||
this->transport, t, this->id, id);
|
this->transport, t, this->id, id);
|
||||||
|
|
@ -526,6 +535,7 @@ static void emit_dynamic_node(struct dynamic_node *this, struct impl *impl,
|
||||||
this->transport = t;
|
this->transport = t;
|
||||||
this->id = id;
|
this->id = id;
|
||||||
this->factory_name = factory_name;
|
this->factory_name = factory_name;
|
||||||
|
this->a2dp_duplex = a2dp_duplex;
|
||||||
|
|
||||||
spa_bt_transport_add_listener(this->transport,
|
spa_bt_transport_add_listener(this->transport,
|
||||||
&this->transport_listener, &dynamic_node_transport_events, this);
|
&this->transport_listener, &dynamic_node_transport_events, this);
|
||||||
|
|
@ -568,9 +578,9 @@ static int emit_nodes(struct impl *this)
|
||||||
else
|
else
|
||||||
this->props.codec = get_hfp_codec_id(t->codec);
|
this->props.codec = get_hfp_codec_id(t->codec);
|
||||||
emit_dynamic_node(&this->dyn_sco_source, this, t,
|
emit_dynamic_node(&this->dyn_sco_source, this, t,
|
||||||
0, SPA_NAME_API_BLUEZ5_SCO_SOURCE);
|
0, SPA_NAME_API_BLUEZ5_SCO_SOURCE, false);
|
||||||
emit_dynamic_node(&this->dyn_sco_sink, this, t,
|
emit_dynamic_node(&this->dyn_sco_sink, this, t,
|
||||||
1, SPA_NAME_API_BLUEZ5_SCO_SINK);
|
1, SPA_NAME_API_BLUEZ5_SCO_SINK, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) {
|
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) {
|
||||||
|
|
@ -578,7 +588,12 @@ static int emit_nodes(struct impl *this)
|
||||||
if (t) {
|
if (t) {
|
||||||
this->props.codec = t->a2dp_codec->id;
|
this->props.codec = t->a2dp_codec->id;
|
||||||
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
|
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
|
||||||
2, SPA_NAME_API_BLUEZ5_A2DP_SOURCE);
|
2, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, false);
|
||||||
|
|
||||||
|
if (t->a2dp_codec->duplex_codec) {
|
||||||
|
emit_dynamic_node(&this->dyn_a2dp_sink, this, t,
|
||||||
|
3, SPA_NAME_API_BLUEZ5_A2DP_SINK, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -588,7 +603,12 @@ static int emit_nodes(struct impl *this)
|
||||||
if (t) {
|
if (t) {
|
||||||
this->props.codec = t->a2dp_codec->id;
|
this->props.codec = t->a2dp_codec->id;
|
||||||
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
|
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
|
||||||
DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE);
|
DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, false);
|
||||||
|
|
||||||
|
if (t->a2dp_codec->duplex_codec) {
|
||||||
|
emit_node(this, t,
|
||||||
|
DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_A2DP_SINK, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -596,7 +616,12 @@ static int emit_nodes(struct impl *this)
|
||||||
t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK, this->props.codec);
|
t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK, this->props.codec);
|
||||||
if (t) {
|
if (t) {
|
||||||
this->props.codec = t->a2dp_codec->id;
|
this->props.codec = t->a2dp_codec->id;
|
||||||
emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_A2DP_SINK);
|
emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_A2DP_SINK, false);
|
||||||
|
|
||||||
|
if (t->a2dp_codec->duplex_codec) {
|
||||||
|
emit_node(this, t,
|
||||||
|
DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -610,8 +635,8 @@ static int emit_nodes(struct impl *this)
|
||||||
this->props.codec = 0;
|
this->props.codec = 0;
|
||||||
else
|
else
|
||||||
this->props.codec = get_hfp_codec_id(t->codec);
|
this->props.codec = get_hfp_codec_id(t->codec);
|
||||||
emit_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_SCO_SOURCE);
|
emit_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_SCO_SOURCE, false);
|
||||||
emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_SCO_SINK);
|
emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_SCO_SINK, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -643,6 +668,7 @@ static void emit_info(struct impl *this, bool full)
|
||||||
static void emit_remove_nodes(struct impl *this)
|
static void emit_remove_nodes(struct impl *this)
|
||||||
{
|
{
|
||||||
remove_dynamic_node (&this->dyn_a2dp_source);
|
remove_dynamic_node (&this->dyn_a2dp_source);
|
||||||
|
remove_dynamic_node (&this->dyn_a2dp_sink);
|
||||||
remove_dynamic_node (&this->dyn_sco_source);
|
remove_dynamic_node (&this->dyn_sco_source);
|
||||||
remove_dynamic_node (&this->dyn_sco_sink);
|
remove_dynamic_node (&this->dyn_sco_sink);
|
||||||
|
|
||||||
|
|
@ -887,16 +913,21 @@ static int impl_sync(void *object, int seq)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t profile_direction_mask(struct impl *this, uint32_t index)
|
static uint32_t profile_direction_mask(struct impl *this, uint32_t index, enum spa_bluetooth_audio_codec codec)
|
||||||
{
|
{
|
||||||
struct spa_bt_device *device = this->bt_dev;
|
struct spa_bt_device *device = this->bt_dev;
|
||||||
uint32_t mask;
|
uint32_t mask;
|
||||||
bool have_output = false, have_input = false;
|
bool have_output = false, have_input = false;
|
||||||
|
const struct a2dp_codec *a2dp_codec;
|
||||||
|
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case DEVICE_PROFILE_A2DP:
|
case DEVICE_PROFILE_A2DP:
|
||||||
if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK)
|
if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK)
|
||||||
have_output = true;
|
have_output = true;
|
||||||
|
|
||||||
|
a2dp_codec = get_supported_a2dp_codec(this, codec);
|
||||||
|
if (a2dp_codec && a2dp_codec->duplex_codec)
|
||||||
|
have_input = true;
|
||||||
break;
|
break;
|
||||||
case DEVICE_PROFILE_HSP_HFP:
|
case DEVICE_PROFILE_HSP_HFP:
|
||||||
if (device->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)
|
if (device->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)
|
||||||
|
|
@ -1070,7 +1101,7 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *
|
||||||
}
|
}
|
||||||
name_and_codec = spa_aprintf("%s-%s", name, a2dp_codec->name);
|
name_and_codec = spa_aprintf("%s-%s", name, a2dp_codec->name);
|
||||||
name = name_and_codec;
|
name = name_and_codec;
|
||||||
if (profile == SPA_BT_PROFILE_A2DP_SINK) {
|
if (profile == SPA_BT_PROFILE_A2DP_SINK && !a2dp_codec->duplex_codec) {
|
||||||
desc = _("High Fidelity Playback (A2DP Sink, codec %s)");
|
desc = _("High Fidelity Playback (A2DP Sink, codec %s)");
|
||||||
} else {
|
} else {
|
||||||
desc = _("High Fidelity Duplex (A2DP Source/Sink, codec %s)");
|
desc = _("High Fidelity Duplex (A2DP Source/Sink, codec %s)");
|
||||||
|
|
@ -1248,15 +1279,6 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev != SPA_ID_INVALID && !(profile_direction_mask(this, this->profile) & (1 << direction)))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
mask = 0;
|
|
||||||
for (i = 1; i < 4; i++)
|
|
||||||
mask |= profile_direction_mask(this, i);
|
|
||||||
if ((mask & (1 << direction)) == 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamRoute, id);
|
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamRoute, id);
|
||||||
spa_pod_builder_add(b,
|
spa_pod_builder_add(b,
|
||||||
SPA_PARAM_ROUTE_index, SPA_POD_Int(port),
|
SPA_PARAM_ROUTE_index, SPA_POD_Int(port),
|
||||||
|
|
@ -1276,23 +1298,37 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
|
||||||
spa_pod_builder_pop(b, &f[1]);
|
spa_pod_builder_pop(b, &f[1]);
|
||||||
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0);
|
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0);
|
||||||
spa_pod_builder_push_array(b, &f[1]);
|
spa_pod_builder_push_array(b, &f[1]);
|
||||||
for (i = 1; (j = get_profile_from_index(this, i, &next, &codec)) != SPA_ID_INVALID; i = next) {
|
|
||||||
|
|
||||||
if (!(profile_direction_mask(this, j) & (1 << direction)))
|
mask = 0;
|
||||||
|
for (i = 1; (j = get_profile_from_index(this, i, &next, &codec)) != SPA_ID_INVALID; i = next) {
|
||||||
|
uint32_t profile_mask;
|
||||||
|
|
||||||
|
profile_mask = profile_direction_mask(this, j, codec);
|
||||||
|
if (!(profile_mask & (1 << direction)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Check the profile actually exists */
|
/* Check the profile actually exists */
|
||||||
if (!validate_profile(this, j, codec))
|
if (!validate_profile(this, j, codec))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
mask |= profile_mask;
|
||||||
spa_pod_builder_int(b, i);
|
spa_pod_builder_int(b, i);
|
||||||
}
|
}
|
||||||
spa_pod_builder_pop(b, &f[1]);
|
spa_pod_builder_pop(b, &f[1]);
|
||||||
|
|
||||||
|
if (!(mask & (1 << direction))) {
|
||||||
|
/* No profile has route direction */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (dev != SPA_ID_INVALID) {
|
if (dev != SPA_ID_INVALID) {
|
||||||
struct node *node = &this->nodes[dev];
|
struct node *node = &this->nodes[dev];
|
||||||
struct spa_bt_transport_volume *t_volume;
|
struct spa_bt_transport_volume *t_volume;
|
||||||
|
|
||||||
|
mask = profile_direction_mask(this, this->profile, this->props.codec);
|
||||||
|
if (!(mask & (1 << direction)))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
t_volume = node->transport
|
t_volume = node->transport
|
||||||
? &node->transport->volumes[node->id]
|
? &node->transport->volumes[node->id]
|
||||||
: NULL;
|
: NULL;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue