summaryrefslogtreecommitdiffstats
path: root/alsa
diff options
context:
space:
mode:
authorMarcel Holtmann <marcel@holtmann.org>2005-02-10 21:05:33 +0000
committerMarcel Holtmann <marcel@holtmann.org>2005-02-10 21:05:33 +0000
commit891e3e2d7cdcb4cd301efff8c20abbf4d7cd3565 (patch)
tree436e6aea9c7b1bac8bf7ae39ad3520923877add2 /alsa
parent490afac7bc8ca6b283499fe5b026a14a7f7df0cb (diff)
Add prototype version of the A2DP plugin
Diffstat (limited to 'alsa')
-rw-r--r--alsa/pcm_a2dp.c358
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);