diff options
author | Adrian Bunk <adrian.bunk@movial.com> | 2011-06-03 09:17:04 +0000 |
---|---|---|
committer | Adrian Bunk <adrian.bunk@movial.com> | 2011-06-03 09:17:04 +0000 |
commit | 799757ccf1d03c33c75bc597cd5ef77741dcb6a7 (patch) | |
tree | a8c3be85c730de28b012586591b76301033d3d21 /test/ipctest.c |
Imported upstream 4.91upstream-4.91upstreampackaging
Diffstat (limited to 'test/ipctest.c')
-rw-r--r-- | test/ipctest.c | 1129 |
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; +} |