openmpt: Add openmptdec element
authorCarlos Rafael Giani <dv@pseudoterminal.org>
Wed, 27 Jul 2016 00:22:26 +0000 (02:22 +0200)
committerJan Schmidt <jan@centricular.com>
Mon, 22 May 2017 14:34:05 +0000 (16:34 +0200)
https://bugzilla.gnome.org/show_bug.cgi?id=768576

configure.ac
ext/Makefile.am
ext/openmpt/Makefile.am [new file with mode: 0644]
ext/openmpt/gstopenmptdec.c [new file with mode: 0644]
ext/openmpt/gstopenmptdec.h [new file with mode: 0644]
ext/openmpt/plugin.c [new file with mode: 0644]

index 6c3805c..151026f 100644 (file)
@@ -2828,6 +2828,14 @@ AG_GST_CHECK_FEATURE(OPENJPEG, [openjpeg library], openjpeg, [
   AC_SUBST(OPENJPEG_LIBS)
 ])
 
+dnl *** OpenMPT ***
+translit(dnm, m, l) AM_CONDITIONAL(USE_OPENMPT, true)
+AG_GST_CHECK_FEATURE(OPENMPT, openmpt, openmpt, [
+  PKG_CHECK_MODULES(OPENMPT, libopenmpt, HAVE_OPENMPT="yes", HAVE_OPENMPT="no")
+  AC_SUBST(OPENMPT_CFLAGS)
+  AC_SUBST(OPENMPT_LIBS)
+])
+
 dnl *** OpenNI2 ***
 translit(dnm, m, l) AM_CONDITIONAL(USE_OPENNI2, true)
 AG_GST_CHECK_FEATURE(OPENNI2, [openni2 library], openni2, [
@@ -3384,6 +3392,7 @@ AM_CONDITIONAL(USE_OPENAL, false)
 AM_CONDITIONAL(USE_OPENCV, false)
 AM_CONDITIONAL(USE_OPENEXR, false)
 AM_CONDITIONAL(USE_OPENJPEG, false)
+AM_CONDITIONAL(USE_OPENMPT, false)
 AM_CONDITIONAL(USE_OPENNI2, false)
 AM_CONDITIONAL(USE_OPUS, false)
 AM_CONDITIONAL(USE_QT, false)
@@ -3685,6 +3694,7 @@ ext/opencv/Makefile
 ext/openexr/Makefile
 ext/openh264/Makefile
 ext/openjpeg/Makefile
+ext/openmpt/Makefile
 ext/openni2/Makefile
 ext/opus/Makefile
 ext/qt/Makefile
index 534b9ac..bc2abed 100644 (file)
@@ -232,6 +232,12 @@ else
 OPENH264_DIR =
 endif
 
+if USE_OPENMPT
+OPENMPT_DIR=openmpt
+else
+OPENMPT_DIR=
+endif
+
 if USE_OPENNI2
 OPENNI2_DIR=openni2
 else
@@ -431,6 +437,7 @@ SUBDIRS=\
        $(OPENEXR_DIR) \
        $(OPENH264_DIR) \
        $(OPENJPEG_DIR) \
+       $(OPENMPT_DIR) \
        $(OPENNI2_DIR) \
        $(OPUS_DIR) \
        $(RSVG_DIR) \
@@ -494,6 +501,7 @@ DIST_SUBDIRS = \
        opencv \
        openexr \
        openh264 \
+       openmpt \
        openni2 \
        openjpeg \
        opus \
diff --git a/ext/openmpt/Makefile.am b/ext/openmpt/Makefile.am
new file mode 100644 (file)
index 0000000..a699a11
--- /dev/null
@@ -0,0 +1,18 @@
+plugin_LTLIBRARIES = libgstopenmpt.la
+
+libgstopenmpt_la_SOURCES = gstopenmptdec.c plugin.c
+
+libgstopenmpt_la_CFLAGS = \
+       -I$(top_srcdir)/gst-libs \
+       -I$(top_builddir)/gst-libs \
+       $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) \
+       $(GST_CFLAGS) $(OPENMPT_CFLAGS)
+libgstopenmpt_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstopenmpt_la_LIBADD = \
+       $(top_builddir)/gst-libs/gst/base/libgstbadbase-$(GST_API_VERSION).la \
+       $(top_builddir)/gst-libs/gst/audio/libgstbadaudio-$(GST_API_VERSION).la \
+       $(GST_PLUGINS_BASE_LIBS) -lgstaudio-@GST_API_VERSION@ \
+       $(GST_BASE_LIBS) $(GST_LIBS) $(OPENMPT_LIBS)
+libgstopenmpt_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
+
+noinst_HEADERS = gstopenmptdec.h
diff --git a/ext/openmpt/gstopenmptdec.c b/ext/openmpt/gstopenmptdec.c
new file mode 100644 (file)
index 0000000..c4830f6
--- /dev/null
@@ -0,0 +1,968 @@
+/* GStreamer
+ * Copyright (C) <2017> Carlos Rafael Giani <dv at pseudoterminal dot org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+/**
+ * SECTION:element-openmptdec
+ * @see_also: #GstOpenMptDec
+ *
+ * openmpdec decodes module music formats, such as S3M, MOD, XM, IT.
+ * It uses the <ulink url="https://lib.openmpt.org">OpenMPT library</ulink>
+ * for this purpose. It can be autoplugged and therefore works with decodebin.
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch-1.0 filesrc location=media/example.it ! openmptdec ! audioconvert ! audioresample ! autoaudiosink
+ * ]|
+ * </refsect2>
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gst/gst.h>
+
+#include "gstopenmptdec.h"
+
+
+GST_DEBUG_CATEGORY_STATIC (openmptdec_debug);
+#define GST_CAT_DEFAULT openmptdec_debug
+
+
+enum
+{
+  PROP_0,
+  PROP_MASTER_GAIN,
+  PROP_STEREO_SEPARATION,
+  PROP_FILTER_LENGTH,
+  PROP_VOLUME_RAMPING,
+  PROP_OUTPUT_BUFFER_SIZE
+};
+
+
+#define DEFAULT_MASTER_GAIN 0
+#define DEFAULT_STEREO_SEPARATION 100
+#define DEFAULT_FILTER_LENGTH 0
+#define DEFAULT_VOLUME_RAMPING -1
+#define DEFAULT_OUTPUT_BUFFER_SIZE 1024
+
+#define DEFAULT_SAMPLE_FORMAT GST_AUDIO_FORMAT_F32
+#define DEFAULT_SAMPLE_RATE 48000
+#define DEFAULT_NUM_CHANNELS 2
+
+
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("audio/x-mod, "
+        "type = (string) { 669, asylum-amf, dsmi-amf, extreme-ams, velvet-ams, "
+        "dbm, digi, dmf, dsm, far, gdm, imf, it, j2b, mdl, med, mod, mt2, mtm, "
+        "okt, psm, ptm, s3m, stm, ult, xm }")
+    );
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("audio/x-raw, "
+        "format = (string) { " GST_AUDIO_NE (S16) ", " GST_AUDIO_NE (F32) " }, "
+        "layout = (string) interleaved, "
+        "rate = (int) [ 1, 192000 ], " "channels = (int) { 1, 2, 4 } ")
+    );
+
+
+
+G_DEFINE_TYPE (GstOpenMptDec, gst_openmpt_dec,
+    GST_TYPE_NONSTREAM_AUDIO_DECODER);
+
+
+
+static void gst_openmpt_dec_finalize (GObject * object);
+
+static void gst_openmpt_dec_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_openmpt_dec_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static gboolean gst_openmpt_dec_seek (GstNonstreamAudioDecoder * dec,
+    GstClockTime * new_position);
+static GstClockTime gst_openmpt_dec_tell (GstNonstreamAudioDecoder * dec);
+
+static void gst_openmpt_dec_log_func (char const *message, void *user);
+static void gst_openmpt_dec_add_metadata_to_tag_list (GstOpenMptDec *
+    openmpt_dec, GstTagList * tags, char const *key, gchar const *tag);
+static gboolean gst_openmpt_dec_load_from_buffer (GstNonstreamAudioDecoder *
+    dec, GstBuffer * source_data, guint initial_subsong,
+    GstNonstreamAudioSubsongMode initial_subsong_mode,
+    GstClockTime * initial_position,
+    GstNonstreamAudioOutputMode * initial_output_mode,
+    gint * initial_num_loops);
+
+static GstTagList *gst_openmpt_dec_get_main_tags (GstNonstreamAudioDecoder *
+    dec);
+
+static gboolean gst_openmpt_dec_set_current_subsong (GstNonstreamAudioDecoder *
+    dec, guint subsong, GstClockTime * initial_position);
+static guint gst_openmpt_dec_get_current_subsong (GstNonstreamAudioDecoder *
+    dec);
+
+static guint gst_openmpt_dec_get_num_subsongs (GstNonstreamAudioDecoder * dec);
+static GstClockTime
+gst_openmpt_dec_get_subsong_duration (GstNonstreamAudioDecoder * dec,
+    guint subsong);
+static GstTagList *gst_openmpt_dec_get_subsong_tags (GstNonstreamAudioDecoder *
+    dec, guint subsong);
+static gboolean gst_openmpt_dec_set_subsong_mode (GstNonstreamAudioDecoder *
+    dec, GstNonstreamAudioSubsongMode mode, GstClockTime * initial_position);
+
+static gboolean gst_openmpt_dec_set_num_loops (GstNonstreamAudioDecoder * dec,
+    gint num_loops);
+static gint gst_openmpt_dec_get_num_loops (GstNonstreamAudioDecoder * dec);
+
+static guint
+gst_openmpt_dec_get_supported_output_modes (GstNonstreamAudioDecoder * dec);
+static gboolean gst_openmpt_dec_decode (GstNonstreamAudioDecoder * dec,
+    GstBuffer ** buffer, guint * num_samples);
+
+static gboolean gst_openmpt_dec_select_subsong (GstOpenMptDec *
+    openmpt_dec, GstNonstreamAudioSubsongMode subsong_mode,
+    gint openmpt_subsong);
+
+
+void
+gst_openmpt_dec_class_init (GstOpenMptDecClass * klass)
+{
+  GObjectClass *object_class;
+  GstElementClass *element_class;
+  GstNonstreamAudioDecoderClass *dec_class;
+
+  GST_DEBUG_CATEGORY_INIT (openmptdec_debug, "openmptdec", 0,
+      "OpenMPT-based module music decoder");
+
+  object_class = G_OBJECT_CLASS (klass);
+  element_class = GST_ELEMENT_CLASS (klass);
+  dec_class = GST_NONSTREAM_AUDIO_DECODER_CLASS (klass);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&sink_template));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&src_template));
+
+  object_class->finalize = GST_DEBUG_FUNCPTR (gst_openmpt_dec_finalize);
+  object_class->set_property = GST_DEBUG_FUNCPTR (gst_openmpt_dec_set_property);
+  object_class->get_property = GST_DEBUG_FUNCPTR (gst_openmpt_dec_get_property);
+
+  dec_class->seek = GST_DEBUG_FUNCPTR (gst_openmpt_dec_seek);
+  dec_class->tell = GST_DEBUG_FUNCPTR (gst_openmpt_dec_tell);
+  dec_class->load_from_buffer =
+      GST_DEBUG_FUNCPTR (gst_openmpt_dec_load_from_buffer);
+  dec_class->get_main_tags = GST_DEBUG_FUNCPTR (gst_openmpt_dec_get_main_tags);
+  dec_class->set_num_loops = GST_DEBUG_FUNCPTR (gst_openmpt_dec_set_num_loops);
+  dec_class->get_num_loops = GST_DEBUG_FUNCPTR (gst_openmpt_dec_get_num_loops);
+  dec_class->get_supported_output_modes =
+      GST_DEBUG_FUNCPTR (gst_openmpt_dec_get_supported_output_modes);
+  dec_class->decode = GST_DEBUG_FUNCPTR (gst_openmpt_dec_decode);
+  dec_class->set_current_subsong =
+      GST_DEBUG_FUNCPTR (gst_openmpt_dec_set_current_subsong);
+  dec_class->get_current_subsong =
+      GST_DEBUG_FUNCPTR (gst_openmpt_dec_get_current_subsong);
+  dec_class->get_num_subsongs =
+      GST_DEBUG_FUNCPTR (gst_openmpt_dec_get_num_subsongs);
+  dec_class->get_subsong_duration =
+      GST_DEBUG_FUNCPTR (gst_openmpt_dec_get_subsong_duration);
+  dec_class->get_subsong_tags =
+      GST_DEBUG_FUNCPTR (gst_openmpt_dec_get_subsong_tags);
+  dec_class->set_subsong_mode =
+      GST_DEBUG_FUNCPTR (gst_openmpt_dec_set_subsong_mode);
+
+  gst_element_class_set_static_metadata (element_class,
+      "OpenMPT-based module music decoder",
+      "Codec/Decoder/Audio",
+      "Decoders module files (MOD/S3M/XM/IT/MTM/...) using OpenMPT",
+      "Carlos Rafael Giani <dv@pseudoterminal.org>");
+
+  g_object_class_install_property (object_class,
+      PROP_MASTER_GAIN,
+      g_param_spec_int ("master-gain",
+          "Master gain",
+          "Gain to apply to the playback, in millibel",
+          -G_MAXINT, G_MAXINT,
+          DEFAULT_MASTER_GAIN, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+      );
+  g_object_class_install_property (object_class,
+      PROP_STEREO_SEPARATION,
+      g_param_spec_int ("stereo-separation",
+          "Stereo separation",
+          "Degree of separation for stereo channels, in percent",
+          0, 400,
+          DEFAULT_STEREO_SEPARATION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+      );
+  g_object_class_install_property (object_class,
+      PROP_FILTER_LENGTH,
+      g_param_spec_int ("filter-length",
+          "Filter length",
+          "Length of interpolation filter to use for the samples (0 = internal default)",
+          0, 8,
+          DEFAULT_FILTER_LENGTH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+      );
+  g_object_class_install_property (object_class,
+      PROP_VOLUME_RAMPING,
+      g_param_spec_int ("volume-ramping",
+          "Volume ramping",
+          "Volume ramping strength; higher value -> slower ramping (-1 = internal default)",
+          -1, 10,
+          DEFAULT_VOLUME_RAMPING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+      );
+  /* 4*4 => quad output with F32 samples; this ensures that no overflow can happen */
+  g_object_class_install_property (object_class,
+      PROP_OUTPUT_BUFFER_SIZE,
+      g_param_spec_uint ("output-buffer-size",
+          "Output buffer size",
+          "Size of each output buffer, in samples (actual size can be smaller "
+          "than this during flush or EOS)",
+          1, G_MAXUINT / (4 * 4),
+          DEFAULT_OUTPUT_BUFFER_SIZE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+      );
+}
+
+
+void
+gst_openmpt_dec_init (GstOpenMptDec * openmpt_dec)
+{
+  openmpt_dec->mod = NULL;
+
+  openmpt_dec->cur_subsong = 0;
+  openmpt_dec->num_subsongs = 0;
+  openmpt_dec->subsong_durations = NULL;
+
+  openmpt_dec->num_loops = 0;
+
+  openmpt_dec->master_gain = DEFAULT_MASTER_GAIN;
+  openmpt_dec->stereo_separation = DEFAULT_STEREO_SEPARATION;
+  openmpt_dec->filter_length = DEFAULT_FILTER_LENGTH;
+  openmpt_dec->volume_ramping = DEFAULT_VOLUME_RAMPING;
+
+  openmpt_dec->output_buffer_size = DEFAULT_OUTPUT_BUFFER_SIZE;
+
+  openmpt_dec->main_tags = NULL;
+
+  openmpt_dec->sample_format = DEFAULT_SAMPLE_FORMAT;
+  openmpt_dec->sample_rate = DEFAULT_SAMPLE_RATE;
+  openmpt_dec->num_channels = DEFAULT_NUM_CHANNELS;
+}
+
+
+static void
+gst_openmpt_dec_finalize (GObject * object)
+{
+  GstOpenMptDec *openmpt_dec;
+
+  g_return_if_fail (GST_IS_OPENMPT_DEC (object));
+  openmpt_dec = GST_OPENMPT_DEC (object);
+
+  if (openmpt_dec->main_tags != NULL)
+    gst_tag_list_unref (openmpt_dec->main_tags);
+
+  if (openmpt_dec->mod != NULL)
+    openmpt_module_destroy (openmpt_dec->mod);
+
+  g_free (openmpt_dec->subsong_durations);
+
+  G_OBJECT_CLASS (gst_openmpt_dec_parent_class)->finalize (object);
+}
+
+
+static void
+gst_openmpt_dec_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstNonstreamAudioDecoder *dec;
+  GstOpenMptDec *openmpt_dec;
+
+  dec = GST_NONSTREAM_AUDIO_DECODER (object);
+  openmpt_dec = GST_OPENMPT_DEC (object);
+
+  switch (prop_id) {
+    case PROP_MASTER_GAIN:
+    {
+      GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec);
+      openmpt_dec->master_gain = g_value_get_int (value);
+      if (openmpt_dec->mod != NULL)
+        openmpt_module_set_render_param (openmpt_dec->mod,
+            OPENMPT_MODULE_RENDER_MASTERGAIN_MILLIBEL,
+            openmpt_dec->master_gain);
+      GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec);
+      break;
+    }
+
+    case PROP_STEREO_SEPARATION:
+    {
+      GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec);
+      openmpt_dec->stereo_separation = g_value_get_int (value);
+      if (openmpt_dec->mod != NULL)
+        openmpt_module_set_render_param (openmpt_dec->mod,
+            OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT,
+            openmpt_dec->stereo_separation);
+      GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec);
+      break;
+    }
+
+    case PROP_FILTER_LENGTH:
+    {
+      GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec);
+      openmpt_dec->filter_length = g_value_get_int (value);
+      if (openmpt_dec->mod != NULL)
+        openmpt_module_set_render_param (openmpt_dec->mod,
+            OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH,
+            openmpt_dec->filter_length);
+      GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec);
+      break;
+    }
+
+    case PROP_VOLUME_RAMPING:
+    {
+      GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec);
+      openmpt_dec->volume_ramping = g_value_get_int (value);
+      if (openmpt_dec->mod != NULL)
+        openmpt_module_set_render_param (openmpt_dec->mod,
+            OPENMPT_MODULE_RENDER_VOLUMERAMPING_STRENGTH,
+            openmpt_dec->volume_ramping);
+      GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec);
+      break;
+    }
+
+    case PROP_OUTPUT_BUFFER_SIZE:
+    {
+      GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec);
+      openmpt_dec->output_buffer_size = g_value_get_uint (value);
+      GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec);
+      break;
+    }
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static void
+gst_openmpt_dec_get_property (GObject * object, guint prop_id, GValue * value,
+    GParamSpec * pspec)
+{
+  GstOpenMptDec *openmpt_dec = GST_OPENMPT_DEC (object);
+
+  switch (prop_id) {
+    case PROP_MASTER_GAIN:
+    {
+      GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
+      g_value_set_int (value, openmpt_dec->master_gain);
+      GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
+      break;
+    }
+
+    case PROP_STEREO_SEPARATION:
+    {
+      GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
+      g_value_set_int (value, openmpt_dec->stereo_separation);
+      GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
+      break;
+    }
+
+    case PROP_FILTER_LENGTH:
+    {
+      GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
+      g_value_set_int (value, openmpt_dec->filter_length);
+      GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
+      break;
+    }
+
+    case PROP_VOLUME_RAMPING:
+    {
+      GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
+      g_value_set_int (value, openmpt_dec->volume_ramping);
+      GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
+      break;
+    }
+
+    case PROP_OUTPUT_BUFFER_SIZE:
+    {
+      GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
+      g_value_set_uint (value, openmpt_dec->output_buffer_size);
+      GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
+      break;
+    }
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static gboolean
+gst_openmpt_dec_seek (GstNonstreamAudioDecoder * dec,
+    GstClockTime * new_position)
+{
+  GstOpenMptDec *openmpt_dec = GST_OPENMPT_DEC (dec);
+  g_return_val_if_fail (openmpt_dec->mod != NULL, FALSE);
+
+  openmpt_module_set_position_seconds (openmpt_dec->mod,
+      (double) (*new_position) / GST_SECOND);
+  *new_position = gst_openmpt_dec_tell (dec);
+
+  return TRUE;
+}
+
+
+static GstClockTime
+gst_openmpt_dec_tell (GstNonstreamAudioDecoder * dec)
+{
+  GstOpenMptDec *openmpt_dec = GST_OPENMPT_DEC (dec);
+  g_return_val_if_fail (openmpt_dec->mod != NULL, GST_CLOCK_TIME_NONE);
+
+  return (GstClockTime) (openmpt_module_get_position_seconds (openmpt_dec->mod)
+      * GST_SECOND);
+}
+
+
+static void
+gst_openmpt_dec_log_func (char const *message, void *user)
+{
+  GST_LOG_OBJECT (GST_OBJECT (user), "%s", message);
+}
+
+
+static void
+gst_openmpt_dec_add_metadata_to_tag_list (GstOpenMptDec * openmpt_dec,
+    GstTagList * tags, char const *key, gchar const *tag)
+{
+  char const *metadata = openmpt_module_get_metadata (openmpt_dec->mod, key);
+
+  if (metadata && *metadata) {
+    GST_DEBUG_OBJECT (openmpt_dec,
+        "adding metadata \"%s\" with key \"%s\" to tag list as tag \"%s\"",
+        metadata, key, tag);
+
+    if (g_strcmp0 (tag, GST_TAG_DATE_TIME) == 0) {
+      /* Special handling for date-time tags - interpret the
+       * metadata string as an iso8601 string and convert it
+       * to a GstDateTime value, since this is the data type
+       * that GST_TAG_DATE_TIME expects. */
+
+      GstDateTime *date_time = gst_date_time_new_from_iso8601_string (metadata);
+      if (date_time) {
+        GST_DEBUG_OBJECT (openmpt_dec,
+            "successfully created date-time object out of iso8601 string");
+        gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, tag, date_time, NULL);
+        gst_date_time_unref (date_time);
+      } else
+        GST_WARNING_OBJECT (openmpt_dec,
+            "could not create date-time object out of iso8601 string - not adding metadata to tags");
+    } else {
+      /* Default handling - just insert the metadata string as-is */
+      gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, tag, metadata, NULL);
+    }
+  } else
+    GST_DEBUG_OBJECT (openmpt_dec,
+        "attempted to add metadata with key \"%s\" to tag list as tag \"%s\", but none exists",
+        key, tag);
+
+  if (metadata)
+    openmpt_free_string (metadata);
+}
+
+
+static gboolean
+gst_openmpt_dec_load_from_buffer (GstNonstreamAudioDecoder * dec,
+    GstBuffer * source_data, guint initial_subsong,
+    GstNonstreamAudioSubsongMode initial_subsong_mode,
+    GstClockTime * initial_position,
+    GstNonstreamAudioOutputMode * initial_output_mode, gint * initial_num_loops)
+{
+  GstMapInfo map;
+  GstOpenMptDec *openmpt_dec;
+
+  openmpt_dec = GST_OPENMPT_DEC (dec);
+
+  /* First, determine the sample rate, channel count, and sample format to use */
+  openmpt_dec->sample_format = DEFAULT_SAMPLE_FORMAT;
+  openmpt_dec->sample_rate = DEFAULT_SAMPLE_RATE;
+  openmpt_dec->num_channels = DEFAULT_NUM_CHANNELS;
+  gst_nonstream_audio_decoder_get_downstream_info (dec,
+      &(openmpt_dec->sample_format), &(openmpt_dec->sample_rate),
+      &(openmpt_dec->num_channels));
+
+  /* Set output format */
+  if (!gst_nonstream_audio_decoder_set_output_format_simple (dec,
+          openmpt_dec->sample_rate,
+          openmpt_dec->sample_format, openmpt_dec->num_channels))
+    return FALSE;
+
+  /* Pass the module data to OpenMPT for loading */
+  gst_buffer_map (source_data, &map, GST_MAP_READ);
+#if OPENMPT_API_VERSION_AT_LEAST(0,3,0)
+  openmpt_dec->mod =
+      openmpt_module_create_from_memory2 (map.data, map.size,
+      gst_openmpt_dec_log_func, dec, NULL, NULL, NULL, NULL, NULL);
+#else
+  openmpt_dec->mod =
+      openmpt_module_create_from_memory (map.data, map.size,
+      gst_openmpt_dec_log_func, dec, NULL);
+#endif
+  gst_buffer_unmap (source_data, &map);
+
+  if (openmpt_dec->mod == NULL) {
+    GST_ERROR_OBJECT (dec, "loading module failed");
+    return FALSE;
+  }
+
+  /* Copy subsong states */
+  openmpt_dec->cur_subsong = initial_subsong;
+  openmpt_dec->cur_subsong_mode = initial_subsong_mode;
+
+  /* Query the number of subsongs available for logging and for checking
+   * the initial subsong index */
+  openmpt_dec->num_subsongs =
+      openmpt_module_get_num_subsongs (openmpt_dec->mod);
+  if (G_UNLIKELY (initial_subsong >= openmpt_dec->num_subsongs)) {
+    GST_WARNING_OBJECT (openmpt_dec,
+        "initial subsong %u out of bounds (there are %u subsongs) - setting it to 0",
+        initial_subsong, openmpt_dec->num_subsongs);
+    initial_subsong = 0;
+  }
+  GST_INFO_OBJECT (openmpt_dec, "%d subsong(s) available",
+      openmpt_dec->num_subsongs);
+
+  /* Query the OpenMPT default subsong (can be -1)
+   * The default subsong is the one that is initially selected, so we
+   * need to query it here, *before* any openmpt_module_select_subsong()
+   * calls are done */
+  {
+    gchar const *subsong_cstr =
+        openmpt_module_ctl_get (openmpt_dec->mod, "subsong");
+    gchar *endptr;
+
+    if (subsong_cstr != NULL) {
+      openmpt_dec->default_openmpt_subsong =
+          g_ascii_strtoll (subsong_cstr, &endptr, 10);
+      if (subsong_cstr == endptr) {
+        GST_WARNING_OBJECT (openmpt_dec,
+            "could not convert ctl string \"%s\" to subsong index - using default OpenMPT index -1 instead",
+            subsong_cstr);
+        openmpt_dec->default_openmpt_subsong = -1;
+      } else
+        GST_DEBUG_OBJECT (openmpt_dec, "default OpenMPT subsong index is %d",
+            openmpt_dec->default_openmpt_subsong);
+
+      openmpt_free_string (subsong_cstr);
+    } else {
+      GST_INFO_OBJECT (openmpt_dec,
+          "could not get subsong ctl string - using default OpenMPT index -1 instead");
+      openmpt_dec->default_openmpt_subsong = -1;
+    }
+  }
+
+  /* Seek to initial position */
+  if (*initial_position != 0) {
+    openmpt_module_set_position_seconds (openmpt_dec->mod,
+        (double) (*initial_position) / GST_SECOND);
+    *initial_position =
+        (GstClockTime) (openmpt_module_get_position_seconds (openmpt_dec->mod) *
+        GST_SECOND);
+  }
+
+  /* LOOPING output mode is not supported */
+  *initial_output_mode = GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY;
+
+  /* Query the durations of each subsong (if any exist) */
+  if (openmpt_dec->num_subsongs > 0) {
+    guint i;
+
+    openmpt_dec->subsong_durations =
+        g_try_malloc (openmpt_dec->num_subsongs * sizeof (double));
+    if (openmpt_dec->subsong_durations == NULL) {
+      GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec);
+      GST_ELEMENT_ERROR (openmpt_dec, RESOURCE, NO_SPACE_LEFT,
+          ("could not allocate memory for subsong duration array"), (NULL));
+      GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec);
+      return FALSE;
+    }
+
+    for (i = 0; i < openmpt_dec->num_subsongs; ++i) {
+      openmpt_module_select_subsong (openmpt_dec->mod, i);
+      openmpt_dec->subsong_durations[i] =
+          openmpt_module_get_duration_seconds (openmpt_dec->mod);
+    }
+  }
+
+  /* Select the initial subsong */
+  gst_openmpt_dec_select_subsong (openmpt_dec, initial_subsong_mode,
+      initial_subsong);
+
+  /* Set the number of loops, and query the actual number
+   * that was chosen by OpenMPT */
+  {
+    int32_t actual_repeat_count;
+    openmpt_module_set_repeat_count (openmpt_dec->mod, *initial_num_loops);
+    actual_repeat_count = openmpt_module_get_repeat_count (openmpt_dec->mod);
+
+    if (actual_repeat_count != *initial_num_loops) {
+      GST_DEBUG_OBJECT (openmpt_dec,
+          "requested num-loops value %d differs from actual value %d",
+          *initial_num_loops, actual_repeat_count);
+      *initial_num_loops = actual_repeat_count;
+    }
+  }
+
+  /* Set render parameters (adjustable via properties) */
+  openmpt_module_set_render_param (openmpt_dec->mod,
+      OPENMPT_MODULE_RENDER_MASTERGAIN_MILLIBEL, openmpt_dec->master_gain);
+  openmpt_module_set_render_param (openmpt_dec->mod,
+      OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT,
+      openmpt_dec->stereo_separation);
+  openmpt_module_set_render_param (openmpt_dec->mod,
+      OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH,
+      openmpt_dec->filter_length);
+  openmpt_module_set_render_param (openmpt_dec->mod,
+      OPENMPT_MODULE_RENDER_VOLUMERAMPING_STRENGTH,
+      openmpt_dec->volume_ramping);
+
+  /* Log the available metadata keys, and produce a
+   * tag list if any keys are available */
+  {
+    char const *metadata_keys =
+        openmpt_module_get_metadata_keys (openmpt_dec->mod);
+    if (metadata_keys != NULL) {
+      GstTagList *tags = gst_tag_list_new_empty ();
+
+      GST_DEBUG_OBJECT (dec, "metadata keys: [%s]", metadata_keys);
+      openmpt_free_string (metadata_keys);
+
+      gst_openmpt_dec_add_metadata_to_tag_list (openmpt_dec, tags, "title",
+          GST_TAG_TITLE);
+      gst_openmpt_dec_add_metadata_to_tag_list (openmpt_dec, tags, "artist",
+          GST_TAG_ARTIST);
+      gst_openmpt_dec_add_metadata_to_tag_list (openmpt_dec, tags, "message",
+          GST_TAG_COMMENT);
+      gst_openmpt_dec_add_metadata_to_tag_list (openmpt_dec, tags, "tracker",
+          GST_TAG_APPLICATION_NAME);
+      gst_openmpt_dec_add_metadata_to_tag_list (openmpt_dec, tags, "type_long",
+          GST_TAG_CODEC);
+      gst_openmpt_dec_add_metadata_to_tag_list (openmpt_dec, tags, "date",
+          GST_TAG_DATE_TIME);
+      gst_openmpt_dec_add_metadata_to_tag_list (openmpt_dec, tags,
+          "container_long", GST_TAG_CONTAINER_FORMAT);
+
+      openmpt_dec->main_tags = tags;
+    } else {
+      GST_DEBUG_OBJECT (dec,
+          "no metadata keys found - not producing a tag list");
+    }
+  }
+
+  /* Log any warnings that were produced by OpenMPT while loading */
+  {
+    char const *warnings =
+        openmpt_module_get_metadata (openmpt_dec->mod, "warnings");
+    if (warnings) {
+      if (*warnings)
+        GST_WARNING_OBJECT (openmpt_dec, "reported warnings during loading: %s",
+            warnings);
+      openmpt_free_string (warnings);
+    }
+  }
+
+  return TRUE;
+}
+
+
+static GstTagList *
+gst_openmpt_dec_get_main_tags (GstNonstreamAudioDecoder * dec)
+{
+  GstOpenMptDec *openmpt_dec = GST_OPENMPT_DEC (dec);
+  return gst_tag_list_ref (openmpt_dec->main_tags);
+}
+
+
+static gboolean
+gst_openmpt_dec_set_current_subsong (GstNonstreamAudioDecoder * dec,
+    guint subsong, GstClockTime * initial_position)
+{
+  GstOpenMptDec *openmpt_dec = GST_OPENMPT_DEC (dec);
+  g_return_val_if_fail (openmpt_dec->mod != NULL, FALSE);
+
+  if (gst_openmpt_dec_select_subsong (openmpt_dec,
+          openmpt_dec->cur_subsong_mode, subsong)) {
+    GST_DEBUG_OBJECT (openmpt_dec,
+        "selected subsong %u and switching subsong mode to SINGLE", subsong);
+    openmpt_dec->cur_subsong_mode = GST_NONSTREAM_AUDIO_SUBSONG_MODE_SINGLE;
+    openmpt_dec->cur_subsong = subsong;
+    *initial_position = 0;
+    return TRUE;
+  } else {
+    GST_ERROR_OBJECT (openmpt_dec, "could not select subsong %u", subsong);
+    return FALSE;
+  }
+}
+
+
+static guint
+gst_openmpt_dec_get_current_subsong (GstNonstreamAudioDecoder * dec)
+{
+  GstOpenMptDec *openmpt_dec = GST_OPENMPT_DEC (dec);
+  return openmpt_dec->cur_subsong;
+}
+
+
+static guint
+gst_openmpt_dec_get_num_subsongs (GstNonstreamAudioDecoder * dec)
+{
+  GstOpenMptDec *openmpt_dec = GST_OPENMPT_DEC (dec);
+  return openmpt_dec->num_subsongs;
+}
+
+
+static GstClockTime
+gst_openmpt_dec_get_subsong_duration (GstNonstreamAudioDecoder * dec,
+    guint subsong)
+{
+  GstOpenMptDec *openmpt_dec = GST_OPENMPT_DEC (dec);
+  return (GstClockTime) (openmpt_dec->subsong_durations[subsong] * GST_SECOND);
+}
+
+
+static GstTagList *
+gst_openmpt_dec_get_subsong_tags (GstNonstreamAudioDecoder * dec, guint subsong)
+{
+  GstOpenMptDec *openmpt_dec;
+  char const *name;
+
+  openmpt_dec = GST_OPENMPT_DEC (dec);
+
+  name = openmpt_module_get_subsong_name (openmpt_dec->mod, subsong);
+  if (name != NULL) {
+    GstTagList *tags = NULL;
+
+    if (*name) {
+      tags = gst_tag_list_new_empty ();
+      gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, "title", name, NULL);
+    }
+
+    openmpt_free_string (name);
+
+    return tags;
+  } else
+    return NULL;
+}
+
+
+static gboolean
+gst_openmpt_dec_set_subsong_mode (GstNonstreamAudioDecoder * dec,
+    GstNonstreamAudioSubsongMode mode, GstClockTime * initial_position)
+{
+  GstOpenMptDec *openmpt_dec = GST_OPENMPT_DEC (dec);
+  g_return_val_if_fail (openmpt_dec->mod != NULL, FALSE);
+
+  if (gst_openmpt_dec_select_subsong (openmpt_dec, mode,
+          openmpt_dec->cur_subsong)) {
+    GST_DEBUG_OBJECT (openmpt_dec, "set subsong mode");
+    openmpt_dec->cur_subsong_mode = mode;
+    *initial_position = 0;
+    return TRUE;
+  } else {
+    GST_ERROR_OBJECT (openmpt_dec, "could not set subsong mode");
+    return FALSE;
+  }
+}
+
+
+static gboolean
+gst_openmpt_dec_set_num_loops (GstNonstreamAudioDecoder * dec, gint num_loops)
+{
+  GstOpenMptDec *openmpt_dec = GST_OPENMPT_DEC (dec);
+  openmpt_dec->num_loops = num_loops;
+
+  if (openmpt_dec->mod != NULL) {
+    if (openmpt_module_set_repeat_count (openmpt_dec->mod, num_loops)) {
+      GST_DEBUG_OBJECT (openmpt_dec, "successfully set repeat count %d",
+          num_loops);
+      return TRUE;
+    } else {
+      GST_ERROR_OBJECT (openmpt_dec, "could not set repeat count %d",
+          num_loops);
+      return FALSE;
+    }
+  } else
+    return TRUE;
+}
+
+
+static gint
+gst_openmpt_dec_get_num_loops (GstNonstreamAudioDecoder * dec)
+{
+  GstOpenMptDec *openmpt_dec = GST_OPENMPT_DEC (dec);
+  return openmpt_dec->num_loops;
+}
+
+
+static guint
+gst_openmpt_dec_get_supported_output_modes (G_GNUC_UNUSED
+    GstNonstreamAudioDecoder * dec)
+{
+  return 1u << GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY;
+}
+
+
+static gboolean
+gst_openmpt_dec_decode (GstNonstreamAudioDecoder * dec, GstBuffer ** buffer,
+    guint * num_samples)
+{
+  GstOpenMptDec *openmpt_dec;
+  GstBuffer *outbuf;
+  GstMapInfo map;
+  size_t num_read_samples;
+  gsize outbuf_size;
+  GstAudioFormatInfo const *fmt_info;
+
+  openmpt_dec = GST_OPENMPT_DEC (dec);
+
+  fmt_info = gst_audio_format_get_info (openmpt_dec->sample_format);
+
+  /* Allocate output buffer */
+  outbuf_size =
+      openmpt_dec->output_buffer_size * (fmt_info->width / 8) *
+      openmpt_dec->num_channels;
+  outbuf =
+      gst_nonstream_audio_decoder_allocate_output_buffer (dec, outbuf_size);
+  if (G_UNLIKELY (outbuf == NULL))
+    return FALSE;
+
+  /* Write samples into the output buffer */
+
+  gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
+
+  switch (openmpt_dec->sample_format) {
+    case GST_AUDIO_FORMAT_S16:
+    {
+      int16_t *out_samples = (int16_t *) (map.data);
+      switch (openmpt_dec->num_channels) {
+        case 1:
+          num_read_samples =
+              openmpt_module_read_mono (openmpt_dec->mod,
+              openmpt_dec->sample_rate, openmpt_dec->output_buffer_size,
+              out_samples);
+          break;
+        case 2:
+          num_read_samples =
+              openmpt_module_read_interleaved_stereo (openmpt_dec->mod,
+              openmpt_dec->sample_rate, openmpt_dec->output_buffer_size,
+              out_samples);
+          break;
+        case 4:
+          num_read_samples =
+              openmpt_module_read_interleaved_quad (openmpt_dec->mod,
+              openmpt_dec->sample_rate, openmpt_dec->output_buffer_size,
+              out_samples);
+          break;
+        default:
+          g_assert_not_reached ();
+      }
+      break;
+    }
+    case GST_AUDIO_FORMAT_F32:
+    {
+      float *out_samples = (float *) (map.data);
+      switch (openmpt_dec->num_channels) {
+        case 1:
+          num_read_samples =
+              openmpt_module_read_float_mono (openmpt_dec->mod,
+              openmpt_dec->sample_rate, openmpt_dec->output_buffer_size,
+              out_samples);
+          break;
+        case 2:
+          num_read_samples =
+              openmpt_module_read_interleaved_float_stereo (openmpt_dec->mod,
+              openmpt_dec->sample_rate, openmpt_dec->output_buffer_size,
+              out_samples);
+          break;
+        case 4:
+          num_read_samples =
+              openmpt_module_read_interleaved_float_quad (openmpt_dec->mod,
+              openmpt_dec->sample_rate, openmpt_dec->output_buffer_size,
+              out_samples);
+          break;
+        default:
+          g_assert_not_reached ();
+      }
+      break;
+    }
+    default:
+    {
+      GST_ERROR_OBJECT (dec, "using unsupported sample format %s",
+          fmt_info->name);
+      g_assert_not_reached ();
+    }
+  }
+
+  gst_buffer_unmap (outbuf, &map);
+
+  if (num_read_samples == 0)
+    return FALSE;
+
+  *buffer = outbuf;
+  *num_samples = num_read_samples;
+
+  return TRUE;
+}
+
+
+static gboolean
+gst_openmpt_dec_select_subsong (GstOpenMptDec * openmpt_dec,
+    GstNonstreamAudioSubsongMode subsong_mode, gint openmpt_subsong)
+{
+  switch (subsong_mode) {
+    case GST_NONSTREAM_AUDIO_SUBSONG_MODE_SINGLE:
+      GST_DEBUG_OBJECT (openmpt_dec, "setting subsong mode to SINGLE");
+      return openmpt_module_select_subsong (openmpt_dec->mod, openmpt_subsong);
+
+    case GST_NONSTREAM_AUDIO_SUBSONG_MODE_ALL:
+      GST_DEBUG_OBJECT (openmpt_dec, "setting subsong mode to ALL");
+      return openmpt_module_select_subsong (openmpt_dec->mod, -1);
+
+    case GST_NONSTREAM_AUDIO_SUBSONG_MODE_DECODER_DEFAULT:
+      /* NOTE: The OpenMPT documentation recommends to not bother
+       * calling openmpt_module_select_subsong() if the decoder
+       * default shall be used. However, the user might have switched
+       * the subsong mode from SINGLE or ALL to DECODER_DEFAULT,
+       * in which case we *do* have to set the default subsong index.
+       * So, just set the default index here. */
+      GST_DEBUG_OBJECT (openmpt_dec, "setting subsong mode to DECODER_DEFAULT");
+      return openmpt_module_select_subsong (openmpt_dec->mod,
+          openmpt_dec->default_openmpt_subsong);
+
+    default:
+      g_assert_not_reached ();
+      return TRUE;
+  }
+}
diff --git a/ext/openmpt/gstopenmptdec.h b/ext/openmpt/gstopenmptdec.h
new file mode 100644 (file)
index 0000000..93e283f
--- /dev/null
@@ -0,0 +1,81 @@
+/* GStreamer
+ * Copyright (C) <2017> Carlos Rafael Giani <dv at pseudoterminal dot org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GST_OPENMPT_DEC_H__
+#define __GST_OPENMPT_DEC_H__
+
+
+#include <gst/gst.h>
+#include "gst/audio/gstnonstreamaudiodecoder.h"
+#include <libopenmpt/libopenmpt.h>
+
+
+G_BEGIN_DECLS
+
+
+typedef struct _GstOpenMptDec GstOpenMptDec;
+typedef struct _GstOpenMptDecClass GstOpenMptDecClass;
+
+
+#define GST_TYPE_OPENMPT_DEC             (gst_openmpt_dec_get_type())
+#define GST_OPENMPT_DEC(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_OPENMPT_DEC, GstOpenMptDec))
+#define GST_OPENMPT_DEC_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_OPENMPT_DEC, GstOpenMptDecClass))
+#define GST_IS_OPENMPT_DEC(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_OPENMPT_DEC))
+#define GST_IS_OPENMPT_DEC_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_OPENMPT_DEC))
+
+
+struct _GstOpenMptDec
+{
+  GstNonstreamAudioDecoder parent;
+  openmpt_module *mod;
+
+  guint cur_subsong, num_subsongs;
+  double *subsong_durations;
+  /* NOTE: this is of type int, not guint, because the value
+   * is defined by OpenMPT, and can be -1 (= "all subsongs") */
+  int default_openmpt_subsong;
+  GstNonstreamAudioSubsongMode cur_subsong_mode;
+
+  gint num_loops;
+
+  gint master_gain, stereo_separation, filter_length, volume_ramping;
+
+  GstAudioFormat sample_format;
+  gint sample_rate, num_channels;
+
+  guint output_buffer_size;
+
+  GstTagList *main_tags;
+};
+
+
+struct _GstOpenMptDecClass
+{
+  GstNonstreamAudioDecoderClass parent_class;
+};
+
+
+GType gst_openmpt_dec_get_type (void);
+
+
+G_END_DECLS
+
+
+#endif /* __GST_OPENMPT_DEC_H__ */
diff --git a/ext/openmpt/plugin.c b/ext/openmpt/plugin.c
new file mode 100644 (file)
index 0000000..6487fa2
--- /dev/null
@@ -0,0 +1,44 @@
+/* GStreamer
+ * Copyright (C) <2016> Carlos Rafael Giani <dv at pseudoterminal dot org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include "gstopenmptdec.h"
+
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  gboolean ret = TRUE;
+  ret = ret
+      && gst_element_register (plugin, "openmptdec", GST_RANK_PRIMARY + 2,
+      gst_openmpt_dec_get_type ());
+  return ret;
+}
+
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    openmpt,
+    "OpenMPT module player",
+    plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)