/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2009 Nokia Corporation * Copyright (C) 2004-2009 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"); if (u->address) 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]"); } if (profile) 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; }