summaryrefslogtreecommitdiff
path: root/audio
diff options
context:
space:
mode:
authorAdrian Bunk <adrian.bunk@movial.com>2011-06-03 09:17:04 +0000
committerAdrian Bunk <adrian.bunk@movial.com>2011-06-03 09:17:04 +0000
commit799757ccf1d03c33c75bc597cd5ef77741dcb6a7 (patch)
treea8c3be85c730de28b012586591b76301033d3d21 /audio
Imported upstream 4.91upstream-4.91upstreampackaging
Diffstat (limited to 'audio')
-rw-r--r--audio/a2dp-codecs.h116
-rw-r--r--audio/a2dp.c2361
-rw-r--r--audio/a2dp.h164
-rw-r--r--audio/audio.conf45
-rw-r--r--audio/avdtp.c3914
-rw-r--r--audio/avdtp.h316
-rw-r--r--audio/bluetooth.conf36
-rw-r--r--audio/control.c1193
-rw-r--r--audio/control.h50
-rw-r--r--audio/ctl_bluetooth.c384
-rw-r--r--audio/device.c862
-rw-r--r--audio/device.h94
-rw-r--r--audio/gateway.c711
-rw-r--r--audio/gateway.h51
-rw-r--r--audio/gsta2dpsink.c730
-rw-r--r--audio/gsta2dpsink.h85
-rw-r--r--audio/gstavdtpsink.c2029
-rw-r--r--audio/gstavdtpsink.h107
-rw-r--r--audio/gstbluetooth.c109
-rw-r--r--audio/gstpragma.h24
-rw-r--r--audio/gstrtpsbcpay.c352
-rw-r--r--audio/gstrtpsbcpay.h66
-rw-r--r--audio/gstsbcdec.c223
-rw-r--r--audio/gstsbcdec.h66
-rw-r--r--audio/gstsbcenc.c603
-rw-r--r--audio/gstsbcenc.h75
-rw-r--r--audio/gstsbcparse.c220
-rw-r--r--audio/gstsbcparse.h69
-rw-r--r--audio/gstsbcutil.c521
-rw-r--r--audio/gstsbcutil.h75
-rw-r--r--audio/headset.c2951
-rw-r--r--audio/headset.h109
-rw-r--r--audio/ipc.c133
-rw-r--r--audio/ipc.h360
-rw-r--r--audio/main.c194
-rw-r--r--audio/manager.c1415
-rw-r--r--audio/manager.h56
-rw-r--r--audio/media.c714
-rw-r--r--audio/media.h54
-rw-r--r--audio/pcm_bluetooth.c1784
-rw-r--r--audio/rtp.h76
-rw-r--r--audio/sink.c743
-rw-r--r--audio/sink.h49
-rw-r--r--audio/source.c633
-rw-r--r--audio/source.h50
-rw-r--r--audio/telephony-dummy.c433
-rw-r--r--audio/telephony-maemo5.c2104
-rw-r--r--audio/telephony-maemo6.c1993
-rw-r--r--audio/telephony-ofono.c1627
-rw-r--r--audio/telephony.h244
-rw-r--r--audio/transport.c927
-rw-r--r--audio/transport.h38
-rw-r--r--audio/unix.c1917
-rw-r--r--audio/unix.h30
54 files changed, 34285 insertions, 0 deletions
diff --git a/audio/a2dp-codecs.h b/audio/a2dp-codecs.h
new file mode 100644
index 0000000..e44634e
--- /dev/null
+++ b/audio/a2dp-codecs.h
@@ -0,0 +1,116 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define A2DP_CODEC_SBC 0x00
+#define A2DP_CODEC_MPEG12 0x01
+#define A2DP_CODEC_MPEG24 0x02
+#define A2DP_CODEC_ATRAC 0x03
+
+#define SBC_SAMPLING_FREQ_16000 (1 << 3)
+#define SBC_SAMPLING_FREQ_32000 (1 << 2)
+#define SBC_SAMPLING_FREQ_44100 (1 << 1)
+#define SBC_SAMPLING_FREQ_48000 1
+
+#define SBC_CHANNEL_MODE_MONO (1 << 3)
+#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2)
+#define SBC_CHANNEL_MODE_STEREO (1 << 1)
+#define SBC_CHANNEL_MODE_JOINT_STEREO 1
+
+#define SBC_BLOCK_LENGTH_4 (1 << 3)
+#define SBC_BLOCK_LENGTH_8 (1 << 2)
+#define SBC_BLOCK_LENGTH_12 (1 << 1)
+#define SBC_BLOCK_LENGTH_16 1
+
+#define SBC_SUBBANDS_4 (1 << 1)
+#define SBC_SUBBANDS_8 1
+
+#define SBC_ALLOCATION_SNR (1 << 1)
+#define SBC_ALLOCATION_LOUDNESS 1
+
+#define MPEG_CHANNEL_MODE_MONO (1 << 3)
+#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2)
+#define MPEG_CHANNEL_MODE_STEREO (1 << 1)
+#define MPEG_CHANNEL_MODE_JOINT_STEREO 1
+
+#define MPEG_LAYER_MP1 (1 << 2)
+#define MPEG_LAYER_MP2 (1 << 1)
+#define MPEG_LAYER_MP3 1
+
+#define MPEG_SAMPLING_FREQ_16000 (1 << 5)
+#define MPEG_SAMPLING_FREQ_22050 (1 << 4)
+#define MPEG_SAMPLING_FREQ_24000 (1 << 3)
+#define MPEG_SAMPLING_FREQ_32000 (1 << 2)
+#define MPEG_SAMPLING_FREQ_44100 (1 << 1)
+#define MPEG_SAMPLING_FREQ_48000 1
+
+#define MAX_BITPOOL 64
+#define MIN_BITPOOL 2
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+typedef struct {
+ uint8_t channel_mode:4;
+ uint8_t frequency:4;
+ uint8_t allocation_method:2;
+ uint8_t subbands:2;
+ uint8_t block_length:4;
+ uint8_t min_bitpool;
+ uint8_t max_bitpool;
+} __attribute__ ((packed)) a2dp_sbc_t;
+
+typedef struct {
+ uint8_t channel_mode:4;
+ uint8_t crc:1;
+ uint8_t layer:3;
+ uint8_t frequency:6;
+ uint8_t mpf:1;
+ uint8_t rfa:1;
+ uint16_t bitrate;
+} __attribute__ ((packed)) a2dp_mpeg_t;
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+typedef struct {
+ uint8_t frequency:4;
+ uint8_t channel_mode:4;
+ uint8_t block_length:4;
+ uint8_t subbands:2;
+ uint8_t allocation_method:2;
+ uint8_t min_bitpool;
+ uint8_t max_bitpool;
+} __attribute__ ((packed)) a2dp_sbc_t;
+
+typedef struct {
+ uint8_t layer:3;
+ uint8_t crc:1;
+ uint8_t channel_mode:4;
+ uint8_t rfa:1;
+ uint8_t mpf:1;
+ uint8_t frequency:6;
+ uint16_t bitrate;
+} __attribute__ ((packed)) a2dp_mpeg_t;
+
+#else
+#error "Unknown byte order"
+#endif
diff --git a/audio/a2dp.c b/audio/a2dp.c
new file mode 100644
index 0000000..9d9c61d
--- /dev/null
+++ b/audio/a2dp.c
@@ -0,0 +1,2361 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#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 "log.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "sink.h"
+#include "source.h"
+#include "unix.h"
+#include "media.h"
+#include "transport.h"
+#include "a2dp.h"
+#include "sdpd.h"
+
+/* The duration that streams without users are allowed to stay in
+ * STREAMING state. */
+#define SUSPEND_TIMEOUT 5
+#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 {
+ struct a2dp_server *server;
+ struct media_endpoint *endpoint;
+ uint8_t type;
+ uint8_t codec;
+ struct avdtp_local_sep *lsep;
+ struct avdtp *session;
+ struct avdtp_stream *stream;
+ guint suspend_timer;
+ gboolean delay_reporting;
+ gboolean locked;
+ gboolean suspending;
+ gboolean starting;
+};
+
+struct a2dp_setup_cb {
+ struct a2dp_setup *setup;
+ a2dp_select_cb_t select_cb;
+ a2dp_config_cb_t config_cb;
+ a2dp_stream_cb_t resume_cb;
+ a2dp_stream_cb_t suspend_cb;
+ void *user_data;
+ unsigned int id;
+};
+
+struct a2dp_setup {
+ struct audio_device *dev;
+ struct avdtp *session;
+ struct a2dp_sep *sep;
+ struct avdtp_remote_sep *rsep;
+ struct avdtp_stream *stream;
+ struct avdtp_error *err;
+ avdtp_set_configuration_cb setconf_cb;
+ GSList *caps;
+ gboolean reconfigure;
+ gboolean start;
+ GSList *cb;
+ int ref;
+};
+
+static DBusConnection *connection = NULL;
+
+struct a2dp_server {
+ bdaddr_t src;
+ GSList *sinks;
+ GSList *sources;
+ uint32_t source_record_id;
+ uint32_t sink_record_id;
+ uint16_t version;
+ gboolean sink_enabled;
+ gboolean source_enabled;
+};
+
+static GSList *servers = NULL;
+static GSList *setups = NULL;
+static unsigned int cb_id = 0;
+
+static struct a2dp_setup *setup_ref(struct a2dp_setup *setup)
+{
+ setup->ref++;
+
+ DBG("%p: ref=%d", setup, setup->ref);
+
+ return setup;
+}
+
+static struct audio_device *a2dp_get_dev(struct avdtp *session)
+{
+ bdaddr_t src, dst;
+
+ avdtp_get_peers(session, &src, &dst);
+
+ return manager_find_device(NULL, &src, &dst, NULL, FALSE);
+}
+
+static struct a2dp_setup *setup_new(struct avdtp *session)
+{
+ struct audio_device *dev;
+ struct a2dp_setup *setup;
+
+ dev = a2dp_get_dev(session);
+ if (!dev) {
+ error("Unable to create setup");
+ return NULL;
+ }
+
+ setup = g_new0(struct a2dp_setup, 1);
+ setup->session = avdtp_ref(session);
+ setup->dev = a2dp_get_dev(session);
+ setups = g_slist_append(setups, setup);
+
+ return setup;
+}
+
+static void setup_free(struct a2dp_setup *s)
+{
+ DBG("%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_slist_foreach(s->caps, (GFunc) g_free, NULL);
+ g_slist_free(s->caps);
+ g_free(s);
+}
+
+static void setup_unref(struct a2dp_setup *setup)
+{
+ setup->ref--;
+
+ DBG("%p: ref=%d", setup, setup->ref);
+
+ if (setup->ref > 0)
+ return;
+
+ setup_free(setup);
+}
+
+static struct a2dp_setup_cb *setup_cb_new(struct a2dp_setup *setup)
+{
+ struct a2dp_setup_cb *cb;
+
+ cb = g_new0(struct a2dp_setup_cb, 1);
+ cb->setup = setup;
+ cb->id = ++cb_id;
+
+ setup->cb = g_slist_append(setup->cb, cb);
+ return cb;
+}
+
+static void setup_cb_free(struct a2dp_setup_cb *cb)
+{
+ struct a2dp_setup *setup = cb->setup;
+
+ setup->cb = g_slist_remove(setup->cb, cb);
+ setup_unref(cb->setup);
+ g_free(cb);
+}
+
+static gboolean finalize_config(struct a2dp_setup *s)
+{
+ GSList *l;
+ struct avdtp_stream *stream = s->err ? NULL : s->stream;
+
+ for (l = s->cb; l != NULL; ) {
+ struct a2dp_setup_cb *cb = l->data;
+
+ l = l->next;
+
+ if (!cb->config_cb)
+ continue;
+
+ cb->config_cb(s->session, s->sep, stream, s->err,
+ cb->user_data);
+ setup_cb_free(cb);
+ }
+
+ return FALSE;
+}
+
+static gboolean finalize_config_errno(struct a2dp_setup *s, int err)
+{
+ struct avdtp_error avdtp_err;
+
+ avdtp_error_init(&avdtp_err, AVDTP_ERRNO, -err);
+ s->err = err ? &avdtp_err : NULL;
+
+ return finalize_config(s);
+}
+
+static gboolean finalize_resume(struct a2dp_setup *s)
+{
+ GSList *l;
+
+ for (l = s->cb; l != NULL; ) {
+ struct a2dp_setup_cb *cb = l->data;
+
+ l = l->next;
+
+ if (!cb->resume_cb)
+ continue;
+
+ cb->resume_cb(s->session, s->err, cb->user_data);
+ setup_cb_free(cb);
+ }
+
+ return FALSE;
+}
+
+static gboolean finalize_resume_errno(struct a2dp_setup *s, int err)
+{
+ struct avdtp_error avdtp_err;
+
+ avdtp_error_init(&avdtp_err, AVDTP_ERRNO, -err);
+ s->err = err ? &avdtp_err : NULL;
+
+ return finalize_resume(s);
+}
+
+static gboolean finalize_suspend(struct a2dp_setup *s)
+{
+ GSList *l;
+
+ for (l = s->cb; l != NULL; ) {
+ struct a2dp_setup_cb *cb = l->data;
+
+ l = l->next;
+
+ if (!cb->suspend_cb)
+ continue;
+
+ cb->suspend_cb(s->session, s->err, cb->user_data);
+ setup_cb_free(cb);
+ }
+
+ return FALSE;
+}
+
+static gboolean finalize_suspend_errno(struct a2dp_setup *s, int err)
+{
+ struct avdtp_error avdtp_err;
+
+ avdtp_error_init(&avdtp_err, AVDTP_ERRNO, -err);
+ s->err = err ? &avdtp_err : NULL;
+
+ return finalize_suspend(s);
+}
+
+static gboolean finalize_select(struct a2dp_setup *s)
+{
+ GSList *l;
+
+ for (l = s->cb; l != NULL; ) {
+ struct a2dp_setup_cb *cb = l->data;
+
+ l = l->next;
+
+ if (!cb->select_cb)
+ continue;
+
+ cb->select_cb(s->session, s->sep, s->caps, cb->user_data);
+ setup_cb_free(cb);
+ }
+
+ return FALSE;
+}
+
+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 *a2dp_setup_get(struct avdtp *session)
+{
+ struct a2dp_setup *setup;
+
+ setup = find_setup_by_session(session);
+ if (!setup) {
+ setup = setup_new(session);
+ if (!setup)
+ return NULL;
+ }
+
+ return setup_ref(setup);
+}
+
+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;
+
+ 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;
+ }
+
+ if (sep->endpoint)
+ media_endpoint_clear_configuration(sep->endpoint);
+
+ sep->stream = NULL;
+
+}
+
+static gboolean auto_config(gpointer data)
+{
+ struct a2dp_setup *setup = data;
+ struct avdtp_error *err = NULL;
+
+ /* Check if configuration was aborted */
+ if (setup->sep->stream == NULL)
+ return FALSE;
+
+ if (setup->err != NULL) {
+ err = setup->err;
+ goto done;
+ }
+
+ avdtp_stream_add_cb(setup->session, setup->stream,
+ stream_state_changed, setup->sep);
+
+ if (setup->sep->type == AVDTP_SEP_TYPE_SOURCE)
+ sink_new_stream(setup->dev, setup->session, setup->stream);
+ else
+ source_new_stream(setup->dev, setup->session, setup->stream);
+
+done:
+ if (setup->setconf_cb)
+ setup->setconf_cb(setup->session, setup->stream, setup->err);
+
+ finalize_config(setup);
+
+ if (err)
+ g_free(err);
+
+ setup_unref(setup);
+
+ return FALSE;
+}
+
+static gboolean sbc_setconf_ind(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream,
+ GSList *caps,
+ avdtp_set_configuration_cb cb,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_setup *setup;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ DBG("Sink %p: Set_Configuration_Ind", sep);
+ else
+ DBG("Source %p: Set_Configuration_Ind", sep);
+
+ setup = a2dp_setup_get(session);
+ if (!setup)
+ return FALSE;
+
+ a2dp_sep->stream = stream;
+ setup->sep = a2dp_sep;
+ setup->stream = stream;
+ setup->setconf_cb = cb;
+
+ /* Check valid settings */
+ for (; caps != NULL; caps = g_slist_next(caps)) {
+ struct avdtp_service_capability *cap = caps->data;
+ struct avdtp_media_codec_capability *codec_cap;
+ struct sbc_codec_cap *sbc_cap;
+
+ if (cap->category == AVDTP_DELAY_REPORTING &&
+ !a2dp_sep->delay_reporting) {
+ setup->err = g_new(struct avdtp_error, 1);
+ avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING,
+ AVDTP_UNSUPPORTED_CONFIGURATION);
+ goto done;
+ }
+
+ if (cap->category != AVDTP_MEDIA_CODEC)
+ continue;
+
+ if (cap->length < sizeof(struct sbc_codec_cap))
+ continue;
+
+ codec_cap = (void *) cap->data;
+
+ if (codec_cap->media_codec_type != A2DP_CODEC_SBC)
+ continue;
+
+ sbc_cap = (void *) codec_cap;
+
+ if (sbc_cap->min_bitpool < MIN_BITPOOL ||
+ sbc_cap->max_bitpool > MAX_BITPOOL) {
+ setup->err = g_new(struct avdtp_error, 1);
+ avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC,
+ AVDTP_UNSUPPORTED_CONFIGURATION);
+ goto done;
+ }
+ }
+
+done:
+ g_idle_add(auto_config, setup);
+ return TRUE;
+}
+
+static gboolean sbc_getcap_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+ gboolean get_all, GSList **caps, uint8_t *err,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct avdtp_service_capability *media_transport, *media_codec;
+ struct sbc_codec_cap sbc_cap;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ DBG("Sink %p: Get_Capability_Ind", sep);
+ else
+ DBG("Source %p: Get_Capability_Ind", sep);
+
+ *caps = NULL;
+
+ media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+ NULL, 0);
+
+ *caps = g_slist_append(*caps, media_transport);
+
+ 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);
+
+ if (get_all) {
+ struct avdtp_service_capability *delay_reporting;
+ delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+ NULL, 0);
+ *caps = g_slist_append(*caps, delay_reporting);
+ }
+
+ return TRUE;
+}
+
+static gboolean mpeg_setconf_ind(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream,
+ GSList *caps,
+ avdtp_set_configuration_cb cb,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_setup *setup;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ DBG("Sink %p: Set_Configuration_Ind", sep);
+ else
+ DBG("Source %p: Set_Configuration_Ind", sep);
+
+ setup = a2dp_setup_get(session);
+ if (!setup)
+ return FALSE;
+
+ a2dp_sep->stream = stream;
+ setup->sep = a2dp_sep;
+ setup->stream = stream;
+ setup->setconf_cb = cb;
+
+ for (; caps != NULL; caps = g_slist_next(caps)) {
+ struct avdtp_service_capability *cap = caps->data;
+
+ if (cap->category == AVDTP_DELAY_REPORTING &&
+ !a2dp_sep->delay_reporting) {
+ setup->err = g_new(struct avdtp_error, 1);
+ avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING,
+ AVDTP_UNSUPPORTED_CONFIGURATION);
+ goto done;
+ }
+ }
+
+done:
+ g_idle_add(auto_config, setup);
+ return TRUE;
+}
+
+static gboolean mpeg_getcap_ind(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ gboolean get_all,
+ GSList **caps, uint8_t *err, void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct avdtp_service_capability *media_transport, *media_codec;
+ struct mpeg_codec_cap mpeg_cap;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ DBG("Sink %p: Get_Capability_Ind", sep);
+ else
+ DBG("Source %p: Get_Capability_Ind", sep);
+
+ *caps = NULL;
+
+ media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+ NULL, 0);
+
+ *caps = g_slist_append(*caps, media_transport);
+
+ 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);
+
+ if (get_all) {
+ struct avdtp_service_capability *delay_reporting;
+ delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+ NULL, 0);
+ *caps = g_slist_append(*caps, delay_reporting);
+ }
+
+ return TRUE;
+}
+
+static void endpoint_setconf_cb(struct media_endpoint *endpoint, void *ret,
+ int size, void *user_data)
+{
+ struct a2dp_setup *setup = user_data;
+
+ if (ret == NULL) {
+ setup->err = g_new(struct avdtp_error, 1);
+ avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC,
+ AVDTP_UNSUPPORTED_CONFIGURATION);
+ }
+
+ auto_config(setup);
+}
+
+static gboolean endpoint_setconf_ind(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream,
+ GSList *caps,
+ avdtp_set_configuration_cb cb,
+ void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct a2dp_setup *setup;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ DBG("Sink %p: Set_Configuration_Ind", sep);
+ else
+ DBG("Source %p: Set_Configuration_Ind", sep);
+
+ setup = a2dp_setup_get(session);
+ if (!session)
+ return FALSE;
+
+ a2dp_sep->stream = stream;
+ setup->sep = a2dp_sep;
+ setup->stream = stream;
+ setup->setconf_cb = cb;
+
+ for (; caps != NULL; caps = g_slist_next(caps)) {
+ struct avdtp_service_capability *cap = caps->data;
+ struct avdtp_media_codec_capability *codec;
+ gboolean ret;
+
+ if (cap->category == AVDTP_DELAY_REPORTING &&
+ !a2dp_sep->delay_reporting) {
+ setup->err = g_new(struct avdtp_error, 1);
+ avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING,
+ AVDTP_UNSUPPORTED_CONFIGURATION);
+ goto done;
+ }
+
+ if (cap->category != AVDTP_MEDIA_CODEC)
+ continue;
+
+ codec = (struct avdtp_media_codec_capability *) cap->data;
+
+ if (codec->media_codec_type != a2dp_sep->codec) {
+ setup->err = g_new(struct avdtp_error, 1);
+ avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC,
+ AVDTP_UNSUPPORTED_CONFIGURATION);
+ goto done;
+ }
+
+ ret = media_endpoint_set_configuration(a2dp_sep->endpoint,
+ setup->dev, codec->data,
+ cap->length - sizeof(*codec),
+ endpoint_setconf_cb, setup);
+ if (ret)
+ return TRUE;
+
+ avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC,
+ AVDTP_UNSUPPORTED_CONFIGURATION);
+ break;
+ }
+
+done:
+ g_idle_add(auto_config, setup);
+ return TRUE;
+}
+
+static gboolean endpoint_getcap_ind(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ gboolean get_all, GSList **caps,
+ uint8_t *err, void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct avdtp_service_capability *media_transport, *media_codec;
+ struct avdtp_media_codec_capability *codec_caps;
+ uint8_t *capabilities;
+ size_t length;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ DBG("Sink %p: Get_Capability_Ind", sep);
+ else
+ DBG("Source %p: Get_Capability_Ind", sep);
+
+ *caps = NULL;
+
+ media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+ NULL, 0);
+
+ *caps = g_slist_append(*caps, media_transport);
+
+ length = media_endpoint_get_capabilities(a2dp_sep->endpoint,
+ &capabilities);
+
+ codec_caps = g_malloc0(sizeof(*codec_caps) + length);
+ codec_caps->media_type = AVDTP_MEDIA_TYPE_AUDIO;
+ codec_caps->media_codec_type = a2dp_sep->codec;
+ memcpy(codec_caps->data, capabilities, length);
+
+ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, codec_caps,
+ sizeof(*codec_caps) + length);
+
+ *caps = g_slist_append(*caps, media_codec);
+ g_free(codec_caps);
+
+ if (get_all) {
+ struct avdtp_service_capability *delay_reporting;
+ delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+ NULL, 0);
+ *caps = g_slist_append(*caps, delay_reporting);
+ }
+
+ return TRUE;
+}
+
+static void endpoint_open_cb(struct media_endpoint *endpoint, void *ret,
+ int size, void *user_data)
+{
+ struct a2dp_setup *setup = user_data;
+ int err;
+
+ if (ret == NULL) {
+ setup->stream = NULL;
+ finalize_config_errno(setup, -EPERM);
+ return;
+ }
+
+ err = avdtp_open(setup->session, setup->stream);
+ if (err == 0)
+ return;
+
+ error("Error on avdtp_open %s (%d)", strerror(-err), -err);
+ setup->stream = NULL;
+ finalize_config_errno(setup, err);
+}
+
+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)
+ DBG("Sink %p: Set_Configuration_Cfm", sep);
+ else
+ DBG("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 D-Bus interface of the new stream */
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE)
+ sink_new_stream(dev, session, setup->stream);
+ else
+ source_new_stream(dev, session, setup->stream);
+
+ /* Notify Endpoint */
+ if (a2dp_sep->endpoint) {
+ struct avdtp_service_capability *service;
+ struct avdtp_media_codec_capability *codec;
+
+ service = avdtp_stream_get_codec(stream);
+ codec = (struct avdtp_media_codec_capability *) service->data;
+
+ if (media_endpoint_set_configuration(a2dp_sep->endpoint, dev,
+ codec->data, service->length -
+ sizeof(*codec),
+ endpoint_open_cb, setup) ==
+ TRUE)
+ return;
+
+ setup->stream = NULL;
+ finalize_config_errno(setup, -EPERM);
+ return;
+ }
+
+ 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)
+ DBG("Sink %p: Get_Configuration_Ind", sep);
+ else
+ DBG("Source %p: Get_Configuration_Ind", sep);
+ 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)
+ DBG("Sink %p: Set_Configuration_Cfm", sep);
+ else
+ DBG("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)
+ DBG("Sink %p: Open_Ind", sep);
+ else
+ DBG("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)
+ DBG("Sink %p: Open_Cfm", sep);
+ else
+ DBG("Source %p: Open_Cfm", sep);
+
+ setup = find_setup_by_session(session);
+ if (!setup)
+ return;
+
+ if (setup->reconfigure)
+ setup->reconfigure = FALSE;
+
+ if (err) {
+ setup->stream = NULL;
+ setup->err = err;
+ }
+
+ finalize_config(setup);
+}
+
+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;
+ struct a2dp_setup *setup;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ DBG("Sink %p: Start_Ind", sep);
+ else
+ DBG("Source %p: Start_Ind", sep);
+
+ setup = find_setup_by_session(session);
+ if (setup)
+ finalize_resume(setup);
+
+ if (!a2dp_sep->locked) {
+ a2dp_sep->session = avdtp_ref(session);
+ a2dp_sep->suspend_timer = g_timeout_add_seconds(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)
+ DBG("Sink %p: Start_Cfm", sep);
+ else
+ DBG("Source %p: Start_Cfm", sep);
+
+ setup = find_setup_by_session(session);
+ if (!setup)
+ return;
+
+ if (err) {
+ setup->stream = NULL;
+ setup->err = err;
+ }
+
+ finalize_resume(setup);
+}
+
+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)
+ DBG("Sink %p: Suspend_Ind", sep);
+ else
+ DBG("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)
+ DBG("Sink %p: Suspend_Cfm", sep);
+ else
+ DBG("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_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;
+ struct a2dp_setup *setup;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ DBG("Sink %p: Close_Ind", sep);
+ else
+ DBG("Source %p: Close_Ind", sep);
+
+ setup = find_setup_by_session(session);
+ if (!setup)
+ return TRUE;
+
+ finalize_suspend_errno(setup, -ECONNRESET);
+ finalize_resume_errno(setup, -ECONNRESET);
+
+ return TRUE;
+}
+
+static gboolean a2dp_reconfigure(gpointer data)
+{
+ struct a2dp_setup *setup = data;
+ struct a2dp_sep *sep = setup->sep;
+ int posix_err;
+ struct avdtp_media_codec_capability *rsep_codec;
+ struct avdtp_service_capability *cap;
+
+ if (setup->rsep) {
+ cap = avdtp_get_codec(setup->rsep);
+ rsep_codec = (struct avdtp_media_codec_capability *) cap->data;
+ }
+
+ if (!setup->rsep || sep->codec != rsep_codec->media_codec_type)
+ setup->rsep = avdtp_find_remote_sep(setup->session, sep->lsep);
+
+ posix_err = avdtp_set_configuration(setup->session, setup->rsep,
+ sep->lsep,
+ setup->caps,
+ &setup->stream);
+ if (posix_err < 0) {
+ error("avdtp_set_configuration: %s", strerror(-posix_err));
+ goto failed;
+ }
+
+ return FALSE;
+
+failed:
+ 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)
+ DBG("Sink %p: Close_Cfm", sep);
+ else
+ DBG("Source %p: Close_Cfm", sep);
+
+ setup = find_setup_by_session(session);
+ if (!setup)
+ return;
+
+ if (err) {
+ setup->stream = NULL;
+ setup->err = err;
+ finalize_config(setup);
+ return;
+ }
+
+ if (!setup->rsep)
+ setup->rsep = avdtp_stream_get_remote_sep(stream);
+
+ 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)
+ DBG("Sink %p: Abort_Ind", sep);
+ else
+ DBG("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)
+ DBG("Sink %p: Abort_Cfm", sep);
+ else
+ DBG("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)
+ DBG("Sink %p: ReConfigure_Ind", sep);
+ else
+ DBG("Source %p: ReConfigure_Ind", sep);
+
+ return TRUE;
+}
+
+static gboolean delayreport_ind(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ uint8_t rseid, uint16_t delay,
+ uint8_t *err, void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct audio_device *dev = a2dp_get_dev(session);
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ DBG("Sink %p: DelayReport_Ind", sep);
+ else
+ DBG("Source %p: DelayReport_Ind", sep);
+
+ unix_delay_report(dev, rseid, delay);
+
+ return TRUE;
+}
+
+static gboolean endpoint_delayreport_ind(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ uint8_t rseid, uint16_t delay,
+ uint8_t *err, void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct media_transport *transport;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ DBG("Sink %p: DelayReport_Ind", sep);
+ else
+ DBG("Source %p: DelayReport_Ind", sep);
+
+ transport = media_endpoint_get_transport(a2dp_sep->endpoint);
+ if (transport == NULL)
+ return FALSE;
+
+ media_transport_update_delay(transport, delay);
+
+ 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)
+ DBG("Sink %p: ReConfigure_Cfm", sep);
+ else
+ DBG("Source %p: ReConfigure_Cfm", sep);
+
+ setup = find_setup_by_session(session);
+ if (!setup)
+ return;
+
+ if (err) {
+ setup->stream = NULL;
+ setup->err = err;
+ }
+
+ finalize_config(setup);
+}
+
+static void delay_report_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)
+ DBG("Sink %p: DelayReport_Cfm", sep);
+ else
+ DBG("Source %p: DelayReport_Cfm", sep);
+}
+
+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,
+ .delay_report = delay_report_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,
+ .delayreport = delayreport_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,
+ .delayreport = delayreport_ind,
+};
+
+static struct avdtp_sep_ind endpoint_ind = {
+ .get_capability = endpoint_getcap_ind,
+ .set_configuration = endpoint_setconf_ind,
+ .get_configuration = getconf_ind,
+ .open = open_ind,
+ .start = start_ind,
+ .suspend = suspend_ind,
+ .close = close_ind,
+ .abort = abort_ind,
+ .reconfigure = reconf_ind,
+ .delayreport = endpoint_delayreport_ind,
+};
+
+static sdp_record_t *a2dp_record(uint8_t type, uint16_t avdtp_ver)
+{
+ sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+ uuid_t root_uuid, l2cap_uuid, avdtp_uuid, a2dp_uuid;
+ 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;
+ uint16_t a2dp_ver = 0x0102, 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);
+
+ if (type == AVDTP_SEP_TYPE_SOURCE)
+ sdp_uuid16_create(&a2dp_uuid, AUDIO_SOURCE_SVCLASS_ID);
+ else
+ sdp_uuid16_create(&a2dp_uuid, AUDIO_SINK_SVCLASS_ID);
+ svclass_id = sdp_list_append(0, &a2dp_uuid);
+ sdp_set_service_classes(record, svclass_id);
+
+ sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID);
+ profile[0].version = a2dp_ver;
+ pfseq = sdp_list_append(0, &profile[0]);
+ sdp_set_profile_descs(record, pfseq);
+
+ sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+ proto[0] = sdp_list_append(0, &l2cap_uuid);
+ 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_uuid, AVDTP_UUID);
+ proto[1] = sdp_list_append(0, &avdtp_uuid);
+ version = sdp_data_alloc(SDP_UINT16, &avdtp_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);
+
+ if (type == AVDTP_SEP_TYPE_SOURCE)
+ sdp_set_info_attr(record, "Audio Source", 0, 0);
+ else
+ sdp_set_info_attr(record, "Audio Sink", 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 struct a2dp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+ GSList *l;
+
+ for (l = list; l; l = l->next) {
+ struct a2dp_server *server = l->data;
+
+ if (bacmp(&server->src, src) == 0)
+ return server;
+ }
+
+ return NULL;
+}
+
+int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
+{
+ int sbc_srcs = 1, sbc_sinks = 1;
+ int mpeg12_srcs = 0, mpeg12_sinks = 0;
+ gboolean source = TRUE, sink = FALSE, socket = TRUE;
+ gboolean delay_reporting = FALSE;
+ char *str;
+ GError *err = NULL;
+ int i;
+ struct a2dp_server *server;
+
+ if (!config)
+ goto proceed;
+
+ str = g_key_file_get_string(config, "General", "Enable", &err);
+
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else {
+ if (strstr(str, "Sink"))
+ source = TRUE;
+ if (strstr(str, "Source"))
+ sink = TRUE;
+ g_free(str);
+ }
+
+ str = g_key_file_get_string(config, "General", "Disable", &err);
+
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else {
+ if (strstr(str, "Sink"))
+ source = FALSE;
+ if (strstr(str, "Source"))
+ sink = FALSE;
+ if (strstr(str, "Socket"))
+ socket = FALSE;
+ g_free(str);
+ }
+
+ /* Don't register any local sep if Socket is disabled */
+ if (socket == FALSE) {
+ sbc_srcs = 0;
+ sbc_sinks = 0;
+ mpeg12_srcs = 0;
+ mpeg12_sinks = 0;
+ goto proceed;
+ }
+
+ str = g_key_file_get_string(config, "A2DP", "SBCSources", &err);
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else {
+ sbc_srcs = atoi(str);
+ g_free(str);
+ }
+
+ str = g_key_file_get_string(config, "A2DP", "MPEG12Sources", &err);
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else {
+ mpeg12_srcs = atoi(str);
+ g_free(str);
+ }
+
+ str = g_key_file_get_string(config, "A2DP", "SBCSinks", &err);
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else {
+ sbc_sinks = atoi(str);
+ g_free(str);
+ }
+
+ str = g_key_file_get_string(config, "A2DP", "MPEG12Sinks", &err);
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else {
+ mpeg12_sinks = atoi(str);
+ g_free(str);
+ }
+
+proceed:
+ if (!connection)
+ connection = dbus_connection_ref(conn);
+
+ server = find_server(servers, src);
+ if (!server) {
+ int av_err;
+
+ server = g_new0(struct a2dp_server, 1);
+ if (!server)
+ return -ENOMEM;
+
+ av_err = avdtp_init(src, config, &server->version);
+ if (av_err < 0) {
+ g_free(server);
+ return av_err;
+ }
+
+ bacpy(&server->src, src);
+ servers = g_slist_append(servers, server);
+ }
+
+ if (config)
+ delay_reporting = g_key_file_get_boolean(config, "A2DP",
+ "DelayReporting", NULL);
+
+ if (delay_reporting)
+ server->version = 0x0103;
+ else
+ server->version = 0x0102;
+
+ server->source_enabled = source;
+ if (source) {
+ for (i = 0; i < sbc_srcs; i++)
+ a2dp_add_sep(src, AVDTP_SEP_TYPE_SOURCE,
+ A2DP_CODEC_SBC, delay_reporting, NULL, NULL);
+
+ for (i = 0; i < mpeg12_srcs; i++)
+ a2dp_add_sep(src, AVDTP_SEP_TYPE_SOURCE,
+ A2DP_CODEC_MPEG12, delay_reporting,
+ NULL, NULL);
+ }
+ server->sink_enabled = sink;
+ if (sink) {
+ for (i = 0; i < sbc_sinks; i++)
+ a2dp_add_sep(src, AVDTP_SEP_TYPE_SINK,
+ A2DP_CODEC_SBC, delay_reporting, NULL, NULL);
+
+ for (i = 0; i < mpeg12_sinks; i++)
+ a2dp_add_sep(src, AVDTP_SEP_TYPE_SINK,
+ A2DP_CODEC_MPEG12, delay_reporting,
+ NULL, NULL);
+ }
+
+ return 0;
+}
+
+static void a2dp_unregister_sep(struct a2dp_sep *sep)
+{
+ if (sep->endpoint) {
+ media_endpoint_release(sep->endpoint);
+ sep->endpoint = NULL;
+ }
+
+ avdtp_unregister_sep(sep->lsep);
+ g_free(sep);
+}
+
+void a2dp_unregister(const bdaddr_t *src)
+{
+ struct a2dp_server *server;
+
+ server = find_server(servers, src);
+ if (!server)
+ return;
+
+ g_slist_foreach(server->sinks, (GFunc) a2dp_remove_sep, NULL);
+ g_slist_free(server->sinks);
+
+ g_slist_foreach(server->sources, (GFunc) a2dp_remove_sep, NULL);
+ g_slist_free(server->sources);
+
+ avdtp_exit(src);
+
+ servers = g_slist_remove(servers, server);
+ g_free(server);
+
+ if (servers)
+ return;
+
+ dbus_connection_unref(connection);
+ connection = NULL;
+}
+
+struct a2dp_sep *a2dp_add_sep(const bdaddr_t *src, uint8_t type,
+ uint8_t codec, gboolean delay_reporting,
+ struct media_endpoint *endpoint, int *err)
+{
+ struct a2dp_server *server;
+ struct a2dp_sep *sep;
+ GSList **l;
+ uint32_t *record_id;
+ sdp_record_t *record;
+ struct avdtp_sep_ind *ind;
+
+ server = find_server(servers, src);
+ if (server == NULL) {
+ if (err)
+ *err = -EINVAL;
+ return NULL;
+ }
+
+ if (type == AVDTP_SEP_TYPE_SINK && !server->sink_enabled) {
+ if (err)
+ *err = -EPROTONOSUPPORT;
+ return NULL;
+ }
+
+ if (type == AVDTP_SEP_TYPE_SOURCE && !server->source_enabled) {
+ if (err)
+ *err = -EPROTONOSUPPORT;
+ return NULL;
+ }
+
+ sep = g_new0(struct a2dp_sep, 1);
+
+ if (endpoint) {
+ ind = &endpoint_ind;
+ goto proceed;
+ }
+
+ ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind;
+
+proceed:
+ sep->lsep = avdtp_register_sep(&server->src, type,
+ AVDTP_MEDIA_TYPE_AUDIO, codec,
+ delay_reporting, ind, &cfm, sep);
+ if (sep->lsep == NULL) {
+ g_free(sep);
+ if (err)
+ *err = -EINVAL;
+ return NULL;
+ }
+
+ sep->server = server;
+ sep->endpoint = endpoint;
+ sep->codec = codec;
+ sep->type = type;
+ sep->delay_reporting = delay_reporting;
+
+ if (type == AVDTP_SEP_TYPE_SOURCE) {
+ l = &server->sources;
+ record_id = &server->source_record_id;
+ } else {
+ l = &server->sinks;
+ record_id = &server->sink_record_id;
+ }
+
+ if (*record_id != 0)
+ goto add;
+
+ record = a2dp_record(type, server->version);
+ if (!record) {
+ error("Unable to allocate new service record");
+ avdtp_unregister_sep(sep->lsep);
+ g_free(sep);
+ if (err)
+ *err = -EINVAL;
+ return NULL;
+ }
+
+ if (add_record_to_server(&server->src, record) < 0) {
+ error("Unable to register A2DP service record");\
+ sdp_record_free(record);
+ avdtp_unregister_sep(sep->lsep);
+ g_free(sep);
+ if (err)
+ *err = -EINVAL;
+ return NULL;
+ }
+ *record_id = record->handle;
+
+add:
+ *l = g_slist_append(*l, sep);
+
+ if (err)
+ *err = 0;
+ return sep;
+}
+
+void a2dp_remove_sep(struct a2dp_sep *sep)
+{
+ struct a2dp_server *server = sep->server;
+
+ if (sep->type == AVDTP_SEP_TYPE_SOURCE) {
+ if (g_slist_find(server->sources, sep) == NULL)
+ return;
+ server->sources = g_slist_remove(server->sources, sep);
+ if (server->sources == NULL && server->source_record_id) {
+ remove_record_from_server(server->source_record_id);
+ server->source_record_id = 0;
+ }
+ } else {
+ if (g_slist_find(server->sinks, sep) == NULL)
+ return;
+ server->sinks = g_slist_remove(server->sinks, sep);
+ if (server->sinks == NULL && server->sink_record_id) {
+ remove_record_from_server(server->sink_record_id);
+ server->sink_record_id = 0;
+ }
+ }
+
+ a2dp_unregister_sep(sep);
+}
+
+struct a2dp_sep *a2dp_get(struct avdtp *session,
+ struct avdtp_remote_sep *rsep)
+{
+ GSList *l;
+ struct a2dp_server *server;
+ struct avdtp_service_capability *cap;
+ struct avdtp_media_codec_capability *codec_cap = NULL;
+ bdaddr_t src;
+
+ avdtp_get_peers(session, &src, NULL);
+ server = find_server(servers, &src);
+ if (!server)
+ return NULL;
+
+ cap = avdtp_get_codec(rsep);
+ codec_cap = (void *) cap->data;
+
+ if (avdtp_get_type(rsep) == AVDTP_SEP_TYPE_SINK)
+ l = server->sources;
+ else
+ l = server->sinks;
+
+ for (; l != NULL; l = l->next) {
+ struct a2dp_sep *sep = l->data;
+
+ if (sep->locked)
+ continue;
+
+ if (sep->codec != codec_cap->media_codec_type)
+ continue;
+
+ if (!sep->stream || avdtp_has_stream(session, sep->stream))
+ return sep;
+ }
+
+ return NULL;
+}
+
+static uint8_t default_bitpool(uint8_t freq, uint8_t mode)
+{
+ switch (freq) {
+ case SBC_SAMPLING_FREQ_16000:
+ case SBC_SAMPLING_FREQ_32000:
+ return 53;
+ case SBC_SAMPLING_FREQ_44100:
+ switch (mode) {
+ case SBC_CHANNEL_MODE_MONO:
+ case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+ return 31;
+ case SBC_CHANNEL_MODE_STEREO:
+ case SBC_CHANNEL_MODE_JOINT_STEREO:
+ return 53;
+ default:
+ error("Invalid channel mode %u", mode);
+ return 53;
+ }
+ case SBC_SAMPLING_FREQ_48000:
+ switch (mode) {
+ case SBC_CHANNEL_MODE_MONO:
+ case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+ return 29;
+ case SBC_CHANNEL_MODE_STEREO:
+ case SBC_CHANNEL_MODE_JOINT_STEREO:
+ return 51;
+ default:
+ error("Invalid channel mode %u", mode);
+ return 51;
+ }
+ default:
+ error("Invalid sampling freq %u", freq);
+ return 53;
+ }
+}
+
+static gboolean select_sbc_params(struct sbc_codec_cap *cap,
+ struct sbc_codec_cap *supported)
+{
+ unsigned int max_bitpool, min_bitpool;
+
+ memset(cap, 0, sizeof(struct sbc_codec_cap));
+
+ cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+ cap->cap.media_codec_type = A2DP_CODEC_SBC;
+
+ if (supported->frequency & SBC_SAMPLING_FREQ_44100)
+ cap->frequency = SBC_SAMPLING_FREQ_44100;
+ else if (supported->frequency & SBC_SAMPLING_FREQ_48000)
+ cap->frequency = SBC_SAMPLING_FREQ_48000;
+ else if (supported->frequency & SBC_SAMPLING_FREQ_32000)
+ cap->frequency = SBC_SAMPLING_FREQ_32000;
+ else if (supported->frequency & SBC_SAMPLING_FREQ_16000)
+ cap->frequency = SBC_SAMPLING_FREQ_16000;
+ else {
+ error("No supported frequencies");
+ return FALSE;
+ }
+
+ if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
+ cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
+ else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO)
+ cap->channel_mode = SBC_CHANNEL_MODE_STEREO;
+ else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
+ cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
+ else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO)
+ cap->channel_mode = SBC_CHANNEL_MODE_MONO;
+ else {
+ error("No supported channel modes");
+ return FALSE;
+ }
+
+ if (supported->block_length & SBC_BLOCK_LENGTH_16)
+ cap->block_length = SBC_BLOCK_LENGTH_16;
+ else if (supported->block_length & SBC_BLOCK_LENGTH_12)
+ cap->block_length = SBC_BLOCK_LENGTH_12;
+ else if (supported->block_length & SBC_BLOCK_LENGTH_8)
+ cap->block_length = SBC_BLOCK_LENGTH_8;
+ else if (supported->block_length & SBC_BLOCK_LENGTH_4)
+ cap->block_length = SBC_BLOCK_LENGTH_4;
+ else {
+ error("No supported block lengths");
+ return FALSE;
+ }
+
+ if (supported->subbands & SBC_SUBBANDS_8)
+ cap->subbands = SBC_SUBBANDS_8;
+ else if (supported->subbands & SBC_SUBBANDS_4)
+ cap->subbands = SBC_SUBBANDS_4;
+ else {
+ error("No supported subbands");
+ return FALSE;
+ }
+
+ if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS)
+ cap->allocation_method = SBC_ALLOCATION_LOUDNESS;
+ else if (supported->allocation_method & SBC_ALLOCATION_SNR)
+ cap->allocation_method = SBC_ALLOCATION_SNR;
+
+ min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool);
+ max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode),
+ supported->max_bitpool);
+
+ cap->min_bitpool = min_bitpool;
+ cap->max_bitpool = max_bitpool;
+
+ return TRUE;
+}
+
+static gboolean select_capabilities(struct avdtp *session,
+ struct avdtp_remote_sep *rsep,
+ GSList **caps)
+{
+ struct avdtp_service_capability *media_transport, *media_codec;
+ struct sbc_codec_cap sbc_cap;
+
+ media_codec = avdtp_get_codec(rsep);
+ if (!media_codec)
+ return FALSE;
+
+ select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data);
+
+ media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+ NULL, 0);
+
+ *caps = g_slist_append(*caps, media_transport);
+
+ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
+ sizeof(sbc_cap));
+
+ *caps = g_slist_append(*caps, media_codec);
+
+ if (avdtp_get_delay_reporting(rsep)) {
+ struct avdtp_service_capability *delay_reporting;
+ delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+ NULL, 0);
+ *caps = g_slist_append(*caps, delay_reporting);
+ }
+
+ return TRUE;
+}
+
+static void select_cb(struct media_endpoint *endpoint, void *ret, int size,
+ void *user_data)
+{
+ struct a2dp_setup *setup = user_data;
+ struct avdtp_service_capability *media_transport, *media_codec;
+ struct avdtp_media_codec_capability *cap;
+
+ if (size < 0) {
+ DBG("Endpoint replied an invalid configuration");
+ goto done;
+ }
+
+ media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+ NULL, 0);
+
+ setup->caps = g_slist_append(setup->caps, media_transport);
+
+ cap = g_malloc0(sizeof(*cap) + size);
+ cap->media_type = AVDTP_MEDIA_TYPE_AUDIO;
+ cap->media_codec_type = setup->sep->codec;
+ memcpy(cap->data, ret, size);
+
+ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, cap,
+ sizeof(*cap) + size);
+
+ setup->caps = g_slist_append(setup->caps, media_codec);
+ g_free(cap);
+
+done:
+ finalize_select(setup);
+}
+
+static gboolean auto_select(gpointer data)
+{
+ struct a2dp_setup *setup = data;
+
+ finalize_select(setup);
+
+ return FALSE;
+}
+
+static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list,
+ const char *sender)
+{
+ for (; list; list = list->next) {
+ struct a2dp_sep *sep = list->data;
+
+ /* Use sender's endpoint if available */
+ if (sender) {
+ const char *name;
+
+ if (sep->endpoint == NULL)
+ continue;
+
+ name = media_endpoint_get_sender(sep->endpoint);
+ if (g_strcmp0(sender, name) != 0)
+ continue;
+ }
+
+ if (avdtp_find_remote_sep(session, sep->lsep) == NULL)
+ continue;
+
+ return sep;
+ }
+
+ return NULL;
+}
+
+static struct a2dp_sep *a2dp_select_sep(struct avdtp *session, uint8_t type,
+ const char *sender)
+{
+ struct a2dp_server *server;
+ struct a2dp_sep *sep;
+ GSList *l;
+ bdaddr_t src;
+
+ avdtp_get_peers(session, &src, NULL);
+ server = find_server(servers, &src);
+ if (!server)
+ return NULL;
+
+ l = type == AVDTP_SEP_TYPE_SINK ? server->sources : server->sinks;
+
+ /* Check sender's seps first */
+ sep = a2dp_find_sep(session, l, sender);
+ if (sep != NULL)
+ return sep;
+
+ return a2dp_find_sep(session, l, NULL);
+}
+
+unsigned int a2dp_select_capabilities(struct avdtp *session,
+ uint8_t type, const char *sender,
+ a2dp_select_cb_t cb,
+ void *user_data)
+{
+ struct a2dp_setup *setup;
+ struct a2dp_setup_cb *cb_data;
+ struct a2dp_sep *sep;
+ struct avdtp_service_capability *service;
+ struct avdtp_media_codec_capability *codec;
+
+ sep = a2dp_select_sep(session, type, sender);
+ if (!sep) {
+ error("Unable to select SEP");
+ return 0;
+ }
+
+ setup = a2dp_setup_get(session);
+ if (!setup)
+ return 0;
+
+ cb_data = setup_cb_new(setup);
+ cb_data->select_cb = cb;
+ cb_data->user_data = user_data;
+
+ setup->sep = sep;
+ setup->rsep = avdtp_find_remote_sep(session, sep->lsep);
+
+ if (setup->rsep == NULL) {
+ error("Could not find remote sep");
+ goto fail;
+ }
+
+ /* FIXME: Remove auto select when it is not longer possible to register
+ endpoint in the configuration file */
+ if (sep->endpoint == NULL) {
+ if (!select_capabilities(session, setup->rsep,
+ &setup->caps)) {
+ error("Unable to auto select remote SEP capabilities");
+ goto fail;
+ }
+
+ g_idle_add(auto_select, setup);
+
+ return cb_data->id;
+ }
+
+ service = avdtp_get_codec(setup->rsep);
+ codec = (struct avdtp_media_codec_capability *) service->data;
+
+ if (media_endpoint_select_configuration(sep->endpoint, codec->data,
+ service->length - sizeof(*codec),
+ select_cb, setup) ==
+ TRUE)
+ return cb_data->id;
+
+fail:
+ setup_cb_free(cb_data);
+ return 0;
+
+}
+
+unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
+ a2dp_config_cb_t cb, GSList *caps,
+ void *user_data)
+{
+ struct a2dp_setup_cb *cb_data;
+ GSList *l;
+ struct a2dp_server *server;
+ struct a2dp_setup *setup;
+ struct a2dp_sep *tmp;
+ struct avdtp_service_capability *cap;
+ struct avdtp_media_codec_capability *codec_cap = NULL;
+ int posix_err;
+ bdaddr_t src;
+ uint8_t remote_type;
+
+ avdtp_get_peers(session, &src, NULL);
+ server = find_server(servers, &src);
+ if (!server)
+ return 0;
+
+ 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;
+
+ if (sep->codec != codec_cap->media_codec_type)
+ return 0;
+
+ DBG("a2dp_config: selected SEP %p", sep->lsep);
+
+ setup = a2dp_setup_get(session);
+ if (!setup)
+ return 0;
+
+ cb_data = setup_cb_new(setup);
+ cb_data->config_cb = cb;
+ cb_data->user_data = user_data;
+
+ setup->sep = sep;
+ setup->stream = sep->stream;
+
+ /* Copy given caps if they are different than current caps */
+ if (setup->caps != caps) {
+ g_slist_foreach(setup->caps, (GFunc) g_free, NULL);
+ g_slist_free(setup->caps);
+ setup->caps = g_slist_copy(caps);
+ }
+
+ switch (avdtp_sep_get_state(sep->lsep)) {
+ case AVDTP_STATE_IDLE:
+ if (sep->type == AVDTP_SEP_TYPE_SOURCE) {
+ l = server->sources;
+ remote_type = AVDTP_SEP_TYPE_SINK;
+ } else {
+ remote_type = AVDTP_SEP_TYPE_SOURCE;
+ l = server->sinks;
+ }
+
+ for (; l != NULL; l = l->next) {
+ tmp = l->data;
+ if (avdtp_has_stream(session, tmp->stream))
+ break;
+ }
+
+ if (l != NULL) {
+ if (a2dp_sep_get_lock(tmp))
+ goto failed;
+ setup->reconfigure = TRUE;
+ if (avdtp_close(session, tmp->stream, FALSE) < 0) {
+ error("avdtp_close failed");
+ goto failed;
+ }
+ break;
+ }
+
+ setup->rsep = avdtp_find_remote_sep(session, sep->lsep);
+ if (setup->rsep == NULL) {
+ error("No matching ACP and INT SEPs found");
+ goto failed;
+ }
+
+ posix_err = avdtp_set_configuration(session, setup->rsep,
+ sep->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)) {
+ DBG("Configuration match: resuming");
+ g_idle_add((GSourceFunc) finalize_config, setup);
+ } else if (!setup->reconfigure) {
+ setup->reconfigure = TRUE;
+ if (avdtp_close(session, sep->stream, FALSE) < 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_cb_free(cb_data);
+ return 0;
+}
+
+unsigned int a2dp_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;
+
+ setup = a2dp_setup_get(session);
+ if (!setup)
+ return 0;
+
+ cb_data = setup_cb_new(setup);
+ cb_data->resume_cb = cb;
+ cb_data->user_data = user_data;
+
+ setup->sep = sep;
+ setup->stream = sep->stream;
+
+ switch (avdtp_sep_get_state(sep->lsep)) {
+ 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 for resume");
+ goto failed;
+ }
+
+ return cb_data->id;
+
+failed:
+ setup_cb_free(cb_data);
+ return 0;
+}
+
+unsigned int a2dp_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;
+
+ setup = a2dp_setup_get(session);
+ if (!setup)
+ return 0;
+
+ cb_data = setup_cb_new(setup);
+ cb_data->suspend_cb = cb;
+ cb_data->user_data = user_data;
+
+ setup->sep = sep;
+ setup->stream = sep->stream;
+
+ switch (avdtp_sep_get_state(sep->lsep)) {
+ case AVDTP_STATE_IDLE:
+ error("a2dp_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;
+ }
+ sep->suspending = TRUE;
+ break;
+ default:
+ error("SEP in bad state for suspend");
+ goto failed;
+ }
+
+ return cb_data->id;
+
+failed:
+ setup_cb_free(cb_data);
+ return 0;
+}
+
+gboolean a2dp_cancel(struct audio_device *dev, unsigned int id)
+{
+ struct a2dp_setup *setup;
+ GSList *l;
+
+ setup = find_setup_by_dev(dev);
+ if (!setup)
+ return FALSE;
+
+ for (l = setup->cb; l != NULL; l = g_slist_next(l)) {
+ struct a2dp_setup_cb *cb = l->data;
+
+ if (cb->id != id)
+ continue;
+
+ setup_ref(setup);
+ setup_cb_free(cb);
+
+ if (!setup->cb) {
+ DBG("aborting setup %p", setup);
+ avdtp_abort(setup->session, setup->stream);
+ return TRUE;
+ }
+
+ setup_unref(setup);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
+{
+ if (sep->locked)
+ return FALSE;
+
+ DBG("SEP %p locked", sep->lsep);
+ 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->lsep);
+
+ sep->locked = FALSE;
+
+ DBG("SEP %p unlocked", sep->lsep);
+
+ 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;
+}
+
+gboolean a2dp_sep_get_lock(struct a2dp_sep *sep)
+{
+ return sep->locked;
+}
+
+static int stream_cmp(gconstpointer data, gconstpointer user_data)
+{
+ const struct a2dp_sep *sep = data;
+ const struct avdtp_stream *stream = user_data;
+
+ return (sep->stream != stream);
+}
+
+struct a2dp_sep *a2dp_get_sep(struct avdtp *session,
+ struct avdtp_stream *stream)
+{
+ struct a2dp_server *server;
+ bdaddr_t src, dst;
+ GSList *l;
+
+ avdtp_get_peers(session, &src, &dst);
+
+ for (l = servers; l; l = l->next) {
+ server = l->data;
+
+ if (bacmp(&src, &server->src) == 0)
+ break;
+ }
+
+ if (!l)
+ return NULL;
+
+ l = g_slist_find_custom(server->sources, stream, stream_cmp);
+ if (l)
+ return l->data;
+
+ l = g_slist_find_custom(server->sinks, stream, stream_cmp);
+ if (l)
+ return l->data;
+
+ return NULL;
+}
+
+struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep)
+{
+ return sep->stream;
+}
diff --git a/audio/a2dp.h b/audio/a2dp.h
new file mode 100644
index 0000000..5c4232d
--- /dev/null
+++ b/audio/a2dp.h
@@ -0,0 +1,164 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define A2DP_CODEC_SBC 0x00
+#define A2DP_CODEC_MPEG12 0x01
+#define A2DP_CODEC_MPEG24 0x02
+#define A2DP_CODEC_ATRAC 0x03
+
+#define SBC_SAMPLING_FREQ_16000 (1 << 3)
+#define SBC_SAMPLING_FREQ_32000 (1 << 2)
+#define SBC_SAMPLING_FREQ_44100 (1 << 1)
+#define SBC_SAMPLING_FREQ_48000 1
+
+#define SBC_CHANNEL_MODE_MONO (1 << 3)
+#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2)
+#define SBC_CHANNEL_MODE_STEREO (1 << 1)
+#define SBC_CHANNEL_MODE_JOINT_STEREO 1
+
+#define SBC_BLOCK_LENGTH_4 (1 << 3)
+#define SBC_BLOCK_LENGTH_8 (1 << 2)
+#define SBC_BLOCK_LENGTH_12 (1 << 1)
+#define SBC_BLOCK_LENGTH_16 1
+
+#define SBC_SUBBANDS_4 (1 << 1)
+#define SBC_SUBBANDS_8 1
+
+#define SBC_ALLOCATION_SNR (1 << 1)
+#define SBC_ALLOCATION_LOUDNESS 1
+
+#define MPEG_CHANNEL_MODE_MONO (1 << 3)
+#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2)
+#define MPEG_CHANNEL_MODE_STEREO (1 << 1)
+#define MPEG_CHANNEL_MODE_JOINT_STEREO 1
+
+#define MPEG_LAYER_MP1 (1 << 2)
+#define MPEG_LAYER_MP2 (1 << 1)
+#define MPEG_LAYER_MP3 1
+
+#define MPEG_SAMPLING_FREQ_16000 (1 << 5)
+#define MPEG_SAMPLING_FREQ_22050 (1 << 4)
+#define MPEG_SAMPLING_FREQ_24000 (1 << 3)
+#define MPEG_SAMPLING_FREQ_32000 (1 << 2)
+#define MPEG_SAMPLING_FREQ_44100 (1 << 1)
+#define MPEG_SAMPLING_FREQ_48000 1
+
+#define MAX_BITPOOL 64
+#define MIN_BITPOOL 2
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+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_select_cb_t) (struct avdtp *session,
+ struct a2dp_sep *sep, GSList *caps,
+ void *user_data);
+typedef void (*a2dp_config_cb_t) (struct avdtp *session, struct a2dp_sep *sep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err,
+ void *user_data);
+typedef void (*a2dp_stream_cb_t) (struct avdtp *session,
+ struct avdtp_error *err,
+ void *user_data);
+
+int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
+void a2dp_unregister(const bdaddr_t *src);
+
+struct a2dp_sep *a2dp_add_sep(const bdaddr_t *src, uint8_t type,
+ uint8_t codec, gboolean delay_reporting,
+ struct media_endpoint *endpoint, int *err);
+void a2dp_remove_sep(struct a2dp_sep *sep);
+
+struct a2dp_sep *a2dp_get(struct avdtp *session, struct avdtp_remote_sep *sep);
+
+unsigned int a2dp_select_capabilities(struct avdtp *session,
+ uint8_t type, const char *sender,
+ a2dp_select_cb_t cb,
+ void *user_data);
+unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
+ a2dp_config_cb_t cb, GSList *caps,
+ void *user_data);
+unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep,
+ a2dp_stream_cb_t cb, void *user_data);
+unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep,
+ a2dp_stream_cb_t cb, void *user_data);
+gboolean a2dp_cancel(struct audio_device *dev, unsigned int id);
+
+gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session);
+gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session);
+gboolean a2dp_sep_get_lock(struct a2dp_sep *sep);
+struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep);
+struct a2dp_sep *a2dp_get_sep(struct avdtp *session,
+ struct avdtp_stream *stream);
diff --git a/audio/audio.conf b/audio/audio.conf
new file mode 100644
index 0000000..302e046
--- /dev/null
+++ b/audio/audio.conf
@@ -0,0 +1,45 @@
+# 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
+
+# Automatically connect both A2DP and HFP/HSP profiles for incoming
+# connections. Some headsets that support both profiles will only connect the
+# other one automatically so the default setting of true is usually a good
+# idea.
+#AutoConnect=true
+
+# 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, false means only HSP is supported
+# Defaults to true
+HFP=true
+
+# Maximum number of connected HSP/HFP devices per adapter. Defaults to 1
+MaxConnected=1
+
+# Set to true to enable use of fast connectable mode (faster page scanning)
+# for HFP when incomming call starts. Default settings are restored after
+# call is answered or rejected. Page scan interval is much shorter and page
+# scan type changed to interlaced. Such allows faster connection initiated
+# by a headset.
+FastConnectable=false
+
+# Just an example of potential config options for the other interfaces
+#[A2DP]
+#SBCSources=1
+#MPEG12Sources=0
diff --git a/audio/avdtp.c b/audio/avdtp.c
new file mode 100644
index 0000000..83b1aa2
--- /dev/null
+++ b/audio/avdtp.c
@@ -0,0 +1,3914 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#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 <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "log.h"
+
+#include "../src/adapter.h"
+#include "../src/manager.h"
+#include "../src/device.h"
+
+#include "device.h"
+#include "manager.h"
+#include "control.h"
+#include "avdtp.h"
+#include "glib-helper.h"
+#include "btio.h"
+#include "sink.h"
+#include "source.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_GET_ALL_CAPABILITIES 0x0C
+#define AVDTP_DELAY_REPORT 0x0D
+
+#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_GEN_REJECT 0x01
+#define AVDTP_MSG_TYPE_ACCEPT 0x02
+#define AVDTP_MSG_TYPE_REJECT 0x03
+
+#define REQ_TIMEOUT 6
+#define ABORT_TIMEOUT 2
+#define DISCONNECT_TIMEOUT 1
+#define STREAM_TIMEOUT 20
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avdtp_common_header {
+ uint8_t message_type:2;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+} __attribute__ ((packed));
+
+struct avdtp_single_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 avdtp_start_header {
+ uint8_t message_type:2;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+ uint8_t no_of_packets;
+ uint8_t signal_id:6;
+ uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct avdtp_continue_header {
+ uint8_t message_type:2;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+} __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_common_header {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t message_type:2;
+} __attribute__ ((packed));
+
+struct avdtp_single_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 avdtp_start_header {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t message_type:2;
+ uint8_t no_of_packets;
+ uint8_t rfa0:2;
+ uint8_t signal_id:6;
+} __attribute__ ((packed));
+
+struct avdtp_continue_header {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t message_type:2;
+} __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 discover_resp {
+ struct seid_info seps[0];
+} __attribute__ ((packed));
+
+struct getcap_resp {
+ uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct start_req {
+ struct seid first_seid;
+ struct seid other_seids[0];
+} __attribute__ ((packed));
+
+struct suspend_req {
+ struct seid first_seid;
+ struct seid other_seids[0];
+} __attribute__ ((packed));
+
+struct seid_rej {
+ uint8_t error;
+} __attribute__ ((packed));
+
+struct conf_rej {
+ uint8_t category;
+ uint8_t error;
+} __attribute__ ((packed));
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct seid_req {
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+} __attribute__ ((packed));
+
+struct setconf_req {
+ 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 {
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+ uint8_t error;
+} __attribute__ ((packed));
+
+struct reconf_req {
+ 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 delay_req {
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+ uint16_t delay;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct seid_req {
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct setconf_req {
+ 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 {
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+ uint8_t error;
+} __attribute__ ((packed));
+
+struct reconf_req {
+ 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 delay_req {
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+ uint16_t delay;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct in_buf {
+ gboolean active;
+ int no_of_packets;
+ uint8_t transaction;
+ uint8_t message_type;
+ uint8_t signal_id;
+ uint8_t buf[1024];
+ uint8_t data_size;
+};
+
+struct pending_req {
+ uint8_t transaction;
+ uint8_t signal_id;
+ void *data;
+ size_t data_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;
+ gboolean delay_reporting;
+ GSList *caps; /* of type struct avdtp_service_capability */
+ struct avdtp_stream *stream;
+};
+
+struct avdtp_server {
+ bdaddr_t src;
+ uint16_t version;
+ GIOChannel *io;
+ GSList *seps;
+ GSList *sessions;
+};
+
+struct avdtp_local_sep {
+ avdtp_state_t state;
+ struct avdtp_stream *stream;
+ struct seid_info info;
+ uint8_t codec;
+ gboolean delay_reporting;
+ GSList *caps;
+ struct avdtp_sep_ind *ind;
+ struct avdtp_sep_cfm *cfm;
+ void *user_data;
+ struct avdtp_server *server;
+};
+
+struct stream_callback {
+ avdtp_stream_state_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+struct avdtp_state_callback {
+ avdtp_session_state_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+struct avdtp_stream {
+ GIOChannel *io;
+ 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_id; /* 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 */
+ gboolean abort_int; /* If we are in INT role for Abort */
+ guint idle_timer;
+ gboolean delay_reporting;
+ uint16_t delay; /* AVDTP 1.3 Delay Reporting feature */
+};
+
+/* Structure describing an AVDTP connection between two devices */
+
+struct avdtp {
+ int ref;
+ int free_lock;
+
+ uint16_t version;
+
+ struct avdtp_server *server;
+ bdaddr_t dst;
+
+ avdtp_session_state_t state;
+
+ /* True if the session should be automatically disconnected */
+ gboolean auto_dc;
+
+ /* True if the entire device is being disconnected */
+ gboolean device_disconnect;
+
+ GIOChannel *io;
+ guint io_id;
+
+ 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 imtu;
+ uint16_t omtu;
+
+ struct in_buf in;
+
+ char *buf;
+
+ avdtp_discover_cb_t discov_cb;
+ void *user_data;
+
+ struct pending_req *req;
+
+ guint dc_timer;
+
+ /* Attempt stream setup instead of disconnecting */
+ gboolean stream_setup;
+
+ DBusPendingCall *pending_auth;
+};
+
+static GSList *servers = NULL;
+
+static GSList *avdtp_callbacks = NULL;
+
+static gboolean auto_connect = TRUE;
+
+static int send_request(struct avdtp *session, gboolean priority,
+ struct avdtp_stream *stream, uint8_t signal_id,
+ void *buffer, size_t size);
+static gboolean avdtp_parse_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ uint8_t transaction, uint8_t signal_id,
+ void *buf, int size);
+static gboolean avdtp_parse_rej(struct avdtp *session,
+ struct avdtp_stream *stream,
+ uint8_t transaction, uint8_t signal_id,
+ void *buf, 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 void auth_cb(DBusError *derr, void *user_data);
+
+static struct avdtp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+ GSList *l;
+
+ for (l = list; l; l = l->next) {
+ struct avdtp_server *server = l->data;
+
+ if (bacmp(&server->src, src) == 0)
+ return server;
+ }
+
+ return NULL;
+}
+
+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 try_send(int sk, void *data, size_t len)
+{
+ int err;
+
+ do {
+ err = send(sk, data, len, 0);
+ } while (err < 0 && errno == EINTR);
+
+ if (err < 0) {
+ error("send: %s (%d)", strerror(errno), errno);
+ return FALSE;
+ } else if ((size_t) err != len) {
+ error("try_send: complete buffer not sent (%d/%zu bytes)",
+ err, len);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean avdtp_send(struct avdtp *session, uint8_t transaction,
+ uint8_t message_type, uint8_t signal_id,
+ void *data, size_t len)
+{
+ unsigned int cont_fragments, sent;
+ struct avdtp_start_header start;
+ struct avdtp_continue_header cont;
+ int sock;
+
+ if (session->io == NULL) {
+ error("avdtp_send: session is closed");
+ return FALSE;
+ }
+
+ sock = g_io_channel_unix_get_fd(session->io);
+
+ /* Single packet - no fragmentation */
+ if (sizeof(struct avdtp_single_header) + len <= session->omtu) {
+ struct avdtp_single_header single;
+
+ memset(&single, 0, sizeof(single));
+
+ single.transaction = transaction;
+ single.packet_type = AVDTP_PKT_TYPE_SINGLE;
+ single.message_type = message_type;
+ single.signal_id = signal_id;
+
+ memcpy(session->buf, &single, sizeof(single));
+ memcpy(session->buf + sizeof(single), data, len);
+
+ return try_send(sock, session->buf, sizeof(single) + len);
+ }
+
+ /* Check if there is enough space to start packet */
+ if (session->omtu < sizeof(start)) {
+ error("No enough space to fragment packet");
+ return FALSE;
+ }
+
+ /* Count the number of needed fragments */
+ cont_fragments = (len - (session->omtu - sizeof(start))) /
+ (session->omtu - sizeof(cont)) + 1;
+
+ DBG("%zu bytes split into %d fragments", len, cont_fragments + 1);
+
+ /* Send the start packet */
+ memset(&start, 0, sizeof(start));
+ start.transaction = transaction;
+ start.packet_type = AVDTP_PKT_TYPE_START;
+ start.message_type = message_type;
+ start.no_of_packets = cont_fragments + 1;
+ start.signal_id = signal_id;
+
+ memcpy(session->buf, &start, sizeof(start));
+ memcpy(session->buf + sizeof(start), data,
+ session->omtu - sizeof(start));
+
+ if (!try_send(sock, session->buf, session->omtu))
+ return FALSE;
+
+ DBG("first packet with %zu bytes sent", session->omtu - sizeof(start));
+
+ sent = session->omtu - sizeof(start);
+
+ /* Send the continue fragments and the end packet */
+ while (sent < len) {
+ int left, to_copy;
+
+ left = len - sent;
+ if (left + sizeof(cont) > session->omtu) {
+ cont.packet_type = AVDTP_PKT_TYPE_CONTINUE;
+ to_copy = session->omtu - sizeof(cont);
+ DBG("sending continue with %d bytes", to_copy);
+ } else {
+ cont.packet_type = AVDTP_PKT_TYPE_END;
+ to_copy = left;
+ DBG("sending end with %d bytes", to_copy);
+ }
+
+ cont.transaction = transaction;
+ cont.message_type = message_type;
+
+ memcpy(session->buf, &cont, sizeof(cont));
+ memcpy(session->buf + sizeof(cont), data + sent, to_copy);
+
+ if (!try_send(sock, session->buf, to_copy + sizeof(cont)))
+ return FALSE;
+
+ sent += to_copy;
+ }
+
+ return TRUE;
+}
+
+static void pending_req_free(struct pending_req *req)
+{
+ if (req->timeout)
+ g_source_remove(req->timeout);
+ g_free(req->data);
+ g_free(req);
+}
+
+static void close_stream(struct avdtp_stream *stream)
+{
+ int sock;
+
+ if (stream->io == NULL)
+ return;
+
+ sock = g_io_channel_unix_get_fd(stream->io);
+
+ shutdown(sock, SHUT_RDWR);
+
+ g_io_channel_shutdown(stream->io, FALSE, NULL);
+
+ g_io_channel_unref(stream->io);
+ stream->io = NULL;
+}
+
+static gboolean stream_close_timeout(gpointer user_data)
+{
+ struct avdtp_stream *stream = user_data;
+
+ DBG("Timed out waiting for peer to close the transport channel");
+
+ stream->timer = 0;
+
+ close_stream(stream);
+
+ return FALSE;
+}
+
+static gboolean stream_open_timeout(gpointer user_data)
+{
+ struct avdtp_stream *stream = user_data;
+
+ DBG("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;
+ struct audio_device *dev;
+ gboolean stream_setup;
+
+ session->dc_timer = 0;
+ stream_setup = session->stream_setup;
+ session->stream_setup = FALSE;
+
+ dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+
+ if (dev && dev->sink && stream_setup)
+ sink_setup_stream(dev->sink, session);
+ else if (dev && dev->source && stream_setup)
+ source_setup_stream(dev->source, session);
+ else
+ connection_lost(session, ETIMEDOUT);
+
+ return FALSE;
+}
+
+static void remove_disconnect_timer(struct avdtp *session)
+{
+ g_source_remove(session->dc_timer);
+ session->dc_timer = 0;
+ session->stream_setup = FALSE;
+}
+
+static void set_disconnect_timer(struct avdtp *session)
+{
+ if (session->dc_timer)
+ remove_disconnect_timer(session);
+
+ if (session->device_disconnect) {
+ g_idle_add(disconnect_timeout, session);
+ return;
+ }
+
+ session->dc_timer = g_timeout_add_seconds(DISCONNECT_TIMEOUT,
+ disconnect_timeout,
+ session);
+}
+
+void avdtp_error_init(struct avdtp_error *err, uint8_t category, int id)
+{
+ err->category = category;
+
+ if (category == AVDTP_ERRNO)
+ err->err.posix_errno = id;
+ else
+ err->err.error_code = id;
+}
+
+uint8_t avdtp_error_category(struct avdtp_error *err)
+{
+ return err->category;
+}
+
+int avdtp_error_error_code(struct avdtp_error *err)
+{
+ assert(err->category != AVDTP_ERRNO);
+ return err->err.error_code;
+}
+
+int avdtp_error_posix_errno(struct avdtp_error *err)
+{
+ assert(err->category == AVDTP_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 avdtp_set_state(struct avdtp *session,
+ avdtp_session_state_t new_state)
+{
+ GSList *l;
+ struct audio_device *dev;
+ bdaddr_t src, dst;
+ avdtp_session_state_t old_state = session->state;
+
+ session->state = new_state;
+
+ avdtp_get_peers(session, &src, &dst);
+ dev = manager_get_device(&src, &dst, FALSE);
+ if (dev == NULL) {
+ error("avdtp_set_state(): no matching audio device");
+ return;
+ }
+
+ for (l = avdtp_callbacks; l != NULL; l = l->next) {
+ struct avdtp_state_callback *cb = l->data;
+ cb->cb(dev, session, old_state, new_state, cb->user_data);
+ }
+}
+
+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->io)
+ close_stream(stream);
+
+ if (stream->io_id)
+ g_source_remove(stream->io_id);
+
+ 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(gpointer user_data)
+{
+ struct avdtp_stream *stream = user_data;
+ struct avdtp *session = stream->session;
+
+ if (avdtp_close(session, stream, FALSE) < 0)
+ error("stream_timeout: closing AVDTP stream failed");
+
+ 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);
+
+ if (!(cond & G_IO_NVAL))
+ close_stream(stream);
+
+ stream->io_id = 0;
+
+ if (!stream->abort_int)
+ avdtp_sep_set_state(stream->session, sep, AVDTP_STATE_IDLE);
+
+ return FALSE;
+}
+
+static int get_send_buffer_size(int sk)
+{
+ int size;
+ socklen_t optlen = sizeof(size);
+
+ if (getsockopt(sk, SOL_SOCKET, SO_SNDBUF, &size, &optlen) < 0) {
+ int err = -errno;
+ error("getsockopt(SO_SNDBUF) failed: %s (%d)", strerror(-err),
+ -err);
+ return err;
+ }
+
+ /*
+ * Doubled value is returned by getsockopt since kernel uses that
+ * space for its own purposes (see man 7 socket, bookkeeping overhead
+ * for SO_SNDBUF).
+ */
+ return size / 2;
+}
+
+static int set_send_buffer_size(int sk, int size)
+{
+ socklen_t optlen = sizeof(size);
+
+ if (setsockopt(sk, SOL_SOCKET, SO_SNDBUF, &size, optlen) < 0) {
+ int err = -errno;
+ error("setsockopt(SO_SNDBUF) failed: %s (%d)", strerror(-err),
+ -err);
+ return err;
+ }
+
+ return 0;
+}
+
+static void handle_transport_connect(struct avdtp *session, GIOChannel *io,
+ uint16_t imtu, uint16_t omtu)
+{
+ struct avdtp_stream *stream = session->pending_open;
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ session->pending_open = NULL;
+
+ if (stream->timer) {
+ g_source_remove(stream->timer);
+ stream->timer = 0;
+ }
+
+ if (io == NULL) {
+ if (!stream->open_acp && sep->cfm && sep->cfm->open) {
+ struct avdtp_error err;
+ avdtp_error_init(&err, AVDTP_ERRNO, EIO);
+ sep->cfm->open(session, sep, NULL, &err,
+ sep->user_data);
+ }
+ return;
+ }
+
+ if (stream->io == NULL)
+ stream->io = g_io_channel_ref(io);
+
+ stream->omtu = omtu;
+ stream->imtu = imtu;
+
+ /* only if local SEP is of type SRC */
+ if (sep->info.type == AVDTP_SEP_TYPE_SOURCE) {
+ int sk, buf_size, min_buf_size;
+
+ sk = g_io_channel_unix_get_fd(stream->io);
+ buf_size = get_send_buffer_size(sk);
+ if (buf_size < 0)
+ goto proceed;
+
+ DBG("sk %d, omtu %d, send buffer size %d", sk, omtu, buf_size);
+ min_buf_size = omtu * 2;
+ if (buf_size < min_buf_size) {
+ DBG("send buffer size to be increassed to %d",
+ min_buf_size);
+ set_send_buffer_size(sk, min_buf_size);
+ }
+ }
+
+proceed:
+ if (!stream->open_acp && sep->cfm && sep->cfm->open)
+ sep->cfm->open(session, sep, stream, NULL, sep->user_data);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+
+ stream->io_id = g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) transport_cb, stream);
+}
+
+static int pending_req_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct pending_req *req = a;
+ const struct avdtp_stream *stream = b;
+
+ if (req->stream == stream)
+ return 0;
+
+ return -1;
+}
+
+static void cleanup_queue(struct avdtp *session, struct avdtp_stream *stream)
+{
+ GSList *l;
+ struct pending_req *req;
+
+ while ((l = g_slist_find_custom(session->prio_queue, stream,
+ pending_req_cmp))) {
+ req = l->data;
+ pending_req_free(req);
+ session->prio_queue = g_slist_remove(session->prio_queue, req);
+ }
+
+ while ((l = g_slist_find_custom(session->req_queue, stream,
+ pending_req_cmp))) {
+ req = l->data;
+ pending_req_free(req);
+ session->req_queue = g_slist_remove(session->req_queue, req);
+ }
+}
+
+static void handle_unanswered_req(struct avdtp *session,
+ struct avdtp_stream *stream)
+{
+ struct pending_req *req;
+ struct avdtp_local_sep *lsep;
+ struct avdtp_error err;
+
+ if (session->req->signal_id == AVDTP_ABORT) {
+ /* Avoid freeing the Abort request here */
+ DBG("handle_unanswered_req: Abort req, returning");
+ session->req->stream = NULL;
+ return;
+ }
+
+ req = session->req;
+ session->req = NULL;
+
+ avdtp_error_init(&err, AVDTP_ERRNO, EIO);
+
+ lsep = stream->lsep;
+
+ switch (req->signal_id) {
+ case AVDTP_RECONFIGURE:
+ error("No reply to Reconfigure request");
+ if (lsep && lsep->cfm && lsep->cfm->reconfigure)
+ lsep->cfm->reconfigure(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_OPEN:
+ error("No reply to Open request");
+ if (lsep && lsep->cfm && lsep->cfm->open)
+ lsep->cfm->open(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_START:
+ error("No reply to Start request");
+ if (lsep && lsep->cfm && lsep->cfm->start)
+ lsep->cfm->start(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_SUSPEND:
+ error("No reply to Suspend request");
+ if (lsep && lsep->cfm && lsep->cfm->suspend)
+ lsep->cfm->suspend(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_CLOSE:
+ error("No reply to Close request");
+ if (lsep && lsep->cfm && lsep->cfm->close)
+ lsep->cfm->close(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_SET_CONFIGURATION:
+ error("No reply to SetConfiguration request");
+ if (lsep && lsep->cfm && lsep->cfm->set_configuration)
+ lsep->cfm->set_configuration(session, lsep, stream,
+ &err, lsep->user_data);
+ }
+
+ pending_req_free(req);
+}
+
+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;
+ GSList *l;
+
+ if (!stream) {
+ error("Error changing sep state: stream not available");
+ return;
+ }
+
+ if (sep->state == state) {
+ avdtp_error_init(&err, AVDTP_ERRNO, EIO);
+ DBG("stream state change failed: %s", avdtp_strerror(&err));
+ err_ptr = &err;
+ } else {
+ err_ptr = NULL;
+ DBG("stream state changed: %s -> %s",
+ avdtp_statestr(sep->state),
+ avdtp_statestr(state));
+ }
+
+ old_state = sep->state;
+ sep->state = state;
+
+ 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_CONFIGURED:
+ if (sep->info.type == AVDTP_SEP_TYPE_SINK)
+ avdtp_delay_report(session, stream, stream->delay);
+ break;
+ case AVDTP_STATE_OPEN:
+ if (old_state > AVDTP_STATE_OPEN && session->auto_dc)
+ stream->idle_timer = g_timeout_add_seconds(STREAM_TIMEOUT,
+ stream_timeout,
+ stream);
+ 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, NULL, 0, 0);
+ if (session->req && session->req->stream == stream)
+ handle_unanswered_req(session, stream);
+ /* Remove pending commands for this stream from the queue */
+ cleanup_queue(session, stream);
+ stream_free(stream);
+ break;
+ default:
+ break;
+ }
+}
+
+static void finalize_discovery(struct avdtp *session, int err)
+{
+ struct avdtp_error avdtp_err;
+
+ avdtp_error_init(&avdtp_err, AVDTP_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->state != AVDTP_STATE_ABORTING ||
+ stream->abort_int))
+ 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)
+{
+ char address[18];
+ struct audio_device *dev;
+
+ ba2str(&session->dst, address);
+ DBG("Disconnected from %s", address);
+
+ dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+
+ if (dev != NULL && session->state == AVDTP_SESSION_STATE_CONNECTING &&
+ err != EACCES)
+ audio_device_cancel_authorization(dev, auth_cb, session);
+
+ 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->io) {
+ g_io_channel_shutdown(session->io, FALSE, NULL);
+ g_io_channel_unref(session->io);
+ session->io = NULL;
+ }
+
+ avdtp_set_state(session, AVDTP_SESSION_STATE_DISCONNECTED);
+
+ if (session->io_id) {
+ g_source_remove(session->io_id);
+ session->io_id = 0;
+ }
+
+ if (session->dc_timer)
+ remove_disconnect_timer(session);
+
+ session->auto_dc = TRUE;
+
+ if (session->ref != 1)
+ error("connection_lost: ref count not 1 after all callbacks");
+ else
+ avdtp_unref(session);
+}
+
+void avdtp_unref(struct avdtp *session)
+{
+ struct avdtp_server *server;
+
+ if (!session)
+ return;
+
+ session->ref--;
+
+ DBG("%p: ref=%d", session, session->ref);
+
+ if (session->ref == 1) {
+ if (session->state == AVDTP_SESSION_STATE_CONNECTING &&
+ session->io) {
+ struct audio_device *dev;
+ dev = manager_get_device(&session->server->src,
+ &session->dst, FALSE);
+ audio_device_cancel_authorization(dev, auth_cb,
+ session);
+ g_io_channel_shutdown(session->io, TRUE, NULL);
+ g_io_channel_unref(session->io);
+ session->io = NULL;
+ avdtp_set_state(session,
+ AVDTP_SESSION_STATE_DISCONNECTED);
+ }
+
+ if (session->io)
+ 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;
+
+ server = session->server;
+
+ DBG("%p: freeing session and removing from list", session);
+
+ if (session->dc_timer)
+ remove_disconnect_timer(session);
+
+ server->sessions = g_slist_remove(server->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++;
+ DBG("%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(struct avdtp_server *server,
+ uint8_t seid)
+{
+ GSList *l;
+
+ for (l = server->seps; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_local_sep *sep = l->data;
+
+ if (sep->info.seid == seid)
+ return sep;
+ }
+
+ return NULL;
+}
+
+struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session,
+ struct avdtp_local_sep *lsep)
+{
+ GSList *l;
+
+ if (lsep->info.inuse)
+ return NULL;
+
+ for (l = session->seps; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_remote_sep *sep = l->data;
+ struct avdtp_service_capability *cap;
+ struct avdtp_media_codec_capability *codec_data;
+
+ /* Type must be different: source <-> sink */
+ if (sep->type == lsep->info.type)
+ continue;
+
+ if (sep->media_type != lsep->info.media_type)
+ continue;
+
+ if (!sep->codec)
+ continue;
+
+ cap = sep->codec;
+ codec_data = (void *) cap->data;
+
+ if (codec_data->media_codec_type != lsep->codec)
+ continue;
+
+ if (sep->stream == NULL)
+ return sep;
+ }
+
+ return NULL;
+}
+
+static GSList *caps_to_list(uint8_t *data, int size,
+ struct avdtp_service_capability **codec,
+ gboolean *delay_reporting)
+{
+ GSList *caps;
+ int processed;
+
+ if (delay_reporting)
+ *delay_reporting = FALSE;
+
+ 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;
+ else if (category == AVDTP_DELAY_REPORTING && delay_reporting)
+ *delay_reporting = TRUE;
+ }
+
+ return caps;
+}
+
+static gboolean avdtp_unknown_cmd(struct avdtp *session, uint8_t transaction,
+ uint8_t signal_id)
+{
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_GEN_REJECT,
+ signal_id, NULL, 0);
+}
+
+static gboolean avdtp_discover_cmd(struct avdtp *session, uint8_t transaction,
+ void *buf, int size)
+{
+ GSList *l;
+ unsigned int rsp_size, sep_count, i;
+ struct seid_info *seps;
+ gboolean ret;
+
+ sep_count = g_slist_length(session->server->seps);
+
+ if (sep_count == 0) {
+ uint8_t err = AVDTP_NOT_SUPPORTED_COMMAND;
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_DISCOVER, &err, sizeof(err));
+ }
+
+ rsp_size = sep_count * sizeof(struct seid_info);
+
+ seps = g_new0(struct seid_info, sep_count);
+
+ for (l = session->server->seps, i = 0; l != NULL; l = l->next, i++) {
+ struct avdtp_local_sep *sep = l->data;
+
+ memcpy(&seps[i], &sep->info, sizeof(struct seid_info));
+ }
+
+ ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_DISCOVER, seps, rsp_size);
+ g_free(seps);
+
+ return ret;
+}
+
+static gboolean avdtp_getcap_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, unsigned int size,
+ gboolean get_all)
+{
+ GSList *l, *caps;
+ struct avdtp_local_sep *sep = NULL;
+ unsigned int rsp_size;
+ uint8_t err, buf[1024], *ptr = buf;
+ uint8_t cmd;
+
+ cmd = get_all ? AVDTP_GET_ALL_CAPABILITIES : AVDTP_GET_CAPABILITIES;
+
+ if (size < sizeof(struct seid_req)) {
+ err = AVDTP_BAD_LENGTH;
+ goto failed;
+ }
+
+ sep = find_local_sep_by_seid(session->server, req->acp_seid);
+ if (!sep) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ if (get_all && session->server->version < 0x0103)
+ return avdtp_unknown_cmd(session, transaction, cmd);
+
+ if (!sep->ind->get_capability(session, sep, get_all, &caps,
+ &err, sep->user_data))
+ goto failed;
+
+ for (l = caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_service_capability *cap = l->data;
+
+ if (rsp_size + cap->length + 2 > sizeof(buf))
+ 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, transaction, AVDTP_MSG_TYPE_ACCEPT, cmd,
+ buf, rsp_size);
+
+failed:
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, cmd,
+ &err, sizeof(err));
+}
+
+static void setconf_cb(struct avdtp *session, struct avdtp_stream *stream,
+ struct avdtp_error *err)
+{
+ struct conf_rej rej;
+ struct avdtp_local_sep *sep;
+
+ if (err != NULL) {
+ rej.error = AVDTP_UNSUPPORTED_CONFIGURATION;
+ rej.category = err->err.error_code;
+ avdtp_send(session, session->in.transaction,
+ AVDTP_MSG_TYPE_REJECT, AVDTP_SET_CONFIGURATION,
+ &rej, sizeof(rej));
+ return;
+ }
+
+ if (!avdtp_send(session, session->in.transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_SET_CONFIGURATION, NULL, 0)) {
+ stream_free(stream);
+ return;
+ }
+
+ sep = stream->lsep;
+ sep->stream = stream;
+ sep->info.inuse = 1;
+ session->streams = g_slist_append(session->streams, stream);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+}
+
+static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction,
+ struct setconf_req *req, unsigned int size)
+{
+ struct conf_rej rej;
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ uint8_t err, category = 0x00;
+ struct audio_device *dev;
+ bdaddr_t src, dst;
+ GSList *l;
+
+ if (size < sizeof(struct setconf_req)) {
+ error("Too short getcap request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(session->server, req->acp_seid);
+ if (!sep) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ if (sep->stream) {
+ err = AVDTP_SEP_IN_USE;
+ goto failed;
+ }
+
+ avdtp_get_peers(session, &src, &dst);
+ dev = manager_get_device(&src, &dst, FALSE);
+ if (!dev) {
+ error("Unable to get a audio device object");
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+
+ switch (sep->info.type) {
+ case AVDTP_SEP_TYPE_SOURCE:
+ if (!dev->sink) {
+ btd_device_add_uuid(dev->btd_dev, A2DP_SINK_UUID);
+ if (!dev->sink) {
+ error("Unable to get a audio sink object");
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+ }
+ break;
+ case AVDTP_SEP_TYPE_SINK:
+ if (!dev->source) {
+ btd_device_add_uuid(dev->btd_dev, A2DP_SOURCE_UUID);
+ if (!dev->sink) {
+ error("Unable to get a audio source object");
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+ }
+ break;
+ }
+
+ 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->delay_reporting);
+
+ /* Verify that the Media Transport capability's length = 0. Reject otherwise */
+ for (l = stream->caps; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_service_capability *cap = l->data;
+
+ if (cap->category == AVDTP_MEDIA_TRANSPORT && cap->length != 0) {
+ err = AVDTP_BAD_MEDIA_TRANSPORT_FORMAT;
+ goto failed_stream;
+ }
+ }
+
+ if (stream->delay_reporting && session->version < 0x0103)
+ session->version = 0x0103;
+
+ if (sep->ind && sep->ind->set_configuration) {
+ if (!sep->ind->set_configuration(session, sep, stream,
+ stream->caps,
+ setconf_cb,
+ sep->user_data)) {
+ err = AVDTP_UNSUPPORTED_CONFIGURATION;
+ category = 0x00;
+ goto failed_stream;
+ }
+ } else {
+ if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_SET_CONFIGURATION, NULL, 0)) {
+ stream_free(stream);
+ return FALSE;
+ }
+
+ sep->stream = stream;
+ sep->info.inuse = 1;
+ session->streams = g_slist_append(session->streams, stream);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+ }
+
+ return TRUE;
+
+failed_stream:
+ stream_free(stream);
+failed:
+ rej.error = err;
+ rej.category = category;
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_SET_CONFIGURATION, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_getconf_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, int size)
+{
+ GSList *l;
+ struct avdtp_local_sep *sep = NULL;
+ int rsp_size;
+ uint8_t err;
+ uint8_t buf[1024];
+ uint8_t *ptr = buf;
+
+ if (size < (int) sizeof(struct seid_req)) {
+ error("Too short getconf request");
+ return FALSE;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ sep = find_local_sep_by_seid(session->server, req->acp_seid);
+ if (!sep) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+ if (!sep->stream || !sep->stream->caps) {
+ err = AVDTP_UNSUPPORTED_CONFIGURATION;
+ goto failed;
+ }
+
+ for (l = sep->stream->caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_service_capability *cap = l->data;
+
+ if (rsp_size + cap->length + 2 > (int) sizeof(buf))
+ break;
+
+ memcpy(ptr, cap, cap->length + 2);
+ rsp_size += cap->length + 2;
+ ptr += cap->length + 2;
+ }
+
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_GET_CONFIGURATION, buf, rsp_size);
+
+failed:
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_GET_CONFIGURATION, &err, sizeof(err));
+}
+
+static gboolean avdtp_reconf_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, int size)
+{
+ return avdtp_unknown_cmd(session, transaction, AVDTP_RECONFIGURE);
+}
+
+static gboolean avdtp_open_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ uint8_t err;
+
+ if (size < sizeof(struct seid_req)) {
+ error("Too short abort request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(session->server, 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;
+ }
+
+ if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_OPEN, NULL, 0))
+ return FALSE;
+
+ stream->open_acp = TRUE;
+ session->pending_open = stream;
+ stream->timer = g_timeout_add_seconds(REQ_TIMEOUT,
+ stream_open_timeout,
+ stream);
+
+ return TRUE;
+
+failed:
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_OPEN, &err, sizeof(err));
+}
+
+static gboolean avdtp_start_cmd(struct avdtp *session, uint8_t transaction,
+ struct start_req *req, unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ 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(session->server,
+ 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);
+ }
+
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_START, NULL, 0);
+
+failed:
+ memset(&rej, 0, sizeof(rej));
+ rej.acp_seid = failed_seid;
+ rej.error = err;
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_START, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_close_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ uint8_t err;
+
+ if (size < sizeof(struct seid_req)) {
+ error("Too short close request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(session->server, 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);
+
+ if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_CLOSE, NULL, 0))
+ return FALSE;
+
+ stream->timer = g_timeout_add_seconds(REQ_TIMEOUT,
+ stream_close_timeout,
+ stream);
+
+ return TRUE;
+
+failed:
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_CLOSE, &err, sizeof(err));
+}
+
+static gboolean avdtp_suspend_cmd(struct avdtp *session, uint8_t transaction,
+ struct suspend_req *req, unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ 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(session->server,
+ 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);
+ }
+
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_SUSPEND, NULL, 0);
+
+failed:
+ memset(&rej, 0, sizeof(rej));
+ rej.acp_seid = failed_seid;
+ rej.error = err;
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_SUSPEND, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_abort_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ uint8_t err;
+ gboolean ret;
+
+ if (size < sizeof(struct seid_req)) {
+ error("Too short abort request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(session->server, 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;
+ }
+
+ ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_ABORT, NULL, 0);
+ if (ret)
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING);
+
+ return ret;
+
+failed:
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_ABORT, &err, sizeof(err));
+}
+
+static gboolean avdtp_secctl_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, int size)
+{
+ return avdtp_unknown_cmd(session, transaction, AVDTP_SECURITY_CONTROL);
+}
+
+static gboolean avdtp_delayreport_cmd(struct avdtp *session,
+ uint8_t transaction,
+ struct delay_req *req,
+ unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ uint8_t err;
+
+ if (size < sizeof(struct delay_req)) {
+ error("Too short delay report request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(session->server, req->acp_seid);
+ if (!sep || !sep->stream) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ stream = sep->stream;
+
+ if (sep->state != AVDTP_STATE_CONFIGURED &&
+ sep->state != AVDTP_STATE_STREAMING) {
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+
+ stream->delay = ntohs(req->delay);
+
+ if (sep->ind && sep->ind->delayreport) {
+ if (!sep->ind->delayreport(session, sep, stream->rseid,
+ stream->delay, &err,
+ sep->user_data))
+ goto failed;
+ }
+
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_DELAY_REPORT, NULL, 0);
+
+failed:
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_DELAY_REPORT, &err, sizeof(err));
+}
+
+static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction,
+ uint8_t signal_id, void *buf, int size)
+{
+ switch (signal_id) {
+ case AVDTP_DISCOVER:
+ DBG("Received DISCOVER_CMD");
+ return avdtp_discover_cmd(session, transaction, buf, size);
+ case AVDTP_GET_CAPABILITIES:
+ DBG("Received GET_CAPABILITIES_CMD");
+ return avdtp_getcap_cmd(session, transaction, buf, size,
+ FALSE);
+ case AVDTP_GET_ALL_CAPABILITIES:
+ DBG("Received GET_ALL_CAPABILITIES_CMD");
+ return avdtp_getcap_cmd(session, transaction, buf, size, TRUE);
+ case AVDTP_SET_CONFIGURATION:
+ DBG("Received SET_CONFIGURATION_CMD");
+ return avdtp_setconf_cmd(session, transaction, buf, size);
+ case AVDTP_GET_CONFIGURATION:
+ DBG("Received GET_CONFIGURATION_CMD");
+ return avdtp_getconf_cmd(session, transaction, buf, size);
+ case AVDTP_RECONFIGURE:
+ DBG("Received RECONFIGURE_CMD");
+ return avdtp_reconf_cmd(session, transaction, buf, size);
+ case AVDTP_OPEN:
+ DBG("Received OPEN_CMD");
+ return avdtp_open_cmd(session, transaction, buf, size);
+ case AVDTP_START:
+ DBG("Received START_CMD");
+ return avdtp_start_cmd(session, transaction, buf, size);
+ case AVDTP_CLOSE:
+ DBG("Received CLOSE_CMD");
+ return avdtp_close_cmd(session, transaction, buf, size);
+ case AVDTP_SUSPEND:
+ DBG("Received SUSPEND_CMD");
+ return avdtp_suspend_cmd(session, transaction, buf, size);
+ case AVDTP_ABORT:
+ DBG("Received ABORT_CMD");
+ return avdtp_abort_cmd(session, transaction, buf, size);
+ case AVDTP_SECURITY_CONTROL:
+ DBG("Received SECURITY_CONTROL_CMD");
+ return avdtp_secctl_cmd(session, transaction, buf, size);
+ case AVDTP_DELAY_REPORT:
+ DBG("Received DELAY_REPORT_CMD");
+ return avdtp_delayreport_cmd(session, transaction, buf, size);
+ default:
+ DBG("Received unknown request id %u", signal_id);
+ return avdtp_unknown_cmd(session, transaction, signal_id);
+ }
+}
+
+enum avdtp_parse_result { PARSE_ERROR, PARSE_FRAGMENT, PARSE_SUCCESS };
+
+static enum avdtp_parse_result avdtp_parse_data(struct avdtp *session,
+ void *buf, size_t size)
+{
+ struct avdtp_common_header *header = buf;
+ struct avdtp_single_header *single = (void *) session->buf;
+ struct avdtp_start_header *start = (void *) session->buf;
+ void *payload;
+ gsize payload_size;
+
+ switch (header->packet_type) {
+ case AVDTP_PKT_TYPE_SINGLE:
+ if (size < sizeof(*single)) {
+ error("Received too small single packet (%zu bytes)", size);
+ return PARSE_ERROR;
+ }
+ if (session->in.active) {
+ error("SINGLE: Invalid AVDTP packet fragmentation");
+ return PARSE_ERROR;
+ }
+
+ payload = session->buf + sizeof(*single);
+ payload_size = size - sizeof(*single);
+
+ session->in.active = TRUE;
+ session->in.data_size = 0;
+ session->in.no_of_packets = 1;
+ session->in.transaction = header->transaction;
+ session->in.message_type = header->message_type;
+ session->in.signal_id = single->signal_id;
+
+ break;
+ case AVDTP_PKT_TYPE_START:
+ if (size < sizeof(*start)) {
+ error("Received too small start packet (%zu bytes)", size);
+ return PARSE_ERROR;
+ }
+ if (session->in.active) {
+ error("START: Invalid AVDTP packet fragmentation");
+ return PARSE_ERROR;
+ }
+
+ session->in.active = TRUE;
+ session->in.data_size = 0;
+ session->in.transaction = header->transaction;
+ session->in.message_type = header->message_type;
+ session->in.no_of_packets = start->no_of_packets;
+ session->in.signal_id = start->signal_id;
+
+ payload = session->buf + sizeof(*start);
+ payload_size = size - sizeof(*start);
+
+ break;
+ case AVDTP_PKT_TYPE_CONTINUE:
+ if (size < sizeof(struct avdtp_continue_header)) {
+ error("Received too small continue packet (%zu bytes)",
+ size);
+ return PARSE_ERROR;
+ }
+ if (!session->in.active) {
+ error("CONTINUE: Invalid AVDTP packet fragmentation");
+ return PARSE_ERROR;
+ }
+ if (session->in.transaction != header->transaction) {
+ error("Continue transaction id doesn't match");
+ return PARSE_ERROR;
+ }
+ if (session->in.no_of_packets <= 1) {
+ error("Too few continue packets");
+ return PARSE_ERROR;
+ }
+
+ payload = session->buf + sizeof(struct avdtp_continue_header);
+ payload_size = size - sizeof(struct avdtp_continue_header);
+
+ break;
+ case AVDTP_PKT_TYPE_END:
+ if (size < sizeof(struct avdtp_continue_header)) {
+ error("Received too small end packet (%zu bytes)", size);
+ return PARSE_ERROR;
+ }
+ if (!session->in.active) {
+ error("END: Invalid AVDTP packet fragmentation");
+ return PARSE_ERROR;
+ }
+ if (session->in.transaction != header->transaction) {
+ error("End transaction id doesn't match");
+ return PARSE_ERROR;
+ }
+ if (session->in.no_of_packets > 1) {
+ error("Got an end packet too early");
+ return PARSE_ERROR;
+ }
+
+ payload = session->buf + sizeof(struct avdtp_continue_header);
+ payload_size = size - sizeof(struct avdtp_continue_header);
+
+ break;
+ default:
+ error("Invalid AVDTP packet type 0x%02X", header->packet_type);
+ return PARSE_ERROR;
+ }
+
+ if (session->in.data_size + payload_size >
+ sizeof(session->in.buf)) {
+ error("Not enough incoming buffer space!");
+ return PARSE_ERROR;
+ }
+
+ memcpy(session->in.buf + session->in.data_size, payload, payload_size);
+ session->in.data_size += payload_size;
+
+ if (session->in.no_of_packets > 1) {
+ session->in.no_of_packets--;
+ DBG("Received AVDTP fragment. %d to go",
+ session->in.no_of_packets);
+ return PARSE_FRAGMENT;
+ }
+
+ session->in.active = FALSE;
+
+ return PARSE_SUCCESS;
+}
+
+static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct avdtp *session = data;
+ struct avdtp_common_header *header;
+ ssize_t size;
+ int fd;
+
+ DBG("");
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ header = (void *) session->buf;
+
+ if (cond & (G_IO_HUP | G_IO_ERR))
+ goto failed;
+
+ fd = g_io_channel_unix_get_fd(chan);
+ size = read(fd, session->buf, session->imtu);
+ if (size < 0) {
+ error("IO Channel read error");
+ goto failed;
+ }
+
+ if ((size_t) size < sizeof(struct avdtp_common_header)) {
+ error("Received too small packet (%zu bytes)", size);
+ goto failed;
+ }
+
+ switch (avdtp_parse_data(session, session->buf, size)) {
+ case PARSE_ERROR:
+ goto failed;
+ case PARSE_FRAGMENT:
+ return TRUE;
+ case PARSE_SUCCESS:
+ break;
+ }
+
+ if (session->in.message_type == AVDTP_MSG_TYPE_COMMAND) {
+ if (!avdtp_parse_cmd(session, session->in.transaction,
+ session->in.signal_id,
+ session->in.buf,
+ session->in.data_size)) {
+ error("Unable to handle command. Disconnecting");
+ goto failed;
+ }
+
+ if (session->ref == 1 && !session->streams && !session->req)
+ set_disconnect_timer(session);
+
+ if (session->streams && session->dc_timer)
+ remove_disconnect_timer(session);
+
+ return TRUE;
+ }
+
+ if (session->req == NULL) {
+ error("No pending request, ignoring message");
+ return TRUE;
+ }
+
+ if (header->transaction != session->req->transaction) {
+ error("Transaction label doesn't match");
+ return TRUE;
+ }
+
+ if (session->in.signal_id != session->req->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,
+ session->in.transaction,
+ session->in.signal_id,
+ session->in.buf,
+ session->in.data_size)) {
+ error("Unable to parse accept response");
+ goto failed;
+ }
+ break;
+ case AVDTP_MSG_TYPE_REJECT:
+ if (!avdtp_parse_rej(session, session->req->stream,
+ session->in.transaction,
+ session->in.signal_id,
+ session->in.buf,
+ session->in.data_size)) {
+ error("Unable to parse reject response");
+ goto failed;
+ }
+ break;
+ case AVDTP_MSG_TYPE_GEN_REJECT:
+ error("Received a General Reject message");
+ break;
+ default:
+ error("Unknown message type 0x%02X", header->message_type);
+ break;
+ }
+
+ pending_req_free(session->req);
+ session->req = NULL;
+
+ process_queue(session);
+
+ return TRUE;
+
+failed:
+ connection_lost(session, EIO);
+
+ return FALSE;
+}
+
+static struct avdtp *find_session(GSList *list, const bdaddr_t *dst)
+{
+ GSList *l;
+
+ for (l = list; l != NULL; l = g_slist_next(l)) {
+ struct avdtp *s = l->data;
+
+ if (bacmp(dst, &s->dst))
+ continue;
+
+ return s;
+ }
+
+ return NULL;
+}
+
+static uint16_t get_version(struct avdtp *session)
+{
+ struct btd_adapter *adapter;
+ struct btd_device *device;
+ const sdp_record_t *rec;
+ sdp_list_t *protos;
+ sdp_data_t *proto_desc;
+ char addr[18];
+ uint16_t ver = 0x0100;
+
+ adapter = manager_find_adapter(&session->server->src);
+ if (!adapter)
+ goto done;
+
+ ba2str(&session->dst, addr);
+ device = adapter_find_device(adapter, addr);
+ if (!device)
+ goto done;
+
+ rec = btd_device_get_record(device, A2DP_SINK_UUID);
+ if (!rec)
+ rec = btd_device_get_record(device, A2DP_SOURCE_UUID);
+
+ if (!rec)
+ goto done;
+
+ if (sdp_get_access_protos(rec, &protos) < 0)
+ goto done;
+
+ proto_desc = sdp_get_proto_desc(protos, AVDTP_UUID);
+ if (proto_desc && proto_desc->dtd == SDP_UINT16)
+ ver = proto_desc->val.uint16;
+
+ sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
+ sdp_list_free(protos, NULL);
+
+done:
+ return ver;
+}
+
+static struct avdtp *avdtp_get_internal(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct avdtp_server *server;
+ struct avdtp *session;
+
+ assert(src != NULL);
+ assert(dst != NULL);
+
+ server = find_server(servers, src);
+ if (server == NULL)
+ return NULL;
+
+ session = find_session(server->sessions, dst);
+ if (session) {
+ if (session->pending_auth)
+ return NULL;
+ else
+ return session;
+ }
+
+ session = g_new0(struct avdtp, 1);
+
+ session->server = server;
+ bacpy(&session->dst, dst);
+ session->ref = 1;
+ /* We don't use avdtp_set_state() here since this isn't a state change
+ * but just setting of the initial state */
+ session->state = AVDTP_SESSION_STATE_DISCONNECTED;
+ session->auto_dc = TRUE;
+
+ session->version = get_version(session);
+
+ server->sessions = g_slist_append(server->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);
+}
+
+static void avdtp_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+ struct avdtp *session = user_data;
+ char address[18];
+ GError *gerr = NULL;
+
+ if (err) {
+ error("%s", err->message);
+ goto failed;
+ }
+
+ if (!session->io)
+ session->io = g_io_channel_ref(chan);
+
+ bt_io_get(chan, BT_IO_L2CAP, &gerr,
+ BT_IO_OPT_OMTU, &session->omtu,
+ BT_IO_OPT_IMTU, &session->imtu,
+ BT_IO_OPT_INVALID);
+ if (gerr) {
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ goto failed;
+ }
+
+ ba2str(&session->dst, address);
+ DBG("AVDTP: connected %s channel to %s",
+ session->pending_open ? "transport" : "signaling",
+ address);
+
+ if (session->state == AVDTP_SESSION_STATE_CONNECTING) {
+ DBG("AVDTP imtu=%u, omtu=%u", session->imtu, session->omtu);
+
+ session->buf = g_malloc0(session->imtu);
+ avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTED);
+
+ if (session->io_id)
+ g_source_remove(session->io_id);
+
+ /* This watch should be low priority since otherwise the
+ * connect callback might be dispatched before the session
+ * callback if the kernel wakes us up at the same time for
+ * them. This could happen if a headset is very quick in
+ * sending the Start command after connecting the stream
+ * transport channel.
+ */
+ session->io_id = g_io_add_watch_full(chan,
+ G_PRIORITY_LOW,
+ G_IO_IN | G_IO_ERR | G_IO_HUP
+ | G_IO_NVAL,
+ (GIOFunc) session_cb, session,
+ NULL);
+
+ if (session->stream_setup) {
+ set_disconnect_timer(session);
+ avdtp_set_auto_disconnect(session, FALSE);
+ }
+ } else if (session->pending_open)
+ handle_transport_connect(session, chan, session->imtu,
+ session->omtu);
+ else
+ goto failed;
+
+ process_queue(session);
+
+ return;
+
+failed:
+ if (session->pending_open) {
+ struct avdtp_stream *stream = session->pending_open;
+
+ handle_transport_connect(session, NULL, 0, 0);
+
+ if (avdtp_abort(session, stream) < 0)
+ avdtp_sep_set_state(session, stream->lsep,
+ AVDTP_STATE_IDLE);
+ } else
+ connection_lost(session, EIO);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+ struct avdtp *session = user_data;
+ GError *err = NULL;
+
+ if (derr && dbus_error_is_set(derr)) {
+ error("Access denied: %s", derr->message);
+ connection_lost(session, EACCES);
+ return;
+ }
+
+ if (!bt_io_accept(session->io, avdtp_connect_cb, session, NULL,
+ &err)) {
+ error("bt_io_accept: %s", err->message);
+ connection_lost(session, EACCES);
+ g_error_free(err);
+ return;
+ }
+
+ /* This is so that avdtp_connect_cb will know to do the right thing
+ * with respect to the disconnect timer */
+ session->stream_setup = TRUE;
+}
+
+static void avdtp_confirm_cb(GIOChannel *chan, gpointer data)
+{
+ struct avdtp *session;
+ struct audio_device *dev;
+ char address[18];
+ bdaddr_t src, dst;
+ int perr;
+ GError *err = NULL;
+
+ bt_io_get(chan, BT_IO_L2CAP, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_DEST, address,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ error("%s", err->message);
+ g_error_free(err);
+ goto drop;
+ }
+
+ DBG("AVDTP: incoming connect from %s", address);
+
+ session = avdtp_get_internal(&src, &dst);
+ if (!session)
+ goto drop;
+
+ /* This state (ie, session is already *connecting*) happens when the
+ * device initiates a connect (really a config'd L2CAP channel) even
+ * though there is a connect we initiated in progress. In sink.c &
+ * source.c, this state is referred to as XCASE connect:connect.
+ * Abort the device's channel in favor of our own.
+ */
+ if (session->state == AVDTP_SESSION_STATE_CONNECTING) {
+ DBG("connect already in progress (XCASE connect:connect)");
+ goto drop;
+ }
+
+ if (session->pending_open && session->pending_open->open_acp) {
+ if (!bt_io_accept(chan, avdtp_connect_cb, session, NULL, NULL))
+ goto drop;
+ return;
+ }
+
+ if (session->io) {
+ error("Refusing unexpected connect from %s", address);
+ goto drop;
+ }
+
+ dev = manager_get_device(&src, &dst, FALSE);
+ if (!dev) {
+ dev = manager_get_device(&src, &dst, TRUE);
+ if (!dev) {
+ error("Unable to get audio device object for %s",
+ address);
+ goto drop;
+ }
+ btd_device_add_uuid(dev->btd_dev, ADVANCED_AUDIO_UUID);
+ }
+
+ session->io = g_io_channel_ref(chan);
+ avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING);
+
+ session->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) session_cb, session);
+
+ perr = audio_device_request_authorization(dev, ADVANCED_AUDIO_UUID,
+ auth_cb, session);
+ if (perr < 0) {
+ avdtp_unref(session);
+ goto drop;
+ }
+
+ dev->auto_connect = auto_connect;
+
+ return;
+
+drop:
+ g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static GIOChannel *l2cap_connect(struct avdtp *session)
+{
+ GError *err = NULL;
+ GIOChannel *io;
+
+ io = bt_io_connect(BT_IO_L2CAP, avdtp_connect_cb, session,
+ NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &session->server->src,
+ BT_IO_OPT_DEST_BDADDR, &session->dst,
+ BT_IO_OPT_PSM, AVDTP_PSM,
+ BT_IO_OPT_INVALID);
+ if (!io) {
+ error("%s", err->message);
+ g_error_free(err);
+ return NULL;
+ }
+
+ return io;
+}
+
+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 uint8_t req_get_seid(struct pending_req *req)
+{
+ if (req->signal_id == AVDTP_DISCOVER)
+ return 0;
+
+ return ((struct seid_req *) (req->data))->acp_seid;
+}
+
+static int cancel_request(struct avdtp *session, int err)
+{
+ struct pending_req *req;
+ struct seid_req sreq;
+ struct avdtp_local_sep *lsep;
+ struct avdtp_stream *stream;
+ uint8_t seid;
+ struct avdtp_error averr;
+
+ req = session->req;
+ session->req = NULL;
+
+ avdtp_error_init(&averr, AVDTP_ERRNO, err);
+
+ seid = req_get_seid(req);
+ if (seid)
+ stream = find_stream_by_rseid(session, seid);
+ else
+ stream = NULL;
+
+ if (stream) {
+ stream->abort_int = TRUE;
+ lsep = stream->lsep;
+ } else
+ lsep = NULL;
+
+ switch (req->signal_id) {
+ case AVDTP_RECONFIGURE:
+ error("Reconfigure: %s (%d)", strerror(err), err);
+ if (lsep && lsep->cfm && lsep->cfm->reconfigure)
+ lsep->cfm->reconfigure(session, lsep, stream, &averr,
+ lsep->user_data);
+ break;
+ case AVDTP_OPEN:
+ error("Open: %s (%d)", strerror(err), err);
+ if (lsep && lsep->cfm && lsep->cfm->open)
+ lsep->cfm->open(session, lsep, stream, &averr,
+ lsep->user_data);
+ break;
+ case AVDTP_START:
+ error("Start: %s (%d)", strerror(err), err);
+ if (lsep && lsep->cfm && lsep->cfm->start)
+ lsep->cfm->start(session, lsep, stream, &averr,
+ lsep->user_data);
+ break;
+ case AVDTP_SUSPEND:
+ error("Suspend: %s (%d)", strerror(err), err);
+ if (lsep && lsep->cfm && lsep->cfm->suspend)
+ lsep->cfm->suspend(session, lsep, stream, &averr,
+ lsep->user_data);
+ break;
+ case AVDTP_CLOSE:
+ error("Close: %s (%d)", strerror(err), err);
+ if (lsep && lsep->cfm && lsep->cfm->close) {
+ lsep->cfm->close(session, lsep, stream, &averr,
+ lsep->user_data);
+ if (stream)
+ stream->close_int = FALSE;
+ }
+ break;
+ case AVDTP_SET_CONFIGURATION:
+ error("SetConfiguration: %s (%d)", strerror(err), err);
+ if (lsep && lsep->cfm && lsep->cfm->set_configuration)
+ lsep->cfm->set_configuration(session, lsep, stream,
+ &averr, lsep->user_data);
+ goto failed;
+ case AVDTP_DISCOVER:
+ error("Discover: %s (%d)", strerror(err), err);
+ goto failed;
+ case AVDTP_GET_CAPABILITIES:
+ error("GetCapabilities: %s (%d)", strerror(err), err);
+ goto failed;
+ case AVDTP_ABORT:
+ error("Abort: %s (%d)", strerror(err), err);
+ goto failed;
+ }
+
+ if (!stream)
+ goto failed;
+
+ memset(&sreq, 0, sizeof(sreq));
+ sreq.acp_seid = seid;
+
+ err = send_request(session, TRUE, stream, AVDTP_ABORT, &sreq,
+ sizeof(sreq));
+ if (err < 0) {
+ error("Unable to send abort request");
+ goto failed;
+ }
+
+ goto done;
+
+failed:
+ connection_lost(session, err);
+done:
+ pending_req_free(req);
+ return err;
+}
+
+static gboolean request_timeout(gpointer user_data)
+{
+ struct avdtp *session = user_data;
+
+ cancel_request(session, ETIMEDOUT);
+
+ return FALSE;
+}
+
+static int send_req(struct avdtp *session, gboolean priority,
+ struct pending_req *req)
+{
+ static int transaction = 0;
+ int err;
+
+ if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) {
+ session->io = l2cap_connect(session);
+ if (!session->io)
+ goto failed;
+ avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING);
+ }
+
+ if (session->state < AVDTP_SESSION_STATE_CONNECTED ||
+ session->req != NULL) {
+ queue_request(session, req, priority);
+ return 0;
+ }
+
+ req->transaction = transaction++;
+ transaction %= 16;
+
+ /* FIXME: Should we retry to send if the buffer
+ was not totally sent or in case of EINTR? */
+ if (!avdtp_send(session, req->transaction, AVDTP_MSG_TYPE_COMMAND,
+ req->signal_id, req->data, req->data_size)) {
+ err = -EIO;
+ goto failed;
+ }
+
+ session->req = req;
+
+ req->timeout = g_timeout_add_seconds(req->signal_id == AVDTP_ABORT ?
+ ABORT_TIMEOUT : REQ_TIMEOUT,
+ request_timeout,
+ session);
+ return 0;
+
+failed:
+ g_free(req->data);
+ g_free(req);
+ return err;
+}
+
+static int send_request(struct avdtp *session, gboolean priority,
+ struct avdtp_stream *stream, uint8_t signal_id,
+ void *buffer, size_t size)
+{
+ struct pending_req *req;
+
+ if (stream && stream->abort_int && signal_id != AVDTP_ABORT) {
+ DBG("Unable to send requests while aborting");
+ return -EINVAL;
+ }
+
+ req = g_new0(struct pending_req, 1);
+ req->signal_id = signal_id;
+ req->data = g_malloc(size);
+ memcpy(req->data, buffer, size);
+ req->data_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;
+ uint8_t getcap_cmd;
+
+ if (session->version >= 0x0103 && session->server->version >= 0x0103)
+ getcap_cmd = AVDTP_GET_ALL_CAPABILITIES;
+ else
+ getcap_cmd = AVDTP_GET_CAPABILITIES;
+
+ sep_count = size / sizeof(struct seid_info);
+
+ for (i = 0; i < sep_count; i++) {
+ struct avdtp_remote_sep *sep;
+ struct avdtp_stream *stream;
+ struct seid_req req;
+ int ret;
+
+ DBG("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));
+ req.acp_seid = sep->seid;
+
+ ret = send_request(session, TRUE, NULL, getcap_cmd,
+ &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,
+ unsigned 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->data)->acp_seid;
+
+ sep = find_remote_sep(session->seps, seid);
+
+ DBG("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->delay_reporting = FALSE;
+ }
+
+ sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp),
+ &sep->codec, &sep->delay_reporting);
+
+ return TRUE;
+}
+
+static gboolean avdtp_set_configuration_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct avdtp_single_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_single_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;
+
+ stream->io = l2cap_connect(session);
+ if (!stream->io) {
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
+ return FALSE;
+ }
+
+ session->pending_open = stream;
+
+ 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);
+
+ /* We might be in STREAMING already if both sides send START_CMD at the
+ * same time and the one in SNK role doesn't reject it as it should */
+ if (sep->state != AVDTP_STATE_STREAMING)
+ 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(stream);
+
+ return TRUE;
+}
+
+static gboolean avdtp_suspend_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ void *data, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+
+ 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;
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING);
+
+ 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_delay_report_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ void *data, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ if (sep->cfm && sep->cfm->delay_report)
+ sep->cfm->delay_report(session, sep, stream, NULL, sep->user_data);
+
+ return TRUE;
+}
+
+static gboolean avdtp_parse_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ uint8_t transaction, uint8_t signal_id,
+ void *buf, int size)
+{
+ struct pending_req *next;
+ const char *get_all = "";
+
+ if (session->prio_queue)
+ next = session->prio_queue->data;
+ else if (session->req_queue)
+ next = session->req_queue->data;
+ else
+ next = NULL;
+
+ switch (signal_id) {
+ case AVDTP_DISCOVER:
+ DBG("DISCOVER request succeeded");
+ return avdtp_discover_resp(session, buf, size);
+ case AVDTP_GET_ALL_CAPABILITIES:
+ get_all = "ALL_";
+ case AVDTP_GET_CAPABILITIES:
+ DBG("GET_%sCAPABILITIES request succeeded", get_all);
+ if (!avdtp_get_capabilities_resp(session, buf, size))
+ return FALSE;
+ if (!(next && (next->signal_id == AVDTP_GET_CAPABILITIES ||
+ next->signal_id == AVDTP_GET_ALL_CAPABILITIES)))
+ finalize_discovery(session, 0);
+ return TRUE;
+ }
+
+ /* The remaining commands require an existing stream so bail out
+ * here if the stream got unexpectedly disconnected */
+ if (!stream) {
+ DBG("AVDTP: stream was closed while waiting for reply");
+ return TRUE;
+ }
+
+ switch (signal_id) {
+ case AVDTP_SET_CONFIGURATION:
+ DBG("SET_CONFIGURATION request succeeded");
+ return avdtp_set_configuration_resp(session, stream,
+ buf, size);
+ case AVDTP_RECONFIGURE:
+ DBG("RECONFIGURE request succeeded");
+ return avdtp_reconfigure_resp(session, stream, buf, size);
+ case AVDTP_OPEN:
+ DBG("OPEN request succeeded");
+ return avdtp_open_resp(session, stream, buf, size);
+ case AVDTP_SUSPEND:
+ DBG("SUSPEND request succeeded");
+ return avdtp_suspend_resp(session, stream, buf, size);
+ case AVDTP_START:
+ DBG("START request succeeded");
+ return avdtp_start_resp(session, stream, buf, size);
+ case AVDTP_CLOSE:
+ DBG("CLOSE request succeeded");
+ return avdtp_close_resp(session, stream, buf, size);
+ case AVDTP_ABORT:
+ DBG("ABORT request succeeded");
+ return avdtp_abort_resp(session, stream, buf, size);
+ case AVDTP_DELAY_REPORT:
+ DBG("DELAY_REPORT request succeeded");
+ return avdtp_delay_report_resp(session, stream, buf, size);
+ }
+
+ error("Unknown signal id in accept response: %u", signal_id);
+ return TRUE;
+}
+
+static gboolean seid_rej_to_err(struct seid_rej *rej, unsigned 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, 0x00, rej->error);
+
+ return TRUE;
+}
+
+static gboolean conf_rej_to_err(struct conf_rej *rej, unsigned int size,
+ struct avdtp_error *err)
+{
+ if (size < sizeof(struct conf_rej)) {
+ error("Too small packet for conf_rej");
+ return FALSE;
+ }
+
+ avdtp_error_init(err, rej->category, rej->error);
+
+ return TRUE;
+}
+
+static gboolean stream_rej_to_err(struct stream_rej *rej, unsigned int size,
+ struct avdtp_error *err,
+ uint8_t *acp_seid)
+{
+ if (size < sizeof(struct stream_rej)) {
+ error("Too small packet for stream_rej");
+ return FALSE;
+ }
+
+ avdtp_error_init(err, 0x00, rej->error);
+
+ if (acp_seid)
+ *acp_seid = rej->acp_seid;
+
+ return TRUE;
+}
+
+static gboolean avdtp_parse_rej(struct avdtp *session,
+ struct avdtp_stream *stream,
+ uint8_t transaction, uint8_t signal_id,
+ void *buf, int size)
+{
+ struct avdtp_error err;
+ uint8_t acp_seid;
+ struct avdtp_local_sep *sep = stream ? stream->lsep : NULL;
+
+ switch (signal_id) {
+ case AVDTP_DISCOVER:
+ if (!seid_rej_to_err(buf, size, &err))
+ return FALSE;
+ error("DISCOVER request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ return TRUE;
+ case AVDTP_GET_CAPABILITIES:
+ case AVDTP_GET_ALL_CAPABILITIES:
+ if (!seid_rej_to_err(buf, 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(buf, 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(buf, size, &err))
+ 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(buf, size, &err))
+ 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(buf, 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(buf, 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(buf, 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);
+ stream->close_int = FALSE;
+ }
+ return TRUE;
+ case AVDTP_ABORT:
+ if (!stream_rej_to_err(buf, 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 FALSE;
+ case AVDTP_DELAY_REPORT:
+ if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+ return FALSE;
+ error("DELAY_REPORT request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->delay_report)
+ sep->cfm->delay_report(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
+ default:
+ error("Unknown reject response signal id: %u", signal_id);
+ return TRUE;
+ }
+}
+
+gboolean avdtp_is_connected(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct avdtp_server *server;
+ struct avdtp *session;
+
+ server = find_server(servers, src);
+ if (!server)
+ return FALSE;
+
+ session = find_session(server->sessions, dst);
+ if (!session)
+ return FALSE;
+
+ if (session->state != AVDTP_SESSION_STATE_DISCONNECTED)
+ return TRUE;
+
+ return FALSE;
+}
+
+struct avdtp_service_capability *avdtp_stream_get_codec(
+ struct avdtp_stream *stream)
+{
+ GSList *l;
+
+ for (l = stream->caps; l; l = l->next) {
+ struct avdtp_service_capability *cap = l->data;
+
+ if (cap->category == AVDTP_MEDIA_CODEC)
+ return cap;
+ }
+
+ return NULL;
+}
+
+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)
+ continue;
+
+ if (memcmp(stream_cap->data, cap->data, cap->length) == 0)
+ 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;
+}
+
+struct avdtp_remote_sep *avdtp_stream_get_remote_sep(
+ struct avdtp_stream *stream)
+{
+ return avdtp_get_remote_sep(stream->session, stream->rseid);
+}
+
+gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock,
+ uint16_t *imtu, uint16_t *omtu,
+ GSList **caps)
+{
+ if (stream->io == NULL)
+ return FALSE;
+
+ if (sock)
+ *sock = g_io_channel_unix_get_fd(stream->io);
+
+ 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_remote_sep *avdtp_get_remote_sep(struct avdtp *session,
+ uint8_t seid)
+{
+ GSList *l;
+
+ for (l = session->seps; l; l = l->next) {
+ struct avdtp_remote_sep *sep = l->data;
+
+ if (sep->seid == seid)
+ return sep;
+ }
+
+ return NULL;
+}
+
+uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep)
+{
+ return sep->seid;
+}
+
+uint8_t avdtp_get_type(struct avdtp_remote_sep *sep)
+{
+ return sep->type;
+}
+
+struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep)
+{
+ return sep->codec;
+}
+
+gboolean avdtp_get_delay_reporting(struct avdtp_remote_sep *sep)
+{
+ return sep->delay_reporting;
+}
+
+struct avdtp_stream *avdtp_get_stream(struct avdtp_remote_sep *sep)
+{
+ return sep->stream;
+}
+
+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_DELAY_REPORTING)
+ return NULL;
+
+ cap = g_malloc(sizeof(struct avdtp_service_capability) + length);
+ cap->category = category;
+ cap->length = length;
+ memcpy(cap->data, data, length);
+
+ return cap;
+}
+
+static gboolean process_discover(gpointer data)
+{
+ struct avdtp *session = data;
+
+ finalize_discovery(session, 0);
+
+ return FALSE;
+}
+
+int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb,
+ void *user_data)
+{
+ int err;
+
+ if (session->discov_cb)
+ return -EBUSY;
+
+ if (session->seps) {
+ session->discov_cb = cb;
+ session->user_data = user_data;
+ g_idle_add(process_discover, session);
+ return 0;
+ }
+
+ err = send_request(session, FALSE, NULL, AVDTP_DISCOVER, NULL, 0);
+ if (err == 0) {
+ session->discov_cb = cb;
+ session->user_data = user_data;
+ }
+
+ return err;
+}
+
+gboolean avdtp_stream_remove_cb(struct avdtp *session,
+ struct avdtp_stream *stream,
+ unsigned int id)
+{
+ GSList *l;
+ struct stream_callback *cb;
+
+ if (!stream)
+ return FALSE;
+
+ for (cb = NULL, l = stream->callbacks; l != NULL; l = l->next) {
+ struct stream_callback *tmp = l->data;
+ if (tmp && 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));
+ req.acp_seid = stream->rseid;
+
+ return send_request(session, FALSE, stream, AVDTP_GET_CONFIGURATION,
+ &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 err, caps_len;
+ struct avdtp_service_capability *cap;
+ GSList *l;
+
+ if (session->state != AVDTP_SESSION_STATE_CONNECTED)
+ return -ENOTCONN;
+
+ if (!(lsep && rsep))
+ return -EINVAL;
+
+ DBG("%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;
+
+ if (rsep->delay_reporting && lsep->delay_reporting) {
+ struct avdtp_service_capability *delay_reporting;
+
+ delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+ NULL, 0);
+ caps = g_slist_append(caps, delay_reporting);
+ new_stream->delay_reporting = TRUE;
+ }
+
+ g_slist_foreach(caps, copy_capabilities, &new_stream->caps);
+
+ /* 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);
+
+ 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;
+ }
+
+ err = send_request(session, FALSE, new_stream,
+ AVDTP_SET_CONFIGURATION, req,
+ sizeof(struct setconf_req) + caps_len);
+ if (err < 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 err;
+}
+
+int avdtp_reconfigure(struct avdtp *session, GSList *caps,
+ struct avdtp_stream *stream)
+{
+ struct reconf_req *req;
+ unsigned char *ptr;
+ int caps_len, err;
+ 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);
+
+ 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;
+ }
+
+ err = send_request(session, FALSE, stream, AVDTP_RECONFIGURE, req,
+ sizeof(*req) + caps_len);
+ g_free(req);
+
+ return err;
+}
+
+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));
+ req.acp_seid = stream->rseid;
+
+ return send_request(session, FALSE, stream, AVDTP_OPEN,
+ &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;
+
+ if (stream->close_int == TRUE) {
+ error("avdtp_start: rejecting start since close is initiated");
+ return -EINVAL;
+ }
+
+ memset(&req, 0, sizeof(req));
+ req.first_seid.seid = stream->rseid;
+
+ return send_request(session, FALSE, stream, AVDTP_START,
+ &req, sizeof(req));
+}
+
+int avdtp_close(struct avdtp *session, struct avdtp_stream *stream,
+ gboolean immediate)
+{
+ struct seid_req req;
+ int ret;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state < AVDTP_STATE_OPEN)
+ return -EINVAL;
+
+ if (stream->close_int == TRUE) {
+ error("avdtp_close: rejecting since close is already initiated");
+ return -EINVAL;
+ }
+
+ if (immediate && session->req && stream == session->req->stream)
+ return avdtp_abort(session, stream);
+
+ memset(&req, 0, sizeof(req));
+ req.acp_seid = stream->rseid;
+
+ ret = send_request(session, FALSE, stream, AVDTP_CLOSE,
+ &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 || stream->close_int)
+ return -EINVAL;
+
+ memset(&req, 0, sizeof(req));
+ req.acp_seid = stream->rseid;
+
+ return send_request(session, FALSE, stream, AVDTP_SUSPEND,
+ &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_ABORTING)
+ return -EINVAL;
+
+ if (session->req && stream == session->req->stream)
+ return cancel_request(session, ECANCELED);
+
+ memset(&req, 0, sizeof(req));
+ req.acp_seid = stream->rseid;
+
+ ret = send_request(session, TRUE, stream, AVDTP_ABORT,
+ &req, sizeof(req));
+ if (ret == 0)
+ stream->abort_int = TRUE;
+
+ return ret;
+}
+
+int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream,
+ uint16_t delay)
+{
+ struct delay_req req;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state != AVDTP_STATE_CONFIGURED &&
+ stream->lsep->state != AVDTP_STATE_STREAMING)
+ return -EINVAL;
+
+ if (!stream->delay_reporting || session->version < 0x0103 ||
+ session->server->version < 0x0103)
+ return -EINVAL;
+
+ stream->delay = delay;
+
+ memset(&req, 0, sizeof(req));
+ req.acp_seid = stream->rseid;
+ req.delay = htons(delay);
+
+ return send_request(session, TRUE, stream, AVDTP_DELAY_REPORT,
+ &req, sizeof(req));
+}
+
+struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type,
+ uint8_t media_type,
+ uint8_t codec_type,
+ gboolean delay_reporting,
+ struct avdtp_sep_ind *ind,
+ struct avdtp_sep_cfm *cfm,
+ void *user_data)
+{
+ struct avdtp_server *server;
+ struct avdtp_local_sep *sep;
+
+ server = find_server(servers, src);
+ if (!server)
+ return NULL;
+
+ if (g_slist_length(server->seps) > MAX_SEID)
+ return NULL;
+
+ sep = g_new0(struct avdtp_local_sep, 1);
+
+ sep->state = AVDTP_STATE_IDLE;
+ sep->info.seid = g_slist_length(server->seps) + 1;
+ 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;
+ sep->server = server;
+ sep->delay_reporting = TRUE;
+
+ DBG("SEP %p registered: type:%d codec:%d seid:%d", sep,
+ sep->info.type, sep->codec, sep->info.seid);
+ server->seps = g_slist_append(server->seps, sep);
+
+ return sep;
+}
+
+int avdtp_unregister_sep(struct avdtp_local_sep *sep)
+{
+ struct avdtp_server *server;
+
+ if (!sep)
+ return -EINVAL;
+
+ server = sep->server;
+ server->seps = g_slist_remove(server->seps, sep);
+
+ if (sep->stream)
+ release_stream(sep->stream, sep->stream->session);
+
+ DBG("SEP %p unregistered: type:%d codec:%d seid:%d", sep,
+ sep->info.type, sep->codec, sep->info.seid);
+
+ g_free(sep);
+
+ return 0;
+}
+
+static GIOChannel *avdtp_server_socket(const bdaddr_t *src, gboolean master)
+{
+ GError *err = NULL;
+ GIOChannel *io;
+
+ io = bt_io_listen(BT_IO_L2CAP, NULL, avdtp_confirm_cb,
+ NULL, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, src,
+ BT_IO_OPT_PSM, AVDTP_PSM,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+ BT_IO_OPT_MASTER, master,
+ BT_IO_OPT_INVALID);
+ if (!io) {
+ error("%s", err->message);
+ g_error_free(err);
+ }
+
+ return io;
+}
+
+const char *avdtp_strerror(struct avdtp_error *err)
+{
+ if (err->category == AVDTP_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->server->src);
+ if (dst)
+ bacpy(dst, &session->dst);
+}
+
+int avdtp_init(const bdaddr_t *src, GKeyFile *config, uint16_t *version)
+{
+ GError *err = NULL;
+ gboolean tmp, master = TRUE;
+ struct avdtp_server *server;
+ uint16_t ver = 0x0102;
+
+ if (!config)
+ goto proceed;
+
+ tmp = g_key_file_get_boolean(config, "General",
+ "Master", &err);
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else
+ master = tmp;
+
+ tmp = g_key_file_get_boolean(config, "General", "AutoConnect",
+ &err);
+ if (err)
+ g_clear_error(&err);
+ else
+ auto_connect = tmp;
+
+ if (g_key_file_get_boolean(config, "A2DP", "DelayReporting", NULL))
+ ver = 0x0103;
+
+proceed:
+ server = g_new0(struct avdtp_server, 1);
+ if (!server)
+ return -ENOMEM;
+
+ server->version = ver;
+
+ if (version)
+ *version = server->version;
+
+ server->io = avdtp_server_socket(src, master);
+ if (!server->io) {
+ g_free(server);
+ return -1;
+ }
+
+ bacpy(&server->src, src);
+
+ servers = g_slist_append(servers, server);
+
+ return 0;
+}
+
+void avdtp_exit(const bdaddr_t *src)
+{
+ struct avdtp_server *server;
+ GSList *l;
+
+ server = find_server(servers, src);
+ if (!server)
+ return;
+
+ for (l = server->sessions; l; l = l->next) {
+ struct avdtp *session = l->data;
+
+ connection_lost(session, -ECONNABORTED);
+ }
+
+ servers = g_slist_remove(servers, server);
+
+ g_io_channel_shutdown(server->io, TRUE, NULL);
+ g_io_channel_unref(server->io);
+ g_free(server);
+}
+
+gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream)
+{
+ return g_slist_find(session->streams, stream) ? TRUE : FALSE;
+}
+
+void avdtp_set_auto_disconnect(struct avdtp *session, gboolean auto_dc)
+{
+ session->auto_dc = auto_dc;
+}
+
+gboolean avdtp_stream_setup_active(struct avdtp *session)
+{
+ return session->stream_setup;
+}
+
+void avdtp_set_device_disconnect(struct avdtp *session, gboolean dev_dc)
+{
+ session->device_disconnect = dev_dc;
+}
+
+unsigned int avdtp_add_state_cb(avdtp_session_state_cb cb, void *user_data)
+{
+ struct avdtp_state_callback *state_cb;
+ static unsigned int id = 0;
+
+ state_cb = g_new(struct avdtp_state_callback, 1);
+ state_cb->cb = cb;
+ state_cb->user_data = user_data;
+ state_cb->id = ++id;
+
+ avdtp_callbacks = g_slist_append(avdtp_callbacks, state_cb);;
+
+ return state_cb->id;
+}
+
+gboolean avdtp_remove_state_cb(unsigned int id)
+{
+ GSList *l;
+
+ for (l = avdtp_callbacks; l != NULL; l = l->next) {
+ struct avdtp_state_callback *cb = l->data;
+ if (cb && cb->id == id) {
+ avdtp_callbacks = g_slist_remove(avdtp_callbacks, cb);
+ g_free(cb);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
diff --git a/audio/avdtp.h b/audio/avdtp.h
new file mode 100644
index 0000000..5f37dc3
--- /dev/null
+++ b/audio/avdtp.h
@@ -0,0 +1,316 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+typedef enum {
+ AVDTP_SESSION_STATE_DISCONNECTED,
+ AVDTP_SESSION_STATE_CONNECTING,
+ AVDTP_SESSION_STATE_CONNECTED
+} avdtp_session_state_t;
+
+struct avdtp;
+struct avdtp_stream;
+struct avdtp_local_sep;
+struct avdtp_remote_sep;
+struct avdtp_error {
+ uint8_t category;
+ 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
+#define AVDTP_DELAY_REPORTING 0x08
+#define AVDTP_ERRNO 0xff
+
+/* 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_session_state_cb) (struct audio_device *dev,
+ struct avdtp *session,
+ avdtp_session_state_t old_state,
+ avdtp_session_state_t new_state,
+ void *user_data);
+
+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);
+
+typedef void (*avdtp_set_configuration_cb) (struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err);
+
+/* 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);
+ void (*delay_report) (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,
+ gboolean get_all,
+ 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,
+ avdtp_set_configuration_cb cb,
+ 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);
+ gboolean (*delayreport) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ uint8_t rseid, uint16_t delay,
+ 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_remote_sep *avdtp_get_remote_sep(struct avdtp *session,
+ uint8_t seid);
+
+uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep);
+
+uint8_t avdtp_get_type(struct avdtp_remote_sep *sep);
+
+struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep);
+
+gboolean avdtp_get_delay_reporting(struct avdtp_remote_sep *sep);
+
+struct avdtp_stream *avdtp_get_stream(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);
+struct avdtp_service_capability *avdtp_stream_get_codec(
+ struct avdtp_stream *stream);
+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);
+struct avdtp_remote_sep *avdtp_stream_get_remote_sep(
+ struct avdtp_stream *stream);
+
+unsigned int avdtp_add_state_cb(avdtp_session_state_cb cb, void *user_data);
+
+gboolean avdtp_remove_state_cb(unsigned int id);
+
+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,
+ gboolean immediate);
+int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream,
+ uint16_t delay);
+
+struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type,
+ uint8_t media_type,
+ uint8_t codec_type,
+ gboolean delay_reporting,
+ struct avdtp_sep_ind *ind,
+ struct avdtp_sep_cfm *cfm,
+ void *user_data);
+
+/* Find a matching pair of local and remote SEP ID's */
+struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session,
+ struct avdtp_local_sep *lsep);
+
+int avdtp_unregister_sep(struct avdtp_local_sep *sep);
+
+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);
+uint8_t avdtp_error_category(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);
+
+void avdtp_set_auto_disconnect(struct avdtp *session, gboolean auto_dc);
+gboolean avdtp_stream_setup_active(struct avdtp *session);
+void avdtp_set_device_disconnect(struct avdtp *session, gboolean dev_dc);
+
+int avdtp_init(const bdaddr_t *src, GKeyFile *config, uint16_t *version);
+void avdtp_exit(const bdaddr_t *src);
diff --git a/audio/bluetooth.conf b/audio/bluetooth.conf
new file mode 100644
index 0000000..55b51e4
--- /dev/null
+++ b/audio/bluetooth.conf
@@ -0,0 +1,36 @@
+# Please note that this ALSA configuration file fragment needs be enabled in
+# /etc/asound.conf or a similar configuration file with directives similar to
+# the following:
+#
+#@hooks [
+# {
+# func load
+# files [
+# "/etc/alsa/bluetooth.conf"
+# ]
+# errors false
+# }
+#]
+
+pcm.rawbluetooth {
+ @args [ ADDRESS ]
+ @args.ADDRESS {
+ type string
+ }
+ type bluetooth
+ device $ADDRESS
+}
+
+pcm.bluetooth {
+ @args [ ADDRESS ]
+ @args.ADDRESS {
+ type string
+ }
+ type plug
+ slave {
+ pcm {
+ type bluetooth
+ device $ADDRESS
+ }
+ }
+}
diff --git a/audio/control.c b/audio/control.c
new file mode 100644
index 0000000..95c0406
--- /dev/null
+++ b/audio/control.c
@@ -0,0 +1,1193 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#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 <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "error.h"
+#include "uinput.h"
+#include "adapter.h"
+#include "../src/device.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "control.h"
+#include "sdpd.h"
+#include "glib-helper.h"
+#include "btio.h"
+#include "dbus-common.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_NOT_IMPLEMENTED 0x8
+#define CTYPE_ACCEPTED 0x9
+#define CTYPE_REJECTED 0xA
+#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 VOL_UP_OP 0x41
+#define VOL_DOWN_OP 0x42
+#define MUTE_OP 0x43
+#define PLAY_OP 0x44
+#define STOP_OP 0x45
+#define PAUSE_OP 0x46
+#define RECORD_OP 0x47
+#define REWIND_OP 0x48
+#define FAST_FORWARD_OP 0x49
+#define EJECT_OP 0x4a
+#define FORWARD_OP 0x4b
+#define BACKWARD_OP 0x4c
+
+#define QUIRK_NO_RELEASE 1 << 0
+
+static DBusConnection *connection = NULL;
+
+static GSList *servers = NULL;
+
+#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));
+#define AVCTP_HEADER_LENGTH 3
+
+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));
+#define AVRCP_HEADER_LENGTH 3
+
+#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));
+#define AVCTP_HEADER_LENGTH 3
+
+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));
+#define AVRCP_HEADER_LENGTH 3
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct avctp_state_callback {
+ avctp_state_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+struct avctp_server {
+ bdaddr_t src;
+ GIOChannel *io;
+ uint32_t tg_record_id;
+ uint32_t ct_record_id;
+};
+
+struct control {
+ struct audio_device *dev;
+
+ avctp_state_t state;
+
+ int uinput;
+
+ GIOChannel *io;
+ guint io_id;
+
+ uint16_t mtu;
+
+ gboolean target;
+
+ uint8_t key_quirks[256];
+};
+
+static struct {
+ const char *name;
+ uint8_t avrcp;
+ uint16_t uinput;
+} key_map[] = {
+ { "PLAY", PLAY_OP, KEY_PLAYCD },
+ { "STOP", STOP_OP, KEY_STOPCD },
+ { "PAUSE", PAUSE_OP, KEY_PAUSECD },
+ { "FORWARD", FORWARD_OP, KEY_NEXTSONG },
+ { "BACKWARD", BACKWARD_OP, KEY_PREVIOUSSONG },
+ { "REWIND", REWIND_OP, KEY_REWIND },
+ { "FAST FORWARD", FAST_FORWARD_OP, KEY_FASTFORWARD },
+ { NULL }
+};
+
+static GSList *avctp_callbacks = NULL;
+
+static void auth_cb(DBusError *derr, void *user_data);
+
+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;
+ uint16_t avrcp_ver = 0x0100, avctp_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, &avctp_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 = avrcp_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;
+ uint16_t avrcp_ver = 0x0100, avctp_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, &avctp_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 = avrcp_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 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 control *control,
+ const unsigned char *operands,
+ int operand_count)
+{
+ const char *status;
+ int pressed, i;
+
+ if (operand_count == 0)
+ return;
+
+ if (operands[0] & 0x80) {
+ status = "released";
+ pressed = 0;
+ } else {
+ status = "pressed";
+ pressed = 1;
+ }
+
+ for (i = 0; key_map[i].name != NULL; i++) {
+ uint8_t key_quirks;
+
+ if ((operands[0] & 0x7F) != key_map[i].avrcp)
+ continue;
+
+ DBG("AVRCP: %s %s", key_map[i].name, status);
+
+ key_quirks = control->key_quirks[key_map[i].avrcp];
+
+ if (key_quirks & QUIRK_NO_RELEASE) {
+ if (!pressed) {
+ DBG("AVRCP: Ignoring release");
+ break;
+ }
+
+ DBG("AVRCP: treating key press as press + release");
+ send_key(control->uinput, key_map[i].uinput, 1);
+ send_key(control->uinput, key_map[i].uinput, 0);
+ break;
+ }
+
+ send_key(control->uinput, key_map[i].uinput, pressed);
+ break;
+ }
+
+ if (key_map[i].name == NULL)
+ DBG("AVRCP: unknown button 0x%02X %s",
+ operands[0] & 0x7F, status);
+}
+
+static void avctp_disconnected(struct audio_device *dev)
+{
+ struct control *control = dev->control;
+
+ if (!control)
+ return;
+
+ if (control->io) {
+ g_io_channel_shutdown(control->io, TRUE, NULL);
+ g_io_channel_unref(control->io);
+ control->io = NULL;
+ }
+
+ if (control->io_id) {
+ g_source_remove(control->io_id);
+ control->io_id = 0;
+
+ if (control->state == AVCTP_STATE_CONNECTING)
+ audio_device_cancel_authorization(dev, auth_cb,
+ control);
+ }
+
+ if (control->uinput >= 0) {
+ char address[18];
+
+ ba2str(&dev->dst, address);
+ DBG("AVRCP: closing uinput for %s", address);
+
+ ioctl(control->uinput, UI_DEV_DESTROY);
+ close(control->uinput);
+ control->uinput = -1;
+ }
+}
+
+static void avctp_set_state(struct control *control, avctp_state_t new_state)
+{
+ GSList *l;
+ struct audio_device *dev = control->dev;
+ avctp_state_t old_state = control->state;
+ gboolean value;
+
+ switch (new_state) {
+ case AVCTP_STATE_DISCONNECTED:
+ DBG("AVCTP Disconnected");
+
+ avctp_disconnected(control->dev);
+
+ if (old_state != AVCTP_STATE_CONNECTED)
+ break;
+
+ value = FALSE;
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ "Disconnected", DBUS_TYPE_INVALID);
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_CONTROL_INTERFACE, "Connected",
+ DBUS_TYPE_BOOLEAN, &value);
+
+ if (!audio_device_is_active(dev, NULL))
+ audio_device_set_authorized(dev, FALSE);
+
+ break;
+ case AVCTP_STATE_CONNECTING:
+ DBG("AVCTP Connecting");
+ break;
+ case AVCTP_STATE_CONNECTED:
+ DBG("AVCTP Connected");
+ value = TRUE;
+ g_dbus_emit_signal(control->dev->conn, control->dev->path,
+ AUDIO_CONTROL_INTERFACE, "Connected",
+ DBUS_TYPE_INVALID);
+ emit_property_changed(control->dev->conn, control->dev->path,
+ AUDIO_CONTROL_INTERFACE, "Connected",
+ DBUS_TYPE_BOOLEAN, &value);
+ break;
+ default:
+ error("Invalid AVCTP state %d", new_state);
+ return;
+ }
+
+ control->state = new_state;
+
+ for (l = avctp_callbacks; l != NULL; l = l->next) {
+ struct avctp_state_callback *cb = l->data;
+ cb->cb(control->dev, old_state, new_state, cb->user_data);
+ }
+}
+
+static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct control *control = data;
+ unsigned char buf[1024], *operands;
+ struct avctp_header *avctp;
+ struct avrcp_header *avrcp;
+ int ret, packet_size, operand_count, sock;
+
+ if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+ goto failed;
+
+ sock = g_io_channel_unix_get_fd(control->io);
+
+ ret = read(sock, buf, sizeof(buf));
+ if (ret <= 0)
+ goto failed;
+
+ DBG("Got %d bytes of data for AVCTP session %p", ret, control);
+
+ if ((unsigned int) ret < sizeof(struct avctp_header)) {
+ error("Too small AVCTP packet");
+ goto failed;
+ }
+
+ packet_size = ret;
+
+ avctp = (struct avctp_header *) buf;
+
+ DBG("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 ((unsigned int) 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;
+
+ DBG("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_RESPONSE;
+ avrcp->code = CTYPE_NOT_IMPLEMENTED;
+ } else if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
+ avctp->ipid = 1;
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->code = CTYPE_REJECTED;
+ } else if (avctp->cr == AVCTP_COMMAND &&
+ avrcp->code == CTYPE_CONTROL &&
+ avrcp->subunit_type == SUBUNIT_PANEL &&
+ avrcp->opcode == OP_PASSTHROUGH) {
+ handle_panel_passthrough(control, operands, operand_count);
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->code = CTYPE_ACCEPTED;
+ } else if (avctp->cr == AVCTP_COMMAND &&
+ avrcp->code == CTYPE_STATUS &&
+ (avrcp->opcode == OP_UNITINFO
+ || avrcp->opcode == OP_SUBUNITINFO)) {
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->code = CTYPE_STABLE;
+ /* The first operand should be 0x07 for the UNITINFO response.
+ * Neither AVRCP (section 22.1, page 117) nor AVC Digital
+ * Interface Command Set (section 9.2.1, page 45) specs
+ * explain this value but both use it */
+ if (operand_count >= 1 && avrcp->opcode == OP_UNITINFO)
+ operands[0] = 0x07;
+ if (operand_count >= 2)
+ operands[1] = SUBUNIT_PANEL << 3;
+ DBG("reply to %s", avrcp->opcode == OP_UNITINFO ?
+ "OP_UNITINFO" : "OP_SUBUNITINFO");
+ } else {
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->code = CTYPE_REJECTED;
+ }
+ ret = write(sock, buf, packet_size);
+
+ return TRUE;
+
+failed:
+ DBG("AVCTP session %p got disconnected", control);
+ avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+ return FALSE;
+}
+
+static int uinput_create(char *name)
+{
+ struct uinput_dev dev;
+ int fd, err, i;
+
+ 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 - 1);
+
+ 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);
+
+ for (i = 0; key_map[i].name != NULL; i++)
+ ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput);
+
+ 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 control *control)
+{
+ struct audio_device *dev = control->dev;
+ char address[18], name[248 + 1];
+
+ device_get_name(dev->btd_dev, name, sizeof(name));
+ if (g_str_equal(name, "Nokia CK-20W")) {
+ control->key_quirks[FORWARD_OP] |= QUIRK_NO_RELEASE;
+ control->key_quirks[BACKWARD_OP] |= QUIRK_NO_RELEASE;
+ control->key_quirks[PLAY_OP] |= QUIRK_NO_RELEASE;
+ control->key_quirks[PAUSE_OP] |= QUIRK_NO_RELEASE;
+ }
+
+ ba2str(&dev->dst, address);
+
+ control->uinput = uinput_create(address);
+ if (control->uinput < 0)
+ error("AVRCP: failed to init uinput for %s", address);
+ else
+ DBG("AVRCP: uinput initialized for %s", address);
+}
+
+static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+ struct control *control = data;
+ char address[18];
+ uint16_t imtu;
+ GError *gerr = NULL;
+
+ if (err) {
+ avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+ error("%s", err->message);
+ return;
+ }
+
+ bt_io_get(chan, BT_IO_L2CAP, &gerr,
+ BT_IO_OPT_DEST, &address,
+ BT_IO_OPT_IMTU, &imtu,
+ BT_IO_OPT_INVALID);
+ if (gerr) {
+ avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ return;
+ }
+
+ DBG("AVCTP: connected to %s", address);
+
+ if (!control->io)
+ control->io = g_io_channel_ref(chan);
+
+ init_uinput(control);
+
+ avctp_set_state(control, AVCTP_STATE_CONNECTED);
+ control->mtu = imtu;
+ control->io_id = g_io_add_watch(chan,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) control_cb, control);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+ struct control *control = user_data;
+ GError *err = NULL;
+
+ if (control->io_id) {
+ g_source_remove(control->io_id);
+ control->io_id = 0;
+ }
+
+ if (derr && dbus_error_is_set(derr)) {
+ error("Access denied: %s", derr->message);
+ avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+ return;
+ }
+
+ if (!bt_io_accept(control->io, avctp_connect_cb, control,
+ NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
+ avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+ }
+}
+
+static void avctp_confirm_cb(GIOChannel *chan, gpointer data)
+{
+ struct control *control = NULL;
+ struct audio_device *dev;
+ char address[18];
+ bdaddr_t src, dst;
+ GError *err = NULL;
+
+ bt_io_get(chan, BT_IO_L2CAP, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_DEST, address,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ error("%s", err->message);
+ g_error_free(err);
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ return;
+ }
+
+ dev = manager_get_device(&src, &dst, TRUE);
+ if (!dev) {
+ error("Unable to get audio device object for %s", address);
+ goto drop;
+ }
+
+ if (!dev->control) {
+ btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID);
+ if (!dev->control)
+ goto drop;
+ }
+
+ control = dev->control;
+
+ if (control->io) {
+ error("Refusing unexpected connect from %s", address);
+ goto drop;
+ }
+
+ avctp_set_state(control, AVCTP_STATE_CONNECTING);
+ control->io = g_io_channel_ref(chan);
+
+ if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID,
+ auth_cb, dev->control) < 0)
+ goto drop;
+
+ control->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ control_cb, control);
+ return;
+
+drop:
+ if (!control || !control->io)
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ if (control)
+ avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+}
+
+static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master)
+{
+ GError *err = NULL;
+ GIOChannel *io;
+
+ io = bt_io_listen(BT_IO_L2CAP, NULL, avctp_confirm_cb, NULL,
+ NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, src,
+ BT_IO_OPT_PSM, AVCTP_PSM,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+ BT_IO_OPT_MASTER, master,
+ BT_IO_OPT_INVALID);
+ if (!io) {
+ error("%s", err->message);
+ g_error_free(err);
+ }
+
+ return io;
+}
+
+gboolean avrcp_connect(struct audio_device *dev)
+{
+ struct control *control = dev->control;
+ GError *err = NULL;
+ GIOChannel *io;
+
+ if (control->state > AVCTP_STATE_DISCONNECTED)
+ return TRUE;
+
+ avctp_set_state(control, AVCTP_STATE_CONNECTING);
+
+ io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, control, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+ BT_IO_OPT_DEST_BDADDR, &dev->dst,
+ BT_IO_OPT_PSM, AVCTP_PSM,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+ error("%s", err->message);
+ g_error_free(err);
+ return FALSE;
+ }
+
+ control->io = io;
+
+ return TRUE;
+}
+
+void avrcp_disconnect(struct audio_device *dev)
+{
+ struct control *control = dev->control;
+
+ if (!(control && control->io))
+ return;
+
+ avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+}
+
+int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
+{
+ sdp_record_t *record;
+ gboolean tmp, master = TRUE;
+ GError *err = NULL;
+ struct avctp_server *server;
+
+ if (config) {
+ tmp = g_key_file_get_boolean(config, "General",
+ "Master", &err);
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_error_free(err);
+ } else
+ master = tmp;
+ }
+
+ server = g_new0(struct avctp_server, 1);
+ if (!server)
+ return -ENOMEM;
+
+ if (!connection)
+ connection = dbus_connection_ref(conn);
+
+ record = avrcp_tg_record();
+ if (!record) {
+ error("Unable to allocate new service record");
+ g_free(server);
+ return -1;
+ }
+
+ if (add_record_to_server(src, record) < 0) {
+ error("Unable to register AVRCP target service record");
+ g_free(server);
+ sdp_record_free(record);
+ return -1;
+ }
+ server->tg_record_id = record->handle;
+
+ record = avrcp_ct_record();
+ if (!record) {
+ error("Unable to allocate new service record");
+ g_free(server);
+ return -1;
+ }
+
+ if (add_record_to_server(src, record) < 0) {
+ error("Unable to register AVRCP controller service record");
+ sdp_record_free(record);
+ g_free(server);
+ return -1;
+ }
+ server->ct_record_id = record->handle;
+
+ server->io = avctp_server_socket(src, master);
+ if (!server->io) {
+ remove_record_from_server(server->ct_record_id);
+ remove_record_from_server(server->tg_record_id);
+ g_free(server);
+ return -1;
+ }
+
+ bacpy(&server->src, src);
+
+ servers = g_slist_append(servers, server);
+
+ return 0;
+}
+
+static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+ GSList *l;
+
+ for (l = list; l; l = l->next) {
+ struct avctp_server *server = l->data;
+
+ if (bacmp(&server->src, src) == 0)
+ return server;
+ }
+
+ return NULL;
+}
+
+void avrcp_unregister(const bdaddr_t *src)
+{
+ struct avctp_server *server;
+
+ server = find_server(servers, src);
+ if (!server)
+ return;
+
+ servers = g_slist_remove(servers, server);
+
+ remove_record_from_server(server->ct_record_id);
+ remove_record_from_server(server->tg_record_id);
+
+ g_io_channel_shutdown(server->io, TRUE, NULL);
+ g_io_channel_unref(server->io);
+ g_free(server);
+
+ if (servers)
+ return;
+
+ 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->state == AVCTP_STATE_CONNECTED);
+
+ dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static int avctp_send_passthrough(struct control *control, uint8_t op)
+{
+ unsigned char buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 2];
+ struct avctp_header *avctp = (void *) buf;
+ struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH];
+ uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH];
+ int sk = g_io_channel_unix_get_fd(control->io);
+ static uint8_t transaction = 0;
+
+ memset(buf, 0, sizeof(buf));
+
+ avctp->transaction = transaction++;
+ 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;
+
+ operands[0] = op & 0x7f;
+ operands[1] = 0;
+
+ if (write(sk, buf, sizeof(buf)) < 0)
+ return -errno;
+
+ /* Button release */
+ avctp->transaction = transaction++;
+ operands[0] |= 0x80;
+
+ if (write(sk, buf, sizeof(buf)) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct control *control = device->control;
+ DBusMessage *reply;
+ int err;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (control->state != AVCTP_STATE_CONNECTED)
+ return btd_error_not_connected(msg);
+
+ if (!control->target)
+ return btd_error_not_supported(msg);
+
+ err = avctp_send_passthrough(control, VOL_UP_OP);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *volume_down(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct control *control = device->control;
+ DBusMessage *reply;
+ int err;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (control->state != AVCTP_STATE_CONNECTED)
+ return btd_error_not_connected(msg);
+
+ if (!control->target)
+ return btd_error_not_supported(msg);
+
+ err = avctp_send_passthrough(control, VOL_DOWN_OP);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *control_get_properties(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ gboolean value;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ /* Connected */
+ value = (device->control->state == AVCTP_STATE_CONNECTED);
+ dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static GDBusMethodTable control_methods[] = {
+ { "IsConnected", "", "b", control_is_connected,
+ G_DBUS_METHOD_FLAG_DEPRECATED },
+ { "GetProperties", "", "a{sv}",control_get_properties },
+ { "VolumeUp", "", "", volume_up },
+ { "VolumeDown", "", "", volume_down },
+ { NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable control_signals[] = {
+ { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
+ { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
+ { "PropertyChanged", "sv" },
+ { NULL, NULL }
+};
+
+static void path_unregister(void *data)
+{
+ struct audio_device *dev = data;
+ struct control *control = dev->control;
+
+ DBG("Unregistered interface %s on path %s",
+ AUDIO_CONTROL_INTERFACE, dev->path);
+
+ if (control->state != AVCTP_STATE_DISCONNECTED)
+ avctp_disconnected(dev);
+
+ g_free(control);
+ dev->control = NULL;
+}
+
+void control_unregister(struct audio_device *dev)
+{
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_CONTROL_INTERFACE);
+}
+
+void control_update(struct audio_device *dev, uint16_t uuid16)
+{
+ struct control *control = dev->control;
+
+ if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID)
+ control->target = TRUE;
+}
+
+struct control *control_init(struct audio_device *dev, uint16_t uuid16)
+{
+ struct control *control;
+
+ if (!g_dbus_register_interface(dev->conn, dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ control_methods, control_signals, NULL,
+ dev, path_unregister))
+ return NULL;
+
+ DBG("Registered interface %s on path %s",
+ AUDIO_CONTROL_INTERFACE, dev->path);
+
+ control = g_new0(struct control, 1);
+ control->dev = dev;
+ control->state = AVCTP_STATE_DISCONNECTED;
+ control->uinput = -1;
+
+ if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID)
+ control->target = TRUE;
+
+ return control;
+}
+
+gboolean control_is_active(struct audio_device *dev)
+{
+ struct control *control = dev->control;
+
+ if (control && control->state != AVCTP_STATE_DISCONNECTED)
+ return TRUE;
+
+ return FALSE;
+}
+
+unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data)
+{
+ struct avctp_state_callback *state_cb;
+ static unsigned int id = 0;
+
+ state_cb = g_new(struct avctp_state_callback, 1);
+ state_cb->cb = cb;
+ state_cb->user_data = user_data;
+ state_cb->id = ++id;
+
+ avctp_callbacks = g_slist_append(avctp_callbacks, state_cb);
+
+ return state_cb->id;
+}
+
+gboolean avctp_remove_state_cb(unsigned int id)
+{
+ GSList *l;
+
+ for (l = avctp_callbacks; l != NULL; l = l->next) {
+ struct avctp_state_callback *cb = l->data;
+ if (cb && cb->id == id) {
+ avctp_callbacks = g_slist_remove(avctp_callbacks, cb);
+ g_free(cb);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
diff --git a/audio/control.h b/audio/control.h
new file mode 100644
index 0000000..49f25c2
--- /dev/null
+++ b/audio/control.h
@@ -0,0 +1,50 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define AUDIO_CONTROL_INTERFACE "org.bluez.Control"
+
+typedef enum {
+ AVCTP_STATE_DISCONNECTED = 0,
+ AVCTP_STATE_CONNECTING,
+ AVCTP_STATE_CONNECTED
+} avctp_state_t;
+
+typedef void (*avctp_state_cb) (struct audio_device *dev,
+ avctp_state_t old_state,
+ avctp_state_t new_state,
+ void *user_data);
+
+unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data);
+gboolean avctp_remove_state_cb(unsigned int id);
+
+int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
+void avrcp_unregister(const bdaddr_t *src);
+
+gboolean avrcp_connect(struct audio_device *dev);
+void avrcp_disconnect(struct audio_device *dev);
+
+struct control *control_init(struct audio_device *dev, uint16_t uuid16);
+void control_update(struct audio_device *dev, uint16_t uuid16);
+void control_unregister(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 0000000..2981385
--- /dev/null
+++ b/audio/ctl_bluetooth.c
@@ -0,0 +1,384 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 *rsp)
+{
+ int ret;
+ struct bt_control_req *req = (void *) rsp;
+ bt_audio_error_t *err = (void *) rsp;
+ const char *type, *name;
+
+ memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+ req->h.type = BT_REQUEST;
+ req->h.name = BT_CONTROL;
+ req->h.length = sizeof(*req);
+
+ req->mode = mode;
+ req->key = key;
+
+ ret = send(data->sock, req, BT_SUGGESTED_BUFFER_SIZE, MSG_NOSIGNAL);
+ if (ret <= 0) {
+ SYSERR("Unable to request new volume value to server");
+ return -errno;
+ }
+
+ ret = recv(data->sock, rsp, BT_SUGGESTED_BUFFER_SIZE, 0);
+ if (ret <= 0) {
+ SNDERR("Unable to receive new volume value from server");
+ return -errno;
+ }
+
+ if (rsp->h.type == BT_ERROR) {
+ SNDERR("BT_CONTROL failed : %s (%d)",
+ strerror(err->posix_errno),
+ err->posix_errno);
+ return -err->posix_errno;
+ }
+
+ type = bt_audio_strtype(rsp->h.type);
+ if (!type) {
+ SNDERR("Bogus message type %d "
+ "received from audio service",
+ rsp->h.type);
+ return -EINVAL;
+ }
+
+ name = bt_audio_strname(rsp->h.name);
+ if (!name) {
+ SNDERR("Bogus message name %d "
+ "received from audio service",
+ rsp->h.name);
+ return -EINVAL;
+ }
+
+ if (rsp->h.name != BT_CONTROL) {
+ SNDERR("Unexpected message %s received", type);
+ return -EINVAL;
+ }
+
+ 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_SUGGESTED_BUFFER_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_SUGGESTED_BUFFER_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_SUGGESTED_BUFFER_SIZE];
+ struct bt_control_ind *ind = (void *) buf;
+ int ret;
+ const char *type, *name;
+
+ DBG("ext %p id %p", ext, id);
+
+ memset(buf, 0, sizeof(buf));
+
+ ret = recv(data->sock, ind, BT_SUGGESTED_BUFFER_SIZE, MSG_DONTWAIT);
+ if (ret < 0) {
+ SNDERR("Failed while receiving data: %s (%d)",
+ strerror(errno), errno);
+ return -errno;
+ }
+
+ type = bt_audio_strtype(ind->h.type);
+ if (!type) {
+ SNDERR("Bogus message type %d "
+ "received from audio service",
+ ind->h.type);
+ return -EAGAIN;
+ }
+
+ name = bt_audio_strname(ind->h.name);
+ if (!name) {
+ SNDERR("Bogus message name %d "
+ "received from audio service",
+ ind->h.name);
+ return -EAGAIN;
+ }
+
+ if (ind->h.name != BT_CONTROL) {
+ SNDERR("Unexpected message %s received", name);
+ 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);
+
+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 0000000..e38e598
--- /dev/null
+++ b/audio/device.c
@@ -0,0 +1,862 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#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/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "textfile.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#include "error.h"
+#include "ipc.h"
+#include "dbus-common.h"
+#include "device.h"
+#include "unix.h"
+#include "avdtp.h"
+#include "control.h"
+#include "headset.h"
+#include "gateway.h"
+#include "sink.h"
+#include "source.h"
+
+#define AUDIO_INTERFACE "org.bluez.Audio"
+
+#define CONTROL_CONNECT_TIMEOUT 2
+#define AVDTP_CONNECT_TIMEOUT 1
+#define HEADSET_CONNECT_TIMEOUT 1
+
+typedef enum {
+ AUDIO_STATE_DISCONNECTED,
+ AUDIO_STATE_CONNECTING,
+ AUDIO_STATE_CONNECTED,
+} audio_state_t;
+
+struct service_auth {
+ service_auth_cb cb;
+ void *user_data;
+};
+
+struct dev_priv {
+ audio_state_t state;
+
+ headset_state_t hs_state;
+ sink_state_t sink_state;
+ avctp_state_t avctp_state;
+ GSList *auths;
+
+ DBusMessage *conn_req;
+ DBusMessage *dc_req;
+
+ guint control_timer;
+ guint avdtp_timer;
+ guint headset_timer;
+ guint dc_id;
+
+ gboolean disconnecting;
+ gboolean authorized;
+ guint auth_idle_id;
+};
+
+static unsigned int sink_callback_id = 0;
+static unsigned int avctp_callback_id = 0;
+static unsigned int avdtp_callback_id = 0;
+static unsigned int headset_callback_id = 0;
+
+static void device_free(struct audio_device *dev)
+{
+ struct dev_priv *priv = dev->priv;
+
+ if (dev->conn)
+ dbus_connection_unref(dev->conn);
+
+ btd_device_unref(dev->btd_dev);
+
+ if (priv) {
+ if (priv->auths)
+ audio_device_cancel_authorization(dev, NULL, NULL);
+ if (priv->control_timer)
+ g_source_remove(priv->control_timer);
+ if (priv->avdtp_timer)
+ g_source_remove(priv->avdtp_timer);
+ if (priv->headset_timer)
+ g_source_remove(priv->headset_timer);
+ if (priv->dc_req)
+ dbus_message_unref(priv->dc_req);
+ if (priv->conn_req)
+ dbus_message_unref(priv->conn_req);
+ if (priv->dc_id)
+ device_remove_disconnect_watch(dev->btd_dev,
+ priv->dc_id);
+ g_free(priv);
+ }
+
+ g_free(dev->path);
+ g_free(dev);
+}
+
+static const char *state2str(audio_state_t state)
+{
+ switch (state) {
+ case AUDIO_STATE_DISCONNECTED:
+ return "disconnected";
+ case AUDIO_STATE_CONNECTING:
+ return "connecting";
+ case AUDIO_STATE_CONNECTED:
+ return "connected";
+ default:
+ error("Invalid audio state %d", state);
+ return NULL;
+ }
+}
+
+static gboolean control_connect_timeout(gpointer user_data)
+{
+ struct audio_device *dev = user_data;
+
+ dev->priv->control_timer = 0;
+
+ if (dev->control)
+ avrcp_connect(dev);
+
+ return FALSE;
+}
+
+static gboolean device_set_control_timer(struct audio_device *dev)
+{
+ struct dev_priv *priv = dev->priv;
+
+ if (!dev->control)
+ return FALSE;
+
+ if (priv->control_timer)
+ return FALSE;
+
+ priv->control_timer = g_timeout_add_seconds(CONTROL_CONNECT_TIMEOUT,
+ control_connect_timeout,
+ dev);
+
+ return TRUE;
+}
+
+static void device_remove_control_timer(struct audio_device *dev)
+{
+ if (dev->priv->control_timer)
+ g_source_remove(dev->priv->control_timer);
+ dev->priv->control_timer = 0;
+}
+
+static void device_remove_avdtp_timer(struct audio_device *dev)
+{
+ if (dev->priv->avdtp_timer)
+ g_source_remove(dev->priv->avdtp_timer);
+ dev->priv->avdtp_timer = 0;
+}
+
+static void device_remove_headset_timer(struct audio_device *dev)
+{
+ if (dev->priv->headset_timer)
+ g_source_remove(dev->priv->headset_timer);
+ dev->priv->headset_timer = 0;
+}
+
+static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
+ void *user_data)
+{
+ struct audio_device *dev = user_data;
+ struct dev_priv *priv = dev->priv;
+
+ if (priv->state == AUDIO_STATE_DISCONNECTED)
+ return;
+
+ if (priv->disconnecting)
+ return;
+
+ priv->disconnecting = TRUE;
+
+ device_remove_control_timer(dev);
+ device_remove_avdtp_timer(dev);
+ device_remove_headset_timer(dev);
+
+ if (dev->control)
+ avrcp_disconnect(dev);
+
+ if (dev->sink && priv->sink_state != SINK_STATE_DISCONNECTED)
+ sink_shutdown(dev->sink);
+ else if (priv->hs_state != HEADSET_STATE_DISCONNECTED)
+ headset_shutdown(dev);
+ else
+ priv->disconnecting = FALSE;
+}
+
+static void device_set_state(struct audio_device *dev, audio_state_t new_state)
+{
+ struct dev_priv *priv = dev->priv;
+ const char *state_str;
+ DBusMessage *reply = NULL;
+
+ state_str = state2str(new_state);
+ if (!state_str)
+ return;
+
+ if (new_state == AUDIO_STATE_DISCONNECTED) {
+ priv->authorized = FALSE;
+
+ if (priv->dc_id) {
+ device_remove_disconnect_watch(dev->btd_dev,
+ priv->dc_id);
+ priv->dc_id = 0;
+ }
+ } else if (new_state == AUDIO_STATE_CONNECTING)
+ priv->dc_id = device_add_disconnect_watch(dev->btd_dev,
+ disconnect_cb, dev, NULL);
+
+ if (dev->priv->state == new_state) {
+ DBG("state change attempted from %s to %s",
+ state_str, state_str);
+ return;
+ }
+
+ dev->priv->state = new_state;
+
+ if (new_state == AUDIO_STATE_DISCONNECTED) {
+ if (priv->dc_req) {
+ reply = dbus_message_new_method_return(priv->dc_req);
+ dbus_message_unref(priv->dc_req);
+ priv->dc_req = NULL;
+ g_dbus_send_message(dev->conn, reply);
+ }
+ priv->disconnecting = FALSE;
+ }
+
+ if (priv->conn_req && new_state != AUDIO_STATE_CONNECTING) {
+ if (new_state == AUDIO_STATE_CONNECTED)
+ reply = dbus_message_new_method_return(priv->conn_req);
+ else
+ reply = btd_error_failed(priv->conn_req,
+ "Connect Failed");
+
+ dbus_message_unref(priv->conn_req);
+ priv->conn_req = NULL;
+ g_dbus_send_message(dev->conn, reply);
+ }
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_INTERFACE, "State",
+ DBUS_TYPE_STRING, &state_str);
+}
+
+static gboolean avdtp_connect_timeout(gpointer user_data)
+{
+ struct audio_device *dev = user_data;
+
+ dev->priv->avdtp_timer = 0;
+
+ if (dev->sink) {
+ struct avdtp *session = avdtp_get(&dev->src, &dev->dst);
+
+ if (!session)
+ return FALSE;
+
+ sink_setup_stream(dev->sink, session);
+ avdtp_unref(session);
+ }
+
+ return FALSE;
+}
+
+static gboolean device_set_avdtp_timer(struct audio_device *dev)
+{
+ struct dev_priv *priv = dev->priv;
+
+ if (!dev->sink)
+ return FALSE;
+
+ if (priv->avdtp_timer)
+ return FALSE;
+
+ priv->avdtp_timer = g_timeout_add_seconds(AVDTP_CONNECT_TIMEOUT,
+ avdtp_connect_timeout,
+ dev);
+
+ return TRUE;
+}
+
+static gboolean headset_connect_timeout(gpointer user_data)
+{
+ struct audio_device *dev = user_data;
+ struct dev_priv *priv = dev->priv;
+
+ dev->priv->headset_timer = 0;
+
+ if (dev->headset == NULL)
+ return FALSE;
+
+ if (headset_config_stream(dev, FALSE, NULL, NULL) == 0) {
+ if (priv->state != AUDIO_STATE_CONNECTED &&
+ (priv->sink_state == SINK_STATE_CONNECTED ||
+ priv->sink_state == SINK_STATE_PLAYING))
+ device_set_state(dev, AUDIO_STATE_CONNECTED);
+ }
+
+ return FALSE;
+}
+
+static gboolean device_set_headset_timer(struct audio_device *dev)
+{
+ struct dev_priv *priv = dev->priv;
+
+ if (!dev->headset)
+ return FALSE;
+
+ if (priv->headset_timer)
+ return FALSE;
+
+ priv->headset_timer = g_timeout_add_seconds(HEADSET_CONNECT_TIMEOUT,
+ headset_connect_timeout, dev);
+
+ return TRUE;
+}
+
+static void device_avdtp_cb(struct audio_device *dev, struct avdtp *session,
+ avdtp_session_state_t old_state,
+ avdtp_session_state_t new_state,
+ void *user_data)
+{
+ if (!dev->sink || !dev->control)
+ return;
+
+ if (new_state == AVDTP_SESSION_STATE_CONNECTED) {
+ if (avdtp_stream_setup_active(session))
+ device_set_control_timer(dev);
+ else
+ avrcp_connect(dev);
+ }
+}
+
+static void device_sink_cb(struct audio_device *dev,
+ sink_state_t old_state,
+ sink_state_t new_state,
+ void *user_data)
+{
+ struct dev_priv *priv = dev->priv;
+
+ if (!dev->sink)
+ return;
+
+ priv->sink_state = new_state;
+
+ switch (new_state) {
+ case SINK_STATE_DISCONNECTED:
+ if (dev->control) {
+ device_remove_control_timer(dev);
+ avrcp_disconnect(dev);
+ }
+ if (priv->hs_state != HEADSET_STATE_DISCONNECTED &&
+ (priv->dc_req || priv->disconnecting)) {
+ headset_shutdown(dev);
+ break;
+ }
+ if (priv->hs_state == HEADSET_STATE_DISCONNECTED)
+ device_set_state(dev, AUDIO_STATE_DISCONNECTED);
+ else if (old_state == SINK_STATE_CONNECTING) {
+ switch (priv->hs_state) {
+ case HEADSET_STATE_CONNECTED:
+ case HEADSET_STATE_PLAY_IN_PROGRESS:
+ case HEADSET_STATE_PLAYING:
+ device_set_state(dev, AUDIO_STATE_CONNECTED);
+ default:
+ break;
+ }
+ }
+ break;
+ case SINK_STATE_CONNECTING:
+ device_remove_avdtp_timer(dev);
+ if (priv->hs_state == HEADSET_STATE_DISCONNECTED)
+ device_set_state(dev, AUDIO_STATE_CONNECTING);
+ break;
+ case SINK_STATE_CONNECTED:
+ if (old_state == SINK_STATE_PLAYING)
+ break;
+ if (dev->auto_connect) {
+ if (!dev->headset)
+ device_set_state(dev, AUDIO_STATE_CONNECTED);
+ else if (priv->hs_state == HEADSET_STATE_DISCONNECTED)
+ device_set_headset_timer(dev);
+ else if (priv->hs_state == HEADSET_STATE_CONNECTED ||
+ priv->hs_state == HEADSET_STATE_PLAY_IN_PROGRESS ||
+ priv->hs_state == HEADSET_STATE_PLAYING)
+ device_set_state(dev, AUDIO_STATE_CONNECTED);
+ } else if (priv->hs_state == HEADSET_STATE_DISCONNECTED ||
+ priv->hs_state == HEADSET_STATE_CONNECTING)
+ device_set_state(dev, AUDIO_STATE_CONNECTED);
+ break;
+ case SINK_STATE_PLAYING:
+ break;
+ }
+}
+
+static void device_avctp_cb(struct audio_device *dev,
+ avctp_state_t old_state,
+ avctp_state_t new_state,
+ void *user_data)
+{
+ if (!dev->control)
+ return;
+
+ dev->priv->avctp_state = new_state;
+
+ switch (new_state) {
+ case AVCTP_STATE_DISCONNECTED:
+ break;
+ case AVCTP_STATE_CONNECTING:
+ device_remove_control_timer(dev);
+ break;
+ case AVCTP_STATE_CONNECTED:
+ break;
+ }
+}
+
+static void device_headset_cb(struct audio_device *dev,
+ headset_state_t old_state,
+ headset_state_t new_state,
+ void *user_data)
+{
+ struct dev_priv *priv = dev->priv;
+
+ if (!dev->headset)
+ return;
+
+ priv->hs_state = new_state;
+
+ switch (new_state) {
+ case HEADSET_STATE_DISCONNECTED:
+ device_remove_avdtp_timer(dev);
+ if (priv->sink_state != SINK_STATE_DISCONNECTED && dev->sink &&
+ (priv->dc_req || priv->disconnecting)) {
+ sink_shutdown(dev->sink);
+ break;
+ }
+ if (priv->sink_state == SINK_STATE_DISCONNECTED)
+ device_set_state(dev, AUDIO_STATE_DISCONNECTED);
+ else if (old_state == HEADSET_STATE_CONNECTING &&
+ (priv->sink_state == SINK_STATE_CONNECTED ||
+ priv->sink_state == SINK_STATE_PLAYING))
+ device_set_state(dev, AUDIO_STATE_CONNECTED);
+ break;
+ case HEADSET_STATE_CONNECTING:
+ device_remove_headset_timer(dev);
+ if (priv->sink_state == SINK_STATE_DISCONNECTED)
+ device_set_state(dev, AUDIO_STATE_CONNECTING);
+ break;
+ case HEADSET_STATE_CONNECTED:
+ if (old_state == HEADSET_STATE_CONNECTED ||
+ old_state == HEADSET_STATE_PLAY_IN_PROGRESS ||
+ old_state == HEADSET_STATE_PLAYING)
+ break;
+ if (dev->auto_connect) {
+ if (!dev->sink)
+ device_set_state(dev, AUDIO_STATE_CONNECTED);
+ else if (priv->sink_state == SINK_STATE_DISCONNECTED)
+ device_set_avdtp_timer(dev);
+ else if (priv->sink_state == SINK_STATE_CONNECTED ||
+ priv->sink_state == SINK_STATE_PLAYING)
+ device_set_state(dev, AUDIO_STATE_CONNECTED);
+ } else if (priv->sink_state == SINK_STATE_DISCONNECTED ||
+ priv->sink_state == SINK_STATE_CONNECTING)
+ device_set_state(dev, AUDIO_STATE_CONNECTED);
+ break;
+ case HEADSET_STATE_PLAY_IN_PROGRESS:
+ break;
+ case HEADSET_STATE_PLAYING:
+ break;
+ }
+}
+
+static DBusMessage *dev_connect(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *dev = data;
+ struct dev_priv *priv = dev->priv;
+
+ if (priv->state == AUDIO_STATE_CONNECTING)
+ return btd_error_in_progress(msg);
+ else if (priv->state == AUDIO_STATE_CONNECTED)
+ return btd_error_already_connected(msg);
+
+ dev->auto_connect = TRUE;
+
+ if (dev->headset)
+ headset_config_stream(dev, FALSE, NULL, NULL);
+
+ if (priv->state != AUDIO_STATE_CONNECTING && dev->sink) {
+ struct avdtp *session = avdtp_get(&dev->src, &dev->dst);
+
+ if (!session)
+ return btd_error_failed(msg,
+ "Failed to get AVDTP session");
+
+ sink_setup_stream(dev->sink, session);
+ avdtp_unref(session);
+ }
+
+ /* The previous calls should cause a call to the state callback to
+ * indicate AUDIO_STATE_CONNECTING */
+ if (priv->state != AUDIO_STATE_CONNECTING)
+ return btd_error_failed(msg, "Connect Failed");
+
+ priv->conn_req = dbus_message_ref(msg);
+
+ return NULL;
+}
+
+static DBusMessage *dev_disconnect(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *dev = data;
+ struct dev_priv *priv = dev->priv;
+
+ if (priv->state == AUDIO_STATE_DISCONNECTED)
+ return btd_error_not_connected(msg);
+
+ if (priv->dc_req)
+ return dbus_message_new_method_return(msg);
+
+ priv->dc_req = dbus_message_ref(msg);
+
+ if (dev->control) {
+ device_remove_control_timer(dev);
+ avrcp_disconnect(dev);
+ }
+
+ if (dev->sink && priv->sink_state != SINK_STATE_DISCONNECTED)
+ sink_shutdown(dev->sink);
+ else if (priv->hs_state != HEADSET_STATE_DISCONNECTED)
+ headset_shutdown(dev);
+ else {
+ dbus_message_unref(priv->dc_req);
+ priv->dc_req = NULL;
+ return dbus_message_new_method_return(msg);
+ }
+
+ return NULL;
+}
+
+static DBusMessage *dev_get_properties(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *state;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ /* State */
+ state = state2str(device->priv->state);
+ if (state)
+ dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static GDBusMethodTable dev_methods[] = {
+ { "Connect", "", "", dev_connect,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "Disconnect", "", "", dev_disconnect },
+ { "GetProperties", "", "a{sv}",dev_get_properties },
+ { NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable dev_signals[] = {
+ { "PropertyChanged", "sv" },
+ { NULL, NULL }
+};
+
+struct audio_device *audio_device_register(DBusConnection *conn,
+ struct btd_device *device,
+ const char *path, const bdaddr_t *src,
+ const bdaddr_t *dst)
+{
+ struct audio_device *dev;
+
+ if (!conn || !path)
+ return NULL;
+
+ dev = g_new0(struct audio_device, 1);
+
+ dev->btd_dev = btd_device_ref(device);
+ dev->path = g_strdup(path);
+ bacpy(&dev->dst, dst);
+ bacpy(&dev->src, src);
+ dev->conn = dbus_connection_ref(conn);
+ dev->priv = g_new0(struct dev_priv, 1);
+ dev->priv->state = AUDIO_STATE_DISCONNECTED;
+
+ if (!g_dbus_register_interface(dev->conn, dev->path,
+ AUDIO_INTERFACE,
+ dev_methods, dev_signals, NULL,
+ dev, NULL)) {
+ error("Unable to register %s on %s", AUDIO_INTERFACE,
+ dev->path);
+ device_free(dev);
+ return NULL;
+ }
+
+ DBG("Registered interface %s on path %s", AUDIO_INTERFACE,
+ dev->path);
+
+ if (sink_callback_id == 0)
+ sink_callback_id = sink_add_state_cb(device_sink_cb, NULL);
+
+ if (avdtp_callback_id == 0)
+ avdtp_callback_id = avdtp_add_state_cb(device_avdtp_cb, NULL);
+ if (avctp_callback_id == 0)
+ avctp_callback_id = avctp_add_state_cb(device_avctp_cb, NULL);
+
+ if (headset_callback_id == 0)
+ headset_callback_id = headset_add_state_cb(device_headset_cb,
+ NULL);
+
+ return dev;
+}
+
+gboolean audio_device_is_active(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->control &&
+ control_is_active(dev))
+ return TRUE;
+ else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway &&
+ gateway_is_connected(dev))
+ return TRUE;
+
+ return FALSE;
+}
+
+void audio_device_unregister(struct audio_device *device)
+{
+ unix_device_removed(device);
+
+ if (device->hs_preauth_id) {
+ g_source_remove(device->hs_preauth_id);
+ device->hs_preauth_id = 0;
+ }
+
+ if (device->headset)
+ headset_unregister(device);
+
+ if (device->gateway)
+ gateway_unregister(device);
+
+ if (device->sink)
+ sink_unregister(device);
+
+ if (device->source)
+ source_unregister(device);
+
+ if (device->control)
+ control_unregister(device);
+
+ g_dbus_unregister_interface(device->conn, device->path,
+ AUDIO_INTERFACE);
+
+ device_free(device);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+ struct audio_device *dev = user_data;
+ struct dev_priv *priv = dev->priv;
+
+ if (derr == NULL)
+ priv->authorized = TRUE;
+
+ while (priv->auths) {
+ struct service_auth *auth = priv->auths->data;
+
+ auth->cb(derr, auth->user_data);
+ priv->auths = g_slist_remove(priv->auths, auth);
+ g_free(auth);
+ }
+}
+
+static gboolean auth_idle_cb(gpointer user_data)
+{
+ struct audio_device *dev = user_data;
+ struct dev_priv *priv = dev->priv;
+
+ priv->auth_idle_id = 0;
+
+ auth_cb(NULL, dev);
+
+ return FALSE;
+}
+
+static gboolean audio_device_is_connected(struct audio_device *dev)
+{
+ if (dev->headset) {
+ headset_state_t state = headset_get_state(dev);
+
+ if (state == HEADSET_STATE_CONNECTED ||
+ state == HEADSET_STATE_PLAY_IN_PROGRESS ||
+ state == HEADSET_STATE_PLAYING)
+ return TRUE;
+ }
+
+ if (dev->sink) {
+ sink_state_t state = sink_get_state(dev);
+
+ if (state == SINK_STATE_CONNECTED ||
+ state == SINK_STATE_PLAYING)
+ return TRUE;
+ }
+
+ if (dev->source) {
+ source_state_t state = source_get_state(dev);
+
+ if (state == SOURCE_STATE_CONNECTED ||
+ state == SOURCE_STATE_PLAYING)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+int audio_device_request_authorization(struct audio_device *dev,
+ const char *uuid, service_auth_cb cb,
+ void *user_data)
+{
+ struct dev_priv *priv = dev->priv;
+ struct service_auth *auth;
+ int err;
+
+ auth = g_try_new0(struct service_auth, 1);
+ if (!auth)
+ return -ENOMEM;
+
+ auth->cb = cb;
+ auth->user_data = user_data;
+
+ priv->auths = g_slist_append(priv->auths, auth);
+ if (g_slist_length(priv->auths) > 1)
+ return 0;
+
+ if (priv->authorized || audio_device_is_connected(dev)) {
+ priv->auth_idle_id = g_idle_add(auth_idle_cb, dev);
+ return 0;
+ }
+
+ err = btd_request_authorization(&dev->src, &dev->dst, uuid, auth_cb,
+ dev);
+ if (err < 0) {
+ priv->auths = g_slist_remove(priv->auths, auth);
+ g_free(auth);
+ }
+
+ return err;
+}
+
+int audio_device_cancel_authorization(struct audio_device *dev,
+ authorization_cb cb, void *user_data)
+{
+ struct dev_priv *priv = dev->priv;
+ GSList *l, *next;
+
+ for (l = priv->auths; l != NULL; l = next) {
+ struct service_auth *auth = l->data;
+
+ next = g_slist_next(l);
+
+ if (cb && auth->cb != cb)
+ continue;
+
+ if (user_data && auth->user_data != user_data)
+ continue;
+
+ priv->auths = g_slist_remove(priv->auths, auth);
+ g_free(auth);
+ }
+
+ if (g_slist_length(priv->auths) == 0) {
+ if (priv->auth_idle_id > 0) {
+ g_source_remove(priv->auth_idle_id);
+ priv->auth_idle_id = 0;
+ } else
+ btd_cancel_authorization(&dev->src, &dev->dst);
+ }
+
+ return 0;
+}
+
+void audio_device_set_authorized(struct audio_device *dev, gboolean auth)
+{
+ struct dev_priv *priv = dev->priv;
+
+ priv->authorized = auth;
+}
diff --git a/audio/device.h b/audio/device.h
new file mode 100644
index 0000000..35af788
--- /dev/null
+++ b/audio/device.h
@@ -0,0 +1,94 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define 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.AudioSource"
+#define AUDIO_CONTROL_INTERFACE "org.bluez.Control"
+
+struct source;
+struct control;
+struct target;
+struct sink;
+struct headset;
+struct gateway;
+struct dev_priv;
+
+struct audio_device {
+ struct btd_device *btd_dev;
+
+ DBusConnection *conn;
+ char *path;
+ bdaddr_t src;
+ bdaddr_t dst;
+
+ gboolean auto_connect;
+
+ struct headset *headset;
+ struct gateway *gateway;
+ struct sink *sink;
+ struct source *source;
+ struct control *control;
+ struct target *target;
+
+ guint hs_preauth_id;
+
+ struct dev_priv *priv;
+};
+
+struct audio_device *audio_device_register(DBusConnection *conn,
+ struct btd_device *device,
+ const char *path, const bdaddr_t *src,
+ const bdaddr_t *dst);
+
+void audio_device_unregister(struct audio_device *device);
+
+gboolean audio_device_is_active(struct audio_device *dev,
+ const char *interface);
+
+typedef void (*authorization_cb) (DBusError *derr, void *user_data);
+
+int audio_device_cancel_authorization(struct audio_device *dev,
+ authorization_cb cb, void *user_data);
+
+int audio_device_request_authorization(struct audio_device *dev,
+ const char *uuid, authorization_cb cb,
+ void *user_data);
+
+void audio_device_set_authorized(struct audio_device *dev, gboolean auth);
diff --git a/audio/gateway.c b/audio/gateway.c
new file mode 100644
index 0000000..ec0ec5d
--- /dev/null
+++ b/audio/gateway.c
@@ -0,0 +1,711 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2008-2009 Leonid Movshovich <event.riga@gmail.org>
+ * Copyright (C) 2010 ProFUSION embedded systems
+ *
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "glib-helper.h"
+#include "device.h"
+#include "gateway.h"
+#include "log.h"
+#include "error.h"
+#include "btio.h"
+#include "dbus-common.h"
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct hf_agent {
+ char *name; /* Bus id */
+ char *path; /* D-Bus path */
+ guint watch; /* Disconnect watch */
+};
+
+struct gateway {
+ gateway_state_t state;
+ GIOChannel *rfcomm;
+ GIOChannel *sco;
+ gateway_stream_cb_t sco_start_cb;
+ void *sco_start_cb_data;
+ struct hf_agent *agent;
+ DBusMessage *msg;
+};
+
+int gateway_close(struct audio_device *device);
+
+static const char *state2str(gateway_state_t state)
+{
+ switch (state) {
+ case GATEWAY_STATE_DISCONNECTED:
+ return "disconnected";
+ case GATEWAY_STATE_CONNECTING:
+ return "connecting";
+ case GATEWAY_STATE_CONNECTED:
+ return "connected";
+ case GATEWAY_STATE_PLAYING:
+ return "playing";
+ default:
+ return "";
+ }
+}
+
+static void agent_free(struct hf_agent *agent)
+{
+ if (!agent)
+ return;
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent);
+}
+
+static void change_state(struct audio_device *dev, gateway_state_t new_state)
+{
+ struct gateway *gw = dev->gateway;
+ const char *val;
+
+ if (gw->state == new_state)
+ return;
+
+ val = state2str(new_state);
+ gw->state = new_state;
+
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE, "State",
+ DBUS_TYPE_STRING, &val);
+}
+
+static void agent_disconnect(struct audio_device *dev, struct hf_agent *agent)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "Release");
+
+ g_dbus_send_message(dev->conn, msg);
+}
+
+static gboolean agent_sendfd(struct hf_agent *agent, int fd,
+ DBusPendingCallNotifyFunction notify, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *msg;
+ DBusPendingCall *call;
+
+ msg = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.HandsfreeAgent", "NewConnection");
+
+ dbus_message_append_args(msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(dev->conn, msg, &call, -1) == FALSE)
+ return FALSE;
+
+ dbus_pending_call_set_notify(call, notify, dev, NULL);
+ dbus_pending_call_unref(call);
+
+ return TRUE;
+}
+
+static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
+ struct audio_device *dev)
+{
+ struct gateway *gw = dev->gateway;
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ if (cond & (G_IO_ERR | G_IO_HUP)) {
+ DBG("sco connection is released");
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+ struct audio_device *dev = (struct audio_device *) user_data;
+ struct gateway *gw = dev->gateway;
+
+ DBG("at the begin of sco_connect_cb() in gateway.c");
+
+ gw->sco = g_io_channel_ref(chan);
+
+ if (gw->sco_start_cb)
+ gw->sco_start_cb(dev, err, gw->sco_start_cb_data);
+
+ if (err) {
+ error("sco_connect_cb(): %s", err->message);
+ gateway_close(dev);
+ return;
+ }
+
+ g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) sco_io_cb, dev);
+}
+
+static void newconnection_reply(DBusPendingCall *call, void *data)
+{
+ struct audio_device *dev = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+
+ if (!dev->gateway->rfcomm) {
+ DBG("RFCOMM disconnected from server before agent reply");
+ goto done;
+ }
+
+ dbus_error_init(&derr);
+ if (!dbus_set_error_from_message(&derr, reply)) {
+ DBG("Agent reply: file descriptor passed successfully");
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+ goto done;
+ }
+
+ DBG("Agent reply: %s", derr.message);
+
+ dbus_error_free(&derr);
+ gateway_close(dev);
+
+done:
+ dbus_message_unref(reply);
+}
+
+static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
+ gpointer user_data)
+{
+ struct audio_device *dev = user_data;
+ struct gateway *gw = dev->gateway;
+ DBusMessage *reply;
+ int sk, ret;
+
+ if (err) {
+ error("connect(): %s", err->message);
+ if (gw->sco_start_cb)
+ gw->sco_start_cb(dev, err, gw->sco_start_cb_data);
+ goto fail;
+ }
+
+ if (!gw->agent) {
+ error("Handsfree Agent not registered");
+ goto fail;
+ }
+
+ sk = g_io_channel_unix_get_fd(chan);
+
+ gw->rfcomm = g_io_channel_ref(chan);
+
+ ret = agent_sendfd(gw->agent, sk, newconnection_reply, dev);
+
+ if (!gw->msg)
+ return;
+
+ if (ret)
+ reply = dbus_message_new_method_return(gw->msg);
+ else
+ reply = btd_error_failed(gw->msg, "Can't pass file descriptor");
+
+ g_dbus_send_message(dev->conn, reply);
+
+ return;
+
+fail:
+ if (gw->msg) {
+ DBusMessage *reply;
+ reply = btd_error_failed(gw->msg, "Connect failed");
+ g_dbus_send_message(dev->conn, reply);
+ }
+
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
+}
+
+static void get_record_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+ struct audio_device *dev = user_data;
+ struct gateway *gw = dev->gateway;
+ int ch;
+ sdp_list_t *protos, *classes;
+ uuid_t uuid;
+ GIOChannel *io;
+ GError *gerr = NULL;
+
+ if (err < 0) {
+ error("Unable to get service record: %s (%d)", strerror(-err),
+ -err);
+ goto fail;
+ }
+
+ if (!recs || !recs->data) {
+ error("No records found");
+ err = -EIO;
+ goto fail;
+ }
+
+ if (sdp_get_service_classes(recs->data, &classes) < 0) {
+ error("Unable to get service classes from record");
+ err = -EINVAL;
+ goto fail;
+ }
+
+ if (sdp_get_access_protos(recs->data, &protos) < 0) {
+ error("Unable to get access protocols from record");
+ err = -ENODATA;
+ goto fail;
+ }
+
+ memcpy(&uuid, classes->data, sizeof(uuid));
+ sdp_list_free(classes, free);
+
+ if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
+ uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
+ sdp_list_free(protos, NULL);
+ error("Invalid service record or not HFP");
+ err = -EIO;
+ goto fail;
+ }
+
+ 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) {
+ error("Unable to extract RFCOMM channel from service record");
+ err = -EIO;
+ goto fail;
+ }
+
+ io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &gerr,
+ BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+ BT_IO_OPT_DEST_BDADDR, &dev->dst,
+ BT_IO_OPT_CHANNEL, ch,
+ BT_IO_OPT_INVALID);
+ if (!io) {
+ error("Unable to connect: %s", gerr->message);
+ gateway_close(dev);
+ goto fail;
+ }
+
+ g_io_channel_unref(io);
+
+ change_state(dev, GATEWAY_STATE_CONNECTING);
+ return;
+
+fail:
+ if (gw->msg) {
+ DBusMessage *reply = btd_error_failed(gw->msg,
+ gerr ? gerr->message : strerror(-err));
+ g_dbus_send_message(dev->conn, reply);
+ }
+
+ change_state(dev, GATEWAY_STATE_DISCONNECTED);
+
+ if (!gerr)
+ g_set_error(&gerr, BT_IO_ERROR, BT_IO_ERROR_FAILED,
+ "connect: %s (%d)", strerror(-err), -err);
+
+ if (gw->sco_start_cb)
+ gw->sco_start_cb(dev, gerr, gw->sco_start_cb_data);
+
+ g_error_free(gerr);
+}
+
+static int get_records(struct audio_device *device)
+{
+ uuid_t uuid;
+
+ sdp_uuid16_create(&uuid, HANDSFREE_AGW_SVCLASS_ID);
+ return bt_search_service(&device->src, &device->dst, &uuid,
+ get_record_cb, device, NULL);
+}
+
+static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *au_dev = (struct audio_device *) data;
+ struct gateway *gw = au_dev->gateway;
+ int err;
+
+ if (!gw->agent)
+ return btd_error_agent_not_available(msg);
+
+ err = get_records(au_dev);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ gw->msg = dbus_message_ref(msg);
+
+ return NULL;
+}
+
+int gateway_close(struct audio_device *device)
+{
+ struct gateway *gw = device->gateway;
+ int sock;
+
+ if (gw->rfcomm) {
+ sock = g_io_channel_unix_get_fd(gw->rfcomm);
+ shutdown(sock, SHUT_RDWR);
+
+ g_io_channel_shutdown(gw->rfcomm, TRUE, NULL);
+ g_io_channel_unref(gw->rfcomm);
+ gw->rfcomm = NULL;
+ }
+
+ if (gw->sco) {
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ }
+
+ change_state(device, GATEWAY_STATE_DISCONNECTED);
+
+ return 0;
+}
+
+static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply = NULL;
+ char gw_addr[18];
+
+ if (!device->conn)
+ return NULL;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (!gw->rfcomm)
+ return btd_error_not_connected(msg);
+
+ gateway_close(device);
+ ba2str(&device->dst, gw_addr);
+ DBG("Disconnected from %s, %s", gw_addr, device->path);
+
+ return reply;
+}
+
+static void agent_exited(DBusConnection *conn, void *data)
+{
+ struct gateway *gateway = data;
+ struct hf_agent *agent = gateway->agent;
+
+ DBG("Agent %s exited", agent->name);
+
+ agent_free(agent);
+ gateway->agent = NULL;
+}
+
+static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *value;
+
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ value = state2str(gw->state);
+ dict_append_entry(&dict, "State",
+ DBUS_TYPE_STRING, &value);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ struct hf_agent *agent;
+ const char *path, *name;
+
+ if (gw->agent)
+ return btd_error_already_exists(msg);
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ name = dbus_message_get_sender(msg);
+ agent = g_new0(struct hf_agent, 1);
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ agent->watch = g_dbus_add_disconnect_watch(conn, name,
+ agent_exited, gw, NULL);
+
+ gw->agent = agent;
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct gateway *gw = device->gateway;
+ const char *path;
+
+ if (!gw->agent)
+ goto done;
+
+ if (strcmp(gw->agent->name, dbus_message_get_sender(msg)) != 0)
+ return btd_error_not_authorized(msg);
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ if (strcmp(gw->agent->path, path) != 0)
+ return btd_error_does_not_exist(msg);
+
+ g_dbus_remove_watch(device->conn, gw->agent->watch);
+
+ agent_free(gw->agent);
+ gw->agent = NULL;
+
+done:
+ return dbus_message_new_method_return(msg);
+}
+
+static GDBusMethodTable gateway_methods[] = {
+ { "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
+ { "Disconnect", "", "", ag_disconnect, G_DBUS_METHOD_FLAG_ASYNC },
+ { "GetProperties", "", "a{sv}", ag_get_properties },
+ { "RegisterAgent", "o", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
+ { NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable gateway_signals[] = {
+ { "PropertyChanged", "sv" },
+ { NULL, NULL }
+};
+
+static void path_unregister(void *data)
+{
+ struct audio_device *dev = data;
+
+ DBG("Unregistered interface %s on path %s",
+ AUDIO_GATEWAY_INTERFACE, dev->path);
+
+ gateway_close(dev);
+
+ g_free(dev->gateway);
+ dev->gateway = NULL;
+}
+
+void gateway_unregister(struct audio_device *dev)
+{
+ if (dev->gateway->agent)
+ agent_disconnect(dev, dev->gateway->agent);
+
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE);
+}
+
+struct gateway *gateway_init(struct audio_device *dev)
+{
+ if (DBUS_TYPE_UNIX_FD < 0)
+ return NULL;
+
+ if (!g_dbus_register_interface(dev->conn, dev->path,
+ AUDIO_GATEWAY_INTERFACE,
+ gateway_methods, gateway_signals,
+ NULL, dev, path_unregister))
+ return NULL;
+
+ return g_new0(struct gateway, 1);
+
+}
+
+gboolean gateway_is_connected(struct audio_device *dev)
+{
+ return (dev && dev->gateway &&
+ dev->gateway->state == GATEWAY_STATE_CONNECTED);
+}
+
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
+{
+ if (!io)
+ return -EINVAL;
+
+ dev->gateway->rfcomm = g_io_channel_ref(io);
+
+ return 0;
+}
+
+int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
+{
+ struct gateway *gw = dev->gateway;
+
+ if (gw->sco)
+ return -EISCONN;
+
+ gw->sco = g_io_channel_ref(io);
+
+ g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) sco_io_cb, dev);
+
+ change_state(dev, GATEWAY_STATE_PLAYING);
+
+ return 0;
+}
+
+void gateway_start_service(struct audio_device *dev)
+{
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
+
+ if (gw->rfcomm == NULL)
+ return;
+
+ if (!bt_io_accept(gw->rfcomm, rfcomm_connect_cb, dev, NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
+ }
+}
+
+/* These are functions to be called from unix.c for audio system
+ * ifaces (alsa, gstreamer, etc.) */
+gboolean gateway_request_stream(struct audio_device *dev,
+ gateway_stream_cb_t cb, void *user_data)
+{
+ struct gateway *gw = dev->gateway;
+ GError *err = NULL;
+ GIOChannel *io;
+
+ if (!gw->rfcomm) {
+ gw->sco_start_cb = cb;
+ gw->sco_start_cb_data = user_data;
+ get_records(dev);
+ } else if (!gw->sco) {
+ gw->sco_start_cb = cb;
+ gw->sco_start_cb_data = user_data;
+ io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+ BT_IO_OPT_DEST_BDADDR, &dev->dst,
+ BT_IO_OPT_INVALID);
+ if (!io) {
+ error("%s", err->message);
+ g_error_free(err);
+ return FALSE;
+ }
+ } else if (cb)
+ cb(dev, err, user_data);
+
+ return TRUE;
+}
+
+int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t sco_cb,
+ void *user_data)
+{
+ struct gateway *gw = dev->gateway;
+
+ if (!gw->rfcomm) {
+ gw->sco_start_cb = sco_cb;
+ gw->sco_start_cb_data = user_data;
+ return get_records(dev);
+ }
+
+ if (sco_cb)
+ sco_cb(dev, NULL, user_data);
+
+ return 0;
+}
+
+gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id)
+{
+ gateway_close(dev);
+ return TRUE;
+}
+
+int gateway_get_sco_fd(struct audio_device *dev)
+{
+ struct gateway *gw = dev->gateway;
+
+ if (!gw || !gw->sco)
+ return -1;
+
+ return g_io_channel_unix_get_fd(gw->sco);
+}
+
+void gateway_suspend_stream(struct audio_device *dev)
+{
+ struct gateway *gw = dev->gateway;
+
+ if (!gw || !gw->sco)
+ return;
+
+ g_io_channel_shutdown(gw->sco, TRUE, NULL);
+ g_io_channel_unref(gw->sco);
+ gw->sco = NULL;
+ gw->sco_start_cb = NULL;
+ gw->sco_start_cb_data = NULL;
+ change_state(dev, GATEWAY_STATE_CONNECTED);
+}
diff --git a/audio/gateway.h b/audio/gateway.h
new file mode 100644
index 0000000..a45ef82
--- /dev/null
+++ b/audio/gateway.h
@@ -0,0 +1,51 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HandsfreeGateway"
+
+#define DEFAULT_HFP_HS_CHANNEL 7
+
+typedef enum {
+ GATEWAY_STATE_DISCONNECTED,
+ GATEWAY_STATE_CONNECTING,
+ GATEWAY_STATE_CONNECTED,
+ GATEWAY_STATE_PLAYING,
+} gateway_state_t;
+
+typedef void (*gateway_stream_cb_t) (struct audio_device *dev, GError *err,
+ void *user_data);
+
+void gateway_unregister(struct audio_device *dev);
+struct gateway *gateway_init(struct audio_device *device);
+gboolean gateway_is_connected(struct audio_device *dev);
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io);
+int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
+void gateway_start_service(struct audio_device *device);
+gboolean gateway_request_stream(struct audio_device *dev,
+ gateway_stream_cb_t cb, void *user_data);
+int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t cb,
+ void *user_data);
+gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id);
+int gateway_get_sco_fd(struct audio_device *dev);
+void gateway_suspend_stream(struct audio_device *dev);
diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c
new file mode 100644
index 0000000..930d14e
--- /dev/null
+++ b/audio/gsta2dpsink.c
@@ -0,0 +1,730 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 "gstpragma.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,
+ PROP_TRANSPORT
+};
+
+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:
+ 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_TRANSPORT:
+ if (self->sink != NULL)
+ gst_avdtp_sink_set_transport(self->sink,
+ g_value_get_string(value));
+
+ if (self->transport != NULL)
+ g_free(self->transport);
+ self->transport = g_value_dup_string(value);
+ break;
+
+ case PROP_AUTOCONNECT:
+ self->autoconnect = g_value_get_boolean(value);
+
+ 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, *transport;
+
+ 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;
+ case PROP_TRANSPORT:
+ if (self->sink != NULL) {
+ transport = gst_avdtp_sink_get_transport(self->sink);
+ if (transport != NULL)
+ g_value_take_string(value, transport);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+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);
+
+ if (self->transport != NULL)
+ gst_avdtp_sink_set_transport(self->sink,
+ self->transport);
+
+ g_object_set(G_OBJECT(self->sink), "auto-connect",
+ self->autoconnect, NULL);
+
+ 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));
+
+ g_object_class_install_property(object_class, PROP_TRANSPORT,
+ g_param_spec_string("transport", "Transport",
+ "Use configured transport",
+ NULL, G_PARAM_READWRITE));
+
+ GST_DEBUG_CATEGORY_INIT(gst_a2dp_sink_debug, "a2dpsink", 0,
+ "A2DP sink element");
+}
+
+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);
+ g_object_set(G_OBJECT(self->sink), "transport", self->transport, 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->transport = 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_MARGINAL, GST_TYPE_A2DP_SINK);
+}
+
diff --git a/audio/gsta2dpsink.h b/audio/gsta2dpsink.h
new file mode 100644
index 0000000..5f96a3e
--- /dev/null
+++ b/audio/gsta2dpsink.h
@@ -0,0 +1,85 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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_A2DP_SINK_H__
+#define __GST_A2DP_SINK_H__
+
+#include <gst/gst.h>
+#include <gst/rtp/gstbasertppayload.h>
+#include "gstavdtpsink.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;
+ gchar *transport;
+ 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 0000000..ce1365a
--- /dev/null
+++ b/audio/gstavdtpsink.c
@@ -0,0 +1,2029 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 <dbus/dbus.h>
+
+#include "ipc.h"
+#include "rtp.h"
+#include "a2dp-codecs.h"
+
+#include "gstpragma.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
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+struct bluetooth_data {
+ struct bt_get_capabilities_rsp *caps; /* Bluetooth device caps */
+ guint link_mtu;
+
+ DBusConnection *conn;
+ guint8 codec; /* Bluetooth transport configuration */
+ gchar *uuid;
+ guint8 *config;
+ gint config_size;
+
+ gchar buffer[BUFFER_SIZE]; /* Codec transfer buffer */
+};
+
+#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,
+ PROP_TRANSPORT
+};
+
+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 int gst_avdtp_sink_audioservice_send(GstAvdtpSink *self,
+ const bt_audio_msg_header_t *msg);
+static int gst_avdtp_sink_audioservice_expect(GstAvdtpSink *self,
+ bt_audio_msg_header_t *outmsg,
+ guint8 expected_name);
+
+
+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 void gst_avdtp_sink_transport_release(GstAvdtpSink *self)
+{
+ DBusMessage *msg;
+ const char *access_type = "w";
+
+ msg = dbus_message_new_method_call("org.bluez", self->transport,
+ "org.bluez.MediaTransport",
+ "Release");
+
+ dbus_message_append_args(msg, DBUS_TYPE_STRING, &access_type,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(self->data->conn, msg, NULL);
+
+ dbus_message_unref(msg);
+}
+
+static gboolean gst_avdtp_sink_stop(GstBaseSink *basesink)
+{
+ GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+
+ 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_shutdown(self->stream, TRUE, NULL);
+ g_io_channel_unref(self->stream);
+ self->stream = NULL;
+ }
+
+ if (self->data) {
+ if (self->transport)
+ gst_avdtp_sink_transport_release(self);
+ if (self->data->conn)
+ dbus_connection_unref(self->data->conn);
+ g_free(self->data);
+ self->data = NULL;
+ }
+
+ 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);
+
+ if (self->transport)
+ g_free(self->transport);
+
+ 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;
+
+ case PROP_TRANSPORT:
+ if (sink->transport)
+ g_free(sink->transport);
+ sink->transport = g_value_dup_string(value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+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;
+
+ case PROP_TRANSPORT:
+ g_value_set_string(value, sink->transport);
+ 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);
+ g_io_channel_set_encoding(sink->stream, NULL, NULL);
+ GST_DEBUG_OBJECT(sink, "stream_fd=%d", ret);
+
+ return 0;
+}
+
+static codec_capabilities_t *gst_avdtp_find_caps(GstAvdtpSink *sink,
+ uint8_t codec_type)
+{
+ struct bt_get_capabilities_rsp *rsp = sink->data->caps;
+ codec_capabilities_t *codec = (void *) rsp->data;
+ int bytes_left = rsp->h.length - sizeof(*rsp);
+
+ while (bytes_left > 0) {
+ if ((codec->type == codec_type) &&
+ !(codec->lock & BT_WRITE_LOCK))
+ break;
+
+ bytes_left -= codec->length;
+ codec = (void *) codec + codec->length;
+ }
+
+ if (bytes_left <= 0)
+ return NULL;
+
+ return codec;
+}
+
+static gboolean gst_avdtp_sink_init_sbc_pkt_conf(GstAvdtpSink *sink,
+ GstCaps *caps,
+ sbc_capabilities_t *pkt)
+{
+ sbc_capabilities_t *cfg;
+ const GValue *value = NULL;
+ const char *pref, *name;
+ gint rate, subbands, blocks;
+ GstStructure *structure = gst_caps_get_structure(caps, 0);
+
+ cfg = (void *) gst_avdtp_find_caps(sink, BT_A2DP_SBC_SINK);
+ 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;
+ GError *gerr = NULL;
+ GIOStatus status;
+ GIOFlags flags;
+ int fd;
+
+ /* Proceed if stream was already acquired */
+ if (self->stream != NULL)
+ goto proceed;
+
+ ret = gst_avdtp_sink_bluetooth_recvmsg_fd(self);
+ if (ret < 0)
+ return FALSE;
+
+ if (!self->stream) {
+ GST_ERROR_OBJECT(self, "Error while configuring device: "
+ "could not acquire audio socket");
+ return FALSE;
+ }
+
+proceed:
+ /* set stream socket to nonblock */
+ GST_LOG_OBJECT(self, "setting stream socket to nonblock");
+ flags = g_io_channel_get_flags(self->stream);
+ 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");
+ }
+
+ fd = g_io_channel_unix_get_fd(self->stream);
+
+ /* 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) {
+ ssize_t bread = read(fd, data->buffer, data->link_mtu);
+ if (bread <= 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)
+{
+ if (cond & G_IO_HUP || cond & G_IO_NVAL)
+ return FALSE;
+ else if (cond & G_IO_ERR)
+ GST_WARNING_OBJECT(GST_AVDTP_SINK(data),
+ "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;
+
+ if (sbc == NULL)
+ return NULL;
+
+ 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;
+
+ if (!mpeg)
+ return NULL;
+
+ 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 GstStructure *gst_avdtp_sink_parse_sbc_raw(GstAvdtpSink *self)
+{
+ a2dp_sbc_t *sbc = (a2dp_sbc_t *) self->data->config;
+ GstStructure *structure;
+ GValue *value;
+ GValue *list;
+ gboolean mono, stereo;
+
+ structure = gst_structure_empty_new("audio/x-sbc");
+ value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING);
+
+ /* mode */
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
+ g_value_set_static_string(value, "mono");
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) {
+ g_value_set_static_string(value, "stereo");
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) {
+ g_value_set_static_string(value, "dual");
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) {
+ g_value_set_static_string(value, "joint");
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "mode", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* subbands */
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ value = g_value_init(value, G_TYPE_INT);
+ if (sbc->subbands & BT_A2DP_SUBBANDS_4) {
+ g_value_set_int(value, 4);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->subbands & BT_A2DP_SUBBANDS_8) {
+ g_value_set_int(value, 8);
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "subbands", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* blocks */
+ value = g_value_init(value, G_TYPE_INT);
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) {
+ g_value_set_int(value, 16);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) {
+ g_value_set_int(value, 12);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) {
+ g_value_set_int(value, 8);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) {
+ g_value_set_int(value, 4);
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "blocks", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* allocation */
+ g_value_init(value, G_TYPE_STRING);
+ list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST);
+ if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) {
+ g_value_set_static_string(value, "loudness");
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) {
+ g_value_set_static_string(value, "snr");
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "allocation", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* rate */
+ g_value_init(value, G_TYPE_INT);
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (sbc->frequency & BT_SBC_SAMPLING_FREQ_48000) {
+ g_value_set_int(value, 48000);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->frequency & BT_SBC_SAMPLING_FREQ_44100) {
+ g_value_set_int(value, 44100);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->frequency & BT_SBC_SAMPLING_FREQ_32000) {
+ g_value_set_int(value, 32000);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (sbc->frequency & BT_SBC_SAMPLING_FREQ_16000) {
+ g_value_set_int(value, 16000);
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "rate", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* bitpool */
+ value = g_value_init(value, GST_TYPE_INT_RANGE);
+ gst_value_set_int_range(value,
+ MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL),
+ MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL));
+ gst_structure_set_value(structure, "bitpool", value);
+ g_value_unset(value);
+
+ /* channels */
+ mono = FALSE;
+ stereo = FALSE;
+ if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+ mono = TRUE;
+ if ((sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) ||
+ (sbc->channel_mode &
+ BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) ||
+ (sbc->channel_mode &
+ BT_A2DP_CHANNEL_MODE_JOINT_STEREO))
+ stereo = TRUE;
+
+ if (mono && stereo) {
+ g_value_init(value, GST_TYPE_INT_RANGE);
+ gst_value_set_int_range(value, 1, 2);
+ } else {
+ g_value_init(value, G_TYPE_INT);
+ if (mono)
+ g_value_set_int(value, 1);
+ else if (stereo)
+ g_value_set_int(value, 2);
+ else {
+ GST_ERROR_OBJECT(self,
+ "Unexpected number of channels");
+ g_value_set_int(value, 0);
+ }
+ }
+
+ gst_structure_set_value(structure, "channels", value);
+ g_free(value);
+
+ return structure;
+}
+
+static GstStructure *gst_avdtp_sink_parse_mpeg_raw(GstAvdtpSink *self)
+{
+ a2dp_mpeg_t *mpeg = (a2dp_mpeg_t *) self->data->config;
+ GstStructure *structure;
+ GValue *value;
+ GValue *list;
+ gboolean valid_layer = FALSE;
+ gboolean mono, stereo;
+
+ GST_LOG_OBJECT(self, "parsing mpeg caps");
+
+ structure = gst_structure_empty_new("audio/mpeg");
+ value = g_new0(GValue, 1);
+ g_value_init(value, G_TYPE_INT);
+
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ g_value_set_int(value, 1);
+ gst_value_list_prepend_value(list, value);
+ g_value_set_int(value, 2);
+ gst_value_list_prepend_value(list, value);
+ gst_structure_set_value(structure, "mpegversion", list);
+ g_free(list);
+
+ /* layer */
+ GST_LOG_OBJECT(self, "setting mpeg layer");
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (mpeg->layer & BT_MPEG_LAYER_1) {
+ g_value_set_int(value, 1);
+ gst_value_list_prepend_value(list, value);
+ valid_layer = TRUE;
+ }
+ if (mpeg->layer & BT_MPEG_LAYER_2) {
+ g_value_set_int(value, 2);
+ gst_value_list_prepend_value(list, value);
+ valid_layer = TRUE;
+ }
+ if (mpeg->layer & BT_MPEG_LAYER_3) {
+ g_value_set_int(value, 3);
+ gst_value_list_prepend_value(list, value);
+ valid_layer = TRUE;
+ }
+ if (list) {
+ gst_structure_set_value(structure, "layer", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ if (!valid_layer) {
+ gst_structure_free(structure);
+ g_free(value);
+ return NULL;
+ }
+
+ /* rate */
+ GST_LOG_OBJECT(self, "setting mpeg rate");
+ list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+ if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_48000) {
+ g_value_set_int(value, 48000);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_44100) {
+ g_value_set_int(value, 44100);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_32000) {
+ g_value_set_int(value, 32000);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_24000) {
+ g_value_set_int(value, 24000);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_22050) {
+ g_value_set_int(value, 22050);
+ gst_value_list_prepend_value(list, value);
+ }
+ if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_16000) {
+ g_value_set_int(value, 16000);
+ gst_value_list_prepend_value(list, value);
+ }
+ g_value_unset(value);
+ if (list) {
+ gst_structure_set_value(structure, "rate", list);
+ g_free(list);
+ list = NULL;
+ }
+
+ /* channels */
+ GST_LOG_OBJECT(self, "setting mpeg channels");
+ mono = FALSE;
+ stereo = FALSE;
+ if (mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+ mono = TRUE;
+ if ((mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) ||
+ (mpeg->channel_mode &
+ BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) ||
+ (mpeg->channel_mode &
+ BT_A2DP_CHANNEL_MODE_JOINT_STEREO))
+ stereo = TRUE;
+
+ if (mono && stereo) {
+ g_value_init(value, GST_TYPE_INT_RANGE);
+ gst_value_set_int_range(value, 1, 2);
+ } else {
+ g_value_init(value, G_TYPE_INT);
+ if (mono)
+ g_value_set_int(value, 1);
+ else if (stereo)
+ g_value_set_int(value, 2);
+ else {
+ GST_ERROR_OBJECT(self,
+ "Unexpected number of channels");
+ g_value_set_int(value, 0);
+ }
+ }
+ gst_structure_set_value(structure, "channels", value);
+ g_free(value);
+
+ return structure;
+}
+
+static gboolean gst_avdtp_sink_update_config(GstAvdtpSink *self)
+{
+ GstStructure *structure;
+ gchar *tmp;
+
+ switch (self->data->codec) {
+ case A2DP_CODEC_SBC:
+ structure = gst_avdtp_sink_parse_sbc_raw(self);
+ break;
+ case A2DP_CODEC_MPEG12:
+ structure = gst_avdtp_sink_parse_mpeg_raw(self);
+ break;
+ default:
+ GST_ERROR_OBJECT(self, "Unsupported configuration");
+ return FALSE;
+ }
+
+ if (structure == NULL)
+ return FALSE;
+
+ if (self->dev_caps != NULL)
+ gst_caps_unref(self->dev_caps);
+
+ self->dev_caps = gst_caps_new_full(structure, NULL);
+
+ tmp = gst_caps_to_string(self->dev_caps);
+ GST_DEBUG_OBJECT(self, "Transport configuration: %s", tmp);
+ g_free(tmp);
+
+ return TRUE;
+}
+
+static gboolean gst_avdtp_sink_update_caps(GstAvdtpSink *self)
+{
+ sbc_capabilities_t *sbc;
+ mpeg_capabilities_t *mpeg;
+ GstStructure *sbc_structure;
+ GstStructure *mpeg_structure;
+ gchar *tmp;
+
+ GST_LOG_OBJECT(self, "updating device caps");
+
+ if (self->data->config_size != 0 && self->data->config != NULL)
+ return gst_avdtp_sink_update_config(self);
+
+ sbc = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK);
+ mpeg = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK);
+
+ 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_SUGGESTED_BUFFER_SIZE];
+ struct bt_get_capabilities_req *req = (void *) buf;
+ struct bt_get_capabilities_rsp *rsp = (void *) buf;
+ int err;
+
+ memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+
+ req->h.type = BT_REQUEST;
+ req->h.name = BT_GET_CAPABILITIES;
+ req->h.length = sizeof(*req);
+
+ if (self->device == NULL)
+ return FALSE;
+ strncpy(req->destination, self->device, 18);
+ if (self->autoconnect)
+ req->flags |= BT_FLAG_AUTOCONNECT;
+
+ err = gst_avdtp_sink_audioservice_send(self, &req->h);
+ if (err < 0) {
+ GST_ERROR_OBJECT(self, "Error while asking device caps");
+ return FALSE;
+ }
+
+ rsp->h.length = 0;
+ err = gst_avdtp_sink_audioservice_expect(self,
+ &rsp->h, BT_GET_CAPABILITIES);
+ if (err < 0) {
+ GST_ERROR_OBJECT(self, "Error while getting device caps");
+ return FALSE;
+ }
+
+ self->data->caps = g_malloc0(rsp->h.length);
+ memcpy(self->data->caps, rsp, rsp->h.length);
+ 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-stereo") == 0)
+ return BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+ else if (strcmp(mode, "dual-channel") == 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_transport_parse_property(GstAvdtpSink *self,
+ DBusMessageIter *i)
+{
+ const char *key;
+ DBusMessageIter variant_i;
+
+ if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
+ GST_ERROR_OBJECT(self, "Property name not a string.");
+ return FALSE;
+ }
+
+ dbus_message_iter_get_basic(i, &key);
+
+ if (!dbus_message_iter_next(i)) {
+ GST_ERROR_OBJECT(self, "Property value missing");
+ return FALSE;
+ }
+
+ if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
+ GST_ERROR_OBJECT(self, "Property value not a variant.");
+ return FALSE;
+ }
+
+ dbus_message_iter_recurse(i, &variant_i);
+
+ switch (dbus_message_iter_get_arg_type(&variant_i)) {
+ case DBUS_TYPE_BYTE: {
+ uint8_t value;
+ dbus_message_iter_get_basic(&variant_i, &value);
+
+ if (g_str_equal(key, "Codec") == TRUE)
+ self->data->codec = value;
+
+ break;
+ }
+ case DBUS_TYPE_STRING: {
+ const char *value;
+ dbus_message_iter_get_basic(&variant_i, &value);
+
+ if (g_str_equal(key, "UUID") == TRUE) {
+ g_free(self->data->uuid);
+ self->data->uuid = g_strdup(value);
+ }
+
+ break;
+ }
+ case DBUS_TYPE_ARRAY: {
+ DBusMessageIter array_i;
+ char *value;
+ int size;
+
+ dbus_message_iter_recurse(&variant_i, &array_i);
+ dbus_message_iter_get_fixed_array(&array_i, &value, &size);
+
+ if (g_str_equal(key, "Configuration")) {
+ g_free(self->data->config);
+ self->data->config = g_new0(guint8, size);
+ self->data->config_size = size;
+ memcpy(self->data->config, value, size);
+ }
+
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean gst_avdtp_sink_transport_acquire(GstAvdtpSink *self)
+{
+ DBusMessage *msg, *reply;
+ DBusError err;
+ const char *access_type = "w";
+ int fd;
+ uint16_t imtu, omtu;
+
+ dbus_error_init(&err);
+
+ if (self->data->conn == NULL)
+ self->data->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+
+ msg = dbus_message_new_method_call("org.bluez", self->transport,
+ "org.bluez.MediaTransport",
+ "Acquire");
+
+ dbus_message_append_args(msg, DBUS_TYPE_STRING, &access_type,
+ DBUS_TYPE_INVALID);
+
+ reply = dbus_connection_send_with_reply_and_block(self->data->conn,
+ msg, -1, &err);
+
+ if (dbus_error_is_set(&err))
+ goto fail;
+
+ if (dbus_message_get_args(reply, &err, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_UINT16, &imtu,
+ DBUS_TYPE_UINT16, &omtu,
+ DBUS_TYPE_INVALID) == FALSE)
+ goto fail;
+
+ dbus_message_unref(reply);
+
+ self->stream = g_io_channel_unix_new(fd);
+ g_io_channel_set_encoding(self->stream, NULL, NULL);
+ g_io_channel_set_close_on_unref(self->stream, TRUE);
+ self->data->link_mtu = omtu;
+ GST_DEBUG_OBJECT(self, "stream_fd=%d mtu=%d", fd, omtu);
+
+ return TRUE;
+
+fail:
+ GST_ERROR_OBJECT(self, "Failed to acquire transport stream: %s",
+ err.message);
+
+ dbus_error_free(&err);
+
+ if (reply)
+ dbus_message_unref(msg);
+
+ return FALSE;
+}
+
+static gboolean gst_avdtp_sink_transport_get_properties(GstAvdtpSink *self)
+{
+ DBusMessage *msg, *reply;
+ DBusMessageIter arg_i, ele_i;
+ DBusError err;
+
+ dbus_error_init(&err);
+
+ /* Transport need to be acquire first to make sure the MTUs are
+ available */
+ if (gst_avdtp_sink_transport_acquire(self) == FALSE)
+ return FALSE;
+
+ msg = dbus_message_new_method_call("org.bluez", self->transport,
+ "org.bluez.MediaTransport",
+ "GetProperties");
+ reply = dbus_connection_send_with_reply_and_block(self->data->conn,
+ msg, -1, &err);
+
+ if (dbus_error_is_set(&err) || reply == NULL) {
+ GST_ERROR_OBJECT(self, "Failed to get transport properties: %s",
+ err.message);
+ goto fail;
+ }
+
+ if (!dbus_message_iter_init(reply, &arg_i)) {
+ GST_ERROR_OBJECT(self, "GetProperties reply has no arguments.");
+ goto fail;
+ }
+
+ if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
+ GST_ERROR_OBJECT(self, "GetProperties argument is not an array.");
+ goto fail;
+ }
+
+ dbus_message_iter_recurse(&arg_i, &ele_i);
+ while (dbus_message_iter_get_arg_type(&ele_i) != DBUS_TYPE_INVALID) {
+
+ if (dbus_message_iter_get_arg_type(&ele_i) ==
+ DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter dict_i;
+
+ dbus_message_iter_recurse(&ele_i, &dict_i);
+
+ gst_avdtp_sink_transport_parse_property(self, &dict_i);
+ }
+
+ if (!dbus_message_iter_next(&ele_i))
+ break;
+ }
+
+ return gst_avdtp_sink_update_caps(self);
+
+fail:
+ dbus_message_unref(msg);
+ dbus_message_unref(reply);
+ return FALSE;
+
+}
+
+static gboolean gst_avdtp_sink_start(GstBaseSink *basesink)
+{
+ GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+ gint sk;
+ gint err;
+
+ GST_INFO_OBJECT(self, "start");
+
+ self->data = g_new0(struct bluetooth_data, 1);
+
+ self->stream = NULL;
+ self->stream_caps = NULL;
+ self->mp3_using_crc = -1;
+ self->channel_mode = -1;
+
+ if (self->transport != NULL)
+ return gst_avdtp_sink_transport_get_properties(self);
+
+ self->watch_id = 0;
+
+ sk = bt_audio_service_open();
+ 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);
+ g_io_channel_set_encoding(self->server, NULL, NULL);
+ self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR |
+ G_IO_NVAL, server_callback, self);
+
+ 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_SUGGESTED_BUFFER_SIZE];
+ struct bt_start_stream_req *req = (void *) buf;
+ struct bt_start_stream_rsp *rsp = (void *) buf;
+ struct bt_new_stream_ind *ind = (void *) buf;
+ int err;
+
+ if (self->transport != NULL)
+ return gst_avdtp_sink_conf_recv_stream_fd(self);
+
+ memset(req, 0, sizeof(buf));
+ req->h.type = BT_REQUEST;
+ req->h.name = BT_START_STREAM;
+ req->h.length = sizeof(*req);
+
+ err = gst_avdtp_sink_audioservice_send(self, &req->h);
+ if (err < 0) {
+ GST_ERROR_OBJECT(self, "Error ocurred while sending "
+ "start packet");
+ return FALSE;
+ }
+
+ rsp->h.length = sizeof(*rsp);
+ err = gst_avdtp_sink_audioservice_expect(self, &rsp->h,
+ BT_START_STREAM);
+ if (err < 0) {
+ GST_ERROR_OBJECT(self, "Error while stream "
+ "start confirmation");
+ return FALSE;
+ }
+
+ ind->h.length = sizeof(*ind);
+ err = gst_avdtp_sink_audioservice_expect(self, &ind->h,
+ BT_NEW_STREAM);
+ if (err < 0) {
+ 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_SUGGESTED_BUFFER_SIZE];
+ struct bt_open_req *open_req = (void *) buf;
+ struct bt_open_rsp *open_rsp = (void *) buf;
+ struct bt_set_configuration_req *req = (void *) buf;
+ struct bt_set_configuration_rsp *rsp = (void *) buf;
+ gboolean ret;
+ gchar *temp;
+ GstStructure *structure;
+ codec_capabilities_t *codec = NULL;
+ int err;
+
+ temp = gst_caps_to_string(caps);
+ GST_DEBUG_OBJECT(self, "configuring device with caps: %s", temp);
+ g_free(temp);
+
+ /* Transport already configured */
+ if (self->transport != NULL)
+ return TRUE;
+
+ structure = gst_caps_get_structure(caps, 0);
+
+ if (gst_structure_has_name(structure, "audio/x-sbc"))
+ codec = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK);
+ else if (gst_structure_has_name(structure, "audio/mpeg"))
+ codec = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK);
+
+ if (codec == NULL) {
+ GST_ERROR_OBJECT(self, "Couldn't parse caps "
+ "to packet configuration");
+ return FALSE;
+ }
+
+ memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+ open_req->h.type = BT_REQUEST;
+ open_req->h.name = BT_OPEN;
+ open_req->h.length = sizeof(*open_req);
+
+ strncpy(open_req->destination, self->device, 18);
+ open_req->seid = codec->seid;
+ open_req->lock = BT_WRITE_LOCK;
+
+ err = gst_avdtp_sink_audioservice_send(self, &open_req->h);
+ if (err < 0) {
+ GST_ERROR_OBJECT(self, "Error ocurred while sending "
+ "open packet");
+ return FALSE;
+ }
+
+ open_rsp->h.length = sizeof(*open_rsp);
+ err = gst_avdtp_sink_audioservice_expect(self, &open_rsp->h,
+ BT_OPEN);
+ if (err < 0) {
+ GST_ERROR_OBJECT(self, "Error while receiving device "
+ "confirmation");
+ return FALSE;
+ }
+
+ memset(req, 0, sizeof(buf));
+ req->h.type = BT_REQUEST;
+ req->h.name = BT_SET_CONFIGURATION;
+ req->h.length = sizeof(*req);
+ memcpy(&req->codec, codec, sizeof(req->codec));
+
+ if (codec->type == BT_A2DP_SBC_SINK)
+ ret = gst_avdtp_sink_init_sbc_pkt_conf(self, caps,
+ (void *) &req->codec);
+ else
+ ret = gst_avdtp_sink_init_mp3_pkt_conf(self, caps,
+ (void *) &req->codec);
+
+ if (!ret) {
+ GST_ERROR_OBJECT(self, "Couldn't parse caps "
+ "to packet configuration");
+ return FALSE;
+ }
+
+ req->h.length += req->codec.length - sizeof(req->codec);
+ err = gst_avdtp_sink_audioservice_send(self, &req->h);
+ if (err < 0) {
+ GST_ERROR_OBJECT(self, "Error ocurred while sending "
+ "configurarion packet");
+ return FALSE;
+ }
+
+ rsp->h.length = sizeof(*rsp);
+ err = gst_avdtp_sink_audioservice_expect(self, &rsp->h,
+ BT_SET_CONFIGURATION);
+ if (err < 0) {
+ GST_ERROR_OBJECT(self, "Error while receiving device "
+ "confirmation");
+ return FALSE;
+ }
+
+ self->data->link_mtu = rsp->link_mtu;
+
+ 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);
+ ssize_t ret;
+ int fd;
+
+ fd = g_io_channel_unix_get_fd(self->stream);
+
+ ret = write(fd, GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE(buffer));
+ if (ret < 0) {
+ GST_ERROR_OBJECT(self, "Error while writting to socket: %s",
+ 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));
+
+ g_object_class_install_property(object_class, PROP_TRANSPORT,
+ g_param_spec_string("transport",
+ "Transport",
+ "Use configured transport",
+ NULL, G_PARAM_READWRITE));
+
+ GST_DEBUG_CATEGORY_INIT(avdtp_sink_debug, "avdtpsink", 0,
+ "A2DP headset sink element");
+}
+
+static void gst_avdtp_sink_init(GstAvdtpSink *self,
+ GstAvdtpSinkClass *klass)
+{
+ self->device = NULL;
+ self->transport = 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 int gst_avdtp_sink_audioservice_send(GstAvdtpSink *self,
+ const bt_audio_msg_header_t *msg)
+{
+ ssize_t written;
+ const char *type, *name;
+ uint16_t length;
+ int fd;
+
+ length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+ fd = g_io_channel_unix_get_fd(self->server);
+
+ written = write(fd, msg, length);
+ if (written < 0) {
+ GST_ERROR_OBJECT(self, "Error sending data to audio service:"
+ " %s", strerror(errno));
+ return -errno;
+ }
+
+ type = bt_audio_strtype(msg->type);
+ name = bt_audio_strname(msg->name);
+
+ GST_DEBUG_OBJECT(self, "sent: %s -> %s", type, name);
+
+ return 0;
+}
+
+static int gst_avdtp_sink_audioservice_recv(GstAvdtpSink *self,
+ bt_audio_msg_header_t *inmsg)
+{
+ ssize_t bytes_read;
+ const char *type, *name;
+ uint16_t length;
+ int fd, err;
+
+ length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+ fd = g_io_channel_unix_get_fd(self->server);
+
+ bytes_read = read(fd, inmsg, length);
+ if (bytes_read < 0) {
+ GST_ERROR_OBJECT(self, "Error receiving data from "
+ "audio service: %s", strerror(errno));
+ return -errno;
+ }
+
+ type = bt_audio_strtype(inmsg->type);
+ if (!type) {
+ err = -EINVAL;
+ GST_ERROR_OBJECT(self, "Bogus message type %d "
+ "received from audio service",
+ inmsg->type);
+ }
+
+ name = bt_audio_strname(inmsg->name);
+ if (!name) {
+ err = -EINVAL;
+ GST_ERROR_OBJECT(self, "Bogus message name %d "
+ "received from audio service",
+ inmsg->name);
+ }
+
+ if (inmsg->type == BT_ERROR) {
+ bt_audio_error_t *msg = (void *) inmsg;
+ err = -EINVAL;
+ GST_ERROR_OBJECT(self, "%s failed : "
+ "%s(%d)",
+ name,
+ strerror(msg->posix_errno),
+ msg->posix_errno);
+ }
+
+ GST_DEBUG_OBJECT(self, "received: %s <- %s", type, name);
+
+ return err;
+}
+
+static int gst_avdtp_sink_audioservice_expect(GstAvdtpSink *self,
+ bt_audio_msg_header_t *outmsg,
+ guint8 expected_name)
+{
+ int err;
+
+ err = gst_avdtp_sink_audioservice_recv(self, outmsg);
+ if (err < 0)
+ return err;
+
+ if (outmsg->name != expected_name)
+ return -EINVAL;
+
+ return 0;
+}
+
+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);
+}
+
+void gst_avdtp_sink_set_transport(GstAvdtpSink *self, const gchar *trans)
+{
+ if (self->transport != NULL)
+ g_free(self->transport);
+
+ GST_LOG_OBJECT(self, "Setting transport: %s", trans);
+ self->transport = g_strdup(trans);
+}
+
+gchar *gst_avdtp_sink_get_device(GstAvdtpSink *self)
+{
+ return g_strdup(self->device);
+}
+
+gchar *gst_avdtp_sink_get_transport(GstAvdtpSink *self)
+{
+ return g_strdup(self->transport);
+}
+
+void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc)
+{
+ gint new_crc;
+
+ 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 0000000..c4e5645
--- /dev/null
+++ b/audio/gstavdtpsink.h
@@ -0,0 +1,107 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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;
+ gchar *transport;
+ 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);
+
+void gst_avdtp_sink_set_transport(GstAvdtpSink *sink,
+ const gchar *transport);
+
+gchar *gst_avdtp_sink_get_device(GstAvdtpSink *sink);
+
+gchar *gst_avdtp_sink_get_transport(GstAvdtpSink *sink);
+
+gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin);
+
+void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc);
+
+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 0000000..9930820
--- /dev/null
+++ b/audio/gstbluetooth.c
@@ -0,0 +1,109 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 <gst/gst.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 (data == NULL)
+ return;
+
+ if (sbc_init(&sbc, 0) < 0)
+ return;
+
+ aux = g_new(guint8, 32);
+ memcpy(aux, data, 32);
+ if (sbc_parse(&sbc, aux, 32) < 0)
+ goto done;
+
+ caps = gst_sbc_parse_caps_from_sbc(&sbc);
+ gst_type_find_suggest(tf, GST_TYPE_FIND_POSSIBLE, caps);
+ gst_caps_unref(caps);
+
+done:
+ g_free(aux);
+ sbc_finish(&sbc);
+}
+
+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;
+}
+
+extern GstPluginDesc gst_plugin_desc __attribute__ ((visibility("default")));
+
+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/gstpragma.h b/audio/gstpragma.h
new file mode 100644
index 0000000..626311c
--- /dev/null
+++ b/audio/gstpragma.h
@@ -0,0 +1,24 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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
+ *
+ */
+
+//#pragma GCC diagnostic warning "-Wmissing-declarations"
diff --git a/audio/gstrtpsbcpay.c b/audio/gstrtpsbcpay.c
new file mode 100644
index 0000000..1159bfe
--- /dev/null
+++ b/audio/gstrtpsbcpay.c
@@ -0,0 +1,352 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 "gstpragma.h"
+#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) ||
+ (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 0000000..398de82
--- /dev/null
+++ b/audio/gstrtpsbcpay.h
@@ -0,0 +1,66 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 0000000..2e5cb0a
--- /dev/null
+++ b/audio/gstsbcdec.c
@@ -0,0 +1,223 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 "gstpragma.h"
+#include "gstsbcutil.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) BYTE_ORDER, "
+ "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;
+
+ codesize = sbc_get_codesize(&dec->sbc);
+
+ 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 0000000..c77feae
--- /dev/null
+++ b/audio/gstsbcdec.h
@@ -0,0 +1,66 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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_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 0000000..d2de4ee
--- /dev/null
+++ b/audio/gstsbcenc.c
@@ -0,0 +1,603 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 "gstpragma.h"
+#include "gstsbcutil.h"
+#include "gstsbcenc.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) BYTE_ORDER, "
+ "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 0000000..0329351
--- /dev/null
+++ b/audio/gstsbcenc.h
@@ -0,0 +1,75 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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;
+
+ guint 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 0000000..a44b52c
--- /dev/null
+++ b/audio/gstsbcparse.c
@@ -0,0 +1,220 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 "gstpragma.h"
+#include "gstsbcutil.h"
+#include "gstsbcparse.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;
+
+ /* 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 0000000..ecb8be4
--- /dev/null
+++ b/audio/gstsbcparse.h
@@ -0,0 +1,69 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 0000000..63c90c2
--- /dev/null
+++ b/audio/gstsbcutil.c
@@ -0,0 +1,521 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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)
+{
+ unsigned int i;
+ const GValue *value;
+ const gchar *aux;
+ gboolean joint, stereo, dual, mono;
+ guint size = gst_value_list_get_size(list);
+
+ joint = stereo = dual = mono = FALSE;
+
+ 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 0000000..a7f84d5
--- /dev/null
+++ b/audio/gstsbcutil.h
@@ -0,0 +1,75 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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);
+
+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 0000000..dff10d1
--- /dev/null
+++ b/audio/headset.c
@@ -0,0 +1,2951 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#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 <assert.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "device.h"
+#include "manager.h"
+#include "error.h"
+#include "telephony.h"
+#include "headset.h"
+#include "glib-helper.h"
+#include "btio.h"
+#include "dbus-common.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#define DC_TIMEOUT 3
+
+#define RING_INTERVAL 3
+
+#define BUF_SIZE 1024
+
+#define HEADSET_GAIN_SPEAKER 'S'
+#define HEADSET_GAIN_MICROPHONE 'M'
+
+static struct {
+ gboolean telephony_ready; /* Telephony plugin initialized */
+ uint32_t features; /* HFP AG features */
+ const struct indicator *indicators; /* Available HFP indicators */
+ int er_mode; /* Event reporting mode */
+ int er_ind; /* Event reporting for indicators */
+ int rh; /* Response and Hold state */
+ char *number; /* Incoming phone number */
+ int number_type; /* Incoming number type */
+ guint ring_timer; /* For incoming call indication */
+ const char *chld; /* Response to AT+CHLD=? */
+} ag = {
+ .telephony_ready = FALSE,
+ .features = 0,
+ .er_mode = 3,
+ .er_ind = 0,
+ .rh = BTRH_NOT_SUPPORTED,
+ .number = NULL,
+ .number_type = 0,
+ .ring_timer = 0,
+};
+
+static gboolean sco_hci = TRUE;
+static gboolean fast_connectable = FALSE;
+
+static GSList *active_devices = NULL;
+
+static char *str_state[] = {
+ "HEADSET_STATE_DISCONNECTED",
+ "HEADSET_STATE_CONNECTING",
+ "HEADSET_STATE_CONNECTED",
+ "HEADSET_STATE_PLAY_IN_PROGRESS",
+ "HEADSET_STATE_PLAYING",
+};
+
+struct headset_state_callback {
+ headset_state_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+struct headset_nrec_callback {
+ unsigned int id;
+ headset_nrec_cb cb;
+ void *user_data;
+};
+
+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;
+ uint16_t svclass;
+};
+
+struct headset_slc {
+ char buf[BUF_SIZE];
+ int data_start;
+ int data_length;
+
+ gboolean cli_active;
+ gboolean cme_enabled;
+ gboolean cwa_enabled;
+ gboolean pending_ring;
+ gboolean inband_ring;
+ gboolean nrec;
+ gboolean nrec_req;
+
+ int sp_gain;
+ int mic_gain;
+
+ unsigned int hf_features;
+};
+
+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 dc_timer;
+
+ gboolean hfp_active;
+ gboolean search_hfp;
+
+ headset_state_t state;
+ struct pending_connect *pending;
+
+ headset_lock_t lock;
+ struct headset_slc *slc;
+ GSList *nrec_cbs;
+};
+
+struct event {
+ const char *cmd;
+ int (*callback) (struct audio_device *device, const char *buf);
+};
+
+static GSList *headset_callbacks = NULL;
+
+static void error_connect_failed(DBusConnection *conn, DBusMessage *msg,
+ int err)
+{
+ DBusMessage *reply = btd_error_failed(msg,
+ err < 0 ? strerror(-err) : "Connect failed");
+ g_dbus_send_message(conn, reply);
+}
+
+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 void print_ag_features(uint32_t features)
+{
+ GString *gstr;
+ char *str;
+
+ if (features == 0) {
+ DBG("HFP AG features: (none)");
+ return;
+ }
+
+ gstr = g_string_new("HFP AG features: ");
+
+ if (features & AG_FEATURE_THREE_WAY_CALLING)
+ g_string_append(gstr, "\"Three-way calling\" ");
+ if (features & AG_FEATURE_EC_ANDOR_NR)
+ g_string_append(gstr, "\"EC and/or NR function\" ");
+ if (features & AG_FEATURE_VOICE_RECOGNITION)
+ g_string_append(gstr, "\"Voice recognition function\" ");
+ if (features & AG_FEATURE_INBAND_RINGTONE)
+ g_string_append(gstr, "\"In-band ring tone capability\" ");
+ if (features & AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG)
+ g_string_append(gstr, "\"Attach a number to a voice tag\" ");
+ if (features & AG_FEATURE_REJECT_A_CALL)
+ g_string_append(gstr, "\"Ability to reject a call\" ");
+ if (features & AG_FEATURE_ENHANCED_CALL_STATUS)
+ g_string_append(gstr, "\"Enhanced call status\" ");
+ if (features & AG_FEATURE_ENHANCED_CALL_CONTROL)
+ g_string_append(gstr, "\"Enhanced call control\" ");
+ if (features & AG_FEATURE_EXTENDED_ERROR_RESULT_CODES)
+ g_string_append(gstr, "\"Extended Error Result Codes\" ");
+
+ str = g_string_free(gstr, FALSE);
+
+ DBG("%s", str);
+
+ g_free(str);
+}
+
+static void print_hf_features(uint32_t features)
+{
+ GString *gstr;
+ char *str;
+
+ if (features == 0) {
+ DBG("HFP HF features: (none)");
+ return;
+ }
+
+ gstr = g_string_new("HFP HF features: ");
+
+ if (features & HF_FEATURE_EC_ANDOR_NR)
+ g_string_append(gstr, "\"EC and/or NR function\" ");
+ if (features & HF_FEATURE_CALL_WAITING_AND_3WAY)
+ g_string_append(gstr, "\"Call waiting and 3-way calling\" ");
+ if (features & HF_FEATURE_CLI_PRESENTATION)
+ g_string_append(gstr, "\"CLI presentation capability\" ");
+ if (features & HF_FEATURE_VOICE_RECOGNITION)
+ g_string_append(gstr, "\"Voice recognition activation\" ");
+ if (features & HF_FEATURE_REMOTE_VOLUME_CONTROL)
+ g_string_append(gstr, "\"Remote volume control\" ");
+ if (features & HF_FEATURE_ENHANCED_CALL_STATUS)
+ g_string_append(gstr, "\"Enhanced call status\" ");
+ if (features & HF_FEATURE_ENHANCED_CALL_CONTROL)
+ g_string_append(gstr, "\"Enhanced call control\" ");
+
+ str = g_string_free(gstr, FALSE);
+
+ DBG("%s", str);
+
+ g_free(str);
+}
+
+static const char *state2str(headset_state_t state)
+{
+ switch (state) {
+ case HEADSET_STATE_DISCONNECTED:
+ return "disconnected";
+ case HEADSET_STATE_CONNECTING:
+ return "connecting";
+ case HEADSET_STATE_CONNECTED:
+ case HEADSET_STATE_PLAY_IN_PROGRESS:
+ return "connected";
+ case HEADSET_STATE_PLAYING:
+ return "playing";
+ }
+
+ return NULL;
+}
+
+static int headset_send_valist(struct headset *hs, char *format, va_list ap)
+{
+ char rsp[BUF_SIZE];
+ ssize_t total_written, count;
+ int fd;
+
+ count = vsnprintf(rsp, sizeof(rsp), format, ap);
+
+ if (count < 0)
+ return -EINVAL;
+
+ if (!hs->rfcomm) {
+ error("headset_send: the headset is not connected");
+ return -EIO;
+ }
+
+ total_written = 0;
+ fd = g_io_channel_unix_get_fd(hs->rfcomm);
+
+ while (total_written < count) {
+ ssize_t written;
+
+ written = write(fd, rsp + total_written,
+ count - total_written);
+ if (written < 0)
+ return -errno;
+
+ total_written += written;
+ }
+
+ return 0;
+}
+
+static int headset_send(struct headset *hs, char *format, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, format);
+ ret = headset_send_valist(hs, format, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+static int supported_features(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+ struct headset_slc *slc = hs->slc;
+ int err;
+
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ slc->hf_features = strtoul(&buf[8], NULL, 10);
+
+ print_hf_features(slc->hf_features);
+
+ 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 char *indicator_ranges(const struct indicator *indicators)
+{
+ int i;
+ GString *gstr;
+
+ gstr = g_string_new("\r\n+CIND: ");
+
+ for (i = 0; indicators[i].desc != NULL; i++) {
+ if (i == 0)
+ g_string_append_printf(gstr, "(\"%s\",(%s))",
+ indicators[i].desc,
+ indicators[i].range);
+ else
+ g_string_append_printf(gstr, ",(\"%s\",(%s))",
+ indicators[i].desc,
+ indicators[i].range);
+ }
+
+ g_string_append(gstr, "\r\n");
+
+ return g_string_free(gstr, FALSE);
+}
+
+static char *indicator_values(const struct indicator *indicators)
+{
+ int i;
+ GString *gstr;
+
+ gstr = g_string_new("\r\n+CIND: ");
+
+ for (i = 0; indicators[i].desc != NULL; i++) {
+ if (i == 0)
+ g_string_append_printf(gstr, "%d", indicators[i].val);
+ else
+ g_string_append_printf(gstr, ",%d", indicators[i].val);
+ }
+
+ g_string_append(gstr, "\r\n");
+
+ return g_string_free(gstr, FALSE);
+}
+
+static int report_indicators(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+ int err;
+ char *str;
+
+ if (strlen(buf) < 8)
+ return -EINVAL;
+
+ if (ag.indicators == NULL) {
+ error("HFP AG indicators not initialized");
+ return headset_send(hs, "\r\nERROR\r\n");
+ }
+
+ if (buf[7] == '=')
+ str = indicator_ranges(ag.indicators);
+ else
+ str = indicator_values(ag.indicators);
+
+ err = headset_send(hs, str);
+
+ g_free(str);
+
+ 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 < 0)
+ 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;
+
+ if (p == NULL)
+ return;
+
+ if (p->svclass)
+ bt_cancel_discovery(&dev->src, &dev->dst);
+
+ 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_shutdown(p->io, TRUE, NULL);
+ 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);
+
+ if (!func)
+ return 0;
+
+ 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 send_foreach_headset(GSList *devices,
+ int (*cmp) (struct headset *hs),
+ char *format, ...)
+{
+ GSList *l;
+ va_list ap;
+
+ for (l = devices; l != NULL; l = l->next) {
+ struct audio_device *device = l->data;
+ struct headset *hs = device->headset;
+ int ret;
+
+ assert(hs != NULL);
+
+ if (cmp && cmp(hs) != 0)
+ continue;
+
+ va_start(ap, format);
+ ret = headset_send_valist(hs, format, ap);
+ if (ret < 0)
+ error("Failed to send to headset: %s (%d)",
+ strerror(-ret), -ret);
+ va_end(ap);
+ }
+}
+
+static int cli_cmp(struct headset *hs)
+{
+ struct headset_slc *slc = hs->slc;
+
+ if (!hs->hfp_active)
+ return -1;
+
+ if (slc->cli_active)
+ return 0;
+ else
+ return -1;
+}
+
+static gboolean ring_timer_cb(gpointer data)
+{
+ send_foreach_headset(active_devices, NULL, "\r\nRING\r\n");
+
+ if (ag.number)
+ send_foreach_headset(active_devices, cli_cmp,
+ "\r\n+CLIP: \"%s\",%d\r\n",
+ ag.number, ag.number_type);
+
+ return TRUE;
+}
+
+static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+ int sk;
+ struct audio_device *dev = user_data;
+ struct headset *hs = dev->headset;
+ struct headset_slc *slc = hs->slc;
+ struct pending_connect *p = hs->pending;
+
+ if (err) {
+ error("%s", err->message);
+
+ if (p != NULL) {
+ p->err = -errno;
+ if (p->msg)
+ error_connect_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;
+ }
+
+ DBG("SCO socket opened for headset %s", dev->path);
+
+ sk = g_io_channel_unix_get_fd(chan);
+
+ DBG("SCO fd=%d", sk);
+
+ if (p) {
+ p->io = NULL;
+ if (p->msg) {
+ DBusMessage *reply;
+ reply = dbus_message_new_method_return(p->msg);
+ g_dbus_send_message(dev->conn, reply);
+ }
+
+ pending_connect_finalize(dev);
+ }
+
+ fcntl(sk, F_SETFL, 0);
+
+ headset_set_state(dev, HEADSET_STATE_PLAYING);
+
+ if (slc->pending_ring) {
+ ring_timer_cb(NULL);
+ ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL,
+ ring_timer_cb,
+ NULL);
+ slc->pending_ring = FALSE;
+ }
+}
+
+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;
+ GError *err = NULL;
+ GIOChannel *io;
+
+ if (hs->state != HEADSET_STATE_CONNECTED)
+ return -EINVAL;
+
+ io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+ BT_IO_OPT_DEST_BDADDR, &dev->dst,
+ BT_IO_OPT_INVALID);
+ if (!io) {
+ error("%s", err->message);
+ g_error_free(err);
+ return -EIO;
+ }
+
+ hs->sco = io;
+
+ 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 int hfp_cmp(struct headset *hs)
+{
+ if (hs->hfp_active)
+ return 0;
+ else
+ return -1;
+}
+
+static void hfp_slc_complete(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+ struct pending_connect *p = hs->pending;
+
+ DBG("HFP Service Level Connection established");
+
+ headset_set_state(dev, HEADSET_STATE_CONNECTED);
+
+ if (p == NULL)
+ return;
+
+ if (p->target_state == HEADSET_STATE_CONNECTED) {
+ if (p->msg) {
+ DBusMessage *reply = dbus_message_new_method_return(p->msg);
+ g_dbus_send_message(dev->conn, reply);
+ }
+ pending_connect_finalize(dev);
+ return;
+ }
+
+ p->err = sco_connect(dev, NULL, NULL, NULL);
+ if (p->err < 0) {
+ if (p->msg)
+ error_connect_failed(dev->conn, p->msg, p->err);
+ pending_connect_finalize(dev);
+ }
+}
+
+static int telephony_generic_rsp(struct audio_device *device, cme_error_t err)
+{
+ struct headset *hs = device->headset;
+ struct headset_slc *slc = hs->slc;
+
+ if (err != CME_ERROR_NONE) {
+ if (slc->cme_enabled)
+ return headset_send(hs, "\r\n+CME ERROR: %d\r\n", err);
+ else
+ return headset_send(hs, "\r\nERROR\r\n");
+ }
+
+ return headset_send(hs, "\r\nOK\r\n");
+}
+
+int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err)
+{
+ struct audio_device *device = telephony_device;
+ struct headset *hs = device->headset;
+ struct headset_slc *slc = hs->slc;
+ int ret;
+
+ if (err != CME_ERROR_NONE)
+ return telephony_generic_rsp(telephony_device, err);
+
+ ret = headset_send(hs, "\r\nOK\r\n");
+ if (ret < 0)
+ return ret;
+
+ if (hs->state != HEADSET_STATE_CONNECTING)
+ return 0;
+
+ if (slc->hf_features & HF_FEATURE_CALL_WAITING_AND_3WAY &&
+ ag.features & AG_FEATURE_THREE_WAY_CALLING)
+ return 0;
+
+ hfp_slc_complete(device);
+
+ return 0;
+}
+
+static int event_reporting(struct audio_device *dev, const char *buf)
+{
+ char **tokens; /* <mode>, <keyp>, <disp>, <ind>, <bfr> */
+
+ if (strlen(buf) < 13)
+ return -EINVAL;
+
+ tokens = g_strsplit(&buf[8], ",", 5);
+ if (g_strv_length(tokens) < 4) {
+ g_strfreev(tokens);
+ return -EINVAL;
+ }
+
+ ag.er_mode = atoi(tokens[0]);
+ ag.er_ind = atoi(tokens[3]);
+
+ g_strfreev(tokens);
+ tokens = NULL;
+
+ DBG("Event reporting (CMER): mode=%d, ind=%d",
+ ag.er_mode, ag.er_ind);
+
+ switch (ag.er_ind) {
+ case 0:
+ case 1:
+ telephony_event_reporting_req(dev, ag.er_ind);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int call_hold(struct audio_device *dev, const char *buf)
+{
+ struct headset *hs = dev->headset;
+ int err;
+
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ if (buf[8] != '?') {
+ telephony_call_hold_req(dev, &buf[8]);
+ return 0;
+ }
+
+ err = headset_send(hs, "\r\n+CHLD: (%s)\r\n", ag.chld);
+ if (err < 0)
+ return err;
+
+ err = headset_send(hs, "\r\nOK\r\n");
+ if (err < 0)
+ return err;
+
+ if (hs->state != HEADSET_STATE_CONNECTING)
+ return 0;
+
+ hfp_slc_complete(dev);
+
+ return 0;
+}
+
+int telephony_key_press_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int key_press(struct audio_device *device, const char *buf)
+{
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ g_dbus_emit_signal(device->conn, device->path,
+ AUDIO_HEADSET_INTERFACE, "AnswerRequested",
+ DBUS_TYPE_INVALID);
+
+ if (ag.ring_timer) {
+ g_source_remove(ag.ring_timer);
+ ag.ring_timer = 0;
+ }
+
+ telephony_key_press_req(device, &buf[8]);
+
+ return 0;
+}
+
+int telephony_answer_call_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int answer_call(struct audio_device *device, const char *buf)
+{
+ if (ag.ring_timer) {
+ g_source_remove(ag.ring_timer);
+ ag.ring_timer = 0;
+ }
+
+ if (ag.number) {
+ g_free(ag.number);
+ ag.number = NULL;
+ }
+
+ telephony_answer_call_req(device);
+
+ return 0;
+}
+
+int telephony_terminate_call_rsp(void *telephony_device,
+ cme_error_t err)
+{
+ struct audio_device *device = telephony_device;
+ struct headset *hs = device->headset;
+
+ if (err != CME_ERROR_NONE)
+ return telephony_generic_rsp(telephony_device, err);
+
+ g_dbus_emit_signal(device->conn, device->path,
+ AUDIO_HEADSET_INTERFACE, "CallTerminated",
+ DBUS_TYPE_INVALID);
+
+ return headset_send(hs, "\r\nOK\r\n");
+}
+
+static int terminate_call(struct audio_device *device, const char *buf)
+{
+ if (ag.number) {
+ g_free(ag.number);
+ ag.number = NULL;
+ }
+
+ if (ag.ring_timer) {
+ g_source_remove(ag.ring_timer);
+ ag.ring_timer = 0;
+ }
+
+ telephony_terminate_call_req(device);
+
+ return 0;
+}
+
+static int cli_notification(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+ struct headset_slc *slc = hs->slc;
+
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ slc->cli_active = buf[8] == '1' ? TRUE : FALSE;
+
+ return headset_send(hs, "\r\nOK\r\n");
+}
+
+int telephony_response_and_hold_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int response_and_hold(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+
+ if (strlen(buf) < 8)
+ return -EINVAL;
+
+ if (ag.rh == BTRH_NOT_SUPPORTED)
+ return telephony_generic_rsp(device, CME_ERROR_NOT_SUPPORTED);
+
+ if (buf[7] == '=') {
+ telephony_response_and_hold_req(device, atoi(&buf[8]) < 0);
+ return 0;
+ }
+
+ if (ag.rh >= 0)
+ headset_send(hs, "\r\n+BTRH: %d\r\n", ag.rh);
+
+ return headset_send(hs, "\r\nOK\r\n", ag.rh);
+}
+
+int telephony_last_dialed_number_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int last_dialed_number(struct audio_device *device, const char *buf)
+{
+ telephony_last_dialed_number_req(device);
+
+ return 0;
+}
+
+int telephony_dial_number_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int dial_number(struct audio_device *device, const char *buf)
+{
+ char number[BUF_SIZE];
+ size_t buf_len;
+
+ buf_len = strlen(buf);
+
+ if (buf[buf_len - 1] != ';') {
+ DBG("Rejecting non-voice call dial request");
+ return -EINVAL;
+ }
+
+ memset(number, 0, sizeof(number));
+ strncpy(number, &buf[3], buf_len - 4);
+
+ telephony_dial_number_req(device, number);
+
+ return 0;
+}
+
+static int headset_set_gain(struct audio_device *device, uint16_t gain, char type)
+{
+ struct headset *hs = device->headset;
+ struct headset_slc *slc = hs->slc;
+ const char *name, *property;
+
+ if (gain > 15) {
+ error("Invalid gain value: %u", gain);
+ return -EINVAL;
+ }
+
+ switch (type) {
+ case HEADSET_GAIN_SPEAKER:
+ if (slc->sp_gain == gain) {
+ DBG("Ignoring no-change in speaker gain");
+ return -EALREADY;
+ }
+ name = "SpeakerGainChanged";
+ property = "SpeakerGain";
+ slc->sp_gain = gain;
+ break;
+ case HEADSET_GAIN_MICROPHONE:
+ if (slc->mic_gain == gain) {
+ DBG("Ignoring no-change in microphone gain");
+ return -EALREADY;
+ }
+ name = "MicrophoneGainChanged";
+ property = "MicrophoneGain";
+ slc->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);
+
+ emit_property_changed(device->conn, device->path,
+ AUDIO_HEADSET_INTERFACE, property,
+ DBUS_TYPE_UINT16, &gain);
+
+ return 0;
+}
+
+static int signal_gain_setting(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+ dbus_uint16_t gain;
+ int err;
+
+ if (strlen(buf) < 8) {
+ error("Too short string for Gain setting");
+ return -EINVAL;
+ }
+
+ gain = (dbus_uint16_t) strtol(&buf[7], NULL, 10);
+
+ err = headset_set_gain(device, gain, buf[5]);
+ if (err < 0 && err != -EALREADY)
+ return err;
+
+ return headset_send(hs, "\r\nOK\r\n");
+}
+
+int telephony_transmit_dtmf_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int dtmf_tone(struct audio_device *device, const char *buf)
+{
+ char tone;
+
+ if (strlen(buf) < 8) {
+ error("Too short string for DTMF tone");
+ return -EINVAL;
+ }
+
+ tone = buf[7];
+ if (tone >= '#' && tone <= 'D')
+ telephony_transmit_dtmf_req(device, tone);
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+int telephony_subscriber_number_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int subscriber_number(struct audio_device *device, const char *buf)
+{
+ telephony_subscriber_number_req(device);
+
+ return 0;
+}
+
+int telephony_list_current_calls_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+static int list_current_calls(struct audio_device *device, const char *buf)
+{
+ telephony_list_current_calls_req(device);
+
+ return 0;
+}
+
+static int extended_errors(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+ struct headset_slc *slc = hs->slc;
+
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ if (buf[8] == '1') {
+ slc->cme_enabled = TRUE;
+ DBG("CME errors enabled for headset %p", hs);
+ } else {
+ slc->cme_enabled = FALSE;
+ DBG("CME errors disabled for headset %p", hs);
+ }
+
+ return headset_send(hs, "\r\nOK\r\n");
+}
+
+static int call_waiting_notify(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+ struct headset_slc *slc = hs->slc;
+
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ if (buf[8] == '1') {
+ slc->cwa_enabled = TRUE;
+ DBG("Call waiting notification enabled for headset %p", hs);
+ } else {
+ slc->cwa_enabled = FALSE;
+ DBG("Call waiting notification disabled for headset %p", hs);
+ }
+
+ return headset_send(hs, "\r\nOK\r\n");
+}
+
+int telephony_operator_selection_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+int telephony_call_hold_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+int telephony_nr_and_ec_rsp(void *telephony_device, cme_error_t err)
+{
+ struct audio_device *device = telephony_device;
+ struct headset *hs = device->headset;
+ struct headset_slc *slc = hs->slc;
+
+ if (err == CME_ERROR_NONE) {
+ GSList *l;
+
+ for (l = hs->nrec_cbs; l; l = l->next) {
+ struct headset_nrec_callback *nrec_cb = l->data;
+
+ nrec_cb->cb(device, slc->nrec_req, nrec_cb->user_data);
+ }
+
+ slc->nrec = hs->slc->nrec_req;
+ }
+
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+int telephony_voice_dial_rsp(void *telephony_device, cme_error_t err)
+{
+ return telephony_generic_rsp(telephony_device, err);
+}
+
+int telephony_operator_selection_ind(int mode, const char *oper)
+{
+ if (!active_devices)
+ return -ENODEV;
+
+ send_foreach_headset(active_devices, hfp_cmp,
+ "\r\n+COPS: %d,0,\"%s\"\r\n",
+ mode, oper);
+ return 0;
+}
+
+static int operator_selection(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+
+ if (strlen(buf) < 8)
+ return -EINVAL;
+
+ switch (buf[7]) {
+ case '?':
+ telephony_operator_selection_req(device);
+ break;
+ case '=':
+ return headset_send(hs, "\r\nOK\r\n");
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int nr_and_ec(struct audio_device *device, const char *buf)
+{
+ struct headset *hs = device->headset;
+ struct headset_slc *slc = hs->slc;
+
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ if (buf[8] == '0')
+ slc->nrec_req = FALSE;
+ else
+ slc->nrec_req = TRUE;
+
+ telephony_nr_and_ec_req(device, slc->nrec_req);
+
+ return 0;
+}
+
+static int voice_dial(struct audio_device *device, const char *buf)
+{
+ gboolean enable;
+
+ if (strlen(buf) < 9)
+ return -EINVAL;
+
+ if (buf[8] == '0')
+ enable = FALSE;
+ else
+ enable = TRUE;
+
+ telephony_voice_dial_req(device, enable);
+
+ return 0;
+}
+
+static struct event event_callbacks[] = {
+ { "ATA", answer_call },
+ { "ATD", dial_number },
+ { "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", key_press },
+ { "AT+CLIP", cli_notification },
+ { "AT+BTRH", response_and_hold },
+ { "AT+BLDN", last_dialed_number },
+ { "AT+VTS", dtmf_tone },
+ { "AT+CNUM", subscriber_number },
+ { "AT+CLCC", list_current_calls },
+ { "AT+CMEE", extended_errors },
+ { "AT+CCWA", call_waiting_notify },
+ { "AT+COPS", operator_selection },
+ { "AT+NREC", nr_and_ec },
+ { "AT+BVRA", voice_dial },
+ { 0 }
+};
+
+static int handle_event(struct audio_device *device, const char *buf)
+{
+ struct event *ev;
+
+ DBG("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) {
+ int sock = g_io_channel_unix_get_fd(hs->sco);
+ shutdown(sock, SHUT_RDWR);
+ g_io_channel_shutdown(hs->sco, TRUE, NULL);
+ g_io_channel_unref(hs->sco);
+ hs->sco = NULL;
+ }
+
+ if (hs->sco_id) {
+ g_source_remove(hs->sco_id);
+ hs->sco_id = 0;
+ }
+}
+
+static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond,
+ struct audio_device *device)
+{
+ struct headset *hs;
+ struct headset_slc *slc;
+ unsigned char buf[BUF_SIZE];
+ ssize_t bytes_read;
+ size_t free_space;
+ int fd;
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ hs = device->headset;
+ slc = hs->slc;
+
+ if (cond & (G_IO_ERR | G_IO_HUP)) {
+ DBG("ERR or HUP on RFCOMM socket");
+ goto failed;
+ }
+
+ fd = g_io_channel_unix_get_fd(chan);
+
+ bytes_read = read(fd, buf, sizeof(buf) - 1);
+ if (bytes_read < 0)
+ return TRUE;
+
+ free_space = sizeof(slc->buf) - slc->data_start -
+ slc->data_length - 1;
+
+ if (free_space < (size_t) 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(&slc->buf[slc->data_start], buf, bytes_read);
+ slc->data_length += bytes_read;
+
+ /* Make sure the data is null terminated so we can use string
+ * functions */
+ slc->buf[slc->data_start + slc->data_length] = '\0';
+
+ while (slc->data_length > 0) {
+ char *cr;
+ int err;
+ off_t cmd_len;
+
+ cr = strchr(&slc->buf[slc->data_start], '\r');
+ if (!cr)
+ break;
+
+ cmd_len = 1 + (off_t) cr - (off_t) &slc->buf[slc->data_start];
+ *cr = '\0';
+
+ if (cmd_len > 1)
+ err = handle_event(device, &slc->buf[slc->data_start]);
+ else
+ /* Silently skip empty commands */
+ err = 0;
+
+ if (err == -EINVAL) {
+ error("Badly formated or unrecognized command: %s",
+ &slc->buf[slc->data_start]);
+ err = headset_send(hs, "\r\nERROR\r\n");
+ } else if (err < 0)
+ error("Error handling command %s: %s (%d)",
+ &slc->buf[slc->data_start],
+ strerror(-err), -err);
+
+ slc->data_start += cmd_len;
+ slc->data_length -= cmd_len;
+
+ if (!slc->data_length)
+ slc->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)
+{
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ error("Audio connection got disconnected");
+
+ pending_connect_finalize(device);
+ headset_set_state(device, HEADSET_STATE_CONNECTED);
+
+ return FALSE;
+}
+
+void headset_connect_cb(GIOChannel *chan, GError *err, 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) {
+ error("%s", err->message);
+ goto failed;
+ }
+
+ /* For HFP telephony isn't ready just disconnect */
+ if (hs->hfp_active && !ag.telephony_ready) {
+ error("Unable to accept HFP connection since the telephony "
+ "subsystem isn't initialized");
+ goto failed;
+ }
+
+ hs->rfcomm = hs->tmp_rfcomm;
+ hs->tmp_rfcomm = NULL;
+
+ ba2str(&dev->dst, hs_address);
+
+ if (p)
+ p->io = NULL;
+ else
+ hs->auto_dc = FALSE;
+
+ g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP| G_IO_NVAL,
+ (GIOFunc) rfcomm_io_cb, dev);
+
+ DBG("%s: Connected to %s", dev->path, hs_address);
+
+ hs->slc = g_new0(struct headset_slc, 1);
+ hs->slc->sp_gain = 15;
+ hs->slc->mic_gain = 15;
+ hs->slc->nrec = TRUE;
+
+ /* In HFP mode wait for Service Level Connection */
+ if (hs->hfp_active)
+ return;
+
+ headset_set_state(dev, HEADSET_STATE_CONNECTED);
+
+ if (p && p->target_state == HEADSET_STATE_PLAYING) {
+ p->err = sco_connect(dev, NULL, NULL, NULL);
+ if (p->err < 0)
+ goto failed;
+ return;
+ }
+
+ if (p && p->msg) {
+ DBusMessage *reply = dbus_message_new_method_return(p->msg);
+ g_dbus_send_message(dev->conn, reply);
+ }
+
+ pending_connect_finalize(dev);
+
+ return;
+
+failed:
+ if (p && p->msg)
+ error_connect_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 int headset_set_channel(struct headset *headset,
+ const 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 -1;
+ }
+
+ 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) {
+ error("Unable to get RFCOMM channel from Headset record");
+ return -1;
+ }
+
+ headset->rfcomm_ch = ch;
+
+ if (svc == HANDSFREE_SVCLASS_ID) {
+ headset->hfp_handle = record->handle;
+ DBG("Discovered Handsfree service on channel %d", ch);
+ } else {
+ headset->hsp_handle = record->handle;
+ DBG("Discovered Headset service on channel %d", ch);
+ }
+
+ return 0;
+}
+
+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;
+ sdp_record_t *record = NULL;
+ sdp_list_t *r;
+ uuid_t uuid;
+
+ assert(hs->pending != NULL);
+
+ if (err < 0) {
+ error("Unable to get service record: %s (%d)",
+ strerror(-err), -err);
+ p->err = -err;
+ if (p->msg)
+ error_connect_failed(dev->conn, p->msg, p->err);
+ goto failed;
+ }
+
+ if (!recs || !recs->data) {
+ error("No records found");
+ goto failed_not_supported;
+ }
+
+ sdp_uuid16_create(&uuid, p->svclass);
+
+ for (r = recs; r != NULL; r = r->next) {
+ sdp_list_t *classes;
+ uuid_t class;
+
+ record = r->data;
+
+ if (sdp_get_service_classes(record, &classes) < 0) {
+ error("Unable to get service classes from record");
+ continue;
+ }
+
+ memcpy(&class, classes->data, sizeof(uuid));
+
+ sdp_list_free(classes, free);
+
+
+ if (sdp_uuid_cmp(&class, &uuid) == 0)
+ break;
+ }
+
+ if (r == NULL) {
+ error("No record found with UUID 0x%04x", p->svclass);
+ goto failed_not_supported;
+ }
+
+ if (headset_set_channel(hs, record, p->svclass) < 0) {
+ error("Unable to extract RFCOMM channel from service record");
+ goto failed_not_supported;
+ }
+
+ /* Set svclass to 0 so we can easily check that SDP is no-longer
+ * going on (to know if bt_cancel_discovery needs to be called) */
+ p->svclass = 0;
+
+ err = rfcomm_connect(dev, NULL, NULL, NULL);
+ if (err < 0) {
+ error("Unable to connect: %s (%d)", strerror(-err), -err);
+ p->err = -err;
+ error_connect_failed(dev->conn, p->msg, p->err);
+ goto failed;
+ }
+
+ return;
+
+failed_not_supported:
+ if (p->svclass == HANDSFREE_SVCLASS_ID &&
+ get_records(dev, NULL, NULL, NULL) == 0)
+ return;
+ if (p->msg) {
+ DBusMessage *reply = btd_error_not_supported(p->msg);
+ g_dbus_send_message(dev->conn, reply);
+ }
+failed:
+ p->svclass = 0;
+ 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;
+ uint16_t svclass;
+ uuid_t uuid;
+ int err;
+
+ if (hs->pending && hs->pending->svclass == HANDSFREE_SVCLASS_ID)
+ svclass = HEADSET_SVCLASS_ID;
+ else
+ svclass = hs->search_hfp ? HANDSFREE_SVCLASS_ID :
+ HEADSET_SVCLASS_ID;
+
+ sdp_uuid16_create(&uuid, svclass);
+
+ err = bt_search_service(&device->src, &device->dst, &uuid,
+ get_record_cb, device, NULL);
+ if (err < 0)
+ return err;
+
+ if (hs->pending) {
+ hs->pending->svclass = svclass;
+ return 0;
+ }
+
+ headset_set_state(device, HEADSET_STATE_CONNECTING);
+
+ pending_connect_init(hs, HEADSET_STATE_CONNECTED);
+
+ hs->pending->svclass = svclass;
+
+ if (cb) {
+ unsigned int id;
+ id = connect_cb_new(hs, HEADSET_STATE_CONNECTED,
+ cb, user_data);
+ if (cb_id)
+ *cb_id = id;
+ }
+
+ return 0;
+}
+
+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];
+ GError *err = NULL;
+
+ if (!manager_allow_headset_connection(dev))
+ return -ECONNREFUSED;
+
+ if (hs->rfcomm_ch < 0)
+ return get_records(dev, cb, user_data, cb_id);
+
+ ba2str(&dev->dst, address);
+
+ DBG("%s: Connecting to %s channel %d", dev->path, address,
+ hs->rfcomm_ch);
+
+ hs->tmp_rfcomm = bt_io_connect(BT_IO_RFCOMM, headset_connect_cb, dev,
+ NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+ BT_IO_OPT_DEST_BDADDR, &dev->dst,
+ BT_IO_OPT_CHANNEL, hs->rfcomm_ch,
+ BT_IO_OPT_INVALID);
+
+ hs->rfcomm_ch = -1;
+
+ if (!hs->tmp_rfcomm) {
+ error("%s", err->message);
+ g_error_free(err);
+ return -EIO;
+ }
+
+ hs->hfp_active = hs->hfp_handle != 0 ? TRUE : FALSE;
+
+ headset_set_state(dev, HEADSET_STATE_CONNECTING);
+
+ 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 btd_error_not_connected(msg);
+
+ 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;
+ char hs_address[18];
+
+ if (hs->state == HEADSET_STATE_DISCONNECTED)
+ return btd_error_not_connected(msg);
+
+ headset_shutdown(device);
+ ba2str(&device->dst, hs_address);
+ info("Disconnected from %s, %s", hs_address, device->path);
+
+ return dbus_message_new_method_return(msg);
+
+}
+
+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_CONNECTING)
+ return btd_error_in_progress(msg);
+ else if (hs->state > HEADSET_STATE_CONNECTING)
+ return btd_error_already_connected(msg);
+
+ if (hs->hfp_handle && !ag.telephony_ready)
+ return btd_error_not_ready(msg);
+
+ device->auto_connect = FALSE;
+
+ err = rfcomm_connect(device, NULL, NULL, NULL);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ hs->auto_dc = FALSE;
+
+ hs->pending->msg = dbus_message_ref(msg);
+
+ return NULL;
+}
+
+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 btd_error_not_connected(msg);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (ag.ring_timer) {
+ DBG("IndicateCall received when already indicating");
+ goto done;
+ }
+
+ err = headset_send(hs, "\r\nRING\r\n");
+ if (err < 0) {
+ dbus_message_unref(reply);
+ return btd_error_failed(msg, strerror(-err));
+ }
+
+ ring_timer_cb(NULL);
+ ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb,
+ NULL);
+
+done:
+ return reply;
+}
+
+static DBusMessage *hs_cancel_call(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 btd_error_not_connected(msg);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (ag.ring_timer) {
+ g_source_remove(ag.ring_timer);
+ ag.ring_timer = 0;
+ } else
+ DBG("Got CancelCall method call but no call is active");
+
+ 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 btd_error_not_available(msg);
+ }
+
+ switch (hs->state) {
+ case HEADSET_STATE_DISCONNECTED:
+ case HEADSET_STATE_CONNECTING:
+ return btd_error_not_connected(msg);
+ case HEADSET_STATE_PLAY_IN_PROGRESS:
+ if (hs->pending && hs->pending->msg == NULL) {
+ hs->pending->msg = dbus_message_ref(msg);
+ return NULL;
+ }
+ return btd_error_busy(msg);
+ case HEADSET_STATE_PLAYING:
+ return btd_error_already_connected(msg);
+ case HEADSET_STATE_CONNECTED:
+ default:
+ break;
+ }
+
+ err = sco_connect(device, NULL, NULL, NULL);
+ if (err < 0)
+ return btd_error_failed(msg, 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;
+ struct headset_slc *slc = hs->slc;
+ DBusMessage *reply;
+ dbus_uint16_t gain;
+
+ if (hs->state < HEADSET_STATE_CONNECTED)
+ return btd_error_not_available(msg);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ gain = (dbus_uint16_t) slc->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;
+ struct headset_slc *slc = hs->slc;
+ DBusMessage *reply;
+ dbus_uint16_t gain;
+
+ if (hs->state < HEADSET_STATE_CONNECTED || slc == NULL)
+ return btd_error_not_available(msg);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ gain = (dbus_uint16_t) slc->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, uint16_t gain,
+ char type)
+{
+ struct audio_device *device = data;
+ struct headset *hs = device->headset;
+ DBusMessage *reply;
+ int err;
+
+ if (hs->state < HEADSET_STATE_CONNECTED)
+ return btd_error_not_connected(msg);
+
+ err = headset_set_gain(device, gain, type);
+ if (err < 0)
+ return btd_error_invalid_args(msg);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ if (hs->state == HEADSET_STATE_PLAYING) {
+ err = headset_send(hs, "\r\n+VG%c=%u\r\n", type, gain);
+ if (err < 0) {
+ dbus_message_unref(reply);
+ return btd_error_failed(msg, strerror(-err));
+ }
+ }
+
+ return reply;
+}
+
+static DBusMessage *hs_set_speaker_gain(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ uint16_t gain;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ return hs_set_gain(conn, msg, data, gain, HEADSET_GAIN_SPEAKER);
+}
+
+static DBusMessage *hs_set_mic_gain(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ uint16_t gain;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &gain,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ return hs_set_gain(conn, msg, data, gain, HEADSET_GAIN_MICROPHONE);
+}
+
+static DBusMessage *hs_get_properties(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ gboolean value;
+ const char *state;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+
+ /* Playing */
+ value = (device->headset->state == HEADSET_STATE_PLAYING);
+ dict_append_entry(&dict, "Playing", DBUS_TYPE_BOOLEAN, &value);
+
+ /* State */
+ state = state2str(device->headset->state);
+ if (state)
+ dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state);
+
+ /* Connected */
+ value = (device->headset->state >= HEADSET_STATE_CONNECTED);
+ dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+ if (!value)
+ goto done;
+
+ /* SpeakerGain */
+ dict_append_entry(&dict, "SpeakerGain",
+ DBUS_TYPE_UINT16,
+ &device->headset->slc->sp_gain);
+
+ /* MicrophoneGain */
+ dict_append_entry(&dict, "MicrophoneGain",
+ DBUS_TYPE_UINT16,
+ &device->headset->slc->mic_gain);
+
+done:
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *hs_set_property(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *property;
+ DBusMessageIter iter;
+ DBusMessageIter sub;
+ uint16_t gain;
+
+ if (!dbus_message_iter_init(msg, &iter))
+ return btd_error_invalid_args(msg);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&iter, &property);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+ return btd_error_invalid_args(msg);
+ dbus_message_iter_recurse(&iter, &sub);
+
+ if (g_str_equal("SpeakerGain", property)) {
+ if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&sub, &gain);
+ return hs_set_gain(conn, msg, data, gain,
+ HEADSET_GAIN_SPEAKER);
+ } else if (g_str_equal("MicrophoneGain", property)) {
+ if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&sub, &gain);
+ return hs_set_gain(conn, msg, data, gain,
+ HEADSET_GAIN_MICROPHONE);
+ }
+
+ return btd_error_invalid_args(msg);
+}
+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_call },
+ { "Play", "", "", hs_play,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "Stop", "", "", hs_stop },
+ { "IsPlaying", "", "b", hs_is_playing,
+ G_DBUS_METHOD_FLAG_DEPRECATED },
+ { "GetSpeakerGain", "", "q", hs_get_speaker_gain,
+ G_DBUS_METHOD_FLAG_DEPRECATED },
+ { "GetMicrophoneGain", "", "q", hs_get_mic_gain,
+ G_DBUS_METHOD_FLAG_DEPRECATED },
+ { "SetSpeakerGain", "q", "", hs_set_speaker_gain,
+ G_DBUS_METHOD_FLAG_DEPRECATED },
+ { "SetMicrophoneGain", "q", "", hs_set_mic_gain,
+ G_DBUS_METHOD_FLAG_DEPRECATED },
+ { "GetProperties", "", "a{sv}",hs_get_properties },
+ { "SetProperty", "sv", "", hs_set_property },
+ { NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable headset_signals[] = {
+ { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED },
+ { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED },
+ { "AnswerRequested", "" },
+ { "Stopped", "", G_DBUS_SIGNAL_FLAG_DEPRECATED },
+ { "Playing", "", G_DBUS_SIGNAL_FLAG_DEPRECATED },
+ { "SpeakerGainChanged", "q", G_DBUS_SIGNAL_FLAG_DEPRECATED },
+ { "MicrophoneGainChanged", "q", G_DBUS_SIGNAL_FLAG_DEPRECATED },
+ { "CallTerminated", "" },
+ { "PropertyChanged", "sv" },
+ { NULL, NULL }
+};
+
+void headset_update(struct audio_device *dev, uint16_t svc,
+ const char *uuidstr)
+{
+ struct headset *headset = dev->headset;
+ const sdp_record_t *record;
+
+ record = btd_device_get_record(dev->btd_dev, uuidstr);
+ if (!record)
+ return;
+
+ 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:
+ DBG("Invalid record passed to headset_update");
+ return;
+ }
+}
+
+static int headset_close_rfcomm(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+ GIOChannel *rfcomm = hs->tmp_rfcomm ? hs->tmp_rfcomm : hs->rfcomm;
+
+ if (rfcomm) {
+ g_io_channel_shutdown(rfcomm, TRUE, NULL);
+ g_io_channel_unref(rfcomm);
+ hs->tmp_rfcomm = NULL;
+ hs->rfcomm = NULL;
+ }
+
+ g_free(hs->slc);
+ hs->slc = NULL;
+
+ return 0;
+}
+
+static 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;
+ }
+
+ close_sco(dev);
+
+ headset_close_rfcomm(dev);
+
+ g_slist_foreach(hs->nrec_cbs, (GFunc) g_free, NULL);
+ g_slist_free(hs->nrec_cbs);
+
+ g_free(hs);
+ dev->headset = NULL;
+}
+
+static void path_unregister(void *data)
+{
+ struct audio_device *dev = data;
+ struct headset *hs = dev->headset;
+
+ if (hs->state > HEADSET_STATE_DISCONNECTED) {
+ DBG("Headset unregistered while device was connected!");
+ headset_shutdown(dev);
+ }
+
+ DBG("Unregistered interface %s on path %s",
+ AUDIO_HEADSET_INTERFACE, dev->path);
+
+ headset_free(dev);
+}
+
+void headset_unregister(struct audio_device *dev)
+{
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE);
+}
+
+struct headset *headset_init(struct audio_device *dev, uint16_t svc,
+ const char *uuidstr)
+{
+ struct headset *hs;
+ const sdp_record_t *record;
+
+ hs = g_new0(struct headset, 1);
+ hs->rfcomm_ch = -1;
+ hs->search_hfp = server_is_enabled(&dev->src, HANDSFREE_SVCLASS_ID);
+
+ record = btd_device_get_record(dev->btd_dev, uuidstr);
+ 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:
+ DBG("Invalid record passed to headset_init");
+ g_free(hs);
+ return NULL;
+ }
+
+register_iface:
+ if (!g_dbus_register_interface(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE,
+ headset_methods, headset_signals, NULL,
+ dev, path_unregister)) {
+ g_free(hs);
+ return NULL;
+ }
+
+ DBG("Registered interface %s on path %s",
+ AUDIO_HEADSET_INTERFACE, dev->path);
+
+ return hs;
+}
+
+uint32_t headset_config_init(GKeyFile *config)
+{
+ GError *err = NULL;
+ 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) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } 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);
+ }
+
+ /* Init fast connectable option */
+ str = g_key_file_get_string(config, "Headset", "FastConnectable",
+ &err);
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else {
+ fast_connectable = strcmp(str, "true") == 0;
+ if (fast_connectable)
+ manager_set_fast_connectable(FALSE);
+ g_free(str);
+ }
+
+ return ag.features;
+}
+
+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;
+
+ if (hs->auto_dc) {
+ if (hs->rfcomm)
+ hs->dc_timer = g_timeout_add_seconds(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->state == HEADSET_STATE_PLAYING) {
+ 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_CONNECTING ||
+ hs->state == HEADSET_STATE_PLAY_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;
+}
+
+unsigned int headset_config_stream(struct audio_device *dev,
+ gboolean auto_dc,
+ headset_stream_cb_t cb,
+ void *user_data)
+{
+ struct headset *hs = dev->headset;
+ unsigned int id = 0;
+
+ if (hs->dc_timer) {
+ g_source_remove(hs->dc_timer);
+ hs->dc_timer = 0;
+ }
+
+ if (hs->state == HEADSET_STATE_CONNECTING)
+ return connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb,
+ user_data);
+
+ if (hs->rfcomm)
+ goto done;
+
+ if (rfcomm_connect(dev, cb, user_data, &id) < 0)
+ return 0;
+
+ hs->auto_dc = auto_dc;
+ hs->pending->target_state = HEADSET_STATE_CONNECTED;
+
+ return id;
+
+done:
+ id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb, user_data);
+ g_idle_add((GSourceFunc) dummy_connect_complete, dev);
+ return id;
+}
+
+unsigned int headset_suspend_stream(struct audio_device *dev,
+ headset_stream_cb_t cb,
+ void *user_data)
+{
+ struct headset *hs = dev->headset;
+ unsigned int id;
+ int sock;
+
+ if (hs->state == HEADSET_STATE_DISCONNECTED ||
+ hs->state == HEADSET_STATE_CONNECTING)
+ return 0;
+
+ if (hs->dc_timer) {
+ g_source_remove(hs->dc_timer);
+ hs->dc_timer = 0;
+ }
+
+ sock = g_io_channel_unix_get_fd(hs->sco);
+
+ /* shutdown but leave the socket open and wait for hup */
+ shutdown(sock, SHUT_RDWR);
+
+ id = connect_cb_new(hs, HEADSET_STATE_CONNECTED, cb, user_data);
+
+ 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;
+}
+
+GIOChannel *headset_get_rfcomm(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+
+ return hs->tmp_rfcomm;
+}
+
+int headset_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
+{
+ struct headset *hs = dev->headset;
+
+ if (hs->tmp_rfcomm)
+ return -EALREADY;
+
+ hs->tmp_rfcomm = g_io_channel_ref(io);
+
+ return 0;
+}
+
+int headset_connect_sco(struct audio_device *dev, GIOChannel *io)
+{
+ struct headset *hs = dev->headset;
+ struct headset_slc *slc = hs->slc;
+
+ if (hs->sco)
+ return -EISCONN;
+
+ hs->sco = g_io_channel_ref(io);
+
+ if (slc->pending_ring) {
+ ring_timer_cb(NULL);
+ ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL,
+ ring_timer_cb,
+ NULL);
+ slc->pending_ring = FALSE;
+ }
+
+ return 0;
+}
+
+void headset_set_state(struct audio_device *dev, headset_state_t state)
+{
+ struct headset *hs = dev->headset;
+ struct headset_slc *slc = hs->slc;
+ gboolean value;
+ const char *state_str;
+ headset_state_t old_state = hs->state;
+ GSList *l;
+
+ if (old_state == state)
+ return;
+
+ state_str = state2str(state);
+
+ switch (state) {
+ case HEADSET_STATE_DISCONNECTED:
+ value = FALSE;
+ close_sco(dev);
+ headset_close_rfcomm(dev);
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE, "State",
+ DBUS_TYPE_STRING, &state_str);
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE,
+ "Disconnected",
+ DBUS_TYPE_INVALID);
+ if (hs->state > HEADSET_STATE_CONNECTING) {
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE, "Connected",
+ DBUS_TYPE_BOOLEAN, &value);
+ telephony_device_disconnected(dev);
+ }
+ active_devices = g_slist_remove(active_devices, dev);
+ break;
+ case HEADSET_STATE_CONNECTING:
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE, "State",
+ DBUS_TYPE_STRING, &state_str);
+ break;
+ case HEADSET_STATE_CONNECTED:
+ close_sco(dev);
+ if (hs->state != HEADSET_STATE_PLAY_IN_PROGRESS)
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE, "State",
+ DBUS_TYPE_STRING, &state_str);
+ if (hs->state < state) {
+ if (ag.features & AG_FEATURE_INBAND_RINGTONE)
+ slc->inband_ring = TRUE;
+ else
+ slc->inband_ring = FALSE;
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE,
+ "Connected",
+ DBUS_TYPE_INVALID);
+ value = TRUE;
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE,
+ "Connected",
+ DBUS_TYPE_BOOLEAN, &value);
+ active_devices = g_slist_append(active_devices, dev);
+ telephony_device_connected(dev);
+ } else if (hs->state == HEADSET_STATE_PLAYING) {
+ value = FALSE;
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE,
+ "Stopped",
+ DBUS_TYPE_INVALID);
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE,
+ "Playing",
+ DBUS_TYPE_BOOLEAN, &value);
+ }
+ break;
+ case HEADSET_STATE_PLAY_IN_PROGRESS:
+ break;
+ case HEADSET_STATE_PLAYING:
+ value = TRUE;
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE, "State",
+ DBUS_TYPE_STRING, &state_str);
+ 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);
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_HEADSET_INTERFACE, "Playing",
+ DBUS_TYPE_BOOLEAN, &value);
+
+ if (slc->sp_gain >= 0)
+ headset_send(hs, "\r\n+VGS=%u\r\n", slc->sp_gain);
+ if (slc->mic_gain >= 0)
+ headset_send(hs, "\r\n+VGM=%u\r\n", slc->mic_gain);
+ break;
+ }
+
+ hs->state = state;
+
+ DBG("State changed %s: %s -> %s", dev->path, str_state[old_state],
+ str_state[state]);
+
+ for (l = headset_callbacks; l != NULL; l = l->next) {
+ struct headset_state_callback *cb = l->data;
+ cb->cb(dev, old_state, state, cb->user_data);
+ }
+}
+
+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;
+}
+
+headset_lock_t headset_get_lock(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+
+ return hs->lock;
+}
+
+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_seconds(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);
+}
+
+gboolean headset_get_nrec(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+
+ if (!hs->slc)
+ return TRUE;
+
+ return hs->slc->nrec;
+}
+
+unsigned int headset_add_nrec_cb(struct audio_device *dev,
+ headset_nrec_cb cb, void *user_data)
+{
+ struct headset *hs = dev->headset;
+ struct headset_nrec_callback *nrec_cb;
+ static unsigned int id = 0;
+
+ nrec_cb = g_new(struct headset_nrec_callback, 1);
+ nrec_cb->cb = cb;
+ nrec_cb->user_data = user_data;
+ nrec_cb->id = ++id;
+
+ hs->nrec_cbs = g_slist_prepend(hs->nrec_cbs, nrec_cb);
+
+ return nrec_cb->id;
+}
+
+gboolean headset_remove_nrec_cb(struct audio_device *dev, unsigned int id)
+{
+ struct headset *hs = dev->headset;
+ GSList *l;
+
+ for (l = hs->nrec_cbs; l != NULL; l = l->next) {
+ struct headset_nrec_callback *cb = l->data;
+ if (cb && cb->id == id) {
+ hs->nrec_cbs = g_slist_remove(hs->nrec_cbs, cb);
+ g_free(cb);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean headset_get_inband(struct audio_device *dev)
+{
+ struct headset *hs = dev->headset;
+
+ if (!hs->slc)
+ return TRUE;
+
+ return hs->slc->inband_ring;
+}
+
+gboolean headset_get_sco_hci(struct audio_device *dev)
+{
+ return sco_hci;
+}
+
+void headset_shutdown(struct audio_device *dev)
+{
+ struct pending_connect *p = dev->headset->pending;
+
+ if (p && p->msg)
+ error_connect_failed(dev->conn, p->msg, ECANCELED);
+
+ pending_connect_finalize(dev);
+ headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+}
+
+int telephony_event_ind(int index)
+{
+ if (!active_devices)
+ return -ENODEV;
+
+ if (!ag.er_ind) {
+ DBG("telephony_report_event called but events are disabled");
+ return -EINVAL;
+ }
+
+ send_foreach_headset(active_devices, hfp_cmp,
+ "\r\n+CIEV: %d,%d\r\n", index + 1,
+ ag.indicators[index].val);
+
+ return 0;
+}
+
+int telephony_response_and_hold_ind(int rh)
+{
+ if (!active_devices)
+ return -ENODEV;
+
+ ag.rh = rh;
+
+ /* If we aren't in any response and hold state don't send anything */
+ if (ag.rh < 0)
+ return 0;
+
+ send_foreach_headset(active_devices, hfp_cmp, "\r\n+BTRH: %d\r\n",
+ ag.rh);
+
+ return 0;
+}
+
+int telephony_incoming_call_ind(const char *number, int type)
+{
+ struct audio_device *dev;
+ struct headset *hs;
+ struct headset_slc *slc;
+
+ if (fast_connectable)
+ manager_set_fast_connectable(TRUE);
+
+ if (!active_devices)
+ return -ENODEV;
+
+ /* Get the latest connected device */
+ dev = active_devices->data;
+ hs = dev->headset;
+ slc = hs->slc;
+
+ if (ag.ring_timer) {
+ DBG("telephony_incoming_call_ind: already calling");
+ return -EBUSY;
+ }
+
+ /* With HSP 1.2 the RING messages should *not* be sent if inband
+ * ringtone is being used */
+ if (!hs->hfp_active && slc->inband_ring)
+ return 0;
+
+ g_free(ag.number);
+ ag.number = g_strdup(number);
+ ag.number_type = type;
+
+ if (slc->inband_ring && hs->hfp_active &&
+ hs->state != HEADSET_STATE_PLAYING) {
+ slc->pending_ring = TRUE;
+ return 0;
+ }
+
+ ring_timer_cb(NULL);
+ ag.ring_timer = g_timeout_add_seconds(RING_INTERVAL, ring_timer_cb,
+ NULL);
+
+ return 0;
+}
+
+int telephony_calling_stopped_ind(void)
+{
+ struct audio_device *dev;
+
+ if (fast_connectable)
+ manager_set_fast_connectable(FALSE);
+
+ if (ag.ring_timer) {
+ g_source_remove(ag.ring_timer);
+ ag.ring_timer = 0;
+ }
+
+ if (!active_devices)
+ return 0;
+
+ /* In case SCO isn't fully up yet */
+ dev = active_devices->data;
+
+ if (!dev->headset->slc->pending_ring && !ag.ring_timer)
+ return -EINVAL;
+
+ dev->headset->slc->pending_ring = FALSE;
+
+ return 0;
+}
+
+int telephony_ready_ind(uint32_t features,
+ const struct indicator *indicators, int rh,
+ const char *chld)
+{
+ ag.telephony_ready = TRUE;
+ ag.features = features;
+ ag.indicators = indicators;
+ ag.rh = rh;
+ ag.chld = chld;
+
+ DBG("Telephony plugin initialized");
+
+ print_ag_features(ag.features);
+
+ return 0;
+}
+
+int telephony_deinit(void)
+{
+ g_free(ag.number);
+
+ memset(&ag, 0, sizeof(ag));
+
+ ag.er_mode = 3;
+ ag.rh = BTRH_NOT_SUPPORTED;
+
+ DBG("Telephony deinitialized");
+
+ return 0;
+}
+
+int telephony_list_current_call_ind(int idx, int dir, int status, int mode,
+ int mprty, const char *number,
+ int type)
+{
+ if (!active_devices)
+ return -ENODEV;
+
+ if (number && strlen(number) > 0)
+ send_foreach_headset(active_devices, hfp_cmp,
+ "\r\n+CLCC: %d,%d,%d,%d,%d,\"%s\",%d\r\n",
+ idx, dir, status, mode, mprty, number, type);
+ else
+ send_foreach_headset(active_devices, hfp_cmp,
+ "\r\n+CLCC: %d,%d,%d,%d,%d\r\n",
+ idx, dir, status, mode, mprty);
+
+ return 0;
+}
+
+int telephony_subscriber_number_ind(const char *number, int type, int service)
+{
+ if (!active_devices)
+ return -ENODEV;
+
+ send_foreach_headset(active_devices, hfp_cmp,
+ "\r\n+CNUM: ,%s,%d,,%d\r\n",
+ number, type, service);
+
+ return 0;
+}
+
+static int cwa_cmp(struct headset *hs)
+{
+ if (!hs->hfp_active)
+ return -1;
+
+ if (hs->slc->cwa_enabled)
+ return 0;
+ else
+ return -1;
+}
+
+int telephony_call_waiting_ind(const char *number, int type)
+{
+ if (!active_devices)
+ return -ENODEV;
+
+ send_foreach_headset(active_devices, cwa_cmp,
+ "\r\n+CCWA: \"%s\",%d\r\n",
+ number, type);
+
+ return 0;
+}
+
+unsigned int headset_add_state_cb(headset_state_cb cb, void *user_data)
+{
+ struct headset_state_callback *state_cb;
+ static unsigned int id = 0;
+
+ state_cb = g_new(struct headset_state_callback, 1);
+ state_cb->cb = cb;
+ state_cb->user_data = user_data;
+ state_cb->id = ++id;
+
+ headset_callbacks = g_slist_append(headset_callbacks, state_cb);
+
+ return state_cb->id;
+}
+
+gboolean headset_remove_state_cb(unsigned int id)
+{
+ GSList *l;
+
+ for (l = headset_callbacks; l != NULL; l = l->next) {
+ struct headset_state_callback *cb = l->data;
+ if (cb && cb->id == id) {
+ headset_callbacks = g_slist_remove(headset_callbacks, cb);
+ g_free(cb);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
diff --git a/audio/headset.h b/audio/headset.h
new file mode 100644
index 0000000..7ce88c8
--- /dev/null
+++ b/audio/headset.h
@@ -0,0 +1,109 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define AUDIO_HEADSET_INTERFACE "org.bluez.Headset"
+
+#define DEFAULT_HS_AG_CHANNEL 12
+#define DEFAULT_HF_AG_CHANNEL 13
+
+typedef enum {
+ HEADSET_STATE_DISCONNECTED,
+ HEADSET_STATE_CONNECTING,
+ 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_state_cb) (struct audio_device *dev,
+ headset_state_t old_state,
+ headset_state_t new_state,
+ void *user_data);
+typedef void (*headset_nrec_cb) (struct audio_device *dev,
+ gboolean nrec,
+ void *user_data);
+
+unsigned int headset_add_state_cb(headset_state_cb cb, void *user_data);
+gboolean headset_remove_state_cb(unsigned int id);
+
+typedef void (*headset_stream_cb_t) (struct audio_device *dev, void *user_data);
+
+void headset_connect_cb(GIOChannel *chan, GError *err, gpointer user_data);
+
+GIOChannel *headset_get_rfcomm(struct audio_device *dev);
+
+struct headset *headset_init(struct audio_device *dev, uint16_t svc,
+ const char *uuidstr);
+
+void headset_unregister(struct audio_device *dev);
+
+uint32_t headset_config_init(GKeyFile *config);
+
+void headset_update(struct audio_device *dev, uint16_t svc,
+ const char *uuidstr);
+
+unsigned int headset_config_stream(struct audio_device *dev,
+ gboolean auto_dc,
+ headset_stream_cb_t cb,
+ void *user_data);
+unsigned int headset_request_stream(struct audio_device *dev,
+ headset_stream_cb_t cb,
+ void *user_data);
+unsigned int headset_suspend_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_connect_sco(struct audio_device *dev, GIOChannel *io);
+
+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_get_nrec(struct audio_device *dev);
+unsigned int headset_add_nrec_cb(struct audio_device *dev,
+ headset_nrec_cb cb, void *user_data);
+gboolean headset_remove_nrec_cb(struct audio_device *dev, unsigned int id);
+gboolean headset_get_inband(struct audio_device *dev);
+gboolean headset_get_sco_hci(struct audio_device *dev);
+
+gboolean headset_is_active(struct audio_device *dev);
+
+headset_lock_t headset_get_lock(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);
+void headset_shutdown(struct audio_device *dev);
diff --git a/audio/ipc.c b/audio/ipc.c
new file mode 100644
index 0000000..1bdad78
--- /dev/null
+++ b/audio/ipc.c
@@ -0,0 +1,133 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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"
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+/* This table contains the string representation for messages types */
+static const char *strtypes[] = {
+ "BT_REQUEST",
+ "BT_RESPONSE",
+ "BT_INDICATION",
+ "BT_ERROR",
+};
+
+/* This table contains the string representation for messages names */
+static const char *strnames[] = {
+ "BT_GET_CAPABILITIES",
+ "BT_OPEN",
+ "BT_SET_CONFIGURATION",
+ "BT_NEW_STREAM",
+ "BT_START_STREAM",
+ "BT_STOP_STREAM",
+ "BT_SUSPEND_STREAM",
+ "BT_RESUME_STREAM",
+ "BT_CONTROL",
+};
+
+int bt_audio_service_open(void)
+{
+ 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) {
+ memcpy(&ret, CMSG_DATA(cmsg), sizeof(int));
+ return ret;
+ }
+ }
+
+ errno = EINVAL;
+ return -1;
+}
+
+const char *bt_audio_strtype(uint8_t type)
+{
+ if (type >= ARRAY_SIZE(strtypes))
+ return NULL;
+
+ return strtypes[type];
+}
+
+const char *bt_audio_strname(uint8_t name)
+{
+ if (name >= ARRAY_SIZE(strnames))
+ return NULL;
+
+ return strnames[name];
+}
diff --git a/audio/ipc.h b/audio/ipc.h
new file mode 100644
index 0000000..d69b97e
--- /dev/null
+++ b/audio/ipc.h
@@ -0,0 +1,360 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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_GET_CAPABILITIES_REQ
+
+ BT_GET_CAPABILITIES_RSP-->
+
+ on snd_pcm_hw_params
+ <--BT_SETCONFIGURATION_REQ
+
+ BT_SET_CONFIGURATION_RSP-->
+
+ on snd_pcm_prepare
+ <--BT_START_STREAM_REQ
+
+ <Moves to streaming state>
+ BT_START_STREAM_RSP-->
+
+ BT_NEW_STREAM_IND -->
+
+ < streams data >
+ ..........
+
+ on snd_pcm_drop/snd_pcm_drain
+
+ <--BT_STOP_STREAM_REQ
+
+ <Moves to open state>
+ BT_STOP_STREAM_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_SUGGESTED_BUFFER_SIZE 512
+#define BT_IPC_SOCKET_NAME "\0/org/bluez/audio"
+
+/* Generic message header definition, except for RESPONSE messages */
+typedef struct {
+ uint8_t type;
+ uint8_t name;
+ uint16_t length;
+} __attribute__ ((packed)) bt_audio_msg_header_t;
+
+typedef struct {
+ bt_audio_msg_header_t h;
+ uint8_t posix_errno;
+} __attribute__ ((packed)) bt_audio_error_t;
+
+/* Message types */
+#define BT_REQUEST 0
+#define BT_RESPONSE 1
+#define BT_INDICATION 2
+#define BT_ERROR 3
+
+/* Messages names */
+#define BT_GET_CAPABILITIES 0
+#define BT_OPEN 1
+#define BT_SET_CONFIGURATION 2
+#define BT_NEW_STREAM 3
+#define BT_START_STREAM 4
+#define BT_STOP_STREAM 5
+#define BT_CLOSE 6
+#define BT_CONTROL 7
+#define BT_DELAY_REPORT 8
+
+#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_get_capabilities_req {
+ bt_audio_msg_header_t h;
+ char source[18]; /* Address of the local Device */
+ char destination[18];/* Address of the remote Device */
+ char object[128]; /* DBus object path */
+ uint8_t transport; /* Requested transport */
+ uint8_t flags; /* Requested flags */
+ uint8_t seid; /* Requested capability configuration */
+} __attribute__ ((packed));
+
+/**
+ * SBC Codec parameters as per A2DP profile 1.0 § 4.3
+ */
+
+/* A2DP seid are 6 bytes long so HSP/HFP are assigned to 7-8 bits */
+#define BT_A2DP_SEID_RANGE (1 << 6) - 1
+
+#define BT_A2DP_SBC_SOURCE 0x00
+#define BT_A2DP_SBC_SINK 0x01
+#define BT_A2DP_MPEG12_SOURCE 0x02
+#define BT_A2DP_MPEG12_SINK 0x03
+#define BT_A2DP_MPEG24_SOURCE 0x04
+#define BT_A2DP_MPEG24_SINK 0x05
+#define BT_A2DP_ATRAC_SOURCE 0x06
+#define BT_A2DP_ATRAC_SINK 0x07
+#define BT_A2DP_UNKNOWN_SOURCE 0x08
+#define BT_A2DP_UNKNOWN_SINK 0x09
+
+#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
+
+#define BT_HFP_CODEC_PCM 0x00
+
+#define BT_PCM_FLAG_NREC 0x01
+#define BT_PCM_FLAG_PCM_ROUTING 0x02
+
+#define BT_WRITE_LOCK (1 << 1)
+#define BT_READ_LOCK 1
+
+typedef struct {
+ uint8_t seid;
+ uint8_t transport;
+ uint8_t type;
+ uint8_t length;
+ uint8_t configured;
+ uint8_t lock;
+ uint8_t data[0];
+} __attribute__ ((packed)) codec_capabilities_t;
+
+typedef struct {
+ codec_capabilities_t capability;
+ 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 {
+ codec_capabilities_t capability;
+ uint8_t channel_mode;
+ uint8_t crc;
+ uint8_t layer;
+ uint8_t frequency;
+ uint8_t mpf;
+ uint16_t bitrate;
+} __attribute__ ((packed)) mpeg_capabilities_t;
+
+typedef struct {
+ codec_capabilities_t capability;
+ uint8_t flags;
+ uint16_t sampling_rate;
+} __attribute__ ((packed)) pcm_capabilities_t;
+
+struct bt_get_capabilities_rsp {
+ bt_audio_msg_header_t h;
+ char source[18]; /* Address of the local Device */
+ char destination[18];/* Address of the remote Device */
+ char object[128]; /* DBus object path */
+ uint8_t data[0]; /* First codec_capabilities_t */
+} __attribute__ ((packed));
+
+struct bt_open_req {
+ bt_audio_msg_header_t h;
+ char source[18]; /* Address of the local Device */
+ char destination[18];/* Address of the remote Device */
+ char object[128]; /* DBus object path */
+ uint8_t seid; /* Requested capability configuration to lock */
+ uint8_t lock; /* Requested lock */
+} __attribute__ ((packed));
+
+struct bt_open_rsp {
+ bt_audio_msg_header_t h;
+ char source[18]; /* Address of the local Device */
+ char destination[18];/* Address of the remote Device */
+ char object[128]; /* DBus object path */
+} __attribute__ ((packed));
+
+struct bt_set_configuration_req {
+ bt_audio_msg_header_t h;
+ codec_capabilities_t codec; /* Requested codec */
+} __attribute__ ((packed));
+
+struct bt_set_configuration_rsp {
+ bt_audio_msg_header_t h;
+ uint16_t link_mtu; /* Max length that transport supports */
+} __attribute__ ((packed));
+
+#define BT_STREAM_ACCESS_READ 0
+#define BT_STREAM_ACCESS_WRITE 1
+#define BT_STREAM_ACCESS_READWRITE 2
+struct bt_start_stream_req {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+struct bt_start_stream_rsp {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+/* This message is followed by one byte of data containing the stream data fd
+ as ancilliary data */
+struct bt_new_stream_ind {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+struct bt_stop_stream_req {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+struct bt_stop_stream_rsp {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+struct bt_close_req {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+struct bt_close_rsp {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+struct bt_suspend_stream_ind {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+struct bt_resume_stream_ind {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+#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));
+
+struct bt_control_rsp {
+ bt_audio_msg_header_t h;
+ uint8_t mode; /* Control Mode */
+ uint8_t key; /* Control Key */
+} __attribute__ ((packed));
+
+struct bt_control_ind {
+ bt_audio_msg_header_t h;
+ uint8_t mode; /* Control Mode */
+ uint8_t key; /* Control Key */
+} __attribute__ ((packed));
+
+struct bt_delay_report_req {
+ bt_audio_msg_header_t h;
+ uint16_t delay;
+} __attribute__ ((packed));
+
+struct bt_delay_report_ind {
+ bt_audio_msg_header_t h;
+ uint16_t delay;
+} __attribute__ ((packed));
+
+/* Function declaration */
+
+/* Opens a connection to the audio service: return a socket descriptor */
+int bt_audio_service_open(void);
+
+/* 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_strtype(uint8_t type);
+
+/* Human readable message name string */
+const char *bt_audio_strname(uint8_t name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BT_AUDIOCLIENT_H */
diff --git a/audio/main.c b/audio/main.c
new file mode 100644
index 0000000..745c307
--- /dev/null
+++ b/audio/main.c
@@ -0,0 +1,194 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "glib-helper.h"
+#include "btio.h"
+#include "plugin.h"
+#include "log.h"
+#include "device.h"
+#include "headset.h"
+#include "manager.h"
+#include "gateway.h"
+
+static GIOChannel *sco_server = NULL;
+
+static GKeyFile *load_config_file(const char *file)
+{
+ GError *err = NULL;
+ GKeyFile *keyfile;
+
+ keyfile = g_key_file_new();
+
+ g_key_file_set_list_separator(keyfile, ',');
+
+ 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 void sco_server_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+ int sk;
+ struct audio_device *device;
+ char addr[18];
+ bdaddr_t src, dst;
+
+ if (err) {
+ error("sco_server_cb: %s", err->message);
+ return;
+ }
+
+ bt_io_get(chan, BT_IO_SCO, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_DEST, addr,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ error("bt_io_get: %s", err->message);
+ goto drop;
+ }
+
+ device = manager_find_device(NULL, &src, &dst, AUDIO_HEADSET_INTERFACE,
+ FALSE);
+ if (!device)
+ device = manager_find_device(NULL, &src, &dst,
+ AUDIO_GATEWAY_INTERFACE,
+ FALSE);
+
+ if (!device)
+ goto drop;
+
+ if (device->headset) {
+ if (headset_get_state(device) < HEADSET_STATE_CONNECTED) {
+ DBG("Refusing SCO from non-connected headset");
+ goto drop;
+ }
+
+ if (!get_hfp_active(device)) {
+ error("Refusing non-HFP SCO connect attempt from %s",
+ addr);
+ goto drop;
+ }
+
+ if (headset_connect_sco(device, chan) < 0)
+ goto drop;
+
+ headset_set_state(device, HEADSET_STATE_PLAYING);
+ } else if (device->gateway) {
+ if (!gateway_is_connected(device)) {
+ DBG("Refusing SCO from non-connected AG");
+ goto drop;
+ }
+
+ if (gateway_connect_sco(device, chan) < 0)
+ goto drop;
+ } else
+ goto drop;
+
+ sk = g_io_channel_unix_get_fd(chan);
+ fcntl(sk, F_SETFL, 0);
+
+ DBG("Accepted SCO connection from %s", addr);
+
+ return;
+
+drop:
+ g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static DBusConnection *connection;
+
+static int audio_init(void)
+{
+ GKeyFile *config;
+ gboolean enable_sco;
+
+ connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+ if (connection == NULL)
+ return -EIO;
+
+ config = load_config_file(CONFIGDIR "/audio.conf");
+
+ if (audio_manager_init(connection, config, &enable_sco) < 0)
+ goto failed;
+
+ if (!enable_sco)
+ return 0;
+
+ sco_server = bt_io_listen(BT_IO_SCO, sco_server_cb, NULL, NULL,
+ NULL, NULL,
+ BT_IO_OPT_INVALID);
+ if (!sco_server) {
+ error("Unable to start SCO server socket");
+ goto failed;
+ }
+
+ return 0;
+
+failed:
+ audio_manager_exit();
+
+ if (connection) {
+ dbus_connection_unref(connection);
+ connection = NULL;
+ }
+
+ return -EIO;
+}
+
+static void audio_exit(void)
+{
+ if (sco_server) {
+ g_io_channel_shutdown(sco_server, TRUE, NULL);
+ g_io_channel_unref(sco_server);
+ sco_server = NULL;
+ }
+
+ audio_manager_exit();
+
+ dbus_connection_unref(connection);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(audio, VERSION,
+ BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, audio_init, audio_exit)
diff --git a/audio/manager.c b/audio/manager.c
new file mode 100644
index 0000000..7e206be
--- /dev/null
+++ b/audio/manager.c
@@ -0,0 +1,1415 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <signal.h>
+
+#include <bluetooth/bluetooth.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 "btio.h"
+#include "../src/adapter.h"
+#include "../src/manager.h"
+#include "../src/device.h"
+
+#include "log.h"
+#include "textfile.h"
+#include "ipc.h"
+#include "device.h"
+#include "error.h"
+#include "avdtp.h"
+#include "media.h"
+#include "a2dp.h"
+#include "headset.h"
+#include "gateway.h"
+#include "sink.h"
+#include "source.h"
+#include "control.h"
+#include "manager.h"
+#include "sdpd.h"
+#include "telephony.h"
+#include "unix.h"
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+typedef enum {
+ HEADSET = 1 << 0,
+ GATEWAY = 1 << 1,
+ 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_adapter {
+ struct btd_adapter *btd_adapter;
+ gboolean powered;
+ uint32_t hsp_ag_record_id;
+ uint32_t hfp_ag_record_id;
+ uint32_t hfp_hs_record_id;
+ GIOChannel *hsp_ag_server;
+ GIOChannel *hfp_ag_server;
+ GIOChannel *hfp_hs_server;
+ gint ref;
+};
+
+static gboolean auto_connect = TRUE;
+static int max_connected_headsets = 1;
+static DBusConnection *connection = NULL;
+static GKeyFile *config = NULL;
+static GSList *adapters = NULL;
+static GSList *devices = NULL;
+
+static struct enabled_interfaces enabled = {
+ .hfp = TRUE,
+ .headset = TRUE,
+ .gateway = FALSE,
+ .sink = TRUE,
+ .source = FALSE,
+ .control = TRUE,
+ .socket = TRUE,
+ .media = FALSE
+};
+
+static struct audio_adapter *find_adapter(GSList *list,
+ struct btd_adapter *btd_adapter)
+{
+ GSList *l;
+
+ for (l = list; l; l = l->next) {
+ struct audio_adapter *adapter = l->data;
+
+ if (adapter->btd_adapter == btd_adapter)
+ return adapter;
+ }
+
+ return NULL;
+}
+
+gboolean server_is_enabled(bdaddr_t *src, uint16_t svc)
+{
+ switch (svc) {
+ case HEADSET_SVCLASS_ID:
+ return enabled.headset;
+ case HEADSET_AGW_SVCLASS_ID:
+ return FALSE;
+ case HANDSFREE_SVCLASS_ID:
+ return enabled.headset && enabled.hfp;
+ case HANDSFREE_AGW_SVCLASS_ID:
+ return enabled.gateway;
+ case AUDIO_SINK_SVCLASS_ID:
+ return enabled.sink;
+ case AUDIO_SOURCE_SVCLASS_ID:
+ return enabled.source;
+ case AV_REMOTE_TARGET_SVCLASS_ID:
+ case AV_REMOTE_SVCLASS_ID:
+ return enabled.control;
+ default:
+ return FALSE;
+ }
+}
+
+static void handle_uuid(const char *uuidstr, struct audio_device *device)
+{
+ uuid_t uuid;
+ uint16_t uuid16;
+
+ if (bt_string2uuid(&uuid, uuidstr) < 0) {
+ error("%s not detected as an UUID-128", uuidstr);
+ return;
+ }
+
+ if (!sdp_uuid128_to_uuid(&uuid) && uuid.type != SDP_UUID16) {
+ error("Could not convert %s to a UUID-16", uuidstr);
+ return;
+ }
+
+ uuid16 = uuid.value.uuid16;
+
+ if (!server_is_enabled(&device->src, uuid16)) {
+ DBG("server not enabled for %s (0x%04x)", uuidstr, uuid16);
+ return;
+ }
+
+ switch (uuid16) {
+ case HEADSET_SVCLASS_ID:
+ DBG("Found Headset record");
+ if (device->headset)
+ headset_update(device, uuid16, uuidstr);
+ else
+ device->headset = headset_init(device, uuid16,
+ uuidstr);
+ break;
+ case HEADSET_AGW_SVCLASS_ID:
+ DBG("Found Headset AG record");
+ break;
+ case HANDSFREE_SVCLASS_ID:
+ DBG("Found Handsfree record");
+ if (device->headset)
+ headset_update(device, uuid16, uuidstr);
+ else
+ device->headset = headset_init(device, uuid16,
+ uuidstr);
+ break;
+ case HANDSFREE_AGW_SVCLASS_ID:
+ DBG("Found Handsfree AG record");
+ if (enabled.gateway && (device->gateway == NULL))
+ device->gateway = gateway_init(device);
+ break;
+ case AUDIO_SINK_SVCLASS_ID:
+ DBG("Found Audio Sink");
+ if (device->sink == NULL)
+ device->sink = sink_init(device);
+ break;
+ case AUDIO_SOURCE_SVCLASS_ID:
+ DBG("Found Audio Source");
+ if (device->source == NULL)
+ device->source = source_init(device);
+ break;
+ case AV_REMOTE_SVCLASS_ID:
+ case AV_REMOTE_TARGET_SVCLASS_ID:
+ DBG("Found AV %s", uuid16 == AV_REMOTE_SVCLASS_ID ?
+ "Remote" : "Target");
+ if (device->control)
+ control_update(device, uuid16);
+ else
+ device->control = control_init(device, uuid16);
+ if (device->sink && sink_is_active(device))
+ avrcp_connect(device);
+ break;
+ default:
+ DBG("Unrecognized UUID: 0x%04X", uuid16);
+ break;
+ }
+}
+
+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 = 0x0102;
+ 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 *hfp_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, HANDSFREE_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]);
+
+ aproto = sdp_list_append(0, apseq);
+ sdp_set_access_protos(record, aproto);
+
+ sdp_set_info_attr(record, "Hands-Free", 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;
+
+ record = sdp_record_alloc();
+ if (!record)
+ return NULL;
+
+ network = sdp_data_alloc(SDP_UINT8, &netid);
+ if (!network) {
+ sdp_record_free(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 headset_auth_cb(DBusError *derr, void *user_data)
+{
+ struct audio_device *device = user_data;
+ GError *err = NULL;
+ GIOChannel *io;
+
+ if (device->hs_preauth_id) {
+ g_source_remove(device->hs_preauth_id);
+ device->hs_preauth_id = 0;
+ }
+
+ if (derr && dbus_error_is_set(derr)) {
+ error("Access denied: %s", derr->message);
+ headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+ return;
+ }
+
+ io = headset_get_rfcomm(device);
+
+ if (!bt_io_accept(io, headset_connect_cb, device, NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
+ headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+ return;
+ }
+}
+
+static gboolean hs_preauth_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer user_data)
+{
+ struct audio_device *device = user_data;
+
+ DBG("Headset disconnected during authorization");
+
+ audio_device_cancel_authorization(device, headset_auth_cb, device);
+
+ headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+
+ device->hs_preauth_id = 0;
+
+ return FALSE;
+}
+
+static void ag_confirm(GIOChannel *chan, gpointer data)
+{
+ const char *server_uuid, *remote_uuid;
+ struct audio_device *device;
+ gboolean hfp_active;
+ bdaddr_t src, dst;
+ int perr;
+ GError *err = NULL;
+ uint8_t ch;
+
+ bt_io_get(chan, BT_IO_RFCOMM, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_CHANNEL, &ch,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ error("%s", err->message);
+ g_error_free(err);
+ goto drop;
+ }
+
+ if (ch == DEFAULT_HS_AG_CHANNEL) {
+ hfp_active = FALSE;
+ server_uuid = HSP_AG_UUID;
+ remote_uuid = HSP_HS_UUID;
+ } else {
+ hfp_active = TRUE;
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
+ }
+
+ device = manager_get_device(&src, &dst, TRUE);
+ if (!device)
+ goto drop;
+
+ if (!manager_allow_headset_connection(device)) {
+ DBG("Refusing headset: too many existing connections");
+ goto drop;
+ }
+
+ if (!device->headset) {
+ btd_device_add_uuid(device->btd_dev, remote_uuid);
+ if (!device->headset)
+ goto drop;
+ }
+
+ if (headset_get_state(device) > HEADSET_STATE_DISCONNECTED) {
+ DBG("Refusing new connection since one already exists");
+ goto drop;
+ }
+
+ set_hfp_active(device, hfp_active);
+
+ if (headset_connect_rfcomm(device, chan) < 0) {
+ error("headset_connect_rfcomm failed");
+ goto drop;
+ }
+
+ headset_set_state(device, HEADSET_STATE_CONNECTING);
+
+ perr = audio_device_request_authorization(device, server_uuid,
+ headset_auth_cb, device);
+ if (perr < 0) {
+ DBG("Authorization denied: %s", strerror(-perr));
+ headset_set_state(device, HEADSET_STATE_DISCONNECTED);
+ return;
+ }
+
+ device->hs_preauth_id = g_io_add_watch(chan,
+ G_IO_NVAL | G_IO_HUP | G_IO_ERR,
+ hs_preauth_cb, device);
+
+ device->auto_connect = auto_connect;
+
+ return;
+
+drop:
+ g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static void gateway_auth_cb(DBusError *derr, void *user_data)
+{
+ struct audio_device *device = user_data;
+
+ if (derr && dbus_error_is_set(derr))
+ error("Access denied: %s", derr->message);
+ else {
+ char ag_address[18];
+
+ ba2str(&device->dst, ag_address);
+ DBG("Accepted AG connection from %s for %s",
+ ag_address, device->path);
+
+ gateway_start_service(device);
+ }
+}
+
+static void hf_io_cb(GIOChannel *chan, gpointer data)
+{
+ bdaddr_t src, dst;
+ GError *err = NULL;
+ uint8_t ch;
+ const char *server_uuid, *remote_uuid;
+ uint16_t svclass;
+ struct audio_device *device;
+ int perr;
+
+ bt_io_get(chan, BT_IO_RFCOMM, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_CHANNEL, &ch,
+ BT_IO_OPT_INVALID);
+
+ if (err) {
+ error("%s", err->message);
+ g_error_free(err);
+ return;
+ }
+
+ server_uuid = HFP_AG_UUID;
+ remote_uuid = HFP_HS_UUID;
+ svclass = HANDSFREE_AGW_SVCLASS_ID;
+
+ device = manager_get_device(&src, &dst, TRUE);
+ if (!device)
+ goto drop;
+
+ if (!device->gateway) {
+ btd_device_add_uuid(device->btd_dev, remote_uuid);
+ if (!device->gateway)
+ goto drop;
+ }
+
+ if (gateway_is_connected(device)) {
+ DBG("Refusing new connection since one already exists");
+ goto drop;
+ }
+
+ if (gateway_connect_rfcomm(device, chan) < 0) {
+ error("Allocating new GIOChannel failed!");
+ goto drop;
+ }
+
+ perr = audio_device_request_authorization(device, server_uuid,
+ gateway_auth_cb, device);
+ if (perr < 0) {
+ DBG("Authorization denied!");
+ goto drop;
+ }
+
+ return;
+
+drop:
+ g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static int headset_server_init(struct audio_adapter *adapter)
+{
+ uint8_t chan = DEFAULT_HS_AG_CHANNEL;
+ sdp_record_t *record;
+ gboolean master = TRUE;
+ GError *err = NULL;
+ uint32_t features;
+ GIOChannel *io;
+ bdaddr_t src;
+
+ if (config) {
+ gboolean tmp;
+
+ tmp = g_key_file_get_boolean(config, "General", "Master",
+ &err);
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else
+ master = tmp;
+ }
+
+ adapter_get_address(adapter->btd_adapter, &src);
+
+ io = bt_io_listen(BT_IO_RFCOMM, NULL, ag_confirm, adapter, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_CHANNEL, chan,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+ BT_IO_OPT_MASTER, master,
+ BT_IO_OPT_INVALID);
+ if (!io)
+ goto failed;
+
+ adapter->hsp_ag_server = io;
+
+ record = hsp_ag_record(chan);
+ if (!record) {
+ error("Unable to allocate new service record");
+ goto failed;
+ }
+
+ if (add_record_to_server(&src, record) < 0) {
+ error("Unable to register HS AG service record");
+ sdp_record_free(record);
+ goto failed;
+ }
+ adapter->hsp_ag_record_id = record->handle;
+
+ features = headset_config_init(config);
+
+ if (!enabled.hfp)
+ return 0;
+
+ chan = DEFAULT_HF_AG_CHANNEL;
+
+ io = bt_io_listen(BT_IO_RFCOMM, NULL, ag_confirm, adapter, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_CHANNEL, chan,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+ BT_IO_OPT_MASTER, master,
+ BT_IO_OPT_INVALID);
+ if (!io)
+ goto failed;
+
+ adapter->hfp_ag_server = io;
+
+ record = hfp_ag_record(chan, features);
+ if (!record) {
+ error("Unable to allocate new service record");
+ goto failed;
+ }
+
+ if (add_record_to_server(&src, record) < 0) {
+ error("Unable to register HF AG service record");
+ sdp_record_free(record);
+ goto failed;
+ }
+ adapter->hfp_ag_record_id = record->handle;
+
+ return 0;
+
+failed:
+ error("%s", err->message);
+ g_error_free(err);
+ if (adapter->hsp_ag_server) {
+ g_io_channel_shutdown(adapter->hsp_ag_server, TRUE, NULL);
+ g_io_channel_unref(adapter->hsp_ag_server);
+ adapter->hsp_ag_server = NULL;
+ }
+
+ if (adapter->hfp_ag_server) {
+ g_io_channel_shutdown(adapter->hfp_ag_server, TRUE, NULL);
+ g_io_channel_unref(adapter->hfp_ag_server);
+ adapter->hfp_ag_server = NULL;
+ }
+
+ return -1;
+}
+
+static int gateway_server_init(struct audio_adapter *adapter)
+{
+ uint8_t chan = DEFAULT_HFP_HS_CHANNEL;
+ sdp_record_t *record;
+ gboolean master = TRUE;
+ GError *err = NULL;
+ GIOChannel *io;
+ bdaddr_t src;
+
+ if (config) {
+ gboolean tmp;
+
+ tmp = g_key_file_get_boolean(config, "General", "Master",
+ &err);
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else
+ master = tmp;
+ }
+
+ adapter_get_address(adapter->btd_adapter, &src);
+
+ io = bt_io_listen(BT_IO_RFCOMM, NULL, hf_io_cb, adapter, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_CHANNEL, chan,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+ BT_IO_OPT_MASTER, master,
+ BT_IO_OPT_INVALID);
+ if (!io) {
+ error("%s", err->message);
+ g_error_free(err);
+ return -1;
+ }
+
+ adapter->hfp_hs_server = io;
+ record = hfp_hs_record(chan);
+ if (!record) {
+ error("Unable to allocate new service record");
+ return -1;
+ }
+
+ if (add_record_to_server(&src, record) < 0) {
+ error("Unable to register HFP HS service record");
+ sdp_record_free(record);
+ g_io_channel_unref(adapter->hfp_hs_server);
+ adapter->hfp_hs_server = NULL;
+ return -1;
+ }
+
+ adapter->hfp_hs_record_id = record->handle;
+
+ return 0;
+}
+
+static int audio_probe(struct btd_device *device, GSList *uuids)
+{
+ struct btd_adapter *adapter = device_get_adapter(device);
+ bdaddr_t src, dst;
+ struct audio_device *audio_dev;
+
+ adapter_get_address(adapter, &src);
+ device_get_address(device, &dst);
+
+ audio_dev = manager_get_device(&src, &dst, TRUE);
+ if (!audio_dev) {
+ DBG("unable to get a device object");
+ return -1;
+ }
+
+ g_slist_foreach(uuids, (GFunc) handle_uuid, audio_dev);
+
+ return 0;
+}
+
+static void audio_remove(struct btd_device *device)
+{
+ struct audio_device *dev;
+ const char *path;
+
+ path = device_get_path(device);
+
+ dev = manager_find_device(path, NULL, NULL, NULL, FALSE);
+ if (!dev)
+ return;
+
+ devices = g_slist_remove(devices, dev);
+
+ audio_device_unregister(dev);
+
+}
+
+static struct audio_adapter *audio_adapter_ref(struct audio_adapter *adp)
+{
+ adp->ref++;
+
+ DBG("%p: ref=%d", adp, adp->ref);
+
+ return adp;
+}
+
+static void audio_adapter_unref(struct audio_adapter *adp)
+{
+ adp->ref--;
+
+ DBG("%p: ref=%d", adp, adp->ref);
+
+ if (adp->ref > 0)
+ return;
+
+ adapters = g_slist_remove(adapters, adp);
+ btd_adapter_unref(adp->btd_adapter);
+ g_free(adp);
+}
+
+static struct audio_adapter *audio_adapter_create(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+
+ adp = g_new0(struct audio_adapter, 1);
+ adp->btd_adapter = btd_adapter_ref(adapter);
+
+ return audio_adapter_ref(adp);
+}
+
+static struct audio_adapter *audio_adapter_get(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+
+ adp = find_adapter(adapters, adapter);
+ if (!adp) {
+ adp = audio_adapter_create(adapter);
+ if (!adp)
+ return NULL;
+ adapters = g_slist_append(adapters, adp);
+ } else
+ audio_adapter_ref(adp);
+
+ return adp;
+}
+
+static void state_changed(struct btd_adapter *adapter, gboolean powered)
+{
+ struct audio_adapter *adp;
+ static gboolean telephony = FALSE;
+ GSList *l;
+
+ DBG("%s powered %s", adapter_get_path(adapter),
+ powered ? "on" : "off");
+
+ /* ignore powered change, adapter is powering down */
+ if (powered && adapter_powering_down(adapter))
+ return;
+
+ adp = find_adapter(adapters, adapter);
+ if (!adp)
+ return;
+
+ adp->powered = powered;
+
+ if (powered) {
+ /* telephony driver already initialized*/
+ if (telephony == TRUE)
+ return;
+ telephony_init();
+ telephony = TRUE;
+ return;
+ }
+
+ /* telephony not initialized just ignore power down */
+ if (telephony == FALSE)
+ return;
+
+ for (l = adapters; l; l = l->next) {
+ adp = l->data;
+
+ if (adp->powered == TRUE)
+ return;
+ }
+
+ telephony_exit();
+ telephony = FALSE;
+}
+
+static int headset_server_probe(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+ const gchar *path = adapter_get_path(adapter);
+ int err;
+
+ DBG("path %s", path);
+
+ adp = audio_adapter_get(adapter);
+ if (!adp)
+ return -EINVAL;
+
+ err = headset_server_init(adp);
+ if (err < 0) {
+ audio_adapter_unref(adp);
+ return err;
+ }
+
+ btd_adapter_register_powered_callback(adapter, state_changed);
+ state_changed(adapter, TRUE);
+
+ return 0;
+}
+
+static void headset_server_remove(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+ const gchar *path = adapter_get_path(adapter);
+
+ DBG("path %s", path);
+
+ adp = find_adapter(adapters, adapter);
+ if (!adp)
+ return;
+
+ if (adp->hsp_ag_record_id) {
+ remove_record_from_server(adp->hsp_ag_record_id);
+ adp->hsp_ag_record_id = 0;
+ }
+
+ if (adp->hsp_ag_server) {
+ g_io_channel_shutdown(adp->hsp_ag_server, TRUE, NULL);
+ g_io_channel_unref(adp->hsp_ag_server);
+ adp->hsp_ag_server = NULL;
+ }
+
+ if (adp->hfp_ag_record_id) {
+ remove_record_from_server(adp->hfp_ag_record_id);
+ adp->hfp_ag_record_id = 0;
+ }
+
+ if (adp->hfp_ag_server) {
+ g_io_channel_shutdown(adp->hfp_ag_server, TRUE, NULL);
+ g_io_channel_unref(adp->hfp_ag_server);
+ adp->hfp_ag_server = NULL;
+ }
+
+ btd_adapter_unregister_powered_callback(adapter, state_changed);
+
+ audio_adapter_unref(adp);
+}
+
+static int gateway_server_probe(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+
+ adp = audio_adapter_get(adapter);
+ if (!adp)
+ return -EINVAL;
+
+ return gateway_server_init(adp);
+}
+
+static void gateway_server_remove(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+ const gchar *path = adapter_get_path(adapter);
+
+ DBG("path %s", path);
+
+ adp = find_adapter(adapters, adapter);
+ if (!adp)
+ return;
+
+ if (adp->hfp_hs_record_id) {
+ remove_record_from_server(adp->hfp_hs_record_id);
+ adp->hfp_hs_record_id = 0;
+ }
+
+ if (adp->hfp_hs_server) {
+ g_io_channel_unref(adp->hfp_hs_server);
+ adp->hfp_hs_server = NULL;
+ }
+
+ audio_adapter_unref(adp);
+}
+
+static int a2dp_server_probe(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+ const gchar *path = adapter_get_path(adapter);
+ bdaddr_t src;
+ int err;
+
+ DBG("path %s", path);
+
+ adp = audio_adapter_get(adapter);
+ if (!adp)
+ return -EINVAL;
+
+ adapter_get_address(adapter, &src);
+
+ err = a2dp_register(connection, &src, config);
+ if (err < 0)
+ audio_adapter_unref(adp);
+
+ return err;
+}
+
+static void a2dp_server_remove(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+ const gchar *path = adapter_get_path(adapter);
+ bdaddr_t src;
+
+ DBG("path %s", path);
+
+ adp = find_adapter(adapters, adapter);
+ if (!adp)
+ return;
+
+ adapter_get_address(adapter, &src);
+ a2dp_unregister(&src);
+ audio_adapter_unref(adp);
+}
+
+static int avrcp_server_probe(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+ const gchar *path = adapter_get_path(adapter);
+ bdaddr_t src;
+
+ DBG("path %s", path);
+
+ adp = audio_adapter_get(adapter);
+ if (!adp)
+ return -EINVAL;
+
+ adapter_get_address(adapter, &src);
+
+ return avrcp_register(connection, &src, config);
+}
+
+static void avrcp_server_remove(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+ const gchar *path = adapter_get_path(adapter);
+ bdaddr_t src;
+
+ DBG("path %s", path);
+
+ adp = find_adapter(adapters, adapter);
+ if (!adp)
+ return;
+
+ adapter_get_address(adapter, &src);
+ avrcp_unregister(&src);
+ audio_adapter_unref(adp);
+}
+
+static int media_server_probe(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+ const gchar *path = adapter_get_path(adapter);
+ bdaddr_t src;
+
+ DBG("path %s", path);
+
+ adp = audio_adapter_get(adapter);
+ if (!adp)
+ return -EINVAL;
+
+ adapter_get_address(adapter, &src);
+
+ return media_register(connection, path, &src);
+}
+
+static void media_server_remove(struct btd_adapter *adapter)
+{
+ struct audio_adapter *adp;
+ const gchar *path = adapter_get_path(adapter);
+
+ DBG("path %s", path);
+
+ adp = find_adapter(adapters, adapter);
+ if (!adp)
+ return;
+
+ media_unregister(path);
+ audio_adapter_unref(adp);
+}
+
+static struct btd_device_driver audio_driver = {
+ .name = "audio",
+ .uuids = BTD_UUIDS(HSP_HS_UUID, HFP_HS_UUID, HSP_AG_UUID, HFP_AG_UUID,
+ ADVANCED_AUDIO_UUID, A2DP_SOURCE_UUID, A2DP_SINK_UUID,
+ AVRCP_TARGET_UUID, AVRCP_REMOTE_UUID),
+ .probe = audio_probe,
+ .remove = audio_remove,
+};
+
+static struct btd_adapter_driver headset_server_driver = {
+ .name = "audio-headset",
+ .probe = headset_server_probe,
+ .remove = headset_server_remove,
+};
+
+static struct btd_adapter_driver gateway_server_driver = {
+ .name = "audio-gateway",
+ .probe = gateway_server_probe,
+ .remove = gateway_server_remove,
+};
+
+static struct btd_adapter_driver a2dp_server_driver = {
+ .name = "audio-a2dp",
+ .probe = a2dp_server_probe,
+ .remove = a2dp_server_remove,
+};
+
+static struct btd_adapter_driver avrcp_server_driver = {
+ .name = "audio-control",
+ .probe = avrcp_server_probe,
+ .remove = avrcp_server_remove,
+};
+
+static struct btd_adapter_driver media_server_driver = {
+ .name = "media",
+ .probe = media_server_probe,
+ .remove = media_server_remove,
+};
+
+int audio_manager_init(DBusConnection *conn, GKeyFile *conf,
+ gboolean *enable_sco)
+{
+ char **list;
+ int i;
+ gboolean b;
+ GError *err = NULL;
+
+ connection = dbus_connection_ref(conn);
+
+ if (!conf)
+ goto proceed;
+
+ config = conf;
+
+ 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;
+ else if (g_str_equal(list[i], "Socket"))
+ enabled.socket = TRUE;
+ else if (g_str_equal(list[i], "Media"))
+ enabled.media = TRUE;
+ }
+ g_strfreev(list);
+
+ 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;
+ else if (g_str_equal(list[i], "Socket"))
+ enabled.socket = FALSE;
+ else if (g_str_equal(list[i], "Media"))
+ enabled.media = FALSE;
+ }
+ g_strfreev(list);
+
+ b = g_key_file_get_boolean(config, "General", "AutoConnect", &err);
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else
+ auto_connect = b;
+
+ b = g_key_file_get_boolean(config, "Headset", "HFP",
+ &err);
+ if (err)
+ g_clear_error(&err);
+ else
+ enabled.hfp = b;
+
+ err = NULL;
+ i = g_key_file_get_integer(config, "Headset", "MaxConnected",
+ &err);
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else
+ max_connected_headsets = i;
+
+proceed:
+ if (enabled.socket)
+ unix_init();
+
+ if (enabled.media)
+ btd_register_adapter_driver(&media_server_driver);
+
+ if (enabled.headset)
+ btd_register_adapter_driver(&headset_server_driver);
+
+ if (enabled.gateway)
+ btd_register_adapter_driver(&gateway_server_driver);
+
+ if (enabled.source || enabled.sink)
+ btd_register_adapter_driver(&a2dp_server_driver);
+
+ if (enabled.control)
+ btd_register_adapter_driver(&avrcp_server_driver);
+
+ btd_register_device_driver(&audio_driver);
+
+ *enable_sco = (enabled.gateway || enabled.headset);
+
+ return 0;
+}
+
+void audio_manager_exit(void)
+{
+ /* Bail out early if we haven't been initialized */
+ if (connection == NULL)
+ return;
+
+ dbus_connection_unref(connection);
+ connection = NULL;
+
+ if (config) {
+ g_key_file_free(config);
+ config = NULL;
+ }
+
+ if (enabled.socket)
+ unix_exit();
+
+ if (enabled.media)
+ btd_unregister_adapter_driver(&media_server_driver);
+
+ if (enabled.headset)
+ btd_unregister_adapter_driver(&headset_server_driver);
+
+ if (enabled.gateway)
+ btd_unregister_adapter_driver(&gateway_server_driver);
+
+ if (enabled.source || enabled.sink)
+ btd_unregister_adapter_driver(&a2dp_server_driver);
+
+ if (enabled.control)
+ btd_unregister_adapter_driver(&avrcp_server_driver);
+
+ btd_unregister_device_driver(&audio_driver);
+}
+
+struct audio_device *manager_find_device(const char *path,
+ const bdaddr_t *src,
+ const bdaddr_t *dst,
+ const char *interface,
+ gboolean connected)
+{
+ GSList *l;
+
+ for (l = devices; l != NULL; l = l->next) {
+ struct audio_device *dev = l->data;
+
+ if ((path && (strcmp(path, "")) && strcmp(dev->path, path)))
+ continue;
+
+ if ((src && bacmp(src, BDADDR_ANY)) && bacmp(&dev->src, src))
+ continue;
+
+ if ((dst && bacmp(dst, BDADDR_ANY)) && bacmp(&dev->dst, dst))
+ continue;
+
+ if (interface && !strcmp(AUDIO_HEADSET_INTERFACE, interface)
+ && !dev->headset)
+ continue;
+
+ if (interface && !strcmp(AUDIO_GATEWAY_INTERFACE, interface)
+ && !dev->gateway)
+ 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 && !audio_device_is_active(dev, interface))
+ continue;
+
+ return dev;
+ }
+
+ return NULL;
+}
+
+struct audio_device *manager_get_device(const bdaddr_t *src,
+ const bdaddr_t *dst,
+ gboolean create)
+{
+ struct audio_device *dev;
+ struct btd_adapter *adapter;
+ struct btd_device *device;
+ char addr[18];
+ const char *path;
+
+ dev = manager_find_device(NULL, src, dst, NULL, FALSE);
+ if (dev)
+ return dev;
+
+ if (!create)
+ return NULL;
+
+ ba2str(src, addr);
+
+ adapter = manager_find_adapter(src);
+ if (!adapter) {
+ error("Unable to get a btd_adapter object for %s",
+ addr);
+ return NULL;
+ }
+
+ ba2str(dst, addr);
+
+ device = adapter_get_device(connection, adapter, addr);
+ if (!device) {
+ error("Unable to get btd_device object for %s", addr);
+ return NULL;
+ }
+
+ path = device_get_path(device);
+
+ dev = audio_device_register(connection, device, path, src, dst);
+ if (!dev)
+ return NULL;
+
+ devices = g_slist_append(devices, dev);
+
+ return dev;
+}
+
+gboolean manager_allow_headset_connection(struct audio_device *device)
+{
+ GSList *l;
+ int connected = 0;
+
+ for (l = devices; l != NULL; l = l->next) {
+ struct audio_device *dev = l->data;
+ struct headset *hs = dev->headset;
+
+ if (dev == device)
+ continue;
+
+ if (bacmp(&dev->src, &device->src))
+ continue;
+
+ if (!hs)
+ continue;
+
+ if (headset_get_state(dev) > HEADSET_STATE_DISCONNECTED)
+ connected++;
+
+ if (connected >= max_connected_headsets)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void manager_set_fast_connectable(gboolean enable)
+{
+ GSList *l;
+
+ for (l = adapters; l != NULL; l = l->next) {
+ struct audio_adapter *adapter = l->data;
+
+ if (btd_adapter_set_fast_connectable(adapter->btd_adapter,
+ enable))
+ error("Changing fast connectable for hci%d failed",
+ adapter_get_dev_id(adapter->btd_adapter));
+ }
+}
diff --git a/audio/manager.h b/audio/manager.h
new file mode 100644
index 0000000..0bf7663
--- /dev/null
+++ b/audio/manager.h
@@ -0,0 +1,56 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+struct enabled_interfaces {
+ gboolean hfp;
+ gboolean headset;
+ gboolean gateway;
+ gboolean sink;
+ gboolean source;
+ gboolean control;
+ gboolean socket;
+ gboolean media;
+};
+
+int audio_manager_init(DBusConnection *conn, GKeyFile *config,
+ gboolean *enable_sco);
+void audio_manager_exit(void);
+
+gboolean server_is_enabled(bdaddr_t *src, uint16_t svc);
+
+struct audio_device *manager_find_device(const char *path,
+ const bdaddr_t *src,
+ const bdaddr_t *dst,
+ const char *interface,
+ gboolean connected);
+
+struct audio_device *manager_get_device(const bdaddr_t *src,
+ const bdaddr_t *dst,
+ gboolean create);
+
+gboolean manager_allow_headset_connection(struct audio_device *device);
+
+/* TRUE to enable fast connectable and FALSE to disable fast connectable for all
+ * audio adapters. */
+void manager_set_fast_connectable(gboolean enable);
diff --git a/audio/media.c b/audio/media.c
new file mode 100644
index 0000000..0efb0a8
--- /dev/null
+++ b/audio/media.c
@@ -0,0 +1,714 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "../src/adapter.h"
+#include "../src/dbus-common.h"
+
+#include "log.h"
+#include "error.h"
+#include "device.h"
+#include "avdtp.h"
+#include "media.h"
+#include "transport.h"
+#include "a2dp.h"
+#include "headset.h"
+#include "manager.h"
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+#define MEDIA_INTERFACE "org.bluez.Media"
+#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint"
+
+#define REQUEST_TIMEOUT (3 * 1000) /* 3 seconds */
+
+struct media_adapter {
+ bdaddr_t src; /* Adapter address */
+ char *path; /* Adapter path */
+ DBusConnection *conn; /* Adapter connection */
+ GSList *endpoints; /* Endpoints list */
+};
+
+struct endpoint_request {
+ DBusMessage *msg;
+ DBusPendingCall *call;
+ media_endpoint_cb_t cb;
+ void *user_data;
+};
+
+struct media_endpoint {
+ struct a2dp_sep *sep;
+ char *sender; /* Endpoint DBus bus id */
+ char *path; /* Endpoint object path */
+ char *uuid; /* Endpoint property UUID */
+ uint8_t codec; /* Endpoint codec */
+ uint8_t *capabilities; /* Endpoint property capabilities */
+ size_t size; /* Endpoint capabilities size */
+ guint hs_watch;
+ guint watch;
+ struct endpoint_request *request;
+ struct media_transport *transport;
+ struct media_adapter *adapter;
+};
+
+static GSList *adapters = NULL;
+
+static void endpoint_request_free(struct endpoint_request *request)
+{
+ if (request->call)
+ dbus_pending_call_unref(request->call);
+
+ dbus_message_unref(request->msg);
+ g_free(request);
+}
+
+static void media_endpoint_cancel(struct media_endpoint *endpoint)
+{
+ struct endpoint_request *request = endpoint->request;
+
+ if (request->call)
+ dbus_pending_call_cancel(request->call);
+
+ endpoint_request_free(request);
+ endpoint->request = NULL;
+}
+
+static void media_endpoint_remove(struct media_endpoint *endpoint)
+{
+ struct media_adapter *adapter = endpoint->adapter;
+
+ if (g_slist_find(adapter->endpoints, endpoint) == NULL)
+ return;
+
+ info("Endpoint unregistered: sender=%s path=%s", endpoint->sender,
+ endpoint->path);
+
+ adapter->endpoints = g_slist_remove(adapter->endpoints, endpoint);
+
+ if (endpoint->sep)
+ a2dp_remove_sep(endpoint->sep);
+
+ if (endpoint->hs_watch)
+ headset_remove_state_cb(endpoint->hs_watch);
+
+ if (endpoint->request)
+ media_endpoint_cancel(endpoint);
+
+ if (endpoint->transport)
+ media_transport_destroy(endpoint->transport);
+
+ g_dbus_remove_watch(adapter->conn, endpoint->watch);
+ g_free(endpoint->capabilities);
+ g_free(endpoint->sender);
+ g_free(endpoint->path);
+ g_free(endpoint->uuid);
+ g_free(endpoint);
+}
+
+static void media_endpoint_exit(DBusConnection *connection, void *user_data)
+{
+ struct media_endpoint *endpoint = user_data;
+
+ endpoint->watch = 0;
+ media_endpoint_remove(endpoint);
+}
+
+static void headset_setconf_cb(struct media_endpoint *endpoint, void *ret,
+ int size, void *user_data)
+{
+ struct audio_device *dev = user_data;
+
+ if (ret != NULL)
+ return;
+
+ headset_set_state(dev, HEADSET_STATE_DISCONNECTED);
+}
+
+static void headset_state_changed(struct audio_device *dev,
+ headset_state_t old_state,
+ headset_state_t new_state,
+ void *user_data)
+{
+ struct media_endpoint *endpoint = user_data;
+
+ DBG("");
+
+ switch (new_state) {
+ case HEADSET_STATE_DISCONNECTED:
+ media_endpoint_clear_configuration(endpoint);
+ break;
+ case HEADSET_STATE_CONNECTING:
+ media_endpoint_set_configuration(endpoint, dev, NULL, 0,
+ headset_setconf_cb, dev);
+ break;
+ case HEADSET_STATE_CONNECTED:
+ break;
+ case HEADSET_STATE_PLAY_IN_PROGRESS:
+ break;
+ case HEADSET_STATE_PLAYING:
+ break;
+ }
+}
+
+static struct media_endpoint *media_endpoint_create(struct media_adapter *adapter,
+ const char *sender,
+ const char *path,
+ const char *uuid,
+ gboolean delay_reporting,
+ uint8_t codec,
+ uint8_t *capabilities,
+ int size,
+ int *err)
+{
+ struct media_endpoint *endpoint;
+
+ endpoint = g_new0(struct media_endpoint, 1);
+ endpoint->sender = g_strdup(sender);
+ endpoint->path = g_strdup(path);
+ endpoint->uuid = g_strdup(uuid);
+ endpoint->codec = codec;
+
+ if (size > 0) {
+ endpoint->capabilities = g_new(uint8_t, size);
+ memcpy(endpoint->capabilities, capabilities, size);
+ endpoint->size = size;
+ }
+
+ endpoint->adapter = adapter;
+
+ if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) {
+ endpoint->sep = a2dp_add_sep(&adapter->src,
+ AVDTP_SEP_TYPE_SOURCE, codec,
+ delay_reporting, endpoint, err);
+ if (endpoint->sep == NULL)
+ goto failed;
+ } else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) {
+ endpoint->sep = a2dp_add_sep(&adapter->src,
+ AVDTP_SEP_TYPE_SINK, codec,
+ delay_reporting, endpoint, err);
+ if (endpoint->sep == NULL)
+ goto failed;
+ } else if (strcasecmp(uuid, HFP_AG_UUID) == 0 ||
+ g_strcmp0(uuid, HSP_AG_UUID) == 0) {
+ struct audio_device *dev;
+
+ endpoint->hs_watch = headset_add_state_cb(headset_state_changed,
+ endpoint);
+ dev = manager_find_device(NULL, &adapter->src, BDADDR_ANY,
+ AUDIO_HEADSET_INTERFACE, TRUE);
+ if (dev)
+ media_endpoint_set_configuration(endpoint, dev, NULL,
+ 0, headset_setconf_cb,
+ dev);
+ } else {
+ if (err)
+ *err = -EINVAL;
+ goto failed;
+ }
+
+ endpoint->watch = g_dbus_add_disconnect_watch(adapter->conn, sender,
+ media_endpoint_exit, endpoint,
+ NULL);
+
+ adapter->endpoints = g_slist_append(adapter->endpoints, endpoint);
+ info("Endpoint registered: sender=%s path=%s", sender, path);
+
+ if (err)
+ *err = 0;
+ return endpoint;
+
+failed:
+ g_free(endpoint);
+ return NULL;
+}
+
+static struct media_endpoint *media_adapter_find_endpoint(
+ struct media_adapter *adapter,
+ const char *sender,
+ const char *path,
+ const char *uuid)
+{
+ GSList *l;
+
+ for (l = adapter->endpoints; l; l = l->next) {
+ struct media_endpoint *endpoint = l->data;
+
+ if (sender && g_strcmp0(endpoint->sender, sender) != 0)
+ continue;
+
+ if (path && g_strcmp0(endpoint->path, path) != 0)
+ continue;
+
+ if (uuid && g_strcmp0(endpoint->uuid, uuid) != 0)
+ continue;
+
+ return endpoint;
+ }
+
+ return NULL;
+}
+
+const char *media_endpoint_get_sender(struct media_endpoint *endpoint)
+{
+ return endpoint->sender;
+}
+
+static int parse_properties(DBusMessageIter *props, const char **uuid,
+ gboolean *delay_reporting, uint8_t *codec,
+ uint8_t **capabilities, int *size)
+{
+ gboolean has_uuid = FALSE;
+ gboolean has_codec = FALSE;
+
+ while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) {
+ const char *key;
+ DBusMessageIter value, entry;
+ int var;
+
+ dbus_message_iter_recurse(props, &entry);
+ dbus_message_iter_get_basic(&entry, &key);
+
+ dbus_message_iter_next(&entry);
+ dbus_message_iter_recurse(&entry, &value);
+
+ var = dbus_message_iter_get_arg_type(&value);
+ if (strcasecmp(key, "UUID") == 0) {
+ if (var != DBUS_TYPE_STRING)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, uuid);
+ has_uuid = TRUE;
+ } else if (strcasecmp(key, "Codec") == 0) {
+ if (var != DBUS_TYPE_BYTE)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, codec);
+ has_codec = TRUE;
+ } else if (strcasecmp(key, "DelayReporting") == 0) {
+ if (var != DBUS_TYPE_BOOLEAN)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, delay_reporting);
+ } else if (strcasecmp(key, "Capabilities") == 0) {
+ DBusMessageIter array;
+
+ if (var != DBUS_TYPE_ARRAY)
+ return -EINVAL;
+
+ dbus_message_iter_recurse(&value, &array);
+ dbus_message_iter_get_fixed_array(&array, capabilities,
+ size);
+ }
+
+ dbus_message_iter_next(props);
+ }
+
+ return (has_uuid && has_codec) ? 0 : -EINVAL;
+}
+
+static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct media_adapter *adapter = data;
+ DBusMessageIter args, props;
+ const char *sender, *path, *uuid;
+ gboolean delay_reporting = FALSE;
+ uint8_t codec;
+ uint8_t *capabilities;
+ int size = 0;
+ int err;
+
+ sender = dbus_message_get_sender(msg);
+
+ dbus_message_iter_init(msg, &args);
+
+ dbus_message_iter_get_basic(&args, &path);
+ dbus_message_iter_next(&args);
+
+ if (media_adapter_find_endpoint(adapter, sender, path, NULL) != NULL)
+ return btd_error_already_exists(msg);
+
+ dbus_message_iter_recurse(&args, &props);
+ if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
+ return btd_error_invalid_args(msg);
+
+ if (parse_properties(&props, &uuid, &delay_reporting, &codec,
+ &capabilities, &size) < 0)
+ return btd_error_invalid_args(msg);
+
+ if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting,
+ codec, capabilities, size, &err) == FALSE) {
+ if (err == -EPROTONOSUPPORT)
+ return btd_error_not_supported(msg);
+ else
+ return btd_error_invalid_args(msg);
+ }
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *unregister_endpoint(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct media_adapter *adapter = data;
+ struct media_endpoint *endpoint;
+ const char *sender, *path;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ sender = dbus_message_get_sender(msg);
+
+ endpoint = media_adapter_find_endpoint(adapter, sender, path, NULL);
+ if (endpoint == NULL)
+ return btd_error_does_not_exist(msg);
+
+ media_endpoint_remove(endpoint);
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static GDBusMethodTable media_methods[] = {
+ { "RegisterEndpoint", "oa{sv}", "", register_endpoint },
+ { "UnregisterEndpoint", "o", "", unregister_endpoint },
+ { },
+};
+
+static void path_free(void *data)
+{
+ struct media_adapter *adapter = data;
+
+ g_slist_foreach(adapter->endpoints, (GFunc) media_endpoint_release,
+ NULL);
+ g_slist_free(adapter->endpoints);
+
+ dbus_connection_unref(adapter->conn);
+
+ adapters = g_slist_remove(adapters, adapter);
+
+ g_free(adapter->path);
+ g_free(adapter);
+}
+
+int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src)
+{
+ struct media_adapter *adapter;
+
+ if (DBUS_TYPE_UNIX_FD < 0)
+ return -EPERM;
+
+ adapter = g_new0(struct media_adapter, 1);
+ adapter->conn = dbus_connection_ref(conn);
+ bacpy(&adapter->src, src);
+ adapter->path = g_strdup(path);
+
+ if (!g_dbus_register_interface(conn, path, MEDIA_INTERFACE,
+ media_methods, NULL, NULL,
+ adapter, path_free)) {
+ error("D-Bus failed to register %s path", path);
+ path_free(adapter);
+ return -1;
+ }
+
+ adapters = g_slist_append(adapters, adapter);
+
+ return 0;
+}
+
+void media_unregister(const char *path)
+{
+ GSList *l;
+
+ for (l = adapters; l; l = l->next) {
+ struct media_adapter *adapter = l->data;
+
+ if (g_strcmp0(path, adapter->path) == 0) {
+ g_dbus_unregister_interface(adapter->conn, path,
+ MEDIA_INTERFACE);
+ return;
+ }
+ }
+}
+
+size_t media_endpoint_get_capabilities(struct media_endpoint *endpoint,
+ uint8_t **capabilities)
+{
+ *capabilities = endpoint->capabilities;
+ return endpoint->size;
+}
+
+static void endpoint_reply(DBusPendingCall *call, void *user_data)
+{
+ struct media_endpoint *endpoint = user_data;
+ struct endpoint_request *request = endpoint->request;
+ DBusMessage *reply;
+ DBusError err;
+ gboolean value;
+ void *ret = NULL;
+ int size = -1;
+
+ /* steal_reply will always return non-NULL since the callback
+ * is only called after a reply has been received */
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("Endpoint replied with an error: %s",
+ err.name);
+
+ /* Clear endpoint configuration in case of NO_REPLY error */
+ if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) {
+ if (request->cb)
+ request->cb(endpoint, NULL, size,
+ request->user_data);
+ media_endpoint_clear_configuration(endpoint);
+ dbus_message_unref(reply);
+ dbus_error_free(&err);
+ return;
+ }
+
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_error_init(&err);
+ if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE,
+ "SelectConfiguration")) {
+ DBusMessageIter args, array;
+ uint8_t *configuration;
+
+ dbus_message_iter_init(reply, &args);
+
+ dbus_message_iter_recurse(&args, &array);
+
+ dbus_message_iter_get_fixed_array(&array, &configuration, &size);
+
+ ret = configuration;
+ goto done;
+ } else if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) {
+ error("Wrong reply signature: %s", err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ size = 1;
+ value = TRUE;
+ ret = &value;
+
+done:
+ dbus_message_unref(reply);
+
+ if (request->cb)
+ request->cb(endpoint, ret, size, request->user_data);
+
+ endpoint_request_free(request);
+ endpoint->request = NULL;
+}
+
+static gboolean media_endpoint_async_call(DBusConnection *conn,
+ DBusMessage *msg,
+ struct media_endpoint *endpoint,
+ media_endpoint_cb_t cb,
+ void *user_data)
+{
+ struct endpoint_request *request;
+
+ if (endpoint->request)
+ return FALSE;
+
+ request = g_new0(struct endpoint_request, 1);
+
+ /* Timeout should be less than avdtp request timeout (4 seconds) */
+ if (dbus_connection_send_with_reply(conn, msg, &request->call,
+ REQUEST_TIMEOUT) == FALSE) {
+ error("D-Bus send failed");
+ g_free(request);
+ return FALSE;
+ }
+
+ dbus_pending_call_set_notify(request->call, endpoint_reply, endpoint, NULL);
+
+ request->msg = msg;
+ request->cb = cb;
+ request->user_data = user_data;
+ endpoint->request = request;
+
+ DBG("Calling %s: name = %s path = %s", dbus_message_get_member(msg),
+ dbus_message_get_destination(msg),
+ dbus_message_get_path(msg));
+
+ return TRUE;
+}
+
+gboolean media_endpoint_set_configuration(struct media_endpoint *endpoint,
+ struct audio_device *device,
+ uint8_t *configuration, size_t size,
+ media_endpoint_cb_t cb,
+ void *user_data)
+{
+ DBusConnection *conn;
+ DBusMessage *msg;
+ const char *path;
+ DBusMessageIter iter;
+
+ if (endpoint->transport != NULL || endpoint->request != NULL)
+ return FALSE;
+
+ conn = endpoint->adapter->conn;
+
+ endpoint->transport = media_transport_create(conn, endpoint, device,
+ configuration, size);
+ if (endpoint->transport == NULL)
+ return FALSE;
+
+ msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+ MEDIA_ENDPOINT_INTERFACE,
+ "SetConfiguration");
+ if (msg == NULL) {
+ error("Couldn't allocate D-Bus message");
+ return FALSE;
+ }
+
+ dbus_message_iter_init_append(msg, &iter);
+
+ path = media_transport_get_path(endpoint->transport);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
+
+ transport_get_properties(endpoint->transport, &iter);
+
+ return media_endpoint_async_call(conn, msg, endpoint, cb, user_data);
+}
+
+gboolean media_endpoint_select_configuration(struct media_endpoint *endpoint,
+ uint8_t *capabilities,
+ size_t length,
+ media_endpoint_cb_t cb,
+ void *user_data)
+{
+ DBusConnection *conn;
+ DBusMessage *msg;
+
+ if (endpoint->request != NULL)
+ return FALSE;
+
+ conn = endpoint->adapter->conn;
+
+ msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+ MEDIA_ENDPOINT_INTERFACE,
+ "SelectConfiguration");
+ if (msg == NULL) {
+ error("Couldn't allocate D-Bus message");
+ return FALSE;
+ }
+
+ dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &capabilities, length,
+ DBUS_TYPE_INVALID);
+
+ return media_endpoint_async_call(conn, msg, endpoint, cb, user_data);
+}
+
+void media_endpoint_clear_configuration(struct media_endpoint *endpoint)
+{
+ DBusConnection *conn;
+ DBusMessage *msg;
+ const char *path;
+
+ if (endpoint->transport == NULL)
+ return;
+
+ if (endpoint->request)
+ media_endpoint_cancel(endpoint);
+
+ conn = endpoint->adapter->conn;
+
+ msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+ MEDIA_ENDPOINT_INTERFACE,
+ "ClearConfiguration");
+ if (msg == NULL) {
+ error("Couldn't allocate D-Bus message");
+ goto done;
+ }
+
+ path = media_transport_get_path(endpoint->transport);
+ dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID);
+ g_dbus_send_message(conn, msg);
+done:
+ media_transport_destroy(endpoint->transport);
+ endpoint->transport = NULL;
+}
+
+void media_endpoint_release(struct media_endpoint *endpoint)
+{
+ DBusMessage *msg;
+
+ DBG("sender=%s path=%s", endpoint->sender, endpoint->path);
+
+ /* already exit */
+ if (endpoint->watch == 0)
+ return;
+
+ msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+ MEDIA_ENDPOINT_INTERFACE,
+ "Release");
+ if (msg == NULL) {
+ error("Couldn't allocate D-Bus message");
+ return;
+ }
+
+ g_dbus_send_message(endpoint->adapter->conn, msg);
+
+ media_endpoint_remove(endpoint);
+}
+
+struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint)
+{
+ return endpoint->sep;
+}
+
+const char *media_endpoint_get_uuid(struct media_endpoint *endpoint)
+{
+ return endpoint->uuid;
+}
+
+uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint)
+{
+ return endpoint->codec;
+}
+
+struct media_transport *media_endpoint_get_transport(
+ struct media_endpoint *endpoint)
+{
+ return endpoint->transport;
+}
diff --git a/audio/media.h b/audio/media.h
new file mode 100644
index 0000000..d089103
--- /dev/null
+++ b/audio/media.h
@@ -0,0 +1,54 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+struct media_endpoint;
+
+typedef void (*media_endpoint_cb_t) (struct media_endpoint *endpoint,
+ void *ret, int size, void *user_data);
+
+int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src);
+void media_unregister(const char *path);
+
+const char *media_endpoint_get_sender(struct media_endpoint *endpoint);
+
+size_t media_endpoint_get_capabilities(struct media_endpoint *endpoint,
+ uint8_t **capabilities);
+gboolean media_endpoint_set_configuration(struct media_endpoint *endpoint,
+ struct audio_device *device,
+ uint8_t *configuration, size_t size,
+ media_endpoint_cb_t cb,
+ void *user_data);
+gboolean media_endpoint_select_configuration(struct media_endpoint *endpoint,
+ uint8_t *capabilities,
+ size_t length,
+ media_endpoint_cb_t cb,
+ void *user_data);
+void media_endpoint_clear_configuration(struct media_endpoint *endpoint);
+void media_endpoint_release(struct media_endpoint *endpoint);
+
+struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint);
+const char *media_endpoint_get_uuid(struct media_endpoint *endpoint);
+uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint);
+struct media_transport *media_endpoint_get_transport(
+ struct media_endpoint *endpoint);
diff --git a/audio/pcm_bluetooth.c b/audio/pcm_bluetooth.c
new file mode 100644
index 0000000..799f17f
--- /dev/null
+++ b/audio/pcm_bluetooth.c
@@ -0,0 +1,1784 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 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 */
+ unsigned int codesize; /* SBC codesize */
+ int samples; /* Number of encoded samples */
+ uint8_t buffer[BUFFER_SIZE]; /* Codec transfer buffer */
+ unsigned 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 */
+ unsigned 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 */
+ unsigned 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 ((unsigned int) 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) {
+ if (errno != EINTR) {
+ SNDERR("poll error: %s (%d)", strerror(errno),
+ errno);
+ 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_SUGGESTED_BUFFER_SIZE];
+ struct bt_start_stream_req *req = (void *) buf;
+ struct bt_start_stream_rsp *rsp = (void *) buf;
+ struct bt_new_stream_ind *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(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+ req->h.type = BT_REQUEST;
+ req->h.name = BT_START_STREAM;
+ req->h.length = sizeof(*req);
+
+ err = audioservice_send(data->server.fd, &req->h);
+ if (err < 0)
+ return err;
+
+ rsp->h.length = sizeof(*rsp);
+ err = audioservice_expect(data->server.fd, &rsp->h,
+ BT_START_STREAM);
+ if (err < 0)
+ return err;
+
+ ind->h.length = sizeof(*ind);
+ err = audioservice_expect(data->server.fd, &ind->h,
+ BT_NEW_STREAM);
+ 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_SUGGESTED_BUFFER_SIZE];
+ struct bt_open_req *open_req = (void *) buf;
+ struct bt_open_rsp *open_rsp = (void *) buf;
+ struct bt_set_configuration_req *req = (void *) buf;
+ struct bt_set_configuration_rsp *rsp = (void *) buf;
+ int err;
+
+ DBG("Preparing with io->period_size=%lu io->buffer_size=%lu",
+ io->period_size, io->buffer_size);
+
+ memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+ open_req->h.type = BT_REQUEST;
+ open_req->h.name = BT_OPEN;
+ open_req->h.length = sizeof(*open_req);
+
+ strncpy(open_req->destination, data->alsa_config.device, 18);
+ open_req->seid = BT_A2DP_SEID_RANGE + 1;
+ open_req->lock = (io->stream == SND_PCM_STREAM_PLAYBACK ?
+ BT_WRITE_LOCK : BT_READ_LOCK);
+
+ err = audioservice_send(data->server.fd, &open_req->h);
+ if (err < 0)
+ return err;
+
+ open_rsp->h.length = sizeof(*open_rsp);
+ err = audioservice_expect(data->server.fd, &open_rsp->h,
+ BT_OPEN);
+ if (err < 0)
+ return err;
+
+ memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+ req->h.type = BT_REQUEST;
+ req->h.name = BT_SET_CONFIGURATION;
+ req->h.length = sizeof(*req);
+
+ req->codec.transport = BT_CAPABILITIES_TRANSPORT_SCO;
+ req->codec.seid = BT_A2DP_SEID_RANGE + 1;
+ req->codec.length = sizeof(pcm_capabilities_t);
+
+ req->h.length += req->codec.length - sizeof(req->codec);
+ err = audioservice_send(data->server.fd, &req->h);
+ if (err < 0)
+ return err;
+
+ rsp->h.length = sizeof(*rsp);
+ err = audioservice_expect(data->server.fd, &rsp->h,
+ BT_SET_CONFIGURATION);
+ if (err < 0)
+ return err;
+
+ data->transport = BT_CAPABILITIES_TRANSPORT_SCO;
+ data->link_mtu = 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_SUGGESTED_BUFFER_SIZE];
+ struct bt_open_req *open_req = (void *) buf;
+ struct bt_open_rsp *open_rsp = (void *) buf;
+ struct bt_set_configuration_req *req = (void *) buf;
+ struct bt_set_configuration_rsp *rsp = (void *) buf;
+ int err;
+
+ DBG("Preparing with io->period_size=%lu io->buffer_size=%lu",
+ io->period_size, io->buffer_size);
+
+ memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+ open_req->h.type = BT_REQUEST;
+ open_req->h.name = BT_OPEN;
+ open_req->h.length = sizeof(*open_req);
+
+ strncpy(open_req->destination, data->alsa_config.device, 18);
+ open_req->seid = a2dp->sbc_capabilities.capability.seid;
+ open_req->lock = (io->stream == SND_PCM_STREAM_PLAYBACK ?
+ BT_WRITE_LOCK : BT_READ_LOCK);
+
+ err = audioservice_send(data->server.fd, &open_req->h);
+ if (err < 0)
+ return err;
+
+ open_rsp->h.length = sizeof(*open_rsp);
+ err = audioservice_expect(data->server.fd, &open_rsp->h,
+ BT_OPEN);
+ if (err < 0)
+ return err;
+
+ err = bluetooth_a2dp_init(data, params);
+ if (err < 0)
+ return err;
+
+ memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+ req->h.type = BT_REQUEST;
+ req->h.name = BT_SET_CONFIGURATION;
+ req->h.length = sizeof(*req);
+
+ memcpy(&req->codec, &a2dp->sbc_capabilities,
+ sizeof(a2dp->sbc_capabilities));
+
+ req->codec.transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+ req->codec.length = sizeof(a2dp->sbc_capabilities);
+ req->h.length += req->codec.length - sizeof(req->codec);
+
+ err = audioservice_send(data->server.fd, &req->h);
+ if (err < 0)
+ return err;
+
+ rsp->h.length = sizeof(*rsp);
+ err = audioservice_expect(data->server.fd, &rsp->h,
+ BT_SET_CONFIGURATION);
+ if (err < 0)
+ return err;
+
+ data->transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+ data->link_mtu = 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 = (pfds[0].revents & POLLIN) ? POLLOUT : 0;
+
+ 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;
+ unsigned int frame_size = 0;
+ int nrecv;
+
+ 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,
+ io->nonblock ? MSG_DONTWAIT : 0);
+
+ if (nrecv < 0) {
+ ret = (errno == EPIPE) ? -EIO : -errno;
+ goto done;
+ }
+
+ if ((unsigned int) 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;
+ unsigned int bytes_left;
+ int frame_size, encoded;
+ ssize_t 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);
+
+ /* Calutate starting pointers */
+ frame_size = areas->step / 8;
+ bytes_left = size * frame_size;
+ buff = (uint8_t *) areas->addr +
+ (areas->first + areas->step * (offset)) / 8;
+
+ /* Check for underrun */
+ if (io->hw_ptr > io->appl_ptr) {
+ ret = bluetooth_playback_stop(io);
+ if (ret == 0)
+ ret = -EPIPE;
+ data->reset = 1;
+ return ret;
+ }
+
+ /* 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)
+ return ret;
+ }
+ }
+
+ snd_pcm_sw_params_free(swparams);
+ }
+
+ /* Check if we have any left over data from the last write */
+ if (data->count > 0) {
+ unsigned int additional_bytes_needed =
+ a2dp->codesize - data->count;
+ if (additional_bytes_needed > bytes_left)
+ goto out;
+
+ memcpy(data->buffer + data->count, buff,
+ additional_bytes_needed);
+
+ /* 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;
+ }
+
+ /* Increment a2dp buffers */
+ a2dp->count += written;
+ a2dp->frame_count++;
+ a2dp->samples += encoded / frame_size;
+ a2dp->nsamples += encoded / frame_size;
+
+ /* 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);
+ }
+
+ /* Increment up buff pointer to take into account
+ * the data processed */
+ buff += additional_bytes_needed;
+ bytes_left -= additional_bytes_needed;
+
+ /* Since data has been process mark it as zero */
+ data->count = 0;
+ }
+
+
+ /* Process this buffer in full chunks */
+ while (bytes_left >= a2dp->codesize) {
+ /* Enough data to encode (sbc wants 1k blocks) */
+ encoded = sbc_encode(&a2dp->sbc, buff, a2dp->codesize,
+ a2dp->buffer + a2dp->count,
+ sizeof(a2dp->buffer) - a2dp->count,
+ &written);
+ if (encoded <= 0) {
+ DBG("Encoding error %d", encoded);
+ goto done;
+ }
+
+ /* Increment up buff pointer to take into account
+ * the data processed */
+ buff += a2dp->codesize;
+ bytes_left -= a2dp->codesize;
+
+ /* Increment a2dp buffers */
+ a2dp->count += written;
+ a2dp->frame_count++;
+ a2dp->samples += encoded / frame_size;
+ a2dp->nsamples += encoded / frame_size;
+
+ /* 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);
+ }
+ }
+
+out:
+ /* Copy the extra to our temp buffer for the next write */
+ if (bytes_left > 0) {
+ memcpy(data->buffer + data->count, buff, bytes_left);
+ data->count += bytes_left;
+ bytes_left = 0;
+ }
+
+done:
+ DBG("returning %ld", size - bytes_left / frame_size);
+
+ return size - bytes_left / frame_size;
+}
+
+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
+ };
+ 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
+ };
+ 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;
+ uint16_t length;
+
+ length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+ DBG("sending %s:%s", bt_audio_strtype(msg->type),
+ bt_audio_strname(msg->name));
+ if (send(sk, msg, length, 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;
+ ssize_t ret;
+ const char *type, *name;
+ uint16_t length;
+
+ length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+ DBG("trying to receive msg from audio service...");
+
+ ret = recv(sk, inmsg, length, 0);
+ if (ret < 0) {
+ err = -errno;
+ SNDERR("Error receiving IPC data from bluetoothd: %s (%d)",
+ strerror(errno), errno);
+ } else if ((size_t) ret < sizeof(bt_audio_msg_header_t)) {
+ SNDERR("Too short (%d bytes) IPC packet from bluetoothd", ret);
+ err = -EINVAL;
+ } else {
+ type = bt_audio_strtype(inmsg->type);
+ name = bt_audio_strname(inmsg->name);
+ if (type && name) {
+ DBG("Received %s - %s", type, name);
+ err = 0;
+ } else {
+ err = -EINVAL;
+ SNDERR("Bogus message type %d - name %d"
+ " received from audio service",
+ inmsg->type, inmsg->name);
+ }
+
+ }
+
+ return err;
+}
+
+static int audioservice_expect(int sk, bt_audio_msg_header_t *rsp,
+ int expected_name)
+{
+ bt_audio_error_t *error;
+ int err = audioservice_recv(sk, rsp);
+
+ if (err != 0)
+ return err;
+
+ if (rsp->name != expected_name) {
+ err = -EINVAL;
+ SNDERR("Bogus message %s received while %s was expected",
+ bt_audio_strname(rsp->name),
+ bt_audio_strname(expected_name));
+ }
+
+ if (rsp->type == BT_ERROR) {
+ error = (void *) rsp;
+ SNDERR("%s failed : %s(%d)",
+ bt_audio_strname(rsp->name),
+ strerror(error->posix_errno),
+ error->posix_errno);
+ return -error->posix_errno;
+ }
+
+ return err;
+}
+
+static int bluetooth_parse_capabilities(struct bluetooth_data *data,
+ struct bt_get_capabilities_rsp *rsp)
+{
+ int bytes_left = rsp->h.length - sizeof(*rsp);
+ codec_capabilities_t *codec = (void *) rsp->data;
+
+ data->transport = codec->transport;
+
+ if (codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP)
+ return 0;
+
+ while (bytes_left > 0) {
+ if ((codec->type == BT_A2DP_SBC_SINK) &&
+ !(codec->lock & BT_WRITE_LOCK))
+ break;
+
+ bytes_left -= codec->length;
+ codec = (void *) codec + codec->length;
+ }
+
+ if (bytes_left <= 0 ||
+ codec->length != sizeof(data->a2dp.sbc_capabilities))
+ return -EINVAL;
+
+ memcpy(&data->a2dp.sbc_capabilities, codec, codec->length);
+
+ return 0;
+}
+
+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_SUGGESTED_BUFFER_SIZE];
+ struct bt_get_capabilities_req *req = (void *) buf;
+ struct bt_get_capabilities_rsp *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(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+ req->h.type = BT_REQUEST;
+ req->h.name = BT_GET_CAPABILITIES;
+ req->h.length = sizeof(*req);
+
+ if (alsa_conf->autoconnect)
+ req->flags |= BT_FLAG_AUTOCONNECT;
+ strncpy(req->destination, alsa_conf->device, 18);
+ if (alsa_conf->has_transport)
+ req->transport = alsa_conf->transport;
+ else
+ req->transport = BT_CAPABILITIES_TRANSPORT_ANY;
+
+ err = audioservice_send(data->server.fd, &req->h);
+ if (err < 0)
+ goto failed;
+
+ rsp->h.length = 0;
+ err = audioservice_expect(data->server.fd, &rsp->h,
+ BT_GET_CAPABILITIES);
+ if (err < 0)
+ goto failed;
+
+ bluetooth_parse_capabilities(data, rsp);
+
+ return 0;
+
+failed:
+ if (sk >= 0)
+ bt_audio_service_close(sk);
+ return err;
+}
+
+SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth);
+
+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:
+ if (data)
+ 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 0000000..45fddcf
--- /dev/null
+++ b/audio/rtp.h
@@ -0,0 +1,76 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 {
+ unsigned cc:4;
+ unsigned x:1;
+ unsigned p:1;
+ unsigned v:2;
+
+ unsigned pt:7;
+ unsigned m:1;
+
+ uint16_t sequence_number;
+ uint32_t timestamp;
+ uint32_t ssrc;
+ uint32_t csrc[0];
+} __attribute__ ((packed));
+
+struct rtp_payload {
+ unsigned frame_count:4;
+ unsigned rfa0:1;
+ unsigned is_last_fragment:1;
+ unsigned is_first_fragment:1;
+ unsigned is_fragmented:1;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct rtp_header {
+ unsigned v:2;
+ unsigned p:1;
+ unsigned x:1;
+ unsigned cc:4;
+
+ unsigned m:1;
+ unsigned pt:7;
+
+ uint16_t sequence_number;
+ uint32_t timestamp;
+ uint32_t ssrc;
+ uint32_t csrc[0];
+} __attribute__ ((packed));
+
+struct rtp_payload {
+ unsigned is_fragmented:1;
+ unsigned is_first_fragment:1;
+ unsigned is_last_fragment:1;
+ unsigned rfa0:1;
+ unsigned 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 0000000..2d5db18
--- /dev/null
+++ b/audio/sink.c
@@ -0,0 +1,743 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+
+#include "device.h"
+#include "avdtp.h"
+#include "media.h"
+#include "a2dp.h"
+#include "error.h"
+#include "sink.h"
+#include "dbus-common.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#define STREAM_SETUP_RETRY_TIMER 2
+
+struct pending_request {
+ DBusConnection *conn;
+ DBusMessage *msg;
+ unsigned int id;
+};
+
+struct sink {
+ struct audio_device *dev;
+ struct avdtp *session;
+ struct avdtp_stream *stream;
+ unsigned int cb_id;
+ guint retry_id;
+ avdtp_session_state_t session_state;
+ avdtp_state_t stream_state;
+ sink_state_t state;
+ struct pending_request *connect;
+ struct pending_request *disconnect;
+ DBusConnection *conn;
+};
+
+struct sink_state_callback {
+ sink_state_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+static GSList *sink_callbacks = NULL;
+
+static unsigned int avdtp_callback_id = 0;
+
+static char *str_state[] = {
+ "SINK_STATE_DISCONNECTED",
+ "SINK_STATE_CONNECTING",
+ "SINK_STATE_CONNECTED",
+ "SINK_STATE_PLAYING",
+};
+
+static const char *state2str(sink_state_t state)
+{
+ switch (state) {
+ case SINK_STATE_DISCONNECTED:
+ return "disconnected";
+ case SINK_STATE_CONNECTING:
+ return "connecting";
+ case SINK_STATE_CONNECTED:
+ return "connected";
+ case SINK_STATE_PLAYING:
+ return "playing";
+ default:
+ error("Invalid sink state %d", state);
+ return NULL;
+ }
+}
+
+static void sink_set_state(struct audio_device *dev, sink_state_t new_state)
+{
+ struct sink *sink = dev->sink;
+ const char *state_str;
+ sink_state_t old_state = sink->state;
+ GSList *l;
+
+ sink->state = new_state;
+
+ state_str = state2str(new_state);
+ if (state_str)
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE, "State",
+ DBUS_TYPE_STRING, &state_str);
+
+ DBG("State changed %s: %s -> %s", dev->path, str_state[old_state],
+ str_state[new_state]);
+
+ for (l = sink_callbacks; l != NULL; l = l->next) {
+ struct sink_state_callback *cb = l->data;
+ cb->cb(dev, old_state, new_state, cb->user_data);
+ }
+}
+
+static void avdtp_state_callback(struct audio_device *dev,
+ struct avdtp *session,
+ avdtp_session_state_t old_state,
+ avdtp_session_state_t new_state,
+ void *user_data)
+{
+ struct sink *sink = dev->sink;
+
+ if (sink == NULL)
+ return;
+
+ switch (new_state) {
+ case AVDTP_SESSION_STATE_DISCONNECTED:
+ if (sink->state != SINK_STATE_CONNECTING) {
+ gboolean value = FALSE;
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE, "Disconnected",
+ DBUS_TYPE_INVALID);
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE, "Connected",
+ DBUS_TYPE_BOOLEAN, &value);
+ }
+ sink_set_state(dev, SINK_STATE_DISCONNECTED);
+ break;
+ case AVDTP_SESSION_STATE_CONNECTING:
+ sink_set_state(dev, SINK_STATE_CONNECTING);
+ break;
+ case AVDTP_SESSION_STATE_CONNECTED:
+ break;
+ }
+
+ sink->session_state = new_state;
+}
+
+static void pending_request_free(struct audio_device *dev,
+ struct pending_request *pending)
+{
+ if (pending->conn)
+ dbus_connection_unref(pending->conn);
+ if (pending->msg)
+ dbus_message_unref(pending->msg);
+ if (pending->id)
+ a2dp_cancel(dev, pending->id);
+
+ 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;
+ gboolean value;
+
+ if (err)
+ return;
+
+ switch (new_state) {
+ case AVDTP_STATE_IDLE:
+ if (sink->disconnect) {
+ DBusMessage *reply;
+ struct pending_request *p;
+
+ p = sink->disconnect;
+ sink->disconnect = NULL;
+
+ reply = dbus_message_new_method_return(p->msg);
+ g_dbus_send_message(p->conn, reply);
+ pending_request_free(dev, 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 &&
+ sink->state == SINK_STATE_CONNECTING) {
+ value = TRUE;
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ "Connected",
+ DBUS_TYPE_INVALID);
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ "Connected",
+ DBUS_TYPE_BOOLEAN, &value);
+ } else if (old_state == AVDTP_STATE_STREAMING) {
+ value = FALSE;
+ g_dbus_emit_signal(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ "Stopped",
+ DBUS_TYPE_INVALID);
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ "Playing",
+ DBUS_TYPE_BOOLEAN, &value);
+ }
+ sink_set_state(dev, SINK_STATE_CONNECTED);
+ break;
+ case AVDTP_STATE_STREAMING:
+ value = TRUE;
+ g_dbus_emit_signal(dev->conn, dev->path, AUDIO_SINK_INTERFACE,
+ "Playing", DBUS_TYPE_INVALID);
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE, "Playing",
+ DBUS_TYPE_BOOLEAN, &value);
+ sink_set_state(dev, SINK_STATE_PLAYING);
+ break;
+ case AVDTP_STATE_CONFIGURED:
+ case AVDTP_STATE_CLOSING:
+ case AVDTP_STATE_ABORTING:
+ default:
+ break;
+ }
+
+ sink->stream_state = new_state;
+}
+
+static void error_failed(DBusConnection *conn, DBusMessage *msg,
+ const char *desc)
+{
+ DBusMessage *reply = btd_error_failed(msg, desc);
+ g_dbus_send_message(conn, reply);
+}
+
+static gboolean stream_setup_retry(gpointer user_data)
+{
+ struct sink *sink = user_data;
+ struct pending_request *pending = sink->connect;
+
+ sink->retry_id = 0;
+
+ if (sink->stream_state >= AVDTP_STATE_OPEN) {
+ DBG("Stream successfully created, after XCASE connect:connect");
+ if (pending->msg) {
+ DBusMessage *reply;
+ reply = dbus_message_new_method_return(pending->msg);
+ g_dbus_send_message(pending->conn, reply);
+ }
+ } else {
+ DBG("Stream setup failed, after XCASE connect:connect");
+ if (pending->msg)
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+ }
+
+ sink->connect = NULL;
+ pending_request_free(sink->dev, 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;
+
+ pending->id = 0;
+
+ if (stream) {
+ DBG("Stream successfully created");
+
+ if (pending->msg) {
+ DBusMessage *reply;
+ reply = dbus_message_new_method_return(pending->msg);
+ g_dbus_send_message(pending->conn, reply);
+ }
+
+ sink->connect = NULL;
+ pending_request_free(sink->dev, pending);
+
+ return;
+ }
+
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+ if (avdtp_error_category(err) == AVDTP_ERRNO
+ && avdtp_error_posix_errno(err) != EHOSTDOWN) {
+ DBG("connect:connect XCASE detected");
+ sink->retry_id = g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
+ stream_setup_retry,
+ sink);
+ } else {
+ if (pending->msg)
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+ sink->connect = NULL;
+ pending_request_free(sink->dev, pending);
+ DBG("Stream setup failed : %s", avdtp_strerror(err));
+ }
+}
+
+static void select_complete(struct avdtp *session, struct a2dp_sep *sep,
+ GSList *caps, void *user_data)
+{
+ struct sink *sink = user_data;
+ struct pending_request *pending;
+ int id;
+
+ pending = sink->connect;
+ pending->id = 0;
+
+ id = a2dp_config(session, sep, stream_setup_complete, caps, sink);
+ if (id == 0)
+ goto failed;
+
+ pending->id = id;
+ return;
+
+failed:
+ if (pending->msg)
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+ pending_request_free(sink->dev, pending);
+ sink->connect = NULL;
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+}
+
+static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err,
+ void *user_data)
+{
+ struct sink *sink = user_data;
+ struct pending_request *pending;
+ int id;
+
+ if (!sink->connect) {
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+ return;
+ }
+
+ pending = sink->connect;
+
+ if (err) {
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+ if (avdtp_error_category(err) == AVDTP_ERRNO
+ && avdtp_error_posix_errno(err) != EHOSTDOWN) {
+ DBG("connect:connect XCASE detected");
+ sink->retry_id =
+ g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
+ stream_setup_retry,
+ sink);
+ } else
+ goto failed;
+ return;
+ }
+
+ DBG("Discovery complete");
+
+ id = a2dp_select_capabilities(sink->session, AVDTP_SEP_TYPE_SINK, NULL,
+ select_complete, sink);
+ if (id == 0)
+ goto failed;
+
+ pending->id = id;
+ return;
+
+failed:
+ if (pending->msg)
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+ pending_request_free(sink->dev, pending);
+ sink->connect = NULL;
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+}
+
+gboolean sink_setup_stream(struct sink *sink, struct avdtp *session)
+{
+ if (sink->connect || sink->disconnect)
+ return FALSE;
+
+ if (session && !sink->session)
+ sink->session = avdtp_ref(session);
+
+ if (!sink->session)
+ return FALSE;
+
+ avdtp_set_auto_disconnect(sink->session, FALSE);
+
+ if (avdtp_discover(sink->session, discovery_complete, sink) < 0)
+ return FALSE;
+
+ sink->connect = g_new0(struct pending_request, 1);
+
+ return TRUE;
+}
+
+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 btd_error_failed(msg, "Unable to get a session");
+
+ if (sink->connect || sink->disconnect)
+ return btd_error_busy(msg);
+
+ if (sink->stream_state >= AVDTP_STATE_OPEN)
+ return btd_error_already_connected(msg);
+
+ if (!sink_setup_stream(sink, NULL))
+ return btd_error_failed(msg, "Failed to create a stream");
+
+ dev->auto_connect = FALSE;
+
+ pending = sink->connect;
+
+ pending->conn = dbus_connection_ref(conn);
+ pending->msg = dbus_message_ref(msg);
+
+ DBG("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 btd_error_not_connected(msg);
+
+ if (sink->connect || sink->disconnect)
+ return btd_error_busy(msg);
+
+ if (sink->stream_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, FALSE);
+ if (err < 0)
+ return btd_error_failed(msg, 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->stream_state >= AVDTP_STATE_CONFIGURED);
+
+ dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *sink_get_properties(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct sink *sink = device->sink;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *state;
+ gboolean value;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ /* Playing */
+ value = (sink->stream_state == AVDTP_STATE_STREAMING);
+ dict_append_entry(&dict, "Playing", DBUS_TYPE_BOOLEAN, &value);
+
+ /* Connected */
+ value = (sink->stream_state >= AVDTP_STATE_CONFIGURED);
+ dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+ /* State */
+ state = state2str(sink->state);
+ if (state)
+ dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ 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,
+ G_DBUS_METHOD_FLAG_DEPRECATED },
+ { "GetProperties", "", "a{sv}",sink_get_properties },
+ { NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable sink_signals[] = {
+ { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED },
+ { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED },
+ { "Playing", "", G_DBUS_SIGNAL_FLAG_DEPRECATED },
+ { "Stopped", "", G_DBUS_SIGNAL_FLAG_DEPRECATED },
+ { "PropertyChanged", "sv" },
+ { NULL, NULL }
+};
+
+static 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(dev, sink->connect);
+
+ if (sink->disconnect)
+ pending_request_free(dev, sink->disconnect);
+
+ if (sink->retry_id)
+ g_source_remove(sink->retry_id);
+
+ g_free(sink);
+ dev->sink = NULL;
+}
+
+static void path_unregister(void *data)
+{
+ struct audio_device *dev = data;
+
+ DBG("Unregistered interface %s on path %s",
+ AUDIO_SINK_INTERFACE, dev->path);
+
+ sink_free(dev);
+}
+
+void sink_unregister(struct audio_device *dev)
+{
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE);
+}
+
+struct sink *sink_init(struct audio_device *dev)
+{
+ struct sink *sink;
+
+ if (!g_dbus_register_interface(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ sink_methods, sink_signals, NULL,
+ dev, path_unregister))
+ return NULL;
+
+ DBG("Registered interface %s on path %s",
+ AUDIO_SINK_INTERFACE, dev->path);
+
+ if (avdtp_callback_id == 0)
+ avdtp_callback_id = avdtp_add_state_cb(avdtp_state_callback,
+ NULL);
+
+ sink = g_new0(struct sink, 1);
+
+ sink->dev = dev;
+
+ return sink;
+}
+
+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->stream_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;
+}
+
+gboolean sink_shutdown(struct sink *sink)
+{
+ if (!sink->session)
+ return FALSE;
+
+ avdtp_set_device_disconnect(sink->session, TRUE);
+
+ /* cancel pending connect */
+ if (sink->connect) {
+ struct pending_request *pending = sink->connect;
+
+ if (pending->msg)
+ error_failed(pending->conn, pending->msg,
+ "Stream setup failed");
+ pending_request_free(sink->dev, pending);
+ sink->connect = NULL;
+
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+
+ return TRUE;
+ }
+
+ /* disconnect already ongoing */
+ if (sink->disconnect)
+ return TRUE;
+
+ if (!sink->stream)
+ return FALSE;
+
+ if (avdtp_close(sink->session, sink->stream, FALSE) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+unsigned int sink_add_state_cb(sink_state_cb cb, void *user_data)
+{
+ struct sink_state_callback *state_cb;
+ static unsigned int id = 0;
+
+ state_cb = g_new(struct sink_state_callback, 1);
+ state_cb->cb = cb;
+ state_cb->user_data = user_data;
+ state_cb->id = ++id;
+
+ sink_callbacks = g_slist_append(sink_callbacks, state_cb);
+
+ return state_cb->id;
+}
+
+gboolean sink_remove_state_cb(unsigned int id)
+{
+ GSList *l;
+
+ for (l = sink_callbacks; l != NULL; l = l->next) {
+ struct sink_state_callback *cb = l->data;
+ if (cb && cb->id == id) {
+ sink_callbacks = g_slist_remove(sink_callbacks, cb);
+ g_free(cb);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
diff --git a/audio/sink.h b/audio/sink.h
new file mode 100644
index 0000000..7b1902b
--- /dev/null
+++ b/audio/sink.h
@@ -0,0 +1,49 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define AUDIO_SINK_INTERFACE "org.bluez.AudioSink"
+
+typedef enum {
+ SINK_STATE_DISCONNECTED,
+ SINK_STATE_CONNECTING,
+ SINK_STATE_CONNECTED,
+ SINK_STATE_PLAYING,
+} sink_state_t;
+
+typedef void (*sink_state_cb) (struct audio_device *dev,
+ sink_state_t old_state,
+ sink_state_t new_state,
+ void *user_data);
+
+unsigned int sink_add_state_cb(sink_state_cb cb, void *user_data);
+gboolean sink_remove_state_cb(unsigned int id);
+
+struct sink *sink_init(struct audio_device *dev);
+void sink_unregister(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);
+gboolean sink_setup_stream(struct sink *sink, struct avdtp *session);
+gboolean sink_shutdown(struct sink *sink);
diff --git a/audio/source.c b/audio/source.c
new file mode 100644
index 0000000..6d266f2
--- /dev/null
+++ b/audio/source.c
@@ -0,0 +1,633 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2009 Joao Paulo Rechi Vita
+ *
+ *
+ * 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 <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+
+#include "device.h"
+#include "avdtp.h"
+#include "media.h"
+#include "a2dp.h"
+#include "error.h"
+#include "source.h"
+#include "dbus-common.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#define STREAM_SETUP_RETRY_TIMER 2
+
+struct pending_request {
+ DBusConnection *conn;
+ DBusMessage *msg;
+ unsigned int id;
+};
+
+struct source {
+ struct audio_device *dev;
+ struct avdtp *session;
+ struct avdtp_stream *stream;
+ unsigned int cb_id;
+ guint retry_id;
+ avdtp_session_state_t session_state;
+ avdtp_state_t stream_state;
+ source_state_t state;
+ struct pending_request *connect;
+ struct pending_request *disconnect;
+ DBusConnection *conn;
+};
+
+struct source_state_callback {
+ source_state_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+static GSList *source_callbacks = NULL;
+
+static unsigned int avdtp_callback_id = 0;
+
+static const char *state2str(source_state_t state)
+{
+ switch (state) {
+ case SOURCE_STATE_DISCONNECTED:
+ return "disconnected";
+ case SOURCE_STATE_CONNECTING:
+ return "connecting";
+ case SOURCE_STATE_CONNECTED:
+ return "connected";
+ case SOURCE_STATE_PLAYING:
+ return "playing";
+ default:
+ error("Invalid source state %d", state);
+ return NULL;
+ }
+}
+
+static void source_set_state(struct audio_device *dev, source_state_t new_state)
+{
+ struct source *source = dev->source;
+ const char *state_str;
+ source_state_t old_state = source->state;
+ GSList *l;
+
+ source->state = new_state;
+
+ state_str = state2str(new_state);
+ if (state_str)
+ emit_property_changed(dev->conn, dev->path,
+ AUDIO_SOURCE_INTERFACE, "State",
+ DBUS_TYPE_STRING, &state_str);
+
+ for (l = source_callbacks; l != NULL; l = l->next) {
+ struct source_state_callback *cb = l->data;
+ cb->cb(dev, old_state, new_state, cb->user_data);
+ }
+}
+
+static void avdtp_state_callback(struct audio_device *dev,
+ struct avdtp *session,
+ avdtp_session_state_t old_state,
+ avdtp_session_state_t new_state,
+ void *user_data)
+{
+ struct source *source = dev->source;
+
+ if (source == NULL)
+ return;
+
+ switch (new_state) {
+ case AVDTP_SESSION_STATE_DISCONNECTED:
+ source_set_state(dev, SOURCE_STATE_DISCONNECTED);
+ break;
+ case AVDTP_SESSION_STATE_CONNECTING:
+ source_set_state(dev, SOURCE_STATE_CONNECTING);
+ break;
+ case AVDTP_SESSION_STATE_CONNECTED:
+ break;
+ }
+
+ source->session_state = new_state;
+}
+
+static void pending_request_free(struct audio_device *dev,
+ struct pending_request *pending)
+{
+ if (pending->conn)
+ dbus_connection_unref(pending->conn);
+ if (pending->msg)
+ dbus_message_unref(pending->msg);
+ if (pending->id)
+ a2dp_cancel(dev, pending->id);
+
+ 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 source *source = dev->source;
+
+ if (err)
+ return;
+
+ switch (new_state) {
+ case AVDTP_STATE_IDLE:
+ if (source->disconnect) {
+ DBusMessage *reply;
+ struct pending_request *p;
+
+ p = source->disconnect;
+ source->disconnect = NULL;
+
+ reply = dbus_message_new_method_return(p->msg);
+ g_dbus_send_message(p->conn, reply);
+ pending_request_free(dev, p);
+ }
+
+ if (source->session) {
+ avdtp_unref(source->session);
+ source->session = NULL;
+ }
+ source->stream = NULL;
+ source->cb_id = 0;
+ break;
+ case AVDTP_STATE_OPEN:
+ source_set_state(dev, SOURCE_STATE_CONNECTED);
+ break;
+ case AVDTP_STATE_STREAMING:
+ source_set_state(dev, SOURCE_STATE_PLAYING);
+ break;
+ case AVDTP_STATE_CONFIGURED:
+ case AVDTP_STATE_CLOSING:
+ case AVDTP_STATE_ABORTING:
+ default:
+ break;
+ }
+
+ source->stream_state = new_state;
+}
+
+static void error_failed(DBusConnection *conn, DBusMessage *msg,
+ const char *desc)
+{
+ DBusMessage *reply = btd_error_failed(msg, desc);
+ g_dbus_send_message(conn, reply);
+}
+
+static gboolean stream_setup_retry(gpointer user_data)
+{
+ struct source *source = user_data;
+ struct pending_request *pending = source->connect;
+
+ source->retry_id = 0;
+
+ if (source->stream_state >= AVDTP_STATE_OPEN) {
+ DBG("Stream successfully created, after XCASE connect:connect");
+ if (pending->msg) {
+ DBusMessage *reply;
+ reply = dbus_message_new_method_return(pending->msg);
+ g_dbus_send_message(pending->conn, reply);
+ }
+ } else {
+ DBG("Stream setup failed, after XCASE connect:connect");
+ if (pending->msg)
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+ }
+
+ source->connect = NULL;
+ pending_request_free(source->dev, 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 source *source = user_data;
+ struct pending_request *pending;
+
+ pending = source->connect;
+
+ pending->id = 0;
+
+ if (stream) {
+ DBG("Stream successfully created");
+
+ if (pending->msg) {
+ DBusMessage *reply;
+ reply = dbus_message_new_method_return(pending->msg);
+ g_dbus_send_message(pending->conn, reply);
+ }
+
+ source->connect = NULL;
+ pending_request_free(source->dev, pending);
+
+ return;
+ }
+
+ avdtp_unref(source->session);
+ source->session = NULL;
+ if (avdtp_error_category(err) == AVDTP_ERRNO
+ && avdtp_error_posix_errno(err) != EHOSTDOWN) {
+ DBG("connect:connect XCASE detected");
+ source->retry_id = g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
+ stream_setup_retry,
+ source);
+ } else {
+ if (pending->msg)
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+ source->connect = NULL;
+ pending_request_free(source->dev, pending);
+ DBG("Stream setup failed : %s", avdtp_strerror(err));
+ }
+}
+
+static void select_complete(struct avdtp *session, struct a2dp_sep *sep,
+ GSList *caps, void *user_data)
+{
+ struct source *source = user_data;
+ struct pending_request *pending;
+ int id;
+
+ pending = source->connect;
+
+ pending->id = 0;
+
+ if (caps == NULL)
+ goto failed;
+
+ id = a2dp_config(session, sep, stream_setup_complete, caps, source);
+ if (id == 0)
+ goto failed;
+
+ pending->id = id;
+ return;
+
+failed:
+ if (pending->msg)
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+ pending_request_free(source->dev, pending);
+ source->connect = NULL;
+ avdtp_unref(source->session);
+ source->session = NULL;
+}
+
+static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err,
+ void *user_data)
+{
+ struct source *source = user_data;
+ struct pending_request *pending;
+ int id;
+
+ pending = source->connect;
+
+ if (err) {
+ avdtp_unref(source->session);
+ source->session = NULL;
+ if (avdtp_error_category(err) == AVDTP_ERRNO
+ && avdtp_error_posix_errno(err) != EHOSTDOWN) {
+ DBG("connect:connect XCASE detected");
+ source->retry_id =
+ g_timeout_add_seconds(STREAM_SETUP_RETRY_TIMER,
+ stream_setup_retry,
+ source);
+ } else
+ goto failed;
+ return;
+ }
+
+ DBG("Discovery complete");
+
+ id = a2dp_select_capabilities(source->session, AVDTP_SEP_TYPE_SOURCE, NULL,
+ select_complete, source);
+ if (id == 0)
+ goto failed;
+
+ pending->id = id;
+ return;
+
+failed:
+ if (pending->msg)
+ error_failed(pending->conn, pending->msg, "Stream setup failed");
+ pending_request_free(source->dev, pending);
+ source->connect = NULL;
+ avdtp_unref(source->session);
+ source->session = NULL;
+}
+
+gboolean source_setup_stream(struct source *source, struct avdtp *session)
+{
+ if (source->connect || source->disconnect)
+ return FALSE;
+
+ if (session && !source->session)
+ source->session = avdtp_ref(session);
+
+ if (!source->session)
+ return FALSE;
+
+ avdtp_set_auto_disconnect(source->session, FALSE);
+
+ if (avdtp_discover(source->session, discovery_complete, source) < 0)
+ return FALSE;
+
+ source->connect = g_new0(struct pending_request, 1);
+
+ return TRUE;
+}
+
+static DBusMessage *source_connect(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *dev = data;
+ struct source *source = dev->source;
+ struct pending_request *pending;
+
+ if (!source->session)
+ source->session = avdtp_get(&dev->src, &dev->dst);
+
+ if (!source->session)
+ return btd_error_failed(msg, "Unable to get a session");
+
+ if (source->connect || source->disconnect)
+ return btd_error_busy(msg);
+
+ if (source->stream_state >= AVDTP_STATE_OPEN)
+ return btd_error_already_connected(msg);
+
+ if (!source_setup_stream(source, NULL))
+ return btd_error_failed(msg, "Failed to create a stream");
+
+ dev->auto_connect = FALSE;
+
+ pending = source->connect;
+
+ pending->conn = dbus_connection_ref(conn);
+ pending->msg = dbus_message_ref(msg);
+
+ DBG("stream creation in progress");
+
+ return NULL;
+}
+
+static DBusMessage *source_disconnect(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct source *source = device->source;
+ struct pending_request *pending;
+ int err;
+
+ if (!source->session)
+ return btd_error_not_connected(msg);
+
+ if (source->connect || source->disconnect)
+ return btd_error_busy(msg);
+
+ if (source->stream_state < AVDTP_STATE_OPEN) {
+ DBusMessage *reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+ avdtp_unref(source->session);
+ source->session = NULL;
+ return reply;
+ }
+
+ err = avdtp_close(source->session, source->stream, FALSE);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ pending = g_new0(struct pending_request, 1);
+ pending->conn = dbus_connection_ref(conn);
+ pending->msg = dbus_message_ref(msg);
+ source->disconnect = pending;
+
+ return NULL;
+}
+
+static DBusMessage *source_get_properties(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct source *source = device->source;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ const char *state;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ /* State */
+ state = state2str(source->state);
+ if (state)
+ dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static GDBusMethodTable source_methods[] = {
+ { "Connect", "", "", source_connect,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "Disconnect", "", "", source_disconnect,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "GetProperties", "", "a{sv}",source_get_properties },
+ { NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable source_signals[] = {
+ { "PropertyChanged", "sv" },
+ { NULL, NULL }
+};
+
+static void source_free(struct audio_device *dev)
+{
+ struct source *source = dev->source;
+
+ if (source->cb_id)
+ avdtp_stream_remove_cb(source->session, source->stream,
+ source->cb_id);
+
+ if (source->session)
+ avdtp_unref(source->session);
+
+ if (source->connect)
+ pending_request_free(dev, source->connect);
+
+ if (source->disconnect)
+ pending_request_free(dev, source->disconnect);
+
+ if (source->retry_id)
+ g_source_remove(source->retry_id);
+
+ g_free(source);
+ dev->source = NULL;
+}
+
+static void path_unregister(void *data)
+{
+ struct audio_device *dev = data;
+
+ DBG("Unregistered interface %s on path %s",
+ AUDIO_SOURCE_INTERFACE, dev->path);
+
+ source_free(dev);
+}
+
+void source_unregister(struct audio_device *dev)
+{
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ AUDIO_SOURCE_INTERFACE);
+}
+
+struct source *source_init(struct audio_device *dev)
+{
+ struct source *source;
+
+ if (!g_dbus_register_interface(dev->conn, dev->path,
+ AUDIO_SOURCE_INTERFACE,
+ source_methods, source_signals, NULL,
+ dev, path_unregister))
+ return NULL;
+
+ DBG("Registered interface %s on path %s",
+ AUDIO_SOURCE_INTERFACE, dev->path);
+
+ if (avdtp_callback_id == 0)
+ avdtp_callback_id = avdtp_add_state_cb(avdtp_state_callback,
+ NULL);
+
+ source = g_new0(struct source, 1);
+
+ source->dev = dev;
+
+ return source;
+}
+
+gboolean source_is_active(struct audio_device *dev)
+{
+ struct source *source = dev->source;
+
+ if (source->session)
+ return TRUE;
+
+ return FALSE;
+}
+
+avdtp_state_t source_get_state(struct audio_device *dev)
+{
+ struct source *source = dev->source;
+
+ return source->stream_state;
+}
+
+gboolean source_new_stream(struct audio_device *dev, struct avdtp *session,
+ struct avdtp_stream *stream)
+{
+ struct source *source = dev->source;
+
+ if (source->stream)
+ return FALSE;
+
+ if (!source->session)
+ source->session = avdtp_ref(session);
+
+ source->stream = stream;
+
+ source->cb_id = avdtp_stream_add_cb(session, stream,
+ stream_state_changed, dev);
+
+ return TRUE;
+}
+
+gboolean source_shutdown(struct source *source)
+{
+ if (!source->stream)
+ return FALSE;
+
+ if (avdtp_close(source->session, source->stream, FALSE) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+unsigned int source_add_state_cb(source_state_cb cb, void *user_data)
+{
+ struct source_state_callback *state_cb;
+ static unsigned int id = 0;
+
+ state_cb = g_new(struct source_state_callback, 1);
+ state_cb->cb = cb;
+ state_cb->user_data = user_data;
+ state_cb->id = ++id;
+
+ source_callbacks = g_slist_append(source_callbacks, state_cb);
+
+ return state_cb->id;
+}
+
+gboolean source_remove_state_cb(unsigned int id)
+{
+ GSList *l;
+
+ for (l = source_callbacks; l != NULL; l = l->next) {
+ struct source_state_callback *cb = l->data;
+ if (cb && cb->id == id) {
+ source_callbacks = g_slist_remove(source_callbacks, cb);
+ g_free(cb);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
diff --git a/audio/source.h b/audio/source.h
new file mode 100644
index 0000000..7837284
--- /dev/null
+++ b/audio/source.h
@@ -0,0 +1,50 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2009 Joao Paulo Rechi Vita
+ *
+ *
+ * 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_SOURCE_INTERFACE "org.bluez.AudioSource"
+
+typedef enum {
+ SOURCE_STATE_DISCONNECTED,
+ SOURCE_STATE_CONNECTING,
+ SOURCE_STATE_CONNECTED,
+ SOURCE_STATE_PLAYING,
+} source_state_t;
+
+typedef void (*source_state_cb) (struct audio_device *dev,
+ source_state_t old_state,
+ source_state_t new_state,
+ void *user_data);
+
+unsigned int source_add_state_cb(source_state_cb cb, void *user_data);
+gboolean source_remove_state_cb(unsigned int id);
+
+struct source *source_init(struct audio_device *dev);
+void source_unregister(struct audio_device *dev);
+gboolean source_is_active(struct audio_device *dev);
+avdtp_state_t source_get_state(struct audio_device *dev);
+gboolean source_new_stream(struct audio_device *dev, struct avdtp *session,
+ struct avdtp_stream *stream);
+gboolean source_setup_stream(struct source *source, struct avdtp *session);
+gboolean source_shutdown(struct source *source);
diff --git a/audio/telephony-dummy.c b/audio/telephony-dummy.c
new file mode 100644
index 0000000..1f89079
--- /dev/null
+++ b/audio/telephony-dummy.c
@@ -0,0 +1,433 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "telephony.h"
+#include "error.h"
+
+#define TELEPHONY_DUMMY_IFACE "org.bluez.TelephonyTest"
+#define TELEPHONY_DUMMY_PATH "/org/bluez/test"
+
+static DBusConnection *connection = NULL;
+
+static const char *chld_str = "0,1,1x,2,2x,3,4";
+static char *subscriber_number = NULL;
+static char *active_call_number = NULL;
+static int active_call_status = 0;
+static int active_call_dir = 0;
+
+static gboolean events_enabled = FALSE;
+
+static struct indicator dummy_indicators[] =
+{
+ { "battchg", "0-5", 5, TRUE },
+ { "signal", "0-5", 5, TRUE },
+ { "service", "0,1", 1, TRUE },
+ { "call", "0,1", 0, TRUE },
+ { "callsetup", "0-3", 0, TRUE },
+ { "callheld", "0-2", 0, FALSE },
+ { "roam", "0,1", 0, TRUE },
+ { NULL }
+};
+
+void telephony_device_connected(void *telephony_device)
+{
+ DBG("telephony-dummy: device %p connected", telephony_device);
+}
+
+void telephony_device_disconnected(void *telephony_device)
+{
+ DBG("telephony-dummy: device %p disconnected", telephony_device);
+ events_enabled = FALSE;
+}
+
+void telephony_event_reporting_req(void *telephony_device, int ind)
+{
+ events_enabled = ind == 1 ? TRUE : FALSE;
+
+ telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_response_and_hold_req(void *telephony_device, int rh)
+{
+ telephony_response_and_hold_rsp(telephony_device,
+ CME_ERROR_NOT_SUPPORTED);
+}
+
+void telephony_last_dialed_number_req(void *telephony_device)
+{
+ telephony_last_dialed_number_rsp(telephony_device, CME_ERROR_NONE);
+
+ /* Notify outgoing call set-up successfully initiated */
+ telephony_update_indicator(dummy_indicators, "callsetup",
+ EV_CALLSETUP_OUTGOING);
+ telephony_update_indicator(dummy_indicators, "callsetup",
+ EV_CALLSETUP_ALERTING);
+
+ active_call_status = CALL_STATUS_ALERTING;
+ active_call_dir = CALL_DIR_OUTGOING;
+}
+
+void telephony_terminate_call_req(void *telephony_device)
+{
+ g_free(active_call_number);
+ active_call_number = NULL;
+
+ telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE);
+
+ if (telephony_get_indicator(dummy_indicators, "callsetup") > 0)
+ telephony_update_indicator(dummy_indicators, "callsetup",
+ EV_CALLSETUP_INACTIVE);
+ else
+ telephony_update_indicator(dummy_indicators, "call",
+ EV_CALL_INACTIVE);
+}
+
+void telephony_answer_call_req(void *telephony_device)
+{
+ telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
+
+ telephony_update_indicator(dummy_indicators, "call", EV_CALL_ACTIVE);
+ telephony_update_indicator(dummy_indicators, "callsetup",
+ EV_CALLSETUP_INACTIVE);
+
+ active_call_status = CALL_STATUS_ACTIVE;
+}
+
+void telephony_dial_number_req(void *telephony_device, const char *number)
+{
+ g_free(active_call_number);
+ active_call_number = g_strdup(number);
+
+ DBG("telephony-dummy: dial request to %s", active_call_number);
+
+ telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);
+
+ /* Notify outgoing call set-up successfully initiated */
+ telephony_update_indicator(dummy_indicators, "callsetup",
+ EV_CALLSETUP_OUTGOING);
+ telephony_update_indicator(dummy_indicators, "callsetup",
+ EV_CALLSETUP_ALERTING);
+
+ active_call_status = CALL_STATUS_ALERTING;
+ active_call_dir = CALL_DIR_OUTGOING;
+}
+
+void telephony_transmit_dtmf_req(void *telephony_device, char tone)
+{
+ DBG("telephony-dummy: transmit dtmf: %c", tone);
+ telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_subscriber_number_req(void *telephony_device)
+{
+ DBG("telephony-dummy: subscriber number request");
+ if (subscriber_number)
+ telephony_subscriber_number_ind(subscriber_number,
+ NUMBER_TYPE_TELEPHONY,
+ SUBSCRIBER_SERVICE_VOICE);
+ telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_list_current_calls_req(void *telephony_device)
+{
+ DBG("telephony-dummy: list current calls request");
+ if (active_call_number)
+ telephony_list_current_call_ind(1, active_call_dir,
+ active_call_status,
+ CALL_MODE_VOICE,
+ CALL_MULTIPARTY_NO,
+ active_call_number,
+ NUMBER_TYPE_TELEPHONY);
+ telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_operator_selection_req(void *telephony_device)
+{
+ telephony_operator_selection_ind(OPERATOR_MODE_AUTO, "DummyOperator");
+ telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_call_hold_req(void *telephony_device, const char *cmd)
+{
+ DBG("telephony-dymmy: got call hold request %s", cmd);
+ telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_nr_and_ec_req(void *telephony_device, gboolean enable)
+{
+ DBG("telephony-dummy: got %s NR and EC request",
+ enable ? "enable" : "disable");
+
+ telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_voice_dial_req(void *telephony_device, gboolean enable)
+{
+ DBG("telephony-dummy: got %s voice dial request",
+ enable ? "enable" : "disable");
+
+ g_dbus_emit_signal(connection, TELEPHONY_DUMMY_PATH,
+ TELEPHONY_DUMMY_IFACE, "VoiceDial",
+ DBUS_TYPE_INVALID);
+
+ telephony_voice_dial_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_key_press_req(void *telephony_device, const char *keys)
+{
+ DBG("telephony-dummy: got key press request for %s", keys);
+ telephony_key_press_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+/* D-Bus method handlers */
+static DBusMessage *outgoing_call(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ const char *number;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ DBG("telephony-dummy: outgoing call to %s", number);
+
+ g_free(active_call_number);
+ active_call_number = g_strdup(number);
+
+ telephony_update_indicator(dummy_indicators, "callsetup",
+ EV_CALLSETUP_OUTGOING);
+ telephony_update_indicator(dummy_indicators, "callsetup",
+ EV_CALLSETUP_ALERTING);
+
+ active_call_status = CALL_STATUS_ALERTING;
+ active_call_dir = CALL_DIR_OUTGOING;
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *incoming_call(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ const char *number;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ DBG("telephony-dummy: incoming call to %s", number);
+
+ g_free(active_call_number);
+ active_call_number = g_strdup(number);
+
+ telephony_update_indicator(dummy_indicators, "callsetup",
+ EV_CALLSETUP_INCOMING);
+
+ active_call_status = CALL_STATUS_INCOMING;
+ active_call_dir = CALL_DIR_INCOMING;
+
+ telephony_incoming_call_ind(number, NUMBER_TYPE_TELEPHONY);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *cancel_call(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ DBG("telephony-dummy: cancel call");
+
+ g_free(active_call_number);
+ active_call_number = NULL;
+
+ if (telephony_get_indicator(dummy_indicators, "callsetup") > 0) {
+ telephony_update_indicator(dummy_indicators, "callsetup",
+ EV_CALLSETUP_INACTIVE);
+ telephony_calling_stopped_ind();
+ }
+
+ if (telephony_get_indicator(dummy_indicators, "call") > 0)
+ telephony_update_indicator(dummy_indicators, "call",
+ EV_CALL_INACTIVE);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *signal_strength(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ dbus_uint32_t strength;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &strength,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ if (strength > 5)
+ return btd_error_invalid_args(msg);
+
+ telephony_update_indicator(dummy_indicators, "signal", strength);
+
+ DBG("telephony-dummy: signal strength set to %u", strength);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *battery_level(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ dbus_uint32_t level;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &level,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ if (level > 5)
+ return btd_error_invalid_args(msg);
+
+ telephony_update_indicator(dummy_indicators, "battchg", level);
+
+ DBG("telephony-dummy: battery level set to %u", level);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *roaming_status(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ dbus_bool_t roaming;
+ int val;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &roaming,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ val = roaming ? EV_ROAM_ACTIVE : EV_ROAM_INACTIVE;
+
+ telephony_update_indicator(dummy_indicators, "roam", val);
+
+ DBG("telephony-dummy: roaming status set to %d", val);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *registration_status(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ dbus_bool_t registration;
+ int val;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &registration,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ val = registration ? EV_SERVICE_PRESENT : EV_SERVICE_NONE;
+
+ telephony_update_indicator(dummy_indicators, "service", val);
+
+ DBG("telephony-dummy: registration status set to %d", val);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *set_subscriber_number(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ const char *number;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ g_free(subscriber_number);
+ subscriber_number = g_strdup(number);
+
+ DBG("telephony-dummy: subscriber number set to %s", number);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static GDBusMethodTable dummy_methods[] = {
+ { "OutgoingCall", "s", "", outgoing_call },
+ { "IncomingCall", "s", "", incoming_call },
+ { "CancelCall", "", "", cancel_call },
+ { "SignalStrength", "u", "", signal_strength },
+ { "BatteryLevel", "u", "", battery_level },
+ { "RoamingStatus", "b", "", roaming_status },
+ { "RegistrationStatus", "b", "", registration_status },
+ { "SetSubscriberNumber","s", "", set_subscriber_number },
+ { }
+};
+
+static GDBusSignalTable dummy_signals[] = {
+ { "VoiceDial", "" },
+ { }
+};
+
+int telephony_init(void)
+{
+ uint32_t features = AG_FEATURE_REJECT_A_CALL |
+ AG_FEATURE_ENHANCED_CALL_STATUS |
+ AG_FEATURE_EXTENDED_ERROR_RESULT_CODES;
+
+ DBG("");
+
+ connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+ if (g_dbus_register_interface(connection, TELEPHONY_DUMMY_PATH,
+ TELEPHONY_DUMMY_IFACE,
+ dummy_methods, dummy_signals,
+ NULL, NULL, NULL) == FALSE) {
+ error("telephony-dummy interface %s init failed on path %s",
+ TELEPHONY_DUMMY_IFACE, TELEPHONY_DUMMY_PATH);
+ return -1;
+ }
+
+ telephony_ready_ind(features, dummy_indicators, BTRH_NOT_SUPPORTED,
+ chld_str);
+
+ return 0;
+}
+
+void telephony_exit(void)
+{
+ DBG("");
+
+ g_dbus_unregister_interface(connection, TELEPHONY_DUMMY_PATH,
+ TELEPHONY_DUMMY_IFACE);
+ dbus_connection_unref(connection);
+ connection = NULL;
+
+ telephony_deinit();
+}
diff --git a/audio/telephony-maemo5.c b/audio/telephony-maemo5.c
new file mode 100644
index 0000000..350df9e
--- /dev/null
+++ b/audio/telephony-maemo5.c
@@ -0,0 +1,2104 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2008-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "telephony.h"
+#include "error.h"
+
+/* SSC D-Bus definitions */
+#define SSC_DBUS_NAME "com.nokia.phone.SSC"
+#define SSC_DBUS_IFACE "com.nokia.phone.SSC"
+#define SSC_DBUS_PATH "/com/nokia/phone/SSC"
+
+/* libcsnet D-Bus definitions */
+#define NETWORK_BUS_NAME "com.nokia.phone.net"
+#define NETWORK_INTERFACE "Phone.Net"
+#define NETWORK_PATH "/com/nokia/phone/net"
+
+/* Mask bits for supported services */
+#define NETWORK_MASK_GPRS_SUPPORT 0x01
+#define NETWORK_MASK_CS_SERVICES 0x02
+#define NETWORK_MASK_EGPRS_SUPPORT 0x04
+#define NETWORK_MASK_HSDPA_AVAIL 0x08
+#define NETWORK_MASK_HSUPA_AVAIL 0x10
+
+/* network get cell info: cell type */
+#define NETWORK_UNKNOWN_CELL 0
+#define NETWORK_GSM_CELL 1
+#define NETWORK_WCDMA_CELL 2
+
+enum net_registration_status {
+ NETWORK_REG_STATUS_HOME = 0x00,
+ NETWORK_REG_STATUS_ROAM,
+ NETWORK_REG_STATUS_ROAM_BLINK,
+ NETWORK_REG_STATUS_NOSERV,
+ NETWORK_REG_STATUS_NOSERV_SEARCHING,
+ NETWORK_REG_STATUS_NOSERV_NOTSEARCHING,
+ NETWORK_REG_STATUS_NOSERV_NOSIM,
+ NETWORK_REG_STATUS_POWER_OFF = 0x08,
+ NETWORK_REG_STATUS_NSPS,
+ NETWORK_REG_STATUS_NSPS_NO_COVERAGE,
+ NETWORK_REG_STATUS_NOSERV_SIM_REJECTED_BY_NW
+};
+
+enum network_types {
+ NETWORK_GSM_HOME_PLMN = 0,
+ NETWORK_GSM_PREFERRED_PLMN,
+ NETWORK_GSM_FORBIDDEN_PLMN,
+ NETWORK_GSM_OTHER_PLMN,
+ NETWORK_GSM_NO_PLMN_AVAIL
+};
+
+enum network_alpha_tag_name_type {
+ NETWORK_HARDCODED_LATIN_OPER_NAME = 0,
+ NETWORK_HARDCODED_USC2_OPER_NAME,
+ NETWORK_NITZ_SHORT_OPER_NAME,
+ NETWORK_NITZ_FULL_OPER_NAME,
+};
+
+#define TELEPHONY_MAEMO_PATH "/com/nokia/MaemoTelephony"
+#define TELEPHONY_MAEMO_INTERFACE "com.nokia.MaemoTelephony"
+
+#define CALLERID_BASE "/var/lib/bluetooth/maemo-callerid-"
+#define ALLOWED_FLAG_FILE "/var/lib/bluetooth/maemo-callerid-allowed"
+#define RESTRICTED_FLAG_FILE "/var/lib/bluetooth/maemo-callerid-restricted"
+#define NONE_FLAG_FILE "/var/lib/bluetooth/maemo-callerid-none"
+
+static uint32_t callerid = 0;
+
+/* CSD CALL plugin D-Bus definitions */
+#define CSD_CALL_BUS_NAME "com.nokia.csd.Call"
+#define CSD_CALL_INTERFACE "com.nokia.csd.Call"
+#define CSD_CALL_INSTANCE "com.nokia.csd.Call.Instance"
+#define CSD_CALL_CONFERENCE "com.nokia.csd.Call.Conference"
+#define CSD_CALL_PATH "/com/nokia/csd/call"
+#define CSD_CALL_CONFERENCE_PATH "/com/nokia/csd/call/conference"
+
+/* Call status values as exported by the CSD CALL plugin */
+#define CSD_CALL_STATUS_IDLE 0
+#define CSD_CALL_STATUS_CREATE 1
+#define CSD_CALL_STATUS_COMING 2
+#define CSD_CALL_STATUS_PROCEEDING 3
+#define CSD_CALL_STATUS_MO_ALERTING 4
+#define CSD_CALL_STATUS_MT_ALERTING 5
+#define CSD_CALL_STATUS_WAITING 6
+#define CSD_CALL_STATUS_ANSWERED 7
+#define CSD_CALL_STATUS_ACTIVE 8
+#define CSD_CALL_STATUS_MO_RELEASE 9
+#define CSD_CALL_STATUS_MT_RELEASE 10
+#define CSD_CALL_STATUS_HOLD_INITIATED 11
+#define CSD_CALL_STATUS_HOLD 12
+#define CSD_CALL_STATUS_RETRIEVE_INITIATED 13
+#define CSD_CALL_STATUS_RECONNECT_PENDING 14
+#define CSD_CALL_STATUS_TERMINATED 15
+#define CSD_CALL_STATUS_SWAP_INITIATED 16
+
+#define CALL_FLAG_NONE 0
+#define CALL_FLAG_PRESENTATION_ALLOWED 0x01
+#define CALL_FLAG_PRESENTATION_RESTRICTED 0x02
+
+/* SIM Phonebook D-Bus definitions */
+#define SIM_PHONEBOOK_BUS_NAME "com.nokia.phone.SIM"
+#define SIM_PHONEBOOK_INTERFACE "Phone.Sim.Phonebook"
+#define SIM_PHONEBOOK_PATH "/com/nokia/phone/SIM/phonebook"
+
+#define PHONEBOOK_INDEX_FIRST_ENTRY 0xFFFF
+#define PHONEBOOK_INDEX_NEXT_FREE_LOCATION 0xFFFE
+
+enum sim_phonebook_type {
+ SIM_PHONEBOOK_TYPE_ADN = 0x0,
+ SIM_PHONEBOOK_TYPE_SDN,
+ SIM_PHONEBOOK_TYPE_FDN,
+ SIM_PHONEBOOK_TYPE_VMBX,
+ SIM_PHONEBOOK_TYPE_MBDN,
+ SIM_PHONEBOOK_TYPE_EN,
+ SIM_PHONEBOOK_TYPE_MSISDN
+};
+
+enum sim_phonebook_location_type {
+ SIM_PHONEBOOK_LOCATION_EXACT = 0x0,
+ SIM_PHONEBOOK_LOCATION_NEXT
+};
+
+struct csd_call {
+ char *object_path;
+ int status;
+ gboolean originating;
+ gboolean emergency;
+ gboolean on_hold;
+ gboolean conference;
+ char *number;
+ gboolean setup;
+};
+
+static struct {
+ uint8_t status;
+ uint16_t lac;
+ uint32_t cell_id;
+ uint32_t operator_code;
+ uint32_t country_code;
+ uint8_t network_type;
+ uint8_t supported_services;
+ uint16_t signals_bar;
+ char *operator_name;
+} net = {
+ .status = NETWORK_REG_STATUS_NOSERV,
+ .lac = 0,
+ .cell_id = 0,
+ .operator_code = 0,
+ .country_code = 0,
+ .network_type = NETWORK_GSM_NO_PLMN_AVAIL,
+ .supported_services = 0,
+ .signals_bar = 0,
+ .operator_name = NULL,
+};
+
+static DBusConnection *connection = NULL;
+
+static GSList *calls = NULL;
+
+/* Reference count for determining the call indicator status */
+static GSList *active_calls = NULL;
+
+static char *msisdn = NULL; /* Subscriber number */
+static char *vmbx = NULL; /* Voice mailbox number */
+
+/* HAL battery namespace key values */
+static int battchg_cur = -1; /* "battery.charge_level.current" */
+static int battchg_last = -1; /* "battery.charge_level.last_full" */
+static int battchg_design = -1; /* "battery.charge_level.design" */
+
+static gboolean get_calls_active = FALSE;
+
+static gboolean events_enabled = FALSE;
+
+/* Supported set of call hold operations */
+static const char *chld_str = "0,1,1x,2,2x,3,4";
+
+static char *last_dialed_number = NULL;
+
+/* Timer for tracking call creation requests */
+static guint create_request_timer = 0;
+
+static struct indicator maemo_indicators[] =
+{
+ { "battchg", "0-5", 5, TRUE },
+ { "signal", "0-5", 0, TRUE },
+ { "service", "0,1", 0, TRUE },
+ { "call", "0,1", 0, TRUE },
+ { "callsetup", "0-3", 0, TRUE },
+ { "callheld", "0-2", 0, FALSE },
+ { "roam", "0,1", 0, TRUE },
+ { NULL }
+};
+
+static char *call_status_str[] = {
+ "IDLE",
+ "CREATE",
+ "COMING",
+ "PROCEEDING",
+ "MO_ALERTING",
+ "MT_ALERTING",
+ "WAITING",
+ "ANSWERED",
+ "ACTIVE",
+ "MO_RELEASE",
+ "MT_RELEASE",
+ "HOLD_INITIATED",
+ "HOLD",
+ "RETRIEVE_INITIATED",
+ "RECONNECT_PENDING",
+ "TERMINATED",
+ "SWAP_INITIATED",
+ "???"
+};
+
+static struct csd_call *find_call(const char *path)
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct csd_call *call = l->data;
+
+ if (g_str_equal(call->object_path, path))
+ return call;
+ }
+
+ return NULL;
+}
+
+static struct csd_call *find_non_held_call(void)
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct csd_call *call = l->data;
+
+ if (call->status == CSD_CALL_STATUS_IDLE)
+ continue;
+
+ if (call->status != CSD_CALL_STATUS_HOLD)
+ return call;
+ }
+
+ return NULL;
+}
+
+static struct csd_call *find_non_idle_call(void)
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct csd_call *call = l->data;
+
+ if (call->status != CSD_CALL_STATUS_IDLE)
+ return call;
+ }
+
+ return NULL;
+}
+
+static struct csd_call *find_call_with_status(int status)
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct csd_call *call = l->data;
+
+ if (call->status == status)
+ return call;
+ }
+
+ return NULL;
+}
+
+static int release_conference(void)
+{
+ DBusMessage *msg;
+
+ DBG("telephony-maemo: releasing conference call");
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+ CSD_CALL_CONFERENCE_PATH,
+ CSD_CALL_INSTANCE,
+ "Release");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int release_call(struct csd_call *call)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+ call->object_path,
+ CSD_CALL_INSTANCE,
+ "Release");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int answer_call(struct csd_call *call)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+ call->object_path,
+ CSD_CALL_INSTANCE,
+ "Answer");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int split_call(struct csd_call *call)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+ call->object_path,
+ CSD_CALL_INSTANCE,
+ "Split");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int unhold_call(struct csd_call *call)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE,
+ "Unhold");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int hold_call(struct csd_call *call)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE,
+ "Hold");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int swap_calls(void)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE,
+ "Swap");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int create_conference(void)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE,
+ "Conference");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int call_transfer(void)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE,
+ "Transfer");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int number_type(const char *number)
+{
+ if (number == NULL)
+ return NUMBER_TYPE_TELEPHONY;
+
+ if (number[0] == '+' || strncmp(number, "00", 2) == 0)
+ return NUMBER_TYPE_INTERNATIONAL;
+
+ return NUMBER_TYPE_TELEPHONY;
+}
+
+void telephony_device_connected(void *telephony_device)
+{
+ struct csd_call *coming;
+
+ DBG("telephony-maemo: device %p connected", telephony_device);
+
+ coming = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING);
+ if (coming) {
+ if (find_call_with_status(CSD_CALL_STATUS_ACTIVE))
+ telephony_call_waiting_ind(coming->number,
+ number_type(coming->number));
+ else
+ telephony_incoming_call_ind(coming->number,
+ number_type(coming->number));
+ }
+}
+
+void telephony_device_disconnected(void *telephony_device)
+{
+ DBG("telephony-maemo: device %p disconnected", telephony_device);
+ events_enabled = FALSE;
+}
+
+void telephony_event_reporting_req(void *telephony_device, int ind)
+{
+ events_enabled = ind == 1 ? TRUE : FALSE;
+
+ telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_response_and_hold_req(void *telephony_device, int rh)
+{
+ telephony_response_and_hold_rsp(telephony_device,
+ CME_ERROR_NOT_SUPPORTED);
+}
+
+void telephony_last_dialed_number_req(void *telephony_device)
+{
+ DBG("telephony-maemo: last dialed number request");
+
+ if (last_dialed_number)
+ telephony_dial_number_req(telephony_device,
+ last_dialed_number);
+ else
+ telephony_last_dialed_number_rsp(telephony_device,
+ CME_ERROR_NOT_ALLOWED);
+}
+
+void telephony_terminate_call_req(void *telephony_device)
+{
+ struct csd_call *call;
+ int err;
+
+ call = find_call_with_status(CSD_CALL_STATUS_ACTIVE);
+ if (!call)
+ call = find_non_idle_call();
+
+ if (!call) {
+ error("No active call");
+ telephony_terminate_call_rsp(telephony_device,
+ CME_ERROR_NOT_ALLOWED);
+ return;
+ }
+
+ if (call->conference)
+ err = release_conference();
+ else
+ err = release_call(call);
+
+ if (err < 0)
+ telephony_terminate_call_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_answer_call_req(void *telephony_device)
+{
+ struct csd_call *call;
+
+ call = find_call_with_status(CSD_CALL_STATUS_COMING);
+ if (!call)
+ call = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING);
+
+ if (!call)
+ call = find_call_with_status(CSD_CALL_STATUS_PROCEEDING);
+
+ if (!call)
+ call = find_call_with_status(CSD_CALL_STATUS_WAITING);
+
+ if (!call) {
+ telephony_answer_call_rsp(telephony_device,
+ CME_ERROR_NOT_ALLOWED);
+ return;
+ }
+
+ if (answer_call(call) < 0)
+ telephony_answer_call_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static int send_method_call(const char *dest, const char *path,
+ const char *interface, const char *method,
+ DBusPendingCallNotifyFunction cb,
+ void *user_data, int type, ...)
+{
+ DBusMessage *msg;
+ DBusPendingCall *call;
+ va_list args;
+
+ msg = dbus_message_new_method_call(dest, path, interface, method);
+ if (!msg) {
+ error("Unable to allocate new D-Bus %s message", method);
+ return -ENOMEM;
+ }
+
+ va_start(args, type);
+
+ if (!dbus_message_append_args_valist(msg, type, args)) {
+ dbus_message_unref(msg);
+ va_end(args);
+ return -EIO;
+ }
+
+ va_end(args);
+
+ if (!cb) {
+ g_dbus_send_message(connection, msg);
+ return 0;
+ }
+
+ if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) {
+ error("Sending %s failed", method);
+ dbus_message_unref(msg);
+ return -EIO;
+ }
+
+ dbus_pending_call_set_notify(call, cb, user_data, NULL);
+ dbus_pending_call_unref(call);
+ dbus_message_unref(msg);
+
+ return 0;
+}
+
+static const char *memory_dial_lookup(int location)
+{
+ if (location == 1)
+ return vmbx;
+ else
+ return NULL;
+}
+
+void telephony_dial_number_req(void *telephony_device, const char *number)
+{
+ uint32_t flags = callerid;
+ int ret;
+
+ DBG("telephony-maemo: dial request to %s", number);
+
+ if (strncmp(number, "*31#", 4) == 0) {
+ number += 4;
+ flags = CALL_FLAG_PRESENTATION_ALLOWED;
+ } else if (strncmp(number, "#31#", 4) == 0) {
+ number += 4;
+ flags = CALL_FLAG_PRESENTATION_RESTRICTED;
+ } else if (number[0] == '>') {
+ const char *location = &number[1];
+
+ number = memory_dial_lookup(strtol(&number[1], NULL, 0));
+ if (!number) {
+ error("No number at memory location %s", location);
+ telephony_dial_number_rsp(telephony_device,
+ CME_ERROR_INVALID_INDEX);
+ return;
+ }
+ }
+
+ ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE, "CreateWith",
+ NULL, NULL,
+ DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_UINT32, &flags,
+ DBUS_TYPE_INVALID);
+ if (ret < 0) {
+ telephony_dial_number_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ return;
+ }
+
+ telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_transmit_dtmf_req(void *telephony_device, char tone)
+{
+ int ret;
+ char buf[2] = { tone, '\0' }, *buf_ptr = buf;
+
+ DBG("telephony-maemo: transmit dtmf: %s", buf);
+
+ ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE, "SendDTMF",
+ NULL, NULL,
+ DBUS_TYPE_STRING, &buf_ptr,
+ DBUS_TYPE_INVALID);
+ if (ret < 0) {
+ telephony_transmit_dtmf_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ return;
+ }
+
+ telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_subscriber_number_req(void *telephony_device)
+{
+ DBG("telephony-maemo: subscriber number request");
+ if (msisdn)
+ telephony_subscriber_number_ind(msisdn,
+ number_type(msisdn),
+ SUBSCRIBER_SERVICE_VOICE);
+ telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static int csd_status_to_hfp(struct csd_call *call)
+{
+ switch (call->status) {
+ case CSD_CALL_STATUS_IDLE:
+ case CSD_CALL_STATUS_MO_RELEASE:
+ case CSD_CALL_STATUS_MT_RELEASE:
+ case CSD_CALL_STATUS_TERMINATED:
+ return -1;
+ case CSD_CALL_STATUS_CREATE:
+ return CALL_STATUS_DIALING;
+ case CSD_CALL_STATUS_WAITING:
+ return CALL_STATUS_WAITING;
+ case CSD_CALL_STATUS_PROCEEDING:
+ /* PROCEEDING can happen in outgoing/incoming */
+ if (call->originating)
+ return CALL_STATUS_DIALING;
+ else
+ return CALL_STATUS_INCOMING;
+ case CSD_CALL_STATUS_COMING:
+ return CALL_STATUS_INCOMING;
+ case CSD_CALL_STATUS_MO_ALERTING:
+ return CALL_STATUS_ALERTING;
+ case CSD_CALL_STATUS_MT_ALERTING:
+ return CALL_STATUS_INCOMING;
+ case CSD_CALL_STATUS_ANSWERED:
+ case CSD_CALL_STATUS_ACTIVE:
+ case CSD_CALL_STATUS_RECONNECT_PENDING:
+ case CSD_CALL_STATUS_SWAP_INITIATED:
+ case CSD_CALL_STATUS_HOLD_INITIATED:
+ return CALL_STATUS_ACTIVE;
+ case CSD_CALL_STATUS_RETRIEVE_INITIATED:
+ case CSD_CALL_STATUS_HOLD:
+ return CALL_STATUS_HELD;
+ default:
+ return -1;
+ }
+}
+
+void telephony_list_current_calls_req(void *telephony_device)
+{
+ GSList *l;
+ int i;
+
+ DBG("telephony-maemo: list current calls request");
+
+ for (l = calls, i = 1; l != NULL; l = l->next, i++) {
+ struct csd_call *call = l->data;
+ int status, direction, multiparty;
+
+ status = csd_status_to_hfp(call);
+ if (status < 0)
+ continue;
+
+ direction = call->originating ?
+ CALL_DIR_OUTGOING : CALL_DIR_INCOMING;
+
+ multiparty = call->conference ?
+ CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO;
+
+ telephony_list_current_call_ind(i, direction, status,
+ CALL_MODE_VOICE, multiparty,
+ call->number,
+ number_type(call->number));
+ }
+
+ telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_operator_selection_req(void *telephony_device)
+{
+ telephony_operator_selection_ind(OPERATOR_MODE_AUTO,
+ net.operator_name ? net.operator_name : "");
+ telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static void foreach_call_with_status(int status,
+ int (*func)(struct csd_call *call))
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct csd_call *call = l->data;
+
+ if (call->status == status)
+ func(call);
+ }
+}
+
+void telephony_call_hold_req(void *telephony_device, const char *cmd)
+{
+ const char *idx;
+ struct csd_call *call;
+ int err = 0;
+
+ DBG("telephony-maemo: got call hold request %s", cmd);
+
+ if (strlen(cmd) > 1)
+ idx = &cmd[1];
+ else
+ idx = NULL;
+
+ if (idx)
+ call = g_slist_nth_data(calls, strtol(idx, NULL, 0) - 1);
+ else
+ call = NULL;
+
+ switch (cmd[0]) {
+ case '0':
+ foreach_call_with_status(CSD_CALL_STATUS_HOLD, release_call);
+ foreach_call_with_status(CSD_CALL_STATUS_WAITING,
+ release_call);
+ break;
+ case '1':
+ if (idx) {
+ if (call)
+ err = release_call(call);
+ break;
+ }
+ foreach_call_with_status(CSD_CALL_STATUS_ACTIVE, release_call);
+ call = find_call_with_status(CSD_CALL_STATUS_WAITING);
+ if (call)
+ err = answer_call(call);
+ break;
+ case '2':
+ if (idx) {
+ if (call)
+ err = split_call(call);
+ } else {
+ struct csd_call *held, *wait;
+
+ call = find_call_with_status(CSD_CALL_STATUS_ACTIVE);
+ held = find_call_with_status(CSD_CALL_STATUS_HOLD);
+ wait = find_call_with_status(CSD_CALL_STATUS_WAITING);
+
+ if (wait)
+ err = answer_call(wait);
+ else if (call && held)
+ err = swap_calls();
+ else {
+ if (call)
+ err = hold_call(call);
+ if (held)
+ err = unhold_call(held);
+ }
+ }
+ break;
+ case '3':
+ if (find_call_with_status(CSD_CALL_STATUS_HOLD) ||
+ find_call_with_status(CSD_CALL_STATUS_WAITING))
+ err = create_conference();
+ break;
+ case '4':
+ err = call_transfer();
+ break;
+ default:
+ DBG("Unknown call hold request");
+ break;
+ }
+
+ if (err)
+ telephony_call_hold_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_nr_and_ec_req(void *telephony_device, gboolean enable)
+{
+ DBG("telephony-maemo: got %s NR and EC request",
+ enable ? "enable" : "disable");
+ telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_key_press_req(void *telephony_device, const char *keys)
+{
+ struct csd_call *active, *waiting;
+ int err;
+
+ DBG("telephony-maemo: got key press request for %s", keys);
+
+ waiting = find_call_with_status(CSD_CALL_STATUS_COMING);
+ if (!waiting)
+ waiting = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING);
+ if (!waiting)
+ waiting = find_call_with_status(CSD_CALL_STATUS_PROCEEDING);
+
+ active = find_call_with_status(CSD_CALL_STATUS_ACTIVE);
+
+ if (waiting)
+ err = answer_call(waiting);
+ else if (active)
+ err = release_call(active);
+ else
+ err = 0;
+
+ if (err < 0)
+ telephony_key_press_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_key_press_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_voice_dial_req(void *telephony_device, gboolean enable)
+{
+ DBG("telephony-maemo: got %s voice dial request",
+ enable ? "enable" : "disable");
+
+ telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED);
+}
+
+static void handle_incoming_call(DBusMessage *msg)
+{
+ const char *number, *call_path;
+ struct csd_call *call;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_OBJECT_PATH, &call_path,
+ DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected parameters in Call.Coming() signal");
+ return;
+ }
+
+ call = find_call(call_path);
+ if (!call) {
+ error("Didn't find any matching call object for %s",
+ call_path);
+ return;
+ }
+
+ DBG("Incoming call to %s from number %s", call_path, number);
+
+ g_free(call->number);
+ call->number = g_strdup(number);
+
+ telephony_update_indicator(maemo_indicators, "callsetup",
+ EV_CALLSETUP_INCOMING);
+
+ if (find_call_with_status(CSD_CALL_STATUS_ACTIVE))
+ telephony_call_waiting_ind(call->number,
+ number_type(call->number));
+ else
+ telephony_incoming_call_ind(call->number,
+ number_type(call->number));
+}
+
+static void handle_outgoing_call(DBusMessage *msg)
+{
+ const char *number, *call_path;
+ struct csd_call *call;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_OBJECT_PATH, &call_path,
+ DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected parameters in Call.Created() signal");
+ return;
+ }
+
+ call = find_call(call_path);
+ if (!call) {
+ error("Didn't find any matching call object for %s",
+ call_path);
+ return;
+ }
+
+ DBG("Outgoing call from %s to number %s", call_path, number);
+
+ g_free(call->number);
+ call->number = g_strdup(number);
+
+ g_free(last_dialed_number);
+ last_dialed_number = g_strdup(number);
+
+ if (create_request_timer) {
+ g_source_remove(create_request_timer);
+ create_request_timer = 0;
+ }
+}
+
+static gboolean create_timeout(gpointer user_data)
+{
+ telephony_update_indicator(maemo_indicators, "callsetup",
+ EV_CALLSETUP_INACTIVE);
+ create_request_timer = 0;
+ return FALSE;
+}
+
+static void handle_create_requested(DBusMessage *msg)
+{
+ DBG("Call.CreateRequested()");
+
+ if (create_request_timer)
+ g_source_remove(create_request_timer);
+
+ create_request_timer = g_timeout_add_seconds(5, create_timeout, NULL);
+
+ telephony_update_indicator(maemo_indicators, "callsetup",
+ EV_CALLSETUP_OUTGOING);
+}
+
+static void handle_call_status(DBusMessage *msg, const char *call_path)
+{
+ struct csd_call *call;
+ dbus_uint32_t status, cause_type, cause;
+ int callheld = telephony_get_indicator(maemo_indicators, "callheld");
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_UINT32, &status,
+ DBUS_TYPE_UINT32, &cause_type,
+ DBUS_TYPE_UINT32, &cause,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected paramters in Instance.CallStatus() signal");
+ return;
+ }
+
+ call = find_call(call_path);
+ if (!call) {
+ error("Didn't find any matching call object for %s",
+ call_path);
+ return;
+ }
+
+ if (status > 16) {
+ error("Invalid call status %u", status);
+ return;
+ }
+
+ DBG("Call %s changed from %s to %s", call_path,
+ call_status_str[call->status], call_status_str[status]);
+
+ if (call->status == (int) status) {
+ DBG("Ignoring CSD Call state change to existing state");
+ return;
+ }
+
+ call->status = (int) status;
+
+ switch (status) {
+ case CSD_CALL_STATUS_IDLE:
+ if (call->setup) {
+ telephony_update_indicator(maemo_indicators,
+ "callsetup",
+ EV_CALLSETUP_INACTIVE);
+ if (!call->originating)
+ telephony_calling_stopped_ind();
+ }
+
+ g_free(call->number);
+ call->number = NULL;
+ call->originating = FALSE;
+ call->emergency = FALSE;
+ call->on_hold = FALSE;
+ call->conference = FALSE;
+ call->setup = FALSE;
+ break;
+ case CSD_CALL_STATUS_CREATE:
+ call->originating = TRUE;
+ call->setup = TRUE;
+ break;
+ case CSD_CALL_STATUS_COMING:
+ call->originating = FALSE;
+ call->setup = TRUE;
+ break;
+ case CSD_CALL_STATUS_PROCEEDING:
+ break;
+ case CSD_CALL_STATUS_MO_ALERTING:
+ telephony_update_indicator(maemo_indicators, "callsetup",
+ EV_CALLSETUP_ALERTING);
+ break;
+ case CSD_CALL_STATUS_MT_ALERTING:
+ break;
+ case CSD_CALL_STATUS_WAITING:
+ break;
+ case CSD_CALL_STATUS_ANSWERED:
+ break;
+ case CSD_CALL_STATUS_ACTIVE:
+ if (call->on_hold) {
+ call->on_hold = FALSE;
+ if (find_call_with_status(CSD_CALL_STATUS_HOLD))
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_MULTIPLE);
+ else
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_NONE);
+ } else {
+ if (!g_slist_find(active_calls, call))
+ active_calls = g_slist_prepend(active_calls, call);
+ if (g_slist_length(active_calls) == 1)
+ telephony_update_indicator(maemo_indicators,
+ "call",
+ EV_CALL_ACTIVE);
+ /* Upgrade callheld status if necessary */
+ if (callheld == EV_CALLHELD_ON_HOLD)
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_MULTIPLE);
+ telephony_update_indicator(maemo_indicators,
+ "callsetup",
+ EV_CALLSETUP_INACTIVE);
+ if (!call->originating)
+ telephony_calling_stopped_ind();
+ call->setup = FALSE;
+ }
+ break;
+ case CSD_CALL_STATUS_MO_RELEASE:
+ case CSD_CALL_STATUS_MT_RELEASE:
+ active_calls = g_slist_remove(active_calls, call);
+ if (g_slist_length(active_calls) == 0)
+ telephony_update_indicator(maemo_indicators, "call",
+ EV_CALL_INACTIVE);
+ break;
+ case CSD_CALL_STATUS_HOLD_INITIATED:
+ break;
+ case CSD_CALL_STATUS_HOLD:
+ call->on_hold = TRUE;
+ if (find_non_held_call())
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_MULTIPLE);
+ else
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_ON_HOLD);
+ break;
+ case CSD_CALL_STATUS_RETRIEVE_INITIATED:
+ break;
+ case CSD_CALL_STATUS_RECONNECT_PENDING:
+ break;
+ case CSD_CALL_STATUS_TERMINATED:
+ if (call->on_hold &&
+ !find_call_with_status(CSD_CALL_STATUS_HOLD))
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_NONE);
+ else if (callheld == EV_CALLHELD_MULTIPLE &&
+ find_call_with_status(CSD_CALL_STATUS_HOLD))
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_ON_HOLD);
+ break;
+ case CSD_CALL_STATUS_SWAP_INITIATED:
+ break;
+ default:
+ error("Unknown call status %u", status);
+ break;
+ }
+}
+
+static void handle_conference(DBusMessage *msg, gboolean joined)
+{
+ const char *path;
+ struct csd_call *call;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected parameters in Conference.%s",
+ dbus_message_get_member(msg));
+ return;
+ }
+
+ call = find_call(path);
+ if (!call) {
+ error("Conference signal for unknown call %s", path);
+ return;
+ }
+
+ DBG("Call %s %s the conference", path, joined ? "joined" : "left");
+
+ call->conference = joined;
+}
+
+static void get_operator_name_reply(DBusPendingCall *pending_call,
+ void *user_data)
+{
+ DBusMessage *reply;
+ DBusError err;
+ const char *name;
+ dbus_int32_t net_err;
+
+ reply = dbus_pending_call_steal_reply(pending_call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("get_operator_name failed: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_error_init(&err);
+ if (!dbus_message_get_args(reply, &err,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INT32, &net_err,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected get_operator_name reply parameters: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ if (net_err != 0) {
+ error("get_operator_name failed with code %d", net_err);
+ goto done;
+ }
+
+ if (strlen(name) == 0)
+ goto done;
+
+ g_free(net.operator_name);
+ net.operator_name = g_strdup(name);
+
+ DBG("telephony-maemo: operator name updated: %s", name);
+
+done:
+ dbus_message_unref(reply);
+}
+
+static void resolve_operator_name(uint32_t operator, uint32_t country)
+{
+ uint8_t name_type = NETWORK_HARDCODED_LATIN_OPER_NAME;
+
+ send_method_call(NETWORK_BUS_NAME, NETWORK_PATH,
+ NETWORK_INTERFACE, "get_operator_name",
+ get_operator_name_reply, NULL,
+ DBUS_TYPE_BYTE, &name_type,
+ DBUS_TYPE_UINT32, &operator,
+ DBUS_TYPE_UINT32, &country,
+ DBUS_TYPE_INVALID);
+}
+
+static void update_registration_status(uint8_t status, uint16_t lac,
+ uint32_t cell_id,
+ uint32_t operator_code,
+ uint32_t country_code,
+ uint8_t network_type,
+ uint8_t supported_services)
+{
+ if (net.status != status) {
+ switch (status) {
+ case NETWORK_REG_STATUS_HOME:
+ telephony_update_indicator(maemo_indicators, "roam",
+ EV_ROAM_INACTIVE);
+ if (net.status >= NETWORK_REG_STATUS_NOSERV)
+ telephony_update_indicator(maemo_indicators,
+ "service",
+ EV_SERVICE_PRESENT);
+ break;
+ case NETWORK_REG_STATUS_ROAM:
+ case NETWORK_REG_STATUS_ROAM_BLINK:
+ telephony_update_indicator(maemo_indicators, "roam",
+ EV_ROAM_ACTIVE);
+ if (net.status >= NETWORK_REG_STATUS_NOSERV)
+ telephony_update_indicator(maemo_indicators,
+ "service",
+ EV_SERVICE_PRESENT);
+ break;
+ case NETWORK_REG_STATUS_NOSERV:
+ case NETWORK_REG_STATUS_NOSERV_SEARCHING:
+ case NETWORK_REG_STATUS_NOSERV_NOTSEARCHING:
+ case NETWORK_REG_STATUS_NOSERV_NOSIM:
+ case NETWORK_REG_STATUS_POWER_OFF:
+ case NETWORK_REG_STATUS_NSPS:
+ case NETWORK_REG_STATUS_NSPS_NO_COVERAGE:
+ case NETWORK_REG_STATUS_NOSERV_SIM_REJECTED_BY_NW:
+ if (net.status < NETWORK_REG_STATUS_NOSERV)
+ telephony_update_indicator(maemo_indicators,
+ "service",
+ EV_SERVICE_NONE);
+ break;
+ }
+
+ net.status = status;
+ }
+
+ net.lac = lac;
+ net.cell_id = cell_id;
+
+ if (net.operator_code != operator_code ||
+ net.country_code != country_code) {
+ g_free(net.operator_name);
+ net.operator_name = NULL;
+ resolve_operator_name(operator_code, country_code);
+ net.operator_code = operator_code;
+ net.country_code = country_code;
+ }
+
+ net.network_type = network_type;
+ net.supported_services = supported_services;
+}
+
+static void handle_registration_status_change(DBusMessage *msg)
+{
+ uint8_t status;
+ dbus_uint16_t lac, network_type, supported_services;
+ dbus_uint32_t cell_id, operator_code, country_code;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_BYTE, &status,
+ DBUS_TYPE_UINT16, &lac,
+ DBUS_TYPE_UINT32, &cell_id,
+ DBUS_TYPE_UINT32, &operator_code,
+ DBUS_TYPE_UINT32, &country_code,
+ DBUS_TYPE_BYTE, &network_type,
+ DBUS_TYPE_BYTE, &supported_services,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected parameters in registration_status_change");
+ return;
+ }
+
+ update_registration_status(status, lac, cell_id, operator_code,
+ country_code, network_type,
+ supported_services);
+}
+
+static void update_signal_strength(uint8_t signals_bar)
+{
+ int signal;
+
+ if (signals_bar > 100) {
+ DBG("signals_bar greater than expected: %u", signals_bar);
+ signals_bar = 100;
+ }
+
+ if (net.signals_bar == signals_bar)
+ return;
+
+ /* A simple conversion from 0-100 to 0-5 (used by HFP) */
+ signal = (signals_bar + 20) / 21;
+
+ telephony_update_indicator(maemo_indicators, "signal", signal);
+
+ net.signals_bar = signals_bar;
+
+ DBG("Signal strength updated: %u/100, %d/5", signals_bar, signal);
+}
+
+static void handle_signal_strength_change(DBusMessage *msg)
+{
+ uint8_t signals_bar, rssi_in_dbm;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_BYTE, &signals_bar,
+ DBUS_TYPE_BYTE, &rssi_in_dbm,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected parameters in signal_strength_change");
+ return;
+ }
+
+ update_signal_strength(signals_bar);
+}
+
+static gboolean iter_get_basic_args(DBusMessageIter *iter,
+ int first_arg_type, ...)
+{
+ int type;
+ va_list ap;
+
+ va_start(ap, first_arg_type);
+
+ for (type = first_arg_type; type != DBUS_TYPE_INVALID;
+ type = va_arg(ap, int)) {
+ void *value = va_arg(ap, void *);
+ int real_type = dbus_message_iter_get_arg_type(iter);
+
+ if (real_type != type) {
+ error("iter_get_basic_args: expected %c but got %c",
+ (char) type, (char) real_type);
+ break;
+ }
+
+ dbus_message_iter_get_basic(iter, value);
+ dbus_message_iter_next(iter);
+ }
+
+ va_end(ap);
+
+ return type == DBUS_TYPE_INVALID ? TRUE : FALSE;
+}
+
+static void hal_battery_level_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ dbus_int32_t level;
+ int *value = user_data;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("hald replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_error_init(&err);
+ if (dbus_message_get_args(reply, &err,
+ DBUS_TYPE_INT32, &level,
+ DBUS_TYPE_INVALID) == FALSE) {
+ error("Unable to parse GetPropertyInteger reply: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ *value = (int) level;
+
+ if (value == &battchg_last)
+ DBG("telephony-maemo: battery.charge_level.last_full is %d",
+ *value);
+ else if (value == &battchg_design)
+ DBG("telephony-maemo: battery.charge_level.design is %d",
+ *value);
+ else
+ DBG("telephony-maemo: battery.charge_level.current is %d",
+ *value);
+
+ if ((battchg_design > 0 || battchg_last > 0) && battchg_cur >= 0) {
+ int new, max;
+
+ if (battchg_last > 0)
+ max = battchg_last;
+ else
+ max = battchg_design;
+
+ new = battchg_cur * 5 / max;
+
+ telephony_update_indicator(maemo_indicators, "battchg", new);
+ }
+done:
+ dbus_message_unref(reply);
+}
+
+static void hal_get_integer(const char *path, const char *key, void *user_data)
+{
+ send_method_call("org.freedesktop.Hal", path,
+ "org.freedesktop.Hal.Device",
+ "GetPropertyInteger",
+ hal_battery_level_reply, user_data,
+ DBUS_TYPE_STRING, &key,
+ DBUS_TYPE_INVALID);
+}
+
+static void handle_hal_property_modified(DBusMessage *msg)
+{
+ DBusMessageIter iter, array;
+ dbus_int32_t num_changes;
+ const char *path;
+
+ path = dbus_message_get_path(msg);
+
+ dbus_message_iter_init(msg, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
+ error("Unexpected signature in hal PropertyModified signal");
+ return;
+ }
+
+ dbus_message_iter_get_basic(&iter, &num_changes);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ error("Unexpected signature in hal PropertyModified signal");
+ return;
+ }
+
+ dbus_message_iter_recurse(&iter, &array);
+
+ while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) {
+ DBusMessageIter prop;
+ const char *name;
+ dbus_bool_t added, removed;
+
+ dbus_message_iter_recurse(&array, &prop);
+
+ if (!iter_get_basic_args(&prop,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_BOOLEAN, &added,
+ DBUS_TYPE_BOOLEAN, &removed,
+ DBUS_TYPE_INVALID)) {
+ error("Invalid hal PropertyModified parameters");
+ break;
+ }
+
+ if (g_str_equal(name, "battery.charge_level.last_full"))
+ hal_get_integer(path, name, &battchg_last);
+ else if (g_str_equal(name, "battery.charge_level.current"))
+ hal_get_integer(path, name, &battchg_cur);
+ else if (g_str_equal(name, "battery.charge_level.design"))
+ hal_get_integer(path, name, &battchg_design);
+
+ dbus_message_iter_next(&array);
+ }
+}
+
+static void csd_call_free(struct csd_call *call)
+{
+ if (!call)
+ return;
+
+ g_free(call->object_path);
+ g_free(call->number);
+
+ g_free(call);
+}
+
+static void parse_call_list(DBusMessageIter *iter)
+{
+ do {
+ DBusMessageIter call_iter;
+ struct csd_call *call;
+ const char *object_path, *number;
+ dbus_uint32_t status;
+ dbus_bool_t originating, terminating, emerg, on_hold, conf;
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT) {
+ error("Unexpected signature in GetCallInfoAll reply");
+ break;
+ }
+
+ dbus_message_iter_recurse(iter, &call_iter);
+
+ if (!iter_get_basic_args(&call_iter,
+ DBUS_TYPE_OBJECT_PATH, &object_path,
+ DBUS_TYPE_UINT32, &status,
+ DBUS_TYPE_BOOLEAN, &originating,
+ DBUS_TYPE_BOOLEAN, &terminating,
+ DBUS_TYPE_BOOLEAN, &emerg,
+ DBUS_TYPE_BOOLEAN, &on_hold,
+ DBUS_TYPE_BOOLEAN, &conf,
+ DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INVALID)) {
+ error("Parsing call D-Bus parameters failed");
+ break;
+ }
+
+ call = find_call(object_path);
+ if (!call) {
+ call = g_new0(struct csd_call, 1);
+ call->object_path = g_strdup(object_path);
+ call->status = (int) status;
+ calls = g_slist_append(calls, call);
+ DBG("telephony-maemo: new csd call instance at %s",
+ object_path);
+ }
+
+ if (call->status == CSD_CALL_STATUS_IDLE)
+ continue;
+
+ /* CSD gives incorrect call_hold property sometimes */
+ if ((call->status != CSD_CALL_STATUS_HOLD && on_hold) ||
+ (call->status == CSD_CALL_STATUS_HOLD &&
+ !on_hold)) {
+ error("Conflicting call status and on_hold property!");
+ on_hold = call->status == CSD_CALL_STATUS_HOLD;
+ }
+
+ call->originating = originating;
+ call->on_hold = on_hold;
+ call->conference = conf;
+ g_free(call->number);
+ call->number = g_strdup(number);
+
+ } while (dbus_message_iter_next(iter));
+}
+
+static void signal_strength_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ uint8_t signals_bar, rssi_in_dbm;
+ dbus_int32_t net_err;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("Unable to get signal strength: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_error_init(&err);
+ if (!dbus_message_get_args(reply, &err,
+ DBUS_TYPE_BYTE, &signals_bar,
+ DBUS_TYPE_BYTE, &rssi_in_dbm,
+ DBUS_TYPE_INT32, &net_err,
+ DBUS_TYPE_INVALID)) {
+ error("Unable to parse signal_strength reply: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ return;
+ }
+
+ if (net_err != 0) {
+ error("get_signal_strength failed with code %d", net_err);
+ return;
+ }
+
+ update_signal_strength(signals_bar);
+
+done:
+ dbus_message_unref(reply);
+}
+
+static int get_signal_strength(void)
+{
+ return send_method_call(NETWORK_BUS_NAME, NETWORK_PATH,
+ NETWORK_INTERFACE, "get_signal_strength",
+ signal_strength_reply, NULL,
+ DBUS_TYPE_INVALID);
+}
+
+static void registration_status_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ uint8_t status;
+ dbus_uint16_t lac, network_type, supported_services;
+ dbus_uint32_t cell_id, operator_code, country_code;
+ dbus_int32_t net_err;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("Unable to get registration status: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_error_init(&err);
+ if (!dbus_message_get_args(reply, &err,
+ DBUS_TYPE_BYTE, &status,
+ DBUS_TYPE_UINT16, &lac,
+ DBUS_TYPE_UINT32, &cell_id,
+ DBUS_TYPE_UINT32, &operator_code,
+ DBUS_TYPE_UINT32, &country_code,
+ DBUS_TYPE_BYTE, &network_type,
+ DBUS_TYPE_BYTE, &supported_services,
+ DBUS_TYPE_INT32, &net_err,
+ DBUS_TYPE_INVALID)) {
+ error("Unable to parse registration_status_change reply:"
+ " %s, %s", err.name, err.message);
+ dbus_error_free(&err);
+ return;
+ }
+
+ if (net_err != 0) {
+ error("get_registration_status failed with code %d", net_err);
+ return;
+ }
+
+ update_registration_status(status, lac, cell_id, operator_code,
+ country_code, network_type,
+ supported_services);
+
+ get_signal_strength();
+
+done:
+ dbus_message_unref(reply);
+}
+
+static int get_registration_status(void)
+{
+ return send_method_call(NETWORK_BUS_NAME, NETWORK_PATH,
+ NETWORK_INTERFACE, "get_registration_status",
+ registration_status_reply, NULL,
+ DBUS_TYPE_INVALID);
+}
+
+static void call_info_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ DBusMessageIter iter, sub;;
+
+ get_calls_active = FALSE;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("csd replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_message_iter_init(reply, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ error("Unexpected signature in GetCallInfoAll return");
+ goto done;
+ }
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ parse_call_list(&sub);
+
+ get_registration_status();
+
+done:
+ dbus_message_unref(reply);
+}
+
+static void hal_find_device_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ DBusMessageIter iter, sub;
+ const char *path;
+ char match_string[256];
+ int type;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("hald replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_message_iter_init(reply, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ error("Unexpected signature in FindDeviceByCapability return");
+ goto done;
+ }
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ type = dbus_message_iter_get_arg_type(&sub);
+
+ if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) {
+ error("No hal device with battery capability found");
+ goto done;
+ }
+
+ dbus_message_iter_get_basic(&sub, &path);
+
+ DBG("telephony-maemo: found battery device at %s", path);
+
+ snprintf(match_string, sizeof(match_string),
+ "type='signal',"
+ "path='%s',"
+ "interface='org.freedesktop.Hal.Device',"
+ "member='PropertyModified'", path);
+ dbus_bus_add_match(connection, match_string, NULL);
+
+ hal_get_integer(path, "battery.charge_level.last_full", &battchg_last);
+ hal_get_integer(path, "battery.charge_level.current", &battchg_cur);
+ hal_get_integer(path, "battery.charge_level.design", &battchg_design);
+
+done:
+ dbus_message_unref(reply);
+}
+
+static void phonebook_read_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError derr;
+ DBusMessage *reply;
+ const char *name, *number;
+ char **number_type = user_data;
+ dbus_int32_t current_location, err;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&derr);
+ if (dbus_set_error_from_message(&derr, reply)) {
+ error("SIM.Phonebook replied with an error: %s, %s",
+ derr.name, derr.message);
+ dbus_error_free(&derr);
+ goto done;
+ }
+
+ dbus_error_init(&derr);
+ if (dbus_message_get_args(reply, &derr,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INT32, &current_location,
+ DBUS_TYPE_INT32, &err,
+ DBUS_TYPE_INVALID) == FALSE) {
+ error("Unable to parse SIM.Phonebook.read arguments: %s, %s",
+ derr.name, derr.message);
+ dbus_error_free(&derr);
+ goto done;
+ }
+
+ if (err != 0) {
+ error("SIM.Phonebook.read failed with error %d", err);
+ if (number_type == &vmbx)
+ vmbx = g_strdup(getenv("VMBX_NUMBER"));
+ goto done;
+ }
+
+ if (number_type == &msisdn) {
+ g_free(msisdn);
+ msisdn = g_strdup(number);
+ DBG("Got MSISDN %s (%s)", number, name);
+ } else {
+ g_free(vmbx);
+ vmbx = g_strdup(number);
+ DBG("Got voice mailbox number %s (%s)", number, name);
+ }
+
+done:
+ dbus_message_unref(reply);
+}
+
+static void csd_init(void)
+{
+ dbus_uint32_t location;
+ uint8_t pb_type, location_type;
+ int ret;
+
+ ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE, "GetCallInfoAll",
+ call_info_reply, NULL, DBUS_TYPE_INVALID);
+ if (ret < 0) {
+ error("Unable to sent GetCallInfoAll method call");
+ return;
+ }
+
+ get_calls_active = TRUE;
+
+ pb_type = SIM_PHONEBOOK_TYPE_MSISDN;
+ location = PHONEBOOK_INDEX_FIRST_ENTRY;
+ location_type = SIM_PHONEBOOK_LOCATION_NEXT;
+
+ ret = send_method_call(SIM_PHONEBOOK_BUS_NAME, SIM_PHONEBOOK_PATH,
+ SIM_PHONEBOOK_INTERFACE, "read",
+ phonebook_read_reply, &msisdn,
+ DBUS_TYPE_BYTE, &pb_type,
+ DBUS_TYPE_INT32, &location,
+ DBUS_TYPE_BYTE, &location_type,
+ DBUS_TYPE_INVALID);
+ if (ret < 0) {
+ error("Unable to send " SIM_PHONEBOOK_INTERFACE ".read()");
+ return;
+ }
+
+ pb_type = SIM_PHONEBOOK_TYPE_MBDN;
+ location = PHONEBOOK_INDEX_FIRST_ENTRY;
+ location_type = SIM_PHONEBOOK_LOCATION_NEXT;
+
+ ret = send_method_call(SIM_PHONEBOOK_BUS_NAME, SIM_PHONEBOOK_PATH,
+ SIM_PHONEBOOK_INTERFACE, "read",
+ phonebook_read_reply, &vmbx,
+ DBUS_TYPE_BYTE, &pb_type,
+ DBUS_TYPE_INT32, &location,
+ DBUS_TYPE_BYTE, &location_type,
+ DBUS_TYPE_INVALID);
+ if (ret < 0) {
+ error("Unable to send " SIM_PHONEBOOK_INTERFACE ".read()");
+ return;
+ }
+}
+
+static uint32_t get_callflag(const char *callerid_setting)
+{
+ if (callerid_setting != NULL) {
+ if (g_str_equal(callerid_setting, "allowed"))
+ return CALL_FLAG_PRESENTATION_ALLOWED;
+ else if (g_str_equal(callerid_setting, "restricted"))
+ return CALL_FLAG_PRESENTATION_RESTRICTED;
+ else
+ return CALL_FLAG_NONE;
+ } else
+ return CALL_FLAG_NONE;
+}
+
+static void generate_flag_file(const char *filename)
+{
+ int fd;
+
+ if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS) ||
+ g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS) ||
+ g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS))
+ return;
+
+ fd = open(filename, O_WRONLY | O_CREAT, 0);
+ if (fd >= 0)
+ close(fd);
+}
+
+static void save_callerid_to_file(const char *callerid_setting)
+{
+ char callerid_file[FILENAME_MAX];
+
+ snprintf(callerid_file, sizeof(callerid_file), "%s%s",
+ CALLERID_BASE, callerid_setting);
+
+ if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS))
+ rename(ALLOWED_FLAG_FILE, callerid_file);
+ else if (g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS))
+ rename(RESTRICTED_FLAG_FILE, callerid_file);
+ else if (g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS))
+ rename(NONE_FLAG_FILE, callerid_file);
+ else
+ generate_flag_file(callerid_file);
+}
+
+static uint32_t callerid_from_file(void)
+{
+ if (g_file_test(ALLOWED_FLAG_FILE, G_FILE_TEST_EXISTS))
+ return CALL_FLAG_PRESENTATION_ALLOWED;
+ else if (g_file_test(RESTRICTED_FLAG_FILE, G_FILE_TEST_EXISTS))
+ return CALL_FLAG_PRESENTATION_RESTRICTED;
+ else if (g_file_test(NONE_FLAG_FILE, G_FILE_TEST_EXISTS))
+ return CALL_FLAG_NONE;
+ else
+ return CALL_FLAG_NONE;
+}
+
+static DBusMessage *set_callerid(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ const char *callerid_setting;
+
+ if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING,
+ &callerid_setting,
+ DBUS_TYPE_INVALID) == FALSE)
+ return btd_error_invalid_args(msg);
+
+ if (g_str_equal(callerid_setting, "allowed") ||
+ g_str_equal(callerid_setting, "restricted") ||
+ g_str_equal(callerid_setting, "none")) {
+ save_callerid_to_file(callerid_setting);
+ callerid = get_callflag(callerid_setting);
+ DBG("telephony-maemo setting callerid flag: %s",
+ callerid_setting);
+ return dbus_message_new_method_return(msg);
+ }
+
+ error("telephony-maemo: invalid argument %s for method call"
+ " SetCallerId", callerid_setting);
+ return btd_error_invalid_args(msg);
+}
+
+static GDBusMethodTable telephony_maemo_methods[] = {
+ {"SetCallerId", "s", "", set_callerid,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { }
+};
+
+static void handle_modem_state(DBusMessage *msg)
+{
+ const char *state;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &state,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected modem state parameters");
+ return;
+ }
+
+ DBG("SSC modem state: %s", state);
+
+ if (calls != NULL || get_calls_active)
+ return;
+
+ if (g_str_equal(state, "cmt_ready") || g_str_equal(state, "online"))
+ csd_init();
+}
+
+static void modem_state_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError err;
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("get_modem_status: %s, %s", err.name, err.message);
+ dbus_error_free(&err);
+ } else
+ handle_modem_state(reply);
+
+ dbus_message_unref(reply);
+}
+
+static DBusHandlerResult signal_filter(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *path = dbus_message_get_path(msg);
+
+ if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL)
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Coming"))
+ handle_incoming_call(msg);
+ else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Created"))
+ handle_outgoing_call(msg);
+ else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE,
+ "CreateRequested"))
+ handle_create_requested(msg);
+ else if (dbus_message_is_signal(msg, CSD_CALL_INSTANCE, "CallStatus"))
+ handle_call_status(msg, path);
+ else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Joined"))
+ handle_conference(msg, TRUE);
+ else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Left"))
+ handle_conference(msg, FALSE);
+ else if (dbus_message_is_signal(msg, NETWORK_INTERFACE,
+ "registration_status_change"))
+ handle_registration_status_change(msg);
+ else if (dbus_message_is_signal(msg, NETWORK_INTERFACE,
+ "signal_strength_change"))
+ handle_signal_strength_change(msg);
+ else if (dbus_message_is_signal(msg, "org.freedesktop.Hal.Device",
+ "PropertyModified"))
+ handle_hal_property_modified(msg);
+ else if (dbus_message_is_signal(msg, SSC_DBUS_IFACE,
+ "modem_state_changed_ind"))
+ handle_modem_state(msg);
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+int telephony_init(void)
+{
+ const char *battery_cap = "battery";
+ uint32_t features = AG_FEATURE_EC_ANDOR_NR |
+ AG_FEATURE_INBAND_RINGTONE |
+ AG_FEATURE_REJECT_A_CALL |
+ AG_FEATURE_ENHANCED_CALL_STATUS |
+ AG_FEATURE_ENHANCED_CALL_CONTROL |
+ AG_FEATURE_EXTENDED_ERROR_RESULT_CODES |
+ AG_FEATURE_THREE_WAY_CALLING;
+
+ connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+ if (!dbus_connection_add_filter(connection, signal_filter,
+ NULL, NULL))
+ error("Can't add signal filter");
+
+ dbus_bus_add_match(connection,
+ "type=signal,interface=" CSD_CALL_INTERFACE, NULL);
+ dbus_bus_add_match(connection,
+ "type=signal,interface=" CSD_CALL_INSTANCE, NULL);
+ dbus_bus_add_match(connection,
+ "type=signal,interface=" CSD_CALL_CONFERENCE, NULL);
+ dbus_bus_add_match(connection,
+ "type=signal,interface=" NETWORK_INTERFACE, NULL);
+ dbus_bus_add_match(connection,
+ "type=signal,interface=" SSC_DBUS_IFACE
+ ",member=modem_state_changed_ind", NULL);
+
+ if (send_method_call(SSC_DBUS_NAME, SSC_DBUS_PATH, SSC_DBUS_IFACE,
+ "get_modem_state", modem_state_reply,
+ NULL, DBUS_TYPE_INVALID) < 0)
+ error("Unable to send " SSC_DBUS_IFACE ".get_modem_state()");
+
+ generate_flag_file(NONE_FLAG_FILE);
+ callerid = callerid_from_file();
+
+ if (!g_dbus_register_interface(connection, TELEPHONY_MAEMO_PATH,
+ TELEPHONY_MAEMO_INTERFACE, telephony_maemo_methods,
+ NULL, NULL, NULL, NULL)) {
+ error("telephony-maemo interface %s init failed on path %s",
+ TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH);
+ }
+
+ DBG("telephony-maemo registering %s interface on path %s",
+ TELEPHONY_MAEMO_INTERFACE, TELEPHONY_MAEMO_PATH);
+
+ telephony_ready_ind(features, maemo_indicators, BTRH_NOT_SUPPORTED,
+ chld_str);
+ if (send_method_call("org.freedesktop.Hal",
+ "/org/freedesktop/Hal/Manager",
+ "org.freedesktop.Hal.Manager",
+ "FindDeviceByCapability",
+ hal_find_device_reply, NULL,
+ DBUS_TYPE_STRING, &battery_cap,
+ DBUS_TYPE_INVALID) < 0)
+ error("Unable to send HAL method call");
+
+ return 0;
+}
+
+void telephony_exit(void)
+{
+ g_slist_foreach(calls, (GFunc) csd_call_free, NULL);
+ g_slist_free(calls);
+ calls = NULL;
+
+ dbus_connection_remove_filter(connection, signal_filter, NULL);
+
+ dbus_connection_unref(connection);
+ connection = NULL;
+
+ telephony_deinit();
+}
diff --git a/audio/telephony-maemo6.c b/audio/telephony-maemo6.c
new file mode 100644
index 0000000..0cef7dd
--- /dev/null
+++ b/audio/telephony-maemo6.c
@@ -0,0 +1,1993 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2008-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "telephony.h"
+#include "error.h"
+
+/* SSC D-Bus definitions */
+#define SSC_DBUS_NAME "com.nokia.phone.SSC"
+#define SSC_DBUS_IFACE "com.nokia.phone.SSC"
+#define SSC_DBUS_PATH "/com/nokia/phone/SSC"
+
+/* libcsnet D-Bus definitions */
+#define CSD_CSNET_BUS_NAME "com.nokia.csd.CSNet"
+#define CSD_CSNET_PATH "/com/nokia/csd/csnet"
+#define CSD_CSNET_IFACE "com.nokia.csd.CSNet"
+#define CSD_CSNET_REGISTRATION "com.nokia.csd.CSNet.NetworkRegistration"
+#define CSD_CSNET_OPERATOR "com.nokia.csd.CSNet.NetworkOperator"
+#define CSD_CSNET_SIGNAL "com.nokia.csd.CSNet.SignalStrength"
+
+enum net_registration_status {
+ NETWORK_REG_STATUS_HOME,
+ NETWORK_REG_STATUS_ROAMING,
+ NETWORK_REG_STATUS_OFFLINE,
+ NETWORK_REG_STATUS_SEARCHING,
+ NETWORK_REG_STATUS_NO_SIM,
+ NETWORK_REG_STATUS_POWEROFF,
+ NETWORK_REG_STATUS_POWERSAFE,
+ NETWORK_REG_STATUS_NO_COVERAGE,
+ NETWORK_REG_STATUS_REJECTED,
+ NETWORK_REG_STATUS_UNKOWN
+};
+
+/* CSD CALL plugin D-Bus definitions */
+#define CSD_CALL_BUS_NAME "com.nokia.csd.Call"
+#define CSD_CALL_INTERFACE "com.nokia.csd.Call"
+#define CSD_CALL_INSTANCE "com.nokia.csd.Call.Instance"
+#define CSD_CALL_CONFERENCE "com.nokia.csd.Call.Conference"
+#define CSD_CALL_PATH "/com/nokia/csd/call"
+#define CSD_CALL_CONFERENCE_PATH "/com/nokia/csd/call/conference"
+
+/* Call status values as exported by the CSD CALL plugin */
+#define CSD_CALL_STATUS_IDLE 0
+#define CSD_CALL_STATUS_CREATE 1
+#define CSD_CALL_STATUS_COMING 2
+#define CSD_CALL_STATUS_PROCEEDING 3
+#define CSD_CALL_STATUS_MO_ALERTING 4
+#define CSD_CALL_STATUS_MT_ALERTING 5
+#define CSD_CALL_STATUS_WAITING 6
+#define CSD_CALL_STATUS_ANSWERED 7
+#define CSD_CALL_STATUS_ACTIVE 8
+#define CSD_CALL_STATUS_MO_RELEASE 9
+#define CSD_CALL_STATUS_MT_RELEASE 10
+#define CSD_CALL_STATUS_HOLD_INITIATED 11
+#define CSD_CALL_STATUS_HOLD 12
+#define CSD_CALL_STATUS_RETRIEVE_INITIATED 13
+#define CSD_CALL_STATUS_RECONNECT_PENDING 14
+#define CSD_CALL_STATUS_TERMINATED 15
+#define CSD_CALL_STATUS_SWAP_INITIATED 16
+
+#define CALL_FLAG_NONE 0
+#define CALL_FLAG_PRESENTATION_ALLOWED 0x01
+#define CALL_FLAG_PRESENTATION_RESTRICTED 0x02
+
+/* SIM Phonebook D-Bus definitions */
+#define CSD_SIMPB_BUS_NAME "com.nokia.csd.SIM"
+#define CSD_SIMPB_INTERFACE "com.nokia.csd.SIM.Phonebook"
+#define CSD_SIMPB_PATH "/com/nokia/csd/sim/phonebook"
+
+#define CSD_SIMPB_TYPE_ADN "ADN"
+#define CSD_SIMPB_TYPE_FDN "FDN"
+#define CSD_SIMPB_TYPE_SDN "SDN"
+#define CSD_SIMPB_TYPE_VMBX "VMBX"
+#define CSD_SIMPB_TYPE_MBDN "MBDN"
+#define CSD_SIMPB_TYPE_EN "EN"
+#define CSD_SIMPB_TYPE_MSISDN "MSISDN"
+
+struct csd_call {
+ char *object_path;
+ int status;
+ gboolean originating;
+ gboolean emergency;
+ gboolean on_hold;
+ gboolean conference;
+ char *number;
+ gboolean setup;
+};
+
+static struct {
+ char *operator_name;
+ uint8_t status;
+ int32_t signal_bars;
+} net = {
+ .operator_name = NULL,
+ .status = NETWORK_REG_STATUS_UNKOWN,
+ /* Init as 0 meaning inactive mode. In modem power off state
+ * can be be -1, but we treat all values as 0s regardless
+ * inactive or power off. */
+ .signal_bars = 0,
+};
+
+struct pending_req {
+ DBusPendingCall *call;
+ void *user_data;
+};
+
+static int get_property(const char *iface, const char *prop);
+
+static DBusConnection *connection = NULL;
+
+static GSList *calls = NULL;
+static GSList *watches = NULL;
+static GSList *pending = NULL;
+
+/* Reference count for determining the call indicator status */
+static GSList *active_calls = NULL;
+
+static char *msisdn = NULL; /* Subscriber number */
+static char *vmbx = NULL; /* Voice mailbox number */
+
+/* HAL battery namespace key values */
+static int battchg_cur = -1; /* "battery.charge_level.current" */
+static int battchg_last = -1; /* "battery.charge_level.last_full" */
+static int battchg_design = -1; /* "battery.charge_level.design" */
+
+static gboolean get_calls_active = FALSE;
+
+static gboolean events_enabled = FALSE;
+
+/* Supported set of call hold operations */
+static const char *chld_str = "0,1,1x,2,2x,3,4";
+
+/* Timer for tracking call creation requests */
+static guint create_request_timer = 0;
+
+static struct indicator maemo_indicators[] =
+{
+ { "battchg", "0-5", 5, TRUE },
+ /* signal strength in terms of bars */
+ { "signal", "0-5", 0, TRUE },
+ { "service", "0,1", 0, TRUE },
+ { "call", "0,1", 0, TRUE },
+ { "callsetup", "0-3", 0, TRUE },
+ { "callheld", "0-2", 0, FALSE },
+ { "roam", "0,1", 0, TRUE },
+ { NULL }
+};
+
+static char *call_status_str[] = {
+ "IDLE",
+ "CREATE",
+ "COMING",
+ "PROCEEDING",
+ "MO_ALERTING",
+ "MT_ALERTING",
+ "WAITING",
+ "ANSWERED",
+ "ACTIVE",
+ "MO_RELEASE",
+ "MT_RELEASE",
+ "HOLD_INITIATED",
+ "HOLD",
+ "RETRIEVE_INITIATED",
+ "RECONNECT_PENDING",
+ "TERMINATED",
+ "SWAP_INITIATED",
+ "???"
+};
+
+static struct csd_call *find_call(const char *path)
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct csd_call *call = l->data;
+
+ if (g_str_equal(call->object_path, path))
+ return call;
+ }
+
+ return NULL;
+}
+
+static struct csd_call *find_non_held_call(void)
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct csd_call *call = l->data;
+
+ if (call->status == CSD_CALL_STATUS_IDLE)
+ continue;
+
+ if (call->status != CSD_CALL_STATUS_HOLD)
+ return call;
+ }
+
+ return NULL;
+}
+
+static struct csd_call *find_non_idle_call(void)
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct csd_call *call = l->data;
+
+ if (call->status != CSD_CALL_STATUS_IDLE)
+ return call;
+ }
+
+ return NULL;
+}
+
+static struct csd_call *find_call_with_status(int status)
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct csd_call *call = l->data;
+
+ if (call->status == status)
+ return call;
+ }
+
+ return NULL;
+}
+
+static int release_conference(void)
+{
+ DBusMessage *msg;
+
+ DBG("telephony-maemo6: releasing conference call");
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+ CSD_CALL_CONFERENCE_PATH,
+ CSD_CALL_INSTANCE,
+ "Release");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int release_call(struct csd_call *call)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+ call->object_path,
+ CSD_CALL_INSTANCE,
+ "Release");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int answer_call(struct csd_call *call)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+ call->object_path,
+ CSD_CALL_INSTANCE,
+ "Answer");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int split_call(struct csd_call *call)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME,
+ call->object_path,
+ CSD_CALL_INSTANCE,
+ "Split");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int unhold_call(struct csd_call *call)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE,
+ "Unhold");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int hold_call(struct csd_call *call)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE,
+ "Hold");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int swap_calls(void)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE,
+ "Swap");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int create_conference(void)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE,
+ "Conference");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int call_transfer(void)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE,
+ "Transfer");
+ if (!msg) {
+ error("Unable to allocate new D-Bus message");
+ return -ENOMEM;
+ }
+
+ g_dbus_send_message(connection, msg);
+
+ return 0;
+}
+
+static int number_type(const char *number)
+{
+ if (number == NULL)
+ return NUMBER_TYPE_TELEPHONY;
+
+ if (number[0] == '+' || strncmp(number, "00", 2) == 0)
+ return NUMBER_TYPE_INTERNATIONAL;
+
+ return NUMBER_TYPE_TELEPHONY;
+}
+
+void telephony_device_connected(void *telephony_device)
+{
+ struct csd_call *coming;
+
+ DBG("telephony-maemo6: device %p connected", telephony_device);
+
+ coming = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING);
+ if (coming) {
+ if (find_call_with_status(CSD_CALL_STATUS_ACTIVE))
+ telephony_call_waiting_ind(coming->number,
+ number_type(coming->number));
+ else
+ telephony_incoming_call_ind(coming->number,
+ number_type(coming->number));
+ }
+}
+
+static void pending_req_finalize(struct pending_req *req)
+{
+ if (!dbus_pending_call_get_completed(req->call))
+ dbus_pending_call_cancel(req->call);
+
+ dbus_pending_call_unref(req->call);
+ g_free(req);
+}
+
+static void remove_pending_by_data(gpointer data, gpointer user_data)
+{
+ struct pending_req *req = data;
+
+ if (req->user_data == user_data) {
+ pending = g_slist_remove(pending, req);
+ pending_req_finalize(req);
+ }
+}
+
+void telephony_device_disconnected(void *telephony_device)
+{
+ DBG("telephony-maemo6: device %p disconnected", telephony_device);
+ events_enabled = FALSE;
+
+ g_slist_foreach(pending, remove_pending_by_data, telephony_device);
+}
+
+void telephony_event_reporting_req(void *telephony_device, int ind)
+{
+ events_enabled = ind == 1 ? TRUE : FALSE;
+
+ telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_response_and_hold_req(void *telephony_device, int rh)
+{
+ telephony_response_and_hold_rsp(telephony_device,
+ CME_ERROR_NOT_SUPPORTED);
+}
+
+void telephony_terminate_call_req(void *telephony_device)
+{
+ struct csd_call *call;
+ struct csd_call *alerting;
+ int err;
+
+ call = find_call_with_status(CSD_CALL_STATUS_ACTIVE);
+ if (!call)
+ call = find_non_idle_call();
+
+ if (!call) {
+ error("No active call");
+ telephony_terminate_call_rsp(telephony_device,
+ CME_ERROR_NOT_ALLOWED);
+ return;
+ }
+
+ alerting = find_call_with_status(CSD_CALL_STATUS_MO_ALERTING);
+ if (call->on_hold && alerting)
+ err = release_call(alerting);
+ else if (call->conference)
+ err = release_conference();
+ else
+ err = release_call(call);
+
+ if (err < 0)
+ telephony_terminate_call_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_answer_call_req(void *telephony_device)
+{
+ struct csd_call *call;
+
+ call = find_call_with_status(CSD_CALL_STATUS_COMING);
+ if (!call)
+ call = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING);
+
+ if (!call)
+ call = find_call_with_status(CSD_CALL_STATUS_PROCEEDING);
+
+ if (!call)
+ call = find_call_with_status(CSD_CALL_STATUS_WAITING);
+
+ if (!call) {
+ telephony_answer_call_rsp(telephony_device,
+ CME_ERROR_NOT_ALLOWED);
+ return;
+ }
+
+ if (answer_call(call) < 0)
+ telephony_answer_call_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static int send_method_call(const char *dest, const char *path,
+ const char *interface, const char *method,
+ DBusPendingCallNotifyFunction cb,
+ void *user_data, int type, ...)
+{
+ DBusMessage *msg;
+ DBusPendingCall *call;
+ va_list args;
+ struct pending_req *req;
+
+ msg = dbus_message_new_method_call(dest, path, interface, method);
+ if (!msg) {
+ error("Unable to allocate new D-Bus %s message", method);
+ return -ENOMEM;
+ }
+
+ va_start(args, type);
+
+ if (!dbus_message_append_args_valist(msg, type, args)) {
+ dbus_message_unref(msg);
+ va_end(args);
+ return -EIO;
+ }
+
+ va_end(args);
+
+ if (!cb) {
+ g_dbus_send_message(connection, msg);
+ return 0;
+ }
+
+ if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) {
+ error("Sending %s failed", method);
+ dbus_message_unref(msg);
+ return -EIO;
+ }
+
+ dbus_pending_call_set_notify(call, cb, user_data, NULL);
+
+ req = g_new0(struct pending_req, 1);
+ req->call = call;
+ req->user_data = user_data;
+
+ pending = g_slist_prepend(pending, req);
+ dbus_message_unref(msg);
+
+ return 0;
+}
+
+static struct pending_req *find_request(const DBusPendingCall *call)
+{
+ GSList *l;
+
+ for (l = pending; l; l = l->next) {
+ struct pending_req *req = l->data;
+
+ if (req->call == call)
+ return req;
+ }
+
+ return NULL;
+}
+
+static void remove_pending(DBusPendingCall *call)
+{
+ struct pending_req *req = find_request(call);
+
+ pending = g_slist_remove(pending, req);
+ pending_req_finalize(req);
+}
+
+static void last_number_call_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ void *telephony_device = user_data;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("csd replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ telephony_dial_number_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ } else
+ telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);
+
+ dbus_message_unref(reply);
+ remove_pending(call);
+}
+
+void telephony_last_dialed_number_req(void *telephony_device)
+{
+ int ret;
+
+ DBG("telephony-maemo6: last dialed number request");
+
+ ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE, "CreateFromLast",
+ last_number_call_reply, telephony_device,
+ DBUS_TYPE_INVALID);
+ if (ret < 0)
+ telephony_dial_number_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+}
+
+static const char *memory_dial_lookup(int location)
+{
+ if (location == 1)
+ return vmbx;
+ else
+ return NULL;
+}
+
+void telephony_dial_number_req(void *telephony_device, const char *number)
+{
+ int ret;
+
+ DBG("telephony-maemo6: dial request to %s", number);
+
+ if (strncmp(number, "*31#", 4) == 0)
+ number += 4;
+ else if (strncmp(number, "#31#", 4) == 0)
+ number += 4;
+ else if (number[0] == '>') {
+ const char *location = &number[1];
+
+ number = memory_dial_lookup(strtol(&number[1], NULL, 0));
+ if (!number) {
+ error("No number at memory location %s", location);
+ telephony_dial_number_rsp(telephony_device,
+ CME_ERROR_INVALID_INDEX);
+ return;
+ }
+ }
+
+ ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE, "Create",
+ NULL, NULL,
+ DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INVALID);
+ if (ret < 0) {
+ telephony_dial_number_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ return;
+ }
+
+ telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_transmit_dtmf_req(void *telephony_device, char tone)
+{
+ int ret;
+ char buf[2] = { tone, '\0' }, *buf_ptr = buf;
+
+ DBG("telephony-maemo6: transmit dtmf: %s", buf);
+
+ ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE, "SendDTMF",
+ NULL, NULL,
+ DBUS_TYPE_STRING, &buf_ptr,
+ DBUS_TYPE_INVALID);
+ if (ret < 0) {
+ telephony_transmit_dtmf_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ return;
+ }
+
+ telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_subscriber_number_req(void *telephony_device)
+{
+ DBG("telephony-maemo6: subscriber number request");
+ if (msisdn)
+ telephony_subscriber_number_ind(msisdn,
+ number_type(msisdn),
+ SUBSCRIBER_SERVICE_VOICE);
+ telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static int csd_status_to_hfp(struct csd_call *call)
+{
+ switch (call->status) {
+ case CSD_CALL_STATUS_IDLE:
+ case CSD_CALL_STATUS_MO_RELEASE:
+ case CSD_CALL_STATUS_MT_RELEASE:
+ case CSD_CALL_STATUS_TERMINATED:
+ return -1;
+ case CSD_CALL_STATUS_CREATE:
+ return CALL_STATUS_DIALING;
+ case CSD_CALL_STATUS_WAITING:
+ return CALL_STATUS_WAITING;
+ case CSD_CALL_STATUS_PROCEEDING:
+ /* PROCEEDING can happen in outgoing/incoming */
+ if (call->originating)
+ return CALL_STATUS_DIALING;
+ else
+ return CALL_STATUS_INCOMING;
+ case CSD_CALL_STATUS_COMING:
+ return CALL_STATUS_INCOMING;
+ case CSD_CALL_STATUS_MO_ALERTING:
+ return CALL_STATUS_ALERTING;
+ case CSD_CALL_STATUS_MT_ALERTING:
+ return CALL_STATUS_INCOMING;
+ case CSD_CALL_STATUS_ANSWERED:
+ case CSD_CALL_STATUS_ACTIVE:
+ case CSD_CALL_STATUS_RECONNECT_PENDING:
+ case CSD_CALL_STATUS_SWAP_INITIATED:
+ case CSD_CALL_STATUS_HOLD_INITIATED:
+ return CALL_STATUS_ACTIVE;
+ case CSD_CALL_STATUS_RETRIEVE_INITIATED:
+ case CSD_CALL_STATUS_HOLD:
+ return CALL_STATUS_HELD;
+ default:
+ return -1;
+ }
+}
+
+void telephony_list_current_calls_req(void *telephony_device)
+{
+ GSList *l;
+ int i;
+
+ DBG("telephony-maemo6: list current calls request");
+
+ for (l = calls, i = 1; l != NULL; l = l->next, i++) {
+ struct csd_call *call = l->data;
+ int status, direction, multiparty;
+
+ status = csd_status_to_hfp(call);
+ if (status < 0)
+ continue;
+
+ direction = call->originating ?
+ CALL_DIR_OUTGOING : CALL_DIR_INCOMING;
+
+ multiparty = call->conference ?
+ CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO;
+
+ telephony_list_current_call_ind(i, direction, status,
+ CALL_MODE_VOICE, multiparty,
+ call->number,
+ number_type(call->number));
+ }
+
+ telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_operator_selection_req(void *telephony_device)
+{
+ telephony_operator_selection_ind(OPERATOR_MODE_AUTO,
+ net.operator_name ? net.operator_name : "");
+ telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static void foreach_call_with_status(int status,
+ int (*func)(struct csd_call *call))
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct csd_call *call = l->data;
+
+ if (call->status == status)
+ func(call);
+ }
+}
+
+void telephony_call_hold_req(void *telephony_device, const char *cmd)
+{
+ const char *idx;
+ struct csd_call *call;
+ int err = 0;
+
+ DBG("telephony-maemo6: got call hold request %s", cmd);
+
+ if (strlen(cmd) > 1)
+ idx = &cmd[1];
+ else
+ idx = NULL;
+
+ if (idx)
+ call = g_slist_nth_data(calls, strtol(idx, NULL, 0) - 1);
+ else
+ call = NULL;
+
+ switch (cmd[0]) {
+ case '0':
+ if (find_call_with_status(CSD_CALL_STATUS_WAITING))
+ foreach_call_with_status(CSD_CALL_STATUS_WAITING,
+ release_call);
+ else
+ foreach_call_with_status(CSD_CALL_STATUS_HOLD,
+ release_call);
+ break;
+ case '1':
+ if (idx) {
+ if (call)
+ err = release_call(call);
+ break;
+ }
+ foreach_call_with_status(CSD_CALL_STATUS_ACTIVE, release_call);
+ call = find_call_with_status(CSD_CALL_STATUS_WAITING);
+ if (call)
+ err = answer_call(call);
+ break;
+ case '2':
+ if (idx) {
+ if (call)
+ err = split_call(call);
+ } else {
+ struct csd_call *held, *wait;
+
+ call = find_call_with_status(CSD_CALL_STATUS_ACTIVE);
+ held = find_call_with_status(CSD_CALL_STATUS_HOLD);
+ wait = find_call_with_status(CSD_CALL_STATUS_WAITING);
+
+ if (wait)
+ err = answer_call(wait);
+ else if (call && held)
+ err = swap_calls();
+ else {
+ if (call)
+ err = hold_call(call);
+ if (held)
+ err = unhold_call(held);
+ }
+ }
+ break;
+ case '3':
+ if (find_call_with_status(CSD_CALL_STATUS_HOLD) ||
+ find_call_with_status(CSD_CALL_STATUS_WAITING))
+ err = create_conference();
+ break;
+ case '4':
+ err = call_transfer();
+ break;
+ default:
+ DBG("Unknown call hold request");
+ break;
+ }
+
+ if (err)
+ telephony_call_hold_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_nr_and_ec_req(void *telephony_device, gboolean enable)
+{
+ DBG("telephony-maemo6: got %s NR and EC request",
+ enable ? "enable" : "disable");
+ telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_key_press_req(void *telephony_device, const char *keys)
+{
+ struct csd_call *active, *waiting;
+ int err;
+
+ DBG("telephony-maemo6: got key press request for %s", keys);
+
+ waiting = find_call_with_status(CSD_CALL_STATUS_COMING);
+ if (!waiting)
+ waiting = find_call_with_status(CSD_CALL_STATUS_MT_ALERTING);
+ if (!waiting)
+ waiting = find_call_with_status(CSD_CALL_STATUS_PROCEEDING);
+
+ active = find_call_with_status(CSD_CALL_STATUS_ACTIVE);
+
+ if (waiting)
+ err = answer_call(waiting);
+ else if (active)
+ err = release_call(active);
+ else
+ err = 0;
+
+ if (err < 0)
+ telephony_key_press_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_key_press_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_voice_dial_req(void *telephony_device, gboolean enable)
+{
+ DBG("telephony-maemo6: got %s voice dial request",
+ enable ? "enable" : "disable");
+
+ telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED);
+}
+
+static void handle_incoming_call(DBusMessage *msg)
+{
+ const char *number, *call_path;
+ struct csd_call *call;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_OBJECT_PATH, &call_path,
+ DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected parameters in Call.Coming() signal");
+ return;
+ }
+
+ call = find_call(call_path);
+ if (!call) {
+ error("Didn't find any matching call object for %s",
+ call_path);
+ return;
+ }
+
+ DBG("Incoming call to %s from number %s", call_path, number);
+
+ g_free(call->number);
+ call->number = g_strdup(number);
+
+ if (find_call_with_status(CSD_CALL_STATUS_ACTIVE) ||
+ find_call_with_status(CSD_CALL_STATUS_HOLD))
+ telephony_call_waiting_ind(call->number,
+ number_type(call->number));
+ else
+ telephony_incoming_call_ind(call->number,
+ number_type(call->number));
+
+ telephony_update_indicator(maemo_indicators, "callsetup",
+ EV_CALLSETUP_INCOMING);
+}
+
+static void handle_outgoing_call(DBusMessage *msg)
+{
+ const char *number, *call_path;
+ struct csd_call *call;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_OBJECT_PATH, &call_path,
+ DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected parameters in Call.Created() signal");
+ return;
+ }
+
+ call = find_call(call_path);
+ if (!call) {
+ error("Didn't find any matching call object for %s",
+ call_path);
+ return;
+ }
+
+ DBG("Outgoing call from %s to number %s", call_path, number);
+
+ g_free(call->number);
+ call->number = g_strdup(number);
+
+ if (create_request_timer) {
+ g_source_remove(create_request_timer);
+ create_request_timer = 0;
+ }
+}
+
+static gboolean create_timeout(gpointer user_data)
+{
+ telephony_update_indicator(maemo_indicators, "callsetup",
+ EV_CALLSETUP_INACTIVE);
+ create_request_timer = 0;
+ return FALSE;
+}
+
+static void handle_create_requested(DBusMessage *msg)
+{
+ DBG("Call.CreateRequested()");
+
+ if (create_request_timer)
+ g_source_remove(create_request_timer);
+
+ create_request_timer = g_timeout_add_seconds(5, create_timeout, NULL);
+
+ telephony_update_indicator(maemo_indicators, "callsetup",
+ EV_CALLSETUP_OUTGOING);
+}
+
+static void call_set_status(struct csd_call *call, dbus_uint32_t status)
+{
+ dbus_uint32_t prev_status;
+ int callheld = telephony_get_indicator(maemo_indicators, "callheld");
+
+ prev_status = call->status;
+ DBG("Call %s changed from %s to %s", call->object_path,
+ call_status_str[prev_status], call_status_str[status]);
+
+ if (prev_status == status) {
+ DBG("Ignoring CSD Call state change to existing state");
+ return;
+ }
+
+ call->status = (int) status;
+
+ switch (status) {
+ case CSD_CALL_STATUS_IDLE:
+ if (call->setup) {
+ telephony_update_indicator(maemo_indicators,
+ "callsetup",
+ EV_CALLSETUP_INACTIVE);
+ if (!call->originating)
+ telephony_calling_stopped_ind();
+ }
+
+ g_free(call->number);
+ call->number = NULL;
+ call->originating = FALSE;
+ call->emergency = FALSE;
+ call->on_hold = FALSE;
+ call->conference = FALSE;
+ call->setup = FALSE;
+ break;
+ case CSD_CALL_STATUS_CREATE:
+ call->originating = TRUE;
+ call->setup = TRUE;
+ break;
+ case CSD_CALL_STATUS_COMING:
+ call->originating = FALSE;
+ call->setup = TRUE;
+ break;
+ case CSD_CALL_STATUS_PROCEEDING:
+ break;
+ case CSD_CALL_STATUS_MO_ALERTING:
+ telephony_update_indicator(maemo_indicators, "callsetup",
+ EV_CALLSETUP_ALERTING);
+ break;
+ case CSD_CALL_STATUS_MT_ALERTING:
+ /* Some headsets expect incoming call notification before they
+ * can send ATA command. When call changed status from waiting
+ * to alerting we need to send missing notification. Otherwise
+ * headsets like Nokia BH-108 or BackBeat 903 are unable to
+ * answer incoming call that was previously waiting. */
+ if (prev_status == CSD_CALL_STATUS_WAITING)
+ telephony_incoming_call_ind(call->number,
+ number_type(call->number));
+ break;
+ case CSD_CALL_STATUS_WAITING:
+ break;
+ case CSD_CALL_STATUS_ANSWERED:
+ break;
+ case CSD_CALL_STATUS_ACTIVE:
+ if (call->on_hold) {
+ call->on_hold = FALSE;
+ if (find_call_with_status(CSD_CALL_STATUS_HOLD))
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_MULTIPLE);
+ else
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_NONE);
+ } else {
+ if (!g_slist_find(active_calls, call))
+ active_calls = g_slist_prepend(active_calls, call);
+ if (g_slist_length(active_calls) == 1)
+ telephony_update_indicator(maemo_indicators,
+ "call",
+ EV_CALL_ACTIVE);
+ /* Upgrade callheld status if necessary */
+ if (callheld == EV_CALLHELD_ON_HOLD)
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_MULTIPLE);
+ telephony_update_indicator(maemo_indicators,
+ "callsetup",
+ EV_CALLSETUP_INACTIVE);
+ if (!call->originating)
+ telephony_calling_stopped_ind();
+ call->setup = FALSE;
+ }
+ break;
+ case CSD_CALL_STATUS_MO_RELEASE:
+ case CSD_CALL_STATUS_MT_RELEASE:
+ active_calls = g_slist_remove(active_calls, call);
+ if (g_slist_length(active_calls) == 0)
+ telephony_update_indicator(maemo_indicators, "call",
+ EV_CALL_INACTIVE);
+ break;
+ case CSD_CALL_STATUS_HOLD_INITIATED:
+ break;
+ case CSD_CALL_STATUS_HOLD:
+ call->on_hold = TRUE;
+ if (find_non_held_call())
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_MULTIPLE);
+ else
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_ON_HOLD);
+ break;
+ case CSD_CALL_STATUS_RETRIEVE_INITIATED:
+ break;
+ case CSD_CALL_STATUS_RECONNECT_PENDING:
+ break;
+ case CSD_CALL_STATUS_TERMINATED:
+ if (call->on_hold &&
+ !find_call_with_status(CSD_CALL_STATUS_HOLD))
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_NONE);
+ else if (callheld == EV_CALLHELD_MULTIPLE &&
+ find_call_with_status(CSD_CALL_STATUS_HOLD))
+ telephony_update_indicator(maemo_indicators,
+ "callheld",
+ EV_CALLHELD_ON_HOLD);
+ break;
+ case CSD_CALL_STATUS_SWAP_INITIATED:
+ break;
+ default:
+ error("Unknown call status %u", status);
+ break;
+ }
+}
+
+static void handle_call_status(DBusMessage *msg, const char *call_path)
+{
+ struct csd_call *call;
+ dbus_uint32_t status, cause_type, cause;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_UINT32, &status,
+ DBUS_TYPE_UINT32, &cause_type,
+ DBUS_TYPE_UINT32, &cause,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected paramters in Instance.CallStatus() signal");
+ return;
+ }
+
+ call = find_call(call_path);
+ if (!call) {
+ error("Didn't find any matching call object for %s",
+ call_path);
+ return;
+ }
+
+ if (status > 16) {
+ error("Invalid call status %u", status);
+ return;
+ }
+
+ call_set_status(call, status);
+}
+
+static void handle_conference(DBusMessage *msg, gboolean joined)
+{
+ const char *path;
+ struct csd_call *call;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected parameters in Conference.%s",
+ dbus_message_get_member(msg));
+ return;
+ }
+
+ call = find_call(path);
+ if (!call) {
+ error("Conference signal for unknown call %s", path);
+ return;
+ }
+
+ DBG("Call %s %s the conference", path, joined ? "joined" : "left");
+
+ call->conference = joined;
+}
+
+static uint8_t str2status(const char *state)
+{
+ if (g_strcmp0(state, "Home") == 0)
+ return NETWORK_REG_STATUS_HOME;
+ else if (g_strcmp0(state, "Roaming") == 0)
+ return NETWORK_REG_STATUS_ROAMING;
+ else if (g_strcmp0(state, "Offline") == 0)
+ return NETWORK_REG_STATUS_OFFLINE;
+ else if (g_strcmp0(state, "Searching") == 0)
+ return NETWORK_REG_STATUS_SEARCHING;
+ else if (g_strcmp0(state, "NoSim") == 0)
+ return NETWORK_REG_STATUS_NO_SIM;
+ else if (g_strcmp0(state, "Poweroff") == 0)
+ return NETWORK_REG_STATUS_POWEROFF;
+ else if (g_strcmp0(state, "Powersafe") == 0)
+ return NETWORK_REG_STATUS_POWERSAFE;
+ else if (g_strcmp0(state, "NoCoverage") == 0)
+ return NETWORK_REG_STATUS_NO_COVERAGE;
+ else if (g_strcmp0(state, "Reject") == 0)
+ return NETWORK_REG_STATUS_REJECTED;
+ else
+ return NETWORK_REG_STATUS_UNKOWN;
+}
+
+static void update_registration_status(const char *status)
+{
+ uint8_t new_status;
+
+ new_status = str2status(status);
+
+ if (net.status == new_status)
+ return;
+
+ switch (new_status) {
+ case NETWORK_REG_STATUS_HOME:
+ telephony_update_indicator(maemo_indicators, "roam",
+ EV_ROAM_INACTIVE);
+ if (net.status > NETWORK_REG_STATUS_ROAMING)
+ telephony_update_indicator(maemo_indicators,
+ "service",
+ EV_SERVICE_PRESENT);
+ break;
+ case NETWORK_REG_STATUS_ROAMING:
+ telephony_update_indicator(maemo_indicators, "roam",
+ EV_ROAM_ACTIVE);
+ if (net.status > NETWORK_REG_STATUS_ROAMING)
+ telephony_update_indicator(maemo_indicators,
+ "service",
+ EV_SERVICE_PRESENT);
+ break;
+ case NETWORK_REG_STATUS_OFFLINE:
+ case NETWORK_REG_STATUS_SEARCHING:
+ case NETWORK_REG_STATUS_NO_SIM:
+ case NETWORK_REG_STATUS_POWEROFF:
+ case NETWORK_REG_STATUS_POWERSAFE:
+ case NETWORK_REG_STATUS_NO_COVERAGE:
+ case NETWORK_REG_STATUS_REJECTED:
+ case NETWORK_REG_STATUS_UNKOWN:
+ if (net.status < NETWORK_REG_STATUS_OFFLINE)
+ telephony_update_indicator(maemo_indicators,
+ "service",
+ EV_SERVICE_NONE);
+ break;
+ }
+
+ net.status = new_status;
+
+ DBG("telephony-maemo6: registration status changed: %s", status);
+}
+
+static void handle_registration_changed(DBusMessage *msg)
+{
+ const char *status;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &status,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected parameters in RegistrationChanged");
+ return;
+ }
+
+ update_registration_status(status);
+}
+
+static void update_signal_strength(int32_t signal_bars)
+{
+ if (signal_bars < 0) {
+ DBG("signal strength smaller than expected: %d < 0",
+ signal_bars);
+ signal_bars = 0;
+ } else if (signal_bars > 5) {
+ DBG("signal strength greater than expected: %d > 5",
+ signal_bars);
+ signal_bars = 5;
+ }
+
+ if (net.signal_bars == signal_bars)
+ return;
+
+ telephony_update_indicator(maemo_indicators, "signal", signal_bars);
+
+ net.signal_bars = signal_bars;
+ DBG("telephony-maemo6: signal strength updated: %d/5", signal_bars);
+}
+
+static void handle_signal_bars_changed(DBusMessage *msg)
+{
+ int32_t signal_bars;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_INT32, &signal_bars,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected parameters in SignalBarsChanged");
+ return;
+ }
+
+ update_signal_strength(signal_bars);
+}
+
+static gboolean iter_get_basic_args(DBusMessageIter *iter,
+ int first_arg_type, ...)
+{
+ int type;
+ va_list ap;
+
+ va_start(ap, first_arg_type);
+
+ for (type = first_arg_type; type != DBUS_TYPE_INVALID;
+ type = va_arg(ap, int)) {
+ void *value = va_arg(ap, void *);
+ int real_type = dbus_message_iter_get_arg_type(iter);
+
+ if (real_type != type) {
+ error("iter_get_basic_args: expected %c but got %c",
+ (char) type, (char) real_type);
+ break;
+ }
+
+ dbus_message_iter_get_basic(iter, value);
+ dbus_message_iter_next(iter);
+ }
+
+ va_end(ap);
+
+ return type == DBUS_TYPE_INVALID ? TRUE : FALSE;
+}
+
+static void hal_battery_level_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ dbus_int32_t level;
+ int *value = user_data;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("hald replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ if (!dbus_message_get_args(reply, NULL,
+ DBUS_TYPE_INT32, &level,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected args in hald reply");
+ goto done;
+ }
+
+ *value = (int) level;
+
+ if (value == &battchg_last)
+ DBG("telephony-maemo6: battery.charge_level.last_full is %d",
+ *value);
+ else if (value == &battchg_design)
+ DBG("telephony-maemo6: battery.charge_level.design is %d",
+ *value);
+ else
+ DBG("telephony-maemo6: battery.charge_level.current is %d",
+ *value);
+
+ if ((battchg_design > 0 || battchg_last > 0) && battchg_cur >= 0) {
+ int new, max;
+
+ if (battchg_last > 0)
+ max = battchg_last;
+ else
+ max = battchg_design;
+
+ new = battchg_cur * 5 / max;
+
+ telephony_update_indicator(maemo_indicators, "battchg", new);
+ }
+
+done:
+ dbus_message_unref(reply);
+ remove_pending(call);
+}
+
+static void hal_get_integer(const char *path, const char *key, void *user_data)
+{
+ send_method_call("org.freedesktop.Hal", path,
+ "org.freedesktop.Hal.Device",
+ "GetPropertyInteger",
+ hal_battery_level_reply, user_data,
+ DBUS_TYPE_STRING, &key,
+ DBUS_TYPE_INVALID);
+}
+
+static void handle_hal_property_modified(DBusMessage *msg)
+{
+ DBusMessageIter iter, array;
+ dbus_int32_t num_changes;
+ const char *path;
+
+ path = dbus_message_get_path(msg);
+
+ dbus_message_iter_init(msg, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
+ error("Unexpected signature in hal PropertyModified signal");
+ return;
+ }
+
+ dbus_message_iter_get_basic(&iter, &num_changes);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ error("Unexpected signature in hal PropertyModified signal");
+ return;
+ }
+
+ dbus_message_iter_recurse(&iter, &array);
+
+ while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) {
+ DBusMessageIter prop;
+ const char *name;
+ dbus_bool_t added, removed;
+
+ dbus_message_iter_recurse(&array, &prop);
+
+ if (!iter_get_basic_args(&prop,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_BOOLEAN, &added,
+ DBUS_TYPE_BOOLEAN, &removed,
+ DBUS_TYPE_INVALID)) {
+ error("Invalid hal PropertyModified parameters");
+ break;
+ }
+
+ if (g_str_equal(name, "battery.charge_level.last_full"))
+ hal_get_integer(path, name, &battchg_last);
+ else if (g_str_equal(name, "battery.charge_level.current"))
+ hal_get_integer(path, name, &battchg_cur);
+ else if (g_str_equal(name, "battery.charge_level.design"))
+ hal_get_integer(path, name, &battchg_design);
+
+ dbus_message_iter_next(&array);
+ }
+}
+
+static void csd_call_free(struct csd_call *call)
+{
+ if (!call)
+ return;
+
+ g_free(call->object_path);
+ g_free(call->number);
+
+ g_free(call);
+}
+
+static void parse_call_list(DBusMessageIter *iter)
+{
+ do {
+ DBusMessageIter call_iter;
+ struct csd_call *call;
+ const char *object_path, *number;
+ dbus_uint32_t status;
+ dbus_bool_t originating, terminating, emerg, on_hold, conf;
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT) {
+ error("Unexpected signature in GetCallInfoAll reply");
+ break;
+ }
+
+ dbus_message_iter_recurse(iter, &call_iter);
+
+ if (!iter_get_basic_args(&call_iter,
+ DBUS_TYPE_OBJECT_PATH, &object_path,
+ DBUS_TYPE_UINT32, &status,
+ DBUS_TYPE_BOOLEAN, &originating,
+ DBUS_TYPE_BOOLEAN, &terminating,
+ DBUS_TYPE_BOOLEAN, &emerg,
+ DBUS_TYPE_BOOLEAN, &on_hold,
+ DBUS_TYPE_BOOLEAN, &conf,
+ DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_INVALID)) {
+ error("Parsing call D-Bus parameters failed");
+ break;
+ }
+
+ call = find_call(object_path);
+ if (!call) {
+ call = g_new0(struct csd_call, 1);
+ call->object_path = g_strdup(object_path);
+ calls = g_slist_append(calls, call);
+ DBG("telephony-maemo6: new csd call instance at %s",
+ object_path);
+ }
+
+ if (status == CSD_CALL_STATUS_IDLE)
+ continue;
+
+ /* CSD gives incorrect call_hold property sometimes */
+ if ((call->status != CSD_CALL_STATUS_HOLD && on_hold) ||
+ (call->status == CSD_CALL_STATUS_HOLD &&
+ !on_hold)) {
+ error("Conflicting call status and on_hold property!");
+ on_hold = call->status == CSD_CALL_STATUS_HOLD;
+ }
+
+ call->originating = originating;
+ call->on_hold = on_hold;
+ call->conference = conf;
+ g_free(call->number);
+ call->number = g_strdup(number);
+
+ /* Update indicators */
+ call_set_status(call, status);
+
+ } while (dbus_message_iter_next(iter));
+}
+
+static void update_operator_name(const char *name)
+{
+ if (name == NULL)
+ return;
+
+ g_free(net.operator_name);
+ net.operator_name = g_strndup(name, 16);
+ DBG("telephony-maemo6: operator name updated: %s", name);
+}
+
+static void get_property_reply(DBusPendingCall *call, void *user_data)
+{
+ char *prop = user_data;
+ DBusError err;
+ DBusMessage *reply;
+ DBusMessageIter iter, sub;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("csd replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_message_iter_init(reply, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
+ error("Unexpected signature in Get return");
+ goto done;
+ }
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ if (g_strcmp0(prop, "RegistrationStatus") == 0) {
+ const char *status;
+
+ dbus_message_iter_get_basic(&sub, &status);
+ update_registration_status(status);
+
+ get_property(CSD_CSNET_OPERATOR, "OperatorName");
+ get_property(CSD_CSNET_SIGNAL, "SignalBars");
+ } else if (g_strcmp0(prop, "OperatorName") == 0) {
+ const char *name;
+
+ dbus_message_iter_get_basic(&sub, &name);
+ update_operator_name(name);
+ } else if (g_strcmp0(prop, "SignalBars") == 0) {
+ int32_t signal_bars;
+
+ dbus_message_iter_get_basic(&sub, &signal_bars);
+ update_signal_strength(signal_bars);
+ }
+
+done:
+ g_free(prop);
+ dbus_message_unref(reply);
+ remove_pending(call);
+}
+
+static int get_property(const char *iface, const char *prop)
+{
+ return send_method_call(CSD_CSNET_BUS_NAME, CSD_CSNET_PATH,
+ DBUS_INTERFACE_PROPERTIES, "Get",
+ get_property_reply, g_strdup(prop),
+ DBUS_TYPE_STRING, &iface,
+ DBUS_TYPE_STRING, &prop,
+ DBUS_TYPE_INVALID);
+}
+
+static void handle_operator_name_changed(DBusMessage *msg)
+{
+ const char *name;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected parameters in OperatorNameChanged");
+ return;
+ }
+
+ update_operator_name(name);
+}
+
+static void call_info_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ DBusMessageIter iter, sub;;
+
+ get_calls_active = FALSE;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("csd replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_message_iter_init(reply, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ error("Unexpected signature in GetCallInfoAll return");
+ goto done;
+ }
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ parse_call_list(&sub);
+
+ get_property(CSD_CSNET_REGISTRATION, "RegistrationStatus");
+
+done:
+ dbus_message_unref(reply);
+ remove_pending(call);
+}
+
+
+static void phonebook_read_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError derr;
+ DBusMessage *reply;
+ const char *name, *number, *secondname, *additionalnumber, *email;
+ int index;
+ char **number_type = user_data;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&derr);
+ if (dbus_set_error_from_message(&derr, reply)) {
+ error("%s.ReadFirst replied with an error: %s, %s",
+ CSD_SIMPB_INTERFACE, derr.name, derr.message);
+ dbus_error_free(&derr);
+ if (number_type == &vmbx)
+ vmbx = g_strdup(getenv("VMBX_NUMBER"));
+ goto done;
+ }
+
+ dbus_error_init(&derr);
+ if (dbus_message_get_args(reply, NULL,
+ DBUS_TYPE_INT32, &index,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_STRING, &secondname,
+ DBUS_TYPE_STRING, &additionalnumber,
+ DBUS_TYPE_STRING, &email,
+ DBUS_TYPE_INVALID) == FALSE) {
+ error("Unable to parse %s.ReadFirst arguments: %s, %s",
+ CSD_SIMPB_INTERFACE, derr.name, derr.message);
+ dbus_error_free(&derr);
+ goto done;
+ }
+
+ if (number_type == &msisdn) {
+ g_free(msisdn);
+ msisdn = g_strdup(number);
+ DBG("Got MSISDN %s (%s)", number, name);
+ } else {
+ g_free(vmbx);
+ vmbx = g_strdup(number);
+ DBG("Got voice mailbox number %s (%s)", number, name);
+ }
+
+done:
+ dbus_message_unref(reply);
+ remove_pending(call);
+}
+
+static void csd_init(void)
+{
+ const char *pb_type;
+ int ret;
+
+ ret = send_method_call(CSD_CALL_BUS_NAME, CSD_CALL_PATH,
+ CSD_CALL_INTERFACE, "GetCallInfoAll",
+ call_info_reply, NULL, DBUS_TYPE_INVALID);
+ if (ret < 0) {
+ error("Unable to sent GetCallInfoAll method call");
+ return;
+ }
+
+ get_calls_active = TRUE;
+
+ pb_type = CSD_SIMPB_TYPE_MSISDN;
+
+ ret = send_method_call(CSD_SIMPB_BUS_NAME, CSD_SIMPB_PATH,
+ CSD_SIMPB_INTERFACE, "ReadFirst",
+ phonebook_read_reply, &msisdn,
+ DBUS_TYPE_STRING, &pb_type,
+ DBUS_TYPE_INVALID);
+ if (ret < 0) {
+ error("Unable to send " CSD_SIMPB_INTERFACE ".read()");
+ return;
+ }
+
+ /* Voicemail should be in MBDN index 0 */
+ pb_type = CSD_SIMPB_TYPE_MBDN;
+
+ ret = send_method_call(CSD_SIMPB_BUS_NAME, CSD_SIMPB_PATH,
+ CSD_SIMPB_INTERFACE, "ReadFirst",
+ phonebook_read_reply, &vmbx,
+ DBUS_TYPE_STRING, &pb_type,
+ DBUS_TYPE_INVALID);
+ if (ret < 0) {
+ error("Unable to send " CSD_SIMPB_INTERFACE ".read()");
+ return;
+ }
+}
+
+static void handle_modem_state(DBusMessage *msg)
+{
+ const char *state;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &state,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected modem state parameters");
+ return;
+ }
+
+ DBG("SSC modem state: %s", state);
+
+ if (calls != NULL || get_calls_active)
+ return;
+
+ if (g_str_equal(state, "cmt_ready") || g_str_equal(state, "online"))
+ csd_init();
+}
+
+static void modem_state_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError err;
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("get_modem_state: %s, %s", err.name, err.message);
+ dbus_error_free(&err);
+ } else
+ handle_modem_state(reply);
+
+ dbus_message_unref(reply);
+ remove_pending(call);
+}
+
+static gboolean signal_filter(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ const char *path = dbus_message_get_path(msg);
+
+ if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Coming"))
+ handle_incoming_call(msg);
+ else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE, "Created"))
+ handle_outgoing_call(msg);
+ else if (dbus_message_is_signal(msg, CSD_CALL_INTERFACE,
+ "CreateRequested"))
+ handle_create_requested(msg);
+ else if (dbus_message_is_signal(msg, CSD_CALL_INSTANCE, "CallStatus"))
+ handle_call_status(msg, path);
+ else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Joined"))
+ handle_conference(msg, TRUE);
+ else if (dbus_message_is_signal(msg, CSD_CALL_CONFERENCE, "Left"))
+ handle_conference(msg, FALSE);
+ else if (dbus_message_is_signal(msg, CSD_CSNET_REGISTRATION,
+ "RegistrationChanged"))
+ handle_registration_changed(msg);
+ else if (dbus_message_is_signal(msg, CSD_CSNET_OPERATOR,
+ "OperatorNameChanged"))
+ handle_operator_name_changed(msg);
+ else if (dbus_message_is_signal(msg, CSD_CSNET_SIGNAL,
+ "SignalBarsChanged"))
+ handle_signal_bars_changed(msg);
+ else if (dbus_message_is_signal(msg, "org.freedesktop.Hal.Device",
+ "PropertyModified"))
+ handle_hal_property_modified(msg);
+ else if (dbus_message_is_signal(msg, SSC_DBUS_IFACE,
+ "modem_state_changed_ind"))
+ handle_modem_state(msg);
+
+ return TRUE;
+}
+
+static void add_watch(const char *sender, const char *path,
+ const char *interface, const char *member)
+{
+ guint watch;
+
+ watch = g_dbus_add_signal_watch(connection, sender, path, interface,
+ member, signal_filter, NULL, NULL);
+
+ watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch));
+}
+
+static void hal_find_device_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ DBusMessageIter iter, sub;
+ const char *path;
+ int type;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("hald replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_message_iter_init(reply, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ error("Unexpected signature in FindDeviceByCapability return");
+ goto done;
+ }
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ type = dbus_message_iter_get_arg_type(&sub);
+
+ if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) {
+ error("No hal device with battery capability found");
+ goto done;
+ }
+
+ dbus_message_iter_get_basic(&sub, &path);
+
+ DBG("telephony-maemo6: found battery device at %s", path);
+
+ add_watch(NULL, path, "org.freedesktop.Hal.Device",
+ "PropertyModified");
+
+ hal_get_integer(path, "battery.charge_level.last_full", &battchg_last);
+ hal_get_integer(path, "battery.charge_level.current", &battchg_cur);
+ hal_get_integer(path, "battery.charge_level.design", &battchg_design);
+
+done:
+ dbus_message_unref(reply);
+ remove_pending(call);
+}
+
+int telephony_init(void)
+{
+ const char *battery_cap = "battery";
+ uint32_t features = AG_FEATURE_EC_ANDOR_NR |
+ AG_FEATURE_INBAND_RINGTONE |
+ AG_FEATURE_REJECT_A_CALL |
+ AG_FEATURE_ENHANCED_CALL_STATUS |
+ AG_FEATURE_ENHANCED_CALL_CONTROL |
+ AG_FEATURE_EXTENDED_ERROR_RESULT_CODES |
+ AG_FEATURE_THREE_WAY_CALLING;
+ int i;
+
+ DBG("");
+
+ connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+ add_watch(NULL, NULL, CSD_CALL_INTERFACE, NULL);
+ add_watch(NULL, NULL, CSD_CALL_INSTANCE, NULL);
+ add_watch(NULL, NULL, CSD_CALL_CONFERENCE, NULL);
+ add_watch(NULL, NULL, CSD_CSNET_REGISTRATION, "RegistrationChanged");
+ add_watch(NULL, NULL, CSD_CSNET_OPERATOR, "OperatorNameChanged");
+ add_watch(NULL, NULL, CSD_CSNET_SIGNAL, "SignalBarsChanged");
+ add_watch(NULL, NULL, SSC_DBUS_IFACE, "modem_state_changed_ind");
+
+ if (send_method_call(SSC_DBUS_NAME, SSC_DBUS_PATH, SSC_DBUS_IFACE,
+ "get_modem_state", modem_state_reply,
+ NULL, DBUS_TYPE_INVALID) < 0)
+ error("Unable to send " SSC_DBUS_IFACE ".get_modem_state()");
+
+ /* Reset indicators */
+ for (i = 0; maemo_indicators[i].desc != NULL; i++) {
+ if (g_str_equal(maemo_indicators[i].desc, "battchg"))
+ maemo_indicators[i].val = 5;
+ else
+ maemo_indicators[i].val = 0;
+ }
+
+ telephony_ready_ind(features, maemo_indicators, BTRH_NOT_SUPPORTED,
+ chld_str);
+ if (send_method_call("org.freedesktop.Hal",
+ "/org/freedesktop/Hal/Manager",
+ "org.freedesktop.Hal.Manager",
+ "FindDeviceByCapability",
+ hal_find_device_reply, NULL,
+ DBUS_TYPE_STRING, &battery_cap,
+ DBUS_TYPE_INVALID) < 0)
+ error("Unable to send HAL method call");
+
+ return 0;
+}
+
+static void remove_watch(gpointer data)
+{
+ g_dbus_remove_watch(connection, GPOINTER_TO_UINT(data));
+}
+
+void telephony_exit(void)
+{
+ DBG("");
+
+ g_free(net.operator_name);
+ net.operator_name = NULL;
+
+ net.status = NETWORK_REG_STATUS_UNKOWN;
+ net.signal_bars = 0;
+
+ g_slist_free(active_calls);
+ active_calls = NULL;
+
+ g_slist_foreach(calls, (GFunc) csd_call_free, NULL);
+ g_slist_free(calls);
+ calls = NULL;
+
+ g_slist_foreach(pending, (GFunc) pending_req_finalize, NULL);
+ g_slist_free(pending);
+ pending = NULL;
+
+ g_slist_foreach(watches, (GFunc) remove_watch, NULL);
+ g_slist_free(watches);
+ watches = NULL;
+
+ dbus_connection_unref(connection);
+ connection = NULL;
+
+ telephony_deinit();
+}
diff --git a/audio/telephony-ofono.c b/audio/telephony-ofono.c
new file mode 100644
index 0000000..6f5685b
--- /dev/null
+++ b/audio/telephony-ofono.c
@@ -0,0 +1,1627 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2009-2010 Intel Corporation
+ * Copyright (C) 2006-2009 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "telephony.h"
+
+enum net_registration_status {
+ NETWORK_REG_STATUS_HOME = 0x00,
+ NETWORK_REG_STATUS_ROAM,
+ NETWORK_REG_STATUS_NOSERV
+};
+
+struct voice_call {
+ char *obj_path;
+ int status;
+ gboolean originating;
+ gboolean conference;
+ char *number;
+ guint watch;
+};
+
+static DBusConnection *connection = NULL;
+static char *modem_obj_path = NULL;
+static char *last_dialed_number = NULL;
+static GSList *calls = NULL;
+static GSList *watches = NULL;
+static GSList *pending = NULL;
+
+#define OFONO_BUS_NAME "org.ofono"
+#define OFONO_PATH "/"
+#define OFONO_MODEM_INTERFACE "org.ofono.Modem"
+#define OFONO_MANAGER_INTERFACE "org.ofono.Manager"
+#define OFONO_NETWORKREG_INTERFACE "org.ofono.NetworkRegistration"
+#define OFONO_VCMANAGER_INTERFACE "org.ofono.VoiceCallManager"
+#define OFONO_VC_INTERFACE "org.ofono.VoiceCall"
+
+/* HAL battery namespace key values */
+static int battchg_cur = -1; /* "battery.charge_level.current" */
+static int battchg_last = -1; /* "battery.charge_level.last_full" */
+static int battchg_design = -1; /* "battery.charge_level.design" */
+
+static struct {
+ uint8_t status;
+ uint32_t signals_bar;
+ char *operator_name;
+} net = {
+ .status = NETWORK_REG_STATUS_NOSERV,
+ .signals_bar = 0,
+ .operator_name = NULL,
+};
+
+static const char *chld_str = "0,1,1x,2,2x,3,4";
+static char *subscriber_number = NULL;
+
+static gboolean events_enabled = FALSE;
+
+static struct indicator ofono_indicators[] =
+{
+ { "battchg", "0-5", 5, TRUE },
+ { "signal", "0-5", 5, TRUE },
+ { "service", "0,1", 1, TRUE },
+ { "call", "0,1", 0, TRUE },
+ { "callsetup", "0-3", 0, TRUE },
+ { "callheld", "0-2", 0, FALSE },
+ { "roam", "0,1", 0, TRUE },
+ { NULL }
+};
+
+static struct voice_call *find_vc(const char *path)
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct voice_call *vc = l->data;
+
+ if (g_str_equal(vc->obj_path, path))
+ return vc;
+ }
+
+ return NULL;
+}
+
+static struct voice_call *find_vc_with_status(int status)
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct voice_call *vc = l->data;
+
+ if (vc->status == status)
+ return vc;
+ }
+
+ return NULL;
+}
+
+static struct voice_call *find_vc_without_status(int status)
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct voice_call *call = l->data;
+
+ if (call->status != status)
+ return call;
+ }
+
+ return NULL;
+}
+
+static int number_type(const char *number)
+{
+ if (number == NULL)
+ return NUMBER_TYPE_TELEPHONY;
+
+ if (number[0] == '+' || strncmp(number, "00", 2) == 0)
+ return NUMBER_TYPE_INTERNATIONAL;
+
+ return NUMBER_TYPE_TELEPHONY;
+}
+
+void telephony_device_connected(void *telephony_device)
+{
+ struct voice_call *coming;
+
+ DBG("telephony-ofono: device %p connected", telephony_device);
+
+ coming = find_vc_with_status(CALL_STATUS_ALERTING);
+ if (coming) {
+ if (find_vc_with_status(CALL_STATUS_ACTIVE))
+ telephony_call_waiting_ind(coming->number,
+ number_type(coming->number));
+ else
+ telephony_incoming_call_ind(coming->number,
+ number_type(coming->number));
+ }
+}
+
+void telephony_device_disconnected(void *telephony_device)
+{
+ DBG("telephony-ofono: device %p disconnected", telephony_device);
+ events_enabled = FALSE;
+}
+
+void telephony_event_reporting_req(void *telephony_device, int ind)
+{
+ events_enabled = ind == 1 ? TRUE : FALSE;
+
+ telephony_event_reporting_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_response_and_hold_req(void *telephony_device, int rh)
+{
+ telephony_response_and_hold_rsp(telephony_device,
+ CME_ERROR_NOT_SUPPORTED);
+}
+
+void telephony_last_dialed_number_req(void *telephony_device)
+{
+ DBG("telephony-ofono: last dialed number request");
+
+ if (last_dialed_number)
+ telephony_dial_number_req(telephony_device, last_dialed_number);
+ else
+ telephony_last_dialed_number_rsp(telephony_device,
+ CME_ERROR_NOT_ALLOWED);
+}
+
+static int send_method_call(const char *dest, const char *path,
+ const char *interface, const char *method,
+ DBusPendingCallNotifyFunction cb,
+ void *user_data, int type, ...)
+{
+ DBusMessage *msg;
+ DBusPendingCall *call;
+ va_list args;
+
+ msg = dbus_message_new_method_call(dest, path, interface, method);
+ if (!msg) {
+ error("Unable to allocate new D-Bus %s message", method);
+ return -ENOMEM;
+ }
+
+ va_start(args, type);
+
+ if (!dbus_message_append_args_valist(msg, type, args)) {
+ dbus_message_unref(msg);
+ va_end(args);
+ return -EIO;
+ }
+
+ va_end(args);
+
+ if (!cb) {
+ g_dbus_send_message(connection, msg);
+ return 0;
+ }
+
+ if (!dbus_connection_send_with_reply(connection, msg, &call, -1)) {
+ error("Sending %s failed", method);
+ dbus_message_unref(msg);
+ return -EIO;
+ }
+
+ dbus_pending_call_set_notify(call, cb, user_data, NULL);
+ pending = g_slist_prepend(pending, call);
+ dbus_message_unref(msg);
+
+ return 0;
+}
+
+static int answer_call(struct voice_call *vc)
+{
+ DBG("%s", vc->number);
+ return send_method_call(OFONO_BUS_NAME, vc->obj_path,
+ OFONO_VC_INTERFACE, "Answer",
+ NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int release_call(struct voice_call *vc)
+{
+ DBG("%s", vc->number);
+ return send_method_call(OFONO_BUS_NAME, vc->obj_path,
+ OFONO_VC_INTERFACE, "Hangup",
+ NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int release_answer_calls()
+{
+ DBG("");
+ return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+ OFONO_VCMANAGER_INTERFACE,
+ "ReleaseAndAnswer",
+ NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int split_call(struct voice_call *call)
+{
+ DBG("%s", call->number);
+ return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+ OFONO_VCMANAGER_INTERFACE,
+ "PrivateChat",
+ NULL, NULL,
+ DBUS_TYPE_OBJECT_PATH,
+ call->obj_path,
+ DBUS_TYPE_INVALID);
+ return -1;
+}
+
+static int swap_calls(void)
+{
+ DBG("");
+ return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+ OFONO_VCMANAGER_INTERFACE,
+ "SwapCalls",
+ NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int create_conference(void)
+{
+ DBG("");
+ return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+ OFONO_VCMANAGER_INTERFACE,
+ "CreateMultiparty",
+ NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int release_conference(void)
+{
+ DBG("");
+ return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+ OFONO_VCMANAGER_INTERFACE,
+ "HangupMultiparty",
+ NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+static int call_transfer(void)
+{
+ DBG("");
+ return send_method_call(OFONO_BUS_NAME, modem_obj_path,
+ OFONO_VCMANAGER_INTERFACE,
+ "Transfer",
+ NULL, NULL, DBUS_TYPE_INVALID);
+}
+
+void telephony_terminate_call_req(void *telephony_device)
+{
+ struct voice_call *call;
+ struct voice_call *alerting;
+ int err;
+
+ call = find_vc_with_status(CALL_STATUS_ACTIVE);
+ if (!call)
+ call = calls->data;
+
+ if (!call) {
+ error("No active call");
+ telephony_terminate_call_rsp(telephony_device,
+ CME_ERROR_NOT_ALLOWED);
+ return;
+ }
+
+ alerting = find_vc_with_status(CALL_STATUS_ALERTING);
+ if (call->status == CALL_STATUS_HELD && alerting)
+ err = release_call(alerting);
+ else if (call->conference)
+ err = release_conference();
+ else
+ err = release_call(call);
+
+ if (err < 0)
+ telephony_terminate_call_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_terminate_call_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_answer_call_req(void *telephony_device)
+{
+ struct voice_call *vc;
+ int ret;
+
+ vc = find_vc_with_status(CALL_STATUS_INCOMING);
+ if (!vc)
+ vc = find_vc_with_status(CALL_STATUS_ALERTING);
+
+ if (!vc)
+ vc = find_vc_with_status(CALL_STATUS_WAITING);
+
+ if (!vc) {
+ telephony_answer_call_rsp(telephony_device,
+ CME_ERROR_NOT_ALLOWED);
+ return;
+ }
+
+ ret = answer_call(vc);
+ if (ret < 0) {
+ telephony_answer_call_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ return;
+ }
+
+ telephony_answer_call_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_dial_number_req(void *telephony_device, const char *number)
+{
+ const char *clir;
+ int ret;
+
+ DBG("telephony-ofono: dial request to %s", number);
+
+ if (!modem_obj_path) {
+ telephony_dial_number_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ return;
+ }
+
+ if (!strncmp(number, "*31#", 4)) {
+ number += 4;
+ clir = "enabled";
+ } else if (!strncmp(number, "#31#", 4)) {
+ number += 4;
+ clir = "disabled";
+ } else
+ clir = "default";
+
+ ret = send_method_call(OFONO_BUS_NAME, modem_obj_path,
+ OFONO_VCMANAGER_INTERFACE,
+ "Dial", NULL, NULL,
+ DBUS_TYPE_STRING, &number,
+ DBUS_TYPE_STRING, &clir,
+ DBUS_TYPE_INVALID);
+
+ if (ret < 0)
+ telephony_dial_number_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_dial_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_transmit_dtmf_req(void *telephony_device, char tone)
+{
+ char *tone_string;
+ int ret;
+
+ DBG("telephony-ofono: transmit dtmf: %c", tone);
+
+ if (!modem_obj_path) {
+ telephony_transmit_dtmf_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ return;
+ }
+
+ tone_string = g_strdup_printf("%c", tone);
+ ret = send_method_call(OFONO_BUS_NAME, modem_obj_path,
+ OFONO_VCMANAGER_INTERFACE,
+ "SendTones", NULL, NULL,
+ DBUS_TYPE_STRING, &tone_string,
+ DBUS_TYPE_INVALID);
+ g_free(tone_string);
+
+ if (ret < 0)
+ telephony_transmit_dtmf_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_transmit_dtmf_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_subscriber_number_req(void *telephony_device)
+{
+ DBG("telephony-ofono: subscriber number request");
+
+ if (subscriber_number)
+ telephony_subscriber_number_ind(subscriber_number,
+ NUMBER_TYPE_TELEPHONY,
+ SUBSCRIBER_SERVICE_VOICE);
+ telephony_subscriber_number_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_list_current_calls_req(void *telephony_device)
+{
+ GSList *l;
+ int i;
+
+ DBG("telephony-ofono: list current calls request");
+
+ for (l = calls, i = 1; l != NULL; l = l->next, i++) {
+ struct voice_call *vc = l->data;
+ int direction, multiparty;
+
+ direction = vc->originating ?
+ CALL_DIR_OUTGOING : CALL_DIR_INCOMING;
+
+ multiparty = vc->conference ?
+ CALL_MULTIPARTY_YES : CALL_MULTIPARTY_NO;
+
+ DBG("call %s direction %d multiparty %d", vc->number,
+ direction, multiparty);
+
+ telephony_list_current_call_ind(i, direction, vc->status,
+ CALL_MODE_VOICE, multiparty,
+ vc->number, number_type(vc->number));
+ }
+
+ telephony_list_current_calls_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_operator_selection_req(void *telephony_device)
+{
+ DBG("telephony-ofono: operator selection request");
+
+ telephony_operator_selection_ind(OPERATOR_MODE_AUTO,
+ net.operator_name ? net.operator_name : "");
+ telephony_operator_selection_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+static void foreach_vc_with_status(int status,
+ int (*func)(struct voice_call *vc))
+{
+ GSList *l;
+
+ for (l = calls; l != NULL; l = l->next) {
+ struct voice_call *call = l->data;
+
+ if (call->status == status)
+ func(call);
+ }
+}
+
+void telephony_call_hold_req(void *telephony_device, const char *cmd)
+{
+ const char *idx;
+ struct voice_call *call;
+ int err = 0;
+
+ DBG("telephony-ofono: got call hold request %s", cmd);
+
+ if (strlen(cmd) > 1)
+ idx = &cmd[1];
+ else
+ idx = NULL;
+
+ if (idx)
+ call = g_slist_nth_data(calls, strtol(idx, NULL, 0) - 1);
+ else
+ call = NULL;
+
+ switch (cmd[0]) {
+ case '0':
+ if (find_vc_with_status(CALL_STATUS_WAITING))
+ foreach_vc_with_status(CALL_STATUS_WAITING,
+ release_call);
+ else
+ foreach_vc_with_status(CALL_STATUS_HELD, release_call);
+ break;
+ case '1':
+ if (idx) {
+ if (call)
+ err = release_call(call);
+ break;
+ }
+ err = release_answer_calls();
+ break;
+ case '2':
+ if (idx) {
+ if (call)
+ err = split_call(call);
+ } else {
+ call = find_vc_with_status(CALL_STATUS_WAITING);
+
+ if (call)
+ err = answer_call(call);
+ else
+ err = swap_calls();
+ }
+ break;
+ case '3':
+ if (find_vc_with_status(CALL_STATUS_HELD) ||
+ find_vc_with_status(CALL_STATUS_WAITING))
+ err = create_conference();
+ break;
+ case '4':
+ err = call_transfer();
+ break;
+ default:
+ DBG("Unknown call hold request");
+ break;
+ }
+
+ if (err)
+ telephony_call_hold_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_call_hold_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_nr_and_ec_req(void *telephony_device, gboolean enable)
+{
+ DBG("telephony-ofono: got %s NR and EC request",
+ enable ? "enable" : "disable");
+
+ telephony_nr_and_ec_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_key_press_req(void *telephony_device, const char *keys)
+{
+ struct voice_call *active, *incoming;
+ int err;
+
+ DBG("telephony-ofono: got key press request for %s", keys);
+
+ incoming = find_vc_with_status(CALL_STATUS_INCOMING);
+
+ active = find_vc_with_status(CALL_STATUS_ACTIVE);
+
+ if (incoming)
+ err = answer_call(incoming);
+ else if (active)
+ err = release_call(active);
+ else
+ err = 0;
+
+ if (err < 0)
+ telephony_key_press_rsp(telephony_device,
+ CME_ERROR_AG_FAILURE);
+ else
+ telephony_key_press_rsp(telephony_device, CME_ERROR_NONE);
+}
+
+void telephony_voice_dial_req(void *telephony_device, gboolean enable)
+{
+ DBG("telephony-ofono: got %s voice dial request",
+ enable ? "enable" : "disable");
+
+ telephony_voice_dial_rsp(telephony_device, CME_ERROR_NOT_SUPPORTED);
+}
+
+static gboolean iter_get_basic_args(DBusMessageIter *iter,
+ int first_arg_type, ...)
+{
+ int type;
+ va_list ap;
+
+ va_start(ap, first_arg_type);
+
+ for (type = first_arg_type; type != DBUS_TYPE_INVALID;
+ type = va_arg(ap, int)) {
+ void *value = va_arg(ap, void *);
+ int real_type = dbus_message_iter_get_arg_type(iter);
+
+ if (real_type != type) {
+ error("iter_get_basic_args: expected %c but got %c",
+ (char) type, (char) real_type);
+ break;
+ }
+
+ dbus_message_iter_get_basic(iter, value);
+ dbus_message_iter_next(iter);
+ }
+
+ va_end(ap);
+
+ return type == DBUS_TYPE_INVALID ? TRUE : FALSE;
+}
+
+static void call_free(struct voice_call *vc)
+{
+ DBG("%s", vc->obj_path);
+
+ if (vc->status == CALL_STATUS_ACTIVE)
+ telephony_update_indicator(ofono_indicators, "call",
+ EV_CALL_INACTIVE);
+ else
+ telephony_update_indicator(ofono_indicators, "callsetup",
+ EV_CALLSETUP_INACTIVE);
+
+ if (vc->status == CALL_STATUS_INCOMING)
+ telephony_calling_stopped_ind();
+
+ g_dbus_remove_watch(connection, vc->watch);
+ g_free(vc->obj_path);
+ g_free(vc->number);
+ g_free(vc);
+}
+
+static gboolean handle_vc_property_changed(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct voice_call *vc = data;
+ const char *obj_path = dbus_message_get_path(msg);
+ DBusMessageIter iter, sub;
+ const char *property, *state;
+
+ DBG("path %s", obj_path);
+
+ dbus_message_iter_init(msg, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+ error("Unexpected signature in vc PropertyChanged signal");
+ return TRUE;
+ }
+
+ dbus_message_iter_get_basic(&iter, &property);
+ DBG("property %s", property);
+
+ dbus_message_iter_next(&iter);
+ dbus_message_iter_recurse(&iter, &sub);
+ if (g_str_equal(property, "State")) {
+ dbus_message_iter_get_basic(&sub, &state);
+ DBG("State %s", state);
+ if (g_str_equal(state, "disconnected")) {
+ calls = g_slist_remove(calls, vc);
+ call_free(vc);
+ } else if (g_str_equal(state, "active")) {
+ telephony_update_indicator(ofono_indicators,
+ "call", EV_CALL_ACTIVE);
+ telephony_update_indicator(ofono_indicators,
+ "callsetup",
+ EV_CALLSETUP_INACTIVE);
+ if (vc->status == CALL_STATUS_INCOMING)
+ telephony_calling_stopped_ind();
+ vc->status = CALL_STATUS_ACTIVE;
+ } else if (g_str_equal(state, "alerting")) {
+ telephony_update_indicator(ofono_indicators,
+ "callsetup", EV_CALLSETUP_ALERTING);
+ vc->status = CALL_STATUS_ALERTING;
+ vc->originating = TRUE;
+ } else if (g_str_equal(state, "incoming")) {
+ /* state change from waiting to incoming */
+ telephony_update_indicator(ofono_indicators,
+ "callsetup", EV_CALLSETUP_INCOMING);
+ telephony_incoming_call_ind(vc->number,
+ NUMBER_TYPE_TELEPHONY);
+ vc->status = CALL_STATUS_INCOMING;
+ vc->originating = FALSE;
+ } else if (g_str_equal(state, "held")) {
+ vc->status = CALL_STATUS_HELD;
+ if (find_vc_without_status(CALL_STATUS_HELD))
+ telephony_update_indicator(ofono_indicators,
+ "callheld",
+ EV_CALLHELD_MULTIPLE);
+ else
+ telephony_update_indicator(ofono_indicators,
+ "callheld",
+ EV_CALLHELD_ON_HOLD);
+ }
+ } else if (g_str_equal(property, "Multiparty")) {
+ dbus_bool_t multiparty;
+
+ dbus_message_iter_get_basic(&sub, &multiparty);
+ DBG("Multiparty %s", multiparty ? "True" : "False");
+ vc->conference = multiparty;
+ }
+
+ return TRUE;
+}
+
+static struct voice_call *call_new(const char *path, DBusMessageIter *properties)
+{
+ struct voice_call *vc;
+
+ DBG("%s", path);
+
+ vc = g_new0(struct voice_call, 1);
+ vc->obj_path = g_strdup(path);
+ vc->watch = g_dbus_add_signal_watch(connection, NULL, path,
+ OFONO_VC_INTERFACE, "PropertyChanged",
+ handle_vc_property_changed, vc, NULL);
+
+ while (dbus_message_iter_get_arg_type(properties)
+ == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter entry, value;
+ const char *property, *cli, *state;
+ dbus_bool_t multiparty;
+
+ dbus_message_iter_recurse(properties, &entry);
+ dbus_message_iter_get_basic(&entry, &property);
+
+ dbus_message_iter_next(&entry);
+ dbus_message_iter_recurse(&entry, &value);
+
+ if (g_str_equal(property, "LineIdentification")) {
+ dbus_message_iter_get_basic(&value, &cli);
+ DBG("cli %s", cli);
+ vc->number = g_strdup(cli);
+ } else if (g_str_equal(property, "State")) {
+ dbus_message_iter_get_basic(&value, &state);
+ DBG("state %s", state);
+ if (g_str_equal(state, "incoming"))
+ vc->status = CALL_STATUS_INCOMING;
+ else if (g_str_equal(state, "dialing"))
+ vc->status = CALL_STATUS_DIALING;
+ else if (g_str_equal(state, "alerting"))
+ vc->status = CALL_STATUS_ALERTING;
+ else if (g_str_equal(state, "waiting"))
+ vc->status = CALL_STATUS_WAITING;
+ else if (g_str_equal(state, "held"))
+ vc->status = CALL_STATUS_HELD;
+ } else if (g_str_equal(property, "Multiparty")) {
+ dbus_message_iter_get_basic(&value, &multiparty);
+ DBG("Multipary %s", multiparty ? "True" : "False");
+ vc->conference = multiparty;
+ }
+
+ dbus_message_iter_next(properties);
+ }
+
+ switch (vc->status) {
+ case CALL_STATUS_INCOMING:
+ DBG("CALL_STATUS_INCOMING");
+ vc->originating = FALSE;
+ telephony_update_indicator(ofono_indicators, "callsetup",
+ EV_CALLSETUP_INCOMING);
+ telephony_incoming_call_ind(vc->number, NUMBER_TYPE_TELEPHONY);
+ break;
+ case CALL_STATUS_DIALING:
+ DBG("CALL_STATUS_DIALING");
+ vc->originating = TRUE;
+ g_free(last_dialed_number);
+ last_dialed_number = g_strdup(vc->number);
+ telephony_update_indicator(ofono_indicators, "callsetup",
+ EV_CALLSETUP_OUTGOING);
+ break;
+ case CALL_STATUS_ALERTING:
+ DBG("CALL_STATUS_ALERTING");
+ vc->originating = TRUE;
+ g_free(last_dialed_number);
+ last_dialed_number = g_strdup(vc->number);
+ telephony_update_indicator(ofono_indicators, "callsetup",
+ EV_CALLSETUP_ALERTING);
+ break;
+ case CALL_STATUS_WAITING:
+ DBG("CALL_STATUS_WAITING");
+ vc->originating = FALSE;
+ telephony_update_indicator(ofono_indicators, "callsetup",
+ EV_CALLSETUP_INCOMING);
+ telephony_call_waiting_ind(vc->number, NUMBER_TYPE_TELEPHONY);
+ break;
+ }
+
+ return vc;
+}
+
+static void remove_pending(DBusPendingCall *call)
+{
+ pending = g_slist_remove(pending, call);
+ dbus_pending_call_unref(call);
+}
+
+static void call_added(const char *path, DBusMessageIter *properties)
+{
+ struct voice_call *vc;
+
+ DBG("%s", path);
+
+ vc = find_vc(path);
+ if (vc)
+ return;
+
+ vc = call_new(path, properties);
+ calls = g_slist_prepend(calls, vc);
+}
+
+static void get_calls_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ DBusMessageIter iter, entry;
+
+ DBG("");
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("ofono replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_message_iter_init(reply, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ error("Unexpected signature");
+ goto done;
+ }
+
+ dbus_message_iter_recurse(&iter, &entry);
+
+ while (dbus_message_iter_get_arg_type(&entry)
+ == DBUS_TYPE_STRUCT) {
+ const char *path;
+ DBusMessageIter value, properties;
+
+ dbus_message_iter_recurse(&entry, &value);
+ dbus_message_iter_get_basic(&value, &path);
+
+ dbus_message_iter_next(&value);
+ dbus_message_iter_recurse(&value, &properties);
+
+ call_added(path, &properties);
+
+ dbus_message_iter_next(&entry);
+ }
+
+done:
+ dbus_message_unref(reply);
+ remove_pending(call);
+}
+
+static void handle_network_property(const char *property, DBusMessageIter *variant)
+{
+ const char *status, *operator;
+ unsigned int signals_bar;
+
+ if (g_str_equal(property, "Status")) {
+ dbus_message_iter_get_basic(variant, &status);
+ DBG("Status is %s", status);
+ if (g_str_equal(status, "registered")) {
+ net.status = NETWORK_REG_STATUS_HOME;
+ telephony_update_indicator(ofono_indicators,
+ "roam", EV_ROAM_INACTIVE);
+ telephony_update_indicator(ofono_indicators,
+ "service", EV_SERVICE_PRESENT);
+ } else if (g_str_equal(status, "roaming")) {
+ net.status = NETWORK_REG_STATUS_ROAM;
+ telephony_update_indicator(ofono_indicators,
+ "roam", EV_ROAM_ACTIVE);
+ telephony_update_indicator(ofono_indicators,
+ "service", EV_SERVICE_PRESENT);
+ } else {
+ net.status = NETWORK_REG_STATUS_NOSERV;
+ telephony_update_indicator(ofono_indicators,
+ "roam", EV_ROAM_INACTIVE);
+ telephony_update_indicator(ofono_indicators,
+ "service", EV_SERVICE_NONE);
+ }
+ } else if (g_str_equal(property, "Name")) {
+ dbus_message_iter_get_basic(variant, &operator);
+ DBG("Operator is %s", operator);
+ g_free(net.operator_name);
+ net.operator_name = g_strdup(operator);
+ } else if (g_str_equal(property, "SignalStrength")) {
+ dbus_message_iter_get_basic(variant, &signals_bar);
+ DBG("SignalStrength is %d", signals_bar);
+ net.signals_bar = signals_bar;
+ telephony_update_indicator(ofono_indicators, "signal",
+ (signals_bar + 20) / 21);
+ }
+}
+
+static int parse_network_properties(DBusMessageIter *properties)
+{
+ uint32_t features = AG_FEATURE_EC_ANDOR_NR |
+ AG_FEATURE_INBAND_RINGTONE |
+ AG_FEATURE_REJECT_A_CALL |
+ AG_FEATURE_ENHANCED_CALL_STATUS |
+ AG_FEATURE_ENHANCED_CALL_CONTROL |
+ AG_FEATURE_EXTENDED_ERROR_RESULT_CODES |
+ AG_FEATURE_THREE_WAY_CALLING;
+ int i;
+
+ /* Reset indicators */
+ for (i = 0; ofono_indicators[i].desc != NULL; i++) {
+ if (g_str_equal(ofono_indicators[i].desc, "battchg"))
+ ofono_indicators[i].val = 5;
+ else
+ ofono_indicators[i].val = 0;
+ }
+
+ while (dbus_message_iter_get_arg_type(properties)
+ == DBUS_TYPE_DICT_ENTRY) {
+ const char *key;
+ DBusMessageIter value, entry;
+
+ dbus_message_iter_recurse(properties, &entry);
+ dbus_message_iter_get_basic(&entry, &key);
+
+ dbus_message_iter_next(&entry);
+ dbus_message_iter_recurse(&entry, &value);
+
+ handle_network_property(key, &value);
+
+ dbus_message_iter_next(properties);
+ }
+
+ telephony_ready_ind(features, ofono_indicators, BTRH_NOT_SUPPORTED,
+ chld_str);
+
+ return 0;
+}
+
+static void get_properties_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ DBusMessageIter iter, properties;
+ int ret = 0;
+
+ DBG("");
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("ofono replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_message_iter_init(reply, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ error("Unexpected signature");
+ goto done;
+ }
+
+ dbus_message_iter_recurse(&iter, &properties);
+
+ ret = parse_network_properties(&properties);
+ if (ret < 0) {
+ error("Unable to parse %s.GetProperty reply",
+ OFONO_NETWORKREG_INTERFACE);
+ goto done;
+ }
+
+ ret = send_method_call(OFONO_BUS_NAME, modem_obj_path,
+ OFONO_VCMANAGER_INTERFACE, "GetCalls",
+ get_calls_reply, NULL, DBUS_TYPE_INVALID);
+ if (ret < 0)
+ error("Unable to send %s.GetCalls",
+ OFONO_VCMANAGER_INTERFACE);
+
+done:
+ dbus_message_unref(reply);
+ remove_pending(call);
+}
+
+static void network_found(const char *path)
+{
+ int ret;
+
+ DBG("%s", path);
+
+ modem_obj_path = g_strdup(path);
+
+ ret = send_method_call(OFONO_BUS_NAME, path,
+ OFONO_NETWORKREG_INTERFACE, "GetProperties",
+ get_properties_reply, NULL, DBUS_TYPE_INVALID);
+ if (ret < 0)
+ error("Unable to send %s.GetProperties",
+ OFONO_NETWORKREG_INTERFACE);
+}
+
+static void modem_removed(const char *path)
+{
+ if (g_strcmp0(modem_obj_path, path) != 0)
+ return;
+
+ DBG("%s", path);
+
+ g_slist_foreach(calls, (GFunc) call_free, NULL);
+ g_slist_free(calls);
+ calls = NULL;
+
+ g_free(net.operator_name);
+ net.operator_name = NULL;
+ net.status = NETWORK_REG_STATUS_NOSERV;
+ net.signals_bar = 0;
+
+ g_free(modem_obj_path);
+ modem_obj_path = NULL;
+}
+
+static void parse_modem_interfaces(const char *path, DBusMessageIter *ifaces)
+{
+ DBG("%s", path);
+
+ while (dbus_message_iter_get_arg_type(ifaces) == DBUS_TYPE_STRING) {
+ const char *iface;
+
+ dbus_message_iter_get_basic(ifaces, &iface);
+
+ if (g_str_equal(iface, OFONO_NETWORKREG_INTERFACE)) {
+ network_found(path);
+ return;
+ }
+
+ dbus_message_iter_next(ifaces);
+ }
+
+ modem_removed(path);
+}
+
+static void modem_added(const char *path, DBusMessageIter *properties)
+{
+ if (modem_obj_path != NULL) {
+ DBG("Ignoring, modem already exist");
+ return;
+ }
+
+ DBG("%s", path);
+
+ while (dbus_message_iter_get_arg_type(properties)
+ == DBUS_TYPE_DICT_ENTRY) {
+ const char *key;
+ DBusMessageIter interfaces, value, entry;
+
+ dbus_message_iter_recurse(properties, &entry);
+ dbus_message_iter_get_basic(&entry, &key);
+
+ dbus_message_iter_next(&entry);
+ dbus_message_iter_recurse(&entry, &value);
+
+ if (strcasecmp(key, "Interfaces") != 0)
+ goto next;
+
+ if (dbus_message_iter_get_arg_type(&value)
+ != DBUS_TYPE_ARRAY) {
+ error("Invalid Signature");
+ return;
+ }
+
+ dbus_message_iter_recurse(&value, &interfaces);
+
+ parse_modem_interfaces(path, &interfaces);
+
+ if (modem_obj_path != NULL)
+ return;
+
+ next:
+ dbus_message_iter_next(properties);
+ }
+}
+
+static void get_modems_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusError err;
+ DBusMessage *reply;
+ DBusMessageIter iter, entry;
+
+ DBG("");
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("ofono replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ /* Skip modem selection if a modem already exist */
+ if (modem_obj_path != NULL)
+ goto done;
+
+ dbus_message_iter_init(reply, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ error("Unexpected signature");
+ goto done;
+ }
+
+ dbus_message_iter_recurse(&iter, &entry);
+
+ while (dbus_message_iter_get_arg_type(&entry)
+ == DBUS_TYPE_STRUCT) {
+ const char *path;
+ DBusMessageIter item, properties;
+
+ dbus_message_iter_recurse(&entry, &item);
+ dbus_message_iter_get_basic(&item, &path);
+
+ dbus_message_iter_next(&item);
+ dbus_message_iter_recurse(&item, &properties);
+
+ modem_added(path, &properties);
+ if (modem_obj_path != NULL)
+ break;
+
+ dbus_message_iter_next(&entry);
+ }
+
+done:
+ dbus_message_unref(reply);
+ remove_pending(call);
+}
+
+static gboolean handle_network_property_changed(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessageIter iter, variant;
+ const char *property;
+
+ dbus_message_iter_init(msg, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+ error("Unexpected signature in networkregistration"
+ " PropertyChanged signal");
+ return TRUE;
+ }
+ dbus_message_iter_get_basic(&iter, &property);
+ DBG("in handle_registration_property_changed(),"
+ " the property is %s", property);
+
+ dbus_message_iter_next(&iter);
+ dbus_message_iter_recurse(&iter, &variant);
+
+ handle_network_property(property, &variant);
+
+ return TRUE;
+}
+
+static void handle_modem_property(const char *path, const char *property,
+ DBusMessageIter *variant)
+{
+ DBG("%s", property);
+
+ if (g_str_equal(property, "Interfaces")) {
+ DBusMessageIter interfaces;
+
+ if (dbus_message_iter_get_arg_type(variant)
+ != DBUS_TYPE_ARRAY) {
+ error("Invalid signature");
+ return;
+ }
+
+ dbus_message_iter_recurse(variant, &interfaces);
+ parse_modem_interfaces(path, &interfaces);
+ }
+}
+
+static gboolean handle_modem_property_changed(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessageIter iter, variant;
+ const char *property, *path;
+
+ path = dbus_message_get_path(msg);
+
+ /* Ignore if modem already exist and paths doesn't match */
+ if (modem_obj_path != NULL &&
+ g_str_equal(path, modem_obj_path) == FALSE)
+ return TRUE;
+
+ dbus_message_iter_init(msg, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+ error("Unexpected signature in %s.%s PropertyChanged signal",
+ dbus_message_get_interface(msg),
+ dbus_message_get_member(msg));
+ return TRUE;
+ }
+
+ dbus_message_iter_get_basic(&iter, &property);
+
+ dbus_message_iter_next(&iter);
+ dbus_message_iter_recurse(&iter, &variant);
+
+ handle_modem_property(path, property, &variant);
+
+ return TRUE;
+}
+
+static gboolean handle_vcmanager_call_added(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessageIter iter, properties;
+ const char *path = dbus_message_get_path(msg);
+
+ /* Ignore call if modem path doesn't math */
+ if (g_strcmp0(modem_obj_path, path) != 0)
+ return TRUE;
+
+ dbus_message_iter_init(msg, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter)
+ != DBUS_TYPE_OBJECT_PATH) {
+ error("Unexpected signature in %s.%s signal",
+ dbus_message_get_interface(msg),
+ dbus_message_get_member(msg));
+ return TRUE;
+ }
+
+ dbus_message_iter_get_basic(&iter, &path);
+ dbus_message_iter_next(&iter);
+ dbus_message_iter_recurse(&iter, &properties);
+
+ call_added(path, &properties);
+
+ return TRUE;
+}
+
+static void call_removed(const char *path)
+{
+ struct voice_call *vc;
+
+ DBG("%s", path);
+
+ vc = find_vc(path);
+ if (vc == NULL)
+ return;
+
+ calls = g_slist_remove(calls, vc);
+ call_free(vc);
+}
+
+static gboolean handle_vcmanager_call_removed(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *path = dbus_message_get_path(msg);
+
+ /* Ignore call if modem path doesn't math */
+ if (g_strcmp0(modem_obj_path, path) != 0)
+ return TRUE;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected signature in %s.%s signal",
+ dbus_message_get_interface(msg),
+ dbus_message_get_member(msg));
+ return TRUE;
+ }
+
+ call_removed(path);
+
+ return TRUE;
+}
+
+static gboolean handle_manager_modem_added(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessageIter iter, properties;
+ const char *path;
+
+ if (modem_obj_path != NULL)
+ return TRUE;
+
+ dbus_message_iter_init(msg, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter)
+ != DBUS_TYPE_OBJECT_PATH) {
+ error("Unexpected signature in %s.%s signal",
+ dbus_message_get_interface(msg),
+ dbus_message_get_member(msg));
+ return TRUE;
+ }
+
+ dbus_message_iter_get_basic(&iter, &path);
+ dbus_message_iter_next(&iter);
+ dbus_message_iter_recurse(&iter, &properties);
+
+ modem_added(path, &properties);
+
+ return TRUE;
+}
+
+static gboolean handle_manager_modem_removed(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *path;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID)) {
+ error("Unexpected signature in %s.%s signal",
+ dbus_message_get_interface(msg),
+ dbus_message_get_member(msg));
+ return TRUE;
+ }
+
+ modem_removed(path);
+
+ return TRUE;
+}
+
+static void hal_battery_level_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusMessage *reply;
+ DBusError err;
+ dbus_int32_t level;
+ int *value = user_data;
+
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("hald replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_error_init(&err);
+ if (dbus_message_get_args(reply, &err,
+ DBUS_TYPE_INT32, &level,
+ DBUS_TYPE_INVALID) == FALSE) {
+ error("Unable to parse GetPropertyInteger reply: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ *value = (int) level;
+
+ if (value == &battchg_last)
+ DBG("telephony-ofono: battery.charge_level.last_full"
+ " is %d", *value);
+ else if (value == &battchg_design)
+ DBG("telephony-ofono: battery.charge_level.design"
+ " is %d", *value);
+ else
+ DBG("telephony-ofono: battery.charge_level.current"
+ " is %d", *value);
+
+ if ((battchg_design > 0 || battchg_last > 0) && battchg_cur >= 0) {
+ int new, max;
+
+ if (battchg_last > 0)
+ max = battchg_last;
+ else
+ max = battchg_design;
+
+ new = battchg_cur * 5 / max;
+
+ telephony_update_indicator(ofono_indicators, "battchg", new);
+ }
+done:
+ dbus_message_unref(reply);
+ remove_pending(call);
+}
+
+static void hal_get_integer(const char *path, const char *key, void *user_data)
+{
+ send_method_call("org.freedesktop.Hal", path,
+ "org.freedesktop.Hal.Device",
+ "GetPropertyInteger",
+ hal_battery_level_reply, user_data,
+ DBUS_TYPE_STRING, &key,
+ DBUS_TYPE_INVALID);
+}
+
+static gboolean handle_hal_property_modified(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *path;
+ DBusMessageIter iter, array;
+ dbus_int32_t num_changes;
+
+ path = dbus_message_get_path(msg);
+
+ dbus_message_iter_init(msg, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
+ error("Unexpected signature in hal PropertyModified signal");
+ return TRUE;
+ }
+
+ dbus_message_iter_get_basic(&iter, &num_changes);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ error("Unexpected signature in hal PropertyModified signal");
+ return TRUE;
+ }
+
+ dbus_message_iter_recurse(&iter, &array);
+
+ while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) {
+ DBusMessageIter prop;
+ const char *name;
+ dbus_bool_t added, removed;
+
+ dbus_message_iter_recurse(&array, &prop);
+
+ if (!iter_get_basic_args(&prop,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_BOOLEAN, &added,
+ DBUS_TYPE_BOOLEAN, &removed,
+ DBUS_TYPE_INVALID)) {
+ error("Invalid hal PropertyModified parameters");
+ break;
+ }
+
+ if (g_str_equal(name, "battery.charge_level.last_full"))
+ hal_get_integer(path, name, &battchg_last);
+ else if (g_str_equal(name, "battery.charge_level.current"))
+ hal_get_integer(path, name, &battchg_cur);
+ else if (g_str_equal(name, "battery.charge_level.design"))
+ hal_get_integer(path, name, &battchg_design);
+
+ dbus_message_iter_next(&array);
+ }
+
+ return TRUE;
+}
+
+static void add_watch(const char *sender, const char *path,
+ const char *interface, const char *member,
+ GDBusSignalFunction function)
+{
+ guint watch;
+
+ watch = g_dbus_add_signal_watch(connection, sender, path, interface,
+ member, function, NULL, NULL);
+
+ watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch));
+}
+
+static void hal_find_device_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusMessage *reply;
+ DBusError err;
+ DBusMessageIter iter, sub;
+ int type;
+ const char *path;
+
+ DBG("begin of hal_find_device_reply()");
+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+
+ if (dbus_set_error_from_message(&err, reply)) {
+ error("hald replied with an error: %s, %s",
+ err.name, err.message);
+ dbus_error_free(&err);
+ goto done;
+ }
+
+ dbus_message_iter_init(reply, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ error("Unexpected signature in hal_find_device_reply()");
+ goto done;
+ }
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ type = dbus_message_iter_get_arg_type(&sub);
+
+ if (type != DBUS_TYPE_OBJECT_PATH && type != DBUS_TYPE_STRING) {
+ error("No hal device with battery capability found");
+ goto done;
+ }
+
+ dbus_message_iter_get_basic(&sub, &path);
+
+ DBG("telephony-ofono: found battery device at %s", path);
+
+ add_watch(NULL, path, "org.freedesktop.Hal.Device",
+ "PropertyModified", handle_hal_property_modified);
+
+ hal_get_integer(path, "battery.charge_level.last_full", &battchg_last);
+ hal_get_integer(path, "battery.charge_level.current", &battchg_cur);
+ hal_get_integer(path, "battery.charge_level.design", &battchg_design);
+done:
+ dbus_message_unref(reply);
+ remove_pending(call);
+}
+
+static void handle_service_connect(DBusConnection *conn, void *user_data)
+{
+ DBG("telephony-ofono: %s found", OFONO_BUS_NAME);
+
+ send_method_call(OFONO_BUS_NAME, OFONO_PATH,
+ OFONO_MANAGER_INTERFACE, "GetModems",
+ get_modems_reply, NULL, DBUS_TYPE_INVALID);
+}
+
+static void handle_service_disconnect(DBusConnection *conn, void *user_data)
+{
+ DBG("telephony-ofono: %s exitted", OFONO_BUS_NAME);
+
+ if (modem_obj_path)
+ modem_removed(modem_obj_path);
+}
+
+int telephony_init(void)
+{
+ const char *battery_cap = "battery";
+ int ret;
+ guint watch;
+
+ connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+ add_watch(OFONO_BUS_NAME, NULL, OFONO_MODEM_INTERFACE,
+ "PropertyChanged", handle_modem_property_changed);
+ add_watch(OFONO_BUS_NAME, NULL, OFONO_NETWORKREG_INTERFACE,
+ "PropertyChanged", handle_network_property_changed);
+ add_watch(OFONO_BUS_NAME, NULL, OFONO_MANAGER_INTERFACE,
+ "ModemAdded", handle_manager_modem_added);
+ add_watch(OFONO_BUS_NAME, NULL, OFONO_MANAGER_INTERFACE,
+ "ModemRemoved", handle_manager_modem_removed);
+ add_watch(OFONO_BUS_NAME, NULL, OFONO_VCMANAGER_INTERFACE,
+ "CallAdded", handle_vcmanager_call_added);
+ add_watch(OFONO_BUS_NAME, NULL, OFONO_VCMANAGER_INTERFACE,
+ "CallRemoved", handle_vcmanager_call_removed);
+
+ watch = g_dbus_add_service_watch(connection, OFONO_BUS_NAME,
+ handle_service_connect,
+ handle_service_disconnect,
+ NULL, NULL);
+ if (watch == 0)
+ return -ENOMEM;
+
+ watches = g_slist_prepend(watches, GUINT_TO_POINTER(watch));
+
+ ret = send_method_call("org.freedesktop.Hal",
+ "/org/freedesktop/Hal/Manager",
+ "org.freedesktop.Hal.Manager",
+ "FindDeviceByCapability",
+ hal_find_device_reply, NULL,
+ DBUS_TYPE_STRING, &battery_cap,
+ DBUS_TYPE_INVALID);
+ if (ret < 0)
+ return ret;
+
+ DBG("telephony_init() successfully");
+
+ return ret;
+}
+
+static void remove_watch(gpointer data)
+{
+ g_dbus_remove_watch(connection, GPOINTER_TO_UINT(data));
+}
+
+void telephony_exit(void)
+{
+ DBG("");
+
+ g_free(last_dialed_number);
+ last_dialed_number = NULL;
+
+ if (modem_obj_path)
+ modem_removed(modem_obj_path);
+
+ g_slist_foreach(watches, (GFunc) remove_watch, NULL);
+ g_slist_free(watches);
+ watches = NULL;
+
+ g_slist_foreach(pending, (GFunc) dbus_pending_call_cancel, NULL);
+ g_slist_foreach(pending, (GFunc) dbus_pending_call_unref, NULL);
+ g_slist_free(pending);
+ pending = NULL;
+
+ dbus_connection_unref(connection);
+ connection = NULL;
+
+ telephony_deinit();
+}
diff --git a/audio/telephony.h b/audio/telephony.h
new file mode 100644
index 0000000..73b390c
--- /dev/null
+++ b/audio/telephony.h
@@ -0,0 +1,244 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include <glib.h>
+
+/* Response and hold values */
+#define BTRH_NOT_SUPPORTED -2
+#define BTRH_NONE -1
+#define BTRH_HOLD 0
+#define BTRH_ACCEPT 1
+#define BTRH_REJECT 2
+
+/* HFP feature bits */
+#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_ENHANCED_CALL_STATUS 0x0040
+#define AG_FEATURE_ENHANCED_CALL_CONTROL 0x0080
+#define AG_FEATURE_EXTENDED_ERROR_RESULT_CODES 0x0100
+
+#define HF_FEATURE_EC_ANDOR_NR 0x0001
+#define HF_FEATURE_CALL_WAITING_AND_3WAY 0x0002
+#define HF_FEATURE_CLI_PRESENTATION 0x0004
+#define HF_FEATURE_VOICE_RECOGNITION 0x0008
+#define HF_FEATURE_REMOTE_VOLUME_CONTROL 0x0010
+#define HF_FEATURE_ENHANCED_CALL_STATUS 0x0020
+#define HF_FEATURE_ENHANCED_CALL_CONTROL 0x0040
+
+/* Indicator event values */
+#define EV_SERVICE_NONE 0
+#define EV_SERVICE_PRESENT 1
+
+#define EV_CALL_INACTIVE 0
+#define EV_CALL_ACTIVE 1
+
+#define EV_CALLSETUP_INACTIVE 0
+#define EV_CALLSETUP_INCOMING 1
+#define EV_CALLSETUP_OUTGOING 2
+#define EV_CALLSETUP_ALERTING 3
+
+#define EV_CALLHELD_NONE 0
+#define EV_CALLHELD_MULTIPLE 1
+#define EV_CALLHELD_ON_HOLD 2
+
+#define EV_ROAM_INACTIVE 0
+#define EV_ROAM_ACTIVE 1
+
+/* Call parameters */
+#define CALL_DIR_OUTGOING 0
+#define CALL_DIR_INCOMING 1
+
+#define CALL_STATUS_ACTIVE 0
+#define CALL_STATUS_HELD 1
+#define CALL_STATUS_DIALING 2
+#define CALL_STATUS_ALERTING 3
+#define CALL_STATUS_INCOMING 4
+#define CALL_STATUS_WAITING 5
+
+#define CALL_MODE_VOICE 0
+#define CALL_MODE_DATA 1
+#define CALL_MODE_FAX 2
+
+#define CALL_MULTIPARTY_NO 0
+#define CALL_MULTIPARTY_YES 1
+
+/* Subscriber number parameters */
+#define SUBSCRIBER_SERVICE_VOICE 4
+#define SUBSCRIBER_SERVICE_FAX 5
+
+/* Operator selection mode values */
+#define OPERATOR_MODE_AUTO 0
+#define OPERATOR_MODE_MANUAL 1
+#define OPERATOR_MODE_DEREGISTER 2
+#define OPERATOR_MODE_MANUAL_AUTO 4
+
+/* Some common number types */
+#define NUMBER_TYPE_UNKNOWN 128
+#define NUMBER_TYPE_TELEPHONY 129
+#define NUMBER_TYPE_INTERNATIONAL 145
+#define NUMBER_TYPE_NATIONAL 161
+#define NUMBER_TYPE_VOIP 255
+
+/* Extended Audio Gateway Error Result Codes */
+typedef enum {
+ CME_ERROR_NONE = -1,
+ CME_ERROR_AG_FAILURE = 0,
+ CME_ERROR_NO_PHONE_CONNECTION = 1,
+ CME_ERROR_NOT_ALLOWED = 3,
+ CME_ERROR_NOT_SUPPORTED = 4,
+ CME_ERROR_PH_SIM_PIN_REQUIRED = 5,
+ CME_ERROR_SIM_NOT_INSERTED = 10,
+ CME_ERROR_SIM_PIN_REQUIRED = 11,
+ CME_ERROR_SIM_PUK_REQUIRED = 12,
+ CME_ERROR_SIM_FAILURE = 13,
+ CME_ERROR_SIM_BUSY = 14,
+ CME_ERROR_INCORRECT_PASSWORD = 16,
+ CME_ERROR_SIM_PIN2_REQUIRED = 17,
+ CME_ERROR_SIM_PUK2_REQUIRED = 18,
+ CME_ERROR_MEMORY_FULL = 20,
+ CME_ERROR_INVALID_INDEX = 21,
+ CME_ERROR_MEMORY_FAILURE = 23,
+ CME_ERROR_TEXT_STRING_TOO_LONG = 24,
+ CME_ERROR_INVALID_TEXT_STRING = 25,
+ CME_ERROR_DIAL_STRING_TOO_LONG = 26,
+ CME_ERROR_INVALID_DIAL_STRING = 27,
+ CME_ERROR_NO_NETWORK_SERVICE = 30,
+ CME_ERROR_NETWORK_TIMEOUT = 31,
+ CME_ERROR_NETWORK_NOT_ALLOWED = 32,
+} cme_error_t;
+
+struct indicator {
+ const char *desc;
+ const char *range;
+ int val;
+ gboolean ignore_redundant;
+};
+
+/* Notify telephony-*.c of connected/disconnected devices. Implemented by
+ * telephony-*.c
+ */
+void telephony_device_connected(void *telephony_device);
+void telephony_device_disconnected(void *telephony_device);
+
+/* HF requests (sent by the handsfree device). These are implemented by
+ * telephony-*.c
+ */
+void telephony_event_reporting_req(void *telephony_device, int ind);
+void telephony_response_and_hold_req(void *telephony_device, int rh);
+void telephony_last_dialed_number_req(void *telephony_device);
+void telephony_terminate_call_req(void *telephony_device);
+void telephony_answer_call_req(void *telephony_device);
+void telephony_dial_number_req(void *telephony_device, const char *number);
+void telephony_transmit_dtmf_req(void *telephony_device, char tone);
+void telephony_subscriber_number_req(void *telephony_device);
+void telephony_list_current_calls_req(void *telephony_device);
+void telephony_operator_selection_req(void *telephony_device);
+void telephony_call_hold_req(void *telephony_device, const char *cmd);
+void telephony_nr_and_ec_req(void *telephony_device, gboolean enable);
+void telephony_voice_dial_req(void *telephony_device, gboolean enable);
+void telephony_key_press_req(void *telephony_device, const char *keys);
+
+/* AG responses to HF requests. These are implemented by headset.c */
+int telephony_event_reporting_rsp(void *telephony_device, cme_error_t err);
+int telephony_response_and_hold_rsp(void *telephony_device, cme_error_t err);
+int telephony_last_dialed_number_rsp(void *telephony_device, cme_error_t err);
+int telephony_terminate_call_rsp(void *telephony_device, cme_error_t err);
+int telephony_answer_call_rsp(void *telephony_device, cme_error_t err);
+int telephony_dial_number_rsp(void *telephony_device, cme_error_t err);
+int telephony_transmit_dtmf_rsp(void *telephony_device, cme_error_t err);
+int telephony_subscriber_number_rsp(void *telephony_device, cme_error_t err);
+int telephony_list_current_calls_rsp(void *telephony_device, cme_error_t err);
+int telephony_operator_selection_rsp(void *telephony_device, cme_error_t err);
+int telephony_call_hold_rsp(void *telephony_device, cme_error_t err);
+int telephony_nr_and_ec_rsp(void *telephony_device, cme_error_t err);
+int telephony_voice_dial_rsp(void *telephony_device, cme_error_t err);
+int telephony_key_press_rsp(void *telephony_device, cme_error_t err);
+
+/* Event indications by AG. These are implemented by headset.c */
+int telephony_event_ind(int index);
+int telephony_response_and_hold_ind(int rh);
+int telephony_incoming_call_ind(const char *number, int type);
+int telephony_calling_stopped_ind(void);
+int telephony_ready_ind(uint32_t features, const struct indicator *indicators,
+ int rh, const char *chld);
+int telephony_deinit(void);
+int telephony_list_current_call_ind(int idx, int dir, int status, int mode,
+ int mprty, const char *number,
+ int type);
+int telephony_subscriber_number_ind(const char *number, int type,
+ int service);
+int telephony_call_waiting_ind(const char *number, int type);
+int telephony_operator_selection_ind(int mode, const char *oper);
+
+/* Helper function for quick indicator updates */
+static inline int telephony_update_indicator(struct indicator *indicators,
+ const char *desc,
+ int new_val)
+{
+ int i;
+ struct indicator *ind = NULL;
+
+ for (i = 0; indicators[i].desc != NULL; i++) {
+ if (g_str_equal(indicators[i].desc, desc)) {
+ ind = &indicators[i];
+ break;
+ }
+ }
+
+ if (!ind)
+ return -ENOENT;
+
+ DBG("Telephony indicator \"%s\" %d->%d", desc, ind->val, new_val);
+
+ if (ind->ignore_redundant && ind->val == new_val) {
+ DBG("Ignoring no-change indication");
+ return 0;
+ }
+
+ ind->val = new_val;
+
+ return telephony_event_ind(i);
+}
+
+static inline int telephony_get_indicator(const struct indicator *indicators,
+ const char *desc)
+{
+ int i;
+
+ for (i = 0; indicators[i].desc != NULL; i++) {
+ if (g_str_equal(indicators[i].desc, desc))
+ return indicators[i].val;
+ }
+
+ return -ENOENT;
+}
+
+int telephony_init(void);
+void telephony_exit(void);
diff --git a/audio/transport.c b/audio/transport.c
new file mode 100644
index 0000000..8ff6c85
--- /dev/null
+++ b/audio/transport.c
@@ -0,0 +1,927 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "../src/adapter.h"
+#include "../src/dbus-common.h"
+
+#include "log.h"
+#include "error.h"
+#include "device.h"
+#include "avdtp.h"
+#include "media.h"
+#include "transport.h"
+#include "a2dp.h"
+#include "headset.h"
+
+#ifndef DBUS_TYPE_UNIX_FD
+#define DBUS_TYPE_UNIX_FD -1
+#endif
+
+#define MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport"
+
+struct media_request {
+ DBusMessage *msg;
+ guint id;
+};
+
+struct media_owner {
+ struct media_transport *transport;
+ struct media_request *pending;
+ char *name;
+ char *accesstype;
+ guint watch;
+};
+
+struct media_transport {
+ DBusConnection *conn;
+ char *path; /* Transport object path */
+ struct audio_device *device; /* Transport device */
+ struct avdtp *session; /* Signalling session (a2dp only) */
+ struct media_endpoint *endpoint; /* Transport endpoint */
+ GSList *owners; /* Transport owners */
+ uint8_t *configuration; /* Transport configuration */
+ int size; /* Transport configuration size */
+ int fd; /* Transport file descriptor */
+ uint16_t imtu; /* Transport input mtu */
+ uint16_t omtu; /* Transport output mtu */
+ uint16_t delay; /* Transport delay (a2dp only) */
+ unsigned int nrec_id; /* Transport nrec watch (headset only) */
+ gboolean read_lock;
+ gboolean write_lock;
+ gboolean in_use;
+ guint (*resume) (struct media_transport *transport,
+ struct media_owner *owner);
+ guint (*suspend) (struct media_transport *transport,
+ struct media_owner *owner);
+ void (*cancel) (struct media_transport *transport,
+ guint id);
+ void (*get_properties) (
+ struct media_transport *transport,
+ DBusMessageIter *dict);
+ int (*set_property) (
+ struct media_transport *transport,
+ const char *property,
+ DBusMessageIter *value);
+};
+
+void media_transport_destroy(struct media_transport *transport)
+{
+ char *path;
+
+ path = g_strdup(transport->path);
+
+ g_dbus_unregister_interface(transport->conn, path,
+ MEDIA_TRANSPORT_INTERFACE);
+
+ g_free(path);
+}
+
+static struct media_request *media_request_create(DBusMessage *msg, guint id)
+{
+ struct media_request *req;
+
+ req = g_new0(struct media_request, 1);
+ req->msg = dbus_message_ref(msg);
+ req->id = id;
+
+ DBG("Request created: method=%s id=%u", dbus_message_get_member(msg),
+ id);
+
+ return req;
+}
+
+static void media_request_reply(struct media_request *req,
+ DBusConnection *conn, int err)
+{
+ DBusMessage *reply;
+
+ DBG("Request %s Reply %s", dbus_message_get_member(req->msg),
+ strerror(err));
+
+ if (!err)
+ reply = g_dbus_create_reply(req->msg, DBUS_TYPE_INVALID);
+ else
+ reply = g_dbus_create_error(req->msg,
+ ERROR_INTERFACE ".Failed",
+ "%s", strerror(err));
+
+ g_dbus_send_message(conn, reply);
+}
+
+static gboolean media_transport_release(struct media_transport *transport,
+ const char *accesstype)
+{
+ if (g_strstr_len(accesstype, -1, "r") != NULL) {
+ transport->read_lock = FALSE;
+ DBG("Transport %s: read lock released", transport->path);
+ }
+
+ if (g_strstr_len(accesstype, -1, "w") != NULL) {
+ transport->write_lock = FALSE;
+ DBG("Transport %s: write lock released", transport->path);
+ }
+
+ return TRUE;
+}
+
+static void media_owner_remove(struct media_owner *owner)
+{
+ struct media_transport *transport = owner->transport;
+ struct media_request *req = owner->pending;
+
+ if (!req)
+ return;
+
+ DBG("Owner %s Request %s", owner->name,
+ dbus_message_get_member(req->msg));
+
+ if (req->id)
+ transport->cancel(transport, req->id);
+
+ owner->pending = NULL;
+ if (req->msg)
+ dbus_message_unref(req->msg);
+
+ g_free(req);
+}
+
+static void media_owner_free(struct media_owner *owner)
+{
+ DBG("Owner %s", owner->name);
+
+ media_owner_remove(owner);
+
+ g_free(owner->name);
+ g_free(owner->accesstype);
+ g_free(owner);
+}
+
+static void media_transport_remove(struct media_transport *transport,
+ struct media_owner *owner)
+{
+ DBG("Transport %s Owner %s", transport->path, owner->name);
+
+ media_transport_release(transport, owner->accesstype);
+
+ /* Reply if owner has a pending request */
+ if (owner->pending)
+ media_request_reply(owner->pending, transport->conn, EIO);
+
+ transport->owners = g_slist_remove(transport->owners, owner);
+
+ if (owner->watch)
+ g_dbus_remove_watch(transport->conn, owner->watch);
+
+ media_owner_free(owner);
+
+ /* Suspend if there is no longer any owner */
+ if (transport->owners == NULL && transport->in_use)
+ transport->suspend(transport, NULL);
+}
+
+static gboolean media_transport_set_fd(struct media_transport *transport,
+ int fd, uint16_t imtu, uint16_t omtu)
+{
+ if (transport->fd == fd)
+ return TRUE;
+
+ transport->fd = fd;
+ transport->imtu = imtu;
+ transport->omtu = omtu;
+
+ info("%s: fd(%d) ready", transport->path, fd);
+
+ return TRUE;
+}
+
+static gboolean remove_owner(gpointer data)
+{
+ struct media_owner *owner = data;
+
+ media_transport_remove(owner->transport, owner);
+
+ return FALSE;
+}
+
+static void a2dp_resume_complete(struct avdtp *session,
+ struct avdtp_error *err, void *user_data)
+{
+ struct media_owner *owner = user_data;
+ struct media_request *req = owner->pending;
+ struct media_transport *transport = owner->transport;
+ struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint);
+ struct avdtp_stream *stream;
+ int fd;
+ uint16_t imtu, omtu;
+ gboolean ret;
+
+ req->id = 0;
+
+ if (err)
+ goto fail;
+
+ stream = a2dp_sep_get_stream(sep);
+ if (stream == NULL)
+ goto fail;
+
+ ret = avdtp_stream_get_transport(stream, &fd, &imtu, &omtu, NULL);
+ if (ret == FALSE)
+ goto fail;
+
+ media_transport_set_fd(transport, fd, imtu, omtu);
+
+ if (g_strstr_len(owner->accesstype, -1, "r") == NULL)
+ imtu = 0;
+
+ if (g_strstr_len(owner->accesstype, -1, "w") == NULL)
+ omtu = 0;
+
+ ret = g_dbus_send_reply(transport->conn, req->msg,
+ DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_UINT16, &imtu,
+ DBUS_TYPE_UINT16, &omtu,
+ DBUS_TYPE_INVALID);
+ if (ret == FALSE)
+ goto fail;
+
+ media_owner_remove(owner);
+
+ return;
+
+fail:
+ /* Let the stream state change before removing the owner */
+ g_idle_add(remove_owner, owner);
+}
+
+static guint resume_a2dp(struct media_transport *transport,
+ struct media_owner *owner)
+{
+ struct media_endpoint *endpoint = transport->endpoint;
+ struct audio_device *device = transport->device;
+ struct a2dp_sep *sep = media_endpoint_get_sep(endpoint);
+
+ if (transport->session == NULL) {
+ transport->session = avdtp_get(&device->src, &device->dst);
+ if (transport->session == NULL)
+ return 0;
+ }
+
+ if (transport->in_use == TRUE)
+ goto done;
+
+ transport->in_use = a2dp_sep_lock(sep, transport->session);
+ if (transport->in_use == FALSE)
+ return 0;
+
+done:
+ return a2dp_resume(transport->session, sep, a2dp_resume_complete,
+ owner);
+}
+
+static void a2dp_suspend_complete(struct avdtp *session,
+ struct avdtp_error *err, void *user_data)
+{
+ struct media_owner *owner = user_data;
+ struct media_transport *transport = owner->transport;
+ struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint);
+
+ /* Release always succeeds */
+ if (owner->pending) {
+ owner->pending->id = 0;
+ media_request_reply(owner->pending, transport->conn, 0);
+ media_owner_remove(owner);
+ }
+
+ a2dp_sep_unlock(sep, transport->session);
+ transport->in_use = FALSE;
+ media_transport_remove(transport, owner);
+}
+
+static guint suspend_a2dp(struct media_transport *transport,
+ struct media_owner *owner)
+{
+ struct media_endpoint *endpoint = transport->endpoint;
+ struct a2dp_sep *sep = media_endpoint_get_sep(endpoint);
+
+ if (!owner) {
+ a2dp_sep_unlock(sep, transport->session);
+ transport->in_use = FALSE;
+ return 0;
+ }
+
+ return a2dp_suspend(transport->session, sep, a2dp_suspend_complete,
+ owner);
+}
+
+static void cancel_a2dp(struct media_transport *transport, guint id)
+{
+ a2dp_cancel(transport->device, id);
+}
+
+static void headset_resume_complete(struct audio_device *dev, void *user_data)
+{
+ struct media_owner *owner = user_data;
+ struct media_request *req = owner->pending;
+ struct media_transport *transport = owner->transport;
+ int fd;
+ uint16_t imtu, omtu;
+ gboolean ret;
+
+ req->id = 0;
+
+ if (dev == NULL)
+ goto fail;
+
+ fd = headset_get_sco_fd(dev);
+ if (fd < 0)
+ goto fail;
+
+ imtu = 48;
+ omtu = 48;
+
+ media_transport_set_fd(transport, fd, imtu, omtu);
+
+ if (g_strstr_len(owner->accesstype, -1, "r") == NULL)
+ imtu = 0;
+
+ if (g_strstr_len(owner->accesstype, -1, "w") == NULL)
+ omtu = 0;
+
+ ret = g_dbus_send_reply(transport->conn, req->msg,
+ DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_UINT16, &imtu,
+ DBUS_TYPE_UINT16, &omtu,
+ DBUS_TYPE_INVALID);
+ if (ret == FALSE)
+ goto fail;
+
+ media_owner_remove(owner);
+
+ return;
+
+fail:
+ media_transport_remove(transport, owner);
+}
+
+static guint resume_headset(struct media_transport *transport,
+ struct media_owner *owner)
+{
+ struct audio_device *device = transport->device;
+
+ if (transport->in_use == TRUE)
+ goto done;
+
+ transport->in_use = headset_lock(device, HEADSET_LOCK_READ |
+ HEADSET_LOCK_WRITE);
+ if (transport->in_use == FALSE)
+ return 0;
+
+done:
+ return headset_request_stream(device, headset_resume_complete,
+ owner);
+}
+
+static void headset_suspend_complete(struct audio_device *dev, void *user_data)
+{
+ struct media_owner *owner = user_data;
+ struct media_transport *transport = owner->transport;
+
+ /* Release always succeeds */
+ if (owner->pending) {
+ owner->pending->id = 0;
+ media_request_reply(owner->pending, transport->conn, 0);
+ media_owner_remove(owner);
+ }
+
+ headset_unlock(dev, HEADSET_LOCK_READ | HEADSET_LOCK_WRITE);
+ transport->in_use = FALSE;
+ media_transport_remove(transport, owner);
+}
+
+static guint suspend_headset(struct media_transport *transport,
+ struct media_owner *owner)
+{
+ struct audio_device *device = transport->device;
+
+ if (!owner) {
+ headset_unlock(device, HEADSET_LOCK_READ | HEADSET_LOCK_WRITE);
+ transport->in_use = FALSE;
+ return 0;
+ }
+
+ return headset_suspend_stream(device, headset_suspend_complete, owner);
+}
+
+static void cancel_headset(struct media_transport *transport, guint id)
+{
+ headset_cancel_stream(transport->device, id);
+}
+
+static void media_owner_exit(DBusConnection *connection, void *user_data)
+{
+ struct media_owner *owner = user_data;
+
+ owner->watch = 0;
+
+ media_owner_remove(owner);
+
+ media_transport_remove(owner->transport, owner);
+}
+
+static gboolean media_transport_acquire(struct media_transport *transport,
+ const char *accesstype)
+{
+ gboolean read_lock = FALSE, write_lock = FALSE;
+
+ if (g_strstr_len(accesstype, -1, "r") != NULL) {
+ if (transport->read_lock == TRUE)
+ return FALSE;
+ read_lock = TRUE;
+ }
+
+ if (g_strstr_len(accesstype, -1, "w") != NULL) {
+ if (transport->write_lock == TRUE)
+ return FALSE;
+ write_lock = TRUE;
+ }
+
+ /* Check invalid accesstype */
+ if (read_lock == FALSE && write_lock == FALSE)
+ return FALSE;
+
+ if (read_lock) {
+ transport->read_lock = read_lock;
+ DBG("Transport %s: read lock acquired", transport->path);
+ }
+
+ if (write_lock) {
+ transport->write_lock = write_lock;
+ DBG("Transport %s: write lock acquired", transport->path);
+ }
+
+
+ return TRUE;
+}
+
+static void media_transport_add(struct media_transport *transport,
+ struct media_owner *owner)
+{
+ DBG("Transport %s Owner %s", transport->path, owner->name);
+ transport->owners = g_slist_append(transport->owners, owner);
+ owner->transport = transport;
+}
+
+static struct media_owner *media_owner_create(DBusConnection *conn,
+ DBusMessage *msg,
+ const char *accesstype)
+{
+ struct media_owner *owner;
+
+ owner = g_new0(struct media_owner, 1);
+ owner->name = g_strdup(dbus_message_get_sender(msg));
+ owner->accesstype = g_strdup(accesstype);
+ owner->watch = g_dbus_add_disconnect_watch(conn, owner->name,
+ media_owner_exit,
+ owner, NULL);
+
+ DBG("Owner created: sender=%s accesstype=%s", owner->name,
+ accesstype);
+
+ return owner;
+}
+
+static void media_owner_add(struct media_owner *owner,
+ struct media_request *req)
+{
+ DBG("Owner %s Request %s", owner->name,
+ dbus_message_get_member(req->msg));
+
+ owner->pending = req;
+}
+
+static struct media_owner *media_transport_find_owner(
+ struct media_transport *transport,
+ const char *name)
+{
+ GSList *l;
+
+ for (l = transport->owners; l; l = l->next) {
+ struct media_owner *owner = l->data;
+
+ if (g_strcmp0(owner->name, name) == 0)
+ return owner;
+ }
+
+ return NULL;
+}
+
+static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct media_transport *transport = data;
+ struct media_owner *owner;
+ struct media_request *req;
+ const char *accesstype, *sender;
+ guint id;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &accesstype,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ sender = dbus_message_get_sender(msg);
+
+ owner = media_transport_find_owner(transport, sender);
+ if (owner != NULL)
+ return btd_error_not_authorized(msg);
+
+ if (media_transport_acquire(transport, accesstype) == FALSE)
+ return btd_error_not_authorized(msg);
+
+ owner = media_owner_create(conn, msg, accesstype);
+ id = transport->resume(transport, owner);
+ if (id == 0) {
+ media_owner_free(owner);
+ return btd_error_not_authorized(msg);
+ }
+
+ req = media_request_create(msg, id);
+ media_owner_add(owner, req);
+ media_transport_add(transport, owner);
+
+ return NULL;
+}
+
+static DBusMessage *release(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct media_transport *transport = data;
+ struct media_owner *owner;
+ const char *accesstype, *sender;
+ struct media_request *req;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &accesstype,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ sender = dbus_message_get_sender(msg);
+
+ owner = media_transport_find_owner(transport, sender);
+ if (owner == NULL)
+ return btd_error_not_authorized(msg);
+
+ if (g_strcmp0(owner->accesstype, accesstype) == 0) {
+ guint id;
+
+ /* Not the last owner, no need to suspend */
+ if (g_slist_length(transport->owners) != 1) {
+ media_transport_remove(transport, owner);
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+ }
+
+ if (owner->pending) {
+ const char *member;
+
+ member = dbus_message_get_member(owner->pending->msg);
+ /* Cancel Acquire request if that exist */
+ if (g_str_equal(member, "Acquire"))
+ media_owner_remove(owner);
+ else
+ return btd_error_in_progress(msg);
+ }
+
+ id = transport->suspend(transport, owner);
+ if (id == 0) {
+ media_transport_remove(transport, owner);
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+ }
+
+ req = media_request_create(msg, id);
+ media_owner_add(owner, req);
+
+ return NULL;
+ } else if (g_strstr_len(owner->accesstype, -1, accesstype) != NULL) {
+ media_transport_release(transport, accesstype);
+ g_strdelimit(owner->accesstype, accesstype, ' ');
+ } else
+ return btd_error_not_authorized(msg);
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static int set_property_a2dp(struct media_transport *transport,
+ const char *property,
+ DBusMessageIter *value)
+{
+ if (g_strcmp0(property, "Delay") == 0) {
+ if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_UINT16)
+ return -EINVAL;
+ dbus_message_iter_get_basic(value, &transport->delay);
+
+ /* FIXME: send new delay */
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int set_property_headset(struct media_transport *transport,
+ const char *property,
+ DBusMessageIter *value)
+{
+ if (g_strcmp0(property, "NREC") == 0) {
+ gboolean nrec;
+
+ if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN)
+ return -EINVAL;
+ dbus_message_iter_get_basic(value, &nrec);
+
+ /* FIXME: set new nrec */
+ return 0;
+ } else if (g_strcmp0(property, "InbandRingtone") == 0) {
+ gboolean inband;
+
+ if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN)
+ return -EINVAL;
+ dbus_message_iter_get_basic(value, &inband);
+
+ /* FIXME: set new inband */
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct media_transport *transport = data;
+ DBusMessageIter iter;
+ DBusMessageIter value;
+ const char *property, *sender;
+ GSList *l;
+ int err;
+
+ if (!dbus_message_iter_init(msg, &iter))
+ return btd_error_invalid_args(msg);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&iter, &property);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+ return btd_error_invalid_args(msg);
+ dbus_message_iter_recurse(&iter, &value);
+
+ sender = dbus_message_get_sender(msg);
+ err = -EINVAL;
+
+ /* Check if sender has acquired the transport */
+ for (l = transport->owners; l; l = l->next) {
+ struct media_owner *owner = l->data;
+
+ if (g_strcmp0(owner->name, sender) == 0) {
+ err = transport->set_property(transport, property,
+ &value);
+ break;
+ }
+ }
+
+ if (err < 0) {
+ if (err == -EINVAL)
+ return btd_error_invalid_args(msg);
+ return btd_error_failed(msg, strerror(-err));
+ }
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static void get_properties_a2dp(struct media_transport *transport,
+ DBusMessageIter *dict)
+{
+ dict_append_entry(dict, "Delay", DBUS_TYPE_UINT16, &transport->delay);
+}
+
+static void get_properties_headset(struct media_transport *transport,
+ DBusMessageIter *dict)
+{
+ gboolean nrec, inband;
+ const char *routing;
+
+ nrec = headset_get_nrec(transport->device);
+ dict_append_entry(dict, "NREC", DBUS_TYPE_BOOLEAN, &nrec);
+
+ inband = headset_get_inband(transport->device);
+ dict_append_entry(dict, "InbandRingtone", DBUS_TYPE_BOOLEAN, &inband);
+
+ routing = headset_get_sco_hci(transport->device) ? "HCI" : "PCM";
+ dict_append_entry(dict, "Routing", DBUS_TYPE_STRING, &routing);
+}
+
+void transport_get_properties(struct media_transport *transport,
+ DBusMessageIter *iter)
+{
+ DBusMessageIter dict;
+ const char *uuid;
+ uint8_t codec;
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ /* Device */
+ dict_append_entry(&dict, "Device", DBUS_TYPE_OBJECT_PATH,
+ &transport->device->path);
+
+ uuid = media_endpoint_get_uuid(transport->endpoint);
+ dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid);
+
+ codec = media_endpoint_get_codec(transport->endpoint);
+ dict_append_entry(&dict, "Codec", DBUS_TYPE_BYTE, &codec);
+
+ dict_append_array(&dict, "Configuration", DBUS_TYPE_BYTE,
+ &transport->configuration, transport->size);
+
+ if (transport->get_properties)
+ transport->get_properties(transport, &dict);
+
+ dbus_message_iter_close_container(iter, &dict);
+}
+
+static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct media_transport *transport = data;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ transport_get_properties(transport, &iter);
+
+ return reply;
+}
+
+static GDBusMethodTable transport_methods[] = {
+ { "GetProperties", "", "a{sv}", get_properties },
+ { "Acquire", "s", "h", acquire,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { "Release", "s", "", release,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { "SetProperty", "sv", "", set_property },
+ { },
+};
+
+static GDBusSignalTable transport_signals[] = {
+ { "PropertyChanged", "sv" },
+ { }
+};
+
+static void media_transport_free(void *data)
+{
+ struct media_transport *transport = data;
+ GSList *l;
+
+ for (l = transport->owners; l; l = l->next)
+ media_transport_remove(transport, l->data);
+
+ g_slist_free(transport->owners);
+
+ if (transport->session)
+ avdtp_unref(transport->session);
+
+ if (transport->nrec_id)
+ headset_remove_nrec_cb(transport->device, transport->nrec_id);
+
+ if (transport->conn)
+ dbus_connection_unref(transport->conn);
+
+ g_free(transport->configuration);
+ g_free(transport->path);
+ g_free(transport);
+}
+
+static void headset_nrec_changed(struct audio_device *dev, gboolean nrec,
+ void *user_data)
+{
+ struct media_transport *transport = user_data;
+
+ DBG("");
+
+ emit_property_changed(transport->conn, transport->path,
+ MEDIA_TRANSPORT_INTERFACE, "NREC",
+ DBUS_TYPE_BOOLEAN, &nrec);
+}
+
+struct media_transport *media_transport_create(DBusConnection *conn,
+ struct media_endpoint *endpoint,
+ struct audio_device *device,
+ uint8_t *configuration,
+ size_t size)
+{
+ struct media_transport *transport;
+ const char *uuid;
+ static int fd = 0;
+
+ transport = g_new0(struct media_transport, 1);
+ transport->conn = dbus_connection_ref(conn);
+ transport->device = device;
+ transport->endpoint = endpoint;
+ transport->configuration = g_new(uint8_t, size);
+ memcpy(transport->configuration, configuration, size);
+ transport->size = size;
+ transport->path = g_strdup_printf("%s/fd%d", device->path, fd++);
+ transport->fd = -1;
+
+ uuid = media_endpoint_get_uuid(endpoint);
+ if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0 ||
+ strcasecmp(uuid, A2DP_SINK_UUID) == 0) {
+ transport->resume = resume_a2dp;
+ transport->suspend = suspend_a2dp;
+ transport->cancel = cancel_a2dp;
+ transport->get_properties = get_properties_a2dp;
+ transport->set_property = set_property_a2dp;
+ } else if (strcasecmp(uuid, HFP_AG_UUID) == 0 ||
+ strcasecmp(uuid, HSP_AG_UUID) == 0) {
+ transport->resume = resume_headset;
+ transport->suspend = suspend_headset;
+ transport->cancel = cancel_headset;
+ transport->get_properties = get_properties_headset;
+ transport->set_property = set_property_headset;
+ transport->nrec_id = headset_add_nrec_cb(device,
+ headset_nrec_changed,
+ transport);
+ } else
+ goto fail;
+
+ if (g_dbus_register_interface(transport->conn, transport->path,
+ MEDIA_TRANSPORT_INTERFACE,
+ transport_methods, transport_signals, NULL,
+ transport, media_transport_free) == FALSE) {
+ error("Could not register transport %s", transport->path);
+ goto fail;
+ }
+
+ return transport;
+
+fail:
+ media_transport_free(transport);
+ return NULL;
+}
+
+const char *media_transport_get_path(struct media_transport *transport)
+{
+ return transport->path;
+}
+
+void media_transport_update_delay(struct media_transport *transport,
+ uint16_t delay)
+{
+ /* Check if delay really changed */
+ if (transport->delay == delay)
+ return;
+
+ transport->delay = delay;
+
+ emit_property_changed(transport->conn, transport->path,
+ MEDIA_TRANSPORT_INTERFACE, "Delay",
+ DBUS_TYPE_UINT16, &transport->delay);
+}
diff --git a/audio/transport.h b/audio/transport.h
new file mode 100644
index 0000000..be4d666
--- /dev/null
+++ b/audio/transport.h
@@ -0,0 +1,38 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+struct media_transport;
+
+struct media_transport *media_transport_create(DBusConnection *conn,
+ struct media_endpoint *endpoint,
+ struct audio_device *device,
+ uint8_t *configuration,
+ size_t size);
+
+void media_transport_destroy(struct media_transport *transport);
+const char *media_transport_get_path(struct media_transport *transport);
+void media_transport_update_delay(struct media_transport *transport,
+ uint16_t delay);
+void transport_get_properties(struct media_transport *transport,
+ DBusMessageIter *iter);
diff --git a/audio/unix.c b/audio/unix.c
new file mode 100644
index 0000000..37c772d
--- /dev/null
+++ b/audio/unix.c
@@ -0,0 +1,1917 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <fcntl.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 "log.h"
+#include "ipc.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "media.h"
+#include "a2dp.h"
+#include "headset.h"
+#include "sink.h"
+#include "gateway.h"
+#include "unix.h"
+#include "glib-helper.h"
+
+#define check_nul(str) (str[sizeof(str) - 1] == '\0')
+
+typedef enum {
+ TYPE_NONE,
+ TYPE_HEADSET,
+ TYPE_GATEWAY,
+ 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 {
+ gboolean locked;
+};
+
+struct unix_client {
+ struct audio_device *dev;
+ GSList *caps;
+ service_type_t type;
+ char *interface;
+ uint8_t seid;
+ union {
+ struct a2dp_data a2dp;
+ struct headset_data hs;
+ } d;
+ int sock;
+ int lock;
+ 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)
+{
+ DBG("client_free(%p)", client);
+
+ if (client->cancel && client->dev && client->req_id > 0)
+ client->cancel(client->dev, client->req_id);
+
+ 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);
+}
+
+static int set_nonblocking(int fd)
+{
+ long arg;
+
+ arg = fcntl(fd, F_GETFL);
+ if (arg < 0)
+ return -errno;
+
+ /* Return if already nonblocking */
+ if (arg & O_NONBLOCK)
+ return 0;
+
+ arg |= O_NONBLOCK;
+ if (fcntl(fd, F_SETFL, arg) < 0)
+ return -errno;
+
+ return 0;
+}
+
+/* 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 */
+ memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+
+ return sendmsg(sock, &msgh, MSG_NOSIGNAL);
+}
+
+static void unix_ipc_sendmsg(struct unix_client *client,
+ const bt_audio_msg_header_t *msg)
+{
+ const char *type = bt_audio_strtype(msg->type);
+ const char *name = bt_audio_strname(msg->name);
+
+ DBG("Audio API: %s -> %s", type, name);
+
+ if (send(client->sock, msg, msg->length, 0) < 0)
+ error("Error %s(%d)", strerror(errno), errno);
+}
+
+static void unix_ipc_error(struct unix_client *client, uint8_t name, int err)
+{
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ bt_audio_error_t *rsp = (void *) buf;
+
+ if (!g_slist_find(clients, client))
+ return;
+
+ memset(buf, 0, sizeof(buf));
+ rsp->h.type = BT_ERROR;
+ rsp->h.name = name;
+ rsp->h.length = sizeof(*rsp);
+
+ rsp->posix_errno = err;
+
+ DBG("sending error %s(%d)", strerror(err), err);
+ unix_ipc_sendmsg(client, &rsp->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->source && avdtp_is_connected(&dev->src,
+ &dev->dst))
+ return TYPE_SOURCE;
+ else if (dev->headset && headset_is_active(dev))
+ return TYPE_HEADSET;
+ else if (dev->sink)
+ return TYPE_SINK;
+ else if (dev->source)
+ return TYPE_SOURCE;
+ else if (dev->headset)
+ return TYPE_HEADSET;
+ } else if (!strcmp(interface, AUDIO_SOURCE_INTERFACE) && dev->source)
+ return TYPE_SOURCE;
+ else if (!strcmp(interface, AUDIO_SINK_INTERFACE) && dev->sink)
+ return TYPE_SINK;
+ else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset)
+ return TYPE_HEADSET;
+ else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway)
+ return TYPE_GATEWAY;
+
+ 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;
+ }
+ if (a2dp->session) {
+ avdtp_unref(a2dp->session);
+ a2dp->session = NULL;
+ }
+ a2dp->stream = NULL;
+ client->cb_id = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+static uint8_t headset_generate_capability(struct audio_device *dev,
+ codec_capabilities_t *codec)
+{
+ pcm_capabilities_t *pcm;
+
+ codec->seid = BT_A2DP_SEID_RANGE + 1;
+ codec->transport = BT_CAPABILITIES_TRANSPORT_SCO;
+ codec->type = BT_HFP_CODEC_PCM;
+ codec->length = sizeof(*pcm);
+
+ pcm = (void *) codec;
+ pcm->sampling_rate = 8000;
+ if (dev->headset) {
+ if (headset_get_nrec(dev))
+ pcm->flags |= BT_PCM_FLAG_NREC;
+ if (!headset_get_sco_hci(dev))
+ pcm->flags |= BT_PCM_FLAG_PCM_ROUTING;
+ codec->configured = headset_is_active(dev);
+ codec->lock = headset_get_lock(dev);
+ } else {
+ pcm->flags |= BT_PCM_FLAG_NREC;
+ codec->configured = TRUE;
+ codec->lock = 0;
+ }
+
+ return codec->length;
+}
+
+static void headset_discovery_complete(struct audio_device *dev, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ struct bt_get_capabilities_rsp *rsp = (void *) buf;
+ uint8_t length;
+
+ client->req_id = 0;
+
+ if (!dev)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+
+ length = headset_generate_capability(dev, (void *) rsp->data);
+
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_GET_CAPABILITIES;
+ rsp->h.length = sizeof(*rsp) + length;
+
+ ba2str(&dev->src, rsp->source);
+ ba2str(&dev->dst, rsp->destination);
+ strncpy(rsp->object, dev->path, sizeof(rsp->object));
+
+ unix_ipc_sendmsg(client, &rsp->h);
+
+ return;
+
+failed:
+ error("discovery failed");
+ unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+}
+
+static void headset_setup_complete(struct audio_device *dev, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ struct bt_set_configuration_rsp *rsp = (void *) buf;
+
+ client->req_id = 0;
+
+ if (!dev)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_SET_CONFIGURATION;
+ rsp->h.length = sizeof(*rsp);
+
+ rsp->link_mtu = 48;
+
+ client->data_fd = headset_get_sco_fd(dev);
+
+ unix_ipc_sendmsg(client, &rsp->h);
+
+ return;
+
+failed:
+ error("config failed");
+ unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+}
+
+static void gateway_setup_complete(struct audio_device *dev, GError *err, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ struct bt_set_configuration_rsp *rsp = (void *) buf;
+
+ if (err) {
+ unix_ipc_error(client, BT_SET_CONFIGURATION, err->code);
+ return;
+ }
+
+ client->req_id = 0;
+
+ memset(buf, 0, sizeof(buf));
+
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_SET_CONFIGURATION;
+ rsp->h.length = sizeof(*rsp);
+
+ rsp->link_mtu = 48;
+
+ client->data_fd = gateway_get_sco_fd(dev);
+
+ unix_ipc_sendmsg(client, &rsp->h);
+}
+
+static void headset_resume_complete(struct audio_device *dev, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ struct bt_start_stream_rsp *rsp = (void *) buf;
+ struct bt_new_stream_ind *ind = (void *) buf;
+
+ client->req_id = 0;
+
+ if (!dev)
+ goto failed;
+
+ client->data_fd = headset_get_sco_fd(dev);
+ if (client->data_fd < 0) {
+ error("Unable to get a SCO fd");
+ goto failed;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_START_STREAM;
+ rsp->h.length = sizeof(*rsp);
+
+ unix_ipc_sendmsg(client, &rsp->h);
+
+ memset(buf, 0, sizeof(buf));
+ ind->h.type = BT_INDICATION;
+ ind->h.name = BT_NEW_STREAM;
+ ind->h.length = sizeof(*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("headset_resume_complete: resume failed");
+ unix_ipc_error(client, BT_START_STREAM, EIO);
+}
+
+static void gateway_resume_complete(struct audio_device *dev, GError *err, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ struct bt_start_stream_rsp *rsp = (void *) buf;
+ struct bt_new_stream_ind *ind = (void *) buf;
+
+ if (err) {
+ unix_ipc_error(client, BT_START_STREAM, err->code);
+ return;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_START_STREAM;
+ rsp->h.length = sizeof(*rsp);
+
+ unix_ipc_sendmsg(client, &rsp->h);
+
+ memset(buf, 0, sizeof(buf));
+ ind->h.type = BT_INDICATION;
+ ind->h.name = BT_NEW_STREAM;
+ ind->h.length = sizeof(*ind);
+
+ unix_ipc_sendmsg(client, &ind->h);
+
+ client->data_fd = gateway_get_sco_fd(dev);
+ if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) {
+ error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno);
+ unix_ipc_error(client, BT_START_STREAM, EIO);
+ }
+
+ client->req_id = 0;
+}
+
+static void headset_suspend_complete(struct audio_device *dev, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ struct bt_stop_stream_rsp *rsp = (void *) buf;
+
+ if (!dev)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_STOP_STREAM;
+ rsp->h.length = sizeof(*rsp);
+
+ unix_ipc_sendmsg(client, &rsp->h);
+
+ return;
+
+failed:
+ error("suspend failed");
+ unix_ipc_error(client, BT_STOP_STREAM, EIO);
+}
+
+static void print_mpeg12(struct mpeg_codec_cap *mpeg)
+{
+ DBG("Media Codec: MPEG12"
+ " Channel Modes: %s%s%s%s"
+ " Frequencies: %s%s%s%s%s%s"
+ " Layers: %s%s%s"
+ " CRC: %s",
+ mpeg->channel_mode & MPEG_CHANNEL_MODE_MONO ? "Mono " : "",
+ mpeg->channel_mode & MPEG_CHANNEL_MODE_DUAL_CHANNEL ?
+ "DualChannel " : "",
+ mpeg->channel_mode & MPEG_CHANNEL_MODE_STEREO ? "Stereo " : "",
+ mpeg->channel_mode & MPEG_CHANNEL_MODE_JOINT_STEREO ?
+ "JointStereo " : "",
+ mpeg->frequency & MPEG_SAMPLING_FREQ_16000 ? "16Khz " : "",
+ mpeg->frequency & MPEG_SAMPLING_FREQ_22050 ? "22.05Khz " : "",
+ mpeg->frequency & MPEG_SAMPLING_FREQ_24000 ? "24Khz " : "",
+ mpeg->frequency & MPEG_SAMPLING_FREQ_32000 ? "32Khz " : "",
+ mpeg->frequency & MPEG_SAMPLING_FREQ_44100 ? "44.1Khz " : "",
+ mpeg->frequency & MPEG_SAMPLING_FREQ_48000 ? "48Khz " : "",
+ mpeg->layer & MPEG_LAYER_MP1 ? "1 " : "",
+ mpeg->layer & MPEG_LAYER_MP2 ? "2 " : "",
+ mpeg->layer & MPEG_LAYER_MP3 ? "3 " : "",
+ mpeg->crc ? "Yes" : "No");
+}
+
+static void print_sbc(struct sbc_codec_cap *sbc)
+{
+ DBG("Media Codec: SBC"
+ " Channel Modes: %s%s%s%s"
+ " Frequencies: %s%s%s%s"
+ " Subbands: %s%s"
+ " Blocks: %s%s%s%s"
+ " Bitpool: %d-%d",
+ sbc->channel_mode & SBC_CHANNEL_MODE_MONO ? "Mono " : "",
+ sbc->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL ?
+ "DualChannel " : "",
+ sbc->channel_mode & SBC_CHANNEL_MODE_STEREO ? "Stereo " : "",
+ sbc->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO ? "JointStereo" : "",
+ sbc->frequency & SBC_SAMPLING_FREQ_16000 ? "16Khz " : "",
+ sbc->frequency & SBC_SAMPLING_FREQ_32000 ? "32Khz " : "",
+ sbc->frequency & SBC_SAMPLING_FREQ_44100 ? "44.1Khz " : "",
+ sbc->frequency & SBC_SAMPLING_FREQ_48000 ? "48Khz " : "",
+ sbc->subbands & SBC_SUBBANDS_4 ? "4 " : "",
+ sbc->subbands & SBC_SUBBANDS_8 ? "8 " : "",
+ sbc->block_length & SBC_BLOCK_LENGTH_4 ? "4 " : "",
+ sbc->block_length & SBC_BLOCK_LENGTH_8 ? "8 " : "",
+ sbc->block_length & SBC_BLOCK_LENGTH_12 ? "12 " : "",
+ sbc->block_length & SBC_BLOCK_LENGTH_16 ? "16 " : "",
+ sbc->min_bitpool, sbc->max_bitpool);
+}
+
+static int a2dp_append_codec(struct bt_get_capabilities_rsp *rsp,
+ struct avdtp_service_capability *cap,
+ uint8_t seid,
+ uint8_t type,
+ uint8_t configured,
+ uint8_t lock)
+{
+ struct avdtp_media_codec_capability *codec_cap = (void *) cap->data;
+ codec_capabilities_t *codec = (void *) rsp + rsp->h.length;
+ size_t space_left;
+
+ if (rsp->h.length > BT_SUGGESTED_BUFFER_SIZE)
+ return -ENOMEM;
+
+ space_left = BT_SUGGESTED_BUFFER_SIZE - rsp->h.length;
+
+ /* endianess prevent direct cast */
+ if (codec_cap->media_codec_type == A2DP_CODEC_SBC) {
+ struct sbc_codec_cap *sbc_cap = (void *) codec_cap;
+ sbc_capabilities_t *sbc = (void *) codec;
+
+ if (space_left < sizeof(sbc_capabilities_t))
+ return -ENOMEM;
+
+ if (type == AVDTP_SEP_TYPE_SINK)
+ codec->type = BT_A2DP_SBC_SINK;
+ else if (type == AVDTP_SEP_TYPE_SOURCE)
+ codec->type = BT_A2DP_SBC_SOURCE;
+ else
+ return -EINVAL;
+
+ codec->length = sizeof(sbc_capabilities_t);
+
+ sbc->channel_mode = sbc_cap->channel_mode;
+ sbc->frequency = sbc_cap->frequency;
+ sbc->allocation_method = sbc_cap->allocation_method;
+ sbc->subbands = sbc_cap->subbands;
+ sbc->block_length = sbc_cap->block_length;
+ sbc->min_bitpool = sbc_cap->min_bitpool;
+ sbc->max_bitpool = sbc_cap->max_bitpool;
+
+ print_sbc(sbc_cap);
+ } else if (codec_cap->media_codec_type == A2DP_CODEC_MPEG12) {
+ struct mpeg_codec_cap *mpeg_cap = (void *) codec_cap;
+ mpeg_capabilities_t *mpeg = (void *) codec;
+
+ if (space_left < sizeof(mpeg_capabilities_t))
+ return -ENOMEM;
+
+ if (type == AVDTP_SEP_TYPE_SINK)
+ codec->type = BT_A2DP_MPEG12_SINK;
+ else if (type == AVDTP_SEP_TYPE_SOURCE)
+ codec->type = BT_A2DP_MPEG12_SOURCE;
+ else
+ return -EINVAL;
+
+ codec->length = sizeof(mpeg_capabilities_t);
+
+ mpeg->channel_mode = mpeg_cap->channel_mode;
+ mpeg->crc = mpeg_cap->crc;
+ mpeg->layer = mpeg_cap->layer;
+ mpeg->frequency = mpeg_cap->frequency;
+ mpeg->mpf = mpeg_cap->mpf;
+ mpeg->bitrate = mpeg_cap->bitrate;
+
+ print_mpeg12(mpeg_cap);
+ } else {
+ size_t codec_length, type_length, total_length;
+
+ codec_length = cap->length - (sizeof(struct avdtp_service_capability)
+ + sizeof(struct avdtp_media_codec_capability));
+ type_length = sizeof(codec_cap->media_codec_type);
+ total_length = type_length + codec_length +
+ sizeof(codec_capabilities_t);
+
+ if (space_left < total_length)
+ return -ENOMEM;
+
+ if (type == AVDTP_SEP_TYPE_SINK)
+ codec->type = BT_A2DP_UNKNOWN_SINK;
+ else if (type == AVDTP_SEP_TYPE_SOURCE)
+ codec->type = BT_A2DP_UNKNOWN_SOURCE;
+ else
+ return -EINVAL;
+
+ codec->length = total_length;
+ memcpy(codec->data, &codec_cap->media_codec_type, type_length);
+ memcpy(codec->data + type_length, codec_cap->data,
+ codec_length);
+ }
+
+ codec->seid = seid;
+ codec->configured = configured;
+ codec->lock = lock;
+ rsp->h.length += codec->length;
+
+ DBG("Append %s seid %d - length %d - total %d",
+ configured ? "configured" : "", seid, codec->length,
+ rsp->h.length);
+
+ return 0;
+}
+
+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_SUGGESTED_BUFFER_SIZE];
+ struct bt_get_capabilities_rsp *rsp = (void *) buf;
+ struct a2dp_data *a2dp = &client->d.a2dp;
+ GSList *l;
+
+ if (!g_slist_find(clients, client)) {
+ DBG("Client disconnected during discovery");
+ return;
+ }
+
+ if (err)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+ client->req_id = 0;
+
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_GET_CAPABILITIES;
+ rsp->h.length = sizeof(*rsp);
+ ba2str(&client->dev->src, rsp->source);
+ ba2str(&client->dev->dst, rsp->destination);
+ strncpy(rsp->object, client->dev->path, sizeof(rsp->object));
+
+ for (l = seps; l; l = g_slist_next(l)) {
+ struct avdtp_remote_sep *rsep = l->data;
+ struct a2dp_sep *sep;
+ struct avdtp_service_capability *cap;
+ struct avdtp_stream *stream;
+ uint8_t type, seid, configured = 0, lock = 0;
+ GSList *cl;
+
+ type = avdtp_get_type(rsep);
+
+ if (type != AVDTP_SEP_TYPE_SINK &&
+ type != AVDTP_SEP_TYPE_SOURCE)
+ continue;
+
+ cap = avdtp_get_codec(rsep);
+
+ if (cap->category != AVDTP_MEDIA_CODEC)
+ continue;
+
+ seid = avdtp_get_seid(rsep);
+
+ if (client->seid != 0 && client->seid != seid)
+ continue;
+
+ stream = avdtp_get_stream(rsep);
+ if (stream) {
+ configured = 1;
+ if (client->seid == seid)
+ cap = avdtp_stream_get_codec(stream);
+ }
+
+ for (cl = clients; cl; cl = cl->next) {
+ struct unix_client *c = cl->data;
+ struct a2dp_data *ca2dp = &c->d.a2dp;
+
+ if (ca2dp->session == session && c->seid == seid) {
+ lock = c->lock;
+ break;
+ }
+ }
+
+ sep = a2dp_get_sep(session, stream);
+ if (sep && a2dp_sep_get_lock(sep))
+ lock = BT_WRITE_LOCK;
+
+ a2dp_append_codec(rsp, cap, seid, type, configured, lock);
+ }
+
+ unix_ipc_sendmsg(client, &rsp->h);
+
+ return;
+
+failed:
+ error("discovery failed");
+ unix_ipc_error(client, BT_GET_CAPABILITIES, EIO);
+
+ if (a2dp->sep) {
+ a2dp_sep_unlock(a2dp->sep, a2dp->session);
+ a2dp->sep = NULL;
+ }
+
+ 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_SUGGESTED_BUFFER_SIZE];
+ struct bt_set_configuration_rsp *rsp = (void *) buf;
+ struct a2dp_data *a2dp = &client->d.a2dp;
+ uint16_t imtu, omtu;
+ GSList *caps;
+
+ client->req_id = 0;
+
+ if (err)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+
+ if (!stream)
+ goto failed;
+
+ if (client->cb_id > 0)
+ avdtp_stream_remove_cb(a2dp->session, a2dp->stream,
+ client->cb_id);
+
+ 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->h.type = BT_RESPONSE;
+ rsp->h.name = BT_SET_CONFIGURATION;
+ rsp->h.length = sizeof(*rsp);
+
+ /* FIXME: Use imtu when fd_opt is CFG_FD_OPT_READ */
+ rsp->link_mtu = omtu;
+
+ unix_ipc_sendmsg(client, &rsp->h);
+
+ client->cb_id = avdtp_stream_add_cb(session, stream,
+ stream_state_changed, client);
+
+ return;
+
+failed:
+ error("config failed");
+
+ unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+
+ avdtp_unref(a2dp->session);
+
+ a2dp->session = NULL;
+ a2dp->stream = NULL;
+ a2dp->sep = 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_SUGGESTED_BUFFER_SIZE];
+ struct bt_start_stream_rsp *rsp = (void *) buf;
+ struct bt_new_stream_ind *ind = (void *) buf;
+ struct a2dp_data *a2dp = &client->d.a2dp;
+
+ if (err)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_START_STREAM;
+ rsp->h.length = sizeof(*rsp);
+
+ unix_ipc_sendmsg(client, &rsp->h);
+
+ memset(buf, 0, sizeof(buf));
+ ind->h.type = BT_RESPONSE;
+ ind->h.name = BT_NEW_STREAM;
+ rsp->h.length = sizeof(*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");
+
+ unix_ipc_error(client, BT_START_STREAM, EIO);
+
+ if (client->cb_id > 0) {
+ avdtp_stream_remove_cb(a2dp->session, a2dp->stream,
+ client->cb_id);
+ client->cb_id = 0;
+ }
+
+ if (a2dp->sep) {
+ a2dp_sep_unlock(a2dp->sep, a2dp->session);
+ a2dp->sep = NULL;
+ }
+
+ 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_SUGGESTED_BUFFER_SIZE];
+ struct bt_stop_stream_rsp *rsp = (void *) buf;
+
+ if (err)
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_STOP_STREAM;
+ rsp->h.length = sizeof(*rsp);
+
+ unix_ipc_sendmsg(client, &rsp->h);
+
+ return;
+
+failed:
+ error("suspend failed");
+
+ unix_ipc_error(client, BT_STOP_STREAM, EIO);
+}
+
+static void start_discovery(struct audio_device *dev, struct unix_client *client)
+{
+ struct a2dp_data *a2dp;
+ int err = 0;
+
+ switch (client->type) {
+ case TYPE_SINK:
+ case TYPE_SOURCE:
+ 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) {
+ if (a2dp->session) {
+ avdtp_unref(a2dp->session);
+ a2dp->session = NULL;
+ }
+ goto failed;
+ }
+ break;
+
+ case TYPE_HEADSET:
+ case TYPE_GATEWAY:
+ 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_GET_CAPABILITIES, err ? : EIO);
+}
+
+static void open_complete(struct audio_device *dev, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ struct bt_open_rsp *rsp = (void *) buf;
+
+ memset(buf, 0, sizeof(buf));
+
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_OPEN;
+ rsp->h.length = sizeof(*rsp);
+
+ ba2str(&dev->src, rsp->source);
+ ba2str(&dev->dst, rsp->destination);
+ strncpy(rsp->object, dev->path, sizeof(rsp->object));
+
+ unix_ipc_sendmsg(client, &rsp->h);
+}
+
+static void start_open(struct audio_device *dev, struct unix_client *client)
+{
+ struct a2dp_data *a2dp;
+ struct headset_data *hs;
+ struct avdtp_remote_sep *rsep;
+ gboolean unref_avdtp_on_fail = FALSE;
+
+ switch (client->type) {
+ case TYPE_SINK:
+ case TYPE_SOURCE:
+ a2dp = &client->d.a2dp;
+
+ if (!a2dp->session) {
+ a2dp->session = avdtp_get(&dev->src, &dev->dst);
+ unref_avdtp_on_fail = TRUE;
+ }
+
+ if (!a2dp->session) {
+ error("Unable to get a session");
+ goto failed;
+ }
+
+ if (a2dp->sep) {
+ error("Client already has an opened session");
+ goto failed;
+ }
+
+ rsep = avdtp_get_remote_sep(a2dp->session, client->seid);
+ if (!rsep) {
+ error("Invalid seid %d", client->seid);
+ goto failed;
+ }
+
+ a2dp->sep = a2dp_get(a2dp->session, rsep);
+ if (!a2dp->sep) {
+ error("seid %d not available or locked", client->seid);
+ goto failed;
+ }
+
+ if (!a2dp_sep_lock(a2dp->sep, a2dp->session)) {
+ error("Unable to open seid %d", client->seid);
+ a2dp->sep = NULL;
+ goto failed;
+ }
+
+ break;
+
+ case TYPE_HEADSET:
+ hs = &client->d.hs;
+
+ if (hs->locked) {
+ error("Client already has an opened session");
+ goto failed;
+ }
+
+ hs->locked = headset_lock(dev, client->lock);
+ if (!hs->locked) {
+ error("Unable to open seid %d", client->seid);
+ goto failed;
+ }
+ break;
+
+ case TYPE_GATEWAY:
+ break;
+ default:
+ error("No known services for device");
+ goto failed;
+ }
+
+ client->dev = dev;
+
+ open_complete(dev, client);
+
+ return;
+
+failed:
+ if (unref_avdtp_on_fail && a2dp->session) {
+ avdtp_unref(a2dp->session);
+ a2dp->session = NULL;
+ }
+ unix_ipc_error(client, BT_OPEN, EINVAL);
+}
+
+static void start_config(struct audio_device *dev, struct unix_client *client)
+{
+ struct a2dp_data *a2dp;
+ struct headset_data *hs;
+ unsigned int id;
+
+ switch (client->type) {
+ case TYPE_SINK:
+ case TYPE_SOURCE:
+ 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("seid %d not opened", client->seid);
+ goto failed;
+ }
+
+ id = a2dp_config(a2dp->session, a2dp->sep, a2dp_config_complete,
+ client->caps, client);
+ client->cancel = a2dp_cancel;
+ break;
+
+ case TYPE_HEADSET:
+ hs = &client->d.hs;
+
+ if (!hs->locked) {
+ error("seid %d not opened", client->seid);
+ goto failed;
+ }
+
+ id = headset_config_stream(dev, TRUE, headset_setup_complete,
+ client);
+ client->cancel = headset_cancel_stream;
+ break;
+ case TYPE_GATEWAY:
+ if (gateway_config_stream(dev, gateway_setup_complete, client) >= 0) {
+ client->cancel = gateway_cancel_stream;
+ id = 1;
+ } else
+ id = 0;
+ break;
+
+ default:
+ error("No known services for device");
+ goto failed;
+ }
+
+ if (id == 0) {
+ error("config failed");
+ goto failed;
+ }
+
+ client->req_id = id;
+ g_slist_free(client->caps);
+ client->caps = NULL;
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_SET_CONFIGURATION, EIO);
+}
+
+static void start_resume(struct audio_device *dev, struct unix_client *client)
+{
+ struct a2dp_data *a2dp = NULL;
+ struct headset_data *hs;
+ unsigned int id;
+ struct avdtp *session = NULL;
+
+ switch (client->type) {
+ case TYPE_SINK:
+ case TYPE_SOURCE:
+ a2dp = &client->d.a2dp;
+
+ if (!a2dp->sep) {
+ error("seid not opened");
+ goto failed;
+ }
+
+ if (!a2dp->session) {
+ session = avdtp_get(&dev->src, &dev->dst);
+ if (!session) {
+ error("Unable to get a session");
+ goto failed;
+ }
+ a2dp->session = session;
+ }
+
+ id = a2dp_resume(a2dp->session, a2dp->sep, a2dp_resume_complete,
+ client);
+ client->cancel = a2dp_cancel;
+
+ break;
+
+ case TYPE_HEADSET:
+ hs = &client->d.hs;
+
+ if (!hs->locked) {
+ error("seid not opened");
+ goto failed;
+ }
+
+ id = headset_request_stream(dev, headset_resume_complete,
+ client);
+ client->cancel = headset_cancel_stream;
+ break;
+
+ case TYPE_GATEWAY:
+ if (gateway_request_stream(dev, gateway_resume_complete, client))
+ id = 1;
+ else
+ id = 0;
+ client->cancel = gateway_cancel_stream;
+ break;
+
+ default:
+ error("No known services for device");
+ goto failed;
+ }
+
+ if (id == 0) {
+ error("start_resume: resume failed");
+ goto failed;
+ }
+
+ client->req_id = id;
+
+ return;
+
+failed:
+ if (session) {
+ avdtp_unref(session);
+ a2dp->session = NULL;
+ }
+
+ unix_ipc_error(client, BT_START_STREAM, EIO);
+}
+
+static void start_suspend(struct audio_device *dev, struct unix_client *client)
+{
+ struct a2dp_data *a2dp = NULL;
+ struct headset_data *hs;
+ unsigned int id;
+ struct avdtp *session = NULL;
+
+ switch (client->type) {
+ case TYPE_SINK:
+ case TYPE_SOURCE:
+ a2dp = &client->d.a2dp;
+
+ if (!a2dp->sep) {
+ error("seid not opened");
+ goto failed;
+ }
+
+ if (!a2dp->session) {
+ session = avdtp_get(&dev->src, &dev->dst);
+ if (!session) {
+ error("Unable to get a session");
+ goto failed;
+ }
+ a2dp->session = session;
+ }
+
+ if (!a2dp->sep) {
+ error("Unable to get a sep");
+ goto failed;
+ }
+
+ id = a2dp_suspend(a2dp->session, a2dp->sep,
+ a2dp_suspend_complete, client);
+ client->cancel = a2dp_cancel;
+ break;
+
+ case TYPE_HEADSET:
+ hs = &client->d.hs;
+
+ if (!hs->locked) {
+ error("seid not opened");
+ goto failed;
+ }
+
+ id = headset_suspend_stream(dev, headset_suspend_complete,
+ client);
+ client->cancel = headset_cancel_stream;
+ break;
+
+ case TYPE_GATEWAY:
+ gateway_suspend_stream(dev);
+ client->cancel = gateway_cancel_stream;
+ headset_suspend_complete(dev, client);
+ id = 1;
+ break;
+
+ default:
+ error("No known services for device");
+ goto failed;
+ }
+
+ if (id == 0) {
+ error("suspend failed");
+ goto failed;
+ }
+
+ return;
+
+failed:
+ if (session) {
+ avdtp_unref(session);
+ a2dp->session = NULL;
+ }
+
+ unix_ipc_error(client, BT_STOP_STREAM, EIO);
+}
+
+static void close_complete(struct audio_device *dev, void *user_data)
+{
+ struct unix_client *client = user_data;
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ struct bt_close_rsp *rsp = (void *) buf;
+
+ memset(buf, 0, sizeof(buf));
+
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_CLOSE;
+ rsp->h.length = sizeof(*rsp);
+
+ unix_ipc_sendmsg(client, &rsp->h);
+
+ return;
+}
+
+static void start_close(struct audio_device *dev, struct unix_client *client,
+ gboolean reply)
+{
+ struct a2dp_data *a2dp;
+ struct headset_data *hs;
+
+ if (!client->dev)
+ goto failed;
+
+ switch (client->type) {
+ case TYPE_HEADSET:
+ hs = &client->d.hs;
+
+ if (client->dev && hs->locked) {
+ headset_unlock(client->dev, client->lock);
+ hs->locked = FALSE;
+ }
+ break;
+ case TYPE_GATEWAY:
+ break;
+ case TYPE_SOURCE:
+ case TYPE_SINK:
+ a2dp = &client->d.a2dp;
+
+ if (client->cb_id > 0) {
+ avdtp_stream_remove_cb(a2dp->session, a2dp->stream,
+ client->cb_id);
+ client->cb_id = 0;
+ }
+ if (a2dp->sep) {
+ a2dp_sep_unlock(a2dp->sep, a2dp->session);
+ a2dp->sep = NULL;
+ }
+ if (a2dp->session) {
+ avdtp_unref(a2dp->session);
+ a2dp->session = NULL;
+ }
+ a2dp->stream = NULL;
+ break;
+ default:
+ error("No known services for device");
+ goto failed;
+ }
+
+ if (!reply)
+ return;
+
+ close_complete(dev, client);
+ client->dev = NULL;
+
+ return;
+
+failed:
+ if (reply)
+ unix_ipc_error(client, BT_STOP_STREAM, EINVAL);
+}
+
+static void handle_getcapabilities_req(struct unix_client *client,
+ struct bt_get_capabilities_req *req)
+{
+ struct audio_device *dev;
+ bdaddr_t src, dst;
+ int err = EIO;
+ const char *interface;
+
+ if (!check_nul(req->source) || !check_nul(req->destination) ||
+ !check_nul(req->object)) {
+ err = EINVAL;
+ goto failed;
+ }
+
+ str2ba(req->source, &src);
+ str2ba(req->destination, &dst);
+
+ if (!manager_find_device(req->object, &src, &dst, NULL, FALSE))
+ goto failed;
+
+ if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO)
+ interface = AUDIO_HEADSET_INTERFACE;
+ else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+ interface = AUDIO_SINK_INTERFACE;
+ else
+ interface = client->interface;
+
+ dev = manager_find_device(req->object, &src, &dst, interface, TRUE);
+ if (!dev && (req->flags & BT_FLAG_AUTOCONNECT))
+ dev = manager_find_device(req->object, &src, &dst,
+ interface, FALSE);
+
+ if (!dev) {
+ if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO)
+ interface = AUDIO_GATEWAY_INTERFACE;
+ else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP)
+ interface = AUDIO_SOURCE_INTERFACE;
+ else
+ interface = NULL;
+ dev = manager_find_device(req->object, &src, &dst,
+ interface, TRUE);
+ if (!dev && (req->flags & BT_FLAG_AUTOCONNECT))
+ dev = manager_find_device(req->object, &src, &dst,
+ interface, FALSE);
+ }
+
+ if (!dev) {
+ error("Unable to find a matching device");
+ goto failed;
+ }
+
+ client->type = select_service(dev, interface);
+ if (client->type == TYPE_NONE) {
+ error("No matching service found");
+ goto failed;
+ }
+
+ if (g_strcmp0(interface, client->interface) != 0) {
+ g_free(client->interface);
+ client->interface = g_strdup(interface);
+ }
+
+ client->seid = req->seid;
+
+ start_discovery(dev, client);
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_GET_CAPABILITIES, err);
+}
+
+static int handle_sco_open(struct unix_client *client, struct bt_open_req *req)
+{
+ if (!client->interface)
+ client->interface = g_strdup(AUDIO_HEADSET_INTERFACE);
+ else if (!g_str_equal(client->interface, AUDIO_HEADSET_INTERFACE) &&
+ !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE))
+ return -EIO;
+
+ DBG("open sco - object=%s source=%s destination=%s lock=%s%s",
+ strcmp(req->object, "") ? req->object : "ANY",
+ strcmp(req->source, "") ? req->source : "ANY",
+ strcmp(req->destination, "") ? req->destination : "ANY",
+ req->lock & BT_READ_LOCK ? "read" : "",
+ req->lock & BT_WRITE_LOCK ? "write" : "");
+
+ return 0;
+}
+
+static int handle_a2dp_open(struct unix_client *client, struct bt_open_req *req)
+{
+ if (!client->interface)
+ /* FIXME: are we treating a sink or a source? */
+ client->interface = g_strdup(AUDIO_SINK_INTERFACE);
+ else if (!g_str_equal(client->interface, AUDIO_SINK_INTERFACE) &&
+ !g_str_equal(client->interface, AUDIO_SOURCE_INTERFACE))
+ return -EIO;
+
+ DBG("open a2dp - object=%s source=%s destination=%s lock=%s%s",
+ strcmp(req->object, "") ? req->object : "ANY",
+ strcmp(req->source, "") ? req->source : "ANY",
+ strcmp(req->destination, "") ? req->destination : "ANY",
+ req->lock & BT_READ_LOCK ? "read" : "",
+ req->lock & BT_WRITE_LOCK ? "write" : "");
+
+ return 0;
+}
+
+static void handle_open_req(struct unix_client *client, struct bt_open_req *req)
+{
+ struct audio_device *dev;
+ bdaddr_t src, dst;
+ int err = 0;
+
+ if (!check_nul(req->source) || !check_nul(req->destination) ||
+ !check_nul(req->object)) {
+ err = EINVAL;
+ goto failed;
+ }
+
+ str2ba(req->source, &src);
+ str2ba(req->destination, &dst);
+
+ if (req->seid > BT_A2DP_SEID_RANGE) {
+ err = handle_sco_open(client, req);
+ if (err < 0) {
+ err = -err;
+ goto failed;
+ }
+ } else {
+ err = handle_a2dp_open(client, req);
+ if (err < 0) {
+ err = -err;
+ goto failed;
+ }
+ }
+
+ if (!manager_find_device(req->object, &src, &dst, NULL, FALSE))
+ goto failed;
+
+ dev = manager_find_device(req->object, &src, &dst, client->interface,
+ TRUE);
+ if (!dev)
+ dev = manager_find_device(req->object, &src, &dst,
+ client->interface, FALSE);
+
+ if (!dev)
+ goto failed;
+
+ client->seid = req->seid;
+ client->lock = req->lock;
+
+ start_open(dev, client);
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_OPEN, err ? : EIO);
+}
+
+static int handle_sco_transport(struct unix_client *client,
+ struct bt_set_configuration_req *req)
+{
+ struct audio_device *dev = client->dev;
+
+ if (!client->interface) {
+ if (dev->headset)
+ client->interface = g_strdup(AUDIO_HEADSET_INTERFACE);
+ else if (dev->gateway)
+ client->interface = g_strdup(AUDIO_GATEWAY_INTERFACE);
+ else
+ return -EIO;
+ } else if (!g_str_equal(client->interface, AUDIO_HEADSET_INTERFACE) &&
+ !g_str_equal(client->interface, AUDIO_GATEWAY_INTERFACE))
+ return -EIO;
+
+ return 0;
+}
+
+static int handle_a2dp_transport(struct unix_client *client,
+ struct bt_set_configuration_req *req)
+{
+ struct avdtp_service_capability *media_transport, *media_codec;
+ struct sbc_codec_cap sbc_cap;
+ struct mpeg_codec_cap mpeg_cap;
+
+ if (!client->interface)
+ /* FIXME: are we treating a sink or a source? */
+ client->interface = g_strdup(AUDIO_SINK_INTERFACE);
+ else if (!g_str_equal(client->interface, AUDIO_SINK_INTERFACE) &&
+ !g_str_equal(client->interface, AUDIO_SOURCE_INTERFACE))
+ return -EIO;
+
+ if (client->caps) {
+ g_slist_foreach(client->caps, (GFunc) g_free, NULL);
+ g_slist_free(client->caps);
+ client->caps = NULL;
+ }
+
+ media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+ NULL, 0);
+
+ client->caps = g_slist_append(client->caps, media_transport);
+
+ if (req->codec.type == BT_A2DP_MPEG12_SINK ||
+ req->codec.type == BT_A2DP_MPEG12_SOURCE) {
+ mpeg_capabilities_t *mpeg = (void *) &req->codec;
+
+ 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 = mpeg->channel_mode;
+ mpeg_cap.crc = mpeg->crc;
+ mpeg_cap.layer = mpeg->layer;
+ mpeg_cap.frequency = mpeg->frequency;
+ mpeg_cap.mpf = mpeg->mpf;
+ mpeg_cap.bitrate = mpeg->bitrate;
+
+ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &mpeg_cap,
+ sizeof(mpeg_cap));
+
+ print_mpeg12(&mpeg_cap);
+ } else if (req->codec.type == BT_A2DP_SBC_SINK ||
+ req->codec.type == BT_A2DP_SBC_SOURCE) {
+ sbc_capabilities_t *sbc = (void *) &req->codec;
+
+ 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 = sbc->channel_mode;
+ sbc_cap.frequency = sbc->frequency;
+ sbc_cap.allocation_method = sbc->allocation_method;
+ sbc_cap.subbands = sbc->subbands;
+ sbc_cap.block_length = sbc->block_length;
+ sbc_cap.min_bitpool = sbc->min_bitpool;
+ sbc_cap.max_bitpool = sbc->max_bitpool;
+
+ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
+ sizeof(sbc_cap));
+
+ print_sbc(&sbc_cap);
+ } 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_set_configuration_req *req)
+{
+ int err = 0;
+
+ if (req->codec.seid != client->seid) {
+ error("Unable to set configuration: seid %d not opened",
+ req->codec.seid);
+ goto failed;
+ }
+
+ if (!client->dev)
+ goto failed;
+
+ if (req->codec.transport == BT_CAPABILITIES_TRANSPORT_SCO) {
+ err = handle_sco_transport(client, req);
+ if (err < 0) {
+ err = -err;
+ goto failed;
+ }
+ } else if (req->codec.transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
+ err = handle_a2dp_transport(client, req);
+ if (err < 0) {
+ err = -err;
+ goto failed;
+ }
+ }
+
+ start_config(client->dev, client);
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_SET_CONFIGURATION, err ? : EIO);
+}
+
+static void handle_streamstart_req(struct unix_client *client,
+ struct bt_start_stream_req *req)
+{
+ if (!client->dev)
+ goto failed;
+
+ start_resume(client->dev, client);
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_START_STREAM, EIO);
+}
+
+static void handle_streamstop_req(struct unix_client *client,
+ struct bt_stop_stream_req *req)
+{
+ if (!client->dev)
+ goto failed;
+
+ start_suspend(client->dev, client);
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_STOP_STREAM, EIO);
+}
+
+static void handle_close_req(struct unix_client *client,
+ struct bt_close_req *req)
+{
+ if (!client->dev)
+ goto failed;
+
+ start_close(client->dev, client, TRUE);
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_CLOSE, EIO);
+}
+
+static void handle_control_req(struct unix_client *client,
+ struct bt_control_req *req)
+{
+ /* FIXME: really implement that */
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ struct bt_set_configuration_rsp *rsp = (void *) buf;
+
+ memset(buf, 0, sizeof(buf));
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_CONTROL;
+ rsp->h.length = sizeof(*rsp);
+
+ unix_ipc_sendmsg(client, &rsp->h);
+}
+
+static void handle_delay_report_req(struct unix_client *client,
+ struct bt_delay_report_req *req)
+{
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ struct bt_set_configuration_rsp *rsp = (void *) buf;
+ struct a2dp_data *a2dp;
+ int err;
+
+ if (!client->dev) {
+ err = -ENODEV;
+ goto failed;
+ }
+
+ switch (client->type) {
+ case TYPE_HEADSET:
+ case TYPE_GATEWAY:
+ err = -EINVAL;
+ goto failed;
+ case TYPE_SOURCE:
+ case TYPE_SINK:
+ a2dp = &client->d.a2dp;
+ if (a2dp->session && a2dp->stream) {
+ err = avdtp_delay_report(a2dp->session, a2dp->stream,
+ req->delay);
+ if (err < 0)
+ goto failed;
+ } else {
+ err = -EINVAL;
+ goto failed;
+ }
+ break;
+ default:
+ error("No known services for device");
+ err = -EINVAL;
+ goto failed;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_DELAY_REPORT;
+ rsp->h.length = sizeof(*rsp);
+
+ unix_ipc_sendmsg(client, &rsp->h);
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_DELAY_REPORT, -err);
+}
+
+static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ bt_audio_msg_header_t *msghdr = (void *) buf;
+ struct unix_client *client = data;
+ int len;
+ const char *type, *name;
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ if (cond & (G_IO_HUP | G_IO_ERR)) {
+ DBG("Unix client disconnected (fd=%d)", client->sock);
+
+ goto failed;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ len = recv(client->sock, buf, sizeof(buf), 0);
+ if (len < 0) {
+ error("recv: %s (%d)", strerror(errno), errno);
+ goto failed;
+ }
+
+ type = bt_audio_strtype(msghdr->type);
+ name = bt_audio_strname(msghdr->name);
+
+ DBG("Audio API: %s <- %s", type, name);
+
+ if (msghdr->length != len) {
+ error("Invalid message: length mismatch");
+ goto failed;
+ }
+
+ switch (msghdr->name) {
+ case BT_GET_CAPABILITIES:
+ handle_getcapabilities_req(client,
+ (struct bt_get_capabilities_req *) msghdr);
+ break;
+ case BT_OPEN:
+ handle_open_req(client,
+ (struct bt_open_req *) msghdr);
+ break;
+ case BT_SET_CONFIGURATION:
+ handle_setconfiguration_req(client,
+ (struct bt_set_configuration_req *) msghdr);
+ break;
+ case BT_START_STREAM:
+ handle_streamstart_req(client,
+ (struct bt_start_stream_req *) msghdr);
+ break;
+ case BT_STOP_STREAM:
+ handle_streamstop_req(client,
+ (struct bt_stop_stream_req *) msghdr);
+ break;
+ case BT_CLOSE:
+ handle_close_req(client,
+ (struct bt_close_req *) msghdr);
+ break;
+ case BT_CONTROL:
+ handle_control_req(client,
+ (struct bt_control_req *) msghdr);
+ break;
+ case BT_DELAY_REPORT:
+ handle_delay_report_req(client,
+ (struct bt_delay_report_req *) msghdr);
+ break;
+ default:
+ error("Audio API: received unexpected message name %d",
+ msghdr->name);
+ }
+
+ return TRUE;
+
+failed:
+ clients = g_slist_remove(clients, client);
+ start_close(client->dev, client, FALSE);
+ 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_shutdown(chan, TRUE, NULL);
+ 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;
+ }
+
+ DBG("Accepted new client connection on unix socket (fd=%d)", cli_sk);
+ set_nonblocking(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;
+}
+
+void unix_device_removed(struct audio_device *dev)
+{
+ GSList *l;
+
+ DBG("unix_device_removed(%p)", dev);
+
+ l = clients;
+ while (l) {
+ struct unix_client *client = l->data;
+
+ l = l->next;
+
+ if (client->dev == dev) {
+ clients = g_slist_remove(clients, client);
+ start_close(client->dev, client, FALSE);
+ client_free(client);
+ }
+ }
+}
+
+void unix_delay_report(struct audio_device *dev, uint8_t seid, uint16_t delay)
+{
+ GSList *l;
+ struct bt_delay_report_ind ind;
+
+ DBG("unix_delay_report(%p): %u.%ums", dev, delay / 10, delay % 10);
+
+ memset(&ind, 0, sizeof(ind));
+ ind.h.type = BT_INDICATION;
+ ind.h.name = BT_DELAY_REPORT;
+ ind.h.length = sizeof(ind);
+ ind.delay = delay;
+
+ for (l = clients; l != NULL; l = g_slist_next(l)) {
+ struct unix_client *client = l->data;
+
+ if (client->dev != dev || client->seid != seid)
+ continue;
+
+ unix_ipc_sendmsg(client, (void *) &ind);
+ }
+}
+
+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);
+
+ if (listen(sk, 1) < 0) {
+ error("Can't listen on unix socket: %s (%d)",
+ strerror(errno), errno);
+ close(sk);
+ return -1;
+ }
+
+ unix_sock = sk;
+
+ 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);
+
+ DBG("Unix socket created: %d", sk);
+
+ return 0;
+}
+
+void unix_exit(void)
+{
+ g_slist_foreach(clients, (GFunc) client_free, NULL);
+ g_slist_free(clients);
+ if (unix_sock >= 0) {
+ close(unix_sock);
+ unix_sock = -1;
+ }
+}
diff --git a/audio/unix.h b/audio/unix.h
new file mode 100644
index 0000000..74ca16d
--- /dev/null
+++ b/audio/unix.h
@@ -0,0 +1,30 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+void unix_device_removed(struct audio_device *dev);
+
+void unix_delay_report(struct audio_device *dev, uint8_t seid, uint16_t delay);
+
+int unix_init(void);
+void unix_exit(void);