From eca37b58a6e8800bf3bc12ac3ed58bedc21f3633 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 15 Aug 2021 19:11:12 +0300 Subject: [PATCH] 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. --- spa/plugins/bluez5/a2dp-codecs.h | 2 + spa/plugins/bluez5/a2dp-source.c | 18 +++++- spa/plugins/bluez5/bluez5-device.c | 90 +++++++++++++++++++++--------- 3 files changed, 82 insertions(+), 28 deletions(-) diff --git a/spa/plugins/bluez5/a2dp-codecs.h b/spa/plugins/bluez5/a2dp-codecs.h index 9753c88df..3724d89d7 100644 --- a/spa/plugins/bluez5/a2dp-codecs.h +++ b/spa/plugins/bluez5/a2dp-codecs.h @@ -355,6 +355,8 @@ struct a2dp_codec { 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, uint8_t caps[A2DP_MAX_CAPS_SIZE]); int (*select_config) (const struct a2dp_codec *codec, uint32_t flags, diff --git a/spa/plugins/bluez5/a2dp-source.c b/spa/plugins/bluez5/a2dp-source.c index 46ad23e46..ad0e07b6d 100644 --- a/spa/plugins/bluez5/a2dp-source.c +++ b/spa/plugins/bluez5/a2dp-source.c @@ -146,6 +146,7 @@ struct impl { uint64_t skip_count; bool is_input; + bool is_duplex; }; #define NAME "a2dp-source" @@ -465,6 +466,8 @@ static void a2dp_on_ready_read(struct spa_source *source) goto stop; } + spa_log_trace(this->log, "socket poll"); + /* update the current pts */ 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); - 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); this->started = true; @@ -1377,6 +1381,8 @@ impl_init(const struct spa_handle_factory *factory, sscanf(str, "pointer:%p", &this->transport); if ((str = spa_dict_lookup(info, "bluez5.a2dp-source-role")) != NULL) 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) { @@ -1388,6 +1394,16 @@ impl_init(const struct spa_handle_factory *factory, return -EINVAL; } 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) this->codec_props = this->codec->init_props(this->codec, this->transport->device->settings); diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index f375ab769..9a0628c9e 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -106,6 +106,7 @@ struct dynamic_node struct spa_hook transport_listener; uint32_t id; const char *factory_name; + bool a2dp_duplex; }; struct impl { @@ -139,6 +140,7 @@ struct impl { size_t supported_codec_count; struct dynamic_node dyn_a2dp_source; + struct dynamic_node dyn_a2dp_sink; struct dynamic_node dyn_sco_source; struct dynamic_node dyn_sco_sink; @@ -232,10 +234,13 @@ static const char *get_hfp_codec_name(unsigned int codec) 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 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, - 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_device_object_info info; - struct spa_dict_item items[7]; + struct spa_dict_item items[8]; uint32_t n_items = 0; char transport[32], str_id[32]; 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); 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[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[4] = SPA_DICT_ITEM_INIT("device.routes", "1"); 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"); 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.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 (!SPA_FLAG_IS_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) { 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, - 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", 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->id = id; this->factory_name = factory_name; + this->a2dp_duplex = a2dp_duplex; spa_bt_transport_add_listener(this->transport, &this->transport_listener, &dynamic_node_transport_events, this); @@ -568,9 +578,9 @@ static int emit_nodes(struct impl *this) else this->props.codec = get_hfp_codec_id(t->codec); 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, - 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) { @@ -578,7 +588,12 @@ static int emit_nodes(struct impl *this) if (t) { this->props.codec = t->a2dp_codec->id; 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; @@ -588,7 +603,12 @@ static int emit_nodes(struct impl *this) if (t) { this->props.codec = t->a2dp_codec->id; 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); if (t) { 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; @@ -610,8 +635,8 @@ static int emit_nodes(struct impl *this) this->props.codec = 0; else 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_SINK, SPA_NAME_API_BLUEZ5_SCO_SINK); + 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, false); } } break; @@ -643,6 +668,7 @@ static void emit_info(struct impl *this, bool full) static void emit_remove_nodes(struct impl *this) { 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_sink); @@ -887,16 +913,21 @@ static int impl_sync(void *object, int seq) 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; uint32_t mask; bool have_output = false, have_input = false; + const struct a2dp_codec *a2dp_codec; switch (index) { case DEVICE_PROFILE_A2DP: if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) have_output = true; + + a2dp_codec = get_supported_a2dp_codec(this, codec); + if (a2dp_codec && a2dp_codec->duplex_codec) + have_input = true; break; case DEVICE_PROFILE_HSP_HFP: 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 = 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)"); } else { 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; } - 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_add(b, 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_prop(b, SPA_PARAM_ROUTE_profiles, 0); 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; /* Check the profile actually exists */ if (!validate_profile(this, j, codec)) continue; + mask |= profile_mask; spa_pod_builder_int(b, i); } spa_pod_builder_pop(b, &f[1]); + if (!(mask & (1 << direction))) { + /* No profile has route direction */ + return NULL; + } + if (dev != SPA_ID_INVALID) { struct node *node = &this->nodes[dev]; 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 ? &node->transport->volumes[node->id] : NULL;