mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-12 02:15:31 +00:00
f874e30998
Remove time_per_frame, it's too inaccurate. Fix convert for -1 values. Use scaling to convert samples to time. Fix segment handling. Fix seeking. Fix buffer clipping (not yet enabled) Protect the WildMidi library with the object lock to avoid races when changing properties. Fix offsets on output buffers, for raw audio the offset contains the sample position not byte position. Add better DISCONT handling. Fixes #519001
969 lines
26 KiB
C
969 lines
26 KiB
C
/*
|
|
* gstwildmidi - wildmidi plugin for gstreamer
|
|
*
|
|
* Copyright 2007 Wouter Paesen <wouter@blue-gate.be>
|
|
*
|
|
* 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-wildmidi
|
|
* @see_also: timidity
|
|
*
|
|
* This element renders midi-files as audio streams using
|
|
* <ulink url="http://wildmidi.sourceforge.net//">Wildmidi</ulink>.
|
|
* 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)
|
|
*
|
|
* <refsect2>
|
|
* <title>Example pipeline</title>
|
|
* |[
|
|
* gst-launch filesrc location=song.mid ! wildmidi ! alsasink
|
|
* ]| This example pipeline will parse the midi and render to raw audio which is
|
|
* played via alsa.
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#define WILDMIDI_RATE 44100
|
|
#define WILDMIDI_BPS (2 * 2)
|
|
|
|
#include <gst/gst.h>
|
|
#include <string.h>
|
|
#include <glib.h>
|
|
#include <glib/gstdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#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
|
|
|
|
static const GstElementDetails gst_wildmidi_details =
|
|
GST_ELEMENT_DETAILS ("WildMidi",
|
|
"Codec/Decoder/Audio",
|
|
"Midi Synthesizer Element",
|
|
"Wouter Paesen <wouter@blue-gate.be>");
|
|
|
|
enum
|
|
{
|
|
/* FILL ME */
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
enum
|
|
{
|
|
ARG_0,
|
|
ARG_LINEAR_VOLUME,
|
|
ARG_HIGH_QUALITY,
|
|
/* FILL ME */
|
|
};
|
|
|
|
static void gst_wildmidi_base_init (gpointer g_class);
|
|
static void gst_wildmidi_class_init (GstWildmidiClass * klass);
|
|
static void gst_wildmidi_finalize (GObject * object);
|
|
|
|
static gboolean gst_wildmidi_sink_event (GstPad * pad, GstEvent * event);
|
|
static gboolean gst_wildmidi_src_event (GstPad * pad, GstEvent * event);
|
|
|
|
static GstStateChangeReturn gst_wildmidi_change_state (GstElement * element,
|
|
GstStateChange transition);
|
|
static gboolean gst_wildmidi_activate (GstPad * pad);
|
|
static gboolean gst_wildmidi_activatepull (GstPad * pad, gboolean active);
|
|
|
|
static void gst_wildmidi_loop (GstPad * sinkpad);
|
|
static GstFlowReturn gst_wildmidi_chain (GstPad * sinkpad, GstBuffer * buffer);
|
|
|
|
static gboolean gst_wildmidi_src_query (GstPad * pad, 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")
|
|
);
|
|
|
|
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("audio/x-raw-int, "
|
|
"rate = (int) 44100, "
|
|
"channels = (int) 2, "
|
|
"endianness = (int) LITTLE_ENDIAN, "
|
|
"width = (int) 16, " "depth = (int) 16, " "signed = (boolean) true"));
|
|
|
|
GST_BOILERPLATE (GstWildmidi, gst_wildmidi, GstElement, GST_TYPE_ELEMENT);
|
|
|
|
static void
|
|
gst_wildmidi_base_init (gpointer gclass)
|
|
{
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&src_factory));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&sink_factory));
|
|
gst_element_class_set_details (element_class, &gst_wildmidi_details);
|
|
}
|
|
|
|
static gboolean
|
|
wildmidi_open_config ()
|
|
{
|
|
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, "/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, "/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, "/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, "/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, ARG_LINEAR_VOLUME,
|
|
g_param_spec_boolean ("linear-volume", "Linear volume",
|
|
"Linear volume", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (gobject_class, ARG_HIGH_QUALITY,
|
|
g_param_spec_boolean ("high-quality", "High Quality",
|
|
"High Quality", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
|
|
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, GstWildmidiClass * g_class)
|
|
{
|
|
GstElementClass *klass = GST_ELEMENT_GET_CLASS (filter);
|
|
|
|
filter->sinkpad =
|
|
gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
|
|
"sink"), "sink");
|
|
|
|
gst_pad_set_activatepull_function (filter->sinkpad,
|
|
gst_wildmidi_activatepull);
|
|
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_template (gst_element_class_get_pad_template (klass,
|
|
"src"), "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;
|
|
}
|
|
|
|
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, GstQuery * query)
|
|
{
|
|
gboolean res = TRUE;
|
|
GstWildmidi *wildmidi = GST_WILDMIDI (gst_pad_get_parent (pad));
|
|
GstFormat src_format, dst_format;
|
|
gint64 src_value, dst_value;
|
|
|
|
if (!wildmidi->song) {
|
|
gst_object_unref (wildmidi);
|
|
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->last_stop, 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:
|
|
gst_query_set_segment (query, wildmidi->o_segment->rate,
|
|
wildmidi->o_segment->format, wildmidi->o_segment->start,
|
|
wildmidi->o_segment->stop);
|
|
break;
|
|
case GST_QUERY_SEEKING:
|
|
gst_query_set_seeking (query, wildmidi->o_segment->format,
|
|
TRUE, 0, wildmidi->o_len);
|
|
break;
|
|
default:
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
|
|
gst_object_unref (wildmidi);
|
|
return res;
|
|
}
|
|
|
|
static GstEvent *
|
|
gst_wildmidi_get_new_segment_event (GstWildmidi * wildmidi, GstFormat format)
|
|
{
|
|
gint64 start, stop, time;
|
|
GstSegment *segment;
|
|
GstEvent *event;
|
|
GstFormat src_format;
|
|
|
|
segment = wildmidi->o_segment;
|
|
src_format = segment->format;
|
|
|
|
/* 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);
|
|
|
|
event = gst_event_new_new_segment_full (FALSE,
|
|
segment->rate, segment->applied_rate, format, start, stop, time);
|
|
|
|
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, accurate;
|
|
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);
|
|
accurate = ((flags & GST_SEEK_FLAG_ACCURATE) == GST_SEEK_FLAG_ACCURATE);
|
|
|
|
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 ());
|
|
}
|
|
|
|
/* update the segment now */
|
|
gst_segment_set_seek (segment, rate, dst_format, flags,
|
|
start_type, start, stop_type, stop, &update);
|
|
|
|
/* we need to seek to last_stop in the segment now, sample will be updated */
|
|
sample = segment->last_stop;
|
|
|
|
GST_OBJECT_LOCK (wildmidi);
|
|
if (accurate) {
|
|
WildMidi_SampledSeek (wildmidi->song, &sample);
|
|
} else {
|
|
WildMidi_FastSeek (wildmidi->song, &sample);
|
|
}
|
|
GST_OBJECT_UNLOCK (wildmidi);
|
|
|
|
segment->start = segment->time = segment->last_stop = 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);
|
|
|
|
wildmidi->discont = TRUE;
|
|
GST_PAD_STREAM_UNLOCK (wildmidi->sinkpad);
|
|
GST_DEBUG ("seek done");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_wildmidi_src_event (GstPad * pad, GstEvent * event)
|
|
{
|
|
gboolean res = FALSE;
|
|
GstWildmidi *wildmidi = GST_WILDMIDI (gst_pad_get_parent (pad));
|
|
|
|
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;
|
|
}
|
|
|
|
g_object_unref (wildmidi);
|
|
return res;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gst_wildmidi_activate (GstPad * sinkpad)
|
|
{
|
|
if (gst_pad_check_pull_range (sinkpad))
|
|
return gst_pad_activate_pull (sinkpad, TRUE);
|
|
|
|
return gst_pad_activate_push (sinkpad, TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
gst_wildmidi_activatepull (GstPad * pad, gboolean active)
|
|
{
|
|
if (active) {
|
|
return gst_pad_start_task (pad, (GstTaskFunction) gst_wildmidi_loop, pad);
|
|
} else {
|
|
return gst_pad_stop_task (pad);
|
|
}
|
|
}
|
|
|
|
static GstBuffer *
|
|
gst_wildmidi_clip_buffer (GstWildmidi * wildmidi, GstBuffer * buffer)
|
|
{
|
|
gint64 start, stop;
|
|
gint64 new_start, new_stop;
|
|
gint64 offset, length;
|
|
GstBuffer *out;
|
|
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;
|
|
out = gst_buffer_create_sub (buffer, offset * bpf, length * bpf);
|
|
|
|
GST_BUFFER_OFFSET (out) = new_start;
|
|
GST_BUFFER_OFFSET_END (out) = new_stop;
|
|
GST_BUFFER_TIMESTAMP (out) =
|
|
gst_util_uint64_scale_int (new_start, GST_SECOND, WILDMIDI_RATE);
|
|
GST_BUFFER_DURATION (out) =
|
|
gst_util_uint64_scale_int (new_stop, GST_SECOND, WILDMIDI_RATE) -
|
|
GST_BUFFER_TIMESTAMP (out);
|
|
|
|
gst_buffer_unref (buffer);
|
|
|
|
return out;
|
|
}
|
|
|
|
/* generate audio data and advance internal timers */
|
|
static GstBuffer *
|
|
gst_wildmidi_get_buffer (GstWildmidi * wildmidi)
|
|
{
|
|
size_t bytes_read;
|
|
gint64 samples;
|
|
GstBuffer *buffer;
|
|
GstSegment *segment;
|
|
guint8 *data;
|
|
guint size;
|
|
guint bpf;
|
|
|
|
bpf = wildmidi->bytes_per_frame;
|
|
|
|
buffer = gst_buffer_new_and_alloc (256 * bpf);
|
|
|
|
data = GST_BUFFER_DATA (buffer);
|
|
size = GST_BUFFER_SIZE (buffer);
|
|
|
|
GST_OBJECT_LOCK (wildmidi);
|
|
bytes_read = WildMidi_GetOutput (wildmidi->song, (char *) data,
|
|
(unsigned long int) size);
|
|
GST_OBJECT_UNLOCK (wildmidi);
|
|
|
|
if (bytes_read == 0) {
|
|
gst_buffer_unref (buffer);
|
|
return NULL;
|
|
}
|
|
|
|
/* adjust buffer size */
|
|
size = GST_BUFFER_SIZE (buffer) = bytes_read;
|
|
|
|
segment = wildmidi->o_segment;
|
|
|
|
GST_BUFFER_OFFSET (buffer) = segment->last_stop;
|
|
GST_BUFFER_TIMESTAMP (buffer) =
|
|
gst_util_uint64_scale_int (segment->last_stop, GST_SECOND, WILDMIDI_RATE);
|
|
|
|
samples = size / bpf;
|
|
segment->last_stop += samples;
|
|
|
|
GST_BUFFER_OFFSET_END (buffer) = segment->last_stop;
|
|
GST_BUFFER_DURATION (buffer) =
|
|
gst_util_uint64_scale_int (segment->last_stop, GST_SECOND,
|
|
WILDMIDI_RATE) - GST_BUFFER_TIMESTAMP (buffer);
|
|
|
|
GST_DEBUG_OBJECT (wildmidi,
|
|
"buffer ts: %" GST_TIME_FORMAT ", dur: %" GST_TIME_FORMAT
|
|
" (%d 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;
|
|
|
|
WildMidi_LoadSamples (wildmidi->song);
|
|
|
|
WildMidi_SetOption (wildmidi->song, WM_MO_LINEAR_VOLUME,
|
|
wildmidi->linear_volume);
|
|
WildMidi_SetOption (wildmidi->song, WM_MO_EXPENSIVE_INTERPOLATION,
|
|
wildmidi->high_quality);
|
|
|
|
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_set_newsegment (wildmidi->o_segment, FALSE, 1.0,
|
|
GST_FORMAT_DEFAULT, 0, GST_CLOCK_TIME_NONE, 0);
|
|
|
|
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;
|
|
}
|
|
|
|
gst_buffer_set_caps (out, GST_PAD_CAPS (wildmidi->srcpad));
|
|
ret = gst_pad_push (wildmidi->srcpad, out);
|
|
|
|
return ret;
|
|
|
|
/* ERRORS */
|
|
eos:
|
|
{
|
|
GST_LOG_OBJECT (wildmidi, "Song ended");
|
|
return GST_FLOW_UNEXPECTED;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_wildmidi_sink_event (GstPad * pad, GstEvent * event)
|
|
{
|
|
gboolean res = FALSE;
|
|
GstWildmidi *wildmidi = GST_WILDMIDI (gst_pad_get_parent (pad));
|
|
|
|
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 */
|
|
gst_pad_start_task (wildmidi->sinkpad,
|
|
(GstTaskFunction) gst_wildmidi_loop, wildmidi->sinkpad);
|
|
/* don't forward the event */
|
|
gst_event_unref (event);
|
|
break;
|
|
default:
|
|
res = gst_pad_push_event (wildmidi->srcpad, event);
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_wildmidi_chain (GstPad * sinkpad, GstBuffer * buffer)
|
|
{
|
|
GstWildmidi *wildmidi;
|
|
|
|
wildmidi = GST_WILDMIDI (GST_PAD_PARENT (sinkpad));
|
|
|
|
/* 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;
|
|
|
|
GST_DEBUG_OBJECT (wildmidi, "loading song");
|
|
|
|
ret =
|
|
gst_pad_pull_range (wildmidi->sinkpad, wildmidi->offset, -1, &buffer);
|
|
|
|
if (ret == GST_FLOW_UNEXPECTED) {
|
|
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_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 (GST_FLOW_IS_FATAL (ret) || ret == GST_FLOW_NOT_LINKED) {
|
|
if (ret == GST_FLOW_UNEXPECTED) {
|
|
/* perform EOS logic */
|
|
event = gst_event_new_eos ();
|
|
gst_pad_push_event (wildmidi->srcpad, event);
|
|
} else {
|
|
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 (wildmidi, STREAM, FAILED,
|
|
("Internal data flow error."),
|
|
("streaming task paused, reason %s (%d)", reason, 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 ARG_LINEAR_VOLUME:
|
|
GST_OBJECT_LOCK (object);
|
|
wildmidi->linear_volume = g_value_get_boolean (value);
|
|
if (wildmidi->song)
|
|
WildMidi_SetOption (wildmidi->song, WM_MO_LINEAR_VOLUME,
|
|
wildmidi->linear_volume);
|
|
GST_OBJECT_UNLOCK (object);
|
|
break;
|
|
case ARG_HIGH_QUALITY:
|
|
GST_OBJECT_LOCK (object);
|
|
wildmidi->high_quality = g_value_get_boolean (value);
|
|
if (wildmidi->song)
|
|
WildMidi_SetOption (wildmidi->song, WM_MO_EXPENSIVE_INTERPOLATION,
|
|
wildmidi->high_quality);
|
|
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 ARG_LINEAR_VOLUME:
|
|
GST_OBJECT_LOCK (object);
|
|
g_value_set_boolean (value, wildmidi->linear_volume);
|
|
GST_OBJECT_UNLOCK (object);
|
|
break;
|
|
case ARG_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)
|