diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c
index 2e68b1d..482de4a 100644
--- a/profiles/audio/a2dp.c
+++ b/profiles/audio/a2dp.c
#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
+struct a2dp_stream {
+ struct avdtp *session;
+ struct avdtp_stream *stream;
+ unsigned int suspend_timer;
+ gboolean locked;
+ gboolean suspending;
+ gboolean starting;
+};
+
struct a2dp_sep {
struct a2dp_server *server;
struct a2dp_endpoint *endpoint;
uint8_t type;
uint8_t codec;
struct avdtp_local_sep *lsep;
- struct avdtp *session;
- struct avdtp_stream *stream;
- unsigned int suspend_timer;
+ struct queue *streams;
gboolean delay_reporting;
- gboolean locked;
- gboolean suspending;
- gboolean starting;
void *user_data;
GDestroyNotify destroy;
};
return NULL;
}
+static bool match_stream_session(const void *data, const void *user_data)
+{
+ const struct a2dp_stream *stream = data;
+ const struct avdtp *session = user_data;
+
+ return stream->session == session;
+}
+
+static struct a2dp_stream *a2dp_stream_new(struct a2dp_sep *sep,
+ struct avdtp *session)
+{
+ struct a2dp_stream *stream;
+
+ if (!sep->streams)
+ sep->streams = queue_new();
+
+ stream = new0(struct a2dp_stream, 1);
+ stream->session = avdtp_ref(session);
+ queue_push_tail(sep->streams, stream);
+
+ return stream;
+}
+
+static struct a2dp_stream *a2dp_stream_get(struct a2dp_sep *sep,
+ struct avdtp *session)
+{
+ struct a2dp_stream *stream;
+
+ DBG("sep %p session %p", sep, session);
+
+ stream = queue_find(sep->streams, match_stream_session, session);
+ if (stream)
+ return stream;
+
+ return a2dp_stream_new(sep, session);
+}
+
+static void a2dp_stream_starting(struct a2dp_sep *sep, struct avdtp *session)
+{
+ struct a2dp_stream *stream;
+
+ stream = a2dp_stream_get(sep, session);
+ if (!stream)
+ return;
+
+ stream->starting = TRUE;
+}
+
+static void a2dp_stream_free(struct a2dp_stream *stream)
+{
+ avdtp_unref(stream->session);
+ free(stream);
+}
+
+static bool match_stream(const void *data, const void *user_data)
+{
+ const struct a2dp_stream *astream = data;
+ const struct avdtp_stream *stream = user_data;
+
+ return astream->stream == stream;
+}
+
+static void a2dp_stream_destroy(struct a2dp_sep *sep,
+ struct avdtp_stream *stream)
+{
+ struct a2dp_stream *astream;
+
+ DBG("sep %p stream %p", sep, stream);
+
+ astream = queue_remove_if(sep->streams, match_stream, stream);
+ if (astream)
+ a2dp_stream_free(astream);
+}
+
static void stream_state_changed(struct avdtp_stream *stream,
avdtp_state_t old_state,
avdtp_state_t new_state,
void *user_data)
{
struct a2dp_sep *sep = user_data;
+ struct a2dp_stream *a2dp_stream;
+ struct btd_device *dev = NULL;
if (new_state == AVDTP_STATE_OPEN) {
struct a2dp_setup *setup;
return;
}
- sep->starting = TRUE;
+ a2dp_stream_starting(sep, setup->session);
return;
}
if (new_state != AVDTP_STATE_IDLE)
return;
- if (sep->suspend_timer) {
- timeout_remove(sep->suspend_timer);
- sep->suspend_timer = 0;
+ a2dp_stream = queue_find(sep->streams, match_stream, stream);
+ if (a2dp_stream) {
+ dev = avdtp_get_device(a2dp_stream->session);
+ a2dp_stream_destroy(sep, stream);
}
- if (sep->session) {
- avdtp_unref(sep->session);
- sep->session = NULL;
- }
-
- sep->stream = NULL;
-
if (sep->endpoint && sep->endpoint->clear_configuration)
- sep->endpoint->clear_configuration(sep, sep->user_data);
+ sep->endpoint->clear_configuration(sep, dev, sep->user_data);
}
static gboolean auto_config(gpointer data)
struct a2dp_setup *setup = data;
struct btd_device *dev = NULL;
struct btd_service *service;
+ struct a2dp_stream *stream;
/* Check if configuration was aborted */
- if (setup->sep->stream == NULL)
+ stream = queue_find(setup->sep->streams, match_stream, setup->stream);
+ if (!stream)
return FALSE;
if (setup->err != NULL)
{
struct a2dp_sep *a2dp_sep = user_data;
struct a2dp_setup *setup;
+ struct a2dp_stream *a2dp_stream;
if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
DBG("Sink %p: Set_Configuration_Ind", sep);
else
DBG("Source %p: Set_Configuration_Ind", sep);
+ a2dp_stream = a2dp_stream_get(a2dp_sep, session);
+ if (!a2dp_stream)
+ return FALSE;
+
setup = a2dp_setup_get(session);
if (!setup)
return FALSE;
- a2dp_sep->stream = stream;
+ a2dp_stream->stream = stream;
setup->sep = a2dp_sep;
setup->stream = stream;
setup->setconf_cb = cb;
{
struct a2dp_sep *a2dp_sep = user_data;
struct a2dp_setup *setup;
+ struct a2dp_stream *a2dp_stream;
struct btd_device *dev;
struct btd_service *service;
int ret;
}
avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
- a2dp_sep->stream = stream;
+
+ a2dp_stream = a2dp_stream_get(a2dp_sep, session);
+ if (a2dp_stream)
+ a2dp_stream->stream = stream;
if (!setup)
return;
return;
}
-static bool suspend_timeout(struct a2dp_sep *sep)
+static bool suspend_timeout(struct a2dp_stream *stream)
{
- if (avdtp_suspend(sep->session, sep->stream) == 0)
- sep->suspending = TRUE;
+ if (avdtp_suspend(stream->session, stream->stream) == 0)
+ stream->suspending = TRUE;
- sep->suspend_timer = 0;
-
- avdtp_unref(sep->session);
- sep->session = NULL;
+ stream->suspend_timer = 0;
return FALSE;
}
void *user_data)
{
struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_stream *a2dp_stream;
struct a2dp_setup *setup;
if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
else
DBG("Source %p: Start_Ind", sep);
- if (!a2dp_sep->locked) {
- a2dp_sep->session = avdtp_ref(session);
- a2dp_sep->suspend_timer = timeout_add_seconds(SUSPEND_TIMEOUT,
+ a2dp_stream = queue_find(a2dp_sep->streams, match_stream, stream);
+ if (!a2dp_stream)
+ return FALSE;
+
+ if (!a2dp_stream->locked)
+ a2dp_stream->suspend_timer = timeout_add_seconds(
+ SUSPEND_TIMEOUT,
(timeout_func_t) suspend_timeout,
- a2dp_sep, NULL);
- }
+ a2dp_stream, NULL);
- if (!a2dp_sep->starting)
+ if (!a2dp_stream->starting)
return TRUE;
- a2dp_sep->starting = FALSE;
+ a2dp_stream->starting = FALSE;
setup = find_setup_by_session(session);
if (setup)
void *user_data)
{
struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_stream *a2dp_stream;
struct a2dp_setup *setup;
if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
else
DBG("Source %p: Start_Cfm", sep);
- a2dp_sep->starting = FALSE;
+ a2dp_stream = queue_find(a2dp_sep->streams, match_stream, stream);
+ if (!a2dp_stream)
+ return;
+
+ a2dp_stream->starting = FALSE;
setup = find_setup_by_session(session);
if (!setup)
void *user_data)
{
struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_stream *a2dp_stream;
struct a2dp_setup *setup;
gboolean start;
int start_err;
else
DBG("Source %p: Suspend_Ind", sep);
- if (a2dp_sep->suspend_timer) {
- timeout_remove(a2dp_sep->suspend_timer);
- a2dp_sep->suspend_timer = 0;
- avdtp_unref(a2dp_sep->session);
- a2dp_sep->session = NULL;
+ a2dp_stream = queue_find(a2dp_sep->streams, match_stream, stream);
+ if (!a2dp_stream)
+ return FALSE;
+
+ if (a2dp_stream->suspend_timer) {
+ timeout_remove(a2dp_stream->suspend_timer);
+ a2dp_stream->suspend_timer = 0;
}
- if (!a2dp_sep->suspending)
+ if (!a2dp_stream->suspending)
return TRUE;
- a2dp_sep->suspending = FALSE;
+ a2dp_stream->suspending = FALSE;
setup = find_setup_by_session(session);
if (!setup)
if (!start)
return TRUE;
- start_err = avdtp_start(session, a2dp_sep->stream);
+ start_err = avdtp_start(session, a2dp_stream->stream);
if (start_err < 0 && start_err != -EINPROGRESS) {
error("avdtp_start: %s (%d)", strerror(-start_err),
-start_err);
void *user_data)
{
struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_stream *a2dp_stream;
struct a2dp_setup *setup;
gboolean start;
int start_err;
else
DBG("Source %p: Suspend_Cfm", sep);
- a2dp_sep->suspending = FALSE;
+ a2dp_stream = queue_find(a2dp_sep->streams, match_stream, stream);
+ if (!a2dp_stream)
+ return;
+
+ a2dp_stream->suspending = FALSE;
setup = find_setup_by_session(session);
if (!setup)
return;
}
- start_err = avdtp_start(session, a2dp_sep->stream);
+ start_err = avdtp_start(session, a2dp_stream->stream);
if (start_err < 0 && start_err != -EINPROGRESS) {
error("avdtp_start: %s (%d)", strerror(-start_err), -start_err);
finalize_setup_errno(setup, start_err, finalize_suspend, NULL);
else
DBG("Source %p: Abort_Ind", sep);
- a2dp_sep->stream = NULL;
+ a2dp_stream_destroy(a2dp_sep, stream);
setup = find_setup_by_session(session);
if (!setup)
uint8_t *caps, int size, void *user_data)
{
struct a2dp_setup *setup;
+ struct a2dp_stream *stream;
struct a2dp_setup_cb *cb_data;
GSList *l;
int err;
- /* Check SEP not used by a different session */
- if (lsep->stream && chan->session &&
- !avdtp_has_stream(chan->session, lsep->stream))
- return -EBUSY;
+ if (!chan->session)
+ return -ENOTCONN;
setup = a2dp_setup_get(chan->session);
if (!setup)
struct a2dp_sep *tmp = l->data;
/* Attempt to reconfigure if a stream already exists */
- if (tmp->stream) {
+ stream = queue_find(tmp->streams, match_stream_session,
+ chan->session);
+ if (stream) {
/* Only allow switching sep from the same sender */
if (strcmp(sender, tmp->endpoint->get_name(tmp,
tmp->user_data))) {
}
/* Check if stream is for the channel */
- if (!avdtp_has_stream(chan->session, tmp->stream))
+ if (!avdtp_has_stream(chan->session, stream->stream))
continue;
- err = avdtp_close(chan->session, tmp->stream, FALSE);
+ err = avdtp_close(chan->session, stream->stream, FALSE);
if (err < 0) {
- err = avdtp_abort(chan->session, tmp->stream);
+ err = avdtp_abort(chan->session,
+ stream->stream);
if (err < 0) {
error("avdtp_abort: %s",
strerror(-err));
return sep;
}
+static bool match_locked(const void *data, const void *user_data)
+{
+ const struct a2dp_stream *stream = data;
+
+ return stream->locked;
+}
+
void a2dp_remove_sep(struct a2dp_sep *sep)
{
struct a2dp_server *server = sep->server;
}
}
- if (sep->locked)
+ if (queue_find(sep->streams, match_locked, NULL))
return;
a2dp_unregister_sep(sep);
GSList *l;
struct a2dp_server *server;
struct a2dp_setup *setup;
- struct a2dp_sep *tmp;
+ struct a2dp_stream *stream;
struct avdtp_service_capability *cap;
struct avdtp_media_codec_capability *codec_cap = NULL;
int posix_err;
cb_data->config_cb = cb;
cb_data->user_data = user_data;
+ stream = queue_find(sep->streams, match_stream_session, session);
+
setup->sep = sep;
- setup->stream = sep->stream;
+ setup->stream = stream ? stream->stream : NULL;
/* Copy given caps if they are different than current caps */
if (setup->caps != caps) {
setup->caps = g_slist_copy(caps);
}
- switch (avdtp_stream_get_state(sep->stream)) {
+ switch (avdtp_stream_get_state(setup->stream)) {
case AVDTP_STATE_IDLE:
if (sep->type == AVDTP_SEP_TYPE_SOURCE)
l = server->sources;
l = server->sinks;
for (; l != NULL; l = l->next) {
- tmp = l->data;
- if (avdtp_has_stream(session, tmp->stream))
+ struct a2dp_sep *tmp = l->data;
+
+ stream = queue_find(tmp->streams, match_stream_session,
+ session);
+ if (!stream)
+ continue;
+
+ if (avdtp_has_stream(session, stream->stream))
break;
}
if (l != NULL) {
- if (tmp->locked)
+ if (stream->locked)
goto failed;
setup->reconfigure = TRUE;
- if (avdtp_close(session, tmp->stream, FALSE) < 0) {
+ if (avdtp_close(session, stream->stream, FALSE) < 0) {
error("avdtp_close failed");
goto failed;
}
setup);
} else if (!setup->reconfigure) {
setup->reconfigure = TRUE;
- if (avdtp_close(session, sep->stream, FALSE) < 0) {
+ if (avdtp_close(session, setup->stream, FALSE) < 0) {
error("avdtp_close failed");
goto failed;
}
{
struct a2dp_setup_cb *cb_data;
struct a2dp_setup *setup;
+ struct a2dp_stream *stream;
setup = a2dp_setup_get(session);
if (!setup)
if (setup->reconfigure)
goto failed;
+ stream = queue_find(sep->streams, match_stream_session, session);
+
setup->sep = sep;
- setup->stream = sep->stream;
+ setup->stream = stream ? stream->stream : NULL;
- switch (avdtp_stream_get_state(sep->stream)) {
+ switch (avdtp_stream_get_state(setup->stream)) {
case AVDTP_STATE_IDLE:
goto failed;
- break;
case AVDTP_STATE_CONFIGURED:
setup->start = TRUE;
break;
case AVDTP_STATE_OPEN:
- if (avdtp_start(session, sep->stream) < 0) {
+ if (avdtp_start(session, setup->stream) < 0) {
error("avdtp_start failed");
goto failed;
}
- sep->starting = TRUE;
+ stream->starting = TRUE;
break;
case AVDTP_STATE_STREAMING:
- if (!sep->suspending && sep->suspend_timer) {
- timeout_remove(sep->suspend_timer);
- sep->suspend_timer = 0;
- avdtp_unref(sep->session);
- sep->session = NULL;
+ if (!stream->suspending && stream->suspend_timer) {
+ timeout_remove(stream->suspend_timer);
+ stream->suspend_timer = 0;
}
- if (sep->suspending)
+ if (stream->suspending)
setup->start = TRUE;
else
cb_data->source_id = g_idle_add(finalize_resume,
{
struct a2dp_setup_cb *cb_data;
struct a2dp_setup *setup;
+ struct a2dp_stream *stream;
setup = a2dp_setup_get(session);
if (!setup)
if (setup->reconfigure)
goto failed;
+ stream = queue_find(sep->streams, match_stream_session, session);
+
setup->sep = sep;
- setup->stream = sep->stream;
+ setup->stream = stream ? stream->stream : NULL;
- switch (avdtp_stream_get_state(sep->stream)) {
+ switch (avdtp_stream_get_state(setup->stream)) {
case AVDTP_STATE_IDLE:
error("a2dp_suspend: no stream to suspend");
goto failed;
cb_data->source_id = g_idle_add(finalize_suspend, setup);
break;
case AVDTP_STATE_STREAMING:
- if (avdtp_suspend(session, sep->stream) < 0) {
+ if (avdtp_suspend(session, setup->stream) < 0) {
error("avdtp_suspend failed");
goto failed;
}
- sep->suspending = TRUE;
+ stream->suspending = TRUE;
break;
case AVDTP_STATE_CONFIGURED:
case AVDTP_STATE_CLOSING:
gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
{
- if (sep->locked)
+ struct a2dp_stream *stream;
+
+ stream = queue_find(sep->streams, match_stream_session, session);
+ if (!stream || stream->locked)
return FALSE;
- DBG("SEP %p locked", sep->lsep);
- sep->locked = TRUE;
+ DBG("stream %p locked", sep->lsep);
+ stream->locked = TRUE;
return TRUE;
}
gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session)
{
struct a2dp_server *server = sep->server;
+ struct a2dp_stream *stream;
avdtp_state_t state;
GSList *l;
- state = avdtp_stream_get_state(sep->stream);
+ stream = queue_find(sep->streams, match_stream_session, session);
+ if (!stream)
+ return FALSE;
+
+ state = avdtp_stream_get_state(stream->stream);
- sep->locked = FALSE;
+ stream->locked = FALSE;
- DBG("SEP %p unlocked", sep->lsep);
+ DBG("stream %p unlocked", stream);
if (sep->type == AVDTP_SEP_TYPE_SOURCE)
l = server->sources;
return TRUE;
}
- if (!sep->stream || state == AVDTP_STATE_IDLE)
+ if (!stream->stream || state == AVDTP_STATE_IDLE)
return TRUE;
switch (state) {
/* Set timer here */
break;
case AVDTP_STATE_STREAMING:
- if (avdtp_suspend(session, sep->stream) == 0)
- sep->suspending = TRUE;
+ if (avdtp_suspend(session, stream->stream) == 0)
+ stream->suspending = TRUE;
break;
case AVDTP_STATE_IDLE:
case AVDTP_STATE_CONFIGURED:
return TRUE;
}
-struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep)
+struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep,
+ struct avdtp *session)
{
- return sep->stream;
+ struct a2dp_stream *stream;
+
+ stream = queue_find(sep->streams, match_stream_session, session);
+ if (stream)
+ return stream->stream;
+
+ return NULL;
}
struct btd_device *a2dp_setup_get_device(struct a2dp_setup *setup)
diff --git a/profiles/audio/a2dp.h b/profiles/audio/a2dp.h
index 615b641..c698bc9 100644
--- a/profiles/audio/a2dp.h
+++ b/profiles/audio/a2dp.h
struct a2dp_setup *setup,
a2dp_endpoint_config_t cb,
void *user_data);
- void (*clear_configuration) (struct a2dp_sep *sep, void *user_data);
+ void (*clear_configuration) (struct a2dp_sep *sep,
+ struct btd_device *device,
+ void *user_data);
void (*set_delay) (struct a2dp_sep *sep, uint16_t delay,
void *user_data);
};
gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session);
gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session);
-struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep);
+struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep,
+ struct avdtp *session);
struct btd_device *a2dp_setup_get_device(struct a2dp_setup *setup);
const char *a2dp_setup_remote_path(struct a2dp_setup *setup);
struct avdtp *a2dp_avdtp_get(struct btd_device *device);
diff --git a/profiles/audio/media.c b/profiles/audio/media.c
index a18ddc9..8e62dca 100644
--- a/profiles/audio/media.c
+++ b/profiles/audio/media.c
return -ENOMEM;
}
-static void clear_config(struct a2dp_sep *sep, void *user_data)
+static void clear_config(struct a2dp_sep *sep, struct btd_device *device,
+ void *user_data)
{
struct media_endpoint *endpoint = user_data;
+ struct media_transport *transport;
- clear_endpoint(endpoint);
+ if (!device) {
+ clear_endpoint(endpoint);
+ return;
+ }
+
+ transport = find_device_transport(endpoint, device);
+ if (!transport)
+ return;
+
+ clear_configuration(endpoint, transport);
}
static void set_delay(struct a2dp_sep *sep, uint16_t delay, void *user_data)
diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index 1535151..a1fdf94 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
#ifdef HAVE_A2DP
static void *transport_a2dp_get_stream(struct media_transport *transport)
{
+ struct a2dp_transport *a2dp = transport->data;
struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint);
if (!sep)
return NULL;
- return a2dp_sep_get_stream(sep);
+ return a2dp_sep_get_stream(sep, a2dp->session);
}
static void a2dp_suspend_complete(struct avdtp *session, int err,