aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c')
-rw-r--r--usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c542
1 files changed, 542 insertions, 0 deletions
diff --git a/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c b/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c
new file mode 100644
index 000000000000..c61eaf1c338d
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_bt_speaker/bt_speaker.c
@@ -0,0 +1,542 @@
+/*-
+ * Copyright (c) 2019 Google LLC, written by Richard Kralovic <riso@google.com>
+ *
+ * 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/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"
+#include "utils.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; /* player only */
+
+ 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; /* to be set later */
+ c->cfg.media_Type = mediaTypeAudio;
+ c->cfg.chmode = MODE_DUAL;
+ c->cfg.aacMode1 = 0; /* TODO: support AAC */
+ 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; /* 2 fragments of 32k each */
+ 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;
+
+ /* repeat last sample */
+ 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;
+
+ /* repeat last sample */
+ 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);
+}