Add debug log to cosmoaudio and add examples

This commit is contained in:
Justine Tunney 2024-09-11 02:14:38 -07:00
parent a5c0189bf6
commit 0f3457c172
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
38 changed files with 504 additions and 371 deletions

View file

@ -18,7 +18,9 @@
*/
#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"
@ -201,7 +203,7 @@ static void *cosmoaudio_dlopen(const char *name) {
return 0;
}
static void cosmoaudio_setup(void) {
static void cosmoaudio_setup_impl(void) {
void *handle;
if (IsOpenbsd())
return; // no dlopen support yet
@ -239,6 +241,12 @@ WeAreGood:
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);
}
@ -249,10 +257,13 @@ COSMOAUDIO_ABI int cosmoaudio_open(
char sbuf[32];
char dbuf[256];
cosmoaudio_init();
if (g_audio.open)
if (g_audio.open) {
BLOCK_SIGNALS;
status = g_audio.open(out_ca, options);
else
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),
@ -263,10 +274,13 @@ COSMOAUDIO_ABI int cosmoaudio_open(
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio *ca) {
int status;
char sbuf[32];
if (g_audio.close)
if (g_audio.close) {
BLOCK_SIGNALS;
status = g_audio.close(ca);
else
ALLOW_SIGNALS;
} else {
status = COSMOAUDIO_ELINK;
}
STRACE("cosmoaudio_close(%p) → %s", ca,
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
return status;
@ -276,10 +290,13 @@ COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio *ca, const float *data,
int frames) {
int status;
char sbuf[32];
if (g_audio.write)
if (g_audio.write) {
BLOCK_SIGNALS;
status = g_audio.write(ca, data, frames);
else
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));
@ -290,10 +307,13 @@ COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio *ca, float *data,
int frames) {
int status;
char sbuf[32];
if (g_audio.read)
if (g_audio.read) {
BLOCK_SIGNALS;
status = g_audio.read(ca, data, frames);
else
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));
@ -303,10 +323,13 @@ COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio *ca, float *data,
COSMOAUDIO_ABI int cosmoaudio_flush(struct CosmoAudio *ca) {
int status;
char sbuf[32];
if (g_audio.flush)
if (g_audio.flush) {
BLOCK_SIGNALS;
status = g_audio.flush(ca);
else
ALLOW_SIGNALS;
} else {
status = COSMOAUDIO_ELINK;
}
DATATRACE("cosmoaudio_flush(%p) → %s", ca,
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
return status;
@ -318,10 +341,13 @@ COSMOAUDIO_ABI int cosmoaudio_poll(struct CosmoAudio *ca,
int status;
char sbuf[32];
char fbuf[2][20];
if (g_audio.poll)
if (g_audio.poll) {
BLOCK_SIGNALS;
status = g_audio.poll(ca, in_out_readFrames, in_out_writeFrames);
else
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),

View file

@ -19,25 +19,19 @@
#include <stdlib.h>
#include <string.h>
#define MA_STATIC
#define MA_DEBUG_OUTPUT
#define MA_DR_MP3_NO_STDIO
#define MA_NO_DECODING
#define MA_NO_ENCODING
#define MA_NO_ENGINE
#define MA_NO_GENERATION
#define MA_NO_NODE_GRAPH
#define MA_NO_RESOURCE_MANAGER
#ifdef NDEBUG
#define MA_DR_MP3_NO_STDIO
#endif
#define MA_STATIC
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
#ifndef NDEBUG
#define LOG(...) fprintf(stderr, __VA_ARGS__)
#else
#define LOG(...) (void)0
#endif
struct CosmoAudio {
enum CosmoAudioDeviceType deviceType;
ma_uint32 outputBufferFrames;
@ -45,14 +39,16 @@ struct CosmoAudio {
int sampleRate;
int channels;
int isLeft;
ma_context context;
ma_device device;
ma_pcm_rb output;
ma_pcm_rb input;
ma_event event;
ma_log log;
};
static int read_ring_buffer(ma_pcm_rb* rb, float* pOutput, ma_uint32 frameCount,
ma_uint32 channels) {
static int read_ring_buffer(ma_log* log, ma_pcm_rb* rb, float* pOutput,
ma_uint32 frameCount, ma_uint32 channels) {
ma_result result;
ma_uint32 framesRead;
ma_uint32 framesToRead;
@ -61,7 +57,9 @@ static int read_ring_buffer(ma_pcm_rb* rb, float* pOutput, ma_uint32 frameCount,
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));
ma_log_postf(log, MA_LOG_LEVEL_WARNING,
"ma_pcm_rb_acquire_read failed: %s\n",
ma_result_description(result));
return COSMOAUDIO_ERROR;
}
if (!framesToRead)
@ -74,14 +72,16 @@ static int read_ring_buffer(ma_pcm_rb* rb, float* pOutput, ma_uint32 frameCount,
framesRead += framesToRead;
break;
}
LOG("ma_pcm_rb_commit_read failed: %s\n", ma_result_description(result));
ma_log_postf(log, MA_LOG_LEVEL_WARNING,
"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,
static int write_ring_buffer(ma_log* log, ma_pcm_rb* rb, const float* pInput,
ma_uint32 frameCount, ma_uint32 channels) {
ma_result result;
ma_uint32 framesWritten;
@ -92,8 +92,9 @@ static int write_ring_buffer(ma_pcm_rb* rb, const float* pInput,
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));
ma_log_postf(log, MA_LOG_LEVEL_WARNING,
"ma_pcm_rb_acquire_write failed: %s\n",
ma_result_description(result));
return COSMOAUDIO_ERROR;
}
if (!framesToWrite)
@ -106,7 +107,9 @@ static int write_ring_buffer(ma_pcm_rb* rb, const float* pInput,
framesWritten += framesToWrite;
break;
}
LOG("ma_pcm_rb_commit_write failed: %s\n", ma_result_description(result));
ma_log_postf(log, MA_LOG_LEVEL_WARNING,
"ma_pcm_rb_commit_write failed: %s\n",
ma_result_description(result));
return COSMOAUDIO_ERROR;
}
}
@ -126,8 +129,8 @@ static void data_callback_f32(ma_device* pDevice, float* pOutput,
// —Quoth miniaudio documentation § 16.1. Low Level API
//
if (ca->isLeft) {
int framesCopied =
read_ring_buffer(&ca->output, pOutput, frameCount, ca->channels);
int framesCopied = read_ring_buffer(&ca->log, &ca->output, pOutput,
frameCount, ca->channels);
if (framesCopied < (int)frameCount)
ca->isLeft = 0;
} else {
@ -140,13 +143,14 @@ static void data_callback_f32(ma_device* pDevice, float* pOutput,
frameOffset = frameCount - availableFrames;
frameCount = availableFrames;
}
read_ring_buffer(&ca->output, pOutput + frameOffset * ca->channels,
frameCount, ca->channels);
read_ring_buffer(&ca->log, &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);
write_ring_buffer(&ca->log, &ca->input, pInput, frameCount, ca->channels);
ma_event_signal(&ca->event);
}
@ -211,6 +215,25 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
return COSMOAUDIO_ERROR;
}
// Create audio log.
if (ma_log_init(NULL, &ca->log) != MA_SUCCESS) {
ma_event_uninit(&ca->event);
free(ca);
return COSMOAUDIO_ERROR;
}
if (!options->debugLog)
ca->log.callbackCount = 0;
// Create audio context.
ma_context_config contextConfig = ma_context_config_init();
contextConfig.pLog = &ca->log;
if (ma_context_init(NULL, 0, &contextConfig, &ca->context) != MA_SUCCESS) {
ma_event_uninit(&ca->event);
ma_log_uninit(&ca->log);
free(ca);
return COSMOAUDIO_ERROR;
}
// Initialize device.
ma_result result;
ma_device_config deviceConfig;
@ -227,9 +250,11 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
}
deviceConfig.dataCallback = data_callback;
deviceConfig.pUserData = ca;
result = ma_device_init(NULL, &deviceConfig, &ca->device);
result = ma_device_init(&ca->context, &deviceConfig, &ca->device);
if (result != MA_SUCCESS) {
ma_context_uninit(&ca->context);
ma_event_uninit(&ca->event);
ma_log_uninit(&ca->log);
free(ca);
return COSMOAUDIO_ERROR;
}
@ -248,7 +273,9 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
NULL, NULL, &ca->output);
if (result != MA_SUCCESS) {
ma_device_uninit(&ca->device);
ma_context_uninit(&ca->context);
ma_event_uninit(&ca->event);
ma_log_uninit(&ca->log);
free(ca);
return COSMOAUDIO_ERROR;
}
@ -268,10 +295,12 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
result = ma_pcm_rb_init(ma_format_f32, ca->channels, ca->inputBufferFrames,
NULL, NULL, &ca->input);
if (result != MA_SUCCESS) {
ma_device_uninit(&ca->device);
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
ma_pcm_rb_uninit(&ca->output);
ma_device_uninit(&ca->device);
ma_context_uninit(&ca->context);
ma_event_uninit(&ca->event);
ma_log_uninit(&ca->log);
free(ca);
return COSMOAUDIO_ERROR;
}
@ -280,12 +309,14 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
// Start audio playback.
if (ma_device_start(&ca->device) != MA_SUCCESS) {
ma_device_uninit(&ca->device);
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
ma_pcm_rb_uninit(&ca->output);
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
ma_pcm_rb_uninit(&ca->input);
ma_device_uninit(&ca->device);
ma_context_uninit(&ca->context);
ma_event_uninit(&ca->event);
ma_log_uninit(&ca->log);
free(ca);
return COSMOAUDIO_ERROR;
}
@ -311,12 +342,14 @@ COSMOAUDIO_ABI int cosmoaudio_open( //
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio* ca) {
if (!ca)
return COSMOAUDIO_EINVAL;
ma_device_uninit(&ca->device); // do this first
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
ma_pcm_rb_uninit(&ca->output);
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
ma_pcm_rb_uninit(&ca->input);
ma_device_uninit(&ca->device);
ma_context_uninit(&ca->context);
ma_event_uninit(&ca->event);
ma_log_uninit(&ca->log);
free(ca);
return COSMOAUDIO_SUCCESS;
}
@ -330,6 +363,12 @@ COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio* ca) {
* repeatedly called at a regular time interval. The caller should
* have its own sleep loop for this purpose.
*
* This function never blocks. Programs that don't have their own timer
* can use cosmoaudio_poll() to wait until audio may be written.
*
* For any given CosmoAudio object, it's assumed that only a single
* thread will call this function.
*
* @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
@ -351,17 +390,23 @@ COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio* ca, const float* data,
return 0;
if (!data)
return COSMOAUDIO_EINVAL;
return write_ring_buffer(&ca->output, data, frames, ca->channels);
return write_ring_buffer(&ca->log, &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.
* played back on the audio device. This has tolerence for a certain
* amount of buffering (based on the `bufferFrames` parameter passed to
* cosmoaudio_open(), which by default assumes this function will be
* called at at a regular time interval.
*
* This function never blocks. Programs that don't have their own timer
* can use cosmoaudio_poll() to wait until audio may be read.
*
* For any given CosmoAudio object, it's assumed that only a single
* thread will call this function.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @param data is pointer to raw audio samples, expected to be in the range
@ -382,11 +427,16 @@ COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio* ca, float* data,
return 0;
if (!data)
return COSMOAUDIO_EINVAL;
return read_ring_buffer(&ca->input, data, frames, ca->channels);
return read_ring_buffer(&ca->log, &ca->input, data, frames, ca->channels);
}
/**
* Waits for read and/or write to become possible.
* Waits until it's possible to read/write audio.
*
* This function is uninterruptible. All signals are masked throughout
* the duration of time this function may block, including cancelation
* signals, because this is not a cancelation point. Cosmopolitan Libc
* applies this masking in its dlopen wrapper.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @param in_out_readFrames if non-NULL specifies how many frames of
@ -447,6 +497,11 @@ COSMOAUDIO_ABI int cosmoaudio_poll(struct CosmoAudio* ca,
*
* This function is only valid to call in playback or duplex mode.
*
* This function is uninterruptible. All signals are masked throughout
* the duration of time this function may block, including cancelation
* signals, because this is not a cancelation point. Cosmopolitan Libc
* applies this masking in its dlopen wrapper.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @return 0 on success, or negative error code on failure
*/

Binary file not shown.

View file

@ -59,6 +59,9 @@ struct CosmoAudioOpenOptions {
// sample for each channel. Set to 0 for default. If this is less than
// the device period size times two, it'll be increased to that value.
int bufferFrames;
// Enables debug logging if non-zero.
int debugLog;
};
COSMOAUDIO_API int cosmoaudio_version(void) COSMOAUDIO_ABI;

View file

@ -53,7 +53,7 @@ int main() {
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;
buf[f * CHANNELS + c] = s * .3f;
}
status = cosmoaudio_write(ca, buf, frames);
if (status != frames) {

View file

@ -90,6 +90,13 @@ const char *cosmoaudio_describe_open_options(
gotsome = true;
}
if (options->debugLog) {
if (gotsome)
append(", ");
append(".debugLog=%d", options->debugLog);
gotsome = true;
}
if (options->sizeofThis) {
if (gotsome)
append(", ");