server/gstdecode.c: Add `-s' option to omit DisOrder's usual framing.
[disorder] / server / gstdecode.c
index 02f349b..93cde85 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;
 
@@ -161,14 +176,14 @@ 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;
 
   /* Set up the global variables. */
   pipeline = gst_pipeline_new("pipe");
@@ -178,35 +193,48 @@ 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. */
+  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);
   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;
   }
@@ -312,7 +340,7 @@ static GstFlowReturn cb_buffer(GstAppSink *sink, gpointer UNUSED u)
 
   /* Write out a frame of audio data. */
   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");
 
@@ -361,10 +389,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 +432,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 +459,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);