summaryrefslogtreecommitdiff
path: root/test/ipctest.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/ipctest.c')
-rw-r--r--test/ipctest.c1129
1 files changed, 1129 insertions, 0 deletions
diff --git a/test/ipctest.c b/test/ipctest.c
new file mode 100644
index 0000000..9fdfac4
--- /dev/null
+++ b/test/ipctest.c
@@ -0,0 +1,1129 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2009 Lennart Poettering
+ * Copyright (C) 2008 Joao Paulo Rechi Vita
+ *
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+#include <libgen.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include <glib.h>
+
+#include "ipc.h"
+#include "sbc.h"
+
+#define DBG(fmt, arg...) \
+ printf("debug %s: " fmt "\n" , __FUNCTION__ , ## arg)
+#define ERR(fmt, arg...) \
+ fprintf(stderr, "ERROR %s: " fmt "\n" , __FUNCTION__ , ## arg)
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+#ifndef MIN
+# define MIN(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
+#ifndef MAX
+# define MAX(x, y) ((x) > (y) ? (x) : (y))
+#endif
+
+#ifndef TRUE
+# define TRUE (1)
+#endif
+
+#ifndef FALSE
+# define FALSE (0)
+#endif
+
+#define YES_NO(t) ((t) ? "yes" : "no")
+
+#define BUFFER_SIZE 2048
+#define MAX_BITPOOL 64
+#define MIN_BITPOOL 2
+
+struct a2dp_info {
+ sbc_capabilities_t sbc_capabilities;
+ sbc_t sbc; /* Codec data */
+ int sbc_initialized; /* Keep track if the encoder is initialized */
+ size_t codesize; /* SBC codesize */
+
+ void* buffer; /* Codec transfer buffer */
+ size_t buffer_size; /* Size of the buffer */
+
+ uint16_t seq_num; /* Cumulative packet sequence */
+};
+
+struct hsp_info {
+ pcm_capabilities_t pcm_capabilities;
+};
+
+struct userdata {
+ int service_fd;
+ int stream_fd;
+ GIOChannel *stream_channel;
+ guint stream_watch;
+ GIOChannel *gin; /* dude, I am thirsty now */
+ guint gin_watch;
+ int transport;
+ uint32_t rate;
+ int channels;
+ char *address;
+ struct a2dp_info a2dp;
+ struct hsp_info hsp;
+ size_t link_mtu;
+ size_t block_size;
+ gboolean debug_stream_read : 1;
+ gboolean debug_stream_write : 1;
+};
+
+static struct userdata data = {
+ .service_fd = -1,
+ .stream_fd = -1,
+ .transport = BT_CAPABILITIES_TRANSPORT_A2DP,
+ .rate = 48000,
+ .channels = 2,
+ .address = NULL
+};
+
+static int start_stream(struct userdata *u);
+static int stop_stream(struct userdata *u);
+static gboolean input_cb(GIOChannel *gin, GIOCondition condition, gpointer data);
+
+static GMainLoop *main_loop;
+
+static int service_send(struct userdata *u, const bt_audio_msg_header_t *msg)
+{
+ int err;
+ uint16_t length;
+
+ assert(u);
+
+ 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(u->service_fd, msg, length, 0) > 0)
+ err = 0;
+ else {
+ err = -errno;
+ ERR("Error sending data to audio service: %s(%d)",
+ strerror(errno), errno);
+ }
+
+ return err;
+}
+
+static int service_recv(struct userdata *u, bt_audio_msg_header_t *rsp)
+{
+ int err;
+ const char *type, *name;
+ uint16_t length;
+
+ assert(u);
+
+ length = rsp->length ? : BT_SUGGESTED_BUFFER_SIZE;
+
+ DBG("trying to receive msg from audio service...");
+ if (recv(u->service_fd, rsp, length, 0) > 0) {
+ type = bt_audio_strtype(rsp->type);
+ name = bt_audio_strname(rsp->name);
+ if (type && name) {
+ DBG("Received %s - %s", type, name);
+ err = 0;
+ } else {
+ err = -EINVAL;
+ ERR("Bogus message type %d - name %d"
+ "received from audio service",
+ rsp->type, rsp->name);
+ }
+ } else {
+ err = -errno;
+ ERR("Error receiving data from audio service: %s(%d)",
+ strerror(errno), errno);
+ }
+
+ return err;
+}
+
+static ssize_t service_expect(struct userdata *u, bt_audio_msg_header_t *rsp,
+ uint8_t expected_name)
+{
+ int r;
+
+ assert(u);
+ assert(u->service_fd >= 0);
+ assert(rsp);
+
+ if ((r = service_recv(u, rsp)) < 0)
+ return r;
+
+ if ((rsp->type != BT_INDICATION && rsp->type != BT_RESPONSE) ||
+ (rsp->name != expected_name)) {
+ if (rsp->type == BT_ERROR && rsp->length == sizeof(bt_audio_error_t))
+ ERR("Received error condition: %s",
+ strerror(((bt_audio_error_t*) rsp)->posix_errno));
+ else
+ ERR("Bogus message %s received while %s was expected",
+ bt_audio_strname(rsp->name),
+ bt_audio_strname(expected_name));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int init_bt(struct userdata *u)
+{
+ assert(u);
+
+ if (u->service_fd != -1)
+ return 0;
+
+ DBG("bt_audio_service_open");
+
+ u->service_fd = bt_audio_service_open();
+ if (u->service_fd <= 0) {
+ perror(strerror(errno));
+ return errno;
+ }
+
+ return 0;
+}
+
+static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *rsp)
+{
+ unsigned char *ptr;
+ uint16_t bytes_left;
+ codec_capabilities_t codec;
+
+ assert(u);
+ assert(rsp);
+
+ bytes_left = rsp->h.length - sizeof(*rsp);
+
+ if (bytes_left < sizeof(codec_capabilities_t)) {
+ ERR("Packet too small to store codec information.");
+ return -1;
+ }
+
+ ptr = ((void *) rsp) + sizeof(*rsp);
+
+ memcpy(&codec, ptr, sizeof(codec)); /** ALIGNMENT? **/
+
+ DBG("Payload size is %lu %lu",
+ (unsigned long) bytes_left, (unsigned long) sizeof(codec));
+
+ if (u->transport != codec.transport) {
+ ERR("Got capabilities for wrong codec.");
+ return -1;
+ }
+
+ if (u->transport == BT_CAPABILITIES_TRANSPORT_SCO) {
+
+ if (bytes_left <= 0 ||
+ codec.length != sizeof(u->hsp.pcm_capabilities))
+ return -1;
+
+ assert(codec.type == BT_HFP_CODEC_PCM);
+
+ memcpy(&u->hsp.pcm_capabilities,
+ &codec, sizeof(u->hsp.pcm_capabilities));
+
+ DBG("Has NREC: %s",
+ YES_NO(u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC));
+
+ } else if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
+
+ while (bytes_left > 0) {
+ if (codec.type == BT_A2DP_SBC_SINK &&
+ !(codec.lock & BT_WRITE_LOCK))
+ break;
+
+ bytes_left -= codec.length;
+ ptr += codec.length;
+ memcpy(&codec, ptr, sizeof(codec));
+ }
+
+ DBG("bytes_left = %d, codec.length = %d",
+ bytes_left, codec.length);
+
+ if (bytes_left <= 0 ||
+ codec.length != sizeof(u->a2dp.sbc_capabilities))
+ return -1;
+
+ assert(codec.type == BT_A2DP_SBC_SINK);
+
+ memcpy(&u->a2dp.sbc_capabilities, &codec,
+ sizeof(u->a2dp.sbc_capabilities));
+ } else {
+ assert(0);
+ }
+
+ return 0;
+}
+
+static int get_caps(struct userdata *u)
+{
+ union {
+ struct bt_get_capabilities_req getcaps_req;
+ struct bt_get_capabilities_rsp getcaps_rsp;
+ bt_audio_error_t error;
+ uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
+ } msg;
+
+ assert(u);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.getcaps_req.h.type = BT_REQUEST;
+ msg.getcaps_req.h.name = BT_GET_CAPABILITIES;
+ msg.getcaps_req.h.length = sizeof(msg.getcaps_req);
+
+ strncpy(msg.getcaps_req.destination, u->address,
+ sizeof(msg.getcaps_req.destination));
+ msg.getcaps_req.transport = u->transport;
+ msg.getcaps_req.flags = BT_FLAG_AUTOCONNECT;
+
+ if (service_send(u, &msg.getcaps_req.h) < 0)
+ return -1;
+
+ msg.getcaps_rsp.h.length = 0;
+ if (service_expect(u, &msg.getcaps_rsp.h, BT_GET_CAPABILITIES) < 0)
+ return -1;
+
+ return parse_caps(u, &msg.getcaps_rsp);
+}
+
+static uint8_t a2dp_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 setup_a2dp(struct userdata *u)
+{
+ sbc_capabilities_t *cap;
+ int i;
+
+ static const struct {
+ uint32_t rate;
+ uint8_t cap;
+ } freq_table[] = {
+ { 16000U, BT_SBC_SAMPLING_FREQ_16000 },
+ { 32000U, BT_SBC_SAMPLING_FREQ_32000 },
+ { 44100U, BT_SBC_SAMPLING_FREQ_44100 },
+ { 48000U, BT_SBC_SAMPLING_FREQ_48000 }
+ };
+
+ assert(u);
+ assert(u->transport == BT_CAPABILITIES_TRANSPORT_A2DP);
+
+ cap = &u->a2dp.sbc_capabilities;
+
+ /* Find the lowest freq that is at least as high as the requested
+ * sampling rate */
+ for (i = 0; (unsigned) i < ARRAY_SIZE(freq_table); i++)
+ if (freq_table[i].rate >= u->rate &&
+ (cap->frequency & freq_table[i].cap)) {
+ u->rate = freq_table[i].rate;
+ cap->frequency = freq_table[i].cap;
+ break;
+ }
+
+ if ((unsigned) i >= ARRAY_SIZE(freq_table)) {
+ for (; i >= 0; i--) {
+ if (cap->frequency & freq_table[i].cap) {
+ u->rate = freq_table[i].rate;
+ cap->frequency = freq_table[i].cap;
+ break;
+ }
+ }
+
+ if (i < 0) {
+ DBG("Not suitable sample rate");
+ return -1;
+ }
+ }
+
+ if (u->channels <= 1) {
+ if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
+ cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
+ u->channels = 1;
+ } else
+ u->channels = 2;
+ }
+
+ if (u->channels >= 2) {
+ u->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;
+ u->channels = 1;
+ } else {
+ DBG("No supported channel modes");
+ return -1;
+ }
+ }
+
+ 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 (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 (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;
+
+ cap->min_bitpool = (uint8_t) MAX(MIN_BITPOOL, cap->min_bitpool);
+ cap->max_bitpool = (uint8_t) MIN(
+ a2dp_default_bitpool(cap->frequency, cap->channel_mode),
+ cap->max_bitpool);
+
+ return 0;
+}
+
+static void setup_sbc(struct a2dp_info *a2dp)
+{
+ sbc_capabilities_t *active_capabilities;
+
+ assert(a2dp);
+
+ active_capabilities = &a2dp->sbc_capabilities;
+
+ if (a2dp->sbc_initialized)
+ sbc_reinit(&a2dp->sbc, 0);
+ else
+ sbc_init(&a2dp->sbc, 0);
+ a2dp->sbc_initialized = TRUE;
+
+ switch (active_capabilities->frequency) {
+ case BT_SBC_SAMPLING_FREQ_16000:
+ a2dp->sbc.frequency = SBC_FREQ_16000;
+ break;
+ case BT_SBC_SAMPLING_FREQ_32000:
+ a2dp->sbc.frequency = SBC_FREQ_32000;
+ break;
+ case BT_SBC_SAMPLING_FREQ_44100:
+ a2dp->sbc.frequency = SBC_FREQ_44100;
+ break;
+ case BT_SBC_SAMPLING_FREQ_48000:
+ a2dp->sbc.frequency = SBC_FREQ_48000;
+ break;
+ default:
+ assert(0);
+ }
+
+ switch (active_capabilities->channel_mode) {
+ case BT_A2DP_CHANNEL_MODE_MONO:
+ a2dp->sbc.mode = SBC_MODE_MONO;
+ break;
+ case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL:
+ a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL;
+ break;
+ case BT_A2DP_CHANNEL_MODE_STEREO:
+ a2dp->sbc.mode = SBC_MODE_STEREO;
+ break;
+ case BT_A2DP_CHANNEL_MODE_JOINT_STEREO:
+ a2dp->sbc.mode = SBC_MODE_JOINT_STEREO;
+ break;
+ default:
+ assert(0);
+ }
+
+ switch (active_capabilities->allocation_method) {
+ case BT_A2DP_ALLOCATION_SNR:
+ a2dp->sbc.allocation = SBC_AM_SNR;
+ break;
+ case BT_A2DP_ALLOCATION_LOUDNESS:
+ a2dp->sbc.allocation = SBC_AM_LOUDNESS;
+ break;
+ default:
+ assert(0);
+ }
+
+ 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;
+ default:
+ assert(0);
+ }
+
+ 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;
+ default:
+ assert(0);
+ }
+
+ a2dp->sbc.bitpool = active_capabilities->max_bitpool;
+ a2dp->codesize = (uint16_t) sbc_get_codesize(&a2dp->sbc);
+}
+
+static int bt_open(struct userdata *u)
+{
+ union {
+ struct bt_open_req open_req;
+ struct bt_open_rsp open_rsp;
+ bt_audio_error_t error;
+ uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
+ } msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.open_req.h.type = BT_REQUEST;
+ msg.open_req.h.name = BT_OPEN;
+ msg.open_req.h.length = sizeof(msg.open_req);
+
+ strncpy(msg.open_req.destination, u->address,
+ sizeof(msg.open_req.destination));
+ msg.open_req.seid = u->transport == BT_CAPABILITIES_TRANSPORT_A2DP ?
+ u->a2dp.sbc_capabilities.capability.seid :
+ BT_A2DP_SEID_RANGE + 1;
+ msg.open_req.lock = u->transport == BT_CAPABILITIES_TRANSPORT_A2DP ?
+ BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK;
+
+ if (service_send(u, &msg.open_req.h) < 0)
+ return -1;
+
+ msg.open_rsp.h.length = sizeof(msg.open_rsp);
+ if (service_expect(u, &msg.open_rsp.h, BT_OPEN) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int set_conf(struct userdata *u)
+{
+ union {
+ struct bt_set_configuration_req setconf_req;
+ struct bt_set_configuration_rsp setconf_rsp;
+ bt_audio_error_t error;
+ uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
+ } msg;
+
+ if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
+ if (setup_a2dp(u) < 0)
+ return -1;
+ }
+
+ memset(&msg, 0, sizeof(msg));
+ msg.setconf_req.h.type = BT_REQUEST;
+ msg.setconf_req.h.name = BT_SET_CONFIGURATION;
+ msg.setconf_req.h.length = sizeof(msg.setconf_req);
+
+ if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
+ memcpy(&msg.setconf_req.codec, &u->a2dp.sbc_capabilities,
+ sizeof(u->a2dp.sbc_capabilities));
+ msg.setconf_req.h.length += msg.setconf_req.codec.length -
+ sizeof(msg.setconf_req.codec);
+ } else {
+ msg.setconf_req.codec.transport = BT_CAPABILITIES_TRANSPORT_SCO;
+ msg.setconf_req.codec.seid = BT_A2DP_SEID_RANGE + 1;
+ msg.setconf_req.codec.length = sizeof(pcm_capabilities_t);
+ }
+
+ if (service_send(u, &msg.setconf_req.h) < 0)
+ return -1;
+
+ msg.setconf_rsp.h.length = sizeof(msg.setconf_rsp);
+ if (service_expect(u, &msg.setconf_rsp.h, BT_SET_CONFIGURATION) < 0)
+ return -1;
+
+ u->link_mtu = msg.setconf_rsp.link_mtu;
+
+ /* setup SBC encoder now we agree on parameters */
+ if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
+ setup_sbc(&u->a2dp);
+ u->block_size = u->a2dp.codesize;
+ DBG("SBC parameters:\n\tallocation=%u\n"
+ "\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",
+ u->a2dp.sbc.allocation, u->a2dp.sbc.subbands,
+ u->a2dp.sbc.blocks, u->a2dp.sbc.bitpool);
+ } else
+ u->block_size = u->link_mtu;
+
+ return 0;
+}
+
+static int setup_bt(struct userdata *u)
+{
+ assert(u);
+
+ if (get_caps(u) < 0)
+ return -1;
+
+ DBG("Got device caps");
+
+ if (bt_open(u) < 0)
+ return -1;
+
+ if (set_conf(u) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int init_profile(struct userdata *u)
+{
+ assert(u);
+
+ return setup_bt(u);
+}
+
+static void shutdown_bt(struct userdata *u)
+{
+ assert(u);
+
+ if (u->stream_fd != -1) {
+ stop_stream(u);
+ DBG("close(stream_fd)");
+ close(u->stream_fd);
+ u->stream_fd = -1;
+ }
+
+ if (u->service_fd != -1) {
+ DBG("bt_audio_service_close");
+ bt_audio_service_close(u->service_fd);
+ u->service_fd = -1;
+ }
+}
+
+static void make_fd_nonblock(int fd)
+{
+ int v;
+
+ assert(fd >= 0);
+ assert((v = fcntl(fd, F_GETFL)) >= 0);
+
+ if (!(v & O_NONBLOCK))
+ assert(fcntl(fd, F_SETFL, v|O_NONBLOCK) >= 0);
+}
+
+static void make_socket_low_delay(int fd)
+{
+/* FIXME: is this widely supported? */
+#ifdef SO_PRIORITY
+ int priority;
+ assert(fd >= 0);
+
+ priority = 6;
+ if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, (void*)&priority,
+ sizeof(priority)) < 0)
+ ERR("SO_PRIORITY failed: %s", strerror(errno));
+#endif
+}
+
+static int read_stream(struct userdata *u)
+{
+ int ret = 0;
+ ssize_t l;
+ char *buf;
+
+ assert(u);
+ assert(u->stream_fd >= 0);
+
+ buf = alloca(u->link_mtu);
+
+ for (;;) {
+ l = read(u->stream_fd, buf, u->link_mtu);
+ if (u->debug_stream_read)
+ DBG("read from socket: %lli bytes", (long long) l);
+ if (l <= 0) {
+ if (l < 0 && errno == EINTR)
+ continue;
+ else {
+ ERR("Failed to read date from stream_fd: %s",
+ ret < 0 ? strerror(errno) : "EOF");
+ return -1;
+ }
+ } else {
+ break;
+ }
+ }
+
+ return ret;
+}
+
+/* It's what PulseAudio is doing, not sure it's necessary for this
+ * test */
+static ssize_t pa_write(int fd, const void *buf, size_t count)
+{
+ ssize_t r;
+
+ if ((r = send(fd, buf, count, MSG_NOSIGNAL)) >= 0)
+ return r;
+
+ if (errno != ENOTSOCK)
+ return r;
+
+ return write(fd, buf, count);
+}
+
+static int write_stream(struct userdata *u)
+{
+ int ret = 0;
+ ssize_t l;
+ char *buf;
+
+ assert(u);
+ assert(u->stream_fd >= 0);
+ buf = alloca(u->link_mtu);
+
+ for (;;) {
+ l = pa_write(u->stream_fd, buf, u->link_mtu);
+ if (u->debug_stream_write)
+ DBG("written to socket: %lli bytes", (long long) l);
+ assert(l != 0);
+ if (l < 0) {
+ if (errno == EINTR)
+ continue;
+ else {
+ ERR("Failed to write data: %s", strerror(errno));
+ ret = -1;
+ break;
+ }
+ } else {
+ assert((size_t)l <= u->link_mtu);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static gboolean stream_cb(GIOChannel *gin, GIOCondition condition, gpointer data)
+{
+ struct userdata *u;
+
+ assert(u = data);
+
+ if (condition & G_IO_IN) {
+ if (read_stream(u) < 0)
+ goto fail;
+ } else if (condition & G_IO_OUT) {
+ if (write_stream(u) < 0)
+ goto fail;
+ } else {
+ DBG("Got %d", condition);
+ g_main_loop_quit(main_loop);
+ return FALSE;
+ }
+
+ return TRUE;
+
+fail:
+ stop_stream(u);
+ return FALSE;
+}
+
+static int start_stream(struct userdata *u)
+{
+ union {
+ bt_audio_msg_header_t rsp;
+ struct bt_start_stream_req start_req;
+ struct bt_start_stream_rsp start_rsp;
+ struct bt_new_stream_ind streamfd_ind;
+ bt_audio_error_t error;
+ uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
+ } msg;
+
+ assert(u);
+
+ if (u->stream_fd >= 0)
+ return 0;
+ if (u->stream_watch != 0) {
+ g_source_remove(u->stream_watch);
+ u->stream_watch = 0;
+ }
+ if (u->stream_channel != 0) {
+ g_io_channel_unref(u->stream_channel);
+ u->stream_channel = NULL;
+ }
+
+ memset(msg.buf, 0, BT_SUGGESTED_BUFFER_SIZE);
+ msg.start_req.h.type = BT_REQUEST;
+ msg.start_req.h.name = BT_START_STREAM;
+ msg.start_req.h.length = sizeof(msg.start_req);
+
+ if (service_send(u, &msg.start_req.h) < 0)
+ return -1;
+
+ msg.rsp.length = sizeof(msg.start_rsp);
+ if (service_expect(u, &msg.rsp, BT_START_STREAM) < 0)
+ return -1;
+
+ msg.rsp.length = sizeof(msg.streamfd_ind);
+ if (service_expect(u, &msg.rsp, BT_NEW_STREAM) < 0)
+ return -1;
+
+ if ((u->stream_fd = bt_audio_service_get_data_fd(u->service_fd)) < 0) {
+ DBG("Failed to get stream fd from audio service.");
+ return -1;
+ }
+
+ make_fd_nonblock(u->stream_fd);
+ make_socket_low_delay(u->stream_fd);
+
+ assert(u->stream_channel = g_io_channel_unix_new(u->stream_fd));
+
+ u->stream_watch = g_io_add_watch(u->stream_channel,
+ G_IO_IN|G_IO_OUT|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
+ stream_cb, u);
+
+ return 0;
+}
+
+static int stop_stream(struct userdata *u)
+{
+ union {
+ bt_audio_msg_header_t rsp;
+ struct bt_stop_stream_req stop_req;
+ struct bt_stop_stream_rsp stop_rsp;
+ bt_audio_error_t error;
+ uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
+ } msg;
+ int r = 0;
+
+ if (u->stream_fd < 0)
+ return 0;
+
+ assert(u);
+ assert(u->stream_channel);
+
+ g_source_remove(u->stream_watch);
+ u->stream_watch = 0;
+ g_io_channel_unref(u->stream_channel);
+ u->stream_channel = NULL;
+
+ memset(msg.buf, 0, BT_SUGGESTED_BUFFER_SIZE);
+ msg.stop_req.h.type = BT_REQUEST;
+ msg.stop_req.h.name = BT_STOP_STREAM;
+ msg.stop_req.h.length = sizeof(msg.stop_req);
+
+ if (service_send(u, &msg.stop_req.h) < 0) {
+ r = -1;
+ goto done;
+ }
+
+ msg.rsp.length = sizeof(msg.stop_rsp);
+ if (service_expect(u, &msg.rsp, BT_STOP_STREAM) < 0)
+ r = -1;
+
+done:
+ close(u->stream_fd);
+ u->stream_fd = -1;
+
+ return r;
+}
+
+static gboolean sleep_cb(gpointer data)
+{
+ struct userdata *u;
+
+ assert(u = data);
+
+ u->gin_watch = g_io_add_watch(u->gin,
+ G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, input_cb, data);
+
+ printf(">>> ");
+ fflush(stdout);
+
+ return FALSE;
+}
+
+static gboolean input_cb(GIOChannel *gin, GIOCondition condition, gpointer data)
+{
+ char *line, *tmp;
+ gsize term_pos;
+ GError *error = NULL;
+ struct userdata *u;
+ int success;
+
+ assert(u = data);
+ if (!(condition & G_IO_IN)) {
+ DBG("Got %d", condition);
+ g_main_loop_quit(main_loop);
+ return FALSE;
+ }
+
+ if (g_io_channel_read_line(gin, &line, NULL, &term_pos, &error) !=
+ G_IO_STATUS_NORMAL)
+ return FALSE;
+
+ line[term_pos] = '\0';
+ g_strstrip(line);
+ if ((tmp = strchr(line, '#')))
+ *tmp = '\0';
+ success = FALSE;
+
+#define IF_CMD(cmd) \
+ if (!success && (success = (strncmp(line, #cmd, strlen(#cmd)) == 0)))
+
+ IF_CMD(quit) {
+ g_main_loop_quit(main_loop);
+ return FALSE;
+ }
+
+ IF_CMD(sleep) {
+ unsigned int seconds;
+ if (sscanf(line, "%*s %d", &seconds) != 1)
+ DBG("sleep SECONDS");
+ else {
+ g_source_remove(u->gin_watch);
+ g_timeout_add_seconds(seconds, sleep_cb, u);
+ return FALSE;
+ }
+ }
+
+ IF_CMD(debug) {
+ char *what = NULL;
+ int enable;
+
+ if (sscanf(line, "%*s %as %d", &what, &enable) != 1)
+ DBG("debug [stream_read|stream_write] [0|1]");
+ if (strncmp(what, "stream_read", 12) == 0) {
+ u->debug_stream_read = enable;
+ } else if (strncmp(what, "stream_write", 13) == 0) {
+ u->debug_stream_write = enable;
+ } else {
+ DBG("debug [stream_read|stream_write] [0|1]");
+ }
+ }
+
+ IF_CMD(init_bt) {
+ DBG("%d", init_bt(u));
+ }
+
+ IF_CMD(init_profile) {
+ DBG("%d", init_profile(u));
+ }
+
+ IF_CMD(start_stream) {
+ DBG("%d", start_stream(u));
+ }
+
+ IF_CMD(stop_stream) {
+ DBG("%d", stop_stream(u));
+ }
+
+ IF_CMD(shutdown_bt) {
+ shutdown_bt(u);
+ }
+
+ IF_CMD(rate) {
+ if (sscanf(line, "%*s %d", &u->rate) != 1)
+ DBG("set with rate RATE");
+ DBG("rate %d", u->rate);
+ }
+
+ IF_CMD(bdaddr) {
+ char *address;
+
+ if (sscanf(line, "%*s %as", &address) != 1)
+ DBG("set with bdaddr BDADDR");
+
+ free(u->address);
+
+ u->address = address;
+ DBG("bdaddr %s", u->address);
+ }
+
+ IF_CMD(profile) {
+ char *profile = NULL;
+
+ if (sscanf(line, "%*s %as", &profile) != 1)
+ DBG("set with profile [hsp|a2dp]");
+ if (strncmp(profile, "hsp", 4) == 0) {
+ u->transport = BT_CAPABILITIES_TRANSPORT_SCO;
+ } else if (strncmp(profile, "a2dp", 5) == 0) {
+ u->transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+ } else {
+ DBG("set with profile [hsp|a2dp]");
+ }
+
+ free(profile);
+ DBG("profile %s", u->transport == BT_CAPABILITIES_TRANSPORT_SCO ?
+ "hsp" : "a2dp");
+ }
+
+ if (!success && strlen(line) != 0) {
+ DBG("%s, unknown command", line);
+ }
+
+ printf(">>> ");
+ fflush(stdout);
+ return TRUE;
+}
+
+
+static void show_usage(char* prgname)
+{
+ printf("%s: ipctest [--interactive] BDADDR\n", basename(prgname));
+}
+
+static void sig_term(int sig)
+{
+ g_main_loop_quit(main_loop);
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc < 2) {
+ show_usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ assert(main_loop = g_main_loop_new(NULL, FALSE));
+
+ if (strncmp("--interactive", argv[1], 14) == 0) {
+ if (argc < 3) {
+ show_usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ data.address = strdup(argv[2]);
+
+ signal(SIGTERM, sig_term);
+ signal(SIGINT, sig_term);
+
+ assert(data.gin = g_io_channel_unix_new(fileno(stdin)));
+
+ data.gin_watch = g_io_add_watch(data.gin,
+ G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, input_cb, &data);
+
+ printf(">>> ");
+ fflush(stdout);
+
+ g_main_loop_run(main_loop);
+
+ } else {
+ data.address = strdup(argv[1]);
+
+ assert(init_bt(&data) == 0);
+
+ assert(init_profile(&data) == 0);
+
+ assert(start_stream(&data) == 0);
+
+ g_main_loop_run(main_loop);
+
+ assert(stop_stream(&data) == 0);
+
+ shutdown_bt(&data);
+ }
+
+ g_main_loop_unref(main_loop);
+
+ printf("\nExiting\n");
+
+ exit(EXIT_SUCCESS);
+
+ return 0;
+}