blob: 89a53211dc031d58c202c0bb9f523e6cb587e05d [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2004-2006 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 <stdio.h>
#include <errno.h>
#include <malloc.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <alsa/asoundlib.h>
#include <alsa/pcm_external.h>
#include "sbc.h"
//#define DBG(fmt, arg...) printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg)
#define DBG(D...)
static void a2dp_init(void) __attribute__ ((constructor));
static void a2dp_exit(void) __attribute__ ((destructor));
typedef struct snd_pcm_a2dp {
snd_pcm_ioplug_t io;
int refcnt;
int timeout;
unsigned long state;
bdaddr_t src;
bdaddr_t dst;
int sk;
sbc_t sbc;
snd_pcm_sframes_t num;
unsigned char buf[1024];
unsigned int len;
unsigned int frame_bytes;
} snd_pcm_a2dp_t;
static void inline a2dp_get(snd_pcm_a2dp_t *a2dp)
{
a2dp->refcnt++;
a2dp->timeout = 0;
}
static void inline a2dp_put(snd_pcm_a2dp_t *a2dp)
{
a2dp->refcnt--;
if (a2dp->refcnt <= 0)
a2dp->timeout = 2;
}
static int a2dp_start(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
DBG("a2dp %p", a2dp);
a2dp->len = 0;
return 0;
}
static int a2dp_stop(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
DBG("a2dp %p", a2dp);
a2dp->len = 0;
return 0;
}
static snd_pcm_sframes_t a2dp_pointer(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
return a2dp->num;
}
static snd_pcm_sframes_t a2dp_transfer(snd_pcm_ioplug_t *io,
const snd_pcm_channel_area_t *areas,
snd_pcm_uframes_t offset, snd_pcm_uframes_t size)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
unsigned char *buf;
int len;
buf = (unsigned char *) areas->addr + (areas->first + areas->step * offset) / 8;
len = sbc_encode(&a2dp->sbc, buf, size * a2dp->frame_bytes);
if (len <= 0)
return len;
if (a2dp->len + a2dp->sbc.len > sizeof(a2dp->buf)) {
write(a2dp->sk, a2dp->buf, a2dp->len);
a2dp->len = 0;
}
memcpy(a2dp->buf + a2dp->len, a2dp->sbc.data, a2dp->sbc.len);
a2dp->len += a2dp->sbc.len;
if (a2dp->state == BT_CONNECTED)
a2dp->num += len / a2dp->frame_bytes;
return len / a2dp->frame_bytes;
}
static int a2dp_close(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
DBG("a2dp %p", a2dp);
a2dp->len = 0;
a2dp_put(a2dp);
return 0;
}
static int a2dp_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
unsigned int period_bytes;
DBG("a2dp %p", a2dp);
a2dp->frame_bytes = (snd_pcm_format_physical_width(io->format) * io->channels) / 8;
period_bytes = io->period_size * a2dp->frame_bytes;
DBG("format %s rate %d channels %d", snd_pcm_format_name(io->format),
io->rate, io->channels);
DBG("frame_bytes %d period_bytes %d period_size %ld buffer_size %ld",
a2dp->frame_bytes, period_bytes, io->period_size, io->buffer_size);
return 0;
}
static int a2dp_prepare(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
DBG("a2dp %p", a2dp);
a2dp->len = 0;
a2dp->num = 0;
a2dp->sbc.rate = io->rate;
a2dp->sbc.channels = io->channels;
return 0;
}
static int a2dp_drain(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
DBG("a2dp %p", a2dp);
a2dp->len = 0;
return 0;
}
static int a2dp_descriptors_count(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
if (a2dp->state == BT_CLOSED)
return 0;
return 1;
}
static int a2dp_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfds, unsigned int space)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
if (a2dp->state == BT_CLOSED)
return 0;
if (space < 1) {
SNDERR("Can't fill in descriptors");
return 0;
}
pfds[0].fd = a2dp->sk;
pfds[0].events = POLLOUT;
return 1;
}
static int a2dp_poll(snd_pcm_ioplug_t *io, struct pollfd *pfds,
unsigned int nfds, unsigned short *revents)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
*revents = pfds[0].revents;
if (a2dp->state == BT_CLOSED)
return 0;
if (pfds[0].revents & POLLHUP) {
a2dp->state = BT_CLOSED;
snd_pcm_ioplug_reinit_status(&a2dp->io);
}
return 0;
}
static snd_pcm_ioplug_callback_t a2dp_callback = {
.start = a2dp_start,
.stop = a2dp_stop,
.pointer = a2dp_pointer,
.transfer = a2dp_transfer,
.close = a2dp_close,
.hw_params = a2dp_params,
.prepare = a2dp_prepare,
.drain = a2dp_drain,
.poll_descriptors_count = a2dp_descriptors_count,
.poll_descriptors = a2dp_descriptors,
.poll_revents = a2dp_poll,
};
static int a2dp_connect(snd_pcm_a2dp_t *a2dp)
{
struct sockaddr_rc addr;
socklen_t len;
int sk;
DBG("a2dp %p", a2dp);
sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
if (sk < 0)
return -errno;
memset(&addr, 0, sizeof(addr));
addr.rc_family = AF_BLUETOOTH;
bacpy(&addr.rc_bdaddr, &a2dp->src);
if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
close(sk);
return -errno;
}
memset(&addr, 0, sizeof(addr));
addr.rc_family = AF_BLUETOOTH;
bacpy(&addr.rc_bdaddr, &a2dp->dst);
addr.rc_channel = 1;
if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
close(sk);
return -errno;
}
memset(&addr, 0, sizeof(addr));
len = sizeof(addr);
if (getsockname(sk, (struct sockaddr *) &addr, &len) < 0) {
close(sk);
return -errno;
}
bacpy(&a2dp->src, &addr.rc_bdaddr);
fcntl(sk, F_SETFL, fcntl(sk, F_GETFL) | O_NONBLOCK);
a2dp->sk = sk;
return 0;
}
static int a2dp_constraint(snd_pcm_a2dp_t *a2dp)
{
snd_pcm_ioplug_t *io = &a2dp->io;
snd_pcm_access_t access_list[] = {
SND_PCM_ACCESS_RW_INTERLEAVED,
SND_PCM_ACCESS_MMAP_INTERLEAVED,
};
unsigned int format[2], channel[2], rate[2];
int err;
DBG("a2dp %p", a2dp);
err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, 2, access_list);
if (err < 0)
return err;
format[0] = SND_PCM_FORMAT_S16_LE;
err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, 1, format);
if (err < 0)
return err;
channel[0] = 1;
channel[1] = 2;
err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_CHANNELS, 2, channel);
if (err < 0)
return err;
rate[0] = 44100;
rate[1] = 48000;
err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_RATE, 2, rate);
if (err < 0)
return err;
err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, 8192, 8192);
if (err < 0)
return err;
err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, 2, 2);
if (err < 0)
return err;
return 0;
}
#define MAX_CONNECTIONS 10
static snd_pcm_a2dp_t *connections[MAX_CONNECTIONS];
static snd_timer_t *timer = NULL;
static volatile sig_atomic_t __locked = 0;
static inline void a2dp_lock(void)
{
while (__locked)
usleep(100);
__locked = 1;
}
static inline void a2dp_unlock(void)
{
__locked = 0;
}
static inline snd_pcm_a2dp_t *a2dp_alloc(void)
{
snd_pcm_a2dp_t *a2dp;
a2dp = malloc(sizeof(*a2dp));
if (!a2dp)
return NULL;
memset(a2dp, 0, sizeof(*a2dp));
a2dp->refcnt = 1;
a2dp->state = BT_OPEN;
sbc_init(&a2dp->sbc, SBC_NULL);
return a2dp;
}
static inline void a2dp_free(snd_pcm_a2dp_t *a2dp)
{
if (a2dp->sk > fileno(stderr))
close(a2dp->sk);
sbc_finish(&a2dp->sbc);
free(a2dp);
}
static void a2dp_timer(snd_async_handler_t *async)
{
snd_timer_t *handle = snd_async_handler_get_timer(async);
snd_timer_read_t tr;
int i, ticks = 0;
while (snd_timer_read(handle, &tr, sizeof(tr)) == sizeof(tr))
ticks += tr.ticks;
a2dp_lock();
for (i = 0; i < MAX_CONNECTIONS; i++) {
snd_pcm_a2dp_t *a2dp = connections[i];
if (a2dp && a2dp->refcnt <= 0) {
a2dp->timeout = ((a2dp->timeout * 1000) - ticks) / 1000;
if (a2dp->timeout <= 0) {
connections[i] = NULL;
a2dp_free(a2dp);
}
}
}
a2dp_unlock();
}
static void a2dp_init(void)
{
snd_async_handler_t *async;
snd_timer_info_t *info;
snd_timer_params_t *params;
long resolution;
char timername[64];
int err, i;
a2dp_lock();
for (i = 0; i < MAX_CONNECTIONS; i++)
connections[i] = NULL;
a2dp_unlock();
snd_timer_info_alloca(&info);
snd_timer_params_alloca(&params);
sprintf(timername, "hw:CLASS=%i,SCLASS=%i,CARD=%i,DEV=%i,SUBDEV=%i",
SND_TIMER_CLASS_GLOBAL, SND_TIMER_CLASS_NONE, 0,
SND_TIMER_GLOBAL_SYSTEM, 0);
err = snd_timer_open(&timer, timername, SND_TIMER_OPEN_NONBLOCK);
if (err < 0) {
SNDERR("Can't open global timer");
return;
}
err = snd_timer_info(timer, info);
if (err < 0) {
SNDERR("Can't get global timer info");
return;
}
snd_timer_params_set_auto_start(params, 1);
resolution = snd_timer_info_get_resolution(info);
snd_timer_params_set_ticks(params, 1000000000 / resolution);
if (snd_timer_params_get_ticks(params) < 1)
snd_timer_params_set_ticks(params, 1);
err = snd_timer_params(timer, params);
if (err < 0) {
SNDERR("Can't set global timer parameters");
snd_timer_close(timer);
return;
}
err = snd_async_add_timer_handler(&async, timer, a2dp_timer, NULL);
if (err < 0) {
SNDERR("Can't create global async callback");
snd_timer_close(timer);
return;
}
err = snd_timer_start(timer);
}
static void a2dp_exit(void)
{
int err, i;
err = snd_timer_stop(timer);
err = snd_timer_close(timer);
a2dp_lock();
for (i = 0; i < MAX_CONNECTIONS; i++) {
snd_pcm_a2dp_t *a2dp = connections[i];
if (a2dp) {
connections[i] = NULL;
a2dp_free(a2dp);
}
}
a2dp_unlock();
}
SND_PCM_PLUGIN_DEFINE_FUNC(a2dp)
{
snd_pcm_a2dp_t *a2dp = NULL;
snd_config_iterator_t i, next;
bdaddr_t src, dst;
int err, n, pos = -1;
DBG("name %s mode %d", name, mode);
bacpy(&src, BDADDR_ANY);
bacpy(&dst, BDADDR_ANY);
snd_config_for_each(i, next, conf) {
snd_config_t *n = snd_config_iterator_entry(i);
const char *id, *addr;
if (snd_config_get_id(n, &id) < 0)
continue;
if (!strcmp(id, "comment") || !strcmp(id, "type"))
continue;
if (!strcmp(id, "bdaddr") || !strcmp(id, "dst")) {
if (snd_config_get_string(n, &addr) < 0) {
SNDERR("Invalid type for %s", id);
return -EINVAL;
}
str2ba(addr, &dst);
continue;
}
if (!strcmp(id, "local") || !strcmp(id, "src")) {
if (snd_config_get_string(n, &addr) < 0) {
SNDERR("Invalid type for %s", id);
return -EINVAL;
}
str2ba(addr, &src);
continue;
}
SNDERR("Unknown field %s", id);
return -EINVAL;
}
a2dp_lock();
for (n = 0; n < MAX_CONNECTIONS; n++) {
if (connections[n]) {
if (!bacmp(&connections[n]->dst, &dst) &&
(!bacmp(&connections[n]->src, &src) ||
!bacmp(&src, BDADDR_ANY))) {
a2dp = connections[n];
a2dp_get(a2dp);
break;
}
} else if (pos < 0)
pos = n;
}
if (!a2dp) {
if (pos < 0) {
SNDERR("Too many connections");
return -ENOMEM;
}
a2dp = a2dp_alloc();
if (!a2dp) {
SNDERR("Can't allocate");
return -ENOMEM;
}
connections[pos] = a2dp;
a2dp->state = BT_CONNECT;
bacpy(&a2dp->src, &src);
bacpy(&a2dp->dst, &dst);
}
a2dp_unlock();
if (a2dp->state != BT_CONNECTED) {
err = a2dp_connect(a2dp);
if (err < 0) {
SNDERR("Can't connect");
goto error;
}
a2dp->state = BT_CONNECTED;
}
a2dp->io.version = SND_PCM_IOPLUG_VERSION;
a2dp->io.name = "Bluetooth Advanced Audio Distribution";
a2dp->io.mmap_rw = 0;
a2dp->io.callback = &a2dp_callback;
a2dp->io.private_data = a2dp;
err = snd_pcm_ioplug_create(&a2dp->io, name, stream, mode);
if (err < 0)
goto error;
err = a2dp_constraint(a2dp);
if (err < 0) {
snd_pcm_ioplug_delete(&a2dp->io);
goto error;
}
*pcmp = a2dp->io.pcm;
return 0;
error:
a2dp_put(a2dp);
return err;
}
SND_PCM_PLUGIN_SYMBOL(a2dp);