Get printvideo audio working on Windows and MacOS

This commit is contained in:
Justine Tunney 2024-09-06 06:45:27 -07:00
parent 07fde68d52
commit 5d3b91d8b9
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
18 changed files with 93470 additions and 212 deletions

View file

@ -278,6 +278,7 @@ include libc/x/BUILD.mk # │
include dsp/scale/BUILD.mk # │
include dsp/mpeg/BUILD.mk # │
include dsp/tty/BUILD.mk # │
include dsp/audio/BUILD.mk # │
include dsp/BUILD.mk # │
include third_party/stb/BUILD.mk # │
include third_party/mbedtls/BUILD.mk # │
@ -439,6 +440,7 @@ COSMOPOLITAN_OBJECTS = \
THIRD_PARTY_OPENMP \
TOOL_ARGS \
NET_HTTP \
DSP_AUDIO \
LIBC_SOCK \
LIBC_NT_WS2_32 \
LIBC_NT_IPHLPAPI \

View file

@ -2,7 +2,8 @@
#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘
.PHONY: o/$(MODE)/dsp
o/$(MODE)/dsp: o/$(MODE)/dsp/core \
o/$(MODE)/dsp: o/$(MODE)/dsp/audio \
o/$(MODE)/dsp/core \
o/$(MODE)/dsp/mpeg \
o/$(MODE)/dsp/scale \
o/$(MODE)/dsp/tty

56
dsp/audio/BUILD.mk Normal file
View file

@ -0,0 +1,56 @@
#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘
PKGS += DSP_AUDIO
DSP_AUDIO_ARTIFACTS += DSP_AUDIO_A
DSP_AUDIO = $(DSP_AUDIO_A_DEPS) $(DSP_AUDIO_A)
DSP_AUDIO_A = o/$(MODE)/dsp/audio/audio.a
DSP_AUDIO_A_FILES := $(wildcard dsp/audio/*)
DSP_AUDIO_A_HDRS = $(filter %.h,$(DSP_AUDIO_A_FILES)) dsp/audio/cosmoaudio/cosmoaudio.h
DSP_AUDIO_A_SRCS = $(filter %.c,$(DSP_AUDIO_A_FILES))
DSP_AUDIO_A_DATA = \
dsp/audio/cosmoaudio/miniaudio.h \
dsp/audio/cosmoaudio/cosmoaudio.c \
dsp/audio/cosmoaudio/cosmoaudio.h \
dsp/audio/cosmoaudio/cosmoaudio.dll \
DSP_AUDIO_A_OBJS = \
$(DSP_AUDIO_A_SRCS:%.c=o/$(MODE)/%.o) \
$(DSP_AUDIO_A_DATA:%=o/$(MODE)/%.zip.o) \
DSP_AUDIO_A_CHECKS = \
$(DSP_AUDIO_A).pkg \
$(DSP_AUDIO_A_HDRS:%=o/$(MODE)/%.ok)
DSP_AUDIO_A_DIRECTDEPS = \
LIBC_CALLS \
LIBC_DLOPEN \
LIBC_INTRIN \
LIBC_NEXGEN32E \
LIBC_STR \
LIBC_SYSV \
LIBC_PROC \
LIBC_THREAD \
DSP_AUDIO_A_DEPS := \
$(call uniq,$(foreach x,$(DSP_AUDIO_A_DIRECTDEPS),$($(x))))
$(DSP_AUDIO_A): dsp/audio/ \
$(DSP_AUDIO_A).pkg \
$(DSP_AUDIO_A_OBJS)
$(DSP_AUDIO_A).pkg: \
$(DSP_AUDIO_A_OBJS) \
$(foreach x,$(DSP_AUDIO_A_DIRECTDEPS),$($(x)_A).pkg)
DSP_AUDIO_LIBS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)))
DSP_AUDIO_SRCS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)_SRCS))
DSP_AUDIO_HDRS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)_HDRS))
DSP_AUDIO_CHECKS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)_CHECKS))
DSP_AUDIO_OBJS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)_OBJS))
$(DSP_AUDIO_OBJS): $(BUILD_FILES) dsp/audio/BUILD.mk
.PHONY: o/$(MODE)/dsp/audio
o/$(MODE)/dsp/audio: $(DSP_AUDIO_CHECKS)

353
dsp/audio/audio.c Normal file
View file

@ -0,0 +1,353 @@
/*-*- 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 "libc/calls/calls.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/limits.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"
__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_read) *read;
} g_audio;
static const char *get_tmp_dir(void) {
const char *tmpdir;
if (!(tmpdir = getenv("TMPDIR")) || !*tmpdir)
if (!(tmpdir = getenv("HOME")) || !*tmpdir)
tmpdir = ".";
return tmpdir;
}
static bool get_app_dir(char *path, size_t size) {
strlcpy(path, get_tmp_dir(), size);
strlcat(path, "/.cosmo/", size);
if (makedirs(path, 0755))
return false;
return true;
}
static bool get_dso_path(char *path, size_t size) {
if (!get_app_dir(path, size))
return false;
strlcat(path, "cosmoaudio", size);
if (IsWindows()) {
strlcat(path, ".dll", size);
} else if (IsXnu()) {
strlcat(path, ".dylib", size);
} else {
strlcat(path, ".so", size);
}
return true;
}
static int is_file_newer_than_time(const char *path, const char *other) {
struct stat st1, st2;
if (stat(path, &st1))
// PATH should always exist when calling this function
return -1;
if (stat(other, &st2)) {
if (errno == ENOENT) {
// PATH should replace OTHER because OTHER doesn't exist yet
return true;
} else {
// some other error happened, so we can't do anything
return -1;
}
}
// PATH should replace OTHER if PATH was modified more recently
return timespec_cmp(st1.st_mtim, st2.st_mtim) > 0;
}
static int is_file_newer_than_bytes(const char *path, const char *other) {
int other_fd;
if ((other_fd = open(other, O_RDONLY | O_CLOEXEC)) == -1) {
if (errno == ENOENT) {
return true;
} else {
return -1;
}
}
int path_fd;
if ((path_fd = open(path, O_RDONLY | O_CLOEXEC)) == -1) {
close(other_fd);
return -1;
}
int res;
long i = 0;
for (;;) {
char path_buf[512];
ssize_t path_rc = pread(path_fd, path_buf, sizeof(path_buf), i);
if (path_rc == -1) {
res = -1;
break;
}
char other_buf[512];
ssize_t other_rc = pread(other_fd, other_buf, sizeof(other_buf), i);
if (other_rc == -1) {
res = -1;
break;
}
if (!path_rc || !other_rc) {
if (!path_rc && !other_rc)
res = false;
else
res = true;
break;
}
size_t size = path_rc;
if (other_rc < path_rc)
size = other_rc;
if (memcmp(path_buf, other_buf, size)) {
res = true;
break;
}
i += size;
}
if (close(path_fd))
res = -1;
if (close(other_fd))
res = -1;
return res;
}
static int is_file_newer_than(const char *path, const char *other) {
if (startswith(path, "/zip/"))
// to keep builds deterministic, embedded zip files always have
// the same timestamp from back in 2022 when it was implemented
return is_file_newer_than_bytes(path, other);
else
return is_file_newer_than_time(path, other);
}
static bool 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 deploy(const char *dso) {
switch (is_file_newer_than("/zip/dsp/audio/cosmoaudio/cosmoaudio.dll", dso)) {
case 0:
return true;
case 1:
return extract("/zip/dsp/audio/cosmoaudio/cosmoaudio.dll", dso);
default:
return false;
}
}
static bool build(const char *dso) {
// extract source code
char src[PATH_MAX];
bool needs_rebuild = false;
for (int i = 0; i < sizeof(srcs) / sizeof(*srcs); ++i) {
get_app_dir(src, PATH_MAX);
strlcat(src, srcs[i].name, sizeof(src));
switch (is_file_newer_than(srcs[i].zip, src)) {
case -1:
return false;
case 0:
break;
case 1:
needs_rebuild = true;
if (!extract(srcs[i].zip, src))
return false;
break;
default:
__builtin_unreachable();
}
}
// determine if we need to build
if (!needs_rebuild) {
switch (is_file_newer_than(src, dso)) {
case -1:
return false;
case 0:
break;
case 1:
needs_rebuild = true;
break;
default:
__builtin_unreachable();
}
}
// compile dynamic shared object
if (needs_rebuild) {
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;
}
char *args[] = {
"cc", //
"-I.", //
"-O2", //
"-fPIC", //
"-shared", //
"-pthread", //
"-DNDEBUG", //
IsAarch64() ? "-ffixed-x28" : "-DIGNORE1", //
src, //
"-o", //
tmpdso, //
"-ldl", //
"-lm", //
NULL,
};
int pid, ws;
errno_t err = posix_spawnp(&pid, "cc", NULL, NULL, args, environ);
if (err)
return false;
while (waitpid(pid, &ws, 0) == -1) {
if (errno != EINTR)
return false;
}
if (ws)
return false;
if (rename(tmpdso, dso))
return false;
}
return true;
}
static void cosmoaudio_setup(void) {
void *handle;
if (!(handle = cosmo_dlopen("cosmoaudio.so", RTLD_LOCAL))) {
if (issetugid())
return;
char dso[PATH_MAX];
if (!get_dso_path(dso, sizeof(dso)))
return;
if (IsWindows())
if (deploy(dso))
if ((handle = cosmo_dlopen(dso, RTLD_LOCAL)))
goto WeAreGood;
if (!build(dso))
return;
if (!(handle = cosmo_dlopen(dso, RTLD_LOCAL)))
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.read = cosmo_dlsym(handle, "cosmoaudio_read");
}
static void cosmoaudio_init(void) {
pthread_once(&g_audio.once, cosmoaudio_setup);
}
COSMOAUDIO_ABI int cosmoaudio_open(struct CosmoAudio **cap, int sampleRate,
int channels) {
cosmoaudio_init();
if (!g_audio.open)
return COSMOAUDIO_ERROR;
return g_audio.open(cap, sampleRate, channels);
}
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio *ca) {
cosmoaudio_init();
if (!g_audio.close)
return COSMOAUDIO_ERROR;
return g_audio.close(ca);
}
COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio *ca, const float *data,
int frames) {
cosmoaudio_init();
if (!g_audio.write)
return COSMOAUDIO_ERROR;
return g_audio.write(ca, data, frames);
}
COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio *ca, float *data,
int frames) {
cosmoaudio_init();
if (!g_audio.read)
return COSMOAUDIO_ERROR;
return g_audio.read(ca, data, frames);
}

3
dsp/audio/cosmoaudio/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.o
/Debug
/Release

View file

@ -0,0 +1,86 @@
# Makefile for MSVC x64 Command Line Developer Tools
#
# nmake /f Makefile.msvc check
# nmake /f Makefile.msvc MODE=debug check
#
# Compiler and linker
CC=cl
LINK=link
# Build mode (can be overridden from command line)
!IFNDEF MODE
MODE=release
!ENDIF
# Library dependencies.
TEST_LIBS=OneCore.lib
# Compiler flags
CFLAGS_COMMON=/nologo /W4 /Gy /EHsc
CFLAGS_DEBUG=/Od /Zi /MDd /D_DEBUG
CFLAGS_RELEASE=/O2 /MD /DNDEBUG
!IF "$(MODE)"=="debug"
CFLAGS=$(CFLAGS_COMMON) $(CFLAGS_DEBUG)
LDFLAGS=/DEBUG
OUT_DIR=Debug
!ELSE
CFLAGS=$(CFLAGS_COMMON) $(CFLAGS_RELEASE) /GL
LDFLAGS=/RELEASE /OPT:REF /OPT:ICF /LTCG /INCREMENTAL:NO
OUT_DIR=Release
!ENDIF
# Additional flags for DLL
DLL_CFLAGS=$(CFLAGS) /D_USRDLL /D_WINDLL
# Linker flags
LDFLAGS=/NOLOGO /SUBSYSTEM:CONSOLE $(LDFLAGS)
# Output file names
DLL_TARGET=$(OUT_DIR)\cosmoaudio.dll
TEST_TARGET=$(OUT_DIR)\test.exe
# Source files
DLL_SOURCES=cosmoaudio.c
TEST_SOURCES=test.c
# Object files
DLL_OBJECTS=$(OUT_DIR)\cosmoaudio.obj
TEST_OBJECTS=$(OUT_DIR)\test.obj
# Default target
all: $(OUT_DIR) $(DLL_TARGET) $(TEST_TARGET)
# Create output directory
$(OUT_DIR):
if not exist $(OUT_DIR) mkdir $(OUT_DIR)
# Rule to build the DLL
$(DLL_TARGET): $(OUT_DIR) $(DLL_OBJECTS)
$(LINK) /DLL $(LDFLAGS) /OUT:$(DLL_TARGET) $(DLL_OBJECTS)
# Rule to build the test program
$(TEST_TARGET): $(OUT_DIR) $(TEST_OBJECTS) $(DLL_TARGET)
$(LINK) $(LDFLAGS) /OUT:$(TEST_TARGET) $(TEST_OBJECTS) $(DLL_TARGET:.dll=.lib) $(TEST_LIBS)
# Rules to compile .c files to .obj files with header dependencies
{.}.c{$(OUT_DIR)}.obj:
$(CC) $(DLL_CFLAGS) /c /Fo$(OUT_DIR)\ $<
$(OUT_DIR)\test.obj: $(OUT_DIR) test.c cosmoaudio.h
$(CC) $(CFLAGS) /c /Fo$(OUT_DIR)\ test.c
$(OUT_DIR)\cosmoaudio.obj: $(OUT_DIR) cosmoaudio.c miniaudio.h cosmoaudio.h
$(CC) $(DLL_CFLAGS) /c /Fo$(OUT_DIR)\ cosmoaudio.c
# Clean target
clean:
if exist $(OUT_DIR) rmdir /s /q $(OUT_DIR)
# Run tests (now called 'check')
check: $(TEST_TARGET)
$(TEST_TARGET)
# Phony targets
.PHONY: all clean check

View file

@ -0,0 +1,251 @@
#define COSMOAUDIO_BUILD
#include "cosmoaudio.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MA_STATIC
#define MA_NO_DECODING
#define MA_NO_ENCODING
#ifdef NDEBUG
#define MA_DR_MP3_NO_STDIO
#endif
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
#ifndef NDEBUG
#define LOG(...) fprintf(stderr, __VA_ARGS__)
#else
#define LOG(...) (void)0
#endif
struct CosmoAudio {
ma_device device;
ma_pcm_rb input;
ma_pcm_rb output;
ma_uint32 sampleRate;
ma_uint32 channels;
};
static int read_ring_buffer(ma_pcm_rb* rb, float* pOutput, ma_uint32 frameCount,
ma_uint32 channels) {
ma_result result;
ma_uint32 framesRead;
ma_uint32 framesToRead;
for (framesRead = 0; framesRead < frameCount; framesRead += framesToRead) {
framesToRead = frameCount - framesRead;
void* pMappedBuffer;
result = ma_pcm_rb_acquire_read(rb, &framesToRead, &pMappedBuffer);
if (result != MA_SUCCESS) {
LOG("ma_pcm_rb_acquire_read failed: %s\n", ma_result_description(result));
return COSMOAUDIO_ERROR;
}
if (!framesToRead)
break;
memcpy(pOutput + framesRead * channels, pMappedBuffer,
framesToRead * channels * sizeof(float));
result = ma_pcm_rb_commit_read(rb, framesToRead);
if (result != MA_SUCCESS) {
if (result == MA_AT_END)
break;
LOG("ma_pcm_rb_commit_read failed: %s\n", ma_result_description(result));
return COSMOAUDIO_ERROR;
}
}
return framesRead;
}
static int write_ring_buffer(ma_pcm_rb* rb, const float* pInput,
ma_uint32 frameCount, ma_uint32 channels) {
ma_result result;
ma_uint32 framesWritten;
ma_uint32 framesToWrite;
for (framesWritten = 0; framesWritten < frameCount;
framesWritten += framesToWrite) {
framesToWrite = frameCount - framesWritten;
void* pMappedBuffer;
result = ma_pcm_rb_acquire_write(rb, &framesToWrite, &pMappedBuffer);
if (result != MA_SUCCESS) {
LOG("ma_pcm_rb_acquire_write failed: %s\n",
ma_result_description(result));
return COSMOAUDIO_ERROR;
}
if (!framesToWrite)
break;
memcpy(pMappedBuffer, pInput + framesWritten * channels,
framesToWrite * channels * sizeof(float));
result = ma_pcm_rb_commit_write(rb, framesToWrite);
if (result != MA_SUCCESS) {
if (result == MA_AT_END)
break;
LOG("ma_pcm_rb_commit_write failed: %s\n", ma_result_description(result));
return COSMOAUDIO_ERROR;
}
}
return framesWritten;
}
static void data_callback_f32(ma_device* pDevice, float* pOutput,
const float* pInput, ma_uint32 frameCount) {
struct CosmoAudio* ca = (struct CosmoAudio*)pDevice->pUserData;
read_ring_buffer(&ca->output, pOutput, frameCount, ca->channels);
write_ring_buffer(&ca->input, pInput, frameCount, ca->channels);
}
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
ma_uint32 frameCount) {
data_callback_f32(pDevice, (float*)pOutput, (const float*)pInput, frameCount);
}
/**
* Opens access to speaker and microphone.
*
* @param cap will receive pointer to allocated CosmoAudio object on success,
* which must be freed by caller with cosmoaudio_close()
* @param sampleRate is sample rate in Hz, e.g. 44100
* @param channels is number of channels (1 for mono, 2 for stereo)
* @return 0 on success, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_open(struct CosmoAudio** cap, int sampleRate,
int channels) {
// Allocate cosmo audio object.
struct CosmoAudio* ca;
if (!(ca = (struct CosmoAudio*)malloc(sizeof(struct CosmoAudio))))
return COSMOAUDIO_ERROR;
ca->channels = channels;
ca->sampleRate = sampleRate;
// Initialize device.
ma_result result;
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex);
deviceConfig.sampleRate = sampleRate;
deviceConfig.capture.channels = channels;
deviceConfig.capture.format = ma_format_f32;
deviceConfig.capture.shareMode = ma_share_mode_shared;
deviceConfig.playback.channels = channels;
deviceConfig.playback.format = ma_format_f32;
deviceConfig.dataCallback = data_callback;
deviceConfig.pUserData = ca;
result = ma_device_init(NULL, &deviceConfig, &ca->device);
if (result != MA_SUCCESS) {
free(ca);
return COSMOAUDIO_ERROR;
}
// Initialize the speaker ring buffer.
result = ma_pcm_rb_init(ma_format_f32, channels,
ca->device.playback.internalPeriodSizeInFrames * 10,
NULL, NULL, &ca->output);
if (result != MA_SUCCESS) {
ma_device_uninit(&ca->device);
free(ca);
return COSMOAUDIO_ERROR;
}
ma_pcm_rb_set_sample_rate(&ca->output, sampleRate);
// Initialize the microphone ring buffer.
result = ma_pcm_rb_init(ma_format_f32, channels,
ca->device.capture.internalPeriodSizeInFrames * 10,
NULL, NULL, &ca->input);
if (result != MA_SUCCESS) {
ma_pcm_rb_uninit(&ca->output);
ma_device_uninit(&ca->device);
free(ca);
return COSMOAUDIO_ERROR;
}
ma_pcm_rb_set_sample_rate(&ca->output, sampleRate);
// Start audio playback.
if (ma_device_start(&ca->device) != MA_SUCCESS) {
ma_pcm_rb_uninit(&ca->input);
ma_pcm_rb_uninit(&ca->output);
ma_device_uninit(&ca->device);
free(ca);
return COSMOAUDIO_ERROR;
}
*cap = ca;
return COSMOAUDIO_SUCCESS;
}
/**
* Closes audio device and frees all associated resources.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @return 0 on success, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio* ca) {
ma_device_uninit(&ca->device);
ma_pcm_rb_uninit(&ca->output);
free(ca);
return COSMOAUDIO_SUCCESS;
}
/**
* Writes raw audio data to speaker.
*
* The data is written to a ring buffer in real-time, which is then
* played back very soon on the audio device. This has tolerence for
* a certain amount of buffering, but expects that this function is
* repeatedly called at a regular time interval. The caller should
* have its own sleep loop for this purpose.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @param data is pointer to raw audio samples, expected to be in the range
* -1.0 to 1.0, where channels are interleaved
* @param frames is the number of frames (i.e. number of samples divided by
* number of channels) from `data` to write to audio device
* @return number of frames written, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio* ca, const float* data,
int frames) {
return write_ring_buffer(&ca->output, data, frames, ca->channels);
}
/**
* Reads raw audio data from microphone.
*
* The data is read from a ring buffer in real-time, which is then
* played back very soon on the audio device. This has tolerence for
* a certain amount of buffering, but expects that this function is
* repeatedly called at a regular time interval. The caller should
* have its own sleep loop for this purpose.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @param data is pointer to raw audio samples, expected to be in the range
* -1.0 to 1.0, where channels are interleaved
* @param frames is the number of frames (i.e. number of samples divided by
* number of channels) from `data` to read from microphone
* @return number of frames read, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio* ca, float* data,
int frames) {
int read;
for (int i = 0; i < frames; i += read) {
int remaining = frames - i;
read = read_ring_buffer(&ca->input, data + i * ca->channels, remaining,
ca->channels);
if (read < 0)
return read;
}
return frames;
}
#ifdef _MSC_VER
#include <Windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,
LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
(void)hModule;
(void)lpReserved;
(void)ul_reason_for_call;
}
#endif

Binary file not shown.

View file

@ -0,0 +1,41 @@
#ifndef COSMOAUDIO_H_
#define COSMOAUDIO_H_
#ifdef _MSC_VER
#define COSMOAUDIO_ABI
#ifdef COSMOAUDIO_BUILD
#define COSMOAUDIO_API __declspec(dllexport)
#else
#define COSMOAUDIO_API __declspec(dllimport)
#endif
#else
#define COSMOAUDIO_API
#ifdef __x86_64__
#define COSMOAUDIO_ABI __attribute__((__ms_abi__))
#else
#define COSMOAUDIO_ABI
#endif
#endif
#define COSMOAUDIO_SUCCESS 0
#define COSMOAUDIO_ERROR -1
#ifdef __cplusplus
extern "C" {
#endif
struct CosmoAudio;
COSMOAUDIO_API int cosmoaudio_open(struct CosmoAudio **, int,
int) COSMOAUDIO_ABI;
COSMOAUDIO_API int cosmoaudio_close(struct CosmoAudio *) COSMOAUDIO_ABI;
COSMOAUDIO_API int cosmoaudio_write(struct CosmoAudio *, const float *,
int) COSMOAUDIO_ABI;
COSMOAUDIO_API int cosmoaudio_read(struct CosmoAudio *, float *,
int) COSMOAUDIO_ABI;
#ifdef __cplusplus
}
#endif
#endif /* COSMOAUDIO_H_ */

View file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,36 @@
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <time.h>
#include "cosmoaudio.h"
#ifndef M_PIf
#define M_PIf 3.14159265358979323846f
#endif
int main() {
int hz = 44100;
int channels = 2;
struct CosmoAudio *ca;
if (cosmoaudio_open(&ca, hz, channels) != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "%s: failed to open audio\n", argv[0]);
return 1;
}
int n = 1000;
int sample = 0;
float *buf = (float *)malloc(sizeof(float) * channels * n);
for (;;) {
for (int i = 0; i < 128; i++) {
float freq = 440;
float t = (float)sample++ / hz;
if (sample == hz)
sample = 0;
buf[i * channels] = sinf(freq * 2.f * M_PIf * t);
buf[i * channels + 1] = sinf(freq * 2.f * M_PIf * t);
}
cosmoaudio_write(ca, buf, 128);
}
}

View file

@ -40,6 +40,7 @@ EXAMPLES_BINS = \
EXAMPLES_DIRECTDEPS = \
CTL \
DSP_AUDIO \
DSP_CORE \
DSP_SCALE \
DSP_TTY \

View file

@ -18,6 +18,7 @@ libc/isystem/byteswap.h \
libc/isystem/clzerointrin.h \
libc/isystem/complex.h \
libc/isystem/cosmo.h \
libc/isystem/cosmoaudio.h \
libc/isystem/cpio.h \
libc/isystem/cpuid.h \
libc/isystem/crypt.h \

View file

@ -171,7 +171,7 @@ static textwindows int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
// some programs like bash like to poll([stdin], 1, -1) so let's
// avoid busy looping in such cases. we could generalize this to
// always avoid busy loops, but we'd need poll to launch threads
if (pn == 1 && sn == 0 && (pipefds[i].events & POLLRDNORM_)) {
if (0 && pn == 1 && sn == 0 && (pipefds[i].events & POLLRDNORM_)) {
int err = errno;
switch (CountConsoleInputBytesBlocking(waitfor, sigmask)) {
case -1:

View file

@ -0,0 +1 @@
#include "dsp/audio/cosmoaudio/cosmoaudio.h"

View file

@ -16,6 +16,7 @@ TOOL_VIZ_BINS = \
$(TOOL_VIZ_COMS:%=%.dbg)
TOOL_VIZ_DIRECTDEPS = \
DSP_AUDIO \
DSP_CORE \
DSP_MPEG \
DSP_SCALE \

View file

@ -16,6 +16,7 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "dsp/audio/cosmoaudio/cosmoaudio.h"
#include "dsp/core/core.h"
#include "dsp/core/half.h"
#include "dsp/core/illumination.h"
@ -142,8 +143,6 @@ Effects Shortcuts:\n\
CTRL-G {Unsharp,Sharp}\n\
\n\
Environment Variables:\n\
SOX overrides location of SoX executable\n\
FFPLAY overrides location of FFmpeg ffplay executable\n\
ROWS=𝑦 sets height [inarticulate mode]\n\
COLUMNS=𝑥 sets width [inarticulate mode]\n\
TERM=dumb inarticulate mode\n\
@ -158,11 +157,6 @@ in a different format, then it's fast and easy to convert them:\n\
The terminal fonts we recommend are PragmataPro, Bitstream Vera Sans\n\
Mono (known as DejaVu Sans Mono in the open source community), Menlo,\n\
and Lucida Console.\n\
\n\
On Linux, playing audio requires either `sox` or `ffplay` being on\n\
the $PATH. Kitty is the fastest terminal. Alacritty also has a fast\n\
display. GNOME Terminal and xterm both work well in 256-color or ANSI\n\
mode.\n\
\n"
#define CTRL(C) ((C) ^ 0100)
@ -226,16 +220,6 @@ struct FrameBuffer {
struct FrameBufferVirtualScreenInfo vscreen;
};
static const struct itimerval kTimerDisarm = {
{0, 0},
{0, 0},
};
static const struct itimerval kTimerHalfSecondSingleShot = {
{0, 0},
{0, 500000},
};
static const struct NamedVector kPrimaries[] = {
{"BT.601", &kBt601Primaries},
{"BT.709", &kBt709Primaries},
@ -256,6 +240,7 @@ static const struct NamedVector kLightings[] = {
static plm_t *plm_;
static float gamma_;
static int volscale_;
struct CosmoAudio *ca_;
static enum Blur blur_;
static enum Sharp sharp_;
static jmp_buf jb_, jbi_;
@ -263,32 +248,27 @@ static double pary_, parx_;
static struct TtyIdent ti_;
static struct YCbCr *ycbcr_;
static bool emboss_, sobel_;
static volatile int playpid_;
static const char *patharg_;
static struct winsize wsize_;
static float hue_, sat_, lit_;
static volatile bool resized_;
static void *xtcodes_, *audio_;
static struct FrameBuffer fb0_;
static unsigned chans_, srate_;
static volatile bool ignoresigs_;
static size_t dh_, dw_, framecount_;
static struct FrameCountRing fcring_;
static volatile bool resized_, piped_;
static int lumakernel_, chromakernel_;
static openspeaker_f tryspeakerfns_[4];
static int primaries_, lighting_, swing_;
static uint64_t t1, t2, t3, t4, t5, t6, t8;
static const char *sox_, *ffplay_, *patharg_;
static int homerow_, lastrow_, infd_, outfd_;
static struct VtFrame vtframe_[2], *f1_, *f2_;
static struct Graphic graphic_[2], *g1_, *g2_;
static struct timespec deadline_, dura_, starttime_;
static bool yes_, stats_, dither_, ttymode_, istango_;
static struct timespec decode_start_, f1_start_, f2_start_;
static int16_t pcm_[PLM_AUDIO_SAMPLES_PER_FRAME * 2 / 8][8];
static int16_t pcmscale_[PLM_AUDIO_SAMPLES_PER_FRAME * 2 / 8][8];
static bool fullclear_, historyclear_, tuned_, yonly_, gotvideo_;
static int homerow_, lastrow_, playfd_, infd_, outfd_, speakerfails_;
static char status_[7][200], logpath_[PATH_MAX], fifopath_[PATH_MAX],
chansstr_[32], sratestr_[32];
static char status_[7][200], logpath_[PATH_MAX], chansstr_[32], sratestr_[32];
static void OnCtrlC(void) {
longjmp(jb_, 1);
@ -298,18 +278,6 @@ static void OnResize(void) {
resized_ = true;
}
static void OnSigPipe(void) {
piped_ = true;
}
static void OnSigChld(void) {
playpid_ = 0, piped_ = true;
}
static void StrikeDownCrapware(int sig) {
kill(playpid_, SIGKILL);
}
static struct timespec GetGraceTime(void) {
return timespec_sub(deadline_, timespec_real());
}
@ -349,32 +317,9 @@ static int GetLighting(const char *s) {
return GetNamedVector(kLightings, ARRAYLEN(kLightings), s);
}
static bool CloseSpeaker(void) {
int rc, wstatus;
rc = 0;
pthread_yield();
if (playfd_) {
rc |= close(playfd_);
playfd_ = -1;
}
if (playpid_) {
kill(playpid_, SIGTERM);
xsigaction(SIGALRM, StrikeDownCrapware, SA_RESETHAND, 0, 0);
setitimer(ITIMER_REAL, &kTimerHalfSecondSingleShot, NULL);
while (playpid_) {
if (waitpid(playpid_, &wstatus, 0) != -1) {
rc |= WEXITSTATUS(wstatus);
} else if (errno == EINTR) {
continue;
} else {
rc = -1;
}
break;
}
playpid_ = 0;
setitimer(ITIMER_REAL, &kTimerDisarm, NULL);
}
return !!rc;
static void CloseSpeaker(void) {
if (ca_)
cosmoaudio_close(ca_);
}
static void ResizeVtFrame(struct VtFrame *f, size_t yn, size_t xn) {
@ -473,106 +418,14 @@ static void DimensionDisplay(void) {
} while (resized_);
}
static int WriteAudio(int fd, const void *data, size_t size, int deadlinems) {
ssize_t rc;
const char *p;
size_t wrote, n;
p = data;
n = size;
do {
TryAgain:
if ((rc = write(fd, p, n)) != -1) {
wrote = rc;
p += wrote;
n -= wrote;
} else if (errno == EINTR) {
goto TryAgain;
} else if (errno == EAGAIN) {
if (poll((struct pollfd[]){{fd, POLLOUT}}, 1, deadlinems) == 0) {
return etimedout();
}
} else {
return -1;
}
} while (n);
return 0;
}
static bool TrySpeaker(const char *prog, char *const *args) {
int pipefds[2];
CHECK_NE(-1, pipe2(pipefds, O_CLOEXEC));
if (!(playpid_ = fork())) {
dup2(pipefds[0], 0);
dup2(fileno(__log_file), 1);
dup2(fileno(__log_file), 2);
close(fileno(__log_file));
execv(prog, args);
abort();
}
playfd_ = pipefds[1];
return true;
}
static bool TrySox(void) {
return TrySpeaker(sox_, ARGZ("play", "-q", "-c", chansstr_, "-traw",
"-esigned", "-b16", "-r", sratestr_, "-"));
}
static bool TryFfplay(void) {
return TrySpeaker(ffplay_, ARGZ("ffplay", "-nodisp", "-loglevel", "quiet",
"-fflags", "nobuffer", "-ac", chansstr_,
"-ar", sratestr_, "-f", "s16le", "pipe:"));
}
static bool OpenSpeaker(void) {
size_t i;
static bool once, count;
if (!once) {
once = true;
i = 0;
if (ffplay_)
tryspeakerfns_[i++] = TryFfplay;
if (sox_)
tryspeakerfns_[i++] = TrySox;
}
snprintf(fifopath_, sizeof(fifopath_), "%s%s.%d.%d.wav", __get_tmpdir(),
firstnonnull(program_invocation_short_name, "unknown"), getpid(),
count);
for (i = 0; i < ARRAYLEN(tryspeakerfns_); ++i) {
if (tryspeakerfns_[i]) {
if (++speakerfails_ <= 2 && tryspeakerfns_[i]()) {
return true;
} else {
speakerfails_ = 0;
tryspeakerfns_[i] = NULL;
}
}
}
return false;
return cosmoaudio_open(&ca_, srate_, chans_) == COSMOAUDIO_SUCCESS;
}
static void OnAudio(plm_t *mpeg, plm_samples_t *samples, void *user) {
if (playfd_ != -1) {
DEBUGF("OnAudio() [grace=%,ldns]", timespec_tonanos(GetGraceTime()));
CHECK_EQ(2, chans_);
CHECK_EQ(ARRAYLEN(pcm_) * 8, samples->count * chans_);
float2short(ARRAYLEN(pcm_), pcm_, (void *)samples->interleaved);
scalevolume(ARRAYLEN(pcm_), pcm_, volscale_);
sad16x8n(ARRAYLEN(pcm_), pcm_, pcmscale_);
DEBUGF("transcoded audio");
TryAgain:
if (WriteAudio(playfd_, pcm_, sizeof(pcm_), 1000) != -1) {
DEBUGF("WriteAudio(%d, %zu) ok [grace=%,ldns]", playfd_,
samples->count * 2, timespec_tonanos(GetGraceTime()));
} else {
WARNF("WriteAudio(%d, %zu) failed: %s", playfd_, samples->count * 2,
strerror(errno));
CloseSpeaker();
if (OpenSpeaker()) {
goto TryAgain;
}
}
}
if (!ca_)
return;
cosmoaudio_write(ca_, samples->interleaved, samples->count);
}
static void DescribeAlgorithms(char *p) {
@ -885,7 +738,6 @@ static void OnVideo(plm_t *mpeg, plm_frame_t *pf, void *user) {
static void OpenVideo(void) {
size_t yn, xn;
playfd_ = -1;
INFOF("%s(%`'s)", "OpenVideo", patharg_);
CHECK_NOTNULL((plm_ = plm_create_with_filename(patharg_)));
swing_ = 219;
@ -1336,14 +1188,8 @@ static void RestoreTty(void) {
}
static void HandleSignals(void) {
if (piped_) {
WARNF("SIGPIPE");
CloseSpeaker();
piped_ = false;
}
if (resized_) {
if (resized_)
RefreshDisplay();
}
}
static void PrintVideo(void) {
@ -1393,17 +1239,6 @@ static bool AskUserYesOrNoQuestion(const char *prompt) {
return c == 'y' || c == 'Y';
}
static bool CanPlayAudio(void) {
if (ffplay_ || sox_) {
return true;
} else if (AskUserYesOrNoQuestion(
"ffplay not found; continue without audio?")) {
return false;
} else {
longjmp(jb_, 1);
}
}
static void PrintUsage(int rc, int fd) {
tinyprint(fd, "Usage: ", program_invocation_name, USAGE, NULL);
exit(rc);
@ -1442,8 +1277,6 @@ static void GetOpts(int argc, char *argv[]) {
}
static void OnExit(void) {
if (playpid_)
kill(playpid_, SIGTERM), sched_yield();
if (plm_)
plm_destroy(plm_), plm_ = NULL;
YCbCrFree(&ycbcr_);
@ -1460,11 +1293,6 @@ static void OnExit(void) {
CloseSpeaker();
}
static void MakeLatencyLittleLessBad(void) {
LOGIFNEG1(sys_mlockall(MCL_CURRENT));
LOGIFNEG1(nice(-5));
}
static void PickDefaults(void) {
/*
* Direct color ain't true color -- it just means xterm does the
@ -1478,13 +1306,6 @@ static void PickDefaults(void) {
}
}
static void RenounceSpecialPrivileges(void) {
if (issetugid()) {
setegid(getgid());
seteuid(getuid());
}
}
#define FBIOGET_VSCREENINFO 0x4600
#define FBIOGET_FSCREENINFO 0x4602
@ -1582,7 +1403,6 @@ static void TryToOpenFrameBuffer(void) {
int main(int argc, char *argv[]) {
sigset_t wut;
const char *s;
ShowCrashReports();
gamma_ = 2.4;
volscale_ -= 2;
@ -1599,17 +1419,6 @@ int main(int argc, char *argv[]) {
if (optind == argc)
PrintUsage(EX_USAGE, STDERR_FILENO);
patharg_ = argv[optind];
s = commandvenv("SOX", "sox");
sox_ = s ? strdup(s) : 0;
s = commandvenv("FFPLAY", "ffplay");
ffplay_ = s ? strdup(s) : 0;
if (!sox_ && !ffplay_) {
fprintf(stderr, "please install either the "
"`play` (sox) or "
"`ffplay` (ffmpeg) "
"commands, so printvideo can play audio\n");
usleep(10000);
}
infd_ = STDIN_FILENO;
outfd_ = STDOUT_FILENO;
if (!setjmp(jb_)) {
@ -1617,8 +1426,6 @@ int main(int argc, char *argv[]) {
xsigaction(SIGHUP, OnCtrlC, 0, 0, NULL);
xsigaction(SIGTERM, OnCtrlC, 0, 0, NULL);
xsigaction(SIGWINCH, OnResize, 0, 0, NULL);
xsigaction(SIGCHLD, OnSigChld, 0, 0, NULL);
xsigaction(SIGPIPE, OnSigPipe, 0, 0, NULL);
if (ttyraw(kTtyLfToCrLf) != -1)
ttymode_ = true;
__cxa_atexit((void *)OnExit, NULL, NULL);
@ -1629,10 +1436,7 @@ int main(int argc, char *argv[]) {
infd_ = -1;
}
/* CHECK_NE(-1, fcntl(outfd_, F_SETFL, O_NONBLOCK)); */
if (CanPlayAudio())
MakeLatencyLittleLessBad();
TryToOpenFrameBuffer();
RenounceSpecialPrivileges();
if (t2 > t1)
longjmp(jb_, 1);
OpenVideo();