blob: 4c0bba1aa58ea863f8d81743aa0a13247d12efea [file] [log] [blame]
/* 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"
#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);
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;
}
}