summaryrefslogtreecommitdiffstats
path: root/audio
diff options
context:
space:
mode:
Diffstat (limited to 'audio')
-rw-r--r--audio/Makefile.am62
-rw-r--r--audio/a2dp.c1470
-rw-r--r--audio/a2dp.h144
-rw-r--r--audio/asound.conf7
-rw-r--r--audio/audio-api.txt311
-rw-r--r--audio/audio.conf41
-rw-r--r--audio/audio.service5
-rw-r--r--audio/avdtp.c2882
-rw-r--r--audio/avdtp.h268
-rw-r--r--audio/control.c949
-rw-r--r--audio/control.h35
-rw-r--r--audio/ctl_bluetooth.c356
-rw-r--r--audio/device.c435
-rw-r--r--audio/device.h83
-rw-r--r--audio/gateway.c34
-rw-r--r--audio/gateway.h34
-rw-r--r--audio/gsta2dpsink.c701
-rw-r--r--audio/gsta2dpsink.h83
-rw-r--r--audio/gstavdtpsink.c1361
-rw-r--r--audio/gstavdtpsink.h101
-rw-r--r--audio/gstbluetooth.c104
-rw-r--r--audio/gstrtpsbcpay.c351
-rw-r--r--audio/gstrtpsbcpay.h66
-rw-r--r--audio/gstsbcdec.c223
-rw-r--r--audio/gstsbcdec.h67
-rw-r--r--audio/gstsbcenc.c602
-rw-r--r--audio/gstsbcenc.h75
-rw-r--r--audio/gstsbcparse.c222
-rw-r--r--audio/gstsbcparse.h69
-rw-r--r--audio/gstsbcutil.c521
-rw-r--r--audio/gstsbcutil.h77
-rw-r--r--audio/headset.c1924
-rw-r--r--audio/headset.h77
-rw-r--r--audio/ipc.c119
-rw-r--r--audio/ipc.h308
-rw-r--r--audio/main.c170
-rw-r--r--audio/manager.c1571
-rw-r--r--audio/manager.h50
-rw-r--r--audio/module-bluetooth-sink.c39
-rw-r--r--audio/pcm_bluetooth.c1665
-rw-r--r--audio/rtp.h76
-rw-r--r--audio/sink.c563
-rw-r--r--audio/sink.h32
-rwxr-xr-xaudio/test-audio27
-rw-r--r--audio/unix.c1155
-rw-r--r--audio/unix.h26
46 files changed, 19541 insertions, 0 deletions
diff --git a/audio/Makefile.am b/audio/Makefile.am
new file mode 100644
index 00000000..0c6acfab
--- /dev/null
+++ b/audio/Makefile.am
@@ -0,0 +1,62 @@
+
+if AUDIOPLUGIN
+plugindir = $(libdir)/bluetooth/plugins
+
+plugin_LTLIBRARIES = audio.la
+
+audio_la_SOURCES = main.c \
+ manager.h manager.c headset.h headset.c \
+ ipc.h ipc.c unix.h unix.c \
+ device.h device.c gateway.h gateway.c \
+ sink.c sink.h avdtp.c avdtp.h \
+ a2dp.c a2dp.h control.c control.h
+
+audio_la_LDFLAGS = -module -avoid-version -no-undefined \
+ -export-symbols-regex bluetooth_plugin_desc
+
+LDADD = $(top_builddir)/common/libhelper.a \
+ @GDBUS_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@
+
+if ALSA
+alsadir = $(libdir)/alsa-lib
+
+alsa_LTLIBRARIES = libasound_module_pcm_bluetooth.la libasound_module_ctl_bluetooth.la
+
+libasound_module_pcm_bluetooth_la_SOURCES = pcm_bluetooth.c rtp.h ipc.h ipc.c
+libasound_module_pcm_bluetooth_la_LDFLAGS = -module -avoid-version -export-symbols-regex [_]*snd_pcm_.*
+libasound_module_pcm_bluetooth_la_LIBADD = @SBC_LIBS@ @ALSA_LIBS@
+libasound_module_pcm_bluetooth_la_CFLAGS = @ALSA_CFLAGS@ @SBC_CFLAGS@
+
+libasound_module_ctl_bluetooth_la_SOURCES = ctl_bluetooth.c rtp.h ipc.h ipc.c
+libasound_module_ctl_bluetooth_la_LDFLAGS = -module -avoid-version -export-symbols-regex [_]*snd_ctl_.*
+libasound_module_ctl_bluetooth_la_LIBADD = @ALSA_LIBS@
+libasound_module_ctl_bluetooth_la_CFLAGS = @ALSA_CFLAGS@
+endif
+
+if GSTREAMER
+gstreamerdir = $(libdir)/gstreamer-0.10
+
+gstreamer_LTLIBRARIES = libgstbluetooth.la
+
+libgstbluetooth_la_SOURCES = gstbluetooth.c \
+ gstsbcenc.h gstsbcenc.c \
+ gstsbcdec.h gstsbcdec.c \
+ gstsbcparse.h gstsbcparse.c \
+ gstavdtpsink.h gstavdtpsink.c \
+ gsta2dpsink.h gsta2dpsink.c \
+ gstsbcutil.h gstsbcutil.c \
+ gstrtpsbcpay.h gstrtpsbcpay.c \
+ rtp.h ipc.h ipc.c
+libgstbluetooth_la_LDFLAGS = -module -avoid-version -export-symbols-regex gst_plugin_desc
+libgstbluetooth_la_LIBADD = @SBC_LIBS@ @GSTREAMER_LIBS@ -lgstaudio-0.10 -lgstrtp-0.10
+libgstbluetooth_la_CFLAGS = @GSTREAMER_CFLAGS@ @SBC_CFLAGS@
+endif
+endif
+
+AM_CFLAGS = @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@
+
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/hcid -I$(top_srcdir)/sdpd
+
+EXTRA_DIST = audio.conf audio-api.txt test-audio asound.conf
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/audio/a2dp.c b/audio/a2dp.c
new file mode 100644
index 00000000..3e89d6b1
--- /dev/null
+++ b/audio/a2dp.c
@@ -0,0 +1,1470 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <stdlib.h>
+#include <errno.h>
+
+#include <dbus/dbus.h>
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "logging.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "sink.h"
+#include "a2dp.h"
+#include "sdpd.h"
+
+/* The duration that streams without users are allowed to stay in
+ * STREAMING state. */
+#define SUSPEND_TIMEOUT 5000
+#define RECONFIGURE_TIMEOUT 500
+
+#ifndef MIN
+# define MIN(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
+#ifndef MAX
+# define MAX(x, y) ((x) > (y) ? (x) : (y))
+#endif
+
+struct a2dp_sep {
+ uint8_t type;
+ uint8_t codec;
+ struct avdtp_local_sep *sep;
+ struct avdtp *session;
+ struct avdtp_stream *stream;
+ guint suspend_timer;
+ gboolean locked;
+ gboolean suspending;
+ gboolean starting;
+};
+
+struct a2dp_setup_cb {
+ a2dp_config_cb_t config_cb;
+ a2dp_stream_cb_t resume_cb;
+ a2dp_stream_cb_t suspend_cb;
+ void *user_data;
+ int id;
+};
+
+struct a2dp_setup {
+ struct avdtp *session;
+ struct a2dp_sep *sep;
+ struct avdtp_stream *stream;
+ struct avdtp_error *err;
+ GSList *client_caps;
+ gboolean reconfigure;
+ gboolean canceled;
+ gboolean start;
+ GSList *cb;
+ int ref;
+};
+
+static DBusConnection *connection = NULL;
+
+static GSList *sinks = NULL;
+static GSList *sources = NULL;
+
+static uint32_t source_record_id = 0;
+static uint32_t sink_record_id = 0;
+
+static GSList *setups = NULL;
+static unsigned int cb_id = 0;
+
+static struct a2dp_setup *setup_ref(struct a2dp_setup *setup)
+{
+ setup->ref++;
+
+ debug("setup_ref(%p): ref=%d", setup, setup->ref);
+
+ return setup;
+}
+
+static void setup_free(struct a2dp_setup *s)
+{
+ debug("setup_free(%p)", s);
+ setups = g_slist_remove(setups, s);
+ if (s->session)
+ avdtp_unref(s->session);
+ g_slist_foreach(s->cb, (GFunc) g_free, NULL);
+ g_slist_free(s->cb);
+ g_free(s);
+}
+
+static void setup_unref(struct a2dp_setup *setup)
+{
+ setup->ref--;
+
+ debug("setup_unref(%p): ref=%d", setup, setup->ref);
+
+ if (setup->ref <= 0)
+ setup_free(setup);
+}
+
+static struct audio_device *a2dp_get_dev(struct avdtp *session)
+{
+ bdaddr_t addr;
+
+ avdtp_get_peers(session, NULL, &addr);
+
+ return manager_device_connected(&addr, A2DP_SOURCE_UUID);
+}
+
+static gboolean finalize_config(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;
+
+ if (cb->config_cb) {
+ cb->config_cb(s->session, s->sep, s->stream, s->err,
+ cb->user_data);
+ cb->config_cb = NULL;
+ setup_unref(s);
+ }
+ }
+
+ setup_unref(s);
+ return FALSE;
+}
+
+static gboolean finalize_config_errno(struct a2dp_setup *s, int err)
+{
+ struct avdtp_error avdtp_err;
+
+ avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err);
+ s->err = err ? &avdtp_err : NULL;
+
+ return finalize_config(s);
+}
+
+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;
+
+ if (cb->resume_cb) {
+ cb->resume_cb(s->session, s->err, cb->user_data);
+ cb->resume_cb = NULL;
+ setup_unref(s);
+ }
+ }
+
+ setup_unref(s);
+ return FALSE;
+}
+
+static gboolean finalize_resume_errno(struct a2dp_setup *s, int err)
+{
+ struct avdtp_error avdtp_err;
+
+ avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err);
+ s->err = err ? &avdtp_err : NULL;
+
+ return finalize_resume(s);
+}
+
+static gboolean finalize_suspend(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;
+
+ if (cb->suspend_cb) {
+ cb->suspend_cb(s->session, s->err, cb->user_data);
+ cb->suspend_cb = NULL;
+ setup_unref(s);
+ }
+ }
+
+ setup_unref(s);
+ return FALSE;
+}
+
+static gboolean finalize_suspend_errno(struct a2dp_setup *s, int err)
+{
+ struct avdtp_error avdtp_err;
+
+ avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err);
+ s->err = err ? &avdtp_err : NULL;
+
+ return finalize_suspend(s);
+}
+
+static struct a2dp_setup *find_setup_by_session(struct avdtp *session)
+{
+ GSList *l;
+
+ for (l = setups; l != NULL; l = l->next) {
+ struct a2dp_setup *setup = l->data;
+
+ if (setup->session == session)
+ return setup;
+ }
+
+ return NULL;
+}
+
+static struct a2dp_setup *find_setup_by_dev(struct audio_device *dev)
+{
+ GSList *l;
+
+ for (l = setups; l != NULL; l = l->next) {
+ struct a2dp_setup *setup = l->data;
+ struct audio_device *setup_dev = a2dp_get_dev(setup->session);
+
+ if (setup_dev == dev)
+ return setup;
+ }
+
+ return NULL;
+}
+
+static void stream_state_changed(struct avdtp_stream *stream,
+ avdtp_state_t old_state,
+ avdtp_state_t new_state,
+ struct avdtp_error *err,
+ void *user_data)
+{
+ struct a2dp_sep *sep = user_data;
+
+ if (new_state != AVDTP_STATE_IDLE)
+ return;
+
+ if (sep->suspend_timer) {
+ g_source_remove(sep->suspend_timer);
+ sep->suspend_timer = 0;
+ }
+
+ if (sep->session) {
+ avdtp_unref(sep->session);
+ sep->session = NULL;
+ }
+
+ sep->stream = NULL;
+
+}
+
+static gboolean sbc_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;
+ struct avdtp_service_capability *cap;
+ struct avdtp_media_codec_capability *codec_cap;
+ struct sbc_codec_cap *sbc_cap;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Set_Configuration_Ind", sep);
+ else
+ debug("Source %p: Set_Configuration_Ind", sep);
+
+ dev = a2dp_get_dev(session);
+ if (!dev) {
+ *err = AVDTP_UNSUPPORTED_CONFIGURATION;
+ *category = 0x00;
+ return FALSE;
+ }
+
+ /* Check bipool range */
+ for (codec_cap = NULL; caps; caps = g_slist_next(caps)) {
+ cap = caps->data;
+ if (cap->category == AVDTP_MEDIA_CODEC) {
+ codec_cap = (void *) cap->data;
+ if (codec_cap->media_codec_type == A2DP_CODEC_SBC) {
+ sbc_cap = (void *) codec_cap;
+ if (sbc_cap->min_bitpool < MIN_BITPOOL ||
+ sbc_cap->max_bitpool > MAX_BITPOOL) {
+ *err = AVDTP_UNSUPPORTED_CONFIGURATION;
+ *category = AVDTP_MEDIA_CODEC;
+ return FALSE;
+ }
+ }
+ break;
+ }
+ }
+
+ 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 sbc_getcap_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+ GSList **caps, uint8_t *err, void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct avdtp_service_capability *media_transport, *media_codec;
+ struct sbc_codec_cap sbc_cap;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Get_Capability_Ind", sep);
+ else
+ debug("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);
+
+ memset(&sbc_cap, 0, sizeof(struct sbc_codec_cap));
+
+ sbc_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+ sbc_cap.cap.media_codec_type = A2DP_CODEC_SBC;
+
+ sbc_cap.frequency = ( SBC_SAMPLING_FREQ_48000 |
+ SBC_SAMPLING_FREQ_44100 |
+ SBC_SAMPLING_FREQ_32000 |
+ SBC_SAMPLING_FREQ_16000 );
+
+ sbc_cap.channel_mode = ( SBC_CHANNEL_MODE_JOINT_STEREO |
+ SBC_CHANNEL_MODE_STEREO |
+ SBC_CHANNEL_MODE_DUAL_CHANNEL |
+ SBC_CHANNEL_MODE_MONO );
+
+ sbc_cap.block_length = ( SBC_BLOCK_LENGTH_16 |
+ SBC_BLOCK_LENGTH_12 |
+ SBC_BLOCK_LENGTH_8 |
+ SBC_BLOCK_LENGTH_4 );
+
+ sbc_cap.subbands = ( SBC_SUBBANDS_8 | SBC_SUBBANDS_4 );
+
+ sbc_cap.allocation_method = ( SBC_ALLOCATION_LOUDNESS |
+ SBC_ALLOCATION_SNR );
+
+ sbc_cap.min_bitpool = MIN_BITPOOL;
+ sbc_cap.max_bitpool = MAX_BITPOOL;
+
+ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
+ sizeof(sbc_cap));
+
+ *caps = g_slist_append(*caps, media_codec);
+
+ return TRUE;
+}
+
+static gboolean mpeg_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)
+ debug("Sink %p: Set_Configuration_Ind", sep);
+ else
+ debug("Source %p: Set_Configuration_Ind", sep);
+
+ dev = a2dp_get_dev(session);
+ if (!dev) {
+ *err = AVDTP_UNSUPPORTED_CONFIGURATION;
+ *category = 0x00;
+ 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 mpeg_getcap_ind(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ GSList **caps, uint8_t *err, void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct avdtp_service_capability *media_transport, *media_codec;
+ struct mpeg_codec_cap mpeg_cap;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Get_Capability_Ind", sep);
+ else
+ debug("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);
+
+ memset(&mpeg_cap, 0, sizeof(struct mpeg_codec_cap));
+
+ mpeg_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+ mpeg_cap.cap.media_codec_type = A2DP_CODEC_MPEG12;
+
+ mpeg_cap.frequency = ( MPEG_SAMPLING_FREQ_48000 |
+ MPEG_SAMPLING_FREQ_44100 |
+ MPEG_SAMPLING_FREQ_32000 |
+ MPEG_SAMPLING_FREQ_24000 |
+ MPEG_SAMPLING_FREQ_22050 |
+ MPEG_SAMPLING_FREQ_16000 );
+
+ mpeg_cap.channel_mode = ( MPEG_CHANNEL_MODE_JOINT_STEREO |
+ MPEG_CHANNEL_MODE_STEREO |
+ MPEG_CHANNEL_MODE_DUAL_CHANNEL |
+ MPEG_CHANNEL_MODE_MONO );
+
+ mpeg_cap.layer = ( MPEG_LAYER_MP3 | MPEG_LAYER_MP2 | MPEG_LAYER_MP1 );
+
+ mpeg_cap.bitrate = 0xFFFF;
+
+ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &mpeg_cap,
+ sizeof(mpeg_cap));
+
+ *caps = g_slist_append(*caps, media_codec);
+
+ 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)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_setup *setup;
+ struct audio_device *dev;
+ int ret;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Set_Configuration_Cfm", sep);
+ else
+ debug("Source %p: Set_Configuration_Cfm", sep);
+
+ setup = find_setup_by_session(session);
+
+ if (err) {
+ if (setup) {
+ setup->err = err;
+ finalize_config(setup);
+ }
+ return;
+ }
+
+ avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
+ a2dp_sep->stream = stream;
+
+ if (!setup)
+ return;
+
+ dev = a2dp_get_dev(session);
+
+ /* Notify sink.c of the new stream */
+ sink_new_stream(dev, session, setup->stream);
+
+ ret = avdtp_open(session, stream);
+ if (ret < 0) {
+ error("Error on avdtp_open %s (%d)", strerror(-ret),
+ -ret);
+ setup->stream = NULL;
+ finalize_config_errno(setup, ret);
+ }
+}
+
+static gboolean getconf_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+ uint8_t *err, void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Get_Configuration_Ind");
+ else
+ debug("Source %p: Get_Configuration_Ind");
+ return TRUE;
+}
+
+static void getconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, struct avdtp_error *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Set_Configuration_Cfm", sep);
+ else
+ debug("Source %p: Set_Configuration_Cfm", sep);
+}
+
+static gboolean open_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Open_Ind", sep);
+ else
+ debug("Source %p: Open_Ind", sep);
+ return TRUE;
+}
+
+static void open_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, struct avdtp_error *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_setup *setup;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Open_Cfm", sep);
+ else
+ debug("Source %p: Open_Cfm", sep);
+
+ setup = find_setup_by_session(session);
+ if (!setup)
+ return;
+
+ if (setup->canceled) {
+ if (!err)
+ avdtp_close(session, stream);
+ setup_unref(setup);
+ return;
+ }
+
+ if (setup->reconfigure)
+ setup->reconfigure = FALSE;
+
+ if (err) {
+ setup->stream = NULL;
+ setup->err = err;
+ finalize_config(setup);
+ }
+ else
+ finalize_config_errno(setup, 0);
+}
+
+static gboolean suspend_timeout(struct a2dp_sep *sep)
+{
+ if (avdtp_suspend(sep->session, sep->stream) == 0)
+ sep->suspending = TRUE;
+
+ sep->suspend_timer = 0;
+
+ avdtp_unref(sep->session);
+ sep->session = NULL;
+
+ return FALSE;
+}
+
+static gboolean start_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Start_Ind", sep);
+ else
+ debug("Source %p: Start_Ind", sep);
+
+ if (!a2dp_sep->locked) {
+ a2dp_sep->session = avdtp_ref(session);
+ a2dp_sep->suspend_timer = g_timeout_add(SUSPEND_TIMEOUT,
+ (GSourceFunc) suspend_timeout,
+ a2dp_sep);
+ }
+
+ return TRUE;
+}
+
+static void start_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, struct avdtp_error *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_setup *setup;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Start_Cfm", sep);
+ else
+ debug("Source %p: Start_Cfm", sep);
+
+ setup = find_setup_by_session(session);
+ if (!setup)
+ return;
+
+ if (setup->canceled) {
+ if (!err)
+ avdtp_close(session, stream);
+ setup_unref(setup);
+ return;
+ }
+
+ if (err) {
+ setup->stream = NULL;
+ setup->err = err;
+ finalize_resume(setup);
+ }
+ else
+ finalize_resume_errno(setup, 0);
+}
+
+static gboolean suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Suspend_Ind", sep);
+ else
+ debug("Source %p: Suspend_Ind", sep);
+
+ if (a2dp_sep->suspend_timer) {
+ g_source_remove(a2dp_sep->suspend_timer);
+ a2dp_sep->suspend_timer = 0;
+ avdtp_unref(a2dp_sep->session);
+ a2dp_sep->session = NULL;
+ }
+
+ return TRUE;
+}
+
+static void suspend_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, struct avdtp_error *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_setup *setup;
+ gboolean start;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Suspend_Cfm", sep);
+ else
+ debug("Source %p: Suspend_Cfm", sep);
+
+ a2dp_sep->suspending = FALSE;
+
+ setup = find_setup_by_session(session);
+ if (!setup)
+ return;
+
+ start = setup->start;
+ setup->start = FALSE;
+
+ if (err) {
+ setup->stream = NULL;
+ setup->err = err;
+ finalize_suspend(setup);
+ }
+ else
+ finalize_suspend_errno(setup, 0);
+
+ if (!start)
+ return;
+
+ if (err) {
+ setup->err = err;
+ finalize_suspend(setup);
+ } else if (avdtp_start(session, a2dp_sep->stream) < 0) {
+ struct avdtp_error start_err;
+ error("avdtp_start failed");
+ avdtp_error_init(&start_err, AVDTP_ERROR_ERRNO, EIO);
+ setup->err = err;
+ finalize_suspend(setup);
+ }
+}
+
+static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Close_Ind", sep);
+ else
+ debug("Source %p: Close_Ind", sep);
+
+ return TRUE;
+}
+
+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;
+ 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;
+ }
+
+ 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");
+ finalize_config_errno(setup, posix_err);
+ }
+
+ posix_err = avdtp_set_configuration(setup->session, rsep, lsep,
+ setup->client_caps,
+ &setup->stream);
+ if (posix_err < 0) {
+ error("avdtp_set_configuration: %s",
+ strerror(-posix_err));
+ finalize_config_errno(setup, posix_err);
+ }
+
+ return FALSE;
+}
+
+static void close_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, struct avdtp_error *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_setup *setup;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Close_Cfm", sep);
+ else
+ debug("Source %p: Close_Cfm", sep);
+
+ setup = find_setup_by_session(session);
+ if (!setup)
+ return;
+
+ if (setup->canceled) {
+ setup_unref(setup);
+ return;
+ }
+
+ if (err) {
+ setup->stream = NULL;
+ setup->err = err;
+ finalize_config(setup);
+ return;
+ }
+
+ if (setup->reconfigure)
+ g_timeout_add(RECONFIGURE_TIMEOUT, a2dp_reconfigure, setup);
+}
+
+static gboolean abort_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Abort_Ind", sep);
+ else
+ debug("Source %p: Abort_Ind", sep);
+
+ a2dp_sep->stream = NULL;
+
+ return TRUE;
+}
+
+static void abort_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, struct avdtp_error *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_setup *setup;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: Abort_Cfm", sep);
+ else
+ debug("Source %p: Abort_Cfm", sep);
+
+ setup = find_setup_by_session(session);
+ if (!setup)
+ return;
+
+ setup_unref(setup);
+}
+
+static gboolean reconf_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+ uint8_t *err, void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: ReConfigure_Ind", sep);
+ else
+ debug("Source %p: ReConfigure_Ind", sep);
+ return TRUE;
+}
+
+static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, struct avdtp_error *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_setup *setup;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: ReConfigure_Cfm", sep);
+ else
+ debug("Source %p: ReConfigure_Cfm", sep);
+
+ setup = find_setup_by_session(session);
+ if (!setup)
+ return;
+
+ if (setup->canceled) {
+ if (!err)
+ avdtp_close(session, stream);
+ setup_unref(setup);
+ return;
+ }
+
+ if (err) {
+ setup->stream = NULL;
+ setup->err = err;
+ finalize_config(setup);
+ }
+ else
+ finalize_config_errno(setup, 0);
+}
+
+static struct avdtp_sep_cfm cfm = {
+ .set_configuration = setconf_cfm,
+ .get_configuration = getconf_cfm,
+ .open = open_cfm,
+ .start = start_cfm,
+ .suspend = suspend_cfm,
+ .close = close_cfm,
+ .abort = abort_cfm,
+ .reconfigure = reconf_cfm
+};
+
+static struct avdtp_sep_ind sbc_ind = {
+ .get_capability = sbc_getcap_ind,
+ .set_configuration = sbc_setconf_ind,
+ .get_configuration = getconf_ind,
+ .open = open_ind,
+ .start = start_ind,
+ .suspend = suspend_ind,
+ .close = close_ind,
+ .abort = abort_ind,
+ .reconfigure = reconf_ind
+};
+
+static struct avdtp_sep_ind mpeg_ind = {
+ .get_capability = mpeg_getcap_ind,
+ .set_configuration = mpeg_setconf_ind,
+ .get_configuration = getconf_ind,
+ .open = open_ind,
+ .start = start_ind,
+ .suspend = suspend_ind,
+ .close = close_ind,
+ .abort = abort_ind,
+ .reconfigure = reconf_ind
+};
+
+static sdp_record_t *a2dp_source_record()
+{
+ sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+ uuid_t root_uuid, l2cap, avdtp, a2src;
+ sdp_profile_desc_t profile[1];
+ sdp_list_t *aproto, *proto[2];
+ sdp_record_t *record;
+ sdp_data_t *psm, *version, *features;
+ uint16_t lp = AVDTP_UUID, ver = 0x0100, feat = 0x000F;
+
+ record = sdp_record_alloc();
+ if (!record)
+ return NULL;
+
+ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+ root = sdp_list_append(0, &root_uuid);
+ sdp_set_browse_groups(record, root);
+
+ sdp_uuid16_create(&a2src, AUDIO_SOURCE_SVCLASS_ID);
+ svclass_id = sdp_list_append(0, &a2src);
+ sdp_set_service_classes(record, svclass_id);
+
+ sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID);
+ profile[0].version = 0x0100;
+ pfseq = sdp_list_append(0, &profile[0]);
+ sdp_set_profile_descs(record, pfseq);
+
+ sdp_uuid16_create(&l2cap, L2CAP_UUID);
+ proto[0] = sdp_list_append(0, &l2cap);
+ psm = sdp_data_alloc(SDP_UINT16, &lp);
+ proto[0] = sdp_list_append(proto[0], psm);
+ apseq = sdp_list_append(0, proto[0]);
+
+ sdp_uuid16_create(&avdtp, AVDTP_UUID);
+ proto[1] = sdp_list_append(0, &avdtp);
+ version = sdp_data_alloc(SDP_UINT16, &ver);
+ proto[1] = sdp_list_append(proto[1], version);
+ apseq = sdp_list_append(apseq, proto[1]);
+
+ aproto = sdp_list_append(0, apseq);
+ sdp_set_access_protos(record, aproto);
+
+ features = sdp_data_alloc(SDP_UINT16, &feat);
+ sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+ sdp_set_info_attr(record, "Audio Source", 0, 0);
+
+ free(psm);
+ free(version);
+ sdp_list_free(proto[0], 0);
+ sdp_list_free(proto[1], 0);
+ sdp_list_free(apseq, 0);
+ sdp_list_free(pfseq, 0);
+ sdp_list_free(aproto, 0);
+ sdp_list_free(root, 0);
+ sdp_list_free(svclass_id, 0);
+
+ return record;
+}
+
+static sdp_record_t *a2dp_sink_record()
+{
+ return NULL;
+}
+
+static struct a2dp_sep *a2dp_add_sep(DBusConnection *conn, uint8_t type,
+ uint8_t codec)
+{
+ struct a2dp_sep *sep;
+ GSList **l;
+ sdp_record_t *(*create_record)(void);
+ 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(type, AVDTP_MEDIA_TYPE_AUDIO, codec,
+ ind, &cfm, sep);
+ if (sep->sep == NULL) {
+ g_free(sep);
+ return NULL;
+ }
+
+ sep->codec = codec;
+ sep->type = type;
+
+ if (type == AVDTP_SEP_TYPE_SOURCE) {
+ l = &sources;
+ create_record = a2dp_source_record;
+ record_id = &source_record_id;
+ } else {
+ l = &sinks;
+ create_record = a2dp_sink_record;
+ record_id = &sink_record_id;
+ }
+
+ if (*record_id != 0)
+ goto add;
+
+ record = create_record();
+ if (!record) {
+ error("Unable to allocate new service record");
+ avdtp_unregister_sep(sep->sep);
+ g_free(sep);
+ return NULL;
+ }
+
+ if (add_record_to_server(BDADDR_ANY, 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;
+}
+
+int a2dp_init(DBusConnection *conn, GKeyFile *config)
+{
+ int sbc_srcs = 1, sbc_sinks = 0;
+ int mpeg12_srcs = 0, mpeg12_sinks = 0;
+ gboolean source = TRUE, sink = TRUE;
+ char *str;
+ GError *err = NULL;
+ int i;
+
+ if (!config)
+ goto proceed;
+
+ str = g_key_file_get_string(config, "General", "Disable", &err);
+
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else {
+ if (strstr(str, "Sink"))
+ source = FALSE;
+ if (strstr(str, "Source"))
+ sink = FALSE;
+ g_free(str);
+ }
+
+ str = g_key_file_get_string(config, "A2DP", "SBCSources", &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else {
+ sbc_srcs = atoi(str);
+ g_free(str);
+ }
+
+ str = g_key_file_get_string(config, "A2DP", "MPEG12Sources", &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else {
+ mpeg12_srcs = atoi(str);
+ g_free(str);
+ }
+
+ str = g_key_file_get_string(config, "A2DP", "SBCSinks", &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else {
+ sbc_sinks = atoi(str);
+ g_free(str);
+ }
+
+ str = g_key_file_get_string(config, "A2DP", "MPEG12Sinks", &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else {
+ mpeg12_sinks = atoi(str);
+ g_free(str);
+ }
+
+proceed:
+ connection = dbus_connection_ref(conn);
+
+ avdtp_init(config);
+
+ if (source) {
+ for (i = 0; i < sbc_srcs; i++)
+ a2dp_add_sep(conn, AVDTP_SEP_TYPE_SOURCE,
+ A2DP_CODEC_SBC);
+
+ for (i = 0; i < mpeg12_srcs; i++)
+ a2dp_add_sep(conn, AVDTP_SEP_TYPE_SOURCE,
+ A2DP_CODEC_MPEG12);
+ }
+
+ if (sink) {
+ for (i = 0; i < sbc_sinks; i++)
+ a2dp_add_sep(conn, AVDTP_SEP_TYPE_SINK,
+ A2DP_CODEC_SBC);
+
+ for (i = 0; i < mpeg12_sinks; i++)
+ a2dp_add_sep(conn, AVDTP_SEP_TYPE_SINK,
+ A2DP_CODEC_MPEG12);
+ }
+
+ return 0;
+}
+
+static void a2dp_unregister_sep(struct a2dp_sep *sep)
+{
+ avdtp_unregister_sep(sep->sep);
+ g_free(sep);
+}
+
+void a2dp_exit()
+{
+ g_slist_foreach(sinks, (GFunc) a2dp_unregister_sep, NULL);
+ g_slist_free(sinks);
+ sinks = NULL;
+
+ g_slist_foreach(sources, (GFunc) a2dp_unregister_sep, NULL);
+ g_slist_free(sources);
+ sources = NULL;
+
+ if (source_record_id) {
+ remove_record_from_server(source_record_id);
+ source_record_id = 0;
+ }
+
+ if (sink_record_id) {
+ remove_record_from_server(sink_record_id);
+ sink_record_id = 0;
+ }
+
+ dbus_connection_unref(connection);
+}
+
+gboolean a2dp_source_cancel(struct audio_device *dev, unsigned int id)
+{
+ struct a2dp_setup_cb *cb_data;
+ struct a2dp_setup *setup;
+ GSList *l;
+
+ setup = find_setup_by_dev(dev);
+ if (!setup)
+ return FALSE;
+
+ for (cb_data = NULL, l = setup->cb; l != NULL; l = g_slist_next(l)) {
+ struct a2dp_setup_cb *cb = l->data;
+
+ if (cb->id == id) {
+ cb_data = cb;
+ break;
+ }
+ }
+
+ if (!cb_data)
+ return FALSE;
+
+ setup->cb = g_slist_remove(setup->cb, cb_data);
+ g_free(cb_data);
+
+ if (!setup->cb) {
+ setup->canceled = TRUE;
+ setup->sep = NULL;
+ }
+
+ return TRUE;
+}
+
+unsigned int a2dp_source_config(struct avdtp *session, a2dp_config_cb_t cb,
+ GSList *caps, void *user_data)
+{
+ struct a2dp_setup_cb *cb_data;
+ GSList *l;
+ struct a2dp_setup *setup;
+ struct a2dp_sep *sep = NULL, *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;
+
+ for (l = 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)
+ return 0;
+
+ for (l = sources; l != NULL; l = l->next) {
+ tmp = l->data;
+
+ if (tmp->locked)
+ continue;
+
+ if (tmp->codec != codec_cap->media_codec_type)
+ continue;
+
+ if (!tmp->stream || avdtp_has_stream(session, tmp->stream)) {
+ sep = tmp;
+ break;
+ }
+ }
+
+ if (!sep) {
+ error("a2dp_source_cfg: no available SEP found");
+ return 0;
+ }
+
+ debug("a2dp_source_config: selected SEP %p", sep->sep);
+
+ cb_data = g_new0(struct a2dp_setup_cb, 1);
+ cb_data->config_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);
+ setups = g_slist_append(setups, setup);
+ }
+
+ setup_ref(setup);
+ setup->cb = g_slist_append(setup->cb, cb_data);
+ setup->sep = sep;
+ setup->stream = sep->stream;
+ setup->client_caps = caps;
+
+ switch (avdtp_sep_get_state(sep->sep)) {
+ case AVDTP_STATE_IDLE:
+ for (l = sources; l != NULL; l = l->next) {
+ tmp = l->data;
+
+ if (avdtp_has_stream(session, tmp->stream))
+ break;
+ }
+
+ if (l != NULL) {
+ setup->reconfigure = TRUE;
+ if (avdtp_close(session, tmp->stream) < 0) {
+ error("avdtp_close failed");
+ goto failed;
+ }
+ break;
+ }
+
+ if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SINK,
+ codec_cap->media_type,
+ codec_cap->media_codec_type,
+ &lsep, &rsep) < 0) {
+ error("No matching ACP and INT SEPs found");
+ goto failed;
+ }
+
+ posix_err = avdtp_set_configuration(session, rsep, lsep,
+ caps, &setup->stream);
+ if (posix_err < 0) {
+ error("avdtp_set_configuration: %s",
+ strerror(-posix_err));
+ goto failed;
+ }
+ break;
+ case AVDTP_STATE_OPEN:
+ case AVDTP_STATE_STREAMING:
+ if (avdtp_stream_has_capabilities(setup->stream, caps))
+ g_idle_add((GSourceFunc) finalize_config, setup);
+ else if (!setup->reconfigure) {
+ setup->reconfigure = TRUE;
+ if (avdtp_close(session, sep->stream) < 0) {
+ error("avdtp_close failed");
+ goto failed;
+ }
+ }
+ break;
+ default:
+ error("SEP in bad state for requesting a new stream");
+ goto failed;
+ }
+
+ return cb_data->id;
+
+failed:
+ setup_unref(setup);
+ cb_id--;
+ return 0;
+}
+
+unsigned int a2dp_source_resume(struct avdtp *session, struct a2dp_sep *sep,
+ a2dp_stream_cb_t cb, void *user_data)
+{
+ struct a2dp_setup_cb *cb_data;
+ struct a2dp_setup *setup;
+
+ cb_data = g_new0(struct a2dp_setup_cb, 1);
+ cb_data->resume_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);
+ setups = g_slist_append(setups, setup);
+ }
+
+ setup_ref(setup);
+ setup->cb = g_slist_append(setup->cb, cb_data);
+ setup->sep = sep;
+ setup->stream = sep->stream;
+
+ switch (avdtp_sep_get_state(sep->sep)) {
+ case AVDTP_STATE_IDLE:
+ goto failed;
+ break;
+ case AVDTP_STATE_OPEN:
+ if (avdtp_start(session, sep->stream) < 0) {
+ error("avdtp_start failed");
+ goto failed;
+ }
+ break;
+ case AVDTP_STATE_STREAMING:
+ if (!sep->suspending && sep->suspend_timer) {
+ g_source_remove(sep->suspend_timer);
+ sep->suspend_timer = 0;
+ avdtp_unref(sep->session);
+ sep->session = NULL;
+ }
+ if (sep->suspending)
+ setup->start = TRUE;
+ else
+ g_idle_add((GSourceFunc) finalize_resume, setup);
+ break;
+ default:
+ error("SEP in bad state");
+ goto failed;
+ }
+
+ return cb_data->id;
+
+failed:
+ setup_unref(setup);
+ cb_id--;
+ return 0;
+}
+
+unsigned int a2dp_source_suspend(struct avdtp *session, struct a2dp_sep *sep,
+ a2dp_stream_cb_t cb, void *user_data)
+{
+ struct a2dp_setup_cb *cb_data;
+ struct a2dp_setup *setup;
+
+ cb_data = g_new0(struct a2dp_setup_cb, 1);
+ cb_data->suspend_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);
+ setups = g_slist_append(setups, setup);
+ }
+
+ setup_ref(setup);
+ setup->cb = g_slist_append(setup->cb, cb_data);
+ setup->sep = sep;
+ setup->stream = sep->stream;
+
+ switch (avdtp_sep_get_state(sep->sep)) {
+ case AVDTP_STATE_IDLE:
+ error("a2dp_source_suspend: no stream to suspend");
+ goto failed;
+ break;
+ case AVDTP_STATE_OPEN:
+ g_idle_add((GSourceFunc) finalize_suspend, setup);
+ break;
+ case AVDTP_STATE_STREAMING:
+ if (avdtp_suspend(session, sep->stream) < 0) {
+ error("avdtp_suspend failed");
+ goto failed;
+ }
+ break;
+ default:
+ error("SEP in bad state for resume");
+ goto failed;
+ }
+
+ return cb_data->id;
+
+failed:
+ setup_unref(setup);
+ cb_id--;
+ return 0;
+}
+
+gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
+{
+ if (sep->locked)
+ return FALSE;
+
+ debug("SEP %p locked", sep->sep);
+ sep->locked = TRUE;
+
+ return TRUE;
+}
+
+gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session)
+{
+ avdtp_state_t state;
+
+ state = avdtp_sep_get_state(sep->sep);
+
+ sep->locked = FALSE;
+
+ debug("SEP %p unlocked", sep->sep);
+
+ if (!sep->stream || state == AVDTP_STATE_IDLE)
+ return TRUE;
+
+ switch (state) {
+ case AVDTP_STATE_OPEN:
+ /* Set timer here */
+ break;
+ case AVDTP_STATE_STREAMING:
+ if (avdtp_suspend(session, sep->stream) == 0)
+ sep->suspending = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
diff --git a/audio/a2dp.h b/audio/a2dp.h
new file mode 100644
index 00000000..6ec3ebc8
--- /dev/null
+++ b/audio/a2dp.h
@@ -0,0 +1,144 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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
+
+struct sbc_codec_cap {
+ struct avdtp_media_codec_capability cap;
+ 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));
+
+struct mpeg_codec_cap {
+ struct avdtp_media_codec_capability cap;
+ 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));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct sbc_codec_cap {
+ struct avdtp_media_codec_capability cap;
+ 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));
+
+struct mpeg_codec_cap {
+ struct avdtp_media_codec_capability cap;
+ 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));
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct a2dp_sep;
+
+typedef void (*a2dp_config_cb_t) (struct avdtp *session, struct a2dp_sep *sep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err,
+ void *user_data);
+typedef void (*a2dp_stream_cb_t) (struct avdtp *session,
+ struct avdtp_error *err,
+ void *user_data);
+
+int a2dp_init(DBusConnection *conn, GKeyFile *config);
+void a2dp_exit(void);
+
+unsigned int a2dp_source_config(struct avdtp *session, a2dp_config_cb_t cb,
+ GSList *caps, void *user_data);
+unsigned int a2dp_source_resume(struct avdtp *session, struct a2dp_sep *sep,
+ a2dp_stream_cb_t cb, void *user_data);
+unsigned int a2dp_source_suspend(struct avdtp *session, struct a2dp_sep *sep,
+ a2dp_stream_cb_t cb, void *user_data);
+gboolean a2dp_source_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);
diff --git a/audio/asound.conf b/audio/asound.conf
new file mode 100644
index 00000000..ec97d46f
--- /dev/null
+++ b/audio/asound.conf
@@ -0,0 +1,7 @@
+pcm.headset {
+ type bluetooth
+}
+
+ctl.headset {
+ type bluetooth
+}
diff --git a/audio/audio-api.txt b/audio/audio-api.txt
new file mode 100644
index 00000000..cba20699
--- /dev/null
+++ b/audio/audio-api.txt
@@ -0,0 +1,311 @@
+Bluetooth audio service API description
+***************************************
+
+Copyright (C) 2004-2007 Marcel Holtmann <marcel@holtmann.org>
+Copyright (C) 2005-2007 Johan Hedberg <johan.hedberg@nokia.com>
+Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com>
+
+
+org.bluez.audio.Manager interface
+=================================
+
+This interface is for managing remote audio devices. It provides methods for
+creating and removing D-Bus objects representing remote audio devices. These
+objects implement one or more of the other interfaces listed in this document.
+
+Object path /org/bluez/audio
+
+Methods
+ string CreateDevice(string address) [experimental]
+
+ Creates a new audio device object. If not yet done,
+ this method will perform a SDP query on the remote
+ device and return first when the query is complete,
+ so be sure to call this method asynchronously.
+
+ The return parameter is the object path of the newly
+ created object.
+
+ void RemoveDevice(string path) [experimental]
+
+ Removes a device from the device tree. If there are
+ any connections open to the device they will be closed.
+
+ array{string} ListDevices() [experimental]
+
+ Retuns an array of strings indicating the object paths
+ of available devices.
+
+ string DefaultDevice()
+
+ Returns the object path for the default device.
+
+ void ChangeDefaultDevice(string path)
+
+ Changes the default device.
+
+ array{string} ListHeadsets()
+
+ Returns list of headset objects that are configured.
+
+ string FindDeviceByAddress(string address) [experimental]
+
+ Searches the list of available devices and returns the
+ object path of the first device which matchess address.
+ If no device is found returns a DoesNotExist error.
+
+ string DefaultHeadset()
+
+ Returns the object path for the default headset device.
+
+ void ChangeDefaultHeadset(string path)
+
+ Changes the default headset.
+
+ string CreateHeadset(string address)
+
+ Create a new headset device and returns its object path
+ on return.
+
+ void RemoveHeadset(string path)
+
+ Removes a headset object and all information
+ related to it.
+
+Signals
+ void DeviceCreated(string path) [experimental]
+
+ Sent when a new device object has been created.
+
+ void DeviceRemoved(string path) [experimental]
+
+ Sent when a device object has been removed.
+
+ void HeadsetCreated(string path)
+
+ Sent when a new headset object has been created.
+
+ void HeadsetRemoved(string path)
+
+ Sent when a headset object has been removed.
+
+ void DefaultHeadsetChanged(string path)
+
+ Sent when the default headset has changed.
+
+
+org.bluez.audio.Device interface
+================================
+
+This interface is implemented by all remote device objects.
+
+Object path(s) /org/bluez/audio/device*
+
+Methods string GetAddress() [experimental]
+
+ Returns the Bluetooth address of the remote device.
+
+ string GetAdapter() [experimental]
+
+ Returns the address of the local adapter that the
+ device is associated with.
+
+ string GetName() [experimental]
+
+ Returns a friendly name for the device.
+
+ array{string} GetConnectedInterfaces() [experimental]
+
+ Returns a string list of interfaces that are in a
+ connected state.
+
+
+org.bluez.audio.Headset interface
+=================================
+
+This interface provides access to headsets that implement the HSP and/or HFP
+profiles.
+
+Object path(s) /org/bluez/audio/device*
+
+Methods void Connect()
+
+ Connect to the HSP/HFP service on the remote device.
+
+ void Disconnect()
+
+ Disconnect from the HSP/HFP service on the remote
+ device.
+
+ boolean IsConnected()
+
+ Returns TRUE if there is a active connection to the
+ HSP/HFP connection on the remote device.
+
+ void IndicateCall()
+
+ Indicate an incoming call on the headset
+ connected to the stream. Will continue to
+ ring the headset about every 3 seconds.
+
+ void CancelCall()
+
+ Cancel the incoming call indication.
+
+ void Play()
+
+ Open the audio connection to the headset.
+
+ void Stop()
+
+ Close the audio connection.
+
+ boolean IsPlaying()
+
+ Returns true if an audio connection to the headset
+ is active.
+
+ uint16 GetSpeakerGain()
+
+ Returns the current speaker gain if available,
+ otherwise returns the error NotAvailable.
+
+ uint16 GetMicrophoneGain()
+
+ Returns the current microphone gain if available,
+ otherwise returns the error NotAvailable.
+
+ void SetSpeakerGain(uint16 gain)
+
+ Changes the current speaker gain if possible.
+
+ void SetMicrophoneGain(uint16 gain)
+
+ Changes the current speaker gain if possible.
+
+ void SetupCall(string value) [experimental]
+
+ Sets up an call with the connected HFP. The value can
+ be "incoming", "outgoing" or "remote" to indicate
+ incoming call, outgoing call and remote party alerted
+ respectively.
+
+ void IdentifyCall(string phone_number, int32 type) [experimental]
+
+ Enables a called subscriber to get the calling
+ line identity (CLI) of the calling party when
+ receiving a call. The value of type shud be
+ the same as provided by the GSM stack.
+
+Signals void AnswerRequested()
+
+ Sent when the answer button is pressed on the headset
+
+ void Connected()
+
+ Sent when the device has been connected to.
+
+ void Disconnected()
+
+ Sent when the device has been disconnected from.
+
+ void Stopped()
+
+ Sent when the audio connection is closed
+
+ void Playing()
+
+ Sent when the audio connection is opened
+
+ void SpeakerGainChanged(uint16 gain)
+
+ The speaker gain changed.
+
+ void MicrophoneGainChanged(uint16 gain)
+
+ The microphone gain changed.
+
+ void CallTerminated()
+
+ Sent when an ongoing call is terminated.
+
+
+org.bluez.audio.Gateway interface
+=================================
+
+[not yet implemented]
+
+This interface is available for remote devices which can function in the Audio
+Gateway role of the HSP and/or HFP profiles.
+
+Object path(s) /org/bluez/audio/device*
+
+
+org.bluez.audio.Sink interface
+==============================
+
+This interface is available for remote devices which contain a A2DP Sink.
+
+Object path(s) /org/bluez/audio/device*
+
+Methods void Connect()
+
+ Connect and setup a stream to a A2DP sink on the
+ remote device.
+
+ void Disconnect()
+
+ Disconnect from the remote device.
+
+ boolean IsConnected()
+
+ Returns TRUE if a stream is setup to a A2DP sink on
+ the remote device.
+
+Signals void Connected()
+
+ Sent when a successful connection has been made to the
+ remote A2DP Sink
+
+ void Disconnected()
+
+ Sent when the device has been disconnected from.
+
+ void Playing()
+
+ Sent when a stream with remote device is started.
+
+ void Stopped()
+
+ Sent when a stream with remote device is suspended.
+
+
+org.bluez.audio.Source interface
+================================
+
+[not yet implemented]
+
+This interface is available for remote devices which implement a A2DP source.
+
+Object path(s) /org/bluez/audio/device*
+
+
+org.bluez.audio.Control interface
+=================================
+
+This interface is available for remote devices which implement support for a
+AVRCP controller.
+
+Object path(s) /org/bluez/audio/device*
+
+Methods boolean IsConnected()
+
+ Returns TRUE if AVRCP is connected.
+
+Signals void Connected()
+
+ Sent when a successful AVRCP connection has been made.
+
+ void Disconnected()
+
+ Sent when the AVRCP connection has been disconnected.
diff --git a/audio/audio.conf b/audio/audio.conf
new file mode 100644
index 00000000..7f760ea6
--- /dev/null
+++ b/audio/audio.conf
@@ -0,0 +1,41 @@
+# Configuration file for the audio service
+
+# This section contains options which are not specific to any
+# particular interface
+[General]
+
+# Switch to master role for incoming connections (defaults to true)
+#Master=true
+
+# If we want to disable support for specific services
+# Defaults to supporting all implemented services
+#Disable=Control,Source
+
+# SCO routing. Either PCM or HCI (in which case audio is routed to/from ALSA)
+# Defaults to HCI
+#SCORouting=PCM
+
+# Headset interface specific options (i.e. options which affect how the audio
+# service interacts with remote headset devices)
+[Headset]
+
+# Set to true to support HFP (in addition to HSP only which is the default)
+# Defaults to false
+HFP=false
+
+# HFP Gateway features
+# Defaults to false
+3WayCalling=false
+EchoCancelNoiseCancel=false
+VoiceRecognition=false
+InBandRingtone=false
+VoiceTags=false
+RejectingCalls=false
+EnhancedCallStatus=false
+EnhancedCallControl=false
+ExtendedErrorResultCodes=false
+
+# Just an example of potential config options for the other interfaces
+#[A2DP]
+#SBCSources=1
+#MPEG12Sources=0
diff --git a/audio/audio.service b/audio/audio.service
new file mode 100644
index 00000000..1638cfff
--- /dev/null
+++ b/audio/audio.service
@@ -0,0 +1,5 @@
+[Bluetooth Service]
+Identifier=audio
+Name=Audio service
+Description=Bluetooth Audio service
+Autostart=false
diff --git a/audio/avdtp.c b/audio/avdtp.c
new file mode 100644
index 00000000..bda21ac5
--- /dev/null
+++ b/audio/avdtp.c
@@ -0,0 +1,2882 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <stdio.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "dbus-service.h"
+#include "logging.h"
+
+#include "device.h"
+#include "manager.h"
+#include "control.h"
+#include "avdtp.h"
+#include "glib-helper.h"
+
+#include <bluetooth/l2cap.h>
+
+#define AVDTP_PSM 25
+
+#define MAX_SEID 0x3E
+
+#define AVDTP_DISCOVER 0x01
+#define AVDTP_GET_CAPABILITIES 0x02
+#define AVDTP_SET_CONFIGURATION 0x03
+#define AVDTP_GET_CONFIGURATION 0x04
+#define AVDTP_RECONFIGURE 0x05
+#define AVDTP_OPEN 0x06
+#define AVDTP_START 0x07
+#define AVDTP_CLOSE 0x08
+#define AVDTP_SUSPEND 0x09
+#define AVDTP_ABORT 0x0A
+#define AVDTP_SECURITY_CONTROL 0x0B
+
+#define AVDTP_PKT_TYPE_SINGLE 0x00
+#define AVDTP_PKT_TYPE_START 0x01
+#define AVDTP_PKT_TYPE_CONTINUE 0x02
+#define AVDTP_PKT_TYPE_END 0x03
+
+#define AVDTP_MSG_TYPE_COMMAND 0x00
+#define AVDTP_MSG_TYPE_ACCEPT 0x02
+#define AVDTP_MSG_TYPE_REJECT 0x03
+
+#define REQ_TIMEOUT 4000
+#define DISCONNECT_TIMEOUT 5000
+#define STREAM_TIMEOUT 20000
+
+typedef enum {
+ AVDTP_SESSION_STATE_DISCONNECTED,
+ AVDTP_SESSION_STATE_CONNECTING,
+ AVDTP_SESSION_STATE_CONNECTED
+} avdtp_session_state_t;
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avdtp_header {
+ uint8_t message_type:2;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+ uint8_t signal_id:6;
+ uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct seid_info {
+ uint8_t rfa0:1;
+ uint8_t inuse:1;
+ uint8_t seid:6;
+ uint8_t rfa2:3;
+ uint8_t type:1;
+ uint8_t media_type:4;
+} __attribute__ ((packed));
+
+struct seid {
+ uint8_t rfa0:2;
+ uint8_t seid:6;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avdtp_header {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t message_type:2;
+ uint8_t rfa0:2;
+ uint8_t signal_id:6;
+} __attribute__ ((packed));
+
+struct seid_info {
+ uint8_t seid:6;
+ uint8_t inuse:1;
+ uint8_t rfa0:1;
+ uint8_t media_type:4;
+ uint8_t type:1;
+ uint8_t rfa2:3;
+} __attribute__ ((packed));
+
+struct seid {
+ uint8_t seid:6;
+ uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+/* packets */
+
+struct gen_req {
+ struct avdtp_header header;
+} __attribute__ ((packed));
+
+struct gen_resp {
+ struct avdtp_header header;
+} __attribute__ ((packed));
+
+struct discover_resp {
+ struct avdtp_header header;
+ struct seid_info seps[0];
+} __attribute__ ((packed));
+
+struct getcap_resp {
+ struct avdtp_header header;
+ uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct start_req {
+ struct avdtp_header header;
+ struct seid first_seid;
+ struct seid other_seids[0];
+} __attribute__ ((packed));
+
+struct suspend_req {
+ struct avdtp_header header;
+ struct seid first_seid;
+ struct seid other_seids[0];
+} __attribute__ ((packed));
+
+struct seid_rej {
+ struct avdtp_header header;
+ uint8_t error;
+} __attribute__ ((packed));
+
+struct conf_rej {
+ struct avdtp_header header;
+ uint8_t category;
+ uint8_t error;
+} __attribute__ ((packed));
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct seid_req {
+ struct avdtp_header header;
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+} __attribute__ ((packed));
+
+struct setconf_req {
+ struct avdtp_header header;
+
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+ uint8_t rfa1:2;
+ uint8_t int_seid:6;
+
+ uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct stream_rej {
+ struct avdtp_header header;
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+ uint8_t error;
+} __attribute__ ((packed));
+
+struct reconf_req {
+ struct avdtp_header header;
+
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+
+ uint8_t serv_cap;
+ uint8_t serv_cap_len;
+
+ uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct avdtp_general_rej {
+ uint8_t message_type:2;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+ uint8_t rfa0;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct seid_req {
+ struct avdtp_header header;
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct setconf_req {
+ struct avdtp_header header;
+
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+ uint8_t int_seid:6;
+ uint8_t rfa1:2;
+
+ uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct stream_rej {
+ struct avdtp_header header;
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+ uint8_t error;
+} __attribute__ ((packed));
+
+struct reconf_req {
+ struct avdtp_header header;
+
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+
+ uint8_t serv_cap;
+ uint8_t serv_cap_len;
+
+ uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct avdtp_general_rej {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t message_type:2;
+ uint8_t rfa0;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct pending_req {
+ struct avdtp_header *msg;
+ int msg_size;
+ struct avdtp_stream *stream; /* Set if the request targeted a stream */
+ guint timeout;
+};
+
+struct avdtp_remote_sep {
+ uint8_t seid;
+ uint8_t type;
+ uint8_t media_type;
+ struct avdtp_service_capability *codec;
+ GSList *caps; /* of type struct avdtp_service_capability */
+ struct avdtp_stream *stream;
+};
+
+struct avdtp_local_sep {
+ avdtp_state_t state;
+ struct avdtp_stream *stream;
+ struct seid_info info;
+ uint8_t codec;
+ GSList *caps;
+ struct avdtp_sep_ind *ind;
+ struct avdtp_sep_cfm *cfm;
+ void *user_data;
+};
+
+struct stream_callback {
+ avdtp_stream_state_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+struct avdtp_stream {
+ int sock;
+ uint16_t imtu;
+ uint16_t omtu;
+ struct avdtp *session;
+ struct avdtp_local_sep *lsep;
+ uint8_t rseid;
+ GSList *caps;
+ GSList *callbacks;
+ struct avdtp_service_capability *codec;
+ guint io; /* Transport GSource ID */
+ guint timer; /* Waiting for other side to close or open
+ the transport channel */
+ gboolean open_acp; /* If we are in ACT role for Open */
+ gboolean close_int; /* If we are in INT role for Close */
+ guint idle_timer;
+};
+
+/* Structure describing an AVDTP connection between two devices */
+struct avdtp {
+ int ref;
+ int free_lock;
+
+ bdaddr_t src;
+ bdaddr_t dst;
+
+ avdtp_session_state_t last_state;
+ avdtp_session_state_t state;
+
+ guint io;
+ int sock;
+
+ GSList *seps; /* Elements of type struct avdtp_remote_sep * */
+
+ GSList *streams; /* Elements of type struct avdtp_stream * */
+
+ GSList *req_queue; /* Elements of type struct pending_req * */
+ GSList *prio_queue; /* Same as req_queue but is processed before it */
+
+ struct avdtp_stream *pending_open;
+
+ uint16_t mtu;
+ char *buf;
+
+ avdtp_discover_cb_t discov_cb;
+ void *user_data;
+
+ struct pending_req *req;
+
+ guint dc_timer;
+
+ DBusPendingCall *pending_auth;
+};
+
+static uint8_t free_seid = 1;
+static GSList *local_seps = NULL;
+
+static GIOChannel *avdtp_server = NULL;
+
+static GSList *sessions = NULL;
+
+static int send_request(struct avdtp *session, gboolean priority,
+ struct avdtp_stream *stream, void *buffer, int size);
+static gboolean avdtp_parse_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct avdtp_header *header, int size);
+static gboolean avdtp_parse_rej(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct avdtp_header *header, int size);
+static int process_queue(struct avdtp *session);
+static void connection_lost(struct avdtp *session, int err);
+static void avdtp_sep_set_state(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ avdtp_state_t state);
+
+static const char *avdtp_statestr(avdtp_state_t state)
+{
+ switch (state) {
+ case AVDTP_STATE_IDLE:
+ return "IDLE";
+ case AVDTP_STATE_CONFIGURED:
+ return "CONFIGURED";
+ case AVDTP_STATE_OPEN:
+ return "OPEN";
+ case AVDTP_STATE_STREAMING:
+ return "STREAMING";
+ case AVDTP_STATE_CLOSING:
+ return "CLOSING";
+ case AVDTP_STATE_ABORTING:
+ return "ABORTING";
+ default:
+ return "<unknown state>";
+ }
+}
+
+static gboolean avdtp_send(struct avdtp *session, void *data, int len)
+{
+ int ret;
+
+ if (session->sock < 0) {
+ error("avdtp_send: session is closed");
+ return FALSE;
+ }
+
+ ret = send(session->sock, data, len, 0);
+
+ if (ret < 0)
+ ret = -errno;
+ else if (ret != len)
+ ret = -EIO;
+
+ if (ret < 0) {
+ error("avdtp_send: %s (%d)", strerror(-ret), -ret);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void pending_req_free(struct pending_req *req)
+{
+ if (req->timeout)
+ g_source_remove(req->timeout);
+ g_free(req->msg);
+ g_free(req);
+}
+
+static gboolean stream_close_timeout(gpointer user_data)
+{
+ struct avdtp_stream *stream = user_data;
+
+ debug("Timed out waiting for peer to close the transport channel");
+
+ stream->timer = 0;
+
+ close(stream->sock);
+
+ return FALSE;
+}
+
+static gboolean stream_open_timeout(gpointer user_data)
+{
+ struct avdtp_stream *stream = user_data;
+
+ debug("Timed out waiting for peer to open the transport channel");
+
+ stream->timer = 0;
+
+ stream->session->pending_open = NULL;
+
+ avdtp_abort(stream->session, stream);
+
+ return FALSE;
+}
+
+static gboolean disconnect_timeout(gpointer user_data)
+{
+ struct avdtp *session = user_data;
+
+ assert(session->ref == 1);
+
+ session->dc_timer = 0;
+
+ connection_lost(session, -ETIMEDOUT);
+
+ return FALSE;
+}
+
+static void remove_disconnect_timer(struct avdtp *session)
+{
+ g_source_remove(session->dc_timer);
+ session->dc_timer = 0;
+}
+
+static void set_disconnect_timer(struct avdtp *session)
+{
+ if (session->dc_timer)
+ remove_disconnect_timer(session);
+
+ session->dc_timer = g_timeout_add(DISCONNECT_TIMEOUT,
+ disconnect_timeout, session);
+}
+
+void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id)
+{
+ err->type = type;
+ switch (type) {
+ case AVDTP_ERROR_ERRNO:
+ err->err.posix_errno = id;
+ break;
+ case AVDTP_ERROR_ERROR_CODE:
+ err->err.error_code = id;
+ break;
+ }
+}
+
+avdtp_error_type_t avdtp_error_type(struct avdtp_error *err)
+{
+ return err->type;
+}
+
+int avdtp_error_error_code(struct avdtp_error *err)
+{
+ assert(err->type == AVDTP_ERROR_ERROR_CODE);
+ return err->err.error_code;
+}
+
+int avdtp_error_posix_errno(struct avdtp_error *err)
+{
+ assert(err->type == AVDTP_ERROR_ERRNO);
+ return err->err.posix_errno;
+}
+
+static struct avdtp_stream *find_stream_by_rseid(struct avdtp *session,
+ uint8_t rseid)
+{
+ GSList *l;
+
+ for (l = session->streams; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_stream *stream = l->data;
+
+ if (stream->rseid == rseid)
+ return stream;
+ }
+
+ return NULL;
+}
+
+static struct avdtp_remote_sep *find_remote_sep(GSList *seps, uint8_t seid)
+{
+ GSList *l;
+
+ for (l = seps; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_remote_sep *sep = l->data;
+
+ if (sep->seid == seid)
+ return sep;
+ }
+
+ return NULL;
+}
+
+static void stream_free(struct avdtp_stream *stream)
+{
+ struct avdtp_remote_sep *rsep;
+
+ stream->lsep->info.inuse = 0;
+ stream->lsep->stream = NULL;
+
+ rsep = find_remote_sep(stream->session->seps, stream->rseid);
+ if (rsep)
+ rsep->stream = NULL;
+
+ if (stream->timer)
+ g_source_remove(stream->timer);
+
+ if (stream->sock >= 0)
+ close(stream->sock);
+
+ if (stream->io)
+ g_source_remove(stream->io);
+
+ g_slist_foreach(stream->callbacks, (GFunc) g_free, NULL);
+ g_slist_free(stream->callbacks);
+
+ g_slist_foreach(stream->caps, (GFunc) g_free, NULL);
+ g_slist_free(stream->caps);
+
+ g_free(stream);
+}
+
+static gboolean stream_timeout(struct avdtp_stream *stream)
+{
+ struct avdtp *session = stream->session;
+
+ avdtp_close(session, stream);
+
+ stream->idle_timer = 0;
+
+ return FALSE;
+}
+
+static gboolean transport_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct avdtp_stream *stream = data;
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ if (stream->close_int && sep->cfm && sep->cfm->close)
+ sep->cfm->close(stream->session, sep, stream, NULL,
+ sep->user_data);
+
+ stream->io = 0;
+
+ avdtp_sep_set_state(stream->session, sep, AVDTP_STATE_IDLE);
+
+ return FALSE;
+}
+
+static void handle_transport_connect(struct avdtp *session, int sock,
+ uint16_t imtu, uint16_t omtu)
+{
+ struct avdtp_stream *stream = session->pending_open;
+ struct avdtp_local_sep *sep = stream->lsep;
+ GIOChannel *channel;
+
+ session->pending_open = NULL;
+
+ if (stream->timer) {
+ g_source_remove(stream->timer);
+ stream->timer = 0;
+ }
+
+ if (sock < 0) {
+ if (!stream->open_acp && sep->cfm && sep->cfm->open) {
+ struct avdtp_error err;
+ avdtp_error_init(&err, AVDTP_ERROR_ERRNO, EIO);
+ sep->cfm->open(session, sep, NULL, &err,
+ sep->user_data);
+ }
+ return;
+ }
+
+ stream->sock = sock;
+ stream->omtu = omtu;
+ stream->imtu = imtu;
+
+ if (!stream->open_acp && sep->cfm && sep->cfm->open)
+ sep->cfm->open(session, sep, stream, NULL, sep->user_data);
+
+ channel = g_io_channel_unix_new(stream->sock);
+
+ stream->io = g_io_add_watch(channel, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) transport_cb, stream);
+ g_io_channel_unref(channel);
+}
+
+static void avdtp_sep_set_state(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ avdtp_state_t state)
+{
+ struct avdtp_stream *stream = sep->stream;
+ avdtp_state_t old_state;
+ struct avdtp_error err, *err_ptr = NULL;
+
+ if (sep->state == state) {
+ avdtp_error_init(&err, AVDTP_ERROR_ERRNO, EIO);
+ debug("stream state change failed: %s", avdtp_strerror(&err));
+ err_ptr = &err;
+ } else {
+ err_ptr = NULL;
+ debug("stream state changed: %s -> %s",
+ avdtp_statestr(sep->state),
+ avdtp_statestr(state));
+ }
+
+ old_state = sep->state;
+ sep->state = state;
+
+ if (stream) {
+ GSList *l;
+ for (l = stream->callbacks; l != NULL; l = g_slist_next(l)) {
+ struct stream_callback *cb = l->data;
+ cb->cb(stream, old_state, state, err_ptr,
+ cb->user_data);
+ }
+ }
+
+ switch (state) {
+ case AVDTP_STATE_OPEN:
+ break;
+ case AVDTP_STATE_STREAMING:
+ case AVDTP_STATE_CLOSING:
+ case AVDTP_STATE_ABORTING:
+ if (stream->idle_timer) {
+ g_source_remove(stream->idle_timer);
+ stream->idle_timer = 0;
+ }
+ break;
+ case AVDTP_STATE_IDLE:
+ if (stream->idle_timer) {
+ g_source_remove(stream->idle_timer);
+ stream->idle_timer = 0;
+ }
+ session->streams = g_slist_remove(session->streams, stream);
+ if (session->pending_open == stream)
+ handle_transport_connect(session, -1, 0, 0);
+ if (session->req && session->req->stream == stream)
+ session->req->stream = NULL;
+ stream_free(stream);
+ if (session->ref == 1 && !session->streams)
+ set_disconnect_timer(session);
+ break;
+ default:
+ break;
+ }
+}
+
+static void finalize_discovery(struct avdtp *session, int err)
+{
+ struct avdtp_error avdtp_err;
+
+ avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err);
+
+ if (!session->discov_cb)
+ return;
+
+ session->discov_cb(session, session->seps,
+ err ? &avdtp_err : NULL,
+ session->user_data);
+
+ session->discov_cb = NULL;
+ session->user_data = NULL;
+}
+
+static void release_stream(struct avdtp_stream *stream, struct avdtp *session)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ if (sep->cfm && sep->cfm->abort)
+ sep->cfm->abort(session, sep, stream, NULL, sep->user_data);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
+}
+
+static void connection_lost(struct avdtp *session, int err)
+{
+ struct audio_device *dev;
+
+ dev = manager_find_device(&session->dst, AUDIO_CONTROL_INTERFACE,
+ FALSE);
+ if (dev)
+ avrcp_disconnect(dev);
+
+ if (session->state == AVDTP_SESSION_STATE_CONNECTED) {
+ char address[18];
+
+ ba2str(&session->dst, address);
+ debug("Disconnected from %s", address);
+ }
+
+ session->free_lock = 1;
+
+ finalize_discovery(session, err);
+
+ g_slist_foreach(session->streams, (GFunc) release_stream, session);
+ session->streams = NULL;
+
+ session->free_lock = 0;
+
+ if (session->sock >= 0) {
+ close(session->sock);
+ session->sock = -1;
+ }
+
+ session->state = AVDTP_SESSION_STATE_DISCONNECTED;
+
+ if (session->io) {
+ g_source_remove(session->io);
+ session->io = 0;
+ }
+
+ if (session->ref != 1)
+ error("connection_lost: ref count not 1 after all callbacks");
+ else
+ avdtp_unref(session);
+}
+
+void avdtp_unref(struct avdtp *session)
+{
+ if (!session)
+ return;
+
+ if (!g_slist_find(sessions, session)) {
+ error("avdtp_unref: trying to unref a unknown session");
+ return;
+ }
+
+ session->ref--;
+
+ debug("avdtp_unref(%p): ref=%d", session, session->ref);
+
+ if (session->ref == 1) {
+ if (session->state == AVDTP_SESSION_STATE_CONNECTING) {
+ close(session->sock);
+ session->sock = -1;
+ }
+
+ if (session->sock >= 0)
+ set_disconnect_timer(session);
+ else if (!session->free_lock) /* Drop the local ref if we
+ aren't connected */
+ session->ref--;
+ }
+
+ if (session->ref > 0)
+ return;
+
+ debug("avdtp_unref(%p): freeing session and removing from list",
+ session);
+
+ if (session->dc_timer)
+ remove_disconnect_timer(session);
+
+ sessions = g_slist_remove(sessions, session);
+
+ if (session->req)
+ pending_req_free(session->req);
+
+ g_slist_foreach(session->seps, (GFunc) g_free, NULL);
+ g_slist_free(session->seps);
+
+ g_free(session->buf);
+
+ g_free(session);
+}
+
+struct avdtp *avdtp_ref(struct avdtp *session)
+{
+ session->ref++;
+ debug("avdtp_ref(%p): ref=%d", session, session->ref);
+ if (session->dc_timer)
+ remove_disconnect_timer(session);
+ return session;
+}
+
+static struct avdtp_local_sep *find_local_sep_by_seid(uint8_t seid)
+{
+ GSList *l;
+
+ for (l = local_seps; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_local_sep *sep = l->data;
+
+ if (sep->info.seid == seid)
+ return sep;
+ }
+
+ return NULL;
+}
+
+static struct avdtp_local_sep *find_local_sep(uint8_t type, uint8_t media_type,
+ uint8_t codec)
+{
+ GSList *l;
+
+ for (l = local_seps; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_local_sep *sep = l->data;
+
+ if (sep->info.inuse)
+ continue;
+
+ if (sep->info.type == type &&
+ sep->info.media_type == media_type &&
+ sep->codec == codec)
+ return sep;
+ }
+
+ return NULL;
+}
+
+static GSList *caps_to_list(uint8_t *data, int size,
+ struct avdtp_service_capability **codec)
+{
+ GSList *caps;
+ int processed;
+
+ for (processed = 0, caps = NULL; processed + 2 < size;) {
+ struct avdtp_service_capability *cap;
+ uint8_t length, category;
+
+ category = data[0];
+ length = data[1];
+
+ if (processed + 2 + length > size) {
+ error("Invalid capability data in getcap resp");
+ break;
+ }
+
+ cap = g_malloc(sizeof(struct avdtp_service_capability) +
+ length);
+ memcpy(cap, data, 2 + length);
+
+ processed += 2 + length;
+ data += 2 + length;
+
+ caps = g_slist_append(caps, cap);
+
+ if (category == AVDTP_MEDIA_CODEC &&
+ length >=
+ sizeof(struct avdtp_media_codec_capability))
+ *codec = cap;
+ }
+
+ return caps;
+}
+
+static void init_response(struct avdtp_header *rsp, struct avdtp_header *req,
+ gboolean accept)
+{
+ rsp->packet_type = AVDTP_PKT_TYPE_SINGLE;
+ rsp->message_type = accept ? AVDTP_MSG_TYPE_ACCEPT :
+ AVDTP_MSG_TYPE_REJECT;
+ rsp->transaction = req->transaction;
+ rsp->signal_id = req->signal_id;
+ rsp->rfa0 = 0;
+}
+
+static gboolean avdtp_unknown_cmd(struct avdtp *session,
+ struct avdtp_header *req, int size)
+{
+ struct avdtp_general_rej rej;
+
+ memset(&rej, 0, sizeof(rej));
+
+ rej.packet_type = AVDTP_PKT_TYPE_SINGLE;
+ rej.message_type = AVDTP_MSG_TYPE_REJECT;
+ rej.transaction = req->transaction;
+
+ return avdtp_send(session, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_discover_cmd(struct avdtp *session,
+ struct gen_req *req, int size)
+{
+ GSList *l;
+ struct discover_resp *rsp = (struct discover_resp *) session->buf;
+ struct seid_info *info;
+ int rsp_size;
+
+ init_response(&rsp->header, &req->header, TRUE);
+ rsp_size = sizeof(struct discover_resp);
+ info = rsp->seps;
+
+ for (l = local_seps; l != NULL; l = l->next) {
+ struct avdtp_local_sep *sep = l->data;
+
+ if (rsp_size + sizeof(struct seid_info) > session->mtu)
+ break;
+
+ memcpy(info, &sep->info, sizeof(struct seid_info));
+ rsp_size += sizeof(struct seid_info);
+ info++;
+ }
+
+ return avdtp_send(session, session->buf, rsp_size);
+}
+
+static gboolean avdtp_getcap_cmd(struct avdtp *session,
+ struct seid_req *req, int size)
+{
+ GSList *l, *caps;
+ struct avdtp_local_sep *sep = NULL;
+ struct seid_rej rej;
+ struct getcap_resp *rsp = (struct getcap_resp *) session->buf;
+ int rsp_size;
+ unsigned char *ptr;
+ uint8_t err;
+
+ if (size < sizeof(struct seid_req)) {
+ error("Too short getcap request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(req->acp_seid);
+ if (!sep) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ if (!sep->ind->get_capability(session, sep, &caps, &err,
+ sep->user_data))
+ goto failed;
+
+ init_response(&rsp->header, &req->header, TRUE);
+ rsp_size = sizeof(struct getcap_resp);
+ ptr = rsp->caps;
+
+ for (l = caps; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_service_capability *cap = l->data;
+
+ if (rsp_size + cap->length + 2 > session->mtu)
+ break;
+
+ memcpy(ptr, cap, cap->length + 2);
+ rsp_size += cap->length + 2;
+ ptr += cap->length + 2;
+
+ g_free(cap);
+ }
+
+ g_slist_free(caps);
+
+ return avdtp_send(session, session->buf, rsp_size);
+
+failed:
+ init_response(&rej.header, &req->header, FALSE);
+ rej.error = AVDTP_BAD_ACP_SEID;
+ return avdtp_send(session, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_setconf_cmd(struct avdtp *session,
+ struct setconf_req *req, int size)
+{
+ struct conf_rej rej;
+ struct gen_resp *rsp = (struct gen_resp *) session->buf;
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ uint8_t err, category = 0x00;
+
+ if (size < sizeof(struct setconf_req)) {
+ error("Too short getcap request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(req->acp_seid);
+ if (!sep) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ if (sep->stream) {
+ err = AVDTP_SEP_IN_USE;
+ goto failed;
+ }
+
+ stream = g_new0(struct avdtp_stream, 1);
+ stream->session = session;
+ stream->lsep = sep;
+ stream->rseid = req->int_seid;
+ stream->caps = caps_to_list(req->caps,
+ size - sizeof(struct setconf_req),
+ &stream->codec);
+ stream->sock = -1;
+
+ if (sep->ind && sep->ind->set_configuration) {
+ if (!sep->ind->set_configuration(session, sep, stream,
+ stream->caps, &err,
+ &category,
+ sep->user_data)) {
+ stream_free(stream);
+ goto failed;
+ }
+ }
+
+ init_response(&rsp->header, &req->header, TRUE);
+
+ if (!avdtp_send(session, rsp, sizeof(struct setconf_req))) {
+ stream_free(stream);
+ return FALSE;
+ }
+
+ sep->stream = stream;
+ session->streams = g_slist_append(session->streams, stream);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+
+ return TRUE;
+
+failed:
+ init_response(&rej.header, &req->header, FALSE);
+ rej.error = err;
+ rej.category = category;
+ return avdtp_send(session, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_getconf_cmd(struct avdtp *session, struct seid_req *req,
+ int size)
+{
+ return avdtp_unknown_cmd(session, (void *) req, size);
+}
+
+static gboolean avdtp_reconf_cmd(struct avdtp *session, struct seid_req *req,
+ int size)
+{
+ return avdtp_unknown_cmd(session, (void *) req, size);
+}
+
+static gboolean avdtp_open_cmd(struct avdtp *session, struct seid_req *req,
+ int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ struct gen_resp *rsp = (struct gen_resp *) session->buf;
+ struct seid_rej rej;
+ uint8_t err;
+
+ if (size < sizeof(struct seid_req)) {
+ error("Too short abort request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(req->acp_seid);
+ if (!sep) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ if (sep->state != AVDTP_STATE_CONFIGURED) {
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+
+ stream = sep->stream;
+
+ if (sep->ind && sep->ind->open) {
+ if (!sep->ind->open(session, sep, stream, &err,
+ sep->user_data))
+ goto failed;
+ }
+
+ init_response(&rsp->header, &req->header, TRUE);
+
+ if (!avdtp_send(session, rsp, sizeof(struct gen_resp)))
+ return FALSE;
+
+ stream->open_acp = TRUE;
+ session->pending_open = stream;
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+ stream->timer = g_timeout_add(REQ_TIMEOUT, stream_open_timeout,
+ stream);
+
+ return TRUE;
+
+failed:
+ init_response(&rej.header, &req->header, FALSE);
+ rej.error = err;
+ return avdtp_send(session, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_start_cmd(struct avdtp *session, struct start_req *req,
+ int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ struct gen_resp *rsp = (struct gen_resp *) session->buf;
+ struct stream_rej rej;
+ struct seid *seid;
+ uint8_t err, failed_seid;
+ int seid_count, i;
+
+ if (size < sizeof(struct start_req)) {
+ error("Too short start request");
+ return FALSE;
+ }
+
+ seid_count = 1 + size - sizeof(struct start_req);
+
+ seid = &req->first_seid;
+
+ for (i = 0; i < seid_count; i++, seid++) {
+ failed_seid = seid->seid;
+
+ sep = find_local_sep_by_seid(req->first_seid.seid);
+ if (!sep || !sep->stream) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ stream = sep->stream;
+
+ if (sep->state != AVDTP_STATE_OPEN) {
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+
+ if (sep->ind && sep->ind->start) {
+ if (!sep->ind->start(session, sep, stream, &err,
+ sep->user_data))
+ goto failed;
+ }
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING);
+ }
+
+ init_response(&rsp->header, &req->header, TRUE);
+
+ return avdtp_send(session, rsp, sizeof(struct gen_resp));
+
+failed:
+ memset(&rej, 0, sizeof(rej));
+ init_response(&rej.header, &req->header, FALSE);
+ rej.acp_seid = failed_seid;
+ rej.error = err;
+ return avdtp_send(session, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_close_cmd(struct avdtp *session, struct seid_req *req,
+ int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ struct gen_resp *rsp = (struct gen_resp *) session->buf;
+ struct seid_rej rej;
+ uint8_t err;
+
+ if (size < sizeof(struct seid_req)) {
+ error("Too short close request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(req->acp_seid);
+ if (!sep || !sep->stream) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ if (sep->state != AVDTP_STATE_OPEN &&
+ sep->state != AVDTP_STATE_STREAMING) {
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+
+ stream = sep->stream;
+
+ if (sep->ind && sep->ind->close) {
+ if (!sep->ind->close(session, sep, stream, &err,
+ sep->user_data))
+ goto failed;
+ }
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING);
+
+ init_response(&rsp->header, &req->header, TRUE);
+
+ if (!avdtp_send(session, rsp, sizeof(struct gen_resp)))
+ return FALSE;
+
+ stream->timer = g_timeout_add(REQ_TIMEOUT, stream_close_timeout,
+ stream);
+
+ return TRUE;
+
+failed:
+ init_response(&rej.header, &req->header, FALSE);
+ rej.error = err;
+ return avdtp_send(session, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_suspend_cmd(struct avdtp *session,
+ struct suspend_req *req, int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ struct gen_resp *rsp = (struct gen_resp *) session->buf;
+ struct stream_rej rej;
+ struct seid *seid;
+ uint8_t err, failed_seid;
+ int seid_count, i;
+
+ if (size < sizeof(struct suspend_req)) {
+ error("Too short suspend request");
+ return FALSE;
+ }
+
+ seid_count = 1 + size - sizeof(struct suspend_req);
+
+ seid = &req->first_seid;
+
+ for (i = 0; i < seid_count; i++, seid++) {
+ failed_seid = seid->seid;
+
+ sep = find_local_sep_by_seid(req->first_seid.seid);
+ if (!sep || !sep->stream) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ stream = sep->stream;
+
+ if (sep->state != AVDTP_STATE_STREAMING) {
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+
+ if (sep->ind && sep->ind->suspend) {
+ if (!sep->ind->suspend(session, sep, stream, &err,
+ sep->user_data))
+ goto failed;
+ }
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+ }
+
+ init_response(&rsp->header, &req->header, TRUE);
+
+ return avdtp_send(session, rsp, sizeof(struct gen_resp));
+
+failed:
+ memset(&rej, 0, sizeof(rej));
+ init_response(&rej.header, &req->header, FALSE);
+ rej.acp_seid = failed_seid;
+ rej.error = err;
+ return avdtp_send(session, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_abort_cmd(struct avdtp *session, struct seid_req *req,
+ int size)
+{
+ struct avdtp_local_sep *sep;
+ struct gen_resp *rsp = (struct gen_resp *) session->buf;
+ struct seid_rej rej;
+ uint8_t err;
+ gboolean ret;
+
+ if (size < sizeof(struct seid_req)) {
+ error("Too short abort request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(req->acp_seid);
+ if (!sep || !sep->stream) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ if (sep->ind && sep->ind->abort) {
+ if (!sep->ind->abort(session, sep, sep->stream, &err,
+ sep->user_data))
+ goto failed;
+ }
+
+ init_response(&rsp->header, &req->header, TRUE);
+ ret = avdtp_send(session, rsp, sizeof(struct gen_resp));
+
+ if (ret)
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING);
+
+ return ret;
+
+failed:
+ init_response(&rej.header, &req->header, FALSE);
+ rej.error = err;
+ return avdtp_send(session, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_secctl_cmd(struct avdtp *session, struct seid_req *req,
+ int size)
+{
+ return avdtp_unknown_cmd(session, (void *) req, size);
+}
+
+static gboolean avdtp_parse_cmd(struct avdtp *session,
+ struct avdtp_header *header, int size)
+{
+ switch (header->signal_id) {
+ case AVDTP_DISCOVER:
+ debug("Received DISCOVER_CMD");
+ return avdtp_discover_cmd(session, (void *) header, size);
+ case AVDTP_GET_CAPABILITIES:
+ debug("Received GET_CAPABILITIES_CMD");
+ return avdtp_getcap_cmd(session, (void *) header, size);
+ case AVDTP_SET_CONFIGURATION:
+ debug("Received SET_CONFIGURATION_CMD");
+ return avdtp_setconf_cmd(session, (void *) header, size);
+ case AVDTP_GET_CONFIGURATION:
+ debug("Received GET_CONFIGURATION_CMD");
+ return avdtp_getconf_cmd(session, (void *) header, size);
+ case AVDTP_RECONFIGURE:
+ debug("Received RECONFIGURE_CMD");
+ return avdtp_reconf_cmd(session, (void *) header, size);
+ case AVDTP_OPEN:
+ debug("Received OPEN_CMD");
+ return avdtp_open_cmd(session, (void *) header, size);
+ case AVDTP_START:
+ debug("Received START_CMD");
+ return avdtp_start_cmd(session, (void *) header, size);
+ case AVDTP_CLOSE:
+ debug("Received CLOSE_CMD");
+ return avdtp_close_cmd(session, (void *) header, size);
+ case AVDTP_SUSPEND:
+ debug("Received SUSPEND_CMD");
+ return avdtp_suspend_cmd(session, (void *) header, size);
+ case AVDTP_ABORT:
+ debug("Received ABORT_CMD");
+ return avdtp_abort_cmd(session, (void *) header, size);
+ case AVDTP_SECURITY_CONTROL:
+ debug("Received SECURITY_CONTROL_CMD");
+ return avdtp_secctl_cmd(session, (void *) header, size);
+ default:
+ debug("Received unknown request id %u", header->signal_id);
+ return avdtp_unknown_cmd(session, (void *) header, size);
+ }
+}
+
+static void init_request(struct avdtp_header *header, int request_id)
+{
+ static int transaction = 0;
+
+ header->packet_type = AVDTP_PKT_TYPE_SINGLE;
+ header->message_type = AVDTP_MSG_TYPE_COMMAND;
+ header->transaction = transaction;
+ header->signal_id = request_id;
+
+ /* clear rfa bits */
+ header->rfa0 = 0;
+
+ transaction = (transaction + 1) % 16;
+}
+
+static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct avdtp *session = data;
+ struct avdtp_header *header;
+ gsize size;
+
+ debug("session_cb");
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ if (cond & (G_IO_HUP | G_IO_ERR))
+ goto failed;
+
+ if (g_io_channel_read(chan, session->buf, session->mtu,
+ &size) != G_IO_ERROR_NONE) {
+ error("IO Channel read error");
+ goto failed;
+ }
+
+ if (size < sizeof(struct avdtp_header)) {
+ error("Received too small packet (%d bytes)", size);
+ goto failed;
+ }
+
+ header = (struct avdtp_header *) session->buf;
+
+ if (header->message_type == AVDTP_MSG_TYPE_COMMAND) {
+ if (!avdtp_parse_cmd(session, header, size)) {
+ error("Unable to handle command. Disconnecting");
+ goto failed;
+ }
+
+ if (session->ref == 1 && !session->streams)
+ set_disconnect_timer(session);
+
+ if (session->streams && session->dc_timer)
+ remove_disconnect_timer(session);
+
+ return TRUE;
+ }
+
+ if (session->req == NULL) {
+ error("No pending request, rejecting message");
+ return TRUE;
+ }
+
+ if (header->transaction != session->req->msg->transaction) {
+ error("Transaction label doesn't match");
+ return TRUE;
+ }
+
+ if (header->signal_id != session->req->msg->signal_id) {
+ error("Reponse signal doesn't match");
+ return TRUE;
+ }
+
+ g_source_remove(session->req->timeout);
+ session->req->timeout = 0;
+
+ switch(header->message_type) {
+ case AVDTP_MSG_TYPE_ACCEPT:
+ if (!avdtp_parse_resp(session, session->req->stream,
+ header, size)) {
+ error("Unable to parse accept response");
+ goto failed;
+ }
+ break;
+ case AVDTP_MSG_TYPE_REJECT:
+ if (!avdtp_parse_rej(session, session->req->stream,
+ header, size)) {
+ error("Unable to parse reject response");
+ goto failed;
+ }
+ break;
+ default:
+ error("Unknown message type");
+ break;
+ }
+
+ pending_req_free(session->req);
+ session->req = NULL;
+
+ process_queue(session);
+
+ return TRUE;
+
+failed:
+ connection_lost(session, -EIO);
+
+ return FALSE;
+}
+
+static void l2cap_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer user_data)
+{
+ struct avdtp *session = user_data;
+ struct l2cap_options l2o;
+ socklen_t len;
+ int sk;
+ char address[18];
+
+ if (!g_slist_find(sessions, session)) {
+ debug("l2cap_connect_cb: session got removed");
+ return;
+ }
+
+ if (err < 0) {
+ error("connect(): %s (%d)", strerror(-err), -err);
+ goto failed;
+ }
+
+ sk = g_io_channel_unix_get_fd(chan);
+
+ if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) {
+ session->sock = sk;
+ session->state = AVDTP_SESSION_STATE_CONNECTING;
+ }
+
+ ba2str(&session->dst, address);
+ debug("AVDTP: connected %s channel to %s",
+ session->pending_open ? "transport" : "signaling",
+ address);
+
+ memset(&l2o, 0, sizeof(l2o));
+ len = sizeof(l2o);
+ if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o,
+ &len) < 0) {
+ err = errno;
+ error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(err),
+ err);
+ goto failed;
+ }
+
+ if (session->state == AVDTP_SESSION_STATE_CONNECTING) {
+ struct audio_device *dev;
+
+ session->mtu = l2o.imtu;
+ session->buf = g_malloc0(session->mtu);
+ session->state = AVDTP_SESSION_STATE_CONNECTED;
+ session->io = g_io_add_watch(chan,
+ G_IO_IN | G_IO_ERR | G_IO_HUP
+ | G_IO_NVAL,
+ (GIOFunc) session_cb, session);
+
+ dev = manager_find_device(&session->dst,
+ AUDIO_CONTROL_INTERFACE, FALSE);
+ if (dev)
+ avrcp_connect(dev);
+ }
+ else if (session->pending_open)
+ handle_transport_connect(session, sk, l2o.imtu, l2o.omtu);
+ else {
+ err = -EIO;
+ goto failed;
+ }
+
+ process_queue(session);
+
+ return;
+
+failed:
+ if (session->pending_open) {
+ avdtp_sep_set_state(session, session->pending_open->lsep,
+ AVDTP_STATE_IDLE);
+ session->pending_open = NULL;
+ } else
+ connection_lost(session, -err);
+
+ return;
+}
+
+static int l2cap_connect(struct avdtp *session)
+{
+ int err;
+
+ err = bt_l2cap_connect(&session->src, &session->dst, AVDTP_PSM, 0,
+ l2cap_connect_cb, session);
+ if (err < 0) {
+ error("Connect failed. %s(%d)", strerror(-err), -err);
+ return err;
+ }
+
+ return 0;
+}
+
+static void queue_request(struct avdtp *session, struct pending_req *req,
+ gboolean priority)
+{
+ if (priority)
+ session->prio_queue = g_slist_append(session->prio_queue, req);
+ else
+ session->req_queue = g_slist_append(session->req_queue, req);
+}
+
+static gboolean request_timeout(gpointer user_data)
+{
+ struct avdtp *session = user_data;
+ struct pending_req *req;
+ struct seid_req sreq;
+ struct avdtp_local_sep *lsep;
+ struct avdtp_stream *stream;
+ uint8_t seid;
+ struct avdtp_error err;
+
+ req = session->req;
+ session->req = NULL;
+
+ avdtp_error_init(&err, AVDTP_ERROR_ERRNO, ETIMEDOUT);
+
+ seid = ((struct seid_req *) (req->msg))->acp_seid;
+
+ stream = find_stream_by_rseid(session, seid);
+
+ if (stream)
+ lsep = stream->lsep;
+ else
+ lsep = NULL;
+
+ switch (req->msg->signal_id) {
+ case AVDTP_RECONFIGURE:
+ error("Reconfigure request timed out");
+ if (lsep && lsep->cfm && lsep->cfm->reconfigure)
+ lsep->cfm->reconfigure(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_OPEN:
+ error("Open request timed out");
+ if (lsep && lsep->cfm && lsep->cfm->open)
+ lsep->cfm->open(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_START:
+ error("Start request timed out");
+ if (lsep && lsep->cfm && lsep->cfm->start)
+ lsep->cfm->start(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_SUSPEND:
+ error("Suspend request timed out");
+ if (lsep && lsep->cfm && lsep->cfm->suspend)
+ lsep->cfm->suspend(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_CLOSE:
+ error("Close request timed out");
+ if (lsep && lsep->cfm && lsep->cfm->close)
+ lsep->cfm->close(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_SET_CONFIGURATION:
+ error("SetConfiguration request timed out");
+ if (lsep && lsep->cfm && lsep->cfm->set_configuration)
+ lsep->cfm->set_configuration(session, lsep, stream,
+ &err, lsep->user_data);
+ goto failed;
+ case AVDTP_DISCOVER:
+ error("Discover request timed out");
+ goto failed;
+ case AVDTP_GET_CAPABILITIES:
+ error("GetCapabilities request timed out");
+ goto failed;
+ case AVDTP_ABORT:
+ error("Abort request timed out");
+ goto failed;
+ }
+
+ memset(&sreq, 0, sizeof(sreq));
+ init_request(&sreq.header, AVDTP_ABORT);
+ sreq.acp_seid = seid;
+
+ if (send_request(session, TRUE, stream, &sreq, sizeof(sreq)) < 0) {
+ error("Unable to send abort request");
+ goto failed;
+ }
+
+ goto done;
+
+failed:
+ connection_lost(session, -ETIMEDOUT);
+done:
+ pending_req_free(req);
+ return FALSE;
+}
+
+static int send_req(struct avdtp *session, gboolean priority,
+ struct pending_req *req)
+{
+ int err;
+
+ if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) {
+ err = l2cap_connect(session);
+ if (err < 0)
+ goto failed;
+ }
+
+ if (session->state < AVDTP_SESSION_STATE_CONNECTED ||
+ session->req != NULL) {
+ queue_request(session, req, priority);
+ return 0;
+ }
+
+ /* FIXME: Should we retry to send if the buffer
+ was not totally sent or in case of EINTR? */
+ if (!avdtp_send(session, req->msg, req->msg_size)) {
+ err = -EIO;
+ goto failed;
+ }
+
+ session->req = req;
+
+ req->timeout = g_timeout_add(REQ_TIMEOUT, request_timeout,
+ session);
+ return 0;
+
+failed:
+ g_free(req->msg);
+ g_free(req);
+ return err;
+}
+
+static int send_request(struct avdtp *session, gboolean priority,
+ struct avdtp_stream *stream, void *buffer, int size)
+{
+ struct pending_req *req;
+
+ req = g_new0(struct pending_req, 1);
+ req->msg = g_malloc(size);
+ memcpy(req->msg, buffer, size);
+ req->msg_size = size;
+ req->stream = stream;
+
+ return send_req(session, priority, req);
+}
+
+static gboolean avdtp_discover_resp(struct avdtp *session,
+ struct discover_resp *resp, int size)
+{
+ int sep_count, i, isize = sizeof(struct seid_info);
+
+ sep_count = (size - sizeof(struct avdtp_header)) / isize;
+
+ for (i = 0; i < sep_count; i++) {
+ struct avdtp_remote_sep *sep;
+ struct avdtp_stream *stream;
+ struct seid_req req;
+ int ret;
+
+ debug("seid %d type %d media %d in use %d",
+ resp->seps[i].seid, resp->seps[i].type,
+ resp->seps[i].media_type, resp->seps[i].inuse);
+
+ stream = find_stream_by_rseid(session, resp->seps[i].seid);
+
+ sep = find_remote_sep(session->seps, resp->seps[i].seid);
+ if (!sep) {
+ if (resp->seps[i].inuse && !stream)
+ continue;
+ sep = g_new0(struct avdtp_remote_sep, 1);
+ session->seps = g_slist_append(session->seps, sep);
+ }
+
+ sep->stream = stream;
+ sep->seid = resp->seps[i].seid;
+ sep->type = resp->seps[i].type;
+ sep->media_type = resp->seps[i].media_type;
+
+ memset(&req, 0, sizeof(req));
+ init_request(&req.header, AVDTP_GET_CAPABILITIES);
+ req.acp_seid = sep->seid;
+
+ ret = send_request(session, TRUE, NULL, &req, sizeof(req));
+ if (ret < 0) {
+ finalize_discovery(session, ret);
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean avdtp_get_capabilities_resp(struct avdtp *session,
+ struct getcap_resp *resp,
+ int size)
+{
+ struct avdtp_remote_sep *sep;
+ uint8_t seid;
+
+ /* Check for minimum required packet size includes:
+ * 1. getcap resp header
+ * 2. media transport capability (2 bytes)
+ * 3. media codec capability type + length (2 bytes)
+ * 4. the actual media codec elements
+ * */
+ if (size < (sizeof(struct getcap_resp) + 4 +
+ sizeof(struct avdtp_media_codec_capability))) {
+ error("Too short getcap resp packet");
+ return FALSE;
+ }
+
+ seid = ((struct seid_req *) session->req->msg)->acp_seid;
+
+ sep = find_remote_sep(session->seps, seid);
+
+ debug("seid %d type %d media %d", sep->seid,
+ sep->type, sep->media_type);
+
+ if (sep->caps) {
+ g_slist_foreach(sep->caps, (GFunc) g_free, NULL);
+ g_slist_free(sep->caps);
+ sep->caps = NULL;
+ sep->codec = NULL;
+ }
+
+ sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp),
+ &sep->codec);
+
+ return TRUE;
+}
+
+static gboolean avdtp_set_configuration_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct avdtp_header *resp,
+ int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ if (sep->cfm && sep->cfm->set_configuration)
+ sep->cfm->set_configuration(session, sep, stream, NULL,
+ sep->user_data);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+
+ return TRUE;
+}
+
+static gboolean avdtp_reconfigure_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct avdtp_header *resp, int size)
+{
+ return TRUE;
+}
+
+static gboolean avdtp_open_resp(struct avdtp *session, struct avdtp_stream *stream,
+ struct seid_rej *resp, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ if (l2cap_connect(session) < 0) {
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
+ return FALSE;
+ }
+
+ session->pending_open = stream;
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+
+ return TRUE;
+}
+
+static gboolean avdtp_start_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct seid_rej *resp, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ if (sep->cfm && sep->cfm->start)
+ sep->cfm->start(session, sep, stream, NULL, sep->user_data);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING);
+
+ return TRUE;
+}
+
+static gboolean avdtp_close_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct seid_rej *resp, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING);
+
+ close(stream->sock);
+ stream->sock = -1;
+
+ return TRUE;
+}
+
+static gboolean avdtp_suspend_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct gen_resp *resp,
+ int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+
+ stream->idle_timer = g_timeout_add(STREAM_TIMEOUT,
+ (GSourceFunc) stream_timeout, stream);
+
+ if (sep->cfm && sep->cfm->suspend)
+ sep->cfm->suspend(session, sep, stream, NULL, sep->user_data);
+
+ return TRUE;
+}
+
+static gboolean avdtp_abort_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct seid_rej *resp, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ if (sep->cfm && sep->cfm->abort)
+ sep->cfm->abort(session, sep, stream, NULL, sep->user_data);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
+
+ return TRUE;
+}
+
+static gboolean avdtp_parse_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct avdtp_header *header, int size)
+{
+ struct avdtp_header *next;
+
+ if (session->prio_queue)
+ next = ((struct pending_req *)
+ (session->prio_queue->data))->msg;
+ else if (session->req_queue)
+ next = ((struct pending_req *)
+ (session->req_queue->data))->msg;
+ else
+ next = NULL;
+
+ switch (header->signal_id) {
+ case AVDTP_DISCOVER:
+ debug("DISCOVER request succeeded");
+ return avdtp_discover_resp(session, (void *) header, size);
+ case AVDTP_GET_CAPABILITIES:
+ debug("GET_CAPABILITIES request succeeded");
+ if (!avdtp_get_capabilities_resp(session,
+ (void *) header, size))
+ return FALSE;
+ if (!(next && next->signal_id == AVDTP_GET_CAPABILITIES))
+ finalize_discovery(session, 0);
+ return TRUE;
+ case AVDTP_SET_CONFIGURATION:
+ debug("SET_CONFIGURATION request succeeded");
+ return avdtp_set_configuration_resp(session, stream,
+ (void *) header, size);
+ case AVDTP_RECONFIGURE:
+ debug("RECONFIGURE request succeeded");
+ return avdtp_reconfigure_resp(session, stream,
+ (void *) header, size);
+ case AVDTP_OPEN:
+ debug("OPEN request succeeded");
+ return avdtp_open_resp(session, stream, (void *) header, size);
+ case AVDTP_SUSPEND:
+ debug("SUSPEND request succeeded");
+ return avdtp_suspend_resp(session, stream,
+ (void *) header, size);
+ case AVDTP_START:
+ debug("START request succeeded");
+ return avdtp_start_resp(session, stream, (void *) header, size);
+ case AVDTP_CLOSE:
+ debug("CLOSE request succeeded");
+ return avdtp_close_resp(session, stream, (void *) header, size);
+ case AVDTP_ABORT:
+ debug("ABORT request succeeded");
+ return avdtp_abort_resp(session, stream, (void *) header, size);
+ }
+
+ error("Unknown signal id in accept response: %u", header->signal_id);
+
+ return TRUE;
+}
+
+static gboolean seid_rej_to_err(struct seid_rej *rej, int size,
+ struct avdtp_error *err)
+{
+ if (size < sizeof(struct seid_rej)) {
+ error("Too small packet for seid_rej");
+ return FALSE;
+ }
+
+ avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error);
+
+ return TRUE;
+}
+
+static gboolean conf_rej_to_err(struct conf_rej *rej, int size,
+ struct avdtp_error *err, uint8_t *category)
+{
+ if (size < sizeof(struct conf_rej)) {
+ error("Too small packet for conf_rej");
+ return FALSE;
+ }
+
+ avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error);
+
+ if (category)
+ *category = rej->category;
+
+ return TRUE;
+}
+
+static gboolean stream_rej_to_err(struct stream_rej *rej, int size,
+ struct avdtp_error *err,
+ uint8_t *acp_seid)
+{
+ if (size < sizeof(struct conf_rej)) {
+ error("Too small packet for stream_rej");
+ return FALSE;
+ }
+
+ avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error);
+
+ if (acp_seid)
+ *acp_seid = rej->acp_seid;
+
+ return TRUE;
+}
+
+static gboolean avdtp_parse_rej(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct avdtp_header *header, int size)
+{
+ struct avdtp_error err;
+ uint8_t acp_seid, category;
+ struct avdtp_local_sep *sep = stream ? stream->lsep : NULL;
+
+ switch (header->signal_id) {
+ case AVDTP_DISCOVER:
+ if (!seid_rej_to_err((void *) header, size, &err))
+ return FALSE;
+ error("DISCOVER request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ return TRUE;
+ case AVDTP_GET_CAPABILITIES:
+ if (!seid_rej_to_err((void *) header, size, &err))
+ return FALSE;
+ error("GET_CAPABILITIES request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ return TRUE;
+ case AVDTP_OPEN:
+ if (!seid_rej_to_err((void *) header, size, &err))
+ return FALSE;
+ error("OPEN request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->open)
+ sep->cfm->open(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
+ case AVDTP_SET_CONFIGURATION:
+ if (!conf_rej_to_err((void *) header, size, &err, &category))
+ return FALSE;
+ error("SET_CONFIGURATION request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->set_configuration)
+ sep->cfm->set_configuration(session, sep, stream,
+ &err, sep->user_data);
+ return TRUE;
+ case AVDTP_RECONFIGURE:
+ if (!conf_rej_to_err((void *) header, size, &err, &category))
+ return FALSE;
+ error("RECONFIGURE request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->reconfigure)
+ sep->cfm->reconfigure(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
+ case AVDTP_START:
+ if (!stream_rej_to_err((void *) header, size, &err, &acp_seid))
+ return FALSE;
+ error("START request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->start)
+ sep->cfm->start(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
+ case AVDTP_SUSPEND:
+ if (!stream_rej_to_err((void *) header, size, &err, &acp_seid))
+ return FALSE;
+ error("SUSPEND request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->suspend)
+ sep->cfm->suspend(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
+ case AVDTP_CLOSE:
+ if (!stream_rej_to_err((void *) header, size, &err, &acp_seid))
+ return FALSE;
+ error("CLOSE request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->close)
+ sep->cfm->close(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
+ case AVDTP_ABORT:
+ if (!stream_rej_to_err((void *) header, size, &err, &acp_seid))
+ return FALSE;
+ error("ABORT request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->abort)
+ sep->cfm->abort(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
+ default:
+ error("Unknown reject response signal id: %u",
+ header->signal_id);
+ return TRUE;
+ }
+}
+
+static struct avdtp *find_session(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ GSList *l;
+
+ for (l = sessions; l != NULL; l = g_slist_next(l)) {
+ struct avdtp *s = l->data;
+
+ if (bacmp(src, &s->src) || bacmp(dst, &s->dst))
+ continue;
+
+ return s;
+ }
+
+ return NULL;
+}
+
+static struct avdtp *avdtp_get_internal(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct avdtp *session;
+
+ assert(src != NULL);
+ assert(dst != NULL);
+
+ session = find_session(src, dst);
+ if (session) {
+ if (session->pending_auth)
+ return NULL;
+ else
+ return session;
+ }
+
+ session = g_new0(struct avdtp, 1);
+
+ session->sock = -1;
+ bacpy(&session->src, src);
+ bacpy(&session->dst, dst);
+ session->ref = 1;
+ session->state = AVDTP_SESSION_STATE_DISCONNECTED;
+
+ sessions = g_slist_append(sessions, session);
+
+ return session;
+}
+
+struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst)
+{
+ struct avdtp *session;
+
+ session = avdtp_get_internal(src, dst);
+
+ if (!session)
+ return NULL;
+
+ return avdtp_ref(session);
+}
+
+gboolean avdtp_is_connected(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct avdtp *session;
+
+ session = find_session(src, dst);
+
+ if (!session)
+ return FALSE;
+
+ if (session->state != AVDTP_SESSION_STATE_DISCONNECTED)
+ return TRUE;
+
+ return FALSE;
+}
+
+gboolean avdtp_stream_has_capability(struct avdtp_stream *stream,
+ struct avdtp_service_capability *cap)
+{
+ GSList *l;
+ struct avdtp_service_capability *stream_cap;
+
+ for (l = stream->caps; l; l = g_slist_next(l)) {
+ stream_cap = l->data;
+ if (stream_cap->category == cap->category &&
+ stream_cap->length == cap->length) {
+ if (!memcmp(stream_cap->data, cap->data, cap->length))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream,
+ GSList *caps)
+{
+ GSList *l;
+
+ for (l = caps; l; l = g_slist_next(l)) {
+ struct avdtp_service_capability *cap = l->data;
+
+ if (!avdtp_stream_has_capability(stream, cap))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock,
+ uint16_t *imtu, uint16_t *omtu,
+ GSList **caps)
+{
+ if (stream->sock < 0)
+ return FALSE;
+
+ if (sock)
+ *sock = stream->sock;
+
+ if (omtu)
+ *omtu = stream->omtu;
+
+ if (imtu)
+ *imtu = stream->imtu;
+
+ if (caps)
+ *caps = stream->caps;
+
+ return TRUE;
+}
+
+static int process_queue(struct avdtp *session)
+{
+ GSList **queue, *l;
+ struct pending_req *req;
+
+ if (session->req)
+ return 0;
+
+ if (session->prio_queue)
+ queue = &session->prio_queue;
+ else
+ queue = &session->req_queue;
+
+ if (!*queue)
+ return 0;
+
+ l = *queue;
+ req = l->data;
+
+ *queue = g_slist_remove(*queue, req);
+
+ return send_req(session, FALSE, req);
+}
+
+struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep)
+{
+ return sep->codec;
+}
+
+struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category,
+ void *data, int length)
+{
+ struct avdtp_service_capability *cap;
+
+ if (category < AVDTP_MEDIA_TRANSPORT || category > AVDTP_MEDIA_CODEC)
+ return NULL;
+
+ cap = g_malloc(sizeof(struct avdtp_service_capability) + length);
+ cap->category = category;
+ cap->length = length;
+ memcpy(cap->data, data, length);
+
+ return cap;
+}
+
+int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb,
+ void *user_data)
+{
+ struct gen_req req;
+ int ret;
+
+ if (session->discov_cb)
+ return -EBUSY;
+
+ if (session->seps) {
+ cb(session, session->seps, NULL, user_data);
+ return 0;
+ }
+
+ memset(&req, 0, sizeof(req));
+ init_request(&req.header, AVDTP_DISCOVER);
+
+ ret = send_request(session, FALSE, NULL, &req, sizeof(req));
+ if (ret == 0) {
+ session->discov_cb = cb;
+ session->user_data = user_data;
+ }
+
+ return ret;
+}
+
+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(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)
+{
+ GSList *l;
+ struct stream_callback *cb;
+
+ for (cb = NULL, l = stream->callbacks; l != NULL; l = l->next) {
+ struct stream_callback *tmp = l->data;
+ if (tmp->id == id) {
+ cb = tmp;
+ break;
+ }
+ }
+
+ if (!cb)
+ return FALSE;
+
+ stream->callbacks = g_slist_remove(stream->callbacks, cb);
+ g_free(cb);
+
+ return TRUE;
+}
+
+unsigned int avdtp_stream_add_cb(struct avdtp *session,
+ struct avdtp_stream *stream,
+ avdtp_stream_state_cb cb, void *data)
+{
+ struct stream_callback *stream_cb;
+ static unsigned int id = 0;
+
+ stream_cb = g_new(struct stream_callback, 1);
+ stream_cb->cb = cb;
+ stream_cb->user_data = data;
+ stream_cb->id = ++id;
+
+ stream->callbacks = g_slist_append(stream->callbacks, stream_cb);;
+
+ return stream_cb->id;
+}
+
+int avdtp_get_configuration(struct avdtp *session, struct avdtp_stream *stream)
+{
+ struct seid_req req;
+
+ if (session->state < AVDTP_SESSION_STATE_CONNECTED)
+ return -EINVAL;
+
+ memset(&req, 0, sizeof(req));
+ init_request(&req.header, AVDTP_GET_CONFIGURATION);
+ req.acp_seid = stream->rseid;
+
+ return send_request(session, FALSE, stream, &req, sizeof(req));
+}
+
+static void copy_capabilities(gpointer data, gpointer user_data)
+{
+ struct avdtp_service_capability *src_cap = data;
+ struct avdtp_service_capability *dst_cap;
+ GSList **l = user_data;
+
+ dst_cap = avdtp_service_cap_new(src_cap->category, src_cap->data,
+ src_cap->length);
+
+ *l = g_slist_append(*l, dst_cap);
+}
+
+int avdtp_set_configuration(struct avdtp *session,
+ struct avdtp_remote_sep *rsep,
+ struct avdtp_local_sep *lsep,
+ GSList *caps,
+ struct avdtp_stream **stream)
+{
+ struct setconf_req *req;
+ struct avdtp_stream *new_stream;
+ unsigned char *ptr;
+ int ret, caps_len;
+ struct avdtp_service_capability *cap;
+ GSList *l;
+
+ if (session->state != AVDTP_SESSION_STATE_CONNECTED)
+ return -ENOTCONN;
+
+ if (!(lsep && rsep))
+ return -EINVAL;
+
+ debug("avdtp_set_configuration(%p): int_seid=%u, acp_seid=%u",
+ session, lsep->info.seid, rsep->seid);
+
+ new_stream = g_new0(struct avdtp_stream, 1);
+
+ new_stream->session = session;
+ new_stream->lsep = lsep;
+ new_stream->rseid = rsep->seid;
+
+ g_slist_foreach(caps, copy_capabilities, &new_stream->caps);
+
+ /* Calculate total size of request */
+ for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) {
+ cap = l->data;
+ caps_len += cap->length + 2;
+ }
+
+ req = g_malloc0(sizeof(struct setconf_req) + caps_len);
+
+ init_request(&req->header, AVDTP_SET_CONFIGURATION);
+ req->int_seid = lsep->info.seid;
+ req->acp_seid = rsep->seid;
+
+ /* Copy the capabilities into the request */
+ for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) {
+ cap = l->data;
+ memcpy(ptr, cap, cap->length + 2);
+ ptr += cap->length + 2;
+ }
+
+ ret = send_request(session, FALSE, new_stream, req,
+ sizeof(struct setconf_req) + caps_len);
+ if (ret < 0)
+ stream_free(new_stream);
+ else {
+ lsep->info.inuse = 1;
+ lsep->stream = new_stream;
+ rsep->stream = new_stream;
+ session->streams = g_slist_append(session->streams, new_stream);
+ if (stream)
+ *stream = new_stream;
+ }
+
+ g_free(req);
+
+ return ret;
+}
+
+int avdtp_reconfigure(struct avdtp *session, GSList *caps,
+ struct avdtp_stream *stream)
+{
+ struct reconf_req *req;
+ unsigned char *ptr;
+ int caps_len;
+ GSList *l;
+ struct avdtp_service_capability *cap;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state != AVDTP_STATE_OPEN)
+ return -EINVAL;
+
+ /* Calculate total size of request */
+ for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) {
+ cap = l->data;
+ caps_len += cap->length + 2;
+ }
+
+ req = g_malloc0(sizeof(struct reconf_req) + caps_len);
+
+ init_request(&req->header, AVDTP_RECONFIGURE);
+ req->acp_seid = stream->rseid;
+
+ /* Copy the capabilities into the request */
+ for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) {
+ cap = l->data;
+ memcpy(ptr, cap, cap->length + 2);
+ ptr += cap->length + 2;
+ }
+
+ return send_request(session, FALSE, stream, req, sizeof(*req)
+ + caps_len);
+}
+
+int avdtp_open(struct avdtp *session, struct avdtp_stream *stream)
+{
+ struct seid_req req;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state > AVDTP_STATE_CONFIGURED)
+ return -EINVAL;
+
+ memset(&req, 0, sizeof(req));
+ init_request(&req.header, AVDTP_OPEN);
+ req.acp_seid = stream->rseid;
+
+ return send_request(session, FALSE, stream, &req, sizeof(req));
+}
+
+int avdtp_start(struct avdtp *session, struct avdtp_stream *stream)
+{
+ struct start_req req;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state != AVDTP_STATE_OPEN)
+ return -EINVAL;
+
+ memset(&req, 0, sizeof(req));
+ init_request(&req.header, AVDTP_START);
+ req.first_seid.seid = stream->rseid;
+
+ return send_request(session, FALSE, stream, &req, sizeof(req));
+}
+
+int avdtp_close(struct avdtp *session, struct avdtp_stream *stream)
+{
+ struct seid_req req;
+ int ret;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state < AVDTP_STATE_OPEN)
+ return -EINVAL;
+
+ memset(&req, 0, sizeof(req));
+ init_request(&req.header, AVDTP_CLOSE);
+ req.acp_seid = stream->rseid;
+
+ ret = send_request(session, FALSE, stream, &req, sizeof(req));
+ if (ret == 0)
+ stream->close_int = TRUE;
+
+ return ret;
+}
+
+int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream)
+{
+ struct seid_req req;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state <= AVDTP_STATE_OPEN)
+ return -EINVAL;
+
+ memset(&req, 0, sizeof(req));
+ init_request(&req.header, AVDTP_SUSPEND);
+ req.acp_seid = stream->rseid;
+
+ return send_request(session, FALSE, stream, &req, sizeof(req));
+}
+
+int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream)
+{
+ struct seid_req req;
+ int ret;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state <= AVDTP_STATE_OPEN)
+ return -EINVAL;
+
+ memset(&req, 0, sizeof(req));
+ init_request(&req.header, AVDTP_ABORT);
+ req.acp_seid = stream->rseid;
+
+ ret = send_request(session, FALSE, stream, &req, sizeof(req));
+ if (ret == 0)
+ avdtp_sep_set_state(session, stream->lsep,
+ AVDTP_STATE_ABORTING);
+
+ return 0;
+}
+
+struct avdtp_local_sep *avdtp_register_sep(uint8_t type, uint8_t media_type,
+ uint8_t codec_type,
+ struct avdtp_sep_ind *ind,
+ struct avdtp_sep_cfm *cfm,
+ void *user_data)
+{
+ struct avdtp_local_sep *sep;
+
+ if (free_seid > MAX_SEID)
+ return NULL;
+
+ sep = g_new0(struct avdtp_local_sep, 1);
+
+ sep->state = AVDTP_STATE_IDLE;
+ sep->info.seid = free_seid++;
+ sep->info.type = type;
+ sep->info.media_type = media_type;
+ sep->codec = codec_type;
+ sep->ind = ind;
+ sep->cfm = cfm;
+ sep->user_data = user_data;
+
+ debug("SEP %p registered: type:%d codec:%d seid:%d", sep,
+ sep->info.type, sep->codec, sep->info.seid);
+ local_seps = g_slist_append(local_seps, sep);
+
+ return sep;
+}
+
+int avdtp_unregister_sep(struct avdtp_local_sep *sep)
+{
+ if (!sep)
+ return -EINVAL;
+
+ if (sep->info.inuse)
+ return -EBUSY;
+
+ local_seps = g_slist_remove(local_seps, sep);
+
+ g_free(sep);
+
+ return 0;
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+ struct avdtp *session = user_data;
+ struct audio_device *dev;
+ GIOChannel *io;
+
+ if (derr && dbus_error_is_set(derr)) {
+ error("Access denied: %s", derr->message);
+ if (dbus_error_has_name(derr, DBUS_ERROR_NO_REPLY)) {
+ debug("Canceling authorization request");
+ service_cancel_auth(&session->src, &session->dst);
+ }
+
+ connection_lost(session, -EACCES);
+ return;
+ }
+
+ session->buf = g_malloc0(session->mtu);
+
+ set_disconnect_timer(session);
+
+ session->state = AVDTP_SESSION_STATE_CONNECTED;
+
+ dev = manager_find_device(&session->dst, AUDIO_CONTROL_INTERFACE,
+ FALSE);
+ if (dev)
+ avrcp_connect(dev);
+
+ g_source_remove(session->io);
+
+ io = g_io_channel_unix_new(session->sock);
+ session->io = g_io_add_watch(io,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) session_cb, session);
+ g_io_channel_unref(io);
+}
+
+static void avdtp_server_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer data)
+{
+ int sk;
+ socklen_t size;
+ struct l2cap_options l2o;
+ struct avdtp *session;
+ char address[18];
+
+ if (err < 0) {
+ error("accept: %s (%d)", strerror(-err), -err);
+ return;
+ }
+
+ sk = g_io_channel_unix_get_fd(chan);
+
+ ba2str(dst, address);
+ debug("AVDTP: incoming connect from %s", address);
+
+ memset(&l2o, 0, sizeof(l2o));
+ size = sizeof(l2o);
+ if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &size) < 0) {
+ error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(errno),
+ errno);
+ goto drop;
+ }
+
+ session = avdtp_get_internal(src, dst);
+
+ if (session->pending_open && session->pending_open->open_acp) {
+ handle_transport_connect(session, sk, l2o.imtu, l2o.omtu);
+ return;
+ }
+
+ if (session->sock >= 0) {
+ error("Refusing unexpected connect from %s", address);
+ goto drop;
+ }
+
+ session->mtu = l2o.imtu;
+ session->sock = sk;
+
+ session->io = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) session_cb, session);
+ g_io_channel_unref(chan);
+
+ if (service_req_auth(src, dst, ADVANCED_AUDIO_UUID, auth_cb,
+ session) < 0) {
+ avdtp_unref(session);
+ goto drop;
+ }
+
+ return;
+
+drop:
+ g_io_channel_close(chan);
+ g_io_channel_unref(chan);
+}
+
+static GIOChannel *avdtp_server_socket(gboolean master)
+{
+ int lm;
+
+ lm = L2CAP_LM_SECURE;
+
+ if (master)
+ lm |= L2CAP_LM_MASTER;
+
+ return bt_l2cap_listen(BDADDR_ANY, AVDTP_PSM, 0, lm, avdtp_server_cb,
+ NULL);
+}
+
+const char *avdtp_strerror(struct avdtp_error *err)
+{
+ if (err->type == AVDTP_ERROR_ERRNO)
+ return strerror(err->err.posix_errno);
+
+ switch(err->err.error_code) {
+ case AVDTP_BAD_HEADER_FORMAT:
+ return "Bad Header Format";
+ case AVDTP_BAD_LENGTH:
+ return "Bad Packet Lenght";
+ case AVDTP_BAD_ACP_SEID:
+ return "Bad Acceptor SEID";
+ case AVDTP_SEP_IN_USE:
+ return "Stream End Point in Use";
+ case AVDTP_SEP_NOT_IN_USE:
+ return "Stream End Point Not in Use";
+ case AVDTP_BAD_SERV_CATEGORY:
+ return "Bad Service Category";
+ case AVDTP_BAD_PAYLOAD_FORMAT:
+ return "Bad Payload format";
+ case AVDTP_NOT_SUPPORTED_COMMAND:
+ return "Command Not Supported";
+ case AVDTP_INVALID_CAPABILITIES:
+ return "Invalid Capabilities";
+ case AVDTP_BAD_RECOVERY_TYPE:
+ return "Bad Recovery Type";
+ case AVDTP_BAD_MEDIA_TRANSPORT_FORMAT:
+ return "Bad Media Transport Format";
+ case AVDTP_BAD_RECOVERY_FORMAT:
+ return "Bad Recovery Format";
+ case AVDTP_BAD_ROHC_FORMAT:
+ return "Bad Header Compression Format";
+ case AVDTP_BAD_CP_FORMAT:
+ return "Bad Content Protetion Format";
+ case AVDTP_BAD_MULTIPLEXING_FORMAT:
+ return "Bad Multiplexing Format";
+ case AVDTP_UNSUPPORTED_CONFIGURATION:
+ return "Configuration not supported";
+ case AVDTP_BAD_STATE:
+ return "Bad State";
+ default:
+ return "Unknow error";
+ }
+}
+
+avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep)
+{
+ return sep->state;
+}
+
+void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst)
+{
+ if (src)
+ bacpy(src, &session->src);
+ if (dst)
+ bacpy(dst, &session->dst);
+}
+
+int avdtp_init(GKeyFile *config)
+{
+ GError *err = NULL;
+ gboolean tmp, master = TRUE;
+
+ if (avdtp_server)
+ return 0;
+
+ if (config) {
+ tmp = g_key_file_get_boolean(config, "General",
+ "Master", &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ } else
+ master = tmp;
+ }
+
+ avdtp_server = avdtp_server_socket(master);
+ if (!avdtp_server)
+ return -1;
+
+ return 0;
+}
+
+void avdtp_exit(void)
+{
+ if (!avdtp_server)
+ return;
+
+ g_io_channel_close(avdtp_server);
+ g_io_channel_unref(avdtp_server);
+ avdtp_server = NULL;
+}
+
+gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream)
+{
+ return g_slist_find(session->streams, stream) ? TRUE : FALSE;
+}
diff --git a/audio/avdtp.h b/audio/avdtp.h
new file mode 100644
index 00000000..de7e6e3d
--- /dev/null
+++ b/audio/avdtp.h
@@ -0,0 +1,268 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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
+ *
+ */
+
+typedef enum {
+ AVDTP_ERROR_ERRNO,
+ AVDTP_ERROR_ERROR_CODE
+} avdtp_error_type_t;
+
+struct avdtp;
+struct avdtp_stream;
+struct avdtp_local_sep;
+struct avdtp_remote_sep;
+struct avdtp_error {
+ avdtp_error_type_t type;
+ union {
+ uint8_t error_code;
+ int posix_errno;
+ } err;
+};
+
+/* SEP capability categories */
+#define AVDTP_MEDIA_TRANSPORT 0x01
+#define AVDTP_REPORTING 0x02
+#define AVDTP_RECOVERY 0x03
+#define AVDTP_CONTENT_PROTECTION 0x04
+#define AVDTP_HEADER_COMPRESSION 0x05
+#define AVDTP_MULTIPLEXING 0x06
+#define AVDTP_MEDIA_CODEC 0x07
+
+/* AVDTP error definitions */
+#define AVDTP_BAD_HEADER_FORMAT 0x01
+#define AVDTP_BAD_LENGTH 0x11
+#define AVDTP_BAD_ACP_SEID 0x12
+#define AVDTP_SEP_IN_USE 0x13
+#define AVDTP_SEP_NOT_IN_USE 0x14
+#define AVDTP_BAD_SERV_CATEGORY 0x17
+#define AVDTP_BAD_PAYLOAD_FORMAT 0x18
+#define AVDTP_NOT_SUPPORTED_COMMAND 0x19
+#define AVDTP_INVALID_CAPABILITIES 0x1A
+#define AVDTP_BAD_RECOVERY_TYPE 0x22
+#define AVDTP_BAD_MEDIA_TRANSPORT_FORMAT 0x23
+#define AVDTP_BAD_RECOVERY_FORMAT 0x25
+#define AVDTP_BAD_ROHC_FORMAT 0x26
+#define AVDTP_BAD_CP_FORMAT 0x27
+#define AVDTP_BAD_MULTIPLEXING_FORMAT 0x28
+#define AVDTP_UNSUPPORTED_CONFIGURATION 0x29
+#define AVDTP_BAD_STATE 0x31
+
+/* SEP types definitions */
+#define AVDTP_SEP_TYPE_SOURCE 0x00
+#define AVDTP_SEP_TYPE_SINK 0x01
+
+/* Media types definitions */
+#define AVDTP_MEDIA_TYPE_AUDIO 0x00
+#define AVDTP_MEDIA_TYPE_VIDEO 0x01
+#define AVDTP_MEDIA_TYPE_MULTIMEDIA 0x02
+
+typedef enum {
+ AVDTP_STATE_IDLE,
+ AVDTP_STATE_CONFIGURED,
+ AVDTP_STATE_OPEN,
+ AVDTP_STATE_STREAMING,
+ AVDTP_STATE_CLOSING,
+ AVDTP_STATE_ABORTING,
+} avdtp_state_t;
+
+struct avdtp_service_capability {
+ uint8_t category;
+ uint8_t length;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avdtp_media_codec_capability {
+ uint8_t rfa0:4;
+ uint8_t media_type:4;
+ uint8_t media_codec_type;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avdtp_media_codec_capability {
+ uint8_t media_type:4;
+ uint8_t rfa0:4;
+ uint8_t media_codec_type;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+typedef void (*avdtp_stream_state_cb) (struct avdtp_stream *stream,
+ avdtp_state_t old_state,
+ avdtp_state_t new_state,
+ struct avdtp_error *err,
+ void *user_data);
+
+/* Callbacks for when a reply is received to a command that we sent */
+struct avdtp_sep_cfm {
+ void (*set_configuration) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err,
+ void *user_data);
+ void (*get_configuration) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err,
+ void *user_data);
+ void (*open) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream, struct avdtp_error *err,
+ void *user_data);
+ void (*start) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream, struct avdtp_error *err,
+ void *user_data);
+ void (*suspend) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err, void *user_data);
+ void (*close) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err, void *user_data);
+ void (*abort) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err, void *user_data);
+ void (*reconfigure) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err, void *user_data);
+};
+
+/* Callbacks for indicating when we received a new command. The return value
+ * indicates whether the command should be rejected or accepted */
+struct avdtp_sep_ind {
+ gboolean (*get_capability) (struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ GSList **caps, uint8_t *err,
+ void *user_data);
+ gboolean (*set_configuration) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ GSList *caps, uint8_t *err,
+ uint8_t *category, void *user_data);
+ gboolean (*get_configuration) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ uint8_t *err, void *user_data);
+ gboolean (*open) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data);
+ gboolean (*start) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data);
+ gboolean (*suspend) (struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data);
+ gboolean (*close) (struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data);
+ gboolean (*abort) (struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data);
+ gboolean (*reconfigure) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ uint8_t *err, void *user_data);
+};
+
+typedef void (*avdtp_discover_cb_t) (struct avdtp *session, GSList *seps,
+ struct avdtp_error *err, void *user_data);
+
+struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst);
+
+void avdtp_unref(struct avdtp *session);
+struct avdtp *avdtp_ref(struct avdtp *session);
+
+gboolean avdtp_is_connected(const bdaddr_t *src, const bdaddr_t *dst);
+
+struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category,
+ void *data, int size);
+
+struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep);
+
+int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb,
+ void *user_data);
+
+gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream);
+
+unsigned int avdtp_stream_add_cb(struct avdtp *session,
+ struct avdtp_stream *stream,
+ avdtp_stream_state_cb cb, void *data);
+gboolean avdtp_stream_remove_cb(struct avdtp *session,
+ struct avdtp_stream *stream,
+ unsigned int id);
+
+gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock,
+ uint16_t *imtu, uint16_t *omtu,
+ GSList **caps);
+
+gboolean avdtp_stream_has_capability(struct avdtp_stream *stream,
+ struct avdtp_service_capability *cap);
+gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream,
+ GSList *caps);
+
+int avdtp_set_configuration(struct avdtp *session,
+ struct avdtp_remote_sep *rsep,
+ struct avdtp_local_sep *lsep,
+ GSList *caps,
+ struct avdtp_stream **stream);
+
+int avdtp_get_configuration(struct avdtp *session,
+ struct avdtp_stream *stream);
+
+int avdtp_open(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_reconfigure(struct avdtp *session, GSList *caps,
+ struct avdtp_stream *stream);
+int avdtp_start(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_close(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream);
+
+struct avdtp_local_sep *avdtp_register_sep(uint8_t type, uint8_t media_type,
+ uint8_t codec_type,
+ struct avdtp_sep_ind *ind,
+ struct avdtp_sep_cfm *cfm,
+ 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);
+
+int avdtp_unregister_sep(struct avdtp_local_sep *sep);
+
+avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep);
+
+void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id);
+const char *avdtp_strerror(struct avdtp_error *err);
+avdtp_error_type_t avdtp_error_type(struct avdtp_error *err);
+int avdtp_error_error_code(struct avdtp_error *err);
+int avdtp_error_posix_errno(struct avdtp_error *err);
+
+void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst);
+
+int avdtp_init(GKeyFile *config);
+void avdtp_exit(void);
diff --git a/audio/control.c b/audio/control.c
new file mode 100644
index 00000000..9139213b
--- /dev/null
+++ b/audio/control.c
@@ -0,0 +1,949 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/l2cap.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "dbus-service.h"
+#include "logging.h"
+#include "uinput.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "control.h"
+#include "sdpd.h"
+#include "glib-helper.h"
+
+#define AVCTP_PSM 23
+
+/* Message types */
+#define AVCTP_COMMAND 0
+#define AVCTP_RESPONSE 1
+
+/* Packet types */
+#define AVCTP_PACKET_SINGLE 0
+#define AVCTP_PACKET_START 1
+#define AVCTP_PACKET_CONTINUE 2
+#define AVCTP_PACKET_END 3
+
+/* ctype entries */
+#define CTYPE_CONTROL 0x0
+#define CTYPE_STATUS 0x1
+#define CTYPE_ACCEPTED 0x9
+#define CTYPE_STABLE 0xC
+
+/* opcodes */
+#define OP_UNITINFO 0x30
+#define OP_SUBUNITINFO 0x31
+#define OP_PASSTHROUGH 0x7c
+
+/* subunits of interest */
+#define SUBUNIT_PANEL 0x09
+
+/* operands in passthrough commands */
+#define VOLUP_OP 0x41
+#define VOLDOWN_OP 0x42
+#define MUTE_OP 0x43
+
+#define PLAY_OP 0x44
+#define STOP_OP 0x45
+#define PAUSE_OP 0x46
+#define REWIND_OP 0x48
+#define FAST_FORWARD_OP 0x49
+#define NEXT_OP 0x4b
+#define PREV_OP 0x4c
+
+static DBusConnection *connection = NULL;
+
+static uint32_t tg_record_id = 0;
+static uint32_t ct_record_id = 0;
+
+static GIOChannel *avctp_server = NULL;
+
+static GSList *sessions = NULL;
+
+typedef enum {
+ AVCTP_STATE_DISCONNECTED = 0,
+ AVCTP_STATE_CONNECTING,
+ AVCTP_STATE_CONNECTED
+} avctp_state_t;
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avctp_header {
+ uint8_t ipid:1;
+ uint8_t cr:1;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+ uint16_t pid;
+} __attribute__ ((packed));
+
+struct avrcp_header {
+ uint8_t code:4;
+ uint8_t _hdr0:4;
+ uint8_t subunit_id:3;
+ uint8_t subunit_type:5;
+ uint8_t opcode;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avctp_header {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t cr:1;
+ uint8_t ipid:1;
+ uint16_t pid;
+} __attribute__ ((packed));
+
+struct avrcp_header {
+ uint8_t _hdr0:4;
+ uint8_t code:4;
+ uint8_t subunit_type:5;
+ uint8_t subunit_id:3;
+ uint8_t opcode;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct avctp {
+ struct audio_device *dev;
+
+ avctp_state_t state;
+
+ bdaddr_t src;
+ bdaddr_t dst;
+
+ int uinput;
+
+ int sock;
+
+ guint io;
+
+ uint16_t mtu;
+};
+
+struct control {
+ struct avctp *session;
+};
+
+static sdp_record_t *avrcp_ct_record()
+{
+ sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+ uuid_t root_uuid, l2cap, avctp, avrct;
+ sdp_profile_desc_t profile[1];
+ sdp_list_t *aproto, *proto[2];
+ sdp_record_t *record;
+ sdp_data_t *psm, *version, *features;
+ uint16_t lp = AVCTP_PSM, ver = 0x0103, feat = 0x000f;
+
+ record = sdp_record_alloc();
+ if (!record)
+ return NULL;
+
+ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+ root = sdp_list_append(0, &root_uuid);
+ sdp_set_browse_groups(record, root);
+
+ /* Service Class ID List */
+ sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID);
+ svclass_id = sdp_list_append(0, &avrct);
+ sdp_set_service_classes(record, svclass_id);
+
+ /* Protocol Descriptor List */
+ sdp_uuid16_create(&l2cap, L2CAP_UUID);
+ proto[0] = sdp_list_append(0, &l2cap);
+ psm = sdp_data_alloc(SDP_UINT16, &lp);
+ proto[0] = sdp_list_append(proto[0], psm);
+ apseq = sdp_list_append(0, proto[0]);
+
+ sdp_uuid16_create(&avctp, AVCTP_UUID);
+ proto[1] = sdp_list_append(0, &avctp);
+ version = sdp_data_alloc(SDP_UINT16, &ver);
+ proto[1] = sdp_list_append(proto[1], version);
+ apseq = sdp_list_append(apseq, proto[1]);
+
+ aproto = sdp_list_append(0, apseq);
+ sdp_set_access_protos(record, aproto);
+
+ /* Bluetooth Profile Descriptor List */
+ sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
+ profile[0].version = ver;
+ pfseq = sdp_list_append(0, &profile[0]);
+ sdp_set_profile_descs(record, pfseq);
+
+ features = sdp_data_alloc(SDP_UINT16, &feat);
+ sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+ sdp_set_info_attr(record, "AVRCP CT", 0, 0);
+
+ free(psm);
+ free(version);
+ sdp_list_free(proto[0], 0);
+ sdp_list_free(proto[1], 0);
+ sdp_list_free(apseq, 0);
+ sdp_list_free(pfseq, 0);
+ sdp_list_free(aproto, 0);
+ sdp_list_free(root, 0);
+ sdp_list_free(svclass_id, 0);
+
+ return record;
+}
+
+static sdp_record_t *avrcp_tg_record()
+{
+ sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+ uuid_t root_uuid, l2cap, avctp, avrtg;
+ sdp_profile_desc_t profile[1];
+ sdp_list_t *aproto, *proto[2];
+ sdp_record_t *record;
+ sdp_data_t *psm, *version, *features;
+ uint16_t lp = AVCTP_PSM, ver = 0x0103, feat = 0x000f;
+
+ record = sdp_record_alloc();
+ if (!record)
+ return NULL;
+
+ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+ root = sdp_list_append(0, &root_uuid);
+ sdp_set_browse_groups(record, root);
+
+ /* Service Class ID List */
+ sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID);
+ svclass_id = sdp_list_append(0, &avrtg);
+ sdp_set_service_classes(record, svclass_id);
+
+ /* Protocol Descriptor List */
+ sdp_uuid16_create(&l2cap, L2CAP_UUID);
+ proto[0] = sdp_list_append(0, &l2cap);
+ psm = sdp_data_alloc(SDP_UINT16, &lp);
+ proto[0] = sdp_list_append(proto[0], psm);
+ apseq = sdp_list_append(0, proto[0]);
+
+ sdp_uuid16_create(&avctp, AVCTP_UUID);
+ proto[1] = sdp_list_append(0, &avctp);
+ version = sdp_data_alloc(SDP_UINT16, &ver);
+ proto[1] = sdp_list_append(proto[1], version);
+ apseq = sdp_list_append(apseq, proto[1]);
+
+ aproto = sdp_list_append(0, apseq);
+ sdp_set_access_protos(record, aproto);
+
+ /* Bluetooth Profile Descriptor List */
+ sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
+ profile[0].version = ver;
+ pfseq = sdp_list_append(0, &profile[0]);
+ sdp_set_profile_descs(record, pfseq);
+
+ features = sdp_data_alloc(SDP_UINT16, &feat);
+ sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+ sdp_set_info_attr(record, "AVRCP TG", 0, 0);
+
+ free(psm);
+ free(version);
+ sdp_list_free(proto[0], 0);
+ sdp_list_free(proto[1], 0);
+ sdp_list_free(apseq, 0);
+ sdp_list_free(aproto, 0);
+ sdp_list_free(pfseq, 0);
+ sdp_list_free(root, 0);
+ sdp_list_free(svclass_id, 0);
+
+ return record;
+}
+
+static struct avctp *find_session(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ GSList *l;
+
+ for (l = sessions; l != NULL; l = g_slist_next(l)) {
+ struct avctp *s = l->data;
+
+ if (bacmp(src, &s->src) || bacmp(dst, &s->dst))
+ continue;
+
+ return s;
+ }
+
+ return NULL;
+}
+
+static struct avctp *avctp_get(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct avctp *session;
+
+ assert(src != NULL);
+ assert(dst != NULL);
+
+ session = find_session(src, dst);
+ if (session)
+ return session;
+
+ session = g_new0(struct avctp, 1);
+
+ session->uinput = -1;
+ session->sock = -1;
+ bacpy(&session->src, src);
+ bacpy(&session->dst, dst);
+
+ sessions = g_slist_append(sessions, session);
+
+ return session;
+}
+
+static int send_event(int fd, uint16_t type, uint16_t code, int32_t value)
+{
+ struct uinput_event event;
+
+ memset(&event, 0, sizeof(event));
+ event.type = type;
+ event.code = code;
+ event.value = value;
+
+ return write(fd, &event, sizeof(event));
+}
+
+static void send_key(int fd, uint16_t key, int pressed)
+{
+ if (fd < 0)
+ return;
+
+ send_event(fd, EV_KEY, key, pressed);
+ send_event(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static void handle_panel_passthrough(struct avctp *session,
+ const unsigned char *operands,
+ int operand_count)
+{
+ const char *status;
+ int pressed;
+
+ if (operand_count == 0)
+ return;
+
+ if (operands[0] & 0x80) {
+ status = "released";
+ pressed = 0;
+ } else {
+ status = "pressed";
+ pressed = 1;
+ }
+
+ switch (operands[0] & 0x7F) {
+ case PLAY_OP:
+ debug("AVRCP: PLAY %s", status);
+ send_key(session->uinput, KEY_PLAYPAUSE, pressed);
+ break;
+ case STOP_OP:
+ debug("AVRCP: STOP %s", status);
+ send_key(session->uinput, KEY_STOP, pressed);
+ break;
+ case PAUSE_OP:
+ debug("AVRCP: PAUSE %s", status);
+ send_key(session->uinput, KEY_PLAYPAUSE, pressed);
+ break;
+ case NEXT_OP:
+ debug("AVRCP: NEXT %s", status);
+ send_key(session->uinput, KEY_NEXTSONG, pressed);
+ break;
+ case PREV_OP:
+ debug("AVRCP: PREV %s", status);
+ send_key(session->uinput, KEY_PREVIOUSSONG, pressed);
+ break;
+ case REWIND_OP:
+ debug("AVRCP: REWIND %s", status);
+ send_key(session->uinput, KEY_REWIND, pressed);
+ break;
+ case FAST_FORWARD_OP:
+ debug("AVRCP: FAST FORWARD %s", status);
+ send_key(session->uinput, KEY_FORWARD, pressed);
+ break;
+ default:
+ debug("AVRCP: unknown button 0x%02X %s", operands[0] & 0x7F, status);
+ break;
+ }
+}
+
+static void avctp_unref(struct avctp *session)
+{
+ sessions = g_slist_remove(sessions, session);
+
+ if (session->state == AVCTP_STATE_CONNECTED)
+ g_dbus_emit_signal(session->dev->conn,
+ session->dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ "Disconnected",
+ DBUS_TYPE_INVALID);
+ if (session->sock >= 0)
+ close(session->sock);
+ if (session->io)
+ g_source_remove(session->io);
+
+ if (session->dev)
+ session->dev->control->session = NULL;
+
+ if (session->uinput >= 0) {
+ ioctl(session->uinput, UI_DEV_DESTROY);
+ close(session->uinput);
+ }
+
+ g_free(session);
+}
+
+static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct avctp *session = data;
+ unsigned char buf[1024], *operands;
+ struct avctp_header *avctp;
+ struct avrcp_header *avrcp;
+ int ret, packet_size, operand_count;
+
+ if (!(cond | G_IO_IN))
+ goto failed;
+
+ ret = read(session->sock, buf, sizeof(buf));
+ if (ret <= 0)
+ goto failed;
+
+ debug("Got %d bytes of data for AVCTP session %p", ret, session);
+
+ if (ret < sizeof(struct avctp_header)) {
+ error("Too small AVCTP packet");
+ goto failed;
+ }
+
+ packet_size = ret;
+
+ avctp = (struct avctp_header *) buf;
+
+ debug("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, "
+ "PID 0x%04X",
+ avctp->transaction, avctp->packet_type,
+ avctp->cr, avctp->ipid, ntohs(avctp->pid));
+
+ ret -= sizeof(struct avctp_header);
+ if (ret < sizeof(struct avrcp_header)) {
+ error("Too small AVRCP packet");
+ goto failed;
+ }
+
+ avrcp = (struct avrcp_header *) (buf + sizeof(struct avctp_header));
+
+ ret -= sizeof(struct avrcp_header);
+
+ operands = buf + sizeof(struct avctp_header) + sizeof(struct avrcp_header);
+ operand_count = ret;
+
+ debug("AVRCP %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
+ "opcode 0x%02X, %d operands",
+ avctp->cr ? "response" : "command",
+ avrcp->code, avrcp->subunit_type, avrcp->subunit_id,
+ avrcp->opcode, operand_count);
+
+ if (avctp->packet_type == AVCTP_PACKET_SINGLE &&
+ avctp->cr == AVCTP_COMMAND &&
+ avctp->pid == htons(AV_REMOTE_SVCLASS_ID) &&
+ avrcp->code == CTYPE_CONTROL &&
+ avrcp->subunit_type == SUBUNIT_PANEL &&
+ avrcp->opcode == OP_PASSTHROUGH) {
+ handle_panel_passthrough(session, operands, operand_count);
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->code = CTYPE_ACCEPTED;
+ ret = write(session->sock, buf, packet_size);
+ }
+
+ if (avctp->packet_type == AVCTP_PACKET_SINGLE &&
+ avctp->cr == AVCTP_COMMAND &&
+ avctp->pid == htons(AV_REMOTE_SVCLASS_ID) &&
+ avrcp->code == CTYPE_STATUS &&
+ (avrcp->opcode == OP_UNITINFO
+ || avrcp->opcode == OP_SUBUNITINFO)) {
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->code = CTYPE_STABLE;
+ debug("reply to %s", avrcp->opcode == OP_UNITINFO ?
+ "OP_UNITINFO" : "OP_SUBUNITINFO");
+ ret = write(session->sock, buf, packet_size);
+ }
+
+ return TRUE;
+
+failed:
+ debug("AVCTP session %p got disconnected", session);
+ avctp_unref(session);
+ return FALSE;
+}
+
+static int uinput_create(char *name)
+{
+ struct uinput_dev dev;
+ int fd, err;
+
+ fd = open("/dev/uinput", O_RDWR);
+ if (fd < 0) {
+ fd = open("/dev/input/uinput", O_RDWR);
+ if (fd < 0) {
+ fd = open("/dev/misc/uinput", O_RDWR);
+ if (fd < 0) {
+ err = errno;
+ error("Can't open input device: %s (%d)",
+ strerror(err), err);
+ return -err;
+ }
+ }
+ }
+
+ memset(&dev, 0, sizeof(dev));
+ if (name)
+ strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE);
+
+ dev.id.bustype = BUS_BLUETOOTH;
+ dev.id.vendor = 0x0000;
+ dev.id.product = 0x0000;
+ dev.id.version = 0x0000;
+
+ if (write(fd, &dev, sizeof(dev)) < 0) {
+ err = errno;
+ error("Can't write device information: %s (%d)",
+ strerror(err), err);
+ close(fd);
+ errno = err;
+ return -err;
+ }
+
+ ioctl(fd, UI_SET_EVBIT, EV_KEY);
+ ioctl(fd, UI_SET_EVBIT, EV_REL);
+ ioctl(fd, UI_SET_EVBIT, EV_REP);
+ ioctl(fd, UI_SET_EVBIT, EV_SYN);
+
+ ioctl(fd, UI_SET_KEYBIT, KEY_PLAYPAUSE);
+ ioctl(fd, UI_SET_KEYBIT, KEY_STOP);
+ ioctl(fd, UI_SET_KEYBIT, KEY_NEXTSONG);
+ ioctl(fd, UI_SET_KEYBIT, KEY_PREVIOUSSONG);
+ ioctl(fd, UI_SET_KEYBIT, KEY_REWIND);
+ ioctl(fd, UI_SET_KEYBIT, KEY_FORWARD);
+
+ if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) {
+ err = errno;
+ error("Can't create uinput device: %s (%d)",
+ strerror(err), err);
+ close(fd);
+ errno = err;
+ return -err;
+ }
+
+ return fd;
+}
+
+static void init_uinput(struct avctp *session)
+{
+ char address[18], *name;
+
+ ba2str(&session->dst, address);
+
+ name = session->dev->name ? session->dev->name : address;
+
+ session->uinput = uinput_create(name);
+ if (session->uinput < 0)
+ error("AVRCP: failed to init uinput for %s", name);
+ else
+ debug("AVRCP: uinput initialized for %s", name);
+}
+
+static void avctp_connect_session(struct avctp *session)
+{
+ GIOChannel *io;
+
+ session->state = AVCTP_STATE_CONNECTED;
+ session->dev = manager_device_connected(&session->dst,
+ AVRCP_TARGET_UUID);
+ session->dev->control->session = session;
+
+ init_uinput(session);
+
+ g_dbus_emit_signal(session->dev->conn, session->dev->path,
+ AUDIO_CONTROL_INTERFACE, "Connected",
+ DBUS_TYPE_INVALID);
+
+ if (session->io)
+ g_source_remove(session->io);
+
+ io = g_io_channel_unix_new(session->sock);
+ session->io = g_io_add_watch(io,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) session_cb, session);
+ g_io_channel_unref(io);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+ struct avctp *session = user_data;
+
+ if (derr && dbus_error_is_set(derr)) {
+ error("Access denied: %s", derr->message);
+ if (dbus_error_has_name(derr, DBUS_ERROR_NO_REPLY)) {
+ debug("Canceling authorization request");
+ service_cancel_auth(&session->src, &session->dst);
+ }
+
+ avctp_unref(session);
+ return;
+ }
+
+ avctp_connect_session(session);
+}
+
+static void avctp_server_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer data)
+{
+ socklen_t size;
+ struct l2cap_options l2o;
+ struct avctp *session;
+ GIOCondition flags = G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+ char address[18];
+
+ if (err < 0) {
+ error("AVCTP server socket: %s (%d)", strerror(-err), -err);
+ return;
+ }
+
+ session = avctp_get(src, dst);
+
+ if (!session) {
+ error("Unable to create new AVCTP session");
+ goto drop;
+ }
+
+ if (session->sock >= 0) {
+ error("Refusing unexpected connect from %s", address);
+ goto drop;
+ }
+
+ session->state = AVCTP_STATE_CONNECTING;
+ session->sock = g_io_channel_unix_get_fd(chan);
+
+ memset(&l2o, 0, sizeof(l2o));
+ size = sizeof(l2o);
+ if (getsockopt(session->sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &size) < 0) {
+ err = errno;
+ error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(err),
+ err);
+ avctp_unref(session);
+ goto drop;
+ }
+
+ session->mtu = l2o.imtu;
+
+ session->io = g_io_add_watch(chan, flags, (GIOFunc) session_cb,
+ session);
+ g_io_channel_unref(chan);
+
+ if (avdtp_is_connected(src, dst))
+ goto proceed;
+
+ if (service_req_auth(src, dst, AVRCP_TARGET_UUID, auth_cb, session) < 0)
+ goto drop;
+
+ return;
+
+proceed:
+ avctp_connect_session(session);
+
+ return;
+
+drop:
+ close(session->sock);
+}
+
+static GIOChannel *avctp_server_socket(gboolean master)
+{
+ int lm;
+ GIOChannel *io;
+
+ lm = L2CAP_LM_SECURE;
+
+ if (master)
+ lm |= L2CAP_LM_MASTER;
+
+ io = bt_l2cap_listen(BDADDR_ANY, AVCTP_PSM, 0, lm, avctp_server_cb,
+ NULL);
+ if (!io) {
+ error("Unable to allocate new io channel");
+ return NULL;
+ }
+
+ return io;
+}
+
+static void avctp_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer data)
+{
+ struct avctp *session = data;
+ struct l2cap_options l2o;
+ socklen_t len;
+ int sk;
+ char address[18];
+
+ if (err < 0) {
+ avctp_unref(session);
+ error("AVCTP connect(%s): %s (%d)", address, strerror(-err),
+ -err);
+ return;
+ }
+
+ ba2str(&session->dst, address);
+ debug("AVCTP: connected to %s", address);
+
+ g_io_channel_set_close_on_unref(chan, FALSE);
+ sk = g_io_channel_unix_get_fd(chan);
+ session->sock = sk;
+
+ memset(&l2o, 0, sizeof(l2o));
+ len = sizeof(l2o);
+ if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) {
+ err = errno;
+ avctp_unref(session);
+ error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(err),
+ err);
+ return;
+ }
+
+ init_uinput(session);
+
+ g_dbus_emit_signal(session->dev->conn, session->dev->path,
+ AUDIO_CONTROL_INTERFACE, "Connected",
+ DBUS_TYPE_INVALID);
+
+ session->state = AVCTP_STATE_CONNECTED;
+ session->mtu = l2o.imtu;
+ session->io = g_io_add_watch(chan,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) session_cb, session);
+}
+
+gboolean avrcp_connect(struct audio_device *dev)
+{
+ struct control *control = dev->control;
+ struct avctp *session;
+ int err;
+
+ if (control->session)
+ return TRUE;
+
+ session = avctp_get(&dev->src, &dev->dst);
+ if (!session) {
+ error("Unable to create new AVCTP session");
+ return FALSE;
+ }
+
+ session->dev = dev;
+ session->state = AVCTP_STATE_CONNECTING;
+
+ err = bt_l2cap_connect(&dev->src, &dev->dst, AVCTP_PSM, 0,
+ avctp_connect_cb, session);
+ if (err < 0) {
+ avctp_unref(session);
+ error("Connect failed. %s(%d)", strerror(-err), -err);
+ return FALSE;
+ }
+
+ control->session = session;
+
+ return TRUE;
+}
+
+void avrcp_disconnect(struct audio_device *dev)
+{
+ struct control *control = dev->control;
+ struct avctp *session = control->session;
+
+ if (!session)
+ return;
+
+ avctp_unref(session);
+ control->session = NULL;
+}
+
+int avrcp_init(DBusConnection *conn, GKeyFile *config)
+{
+ sdp_record_t *record;
+ gboolean tmp, master = TRUE;
+ GError *err = NULL;
+
+ if (avctp_server)
+ return 0;
+
+ if (config) {
+ tmp = g_key_file_get_boolean(config, "General",
+ "Master", &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ } else
+ master = tmp;
+ }
+
+ connection = dbus_connection_ref(conn);
+
+ record = avrcp_tg_record();
+ if (!record) {
+ error("Unable to allocate new service record");
+ return -1;
+ }
+
+ if (add_record_to_server(BDADDR_ANY, record) < 0) {
+ error("Unable to register AVRCP target service record");
+ sdp_record_free(record);
+ return -1;
+ }
+ tg_record_id = record->handle;
+
+ record = avrcp_ct_record();
+ if (!record) {
+ error("Unable to allocate new service record");
+ return -1;
+ }
+
+ if (add_record_to_server(BDADDR_ANY, record) < 0) {
+ error("Unable to register AVRCP controller service record");
+ sdp_record_free(record);
+ return -1;
+ }
+ ct_record_id = record->handle;
+
+ avctp_server = avctp_server_socket(master);
+ if (!avctp_server)
+ return -1;
+
+ return 0;
+}
+
+void avrcp_exit(void)
+{
+ if (!avctp_server)
+ return;
+
+ g_io_channel_close(avctp_server);
+ g_io_channel_unref(avctp_server);
+ avctp_server = NULL;
+
+ remove_record_from_server(ct_record_id);
+ ct_record_id = 0;
+
+ remove_record_from_server(tg_record_id);
+ tg_record_id = 0;
+
+ dbus_connection_unref(connection);
+ connection = NULL;
+}
+
+static DBusMessage *control_is_connected(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct control *control = device->control;
+ DBusMessage *reply;
+ dbus_bool_t connected;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ connected = (control->session != NULL);
+
+ dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static GDBusMethodTable control_methods[] = {
+ { "IsConnected", "", "b", control_is_connected },
+ { NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable control_signals[] = {
+ { "Connected", "" },
+ { "Disconnected", "" },
+ { NULL, NULL }
+};
+
+struct control *control_init(struct audio_device *dev)
+{
+ if (!g_dbus_register_interface(dev->conn, dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ control_methods, control_signals, NULL,
+ dev, NULL))
+ return NULL;
+
+ return g_new0(struct control, 1);
+}
+
+void control_free(struct audio_device *dev)
+{
+ struct control *control = dev->control;
+
+ if (control->session)
+ avctp_unref(control->session);
+
+ g_free(control);
+ dev->control = NULL;
+}
+
+gboolean control_is_active(struct audio_device *dev)
+{
+ struct control *control = dev->control;
+
+ if (control->session &&
+ control->session->state != AVCTP_STATE_DISCONNECTED)
+ return TRUE;
+
+ return FALSE;
+}
diff --git a/audio/control.h b/audio/control.h
new file mode 100644
index 00000000..bbb5128c
--- /dev/null
+++ b/audio/control.h
@@ -0,0 +1,35 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 AUDIO_CONTROL_INTERFACE "org.bluez.audio.Control"
+
+int avrcp_init(DBusConnection *conn, GKeyFile *config);
+void avrcp_exit(void);
+
+gboolean avrcp_connect(struct audio_device *dev);
+void avrcp_disconnect(struct audio_device *dev);
+
+struct control *control_init(struct audio_device *dev);
+void control_free(struct audio_device *dev);
+gboolean control_is_active(struct audio_device *dev);
diff --git a/audio/ctl_bluetooth.c b/audio/ctl_bluetooth.c
new file mode 100644
index 00000000..a87c3c19
--- /dev/null
+++ b/audio/ctl_bluetooth.c
@@ -0,0 +1,356 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; 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 <sys/socket.h>
+#include <sys/un.h>
+
+#include <alsa/asoundlib.h>
+#include <alsa/control_external.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include "ipc.h"
+
+#ifdef ENABLE_DEBUG
+#define DBG(fmt, arg...) printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg)
+#else
+#define DBG(fmt, arg...)
+#endif
+
+#define BLUETOOTH_MINVOL 0
+#define BLUETOOTH_MAXVOL 15
+
+struct bluetooth_data {
+ snd_ctl_ext_t ext;
+ int sock;
+};
+
+enum {
+ BLUETOOTH_PLAYBACK,
+ BLUETOOTH_CAPTURE,
+};
+
+static const char *vol_devices[2] = {
+ [BLUETOOTH_PLAYBACK] = "Playback volume",
+ [BLUETOOTH_CAPTURE] = "Capture volume",
+};
+
+static void bluetooth_exit(struct bluetooth_data *data)
+{
+ if (data == NULL)
+ return;
+
+ if (data->sock >= 0)
+ bt_audio_service_close(data->sock);
+
+ free(data);
+}
+
+static void bluetooth_close(snd_ctl_ext_t *ext)
+{
+ struct bluetooth_data *data = ext->private_data;
+
+ DBG("ext %p", ext);
+
+ bluetooth_exit(data);
+}
+
+static int bluetooth_elem_count(snd_ctl_ext_t *ext)
+{
+ DBG("ext %p", ext);
+
+ return 2;
+}
+
+static int bluetooth_elem_list(snd_ctl_ext_t *ext,
+ unsigned int offset, snd_ctl_elem_id_t *id)
+{
+ DBG("ext %p offset %d id %p", ext, offset, id);
+
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+
+ if (offset > 1)
+ return -EINVAL;
+
+ snd_ctl_elem_id_set_name(id, vol_devices[offset]);
+
+ return 0;
+}
+
+static snd_ctl_ext_key_t bluetooth_find_elem(snd_ctl_ext_t *ext,
+ const snd_ctl_elem_id_t *id)
+{
+ const char *name = snd_ctl_elem_id_get_name(id);
+ int i;
+
+ DBG("ext %p id %p name '%s'", ext, id, name);
+
+ for (i = 0; i < 2; i++)
+ if (strcmp(name, vol_devices[i]) == 0)
+ return i;
+
+ return SND_CTL_EXT_KEY_NOT_FOUND;
+}
+
+static int bluetooth_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+ int *type, unsigned int *acc, unsigned int *count)
+{
+ DBG("ext %p key %ld", ext, key);
+
+ *type = SND_CTL_ELEM_TYPE_INTEGER;
+ *acc = SND_CTL_EXT_ACCESS_READWRITE;
+ *count = 1;
+
+ return 0;
+}
+
+static int bluetooth_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+ long *imin, long *imax, long *istep)
+{
+ DBG("ext %p key %ld", ext, key);
+
+ *istep = 1;
+ *imin = BLUETOOTH_MINVOL;
+ *imax = BLUETOOTH_MAXVOL;
+
+ return 0;
+}
+
+static int bluetooth_send_ctl(struct bluetooth_data *data,
+ uint8_t mode, uint8_t key, struct bt_control_rsp *ctl_rsp)
+{
+ int ret;
+ struct bt_control_req *ctl_req = (void *) ctl_rsp;
+ const char *type;
+
+ memset(ctl_req, 0, BT_AUDIO_IPC_PACKET_SIZE);
+ ctl_req->h.msg_type = BT_CONTROL_REQ;
+ ctl_req->mode = mode;
+ ctl_req->key = key;
+
+ ret = send(data->sock, ctl_req, BT_AUDIO_IPC_PACKET_SIZE, MSG_NOSIGNAL);
+ if (ret <= 0) {
+ SYSERR("Unable to request new volume value to server");
+ return -errno;
+ }
+
+ ret = recv(data->sock, ctl_rsp, BT_AUDIO_IPC_PACKET_SIZE, 0);
+ if (ret <= 0) {
+ SNDERR("Unable to receive new volume value from server");
+ return -errno;
+ }
+
+ type = bt_audio_strmsg(ctl_rsp->rsp_h.msg_h.msg_type);
+ if (!type) {
+ SNDERR("Bogus message type %d "
+ "received from audio service",
+ ctl_rsp->rsp_h.msg_h.msg_type);
+ return -EINVAL;
+ }
+
+ if (ctl_rsp->rsp_h.msg_h.msg_type != BT_CONTROL_RSP) {
+ SNDERR("Unexpected message %s received", type);
+ return -EINVAL;
+ }
+
+ if (ctl_rsp->rsp_h.posix_errno != 0) {
+ SNDERR("BT_CONTROL failed : %s (%d)",
+ strerror(ctl_rsp->rsp_h.posix_errno),
+ ctl_rsp->rsp_h.posix_errno);
+ return -ctl_rsp->rsp_h.posix_errno;
+ }
+
+ return 0;
+}
+
+static int bluetooth_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+ long *value)
+{
+ struct bluetooth_data *data = ext->private_data;
+ int ret;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_control_rsp *rsp = (void *) buf;
+
+ DBG("ext %p key %ld", ext, key);
+
+ memset(buf, 0, sizeof(buf));
+ *value = 0;
+
+ ret = bluetooth_send_ctl(data, key, 0, rsp);
+ if (ret < 0)
+ goto done;
+
+ *value = rsp->key;
+done:
+ return ret;
+}
+
+static int bluetooth_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+ long *value)
+{
+ struct bluetooth_data *data = ext->private_data;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_control_rsp *rsp = (void *) buf;
+ long current;
+ int ret, keyvalue;
+
+ DBG("ext %p key %ld", ext, key);
+
+ ret = bluetooth_read_integer(ext, key, &current);
+ if (ret < 0)
+ return ret;
+
+ if (*value == current)
+ return 0;
+
+ while (*value != current) {
+ keyvalue = (*value > current) ? BT_CONTROL_KEY_VOL_UP :
+ BT_CONTROL_KEY_VOL_DOWN;
+
+ ret = bluetooth_send_ctl(data, key, keyvalue, rsp);
+ if (ret < 0)
+ break;
+
+ current = keyvalue;
+ }
+
+ return ret;
+}
+
+static int bluetooth_read_event(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id,
+ unsigned int *event_mask)
+{
+ struct bluetooth_data *data = ext->private_data;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_control_ind *ind = (void *) buf;
+ int ret;
+ const char *type;
+
+ DBG("ext %p id %p", ext, id);
+
+ memset(buf, 0, sizeof(buf));
+
+ ret = recv(data->sock, ind, BT_AUDIO_IPC_PACKET_SIZE, MSG_DONTWAIT);
+ type = bt_audio_strmsg(ind->h.msg_type);
+ if (!type) {
+ SNDERR("Bogus message type %d "
+ "received from audio service",
+ ind->h.msg_type);
+ return -EAGAIN;
+ }
+
+ if (ind->h.msg_type != BT_CONTROL_IND) {
+ SNDERR("Unexpected message %s received", type);
+ return -EAGAIN;
+ }
+
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+ snd_ctl_elem_id_set_name(id, ind->mode == BLUETOOTH_PLAYBACK ?
+ vol_devices[BLUETOOTH_PLAYBACK] :
+ vol_devices[BLUETOOTH_CAPTURE]);
+ *event_mask = SND_CTL_EVENT_MASK_VALUE;
+
+ return 1;
+}
+
+static snd_ctl_ext_callback_t bluetooth_callback = {
+ .close = bluetooth_close,
+ .elem_count = bluetooth_elem_count,
+ .elem_list = bluetooth_elem_list,
+ .find_elem = bluetooth_find_elem,
+ .get_attribute = bluetooth_get_attribute,
+ .get_integer_info = bluetooth_get_integer_info,
+ .read_integer = bluetooth_read_integer,
+ .write_integer = bluetooth_write_integer,
+ .read_event = bluetooth_read_event,
+};
+
+static int bluetooth_init(struct bluetooth_data *data)
+{
+ int sk;
+
+ if (!data)
+ return -EINVAL;
+
+ memset(data, 0, sizeof(struct bluetooth_data));
+
+ data->sock = -1;
+
+ sk = bt_audio_service_open();
+ if (sk < 0)
+ return -errno;
+
+ data->sock = sk;
+
+ return 0;
+}
+
+SND_CTL_PLUGIN_DEFINE_FUNC(bluetooth)
+{
+ struct bluetooth_data *data;
+ int err;
+
+ DBG("Bluetooth Control plugin");
+
+ data = malloc(sizeof(struct bluetooth_data));
+ if (!data) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ err = bluetooth_init(data);
+ if (err < 0)
+ goto error;
+
+ data->ext.version = SND_CTL_EXT_VERSION;
+ data->ext.card_idx = -1;
+
+ strncpy(data->ext.id, "bluetooth", sizeof(data->ext.id) - 1);
+ strncpy(data->ext.driver, "Bluetooth-Audio", sizeof(data->ext.driver) - 1);
+ strncpy(data->ext.name, "Bluetooth Audio", sizeof(data->ext.name) - 1);
+ strncpy(data->ext.longname, "Bluetooth Audio", sizeof(data->ext.longname) - 1);
+ strncpy(data->ext.mixername, "Bluetooth Audio", sizeof(data->ext.mixername) - 1);
+
+ data->ext.callback = &bluetooth_callback;
+ data->ext.poll_fd = data->sock;
+ data->ext.private_data = data;
+
+ err = snd_ctl_ext_create(&data->ext, name, mode);
+ if (err < 0)
+ goto error;
+
+ *handlep = data->ext.handle;
+
+ return 0;
+
+error:
+ bluetooth_exit(data);
+
+ return err;
+}
+
+SND_CTL_PLUGIN_SYMBOL(bluetooth);
diff --git a/audio/device.c b/audio/device.c
new file mode 100644
index 00000000..2f5f834f
--- /dev/null
+++ b/audio/device.c
@@ -0,0 +1,435 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "textfile.h"
+
+#include "error.h"
+#include "ipc.h"
+#include "device.h"
+#include "avdtp.h"
+#include "control.h"
+#include "headset.h"
+#include "sink.h"
+
+static DBusMessage *device_get_address(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ DBusMessage *reply;
+ char address[18], *ptr = address;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ ba2str(&device->dst, address);
+
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &ptr,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static char *get_dev_name(DBusConnection *conn, const bdaddr_t *src,
+ const bdaddr_t *bda)
+{
+ char address[18], filename[PATH_MAX + 1];
+
+ ba2str(src, address);
+
+ /* check if it is in the cache */
+ create_name(filename, PATH_MAX, STORAGEDIR, address, "names");
+
+ ba2str(bda, address);
+ return textfile_caseget(filename, address);
+}
+
+static DBusMessage *device_get_name(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *reply;
+ const char *name = dev->name ? dev->name : "";
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *device_get_adapter(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ DBusMessage *reply;
+ char address[18], *ptr = address;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ ba2str(&device->src, address);
+
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &ptr,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+
+static DBusMessage *device_get_connected(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessageIter iter, array_iter;
+ struct audio_device *device = data;
+ DBusMessage *reply;
+ const char *iface;
+
+ 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_TYPE_STRING_AS_STRING,
+ &array_iter);
+
+ if (device->headset &&
+ headset_get_state(device) >= HEADSET_STATE_CONNECTED) {
+ iface = AUDIO_HEADSET_INTERFACE;
+ dbus_message_iter_append_basic(&array_iter,
+ DBUS_TYPE_STRING, &iface);
+ }
+
+ dbus_message_iter_close_container(&iter, &array_iter);
+
+ return reply;
+}
+
+static GDBusMethodTable device_methods[] = {
+ { "GetAddress", "", "s", device_get_address },
+ { "GetName", "", "s", device_get_name },
+ { "GetAdapter", "", "s", device_get_adapter },
+ { "GetConnectedInterfaces", "", "as", device_get_connected },
+ { }
+};
+
+static void device_free(struct audio_device *dev)
+{
+ if (dev->headset)
+ headset_free(dev);
+
+ if (dev->sink)
+ sink_free(dev);
+
+ if (dev->control)
+ control_free(dev);
+
+ if (dev->conn)
+ dbus_connection_unref(dev->conn);
+
+ g_free(dev->adapter_path);
+ g_free(dev->path);
+ g_free(dev->name);
+
+ g_free(dev);
+}
+
+static void device_unregister(void *data)
+{
+ struct audio_device *device = data;
+
+ info("Unregistered device path:%s", device->path);
+
+ device_free(device);
+}
+
+struct audio_device *device_register(DBusConnection *conn,
+ const char *path, const bdaddr_t *bda)
+{
+ struct audio_device *dev;
+ bdaddr_t src;
+ int dev_id;
+
+ if (!conn || !path)
+ return NULL;
+
+ bacpy(&src, BDADDR_ANY);
+ dev_id = hci_get_route(&src);
+ if ((dev_id < 0) || (hci_devba(dev_id, &src) < 0))
+ return NULL;
+
+ dev = g_new0(struct audio_device, 1);
+
+ /* FIXME just to maintain compatibility */
+ dev->adapter_path = g_strdup_printf("/org/bluez/hci%d", dev_id);
+ if (!dev->adapter_path) {
+ device_free(dev);
+ return NULL;
+ }
+
+ if (!g_dbus_register_interface(conn, path,
+ AUDIO_DEVICE_INTERFACE,
+ device_methods, NULL, NULL,
+ dev, device_unregister)) {
+ error("Failed to register %s interface to %s",
+ AUDIO_DEVICE_INTERFACE, path);
+ device_free(dev);
+ return NULL;
+ }
+
+ dev->name = get_dev_name(conn, &src, bda);
+ dev->path = g_strdup(path);
+ bacpy(&dev->dst, bda);
+ bacpy(&dev->src, &src);
+ bacpy(&dev->store, &src);
+ dev->conn = dbus_connection_ref(conn);
+
+ return dev;
+}
+
+int device_store(struct audio_device *dev, gboolean is_default)
+{
+ char value[64];
+ char filename[PATH_MAX + 1];
+ char src_addr[18], dst_addr[18];
+ int offset = 0;
+
+ if (!dev->path)
+ return -EINVAL;
+
+ ba2str(&dev->dst, dst_addr);
+ ba2str(&dev->store, src_addr);
+
+ create_name(filename, PATH_MAX, STORAGEDIR, src_addr, "audio");
+ create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ if (is_default)
+ textfile_put(filename, "default", dst_addr);
+ if (dev->headset) {
+ snprintf(value, 64, "headset ");
+ offset += strlen("headset ");
+ }
+ if (dev->gateway) {
+ snprintf(value + offset, 64 - offset, "gateway ");
+ offset += strlen("gateway ");
+ }
+ if (dev->sink) {
+ snprintf(value + offset, 64 - offset, "sink ");
+ offset += strlen("sink ");
+ }
+ if (dev->source) {
+ snprintf(value + offset, 64 - offset, "source ");
+ offset += strlen("source ");
+ }
+ if (dev->control) {
+ snprintf(value + offset, 64 - offset, "control ");
+ offset += strlen("control ");
+ }
+ if (dev->target)
+ snprintf(value + offset, 64 - offset, "target");
+
+ return textfile_put(filename, dst_addr, value);
+}
+
+int device_remove_stored(struct audio_device *dev)
+{
+ char filename[PATH_MAX + 1];
+ char src_addr[18], dst_addr[18];
+
+ ba2str(&dev->dst, dst_addr);
+ ba2str(&dev->store, src_addr);
+
+ create_name(filename, PATH_MAX, STORAGEDIR, src_addr, "audio");
+
+ return textfile_del(filename, dst_addr);
+}
+
+void device_finish_sdp_transaction(struct audio_device *dev)
+{
+ char address[18], *addr_ptr = address;
+ DBusMessage *msg;
+
+ ba2str(&dev->dst, address);
+
+ msg = dbus_message_new_method_call("org.bluez", dev->adapter_path,
+ "org.bluez.Adapter",
+ "FinishRemoteServiceTransaction");
+ if (!msg) {
+ error("Unable to allocate new method call");
+ return;
+ }
+
+ dbus_message_append_args(msg, DBUS_TYPE_STRING, &addr_ptr,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(dev->conn, msg, NULL);
+
+ dbus_message_unref(msg);
+}
+
+#if 0
+static avdtp_state_t ipc_to_avdtp_state(uint8_t ipc_state)
+{
+ switch (ipc_state) {
+ case STATE_DISCONNECTED:
+ return AVDTP_STATE_IDLE;
+ case STATE_CONNECTING:
+ return AVDTP_STATE_CONFIGURED;
+ case STATE_CONNECTED:
+ return AVDTP_STATE_OPEN;
+ case STATE_STREAM_STARTING:
+ case STATE_STREAMING:
+ return AVDTP_STATE_STREAMING;
+ default:
+ error("Unknown ipc state");
+ return AVDTP_STATE_IDLE;
+ }
+}
+
+static headset_state_t ipc_to_hs_state(uint8_t ipc_state)
+{
+ switch (ipc_state) {
+ case STATE_DISCONNECTED:
+ return HEADSET_STATE_DISCONNECTED;
+ case STATE_CONNECTING:
+ return HEADSET_STATE_CONNECT_IN_PROGRESS;
+ case STATE_CONNECTED:
+ return HEADSET_STATE_CONNECTED;
+ case STATE_STREAM_STARTING:
+ return HEADSET_STATE_PLAY_IN_PROGRESS;
+ case STATE_STREAMING:
+ return HEADSET_STATE_PLAYING;
+ default:
+ error("Unknown ipc state");
+ return HEADSET_STATE_DISCONNECTED;
+ }
+}
+
+static uint8_t avdtp_to_ipc_state(avdtp_state_t state)
+{
+ switch (state) {
+ case AVDTP_STATE_IDLE:
+ return STATE_DISCONNECTED;
+ case AVDTP_STATE_CONFIGURED:
+ return STATE_CONNECTING;
+ case AVDTP_STATE_OPEN:
+ return STATE_CONNECTED;
+ case AVDTP_STATE_STREAMING:
+ return STATE_STREAMING;
+ default:
+ error("Unknown avdt state");
+ return AVDTP_STATE_IDLE;
+ }
+}
+
+static uint8_t hs_to_ipc_state(headset_state_t state)
+{
+ switch (state) {
+ case HEADSET_STATE_DISCONNECTED:
+ return STATE_DISCONNECTED;
+ case HEADSET_STATE_CONNECT_IN_PROGRESS:
+ return STATE_CONNECTING;
+ case HEADSET_STATE_CONNECTED:
+ return STATE_CONNECTED;
+ case HEADSET_STATE_PLAY_IN_PROGRESS:
+ return STATE_STREAMING;
+ default:
+ error("Unknown headset state");
+ return AVDTP_STATE_IDLE;
+ }
+}
+
+uint8_t device_get_state(struct audio_device *dev)
+{
+ avdtp_state_t sink_state;
+ headset_state_t hs_state;
+
+ if (dev->sink && sink_is_active(dev)) {
+ sink_state = sink_get_state(dev);
+ return avdtp_to_ipc_state(sink_state);
+ }
+ else if (dev->headset && headset_is_active(dev)) {
+ hs_state = headset_get_state(dev);
+ return hs_to_ipc_state(hs_state);
+ }
+ else if (dev->control && control_is_active(dev))
+ return STATE_CONNECTED;
+
+ return STATE_DISCONNECTED;
+}
+#endif
+
+gboolean device_is_connected(struct audio_device *dev, const char *interface)
+{
+ if (!interface) {
+ if ((dev->sink || dev->source) &&
+ avdtp_is_connected(&dev->src, &dev->dst))
+ return TRUE;
+
+ if (dev->headset && headset_is_active(dev))
+ return TRUE;
+ }
+ else if (!strcmp(interface, AUDIO_SINK_INTERFACE) && dev->sink &&
+ avdtp_is_connected(&dev->src, &dev->dst))
+ return TRUE;
+ else if (!strcmp(interface, AUDIO_SOURCE_INTERFACE) && dev->source &&
+ avdtp_is_connected(&dev->src, &dev->dst))
+ return TRUE;
+ else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset &&
+ headset_is_active(dev))
+ return TRUE;
+ else if (!strcmp(interface, AUDIO_CONTROL_INTERFACE) && dev->headset &&
+ control_is_active(dev))
+ return TRUE;
+
+ return FALSE;
+}
diff --git a/audio/device.h b/audio/device.h
new file mode 100644
index 00000000..5dc99b9d
--- /dev/null
+++ b/audio/device.h
@@ -0,0 +1,83 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 AUDIO_DEVICE_INTERFACE "org.bluez.audio.Device"
+
+#define GENERIC_AUDIO_UUID "00001203-0000-1000-8000-00805F9B34FB"
+
+#define HSP_HS_UUID "00001108-0000-1000-8000-00805F9B34FB"
+#define HSP_AG_UUID "00001112-0000-1000-8000-00805F9B34FB"
+
+#define HFP_HS_UUID "0000111E-0000-1000-8000-00805F9B34FB"
+#define HFP_AG_UUID "0000111F-0000-1000-8000-00805F9B34FB"
+
+#define ADVANCED_AUDIO_UUID "0000110D-0000-1000-8000-00805F9B34FB"
+
+#define A2DP_SOURCE_UUID "0000110A-0000-1000-8000-00805F9B34FB"
+#define A2DP_SINK_UUID "0000110B-0000-1000-8000-00805F9B34FB"
+
+#define AVRCP_REMOTE_UUID "0000110E-0000-1000-8000-00805F9B34FB"
+#define AVRCP_TARGET_UUID "0000110C-0000-1000-8000-00805F9B34FB"
+
+/* Move these to respective .h files once they exist */
+#define AUDIO_SOURCE_INTERFACE "org.bluez.audio.Source"
+#define AUDIO_CONTROL_INTERFACE "org.bluez.audio.Control"
+#define AUDIO_TARGET_INTERFACE "org.bluez.audio.Target"
+
+struct source;
+struct control;
+struct target;
+struct sink;
+struct headset;
+struct gateway;
+
+struct audio_device {
+ DBusConnection *conn;
+ char *adapter_path;
+ char *path;
+ char *name;
+ bdaddr_t store;
+ bdaddr_t src;
+ bdaddr_t dst;
+
+ struct headset *headset;
+ struct gateway *gateway;
+ struct sink *sink;
+ struct source *source;
+ struct control *control;
+ struct target *target;
+};
+
+struct audio_device *device_register(DBusConnection *conn,
+ const char *path, const bdaddr_t *bda);
+
+int device_store(struct audio_device *device, gboolean is_default);
+
+int device_remove_stored(struct audio_device *dev);
+
+void device_finish_sdp_transaction(struct audio_device *device);
+
+uint8_t device_get_state(struct audio_device *dev);
+
+gboolean device_is_connected(struct audio_device *dev, const char *interface);
diff --git a/audio/gateway.c b/audio/gateway.c
new file mode 100644
index 00000000..a299d28a
--- /dev/null
+++ b/audio/gateway.c
@@ -0,0 +1,34 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <stdint.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "gateway.h"
diff --git a/audio/gateway.h b/audio/gateway.h
new file mode 100644
index 00000000..12e413fd
--- /dev/null
+++ b/audio/gateway.h
@@ -0,0 +1,34 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 AUDIO_GATEWAY_INTERFACE "org.bluez.audio.Gateway"
+
+#define DEFAULT_HSP_HS_CHANNEL 6
+#define DEFAULT_HFP_HS_CHANNEL 7
+
+int gateway_init(DBusConnection *conn, gboolean disable_hfp, gboolean sco_hci);
+
+void gateway_exit(void);
+
+gboolean gateway_is_enabled(uint16_t svc);
diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c
new file mode 100644
index 00000000..d25cbded
--- /dev/null
+++ b/audio/gsta2dpsink.c
@@ -0,0 +1,701 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; 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 <unistd.h>
+#include <pthread.h>
+
+#include "gsta2dpsink.h"
+
+GST_DEBUG_CATEGORY_STATIC(gst_a2dp_sink_debug);
+#define GST_CAT_DEFAULT gst_a2dp_sink_debug
+
+#define A2DP_SBC_RTP_PAYLOAD_TYPE 1
+#define TEMPLATE_MAX_BITPOOL_STR "64"
+
+#define DEFAULT_AUTOCONNECT TRUE
+
+enum {
+ PROP_0,
+ PROP_DEVICE,
+ PROP_AUTOCONNECT
+};
+
+GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBin, GST_TYPE_BIN);
+
+static const GstElementDetails gst_a2dp_sink_details =
+ GST_ELEMENT_DETAILS("Bluetooth A2DP sink",
+ "Sink/Audio",
+ "Plays audio to an A2DP device",
+ "Marcel Holtmann <marcel@holtmann.org>");
+
+static GstStaticPadTemplate gst_a2dp_sink_factory =
+ GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS("audio/x-sbc, "
+ "rate = (int) { 16000, 32000, 44100, 48000 }, "
+ "channels = (int) [ 1, 2 ], "
+ "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, "
+ "blocks = (int) { 4, 8, 12, 16 }, "
+ "subbands = (int) { 4, 8 }, "
+ "allocation = (string) { \"snr\", \"loudness\" }, "
+ "bitpool = (int) [ 2, "
+ TEMPLATE_MAX_BITPOOL_STR " ]; "
+ "audio/mpeg"
+ ));
+
+static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event);
+static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps);
+static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad);
+static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self);
+static gboolean gst_a2dp_sink_init_fakesink(GstA2dpSink *self);
+static gboolean gst_a2dp_sink_remove_fakesink(GstA2dpSink *self);
+
+static void gst_a2dp_sink_finalize(GObject *obj)
+{
+ GstA2dpSink *self = GST_A2DP_SINK(obj);
+
+ g_mutex_free(self->cb_mutex);
+
+ G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static GstState gst_a2dp_sink_get_state(GstA2dpSink *self)
+{
+ GstState current, pending;
+
+ gst_element_get_state(GST_ELEMENT(self), &current, &pending, 0);
+ if (pending == GST_STATE_VOID_PENDING)
+ return current;
+
+ return pending;
+}
+
+/*
+ * Helper function to create elements, add to the bin and link it
+ * to another element.
+ */
+static GstElement* gst_a2dp_sink_init_element(GstA2dpSink *self,
+ const gchar* elementname, const gchar* name,
+ GstElement *link_to)
+{
+ GstElement *element;
+ GstState state;
+
+ GST_LOG_OBJECT(self, "Initializing %s", elementname);
+
+ element = gst_element_factory_make(elementname, name);
+ if (element == NULL) {
+ GST_DEBUG_OBJECT(self, "Couldn't create %s", elementname);
+ return NULL;
+ }
+
+ if (!gst_bin_add(GST_BIN(self), element)) {
+ GST_DEBUG_OBJECT(self, "failed to add %s to the bin",
+ elementname);
+ goto cleanup_and_fail;
+ }
+
+ state = gst_a2dp_sink_get_state(self);
+ if (gst_element_set_state(element, state) ==
+ GST_STATE_CHANGE_FAILURE) {
+ GST_DEBUG_OBJECT(self, "%s failed to go to playing",
+ elementname);
+ goto remove_element_and_fail;
+ }
+
+ if (link_to != NULL)
+ if (!gst_element_link(link_to, element)) {
+ GST_DEBUG_OBJECT(self, "couldn't link %s",
+ elementname);
+ goto remove_element_and_fail;
+ }
+
+ return element;
+
+remove_element_and_fail:
+ gst_element_set_state(element, GST_STATE_NULL);
+ gst_bin_remove(GST_BIN(self), element);
+ return NULL;
+
+cleanup_and_fail:
+ if (element != NULL)
+ g_object_unref(G_OBJECT(element));
+
+ return NULL;
+}
+
+static void gst_a2dp_sink_base_init(gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+ gst_element_class_set_details(element_class,
+ &gst_a2dp_sink_details);
+ gst_element_class_add_pad_template(element_class,
+ gst_static_pad_template_get(&gst_a2dp_sink_factory));
+}
+
+static void gst_a2dp_sink_set_property(GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ GstA2dpSink *self = GST_A2DP_SINK(object);
+
+ switch (prop_id) {
+ case PROP_DEVICE:
+ if (self->sink != NULL)
+ gst_avdtp_sink_set_device(self->sink,
+ g_value_get_string(value));
+
+ if (self->device != NULL)
+ g_free(self->device);
+ self->device = g_value_dup_string(value);
+ break;
+
+ case PROP_AUTOCONNECT:
+ self->autoconnect = g_value_get_boolean(value);
+
+ if (self->sink != NULL)
+ g_object_set(G_OBJECT(self->sink), "auto-connect",
+ self->autoconnect, NULL);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void gst_a2dp_sink_get_property(GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ GstA2dpSink *self = GST_A2DP_SINK(object);
+ gchar *device;
+
+ switch (prop_id) {
+ case PROP_DEVICE:
+ if (self->sink != NULL) {
+ device = gst_avdtp_sink_get_device(self->sink);
+ if (device != NULL)
+ g_value_take_string(value, device);
+ }
+ break;
+ case PROP_AUTOCONNECT:
+ if (self->sink != NULL)
+ g_object_get(G_OBJECT(self->sink), "auto-connect",
+ &self->autoconnect, NULL);
+
+ g_value_set_boolean(value, self->autoconnect);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean gst_a2dp_sink_init_ghost_pad(GstA2dpSink *self)
+{
+ GstPad *capsfilter_pad;
+
+ /* we search for the capsfilter sinkpad */
+ capsfilter_pad = gst_element_get_static_pad(self->capsfilter, "sink");
+
+ /* now we add a ghostpad */
+ self->ghostpad = GST_GHOST_PAD(gst_ghost_pad_new("sink",
+ capsfilter_pad));
+ g_object_unref(capsfilter_pad);
+
+ /* the getcaps of our ghostpad must reflect the device caps */
+ gst_pad_set_getcaps_function(GST_PAD(self->ghostpad),
+ gst_a2dp_sink_get_caps);
+ self->ghostpad_setcapsfunc = GST_PAD_SETCAPSFUNC(self->ghostpad);
+ gst_pad_set_setcaps_function(GST_PAD(self->ghostpad),
+ GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps));
+
+ /* we need to handle events on our own and we also need the eventfunc
+ * of the ghostpad for forwarding calls */
+ self->ghostpad_eventfunc = GST_PAD_EVENTFUNC(GST_PAD(self->ghostpad));
+ gst_pad_set_event_function(GST_PAD(self->ghostpad),
+ gst_a2dp_sink_handle_event);
+
+ if (!gst_element_add_pad(GST_ELEMENT(self), GST_PAD(self->ghostpad)))
+ GST_ERROR_OBJECT(self, "failed to add ghostpad");
+
+ return TRUE;
+}
+
+static void gst_a2dp_sink_remove_dynamic_elements(GstA2dpSink *self)
+{
+ if (self->rtp) {
+ GST_LOG_OBJECT(self, "removing rtp element from the bin");
+ if (!gst_bin_remove(GST_BIN(self), GST_ELEMENT(self->rtp)))
+ GST_WARNING_OBJECT(self, "failed to remove rtp "
+ "element from bin");
+ else
+ self->rtp = NULL;
+ }
+}
+
+static GstStateChangeReturn gst_a2dp_sink_change_state(GstElement *element,
+ GstStateChange transition)
+{
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+ GstA2dpSink *self = GST_A2DP_SINK(element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ self->taglist = gst_tag_list_new();
+
+ gst_a2dp_sink_init_fakesink(self);
+ break;
+
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ self->sink_is_in_bin = FALSE;
+ self->sink = GST_AVDTP_SINK(gst_element_factory_make(
+ "avdtpsink", "avdtpsink"));
+ if (self->sink == NULL) {
+ GST_WARNING_OBJECT(self, "failed to create avdtpsink");
+ return GST_STATE_CHANGE_FAILURE;
+ }
+
+ if (self->device != NULL)
+ gst_avdtp_sink_set_device(self->sink,
+ self->device);
+
+ g_object_set(G_OBJECT(self->sink), "auto-connect",
+ self->autoconnect, NULL);
+
+ ret = gst_element_set_state(GST_ELEMENT(self->sink),
+ GST_STATE_READY);
+ break;
+ default:
+ break;
+ }
+
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ ret = GST_ELEMENT_CLASS(parent_class)->change_state(element,
+ transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ if (self->taglist) {
+ gst_tag_list_free(self->taglist);
+ self->taglist = NULL;
+ }
+ if (self->newseg_event != NULL) {
+ gst_event_unref(self->newseg_event);
+ self->newseg_event = NULL;
+ }
+ gst_a2dp_sink_remove_fakesink(self);
+ break;
+
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ if (self->sink_is_in_bin) {
+ if (!gst_bin_remove(GST_BIN(self),
+ GST_ELEMENT(self->sink)))
+ GST_WARNING_OBJECT(self, "Failed to remove "
+ "avdtpsink from bin");
+ } else if (self->sink != NULL) {
+ gst_element_set_state(GST_ELEMENT(self->sink),
+ GST_STATE_NULL);
+ g_object_unref(G_OBJECT(self->sink));
+ }
+
+ self->sink = NULL;
+
+ gst_a2dp_sink_remove_dynamic_elements(self);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
+
+ parent_class = g_type_class_peek_parent(klass);
+
+ object_class->set_property = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sink_set_property);
+ object_class->get_property = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sink_get_property);
+
+ object_class->finalize = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sink_finalize);
+
+ element_class->change_state = GST_DEBUG_FUNCPTR(
+ gst_a2dp_sink_change_state);
+
+ g_object_class_install_property(object_class, PROP_DEVICE,
+ g_param_spec_string("device", "Device",
+ "Bluetooth remote device address",
+ NULL, G_PARAM_READWRITE));
+
+ g_object_class_install_property(object_class, PROP_AUTOCONNECT,
+ g_param_spec_boolean("auto-connect", "Auto-connect",
+ "Automatically attempt to connect to device",
+ DEFAULT_AUTOCONNECT, G_PARAM_READWRITE));
+
+ GST_DEBUG_CATEGORY_INIT(gst_a2dp_sink_debug, "a2dpsink", 0,
+ "A2DP sink element");
+}
+
+GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self)
+{
+ return gst_avdtp_sink_get_device_caps(self->sink);
+}
+
+static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad)
+{
+ GstCaps *caps;
+ GstCaps *caps_aux;
+ GstA2dpSink *self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
+
+ if (self->sink == NULL) {
+ GST_DEBUG_OBJECT(self, "a2dpsink isn't initialized "
+ "returning template caps");
+ caps = gst_static_pad_template_get_caps(
+ &gst_a2dp_sink_factory);
+ } else {
+ GST_LOG_OBJECT(self, "Getting device caps");
+ caps = gst_a2dp_sink_get_device_caps(self);
+ if (caps == NULL)
+ caps = gst_static_pad_template_get_caps(
+ &gst_a2dp_sink_factory);
+ }
+ caps_aux = gst_caps_copy(caps);
+ g_object_set(self->capsfilter, "caps", caps_aux, NULL);
+ gst_caps_unref(caps_aux);
+ return caps;
+}
+
+static gboolean gst_a2dp_sink_init_avdtp_sink(GstA2dpSink *self)
+{
+ GstElement *sink;
+
+ /* check if we don't need a new sink */
+ if (self->sink_is_in_bin)
+ return TRUE;
+
+ if (self->sink == NULL)
+ sink = gst_element_factory_make("avdtpsink", "avdtpsink");
+ else
+ sink = GST_ELEMENT(self->sink);
+
+ if (sink == NULL) {
+ GST_ERROR_OBJECT(self, "Couldn't create avdtpsink");
+ return FALSE;
+ }
+
+ if (!gst_bin_add(GST_BIN(self), sink)) {
+ GST_ERROR_OBJECT(self, "failed to add avdtpsink "
+ "to the bin");
+ goto cleanup_and_fail;
+ }
+
+ if (gst_element_set_state(sink, GST_STATE_READY) ==
+ GST_STATE_CHANGE_FAILURE) {
+ GST_ERROR_OBJECT(self, "avdtpsink failed to go to ready");
+ goto remove_element_and_fail;
+ }
+
+ if (!gst_element_link(GST_ELEMENT(self->rtp), sink)) {
+ GST_ERROR_OBJECT(self, "couldn't link rtpsbcpay "
+ "to avdtpsink");
+ goto remove_element_and_fail;
+ }
+
+ self->sink = GST_AVDTP_SINK(sink);
+ self->sink_is_in_bin = TRUE;
+ g_object_set(G_OBJECT(self->sink), "device", self->device, NULL);
+
+ gst_element_set_state(sink, GST_STATE_PAUSED);
+
+ return TRUE;
+
+remove_element_and_fail:
+ gst_element_set_state (sink, GST_STATE_NULL);
+ gst_bin_remove(GST_BIN(self), sink);
+ return FALSE;
+
+cleanup_and_fail:
+ if (sink != NULL)
+ g_object_unref(G_OBJECT(sink));
+
+ return FALSE;
+}
+
+static gboolean gst_a2dp_sink_init_rtp_sbc_element(GstA2dpSink *self)
+{
+ GstElement *rtppay;
+
+ /* if we already have a rtp, we don't need a new one */
+ if (self->rtp != NULL)
+ return TRUE;
+
+ rtppay = gst_a2dp_sink_init_element(self, "rtpsbcpay", "rtp",
+ self->capsfilter);
+ if (rtppay == NULL)
+ return FALSE;
+
+ self->rtp = GST_BASE_RTP_PAYLOAD(rtppay);
+ g_object_set(G_OBJECT(self->rtp), "min-frames", -1, NULL);
+
+ gst_element_set_state(rtppay, GST_STATE_PAUSED);
+
+ return TRUE;
+}
+
+static gboolean gst_a2dp_sink_init_rtp_mpeg_element(GstA2dpSink *self)
+{
+ GstElement *rtppay;
+
+ /* check if we don't need a new rtp */
+ if (self->rtp)
+ return TRUE;
+
+ GST_LOG_OBJECT(self, "Initializing rtp mpeg element");
+ /* if capsfilter is not created then we can't have our rtp element */
+ if (self->capsfilter == NULL)
+ return FALSE;
+
+ rtppay = gst_a2dp_sink_init_element(self, "rtpmpapay", "rtp",
+ self->capsfilter);
+ if (rtppay == NULL)
+ return FALSE;
+
+ self->rtp = GST_BASE_RTP_PAYLOAD(rtppay);
+
+ gst_element_set_state(rtppay, GST_STATE_PAUSED);
+
+ return TRUE;
+}
+
+static gboolean gst_a2dp_sink_init_dynamic_elements(GstA2dpSink *self,
+ GstCaps *caps)
+{
+ GstStructure *structure;
+ GstEvent *event;
+ GstPad *capsfilterpad;
+ gboolean crc;
+ gchar *mode = NULL;
+
+ structure = gst_caps_get_structure(caps, 0);
+
+ /* before everything we need to remove fakesink */
+ gst_a2dp_sink_remove_fakesink(self);
+
+ /* first, we need to create our rtp payloader */
+ if (gst_structure_has_name(structure, "audio/x-sbc")) {
+ GST_LOG_OBJECT(self, "sbc media received");
+ if (!gst_a2dp_sink_init_rtp_sbc_element(self))
+ return FALSE;
+ } else if (gst_structure_has_name(structure, "audio/mpeg")) {
+ GST_LOG_OBJECT(self, "mp3 media received");
+ if (!gst_a2dp_sink_init_rtp_mpeg_element(self))
+ return FALSE;
+ } else {
+ GST_ERROR_OBJECT(self, "Unexpected media type");
+ return FALSE;
+ }
+
+ if (!gst_a2dp_sink_init_avdtp_sink(self))
+ return FALSE;
+
+ /* check if we should push the taglist FIXME should we push this?
+ * we can send the tags directly if needed */
+ if (self->taglist != NULL &&
+ gst_structure_has_name(structure, "audio/mpeg")) {
+
+ event = gst_event_new_tag(self->taglist);
+
+ /* send directly the crc */
+ if (gst_tag_list_get_boolean(self->taglist, "has-crc", &crc))
+ gst_avdtp_sink_set_crc(self->sink, crc);
+
+ if (gst_tag_list_get_string(self->taglist, "channel-mode",
+ &mode))
+ gst_avdtp_sink_set_channel_mode(self->sink, mode);
+
+ capsfilterpad = gst_ghost_pad_get_target(self->ghostpad);
+ gst_pad_send_event(capsfilterpad, event);
+ self->taglist = NULL;
+ g_free(mode);
+ }
+
+ if (!gst_avdtp_sink_set_device_caps(self->sink, caps))
+ return FALSE;
+
+ g_object_set(G_OBJECT(self->rtp), "mtu",
+ gst_avdtp_sink_get_link_mtu(self->sink), NULL);
+
+ /* we forward our new segment here if we have one */
+ if (self->newseg_event) {
+ gst_pad_send_event(GST_BASE_RTP_PAYLOAD_SINKPAD(self->rtp),
+ self->newseg_event);
+ self->newseg_event = NULL;
+ }
+
+ return TRUE;
+}
+
+static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps)
+{
+ GstA2dpSink *self;
+
+ self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
+ GST_INFO_OBJECT(self, "setting caps");
+
+ /* now we know the caps */
+ gst_a2dp_sink_init_dynamic_elements(self, caps);
+
+ return self->ghostpad_setcapsfunc(GST_PAD(self->ghostpad), caps);
+}
+
+/* used for catching newsegment events while we don't have a sink, for
+ * later forwarding it to the sink */
+static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event)
+{
+ GstA2dpSink *self;
+ GstTagList *taglist = NULL;
+ GstObject *parent;
+
+ self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
+ parent = gst_element_get_parent(GST_ELEMENT(self->sink));
+
+ if (GST_EVENT_TYPE(event) == GST_EVENT_NEWSEGMENT &&
+ parent != GST_OBJECT_CAST(self)) {
+ if (self->newseg_event != NULL)
+ gst_event_unref(self->newseg_event);
+ self->newseg_event = gst_event_ref(event);
+
+ } else if (GST_EVENT_TYPE(event) == GST_EVENT_TAG &&
+ parent != GST_OBJECT_CAST(self)) {
+ if (self->taglist == NULL)
+ gst_event_parse_tag(event, &self->taglist);
+ else {
+ gst_event_parse_tag(event, &taglist);
+ gst_tag_list_insert(self->taglist, taglist,
+ GST_TAG_MERGE_REPLACE);
+ }
+ }
+
+ if (parent != NULL)
+ gst_object_unref(GST_OBJECT(parent));
+
+ return self->ghostpad_eventfunc(GST_PAD(self->ghostpad), event);
+}
+
+static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self)
+{
+ GstElement *element;
+
+ element = gst_element_factory_make("capsfilter", "filter");
+ if (element == NULL)
+ goto failed;
+
+ if (!gst_bin_add(GST_BIN(self), element))
+ goto failed;
+
+ self->capsfilter = element;
+ return TRUE;
+
+failed:
+ GST_ERROR_OBJECT(self, "Failed to initialize caps filter");
+ return FALSE;
+}
+
+static gboolean gst_a2dp_sink_init_fakesink(GstA2dpSink *self)
+{
+ if (self->fakesink != NULL)
+ return TRUE;
+
+ g_mutex_lock (self->cb_mutex);
+ self->fakesink = gst_a2dp_sink_init_element(self, "fakesink",
+ "fakesink", self->capsfilter);
+ g_mutex_unlock (self->cb_mutex);
+
+ if (!self->fakesink)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean gst_a2dp_sink_remove_fakesink(GstA2dpSink *self)
+{
+ g_mutex_lock(self->cb_mutex);
+
+ if (self->fakesink != NULL) {
+ gst_element_set_locked_state(self->fakesink, TRUE);
+ gst_element_set_state(self->fakesink, GST_STATE_NULL);
+
+ gst_bin_remove(GST_BIN(self), self->fakesink);
+ self->fakesink = NULL;
+ }
+
+ g_mutex_unlock(self->cb_mutex);
+
+ return TRUE;
+}
+
+static void gst_a2dp_sink_init(GstA2dpSink *self,
+ GstA2dpSinkClass *klass)
+{
+ self->sink = NULL;
+ self->fakesink = NULL;
+ self->rtp = NULL;
+ self->device = NULL;
+ self->autoconnect = DEFAULT_AUTOCONNECT;
+ self->capsfilter = NULL;
+ self->newseg_event = NULL;
+ self->taglist = NULL;
+ self->ghostpad = NULL;
+ self->sink_is_in_bin = FALSE;
+
+ self->cb_mutex = g_mutex_new();
+
+ /* we initialize our capsfilter */
+ gst_a2dp_sink_init_caps_filter(self);
+ g_object_set(self->capsfilter, "caps",
+ gst_static_pad_template_get_caps(&gst_a2dp_sink_factory),
+ NULL);
+
+ gst_a2dp_sink_init_fakesink(self);
+
+ gst_a2dp_sink_init_ghost_pad(self);
+
+}
+
+gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "a2dpsink",
+ GST_RANK_PRIMARY, GST_TYPE_A2DP_SINK);
+}
+
diff --git a/audio/gsta2dpsink.h b/audio/gsta2dpsink.h
new file mode 100644
index 00000000..13409232
--- /dev/null
+++ b/audio/gsta2dpsink.h
@@ -0,0 +1,83 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <gst/gst.h>
+#include <gst/rtp/gstbasertppayload.h>
+#include "gstavdtpsink.h"
+
+#ifndef __GST_A2DP_SINK_H__
+#define __GST_A2DP_SINK_H__
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_A2DP_SINK \
+ (gst_a2dp_sink_get_type())
+#define GST_A2DP_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_A2DP_SINK,GstA2dpSink))
+#define GST_A2DP_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_A2DP_SINK,GstA2dpSinkClass))
+#define GST_IS_A2DP_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_A2DP_SINK))
+#define GST_IS_A2DP_SINK_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_A2DP_SINK))
+
+typedef struct _GstA2dpSink GstA2dpSink;
+typedef struct _GstA2dpSinkClass GstA2dpSinkClass;
+
+struct _GstA2dpSink {
+ GstBin bin;
+
+ GstBaseRTPPayload *rtp;
+ GstAvdtpSink *sink;
+ GstElement *capsfilter;
+ GstElement *fakesink;
+
+ gchar *device;
+ gboolean autoconnect;
+ gboolean sink_is_in_bin;
+
+ GstGhostPad *ghostpad;
+ GstPadSetCapsFunction ghostpad_setcapsfunc;
+ GstPadEventFunction ghostpad_eventfunc;
+
+ GstEvent *newseg_event;
+ /* Store the tags received before the a2dpsender sink is created
+ * when it is created we forward this to it */
+ GstTagList *taglist;
+
+ GMutex *cb_mutex;
+};
+
+struct _GstA2dpSinkClass {
+ GstBinClass parent_class;
+};
+
+GType gst_a2dp_sink_get_type(void);
+gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin);
+
+GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self);
+
+G_END_DECLS
+
+#endif
+
diff --git a/audio/gstavdtpsink.c b/audio/gstavdtpsink.c
new file mode 100644
index 00000000..4c453ae9
--- /dev/null
+++ b/audio/gstavdtpsink.c
@@ -0,0 +1,1361 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; 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 <unistd.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <pthread.h>
+
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <gst/rtp/gstrtpbuffer.h>
+
+#include "ipc.h"
+#include "rtp.h"
+
+#include "gstavdtpsink.h"
+
+GST_DEBUG_CATEGORY_STATIC(avdtp_sink_debug);
+#define GST_CAT_DEFAULT avdtp_sink_debug
+
+#define BUFFER_SIZE 2048
+#define TEMPLATE_MAX_BITPOOL 64
+#define CRC_PROTECTED 1
+#define CRC_UNPROTECTED 0
+
+#define DEFAULT_AUTOCONNECT TRUE
+
+#define GST_AVDTP_SINK_MUTEX_LOCK(s) G_STMT_START { \
+ g_mutex_lock (s->sink_lock); \
+ } G_STMT_END
+
+#define GST_AVDTP_SINK_MUTEX_UNLOCK(s) G_STMT_START { \
+ g_mutex_unlock (s->sink_lock); \
+ } G_STMT_END
+
+
+struct bluetooth_data {
+ struct bt_getcapabilities_rsp caps; /* Bluetooth device caps */
+ guint link_mtu;
+
+ gchar buffer[BUFFER_SIZE]; /* Codec transfer buffer */
+};
+
+#define IS_SBC(n) (strcmp((n), "audio/x-sbc") == 0)
+#define IS_MPEG_AUDIO(n) (strcmp((n), "audio/mpeg") == 0)
+
+enum {
+ PROP_0,
+ PROP_DEVICE,
+ PROP_AUTOCONNECT
+};
+
+GST_BOILERPLATE(GstAvdtpSink, gst_avdtp_sink, GstBaseSink,
+ GST_TYPE_BASE_SINK);
+
+static const GstElementDetails avdtp_sink_details =
+ GST_ELEMENT_DETAILS("Bluetooth AVDTP sink",
+ "Sink/Audio",
+ "Plays audio to an A2DP device",
+ "Marcel Holtmann <marcel@holtmann.org>");
+
+static GstStaticPadTemplate avdtp_sink_factory =
+ GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS("application/x-rtp, "
+ "media = (string) \"audio\","
+ "payload = (int) "
+ GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
+ "clock-rate = (int) { 16000, 32000, "
+ "44100, 48000 }, "
+ "encoding-name = (string) \"SBC\"; "
+ "application/x-rtp, "
+ "media = (string) \"audio\", "
+ "payload = (int) "
+ GST_RTP_PAYLOAD_MPA_STRING ", "
+ "clock-rate = (int) 90000; "
+ "application/x-rtp, "
+ "media = (string) \"audio\", "
+ "payload = (int) "
+ GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
+ "clock-rate = (int) 90000, "
+ "encoding-name = (string) \"MPA\""
+ ));
+
+static GIOError gst_avdtp_sink_audioservice_send(GstAvdtpSink *self,
+ const bt_audio_msg_header_t *msg);
+static GIOError gst_avdtp_sink_audioservice_expect(
+ GstAvdtpSink *self,
+ bt_audio_msg_header_t *outmsg,
+ int expected_type);
+
+
+static void gst_avdtp_sink_base_init(gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+ gst_element_class_add_pad_template(element_class,
+ gst_static_pad_template_get(&avdtp_sink_factory));
+
+ gst_element_class_set_details(element_class, &avdtp_sink_details);
+}
+
+static gboolean gst_avdtp_sink_stop(GstBaseSink *basesink)
+{
+ GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+
+ GST_INFO_OBJECT(self, "stop");
+
+ if (self->watch_id != 0) {
+ g_source_remove(self->watch_id);
+ self->watch_id = 0;
+ }
+
+ if (self->server) {
+ bt_audio_service_close(g_io_channel_unix_get_fd(self->server));
+ g_io_channel_unref(self->server);
+ self->server = NULL;
+ }
+
+ if (self->stream) {
+ g_io_channel_flush(self->stream, NULL);
+ g_io_channel_close(self->stream);
+ g_io_channel_unref(self->stream);
+ self->stream = NULL;
+ }
+
+ if (self->data) {
+ g_free(self->data);
+ self->data = NULL;
+ }
+
+ if (self->stream_caps) {
+ gst_caps_unref(self->stream_caps);
+ self->stream_caps = NULL;
+ }
+
+ if (self->dev_caps) {
+ gst_caps_unref(self->dev_caps);
+ self->dev_caps = NULL;
+ }
+
+ return TRUE;
+}
+
+static void gst_avdtp_sink_finalize(GObject *object)
+{
+ GstAvdtpSink *self = GST_AVDTP_SINK(object);
+
+ if (self->data)
+ gst_avdtp_sink_stop(GST_BASE_SINK(self));
+
+ if (self->device)
+ g_free(self->device);
+
+ g_mutex_free(self->sink_lock);
+
+ G_OBJECT_CLASS(parent_class)->finalize(object);
+}
+
+static void gst_avdtp_sink_set_property(GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ GstAvdtpSink *sink = GST_AVDTP_SINK(object);
+
+ switch (prop_id) {
+ case PROP_DEVICE:
+ if (sink->device)
+ g_free(sink->device);
+ sink->device = g_value_dup_string(value);
+ break;
+
+ case PROP_AUTOCONNECT:
+ sink->autoconnect = g_value_get_boolean(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void gst_avdtp_sink_get_property(GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ GstAvdtpSink *sink = GST_AVDTP_SINK(object);
+
+ switch (prop_id) {
+ case PROP_DEVICE:
+ g_value_set_string(value, sink->device);
+ break;
+
+ case PROP_AUTOCONNECT:
+ g_value_set_boolean(value, sink->autoconnect);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static gint gst_avdtp_sink_bluetooth_recvmsg_fd(GstAvdtpSink *sink)
+{
+ int err, ret;
+
+ ret = bt_audio_service_get_data_fd(
+ g_io_channel_unix_get_fd(sink->server));
+
+ if (ret < 0) {
+ err = errno;
+ GST_ERROR_OBJECT(sink, "Unable to receive fd: %s (%d)",
+ strerror(err), err);
+ return -err;
+ }
+
+ sink->stream = g_io_channel_unix_new(ret);
+ GST_DEBUG_OBJECT(sink, "stream_fd=%d", ret);
+
+ return 0;
+}
+
+static gboolean gst_avdtp_sink_init_sbc_pkt_conf(GstAvdtpSink *sink,
+ GstCaps *caps,
+ sbc_capabilities_t *pkt)
+{
+ sbc_capabilities_t *cfg = &sink->data->caps.sbc_capabilities;
+ const GValue *value = NULL;
+ const char *pref, *name;
+ gint rate, subbands, blocks;
+ GstStructure *structure = gst_caps_get_structure(caps, 0);
+
+ name = gst_structure_get_name(structure);
+
+ if (!(IS_SBC(name))) {
+ GST_ERROR_OBJECT(sink, "Unexpected format %s, "
+ "was expecting sbc", name);
+ return FALSE;
+ }
+
+ value = gst_structure_get_value(structure, "rate");
+ rate = g_value_get_int(value);
+ if (rate == 44100)
+ cfg->frequency = BT_SBC_SAMPLING_FREQ_44100;
+ else if (rate == 48000)
+ cfg->frequency = BT_SBC_SAMPLING_FREQ_48000;
+ else if (rate == 32000)
+ cfg->frequency = BT_SBC_SAMPLING_FREQ_32000;
+ else if (rate == 16000)
+ cfg->frequency = BT_SBC_SAMPLING_FREQ_16000;
+ else {
+ GST_ERROR_OBJECT(sink, "Invalid rate while setting caps");
+ return FALSE;
+ }
+
+ value = gst_structure_get_value(structure, "mode");
+ pref = g_value_get_string(value);
+ if (strcmp(pref, "mono") == 0)
+ cfg->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
+ else if (strcmp(pref, "dual") == 0)
+ cfg->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
+ else if (strcmp(pref, "stereo") == 0)
+ cfg->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
+ else if (strcmp(pref, "joint") == 0)
+ cfg->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+ else {
+ GST_ERROR_OBJECT(sink, "Invalid mode %s", pref);
+ return FALSE;
+ }
+
+ value = gst_structure_get_value(structure, "allocation");
+ pref = g_value_get_string(value);
+ if (strcmp(pref, "loudness") == 0)
+ cfg->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS;
+ else if (strcmp(pref, "snr") == 0)
+ cfg->allocation_method = BT_A2DP_ALLOCATION_SNR;
+ else {
+ GST_ERROR_OBJECT(sink, "Invalid allocation: %s", pref);
+ return FALSE;
+ }
+
+ value = gst_structure_get_value(structure, "subbands");
+ subbands = g_value_get_int(value);
+ if (subbands == 8)
+ cfg->subbands = BT_A2DP_SUBBANDS_8;
+ else if (subbands == 4)
+ cfg->subbands = BT_A2DP_SUBBANDS_4;
+ else {
+ GST_ERROR_OBJECT(sink, "Invalid subbands %d", subbands);
+ return FALSE;
+ }
+
+ value = gst_structure_get_value(structure, "blocks");
+ blocks = g_value_get_int(value);
+ if (blocks == 16)
+ cfg->block_length = BT_A2DP_BLOCK_LENGTH_16;
+ else if (blocks == 12)
+ cfg->block_length = BT_A2DP_BLOCK_LENGTH_12;
+ else if (blocks == 8)
+ cfg->block_length = BT_A2DP_BLOCK_LENGTH_8;
+ else if (blocks == 4)
+ cfg->block_length = BT_A2DP_BLOCK_LENGTH_4;
+ else {
+ GST_ERROR_OBJECT(sink, "Invalid blocks %d", blocks);
+ return FALSE;
+ }
+
+ value = gst_structure_get_value(structure, "bitpool");
+ cfg->max_bitpool = cfg->min_bitpool = g_value_get_int(value);
+
+ memcpy(pkt, cfg, sizeof(*pkt));
+
+ return TRUE;
+}
+
+static gboolean gst_avdtp_sink_conf_recv_stream_fd(
+ GstAvdtpSink *self)
+{
+ struct bluetooth_data *data = self->data;
+ gint ret;
+ GIOError err;
+ GError *gerr = NULL;
+ GIOStatus status;
+ GIOFlags flags;
+ gsize read;
+
+ ret = gst_avdtp_sink_bluetooth_recvmsg_fd(self);
+ if (ret < 0)
+ return FALSE;
+
+ if (!self->stream) {
+ GST_ERROR_OBJECT(self, "Error while configuring device: "
+ "could not acquire audio socket");
+ return FALSE;
+ }
+
+ /* set stream socket to nonblock */
+ GST_LOG_OBJECT(self, "setting stream socket to nonblock");
+ flags = g_io_channel_get_flags(self->stream);
+ flags |= G_IO_FLAG_NONBLOCK;
+ status = g_io_channel_set_flags(self->stream, flags, &gerr);
+ if (status != G_IO_STATUS_NORMAL) {
+ if (gerr)
+ GST_WARNING_OBJECT(self, "Error while "
+ "setting server socket to nonblock: "
+ "%s", gerr->message);
+ else
+ GST_WARNING_OBJECT(self, "Error while "
+ "setting server "
+ "socket to nonblock");
+ }
+
+ /* It is possible there is some outstanding
+ data in the pipe - we have to empty it */
+ GST_LOG_OBJECT(self, "emptying stream pipe");
+ while (1) {
+ err = g_io_channel_read(self->stream, data->buffer,
+ (gsize) data->link_mtu,
+ &read);
+ if (err != G_IO_ERROR_NONE || read <= 0)
+ break;
+ }
+
+ /* set stream socket to block */
+ GST_LOG_OBJECT(self, "setting stream socket to block");
+ flags = g_io_channel_get_flags(self->stream);
+ flags &= ~G_IO_FLAG_NONBLOCK;
+ status = g_io_channel_set_flags(self->stream, flags, &gerr);
+ if (status != G_IO_STATUS_NORMAL) {
+ if (gerr)
+ GST_WARNING_OBJECT(self, "Error while "
+ "setting server socket to block:"
+ "%s", gerr->message);
+ else
+ GST_WARNING_OBJECT(self, "Error while "
+ "setting server "
+ "socket to block");
+ }
+
+ memset(data->buffer, 0, sizeof(data->buffer));
+
+ return TRUE;
+}
+
+static gboolean server_callback(GIOChannel *chan,
+ GIOCondition cond, gpointer data)
+{
+ GstAvdtpSink *sink;
+
+ if (cond & G_IO_HUP || cond & G_IO_NVAL)
+ return FALSE;
+ else if (cond & G_IO_ERR) {
+ sink = GST_AVDTP_SINK(data);
+ GST_WARNING_OBJECT(sink, "Untreated callback G_IO_ERR");
+ }
+
+ return TRUE;
+}
+
+static GstStructure *gst_avdtp_sink_parse_sbc_caps(
+ GstAvdtpSink *self, sbc_capabilities_t *sbc)
+{
+ 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_caps(
+ GstAvdtpSink *self, mpeg_capabilities_t *mpeg)
+{
+ 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_caps(GstAvdtpSink *self)
+{
+ sbc_capabilities_t *sbc = &self->data->caps.sbc_capabilities;
+ mpeg_capabilities_t *mpeg = &self->data->caps.mpeg_capabilities;
+ GstStructure *sbc_structure;
+ GstStructure *mpeg_structure;
+ gchar *tmp;
+
+ GST_LOG_OBJECT(self, "updating device caps");
+
+ sbc_structure = gst_avdtp_sink_parse_sbc_caps(self, sbc);
+ mpeg_structure = gst_avdtp_sink_parse_mpeg_caps(self, mpeg);
+
+ if (self->dev_caps != NULL)
+ gst_caps_unref(self->dev_caps);
+ self->dev_caps = gst_caps_new_full(sbc_structure, NULL);
+ if (mpeg_structure != NULL)
+ gst_caps_append_structure(self->dev_caps, mpeg_structure);
+
+ tmp = gst_caps_to_string(self->dev_caps);
+ GST_DEBUG_OBJECT(self, "Device capabilities: %s", tmp);
+ g_free(tmp);
+
+ return TRUE;
+}
+
+static gboolean gst_avdtp_sink_get_capabilities(GstAvdtpSink *self)
+{
+ gchar *buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_getcapabilities_req *req = (void *) buf;
+ struct bt_getcapabilities_rsp *rsp = (void *) buf;
+ GIOError io_error;
+
+ memset(req, 0, BT_AUDIO_IPC_PACKET_SIZE);
+
+ req->h.msg_type = BT_GETCAPABILITIES_REQ;
+ if (self->device == NULL)
+ return FALSE;
+ strncpy(req->device, self->device, 18);
+ if (self->autoconnect)
+ req->flags |= BT_FLAG_AUTOCONNECT;
+
+ io_error = gst_avdtp_sink_audioservice_send(self, &req->h);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error while asking device caps");
+ return FALSE;
+ }
+
+ io_error = gst_avdtp_sink_audioservice_expect(self,
+ &rsp->rsp_h.msg_h, BT_GETCAPABILITIES_RSP);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error while getting device caps");
+ return FALSE;
+ }
+
+ if (rsp->rsp_h.posix_errno != 0) {
+ GST_ERROR_OBJECT(self, "BT_GETCAPABILITIES failed : %s(%d)",
+ strerror(rsp->rsp_h.posix_errno),
+ rsp->rsp_h.posix_errno);
+ return FALSE;
+ }
+
+ memcpy(&self->data->caps, rsp, sizeof(*rsp));
+ if (!gst_avdtp_sink_update_caps(self)) {
+ GST_WARNING_OBJECT(self, "failed to update capabilities");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gint gst_avdtp_sink_get_channel_mode(const gchar *mode)
+{
+ if (strcmp(mode, "stereo") == 0)
+ return BT_A2DP_CHANNEL_MODE_STEREO;
+ else if (strcmp(mode, "joint") == 0)
+ return BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+ else if (strcmp(mode, "dual") == 0)
+ return BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
+ else if (strcmp(mode, "mono") == 0)
+ return BT_A2DP_CHANNEL_MODE_MONO;
+ else
+ return -1;
+}
+
+static void gst_avdtp_sink_tag(const GstTagList *taglist,
+ const gchar* tag, gpointer user_data)
+{
+ gboolean crc;
+ gchar *channel_mode = NULL;
+ GstAvdtpSink *self = GST_AVDTP_SINK(user_data);
+
+ if (strcmp(tag, "has-crc") == 0) {
+
+ if (!gst_tag_list_get_boolean(taglist, tag, &crc)) {
+ GST_WARNING_OBJECT(self, "failed to get crc tag");
+ return;
+ }
+
+ gst_avdtp_sink_set_crc(self, crc);
+
+ } else if (strcmp(tag, "channel-mode") == 0) {
+
+ if (!gst_tag_list_get_string(taglist, tag, &channel_mode)) {
+ GST_WARNING_OBJECT(self,
+ "failed to get channel-mode tag");
+ return;
+ }
+
+ self->channel_mode = gst_avdtp_sink_get_channel_mode(
+ channel_mode);
+ if (self->channel_mode == -1)
+ GST_WARNING_OBJECT(self, "Received invalid channel "
+ "mode: %s", channel_mode);
+ g_free(channel_mode);
+
+ } else
+ GST_DEBUG_OBJECT(self, "received unused tag: %s", tag);
+}
+
+static gboolean gst_avdtp_sink_event(GstBaseSink *basesink,
+ GstEvent *event)
+{
+ GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+ GstTagList *taglist = NULL;
+
+ if (GST_EVENT_TYPE(event) == GST_EVENT_TAG) {
+ /* we check the tags, mp3 has tags that are importants and
+ * are outside caps */
+ gst_event_parse_tag(event, &taglist);
+ gst_tag_list_foreach(taglist, gst_avdtp_sink_tag, self);
+ }
+
+ return TRUE;
+}
+
+static gboolean gst_avdtp_sink_start(GstBaseSink *basesink)
+{
+ GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+ gint sk;
+ gint err;
+
+ GST_INFO_OBJECT(self, "start");
+
+ self->watch_id = 0;
+
+ sk = bt_audio_service_open();
+ if (sk <= 0) {
+ err = errno;
+ GST_ERROR_OBJECT(self, "Cannot open connection to bt "
+ "audio service: %s %d", strerror(err), err);
+ goto failed;
+ }
+
+ self->server = g_io_channel_unix_new(sk);
+ 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);
+ memset(self->data, 0, sizeof(struct bluetooth_data));
+
+ 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");
+ goto failed;
+ }
+
+ return TRUE;
+
+failed:
+ bt_audio_service_close(sk);
+ return FALSE;
+}
+
+static gboolean gst_avdtp_sink_stream_start(GstAvdtpSink *self)
+{
+ gchar buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_streamstart_req *req = (void *) buf;
+ struct bt_streamstart_rsp *rsp = (void *) buf;
+ struct bt_streamfd_ind *ind = (void*) buf;
+ GIOError io_error;
+
+ GST_DEBUG_OBJECT(self, "stream start");
+
+ memset (req, 0, sizeof(buf));
+ req->h.msg_type = BT_STREAMSTART_REQ;
+
+ io_error = gst_avdtp_sink_audioservice_send(self, &req->h);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error ocurred while sending "
+ "start packet");
+ return FALSE;
+ }
+
+ GST_DEBUG_OBJECT(self, "stream start packet sent");
+
+ io_error = gst_avdtp_sink_audioservice_expect(self,
+ &rsp->rsp_h.msg_h, BT_STREAMSTART_RSP);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error while stream "
+ "start confirmation");
+ return FALSE;
+ }
+
+ if (rsp->rsp_h.posix_errno != 0) {
+ GST_ERROR_OBJECT(self, "BT_STREAMSTART_RSP failed : %s(%d)",
+ strerror(rsp->rsp_h.posix_errno),
+ rsp->rsp_h.posix_errno);
+ return FALSE;
+ }
+
+ GST_DEBUG_OBJECT(self, "stream started");
+
+ io_error = gst_avdtp_sink_audioservice_expect(self, &ind->h,
+ BT_STREAMFD_IND);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error while receiving "
+ "stream filedescriptor");
+ return FALSE;
+ }
+
+ if (!gst_avdtp_sink_conf_recv_stream_fd(self))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean gst_avdtp_sink_init_mp3_pkt_conf(
+ GstAvdtpSink *self, GstCaps *caps,
+ mpeg_capabilities_t *pkt)
+{
+ const GValue *value = NULL;
+ gint rate, layer;
+ const gchar* name;
+ GstStructure *structure = gst_caps_get_structure(caps, 0);
+
+ name = gst_structure_get_name(structure);
+
+ if (!(IS_MPEG_AUDIO(name))) {
+ GST_ERROR_OBJECT(self, "Unexpected format %s, "
+ "was expecting mp3", name);
+ return FALSE;
+ }
+
+ /* layer */
+ value = gst_structure_get_value(structure, "layer");
+ layer = g_value_get_int(value);
+ if (layer == 1)
+ pkt->layer = BT_MPEG_LAYER_1;
+ else if (layer == 2)
+ pkt->layer = BT_MPEG_LAYER_2;
+ else if (layer == 3)
+ pkt->layer = BT_MPEG_LAYER_3;
+ else {
+ GST_ERROR_OBJECT(self, "Unexpected layer: %d", layer);
+ return FALSE;
+ }
+
+ /* crc */
+ if (self->mp3_using_crc != -1)
+ pkt->crc = self->mp3_using_crc;
+ else {
+ GST_ERROR_OBJECT(self, "No info about crc was received, "
+ " can't proceed");
+ return FALSE;
+ }
+
+ /* channel mode */
+ if (self->channel_mode != -1)
+ pkt->channel_mode = self->channel_mode;
+ else {
+ GST_ERROR_OBJECT(self, "No info about channel mode "
+ "received, can't proceed");
+ return FALSE;
+ }
+
+ /* mpf - we will only use the mandatory one */
+ pkt->mpf = 0;
+
+ value = gst_structure_get_value(structure, "rate");
+ rate = g_value_get_int(value);
+ if (rate == 44100)
+ pkt->frequency = BT_MPEG_SAMPLING_FREQ_44100;
+ else if (rate == 48000)
+ pkt->frequency = BT_MPEG_SAMPLING_FREQ_48000;
+ else if (rate == 32000)
+ pkt->frequency = BT_MPEG_SAMPLING_FREQ_32000;
+ else if (rate == 24000)
+ pkt->frequency = BT_MPEG_SAMPLING_FREQ_24000;
+ else if (rate == 22050)
+ pkt->frequency = BT_MPEG_SAMPLING_FREQ_22050;
+ else if (rate == 16000)
+ pkt->frequency = BT_MPEG_SAMPLING_FREQ_16000;
+ else {
+ GST_ERROR_OBJECT(self, "Invalid rate while setting caps");
+ return FALSE;
+ }
+
+ /* vbr - we always say its vbr, we don't have how to know it */
+ pkt->bitrate = 0x8000;
+
+ return TRUE;
+}
+
+static gboolean gst_avdtp_sink_configure(GstAvdtpSink *self,
+ GstCaps *caps)
+{
+ gchar buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_setconfiguration_req *req = (void *) buf;
+ struct bt_setconfiguration_rsp *rsp = (void *) buf;
+ gboolean ret;
+ GIOError io_error;
+ gchar *temp;
+ GstStructure *structure;
+
+ temp = gst_caps_to_string(caps);
+ GST_DEBUG_OBJECT(self, "configuring device with caps: %s", temp);
+ g_free(temp);
+
+ memset (req, 0, sizeof(buf));
+ req->h.msg_type = BT_SETCONFIGURATION_REQ;
+ req->access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE;
+ strncpy(req->device, self->device, 18);
+ structure = gst_caps_get_structure(caps, 0);
+
+ if (gst_structure_has_name(structure, "audio/x-sbc"))
+ ret = gst_avdtp_sink_init_sbc_pkt_conf(self, caps,
+ &req->sbc_capabilities);
+ else if (gst_structure_has_name(structure, "audio/mpeg"))
+ ret = gst_avdtp_sink_init_mp3_pkt_conf(self, caps,
+ &req->mpeg_capabilities);
+ else
+ ret = FALSE;
+
+ if (!ret) {
+ GST_ERROR_OBJECT(self, "Couldn't parse caps "
+ "to packet configuration");
+ return FALSE;
+ }
+
+ io_error = gst_avdtp_sink_audioservice_send(self, &req->h);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error ocurred while sending "
+ "configurarion packet");
+ return FALSE;
+ }
+
+ GST_DEBUG_OBJECT(self, "configuration packet sent");
+
+ io_error = gst_avdtp_sink_audioservice_expect(self,
+ &rsp->rsp_h.msg_h, BT_SETCONFIGURATION_RSP);
+ if (io_error != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error while receiving device "
+ "confirmation");
+ return FALSE;
+ }
+
+ if (rsp->rsp_h.posix_errno != 0) {
+ GST_ERROR_OBJECT(self, "BT_SETCONFIGURATION_RSP failed : "
+ "%s(%d)",
+ strerror(rsp->rsp_h.posix_errno),
+ rsp->rsp_h.posix_errno);
+ return FALSE;
+ }
+
+ self->data->link_mtu = rsp->link_mtu;
+ GST_DEBUG_OBJECT(self, "configuration set");
+
+ return TRUE;
+}
+
+static GstFlowReturn gst_avdtp_sink_preroll(GstBaseSink *basesink,
+ GstBuffer *buffer)
+{
+ GstAvdtpSink *sink = GST_AVDTP_SINK(basesink);
+ gboolean ret;
+
+ GST_AVDTP_SINK_MUTEX_LOCK(sink);
+
+ ret = gst_avdtp_sink_stream_start(sink);
+
+ GST_AVDTP_SINK_MUTEX_UNLOCK(sink);
+
+ if (!ret)
+ return GST_FLOW_ERROR;
+
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn gst_avdtp_sink_render(GstBaseSink *basesink,
+ GstBuffer *buffer)
+{
+ GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+ gsize ret;
+ GIOError err;
+
+ err = g_io_channel_write(self->stream, (gchar*)GST_BUFFER_DATA(buffer),
+ (gsize)(GST_BUFFER_SIZE(buffer)), &ret);
+
+ if (err != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error while writting to socket: %d %s",
+ errno, strerror(errno));
+ return GST_FLOW_ERROR;
+ }
+
+ return GST_FLOW_OK;
+}
+
+static gboolean gst_avdtp_sink_unlock(GstBaseSink *basesink)
+{
+ GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+
+ if (self->stream != NULL)
+ g_io_channel_flush (self->stream, NULL);
+
+ return TRUE;
+}
+
+static GstFlowReturn gst_avdtp_sink_buffer_alloc(GstBaseSink *basesink,
+ guint64 offset, guint size, GstCaps* caps,
+ GstBuffer **buf)
+{
+ GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+
+ *buf = gst_buffer_new_and_alloc(size);
+ if (!(*buf)) {
+ GST_ERROR_OBJECT(self, "buffer allocation failed");
+ return GST_FLOW_ERROR;
+ }
+
+ gst_buffer_set_caps(*buf, caps);
+
+ GST_BUFFER_OFFSET(*buf) = offset;
+
+ return GST_FLOW_OK;
+}
+
+static void gst_avdtp_sink_class_init(GstAvdtpSinkClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS(klass);
+
+ parent_class = g_type_class_peek_parent(klass);
+
+ object_class->finalize = GST_DEBUG_FUNCPTR(
+ gst_avdtp_sink_finalize);
+ object_class->set_property = GST_DEBUG_FUNCPTR(
+ gst_avdtp_sink_set_property);
+ object_class->get_property = GST_DEBUG_FUNCPTR(
+ gst_avdtp_sink_get_property);
+
+ basesink_class->start = GST_DEBUG_FUNCPTR(gst_avdtp_sink_start);
+ basesink_class->stop = GST_DEBUG_FUNCPTR(gst_avdtp_sink_stop);
+ basesink_class->render = GST_DEBUG_FUNCPTR(
+ gst_avdtp_sink_render);
+ basesink_class->preroll = GST_DEBUG_FUNCPTR(
+ gst_avdtp_sink_preroll);
+ basesink_class->unlock = GST_DEBUG_FUNCPTR(
+ gst_avdtp_sink_unlock);
+ basesink_class->event = GST_DEBUG_FUNCPTR(
+ gst_avdtp_sink_event);
+
+ basesink_class->buffer_alloc =
+ GST_DEBUG_FUNCPTR(gst_avdtp_sink_buffer_alloc);
+
+ g_object_class_install_property(object_class, PROP_DEVICE,
+ g_param_spec_string("device", "Device",
+ "Bluetooth remote device address",
+ NULL, G_PARAM_READWRITE));
+
+ g_object_class_install_property(object_class, PROP_AUTOCONNECT,
+ g_param_spec_boolean("auto-connect",
+ "Auto-connect",
+ "Automatically attempt to connect "
+ "to device", DEFAULT_AUTOCONNECT,
+ G_PARAM_READWRITE));
+
+ GST_DEBUG_CATEGORY_INIT(avdtp_sink_debug, "avdtpsink", 0,
+ "A2DP headset sink element");
+}
+
+static void gst_avdtp_sink_init(GstAvdtpSink *self,
+ GstAvdtpSinkClass *klass)
+{
+ self->device = NULL;
+ self->data = NULL;
+
+ self->stream = NULL;
+
+ self->dev_caps = NULL;
+
+ self->autoconnect = DEFAULT_AUTOCONNECT;
+
+ self->sink_lock = g_mutex_new();
+
+ /* FIXME this is for not synchronizing with clock, should be tested
+ * with devices to see the behaviour
+ gst_base_sink_set_sync(GST_BASE_SINK(self), FALSE);
+ */
+}
+
+static GIOError gst_avdtp_sink_audioservice_send(
+ GstAvdtpSink *self,
+ const bt_audio_msg_header_t *msg)
+{
+ GIOError error;
+ gsize written;
+
+ error = g_io_channel_write(self->server, (const gchar*) msg,
+ BT_AUDIO_IPC_PACKET_SIZE, &written);
+ if (error != G_IO_ERROR_NONE)
+ GST_ERROR_OBJECT(self, "Error sending data to audio service:"
+ " %s(%d)", strerror(errno), errno);
+
+ return error;
+}
+
+static GIOError gst_avdtp_sink_audioservice_recv(
+ GstAvdtpSink *self,
+ bt_audio_msg_header_t *inmsg)
+{
+ GIOError status;
+ gsize bytes_read;
+ const char *type;
+
+ status = g_io_channel_read(self->server, (gchar*) inmsg,
+ BT_AUDIO_IPC_PACKET_SIZE, &bytes_read);
+ if (status != G_IO_ERROR_NONE) {
+ GST_ERROR_OBJECT(self, "Error receiving data from "
+ "audio service");
+ return status;
+ }
+
+ type = bt_audio_strmsg(inmsg->msg_type);
+ if (!type) {
+ status = G_IO_ERROR_INVAL;
+ GST_ERROR_OBJECT(self, "Bogus message type %d "
+ "received from audio service",
+ inmsg->msg_type);
+ }
+
+ return status;
+}
+
+static GIOError gst_avdtp_sink_audioservice_expect(
+ GstAvdtpSink *self, bt_audio_msg_header_t *outmsg,
+ int expected_type)
+{
+ GIOError status;
+
+ status = gst_avdtp_sink_audioservice_recv(self, outmsg);
+ if (status != G_IO_ERROR_NONE)
+ return status;
+
+ if (outmsg->msg_type != expected_type)
+ status = G_IO_ERROR_INVAL;
+
+ return status;
+}
+
+gboolean gst_avdtp_sink_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "avdtpsink",
+ GST_RANK_NONE, GST_TYPE_AVDTP_SINK);
+}
+
+
+/* public functions */
+GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink)
+{
+ if (sink->dev_caps == NULL)
+ return NULL;
+
+ return gst_caps_copy(sink->dev_caps);
+}
+
+gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *self,
+ GstCaps *caps)
+{
+ gboolean ret;
+
+ GST_DEBUG_OBJECT(self, "setting device caps");
+ GST_AVDTP_SINK_MUTEX_LOCK(self);
+ ret = gst_avdtp_sink_configure(self, caps);
+
+ if (self->stream_caps)
+ gst_caps_unref(self->stream_caps);
+ self->stream_caps = gst_caps_ref(caps);
+
+ GST_AVDTP_SINK_MUTEX_UNLOCK(self);
+
+ return ret;
+}
+
+guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink)
+{
+ return sink->data->link_mtu;
+}
+
+void gst_avdtp_sink_set_device(GstAvdtpSink *self, const gchar* dev)
+{
+ if (self->device != NULL)
+ g_free(self->device);
+
+ GST_LOG_OBJECT(self, "Setting device: %s", dev);
+ self->device = g_strdup(dev);
+}
+
+gchar *gst_avdtp_sink_get_device(GstAvdtpSink *self)
+{
+ return g_strdup(self->device);
+}
+
+void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc)
+{
+ gint new_crc;
+
+ new_crc = crc ? CRC_PROTECTED : CRC_UNPROTECTED;
+
+ /* test if we already received a different crc */
+ if (self->mp3_using_crc != -1 && new_crc != self->mp3_using_crc) {
+ GST_WARNING_OBJECT(self, "crc changed during stream");
+ return;
+ }
+ self->mp3_using_crc = new_crc;
+
+}
+
+void gst_avdtp_sink_set_channel_mode(GstAvdtpSink *self,
+ const gchar *mode)
+{
+ gint new_mode;
+
+ new_mode = gst_avdtp_sink_get_channel_mode(mode);
+
+ if (self->channel_mode != -1 && new_mode != self->channel_mode) {
+ GST_WARNING_OBJECT(self, "channel mode changed during stream");
+ return;
+ }
+
+ self->channel_mode = new_mode;
+ if (self->channel_mode == -1)
+ GST_WARNING_OBJECT(self, "Received invalid channel "
+ "mode: %s", mode);
+}
+
+
diff --git a/audio/gstavdtpsink.h b/audio/gstavdtpsink.h
new file mode 100644
index 00000000..5ebc0fee
--- /dev/null
+++ b/audio/gstavdtpsink.h
@@ -0,0 +1,101 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __GST_AVDTP_SINK_H
+#define __GST_AVDTP_SINK_H
+
+#include <gst/gst.h>
+#include <gst/base/gstbasesink.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_AVDTP_SINK \
+ (gst_avdtp_sink_get_type())
+#define GST_AVDTP_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AVDTP_SINK,\
+ GstAvdtpSink))
+#define GST_AVDTP_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AVDTP_SINK,\
+ GstAvdtpSinkClass))
+#define GST_IS_AVDTP_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AVDTP_SINK))
+#define GST_IS_AVDTP_SINK_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AVDTP_SINK))
+
+typedef struct _GstAvdtpSink GstAvdtpSink;
+typedef struct _GstAvdtpSinkClass GstAvdtpSinkClass;
+
+struct bluetooth_data;
+
+struct _GstAvdtpSink {
+ GstBaseSink sink;
+
+ gchar *device;
+ GIOChannel *stream;
+
+ struct bluetooth_data *data;
+ gboolean autoconnect;
+ GIOChannel *server;
+
+ /* mp3 stream data (outside caps data)*/
+ gint mp3_using_crc;
+ gint channel_mode;
+
+ /* stream connection data */
+ GstCaps *stream_caps;
+
+ GstCaps *dev_caps;
+
+ GMutex *sink_lock;
+
+ guint watch_id;
+};
+
+struct _GstAvdtpSinkClass {
+ GstBaseSinkClass parent_class;
+};
+
+GType gst_avdtp_sink_get_type(void);
+
+GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink);
+gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *sink,
+ GstCaps *caps);
+
+guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink);
+
+void gst_avdtp_sink_set_device(GstAvdtpSink *sink,
+ const gchar* device);
+
+gchar *gst_avdtp_sink_get_device(GstAvdtpSink *sink);
+
+gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin);
+
+void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc);
+
+void gst_avdtp_sink_set_channel_mode(GstAvdtpSink *self,
+ const gchar *mode);
+
+
+G_END_DECLS
+
+#endif /* __GST_AVDTP_SINK_H */
diff --git a/audio/gstbluetooth.c b/audio/gstbluetooth.c
new file mode 100644
index 00000000..ffc69768
--- /dev/null
+++ b/audio/gstbluetooth.c
@@ -0,0 +1,104 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; 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 <gst/gst.h>
+
+#include <string.h>
+
+#include "gstsbcutil.h"
+#include <sbc.h>
+
+#include "gstsbcenc.h"
+#include "gstsbcdec.h"
+#include "gstsbcparse.h"
+#include "gstavdtpsink.h"
+#include "gsta2dpsink.h"
+#include "gstrtpsbcpay.h"
+
+static GstStaticCaps sbc_caps = GST_STATIC_CAPS("audio/x-sbc");
+
+#define SBC_CAPS (gst_static_caps_get(&sbc_caps))
+
+static void sbc_typefind(GstTypeFind *tf, gpointer ignore)
+{
+ GstCaps *caps;
+ guint8 *aux;
+ sbc_t sbc;
+ guint8 *data = gst_type_find_peek(tf, 0, 32);
+
+ if (sbc_init(&sbc, 0) < 0)
+ return;
+
+ if (data == NULL || *data != 0x9c) /* SBC syncword */
+ return;
+
+ aux = g_new(guint8, 32);
+ memcpy(aux, data, 32);
+ sbc_parse(&sbc, aux, 32);
+ g_free(aux);
+ caps = gst_sbc_parse_caps_from_sbc(&sbc);
+ sbc_finish(&sbc);
+
+ gst_type_find_suggest(tf, GST_TYPE_FIND_POSSIBLE, caps);
+ gst_caps_unref(caps);
+}
+
+static gchar *sbc_exts[] = { "sbc", NULL };
+
+static gboolean plugin_init(GstPlugin *plugin)
+{
+ GST_INFO("Bluetooth plugin %s", VERSION);
+
+ if (gst_type_find_register(plugin, "sbc",
+ GST_RANK_PRIMARY, sbc_typefind, sbc_exts,
+ SBC_CAPS, NULL, NULL) == FALSE)
+ return FALSE;
+
+ if (!gst_sbc_enc_plugin_init(plugin))
+ return FALSE;
+
+ if (!gst_sbc_dec_plugin_init(plugin))
+ return FALSE;
+
+ if (!gst_sbc_parse_plugin_init(plugin))
+ return FALSE;
+
+ if (!gst_avdtp_sink_plugin_init(plugin))
+ return FALSE;
+
+ if (!gst_a2dp_sink_plugin_init(plugin))
+ return FALSE;
+
+ if (!gst_rtp_sbc_pay_plugin_init(plugin))
+ return FALSE;
+
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR,
+ "bluetooth", "Bluetooth plugin library",
+ plugin_init, VERSION, "LGPL", "BlueZ", "http://www.bluez.org/")
diff --git a/audio/gstrtpsbcpay.c b/audio/gstrtpsbcpay.c
new file mode 100644
index 00000000..3627ad0b
--- /dev/null
+++ b/audio/gstrtpsbcpay.c
@@ -0,0 +1,351 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; 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 "gstrtpsbcpay.h"
+#include <math.h>
+#include <string.h>
+
+#define RTP_SBC_PAYLOAD_HEADER_SIZE 1
+#define DEFAULT_MIN_FRAMES 0
+#define RTP_SBC_HEADER_TOTAL (12 + RTP_SBC_PAYLOAD_HEADER_SIZE)
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct rtp_payload {
+ guint8 frame_count:4;
+ guint8 rfa0:1;
+ guint8 is_last_fragment:1;
+ guint8 is_first_fragment:1;
+ guint8 is_fragmented:1;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct rtp_payload {
+ guint8 is_fragmented:1;
+ guint8 is_first_fragment:1;
+ guint8 is_last_fragment:1;
+ guint8 rfa0:1;
+ guint8 frame_count:4;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+enum {
+ PROP_0,
+ PROP_MIN_FRAMES
+};
+
+GST_DEBUG_CATEGORY_STATIC(gst_rtp_sbc_pay_debug);
+#define GST_CAT_DEFAULT gst_rtp_sbc_pay_debug
+
+GST_BOILERPLATE(GstRtpSBCPay, gst_rtp_sbc_pay, GstBaseRTPPayload,
+ GST_TYPE_BASE_RTP_PAYLOAD);
+
+static const GstElementDetails gst_rtp_sbc_pay_details =
+ GST_ELEMENT_DETAILS("RTP packet payloader",
+ "Codec/Payloader/Network",
+ "Payload SBC audio as RTP packets",
+ "Thiago Sousa Santos "
+ "<thiagoss@lcc.ufcg.edu.br>");
+
+static GstStaticPadTemplate gst_rtp_sbc_pay_sink_factory =
+ GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS("audio/x-sbc, "
+ "rate = (int) { 16000, 32000, 44100, 48000 }, "
+ "channels = (int) [ 1, 2 ], "
+ "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, "
+ "blocks = (int) { 4, 8, 12, 16 }, "
+ "subbands = (int) { 4, 8 }, "
+ "allocation = (string) { \"snr\", \"loudness\" }, "
+ "bitpool = (int) [ 2, 64 ]")
+ );
+
+static GstStaticPadTemplate gst_rtp_sbc_pay_src_factory =
+ GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS(
+ "application/x-rtp, "
+ "media = (string) \"audio\","
+ "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
+ "clock-rate = (int) { 16000, 32000, 44100, 48000 },"
+ "encoding-name = (string) \"SBC\"")
+ );
+
+static void gst_rtp_sbc_pay_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_rtp_sbc_pay_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gint gst_rtp_sbc_pay_get_frame_len(gint subbands, gint channels,
+ gint blocks, gint bitpool, const gchar* channel_mode)
+{
+ gint len;
+ gint join;
+
+ len = 4 + (4 * subbands * channels)/8;
+
+ if (strcmp(channel_mode, "mono") == 0 ||
+ strcmp(channel_mode, "dual") == 0)
+ len += ((blocks * channels * bitpool)+7) / 8;
+ else {
+ join = strcmp(channel_mode, "joint") == 0 ? 1 : 0;
+ len += ((join * subbands + blocks * bitpool)+7)/8;
+ }
+
+ return len;
+}
+
+static gboolean gst_rtp_sbc_pay_set_caps(GstBaseRTPPayload *payload,
+ GstCaps *caps)
+{
+ GstRtpSBCPay *sbcpay;
+ gint rate, subbands, channels, blocks, bitpool;
+ gint frame_len;
+ const gchar* channel_mode;
+ GstStructure *structure;
+
+ sbcpay = GST_RTP_SBC_PAY(payload);
+
+ structure = gst_caps_get_structure(caps, 0);
+ if (!gst_structure_get_int(structure, "rate", &rate))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "channels", &channels))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "blocks", &blocks))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "bitpool", &bitpool))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "subbands", &subbands))
+ return FALSE;
+
+ channel_mode = gst_structure_get_string(structure, "mode");
+ if (!channel_mode)
+ return FALSE;
+
+ frame_len = gst_rtp_sbc_pay_get_frame_len(subbands, channels, blocks,
+ bitpool, channel_mode);
+
+ sbcpay->frame_length = frame_len;
+
+ gst_basertppayload_set_options (payload, "audio", TRUE, "SBC", rate);
+
+ GST_DEBUG_OBJECT(payload, "calculated frame length: %d ", frame_len);
+
+ return gst_basertppayload_set_outcaps (payload, NULL);
+}
+
+static GstFlowReturn gst_rtp_sbc_pay_flush_buffers(GstRtpSBCPay *sbcpay)
+{
+ guint available;
+ guint max_payload;
+ GstBuffer* outbuf;
+ guint8 *payload_data;
+ guint frame_count;
+ guint payload_length;
+ struct rtp_payload *payload;
+
+ if (sbcpay->frame_length == 0) {
+ GST_ERROR_OBJECT(sbcpay, "Frame length is 0");
+ return GST_FLOW_ERROR;
+ }
+
+ available = gst_adapter_available(sbcpay->adapter);
+
+ max_payload = gst_rtp_buffer_calc_payload_len(
+ GST_BASE_RTP_PAYLOAD_MTU(sbcpay) - RTP_SBC_PAYLOAD_HEADER_SIZE,
+ 0, 0);
+
+ max_payload = MIN(max_payload, available);
+ frame_count = max_payload / sbcpay->frame_length;
+ payload_length = frame_count * sbcpay->frame_length;
+ if (payload_length == 0) /* Nothing to send */
+ return GST_FLOW_OK;
+
+ outbuf = gst_rtp_buffer_new_allocate(payload_length +
+ RTP_SBC_PAYLOAD_HEADER_SIZE, 0, 0);
+
+ gst_rtp_buffer_set_payload_type(outbuf,
+ GST_BASE_RTP_PAYLOAD_PT(sbcpay));
+
+ payload_data = gst_rtp_buffer_get_payload(outbuf);
+ payload = (struct rtp_payload*) payload_data;
+ memset(payload, 0, sizeof(struct rtp_payload));
+ payload->frame_count = frame_count;
+
+ gst_adapter_copy(sbcpay->adapter, payload_data +
+ RTP_SBC_PAYLOAD_HEADER_SIZE, 0, payload_length);
+ gst_adapter_flush(sbcpay->adapter, payload_length);
+
+ GST_BUFFER_TIMESTAMP(outbuf) = sbcpay->timestamp;
+ GST_DEBUG_OBJECT (sbcpay, "Pushing %d bytes", payload_length);
+
+ return gst_basertppayload_push(GST_BASE_RTP_PAYLOAD(sbcpay), outbuf);
+}
+
+static GstFlowReturn gst_rtp_sbc_pay_handle_buffer(GstBaseRTPPayload *payload,
+ GstBuffer *buffer)
+{
+ GstRtpSBCPay *sbcpay;
+ guint available;
+
+ /* FIXME check for negotiation */
+
+ sbcpay = GST_RTP_SBC_PAY(payload);
+ sbcpay->timestamp = GST_BUFFER_TIMESTAMP(buffer);
+
+ gst_adapter_push(sbcpay->adapter, buffer);
+
+ available = gst_adapter_available(sbcpay->adapter);
+ if (available + RTP_SBC_HEADER_TOTAL >=
+ GST_BASE_RTP_PAYLOAD_MTU(sbcpay) ||
+ (sbcpay->min_frames != -1 && available >
+ (sbcpay->min_frames * sbcpay->frame_length)))
+ return gst_rtp_sbc_pay_flush_buffers(sbcpay);
+
+ return GST_FLOW_OK;
+}
+
+static gboolean gst_rtp_sbc_pay_handle_event(GstPad *pad,
+ GstEvent *event)
+{
+ GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY(GST_PAD_PARENT(pad));
+
+ switch (GST_EVENT_TYPE(event)) {
+ case GST_EVENT_EOS:
+ gst_rtp_sbc_pay_flush_buffers(sbcpay);
+ break;
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static void gst_rtp_sbc_pay_base_init(gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+ gst_element_class_add_pad_template(element_class,
+ gst_static_pad_template_get(&gst_rtp_sbc_pay_sink_factory));
+ gst_element_class_add_pad_template(element_class,
+ gst_static_pad_template_get(&gst_rtp_sbc_pay_src_factory));
+
+ gst_element_class_set_details(element_class, &gst_rtp_sbc_pay_details);
+}
+
+static void gst_rtp_sbc_pay_finalize(GObject *object)
+{
+ GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY(object);
+ g_object_unref (sbcpay->adapter);
+
+ GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
+}
+
+static void gst_rtp_sbc_pay_class_init(GstRtpSBCPayClass *klass)
+{
+ GObjectClass *gobject_class;
+ GstBaseRTPPayloadClass *payload_class =
+ GST_BASE_RTP_PAYLOAD_CLASS(klass);
+
+ gobject_class = G_OBJECT_CLASS(klass);
+ parent_class = g_type_class_peek_parent(klass);
+
+ gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_rtp_sbc_pay_finalize);
+ gobject_class->set_property = GST_DEBUG_FUNCPTR(
+ gst_rtp_sbc_pay_set_property);
+ gobject_class->get_property = GST_DEBUG_FUNCPTR(
+ gst_rtp_sbc_pay_get_property);
+
+ payload_class->set_caps = GST_DEBUG_FUNCPTR(gst_rtp_sbc_pay_set_caps);
+ payload_class->handle_buffer = GST_DEBUG_FUNCPTR(
+ gst_rtp_sbc_pay_handle_buffer);
+ payload_class->handle_event = GST_DEBUG_FUNCPTR(
+ gst_rtp_sbc_pay_handle_event);
+
+ /* properties */
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ PROP_MIN_FRAMES,
+ g_param_spec_int ("min-frames", "minimum frame number",
+ "Minimum quantity of frames to send in one packet "
+ "(-1 for maximum allowed by the mtu)",
+ -1, G_MAXINT, DEFAULT_MIN_FRAMES, G_PARAM_READWRITE));
+
+ GST_DEBUG_CATEGORY_INIT(gst_rtp_sbc_pay_debug, "rtpsbcpay", 0,
+ "RTP SBC payloader");
+}
+
+static void gst_rtp_sbc_pay_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstRtpSBCPay *sbcpay;
+
+ sbcpay = GST_RTP_SBC_PAY (object);
+
+ switch (prop_id) {
+ case PROP_MIN_FRAMES:
+ sbcpay->min_frames = g_value_get_int(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void gst_rtp_sbc_pay_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstRtpSBCPay *sbcpay;
+
+ sbcpay = GST_RTP_SBC_PAY (object);
+
+ switch (prop_id) {
+ case PROP_MIN_FRAMES:
+ g_value_set_int(value, sbcpay->min_frames);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void gst_rtp_sbc_pay_init(GstRtpSBCPay *self, GstRtpSBCPayClass *klass)
+{
+ self->adapter = gst_adapter_new();
+ self->frame_length = 0;
+ self->timestamp = 0;
+
+ self->min_frames = DEFAULT_MIN_FRAMES;
+}
+
+gboolean gst_rtp_sbc_pay_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "rtpsbcpay",
+ GST_RANK_NONE, GST_TYPE_RTP_SBC_PAY);
+}
+
diff --git a/audio/gstrtpsbcpay.h b/audio/gstrtpsbcpay.h
new file mode 100644
index 00000000..e56fef35
--- /dev/null
+++ b/audio/gstrtpsbcpay.h
@@ -0,0 +1,66 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <gst/gst.h>
+#include <gst/rtp/gstbasertppayload.h>
+#include <gst/base/gstadapter.h>
+#include <gst/rtp/gstrtpbuffer.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_RTP_SBC_PAY \
+ (gst_rtp_sbc_pay_get_type())
+#define GST_RTP_SBC_PAY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_SBC_PAY,\
+ GstRtpSBCPay))
+#define GST_RTP_SBC_PAY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_SBC_PAY,\
+ GstRtpSBCPayClass))
+#define GST_IS_RTP_SBC_PAY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_SBC_PAY))
+#define GST_IS_RTP_SBC_PAY_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_SBC_PAY))
+
+typedef struct _GstRtpSBCPay GstRtpSBCPay;
+typedef struct _GstRtpSBCPayClass GstRtpSBCPayClass;
+
+struct _GstRtpSBCPay {
+ GstBaseRTPPayload base;
+
+ GstAdapter *adapter;
+ GstClockTime timestamp;
+
+ guint frame_length;
+
+ guint min_frames;
+};
+
+struct _GstRtpSBCPayClass {
+ GstBaseRTPPayloadClass parent_class;
+};
+
+GType gst_rtp_sbc_pay_get_type(void);
+
+gboolean gst_rtp_sbc_pay_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
diff --git a/audio/gstsbcdec.c b/audio/gstsbcdec.c
new file mode 100644
index 00000000..43a40f44
--- /dev/null
+++ b/audio/gstsbcdec.c
@@ -0,0 +1,223 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; 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 <string.h>
+
+#include "gstsbcdec.h"
+
+GST_DEBUG_CATEGORY_STATIC(sbc_dec_debug);
+#define GST_CAT_DEFAULT sbc_dec_debug
+
+GST_BOILERPLATE(GstSbcDec, gst_sbc_dec, GstElement, GST_TYPE_ELEMENT);
+
+static const GstElementDetails sbc_dec_details =
+ GST_ELEMENT_DETAILS("Bluetooth SBC decoder",
+ "Codec/Decoder/Audio",
+ "Decode a SBC audio stream",
+ "Marcel Holtmann <marcel@holtmann.org>");
+
+static GstStaticPadTemplate sbc_dec_sink_factory =
+ GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS("audio/x-sbc"));
+
+static GstStaticPadTemplate sbc_dec_src_factory =
+ GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS("audio/x-raw-int, "
+ "rate = (int) { 16000, 32000, 44100, 48000 }, "
+ "channels = (int) [ 1, 2 ], "
+ "endianness = (int) LITTLE_ENDIAN, "
+ "signed = (boolean) true, "
+ "width = (int) 16, "
+ "depth = (int) 16"));
+
+static GstFlowReturn sbc_dec_chain(GstPad *pad, GstBuffer *buffer)
+{
+ GstSbcDec *dec = GST_SBC_DEC(gst_pad_get_parent(pad));
+ GstFlowReturn res = GST_FLOW_OK;
+ guint size, codesize, offset = 0;
+ guint8 *data;
+ GstClockTime timestamp;
+
+ codesize = sbc_get_codesize(&dec->sbc);
+ timestamp = GST_BUFFER_TIMESTAMP(buffer);
+
+ if (dec->buffer) {
+ GstBuffer *temp = buffer;
+ buffer = gst_buffer_span(dec->buffer, 0, buffer,
+ GST_BUFFER_SIZE(dec->buffer) + GST_BUFFER_SIZE(buffer));
+ gst_buffer_unref(temp);
+ gst_buffer_unref(dec->buffer);
+ dec->buffer = NULL;
+ }
+
+ data = GST_BUFFER_DATA(buffer);
+ size = GST_BUFFER_SIZE(buffer);
+
+ while (offset < size) {
+ GstBuffer *output;
+ GstPadTemplate *template;
+ GstCaps *caps;
+ int consumed;
+
+ res = gst_pad_alloc_buffer_and_set_caps(dec->srcpad,
+ GST_BUFFER_OFFSET_NONE,
+ codesize, NULL, &output);
+
+ if (res != GST_FLOW_OK)
+ goto done;
+
+ consumed = sbc_decode(&dec->sbc, data + offset, size - offset,
+ GST_BUFFER_DATA(output), codesize,
+ NULL);
+ if (consumed <= 0)
+ break;
+
+ /* we will reuse the same caps object */
+ if (dec->outcaps == NULL) {
+ caps = gst_caps_new_simple("audio/x-raw-int",
+ "rate", G_TYPE_INT,
+ gst_sbc_parse_rate_from_sbc(
+ dec->sbc.frequency),
+ "channels", G_TYPE_INT,
+ gst_sbc_get_channel_number(
+ dec->sbc.mode),
+ NULL);
+
+ template = gst_static_pad_template_get(&sbc_dec_src_factory);
+
+ dec->outcaps = gst_caps_intersect(caps,
+ gst_pad_template_get_caps(template));
+
+ gst_caps_unref(caps);
+ }
+
+ gst_buffer_set_caps(output, dec->outcaps);
+
+ /* FIXME get a real timestamp */
+ GST_BUFFER_TIMESTAMP(output) = GST_CLOCK_TIME_NONE;
+
+ res = gst_pad_push(dec->srcpad, output);
+ if (res != GST_FLOW_OK)
+ goto done;
+
+ offset += consumed;
+ }
+
+ if (offset < size)
+ dec->buffer = gst_buffer_create_sub(buffer,
+ offset, size - offset);
+
+done:
+ gst_buffer_unref(buffer);
+ gst_object_unref(dec);
+
+ return res;
+}
+
+static GstStateChangeReturn sbc_dec_change_state(GstElement *element,
+ GstStateChange transition)
+{
+ GstSbcDec *dec = GST_SBC_DEC(element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ GST_DEBUG("Setup subband codec");
+ if (dec->buffer) {
+ gst_buffer_unref(dec->buffer);
+ dec->buffer = NULL;
+ }
+ sbc_init(&dec->sbc, 0);
+ dec->outcaps = NULL;
+ break;
+
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ GST_DEBUG("Finish subband codec");
+ if (dec->buffer) {
+ gst_buffer_unref(dec->buffer);
+ dec->buffer = NULL;
+ }
+ sbc_finish(&dec->sbc);
+ if (dec->outcaps) {
+ gst_caps_unref(dec->outcaps);
+ dec->outcaps = NULL;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return parent_class->change_state(element, transition);
+}
+
+static void gst_sbc_dec_base_init(gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+ gst_element_class_add_pad_template(element_class,
+ gst_static_pad_template_get(&sbc_dec_sink_factory));
+
+ gst_element_class_add_pad_template(element_class,
+ gst_static_pad_template_get(&sbc_dec_src_factory));
+
+ gst_element_class_set_details(element_class, &sbc_dec_details);
+}
+
+static void gst_sbc_dec_class_init(GstSbcDecClass *klass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
+
+ parent_class = g_type_class_peek_parent(klass);
+
+ element_class->change_state = GST_DEBUG_FUNCPTR(sbc_dec_change_state);
+
+ GST_DEBUG_CATEGORY_INIT(sbc_dec_debug, "sbcdec", 0,
+ "SBC decoding element");
+}
+
+static void gst_sbc_dec_init(GstSbcDec *self, GstSbcDecClass *klass)
+{
+ self->sinkpad = gst_pad_new_from_static_template(
+ &sbc_dec_sink_factory, "sink");
+ gst_pad_set_chain_function(self->sinkpad, GST_DEBUG_FUNCPTR(
+ sbc_dec_chain));
+ gst_element_add_pad(GST_ELEMENT(self), self->sinkpad);
+
+ self->srcpad = gst_pad_new_from_static_template(
+ &sbc_dec_src_factory, "src");
+ gst_element_add_pad(GST_ELEMENT(self), self->srcpad);
+
+ self->outcaps = NULL;
+}
+
+gboolean gst_sbc_dec_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "sbcdec",
+ GST_RANK_PRIMARY, GST_TYPE_SBC_DEC);
+}
+
+
diff --git a/audio/gstsbcdec.h b/audio/gstsbcdec.h
new file mode 100644
index 00000000..df687933
--- /dev/null
+++ b/audio/gstsbcdec.h
@@ -0,0 +1,67 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <gst/gst.h>
+
+#include "sbc.h"
+#include "gstsbcutil.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_SBC_DEC \
+ (gst_sbc_dec_get_type())
+#define GST_SBC_DEC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SBC_DEC,GstSbcDec))
+#define GST_SBC_DEC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SBC_DEC,GstSbcDecClass))
+#define GST_IS_SBC_DEC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SBC_DEC))
+#define GST_IS_SBC_DEC_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SBC_DEC))
+
+typedef struct _GstSbcDec GstSbcDec;
+typedef struct _GstSbcDecClass GstSbcDecClass;
+
+struct _GstSbcDec {
+ GstElement element;
+
+ GstPad *sinkpad;
+ GstPad *srcpad;
+
+ GstBuffer *buffer;
+
+ /* caps for outgoing buffers */
+ GstCaps *outcaps;
+
+ sbc_t sbc;
+};
+
+struct _GstSbcDecClass {
+ GstElementClass parent_class;
+};
+
+GType gst_sbc_dec_get_type(void);
+
+gboolean gst_sbc_dec_plugin_init(GstPlugin *plugin);
+
+G_END_DECLS
diff --git a/audio/gstsbcenc.c b/audio/gstsbcenc.c
new file mode 100644
index 00000000..65e3da5b
--- /dev/null
+++ b/audio/gstsbcenc.c
@@ -0,0 +1,602 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; 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 <string.h>
+
+#include "gstsbcenc.h"
+#include "gstsbcutil.h"
+
+#define SBC_ENC_DEFAULT_MODE SBC_MODE_AUTO
+#define SBC_ENC_DEFAULT_BLOCKS 0
+#define SBC_ENC_DEFAULT_SUB_BANDS 0
+#define SBC_ENC_DEFAULT_ALLOCATION SBC_AM_AUTO
+#define SBC_ENC_DEFAULT_RATE 0
+#define SBC_ENC_DEFAULT_CHANNELS 0
+
+#define SBC_ENC_BITPOOL_AUTO 1
+#define SBC_ENC_BITPOOL_MIN 2
+#define SBC_ENC_BITPOOL_MIN_STR "2"
+#define SBC_ENC_BITPOOL_MAX 64
+#define SBC_ENC_BITPOOL_MAX_STR "64"
+
+GST_DEBUG_CATEGORY_STATIC(sbc_enc_debug);
+#define GST_CAT_DEFAULT sbc_enc_debug
+
+#define GST_TYPE_SBC_MODE (gst_sbc_mode_get_type())
+
+static GType gst_sbc_mode_get_type(void)
+{
+ static GType sbc_mode_type = 0;
+ static GEnumValue sbc_modes[] = {
+ { SBC_MODE_MONO, "Mono", "mono" },
+ { SBC_MODE_DUAL_CHANNEL, "Dual Channel", "dual" },
+ { SBC_MODE_STEREO, "Stereo", "stereo"},
+ { SBC_MODE_JOINT_STEREO, "Joint Stereo", "joint" },
+ { SBC_MODE_AUTO, "Auto", "auto" },
+ { -1, NULL, NULL}
+ };
+
+ if (!sbc_mode_type)
+ sbc_mode_type = g_enum_register_static("GstSbcMode", sbc_modes);
+
+ return sbc_mode_type;
+}
+
+#define GST_TYPE_SBC_ALLOCATION (gst_sbc_allocation_get_type())
+
+static GType gst_sbc_allocation_get_type(void)
+{
+ static GType sbc_allocation_type = 0;
+ static GEnumValue sbc_allocations[] = {
+ { SBC_AM_LOUDNESS, "Loudness", "loudness" },
+ { SBC_AM_SNR, "SNR", "snr" },
+ { SBC_AM_AUTO, "Auto", "auto" },
+ { -1, NULL, NULL}
+ };
+
+ if (!sbc_allocation_type)
+ sbc_allocation_type = g_enum_register_static(
+ "GstSbcAllocation", sbc_allocations);
+
+ return sbc_allocation_type;
+}
+
+#define GST_TYPE_SBC_BLOCKS (gst_sbc_blocks_get_type())
+
+static GType gst_sbc_blocks_get_type(void)
+{
+ static GType sbc_blocks_type = 0;
+ static GEnumValue sbc_blocks[] = {
+ { 0, "Auto", "auto" },
+ { 4, "4", "4" },
+ { 8, "8", "8" },
+ { 12, "12", "12" },
+ { 16, "16", "16" },
+ { -1, NULL, NULL}
+ };
+
+ if (!sbc_blocks_type)
+ sbc_blocks_type = g_enum_register_static(
+ "GstSbcBlocks", sbc_blocks);
+
+ return sbc_blocks_type;
+}
+
+#define GST_TYPE_SBC_SUBBANDS (gst_sbc_subbands_get_type())
+
+static GType gst_sbc_subbands_get_type(void)
+{
+ static GType sbc_subbands_type = 0;
+ static GEnumValue sbc_subbands[] = {
+ { 0, "Auto", "auto" },
+ { 4, "4 subbands", "4" },
+ { 8, "8 subbands", "8" },
+ { -1, NULL, NULL}
+ };
+
+ if (!sbc_subbands_type)
+ sbc_subbands_type = g_enum_register_static(
+ "GstSbcSubbands", sbc_subbands);
+
+ return sbc_subbands_type;
+}
+
+enum {
+ PROP_0,
+ PROP_MODE,
+ PROP_ALLOCATION,
+ PROP_BLOCKS,
+ PROP_SUBBANDS,
+ PROP_BITPOOL
+};
+
+GST_BOILERPLATE(GstSbcEnc, gst_sbc_enc, GstElement, GST_TYPE_ELEMENT);
+
+static const GstElementDetails sbc_enc_details =
+ GST_ELEMENT_DETAILS("Bluetooth SBC encoder",
+ "Codec/Encoder/Audio",
+ "Encode a SBC audio stream",
+ "Marcel Holtmann <marcel@holtmann.org>");
+
+static GstStaticPadTemplate sbc_enc_sink_factory =
+ GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS("audio/x-raw-int, "
+ "rate = (int) { 16000, 32000, 44100, 48000 }, "
+ "channels = (int) [ 1, 2 ], "
+ "endianness = (int) LITTLE_ENDIAN, "
+ "signed = (boolean) true, "
+ "width = (int) 16, "
+ "depth = (int) 16"));
+
+static GstStaticPadTemplate sbc_enc_src_factory =
+ GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS("audio/x-sbc, "
+ "rate = (int) { 16000, 32000, 44100, 48000 }, "
+ "channels = (int) [ 1, 2 ], "
+ "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, "
+ "blocks = (int) { 4, 8, 12, 16 }, "
+ "subbands = (int) { 4, 8 }, "
+ "allocation = (string) { \"snr\", \"loudness\" }, "
+ "bitpool = (int) [ " SBC_ENC_BITPOOL_MIN_STR
+ ", " SBC_ENC_BITPOOL_MAX_STR " ]"));
+
+gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps);
+
+static GstCaps* sbc_enc_generate_srcpad_caps(GstSbcEnc *enc)
+{
+ GstCaps* src_caps;
+ GstStructure *structure;
+ GEnumValue *enum_value;
+ GEnumClass *enum_class;
+ GValue *value;
+
+ src_caps = gst_caps_copy(gst_pad_get_pad_template_caps(enc->srcpad));
+ structure = gst_caps_get_structure(src_caps, 0);
+
+ value = g_new0(GValue, 1);
+
+ if (enc->rate != 0)
+ gst_sbc_util_set_structure_int_param(structure, "rate",
+ enc->rate, value);
+
+ if (enc->channels != 0)
+ gst_sbc_util_set_structure_int_param(structure, "channels",
+ enc->channels, value);
+
+ if (enc->subbands != 0)
+ gst_sbc_util_set_structure_int_param(structure, "subbands",
+ enc->subbands, value);
+
+ if (enc->blocks != 0)
+ gst_sbc_util_set_structure_int_param(structure, "blocks",
+ enc->blocks, value);
+
+ if (enc->bitpool != SBC_ENC_BITPOOL_AUTO)
+ gst_sbc_util_set_structure_int_param(structure, "bitpool",
+ enc->bitpool, value);
+
+ if (enc->mode != SBC_ENC_DEFAULT_MODE) {
+ enum_class = g_type_class_ref(GST_TYPE_SBC_MODE);
+ enum_value = g_enum_get_value(enum_class, enc->mode);
+ gst_sbc_util_set_structure_string_param(structure, "mode",
+ enum_value->value_nick, value);
+ g_type_class_unref(enum_class);
+ }
+
+ if (enc->allocation != SBC_AM_AUTO) {
+ enum_class = g_type_class_ref(GST_TYPE_SBC_ALLOCATION);
+ enum_value = g_enum_get_value(enum_class, enc->allocation);
+ gst_sbc_util_set_structure_string_param(structure, "allocation",
+ enum_value->value_nick, value);
+ g_type_class_unref(enum_class);
+ }
+
+ g_free(value);
+
+ return src_caps;
+}
+
+static GstCaps* sbc_enc_src_getcaps (GstPad * pad)
+{
+ GstSbcEnc *enc;
+
+ enc = GST_SBC_ENC(GST_PAD_PARENT(pad));
+
+ return sbc_enc_generate_srcpad_caps(enc);
+}
+
+static gboolean sbc_enc_src_setcaps (GstPad *pad, GstCaps *caps)
+{
+ GstSbcEnc *enc = GST_SBC_ENC(GST_PAD_PARENT(pad));
+
+ GST_LOG_OBJECT(enc, "setting srcpad caps");
+
+ return gst_sbc_enc_fill_sbc_params(enc, caps);
+}
+
+static GstCaps* sbc_enc_src_caps_fixate(GstSbcEnc *enc, GstCaps *caps)
+{
+ gchar *error_message = NULL;
+ GstCaps* result;
+
+ result = gst_sbc_util_caps_fixate(caps, &error_message);
+
+ if (!result) {
+ GST_WARNING_OBJECT (enc, "Invalid input caps caused parsing "
+ "error: %s", error_message);
+ g_free(error_message);
+ return NULL;
+ }
+
+ return result;
+}
+
+static GstCaps* sbc_enc_get_fixed_srcpad_caps(GstSbcEnc *enc)
+{
+ GstCaps *caps;
+ gboolean res = TRUE;
+ GstCaps *result_caps = NULL;
+
+ caps = gst_pad_get_allowed_caps(enc->srcpad);
+ if (caps == NULL)
+ caps = sbc_enc_src_getcaps(enc->srcpad);
+
+ if (caps == GST_CAPS_NONE || gst_caps_is_empty(caps)) {
+ res = FALSE;
+ goto done;
+ }
+
+ result_caps = sbc_enc_src_caps_fixate(enc, caps);
+
+done:
+ gst_caps_unref(caps);
+
+ if (!res)
+ return NULL;
+
+ return result_caps;
+}
+
+static gboolean sbc_enc_sink_setcaps (GstPad * pad, GstCaps * caps)
+{
+ GstSbcEnc *enc;
+ GstStructure *structure;
+ GstCaps *src_caps;
+ gint rate, channels;
+ gboolean res;
+
+ enc = GST_SBC_ENC(GST_PAD_PARENT (pad));
+ structure = gst_caps_get_structure(caps, 0);
+
+ if (!gst_structure_get_int(structure, "rate", &rate))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "channels", &channels))
+ return FALSE;
+
+ enc->rate = rate;
+ enc->channels = channels;
+
+ src_caps = sbc_enc_get_fixed_srcpad_caps(enc);
+ if (!src_caps)
+ return FALSE;
+ res = gst_pad_set_caps(enc->srcpad, src_caps);
+ gst_caps_unref(src_caps);
+
+ return res;
+}
+
+gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps)
+{
+ if (!gst_caps_is_fixed(caps)) {
+ GST_DEBUG_OBJECT(enc, "didn't receive fixed caps, "
+ "returning false");
+ return FALSE;
+ }
+
+ if (!gst_sbc_util_fill_sbc_params(&enc->sbc, caps))
+ return FALSE;
+
+ if (enc->rate != 0 && gst_sbc_parse_rate_from_sbc(enc->sbc.frequency)
+ != enc->rate)
+ goto fail;
+
+ if (enc->channels != 0 && gst_sbc_get_channel_number(enc->sbc.mode)
+ != enc->channels)
+ goto fail;
+
+ if (enc->blocks != 0 && gst_sbc_parse_blocks_from_sbc(enc->sbc.blocks)
+ != enc->blocks)
+ goto fail;
+
+ if (enc->subbands != 0 && gst_sbc_parse_subbands_from_sbc(
+ enc->sbc.subbands) != enc->subbands)
+ goto fail;
+
+ if (enc->mode != SBC_ENC_DEFAULT_MODE && enc->sbc.mode != enc->mode)
+ goto fail;
+
+ if (enc->allocation != SBC_AM_AUTO &&
+ enc->sbc.allocation != enc->allocation)
+ goto fail;
+
+ if (enc->bitpool != SBC_ENC_BITPOOL_AUTO &&
+ enc->sbc.bitpool != enc->bitpool)
+ goto fail;
+
+ enc->codesize = sbc_get_codesize(&enc->sbc);
+ enc->frame_length = sbc_get_frame_length(&enc->sbc);
+ enc->frame_duration = sbc_get_frame_duration(&enc->sbc);
+
+ GST_DEBUG_OBJECT(enc, "codesize: %d, frame_length: %d, frame_duration:"
+ " %d", enc->codesize, enc->frame_length,
+ enc->frame_duration);
+
+ return TRUE;
+
+fail:
+ memset(&enc->sbc, 0, sizeof(sbc_t));
+ return FALSE;
+}
+
+static GstFlowReturn sbc_enc_chain(GstPad *pad, GstBuffer *buffer)
+{
+ GstSbcEnc *enc = GST_SBC_ENC(gst_pad_get_parent(pad));
+ GstAdapter *adapter = enc->adapter;
+ GstFlowReturn res = GST_FLOW_OK;
+
+ gst_adapter_push(adapter, buffer);
+
+ while (gst_adapter_available(adapter) >= enc->codesize &&
+ res == GST_FLOW_OK) {
+ GstBuffer *output;
+ GstCaps *caps;
+ const guint8 *data;
+ gint consumed;
+
+ caps = GST_PAD_CAPS(enc->srcpad);
+ res = gst_pad_alloc_buffer_and_set_caps(enc->srcpad,
+ GST_BUFFER_OFFSET_NONE,
+ enc->frame_length, caps,
+ &output);
+ if (res != GST_FLOW_OK)
+ goto done;
+
+ data = gst_adapter_peek(adapter, enc->codesize);
+
+ consumed = sbc_encode(&enc->sbc, (gpointer) data,
+ enc->codesize,
+ GST_BUFFER_DATA(output),
+ GST_BUFFER_SIZE(output), NULL);
+ if (consumed <= 0) {
+ GST_DEBUG_OBJECT (enc, "comsumed < 0, codesize: %d",
+ enc->codesize);
+ break;
+ }
+ gst_adapter_flush(adapter, consumed);
+
+ GST_BUFFER_TIMESTAMP(output) = GST_BUFFER_TIMESTAMP(buffer);
+ /* we have only 1 frame */
+ GST_BUFFER_DURATION(output) = enc->frame_duration;
+
+ res = gst_pad_push(enc->srcpad, output);
+
+ if (res != GST_FLOW_OK)
+ goto done;
+ }
+
+done:
+ gst_object_unref(enc);
+
+ return res;
+}
+
+static GstStateChangeReturn sbc_enc_change_state(GstElement *element,
+ GstStateChange transition)
+{
+ GstSbcEnc *enc = GST_SBC_ENC(element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ GST_DEBUG("Setup subband codec");
+ sbc_init(&enc->sbc, 0);
+ break;
+
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ GST_DEBUG("Finish subband codec");
+ sbc_finish(&enc->sbc);
+ break;
+
+ default:
+ break;
+ }
+
+ return parent_class->change_state(element, transition);
+}
+
+static void gst_sbc_enc_dispose(GObject *object)
+{
+ GstSbcEnc *enc = GST_SBC_ENC(object);
+
+ if (enc->adapter != NULL)
+ g_object_unref (G_OBJECT (enc->adapter));
+
+ enc->adapter = NULL;
+}
+
+static void gst_sbc_enc_base_init(gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+ gst_element_class_add_pad_template(element_class,
+ gst_static_pad_template_get(&sbc_enc_sink_factory));
+
+ gst_element_class_add_pad_template(element_class,
+ gst_static_pad_template_get(&sbc_enc_src_factory));
+
+ gst_element_class_set_details(element_class, &sbc_enc_details);
+}
+
+static void gst_sbc_enc_set_property(GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ GstSbcEnc *enc = GST_SBC_ENC(object);
+
+ /* changes to those properties will only happen on the next caps
+ * negotiation */
+
+ switch (prop_id) {
+ case PROP_MODE:
+ enc->mode = g_value_get_enum(value);
+ break;
+ case PROP_ALLOCATION:
+ enc->allocation = g_value_get_enum(value);
+ break;
+ case PROP_BLOCKS:
+ enc->blocks = g_value_get_enum(value);
+ break;
+ case PROP_SUBBANDS:
+ enc->subbands = g_value_get_enum(value);
+ break;
+ case PROP_BITPOOL:
+ enc->bitpool = g_value_get_int(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void gst_sbc_enc_get_property(GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ GstSbcEnc *enc = GST_SBC_ENC(object);
+
+ switch (prop_id) {
+ case PROP_MODE:
+ g_value_set_enum(value, enc->mode);
+ break;
+ case PROP_ALLOCATION:
+ g_value_set_enum(value, enc->allocation);
+ break;
+ case PROP_BLOCKS:
+ g_value_set_enum(value, enc->blocks);
+ break;
+ case PROP_SUBBANDS:
+ g_value_set_enum(value, enc->subbands);
+ break;
+ case PROP_BITPOOL:
+ g_value_set_int(value, enc->bitpool);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void gst_sbc_enc_class_init(GstSbcEncClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
+
+ parent_class = g_type_class_peek_parent(klass);
+
+ object_class->set_property = GST_DEBUG_FUNCPTR(gst_sbc_enc_set_property);
+ object_class->get_property = GST_DEBUG_FUNCPTR(gst_sbc_enc_get_property);
+ object_class->dispose = GST_DEBUG_FUNCPTR(gst_sbc_enc_dispose);
+
+ element_class->change_state = GST_DEBUG_FUNCPTR(sbc_enc_change_state);
+
+ g_object_class_install_property(object_class, PROP_MODE,
+ g_param_spec_enum("mode", "Mode",
+ "Encoding mode", GST_TYPE_SBC_MODE,
+ SBC_ENC_DEFAULT_MODE, G_PARAM_READWRITE));
+
+ g_object_class_install_property(object_class, PROP_ALLOCATION,
+ g_param_spec_enum("allocation", "Allocation",
+ "Allocation method", GST_TYPE_SBC_ALLOCATION,
+ SBC_ENC_DEFAULT_ALLOCATION, G_PARAM_READWRITE));
+
+ g_object_class_install_property(object_class, PROP_BLOCKS,
+ g_param_spec_enum("blocks", "Blocks",
+ "Blocks", GST_TYPE_SBC_BLOCKS,
+ SBC_ENC_DEFAULT_BLOCKS, G_PARAM_READWRITE));
+
+ g_object_class_install_property(object_class, PROP_SUBBANDS,
+ g_param_spec_enum("subbands", "Sub bands",
+ "Number of sub bands", GST_TYPE_SBC_SUBBANDS,
+ SBC_ENC_DEFAULT_SUB_BANDS, G_PARAM_READWRITE));
+
+ g_object_class_install_property(object_class, PROP_BITPOOL,
+ g_param_spec_int("bitpool", "Bitpool",
+ "Bitpool (use 1 for automatic selection)",
+ SBC_ENC_BITPOOL_AUTO, SBC_ENC_BITPOOL_MAX,
+ SBC_ENC_BITPOOL_AUTO, G_PARAM_READWRITE));
+
+ GST_DEBUG_CATEGORY_INIT(sbc_enc_debug, "sbcenc", 0,
+ "SBC encoding element");
+}
+
+static void gst_sbc_enc_init(GstSbcEnc *self, GstSbcEncClass *klass)
+{
+ self->sinkpad = gst_pad_new_from_static_template(
+ &sbc_enc_sink_factory, "sink");
+ gst_pad_set_setcaps_function (self->sinkpad,
+ GST_DEBUG_FUNCPTR (sbc_enc_sink_setcaps));
+ gst_element_add_pad(GST_ELEMENT(self), self->sinkpad);
+
+ self->srcpad = gst_pad_new_from_static_template(
+ &sbc_enc_src_factory, "src");
+ gst_pad_set_getcaps_function(self->srcpad,
+ GST_DEBUG_FUNCPTR(sbc_enc_src_getcaps));
+ gst_pad_set_setcaps_function(self->srcpad,
+ GST_DEBUG_FUNCPTR(sbc_enc_src_setcaps));
+ gst_element_add_pad(GST_ELEMENT(self), self->srcpad);
+
+ gst_pad_set_chain_function(self->sinkpad,
+ GST_DEBUG_FUNCPTR(sbc_enc_chain));
+
+ self->subbands = SBC_ENC_DEFAULT_SUB_BANDS;
+ self->blocks = SBC_ENC_DEFAULT_BLOCKS;
+ self->mode = SBC_ENC_DEFAULT_MODE;
+ self->allocation = SBC_ENC_DEFAULT_ALLOCATION;
+ self->rate = SBC_ENC_DEFAULT_RATE;
+ self->channels = SBC_ENC_DEFAULT_CHANNELS;
+ self->bitpool = SBC_ENC_BITPOOL_AUTO;
+
+ self->frame_length = 0;
+ self->frame_duration = 0;
+
+ self->adapter = gst_adapter_new();
+}
+
+gboolean gst_sbc_enc_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "sbcenc",
+ GST_RANK_NONE, GST_TYPE_SBC_ENC);
+}
+
+
diff --git a/audio/gstsbcenc.h b/audio/gstsbcenc.h
new file mode 100644
index 00000000..17e89549
--- /dev/null
+++ b/audio/gstsbcenc.h
@@ -0,0 +1,75 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <gst/gst.h>
+#include <gst/base/gstadapter.h>
+
+#include "sbc.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_SBC_ENC \
+ (gst_sbc_enc_get_type())
+#define GST_SBC_ENC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SBC_ENC,GstSbcEnc))
+#define GST_SBC_ENC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SBC_ENC,GstSbcEncClass))
+#define GST_IS_SBC_ENC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SBC_ENC))
+#define GST_IS_SBC_ENC_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SBC_ENC))
+
+typedef struct _GstSbcEnc GstSbcEnc;
+typedef struct _GstSbcEncClass GstSbcEncClass;
+
+struct _GstSbcEnc {
+ GstElement element;
+
+ GstPad *sinkpad;
+ GstPad *srcpad;
+ GstAdapter *adapter;
+
+ gint rate;
+ gint channels;
+ gint mode;
+ gint blocks;
+ gint allocation;
+ gint subbands;
+ gint bitpool;
+
+ gint codesize;
+ gint frame_length;
+ gint frame_duration;
+
+ sbc_t sbc;
+};
+
+struct _GstSbcEncClass {
+ GstElementClass parent_class;
+};
+
+GType gst_sbc_enc_get_type(void);
+
+gboolean gst_sbc_enc_plugin_init(GstPlugin *plugin);
+
+G_END_DECLS
diff --git a/audio/gstsbcparse.c b/audio/gstsbcparse.c
new file mode 100644
index 00000000..4521c563
--- /dev/null
+++ b/audio/gstsbcparse.c
@@ -0,0 +1,222 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; 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 <string.h>
+
+#include "gstsbcparse.h"
+#include "gstsbcutil.h"
+
+GST_DEBUG_CATEGORY_STATIC(sbc_parse_debug);
+#define GST_CAT_DEFAULT sbc_parse_debug
+
+GST_BOILERPLATE(GstSbcParse, gst_sbc_parse, GstElement, GST_TYPE_ELEMENT);
+
+static const GstElementDetails sbc_parse_details =
+ GST_ELEMENT_DETAILS("Bluetooth SBC parser",
+ "Codec/Parser/Audio",
+ "Parse a SBC audio stream",
+ "Marcel Holtmann <marcel@holtmann.org>");
+
+static GstStaticPadTemplate sbc_parse_sink_factory =
+ GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS("audio/x-sbc,"
+ "parsed = (boolean) false"));
+
+static GstStaticPadTemplate sbc_parse_src_factory =
+ GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS("audio/x-sbc, "
+ "rate = (int) { 16000, 32000, 44100, 48000 }, "
+ "channels = (int) [ 1, 2 ], "
+ "mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, "
+ "blocks = (int) { 4, 8, 12, 16 }, "
+ "subbands = (int) { 4, 8 }, "
+ "allocation = (string) { \"snr\", \"loudness\" },"
+ "bitpool = (int) [ 2, 64 ],"
+ "parsed = (boolean) true"));
+
+static GstFlowReturn sbc_parse_chain(GstPad *pad, GstBuffer *buffer)
+{
+ GstSbcParse *parse = GST_SBC_PARSE(gst_pad_get_parent(pad));
+ GstFlowReturn res = GST_FLOW_OK;
+ guint size, offset = 0;
+ guint8 *data;
+ GstClockTime timestamp;
+
+ timestamp = GST_BUFFER_TIMESTAMP(buffer);
+
+ /* FIXME use a gstadpter */
+ if (parse->buffer) {
+ GstBuffer *temp;
+ temp = buffer;
+ buffer = gst_buffer_span(parse->buffer, 0, buffer,
+ GST_BUFFER_SIZE(parse->buffer)
+ + GST_BUFFER_SIZE(buffer));
+ gst_buffer_unref(parse->buffer);
+ gst_buffer_unref(temp);
+ parse->buffer = NULL;
+ }
+
+ data = GST_BUFFER_DATA(buffer);
+ size = GST_BUFFER_SIZE(buffer);
+
+ while (offset < size) {
+ GstBuffer *output;
+ int consumed;
+
+ consumed = sbc_parse(&parse->new_sbc, data + offset,
+ size - offset);
+ if (consumed <= 0)
+ break;
+
+ if (parse->first_parsing || (memcmp(&parse->sbc,
+ &parse->new_sbc, sizeof(sbc_t)) != 0)) {
+
+ memcpy(&parse->sbc, &parse->new_sbc, sizeof(sbc_t));
+ if (parse->outcaps != NULL)
+ gst_caps_unref(parse->outcaps);
+
+ parse->outcaps = gst_sbc_parse_caps_from_sbc(
+ &parse->sbc);
+
+ parse->first_parsing = FALSE;
+ }
+
+ res = gst_pad_alloc_buffer_and_set_caps(parse->srcpad,
+ GST_BUFFER_OFFSET_NONE,
+ consumed, parse->outcaps, &output);
+
+ if (res != GST_FLOW_OK)
+ goto done;
+
+ memcpy(GST_BUFFER_DATA(output), data + offset, consumed);
+
+ res = gst_pad_push(parse->srcpad, output);
+ if (res != GST_FLOW_OK)
+ goto done;
+
+ offset += consumed;
+ }
+
+ if (offset < size)
+ parse->buffer = gst_buffer_create_sub(buffer,
+ offset, size - offset);
+
+done:
+ gst_buffer_unref(buffer);
+ gst_object_unref(parse);
+
+ return res;
+}
+
+static GstStateChangeReturn sbc_parse_change_state(GstElement *element,
+ GstStateChange transition)
+{
+ GstSbcParse *parse = GST_SBC_PARSE(element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ GST_DEBUG("Setup subband codec");
+
+ parse->channels = -1;
+ parse->rate = -1;
+ parse->first_parsing = TRUE;
+
+ sbc_init(&parse->sbc, 0);
+ break;
+
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ GST_DEBUG("Finish subband codec");
+
+ if (parse->buffer) {
+ gst_buffer_unref(parse->buffer);
+ parse->buffer = NULL;
+ }
+ if (parse->outcaps != NULL) {
+ gst_caps_unref(parse->outcaps);
+ parse->outcaps = NULL;
+ }
+
+ sbc_finish(&parse->sbc);
+ break;
+
+ default:
+ break;
+ }
+
+ return parent_class->change_state(element, transition);
+}
+
+static void gst_sbc_parse_base_init(gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+ gst_element_class_add_pad_template(element_class,
+ gst_static_pad_template_get(&sbc_parse_sink_factory));
+
+ gst_element_class_add_pad_template(element_class,
+ gst_static_pad_template_get(&sbc_parse_src_factory));
+
+ gst_element_class_set_details(element_class, &sbc_parse_details);
+}
+
+static void gst_sbc_parse_class_init(GstSbcParseClass *klass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
+
+ parent_class = g_type_class_peek_parent(klass);
+
+ element_class->change_state = GST_DEBUG_FUNCPTR(sbc_parse_change_state);
+
+ GST_DEBUG_CATEGORY_INIT(sbc_parse_debug, "sbcparse", 0,
+ "SBC parsing element");
+}
+
+static void gst_sbc_parse_init(GstSbcParse *self, GstSbcParseClass *klass)
+{
+ self->sinkpad = gst_pad_new_from_static_template(
+ &sbc_parse_sink_factory, "sink");
+ gst_pad_set_chain_function(self->sinkpad,
+ GST_DEBUG_FUNCPTR(sbc_parse_chain));
+ gst_element_add_pad(GST_ELEMENT(self), self->sinkpad);
+
+ self->srcpad = gst_pad_new_from_static_template(
+ &sbc_parse_src_factory, "src");
+ gst_element_add_pad(GST_ELEMENT(self), self->srcpad);
+
+ self->outcaps = NULL;
+ self->buffer = NULL;
+ self->channels = -1;
+ self->rate = -1;
+ self->first_parsing = TRUE;
+}
+
+gboolean gst_sbc_parse_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "sbcparse",
+ GST_RANK_NONE, GST_TYPE_SBC_PARSE);
+}
+
diff --git a/audio/gstsbcparse.h b/audio/gstsbcparse.h
new file mode 100644
index 00000000..eceeeea1
--- /dev/null
+++ b/audio/gstsbcparse.h
@@ -0,0 +1,69 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <gst/gst.h>
+
+#include "sbc.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_SBC_PARSE \
+ (gst_sbc_parse_get_type())
+#define GST_SBC_PARSE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SBC_PARSE,GstSbcParse))
+#define GST_SBC_PARSE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SBC_PARSE,GstSbcParseClass))
+#define GST_IS_SBC_PARSE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SBC_PARSE))
+#define GST_IS_SBC_PARSE_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SBC_PARSE))
+
+typedef struct _GstSbcParse GstSbcParse;
+typedef struct _GstSbcParseClass GstSbcParseClass;
+
+struct _GstSbcParse {
+ GstElement element;
+
+ GstPad *sinkpad;
+ GstPad *srcpad;
+
+ GstBuffer *buffer;
+
+ sbc_t sbc;
+ sbc_t new_sbc;
+ GstCaps *outcaps;
+ gboolean first_parsing;
+
+ gint channels;
+ gint rate;
+};
+
+struct _GstSbcParseClass {
+ GstElementClass parent_class;
+};
+
+GType gst_sbc_parse_get_type(void);
+
+gboolean gst_sbc_parse_plugin_init(GstPlugin *plugin);
+
+G_END_DECLS
diff --git a/audio/gstsbcutil.c b/audio/gstsbcutil.c
new file mode 100644
index 00000000..5d646cfb
--- /dev/null
+++ b/audio/gstsbcutil.c
@@ -0,0 +1,521 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; 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 <math.h>
+#include "gstsbcutil.h"
+
+/*
+ * Selects one rate from a list of possible rates
+ * TODO - use a better approach to this (it is selecting the last element)
+ */
+gint gst_sbc_select_rate_from_list(const GValue *value)
+{
+ guint size = gst_value_list_get_size(value);
+ return g_value_get_int(gst_value_list_get_value(value, size-1));
+}
+
+/*
+ * Selects one number of channels option from a range of possible numbers
+ * TODO - use a better approach to this (it is selecting the maximum value)
+ */
+gint gst_sbc_select_channels_from_range(const GValue *value)
+{
+ return gst_value_get_int_range_max(value);
+}
+
+/*
+ * Selects one number of blocks from a list of possible blocks
+ * TODO - use a better approach to this (it is selecting the last element)
+ */
+gint gst_sbc_select_blocks_from_list(const GValue *value)
+{
+ guint size = gst_value_list_get_size(value);
+ return g_value_get_int(gst_value_list_get_value(value, size-1));
+}
+
+/*
+ * Selects one number of subbands from a list
+ * TODO - use a better approach to this (it is selecting the last element)
+ */
+gint gst_sbc_select_subbands_from_list(const GValue *value)
+{
+ guint size = gst_value_list_get_size(value);
+ return g_value_get_int(gst_value_list_get_value(value, size-1));
+}
+
+/*
+ * Selects one bitpool option from a range
+ * TODO - use a better approach to this (it is selecting the maximum value)
+ */
+gint gst_sbc_select_bitpool_from_range(const GValue *value)
+{
+ return gst_value_get_int_range_max(value);
+}
+
+/*
+ * Selects one allocation mode from the ones on the list
+ * TODO - use a better approach
+ */
+const gchar *gst_sbc_get_allocation_from_list(const GValue *value)
+{
+ guint size = gst_value_list_get_size(value);
+ return g_value_get_string(gst_value_list_get_value(value, size-1));
+}
+
+/*
+ * Selects one mode from the ones on the list
+ */
+const gchar *gst_sbc_get_mode_from_list(const GValue *list, gint channels)
+{
+ int i;
+ const GValue *value;
+ const gchar *aux;
+ gboolean joint, stereo, dual, mono;
+
+ joint = stereo = dual = mono = FALSE;
+ guint size = gst_value_list_get_size(list);
+
+ for (i = 0; i < size; i++) {
+ value = gst_value_list_get_value(list, i);
+ aux = g_value_get_string(value);
+ if (strcmp("joint", aux) == 0)
+ joint = TRUE;
+ else if (strcmp("stereo", aux) == 0)
+ stereo = TRUE;
+ else if (strcmp("dual", aux) == 0)
+ dual = TRUE;
+ else if (strcmp("mono", aux) == 0)
+ mono = TRUE;
+ }
+
+ if (channels == 1 && mono)
+ return "mono";
+ else if (channels == 2) {
+ if (joint)
+ return "joint";
+ else if (stereo)
+ return "stereo";
+ else if (dual)
+ return "dual";
+ }
+
+ return NULL;
+}
+
+gint gst_sbc_parse_rate_from_sbc(gint frequency)
+{
+ switch (frequency) {
+ case SBC_FREQ_16000:
+ return 16000;
+ case SBC_FREQ_32000:
+ return 32000;
+ case SBC_FREQ_44100:
+ return 44100;
+ case SBC_FREQ_48000:
+ return 48000;
+ default:
+ return 0;
+ }
+}
+
+gint gst_sbc_parse_rate_to_sbc(gint rate)
+{
+ switch (rate) {
+ case 16000:
+ return SBC_FREQ_16000;
+ case 32000:
+ return SBC_FREQ_32000;
+ case 44100:
+ return SBC_FREQ_44100;
+ case 48000:
+ return SBC_FREQ_48000;
+ default:
+ return -1;
+ }
+}
+
+gint gst_sbc_get_channel_number(gint mode)
+{
+ switch (mode) {
+ case SBC_MODE_JOINT_STEREO:
+ case SBC_MODE_STEREO:
+ case SBC_MODE_DUAL_CHANNEL:
+ return 2;
+ case SBC_MODE_MONO:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+gint gst_sbc_parse_subbands_from_sbc(gint subbands)
+{
+ switch (subbands) {
+ case SBC_SB_4:
+ return 4;
+ case SBC_SB_8:
+ return 8;
+ default:
+ return 0;
+ }
+}
+
+gint gst_sbc_parse_subbands_to_sbc(gint subbands)
+{
+ switch (subbands) {
+ case 4:
+ return SBC_SB_4;
+ case 8:
+ return SBC_SB_8;
+ default:
+ return -1;
+ }
+}
+
+gint gst_sbc_parse_blocks_from_sbc(gint blocks)
+{
+ switch (blocks) {
+ case SBC_BLK_4:
+ return 4;
+ case SBC_BLK_8:
+ return 8;
+ case SBC_BLK_12:
+ return 12;
+ case SBC_BLK_16:
+ return 16;
+ default:
+ return 0;
+ }
+}
+
+gint gst_sbc_parse_blocks_to_sbc(gint blocks)
+{
+ switch (blocks) {
+ case 4:
+ return SBC_BLK_4;
+ case 8:
+ return SBC_BLK_8;
+ case 12:
+ return SBC_BLK_12;
+ case 16:
+ return SBC_BLK_16;
+ default:
+ return -1;
+ }
+}
+
+const gchar *gst_sbc_parse_mode_from_sbc(gint mode)
+{
+ switch (mode) {
+ case SBC_MODE_MONO:
+ return "mono";
+ case SBC_MODE_DUAL_CHANNEL:
+ return "dual";
+ case SBC_MODE_STEREO:
+ return "stereo";
+ case SBC_MODE_JOINT_STEREO:
+ case SBC_MODE_AUTO:
+ return "joint";
+ default:
+ return NULL;
+ }
+}
+
+gint gst_sbc_parse_mode_to_sbc(const gchar *mode)
+{
+ if (g_ascii_strcasecmp(mode, "joint") == 0)
+ return SBC_MODE_JOINT_STEREO;
+ else if (g_ascii_strcasecmp(mode, "stereo") == 0)
+ return SBC_MODE_STEREO;
+ else if (g_ascii_strcasecmp(mode, "dual") == 0)
+ return SBC_MODE_DUAL_CHANNEL;
+ else if (g_ascii_strcasecmp(mode, "mono") == 0)
+ return SBC_MODE_MONO;
+ else if (g_ascii_strcasecmp(mode, "auto") == 0)
+ return SBC_MODE_JOINT_STEREO;
+ else
+ return -1;
+}
+
+const gchar *gst_sbc_parse_allocation_from_sbc(gint alloc)
+{
+ switch (alloc) {
+ case SBC_AM_LOUDNESS:
+ return "loudness";
+ case SBC_AM_SNR:
+ return "snr";
+ case SBC_AM_AUTO:
+ return "loudness";
+ default:
+ return NULL;
+ }
+}
+
+gint gst_sbc_parse_allocation_to_sbc(const gchar *allocation)
+{
+ if (g_ascii_strcasecmp(allocation, "loudness") == 0)
+ return SBC_AM_LOUDNESS;
+ else if (g_ascii_strcasecmp(allocation, "snr") == 0)
+ return SBC_AM_SNR;
+ else
+ return SBC_AM_LOUDNESS;
+}
+
+GstCaps* gst_sbc_parse_caps_from_sbc(sbc_t *sbc)
+{
+ GstCaps *caps;
+ const gchar *mode_str;
+ const gchar *allocation_str;
+
+ mode_str = gst_sbc_parse_mode_from_sbc(sbc->mode);
+ allocation_str = gst_sbc_parse_allocation_from_sbc(sbc->allocation);
+ caps = gst_caps_new_simple("audio/x-sbc",
+ "rate", G_TYPE_INT,
+ gst_sbc_parse_rate_from_sbc(sbc->frequency),
+ "channels", G_TYPE_INT,
+ gst_sbc_get_channel_number(sbc->mode),
+ "mode", G_TYPE_STRING, mode_str,
+ "subbands", G_TYPE_INT,
+ gst_sbc_parse_subbands_from_sbc(sbc->subbands),
+ "blocks", G_TYPE_INT,
+ gst_sbc_parse_blocks_from_sbc(sbc->blocks),
+ "allocation", G_TYPE_STRING, allocation_str,
+ "bitpool", G_TYPE_INT, sbc->bitpool,
+ NULL);
+
+ return caps;
+}
+
+/*
+ * Given a GstCaps, this will return a fixed GstCaps on sucessfull conversion.
+ * If an error occurs, it will return NULL and error_message will contain the
+ * error message.
+ *
+ * error_message must be passed NULL, if an error occurs, the caller has the
+ * ownership of the error_message, it must be freed after use.
+ */
+GstCaps* gst_sbc_util_caps_fixate(GstCaps *caps, gchar** error_message)
+{
+ GstCaps *result;
+ GstStructure *structure;
+ const GValue *value;
+ gboolean error = FALSE;
+ gint temp, rate, channels, blocks, subbands, bitpool;
+ const gchar* allocation = NULL;
+ const gchar* mode = NULL;
+
+ g_assert(*error_message == NULL);
+
+ structure = gst_caps_get_structure(caps, 0);
+
+ if (!gst_structure_has_field(structure, "rate")) {
+ error = TRUE;
+ *error_message = g_strdup("no rate");
+ goto error;
+ } else {
+ value = gst_structure_get_value(structure, "rate");
+ if (GST_VALUE_HOLDS_LIST(value))
+ temp = gst_sbc_select_rate_from_list(value);
+ else
+ temp = g_value_get_int(value);
+ rate = temp;
+ }
+
+ if (!gst_structure_has_field(structure, "channels")) {
+ error = TRUE;
+ *error_message = g_strdup("no channels");
+ goto error;
+ } else {
+ value = gst_structure_get_value(structure, "channels");
+ if (GST_VALUE_HOLDS_INT_RANGE(value))
+ temp = gst_sbc_select_channels_from_range(value);
+ else
+ temp = g_value_get_int(value);
+ channels = temp;
+ }
+
+ if (!gst_structure_has_field(structure, "blocks")) {
+ error = TRUE;
+ *error_message = g_strdup("no blocks.");
+ goto error;
+ } else {
+ value = gst_structure_get_value(structure, "blocks");
+ if (GST_VALUE_HOLDS_LIST(value))
+ temp = gst_sbc_select_blocks_from_list(value);
+ else
+ temp = g_value_get_int(value);
+ blocks = temp;
+ }
+
+ if (!gst_structure_has_field(structure, "subbands")) {
+ error = TRUE;
+ *error_message = g_strdup("no subbands");
+ goto error;
+ } else {
+ value = gst_structure_get_value(structure, "subbands");
+ if (GST_VALUE_HOLDS_LIST(value))
+ temp = gst_sbc_select_subbands_from_list(value);
+ else
+ temp = g_value_get_int(value);
+ subbands = temp;
+ }
+
+ if (!gst_structure_has_field(structure, "bitpool")) {
+ error = TRUE;
+ *error_message = g_strdup("no bitpool");
+ goto error;
+ } else {
+ value = gst_structure_get_value(structure, "bitpool");
+ if (GST_VALUE_HOLDS_INT_RANGE(value))
+ temp = gst_sbc_select_bitpool_from_range(value);
+ else
+ temp = g_value_get_int(value);
+ bitpool = temp;
+ }
+
+ if (!gst_structure_has_field(structure, "allocation")) {
+ error = TRUE;
+ *error_message = g_strdup("no allocation");
+ goto error;
+ } else {
+ value = gst_structure_get_value(structure, "allocation");
+ if (GST_VALUE_HOLDS_LIST(value))
+ allocation = gst_sbc_get_allocation_from_list(value);
+ else
+ allocation = g_value_get_string(value);
+ }
+
+ if (!gst_structure_has_field(structure, "mode")) {
+ error = TRUE;
+ *error_message = g_strdup("no mode");
+ goto error;
+ } else {
+ value = gst_structure_get_value(structure, "mode");
+ if (GST_VALUE_HOLDS_LIST(value)) {
+ mode = gst_sbc_get_mode_from_list(value, channels);
+ } else
+ mode = g_value_get_string(value);
+ }
+
+ /* perform validation
+ * if channels is 1, we must have channel mode = mono
+ * if channels is 2, we can't have channel mode = mono */
+ if ( (channels == 1 && (strcmp(mode, "mono") != 0) ) ||
+ ( channels == 2 && ( strcmp(mode, "mono") == 0))) {
+ *error_message = g_strdup_printf("Invalid combination of "
+ "channels (%d) and channel mode (%s)",
+ channels, mode);
+ error = TRUE;
+ }
+
+error:
+ if (error)
+ return NULL;
+
+ result = gst_caps_new_simple("audio/x-sbc",
+ "rate", G_TYPE_INT, rate,
+ "channels", G_TYPE_INT, channels,
+ "mode", G_TYPE_STRING, mode,
+ "blocks", G_TYPE_INT, blocks,
+ "subbands", G_TYPE_INT, subbands,
+ "allocation", G_TYPE_STRING, allocation,
+ "bitpool", G_TYPE_INT, bitpool,
+ NULL);
+
+ return result;
+}
+
+/**
+ * Sets the int field_value to the param "field" on the structure.
+ * value is used to do the operation, it must be a uninitialized (zero-filled)
+ * GValue, it will be left unitialized at the end of the function.
+ */
+void gst_sbc_util_set_structure_int_param(GstStructure *structure,
+ const gchar* field, gint field_value,
+ GValue *value)
+{
+ value = g_value_init(value, G_TYPE_INT);
+ g_value_set_int(value, field_value);
+ gst_structure_set_value(structure, field, value);
+ g_value_unset(value);
+}
+
+/**
+ * Sets the string field_value to the param "field" on the structure.
+ * value is used to do the operation, it must be a uninitialized (zero-filled)
+ * GValue, it will be left unitialized at the end of the function.
+ */
+void gst_sbc_util_set_structure_string_param(GstStructure *structure,
+ const gchar* field, const gchar* field_value,
+ GValue *value)
+{
+ value = g_value_init(value, G_TYPE_STRING);
+ g_value_set_string(value, field_value);
+ gst_structure_set_value(structure, field, value);
+ g_value_unset(value);
+}
+
+gboolean gst_sbc_util_fill_sbc_params(sbc_t *sbc, GstCaps *caps)
+{
+ GstStructure *structure;
+ gint rate, channels, subbands, blocks, bitpool;
+ const gchar* mode;
+ const gchar* allocation;
+
+ g_assert(gst_caps_is_fixed(caps));
+
+ structure = gst_caps_get_structure(caps, 0);
+
+ if (!gst_structure_get_int(structure, "rate", &rate))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "channels", &channels))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "subbands", &subbands))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "blocks", &blocks))
+ return FALSE;
+ if (!gst_structure_get_int(structure, "bitpool", &bitpool))
+ return FALSE;
+
+ if (!(mode = gst_structure_get_string(structure, "mode")))
+ return FALSE;
+ if (!(allocation = gst_structure_get_string(structure, "allocation")))
+ return FALSE;
+
+ if (channels == 1 && strcmp(mode, "mono") != 0)
+ return FALSE;
+
+ sbc->frequency = gst_sbc_parse_rate_to_sbc(rate);
+ sbc->blocks = gst_sbc_parse_blocks_to_sbc(blocks);
+ sbc->subbands = gst_sbc_parse_subbands_to_sbc(subbands);
+ sbc->bitpool = bitpool;
+ sbc->mode = gst_sbc_parse_mode_to_sbc(mode);
+ sbc->allocation = gst_sbc_parse_allocation_to_sbc(allocation);
+
+ return TRUE;
+}
+
diff --git a/audio/gstsbcutil.h b/audio/gstsbcutil.h
new file mode 100644
index 00000000..a3cf937c
--- /dev/null
+++ b/audio/gstsbcutil.h
@@ -0,0 +1,77 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <gst/gst.h>
+
+#include "sbc.h"
+#include <string.h>
+
+#define SBC_AM_AUTO 0x02
+#define SBC_MODE_AUTO 0x04
+
+gint gst_sbc_select_rate_from_list(const GValue *value);
+
+gint gst_sbc_select_channels_from_range(const GValue *value);
+
+gint gst_sbc_select_blocks_from_list(const GValue *value);
+
+gint gst_sbc_select_subbands_from_list(const GValue *value);
+
+gint gst_sbc_select_bitpool_from_range(const GValue *value);
+
+gint gst_sbc_select_bitpool_from_range(const GValue *value);
+
+const gchar *gst_sbc_get_allocation_from_list(const GValue *value);
+
+const gchar *gst_sbc_get_mode_from_list(const GValue *value, gint channels);
+
+gint gst_sbc_get_channel_number(gint mode);
+gint gst_sbc_parse_rate_from_sbc(gint frequency);
+gint gst_sbc_parse_rate_to_sbc(gint rate);
+
+gint gst_sbc_parse_subbands_from_sbc(gint subbands);
+gint gst_sbc_parse_subbands_to_sbc(gint subbands);
+
+gint gst_sbc_parse_blocks_from_sbc(gint blocks);
+gint gst_sbc_parse_blocks_to_sbc(gint blocks);
+
+const gchar *gst_sbc_parse_mode_from_sbc(gint mode);
+gint gst_sbc_parse_mode_to_sbc(const gchar *mode);
+
+const gchar *gst_sbc_parse_allocation_from_sbc(gint alloc);
+gint gst_sbc_parse_allocation_to_sbc(const gchar *allocation);
+
+GstCaps* gst_sbc_parse_caps_from_sbc(sbc_t *sbc);
+
+GstCaps* gst_sbc_util_caps_fixate(GstCaps *caps, gchar** error_message);
+
+void gst_sbc_util_set_structure_int_param(GstStructure *structure,
+ const gchar* field, gint field_value,
+ GValue *value);
+
+void gst_sbc_util_set_structure_string_param(GstStructure *structure,
+ const gchar* field, const gchar* field_value,
+ GValue *value);
+
+gboolean gst_sbc_util_fill_sbc_params(sbc_t *sbc, GstCaps *caps);
+
diff --git a/audio/headset.c b/audio/headset.c
new file mode 100644
index 00000000..0adc1e9d
--- /dev/null
+++ b/audio/headset.c
@@ -0,0 +1,1924 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "device.h"
+#include "manager.h"
+#include "error.h"
+#include "headset.h"
+#include "glib-helper.h"
+
+#define DC_TIMEOUT 3000
+
+#define RING_INTERVAL 3000
+
+#define BUF_SIZE 1024
+
+#define HEADSET_GAIN_SPEAKER 'S'
+#define HEADSET_GAIN_MICROPHONE 'M'
+
+#define AG_FEATURE_THREE_WAY_CALLING 0x0001
+#define AG_FEATURE_EC_ANDOR_NR 0x0002
+#define AG_FEATURE_VOICE_RECOGNITION 0x0004
+#define AG_FEATURE_INBAND_RINGTONE 0x0008
+#define AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG 0x0010
+#define AG_FEATURE_REJECT_A_CALL 0x0020
+#define AG_FEATURE_ENHANCES_CALL_STATUS 0x0040
+#define AG_FEATURE_ENHANCES_CALL_CONTROL 0x0080
+#define AG_FEATURE_EXTENDED_ERROR_RESULT_CODES 0x0100
+
+static uint32_t ag_features = 0;
+
+static gboolean sco_hci = TRUE;
+
+static char *str_state[] = {
+ "HEADSET_STATE_DISCONNECTED",
+ "HEADSET_STATE_CONNECT_IN_PROGRESS",
+ "HEADSET_STATE_CONNECTED",
+ "HEADSET_STATE_PLAY_IN_PROGRESS",
+ "HEADSET_STATE_PLAYING",
+};
+
+struct connect_cb {
+ unsigned int id;
+ headset_stream_cb_t cb;
+ void *cb_data;
+};
+
+struct pending_connect {
+ DBusMessage *msg;
+ DBusPendingCall *call;
+ GIOChannel *io;
+ int err;
+ headset_state_t target_state;
+ GSList *callbacks;
+};
+
+struct headset {
+ uint32_t hsp_handle;
+ uint32_t hfp_handle;
+
+ int rfcomm_ch;
+
+ GIOChannel *rfcomm;
+ GIOChannel *tmp_rfcomm;
+ GIOChannel *sco;
+ guint sco_id;
+
+ gboolean auto_dc;
+
+ guint ring_timer;
+
+ guint dc_timer;
+
+ char buf[BUF_SIZE];
+ int data_start;
+ int data_length;
+
+ gboolean hfp_active;
+ gboolean search_hfp;
+ gboolean cli_active;
+ char *ph_number;
+ int type;
+
+ headset_state_t state;
+ struct pending_connect *pending;
+
+ int sp_gain;
+ int mic_gain;
+
+ unsigned int hfp_features;
+ headset_lock_t lock;
+};
+
+struct event {
+ const char *cmd;
+ int (*callback) (struct audio_device *device, const char *buf);
+};
+
+static int rfcomm_connect(struct audio_device *device, headset_stream_cb_t cb,
+ void *user_data, unsigned int *cb_id);
+static int get_records(struct audio_device *device, headset_stream_cb_t cb,
+ void *user_data, unsigned int *cb_id);
+
+static int headset_send(struct headset *hs, char *format, ...)
+{
+ char rsp[BUF_SIZE];
+ va_list ap;
+ ssize_t total_written, written, count;
+ int fd;
+
+ va_start(ap, format);
+ count = vsnprintf(rsp, sizeof(rsp), format, ap);
+ va_end(ap);
+
+ if (count < 0)
+ return -EINVAL;
+
+ if (!hs->rfcomm) {
+ error("headset_send: the headset is not connected");
+ return -EIO;
+ }
+
+ written = total_written = 0;
+ fd = g_io_channel_unix_get_fd(hs->rfcomm);
+
+ while (total_written < count) {
+ written = write(fd, rsp + total_written, count - total_written);
+ if (written < 0)
+ return -errno;
+
+ total_written += written;
+ }
+
+ return 0;
+}
+
+static int supported_features(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+ int err;
+
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ hs->hfp_features = strtoul(&buf[8], NULL, 10);
+ err = headset_send(hs, "\r\n+BRSF=%u\r\n", ag_features);
+ if (err < 0)
+ return err;
+
+ return headset_send(hs, "\r\nOK\r\n");
+}
+
+static int report_indicators(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+ int err;
+
+ if (buf[7] == '=')
+ err = headset_send(hs, "\r\n+CIND:(\"service\",(0,1)),"
+ "(\"call\",(0,1)),(\"callsetup\",(0-3))\r\n");
+ else
+ err = headset_send(hs, "\r\n+CIND:1,0,0\r\n");
+
+ if (err < 0)
+ return err;
+
+ return headset_send(hs, "\r\nOK\r\n");
+}
+
+static void pending_connect_complete(struct connect_cb *cb, struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+
+ if (hs->pending->err)
+ cb->cb(NULL, cb->cb_data);
+ else
+ cb->cb(dev, cb->cb_data);
+}
+
+static void pending_connect_finalize(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+ struct pending_connect *p = hs->pending;
+
+ g_slist_foreach(p->callbacks, (GFunc) pending_connect_complete, dev);
+
+ g_slist_foreach(p->callbacks, (GFunc) g_free, NULL);
+ g_slist_free(p->callbacks);
+
+ if (p->io) {
+ g_io_channel_close(p->io);
+ g_io_channel_unref(p->io);
+ }
+
+ if (p->msg)
+ dbus_message_unref(p->msg);
+
+ if (p->call) {
+ dbus_pending_call_cancel(p->call);
+ dbus_pending_call_unref(p->call);
+ }
+
+ g_free(p);
+
+ hs->pending = NULL;
+}
+
+static void pending_connect_init(struct headset *hs, headset_state_t target_state)
+{
+ if (hs->pending) {
+ if (hs->pending->target_state < target_state)
+ hs->pending->target_state = target_state;
+ return;
+ }
+
+ hs->pending = g_new0(struct pending_connect, 1);
+ hs->pending->target_state = target_state;
+}
+
+static unsigned int connect_cb_new(struct headset *hs,
+ headset_state_t target_state,
+ headset_stream_cb_t func,
+ void *user_data)
+{
+ struct connect_cb *cb;
+ unsigned int free_cb_id = 1;
+
+ pending_connect_init(hs, target_state);
+
+ cb = g_new(struct connect_cb, 1);
+
+ cb->cb = func;
+ cb->cb_data = user_data;
+ cb->id = free_cb_id++;
+
+ hs->pending->callbacks = g_slist_append(hs->pending->callbacks,
+ cb);
+
+ return cb->id;
+}
+
+static void sco_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer user_data)
+{
+ int sk;
+ struct audio_device *dev = user_data;
+ struct headset *hs = dev->headset;
+ struct pending_connect *p = hs->pending;
+
+ if (err < 0) {
+ error("connect(): %s (%d)", strerror(-err), -err);
+
+ if (p->msg)
+ error_connection_attempt_failed(dev->conn, p->msg, p->err);
+
+ pending_connect_finalize(dev);
+ if (hs->rfcomm)
+ headset_set_state(dev, HEADSET_STATE_CONNECTED);
+ else
+ headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+
+ return;
+ }
+
+ debug("SCO socket opened for headset %s", dev->path);
+
+ sk = g_io_channel_unix_get_fd(chan);
+
+ info("SCO fd=%d", sk);
+ hs->sco = chan;
+ p->io = NULL;
+
+ if (p->msg) {
+ DBusMessage *reply = dbus_message_new_method_return(p->msg);
+ dbus_connection_send(dev->conn, reply, NULL);
+ dbus_message_unref(reply);
+ }
+
+ pending_connect_finalize(dev);
+
+ fcntl(sk, F_SETFL, 0);
+
+ headset_set_state(dev, HEADSET_STATE_PLAYING);
+}
+
+static int sco_connect(struct audio_device *dev, headset_stream_cb_t cb,
+ void *user_data, unsigned int *cb_id)
+{
+ struct headset *hs = dev->headset;
+ int err;
+
+ if (hs->state != HEADSET_STATE_CONNECTED)
+ return -EINVAL;
+
+ err = bt_sco_connect(&dev->src, &dev->dst, sco_connect_cb, dev);
+ if (err < 0) {
+ error("connect: %s (%d)", strerror(-err), -err);
+ return -err;
+ }
+
+ headset_set_state(dev, HEADSET_STATE_PLAY_IN_PROGRESS);
+
+ pending_connect_init(hs, HEADSET_STATE_PLAYING);
+
+ if (cb) {
+ unsigned int id = connect_cb_new(hs, HEADSET_STATE_PLAYING,
+ cb, user_data);
+ if (cb_id)
+ *cb_id = id;
+ }
+
+ return 0;
+}
+
+static void hfp_slc_complete(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+ struct pending_connect *p = hs->pending;
+
+ debug("HFP Service Level Connection established");
+
+ headset_set_state(dev, HEADSET_STATE_CONNECTED);
+
+ if (p == NULL)
+ return;
+
+ if (p->msg) {
+ DBusMessage *reply = dbus_message_new_method_return(p->msg);
+ dbus_connection_send(dev->conn, reply, NULL);
+ dbus_message_unref(reply);
+ }
+
+ if (p->target_state == HEADSET_STATE_CONNECTED) {
+ pending_connect_finalize(dev);
+ return;
+ }
+
+ p->err = sco_connect(dev, NULL, NULL, NULL);
+ if (p->err < 0)
+ pending_connect_finalize(dev);
+}
+
+static int event_reporting(struct audio_device *dev, const char *buf)
+{
+ struct headset *hs = dev->headset;
+ int ret;
+
+ ret = headset_send(hs, "\r\nOK\r\n");
+ if (ret < 0)
+ return ret;
+
+ if (hs->state != HEADSET_STATE_CONNECT_IN_PROGRESS)
+ return 0;
+
+ if (ag_features & AG_FEATURE_THREE_WAY_CALLING)
+ return 0;
+
+ hfp_slc_complete(dev);
+
+ return 0;
+}
+
+static int call_hold(struct audio_device *dev, const char *buf)
+{
+ struct headset *hs = dev->headset;
+ int err;
+
+ err = headset_send(hs, "\r\n+CHLD:(0,1,1x,2,2x,3,4)\r\n");
+ if (err < 0)
+ return err;
+
+ err = headset_send(hs, "\r\nOK\r\n");
+ if (err < 0)
+ return err;
+
+ if (hs->state != HEADSET_STATE_CONNECT_IN_PROGRESS)
+ return 0;
+
+ hfp_slc_complete(dev);
+
+ return 0;
+}
+
+static int answer_call(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+ int err;
+
+ g_dbus_emit_signal(device->conn, device->path,
+ AUDIO_HEADSET_INTERFACE, "AnswerRequested",
+ DBUS_TYPE_INVALID);
+
+ if (hs->ring_timer) {
+ g_source_remove(hs->ring_timer);
+ hs->ring_timer = 0;
+ }
+
+ if (!hs->hfp_active)
+ return headset_send(hs, "\r\nOK\r\n");
+
+ if (hs->ph_number) {
+ g_free(hs->ph_number);
+ hs->ph_number = NULL;
+ }
+
+ err = headset_send(hs, "\r\nOK\r\n");
+ if (err < 0)
+ return err;
+
+ /*+CIEV: (call = 1)*/
+ err = headset_send(hs, "\r\n+CIEV:2,1\r\n");
+ if (err < 0)
+ return err;
+
+ /*+CIEV: (callsetup = 0)*/
+ return headset_send(hs, "\r\n+CIEV:3,0\r\n");
+}
+
+static int terminate_call(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+ int err;
+
+ g_dbus_emit_signal(device->conn, device->path,
+ AUDIO_HEADSET_INTERFACE, "CallTerminated",
+ DBUS_TYPE_INVALID);
+
+ err = headset_send(hs, "\r\nOK\r\n");
+ if (err < 0)
+ return err;
+
+ if (hs->ph_number) {
+ g_free(hs->ph_number);
+ hs->ph_number = NULL;
+ }
+
+ if (hs->ring_timer) {
+ g_source_remove(hs->ring_timer);
+ hs->ring_timer = 0;
+ /*+CIEV: (callsetup = 0)*/
+ return headset_send(hs, "\r\n+CIEV:3,0\r\n");
+ }
+
+ /*+CIEV: (call = 0)*/
+ return headset_send(hs, "\r\n+CIEV:2,0\r\n");
+}
+
+static int cli_notification(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ hs->cli_active = buf[8] == '1' ? TRUE : FALSE;
+
+ return headset_send(hs, "\r\nOK\r\n");
+}
+
+static int signal_gain_setting(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+ const char *name;
+ dbus_uint16_t gain;
+
+ if (strlen(buf) < 8) {
+ error("Too short string for Gain setting");
+ return -EINVAL;
+ }
+
+ gain = (dbus_uint16_t) strtol(&buf[7], NULL, 10);
+
+ if (gain > 15) {
+ error("Invalid gain value received: %u", gain);
+ return -EINVAL;
+ }
+
+ switch (buf[5]) {
+ case HEADSET_GAIN_SPEAKER:
+ if (hs->sp_gain == gain)
+ goto ok;
+ name = "SpeakerGainChanged";
+ hs->sp_gain = gain;
+ break;
+ case HEADSET_GAIN_MICROPHONE:
+ if (hs->mic_gain == gain)
+ goto ok;
+ name = "MicrophoneGainChanged";
+ hs->mic_gain = gain;
+ break;
+ default:
+ error("Unknown gain setting");
+ return -EINVAL;
+ }
+
+ g_dbus_emit_signal(device->conn, device->path,
+ AUDIO_HEADSET_INTERFACE, name,
+ DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID);
+
+ok:
+ return headset_send(hs, "\r\nOK\r\n");
+}
+
+static struct event event_callbacks[] = {
+ { "ATA", answer_call },
+ { "AT+VG", signal_gain_setting },
+ { "AT+BRSF", supported_features },
+ { "AT+CIND", report_indicators },
+ { "AT+CMER", event_reporting },
+ { "AT+CHLD", call_hold },
+ { "AT+CHUP", terminate_call },
+ { "AT+CKPD", answer_call },
+ { "AT+CLIP", cli_notification },
+ { 0 }
+};
+
+static int handle_event(struct audio_device *device, const char *buf)
+{
+ struct event *ev;
+
+ debug("Received %s", buf);
+
+ for (ev = event_callbacks; ev->cmd; ev++) {
+ if (!strncmp(buf, ev->cmd, strlen(ev->cmd)))
+ return ev->callback(device, buf);
+ }
+
+ return -EINVAL;
+}
+
+static void close_sco(struct audio_device *device)
+{
+ struct headset *hs = device->headset;
+
+ if (hs->sco) {
+ g_source_remove(hs->sco_id);
+ hs->sco_id = 0;
+ g_io_channel_close(hs->sco);
+ g_io_channel_unref(hs->sco);
+ hs->sco = NULL;
+ }
+}
+
+static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond,
+ struct audio_device *device)
+{
+ struct headset *hs;
+ unsigned char buf[BUF_SIZE];
+ char *cr;
+ gsize bytes_read = 0;
+ gsize free_space;
+ int err;
+ off_t cmd_len;
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ hs = device->headset;
+
+ if (cond & (G_IO_ERR | G_IO_HUP))
+ goto failed;
+
+ if (g_io_channel_read(chan, (gchar *) buf, sizeof(buf) - 1,
+ &bytes_read) != G_IO_ERROR_NONE)
+ return TRUE;
+
+ free_space = sizeof(hs->buf) - hs->data_start - hs->data_length - 1;
+
+ if (free_space < bytes_read) {
+ /* Very likely that the HS is sending us garbage so
+ * just ignore the data and disconnect */
+ error("Too much data to fit incomming buffer");
+ goto failed;
+ }
+
+ memcpy(&hs->buf[hs->data_start], buf, bytes_read);
+ hs->data_length += bytes_read;
+
+ /* Make sure the data is null terminated so we can use string
+ * functions */
+ hs->buf[hs->data_start + hs->data_length] = '\0';
+
+ cr = strchr(&hs->buf[hs->data_start], '\r');
+ if (!cr)
+ return TRUE;
+
+ cmd_len = 1 + (off_t) cr - (off_t) &hs->buf[hs->data_start];
+ *cr = '\0';
+
+ err = handle_event(device, &hs->buf[hs->data_start]);
+ if (err == -EINVAL) {
+ error("Badly formated or unrecognized command: %s",
+ &hs->buf[hs->data_start]);
+ err = headset_send(hs, "\r\nERROR\r\n");
+ } else if (err < 0)
+ error("Error handling command %s: %s (%d)",
+ &hs->buf[hs->data_start], strerror(-err), -err);
+
+ hs->data_start += cmd_len;
+ hs->data_length -= cmd_len;
+
+ if (!hs->data_length)
+ hs->data_start = 0;
+
+ return TRUE;
+
+failed:
+ headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+
+ return FALSE;
+}
+
+static gboolean sco_cb(GIOChannel *chan, GIOCondition cond,
+ struct audio_device *device)
+{
+ struct headset *hs;
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ hs = device->headset;
+
+ error("Audio connection got disconnected");
+
+ headset_set_state(device, HEADSET_STATE_CONNECTED);
+
+ return FALSE;
+}
+
+static void rfcomm_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer user_data)
+{
+ struct audio_device *dev = user_data;
+ struct headset *hs = dev->headset;
+ struct pending_connect *p = hs->pending;
+ char hs_address[18];
+
+ if (err < 0) {
+ error("connect(): %s (%d)", strerror(-err), -err);
+ goto failed;
+ }
+
+ ba2str(&dev->dst, hs_address);
+ hs->rfcomm = chan;
+ p->io = NULL;
+
+ if (server_is_enabled(HANDSFREE_SVCLASS_ID) && hs->hfp_handle != 0)
+ hs->hfp_active = TRUE;
+ else
+ hs->hfp_active = FALSE;
+
+ g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP| G_IO_NVAL,
+ (GIOFunc) rfcomm_io_cb, dev);
+
+ debug("%s: Connected to %s", dev->path, hs_address);
+
+ /* In HFP mode wait for Service Level Connection */
+ if (hs->hfp_active)
+ return;
+
+ headset_set_state(dev, HEADSET_STATE_CONNECTED);
+
+ if (p->target_state == HEADSET_STATE_PLAYING) {
+ p->err = sco_connect(dev, NULL, NULL, NULL);
+ if (p->err < 0)
+ goto failed;
+ return;
+ }
+
+ if (p->msg) {
+ DBusMessage *reply = dbus_message_new_method_return(p->msg);
+ dbus_connection_send(dev->conn, reply, NULL);
+ dbus_message_unref(reply);
+ }
+
+ pending_connect_finalize(dev);
+
+ return;
+
+failed:
+ if (p->msg)
+ error_connection_attempt_failed(dev->conn, p->msg, p->err);
+ pending_connect_finalize(dev);
+ if (hs->rfcomm)
+ headset_set_state(dev, HEADSET_STATE_CONNECTED);
+ else
+ headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+}
+
+static void get_record_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+ struct audio_device *dev = user_data;
+ struct headset *hs = dev->headset;
+ struct pending_connect *p = hs->pending;
+ int ch = -1;
+ sdp_record_t *record = NULL;
+ sdp_list_t *protos, *classes = NULL;
+ uuid_t uuid;
+
+ if (err < 0) {
+ error("Unable to get service record: %s (%d)", strerror(-err),
+ -err);
+ goto failed_not_supported;
+ }
+
+ if (!recs || !recs->data) {
+ error("No records found");
+ goto failed_not_supported;
+ }
+
+ record = recs->data;
+
+ if (sdp_get_service_classes(record, &classes) < 0) {
+ error("Unable to get service classes from record");
+ goto failed_not_supported;
+ }
+
+ memcpy(&uuid, classes->data, sizeof(uuid));
+
+ if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16) {
+ error("Not a 16 bit UUID");
+ goto failed_not_supported;
+ }
+
+ if (hs->search_hfp) {
+ if (uuid.value.uuid16 != HANDSFREE_SVCLASS_ID) {
+ error("Service record didn't contain the HFP UUID");
+ goto failed_not_supported;
+ }
+ hs->hfp_handle = record->handle;
+ } else {
+ if (uuid.value.uuid16 != HEADSET_SVCLASS_ID) {
+ error("Service record didn't contain the HSP UUID");
+ goto failed_not_supported;
+ }
+ hs->hsp_handle = record->handle;
+ }
+
+ if (!sdp_get_access_protos(record, &protos)) {
+ ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+ sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
+ NULL);
+ sdp_list_free(protos, NULL);
+ protos = NULL;
+ }
+
+ if (ch == -1) {
+ error("Unable to extract RFCOMM channel from service record");
+ goto failed_not_supported;
+ }
+
+ hs->rfcomm_ch = ch;
+
+ err = rfcomm_connect(dev, NULL, NULL, NULL);
+ if (err < 0) {
+ error("Unable to connect: %s (%s)", strerror(-err), -err);
+ p->err = -err;
+ error_connection_attempt_failed(dev->conn, p->msg, p->err);
+ goto failed;
+ }
+
+ sdp_list_free(classes, free);
+ sdp_record_free(record);
+
+ return;
+
+failed_not_supported:
+ if (p->msg) {
+ error_not_supported(dev->conn, p->msg);
+ dbus_message_unref(p->msg);
+ p->msg = NULL;
+ }
+failed:
+ if (classes)
+ sdp_list_free(classes, free);
+ if (record)
+ sdp_record_free(record);
+ pending_connect_finalize(dev);
+ headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+}
+
+static int get_records(struct audio_device *device, headset_stream_cb_t cb,
+ void *user_data, unsigned int *cb_id)
+{
+ struct headset *hs = device->headset;
+ uuid_t uuid;
+
+ sdp_uuid16_create(&uuid, hs->search_hfp ? HANDSFREE_SVCLASS_ID :
+ HEADSET_SVCLASS_ID);
+
+ headset_set_state(device, HEADSET_STATE_CONNECT_IN_PROGRESS);
+
+ pending_connect_init(hs, HEADSET_STATE_CONNECTED);
+
+ if (cb) {
+ unsigned int id;
+ id = connect_cb_new(hs, HEADSET_STATE_CONNECTED,
+ cb, user_data);
+ if (cb_id)
+ *cb_id = id;
+ }
+
+ return bt_search_service(&device->src, &device->dst, &uuid,
+ get_record_cb, device, NULL);
+}
+
+static int rfcomm_connect(struct audio_device *dev, headset_stream_cb_t cb,
+ void *user_data, unsigned int *cb_id)
+{
+ struct headset *hs = dev->headset;
+ char address[18];
+ int err;
+
+ if (hs->rfcomm_ch < 0)
+ return get_records(dev, cb, user_data, cb_id);
+
+ ba2str(&dev->dst, address);
+
+ debug("%s: Connecting to %s channel %d", dev->path, address,
+ hs->rfcomm_ch);
+
+ err = bt_rfcomm_connect(&dev->src, &dev->dst, hs->rfcomm_ch,
+ rfcomm_connect_cb, dev);
+ if (err < 0) {
+ error("connect() failed: %s (%d)", strerror(-err), -err);
+ return err;
+ }
+
+ headset_set_state(dev, HEADSET_STATE_CONNECT_IN_PROGRESS);
+
+ pending_connect_init(hs, HEADSET_STATE_CONNECTED);
+
+ if (cb) {
+ unsigned int id = connect_cb_new(hs, HEADSET_STATE_CONNECTED,
+ cb, user_data);
+ if (cb_id)
+ *cb_id = id;
+ }
+
+ return 0;
+}
+
+static DBusMessage *hs_stop(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ DBusMessage *reply = NULL;
+
+ if (hs->state < HEADSET_STATE_PLAY_IN_PROGRESS)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".NotConnected",
+ "Device not Connected");
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ headset_set_state(device, HEADSET_STATE_CONNECTED);
+
+ return reply;
+}
+
+static DBusMessage *hs_is_playing(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ DBusMessage *reply;
+ dbus_bool_t playing;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ playing = (hs->state == HEADSET_STATE_PLAYING);
+
+ dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &playing,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *hs_disconnect(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ DBusMessage *reply = NULL;
+ char hs_address[18];
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (hs->state == HEADSET_STATE_DISCONNECTED)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".NotConnected",
+ "Device not Connected");
+
+ headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+ ba2str(&device->dst, hs_address);
+ info("Disconnected from %s, %s", hs_address, device->path);
+
+ return reply;
+}
+
+static DBusMessage *hs_is_connected(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ DBusMessage *reply;
+ dbus_bool_t connected;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ connected = (device->headset->state >= HEADSET_STATE_CONNECTED);
+
+ dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *hs_connect(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ int err;
+
+ if (hs->state == HEADSET_STATE_CONNECT_IN_PROGRESS)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress",
+ "Connect in Progress");
+ else if (hs->state > HEADSET_STATE_CONNECT_IN_PROGRESS)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".AlreadyConnected",
+ "Already Connected");
+
+ err = rfcomm_connect(device, NULL, NULL, NULL);
+ if (err < 0)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".ConnectAttemptFailed",
+ "Connect Attempt Failed");
+
+ hs->auto_dc = FALSE;
+
+ hs->pending->msg = dbus_message_ref(msg);
+
+ return NULL;
+}
+
+static gboolean ring_timer_cb(gpointer data)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ int err;
+
+ err = headset_send(hs, "\r\nRING\r\n");
+
+ if (err < 0)
+ error("Error while sending RING: %s (%d)",
+ strerror(-err), -err);
+
+ if (hs->cli_active && hs->ph_number) {
+ err = headset_send(hs, "\r\n+CLIP:\"%s\",%d\r\n",
+ hs->ph_number, hs->type);
+
+ if (err < 0)
+ error("Error while sending CLIP: %s (%d)",
+ strerror(-err), -err);
+ }
+
+ return TRUE;
+}
+
+static DBusMessage *hs_ring(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ DBusMessage *reply = NULL;
+ int err;
+
+ if (hs->state < HEADSET_STATE_CONNECTED)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".NotConnected",
+ "Device not Connected");
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (hs->ring_timer) {
+ debug("IndicateCall received when already indicating");
+ goto done;
+ }
+
+ err = headset_send(hs, "\r\nRING\r\n");
+ if (err < 0) {
+ dbus_message_unref(reply);
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "%s", strerror(-err));
+ }
+
+ if (hs->cli_active && hs->ph_number) {
+ err = headset_send(hs, "\r\n+CLIP:\"%s\",%d\r\n",
+ hs->ph_number, hs->type);
+ if (err < 0) {
+ dbus_message_unref(reply);
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".Failed", "%s",
+ strerror(-err));
+ }
+ }
+
+ hs->ring_timer = g_timeout_add(RING_INTERVAL, ring_timer_cb, device);
+
+done:
+ return reply;
+}
+
+static DBusMessage *hs_cancel_ringing(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ DBusMessage *reply = NULL;
+
+ if (hs->state < HEADSET_STATE_CONNECTED)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".NotConnected",
+ "Device not Connected");
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (!hs->ring_timer) {
+ debug("Got CancelRinging method call but ringing is not in progress");
+ goto done;
+ }
+
+ g_source_remove(hs->ring_timer);
+ hs->ring_timer = 0;
+
+done:
+ if (hs->hfp_active) {
+ int err;
+ /*+CIEV: (callsetup = 0)*/
+ err = headset_send(hs, "\r\n+CIEV:3,0\r\n");
+ if (err < 0) {
+ dbus_message_unref(reply);
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".Failed", "%s",
+ strerror(-err));
+ }
+ }
+
+ return reply;
+}
+
+static DBusMessage *hs_play(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ int err;
+
+ if (sco_hci) {
+ error("Refusing Headset.Play() because SCO HCI routing "
+ "is enabled");
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
+ "Operation not Available");
+ }
+
+ switch (hs->state) {
+ case HEADSET_STATE_DISCONNECTED:
+ case HEADSET_STATE_CONNECT_IN_PROGRESS:
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".NotConnected",
+ "Device not Connected");
+ case HEADSET_STATE_PLAY_IN_PROGRESS:
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".InProgress",
+ "Play in Progress");
+ case HEADSET_STATE_PLAYING:
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".AlreadyConnected",
+ "Device Already Connected");
+ case HEADSET_STATE_CONNECTED:
+ default:
+ break;
+ }
+
+ err = sco_connect(device, NULL, NULL, NULL);
+ if (err < 0)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "%s", strerror(-err));
+
+ hs->pending->msg = dbus_message_ref(msg);
+
+ return NULL;
+}
+
+static DBusMessage *hs_get_speaker_gain(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ DBusMessage *reply;
+ dbus_uint16_t gain;
+
+ if (hs->state < HEADSET_STATE_CONNECTED || hs->sp_gain < 0)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
+ "Operation not Available");
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ gain = (dbus_uint16_t) hs->sp_gain;
+
+ dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *hs_get_mic_gain(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ DBusMessage *reply;
+ dbus_uint16_t gain;
+
+ if (hs->state < HEADSET_STATE_CONNECTED || hs->mic_gain < 0)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
+ "Operation not Available");
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ gain = (dbus_uint16_t) hs->mic_gain;
+
+ dbus_message_append_args(reply, DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *hs_set_gain(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data, char type)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ DBusMessage *reply;
+ dbus_uint16_t gain;
+ int err;
+
+ if (hs->state < HEADSET_STATE_CONNECTED)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".NotConnected",
+ "Device not Connected");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ if (gain > 15)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".InvalidArgument",
+ "Must be less than or equal to 15");
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (hs->state != HEADSET_STATE_PLAYING)
+ goto done;
+
+ err = headset_send(hs, "\r\n+VG%c=%u\r\n", type, gain);
+ if (err < 0) {
+ dbus_message_unref(reply);
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "%s", strerror(-err));
+ }
+
+done:
+ if (type == HEADSET_GAIN_SPEAKER) {
+ hs->sp_gain = gain;
+ g_dbus_emit_signal(conn, device->path,
+ AUDIO_HEADSET_INTERFACE,
+ "SpeakerGainChanged",
+ DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID);
+ } else {
+ hs->mic_gain = gain;
+ g_dbus_emit_signal(conn, device->path,
+ AUDIO_HEADSET_INTERFACE,
+ "MicrophoneGainChanged",
+ DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID);
+ }
+
+ return reply;
+}
+
+static DBusMessage *hs_set_speaker_gain(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ return hs_set_gain(conn, msg, data, HEADSET_GAIN_SPEAKER);
+}
+
+static DBusMessage *hs_set_mic_gain(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ return hs_set_gain(conn, msg, data, HEADSET_GAIN_MICROPHONE);
+}
+
+static DBusMessage *hf_setup_call(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ DBusMessage *reply;
+ const char *value;
+ int err;
+
+ if (!hs->hfp_active)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".NotSuppported",
+ "Not Supported");
+
+ if (hs->state < HEADSET_STATE_CONNECTED)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".NotConnected",
+ "Device not Connected");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &value,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (!strncmp(value, "incoming", 8))
+ err = headset_send(hs, "\r\n+CIEV:3,1\r\n");
+ else if (!strncmp(value, "outgoing", 8))
+ err = headset_send(hs, "\r\n+CIEV:3,2\r\n");
+ else if (!strncmp(value, "remote", 6))
+ err = headset_send(hs, "\r\n+CIEV:3,3\r\n");
+ else
+ err = -EINVAL;
+
+ if (err < 0) {
+ dbus_message_unref(reply);
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "%s", strerror(-err));
+ }
+
+ return reply;
+}
+
+static DBusMessage *hf_identify_call(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ DBusMessage *reply;
+ const char *number;
+ dbus_int32_t type;
+
+ if (!hs->hfp_active && !hs->cli_active)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".NotSuppported",
+ "Not Supported");
+
+ if (hs->state < HEADSET_STATE_CONNECTED)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".NotConnected",
+ "Device not Connected");
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INT32, &type, DBUS_TYPE_INVALID))
+ return NULL;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ g_free(hs->ph_number);
+
+ hs->ph_number = g_strdup(number);
+ hs->type = type;
+
+ return reply;
+}
+
+static GDBusMethodTable headset_methods[] = {
+ { "Connect", "", "", hs_connect,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "Disconnect", "", "", hs_disconnect },
+ { "IsConnected", "", "b", hs_is_connected },
+ { "IndicateCall", "", "", hs_ring },
+ { "CancelCall", "", "", hs_cancel_ringing },
+ { "Play", "", "", hs_play,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "Stop", "", "", hs_stop },
+ { "IsPlaying", "", "b", hs_is_playing },
+ { "GetSpeakerGain", "", "q", hs_get_speaker_gain },
+ { "GetMicrophoneGain", "", "q", hs_get_mic_gain },
+ { "SetSpeakerGain", "q", "", hs_set_speaker_gain },
+ { "SetMicrophoneGain", "q", "", hs_set_mic_gain },
+ { "SetupCall", "s", "", hf_setup_call },
+ { "IdentifyCall", "si", "", hf_identify_call },
+ { NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable headset_signals[] = {
+ { "Connected", "" },
+ { "Disconnected", "" },
+ { "AnswerRequested", "" },
+ { "Stopped", "" },
+ { "Playing", "" },
+ { "SpeakerGainChanged", "q" },
+ { "MicrophoneGainChanged", "q" },
+ { "CallTerminated", "" },
+ { NULL, NULL }
+};
+
+static void headset_set_channel(struct headset *headset, sdp_record_t *record,
+ uint16_t svc)
+{
+ int ch;
+ sdp_list_t *protos;
+
+ if (sdp_get_access_protos(record, &protos) < 0) {
+ error("Unable to get access protos from headset record");
+ return;
+ }
+
+ ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+
+ sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
+ sdp_list_free(protos, NULL);
+
+ if (ch > 0) {
+ headset->rfcomm_ch = ch;
+ debug("Discovered %s service on RFCOMM channel %d",
+ svc == HEADSET_SVCLASS_ID ? "Headset" : "Handsfree",
+ ch);
+ } else
+ error("Unable to get RFCOMM channel from Headset record");
+}
+
+void headset_update(struct audio_device *dev, sdp_record_t *record, uint16_t svc)
+{
+ struct headset *headset = dev->headset;
+
+ switch (svc) {
+ case HANDSFREE_SVCLASS_ID:
+ if (headset->hfp_handle &&
+ (headset->hfp_handle != record->handle)) {
+ error("More than one HFP record found on device");
+ return;
+ }
+
+ headset->hfp_handle = record->handle;
+ break;
+
+ case HEADSET_SVCLASS_ID:
+ if (headset->hsp_handle &&
+ (headset->hsp_handle != record->handle)) {
+ error("More than one HSP record found on device");
+ return;
+ }
+
+ headset->hsp_handle = record->handle;
+
+ /* Ignore this record if we already have access to HFP */
+ if (headset->hfp_handle)
+ return;
+
+ break;
+
+ default:
+ debug("Invalid record passed to headset_update");
+ return;
+ }
+
+ headset_set_channel(headset, record, svc);
+}
+
+struct headset *headset_init(struct audio_device *dev, sdp_record_t *record,
+ uint16_t svc)
+{
+ struct headset *hs;
+
+ hs = g_new0(struct headset, 1);
+ hs->rfcomm_ch = -1;
+ hs->sp_gain = -1;
+ hs->mic_gain = -1;
+ hs->search_hfp = server_is_enabled(HANDSFREE_SVCLASS_ID);
+ hs->hfp_active = FALSE;
+ hs->cli_active = FALSE;
+ hs->ph_number = NULL;
+
+ if (!record)
+ goto register_iface;
+
+ switch (svc) {
+ case HANDSFREE_SVCLASS_ID:
+ hs->hfp_handle = record->handle;
+ break;
+
+ case HEADSET_SVCLASS_ID:
+ hs->hsp_handle = record->handle;
+ break;
+
+ default:
+ debug("Invalid record passed to headset_init");
+ g_free(hs);
+ return NULL;
+ }
+
+ headset_set_channel(hs, record, svc);
+register_iface:
+ if (!g_dbus_register_interface(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE,
+ headset_methods, headset_signals, NULL,
+ dev, NULL)) {
+ g_free(hs);
+ return NULL;
+ }
+
+ return hs;
+}
+
+uint32_t headset_config_init(GKeyFile *config)
+{
+ GError *err = NULL;
+ gboolean value;
+ char *str;
+
+ /* Use the default values if there is no config file */
+ if (config == NULL)
+ return ag_features;
+
+ str = g_key_file_get_string(config, "General", "SCORouting",
+ &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else {
+ if (strcmp(str, "PCM") == 0)
+ sco_hci = FALSE;
+ else if (strcmp(str, "HCI") == 0)
+ sco_hci = TRUE;
+ else
+ error("Invalid Headset Routing value: %s", str);
+ g_free(str);
+ }
+
+ value = g_key_file_get_boolean(config, "Headset", "3WayCalling",
+ &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else if (value)
+ ag_features |= AG_FEATURE_THREE_WAY_CALLING;
+
+ value = g_key_file_get_boolean(config, "Headset", "EchoCancelNoiseCancel",
+ &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else if (value)
+ ag_features |= AG_FEATURE_EC_ANDOR_NR;
+
+ value = g_key_file_get_boolean(config, "Headset", "VoiceRecognition",
+ &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else if (value)
+ ag_features |= AG_FEATURE_VOICE_RECOGNITION;
+
+ value = g_key_file_get_boolean(config, "Headset", "InBandRingtone",
+ &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else if (value)
+ ag_features |= AG_FEATURE_INBAND_RINGTONE;
+
+ value = g_key_file_get_boolean(config, "Headset", "VoiceTags",
+ &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else if (value)
+ ag_features |= AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG;
+
+ value = g_key_file_get_boolean(config, "Headset", "RejectingCalls",
+ &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else if (value)
+ ag_features |= AG_FEATURE_REJECT_A_CALL;
+
+ value = g_key_file_get_boolean(config, "Headset", "EnhancedCallStatus",
+ &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else if (value)
+ ag_features |= AG_FEATURE_ENHANCES_CALL_STATUS;
+
+ value = g_key_file_get_boolean(config, "Headset", "EnhancedCallControl",
+ &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else if (value)
+ ag_features |= AG_FEATURE_ENHANCES_CALL_CONTROL;
+
+ value = g_key_file_get_boolean(config, "Headset",
+ "ExtendedErrorResultCodes", &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else if (value)
+ ag_features |= AG_FEATURE_EXTENDED_ERROR_RESULT_CODES;
+
+ return ag_features;
+}
+
+void headset_free(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+
+ if (hs->dc_timer) {
+ g_source_remove(hs->dc_timer);
+ hs->dc_timer = 0;
+ }
+
+ if (hs->sco) {
+ g_io_channel_close(hs->sco);
+ g_io_channel_unref(hs->sco);
+ }
+
+ if (hs->rfcomm) {
+ g_io_channel_close(hs->rfcomm);
+ g_io_channel_unref(hs->rfcomm);
+ }
+
+ g_free(hs);
+ dev->headset = NULL;
+}
+
+static gboolean hs_dc_timeout(struct audio_device *dev)
+{
+ headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+ return FALSE;
+}
+
+gboolean headset_cancel_stream(struct audio_device *dev, unsigned int id)
+{
+ struct headset *hs = dev->headset;
+ struct pending_connect *p = hs->pending;
+ GSList *l;
+ struct connect_cb *cb = NULL;
+
+ if (!p)
+ return FALSE;
+
+ for (l = p->callbacks; l != NULL; l = l->next) {
+ struct connect_cb *tmp = l->data;
+
+ if (tmp->id == id) {
+ cb = tmp;
+ break;
+ }
+ }
+
+ if (!cb)
+ return FALSE;
+
+ p->callbacks = g_slist_remove(p->callbacks, cb);
+ g_free(cb);
+
+ if (p->callbacks || p->msg)
+ return TRUE;
+
+ pending_connect_finalize(dev);
+
+ if (hs->auto_dc) {
+ if (hs->rfcomm)
+ hs->dc_timer = g_timeout_add(DC_TIMEOUT,
+ (GSourceFunc) hs_dc_timeout,
+ dev);
+ else
+ headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+ }
+
+ return TRUE;
+}
+
+static gboolean dummy_connect_complete(struct audio_device *dev)
+{
+ pending_connect_finalize(dev);
+ return FALSE;
+}
+
+unsigned int headset_request_stream(struct audio_device *dev, headset_stream_cb_t cb,
+ void *user_data)
+{
+ struct headset *hs = dev->headset;
+ unsigned int id;
+
+ if (hs->rfcomm && hs->sco) {
+ id = connect_cb_new(hs, HEADSET_STATE_PLAYING, cb, user_data);
+ g_idle_add((GSourceFunc) dummy_connect_complete, dev);
+ return id;
+ }
+
+ if (hs->dc_timer) {
+ g_source_remove(hs->dc_timer);
+ hs->dc_timer = 0;
+ }
+
+ if (hs->state == HEADSET_STATE_CONNECT_IN_PROGRESS)
+ return connect_cb_new(hs, HEADSET_STATE_PLAYING, cb, user_data);
+
+ if (hs->rfcomm == NULL) {
+ if (rfcomm_connect(dev, cb, user_data, &id) < 0)
+ return 0;
+ hs->auto_dc = TRUE;
+ } else {
+ if (sco_connect(dev, cb, user_data, &id) < 0)
+ return 0;
+ }
+
+ hs->pending->target_state = HEADSET_STATE_PLAYING;
+
+ return id;
+}
+
+gboolean get_hfp_active(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+
+ return hs->hfp_active;
+}
+
+void set_hfp_active(struct audio_device *dev, gboolean active)
+{
+ struct headset *hs = dev->headset;
+
+ hs->hfp_active = active;
+}
+
+int headset_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
+{
+ struct headset *hs = dev->headset;
+
+ hs->tmp_rfcomm = io;
+
+ return hs->tmp_rfcomm ? 0 : -EINVAL;
+}
+
+int headset_close_rfcomm(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+ GIOChannel *rfcomm = hs->tmp_rfcomm ? hs->tmp_rfcomm : hs->rfcomm;
+
+ if (hs->ring_timer) {
+ g_source_remove(hs->ring_timer);
+ hs->ring_timer = 0;
+ }
+
+ if (rfcomm) {
+ g_io_channel_close(rfcomm);
+ g_io_channel_unref(rfcomm);
+ hs->tmp_rfcomm = NULL;
+ hs->rfcomm = NULL;
+ }
+
+ hs->data_start = 0;
+ hs->data_length = 0;
+
+ return 0;
+}
+
+void headset_set_authorized(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+
+ hs->rfcomm = hs->tmp_rfcomm;
+ hs->tmp_rfcomm = NULL;
+
+ g_io_add_watch(hs->rfcomm,
+ G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ (GIOFunc) rfcomm_io_cb, dev);
+
+ hs->auto_dc = FALSE;
+
+ if (!hs->hfp_active)
+ headset_set_state(dev, HEADSET_STATE_CONNECTED);
+}
+
+void headset_set_state(struct audio_device *dev, headset_state_t state)
+{
+ struct headset *hs = dev->headset;
+
+ if (hs->state == state)
+ return;
+
+ switch (state) {
+ case HEADSET_STATE_DISCONNECTED:
+ close_sco(dev);
+ headset_close_rfcomm(dev);
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE,
+ "Disconnected",
+ DBUS_TYPE_INVALID);
+ break;
+ case HEADSET_STATE_CONNECT_IN_PROGRESS:
+ break;
+ case HEADSET_STATE_CONNECTED:
+ close_sco(dev);
+ if (hs->state < state) {
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE,
+ "Connected",
+ DBUS_TYPE_INVALID);
+ } else if (hs->state == HEADSET_STATE_PLAYING) {
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE,
+ "Stopped",
+ DBUS_TYPE_INVALID);
+ }
+ break;
+ case HEADSET_STATE_PLAY_IN_PROGRESS:
+ break;
+ case HEADSET_STATE_PLAYING:
+ hs->sco_id = g_io_add_watch(hs->sco,
+ G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) sco_cb, dev);
+
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE,
+ "Playing", DBUS_TYPE_INVALID);
+
+ if (hs->sp_gain >= 0)
+ headset_send(hs, "\r\n+VGS=%u\r\n", hs->sp_gain);
+ if (hs->mic_gain >= 0)
+ headset_send(hs, "\r\n+VGM=%u\r\n", hs->mic_gain);
+ break;
+ }
+
+ debug("State changed %s: %s -> %s", dev->path, str_state[hs->state],
+ str_state[state]);
+ hs->state = state;
+}
+
+headset_state_t headset_get_state(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+
+ return hs->state;
+}
+
+int headset_get_channel(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+
+ return hs->rfcomm_ch;
+}
+
+gboolean headset_is_active(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+
+ if (hs->state != HEADSET_STATE_DISCONNECTED)
+ return TRUE;
+
+ return FALSE;
+}
+
+gboolean headset_lock(struct audio_device *dev, headset_lock_t lock)
+{
+ struct headset *hs = dev->headset;
+
+ if (hs->lock & lock)
+ return FALSE;
+
+ hs->lock |= lock;
+
+ return TRUE;
+}
+
+gboolean headset_unlock(struct audio_device *dev, headset_lock_t lock)
+{
+ struct headset *hs = dev->headset;
+
+ if (!(hs->lock & lock))
+ return FALSE;
+
+ hs->lock &= ~lock;
+
+ if (hs->lock)
+ return TRUE;
+
+ if (hs->state == HEADSET_STATE_PLAYING)
+ headset_set_state(dev, HEADSET_STATE_CONNECTED);
+
+ if (hs->auto_dc) {
+ if (hs->state == HEADSET_STATE_CONNECTED)
+ hs->dc_timer = g_timeout_add(DC_TIMEOUT,
+ (GSourceFunc) hs_dc_timeout,
+ dev);
+ else
+ headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+ }
+
+ return TRUE;
+}
+
+gboolean headset_suspend(struct audio_device *dev, void *data)
+{
+ return TRUE;
+}
+
+gboolean headset_play(struct audio_device *dev, void *data)
+{
+ return TRUE;
+}
+
+int headset_get_sco_fd(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+
+ if (!hs->sco)
+ return -1;
+
+ return g_io_channel_unix_get_fd(hs->sco);
+}
diff --git a/audio/headset.h b/audio/headset.h
new file mode 100644
index 00000000..b61a1015
--- /dev/null
+++ b/audio/headset.h
@@ -0,0 +1,77 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 AUDIO_HEADSET_INTERFACE "org.bluez.audio.Headset"
+
+#define DEFAULT_HS_AG_CHANNEL 12
+#define DEFAULT_HF_AG_CHANNEL 13
+
+typedef enum {
+ HEADSET_STATE_DISCONNECTED,
+ HEADSET_STATE_CONNECT_IN_PROGRESS,
+ HEADSET_STATE_CONNECTED,
+ HEADSET_STATE_PLAY_IN_PROGRESS,
+ HEADSET_STATE_PLAYING
+} headset_state_t;
+
+typedef enum {
+ HEADSET_LOCK_READ = 1,
+ HEADSET_LOCK_WRITE = 1 << 1,
+} headset_lock_t;
+
+typedef void (*headset_stream_cb_t) (struct audio_device *dev, void *user_data);
+
+struct headset *headset_init(struct audio_device *dev, sdp_record_t *record,
+ uint16_t svc);
+
+void headset_free(struct audio_device *dev);
+
+uint32_t headset_config_init(GKeyFile *config);
+
+void headset_update(struct audio_device *dev, sdp_record_t *record, uint16_t svc);
+
+unsigned int headset_request_stream(struct audio_device *dev,
+ headset_stream_cb_t cb, void *user_data);
+gboolean headset_cancel_stream(struct audio_device *dev, unsigned int id);
+
+gboolean get_hfp_active(struct audio_device *dev);
+void set_hfp_active(struct audio_device *dev, gboolean active);
+
+void headset_set_authorized(struct audio_device *dev);
+int headset_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+int headset_close_rfcomm(struct audio_device *dev);
+
+headset_state_t headset_get_state(struct audio_device *dev);
+void headset_set_state(struct audio_device *dev, headset_state_t state);
+
+int headset_get_channel(struct audio_device *dev);
+
+int headset_get_sco_fd(struct audio_device *dev);
+
+gboolean headset_is_active(struct audio_device *dev);
+
+gboolean headset_lock(struct audio_device *dev, headset_lock_t lock);
+gboolean headset_unlock(struct audio_device *dev, headset_lock_t lock);
+gboolean headset_suspend(struct audio_device *dev, void *data);
+gboolean headset_play(struct audio_device *dev, void *data);
diff --git a/audio/ipc.c b/audio/ipc.c
new file mode 100644
index 00000000..e7b712da
--- /dev/null
+++ b/audio/ipc.c
@@ -0,0 +1,119 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "ipc.h"
+
+/* This table contains the string representation for messages */
+static const char *strmsg[] = {
+ "BT_GETCAPABILITIES_REQ",
+ "BT_GETCAPABILITIES_RSP",
+ "BT_SETCONFIGURATION_REQ",
+ "BT_SETCONFIGURATION_RSP",
+ "BT_STREAMSTART_REQ",
+ "BT_STREAMSTART_RSP",
+ "BT_STREAMSTOP_REQ",
+ "BT_STREAMSTOP_RSP",
+ "BT_STREAMSUSPEND_IND",
+ "BT_STREAMRESUME_IND",
+ "BT_CONTROL_REQ",
+ "BT_CONTROL_RSP",
+ "BT_CONTROL_IND",
+ "BT_STREAMFD_IND",
+};
+
+int bt_audio_service_open()
+{
+ int sk;
+ int err;
+ struct sockaddr_un addr = {
+ AF_UNIX, BT_IPC_SOCKET_NAME
+ };
+
+ sk = socket(PF_LOCAL, SOCK_STREAM, 0);
+ if (sk < 0) {
+ err = errno;
+ fprintf(stderr, "%s: Cannot open socket: %s (%d)\n",
+ __FUNCTION__, strerror(err), err);
+ errno = err;
+ return -1;
+ }
+
+ if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ err = errno;
+ fprintf(stderr, "%s: connect() failed: %s (%d)\n",
+ __FUNCTION__, strerror(err), err);
+ close(sk);
+ errno = err;
+ return -1;
+ }
+
+ return sk;
+}
+
+int bt_audio_service_close(int sk)
+{
+ return close(sk);
+}
+
+int bt_audio_service_get_data_fd(int sk)
+{
+ char cmsg_b[CMSG_SPACE(sizeof(int))], m;
+ int err, ret;
+ struct iovec iov = { &m, sizeof(m) };
+ struct msghdr msgh;
+ struct cmsghdr *cmsg;
+
+ memset(&msgh, 0, sizeof(msgh));
+ msgh.msg_iov = &iov;
+ msgh.msg_iovlen = 1;
+ msgh.msg_control = &cmsg_b;
+ msgh.msg_controllen = CMSG_LEN(sizeof(int));
+
+ ret = recvmsg(sk, &msgh, 0);
+ if (ret < 0) {
+ err = errno;
+ fprintf(stderr, "%s: Unable to receive fd: %s (%d)\n",
+ __FUNCTION__, strerror(err), err);
+ errno = err;
+ return -1;
+ }
+
+ /* Receive auxiliary data in msgh */
+ for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL;
+ cmsg = CMSG_NXTHDR(&msgh, cmsg)) {
+ if (cmsg->cmsg_level == SOL_SOCKET
+ && cmsg->cmsg_type == SCM_RIGHTS)
+ return (*(int *) CMSG_DATA(cmsg));
+ }
+
+ errno = EINVAL;
+ return -1;
+}
+
+const char *bt_audio_strmsg(int type)
+{
+ if (type < 0 || type > (sizeof(strmsg) / sizeof(strmsg[0])))
+ return NULL;
+
+ return strmsg[type];
+}
+
diff --git a/audio/ipc.h b/audio/ipc.h
new file mode 100644
index 00000000..c900fcd1
--- /dev/null
+++ b/audio/ipc.h
@@ -0,0 +1,308 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+/*
+ Message sequence chart of streaming sequence for A2DP transport
+
+ Audio daemon User
+ on snd_pcm_open
+ <--BT_GETCAPABILITIES_REQ
+
+ BT_GETCAPABILITIES_RSP-->
+
+ on snd_pcm_hw_params
+ <--BT_SETCONFIGURATION_REQ
+
+ BT_SETCONFIGURATION_RSP-->
+
+ on snd_pcm_prepare
+ <--BT_STREAMSTART_REQ
+
+ <Moves to streaming state>
+ BT_STREAMSTART_RSP-->
+
+ BT_STREAMFD_IND -->
+
+ < streams data >
+ ..........
+
+ on snd_pcm_drop/snd_pcm_drain
+
+ <--BT_STREAMSTOP_REQ
+
+ <Moves to open state>
+ BT_STREAMSTOP_RSP-->
+
+ on IPC close or appl crash
+ <Moves to idle>
+
+ */
+
+#ifndef BT_AUDIOCLIENT_H
+#define BT_AUDIOCLIENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+
+#define BT_AUDIO_IPC_PACKET_SIZE 128
+#define BT_IPC_SOCKET_NAME "\0/org/bluez/audio"
+
+/* Generic message header definition, except for RSP messages */
+typedef struct {
+ uint8_t msg_type;
+} __attribute__ ((packed)) bt_audio_msg_header_t;
+
+/* Generic message header definition, for all RSP messages */
+typedef struct {
+ bt_audio_msg_header_t msg_h;
+ uint8_t posix_errno;
+} __attribute__ ((packed)) bt_audio_rsp_msg_header_t;
+
+/* Messages list */
+#define BT_GETCAPABILITIES_REQ 0
+#define BT_GETCAPABILITIES_RSP 1
+
+#define BT_SETCONFIGURATION_REQ 2
+#define BT_SETCONFIGURATION_RSP 3
+
+#define BT_STREAMSTART_REQ 4
+#define BT_STREAMSTART_RSP 5
+
+#define BT_STREAMSTOP_REQ 6
+#define BT_STREAMSTOP_RSP 7
+
+#define BT_STREAMSUSPEND_IND 8
+#define BT_STREAMRESUME_IND 9
+
+#define BT_CONTROL_REQ 10
+#define BT_CONTROL_RSP 11
+#define BT_CONTROL_IND 12
+
+#define BT_STREAMFD_IND 13
+
+/* BT_GETCAPABILITIES_REQ */
+
+#define BT_CAPABILITIES_TRANSPORT_A2DP 0
+#define BT_CAPABILITIES_TRANSPORT_SCO 1
+#define BT_CAPABILITIES_TRANSPORT_ANY 2
+
+#define BT_CAPABILITIES_ACCESS_MODE_READ 1
+#define BT_CAPABILITIES_ACCESS_MODE_WRITE 2
+#define BT_CAPABILITIES_ACCESS_MODE_READWRITE 3
+
+#define BT_FLAG_AUTOCONNECT 1
+
+struct bt_getcapabilities_req {
+ bt_audio_msg_header_t h;
+ char device[18]; /* Address of the remote Device */
+ uint8_t transport; /* Requested transport */
+ uint8_t flags; /* Requested flags */
+} __attribute__ ((packed));
+
+/* BT_GETCAPABILITIES_RSP */
+
+/**
+ * SBC Codec parameters as per A2DP profile 1.0 § 4.3
+ */
+
+#define BT_SBC_SAMPLING_FREQ_16000 (1 << 3)
+#define BT_SBC_SAMPLING_FREQ_32000 (1 << 2)
+#define BT_SBC_SAMPLING_FREQ_44100 (1 << 1)
+#define BT_SBC_SAMPLING_FREQ_48000 1
+
+#define BT_A2DP_CHANNEL_MODE_MONO (1 << 3)
+#define BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL (1 << 2)
+#define BT_A2DP_CHANNEL_MODE_STEREO (1 << 1)
+#define BT_A2DP_CHANNEL_MODE_JOINT_STEREO 1
+
+#define BT_A2DP_BLOCK_LENGTH_4 (1 << 3)
+#define BT_A2DP_BLOCK_LENGTH_8 (1 << 2)
+#define BT_A2DP_BLOCK_LENGTH_12 (1 << 1)
+#define BT_A2DP_BLOCK_LENGTH_16 1
+
+#define BT_A2DP_SUBBANDS_4 (1 << 1)
+#define BT_A2DP_SUBBANDS_8 1
+
+#define BT_A2DP_ALLOCATION_SNR (1 << 1)
+#define BT_A2DP_ALLOCATION_LOUDNESS 1
+
+#define BT_MPEG_SAMPLING_FREQ_16000 (1 << 5)
+#define BT_MPEG_SAMPLING_FREQ_22050 (1 << 4)
+#define BT_MPEG_SAMPLING_FREQ_24000 (1 << 3)
+#define BT_MPEG_SAMPLING_FREQ_32000 (1 << 2)
+#define BT_MPEG_SAMPLING_FREQ_44100 (1 << 1)
+#define BT_MPEG_SAMPLING_FREQ_48000 1
+
+#define BT_MPEG_LAYER_1 (1 << 2)
+#define BT_MPEG_LAYER_2 (1 << 1)
+#define BT_MPEG_LAYER_3 1
+
+typedef struct {
+ uint8_t channel_mode;
+ uint8_t frequency;
+ uint8_t allocation_method;
+ uint8_t subbands;
+ uint8_t block_length;
+ uint8_t min_bitpool;
+ uint8_t max_bitpool;
+} __attribute__ ((packed)) sbc_capabilities_t;
+
+typedef struct {
+ uint8_t channel_mode;
+ uint8_t crc;
+ uint8_t layer;
+ uint8_t frequency;
+ uint8_t mpf;
+ uint16_t bitrate;
+} __attribute__ ((packed)) mpeg_capabilities_t;
+
+struct bt_getcapabilities_rsp {
+ bt_audio_rsp_msg_header_t rsp_h;
+ uint8_t transport; /* Granted transport */
+ sbc_capabilities_t sbc_capabilities; /* A2DP only */
+ mpeg_capabilities_t mpeg_capabilities; /* A2DP only */
+ uint16_t sampling_rate; /* SCO only */
+} __attribute__ ((packed));
+
+/* BT_SETCONFIGURATION_REQ */
+struct bt_setconfiguration_req {
+ bt_audio_msg_header_t h;
+ char device[18]; /* Address of the remote Device */
+ uint8_t transport; /* Requested transport */
+ uint8_t access_mode; /* Requested access mode */
+ sbc_capabilities_t sbc_capabilities; /* A2DP only - only one of this field
+ and next one must be filled */
+ mpeg_capabilities_t mpeg_capabilities; /* A2DP only */
+} __attribute__ ((packed));
+
+/* BT_SETCONFIGURATION_RSP */
+struct bt_setconfiguration_rsp {
+ bt_audio_rsp_msg_header_t rsp_h;
+ uint8_t transport; /* Granted transport */
+ uint8_t access_mode; /* Granted access mode */
+ uint16_t link_mtu; /* Max length that transport supports */
+} __attribute__ ((packed));
+
+/* BT_STREAMSTART_REQ */
+#define BT_STREAM_ACCESS_READ 0
+#define BT_STREAM_ACCESS_WRITE 1
+#define BT_STREAM_ACCESS_READWRITE 2
+struct bt_streamstart_req {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+/* BT_STREAMSTART_RSP */
+struct bt_streamstart_rsp {
+ bt_audio_rsp_msg_header_t rsp_h;
+} __attribute__ ((packed));
+
+/* BT_STREAMFD_IND */
+/* This message is followed by one byte of data containing the stream data fd
+ as ancilliary data */
+struct bt_streamfd_ind {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+/* BT_STREAMSTOP_REQ */
+struct bt_streamstop_req {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+/* BT_STREAMSTOP_RSP */
+struct bt_streamstop_rsp {
+ bt_audio_rsp_msg_header_t rsp_h;
+} __attribute__ ((packed));
+
+/* BT_STREAMSUSPEND_IND */
+struct bt_streamsuspend_ind {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+/* BT_STREAMRESUME_IND */
+struct bt_streamresume_ind {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+/* BT_CONTROL_REQ */
+
+#define BT_CONTROL_KEY_POWER 0x40
+#define BT_CONTROL_KEY_VOL_UP 0x41
+#define BT_CONTROL_KEY_VOL_DOWN 0x42
+#define BT_CONTROL_KEY_MUTE 0x43
+#define BT_CONTROL_KEY_PLAY 0x44
+#define BT_CONTROL_KEY_STOP 0x45
+#define BT_CONTROL_KEY_PAUSE 0x46
+#define BT_CONTROL_KEY_RECORD 0x47
+#define BT_CONTROL_KEY_REWIND 0x48
+#define BT_CONTROL_KEY_FAST_FORWARD 0x49
+#define BT_CONTROL_KEY_EJECT 0x4A
+#define BT_CONTROL_KEY_FORWARD 0x4B
+#define BT_CONTROL_KEY_BACKWARD 0x4C
+
+struct bt_control_req {
+ bt_audio_msg_header_t h;
+ uint8_t mode; /* Control Mode */
+ uint8_t key; /* Control Key */
+} __attribute__ ((packed));
+
+/* BT_CONTROL_RSP */
+struct bt_control_rsp {
+ bt_audio_rsp_msg_header_t rsp_h;
+ uint8_t mode; /* Control Mode */
+ uint8_t key; /* Control Key */
+} __attribute__ ((packed));
+
+/* BT_CONTROL_IND */
+struct bt_control_ind {
+ bt_audio_msg_header_t h;
+ uint8_t mode; /* Control Mode */
+ uint8_t key; /* Control Key */
+} __attribute__ ((packed));
+
+/* Function declaration */
+
+/* Opens a connection to the audio service: return a socket descriptor */
+int bt_audio_service_open();
+
+/* Closes a connection to the audio service */
+int bt_audio_service_close(int sk);
+
+/* Receives stream data file descriptor : must be called after a
+BT_STREAMFD_IND message is returned */
+int bt_audio_service_get_data_fd(int sk);
+
+/* Human readable message type string */
+const char *bt_audio_strmsg(int type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BT_AUDIOCLIENT_H */
diff --git a/audio/main.c b/audio/main.c
new file mode 100644
index 00000000..607e3ca9
--- /dev/null
+++ b/audio/main.c
@@ -0,0 +1,170 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <sys/socket.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "plugin.h"
+#include "../hcid/device.h"
+#include "logging.h"
+#include "unix.h"
+#include "device.h"
+#include "manager.h"
+
+static DBusConnection *conn;
+
+static int headset_probe(struct btd_device *device)
+{
+ DBG("path %s", device->path);
+
+ return 0;
+}
+
+static void headset_remove(struct btd_device *device)
+{
+ DBG("path %s", device->path);
+}
+
+static struct btd_device_driver headset_driver = {
+ .name = "headset",
+ .uuids = BTD_UUIDS(HSP_HS_UUID, HFP_HS_UUID),
+ .probe = headset_probe,
+ .remove = headset_remove,
+};
+
+static int a2dp_probe(struct btd_device *device)
+{
+ DBG("path %s", device->path);
+
+ return 0;
+}
+
+static void a2dp_remove(struct btd_device *device)
+{
+ DBG("path %s", device->path);
+}
+
+static struct btd_device_driver a2dp_driver = {
+ .name = "sink",
+ .uuids = BTD_UUIDS(A2DP_SINK_UUID),
+ .probe = a2dp_probe,
+ .remove = a2dp_remove,
+};
+
+static int audio_probe(struct btd_device *device)
+{
+ DBG("path %s", device->path);
+
+ return 0;
+}
+
+static void audio_remove(struct btd_device *device)
+{
+ DBG("path %s", device->path);
+}
+
+static struct btd_device_driver audio_driver = {
+ .name = "audio",
+ .uuids = BTD_UUIDS(HSP_HS_UUID, HFP_HS_UUID, HSP_AG_UUID, HFP_AG_UUID,
+ ADVANCED_AUDIO_UUID, A2DP_SOURCE_UUID, A2DP_SINK_UUID,
+ AVRCP_TARGET_UUID, AVRCP_REMOTE_UUID),
+ .probe = audio_probe,
+ .remove = audio_remove,
+};
+
+
+static GKeyFile *load_config_file(const char *file)
+{
+ GError *err = NULL;
+ GKeyFile *keyfile;
+
+ keyfile = g_key_file_new();
+
+ if (!g_key_file_load_from_file(keyfile, file, 0, &err)) {
+ error("Parsing %s failed: %s", file, err->message);
+ g_error_free(err);
+ g_key_file_free(keyfile);
+ return NULL;
+ }
+
+ return keyfile;
+}
+
+static int audio_init(void)
+{
+ GKeyFile *config;
+
+ conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+ if (conn == NULL)
+ return -EIO;
+
+ config = load_config_file(CONFIGDIR "/audio.conf");
+
+ if (unix_init() < 0) {
+ error("Unable to setup unix socket");
+ return -EIO;
+ }
+
+ if (audio_manager_init(conn, config) < 0) {
+ dbus_connection_unref(conn);
+ return -EIO;
+ }
+
+ if (config)
+ g_key_file_free(config);
+
+ btd_register_device_driver(&headset_driver);
+
+ btd_register_device_driver(&a2dp_driver);
+
+ btd_register_device_driver(&audio_driver);
+
+ return 0;
+}
+
+static void audio_exit(void)
+{
+ btd_unregister_device_driver(&audio_driver);
+
+ btd_unregister_device_driver(&a2dp_driver);
+
+ btd_unregister_device_driver(&headset_driver);
+
+ audio_manager_exit();
+
+ unix_exit();
+
+ dbus_connection_unref(conn);
+}
+
+BLUETOOTH_PLUGIN_DEFINE("audio", audio_init, audio_exit)
diff --git a/audio/manager.c b/audio/manager.c
new file mode 100644
index 00000000..18ef9eb4
--- /dev/null
+++ b/audio/manager.c
@@ -0,0 +1,1571 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <signal.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "glib-helper.h"
+
+#include "dbus-service.h"
+#include "logging.h"
+#include "textfile.h"
+#include "ipc.h"
+#include "device.h"
+#include "error.h"
+#include "avdtp.h"
+#include "a2dp.h"
+#include "headset.h"
+#include "gateway.h"
+#include "sink.h"
+#include "control.h"
+#include "manager.h"
+#include "sdpd.h"
+
+typedef enum {
+ HEADSET = 1 << 0,
+ GATEWAY = 1 << 1,
+ SINK = 1 << 2,
+ SOURCE = 1 << 3,
+ CONTROL = 1 << 4,
+ TARGET = 1 << 5,
+ INVALID = 1 << 6
+} audio_service_type;
+
+typedef enum {
+ GENERIC_AUDIO = 0,
+ ADVANCED_AUDIO,
+ AV_REMOTE,
+ GET_RECORDS
+} audio_sdp_state_t;
+
+struct audio_sdp_data {
+ struct audio_device *device;
+
+ DBusMessage *msg; /* Method call or NULL */
+
+ GSList *records; /* sdp_record_t * */
+
+ audio_sdp_state_t state;
+
+ create_dev_cb_t cb;
+ void *cb_data;
+};
+
+static DBusConnection *connection = NULL;
+
+static struct audio_device *default_hs = NULL;
+static struct audio_device *default_dev = NULL;
+
+static GSList *devices = NULL;
+
+static uint32_t hsp_ag_record_id = 0;
+static uint32_t hfp_ag_record_id = 0;
+
+static uint32_t hsp_hs_record_id = 0;
+
+static GIOChannel *hsp_ag_server = NULL;
+static GIOChannel *hfp_ag_server = NULL;
+
+static GIOChannel *hsp_hs_server = NULL;
+
+static struct enabled_interfaces enabled = {
+ .headset = TRUE,
+ .gateway = FALSE,
+ .sink = TRUE,
+ .source = FALSE,
+ .control = TRUE,
+};
+
+static DBusMessage *get_records(uuid_t *uuid, struct audio_sdp_data *data);
+
+static struct audio_device *create_device(const bdaddr_t *bda)
+{
+ static int device_id = 0;
+ char path[128];
+
+ snprintf(path, sizeof(path) - 1,
+ "%s/device%d", AUDIO_MANAGER_PATH, device_id++);
+
+ return device_register(connection, path, bda);
+}
+
+static void destroy_device(struct audio_device *device)
+{
+ g_dbus_unregister_all_interfaces(connection, device->path);
+}
+
+static void remove_device(struct audio_device *device)
+{
+ if (device == default_dev) {
+ debug("Removing default device");
+ default_dev = NULL;
+ }
+
+ if (device == default_hs) {
+ debug("Removing default headset");
+ default_hs = NULL;
+ }
+
+ devices = g_slist_remove(devices, device);
+
+ destroy_device(device);
+}
+
+static gboolean add_device(struct audio_device *device, gboolean send_signals)
+{
+ if (!send_signals)
+ goto add;
+
+ g_dbus_emit_signal(connection, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ "DeviceCreated",
+ DBUS_TYPE_STRING, &device->path,
+ DBUS_TYPE_INVALID);
+
+ if (device->headset)
+ g_dbus_emit_signal(connection,
+ AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ "HeadsetCreated",
+ DBUS_TYPE_STRING, &device->path,
+ DBUS_TYPE_INVALID);
+add:
+
+ if (default_dev == NULL && g_slist_length(devices) == 0) {
+ debug("Selecting default device");
+ default_dev = device;
+ }
+
+ if (!default_hs && device->headset && !devices)
+ default_hs = device;
+
+ devices = g_slist_append(devices, device);
+
+ return TRUE;
+}
+
+static uint16_t get_service_uuid(const sdp_record_t *record)
+{
+ sdp_list_t *classes;
+ uuid_t uuid;
+ uint16_t uuid16 = 0;
+
+ if (sdp_get_service_classes(record, &classes) < 0) {
+ error("Unable to get service classes from record");
+ return 0;
+ }
+
+ memcpy(&uuid, classes->data, sizeof(uuid));
+
+ if (!sdp_uuid128_to_uuid(&uuid)) {
+ error("Not a 16 bit UUID");
+ sdp_list_free(classes, free);
+ return 0;
+ }
+
+ if (uuid.type == SDP_UUID32) {
+ if (uuid.value.uuid32 > 0xFFFF) {
+ error("Not a 16 bit UUID");
+ goto done;
+ }
+ uuid16 = (uint16_t) uuid.value.uuid32;
+ } else
+ uuid16 = uuid.value.uuid16;
+
+done:
+ sdp_list_free(classes, free);
+
+ return uuid16;
+}
+
+gboolean server_is_enabled(uint16_t svc)
+{
+ gboolean ret;
+
+ switch (svc) {
+ case HEADSET_SVCLASS_ID:
+ ret = (hsp_ag_server != NULL);
+ break;
+ case HEADSET_AGW_SVCLASS_ID:
+ ret = (hsp_hs_server != NULL);
+ break;
+ case HANDSFREE_SVCLASS_ID:
+ ret = (hfp_ag_server != NULL);
+ break;
+ case HANDSFREE_AGW_SVCLASS_ID:
+ ret = FALSE;
+ break;
+ case AUDIO_SINK_SVCLASS_ID:
+ return enabled.sink;
+ case AV_REMOTE_TARGET_SVCLASS_ID:
+ case AV_REMOTE_SVCLASS_ID:
+ return enabled.control;
+ default:
+ ret = FALSE;
+ break;
+ }
+
+ return ret;
+}
+
+static void handle_record(sdp_record_t *record, struct audio_device *device)
+{
+ gboolean is_default;
+ uint16_t uuid16;
+
+ uuid16 = get_service_uuid(record);
+
+ if (!server_is_enabled(uuid16))
+ return;
+
+ switch (uuid16) {
+ case HEADSET_SVCLASS_ID:
+ debug("Found Headset record");
+ if (device->headset)
+ headset_update(device, record, uuid16);
+ else
+ device->headset = headset_init(device,
+ record, uuid16);
+ break;
+ case HEADSET_AGW_SVCLASS_ID:
+ debug("Found Headset AG record");
+ break;
+ case HANDSFREE_SVCLASS_ID:
+ debug("Found Hansfree record");
+ if (device->headset)
+ headset_update(device, record, uuid16);
+ else
+ device->headset = headset_init(device,
+ record, uuid16);
+ break;
+ case HANDSFREE_AGW_SVCLASS_ID:
+ debug("Found Handsfree AG record");
+ break;
+ case AUDIO_SINK_SVCLASS_ID:
+ debug("Found Audio Sink");
+ if (device->sink == NULL)
+ device->sink = sink_init(device);
+ break;
+ case AUDIO_SOURCE_SVCLASS_ID:
+ debug("Found Audio Source");
+ break;
+ case AV_REMOTE_SVCLASS_ID:
+ debug("Found AV Remote");
+ if (device->control == NULL)
+ device->control = control_init(device);
+ if (device->sink && sink_is_active(device))
+ avrcp_connect(device);
+ break;
+ case AV_REMOTE_TARGET_SVCLASS_ID:
+ debug("Found AV Target");
+ if (device->control == NULL)
+ device->control = control_init(device);
+ if (device->sink && sink_is_active(device))
+ avrcp_connect(device);
+ break;
+ default:
+ debug("Unrecognized UUID: 0x%04X", uuid16);
+ break;
+ }
+
+ is_default = (default_dev == device) ? TRUE : FALSE;
+
+ device_store(device, is_default);
+}
+
+static void finish_sdp(struct audio_sdp_data *data, gboolean success)
+{
+ const char *addr;
+ DBusMessage *reply = NULL;
+ DBusError derr;
+
+ debug("Audio service discovery completed with %s",
+ success ? "success" : "failure");
+
+ if (!success)
+ goto done;
+
+ if (!data->msg)
+ goto update;
+
+ dbus_error_init(&derr);
+ dbus_message_get_args(data->msg, &derr,
+ DBUS_TYPE_STRING, &addr,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_error_is_set(&derr)) {
+ error("Unable to get message args");
+ success = FALSE;
+ error_failed(connection, data->msg, derr.message);
+ dbus_error_free(&derr);
+ goto done;
+ }
+
+ /* Return error if no audio related service records were found */
+ if (!data->records) {
+ debug("No audio audio related service records were found");
+ success = FALSE;
+ error_not_supported(connection, data->msg);
+ goto done;
+ }
+
+ reply = dbus_message_new_method_return(data->msg);
+ if (!reply) {
+ success = FALSE;
+ error_failed(connection, data->msg, "Out of memory");
+ goto done;
+ }
+
+update:
+ g_slist_foreach(data->records, (GFunc) handle_record, data->device);
+
+ if (!g_slist_find(devices, data->device))
+ add_device(data->device, TRUE);
+
+ if (reply) {
+ dbus_message_append_args(reply, DBUS_TYPE_STRING,
+ &data->device->path,
+ DBUS_TYPE_INVALID);
+ dbus_connection_send(connection, reply, NULL);
+ dbus_message_unref(reply);
+ }
+
+done:
+ if (success) {
+ if (data->cb)
+ data->cb(data->device, data->cb_data);
+ } else {
+ if (data->cb)
+ data->cb(NULL, data->cb_data);
+ if (!g_slist_find(devices, data->device))
+ destroy_device(data->device);
+ }
+ if (data->msg)
+ dbus_message_unref(data->msg);
+ g_slist_foreach(data->records, (GFunc) sdp_record_free, NULL);
+ g_slist_free(data->records);
+ g_free(data);
+}
+
+static void get_records_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+ struct audio_sdp_data *data = user_data;
+ sdp_list_t *seq;
+ uuid_t uuid;
+
+ if (err < 0) {
+ error_connection_attempt_failed(connection, data->msg, -err);
+ finish_sdp(data, FALSE);
+ return;
+ }
+
+ for (seq = recs; seq; seq = seq->next) {
+ sdp_record_t *rec = (sdp_record_t *) seq->data;
+
+ if (!rec)
+ break;
+
+ data->records = g_slist_append(data->records, rec);
+ }
+
+ sdp_list_free(recs, NULL);
+
+ data->state++;
+
+ switch (data->state) {
+ case ADVANCED_AUDIO:
+ sdp_uuid16_create(&uuid, ADVANCED_AUDIO_SVCLASS_ID);
+ break;
+ case AV_REMOTE:
+ sdp_uuid16_create(&uuid, AV_REMOTE_SVCLASS_ID);
+ break;
+ default:
+ finish_sdp(data, TRUE);
+ return;
+ }
+
+ get_records(&uuid, data);
+}
+
+static DBusMessage *get_records(uuid_t *uuid, struct audio_sdp_data *data)
+{
+ struct audio_device *device = data->device;
+ DBusMessage *reply = NULL;
+ int err;
+
+ err = bt_search_service(&device->src, &device->dst, uuid,
+ get_records_cb, data, NULL);
+ if (!err)
+ return NULL;
+
+ if (data->msg)
+ reply = g_dbus_create_error(data->msg,
+ ERROR_INTERFACE ".ConnectionAttemptFailed",
+ strerror(-err));
+
+ finish_sdp(data, FALSE);
+
+ return reply;
+}
+
+static DBusMessage *resolve_services(DBusMessage *msg,
+ struct audio_device *device,
+ create_dev_cb_t cb,
+ void *user_data)
+{
+ struct audio_sdp_data *sdp_data;
+ uuid_t uuid;
+
+ sdp_data = g_new0(struct audio_sdp_data, 1);
+ if (msg)
+ sdp_data->msg = dbus_message_ref(msg);
+ sdp_data->device = device;
+ sdp_data->cb = cb;
+ sdp_data->cb_data = user_data;
+
+ sdp_uuid16_create(&uuid, GENERIC_AUDIO_SVCLASS_ID);
+
+ return get_records(&uuid, sdp_data);
+}
+
+struct audio_device *manager_device_connected(const bdaddr_t *bda, const char *uuid)
+{
+ struct audio_device *device;
+ const char *path;
+ gboolean headset = FALSE, created = FALSE;
+
+ device = manager_find_device(bda, NULL, FALSE);
+ if (!device) {
+ device = create_device(bda);
+ if (!device)
+ return NULL;
+ if (!add_device(device, TRUE)) {
+ destroy_device(device);
+ return NULL;
+ }
+ created = TRUE;
+ }
+
+ if (!strcmp(uuid, HSP_AG_UUID) || !strcmp(uuid, HFP_AG_UUID)) {
+ if (device->headset)
+ return device;
+
+ device->headset = headset_init(device, NULL, 0);
+
+ if (!device->headset)
+ return NULL;
+
+ headset = TRUE;
+ } else if (!strcmp(uuid, A2DP_SOURCE_UUID)) {
+ if (device->sink)
+ return device;
+
+ device->sink = sink_init(device);
+
+ if (!device->sink)
+ return NULL;
+ } else if (!strcmp(uuid, AVRCP_TARGET_UUID)) {
+ if (device->control)
+ return device;
+
+ device->control = control_init(device);
+
+ if (!device->control)
+ return NULL;
+ } else
+ return NULL;
+
+ path = device->path;
+
+ if (created) {
+ g_dbus_emit_signal(connection, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ "DeviceCreated",
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID);
+ resolve_services(NULL, device, NULL, NULL);
+ }
+
+ if (headset)
+ g_dbus_emit_signal(connection, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ "HeadsetCreated",
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID);
+
+ if (headset && !default_hs) {
+ default_hs = device;
+ g_dbus_emit_signal(connection, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ "DefaultHeadsetChanged",
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID);
+ }
+
+ if (!default_dev) {
+ default_dev = device;
+ g_dbus_emit_signal(connection, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ "DefaultDeviceChanged",
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID);
+ }
+
+ return device;
+}
+
+gboolean manager_create_device(bdaddr_t *bda, create_dev_cb_t cb,
+ void *user_data)
+{
+ struct audio_device *dev;
+
+ dev = create_device(bda);
+ if (!dev)
+ return FALSE;
+
+ resolve_services(NULL, dev, cb, user_data);
+
+ return TRUE;
+}
+
+static DBusMessage *am_create_device(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ const char *address, *path;
+ bdaddr_t bda;
+ struct audio_device *device;
+ DBusMessage *reply;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &address,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ str2ba(address, &bda);
+
+ device = manager_find_device(&bda, NULL, FALSE);
+ if (!device) {
+ device = create_device(&bda);
+ return resolve_services(msg, device, NULL, NULL);
+ }
+
+ path = device->path;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *am_list_devices(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ DBusMessageIter iter, array_iter;
+ DBusMessage *reply;
+ DBusError derr;
+ GSList *l;
+ gboolean hs_only = FALSE;
+
+ dbus_error_init(&derr);
+
+ if (dbus_message_is_method_call(msg, AUDIO_MANAGER_INTERFACE,
+ "ListHeadsets"))
+ hs_only = TRUE;
+ else
+ hs_only = FALSE;
+
+ 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_TYPE_STRING_AS_STRING, &array_iter);
+
+ for (l = devices; l != NULL; l = l->next) {
+ struct audio_device *device = l->data;
+
+ if (hs_only && !device->headset)
+ continue;
+
+ dbus_message_iter_append_basic(&array_iter,
+ DBUS_TYPE_STRING, &device->path);
+ }
+
+ dbus_message_iter_close_container(&iter, &array_iter);
+
+ return reply;
+}
+
+static gint device_path_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct audio_device *device = a;
+ const char *path = b;
+
+ return strcmp(device->path, path);
+}
+
+static DBusMessage *am_remove_device(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ DBusMessage *reply;
+ GSList *match;
+ const char *path;
+ struct audio_device *device;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ match = g_slist_find_custom(devices, path, device_path_cmp);
+ if (!match)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExists",
+ "Device does not exists");
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ device = match->data;
+ device_remove_stored(device);
+ remove_device(device);
+
+ /* Fallback to a valid default */
+ if (default_dev == NULL) {
+ const char *param;
+ GSList *l;
+
+ default_dev = manager_find_device(BDADDR_ANY, NULL, TRUE);
+
+ if (!default_dev && devices) {
+ l = devices;
+ default_dev = (g_slist_last(l))->data;
+ }
+
+ param = default_dev ? default_dev->path : "";
+
+ g_dbus_emit_signal(conn, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ "DefaultHeadsetChanged",
+ DBUS_TYPE_STRING, &param,
+ DBUS_TYPE_INVALID);
+
+ g_dbus_emit_signal(conn, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ "DefaultDeviceChanged",
+ DBUS_TYPE_STRING, &param,
+ DBUS_TYPE_INVALID);
+
+ if (default_dev)
+ device_store(default_dev, TRUE);
+ }
+
+ g_dbus_emit_signal(conn, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ "HeadsetRemoved",
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID);
+
+ g_dbus_emit_signal(conn, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ "DeviceRemoved",
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *am_find_by_addr(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ const char *address;
+ DBusMessage *reply;
+ struct audio_device *device;
+ bdaddr_t bda;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &address,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ str2ba(address, &bda);
+ device = manager_find_device(&bda, NULL, FALSE);
+
+ if (!device)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExists",
+ "Device does not exists");
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &device->path,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *am_default_device(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ DBusMessage *reply;
+
+ if (!default_dev)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExists",
+ "Device does not exists");
+
+ if (default_dev->headset == NULL &&
+ dbus_message_is_method_call(msg, AUDIO_MANAGER_INTERFACE,
+ "DefaultHeadset"))
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExists",
+ "Device does not exists");
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &default_dev->path,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *am_change_default_device(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ DBusMessage *reply;
+ GSList *match;
+ const char *path;
+ struct audio_device *device;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ match = g_slist_find_custom(devices, path, device_path_cmp);
+ if (!match)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExists",
+ "Device does not exists");
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ device = match->data;
+
+ if (!dbus_message_is_method_call(msg, AUDIO_MANAGER_INTERFACE,
+ "ChangeDefaultHeadset"))
+ g_dbus_emit_signal(conn, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ "DefaultDeviceChanged",
+ DBUS_TYPE_STRING, &device->path,
+ DBUS_TYPE_INVALID);
+ else if (device->headset)
+ g_dbus_emit_signal(conn, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ "DefaultHeadsetChanged",
+ DBUS_TYPE_STRING, &device->path,
+ DBUS_TYPE_INVALID);
+ else
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExists",
+ "Device does not exists");
+
+ default_dev = device;
+ device_store(device, TRUE);
+
+ return reply;
+}
+
+static GDBusMethodTable manager_methods[] = {
+ { "CreateDevice", "s", "s", am_create_device,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "RemoveDevice", "s", "", am_remove_device },
+ { "ListDevices", "", "as", am_list_devices },
+ { "DefaultDevice", "", "s", am_default_device },
+ { "ChangeDefaultDevice", "s", "", am_change_default_device },
+ { "CreateHeadset", "s", "s", am_create_device,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "RemoveHeadset", "s", "", am_remove_device },
+ { "ListHeadsets", "", "as", am_list_devices },
+ { "FindDeviceByAddress", "s", "s", am_find_by_addr },
+ { "DefaultHeadset", "", "s", am_default_device },
+ { "ChangeDefaultHeadset", "s", "", am_change_default_device },
+ { }
+};
+
+static GDBusSignalTable manager_signals[] = {
+ { "DeviceCreated", "s" },
+ { "DeviceRemoved", "s" },
+ { "HeadsetCreated", "s" },
+ { "HeadsetRemoved", "s" },
+ { "DefaultDeviceChanged", "s" },
+ { "DefaultHeadsetChanged", "s" },
+ { }
+};
+
+static void parse_stored_devices(char *key, char *value, void *data)
+{
+ bdaddr_t *src = data;
+ struct audio_device *device;
+ bdaddr_t dst;
+
+ if (!key || !value || strcmp(key, "default") == 0)
+ return;
+
+ str2ba(key, &dst);
+ device = manager_find_device(&dst, NULL, FALSE);
+
+ if (device)
+ return;
+
+ info("Loading device %s (%s)", key, value);
+
+ device = create_device(&dst);
+ if (!device)
+ return;
+
+ /* Change storage to source adapter */
+ bacpy(&device->store, src);
+
+ if (enabled.headset && strstr(value, "headset"))
+ device->headset = headset_init(device, NULL, 0);
+ if (enabled.sink && strstr(value, "sink"))
+ device->sink = sink_init(device);
+ if (enabled.control && strstr(value, "control"))
+ device->control = control_init(device);
+ add_device(device, FALSE);
+}
+
+static void register_devices_stored(const char *adapter)
+{
+ char filename[PATH_MAX + 1];
+ struct stat st;
+ struct audio_device *device;
+ bdaddr_t default_src;
+ bdaddr_t dst;
+ bdaddr_t src;
+ char *addr;
+ int dev_id;
+
+ create_name(filename, PATH_MAX, STORAGEDIR, adapter, "audio");
+
+ str2ba(adapter, &src);
+
+ if (stat(filename, &st) < 0)
+ return;
+
+ if (!(st.st_mode & __S_IFREG))
+ return;
+
+ textfile_foreach(filename, parse_stored_devices, &src);
+
+ bacpy(&default_src, BDADDR_ANY);
+ dev_id = hci_get_route(&default_src);
+ if (dev_id < 0 || hci_devba(dev_id, &default_src) < 0)
+ return;
+
+ if (bacmp(&default_src, &src) != 0)
+ return;
+
+ addr = textfile_get(filename, "default");
+ if (!addr)
+ return;
+
+ str2ba(addr, &dst);
+ device = manager_find_device(&dst, NULL, FALSE);
+
+ if (device) {
+ info("Setting %s as default device", addr);
+ default_dev = device;
+ }
+
+ free(addr);
+}
+
+static void register_stored(void)
+{
+ char dirname[PATH_MAX + 1];
+ struct dirent *de;
+ DIR *dir;
+
+ snprintf(dirname, PATH_MAX, "%s", STORAGEDIR);
+
+ dir = opendir(dirname);
+ if (!dir)
+ return;
+
+ while ((de = readdir(dir)) != NULL) {
+ if (!isdigit(de->d_name[0]))
+ continue;
+
+ /* Device objects */
+ register_devices_stored(de->d_name);
+ }
+
+ closedir(dir);
+}
+
+static void manager_unregister(void *data)
+{
+ info("Unregistered manager path");
+
+ if (devices) {
+ g_slist_foreach(devices, (GFunc) remove_device, NULL);
+ g_slist_free(devices);
+ devices = NULL;
+ }
+}
+
+static sdp_record_t *hsp_ag_record(uint8_t ch)
+{
+ sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+ uuid_t root_uuid, svclass_uuid, ga_svclass_uuid;
+ uuid_t l2cap_uuid, rfcomm_uuid;
+ sdp_profile_desc_t profile;
+ sdp_record_t *record;
+ sdp_list_t *aproto, *proto[2];
+ sdp_data_t *channel;
+
+ record = sdp_record_alloc();
+ if (!record)
+ return NULL;
+
+ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+ root = sdp_list_append(0, &root_uuid);
+ sdp_set_browse_groups(record, root);
+
+ sdp_uuid16_create(&svclass_uuid, HEADSET_AGW_SVCLASS_ID);
+ svclass_id = sdp_list_append(0, &svclass_uuid);
+ sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
+ svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
+ sdp_set_service_classes(record, svclass_id);
+
+ sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID);
+ profile.version = 0x0100;
+ pfseq = sdp_list_append(0, &profile);
+ sdp_set_profile_descs(record, pfseq);
+
+ sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+ proto[0] = sdp_list_append(0, &l2cap_uuid);
+ apseq = sdp_list_append(0, proto[0]);
+
+ sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+ proto[1] = sdp_list_append(0, &rfcomm_uuid);
+ channel = sdp_data_alloc(SDP_UINT8, &ch);
+ proto[1] = sdp_list_append(proto[1], channel);
+ apseq = sdp_list_append(apseq, proto[1]);
+
+ aproto = sdp_list_append(0, apseq);
+ sdp_set_access_protos(record, aproto);
+
+ sdp_set_info_attr(record, "Headset Audio Gateway", 0, 0);
+
+ sdp_data_free(channel);
+ sdp_list_free(proto[0], 0);
+ sdp_list_free(proto[1], 0);
+ sdp_list_free(apseq, 0);
+ sdp_list_free(pfseq, 0);
+ sdp_list_free(aproto, 0);
+ sdp_list_free(root, 0);
+ sdp_list_free(svclass_id, 0);
+
+ return record;
+}
+
+static sdp_record_t *hsp_hs_record(uint8_t ch)
+{
+ sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+ uuid_t root_uuid, svclass_uuid, ga_svclass_uuid;
+ uuid_t l2cap_uuid, rfcomm_uuid;
+ sdp_profile_desc_t profile;
+ sdp_record_t *record;
+ sdp_list_t *aproto, *proto[2];
+ sdp_data_t *channel;
+
+ record = sdp_record_alloc();
+ if (!record)
+ return NULL;
+
+ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+ root = sdp_list_append(0, &root_uuid);
+ sdp_set_browse_groups(record, root);
+
+ sdp_uuid16_create(&svclass_uuid, HEADSET_SVCLASS_ID);
+ svclass_id = sdp_list_append(0, &svclass_uuid);
+ sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
+ svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
+ sdp_set_service_classes(record, svclass_id);
+
+ sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID);
+ profile.version = 0x0100;
+ pfseq = sdp_list_append(0, &profile);
+ sdp_set_profile_descs(record, pfseq);
+
+ sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+ proto[0] = sdp_list_append(0, &l2cap_uuid);
+ apseq = sdp_list_append(0, proto[0]);
+
+ sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+ proto[1] = sdp_list_append(0, &rfcomm_uuid);
+ channel = sdp_data_alloc(SDP_UINT8, &ch);
+ proto[1] = sdp_list_append(proto[1], channel);
+ apseq = sdp_list_append(apseq, proto[1]);
+
+ aproto = sdp_list_append(0, apseq);
+ sdp_set_access_protos(record, aproto);
+
+ sdp_set_info_attr(record, "Headset", 0, 0);
+
+ sdp_data_free(channel);
+ sdp_list_free(proto[0], 0);
+ sdp_list_free(proto[1], 0);
+ sdp_list_free(apseq, 0);
+ sdp_list_free(pfseq, 0);
+ sdp_list_free(aproto, 0);
+ sdp_list_free(root, 0);
+ sdp_list_free(svclass_id, 0);
+
+ return record;
+}
+
+static sdp_record_t *hfp_ag_record(uint8_t ch, uint32_t feat)
+{
+ sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+ uuid_t root_uuid, svclass_uuid, ga_svclass_uuid;
+ uuid_t l2cap_uuid, rfcomm_uuid;
+ sdp_profile_desc_t profile;
+ sdp_list_t *aproto, *proto[2];
+ sdp_record_t *record;
+ sdp_data_t *channel, *features;
+ uint8_t netid = 0x01;
+ uint16_t sdpfeat;
+ sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid);
+
+ record = sdp_record_alloc();
+ if (!record)
+ return NULL;
+
+ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+ root = sdp_list_append(0, &root_uuid);
+ sdp_set_browse_groups(record, root);
+
+ sdp_uuid16_create(&svclass_uuid, HANDSFREE_AGW_SVCLASS_ID);
+ svclass_id = sdp_list_append(0, &svclass_uuid);
+ sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
+ svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
+ sdp_set_service_classes(record, svclass_id);
+
+ sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID);
+ profile.version = 0x0105;
+ pfseq = sdp_list_append(0, &profile);
+ sdp_set_profile_descs(record, pfseq);
+
+ sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+ proto[0] = sdp_list_append(0, &l2cap_uuid);
+ apseq = sdp_list_append(0, proto[0]);
+
+ sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+ proto[1] = sdp_list_append(0, &rfcomm_uuid);
+ channel = sdp_data_alloc(SDP_UINT8, &ch);
+ proto[1] = sdp_list_append(proto[1], channel);
+ apseq = sdp_list_append(apseq, proto[1]);
+
+ sdpfeat = (uint16_t) feat & 0xF;
+ features = sdp_data_alloc(SDP_UINT16, &sdpfeat);
+ sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+ aproto = sdp_list_append(0, apseq);
+ sdp_set_access_protos(record, aproto);
+
+ sdp_set_info_attr(record, "Hands-Free Audio Gateway", 0, 0);
+
+ sdp_attr_add(record, SDP_ATTR_EXTERNAL_NETWORK, network);
+
+ sdp_data_free(channel);
+ sdp_list_free(proto[0], 0);
+ sdp_list_free(proto[1], 0);
+ sdp_list_free(apseq, 0);
+ sdp_list_free(pfseq, 0);
+ sdp_list_free(aproto, 0);
+ sdp_list_free(root, 0);
+ sdp_list_free(svclass_id, 0);
+
+ return record;
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+ struct audio_device *device = user_data;
+ const char *uuid;
+
+ if (get_hfp_active(device))
+ uuid = HFP_AG_UUID;
+ else
+ uuid = HSP_AG_UUID;
+
+ if (derr && dbus_error_is_set(derr)) {
+ error("Access denied: %s", derr->message);
+ if (dbus_error_has_name(derr, DBUS_ERROR_NO_REPLY)) {
+ debug("Canceling authorization request");
+ service_cancel_auth(&device->src, &device->dst);
+ }
+
+ headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+ } else {
+ char hs_address[18];
+
+ headset_set_authorized(device);
+
+ ba2str(&device->dst, hs_address);
+
+ debug("Accepted headset connection from %s for %s",
+ hs_address, device->path);
+ headset_set_state(device, HEADSET_STATE_CONNECTED);
+ }
+}
+
+static void ag_io_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer data)
+{
+ const char *uuid;
+ struct audio_device *device;
+ gboolean hfp_active;
+
+ if (err < 0) {
+ error("accept: %s (%d)", strerror(-err), -err);
+ return;
+ }
+
+ if (chan == hsp_ag_server) {
+ hfp_active = FALSE;
+ uuid = HSP_AG_UUID;
+ } else {
+ hfp_active = TRUE;
+ uuid = HFP_AG_UUID;
+ }
+
+ device = manager_device_connected(dst, uuid);
+ if (!device)
+ goto drop;
+
+ if (headset_get_state(device) > HEADSET_STATE_DISCONNECTED) {
+ debug("Refusing new connection since one already exists");
+ goto drop;
+ }
+
+ set_hfp_active(device, hfp_active);
+
+ if (headset_connect_rfcomm(device, chan) < 0) {
+ error("Allocating new GIOChannel failed!");
+ goto drop;
+ }
+
+ err = service_req_auth(&device->src, &device->dst, uuid, auth_cb,
+ device);
+ if (err < 0) {
+ debug("Authorization denied: %s", strerror(-err));
+ headset_close_rfcomm(device);
+ return;
+ }
+
+ headset_set_state(device, HEADSET_STATE_CONNECT_IN_PROGRESS);
+
+ return;
+
+drop:
+ g_io_channel_close(chan);
+ g_io_channel_unref(chan);
+ return;
+}
+
+static void hs_io_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, void *data)
+{
+ /*Stub*/
+ return;
+}
+
+static int headset_server_init(DBusConnection *conn, GKeyFile *config)
+{
+ uint8_t chan = DEFAULT_HS_AG_CHANNEL;
+ sdp_record_t *record;
+ gboolean hfp = TRUE, master = TRUE;
+ GError *err = NULL;
+ uint32_t features, flags;
+
+ if (!enabled.headset)
+ return 0;
+
+ if (config) {
+ gboolean tmp;
+
+ tmp = g_key_file_get_boolean(config, "General", "Master",
+ &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else
+ master = tmp;
+
+ tmp = g_key_file_get_boolean(config, "Headset", "HFP",
+ &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else
+ hfp = tmp;
+ }
+
+ flags = RFCOMM_LM_SECURE;
+
+ if (master)
+ flags |= RFCOMM_LM_MASTER;
+
+ hsp_ag_server = bt_rfcomm_listen(BDADDR_ANY, chan, flags, ag_io_cb,
+ NULL);
+ if (!hsp_ag_server)
+ return -1;
+
+ record = hsp_ag_record(chan);
+ if (!record) {
+ error("Unable to allocate new service record");
+ return -1;
+ }
+
+ if (add_record_to_server(BDADDR_ANY, record) < 0) {
+ error("Unable to register HS AG service record");
+ sdp_record_free(record);
+ g_io_channel_unref(hsp_ag_server);
+ hsp_ag_server = NULL;
+ return -1;
+ }
+ hsp_ag_record_id = record->handle;
+
+ features = headset_config_init(config);
+
+ if (!hfp)
+ return 0;
+
+ chan = DEFAULT_HF_AG_CHANNEL;
+
+ hfp_ag_server = bt_rfcomm_listen(BDADDR_ANY, chan, flags, ag_io_cb,
+ NULL);
+ if (!hfp_ag_server)
+ return -1;
+
+ record = hfp_ag_record(chan, features);
+ if (!record) {
+ error("Unable to allocate new service record");
+ return -1;
+ }
+
+ if (add_record_to_server(BDADDR_ANY, record) < 0) {
+ error("Unable to register HF AG service record");
+ sdp_record_free(record);
+ g_io_channel_unref(hfp_ag_server);
+ hfp_ag_server = NULL;
+ return -1;
+ }
+ hfp_ag_record_id = record->handle;
+
+ return 0;
+}
+
+static int gateway_server_init(DBusConnection *conn, GKeyFile *config)
+{
+ uint8_t chan = DEFAULT_HSP_HS_CHANNEL;
+ sdp_record_t *record;
+ gboolean master = TRUE;
+ GError *err = NULL;
+ uint32_t flags;
+
+ if (!enabled.gateway)
+ return 0;
+
+ if (config) {
+ gboolean tmp;
+
+ tmp = g_key_file_get_boolean(config, "General", "Master",
+ &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ err = NULL;
+ } else
+ master = tmp;
+ }
+
+ flags = RFCOMM_LM_SECURE;
+
+ if (master)
+ flags |= RFCOMM_LM_MASTER;
+
+ hsp_hs_server = bt_rfcomm_listen(BDADDR_ANY, chan, flags, hs_io_cb,
+ NULL);
+ if (!hsp_hs_server)
+ return -1;
+
+ record = hsp_hs_record(chan);
+ if (!record) {
+ error("Unable to allocate new service record");
+ return -1;
+ }
+
+ if (add_record_to_server(BDADDR_ANY, record) < 0) {
+ error("Unable to register HSP HS service record");
+ sdp_record_free(record);
+ g_io_channel_unref(hsp_hs_server);
+ hsp_hs_server = NULL;
+ return -1;
+ }
+
+ hsp_hs_record_id = record->handle;
+
+ return 0;
+}
+
+static void server_exit(void)
+{
+ if (hsp_ag_record_id) {
+ remove_record_from_server(hsp_ag_record_id);
+ hsp_ag_record_id = 0;
+ }
+
+ if (hsp_ag_server) {
+ g_io_channel_unref(hsp_ag_server);
+ hsp_ag_server = NULL;
+ }
+
+ if (hsp_hs_record_id) {
+ remove_record_from_server(hsp_hs_record_id);
+ hsp_hs_record_id = 0;
+ }
+
+ if (hsp_hs_server) {
+ g_io_channel_unref(hsp_hs_server);
+ hsp_hs_server = NULL;
+ }
+
+ if (hfp_ag_record_id) {
+ remove_record_from_server(hfp_ag_record_id);
+ hfp_ag_record_id = 0;
+ }
+
+ if (hfp_ag_server) {
+ g_io_channel_unref(hfp_ag_server);
+ hfp_ag_server = NULL;
+ }
+}
+
+int audio_manager_init(DBusConnection *conn, GKeyFile *config)
+{
+ char **list;
+ int i;
+
+ connection = dbus_connection_ref(conn);
+
+ if (!config)
+ goto proceed;
+
+ list = g_key_file_get_string_list(config, "General", "Enable",
+ NULL, NULL);
+ for (i = 0; list && list[i] != NULL; i++) {
+ if (g_str_equal(list[i], "Headset"))
+ enabled.headset = TRUE;
+ else if (g_str_equal(list[i], "Gateway"))
+ enabled.gateway = TRUE;
+ else if (g_str_equal(list[i], "Sink"))
+ enabled.sink = TRUE;
+ else if (g_str_equal(list[i], "Source"))
+ enabled.source = TRUE;
+ else if (g_str_equal(list[i], "Control"))
+ enabled.control = TRUE;
+ }
+ g_strfreev(list);
+
+ list = g_key_file_get_string_list(config, "General", "Disable",
+ NULL, NULL);
+ for (i = 0; list && list[i] != NULL; i++) {
+ if (g_str_equal(list[i], "Headset"))
+ enabled.headset = FALSE;
+ else if (g_str_equal(list[i], "Gateway"))
+ enabled.gateway = FALSE;
+ else if (g_str_equal(list[i], "Sink"))
+ enabled.sink = FALSE;
+ else if (g_str_equal(list[i], "Source"))
+ enabled.source = FALSE;
+ else if (g_str_equal(list[i], "Control"))
+ enabled.control = FALSE;
+ }
+ g_strfreev(list);
+
+proceed:
+ if (enabled.headset) {
+ if (headset_server_init(conn, config) < 0)
+ goto failed;
+ }
+
+ if (enabled.gateway) {
+ if (gateway_server_init(conn, config) < 0)
+ goto failed;
+ }
+
+ if (enabled.source || enabled.sink) {
+ if (a2dp_init(conn, config) < 0)
+ goto failed;
+ }
+
+ if (enabled.control && avrcp_init(conn, config) < 0)
+ goto failed;
+
+ if (!g_dbus_register_interface(conn, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE,
+ manager_methods, manager_signals,
+ NULL, NULL, manager_unregister)) {
+ error("Failed to register %s interface to %s",
+ AUDIO_MANAGER_INTERFACE, AUDIO_MANAGER_PATH);
+ goto failed;
+ }
+
+ info("Registered manager path:%s", AUDIO_MANAGER_PATH);
+
+ register_stored();
+
+ return 0;
+failed:
+ audio_manager_exit();
+ return -1;
+}
+
+void audio_manager_exit(void)
+{
+ server_exit();
+
+ g_dbus_unregister_interface(connection, AUDIO_MANAGER_PATH,
+ AUDIO_MANAGER_INTERFACE);
+
+ dbus_connection_unref(connection);
+
+ connection = NULL;
+}
+
+struct audio_device *manager_default_device(void)
+{
+ return default_dev;
+}
+
+struct audio_device *manager_get_connected_device(void)
+{
+ GSList *l;
+
+ for (l = devices; l != NULL; l = g_slist_next(l)) {
+ struct audio_device *device = l->data;
+
+ if ((device->sink || device->source) &&
+ avdtp_is_connected(&device->src, &device->dst))
+ return device;
+
+ if (device->headset && headset_is_active(device))
+ return device;
+ }
+
+ return NULL;
+}
+
+struct audio_device *manager_find_device(const bdaddr_t *bda, const char *interface,
+ gboolean connected)
+{
+ GSList *l;
+
+ if (!bacmp(bda, BDADDR_ANY) && !interface && !connected)
+ return default_dev;
+
+ for (l = devices; l != NULL; l = l->next) {
+ struct audio_device *dev = l->data;
+
+ if (bacmp(bda, BDADDR_ANY) && bacmp(&dev->dst, bda))
+ continue;
+
+ if (interface && !strcmp(AUDIO_HEADSET_INTERFACE, interface)
+ && !dev->headset)
+ continue;
+
+ if (interface && !strcmp(AUDIO_SINK_INTERFACE, interface)
+ && !dev->sink)
+ continue;
+
+ if (interface && !strcmp(AUDIO_SOURCE_INTERFACE, interface)
+ && !dev->source)
+ continue;
+
+ if (interface && !strcmp(AUDIO_CONTROL_INTERFACE, interface)
+ && !dev->control)
+ continue;
+
+ if (connected && !device_is_connected(dev, interface))
+ continue;
+
+ return dev;
+ }
+
+ return NULL;
+}
diff --git a/audio/manager.h b/audio/manager.h
new file mode 100644
index 00000000..8f7f6be3
--- /dev/null
+++ b/audio/manager.h
@@ -0,0 +1,50 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 MAX_PATH_LENGTH 64 /* D-Bus path */
+#define AUDIO_MANAGER_PATH "/org/bluez/audio"
+#define AUDIO_MANAGER_INTERFACE "org.bluez.audio.Manager"
+
+struct enabled_interfaces {
+ gboolean headset;
+ gboolean gateway;
+ gboolean sink;
+ gboolean source;
+ gboolean control;
+};
+
+typedef void (*create_dev_cb_t) (struct audio_device *dev, void *user_data);
+
+int audio_manager_init(DBusConnection *conn, GKeyFile *config);
+void audio_manager_exit(void);
+
+gboolean server_is_enabled(uint16_t svc);
+
+struct audio_device *manager_find_device(const bdaddr_t *bda, const char *interface,
+ gboolean connected);
+
+struct audio_device *manager_device_connected(const bdaddr_t *bda, const char *uuid);
+
+gboolean manager_create_device(bdaddr_t *bda, create_dev_cb_t cb,
+ void *user_data);
diff --git a/audio/module-bluetooth-sink.c b/audio/module-bluetooth-sink.c
new file mode 100644
index 00000000..f4eb355b
--- /dev/null
+++ b/audio/module-bluetooth-sink.c
@@ -0,0 +1,39 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; 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
+
+#if 0
+#include <pulsecore/module.h>
+
+PA_MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>")
+PA_MODULE_DESCRIPTION("Bluetooth sink")
+PA_MODULE_VERSION(VERSION)
+
+int pa__init(pa_core *core, pa_module *module)
+{
+ return 0;
+}
+#endif
diff --git a/audio/pcm_bluetooth.c b/audio/pcm_bluetooth.c
new file mode 100644
index 00000000..0dec0a12
--- /dev/null
+++ b/audio/pcm_bluetooth.c
@@ -0,0 +1,1665 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; 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 <stdint.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <time.h>
+#include <sys/time.h>
+#include <pthread.h>
+#include <signal.h>
+#include <limits.h>
+
+#include <netinet/in.h>
+
+#include <alsa/asoundlib.h>
+#include <alsa/pcm_external.h>
+
+#include "ipc.h"
+#include "sbc.h"
+#include "rtp.h"
+
+//#define ENABLE_DEBUG
+
+#define UINT_SECS_MAX (UINT_MAX / 1000000 - 1)
+
+#define MIN_PERIOD_TIME 1
+
+#define BUFFER_SIZE 2048
+
+#ifdef ENABLE_DEBUG
+#define DBG(fmt, arg...) printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg)
+#else
+#define DBG(fmt, arg...)
+#endif
+
+#ifndef SOL_SCO
+#define SOL_SCO 17
+#endif
+
+#ifndef SCO_TXBUFS
+#define SCO_TXBUFS 0x03
+#endif
+
+#ifndef SCO_RXBUFS
+#define SCO_RXBUFS 0x04
+#endif
+
+#ifndef MIN
+# define MIN(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
+#ifndef MAX
+# define MAX(x, y) ((x) > (y) ? (x) : (y))
+#endif
+
+#define MAX_BITPOOL 64
+#define MIN_BITPOOL 2
+
+/* adapted from glibc sys/time.h timersub() macro */
+#define priv_timespecsub(a, b, result) \
+ do { \
+ (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
+ (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \
+ if ((result)->tv_nsec < 0) { \
+ --(result)->tv_sec; \
+ (result)->tv_nsec += 1000000000; \
+ } \
+ } while (0)
+
+struct bluetooth_a2dp {
+ sbc_capabilities_t sbc_capabilities;
+ sbc_t sbc; /* Codec data */
+ int sbc_initialized; /* Keep track if the encoder is initialized */
+ int codesize; /* SBC codesize */
+ int samples; /* Number of encoded samples */
+ uint8_t buffer[BUFFER_SIZE]; /* Codec transfer buffer */
+ int count; /* Codec transfer buffer counter */
+
+ int nsamples; /* Cumulative number of codec samples */
+ uint16_t seq_num; /* Cumulative packet sequence */
+ int frame_count; /* Current frames in buffer*/
+};
+
+struct bluetooth_alsa_config {
+ char device[18]; /* Address of the remote Device */
+ int has_device;
+ uint8_t transport; /* Requested transport */
+ int has_transport;
+ uint16_t rate;
+ int has_rate;
+ uint8_t channel_mode; /* A2DP only */
+ int has_channel_mode;
+ uint8_t allocation_method; /* A2DP only */
+ int has_allocation_method;
+ uint8_t subbands; /* A2DP only */
+ int has_subbands;
+ uint8_t block_length; /* A2DP only */
+ int has_block_length;
+ uint8_t bitpool; /* A2DP only */
+ int has_bitpool;
+ int autoconnect;
+};
+
+struct bluetooth_data {
+ snd_pcm_ioplug_t io;
+ struct bluetooth_alsa_config alsa_config; /* ALSA resource file parameters */
+ volatile snd_pcm_sframes_t hw_ptr;
+ int transport; /* chosen transport SCO or AD2P */
+ int link_mtu; /* MTU for selected transport channel */
+ volatile struct pollfd stream; /* Audio stream filedescriptor */
+ struct pollfd server; /* Audio daemon filedescriptor */
+ uint8_t buffer[BUFFER_SIZE]; /* Encoded transfer buffer */
+ int count; /* Transfer buffer counter */
+ struct bluetooth_a2dp a2dp; /* A2DP data */
+
+ pthread_t hw_thread; /* Makes virtual hw pointer move */
+ int pipefd[2]; /* Inter thread communication */
+ int stopped;
+ sig_atomic_t reset; /* Request XRUN handling */
+};
+
+static int audioservice_send(int sk, const bt_audio_msg_header_t *msg);
+static int audioservice_expect(int sk, bt_audio_msg_header_t *outmsg,
+ int expected_type);
+
+static int bluetooth_start(snd_pcm_ioplug_t *io)
+{
+ DBG("bluetooth_start %p", io);
+
+ return 0;
+}
+
+static int bluetooth_stop(snd_pcm_ioplug_t *io)
+{
+ DBG("bluetooth_stop %p", io);
+
+ return 0;
+}
+
+static void *playback_hw_thread(void *param)
+{
+ struct bluetooth_data *data = param;
+ unsigned int prev_periods;
+ double period_time;
+ struct timespec start;
+ struct pollfd fds[2];
+ int poll_timeout;
+
+ data->server.events = POLLIN;
+ /* note: only errors for data->stream.events */
+
+ fds[0] = data->server;
+ fds[1] = data->stream;
+
+ prev_periods = 0;
+ period_time = 1000000.0 * data->io.period_size / data->io.rate;
+ if (period_time > (int) (MIN_PERIOD_TIME * 1000))
+ poll_timeout = (int) (period_time / 1000.0f);
+ else
+ poll_timeout = MIN_PERIOD_TIME;
+
+ clock_gettime(CLOCK_MONOTONIC, &start);
+
+ while (1) {
+ unsigned int dtime, periods;
+ struct timespec cur, delta;
+ int ret;
+
+ if (data->stopped)
+ goto iter_sleep;
+
+ if (data->reset) {
+ DBG("Handle XRUN in hw-thread.");
+ data->reset = 0;
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ prev_periods = 0;
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &cur);
+
+ priv_timespecsub(&cur, &start, &delta);
+
+ dtime = delta.tv_sec * 1000000 + delta.tv_nsec / 1000;
+ periods = 1.0 * dtime / period_time;
+
+ if (periods > prev_periods) {
+ char c = 'w';
+ int frags = periods - prev_periods, n;
+
+ data->hw_ptr += frags * data->io.period_size;
+ data->hw_ptr %= data->io.buffer_size;
+
+ for (n = 0; n < frags; n++) {
+ /* Notify user that hardware pointer
+ * has moved * */
+ if (write(data->pipefd[1], &c, 1) < 0)
+ pthread_testcancel();
+ }
+
+ /* Reset point of reference to avoid too big values
+ * that wont fit an unsigned int */
+ if (delta.tv_sec < UINT_SECS_MAX)
+ prev_periods = periods;
+ else {
+ prev_periods = 0;
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ }
+ }
+
+iter_sleep:
+ /* sleep up to one period interval */
+ ret = poll(fds, 2, poll_timeout);
+
+ if (ret < 0) {
+ SNDERR("poll error: %s (%d)", strerror(errno), errno);
+ if (errno != EINTR)
+ break;
+ } else if (ret > 0) {
+ ret = (fds[0].revents) ? 0 : 1;
+ SNDERR("poll fd %d revents %d", ret, fds[ret].revents);
+ if (fds[ret].revents & (POLLERR | POLLHUP | POLLNVAL))
+ break;
+ }
+
+ /* Offer opportunity to be canceled by main thread */
+ pthread_testcancel();
+ }
+
+ data->hw_thread = 0;
+ pthread_exit(NULL);
+}
+
+static int bluetooth_playback_start(snd_pcm_ioplug_t *io)
+{
+ struct bluetooth_data *data = io->private_data;
+ int err;
+
+ DBG("%p", io);
+
+ data->stopped = 0;
+
+ if (data->hw_thread)
+ return 0;
+
+ err = pthread_create(&data->hw_thread, 0, playback_hw_thread, data);
+
+ return -err;
+}
+
+static int bluetooth_playback_stop(snd_pcm_ioplug_t *io)
+{
+ struct bluetooth_data *data = io->private_data;
+
+ DBG("%p", io);
+
+ data->stopped = 1;
+
+ return 0;
+}
+
+static snd_pcm_sframes_t bluetooth_pointer(snd_pcm_ioplug_t *io)
+{
+ struct bluetooth_data *data = io->private_data;
+
+ return data->hw_ptr;
+}
+
+static void bluetooth_exit(struct bluetooth_data *data)
+{
+ struct bluetooth_a2dp *a2dp = &data->a2dp;
+
+ if (data->server.fd >= 0)
+ bt_audio_service_close(data->server.fd);
+
+ if (data->stream.fd >= 0)
+ close(data->stream.fd);
+
+ if (data->hw_thread) {
+ pthread_cancel(data->hw_thread);
+ pthread_join(data->hw_thread, 0);
+ }
+
+ if (a2dp->sbc_initialized)
+ sbc_finish(&a2dp->sbc);
+
+ if (data->pipefd[0] > 0)
+ close(data->pipefd[0]);
+
+ if (data->pipefd[1] > 0)
+ close(data->pipefd[1]);
+
+ free(data);
+}
+
+static int bluetooth_close(snd_pcm_ioplug_t *io)
+{
+ struct bluetooth_data *data = io->private_data;
+
+ DBG("%p", io);
+
+ bluetooth_exit(data);
+
+ return 0;
+}
+
+static int bluetooth_prepare(snd_pcm_ioplug_t *io)
+{
+ struct bluetooth_data *data = io->private_data;
+ char c = 'w';
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_streamstart_req *start_req = (void*) buf;
+ bt_audio_rsp_msg_header_t *rsp_hdr = (void*) buf;
+ struct bt_streamfd_ind *streamfd_ind = (void*) buf;
+ uint32_t period_count = io->buffer_size / io->period_size;
+ int opt_name, err;
+ struct timeval t = { 0, period_count };
+
+ DBG("Preparing with io->period_size=%lu io->buffer_size=%lu",
+ io->period_size, io->buffer_size);
+
+ data->reset = 0;
+
+ /* As we're gonna receive messages on the server socket, we have to stop the
+ hw thread that is polling on it, if any */
+ if (data->hw_thread) {
+ pthread_cancel(data->hw_thread);
+ pthread_join(data->hw_thread, 0);
+ data->hw_thread = 0;
+ }
+
+ if (io->stream == SND_PCM_STREAM_PLAYBACK)
+ /* If not null for playback, xmms doesn't display time
+ * correctly */
+ data->hw_ptr = 0;
+ else
+ /* ALSA library is really picky on the fact hw_ptr is not null.
+ * If it is, capture won't start */
+ data->hw_ptr = io->period_size;
+
+ /* send start */
+ memset(start_req, 0, BT_AUDIO_IPC_PACKET_SIZE);
+ start_req->h.msg_type = BT_STREAMSTART_REQ;
+
+ err = audioservice_send(data->server.fd, &start_req->h);
+ if (err < 0)
+ return err;
+
+ err = audioservice_expect(data->server.fd, &rsp_hdr->msg_h,
+ BT_STREAMSTART_RSP);
+ if (err < 0)
+ return err;
+
+ if (rsp_hdr->posix_errno != 0) {
+ SNDERR("BT_START failed : %s(%d)",
+ strerror(rsp_hdr->posix_errno),
+ rsp_hdr->posix_errno);
+ return -rsp_hdr->posix_errno;
+ }
+
+ err = audioservice_expect(data->server.fd, &streamfd_ind->h,
+ BT_STREAMFD_IND);
+ if (err < 0)
+ return err;
+
+ if (data->stream.fd >= 0)
+ close(data->stream.fd);
+
+ data->stream.fd = bt_audio_service_get_data_fd(data->server.fd);
+ if (data->stream.fd < 0) {
+ return -errno;
+ }
+
+ if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
+ opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ?
+ SO_SNDTIMEO : SO_RCVTIMEO;
+
+ if (setsockopt(data->stream.fd, SOL_SOCKET, opt_name, &t,
+ sizeof(t)) < 0)
+ return -errno;
+ } else {
+ opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ?
+ SCO_TXBUFS : SCO_RXBUFS;
+
+ if (setsockopt(data->stream.fd, SOL_SCO, opt_name, &period_count,
+ sizeof(period_count)) == 0)
+ return 0;
+
+ opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ?
+ SO_SNDBUF : SO_RCVBUF;
+
+ if (setsockopt(data->stream.fd, SOL_SCO, opt_name, &period_count,
+ sizeof(period_count)) == 0)
+ return 0;
+
+ /* FIXME : handle error codes */
+ }
+
+ /* wake up any client polling at us */
+ err = write(data->pipefd[1], &c, 1);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int bluetooth_hsp_hw_params(snd_pcm_ioplug_t *io,
+ snd_pcm_hw_params_t *params)
+{
+ struct bluetooth_data *data = io->private_data;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ bt_audio_rsp_msg_header_t *rsp_hdr = (void*) buf;
+ struct bt_setconfiguration_req *setconf_req = (void*) buf;
+ struct bt_setconfiguration_rsp *setconf_rsp = (void*) buf;
+ int err;
+
+ DBG("Preparing with io->period_size=%lu io->buffer_size=%lu",
+ io->period_size, io->buffer_size);
+
+ memset(setconf_req, 0, BT_AUDIO_IPC_PACKET_SIZE);
+ setconf_req->h.msg_type = BT_SETCONFIGURATION_REQ;
+ strncpy(setconf_req->device, data->alsa_config.device, 18);
+ setconf_req->transport = BT_CAPABILITIES_TRANSPORT_SCO;
+ setconf_req->access_mode = (io->stream == SND_PCM_STREAM_PLAYBACK ?
+ BT_CAPABILITIES_ACCESS_MODE_WRITE :
+ BT_CAPABILITIES_ACCESS_MODE_READ);
+
+ err = audioservice_send(data->server.fd, &setconf_req->h);
+ if (err < 0)
+ return err;
+
+ err = audioservice_expect(data->server.fd, &rsp_hdr->msg_h,
+ BT_SETCONFIGURATION_RSP);
+ if (err < 0)
+ return err;
+
+ if (rsp_hdr->posix_errno != 0) {
+ SNDERR("BT_SETCONFIGURATION failed : %s(%d)",
+ strerror(rsp_hdr->posix_errno),
+ rsp_hdr->posix_errno);
+ return -rsp_hdr->posix_errno;
+ }
+
+ data->transport = setconf_rsp->transport;
+ data->link_mtu = setconf_rsp->link_mtu;
+
+ return 0;
+}
+
+static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+{
+ switch (freq) {
+ case BT_SBC_SAMPLING_FREQ_16000:
+ case BT_SBC_SAMPLING_FREQ_32000:
+ return 53;
+ case BT_SBC_SAMPLING_FREQ_44100:
+ switch (mode) {
+ case BT_A2DP_CHANNEL_MODE_MONO:
+ case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL:
+ return 31;
+ case BT_A2DP_CHANNEL_MODE_STEREO:
+ case BT_A2DP_CHANNEL_MODE_JOINT_STEREO:
+ return 53;
+ default:
+ DBG("Invalid channel mode %u", mode);
+ return 53;
+ }
+ case BT_SBC_SAMPLING_FREQ_48000:
+ switch (mode) {
+ case BT_A2DP_CHANNEL_MODE_MONO:
+ case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL:
+ return 29;
+ case BT_A2DP_CHANNEL_MODE_STEREO:
+ case BT_A2DP_CHANNEL_MODE_JOINT_STEREO:
+ return 51;
+ default:
+ DBG("Invalid channel mode %u", mode);
+ return 51;
+ }
+ default:
+ DBG("Invalid sampling freq %u", freq);
+ return 53;
+ }
+}
+
+static int bluetooth_a2dp_init(struct bluetooth_data *data,
+ snd_pcm_hw_params_t *params)
+{
+ struct bluetooth_alsa_config *cfg = &data->alsa_config;
+ sbc_capabilities_t *cap = &data->a2dp.sbc_capabilities;
+ unsigned int max_bitpool, min_bitpool, rate, channels;
+ int dir;
+
+ snd_pcm_hw_params_get_rate(params, &rate, &dir);
+ snd_pcm_hw_params_get_channels(params, &channels);
+
+ switch (rate) {
+ case 48000:
+ cap->frequency = BT_SBC_SAMPLING_FREQ_48000;
+ break;
+ case 44100:
+ cap->frequency = BT_SBC_SAMPLING_FREQ_44100;
+ break;
+ case 32000:
+ cap->frequency = BT_SBC_SAMPLING_FREQ_32000;
+ break;
+ case 16000:
+ cap->frequency = BT_SBC_SAMPLING_FREQ_16000;
+ break;
+ default:
+ DBG("Rate %d not supported", rate);
+ return -1;
+ }
+
+ if (cfg->has_channel_mode)
+ cap->channel_mode = cfg->channel_mode;
+ else if (channels == 2) {
+ if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO)
+ cap->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+ else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO)
+ cap->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
+ else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL)
+ cap->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
+ } else {
+ if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+ cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
+ }
+
+ if (!cap->channel_mode) {
+ DBG("No supported channel modes");
+ return -1;
+ }
+
+ if (cfg->has_block_length)
+ cap->block_length = cfg->block_length;
+ else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16)
+ cap->block_length = BT_A2DP_BLOCK_LENGTH_16;
+ else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12)
+ cap->block_length = BT_A2DP_BLOCK_LENGTH_12;
+ else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8)
+ cap->block_length = BT_A2DP_BLOCK_LENGTH_8;
+ else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4)
+ cap->block_length = BT_A2DP_BLOCK_LENGTH_4;
+ else {
+ DBG("No supported block lengths");
+ return -1;
+ }
+
+ if (cfg->has_subbands)
+ cap->subbands = cfg->subbands;
+ if (cap->subbands & BT_A2DP_SUBBANDS_8)
+ cap->subbands = BT_A2DP_SUBBANDS_8;
+ else if (cap->subbands & BT_A2DP_SUBBANDS_4)
+ cap->subbands = BT_A2DP_SUBBANDS_4;
+ else {
+ DBG("No supported subbands");
+ return -1;
+ }
+
+ if (cfg->has_allocation_method)
+ cap->allocation_method = cfg->allocation_method;
+ if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS)
+ cap->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS;
+ else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR)
+ cap->allocation_method = BT_A2DP_ALLOCATION_SNR;
+
+ if (cfg->has_bitpool)
+ min_bitpool = max_bitpool = cfg->bitpool;
+ else {
+ min_bitpool = MAX(MIN_BITPOOL, cap->min_bitpool);
+ max_bitpool = MIN(default_bitpool(cap->frequency,
+ cap->channel_mode),
+ cap->max_bitpool);
+ }
+
+ cap->min_bitpool = min_bitpool;
+ cap->max_bitpool = max_bitpool;
+
+ return 0;
+}
+
+static void bluetooth_a2dp_setup(struct bluetooth_a2dp *a2dp)
+{
+ sbc_capabilities_t active_capabilities = a2dp->sbc_capabilities;
+
+ if (a2dp->sbc_initialized)
+ sbc_reinit(&a2dp->sbc, 0);
+ else
+ sbc_init(&a2dp->sbc, 0);
+ a2dp->sbc_initialized = 1;
+
+ if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_16000)
+ a2dp->sbc.frequency = SBC_FREQ_16000;
+
+ if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_32000)
+ a2dp->sbc.frequency = SBC_FREQ_32000;
+
+ if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_44100)
+ a2dp->sbc.frequency = SBC_FREQ_44100;
+
+ if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_48000)
+ a2dp->sbc.frequency = SBC_FREQ_48000;
+
+ if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+ a2dp->sbc.mode = SBC_MODE_MONO;
+
+ if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL)
+ a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL;
+
+ if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_STEREO)
+ a2dp->sbc.mode = SBC_MODE_STEREO;
+
+ if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO)
+ a2dp->sbc.mode = SBC_MODE_JOINT_STEREO;
+
+ a2dp->sbc.allocation = active_capabilities.allocation_method
+ == BT_A2DP_ALLOCATION_SNR ? SBC_AM_SNR
+ : SBC_AM_LOUDNESS;
+
+ switch (active_capabilities.subbands) {
+ case BT_A2DP_SUBBANDS_4:
+ a2dp->sbc.subbands = SBC_SB_4;
+ break;
+ case BT_A2DP_SUBBANDS_8:
+ a2dp->sbc.subbands = SBC_SB_8;
+ break;
+ }
+
+ switch (active_capabilities.block_length) {
+ case BT_A2DP_BLOCK_LENGTH_4:
+ a2dp->sbc.blocks = SBC_BLK_4;
+ break;
+ case BT_A2DP_BLOCK_LENGTH_8:
+ a2dp->sbc.blocks = SBC_BLK_8;
+ break;
+ case BT_A2DP_BLOCK_LENGTH_12:
+ a2dp->sbc.blocks = SBC_BLK_12;
+ break;
+ case BT_A2DP_BLOCK_LENGTH_16:
+ a2dp->sbc.blocks = SBC_BLK_16;
+ break;
+ }
+
+ a2dp->sbc.bitpool = active_capabilities.max_bitpool;
+ a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
+ a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload);
+}
+
+static int bluetooth_a2dp_hw_params(snd_pcm_ioplug_t *io,
+ snd_pcm_hw_params_t *params)
+{
+ struct bluetooth_data *data = io->private_data;
+ struct bluetooth_a2dp *a2dp = &data->a2dp;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ bt_audio_rsp_msg_header_t *rsp_hdr = (void*) buf;
+ struct bt_setconfiguration_req *setconf_req = (void*) buf;
+ struct bt_setconfiguration_rsp *setconf_rsp = (void*) buf;
+ int err;
+
+ DBG("Preparing with io->period_size=%lu io->buffer_size=%lu",
+ io->period_size, io->buffer_size);
+
+ err = bluetooth_a2dp_init(data, params);
+ if (err < 0)
+ return err;
+
+ memset(setconf_req, 0, BT_AUDIO_IPC_PACKET_SIZE);
+ setconf_req->h.msg_type = BT_SETCONFIGURATION_REQ;
+ strncpy(setconf_req->device, data->alsa_config.device, 18);
+ setconf_req->transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+ setconf_req->sbc_capabilities = a2dp->sbc_capabilities;
+ setconf_req->access_mode = (io->stream == SND_PCM_STREAM_PLAYBACK ?
+ BT_CAPABILITIES_ACCESS_MODE_WRITE :
+ BT_CAPABILITIES_ACCESS_MODE_READ);
+
+ err = audioservice_send(data->server.fd, &setconf_req->h);
+ if (err < 0)
+ return err;
+
+ err = audioservice_expect(data->server.fd, &rsp_hdr->msg_h,
+ BT_SETCONFIGURATION_RSP);
+ if (err < 0)
+ return err;
+
+ if (rsp_hdr->posix_errno != 0) {
+ SNDERR("BT_SETCONFIGURATION failed : %s(%d)",
+ strerror(rsp_hdr->posix_errno),
+ rsp_hdr->posix_errno);
+ return -rsp_hdr->posix_errno;
+ }
+
+ data->transport = setconf_rsp->transport;
+ data->link_mtu = setconf_rsp->link_mtu;
+
+ /* Setup SBC encoder now we agree on parameters */
+ bluetooth_a2dp_setup(a2dp);
+
+ DBG("\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",
+ a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks,
+ a2dp->sbc.bitpool);
+
+ return 0;
+}
+
+static int bluetooth_poll_descriptors(snd_pcm_ioplug_t *io,
+ struct pollfd *pfd, unsigned int space)
+{
+ struct bluetooth_data *data = io->private_data;
+
+ assert(io);
+
+ if (space < 1)
+ return 0;
+
+ pfd[0].fd = data->stream.fd;
+ pfd[0].events = POLLIN;
+ pfd[0].revents = 0;
+
+ return 1;
+}
+
+static int bluetooth_poll_revents(snd_pcm_ioplug_t *io ATTRIBUTE_UNUSED,
+ struct pollfd *pfds, unsigned int nfds,
+ unsigned short *revents)
+{
+ assert(pfds && nfds == 1 && revents);
+
+ *revents = pfds[0].revents;
+
+ return 0;
+}
+
+static int bluetooth_playback_poll_descriptors_count(snd_pcm_ioplug_t *io)
+{
+ return 2;
+}
+
+static int bluetooth_playback_poll_descriptors(snd_pcm_ioplug_t *io,
+ struct pollfd *pfd, unsigned int space)
+{
+ struct bluetooth_data *data = io->private_data;
+
+ DBG("");
+
+ assert(data->pipefd[0] >= 0);
+
+ if (space < 2)
+ return 0;
+
+ pfd[0].fd = data->pipefd[0];
+ pfd[0].events = POLLIN;
+ pfd[0].revents = 0;
+ pfd[1].fd = data->stream.fd;
+ pfd[1].events = POLLERR | POLLHUP | POLLNVAL;
+ pfd[1].revents = 0;
+
+ return 2;
+}
+
+static int bluetooth_playback_poll_revents(snd_pcm_ioplug_t *io,
+ struct pollfd *pfds, unsigned int nfds,
+ unsigned short *revents)
+{
+ static char buf[1];
+ int ret;
+
+ DBG("");
+
+ assert(pfds);
+ assert(nfds == 2);
+ assert(revents);
+ assert(pfds[0].fd >= 0);
+ assert(pfds[1].fd >= 0);
+
+ if (io->state != SND_PCM_STATE_PREPARED)
+ ret = read(pfds[0].fd, buf, 1);
+
+ if (pfds[1].revents & (POLLERR | POLLHUP | POLLNVAL))
+ io->state = SND_PCM_STATE_DISCONNECTED;
+
+ revents[0] = (pfds[0].revents & ~POLLIN) | POLLOUT;
+ revents[1] = (pfds[1].revents & ~POLLIN);
+
+ return 0;
+}
+
+
+static snd_pcm_sframes_t bluetooth_hsp_read(snd_pcm_ioplug_t *io,
+ const snd_pcm_channel_area_t *areas,
+ snd_pcm_uframes_t offset,
+ snd_pcm_uframes_t size)
+{
+ struct bluetooth_data *data = io->private_data;
+ snd_pcm_uframes_t frames_to_write, ret;
+ unsigned char *buff;
+ int nrecv, frame_size = 0;
+
+ DBG("areas->step=%u areas->first=%u offset=%lu size=%lu io->nonblock=%u",
+ areas->step, areas->first, offset, size, io->nonblock);
+
+ frame_size = areas->step / 8;
+
+ if (data->count > 0)
+ goto proceed;
+
+ nrecv = recv(data->stream.fd, data->buffer, data->link_mtu,
+ MSG_WAITALL | (io->nonblock ? MSG_DONTWAIT : 0));
+
+ if (nrecv < 0) {
+ ret = (errno == EPIPE) ? -EIO : -errno;
+ goto done;
+ }
+
+ if (nrecv != data->link_mtu) {
+ ret = -EIO;
+ SNDERR(strerror(-ret));
+ goto done;
+ }
+
+ /* Increment hardware transmition pointer */
+ data->hw_ptr = (data->hw_ptr + data->link_mtu / frame_size) %
+ io->buffer_size;
+
+proceed:
+ buff = (unsigned char *) areas->addr +
+ (areas->first + areas->step * offset) / 8;
+
+ if ((data->count + size * frame_size) <= data->link_mtu)
+ frames_to_write = size;
+ else
+ frames_to_write = (data->link_mtu - data->count) / frame_size;
+
+ memcpy(buff, data->buffer + data->count, frame_size * frames_to_write);
+ data->count += (frame_size * frames_to_write);
+ data->count %= data->link_mtu;
+
+ /* Return written frames count */
+ ret = frames_to_write;
+
+done:
+ DBG("returning %lu", ret);
+ return ret;
+}
+
+static snd_pcm_sframes_t bluetooth_hsp_write(snd_pcm_ioplug_t *io,
+ const snd_pcm_channel_area_t *areas,
+ snd_pcm_uframes_t offset,
+ snd_pcm_uframes_t size)
+{
+ struct bluetooth_data *data = io->private_data;
+ snd_pcm_sframes_t ret = 0;
+ snd_pcm_uframes_t frames_to_read;
+ uint8_t *buff;
+ int rsend, frame_size;
+
+ DBG("areas->step=%u areas->first=%u offset=%lu, size=%lu io->nonblock=%u",
+ areas->step, areas->first, offset, size, io->nonblock);
+
+ if (io->hw_ptr > io->appl_ptr) {
+ ret = bluetooth_playback_stop(io);
+ if (ret == 0)
+ ret = -EPIPE;
+ goto done;
+ }
+
+ frame_size = areas->step / 8;
+ if ((data->count + size * frame_size) <= data->link_mtu)
+ frames_to_read = size;
+ else
+ frames_to_read = (data->link_mtu - data->count) / frame_size;
+
+ DBG("count=%d frames_to_read=%lu", data->count, frames_to_read);
+
+ /* Ready for more data */
+ buff = (uint8_t *) areas->addr +
+ (areas->first + areas->step * offset) / 8;
+ memcpy(data->buffer + data->count, buff, frame_size * frames_to_read);
+
+ /* Remember we have some frames in the pipe now */
+ data->count += frames_to_read * frame_size;
+ if (data->count != data->link_mtu) {
+ ret = frames_to_read;
+ goto done;
+ }
+
+ rsend = send(data->stream.fd, data->buffer, data->link_mtu,
+ io->nonblock ? MSG_DONTWAIT : 0);
+ if (rsend > 0) {
+ /* Reset count pointer */
+ data->count = 0;
+
+ ret = frames_to_read;
+ } else if (rsend < 0)
+ ret = (errno == EPIPE) ? -EIO : -errno;
+ else
+ ret = -EIO;
+
+done:
+ DBG("returning %ld", ret);
+ return ret;
+}
+
+static snd_pcm_sframes_t bluetooth_a2dp_read(snd_pcm_ioplug_t *io,
+ const snd_pcm_channel_area_t *areas,
+ snd_pcm_uframes_t offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_uframes_t ret = 0;
+ return ret;
+}
+
+static int avdtp_write(struct bluetooth_data *data)
+{
+ int ret = 0;
+ struct rtp_header *header;
+ struct rtp_payload *payload;
+ struct bluetooth_a2dp *a2dp = &data->a2dp;
+
+ header = (void *) a2dp->buffer;
+ payload = (void *) (a2dp->buffer + sizeof(*header));
+
+ memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload));
+
+ payload->frame_count = a2dp->frame_count;
+ header->v = 2;
+ header->pt = 1;
+ header->sequence_number = htons(a2dp->seq_num);
+ header->timestamp = htonl(a2dp->nsamples);
+ header->ssrc = htonl(1);
+
+ ret = send(data->stream.fd, a2dp->buffer, a2dp->count, MSG_DONTWAIT);
+ if (ret < 0) {
+ DBG("send returned %d errno %s.", ret, strerror(errno));
+ ret = -errno;
+ }
+
+ /* Reset buffer of data to send */
+ a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload);
+ a2dp->frame_count = 0;
+ a2dp->samples = 0;
+ a2dp->seq_num++;
+
+ return ret;
+}
+
+static snd_pcm_sframes_t bluetooth_a2dp_write(snd_pcm_ioplug_t *io,
+ const snd_pcm_channel_area_t *areas,
+ snd_pcm_uframes_t offset, snd_pcm_uframes_t size)
+{
+ struct bluetooth_data *data = io->private_data;
+ struct bluetooth_a2dp *a2dp = &data->a2dp;
+ snd_pcm_sframes_t ret = 0;
+ snd_pcm_uframes_t frames_to_read, frames_left = size;
+ int frame_size, encoded, written;
+ uint8_t *buff;
+
+ DBG("areas->step=%u areas->first=%u offset=%lu size=%lu",
+ areas->step, areas->first, offset, size);
+ DBG("hw_ptr=%lu appl_ptr=%lu diff=%lu", io->hw_ptr, io->appl_ptr,
+ io->appl_ptr - io->hw_ptr);
+
+ if (io->hw_ptr > io->appl_ptr) {
+ ret = bluetooth_playback_stop(io);
+ if (ret == 0)
+ ret = -EPIPE;
+ data->reset = 1;
+ goto done;
+ }
+
+ /* Check if we should autostart */
+ if (io->state == SND_PCM_STATE_PREPARED) {
+ snd_pcm_sw_params_t *swparams;
+ snd_pcm_uframes_t threshold;
+
+ snd_pcm_sw_params_malloc(&swparams);
+ if (!snd_pcm_sw_params_current(io->pcm, swparams) &&
+ !snd_pcm_sw_params_get_start_threshold(swparams,
+ &threshold)) {
+ if (io->appl_ptr >= threshold) {
+ ret = snd_pcm_start(io->pcm);
+ if (ret != 0)
+ goto done;
+ }
+ }
+
+ snd_pcm_sw_params_free(swparams);
+ }
+
+ while (frames_left > 0) {
+ frame_size = areas->step / 8;
+
+ if ((data->count + frames_left * frame_size) <= a2dp->codesize)
+ frames_to_read = frames_left;
+ else
+ frames_to_read = (a2dp->codesize - data->count) / frame_size;
+
+ DBG("count=%d frames_to_read=%lu", data->count, frames_to_read);
+ DBG("a2dp.count=%d data.link_mtu=%d", a2dp->count, data->link_mtu);
+
+ /* FIXME: If state is not streaming then return */
+
+ /* Ready for more data */
+ buff = (uint8_t *) areas->addr +
+ (areas->first + areas->step * (offset + ret)) / 8;
+ memcpy(data->buffer + data->count, buff,
+ frame_size * frames_to_read);
+
+ /* Remember we have some frames in the pipe now */
+ data->count += frames_to_read * frame_size;
+ if (data->count != a2dp->codesize) {
+ ret = frames_to_read;
+ goto done;
+ }
+
+ /* Enough data to encode (sbc wants 1k blocks) */
+ encoded = sbc_encode(&(a2dp->sbc), data->buffer, a2dp->codesize,
+ a2dp->buffer + a2dp->count,
+ sizeof(a2dp->buffer) - a2dp->count,
+ &written);
+ if (encoded <= 0) {
+ DBG("Encoding error %d", encoded);
+ goto done;
+ }
+
+ data->count -= encoded;
+ a2dp->count += written;
+ a2dp->frame_count++;
+ a2dp->samples += encoded / frame_size;
+ a2dp->nsamples += encoded / frame_size;
+
+ DBG("encoded=%d written=%d count=%d", encoded,
+ written, a2dp->count);
+
+ /* No space left for another frame then send */
+ if (a2dp->count + written >= data->link_mtu) {
+ avdtp_write(data);
+ DBG("sending packet %d, count %d, link_mtu %u",
+ a2dp->seq_num, a2dp->count,
+ data->link_mtu);
+ }
+
+ ret += frames_to_read;
+ frames_left -= frames_to_read;
+ }
+
+ /* note: some ALSA apps will get confused otherwise */
+ if (ret > size)
+ ret = size;
+
+done:
+ DBG("returning %ld", ret);
+ return ret;
+}
+
+static int bluetooth_playback_delay(snd_pcm_ioplug_t *io,
+ snd_pcm_sframes_t *delayp)
+{
+ DBG("");
+
+ /* This updates io->hw_ptr value using pointer() function */
+ snd_pcm_hwsync(io->pcm);
+
+ *delayp = io->appl_ptr - io->hw_ptr;
+ if ((io->state == SND_PCM_STATE_RUNNING) && (*delayp < 0)) {
+ io->callback->stop(io);
+ io->state = SND_PCM_STATE_XRUN;
+ *delayp = 0;
+ }
+
+ /* This should never fail, ALSA API is really not
+ prepared to handle a non zero return value */
+ return 0;
+}
+
+static snd_pcm_ioplug_callback_t bluetooth_hsp_playback = {
+ .start = bluetooth_playback_start,
+ .stop = bluetooth_playback_stop,
+ .pointer = bluetooth_pointer,
+ .close = bluetooth_close,
+ .hw_params = bluetooth_hsp_hw_params,
+ .prepare = bluetooth_prepare,
+ .transfer = bluetooth_hsp_write,
+ .poll_descriptors_count = bluetooth_playback_poll_descriptors_count,
+ .poll_descriptors = bluetooth_playback_poll_descriptors,
+ .poll_revents = bluetooth_playback_poll_revents,
+ .delay = bluetooth_playback_delay,
+};
+
+static snd_pcm_ioplug_callback_t bluetooth_hsp_capture = {
+ .start = bluetooth_start,
+ .stop = bluetooth_stop,
+ .pointer = bluetooth_pointer,
+ .close = bluetooth_close,
+ .hw_params = bluetooth_hsp_hw_params,
+ .prepare = bluetooth_prepare,
+ .transfer = bluetooth_hsp_read,
+ .poll_descriptors = bluetooth_poll_descriptors,
+ .poll_revents = bluetooth_poll_revents,
+};
+
+static snd_pcm_ioplug_callback_t bluetooth_a2dp_playback = {
+ .start = bluetooth_playback_start,
+ .stop = bluetooth_playback_stop,
+ .pointer = bluetooth_pointer,
+ .close = bluetooth_close,
+ .hw_params = bluetooth_a2dp_hw_params,
+ .prepare = bluetooth_prepare,
+ .transfer = bluetooth_a2dp_write,
+ .poll_descriptors_count = bluetooth_playback_poll_descriptors_count,
+ .poll_descriptors = bluetooth_playback_poll_descriptors,
+ .poll_revents = bluetooth_playback_poll_revents,
+ .delay = bluetooth_playback_delay,
+};
+
+static snd_pcm_ioplug_callback_t bluetooth_a2dp_capture = {
+ .start = bluetooth_start,
+ .stop = bluetooth_stop,
+ .pointer = bluetooth_pointer,
+ .close = bluetooth_close,
+ .hw_params = bluetooth_a2dp_hw_params,
+ .prepare = bluetooth_prepare,
+ .transfer = bluetooth_a2dp_read,
+ .poll_descriptors = bluetooth_poll_descriptors,
+ .poll_revents = bluetooth_poll_revents,
+};
+
+#define ARRAY_NELEMS(a) (sizeof((a)) / sizeof((a)[0]))
+
+static int bluetooth_hsp_hw_constraint(snd_pcm_ioplug_t *io)
+{
+ struct bluetooth_data *data = io->private_data;
+ snd_pcm_access_t access_list[] = {
+ SND_PCM_ACCESS_RW_INTERLEAVED,
+ /* Mmap access is really useless fo this driver, but we
+ * support it because some pieces of software out there
+ * insist on using it */
+ SND_PCM_ACCESS_MMAP_INTERLEAVED
+ };
+ unsigned int format_list[] = {
+ SND_PCM_FORMAT_S16_LE
+ };
+ int err;
+
+ /* access type */
+ err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
+ ARRAY_NELEMS(access_list), access_list);
+ if (err < 0)
+ return err;
+
+ /* supported formats */
+ err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
+ ARRAY_NELEMS(format_list), format_list);
+ if (err < 0)
+ return err;
+
+ /* supported channels */
+ err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
+ 1, 1);
+ if (err < 0)
+ return err;
+
+ /* supported rate */
+ err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE,
+ 8000, 8000);
+ if (err < 0)
+ return err;
+
+ /* supported block size */
+ err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES,
+ data->link_mtu, data->link_mtu);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS,
+ 2, 200);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int bluetooth_a2dp_hw_constraint(snd_pcm_ioplug_t *io)
+{
+ struct bluetooth_data *data = io->private_data;
+ struct bluetooth_a2dp *a2dp = &data->a2dp;
+ struct bluetooth_alsa_config *cfg = &data->alsa_config;
+ snd_pcm_access_t access_list[] = {
+ SND_PCM_ACCESS_RW_INTERLEAVED,
+ /* Mmap access is really useless fo this driver, but we
+ * support it because some pieces of software out there
+ * insist on using it */
+ SND_PCM_ACCESS_MMAP_INTERLEAVED
+ };
+ unsigned int format_list[] = {
+ SND_PCM_FORMAT_S16_LE
+ };
+ unsigned int rate_list[4];
+ unsigned int rate_count;
+ int err, min_channels, max_channels;
+ unsigned int period_list[] = {
+ 2048,
+ 4096, /* e.g. 23.2msec/period (stereo 16bit at 44.1kHz) */
+ 8192
+ };
+
+ /* access type */
+ err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
+ ARRAY_NELEMS(access_list), access_list);
+ if (err < 0)
+ return err;
+
+ /* supported formats */
+ err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
+ ARRAY_NELEMS(format_list), format_list);
+ if (err < 0)
+ return err;
+
+ /* supported channels */
+ if (cfg->has_channel_mode)
+ a2dp->sbc_capabilities.channel_mode = cfg->channel_mode;
+
+ if (a2dp->sbc_capabilities.channel_mode &
+ BT_A2DP_CHANNEL_MODE_MONO)
+ min_channels = 1;
+ else
+ min_channels = 2;
+
+ if (a2dp->sbc_capabilities.channel_mode &
+ (~BT_A2DP_CHANNEL_MODE_MONO))
+ max_channels = 2;
+ else
+ max_channels = 1;
+
+ err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
+ min_channels, max_channels);
+ if (err < 0)
+ return err;
+
+ /* supported buffer sizes
+ * (can be used as 3*8192, 6*4096, 12*2048, ...) */
+ err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_BUFFER_BYTES,
+ 8192*3, 8192*3);
+ if (err < 0)
+ return err;
+
+ /* supported block sizes: */
+ err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES,
+ ARRAY_NELEMS(period_list), period_list);
+ if (err < 0)
+ return err;
+
+ /* supported rates */
+ rate_count = 0;
+ if (cfg->has_rate) {
+ rate_list[rate_count] = cfg->rate;
+ rate_count++;
+ } else {
+ if (a2dp->sbc_capabilities.frequency &
+ BT_SBC_SAMPLING_FREQ_16000) {
+ rate_list[rate_count] = 16000;
+ rate_count++;
+ }
+
+ if (a2dp->sbc_capabilities.frequency &
+ BT_SBC_SAMPLING_FREQ_32000) {
+ rate_list[rate_count] = 32000;
+ rate_count++;
+ }
+
+ if (a2dp->sbc_capabilities.frequency &
+ BT_SBC_SAMPLING_FREQ_44100) {
+ rate_list[rate_count] = 44100;
+ rate_count++;
+ }
+
+ if (a2dp->sbc_capabilities.frequency &
+ BT_SBC_SAMPLING_FREQ_48000) {
+ rate_list[rate_count] = 48000;
+ rate_count++;
+ }
+ }
+
+ err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_RATE,
+ rate_count, rate_list);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int bluetooth_parse_config(snd_config_t *conf,
+ struct bluetooth_alsa_config *bt_config)
+{
+ snd_config_iterator_t i, next;
+
+ memset(bt_config, 0, sizeof(struct bluetooth_alsa_config));
+
+ /* Set defaults */
+ bt_config->autoconnect = 1;
+
+ snd_config_for_each(i, next, conf) {
+ snd_config_t *n = snd_config_iterator_entry(i);
+ const char *id, *value;
+
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+
+ if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
+ continue;
+
+ if (strcmp(id, "autoconnect") == 0) {
+ int b;
+
+ b = snd_config_get_bool(n);
+ if (b < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+
+ bt_config->autoconnect = b;
+ continue;
+ }
+
+ if (strcmp(id, "device") == 0 || strcmp(id, "bdaddr") == 0) {
+ if (snd_config_get_string(n, &value) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+
+ bt_config->has_device = 1;
+ strncpy(bt_config->device, value, 18);
+ continue;
+ }
+
+ if (strcmp(id, "profile") == 0) {
+ if (snd_config_get_string(n, &value) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+
+ if (strcmp(value, "auto") == 0) {
+ bt_config->transport = BT_CAPABILITIES_TRANSPORT_ANY;
+ bt_config->has_transport = 1;
+ } else if (strcmp(value, "voice") == 0 ||
+ strcmp(value, "hfp") == 0) {
+ bt_config->transport = BT_CAPABILITIES_TRANSPORT_SCO;
+ bt_config->has_transport = 1;
+ } else if (strcmp(value, "hifi") == 0 ||
+ strcmp(value, "a2dp") == 0) {
+ bt_config->transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+ bt_config->has_transport = 1;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "rate") == 0) {
+ if (snd_config_get_string(n, &value) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+
+ bt_config->rate = atoi(value);
+ bt_config->has_rate = 1;
+ continue;
+ }
+
+ if (strcmp(id, "mode") == 0) {
+ if (snd_config_get_string(n, &value) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+
+ if (strcmp(value, "mono") == 0) {
+ bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
+ bt_config->has_channel_mode = 1;
+ } else if (strcmp(value, "dual") == 0) {
+ bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
+ bt_config->has_channel_mode = 1;
+ } else if (strcmp(value, "stereo") == 0) {
+ bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
+ bt_config->has_channel_mode = 1;
+ } else if (strcmp(value, "joint") == 0) {
+ bt_config->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+ bt_config->has_channel_mode = 1;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "allocation") == 0) {
+ if (snd_config_get_string(n, &value) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+
+ if (strcmp(value, "loudness") == 0) {
+ bt_config->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS;
+ bt_config->has_allocation_method = 1;
+ } else if (strcmp(value, "snr") == 0) {
+ bt_config->allocation_method = BT_A2DP_ALLOCATION_SNR;
+ bt_config->has_allocation_method = 1;
+ }
+ continue;
+ }
+
+ if (strcmp(id, "subbands") == 0) {
+ if (snd_config_get_string(n, &value) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+
+ bt_config->subbands = atoi(value);
+ bt_config->has_subbands = 1;
+ continue;
+ }
+
+ if (strcmp(id, "blocks") == 0) {
+ if (snd_config_get_string(n, &value) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+
+ bt_config->block_length = atoi(value);
+ bt_config->has_block_length = 1;
+ continue;
+ }
+
+ if (strcmp(id, "bitpool") == 0) {
+ if (snd_config_get_string(n, &value) < 0) {
+ SNDERR("Invalid type for %s", id);
+ return -EINVAL;
+ }
+
+ bt_config->bitpool = atoi(value);
+ bt_config->has_bitpool = 1;
+ continue;
+ }
+
+ SNDERR("Unknown field %s", id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int audioservice_send(int sk, const bt_audio_msg_header_t *msg)
+{
+ int err;
+
+ DBG("sending %s", bt_audio_strmsg(msg->msg_type));
+ if (send(sk, msg, BT_AUDIO_IPC_PACKET_SIZE, 0) > 0)
+ err = 0;
+ else {
+ err = -errno;
+ SNDERR("Error sending data to audio service: %s(%d)",
+ strerror(errno), errno);
+ }
+
+ return err;
+}
+
+static int audioservice_recv(int sk, bt_audio_msg_header_t *inmsg)
+{
+ int err;
+ const char *type;
+
+ DBG("trying to receive msg from audio service...");
+ if (recv(sk, inmsg, BT_AUDIO_IPC_PACKET_SIZE, 0) > 0) {
+ type = bt_audio_strmsg(inmsg->msg_type);
+ if (type) {
+ DBG("Received %s", type);
+ err = 0;
+ } else {
+ err = -EINVAL;
+ SNDERR("Bogus message type %d "
+ "received from audio service",
+ inmsg->msg_type);
+ }
+ } else {
+ err = -errno;
+ SNDERR("Error receiving data from audio service: %s(%d)",
+ strerror(errno), errno);
+ }
+
+ return err;
+}
+
+static int audioservice_expect(int sk, bt_audio_msg_header_t *rsp_hdr,
+ int expected_type)
+{
+ int err = audioservice_recv(sk, rsp_hdr);
+ if (err == 0) {
+ if (rsp_hdr->msg_type != expected_type) {
+ err = -EINVAL;
+ SNDERR("Bogus message %s received while "
+ "%s was expected",
+ bt_audio_strmsg(rsp_hdr->msg_type),
+ bt_audio_strmsg(expected_type));
+ }
+ }
+ return err;
+}
+
+static int bluetooth_init(struct bluetooth_data *data, snd_pcm_stream_t stream,
+ snd_config_t *conf)
+{
+ int sk, err;
+ struct bluetooth_alsa_config *alsa_conf = &data->alsa_config;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ bt_audio_rsp_msg_header_t *rsp_hdr = (void*) buf;
+ struct bt_getcapabilities_req *getcaps_req = (void*) buf;
+ struct bt_getcapabilities_rsp *getcaps_rsp = (void*) buf;
+
+ memset(data, 0, sizeof(struct bluetooth_data));
+
+ err = bluetooth_parse_config(conf, alsa_conf);
+ if (err < 0)
+ return err;
+
+ data->server.fd = -1;
+ data->stream.fd = -1;
+
+ sk = bt_audio_service_open();
+ if (sk <= 0) {
+ err = -errno;
+ goto failed;
+ }
+
+ data->server.fd = sk;
+ data->server.events = POLLIN;
+
+ data->pipefd[0] = -1;
+ data->pipefd[1] = -1;
+
+ if (pipe(data->pipefd) < 0) {
+ err = -errno;
+ goto failed;
+ }
+ if (fcntl(data->pipefd[0], F_SETFL, O_NONBLOCK) < 0) {
+ err = -errno;
+ goto failed;
+ }
+ if (fcntl(data->pipefd[1], F_SETFL, O_NONBLOCK) < 0) {
+ err = -errno;
+ goto failed;
+ }
+
+ memset(getcaps_req, 0, BT_AUDIO_IPC_PACKET_SIZE);
+ getcaps_req->h.msg_type = BT_GETCAPABILITIES_REQ;
+ getcaps_req->flags = 0;
+ if (alsa_conf->autoconnect)
+ getcaps_req->flags |= BT_FLAG_AUTOCONNECT;
+ strncpy(getcaps_req->device, alsa_conf->device, 18);
+ if (alsa_conf->has_transport)
+ getcaps_req->transport = alsa_conf->transport;
+ else
+ getcaps_req->transport = BT_CAPABILITIES_TRANSPORT_ANY;
+
+ err = audioservice_send(data->server.fd, &getcaps_req->h);
+ if (err < 0)
+ goto failed;
+
+ err = audioservice_expect(data->server.fd, &rsp_hdr->msg_h, BT_GETCAPABILITIES_RSP);
+ if (err < 0)
+ goto failed;
+
+ if (rsp_hdr->posix_errno != 0) {
+ SNDERR("BT_GETCAPABILITIES failed : %s(%d)",
+ strerror(rsp_hdr->posix_errno),
+ rsp_hdr->posix_errno);
+ return -rsp_hdr->posix_errno;
+ }
+
+ data->transport = getcaps_rsp->transport;
+
+ if (getcaps_rsp->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+ data->a2dp.sbc_capabilities = getcaps_rsp->sbc_capabilities;
+
+ return 0;
+
+failed:
+ bt_audio_service_close(sk);
+ return err;
+}
+
+SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth)
+{
+ struct bluetooth_data *data;
+ int err;
+
+ DBG("Bluetooth PCM plugin (%s)",
+ stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture");
+
+ data = malloc(sizeof(struct bluetooth_data));
+ if (!data) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ err = bluetooth_init(data, stream, conf);
+ if (err < 0)
+ goto error;
+
+ data->io.version = SND_PCM_IOPLUG_VERSION;
+ data->io.name = "Bluetooth Audio Device";
+ data->io.mmap_rw = 0; /* No direct mmap communication */
+ data->io.private_data = data;
+
+ if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+ data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ?
+ &bluetooth_a2dp_playback :
+ &bluetooth_a2dp_capture;
+ else
+ data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ?
+ &bluetooth_hsp_playback :
+ &bluetooth_hsp_capture;
+
+ err = snd_pcm_ioplug_create(&data->io, name, stream, mode);
+ if (err < 0)
+ goto error;
+
+ if (data->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+ err = bluetooth_a2dp_hw_constraint(&data->io);
+ else
+ err = bluetooth_hsp_hw_constraint(&data->io);
+
+ if (err < 0) {
+ snd_pcm_ioplug_delete(&data->io);
+ goto error;
+ }
+
+ *pcmp = data->io.pcm;
+
+ return 0;
+
+error:
+ bluetooth_exit(data);
+
+ return err;
+}
+
+SND_PCM_PLUGIN_SYMBOL(bluetooth);
diff --git a/audio/rtp.h b/audio/rtp.h
new file mode 100644
index 00000000..690bd43a
--- /dev/null
+++ b/audio/rtp.h
@@ -0,0 +1,76 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct rtp_header {
+ uint8_t cc:4;
+ uint8_t x:1;
+ uint8_t p:1;
+ uint8_t v:2;
+
+ uint8_t pt:7;
+ uint8_t m:1;
+
+ uint16_t sequence_number;
+ uint32_t timestamp;
+ uint32_t ssrc;
+ uint32_t csrc[0];
+} __attribute__ ((packed));
+
+struct rtp_payload {
+ uint8_t frame_count:4;
+ uint8_t rfa0:1;
+ uint8_t is_last_fragment:1;
+ uint8_t is_first_fragment:1;
+ uint8_t is_fragmented:1;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct rtp_header {
+ uint8_t v:2;
+ uint8_t p:1;
+ uint8_t x:1;
+ uint8_t cc:4;
+
+ uint8_t m:1;
+ uint8_t pt:7;
+
+ uint16_t sequence_number;
+ uint32_t timestamp;
+ uint32_t ssrc;
+ uint32_t csrc[0];
+} __attribute__ ((packed));
+
+struct rtp_payload {
+ uint8_t is_fragmented:1;
+ uint8_t is_first_fragment:1;
+ uint8_t is_last_fragment:1;
+ uint8_t rfa0:1;
+ uint8_t frame_count:4;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
diff --git a/audio/sink.c b/audio/sink.c
new file mode 100644
index 00000000..2af74bb3
--- /dev/null
+++ b/audio/sink.c
@@ -0,0 +1,563 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <stdint.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+
+#include "avdtp.h"
+#include "device.h"
+#include "a2dp.h"
+#include "error.h"
+#include "sink.h"
+
+#define STREAM_SETUP_RETRY_TIMER 2000
+
+struct pending_request {
+ DBusConnection *conn;
+ DBusMessage *msg;
+ unsigned int id;
+};
+
+struct sink {
+ struct avdtp *session;
+ struct avdtp_stream *stream;
+ unsigned int cb_id;
+ uint8_t state;
+ struct pending_request *connect;
+ struct pending_request *disconnect;
+ DBusConnection *conn;
+};
+
+static void pending_request_free(struct pending_request *pending)
+{
+ if (pending->conn)
+ dbus_connection_unref(pending->conn);
+ if (pending->msg)
+ dbus_message_unref(pending->msg);
+ g_free(pending);
+}
+
+static void stream_state_changed(struct avdtp_stream *stream,
+ avdtp_state_t old_state,
+ avdtp_state_t new_state,
+ struct avdtp_error *err,
+ void *user_data)
+{
+ struct audio_device *dev = user_data;
+ struct sink *sink = dev->sink;
+
+ if (err)
+ return;
+
+ switch (new_state) {
+ case AVDTP_STATE_IDLE:
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ "Disconnected",
+ DBUS_TYPE_INVALID);
+ if (sink->disconnect) {
+ DBusMessage *reply;
+ struct pending_request *p;
+
+ p = sink->disconnect;
+ sink->disconnect = NULL;
+
+ reply = dbus_message_new_method_return(p->msg);
+ dbus_connection_send(p->conn, reply, NULL);
+ dbus_message_unref(reply);
+ pending_request_free(p);
+ }
+
+ if (sink->session) {
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+ }
+ sink->stream = NULL;
+ sink->cb_id = 0;
+ break;
+ case AVDTP_STATE_OPEN:
+ if (old_state == AVDTP_STATE_CONFIGURED)
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ "Connected",
+ DBUS_TYPE_INVALID);
+ else if (old_state == AVDTP_STATE_STREAMING)
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ "Stopped",
+ DBUS_TYPE_INVALID);
+ break;
+ case AVDTP_STATE_STREAMING:
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ "Playing",
+ DBUS_TYPE_INVALID);
+ break;
+ case AVDTP_STATE_CONFIGURED:
+ case AVDTP_STATE_CLOSING:
+ case AVDTP_STATE_ABORTING:
+ default:
+ break;
+ }
+
+ sink->state = new_state;
+}
+
+static gboolean stream_setup_retry(gpointer user_data)
+{
+ struct sink *sink = user_data;
+ struct pending_request *pending = sink->connect;
+
+ if (sink->state >= AVDTP_STATE_OPEN) {
+ DBusMessage *reply;
+ debug("Stream successfully created, after XCASE connect:connect");
+ reply = dbus_message_new_method_return(pending->msg);
+ dbus_connection_send(pending->conn, reply, NULL);
+ dbus_message_unref(reply);
+ } else {
+ debug("Stream setup failed, after XCASE connect:connect");
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+ }
+
+ sink->connect = NULL;
+ pending_request_free(pending);
+
+ return FALSE;
+}
+
+static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err, void *user_data)
+{
+ struct sink *sink = user_data;
+ struct pending_request *pending;
+
+ pending = sink->connect;
+
+ if (stream) {
+ DBusMessage *reply;
+ sink->connect = NULL;
+ reply = dbus_message_new_method_return(pending->msg);
+ dbus_connection_send(pending->conn, reply, NULL);
+ dbus_message_unref(reply);
+ pending_request_free(pending);
+ debug("Stream successfully created");
+ } else {
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+ if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO
+ && avdtp_error_posix_errno(err) != EHOSTDOWN) {
+ debug("connect:connect XCASE detected");
+ g_timeout_add(STREAM_SETUP_RETRY_TIMER,
+ stream_setup_retry, sink);
+ } else {
+ sink->connect = NULL;
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+ pending_request_free(pending);
+ debug("Stream setup failed : %s", avdtp_strerror(err));
+ }
+ }
+}
+
+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);
+
+
+ return TRUE;
+}
+
+static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err,
+ void *user_data)
+{
+ struct sink *sink = user_data;
+ struct pending_request *pending;
+ struct avdtp_local_sep *lsep;
+ struct avdtp_remote_sep *rsep;
+ GSList *caps = NULL;
+ int id;
+
+ pending = sink->connect;
+
+ if (err) {
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+ if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO
+ && avdtp_error_posix_errno(err) != EHOSTDOWN) {
+ debug("connect:connect XCASE detected");
+ g_timeout_add(STREAM_SETUP_RETRY_TIMER,
+ stream_setup_retry, sink);
+ } else
+ goto failed;
+ return;
+ }
+
+ debug("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;
+ }
+
+ id = a2dp_source_config(sink->session, stream_setup_complete,
+ caps, sink);
+ if (id == 0)
+ goto failed;
+
+ pending->id = id;
+ return;
+
+failed:
+ pending_request_free(pending);
+ sink->connect = NULL;
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+}
+
+static DBusMessage *sink_connect(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *dev = data;
+ struct sink *sink = dev->sink;
+ struct pending_request *pending;
+
+ if (!sink->session)
+ sink->session = avdtp_get(&dev->src, &dev->dst);
+
+ if (!sink->session)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Unable to get a session");
+
+ if (sink->connect || sink->disconnect)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "%s", strerror(EBUSY));
+
+ if (sink->state >= AVDTP_STATE_OPEN)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".AlreadyConnected",
+ "Device Already Connected");
+
+ pending = g_new0(struct pending_request, 1);
+ pending->conn = dbus_connection_ref(conn);
+ pending->msg = dbus_message_ref(msg);
+ sink->connect = pending;
+
+ avdtp_discover(sink->session, discovery_complete, sink);
+
+ debug("stream creation in progress");
+
+ return NULL;
+}
+
+static DBusMessage *sink_disconnect(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct sink *sink = device->sink;
+ struct pending_request *pending;
+ int err;
+
+ if (!sink->session)
+ return g_dbus_create_error(msg, ERROR_INTERFACE
+ ".NotConnected",
+ "Device not Connected");
+
+ if (sink->connect || sink->disconnect)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "%s", strerror(EBUSY));
+
+ if (sink->state < AVDTP_STATE_OPEN) {
+ DBusMessage *reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+ return reply;
+ }
+
+ err = avdtp_close(sink->session, sink->stream);
+ if (err < 0)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "%s", strerror(-err));
+
+ pending = g_new0(struct pending_request, 1);
+ pending->conn = dbus_connection_ref(conn);
+ pending->msg = dbus_message_ref(msg);
+ sink->disconnect = pending;
+
+ return NULL;
+}
+
+static DBusMessage *sink_is_connected(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct sink *sink = device->sink;
+ DBusMessage *reply;
+ dbus_bool_t connected;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ connected = (sink->state >= AVDTP_STATE_CONFIGURED);
+
+ dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static GDBusMethodTable sink_methods[] = {
+ { "Connect", "", "", sink_connect,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "Disconnect", "", "", sink_disconnect,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "IsConnected", "", "b", sink_is_connected },
+ { NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable sink_signals[] = {
+ { "Connected", "" },
+ { "Disconnected", "" },
+ { "Playing", "" },
+ { "Stopped", "" },
+ { NULL, NULL }
+};
+
+struct sink *sink_init(struct audio_device *dev)
+{
+ if (!g_dbus_register_interface(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ sink_methods, sink_signals, NULL,
+ dev, NULL))
+ return NULL;
+
+ return g_new0(struct sink, 1);
+}
+
+void sink_free(struct audio_device *dev)
+{
+ struct sink *sink = dev->sink;
+
+ if (sink->cb_id)
+ avdtp_stream_remove_cb(sink->session, sink->stream,
+ sink->cb_id);
+
+ if (sink->session)
+ avdtp_unref(sink->session);
+
+ if (sink->connect)
+ pending_request_free(sink->connect);
+
+ if (sink->disconnect)
+ pending_request_free(sink->disconnect);
+
+ g_free(sink);
+ dev->sink = NULL;
+}
+
+gboolean sink_is_active(struct audio_device *dev)
+{
+ struct sink *sink = dev->sink;
+
+ if (sink->session)
+ return TRUE;
+
+ return FALSE;
+}
+
+avdtp_state_t sink_get_state(struct audio_device *dev)
+{
+ struct sink *sink = dev->sink;
+
+ return sink->state;
+}
+
+gboolean sink_new_stream(struct audio_device *dev, struct avdtp *session,
+ struct avdtp_stream *stream)
+{
+ struct sink *sink = dev->sink;
+
+ if (sink->stream)
+ return FALSE;
+
+ if (!sink->session)
+ sink->session = avdtp_ref(session);
+
+ sink->stream = stream;
+
+ sink->cb_id = avdtp_stream_add_cb(session, stream,
+ stream_state_changed, dev);
+
+ return TRUE;
+}
diff --git a/audio/sink.h b/audio/sink.h
new file mode 100644
index 00000000..5e4b6aaa
--- /dev/null
+++ b/audio/sink.h
@@ -0,0 +1,32 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 AUDIO_SINK_INTERFACE "org.bluez.audio.Sink"
+
+struct sink *sink_init(struct audio_device *dev);
+void sink_free(struct audio_device *dev);
+gboolean sink_is_active(struct audio_device *dev);
+avdtp_state_t sink_get_state(struct audio_device *dev);
+gboolean sink_new_stream(struct audio_device *dev, struct avdtp *session,
+ struct avdtp_stream *stream);
diff --git a/audio/test-audio b/audio/test-audio
new file mode 100755
index 00000000..19ea6252
--- /dev/null
+++ b/audio/test-audio
@@ -0,0 +1,27 @@
+#!/usr/bin/python
+
+import dbus
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object('org.bluez', '/org/bluez'),
+ 'org.bluez.Manager')
+
+conn = manager.ActivateService('audio')
+
+audio = dbus.Interface(bus.get_object(conn, '/org/bluez/audio'),
+ 'org.bluez.audio.Manager')
+
+try:
+ headset = dbus.Interface(bus.get_object(conn, audio.DefaultHeadset()),
+ 'org.bluez.audio.Headset')
+except:
+ pass
+
+try:
+ device = dbus.Interface(bus.get_object(conn, audio.DefaultDevice()),
+ 'org.bluez.audio.Device')
+ sink = dbus.Interface(bus.get_object(conn, audio.DefaultDevice()),
+ 'org.bluez.audio.Sink')
+except:
+ pass
diff --git a/audio/unix.c b/audio/unix.c
new file mode 100644
index 00000000..93f5788b
--- /dev/null
+++ b/audio/unix.c
@@ -0,0 +1,1155 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <stdio.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdint.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <dbus/dbus.h>
+#include <glib.h>
+
+#include "logging.h"
+#include "ipc.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "a2dp.h"
+#include "headset.h"
+#include "sink.h"
+#include "unix.h"
+#include "glib-helper.h"
+
+typedef enum {
+ TYPE_NONE,
+ TYPE_HEADSET,
+ TYPE_SINK,
+ TYPE_SOURCE
+} service_type_t;
+
+typedef void (*notify_cb_t) (struct audio_device *dev, void *data);
+
+struct a2dp_data {
+ struct avdtp *session;
+ struct avdtp_stream *stream;
+ struct a2dp_sep *sep;
+};
+
+struct headset_data {
+ headset_lock_t lock;
+};
+
+struct unix_client {
+ struct audio_device *dev;
+ GSList *caps;
+ service_type_t type;
+ char *interface;
+ union {
+ struct a2dp_data a2dp;
+ struct headset_data hs;
+ } d;
+ int sock;
+ int access_mode;
+ int data_fd; /* To be deleted once two phase configuration is fully implemented */
+ unsigned int req_id;
+ unsigned int cb_id;
+ gboolean (*cancel) (struct audio_device *dev, unsigned int id);
+};
+
+static GSList *clients = NULL;
+
+static int unix_sock = -1;
+
+static void client_free(struct unix_client *client)
+{
+ struct a2dp_data *a2dp;
+
+ switch (client->type) {
+ case TYPE_SINK:
+ case TYPE_SOURCE:
+ a2dp = &client->d.a2dp;
+ if (client->cb_id > 0)
+ avdtp_stream_remove_cb(a2dp->session, a2dp->stream,
+ client->cb_id);
+ if (a2dp->sep)
+ a2dp_sep_unlock(a2dp->sep, a2dp->session);
+ if (a2dp->session)
+ avdtp_unref(a2dp->session);
+ break;
+ default:
+ break;
+ }
+
+ if (client->sock >= 0)
+ close(client->sock);
+
+ if (client->caps) {
+ g_slist_foreach(client->caps, (GFunc) g_free, NULL);
+ g_slist_free(client->caps);
+ }
+
+ g_free(client->interface);
+ g_free(client);
+}
+
+/* Pass file descriptor through local domain sockets (AF_LOCAL, formerly
+ * AF_UNIX) and the sendmsg() system call with the cmsg_type field of a "struct
+ * cmsghdr" set to SCM_RIGHTS and the data being an integer value equal to the
+ * handle of the file descriptor to be passed. */
+static int unix_sendmsg_fd(int sock, int fd)
+{
+ char cmsg_b[CMSG_SPACE(sizeof(int))], m = 'm';
+ struct cmsghdr *cmsg;
+ struct iovec iov = { &m, sizeof(m) };
+ struct msghdr msgh;
+
+ memset(&msgh, 0, sizeof(msgh));
+ msgh.msg_iov = &iov;
+ msgh.msg_iovlen = 1;
+ msgh.msg_control = &cmsg_b;
+ msgh.msg_controllen = CMSG_LEN(sizeof(int));
+
+ cmsg = CMSG_FIRSTHDR(&msgh);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ /* Initialize the payload */
+ (*(int *) CMSG_DATA(cmsg)) = fd;
+
+ return sendmsg(sock, &msgh, MSG_NOSIGNAL);
+}
+
+static void unix_ipc_sendmsg(struct unix_client *client,
+ const bt_audio_msg_header_t *msg)
+{
+ info("Audio API: sending %s", bt_audio_strmsg(msg->msg_type));
+
+ if (send(client->sock, msg, BT_AUDIO_IPC_PACKET_SIZE, 0) < 0)
+ error("Error %s(%d)", strerror(errno), errno);
+}
+
+static void unix_ipc_error(struct unix_client *client, int type, int err)
+{
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ bt_audio_rsp_msg_header_t *rsp_hdr = (void *) buf;
+
+ if (!g_slist_find(clients, client))
+ return;
+
+ memset(buf, 0, sizeof(buf));
+ rsp_hdr->msg_h.msg_type = type;
+ rsp_hdr->posix_errno = err;
+
+ unix_ipc_sendmsg(client, &rsp_hdr->msg_h);
+}
+
+static service_type_t select_service(struct audio_device *dev, const char *interface)
+{
+ if (!interface) {
+ if (dev->sink && avdtp_is_connected(&dev->src, &dev->dst))
+ return TYPE_SINK;
+ else if (dev->headset && headset_is_active(dev))
+ return TYPE_HEADSET;
+ else if (dev->sink)
+ return TYPE_SINK;
+ else if (dev->headset)
+ return TYPE_HEADSET;
+ } else if (!strcmp(interface, AUDIO_SINK_INTERFACE) && dev->sink)
+ return TYPE_SINK;
+ else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset)
+ return TYPE_HEADSET;
+
+ return TYPE_NONE;
+}
+
+static void stream_state_changed(struct avdtp_stream *stream,
+ avdtp_state_t old_state,
+ avdtp_state_t new_state,
+ struct avdtp_error *err,
+ void *user_data)
+{
+ struct unix_client *client = user_data;
+ struct a2dp_data *a2dp = &client->d.a2dp;
+
+ switch (new_state) {
+ case AVDTP_STATE_IDLE:
+ if (a2dp->sep) {
+ a2dp_sep_unlock(a2dp->sep, a2dp->session);
+ a2dp->sep = NULL;
+ }
+ client->dev = NULL;
+ if (a2dp->session) {
+ avdtp_unref(a2dp->session);
+ a2dp->session = NULL;
+ }
+ a2dp->stream = NULL;
+ client->cb_id = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+static void headset_discovery_complete(struct audio_device *dev, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_getcapabilities_rsp *rsp = (void *) buf;
+
+ client->req_id = 0;
+
+ if (!dev)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+
+ rsp->rsp_h.msg_h.msg_type = BT_GETCAPABILITIES_RSP;
+ rsp->transport = BT_CAPABILITIES_TRANSPORT_SCO;
+ rsp->sampling_rate = 8000;
+
+ unix_ipc_sendmsg(client, &rsp->rsp_h.msg_h);
+
+ return;
+
+failed:
+ error("discovery failed");
+ unix_ipc_error(client, BT_SETCONFIGURATION_RSP, EIO);
+ client->dev = NULL;
+}
+
+static void headset_setup_complete(struct audio_device *dev, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_setconfiguration_rsp *rsp = (void *) buf;
+ struct headset_data *hs = &client->d.hs;
+
+ client->req_id = 0;
+
+ if (!dev)
+ goto failed;
+
+ switch (client->access_mode) {
+ case BT_CAPABILITIES_ACCESS_MODE_READ:
+ hs->lock = HEADSET_LOCK_READ;
+ break;
+ case BT_CAPABILITIES_ACCESS_MODE_WRITE:
+ hs->lock = HEADSET_LOCK_WRITE;
+ break;
+ case BT_CAPABILITIES_ACCESS_MODE_READWRITE:
+ hs->lock = HEADSET_LOCK_READ | HEADSET_LOCK_WRITE;
+ break;
+ default:
+ hs->lock = 0;
+ break;
+ }
+
+ if (!headset_lock(dev, hs->lock)) {
+ error("Unable to lock headset");
+ goto failed;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ rsp->rsp_h.msg_h.msg_type = BT_SETCONFIGURATION_RSP;
+ rsp->transport = BT_CAPABILITIES_TRANSPORT_SCO;
+ rsp->access_mode = client->access_mode;
+ rsp->link_mtu = 48;
+
+ client->data_fd = headset_get_sco_fd(dev);
+
+ unix_ipc_sendmsg(client, &rsp->rsp_h.msg_h);
+
+ return;
+
+failed:
+ error("config failed");
+ unix_ipc_error(client, BT_SETCONFIGURATION_RSP, EIO);
+ client->dev = NULL;
+}
+
+static void headset_resume_complete(struct audio_device *dev, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_streamstart_rsp *rsp = (void *) buf;
+ struct bt_streamfd_ind *ind = (void *) buf;
+
+ client->req_id = 0;
+
+ if (!dev)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+
+ rsp->rsp_h.msg_h.msg_type = BT_STREAMSTART_RSP;
+
+ unix_ipc_sendmsg(client, &rsp->rsp_h.msg_h);
+
+ memset(buf, 0, sizeof(buf));
+ ind->h.msg_type = BT_STREAMFD_IND;
+ unix_ipc_sendmsg(client, &ind->h);
+
+ client->data_fd = headset_get_sco_fd(dev);
+
+ if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) {
+ error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno);
+ goto failed;
+ }
+
+ return;
+
+failed:
+ error("resume failed");
+ unix_ipc_error(client, BT_STREAMSTART_RSP, EIO);
+ client->dev = NULL;
+}
+
+static void a2dp_discovery_complete(struct avdtp *session, GSList *seps,
+ struct avdtp_error *err,
+ void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_getcapabilities_rsp *rsp = (void *) buf;
+ struct a2dp_data *a2dp = &client->d.a2dp;
+ struct sbc_codec_cap *sbc_cap = NULL;
+ struct mpeg_codec_cap *mpeg_cap = NULL;
+ GSList *l;
+
+ if (!g_slist_find(clients, client)) {
+ debug("Client disconnected during discovery");
+ return;
+ }
+
+ if (err)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+ client->req_id = 0;
+
+ rsp->rsp_h.msg_h.msg_type = BT_GETCAPABILITIES_RSP;
+ rsp->transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+
+ for (l = seps; l; l = g_slist_next(l)) {
+ struct avdtp_remote_sep *rsep = l->data;
+ struct avdtp_service_capability *cap;
+ struct avdtp_media_codec_capability *codec_cap;
+
+ cap = avdtp_get_codec(rsep);
+
+ if (cap->category != AVDTP_MEDIA_CODEC)
+ continue;
+
+ codec_cap = (void *) cap->data;
+
+ if (codec_cap->media_codec_type == A2DP_CODEC_SBC && !sbc_cap)
+ sbc_cap = (void *) codec_cap;
+
+ if (codec_cap->media_codec_type == A2DP_CODEC_MPEG12 && !mpeg_cap)
+ mpeg_cap = (void *) codec_cap;
+ }
+
+ /* endianess prevent direct cast */
+ if (sbc_cap) {
+ rsp->sbc_capabilities.channel_mode = sbc_cap->channel_mode;
+ rsp->sbc_capabilities.frequency = sbc_cap->frequency;
+ rsp->sbc_capabilities.allocation_method = sbc_cap->allocation_method;
+ rsp->sbc_capabilities.subbands = sbc_cap->subbands;
+ rsp->sbc_capabilities.block_length = sbc_cap->block_length;
+ rsp->sbc_capabilities.min_bitpool = sbc_cap->min_bitpool;
+ rsp->sbc_capabilities.max_bitpool = sbc_cap->max_bitpool;
+ }
+
+ if (mpeg_cap) {
+ rsp->mpeg_capabilities.channel_mode = mpeg_cap->channel_mode;
+ rsp->mpeg_capabilities.crc = mpeg_cap->crc;
+ rsp->mpeg_capabilities.layer = mpeg_cap->layer;
+ rsp->mpeg_capabilities.frequency = mpeg_cap->frequency;
+ rsp->mpeg_capabilities.mpf = mpeg_cap->mpf;
+ rsp->mpeg_capabilities.bitrate = mpeg_cap->bitrate;
+ }
+
+ unix_ipc_sendmsg(client, &rsp->rsp_h.msg_h);
+
+ return;
+
+failed:
+ error("discovery failed");
+ unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO);
+
+ avdtp_unref(a2dp->session);
+
+ a2dp->session = NULL;
+ a2dp->stream = NULL;
+}
+
+static void a2dp_config_complete(struct avdtp *session, struct a2dp_sep *sep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err,
+ void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_setconfiguration_rsp *rsp = (void *) buf;
+ struct a2dp_data *a2dp = &client->d.a2dp;
+ uint16_t imtu, omtu;
+ GSList *caps;
+
+ if (err)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+ client->req_id = 0;
+
+ if (!stream)
+ goto failed;
+
+ if (!a2dp_sep_lock(sep, session)) {
+ error("Unable to lock A2DP source SEP");
+ goto failed;
+ }
+
+ a2dp->sep = sep;
+ a2dp->stream = stream;
+
+ if (!avdtp_stream_get_transport(stream, &client->data_fd, &imtu, &omtu,
+ &caps)) {
+ error("Unable to get stream transport");
+ goto failed;
+ }
+
+ rsp->rsp_h.msg_h.msg_type = BT_SETCONFIGURATION_RSP;
+ rsp->transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+ client->access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE;
+ rsp->access_mode = client->access_mode;
+ /* FIXME: Use imtu when fd_opt is CFG_FD_OPT_READ */
+ rsp->link_mtu = omtu;
+
+ unix_ipc_sendmsg(client, &rsp->rsp_h.msg_h);
+
+ client->cb_id = avdtp_stream_add_cb(session, stream,
+ stream_state_changed, client);
+
+ return;
+
+failed:
+ error("config failed");
+
+ if (a2dp->sep) {
+ a2dp_sep_unlock(a2dp->sep, a2dp->session);
+ a2dp->sep = NULL;
+ }
+ unix_ipc_error(client, BT_SETCONFIGURATION_RSP, EIO);
+
+ avdtp_unref(a2dp->session);
+
+ a2dp->session = NULL;
+ a2dp->stream = NULL;
+}
+
+static void a2dp_resume_complete(struct avdtp *session,
+ struct avdtp_error *err, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_streamstart_rsp *rsp = (void *) buf;
+ struct bt_streamfd_ind *ind = (void *) buf;
+ struct a2dp_data *a2dp = &client->d.a2dp;
+
+ if (err)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+ rsp->rsp_h.msg_h.msg_type = BT_STREAMSTART_RSP;
+ rsp->rsp_h.posix_errno = 0;
+ unix_ipc_sendmsg(client, &rsp->rsp_h.msg_h);
+
+ memset(buf, 0, sizeof(buf));
+ ind->h.msg_type = BT_STREAMFD_IND;
+ unix_ipc_sendmsg(client, &ind->h);
+
+ if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) {
+ error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno);
+ goto failed;
+ }
+
+ return;
+
+failed:
+ error("resume failed");
+
+ if (a2dp->sep) {
+ a2dp_sep_unlock(a2dp->sep, a2dp->session);
+ a2dp->sep = NULL;
+ }
+ unix_ipc_error(client, BT_STREAMSTART_RSP, EIO);
+
+ avdtp_unref(a2dp->session);
+
+ a2dp->session = NULL;
+ a2dp->stream = NULL;
+}
+
+static void a2dp_suspend_complete(struct avdtp *session,
+ struct avdtp_error *err, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_streamstart_rsp *rsp = (void *) buf;
+ struct a2dp_data *a2dp = &client->d.a2dp;
+
+ if (err)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+ rsp->rsp_h.msg_h.msg_type = BT_STREAMSTOP_RSP;
+ rsp->rsp_h.posix_errno = 0;
+ unix_ipc_sendmsg(client, &rsp->rsp_h.msg_h);
+
+ return;
+
+failed:
+ error("suspend failed");
+
+ if (a2dp->sep) {
+ a2dp_sep_unlock(a2dp->sep, a2dp->session);
+ a2dp->sep = NULL;
+ }
+ unix_ipc_error(client, BT_STREAMSTOP_RSP, EIO);
+
+ avdtp_unref(a2dp->session);
+
+ a2dp->session = NULL;
+ a2dp->stream = NULL;
+}
+
+static void start_discovery(struct audio_device *dev, struct unix_client *client)
+{
+ struct a2dp_data *a2dp;
+ int err = 0;
+
+ client->type = select_service(dev, client->interface);
+
+ switch (client->type) {
+ case TYPE_SINK:
+ a2dp = &client->d.a2dp;
+
+ if (!a2dp->session)
+ a2dp->session = avdtp_get(&dev->src, &dev->dst);
+
+ if (!a2dp->session) {
+ error("Unable to get a session");
+ goto failed;
+ }
+
+ err = avdtp_discover(a2dp->session, a2dp_discovery_complete,
+ client);
+ if (err)
+ goto failed;
+ break;
+
+ case TYPE_HEADSET:
+ headset_discovery_complete(dev, client);
+ break;
+
+ default:
+ error("No known services for device");
+ goto failed;
+ }
+
+ client->dev = dev;
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_GETCAPABILITIES_RSP, err ? : EIO);
+}
+
+static void start_config(struct audio_device *dev, struct unix_client *client)
+{
+ struct a2dp_data *a2dp;
+ unsigned int id;
+
+ client->type = select_service(dev, client->interface);
+
+ switch (client->type) {
+ case TYPE_SINK:
+ a2dp = &client->d.a2dp;
+
+ if (!a2dp->session)
+ a2dp->session = avdtp_get(&dev->src, &dev->dst);
+
+ if (!a2dp->session) {
+ error("Unable to get a session");
+ goto failed;
+ }
+
+ id = a2dp_source_config(a2dp->session, a2dp_config_complete,
+ client->caps, client);
+ client->cancel = a2dp_source_cancel;
+ break;
+
+ case TYPE_HEADSET:
+ id = headset_request_stream(dev, headset_setup_complete, client);
+ client->cancel = headset_cancel_stream;
+ break;
+
+ default:
+ error("No known services for device");
+ goto failed;
+ }
+
+ if (id == 0) {
+ error("config failed");
+ goto failed;
+ }
+
+ client->req_id = id;
+ client->dev = dev;
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_SETCONFIGURATION_RSP, EIO);
+}
+
+static void start_resume(struct audio_device *dev, struct unix_client *client)
+{
+ struct a2dp_data *a2dp;
+ unsigned int id;
+
+ client->type = select_service(dev, client->interface);
+
+ switch (client->type) {
+ case TYPE_SINK:
+ a2dp = &client->d.a2dp;
+
+ if (!a2dp->session)
+ a2dp->session = avdtp_get(&dev->src, &dev->dst);
+
+ if (!a2dp->session) {
+ error("Unable to get a session");
+ goto failed;
+ }
+
+ if (!a2dp->sep) {
+ error("Unable to get a sep");
+ goto failed;
+ }
+
+ id = a2dp_source_resume(a2dp->session, a2dp->sep,
+ a2dp_resume_complete, client);
+ client->cancel = a2dp_source_cancel;
+
+ if (id == 0) {
+ error("resume failed");
+ goto failed;
+ }
+
+ break;
+
+ case TYPE_HEADSET:
+ headset_resume_complete(dev, client);
+ break;
+
+ default:
+ error("No known services for device");
+ goto failed;
+ }
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_STREAMSTART_RSP, EIO);
+}
+
+static void start_suspend(struct audio_device *dev, struct unix_client *client)
+{
+ struct a2dp_data *a2dp;
+ unsigned int id;
+
+ client->type = select_service(dev, client->interface);
+
+ switch (client->type) {
+ case TYPE_SINK:
+ a2dp = &client->d.a2dp;
+
+ if (!a2dp->session)
+ a2dp->session = avdtp_get(&dev->src, &dev->dst);
+
+ if (!a2dp->session) {
+ error("Unable to get a session");
+ goto failed;
+ }
+
+ if (!a2dp->sep) {
+ error("Unable to get a sep");
+ goto failed;
+ }
+
+ id = a2dp_source_suspend(a2dp->session, a2dp->sep,
+ a2dp_suspend_complete, client);
+ client->cancel = a2dp_source_cancel;
+ break;
+
+ case TYPE_HEADSET:
+ id = headset_request_stream(dev, headset_setup_complete, client);
+ client->cancel = headset_cancel_stream;
+ break;
+
+ default:
+ error("No known services for device");
+ goto failed;
+ }
+
+ if (id == 0) {
+ error("suspend failed");
+ goto failed;
+ }
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_STREAMSTOP_RSP, EIO);
+}
+
+static void create_cb(struct audio_device *dev, void *user_data)
+{
+ struct unix_client *client = user_data;
+
+ if (!dev)
+ unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO);
+ else
+ start_discovery(dev, client);
+}
+
+static void handle_getcapabilities_req(struct unix_client *client,
+ struct bt_getcapabilities_req *req)
+{
+ struct audio_device *dev;
+ bdaddr_t bdaddr;
+
+ str2ba(req->device, &bdaddr);
+
+ if (client->interface) {
+ g_free(client->interface);
+ client->interface = NULL;
+ }
+
+ if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO)
+ client->interface = g_strdup(AUDIO_HEADSET_INTERFACE);
+ else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+ client->interface = g_strdup(AUDIO_SINK_INTERFACE);
+
+ if (!manager_find_device(&bdaddr, NULL, FALSE)) {
+ if (!(req->flags & BT_FLAG_AUTOCONNECT))
+ goto failed;
+ if (!bacmp(&bdaddr, BDADDR_ANY))
+ goto failed;
+ if (!manager_create_device(&bdaddr, create_cb, client))
+ goto failed;
+ return;
+ }
+
+ dev = manager_find_device(&bdaddr, client->interface, TRUE);
+ if (!dev) {
+ if (req->flags & BT_FLAG_AUTOCONNECT)
+ dev = manager_find_device(&bdaddr, client->interface, FALSE);
+ else
+ goto failed;
+ }
+
+ if (!dev)
+ goto failed;
+
+ start_discovery(dev, client);
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO);
+}
+
+static int handle_sco_transport(struct unix_client *client,
+ struct bt_setconfiguration_req *req)
+{
+ client->interface = g_strdup(AUDIO_HEADSET_INTERFACE);
+
+ info("config sco - device = %s access_mode = %u", req->device,
+ req->access_mode);
+
+ return 0;
+}
+
+static int handle_a2dp_transport(struct unix_client *client,
+ struct bt_setconfiguration_req *req)
+{
+ struct avdtp_service_capability *media_transport, *media_codec;
+ struct sbc_codec_cap sbc_cap;
+ struct mpeg_codec_cap mpeg_cap;
+
+ client->interface = g_strdup(AUDIO_SINK_INTERFACE);
+
+ if (client->caps) {
+ g_slist_foreach(client->caps, (GFunc) g_free, NULL);
+ g_slist_free(client->caps);
+ }
+
+ media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+ NULL, 0);
+
+ client->caps = g_slist_append(client->caps, media_transport);
+
+ info("config a2dp - device = %s access_mode = %u", req->device,
+ req->access_mode);
+
+ if (req->mpeg_capabilities.frequency) {
+
+ memset(&mpeg_cap, 0, sizeof(mpeg_cap));
+
+ mpeg_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+ mpeg_cap.cap.media_codec_type = A2DP_CODEC_MPEG12;
+ mpeg_cap.channel_mode = req->mpeg_capabilities.channel_mode;
+ mpeg_cap.crc = req->mpeg_capabilities.crc;
+ mpeg_cap.layer = req->mpeg_capabilities.layer;
+ mpeg_cap.frequency = req->mpeg_capabilities.frequency;
+ mpeg_cap.mpf = req->mpeg_capabilities.mpf;
+ mpeg_cap.bitrate = req->mpeg_capabilities.bitrate;
+
+ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &mpeg_cap,
+ sizeof(mpeg_cap));
+
+ info("codec mpeg12 - frequency = %u channel_mode = %u "
+ "layer = %u crc = %u mpf = %u bitrate = %u",
+ mpeg_cap.frequency, mpeg_cap.channel_mode,
+ mpeg_cap.layer, mpeg_cap.crc, mpeg_cap.mpf,
+ mpeg_cap.bitrate);
+ } else if (req->sbc_capabilities.frequency) {
+ memset(&sbc_cap, 0, sizeof(sbc_cap));
+
+ sbc_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+ sbc_cap.cap.media_codec_type = A2DP_CODEC_SBC;
+ sbc_cap.channel_mode = req->sbc_capabilities.channel_mode;
+ sbc_cap.frequency = req->sbc_capabilities.frequency;
+ sbc_cap.allocation_method = req->sbc_capabilities.allocation_method;
+ sbc_cap.subbands = req->sbc_capabilities.subbands;
+ sbc_cap.block_length = req->sbc_capabilities.block_length;
+ sbc_cap.min_bitpool = req->sbc_capabilities.min_bitpool;
+ sbc_cap.max_bitpool = req->sbc_capabilities.max_bitpool;
+
+ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
+ sizeof(sbc_cap));
+
+ info("codec sbc - frequency = %u channel_mode = %u "
+ "allocation = %u subbands = %u blocks = %u "
+ "bitpool = %u", sbc_cap.frequency,
+ sbc_cap.channel_mode, sbc_cap.allocation_method,
+ sbc_cap.subbands, sbc_cap.block_length,
+ sbc_cap.max_bitpool);
+ } else
+ return -EINVAL;
+
+ client->caps = g_slist_append(client->caps, media_codec);
+
+ return 0;
+}
+
+static void handle_setconfiguration_req(struct unix_client *client,
+ struct bt_setconfiguration_req *req)
+{
+ struct audio_device *dev;
+ bdaddr_t bdaddr;
+ int err = 0;
+
+ if (!req->access_mode) {
+ err = EINVAL;
+ goto failed;
+ }
+
+ str2ba(req->device, &bdaddr);
+
+ if (client->interface) {
+ g_free(client->interface);
+ client->interface = NULL;
+ }
+
+ if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO) {
+ err = handle_sco_transport(client, req);
+ if (err < 0) {
+ err = -err;
+ goto failed;
+ }
+ } else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
+ err = handle_a2dp_transport(client, req);
+ if (err < 0) {
+ err = -err;
+ goto failed;
+ }
+ }
+
+ if (!manager_find_device(&bdaddr, NULL, FALSE)) {
+ if (!bacmp(&bdaddr, BDADDR_ANY))
+ goto failed;
+ if (!manager_create_device(&bdaddr, create_cb, client))
+ goto failed;
+ return;
+ }
+
+ dev = manager_find_device(&bdaddr, client->interface, TRUE);
+ if (!dev)
+ dev = manager_find_device(&bdaddr, client->interface, FALSE);
+
+ if (!dev)
+ goto failed;
+
+ client->access_mode = req->access_mode;
+
+ start_config(dev, client);
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_SETCONFIGURATION_RSP, err ? : EIO);
+}
+
+static void handle_streamstart_req(struct unix_client *client,
+ struct bt_streamstart_req *req)
+{
+ if (!client->dev)
+ goto failed;
+
+ start_resume(client->dev, client);
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_STREAMSTART_REQ, EIO);
+}
+
+static void handle_streamstop_req(struct unix_client *client,
+ struct bt_streamstop_req *req)
+{
+ if (!client->dev)
+ goto failed;
+
+ start_suspend(client->dev, client);
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_STREAMSTOP_REQ, EIO);
+}
+
+static void handle_control_req(struct unix_client *client,
+ struct bt_control_req *req)
+{
+ /* FIXME: really implement that */
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ struct bt_setconfiguration_rsp *rsp = (void *) buf;
+
+ memset(buf, 0, sizeof(buf));
+ rsp->rsp_h.msg_h.msg_type = BT_CONTROL_RSP;
+ rsp->rsp_h.posix_errno = 0;
+
+ unix_ipc_sendmsg(client, &rsp->rsp_h.msg_h);
+}
+
+static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+ char buf[BT_AUDIO_IPC_PACKET_SIZE];
+ bt_audio_msg_header_t *msghdr = (void *) buf;
+ struct unix_client *client = data;
+ int len;
+ struct a2dp_data *a2dp = &client->d.a2dp;
+ struct headset_data *hs = &client->d.hs;
+ const char *type;
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ if (cond & (G_IO_HUP | G_IO_ERR)) {
+ debug("Unix client disconnected (fd=%d)", client->sock);
+ switch (client->type) {
+ case TYPE_HEADSET:
+ if (client->dev)
+ headset_unlock(client->dev, hs->lock);
+ break;
+ case TYPE_SOURCE:
+ case TYPE_SINK:
+ if (a2dp->sep) {
+ a2dp_sep_unlock(a2dp->sep, a2dp->session);
+ a2dp->sep = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (client->cancel && client->req_id > 0)
+ client->cancel(client->dev, client->req_id);
+ goto failed;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ len = recv(client->sock, buf, sizeof(buf), MSG_WAITALL);
+ if (len < 0) {
+ error("recv: %s (%d)", strerror(errno), errno);
+ goto failed;
+ }
+
+ if ((type = bt_audio_strmsg(msghdr->msg_type)))
+ info("Audio API: received %s", type);
+
+ switch (msghdr->msg_type) {
+ case BT_GETCAPABILITIES_REQ:
+ handle_getcapabilities_req(client,
+ (struct bt_getcapabilities_req *) msghdr);
+ break;
+ case BT_SETCONFIGURATION_REQ:
+ handle_setconfiguration_req(client,
+ (struct bt_setconfiguration_req *) msghdr);
+ break;
+ case BT_STREAMSTART_REQ:
+ handle_streamstart_req(client,
+ (struct bt_streamstart_req *) msghdr);
+ break;
+ case BT_STREAMSTOP_REQ:
+ handle_streamstop_req(client,
+ (struct bt_streamstop_req *) msghdr);
+ break;
+ case BT_CONTROL_REQ:
+ handle_control_req(client,
+ (struct bt_control_req *) msghdr);
+ break;
+ default:
+ error("Audio API: received unexpected packet type %d",
+ msghdr->msg_type);
+ }
+
+ return TRUE;
+
+failed:
+ clients = g_slist_remove(clients, client);
+ client_free(client);
+ return FALSE;
+}
+
+static gboolean server_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+ struct sockaddr_un addr;
+ socklen_t addrlen;
+ int sk, cli_sk;
+ struct unix_client *client;
+ GIOChannel *io;
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ if (cond & (G_IO_HUP | G_IO_ERR)) {
+ g_io_channel_close(chan);
+ return FALSE;
+ }
+
+ sk = g_io_channel_unix_get_fd(chan);
+
+ memset(&addr, 0, sizeof(addr));
+ addrlen = sizeof(addr);
+
+ cli_sk = accept(sk, (struct sockaddr *) &addr, &addrlen);
+ if (cli_sk < 0) {
+ error("accept: %s (%d)", strerror(errno), errno);
+ return TRUE;
+ }
+
+ debug("Accepted new client connection on unix socket (fd=%d)", cli_sk);
+
+ client = g_new0(struct unix_client, 1);
+ client->sock = cli_sk;
+ clients = g_slist_append(clients, client);
+
+ io = g_io_channel_unix_new(cli_sk);
+ g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ client_cb, client);
+ g_io_channel_unref(io);
+
+ return TRUE;
+}
+
+int unix_init(void)
+{
+ GIOChannel *io;
+ struct sockaddr_un addr = {
+ AF_UNIX, BT_IPC_SOCKET_NAME
+ };
+
+ int sk, err;
+
+ sk = socket(PF_LOCAL, SOCK_STREAM, 0);
+ if (sk < 0) {
+ err = errno;
+ error("Can't create unix socket: %s (%d)", strerror(err), err);
+ return -err;
+ }
+
+ if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ error("Can't bind unix socket: %s (%d)", strerror(errno),
+ errno);
+ close(sk);
+ return -1;
+ }
+
+ set_nonblocking(sk);
+
+ unix_sock = sk;
+
+ listen(sk, 1);
+
+ io = g_io_channel_unix_new(sk);
+ g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ server_cb, NULL);
+ g_io_channel_unref(io);
+
+ info("Unix socket created: %d", sk);
+
+ return 0;
+}
+
+void unix_exit(void)
+{
+ g_slist_foreach(clients, (GFunc) client_free, NULL);
+ g_slist_free(clients);
+ close(unix_sock);
+ unix_sock = -1;
+}
diff --git a/audio/unix.h b/audio/unix.h
new file mode 100644
index 00000000..62474509
--- /dev/null
+++ b/audio/unix.h
@@ -0,0 +1,26 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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
+ *
+ */
+
+int unix_init(void);
+void unix_exit(void);