blob: 3a3814833f4249fcd1be25a86bbfbff95cca87e5 [file] [log] [blame]
/* GStreamer
* Copyright (C) <2016> Carlos Rafael Giani <dv at pseudoterminal dot org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:element-wildmididec
* @see_also: #GstWildmidiDec
*
* wildmididec decodes MIDI files.
* It uses <ulink url="https://www.mindwerks.net/projects/wildmidi/">WildMidi</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.mid ! wildmididec ! audioconvert ! audioresample ! autoaudiosink
* ]|
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#include <gst/gst.h>
#include <glib/gstdio.h>
#ifdef G_OS_WIN32
#ifndef R_OK
#define R_OK 4 /* Test for read permission */
#endif
#else
#include <unistd.h>
#endif
#include "gstwildmididec.h"
GST_DEBUG_CATEGORY_STATIC (wildmididec_debug);
#define GST_CAT_DEFAULT wildmididec_debug
/* This is hardcoded because the sample rate is set once,
* globally, in WildMidi_Init() */
#define WILDMIDI_SAMPLE_RATE 44100
/* WildMidi always outputs stereo data */
#define WILDMIDI_NUM_CHANNELS 2
#ifndef WILDMIDI_CFG
#define WILDMIDI_CFG "/etc/timidity.cfg"
#endif
#define DEFAULT_LOG_VOLUME_SCALE TRUE
#define DEFAULT_ENHANCED_RESAMPLING TRUE
#define DEFAULT_REVERB FALSE
#define DEFAULT_OUTPUT_BUFFER_SIZE 1024
enum
{
PROP_0,
PROP_LOG_VOLUME_SCALE,
PROP_ENHANCED_RESAMPLING,
PROP_REVERB,
PROP_OUTPUT_BUFFER_SIZE
};
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/midi; audio/riff-midi")
);
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) ", "
"layout = (string) interleaved, "
"rate = (int) " G_STRINGIFY (WILDMIDI_SAMPLE_RATE) ", "
"channels = (int) " G_STRINGIFY (WILDMIDI_NUM_CHANNELS)
)
);
G_DEFINE_TYPE (GstWildmidiDec, gst_wildmidi_dec,
GST_TYPE_NONSTREAM_AUDIO_DECODER);
static void gst_wildmidi_dec_finalize (GObject * object);
static void gst_wildmidi_dec_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_wildmidi_dec_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static gboolean gst_wildmidi_dec_seek (GstNonstreamAudioDecoder * dec,
GstClockTime * new_position);
static GstClockTime gst_wildmidi_dec_tell (GstNonstreamAudioDecoder * dec);
static gboolean gst_wildmidi_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 guint gst_wildmidi_dec_get_current_subsong (GstNonstreamAudioDecoder *
dec);
static guint gst_wildmidi_dec_get_num_subsongs (GstNonstreamAudioDecoder * dec);
static GstClockTime
gst_wildmidi_dec_get_subsong_duration (GstNonstreamAudioDecoder * dec,
guint subsong);
static guint
gst_wildmidi_dec_get_supported_output_modes (GstNonstreamAudioDecoder * dec);
static gboolean gst_wildmidi_dec_decode (GstNonstreamAudioDecoder * dec,
GstBuffer ** buffer, guint * num_samples);
static void gst_wildmidi_dec_update_options (GstWildmidiDec * wildmidi_dec);
static GMutex load_mutex;
static unsigned long init_refcount = 0;
static volatile gint wildmidi_initialized = 0;
static gchar *
gst_wildmidi_get_config_path (void)
{
/* This code is adapted from the original wildmidi
* gst-plugins-bad decoder element */
gchar *path = g_strdup (g_getenv ("WILDMIDI_CFG"));
GST_DEBUG
("trying configuration path \"%s\" from WILDMIDI_CFG environment variable",
GST_STR_NULL (path));
if (path && (g_access (path, R_OK) == -1)) {
g_free (path);
path = NULL;
}
if (path == NULL) {
path =
g_build_path (G_DIR_SEPARATOR_S, g_get_home_dir (), ".wildmidirc",
NULL);
GST_DEBUG ("trying configuration path \"%s\"", path);
if (path && (g_access (path, R_OK) == -1)) {
g_free (path);
path = NULL;
}
}
if (path == NULL) {
path =
g_build_path (G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S "etc",
"wildmidi.cfg", NULL);
GST_DEBUG ("trying configuration path \"%s\"", path);
if (path && (g_access (path, R_OK) == -1)) {
g_free (path);
path = NULL;
}
}
if (path == NULL) {
path =
g_build_path (G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S "etc", "wildmidi",
"wildmidi.cfg", NULL);
GST_DEBUG ("trying configuration path \"%s\"", path);
if (path && (g_access (path, R_OK) == -1)) {
g_free (path);
path = NULL;
}
}
if (path == NULL) {
path = g_strdup (WILDMIDI_CFG);
GST_DEBUG ("trying default configuration path \"%s\"", path);
if (path && (g_access (path, R_OK) == -1)) {
g_free (path);
path = NULL;
}
}
if (path == NULL) {
path =
g_build_path (G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S "etc",
"timidity.cfg", NULL);
GST_DEBUG ("trying TiMidity configuration path \"%s\"", path);
if (path && (g_access (path, R_OK) == -1)) {
g_free (path);
path = NULL;
}
}
if (path == NULL) {
path =
g_build_path (G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S "etc", "timidity",
"timidity.cfg", NULL);
GST_DEBUG ("trying TiMidity configuration path \"%s\"", path);
if (path && (g_access (path, R_OK) == -1)) {
g_free (path);
path = NULL;
}
}
return path;
}
static void
gst_wildmidi_init_library (void)
{
GST_DEBUG ("WildMidi init instance counter: %lu", init_refcount);
g_mutex_lock (&load_mutex);
if (init_refcount != 0) {
++init_refcount;
} else {
gchar *config_path = gst_wildmidi_get_config_path ();
if (config_path != NULL) {
int ret = WildMidi_Init (config_path, WILDMIDI_SAMPLE_RATE, 0);
g_free (config_path);
if (ret == 0) {
GST_DEBUG ("WildMidi initialized, version string: %s",
WildMidi_GetString (WM_GS_VERSION));
++init_refcount;
g_atomic_int_set (&wildmidi_initialized, 1);
} else {
GST_ERROR ("initializing WildMidi failed");
g_atomic_int_set (&wildmidi_initialized, 0);
}
} else {
GST_ERROR ("no config file, can't initialise");
g_atomic_int_set (&wildmidi_initialized, 0);
}
}
g_mutex_unlock (&load_mutex);
}
static void
gst_wildmidi_shutdown_library (void)
{
GST_DEBUG ("WildMidi init instance counter: %lu", init_refcount);
g_mutex_lock (&load_mutex);
if (init_refcount != 0) {
--init_refcount;
if (init_refcount == 0) {
WildMidi_Shutdown ();
GST_DEBUG ("WildMidi shut down");
g_atomic_int_set (&wildmidi_initialized, 0);
}
}
g_mutex_unlock (&load_mutex);
}
void
gst_wildmidi_dec_class_init (GstWildmidiDecClass * klass)
{
GObjectClass *object_class;
GstElementClass *element_class;
GstNonstreamAudioDecoderClass *dec_class;
GST_DEBUG_CATEGORY_INIT (wildmididec_debug, "wildmididec", 0,
"WildMidi-based MIDI 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_wildmidi_dec_finalize);
object_class->set_property =
GST_DEBUG_FUNCPTR (gst_wildmidi_dec_set_property);
object_class->get_property =
GST_DEBUG_FUNCPTR (gst_wildmidi_dec_get_property);
dec_class->tell = GST_DEBUG_FUNCPTR (gst_wildmidi_dec_tell);
dec_class->seek = GST_DEBUG_FUNCPTR (gst_wildmidi_dec_seek);
dec_class->load_from_buffer =
GST_DEBUG_FUNCPTR (gst_wildmidi_dec_load_from_buffer);
dec_class->get_current_subsong =
GST_DEBUG_FUNCPTR (gst_wildmidi_dec_get_current_subsong);
dec_class->get_num_subsongs =
GST_DEBUG_FUNCPTR (gst_wildmidi_dec_get_num_subsongs);
dec_class->get_subsong_duration =
GST_DEBUG_FUNCPTR (gst_wildmidi_dec_get_subsong_duration);
dec_class->get_supported_output_modes =
GST_DEBUG_FUNCPTR (gst_wildmidi_dec_get_supported_output_modes);
dec_class->decode = GST_DEBUG_FUNCPTR (gst_wildmidi_dec_decode);
gst_element_class_set_static_metadata (element_class,
"WildMidi-based MIDI music decoder",
"Codec/Decoder/Audio",
"Decodes MIDI music using WildMidi",
"Carlos Rafael Giani <dv@pseudoterminal.org>");
g_object_class_install_property (object_class,
PROP_LOG_VOLUME_SCALE,
g_param_spec_boolean ("log-volume-scale",
"Logarithmic volume scale",
"Use a logarithmic volume scale if set to TRUE, or a linear scale if set to FALSE",
DEFAULT_LOG_VOLUME_SCALE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
);
g_object_class_install_property (object_class,
PROP_ENHANCED_RESAMPLING,
g_param_spec_boolean ("enhanced-resampling",
"Enhanced resampling",
"Use enhanced resampling if set to TRUE, or linear interpolation if set to FALSE",
DEFAULT_ENHANCED_RESAMPLING,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
);
g_object_class_install_property (object_class,
PROP_REVERB,
g_param_spec_boolean ("reverb",
"Reverb",
"Whether or not to enable the WildMidi 8 reflection reverb engine to add more depth to the sound",
DEFAULT_REVERB, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
);
/* 2*2 => stereo output with S16 samples; the division 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 / (2 * 2),
DEFAULT_OUTPUT_BUFFER_SIZE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
);
}
void
gst_wildmidi_dec_init (GstWildmidiDec * wildmidi_dec)
{
wildmidi_dec->song = NULL;
wildmidi_dec->log_volume_scale = DEFAULT_LOG_VOLUME_SCALE;
wildmidi_dec->enhanced_resampling = DEFAULT_ENHANCED_RESAMPLING;
wildmidi_dec->reverb = DEFAULT_REVERB;
wildmidi_dec->output_buffer_size = DEFAULT_OUTPUT_BUFFER_SIZE;
gst_wildmidi_init_library ();
}
static void
gst_wildmidi_dec_finalize (GObject * object)
{
GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (object);
if (wildmidi_dec->song != NULL)
WildMidi_Close (wildmidi_dec->song);
gst_wildmidi_shutdown_library ();
G_OBJECT_CLASS (gst_wildmidi_dec_parent_class)->finalize (object);
}
static void
gst_wildmidi_dec_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstWildmidiDec *wildmidi_dec;
wildmidi_dec = GST_WILDMIDI_DEC (object);
switch (prop_id) {
case PROP_LOG_VOLUME_SCALE:
GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
wildmidi_dec->log_volume_scale = g_value_get_boolean (value);
gst_wildmidi_dec_update_options (wildmidi_dec);
GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
break;
case PROP_ENHANCED_RESAMPLING:
GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
wildmidi_dec->enhanced_resampling = g_value_get_boolean (value);
gst_wildmidi_dec_update_options (wildmidi_dec);
GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
break;
case PROP_REVERB:
GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
wildmidi_dec->reverb = g_value_get_boolean (value);
gst_wildmidi_dec_update_options (wildmidi_dec);
GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
break;
case PROP_OUTPUT_BUFFER_SIZE:
GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
wildmidi_dec->output_buffer_size = g_value_get_uint (value);
GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_wildmidi_dec_get_property (GObject * object, guint prop_id, GValue * value,
GParamSpec * pspec)
{
GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (object);
switch (prop_id) {
case PROP_LOG_VOLUME_SCALE:
GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
g_value_set_boolean (value, wildmidi_dec->log_volume_scale);
GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
break;
case PROP_ENHANCED_RESAMPLING:
GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
g_value_set_boolean (value, wildmidi_dec->enhanced_resampling);
GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
break;
case PROP_REVERB:
GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
g_value_set_boolean (value, wildmidi_dec->reverb);
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, wildmidi_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_wildmidi_dec_seek (GstNonstreamAudioDecoder * dec,
GstClockTime * new_position)
{
GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (dec);
unsigned long int sample_pos =
gst_util_uint64_scale_int (*new_position, WILDMIDI_SAMPLE_RATE,
GST_SECOND);
if (G_UNLIKELY (wildmidi_dec->song == NULL))
return FALSE;
WildMidi_FastSeek (wildmidi_dec->song, &sample_pos);
*new_position =
gst_util_uint64_scale_int (sample_pos, GST_SECOND, WILDMIDI_SAMPLE_RATE);
return TRUE;
}
static GstClockTime
gst_wildmidi_dec_tell (GstNonstreamAudioDecoder * dec)
{
GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (dec);
struct _WM_Info *info;
if (G_UNLIKELY (wildmidi_dec->song == NULL))
return GST_CLOCK_TIME_NONE;
info = WildMidi_GetInfo (wildmidi_dec->song);
return gst_util_uint64_scale_int (info->current_sample, GST_SECOND,
WILDMIDI_SAMPLE_RATE);
}
static gboolean
gst_wildmidi_dec_load_from_buffer (GstNonstreamAudioDecoder * dec,
GstBuffer * source_data, G_GNUC_UNUSED guint initial_subsong,
G_GNUC_UNUSED GstNonstreamAudioSubsongMode initial_subsong_mode,
GstClockTime * initial_position,
GstNonstreamAudioOutputMode * initial_output_mode,
G_GNUC_UNUSED gint * initial_num_loops)
{
GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (dec);
GstMapInfo buffer_map;
if (g_atomic_int_get (&wildmidi_initialized) == 0) {
GST_ERROR_OBJECT (wildmidi_dec,
"Could not start loading: WildMidi is not initialized");
return FALSE;
}
/* Set output format */
if (!gst_nonstream_audio_decoder_set_output_format_simple (dec,
WILDMIDI_SAMPLE_RATE, GST_AUDIO_FORMAT_S16, WILDMIDI_NUM_CHANNELS))
return FALSE;
/* Load MIDI */
gst_buffer_map (source_data, &buffer_map, GST_MAP_READ);
wildmidi_dec->song = WildMidi_OpenBuffer (buffer_map.data, buffer_map.size);
gst_buffer_unmap (source_data, &buffer_map);
if (wildmidi_dec->song == NULL) {
GST_ERROR_OBJECT (wildmidi_dec, "Could not load MIDI tune");
return FALSE;
}
gst_wildmidi_dec_update_options (wildmidi_dec);
/* Seek to initial position */
if (*initial_position != 0) {
unsigned long int sample_pos =
gst_util_uint64_scale_int (*initial_position, WILDMIDI_SAMPLE_RATE,
GST_SECOND);
WildMidi_FastSeek (wildmidi_dec->song, &sample_pos);
*initial_position =
gst_util_uint64_scale_int (sample_pos, GST_SECOND,
WILDMIDI_SAMPLE_RATE);
}
/* LOOPING output mode is not supported */
*initial_output_mode = GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY;
return TRUE;
}
static guint
gst_wildmidi_dec_get_current_subsong (G_GNUC_UNUSED GstNonstreamAudioDecoder *
dec)
{
return 0;
}
static guint
gst_wildmidi_dec_get_num_subsongs (G_GNUC_UNUSED GstNonstreamAudioDecoder * dec)
{
return 1;
}
static GstClockTime
gst_wildmidi_dec_get_subsong_duration (GstNonstreamAudioDecoder * dec,
G_GNUC_UNUSED guint subsong)
{
GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (dec);
struct _WM_Info *info;
if (G_UNLIKELY (wildmidi_dec->song == NULL))
return GST_CLOCK_TIME_NONE;
info = WildMidi_GetInfo (wildmidi_dec->song);
return gst_util_uint64_scale_int (info->approx_total_samples, GST_SECOND,
WILDMIDI_SAMPLE_RATE);
}
static guint
gst_wildmidi_dec_get_supported_output_modes (G_GNUC_UNUSED
GstNonstreamAudioDecoder * dec)
{
return 1u << GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY;
}
static gboolean
gst_wildmidi_dec_decode (GstNonstreamAudioDecoder * dec, GstBuffer ** buffer,
guint * num_samples)
{
GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (dec);
GstMapInfo info;
GstBuffer *outbuf;
gsize outbuf_size;
int decoded_size_in_bytes;
if (G_UNLIKELY (wildmidi_dec->song == NULL))
return FALSE;
/* Allocate output buffer
* Multiply by 2 to accomodate for the sample size (16 bit = 2 byte) */
outbuf_size = wildmidi_dec->output_buffer_size * 2 * WILDMIDI_NUM_CHANNELS;
outbuf =
gst_nonstream_audio_decoder_allocate_output_buffer (dec, outbuf_size);
if (G_UNLIKELY (outbuf == NULL))
return FALSE;
/* The actual decoding */
gst_buffer_map (outbuf, &info, GST_MAP_WRITE);
decoded_size_in_bytes =
WildMidi_GetOutput (wildmidi_dec->song, (int8_t *) (info.data),
info.size);
gst_buffer_unmap (outbuf, &info);
if (decoded_size_in_bytes == 0) {
gst_buffer_unref (outbuf);
return FALSE;
}
*buffer = outbuf;
*num_samples = decoded_size_in_bytes / 2 / WILDMIDI_NUM_CHANNELS;
return TRUE;
}
static void
gst_wildmidi_dec_update_options (GstWildmidiDec * wildmidi_dec)
{
unsigned short int options = 0;
if (wildmidi_dec->song == NULL)
return;
if (wildmidi_dec->log_volume_scale)
options |= WM_MO_LOG_VOLUME;
if (wildmidi_dec->enhanced_resampling)
options |= WM_MO_ENHANCED_RESAMPLING;
if (wildmidi_dec->reverb)
options |= WM_MO_REVERB;
WildMidi_SetOption (wildmidi_dec->song,
WM_MO_LOG_VOLUME | WM_MO_ENHANCED_RESAMPLING | WM_MO_REVERB, options);
}
static gboolean
plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, "wildmididec", GST_RANK_MARGINAL,
gst_wildmidi_dec_get_type ());
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
wildmidi,
"WildMidi-based MIDI playback plugin",
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)