[PATCH] A2DP Sink connection

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This set of patches implements the necessary code for the A2DP Sink.
It wasn't extensively tested because we need a client to actually send
audio through it, but avinfo and the D-Bus Connect() method works.
Also, it didn't work over 2.6.28 kernel from Jaunty (connection
refused), and it latter worked over 2.6.30 from kernel.ubuntu.com. I
don't know if was a bug in kernel part or some ubuntu config file
issue.

Looking at the code one will notice that there is a lot of duplicated
code from Source SEP and Sink API. I have plans to merge both codes in
the future, but I would like to see it working with the client first,
so we don't change working code without testing the new one first. To
be able to test it, Source interface needs to be explicitly enabled in
audio.conf (Enable=Source).

-- 
João Paulo Rechi Vita
MSc Computer Science Student
Computer Engineer
IC / Unicamp
http://jprvita.wordpress.com/
From 623cd6abf8faffdfb5af8e8054b6c9ec198c1161 Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Jo=C3=A3o=20Paulo=20Rechi=20Vita?= <jprvita@xxxxxxxxx>
Date: Tue, 2 Jun 2009 17:36:03 -0300
Subject: [PATCH] Create A2DP Sink SDP record.

---
 audio/a2dp.c |   57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 56 insertions(+), 1 deletions(-)

diff --git a/audio/a2dp.c b/audio/a2dp.c
index 2c4d047..1ef23c4 100644
--- a/audio/a2dp.c
+++ b/audio/a2dp.c
@@ -995,7 +995,62 @@ static sdp_record_t *a2dp_source_record()
 
 static sdp_record_t *a2dp_sink_record()
 {
-	return NULL;
+	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+	uuid_t root_uuid, l2cap, avdtp, a2snk;
+	sdp_profile_desc_t profile[1];
+	sdp_list_t *aproto, *proto[2];
+	sdp_record_t *record;
+	sdp_data_t *psm, *version, *features;
+	uint16_t lp = AVDTP_UUID, ver = 0x0100, feat = 0x000F;
+
+	record = sdp_record_alloc();
+	if (!record)
+		return NULL;
+
+	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+	root = sdp_list_append(0, &root_uuid);
+	sdp_set_browse_groups(record, root);
+
+	sdp_uuid16_create(&a2snk, AUDIO_SINK_SVCLASS_ID);
+	svclass_id = sdp_list_append(0, &a2snk);
+	sdp_set_service_classes(record, svclass_id);
+
+	sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID);
+	profile[0].version = 0x0100;
+	pfseq = sdp_list_append(0, &profile[0]);
+	sdp_set_profile_descs(record, pfseq);
+
+	sdp_uuid16_create(&l2cap, L2CAP_UUID);
+	proto[0] = sdp_list_append(0, &l2cap);
+	psm = sdp_data_alloc(SDP_UINT16, &lp);
+	proto[0] = sdp_list_append(proto[0], psm);
+	apseq = sdp_list_append(0, proto[0]);
+
+	sdp_uuid16_create(&avdtp, AVDTP_UUID);
+	proto[1] = sdp_list_append(0, &avdtp);
+	version = sdp_data_alloc(SDP_UINT16, &ver);
+	proto[1] = sdp_list_append(proto[1], version);
+	apseq = sdp_list_append(apseq, proto[1]);
+
+	aproto = sdp_list_append(0, apseq);
+	sdp_set_access_protos(record, aproto);
+
+	features = sdp_data_alloc(SDP_UINT16, &feat);
+	sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+	sdp_set_info_attr(record, "Audio Sink", 0, 0);
+
+	free(psm);
+	free(version);
+	sdp_list_free(proto[0], 0);
+	sdp_list_free(proto[1], 0);
+	sdp_list_free(apseq, 0);
+	sdp_list_free(pfseq, 0);
+	sdp_list_free(aproto, 0);
+	sdp_list_free(root, 0);
+	sdp_list_free(svclass_id, 0);
+
+	return record;
 }
 
 static struct a2dp_sep *a2dp_add_sep(struct a2dp_server *server, uint8_t type,
-- 
1.6.0.4

From bfa30d671ffb996ba4c38b9d5aa0be70dd1a3b4a Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Jo=C3=A3o=20Paulo=20Rechi=20Vita?= <jprvita@xxxxxxxxx>
Date: Wed, 3 Jun 2009 10:29:42 -0300
Subject: [PATCH] Create a2dp_sink_cancel().

---
 audio/a2dp.c |   35 +++++++++++++++++++++++++++++++++++
 audio/a2dp.h |    2 ++
 2 files changed, 37 insertions(+), 0 deletions(-)

diff --git a/audio/a2dp.c b/audio/a2dp.c
index 1ef23c4..67e2576 100644
--- a/audio/a2dp.c
+++ b/audio/a2dp.c
@@ -1566,6 +1566,41 @@ failed:
 	return 0;
 }
 
+gboolean a2dp_sink_cancel(struct audio_device *dev, unsigned int id)
+{
+	struct a2dp_setup_cb *cb_data;
+	struct a2dp_setup *setup;
+	GSList *l;
+
+	debug("a2dp_sink_cancel()");
+
+	setup = find_setup_by_dev(dev);
+	if (!setup)
+		return FALSE;
+
+	for (cb_data = NULL, l = setup->cb; l != NULL; l = g_slist_next(l)) {
+		struct a2dp_setup_cb *cb = l->data;
+
+		if (cb->id == id) {
+			cb_data = cb;
+			break;
+		}
+	}
+
+	if (!cb_data)
+		error("a2dp_sink_cancel: no matching callback with id %u", id);
+
+	setup->cb = g_slist_remove(setup->cb, cb_data);
+	g_free(cb_data);
+
+	if (!setup->cb) {
+		setup->canceled = TRUE;
+		setup->sep = NULL;
+	}
+
+	return TRUE;
+}
+
 gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
 {
 	if (sep->locked)
diff --git a/audio/a2dp.h b/audio/a2dp.h
index 0e5f796..f72480c 100644
--- a/audio/a2dp.h
+++ b/audio/a2dp.h
@@ -143,6 +143,8 @@ unsigned int a2dp_source_suspend(struct avdtp *session, struct a2dp_sep *sep,
 				a2dp_stream_cb_t cb, void *user_data);
 gboolean a2dp_source_cancel(struct audio_device *dev, unsigned int id);
 
+gboolean a2dp_sink_cancel(struct audio_device *dev, unsigned int id);
+
 gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session);
 gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session);
 gboolean a2dp_sep_get_lock(struct a2dp_sep *sep);
-- 
1.6.0.4

From 35df3fc3121c51ae85aca767ee44a0b3cd692faf Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Jo=C3=A3o=20Paulo=20Rechi=20Vita?= <jprvita@xxxxxxxxx>
Date: Wed, 3 Jun 2009 11:19:36 -0300
Subject: [PATCH] Create a2dp_sink_get().

---
 audio/a2dp.c |   33 +++++++++++++++++++++++++++++++++
 audio/a2dp.h |    2 ++
 2 files changed, 35 insertions(+), 0 deletions(-)

diff --git a/audio/a2dp.c b/audio/a2dp.c
index 67e2576..605dfed 100644
--- a/audio/a2dp.c
+++ b/audio/a2dp.c
@@ -1601,6 +1601,39 @@ gboolean a2dp_sink_cancel(struct audio_device *dev, unsigned int id)
 	return TRUE;
 }
 
+struct a2dp_sep *a2dp_sink_get(struct avdtp *session,
+				struct avdtp_remote_sep *rsep)
+{
+	GSList *l;
+	struct a2dp_server *server;
+	struct avdtp_service_capability *cap;
+	struct avdtp_media_codec_capability *codec_cap = NULL;
+	bdaddr_t src;
+
+	avdtp_get_peers(session, &src, NULL);
+	server = find_server(servers, &src);
+	if (!server)
+		return NULL;
+
+	cap = avdtp_get_codec(rsep);
+	codec_cap = (void *) cap->data;
+
+	for (l = server->sinks; l != NULL; l = l->next) {
+		struct a2dp_sep *sep = l->data;
+
+		if (sep->locked)
+			continue;
+
+		if (sep->codec != codec_cap->media_codec_type)
+			continue;
+
+		if (!sep->stream || avdtp_has_stream(session, sep->stream))
+			return sep;
+	}
+
+	return NULL;
+}
+
 gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
 {
 	if (sep->locked)
diff --git a/audio/a2dp.h b/audio/a2dp.h
index f72480c..f0a61e3 100644
--- a/audio/a2dp.h
+++ b/audio/a2dp.h
@@ -143,6 +143,8 @@ unsigned int a2dp_source_suspend(struct avdtp *session, struct a2dp_sep *sep,
 				a2dp_stream_cb_t cb, void *user_data);
 gboolean a2dp_source_cancel(struct audio_device *dev, unsigned int id);
 
+struct a2dp_sep *a2dp_sink_get(struct avdtp *session,
+				struct avdtp_remote_sep *sep);
 gboolean a2dp_sink_cancel(struct audio_device *dev, unsigned int id);
 
 gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session);
-- 
1.6.0.4

From 3dbd41357795961e204a6deefc14977e8aafbf9c Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Jo=C3=A3o=20Paulo=20Rechi=20Vita?= <jprvita@xxxxxxxxx>
Date: Wed, 3 Jun 2009 15:31:58 -0300
Subject: [PATCH] Create a2dp_sink_config().

---
 audio/a2dp.c |  118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 audio/a2dp.h |    3 +
 2 files changed, 121 insertions(+), 0 deletions(-)

diff --git a/audio/a2dp.c b/audio/a2dp.c
index 605dfed..dc4f699 100644
--- a/audio/a2dp.c
+++ b/audio/a2dp.c
@@ -1634,6 +1634,124 @@ struct a2dp_sep *a2dp_sink_get(struct avdtp *session,
 	return NULL;
 }
 
+unsigned int a2dp_sink_config(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_config_cb_t cb, GSList *caps,
+				void *user_data)
+{
+	struct a2dp_setup_cb *cb_data;
+	GSList *l;
+	struct a2dp_server *server;
+	struct a2dp_setup *setup;
+	struct a2dp_sep *tmp;
+	struct avdtp_local_sep *lsep;
+	struct avdtp_remote_sep *rsep;
+	struct avdtp_service_capability *cap;
+	struct avdtp_media_codec_capability *codec_cap = NULL;
+	int posix_err;
+	bdaddr_t src;
+
+	avdtp_get_peers(session, &src, NULL);
+	server = find_server(servers, &src);
+	if (!server)
+		return 0;
+
+	for (l = caps; l != NULL; l = l->next) {
+		cap = l->data;
+
+		if (cap->category != AVDTP_MEDIA_CODEC)
+			continue;
+
+		codec_cap = (void *) cap->data;
+		break;
+	}
+
+	if (!codec_cap)
+		return 0;
+
+	if (sep->codec != codec_cap->media_codec_type)
+		return 0;
+
+	debug("a2dp_sink_config: selected SEP %p", sep->sep);
+
+	cb_data = g_new0(struct a2dp_setup_cb, 1);
+	cb_data->config_cb = cb;
+	cb_data->user_data = user_data;
+	cb_data->id = ++cb_id;
+
+	setup = find_setup_by_session(session);
+	if (!setup) {
+		setup = g_new0(struct a2dp_setup, 1);
+		setup->session = avdtp_ref(session);
+		setup->dev = a2dp_get_dev(session);
+		setups = g_slist_append(setups, setup);
+	}
+
+	setup_ref(setup);
+	setup->cb = g_slist_append(setup->cb, cb_data);
+	setup->sep = sep;
+	setup->stream = sep->stream;
+	setup->client_caps = caps;
+
+	switch (avdtp_sep_get_state(sep->sep)) {
+	case AVDTP_STATE_IDLE:
+		for (l = server->sinks; l != NULL; l = l->next) {
+			tmp = l->data;
+
+			if (avdtp_has_stream(session, tmp->stream))
+				break;
+		}
+
+		if (l != NULL) {
+			setup->reconfigure = TRUE;
+			if (avdtp_close(session, tmp->stream) < 0) {
+				error("avdtp_close failed");
+				goto failed;
+			}
+			break;
+		}
+
+		if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SOURCE,
+				codec_cap->media_type,
+				codec_cap->media_codec_type,
+				&lsep, &rsep) < 0) {
+			error("No matching ACP and INT SEPs found");
+			goto failed;
+		}
+
+		posix_err = avdtp_set_configuration(session, rsep, lsep,
+							caps, &setup->stream);
+		if (posix_err < 0) {
+			error("avdtp_set_configuration: %s",
+				strerror(-posix_err));
+			goto failed;
+		}
+		break;
+	case AVDTP_STATE_OPEN:
+	case AVDTP_STATE_STREAMING:
+		if (avdtp_stream_has_capabilities(setup->stream, caps)) {
+			debug("Configuration match: resuming");
+			g_idle_add((GSourceFunc) finalize_config, setup);
+		} else if (!setup->reconfigure) {
+			setup->reconfigure = TRUE;
+			if (avdtp_close(session, sep->stream) < 0) {
+				error("avdtp_close failed");
+				goto failed;
+			}
+		}
+		break;
+	default:
+		error("SEP in bad state for requesting a new stream");
+		goto failed;
+	}
+
+	return cb_data->id;
+
+failed:
+	setup_unref(setup);
+	cb_id--;
+	return 0;
+}
+
 gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
 {
 	if (sep->locked)
diff --git a/audio/a2dp.h b/audio/a2dp.h
index f0a61e3..3be38a7 100644
--- a/audio/a2dp.h
+++ b/audio/a2dp.h
@@ -145,6 +145,9 @@ gboolean a2dp_source_cancel(struct audio_device *dev, unsigned int id);
 
 struct a2dp_sep *a2dp_sink_get(struct avdtp *session,
 				struct avdtp_remote_sep *sep);
+unsigned int a2dp_sink_config(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_config_cb_t cb, GSList *caps,
+				void *user_data);
 gboolean a2dp_sink_cancel(struct audio_device *dev, unsigned int id);
 
 gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session);
-- 
1.6.0.4

From 0fccc66dae98426e401687d9bb3de5171549e351 Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Jo=C3=A3o=20Paulo=20Rechi=20Vita?= <jprvita@xxxxxxxxx>
Date: Wed, 3 Jun 2009 16:00:04 -0300
Subject: [PATCH] Create a2dp_sink_resume().

---
 audio/a2dp.c |   59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 audio/a2dp.h |    2 +
 2 files changed, 61 insertions(+), 0 deletions(-)

diff --git a/audio/a2dp.c b/audio/a2dp.c
index dc4f699..1c05f21 100644
--- a/audio/a2dp.c
+++ b/audio/a2dp.c
@@ -1752,6 +1752,65 @@ failed:
 	return 0;
 }
 
+unsigned int a2dp_sink_resume(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_stream_cb_t cb, void *user_data)
+{
+	struct a2dp_setup_cb *cb_data;
+	struct a2dp_setup *setup;
+
+	cb_data = g_new0(struct a2dp_setup_cb, 1);
+	cb_data->resume_cb = cb;
+	cb_data->user_data = user_data;
+	cb_data->id = ++cb_id;
+
+	setup = find_setup_by_session(session);
+	if (!setup) {
+		setup = g_new0(struct a2dp_setup, 1);
+		setup->session = avdtp_ref(session);
+		setup->dev = a2dp_get_dev(session);
+		setups = g_slist_append(setups, setup);
+	}
+
+	setup_ref(setup);
+	setup->cb = g_slist_append(setup->cb, cb_data);
+	setup->sep = sep;
+	setup->stream = sep->stream;
+
+	switch (avdtp_sep_get_state(sep->sep)) {
+	case AVDTP_STATE_IDLE:
+		goto failed;
+		break;
+	case AVDTP_STATE_OPEN:
+		if (avdtp_start(session, sep->stream) < 0) {
+			error("avdtp_start failed");
+			goto failed;
+		}
+		break;
+	case AVDTP_STATE_STREAMING:
+		if (!sep->suspending && sep->suspend_timer) {
+			g_source_remove(sep->suspend_timer);
+			sep->suspend_timer = 0;
+			avdtp_unref(sep->session);
+			sep->session = NULL;
+		}
+		if (sep->suspending)
+			setup->start = TRUE;
+		else
+			g_idle_add((GSourceFunc) finalize_resume, setup);
+		break;
+	default:
+		error("SEP in bad state for resume");
+		goto failed;
+	}
+
+	return cb_data->id;
+
+failed:
+	setup_unref(setup);
+	cb_id--;
+	return 0;
+}
+
 gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
 {
 	if (sep->locked)
diff --git a/audio/a2dp.h b/audio/a2dp.h
index 3be38a7..db378da 100644
--- a/audio/a2dp.h
+++ b/audio/a2dp.h
@@ -148,6 +148,8 @@ struct a2dp_sep *a2dp_sink_get(struct avdtp *session,
 unsigned int a2dp_sink_config(struct avdtp *session, struct a2dp_sep *sep,
 				a2dp_config_cb_t cb, GSList *caps,
 				void *user_data);
+unsigned int a2dp_sink_resume(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_stream_cb_t cb, void *user_data);
 gboolean a2dp_sink_cancel(struct audio_device *dev, unsigned int id);
 
 gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session);
-- 
1.6.0.4

From bb42272634544fd5085e999d6d886626194e2429 Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Jo=C3=A3o=20Paulo=20Rechi=20Vita?= <jprvita@xxxxxxxxx>
Date: Wed, 3 Jun 2009 20:03:11 -0300
Subject: [PATCH] Create a2dp_sink_suspend().

---
 audio/a2dp.c |   51 +++++++++++++++++++++++++++++++++++++++++++++++++++
 audio/a2dp.h |    2 ++
 2 files changed, 53 insertions(+), 0 deletions(-)

diff --git a/audio/a2dp.c b/audio/a2dp.c
index 1c05f21..721ad16 100644
--- a/audio/a2dp.c
+++ b/audio/a2dp.c
@@ -1811,6 +1811,57 @@ failed:
 	return 0;
 }
 
+unsigned int a2dp_sink_suspend(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_stream_cb_t cb, void *user_data)
+{
+	struct a2dp_setup_cb *cb_data;
+	struct a2dp_setup *setup;
+
+	cb_data = g_new0(struct a2dp_setup_cb, 1);
+	cb_data->suspend_cb = cb;
+	cb_data->user_data = user_data;
+	cb_data->id = ++cb_id;
+
+	setup = find_setup_by_session(session);
+	if (!setup) {
+		setup = g_new0(struct a2dp_setup, 1);
+		setup->session = avdtp_ref(session);
+		setup->dev = a2dp_get_dev(session);
+		setups = g_slist_append(setups, setup);
+	}
+
+	setup_ref(setup);
+	setup->cb = g_slist_append(setup->cb, cb_data);
+	setup->sep = sep;
+	setup->stream = sep->stream;
+
+	switch (avdtp_sep_get_state(sep->sep)) {
+	case AVDTP_STATE_IDLE:
+		error("a2dp_sink_suspend: no stream to suspend");
+		goto failed;
+		break;
+	case AVDTP_STATE_OPEN:
+		g_idle_add((GSourceFunc) finalize_suspend, setup);
+		break;
+	case AVDTP_STATE_STREAMING:
+		if (avdtp_suspend(session, sep->stream) < 0) {
+			error("avdtp_suspend failed");
+			goto failed;
+		}
+		break;
+	default:
+		error("SEP in bad state for suspend");
+		goto failed;
+	}
+
+	return cb_data->id;
+
+failed:
+	setup_unref(setup);
+	cb_id--;
+	return 0;
+}
+
 gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
 {
 	if (sep->locked)
diff --git a/audio/a2dp.h b/audio/a2dp.h
index db378da..cb1bcbd 100644
--- a/audio/a2dp.h
+++ b/audio/a2dp.h
@@ -150,6 +150,8 @@ unsigned int a2dp_sink_config(struct avdtp *session, struct a2dp_sep *sep,
 				void *user_data);
 unsigned int a2dp_sink_resume(struct avdtp *session, struct a2dp_sep *sep,
 				a2dp_stream_cb_t cb, void *user_data);
+unsigned int a2dp_sink_suspend(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_stream_cb_t cb, void *user_data);
 gboolean a2dp_sink_cancel(struct audio_device *dev, unsigned int id);
 
 gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session);
-- 
1.6.0.4

From b429ae0d4e507d206d689f7a9cb09851c6697b3c Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Jo=C3=A3o=20Paulo=20Rechi=20Vita?= <jprvita@xxxxxxxxx>
Date: Sat, 13 Jun 2009 02:33:33 -0300
Subject: [PATCH] Create A2DP Source interface.

---
 audio/Makefile.am |    3 +-
 audio/manager.c   |    5 +
 audio/source.c    |  868 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 audio/source.h    |   48 +++
 4 files changed, 923 insertions(+), 1 deletions(-)
 create mode 100644 audio/source.c
 create mode 100644 audio/source.h

diff --git a/audio/Makefile.am b/audio/Makefile.am
index cbdad9f..31736b4 100644
--- a/audio/Makefile.am
+++ b/audio/Makefile.am
@@ -9,7 +9,8 @@ plugin_LTLIBRARIES = audio.la
 audio_la_SOURCES = main.c \
 	ipc.h ipc.c unix.h unix.c manager.h manager.c telephony.h \
 	device.h device.c headset.h headset.c gateway.h gateway.c \
-	avdtp.h avdtp.c a2dp.h a2dp.c sink.h sink.c control.h control.c
+	avdtp.h avdtp.c a2dp.h a2dp.c sink.h sink.c source.h source.c \
+	control.h control.c
 
 nodist_audio_la_SOURCES = $(BUILT_SOURCES)
 
diff --git a/audio/manager.c b/audio/manager.c
index dc0f6a9..b60c34e 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -65,6 +65,7 @@
 #include "headset.h"
 #include "gateway.h"
 #include "sink.h"
+#include "source.h"
 #include "control.h"
 #include "manager.h"
 #include "sdpd.h"
@@ -142,6 +143,8 @@ gboolean server_is_enabled(bdaddr_t *src, uint16_t svc)
 		return enabled.gateway;
 	case AUDIO_SINK_SVCLASS_ID:
 		return enabled.sink;
+	case AUDIO_SOURCE_SVCLASS_ID:
+		return enabled.source;
 	case AV_REMOTE_TARGET_SVCLASS_ID:
 	case AV_REMOTE_SVCLASS_ID:
 		return enabled.control;
@@ -205,6 +208,8 @@ static void handle_uuid(const char *uuidstr, struct audio_device *device)
 		break;
 	case AUDIO_SOURCE_SVCLASS_ID:
 		debug("Found Audio Source");
+		if (device->source == NULL)
+			device->source = source_init(device);
 		break;
 	case AV_REMOTE_SVCLASS_ID:
 	case AV_REMOTE_TARGET_SVCLASS_ID:
diff --git a/audio/source.c b/audio/source.c
new file mode 100644
index 0000000..07fb87b
--- /dev/null
+++ b/audio/source.c
@@ -0,0 +1,868 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C)
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+
+#include "device.h"
+#include "avdtp.h"
+#include "a2dp.h"
+#include "error.h"
+#include "source.h"
+#include "dbus-common.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#define STREAM_SETUP_RETRY_TIMER 2
+
+struct pending_request {
+	DBusConnection *conn;
+	DBusMessage *msg;
+	unsigned int id;
+};
+
+struct source {
+	struct audio_device *dev;
+	struct avdtp *session;
+	struct avdtp_stream *stream;
+	unsigned int cb_id;
+	guint dc_id;
+	guint retry_id;
+	avdtp_session_state_t session_state;
+	avdtp_state_t stream_state;
+	source_state_t state;
+	struct pending_request *connect;
+	struct pending_request *disconnect;
+	DBusConnection *conn;
+};
+
+struct source_state_callback {
+	source_state_cb cb;
+	void *user_data;
+	unsigned int id;
+};
+
+static GSList *source_callbacks = NULL;
+
+static unsigned int avdtp_callback_id = 0;
+
+static const char *state2str(source_state_t state)
+{
+	switch (state) {
+	case SOURCE_STATE_DISCONNECTED:
+		return "disconnected";
+	case SOURCE_STATE_CONNECTING:
+		return "connecting";
+	case SOURCE_STATE_CONNECTED:
+		return "connected";
+	case SOURCE_STATE_PLAYING:
+		return "playing";
+	default:
+		error("Invalid source state %d", state);
+		return NULL;
+	}
+}
+
+static void source_set_state(struct audio_device *dev, source_state_t new_state)
+{
+	struct source *source = dev->source;
+	const char *state_str;
+	source_state_t old_state = source->state;
+	GSList *l;
+
+	source->state = new_state;
+
+	state_str = state2str(new_state);
+	if (state_str)
+		emit_property_changed(dev->conn, dev->path,
+					AUDIO_SOURCE_INTERFACE, "State",
+					DBUS_TYPE_STRING, &state_str);
+
+	for (l = source_callbacks; l != NULL; l = l->next) {
+		struct source_state_callback *cb = l->data;
+		cb->cb(dev, old_state, new_state, cb->user_data);
+	}
+}
+
+static void avdtp_state_callback(struct audio_device *dev,
+					struct avdtp *session,
+					avdtp_session_state_t old_state,
+					avdtp_session_state_t new_state,
+					void *user_data)
+{
+	struct source *source = dev->source;
+
+	if (source == NULL)
+		return;
+
+	switch (new_state) {
+	case AVDTP_SESSION_STATE_DISCONNECTED:
+		if (source->state != SOURCE_STATE_CONNECTING) {
+			gboolean value = FALSE;
+			g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_SOURCE_INTERFACE, "Disconnected",
+					DBUS_TYPE_INVALID);
+			emit_property_changed(dev->conn, dev->path,
+					AUDIO_SOURCE_INTERFACE, "Connected",
+					DBUS_TYPE_BOOLEAN, &value);
+			if (source->dc_id) {
+				device_remove_disconnect_watch(dev->btd_dev,
+								source->dc_id);
+				source->dc_id = 0;
+			}
+		}
+		source_set_state(dev, SOURCE_STATE_DISCONNECTED);
+		break;
+	case AVDTP_SESSION_STATE_CONNECTING:
+		source_set_state(dev, SOURCE_STATE_CONNECTING);
+		break;
+	case AVDTP_SESSION_STATE_CONNECTED:
+		break;
+	}
+
+	source->session_state = new_state;
+}
+
+static void pending_request_free(struct audio_device *dev,
+					struct pending_request *pending)
+{
+	if (pending->conn)
+		dbus_connection_unref(pending->conn);
+	if (pending->msg)
+		dbus_message_unref(pending->msg);
+	if (pending->id)
+		a2dp_sink_cancel(dev, pending->id);
+
+	g_free(pending);
+}
+
+static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
+				void *user_data)
+{
+	struct audio_device *device = user_data;
+	struct source *source = device->source;
+
+	debug("Source: disconnect %s", device->path);
+
+	avdtp_close(source->session, source->stream);
+}
+
+static void stream_state_changed(struct avdtp_stream *stream,
+					avdtp_state_t old_state,
+					avdtp_state_t new_state,
+					struct avdtp_error *err,
+					void *user_data)
+{
+	struct audio_device *dev = user_data;
+	struct source *source = dev->source;
+	gboolean value;
+
+	if (err)
+		return;
+
+	switch (new_state) {
+	case AVDTP_STATE_IDLE:
+		if (source->disconnect) {
+			DBusMessage *reply;
+			struct pending_request *p;
+
+			p = source->disconnect;
+			source->disconnect = NULL;
+
+			reply = dbus_message_new_method_return(p->msg);
+			g_dbus_send_message(p->conn, reply);
+			pending_request_free(dev, p);
+		}
+
+		if (source->dc_id) {
+			device_remove_disconnect_watch(dev->btd_dev,
+							source->dc_id);
+			source->dc_id = 0;
+		}
+
+		if (source->session) {
+			avdtp_unref(source->session);
+			source->session = NULL;
+		}
+		source->stream = NULL;
+		source->cb_id = 0;
+		break;
+	case AVDTP_STATE_OPEN:
+		if (old_state == AVDTP_STATE_CONFIGURED &&
+				source->state == SOURCE_STATE_CONNECTING) {
+			value = TRUE;
+			g_dbus_emit_signal(dev->conn, dev->path,
+						AUDIO_SOURCE_INTERFACE,
+						"Connected",
+						DBUS_TYPE_INVALID);
+			emit_property_changed(dev->conn, dev->path,
+						AUDIO_SOURCE_INTERFACE,
+						"Connected",
+						DBUS_TYPE_BOOLEAN, &value);
+			source->dc_id = device_add_disconnect_watch(dev->btd_dev,
+								disconnect_cb,
+								dev, NULL);
+		} else if (old_state == AVDTP_STATE_STREAMING) {
+			value = FALSE;
+			g_dbus_emit_signal(dev->conn, dev->path,
+						AUDIO_SOURCE_INTERFACE,
+						"Stopped",
+						DBUS_TYPE_INVALID);
+			emit_property_changed(dev->conn, dev->path,
+						AUDIO_SOURCE_INTERFACE,
+						"Playing",
+						DBUS_TYPE_BOOLEAN, &value);
+		}
+		source_set_state(dev, SOURCE_STATE_CONNECTED);
+		break;
+	case AVDTP_STATE_STREAMING:
+		value = TRUE;
+		g_dbus_emit_signal(dev->conn, dev->path, AUDIO_SOURCE_INTERFACE,
+					"Playing", DBUS_TYPE_INVALID);
+		emit_property_changed(dev->conn, dev->path,
+					AUDIO_SOURCE_INTERFACE, "Playing",
+					DBUS_TYPE_BOOLEAN, &value);
+		source_set_state(dev, SOURCE_STATE_PLAYING);
+		break;
+	case AVDTP_STATE_CONFIGURED:
+	case AVDTP_STATE_CLOSING:
+	case AVDTP_STATE_ABORTING:
+	default:
+		break;
+	}
+
+	source->stream_state = new_state;
+}
+
+static DBusHandlerResult error_failed(DBusConnection *conn,
+					DBusMessage *msg, const char * desc)
+{
+	return error_common_reply(conn, msg, ERROR_INTERFACE ".Failed", desc);
+}
+
+static gboolean stream_setup_retry(gpointer user_data)
+{
+	struct source *source = user_data;
+	struct pending_request *pending = source->connect;
+
+	source->retry_id = 0;
+
+	if (source->stream_state >= AVDTP_STATE_OPEN) {
+		debug("Stream successfully created, after XCASE connect:connect");
+		if (pending->msg) {
+			DBusMessage *reply;
+			reply = dbus_message_new_method_return(pending->msg);
+			g_dbus_send_message(pending->conn, reply);
+		}
+	} else {
+		debug("Stream setup failed, after XCASE connect:connect");
+		if (pending->msg)
+			error_failed(pending->conn, pending->msg, "Stream setup failed");
+	}
+
+	source->connect = NULL;
+	pending_request_free(source->dev, pending);
+
+	return FALSE;
+}
+
+static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep,
+					struct avdtp_stream *stream,
+					struct avdtp_error *err, void *user_data)
+{
+	struct source *source = user_data;
+	struct pending_request *pending;
+
+	pending = source->connect;
+
+	pending->id = 0;
+
+	if (stream) {
+		debug("Stream successfully created");
+
+		if (pending->msg) {
+			DBusMessage *reply;
+			reply = dbus_message_new_method_return(pending->msg);
+			g_dbus_send_message(pending->conn, reply);
+		}
+
+		source->connect = NULL;
+		pending_request_free(source->dev, pending);
+
+		return;
+	}
+
+	avdtp_unref(source->session);
+	source->session = NULL;
+	if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO
+			&& avdtp_error_posix_errno(err) != EHOSTDOWN) {
+		debug("connect:connect XCASE detected");
+		source->retry_id = g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
+							stream_setup_retry,
+							source);
+	} else {
+		if (pending->msg)
+			error_failed(pending->conn, pending->msg, "Stream setup failed");
+		source->connect = NULL;
+		pending_request_free(source->dev, pending);
+		debug("Stream setup failed : %s", avdtp_strerror(err));
+	}
+}
+
+static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+{
+	switch (freq) {
+	case SBC_SAMPLING_FREQ_16000:
+	case SBC_SAMPLING_FREQ_32000:
+		return 53;
+	case SBC_SAMPLING_FREQ_44100:
+		switch (mode) {
+		case SBC_CHANNEL_MODE_MONO:
+		case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+			return 31;
+		case SBC_CHANNEL_MODE_STEREO:
+		case SBC_CHANNEL_MODE_JOINT_STEREO:
+			return 53;
+		default:
+			error("Invalid channel mode %u", mode);
+			return 53;
+		}
+	case SBC_SAMPLING_FREQ_48000:
+		switch (mode) {
+		case SBC_CHANNEL_MODE_MONO:
+		case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+			return 29;
+		case SBC_CHANNEL_MODE_STEREO:
+		case SBC_CHANNEL_MODE_JOINT_STEREO:
+			return 51;
+		default:
+			error("Invalid channel mode %u", mode);
+			return 51;
+		}
+	default:
+		error("Invalid sampling freq %u", freq);
+		return 53;
+	}
+}
+
+static gboolean select_sbc_params(struct sbc_codec_cap *cap,
+					struct sbc_codec_cap *supported)
+{
+	unsigned int max_bitpool, min_bitpool;
+
+	memset(cap, 0, sizeof(struct sbc_codec_cap));
+
+	cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+	cap->cap.media_codec_type = A2DP_CODEC_SBC;
+
+	if (supported->frequency & SBC_SAMPLING_FREQ_44100)
+		cap->frequency = SBC_SAMPLING_FREQ_44100;
+	else if (supported->frequency & SBC_SAMPLING_FREQ_48000)
+		cap->frequency = SBC_SAMPLING_FREQ_48000;
+	else if (supported->frequency & SBC_SAMPLING_FREQ_32000)
+		cap->frequency = SBC_SAMPLING_FREQ_32000;
+	else if (supported->frequency & SBC_SAMPLING_FREQ_16000)
+		cap->frequency = SBC_SAMPLING_FREQ_16000;
+	else {
+		error("No supported frequencies");
+		return FALSE;
+	}
+
+	if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+		cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+	else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO)
+		cap->channel_mode = SBC_CHANNEL_MODE_STEREO;
+	else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+		cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+	else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO)
+		cap->channel_mode = SBC_CHANNEL_MODE_MONO;
+	else {
+		error("No supported channel modes");
+		return FALSE;
+	}
+
+	if (supported->block_length & SBC_BLOCK_LENGTH_16)
+		cap->block_length = SBC_BLOCK_LENGTH_16;
+	else if (supported->block_length & SBC_BLOCK_LENGTH_12)
+		cap->block_length = SBC_BLOCK_LENGTH_12;
+	else if (supported->block_length & SBC_BLOCK_LENGTH_8)
+		cap->block_length = SBC_BLOCK_LENGTH_8;
+	else if (supported->block_length & SBC_BLOCK_LENGTH_4)
+		cap->block_length = SBC_BLOCK_LENGTH_4;
+	else {
+		error("No supported block lengths");
+		return FALSE;
+	}
+
+	if (supported->subbands & SBC_SUBBANDS_8)
+		cap->subbands = SBC_SUBBANDS_8;
+	else if (supported->subbands & SBC_SUBBANDS_4)
+		cap->subbands = SBC_SUBBANDS_4;
+	else {
+		error("No supported subbands");
+		return FALSE;
+	}
+
+	if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS)
+		cap->allocation_method = SBC_ALLOCATION_LOUDNESS;
+	else if (supported->allocation_method & SBC_ALLOCATION_SNR)
+		cap->allocation_method = SBC_ALLOCATION_SNR;
+
+	min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool);
+	max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode),
+							supported->max_bitpool);
+
+	cap->min_bitpool = min_bitpool;
+	cap->max_bitpool = max_bitpool;
+
+	return TRUE;
+}
+
+static gboolean select_capabilities(struct avdtp *session,
+					struct avdtp_remote_sep *rsep,
+					GSList **caps)
+{
+	struct avdtp_service_capability *media_transport, *media_codec;
+	struct sbc_codec_cap sbc_cap;
+
+	media_codec = avdtp_get_codec(rsep);
+	if (!media_codec)
+		return FALSE;
+
+	select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data);
+
+	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+						NULL, 0);
+
+	*caps = g_slist_append(*caps, media_transport);
+
+	media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
+						sizeof(sbc_cap));
+
+	*caps = g_slist_append(*caps, media_codec);
+
+
+	return TRUE;
+}
+
+static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err,
+				void *user_data)
+{
+	struct source *source = user_data;
+	struct pending_request *pending;
+	struct avdtp_local_sep *lsep;
+	struct avdtp_remote_sep *rsep;
+	struct a2dp_sep *sep;
+	GSList *caps = NULL;
+	int id;
+
+	pending = source->connect;
+
+	if (err) {
+		avdtp_unref(source->session);
+		source->session = NULL;
+		if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO
+				&& avdtp_error_posix_errno(err) != EHOSTDOWN) {
+			debug("connect:connect XCASE detected");
+			source->retry_id =
+				g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
+							stream_setup_retry,
+							source);
+		} else
+			goto failed;
+		return;
+	}
+
+	debug("Discovery complete");
+
+	if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SOURCE, AVDTP_MEDIA_TYPE_AUDIO,
+				A2DP_CODEC_SBC, &lsep, &rsep) < 0) {
+		error("No matching ACP and INT SEPs found");
+		goto failed;
+	}
+
+	if (!select_capabilities(session, rsep, &caps)) {
+		error("Unable to select remote SEP capabilities");
+		goto failed;
+	}
+
+	sep = a2dp_sink_get(session, rsep);
+	if (!sep) {
+		error("Unable to get a local sink SEP");
+		goto failed;
+	}
+
+	id = a2dp_sink_config(source->session, sep, stream_setup_complete,
+				caps, source);
+	if (id == 0)
+		goto failed;
+
+	pending->id = id;
+	return;
+
+failed:
+	if (pending->msg)
+		error_failed(pending->conn, pending->msg, "Stream setup failed");
+	pending_request_free(source->dev, pending);
+	source->connect = NULL;
+	avdtp_unref(source->session);
+	source->session = NULL;
+}
+
+gboolean source_setup_stream(struct source *source, struct avdtp *session)
+{
+	if (source->connect || source->disconnect)
+		return FALSE;
+
+	if (session && !source->session)
+		source->session = avdtp_ref(session);
+
+	if (!source->session)
+		return FALSE;
+
+	avdtp_set_auto_disconnect(source->session, FALSE);
+
+	if (avdtp_discover(source->session, discovery_complete, source) < 0)
+		return FALSE;
+
+	source->connect = g_new0(struct pending_request, 1);
+
+	return TRUE;
+}
+
+static DBusMessage *source_connect(DBusConnection *conn,
+				DBusMessage *msg, void *data)
+{
+	struct audio_device *dev = data;
+	struct source *source = dev->source;
+	struct pending_request *pending;
+
+	if (!source->session)
+		source->session = avdtp_get(&dev->src, &dev->dst);
+
+	if (!source->session)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"Unable to get a session");
+
+	if (source->connect || source->disconnect)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(EBUSY));
+
+	if (source->stream_state >= AVDTP_STATE_OPEN)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".AlreadyConnected",
+						"Device Already Connected");
+
+	if (!source_setup_stream(source, NULL))
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"Failed to create a stream");
+
+	dev->auto_connect = FALSE;
+
+	pending = source->connect;
+
+	pending->conn = dbus_connection_ref(conn);
+	pending->msg = dbus_message_ref(msg);
+
+	debug("stream creation in progress");
+
+	return NULL;
+}
+
+static DBusMessage *source_disconnect(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct source *source = device->source;
+	struct pending_request *pending;
+	int err;
+
+	if (!source->session)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+
+	if (source->connect || source->disconnect)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(EBUSY));
+
+	if (source->stream_state < AVDTP_STATE_OPEN) {
+		DBusMessage *reply = dbus_message_new_method_return(msg);
+		if (!reply)
+			return NULL;
+		avdtp_unref(source->session);
+		source->session = NULL;
+		return reply;
+	}
+
+	err = avdtp_close(source->session, source->stream);
+	if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+						"%s", strerror(-err));
+
+	pending = g_new0(struct pending_request, 1);
+	pending->conn = dbus_connection_ref(conn);
+	pending->msg = dbus_message_ref(msg);
+	source->disconnect = pending;
+
+	return NULL;
+}
+
+static DBusMessage *source_is_connected(DBusConnection *conn,
+					DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct source *source = device->source;
+	DBusMessage *reply;
+	dbus_bool_t connected;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	connected = (source->stream_state >= AVDTP_STATE_CONFIGURED);
+
+	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+					DBUS_TYPE_INVALID);
+
+	return reply;
+}
+
+static DBusMessage *source_get_properties(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct source *source = device->source;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	const char *state;
+	gboolean value;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+	/* Playing */
+	value = (source->stream_state == AVDTP_STATE_STREAMING);
+	dict_append_entry(&dict, "Playing", DBUS_TYPE_BOOLEAN, &value);
+
+	/* Connected */
+	value = (source->stream_state >= AVDTP_STATE_CONFIGURED);
+	dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+	/* State */
+	state = state2str(source->state);
+	if (state)
+		dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state);
+
+	dbus_message_iter_close_container(&iter, &dict);
+
+	return reply;
+}
+
+static GDBusMethodTable source_methods[] = {
+	{ "Connect",		"",	"",	source_connect,
+						G_DBUS_METHOD_FLAG_ASYNC },
+	{ "Disconnect",		"",	"",	source_disconnect,
+						G_DBUS_METHOD_FLAG_ASYNC },
+	{ "IsConnected",	"",	"b",	source_is_connected,
+						G_DBUS_METHOD_FLAG_DEPRECATED },
+	{ "GetProperties",	"",	"a{sv}",source_get_properties },
+	{ NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable source_signals[] = {
+	{ "Connected",			"",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "Disconnected",		"",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "Playing",			"",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "Stopped",			"",	G_DBUS_SIGNAL_FLAG_DEPRECATED },
+	{ "PropertyChanged",		"sv"	},
+	{ NULL, NULL }
+};
+
+static void source_free(struct audio_device *dev)
+{
+	struct source *source = dev->source;
+
+	if (source->cb_id)
+		avdtp_stream_remove_cb(source->session, source->stream,
+					source->cb_id);
+
+	if (source->dc_id)
+		device_remove_disconnect_watch(dev->btd_dev, source->dc_id);
+
+	if (source->session)
+		avdtp_unref(source->session);
+
+	if (source->connect)
+		pending_request_free(dev, source->connect);
+
+	if (source->disconnect)
+		pending_request_free(dev, source->disconnect);
+
+	if (source->retry_id)
+		g_source_remove(source->retry_id);
+
+	g_free(source);
+	dev->source = NULL;
+}
+
+static void path_unregister(void *data)
+{
+	struct audio_device *dev = data;
+
+	debug("Unregistered interface %s on path %s",
+		AUDIO_SOURCE_INTERFACE, dev->path);
+
+	source_free(dev);
+}
+
+void source_unregister(struct audio_device *dev)
+{
+	g_dbus_unregister_interface(dev->conn, dev->path,
+		AUDIO_SOURCE_INTERFACE);
+}
+
+struct source *source_init(struct audio_device *dev)
+{
+	struct source *source;
+
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+					AUDIO_SOURCE_INTERFACE,
+					source_methods, source_signals, NULL,
+					dev, path_unregister))
+		return NULL;
+
+	debug("Registered interface %s on path %s",
+		AUDIO_SOURCE_INTERFACE, dev->path);
+
+	if (avdtp_callback_id == 0)
+		avdtp_callback_id = avdtp_add_state_cb(avdtp_state_callback,
+									NULL);
+
+	source = g_new0(struct source, 1);
+
+	source->dev = dev;
+
+	return source;
+}
+
+gboolean source_is_active(struct audio_device *dev)
+{
+	struct source *source = dev->source;
+
+	if (source->session)
+		return TRUE;
+
+	return FALSE;
+}
+
+avdtp_state_t source_get_state(struct audio_device *dev)
+{
+	struct source *source = dev->source;
+
+	return source->stream_state;
+}
+
+gboolean source_new_stream(struct audio_device *dev, struct avdtp *session,
+				struct avdtp_stream *stream)
+{
+	struct source *source = dev->source;
+
+	if (source->stream)
+		return FALSE;
+
+	if (!source->session)
+		source->session = avdtp_ref(session);
+
+	source->stream = stream;
+
+	source->cb_id = avdtp_stream_add_cb(session, stream,
+						stream_state_changed, dev);
+
+	return TRUE;
+}
+
+gboolean source_shutdown(struct source *source)
+{
+	if (!source->stream)
+		return FALSE;
+
+	if (avdtp_close(source->session, source->stream) < 0)
+		return FALSE;
+
+	return TRUE;
+}
+
+unsigned int source_add_state_cb(source_state_cb cb, void *user_data)
+{
+	struct source_state_callback *state_cb;
+	static unsigned int id = 0;
+
+	state_cb = g_new(struct source_state_callback, 1);
+	state_cb->cb = cb;
+	state_cb->user_data = user_data;
+	state_cb->id = ++id;
+
+	source_callbacks = g_slist_append(source_callbacks, state_cb);
+
+	return state_cb->id;
+}
+
+gboolean source_remove_state_cb(unsigned int id)
+{
+	GSList *l;
+
+	for (l = source_callbacks; l != NULL; l = l->next) {
+		struct source_state_callback *cb = l->data;
+		if (cb && cb->id == id) {
+			source_callbacks = g_slist_remove(source_callbacks, cb);
+			g_free(cb);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
diff --git a/audio/source.h b/audio/source.h
new file mode 100644
index 0000000..d7dbfce
--- /dev/null
+++ b/audio/source.h
@@ -0,0 +1,48 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2009
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#define AUDIO_SINK_INTERFACE "org.bluez.AudioSource"
+
+typedef enum {
+	SOURCE_STATE_DISCONNECTED,
+	SOURCE_STATE_CONNECTING,
+	SOURCE_STATE_CONNECTED,
+	SOURCE_STATE_PLAYING,
+} source_state_t;
+
+typedef void (*source_state_cb) (struct audio_device *dev,
+				source_state_t old_state,
+				source_state_t new_state,
+				void *user_data);
+
+unsigned int source_add_state_cb(source_state_cb cb, void *user_data);
+gboolean source_remove_state_cb(unsigned int id);
+
+struct source *source_init(struct audio_device *dev);
+void source_unregister(struct audio_device *dev);
+gboolean source_is_active(struct audio_device *dev);
+avdtp_state_t source_get_state(struct audio_device *dev);
+gboolean source_new_stream(struct audio_device *dev, struct avdtp *session,
+				struct avdtp_stream *stream);
+gboolean source_setup_stream(struct source *source, struct avdtp *session);
+gboolean source_shutdown(struct source *source);
-- 
1.6.0.4

From 771f28a5295c9285138038fdb8ee3aeedafc3495 Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Jo=C3=A3o=20Paulo=20Rechi=20Vita?= <jprvita@xxxxxxxxx>
Date: Sat, 13 Jun 2009 02:38:22 -0300
Subject: [PATCH] Create one SBC Sink when registering A2DP server.

---
 audio/a2dp.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/audio/a2dp.c b/audio/a2dp.c
index 721ad16..59932cd 100644
--- a/audio/a2dp.c
+++ b/audio/a2dp.c
@@ -1129,7 +1129,7 @@ static struct a2dp_server *find_server(GSList *list, const bdaddr_t *src)
 
 int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
 {
-	int sbc_srcs = 1, sbc_sinks = 0;
+	int sbc_srcs = 1, sbc_sinks = 1;
 	int mpeg12_srcs = 0, mpeg12_sinks = 0;
 	gboolean source = TRUE, sink = TRUE;
 	char *str;
-- 
1.6.0.4


[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux