diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index 0c60f06..64b6ec6 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
struct avdtp *session;
uint16_t delay;
int8_t volume;
+ guint watch;
};
struct bap_transport {
guint resume_id;
};
+struct media_transport_ops {
+ const char *uuid;
+ const GDBusPropertyTable *properties;
+ void *(*init)(struct media_transport *transport, void *stream);
+ 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);
+ void (*set_state)(struct media_transport *transport,
+ transport_state_t state);
+ void *(*get_stream)(struct media_transport *transport);
+ int8_t (*get_volume)(struct media_transport *transport);
+ int (*set_volume)(struct media_transport *transport, int8_t level);
+ GDestroyNotify destroy;
+};
+
struct media_transport {
char *path; /* Transport object path */
struct btd_device *device; /* Transport device */
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);
- void (*set_state) (struct media_transport *transport,
- transport_state_t state);
- void *(*get_stream)
- (struct media_transport *transport);
- GDestroyNotify destroy;
+ struct media_transport_ops *ops;
void *data;
};
"State");
/* Update transport specific data */
- if (transport->set_state)
- transport->set_state(transport, state);
+ if (transport->ops && transport->ops->set_state)
+ transport->ops->set_state(transport, 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);
DBG("Owner %s Request %s", owner->name,
dbus_message_get_member(req->msg));
- if (req->id)
- transport->cancel(transport, req->id);
+ if (req->id && transport->ops && transport->ops->cancel)
+ transport->ops->cancel(transport, req->id);
owner->pending = NULL;
if (req->msg)
transport->owner = NULL;
}
+static guint media_transport_suspend(struct media_transport *transport,
+ struct media_owner *owner)
+{
+ if (!state_in_use(transport->state))
+ return 0;
+
+ DBG("Transport %s Owner %s", transport->path, owner ? owner->name : "");
+
+ if (transport->ops && transport->ops->suspend)
+ return transport->ops->suspend(transport, owner);
+
+ return 0;
+}
+
static void media_transport_remove_owner(struct media_transport *transport)
{
struct media_owner *owner = transport->owner;
media_request_reply(owner->pending, EIO);
transport->owner = NULL;
- if (bap->linked) {
+ if (bap && bap->linked) {
struct bt_bap_stream *link;
link = bt_bap_stream_io_get_link(bap->stream);
media_owner_free(owner);
- if (state_in_use(transport->state))
- transport->suspend(transport, NULL);
+ media_transport_suspend(transport, NULL);
}
static gboolean media_transport_set_fd(struct media_transport *transport,
media_transport_remove_owner(transport);
}
-static guint resume_a2dp(struct media_transport *transport,
+static guint transport_a2dp_resume(struct media_transport *transport,
struct media_owner *owner)
{
struct a2dp_transport *a2dp = transport->data;
media_transport_remove_owner(transport);
}
-static guint suspend_a2dp(struct media_transport *transport,
+static guint transport_a2dp_suspend(struct media_transport *transport,
struct media_owner *owner)
{
struct a2dp_transport *a2dp = transport->data;
return 0;
}
-static void cancel_a2dp(struct media_transport *transport, guint id)
+static void transport_a2dp_cancel(struct media_transport *transport, guint id)
{
a2dp_cancel(id);
}
+static int8_t transport_a2dp_get_volume(struct media_transport *transport)
+{
+ struct a2dp_transport *a2dp = transport->data;
+ return a2dp->volume;
+}
+
+static int transport_a2dp_src_set_volume(struct media_transport *transport,
+ int8_t level)
+{
+ struct a2dp_transport *a2dp = transport->data;
+
+ if (a2dp->volume == level)
+ return 0;
+
+ return avrcp_set_volume(transport->device, level, false);
+}
+
+static int transport_a2dp_snk_set_volume(struct media_transport *transport,
+ int8_t level)
+{
+ struct a2dp_transport *a2dp = transport->data;
+ bool notify;
+
+ if (a2dp->volume == level)
+ return 0;
+
+ notify = a2dp->watch ? true : false;
+ if (notify) {
+ a2dp->volume = level;
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ transport->path,
+ MEDIA_TRANSPORT_INTERFACE,
+ "Volume");
+ }
+
+ return avrcp_set_volume(transport->device, level, notify);
+}
+
static void media_owner_exit(DBusConnection *connection, void *user_data)
{
struct media_owner *owner = user_data;
owner->pending = req;
}
-static void *get_stream_bap(struct media_transport *transport)
+static void *transport_bap_get_stream(struct media_transport *transport)
{
struct bap_transport *bap = transport->data;
return bap->stream;
}
+static guint media_transport_resume(struct media_transport *transport,
+ struct media_owner *owner)
+{
+ DBG("Transport %s Owner %s", transport->path, owner ? owner->name : "");
+
+ if (transport->ops && transport->ops->resume)
+ return transport->ops->resume(transport, owner);
+
+ return 0;
+}
+
static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg,
void *data)
{
media_transport_set_owner(transport, owner);
}
- id = transport->resume(transport, owner);
+ id = media_transport_resume(transport, owner);
if (id == 0) {
media_owner_free(owner);
return btd_error_not_authorized(msg);
return btd_error_not_available(msg);
owner = media_owner_create(msg);
- id = transport->resume(transport, owner);
+ id = media_transport_resume(transport, owner);
if (id == 0) {
media_owner_free(owner);
return btd_error_not_authorized(msg);
void *user_data)
{
struct media_owner *owner = user_data;
- struct media_request *req = owner->pending;
- struct media_transport *transport = owner->transport;
+ struct media_request *req;
+ struct media_transport *transport;
+
+ if (!owner)
+ return;
+
+ req = owner->pending;
/* Release always succeeds */
if (req) {
media_owner_remove(owner);
}
- transport_set_state(transport, TRANSPORT_STATE_IDLE);
- media_transport_remove_owner(transport);
+ transport = owner->transport;
+
+ if (transport) {
+ transport_set_state(transport, TRANSPORT_STATE_IDLE);
+ media_transport_remove_owner(transport);
+ }
}
static void bap_disable_complete(struct bt_bap_stream *stream,
transport_set_state(transport, TRANSPORT_STATE_SUSPENDING);
- id = transport->suspend(transport, owner);
+ id = media_transport_suspend(transport, owner);
if (id == 0) {
media_transport_remove_owner(transport);
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
static gboolean volume_exists(const GDBusPropertyTable *property, void *data)
{
struct media_transport *transport = data;
- struct a2dp_transport *a2dp = transport->data;
+ int8_t volume;
- return a2dp->volume >= 0;
+ if (media_transport_get_volume(transport, &volume))
+ return FALSE;
+
+ return volume >= 0;
+}
+
+int media_transport_get_volume(struct media_transport *transport,
+ int8_t *volume)
+{
+ if (transport->ops && transport->ops->get_volume) {
+ *volume = transport->ops->get_volume(transport);
+ return 0;
+ }
+
+ return -EINVAL;
}
static gboolean get_volume(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct media_transport *transport = data;
- struct a2dp_transport *a2dp = transport->data;
- uint16_t volume = (uint16_t)a2dp->volume;
+ int8_t level;
+ uint16_t volume;
+
+ if (media_transport_get_volume(transport, &level))
+ return FALSE;
+
+ volume = level;
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &volume);
return TRUE;
}
+static int media_transport_set_volume(struct media_transport *transport,
+ int8_t level)
+{
+ DBG("Transport %s level %d", transport->path, level);
+
+ if (transport->ops && transport->ops->set_volume)
+ return transport->ops->set_volume(transport, level);
+
+ return 0;
+}
+
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 arg;
- int8_t volume;
- bool notify;
int err;
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) {
return;
}
- volume = (int8_t)arg;
- if (a2dp->volume == volume)
- return;
-
- notify = transport->source_watch ? true : false;
- if (notify) {
- a2dp->volume = volume;
- g_dbus_emit_property_changed(btd_get_dbus_connection(),
- transport->path,
- MEDIA_TRANSPORT_INTERFACE,
- "Volume");
- }
-
- err = avrcp_set_volume(transport->device, volume, notify);
+ err = media_transport_set_volume(transport, arg);
if (err) {
- error("avrcp_set_volume returned %s (%d)", strerror(-err), err);
+ error("Unable to set volume: %s (%d)", strerror(-err), err);
g_dbus_pending_property_error(id,
- ERROR_INTERFACE ".Failed",
- "Internal error %s (%d)",
- strerror(-err), err);
+ ERROR_INTERFACE ".Failed",
+ "Internal error %s (%d)",
+ strerror(-err), err);
return;
}
{ },
};
-static const GDBusPropertyTable a2dp_properties[] = {
+static const GDBusPropertyTable transport_a2dp_properties[] = {
{ "Device", "o", get_device },
{ "UUID", "s", get_uuid },
{ "Codec", "y", get_codec },
return bap->qos.ucast.io_qos.phy != 0x00;
}
-static const GDBusPropertyTable bap_ucast_properties[] = {
+static const GDBusPropertyTable transport_bap_uc_properties[] = {
{ "Device", "o", get_device },
{ "UUID", "s", get_uuid },
{ "Codec", "y", get_codec },
return bap->qos.bcast.io_qos.phy != 0x00;
}
-static const GDBusPropertyTable bap_bcast_properties[] = {
+static const GDBusPropertyTable transport_bap_bc_properties[] = {
{ "Device", "o", get_device },
{ "UUID", "s", get_uuid },
{ "Codec", "y", get_codec },
{ }
};
-static void destroy_a2dp(void *data)
+static void transport_a2dp_destroy(void *data)
{
struct a2dp_transport *a2dp = data;
if (a2dp->session)
avdtp_unref(a2dp->session);
- g_free(a2dp);
+ free(a2dp);
+}
+
+static void transport_a2dp_src_destroy(void *data)
+{
+ struct a2dp_transport *a2dp = data;
+
+ if (a2dp->watch)
+ sink_remove_state_cb(a2dp->watch);
+
+ transport_a2dp_destroy(data);
+}
+
+static void transport_a2dp_snk_destroy(void *data)
+{
+ struct a2dp_transport *a2dp = data;
+
+ if (a2dp->watch)
+ source_remove_state_cb(a2dp->watch);
+
+ transport_a2dp_destroy(data);
}
static void media_transport_free(void *data)
if (transport->owner)
media_transport_remove_owner(transport);
- if (transport->destroy != NULL)
- transport->destroy(transport->data);
+ if (transport->ops && transport->ops->destroy)
+ transport->ops->destroy(transport->data);
g_free(transport->configuration);
g_free(transport->path);
transport_update_playing(transport, FALSE);
}
-static int media_transport_init_source(struct media_transport *transport)
+static void *transport_a2dp_src_init(struct media_transport *transport,
+ void *stream)
{
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;
+ if (!service)
+ return NULL;
+ a2dp = new0(struct a2dp_transport, 1);
a2dp->volume = -1;
- transport->sink_watch = sink_add_state_cb(service, sink_state_changed,
- transport);
+ a2dp->watch = sink_add_state_cb(service, sink_state_changed, transport);
- return 0;
+ return a2dp;
}
-static int media_transport_init_sink(struct media_transport *transport)
+static void *transport_a2dp_snk_init(struct media_transport *transport,
+ void *stream)
{
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;
+ if (!service)
+ return NULL;
+ a2dp = new0(struct a2dp_transport, 1);
a2dp->volume = 127;
- transport->source_watch = source_add_state_cb(service,
- source_state_changed,
- transport);
+ a2dp->watch = source_add_state_cb(service, source_state_changed,
+ transport);
- return 0;
+ return a2dp;
}
static void bap_enable_complete(struct bt_bap_stream *stream,
"Configuration");
}
-static guint resume_bap(struct media_transport *transport,
+static guint transport_bap_resume(struct media_transport *transport,
struct media_owner *owner)
{
struct bap_transport *bap = transport->data;
return id;
}
-static guint suspend_bap(struct media_transport *transport,
+static guint transport_bap_suspend(struct media_transport *transport,
struct media_owner *owner)
{
struct bap_transport *bap = transport->data;
return id;
}
-static void cancel_bap(struct media_transport *transport, guint id)
+static void transport_bap_cancel(struct media_transport *transport, guint id)
{
struct bap_transport *bap = transport->data;
transport_set_state(transport, state);
}
-static void set_state_bap(struct media_transport *transport,
+static void transport_bap_set_state(struct media_transport *transport,
transport_state_t state)
{
struct bap_transport *bap = transport->data;
bap_update_links(transport);
}
-static void free_bap(void *data)
+static void transport_bap_destroy(void *data)
{
struct bap_transport *bap = data;
free(bap);
}
-static int media_transport_init_bap(struct media_transport *transport,
- void *stream)
+static void *transport_bap_init(struct media_transport *transport, void *stream)
{
struct bt_bap_qos *qos;
struct bap_transport *bap;
bap_connecting,
transport, NULL);
- transport->data = bap;
- transport->resume = resume_bap;
- transport->suspend = suspend_bap;
- transport->cancel = cancel_bap;
- transport->set_state = set_state_bap;
- transport->get_stream = get_stream_bap;
- transport->destroy = free_bap;
+ return bap;
+}
+
+#define TRANSPORT_OPS(_uuid, _props, _init, _resume, _suspend, _cancel, \
+ _set_state, _get_stream, _get_volume, _set_volume, \
+ _destroy) \
+{ \
+ .uuid = _uuid, \
+ .properties = _props, \
+ .init = _init, \
+ .resume = _resume, \
+ .suspend = _suspend, \
+ .cancel = _cancel, \
+ .set_state = _set_state, \
+ .get_stream = _get_stream, \
+ .get_volume = _get_volume, \
+ .set_volume = _set_volume, \
+ .destroy = _destroy \
+}
+
+#define A2DP_OPS(_uuid, _init, _set_volume, _destroy) \
+ TRANSPORT_OPS(_uuid, transport_a2dp_properties, _init, \
+ transport_a2dp_resume, transport_a2dp_suspend, \
+ transport_a2dp_cancel, NULL, NULL, \
+ transport_a2dp_get_volume, _set_volume, \
+ _destroy)
+
+#define BAP_OPS(_uuid, _props) \
+ TRANSPORT_OPS(_uuid, _props, transport_bap_init, \
+ transport_bap_resume, transport_bap_suspend, \
+ transport_bap_cancel, transport_bap_set_state, \
+ transport_bap_get_stream, NULL, NULL, \
+ transport_bap_destroy)
+
+#define BAP_UC_OPS(_uuid) \
+ BAP_OPS(_uuid, transport_bap_uc_properties)
+
+#define BAP_BC_OPS(_uuid) \
+ BAP_OPS(_uuid, transport_bap_bc_properties)
+
+static struct media_transport_ops transport_ops[] = {
+ A2DP_OPS(A2DP_SOURCE_UUID, transport_a2dp_src_init,
+ transport_a2dp_src_set_volume,
+ transport_a2dp_src_destroy),
+ A2DP_OPS(A2DP_SINK_UUID, transport_a2dp_snk_init,
+ transport_a2dp_snk_set_volume,
+ transport_a2dp_snk_destroy),
+ BAP_UC_OPS(PAC_SOURCE_UUID),
+ BAP_UC_OPS(PAC_SINK_UUID),
+ BAP_BC_OPS(BCAA_SERVICE_UUID),
+ BAP_BC_OPS(BAA_SERVICE_UUID),
+};
- return 0;
+static struct media_transport_ops *media_transport_find_ops(const char *uuid)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(transport_ops); i++) {
+ struct media_transport_ops *ops = &transport_ops[i];
+
+ if (!strcasecmp(uuid, ops->uuid))
+ return ops;
+ }
+
+ return NULL;
}
struct media_transport *media_transport_create(struct btd_device *device,
{
struct media_endpoint *endpoint = data;
struct media_transport *transport;
- const char *uuid;
+ struct media_transport_ops *ops;
static int fd = 0;
- const GDBusPropertyTable *properties;
transport = g_new0(struct media_transport, 1);
if (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;
- properties = a2dp_properties;
- } else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) {
- if (media_transport_init_sink(transport) < 0)
- goto fail;
- properties = a2dp_properties;
- } else if (!strcasecmp(uuid, PAC_SINK_UUID) ||
- !strcasecmp(uuid, PAC_SOURCE_UUID)) {
- if (media_transport_init_bap(transport, stream) < 0)
- goto fail;
- properties = bap_ucast_properties;
- } else if (!strcasecmp(uuid, BCAA_SERVICE_UUID) ||
- !strcasecmp(uuid, BAA_SERVICE_UUID)) {
- if (media_transport_init_bap(transport, stream) < 0)
- goto fail;
- properties = bap_bcast_properties;
- } else
+ ops = media_transport_find_ops(media_endpoint_get_uuid(endpoint));
+ if (!ops)
goto fail;
+ transport->ops = ops;
+
+ if (ops->init) {
+ transport->data = ops->init(transport, stream);
+ if (!transport->data)
+ goto fail;
+ }
+
if (g_dbus_register_interface(btd_get_dbus_connection(),
transport->path, MEDIA_TRANSPORT_INTERFACE,
- transport_methods, NULL, properties,
+ transport_methods, NULL, ops->properties,
transport, media_transport_free) == FALSE) {
error("Could not register transport %s", transport->path);
goto fail;
void *media_transport_get_stream(struct media_transport *transport)
{
- if (transport->get_stream)
- return transport->get_stream(transport);
+ if (transport->ops && transport->ops->get_stream)
+ return transport->ops->get_stream(transport);
return NULL;
}
return transport->device;
}
-int8_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,
int8_t volume)
{
continue;
/* Volume is A2DP only */
- if (media_endpoint_get_sep(transport->endpoint))
- return media_transport_get_volume(transport);
+ if (media_endpoint_get_sep(transport->endpoint)) {
+ int8_t volume;
+
+ if (!media_transport_get_volume(transport, &volume))
+ return volume;
+
+ return -1;
+ }
}
/* If transport volume doesn't exists use device_volume */
diff --git a/profiles/audio/transport.h b/profiles/audio/transport.h
index 5ca9b8f..b46bc80 100644
--- a/profiles/audio/transport.h
+++ b/profiles/audio/transport.h
const char *media_transport_get_path(struct media_transport *transport);
void *media_transport_get_stream(struct media_transport *transport);
struct btd_device *media_transport_get_dev(struct media_transport *transport);
-int8_t media_transport_get_volume(struct media_transport *transport);
+int media_transport_get_volume(struct media_transport *transport,
+ int8_t *volume);
void media_transport_update_delay(struct media_transport *transport,
uint16_t delay);
void media_transport_update_volume(struct media_transport *transport,