commit c2297fa38e218ad8f91175bbb242fb719b1a8435 from: Omar Polo date: Sun Dec 10 11:02:56 2023 UTC add a liboboe audio backend for android Oboe is a Google C++ library for audio on Android. The backend is currently based on the libao skeleton since I couldn't get it to play audio in a non-blocking way. (It would also be pointless since there isn't a way to poll(1).) It would be worthing experimenting with the callback API. So far, it works on my phone under termux. I can control amused with amused-web. It still lacks a test in the configure since we would need to use C++ for it. commit - 4e457e622a3cc784da56111553ff67082014d33b commit + c2297fa38e218ad8f91175bbb242fb719b1a8435 blob - ad1273c0a22318d0fc4e2061e5313da3d9857ee7 blob + b17bc268d86b86f7a2c753aa6d2766d68141635b --- Makefile +++ Makefile @@ -42,6 +42,7 @@ DISTFILES = CHANGES \ ${SOURCES} \ audio_alsa.c \ audio_ao.c \ + audio_oboe.cpp \ audio_sndio.c all: ${PROG} @@ -111,6 +112,7 @@ ${DISTNAME}.tar.gz: ${DISTFILES} -include amused.d -include audio_alsa.d -include audio_ao.d +-include audio_oboe.d -include audio_sndio.d -include compats.d -include control.d blob - /dev/null blob + 3fb5b36cce11850d3ae4b0ecbb84f150c46de9fa (mode 644) --- /dev/null +++ audio_oboe.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2023 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +extern "C" { +#include "config.h" +} + +#include + +#include +#include +#include +#include + +#include + +extern "C" { +#include "amused.h" +#include "log.h" +} + +#define ext extern "C" + +static void (*onmove_cb)(void *, int); +static int sp[2]; /* main, audio thread */ +static pthread_t at; + +static int bpf; +static unsigned int bits, rate, chan; +static oboe::AudioFormat fmt; +static char buf[BUFSIZ]; +static size_t buflen; + +static std::shared_ptr stream; + +static void * +aworker(void *d) +{ + unsigned int last_bits, last_rate, last_chan; + ssize_t r; + int sock = sp[1]; + char ch; + + stream = nullptr; + last_bits = last_rate = last_chan = 0; + + log_info("%s: starting", __func__); + for (;;) { + ch = 1; + if ((r = write(sock, &ch, 1)) == -1) + fatal("write"); + if (r == 0) + break; + + if ((r = read(sock, &ch, 1)) == -1) + fatal("read"); + if (r == 0) + break; + + if (bits != last_bits || + rate != last_rate || + chan != last_chan) { + if (stream) { + stream->close(); + stream = nullptr; + } + + last_bits = bits; + last_rate = rate; + last_chan = chan; + + log_debug("setting bits=%d rate=%d chan=%d bpf=%d", + bits, rate, chan, bpf); + + oboe::AudioStreamBuilder streamBuilder; + streamBuilder.setFormat(fmt); + streamBuilder.setSampleRate(rate); + streamBuilder.setChannelCount(chan); + oboe::Result result = streamBuilder.openStream(stream); + if (result != oboe::Result::OK) + fatalx("Error opening stream %s", + oboe::convertToText(result)); + + stream->requestStart(); + } + + // oboe works in terms of FRAMES not BYTES! + unsigned int len = buflen / bpf; + + // XXX should be the timeout in nanoseconds... + auto ret = stream->write(buf, len, 1000000000); + if (!ret) { + fatalx("write failed: %s", + oboe::convertToText(ret.error())); + } + } + + return nullptr; +} + +ext int +audio_open(void (*cb)(void *, int)) +{ + onmove_cb = cb; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == -1) { + log_warn("socketpair"); + return (-1); + } + + if (pthread_create(&at, NULL, aworker, NULL) == -1) { + log_warn("pthread_create"); + return (-1); + } + + return (0); +} + +ext int +audio_setup(unsigned int p_bits, unsigned int p_rate, unsigned int p_chan, + struct pollfd *pfds, int nfds) +{ + bits = p_bits; + rate = p_rate; + chan = p_chan; + + if (bits == 8) { + log_warnx("would require a conversion layer..."); + return (-1); + } else if (bits == 16) { + bpf = 2; + fmt = oboe::AudioFormat::I16; + } else if (bits == 24) { + bpf = 4; + fmt = oboe::AudioFormat::I24; + } else if (bits == 32) { + // XXX not so sure... + bpf = 4; + fmt = oboe::AudioFormat::I24; + } else { + log_warnx("can't handle %d bits", bits); + return (-1); + } + + bpf *= chan; + + return (0); +} + +ext int +audio_nfds(void) +{ + return 1; +} + +ext int +audio_pollfd(struct pollfd *pfds, int nfds, int events) +{ + if (nfds != 1) { + errno = EINVAL; + return -1; + } + + pfds[0].fd = sp[0]; + pfds[0].events = POLLIN; + return (0); +} + +ext int +audio_revents(struct pollfd *pfds, int nfds) +{ + if (nfds != 1) { + log_warnx("%s: called with %d nfds", __func__, nfds); + return 0; + } + + /* don't need to check; if we're here the audio thread is ready */ + return POLLOUT; +} + +ext size_t +audio_write(const void *data, size_t len) +{ + char ch; + ssize_t r; + + if ((r = read(sp[0], &ch, 1)) == -1) { + log_warn("oboe/%s: read failed", __func__); + return 0; + } + if (r == 0) + return 0; + + if (len > sizeof(buf)) + len = sizeof(buf); + + memcpy(buf, data, len); + buflen = len; + + ch = 1; + if ((r = write(sp[0], &ch, 1)) == -1) { + log_warn("oboe/%s: write failed", __func__); + return 0; + } + if (r == 0) { + log_warnx("oboe/%s: write got EOF", __func__); + return 0; + } + + if (onmove_cb) + onmove_cb(NULL, len / bpf); + + return len; +} + +ext int +audio_flush(void) +{ + return 0; // XXX request flush +} + +ext int +audio_stop(void) +{ + return 0; // XXX request stop +} blob - 26f15a0acdb7fca63a4345efbd1163deb204f9ba blob + 83d656a5ddf332bb0cb27a4bf8fc2b8b9d7a0407 --- compats.c +++ compats.c @@ -245,7 +245,8 @@ explicit_bzero(void *p, size_t n) * Indirect memset through a volatile pointer to hopefully avoid * dead-store optimisation eliminating the call. */ -static void (* volatile ssh_memset)(void *, int, size_t) = memset; +static void (* volatile ssh_memset)(void *, int, size_t) = + (void (*volatile)(void *, int, size_t))memset; void explicit_bzero(void *p, size_t n) blob - fc890e034e60584ffbbdb798b8235be8ad3af3dc blob + 497e87b133dfeb21e3bc7097dc37fa81fed4cd58 --- configure +++ configure @@ -137,10 +137,13 @@ fi BACKEND=auto CC=`printf "all:\\n\\t@echo \\\$(CC)\\n" | make ${MAKE_FLAGS} -sf -` +CXX=`printf "all:\\n\\t@echo \\\$(CXX)\\n" | make ${MAKE_FLAGS} -sf -` CFLAGS=`printf "all:\\n\\t@echo \\\$(CFLAGS)\\n" | make ${MAKE_FLAGS} -sf -` CFLAGS="${CFLAGS} -g -W -Wall -Wextra -Wmissing-prototypes" CFLAGS="${CFLAGS} -Wstrict-prototypes -Wmissing-declarations" CFLAGS="${CFLAGS} -Wno-unused-parameter -Wno-sign-compare -Wno-pointer-sign" +CXXFLAGS=`printf "all:\\n\\t@echo \\\$(CXXFLAGS)\\n" | make ${MAKE_FLAGS} -sf -` +CXXFLAGS="${CXXFLAGS} -g -W -Wall -Wextra -Wno-unused-parameter" LDADD= LDADD_LIB_AO= LDADD_LIB_ASOUND= @@ -298,6 +301,7 @@ while [ $# -gt 0 ]; do case "$val" in alsa) BACKEND=alsa ;; ao) BACKEND=ao ;; + oboe) BACKEND=oboe ;; sndio) BACKEND=sndio ;; *) echo "unknown audio backend: $val" 1>&2 @@ -307,6 +311,8 @@ while [ $# -gt 0 ]; do CC="$val" ;; CFLAGS) CFLAGS="$val" ;; + CXXFLAGS) + CXXFLAGS="$val" ;; LDADD) LDADD="$val" ;; LDADD_LIB_AO) @@ -535,8 +541,9 @@ runtest() { if runtest -MMD _MMD -MMD; then CFLAGS="${CFLAGS} -MMD" - echo "adding -MMD to CFLAGS" 1>&2 - echo "adding -MMD to CFLAGS" 1>&3 + CXXFLAGS="${CXXFLAGS} -MMD" + echo "adding -MMD to CFLAGS and CXXFLAGS" 1>&2 + echo "adding -MMD to CFLAGS and CXXFLAGS" 1>&3 fi runtest capsicum CAPSICUM || true @@ -639,6 +646,18 @@ if [ $BACKEND = auto -o $BACKEND = ao ]; then CFLAGS="${CFLAGS} -pthread" fi +if [ $BACKEND = auto -o $BACKEND = oboe ]; then + runtest pthread LIB_PTHREAD "" "-pthread" || true + if [ "${HAVE_LIB_PTHREAD}" -eq 0 ]; then + echo "Fatal: missing pthread" 1>&2 + echo "Fatal: missing pthread" 1>&3 + exit 1 + fi + CFLAGS="${CFLAGS} -pthread" + + LDADD="${LDADD} -lstdc++ -lm -llog -lOpenSLES" +fi + if [ "${HAVE_ENDIAN_H}" -eq 0 ]; then CFLAGS="${CFLAGS} -I." fi @@ -668,14 +687,19 @@ cat << __HEREDOC__ #ifndef OCONFIGURE_CONFIG_H #define OCONFIGURE_CONFIG_H +/* the oboe (android) backend uses c++ */ +#ifndef __ANDROID__ #ifdef __cplusplus # error "Do not use C++: this is a C application." #endif +#endif /* __ANDROID__ */ #if !defined(__GNUC__) || (__GNUC__ < 4) # define __attribute__(x) #endif #if defined(__linux__) || defined(__MINT__) -# define _GNU_SOURCE /* memmem, memrchr, setresuid... */ +# ifndef __ANDROID__ +# define _GNU_SOURCE /* memmem, memrchr, setresuid... */ +# endif # define _DEFAULT_SOURCE /* le32toh, crypt, ... */ #endif #if defined(__NetBSD__) @@ -1032,6 +1056,7 @@ cat << __HEREDOC__ BACKEND = ${BACKEND} CC = ${CC} CFLAGS = ${CFLAGS} +CXXFLAGS = ${CXXFLAGS} CPPFLAGS = ${CPPFLAGS} LDADD = ${LDADD} LDADD_LIB_IMSG = ${LDADD_LIB_IMSG}