All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC BlueZ v0 00/10] HSP plugin
@ 2013-07-12 10:54 Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 01/10] media: Expose Media API internally Mikel Astiz
                   ` (10 more replies)
  0 siblings, 11 replies; 14+ messages in thread
From: Mikel Astiz @ 2013-07-12 10:54 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Mikel Astiz

From: Mikel Astiz <mikel.astiz@bmw-carit.de>

GNOME 3.10 freeze date is 2013-08-19 and they want to upgrade to BlueZ 5.

One of the arguments against the upgrade is the lack of headset support. oFono should be convering this feature but it's development as well as the required work in PulseAudio are advancing too slowly for the mentioned target date.

This patchset proposes a simple HSP plugin implementing the AG role, which is the most common use-case in desktop environments. It's a fallback alternative to a full-featured HSP/HFP implementation (i.e. oFono) in case the later is not available (i.e. not fully implemented or packaged in a specific distro).

I think we could all agree about this not being the best long-term strategy, due to the overlap and conflict with oFono, but it might be useful in the short-term to boost the adoption of BlueZ 5.

The plugin approach is more intrusive than I first thought because the Media API code was significantly simplified when all HSP/HFP code was removed. Hence, the patchset brings this code back along with the necessary APIs to be able to implement this inside a plugin.

In order to test this, some PulseAudio patches are required (the last 5 patches of "[RFC next v5 00/11] bluetooth: BlueZ 5 development patches"). These were never merged in PA due to a chicken-egg problem: there was no code in BlueZ to test against.

Mikel Astiz (10):
  media: Expose Media API internally
  media: Add callback to report new endpoints
  transport: Regroup a2dp-specific members in struct
  transport: Add API to register drivers
  transport: Add API to report suspend/resume complete
  transport: Add microphone/speaker gains
  audio: Add function to remove inactive devices
  hsp: Add initial HSP plugin
  hsp: Add Media API integration
  hsp: Implement media transport driver

 Makefile.plugins           |    3 +
 plugins/hsp.c              | 1357 ++++++++++++++++++++++++++++++++++++++++++++
 profiles/audio/manager.c   |   12 +
 profiles/audio/manager.h   |    1 +
 profiles/audio/media.c     |  132 ++++-
 profiles/audio/media.h     |   30 +
 profiles/audio/transport.c |  384 +++++++++----
 profiles/audio/transport.h |   29 +
 8 files changed, 1818 insertions(+), 130 deletions(-)
 create mode 100644 plugins/hsp.c

-- 
1.8.1.4


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [RFC BlueZ v0 01/10] media: Expose Media API internally
  2013-07-12 10:54 [RFC BlueZ v0 00/10] HSP plugin Mikel Astiz
@ 2013-07-12 10:54 ` Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 02/10] media: Add callback to report new endpoints Mikel Astiz
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Mikel Astiz @ 2013-07-12 10:54 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Mikel Astiz

From: Mikel Astiz <mikel.astiz@bmw-carit.de>

The API is exposed in D-Bus but not internally, so make this possible
in case some plugins are interested in using it.
---
 profiles/audio/media.c | 52 +++++++++++++++++++++++++++++++++-----------------
 profiles/audio/media.h | 20 +++++++++++++++++++
 2 files changed, 54 insertions(+), 18 deletions(-)

diff --git a/profiles/audio/media.c b/profiles/audio/media.c
index d4d82cf..c24ac7d 100644
--- a/profiles/audio/media.c
+++ b/profiles/audio/media.c
@@ -217,7 +217,7 @@ static void media_endpoint_exit(DBusConnection *connection, void *user_data)
 	media_endpoint_remove(endpoint);
 }
 
-static void clear_configuration(struct media_endpoint *endpoint,
+void btd_media_endpoint_clear_configuration(struct media_endpoint *endpoint,
 					struct media_transport *transport)
 {
 	DBusMessage *msg;
@@ -245,7 +245,8 @@ static void clear_endpoint(struct media_endpoint *endpoint)
 	media_endpoint_cancel_all(endpoint);
 
 	while (endpoint->transports != NULL)
-		clear_configuration(endpoint, endpoint->transports->data);
+		btd_media_endpoint_clear_configuration(endpoint,
+						endpoint->transports->data);
 }
 
 static void endpoint_reply(DBusPendingCall *call, void *user_data)
@@ -352,7 +353,8 @@ static gboolean media_endpoint_async_call(DBusMessage *msg,
 	return TRUE;
 }
 
-static gboolean select_configuration(struct media_endpoint *endpoint,
+gboolean btd_media_endpoint_select_configuration(
+						struct media_endpoint *endpoint,
 						uint8_t *capabilities,
 						size_t length,
 						media_endpoint_cb_t cb,
@@ -401,7 +403,8 @@ static struct media_transport *find_device_transport(
 	return match->data;
 }
 
-static gboolean set_configuration(struct media_endpoint *endpoint,
+struct media_transport *btd_media_endpoint_set_configuration(
+					struct media_endpoint *endpoint,
 					struct audio_device *device,
 					uint8_t *configuration, size_t size,
 					media_endpoint_cb_t cb,
@@ -417,12 +420,12 @@ static gboolean set_configuration(struct media_endpoint *endpoint,
 	transport = find_device_transport(endpoint, device);
 
 	if (transport != NULL)
-		return FALSE;
+		return NULL;
 
 	transport = media_transport_create(device, configuration, size,
 								endpoint);
 	if (transport == NULL)
-		return FALSE;
+		return NULL;
 
 	msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
 						MEDIA_ENDPOINT_INTERFACE,
@@ -430,11 +433,9 @@ static gboolean set_configuration(struct media_endpoint *endpoint,
 	if (msg == NULL) {
 		error("Couldn't allocate D-Bus message");
 		media_transport_destroy(transport);
-		return FALSE;
+		return NULL;
 	}
 
-	endpoint->transports = g_slist_append(endpoint->transports, transport);
-
 	dbus_message_iter_init_append(msg, &iter);
 
 	path = media_transport_get_path(transport);
@@ -442,7 +443,14 @@ static gboolean set_configuration(struct media_endpoint *endpoint,
 
 	g_dbus_get_properties(conn, path, "org.bluez.MediaTransport1", &iter);
 
-	return media_endpoint_async_call(msg, endpoint, cb, user_data, destroy);
+	if (!media_endpoint_async_call(msg, endpoint, cb, user_data, destroy)) {
+		media_transport_destroy(transport);
+		return NULL;
+	}
+
+	endpoint->transports = g_slist_append(endpoint->transports, transport);
+
+	return transport;
 }
 
 static void release_endpoint(struct media_endpoint *endpoint)
@@ -516,8 +524,8 @@ static int select_config(struct a2dp_sep *sep, uint8_t *capabilities,
 	data->setup = setup;
 	data->cb = cb;
 
-	if (select_configuration(endpoint, capabilities, length,
-					select_cb, data, g_free) == TRUE)
+	if (btd_media_endpoint_select_configuration(endpoint, capabilities,
+				length, select_cb, data, g_free) == TRUE)
 		return 0;
 
 	g_free(data);
@@ -545,8 +553,8 @@ static int set_config(struct a2dp_sep *sep, struct audio_device *dev,
 	data->setup = setup;
 	data->cb = cb;
 
-	if (set_configuration(endpoint, dev, configuration, length,
-					config_cb, data, g_free) == TRUE)
+	if (btd_media_endpoint_set_configuration(endpoint, dev, configuration,
+				length, config_cb, data, g_free) != NULL)
 		return 0;
 
 	g_free(data);
@@ -615,27 +623,35 @@ static gboolean endpoint_init_a2dp_sink(struct media_endpoint *endpoint,
 	return TRUE;
 }
 
-static struct media_adapter *find_adapter(struct btd_device *device)
+static struct media_adapter *find_adapter(struct btd_adapter *btd_adapter)
 {
 	GSList *l;
 
 	for (l = adapters; l; l = l->next) {
 		struct media_adapter *adapter = l->data;
 
-		if (adapter->btd_adapter == device_get_adapter(device))
+		if (adapter->btd_adapter == btd_adapter)
 			return adapter;
 	}
 
 	return NULL;
 }
 
+struct media_endpoint *btd_media_endpoint_find(struct btd_adapter *btd_adapter,
+							const char *uuid)
+{
+	struct media_adapter *adapter = find_adapter(btd_adapter);
+
+	return media_adapter_find_endpoint(adapter, NULL, NULL, uuid);
+}
+
 static bool endpoint_properties_exists(const char *uuid,
 						struct btd_device *dev,
 						void *user_data)
 {
 	struct media_adapter *adapter;
 
-	adapter = find_adapter(dev);
+	adapter = find_adapter(device_get_adapter(dev));
 	if (adapter == NULL)
 		return false;
 
@@ -686,7 +702,7 @@ static bool endpoint_properties_get(const char *uuid,
 	DBusMessageIter dict;
 	GSList *l;
 
-	adapter = find_adapter(dev);
+	adapter = find_adapter(device_get_adapter(dev));
 	if (adapter == NULL)
 		return false;
 
diff --git a/profiles/audio/media.h b/profiles/audio/media.h
index dd630d4..ab187dd 100644
--- a/profiles/audio/media.h
+++ b/profiles/audio/media.h
@@ -23,6 +23,7 @@
  */
 
 struct media_endpoint;
+struct media_transport;
 
 typedef void (*media_endpoint_cb_t) (struct media_endpoint *endpoint,
 					void *ret, int size, void *user_data);
@@ -33,3 +34,22 @@ void media_unregister(struct btd_adapter *btd_adapter);
 struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint);
 const char *media_endpoint_get_uuid(struct media_endpoint *endpoint);
 uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint);
+
+struct media_endpoint *btd_media_endpoint_find(struct btd_adapter *btd_adapter,
+							const char *uuid);
+struct media_transport *btd_media_endpoint_set_configuration(
+					struct media_endpoint *endpoint,
+					struct audio_device *device,
+					uint8_t *configuration, size_t size,
+					media_endpoint_cb_t cb,
+					void *user_data,
+					GDestroyNotify destroy);
+gboolean btd_media_endpoint_select_configuration(
+						struct media_endpoint *endpoint,
+						uint8_t *capabilities,
+						size_t length,
+						media_endpoint_cb_t cb,
+						void *user_data,
+						GDestroyNotify destroy);
+void btd_media_endpoint_clear_configuration(struct media_endpoint *endpoint,
+					struct media_transport *transport);
-- 
1.8.1.4


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [RFC BlueZ v0 02/10] media: Add callback to report new endpoints
  2013-07-12 10:54 [RFC BlueZ v0 00/10] HSP plugin Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 01/10] media: Expose Media API internally Mikel Astiz
@ 2013-07-12 10:54 ` Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 03/10] transport: Regroup a2dp-specific members in struct Mikel Astiz
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Mikel Astiz @ 2013-07-12 10:54 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Mikel Astiz

From: Mikel Astiz <mikel.astiz@bmw-carit.de>

When a new endpoint gets registered, propagate information to any
interested user.
---
 profiles/audio/media.c | 80 +++++++++++++++++++++++++++++++++++++++++++++-----
 profiles/audio/media.h | 10 +++++++
 2 files changed, 82 insertions(+), 8 deletions(-)

diff --git a/profiles/audio/media.c b/profiles/audio/media.c
index c24ac7d..8fa2fa3 100644
--- a/profiles/audio/media.c
+++ b/profiles/audio/media.c
@@ -60,6 +60,7 @@ struct media_adapter {
 	struct btd_adapter	*btd_adapter;
 	GSList			*endpoints;	/* Endpoints list */
 	GSList			*players;	/* Players list */
+	GSList			*endpoint_register_callbacks;
 };
 
 struct endpoint_request {
@@ -109,8 +110,16 @@ struct media_player {
 	bool			control;
 };
 
+struct endpoint_register_callback {
+	media_endpoint_register_cb_t cb;
+	void			*user_data;
+	unsigned int		id;
+};
+
 static GSList *adapters = NULL;
 
+static struct media_adapter *find_adapter(struct btd_adapter *adapter);
+
 static void endpoint_request_free(struct endpoint_request *request)
 {
 	if (request->call)
@@ -389,6 +398,54 @@ static int transport_device_cmp(gconstpointer data, gconstpointer user_data)
 	return -1;
 }
 
+unsigned int btd_media_endpoint_add_register_cb(struct btd_adapter *btd_adapter,
+						media_endpoint_register_cb_t cb,
+						void *user_data)
+{
+	struct media_adapter *adapter;
+	struct endpoint_register_callback *register_cb;
+	static unsigned int id = 0;
+
+	adapter = find_adapter(btd_adapter);
+	if (adapter == NULL)
+		return 0;
+
+	register_cb = g_new0(struct endpoint_register_callback, 1);
+	register_cb->cb = cb;
+	register_cb->user_data = user_data;
+	register_cb->id = ++id;
+
+	adapter->endpoint_register_callbacks = g_slist_append(
+			adapter->endpoint_register_callbacks, register_cb);
+
+	return register_cb->id;
+}
+
+bool btd_media_endpoint_remove_register_cb(struct btd_adapter *btd_adapter,
+								unsigned int id)
+{
+	struct media_adapter *adapter;
+	GSList *l;
+
+	adapter = find_adapter(btd_adapter);
+	if (adapter == NULL)
+		return false;
+
+	for (l = adapter->endpoint_register_callbacks; l != NULL;
+							l = g_slist_next(l)) {
+		struct endpoint_register_callback *cb = l->data;
+
+		if (cb && cb->id == id) {
+			adapter->endpoint_register_callbacks = g_slist_remove(
+				adapter->endpoint_register_callbacks, cb);
+			g_free(cb);
+			return true;
+		}
+	}
+
+	return false;
+}
+
 static struct media_transport *find_device_transport(
 					struct media_endpoint *endpoint,
 					struct audio_device *device)
@@ -738,6 +795,7 @@ static struct media_endpoint *media_endpoint_create(struct media_adapter *adapte
 						int *err)
 {
 	struct media_endpoint *endpoint;
+	GSList *l;
 	gboolean succeeded;
 
 	endpoint = g_new0(struct media_endpoint, 1);
@@ -792,6 +850,13 @@ static struct media_endpoint *media_endpoint_create(struct media_adapter *adapte
 	adapter->endpoints = g_slist_append(adapter->endpoints, endpoint);
 	info("Endpoint registered: sender=%s path=%s", sender, path);
 
+	for (l = adapter->endpoint_register_callbacks; l != NULL;
+							l = g_slist_next(l)) {
+		struct endpoint_register_callback *cb = l->data;
+
+		cb->cb(endpoint, cb->user_data);
+	}
+
 	if (err)
 		*err = 0;
 	return endpoint;
@@ -1821,6 +1886,8 @@ static void path_free(void *data)
 	while (adapter->players)
 		media_player_destroy(adapter->players->data);
 
+	g_slist_free(adapter->endpoint_register_callbacks);
+
 	adapters = g_slist_remove(adapters, adapter);
 
 	btd_adapter_unref(adapter->btd_adapter);
@@ -1852,18 +1919,15 @@ int media_register(struct btd_adapter *btd_adapter)
 
 void media_unregister(struct btd_adapter *btd_adapter)
 {
-	GSList *l;
+	struct media_adapter *adapter;
 
-	for (l = adapters; l; l = l->next) {
-		struct media_adapter *adapter = l->data;
+	adapter = find_adapter(btd_adapter);
+	if (adapter == NULL)
+		return;
 
-		if (adapter->btd_adapter == btd_adapter) {
-			g_dbus_unregister_interface(btd_get_dbus_connection(),
+	g_dbus_unregister_interface(btd_get_dbus_connection(),
 						adapter_get_path(btd_adapter),
 						MEDIA_INTERFACE);
-			return;
-		}
-	}
 }
 
 struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint)
diff --git a/profiles/audio/media.h b/profiles/audio/media.h
index ab187dd..25013d9 100644
--- a/profiles/audio/media.h
+++ b/profiles/audio/media.h
@@ -28,6 +28,9 @@ struct media_transport;
 typedef void (*media_endpoint_cb_t) (struct media_endpoint *endpoint,
 					void *ret, int size, void *user_data);
 
+typedef void (*media_endpoint_register_cb_t) (struct media_endpoint *endpoint,
+							void *user_data);
+
 int media_register(struct btd_adapter *btd_adapter);
 void media_unregister(struct btd_adapter *btd_adapter);
 
@@ -37,6 +40,13 @@ uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint);
 
 struct media_endpoint *btd_media_endpoint_find(struct btd_adapter *btd_adapter,
 							const char *uuid);
+
+unsigned int btd_media_endpoint_add_register_cb(struct btd_adapter *btd_adapter,
+						media_endpoint_register_cb_t cb,
+						void *user_data);
+bool btd_media_endpoint_remove_register_cb(struct btd_adapter *btd_adapter,
+							unsigned int id);
+
 struct media_transport *btd_media_endpoint_set_configuration(
 					struct media_endpoint *endpoint,
 					struct audio_device *device,
-- 
1.8.1.4


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [RFC BlueZ v0 03/10] transport: Regroup a2dp-specific members in struct
  2013-07-12 10:54 [RFC BlueZ v0 00/10] HSP plugin Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 01/10] media: Expose Media API internally Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 02/10] media: Add callback to report new endpoints Mikel Astiz
@ 2013-07-12 10:54 ` Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 04/10] transport: Add API to register drivers Mikel Astiz
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Mikel Astiz @ 2013-07-12 10:54 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Mikel Astiz

From: Mikel Astiz <mikel.astiz@bmw-carit.de>

Any property accessible in D-Bus should be associated to the transport
object regardless of its type.
---
 profiles/audio/transport.c | 65 ++++++++++++++++++++--------------------------
 1 file changed, 28 insertions(+), 37 deletions(-)

diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index f585c3a..9e9efe3 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
@@ -79,8 +79,8 @@ struct media_owner {
 
 struct a2dp_transport {
 	struct avdtp		*session;
-	uint16_t		delay;
-	uint16_t		volume;
+	guint			source_watch;
+	guint			sink_watch;
 };
 
 struct media_transport {
@@ -93,10 +93,9 @@ struct media_transport {
 	int			fd;		/* Transport file descriptor */
 	uint16_t		imtu;		/* Transport input mtu */
 	uint16_t		omtu;		/* Transport output mtu */
+	uint16_t		delay;
+	uint16_t		volume;
 	transport_state_t	state;
-	guint			hs_watch;
-	guint			source_watch;
-	guint			sink_watch;
 	guint			(*resume) (struct media_transport *transport,
 					struct media_owner *owner);
 	guint			(*suspend) (struct media_transport *transport,
@@ -167,12 +166,6 @@ void media_transport_destroy(struct media_transport *transport)
 {
 	char *path;
 
-	if (transport->sink_watch)
-		sink_remove_state_cb(transport->sink_watch);
-
-	if (transport->source_watch)
-		source_remove_state_cb(transport->source_watch);
-
 	path = g_strdup(transport->path);
 	g_dbus_unregister_interface(btd_get_dbus_connection(), path,
 						MEDIA_TRANSPORT_INTERFACE);
@@ -606,18 +599,17 @@ static gboolean get_state(const GDBusPropertyTable *property,
 static gboolean delay_exists(const GDBusPropertyTable *property, void *data)
 {
 	struct media_transport *transport = data;
-	struct a2dp_transport *a2dp = transport->data;
 
-	return a2dp->delay != 0;
+	return transport->delay != 0;
 }
 
 static gboolean get_delay(const GDBusPropertyTable *property,
 					DBusMessageIter *iter, void *data)
 {
 	struct media_transport *transport = data;
-	struct a2dp_transport *a2dp = transport->data;
 
-	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &a2dp->delay);
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16,
+							&transport->delay);
 
 	return TRUE;
 }
@@ -625,18 +617,17 @@ static gboolean get_delay(const GDBusPropertyTable *property,
 static gboolean volume_exists(const GDBusPropertyTable *property, void *data)
 {
 	struct media_transport *transport = data;
-	struct a2dp_transport *a2dp = transport->data;
 
-	return a2dp->volume <= 127;
+	return transport->volume <= 127;
 }
 
 static gboolean get_volume(const GDBusPropertyTable *property,
 					DBusMessageIter *iter, void *data)
 {
 	struct media_transport *transport = data;
-	struct a2dp_transport *a2dp = transport->data;
 
-	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &a2dp->volume);
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16,
+							&transport->volume);
 
 	return TRUE;
 }
@@ -646,7 +637,6 @@ static void set_volume(const GDBusPropertyTable *property,
 			void *data)
 {
 	struct media_transport *transport = data;
-	struct a2dp_transport *a2dp = transport->data;
 	uint16_t volume;
 
 	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) {
@@ -665,10 +655,10 @@ static void set_volume(const GDBusPropertyTable *property,
 		return;
 	}
 
-	if (a2dp->volume != volume)
+	if (transport->volume != volume)
 		avrcp_set_volume(transport->device, volume);
 
-	a2dp->volume = volume;
+	transport->volume = volume;
 
 	g_dbus_pending_property_success(id);
 }
@@ -703,6 +693,12 @@ static void destroy_a2dp(void *data)
 {
 	struct a2dp_transport *a2dp = data;
 
+	if (a2dp->sink_watch)
+		sink_remove_state_cb(a2dp->sink_watch);
+
+	if (a2dp->source_watch)
+		source_remove_state_cb(a2dp->source_watch);
+
 	if (a2dp->session)
 		avdtp_unref(a2dp->session);
 
@@ -788,6 +784,7 @@ struct media_transport *media_transport_create(struct audio_device *device,
 	transport->path = g_strdup_printf("%s/fd%d",
 				device_get_path(device->btd_dev), fd++);
 	transport->fd = -1;
+	transport->volume = -1;
 
 	uuid = media_endpoint_get_uuid(endpoint);
 	if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0 ||
@@ -803,14 +800,13 @@ struct media_transport *media_transport_create(struct audio_device *device,
 		transport->destroy = destroy_a2dp;
 
 		if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) {
-			a2dp->volume = -1;
-			transport->sink_watch = sink_add_state_cb(device,
+			a2dp->sink_watch = sink_add_state_cb(device,
 							sink_state_changed,
 							transport);
 		} else {
-			a2dp->volume = 127;
-			avrcp_set_volume(device, a2dp->volume);
-			transport->source_watch = source_add_state_cb(device,
+			transport->volume = 127;
+			avrcp_set_volume(device, transport->volume);
+			a2dp->source_watch = source_add_state_cb(device,
 							source_state_changed,
 							transport);
 		}
@@ -842,13 +838,11 @@ const char *media_transport_get_path(struct media_transport *transport)
 void media_transport_update_delay(struct media_transport *transport,
 							uint16_t delay)
 {
-	struct a2dp_transport *a2dp = transport->data;
-
 	/* Check if delay really changed */
-	if (a2dp->delay == delay)
+	if (transport->delay == delay)
 		return;
 
-	a2dp->delay = delay;
+	transport->delay = delay;
 
 	g_dbus_emit_property_changed(btd_get_dbus_connection(),
 					transport->path,
@@ -862,20 +856,17 @@ struct audio_device *media_transport_get_dev(struct media_transport *transport)
 
 uint16_t media_transport_get_volume(struct media_transport *transport)
 {
-	struct a2dp_transport *a2dp = transport->data;
-	return a2dp->volume;
+	return transport->volume;
 }
 
 void media_transport_update_volume(struct media_transport *transport,
 								uint8_t volume)
 {
-	struct a2dp_transport *a2dp = transport->data;
-
 	/* Check if volume really changed */
-	if (a2dp->volume == volume)
+	if (transport->volume == volume)
 		return;
 
-	a2dp->volume = volume;
+	transport->volume = volume;
 
 	g_dbus_emit_property_changed(btd_get_dbus_connection(),
 					transport->path,
-- 
1.8.1.4


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [RFC BlueZ v0 04/10] transport: Add API to register drivers
  2013-07-12 10:54 [RFC BlueZ v0 00/10] HSP plugin Mikel Astiz
                   ` (2 preceding siblings ...)
  2013-07-12 10:54 ` [RFC BlueZ v0 03/10] transport: Regroup a2dp-specific members in struct Mikel Astiz
@ 2013-07-12 10:54 ` Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 05/10] transport: Add API to report suspend/resume complete Mikel Astiz
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Mikel Astiz @ 2013-07-12 10:54 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Mikel Astiz

From: Mikel Astiz <mikel.astiz@bmw-carit.de>

Let plugins define the operations associated to a specific transport
type.
---
 profiles/audio/transport.c | 141 ++++++++++++++++++++++++++++++---------------
 profiles/audio/transport.h |  17 ++++++
 2 files changed, 112 insertions(+), 46 deletions(-)

diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index 9e9efe3..14e4b9b 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
@@ -96,17 +96,12 @@ struct media_transport {
 	uint16_t		delay;
 	uint16_t		volume;
 	transport_state_t	state;
-	guint			(*resume) (struct media_transport *transport,
-					struct media_owner *owner);
-	guint			(*suspend) (struct media_transport *transport,
-					struct media_owner *owner);
-	void			(*cancel) (struct media_transport *transport,
-								guint id);
-	GDestroyNotify		destroy;
+	struct media_transport_driver *driver;
 	void			*data;
 };
 
 static GSList *transports = NULL;
+static GSList *drivers = NULL;
 
 static const char *state2str(transport_state_t state)
 {
@@ -216,7 +211,7 @@ static void media_owner_remove(struct media_owner *owner)
 					dbus_message_get_member(req->msg));
 
 	if (req->id)
-		transport->cancel(transport, req->id);
+		transport->driver->cancel(transport, req->id);
 
 	owner->pending = NULL;
 	if (req->msg)
@@ -253,7 +248,7 @@ static void media_transport_remove_owner(struct media_transport *transport)
 	media_owner_free(owner);
 
 	if (state_in_use(transport->state))
-		transport->suspend(transport, NULL);
+		transport->driver->suspend(transport, NULL);
 }
 
 static gboolean media_transport_set_fd(struct media_transport *transport,
@@ -344,9 +339,6 @@ static guint resume_a2dp(struct media_transport *transport,
 		return 0;
 	}
 
-	if (transport->state == TRANSPORT_STATE_IDLE)
-		transport_set_state(transport, TRANSPORT_STATE_REQUESTING);
-
 	return id;
 }
 
@@ -451,12 +443,15 @@ static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg,
 		return btd_error_not_authorized(msg);
 
 	owner = media_owner_create(msg);
-	id = transport->resume(transport, owner);
+	id = transport->driver->resume(transport, owner);
 	if (id == 0) {
 		media_owner_free(owner);
 		return btd_error_not_authorized(msg);
 	}
 
+	if (transport->state == TRANSPORT_STATE_IDLE)
+		transport_set_state(transport, TRANSPORT_STATE_REQUESTING);
+
 	req = media_request_create(msg, id);
 	media_owner_add(owner, req);
 	media_transport_set_owner(transport, owner);
@@ -482,7 +477,7 @@ static DBusMessage *try_acquire(DBusConnection *conn, DBusMessage *msg,
 		return btd_error_not_available(msg);
 
 	owner = media_owner_create(msg);
-	id = transport->resume(transport, owner);
+	id = transport->driver->resume(transport, owner);
 	if (id == 0) {
 		media_owner_free(owner);
 		return btd_error_not_authorized(msg);
@@ -522,7 +517,7 @@ static DBusMessage *release(DBusConnection *conn, DBusMessage *msg,
 
 	transport_set_state(transport, TRANSPORT_STATE_SUSPENDING);
 
-	id = transport->suspend(transport, owner);
+	id = transport->driver->suspend(transport, owner);
 	if (id == 0) {
 		media_transport_remove_owner(transport);
 		return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
@@ -714,8 +709,7 @@ static void media_transport_free(void *data)
 	if (transport->owner)
 		media_transport_remove_owner(transport);
 
-	if (transport->destroy != NULL)
-		transport->destroy(transport->data);
+	transport->driver->destroy(transport->data);
 
 	g_free(transport->configuration);
 	g_free(transport->path);
@@ -766,52 +760,92 @@ static void source_state_changed(struct audio_device *dev,
 		transport_update_playing(transport, FALSE);
 }
 
+static void *init_a2dp_source(struct media_transport *transport)
+{
+	struct a2dp_transport *a2dp;
+
+	a2dp = g_new0(struct a2dp_transport, 1);
+	a2dp->sink_watch = sink_add_state_cb(transport->device,
+							sink_state_changed,
+							transport);
+
+	return a2dp;
+}
+
+static void *init_a2dp_sink(struct media_transport *transport)
+{
+	struct a2dp_transport *a2dp;
+
+	a2dp = g_new0(struct a2dp_transport, 1);
+	a2dp->source_watch = source_add_state_cb(transport->device,
+							source_state_changed,
+							transport);
+
+	transport->volume = 127;
+	avrcp_set_volume(transport->device, transport->volume);
+
+	return a2dp;
+}
+
+static struct media_transport_driver a2dp_source_driver = {
+	.uuid = A2DP_SOURCE_UUID,
+	.init = init_a2dp_source,
+	.resume = resume_a2dp,
+	.suspend = suspend_a2dp,
+	.cancel = cancel_a2dp,
+	.destroy = destroy_a2dp
+};
+
+static struct media_transport_driver a2dp_sink_driver = {
+	.uuid = A2DP_SINK_UUID,
+	.init = init_a2dp_sink,
+	.resume = resume_a2dp,
+	.suspend = suspend_a2dp,
+	.cancel = cancel_a2dp,
+	.destroy = destroy_a2dp
+};
+
 struct media_transport *media_transport_create(struct audio_device *device,
 						uint8_t *configuration,
 						size_t size, void *data)
 {
 	struct media_endpoint *endpoint = data;
 	struct media_transport *transport;
+	GSList *l;
 	const char *uuid;
 	static int fd = 0;
 
+	if (drivers == NULL) {
+		media_transport_driver_register(&a2dp_source_driver);
+		media_transport_driver_register(&a2dp_sink_driver);
+	}
+
+	uuid = media_endpoint_get_uuid(endpoint);
+
+	for (l = drivers; l != NULL; l = g_slist_next(l)) {
+		struct media_transport_driver *driver = l->data;
+
+		if (strcasecmp(uuid, driver->uuid) == 0)
+			break;
+	}
+
+	if (l == NULL) {
+		DBG("No driver found for UUID %s", uuid);
+		return NULL;
+	}
+
 	transport = g_new0(struct media_transport, 1);
+	transport->driver = l->data;
 	transport->device = device;
 	transport->endpoint = endpoint;
 	transport->configuration = g_new(uint8_t, size);
 	memcpy(transport->configuration, configuration, size);
 	transport->size = size;
-	transport->path = g_strdup_printf("%s/fd%d",
-				device_get_path(device->btd_dev), fd++);
 	transport->fd = -1;
 	transport->volume = -1;
-
-	uuid = media_endpoint_get_uuid(endpoint);
-	if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0 ||
-			strcasecmp(uuid, A2DP_SINK_UUID) == 0) {
-		struct a2dp_transport *a2dp;
-
-		a2dp = g_new0(struct a2dp_transport, 1);
-
-		transport->resume = resume_a2dp;
-		transport->suspend = suspend_a2dp;
-		transport->cancel = cancel_a2dp;
-		transport->data = a2dp;
-		transport->destroy = destroy_a2dp;
-
-		if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) {
-			a2dp->sink_watch = sink_add_state_cb(device,
-							sink_state_changed,
-							transport);
-		} else {
-			transport->volume = 127;
-			avrcp_set_volume(device, transport->volume);
-			a2dp->source_watch = source_add_state_cb(device,
-							source_state_changed,
-							transport);
-		}
-	} else
-		goto fail;
+	transport->data = transport->driver->init(transport);
+	transport->path = g_strdup_printf("%s/fd%d",
+				device_get_path(device->btd_dev), fd++);
 
 	if (g_dbus_register_interface(btd_get_dbus_connection(),
 				transport->path, MEDIA_TRANSPORT_INTERFACE,
@@ -854,6 +888,11 @@ struct audio_device *media_transport_get_dev(struct media_transport *transport)
 	return transport->device;
 }
 
+void *media_transport_get_data(struct media_transport *transport)
+{
+	return transport->data;
+}
+
 uint16_t media_transport_get_volume(struct media_transport *transport)
 {
 	return transport->volume;
@@ -911,3 +950,13 @@ void media_transport_update_device_volume(struct audio_device *dev,
 			media_transport_update_volume(transport, volume);
 	}
 }
+
+void media_transport_driver_register(struct media_transport_driver *driver)
+{
+	drivers = g_slist_append(drivers, driver);
+}
+
+void media_transport_driver_unregister(struct media_transport_driver *driver)
+{
+	drivers = g_slist_remove(drivers, driver);
+}
diff --git a/profiles/audio/transport.h b/profiles/audio/transport.h
index 5e5da20..c276428 100644
--- a/profiles/audio/transport.h
+++ b/profiles/audio/transport.h
@@ -23,6 +23,19 @@
  */
 
 struct media_transport;
+struct media_owner;
+
+struct media_transport_driver {
+	const char *uuid;
+
+	void *(*init) (struct media_transport *transport);
+	guint (*resume) (struct media_transport *transport,
+						struct media_owner *owner);
+	guint (*suspend) (struct media_transport *transport,
+						struct media_owner *owner);
+	void (*cancel) (struct media_transport *transport, guint id);
+	GDestroyNotify destroy;
+};
 
 struct media_transport *media_transport_create(struct audio_device *device,
 						uint8_t *configuration,
@@ -31,6 +44,7 @@ struct media_transport *media_transport_create(struct audio_device *device,
 void media_transport_destroy(struct media_transport *transport);
 const char *media_transport_get_path(struct media_transport *transport);
 struct audio_device *media_transport_get_dev(struct media_transport *transport);
+void *media_transport_get_data(struct media_transport *transport);
 uint16_t media_transport_get_volume(struct media_transport *transport);
 void media_transport_update_delay(struct media_transport *transport,
 							uint16_t delay);
@@ -42,3 +56,6 @@ void transport_get_properties(struct media_transport *transport,
 uint8_t media_transport_get_device_volume(struct audio_device *dev);
 void media_transport_update_device_volume(struct audio_device *dev,
 								uint8_t volume);
+
+void media_transport_driver_register(struct media_transport_driver *driver);
+void media_transport_driver_unregister(struct media_transport_driver *driver);
-- 
1.8.1.4


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [RFC BlueZ v0 05/10] transport: Add API to report suspend/resume complete
  2013-07-12 10:54 [RFC BlueZ v0 00/10] HSP plugin Mikel Astiz
                   ` (3 preceding siblings ...)
  2013-07-12 10:54 ` [RFC BlueZ v0 04/10] transport: Add API to register drivers Mikel Astiz
@ 2013-07-12 10:54 ` Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 06/10] transport: Add microphone/speaker gains Mikel Astiz
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Mikel Astiz @ 2013-07-12 10:54 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Mikel Astiz

From: Mikel Astiz <mikel.astiz@bmw-carit.de>

The driver needs to inform the core about the completion of these
asynchronous operations.
---
 profiles/audio/transport.c | 60 ++++++++++++++++++++++++++--------------------
 profiles/audio/transport.h |  4 ++++
 2 files changed, 38 insertions(+), 26 deletions(-)

diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index 14e4b9b..a9b8c3b 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
@@ -251,19 +251,32 @@ static void media_transport_remove_owner(struct media_transport *transport)
 		transport->driver->suspend(transport, NULL);
 }
 
-static gboolean media_transport_set_fd(struct media_transport *transport,
-					int fd, uint16_t imtu, uint16_t omtu)
+void media_transport_resume_complete(struct media_owner *owner, int fd,
+						uint16_t imtu, uint16_t omtu)
 {
-	if (transport->fd == fd)
-		return TRUE;
+	struct media_transport *transport = owner->transport;
+	struct media_request *req = owner->pending;
+
+	if (fd < 0) {
+		media_transport_remove_owner(transport);
+		return;
+	}
+
+	info("%s: fd(%d) ready", transport->path, fd);
 
 	transport->fd = fd;
 	transport->imtu = imtu;
 	transport->omtu = omtu;
 
-	info("%s: fd(%d) ready", transport->path, fd);
+	g_dbus_send_reply(btd_get_dbus_connection(), req->msg,
+						DBUS_TYPE_UNIX_FD, &fd,
+						DBUS_TYPE_UINT16, &imtu,
+						DBUS_TYPE_UINT16, &omtu,
+						DBUS_TYPE_INVALID);
 
-	return TRUE;
+	media_owner_remove(owner);
+
+	transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
 }
 
 static void a2dp_resume_complete(struct avdtp *session,
@@ -291,24 +304,12 @@ static void a2dp_resume_complete(struct avdtp *session,
 	if (ret == FALSE)
 		goto fail;
 
-	media_transport_set_fd(transport, fd, imtu, omtu);
-
-	ret = g_dbus_send_reply(btd_get_dbus_connection(), req->msg,
-						DBUS_TYPE_UNIX_FD, &fd,
-						DBUS_TYPE_UINT16, &imtu,
-						DBUS_TYPE_UINT16, &omtu,
-						DBUS_TYPE_INVALID);
-	if (ret == FALSE)
-		goto fail;
-
-	media_owner_remove(owner);
-
-	transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
+	media_transport_resume_complete(owner, fd, imtu, omtu);
 
 	return;
 
 fail:
-	media_transport_remove_owner(transport);
+	media_transport_resume_complete(owner, -1, 0, 0);
 }
 
 static guint resume_a2dp(struct media_transport *transport,
@@ -342,13 +343,9 @@ static guint resume_a2dp(struct media_transport *transport,
 	return id;
 }
 
-static void a2dp_suspend_complete(struct avdtp *session,
-				struct avdtp_error *err, void *user_data)
+void media_transport_suspend_complete(struct media_owner *owner)
 {
-	struct media_owner *owner = user_data;
 	struct media_transport *transport = owner->transport;
-	struct a2dp_transport *a2dp = transport->data;
-	struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint);
 
 	/* Release always succeeds */
 	if (owner->pending) {
@@ -357,11 +354,22 @@ static void a2dp_suspend_complete(struct avdtp *session,
 		media_owner_remove(owner);
 	}
 
-	a2dp_sep_unlock(sep, a2dp->session);
 	transport_set_state(transport, TRANSPORT_STATE_IDLE);
 	media_transport_remove_owner(transport);
 }
 
+static void a2dp_suspend_complete(struct avdtp *session,
+				struct avdtp_error *err, void *user_data)
+{
+	struct media_owner *owner = user_data;
+	struct media_transport *transport = owner->transport;
+	struct a2dp_transport *a2dp = transport->data;
+	struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint);
+
+	a2dp_sep_unlock(sep, a2dp->session);
+	media_transport_suspend_complete(owner);
+}
+
 static guint suspend_a2dp(struct media_transport *transport,
 						struct media_owner *owner)
 {
diff --git a/profiles/audio/transport.h b/profiles/audio/transport.h
index c276428..be0fcea 100644
--- a/profiles/audio/transport.h
+++ b/profiles/audio/transport.h
@@ -59,3 +59,7 @@ void media_transport_update_device_volume(struct audio_device *dev,
 
 void media_transport_driver_register(struct media_transport_driver *driver);
 void media_transport_driver_unregister(struct media_transport_driver *driver);
+
+void media_transport_resume_complete(struct media_owner *owner, int fd,
+						uint16_t imtu, uint16_t omtu);
+void media_transport_suspend_complete(struct media_owner *owner);
-- 
1.8.1.4


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [RFC BlueZ v0 06/10] transport: Add microphone/speaker gains
  2013-07-12 10:54 [RFC BlueZ v0 00/10] HSP plugin Mikel Astiz
                   ` (4 preceding siblings ...)
  2013-07-12 10:54 ` [RFC BlueZ v0 05/10] transport: Add API to report suspend/resume complete Mikel Astiz
@ 2013-07-12 10:54 ` Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 07/10] audio: Add function to remove inactive devices Mikel Astiz
                   ` (4 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Mikel Astiz @ 2013-07-12 10:54 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Mikel Astiz

From: Mikel Astiz <mikel.astiz@bmw-carit.de>

Add the D-Bus properties to the transport as described in the API
documentation.
---
 profiles/audio/transport.c | 128 +++++++++++++++++++++++++++++++++++++++++++++
 profiles/audio/transport.h |   8 +++
 2 files changed, 136 insertions(+)

diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index a9b8c3b..cd2b45f 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
@@ -95,6 +95,8 @@ struct media_transport {
 	uint16_t		omtu;		/* Transport output mtu */
 	uint16_t		delay;
 	uint16_t		volume;
+	char			microphone_gain;
+	char			speaker_gain;
 	transport_state_t	state;
 	struct media_transport_driver *driver;
 	void			*data;
@@ -666,6 +668,94 @@ static void set_volume(const GDBusPropertyTable *property,
 	g_dbus_pending_property_success(id);
 }
 
+static gboolean microphone_gain_exists(const GDBusPropertyTable *property,
+								void *data)
+{
+	struct media_transport *transport = data;
+
+	return transport->microphone_gain >= 0;
+}
+
+static gboolean get_microphone_gain(const GDBusPropertyTable *property,
+					DBusMessageIter *iter, void *data)
+{
+	struct media_transport *transport = data;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE,
+						&transport->microphone_gain);
+
+	return TRUE;
+}
+
+static char parse_gain(DBusMessageIter *iter, GDBusPendingPropertySet id)
+{
+	if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_BYTE) {
+		char gain;
+
+		dbus_message_iter_get_basic(iter, &gain);
+
+		if (gain >= 0 && gain <= 15)
+			return gain;
+	}
+
+	g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments",
+					"Invalid arguments in method call");
+	return -1;
+}
+
+static void set_microphone_gain(const GDBusPropertyTable *property,
+			DBusMessageIter *iter, GDBusPendingPropertySet id,
+			void *data)
+{
+	struct media_transport *transport = data;
+	char gain = parse_gain(iter, id);
+
+	if (gain < 0)
+		return;
+
+	if (transport->microphone_gain != gain)
+		transport->driver->set_microphone_gain(transport, gain);
+
+	transport->microphone_gain = gain;
+	g_dbus_pending_property_success(id);
+}
+
+static gboolean speaker_gain_exists(const GDBusPropertyTable *property,
+								void *data)
+{
+	struct media_transport *transport = data;
+
+	return transport->speaker_gain >= 0;
+}
+
+static gboolean get_speaker_gain(const GDBusPropertyTable *property,
+					DBusMessageIter *iter, void *data)
+{
+	struct media_transport *transport = data;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE,
+						&transport->speaker_gain);
+
+	return TRUE;
+}
+
+static void set_speaker_gain(const GDBusPropertyTable *property,
+			DBusMessageIter *iter, GDBusPendingPropertySet id,
+			void *data)
+{
+	struct media_transport *transport = data;
+	char gain = parse_gain(iter, id);
+
+	if (gain < 0)
+		return;
+
+	if (transport->speaker_gain != gain)
+		transport->driver->set_speaker_gain(transport, gain);
+
+	transport->speaker_gain = gain;
+	g_dbus_pending_property_success(id);
+}
+
 static const GDBusMethodTable transport_methods[] = {
 	{ GDBUS_ASYNC_METHOD("Acquire",
 			NULL,
@@ -689,6 +779,10 @@ static const GDBusPropertyTable transport_properties[] = {
 	{ "State", "s", get_state },
 	{ "Delay", "q", get_delay, NULL, delay_exists },
 	{ "Volume", "q", get_volume, set_volume, volume_exists },
+	{ "MicrophoneGain", "y", get_microphone_gain, set_microphone_gain,
+						microphone_gain_exists },
+	{ "SpeakerGain", "y", get_speaker_gain, set_speaker_gain,
+						speaker_gain_exists },
 	{ }
 };
 
@@ -851,6 +945,8 @@ struct media_transport *media_transport_create(struct audio_device *device,
 	transport->size = size;
 	transport->fd = -1;
 	transport->volume = -1;
+	transport->microphone_gain = -1;
+	transport->speaker_gain = -1;
 	transport->data = transport->driver->init(transport);
 	transport->path = g_strdup_printf("%s/fd%d",
 				device_get_path(device->btd_dev), fd++);
@@ -920,6 +1016,38 @@ void media_transport_update_volume(struct media_transport *transport,
 					MEDIA_TRANSPORT_INTERFACE, "Volume");
 }
 
+void media_transport_update_microphone_gain(struct media_transport *transport,
+								char gain)
+{
+	if (transport->microphone_gain == gain)
+		return;
+
+	transport->microphone_gain = gain;
+
+	if (transport->path == NULL)
+		return;
+
+	g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path,
+						MEDIA_TRANSPORT_INTERFACE,
+						"MicrophoneGain");
+}
+
+void media_transport_update_speaker_gain(struct media_transport *transport,
+								char gain)
+{
+	if (transport->speaker_gain == gain)
+		return;
+
+	transport->speaker_gain = gain;
+
+	if (transport->path == NULL)
+		return;
+
+	g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path,
+						MEDIA_TRANSPORT_INTERFACE,
+						"SpeakerGain");
+}
+
 uint8_t media_transport_get_device_volume(struct audio_device *dev)
 {
 	GSList *l;
diff --git a/profiles/audio/transport.h b/profiles/audio/transport.h
index be0fcea..0fcc45b 100644
--- a/profiles/audio/transport.h
+++ b/profiles/audio/transport.h
@@ -33,6 +33,10 @@ struct media_transport_driver {
 						struct media_owner *owner);
 	guint (*suspend) (struct media_transport *transport,
 						struct media_owner *owner);
+	void (*set_speaker_gain) (struct media_transport *transport,
+						char gain);
+	void (*set_microphone_gain) (struct media_transport *transport,
+						char gain);
 	void (*cancel) (struct media_transport *transport, guint id);
 	GDestroyNotify destroy;
 };
@@ -50,6 +54,10 @@ void media_transport_update_delay(struct media_transport *transport,
 							uint16_t delay);
 void media_transport_update_volume(struct media_transport *transport,
 								uint8_t volume);
+void media_transport_update_microphone_gain(struct media_transport *transport,
+							char gain);
+void media_transport_update_speaker_gain(struct media_transport *transport,
+							char gain);
 void transport_get_properties(struct media_transport *transport,
 							DBusMessageIter *iter);
 
-- 
1.8.1.4


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [RFC BlueZ v0 07/10] audio: Add function to remove inactive devices
  2013-07-12 10:54 [RFC BlueZ v0 00/10] HSP plugin Mikel Astiz
                   ` (5 preceding siblings ...)
  2013-07-12 10:54 ` [RFC BlueZ v0 06/10] transport: Add microphone/speaker gains Mikel Astiz
@ 2013-07-12 10:54 ` Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 08/10] hsp: Add initial HSP plugin Mikel Astiz
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Mikel Astiz @ 2013-07-12 10:54 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Mikel Astiz

From: Mikel Astiz <mikel.astiz@bmw-carit.de>

A plugin might create an instance of audio_device using the manager's
API and therefore a equivalent function is needed for removal. This
simple approach assumes that there will be no more than one plugin
adding/removing audio devices.
---
 profiles/audio/manager.c | 12 ++++++++++++
 profiles/audio/manager.h |  1 +
 2 files changed, 13 insertions(+)

diff --git a/profiles/audio/manager.c b/profiles/audio/manager.c
index 7f02fbd..6b5d912 100644
--- a/profiles/audio/manager.c
+++ b/profiles/audio/manager.c
@@ -464,6 +464,18 @@ struct audio_device *manager_get_audio_device(struct btd_device *device,
 	return dev;
 }
 
+void manager_remove_audio_device(struct audio_device *dev)
+{
+	if (dev == NULL)
+		return;
+
+	if (dev->sink || dev->source || dev->control)
+		return;
+
+	devices = g_slist_remove(devices, dev);
+	audio_device_unregister(dev);
+}
+
 static void set_fast_connectable(struct btd_adapter *adapter,
 							gpointer user_data)
 {
diff --git a/profiles/audio/manager.h b/profiles/audio/manager.h
index b8d8ef7..8167a86 100644
--- a/profiles/audio/manager.h
+++ b/profiles/audio/manager.h
@@ -34,6 +34,7 @@ void audio_manager_exit(void);
 
 struct audio_device *manager_get_audio_device(struct btd_device *device,
 							gboolean create);
+void manager_remove_audio_device(struct audio_device *dev);
 
 /* TRUE to enable fast connectable and FALSE to disable fast connectable for all
  * audio adapters. */
-- 
1.8.1.4


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [RFC BlueZ v0 08/10] hsp: Add initial HSP plugin
  2013-07-12 10:54 [RFC BlueZ v0 00/10] HSP plugin Mikel Astiz
                   ` (6 preceding siblings ...)
  2013-07-12 10:54 ` [RFC BlueZ v0 07/10] audio: Add function to remove inactive devices Mikel Astiz
@ 2013-07-12 10:54 ` Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 09/10] hsp: Add Media API integration Mikel Astiz
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 14+ messages in thread
From: Mikel Astiz @ 2013-07-12 10:54 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Mikel Astiz

From: Mikel Astiz <mikel.astiz@bmw-carit.de>

This simple HSP plugin implements the AG role, typically used in desktop
environments. The goal is to have a fallback alternative in case a
full-blown HSP/HFP implementation (e.g. oFono) is not available.
---
 Makefile.plugins |    3 +
 plugins/hsp.c    | 1013 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1016 insertions(+)
 create mode 100644 plugins/hsp.c

diff --git a/Makefile.plugins b/Makefile.plugins
index 3efe849..e7532a4 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -107,4 +107,7 @@ builtin_sources += profiles/heartrate/heartrate.c
 
 builtin_modules += cyclingspeed
 builtin_sources += profiles/cyclingspeed/cyclingspeed.c
+
+builtin_modules += hsp
+builtin_sources += plugins/hsp.c
 endif
diff --git a/plugins/hsp.c b/plugins/hsp.c
new file mode 100644
index 0000000..f0b55c9
--- /dev/null
+++ b/plugins/hsp.c
@@ -0,0 +1,1013 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2012-2013  BMW Car IT GmbH. All rights reserved.
+ *
+ *
+ *  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 <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <assert.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "lib/uuid.h"
+#include "plugin.h"
+#include "log.h"
+#include "adapter.h"
+#include "device.h"
+#include "profile.h"
+#include "service.h"
+#include "error.h"
+#include "sdp-client.h"
+#include "sdpd.h"
+#include "btio.h"
+#include "profiles/audio/manager.h"
+
+#define DEFAULT_HS_AG_CHANNEL 12
+#define BUF_SIZE 1024
+
+typedef enum {
+	HEADSET_STATE_DISCONNECTED,
+	HEADSET_STATE_CONNECTING,
+	HEADSET_STATE_CONNECTED,
+	HEADSET_STATE_PLAYING,
+} headset_state_t;
+
+struct headset_slc {
+	char buf[BUF_SIZE];
+	int data_start;
+	int data_length;
+
+	int sp_gain;
+	int mic_gain;
+};
+
+struct server {
+	struct btd_adapter *adapter;
+	GIOChannel *io;
+	GIOChannel *sco_io;
+	uint32_t record_id;
+	GSList *active_headsets;
+};
+
+struct headset {
+	struct server *server;
+	struct btd_service *service;
+	struct audio_device *audio_dev;
+
+	bool discovering;
+	int rfcomm_ch;
+	GIOChannel *rfcomm;
+	struct headset_slc *slc;
+
+	GIOChannel *sco;
+	guint sco_id;
+
+	headset_state_t state;
+};
+
+struct event {
+	const char *cmd;
+	int (*callback) (struct headset *hs, const char *buf);
+};
+
+static GSList *servers = NULL;
+
+static void headset_set_state(struct headset *hs, headset_state_t state);
+
+static const char *state2str(headset_state_t state)
+{
+	switch (state) {
+	case HEADSET_STATE_DISCONNECTED:
+		return "HEADSET_STATE_DISCONNECTED";
+	case HEADSET_STATE_CONNECTING:
+		return "HEADSET_STATE_CONNECTING";
+	case HEADSET_STATE_CONNECTED:
+		return "HEADSET_STATE_CONNECTED";
+	case HEADSET_STATE_PLAYING:
+		return "HEADSET_STATE_PLAYING";
+	}
+
+	return NULL;
+}
+
+static struct server *find_server(const struct btd_adapter *adapter)
+{
+	GSList *l;
+
+	for (l = servers; l != NULL; l = g_slist_next(l)) {
+		struct server *server = l->data;
+
+		if (server->adapter == adapter)
+			return server;
+	}
+
+	return NULL;
+}
+
+static int headset_send_valist(struct headset *hs, char *format, va_list ap)
+{
+	char rsp[BUF_SIZE];
+	ssize_t total_written, count;
+	int fd;
+
+	count = vsnprintf(rsp, sizeof(rsp), format, ap);
+
+	if (count < 0)
+		return -EINVAL;
+
+	if (hs->rfcomm == NULL) {
+		error("headset_send: the headset is not connected");
+		return -EIO;
+	}
+
+	total_written = 0;
+	fd = g_io_channel_unix_get_fd(hs->rfcomm);
+
+	while (total_written < count) {
+		ssize_t written;
+
+		written = write(fd, rsp + total_written,
+				count - total_written);
+		if (written < 0)
+			return -errno;
+
+		total_written += written;
+	}
+
+	return 0;
+}
+
+static int __attribute__((format(printf, 2, 3)))
+			headset_send(struct headset *hs, char *format, ...)
+{
+	va_list ap;
+	int ret;
+
+	va_start(ap, format);
+	ret = headset_send_valist(hs, format, ap);
+	va_end(ap);
+
+	return ret;
+}
+
+static gboolean sco_io_cb(GIOChannel *io, GIOCondition cond, gpointer user_data)
+{
+	struct headset *hs = user_data;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	DBG("SCO connection got disconnected");
+
+	if (hs->state > HEADSET_STATE_CONNECTED)
+		headset_set_state(hs, HEADSET_STATE_CONNECTED);
+
+	return FALSE;
+}
+
+static void headset_init_sco(struct headset *hs)
+{
+	int fd;
+
+	assert(hs->sco != NULL);
+
+	fd = g_io_channel_unix_get_fd(hs->sco);
+	fcntl(fd, F_SETFL, 0);
+
+	/* Do not watch HUP since we need to know when the link is
+	   really disconnected */
+	hs->sco_id = g_io_add_watch(hs->sco, G_IO_ERR | G_IO_NVAL,
+								sco_io_cb, hs);
+
+	headset_send(hs, "\r\n+VGS=%u\r\n", hs->slc->sp_gain);
+	headset_send(hs, "\r\n+VGM=%u\r\n", hs->slc->mic_gain);
+
+	headset_set_state(hs, HEADSET_STATE_PLAYING);
+}
+
+static void headset_close_sco(struct headset *hs)
+{
+	if (hs->sco != NULL) {
+		int sock = g_io_channel_unix_get_fd(hs->sco);
+		shutdown(sock, SHUT_RDWR);
+		g_io_channel_shutdown(hs->sco, TRUE, NULL);
+		g_io_channel_unref(hs->sco);
+		hs->sco = NULL;
+	}
+
+	if (hs->sco_id != 0) {
+		g_source_remove(hs->sco_id);
+		hs->sco_id = 0;
+	}
+}
+
+static int key_press(struct headset *hs, const char *buf)
+{
+	if (strlen(buf) < 9)
+		return -EINVAL;
+
+	return headset_send(hs, "\r\nOK\r\n");
+}
+
+static int signal_gain_setting(struct headset *hs, const char *buf)
+{
+	struct headset_slc *slc = hs->slc;
+	long int gain;
+
+	if (strlen(buf) < 8) {
+		error("Too short string for Gain setting");
+		return -EINVAL;
+	}
+
+	gain = strtol(&buf[7], NULL, 10);
+
+	if (gain < 0 || gain > 15) {
+		error("Invalid gain value: %ld", gain);
+		return -EINVAL;
+	}
+
+	switch (buf[5]) {
+	case 'S':
+		if (slc->sp_gain == gain)
+			return -EALREADY;
+
+		slc->sp_gain = gain;
+		break;
+	case 'M':
+		if (slc->mic_gain == gain)
+			return 0;
+
+		slc->mic_gain = gain;
+		break;
+	default:
+		error("Unknown gain setting");
+		return -EINVAL;
+	}
+
+	return headset_send(hs, "\r\nOK\r\n");
+}
+
+static struct event event_callbacks[] = {
+	{ "AT+CKPD", key_press },
+	{ "AT+VG", signal_gain_setting },
+	{ 0 }
+};
+
+static int handle_event(struct headset *hs, const char *buf)
+{
+	struct event *ev;
+
+	DBG("Received %s", buf);
+
+	for (ev = event_callbacks; ev->cmd; ev++)
+		if (!strncmp(buf, ev->cmd, strlen(ev->cmd)))
+			return ev->callback(hs, buf);
+
+	return -EINVAL;
+}
+
+static gboolean rfcomm_io_cb(GIOChannel *io, GIOCondition cond,
+							gpointer user_data)
+{
+	struct headset *hs = user_data;
+	struct headset_slc *slc;
+	unsigned char buf[BUF_SIZE];
+	ssize_t bytes_read;
+	size_t free_space;
+	int fd;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	slc = hs->slc;
+
+	if (cond & (G_IO_ERR | G_IO_HUP)) {
+		DBG("ERR or HUP on RFCOMM socket");
+		goto failed;
+	}
+
+	fd = g_io_channel_unix_get_fd(io);
+	bytes_read = read(fd, buf, sizeof(buf) - 1);
+	if (bytes_read < 0)
+		return TRUE;
+
+	free_space = sizeof(slc->buf) - slc->data_start - slc->data_length - 1;
+
+	if (free_space < (size_t) bytes_read) {
+		/* Very likely that the HS is sending us garbage so
+		 * just ignore the data and disconnect */
+		error("Too much data to fit incoming buffer");
+		goto failed;
+	}
+
+	memcpy(&slc->buf[slc->data_start], buf, bytes_read);
+	slc->data_length += bytes_read;
+
+	/* Make sure the data is null terminated so we can use string
+	 * functions */
+	slc->buf[slc->data_start + slc->data_length] = '\0';
+
+	while (slc->data_length > 0) {
+		char *cr;
+		int err;
+		off_t cmd_len;
+
+		cr = strchr(&slc->buf[slc->data_start], '\r');
+		if (cr == NULL)
+			break;
+
+		cmd_len = 1 + (off_t) cr - (off_t) &slc->buf[slc->data_start];
+		*cr = '\0';
+
+		if (cmd_len > 1)
+			err = handle_event(hs, &slc->buf[slc->data_start]);
+		else
+			/* Silently skip empty commands */
+			err = 0;
+
+		if (err == -EINVAL) {
+			error("Badly formated or unrecognized command: %s",
+					&slc->buf[slc->data_start]);
+			err = headset_send(hs, "\r\nERROR\r\n");
+			if (err < 0)
+				goto failed;
+		} else if (err < 0)
+			error("Error handling command %s: %s (%d)",
+						&slc->buf[slc->data_start],
+						strerror(-err), -err);
+
+		slc->data_start += cmd_len;
+		slc->data_length -= cmd_len;
+
+		if (slc->data_length == 0)
+			slc->data_start = 0;
+	}
+
+	return TRUE;
+
+failed:
+	headset_set_state(hs, HEADSET_STATE_DISCONNECTED);
+
+	return FALSE;
+}
+
+static void headset_connect_rfcomm_cb(GIOChannel *io, GError *err,
+							gpointer user_data)
+{
+	struct headset *hs = user_data;
+	struct btd_device *device = btd_service_get_device(hs->service);
+	char addr[18];
+
+	if (err) {
+		error("%s", err->message);
+		headset_set_state(hs, HEADSET_STATE_DISCONNECTED);
+		return;
+	}
+
+	g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+							rfcomm_io_cb, hs);
+
+	hs->slc = g_new0(struct headset_slc, 1);
+	hs->slc->sp_gain = 15;
+	hs->slc->mic_gain = 15;
+
+	ba2str(device_get_address(device), addr);
+	DBG("Connected to %s", addr);
+
+	headset_set_state(hs, HEADSET_STATE_CONNECTED);
+}
+
+static int headset_connect_rfcomm(struct headset *hs)
+{
+	struct btd_device *device = btd_service_get_device(hs->service);
+	struct btd_adapter *adapter = device_get_adapter(device);
+	const bdaddr_t *src, *dst;
+	char addr[18];
+	GError *err = NULL;
+
+	if (hs->rfcomm_ch < 0)
+		return -EINVAL;
+
+	src = adapter_get_address(adapter);
+	dst = device_get_address(device);
+
+	ba2str(dst, addr);
+	DBG("Connecting to %s channel %d", addr, hs->rfcomm_ch);
+
+	hs->rfcomm = bt_io_connect(headset_connect_rfcomm_cb, hs, NULL,
+					&err,
+					BT_IO_OPT_SOURCE_BDADDR, src,
+					BT_IO_OPT_DEST_BDADDR, dst,
+					BT_IO_OPT_CHANNEL, hs->rfcomm_ch,
+					BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+					BT_IO_OPT_INVALID);
+
+	hs->rfcomm_ch = -1;
+
+	if (hs->rfcomm == NULL) {
+		error("%s", err->message);
+		g_error_free(err);
+		return -EIO;
+	}
+
+	headset_set_state(hs, HEADSET_STATE_CONNECTING);
+
+	return 0;
+}
+
+static int headset_close_rfcomm(struct headset *hs)
+{
+	if (hs->rfcomm != NULL) {
+		g_io_channel_shutdown(hs->rfcomm, TRUE, NULL);
+		g_io_channel_unref(hs->rfcomm);
+		hs->rfcomm = NULL;
+	}
+
+	g_free(hs->slc);
+	hs->slc = NULL;
+
+	return 0;
+}
+
+static int headset_set_channel(struct headset *headset,
+						const sdp_record_t *record)
+{
+	int ch;
+	sdp_list_t *protos;
+
+	if (sdp_get_access_protos(record, &protos) < 0) {
+		error("Unable to get access protos from headset record");
+		return -1;
+	}
+
+	ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+
+	sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
+	sdp_list_free(protos, NULL);
+
+	if (ch <= 0) {
+		error("Unable to get RFCOMM channel from Headset record");
+		return -1;
+	}
+
+	headset->rfcomm_ch = ch;
+
+	return 0;
+}
+
+static void get_record_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+	struct headset *hs = user_data;
+	sdp_record_t *record = NULL;
+	sdp_list_t *r;
+	uuid_t uuid;
+
+	hs->discovering = false;
+
+	if (err < 0) {
+		error("Unable to get service record: %s (%d)",
+							strerror(-err), -err);
+		goto failed;
+	}
+
+	if (recs == NULL || recs->data == NULL) {
+		error("No records found");
+		goto failed;
+	}
+
+	sdp_uuid16_create(&uuid, HEADSET_SVCLASS_ID);
+
+	for (r = recs; r != NULL; r = r->next) {
+		sdp_list_t *classes;
+		uuid_t class;
+
+		record = r->data;
+
+		if (sdp_get_service_classes(record, &classes) < 0) {
+			error("Unable to get service classes from record");
+			continue;
+		}
+
+		memcpy(&class, classes->data, sizeof(uuid));
+		sdp_list_free(classes, free);
+
+		if (sdp_uuid_cmp(&class, &uuid) == 0)
+			break;
+	}
+
+	if (r == NULL) {
+		error("No record found with HSP UUID");
+		goto failed;
+	}
+
+	if (headset_set_channel(hs, record) < 0) {
+		error("Unable to extract RFCOMM channel from service record");
+		goto failed;
+	}
+
+	err = headset_connect_rfcomm(hs);
+	if (err < 0) {
+		error("Unable to connect: %s (%d)", strerror(-err), -err);
+		goto failed;
+	}
+
+	return;
+
+failed:
+	headset_set_state(hs, HEADSET_STATE_DISCONNECTED);
+}
+
+static int get_records(struct headset *hs)
+{
+	struct btd_device *device = btd_service_get_device(hs->service);
+	struct btd_adapter *adapter = device_get_adapter(device);
+	uuid_t uuid;
+	int err;
+
+	sdp_uuid16_create(&uuid, HEADSET_SVCLASS_ID);
+
+	err = bt_search_service(adapter_get_address(adapter),
+						device_get_address(device),
+						&uuid, get_record_cb, hs, NULL);
+	if (err < 0)
+		return err;
+
+	hs->discovering = true;
+	headset_set_state(hs, HEADSET_STATE_CONNECTING);
+
+	return 0;
+}
+
+static void headset_cancel_discovery(struct headset *hs)
+{
+	struct btd_device *device = btd_service_get_device(hs->service);
+	struct btd_adapter *adapter = device_get_adapter(device);
+
+	if (!hs->discovering)
+		return;
+
+	bt_cancel_discovery(adapter_get_address(adapter),
+						device_get_address(device));
+	hs->discovering = false;
+	headset_set_state(hs, HEADSET_STATE_DISCONNECTED);
+}
+
+static void headset_set_state(struct headset *hs, headset_state_t state)
+{
+	struct btd_device *device = btd_service_get_device(hs->service);
+	struct server *server = hs->server;
+	headset_state_t old_state = hs->state;
+	btd_service_state_t service_state = btd_service_get_state(hs->service);
+	char addr[18];
+
+	if (old_state == state)
+		return;
+
+	switch (state) {
+	case HEADSET_STATE_DISCONNECTED:
+		headset_cancel_discovery(hs);
+		headset_close_sco(hs);
+		headset_close_rfcomm(hs);
+		server->active_headsets = g_slist_remove(
+						server->active_headsets, hs);
+
+		if (service_state == BTD_SERVICE_STATE_CONNECTING)
+			btd_service_connecting_complete(hs->service, -EIO);
+		else if (service_state == BTD_SERVICE_STATE_DISCONNECTING)
+			btd_service_disconnecting_complete(hs->service, 0);
+
+		break;
+	case HEADSET_STATE_CONNECTING:
+		break;
+	case HEADSET_STATE_CONNECTED:
+		headset_close_sco(hs);
+
+		if (service_state == BTD_SERVICE_STATE_CONNECTING)
+			btd_service_connecting_complete(hs->service, 0);
+
+		if (hs->state >= state)
+			break;
+
+		server->active_headsets = g_slist_append(
+						server->active_headsets, hs);
+		break;
+	case HEADSET_STATE_PLAYING:
+		break;
+	}
+
+	hs->state = state;
+	ba2str(device_get_address(device), addr);
+	DBG("%s state changed: %s -> %s", addr, state2str(old_state),
+							state2str(state));
+}
+
+static int hsp_hs_probe(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	struct headset *hs;
+
+	DBG("path %s", device_get_path(device));
+
+	hs = g_new0(struct headset, 1);
+	hs->server = find_server(device_get_adapter(device));
+	hs->service = btd_service_ref(service);
+	hs->audio_dev = manager_get_audio_device(device, TRUE);
+	hs->rfcomm_ch = -1;
+
+	btd_service_set_user_data(service, hs);
+
+	return 0;
+}
+
+static void hsp_hs_remove(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	struct headset *hs = btd_service_get_user_data(service);
+
+	DBG("path %s", device_get_path(device));
+
+	headset_cancel_discovery(hs);
+	headset_close_sco(hs);
+	headset_close_rfcomm(hs);
+	btd_service_unref(hs->service);
+
+	manager_remove_audio_device(hs->audio_dev);
+
+	g_free(hs);
+}
+
+static int hsp_hs_connect(struct btd_service *service)
+{
+	struct headset *hs = btd_service_get_user_data(service);
+	struct btd_device *device = btd_service_get_device(service);
+
+	DBG("path %s", device_get_path(device));
+
+	if (hs->rfcomm_ch < 0)
+		return get_records(hs);
+
+	return headset_connect_rfcomm(hs);
+}
+
+static int hsp_hs_disconnect(struct btd_service *service)
+{
+	struct headset *hs = btd_service_get_user_data(service);
+	struct btd_device *device = btd_service_get_device(service);
+
+	DBG("path %s", device_get_path(device));
+
+	headset_close_sco(hs);
+	headset_close_rfcomm(hs);
+	headset_set_state(hs, HEADSET_STATE_DISCONNECTED);
+
+	return 0;
+}
+
+static sdp_record_t *hsp_ag_record(uint8_t ch)
+{
+	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+	uuid_t root_uuid, svclass_uuid, ga_svclass_uuid;
+	uuid_t l2cap_uuid, rfcomm_uuid;
+	sdp_profile_desc_t profile;
+	sdp_record_t *record;
+	sdp_list_t *aproto, *proto[2];
+	sdp_data_t *channel;
+
+	record = sdp_record_alloc();
+	if (record == NULL)
+		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(&svclass_uuid, HEADSET_AGW_SVCLASS_ID);
+	svclass_id = sdp_list_append(0, &svclass_uuid);
+	sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
+	svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
+	sdp_set_service_classes(record, svclass_id);
+
+	sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID);
+	profile.version = 0x0102;
+	pfseq = sdp_list_append(0, &profile);
+	sdp_set_profile_descs(record, pfseq);
+
+	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+	proto[0] = sdp_list_append(0, &l2cap_uuid);
+	apseq = sdp_list_append(0, proto[0]);
+
+	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+	proto[1] = sdp_list_append(0, &rfcomm_uuid);
+	channel = sdp_data_alloc(SDP_UINT8, &ch);
+	proto[1] = sdp_list_append(proto[1], channel);
+	apseq = sdp_list_append(apseq, proto[1]);
+
+	aproto = sdp_list_append(0, apseq);
+	sdp_set_access_protos(record, aproto);
+
+	sdp_set_info_attr(record, "Headset Audio Gateway", 0, 0);
+
+	sdp_data_free(channel);
+	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 headset *find_headset(struct server *server, const bdaddr_t *addr)
+{
+	struct btd_device *device;
+	struct btd_service *service;
+
+	device = adapter_find_device(server->adapter, addr);
+	if (device == NULL)
+		return NULL;
+
+	service = btd_device_get_service(device, HSP_HS_UUID);
+	if (service == NULL)
+		return NULL;
+
+	return btd_service_get_user_data(service);
+}
+
+static void confirm_event_cb(GIOChannel *io, gpointer user_data)
+{
+	struct server *server = user_data;
+	struct headset *hs;
+	bdaddr_t dst;
+	uint8_t ch;
+	char addr[18];
+	GError *err = NULL;
+
+	bt_io_get(io, &err, BT_IO_OPT_DEST_BDADDR, &dst,
+						BT_IO_OPT_CHANNEL, &ch,
+						BT_IO_OPT_INVALID);
+
+	if (err != NULL) {
+		error("bt_io_get: %s", err->message);
+		g_error_free(err);
+		goto drop;
+	}
+
+	ba2str(&dst, addr);
+	DBG("Incoming connection from %s on channel %u", addr, ch);
+
+	hs = find_headset(server, &dst);
+	if (hs == NULL) {
+		DBG("Refusing connection for unknown headset");
+		goto drop;
+	}
+
+	if (hs->rfcomm != NULL) {
+		DBG("Refusing new connection since one already exists");
+		goto drop;
+	}
+
+	DBG("Accepted headset RFCOMM connection from %s", addr);
+	hs->rfcomm = g_io_channel_ref(io);
+
+	headset_set_state(hs, HEADSET_STATE_CONNECTED);
+
+	return;
+
+drop:
+	g_io_channel_shutdown(io, TRUE, NULL);
+}
+
+static void sco_server_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+	struct server *server = user_data;
+	struct headset *hs;
+	char addr[18];
+	bdaddr_t dst;
+
+	if (err != NULL) {
+		error("sco_server_cb: %s", err->message);
+		return;
+	}
+
+	bt_io_get(io, &err, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_INVALID);
+
+	if (err != NULL) {
+		error("bt_io_get: %s", err->message);
+		goto drop;
+	}
+
+	ba2str(&dst, addr);
+	DBG("Incoming SCO from %s", addr);
+
+	hs = find_headset(server, &dst);
+	if (hs == NULL) {
+		DBG("Refusing SCO connection for unknown headset");
+		goto drop;
+	}
+
+	switch (hs->state) {
+	case HEADSET_STATE_DISCONNECTED:
+	case HEADSET_STATE_CONNECTING:
+		DBG("Refusing SCO from non-connected headset");
+		goto drop;
+	case HEADSET_STATE_CONNECTED:
+		break;
+	case HEADSET_STATE_PLAYING:
+		DBG("Refusing second SCO from same headset");
+		goto drop;
+	}
+
+	DBG("Accepted headset SCO connection from %s", addr);
+	hs->sco = g_io_channel_ref(io);
+	headset_init_sco(hs);
+
+	return;
+
+drop:
+	g_io_channel_shutdown(io, TRUE, NULL);
+}
+
+static int hsp_hs_server_probe(struct btd_profile *p,
+						struct btd_adapter *adapter)
+{
+	const bdaddr_t *src = adapter_get_address(adapter);
+	struct server *server;
+	static sdp_record_t *record;
+	gboolean master = TRUE;
+	uint8_t chan = DEFAULT_HS_AG_CHANNEL;
+	GError *err = NULL;
+
+	DBG("path %s", adapter_get_path(adapter));
+
+	server = g_new0(struct server, 1);
+	server->adapter = adapter;
+	server->io = bt_io_listen(NULL, confirm_event_cb, server, NULL, &err,
+					BT_IO_OPT_SOURCE_BDADDR, &src,
+					BT_IO_OPT_CHANNEL, chan,
+					BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+					BT_IO_OPT_MASTER, master,
+					BT_IO_OPT_INVALID);
+	if (server->io == NULL) {
+		if (err == NULL)
+			error("Unable to listen on HS AG socket");
+		else {
+			error("Unable to listen on HS AG socket: %s",
+								err->message);
+			g_error_free(err);
+		}
+
+		goto failed;
+	}
+
+	server->sco_io = bt_io_listen(sco_server_cb, NULL, server, NULL, &err,
+					BT_IO_OPT_SOURCE_BDADDR, &src,
+					BT_IO_OPT_INVALID);
+	if (server->sco_io == NULL) {
+		if (err == NULL)
+			error("Unable to listen on SCO socket");
+		else {
+			error("Unable to listen on SCO socket: %s",
+								err->message);
+			g_error_free(err);
+		}
+
+		goto failed;
+	}
+
+	record = hsp_ag_record(chan);
+	if (record == NULL) {
+		error("Unable to allocate new service record");
+		goto failed;
+	}
+
+	if (add_record_to_server(src, record) < 0) {
+		error("Unable to register HSP AG service record");
+		sdp_record_free(record);
+		goto failed;
+	}
+
+	server->record_id = record->handle;
+	servers = g_slist_append(servers, server);
+
+	return 0;
+
+failed:
+	if (server->sco_io != NULL) {
+		g_io_channel_shutdown(server->sco_io, TRUE, NULL);
+		g_io_channel_unref(server->sco_io);
+		server->sco_io = NULL;
+	}
+
+	if (server->io != NULL) {
+		g_io_channel_shutdown(server->io, TRUE, NULL);
+		g_io_channel_unref(server->io);
+		server->io = NULL;
+	}
+
+	return -EIO;
+}
+
+static void hsp_hs_server_remove(struct btd_profile *p,
+						struct btd_adapter *adapter)
+{
+	struct server *server;
+
+	DBG("path %s", adapter_get_path(adapter));
+
+	server = find_server(adapter);
+	if (server == NULL) {
+		DBG("Server not found");
+		return;
+	}
+
+	if (server->record_id != 0)
+		remove_record_from_server(server->record_id);
+
+	if (server->sco_io != NULL) {
+		g_io_channel_shutdown(server->sco_io, TRUE, NULL);
+		g_io_channel_unref(server->sco_io);
+	}
+
+	if (server->io != NULL) {
+		g_io_channel_shutdown(server->io, TRUE, NULL);
+		g_io_channel_unref(server->io);
+	}
+
+	servers = g_slist_remove(servers, server);
+	g_free(server);
+}
+
+static struct btd_profile hsp_profile = {
+	.name		= "audio-hsp-hs",
+
+	.remote_uuid	= HSP_HS_UUID,
+	.device_probe	= hsp_hs_probe,
+	.device_remove	= hsp_hs_remove,
+
+	.auto_connect	= true,
+	.connect	= hsp_hs_connect,
+	.disconnect	= hsp_hs_disconnect,
+
+	.adapter_probe	= hsp_hs_server_probe,
+	.adapter_remove = hsp_hs_server_remove,
+};
+
+static int hsp_init(void)
+{
+	int err;
+
+	err = btd_profile_register(&hsp_profile);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static void hsp_exit(void)
+{
+	btd_profile_unregister(&hsp_profile);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(hsp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+							hsp_init, hsp_exit)
-- 
1.8.1.4


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [RFC BlueZ v0 09/10] hsp: Add Media API integration
  2013-07-12 10:54 [RFC BlueZ v0 00/10] HSP plugin Mikel Astiz
                   ` (7 preceding siblings ...)
  2013-07-12 10:54 ` [RFC BlueZ v0 08/10] hsp: Add initial HSP plugin Mikel Astiz
@ 2013-07-12 10:54 ` Mikel Astiz
  2013-07-12 10:54 ` [RFC BlueZ v0 10/10] hsp: Implement media transport driver Mikel Astiz
  2013-07-12 11:28 ` [RFC BlueZ v0 00/10] HSP plugin Luiz Augusto von Dentz
  10 siblings, 0 replies; 14+ messages in thread
From: Mikel Astiz @ 2013-07-12 10:54 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Mikel Astiz

From: Mikel Astiz <mikel.astiz@bmw-carit.de>

Extend the HSP plugin with the required interactions with the core Media
API infrastructure.
---
 plugins/hsp.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 91 insertions(+)

diff --git a/plugins/hsp.c b/plugins/hsp.c
index f0b55c9..740b857 100644
--- a/plugins/hsp.c
+++ b/plugins/hsp.c
@@ -60,6 +60,7 @@
 #include "sdpd.h"
 #include "btio.h"
 #include "profiles/audio/manager.h"
+#include "profiles/audio/media.h"
 
 #define DEFAULT_HS_AG_CHANNEL 12
 #define BUF_SIZE 1024
@@ -86,6 +87,7 @@ struct server {
 	GIOChannel *sco_io;
 	uint32_t record_id;
 	GSList *active_headsets;
+	unsigned int endpoint_register_cb_id;
 };
 
 struct headset {
@@ -102,6 +104,9 @@ struct headset {
 	guint sco_id;
 
 	headset_state_t state;
+
+	struct media_endpoint *endpoint;
+	struct media_transport *transport;
 };
 
 struct event {
@@ -305,6 +310,58 @@ static int handle_event(struct headset *hs, const char *buf)
 	return -EINVAL;
 }
 
+static void headset_setconf_cb(struct media_endpoint *endpoint, void *ret,
+						int size, void *user_data)
+{
+	struct headset *hs = user_data;
+
+	DBG("%p: ret=%p", hs, ret);
+
+	if (ret != NULL) {
+		hs->endpoint = endpoint;
+		return;
+	}
+
+	hs->transport = NULL;
+
+	error("Transport SetConfiguration() failed");
+}
+
+static void headset_setconf(struct headset *hs)
+{
+	struct btd_device *device = btd_service_get_device(hs->service);
+	struct media_endpoint *endpoint;
+	struct btd_adapter *adapter;
+
+	adapter = device_get_adapter(device);
+	endpoint = btd_media_endpoint_find(adapter, HSP_AG_UUID);
+
+	if (endpoint == NULL)
+		endpoint = btd_media_endpoint_find(adapter, HFP_AG_UUID);
+
+	if (endpoint == NULL) {
+		DBG("No media enpoint registered for headset");
+		return;
+	}
+
+	DBG("Setting configuration using endpoint %p", endpoint);
+
+	hs->transport = btd_media_endpoint_set_configuration(
+					endpoint, hs->audio_dev, NULL, 0,
+					headset_setconf_cb, hs, NULL);
+}
+
+static void headset_clearconf(struct headset *hs)
+{
+	if (hs->endpoint == NULL)
+		return;
+
+	DBG("Clear endpoint %p (transport %p)", hs->endpoint, hs->transport);
+	btd_media_endpoint_clear_configuration(hs->endpoint, hs->transport);
+	hs->transport = NULL;
+	hs->endpoint = NULL;
+}
+
 static gboolean rfcomm_io_cb(GIOChannel *io, GIOCondition cond,
 							gpointer user_data)
 {
@@ -413,6 +470,7 @@ static void headset_connect_rfcomm_cb(GIOChannel *io, GError *err,
 	ba2str(device_get_address(device), addr);
 	DBG("Connected to %s", addr);
 
+	headset_setconf(hs);
 	headset_set_state(hs, HEADSET_STATE_CONNECTED);
 }
 
@@ -604,6 +662,7 @@ static void headset_set_state(struct headset *hs, headset_state_t state)
 
 	switch (state) {
 	case HEADSET_STATE_DISCONNECTED:
+		headset_clearconf(hs);
 		headset_cancel_discovery(hs);
 		headset_close_sco(hs);
 		headset_close_rfcomm(hs);
@@ -810,6 +869,7 @@ static void confirm_event_cb(GIOChannel *io, gpointer user_data)
 	DBG("Accepted headset RFCOMM connection from %s", addr);
 	hs->rfcomm = g_io_channel_ref(io);
 
+	headset_setconf(hs);
 	headset_set_state(hs, HEADSET_STATE_CONNECTED);
 
 	return;
@@ -868,6 +928,29 @@ drop:
 	g_io_channel_shutdown(io, TRUE, NULL);
 }
 
+static void media_endpoint_register_cb(struct media_endpoint *endpoint,
+								void *user_data)
+{
+	struct server *server = user_data;
+	GSList *l;
+
+	if (strcasecmp(media_endpoint_get_uuid(endpoint), HSP_AG_UUID) != 0 &&
+		strcasecmp(media_endpoint_get_uuid(endpoint), HFP_AG_UUID) != 0)
+		return;
+
+	DBG("HSP endpoint registered: configuring %u active headsets",
+				g_slist_length(server->active_headsets));
+
+	for (l = server->active_headsets; l != NULL; l = g_slist_next(l)) {
+		struct headset *hs = l->data;
+
+		hs->transport = btd_media_endpoint_set_configuration(
+						endpoint, hs->audio_dev,
+						NULL, 0, headset_setconf_cb,
+						hs, NULL);
+	}
+}
+
 static int hsp_hs_server_probe(struct btd_profile *p,
 						struct btd_adapter *adapter)
 {
@@ -927,6 +1010,10 @@ static int hsp_hs_server_probe(struct btd_profile *p,
 		goto failed;
 	}
 
+	server->endpoint_register_cb_id = btd_media_endpoint_add_register_cb(
+					adapter, media_endpoint_register_cb,
+					server);
+
 	server->record_id = record->handle;
 	servers = g_slist_append(servers, server);
 
@@ -961,6 +1048,10 @@ static void hsp_hs_server_remove(struct btd_profile *p,
 		return;
 	}
 
+	if (server->endpoint_register_cb_id != 0)
+		btd_media_endpoint_remove_register_cb(adapter,
+					server->endpoint_register_cb_id);
+
 	if (server->record_id != 0)
 		remove_record_from_server(server->record_id);
 
-- 
1.8.1.4


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [RFC BlueZ v0 10/10] hsp: Implement media transport driver
  2013-07-12 10:54 [RFC BlueZ v0 00/10] HSP plugin Mikel Astiz
                   ` (8 preceding siblings ...)
  2013-07-12 10:54 ` [RFC BlueZ v0 09/10] hsp: Add Media API integration Mikel Astiz
@ 2013-07-12 10:54 ` Mikel Astiz
  2013-07-12 11:28 ` [RFC BlueZ v0 00/10] HSP plugin Luiz Augusto von Dentz
  10 siblings, 0 replies; 14+ messages in thread
From: Mikel Astiz @ 2013-07-12 10:54 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Mikel Astiz

From: Mikel Astiz <mikel.astiz@bmw-carit.de>

Implement the callbacks required by the Media API integration.
---
 plugins/hsp.c | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 253 insertions(+)

diff --git a/plugins/hsp.c b/plugins/hsp.c
index 740b857..7fcb990 100644
--- a/plugins/hsp.c
+++ b/plugins/hsp.c
@@ -60,16 +60,22 @@
 #include "sdpd.h"
 #include "btio.h"
 #include "profiles/audio/manager.h"
+#include "profiles/audio/device.h"
 #include "profiles/audio/media.h"
+#include "profiles/audio/transport.h"
 
 #define DEFAULT_HS_AG_CHANNEL 12
 #define BUF_SIZE 1024
+#define SCO_INPUT_MTU 48
+#define SCO_OUTPUT_MTU 48
 
 typedef enum {
 	HEADSET_STATE_DISCONNECTED,
 	HEADSET_STATE_CONNECTING,
 	HEADSET_STATE_CONNECTED,
+	HEADSET_STATE_RESUMING,
 	HEADSET_STATE_PLAYING,
+	HEADSET_STATE_SUSPENDING,
 } headset_state_t;
 
 struct headset_slc {
@@ -107,6 +113,7 @@ struct headset {
 
 	struct media_endpoint *endpoint;
 	struct media_transport *transport;
+	struct media_owner *pending_media_owner; /* Ongoing suspend or resume */
 };
 
 struct event {
@@ -127,8 +134,12 @@ static const char *state2str(headset_state_t state)
 		return "HEADSET_STATE_CONNECTING";
 	case HEADSET_STATE_CONNECTED:
 		return "HEADSET_STATE_CONNECTED";
+	case HEADSET_STATE_RESUMING:
+		return "HEADSET_STATE_RESUMING";
 	case HEADSET_STATE_PLAYING:
 		return "HEADSET_STATE_PLAYING";
+	case HEADSET_STATE_SUSPENDING:
+		return "HEADSET_STATE_SUSPENDING";
 	}
 
 	return NULL;
@@ -215,6 +226,8 @@ static void headset_init_sco(struct headset *hs)
 
 	assert(hs->sco != NULL);
 
+	DBG("media_owner: %p", hs->pending_media_owner);
+
 	fd = g_io_channel_unix_get_fd(hs->sco);
 	fcntl(fd, F_SETFL, 0);
 
@@ -226,6 +239,12 @@ static void headset_init_sco(struct headset *hs)
 	headset_send(hs, "\r\n+VGS=%u\r\n", hs->slc->sp_gain);
 	headset_send(hs, "\r\n+VGM=%u\r\n", hs->slc->mic_gain);
 
+	if (hs->state == HEADSET_STATE_RESUMING) {
+		media_transport_resume_complete(hs->pending_media_owner, fd,
+						SCO_INPUT_MTU, SCO_OUTPUT_MTU);
+		hs->pending_media_owner = NULL;
+	}
+
 	headset_set_state(hs, HEADSET_STATE_PLAYING);
 }
 
@@ -276,12 +295,22 @@ static int signal_gain_setting(struct headset *hs, const char *buf)
 			return -EALREADY;
 
 		slc->sp_gain = gain;
+
+		if (hs->transport != NULL)
+			media_transport_update_speaker_gain(hs->transport,
+									gain);
+
 		break;
 	case 'M':
 		if (slc->mic_gain == gain)
 			return 0;
 
 		slc->mic_gain = gain;
+
+		if (hs->transport != NULL)
+			media_transport_update_microphone_gain(hs->transport,
+									gain);
+
 		break;
 	default:
 		error("Unknown gain setting");
@@ -660,6 +689,16 @@ static void headset_set_state(struct headset *hs, headset_state_t state)
 	if (old_state == state)
 		return;
 
+	if (old_state == HEADSET_STATE_SUSPENDING) {
+		media_transport_suspend_complete(hs->pending_media_owner);
+		hs->pending_media_owner = NULL;
+	} else if (old_state == HEADSET_STATE_RESUMING &&
+					state != HEADSET_STATE_PLAYING) {
+		media_transport_resume_complete(hs->pending_media_owner, -1, 0,
+									0);
+		hs->pending_media_owner = NULL;
+	}
+
 	switch (state) {
 	case HEADSET_STATE_DISCONNECTED:
 		headset_clearconf(hs);
@@ -689,7 +728,9 @@ static void headset_set_state(struct headset *hs, headset_state_t state)
 		server->active_headsets = g_slist_append(
 						server->active_headsets, hs);
 		break;
+	case HEADSET_STATE_RESUMING:
 	case HEADSET_STATE_PLAYING:
+	case HEADSET_STATE_SUSPENDING:
 		break;
 	}
 
@@ -913,9 +954,15 @@ static void sco_server_cb(GIOChannel *io, GError *err, gpointer user_data)
 		goto drop;
 	case HEADSET_STATE_CONNECTED:
 		break;
+	case HEADSET_STATE_RESUMING:
+		DBG("Refusing SCO due to connect:connect xcase");
+		goto drop;
 	case HEADSET_STATE_PLAYING:
 		DBG("Refusing second SCO from same headset");
 		goto drop;
+	case HEADSET_STATE_SUSPENDING:
+		DBG("Refusing SCO while suspending previous one");
+		goto drop;
 	}
 
 	DBG("Accepted headset SCO connection from %s", addr);
@@ -1084,6 +1131,207 @@ static struct btd_profile hsp_profile = {
 	.adapter_remove = hsp_hs_server_remove,
 };
 
+static struct headset *find_transport_headset(struct media_transport *transport)
+{
+	struct audio_device *audio_dev = media_transport_get_dev(transport);
+	struct btd_service *service;
+
+	service = btd_device_get_service(audio_dev->btd_dev, HSP_HS_UUID);
+	assert(service != NULL);
+
+	return btd_service_get_user_data(service);
+}
+
+static void *hsp_transport_init(struct media_transport *transport)
+{
+	struct headset *hs = find_transport_headset(transport);
+
+	assert(hs != NULL);
+	assert(hs->slc != NULL);
+
+	media_transport_update_microphone_gain(transport, hs->slc->sp_gain);
+	media_transport_update_speaker_gain(transport, hs->slc->mic_gain);
+
+	return hs;
+}
+
+static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+	struct headset *hs = user_data;
+	struct btd_service *service = hs->service;
+	struct btd_device *device = btd_service_get_device(service);
+	char addr[18];
+
+	if (err) {
+		error("%s", err->message);
+
+		if (hs->rfcomm != NULL)
+			headset_set_state(hs, HEADSET_STATE_CONNECTED);
+		else
+			headset_set_state(hs, HEADSET_STATE_DISCONNECTED);
+
+		return;
+	}
+
+	ba2str(device_get_address(device), addr);
+	DBG("SCO socket opened for headset %s", addr);
+	headset_init_sco(hs);
+}
+
+static guint hsp_transport_resume(struct media_transport *transport,
+						struct media_owner *owner)
+{
+	struct headset *hs = media_transport_get_data(transport);
+	struct btd_device *device = btd_service_get_device(hs->service);
+	GError *err = NULL;
+	const bdaddr_t *src, *dst;
+	char addr[18];
+
+	if (hs->state == HEADSET_STATE_PLAYING)
+		return 0;
+
+	switch (hs->state) {
+	case HEADSET_STATE_DISCONNECTED:
+	case HEADSET_STATE_CONNECTING:
+		return -ENOTCONN;
+	case HEADSET_STATE_CONNECTED:
+		break;
+	case HEADSET_STATE_RESUMING:
+		return -EBUSY;
+	case HEADSET_STATE_PLAYING:
+		return -EISCONN;
+	case HEADSET_STATE_SUSPENDING:
+		return -EBUSY;
+	}
+
+	assert(hs->rfcomm != NULL);
+	assert(hs->sco == NULL);
+	assert(hs->pending_media_owner == NULL);
+
+	src = adapter_get_address(device_get_adapter(device));
+	dst = device_get_address(device);
+
+	ba2str(device_get_address(device), addr);
+	DBG("Requesting SCO for %s, media_owner %p", addr, owner);
+
+	hs->sco = bt_io_connect(sco_connect_cb, hs, NULL, &err,
+						BT_IO_OPT_SOURCE_BDADDR, src,
+						BT_IO_OPT_DEST_BDADDR, dst,
+						BT_IO_OPT_INVALID);
+
+	if (hs->sco == NULL) {
+		error("%s", err->message);
+		g_error_free(err);
+		return -EIO;
+	}
+
+	hs->pending_media_owner = owner;
+	headset_set_state(hs, HEADSET_STATE_RESUMING);
+
+	return 1;
+}
+
+static guint hsp_transport_suspend(struct media_transport *transport,
+						struct media_owner *owner)
+{
+	struct headset *hs = media_transport_get_data(transport);
+	struct btd_device *device = btd_service_get_device(hs->service);
+	char addr[18];
+	int sock;
+
+	switch (hs->state) {
+	case HEADSET_STATE_DISCONNECTED:
+	case HEADSET_STATE_CONNECTING:
+		return -ENOTCONN;
+	case HEADSET_STATE_CONNECTED:
+		return -EISCONN;
+	case HEADSET_STATE_RESUMING:
+		assert(hs->pending_media_owner != NULL);
+		media_transport_resume_complete(hs->pending_media_owner, -1, 0,
+									0);
+		hs->pending_media_owner = NULL;
+		break;
+	case HEADSET_STATE_PLAYING:
+		break;
+	case HEADSET_STATE_SUSPENDING:
+		return 0;
+	}
+
+	assert(hs->sco != NULL);
+	assert(hs->pending_media_owner == NULL);
+
+	ba2str(device_get_address(device), addr);
+	DBG("Suspending SCO for %s, media_owner %p", addr, owner);
+
+	/* shutdown but leave the socket open and wait for hup */
+	sock = g_io_channel_unix_get_fd(hs->sco);
+	shutdown(sock, SHUT_RDWR);
+
+	hs->pending_media_owner = owner;
+	headset_set_state(hs, HEADSET_STATE_SUSPENDING);
+
+	return 1;
+}
+
+static void hsp_transport_set_microphone_gain(
+				struct media_transport *transport, char gain)
+{
+	struct headset *hs = media_transport_get_data(transport);
+
+	hs->slc->mic_gain = gain;
+	headset_send(hs, "\r\n+VGM=%u\r\n", gain);
+}
+
+static void hsp_transport_set_speaker_gain(
+				struct media_transport *transport, char gain)
+{
+	struct headset *hs = media_transport_get_data(transport);
+
+	hs->slc->sp_gain = gain;
+	headset_send(hs, "\r\n+VGS=%u\r\n", gain);
+}
+
+static void hsp_transport_cancel(struct media_transport *transport, guint id)
+{
+}
+
+static void hsp_transport_destroy(void *data)
+{
+	struct headset *hs = data;
+
+	hs->transport = NULL;
+	hs->endpoint = NULL;
+	hs->pending_media_owner = NULL;
+
+	if (hs->state <= HEADSET_STATE_CONNECTED)
+		return;
+
+	headset_close_sco(hs);
+	headset_set_state(hs, HEADSET_STATE_CONNECTED);
+}
+
+static struct media_transport_driver hsp_ag_driver = {
+	.uuid			= HSP_AG_UUID,
+	.init			= hsp_transport_init,
+	.resume			= hsp_transport_resume,
+	.suspend		= hsp_transport_suspend,
+	.set_microphone_gain	= hsp_transport_set_microphone_gain,
+	.set_speaker_gain	= hsp_transport_set_speaker_gain,
+	.cancel			= hsp_transport_cancel,
+	.destroy		= hsp_transport_destroy,
+};
+
+static struct media_transport_driver hfp_ag_driver = {
+	.uuid			= HFP_AG_UUID,
+	.init			= hsp_transport_init,
+	.resume			= hsp_transport_resume,
+	.suspend		= hsp_transport_suspend,
+	.set_microphone_gain	= hsp_transport_set_microphone_gain,
+	.set_speaker_gain	= hsp_transport_set_speaker_gain,
+	.cancel			= hsp_transport_cancel,
+	.destroy		= hsp_transport_destroy,
+};
+
 static int hsp_init(void)
 {
 	int err;
@@ -1092,11 +1340,16 @@ static int hsp_init(void)
 	if (err < 0)
 		return err;
 
+	media_transport_driver_register(&hsp_ag_driver);
+	media_transport_driver_register(&hfp_ag_driver);
+
 	return 0;
 }
 
 static void hsp_exit(void)
 {
+	media_transport_driver_unregister(&hfp_ag_driver);
+	media_transport_driver_unregister(&hsp_ag_driver);
 	btd_profile_unregister(&hsp_profile);
 }
 
-- 
1.8.1.4


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [RFC BlueZ v0 00/10] HSP plugin
  2013-07-12 10:54 [RFC BlueZ v0 00/10] HSP plugin Mikel Astiz
                   ` (9 preceding siblings ...)
  2013-07-12 10:54 ` [RFC BlueZ v0 10/10] hsp: Implement media transport driver Mikel Astiz
@ 2013-07-12 11:28 ` Luiz Augusto von Dentz
  2013-07-12 13:48   ` Mikel Astiz
  10 siblings, 1 reply; 14+ messages in thread
From: Luiz Augusto von Dentz @ 2013-07-12 11:28 UTC (permalink / raw)
  To: Mikel Astiz; +Cc: linux-bluetooth, Mikel Astiz

Hi Mikel,

On Fri, Jul 12, 2013 at 1:54 PM, Mikel Astiz <mikel.astiz.oss@gmail.com> wrote:
> From: Mikel Astiz <mikel.astiz@bmw-carit.de>
>
> GNOME 3.10 freeze date is 2013-08-19 and they want to upgrade to BlueZ 5.
>
> One of the arguments against the upgrade is the lack of headset support. oFono should be convering this feature but it's development as well as the required work in PulseAudio are advancing too slowly for the mentioned target date.
>
> This patchset proposes a simple HSP plugin implementing the AG role, which is the most common use-case in desktop environments. It's a fallback alternative to a full-featured HSP/HFP implementation (i.e. oFono) in case the later is not available (i.e. not fully implemented or packaged in a specific distro).
>
> I think we could all agree about this not being the best long-term strategy, due to the overlap and conflict with oFono, but it might be useful in the short-term to boost the adoption of BlueZ 5.
>
> The plugin approach is more intrusive than I first thought because the Media API code was significantly simplified when all HSP/HFP code was removed. Hence, the patchset brings this code back along with the necessary APIs to be able to implement this inside a plugin.
>
> In order to test this, some PulseAudio patches are required (the last 5 patches of "[RFC next v5 00/11] bluetooth: BlueZ 5 development patches"). These were never merged in PA due to a chicken-egg problem: there was no code in BlueZ to test against.
>
> Mikel Astiz (10):
>   media: Expose Media API internally
>   media: Add callback to report new endpoints
>   transport: Regroup a2dp-specific members in struct
>   transport: Add API to register drivers
>   transport: Add API to report suspend/resume complete
>   transport: Add microphone/speaker gains
>   audio: Add function to remove inactive devices
>   hsp: Add initial HSP plugin
>   hsp: Add Media API integration
>   hsp: Implement media transport driver
>
>  Makefile.plugins           |    3 +
>  plugins/hsp.c              | 1357 ++++++++++++++++++++++++++++++++++++++++++++
>  profiles/audio/manager.c   |   12 +
>  profiles/audio/manager.h   |    1 +
>  profiles/audio/media.c     |  132 ++++-
>  profiles/audio/media.h     |   30 +
>  profiles/audio/transport.c |  384 +++++++++----
>  profiles/audio/transport.h |   29 +
>  8 files changed, 1818 insertions(+), 130 deletions(-)
>  create mode 100644 plugins/hsp.c
>
> --
> 1.8.1.4

I guess my suggestion of having this as a separate plugin was a bit
misleading since we are going to depend on media API anyway that
should be part of the same plugin, the other option would be to make
it a core API which I don't think is a good idea. Btw it seems this
would only work if audio plugin is loaded before hsp because you are
not really linking media symbols I guess there are being resolved by
pure luck and if you do link it might generate duplicated symbols when
loading the plugins, so interdependency between plugins files should
be avoided.


--
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC BlueZ v0 00/10] HSP plugin
  2013-07-12 11:28 ` [RFC BlueZ v0 00/10] HSP plugin Luiz Augusto von Dentz
@ 2013-07-12 13:48   ` Mikel Astiz
  2013-08-02 14:18     ` Luiz Augusto von Dentz
  0 siblings, 1 reply; 14+ messages in thread
From: Mikel Astiz @ 2013-07-12 13:48 UTC (permalink / raw)
  To: Marcel Holtmann; +Cc: linux-bluetooth, Luiz Augusto von Dentz

Hi Marcel,

On Fri, Jul 12, 2013 at 1:28 PM, Luiz Augusto von Dentz
<luiz.dentz@gmail.com> wrote:
> Hi Mikel,
>
> On Fri, Jul 12, 2013 at 1:54 PM, Mikel Astiz <mikel.astiz.oss@gmail.com> =
wrote:
>> From: Mikel Astiz <mikel.astiz@bmw-carit.de>
>>
>> GNOME 3.10 freeze date is 2013-08-19 and they want to upgrade to BlueZ 5=
.
>>
>> One of the arguments against the upgrade is the lack of headset support.=
 oFono should be convering this feature but it's development as well as the=
 required work in PulseAudio are advancing too slowly for the mentioned tar=
get date.
>>
>> This patchset proposes a simple HSP plugin implementing the AG role, whi=
ch is the most common use-case in desktop environments. It's a fallback alt=
ernative to a full-featured HSP/HFP implementation (i.e. oFono) in case the=
 later is not available (i.e. not fully implemented or packaged in a specif=
ic distro).
>>
>> I think we could all agree about this not being the best long-term strat=
egy, due to the overlap and conflict with oFono, but it might be useful in =
the short-term to boost the adoption of BlueZ 5.
>>
>> The plugin approach is more intrusive than I first thought because the M=
edia API code was significantly simplified when all HSP/HFP code was remove=
d. Hence, the patchset brings this code back along with the necessary APIs =
to be able to implement this inside a plugin.
>>
>> In order to test this, some PulseAudio patches are required (the last 5 =
patches of "[RFC next v5 00/11] bluetooth: BlueZ 5 development patches"). T=
hese were never merged in PA due to a chicken-egg problem: there was no cod=
e in BlueZ to test against.
>>
>> Mikel Astiz (10):
>>   media: Expose Media API internally
>>   media: Add callback to report new endpoints
>>   transport: Regroup a2dp-specific members in struct
>>   transport: Add API to register drivers
>>   transport: Add API to report suspend/resume complete
>>   transport: Add microphone/speaker gains
>>   audio: Add function to remove inactive devices
>>   hsp: Add initial HSP plugin
>>   hsp: Add Media API integration
>>   hsp: Implement media transport driver
>>
>>  Makefile.plugins           |    3 +
>>  plugins/hsp.c              | 1357 +++++++++++++++++++++++++++++++++++++=
+++++++
>>  profiles/audio/manager.c   |   12 +
>>  profiles/audio/manager.h   |    1 +
>>  profiles/audio/media.c     |  132 ++++-
>>  profiles/audio/media.h     |   30 +
>>  profiles/audio/transport.c |  384 +++++++++----
>>  profiles/audio/transport.h |   29 +
>>  8 files changed, 1818 insertions(+), 130 deletions(-)
>>  create mode 100644 plugins/hsp.c
>>
>> --
>> 1.8.1.4
>
> I guess my suggestion of having this as a separate plugin was a bit
> misleading since we are going to depend on media API anyway that
> should be part of the same plugin, the other option would be to make
> it a core API which I don't think is a good idea. Btw it seems this
> would only work if audio plugin is loaded before hsp because you are
> not really linking media symbols I guess there are being resolved by
> pure luck and if you do link it might generate duplicated symbols when
> loading the plugins, so interdependency between plugins files should
> be avoided.

Besides the issues pointed out by Luiz, which I could fix in a next
proposal, can you give your opinion on this approach?

It makes little sense to try to advance in this direction if it
doesn't fit the project plan.

Johan also suggested that such a feature could be automatically
disabled when an external profile (oFono) registers.

Cheers,
Mikel

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC BlueZ v0 00/10] HSP plugin
  2013-07-12 13:48   ` Mikel Astiz
@ 2013-08-02 14:18     ` Luiz Augusto von Dentz
  0 siblings, 0 replies; 14+ messages in thread
From: Luiz Augusto von Dentz @ 2013-08-02 14:18 UTC (permalink / raw)
  To: Mikel Astiz; +Cc: Marcel Holtmann, linux-bluetooth

Hi Mikel,

On Fri, Jul 12, 2013 at 4:48 PM, Mikel Astiz <mikel.astiz.oss@gmail.com> wrote:
> Besides the issues pointed out by Luiz, which I could fix in a next
> proposal, can you give your opinion on this approach?
>
> It makes little sense to try to advance in this direction if it
> doesn't fit the project plan.
>
> Johan also suggested that such a feature could be automatically
> disabled when an external profile (oFono) registers.

So as some know the initial idea was reject due to the use of Media
API which is now supposed to be A2DP specific, so Im currently
discussing with PulseAudio folks where this should be done then, so
far we come up with 3 options:

1. External profile implemented inside PA
2. BlueZ plugin with a different API
3. Dedicated daemon

I guess 1 or 2 is preferable as 3 might be overkill to maintain
independently, for solution 2 probably the API would be very similar
to the one implemented by oFono for HFP.


-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2013-08-02 14:18 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-07-12 10:54 [RFC BlueZ v0 00/10] HSP plugin Mikel Astiz
2013-07-12 10:54 ` [RFC BlueZ v0 01/10] media: Expose Media API internally Mikel Astiz
2013-07-12 10:54 ` [RFC BlueZ v0 02/10] media: Add callback to report new endpoints Mikel Astiz
2013-07-12 10:54 ` [RFC BlueZ v0 03/10] transport: Regroup a2dp-specific members in struct Mikel Astiz
2013-07-12 10:54 ` [RFC BlueZ v0 04/10] transport: Add API to register drivers Mikel Astiz
2013-07-12 10:54 ` [RFC BlueZ v0 05/10] transport: Add API to report suspend/resume complete Mikel Astiz
2013-07-12 10:54 ` [RFC BlueZ v0 06/10] transport: Add microphone/speaker gains Mikel Astiz
2013-07-12 10:54 ` [RFC BlueZ v0 07/10] audio: Add function to remove inactive devices Mikel Astiz
2013-07-12 10:54 ` [RFC BlueZ v0 08/10] hsp: Add initial HSP plugin Mikel Astiz
2013-07-12 10:54 ` [RFC BlueZ v0 09/10] hsp: Add Media API integration Mikel Astiz
2013-07-12 10:54 ` [RFC BlueZ v0 10/10] hsp: Implement media transport driver Mikel Astiz
2013-07-12 11:28 ` [RFC BlueZ v0 00/10] HSP plugin Luiz Augusto von Dentz
2013-07-12 13:48   ` Mikel Astiz
2013-08-02 14:18     ` Luiz Augusto von Dentz

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.