Changeset View
Changeset View
Standalone View
Standalone View
lib/virtual_oss/bt/bt.c
- This file was added.
| /*- | |||||
| * 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, | |||||
| }; | |||||