mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-06-03 11:12:27 +00:00
Remove callback from cosmoaudio API
Using callbacks is still problematic with cosmo_dlopen() due to the need to restore the TLS register. So using callbacks is even more strict than using signal handlers. We are better off introducing a cosmoaudio_poll() function. It makes the API more UNIX-like. How bad could the latency be?
This commit is contained in:
parent
d99f066114
commit
d50d954a3c
17 changed files with 433 additions and 158 deletions
1
Makefile
1
Makefile
|
@ -493,6 +493,7 @@ COSMOPOLITAN_OBJECTS = \
|
||||||
|
|
||||||
COSMOPOLITAN_H_PKGS = \
|
COSMOPOLITAN_H_PKGS = \
|
||||||
APE \
|
APE \
|
||||||
|
DSP_AUDIO \
|
||||||
LIBC \
|
LIBC \
|
||||||
LIBC_CALLS \
|
LIBC_CALLS \
|
||||||
LIBC_ELF \
|
LIBC_ELF \
|
||||||
|
|
|
@ -58,7 +58,9 @@ static struct {
|
||||||
typeof(cosmoaudio_open) *open;
|
typeof(cosmoaudio_open) *open;
|
||||||
typeof(cosmoaudio_close) *close;
|
typeof(cosmoaudio_close) *close;
|
||||||
typeof(cosmoaudio_write) *write;
|
typeof(cosmoaudio_write) *write;
|
||||||
|
typeof(cosmoaudio_flush) *flush;
|
||||||
typeof(cosmoaudio_read) *read;
|
typeof(cosmoaudio_read) *read;
|
||||||
|
typeof(cosmoaudio_poll) *poll;
|
||||||
} g_audio;
|
} g_audio;
|
||||||
|
|
||||||
static const char *cosmoaudio_tmp_dir(void) {
|
static const char *cosmoaudio_tmp_dir(void) {
|
||||||
|
@ -232,7 +234,9 @@ WeAreGood:
|
||||||
g_audio.open = cosmo_dlsym(handle, "cosmoaudio_open");
|
g_audio.open = cosmo_dlsym(handle, "cosmoaudio_open");
|
||||||
g_audio.close = cosmo_dlsym(handle, "cosmoaudio_close");
|
g_audio.close = cosmo_dlsym(handle, "cosmoaudio_close");
|
||||||
g_audio.write = cosmo_dlsym(handle, "cosmoaudio_write");
|
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.read = cosmo_dlsym(handle, "cosmoaudio_read");
|
||||||
|
g_audio.poll = cosmo_dlsym(handle, "cosmoaudio_poll");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cosmoaudio_init(void) {
|
static void cosmoaudio_init(void) {
|
||||||
|
@ -295,3 +299,34 @@ COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio *ca, float *data,
|
||||||
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
COSMOAUDIO_ABI int cosmoaudio_flush(struct CosmoAudio *ca) {
|
||||||
|
int status;
|
||||||
|
char sbuf[32];
|
||||||
|
if (g_audio.flush)
|
||||||
|
status = g_audio.flush(ca);
|
||||||
|
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)
|
||||||
|
status = g_audio.poll(ca, in_out_readFrames, in_out_writeFrames);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# nmake /f Makefile.msvc check
|
# nmake /f Makefile.msvc check
|
||||||
# nmake /f Makefile.msvc MODE=debug check
|
# nmake /f Makefile.msvc MODE=debug check
|
||||||
#
|
#
|
||||||
|
# Note: MSVC 2019 makes the DLL 64kb smaller than MSVC 2022.
|
||||||
|
|
||||||
# Compiler and linker
|
# Compiler and linker
|
||||||
CC=cl
|
CC=cl
|
||||||
|
|
|
@ -39,15 +39,16 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct CosmoAudio {
|
struct CosmoAudio {
|
||||||
ma_device device;
|
|
||||||
ma_pcm_rb input;
|
|
||||||
ma_pcm_rb output;
|
|
||||||
ma_uint32 sampleRate;
|
|
||||||
ma_uint32 channels;
|
|
||||||
ma_uint32 periods;
|
|
||||||
enum CosmoAudioDeviceType deviceType;
|
enum CosmoAudioDeviceType deviceType;
|
||||||
cosmoaudio_data_callback_f* dataCallback;
|
ma_uint32 outputBufferFrames;
|
||||||
void* argument;
|
ma_uint32 inputBufferFrames;
|
||||||
|
int sampleRate;
|
||||||
|
int channels;
|
||||||
|
int isLeft;
|
||||||
|
ma_device device;
|
||||||
|
ma_pcm_rb output;
|
||||||
|
ma_pcm_rb input;
|
||||||
|
ma_event event;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int read_ring_buffer(ma_pcm_rb* rb, float* pOutput, ma_uint32 frameCount,
|
static int read_ring_buffer(ma_pcm_rb* rb, float* pOutput, ma_uint32 frameCount,
|
||||||
|
@ -69,8 +70,10 @@ static int read_ring_buffer(ma_pcm_rb* rb, float* pOutput, ma_uint32 frameCount,
|
||||||
framesToRead * channels * sizeof(float));
|
framesToRead * channels * sizeof(float));
|
||||||
result = ma_pcm_rb_commit_read(rb, framesToRead);
|
result = ma_pcm_rb_commit_read(rb, framesToRead);
|
||||||
if (result != MA_SUCCESS) {
|
if (result != MA_SUCCESS) {
|
||||||
if (result == MA_AT_END)
|
if (result == MA_AT_END) {
|
||||||
|
framesRead += framesToRead;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
LOG("ma_pcm_rb_commit_read failed: %s\n", ma_result_description(result));
|
LOG("ma_pcm_rb_commit_read failed: %s\n", ma_result_description(result));
|
||||||
return COSMOAUDIO_ERROR;
|
return COSMOAUDIO_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -99,8 +102,10 @@ static int write_ring_buffer(ma_pcm_rb* rb, const float* pInput,
|
||||||
framesToWrite * channels * sizeof(float));
|
framesToWrite * channels * sizeof(float));
|
||||||
result = ma_pcm_rb_commit_write(rb, framesToWrite);
|
result = ma_pcm_rb_commit_write(rb, framesToWrite);
|
||||||
if (result != MA_SUCCESS) {
|
if (result != MA_SUCCESS) {
|
||||||
if (result == MA_AT_END)
|
if (result == MA_AT_END) {
|
||||||
|
framesWritten += framesToWrite;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
LOG("ma_pcm_rb_commit_write failed: %s\n", ma_result_description(result));
|
LOG("ma_pcm_rb_commit_write failed: %s\n", ma_result_description(result));
|
||||||
return COSMOAUDIO_ERROR;
|
return COSMOAUDIO_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -111,15 +116,38 @@ static int write_ring_buffer(ma_pcm_rb* rb, const float* pInput,
|
||||||
static void data_callback_f32(ma_device* pDevice, float* pOutput,
|
static void data_callback_f32(ma_device* pDevice, float* pOutput,
|
||||||
const float* pInput, ma_uint32 frameCount) {
|
const float* pInput, ma_uint32 frameCount) {
|
||||||
struct CosmoAudio* ca = (struct CosmoAudio*)pDevice->pUserData;
|
struct CosmoAudio* ca = (struct CosmoAudio*)pDevice->pUserData;
|
||||||
if (ca->dataCallback) {
|
if (ca->deviceType & kCosmoAudioDeviceTypePlayback) {
|
||||||
ca->dataCallback(ca, pOutput, pInput, frameCount, ca->channels,
|
//
|
||||||
ca->argument);
|
// "By default, miniaudio will pre-silence the data callback's
|
||||||
} else {
|
// output buffer. If you know that you will always write valid data
|
||||||
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
|
// to the output buffer you can disable pre-silencing by setting
|
||||||
read_ring_buffer(&ca->output, pOutput, frameCount, ca->channels);
|
// the noPreSilence config option in the device config to true."
|
||||||
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
|
//
|
||||||
write_ring_buffer(&ca->input, pInput, frameCount, ca->channels);
|
// —Quoth miniaudio documentation § 16.1. Low Level API
|
||||||
|
//
|
||||||
|
if (ca->isLeft) {
|
||||||
|
int framesCopied =
|
||||||
|
read_ring_buffer(&ca->output, pOutput, frameCount, ca->channels);
|
||||||
|
if (framesCopied < (int)frameCount)
|
||||||
|
ca->isLeft = 0;
|
||||||
|
} else {
|
||||||
|
// TODO(jart): Maybe we should stretch the audio too short?
|
||||||
|
int frameOffset;
|
||||||
|
int availableFrames = ma_pcm_rb_available_read(&ca->output);
|
||||||
|
if (availableFrames >= (int)frameCount) {
|
||||||
|
frameOffset = 0;
|
||||||
|
} else {
|
||||||
|
frameOffset = frameCount - availableFrames;
|
||||||
|
frameCount = availableFrames;
|
||||||
|
}
|
||||||
|
read_ring_buffer(&ca->output, pOutput + frameOffset * ca->channels,
|
||||||
|
frameCount, ca->channels);
|
||||||
|
ca->isLeft = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
|
||||||
|
write_ring_buffer(&ca->input, pInput, frameCount, ca->channels);
|
||||||
|
ma_event_signal(&ca->event);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
|
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
|
||||||
|
@ -156,7 +184,7 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
|
||||||
return COSMOAUDIO_EINVAL;
|
return COSMOAUDIO_EINVAL;
|
||||||
if (options->sizeofThis < (int)sizeof(struct CosmoAudioOpenOptions))
|
if (options->sizeofThis < (int)sizeof(struct CosmoAudioOpenOptions))
|
||||||
return COSMOAUDIO_EINVAL;
|
return COSMOAUDIO_EINVAL;
|
||||||
if (options->periods < 0)
|
if (options->bufferFrames < 0)
|
||||||
return COSMOAUDIO_EINVAL;
|
return COSMOAUDIO_EINVAL;
|
||||||
if (options->sampleRate < 8000)
|
if (options->sampleRate < 8000)
|
||||||
return COSMOAUDIO_EINVAL;
|
return COSMOAUDIO_EINVAL;
|
||||||
|
@ -170,14 +198,18 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
|
||||||
|
|
||||||
// Allocate cosmo audio object.
|
// Allocate cosmo audio object.
|
||||||
struct CosmoAudio* ca;
|
struct CosmoAudio* ca;
|
||||||
if (!(ca = (struct CosmoAudio*)calloc(1, sizeof(struct CosmoAudio))))
|
ca = (struct CosmoAudio*)calloc(1, sizeof(struct CosmoAudio));
|
||||||
|
if (!ca)
|
||||||
return COSMOAUDIO_ERROR;
|
return COSMOAUDIO_ERROR;
|
||||||
ca->channels = options->channels;
|
ca->channels = options->channels;
|
||||||
ca->sampleRate = options->sampleRate;
|
ca->sampleRate = options->sampleRate;
|
||||||
ca->deviceType = options->deviceType;
|
ca->deviceType = options->deviceType;
|
||||||
ca->periods = options->periods ? options->periods : 10;
|
|
||||||
ca->dataCallback = options->dataCallback;
|
// Create win32-style condition variable.
|
||||||
ca->argument = options->argument;
|
if (ma_event_init(&ca->event) != MA_SUCCESS) {
|
||||||
|
free(ca);
|
||||||
|
return COSMOAUDIO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize device.
|
// Initialize device.
|
||||||
ma_result result;
|
ma_result result;
|
||||||
|
@ -197,18 +229,26 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
|
||||||
deviceConfig.pUserData = ca;
|
deviceConfig.pUserData = ca;
|
||||||
result = ma_device_init(NULL, &deviceConfig, &ca->device);
|
result = ma_device_init(NULL, &deviceConfig, &ca->device);
|
||||||
if (result != MA_SUCCESS) {
|
if (result != MA_SUCCESS) {
|
||||||
|
ma_event_uninit(&ca->event);
|
||||||
free(ca);
|
free(ca);
|
||||||
return COSMOAUDIO_ERROR;
|
return COSMOAUDIO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the speaker ring buffer.
|
// Initialize the speaker ring buffer.
|
||||||
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypePlayback)) {
|
int period = ca->device.playback.internalPeriodSizeInFrames;
|
||||||
result = ma_pcm_rb_init(
|
if (!options->bufferFrames) {
|
||||||
ma_format_f32, ca->channels,
|
ca->outputBufferFrames = period * 10;
|
||||||
ca->device.playback.internalPeriodSizeInFrames * ca->periods, NULL,
|
} else if (options->bufferFrames < period * 2) {
|
||||||
NULL, &ca->output);
|
ca->outputBufferFrames = period * 2;
|
||||||
|
} else {
|
||||||
|
ca->outputBufferFrames = options->bufferFrames;
|
||||||
|
}
|
||||||
|
if (ca->deviceType & kCosmoAudioDeviceTypePlayback) {
|
||||||
|
result = ma_pcm_rb_init(ma_format_f32, ca->channels, ca->outputBufferFrames,
|
||||||
|
NULL, NULL, &ca->output);
|
||||||
if (result != MA_SUCCESS) {
|
if (result != MA_SUCCESS) {
|
||||||
ma_device_uninit(&ca->device);
|
ma_device_uninit(&ca->device);
|
||||||
|
ma_event_uninit(&ca->event);
|
||||||
free(ca);
|
free(ca);
|
||||||
return COSMOAUDIO_ERROR;
|
return COSMOAUDIO_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -216,15 +256,22 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the microphone ring buffer.
|
// Initialize the microphone ring buffer.
|
||||||
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypeCapture)) {
|
period = ca->device.capture.internalPeriodSizeInFrames;
|
||||||
result = ma_pcm_rb_init(
|
if (!options->bufferFrames) {
|
||||||
ma_format_f32, ca->channels,
|
ca->inputBufferFrames = period * 10;
|
||||||
ca->device.capture.internalPeriodSizeInFrames * ca->periods, NULL, NULL,
|
} else if (options->bufferFrames < period * 2) {
|
||||||
&ca->input);
|
ca->inputBufferFrames = period * 2;
|
||||||
|
} else {
|
||||||
|
ca->inputBufferFrames = options->bufferFrames;
|
||||||
|
}
|
||||||
|
if (ca->deviceType & kCosmoAudioDeviceTypeCapture) {
|
||||||
|
result = ma_pcm_rb_init(ma_format_f32, ca->channels, ca->inputBufferFrames,
|
||||||
|
NULL, NULL, &ca->input);
|
||||||
if (result != MA_SUCCESS) {
|
if (result != MA_SUCCESS) {
|
||||||
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypePlayback))
|
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
|
||||||
ma_pcm_rb_uninit(&ca->output);
|
ma_pcm_rb_uninit(&ca->output);
|
||||||
ma_device_uninit(&ca->device);
|
ma_device_uninit(&ca->device);
|
||||||
|
ma_event_uninit(&ca->event);
|
||||||
free(ca);
|
free(ca);
|
||||||
return COSMOAUDIO_ERROR;
|
return COSMOAUDIO_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -233,11 +280,12 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
|
||||||
|
|
||||||
// Start audio playback.
|
// Start audio playback.
|
||||||
if (ma_device_start(&ca->device) != MA_SUCCESS) {
|
if (ma_device_start(&ca->device) != MA_SUCCESS) {
|
||||||
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypeCapture))
|
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
|
||||||
ma_pcm_rb_uninit(&ca->input);
|
|
||||||
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypePlayback))
|
|
||||||
ma_pcm_rb_uninit(&ca->output);
|
ma_pcm_rb_uninit(&ca->output);
|
||||||
|
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
|
||||||
|
ma_pcm_rb_uninit(&ca->input);
|
||||||
ma_device_uninit(&ca->device);
|
ma_device_uninit(&ca->device);
|
||||||
|
ma_event_uninit(&ca->event);
|
||||||
free(ca);
|
free(ca);
|
||||||
return COSMOAUDIO_ERROR;
|
return COSMOAUDIO_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -249,8 +297,13 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
|
||||||
/**
|
/**
|
||||||
* Closes audio device and frees all associated resources.
|
* Closes audio device and frees all associated resources.
|
||||||
*
|
*
|
||||||
|
* This function is non-blocking and will drop buffered audio. In
|
||||||
|
* playback mode, you need to call cosmoaudio_flush() to ensure data
|
||||||
|
* supplied by cosmoaudio_write() gets played on your speaker.
|
||||||
|
*
|
||||||
* Calling this function twice on the same object will result in
|
* Calling this function twice on the same object will result in
|
||||||
* undefined behavior.
|
* undefined behavior. Even if this function fails, the `ca` will be
|
||||||
|
* freed to the greatest extent possible.
|
||||||
*
|
*
|
||||||
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
|
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
|
||||||
* @return 0 on success, or negative error code on failure
|
* @return 0 on success, or negative error code on failure
|
||||||
|
@ -258,11 +311,12 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
|
||||||
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio* ca) {
|
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio* ca) {
|
||||||
if (!ca)
|
if (!ca)
|
||||||
return COSMOAUDIO_EINVAL;
|
return COSMOAUDIO_EINVAL;
|
||||||
ma_device_uninit(&ca->device);
|
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
|
||||||
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypePlayback))
|
|
||||||
ma_pcm_rb_uninit(&ca->output);
|
ma_pcm_rb_uninit(&ca->output);
|
||||||
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypeCapture))
|
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
|
||||||
ma_pcm_rb_uninit(&ca->input);
|
ma_pcm_rb_uninit(&ca->input);
|
||||||
|
ma_device_uninit(&ca->device);
|
||||||
|
ma_event_uninit(&ca->event);
|
||||||
free(ca);
|
free(ca);
|
||||||
return COSMOAUDIO_SUCCESS;
|
return COSMOAUDIO_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -289,10 +343,10 @@ COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio* ca, const float* data,
|
||||||
return COSMOAUDIO_EINVAL;
|
return COSMOAUDIO_EINVAL;
|
||||||
if (frames < 0)
|
if (frames < 0)
|
||||||
return COSMOAUDIO_EINVAL;
|
return COSMOAUDIO_EINVAL;
|
||||||
if (ca->dataCallback)
|
|
||||||
return COSMOAUDIO_EINVAL;
|
|
||||||
if (!(ca->deviceType & kCosmoAudioDeviceTypePlayback))
|
if (!(ca->deviceType & kCosmoAudioDeviceTypePlayback))
|
||||||
return COSMOAUDIO_EINVAL;
|
return COSMOAUDIO_EINVAL;
|
||||||
|
if (1u + frames > ca->outputBufferFrames)
|
||||||
|
return COSMOAUDIO_ENOBUF;
|
||||||
if (!frames)
|
if (!frames)
|
||||||
return 0;
|
return 0;
|
||||||
if (!data)
|
if (!data)
|
||||||
|
@ -322,8 +376,6 @@ COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio* ca, float* data,
|
||||||
return COSMOAUDIO_EINVAL;
|
return COSMOAUDIO_EINVAL;
|
||||||
if (frames < 0)
|
if (frames < 0)
|
||||||
return COSMOAUDIO_EINVAL;
|
return COSMOAUDIO_EINVAL;
|
||||||
if (ca->dataCallback)
|
|
||||||
return COSMOAUDIO_EINVAL;
|
|
||||||
if (!(ca->deviceType & kCosmoAudioDeviceTypeCapture))
|
if (!(ca->deviceType & kCosmoAudioDeviceTypeCapture))
|
||||||
return COSMOAUDIO_EINVAL;
|
return COSMOAUDIO_EINVAL;
|
||||||
if (!frames)
|
if (!frames)
|
||||||
|
@ -333,20 +385,80 @@ COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio* ca, float* data,
|
||||||
return read_ring_buffer(&ca->input, data, frames, ca->channels);
|
return read_ring_buffer(&ca->input, data, frames, ca->channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
/**
|
||||||
#include <Windows.h>
|
* Waits for read and/or write to become possible.
|
||||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,
|
*
|
||||||
LPVOID lpReserved) {
|
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
|
||||||
switch (ul_reason_for_call) {
|
* @param in_out_readFrames if non-NULL specifies how many frames of
|
||||||
case DLL_PROCESS_ATTACH:
|
* capture data be immediately readable by cosmoaudio_read() before
|
||||||
case DLL_THREAD_ATTACH:
|
* this can return; it must not exceed the buffer size; on return
|
||||||
case DLL_THREAD_DETACH:
|
* this will be set to the actual number of frames in the buffer;
|
||||||
case DLL_PROCESS_DETACH:
|
* if the caller supplies a zero then this call is a non-blocking
|
||||||
break;
|
* way to query buffer sizes
|
||||||
|
* @param in_out_writeFrames if non-NULL specifies how many frames of
|
||||||
|
* capture data be immediately writable by cosmoaudio_write() before
|
||||||
|
* this can return; it must not exceed the buffer size; on return
|
||||||
|
* this will be set to the actual number of frames in the buffer;
|
||||||
|
* if the caller supplies a zero then this call is a non-blocking
|
||||||
|
* way to query buffer sizes
|
||||||
|
* @return 0 on success, or negative error code on error
|
||||||
|
*/
|
||||||
|
COSMOAUDIO_ABI int cosmoaudio_poll(struct CosmoAudio* ca,
|
||||||
|
int* in_out_readFrames,
|
||||||
|
int* in_out_writeFrames) {
|
||||||
|
if (!ca)
|
||||||
|
return COSMOAUDIO_EINVAL;
|
||||||
|
if (!in_out_readFrames && !in_out_writeFrames)
|
||||||
|
return COSMOAUDIO_EINVAL;
|
||||||
|
if (in_out_readFrames && !(ca->deviceType & kCosmoAudioDeviceTypeCapture))
|
||||||
|
return COSMOAUDIO_EINVAL;
|
||||||
|
if (in_out_writeFrames && !(ca->deviceType & kCosmoAudioDeviceTypePlayback))
|
||||||
|
return COSMOAUDIO_EINVAL;
|
||||||
|
if (in_out_readFrames && 1u + *in_out_readFrames > ca->inputBufferFrames)
|
||||||
|
return COSMOAUDIO_ENOBUF;
|
||||||
|
if (in_out_writeFrames && 1u + *in_out_writeFrames > ca->outputBufferFrames)
|
||||||
|
return COSMOAUDIO_ENOBUF;
|
||||||
|
for (;;) {
|
||||||
|
int done = 0;
|
||||||
|
ma_uint32 readable = 0;
|
||||||
|
ma_uint32 writable = 0;
|
||||||
|
if (in_out_readFrames) {
|
||||||
|
readable = ma_pcm_rb_available_read(&ca->input);
|
||||||
|
done |= readable >= (ma_uint32)*in_out_readFrames;
|
||||||
|
}
|
||||||
|
if (in_out_writeFrames) {
|
||||||
|
writable = ma_pcm_rb_available_write(&ca->output);
|
||||||
|
done |= writable >= (ma_uint32)*in_out_writeFrames;
|
||||||
|
}
|
||||||
|
if (done) {
|
||||||
|
if (in_out_readFrames)
|
||||||
|
*in_out_readFrames = readable;
|
||||||
|
if (in_out_writeFrames)
|
||||||
|
*in_out_writeFrames = writable;
|
||||||
|
return COSMOAUDIO_SUCCESS;
|
||||||
|
}
|
||||||
|
if (ma_event_wait(&ca->event) != MA_SUCCESS)
|
||||||
|
return COSMOAUDIO_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for written samples to be sent to device.
|
||||||
|
*
|
||||||
|
* This function is only valid to call in playback or duplex mode.
|
||||||
|
*
|
||||||
|
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
|
||||||
|
* @return 0 on success, or negative error code on failure
|
||||||
|
*/
|
||||||
|
COSMOAUDIO_ABI int cosmoaudio_flush(struct CosmoAudio* ca) {
|
||||||
|
if (!ca)
|
||||||
|
return COSMOAUDIO_EINVAL;
|
||||||
|
if (!(ca->deviceType & kCosmoAudioDeviceTypePlayback))
|
||||||
|
return COSMOAUDIO_EINVAL;
|
||||||
|
for (;;) {
|
||||||
|
if (!ma_pcm_rb_available_read(&ca->output))
|
||||||
|
return COSMOAUDIO_SUCCESS;
|
||||||
|
if (ma_event_wait(&ca->event) != MA_SUCCESS)
|
||||||
|
return COSMOAUDIO_ERROR;
|
||||||
}
|
}
|
||||||
return TRUE;
|
|
||||||
(void)hModule;
|
|
||||||
(void)lpReserved;
|
|
||||||
(void)ul_reason_for_call;
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
Binary file not shown.
|
@ -21,6 +21,7 @@
|
||||||
#define COSMOAUDIO_ERROR -1 // unspecified error
|
#define COSMOAUDIO_ERROR -1 // unspecified error
|
||||||
#define COSMOAUDIO_EINVAL -2 // invalid parameters passed to api
|
#define COSMOAUDIO_EINVAL -2 // invalid parameters passed to api
|
||||||
#define COSMOAUDIO_ELINK -3 // loading cosmoaudio dso failed
|
#define COSMOAUDIO_ELINK -3 // loading cosmoaudio dso failed
|
||||||
|
#define COSMOAUDIO_ENOBUF -4 // invalid buffering parameters
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -28,14 +29,6 @@ extern "C" {
|
||||||
|
|
||||||
struct CosmoAudio;
|
struct CosmoAudio;
|
||||||
|
|
||||||
typedef void cosmoaudio_data_callback_f( //
|
|
||||||
struct CosmoAudio *ca, //
|
|
||||||
float *outputSamples, //
|
|
||||||
const float *inputSamples, //
|
|
||||||
int frameCount, //
|
|
||||||
int channels, //
|
|
||||||
void *argument);
|
|
||||||
|
|
||||||
enum CosmoAudioDeviceType {
|
enum CosmoAudioDeviceType {
|
||||||
kCosmoAudioDeviceTypePlayback = 1,
|
kCosmoAudioDeviceTypePlayback = 1,
|
||||||
kCosmoAudioDeviceTypeCapture = 2,
|
kCosmoAudioDeviceTypeCapture = 2,
|
||||||
|
@ -62,20 +55,10 @@ struct CosmoAudioOpenOptions {
|
||||||
// for mono or 2 for stereo.
|
// for mono or 2 for stereo.
|
||||||
int channels;
|
int channels;
|
||||||
|
|
||||||
// Number of periods in ring buffer. Set to 0 for default. Higher
|
// Number of frames in each ring buffer. A frame consists of a PCM
|
||||||
// numbers (e.g. 20) means more buffering. Lower numbers (e.g. 2)
|
// sample for each channel. Set to 0 for default. If this is less than
|
||||||
// means less buffering. This is ignored if callback is specified.
|
// the device period size times two, it'll be increased to that value.
|
||||||
int periods;
|
int bufferFrames;
|
||||||
|
|
||||||
// If callback is NULL, then cosmoaudio_write() and cosmoaudio_read()
|
|
||||||
// should be used, which ring buffer audio to the default internal
|
|
||||||
// routine. Setting this callback to non-NULL puts CosmoAudio in
|
|
||||||
// manual mode, where the callback is responsible for copying PCM
|
|
||||||
// samples each time the device calls this.
|
|
||||||
cosmoaudio_data_callback_f *dataCallback;
|
|
||||||
|
|
||||||
// This is an arbitrary value passed to the callback.
|
|
||||||
void *argument;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
COSMOAUDIO_API int cosmoaudio_version(void) COSMOAUDIO_ABI;
|
COSMOAUDIO_API int cosmoaudio_version(void) COSMOAUDIO_ABI;
|
||||||
|
@ -95,12 +78,22 @@ COSMOAUDIO_API int cosmoaudio_write( //
|
||||||
int frameCount //
|
int frameCount //
|
||||||
) COSMOAUDIO_ABI;
|
) COSMOAUDIO_ABI;
|
||||||
|
|
||||||
|
COSMOAUDIO_API int cosmoaudio_flush( //
|
||||||
|
struct CosmoAudio *ca //
|
||||||
|
) COSMOAUDIO_ABI;
|
||||||
|
|
||||||
COSMOAUDIO_API int cosmoaudio_read( //
|
COSMOAUDIO_API int cosmoaudio_read( //
|
||||||
struct CosmoAudio *ca, //
|
struct CosmoAudio *ca, //
|
||||||
float *out_samples, //
|
float *out_samples, //
|
||||||
int frameCount //
|
int frameCount //
|
||||||
) COSMOAUDIO_ABI;
|
) COSMOAUDIO_ABI;
|
||||||
|
|
||||||
|
COSMOAUDIO_API int cosmoaudio_poll( //
|
||||||
|
struct CosmoAudio *ca, //
|
||||||
|
int *in_out_readFrames, //
|
||||||
|
int *in_out_writeFrames //
|
||||||
|
) COSMOAUDIO_ABI;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -9,50 +9,68 @@
|
||||||
#endif
|
#endif
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include "cosmoaudio.h"
|
#include "cosmoaudio.h"
|
||||||
|
|
||||||
|
#define SAMPLING_RATE 44100
|
||||||
|
#define WAVE_INTERVAL 440
|
||||||
|
#define CHANNELS 2
|
||||||
|
|
||||||
#ifndef M_PIf
|
#ifndef M_PIf
|
||||||
#define M_PIf 3.14159265358979323846f
|
#define M_PIf 3.14159265358979323846f
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int g_hz = 44100;
|
|
||||||
int g_channels = 2;
|
|
||||||
int g_generation = 0;
|
|
||||||
int g_freq = 440;
|
|
||||||
|
|
||||||
void data_callback(struct CosmoAudio *ca, float *outputSamples,
|
|
||||||
const float *inputSamples, int frameCount, int channels,
|
|
||||||
void *argument) {
|
|
||||||
for (int i = 0; i < frameCount; i++) {
|
|
||||||
float t = (float)g_generation++ / g_hz;
|
|
||||||
if (g_generation == g_hz)
|
|
||||||
g_generation = 0;
|
|
||||||
float s = sinf(2 * M_PIf * g_freq * t);
|
|
||||||
for (int j = 0; j < channels; j++)
|
|
||||||
outputSamples[i * channels + j] = s;
|
|
||||||
}
|
|
||||||
(void)inputSamples;
|
|
||||||
(void)argument;
|
|
||||||
(void)ca;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|
||||||
struct CosmoAudioOpenOptions cao = {};
|
struct CosmoAudioOpenOptions cao = {0};
|
||||||
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
|
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
|
||||||
cao.deviceType = kCosmoAudioDeviceTypePlayback;
|
cao.deviceType = kCosmoAudioDeviceTypePlayback;
|
||||||
cao.sampleRate = g_hz;
|
cao.sampleRate = SAMPLING_RATE;
|
||||||
cao.channels = g_channels;
|
cao.channels = CHANNELS;
|
||||||
cao.dataCallback = data_callback;
|
|
||||||
|
|
||||||
|
int status;
|
||||||
struct CosmoAudio *ca;
|
struct CosmoAudio *ca;
|
||||||
if (cosmoaudio_open(&ca, &cao) != COSMOAUDIO_SUCCESS) {
|
status = cosmoaudio_open(&ca, &cao);
|
||||||
fprintf(stderr, "failed to open audio\n");
|
if (status != COSMOAUDIO_SUCCESS) {
|
||||||
|
fprintf(stderr, "failed to open audio: %d\n", status);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fgetc(stdin);
|
float buf[256 * CHANNELS];
|
||||||
|
for (int g = 0; g < SAMPLING_RATE;) {
|
||||||
|
int frames = 1;
|
||||||
|
status = cosmoaudio_poll(ca, NULL, &frames);
|
||||||
|
if (status != COSMOAUDIO_SUCCESS) {
|
||||||
|
fprintf(stderr, "failed to poll output: %d\n", status);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (frames > 256)
|
||||||
|
frames = 256;
|
||||||
|
if (frames > SAMPLING_RATE - g)
|
||||||
|
frames = SAMPLING_RATE - g;
|
||||||
|
for (int f = 0; f < frames; ++f) {
|
||||||
|
float t = (float)g++ / SAMPLING_RATE;
|
||||||
|
float s = sinf(2 * M_PIf * WAVE_INTERVAL * t);
|
||||||
|
for (int c = 0; c < CHANNELS; c++)
|
||||||
|
buf[f * CHANNELS + c] = s;
|
||||||
|
}
|
||||||
|
status = cosmoaudio_write(ca, buf, frames);
|
||||||
|
if (status != frames) {
|
||||||
|
fprintf(stderr, "failed to write output: %d\n", status);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cosmoaudio_close(ca);
|
status = cosmoaudio_flush(ca);
|
||||||
|
if (status != COSMOAUDIO_SUCCESS) {
|
||||||
|
fprintf(stderr, "failed to flush output: %d\n", status);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = cosmoaudio_close(ca);
|
||||||
|
if (status != COSMOAUDIO_SUCCESS) {
|
||||||
|
fprintf(stderr, "failed to close audio: %d\n", status);
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,8 @@ const char *cosmoaudio_describe_status(char *buf, int n, int status) {
|
||||||
return "COSMOAUDIO_EINVAL";
|
return "COSMOAUDIO_EINVAL";
|
||||||
case COSMOAUDIO_ELINK:
|
case COSMOAUDIO_ELINK:
|
||||||
return "COSMOAUDIO_ELINK";
|
return "COSMOAUDIO_ELINK";
|
||||||
|
case COSMOAUDIO_ENOBUF:
|
||||||
|
return "COSMOAUDIO_ENOBUF";
|
||||||
default:
|
default:
|
||||||
ksnprintf(buf, n, "%d", status);
|
ksnprintf(buf, n, "%d", status);
|
||||||
return buf;
|
return buf;
|
||||||
|
@ -81,24 +83,11 @@ const char *cosmoaudio_describe_open_options(
|
||||||
gotsome = true;
|
gotsome = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options->dataCallback) {
|
if (options->bufferFrames) {
|
||||||
if (gotsome)
|
if (gotsome)
|
||||||
append(", ");
|
append(", ");
|
||||||
append(".dataCallback=%t", options->dataCallback);
|
append(".bufferFrames=%d", options->bufferFrames);
|
||||||
gotsome = true;
|
gotsome = true;
|
||||||
if (options->argument) {
|
|
||||||
if (gotsome)
|
|
||||||
append(", ");
|
|
||||||
append(".argument=%p", options->argument);
|
|
||||||
gotsome = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (options->periods) {
|
|
||||||
if (gotsome)
|
|
||||||
append(", ");
|
|
||||||
append(".periods=%d", options->periods);
|
|
||||||
gotsome = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options->sizeofThis) {
|
if (options->sizeofThis) {
|
||||||
|
@ -111,3 +100,15 @@ const char *cosmoaudio_describe_open_options(
|
||||||
append("}");
|
append("}");
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *cosmoaudio_describe_poll_frames(char *buf, int n,
|
||||||
|
int *in_out_frames) {
|
||||||
|
if (!in_out_frames)
|
||||||
|
return "NULL";
|
||||||
|
if (kisdangerous(in_out_frames)) {
|
||||||
|
ksnprintf(buf, n, "%p", in_out_frames);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
ksnprintf(buf, n, "[%d]", *in_out_frames);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ COSMOPOLITAN_C_START_
|
||||||
const char *cosmoaudio_describe_status(char *, int, int);
|
const char *cosmoaudio_describe_status(char *, int, int);
|
||||||
const char *cosmoaudio_describe_open_options(
|
const char *cosmoaudio_describe_open_options(
|
||||||
char *, int, const struct CosmoAudioOpenOptions *);
|
char *, int, const struct CosmoAudioOpenOptions *);
|
||||||
|
const char *cosmoaudio_describe_poll_frames(char *, int, int *);
|
||||||
|
|
||||||
COSMOPOLITAN_C_END_
|
COSMOPOLITAN_C_END_
|
||||||
#endif /* COSMOPOLITAN_DSP_AUDIO_DESCRIBE_H_ */
|
#endif /* COSMOPOLITAN_DSP_AUDIO_DESCRIBE_H_ */
|
||||||
|
|
|
@ -53,11 +53,11 @@ struct SamplingSolution {
|
||||||
static double ComputeWeight(double x) {
|
static double ComputeWeight(double x) {
|
||||||
if (-1.5 < x && x < 1.5) {
|
if (-1.5 < x && x < 1.5) {
|
||||||
if (-.5 < x && x < .5) {
|
if (-.5 < x && x < .5) {
|
||||||
return.75 - SQR(x);
|
return .75 - SQR(x);
|
||||||
} else if (x < 0) {
|
} else if (x < 0) {
|
||||||
return.5 * SQR(x + 1.5);
|
return .5 * SQR(x + 1.5);
|
||||||
} else {
|
} else {
|
||||||
return.5 * SQR(x - 1.5);
|
return .5 * SQR(x - 1.5);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -164,12 +164,19 @@ static void GyaradosImpl(long dyw, long dxw, int dst[dyw][dxw], long syw,
|
||||||
tmp0[dy][sx] = QRS(M, eax);
|
tmp0[dy][sx] = QRS(M, eax);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (dy = 0; dy < dyn; ++dy) {
|
if (sharpen) {
|
||||||
for (sx = 0; sx < sxn; ++sx) {
|
for (dy = 0; dy < dyn; ++dy) {
|
||||||
tmp1[dy][sx] = sharpen ? Sharpen(tmp0[MIN(dyn - 1, MAX(0, dy - 1))][sx],
|
for (sx = 0; sx < sxn; ++sx) {
|
||||||
tmp0[dy][sx],
|
tmp1[dy][sx] =
|
||||||
tmp0[MIN(dyn - 1, MAX(0, dy + 1))][sx])
|
Sharpen(tmp0[MIN(dyn - 1, MAX(0, dy - 1))][sx], tmp0[dy][sx],
|
||||||
: tmp0[dy][sx];
|
tmp0[MIN(dyn - 1, MAX(0, dy + 1))][sx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (dy = 0; dy < dyn; ++dy) {
|
||||||
|
for (sx = 0; sx < sxn; ++sx) {
|
||||||
|
tmp1[dy][sx] = tmp0[dy][sx];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (dx = 0; dx < dxn; ++dx) {
|
for (dx = 0; dx < dxn; ++dx) {
|
||||||
|
@ -180,12 +187,19 @@ static void GyaradosImpl(long dyw, long dxw, int dst[dyw][dxw], long syw,
|
||||||
tmp2[dy][dx] = QRS(M, eax);
|
tmp2[dy][dx] = QRS(M, eax);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (dx = 0; dx < dxn; ++dx) {
|
if (sharpen) {
|
||||||
for (dy = 0; dy < dyn; ++dy) {
|
for (dx = 0; dx < dxn; ++dx) {
|
||||||
dst[dy][dx] = sharpen ? Sharpen(tmp2[dy][MIN(dxn - 1, MAX(0, dx - 1))],
|
for (dy = 0; dy < dyn; ++dy) {
|
||||||
tmp2[dy][dx],
|
dst[dy][dx] =
|
||||||
tmp2[dy][MIN(dxn - 1, MAX(0, dx + 1))])
|
Sharpen(tmp2[dy][MIN(dxn - 1, MAX(0, dx - 1))], tmp2[dy][dx],
|
||||||
: tmp2[dy][dx];
|
tmp2[dy][MIN(dxn - 1, MAX(0, dx + 1))]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (dx = 0; dx < dxn; ++dx) {
|
||||||
|
for (dy = 0; dy < dyn; ++dy) {
|
||||||
|
dst[dy][dx] = tmp2[dy][dx];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,7 +186,6 @@ if [ ! -x bin/x86_64-linux-cosmo-gcc ]; then
|
||||||
rm -f x86_64-gcc.zip
|
rm -f x86_64-gcc.zip
|
||||||
rm -f llvm.zip
|
rm -f llvm.zip
|
||||||
mv bin/clang-19 bin/cosmo-clang
|
mv bin/clang-19 bin/cosmo-clang
|
||||||
mv bin/clang-format bin/clang-format
|
|
||||||
fi
|
fi
|
||||||
rm -f bin/*-cpp
|
rm -f bin/*-cpp
|
||||||
rm -f bin/*-gcc-*
|
rm -f bin/*-gcc-*
|
||||||
|
|
|
@ -36,6 +36,7 @@ TOOL_VIZ_LIB_A_DIRECTDEPS = \
|
||||||
LIBC_RUNTIME \
|
LIBC_RUNTIME \
|
||||||
LIBC_STDIO \
|
LIBC_STDIO \
|
||||||
LIBC_STR \
|
LIBC_STR \
|
||||||
|
LIBC_THREAD \
|
||||||
LIBC_SYSV \
|
LIBC_SYSV \
|
||||||
LIBC_TESTLIB \
|
LIBC_TESTLIB \
|
||||||
LIBC_TINYMATH \
|
LIBC_TINYMATH \
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
#include "libc/str/str.h"
|
#include "libc/str/str.h"
|
||||||
#include "libc/sysv/consts/sig.h"
|
#include "libc/sysv/consts/sig.h"
|
||||||
#include "libc/sysv/errfuns.h"
|
#include "libc/sysv/errfuns.h"
|
||||||
|
#include "libc/thread/thread.h"
|
||||||
#include "libc/time.h"
|
#include "libc/time.h"
|
||||||
#include "libc/x/x.h"
|
#include "libc/x/x.h"
|
||||||
#include "tool/viz/lib/graphic.h"
|
#include "tool/viz/lib/graphic.h"
|
||||||
|
@ -69,6 +70,7 @@ struct timespec magikarp_start_;
|
||||||
|
|
||||||
struct YCbCr {
|
struct YCbCr {
|
||||||
bool yonly;
|
bool yonly;
|
||||||
|
int cpu_count;
|
||||||
int magnums[8][4];
|
int magnums[8][4];
|
||||||
int lighting[6][4];
|
int lighting[6][4];
|
||||||
unsigned char transfer[2][256];
|
unsigned char transfer[2][256];
|
||||||
|
@ -165,6 +167,7 @@ void YCbCrInit(struct YCbCr **ycbcr, bool yonly, int swing, double gamma,
|
||||||
if (!*ycbcr)
|
if (!*ycbcr)
|
||||||
*ycbcr = xcalloc(1, sizeof(struct YCbCr));
|
*ycbcr = xcalloc(1, sizeof(struct YCbCr));
|
||||||
(*ycbcr)->yonly = yonly;
|
(*ycbcr)->yonly = yonly;
|
||||||
|
(*ycbcr)->cpu_count = __get_cpu_count();
|
||||||
bzero((*ycbcr)->magnums, sizeof((*ycbcr)->magnums));
|
bzero((*ycbcr)->magnums, sizeof((*ycbcr)->magnums));
|
||||||
bzero((*ycbcr)->lighting, sizeof((*ycbcr)->lighting));
|
bzero((*ycbcr)->lighting, sizeof((*ycbcr)->lighting));
|
||||||
YCbCrComputeCoefficients(swing, gamma, gamut, illuminant, (*ycbcr)->magnums,
|
YCbCrComputeCoefficients(swing, gamma, gamut, illuminant, (*ycbcr)->magnums,
|
||||||
|
@ -263,14 +266,32 @@ void YCbCrConvert(struct YCbCr *me, long yn, long xn,
|
||||||
const unsigned char Y[restrict yys][yxs], long cys, long cxs,
|
const unsigned char Y[restrict yys][yxs], long cys, long cxs,
|
||||||
unsigned char Cb[restrict cys][cxs],
|
unsigned char Cb[restrict cys][cxs],
|
||||||
unsigned char Cr[restrict cys][cxs]) {
|
unsigned char Cr[restrict cys][cxs]) {
|
||||||
struct timespec ts = timespec_real();
|
struct timespec ts = timespec_mono();
|
||||||
if (!me->yonly) {
|
if (!me->yonly) {
|
||||||
YCbCr2Rgb(yn, xn, RGB, yys, yxs, Y, cys, cxs, Cb, Cr, me->magnums,
|
YCbCr2Rgb(yn, xn, RGB, yys, yxs, Y, cys, cxs, Cb, Cr, me->magnums,
|
||||||
me->lighting, me->transfer[pf10_]);
|
me->lighting, me->transfer[pf10_]);
|
||||||
} else {
|
} else {
|
||||||
Y2Rgb(yn, xn, RGB, yys, yxs, Y, me->magnums, me->transfer[pf10_]);
|
Y2Rgb(yn, xn, RGB, yys, yxs, Y, me->magnums, me->transfer[pf10_]);
|
||||||
}
|
}
|
||||||
ycbcr2rgb_latency_ = timespec_tomicros(timespec_sub(timespec_real(), ts));
|
ycbcr2rgb_latency_ = timespec_tomicros(timespec_sub(timespec_mono(), ts));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct YCbCr2RgbScalerThreadData {
|
||||||
|
long syw, sxw, dyw, dxw, dyn, dxn, syn, sxn;
|
||||||
|
unsigned char *src;
|
||||||
|
unsigned char *dst;
|
||||||
|
int min, max;
|
||||||
|
struct SamplingSolution *cy, *cx;
|
||||||
|
bool sharpen;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void *YCbCr2RgbScalerThread(void *arg) {
|
||||||
|
struct YCbCr2RgbScalerThreadData *data =
|
||||||
|
(struct YCbCr2RgbScalerThreadData *)arg;
|
||||||
|
GyaradosUint8(data->syw, data->sxw, data->src, data->dyw, data->dxw,
|
||||||
|
data->dst, data->dyn, data->dxn, data->syn, data->sxn,
|
||||||
|
data->min, data->max, data->cy, data->cx, data->sharpen);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void YCbCr2RgbScaler(struct YCbCr *me, long dyn, long dxn,
|
void YCbCr2RgbScaler(struct YCbCr *me, long dyn, long dxn,
|
||||||
|
@ -297,7 +318,7 @@ void YCbCr2RgbScaler(struct YCbCr *me, long dyn, long dxn,
|
||||||
Magkern2xY(cys, cxs, Cr, scyn, scxn), HALF(yyn), yxn,
|
Magkern2xY(cys, cxs, Cr, scyn, scxn), HALF(yyn), yxn,
|
||||||
HALF(cyn), scxn, syn / 2, sxn, pry, prx);
|
HALF(cyn), scxn, syn / 2, sxn, pry, prx);
|
||||||
} else {
|
} else {
|
||||||
struct timespec ts = timespec_real();
|
struct timespec ts = timespec_mono();
|
||||||
magikarp_latency_ = timespec_tomicros(timespec_sub(ts, magikarp_start_));
|
magikarp_latency_ = timespec_tomicros(timespec_sub(ts, magikarp_start_));
|
||||||
yry = syn / dyn;
|
yry = syn / dyn;
|
||||||
yrx = sxn / dxn;
|
yrx = sxn / dxn;
|
||||||
|
@ -322,13 +343,83 @@ void YCbCr2RgbScaler(struct YCbCr *me, long dyn, long dxn,
|
||||||
sharpen(1, yys, yxs, (void *)Y, yyn, yxn);
|
sharpen(1, yys, yxs, (void *)Y, yyn, yxn);
|
||||||
if (pf9_)
|
if (pf9_)
|
||||||
unsharp(1, yys, yxs, (void *)Y, yyn, yxn);
|
unsharp(1, yys, yxs, (void *)Y, yyn, yxn);
|
||||||
GyaradosUint8(yys, yxs, Y, yys, yxs, Y, dyn, dxn, syn, sxn, 0, 255,
|
|
||||||
me->luma.cy, me->luma.cx, true);
|
if (me->cpu_count < 6) {
|
||||||
GyaradosUint8(cys, cxs, Cb, cys, cxs, Cb, dyn, dxn, scyn, scxn, 0, 255,
|
GyaradosUint8(yys, yxs, Y, yys, yxs, Y, dyn, dxn, syn, sxn, 0, 255,
|
||||||
me->chroma.cy, me->chroma.cx, false);
|
me->luma.cy, me->luma.cx, true);
|
||||||
GyaradosUint8(cys, cxs, Cr, cys, cxs, Cr, dyn, dxn, scyn, scxn, 0, 255,
|
GyaradosUint8(cys, cxs, Cb, cys, cxs, Cb, dyn, dxn, scyn, scxn, 0, 255,
|
||||||
me->chroma.cy, me->chroma.cx, false);
|
me->chroma.cy, me->chroma.cx, false);
|
||||||
gyarados_latency_ = timespec_tomicros(timespec_sub(timespec_real(), ts));
|
GyaradosUint8(cys, cxs, Cr, cys, cxs, Cr, dyn, dxn, scyn, scxn, 0, 255,
|
||||||
|
me->chroma.cy, me->chroma.cx, false);
|
||||||
|
} else {
|
||||||
|
pthread_t threads[3];
|
||||||
|
struct YCbCr2RgbScalerThreadData thread_data[3];
|
||||||
|
|
||||||
|
// Set up thread data for Y plane.
|
||||||
|
thread_data[0] = (struct YCbCr2RgbScalerThreadData){
|
||||||
|
.syw = yys,
|
||||||
|
.sxw = yxs,
|
||||||
|
.dyw = yys,
|
||||||
|
.dxw = yxs,
|
||||||
|
.dyn = dyn,
|
||||||
|
.dxn = dxn,
|
||||||
|
.syn = syn,
|
||||||
|
.sxn = sxn,
|
||||||
|
.src = (unsigned char *)Y,
|
||||||
|
.dst = (unsigned char *)Y,
|
||||||
|
.min = 0,
|
||||||
|
.max = 255,
|
||||||
|
.cy = me->luma.cy,
|
||||||
|
.cx = me->luma.cx,
|
||||||
|
.sharpen = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up thread data for Cb plane.
|
||||||
|
thread_data[1] = (struct YCbCr2RgbScalerThreadData){
|
||||||
|
.syw = cys,
|
||||||
|
.sxw = cxs,
|
||||||
|
.dyw = cys,
|
||||||
|
.dxw = cxs,
|
||||||
|
.dyn = dyn,
|
||||||
|
.dxn = dxn,
|
||||||
|
.syn = scyn,
|
||||||
|
.sxn = scxn,
|
||||||
|
.src = (unsigned char *)Cb,
|
||||||
|
.dst = (unsigned char *)Cb,
|
||||||
|
.min = 0,
|
||||||
|
.max = 255,
|
||||||
|
.cy = me->chroma.cy,
|
||||||
|
.cx = me->chroma.cx,
|
||||||
|
.sharpen = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up thread data for Cr plane.
|
||||||
|
thread_data[2] = (struct YCbCr2RgbScalerThreadData){
|
||||||
|
.syw = cys,
|
||||||
|
.sxw = cxs,
|
||||||
|
.dyw = cys,
|
||||||
|
.dxw = cxs,
|
||||||
|
.dyn = dyn,
|
||||||
|
.dxn = dxn,
|
||||||
|
.syn = scyn,
|
||||||
|
.sxn = scxn,
|
||||||
|
.src = (unsigned char *)Cr,
|
||||||
|
.dst = (unsigned char *)Cr,
|
||||||
|
.min = 0,
|
||||||
|
.max = 255,
|
||||||
|
.cy = me->chroma.cy,
|
||||||
|
.cx = me->chroma.cx,
|
||||||
|
.sharpen = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dispatch threads.
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
pthread_create(&threads[i], NULL, YCbCr2RgbScalerThread,
|
||||||
|
&thread_data[i]);
|
||||||
|
for (int i = 3; i--;)
|
||||||
|
pthread_join(threads[i], NULL);
|
||||||
|
}
|
||||||
|
gyarados_latency_ = timespec_tomicros(timespec_sub(timespec_mono(), ts));
|
||||||
YCbCrConvert(me, dyn, dxn, RGB, yys, yxs, Y, cys, cxs, Cb, Cr);
|
YCbCrConvert(me, dyn, dxn, RGB, yys, yxs, Y, cys, cxs, Cb, Cr);
|
||||||
INFOF("done");
|
INFOF("done");
|
||||||
}
|
}
|
||||||
|
@ -383,7 +474,7 @@ void *YCbCr2RgbScale(long dyn, long dxn,
|
||||||
CHECK_LE(cyn, cys);
|
CHECK_LE(cyn, cys);
|
||||||
CHECK_LE(cxn, cxs);
|
CHECK_LE(cxn, cxs);
|
||||||
INFOF("magikarp2x");
|
INFOF("magikarp2x");
|
||||||
magikarp_start_ = timespec_real();
|
magikarp_start_ = timespec_mono();
|
||||||
minyys = MAX(ceil(syn), MAX(yyn, ceil(dyn * pry)));
|
minyys = MAX(ceil(syn), MAX(yyn, ceil(dyn * pry)));
|
||||||
minyxs = MAX(ceil(sxn), MAX(yxn, ceil(dxn * prx)));
|
minyxs = MAX(ceil(sxn), MAX(yxn, ceil(dxn * prx)));
|
||||||
mincys = MAX(cyn, ceil(dyn * pry));
|
mincys = MAX(cyn, ceil(dyn * pry));
|
||||||
|
|
|
@ -1130,7 +1130,7 @@ static bool ShouldDraw(void) {
|
||||||
static struct timespec next;
|
static struct timespec next;
|
||||||
if (!isdragging)
|
if (!isdragging)
|
||||||
return true;
|
return true;
|
||||||
now = timespec_real();
|
now = timespec_mono();
|
||||||
if (timespec_cmp(now, next) > 0 && !HasPendingInput()) {
|
if (timespec_cmp(now, next) > 0 && !HasPendingInput()) {
|
||||||
next = timespec_add(now, timespec_frommicros(1. / 24 * 1e6));
|
next = timespec_add(now, timespec_frommicros(1. / 24 * 1e6));
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -35,14 +35,14 @@ void *worker(void *arg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void test(int n) {
|
void test(int n) {
|
||||||
struct timespec start = timespec_real();
|
struct timespec start = timespec_mono();
|
||||||
pthread_t *th = malloc(sizeof(pthread_t) * n);
|
pthread_t *th = malloc(sizeof(pthread_t) * n);
|
||||||
for (int i = 0; i < n; ++i)
|
for (int i = 0; i < n; ++i)
|
||||||
pthread_create(th + i, 0, worker, 0);
|
pthread_create(th + i, 0, worker, 0);
|
||||||
for (int i = 0; i < n; ++i)
|
for (int i = 0; i < n; ++i)
|
||||||
pthread_join(th[i], 0);
|
pthread_join(th[i], 0);
|
||||||
free(th);
|
free(th);
|
||||||
struct timespec end = timespec_real();
|
struct timespec end = timespec_mono();
|
||||||
printf("%2d threads * %d allocs = %ld us\n", n, ALLOCATIONS,
|
printf("%2d threads * %d allocs = %ld us\n", n, ALLOCATIONS,
|
||||||
timespec_tomicros(timespec_sub(end, start)));
|
timespec_tomicros(timespec_sub(end, start)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -335,10 +335,11 @@ static long Index(long y, long x) {
|
||||||
static void PreventBufferbloat(void) {
|
static void PreventBufferbloat(void) {
|
||||||
struct timespec now, rate;
|
struct timespec now, rate;
|
||||||
static struct timespec last;
|
static struct timespec last;
|
||||||
now = timespec_real();
|
now = timespec_mono();
|
||||||
rate = timespec_frommicros(1. / fps * 1e6);
|
rate = timespec_frommicros(1. / fps * 1e6);
|
||||||
if (timespec_cmp(timespec_sub(now, last), rate) < 0) {
|
if (timespec_cmp(timespec_sub(now, last), rate) < 0) {
|
||||||
timespec_sleep(CLOCK_REALTIME, timespec_sub(rate, timespec_sub(now, last)));
|
timespec_sleep(CLOCK_MONOTONIC,
|
||||||
|
timespec_sub(rate, timespec_sub(now, last)));
|
||||||
}
|
}
|
||||||
last = now;
|
last = now;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
#include "libc/calls/ucontext.h"
|
#include "libc/calls/ucontext.h"
|
||||||
#include "libc/ctype.h"
|
#include "libc/ctype.h"
|
||||||
#include "libc/cxxabi.h"
|
#include "libc/cxxabi.h"
|
||||||
|
#include "libc/dce.h"
|
||||||
#include "libc/errno.h"
|
#include "libc/errno.h"
|
||||||
#include "libc/fmt/conv.h"
|
#include "libc/fmt/conv.h"
|
||||||
#include "libc/fmt/itoa.h"
|
#include "libc/fmt/itoa.h"
|
||||||
|
@ -56,7 +57,9 @@
|
||||||
#include "libc/nexgen32e/bench.h"
|
#include "libc/nexgen32e/bench.h"
|
||||||
#include "libc/nexgen32e/x86feature.h"
|
#include "libc/nexgen32e/x86feature.h"
|
||||||
#include "libc/nt/console.h"
|
#include "libc/nt/console.h"
|
||||||
|
#include "libc/nt/enum/threadpriority.h"
|
||||||
#include "libc/nt/runtime.h"
|
#include "libc/nt/runtime.h"
|
||||||
|
#include "libc/nt/thread.h"
|
||||||
#include "libc/runtime/runtime.h"
|
#include "libc/runtime/runtime.h"
|
||||||
#include "libc/sock/sock.h"
|
#include "libc/sock/sock.h"
|
||||||
#include "libc/sock/struct/pollfd.h"
|
#include "libc/sock/struct/pollfd.h"
|
||||||
|
@ -1398,6 +1401,10 @@ static void TryToOpenFrameBuffer(void) {
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
sigset_t wut;
|
sigset_t wut;
|
||||||
ShowCrashReports();
|
ShowCrashReports();
|
||||||
|
#ifdef __x86_64__
|
||||||
|
if (IsWindows())
|
||||||
|
SetThreadPriority(GetCurrentThread(), kNtThreadPriorityHighest);
|
||||||
|
#endif
|
||||||
gamma_ = 2.4;
|
gamma_ = 2.4;
|
||||||
volscale_ = 1.f;
|
volscale_ = 1.f;
|
||||||
dither_ = true;
|
dither_ = true;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue