105 lines
4.4 KiB
C
105 lines
4.4 KiB
C
|
|
#define _POSIX_C_SOURCE 200809L
|
||
|
|
#include "wave_internal.h"
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <float.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
struct xtc_recorder {
|
||
|
|
float *ring;
|
||
|
|
size_t capacity;
|
||
|
|
uint64_t write_position, read_position, written;
|
||
|
|
unsigned int error, rate;
|
||
|
|
FILE *file;
|
||
|
|
char *path, *temporary;
|
||
|
|
int finished;
|
||
|
|
};
|
||
|
|
xtc_result xtc_recorder_create(const char *path, unsigned int rate, size_t capacity, xtc_recorder **out)
|
||
|
|
{
|
||
|
|
xtc_recorder *r;
|
||
|
|
size_t length = 0;
|
||
|
|
int fd;
|
||
|
|
if (!out) return XTC_ERR_INVALID_ARGUMENT;
|
||
|
|
*out = NULL;
|
||
|
|
if (!path || !rate || rate > 768000 || !capacity || capacity > 2097152U) return XTC_ERR_INVALID_ARGUMENT;
|
||
|
|
while (length < 4096 && path[length]) ++length;
|
||
|
|
if (!length || length == 4096) return XTC_ERR_INVALID_ARGUMENT;
|
||
|
|
if (sizeof(float) != 4 || FLT_RADIX != 2 || FLT_MANT_DIG != 24 || FLT_MAX_EXP != 128) return XTC_ERR_UNSUPPORTED;
|
||
|
|
if (!__atomic_always_lock_free(sizeof(uint64_t), 0) ||
|
||
|
|
!__atomic_always_lock_free(sizeof(unsigned int), 0)) return XTC_ERR_UNSUPPORTED;
|
||
|
|
r = calloc(1, sizeof(*r));
|
||
|
|
if (!r) return XTC_ERR_OUT_OF_MEMORY;
|
||
|
|
r->capacity = capacity; r->rate = rate;
|
||
|
|
r->ring = malloc(capacity * sizeof(float));
|
||
|
|
r->path = malloc(length + 1); r->temporary = calloc(length + 12, 1);
|
||
|
|
if (!r->ring || !r->path || !r->temporary) { xtc_recorder_destroy(r); return XTC_ERR_OUT_OF_MEMORY; }
|
||
|
|
memcpy(r->path, path, length + 1);
|
||
|
|
memcpy(r->temporary, path, length); memcpy(r->temporary + length, ".tmp.XXXXXX", 12);
|
||
|
|
fd = mkstemp(r->temporary);
|
||
|
|
if (fd < 0) { xtc_recorder_destroy(r); return XTC_ERR_IO; }
|
||
|
|
r->file = fdopen(fd, "w+b");
|
||
|
|
if (!r->file) { close(fd); xtc_recorder_destroy(r); return XTC_ERR_IO; }
|
||
|
|
if (!xtc_wave_header(r->file, rate, 0)) { xtc_recorder_destroy(r); return XTC_ERR_IO; }
|
||
|
|
*out = r; return XTC_OK;
|
||
|
|
}
|
||
|
|
void xtc_recorder_push(xtc_recorder *r, const float *input, size_t frames)
|
||
|
|
{
|
||
|
|
uint64_t write, read;
|
||
|
|
size_t i;
|
||
|
|
if (!r || !frames || __atomic_load_n(&r->error, __ATOMIC_ACQUIRE)) return;
|
||
|
|
write = __atomic_load_n(&r->write_position, __ATOMIC_RELAXED);
|
||
|
|
read = __atomic_load_n(&r->read_position, __ATOMIC_ACQUIRE);
|
||
|
|
if (!input || frames > r->capacity || frames > r->capacity - (size_t)(write - read) ||
|
||
|
|
write > UINT64_C(1073741814) - frames) {
|
||
|
|
__atomic_store_n(&r->error, (unsigned int)(input ? XTC_ERR_CAPACITY : XTC_ERR_IO), __ATOMIC_RELEASE);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
for (i = 0; i < frames; ++i) r->ring[(size_t)((write + i) % r->capacity)] = input[i];
|
||
|
|
__atomic_store_n(&r->write_position, write + frames, __ATOMIC_RELEASE);
|
||
|
|
}
|
||
|
|
xtc_result xtc_recorder_drain(xtc_recorder *r)
|
||
|
|
{
|
||
|
|
uint64_t read, write;
|
||
|
|
if (!r || !r->file || r->finished) return XTC_ERR_INVALID_STATE;
|
||
|
|
read = __atomic_load_n(&r->read_position, __ATOMIC_RELAXED);
|
||
|
|
write = __atomic_load_n(&r->write_position, __ATOMIC_ACQUIRE);
|
||
|
|
while (read < write) {
|
||
|
|
size_t offset = (size_t)(read % r->capacity);
|
||
|
|
size_t count = r->capacity - offset;
|
||
|
|
if (count > write - read) count = (size_t)(write - read);
|
||
|
|
if (!xtc_wave_write(r->file, r->ring + offset, count)) {
|
||
|
|
__atomic_store_n(&r->error, (unsigned int)XTC_ERR_IO, __ATOMIC_RELEASE);
|
||
|
|
return XTC_ERR_IO;
|
||
|
|
}
|
||
|
|
read += count; r->written += count;
|
||
|
|
__atomic_store_n(&r->read_position, read, __ATOMIC_RELEASE);
|
||
|
|
}
|
||
|
|
return (xtc_result)__atomic_load_n(&r->error, __ATOMIC_ACQUIRE);
|
||
|
|
}
|
||
|
|
xtc_result xtc_recorder_status(const xtc_recorder *r, uint64_t *frames)
|
||
|
|
{
|
||
|
|
if (!r || !frames) return XTC_ERR_INVALID_ARGUMENT;
|
||
|
|
*frames = __atomic_load_n(&r->write_position, __ATOMIC_ACQUIRE);
|
||
|
|
return (xtc_result)__atomic_load_n(&r->error, __ATOMIC_ACQUIRE);
|
||
|
|
}
|
||
|
|
xtc_result xtc_recorder_finish(xtc_recorder *r)
|
||
|
|
{
|
||
|
|
xtc_result result;
|
||
|
|
int failed = 0;
|
||
|
|
if (!r || !r->file || r->finished) return XTC_ERR_INVALID_STATE;
|
||
|
|
result = xtc_recorder_drain(r);
|
||
|
|
if (result == XTC_ERR_IO) failed = 1;
|
||
|
|
if (fseek(r->file, 0, SEEK_SET) || !xtc_wave_header(r->file, r->rate, (uint32_t)r->written) ||
|
||
|
|
fflush(r->file) || fsync(fileno(r->file))) failed = 1;
|
||
|
|
if (fclose(r->file)) failed = 1;
|
||
|
|
r->file = NULL;
|
||
|
|
if (!failed && rename(r->temporary, r->path)) failed = 1;
|
||
|
|
r->finished = 1;
|
||
|
|
return failed ? XTC_ERR_IO : result;
|
||
|
|
}
|
||
|
|
void xtc_recorder_destroy(xtc_recorder *r)
|
||
|
|
{
|
||
|
|
if (!r) return;
|
||
|
|
if (r->file) fclose(r->file);
|
||
|
|
if (r->temporary) unlink(r->temporary);
|
||
|
|
free(r->temporary); free(r->path); free(r->ring); free(r);
|
||
|
|
}
|