108 lines
5.3 KiB
C
108 lines
5.3 KiB
C
|
|
#define _POSIX_C_SOURCE 200809L
|
||
|
|
#include "xtc/xtc_audio.h"
|
||
|
|
#include <assert.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <signal.h>
|
||
|
|
#include <sys/resource.h>
|
||
|
|
#include <sys/stat.h>
|
||
|
|
#include <sys/wait.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
static void file(const char *path, const char *text)
|
||
|
|
{ FILE *f = fopen(path, "wb"); assert(f); assert(fputs(text, f) >= 0 && fclose(f) == 0); }
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
char dir[] = "/tmp/xtc-recording-XXXXXX", take[256], recovered[256], retained[4096], song[256];
|
||
|
|
xtc_recorder *r = NULL;
|
||
|
|
float samples[4096];
|
||
|
|
struct stat info;
|
||
|
|
uint64_t position;
|
||
|
|
size_t i;
|
||
|
|
assert(mkdtemp(dir));
|
||
|
|
snprintf(take, sizeof(take), "%s/take.wav", dir);
|
||
|
|
snprintf(recovered, sizeof(recovered), "%s/recovered.wav", dir);
|
||
|
|
snprintf(song, sizeof(song), "%s/song.xtc", dir);
|
||
|
|
for (i = 0; i < 4096; ++i) samples[i] = 0.25f;
|
||
|
|
file(take, "existing");
|
||
|
|
assert(xtc_recorder_create(take, 8000, 4096, &r) == XTC_ERR_IO && !r);
|
||
|
|
assert(unlink(take) == 0 && symlink("missing", take) == 0);
|
||
|
|
assert(xtc_recorder_create(take, 8000, 4096, &r) == XTC_ERR_IO && !r);
|
||
|
|
assert(unlink(take) == 0);
|
||
|
|
assert(xtc_recorder_create("/missing/xtc/take.wav", 8000, 4096, &r) == XTC_ERR_IO && !r);
|
||
|
|
/* Destination appears after creation: no-clobber publication must preserve it. */
|
||
|
|
assert(xtc_recorder_create(take, 8000, 4096, &r) == XTC_OK);
|
||
|
|
xtc_recorder_push(r, samples, 4096);
|
||
|
|
file(take, "racing writer");
|
||
|
|
assert(xtc_recorder_finish(r) == XTC_ERR_IO);
|
||
|
|
snprintf(retained, sizeof(retained), "%s", xtc_recorder_recovery_path(r));
|
||
|
|
xtc_recorder_destroy(r);
|
||
|
|
assert(stat(take, &info) == 0 && info.st_size == 13);
|
||
|
|
assert(xtc_recorder_recover(retained, take) == XTC_ERR_IO);
|
||
|
|
assert(xtc_recorder_recover(retained, recovered) == XTC_OK);
|
||
|
|
assert(stat(recovered, &info) == 0 && info.st_size == 44 + 4096 * 4);
|
||
|
|
assert(unlink(retained) == 0 && unlink(recovered) == 0 && unlink(take) == 0);
|
||
|
|
/* Last-resort destruction drains accepted samples after producer quiescence. */
|
||
|
|
assert(xtc_recorder_create(take, 8000, 4096, &r) == XTC_OK);
|
||
|
|
xtc_recorder_push(r, samples, 4096); xtc_recorder_destroy(r);
|
||
|
|
assert(stat(take, &info) == 0 && info.st_size == 44 + 4096 * 4);
|
||
|
|
assert(unlink(take) == 0);
|
||
|
|
/* Thirty minutes of virtual 8 kHz audio, repeated ring wrap/drain, no sleep. */
|
||
|
|
assert(xtc_recorder_create(take, 8000, 4096, &r) == XTC_OK);
|
||
|
|
for (position = 0; position < 14400000;) {
|
||
|
|
size_t count = 14400000 - position > 4096 ? 4096 : (size_t)(14400000 - position);
|
||
|
|
xtc_recorder_push(r, samples, count);
|
||
|
|
assert(xtc_recorder_drain(r) == XTC_OK);
|
||
|
|
position += count;
|
||
|
|
}
|
||
|
|
assert(xtc_recorder_finish(r) == XTC_OK); xtc_recorder_destroy(r);
|
||
|
|
assert(stat(take, &info) == 0 && info.st_size == 57600044);
|
||
|
|
assert(unlink(take) == 0);
|
||
|
|
/* Child-local disk-size failure preserves a recoverable on-disk prefix. */
|
||
|
|
{
|
||
|
|
int pipes[2], status;
|
||
|
|
pid_t child;
|
||
|
|
assert(pipe(pipes) == 0);
|
||
|
|
child = fork(); assert(child >= 0);
|
||
|
|
if (!child) {
|
||
|
|
struct rlimit limit = {128, 128};
|
||
|
|
close(pipes[0]); signal(SIGXFSZ, SIG_IGN);
|
||
|
|
assert(setrlimit(RLIMIT_FSIZE, &limit) == 0);
|
||
|
|
assert(xtc_recorder_create(take, 8000, 4096, &r) == XTC_OK);
|
||
|
|
xtc_recorder_push(r, samples, 4096);
|
||
|
|
(void)xtc_recorder_drain(r);
|
||
|
|
assert(xtc_recorder_finish(r) == XTC_ERR_IO);
|
||
|
|
snprintf(retained, sizeof(retained), "%s", xtc_recorder_recovery_path(r));
|
||
|
|
xtc_recorder_destroy(r);
|
||
|
|
assert(write(pipes[1], retained, sizeof(retained)) == (ssize_t)sizeof(retained));
|
||
|
|
_exit(0);
|
||
|
|
}
|
||
|
|
close(pipes[1]);
|
||
|
|
assert(read(pipes[0], retained, sizeof(retained)) == (ssize_t)sizeof(retained)); close(pipes[0]);
|
||
|
|
assert(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||
|
|
assert(access(take, F_OK) != 0 && stat(retained, &info) == 0 && info.st_size == 128);
|
||
|
|
assert(xtc_recorder_recover(retained, recovered) == XTC_OK);
|
||
|
|
assert(stat(recovered, &info) == 0 && info.st_size == 128);
|
||
|
|
assert(unlink(retained) == 0 && unlink(recovered) == 0);
|
||
|
|
}
|
||
|
|
/* Incomplete and missing references survive save/reopen and are reported. */
|
||
|
|
{
|
||
|
|
xtc_project *p = NULL;
|
||
|
|
xtc_audio_plan *plan = NULL;
|
||
|
|
uint64_t track, clip;
|
||
|
|
const char *issue;
|
||
|
|
assert(xtc_project_create(&p) == XTC_OK && xtc_project_add_track(p, "Incomplete", &track) == XTC_OK);
|
||
|
|
assert(xtc_project_add_clip(p, track, "Broken take", 0, 960, &clip) == XTC_OK);
|
||
|
|
assert(xtc_project_set_clip_asset(p, clip, take) == XTC_OK);
|
||
|
|
file(take, "RIFF incomplete");
|
||
|
|
assert(xtc_project_save(p, song) == XTC_OK && xtc_project_load(p, song) == XTC_OK);
|
||
|
|
assert(xtc_audio_plan_create(p, 8000, &plan) == XTC_OK && xtc_audio_plan_issues(plan, &issue) == 1);
|
||
|
|
assert(!strcmp(issue, take)); xtc_audio_plan_destroy(plan);
|
||
|
|
assert(unlink(take) == 0);
|
||
|
|
assert(xtc_audio_plan_create(p, 8000, &plan) == XTC_OK && xtc_audio_plan_issues(plan, &issue) == 1);
|
||
|
|
xtc_audio_plan_destroy(plan); xtc_project_destroy(p);
|
||
|
|
assert(unlink(song) == 0);
|
||
|
|
}
|
||
|
|
assert(rmdir(dir) == 0);
|
||
|
|
puts("30-minute virtual recording, no-clobber races, disk failure recovery and incomplete/missing media passed.");
|
||
|
|
}
|