summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Dröge <sebastian.droege@collabora.co.uk>2009-07-14 14:06:09 +0200
committerSebastian Dröge <slomo@circular-chaos.org>2013-09-05 10:24:13 +0200
commit815e5ab0a52dd1db09cf5befc86692fc274b7c9c (patch)
treec8ff01e380fef4773da71ff1976592070735f847
parent05183675077d502aaf0e7483656f5610bb4d0f43 (diff)
musepack: Add first version of a parsermusepack
-rw-r--r--ext/musepack/Makefile.am12
-rw-r--r--ext/musepack/gstbaseparse.c1762
-rw-r--r--ext/musepack/gstbaseparse.h238
-rw-r--r--ext/musepack/gstmusepackdec.c7
-rw-r--r--ext/musepack/gstmusepackparse.c661
-rw-r--r--ext/musepack/gstmusepackparse.h71
6 files changed, 2745 insertions, 6 deletions
diff --git a/ext/musepack/Makefile.am b/ext/musepack/Makefile.am
index ff2532c0d..7ed19390e 100644
--- a/ext/musepack/Makefile.am
+++ b/ext/musepack/Makefile.am
@@ -2,12 +2,16 @@ plugin_LTLIBRARIES = libgstmusepack.la
libgstmusepack_la_SOURCES = \
gstmusepackdec.c \
- gstmusepackreader.c
-libgstmusepack_la_CFLAGS = $(MUSEPACK_CFLAGS) $(GST_CFLAGS)
-libgstmusepack_la_LIBADD = $(MUSEPACK_LIBS) $(GST_LIBS)
+ gstmusepackreader.c \
+ gstbaseparse.c \
+ gstmusepackparse.c
+libgstmusepack_la_CFLAGS = $(MUSEPACK_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS)
+libgstmusepack_la_LIBADD = $(MUSEPACK_LIBS) $(GST_BASE_LIBS) $(GST_LIBS)
libgstmusepack_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
libgstmusepack_la_LIBTOOLFLAGS = --tag=disable-static
noinst_HEADERS = \
gstmusepackdec.h \
- gstmusepackreader.h
+ gstmusepackreader.h \
+ gstbaseparse.h \
+ gstmusepackparse.h
diff --git a/ext/musepack/gstbaseparse.c b/ext/musepack/gstbaseparse.c
new file mode 100644
index 000000000..838547271
--- /dev/null
+++ b/ext/musepack/gstbaseparse.c
@@ -0,0 +1,1762 @@
+/* GStreamer
+ * Copyright (C) 2008 Nokia Corporation. All rights reserved.
+ * Contact: Stefan Kost <stefan.kost@nokia.com>
+ * Copyright (C) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>.
+ *
+ * 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:gstbaseparse
+ * @short_description: Base class for stream parsers
+ * @see_also: #GstBaseTransform
+ *
+ * This base class is for parser elements that process data and splits it
+ * into separate audio/video/whatever frames.
+ *
+ * It provides for:
+ * <itemizedlist>
+ * <listitem><para>One sinkpad and one srcpad</para></listitem>
+ * <listitem><para>Handles state changes</para></listitem>
+ * <listitem><para>Does flushing</para></listitem>
+ * <listitem><para>Push mode</para></listitem>
+ * <listitem><para>Pull mode</para></listitem>
+ * <listitem><para>Handles events (NEWSEGMENT/EOS/FLUSH)</para></listitem>
+ * <listitem><para>Handles seeking in both modes</para></listitem>
+ * <listitem><para>
+ * Handles POSITION/DURATION/SEEKING/FORMAT/CONVERT queries
+ * </para></listitem>
+ * </itemizedlist>
+ *
+ * The purpose of this base class is to provide a basic functionality of
+ * a parser and share a lot of rather complex code.
+ *
+ * Description of the parsing mechanism:
+ * <orderedlist>
+ * <listitem>
+ * <itemizedlist><title>Set-up phase</title>
+ * <listitem><para>
+ * GstBaseParse class calls @set_sink_caps to inform the subclass about
+ * incoming sinkpad caps. Subclass should set the srcpad caps accordingly.
+ * </para></listitem>
+ * <listitem><para>
+ * GstBaseParse calls @start to inform subclass that data processing is
+ * about to start now.
+ * </para></listitem>
+ * <listitem><para>
+ * At least in this point subclass needs to tell the GstBaseParse class
+ * how big data chunks it wants to receive (min_frame_size). It can do
+ * this with @gst_base_parse_set_min_frame_size.
+ * </para></listitem>
+ * <listitem><para>
+ * GstBaseParse class sets up appropriate data passing mode (pull/push)
+ * and starts to process the data.
+ * </para></listitem>
+ * </itemizedlist>
+ * </listitem>
+ * <listitem>
+ * <itemizedlist>
+ * <title>Parsing phase</title>
+ * <listitem><para>
+ * GstBaseParse gathers at least min_frame_size bytes of data either
+ * by pulling it from upstream or collecting buffers into internal
+ * #GstAdapter.
+ * </para></listitem>
+ * <listitem><para>
+ * A buffer of min_frame_size bytes is passed to subclass with
+ * @check_valid_frame. Subclass checks the contents and returns TRUE
+ * if the buffer contains a valid frame. It also needs to set the
+ * @framesize according to the detected frame size. If buffer didn't
+ * contain a valid frame, this call must return FALSE and optionally
+ * set the @skipsize value to inform base class that how many bytes
+ * it needs to skip in order to find a valid frame. The passed buffer
+ * is read-only.
+ * </para></listitem>
+ * <listitem><para>
+ * After valid frame is found, it will be passed again to subclass with
+ * @parse_frame call. Now subclass is responsible for parsing the
+ * frame contents and setting the buffer timestamp, duration and caps.
+ * </para></listitem>
+ * <listitem><para>
+ * Finally the buffer can be pushed downstream and parsing loop starts
+ * over again.
+ * </para></listitem>
+ * <listitem><para>
+ * During the parsing process GstBaseClass will handle both srcpad and
+ * sinkpad events. They will be passed to subclass if @event or
+ * @src_event callbacks have been provided.
+ * </para></listitem>
+ * </itemizedlist>
+ * </listitem>
+ * <listitem>
+ * <itemizedlist><title>Shutdown phase</title>
+ * <listitem><para>
+ * GstBaseParse class calls @stop to inform the subclass that data
+ * parsing will be stopped.
+ * </para></listitem>
+ * </itemizedlist>
+ * </listitem>
+ * </orderedlist>
+ *
+ * Subclass is responsible for providing pad template caps for
+ * source and sink pads. The pads need to be named "sink" and "src". It also
+ * needs to set the fixed caps on srcpad, when the format is ensured (e.g.
+ * when base class calls subclass' @set_sink_caps function).
+ *
+ * This base class uses GST_FORMAT_DEFAULT as a meaning of frames. So,
+ * subclass conversion routine needs to know that conversion from
+ * GST_FORMAT_TIME to GST_FORMAT_DEFAULT must return the
+ * frame number that can be found from the given byte position.
+ *
+ * GstBaseParse uses subclasses conversion methods also for seeking. If
+ * subclass doesn't provide @convert function, seeking will get disabled.
+ *
+ * Subclass @start and @stop functions will be called to inform the beginning
+ * and end of data processing.
+ *
+ * Things that subclass need to take care of:
+ * <itemizedlist>
+ * <listitem><para>Provide pad templates</para></listitem>
+ * <listitem><para>
+ * Fixate the source pad caps when appropriate
+ * </para></listitem>
+ * <listitem><para>
+ * Inform base class how big data chunks should be retrieved. This is
+ * done with @gst_base_parse_set_min_frame_size function.
+ * </para></listitem>
+ * <listitem><para>
+ * Examine data chunks passed to subclass with @check_valid_frame
+ * and tell if they contain a valid frame
+ * </para></listitem>
+ * <listitem><para>
+ * Set the caps and timestamp to frame that is passed to subclass with
+ * @parse_frame function.
+ * </para></listitem>
+ * <listitem><para>Provide conversion functions</para></listitem>
+ * <listitem><para>
+ * Update the duration information with @gst_base_parse_set_duration
+ * </para></listitem>
+ * </itemizedlist>
+ *
+ */
+
+/* TODO:
+ * - Better segment handling:
+ * - NEWSEGMENT for gaps
+ * - Not NEWSEGMENT starting at 0 but at first frame timestamp
+ * - GstIndex support
+ * - Seek table generation and subclass seek entry injection
+ * - Accurate seeking
+ * - In push mode provide a queue of adapter-"queued" buffers for upstream
+ * buffer metadata
+ * - Timestamp tracking and setting
+ * - Handle upstream timestamps
+ * - Queue buffers/events until caps are set
+ * - Bitrate tracking => inaccurate seeking, inaccurate duration calculation
+ * - Let subclass decide if frames outside the segment should be dropped
+ * - Send queries upstream
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "gstbaseparse.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_base_parse_debug);
+#define GST_CAT_DEFAULT gst_base_parse_debug
+
+/* Supported formats */
+static GstFormat fmtlist[] = {
+ GST_FORMAT_DEFAULT,
+ GST_FORMAT_BYTES,
+ GST_FORMAT_TIME,
+ 0
+};
+
+#define GST_BASE_PARSE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_BASE_PARSE, GstBaseParsePrivate))
+
+struct _GstBaseParsePrivate
+{
+ GstActivateMode pad_mode;
+
+ gint64 duration;
+ GstFormat duration_fmt;
+
+ guint min_frame_size;
+
+ gboolean discont;
+ gboolean flushing;
+
+ gint64 offset;
+
+ GList *pending_events;
+
+ GstBuffer *cache;
+};
+
+struct _GstBaseParseClassPrivate
+{
+ gpointer _padding;
+};
+
+static GstElementClass *parent_class = NULL;
+
+static void gst_base_parse_base_init (gpointer g_class);
+static void gst_base_parse_base_finalize (gpointer g_class);
+static void gst_base_parse_class_init (GstBaseParseClass * klass);
+static void gst_base_parse_init (GstBaseParse * parse,
+ GstBaseParseClass * klass);
+
+GType
+gst_base_parse_get_type (void)
+{
+ static GType base_parse_type = 0;
+
+ if (!base_parse_type) {
+ static const GTypeInfo base_parse_info = {
+ sizeof (GstBaseParseClass),
+ (GBaseInitFunc) gst_base_parse_base_init,
+ (GBaseFinalizeFunc) gst_base_parse_base_finalize,
+ (GClassInitFunc) gst_base_parse_class_init,
+ NULL,
+ NULL,
+ sizeof (GstBaseParse),
+ 0,
+ (GInstanceInitFunc) gst_base_parse_init,
+ };
+
+ base_parse_type = g_type_register_static (GST_TYPE_ELEMENT,
+ "GstMpcBaseParse", &base_parse_info, G_TYPE_FLAG_ABSTRACT);
+ }
+ return base_parse_type;
+}
+
+static void gst_base_parse_finalize (GObject * object);
+
+static gboolean gst_base_parse_sink_activate (GstPad * sinkpad);
+static gboolean gst_base_parse_sink_activate_push (GstPad * pad,
+ gboolean active);
+static gboolean gst_base_parse_sink_activate_pull (GstPad * pad,
+ gboolean active);
+static gboolean gst_base_parse_handle_seek (GstBaseParse * parse,
+ GstEvent * event);
+
+static gboolean gst_base_parse_src_event (GstPad * pad, GstEvent * event);
+static gboolean gst_base_parse_sink_event (GstPad * pad, GstEvent * event);
+static gboolean gst_base_parse_query (GstPad * pad, GstQuery * query);
+static gboolean gst_base_parse_sink_setcaps (GstPad * pad, GstCaps * caps);
+static const GstQueryType *gst_base_parse_get_querytypes (GstPad * pad);
+
+static GstFlowReturn gst_base_parse_chain (GstPad * pad, GstBuffer * buffer);
+static void gst_base_parse_loop (GstPad * pad);
+
+static gboolean gst_base_parse_check_frame (GstBaseParse * parse,
+ GstBuffer * buffer, guint * framesize, gint * skipsize);
+
+static GstFlowReturn gst_base_parse_parse_frame (GstBaseParse * parse,
+ GstBuffer * buffer);
+
+static gboolean gst_base_parse_sink_eventfunc (GstBaseParse * parse,
+ GstEvent * event);
+
+static gboolean gst_base_parse_src_eventfunc (GstBaseParse * parse,
+ GstEvent * event);
+
+static gboolean gst_base_parse_is_seekable (GstBaseParse * parse);
+
+static void gst_base_parse_drain (GstBaseParse * parse);
+
+static void
+gst_base_parse_base_init (gpointer g_class)
+{
+ GstBaseParseClass *klass = GST_BASE_PARSE_CLASS (g_class);
+ GstBaseParseClassPrivate *priv;
+
+ GST_DEBUG_CATEGORY_INIT (gst_base_parse_debug, "mpcbaseparse", 0,
+ "baseparse element");
+
+ /* TODO: Remove this once GObject supports class private data */
+ priv = g_slice_new0 (GstBaseParseClassPrivate);
+ if (klass->priv)
+ memcpy (priv, klass->priv, sizeof (GstBaseParseClassPrivate));
+ klass->priv = priv;
+}
+
+static void
+gst_base_parse_base_finalize (gpointer g_class)
+{
+ GstBaseParseClass *klass = GST_BASE_PARSE_CLASS (g_class);
+
+ g_slice_free (GstBaseParseClassPrivate, klass->priv);
+ klass->priv = NULL;
+}
+
+static void
+gst_base_parse_finalize (GObject * object)
+{
+ GstBaseParse *parse = GST_BASE_PARSE (object);
+
+ g_mutex_free (parse->parse_lock);
+ g_object_unref (parse->adapter);
+
+ if (parse->pending_segment) {
+ gst_event_replace (&parse->pending_segment, NULL);
+ }
+ if (parse->close_segment) {
+ gst_event_replace (&parse->close_segment, NULL);
+ }
+
+ if (parse->priv->cache) {
+ gst_buffer_unref (parse->priv->cache);
+ parse->priv->cache = NULL;
+ }
+
+ g_list_foreach (parse->priv->pending_events, (GFunc) gst_mini_object_unref,
+ NULL);
+ g_list_free (parse->priv->pending_events);
+ parse->priv->pending_events = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_base_parse_class_init (GstBaseParseClass * klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ g_type_class_add_private (klass, sizeof (GstBaseParsePrivate));
+ parent_class = g_type_class_peek_parent (klass);
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_base_parse_finalize);
+
+ /* Default handlers */
+ klass->check_valid_frame = gst_base_parse_check_frame;
+ klass->parse_frame = gst_base_parse_parse_frame;
+ klass->event = gst_base_parse_sink_eventfunc;
+ klass->src_event = gst_base_parse_src_eventfunc;
+ klass->is_seekable = gst_base_parse_is_seekable;
+}
+
+static void
+gst_base_parse_init (GstBaseParse * parse, GstBaseParseClass * bclass)
+{
+ GstPadTemplate *pad_template;
+
+ GST_DEBUG_OBJECT (parse, "gst_base_parse_init");
+
+ parse->priv = GST_BASE_PARSE_GET_PRIVATE (parse);
+
+ pad_template =
+ gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "sink");
+ g_return_if_fail (pad_template != NULL);
+ parse->sinkpad = gst_pad_new_from_template (pad_template, "sink");
+ gst_pad_set_event_function (parse->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_base_parse_sink_event));
+ gst_pad_set_setcaps_function (parse->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_base_parse_sink_setcaps));
+ gst_pad_set_chain_function (parse->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_base_parse_chain));
+ gst_pad_set_activate_function (parse->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_base_parse_sink_activate));
+ gst_pad_set_activatepush_function (parse->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_base_parse_sink_activate_push));
+ gst_pad_set_activatepull_function (parse->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_base_parse_sink_activate_pull));
+ gst_element_add_pad (GST_ELEMENT (parse), parse->sinkpad);
+
+ GST_DEBUG_OBJECT (parse, "sinkpad created");
+
+ pad_template =
+ gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "src");
+ g_return_if_fail (pad_template != NULL);
+ parse->srcpad = gst_pad_new_from_template (pad_template, "src");
+ gst_pad_set_event_function (parse->srcpad,
+ GST_DEBUG_FUNCPTR (gst_base_parse_src_event));
+ gst_pad_set_query_type_function (parse->srcpad,
+ GST_DEBUG_FUNCPTR (gst_base_parse_get_querytypes));
+ gst_pad_set_query_function (parse->srcpad,
+ GST_DEBUG_FUNCPTR (gst_base_parse_query));
+ gst_element_add_pad (GST_ELEMENT (parse), parse->srcpad);
+ GST_DEBUG_OBJECT (parse, "src created");
+
+ parse->parse_lock = g_mutex_new ();
+ parse->adapter = gst_adapter_new ();
+ parse->pending_segment = NULL;
+ parse->close_segment = NULL;
+
+ parse->priv->pad_mode = GST_ACTIVATE_NONE;
+ parse->priv->duration = -1;
+ parse->priv->min_frame_size = 1;
+ parse->priv->discont = FALSE;
+ parse->priv->flushing = FALSE;
+ parse->priv->offset = 0;
+ GST_DEBUG_OBJECT (parse, "init ok");
+}
+
+
+
+/**
+ * gst_base_parse_check_frame:
+ * @parse: #GstBaseParse.
+ * @buffer: GstBuffer.
+ * @framesize: This will be set to tell the found frame size in bytes.
+ * @skipsize: Output parameter that tells how much data needs to be skipped
+ * in order to find the following frame header.
+ *
+ * Default callback for check_valid_frame.
+ *
+ * Returns: Always TRUE.
+ */
+static gboolean
+gst_base_parse_check_frame (GstBaseParse * parse,
+ GstBuffer * buffer, guint * framesize, gint * skipsize)
+{
+ *framesize = GST_BUFFER_SIZE (buffer);
+ *skipsize = 0;
+ return TRUE;
+}
+
+
+/**
+ * gst_base_parse_parse_frame:
+ * @parse: #GstBaseParse.
+ * @buffer: #GstBuffer.
+ *
+ * Default callback for parse_frame.
+ */
+static GstFlowReturn
+gst_base_parse_parse_frame (GstBaseParse * parse, GstBuffer * buffer)
+{
+ /* FIXME: Could we even _try_ to do something clever here? */
+ GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE;
+ GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE;
+ return GST_FLOW_OK;
+}
+
+
+/**
+ * gst_base_parse_bytepos_to_time:
+ * @parse: #GstBaseParse.
+ * @bytepos: Position (in bytes) to be converted.
+ * @pos_in_time: #GstClockTime pointer where the result is set.
+ *
+ * Convert given byte position into #GstClockTime format.
+ *
+ * Returns: TRUE if conversion succeeded.
+ */
+static gboolean
+gst_base_parse_bytepos_to_time (GstBaseParse * parse, gint64 bytepos,
+ GstClockTime * pos_in_time)
+{
+ GstBaseParseClass *klass;
+ gboolean res = FALSE;
+
+ klass = GST_BASE_PARSE_GET_CLASS (parse);
+
+ if (klass->convert) {
+ res = klass->convert (parse, GST_FORMAT_BYTES, bytepos,
+ GST_FORMAT_TIME, (gint64 *) pos_in_time);
+ }
+ return res;
+}
+
+
+/**
+ * gst_base_parse_sink_event:
+ * @pad: #GstPad that received the event.
+ * @event: #GstEvent to be handled.
+ *
+ * Handler for sink pad events.
+ *
+ * Returns: TRUE if the event was handled.
+ */
+static gboolean
+gst_base_parse_sink_event (GstPad * pad, GstEvent * event)
+{
+ GstBaseParse *parse;
+ GstBaseParseClass *bclass;
+ gboolean handled = FALSE;
+ gboolean ret = TRUE;
+
+
+ parse = GST_BASE_PARSE (gst_pad_get_parent (pad));
+ bclass = GST_BASE_PARSE_GET_CLASS (parse);
+
+ GST_DEBUG_OBJECT (parse, "handling event %d", GST_EVENT_TYPE (event));
+
+ /* Cache all events except EOS, NEWSEGMENT and FLUSH_STOP if we have a
+ * pending segment */
+ if (parse->pending_segment && GST_EVENT_TYPE (event) != GST_EVENT_EOS
+ && GST_EVENT_TYPE (event) != GST_EVENT_NEWSEGMENT
+ && GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_START
+ && GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_STOP) {
+ parse->priv->pending_events =
+ g_list_append (parse->priv->pending_events, event);
+ ret = TRUE;
+ } else {
+
+ if (bclass->event)
+ handled = bclass->event (parse, event);
+
+ if (!handled)
+ ret = gst_pad_event_default (pad, event);
+ }
+
+ gst_object_unref (parse);
+ GST_DEBUG_OBJECT (parse, "event handled");
+ return ret;
+}
+
+
+/**
+ * gst_base_parse_sink_eventfunc:
+ * @parse: #GstBaseParse.
+ * @event: #GstEvent to be handled.
+ *
+ * Element-level event handler function.
+ *
+ * Returns: TRUE if the event was handled and not need forwarding.
+ */
+static gboolean
+gst_base_parse_sink_eventfunc (GstBaseParse * parse, GstEvent * event)
+{
+ gboolean handled = FALSE;
+ GstEvent **eventp;
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_NEWSEGMENT:
+ {
+ gdouble rate, applied_rate;
+ GstFormat format;
+ gint64 start, stop, pos, offset = 0;
+ gboolean update;
+
+ gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate,
+ &format, &start, &stop, &pos);
+
+
+ if (format == GST_FORMAT_BYTES) {
+ GstClockTime seg_start, seg_stop, seg_pos;
+
+ /* stop time is allowed to be open-ended, but not start & pos */
+ seg_stop = GST_CLOCK_TIME_NONE;
+ offset = pos;
+
+ if (gst_base_parse_bytepos_to_time (parse, start, &seg_start) &&
+ gst_base_parse_bytepos_to_time (parse, pos, &seg_pos)) {
+ gst_event_unref (event);
+ event = gst_event_new_new_segment_full (update, rate, applied_rate,
+ GST_FORMAT_TIME, seg_start, seg_stop, seg_pos);
+ format = GST_FORMAT_TIME;
+ GST_DEBUG_OBJECT (parse, "Converted incoming segment to TIME. "
+ "start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT
+ ", pos = %" GST_TIME_FORMAT, GST_TIME_ARGS (seg_start),
+ GST_TIME_ARGS (seg_stop), GST_TIME_ARGS (seg_pos));
+ }
+ }
+
+ if (format != GST_FORMAT_TIME) {
+ /* Unknown incoming segment format. Output a default open-ended
+ * TIME segment */
+ gst_event_unref (event);
+ event = gst_event_new_new_segment_full (update, rate, applied_rate,
+ GST_FORMAT_TIME, 0, GST_CLOCK_TIME_NONE, 0);
+ }
+
+ gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate,
+ &format, &start, &stop, &pos);
+
+ gst_segment_set_newsegment_full (&parse->segment, update, rate,
+ applied_rate, format, start, stop, pos);
+
+ GST_DEBUG_OBJECT (parse, "Created newseg rate %g, applied rate %g, "
+ "format %d, start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT
+ ", pos = %" GST_TIME_FORMAT, rate, applied_rate, format,
+ GST_TIME_ARGS (start), GST_TIME_ARGS (stop), GST_TIME_ARGS (pos));
+
+ /* save the segment for later, right before we push a new buffer so that
+ * the caps are fixed and the next linked element can receive
+ * the segment. */
+ eventp = &parse->pending_segment;
+ gst_event_replace (eventp, event);
+ gst_event_unref (event);
+ handled = TRUE;
+
+ /* but finish the current segment */
+ GST_DEBUG_OBJECT (parse, "draining current segment");
+ gst_base_parse_drain (parse);
+ gst_adapter_clear (parse->adapter);
+ parse->priv->offset = offset;
+ break;
+ }
+
+ case GST_EVENT_FLUSH_START:
+ parse->priv->flushing = TRUE;
+ handled = gst_pad_push_event (parse->srcpad, event);
+ /* Wait for _chain() to exit by taking the srcpad STREAM_LOCK */
+ GST_PAD_STREAM_LOCK (parse->srcpad);
+ GST_PAD_STREAM_UNLOCK (parse->srcpad);
+
+ break;
+
+ case GST_EVENT_FLUSH_STOP:
+ gst_adapter_clear (parse->adapter);
+ parse->priv->flushing = FALSE;
+ parse->priv->discont = TRUE;
+ break;
+
+ case GST_EVENT_EOS:
+ gst_base_parse_drain (parse);
+ break;
+
+ default:
+ break;
+ }
+
+ return handled;
+}
+
+
+/**
+ * gst_base_parse_src_event:
+ * @pad: #GstPad that received the event.
+ * @event: #GstEvent that was received.
+ *
+ * Handler for source pad events.
+ *
+ * Returns: TRUE if the event was handled.
+ */
+static gboolean
+gst_base_parse_src_event (GstPad * pad, GstEvent * event)
+{
+ GstBaseParse *parse;
+ GstBaseParseClass *bclass;
+ gboolean handled = FALSE;
+ gboolean ret = TRUE;
+
+ parse = GST_BASE_PARSE (gst_pad_get_parent (pad));
+ bclass = GST_BASE_PARSE_GET_CLASS (parse);
+
+ GST_DEBUG_OBJECT (parse, "event %d, %s", GST_EVENT_TYPE (event),
+ GST_EVENT_TYPE_NAME (event));
+
+ if (bclass->src_event)
+ handled = bclass->src_event (parse, event);
+
+ if (!handled)
+ ret = gst_pad_event_default (pad, event);
+
+ gst_object_unref (parse);
+ return ret;
+}
+
+
+/**
+ * gst_base_parse_src_eventfunc:
+ * @parse: #GstBaseParse.
+ * @event: #GstEvent that was received.
+ *
+ * Default srcpad event handler.
+ *
+ * Returns: TRUE if the event was handled and can be dropped.
+ */
+static gboolean
+gst_base_parse_src_eventfunc (GstBaseParse * parse, GstEvent * event)
+{
+ gboolean handled = FALSE;
+ GstBaseParseClass *bclass;
+
+ bclass = GST_BASE_PARSE_GET_CLASS (parse);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_SEEK:
+ {
+ if (bclass->is_seekable (parse)) {
+ handled = gst_base_parse_handle_seek (parse, event);
+ gst_event_unref (event);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return handled;
+}
+
+
+/**
+ * gst_base_parse_is_seekable:
+ * @parse: #GstBaseParse.
+ *
+ * Default handler for is_seekable.
+ *
+ * Returns: Always TRUE.
+ */
+static gboolean
+gst_base_parse_is_seekable (GstBaseParse * parse)
+{
+ return TRUE;
+}
+
+
+/**
+ * gst_base_parse_handle_and_push_buffer:
+ * @parse: #GstBaseParse.
+ * @klass: #GstBaseParseClass.
+ * @buffer: #GstBuffer.
+ *
+ * Parses the frame from given buffer and pushes it forward. Also performs
+ * timestamp handling and checks the segment limits.
+ *
+ * This is called with srcpad STREAM_LOCK held.
+ *
+ * Returns: #GstFlowReturn
+ */
+static GstFlowReturn
+gst_base_parse_handle_and_push_buffer (GstBaseParse * parse,
+ GstBaseParseClass * klass, GstBuffer * buffer)
+{
+ GstFlowReturn ret;
+
+ if (parse->priv->discont) {
+ GST_DEBUG_OBJECT (parse, "marking DISCONT");
+ GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
+ parse->priv->discont = FALSE;
+ }
+
+ ret = klass->parse_frame (parse, buffer);
+
+ /* FIXME: Check the output buffer for any missing metadata,
+ * keep track of timestamp and calculate everything possible
+ * if not set already */
+
+ /* First buffers are dropped, this means that the subclass needs more
+ * frames to decide on the format and queues them internally */
+ if (ret == GST_BASE_PARSE_FLOW_DROPPED && !GST_PAD_CAPS (parse->srcpad)) {
+ gst_buffer_unref (buffer);
+ return GST_FLOW_OK;
+ }
+
+ /* convert internal flow to OK and mark discont for the next buffer. */
+ if (ret == GST_BASE_PARSE_FLOW_DROPPED) {
+ parse->priv->discont = TRUE;
+ ret = GST_FLOW_OK;
+
+ gst_buffer_unref (buffer);
+
+ return ret;
+ } else if (ret != GST_FLOW_OK) {
+ return ret;
+ }
+
+ return gst_base_parse_push_buffer (parse, buffer);
+}
+
+GstFlowReturn
+gst_base_parse_push_buffer (GstBaseParse * parse, GstBuffer * buffer)
+{
+ GstFlowReturn ret = GST_FLOW_OK;
+ GstClockTime last_stop = GST_CLOCK_TIME_NONE;
+
+ if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
+ last_stop = GST_BUFFER_TIMESTAMP (buffer);
+ if (last_stop != GST_CLOCK_TIME_NONE && GST_BUFFER_DURATION_IS_VALID (buffer))
+ last_stop += GST_BUFFER_DURATION (buffer);
+
+ /* should have caps by now */
+ g_return_val_if_fail (GST_PAD_CAPS (parse->srcpad), GST_FLOW_ERROR);
+
+ gst_buffer_set_caps (buffer, GST_PAD_CAPS (parse->srcpad));
+
+ /* and should then also be linked downstream, so safe to send some events */
+ if (parse->priv->pad_mode == GST_ACTIVATE_PULL) {
+ if (G_UNLIKELY (parse->close_segment)) {
+ GST_DEBUG_OBJECT (parse, "loop sending close segment");
+ gst_pad_push_event (parse->srcpad, parse->close_segment);
+ parse->close_segment = NULL;
+ }
+
+ if (G_UNLIKELY (parse->pending_segment)) {
+ GST_DEBUG_OBJECT (parse, "loop push pending segment");
+ gst_pad_push_event (parse->srcpad, parse->pending_segment);
+ parse->pending_segment = NULL;
+ }
+ } else {
+ if (G_UNLIKELY (parse->pending_segment)) {
+ GST_DEBUG_OBJECT (parse, "chain pushing a pending segment");
+ gst_pad_push_event (parse->srcpad, parse->pending_segment);
+ parse->pending_segment = NULL;
+ }
+ }
+
+ if (G_UNLIKELY (parse->priv->pending_events)) {
+ GList *l;
+
+ for (l = parse->priv->pending_events; l != NULL; l = l->next) {
+ gst_pad_push_event (parse->srcpad, GST_EVENT (l->data));
+ }
+ g_list_free (parse->priv->pending_events);
+ parse->priv->pending_events = NULL;
+ }
+
+ /* TODO: Add to seek table */
+
+ if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) &&
+ GST_CLOCK_TIME_IS_VALID (parse->segment.stop) &&
+ GST_BUFFER_TIMESTAMP (buffer) > parse->segment.stop) {
+ GST_LOG_OBJECT (parse, "Dropped frame, after segment");
+ gst_buffer_unref (buffer);
+ } else if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) &&
+ GST_BUFFER_DURATION_IS_VALID (buffer) &&
+ GST_CLOCK_TIME_IS_VALID (parse->segment.start) &&
+ GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer)
+ < parse->segment.start) {
+ /* FIXME: subclass needs way to override the start as downstream might
+ * need frames before for proper decoding */
+ GST_LOG_OBJECT (parse, "Dropped frame, before segment");
+ gst_buffer_unref (buffer);
+ } else {
+ ret = gst_pad_push (parse->srcpad, buffer);
+ GST_LOG_OBJECT (parse, "frame (%d bytes) pushed: %d",
+ GST_BUFFER_SIZE (buffer), ret);
+ }
+
+ /* Update current running segment position */
+ if (ret == GST_FLOW_OK && last_stop != GST_CLOCK_TIME_NONE)
+ gst_segment_set_last_stop (&parse->segment, GST_FORMAT_TIME, last_stop);
+
+ return ret;
+}
+
+
+/**
+ * gst_base_parse_drain:
+ * @parse: #GstBaseParse.
+ *
+ * Drains the adapter until it is empty. It decreases the min_frame_size to
+ * match the current adapter size and calls chain method until the adapter
+ * is emptied or chain returns with error.
+ */
+static void
+gst_base_parse_drain (GstBaseParse * parse)
+{
+ guint avail;
+
+ for (;;) {
+ avail = gst_adapter_available (parse->adapter);
+ if (!avail)
+ break;
+
+ gst_base_parse_set_min_frame_size (parse, avail);
+ if (gst_base_parse_chain (parse->sinkpad, NULL) != GST_FLOW_OK) {
+ break;
+ }
+ }
+}
+
+
+/**
+ * gst_base_parse_chain:
+ * @pad: #GstPad.
+ * @buffer: #GstBuffer.
+ *
+ * Returns: #GstFlowReturn.
+ */
+static GstFlowReturn
+gst_base_parse_chain (GstPad * pad, GstBuffer * buffer)
+{
+ GstBaseParseClass *bclass;
+ GstBaseParse *parse;
+ GstFlowReturn ret = GST_FLOW_OK;
+ GstBuffer *outbuf = NULL;
+ GstBuffer *tmpbuf = NULL;
+ guint fsize = 0;
+ gint skip = -1;
+ const guint8 *data;
+ guint min_size;
+
+ parse = GST_BASE_PARSE (GST_OBJECT_PARENT (pad));
+ bclass = GST_BASE_PARSE_GET_CLASS (parse);
+
+ if (G_LIKELY (buffer)) {
+ GST_LOG_OBJECT (parse, "buffer size: %d, offset = %lld",
+ GST_BUFFER_SIZE (buffer), GST_BUFFER_OFFSET (buffer));
+ gst_adapter_push (parse->adapter, buffer);
+ }
+
+ /* Parse and push as many frames as possible */
+ /* Stop either when adapter is empty or we are flushing */
+ while (!parse->priv->flushing) {
+ tmpbuf = gst_buffer_new ();
+
+ /* Synchronization loop */
+ for (;;) {
+ GST_BASE_PARSE_LOCK (parse);
+ min_size = parse->priv->min_frame_size;
+ GST_BASE_PARSE_UNLOCK (parse);
+
+ /* Collect at least min_frame_size bytes */
+ if (gst_adapter_available (parse->adapter) < min_size) {
+ GST_DEBUG_OBJECT (parse, "not enough data available (only %d bytes)",
+ gst_adapter_available (parse->adapter));
+ gst_buffer_unref (tmpbuf);
+ goto done;
+ }
+
+ data = gst_adapter_peek (parse->adapter, min_size);
+ GST_BUFFER_DATA (tmpbuf) = (guint8 *) data;
+ GST_BUFFER_SIZE (tmpbuf) = min_size;
+ GST_BUFFER_OFFSET (tmpbuf) = parse->priv->offset;
+ GST_BUFFER_FLAG_SET (tmpbuf, GST_MINI_OBJECT_FLAG_READONLY);
+
+ if (parse->priv->discont) {
+ GST_DEBUG_OBJECT (parse, "marking DISCONT");
+ GST_BUFFER_FLAG_SET (tmpbuf, GST_BUFFER_FLAG_DISCONT);
+ }
+
+ skip = -1;
+ if (bclass->check_valid_frame (parse, tmpbuf, &fsize, &skip)) {
+ if (gst_adapter_available (parse->adapter) < fsize) {
+ GST_DEBUG_OBJECT (parse,
+ "found valid frame but not enough data available (only %d bytes)",
+ gst_adapter_available (parse->adapter));
+ goto done;
+ }
+ break;
+ }
+ if (skip > 0) {
+ GST_LOG_OBJECT (parse, "finding sync, skipping %d bytes", skip);
+ gst_adapter_flush (parse->adapter, skip);
+ parse->priv->offset += skip;
+ } else if (skip == -1) {
+ /* subclass didn't touch this value. By default we skip 1 byte */
+ GST_LOG_OBJECT (parse, "finding sync, skipping 1 byte");
+ gst_adapter_flush (parse->adapter, 1);
+ parse->priv->offset++;
+ }
+ /* There is a possibility that subclass set the skip value to zero.
+ This means that it has probably found a frame but wants to ask
+ more data (by increasing the min_size) to be sure of this. */
+ }
+ gst_buffer_unref (tmpbuf);
+ tmpbuf = NULL;
+
+ if (skip > 0) {
+ /* Subclass found the sync, but still wants to skip some data */
+ GST_LOG_OBJECT (parse, "skipping %d bytes", skip);
+ gst_adapter_flush (parse->adapter, skip);
+ parse->priv->offset += skip;
+ }
+
+ /* Grab lock to prevent a race with FLUSH_START handler */
+ GST_PAD_STREAM_LOCK (parse->srcpad);
+
+ /* FLUSH_START event causes the "flushing" flag to be set. In this
+ * case we can leave the frame pushing loop */
+ if (parse->priv->flushing) {
+ GST_PAD_STREAM_UNLOCK (parse->srcpad);
+ break;
+ }
+
+ /* FIXME: Would it be more efficient to make a subbuffer instead? */
+ outbuf = gst_adapter_take_buffer (parse->adapter, fsize);
+
+ /* Subclass may want to know the data offset */
+ GST_BUFFER_OFFSET (outbuf) = parse->priv->offset;
+ parse->priv->offset += fsize;
+
+ ret = gst_base_parse_handle_and_push_buffer (parse, bclass, outbuf);
+ GST_PAD_STREAM_UNLOCK (parse->srcpad);
+
+ if (ret != GST_FLOW_OK) {
+ GST_LOG_OBJECT (parse, "push returned %d", ret);
+ break;
+ }
+ }
+
+done:
+ GST_LOG_OBJECT (parse, "chain leaving");
+ return ret;
+}
+
+static GstFlowReturn
+gst_base_parse_pull_range (GstBaseParse * parse, guint size,
+ GstBuffer ** buffer)
+{
+ GstFlowReturn ret = GST_FLOW_OK;
+
+ g_return_val_if_fail (buffer != NULL, GST_FLOW_ERROR);
+
+ /* Caching here actually makes much less difference than one would expect.
+ * We do it mainly to avoid pulling buffers of 1 byte all the time */
+ if (parse->priv->cache) {
+ guint64 cache_offset = GST_BUFFER_OFFSET (parse->priv->cache);
+ guint cache_size = GST_BUFFER_SIZE (parse->priv->cache);
+
+ if (cache_offset <= parse->priv->offset &&
+ (parse->priv->offset + size) < (cache_offset + cache_size)) {
+ *buffer = gst_buffer_create_sub (parse->priv->cache,
+ parse->priv->offset - cache_offset, size);
+ GST_BUFFER_OFFSET (*buffer) = parse->priv->offset;
+ return GST_FLOW_OK;
+ }
+ /* not enough data in the cache, free cache and get a new one */
+ gst_buffer_unref (parse->priv->cache);
+ parse->priv->cache = NULL;
+ }
+
+ /* refill the cache */
+ ret =
+ gst_pad_pull_range (parse->sinkpad, parse->priv->offset, MAX (size,
+ 64 * 1024), &parse->priv->cache);
+ if (ret != GST_FLOW_OK) {
+ parse->priv->cache = NULL;
+ return ret;
+ }
+
+ if (GST_BUFFER_SIZE (parse->priv->cache) >= size) {
+ *buffer = gst_buffer_create_sub (parse->priv->cache, 0, size);
+ GST_BUFFER_OFFSET (*buffer) = parse->priv->offset;
+ return GST_FLOW_OK;
+ }
+
+ /* Not possible to get enough data, try a last time with
+ * requesting exactly the size we need */
+ gst_buffer_unref (parse->priv->cache);
+ parse->priv->cache = NULL;
+
+ ret = gst_pad_pull_range (parse->sinkpad, parse->priv->offset, size,
+ &parse->priv->cache);
+
+ if (ret != GST_FLOW_OK) {
+ GST_DEBUG_OBJECT (parse, "pull_range returned %d", ret);
+ *buffer = NULL;
+ return ret;
+ }
+
+ if (GST_BUFFER_SIZE (parse->priv->cache) < size) {
+ GST_WARNING_OBJECT (parse, "Dropping short buffer at offset %"
+ G_GUINT64_FORMAT ": wanted %u bytes, got %u bytes", parse->priv->offset,
+ size, GST_BUFFER_SIZE (parse->priv->cache));
+
+ gst_buffer_unref (parse->priv->cache);
+ parse->priv->cache = NULL;
+
+ *buffer = NULL;
+ return GST_FLOW_UNEXPECTED;
+ }
+
+ *buffer = gst_buffer_create_sub (parse->priv->cache, 0, size);
+ GST_BUFFER_OFFSET (*buffer) = parse->priv->offset;
+
+ return GST_FLOW_OK;
+}
+
+/**
+ * gst_base_parse_loop:
+ * @pad: GstPad
+ *
+ * Loop that is used in pull mode to retrieve data from upstream.
+ */
+static void
+gst_base_parse_loop (GstPad * pad)
+{
+ GstBaseParse *parse;
+ GstBaseParseClass *klass;
+ GstBuffer *buffer, *outbuf;
+ gboolean ret = FALSE;
+ guint fsize = 0, min_size;
+ gint skip = 0;
+
+ parse = GST_BASE_PARSE (gst_pad_get_parent (pad));
+ klass = GST_BASE_PARSE_GET_CLASS (parse);
+
+ /* TODO: Check if we reach segment stop limit */
+
+ while (TRUE) {
+
+ GST_BASE_PARSE_LOCK (parse);
+ min_size = parse->priv->min_frame_size;
+ GST_BASE_PARSE_UNLOCK (parse);
+
+ ret = gst_base_parse_pull_range (parse, min_size, &buffer);
+
+ if (ret == GST_FLOW_UNEXPECTED)
+ goto eos;
+ else if (ret != GST_FLOW_OK)
+ goto need_pause;
+
+ if (parse->priv->discont) {
+ GST_DEBUG_OBJECT (parse, "marking DISCONT");
+ GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
+ }
+
+ skip = -1;
+ if (klass->check_valid_frame (parse, buffer, &fsize, &skip)) {
+ break;
+ }
+ if (skip > 0) {
+ GST_LOG_OBJECT (parse, "finding sync, skipping %d bytes", skip);
+ parse->priv->offset += skip;
+ } else if (skip == -1) {
+ GST_LOG_OBJECT (parse, "finding sync, skipping 1 byte");
+ parse->priv->offset++;
+ }
+ GST_DEBUG_OBJECT (parse, "finding sync...");
+ gst_buffer_unref (buffer);
+ }
+
+ if (fsize <= GST_BUFFER_SIZE (buffer)) {
+ outbuf = gst_buffer_create_sub (buffer, 0, fsize);
+ GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET (buffer);
+ gst_buffer_unref (buffer);
+ } else {
+ gst_buffer_unref (buffer);
+ ret = gst_base_parse_pull_range (parse, fsize, &outbuf);
+
+ if (ret == GST_FLOW_UNEXPECTED)
+ goto eos;
+ else if (ret != GST_FLOW_OK)
+ goto need_pause;
+ }
+
+ parse->priv->offset += fsize;
+
+ /* Does the subclass want to skip too? */
+ if (skip > 0)
+ parse->priv->offset += skip;
+
+ /* This always unrefs the outbuf, even if error occurs */
+ ret = gst_base_parse_handle_and_push_buffer (parse, klass, outbuf);
+
+ if (ret != GST_FLOW_OK) {
+ GST_DEBUG_OBJECT (parse, "flow: %s", gst_flow_get_name (ret));
+ if (GST_FLOW_IS_FATAL (ret)) {
+ GST_ELEMENT_ERROR (parse, STREAM, FAILED, (NULL),
+ ("streaming task paused, reason: %s", gst_flow_get_name (ret)));
+ gst_pad_push_event (parse->srcpad, gst_event_new_eos ());
+ }
+ goto need_pause;
+ }
+
+ gst_object_unref (parse);
+ return;
+
+need_pause:
+ {
+ GST_LOG_OBJECT (parse, "pausing task");
+ gst_pad_pause_task (pad);
+ gst_object_unref (parse);
+ return;
+ }
+eos:
+ {
+ GST_LOG_OBJECT (parse, "pausing task %d", ret);
+ gst_pad_push_event (parse->srcpad, gst_event_new_eos ());
+ gst_pad_pause_task (pad);
+ gst_object_unref (parse);
+ return;
+ }
+}
+
+
+/**
+ * gst_base_parse_sink_activate:
+ * @sinkpad: #GstPad to be activated.
+ *
+ * Returns: TRUE if activation succeeded.
+ */
+static gboolean
+gst_base_parse_sink_activate (GstPad * sinkpad)
+{
+ GstBaseParse *parse;
+ gboolean result = TRUE;
+
+ parse = GST_BASE_PARSE (gst_pad_get_parent (sinkpad));
+
+ GST_DEBUG_OBJECT (parse, "sink activate");
+
+ if (gst_pad_check_pull_range (sinkpad)) {
+ GST_DEBUG_OBJECT (parse, "trying to activate in pull mode");
+ result = gst_pad_activate_pull (sinkpad, TRUE);
+ } else {
+ GST_DEBUG_OBJECT (parse, "trying to activate in push mode");
+ result = gst_pad_activate_push (sinkpad, TRUE);
+ }
+
+ GST_DEBUG_OBJECT (parse, "sink activate return %d", result);
+ gst_object_unref (parse);
+ return result;
+}
+
+
+/**
+ * gst_base_parse_activate:
+ * @parse: #GstBaseParse.
+ * @active: TRUE if element will be activated, FALSE if disactivated.
+ *
+ * Returns: TRUE if the operation succeeded.
+ */
+static gboolean
+gst_base_parse_activate (GstBaseParse * parse, gboolean active)
+{
+ GstBaseParseClass *klass;
+ gboolean result = FALSE;
+
+ GST_DEBUG_OBJECT (parse, "activate");
+
+ klass = GST_BASE_PARSE_GET_CLASS (parse);
+
+ if (active) {
+ if (parse->priv->pad_mode == GST_ACTIVATE_NONE && klass->start)
+ result = klass->start (parse);
+
+ GST_OBJECT_LOCK (parse);
+ gst_segment_init (&parse->segment, GST_FORMAT_TIME);
+ parse->priv->duration = -1;
+ parse->priv->discont = FALSE;
+ parse->priv->flushing = FALSE;
+ parse->priv->offset = 0;
+
+ if (parse->pending_segment)
+ gst_event_unref (parse->pending_segment);
+
+ parse->pending_segment =
+ gst_event_new_new_segment (FALSE, parse->segment.rate,
+ parse->segment.format,
+ parse->segment.start, parse->segment.stop, parse->segment.last_stop);
+
+ GST_OBJECT_UNLOCK (parse);
+ } else {
+ /* We must make sure streaming has finished before resetting things
+ * and calling the ::stop vfunc */
+ GST_PAD_STREAM_LOCK (parse->sinkpad);
+ GST_PAD_STREAM_UNLOCK (parse->sinkpad);
+
+ if (parse->priv->pad_mode != GST_ACTIVATE_NONE && klass->stop)
+ result = klass->stop (parse);
+
+ g_list_foreach (parse->priv->pending_events, (GFunc) gst_mini_object_unref,
+ NULL);
+ g_list_free (parse->priv->pending_events);
+ parse->priv->pending_events = NULL;
+
+ if (parse->priv->cache) {
+ gst_buffer_unref (parse->priv->cache);
+ parse->priv->cache = NULL;
+ }
+
+ parse->priv->pad_mode = GST_ACTIVATE_NONE;
+ }
+ GST_DEBUG_OBJECT (parse, "activate: %d", result);
+ return result;
+}
+
+
+/**
+ * gst_base_parse_sink_activate_push:
+ * @pad: #GstPad to be (de)activated.
+ * @active: TRUE when activating, FALSE when deactivating.
+ *
+ * Returns: TRUE if (de)activation succeeded.
+ */
+static gboolean
+gst_base_parse_sink_activate_push (GstPad * pad, gboolean active)
+{
+ gboolean result = TRUE;
+ GstBaseParse *parse;
+
+ parse = GST_BASE_PARSE (gst_pad_get_parent (pad));
+
+ GST_DEBUG_OBJECT (parse, "sink activate push");
+
+ result = gst_base_parse_activate (parse, active);
+
+ if (result)
+ parse->priv->pad_mode = active ? GST_ACTIVATE_PUSH : GST_ACTIVATE_NONE;
+
+ GST_DEBUG_OBJECT (parse, "sink activate push: %d", result);
+
+ gst_object_unref (parse);
+ return result;
+}
+
+
+/**
+ * gst_base_parse_sink_activate_pull:
+ * @sinkpad: #GstPad to be (de)activated.
+ * @active: TRUE when activating, FALSE when deactivating.
+ *
+ * Returns: TRUE if (de)activation succeeded.
+ */
+static gboolean
+gst_base_parse_sink_activate_pull (GstPad * sinkpad, gboolean active)
+{
+ gboolean result = FALSE;
+ GstBaseParse *parse;
+
+ parse = GST_BASE_PARSE (gst_pad_get_parent (sinkpad));
+
+ GST_DEBUG_OBJECT (parse, "activate pull");
+
+ result = gst_base_parse_activate (parse, active);
+
+ if (result) {
+ if (active) {
+ result &= gst_pad_start_task (sinkpad,
+ (GstTaskFunction) gst_base_parse_loop, sinkpad);
+ } else {
+ result &= gst_pad_stop_task (sinkpad);
+ }
+ }
+
+ if (result)
+ parse->priv->pad_mode = active ? GST_ACTIVATE_PULL : GST_ACTIVATE_NONE;
+
+ GST_DEBUG_OBJECT (parse, "sink activate pull: %d", result);
+
+ gst_object_unref (parse);
+ return result;
+}
+
+
+/**
+ * gst_base_parse_set_duration:
+ * @parse: #GstBaseParse.
+ * @fmt: #GstFormat.
+ * @duration: duration value.
+ *
+ * Sets the duration of the currently playing media. Subclass can use this
+ * when it notices a change in the media duration.
+ */
+void
+gst_base_parse_set_duration (GstBaseParse * parse,
+ GstFormat fmt, gint64 duration)
+{
+ g_return_if_fail (parse != NULL);
+
+ GST_BASE_PARSE_LOCK (parse);
+ if (duration != parse->priv->duration) {
+ GstMessage *m;
+
+ m = gst_message_new_duration (GST_OBJECT (parse), fmt, duration);
+ gst_element_post_message (GST_ELEMENT (parse), m);
+
+ /* TODO: what about duration tag? */
+ }
+ parse->priv->duration = duration;
+ parse->priv->duration_fmt = fmt;
+ GST_DEBUG_OBJECT (parse, "set duration: %lld", duration);
+ GST_BASE_PARSE_UNLOCK (parse);
+}
+
+
+/**
+ * gst_base_parse_set_min_frame_size:
+ * @parse: #GstBaseParse.
+ * @min_size: Minimum size of the data that this base class should give to
+ * subclass.
+ *
+ * Subclass can use this function to tell the base class that it needs to
+ * give at least #min_size buffers.
+ */
+void
+gst_base_parse_set_min_frame_size (GstBaseParse * parse, guint min_size)
+{
+ g_return_if_fail (parse != NULL);
+
+ GST_BASE_PARSE_LOCK (parse);
+ parse->priv->min_frame_size = min_size;
+ GST_LOG_OBJECT (parse, "set frame_min_size: %d", min_size);
+ GST_BASE_PARSE_UNLOCK (parse);
+}
+
+
+/**
+ * gst_base_parse_get_querytypes:
+ * @pad: GstPad
+ *
+ * Returns: A table of #GstQueryType items describing supported query types.
+ */
+static const GstQueryType *
+gst_base_parse_get_querytypes (GstPad * pad)
+{
+ static const GstQueryType list[] = {
+ GST_QUERY_POSITION,
+ GST_QUERY_DURATION,
+ GST_QUERY_FORMATS,
+ GST_QUERY_SEEKING,
+ GST_QUERY_CONVERT,
+ 0
+ };
+
+ return list;
+}
+
+
+/**
+ * gst_base_parse_query:
+ * @pad: #GstPad.
+ * @query: #GstQuery.
+ *
+ * Returns: TRUE on success.
+ */
+static gboolean
+gst_base_parse_query (GstPad * pad, GstQuery * query)
+{
+ GstBaseParse *parse;
+ GstBaseParseClass *klass;
+ gboolean res = FALSE;
+
+ parse = GST_BASE_PARSE (GST_PAD_PARENT (pad));
+ klass = GST_BASE_PARSE_GET_CLASS (parse);
+
+ /* If subclass doesn't provide conversion function we can't reply
+ to the query either */
+ if (!klass->convert) {
+ return FALSE;
+ }
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_POSITION:
+ {
+ gint64 dest_value;
+ GstFormat format;
+
+ GST_DEBUG_OBJECT (parse, "position query");
+
+ gst_query_parse_position (query, &format, NULL);
+
+ g_mutex_lock (parse->parse_lock);
+
+ if (format == GST_FORMAT_BYTES) {
+ dest_value = parse->priv->offset;
+ res = TRUE;
+ } else if (format == parse->segment.format &&
+ GST_CLOCK_TIME_IS_VALID (parse->segment.last_stop)) {
+ dest_value = parse->segment.last_stop;
+ res = TRUE;
+ } else {
+ /* priv->offset is updated in both PUSH/PULL modes */
+ res = klass->convert (parse, GST_FORMAT_BYTES, parse->priv->offset,
+ format, &dest_value);
+ }
+ g_mutex_unlock (parse->parse_lock);
+
+ if (res)
+ gst_query_set_position (query, format, dest_value);
+ else
+ res = gst_pad_query_default (pad, query);
+
+ break;
+ }
+ case GST_QUERY_DURATION:
+ {
+ GstFormat format;
+ gint64 dest_value;
+
+ GST_DEBUG_OBJECT (parse, "duration query");
+
+ gst_query_parse_duration (query, &format, NULL);
+
+ g_mutex_lock (parse->parse_lock);
+
+ if (format == GST_FORMAT_BYTES) {
+ res = gst_pad_query_peer_duration (parse->sinkpad, &format,
+ &dest_value);
+ } else if (parse->priv->duration != -1 &&
+ format == parse->priv->duration_fmt) {
+ dest_value = parse->priv->duration;
+ res = TRUE;
+ } else if (parse->priv->duration != -1) {
+ res = klass->convert (parse, parse->priv->duration_fmt,
+ parse->priv->duration, format, &dest_value);
+ }
+
+ g_mutex_unlock (parse->parse_lock);
+
+ if (res)
+ gst_query_set_duration (query, format, dest_value);
+ else
+ res = gst_pad_query_default (pad, query);
+ break;
+ }
+ case GST_QUERY_SEEKING:
+ {
+ GstFormat fmt;
+ gboolean seekable = FALSE;
+
+ GST_DEBUG_OBJECT (parse, "seeking query");
+
+ gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL);
+
+ if (fmt != GST_FORMAT_TIME) {
+ return gst_pad_query_default (pad, query);
+ }
+
+ seekable = klass->is_seekable (parse);
+
+ /* TODO: could this duration be calculated/converted if subclass
+ hasn't given it? */
+ gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0,
+ (parse->priv->duration == -1) ?
+ GST_CLOCK_TIME_NONE : parse->priv->duration);
+
+ GST_DEBUG_OBJECT (parse, "seekable: %d", seekable);
+ res = TRUE;
+ break;
+ }
+ case GST_QUERY_FORMATS:
+ gst_query_set_formatsv (query, 3, fmtlist);
+ res = TRUE;
+ break;
+
+ case GST_QUERY_CONVERT:
+ {
+ GstFormat src_format, dest_format;
+ gint64 src_value, dest_value;
+
+ gst_query_parse_convert (query, &src_format, &src_value,
+ &dest_format, &dest_value);
+
+ /* FIXME: hm? doesn't make sense
+ * We require all those values to be given
+ if (src_format && src_value && dest_format && dest_value ) { */
+ res = klass->convert (parse, src_format, src_value,
+ dest_format, &dest_value);
+ if (res) {
+ gst_query_set_convert (query, src_format, src_value,
+ dest_format, dest_value);
+ }
+ /*} */
+ break;
+ }
+ default:
+ res = gst_pad_query_default (pad, query);
+ break;
+ }
+ return res;
+}
+
+
+/**
+ * gst_base_parse_handle_seek:
+ * @parse: #GstBaseParse.
+ * @event: #GstEvent.
+ *
+ * Returns: TRUE if seek succeeded.
+ */
+static gboolean
+gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event)
+{
+ GstBaseParseClass *klass;
+ gdouble rate;
+ GstFormat format;
+ GstSeekFlags flags;
+ GstSeekType cur_type = GST_SEEK_TYPE_NONE, stop_type;
+ gboolean flush, update, res = TRUE;
+ gint64 cur, stop, seekpos;
+ GstSegment seeksegment = { 0, };
+ GstFormat dstformat;
+
+ klass = GST_BASE_PARSE_GET_CLASS (parse);
+
+ gst_event_parse_seek (event, &rate, &format, &flags,
+ &cur_type, &cur, &stop_type, &stop);
+
+ /* no negative rates yet */
+ if (rate < 0.0)
+ goto negative_rate;
+
+ if (cur_type != GST_SEEK_TYPE_SET)
+ goto wrong_type;
+
+ /* For any format other than TIME, see if upstream handles
+ * it directly or fail. For TIME, try upstream, but do it ourselves if
+ * it fails upstream */
+ if (format != GST_FORMAT_TIME) {
+ return gst_pad_push_event (parse->sinkpad, event);
+ } else {
+ gst_event_ref (event);
+ if (gst_pad_push_event (parse->sinkpad, event)) {
+ gst_event_unref (event);
+ return TRUE;
+ }
+ }
+
+ /* get flush flag */
+ flush = flags & GST_SEEK_FLAG_FLUSH;
+
+ dstformat = GST_FORMAT_BYTES;
+ if (!gst_pad_query_convert (parse->srcpad, format, cur, &dstformat, &seekpos)) {
+ GST_DEBUG_OBJECT (parse, "conversion failed");
+ return FALSE;
+ }
+
+ GST_DEBUG_OBJECT (parse, "seek position %lld in bytes: %lld", cur, seekpos);
+
+ if (parse->priv->pad_mode == GST_ACTIVATE_PULL) {
+ gint64 last_stop;
+
+ GST_DEBUG_OBJECT (parse, "seek in PULL mode");
+
+ if (flush) {
+ if (parse->srcpad) {
+ GST_DEBUG_OBJECT (parse, "sending flush start");
+ gst_pad_push_event (parse->srcpad, gst_event_new_flush_start ());
+ }
+ } else {
+ gst_pad_pause_task (parse->sinkpad);
+ }
+
+ /* we should now be able to grab the streaming thread because we stopped it
+ * with the above flush/pause code */
+ GST_PAD_STREAM_LOCK (parse->sinkpad);
+
+ /* save current position */
+ last_stop = parse->segment.last_stop;
+ GST_DEBUG_OBJECT (parse, "stopped streaming at %" G_GINT64_FORMAT,
+ last_stop);
+
+ /* copy segment, we need this because we still need the old
+ * segment when we close the current segment. */
+ memcpy (&seeksegment, &parse->segment, sizeof (GstSegment));
+
+ GST_DEBUG_OBJECT (parse, "configuring seek");
+ gst_segment_set_seek (&seeksegment, rate, format, flags,
+ cur_type, cur, stop_type, stop, &update);
+
+ /* figure out the last position we need to play. If it's configured (stop !=
+ * -1), use that, else we play until the total duration of the file */
+ if ((stop = seeksegment.stop) == -1)
+ stop = seeksegment.duration;
+
+ parse->priv->offset = seekpos;
+
+ /* prepare for streaming again */
+ if (flush) {
+ GST_DEBUG_OBJECT (parse, "sending flush stop");
+ gst_pad_push_event (parse->srcpad, gst_event_new_flush_stop ());
+ } else {
+ if (parse->close_segment)
+ gst_event_unref (parse->close_segment);
+
+ parse->close_segment = gst_event_new_new_segment (TRUE,
+ parse->segment.rate, parse->segment.format,
+ parse->segment.accum, parse->segment.last_stop, parse->segment.accum);
+
+ /* keep track of our last_stop */
+ seeksegment.accum = parse->segment.last_stop;
+
+ GST_DEBUG_OBJECT (parse, "Created close seg format %d, "
+ "start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT
+ ", pos = %" GST_TIME_FORMAT, format,
+ GST_TIME_ARGS (parse->segment.accum),
+ GST_TIME_ARGS (parse->segment.last_stop),
+ GST_TIME_ARGS (parse->segment.accum));
+ }
+
+ memcpy (&parse->segment, &seeksegment, sizeof (GstSegment));
+
+ /* store the newsegment event so it can be sent from the streaming thread. */
+ if (parse->pending_segment)
+ gst_event_unref (parse->pending_segment);
+
+ /* This will be sent later in _loop() */
+ parse->pending_segment =
+ gst_event_new_new_segment (FALSE, parse->segment.rate,
+ parse->segment.format,
+ parse->segment.last_stop, stop, parse->segment.last_stop);
+
+ GST_DEBUG_OBJECT (parse, "Created newseg format %d, "
+ "start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT
+ ", pos = %" GST_TIME_FORMAT, format,
+ GST_TIME_ARGS (parse->segment.last_stop),
+ GST_TIME_ARGS (stop), GST_TIME_ARGS (parse->segment.last_stop));
+
+ /* mark discont if we are going to stream from another position. */
+ if (last_stop != parse->segment.last_stop) {
+ GST_DEBUG_OBJECT (parse,
+ "mark DISCONT, we did a seek to another position");
+ parse->priv->discont = TRUE;
+ }
+
+ /* Start streaming thread if paused */
+ gst_pad_start_task (parse->sinkpad,
+ (GstTaskFunction) gst_base_parse_loop, parse->sinkpad);
+
+ GST_PAD_STREAM_UNLOCK (parse->sinkpad);
+ } else {
+ GstEvent *new_event;
+ /* The only thing we need to do in PUSH-mode is to send the
+ seek event (in bytes) to upstream. Segment / flush handling happens
+ in corresponding src event handlers */
+ GST_DEBUG_OBJECT (parse, "seek in PUSH mode");
+ new_event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flush,
+ GST_SEEK_TYPE_SET, seekpos, stop_type, stop);
+
+ res = gst_pad_push_event (parse->sinkpad, new_event);
+ }
+
+done:
+ return res;
+
+ /* ERRORS */
+negative_rate:
+ {
+ GST_DEBUG_OBJECT (parse, "negative playback rates are not supported yet.");
+ res = FALSE;
+ goto done;
+ }
+wrong_type:
+ {
+ GST_DEBUG_OBJECT (parse, "unsupported seek type.");
+ res = FALSE;
+ goto done;
+ }
+}
+
+
+/**
+ * gst_base_parse_sink_setcaps:
+ * @pad: #GstPad.
+ * @caps: #GstCaps.
+ *
+ * Returns: TRUE if caps were accepted.
+ */
+static gboolean
+gst_base_parse_sink_setcaps (GstPad * pad, GstCaps * caps)
+{
+ GstBaseParse *parse;
+ GstBaseParseClass *klass;
+ gboolean res = TRUE;
+
+ parse = GST_BASE_PARSE (GST_PAD_PARENT (pad));
+ klass = GST_BASE_PARSE_GET_CLASS (parse);
+
+ GST_DEBUG_OBJECT (parse, "caps: %" GST_PTR_FORMAT, caps);
+
+ if (klass->set_sink_caps)
+ res = klass->set_sink_caps (parse, caps);
+
+ parse->negotiated = res;
+ return res && gst_pad_set_caps (pad, caps);
+}
diff --git a/ext/musepack/gstbaseparse.h b/ext/musepack/gstbaseparse.h
new file mode 100644
index 000000000..af8c38c6a
--- /dev/null
+++ b/ext/musepack/gstbaseparse.h
@@ -0,0 +1,238 @@
+/* GStreamer
+ * Copyright (C) 2008 Nokia Corporation. All rights reserved.
+ *
+ * Contact: Stefan Kost <stefan.kost@nokia.com>
+ *
+ * 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.
+ */
+
+#ifndef __GST_BASE_PARSE_H__
+#define __GST_BASE_PARSE_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstadapter.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_BASE_PARSE (gst_base_parse_get_type())
+#define GST_BASE_PARSE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BASE_PARSE,GstBaseParse))
+#define GST_BASE_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BASE_PARSE,GstBaseParseClass))
+#define GST_BASE_PARSE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_BASE_PARSE,GstBaseParseClass))
+#define GST_IS_BASE_PARSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BASE_PARSE))
+#define GST_IS_BASE_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASE_PARSE))
+#define GST_BASE_PARSE_CAST(obj) ((GstBaseParse *)(obj))
+
+/**
+ * GST_BASE_PARSE_SINK_NAME:
+ *
+ * the name of the templates for the sink pad
+ */
+#define GST_BASE_PARSE_SINK_NAME "sink"
+/**
+ * GST_BASE_PARSE_SRC_NAME:
+ *
+ * the name of the templates for the source pad
+ */
+#define GST_BASE_PARSE_SRC_NAME "src"
+
+/**
+ * GST_BASE_PARSE_SRC_PAD:
+ * @obj: base parse instance
+ *
+ * Gives the pointer to the source #GstPad object of the element.
+ *
+ * Since: 0.10.x
+ */
+#define GST_BASE_PARSE_SRC_PAD(obj) (GST_BASE_PARSE_CAST (obj)->srcpad)
+
+/**
+ * GST_BASE_PARSE_SINK_PAD:
+ * @obj: base parse instance
+ *
+ * Gives the pointer to the sink #GstPad object of the element.
+ *
+ * Since: 0.10.x
+ */
+#define GST_BASE_PARSE_SINK_PAD(obj) (GST_BASE_PARSE_CAST (obj)->sinkpad)
+
+/**
+ * GST_BASE_PARSE_FLOW_DROPPED:
+ *
+ * A #GstFlowReturn that can be returned from parse_frame to
+ * indicate that no output buffer was generated.
+ *
+ * Since: 0.10.x
+ */
+#define GST_BASE_PARSE_FLOW_DROPPED GST_FLOW_CUSTOM_SUCCESS
+
+/**
+ * GST_BASE_PARSE_LOCK:
+ * @obj: base parse instance
+ *
+ * Obtain a lock to protect the parse function from concurrent access.
+ *
+ * Since: 0.10.x
+ */
+#define GST_BASE_PARSE_LOCK(obj) g_mutex_lock (GST_BASE_PARSE_CAST (obj)->parse_lock)
+
+/**
+ * GST_BASE_PARSE_UNLOCK:
+ * @obj: base parse instance
+ *
+ * Release the lock that protects the parse function from concurrent access.
+ *
+ * Since: 0.10.x
+ */
+#define GST_BASE_PARSE_UNLOCK(obj) g_mutex_unlock (GST_BASE_PARSE_CAST (obj)->parse_lock)
+
+typedef struct _GstBaseParse GstBaseParse;
+typedef struct _GstBaseParseClass GstBaseParseClass;
+typedef struct _GstBaseParsePrivate GstBaseParsePrivate;
+typedef struct _GstBaseParseClassPrivate GstBaseParseClassPrivate;
+
+/**
+ * GstBaseParse:
+ * @element: the parent element.
+ *
+ * The opaque #GstBaseParse data structure.
+ */
+struct _GstBaseParse {
+ GstElement element;
+ GstAdapter *adapter;
+
+ /*< protected >*/
+ /* source and sink pads */
+ GstPad *sinkpad;
+ GstPad *srcpad;
+
+ /* MT-protected (with STREAM_LOCK) */
+ GstSegment segment;
+
+ /* Newsegment event to be sent after SEEK */
+ GstEvent *pending_segment;
+
+ /* Segment event that closes the running segment prior to SEEK */
+ GstEvent *close_segment;
+
+ /* Caps nego done already? */
+ gboolean negotiated;
+
+ GMutex *parse_lock;
+
+ /*< private >*/
+ gpointer _gst_reserved[GST_PADDING_LARGE];
+ GstBaseParsePrivate *priv;
+};
+
+/**
+ * GstBaseParseClass:
+ * @start: Optional.
+ * Called when the element starts processing.
+ * Allows opening external resources.
+ * @stop: Optional.
+ * Called when the element stops processing.
+ * Allows closing external resources.
+ * @set_sink_caps: allows the subclass to be notified of the actual caps set.
+ * @check_valid_frame: Check if the given piece of data contains a valid
+ * frame.
+ * @parse_frame: Parse the already checked frame. Subclass need to
+ * set the buffer timestamp, duration, caps and possibly
+ * other necessary metadata. This is called with srcpad's
+ * STREAM_LOCK held.
+ * @convert: Optional.
+ * Convert between formats.
+ * @find_frame: Optional.
+ * Finds a frame. Gets a position passed and should return
+ * TRUE and the offset in bytes where this position is.
+ * Will only be called in pull mode and the subclass can pull
+ * whatever it wants from upstream. If not implemented,
+ * the base class will implement it by calling
+ * @check_valid_frame and @parse_frame to find the wanted
+ * frame and build a seek table.
+ * @event: Optional.
+ * Event handler on the sink pad. This function should return
+ * TRUE if the event was handled and can be dropped.
+ * @src_event: Optional.
+ * Event handler on the source pad. Should return TRUE
+ * if the event was handled and can be dropped.
+ * @is_seekable: Optional.
+ * Subclass can override this if it wants to control the
+ * seekability of the stream. Otherwise the element assumes
+ * that stream is always seekable.
+ *
+ * Subclasses can override any of the available virtual methods or not, as
+ * needed. At minimum @check_valid_frame and @parse_frame needs to be
+ * overridden.
+ */
+struct _GstBaseParseClass {
+ GstElementClass parent_class;
+
+ /*< public >*/
+ /* virtual methods for subclasses */
+
+ gboolean (*start) (GstBaseParse *parse);
+
+ gboolean (*stop) (GstBaseParse *parse);
+
+ gboolean (*set_sink_caps) (GstBaseParse *parse,
+ GstCaps *caps);
+
+ gboolean (*check_valid_frame) (GstBaseParse *parse,
+ GstBuffer *buffer,
+ guint *framesize,
+ gint *skipsize);
+
+ GstFlowReturn (*parse_frame) (GstBaseParse *parse,
+ GstBuffer *buffer);
+
+ gboolean (*convert) (GstBaseParse * parse,
+ GstFormat src_format,
+ gint64 src_value,
+ GstFormat dest_format,
+ gint64 * dest_value);
+
+ gboolean (*find_frame) (GstBaseParse *parse,
+ GstFormat src_format,
+ gint64 src_value,
+ gint64 * dest_value);
+
+ gboolean (*event) (GstBaseParse *parse,
+ GstEvent *event);
+
+ gboolean (*src_event) (GstBaseParse *parse,
+ GstEvent *event);
+
+ gboolean (*is_seekable) (GstBaseParse *parse);
+
+ /*< private >*/
+ gpointer _gst_reserved[GST_PADDING_LARGE];
+ GstBaseParseClassPrivate *priv;
+};
+
+GType gst_base_parse_get_type (void);
+
+GstFlowReturn gst_base_parse_push_buffer (GstBaseParse *parse, GstBuffer *buffer);
+
+void gst_base_parse_set_duration (GstBaseParse *parse,
+ GstFormat fmt,
+ gint64 duration);
+
+void gst_base_parse_set_min_frame_size (GstBaseParse *parse,
+ guint min_size);
+
+G_END_DECLS
+
+#endif /* __GST_BASE_PARSE_H__ */
diff --git a/ext/musepack/gstmusepackdec.c b/ext/musepack/gstmusepackdec.c
index 899a4c25a..8ebf13ee0 100644
--- a/ext/musepack/gstmusepackdec.c
+++ b/ext/musepack/gstmusepackdec.c
@@ -29,6 +29,7 @@
#include "gstmusepackdec.h"
#include "gstmusepackreader.h"
+#include "gstmusepackparse.h"
GST_DEBUG_CATEGORY (musepackdec_debug);
#define GST_CAT_DEFAULT musepackdec_debug
@@ -659,8 +660,10 @@ plugin_init (GstPlugin * plugin)
{
GST_DEBUG_CATEGORY_INIT (musepackdec_debug, "musepackdec", 0, "mpc decoder");
- return gst_element_register (plugin, "musepackdec",
- GST_RANK_PRIMARY, GST_TYPE_MUSEPACK_DEC);
+ return (gst_element_register (plugin, "musepackdec",
+ GST_RANK_PRIMARY, GST_TYPE_MUSEPACK_DEC) &&
+ gst_element_register (plugin, "musepackparse",
+ GST_RANK_NONE, GST_TYPE_MUSEPACK_PARSE));
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
diff --git a/ext/musepack/gstmusepackparse.c b/ext/musepack/gstmusepackparse.c
new file mode 100644
index 000000000..92c481ad7
--- /dev/null
+++ b/ext/musepack/gstmusepackparse.c
@@ -0,0 +1,661 @@
+/* GStreamer
+ *
+ * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ *
+ * 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.
+ */
+
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstmusepackparse.h"
+
+/* CRC-32 code from the PNG spec: http://www.w3.org/TR/PNG/#D-CRCAppendix */
+
+/* Table of CRCs of all 8-bit messages. */
+static guint32 crc_table[256];
+
+/* Make the table for a fast CRC. */
+static void
+make_crc_table (void)
+{
+ guint32 c;
+ gint n, k;
+
+ for (n = 0; n < 256; n++) {
+ c = (guint32) n;
+ for (k = 0; k < 8; k++) {
+ if (c & 1)
+ c = 0xedb88320L ^ (c >> 1);
+ else
+ c = c >> 1;
+ }
+ crc_table[n] = c;
+ }
+}
+
+/* Update a running CRC with the bytes buf[0..len-1]--the CRC
+ should be initialized to all 1's, and the transmitted value
+ is the 1's complement of the final running CRC (see the
+ crc() routine below). */
+
+static guint32
+update_crc (guint32 crc, const guint8 * buf, gint len)
+{
+ guint32 c = crc;
+ gint n;
+
+ for (n = 0; n < len; n++) {
+ c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
+ }
+ return c;
+}
+
+/* Return the CRC of the bytes buf[0..len-1]. */
+static guint32
+crc (const guint8 * buf, gint len)
+{
+ return update_crc (0xffffffffL, buf, len) ^ 0xffffffffL;
+}
+
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-musepack, "
+ "streamversion = (int) { 7, 8 }, "
+ "rate = (int) { 32000, 37800, 44100, 48000 }, "
+ "channels = (int) [ 1, 2 ], " "framed = (boolean) TRUE"));
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-musepack"));
+
+GST_DEBUG_CATEGORY_STATIC (gst_musepack_parse_debug);
+#define GST_CAT_DEFAULT gst_musepack_parse_debug
+
+
+static void gst_musepack_parse_finalize (GObject * object);
+
+static gboolean gst_musepack_parse_check_valid_frame (GstBaseParse * parse,
+ GstBuffer * buffer, guint * size, gint * skipsize);
+static GstFlowReturn gst_musepack_parse_parse_frame (GstBaseParse * parse,
+ GstBuffer * buffer);
+static gboolean gst_musepack_parse_start (GstBaseParse * parse);
+static gboolean gst_musepack_parse_stop (GstBaseParse * parse);
+
+#define _do_init(bla) \
+ GST_DEBUG_CATEGORY_INIT (gst_musepack_parse_debug, "musepackparse", 0, \
+ "MusePack parser");
+
+GST_BOILERPLATE_FULL (GstMusepackParse, gst_musepack_parse, GstBaseParse,
+ GST_TYPE_BASE_PARSE, _do_init);
+
+static void
+gst_musepack_parse_base_init (gpointer klass)
+{
+ GstElementClass *element_class = GST_ELEMENT_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));
+
+ gst_element_class_set_details_simple (element_class,
+ "MusePack parser",
+ "Codec/Parser/Audio",
+ "MusePack parser", "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
+}
+
+static void
+gst_musepack_parse_class_init (GstMusepackParseClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GstBaseParseClass *parse_class = GST_BASE_PARSE_CLASS (klass);
+
+ object_class->finalize = gst_musepack_parse_finalize;
+
+ parse_class->start = GST_DEBUG_FUNCPTR (gst_musepack_parse_start);
+ parse_class->stop = GST_DEBUG_FUNCPTR (gst_musepack_parse_stop);
+ parse_class->parse_frame = GST_DEBUG_FUNCPTR (gst_musepack_parse_parse_frame);
+ parse_class->check_valid_frame =
+ GST_DEBUG_FUNCPTR (gst_musepack_parse_check_valid_frame);
+
+ make_crc_table ();
+}
+
+static void
+gst_musepack_parse_init (GstMusepackParse * self, GstMusepackParseClass * klass)
+{
+ gst_base_parse_set_min_frame_size (GST_BASE_PARSE_CAST (self), 4);
+}
+
+static void
+gst_musepack_parse_finalize (GObject * object)
+{
+ GstMusepackParse *self = GST_MUSEPACK_PARSE (object);
+
+ g_list_foreach (self->pending_buffers, (GFunc) gst_mini_object_unref, NULL);
+ g_list_free (self->pending_buffers);
+ self->pending_buffers = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_musepack_parse_start (GstBaseParse * parse)
+{
+ GstMusepackParse *self = GST_MUSEPACK_PARSE (parse);
+
+ self->state = GST_MUSEPACK_PARSE_STATE_HEADER;
+
+ self->sv_major = self->sv_minor = 0;
+ self->sample_count = 0;
+ self->sample_frequency = self->channel_count = 0;
+ self->audio_block_frames = 0;
+
+ gst_base_parse_set_min_frame_size (GST_BASE_PARSE_CAST (self), 4);
+
+ return TRUE;
+}
+
+static gboolean
+gst_musepack_parse_stop (GstBaseParse * parse)
+{
+ GstMusepackParse *self = GST_MUSEPACK_PARSE (parse);
+
+ g_list_foreach (self->pending_buffers, (GFunc) gst_mini_object_unref, NULL);
+ g_list_free (self->pending_buffers);
+ self->pending_buffers = NULL;
+
+ return TRUE;
+}
+
+static gboolean
+gst_musepack_parse_check_valid_frame (GstBaseParse * parse,
+ GstBuffer * buffer, guint * framesize, gint * skipsize)
+{
+ GstMusepackParse *self = GST_MUSEPACK_PARSE (parse);
+ const guint8 *data;
+ guint size;
+ gboolean ret = FALSE;
+
+ data = GST_BUFFER_DATA (buffer);
+ size = GST_BUFFER_SIZE (buffer);
+
+ if (self->state == GST_MUSEPACK_PARSE_STATE_HEADER) {
+ if (memcmp (data, "MP+", 3) == 0) {
+ self->sv_major = data[3] & 0x0f;
+ self->sv_minor = data[3] >> 4;
+
+ GST_DEBUG_OBJECT (self, "Detected MusePack StreamVersion %u.%u",
+ self->sv_major, self->sv_minor);
+ g_assert_not_reached ();
+
+ /* Require the complete SV7 streaminfo header */
+ gst_base_parse_set_min_frame_size (GST_BASE_PARSE_CAST (self), 7 * 4);
+ ret = FALSE;
+ *skipsize = 0;
+ } else if (memcmp (data, "MPCK", 4) == 0) {
+ self->sv_major = 8;
+ *framesize = 4;
+ ret = TRUE;
+ GST_DEBUG_OBJECT (self, "Detected MusePack StreamVersion 8");
+ } else {
+ ret = FALSE;
+ }
+ } else if (self->sv_major == 8) {
+ guint64 len = 0;
+ gint i;
+
+ if (data[0] < 65 || data[0] > 90 || data[1] < 65 || data[1] > 90) {
+ GST_DEBUG_OBJECT (self, "Invalid packet key 0x%02x%02x", data[0],
+ data[1]);
+ ret = FALSE;
+ goto out;
+ }
+
+ data += 2;
+ size -= 1;
+
+ for (i = 0; i < 9; i++) {
+ if (size == 0) {
+ *skipsize = 0;
+ ret = FALSE;
+ gst_base_parse_set_min_frame_size (GST_BASE_PARSE_CAST (self), 3 + i);
+ goto out;
+ }
+
+ len <<= 7;
+ len |= data[i] & 0x7f;
+
+ if ((data[i] & 0x80) == 0)
+ break;
+ }
+
+ if (i == 9) {
+ gst_base_parse_set_min_frame_size (GST_BASE_PARSE_CAST (self), 3);
+ ret = FALSE;
+ goto out;
+ }
+
+ *framesize = len;
+ ret = TRUE;
+ GST_DEBUG_OBJECT (self, "Detected packet of size %u with key '%c%c'",
+ *framesize, GST_BUFFER_DATA (buffer)[0], GST_BUFFER_DATA (buffer)[1]);
+ } else {
+ g_assert_not_reached ();
+ }
+
+out:
+ return ret;
+}
+
+static guint64
+read_mpc_vint (const guint8 * data, guint size, guint * skipsize)
+{
+ guint64 len = 0;
+ gint i;
+
+ for (i = 0; i < 9; i++) {
+ if (size == 0) {
+ *skipsize = -1;
+ return -1;
+ }
+
+ len <<= 7;
+ len |= data[i] & 0x7f;
+
+ if ((data[i] & 0x80) == 0)
+ break;
+ }
+
+ if (i == 9) {
+ *skipsize = -1;
+ return -1;
+ }
+
+ *skipsize = i + 1;
+
+ return len;
+}
+
+static GstFlowReturn
+gst_musepack_parse_parse_sv8_stream_header (GstMusepackParse * self,
+ const guint8 * data, guint size)
+{
+ static const guint sr_table[] = { 44100, 48000, 37800, 32000 };
+ guint32 expected_crc;
+ guint8 sv;
+ guint64 sample_count, beginning_silence;
+ guint skipsize;
+ guint sample_frequency;
+ guint max_used_bands;
+ guint channel_count;
+ gboolean ms_used;
+ guint audio_block_frames;
+ GstCaps *caps;
+
+ if (size < 7)
+ goto too_small;
+
+ expected_crc = GST_READ_UINT32_BE (data);
+ if (expected_crc) {
+ guint32 real_crc = crc (data + 4, size - 4);
+
+ if (expected_crc != real_crc) {
+ GST_ERROR_OBJECT (self,
+ "Invalid stream header crc (was: 0x%08x, expected 0x%08x)", real_crc,
+ expected_crc);
+ return GST_FLOW_ERROR;
+ }
+ }
+
+ data += 4;
+ size -= 4;
+
+ if (size < 1)
+ goto too_small;
+ sv = GST_READ_UINT8 (data);
+ if (sv != 8) {
+ GST_ERROR_OBJECT (self, "Unsupported stream version %u\n", sv);
+ return GST_FLOW_ERROR;
+ }
+ data += 1;
+ size -= 1;
+
+ sample_count = read_mpc_vint (data, size, &skipsize);
+ if (skipsize == -1)
+ goto too_small;
+
+ data += skipsize;
+ size -= skipsize;
+
+ beginning_silence = read_mpc_vint (data, size, &skipsize);
+ if (skipsize == -1)
+ goto too_small;
+
+ data += skipsize;
+ size -= skipsize;
+
+ if (size < 2)
+ goto too_small;
+
+ sample_frequency = (data[0] & 0xe0) >> 5;
+ if (sample_frequency > 3) {
+ GST_ERROR_OBJECT (self, "Invalid sample frequency");
+ return GST_FLOW_ERROR;
+ }
+ sample_frequency = sr_table[sample_frequency];
+
+ max_used_bands = (data[0] & 0x1f) + 1;
+
+ data += 1;
+ size -= 1;
+
+ channel_count = ((data[0] & 0xf0) >> 4) + 1;
+ if (channel_count > 2) {
+ GST_ERROR_OBJECT (self, "Invalid number of channels %u\n", channel_count);
+ return GST_FLOW_ERROR;
+ }
+
+ ms_used = (data[0] & 0x08) != 0;
+ audio_block_frames = (data[0] & 0x07) * 2;
+
+ GST_DEBUG_OBJECT (self, "Stream header content:");
+ GST_DEBUG_OBJECT (self, "\t CRC = 0x%04x", expected_crc);
+ GST_DEBUG_OBJECT (self, "\t Stream Version = %u", sv);
+ GST_DEBUG_OBJECT (self, "\t Sample Count = %" G_GUINT64_FORMAT, sample_count);
+ GST_DEBUG_OBJECT (self, "\t Beginning Silence = %" G_GUINT64_FORMAT,
+ beginning_silence);
+ GST_DEBUG_OBJECT (self, "\t Sample Frequency = %u", sample_frequency);
+ GST_DEBUG_OBJECT (self, "\t Max Used Bands = %u", max_used_bands);
+ GST_DEBUG_OBJECT (self, "\t Channel Count = %u", channel_count);
+ GST_DEBUG_OBJECT (self, "\t MS Used = %s", ms_used ? "yes" : "no");
+ GST_DEBUG_OBJECT (self, "\t Audio Block Frames = %u",
+ 1 << audio_block_frames);
+
+ self->sample_count = sample_count;
+ self->sample_frequency = sample_frequency;
+ self->channel_count = channel_count;
+ self->audio_block_frames = audio_block_frames;
+
+ caps = gst_caps_new_simple ("audio/x-musepack",
+ "streamversion", G_TYPE_INT, self->sv_major,
+ "rate", G_TYPE_INT, self->sample_frequency,
+ "channels", G_TYPE_INT, self->channel_count,
+ "framed", G_TYPE_BOOLEAN, TRUE, NULL);
+
+ if (sample_count != 0) {
+ guint64 duration =
+ gst_util_uint64_scale (sample_count, GST_SECOND, sample_frequency);
+
+ gst_base_parse_set_duration (GST_BASE_PARSE_CAST (self), GST_FORMAT_TIME,
+ duration);
+ }
+
+ GST_DEBUG_OBJECT (self, "Setting caps %" GST_PTR_FORMAT, caps);
+
+ gst_pad_set_caps (GST_BASE_PARSE_CAST (self)->srcpad, caps);
+ gst_caps_unref (caps);
+
+ return GST_FLOW_OK;
+
+too_small:
+ GST_ERROR_OBJECT (self, "Too small SV8 stream header");
+ return GST_FLOW_ERROR;
+}
+
+static GstFlowReturn
+gst_musepack_parse_parse_sv8_replay_gain (GstMusepackParse * self,
+ const guint8 * data, guint size)
+{
+ gdouble title_gain, title_peak;
+ gdouble album_gain, album_peak;
+ GstTagList *tags;
+
+ if (size < 9) {
+ GST_ERROR_OBJECT (self, "Too small replaygain packet");
+ return GST_FLOW_ERROR;
+ }
+
+ if (data[0] != 1) {
+ GST_WARNING_OBJECT (self, "Unsupported replaygain version %u", data[0]);
+ return GST_FLOW_OK;
+ }
+
+ data++;
+
+ tags = gst_tag_list_new ();
+
+ title_gain = GST_READ_UINT16_BE (data);
+ if (title_gain != 0) {
+ title_gain /= 256.0;
+ gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_TRACK_GAIN,
+ title_gain, NULL);
+ }
+
+ title_peak = GST_READ_UINT16_BE (data);
+ if (title_peak != 0) {
+ title_peak /= 256.0;
+ gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_TRACK_PEAK,
+ title_peak, NULL);
+ }
+
+ album_gain = GST_READ_UINT16_BE (data);
+ if (album_gain != 0) {
+ album_gain /= 256.0;
+ gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_ALBUM_GAIN,
+ album_gain, NULL);
+ }
+
+ album_peak = GST_READ_UINT16_BE (data);
+ if (album_peak != 0) {
+ album_peak /= 256.0;
+ gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_ALBUM_PEAK,
+ album_peak, NULL);
+ }
+
+ if (!gst_tag_list_is_empty (tags)) {
+ GST_DEBUG_OBJECT (self, "Found replaygain tags: %" GST_PTR_FORMAT, tags);
+ gst_element_found_tags_for_pad (GST_ELEMENT_CAST (self),
+ GST_BASE_PARSE_CAST (self)->srcpad, tags);
+ } else {
+ GST_DEBUG_OBJECT (self, "Found no replaygain tags");
+ gst_tag_list_free (tags);
+ }
+
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_musepack_parse_parse_sv8_encoder_info (GstMusepackParse * self,
+ const guint8 * data, guint size)
+{
+ gboolean pns_tool;
+ gfloat profile;
+ guint8 major, minor, build;
+ GstTagList *tags;
+ gchar *encoder_string;
+
+ if (size < 4) {
+ GST_ERROR_OBJECT (self, "Too small encoder info packet");
+ return GST_FLOW_ERROR;
+ }
+
+ profile = (data[0] >> 1) / 8.0;
+ pns_tool = (data[0] & 0x01) != 0;
+
+ major = data[1];
+ minor = data[2];
+ build = data[3];
+
+ encoder_string =
+ g_strdup_printf ("MPC Encoder %u.%u.%u (%s, profile %.3f%s)", major,
+ minor, build, minor % 2 == 0 ? "stable" : "unstable", profile,
+ pns_tool ? ", PNS" : "");
+
+ GST_DEBUG_OBJECT (self, "Encoder info content:");
+ GST_DEBUG_OBJECT (self, "\tProfile = %f", profile);
+ GST_DEBUG_OBJECT (self, "\tPNS Tools = %s", pns_tool ? "yes" : "no");
+ GST_DEBUG_OBJECT (self, "\tEncoder Version = %s", encoder_string);
+
+ tags = gst_tag_list_new_full (GST_TAG_ENCODER, encoder_string, NULL);
+ g_free (encoder_string);
+
+ gst_element_found_tags_for_pad (GST_ELEMENT_CAST (self),
+ GST_BASE_PARSE_CAST (self)->srcpad, tags);
+
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_musepack_parse_parse_sv8_seektable_offset (GstMusepackParse * self,
+ const guint8 * data, guint size)
+{
+ guint skipsize;
+ guint64 st_offset = read_mpc_vint (data, size, &skipsize);
+
+ if (skipsize == -1) {
+ GST_ERROR_OBJECT (self, "Invalid seektable offset packet");
+ return GST_FLOW_ERROR;
+ }
+
+ GST_DEBUG_OBJECT (self, "Seek table offset: %" G_GUINT64_FORMAT, st_offset);
+
+ /* TODO: Now we probably want to seek to st_offset, read the seektable
+ * and come back to this position */
+
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_musepack_parse_parse_sv8_seektable (GstMusepackParse * self,
+ const guint8 * data, guint size)
+{
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_musepack_parse_parse_sv8_chapter_tag (GstMusepackParse * self,
+ const guint8 * data, guint size)
+{
+ return GST_FLOW_OK;
+}
+
+#define MPC_KEY(x) ((guint16)(((x)[0] << 8) | (x)[1]))
+
+static GstFlowReturn
+gst_musepack_parse_parse_frame (GstBaseParse * parse, GstBuffer * buffer)
+{
+ GstMusepackParse *self = GST_MUSEPACK_PARSE (parse);
+ GstFlowReturn ret = GST_FLOW_OK;
+ const guint8 *data = GST_BUFFER_DATA (buffer);
+ guint size = GST_BUFFER_SIZE (buffer);
+
+ if (self->sv_major == 8) {
+ if (self->state == GST_MUSEPACK_PARSE_STATE_HEADER) {
+ self->pending_buffers =
+ g_list_prepend (self->pending_buffers, gst_buffer_ref (buffer));
+ self->state = GST_MUSEPACK_PARSE_STATE_DATA;
+ ret = GST_BASE_PARSE_FLOW_DROPPED;
+
+ /* Require the SV8 packet key and first part of the length field */
+ gst_base_parse_set_min_frame_size (GST_BASE_PARSE_CAST (self), 3);
+ } else {
+ guint16 key = GST_READ_UINT16_BE (data);
+
+ data += 2;
+ size -= 2;
+
+ do {
+ /* Impossible that this happens */
+ g_assert (size != 0);
+ data++;
+ size--;
+ } while ((*(data - 1) & 0x80) != 0);
+
+ if (key == MPC_KEY ("SH")) {
+ ret = gst_musepack_parse_parse_sv8_stream_header (self, data, size);
+ } else if (key == MPC_KEY ("RG")) {
+ ret = gst_musepack_parse_parse_sv8_replay_gain (self, data, size);
+ } else if (key == MPC_KEY ("EI")) {
+ ret = gst_musepack_parse_parse_sv8_encoder_info (self, data, size);
+ } else if (key == MPC_KEY ("SO")) {
+ ret = gst_musepack_parse_parse_sv8_seektable_offset (self, data, size);
+ } else if (key == MPC_KEY ("AP")) {
+ guint64 duration = gst_util_uint64_scale (1 << self->audio_block_frames,
+ (36 * 32) * GST_SECOND, self->sample_frequency);
+
+ /* Audio Packet */
+ GST_BUFFER_DURATION (buffer) = duration;
+ } else if (key == MPC_KEY ("ST")) {
+ ret = gst_musepack_parse_parse_sv8_seektable (self, data, size);
+ } else if (key == MPC_KEY ("CT")) {
+ ret = gst_musepack_parse_parse_sv8_chapter_tag (self, data, size);
+ } else if (key == MPC_KEY ("SE")) {
+ /* Stream End */
+ } else {
+ GST_DEBUG_OBJECT (self, "Unknown MusePack packet key '%c%c'", data[0],
+ data[1]);
+ ret = GST_FLOW_OK;
+ }
+
+ if (ret != GST_FLOW_OK)
+ goto out;
+
+ if (!GST_PAD_CAPS (GST_BASE_PARSE_CAST (self)->srcpad)) {
+ self->pending_buffers =
+ g_list_prepend (self->pending_buffers, gst_buffer_ref (buffer));
+ self->state = GST_MUSEPACK_PARSE_STATE_DATA;
+ ret = GST_BASE_PARSE_FLOW_DROPPED;
+ } else {
+ GList *l = g_list_last (self->pending_buffers);
+
+ for (; l; l = l->prev) {
+ GstBuffer *pending = l->data;
+
+ GST_BUFFER_TIMESTAMP (pending) = GST_BUFFER_DURATION (pending) =
+ GST_BUFFER_OFFSET (pending) = GST_BUFFER_OFFSET_END (pending) =
+ GST_CLOCK_TIME_NONE;
+ gst_buffer_set_caps (pending,
+ GST_PAD_CAPS (GST_BASE_PARSE_CAST (self)->srcpad));
+ ret =
+ gst_base_parse_push_buffer (GST_BASE_PARSE_CAST (self), pending);
+
+ if (ret != GST_FLOW_OK)
+ goto out;
+ }
+
+ g_list_free (self->pending_buffers);
+ self->pending_buffers = NULL;
+ }
+
+ /* Require the SV8 packet key and first part of the length field */
+ gst_base_parse_set_min_frame_size (GST_BASE_PARSE_CAST (self), 3);
+ }
+ } else {
+ g_assert_not_reached ();
+ }
+
+out:
+
+ return ret;
+}
diff --git a/ext/musepack/gstmusepackparse.h b/ext/musepack/gstmusepackparse.h
new file mode 100644
index 000000000..2345a4a38
--- /dev/null
+++ b/ext/musepack/gstmusepackparse.h
@@ -0,0 +1,71 @@
+/* GStreamer
+ *
+ * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ *
+ * 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.
+ */
+
+#ifndef __GST_MUSEPACK_PARSE_H__
+#define __GST_MUSEPACK_PARSE_H__
+
+#include <gst/gst.h>
+#include "gstbaseparse.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_MUSEPACK_PARSE \
+ (gst_musepack_parse_get_type())
+#define GST_MUSEPACK_PARSE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_MUSEPACK_PARSE, GstMusepackParse))
+#define GST_MUSEPACK_PARSE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_MUSEPACK_PARSE, GstMusepackParseClass))
+#define GST_IS_MUSEPACK_PARSE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_MUSEPACK_PARSE))
+#define GST_IS_MUSEPACK_PARSE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_MUSEPACK_PARSE))
+
+typedef struct _GstMusepackParse GstMusepackParse;
+typedef struct _GstMusepackParseClass GstMusepackParseClass;
+
+typedef enum {
+ GST_MUSEPACK_PARSE_STATE_HEADER,
+ GST_MUSEPACK_PARSE_STATE_DATA
+} GstMusepackParseState;
+
+struct _GstMusepackParse {
+ GstBaseParse element;
+
+ GstMusepackParseState state;
+
+ guint sv_major, sv_minor;
+
+ guint64 sample_count;
+ gint sample_frequency;
+ gint channel_count;
+ guint audio_block_frames;
+
+ GList *pending_buffers;
+};
+
+struct _GstMusepackParseClass {
+ GstBaseParseClass parent_class;
+};
+
+GType gst_musepack_parse_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_MUSEPACK_PARSE_H__ */