diff --git a/configure.ac b/configure.ac index dc2a7d5f35..ad0580763d 100644 --- a/configure.ac +++ b/configure.ac @@ -366,6 +366,7 @@ AG_GST_CHECK_PLUGIN(jp2kdecimator) AG_GST_CHECK_PLUGIN(jpegformat) AG_GST_CHECK_PLUGIN(librfb) AG_GST_CHECK_PLUGIN(liveadder) +AG_GST_CHECK_PLUGIN(midi) AG_GST_CHECK_PLUGIN(mpegdemux) AG_GST_CHECK_PLUGIN(mpegtsdemux) AG_GST_CHECK_PLUGIN(mpegtsmux) @@ -2319,6 +2320,7 @@ gst/jp2kdecimator/Makefile gst/jpegformat/Makefile gst/librfb/Makefile gst/liveadder/Makefile +gst/midi/Makefile gst/mpegdemux/Makefile gst/mpegtsdemux/Makefile gst/mpegtsmux/Makefile diff --git a/gst/midi/Makefile.am b/gst/midi/Makefile.am new file mode 100644 index 0000000000..4ed1a4cdd2 --- /dev/null +++ b/gst/midi/Makefile.am @@ -0,0 +1,30 @@ +plugin_LTLIBRARIES = libgstmidi.la + +libgstmidi_la_SOURCES = midi.c midiparse.c +libgstmidi_la_CFLAGS = \ + $(GST_PLUGINS_BAD_CFLAGS) \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) +libgstmidi_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) -lgsttag-$(GST_API_VERSION) \ + $(GST_BASE_LIBS) \ + $(LIBM) +libgstmidi_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstmidi_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = midiparse.h + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstmidi -:SHARED libgstmidi \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstmidi_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstmidi_la_CFLAGS) \ + -:LDFLAGS $(libgstmidi_la_LDFLAGS) \ + $(libgstmidi_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-1.0' \ + > $@ diff --git a/gst/midi/midi.c b/gst/midi/midi.c new file mode 100644 index 0000000000..6171359f62 --- /dev/null +++ b/gst/midi/midi.c @@ -0,0 +1,58 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */ +/* GStreamer MIDI plugin initialisation + * Copyright (C) <2013> Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +#include "midiparse.h" + +GST_DEBUG_CATEGORY_STATIC (midi_debug); +#define GST_CAT_DEFAULT (midi_debug) + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean ret; + + GST_DEBUG_CATEGORY_INIT (midi_debug, "midi", 0, "MIDI plugin"); + +#ifdef ENABLE_NLS + GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, + LOCALEDIR); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +#endif + + ret = gst_element_register (plugin, "midiparse", GST_RANK_PRIMARY, + GST_TYPE_MIDI_PARSE); + + return ret; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + midi, + "Parse MIDI files", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/gst/midi/midiparse.c b/gst/midi/midiparse.c new file mode 100644 index 0000000000..171c136ff8 --- /dev/null +++ b/gst/midi/midiparse.c @@ -0,0 +1,906 @@ +/* + * midiparse - midi parser plugin for gstreamer + * + * Copyright 2013 Wim Taymans + * + * 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-midiparse + * @see_also: timidity, wildmidi + * + * This element renders midi-files as audio streams using + * MidiParse. + * It offers better sound quality compared to the timidity or wildmidi element. + * + * + * Example pipeline + * |[ + * gst-launch-1.0 filesrc location=song.mid ! midiparse ! pulsesink + * ]| This example pipeline will parse the midi and render to raw audio which is + * played via pulseaudio. + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +#include "midiparse.h" + +GST_DEBUG_CATEGORY_STATIC (gst_midi_parse_debug); +#define GST_CAT_DEFAULT gst_midi_parse_debug + +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + /* FILL ME */ +}; + +typedef struct +{ + guint8 *data; + guint size; + guint offset; + + guint8 running_status; + GstClockTime position; + +} GstMidiTrack; + +static void gst_midi_parse_finalize (GObject * object); + +static gboolean gst_midi_parse_sink_event (GstPad * pad, GstObject * parent, + GstEvent * event); +static gboolean gst_midi_parse_src_event (GstPad * pad, GstObject * parent, + GstEvent * event); + +static GstStateChangeReturn gst_midi_parse_change_state (GstElement * element, + GstStateChange transition); +static gboolean gst_midi_parse_activate (GstPad * pad, GstObject * parent); +static gboolean gst_midi_parse_activatemode (GstPad * pad, GstObject * parent, + GstPadMode mode, gboolean active); + +static void gst_midi_parse_loop (GstPad * sinkpad); +static GstFlowReturn gst_midi_parse_chain (GstPad * sinkpad, GstObject * parent, + GstBuffer * buffer); + +static gboolean gst_midi_parse_src_query (GstPad * pad, GstObject * parent, + GstQuery * query); + +static void gst_midi_parse_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_midi_parse_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-midi-event")); + +#define parent_class gst_midi_parse_parent_class +G_DEFINE_TYPE (GstMidiParse, gst_midi_parse, GST_TYPE_ELEMENT); + +/* initialize the plugin's class */ +static void +gst_midi_parse_class_init (GstMidiParseClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->finalize = gst_midi_parse_finalize; + gobject_class->set_property = gst_midi_parse_set_property; + gobject_class->get_property = gst_midi_parse_get_property; + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&src_factory)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sink_factory)); + gst_element_class_set_static_metadata (gstelement_class, "MidiParse", + "Codec/Decoder/Audio", + "Midi Parser Element", "Wim Taymans "); + + GST_DEBUG_CATEGORY_INIT (gst_midi_parse_debug, "midiparse", + 0, "MIDI parser plugin"); + + gstelement_class->change_state = gst_midi_parse_change_state; +} + +/* initialize the new element + * instantiate pads and add them to element + * set functions + * initialize structure + */ +static void +gst_midi_parse_init (GstMidiParse * filter) +{ + filter->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink"); + + gst_pad_set_activatemode_function (filter->sinkpad, + gst_midi_parse_activatemode); + gst_pad_set_activate_function (filter->sinkpad, gst_midi_parse_activate); + gst_pad_set_event_function (filter->sinkpad, gst_midi_parse_sink_event); + gst_pad_set_chain_function (filter->sinkpad, gst_midi_parse_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_midi_parse_src_query); + gst_pad_set_event_function (filter->srcpad, gst_midi_parse_src_event); + gst_pad_use_fixed_caps (filter->srcpad); + + gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad); + + gst_segment_init (&filter->segment, GST_FORMAT_TIME); + + filter->adapter = gst_adapter_new (); +} + +static void +gst_midi_parse_finalize (GObject * object) +{ + GstMidiParse *midiparse; + + midiparse = GST_MIDI_PARSE (object); + + g_object_unref (midiparse->adapter); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_midi_parse_src_query (GstPad * pad, GstObject * parent, GstQuery * query) +{ + gboolean res = TRUE; + GstMidiParse *midiparse = GST_MIDI_PARSE (parent); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_DURATION: + gst_query_set_duration (query, GST_FORMAT_TIME, + midiparse->segment.duration); + break; + case GST_QUERY_POSITION: + gst_query_set_position (query, GST_FORMAT_TIME, + midiparse->segment.position); + break; + case GST_QUERY_FORMATS: + gst_query_set_formats (query, 1, GST_FORMAT_TIME); + break; + case GST_QUERY_SEGMENT: + gst_query_set_segment (query, midiparse->segment.rate, + midiparse->segment.format, midiparse->segment.start, + midiparse->segment.stop); + break; + case GST_QUERY_SEEKING: + gst_query_set_seeking (query, midiparse->segment.format, + TRUE, 0, midiparse->segment.duration); + break; + default: + res = gst_pad_query_default (pad, parent, query); + break; + } + + return res; +} + +static GstEvent * +gst_midi_parse_get_new_segment_event (GstMidiParse * midiparse, + GstFormat format) +{ + GstSegment *segment, newseg; + GstEvent *event; + + segment = &midiparse->segment; + newseg = *segment; + + newseg.format = format; + newseg.start = segment->start; + newseg.stop = segment->stop; + newseg.time = segment->time; + + event = gst_event_new_segment (&newseg); + + return event; +} + +static gboolean +gst_midi_parse_src_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + gboolean res = FALSE; + + GST_DEBUG_OBJECT (pad, "%s event received", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + default: + break; + } + gst_event_unref (event); + + return res; +} + + +static gboolean +gst_midi_parse_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_midi_parse_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_midi_parse_loop, + pad, NULL); + } else { + res = gst_pad_stop_task (pad); + } + break; + default: + res = FALSE; + break; + } + return res; +} + +static gboolean +parse_MThd (GstMidiParse * midiparse, guint8 * data, guint size) +{ + guint16 format, ntracks, division; + gboolean multitrack; + + format = GST_READ_UINT16_BE (data); + switch (format) { + case 0: + multitrack = FALSE; + break; + case 1: + multitrack = TRUE; + break; + default: + case 2: + goto invalid_format; + } + ntracks = GST_READ_UINT16_BE (data + 2); + if (ntracks > 1 && !multitrack) + goto invalid_tracks; + + division = GST_READ_UINT16_BE (data + 4); + if (division & 0x8000) + goto invalid_division; + + GST_DEBUG_OBJECT (midiparse, "format %u, tracks %u, division %u", + format, ntracks, division); + + midiparse->ntracks = ntracks; + midiparse->division = division; + + return TRUE; + +invalid_format: + { + GST_ERROR_OBJECT (midiparse, "unsupported midi format %u", format); + return FALSE; + } +invalid_tracks: + { + GST_ERROR_OBJECT (midiparse, "invalid number of tracks %u for format %u", + ntracks, format); + return FALSE; + } +invalid_division: + { + GST_ERROR_OBJECT (midiparse, "unsupported division"); + return FALSE; + } +} + +static guint +parse_varlen (GstMidiParse * midiparse, guint8 * data, guint size, + gint32 * result) +{ + gint32 res; + gint i; + + res = 0; + for (i = 0; i < 4; i++) { + if (size == 0) + return 0; + + res = (res << 7) | ((data[i]) & 0x7f); + if ((data[i] & 0x80) == 0) { + *result = res; + return i + 1; + } + } + return 0; +} + +static gboolean +update_track_position (GstMidiParse * midiparse, GstMidiTrack * track) +{ + gint32 delta_time; + guint8 *data; + guint size, consumed; + + if (track->offset >= track->size) + goto eot; + + data = track->data + track->offset; + size = track->size - track->offset; + + consumed = parse_varlen (midiparse, data, size, &delta_time); + if (consumed == 0) + goto eot; + + track->position += delta_time; + track->offset += consumed; + + GST_LOG_OBJECT (midiparse, "updated track to %" GST_TIME_FORMAT, + GST_TIME_ARGS (track->position)); + + return TRUE; + + /* ERRORS */ +eot: + { + GST_DEBUG_OBJECT (midiparse, "track ended"); + track->position = GST_CLOCK_TIME_NONE; + return FALSE; + } +} + +static gboolean +parse_MTrk (GstMidiParse * midiparse, guint8 * data, guint size) +{ + GstMidiTrack *track; + + track = g_slice_new (GstMidiTrack); + track->data = data; + track->size = size; + track->offset = 0; + track->position = 0; + track->running_status = 0xff; + update_track_position (midiparse, track); + + midiparse->tracks = g_list_append (midiparse->tracks, track); + + return TRUE; +} + +static guint +gst_midi_parse_chunk (GstMidiParse * midiparse, guint8 * data, guint size) +{ + guint32 type, length = 0; + + if (size < 8) + goto short_chunk; + + length = GST_READ_UINT32_BE (data + 4); + + GST_DEBUG_OBJECT (midiparse, "have type %c%c%c%c, length %u", + data[0], data[1], data[2], data[3], length); + + if (size < length + 8) + goto short_chunk; + + type = *((guint32 *) data); + + switch (type) { + case GST_MAKE_FOURCC ('M', 'T', 'h', 'd'): + if (!parse_MThd (midiparse, data + 8, length)) + goto invalid_format; + break; + case GST_MAKE_FOURCC ('M', 'T', 'r', 'k'): + if (!parse_MTrk (midiparse, data + 8, length)) + goto invalid_format; + break; + default: + GST_LOG_OBJECT (midiparse, "ignore chunk"); + break; + } + return length + 8; + + /* ERRORS */ +short_chunk: + { + GST_LOG_OBJECT (midiparse, "not enough data %u < %u", length + 8, size); + return 0; + } +invalid_format: + { + GST_ERROR_OBJECT (midiparse, "invalid format"); + return 0; + } +} + +static GstFlowReturn +gst_midi_parse_parse_song (GstMidiParse * midiparse) +{ + GstCaps *outcaps; + guint8 *data; + guint size; + + GST_DEBUG_OBJECT (midiparse, "Parsing song"); + + size = gst_adapter_available (midiparse->adapter); + data = gst_adapter_take (midiparse->adapter, size); + + midiparse->tempo = 60; + + while (size) { + guint consumed; + + consumed = gst_midi_parse_chunk (midiparse, data, size); + if (consumed == 0) + goto short_file; + + data += consumed; + size -= consumed; + } + + outcaps = gst_caps_copy (gst_pad_get_pad_template_caps (midiparse->srcpad)); + gst_pad_set_caps (midiparse->srcpad, outcaps); + gst_caps_unref (outcaps); + + gst_segment_init (&midiparse->segment, GST_FORMAT_TIME); + + gst_pad_push_event (midiparse->srcpad, + gst_midi_parse_get_new_segment_event (midiparse, GST_FORMAT_TIME)); + + GST_DEBUG_OBJECT (midiparse, "Parsing song done"); + + return GST_FLOW_OK; + + /* ERRORS */ +short_file: + { + GST_ERROR_OBJECT (midiparse, "not enough data"); + g_free (data); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +handle_meta_event (GstMidiParse * midiparse, GstMidiTrack * track) +{ + guint8 type; + guint8 *data; + guint size, consumed; + gint32 length; + + track->offset += 1; + + data = track->data + track->offset; + size = track->size - track->offset; + + if (size < 1) + goto short_file; + + type = data[0]; + + consumed = parse_varlen (midiparse, data + 1, size - 1, &length); + if (consumed == 0) + goto short_file; + + data += consumed + 1; + size -= consumed + 1; + + GST_DEBUG_OBJECT (midiparse, "handle meta event type 0x%02x, length %u", + type, length); + + switch (type) { + case 0x51: + { + guint32 uspqn = (data[0] << 16) | (data[1] << 8) | data[2]; + midiparse->tempo = (uspqn ? uspqn : 1); + GST_DEBUG_OBJECT (midiparse, "tempo %u", midiparse->tempo); + break; + } + default: + break; + } + + track->offset += consumed + length + 1; + + return GST_FLOW_OK; + + /* ERRORS */ +short_file: + { + GST_DEBUG_OBJECT (midiparse, "not enough data"); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +handle_sysex_event (GstMidiParse * midiparse, guint8 event, + GstMidiTrack * track) +{ + guint8 *data; + guint size, consumed; + gint32 length; + + track->offset += 1; + + data = track->data + track->offset; + size = track->size - track->offset; + + consumed = parse_varlen (midiparse, data, size, &length); + if (consumed == 0) + goto short_file; + + data += consumed; + size -= consumed; + + GST_DEBUG_OBJECT (midiparse, "handle sysex event 0x%02x, length %u", + event, length); + + track->offset += consumed + length; + + return GST_FLOW_OK; + + /* ERRORS */ +short_file: + { + GST_DEBUG_OBJECT (midiparse, "not enough data"); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +handle_next_event (GstMidiParse * midiparse, GstMidiTrack * track) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint8 status, event; + guint length; + guint8 *data; + + data = &track->data[track->offset]; + + status = data[0]; + + GST_LOG_OBJECT (midiparse, "track %p, status 0x%02x", track, status); + + if ((status & 0x80) == 0) { + if ((track->running_status & 0x80) == 0) { + GST_ERROR_OBJECT (midiparse, + "Undefined status and invalid running status"); + return GST_FLOW_ERROR; + } + event = track->running_status; + } else { + event = status; + } + + switch (event & 0xf0) { + case 0xf0: + switch (event) { + case 0xff: + ret = handle_meta_event (midiparse, track); + break; + case 0xf0: + case 0xf7: + ret = handle_sysex_event (midiparse, event, track); + break; + default: + GST_ERROR_OBJECT (midiparse, "unhandled event 0x%08x", event); + return GST_FLOW_ERROR; + } + length = 0; + break; + case 0xc0: + case 0xd0: + length = 1; + break; + default: + length = 2; + break; + } + + if (length > 0) { + GstBuffer *outbuf; + GstMapInfo info; + + outbuf = gst_buffer_new_allocate (NULL, length + 1, NULL); + + gst_buffer_map (outbuf, &info, GST_MAP_WRITE); + + info.data[0] = event; + + if (status & 0x80) { + memcpy (&info.data[1], &data[1], length); + track->offset += length + 1; + } else { + info.data[1] = status; + memcpy (&info.data[2], &data[1], length - 1); + track->offset += length; + } + gst_buffer_unmap (outbuf, &info); + + GST_BUFFER_PTS (outbuf) = gst_util_uint64_scale (track->position, + 1000 * midiparse->tempo, midiparse->division); + + GST_DEBUG_OBJECT (midiparse, "pushing %" GST_TIME_FORMAT, + GST_TIME_ARGS (GST_BUFFER_PTS (outbuf))); + + ret = gst_pad_push (midiparse->srcpad, outbuf); + } + + if (event < 0xF8) + track->running_status = event; + + return ret; +} + +static GstFlowReturn +gst_midi_parse_do_play (GstMidiParse * midiparse) +{ + GstFlowReturn res = GST_FLOW_OK; + GList *walk; + guint64 position, next_position = GST_CLOCK_TIME_NONE; + + position = midiparse->segment.position; + + for (walk = midiparse->tracks; walk; walk = g_list_next (walk)) { + GstMidiTrack *track = walk->data; + + while (track->position == position) { + res = handle_next_event (midiparse, track); + if (res != GST_FLOW_OK) + goto done; + + update_track_position (midiparse, track); + } + + if (track->position < next_position) + next_position = track->position; + } + + if (next_position == GST_CLOCK_TIME_NONE) + goto eos; + + midiparse->segment.position = next_position; + +done: + + return res; + +eos: + { + GST_DEBUG_OBJECT (midiparse, "we are EOS"); + return GST_FLOW_EOS; + } +} + +static gboolean +gst_midi_parse_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + gboolean res; + GstMidiParse *midiparse = GST_MIDI_PARSE (parent); + + GST_DEBUG_OBJECT (pad, "%s event received", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + midiparse->state = GST_MIDI_PARSE_STATE_PARSE; + /* now start the parsing task */ + res = gst_pad_start_task (midiparse->sinkpad, + (GstTaskFunction) gst_midi_parse_loop, midiparse->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_midi_parse_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * buffer) +{ + GstMidiParse *midiparse; + + midiparse = GST_MIDI_PARSE (parent); + + /* push stuff in the adapter, we will start doing something in the sink event + * handler when we get EOS */ + gst_adapter_push (midiparse->adapter, buffer); + + return GST_FLOW_OK; +} + +static void +gst_midi_parse_loop (GstPad * sinkpad) +{ + GstMidiParse *midiparse = GST_MIDI_PARSE (GST_PAD_PARENT (sinkpad)); + GstFlowReturn ret; + + switch (midiparse->state) { + case GST_MIDI_PARSE_STATE_LOAD: + { + GstBuffer *buffer = NULL; + + GST_DEBUG_OBJECT (midiparse, "loading song"); + + ret = + gst_pad_pull_range (midiparse->sinkpad, midiparse->offset, -1, + &buffer); + + if (ret == GST_FLOW_EOS) { + GST_DEBUG_OBJECT (midiparse, "Song loaded"); + midiparse->state = GST_MIDI_PARSE_STATE_PARSE; + } else if (ret != GST_FLOW_OK) { + GST_ELEMENT_ERROR (midiparse, STREAM, DECODE, (NULL), + ("Unable to read song")); + goto pause; + } else { + GST_DEBUG_OBJECT (midiparse, "pushing buffer"); + gst_adapter_push (midiparse->adapter, buffer); + midiparse->offset += gst_buffer_get_size (buffer); + } + break; + } + case GST_MIDI_PARSE_STATE_PARSE: + ret = gst_midi_parse_parse_song (midiparse); + if (ret != GST_FLOW_OK) + goto pause; + midiparse->state = GST_MIDI_PARSE_STATE_PLAY; + break; + case GST_MIDI_PARSE_STATE_PLAY: + ret = gst_midi_parse_do_play (midiparse); + 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 (midiparse, "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 (midiparse->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_ERROR (midiparse, STREAM, FAILED, + ("Internal data flow error."), + ("streaming task paused, reason %s (%d)", reason, ret)); + gst_pad_push_event (midiparse->srcpad, event); + } + } +} + +static GstStateChangeReturn +gst_midi_parse_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstMidiParse *midiparse = GST_MIDI_PARSE (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + midiparse->offset = 0; + midiparse->state = GST_MIDI_PARSE_STATE_LOAD; + midiparse->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_adapter_clear (midiparse->adapter); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + return ret; +} + +static void +gst_midi_parse_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_midi_parse_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} diff --git a/gst/midi/midiparse.h b/gst/midi/midiparse.h new file mode 100644 index 0000000000..bf0775f015 --- /dev/null +++ b/gst/midi/midiparse.h @@ -0,0 +1,84 @@ +/* + * gstmidiparse - midiparse plugin for gstreamer + * + * Copyright 2007 Wouter Paesen + * Copyright 2013 Wim Taymans + * + * 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_MIDIPARSE_H__ +#define __GST_MIDIPARSE_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_MIDI_PARSE \ + (gst_midi_parse_get_type()) +#define GST_MIDI_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MIDI_PARSE,GstMidiParse)) +#define GST_MIDI_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MIDI_PARSE,GstMidiParseClass)) +#define GST_IS_MIDI_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MIDI_PARSE)) +#define GST_IS_MIDI_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MIDI_PARSE)) + +typedef struct _GstMidiParse GstMidiParse; +typedef struct _GstMidiParseClass GstMidiParseClass; + +typedef enum { + GST_MIDI_PARSE_STATE_LOAD, + GST_MIDI_PARSE_STATE_PARSE, + GST_MIDI_PARSE_STATE_PLAY +} GstMidiParseState; + +struct _GstMidiParse +{ + GstElement element; + + GstPad *sinkpad, *srcpad; + + /* input stream properties */ + GstMidiParseState state; + + guint tempo; + guint16 ntracks; + guint16 division; + + GList *tracks; + + guint64 offset; + GstAdapter *adapter; + + /* output data */ + gboolean discont; + GstSegment segment; +}; + +struct _GstMidiParseClass +{ + GstElementClass parent_class; +}; + +GType gst_midi_parse_get_type (void); + +G_END_DECLS + +#endif /* __GST_MIDI_PARSE_H__ */