/* 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](https://lib.openmpt.org) 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" #ifndef OPENMPT_API_VERSION_AT_LEAST #define OPENMPT_API_VERSION_AT_LEAST(x, y, z) (FALSE) #endif 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); GST_ELEMENT_REGISTER_DEFINE (openmptdec, "openmptdec", GST_RANK_PRIMARY + 2, gst_openmpt_dec_get_type ()); 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 */ { #if OPENMPT_API_VERSION_AT_LEAST(0,5,0) gchar const *subsong_cstr = openmpt_module_ctl_get_text (openmpt_dec->mod, "subsong"); #else gchar const *subsong_cstr = openmpt_module_ctl_get (openmpt_dec->mod, "subsong"); #endif 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; } }