mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-07 06:53:33 +00:00
358 lines
11 KiB
C
358 lines
11 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2024 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ Permission to use, copy, modify, and/or 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. │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "dsp/audio/cosmoaudio/cosmoaudio.h"
|
|
#include "dsp/audio/describe.h"
|
|
#include "libc/calls/blockcancel.internal.h"
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/calls/struct/sigset.internal.h"
|
|
#include "libc/calls/struct/stat.h"
|
|
#include "libc/calls/struct/timespec.h"
|
|
#include "libc/dce.h"
|
|
#include "libc/dlopen/dlfcn.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/intrin/describeflags.h"
|
|
#include "libc/intrin/strace.h"
|
|
#include "libc/limits.h"
|
|
#include "libc/macros.h"
|
|
#include "libc/proc/posix_spawn.h"
|
|
#include "libc/runtime/runtime.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/o.h"
|
|
#include "libc/temp.h"
|
|
#include "libc/thread/thread.h"
|
|
|
|
#define COSMOAUDIO_MINIMUM_VERISON 1
|
|
|
|
#define COSMOAUDIO_DSO_NAME "cosmoaudio." STRINGIFY(COSMOAUDIO_MINIMUM_VERISON)
|
|
|
|
__static_yoink("dsp/audio/cosmoaudio/miniaudio.h");
|
|
__static_yoink("dsp/audio/cosmoaudio/cosmoaudio.h");
|
|
__static_yoink("dsp/audio/cosmoaudio/cosmoaudio.c");
|
|
__static_yoink("dsp/audio/cosmoaudio/cosmoaudio.dll");
|
|
|
|
static const struct Source {
|
|
const char *zip;
|
|
const char *name;
|
|
} srcs[] = {
|
|
{"/zip/dsp/audio/cosmoaudio/miniaudio.h", "miniaudio.h"},
|
|
{"/zip/dsp/audio/cosmoaudio/cosmoaudio.h", "cosmoaudio.h"},
|
|
{"/zip/dsp/audio/cosmoaudio/cosmoaudio.c", "cosmoaudio.c"}, // must last
|
|
};
|
|
|
|
static struct {
|
|
pthread_once_t once;
|
|
typeof(cosmoaudio_open) *open;
|
|
typeof(cosmoaudio_close) *close;
|
|
typeof(cosmoaudio_write) *write;
|
|
typeof(cosmoaudio_flush) *flush;
|
|
typeof(cosmoaudio_read) *read;
|
|
typeof(cosmoaudio_poll) *poll;
|
|
} g_audio;
|
|
|
|
static const char *cosmoaudio_tmp_dir(void) {
|
|
const char *tmpdir;
|
|
if (!(tmpdir = getenv("TMPDIR")) || !*tmpdir)
|
|
if (!(tmpdir = getenv("HOME")) || !*tmpdir)
|
|
tmpdir = ".";
|
|
return tmpdir;
|
|
}
|
|
|
|
static bool cosmoaudio_app_dir(char *path, size_t size) {
|
|
strlcpy(path, cosmoaudio_tmp_dir(), size);
|
|
strlcat(path, "/.cosmo/", size);
|
|
if (makedirs(path, 0755))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static bool cosmoaudio_dso_path(char *path, size_t size) {
|
|
if (!cosmoaudio_app_dir(path, size))
|
|
return false;
|
|
strlcat(path, COSMOAUDIO_DSO_NAME, size);
|
|
if (IsWindows()) {
|
|
strlcat(path, ".dll", size);
|
|
} else if (IsXnu()) {
|
|
strlcat(path, ".dylib", size);
|
|
} else {
|
|
strlcat(path, ".so", size);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool cosmoaudio_extract(const char *zip, const char *to) {
|
|
int fdin, fdout;
|
|
char stage[PATH_MAX];
|
|
strlcpy(stage, to, sizeof(stage));
|
|
if (strlcat(stage, ".XXXXXX", sizeof(stage)) >= sizeof(stage)) {
|
|
errno = ENAMETOOLONG;
|
|
return false;
|
|
}
|
|
if ((fdout = mkostemp(stage, O_CLOEXEC)) == -1)
|
|
return false;
|
|
if ((fdin = open(zip, O_RDONLY | O_CLOEXEC)) == -1) {
|
|
close(fdout);
|
|
unlink(stage);
|
|
return false;
|
|
}
|
|
if (copyfd(fdin, fdout, -1) == -1) {
|
|
close(fdin);
|
|
close(fdout);
|
|
unlink(stage);
|
|
return false;
|
|
}
|
|
if (close(fdout)) {
|
|
close(fdin);
|
|
unlink(stage);
|
|
return false;
|
|
}
|
|
if (close(fdin)) {
|
|
unlink(stage);
|
|
return false;
|
|
}
|
|
if (rename(stage, to)) {
|
|
unlink(stage);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool cosmoaudio_build(const char *dso) {
|
|
|
|
// extract sauce
|
|
char src[PATH_MAX];
|
|
for (int i = 0; i < sizeof(srcs) / sizeof(*srcs); ++i) {
|
|
if (!cosmoaudio_app_dir(src, PATH_MAX))
|
|
return false;
|
|
strlcat(src, srcs[i].name, sizeof(src));
|
|
if (!cosmoaudio_extract(srcs[i].zip, src))
|
|
return false;
|
|
}
|
|
|
|
// create temporary name for compiled dso
|
|
// it'll ensure build operation is atomic
|
|
int fd;
|
|
char tmpdso[PATH_MAX];
|
|
strlcpy(tmpdso, dso, sizeof(tmpdso));
|
|
strlcat(tmpdso, ".XXXXXX", sizeof(tmpdso));
|
|
if ((fd = mkostemp(tmpdso, O_CLOEXEC)) != -1) {
|
|
close(fd);
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
// build cosmoaudio with host c compiler
|
|
char *args[] = {
|
|
"cc", //
|
|
"-w", //
|
|
"-I.", //
|
|
"-O2", //
|
|
"-fPIC", //
|
|
"-shared", //
|
|
"-pthread", //
|
|
"-DNDEBUG", //
|
|
IsAarch64() ? "-ffixed-x28" : "-DIGNORE1", //
|
|
src, //
|
|
"-o", //
|
|
tmpdso, //
|
|
"-lm", //
|
|
IsNetbsd() ? 0 : "-ldl", //
|
|
NULL,
|
|
};
|
|
int pid, ws;
|
|
errno_t err = posix_spawnp(&pid, args[0], NULL, NULL, args, environ);
|
|
if (err)
|
|
return false;
|
|
while (waitpid(pid, &ws, 0) == -1)
|
|
if (errno != EINTR)
|
|
return false;
|
|
if (ws)
|
|
return false;
|
|
|
|
// move dso to its final destination
|
|
if (rename(tmpdso, dso))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void *cosmoaudio_dlopen(const char *name) {
|
|
void *handle;
|
|
if ((handle = cosmo_dlopen(name, RTLD_NOW))) {
|
|
typeof(cosmoaudio_version) *version;
|
|
if ((version = cosmo_dlsym(handle, "cosmoaudio_version")))
|
|
if (version() >= COSMOAUDIO_MINIMUM_VERISON)
|
|
return handle;
|
|
cosmo_dlclose(handle);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void cosmoaudio_setup_impl(void) {
|
|
void *handle;
|
|
if (IsOpenbsd())
|
|
return; // no dlopen support yet
|
|
if (IsXnu() && !IsXnuSilicon())
|
|
return; // no dlopen support yet
|
|
if (!(handle = cosmoaudio_dlopen(COSMOAUDIO_DSO_NAME ".so")) &&
|
|
!(handle = cosmoaudio_dlopen("lib" COSMOAUDIO_DSO_NAME ".so")) &&
|
|
!(handle = cosmoaudio_dlopen("cosmoaudio.so")) &&
|
|
!(handle = cosmoaudio_dlopen("libcosmoaudio.so"))) {
|
|
char dso[PATH_MAX];
|
|
if (!cosmoaudio_dso_path(dso, sizeof(dso)))
|
|
return;
|
|
if ((handle = cosmoaudio_dlopen(dso)))
|
|
goto WeAreGood;
|
|
if (IsWindows()) {
|
|
if (cosmoaudio_extract("/zip/dsp/audio/cosmoaudio/cosmoaudio.dll", dso)) {
|
|
if ((handle = cosmoaudio_dlopen(dso))) {
|
|
goto WeAreGood;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (!cosmoaudio_build(dso))
|
|
return;
|
|
if (!(handle = cosmoaudio_dlopen(dso)))
|
|
return;
|
|
}
|
|
WeAreGood:
|
|
g_audio.open = cosmo_dlsym(handle, "cosmoaudio_open");
|
|
g_audio.close = cosmo_dlsym(handle, "cosmoaudio_close");
|
|
g_audio.write = cosmo_dlsym(handle, "cosmoaudio_write");
|
|
g_audio.flush = cosmo_dlsym(handle, "cosmoaudio_flush");
|
|
g_audio.read = cosmo_dlsym(handle, "cosmoaudio_read");
|
|
g_audio.poll = cosmo_dlsym(handle, "cosmoaudio_poll");
|
|
}
|
|
|
|
static void cosmoaudio_setup(void) {
|
|
BLOCK_CANCELATION;
|
|
cosmoaudio_setup_impl();
|
|
ALLOW_CANCELATION;
|
|
}
|
|
|
|
static void cosmoaudio_init(void) {
|
|
pthread_once(&g_audio.once, cosmoaudio_setup);
|
|
}
|
|
|
|
COSMOAUDIO_ABI int cosmoaudio_open(
|
|
struct CosmoAudio **out_ca, const struct CosmoAudioOpenOptions *options) {
|
|
int status;
|
|
char sbuf[32];
|
|
char dbuf[256];
|
|
cosmoaudio_init();
|
|
if (g_audio.open) {
|
|
BLOCK_SIGNALS;
|
|
status = g_audio.open(out_ca, options);
|
|
ALLOW_SIGNALS;
|
|
} else {
|
|
status = COSMOAUDIO_ELINK;
|
|
}
|
|
STRACE("cosmoaudio_open([%p], %s) → %s",
|
|
out_ca ? *out_ca : (struct CosmoAudio *)-1,
|
|
cosmoaudio_describe_open_options(dbuf, sizeof(dbuf), options),
|
|
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
|
return status;
|
|
}
|
|
|
|
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio *ca) {
|
|
int status;
|
|
char sbuf[32];
|
|
if (g_audio.close) {
|
|
BLOCK_SIGNALS;
|
|
status = g_audio.close(ca);
|
|
ALLOW_SIGNALS;
|
|
} else {
|
|
status = COSMOAUDIO_ELINK;
|
|
}
|
|
STRACE("cosmoaudio_close(%p) → %s", ca,
|
|
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
|
return status;
|
|
}
|
|
|
|
COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio *ca, const float *data,
|
|
int frames) {
|
|
int status;
|
|
char sbuf[32];
|
|
if (g_audio.write) {
|
|
BLOCK_SIGNALS;
|
|
status = g_audio.write(ca, data, frames);
|
|
ALLOW_SIGNALS;
|
|
} else {
|
|
status = COSMOAUDIO_ELINK;
|
|
}
|
|
if (frames <= 0 || frames >= 160)
|
|
DATATRACE("cosmoaudio_write(%p, %p, %d) → %s", ca, data, frames,
|
|
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
|
return status;
|
|
}
|
|
|
|
COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio *ca, float *data,
|
|
int frames) {
|
|
int status;
|
|
char sbuf[32];
|
|
if (g_audio.read) {
|
|
BLOCK_SIGNALS;
|
|
status = g_audio.read(ca, data, frames);
|
|
ALLOW_SIGNALS;
|
|
} else {
|
|
status = COSMOAUDIO_ELINK;
|
|
}
|
|
if (frames <= 0 || frames >= 160)
|
|
DATATRACE("cosmoaudio_read(%p, %p, %d) → %s", ca, data, frames,
|
|
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
|
return status;
|
|
}
|
|
|
|
COSMOAUDIO_ABI int cosmoaudio_flush(struct CosmoAudio *ca) {
|
|
int status;
|
|
char sbuf[32];
|
|
if (g_audio.flush) {
|
|
BLOCK_SIGNALS;
|
|
status = g_audio.flush(ca);
|
|
ALLOW_SIGNALS;
|
|
} else {
|
|
status = COSMOAUDIO_ELINK;
|
|
}
|
|
DATATRACE("cosmoaudio_flush(%p) → %s", ca,
|
|
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
|
return status;
|
|
}
|
|
|
|
COSMOAUDIO_ABI int cosmoaudio_poll(struct CosmoAudio *ca,
|
|
int *in_out_readFrames,
|
|
int *in_out_writeFrames) {
|
|
int status;
|
|
char sbuf[32];
|
|
char fbuf[2][20];
|
|
if (g_audio.poll) {
|
|
BLOCK_SIGNALS;
|
|
status = g_audio.poll(ca, in_out_readFrames, in_out_writeFrames);
|
|
ALLOW_SIGNALS;
|
|
} else {
|
|
status = COSMOAUDIO_ELINK;
|
|
}
|
|
DATATRACE("cosmoaudio_poll(%p, %s, %s) → %s", ca,
|
|
cosmoaudio_describe_poll_frames(fbuf[0], sizeof(fbuf[0]),
|
|
in_out_readFrames),
|
|
cosmoaudio_describe_poll_frames(fbuf[1], sizeof(fbuf[1]),
|
|
in_out_writeFrames),
|
|
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
|
return status;
|
|
}
|