diff options
author | Marcel Holtmann <marcel@holtmann.org> | 2005-02-10 21:05:33 +0000 |
---|---|---|
committer | Marcel Holtmann <marcel@holtmann.org> | 2005-02-10 21:05:33 +0000 |
commit | 891e3e2d7cdcb4cd301efff8c20abbf4d7cd3565 (patch) | |
tree | 436e6aea9c7b1bac8bf7ae39ad3520923877add2 | |
parent | 490afac7bc8ca6b283499fe5b026a14a7f7df0cb (diff) |
Add prototype version of the A2DP plugin
-rw-r--r-- | alsa/pcm_a2dp.c | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/alsa/pcm_a2dp.c b/alsa/pcm_a2dp.c index e69de29b..90ee7107 100644 --- a/alsa/pcm_a2dp.c +++ b/alsa/pcm_a2dp.c @@ -0,0 +1,358 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <malloc.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...) + +typedef struct snd_pcm_a2dp { + snd_pcm_ioplug_t io; + 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 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; + + size *= a2dp->frame_bytes; + + len = sbc_encode(&a2dp->sbc, buf, size); + if (len <= 0) + return len; + + memcpy(a2dp->buf + a2dp->len, a2dp->sbc.data, a2dp->sbc.len); + a2dp->len += a2dp->sbc.len; + + if (a2dp->len > 700) { + write(a2dp->sk, a2dp->buf, a2dp->len); + a2dp->len = 0; + } + + 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; + + if (a2dp->sk >= 0) { + shutdown(a2dp->sk, SHUT_RDWR); + sleep(1); + close(a2dp->sk); + } + + sbc_finish(&a2dp->sbc); + + free(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_byts %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->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 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, +}; + +static int a2dp_connect(snd_pcm_a2dp_t *a2dp) +{ + struct sockaddr_rc addr; + 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; + } + + 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, 2048, 2048); + 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; + + sbc_init(&a2dp->sbc, SBC_NULL); + + return 0; +} + +SND_PCM_PLUGIN_DEFINE_FUNC(a2dp) +{ + snd_pcm_a2dp_t *a2dp; + snd_config_iterator_t i, next; + bdaddr_t src, dst; + int err; + + 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, "src") == 0) { + 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 = malloc(sizeof(*a2dp)); + if (!a2dp) { + SNDERR("Cannot allocate"); + return -ENOMEM; + } + + memset(a2dp, 0, sizeof(*a2dp)); + + bacpy(&a2dp->src, &src); + bacpy(&a2dp->dst, &dst); + + err = a2dp_connect(a2dp); + if (err < 0) { + SNDERR("Cannot connect"); + goto error; + } + + a2dp->io.name = "Bluetooth Advanced Audio Distribution"; + a2dp->io.poll_fd = a2dp->sk; + a2dp->io.poll_events = POLLOUT | POLLHUP; + 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); + return err; + } + + *pcmp = a2dp->io.pcm; + return 0; + +error: + free(a2dp); + return err; +} + +SND_PCM_PLUGIN_SYMBOL(a2dp); |