aboutsummaryrefslogtreecommitdiff
path: root/lib/virtual_oss/bt/bt.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/virtual_oss/bt/bt.c')
-rw-r--r--lib/virtual_oss/bt/bt.c1061
1 files changed, 1061 insertions, 0 deletions
diff --git a/lib/virtual_oss/bt/bt.c b/lib/virtual_oss/bt/bt.c
new file mode 100644
index 000000000000..57f000b067d5
--- /dev/null
+++ b/lib/virtual_oss/bt/bt.c
@@ -0,0 +1,1061 @@
+/*-
+ * Copyright (c) 2015-2019 Hans Petter Selasky
+ * Copyright (c) 2015 Nathanial Sloss <nathanialsloss@yahoo.com.au>
+ * Copyright (c) 2006 Itronix Inc
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/queue.h>
+#include <sys/filio.h>
+#include <sys/soundcard.h>
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <err.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+
+#include "backend.h"
+#include "int.h"
+
+#include "avdtp_signal.h"
+#include "bt.h"
+
+#define DPRINTF(...) printf("backend_bt: " __VA_ARGS__)
+
+struct l2cap_info {
+ bdaddr_t laddr;
+ bdaddr_t raddr;
+};
+
+static struct bt_config bt_play_cfg;
+static struct bt_config bt_rec_cfg;
+
+int
+bt_receive(struct bt_config *cfg, void *ptr, int len, int use_delay)
+{
+ struct sbc_header *phdr = (struct sbc_header *)cfg->mtu_data;
+ struct sbc_encode *sbc = cfg->handle.sbc_enc;
+ uint8_t *tmp = ptr;
+ int old_len = len;
+ int delta;
+ int err;
+
+ /* wait for service interval, if any */
+ if (use_delay)
+ virtual_oss_wait();
+
+ switch (cfg->blocks) {
+ case BLOCKS_4:
+ sbc->blocks = 4;
+ break;
+ case BLOCKS_8:
+ sbc->blocks = 8;
+ break;
+ case BLOCKS_12:
+ sbc->blocks = 12;
+ break;
+ default:
+ sbc->blocks = 16;
+ break;
+ }
+
+ switch (cfg->bands) {
+ case BANDS_4:
+ sbc->bands = 4;
+ break;
+ default:
+ sbc->bands = 8;
+ break;
+ }
+
+ if (cfg->chmode != MODE_MONO) {
+ sbc->channels = 2;
+ } else {
+ sbc->channels = 1;
+ }
+
+ while (1) {
+ delta = len & ~1;
+ if (delta > (int)(2 * sbc->rem_len))
+ delta = (2 * sbc->rem_len);
+
+ /* copy out samples, if any */
+ memcpy(tmp, (char *)sbc->music_data + sbc->rem_off, delta);
+ tmp += delta;
+ len -= delta;
+ sbc->rem_off += delta / 2;
+ sbc->rem_len -= delta / 2;
+ if (len == 0)
+ break;
+
+ if (sbc->rem_len == 0 &&
+ sbc->rem_data_frames != 0) {
+ err = sbc_decode_frame(cfg, sbc->rem_data_len * 8);
+ sbc->rem_data_frames--;
+ sbc->rem_data_ptr += err;
+ sbc->rem_data_len -= err;
+ continue;
+ }
+ /* TODO: Support fragmented SBC frames */
+ err = read(cfg->fd, cfg->mtu_data, cfg->mtu);
+
+ if (err == 0) {
+ break;
+ } else if (err < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ break;
+ else
+ return (-1); /* disconnected */
+ }
+
+ /* verify RTP header */
+ if (err < (int)sizeof(*phdr) || phdr->id != 0x80)
+ continue;
+
+ sbc->rem_data_frames = phdr->numFrames;
+ sbc->rem_data_ptr = (uint8_t *)(phdr + 1);
+ sbc->rem_data_len = err - sizeof(*phdr);
+ }
+ return (old_len - len);
+}
+
+static int
+bt_set_format(int *format)
+{
+ int value;
+
+ value = *format & AFMT_S16_NE;
+ if (value != 0) {
+ *format = value;
+ return (0);
+ }
+ return (-1);
+}
+
+static void
+bt_close(struct voss_backend *pbe)
+{
+ struct bt_config *cfg = pbe->arg;
+
+ if (cfg->hc > 0) {
+ avdtpAbort(cfg->hc, cfg->sep);
+ avdtpClose(cfg->hc, cfg->sep);
+ close(cfg->hc);
+ cfg->hc = -1;
+ }
+ if (cfg->fd > 0) {
+ close(cfg->fd);
+ cfg->fd = -1;
+ }
+}
+
+static void
+bt_play_close(struct voss_backend *pbe)
+{
+ struct bt_config *cfg = pbe->arg;
+
+ switch (cfg->codec) {
+ case CODEC_SBC:
+ if (cfg->handle.sbc_enc == NULL)
+ break;
+ free(cfg->handle.sbc_enc);
+ cfg->handle.sbc_enc = NULL;
+ break;
+#ifdef HAVE_LIBAV
+ case CODEC_AAC:
+ if (cfg->handle.av.context == NULL)
+ break;
+ av_free(cfg->rem_in_data);
+ av_frame_free(&cfg->handle.av.frame);
+ avcodec_close(cfg->handle.av.context);
+ avformat_free_context(cfg->handle.av.format);
+ cfg->handle.av.context = NULL;
+ break;
+#endif
+ default:
+ break;
+ }
+ return (bt_close(pbe));
+}
+
+static void
+bt_rec_close(struct voss_backend *pbe)
+{
+ struct bt_config *cfg = pbe->arg;
+
+ switch (cfg->codec) {
+ case CODEC_SBC:
+ break;
+#ifdef HAVE_LIBAV
+ case CODEC_AAC:
+ break;
+#endif
+
+ default:
+ break;
+ }
+ return (bt_close(pbe));
+}
+
+static const uint32_t bt_attrs[] = {
+ SDP_ATTR_RANGE(SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST),
+};
+
+#define BT_NUM_VALUES 32
+#define BT_BUF_SIZE 32
+
+static int
+bt_find_psm(const uint8_t *start, const uint8_t *end)
+{
+ uint32_t type;
+ uint32_t len;
+ int protover = 0;
+ int psm = -1;
+
+ if ((end - start) < 2)
+ return (-1);
+
+ SDP_GET8(type, start);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(len, start);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(len, start);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(len, start);
+ break;
+
+ default:
+ return (-1);
+ }
+
+ while (start < end) {
+ SDP_GET8(type, start);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(len, start);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(len, start);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(len, start);
+ break;
+
+ default:
+ return (-1);
+ }
+ /* check range */
+ if (len > (uint32_t)(end - start))
+ break;
+
+ if (len >= 6) {
+ const uint8_t *ptr = start;
+
+ SDP_GET8(type, ptr);
+ if (type == SDP_DATA_UUID16) {
+ uint16_t temp;
+
+ SDP_GET16(temp, ptr);
+ switch (temp) {
+ case SDP_UUID_PROTOCOL_L2CAP:
+ SDP_GET8(type, ptr);
+ SDP_GET16(psm, ptr);
+ break;
+ case SDP_UUID_PROTOCOL_AVDTP:
+ SDP_GET8(type, ptr);
+ SDP_GET16(protover, ptr);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ start += len;
+
+ if (protover >= 0x0100 && psm > -1)
+ return (htole16(psm));
+ }
+ return (-1);
+}
+
+static int
+bt_query(struct l2cap_info *info, uint16_t service_class)
+{
+ sdp_attr_t values[BT_NUM_VALUES];
+ uint8_t buffer[BT_NUM_VALUES][BT_BUF_SIZE];
+ void *ss;
+ int psm = -1;
+ int n;
+
+ memset(buffer, 0, sizeof(buffer));
+ memset(values, 0, sizeof(values));
+
+ ss = sdp_open(&info->laddr, &info->raddr);
+ if (ss == NULL || sdp_error(ss) != 0) {
+ DPRINTF("Could not open SDP\n");
+ sdp_close(ss);
+ return (psm);
+ }
+ /* Initialize attribute values array */
+ for (n = 0; n != BT_NUM_VALUES; n++) {
+ values[n].flags = SDP_ATTR_INVALID;
+ values[n].vlen = BT_BUF_SIZE;
+ values[n].value = buffer[n];
+ }
+
+ /* Do SDP Service Search Attribute Request */
+ n = sdp_search(ss, 1, &service_class, 1, bt_attrs, BT_NUM_VALUES, values);
+ if (n != 0) {
+ DPRINTF("SDP search failed\n");
+ goto done;
+ }
+ /* Print attributes values */
+ for (n = 0; n != BT_NUM_VALUES; n++) {
+ if (values[n].flags != SDP_ATTR_OK)
+ break;
+ if (values[n].attr != SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST)
+ continue;
+ psm = bt_find_psm(values[n].value, values[n].value + values[n].vlen);
+ if (psm > -1)
+ break;
+ }
+done:
+ sdp_close(ss);
+ return (psm);
+}
+
+static int
+bt_open(struct voss_backend *pbe __unused, const char *devname, int samplerate,
+ int bufsize __unused, int *pchannels, int *pformat, struct bt_config *cfg,
+ int service_class, int isSink)
+{
+ struct sockaddr_l2cap addr;
+ struct l2cap_info info;
+ socklen_t mtusize = sizeof(uint16_t);
+ int tmpbitpool;
+ int l2cap_psm;
+ int temp;
+
+ memset(&info, 0, sizeof(info));
+
+ if (strstr(devname, "/dev/bluetooth/") != devname) {
+ printf("Invalid device name '%s'", devname);
+ goto error;
+ }
+ /* skip prefix */
+ devname += sizeof("/dev/bluetooth/") - 1;
+
+ if (!bt_aton(devname, &info.raddr)) {
+ struct hostent *he = NULL;
+
+ if ((he = bt_gethostbyname(devname)) == NULL) {
+ DPRINTF("Could not get host by name\n");
+ goto error;
+ }
+ bdaddr_copy(&info.raddr, (bdaddr_t *)he->h_addr);
+ }
+ switch (samplerate) {
+ case 8000:
+ cfg->freq = FREQ_UNDEFINED;
+ cfg->aacMode1 = 0x80;
+ cfg->aacMode2 = 0x0C;
+ break;
+ case 11025:
+ cfg->freq = FREQ_UNDEFINED;
+ cfg->aacMode1 = 0x40;
+ cfg->aacMode2 = 0x0C;
+ break;
+ case 12000:
+ cfg->freq = FREQ_UNDEFINED;
+ cfg->aacMode1 = 0x20;
+ cfg->aacMode2 = 0x0C;
+ break;
+ case 16000:
+ cfg->freq = FREQ_16K;
+ cfg->aacMode1 = 0x10;
+ cfg->aacMode2 = 0x0C;
+ break;
+ case 22050:
+ cfg->freq = FREQ_UNDEFINED;
+ cfg->aacMode1 = 0x08;
+ cfg->aacMode2 = 0x0C;
+ break;
+ case 24000:
+ cfg->freq = FREQ_UNDEFINED;
+ cfg->aacMode1 = 0x04;
+ cfg->aacMode2 = 0x0C;
+ break;
+ case 32000:
+ cfg->freq = FREQ_32K;
+ cfg->aacMode1 = 0x02;
+ cfg->aacMode2 = 0x0C;
+ break;
+ case 44100:
+ cfg->freq = FREQ_44_1K;
+ cfg->aacMode1 = 0x01;
+ cfg->aacMode2 = 0x0C;
+ break;
+ case 48000:
+ cfg->freq = FREQ_48K;
+ cfg->aacMode1 = 0;
+ cfg->aacMode2 = 0x8C;
+ break;
+ case 64000:
+ cfg->freq = FREQ_UNDEFINED;
+ cfg->aacMode1 = 0;
+ cfg->aacMode2 = 0x4C;
+ break;
+ case 88200:
+ cfg->freq = FREQ_UNDEFINED;
+ cfg->aacMode1 = 0;
+ cfg->aacMode2 = 0x2C;
+ break;
+ case 96000:
+ cfg->freq = FREQ_UNDEFINED;
+ cfg->aacMode1 = 0;
+ cfg->aacMode2 = 0x1C;
+ break;
+ default:
+ DPRINTF("Invalid samplerate %d", samplerate);
+ goto error;
+ }
+ cfg->bands = BANDS_8;
+ cfg->bitpool = 0;
+
+ switch (*pchannels) {
+ case 1:
+ cfg->aacMode2 &= 0xF8;
+ cfg->chmode = MODE_MONO;
+ break;
+ default:
+ cfg->aacMode2 &= 0xF4;
+ cfg->chmode = MODE_STEREO;
+ break;
+ }
+
+ cfg->allocm = ALLOC_LOUDNESS;
+
+ if (cfg->chmode == MODE_MONO || cfg->chmode == MODE_DUAL)
+ tmpbitpool = 16;
+ else
+ tmpbitpool = 32;
+
+ if (cfg->bands == BANDS_8)
+ tmpbitpool *= 8;
+ else
+ tmpbitpool *= 4;
+
+ if (tmpbitpool > DEFAULT_MAXBPOOL)
+ tmpbitpool = DEFAULT_MAXBPOOL;
+
+ cfg->bitpool = tmpbitpool;
+
+ if (bt_set_format(pformat)) {
+ DPRINTF("Unsupported sample format\n");
+ goto error;
+ }
+ l2cap_psm = bt_query(&info, service_class);
+ DPRINTF("PSM=0x%02x\n", l2cap_psm);
+ if (l2cap_psm < 0) {
+ DPRINTF("PSM not found\n");
+ goto error;
+ }
+ cfg->hc = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
+ if (cfg->hc < 0) {
+ DPRINTF("Could not create BT socket\n");
+ goto error;
+ }
+ memset(&addr, 0, sizeof(addr));
+ addr.l2cap_len = sizeof(addr);
+ addr.l2cap_family = AF_BLUETOOTH;
+ bdaddr_copy(&addr.l2cap_bdaddr, &info.laddr);
+
+ if (bind(cfg->hc, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ DPRINTF("Could not bind to HC\n");
+ goto error;
+ }
+ bdaddr_copy(&addr.l2cap_bdaddr, &info.raddr);
+ addr.l2cap_psm = l2cap_psm;
+ if (connect(cfg->hc, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ DPRINTF("Could not connect to HC: %d\n", errno);
+ goto error;
+ }
+ if (avdtpDiscoverAndConfig(cfg, isSink)) {
+ DPRINTF("DISCOVER FAILED\n");
+ goto error;
+ }
+ if (avdtpOpen(cfg->hc, cfg->sep)) {
+ DPRINTF("OPEN FAILED\n");
+ goto error;
+ }
+ cfg->fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
+ if (cfg->fd < 0) {
+ DPRINTF("Could not create BT socket\n");
+ goto error;
+ }
+ memset(&addr, 0, sizeof(addr));
+
+ addr.l2cap_len = sizeof(addr);
+ addr.l2cap_family = AF_BLUETOOTH;
+ bdaddr_copy(&addr.l2cap_bdaddr, &info.laddr);
+
+ if (bind(cfg->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ DPRINTF("Could not bind\n");
+ goto error;
+ }
+ bdaddr_copy(&addr.l2cap_bdaddr, &info.raddr);
+ addr.l2cap_psm = l2cap_psm;
+ if (connect(cfg->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ DPRINTF("Could not connect: %d\n", errno);
+ goto error;
+ }
+ if (isSink) {
+ if (getsockopt(cfg->fd, SOL_L2CAP, SO_L2CAP_OMTU, &cfg->mtu, &mtusize) == -1) {
+ DPRINTF("Could not get MTU\n");
+ goto error;
+ }
+ temp = cfg->mtu * 16;
+ if (setsockopt(cfg->fd, SOL_SOCKET, SO_SNDBUF, &temp, sizeof(temp)) == -1) {
+ DPRINTF("Could not set send buffer size\n");
+ goto error;
+ }
+ temp = cfg->mtu;
+ if (setsockopt(cfg->fd, SOL_SOCKET, SO_SNDLOWAT, &temp, sizeof(temp)) == -1) {
+ DPRINTF("Could not set low water mark\n");
+ goto error;
+ }
+ } else {
+ if (getsockopt(cfg->fd, SOL_L2CAP, SO_L2CAP_IMTU, &cfg->mtu, &mtusize) == -1) {
+ DPRINTF("Could not get MTU\n");
+ goto error;
+ }
+ temp = cfg->mtu * 16;
+ if (setsockopt(cfg->fd, SOL_SOCKET, SO_RCVBUF, &temp, sizeof(temp)) == -1) {
+ DPRINTF("Could not set receive buffer size\n");
+ goto error;
+ }
+ temp = 1;
+ if (setsockopt(cfg->fd, SOL_SOCKET, SO_RCVLOWAT, &temp, sizeof(temp)) == -1) {
+ DPRINTF("Could not set low water mark\n");
+ goto error;
+ }
+ temp = 1;
+ if (ioctl(cfg->fd, FIONBIO, &temp) == -1) {
+ DPRINTF("Could not set non-blocking I/O for receive direction\n");
+ goto error;
+ }
+ }
+
+ if (avdtpStart(cfg->hc, cfg->sep)) {
+ DPRINTF("START FAILED\n");
+ goto error;
+ }
+ switch (cfg->chmode) {
+ case MODE_MONO:
+ *pchannels = 1;
+ break;
+ default:
+ *pchannels = 2;
+ break;
+ }
+ return (0);
+
+error:
+ if (cfg->hc > 0) {
+ close(cfg->hc);
+ cfg->hc = -1;
+ }
+ if (cfg->fd > 0) {
+ close(cfg->fd);
+ cfg->fd = -1;
+ }
+ return (-1);
+}
+
+static void
+bt_init_cfg(struct bt_config *cfg)
+{
+ memset(cfg, 0, sizeof(*cfg));
+}
+
+static int
+bt_rec_open(struct voss_backend *pbe, const char *devname, int samplerate,
+ int bufsize, int *pchannels, int *pformat)
+{
+ struct bt_config *cfg = pbe->arg;
+ int retval;
+
+ bt_init_cfg(cfg);
+
+ retval = bt_open(pbe, devname, samplerate, bufsize, pchannels, pformat,
+ cfg, SDP_SERVICE_CLASS_AUDIO_SOURCE, 0);
+ if (retval != 0)
+ return (retval);
+ return (0);
+}
+
+static int
+bt_play_open(struct voss_backend *pbe, const char *devname, int samplerate,
+ int bufsize, int *pchannels, int *pformat)
+{
+ struct bt_config *cfg = pbe->arg;
+ int retval;
+
+ bt_init_cfg(cfg);
+
+ retval = bt_open(pbe, devname, samplerate, bufsize, pchannels, pformat,
+ cfg, SDP_SERVICE_CLASS_AUDIO_SINK, 1);
+ if (retval != 0)
+ return (retval);
+
+ /* setup codec */
+ switch (cfg->codec) {
+ case CODEC_SBC:
+ cfg->handle.sbc_enc =
+ malloc(sizeof(*cfg->handle.sbc_enc));
+ if (cfg->handle.sbc_enc == NULL)
+ return (-1);
+ memset(cfg->handle.sbc_enc, 0, sizeof(*cfg->handle.sbc_enc));
+ break;
+#ifdef HAVE_LIBAV
+ case CODEC_AAC:
+ cfg->handle.av.codec = __DECONST(AVCodec *,
+ avcodec_find_encoder_by_name("aac"));
+ if (cfg->handle.av.codec == NULL) {
+ DPRINTF("Codec AAC encoder not found\n");
+ goto av_error_0;
+ }
+ cfg->handle.av.format = avformat_alloc_context();
+ if (cfg->handle.av.format == NULL) {
+ DPRINTF("Could not allocate format context\n");
+ goto av_error_0;
+ }
+ cfg->handle.av.format->oformat =
+ av_guess_format("latm", NULL, NULL);
+ if (cfg->handle.av.format->oformat == NULL) {
+ DPRINTF("Could not guess output format\n");
+ goto av_error_1;
+ }
+ cfg->handle.av.stream = avformat_new_stream(
+ cfg->handle.av.format, cfg->handle.av.codec);
+
+ if (cfg->handle.av.stream == NULL) {
+ DPRINTF("Could not create new stream\n");
+ goto av_error_1;
+ }
+ cfg->handle.av.context = avcodec_alloc_context3(cfg->handle.av.codec);
+ if (cfg->handle.av.context == NULL) {
+ DPRINTF("Could not allocate audio context\n");
+ goto av_error_1;
+ }
+ /*avcodec_get_context_defaults3(cfg->handle.av.context,*/
+ /*cfg->handle.av.codec);*/
+
+ cfg->handle.av.context->bit_rate = 128000;
+ cfg->handle.av.context->sample_fmt = AV_SAMPLE_FMT_FLTP;
+ cfg->handle.av.context->sample_rate = samplerate;
+ switch (*pchannels) {
+ case 1:
+ cfg->handle.av.context->ch_layout = *(AVChannelLayout *)AV_CH_LAYOUT_MONO;
+ break;
+ default:
+ cfg->handle.av.context->ch_layout = *(AVChannelLayout *)AV_CH_LAYOUT_STEREO;
+ break;
+ }
+
+ cfg->handle.av.context->profile = FF_PROFILE_AAC_LOW;
+ if (1) {
+ AVDictionary *opts = NULL;
+
+ av_dict_set(&opts, "strict", "-2", 0);
+ av_dict_set_int(&opts, "latm", 1, 0);
+
+ if (avcodec_open2(cfg->handle.av.context,
+ cfg->handle.av.codec, &opts) < 0) {
+ av_dict_free(&opts);
+
+ DPRINTF("Could not open codec\n");
+ goto av_error_1;
+ }
+ av_dict_free(&opts);
+ }
+ cfg->handle.av.frame = av_frame_alloc();
+ if (cfg->handle.av.frame == NULL) {
+ DPRINTF("Could not allocate audio frame\n");
+ goto av_error_2;
+ }
+ cfg->handle.av.frame->nb_samples = cfg->handle.av.context->frame_size;
+ cfg->handle.av.frame->format = cfg->handle.av.context->sample_fmt;
+ cfg->handle.av.frame->ch_layout = cfg->handle.av.context->ch_layout;
+ cfg->rem_in_size = av_samples_get_buffer_size(NULL,
+ cfg->handle.av.context->ch_layout.nb_channels,
+ cfg->handle.av.context->frame_size,
+ cfg->handle.av.context->sample_fmt, 0);
+
+ cfg->rem_in_data = av_malloc(cfg->rem_in_size);
+ if (cfg->rem_in_data == NULL) {
+ DPRINTF("Could not allocate %u bytes sample buffer\n",
+ (unsigned)cfg->rem_in_size);
+ goto av_error_3;
+ }
+ retval = avcodec_fill_audio_frame(cfg->handle.av.frame,
+ cfg->handle.av.context->ch_layout.nb_channels,
+ cfg->handle.av.context->sample_fmt,
+ cfg->rem_in_data, cfg->rem_in_size, 0);
+ if (retval < 0) {
+ DPRINTF("Could not setup audio frame\n");
+ goto av_error_4;
+ }
+ break;
+av_error_4:
+ av_free(cfg->rem_in_data);
+av_error_3:
+ av_frame_free(&cfg->handle.av.frame);
+av_error_2:
+ avcodec_close(cfg->handle.av.context);
+av_error_1:
+ avformat_free_context(cfg->handle.av.format);
+ cfg->handle.av.context = NULL;
+av_error_0:
+ bt_close(pbe);
+ return (-1);
+#endif
+ default:
+ bt_close(pbe);
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+bt_rec_transfer(struct voss_backend *pbe, void *ptr, int len)
+{
+ return (bt_receive(pbe->arg, ptr, len, 1));
+}
+
+static int
+bt_play_sbc_transfer(struct voss_backend *pbe, void *ptr, int len)
+{
+ struct bt_config *cfg = pbe->arg;
+ struct sbc_encode *sbc = cfg->handle.sbc_enc;
+ int rem_size = 1;
+ int old_len = len;
+ int err = 0;
+
+ switch (cfg->blocks) {
+ case BLOCKS_4:
+ sbc->blocks = 4;
+ rem_size *= 4;
+ break;
+ case BLOCKS_8:
+ sbc->blocks = 8;
+ rem_size *= 8;
+ break;
+ case BLOCKS_12:
+ sbc->blocks = 12;
+ rem_size *= 12;
+ break;
+ default:
+ sbc->blocks = 16;
+ rem_size *= 16;
+ break;
+ }
+
+ switch (cfg->bands) {
+ case BANDS_4:
+ rem_size *= 4;
+ sbc->bands = 4;
+ break;
+ default:
+ rem_size *= 8;
+ sbc->bands = 8;
+ break;
+ }
+
+ /* store number of samples per frame */
+ sbc->framesamples = rem_size;
+
+ if (cfg->chmode != MODE_MONO) {
+ rem_size *= 2;
+ sbc->channels = 2;
+ } else {
+ sbc->channels = 1;
+ }
+
+ rem_size *= 2; /* 16-bit samples */
+
+ while (len > 0) {
+ int delta = len;
+
+ if (delta > (int)(rem_size - sbc->rem_len))
+ delta = (int)(rem_size - sbc->rem_len);
+
+ /* copy in samples */
+ memcpy((char *)sbc->music_data + sbc->rem_len, ptr, delta);
+
+ ptr = (char *)ptr + delta;
+ len -= delta;
+ sbc->rem_len += delta;
+
+ /* check if buffer is full */
+ if ((int)sbc->rem_len == rem_size) {
+ struct sbc_header *phdr = (struct sbc_header *)cfg->mtu_data;
+ uint32_t pkt_len;
+ uint32_t rem;
+
+ if (cfg->chmode == MODE_MONO)
+ sbc->channels = 1;
+ else
+ sbc->channels = 2;
+
+ pkt_len = sbc_encode_frame(cfg);
+
+ retry:
+ if (cfg->mtu_offset == 0) {
+ phdr->id = 0x80; /* RTP v2 */
+ phdr->id2 = 0x60; /* payload type 96. */
+ phdr->seqnumMSB = (uint8_t)(cfg->mtu_seqnumber >> 8);
+ phdr->seqnumLSB = (uint8_t)(cfg->mtu_seqnumber);
+ phdr->ts3 = (uint8_t)(cfg->mtu_timestamp >> 24);
+ phdr->ts2 = (uint8_t)(cfg->mtu_timestamp >> 16);
+ phdr->ts1 = (uint8_t)(cfg->mtu_timestamp >> 8);
+ phdr->ts0 = (uint8_t)(cfg->mtu_timestamp);
+ phdr->reserved0 = 0x01;
+ phdr->numFrames = 0;
+
+ cfg->mtu_seqnumber++;
+ cfg->mtu_offset += sizeof(*phdr);
+ }
+ /* compute bytes left */
+ rem = cfg->mtu - cfg->mtu_offset;
+
+ if (phdr->numFrames == 255 || rem < pkt_len) {
+ int xlen;
+
+ if (phdr->numFrames == 0)
+ return (-1);
+ do {
+ xlen = write(cfg->fd, cfg->mtu_data, cfg->mtu_offset);
+ } while (xlen < 0 && errno == EAGAIN);
+
+ if (xlen < 0)
+ return (-1);
+
+ cfg->mtu_offset = 0;
+ goto retry;
+ }
+ memcpy(cfg->mtu_data + cfg->mtu_offset, sbc->data, pkt_len);
+ memset(sbc->data, 0, pkt_len);
+ cfg->mtu_offset += pkt_len;
+ cfg->mtu_timestamp += sbc->framesamples;
+ phdr->numFrames++;
+
+ sbc->rem_len = 0;
+ }
+ }
+ if (err == 0)
+ return (old_len);
+ return (err);
+}
+
+#ifdef HAVE_LIBAV
+static int
+bt_play_aac_transfer(struct voss_backend *pbe, void *ptr, int len)
+{
+ struct bt_config *cfg = pbe->arg;
+ struct aac_header {
+ uint8_t id;
+ uint8_t id2;
+ uint8_t seqnumMSB;
+ uint8_t seqnumLSB;
+ uint8_t ts3;
+ uint8_t ts2;
+ uint8_t ts1;
+ uint8_t ts0;
+ uint8_t sync3;
+ uint8_t sync2;
+ uint8_t sync1;
+ uint8_t sync0;
+ uint8_t fixed[8];
+ };
+ int old_len = len;
+ int err = 0;
+
+ while (len > 0) {
+ int delta = len;
+ int rem;
+
+ if (delta > (int)(cfg->rem_in_size - cfg->rem_in_len))
+ delta = (int)(cfg->rem_in_size - cfg->rem_in_len);
+
+ memcpy(cfg->rem_in_data + cfg->rem_in_len, ptr, delta);
+
+ ptr = (char *)ptr + delta;
+ len -= delta;
+ cfg->rem_in_len += delta;
+
+ /* check if buffer is full */
+ if (cfg->rem_in_len == cfg->rem_in_size) {
+ struct aac_header *phdr = (struct aac_header *)cfg->mtu_data;
+ AVPacket *pkt;
+ uint8_t *pkt_buf;
+ int pkt_len;
+
+ pkt = av_packet_alloc();
+ err = avcodec_send_frame(cfg->handle.av.context,
+ cfg->handle.av.frame);
+ if (err < 0) {
+ DPRINTF("Error encoding audio frame\n");
+ return (-1);
+ }
+ phdr->id = 0x80;/* RTP v2 */
+ phdr->id2 = 0x60; /* payload type 96. */
+ phdr->seqnumMSB = (uint8_t)(cfg->mtu_seqnumber >> 8);
+ phdr->seqnumLSB = (uint8_t)(cfg->mtu_seqnumber);
+ phdr->ts3 = (uint8_t)(cfg->mtu_timestamp >> 24);
+ phdr->ts2 = (uint8_t)(cfg->mtu_timestamp >> 16);
+ phdr->ts1 = (uint8_t)(cfg->mtu_timestamp >> 8);
+ phdr->ts0 = (uint8_t)(cfg->mtu_timestamp);
+ phdr->sync3 = 0;
+ phdr->sync2 = 0;
+ phdr->sync1 = 0;
+ phdr->sync0 = 0;
+ phdr->fixed[0] = 0xfc;
+ phdr->fixed[1] = 0x00;
+ phdr->fixed[2] = 0x00;
+ phdr->fixed[3] = 0xb0;
+ phdr->fixed[4] = 0x90;
+ phdr->fixed[5] = 0x80;
+ phdr->fixed[6] = 0x03;
+ phdr->fixed[7] = 0x00;
+
+ cfg->mtu_seqnumber++;
+ cfg->mtu_offset = sizeof(*phdr);
+
+ /* compute bytes left */
+ rem = cfg->mtu - cfg->mtu_offset;
+
+ if (avio_open_dyn_buf(&cfg->handle.av.format->pb) == 0) {
+ static int once = 0;
+
+ if (!once++)
+ (void)avformat_write_header(cfg->handle.av.format, NULL);
+ av_write_frame(cfg->handle.av.format, pkt);
+ av_packet_unref(pkt);
+ pkt_len = avio_close_dyn_buf(cfg->handle.av.format->pb, &pkt_buf);
+ if (rem < pkt_len)
+ DPRINTF("Out of buffer space\n");
+ if (pkt_len >= 3 && rem >= pkt_len) {
+ int xlen;
+
+ memcpy(cfg->mtu_data + cfg->mtu_offset, pkt_buf + 3, pkt_len - 3);
+
+ av_free(pkt_buf);
+
+ cfg->mtu_offset += pkt_len - 3;
+ if (cfg->chmode != MODE_MONO)
+ cfg->mtu_timestamp += cfg->rem_in_size / 4;
+ else
+ cfg->mtu_timestamp += cfg->rem_in_size / 2;
+ do {
+ xlen = write(cfg->fd, cfg->mtu_data, cfg->mtu_offset);
+ } while (xlen < 0 && errno == EAGAIN);
+
+ if (xlen < 0)
+ return (-1);
+ } else {
+ av_free(pkt_buf);
+ }
+ } else {
+ av_packet_unref(pkt);
+ }
+ /* reset remaining length */
+ cfg->rem_in_len = 0;
+ }
+ }
+ if (err == 0)
+ return (old_len);
+ return (err);
+}
+
+#endif
+
+static int
+bt_play_transfer(struct voss_backend *pbe, void *ptr, int len)
+{
+ struct bt_config *cfg = pbe->arg;
+
+ switch (cfg->codec) {
+ case CODEC_SBC:
+ return (bt_play_sbc_transfer(pbe, ptr, len));
+#ifdef HAVE_LIBAV
+ case CODEC_AAC:
+ return (bt_play_aac_transfer(pbe, ptr, len));
+#endif
+ default:
+ return (-1);
+ }
+}
+
+static void
+bt_rec_delay(struct voss_backend *pbe __unused, int *pdelay)
+{
+ *pdelay = -1;
+}
+
+static void
+bt_play_delay(struct voss_backend *pbe __unused, int *pdelay)
+{
+ /* TODO */
+ *pdelay = -1;
+}
+
+struct voss_backend voss_backend_bt_rec = {
+ .open = bt_rec_open,
+ .close = bt_rec_close,
+ .transfer = bt_rec_transfer,
+ .delay = bt_rec_delay,
+ .arg = &bt_rec_cfg,
+};
+
+struct voss_backend voss_backend_bt_play = {
+ .open = bt_play_open,
+ .close = bt_play_close,
+ .transfer = bt_play_transfer,
+ .delay = bt_play_delay,
+ .arg = &bt_play_cfg,
+};