Home | History | Annotate | Download | only in audio
      1 /*
      2  *
      3  *  BlueZ - Bluetooth protocol stack for Linux
      4  *
      5  *  Copyright (C) 2006-2007  Nokia Corporation
      6  *  Copyright (C) 2004-2009  Marcel Holtmann <marcel (at) holtmann.org>
      7  *
      8  *
      9  *  This program is free software; you can redistribute it and/or modify
     10  *  it under the terms of the GNU General Public License as published by
     11  *  the Free Software Foundation; either version 2 of the License, or
     12  *  (at your option) any later version.
     13  *
     14  *  This program is distributed in the hope that it will be useful,
     15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17  *  GNU General Public License for more details.
     18  *
     19  *  You should have received a copy of the GNU General Public License
     20  *  along with this program; if not, write to the Free Software
     21  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     22  *
     23  */
     24 
     25 #ifdef HAVE_CONFIG_H
     26 #include <config.h>
     27 #endif
     28 
     29 #include <errno.h>
     30 
     31 #include <glib.h>
     32 #include <gdbus.h>
     33 
     34 #include "../src/adapter.h"
     35 #include "../src/dbus-common.h"
     36 
     37 #include "log.h"
     38 #include "error.h"
     39 #include "device.h"
     40 #include "avdtp.h"
     41 #include "media.h"
     42 #include "transport.h"
     43 #include "a2dp.h"
     44 #include "headset.h"
     45 #include "manager.h"
     46 
     47 #ifndef DBUS_TYPE_UNIX_FD
     48 #define DBUS_TYPE_UNIX_FD -1
     49 #endif
     50 
     51 #define MEDIA_INTERFACE "org.bluez.Media"
     52 #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint"
     53 
     54 #define REQUEST_TIMEOUT (3 * 1000)		/* 3 seconds */
     55 
     56 struct media_adapter {
     57 	bdaddr_t		src;		/* Adapter address */
     58 	char			*path;		/* Adapter path */
     59 	DBusConnection		*conn;		/* Adapter connection */
     60 	GSList			*endpoints;	/* Endpoints list */
     61 };
     62 
     63 struct endpoint_request {
     64 	DBusMessage		*msg;
     65 	DBusPendingCall		*call;
     66 	media_endpoint_cb_t	cb;
     67 	void			*user_data;
     68 };
     69 
     70 struct media_endpoint {
     71 	struct a2dp_sep		*sep;
     72 	char			*sender;	/* Endpoint DBus bus id */
     73 	char			*path;		/* Endpoint object path */
     74 	char			*uuid;		/* Endpoint property UUID */
     75 	uint8_t			codec;		/* Endpoint codec */
     76 	uint8_t			*capabilities;	/* Endpoint property capabilities */
     77 	size_t			size;		/* Endpoint capabilities size */
     78 	guint			hs_watch;
     79 	guint			watch;
     80 	struct endpoint_request *request;
     81 	struct media_transport	*transport;
     82 	struct media_adapter	*adapter;
     83 };
     84 
     85 static GSList *adapters = NULL;
     86 
     87 static void endpoint_request_free(struct endpoint_request *request)
     88 {
     89 	if (request->call)
     90 		dbus_pending_call_unref(request->call);
     91 
     92 	dbus_message_unref(request->msg);
     93 	g_free(request);
     94 }
     95 
     96 static void media_endpoint_cancel(struct media_endpoint *endpoint)
     97 {
     98 	struct endpoint_request *request = endpoint->request;
     99 
    100 	if (request->call)
    101 		dbus_pending_call_cancel(request->call);
    102 
    103 	endpoint_request_free(request);
    104 	endpoint->request = NULL;
    105 }
    106 
    107 static void media_endpoint_remove(struct media_endpoint *endpoint)
    108 {
    109 	struct media_adapter *adapter = endpoint->adapter;
    110 
    111 	if (g_slist_find(adapter->endpoints, endpoint) == NULL)
    112 		return;
    113 
    114 	info("Endpoint unregistered: sender=%s path=%s", endpoint->sender,
    115 			endpoint->path);
    116 
    117 	adapter->endpoints = g_slist_remove(adapter->endpoints, endpoint);
    118 
    119 	if (endpoint->sep)
    120 		a2dp_remove_sep(endpoint->sep);
    121 
    122 	if (endpoint->hs_watch)
    123 		headset_remove_state_cb(endpoint->hs_watch);
    124 
    125 	if (endpoint->request)
    126 		media_endpoint_cancel(endpoint);
    127 
    128 	if (endpoint->transport)
    129 		media_transport_destroy(endpoint->transport);
    130 
    131 	g_dbus_remove_watch(adapter->conn, endpoint->watch);
    132 	g_free(endpoint->capabilities);
    133 	g_free(endpoint->sender);
    134 	g_free(endpoint->path);
    135 	g_free(endpoint->uuid);
    136 	g_free(endpoint);
    137 }
    138 
    139 static void media_endpoint_exit(DBusConnection *connection, void *user_data)
    140 {
    141 	struct media_endpoint *endpoint = user_data;
    142 
    143 	endpoint->watch = 0;
    144 	media_endpoint_remove(endpoint);
    145 }
    146 
    147 static void headset_setconf_cb(struct media_endpoint *endpoint, void *ret,
    148 						int size, void *user_data)
    149 {
    150 	struct audio_device *dev = user_data;
    151 
    152 	if (ret != NULL)
    153 		return;
    154 
    155 	headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
    156 }
    157 
    158 static void headset_state_changed(struct audio_device *dev,
    159 					headset_state_t old_state,
    160 					headset_state_t new_state,
    161 					void *user_data)
    162 {
    163 	struct media_endpoint *endpoint = user_data;
    164 
    165 	DBG("");
    166 
    167 	switch (new_state) {
    168 	case HEADSET_STATE_DISCONNECTED:
    169 		media_endpoint_clear_configuration(endpoint);
    170 		break;
    171 	case HEADSET_STATE_CONNECTING:
    172 		media_endpoint_set_configuration(endpoint, dev, NULL, 0,
    173 						headset_setconf_cb, dev);
    174 		break;
    175 	case HEADSET_STATE_CONNECTED:
    176 		break;
    177 	case HEADSET_STATE_PLAY_IN_PROGRESS:
    178 		break;
    179 	case HEADSET_STATE_PLAYING:
    180 		break;
    181 	}
    182 }
    183 
    184 static struct media_endpoint *media_endpoint_create(struct media_adapter *adapter,
    185 						const char *sender,
    186 						const char *path,
    187 						const char *uuid,
    188 						gboolean delay_reporting,
    189 						uint8_t codec,
    190 						uint8_t *capabilities,
    191 						int size,
    192 						int *err)
    193 {
    194 	struct media_endpoint *endpoint;
    195 
    196 	endpoint = g_new0(struct media_endpoint, 1);
    197 	endpoint->sender = g_strdup(sender);
    198 	endpoint->path = g_strdup(path);
    199 	endpoint->uuid = g_strdup(uuid);
    200 	endpoint->codec = codec;
    201 
    202 	if (size > 0) {
    203 		endpoint->capabilities = g_new(uint8_t, size);
    204 		memcpy(endpoint->capabilities, capabilities, size);
    205 		endpoint->size = size;
    206 	}
    207 
    208 	endpoint->adapter = adapter;
    209 
    210 	if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) {
    211 		endpoint->sep = a2dp_add_sep(&adapter->src,
    212 					AVDTP_SEP_TYPE_SOURCE, codec,
    213 					delay_reporting, endpoint, err);
    214 		if (endpoint->sep == NULL)
    215 			goto failed;
    216 	} else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) {
    217 		endpoint->sep = a2dp_add_sep(&adapter->src,
    218 						AVDTP_SEP_TYPE_SINK, codec,
    219 						delay_reporting, endpoint, err);
    220 		if (endpoint->sep == NULL)
    221 			goto failed;
    222 	} else if (strcasecmp(uuid, HFP_AG_UUID) == 0 ||
    223 					g_strcmp0(uuid, HSP_AG_UUID) == 0) {
    224 		struct audio_device *dev;
    225 
    226 		endpoint->hs_watch = headset_add_state_cb(headset_state_changed,
    227 								endpoint);
    228 		dev = manager_find_device(NULL, &adapter->src, BDADDR_ANY,
    229 						AUDIO_HEADSET_INTERFACE, TRUE);
    230 		if (dev)
    231 			media_endpoint_set_configuration(endpoint, dev, NULL,
    232 							0, headset_setconf_cb,
    233 							dev);
    234 	} else {
    235 		if (err)
    236 			*err = -EINVAL;
    237 		goto failed;
    238 	}
    239 
    240 	endpoint->watch = g_dbus_add_disconnect_watch(adapter->conn, sender,
    241 						media_endpoint_exit, endpoint,
    242 						NULL);
    243 
    244 	adapter->endpoints = g_slist_append(adapter->endpoints, endpoint);
    245 	info("Endpoint registered: sender=%s path=%s", sender, path);
    246 
    247 	if (err)
    248 		*err = 0;
    249 	return endpoint;
    250 
    251 failed:
    252 	g_free(endpoint);
    253 	return NULL;
    254 }
    255 
    256 static struct media_endpoint *media_adapter_find_endpoint(
    257 						struct media_adapter *adapter,
    258 						const char *sender,
    259 						const char *path,
    260 						const char *uuid)
    261 {
    262 	GSList *l;
    263 
    264 	for (l = adapter->endpoints; l; l = l->next) {
    265 		struct media_endpoint *endpoint = l->data;
    266 
    267 		if (sender && g_strcmp0(endpoint->sender, sender) != 0)
    268 			continue;
    269 
    270 		if (path && g_strcmp0(endpoint->path, path) != 0)
    271 			continue;
    272 
    273 		if (uuid && g_strcmp0(endpoint->uuid, uuid) != 0)
    274 			continue;
    275 
    276 		return endpoint;
    277 	}
    278 
    279 	return NULL;
    280 }
    281 
    282 const char *media_endpoint_get_sender(struct media_endpoint *endpoint)
    283 {
    284 	return endpoint->sender;
    285 }
    286 
    287 static int parse_properties(DBusMessageIter *props, const char **uuid,
    288 				gboolean *delay_reporting, uint8_t *codec,
    289 				uint8_t **capabilities, int *size)
    290 {
    291 	gboolean has_uuid = FALSE;
    292 	gboolean has_codec = FALSE;
    293 
    294 	while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) {
    295 		const char *key;
    296 		DBusMessageIter value, entry;
    297 		int var;
    298 
    299 		dbus_message_iter_recurse(props, &entry);
    300 		dbus_message_iter_get_basic(&entry, &key);
    301 
    302 		dbus_message_iter_next(&entry);
    303 		dbus_message_iter_recurse(&entry, &value);
    304 
    305 		var = dbus_message_iter_get_arg_type(&value);
    306 		if (strcasecmp(key, "UUID") == 0) {
    307 			if (var != DBUS_TYPE_STRING)
    308 				return -EINVAL;
    309 			dbus_message_iter_get_basic(&value, uuid);
    310 			has_uuid = TRUE;
    311 		} else if (strcasecmp(key, "Codec") == 0) {
    312 			if (var != DBUS_TYPE_BYTE)
    313 				return -EINVAL;
    314 			dbus_message_iter_get_basic(&value, codec);
    315 			has_codec = TRUE;
    316 		} else if (strcasecmp(key, "DelayReporting") == 0) {
    317 			if (var != DBUS_TYPE_BOOLEAN)
    318 				return -EINVAL;
    319 			dbus_message_iter_get_basic(&value, delay_reporting);
    320 		} else if (strcasecmp(key, "Capabilities") == 0) {
    321 			DBusMessageIter array;
    322 
    323 			if (var != DBUS_TYPE_ARRAY)
    324 				return -EINVAL;
    325 
    326 			dbus_message_iter_recurse(&value, &array);
    327 			dbus_message_iter_get_fixed_array(&array, capabilities,
    328 							size);
    329 		}
    330 
    331 		dbus_message_iter_next(props);
    332 	}
    333 
    334 	return (has_uuid && has_codec) ? 0 : -EINVAL;
    335 }
    336 
    337 static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg,
    338 					void *data)
    339 {
    340 	struct media_adapter *adapter = data;
    341 	DBusMessageIter args, props;
    342 	const char *sender, *path, *uuid;
    343 	gboolean delay_reporting = FALSE;
    344 	uint8_t codec;
    345 	uint8_t *capabilities;
    346 	int size = 0;
    347 	int err;
    348 
    349 	sender = dbus_message_get_sender(msg);
    350 
    351 	dbus_message_iter_init(msg, &args);
    352 
    353 	dbus_message_iter_get_basic(&args, &path);
    354 	dbus_message_iter_next(&args);
    355 
    356 	if (media_adapter_find_endpoint(adapter, sender, path, NULL) != NULL)
    357 		return btd_error_already_exists(msg);
    358 
    359 	dbus_message_iter_recurse(&args, &props);
    360 	if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
    361 		return btd_error_invalid_args(msg);
    362 
    363 	if (parse_properties(&props, &uuid, &delay_reporting, &codec,
    364 						&capabilities, &size) < 0)
    365 		return btd_error_invalid_args(msg);
    366 
    367 	if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting,
    368 				codec, capabilities, size, &err) == FALSE) {
    369 		if (err == -EPROTONOSUPPORT)
    370 			return btd_error_not_supported(msg);
    371 		else
    372 			return btd_error_invalid_args(msg);
    373 	}
    374 
    375 	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
    376 }
    377 
    378 static DBusMessage *unregister_endpoint(DBusConnection *conn, DBusMessage *msg,
    379 					void *data)
    380 {
    381 	struct media_adapter *adapter = data;
    382 	struct media_endpoint *endpoint;
    383 	const char *sender, *path;
    384 
    385 	if (!dbus_message_get_args(msg, NULL,
    386 				DBUS_TYPE_OBJECT_PATH, &path,
    387 				DBUS_TYPE_INVALID))
    388 		return NULL;
    389 
    390 	sender = dbus_message_get_sender(msg);
    391 
    392 	endpoint = media_adapter_find_endpoint(adapter, sender, path, NULL);
    393 	if (endpoint == NULL)
    394 		return btd_error_does_not_exist(msg);
    395 
    396 	media_endpoint_remove(endpoint);
    397 
    398 	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
    399 }
    400 
    401 static GDBusMethodTable media_methods[] = {
    402 	{ "RegisterEndpoint",	"oa{sv}",	"",	register_endpoint },
    403 	{ "UnregisterEndpoint",	"o",		"",	unregister_endpoint },
    404 	{ },
    405 };
    406 
    407 static void path_free(void *data)
    408 {
    409 	struct media_adapter *adapter = data;
    410 
    411 	g_slist_foreach(adapter->endpoints, (GFunc) media_endpoint_release,
    412 									NULL);
    413 	g_slist_free(adapter->endpoints);
    414 
    415 	dbus_connection_unref(adapter->conn);
    416 
    417 	adapters = g_slist_remove(adapters, adapter);
    418 
    419 	g_free(adapter->path);
    420 	g_free(adapter);
    421 }
    422 
    423 int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src)
    424 {
    425 	struct media_adapter *adapter;
    426 
    427 	if (DBUS_TYPE_UNIX_FD < 0)
    428 		return -EPERM;
    429 
    430 	adapter = g_new0(struct media_adapter, 1);
    431 	adapter->conn = dbus_connection_ref(conn);
    432 	bacpy(&adapter->src, src);
    433 	adapter->path = g_strdup(path);
    434 
    435 	if (!g_dbus_register_interface(conn, path, MEDIA_INTERFACE,
    436 					media_methods, NULL, NULL,
    437 					adapter, path_free)) {
    438 		error("D-Bus failed to register %s path", path);
    439 		path_free(adapter);
    440 		return -1;
    441 	}
    442 
    443 	adapters = g_slist_append(adapters, adapter);
    444 
    445 	return 0;
    446 }
    447 
    448 void media_unregister(const char *path)
    449 {
    450 	GSList *l;
    451 
    452 	for (l = adapters; l; l = l->next) {
    453 		struct media_adapter *adapter = l->data;
    454 
    455 		if (g_strcmp0(path, adapter->path) == 0) {
    456 			g_dbus_unregister_interface(adapter->conn, path,
    457 							MEDIA_INTERFACE);
    458 			return;
    459 		}
    460 	}
    461 }
    462 
    463 size_t media_endpoint_get_capabilities(struct media_endpoint *endpoint,
    464 					uint8_t **capabilities)
    465 {
    466 	*capabilities = endpoint->capabilities;
    467 	return endpoint->size;
    468 }
    469 
    470 static void endpoint_reply(DBusPendingCall *call, void *user_data)
    471 {
    472 	struct media_endpoint *endpoint = user_data;
    473 	struct endpoint_request *request = endpoint->request;
    474 	DBusMessage *reply;
    475 	DBusError err;
    476 	gboolean value;
    477 	void *ret = NULL;
    478 	int size = -1;
    479 
    480 	/* steal_reply will always return non-NULL since the callback
    481 	 * is only called after a reply has been received */
    482 	reply = dbus_pending_call_steal_reply(call);
    483 
    484 	dbus_error_init(&err);
    485 	if (dbus_set_error_from_message(&err, reply)) {
    486 		error("Endpoint replied with an error: %s",
    487 				err.name);
    488 
    489 		/* Clear endpoint configuration in case of NO_REPLY error */
    490 		if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) {
    491 			if (request->cb)
    492 				request->cb(endpoint, NULL, size,
    493 							request->user_data);
    494 			media_endpoint_clear_configuration(endpoint);
    495 			dbus_message_unref(reply);
    496 			dbus_error_free(&err);
    497 			return;
    498 		}
    499 
    500 		dbus_error_free(&err);
    501 		goto done;
    502 	}
    503 
    504 	dbus_error_init(&err);
    505 	if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE,
    506 				"SelectConfiguration")) {
    507 		DBusMessageIter args, array;
    508 		uint8_t *configuration;
    509 
    510 		dbus_message_iter_init(reply, &args);
    511 
    512 		dbus_message_iter_recurse(&args, &array);
    513 
    514 		dbus_message_iter_get_fixed_array(&array, &configuration, &size);
    515 
    516 		ret = configuration;
    517 		goto done;
    518 	} else  if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) {
    519 		error("Wrong reply signature: %s", err.message);
    520 		dbus_error_free(&err);
    521 		goto done;
    522 	}
    523 
    524 	size = 1;
    525 	value = TRUE;
    526 	ret = &value;
    527 
    528 done:
    529 	dbus_message_unref(reply);
    530 
    531 	if (request->cb)
    532 		request->cb(endpoint, ret, size, request->user_data);
    533 
    534 	endpoint_request_free(request);
    535 	endpoint->request = NULL;
    536 }
    537 
    538 static gboolean media_endpoint_async_call(DBusConnection *conn,
    539 					DBusMessage *msg,
    540 					struct media_endpoint *endpoint,
    541 					media_endpoint_cb_t cb,
    542 					void *user_data)
    543 {
    544 	struct endpoint_request *request;
    545 
    546 	if (endpoint->request)
    547 		return FALSE;
    548 
    549 	request = g_new0(struct endpoint_request, 1);
    550 
    551 	/* Timeout should be less than avdtp request timeout (4 seconds) */
    552 	if (dbus_connection_send_with_reply(conn, msg, &request->call,
    553 						REQUEST_TIMEOUT) == FALSE) {
    554 		error("D-Bus send failed");
    555 		g_free(request);
    556 		return FALSE;
    557 	}
    558 
    559 	dbus_pending_call_set_notify(request->call, endpoint_reply, endpoint, NULL);
    560 
    561 	request->msg = msg;
    562 	request->cb = cb;
    563 	request->user_data = user_data;
    564 	endpoint->request = request;
    565 
    566 	DBG("Calling %s: name = %s path = %s", dbus_message_get_member(msg),
    567 			dbus_message_get_destination(msg),
    568 			dbus_message_get_path(msg));
    569 
    570 	return TRUE;
    571 }
    572 
    573 gboolean media_endpoint_set_configuration(struct media_endpoint *endpoint,
    574 					struct audio_device *device,
    575 					uint8_t *configuration, size_t size,
    576 					media_endpoint_cb_t cb,
    577 					void *user_data)
    578 {
    579 	DBusConnection *conn;
    580 	DBusMessage *msg;
    581 	const char *path;
    582 	DBusMessageIter iter;
    583 
    584 	if (endpoint->transport != NULL || endpoint->request != NULL)
    585 		return FALSE;
    586 
    587 	conn = endpoint->adapter->conn;
    588 
    589 	endpoint->transport = media_transport_create(conn, endpoint, device,
    590 						configuration, size);
    591 	if (endpoint->transport == NULL)
    592 		return FALSE;
    593 
    594 	msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
    595 						MEDIA_ENDPOINT_INTERFACE,
    596 						"SetConfiguration");
    597 	if (msg == NULL) {
    598 		error("Couldn't allocate D-Bus message");
    599 		return FALSE;
    600 	}
    601 
    602 	dbus_message_iter_init_append(msg, &iter);
    603 
    604 	path = media_transport_get_path(endpoint->transport);
    605 	dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
    606 
    607 	transport_get_properties(endpoint->transport, &iter);
    608 
    609 	return media_endpoint_async_call(conn, msg, endpoint, cb, user_data);
    610 }
    611 
    612 gboolean media_endpoint_select_configuration(struct media_endpoint *endpoint,
    613 						uint8_t *capabilities,
    614 						size_t length,
    615 						media_endpoint_cb_t cb,
    616 						void *user_data)
    617 {
    618 	DBusConnection *conn;
    619 	DBusMessage *msg;
    620 
    621 	if (endpoint->request != NULL)
    622 		return FALSE;
    623 
    624 	conn = endpoint->adapter->conn;
    625 
    626 	msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
    627 						MEDIA_ENDPOINT_INTERFACE,
    628 						"SelectConfiguration");
    629 	if (msg == NULL) {
    630 		error("Couldn't allocate D-Bus message");
    631 		return FALSE;
    632 	}
    633 
    634 	dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
    635 					&capabilities, length,
    636 					DBUS_TYPE_INVALID);
    637 
    638 	return media_endpoint_async_call(conn, msg, endpoint, cb, user_data);
    639 }
    640 
    641 void media_endpoint_clear_configuration(struct media_endpoint *endpoint)
    642 {
    643 	DBusConnection *conn;
    644 	DBusMessage *msg;
    645 	const char *path;
    646 
    647 	if (endpoint->transport == NULL)
    648 		return;
    649 
    650 	if (endpoint->request)
    651 		media_endpoint_cancel(endpoint);
    652 
    653 	conn = endpoint->adapter->conn;
    654 
    655 	msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
    656 						MEDIA_ENDPOINT_INTERFACE,
    657 						"ClearConfiguration");
    658 	if (msg == NULL) {
    659 		error("Couldn't allocate D-Bus message");
    660 		goto done;
    661 	}
    662 
    663 	path = media_transport_get_path(endpoint->transport);
    664 	dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path,
    665 							DBUS_TYPE_INVALID);
    666 	g_dbus_send_message(conn, msg);
    667 done:
    668 	media_transport_destroy(endpoint->transport);
    669 	endpoint->transport = NULL;
    670 }
    671 
    672 void media_endpoint_release(struct media_endpoint *endpoint)
    673 {
    674 	DBusMessage *msg;
    675 
    676 	DBG("sender=%s path=%s", endpoint->sender, endpoint->path);
    677 
    678 	/* already exit */
    679 	if (endpoint->watch == 0)
    680 		return;
    681 
    682 	msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
    683 						MEDIA_ENDPOINT_INTERFACE,
    684 						"Release");
    685 	if (msg == NULL) {
    686 		error("Couldn't allocate D-Bus message");
    687 		return;
    688 	}
    689 
    690 	g_dbus_send_message(endpoint->adapter->conn, msg);
    691 
    692 	media_endpoint_remove(endpoint);
    693 }
    694 
    695 struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint)
    696 {
    697 	return endpoint->sep;
    698 }
    699 
    700 const char *media_endpoint_get_uuid(struct media_endpoint *endpoint)
    701 {
    702 	return endpoint->uuid;
    703 }
    704 
    705 uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint)
    706 {
    707 	return endpoint->codec;
    708 }
    709 
    710 struct media_transport *media_endpoint_get_transport(
    711 					struct media_endpoint *endpoint)
    712 {
    713 	return endpoint->transport;
    714 }
    715