#include <sys/types.h>
#include <sys/rtprio.h>
#include <sys/soundcard.h>
#include <dlfcn.h>
#include <err.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <time.h>
#include <unistd.h>
#include <poll.h>
#include <getopt.h>
#define L2CAP_SOCKET_CHECKED
#include <bluetooth.h>
#include <sdp.h>
#include "avdtp_signal.h"
#include "bt.h"
static int (*bt_receive_f)(struct bt_config *, void *, int, int);
static int (*avdtpACPHandlePacket_f)(struct bt_config *cfg);
static void (*avdtpACPFree_f)(struct bt_config *);
static int bt_in_background;
static void
message(const char *fmt,...)
{
va_list list;
if (bt_in_background)
return;
va_start(list, fmt);
vfprintf(stderr, fmt, list);
va_end(list);
}
struct bt_audio_receiver {
const char *devname;
const char *sdp_socket_path;
uint16_t l2cap_psm;
int fd_listen;
void *sdp_session;
uint32_t sdp_handle;
};
static int
register_sdp(struct bt_audio_receiver *r)
{
struct sdp_audio_sink_profile record = {};
r->sdp_session = sdp_open_local(r->sdp_socket_path);
if (r->sdp_session == NULL || sdp_error(r->sdp_session)) {
sdp_close(r->sdp_session);
r->sdp_session = NULL;
return (0);
}
record.psm = r->l2cap_psm;
record.protover = 0x100;
record.features = 0x01;
if (sdp_register_service(r->sdp_session, SDP_SERVICE_CLASS_AUDIO_SINK,
NG_HCI_BDADDR_ANY, (const uint8_t *)&record, sizeof(record),
&r->sdp_handle)) {
message("SDP failed to register: %s\n",
strerror(sdp_error(r->sdp_session)));
sdp_close(r->sdp_session);
r->sdp_session = NULL;
return (0);
}
return (1);
}
static void
unregister_sdp(struct bt_audio_receiver *r)
{
sdp_unregister_service(r->sdp_session, r->sdp_handle);
sdp_close(r->sdp_session);
r->sdp_session = NULL;
}
static int
start_listen(struct bt_audio_receiver *r)
{
struct sockaddr_l2cap addr = {};
r->fd_listen = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
if (r->fd_listen < 0)
return (0);
addr.l2cap_len = sizeof(addr);
addr.l2cap_family = AF_BLUETOOTH;
addr.l2cap_psm = r->l2cap_psm;
if (bind(r->fd_listen, (struct sockaddr *)&addr, sizeof(addr)) < 0 ||
listen(r->fd_listen, 4) < 0) {
close(r->fd_listen);
return (0);
}
return (1);
}
static void
stop_listen(struct bt_audio_receiver *r)
{
close(r->fd_listen);
}
struct bt_audio_connection {
struct bt_audio_receiver *r;
struct sockaddr_l2cap peer_addr;
struct bt_config cfg;
int oss_fd;
};
static void
close_connection(struct bt_audio_connection *c)
{
avdtpACPFree_f(&c->cfg);
if (c->cfg.fd != -1)
close(c->cfg.fd);
if (c->cfg.hc != -1)
close(c->cfg.hc);
if (c->oss_fd != -1)
close(c->oss_fd);
free(c);
}
static struct bt_audio_connection *
wait_for_connection(struct bt_audio_receiver *r)
{
struct bt_audio_connection *c =
malloc(sizeof(struct bt_audio_connection));
socklen_t addrlen;
memset(c, 0, sizeof(*c));
c->r = r;
c->cfg.fd = -1;
c->oss_fd = -1;
addrlen = sizeof(c->peer_addr);
c->cfg.hc = accept(r->fd_listen, (struct sockaddr *)&c->peer_addr, &addrlen);
message("Accepted control connection, %d\n", c->cfg.hc);
if (c->cfg.hc < 0) {
close_connection(c);
return NULL;
}
c->cfg.sep = 0;
c->cfg.media_Type = mediaTypeAudio;
c->cfg.chmode = MODE_DUAL;
c->cfg.aacMode1 = 0;
c->cfg.aacMode2 = 0;
c->cfg.acceptor_state = acpInitial;
return (c);
}
static void
setup_oss(struct bt_audio_connection *c)
{
c->oss_fd = open(c->r->devname, O_WRONLY);
if (c->oss_fd < 0)
goto err;
int v;
switch (c->cfg.chmode) {
case MODE_STEREO:
case MODE_JOINT:
case MODE_DUAL:
v = 2;
break;
case MODE_MONO:
v = 1;
break;
default:
message("Wrong chmode\n");
goto err;
}
if (ioctl(c->oss_fd, SNDCTL_DSP_CHANNELS, &v) < 0) {
message("SNDCTL_DSP_CHANNELS failed\n");
goto err;
}
v = AFMT_S16_NE;
if (ioctl(c->oss_fd, SNDCTL_DSP_SETFMT, &v) < 0) {
message("SNDCTL_DSP_SETFMT failed\n");
goto err;
}
switch (c->cfg.freq) {
case FREQ_16K:
v = 16000;
break;
case FREQ_32K:
v = 32000;
break;
case FREQ_44_1K:
v = 44100;
break;
case FREQ_48K:
v = 48000;
break;
default:
message("Wrong freq\n");
goto err;
}
if (ioctl(c->oss_fd, SNDCTL_DSP_SPEED, &v) < 0) {
message("SNDCTL_DSP_SETFMT failed\n");
goto err;
}
v = (2 << 16) | 15;
if (ioctl(c->oss_fd, SNDCTL_DSP_SETFRAGMENT, &v) < 0) {
message("SNDCTL_DSP_SETFRAGMENT failed\n");
goto err;
}
return;
err:
c->oss_fd = -1;
message("Cannot open oss device %s\n", c->r->devname);
}
static void
process_connection(struct bt_audio_connection *c)
{
struct pollfd pfd[3] = {};
time_t oss_attempt = 0;
while (c->cfg.acceptor_state != acpStreamClosed) {
int np;
pfd[0].fd = c->r->fd_listen;
pfd[0].events = POLLIN | POLLRDNORM;
pfd[0].revents = 0;
pfd[1].fd = c->cfg.hc;
pfd[1].events = POLLIN | POLLRDNORM;
pfd[1].revents = 0;
pfd[2].fd = c->cfg.fd;
pfd[2].events = POLLIN | POLLRDNORM;
pfd[2].revents = 0;
if (c->cfg.fd != -1)
np = 3;
else
np = 2;
if (poll(pfd, np, INFTIM) < 0)
return;
if (pfd[1].revents != 0) {
int retval;
message("Handling packet: state = %d, ",
c->cfg.acceptor_state);
retval = avdtpACPHandlePacket_f(&c->cfg);
message("retval = %d\n", retval);
if (retval < 0)
return;
}
if (pfd[0].revents != 0) {
socklen_t addrlen = sizeof(c->peer_addr);
int fd = accept4(c->r->fd_listen,
(struct sockaddr *)&c->peer_addr, &addrlen,
SOCK_NONBLOCK);
if (fd < 0)
return;
if (c->cfg.fd < 0) {
if (c->cfg.acceptor_state == acpStreamOpened) {
socklen_t mtusize = sizeof(uint16_t);
c->cfg.fd = fd;
if (getsockopt(c->cfg.fd, SOL_L2CAP, SO_L2CAP_IMTU, &c->cfg.mtu, &mtusize) == -1) {
message("Could not get MTU size\n");
return;
}
int temp = c->cfg.mtu * 32;
if (setsockopt(c->cfg.fd, SOL_SOCKET, SO_RCVBUF, &temp, sizeof(temp)) == -1) {
message("Could not set send buffer size\n");
return;
}
temp = 1;
if (setsockopt(c->cfg.fd, SOL_SOCKET, SO_RCVLOWAT, &temp, sizeof(temp)) == -1) {
message("Could not set low water mark\n");
return;
}
message("Accepted data connection, %d\n", c->cfg.fd);
}
} else {
close(fd);
}
}
if (pfd[2].revents != 0) {
uint8_t data[65536];
int len;
if ((len = bt_receive_f(&c->cfg, data, sizeof(data), 0)) < 0) {
return;
}
if (c->cfg.acceptor_state != acpStreamSuspended &&
c->oss_fd < 0 &&
time(NULL) != oss_attempt) {
message("Trying to open dsp\n");
setup_oss(c);
oss_attempt = time(NULL);
}
if (c->oss_fd > -1) {
uint8_t *end = data + len;
uint8_t *ptr = data;
unsigned delay;
unsigned jitter_limit;
switch (c->cfg.freq) {
case FREQ_16K:
jitter_limit = (16000 / 20);
break;
case FREQ_32K:
jitter_limit = (32000 / 20);
break;
case FREQ_44_1K:
jitter_limit = (44100 / 20);
break;
default:
jitter_limit = (48000 / 20);
break;
}
if (c->cfg.chmode == MODE_MONO) {
if (len >= 2 &&
ioctl(c->oss_fd, SNDCTL_DSP_GETODELAY, &delay) == 0 &&
delay < (jitter_limit * 2)) {
uint8_t jitter[jitter_limit * 4] __aligned(4);
size_t x;
for (x = 0; x != sizeof(jitter); x++)
jitter[x] = ptr[x % 2];
write(c->oss_fd, jitter, sizeof(jitter));
}
} else {
if (len >= 4 &&
ioctl(c->oss_fd, SNDCTL_DSP_GETODELAY, &delay) == 0 &&
delay < (jitter_limit * 4)) {
uint8_t jitter[jitter_limit * 8] __aligned(4);
size_t x;
for (x = 0; x != sizeof(jitter); x++)
jitter[x] = ptr[x % 4];
write(c->oss_fd, jitter, sizeof(jitter));
}
}
while (ptr != end) {
int written = write(c->oss_fd, ptr, end - ptr);
if (written < 0) {
if (errno != EINTR && errno != EAGAIN)
break;
written = 0;
}
ptr += written;
}
if (ptr != end) {
message("Not all written, closing dsp\n");
close(c->oss_fd);
c->oss_fd = -1;
oss_attempt = time(NULL);
}
}
}
if (c->cfg.acceptor_state == acpStreamSuspended &&
c->oss_fd > -1) {
close(c->oss_fd);
c->oss_fd = -1;
}
}
}
static struct option bt_speaker_opts[] = {
{"device", required_argument, NULL, 'd'},
{"sdp_socket_path", required_argument, NULL, 'p'},
{"rtprio", required_argument, NULL, 'i'},
{"background", no_argument, NULL, 'B'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
static void
usage(void)
{
fprintf(stderr, "Usage: virtual_bt_speaker -d /dev/dsp\n"
"\t" "-d, --device [device]\n"
"\t" "-p, --sdp_socket_path [path]\n"
"\t" "-i, --rtprio [priority]\n"
"\t" "-B, --background\n"
);
exit(EX_USAGE);
}
int
main(int argc, char **argv)
{
struct bt_audio_receiver r = {};
struct rtprio rtp = {};
void *hdl;
int ch;
r.devname = NULL;
r.sdp_socket_path = NULL;
r.l2cap_psm = SDP_UUID_PROTOCOL_AVDTP;
while ((ch = getopt_long(argc, argv, "p:i:d:Bh", bt_speaker_opts, NULL)) != -1) {
switch (ch) {
case 'd':
r.devname = optarg;
break;
case 'p':
r.sdp_socket_path = optarg;
break;
case 'B':
bt_in_background = 1;
break;
case 'i':
rtp.type = RTP_PRIO_REALTIME;
rtp.prio = atoi(optarg);
if (rtprio(RTP_SET, getpid(), &rtp) != 0) {
message("Cannot set realtime priority\n");
}
break;
default:
usage();
break;
}
}
if (r.devname == NULL)
errx(EX_USAGE, "No devicename specified");
if (bt_in_background) {
if (daemon(0, 0) != 0)
errx(EX_SOFTWARE, "Cannot become daemon");
}
if ((hdl = dlopen("/usr/lib/virtual_oss/voss_bt.so", RTLD_NOW)) == NULL)
errx(1, "%s", dlerror());
if ((bt_receive_f = dlsym(hdl, "bt_receive")) == NULL)
goto err_dlsym;
if ((avdtpACPHandlePacket_f = dlsym(hdl, "avdtpACPHandlePacket")) ==
NULL)
goto err_dlsym;
if ((avdtpACPFree_f = dlsym(hdl, "avdtpACPFree")) == NULL)
goto err_dlsym;
while (1) {
message("Starting to listen\n");
if (!start_listen(&r)) {
message("Failed to initialize server socket\n");
goto err_listen;
}
message("Registering service via SDP\n");
if (!register_sdp(&r)) {
message("Failed to register in SDP\n");
goto err_sdp;
}
while (1) {
message("Waiting for connection...\n");
struct bt_audio_connection *c = wait_for_connection(&r);
if (c == NULL) {
message("Failed to get connection\n");
goto err_conn;
}
message("Got connection...\n");
process_connection(c);
message("Connection finished...\n");
close_connection(c);
}
err_conn:
message("Unregistering service\n");
unregister_sdp(&r);
err_sdp:
stop_listen(&r);
err_listen:
sleep(5);
}
return (0);
err_dlsym:
warnx("%s", dlerror());
dlclose(hdl);
exit(EXIT_FAILURE);
}