From f961b857eafaf6b57ab345693a847dac32dd8437 Mon Sep 17 00:00:00 2001 From: mj Date: Sun, 18 Dec 2005 23:11:00 +0000 Subject: [PATCH] Initial revision --- Makefile | 14 + vorbiscut.c | 780 +++++++++++++++++++++++++++++++++++++++++++++++++++ vorbistest.c | 65 +++++ 3 files changed, 859 insertions(+) create mode 100644 Makefile create mode 100644 vorbiscut.c create mode 100644 vorbistest.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bd4ba4e --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +CC=gcc-4.0 +LD=gcc-4.0 +CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99 + +all: vorbiscut + +oggtest: LDFLAGS+=-logg +vorbistest: LDFLAGS+=-lvorbisfile -logg -lvorbis +vorbiscut: LDFLAGS+=-L/opt/lib -lvorbisfile -logg -lvorbis -lasound -lsndfile +vorbiscut.o: CFLAGS+=-I/opt/include + +clean: + rm -f `find . -name "*~" -or -name "*.[oa]" -or -name "\#*\#" -or -name TAGS -or -name core -or -name .depend -or -name .#*` + rm -f oggtest vorbistest vorbiscut diff --git a/vorbiscut.c b/vorbiscut.c new file mode 100644 index 0000000..f4ad4b5 --- /dev/null +++ b/vorbiscut.c @@ -0,0 +1,780 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void __attribute__((noreturn)) die(char *msg, ...) +{ + va_list args; + va_start(args, msg); + vfprintf(stderr, msg, args); + fputc('\n', stderr); + exit(1); +} + +typedef long long s64; +typedef short s16; +#define MIN(a,b) ((a)<(b) ? (a) : (b)) +#define MAX(a,b) ((a)>(b) ? (a) : (b)) +#define TRIPLE(pos) (int)(((pos)/rate)/60), (int)(((pos)/rate)%60), (int)((pos)%rate) + +static struct termios tios, tios_old; + +static void key_init(void) +{ + if (tcgetattr(0, &tios_old) < 0) + die("tcgetattr failed: %m"); + tios = tios_old; + tios.c_iflag = IGNBRK; + tios.c_lflag = 0; + tios.c_cc[VTIME] = 0; + tios.c_cc[VMIN] = 1; + if (tcsetattr(0, 0, &tios) < 0) + die("tcsetattr failed: %m"); + fcntl(0, F_SETFL, O_NONBLOCK); +} + +static void key_cleanup(void) +{ + tcsetattr(0, 0, &tios_old); +} + +static int key_get(void) +{ + char keybuf[1]; + static int esc_state, esc_num; + + for (;;) + { + int e = read(0, keybuf, 1); + if (e != 1) + return 0; + + int key = keybuf[0]; + switch (esc_state) + { + case 0: + if (key != '\e') + return key; + esc_state = 1; + esc_num = 0; + break; + case 1: + if (key == '[') + esc_state = 2; + else + { + esc_state = 0; + return 0x0100 + key; + } + break; + case 2: + if (key >= '0' && key <= '9') + esc_num = 10*esc_num + key - '0'; + else + { + esc_state = 0; + return (esc_num << 16) + 0x200 + key; + } + break; + } + } +} + +static FILE *infile; +enum { IN_WAV, IN_OGG } inmode; +static OggVorbis_File vf; +static SNDFILE *sndf; +static unsigned int rate; +static char *find_title = "Toulky ceskou minulosti"; +static s64 total_samples = 0; +static s64 start_pos = -1; +static s64 end_pos = -1; +double prefade = 1, postfade = 1; + +static void scan_streams(void) +{ + int nstr = ov_streams(&vf); + printf("OGG: Scanning %d logical streams:\n", nstr); + if (!nstr) + die("No streams found"); + for (int i=0; ichannels != 2) + die("Stream %d has %d channels, which is not supported", i, vi->channels); + if ((unsigned int) vi->rate != rate) + { + if (rate) + die("Stream %d has sample rate %d, while the previous had %d", i, vi->rate, rate); + else + rate = vi->rate; + } + + vorbis_comment *vc; + vc = ov_comment(&vf, i); + if (!vc) + die("ov_comment failed"); + char title[1024]; + title[0] = 0; + for (int j=0; jcomments; j++) + if (vc->comment_lengths[j] > 6 && !strncasecmp(vc->user_comments[j], "title=", 6)) + { + int l = vc->comment_lengths[j] - 6; + memcpy(title, vc->user_comments[j]+6, l); + title[l] = 0; + } + if (!title[0]) + strcpy(title, ""); + + s64 samples = ov_pcm_total(&vf, i); + int sec = (samples + rate - 1) / rate; + printf(" %d: `%s' (%d:%02d, %d bits/sec) @%Ld\n", i, title, sec/60, sec%60, (int)vi->bitrate_nominal, total_samples); + + if (find_title && strstr(title, find_title)) + { + if (start_pos < 0) + start_pos = total_samples; + else if (end_pos != total_samples) + printf("WARNING: Gap encountered!\n"); + end_pos = total_samples + samples; + } + + total_samples += samples; + } + + if (start_pos < 0) + { + if (find_title) + printf("WARNING: Title not found, marking whole file"); + start_pos = 0; + end_pos = total_samples; + } +} + +static void in_open(char *name) +{ + infile = fopen(name, "r"); + if (!infile) + die("Cannot open %s: %m", name); + + char s[4]; + if (fread(s, 1, 4, infile) != 4) + die("Input file too short"); + rewind(infile); + if (!memcmp(s, "RIFF", 4)) + { + puts("INPUT: WAV file detected"); + inmode = IN_WAV; + } + else if (!memcmp(s, "OggS", 4)) + { + puts("INPUT: OGG file detected"); + inmode = IN_OGG; + } + else if (!memcmp(s, "HTTP", 4)) + { + puts("INPUT: HTTP header detected, expecting OGG inside"); + inmode = IN_OGG; + } + else + die("Unable to identify input format"); + + if (inmode == IN_WAV) + { + SF_INFO si; + bzero(&si, sizeof(si)); + lseek(fileno(infile), 0, SEEK_SET); + sndf = sf_open_fd(fileno(infile), O_RDONLY, &si, 0); + if (!sndf) + die("sf_open_fd() failed: %s", sf_strerror(NULL)); + total_samples = si.frames; + rate = si.samplerate; + if (si.channels != 2) + die("Got %d channels instead of 2", si.channels); + if (si.sections != 1) + die("Found %d sections, what does it mean?", si.sections); + printf("WAV: format id=%08x\n", si.format); + start_pos = 0; + end_pos = total_samples; + } + else + { + int err = ov_open(infile, &vf, NULL, 0); + if (err) + die("ov_open: error %d", err); + if (!ov_seekable(&vf)) + die("Input is not seekable, how come?"); + scan_streams(); + } + + printf("INPUT: length %3d:%02d.%05d, %Ld samples at rate %d\n", TRIPLE(total_samples), total_samples, rate); +} + +static void in_goto(s64 go) +{ + if (inmode == IN_OGG) + ov_pcm_seek(&vf, go); + else + sf_seek(sndf, go, SEEK_SET); +} + +static int in_read(s16 *buf, s64 pos, int nsamp) +{ + if (inmode == IN_OGG) + { + if (ov_pcm_tell(&vf) != pos) + printf("!!! CONFUSED POSITION\n"); + for (;;) + { + int bp; + int e = ov_read(&vf, (char*)buf, 4*nsamp, 0, 2, 1, &bp); + if (e == OV_HOLE) + printf("!!! HOLE DETECTED\n"); + else if (e == OV_EBADLINK) + printf("!!! BAD LINK\n"); + else if (e % 4) + die("ov_read returned %d bytes, which means non-integer number of samples. Huh.", e); + else if (e/4 <= nsamp) + return e/4; + else + die("ov_read returned %d samples, although we wanted %d", e/4, nsamp); + } + } + else + { + int e = sf_readf_short(sndf, buf, nsamp); + if (e != nsamp) + printf("!!!! sf_readf_short mismatch: %d != %d\n", e, nsamp); + return e; + } +} + +static void in_close(void) +{ + if (inmode == IN_OGG) + ov_clear(&vf); + else + sf_close(sndf); +} + +static s16 *prefader, *postfader; + +static s16 *calc_fader(int len, int rev) +{ + if (!len) + return NULL; + s16 *x = malloc(2*len); + for (int i=0; i= flen) + return; + int rel = relpos; + if (rel < 0) + { + rel = -rel; + sig += 2*rel; + nsamp -= rel; + rel = 0; + } + while (nsamp > 0 && rel < flen) + { + sig[0] = (sig[0] * fader[rel]) >> 15; + sig[1] = (sig[1] * fader[rel]) >> 15; + sig += 2; + nsamp--; + rel++; + } +} + +static int cooked_read(s16 *buf, s64 pos, int nsamp, int apply_pre, int apply_post) +{ + nsamp = in_read(buf, pos, nsamp); + int presamp = prefade * rate; + int postsamp = postfade * rate; + if (apply_pre) + apply_fader(buf, nsamp, pos, start_pos, presamp, prefader); + if (apply_post) + apply_fader(buf, nsamp, pos, end_pos-postsamp, postsamp, postfader); + return nsamp; +} + +static char *cuename; + +static int cue_load(void) +{ + if (cuename) + { + FILE *f = fopen(cuename, "r"); + if (f) + { + if (fscanf(f, "%Ld%lf%Ld%lf", &start_pos, &prefade, &end_pos, &postfade) != 4) + die("CUE: Invalid syntax"); + fclose(f); + printf("CUE: Loaded\n"); + return 1; + } + } + return 0; +} + +static void cue_save(void) +{ + if (cuename) + { + FILE *f = fopen(cuename, "w"); + if (!f) + die("CUE: Cannot write"); + fprintf(f, "%Ld %g %Ld %g\n", start_pos, prefade, end_pos, postfade); + fclose(f); + } +} + +static void editor(void) +{ + printf("Initializing ALSA\n"); + snd_pcm_t *pcm; + snd_pcm_hw_params_t *apars; + int err; +#define ALSACALL(f,args) if ((err = f args) < 0) die(#f " failed: %s", snd_strerror(err)) + ALSACALL(snd_pcm_open, (&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0)); + ALSACALL(snd_pcm_hw_params_malloc, (&apars)); + ALSACALL(snd_pcm_hw_params_any, (pcm, apars)); + ALSACALL(snd_pcm_hw_params_set_access, (pcm, apars, SND_PCM_ACCESS_RW_INTERLEAVED)); + ALSACALL(snd_pcm_hw_params_set_format, (pcm, apars, SND_PCM_FORMAT_S16_LE)); + unsigned int xrate = rate; + int dir = 0; + ALSACALL(snd_pcm_hw_params_set_rate_near, (pcm, apars, &xrate, &dir)); + if (xrate != rate) + printf("WARNING: Rate set to %d instead of %d\n", xrate, rate); + ALSACALL(snd_pcm_hw_params_set_channels, (pcm, apars, 2)); + ALSACALL(snd_pcm_hw_params, (pcm, apars)); + snd_pcm_hw_params_free(apars); + ALSACALL(snd_pcm_prepare, (pcm)); + + s64 go = start_pos; + s64 pos = -1; + +#define CLAMP(x) (((x) < 0) ? 0 : ((x) >= total_samples) ? total_samples-1 : (x)) + + s16 buf[2048]; + key_init(); + + enum { + M_START, + M_END, + M_PRE, + M_POST, + } mode = M_START; + int silence = 0; + int step = rate; + double fst = 1; + double fsil = 1; + int lback = 3*rate; + for(;;) + { + if (go >= 0) + { + if (pos != go) + { + in_goto(go); + pos = go; + } + go = -1; + } + + int key = key_get(); + switch (key) + { + case ' ': + if (mode == M_START || mode == M_PRE) + { + start_pos = pos; + go = start_pos; + mode = M_PRE; + } + else if (mode == M_END || mode == M_POST) + { + end_pos = pos; + go = CLAMP(end_pos - lback); + mode = M_POST; + } + break; + case 0x241: + fst *= 2; + step = rate * fst; + break; + case 0x242: + fst /= 2; + step = rate * fst; + break; + case '1' ... '4': + fst = 1. / (16 >> (key - '0')); + step = rate * fst; + break; + case '5' ... '9': + fst = 1 << (key - '4'); + step = rate * fst; + break; + case 0x244: + if (mode == M_PRE) + { + start_pos = CLAMP(start_pos - step); + go = start_pos; + } + else if (mode == M_POST) + { + end_pos = CLAMP(end_pos - step); + go = CLAMP(end_pos - lback); + } + else + go = CLAMP(pos - step); + break; + case 0x243: + if (mode == M_PRE) + { + start_pos = CLAMP(start_pos + step); + go = start_pos; + } + else if (mode == M_POST) + { + end_pos = CLAMP(end_pos + step); + go = CLAMP(end_pos - lback); + } + else + go = CLAMP(pos + step); + break; + case '\r': + case '\n': + if (mode == M_PRE) + { + go = start_pos; + silence = fsil*rate; + } + else if (mode == M_POST) + { + go = CLAMP(end_pos - lback); + silence = fsil*rate; + } + else + silence = 0; + break; + case 0x7027e: // Home + if (mode <= M_END) + go = 0; + break; + case 0x8027e: // End + if (mode <= M_END) + go = CLAMP(total_samples - lback); + break; + case 0x5027e: // PgUp + if (mode <= M_END) + go = CLAMP(pos - 60*rate); + break; + case 0x6027e: // PgUp + if (mode <= M_END) + go = CLAMP(pos + 60*rate); + break; + case 0x2027e: // Insert + mode = M_START; + break; + case 0x3027e: // Delete + mode = M_END; + break; + case '[': + mode = M_PRE; + go = start_pos; + break; + case ']': + mode = M_POST; + go = CLAMP(end_pos - lback); + break; + case 0x7f: + silence = -1; + break; + case '+': + case '-': + case '/': + case '*': + if (mode == M_PRE || mode == M_POST) + { + double *f = (mode == M_PRE ? &prefade : &postfade); + if (key == '/') + *f = 0; + else if (key == '*') + *f = MIN(fst, 10); + else if (key == '+') + *f = MIN(*f + fst, 10); + else + *f = MAX(*f - fst, 0); + recalc_faders(); + if (mode == 1) + go = start_pos; + else + go = CLAMP(end_pos - lback); + } + break; + case 'q': + cue_save(); + /* fall-thru */ + case 'Q': + case 3: + goto done; + default: + printf("KEY <%x>\n", key); + case 0: ; + } + if (go >= 0) + continue; + + printf("%3d:%02d.%05d [%s %3d:%02d.%05d/%g -> %s %3d:%02d.%05d\\%g] step=%g%s\e[K\r", + TRIPLE(pos), + (mode == M_START) ? "START" : (mode == M_PRE) ? "START>" : "start", TRIPLE(start_pos), prefade, + (mode == M_END) ? "END" : (mode == M_POST) ? ">END" : "end", TRIPLE(end_pos), postfade, + fst, + (silence < 0) ? " [STOP]" : ""); + fflush(stdout); + + s64 end = (mode == M_POST) ? end_pos : total_samples; + int nsamp, nread; + if (pos >= end || silence) + { + bzero(buf, 1024); + nsamp = 1024/4; + if (silence > 0) + { + nsamp = MIN(nsamp, silence); + silence -= nsamp; + } + nread = 0; + } + else + { + nsamp = sizeof(buf)/4; + if (pos + nsamp > end_pos) + nsamp = end_pos - pos; + nsamp = cooked_read(buf, pos, nsamp, (mode == M_PRE), (mode == M_POST)); + nread = nsamp; + } + + int err = snd_pcm_writei(pcm, buf, nsamp); + if (err == -EPIPE) + { + puts("[xrun detected]"); + err = snd_pcm_prepare(pcm); + if (err < 0) + die("xrun recovery failed: error %d", err); + } + if (err < 0) + die("snd_pcm_writei failed: error %d", err); + pos += nread; + if (pos > end) + pos = end; + } + + done: + key_cleanup(); +} + +static int nchildren; + +static int add_filter(int fd, char **args) +{ + int p[2]; + if (pipe(p) < 0) + die("pipe: %m"); + pid_t pid = fork(); + if (pid < 0) + die("fork: %m"); + if (!pid) + { + close(p[1]); + dup2(p[0], 0); + close(p[0]); + dup2(fd, 1); + close(fd); + execvp(args[0], args); + die("execvp(%s) failed: %m", args[0]); + } + else + { + nchildren++; + printf("FILTER: Forked pid %d for %s\n", pid, args[0]); + close(p[0]); + close(fd); + return p[1]; + } +} + +static int orig_stdout; + +static void render(char *name) +{ + char *sfix; + int fd; + if (!strcmp(name, "-")) + { + fd = orig_stdout; + sfix = ".wav"; + } + else + { + sfix = strrchr(name, '.') ? : ""; + fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) + die("Unable to create %s: %m", name); + } + if (!strcmp(sfix, ".WAV")) + printf("RENDER: WAV output without sample rate conversion\n"); + else if (!strcmp(sfix, ".wav")) + { + printf("RENDER: WAV output\n"); + if (rate != 44100) + { + printf("RENDER: Adding resample filter\n"); + char *f[] = { "sox", "-twav", "-", "-r44100", "-twav", "-", "vol", "0.9", "resample", NULL }; + fd = add_filter(fd, f); + } + } + else + die("Unknown output file suffix"); + + s64 outlen = end_pos - start_pos; + +#if 0 + /* GRRRR! libsndfile is unable to write WAV files to pipes! */ + SF_INFO si = { + .frames = 0, + .samplerate = rate, + .channels = 2, + .format = SF_FORMAT_WAV | SF_FORMAT_PCM_16, + .sections = 1, + .seekable = 0 + }; + SNDFILE *sf = sf_open_fd(fd, SFM_WRITE, &si, 0); + if (!sf) + die("Output sf_open_fd() failed: %s", sf_strerror(NULL)); +#else + FILE *of = fdopen(fd, "w"); + void wt(char *x) { fwrite(x, 4, 1, of); } + void wr(unsigned int x) { fwrite(&x, sizeof(x), 1, of); } + wt("RIFF"); + wr(8 + 0x10 + 8 + 4*outlen); + wt("WAVE"); + wt("fmt "); + wr(0x10); + wr(0x00020001); // 2 channels, not compressed + wr(rate); + wr(4*rate); + wr(0x00100004); // 16-bit + wt("data"); + wr(4*outlen); +#endif + + s64 pos = start_pos; + s64 outpos = 0; + s16 buf[65536]; + in_goto(pos); + printf("RENDER: Generating %d:%02d.%05d of output\n", TRIPLE(outlen)); + for (;;) + { + int n = sizeof(buf) / 4; + if (pos + n > end_pos) + n = end_pos - pos; + if (!n) + break; + n = cooked_read(buf, pos, n, 1, 1); + pos += n; + outpos += n; +#if 0 + if (sf_writef_short(sf, buf, n) != n) +#else + if ((int)fwrite(buf, 4, n, of) != n) +#endif + die("Short write, oops!"); + printf("RENDER: %3d:%02d.%05d\r", TRIPLE(outpos)); + fflush(stdout); + } + +#if 0 + sf_close(sf); + close(fd); +#else + fclose(of); +#endif + while (nchildren) + { + int st; + pid_t p = wait(&st); + if (p < 0) + printf("wait(): %m\n"); + else + { + nchildren--; + if (WIFEXITED(st)) + { + if (WEXITSTATUS(st)) + printf("!!! pid %d failed with exit code %d\n", p, WEXITSTATUS(st)); + else + printf("FILTER: pid %d finished OK\n", p); + } + else + printf("!!! pid %d failed with status %x\n", p, st); + } + } + printf("RENDER: Rendering successfully completed.\n"); +} + +int main(int argc, char **argv) +{ + if (argc < 2 || argc > 4) + die("Usage: vorbiscut [ [|-]]"); + + if (argc >= 4) + { + orig_stdout = dup(1); + dup2(2, 1); + } + + in_open(argv[1]); + if (argc >= 3) + cuename = argv[2]; + int cueok = cue_load(); + recalc_faders(); + if (argc >= 4) + { + if (!cueok) + printf("!!!!! Cue sheet not present, rendering with defaults !!!!!\n"); + render(argv[3]); + } + else + { + editor(); + } + in_close(); + return 0; +} diff --git a/vorbistest.c b/vorbistest.c new file mode 100644 index 0000000..452b811 --- /dev/null +++ b/vorbistest.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include + +static void die(char *msg, ...) +{ + va_list args; + va_start(args, msg); + vfprintf(stderr, msg, args); + fputc('\n', stderr); + exit(1); +} + +int main(void) +{ + OggVorbis_File vf; + int err; + + err = ov_open(stdin, &vf, NULL, 0); + if (err) + die("ov_open: error %d", err); + if (!ov_seekable(&vf)) + die("Input is not seekable"); + + int nstr = ov_streams(&vf); + printf("Found %d logical streams\n", nstr); + for (int i=0; iversion, vi->channels, vi->rate, vi->bitrate_nominal, + ov_raw_total(&vf, i), ov_pcm_total(&vf, i)); + + vorbis_comment *vc; + vc = ov_comment(&vf, i); + if (!vc) + die("ov_comment failed"); + for (int j=0; jcomments; j++) + printf("\t%.*s\n", vc->comment_lengths[j], vc->user_comments[j]); + } + + printf("Decoding...\n"); + char buf[4096]; + for(;;) + { + printf("@%Ld #%ld ", ov_pcm_tell(&vf), ov_serialnumber(&vf, -1)); + int bp; + int e = ov_read(&vf, buf, sizeof(buf), 0, 2, 1, &bp); + printf("S%d >%d\n", bp, e); + if (!e) + break; + if (e == OV_HOLE) + printf("!!! HOLE DETECTED\n"); + else if (e == OV_EBADLINK) + printf("!!! BAD LINK\n"); + } + + ov_clear(&vf); + return 0; +} -- 2.39.2