From e7a5fdfde429b04d240211523c536438627a2a52 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Wed, 27 Jul 2016 02:22:26 +0200 Subject: [PATCH] openmpt: Add openmptdec element https://bugzilla.gnome.org/show_bug.cgi?id=768576 --- configure.ac | 10 + ext/Makefile.am | 8 + ext/openmpt/Makefile.am | 18 + ext/openmpt/gstopenmptdec.c | 968 ++++++++++++++++++++++++++++++++++++ ext/openmpt/gstopenmptdec.h | 81 +++ ext/openmpt/plugin.c | 44 ++ 6 files changed, 1129 insertions(+) create mode 100644 ext/openmpt/Makefile.am create mode 100644 ext/openmpt/gstopenmptdec.c create mode 100644 ext/openmpt/gstopenmptdec.h create mode 100644 ext/openmpt/plugin.c diff --git a/configure.ac b/configure.ac index 6c3805c153..151026fc3e 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/ext/Makefile.am b/ext/Makefile.am index 534b9ac7b8..bc2abed317 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -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 index 0000000000..a699a11e6f --- /dev/null +++ b/ext/openmpt/Makefile.am @@ -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 index 0000000000..c4830f6f24 --- /dev/null +++ b/ext/openmpt/gstopenmptdec.c @@ -0,0 +1,968 @@ +/* GStreamer + * Copyright (C) <2017> Carlos Rafael Giani + * + * 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 OpenMPT library + * for this purpose. It can be autoplugged and therefore works with decodebin. + * + * + * Example launch line + * |[ + * gst-launch-1.0 filesrc location=media/example.it ! openmptdec ! audioconvert ! audioresample ! autoaudiosink + * ]| + * + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#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 "); + + 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 index 0000000000..93e283f4f0 --- /dev/null +++ b/ext/openmpt/gstopenmptdec.h @@ -0,0 +1,81 @@ +/* GStreamer + * Copyright (C) <2017> Carlos Rafael Giani + * + * 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 +#include "gst/audio/gstnonstreamaudiodecoder.h" +#include + + +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 index 0000000000..6487fa2b61 --- /dev/null +++ b/ext/openmpt/plugin.c @@ -0,0 +1,44 @@ +/* GStreamer + * Copyright (C) <2016> Carlos Rafael Giani + * + * 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 +#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)