From ff32a4297f2d50cd5288b2cac930f61ce783bc00 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Wed, 8 Mar 2017 22:17:41 +0100 Subject: [PATCH] wildmidi: Port to 1.0 on top of the nonstreamaudiodecoder base class https://bugzilla.gnome.org/show_bug.cgi?id=768576 --- docs/plugins/Makefile.am | 2 +- ext/wildmidi/Makefile.am | 16 +- ext/wildmidi/gstwildmidi.c | 1038 --------------------------------- ext/wildmidi/gstwildmidi.h | 90 --- ext/wildmidi/gstwildmididec.c | 683 ++++++++++++++++++++++ ext/wildmidi/gstwildmididec.h | 69 +++ 6 files changed, 765 insertions(+), 1133 deletions(-) delete mode 100644 ext/wildmidi/gstwildmidi.c delete mode 100644 ext/wildmidi/gstwildmidi.h create mode 100644 ext/wildmidi/gstwildmididec.c create mode 100644 ext/wildmidi/gstwildmididec.h diff --git a/docs/plugins/Makefile.am b/docs/plugins/Makefile.am index 69e8f882f0..7f91636129 100644 --- a/docs/plugins/Makefile.am +++ b/docs/plugins/Makefile.am @@ -103,7 +103,7 @@ EXTRA_HFILES = \ $(top_srcdir)/ext/rtmp/gstrtmpsink.h \ $(top_srcdir)/ext/spandsp/gstspanplc.h \ $(top_srcdir)/ext/spandsp/gstdtmfdetect.h \ - $(top_srcdir)/ext/wildmidi/gstwildmidi.h \ + $(top_srcdir)/ext/wildmidi/gstwildmididec.h \ $(top_srcdir)/ext/voaacenc/gstvoaacenc.h \ $(top_srcdir)/ext/voamrwbenc/gstvoamrwbenc.h \ $(top_srcdir)/ext/webrtcdsp/gstwebrtcdsp.h \ diff --git a/ext/wildmidi/Makefile.am b/ext/wildmidi/Makefile.am index 42f88c0308..63fa690ebb 100644 --- a/ext/wildmidi/Makefile.am +++ b/ext/wildmidi/Makefile.am @@ -5,14 +5,22 @@ if USE_WILDMIDI plugin_LTLIBRARIES += libgstwildmidi.la # sources used to compile this plug-in -libgstwildmidi_la_SOURCES = gstwildmidi.c +libgstwildmidi_la_SOURCES = gstwildmididec.c # flags used to compile this plugin # add other _CFLAGS and _LIBS as needed -libgstwildmidi_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(WILDMIDI_CFLAGS) -libgstwildmidi_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) $(WILDMIDI_LIBS) +libgstwildmidi_la_CFLAGS = \ + -I$(top_srcdir)/gst-libs \ + -I$(top_builddir)/gst-libs \ + $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) $(WILDMIDI_CFLAGS) +libgstwildmidi_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_LIBS) $(GST_BASE_LIBS) $(WILDMIDI_LIBS) libgstwildmidi_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) endif -noinst_HEADERS = gstwildmidi.h +noinst_HEADERS = gstwildmididec.h diff --git a/ext/wildmidi/gstwildmidi.c b/ext/wildmidi/gstwildmidi.c deleted file mode 100644 index 89bdcebb3f..0000000000 --- a/ext/wildmidi/gstwildmidi.c +++ /dev/null @@ -1,1038 +0,0 @@ -/* - * gstwildmidi - wildmidi plugin for gstreamer - * - * Copyright 2007 Wouter Paesen - * - * 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-wildmidi - * @title: wildmidi - * @see_also: timidity - * - * This element renders midi-files as audio streams using - * Wildmidi. - * It offers better sound quality compared to the timidity element. Wildmidi - * uses the same sound-patches as timidity (it tries the path in $WILDMIDI_CFG, - * $HOME/.wildmidirc and /etc/wildmidi.cfg) - * - * ## Example pipeline - * |[ - * gst-launch-1.0 filesrc location=song.mid ! wildmidi ! alsasink - * ]| This example pipeline will parse the midi and render to raw audio which is - * played via alsa. - * - */ - -#ifdef HAVE_CONFIG_H -# include -#endif - -#define WILDMIDI_RATE 44100 -#define WILDMIDI_BPS (2 * 2) - -#include -#include -#include -#include -#include -#include - -#include "gstwildmidi.h" - -#ifndef WILDMIDI_CFG -#define WILDMIDI_CFG "/etc/timidity.cfg" -#endif - -GST_DEBUG_CATEGORY_STATIC (gst_wildmidi_debug); -#define GST_CAT_DEFAULT gst_wildmidi_debug - -enum -{ - /* FILL ME */ - LAST_SIGNAL -}; - -enum -{ - PROP_0, - PROP_LINEAR_VOLUME, - PROP_HIGH_QUALITY, - /* FILL ME */ -}; - -#define DEFAULT_LINEAR_VOLUME TRUE -#define DEFAULT_HIGH_QUALITY TRUE - -static void gst_wildmidi_finalize (GObject * object); - -static gboolean gst_wildmidi_sink_event (GstPad * pad, GstObject * parent, - GstEvent * event); -static gboolean gst_wildmidi_src_event (GstPad * pad, GstObject * parent, - GstEvent * event); - -static GstStateChangeReturn gst_wildmidi_change_state (GstElement * element, - GstStateChange transition); -static gboolean gst_wildmidi_activate (GstPad * pad, GstObject * parent); -static gboolean gst_wildmidi_activatemode (GstPad * pad, GstObject * parent, - GstPadMode mode, gboolean active); - -static void gst_wildmidi_loop (GstPad * sinkpad); -static GstFlowReturn gst_wildmidi_chain (GstPad * sinkpad, GstObject * parent, - GstBuffer * buffer); - -static gboolean gst_wildmidi_src_query (GstPad * pad, GstObject * parent, - GstQuery * query); - -static void gst_wildmidi_set_property (GObject * object, guint prop_id, - const GValue * value, GParamSpec * pspec); -static void gst_wildmidi_get_property (GObject * object, guint prop_id, - GValue * value, GParamSpec * pspec); - -static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", - GST_PAD_SINK, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/midi; audio/riff-midi") - ); - -static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", - GST_PAD_SRC, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/x-raw, " - "format = (string) S16LE, " - "rate = (int) 44100, " - "channels = (int) 2, " "layout = (string) interleaved")); - -#define parent_class gst_wildmidi_parent_class -G_DEFINE_TYPE (GstWildmidi, gst_wildmidi, GST_TYPE_ELEMENT); - -static gboolean -wildmidi_open_config (void) -{ - gchar *path = g_strdup (g_getenv ("WILDMIDI_CFG")); - gint ret; - - GST_DEBUG ("trying %s", 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 %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 %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 %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 %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 %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 %s", path); - if (path && (g_access (path, R_OK) == -1)) { - g_free (path); - path = NULL; - } - } - - if (path == NULL) { - /* I've created a symlink to get it playing - * ln -s /usr/share/timidity/timidity.cfg /etc/wildmidi.cfg - * we could make it use : WILDMIDI_CFG - * but unfortunately it fails to create a proper filename if the config - * has a redirect - * http://sourceforge.net/tracker/index.php?func=detail&aid=1657358&group_id=42635&atid=433744 - */ - GST_WARNING ("no config file, can't initialise"); - return FALSE; - } - - /* this also initializes a some filter and stuff and thus is slow */ - ret = WildMidi_Init (path, WILDMIDI_RATE, 0); - g_free (path); - - return (ret == 0); -} - -/* initialize the plugin's class */ -static void -gst_wildmidi_class_init (GstWildmidiClass * klass) -{ - GObjectClass *gobject_class; - GstElementClass *gstelement_class; - - gobject_class = (GObjectClass *) klass; - gstelement_class = (GstElementClass *) klass; - - gobject_class->finalize = gst_wildmidi_finalize; - gobject_class->set_property = gst_wildmidi_set_property; - gobject_class->get_property = gst_wildmidi_get_property; - - g_object_class_install_property (gobject_class, PROP_LINEAR_VOLUME, - g_param_spec_boolean ("linear-volume", "Linear volume", - "Linear volume", DEFAULT_LINEAR_VOLUME, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (gobject_class, PROP_HIGH_QUALITY, - g_param_spec_boolean ("high-quality", "High Quality", - "High Quality", DEFAULT_HIGH_QUALITY, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - gst_element_class_add_static_pad_template (gstelement_class, &src_factory); - gst_element_class_add_static_pad_template (gstelement_class, &sink_factory); - gst_element_class_set_static_metadata (gstelement_class, "WildMidi", - "Codec/Decoder/Audio", - "Midi Synthesizer Element", "Wouter Paesen "); - - gstelement_class->change_state = gst_wildmidi_change_state; -} - -/* initialize the new element - * instantiate pads and add them to element - * set functions - * initialize structure - */ -static void -gst_wildmidi_init (GstWildmidi * filter) -{ - filter->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink"); - - gst_pad_set_activatemode_function (filter->sinkpad, - gst_wildmidi_activatemode); - gst_pad_set_activate_function (filter->sinkpad, gst_wildmidi_activate); - gst_pad_set_event_function (filter->sinkpad, gst_wildmidi_sink_event); - gst_pad_set_chain_function (filter->sinkpad, gst_wildmidi_chain); - gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad); - - filter->srcpad = gst_pad_new_from_static_template (&src_factory, "src"); - - gst_pad_set_query_function (filter->srcpad, gst_wildmidi_src_query); - gst_pad_set_event_function (filter->srcpad, gst_wildmidi_src_event); - gst_pad_use_fixed_caps (filter->srcpad); - - gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad); - - gst_segment_init (filter->o_segment, GST_FORMAT_DEFAULT); - - filter->adapter = gst_adapter_new (); - - filter->bytes_per_frame = WILDMIDI_BPS; - - filter->high_quality = DEFAULT_HIGH_QUALITY; - filter->linear_volume = DEFAULT_LINEAR_VOLUME; -} - -static void -gst_wildmidi_finalize (GObject * object) -{ - GstWildmidi *wildmidi; - - wildmidi = GST_WILDMIDI (object); - - g_object_unref (wildmidi->adapter); - - G_OBJECT_CLASS (parent_class)->finalize (object); -} - -static gboolean -gst_wildmidi_src_convert (GstWildmidi * wildmidi, - GstFormat src_format, gint64 src_value, - GstFormat * dest_format, gint64 * dest_value) -{ - gboolean res = TRUE; - gint64 frames; - - if (src_format == *dest_format || src_value == -1) { - *dest_value = src_value; - goto done; - } - - switch (src_format) { - case GST_FORMAT_TIME: - frames = gst_util_uint64_scale_int (src_value, WILDMIDI_RATE, GST_SECOND); - break; - case GST_FORMAT_BYTES: - frames = src_value / (wildmidi->bytes_per_frame); - break; - case GST_FORMAT_DEFAULT: - frames = src_value; - break; - default: - res = FALSE; - goto done; - } - - switch (*dest_format) { - case GST_FORMAT_TIME: - *dest_value = - gst_util_uint64_scale_int (frames, GST_SECOND, WILDMIDI_RATE); - break; - case GST_FORMAT_BYTES: - *dest_value = frames * wildmidi->bytes_per_frame; - break; - case GST_FORMAT_DEFAULT: - *dest_value = frames; - break; - default: - res = FALSE; - break; - } - -done: - return res; -} - -static gboolean -gst_wildmidi_src_query (GstPad * pad, GstObject * parent, GstQuery * query) -{ - gboolean res = TRUE; - GstWildmidi *wildmidi = GST_WILDMIDI (parent); - GstFormat src_format, dst_format; - gint64 src_value, dst_value; - - if (!wildmidi->song) - return FALSE; - - switch (GST_QUERY_TYPE (query)) { - case GST_QUERY_DURATION: - gst_query_set_duration (query, GST_FORMAT_TIME, - gst_util_uint64_scale_int (wildmidi->o_len, GST_SECOND, - WILDMIDI_RATE)); - break; - case GST_QUERY_POSITION: - gst_query_set_position (query, GST_FORMAT_TIME, - gst_util_uint64_scale_int (wildmidi->o_segment->position, GST_SECOND, - WILDMIDI_RATE)); - break; - case GST_QUERY_CONVERT: - gst_query_parse_convert (query, &src_format, &src_value, - &dst_format, NULL); - - res = - gst_wildmidi_src_convert (wildmidi, src_format, src_value, - &dst_format, &dst_value); - if (res) - gst_query_set_convert (query, src_format, src_value, dst_format, - dst_value); - - break; - case GST_QUERY_FORMATS: - gst_query_set_formats (query, 3, - GST_FORMAT_TIME, GST_FORMAT_BYTES, GST_FORMAT_DEFAULT); - break; - case GST_QUERY_SEGMENT:{ - GstFormat format; - gint64 start, stop; - - format = wildmidi->o_segment->format; - - start = - gst_segment_to_stream_time (wildmidi->o_segment, format, - wildmidi->o_segment->start); - if ((stop = wildmidi->o_segment->stop) == -1) - stop = wildmidi->o_segment->duration; - else - stop = gst_segment_to_stream_time (wildmidi->o_segment, format, stop); - - gst_query_set_segment (query, wildmidi->o_segment->rate, format, start, - stop); - res = TRUE; - break; - } - case GST_QUERY_SEEKING: - gst_query_set_seeking (query, wildmidi->o_segment->format, - TRUE, 0, wildmidi->o_len); - break; - default: - res = gst_pad_query_default (pad, parent, query); - break; - } - - return res; -} - -static GstEvent * -gst_wildmidi_get_new_segment_event (GstWildmidi * wildmidi, GstFormat format) -{ - gint64 start = 0, stop = -1, time = 0; - GstSegment *segment, newseg; - GstEvent *event; - GstFormat src_format; - - segment = wildmidi->o_segment; - src_format = segment->format; - newseg = *segment; - - /* convert the segment values to the target format */ - gst_wildmidi_src_convert (wildmidi, src_format, segment->start, &format, - &start); - gst_wildmidi_src_convert (wildmidi, src_format, segment->stop, &format, - &stop); - gst_wildmidi_src_convert (wildmidi, src_format, segment->time, &format, - &time); - - newseg.format = format; - newseg.start = start; - newseg.stop = stop; - newseg.time = time; - - event = gst_event_new_segment (&newseg); - - return event; -} - -static gboolean -gst_wildmidi_do_seek (GstWildmidi * wildmidi, GstEvent * event) -{ - gdouble rate; - GstFormat src_format, dst_format; - GstSeekFlags flags; - GstSeekType start_type, stop_type; - gint64 start, stop; - gboolean flush, update; -#ifdef HAVE_WILDMIDI_0_2_2 - gboolean accurate; -#endif - gboolean res; - unsigned long int sample; - GstSegment *segment; - - if (!wildmidi->song) - return FALSE; - - gst_event_parse_seek (event, &rate, &src_format, &flags, - &start_type, &start, &stop_type, &stop); - - /* convert the input format to samples */ - dst_format = GST_FORMAT_DEFAULT; - res = TRUE; - if (start_type != GST_SEEK_TYPE_NONE) { - res = - gst_wildmidi_src_convert (wildmidi, src_format, start, &dst_format, - &start); - } - if (res && stop_type != GST_SEEK_TYPE_NONE) { - res = - gst_wildmidi_src_convert (wildmidi, src_format, stop, &dst_format, - &stop); - } - /* unsupported format */ - if (!res) - return res; - - flush = ((flags & GST_SEEK_FLAG_FLUSH) == GST_SEEK_FLAG_FLUSH); -#ifdef HAVE_WILDMIDI_0_2_2 - accurate = ((flags & GST_SEEK_FLAG_ACCURATE) == GST_SEEK_FLAG_ACCURATE); -#endif - - if (flush) { - GST_DEBUG ("performing flush"); - gst_pad_push_event (wildmidi->srcpad, gst_event_new_flush_start ()); - } else { - gst_pad_stop_task (wildmidi->sinkpad); - } - - segment = wildmidi->o_segment; - - GST_PAD_STREAM_LOCK (wildmidi->sinkpad); - - if (flush) { - gst_pad_push_event (wildmidi->srcpad, gst_event_new_flush_stop (TRUE)); - } - - /* update the segment now */ - gst_segment_do_seek (segment, rate, dst_format, flags, - start_type, start, stop_type, stop, &update); - - /* we need to seek to position in the segment now, sample will be updated */ - sample = segment->position; - - GST_OBJECT_LOCK (wildmidi); -#ifdef HAVE_WILDMIDI_0_2_2 - if (accurate) { - WildMidi_SampledSeek (wildmidi->song, &sample); - } else { - WildMidi_FastSeek (wildmidi->song, &sample); - } -#else - WildMidi_FastSeek (wildmidi->song, &sample); -#endif - - GST_OBJECT_UNLOCK (wildmidi); - - segment->start = segment->time = segment->position = sample; - - gst_pad_push_event (wildmidi->srcpad, - gst_wildmidi_get_new_segment_event (wildmidi, GST_FORMAT_TIME)); - - gst_pad_start_task (wildmidi->sinkpad, - (GstTaskFunction) gst_wildmidi_loop, wildmidi->sinkpad, NULL); - - wildmidi->discont = TRUE; - GST_PAD_STREAM_UNLOCK (wildmidi->sinkpad); - GST_DEBUG ("seek done"); - - return TRUE; -} - -static gboolean -gst_wildmidi_src_event (GstPad * pad, GstObject * parent, GstEvent * event) -{ - gboolean res = FALSE; - GstWildmidi *wildmidi = GST_WILDMIDI (parent); - - GST_DEBUG_OBJECT (pad, "%s event received", GST_EVENT_TYPE_NAME (event)); - - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_SEEK: - res = gst_wildmidi_do_seek (wildmidi, event); - break; - default: - break; - } - gst_event_unref (event); - - return res; -} - - -static gboolean -gst_wildmidi_activate (GstPad * sinkpad, GstObject * parent) -{ - GstQuery *query; - gboolean pull_mode; - - query = gst_query_new_scheduling (); - - if (!gst_pad_peer_query (sinkpad, query)) { - gst_query_unref (query); - goto activate_push; - } - - pull_mode = gst_query_has_scheduling_mode_with_flags (query, - GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE); - gst_query_unref (query); - - if (!pull_mode) - goto activate_push; - - GST_DEBUG_OBJECT (sinkpad, "activating pull"); - return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE); - -activate_push: - { - GST_DEBUG_OBJECT (sinkpad, "activating push"); - return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE); - } -} - -static gboolean -gst_wildmidi_activatemode (GstPad * pad, GstObject * parent, - GstPadMode mode, gboolean active) -{ - gboolean res; - - switch (mode) { - case GST_PAD_MODE_PUSH: - res = TRUE; - break; - case GST_PAD_MODE_PULL: - if (active) { - res = gst_pad_start_task (pad, (GstTaskFunction) gst_wildmidi_loop, - pad, NULL); - } else { - res = gst_pad_stop_task (pad); - } - break; - default: - res = FALSE; - break; - } - return res; -} - -static GstBuffer * -gst_wildmidi_clip_buffer (GstWildmidi * wildmidi, GstBuffer * buffer) -{ - guint64 start, stop; - guint64 new_start, new_stop; - gint64 offset, length; - guint64 bpf; - - /* clipping disabled for now */ - return buffer; - - start = GST_BUFFER_OFFSET (buffer); - stop = GST_BUFFER_OFFSET_END (buffer); - - if (!gst_segment_clip (wildmidi->o_segment, GST_FORMAT_DEFAULT, - start, stop, &new_start, &new_stop)) { - gst_buffer_unref (buffer); - return NULL; - } - - if (start == new_start && stop == new_stop) - return buffer; - - - offset = new_start - start; - length = new_stop - new_start; - - bpf = wildmidi->bytes_per_frame; - buffer = gst_buffer_make_writable (buffer); - gst_buffer_resize (buffer, offset * bpf, length * bpf); - - GST_BUFFER_OFFSET (buffer) = new_start; - GST_BUFFER_OFFSET_END (buffer) = new_stop; - GST_BUFFER_TIMESTAMP (buffer) = - gst_util_uint64_scale_int (new_start, GST_SECOND, WILDMIDI_RATE); - GST_BUFFER_DURATION (buffer) = - gst_util_uint64_scale_int (new_stop, GST_SECOND, WILDMIDI_RATE) - - GST_BUFFER_TIMESTAMP (buffer); - - return buffer; -} - -/* generate audio data and advance internal timers */ -static GstBuffer * -gst_wildmidi_get_buffer (GstWildmidi * wildmidi) -{ - size_t size; - gint64 samples; - GstBuffer *buffer; - GstSegment *segment; - GstMapInfo info; - guint bpf; - - bpf = wildmidi->bytes_per_frame; - - buffer = gst_buffer_new_and_alloc (256 * bpf); - - gst_buffer_map (buffer, &info, GST_MAP_READWRITE); - - GST_OBJECT_LOCK (wildmidi); - size = WildMidi_GetOutput (wildmidi->song, (gpointer) info.data, - (unsigned long int) info.size); - GST_OBJECT_UNLOCK (wildmidi); - - gst_buffer_unmap (buffer, &info); - - if (size == 0) { - gst_buffer_unref (buffer); - return NULL; - } - - /* adjust buffer size */ - gst_buffer_resize (buffer, 0, size); - - segment = wildmidi->o_segment; - - GST_BUFFER_OFFSET (buffer) = segment->position; - GST_BUFFER_TIMESTAMP (buffer) = - gst_util_uint64_scale_int (segment->position, GST_SECOND, WILDMIDI_RATE); - - samples = size / bpf; - segment->position += samples; - - GST_BUFFER_OFFSET_END (buffer) = segment->position; - GST_BUFFER_DURATION (buffer) = - gst_util_uint64_scale_int (segment->position, GST_SECOND, - WILDMIDI_RATE) - GST_BUFFER_TIMESTAMP (buffer); - - GST_DEBUG_OBJECT (wildmidi, "buffer ts: %" GST_TIME_FORMAT ", " - "duration: %" GST_TIME_FORMAT " (%" G_GINT64_FORMAT " samples)", - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), - GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)), samples); - - return gst_wildmidi_clip_buffer (wildmidi, buffer); -} - -static GstFlowReturn -gst_wildmidi_parse_song (GstWildmidi * wildmidi) -{ - struct _WM_Info *info; - GstCaps *outcaps; - guint8 *data; - guint size; - - GST_DEBUG_OBJECT (wildmidi, "Parsing song"); - - size = gst_adapter_available (wildmidi->adapter); - data = gst_adapter_take (wildmidi->adapter, size); - - /* this method takes our memory block */ - GST_OBJECT_LOCK (wildmidi); - wildmidi->song = WildMidi_OpenBuffer (data, size); - - if (!wildmidi->song) - goto open_failed; - -#ifdef HAVE_WILDMIDI_0_2_2 - WildMidi_LoadSamples (wildmidi->song); -#endif - -#ifdef HAVE_WILDMIDI_0_2_2 - WildMidi_SetOption (wildmidi->song, WM_MO_LINEAR_VOLUME, - wildmidi->linear_volume); - WildMidi_SetOption (wildmidi->song, WM_MO_EXPENSIVE_INTERPOLATION, - wildmidi->high_quality); -#else - WildMidi_SetOption (wildmidi->song, WM_MO_LOG_VOLUME, - !wildmidi->linear_volume); - WildMidi_SetOption (wildmidi->song, WM_MO_ENHANCED_RESAMPLING, - wildmidi->high_quality); -#endif - - info = WildMidi_GetInfo (wildmidi->song); - GST_OBJECT_UNLOCK (wildmidi); - - wildmidi->o_len = info->approx_total_samples; - - outcaps = gst_caps_copy (gst_pad_get_pad_template_caps (wildmidi->srcpad)); - gst_pad_set_caps (wildmidi->srcpad, outcaps); - gst_caps_unref (outcaps); - - /* we keep an internal segment in samples */ - gst_segment_init (wildmidi->o_segment, GST_FORMAT_DEFAULT); - - gst_pad_push_event (wildmidi->srcpad, - gst_wildmidi_get_new_segment_event (wildmidi, GST_FORMAT_TIME)); - - GST_DEBUG_OBJECT (wildmidi, "Parsing song done"); - - return GST_FLOW_OK; - - /* ERRORS */ -open_failed: - { - GST_OBJECT_UNLOCK (wildmidi); - GST_ELEMENT_ERROR (wildmidi, STREAM, DECODE, (NULL), - ("Unable to parse midi data")); - return GST_FLOW_ERROR; - } -} - -static GstFlowReturn -gst_wildmidi_do_play (GstWildmidi * wildmidi) -{ - GstBuffer *out; - GstFlowReturn ret; - - if (!(out = gst_wildmidi_get_buffer (wildmidi))) - goto eos; - - if (wildmidi->discont) { - GST_BUFFER_FLAG_SET (out, GST_BUFFER_FLAG_DISCONT); - wildmidi->discont = FALSE; - } - - ret = gst_pad_push (wildmidi->srcpad, out); - - return ret; - - /* ERRORS */ -eos: - { - GST_LOG_OBJECT (wildmidi, "Song ended"); - return GST_FLOW_EOS; - } -} - -static gboolean -gst_wildmidi_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) -{ - gboolean res; - GstWildmidi *wildmidi = GST_WILDMIDI (parent); - - GST_DEBUG_OBJECT (pad, "%s event received", GST_EVENT_TYPE_NAME (event)); - - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_EOS: - wildmidi->state = GST_WILDMIDI_STATE_PARSE; - /* now start the parsing task */ - res = gst_pad_start_task (wildmidi->sinkpad, - (GstTaskFunction) gst_wildmidi_loop, wildmidi->sinkpad, NULL); - /* don't forward the event */ - gst_event_unref (event); - break; - default: - res = gst_pad_event_default (pad, parent, event); - break; - } - return res; -} - -static GstFlowReturn -gst_wildmidi_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * buffer) -{ - GstWildmidi *wildmidi; - - wildmidi = GST_WILDMIDI (parent); - - /* push stuff in the adapter, we will start doing something in the sink event - * handler when we get EOS */ - gst_adapter_push (wildmidi->adapter, buffer); - - return GST_FLOW_OK; -} - -static void -gst_wildmidi_loop (GstPad * sinkpad) -{ - GstWildmidi *wildmidi = GST_WILDMIDI (GST_PAD_PARENT (sinkpad)); - GstFlowReturn ret; - - switch (wildmidi->state) { - case GST_WILDMIDI_STATE_LOAD: - { - GstBuffer *buffer = NULL; - - GST_DEBUG_OBJECT (wildmidi, "loading song"); - - ret = - gst_pad_pull_range (wildmidi->sinkpad, wildmidi->offset, -1, &buffer); - - if (ret == GST_FLOW_EOS) { - GST_DEBUG_OBJECT (wildmidi, "Song loaded"); - wildmidi->state = GST_WILDMIDI_STATE_PARSE; - } else if (ret != GST_FLOW_OK) { - GST_ELEMENT_ERROR (wildmidi, STREAM, DECODE, (NULL), - ("Unable to read song")); - goto pause; - } else { - GST_DEBUG_OBJECT (wildmidi, "pushing buffer"); - gst_adapter_push (wildmidi->adapter, buffer); - wildmidi->offset += gst_buffer_get_size (buffer); - } - break; - } - case GST_WILDMIDI_STATE_PARSE: - ret = gst_wildmidi_parse_song (wildmidi); - if (ret != GST_FLOW_OK) - goto pause; - wildmidi->state = GST_WILDMIDI_STATE_PLAY; - break; - case GST_WILDMIDI_STATE_PLAY: - ret = gst_wildmidi_do_play (wildmidi); - if (ret != GST_FLOW_OK) - goto pause; - break; - default: - break; - } - return; - -pause: - { - const gchar *reason = gst_flow_get_name (ret); - GstEvent *event; - - GST_DEBUG_OBJECT (wildmidi, "pausing task, reason %s", reason); - gst_pad_pause_task (sinkpad); - if (ret == GST_FLOW_EOS) { - /* perform EOS logic */ - event = gst_event_new_eos (); - gst_pad_push_event (wildmidi->srcpad, event); - } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) { - event = gst_event_new_eos (); - /* for fatal errors we post an error message, post the error - * first so the app knows about the error first. */ - GST_ELEMENT_FLOW_ERROR (wildmidi, ret); - gst_pad_push_event (wildmidi->srcpad, event); - } - } -} - -static GstStateChangeReturn -gst_wildmidi_change_state (GstElement * element, GstStateChange transition) -{ - GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; - GstWildmidi *wildmidi = GST_WILDMIDI (element); - - switch (transition) { - case GST_STATE_CHANGE_NULL_TO_READY: - break; - case GST_STATE_CHANGE_READY_TO_PAUSED: - wildmidi->offset = 0; - wildmidi->state = GST_WILDMIDI_STATE_LOAD; - wildmidi->discont = FALSE; - break; - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - break; - default: - break; - } - - ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); - - switch (transition) { - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_READY: - GST_OBJECT_LOCK (wildmidi); - if (wildmidi->song) - WildMidi_Close (wildmidi->song); - wildmidi->song = NULL; - GST_OBJECT_UNLOCK (wildmidi); - gst_adapter_clear (wildmidi->adapter); - break; - case GST_STATE_CHANGE_READY_TO_NULL: - break; - default: - break; - } - - return ret; -} - -static void -gst_wildmidi_set_property (GObject * object, guint prop_id, - const GValue * value, GParamSpec * pspec) -{ - GstWildmidi *wildmidi; - - g_return_if_fail (GST_IS_WILDMIDI (object)); - - wildmidi = GST_WILDMIDI (object); - - switch (prop_id) { - case PROP_LINEAR_VOLUME: - GST_OBJECT_LOCK (object); - wildmidi->linear_volume = g_value_get_boolean (value); - if (wildmidi->song) -#ifdef HAVE_WILDMIDI_0_2_2 - WildMidi_SetOption (wildmidi->song, WM_MO_LINEAR_VOLUME, - wildmidi->linear_volume); -#else - WildMidi_SetOption (wildmidi->song, WM_MO_LOG_VOLUME, - !wildmidi->linear_volume); -#endif - GST_OBJECT_UNLOCK (object); - break; - case PROP_HIGH_QUALITY: - GST_OBJECT_LOCK (object); - wildmidi->high_quality = g_value_get_boolean (value); - if (wildmidi->song) -#ifdef HAVE_WILDMIDI_0_2_2 - WildMidi_SetOption (wildmidi->song, WM_MO_EXPENSIVE_INTERPOLATION, - wildmidi->high_quality); -#else - WildMidi_SetOption (wildmidi->song, WM_MO_ENHANCED_RESAMPLING, - wildmidi->high_quality); -#endif - GST_OBJECT_UNLOCK (object); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -gst_wildmidi_get_property (GObject * object, guint prop_id, - GValue * value, GParamSpec * pspec) -{ - GstWildmidi *wildmidi; - - g_return_if_fail (GST_IS_WILDMIDI (object)); - - wildmidi = GST_WILDMIDI (object); - - switch (prop_id) { - case PROP_LINEAR_VOLUME: - GST_OBJECT_LOCK (object); - g_value_set_boolean (value, wildmidi->linear_volume); - GST_OBJECT_UNLOCK (object); - break; - case PROP_HIGH_QUALITY: - GST_OBJECT_LOCK (object); - g_value_set_boolean (value, wildmidi->high_quality); - GST_OBJECT_UNLOCK (object); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static gboolean -plugin_init (GstPlugin * plugin) -{ - GST_DEBUG_CATEGORY_INIT (gst_wildmidi_debug, "wildmidi", - 0, "Wildmidi plugin"); - - if (!wildmidi_open_config ()) { - GST_WARNING ("Can't initialize wildmidi"); - return FALSE; - } - - return gst_element_register (plugin, "wildmidi", - GST_RANK_SECONDARY, GST_TYPE_WILDMIDI); -} - -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, - GST_VERSION_MINOR, - wildmidi, - "Wildmidi Plugin", - plugin_init, VERSION, "GPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/ext/wildmidi/gstwildmidi.h b/ext/wildmidi/gstwildmidi.h deleted file mode 100644 index 54a915b303..0000000000 --- a/ext/wildmidi/gstwildmidi.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * gstwildmidi - wildmidi plugin for gstreamer - * - * Copyright 2007 Wouter Paesen - * - * 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. - * - * Wrapper element for libtimidity. This element works in pull - * based mode because that's essentially how libwildmidi works. - * We create a libwildmidi stream that operates on the srcpad. - * The sinkpad is in pull mode. - */ - -#ifndef __GST_WILDMIDI_H__ -#define __GST_WILDMIDI_H__ - -#include -#include -#include - -G_BEGIN_DECLS - -#define GST_TYPE_WILDMIDI \ - (gst_wildmidi_get_type()) -#define GST_WILDMIDI(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WILDMIDI,GstWildmidi)) -#define GST_WILDMIDI_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_WILDMIDI,GstWildmidiClass)) -#define GST_IS_WILDMIDI(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WILDMIDI)) -#define GST_IS_WILDMIDI_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_WILDMIDI)) - -typedef struct _GstWildmidi GstWildmidi; -typedef struct _GstWildmidiClass GstWildmidiClass; - -typedef enum { - GST_WILDMIDI_STATE_LOAD, - GST_WILDMIDI_STATE_PARSE, - GST_WILDMIDI_STATE_PLAY -} GstWildmidiState; - -struct _GstWildmidi -{ - GstElement element; - - GstPad *sinkpad, *srcpad; - - /* input stream properties */ - GstWildmidiState state; - GstAdapter *adapter; - midi *song; - guint64 offset; - - /* output data */ - gboolean discont; - GstSegment o_segment[1]; - gint64 o_len; - - /* format of the stream */ - guint bytes_per_frame; - - /* wildmidi settings */ - gboolean high_quality; - gboolean linear_volume; -}; - -struct _GstWildmidiClass -{ - GstElementClass parent_class; -}; - -GType gst_wildmidi_get_type (void); - -G_END_DECLS - -#endif /* __GST_WILDMIDI_H__ */ diff --git a/ext/wildmidi/gstwildmididec.c b/ext/wildmidi/gstwildmididec.c new file mode 100644 index 0000000000..69aa75c324 --- /dev/null +++ b/ext/wildmidi/gstwildmididec.c @@ -0,0 +1,683 @@ +/* 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. + */ + + +/** + * SECTION:element-wildmididec + * @see_also: #GstWildmidiDec + * + * wildmididec decodes MIDI files. + * It uses WildMidi + * for this purpose. It can be autoplugged and therefore works with decodebin. + * + * + * Example launch line + * |[ + * gst-launch-1.0 filesrc location=media/example.mid ! wildmididec ! audioconvert ! audioresample ! autoaudiosink + * ]| + * + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#ifdef G_OS_WIN32 + +#ifndef R_OK +#define R_OK 4 /* Test for read permission */ +#endif + +#else +#include +#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 "); + + 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, (char *) (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) diff --git a/ext/wildmidi/gstwildmididec.h b/ext/wildmidi/gstwildmididec.h new file mode 100644 index 0000000000..cc5aea08b4 --- /dev/null +++ b/ext/wildmidi/gstwildmididec.h @@ -0,0 +1,69 @@ +/* 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_WILDMIDI_DEC_H__ +#define __GST_WILDMIDI_DEC_H__ + + +#include +#include "gst/audio/gstnonstreamaudiodecoder.h" +#include + + +G_BEGIN_DECLS + + +typedef struct _GstWildmidiDec GstWildmidiDec; +typedef struct _GstWildmidiDecClass GstWildmidiDecClass; + + +#define GST_TYPE_WILDMIDI_DEC (gst_wildmidi_dec_get_type()) +#define GST_WILDMIDI_DEC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_WILDMIDI_DEC, GstWildmidiDec)) +#define GST_WILDMIDI_DEC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_WILDMIDI_DEC, GstWildmidiDecClass)) +#define GST_IS_WILDMIDI_DEC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_WILDMIDI_DEC)) +#define GST_IS_WILDMIDI_DEC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_WILDMIDI_DEC)) + + +struct _GstWildmidiDec +{ + GstNonstreamAudioDecoder parent; + + midi *song; + + gboolean log_volume_scale; + gboolean enhanced_resampling; + gboolean reverb; + guint output_buffer_size; +}; + + +struct _GstWildmidiDecClass +{ + GstNonstreamAudioDecoderClass parent_class; +}; + + +GType gst_wildmidi_dec_get_type (void); + + +G_END_DECLS + + +#endif /* __GST_WILDMIDI_DEC_H__ */