diff options
Diffstat (limited to 'profiles/audio/transport.c')
-rw-r--r-- | profiles/audio/transport.c | 953 |
1 files changed, 953 insertions, 0 deletions
diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c new file mode 100644 index 00000000..087c0ee5 --- /dev/null +++ b/profiles/audio/transport.c @@ -0,0 +1,953 @@ +/* + * + * 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/gdbus.h> + +#include "lib/uuid.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/dbus-common.h" + +#include "log.h" +#include "error.h" +#include "avdtp.h" +#include "media.h" +#include "transport.h" +#include "a2dp.h" +#include "sink.h" +#include "source.h" +#include "avrcp.h" + +#define MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport1" + +typedef enum { + TRANSPORT_STATE_IDLE, /* Not acquired and suspended */ + TRANSPORT_STATE_PENDING, /* Playing but not acquired */ + TRANSPORT_STATE_REQUESTING, /* Acquire in progress */ + TRANSPORT_STATE_ACTIVE, /* Acquired and playing */ + TRANSPORT_STATE_SUSPENDING, /* Release in progress */ +} transport_state_t; + +static char *str_state[] = { + "TRANSPORT_STATE_IDLE", + "TRANSPORT_STATE_PENDING", + "TRANSPORT_STATE_REQUESTING", + "TRANSPORT_STATE_ACTIVE", + "TRANSPORT_STATE_SUSPENDING", +}; + +struct media_request { + DBusMessage *msg; + guint id; +}; + +struct media_owner { + struct media_transport *transport; + struct media_request *pending; + char *name; + guint watch; +}; + +struct a2dp_transport { + struct avdtp *session; + uint16_t delay; + uint16_t volume; +}; + +struct media_transport { + char *path; /* Transport object path */ + struct btd_device *device; /* Transport device */ + struct media_endpoint *endpoint; /* Transport endpoint */ + struct media_owner *owner; /* Transport owner */ + uint8_t *configuration; /* Transport configuration */ + int size; /* Transport configuration size */ + int fd; /* Transport file descriptor */ + uint16_t imtu; /* Transport input mtu */ + uint16_t omtu; /* Transport output mtu */ + 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, + struct media_owner *owner); + void (*cancel) (struct media_transport *transport, + guint id); + GDestroyNotify destroy; + void *data; +}; + +static GSList *transports = NULL; + +static const char *state2str(transport_state_t state) +{ + switch (state) { + case TRANSPORT_STATE_IDLE: + case TRANSPORT_STATE_REQUESTING: + return "idle"; + case TRANSPORT_STATE_PENDING: + return "pending"; + case TRANSPORT_STATE_ACTIVE: + case TRANSPORT_STATE_SUSPENDING: + return "active"; + } + + return NULL; +} + +static gboolean state_in_use(transport_state_t state) +{ + switch (state) { + case TRANSPORT_STATE_IDLE: + case TRANSPORT_STATE_PENDING: + return FALSE; + case TRANSPORT_STATE_REQUESTING: + case TRANSPORT_STATE_ACTIVE: + case TRANSPORT_STATE_SUSPENDING: + return TRUE; + } + + return FALSE; +} + +static void transport_set_state(struct media_transport *transport, + transport_state_t state) +{ + transport_state_t old_state = transport->state; + const char *str; + + if (old_state == state) + return; + + transport->state = state; + + DBG("State changed %s: %s -> %s", transport->path, str_state[old_state], + str_state[state]); + + str = state2str(state); + + if (g_strcmp0(str, state2str(old_state)) != 0) + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, + MEDIA_TRANSPORT_INTERFACE, + "State"); +} + +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); + + g_free(path); +} + +static struct media_request *media_request_create(DBusMessage *msg, guint id) +{ + struct media_request *req; + + req = g_new0(struct media_request, 1); + req->msg = dbus_message_ref(msg); + req->id = id; + + DBG("Request created: method=%s id=%u", dbus_message_get_member(msg), + id); + + return req; +} + +static void media_request_reply(struct media_request *req, int err) +{ + DBusMessage *reply; + + DBG("Request %s Reply %s", dbus_message_get_member(req->msg), + strerror(err)); + + if (!err) + reply = g_dbus_create_reply(req->msg, DBUS_TYPE_INVALID); + else + reply = g_dbus_create_error(req->msg, + ERROR_INTERFACE ".Failed", + "%s", strerror(err)); + + g_dbus_send_message(btd_get_dbus_connection(), reply); +} + +static void media_owner_remove(struct media_owner *owner) +{ + struct media_transport *transport = owner->transport; + struct media_request *req = owner->pending; + + if (!req) + return; + + DBG("Owner %s Request %s", owner->name, + dbus_message_get_member(req->msg)); + + if (req->id) + transport->cancel(transport, req->id); + + owner->pending = NULL; + if (req->msg) + dbus_message_unref(req->msg); + + g_free(req); +} + +static void media_owner_free(struct media_owner *owner) +{ + DBG("Owner %s", owner->name); + + media_owner_remove(owner); + + g_free(owner->name); + g_free(owner); +} + +static void media_transport_remove_owner(struct media_transport *transport) +{ + struct media_owner *owner = transport->owner; + + DBG("Transport %s Owner %s", transport->path, owner->name); + + /* Reply if owner has a pending request */ + if (owner->pending) + media_request_reply(owner->pending, EIO); + + transport->owner = NULL; + + if (owner->watch) + g_dbus_remove_watch(btd_get_dbus_connection(), owner->watch); + + media_owner_free(owner); + + if (state_in_use(transport->state)) + transport->suspend(transport, NULL); +} + +static gboolean media_transport_set_fd(struct media_transport *transport, + int fd, uint16_t imtu, uint16_t omtu) +{ + if (transport->fd == fd) + return TRUE; + + transport->fd = fd; + transport->imtu = imtu; + transport->omtu = omtu; + + info("%s: fd(%d) ready", transport->path, fd); + + return TRUE; +} + +static void a2dp_resume_complete(struct avdtp *session, + struct avdtp_error *err, void *user_data) +{ + struct media_owner *owner = user_data; + struct media_request *req = owner->pending; + struct media_transport *transport = owner->transport; + struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint); + struct avdtp_stream *stream; + int fd; + uint16_t imtu, omtu; + gboolean ret; + + req->id = 0; + + if (err) + goto fail; + + stream = a2dp_sep_get_stream(sep); + if (stream == NULL) + goto fail; + + ret = avdtp_stream_get_transport(stream, &fd, &imtu, &omtu, NULL); + 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); + + return; + +fail: + media_transport_remove_owner(transport); +} + +static guint resume_a2dp(struct media_transport *transport, + struct media_owner *owner) +{ + struct a2dp_transport *a2dp = transport->data; + struct media_endpoint *endpoint = transport->endpoint; + struct a2dp_sep *sep = media_endpoint_get_sep(endpoint); + guint id; + + if (a2dp->session == NULL) { + a2dp->session = avdtp_get(transport->device); + if (a2dp->session == NULL) + return 0; + } + + if (state_in_use(transport->state)) + return a2dp_resume(a2dp->session, sep, a2dp_resume_complete, + owner); + + if (a2dp_sep_lock(sep, a2dp->session) == FALSE) + return 0; + + id = a2dp_resume(a2dp->session, sep, a2dp_resume_complete, owner); + + if (id == 0) { + a2dp_sep_unlock(sep, a2dp->session); + return 0; + } + + if (transport->state == TRANSPORT_STATE_IDLE) + transport_set_state(transport, TRANSPORT_STATE_REQUESTING); + + return id; +} + +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); + + /* Release always succeeds */ + if (owner->pending) { + owner->pending->id = 0; + media_request_reply(owner->pending, 0); + media_owner_remove(owner); + } + + a2dp_sep_unlock(sep, a2dp->session); + transport_set_state(transport, TRANSPORT_STATE_IDLE); + media_transport_remove_owner(transport); +} + +static guint suspend_a2dp(struct media_transport *transport, + struct media_owner *owner) +{ + struct a2dp_transport *a2dp = transport->data; + struct media_endpoint *endpoint = transport->endpoint; + struct a2dp_sep *sep = media_endpoint_get_sep(endpoint); + + if (owner != NULL) + return a2dp_suspend(a2dp->session, sep, a2dp_suspend_complete, + owner); + + transport_set_state(transport, TRANSPORT_STATE_IDLE); + a2dp_sep_unlock(sep, a2dp->session); + + return 0; +} + +static void cancel_a2dp(struct media_transport *transport, guint id) +{ + a2dp_cancel(id); +} + +static void media_owner_exit(DBusConnection *connection, void *user_data) +{ + struct media_owner *owner = user_data; + + owner->watch = 0; + + media_owner_remove(owner); + + media_transport_remove_owner(owner->transport); +} + +static void media_transport_set_owner(struct media_transport *transport, + struct media_owner *owner) +{ + DBG("Transport %s Owner %s", transport->path, owner->name); + transport->owner = owner; + owner->transport = transport; + owner->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(), + owner->name, + media_owner_exit, + owner, NULL); +} + +static struct media_owner *media_owner_create(DBusMessage *msg) +{ + struct media_owner *owner; + + owner = g_new0(struct media_owner, 1); + owner->name = g_strdup(dbus_message_get_sender(msg)); + + DBG("Owner created: sender=%s", owner->name); + + return owner; +} + +static void media_owner_add(struct media_owner *owner, + struct media_request *req) +{ + DBG("Owner %s Request %s", owner->name, + dbus_message_get_member(req->msg)); + + owner->pending = req; +} + +static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + struct media_owner *owner; + struct media_request *req; + guint id; + + if (transport->owner != NULL) + return btd_error_not_authorized(msg); + + if (transport->state >= TRANSPORT_STATE_REQUESTING) + return btd_error_not_authorized(msg); + + owner = media_owner_create(msg); + id = transport->resume(transport, owner); + if (id == 0) { + media_owner_free(owner); + return btd_error_not_authorized(msg); + } + + req = media_request_create(msg, id); + media_owner_add(owner, req); + media_transport_set_owner(transport, owner); + + return NULL; +} + +static DBusMessage *try_acquire(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + struct media_owner *owner; + struct media_request *req; + guint id; + + if (transport->owner != NULL) + return btd_error_not_authorized(msg); + + if (transport->state >= TRANSPORT_STATE_REQUESTING) + return btd_error_not_authorized(msg); + + if (transport->state != TRANSPORT_STATE_PENDING) + return btd_error_not_available(msg); + + owner = media_owner_create(msg); + id = transport->resume(transport, owner); + if (id == 0) { + media_owner_free(owner); + return btd_error_not_authorized(msg); + } + + req = media_request_create(msg, id); + media_owner_add(owner, req); + media_transport_set_owner(transport, owner); + + return NULL; +} + +static DBusMessage *release(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + struct media_owner *owner = transport->owner; + const char *sender; + struct media_request *req; + guint id; + + sender = dbus_message_get_sender(msg); + + if (owner == NULL || g_strcmp0(owner->name, sender) != 0) + return btd_error_not_authorized(msg); + + if (owner->pending) { + const char *member; + + member = dbus_message_get_member(owner->pending->msg); + /* Cancel Acquire request if that exist */ + if (g_str_equal(member, "Acquire")) + media_owner_remove(owner); + else + return btd_error_in_progress(msg); + } + + transport_set_state(transport, TRANSPORT_STATE_SUSPENDING); + + id = transport->suspend(transport, owner); + if (id == 0) { + media_transport_remove_owner(transport); + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + } + + req = media_request_create(msg, id); + media_owner_add(owner, req); + + return NULL; +} + +static gboolean get_device(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + const char *path = device_get_path(transport->device); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + + return TRUE; +} + +static gboolean get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + const char *uuid = media_endpoint_get_uuid(transport->endpoint); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); + + return TRUE; +} + +static gboolean get_codec(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + uint8_t codec = media_endpoint_get_codec(transport->endpoint); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &codec); + + return TRUE; +} + +static gboolean get_configuration(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, &array); + + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &transport->configuration, + transport->size); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean get_state(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + const char *state = state2str(transport->state); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &state); + + return TRUE; +} + +static gboolean delay_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_transport *transport = data; + struct a2dp_transport *a2dp = transport->data; + + return a2dp->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); + + return TRUE; +} + +static gboolean volume_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_transport *transport = data; + struct a2dp_transport *a2dp = transport->data; + + return a2dp->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); + + return TRUE; +} + +static void set_volume(const GDBusPropertyTable *property, + DBusMessageIter *iter, GDBusPendingPropertySet id, + 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) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(iter, &volume); + + if (volume > 127) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + if (a2dp->volume != volume) + avrcp_set_volume(transport->device, volume); + + a2dp->volume = volume; + + g_dbus_pending_property_success(id); +} + +static const GDBusMethodTable transport_methods[] = { + { GDBUS_ASYNC_METHOD("Acquire", + NULL, + GDBUS_ARGS({ "fd", "h" }, { "mtu_r", "q" }, + { "mtu_w", "q" }), + acquire) }, + { GDBUS_ASYNC_METHOD("TryAcquire", + NULL, + GDBUS_ARGS({ "fd", "h" }, { "mtu_r", "q" }, + { "mtu_w", "q" }), + try_acquire) }, + { GDBUS_ASYNC_METHOD("Release", NULL, NULL, release) }, + { }, +}; + +static const GDBusPropertyTable transport_properties[] = { + { "Device", "o", get_device }, + { "UUID", "s", get_uuid }, + { "Codec", "y", get_codec }, + { "Configuration", "ay", get_configuration }, + { "State", "s", get_state }, + { "Delay", "q", get_delay, NULL, delay_exists }, + { "Volume", "q", get_volume, set_volume, volume_exists }, + { } +}; + +static void destroy_a2dp(void *data) +{ + struct a2dp_transport *a2dp = data; + + if (a2dp->session) + avdtp_unref(a2dp->session); + + g_free(a2dp); +} + +static void media_transport_free(void *data) +{ + struct media_transport *transport = data; + + transports = g_slist_remove(transports, transport); + + if (transport->owner) + media_transport_remove_owner(transport); + + if (transport->destroy != NULL) + transport->destroy(transport->data); + + g_free(transport->configuration); + g_free(transport->path); + g_free(transport); +} + +static void transport_update_playing(struct media_transport *transport, + gboolean playing) +{ + DBG("%s State=%s Playing=%d", transport->path, + str_state[transport->state], playing); + + if (playing == FALSE) { + if (transport->state == TRANSPORT_STATE_PENDING) + transport_set_state(transport, TRANSPORT_STATE_IDLE); + else if (transport->state == TRANSPORT_STATE_ACTIVE) { + /* Remove owner */ + if (transport->owner != NULL) + media_transport_remove_owner(transport); + } + } else if (transport->state == TRANSPORT_STATE_IDLE) + transport_set_state(transport, TRANSPORT_STATE_PENDING); +} + +static void sink_state_changed(struct btd_service *service, + sink_state_t old_state, + sink_state_t new_state, + void *user_data) +{ + struct media_transport *transport = user_data; + + if (new_state == SINK_STATE_PLAYING) + transport_update_playing(transport, TRUE); + else + transport_update_playing(transport, FALSE); +} + +static void source_state_changed(struct btd_service *service, + source_state_t old_state, + source_state_t new_state, + void *user_data) +{ + struct media_transport *transport = user_data; + + if (new_state == SOURCE_STATE_PLAYING) + transport_update_playing(transport, TRUE); + else + transport_update_playing(transport, FALSE); +} + +static int media_transport_init_source(struct media_transport *transport) +{ + struct btd_service *service; + struct a2dp_transport *a2dp; + + service = btd_device_get_service(transport->device, A2DP_SINK_UUID); + if (service == NULL) + return -EINVAL; + + 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; + + a2dp->volume = -1; + transport->sink_watch = sink_add_state_cb(service, sink_state_changed, + transport); + + return 0; +} + +static int media_transport_init_sink(struct media_transport *transport) +{ + struct btd_service *service; + struct a2dp_transport *a2dp; + + service = btd_device_get_service(transport->device, A2DP_SOURCE_UUID); + if (service == NULL) + return -EINVAL; + + 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; + + a2dp->volume = 127; + avrcp_set_volume(transport->device, a2dp->volume); + transport->source_watch = source_add_state_cb(service, + source_state_changed, + transport); + + return 0; +} + +struct media_transport *media_transport_create(struct btd_device *device, + uint8_t *configuration, + size_t size, void *data) +{ + struct media_endpoint *endpoint = data; + struct media_transport *transport; + const char *uuid; + static int fd = 0; + + transport = g_new0(struct media_transport, 1); + 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), + fd++); + transport->fd = -1; + + uuid = media_endpoint_get_uuid(endpoint); + if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) { + if (media_transport_init_source(transport) < 0) + goto fail; + } else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) { + if (media_transport_init_sink(transport) < 0) + goto fail; + } else + goto fail; + + if (g_dbus_register_interface(btd_get_dbus_connection(), + transport->path, MEDIA_TRANSPORT_INTERFACE, + transport_methods, NULL, transport_properties, + transport, media_transport_free) == FALSE) { + error("Could not register transport %s", transport->path); + goto fail; + } + + transports = g_slist_append(transports, transport); + + return transport; + +fail: + media_transport_free(transport); + return NULL; +} + +const char *media_transport_get_path(struct media_transport *transport) +{ + return transport->path; +} + +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) + return; + + a2dp->delay = delay; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, + MEDIA_TRANSPORT_INTERFACE, "Delay"); +} + +struct btd_device *media_transport_get_dev(struct media_transport *transport) +{ + return transport->device; +} + +uint16_t media_transport_get_volume(struct media_transport *transport) +{ + struct a2dp_transport *a2dp = transport->data; + return a2dp->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) + return; + + a2dp->volume = volume; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, + MEDIA_TRANSPORT_INTERFACE, "Volume"); +} + +uint8_t media_transport_get_device_volume(struct btd_device *dev) +{ + GSList *l; + + if (dev == NULL) + return 128; + + for (l = transports; l; l = l->next) { + struct media_transport *transport = l->data; + if (transport->device != dev) + continue; + + /* Volume is A2DP only */ + if (media_endpoint_get_sep(transport->endpoint)) + return media_transport_get_volume(transport); + } + + return 0; +} + +void media_transport_update_device_volume(struct btd_device *dev, + uint8_t volume) +{ + GSList *l; + + if (dev == NULL) + return; + + for (l = transports; l; l = l->next) { + struct media_transport *transport = l->data; + if (transport->device != dev) + continue; + + /* Volume is A2DP only */ + if (media_endpoint_get_sep(transport->endpoint)) + media_transport_update_volume(transport, volume); + } +} |