]> mj.ucw.cz Git - vcut.git/commitdiff
Initial revision
authormj <mj>
Sun, 18 Dec 2005 23:11:00 +0000 (23:11 +0000)
committermj <mj>
Sun, 18 Dec 2005 23:11:00 +0000 (23:11 +0000)
Makefile [new file with mode: 0644]
vorbiscut.c [new file with mode: 0644]
vorbistest.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
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 (file)
index 0000000..f4ad4b5
--- /dev/null
@@ -0,0 +1,780 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <termios.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <vorbis/vorbisfile.h>
+#include <alsa/asoundlib.h>
+#include <sndfile.h>
+
+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; i<nstr; i++)
+    {
+      vorbis_info *vi;
+      vi = ov_info(&vf, i);
+      if (!vi)
+       die("ov_info failed");
+      if (vi->channels != 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; j<vc->comments; 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, "<none>");
+
+      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<len; i++)
+    x[rev ? len-1-i : i] = (s64)i*32767/len;
+  return x;
+}
+
+static void recalc_faders(void)
+{
+  // printf("FADERS: set to %g pre, %g post\n", prefade, postfade);
+  free(prefader);
+  prefader = calc_fader(prefade*rate, 0);
+  free(postfader);
+  postfader = calc_fader(postfade*rate, 1);
+}
+
+static void apply_fader(s16 *sig, int nsamp, s64 pos, s64 fstart, int flen, s16 *fader)
+{
+  s64 relpos = pos - fstart;
+  if (relpos <= -nsamp || relpos >= 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 <infile> [<cuefile> [<outfile>|-]]");
+
+  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 (file)
index 0000000..452b811
--- /dev/null
@@ -0,0 +1,65 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <vorbis/vorbisfile.h>
+
+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; i<nstr; i++)
+    {
+      vorbis_info *vi;
+      vi = ov_info(&vf, i);
+      if (!vi)
+       die("ov_info failed");
+      printf("Stream %d: v=%d chan=%d rate=%ld nbr=%ld rawsize=%Ld samples=%Ld\n", i,
+            vi->version, 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; j<vc->comments; 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;
+}