#include <gst/app/gstappsink.h>
#include <gst/audio/audio.h>
-/* The only application we have for `attribute' is declaring function
+/* The only applications we have for `attribute' is declaring function
* arguments as being unused, because we have a lot of callback functions
- * which are meant to comply with an externally defined interface.
+ * which are meant to comply with an externally defined interface; and
+ * marking `help' as not returning.
*/
#ifdef __GNUC__
+# define NORETURN __attribute__((noreturn))
# define UNUSED __attribute__((unused))
#endif
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 {
#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;
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:
- cs = gst_caps_to_string(gst_pad_get_caps(pad));
+#ifdef HAVE_GSTREAMER_0_10
+ cs = gst_caps_to_string(GST_PAD_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);
+ gst_caps_unref(caps);
+#endif
disorder_error(0, " `%s' %s pad: %s", GST_OBJECT_NAME(elt), what, cs);
g_free(cs);
- g_object_unref(pad);
+ gst_object_unref(pad);
break;
case GST_ITERATOR_RESYNC:
gst_iterator_resync(it);
disorder_error(0, "failed to link GStreamer elements `%s' and `%s'",
GST_OBJECT_NAME(left), GST_OBJECT_NAME(right));
report_element_pads("source", left, gst_element_iterate_src_pads(left));
- report_element_pads("source", right, gst_element_iterate_sink_pads(right));
+ report_element_pads("dest", right, gst_element_iterate_sink_pads(right));
disorder_fatal(0, "can't decode `%s'", file);
}
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;
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;
}
- return;
+ goto end;
match:
/* Yes, it's audio. Link the two elements together. */
GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(pipeline),
GST_DEBUG_GRAPH_SHOW_ALL,
"disorder-gstdecode");
+
+end:
+ gst_caps_unref(caps);
}
/* Prepare the GStreamer pipeline, ready to decode the given FILE. This sets
{
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;
+
+ if(!source || !decode || !resample || !convert || !sink)
+ disorder_fatal(0, "failed to create GStreamer elements: "
+ "need base and good plugins");
+
+#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");
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);
+ gst_caps_unref(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);
+ if(!gain)
+ disorder_fatal(0, "failed to create GStreamer elements: "
+ "need base and good plugins");
+ 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;
}
static void bus_message(GstBus UNUSED *bus, GstMessage *msg,
gpointer UNUSED u)
{
- switch(msg->type) {
+ switch(GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR:
disorder_fatal(0, "%s",
- gst_structure_get_string(msg->structure, "debug"));
+ gst_structure_get_string(gst_message_get_structure(msg),
+ "debug"));
default:
break;
}
*/
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
#endif
+#ifdef HAVE_GSTREAMER_0_10
gst_buffer_unref(buf);
+#else
+ gst_sample_unref(samp);
+#endif
return GST_FLOW_OK;
}
*/
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. */
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. */
- gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING);
+ gst_element_set_state(pipeline, GST_STATE_PLAYING);
/* And wait for the miracle to come. */
g_main_loop_run(loop);
/* Shut down the pipeline. This isn't strictly necessary, since we're
* about to exit very soon, but it's kind of polite.
*/
- gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_NULL);
+ gst_element_set_state(pipeline, GST_STATE_NULL);
}
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 }
};
-static void help(void)
+static void NORETURN help(void)
{
xprintf("Usage:\n"
" disorder-gstdecode [OPTIONS] PATH\n"
"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");
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");
+ config_per_user = 0;
+ if(config_read(1, 0)) disorder_fatal(0, "cannot read configuration");
/* Set up the GStreamer machinery. */
gst_init(0, 0);