libsoundio/test/latency.c
Andrew Kelley ee7c0d3e11 convert source code to pure C
List<T> is now a really ugly macro.

Added a workaround for jack.h not putting `void` in function
prototypes for functions that take no arguments. I made upstream
pull requests to jack1 and jack2 but I don't have high hopes
about them getting merged.

I removed the lock-free atomic asserts. clang reports
non-lock-free atomics when in fact it does have lock-free
atomics. I inspected the generated code for gcc and clang
for fetch_add, load, and store, on x86_64 and armhf, and
it's all lock free.

Closes #45.
2015-11-10 02:24:18 -07:00

252 lines
8.8 KiB
C

/*
* Copyright (c) 2015 Andrew Kelley
*
* This file is part of libsoundio, which is MIT licensed.
* See http://opensource.org/licenses/MIT
*/
#include "soundio_private.h"
#include "os.h"
#include "util.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
static int usage(char *exe) {
fprintf(stderr, "Usage: %s [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi] [--latency seconds]\n", exe);
return 1;
}
static void write_sample_s16ne(char *ptr, double sample) {
int16_t *buf = (int16_t *)ptr;
double range = (double)INT16_MAX - (double)INT16_MIN;
double val = sample * range / 2.0;
*buf = val;
}
static void write_sample_s32ne(char *ptr, double sample) {
int32_t *buf = (int32_t *)ptr;
double range = (double)INT32_MAX - (double)INT32_MIN;
double val = sample * range / 2.0;
*buf = val;
}
static void write_sample_float32ne(char *ptr, double sample) {
float *buf = (float *)ptr;
*buf = sample;
}
static void write_sample_float64ne(char *ptr, double sample) {
double *buf = (double *)ptr;
*buf = sample;
}
static void (*write_sample)(char *ptr, double sample);
static int frames_until_pulse = 0;
static int pulse_frames_left = -1;
static const double PI = 3.14159265358979323846264338328;
static double seconds_offset = 0.0;
static struct SoundIoRingBuffer pulse_rb;
static void write_time(struct SoundIoOutStream *outstream, double extra) {
double latency;
int err;
if ((err = soundio_outstream_get_latency(outstream, &latency))) {
soundio_panic("getting latency: %s", soundio_strerror(err));
}
double now = soundio_os_get_time();
double audible_time = now + latency + extra;
double *write_ptr = (double *)soundio_ring_buffer_write_ptr(&pulse_rb);
*write_ptr = audible_time;
soundio_ring_buffer_advance_write_ptr(&pulse_rb, sizeof(double));
}
static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) {
double float_sample_rate = outstream->sample_rate;
double seconds_per_frame = 1.0f / float_sample_rate;
struct SoundIoChannelArea *areas;
int err;
int frames_left = frame_count_max;
while (frames_left > 0) {
int frame_count = frames_left;
if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count)))
soundio_panic("begin write: %s", soundio_strerror(err));
if (!frame_count)
break;
const struct SoundIoChannelLayout *layout = &outstream->layout;
double pitch = 440.0;
double radians_per_second = pitch * 2.0 * PI;
for (int frame = 0; frame < frame_count; frame += 1) {
double sample;
if (frames_until_pulse <= 0) {
if (pulse_frames_left == -1) {
pulse_frames_left = 0.25 * float_sample_rate;
write_time(outstream, seconds_per_frame * frame); // announce beep start
}
if (pulse_frames_left > 0) {
pulse_frames_left -= 1;
sample = sinf((seconds_offset + frame * seconds_per_frame) * radians_per_second);
} else {
frames_until_pulse = (0.5 + (rand() / (double)RAND_MAX) * 2.0) * float_sample_rate;
pulse_frames_left = -1;
sample = 0.0;
write_time(outstream, seconds_per_frame * frame); // announce beep end
}
} else {
frames_until_pulse -= 1;
sample = 0.0;
}
for (int channel = 0; channel < layout->channel_count; channel += 1) {
write_sample(areas[channel].ptr, sample);
areas[channel].ptr += areas[channel].step;
}
}
seconds_offset += seconds_per_frame * frame_count;
if ((err = soundio_outstream_end_write(outstream)))
soundio_panic("end write: %s", soundio_strerror(err));
frames_left -= frame_count;
}
}
static void underflow_callback(struct SoundIoOutStream *outstream) {
soundio_panic("underflow\n");
}
int main(int argc, char **argv) {
char *exe = argv[0];
enum SoundIoBackend backend = SoundIoBackendNone;
double software_latency = 0.0;
for (int i = 1; i < argc; i += 1) {
char *arg = argv[i];
if (arg[0] == '-' && arg[1] == '-') {
i += 1;
if (i >= argc) {
return usage(exe);
} else if (strcmp(arg, "--backend") == 0) {
if (strcmp("dummy", argv[i]) == 0) {
backend = SoundIoBackendDummy;
} else if (strcmp("alsa", argv[i]) == 0) {
backend = SoundIoBackendAlsa;
} else if (strcmp("pulseaudio", argv[i]) == 0) {
backend = SoundIoBackendPulseAudio;
} else if (strcmp("jack", argv[i]) == 0) {
backend = SoundIoBackendJack;
} else if (strcmp("coreaudio", argv[i]) == 0) {
backend = SoundIoBackendCoreAudio;
} else if (strcmp("wasapi", argv[i]) == 0) {
backend = SoundIoBackendWasapi;
} else {
fprintf(stderr, "Invalid backend: %s\n", argv[i]);
return 1;
}
} else if (strcmp(arg, "--latency") == 0) {
software_latency = atof(argv[i]);
} else {
return usage(exe);
}
} else {
return usage(exe);
}
}
struct SoundIo *soundio;
if (!(soundio = soundio_create()))
soundio_panic("out of memory");
int err = (backend == SoundIoBackendNone) ?
soundio_connect(soundio) : soundio_connect_backend(soundio, backend);
if (err)
soundio_panic("error connecting: %s", soundio_strerror(err));
soundio_flush_events(soundio);
int default_out_device_index = soundio_default_output_device_index(soundio);
if (default_out_device_index < 0)
soundio_panic("no output device found");
struct SoundIoDevice *device = soundio_get_output_device(soundio, default_out_device_index);
if (!device)
soundio_panic("out of memory");
fprintf(stderr, "Output device: %s\n", device->name);
struct SoundIoOutStream *outstream = soundio_outstream_create(device);
outstream->format = SoundIoFormatFloat32NE;
outstream->write_callback = write_callback;
outstream->underflow_callback = underflow_callback;
outstream->software_latency = software_latency;
if (soundio_device_supports_format(device, SoundIoFormatFloat32NE)) {
outstream->format = SoundIoFormatFloat32NE;
write_sample = write_sample_float32ne;
} else if (soundio_device_supports_format(device, SoundIoFormatFloat64NE)) {
outstream->format = SoundIoFormatFloat64NE;
write_sample = write_sample_float64ne;
} else if (soundio_device_supports_format(device, SoundIoFormatS32NE)) {
outstream->format = SoundIoFormatS32NE;
write_sample = write_sample_s32ne;
} else if (soundio_device_supports_format(device, SoundIoFormatS16NE)) {
outstream->format = SoundIoFormatS16NE;
write_sample = write_sample_s16ne;
} else {
soundio_panic("No suitable device format available.\n");
}
if ((err = soundio_ring_buffer_init(&pulse_rb, 1024)))
soundio_panic("ring buffer init: %s", soundio_strerror(err));
if ((err = soundio_outstream_open(outstream)))
soundio_panic("unable to open device: %s", soundio_strerror(err));
if (outstream->layout_error)
fprintf(stderr, "unable to set channel layout: %s\n", soundio_strerror(outstream->layout_error));
if ((err = soundio_outstream_start(outstream)))
soundio_panic("unable to start device: %s", soundio_strerror(err));
bool beep_on = true;
int count = 0;
for (;;) {
int fill_count = soundio_ring_buffer_fill_count(&pulse_rb);
if (fill_count >= (int)sizeof(double)) {
double *read_ptr = (double *)soundio_ring_buffer_read_ptr(&pulse_rb);
double audible_time = *read_ptr;
while (audible_time > soundio_os_get_time()) {
// Burn the CPU while we wait for our precisely timed event.
}
if (beep_on) {
fprintf(stderr, "BEEP %d start\n", count);
} else {
fprintf(stderr, "BEEP %d end\n", count++);
}
fflush(stderr);
double off_by = soundio_os_get_time() - audible_time;
if (off_by > 0.0001)
fprintf(stderr, "off by %f\n", off_by);
beep_on = !beep_on;
soundio_ring_buffer_advance_read_ptr(&pulse_rb, sizeof(double));
}
}
soundio_outstream_destroy(outstream);
soundio_device_unref(device);
soundio_destroy(soundio);
return 0;
}