diff options
author | Adrian Bunk <adrian.bunk@movial.com> | 2011-06-03 09:17:04 +0000 |
---|---|---|
committer | Adrian Bunk <adrian.bunk@movial.com> | 2011-06-03 09:17:04 +0000 |
commit | 799757ccf1d03c33c75bc597cd5ef77741dcb6a7 (patch) | |
tree | a8c3be85c730de28b012586591b76301033d3d21 /audio/media.c |
Imported upstream 4.91upstream-4.91upstreampackaging
Diffstat (limited to 'audio/media.c')
-rw-r--r-- | audio/media.c | 714 |
1 files changed, 714 insertions, 0 deletions
diff --git a/audio/media.c b/audio/media.c new file mode 100644 index 0000000..0efb0a8 --- /dev/null +++ b/audio/media.c @@ -0,0 +1,714 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <errno.h> + +#include <glib.h> +#include <gdbus.h> + +#include "../src/adapter.h" +#include "../src/dbus-common.h" + +#include "log.h" +#include "error.h" +#include "device.h" +#include "avdtp.h" +#include "media.h" +#include "transport.h" +#include "a2dp.h" +#include "headset.h" +#include "manager.h" + +#ifndef DBUS_TYPE_UNIX_FD +#define DBUS_TYPE_UNIX_FD -1 +#endif + +#define MEDIA_INTERFACE "org.bluez.Media" +#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint" + +#define REQUEST_TIMEOUT (3 * 1000) /* 3 seconds */ + +struct media_adapter { + bdaddr_t src; /* Adapter address */ + char *path; /* Adapter path */ + DBusConnection *conn; /* Adapter connection */ + GSList *endpoints; /* Endpoints list */ +}; + +struct endpoint_request { + DBusMessage *msg; + DBusPendingCall *call; + media_endpoint_cb_t cb; + void *user_data; +}; + +struct media_endpoint { + struct a2dp_sep *sep; + char *sender; /* Endpoint DBus bus id */ + char *path; /* Endpoint object path */ + char *uuid; /* Endpoint property UUID */ + uint8_t codec; /* Endpoint codec */ + uint8_t *capabilities; /* Endpoint property capabilities */ + size_t size; /* Endpoint capabilities size */ + guint hs_watch; + guint watch; + struct endpoint_request *request; + struct media_transport *transport; + struct media_adapter *adapter; +}; + +static GSList *adapters = NULL; + +static void endpoint_request_free(struct endpoint_request *request) +{ + if (request->call) + dbus_pending_call_unref(request->call); + + dbus_message_unref(request->msg); + g_free(request); +} + +static void media_endpoint_cancel(struct media_endpoint *endpoint) +{ + struct endpoint_request *request = endpoint->request; + + if (request->call) + dbus_pending_call_cancel(request->call); + + endpoint_request_free(request); + endpoint->request = NULL; +} + +static void media_endpoint_remove(struct media_endpoint *endpoint) +{ + struct media_adapter *adapter = endpoint->adapter; + + if (g_slist_find(adapter->endpoints, endpoint) == NULL) + return; + + info("Endpoint unregistered: sender=%s path=%s", endpoint->sender, + endpoint->path); + + adapter->endpoints = g_slist_remove(adapter->endpoints, endpoint); + + if (endpoint->sep) + a2dp_remove_sep(endpoint->sep); + + if (endpoint->hs_watch) + headset_remove_state_cb(endpoint->hs_watch); + + if (endpoint->request) + media_endpoint_cancel(endpoint); + + if (endpoint->transport) + media_transport_destroy(endpoint->transport); + + g_dbus_remove_watch(adapter->conn, endpoint->watch); + g_free(endpoint->capabilities); + g_free(endpoint->sender); + g_free(endpoint->path); + g_free(endpoint->uuid); + g_free(endpoint); +} + +static void media_endpoint_exit(DBusConnection *connection, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + endpoint->watch = 0; + media_endpoint_remove(endpoint); +} + +static void headset_setconf_cb(struct media_endpoint *endpoint, void *ret, + int size, void *user_data) +{ + struct audio_device *dev = user_data; + + if (ret != NULL) + return; + + headset_set_state(dev, HEADSET_STATE_DISCONNECTED); +} + +static void headset_state_changed(struct audio_device *dev, + headset_state_t old_state, + headset_state_t new_state, + void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + DBG(""); + + switch (new_state) { + case HEADSET_STATE_DISCONNECTED: + media_endpoint_clear_configuration(endpoint); + break; + case HEADSET_STATE_CONNECTING: + media_endpoint_set_configuration(endpoint, dev, NULL, 0, + headset_setconf_cb, dev); + break; + case HEADSET_STATE_CONNECTED: + break; + case HEADSET_STATE_PLAY_IN_PROGRESS: + break; + case HEADSET_STATE_PLAYING: + break; + } +} + +static struct media_endpoint *media_endpoint_create(struct media_adapter *adapter, + const char *sender, + const char *path, + const char *uuid, + gboolean delay_reporting, + uint8_t codec, + uint8_t *capabilities, + int size, + int *err) +{ + struct media_endpoint *endpoint; + + endpoint = g_new0(struct media_endpoint, 1); + endpoint->sender = g_strdup(sender); + endpoint->path = g_strdup(path); + endpoint->uuid = g_strdup(uuid); + endpoint->codec = codec; + + if (size > 0) { + endpoint->capabilities = g_new(uint8_t, size); + memcpy(endpoint->capabilities, capabilities, size); + endpoint->size = size; + } + + endpoint->adapter = adapter; + + if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) { + endpoint->sep = a2dp_add_sep(&adapter->src, + AVDTP_SEP_TYPE_SOURCE, codec, + delay_reporting, endpoint, err); + if (endpoint->sep == NULL) + goto failed; + } else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) { + endpoint->sep = a2dp_add_sep(&adapter->src, + AVDTP_SEP_TYPE_SINK, codec, + delay_reporting, endpoint, err); + if (endpoint->sep == NULL) + goto failed; + } else if (strcasecmp(uuid, HFP_AG_UUID) == 0 || + g_strcmp0(uuid, HSP_AG_UUID) == 0) { + struct audio_device *dev; + + endpoint->hs_watch = headset_add_state_cb(headset_state_changed, + endpoint); + dev = manager_find_device(NULL, &adapter->src, BDADDR_ANY, + AUDIO_HEADSET_INTERFACE, TRUE); + if (dev) + media_endpoint_set_configuration(endpoint, dev, NULL, + 0, headset_setconf_cb, + dev); + } else { + if (err) + *err = -EINVAL; + goto failed; + } + + endpoint->watch = g_dbus_add_disconnect_watch(adapter->conn, sender, + media_endpoint_exit, endpoint, + NULL); + + adapter->endpoints = g_slist_append(adapter->endpoints, endpoint); + info("Endpoint registered: sender=%s path=%s", sender, path); + + if (err) + *err = 0; + return endpoint; + +failed: + g_free(endpoint); + return NULL; +} + +static struct media_endpoint *media_adapter_find_endpoint( + struct media_adapter *adapter, + const char *sender, + const char *path, + const char *uuid) +{ + GSList *l; + + for (l = adapter->endpoints; l; l = l->next) { + struct media_endpoint *endpoint = l->data; + + if (sender && g_strcmp0(endpoint->sender, sender) != 0) + continue; + + if (path && g_strcmp0(endpoint->path, path) != 0) + continue; + + if (uuid && g_strcmp0(endpoint->uuid, uuid) != 0) + continue; + + return endpoint; + } + + return NULL; +} + +const char *media_endpoint_get_sender(struct media_endpoint *endpoint) +{ + return endpoint->sender; +} + +static int parse_properties(DBusMessageIter *props, const char **uuid, + gboolean *delay_reporting, uint8_t *codec, + uint8_t **capabilities, int *size) +{ + gboolean has_uuid = FALSE; + gboolean has_codec = FALSE; + + while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + if (strcasecmp(key, "UUID") == 0) { + if (var != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(&value, uuid); + has_uuid = TRUE; + } else if (strcasecmp(key, "Codec") == 0) { + if (var != DBUS_TYPE_BYTE) + return -EINVAL; + dbus_message_iter_get_basic(&value, codec); + has_codec = TRUE; + } else if (strcasecmp(key, "DelayReporting") == 0) { + if (var != DBUS_TYPE_BOOLEAN) + return -EINVAL; + dbus_message_iter_get_basic(&value, delay_reporting); + } else if (strcasecmp(key, "Capabilities") == 0) { + DBusMessageIter array; + + if (var != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_fixed_array(&array, capabilities, + size); + } + + dbus_message_iter_next(props); + } + + return (has_uuid && has_codec) ? 0 : -EINVAL; +} + +static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_adapter *adapter = data; + DBusMessageIter args, props; + const char *sender, *path, *uuid; + gboolean delay_reporting = FALSE; + uint8_t codec; + uint8_t *capabilities; + int size = 0; + int err; + + sender = dbus_message_get_sender(msg); + + dbus_message_iter_init(msg, &args); + + dbus_message_iter_get_basic(&args, &path); + dbus_message_iter_next(&args); + + if (media_adapter_find_endpoint(adapter, sender, path, NULL) != NULL) + return btd_error_already_exists(msg); + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) + return btd_error_invalid_args(msg); + + if (parse_properties(&props, &uuid, &delay_reporting, &codec, + &capabilities, &size) < 0) + return btd_error_invalid_args(msg); + + if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting, + codec, capabilities, size, &err) == FALSE) { + if (err == -EPROTONOSUPPORT) + return btd_error_not_supported(msg); + else + return btd_error_invalid_args(msg); + } + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *unregister_endpoint(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_adapter *adapter = data; + struct media_endpoint *endpoint; + const char *sender, *path; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return NULL; + + sender = dbus_message_get_sender(msg); + + endpoint = media_adapter_find_endpoint(adapter, sender, path, NULL); + if (endpoint == NULL) + return btd_error_does_not_exist(msg); + + media_endpoint_remove(endpoint); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static GDBusMethodTable media_methods[] = { + { "RegisterEndpoint", "oa{sv}", "", register_endpoint }, + { "UnregisterEndpoint", "o", "", unregister_endpoint }, + { }, +}; + +static void path_free(void *data) +{ + struct media_adapter *adapter = data; + + g_slist_foreach(adapter->endpoints, (GFunc) media_endpoint_release, + NULL); + g_slist_free(adapter->endpoints); + + dbus_connection_unref(adapter->conn); + + adapters = g_slist_remove(adapters, adapter); + + g_free(adapter->path); + g_free(adapter); +} + +int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src) +{ + struct media_adapter *adapter; + + if (DBUS_TYPE_UNIX_FD < 0) + return -EPERM; + + adapter = g_new0(struct media_adapter, 1); + adapter->conn = dbus_connection_ref(conn); + bacpy(&adapter->src, src); + adapter->path = g_strdup(path); + + if (!g_dbus_register_interface(conn, path, MEDIA_INTERFACE, + media_methods, NULL, NULL, + adapter, path_free)) { + error("D-Bus failed to register %s path", path); + path_free(adapter); + return -1; + } + + adapters = g_slist_append(adapters, adapter); + + return 0; +} + +void media_unregister(const char *path) +{ + GSList *l; + + for (l = adapters; l; l = l->next) { + struct media_adapter *adapter = l->data; + + if (g_strcmp0(path, adapter->path) == 0) { + g_dbus_unregister_interface(adapter->conn, path, + MEDIA_INTERFACE); + return; + } + } +} + +size_t media_endpoint_get_capabilities(struct media_endpoint *endpoint, + uint8_t **capabilities) +{ + *capabilities = endpoint->capabilities; + return endpoint->size; +} + +static void endpoint_reply(DBusPendingCall *call, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + struct endpoint_request *request = endpoint->request; + DBusMessage *reply; + DBusError err; + gboolean value; + void *ret = NULL; + int size = -1; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("Endpoint replied with an error: %s", + err.name); + + /* Clear endpoint configuration in case of NO_REPLY error */ + if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { + if (request->cb) + request->cb(endpoint, NULL, size, + request->user_data); + media_endpoint_clear_configuration(endpoint); + dbus_message_unref(reply); + dbus_error_free(&err); + return; + } + + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE, + "SelectConfiguration")) { + DBusMessageIter args, array; + uint8_t *configuration; + + dbus_message_iter_init(reply, &args); + + dbus_message_iter_recurse(&args, &array); + + dbus_message_iter_get_fixed_array(&array, &configuration, &size); + + ret = configuration; + goto done; + } else if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) { + error("Wrong reply signature: %s", err.message); + dbus_error_free(&err); + goto done; + } + + size = 1; + value = TRUE; + ret = &value; + +done: + dbus_message_unref(reply); + + if (request->cb) + request->cb(endpoint, ret, size, request->user_data); + + endpoint_request_free(request); + endpoint->request = NULL; +} + +static gboolean media_endpoint_async_call(DBusConnection *conn, + DBusMessage *msg, + struct media_endpoint *endpoint, + media_endpoint_cb_t cb, + void *user_data) +{ + struct endpoint_request *request; + + if (endpoint->request) + return FALSE; + + request = g_new0(struct endpoint_request, 1); + + /* Timeout should be less than avdtp request timeout (4 seconds) */ + if (dbus_connection_send_with_reply(conn, msg, &request->call, + REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + g_free(request); + return FALSE; + } + + dbus_pending_call_set_notify(request->call, endpoint_reply, endpoint, NULL); + + request->msg = msg; + request->cb = cb; + request->user_data = user_data; + endpoint->request = request; + + DBG("Calling %s: name = %s path = %s", dbus_message_get_member(msg), + dbus_message_get_destination(msg), + dbus_message_get_path(msg)); + + return TRUE; +} + +gboolean 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) +{ + DBusConnection *conn; + DBusMessage *msg; + const char *path; + DBusMessageIter iter; + + if (endpoint->transport != NULL || endpoint->request != NULL) + return FALSE; + + conn = endpoint->adapter->conn; + + endpoint->transport = media_transport_create(conn, endpoint, device, + configuration, size); + if (endpoint->transport == NULL) + return FALSE; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "SetConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return FALSE; + } + + dbus_message_iter_init_append(msg, &iter); + + path = media_transport_get_path(endpoint->transport); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); + + transport_get_properties(endpoint->transport, &iter); + + return media_endpoint_async_call(conn, msg, endpoint, cb, user_data); +} + +gboolean media_endpoint_select_configuration(struct media_endpoint *endpoint, + uint8_t *capabilities, + size_t length, + media_endpoint_cb_t cb, + void *user_data) +{ + DBusConnection *conn; + DBusMessage *msg; + + if (endpoint->request != NULL) + return FALSE; + + conn = endpoint->adapter->conn; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "SelectConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return FALSE; + } + + dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, + &capabilities, length, + DBUS_TYPE_INVALID); + + return media_endpoint_async_call(conn, msg, endpoint, cb, user_data); +} + +void media_endpoint_clear_configuration(struct media_endpoint *endpoint) +{ + DBusConnection *conn; + DBusMessage *msg; + const char *path; + + if (endpoint->transport == NULL) + return; + + if (endpoint->request) + media_endpoint_cancel(endpoint); + + conn = endpoint->adapter->conn; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "ClearConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + goto done; + } + + path = media_transport_get_path(endpoint->transport); + dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + g_dbus_send_message(conn, msg); +done: + media_transport_destroy(endpoint->transport); + endpoint->transport = NULL; +} + +void media_endpoint_release(struct media_endpoint *endpoint) +{ + DBusMessage *msg; + + DBG("sender=%s path=%s", endpoint->sender, endpoint->path); + + /* already exit */ + if (endpoint->watch == 0) + return; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "Release"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return; + } + + g_dbus_send_message(endpoint->adapter->conn, msg); + + media_endpoint_remove(endpoint); +} + +struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint) +{ + return endpoint->sep; +} + +const char *media_endpoint_get_uuid(struct media_endpoint *endpoint) +{ + return endpoint->uuid; +} + +uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint) +{ + return endpoint->codec; +} + +struct media_transport *media_endpoint_get_transport( + struct media_endpoint *endpoint) +{ + return endpoint->transport; +} |