* [PATCH 04/10] Add support for media transport in gstreamer plugin
2010-09-13 14:15 [PATCH 00/10] Media API Luiz Augusto von Dentz
` (2 preceding siblings ...)
2010-09-13 14:15 ` [PATCH 03/10] Add option to enable/disable unix ipc via audio.conf Luiz Augusto von Dentz
@ 2010-09-13 14:15 ` Luiz Augusto von Dentz
2010-09-13 14:15 ` [PATCH 05/10] Add simple-endpoint test script Luiz Augusto von Dentz
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Luiz Augusto von Dentz @ 2010-09-13 14:15 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.dentz-von@nokia.com>
---
Makefile.am | 4 +-
audio/a2dp-codecs.h | 116 ++++++++++
audio/gsta2dpsink.c | 33 +++-
audio/gsta2dpsink.h | 1 +
audio/gstavdtpsink.c | 609 +++++++++++++++++++++++++++++++++++++++++++++++++-
audio/gstavdtpsink.h | 6 +
6 files changed, 757 insertions(+), 12 deletions(-)
create mode 100644 audio/a2dp-codecs.h
diff --git a/Makefile.am b/Makefile.am
index a8829d9..46f5449 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -307,9 +307,9 @@ audio_libgstbluetooth_la_SOURCES = audio/gstbluetooth.c audio/gstpragma.h \
audio/rtp.h audio/ipc.h audio/ipc.c
audio_libgstbluetooth_la_LDFLAGS = -module -avoid-version
audio_libgstbluetooth_la_LIBADD = sbc/libsbc.la lib/libbluetooth.la \
- @GSTREAMER_LIBS@ -lgstaudio-0.10 -lgstrtp-0.10
+ @DBUS_LIBS@ @GSTREAMER_LIBS@ -lgstaudio-0.10 -lgstrtp-0.10
audio_libgstbluetooth_la_CFLAGS = -fvisibility=hidden -fno-strict-aliasing \
- $(AM_CFLAGS) @GSTREAMER_CFLAGS@
+ $(AM_CFLAGS) @DBUS_CFLAGS@ @GSTREAMER_CFLAGS@
endif
endif
diff --git a/audio/a2dp-codecs.h b/audio/a2dp-codecs.h
new file mode 100644
index 0000000..e44634e
--- /dev/null
+++ b/audio/a2dp-codecs.h
@@ -0,0 +1,116 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 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
+ *
+ */
+
+#define A2DP_CODEC_SBC 0x00
+#define A2DP_CODEC_MPEG12 0x01
+#define A2DP_CODEC_MPEG24 0x02
+#define A2DP_CODEC_ATRAC 0x03
+
+#define SBC_SAMPLING_FREQ_16000 (1 << 3)
+#define SBC_SAMPLING_FREQ_32000 (1 << 2)
+#define SBC_SAMPLING_FREQ_44100 (1 << 1)
+#define SBC_SAMPLING_FREQ_48000 1
+
+#define SBC_CHANNEL_MODE_MONO (1 << 3)
+#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2)
+#define SBC_CHANNEL_MODE_STEREO (1 << 1)
+#define SBC_CHANNEL_MODE_JOINT_STEREO 1
+
+#define SBC_BLOCK_LENGTH_4 (1 << 3)
+#define SBC_BLOCK_LENGTH_8 (1 << 2)
+#define SBC_BLOCK_LENGTH_12 (1 << 1)
+#define SBC_BLOCK_LENGTH_16 1
+
+#define SBC_SUBBANDS_4 (1 << 1)
+#define SBC_SUBBANDS_8 1
+
+#define SBC_ALLOCATION_SNR (1 << 1)
+#define SBC_ALLOCATION_LOUDNESS 1
+
+#define MPEG_CHANNEL_MODE_MONO (1 << 3)
+#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2)
+#define MPEG_CHANNEL_MODE_STEREO (1 << 1)
+#define MPEG_CHANNEL_MODE_JOINT_STEREO 1
+
+#define MPEG_LAYER_MP1 (1 << 2)
+#define MPEG_LAYER_MP2 (1 << 1)
+#define MPEG_LAYER_MP3 1
+
+#define MPEG_SAMPLING_FREQ_16000 (1 << 5)
+#define MPEG_SAMPLING_FREQ_22050 (1 << 4)
+#define MPEG_SAMPLING_FREQ_24000 (1 << 3)
+#define MPEG_SAMPLING_FREQ_32000 (1 << 2)
+#define MPEG_SAMPLING_FREQ_44100 (1 << 1)
+#define MPEG_SAMPLING_FREQ_48000 1
+
+#define MAX_BITPOOL 64
+#define MIN_BITPOOL 2
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+typedef struct {
+ uint8_t channel_mode:4;
+ uint8_t frequency:4;
+ uint8_t allocation_method:2;
+ uint8_t subbands:2;
+ uint8_t block_length:4;
+ uint8_t min_bitpool;
+ uint8_t max_bitpool;
+} __attribute__ ((packed)) a2dp_sbc_t;
+
+typedef struct {
+ uint8_t channel_mode:4;
+ uint8_t crc:1;
+ uint8_t layer:3;
+ uint8_t frequency:6;
+ uint8_t mpf:1;
+ uint8_t rfa:1;
+ uint16_t bitrate;
+} __attribute__ ((packed)) a2dp_mpeg_t;
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+typedef struct {
+ uint8_t frequency:4;
+ uint8_t channel_mode:4;
+ uint8_t block_length:4;
+ uint8_t subbands:2;
+ uint8_t allocation_method:2;
+ uint8_t min_bitpool;
+ uint8_t max_bitpool;
+} __attribute__ ((packed)) a2dp_sbc_t;
+
+typedef struct {
+ uint8_t layer:3;
+ uint8_t crc:1;
+ uint8_t channel_mode:4;
+ uint8_t rfa:1;
+ uint8_t mpf:1;
+ uint8_t frequency:6;
+ uint16_t bitrate;
+} __attribute__ ((packed)) a2dp_mpeg_t;
+
+#else
+#error "Unknown byte order"
+#endif
diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c
index 492fc63..ac63424 100644
--- a/audio/gsta2dpsink.c
+++ b/audio/gsta2dpsink.c
@@ -44,7 +44,8 @@ GST_DEBUG_CATEGORY_STATIC(gst_a2dp_sink_debug);
enum {
PROP_0,
PROP_DEVICE,
- PROP_AUTOCONNECT
+ PROP_AUTOCONNECT,
+ PROP_TRANSPORT
};
GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBin, GST_TYPE_BIN);
@@ -175,6 +176,16 @@ static void gst_a2dp_sink_set_property(GObject *object, guint prop_id,
self->device = g_value_dup_string(value);
break;
+ case PROP_TRANSPORT:
+ if (self->sink != NULL)
+ gst_avdtp_sink_set_transport(self->sink,
+ g_value_get_string(value));
+
+ if (self->transport != NULL)
+ g_free(self->transport);
+ self->transport = g_value_dup_string(value);
+ break;
+
case PROP_AUTOCONNECT:
self->autoconnect = g_value_get_boolean(value);
@@ -193,7 +204,7 @@ static void gst_a2dp_sink_get_property(GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
GstA2dpSink *self = GST_A2DP_SINK(object);
- gchar *device;
+ gchar *device, *transport;
switch (prop_id) {
case PROP_DEVICE:
@@ -210,6 +221,13 @@ static void gst_a2dp_sink_get_property(GObject *object, guint prop_id,
g_value_set_boolean(value, self->autoconnect);
break;
+ case PROP_TRANSPORT:
+ if (self->sink != NULL) {
+ transport = gst_avdtp_sink_get_transport(self->sink);
+ if (transport != NULL)
+ g_value_take_string(value, transport);
+ }
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
@@ -285,6 +303,10 @@ static GstStateChangeReturn gst_a2dp_sink_change_state(GstElement *element,
gst_avdtp_sink_set_device(self->sink,
self->device);
+ if (self->transport != NULL)
+ gst_avdtp_sink_set_transport(self->sink,
+ self->transport);
+
g_object_set(G_OBJECT(self->sink), "auto-connect",
self->autoconnect, NULL);
@@ -365,6 +387,11 @@ static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass)
"Automatically attempt to connect to device",
DEFAULT_AUTOCONNECT, G_PARAM_READWRITE));
+ g_object_class_install_property(object_class, PROP_TRANSPORT,
+ g_param_spec_string("transport", "Transport",
+ "Use configured transport",
+ NULL, G_PARAM_READWRITE));
+
GST_DEBUG_CATEGORY_INIT(gst_a2dp_sink_debug, "a2dpsink", 0,
"A2DP sink element");
}
@@ -437,6 +464,7 @@ static gboolean gst_a2dp_sink_init_avdtp_sink(GstA2dpSink *self)
self->sink = GST_AVDTP_SINK(sink);
self->sink_is_in_bin = TRUE;
g_object_set(G_OBJECT(self->sink), "device", self->device, NULL);
+ g_object_set(G_OBJECT(self->sink), "transport", self->transport, NULL);
gst_element_set_state(sink, GST_STATE_PAUSED);
@@ -674,6 +702,7 @@ static void gst_a2dp_sink_init(GstA2dpSink *self,
self->fakesink = NULL;
self->rtp = NULL;
self->device = NULL;
+ self->transport = NULL;
self->autoconnect = DEFAULT_AUTOCONNECT;
self->capsfilter = NULL;
self->newseg_event = NULL;
diff --git a/audio/gsta2dpsink.h b/audio/gsta2dpsink.h
index 3ff4e45..3c4510e 100644
--- a/audio/gsta2dpsink.h
+++ b/audio/gsta2dpsink.h
@@ -53,6 +53,7 @@ struct _GstA2dpSink {
GstElement *fakesink;
gchar *device;
+ gchar *transport;
gboolean autoconnect;
gboolean sink_is_in_bin;
diff --git a/audio/gstavdtpsink.c b/audio/gstavdtpsink.c
index b8c8832..95c4811 100644
--- a/audio/gstavdtpsink.c
+++ b/audio/gstavdtpsink.c
@@ -37,8 +37,11 @@
#include <gst/rtp/gstrtpbuffer.h>
+#include <dbus/dbus.h>
+
#include "ipc.h"
#include "rtp.h"
+#include "a2dp-codecs.h"
#include "gstpragma.h"
#include "gstavdtpsink.h"
@@ -61,11 +64,20 @@ GST_DEBUG_CATEGORY_STATIC(avdtp_sink_debug);
g_mutex_unlock(s->sink_lock); \
} G_STMT_END
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
struct bluetooth_data {
struct bt_get_capabilities_rsp *caps; /* Bluetooth device caps */
guint link_mtu;
+ DBusConnection *conn;
+ guint8 codec; /* Bluetooth transport configuration */
+ gchar *uuid;
+ guint8 *config;
+ gint config_size;
+
gchar buffer[BUFFER_SIZE]; /* Codec transfer buffer */
};
@@ -75,7 +87,8 @@ struct bluetooth_data {
enum {
PROP_0,
PROP_DEVICE,
- PROP_AUTOCONNECT
+ PROP_AUTOCONNECT,
+ PROP_TRANSPORT
};
GST_BOILERPLATE(GstAvdtpSink, gst_avdtp_sink, GstBaseSink,
@@ -127,6 +140,23 @@ static void gst_avdtp_sink_base_init(gpointer g_class)
gst_element_class_set_details(element_class, &avdtp_sink_details);
}
+static void gst_avdtp_sink_transport_release(GstAvdtpSink *self)
+{
+ DBusMessage *msg;
+ const char *access_type = "w";
+
+ msg = dbus_message_new_method_call("org.bluez", self->transport,
+ "org.bluez.MediaTransport",
+ "Release");
+
+ dbus_message_append_args(msg, DBUS_TYPE_STRING, &access_type,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(self->data->conn, msg, NULL);
+
+ dbus_message_unref(msg);
+}
+
static gboolean gst_avdtp_sink_stop(GstBaseSink *basesink)
{
GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
@@ -151,6 +181,10 @@ static gboolean gst_avdtp_sink_stop(GstBaseSink *basesink)
}
if (self->data) {
+ if (self->transport)
+ gst_avdtp_sink_transport_release(self);
+ if (self->data->conn)
+ dbus_connection_unref(self->data->conn);
g_free(self->data);
self->data = NULL;
}
@@ -178,6 +212,9 @@ static void gst_avdtp_sink_finalize(GObject *object)
if (self->device)
g_free(self->device);
+ if (self->transport)
+ g_free(self->transport);
+
g_mutex_free(self->sink_lock);
G_OBJECT_CLASS(parent_class)->finalize(object);
@@ -198,6 +235,13 @@ static void gst_avdtp_sink_set_property(GObject *object, guint prop_id,
case PROP_AUTOCONNECT:
sink->autoconnect = g_value_get_boolean(value);
break;
+
+ case PROP_TRANSPORT:
+ if (sink->transport)
+ g_free(sink->transport);
+ sink->transport = g_value_dup_string(value);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
@@ -217,6 +261,11 @@ static void gst_avdtp_sink_get_property(GObject *object, guint prop_id,
case PROP_AUTOCONNECT:
g_value_set_boolean(value, sink->autoconnect);
break;
+
+ case PROP_TRANSPORT:
+ g_value_set_string(value, sink->transport);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
@@ -370,6 +419,10 @@ static gboolean gst_avdtp_sink_conf_recv_stream_fd(
GIOFlags flags;
gsize read;
+ /* Proceed if stream was already acquired */
+ if (self->stream != NULL)
+ goto proceed;
+
ret = gst_avdtp_sink_bluetooth_recvmsg_fd(self);
if (ret < 0)
return FALSE;
@@ -380,6 +433,7 @@ static gboolean gst_avdtp_sink_conf_recv_stream_fd(
return FALSE;
}
+proceed:
/* set stream socket to nonblock */
GST_LOG_OBJECT(self, "setting stream socket to nonblock");
flags = g_io_channel_get_flags(self->stream);
@@ -733,6 +787,325 @@ static GstStructure *gst_avdtp_sink_parse_mpeg_caps(
return structure;
}
+static GstStructure *gst_avdtp_sink_parse_sbc_raw(GstAvdtpSink *self)
+{
+ a2dp_sbc_t *sbc = (a2dp_sbc_t *) self->data->config;
+ GstStructure *structure;
+ GValue *value;
+ GValue *list;
+ gboolean mono, stereo;
+
+ structure = gst_structure_empty_new("audio/x-sbc");
+ value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING);
+
+ /* mode */
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
+ g_value_set_static_string(value, "mono");
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) {
+ g_value_set_static_string(value, "stereo");
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) {
+ g_value_set_static_string(value, "dual");
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) {
+ g_value_set_static_string(value, "joint");
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "mode", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* subbands */
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ value = g_value_init(value, G_TYPE_INT);
+ if (sbc->subbands & BT_A2DP_SUBBANDS_4) {
+ g_value_set_int(value, 4);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->subbands & BT_A2DP_SUBBANDS_8) {
+ g_value_set_int(value, 8);
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "subbands", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* blocks */
+ value = g_value_init(value, G_TYPE_INT);
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) {
+ g_value_set_int(value, 16);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) {
+ g_value_set_int(value, 12);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) {
+ g_value_set_int(value, 8);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) {
+ g_value_set_int(value, 4);
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "blocks", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* allocation */
+ g_value_init(value, G_TYPE_STRING);
+ list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST);
+ if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) {
+ g_value_set_static_string(value, "loudness");
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) {
+ g_value_set_static_string(value, "snr");
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "allocation", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* rate */
+ g_value_init(value, G_TYPE_INT);
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (sbc->frequency & BT_SBC_SAMPLING_FREQ_48000) {
+ g_value_set_int(value, 48000);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->frequency & BT_SBC_SAMPLING_FREQ_44100) {
+ g_value_set_int(value, 44100);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->frequency & BT_SBC_SAMPLING_FREQ_32000) {
+ g_value_set_int(value, 32000);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->frequency & BT_SBC_SAMPLING_FREQ_16000) {
+ g_value_set_int(value, 16000);
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "rate", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* bitpool */
+ value = g_value_init(value, GST_TYPE_INT_RANGE);
+ gst_value_set_int_range(value,
+ MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL),
+ MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL));
+ gst_structure_set_value(structure, "bitpool", value);
+ g_value_unset(value);
+
+ /* channels */
+ mono = FALSE;
+ stereo = FALSE;
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+ mono = TRUE;
+ if ((sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) ||
+ (sbc->channel_mode &
+ BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) ||
+ (sbc->channel_mode &
+ BT_A2DP_CHANNEL_MODE_JOINT_STEREO))
+ stereo = TRUE;
+
+ if (mono && stereo) {
+ g_value_init(value, GST_TYPE_INT_RANGE);
+ gst_value_set_int_range(value, 1, 2);
+ } else {
+ g_value_init(value, G_TYPE_INT);
+ if (mono)
+ g_value_set_int(value, 1);
+ else if (stereo)
+ g_value_set_int(value, 2);
+ else {
+ GST_ERROR_OBJECT(self,
+ "Unexpected number of channels");
+ g_value_set_int(value, 0);
+ }
+ }
+
+ gst_structure_set_value(structure, "channels", value);
+ g_free(value);
+
+ return structure;
+}
+
+static GstStructure *gst_avdtp_sink_parse_mpeg_raw(GstAvdtpSink *self)
+{
+ a2dp_mpeg_t *mpeg = (a2dp_mpeg_t *) self->data->config;
+ GstStructure *structure;
+ GValue *value;
+ GValue *list;
+ gboolean valid_layer = FALSE;
+ gboolean mono, stereo;
+
+ GST_LOG_OBJECT(self, "parsing mpeg caps");
+
+ structure = gst_structure_empty_new("audio/mpeg");
+ value = g_new0(GValue, 1);
+ g_value_init(value, G_TYPE_INT);
+
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ g_value_set_int(value, 1);
+ gst_value_list_prepend_value(list, value);
+ g_value_set_int(value, 2);
+ gst_value_list_prepend_value(list, value);
+ gst_structure_set_value(structure, "mpegversion", list);
+ g_free(list);
+
+ /* layer */
+ GST_LOG_OBJECT(self, "setting mpeg layer");
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (mpeg->layer & BT_MPEG_LAYER_1) {
+ g_value_set_int(value, 1);
+ gst_value_list_prepend_value(list, value);
+ valid_layer = TRUE;
+ }
+ if (mpeg->layer & BT_MPEG_LAYER_2) {
+ g_value_set_int(value, 2);
+ gst_value_list_prepend_value(list, value);
+ valid_layer = TRUE;
+ }
+ if (mpeg->layer & BT_MPEG_LAYER_3) {
+ g_value_set_int(value, 3);
+ gst_value_list_prepend_value(list, value);
+ valid_layer = TRUE;
+ }
+ if (list) {
+ gst_structure_set_value(structure, "layer", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ if (!valid_layer) {
+ gst_structure_free(structure);
+ g_free(value);
+ return NULL;
+ }
+
+ /* rate */
+ GST_LOG_OBJECT(self, "setting mpeg rate");
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_48000) {
+ g_value_set_int(value, 48000);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_44100) {
+ g_value_set_int(value, 44100);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_32000) {
+ g_value_set_int(value, 32000);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_24000) {
+ g_value_set_int(value, 24000);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_22050) {
+ g_value_set_int(value, 22050);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_16000) {
+ g_value_set_int(value, 16000);
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "rate", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* channels */
+ GST_LOG_OBJECT(self, "setting mpeg channels");
+ mono = FALSE;
+ stereo = FALSE;
+ if (mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+ mono = TRUE;
+ if ((mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) ||
+ (mpeg->channel_mode &
+ BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) ||
+ (mpeg->channel_mode &
+ BT_A2DP_CHANNEL_MODE_JOINT_STEREO))
+ stereo = TRUE;
+
+ if (mono && stereo) {
+ g_value_init(value, GST_TYPE_INT_RANGE);
+ gst_value_set_int_range(value, 1, 2);
+ } else {
+ g_value_init(value, G_TYPE_INT);
+ if (mono)
+ g_value_set_int(value, 1);
+ else if (stereo)
+ g_value_set_int(value, 2);
+ else {
+ GST_ERROR_OBJECT(self,
+ "Unexpected number of channels");
+ g_value_set_int(value, 0);
+ }
+ }
+ gst_structure_set_value(structure, "channels", value);
+ g_free(value);
+
+ return structure;
+}
+
+static gboolean gst_avdtp_sink_update_config(GstAvdtpSink *self)
+{
+ GstStructure *structure;
+ gchar *tmp;
+
+ switch (self->data->codec) {
+ case A2DP_CODEC_SBC:
+ structure = gst_avdtp_sink_parse_sbc_raw(self);
+ break;
+ case A2DP_CODEC_MPEG12:
+ structure = gst_avdtp_sink_parse_mpeg_raw(self);
+ break;
+ default:
+ GST_ERROR_OBJECT(self, "Unsupported configuration");
+ return FALSE;
+ }
+
+ if (structure == NULL)
+ return FALSE;
+
+ if (self->dev_caps != NULL)
+ gst_caps_unref(self->dev_caps);
+
+ self->dev_caps = gst_caps_new_full(structure, NULL);
+
+ tmp = gst_caps_to_string(self->dev_caps);
+ GST_DEBUG_OBJECT(self, "Transport configuration: %s", tmp);
+ g_free(tmp);
+
+ return TRUE;
+}
+
static gboolean gst_avdtp_sink_update_caps(GstAvdtpSink *self)
{
sbc_capabilities_t *sbc;
@@ -743,6 +1116,9 @@ static gboolean gst_avdtp_sink_update_caps(GstAvdtpSink *self)
GST_LOG_OBJECT(self, "updating device caps");
+ if (self->data->config_size != 0 && self->data->config != NULL)
+ return gst_avdtp_sink_update_config(self);
+
sbc = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK);
mpeg = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK);
@@ -870,6 +1246,192 @@ static gboolean gst_avdtp_sink_event(GstBaseSink *basesink,
return TRUE;
}
+static gboolean gst_avdtp_sink_transport_parse_property(GstAvdtpSink *self,
+ DBusMessageIter *i)
+{
+ const char *key;
+ DBusMessageIter variant_i;
+
+ if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
+ GST_ERROR_OBJECT(self, "Property name not a string.");
+ return FALSE;
+ }
+
+ dbus_message_iter_get_basic(i, &key);
+
+ if (!dbus_message_iter_next(i)) {
+ GST_ERROR_OBJECT(self, "Property value missing");
+ return FALSE;
+ }
+
+ if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
+ GST_ERROR_OBJECT(self, "Property value not a variant.");
+ return FALSE;
+ }
+
+ dbus_message_iter_recurse(i, &variant_i);
+
+ switch (dbus_message_iter_get_arg_type(&variant_i)) {
+ case DBUS_TYPE_BYTE: {
+ uint8_t value;
+ dbus_message_iter_get_basic(&variant_i, &value);
+
+ if (g_str_equal(key, "Codec") == TRUE)
+ self->data->codec = value;
+
+ break;
+ }
+ case DBUS_TYPE_UINT16: {
+ uint16_t value;
+ dbus_message_iter_get_basic(&variant_i, &value);
+
+ if (g_str_equal(key, "OMTU") == TRUE)
+ self->data->link_mtu = value;
+
+ break;
+ }
+ case DBUS_TYPE_STRING: {
+ const char *value;
+ dbus_message_iter_get_basic(&variant_i, &value);
+
+ if (g_str_equal(key, "UUID") == TRUE) {
+ g_free(self->data->uuid);
+ self->data->uuid = g_strdup(value);
+ }
+
+ break;
+ }
+ case DBUS_TYPE_ARRAY: {
+ DBusMessageIter array_i;
+ char *value;
+ int size;
+
+ dbus_message_iter_recurse(&variant_i, &array_i);
+ dbus_message_iter_get_fixed_array(&array_i, &value, &size);
+
+ if (g_str_equal(key, "Configuration")) {
+ g_free(self->data->config);
+ self->data->config = g_new0(guint8, size);
+ self->data->config_size = size;
+ memcpy(self->data->config, value, size);
+ }
+
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean gst_avdtp_sink_transport_acquire(GstAvdtpSink *self)
+{
+ DBusMessage *msg, *reply;
+ DBusError err;
+ const char *access_type = "w";
+ int fd;
+
+ dbus_error_init(&err);
+
+ if (self->data->conn == NULL)
+ self->data->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+
+ msg = dbus_message_new_method_call("org.bluez", self->transport,
+ "org.bluez.MediaTransport",
+ "Acquire");
+
+ dbus_message_append_args(msg, DBUS_TYPE_STRING, &access_type,
+ DBUS_TYPE_INVALID);
+
+ reply = dbus_connection_send_with_reply_and_block(self->data->conn,
+ msg, -1, &err);
+
+ if (dbus_error_is_set(&err))
+ goto fail;
+
+ if (dbus_message_get_args(reply, &err, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID) == FALSE)
+ goto fail;
+
+ dbus_message_unref(reply);
+
+ self->stream = g_io_channel_unix_new(fd);
+ g_io_channel_set_close_on_unref(self->stream, TRUE);
+ GST_DEBUG_OBJECT(self, "stream_fd=%d", fd);
+
+ return TRUE;
+
+fail:
+ GST_ERROR_OBJECT(self, "Failed to acquire transport stream: %s",
+ err.message);
+
+ dbus_error_free(&err);
+
+ if (reply)
+ dbus_message_unref(msg);
+
+ return FALSE;
+}
+
+static gboolean gst_avdtp_sink_transport_get_properties(GstAvdtpSink *self)
+{
+ DBusMessage *msg, *reply;
+ DBusMessageIter arg_i, ele_i;
+ DBusError err;
+
+ dbus_error_init(&err);
+
+ /* Transport need to be acquire first to make sure the MTUs are
+ available */
+ if (gst_avdtp_sink_transport_acquire(self) == FALSE)
+ return FALSE;
+
+ msg = dbus_message_new_method_call("org.bluez", self->transport,
+ "org.bluez.MediaTransport",
+ "GetProperties");
+ reply = dbus_connection_send_with_reply_and_block(self->data->conn,
+ msg, -1, &err);
+
+ if (dbus_error_is_set(&err) || reply == NULL) {
+ GST_ERROR_OBJECT(self, "Failed to get transport properties: %s",
+ err.message);
+ goto fail;
+ }
+
+ if (!dbus_message_iter_init(reply, &arg_i)) {
+ GST_ERROR_OBJECT(self, "GetProperties reply has no arguments.");
+ goto fail;
+ }
+
+ if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
+ GST_ERROR_OBJECT(self, "GetProperties argument is not an array.");
+ goto fail;
+ }
+
+ dbus_message_iter_recurse(&arg_i, &ele_i);
+ while (dbus_message_iter_get_arg_type(&ele_i) != DBUS_TYPE_INVALID) {
+
+ if (dbus_message_iter_get_arg_type(&ele_i) ==
+ DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter dict_i;
+
+ dbus_message_iter_recurse(&ele_i, &dict_i);
+
+ gst_avdtp_sink_transport_parse_property(self, &dict_i);
+ }
+
+ if (!dbus_message_iter_next(&ele_i))
+ break;
+ }
+
+ return gst_avdtp_sink_update_caps(self);
+
+fail:
+ dbus_message_unref(msg);
+ dbus_message_unref(reply);
+ return FALSE;
+
+}
+
static gboolean gst_avdtp_sink_start(GstBaseSink *basesink)
{
GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
@@ -878,6 +1440,16 @@ static gboolean gst_avdtp_sink_start(GstBaseSink *basesink)
GST_INFO_OBJECT(self, "start");
+ self->data = g_new0(struct bluetooth_data, 1);
+
+ self->stream = NULL;
+ self->stream_caps = NULL;
+ self->mp3_using_crc = -1;
+ self->channel_mode = -1;
+
+ if (self->transport != NULL)
+ return gst_avdtp_sink_transport_get_properties(self);
+
self->watch_id = 0;
sk = bt_audio_service_open();
@@ -892,13 +1464,6 @@ static gboolean gst_avdtp_sink_start(GstBaseSink *basesink)
self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR |
G_IO_NVAL, server_callback, self);
- self->data = g_new0(struct bluetooth_data, 1);
-
- self->stream = NULL;
- self->stream_caps = NULL;
- self->mp3_using_crc = -1;
- self->channel_mode = -1;
-
if (!gst_avdtp_sink_get_capabilities(self)) {
GST_ERROR_OBJECT(self, "failed to get capabilities "
"from device");
@@ -920,6 +1485,9 @@ static gboolean gst_avdtp_sink_stream_start(GstAvdtpSink *self)
struct bt_new_stream_ind *ind = (void *) buf;
GIOError io_error;
+ if (self->transport != NULL)
+ return gst_avdtp_sink_conf_recv_stream_fd(self);
+
memset(req, 0, sizeof(buf));
req->h.type = BT_REQUEST;
req->h.name = BT_START_STREAM;
@@ -1051,6 +1619,10 @@ static gboolean gst_avdtp_sink_configure(GstAvdtpSink *self,
GST_DEBUG_OBJECT(self, "configuring device with caps: %s", temp);
g_free(temp);
+ /* Transport already configured */
+ if (self->transport != NULL)
+ return TRUE;
+
structure = gst_caps_get_structure(caps, 0);
if (gst_structure_has_name(structure, "audio/x-sbc"))
@@ -1237,6 +1809,12 @@ static void gst_avdtp_sink_class_init(GstAvdtpSinkClass *klass)
"to device", DEFAULT_AUTOCONNECT,
G_PARAM_READWRITE));
+ g_object_class_install_property(object_class, PROP_TRANSPORT,
+ g_param_spec_string("transport",
+ "Transport",
+ "Use configured transport",
+ NULL, G_PARAM_READWRITE));
+
GST_DEBUG_CATEGORY_INIT(avdtp_sink_debug, "avdtpsink", 0,
"A2DP headset sink element");
}
@@ -1245,6 +1823,7 @@ static void gst_avdtp_sink_init(GstAvdtpSink *self,
GstAvdtpSinkClass *klass)
{
self->device = NULL;
+ self->transport = NULL;
self->data = NULL;
self->stream = NULL;
@@ -1400,11 +1979,25 @@ void gst_avdtp_sink_set_device(GstAvdtpSink *self, const gchar *dev)
self->device = g_strdup(dev);
}
+void gst_avdtp_sink_set_transport(GstAvdtpSink *self, const gchar *trans)
+{
+ if (self->transport != NULL)
+ g_free(self->transport);
+
+ GST_LOG_OBJECT(self, "Setting transport: %s", trans);
+ self->transport = g_strdup(trans);
+}
+
gchar *gst_avdtp_sink_get_device(GstAvdtpSink *self)
{
return g_strdup(self->device);
}
+gchar *gst_avdtp_sink_get_transport(GstAvdtpSink *self)
+{
+ return g_strdup(self->transport);
+}
+
void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc)
{
gint new_crc;
diff --git a/audio/gstavdtpsink.h b/audio/gstavdtpsink.h
index eb78248..888169f 100644
--- a/audio/gstavdtpsink.h
+++ b/audio/gstavdtpsink.h
@@ -51,6 +51,7 @@ struct _GstAvdtpSink {
GstBaseSink sink;
gchar *device;
+ gchar *transport;
GIOChannel *stream;
struct bluetooth_data *data;
@@ -86,8 +87,13 @@ guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink);
void gst_avdtp_sink_set_device(GstAvdtpSink *sink,
const gchar* device);
+void gst_avdtp_sink_set_transport(GstAvdtpSink *sink,
+ const gchar *transport);
+
gchar *gst_avdtp_sink_get_device(GstAvdtpSink *sink);
+gchar *gst_avdtp_sink_get_transport(GstAvdtpSink *sink);
+
gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin);
void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc);
--
1.7.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 06/10] Add initial implementation of org.bluez.Media spec
2010-09-13 14:15 [PATCH 00/10] Media API Luiz Augusto von Dentz
` (4 preceding siblings ...)
2010-09-13 14:15 ` [PATCH 05/10] Add simple-endpoint test script Luiz Augusto von Dentz
@ 2010-09-13 14:15 ` Luiz Augusto von Dentz
2010-09-13 14:15 ` [PATCH 07/10] Introduce headset_get_inband Luiz Augusto von Dentz
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Luiz Augusto von Dentz @ 2010-09-13 14:15 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.dentz-von@nokia.com>
---
Makefile.am | 2 +
audio/a2dp.c | 726 ++++++++++++++++++++++++++++++++++++++++++++---------
audio/a2dp.h | 15 ++
audio/avdtp.c | 88 +++----
audio/avdtp.h | 5 +-
audio/manager.c | 54 ++++
audio/manager.h | 1 +
audio/media.c | 683 +++++++++++++++++++++++++++++++++++++++++++++++++
audio/media.h | 52 ++++
audio/sink.c | 179 ++------------
audio/source.c | 174 ++-----------
audio/transport.c | 671 +++++++++++++++++++++++++++++++++++++++++++++++++
audio/transport.h | 34 +++
audio/unix.c | 1 +
14 files changed, 2198 insertions(+), 487 deletions(-)
create mode 100644 audio/media.c
create mode 100644 audio/media.h
create mode 100644 audio/transport.c
create mode 100644 audio/transport.h
diff --git a/Makefile.am b/Makefile.am
index 46f5449..347205e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -139,6 +139,8 @@ builtin_sources += audio/main.c \
audio/avdtp.h audio/avdtp.c \
audio/ipc.h audio/ipc.c \
audio/unix.h audio/unix.c \
+ audio/media.h audio/media.c \
+ audio/transport.h audio/transport.c \
audio/telephony.h
builtin_nodist += audio/telephony.c
diff --git a/audio/a2dp.c b/audio/a2dp.c
index ef0df17..e6daf3b 100644
--- a/audio/a2dp.c
+++ b/audio/a2dp.c
@@ -43,6 +43,7 @@
#include "sink.h"
#include "source.h"
#include "unix.h"
+#include "media.h"
#include "a2dp.h"
#include "sdpd.h"
@@ -60,9 +61,11 @@
#endif
struct a2dp_sep {
+ struct a2dp_server *server;
+ struct media_endpoint *endpoint;
uint8_t type;
uint8_t codec;
- struct avdtp_local_sep *sep;
+ struct avdtp_local_sep *lsep;
struct avdtp *session;
struct avdtp_stream *stream;
guint suspend_timer;
@@ -73,6 +76,7 @@ struct a2dp_sep {
};
struct a2dp_setup_cb {
+ a2dp_select_cb_t select_cb;
a2dp_config_cb_t config_cb;
a2dp_stream_cb_t resume_cb;
a2dp_stream_cb_t suspend_cb;
@@ -84,6 +88,7 @@ struct a2dp_setup {
struct audio_device *dev;
struct avdtp *session;
struct a2dp_sep *sep;
+ struct avdtp_remote_sep *rsep;
struct avdtp_stream *stream;
struct avdtp_error *err;
GSList *client_caps;
@@ -150,11 +155,11 @@ static struct audio_device *a2dp_get_dev(struct avdtp *session)
static gboolean finalize_config(struct a2dp_setup *s)
{
GSList *l;
+ struct avdtp_stream *stream = s->err ? NULL : s->stream;
setup_ref(s);
for (l = s->cb; l != NULL; l = l->next) {
struct a2dp_setup_cb *cb = l->data;
- struct avdtp_stream *stream = s->err ? NULL : s->stream;
if (!cb->config_cb)
continue;
@@ -166,6 +171,7 @@ static gboolean finalize_config(struct a2dp_setup *s)
}
setup_unref(s);
+
return FALSE;
}
@@ -184,6 +190,7 @@ static gboolean finalize_resume(struct a2dp_setup *s)
GSList *l;
setup_ref(s);
+
for (l = s->cb; l != NULL; l = l->next) {
struct a2dp_setup_cb *cb = l->data;
@@ -195,6 +202,7 @@ static gboolean finalize_resume(struct a2dp_setup *s)
}
setup_unref(s);
+
return FALSE;
}
@@ -227,6 +235,25 @@ static gboolean finalize_suspend_errno(struct a2dp_setup *s, int err)
return finalize_suspend(s);
}
+static gboolean finalize_select(struct a2dp_setup *s, GSList *caps)
+{
+ GSList *l;
+
+ setup_ref(s);
+ for (l = s->cb; l != NULL; l = l->next) {
+ struct a2dp_setup_cb *cb = l->data;
+
+ if (cb->select_cb) {
+ cb->select_cb(s->session, s->sep, caps, cb->user_data);
+ cb->select_cb = NULL;
+ setup_unref(s);
+ }
+ }
+
+ setup_unref(s);
+ return FALSE;
+}
+
static struct a2dp_setup *find_setup_by_session(struct avdtp *session)
{
GSList *l;
@@ -276,6 +303,9 @@ static void stream_state_changed(struct avdtp_stream *stream,
sep->session = NULL;
}
+ if (sep->endpoint)
+ media_endpoint_clear_configuration(sep->endpoint);
+
sep->stream = NULL;
}
@@ -505,6 +535,117 @@ static gboolean mpeg_getcap_ind(struct avdtp *session,
return TRUE;
}
+static gboolean endpoint_setconf_ind(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream,
+ GSList *caps, uint8_t *err,
+ uint8_t *category, void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct audio_device *dev;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ DBG("Sink %p: Set_Configuration_Ind", sep);
+ else
+ DBG("Source %p: Set_Configuration_Ind", sep);
+
+ dev = a2dp_get_dev(session);
+ if (!dev) {
+ *err = AVDTP_UNSUPPORTED_CONFIGURATION;
+ *category = 0x00;
+ return FALSE;
+ }
+
+ for (; caps != NULL; caps = g_slist_next(caps)) {
+ struct avdtp_service_capability *cap = caps->data;
+ struct avdtp_media_codec_capability *codec;
+ gboolean ret;
+
+ if (cap->category == AVDTP_DELAY_REPORTING &&
+ !a2dp_sep->delay_reporting) {
+ *err = AVDTP_UNSUPPORTED_CONFIGURATION;
+ *category = AVDTP_DELAY_REPORTING;
+ return FALSE;
+ }
+
+ if (cap->category != AVDTP_MEDIA_CODEC)
+ continue;
+
+ codec = (struct avdtp_media_codec_capability *) cap->data;
+
+ if (codec->media_codec_type != a2dp_sep->codec) {
+ *err = AVDTP_UNSUPPORTED_CONFIGURATION;
+ *category = AVDTP_MEDIA_CODEC;
+ return FALSE;
+ }
+
+ ret = media_endpoint_set_configuration(a2dp_sep->endpoint, dev,
+ codec->data, cap->length - sizeof(*codec),
+ NULL, NULL);
+ if (ret)
+ break;
+
+ *err = AVDTP_UNSUPPORTED_CONFIGURATION;
+ *category = AVDTP_MEDIA_CODEC;
+ return FALSE;
+ }
+
+ avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
+ a2dp_sep->stream = stream;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE)
+ sink_new_stream(dev, session, stream);
+
+ return TRUE;
+}
+
+static gboolean endpoint_getcap_ind(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ gboolean get_all,
+ GSList **caps, uint8_t *err, void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct avdtp_service_capability *media_transport, *media_codec;
+ struct avdtp_media_codec_capability *codec_caps;
+ uint8_t *capabilities;
+ size_t length;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ DBG("Sink %p: Get_Capability_Ind", sep);
+ else
+ DBG("Source %p: Get_Capability_Ind", sep);
+
+ *caps = NULL;
+
+ media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+ NULL, 0);
+
+ *caps = g_slist_append(*caps, media_transport);
+
+ length = media_endpoint_get_capabilities(a2dp_sep->endpoint,
+ &capabilities);
+
+ codec_caps = g_malloc0(sizeof(*codec_caps) + length);
+ codec_caps->media_type = AVDTP_MEDIA_TYPE_AUDIO;
+ codec_caps->media_codec_type = a2dp_sep->codec;
+ memcpy(codec_caps->data, capabilities, length);
+
+ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, codec_caps,
+ sizeof(*codec_caps) + length);
+
+ *caps = g_slist_append(*caps, media_codec);
+ g_free(codec_caps);
+
+ if (get_all) {
+ struct avdtp_service_capability *delay_reporting;
+ delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+ NULL, 0);
+ *caps = g_slist_append(*caps, delay_reporting);
+ }
+
+ return TRUE;
+}
+
static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream,
struct avdtp_error *err, void *user_data)
@@ -543,6 +684,23 @@ static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
else
source_new_stream(dev, session, setup->stream);
+ /* Notify Endpoint */
+ if (a2dp_sep->endpoint) {
+ struct avdtp_service_capability *service;
+ struct avdtp_media_codec_capability *codec;
+
+ service = avdtp_stream_get_codec(stream);
+ codec = (struct avdtp_media_codec_capability *) service->data;
+
+ if (media_endpoint_set_configuration(a2dp_sep->endpoint, dev,
+ codec->data, service->length -
+ sizeof(*codec), NULL, NULL) ==
+ FALSE) {
+ setup->stream = NULL;
+ finalize_config_errno(setup, -EPERM);
+ }
+ }
+
ret = avdtp_open(session, stream);
if (ret < 0) {
error("Error on avdtp_open %s (%d)", strerror(-ret), -ret);
@@ -761,39 +919,11 @@ static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep,
static gboolean a2dp_reconfigure(gpointer data)
{
struct a2dp_setup *setup = data;
- struct avdtp_local_sep *lsep;
- struct avdtp_remote_sep *rsep;
- struct avdtp_service_capability *cap;
- struct avdtp_media_codec_capability *codec_cap = NULL;
- GSList *l;
+ struct a2dp_sep *sep = setup->sep;
int posix_err;
- for (l = setup->client_caps; l != NULL; l = l->next) {
- cap = l->data;
-
- if (cap->category != AVDTP_MEDIA_CODEC)
- continue;
-
- codec_cap = (void *) cap->data;
- break;
- }
-
- if (!codec_cap) {
- error("Cannot find capabilities to reconfigure");
- posix_err = -EINVAL;
- goto failed;
- }
-
- posix_err = avdtp_get_seps(setup->session, AVDTP_SEP_TYPE_SINK,
- codec_cap->media_type,
- codec_cap->media_codec_type,
- &lsep, &rsep);
- if (posix_err < 0) {
- error("No matching ACP and INT SEPs found");
- goto failed;
- }
-
- posix_err = avdtp_set_configuration(setup->session, rsep, lsep,
+ posix_err = avdtp_set_configuration(setup->session, setup->rsep,
+ sep->lsep,
setup->client_caps,
&setup->stream);
if (posix_err < 0) {
@@ -975,6 +1105,19 @@ static struct avdtp_sep_ind mpeg_ind = {
.delayreport = delayreport_ind,
};
+static struct avdtp_sep_ind endpoint_ind = {
+ .get_capability = endpoint_getcap_ind,
+ .set_configuration = endpoint_setconf_ind,
+ .get_configuration = getconf_ind,
+ .open = open_ind,
+ .start = start_ind,
+ .suspend = suspend_ind,
+ .close = close_ind,
+ .abort = abort_ind,
+ .reconfigure = reconf_ind,
+ .delayreport = delayreport_ind,
+};
+
static sdp_record_t *a2dp_record(uint8_t type, uint16_t avdtp_ver)
{
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
@@ -1042,64 +1185,6 @@ static sdp_record_t *a2dp_record(uint8_t type, uint16_t avdtp_ver)
return record;
}
-static struct a2dp_sep *a2dp_add_sep(struct a2dp_server *server, uint8_t type,
- uint8_t codec, gboolean delay_reporting)
-{
- struct a2dp_sep *sep;
- GSList **l;
- uint32_t *record_id;
- sdp_record_t *record;
- struct avdtp_sep_ind *ind;
-
- sep = g_new0(struct a2dp_sep, 1);
-
- ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind;
- sep->sep = avdtp_register_sep(&server->src, type,
- AVDTP_MEDIA_TYPE_AUDIO, codec,
- delay_reporting, ind, &cfm, sep);
- if (sep->sep == NULL) {
- g_free(sep);
- return NULL;
- }
-
- sep->codec = codec;
- sep->type = type;
- sep->delay_reporting = delay_reporting;
-
- if (type == AVDTP_SEP_TYPE_SOURCE) {
- l = &server->sources;
- record_id = &server->source_record_id;
- } else {
- l = &server->sinks;
- record_id = &server->sink_record_id;
- }
-
- if (*record_id != 0)
- goto add;
-
- record = a2dp_record(type, server->version);
- if (!record) {
- error("Unable to allocate new service record");
- avdtp_unregister_sep(sep->sep);
- g_free(sep);
- return NULL;
- }
-
- if (add_record_to_server(&server->src, record) < 0) {
- error("Unable to register A2DP service record");\
- sdp_record_free(record);
- avdtp_unregister_sep(sep->sep);
- g_free(sep);
- return NULL;
- }
- *record_id = record->handle;
-
-add:
- *l = g_slist_append(*l, sep);
-
- return sep;
-}
-
static struct a2dp_server *find_server(GSList *list, const bdaddr_t *src)
{
GSList *l;
@@ -1220,22 +1305,23 @@ proceed:
if (source) {
for (i = 0; i < sbc_srcs; i++)
- a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE,
- A2DP_CODEC_SBC, delay_reporting);
+ a2dp_add_sep(src, AVDTP_SEP_TYPE_SOURCE,
+ A2DP_CODEC_SBC, delay_reporting, NULL);
for (i = 0; i < mpeg12_srcs; i++)
- a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE,
- A2DP_CODEC_MPEG12, delay_reporting);
+ a2dp_add_sep(src, AVDTP_SEP_TYPE_SOURCE,
+ A2DP_CODEC_MPEG12, delay_reporting, NULL);
}
if (sink) {
for (i = 0; i < sbc_sinks; i++)
- a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK,
- A2DP_CODEC_SBC, delay_reporting);
+ a2dp_add_sep(src, AVDTP_SEP_TYPE_SINK,
+ A2DP_CODEC_SBC, delay_reporting, NULL);
for (i = 0; i < mpeg12_sinks; i++)
- a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK,
- A2DP_CODEC_MPEG12, delay_reporting);
+ a2dp_add_sep(src, AVDTP_SEP_TYPE_SINK,
+ A2DP_CODEC_MPEG12, delay_reporting,
+ NULL);
}
return 0;
@@ -1243,7 +1329,7 @@ proceed:
static void a2dp_unregister_sep(struct a2dp_sep *sep)
{
- avdtp_unregister_sep(sep->sep);
+ avdtp_unregister_sep(sep->lsep);
g_free(sep);
}
@@ -1255,20 +1341,14 @@ void a2dp_unregister(const bdaddr_t *src)
if (!server)
return;
- g_slist_foreach(server->sinks, (GFunc) a2dp_unregister_sep, NULL);
+ g_slist_foreach(server->sinks, (GFunc) a2dp_remove_sep, NULL);
g_slist_free(server->sinks);
- g_slist_foreach(server->sources, (GFunc) a2dp_unregister_sep, NULL);
+ g_slist_foreach(server->sources, (GFunc) a2dp_remove_sep, NULL);
g_slist_free(server->sources);
avdtp_exit(src);
- if (server->source_record_id)
- remove_record_from_server(server->source_record_id);
-
- if (server->sink_record_id)
- remove_record_from_server(server->sink_record_id);
-
servers = g_slist_remove(servers, server);
g_free(server);
@@ -1279,6 +1359,100 @@ void a2dp_unregister(const bdaddr_t *src)
connection = NULL;
}
+struct a2dp_sep *a2dp_add_sep(const bdaddr_t *src, uint8_t type,
+ uint8_t codec, gboolean delay_reporting,
+ struct media_endpoint *endpoint)
+{
+ struct a2dp_server *server;
+ struct a2dp_sep *sep;
+ GSList **l;
+ uint32_t *record_id;
+ sdp_record_t *record;
+ struct avdtp_sep_ind *ind;
+
+ server = find_server(servers, src);
+ if (server == NULL)
+ return NULL;
+
+ sep = g_new0(struct a2dp_sep, 1);
+
+ if (endpoint) {
+ ind = &endpoint_ind;
+ goto proceed;
+ }
+
+ ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind;
+
+proceed:
+ sep->lsep = avdtp_register_sep(&server->src, type,
+ AVDTP_MEDIA_TYPE_AUDIO, codec,
+ delay_reporting, ind, &cfm, sep);
+ if (sep->lsep == NULL) {
+ g_free(sep);
+ return NULL;
+ }
+
+ sep->server = server;
+ sep->endpoint = endpoint;
+ sep->codec = codec;
+ sep->type = type;
+ sep->delay_reporting = delay_reporting;
+
+ if (type == AVDTP_SEP_TYPE_SOURCE) {
+ l = &server->sources;
+ record_id = &server->source_record_id;
+ } else {
+ l = &server->sinks;
+ record_id = &server->sink_record_id;
+ }
+
+ if (*record_id != 0)
+ goto add;
+
+ record = a2dp_record(type, server->version);
+ if (!record) {
+ error("Unable to allocate new service record");
+ avdtp_unregister_sep(sep->lsep);
+ g_free(sep);
+ return NULL;
+ }
+
+ if (add_record_to_server(&server->src, record) < 0) {
+ error("Unable to register A2DP service record");\
+ sdp_record_free(record);
+ avdtp_unregister_sep(sep->lsep);
+ g_free(sep);
+ return NULL;
+ }
+ *record_id = record->handle;
+
+add:
+ *l = g_slist_append(*l, sep);
+
+ return sep;
+}
+
+void a2dp_remove_sep(struct a2dp_sep *sep)
+{
+ struct a2dp_server *server = sep->server;
+
+ if (sep->type == AVDTP_SEP_TYPE_SOURCE) {
+ server->sources = g_slist_remove(server->sources, sep);
+ if (server->sources == NULL && server->source_record_id) {
+ remove_record_from_server(server->source_record_id);
+ server->source_record_id = 0;
+ }
+ } else {
+ server->sinks = g_slist_remove(server->sinks, sep);
+ if (server->sinks == NULL && server->sink_record_id) {
+ remove_record_from_server(server->sink_record_id);
+ server->sink_record_id = 0;
+ }
+ }
+
+ a2dp_unregister_sep(sep);
+}
+
struct a2dp_sep *a2dp_get(struct avdtp *session,
struct avdtp_remote_sep *rsep)
{
@@ -1317,6 +1491,309 @@ struct a2dp_sep *a2dp_get(struct avdtp *session,
return NULL;
}
+static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+{
+ switch (freq) {
+ case SBC_SAMPLING_FREQ_16000:
+ case SBC_SAMPLING_FREQ_32000:
+ return 53;
+ case SBC_SAMPLING_FREQ_44100:
+ switch (mode) {
+ case SBC_CHANNEL_MODE_MONO:
+ case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+ return 31;
+ case SBC_CHANNEL_MODE_STEREO:
+ case SBC_CHANNEL_MODE_JOINT_STEREO:
+ return 53;
+ default:
+ error("Invalid channel mode %u", mode);
+ return 53;
+ }
+ case SBC_SAMPLING_FREQ_48000:
+ switch (mode) {
+ case SBC_CHANNEL_MODE_MONO:
+ case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+ return 29;
+ case SBC_CHANNEL_MODE_STEREO:
+ case SBC_CHANNEL_MODE_JOINT_STEREO:
+ return 51;
+ default:
+ error("Invalid channel mode %u", mode);
+ return 51;
+ }
+ default:
+ error("Invalid sampling freq %u", freq);
+ return 53;
+ }
+}
+
+static gboolean select_sbc_params(struct sbc_codec_cap *cap,
+ struct sbc_codec_cap *supported)
+{
+ unsigned int max_bitpool, min_bitpool;
+
+ memset(cap, 0, sizeof(struct sbc_codec_cap));
+
+ cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+ cap->cap.media_codec_type = A2DP_CODEC_SBC;
+
+ if (supported->frequency & SBC_SAMPLING_FREQ_44100)
+ cap->frequency = SBC_SAMPLING_FREQ_44100;
+ else if (supported->frequency & SBC_SAMPLING_FREQ_48000)
+ cap->frequency = SBC_SAMPLING_FREQ_48000;
+ else if (supported->frequency & SBC_SAMPLING_FREQ_32000)
+ cap->frequency = SBC_SAMPLING_FREQ_32000;
+ else if (supported->frequency & SBC_SAMPLING_FREQ_16000)
+ cap->frequency = SBC_SAMPLING_FREQ_16000;
+ else {
+ error("No supported frequencies");
+ return FALSE;
+ }
+
+ if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+ cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+ else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO)
+ cap->channel_mode = SBC_CHANNEL_MODE_STEREO;
+ else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+ cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+ else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO)
+ cap->channel_mode = SBC_CHANNEL_MODE_MONO;
+ else {
+ error("No supported channel modes");
+ return FALSE;
+ }
+
+ if (supported->block_length & SBC_BLOCK_LENGTH_16)
+ cap->block_length = SBC_BLOCK_LENGTH_16;
+ else if (supported->block_length & SBC_BLOCK_LENGTH_12)
+ cap->block_length = SBC_BLOCK_LENGTH_12;
+ else if (supported->block_length & SBC_BLOCK_LENGTH_8)
+ cap->block_length = SBC_BLOCK_LENGTH_8;
+ else if (supported->block_length & SBC_BLOCK_LENGTH_4)
+ cap->block_length = SBC_BLOCK_LENGTH_4;
+ else {
+ error("No supported block lengths");
+ return FALSE;
+ }
+
+ if (supported->subbands & SBC_SUBBANDS_8)
+ cap->subbands = SBC_SUBBANDS_8;
+ else if (supported->subbands & SBC_SUBBANDS_4)
+ cap->subbands = SBC_SUBBANDS_4;
+ else {
+ error("No supported subbands");
+ return FALSE;
+ }
+
+ if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS)
+ cap->allocation_method = SBC_ALLOCATION_LOUDNESS;
+ else if (supported->allocation_method & SBC_ALLOCATION_SNR)
+ cap->allocation_method = SBC_ALLOCATION_SNR;
+
+ min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool);
+ max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode),
+ supported->max_bitpool);
+
+ cap->min_bitpool = min_bitpool;
+ cap->max_bitpool = max_bitpool;
+
+ return TRUE;
+}
+
+static gboolean select_capabilities(struct avdtp *session,
+ struct avdtp_remote_sep *rsep,
+ GSList **caps)
+{
+ struct avdtp_service_capability *media_transport, *media_codec;
+ struct sbc_codec_cap sbc_cap;
+
+ media_codec = avdtp_get_codec(rsep);
+ if (!media_codec)
+ return FALSE;
+
+ select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data);
+
+ media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+ NULL, 0);
+
+ *caps = g_slist_append(*caps, media_transport);
+
+ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
+ sizeof(sbc_cap));
+
+ *caps = g_slist_append(*caps, media_codec);
+
+ if (avdtp_get_delay_reporting(rsep)) {
+ struct avdtp_service_capability *delay_reporting;
+ delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+ NULL, 0);
+ *caps = g_slist_append(*caps, delay_reporting);
+ }
+
+ return TRUE;
+}
+
+static void select_cb(struct media_endpoint *endpoint, void *ret, int size,
+ void *user_data)
+{
+ struct a2dp_setup *setup = user_data;
+ struct avdtp_service_capability *media_transport, *media_codec;
+ struct avdtp_media_codec_capability *cap;
+ GSList *caps = NULL;
+
+ if (size < 0) {
+ DBG("Endpoint replied an invalid configuration");
+ goto done;
+ }
+
+ media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+ NULL, 0);
+
+ caps = g_slist_append(caps, media_transport);
+
+ cap = g_malloc0(sizeof(*cap) + size);
+ cap->media_type = AVDTP_MEDIA_TYPE_AUDIO;
+ cap->media_codec_type = setup->sep->codec;
+ memcpy(cap->data, ret, size);
+
+ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, cap,
+ sizeof(*cap) + size);
+
+ caps = g_slist_append(caps, media_codec);
+
+done:
+ finalize_select(setup, caps);
+}
+
+static gboolean auto_select(gpointer data)
+{
+ struct a2dp_setup *setup = data;
+
+ finalize_select(setup, setup->client_caps);
+
+ return FALSE;
+}
+
+static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list,
+ const char *sender)
+{
+ for (; list; list = list->next) {
+ struct a2dp_sep *sep = list->data;
+
+ /* Use sender's endpoint if available */
+ if (sender) {
+ const char *name;
+
+ if (sep->endpoint == NULL)
+ continue;
+
+ name = media_endpoint_get_sender(sep->endpoint);
+ if (g_strcmp0(sender, name) != 0)
+ continue;
+ }
+
+ if (avdtp_find_remote_sep(session, sep->lsep) == NULL)
+ continue;
+
+ return sep;
+ }
+
+ return NULL;
+}
+
+static struct a2dp_sep *a2dp_select_sep(struct avdtp *session, uint8_t type,
+ const char *sender)
+{
+ struct a2dp_server *server;
+ struct a2dp_sep *sep;
+ GSList *l;
+ bdaddr_t src;
+
+ avdtp_get_peers(session, &src, NULL);
+ server = find_server(servers, &src);
+ if (!server)
+ return NULL;
+
+ l = type == AVDTP_SEP_TYPE_SINK ? server->sources : server->sinks;
+
+ /* Check sender's seps first */
+ sep = a2dp_find_sep(session, l, sender);
+ if (sep != NULL)
+ return sep;
+
+ return a2dp_find_sep(session, l, NULL);
+}
+
+unsigned int a2dp_select_capabilities(struct avdtp *session,
+ uint8_t type, const char *sender,
+ a2dp_select_cb_t cb,
+ void *user_data)
+{
+ struct a2dp_setup *setup;
+ struct a2dp_setup_cb *cb_data;
+ struct a2dp_sep *sep;
+ struct avdtp_service_capability *service;
+ struct avdtp_media_codec_capability *codec;
+
+ sep = a2dp_select_sep(session, type, sender);
+ if (!sep) {
+ error("Unable to select SEP");
+ return 0;
+ }
+
+ cb_data = g_new0(struct a2dp_setup_cb, 1);
+ cb_data->select_cb = cb;
+ cb_data->user_data = user_data;
+ cb_data->id = ++cb_id;
+
+ setup = find_setup_by_session(session);
+ if (!setup) {
+ setup = g_new0(struct a2dp_setup, 1);
+ setup->session = avdtp_ref(session);
+ setup->dev = a2dp_get_dev(session);
+ setups = g_slist_append(setups, setup);
+ }
+
+ setup_ref(setup);
+ setup->cb = g_slist_append(setup->cb, cb_data);
+ setup->sep = sep;
+ setup->rsep = avdtp_find_remote_sep(session, sep->lsep);
+
+ if (setup->rsep == NULL) {
+ error("Could not find remote sep");
+ goto fail;
+ }
+
+ /* FIXME: Remove auto select when it is not longer possible to register
+ endpoint in the configuration file */
+ if (sep->endpoint == NULL) {
+ if (!select_capabilities(session, setup->rsep,
+ &setup->client_caps)) {
+ error("Unable to auto select remote SEP capabilities");
+ goto fail;
+ }
+
+ g_idle_add(auto_select, setup);
+
+ return cb_data->id;
+ }
+
+ service = avdtp_get_codec(setup->rsep);
+ codec = (struct avdtp_media_codec_capability *) service->data;
+
+ if (media_endpoint_select_configuration(sep->endpoint, codec->data,
+ service->length - sizeof(*codec),
+ select_cb, setup) ==
+ TRUE)
+ return cb_data->id;
+
+fail:
+ setup_unref(setup);
+ cb_id--;
+ return 0;
+
+}
+
unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
a2dp_config_cb_t cb, GSList *caps,
void *user_data)
@@ -1326,8 +1803,6 @@ unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
struct a2dp_server *server;
struct a2dp_setup *setup;
struct a2dp_sep *tmp;
- struct avdtp_local_sep *lsep;
- struct avdtp_remote_sep *rsep;
struct avdtp_service_capability *cap;
struct avdtp_media_codec_capability *codec_cap = NULL;
int posix_err;
@@ -1355,7 +1830,7 @@ unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
if (sep->codec != codec_cap->media_codec_type)
return 0;
- DBG("a2dp_config: selected SEP %p", sep->sep);
+ DBG("a2dp_config: selected SEP %p", sep->lsep);
cb_data = g_new0(struct a2dp_setup_cb, 1);
cb_data->config_cb = cb;
@@ -1376,7 +1851,7 @@ unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
setup->stream = sep->stream;
setup->client_caps = caps;
- switch (avdtp_sep_get_state(sep->sep)) {
+ switch (avdtp_sep_get_state(sep->lsep)) {
case AVDTP_STATE_IDLE:
if (sep->type == AVDTP_SEP_TYPE_SOURCE) {
l = server->sources;
@@ -1403,16 +1878,15 @@ unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
break;
}
- if (avdtp_get_seps(session, remote_type,
- codec_cap->media_type,
- codec_cap->media_codec_type,
- &lsep, &rsep) < 0) {
+ setup->rsep = avdtp_find_remote_sep(session, sep->lsep);
+ if (setup->rsep == NULL) {
error("No matching ACP and INT SEPs found");
goto failed;
}
- posix_err = avdtp_set_configuration(session, rsep, lsep,
- caps, &setup->stream);
+ posix_err = avdtp_set_configuration(session, setup->rsep,
+ sep->lsep, caps,
+ &setup->stream);
if (posix_err < 0) {
error("avdtp_set_configuration: %s",
strerror(-posix_err));
@@ -1469,7 +1943,7 @@ unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep,
setup->sep = sep;
setup->stream = sep->stream;
- switch (avdtp_sep_get_state(sep->sep)) {
+ switch (avdtp_sep_get_state(sep->lsep)) {
case AVDTP_STATE_IDLE:
goto failed;
break;
@@ -1528,7 +2002,7 @@ unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep,
setup->sep = sep;
setup->stream = sep->stream;
- switch (avdtp_sep_get_state(sep->sep)) {
+ switch (avdtp_sep_get_state(sep->lsep)) {
case AVDTP_STATE_IDLE:
error("a2dp_suspend: no stream to suspend");
goto failed;
@@ -1541,6 +2015,7 @@ unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep,
error("avdtp_suspend failed");
goto failed;
}
+ sep->suspending = TRUE;
break;
default:
error("SEP in bad state for suspend");
@@ -1595,7 +2070,7 @@ gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
if (sep->locked)
return FALSE;
- DBG("SEP %p locked", sep->sep);
+ DBG("SEP %p locked", sep->lsep);
sep->locked = TRUE;
return TRUE;
@@ -1605,11 +2080,11 @@ gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session)
{
avdtp_state_t state;
- state = avdtp_sep_get_state(sep->sep);
+ state = avdtp_sep_get_state(sep->lsep);
sep->locked = FALSE;
- DBG("SEP %p unlocked", sep->sep);
+ DBG("SEP %p unlocked", sep->lsep);
if (!sep->stream || state == AVDTP_STATE_IDLE)
return TRUE;
@@ -1671,3 +2146,8 @@ struct a2dp_sep *a2dp_get_sep(struct avdtp *session,
return NULL;
}
+
+struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep)
+{
+ return sep->stream;
+}
diff --git a/audio/a2dp.h b/audio/a2dp.h
index fa81776..21fccaa 100644
--- a/audio/a2dp.h
+++ b/audio/a2dp.h
@@ -121,6 +121,10 @@ struct mpeg_codec_cap {
struct a2dp_sep;
+
+typedef void (*a2dp_select_cb_t) (struct avdtp *session,
+ struct a2dp_sep *sep, GSList *caps,
+ void *user_data);
typedef void (*a2dp_config_cb_t) (struct avdtp *session, struct a2dp_sep *sep,
struct avdtp_stream *stream,
struct avdtp_error *err,
@@ -132,7 +136,17 @@ typedef void (*a2dp_stream_cb_t) (struct avdtp *session,
int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
void a2dp_unregister(const bdaddr_t *src);
+struct a2dp_sep *a2dp_add_sep(const bdaddr_t *src, uint8_t type,
+ uint8_t codec, gboolean delay_reporting,
+ struct media_endpoint *endpoint);
+void a2dp_remove_sep(struct a2dp_sep *sep);
+
struct a2dp_sep *a2dp_get(struct avdtp *session, struct avdtp_remote_sep *sep);
+
+unsigned int a2dp_select_capabilities(struct avdtp *session,
+ uint8_t type, const char *sender,
+ a2dp_select_cb_t cb,
+ void *user_data);
unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
a2dp_config_cb_t cb, GSList *caps,
void *user_data);
@@ -145,5 +159,6 @@ gboolean a2dp_cancel(struct audio_device *dev, unsigned int id);
gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session);
gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session);
gboolean a2dp_sep_get_lock(struct a2dp_sep *sep);
+struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep);
struct a2dp_sep *a2dp_get_sep(struct avdtp *session,
struct avdtp_stream *stream);
diff --git a/audio/avdtp.c b/audio/avdtp.c
index cc7066f..b9a03f8 100644
--- a/audio/avdtp.c
+++ b/audio/avdtp.c
@@ -1191,22 +1191,36 @@ static struct avdtp_local_sep *find_local_sep_by_seid(struct avdtp_server *serve
return NULL;
}
-static struct avdtp_local_sep *find_local_sep(struct avdtp_server *server,
- uint8_t type,
- uint8_t media_type,
- uint8_t codec)
+struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session,
+ struct avdtp_local_sep *lsep)
{
GSList *l;
- for (l = server->seps; l != NULL; l = g_slist_next(l)) {
- struct avdtp_local_sep *sep = l->data;
+ if (lsep->info.inuse)
+ return NULL;
+
+ for (l = session->seps; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_remote_sep *sep = l->data;
+ struct avdtp_service_capability *cap;
+ struct avdtp_media_codec_capability *codec_data;
+
+ /* Type must be different: source <-> sink */
+ if (sep->type == lsep->info.type)
+ continue;
+
+ if (sep->media_type != lsep->info.media_type)
+ continue;
- if (sep->info.inuse)
+ if (!sep->codec)
continue;
- if (sep->info.type == type &&
- sep->info.media_type == media_type &&
- sep->codec == codec)
+ cap = sep->codec;
+ codec_data = (void *) cap->data;
+
+ if (codec_data->media_codec_type != lsep->codec)
+ continue;
+
+ if (sep->stream == NULL)
return sep;
}
@@ -3214,49 +3228,6 @@ int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb,
return err;
}
-int avdtp_get_seps(struct avdtp *session, uint8_t acp_type, uint8_t media_type,
- uint8_t codec, struct avdtp_local_sep **lsep,
- struct avdtp_remote_sep **rsep)
-{
- GSList *l;
- uint8_t int_type;
-
- int_type = acp_type == AVDTP_SEP_TYPE_SINK ?
- AVDTP_SEP_TYPE_SOURCE : AVDTP_SEP_TYPE_SINK;
-
- *lsep = find_local_sep(session->server, int_type, media_type, codec);
- if (!*lsep)
- return -EINVAL;
-
- for (l = session->seps; l != NULL; l = g_slist_next(l)) {
- struct avdtp_remote_sep *sep = l->data;
- struct avdtp_service_capability *cap;
- struct avdtp_media_codec_capability *codec_data;
-
- if (sep->type != acp_type)
- continue;
-
- if (sep->media_type != media_type)
- continue;
-
- if (!sep->codec)
- continue;
-
- cap = sep->codec;
- codec_data = (void *) cap->data;
-
- if (codec_data->media_codec_type != codec)
- continue;
-
- if (!sep->stream) {
- *rsep = sep;
- return 0;
- }
- }
-
- return -EINVAL;
-}
-
gboolean avdtp_stream_remove_cb(struct avdtp *session,
struct avdtp_stream *stream,
unsigned int id)
@@ -3354,8 +3325,14 @@ int avdtp_set_configuration(struct avdtp *session,
new_stream->lsep = lsep;
new_stream->rseid = rsep->seid;
- if (rsep->delay_reporting && lsep->delay_reporting)
+ if (rsep->delay_reporting && lsep->delay_reporting) {
+ struct avdtp_service_capability *delay_reporting;
+
+ delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+ NULL, 0);
+ caps = g_slist_append(caps, delay_reporting);
new_stream->delay_reporting = TRUE;
+ }
g_slist_foreach(caps, copy_capabilities, &new_stream->caps);
@@ -3625,6 +3602,9 @@ int avdtp_unregister_sep(struct avdtp_local_sep *sep)
if (sep->stream)
release_stream(sep->stream, sep->stream->session);
+ DBG("SEP %p unregistered: type:%d codec:%d seid:%d", sep,
+ sep->info.type, sep->codec, sep->info.seid);
+
g_free(sep);
return 0;
diff --git a/audio/avdtp.h b/audio/avdtp.h
index 3fe682b..bf10a2f 100644
--- a/audio/avdtp.h
+++ b/audio/avdtp.h
@@ -290,9 +290,8 @@ struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type,
void *user_data);
/* Find a matching pair of local and remote SEP ID's */
-int avdtp_get_seps(struct avdtp *session, uint8_t type, uint8_t media,
- uint8_t codec, struct avdtp_local_sep **lsep,
- struct avdtp_remote_sep **rsep);
+struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session,
+ struct avdtp_local_sep *lsep);
int avdtp_unregister_sep(struct avdtp_local_sep *sep);
diff --git a/audio/manager.c b/audio/manager.c
index 87e7a2a..816c807 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -61,6 +61,7 @@
#include "device.h"
#include "error.h"
#include "avdtp.h"
+#include "media.h"
#include "a2dp.h"
#include "headset.h"
#include "gateway.h"
@@ -72,6 +73,10 @@
#include "telephony.h"
#include "unix.h"
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
typedef enum {
HEADSET = 1 << 0,
GATEWAY = 1 << 1,
@@ -115,6 +120,7 @@ static struct enabled_interfaces enabled = {
.source = FALSE,
.control = TRUE,
.socket = TRUE,
+ .media = FALSE
};
static struct audio_adapter *find_adapter(GSList *list,
@@ -1015,6 +1021,38 @@ static void avrcp_server_remove(struct btd_adapter *adapter)
audio_adapter_unref(adp);
}
+static int media_server_probe(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+ const gchar *path = adapter_get_path(adapter);
+ bdaddr_t src;
+
+ DBG("path %s", path);
+
+ adp = audio_adapter_get(adapter);
+ if (!adp)
+ return -EINVAL;
+
+ adapter_get_address(adapter, &src);
+
+ return media_register(connection, path, &src);
+}
+
+static void media_server_remove(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+ const gchar *path = adapter_get_path(adapter);
+
+ DBG("path %s", path);
+
+ adp = find_adapter(adapters, adapter);
+ if (!adp)
+ return;
+
+ media_unregister(path);
+ audio_adapter_unref(adp);
+}
+
static struct btd_device_driver audio_driver = {
.name = "audio",
.uuids = BTD_UUIDS(HSP_HS_UUID, HFP_HS_UUID, HSP_AG_UUID, HFP_AG_UUID,
@@ -1048,6 +1086,12 @@ static struct btd_adapter_driver avrcp_server_driver = {
.remove = avrcp_server_remove,
};
+static struct btd_adapter_driver media_server_driver = {
+ .name = "media",
+ .probe = media_server_probe,
+ .remove = media_server_remove,
+};
+
int audio_manager_init(DBusConnection *conn, GKeyFile *conf,
gboolean *enable_sco)
{
@@ -1078,6 +1122,8 @@ int audio_manager_init(DBusConnection *conn, GKeyFile *conf,
enabled.control = TRUE;
else if (g_str_equal(list[i], "Socket"))
enabled.socket = TRUE;
+ else if (g_str_equal(list[i], "Media"))
+ enabled.media = TRUE;
}
g_strfreev(list);
@@ -1096,6 +1142,8 @@ int audio_manager_init(DBusConnection *conn, GKeyFile *conf,
enabled.control = FALSE;
else if (g_str_equal(list[i], "Socket"))
enabled.socket = FALSE;
+ else if (g_str_equal(list[i], "Media"))
+ enabled.media = FALSE;
}
g_strfreev(list);
@@ -1126,6 +1174,9 @@ proceed:
if (enabled.socket)
unix_init();
+ if (enabled.media)
+ btd_register_adapter_driver(&media_server_driver);
+
if (enabled.headset) {
telephony_init();
btd_register_adapter_driver(&headset_server_driver);
@@ -1164,6 +1215,9 @@ void audio_manager_exit(void)
if (enabled.socket)
unix_exit();
+ if (enabled.media)
+ btd_unregister_adapter_driver(&media_server_driver);
+
if (enabled.headset) {
btd_unregister_adapter_driver(&headset_server_driver);
telephony_exit();
diff --git a/audio/manager.h b/audio/manager.h
index c79b761..0bf7663 100644
--- a/audio/manager.h
+++ b/audio/manager.h
@@ -30,6 +30,7 @@ struct enabled_interfaces {
gboolean source;
gboolean control;
gboolean socket;
+ gboolean media;
};
int audio_manager_init(DBusConnection *conn, GKeyFile *config,
diff --git a/audio/media.c b/audio/media.c
new file mode 100644
index 0000000..da9d3ae
--- /dev/null
+++ b/audio/media.c
@@ -0,0 +1,683 @@
+/*
+ *
+ * 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"
+
+#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_cancel(request->call);
+
+ dbus_message_unref(request->msg);
+ g_free(request);
+}
+
+static void media_endpoint_remove(struct media_endpoint *endpoint)
+{
+ struct media_adapter *adapter = endpoint->adapter;
+
+ 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)
+ endpoint_request_free(endpoint->request);
+
+ if (endpoint->transport)
+ media_transport_remove(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_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:
+ if (old_state != HEADSET_STATE_CONNECTING)
+ media_endpoint_clear_configuration(endpoint);
+ case HEADSET_STATE_CONNECTING:
+ break;
+ case HEADSET_STATE_CONNECTED:
+ if (old_state != HEADSET_STATE_PLAY_IN_PROGRESS &&
+ old_state != HEADSET_STATE_PLAYING)
+ media_endpoint_set_configuration(endpoint, dev, NULL,
+ 0, NULL, NULL);
+ 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)
+{
+ 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;
+ endpoint->capabilities = g_new(uint8_t, size);
+ memcpy(endpoint->capabilities, capabilities, size);
+ endpoint->size = size;
+ endpoint->adapter = adapter;
+
+ if (g_strcmp0(uuid, A2DP_SOURCE_UUID) == 0) {
+ endpoint->sep = a2dp_add_sep(&adapter->src,
+ AVDTP_SEP_TYPE_SOURCE, codec,
+ delay_reporting, endpoint);
+ if (endpoint->sep == NULL)
+ goto failed;
+ } else if (g_strcmp0(uuid, A2DP_SINK_UUID) == 0) {
+ endpoint->sep = a2dp_add_sep(&adapter->src,
+ AVDTP_SEP_TYPE_SINK, codec,
+ delay_reporting, endpoint);
+ if (endpoint->sep == NULL)
+ goto failed;
+ } else if (g_strcmp0(uuid, HFP_AG_UUID) == 0 ||
+ g_strcmp0(uuid, HSP_AG_UUID) == 0)
+ endpoint->hs_watch = headset_add_state_cb(headset_state_changed,
+ endpoint);
+ else
+ 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);
+
+ 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)
+{
+ 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);
+ } else if (strcasecmp(key, "Codec") == 0) {
+ if (var != DBUS_TYPE_BYTE)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, codec);
+ } 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 0;
+}
+
+static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct media_adapter *adapter = data;
+ DBusMessageIter args, props;
+ const char *sender, *path, *uuid = NULL;
+ gboolean delay_reporting;
+ uint8_t codec;
+ uint8_t *capabilities;
+ int size;
+
+ 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 g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Endpoint already registered");
+
+ dbus_message_iter_recurse(&args, &props);
+ if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".Failed", "Invalid argument");
+
+ if (parse_properties(&props, &uuid, &delay_reporting, &codec,
+ &capabilities, &size) || uuid == NULL)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Invalid argument");
+
+ if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting,
+ codec, capabilities, size) == FALSE)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Invalid argument");
+
+ 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 g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Endpoint not registered");
+
+ 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)) {
+ 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);
+
+ 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, *reply;
+ DBusError err;
+ const char *path;
+
+ 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;
+ }
+
+ path = media_transport_get_path(endpoint->transport);
+ dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &configuration, size,
+ DBUS_TYPE_INVALID);
+
+ if (cb != NULL)
+ return media_endpoint_async_call(conn, msg, endpoint, cb, user_data);
+
+ dbus_error_init(&err);
+
+ DBG("Calling %s: name = %s path = %s", dbus_message_get_member(msg),
+ dbus_message_get_destination(msg),
+ dbus_message_get_path(msg));
+
+ /* FIXME: remove once we can reply setconf asynchronously */
+ reply = dbus_connection_send_with_reply_and_block(conn, msg,
+ REQUEST_TIMEOUT, &err);
+
+ dbus_message_unref(msg);
+
+ if (reply) {
+ dbus_message_unref(reply);
+ return TRUE;
+ }
+
+ if (dbus_error_is_set(&err)) {
+ error("Endpoint replied with an error: %s", err.name);
+
+ if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY))
+ media_endpoint_clear_configuration(endpoint);
+
+ dbus_error_free(&err);
+ }
+
+ return FALSE;
+}
+
+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;
+
+ if (endpoint->transport == NULL)
+ return;
+
+ if (endpoint->request) {
+ endpoint_request_free(endpoint->request);
+ endpoint->request = NULL;
+ }
+
+ conn = endpoint->adapter->conn;
+
+ media_transport_remove(endpoint->transport);
+ endpoint->transport = NULL;
+
+ msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+ MEDIA_ENDPOINT_INTERFACE,
+ "ClearConfiguration");
+ if (msg == NULL) {
+ error("Couldn't allocate D-Bus message");
+ return;
+ }
+
+ g_dbus_send_message(conn, msg);
+}
+
+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;
+}
diff --git a/audio/media.h b/audio/media.h
new file mode 100644
index 0000000..f6d144c
--- /dev/null
+++ b/audio/media.h
@@ -0,0 +1,52 @@
+/*
+ *
+ * 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
+ *
+ */
+
+struct media_endpoint;
+
+typedef void (*media_endpoint_cb_t) (struct media_endpoint *endpoint,
+ void *ret, int size, void *user_data);
+
+int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src);
+void media_unregister(const char *path);
+
+const char *media_endpoint_get_sender(struct media_endpoint *endpoint);
+
+size_t media_endpoint_get_capabilities(struct media_endpoint *endpoint,
+ uint8_t **capabilities);
+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);
+gboolean media_endpoint_select_configuration(struct media_endpoint *endpoint,
+ uint8_t *capabilities,
+ size_t length,
+ media_endpoint_cb_t cb,
+ void *user_data);
+void media_endpoint_clear_configuration(struct media_endpoint *endpoint);
+void media_endpoint_release(struct media_endpoint *endpoint);
+
+struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint);
+const char *media_endpoint_get_uuid(struct media_endpoint *endpoint);
+uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint);
diff --git a/audio/sink.c b/audio/sink.c
index f4dce28..67cffee 100644
--- a/audio/sink.c
+++ b/audio/sink.c
@@ -40,6 +40,7 @@
#include "device.h"
#include "avdtp.h"
+#include "media.h"
#include "a2dp.h"
#include "error.h"
#include "sink.h"
@@ -343,146 +344,30 @@ static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep,
}
}
-static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+static void select_complete(struct avdtp *session, struct a2dp_sep *sep,
+ GSList *caps, void *user_data)
{
- switch (freq) {
- case SBC_SAMPLING_FREQ_16000:
- case SBC_SAMPLING_FREQ_32000:
- return 53;
- case SBC_SAMPLING_FREQ_44100:
- switch (mode) {
- case SBC_CHANNEL_MODE_MONO:
- case SBC_CHANNEL_MODE_DUAL_CHANNEL:
- return 31;
- case SBC_CHANNEL_MODE_STEREO:
- case SBC_CHANNEL_MODE_JOINT_STEREO:
- return 53;
- default:
- error("Invalid channel mode %u", mode);
- return 53;
- }
- case SBC_SAMPLING_FREQ_48000:
- switch (mode) {
- case SBC_CHANNEL_MODE_MONO:
- case SBC_CHANNEL_MODE_DUAL_CHANNEL:
- return 29;
- case SBC_CHANNEL_MODE_STEREO:
- case SBC_CHANNEL_MODE_JOINT_STEREO:
- return 51;
- default:
- error("Invalid channel mode %u", mode);
- return 51;
- }
- default:
- error("Invalid sampling freq %u", freq);
- return 53;
- }
-}
-
-static gboolean select_sbc_params(struct sbc_codec_cap *cap,
- struct sbc_codec_cap *supported)
-{
- unsigned int max_bitpool, min_bitpool;
-
- memset(cap, 0, sizeof(struct sbc_codec_cap));
-
- cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
- cap->cap.media_codec_type = A2DP_CODEC_SBC;
-
- if (supported->frequency & SBC_SAMPLING_FREQ_44100)
- cap->frequency = SBC_SAMPLING_FREQ_44100;
- else if (supported->frequency & SBC_SAMPLING_FREQ_48000)
- cap->frequency = SBC_SAMPLING_FREQ_48000;
- else if (supported->frequency & SBC_SAMPLING_FREQ_32000)
- cap->frequency = SBC_SAMPLING_FREQ_32000;
- else if (supported->frequency & SBC_SAMPLING_FREQ_16000)
- cap->frequency = SBC_SAMPLING_FREQ_16000;
- else {
- error("No supported frequencies");
- return FALSE;
- }
-
- if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
- cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
- else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO)
- cap->channel_mode = SBC_CHANNEL_MODE_STEREO;
- else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
- cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
- else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO)
- cap->channel_mode = SBC_CHANNEL_MODE_MONO;
- else {
- error("No supported channel modes");
- return FALSE;
- }
-
- if (supported->block_length & SBC_BLOCK_LENGTH_16)
- cap->block_length = SBC_BLOCK_LENGTH_16;
- else if (supported->block_length & SBC_BLOCK_LENGTH_12)
- cap->block_length = SBC_BLOCK_LENGTH_12;
- else if (supported->block_length & SBC_BLOCK_LENGTH_8)
- cap->block_length = SBC_BLOCK_LENGTH_8;
- else if (supported->block_length & SBC_BLOCK_LENGTH_4)
- cap->block_length = SBC_BLOCK_LENGTH_4;
- else {
- error("No supported block lengths");
- return FALSE;
- }
-
- if (supported->subbands & SBC_SUBBANDS_8)
- cap->subbands = SBC_SUBBANDS_8;
- else if (supported->subbands & SBC_SUBBANDS_4)
- cap->subbands = SBC_SUBBANDS_4;
- else {
- error("No supported subbands");
- return FALSE;
- }
-
- if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS)
- cap->allocation_method = SBC_ALLOCATION_LOUDNESS;
- else if (supported->allocation_method & SBC_ALLOCATION_SNR)
- cap->allocation_method = SBC_ALLOCATION_SNR;
-
- min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool);
- max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode),
- supported->max_bitpool);
-
- cap->min_bitpool = min_bitpool;
- cap->max_bitpool = max_bitpool;
-
- return TRUE;
-}
-
-static gboolean select_capabilities(struct avdtp *session,
- struct avdtp_remote_sep *rsep,
- GSList **caps)
-{
- struct avdtp_service_capability *media_transport, *media_codec;
- struct sbc_codec_cap sbc_cap;
-
- media_codec = avdtp_get_codec(rsep);
- if (!media_codec)
- return FALSE;
-
- select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data);
-
- media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
- NULL, 0);
-
- *caps = g_slist_append(*caps, media_transport);
+ struct sink *sink = user_data;
+ struct pending_request *pending;
+ int id;
- media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
- sizeof(sbc_cap));
+ pending = sink->connect;
+ pending->id = 0;
- *caps = g_slist_append(*caps, media_codec);
+ id = a2dp_config(session, sep, stream_setup_complete, caps, sink);
+ if (id == 0)
+ goto failed;
- if (avdtp_get_delay_reporting(rsep)) {
- struct avdtp_service_capability *delay_reporting;
- delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
- NULL, 0);
- *caps = g_slist_append(*caps, delay_reporting);
- }
+ pending->id = id;
+ return;
- return TRUE;
+failed:
+ if (pending->msg)
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+ pending_request_free(sink->dev, pending);
+ sink->connect = NULL;
+ avdtp_unref(sink->session);
+ sink->session = NULL;
}
static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err,
@@ -490,10 +375,6 @@ static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp
{
struct sink *sink = user_data;
struct pending_request *pending;
- struct avdtp_local_sep *lsep;
- struct avdtp_remote_sep *rsep;
- struct a2dp_sep *sep;
- GSList *caps = NULL;
int id;
pending = sink->connect;
@@ -515,24 +396,8 @@ static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp
DBG("Discovery complete");
- if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SINK, AVDTP_MEDIA_TYPE_AUDIO,
- A2DP_CODEC_SBC, &lsep, &rsep) < 0) {
- error("No matching ACP and INT SEPs found");
- goto failed;
- }
-
- if (!select_capabilities(session, rsep, &caps)) {
- error("Unable to select remote SEP capabilities");
- goto failed;
- }
-
- sep = a2dp_get(session, rsep);
- if (!sep) {
- error("Unable to get a local source SEP");
- goto failed;
- }
-
- id = a2dp_config(sink->session, sep, stream_setup_complete, caps, sink);
+ id = a2dp_select_capabilities(sink->session, AVDTP_SEP_TYPE_SINK, NULL,
+ select_complete, sink);
if (id == 0)
goto failed;
diff --git a/audio/source.c b/audio/source.c
index 35d8136..01173f5 100644
--- a/audio/source.c
+++ b/audio/source.c
@@ -41,6 +41,7 @@
#include "device.h"
#include "avdtp.h"
+#include "media.h"
#include "a2dp.h"
#include "error.h"
#include "source.h"
@@ -310,140 +311,34 @@ static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep,
}
}
-static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+static void select_complete(struct avdtp *session, struct a2dp_sep *sep,
+ GSList *caps, void *user_data)
{
- switch (freq) {
- case SBC_SAMPLING_FREQ_16000:
- case SBC_SAMPLING_FREQ_32000:
- return 53;
- case SBC_SAMPLING_FREQ_44100:
- switch (mode) {
- case SBC_CHANNEL_MODE_MONO:
- case SBC_CHANNEL_MODE_DUAL_CHANNEL:
- return 31;
- case SBC_CHANNEL_MODE_STEREO:
- case SBC_CHANNEL_MODE_JOINT_STEREO:
- return 53;
- default:
- error("Invalid channel mode %u", mode);
- return 53;
- }
- case SBC_SAMPLING_FREQ_48000:
- switch (mode) {
- case SBC_CHANNEL_MODE_MONO:
- case SBC_CHANNEL_MODE_DUAL_CHANNEL:
- return 29;
- case SBC_CHANNEL_MODE_STEREO:
- case SBC_CHANNEL_MODE_JOINT_STEREO:
- return 51;
- default:
- error("Invalid channel mode %u", mode);
- return 51;
- }
- default:
- error("Invalid sampling freq %u", freq);
- return 53;
- }
-}
-
-static gboolean select_sbc_params(struct sbc_codec_cap *cap,
- struct sbc_codec_cap *supported)
-{
- unsigned int max_bitpool, min_bitpool;
-
- memset(cap, 0, sizeof(struct sbc_codec_cap));
-
- cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
- cap->cap.media_codec_type = A2DP_CODEC_SBC;
-
- if (supported->frequency & SBC_SAMPLING_FREQ_44100)
- cap->frequency = SBC_SAMPLING_FREQ_44100;
- else if (supported->frequency & SBC_SAMPLING_FREQ_48000)
- cap->frequency = SBC_SAMPLING_FREQ_48000;
- else if (supported->frequency & SBC_SAMPLING_FREQ_32000)
- cap->frequency = SBC_SAMPLING_FREQ_32000;
- else if (supported->frequency & SBC_SAMPLING_FREQ_16000)
- cap->frequency = SBC_SAMPLING_FREQ_16000;
- else {
- error("No supported frequencies");
- return FALSE;
- }
-
- if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
- cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
- else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO)
- cap->channel_mode = SBC_CHANNEL_MODE_STEREO;
- else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
- cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
- else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO)
- cap->channel_mode = SBC_CHANNEL_MODE_MONO;
- else {
- error("No supported channel modes");
- return FALSE;
- }
-
- if (supported->block_length & SBC_BLOCK_LENGTH_16)
- cap->block_length = SBC_BLOCK_LENGTH_16;
- else if (supported->block_length & SBC_BLOCK_LENGTH_12)
- cap->block_length = SBC_BLOCK_LENGTH_12;
- else if (supported->block_length & SBC_BLOCK_LENGTH_8)
- cap->block_length = SBC_BLOCK_LENGTH_8;
- else if (supported->block_length & SBC_BLOCK_LENGTH_4)
- cap->block_length = SBC_BLOCK_LENGTH_4;
- else {
- error("No supported block lengths");
- return FALSE;
- }
-
- if (supported->subbands & SBC_SUBBANDS_8)
- cap->subbands = SBC_SUBBANDS_8;
- else if (supported->subbands & SBC_SUBBANDS_4)
- cap->subbands = SBC_SUBBANDS_4;
- else {
- error("No supported subbands");
- return FALSE;
- }
-
- if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS)
- cap->allocation_method = SBC_ALLOCATION_LOUDNESS;
- else if (supported->allocation_method & SBC_ALLOCATION_SNR)
- cap->allocation_method = SBC_ALLOCATION_SNR;
-
- min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool);
- max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode),
- supported->max_bitpool);
-
- cap->min_bitpool = min_bitpool;
- cap->max_bitpool = max_bitpool;
-
- return TRUE;
-}
-
-static gboolean select_capabilities(struct avdtp *session,
- struct avdtp_remote_sep *rsep,
- GSList **caps)
-{
- struct avdtp_service_capability *media_transport, *media_codec;
- struct sbc_codec_cap sbc_cap;
-
- media_codec = avdtp_get_codec(rsep);
- if (!media_codec)
- return FALSE;
-
- select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data);
+ struct source *source = user_data;
+ struct pending_request *pending;
+ int id;
- media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
- NULL, 0);
+ pending = source->connect;
- *caps = g_slist_append(*caps, media_transport);
+ pending->id = 0;
- media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
- sizeof(sbc_cap));
+ if (caps == NULL)
+ goto failed;
- *caps = g_slist_append(*caps, media_codec);
+ id = a2dp_config(session, sep, stream_setup_complete, caps, source);
+ if (id == 0)
+ goto failed;
+ pending->id = id;
+ return;
- return TRUE;
+failed:
+ if (pending->msg)
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+ pending_request_free(source->dev, pending);
+ source->connect = NULL;
+ avdtp_unref(source->session);
+ source->session = NULL;
}
static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err,
@@ -451,10 +346,6 @@ static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp
{
struct source *source = user_data;
struct pending_request *pending;
- struct avdtp_local_sep *lsep;
- struct avdtp_remote_sep *rsep;
- struct a2dp_sep *sep;
- GSList *caps = NULL;
int id;
pending = source->connect;
@@ -476,25 +367,8 @@ static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp
DBG("Discovery complete");
- if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SOURCE, AVDTP_MEDIA_TYPE_AUDIO,
- A2DP_CODEC_SBC, &lsep, &rsep) < 0) {
- error("No matching ACP and INT SEPs found");
- goto failed;
- }
-
- if (!select_capabilities(session, rsep, &caps)) {
- error("Unable to select remote SEP capabilities");
- goto failed;
- }
-
- sep = a2dp_get(session, rsep);
- if (!sep) {
- error("Unable to get a local sink SEP");
- goto failed;
- }
-
- id = a2dp_config(source->session, sep, stream_setup_complete, caps,
- source);
+ id = a2dp_select_capabilities(source->session, AVDTP_SEP_TYPE_SOURCE, NULL,
+ select_complete, source);
if (id == 0)
goto failed;
diff --git a/audio/transport.c b/audio/transport.c
new file mode 100644
index 0000000..c4d78a4
--- /dev/null
+++ b/audio/transport.c
@@ -0,0 +1,671 @@
+/*
+ *
+ * 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"
+
+#define MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport"
+
+struct acquire_request {
+ DBusMessage *msg;
+ guint id;
+ struct media_renderer *renderer;
+};
+
+struct media_renderer {
+ struct media_transport *transport;
+ struct acquire_request *request;
+ char *name;
+ char *accesstype;
+ guint watch;
+};
+
+struct media_transport {
+ DBusConnection *conn;
+ char *path; /* Transport object path */
+ struct audio_device *device; /* Transport device */
+ struct avdtp *session; /* Signalling session (a2dp only) */
+ struct media_endpoint *endpoint; /* Transport endpoint */
+ GSList *renderers; /* Transport renderers */
+ 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 */
+ uint16_t delay; /* Transport delay (a2dp only) */
+ gboolean nrec; /* Transport nrec (hfp only) */
+ gboolean inband; /* Transport inband ringtone support (hfp only) */
+ gboolean read_lock;
+ gboolean write_lock;
+ gboolean in_use;
+ guint (*resume) (struct media_transport *transport,
+ struct media_renderer *renderer);
+ void (*suspend) (struct media_transport *transport);
+ void (*cancel) (struct media_transport *transport,
+ guint id);
+ void (*get_properties) (
+ struct media_transport *transport,
+ DBusMessageIter *dict);
+ DBusMessage *(*set_property) (
+ struct media_transport *transport,
+ DBusConnection *conn,
+ DBusMessage *msg);
+};
+
+void media_transport_remove(struct media_transport *transport)
+{
+ char *path;
+
+ path = g_strdup(transport->path);
+
+ g_dbus_unregister_interface(transport->conn, path,
+ MEDIA_TRANSPORT_INTERFACE);
+
+ g_free(path);
+}
+
+static void acquire_request_free(struct acquire_request *req)
+{
+ struct media_renderer *renderer = req->renderer;
+ struct media_transport *transport = renderer->transport;
+
+ if (req->id)
+ transport->cancel(renderer->transport, req->id);
+
+ if (req->msg)
+ dbus_message_unref(req->msg);
+
+ renderer->request = NULL;
+ g_free(req);
+}
+
+static gboolean media_transport_release(struct media_transport *transport,
+ const char *accesstype)
+{
+ if (g_strstr_len(accesstype, -1, "r") != NULL) {
+ transport->read_lock = FALSE;
+ DBG("Transport %s: read lock released", transport->path);
+ }
+
+ if (g_strstr_len(accesstype, -1, "w") != NULL) {
+ transport->write_lock = FALSE;
+ DBG("Transport %s: write lock released", transport->path);
+ }
+
+ return TRUE;
+}
+
+static void media_renderer_remove(struct media_renderer *renderer)
+{
+ struct media_transport *transport = renderer->transport;
+
+ media_transport_release(transport, renderer->accesstype);
+
+ if (renderer->watch)
+ g_dbus_remove_watch(transport->conn, renderer->watch);
+
+ if (renderer->request) {
+ DBusMessage *reply = g_dbus_create_error(renderer->request->msg,
+ ERROR_INTERFACE ".Failed",
+ "%s", strerror(EIO));
+
+ g_dbus_send_message(transport->conn, reply);
+
+ acquire_request_free(renderer->request);
+ }
+
+ transport->renderers = g_slist_remove(transport->renderers, renderer);
+
+ /* Suspend if the is no longer any renderer */
+ if (transport->renderers == NULL)
+ transport->suspend(transport);
+
+ DBG("Renderer removed: sender=%s accesstype=%s", renderer->name,
+ renderer->accesstype);
+
+ g_free(renderer->name);
+ g_free(renderer->accesstype);
+ g_free(renderer);
+}
+
+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);
+
+ emit_property_changed(transport->conn, transport->path,
+ MEDIA_TRANSPORT_INTERFACE, "IMTU",
+ DBUS_TYPE_UINT16, &transport->imtu);
+
+ emit_property_changed(transport->conn, transport->path,
+ MEDIA_TRANSPORT_INTERFACE, "OMTU",
+ DBUS_TYPE_UINT16, &transport->omtu);
+
+ return TRUE;
+}
+
+static gboolean remove_renderer(gpointer data)
+{
+ media_renderer_remove(data);
+
+ return FALSE;
+}
+
+static void a2dp_resume_complete(struct avdtp *session,
+ struct avdtp_error *err, void *user_data)
+{
+ struct media_renderer *renderer = user_data;
+ struct acquire_request *req = renderer->request;
+ struct media_transport *transport = renderer->transport;
+ struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint);
+ struct avdtp_stream *stream;
+ int fd;
+ uint16_t imtu, omtu;
+
+ req->id = 0;
+
+ if (err)
+ goto fail;
+
+ stream = a2dp_sep_get_stream(sep);
+ if (stream == NULL)
+ goto fail;
+
+ if (avdtp_stream_get_transport(stream, &fd, &imtu, &omtu, NULL) ==
+ FALSE)
+ goto fail;
+
+ media_transport_set_fd(transport, fd, imtu, omtu);
+
+ if (g_dbus_send_reply(transport->conn, req->msg,
+ DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID) == FALSE)
+ goto fail;
+
+ return;
+
+fail:
+ /* Let the stream state change before removing the renderer */
+ g_idle_add(remove_renderer, renderer);
+}
+
+static guint resume_a2dp(struct media_transport *transport,
+ struct media_renderer *renderer)
+{
+ struct media_endpoint *endpoint = transport->endpoint;
+ struct audio_device *device = transport->device;
+ struct a2dp_sep *sep = media_endpoint_get_sep(endpoint);
+
+ if (transport->session == NULL) {
+ transport->session = avdtp_get(&device->src, &device->dst);
+ if (transport->session == NULL)
+ return 0;
+ }
+
+ if (transport->in_use == TRUE)
+ goto done;
+
+ transport->in_use = a2dp_sep_lock(sep, transport->session);
+ if (transport->in_use == FALSE)
+ return 0;
+
+done:
+ return a2dp_resume(transport->session, sep, a2dp_resume_complete,
+ renderer);
+}
+
+static void suspend_a2dp(struct media_transport *transport)
+{
+ struct media_endpoint *endpoint = transport->endpoint;
+ struct a2dp_sep *sep = media_endpoint_get_sep(endpoint);
+
+ a2dp_sep_unlock(sep, transport->session);
+ transport->in_use = FALSE;
+}
+
+static void cancel_a2dp(struct media_transport *transport, guint id)
+{
+ a2dp_cancel(transport->device, id);
+}
+
+static void headset_resume_complete(struct audio_device *dev, void *user_data)
+{
+ struct media_renderer *renderer = user_data;
+ struct acquire_request *req = renderer->request;
+ struct media_transport *transport = renderer->transport;
+ int fd;
+
+ req->id = 0;
+
+ if (dev == NULL)
+ goto fail;
+
+ fd = headset_get_sco_fd(dev);
+ if (fd < 0)
+ goto fail;
+
+ media_transport_set_fd(transport, fd, 48, 48);
+
+ if (g_dbus_send_reply(transport->conn, req->msg,
+ DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID) == FALSE)
+ goto fail;
+
+ return;
+
+fail:
+ media_renderer_remove(renderer);
+}
+
+static guint resume_headset(struct media_transport *transport,
+ struct media_renderer *renderer)
+{
+ struct audio_device *device = transport->device;
+
+ if (transport->in_use == TRUE)
+ goto done;
+
+ transport->in_use = headset_lock(device, HEADSET_LOCK_READ |
+ HEADSET_LOCK_WRITE);
+ if (transport->in_use == FALSE)
+ return 0;
+
+done:
+ return headset_request_stream(device, headset_resume_complete,
+ renderer);
+}
+
+static void suspend_headset(struct media_transport *transport)
+{
+ struct audio_device *device = transport->device;
+
+ headset_unlock(device, HEADSET_LOCK_READ | HEADSET_LOCK_WRITE);
+ transport->in_use = FALSE;
+}
+
+static void cancel_headset(struct media_transport *transport, guint id)
+{
+ headset_cancel_stream(transport->device, id);
+}
+
+static void media_renderer_exit(DBusConnection *connection, void *user_data)
+{
+ struct media_renderer *renderer = user_data;
+
+ renderer->watch = 0;
+ if (renderer->request != NULL)
+ acquire_request_free(renderer->request);
+
+ media_renderer_remove(renderer);
+}
+
+static gboolean media_transport_acquire(struct media_transport *transport,
+ const char *accesstype)
+{
+ gboolean read_lock = FALSE, write_lock = FALSE;
+
+ if (g_strstr_len(accesstype, -1, "r") != NULL) {
+ if (transport->read_lock == TRUE)
+ return FALSE;
+ read_lock = TRUE;
+ }
+
+ if (g_strstr_len(accesstype, -1, "w") != NULL) {
+ if (transport->write_lock == TRUE)
+ return FALSE;
+ write_lock = TRUE;
+ }
+
+ /* Check invalid accesstype */
+ if (read_lock == FALSE && write_lock == FALSE)
+ return FALSE;
+
+ if (read_lock) {
+ transport->read_lock = read_lock;
+ DBG("Transport %s: read lock acquired", transport->path);
+ }
+
+ if (write_lock) {
+ transport->write_lock = write_lock;
+ DBG("Transport %s: write lock acquired", transport->path);
+ }
+
+
+ return TRUE;
+}
+
+static struct media_renderer *media_renderer_create(
+ struct media_transport *transport,
+ DBusMessage *msg,
+ const char *accesstype)
+{
+ struct media_renderer *renderer;
+
+ renderer = g_new0(struct media_renderer, 1);
+ renderer->transport = transport;
+ renderer->name = g_strdup(dbus_message_get_sender(msg));
+ renderer->accesstype = g_strdup(accesstype);
+ renderer->watch = g_dbus_add_disconnect_watch(transport->conn,
+ renderer->name,
+ media_renderer_exit,
+ renderer, NULL);
+ transport->renderers = g_slist_append(transport->renderers, renderer);
+
+ DBG("Renderer created: sender=%s accesstype=%s", renderer->name,
+ accesstype);
+
+ return renderer;
+}
+
+static struct media_renderer *media_transport_find_renderer(
+ struct media_transport *transport,
+ const char *name)
+{
+ GSList *l;
+
+ for (l = transport->renderers; l; l = l->next) {
+ struct media_renderer *renderer = l->data;
+
+ if (g_strcmp0(renderer->name, name) == 0)
+ return renderer;
+ }
+
+ return NULL;
+}
+
+static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct media_transport *transport = data;
+ struct media_renderer *renderer;
+ struct acquire_request *req;
+ const char *accesstype, *sender;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &accesstype,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ sender = dbus_message_get_sender(msg);
+
+ renderer = media_transport_find_renderer(transport, sender);
+ if (renderer != NULL)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".Failed",
+ "Permission denied");
+
+ if (media_transport_acquire(transport, accesstype) == FALSE)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".Failed",
+ "Permission denied");
+
+ renderer = media_renderer_create(transport, msg, accesstype);
+ req = g_new0(struct acquire_request, 1);
+ req->msg = dbus_message_ref(msg);
+ req->renderer = renderer;
+ req->id = transport->resume(transport, renderer);
+ renderer->request = req;
+ if (req->id == 0)
+ media_renderer_remove(renderer);
+
+ return NULL;
+}
+
+static DBusMessage *release(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct media_transport *transport = data;
+ struct media_renderer *renderer;
+ const char *accesstype, *sender;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &accesstype,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ sender = dbus_message_get_sender(msg);
+
+ renderer = media_transport_find_renderer(transport, sender);
+ if (renderer == NULL)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".Failed",
+ "Permission denied");
+
+ if (g_strcmp0(renderer->accesstype, accesstype) == 0)
+ media_renderer_remove(renderer);
+ else if (g_strstr_len(renderer->accesstype, -1, accesstype) != NULL) {
+ media_transport_release(transport, accesstype);
+ g_strdelimit(renderer->accesstype, accesstype, ' ');
+ } else
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".Failed",
+ "Permission denied");
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *set_property_a2dp(struct media_transport *transport,
+ DBusConnection *conn, DBusMessage *msg)
+{
+ return NULL;
+}
+
+static DBusMessage *set_property_headset(struct media_transport *transport,
+ DBusConnection *conn, DBusMessage *msg)
+{
+ return NULL;
+}
+
+static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct media_transport *transport = data;
+
+ return transport->set_property(transport, conn, msg);
+}
+
+static void get_properties_a2dp(struct media_transport *transport,
+ DBusMessageIter *dict)
+{
+ dict_append_entry(dict, "Delay", DBUS_TYPE_UINT16, &transport->delay);
+}
+
+static void get_properties_headset(struct media_transport *transport,
+ DBusMessageIter *dict)
+{
+ dict_append_entry(dict, "NREC", DBUS_TYPE_BOOLEAN, &transport->nrec);
+
+ dict_append_entry(dict, "InbandRingtone", DBUS_TYPE_BOOLEAN,
+ &transport->inband);
+}
+
+static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct media_transport *transport = data;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *uuid;
+ uint8_t codec;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ /* Device */
+ dict_append_entry(&dict, "Device", DBUS_TYPE_OBJECT_PATH,
+ &transport->device->path);
+
+ dict_append_entry(&dict, "ReadLock", DBUS_TYPE_BOOLEAN,
+ &transport->read_lock);
+
+ dict_append_entry(&dict, "WriteLock", DBUS_TYPE_BOOLEAN,
+ &transport->write_lock);
+
+ dict_append_entry(&dict, "IMTU", DBUS_TYPE_UINT16,
+ &transport->imtu);
+
+ dict_append_entry(&dict, "OMTU", DBUS_TYPE_UINT16,
+ &transport->omtu);
+
+ uuid = media_endpoint_get_uuid(transport->endpoint);
+ dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid);
+
+ codec = media_endpoint_get_codec(transport->endpoint);
+ dict_append_entry(&dict, "Codec", DBUS_TYPE_BYTE, &codec);
+
+ dict_append_array(&dict, "Configuration", DBUS_TYPE_BYTE,
+ &transport->configuration, transport->size);
+
+ if (transport->get_properties)
+ transport->get_properties(transport, &dict);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static GDBusMethodTable transport_methods[] = {
+ { "GetProperties", "", "a{sv}", get_properties },
+ { "Acquire", "s", "h", acquire,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { "Release", "s", "", release },
+ { "SetProperty", "sv", "", set_property },
+ { },
+};
+
+static GDBusSignalTable transport_signals[] = {
+ { "PropertyChanged", "sv" },
+ { }
+};
+
+static void media_transport_free(void *data)
+{
+ struct media_transport *transport = data;
+
+ g_slist_foreach(transport->renderers, (GFunc) media_renderer_remove,
+ NULL);
+ g_slist_free(transport->renderers);
+
+ if (transport->session)
+ avdtp_unref(transport->session);
+
+ if (transport->conn)
+ dbus_connection_unref(transport->conn);
+
+ g_free(transport->configuration);
+ g_free(transport->path);
+ g_free(transport);
+}
+
+struct media_transport *media_transport_create(DBusConnection *conn,
+ struct media_endpoint *endpoint,
+ struct audio_device *device,
+ uint8_t *configuration,
+ size_t size)
+{
+ struct media_transport *transport;
+ const char *uuid;
+ static int fd = 0;
+
+ transport = g_new0(struct media_transport, 1);
+ transport->conn = dbus_connection_ref(conn);
+ 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->path, fd++);
+ transport->fd = -1;
+
+ uuid = media_endpoint_get_uuid(endpoint);
+ if (g_strcmp0(uuid, A2DP_SOURCE_UUID) == 0 ||
+ g_strcmp0(uuid, A2DP_SINK_UUID) == 0) {
+ transport->resume = resume_a2dp;
+ transport->suspend = suspend_a2dp;
+ transport->cancel = cancel_a2dp;
+ transport->get_properties = get_properties_a2dp;
+ transport->set_property = set_property_a2dp;
+ } else if (g_strcmp0(uuid, HFP_AG_UUID) == 0 ||
+ g_strcmp0(uuid, HSP_AG_UUID) == 0) {
+ transport->resume = resume_headset;
+ transport->suspend = suspend_headset;
+ transport->cancel = cancel_headset;
+ transport->get_properties = get_properties_headset;
+ transport->set_property = set_property_headset;
+ } else
+ goto fail;
+
+ if (g_dbus_register_interface(transport->conn, transport->path,
+ MEDIA_TRANSPORT_INTERFACE,
+ transport_methods, transport_signals, NULL,
+ transport, media_transport_free) == FALSE) {
+ error("Could not register transport %s", transport->path);
+ goto fail;
+ }
+
+ return transport;
+
+fail:
+ media_transport_free(transport);
+ return NULL;
+}
+
+const char *media_transport_get_path(struct media_transport *transport)
+{
+ return transport->path;
+}
diff --git a/audio/transport.h b/audio/transport.h
new file mode 100644
index 0000000..a7594f0
--- /dev/null
+++ b/audio/transport.h
@@ -0,0 +1,34 @@
+/*
+ *
+ * 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
+ *
+ */
+
+struct media_transport;
+
+struct media_transport *media_transport_create(DBusConnection *conn,
+ struct media_endpoint *endpoint,
+ struct audio_device *device,
+ uint8_t *configuration,
+ size_t size);
+
+void media_transport_remove(struct media_transport *transport);
+const char *media_transport_get_path(struct media_transport *transport);
diff --git a/audio/unix.c b/audio/unix.c
index cf2704c..ad822bd 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -44,6 +44,7 @@
#include "device.h"
#include "manager.h"
#include "avdtp.h"
+#include "media.h"
#include "a2dp.h"
#include "headset.h"
#include "sink.h"
--
1.7.1
^ permalink raw reply related [flat|nested] 12+ messages in thread