summaryrefslogtreecommitdiff
path: root/audio/media.c
diff options
context:
space:
mode:
Diffstat (limited to 'audio/media.c')
-rw-r--r--audio/media.c714
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;
+}