chiark / gitweb /
libtests: Include the Unicode test files directly.
[disorder] / server / gstdecode.c
index 02f349bbd2280066c80e5ac9e815a8a751f1dbb6..e8883bbfc5b6c6fb4653fcd630de99c36f150f29 100644 (file)
@@ -51,6 +51,8 @@ static const char *file;
 static GstAppSink *appsink;
 static GstElement *pipeline;
 static GMainLoop *loop;
+static unsigned flags = 0;
+#define f_stream 1u
 
 #define MODES(_) _("off", OFF) _("track", TRACK) _("album", ALBUM)
 enum {
@@ -65,7 +67,20 @@ static const char *const modes[] = {
 #undef DEFNAME
   0
 };
+
+static const char *const dithers[] = {
+  "none", "rpdf", "tpdf", "tpdf-hf", 0
+};
+
+static const char *const shapes[] = {
+  "none", "error-feedback", "simple", "medium", "high", 0
+};
+
+static int dither = -1;
 static int mode = ALBUM;
+static int quality = -1;
+static int shape = -1;
+static gdouble fallback = 0.0;
 
 static struct stream_header hdr;
 
@@ -76,17 +91,37 @@ static void report_element_pads(const char *what, GstElement *elt,
                                 GstIterator *it)
 {
   gchar *cs;
+#ifdef HAVE_GSTREAMER_0_10
   gpointer pad;
+#else
+  GValue gv;
+  GstPad *pad;
+  GstCaps *caps;
+#endif
 
   for(;;) {
+#ifdef HAVE_GSTREAMER_0_10
     switch(gst_iterator_next(it, &pad)) {
+#else
+    switch(gst_iterator_next(it, &gv)) {
+#endif
     case GST_ITERATOR_DONE:
       goto done;
     case GST_ITERATOR_OK:
+#ifdef HAVE_GSTREAMER_0_10
       cs = gst_caps_to_string(gst_pad_get_caps(pad));
+#else
+      assert(G_VALUE_HOLDS(&gv, GST_TYPE_PAD));
+      pad = g_value_get_object(&gv);
+      caps = gst_pad_query_caps(pad, 0);
+      cs = gst_caps_to_string(caps);
+      g_object_unref(caps);
+#endif
       disorder_error(0, "  `%s' %s pad: %s", GST_OBJECT_NAME(elt), what, cs);
       g_free(cs);
+#ifdef HAVE_GSTREAMER_0_10
       g_object_unref(pad);
+#endif
       break;
     case GST_ITERATOR_RESYNC:
       gst_iterator_resync(it);
@@ -127,7 +162,11 @@ static void link_elements(GstElement *left, GstElement *right)
 static void decoder_pad_arrived(GstElement *decode, GstPad *pad, gpointer u)
 {
   GstElement *tail = u;
+#ifdef HAVE_GSTREAMER_0_10
   GstCaps *caps = gst_pad_get_caps(pad);
+#else
+  GstCaps *caps = gst_pad_get_current_caps(pad);
+#endif
   GstStructure *s;
   guint i, n;
   const gchar *name;
@@ -138,8 +177,16 @@ static void decoder_pad_arrived(GstElement *decode, GstPad *pad, gpointer u)
   for(i = 0, n = gst_caps_get_size(caps); i < n; i++) {
     s = gst_caps_get_structure(caps, i);
     name = gst_structure_get_name(s);
-    if(strncmp(name, "audio/x-raw-", 12) == 0) goto match;
+#ifdef HAVE_GSTREAMER_0_10
+    if(strncmp(name, "audio/x-raw-", 12) == 0)
+#else
+    if(strcmp(name, "audio/x-raw") == 0)
+#endif
+      goto match;
   }
+#ifndef HAVE_GSTREAMER_0_10
+  g_object_unref(caps);
+#endif
   return;
 
 match:
@@ -161,14 +208,29 @@ static void prepare_pipeline(void)
 {
   GstElement *source = gst_element_factory_make("filesrc", "file");
   GstElement *decode = gst_element_factory_make("decodebin", "decode");
+  GstElement *resample = gst_element_factory_make("audioresample",
+                                                  "resample");
   GstElement *convert = gst_element_factory_make("audioconvert", "convert");
   GstElement *sink = gst_element_factory_make("appsink", "sink");
   GstElement *tail = sink;
   GstElement *gain;
-  GstCaps *caps = gst_caps_new_empty();
-  GstCaps *c;
-  static const int widths[] = { 8, 16 };
-  size_t i;
+  GstCaps *caps;
+  const struct stream_header *fmt = &config->sample_format;
+
+#ifndef HAVE_GSTREAMER_0_10
+  static const struct fmttab {
+    const char *fmt;
+    unsigned bits;
+    unsigned endian;
+  } fmttab[] = {
+    { "S8", 8, ENDIAN_BIG },
+    { "S8", 8, ENDIAN_LITTLE },
+    { "S16BE", 16, ENDIAN_BIG },
+    { "S16LE", 16, ENDIAN_LITTLE },
+    { 0 }
+  };
+  const struct fmttab *ft;
+#endif
 
   /* Set up the global variables. */
   pipeline = gst_pipeline_new("pipe");
@@ -178,35 +240,62 @@ static void prepare_pipeline(void)
   g_object_set(source, "location", file, END);
   g_object_set(sink, "sync", FALSE, END);
 
-  /* Set up the sink's capabilities. */
-  for(i = 0; i < N(widths); i++) {
-    c = gst_caps_new_simple("audio/x-raw-int",
-                            "width", G_TYPE_INT, widths[i],
-                            "depth", G_TYPE_INT, widths[i],
-                            "channels", GST_TYPE_INT_RANGE, 1, 2,
-                            "signed", G_TYPE_BOOLEAN, TRUE,
-                            "rate", GST_TYPE_INT_RANGE, 100, 1000000,
-                            END);
-    gst_caps_append(caps, c);
+  /* Configure the resampler and converter.  Leave things as their defaults
+   * if the user hasn't made an explicit request.
+   */
+  if(quality >= 0) g_object_set(resample, "quality", quality, END);
+  if(dither >= 0) g_object_set(convert, "dithering", dither, END);
+  if(shape >= 0) g_object_set(convert, "noise-shaping", shape, END);
+
+  /* Set up the sink's capabilities from the configuration. */
+#ifdef HAVE_GSTREAMER_0_10
+  caps = gst_caps_new_simple("audio/x-raw-int",
+                             "width", G_TYPE_INT, fmt->bits,
+                             "depth", G_TYPE_INT, fmt->bits,
+                             "channels", G_TYPE_INT, fmt->channels,
+                             "signed", G_TYPE_BOOLEAN, TRUE,
+                             "rate", G_TYPE_INT, fmt->rate,
+                             "endianness", G_TYPE_INT,
+                               fmt->endian == ENDIAN_BIG ?
+                                 G_BIG_ENDIAN : G_LITTLE_ENDIAN,
+                             END);
+#else
+  for (ft = fmttab; ft->fmt; ft++)
+    if (ft->bits == fmt->bits && ft->endian == fmt->endian) break;
+  if(!ft->fmt) {
+    disorder_fatal(0, "unsupported sample format: bits=%"PRIu32", endian=%u",
+                   fmt->bits, fmt->endian);
   }
+  caps = gst_caps_new_simple("audio/x-raw",
+                             "format", G_TYPE_STRING, ft->fmt,
+                             "channels", G_TYPE_INT, fmt->channels,
+                             "rate", G_TYPE_INT, fmt->rate,
+                             END);
+#endif
   gst_app_sink_set_caps(appsink, caps);
 
   /* Add the various elements into the pipeline.  We'll stitch them together
    * in pieces, because the pipeline is somewhat dynamic.
    */
-  gst_bin_add_many(GST_BIN(pipeline), source, decode, convert, sink, END);
+  gst_bin_add_many(GST_BIN(pipeline),
+                   source, decode,
+                   resample, convert, sink, END);
 
-  /* Link an audio conversion stage onto the front.  The rest of DisOrder
+  /* Link audio conversion stages onto the front.  The rest of DisOrder
    * doesn't handle much of the full panoply of exciting audio formats.
    */
   link_elements(convert, tail); tail = convert;
+  link_elements(resample, tail); tail = resample;
 
   /* If we're meant to do ReplayGain then insert it into the pipeline before
    * the converter.
    */
   if(mode != OFF) {
     gain = gst_element_factory_make("rgvolume", "gain");
-    g_object_set(gain, "album-mode", mode == ALBUM, END);
+    g_object_set(gain,
+                 "album-mode", mode == ALBUM,
+                 "fallback-gain", fallback,
+                 END);
     gst_bin_add(GST_BIN(pipeline), gain);
     link_elements(gain, tail); tail = gain;
   }
@@ -228,8 +317,14 @@ static void bus_message(GstBus UNUSED *bus, GstMessage *msg,
 {
   switch(msg->type) {
   case GST_MESSAGE_ERROR:
+#ifdef HAVE_GSTREAMER_0_10
     disorder_fatal(0, "%s",
                    gst_structure_get_string(msg->structure, "debug"));
+#else
+    disorder_fatal(0, "%s",
+                   gst_structure_get_string(gst_message_get_structure(msg),
+                                            "debug"));
+#endif
   default:
     break;
   }
@@ -247,8 +342,13 @@ static void cb_eos(GstAppSink UNUSED *sink, gpointer UNUSED u)
  */
 static GstFlowReturn cb_preroll(GstAppSink *sink, gpointer UNUSED u)
 {
+#ifdef HAVE_GSTREAMER_0_10
   GstBuffer *buf = gst_app_sink_pull_preroll(sink);
   GstCaps *caps = GST_BUFFER_CAPS(buf);
+#else
+  GstSample *samp = gst_app_sink_pull_preroll(sink);
+  GstCaps *caps = gst_sample_get_caps(samp);
+#endif
 
 #ifdef HAVE_GST_AUDIO_INFO_FROM_CAPS
 
@@ -296,7 +396,11 @@ static GstFlowReturn cb_preroll(GstAppSink *sink, gpointer UNUSED u)
 
 #endif
 
+#ifdef HAVE_GSTREAMER_0_10
   gst_buffer_unref(buf);
+#else
+  gst_sample_unref(samp);
+#endif
   return GST_FLOW_OK;
 }
 
@@ -305,26 +409,55 @@ static GstFlowReturn cb_preroll(GstAppSink *sink, gpointer UNUSED u)
  */
 static GstFlowReturn cb_buffer(GstAppSink *sink, gpointer UNUSED u)
 {
+#ifdef HAVE_GSTREAMER_0_10
   GstBuffer *buf = gst_app_sink_pull_buffer(sink);
+#else
+  GstSample *samp = gst_app_sink_pull_sample(sink);
+  GstBuffer *buf = gst_sample_get_buffer(samp);
+  GstMemory *mem;
+  GstMapInfo map;
+  gint i, n;
+#endif
 
   /* Make sure we actually have a grip on the sample format here. */
   if(!hdr.rate) disorder_fatal(0, "format unset");
 
   /* Write out a frame of audio data. */
+#ifdef HAVE_GSTREAMER_0_10
   hdr.nbytes = GST_BUFFER_SIZE(buf);
-  if(fwrite(&hdr, sizeof(hdr), 1, fp) != 1 ||
+  if((!(flags&f_stream) && fwrite(&hdr, sizeof(hdr), 1, fp) != 1) ||
      fwrite(GST_BUFFER_DATA(buf), 1, hdr.nbytes, fp) != hdr.nbytes)
     disorder_fatal(errno, "output");
+#else
+  for(i = 0, n = gst_buffer_n_memory(buf); i < n; i++) {
+    mem = gst_buffer_peek_memory(buf, i);
+    if(!gst_memory_map(mem, &map, GST_MAP_READ))
+      disorder_fatal(0, "failed to map sample buffer");
+    hdr.nbytes = map.size;
+    if((!(flags&f_stream) && fwrite(&hdr, sizeof(hdr), 1, fp) != 1) ||
+       fwrite(map.data, 1, map.size, fp) != map.size)
+      disorder_fatal(errno, "output");
+    gst_memory_unmap(mem, &map);
+  }
+#endif
 
   /* And we're done. */
+#ifdef HAVE_GSTREAMER_0_10
   gst_buffer_unref(buf);
+#else
+  gst_sample_unref(samp);
+#endif
   return GST_FLOW_OK;
 }
 
 static GstAppSinkCallbacks callbacks = {
   .eos = cb_eos,
   .new_preroll = cb_preroll,
+#ifdef HAVE_GSTREAMER_0_10
   .new_buffer = cb_buffer
+#else
+  .new_sample = cb_buffer
+#endif
 };
 
 /* Decode the audio file.  We're already set up for everything. */
@@ -338,6 +471,8 @@ static void decode(void)
   g_signal_connect(bus, "message", G_CALLBACK(bus_message), 0);
 
   /* Tell the sink to call us when interesting things happen. */
+  gst_app_sink_set_max_buffers(appsink, 16);
+  gst_app_sink_set_drop(appsink, FALSE);
   gst_app_sink_set_callbacks(appsink, &callbacks, 0, 0);
 
   /* Set the ball rolling. */
@@ -361,10 +496,39 @@ static int getenum(const char *what, const char *s, const char *const *tags)
   disorder_fatal(0, "unknown %s `%s'", what, s);
 }
 
+static double getfloat(const char *what, const char *s)
+{
+  double d;
+  char *q;
+
+  errno = 0;
+  d = strtod(s, &q);
+  if(*q || errno) disorder_fatal(0, "invalid %s `%s'", what, s);
+  return d;
+}
+
+static int getint(const char *what, const char *s, int min, int max)
+{
+  long i;
+  char *q;
+
+  errno = 0;
+  i = strtol(s, &q, 10);
+  if(*q || errno || min > i || i > max)
+    disorder_fatal(0, "invalid %s `%s'", what, s);
+  return (int)i;
+}
+
 static const struct option options[] = {
   { "help", no_argument, 0, 'h' },
   { "version", no_argument, 0, 'V' },
+  { "config", required_argument, 0, 'c' },
+  { "dither", required_argument, 0, 'd' },
+  { "fallback-gain", required_argument, 0, 'f' },
+  { "noise-shape", required_argument, 0, 'n' },
+  { "quality", required_argument, 0, 'q' },
   { "replay-gain", required_argument, 0, 'r' },
+  { "stream", no_argument, 0, 's' },
   { 0, 0, 0, 0 }
 };
 
@@ -375,7 +539,15 @@ static void help(void)
           "Options:\n"
           "  --help, -h                 Display usage message\n"
           "  --version, -V              Display version number\n"
+          "  --config PATH, -c PATH     Set configuration file\n"
+          "  --dither TYPE, -d TYPE     TYPE is `none', `rpdf', `tpdf', or "
+                                                "`tpdf-hf'\n"
+          "  --fallback-gain DB, -f DB  For tracks without ReplayGain data\n"
+          "  --noise-shape TYPE, -n TYPE  TYPE is `none', `error-feedback',\n"
+          "                                     `simple', `medium' or `high'\n"
+          "  --quality QUAL, -q QUAL    Resampling quality: 0 poor, 10 good\n"
           "  --replay-gain MODE, -r MODE  MODE is `off', `track' or `album'\n"
+          "  --stream, -s               Output raw samples, without framing\n"
           "\n"
           "Alternative audio decoder for DisOrder.  Only intended to be\n"
           "used by speaker process, not for normal users.\n");
@@ -394,17 +566,24 @@ int main(int argc, char *argv[])
   if(!setlocale(LC_CTYPE, "")) disorder_fatal(errno, "calling setlocale");
 
   /* Parse command line. */
-  while((n = getopt_long(argc, argv, "hVr:", options, 0)) >= 0) {
+  while((n = getopt_long(argc, argv, "hVc:d:f:n:q:r:s", options, 0)) >= 0) {
     switch(n) {
     case 'h': help();
     case 'V': version("disorder-gstdecode");
+    case 'c': configfile = optarg; break;
+    case 'd': dither = getenum("dither type", optarg, dithers); break;
+    case 'f': fallback = getfloat("fallback gain", optarg); break;
+    case 'n': shape = getenum("noise-shaping type", optarg, shapes); break;
+    case 'q': quality = getint("resample quality", optarg, 0, 10); break;
     case 'r': mode = getenum("ReplayGain mode", optarg, modes); break;
+    case 's': flags |= f_stream; break;
     default: disorder_fatal(0, "invalid option");
     }
   }
   if(optind >= argc) disorder_fatal(0, "missing filename");
   file = argv[optind++];
   if(optind < argc) disorder_fatal(0, "excess arguments");
+  if(config_read(1, 0)) disorder_fatal(0, "cannot read configuration");
 
   /* Set up the GStreamer machinery. */
   gst_init(0, 0);