bluez5: emit & remove the A2DP source node depending on transport state

Typically a source stops the connection when it has nothing to play
and this causes the transport to become "idle" and our A2DP source
also stops. However, the node is still present and "running" (if linked),
which causes the graph to underrun as it receives no data from this node.

This patch dynamically creates and destroys the a2dp source node depending
on the transport state. So, when the transport is idle, there is no node
in the graph at all.
This commit is contained in:
George Kiagiadakis 2021-03-17 22:00:44 +02:00 committed by Wim Taymans
parent c81d44e8a9
commit a75fe69c8e
2 changed files with 84 additions and 46 deletions

View file

@ -107,7 +107,6 @@ struct impl {
struct props props;
struct spa_bt_transport *transport;
struct spa_hook transport_listener;
struct port port;
@ -1165,45 +1164,6 @@ static const struct spa_node_methods impl_node = {
.process = impl_node_process,
};
static int do_transport_destroy(struct spa_loop *loop,
bool async,
uint32_t seq,
const void *data,
size_t size,
void *user_data)
{
struct impl *this = user_data;
this->transport = NULL;
this->transport_acquired = false;
return 0;
}
static void transport_destroy(void *data)
{
struct impl *this = data;
spa_log_debug(this->log, "transport %p destroy", this->transport);
spa_loop_invoke(this->data_loop, do_transport_destroy, 0, NULL, 0, true, this);
}
static void transport_state_changed(void *data, enum spa_bt_transport_state old,
enum spa_bt_transport_state state)
{
struct impl *this = data;
spa_log_debug(this->log, "transport %p state %d->%d started:%d",
this->transport, old, state, this->started);
if (state >= SPA_BT_TRANSPORT_STATE_PENDING && old < SPA_BT_TRANSPORT_STATE_PENDING)
transport_start(this);
else if (state < SPA_BT_TRANSPORT_STATE_PENDING && old >= SPA_BT_TRANSPORT_STATE_PENDING)
transport_stop(this);
}
static const struct spa_bt_transport_events transport_events = {
SPA_VERSION_BT_TRANSPORT_EVENTS,
.destroy = transport_destroy,
.state_changed = transport_state_changed,
};
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
{
struct impl *this;
@ -1226,8 +1186,6 @@ static int impl_clear(struct spa_handle *handle)
struct impl *this = (struct impl *) handle;
if (this->codec_data)
this->codec->deinit(this->codec_data);
if (this->transport)
spa_hook_remove(&this->transport_listener);
return 0;
}
@ -1325,9 +1283,6 @@ impl_init(const struct spa_handle_factory *factory,
}
this->codec = this->transport->a2dp_codec;
spa_bt_transport_add_listener(this->transport,
&this->transport_listener, &transport_events, this);
return 0;
}

View file

@ -77,6 +77,16 @@ struct node {
float volumes[SPA_AUDIO_MAX_CHANNELS];
};
struct impl;
struct dynamic_node
{
struct impl *impl;
struct spa_bt_transport *transport;
struct spa_hook transport_listener;
uint32_t id;
const char *factory_name;
};
struct impl {
struct spa_handle handle;
struct spa_device device;
@ -106,6 +116,8 @@ struct impl {
const struct a2dp_codec **supported_codecs;
size_t supported_codec_count;
struct dynamic_node dyn_a2dp_source;
#define MAX_SETTINGS 32
struct spa_dict_item setting_items[MAX_SETTINGS];
struct spa_dict setting_dict;
@ -206,6 +218,74 @@ static struct spa_bt_transport *find_transport(struct impl *this, int profile, c
return NULL;
}
static void dynamic_node_transport_destroy(void *data)
{
struct dynamic_node *this = data;
spa_log_debug(this->impl->log, "transport %p destroy", this->transport);
}
static void dynamic_node_transport_state_changed(void *data,
enum spa_bt_transport_state old,
enum spa_bt_transport_state state)
{
struct dynamic_node *this = data;
struct impl *impl = this->impl;
struct spa_bt_transport *t = this->transport;
spa_log_debug(impl->log, "transport %p state %d->%d", t, old, state);
if (state >= SPA_BT_TRANSPORT_STATE_PENDING && old < SPA_BT_TRANSPORT_STATE_PENDING) {
if (!impl->nodes[this->id].active) {
emit_node(impl, t, this->id, this->factory_name);
}
} else if (state < SPA_BT_TRANSPORT_STATE_PENDING && old >= SPA_BT_TRANSPORT_STATE_PENDING) {
if (impl->nodes[this->id].active) {
spa_device_emit_object_info(&impl->hooks, this->id, NULL);
impl->nodes[this->id].active = false;
}
}
}
static const struct spa_bt_transport_events dynamic_node_transport_events = {
SPA_VERSION_BT_TRANSPORT_EVENTS,
.destroy = dynamic_node_transport_destroy,
.state_changed = dynamic_node_transport_state_changed,
};
static void emit_dynamic_node(struct dynamic_node *this, struct impl *impl,
struct spa_bt_transport *t, uint32_t id, const char *factory_name)
{
if (this->transport != NULL)
return;
this->impl = impl;
this->transport = t;
this->id = id;
this->factory_name = factory_name;
spa_bt_transport_add_listener(this->transport,
&this->transport_listener, &dynamic_node_transport_events, this);
/* emits the node if the state is already pending */
dynamic_node_transport_state_changed (this, SPA_BT_TRANSPORT_STATE_IDLE, t->state);
}
static void remove_dynamic_node(struct dynamic_node *this)
{
if (this->transport == NULL)
return;
/* destroy the node, if it exists */
dynamic_node_transport_state_changed (this, this->transport->state,
SPA_BT_TRANSPORT_STATE_IDLE);
spa_hook_remove(&this->transport_listener);
this->impl = NULL;
this->transport = NULL;
this->id = 0;
this->factory_name = NULL;
}
static int emit_nodes(struct impl *this)
{
struct spa_bt_transport *t;
@ -217,7 +297,8 @@ static int emit_nodes(struct impl *this)
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) {
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, this->selected_a2dp_codec);
if (t)
emit_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE);
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE);
}
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) {
@ -270,6 +351,8 @@ static void emit_remove_nodes(struct impl *this)
{
uint32_t i;
remove_dynamic_node (&this->dyn_a2dp_source);
for (i = 0; i < 2; i++) {
if (this->nodes[i].active) {
spa_device_emit_object_info(&this->hooks, i, NULL);