diff options
author | Sebastian Dröge <slomo@circular-chaos.org> | 2008-09-29 08:26:54 +0000 |
---|---|---|
committer | Sebastian Dröge <slomo@circular-chaos.org> | 2008-09-29 08:26:54 +0000 |
commit | ba0a1f60ed7874077eee375ee358234b04c5a268 (patch) | |
tree | e735ce7071f704c90fe86de6a7c8befff9455cfe /gst/flacparse | |
parent | a8736df9b53a190f21d6c2e132a04f296797782a (diff) | |
download | gst-plugins-bad-ba0a1f60ed7874077eee375ee358234b04c5a268.tar.gz gst-plugins-bad-ba0a1f60ed7874077eee375ee358234b04c5a268.tar.bz2 gst-plugins-bad-ba0a1f60ed7874077eee375ee358234b04c5a268.zip |
Add FLAC parser, based on GstBaseParse. Also add the bit and byte reader that will be added to libgstbase later.
Original commit message from CVS:
* configure.ac:
* gst/flacparse/Makefile.am:
* gst/flacparse/gstbaseparse.c: (gst_base_parse_get_type),
(gst_base_parse_base_init), (gst_base_parse_base_finalize),
(gst_base_parse_finalize), (gst_base_parse_class_init),
(gst_base_parse_init), (gst_base_parse_check_frame),
(gst_base_parse_parse_frame), (gst_base_parse_bytepos_to_time),
(gst_base_parse_sink_event), (gst_base_parse_sink_eventfunc),
(gst_base_parse_src_event), (gst_base_parse_src_eventfunc),
(gst_base_parse_is_seekable), (gst_base_parse_push_buffer),
(gst_base_parse_handle_and_push_buffer), (gst_base_parse_drain),
(gst_base_parse_chain), (gst_base_parse_pull_range),
(gst_base_parse_loop), (gst_base_parse_sink_activate),
(gst_base_parse_activate), (gst_base_parse_sink_activate_push),
(gst_base_parse_sink_activate_pull), (gst_base_parse_set_duration),
(gst_base_parse_set_min_frame_size),
(gst_base_parse_get_querytypes), (gst_base_parse_query),
(gst_base_parse_handle_seek), (gst_base_parse_sink_setcaps):
* gst/flacparse/gstbaseparse.h:
* gst/flacparse/gstbitreader.c: (gst_bit_reader_new),
(gst_bit_reader_new_from_buffer), (gst_bit_reader_free),
(gst_bit_reader_init), (gst_bit_reader_init_from_buffer),
(gst_bit_reader_set_pos), (gst_bit_reader_get_pos),
(gst_bit_reader_get_remaining), (gst_bit_reader_skip),
(gst_bit_reader_skip_to_byte):
* gst/flacparse/gstbitreader.h:
* gst/flacparse/gstbytereader.c: (GDOUBLE_SWAP_LE_BE),
(GFLOAT_SWAP_LE_BE), (gst_byte_reader_new),
(gst_byte_reader_new_from_buffer), (gst_byte_reader_free),
(gst_byte_reader_init), (gst_byte_reader_init_from_buffer),
(gst_byte_reader_set_pos), (gst_byte_reader_get_pos),
(gst_byte_reader_get_remaining), (gst_byte_reader_skip),
(gst_byte_reader_get_uint8), (gst_byte_reader_get_int8),
(gst_byte_reader_peek_uint8), (gst_byte_reader_peek_int8),
(gst_byte_reader_get_uint24_le), (gst_byte_reader_get_uint24_be),
(gst_byte_reader_get_int24_le), (gst_byte_reader_get_int24_be),
(gst_byte_reader_peek_uint24_le), (gst_byte_reader_peek_uint24_be),
(gst_byte_reader_peek_int24_le), (gst_byte_reader_peek_int24_be):
* gst/flacparse/gstbytereader.h:
* gst/flacparse/gstflac.c: (plugin_init):
* gst/flacparse/gstflacparse.c: (gst_flac_parse_base_init),
(gst_flac_parse_class_init), (gst_flac_parse_init),
(gst_flac_parse_finalize), (gst_flac_parse_start),
(gst_flac_parse_stop), (gst_flac_parse_get_frame_size),
(gst_flac_parse_check_valid_frame),
(gst_flac_parse_handle_streaminfo),
(gst_flac_parse_handle_vorbiscomment),
(gst_flac_parse_handle_picture), (_value_array_append_buffer),
(gst_flac_parse_handle_headers), (gst_flac_parse_generate_headers),
(gst_flac_parse_parse_frame):
* gst/flacparse/gstflacparse.h:
Add FLAC parser, based on GstBaseParse. Also add the bit and byte reader
that will be added to libgstbase later.
The FLAC parser is currently not 100% bug free and fails to get the
correct frame size for some frames in some streams.
Diffstat (limited to 'gst/flacparse')
-rw-r--r-- | gst/flacparse/Makefile.am | 11 | ||||
-rw-r--r-- | gst/flacparse/gstbaseparse.c | 1763 | ||||
-rw-r--r-- | gst/flacparse/gstbaseparse.h | 237 | ||||
-rw-r--r-- | gst/flacparse/gstbitreader.c | 400 | ||||
-rw-r--r-- | gst/flacparse/gstbitreader.h | 99 | ||||
-rw-r--r-- | gst/flacparse/gstbytereader.c | 1231 | ||||
-rw-r--r-- | gst/flacparse/gstbytereader.h | 134 | ||||
-rw-r--r-- | gst/flacparse/gstflac.c | 52 | ||||
-rw-r--r-- | gst/flacparse/gstflacparse.c | 1233 | ||||
-rw-r--r-- | gst/flacparse/gstflacparse.h | 87 |
10 files changed, 5247 insertions, 0 deletions
diff --git a/gst/flacparse/Makefile.am b/gst/flacparse/Makefile.am new file mode 100644 index 00000000..21e2f745 --- /dev/null +++ b/gst/flacparse/Makefile.am @@ -0,0 +1,11 @@ +plugin_LTLIBRARIES = libgstflacparse.la + +libgstflacparse_la_SOURCES = gstflac.c gstbaseparse.c gstflacparse.c gstbitreader.c gstbytereader.c +libgstflacparse_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) +libgstflacparse_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) -lgsttag-$(GST_MAJORMINOR) \ + -lgstaudio-$(GST_MAJORMINOR) \ + $(GST_BASE_LIBS) $(GST_LIBS) +libgstflacparse_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +noinst_HEADERS = gstbitreader.h gstbytereader.h gstbaseparse.h gstflacparse.h diff --git a/gst/flacparse/gstbaseparse.c b/gst/flacparse/gstbaseparse.c new file mode 100644 index 00000000..dd990ab3 --- /dev/null +++ b/gst/flacparse/gstbaseparse.c @@ -0,0 +1,1763 @@ +/* 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 + * - Handle upstream timestamps + * - Bitrate tracking => inaccurate seeking, inaccurate duration calculation + * - Let subclass decide if frames outside the segment should be dropped + */ + +#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; + gint64 pending_offset; + + GList *pending_events; + + GstBuffer *cache; + + GstAdapter *adapter; + + guint64 bitrate_sum; + guint64 avg_bitrate; +}; + +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, + "GstFlacBaseParse", &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 gboolean 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, "flacbaseparse", 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->priv->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->priv->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 gboolean +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 TRUE; +} + + +/** + * 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; + 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; + parse->priv->pending_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; + break; + } + + case GST_EVENT_FLUSH_START: + parse->priv->flushing = TRUE; + /* Wait for _chain() to exit by taking the srcpad STREAM_LOCK */ + GST_PAD_STREAM_LOCK (parse->srcpad); + handled = gst_pad_push_event (parse->srcpad, event); + GST_PAD_STREAM_UNLOCK (parse->srcpad); + break; + + case GST_EVENT_FLUSH_STOP: + gst_adapter_clear (parse->priv->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_push_buffer: + * @parse: #GstBaseParse. + * @buffer: #GstBuffer. + * + * Pushes @buffer on the srcpad and updates the internal state. + * + * This must be called with srcpad STREAM_LOCK held. + * + * Returns: #GstFlowReturn + */ +GstFlowReturn +gst_base_parse_push_buffer (GstBaseParse * parse, GstBuffer * buffer) +{ + GstClockTime last_stop = GST_CLOCK_TIME_NONE; + GstFlowReturn ret = GST_FLOW_OK; + + g_return_val_if_fail (GST_PAD_CAPS (parse->srcpad), GST_FLOW_ERROR); + + if (parse->priv->pad_mode == GST_ACTIVATE_PULL) { + if (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 (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 (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 (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; + } + + 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); + + gst_buffer_set_caps (buffer, GST_PAD_CAPS (parse->srcpad)); + + /* 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_IS_VALID (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 { + guint size = GST_BUFFER_SIZE (buffer); + + ret = gst_pad_push (parse->srcpad, buffer); + + GST_LOG_OBJECT (parse, "frame (%d bytes) pushed: %s", size, + gst_flow_get_name (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_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; + } + + /* FIXME: Set upstream timestamp if possible */ + + ret = klass->parse_frame (parse, buffer); + + /* 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; + + return ret; + } + + g_return_val_if_fail (GST_PAD_CAPS (parse->srcpad), GST_FLOW_ERROR); + + ret = gst_base_parse_push_buffer (parse, buffer); + + 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->priv->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); + + /* Make sure that adapter doesn't have any old data after + newsegment has been received and update our offset */ + if (parse->pending_segment) { + gst_adapter_clear (parse->priv->adapter); + parse->priv->offset = parse->priv->pending_offset; + } + + if (buffer) { + GST_LOG_OBJECT (parse, "buffer size: %d, offset = %lld", + GST_BUFFER_SIZE (buffer), GST_BUFFER_OFFSET (buffer)); + + + gst_adapter_push (parse->priv->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->priv->adapter) < min_size) { + GST_DEBUG_OBJECT (parse, "not enough data available (only %d bytes)", + gst_adapter_available (parse->priv->adapter)); + gst_buffer_unref (tmpbuf); + goto done; + } + + data = gst_adapter_peek (parse->priv->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 (buffer, GST_BUFFER_FLAG_DISCONT); + } + + skip = -1; + if (bclass->check_valid_frame (parse, tmpbuf, &fsize, &skip)) { + break; + } + if (skip > 0) { + GST_LOG_OBJECT (parse, "finding sync, skipping %d bytes", skip); + gst_adapter_flush (parse->priv->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->priv->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->priv->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->priv->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); + + 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) { + gst_event_ref (event); + return gst_pad_push_event (parse->sinkpad, event); + } else { + gst_event_ref (event); + if (gst_pad_push_event (parse->sinkpad, 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; + + gchar *caps_str = gst_caps_to_string (caps); + g_free (caps_str); + + parse = GST_BASE_PARSE (gst_pad_get_parent (pad)); + klass = GST_BASE_PARSE_GET_CLASS (parse); + + GST_DEBUG_OBJECT (parse, "setcaps: %s", caps_str); + + if (klass->set_sink_caps) + res = klass->set_sink_caps (parse, caps); + + parse->negotiated = res; + gst_object_unref (parse); + return gst_pad_set_caps (pad, caps); +} diff --git a/gst/flacparse/gstbaseparse.h b/gst/flacparse/gstbaseparse.h new file mode 100644 index 00000000..b4b44c26 --- /dev/null +++ b/gst/flacparse/gstbaseparse.h @@ -0,0 +1,237 @@ +/* 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; + + /*< 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/gst/flacparse/gstbitreader.c b/gst/flacparse/gstbitreader.c new file mode 100644 index 00000000..7647ab94 --- /dev/null +++ b/gst/flacparse/gstbitreader.c @@ -0,0 +1,400 @@ +/* GStreamer + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstbitreader.h" + +#include <string.h> + +/** + * SECTION:gstbitreader + * @short_description: Reads any number of bits from a memory buffer + * + * #GstBitReader provides a bit reader that can read any number of bits + * from a memory buffer. It provides functions for reading any number of bits + * into 8, 16, 32 and 64 bit variables. + */ + +/** + * gst_bit_reader_new: + * @data: Data from which the #GstBitReader should read + * @size: Size of @data in bytes + * + * Create a new #GstBitReader instance, which will read from @data. + * + * Returns: a new #GstBitReader instance + * + * Since: 0.10.22 + */ +GstBitReader * +gst_bit_reader_new (const guint8 * data, guint size) +{ + GstBitReader *ret = g_slice_new0 (GstBitReader); + + ret->data = data; + ret->size = size; + + return ret; +} + +/** + * gst_bit_reader_new_from_buffer: + * @buffer: Buffer from which the #GstBitReader should read + * + * Create a new #GstBitReader instance, which will read from the + * #GstBuffer @buffer. + * + * Returns: a new #GstBitReader instance + * + * Since: 0.10.22 + */ +GstBitReader * +gst_bit_reader_new_from_buffer (const GstBuffer * buffer) +{ + g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL); + + return gst_bit_reader_new (GST_BUFFER_DATA (buffer), + GST_BUFFER_SIZE (buffer)); +} + +/** + * gst_bit_reader_free: + * @reader: a #GstBitReader instance + * + * Frees a #GstBitReader instance, which was previously allocated by + * gst_bit_reader_new() or gst_bit_reader_new_from_buffer(). + * + * Since: 0.10.22 + */ +void +gst_bit_reader_free (GstBitReader * reader) +{ + g_return_if_fail (reader != NULL); + + g_slice_free (GstBitReader, reader); +} + +/** + * gst_bit_reader_init: + * @reader: a #GstBitReader instance + * @data: Data from which the #GstBitReader should read + * @size: Size of @data in bytes + * + * Initializes a #GstBitReader instance to read from @data. This function + * can be called on already initialized instances. + * + * Since: 0.10.22 + */ +void +gst_bit_reader_init (GstBitReader * reader, const guint8 * data, guint size) +{ + g_return_if_fail (reader != NULL); + + reader->data = data; + reader->size = size; + reader->byte = reader->bit = 0; +} + +/** + * gst_bit_reader_init: + * @reader: a #GstBitReader instance + * @buffer: Buffer from which the #GstBitReader should read + * + * Initializes a #GstBitReader instance to read from @buffer. This function + * can be called on already initialized instances. + * + * Since: 0.10.22 + */ +void +gst_bit_reader_init_from_buffer (GstBitReader * reader, + const GstBuffer * buffer) +{ + g_return_if_fail (GST_IS_BUFFER (buffer)); + + gst_bit_reader_init (reader, GST_BUFFER_DATA (buffer), + GST_BUFFER_SIZE (buffer)); +} + +/** + * gst_bit_reader_set_pos: + * @reader: a #GstBitReader instance + * @pos: The new position in bits + * + * Sets the new position of a #GstBitReader instance to @pos in bits. + * + * Returns: %TRUE if the position could be set successfully, %FALSE + * otherwise. + * + * Since: 0.10.22 + */ +gboolean +gst_bit_reader_set_pos (GstBitReader * reader, guint pos) +{ + g_return_val_if_fail (reader != NULL, FALSE); + + if (pos > reader->size * 8) + return FALSE; + + reader->byte = pos / 8; + reader->bit = pos % 8; + + return TRUE; +} + +/** + * gst_bit_reader_get_pos: + * @reader: a #GstBitReader instance + * + * Returns the current position of a #GstBitReader instance in bits. + * + * Returns: The current position of @reader in bits. + * + * Since: 0.10.22 + */ +guint +gst_bit_reader_get_pos (const GstBitReader * reader) +{ + g_return_val_if_fail (reader != NULL, 0); + + return reader->byte * 8 + reader->bit; +} + +/** + * gst_bit_reader_get_remaining: + * @reader: a #GstBitReader instance + * + * Returns the remaining number of bits of a #GstBitReader instance. + * + * Returns: The remaining number of bits of @reader instance. + * + * Since: 0.10.22 + */ +guint +gst_bit_reader_get_remaining (const GstBitReader * reader) +{ + g_return_val_if_fail (reader != NULL, 0); + + return reader->size * 8 - (reader->byte * 8 + reader->bit); +} + +/** + * gst_bit_reader_skip: + * @reader: a #GstBitReader instance + * @nbits: the number of bits to skip + * + * Skips @nbits bits of the #GstBitReader instance. + * + * Returns: %TRUE if @nbits bits could be skipped, %FALSE otherwise. + * + * Since: 0.10.22 + */ +gboolean +gst_bit_reader_skip (GstBitReader * reader, guint nbits) +{ + g_return_val_if_fail (reader != NULL, FALSE); + + if (gst_bit_reader_get_remaining (reader) < nbits) + return FALSE; + + reader->bit += nbits; + reader->byte += reader->bit / 8; + reader->bit = reader->bit % 8; + + return TRUE; +} + +/** + * gst_bit_reader_skip_to_byte: + * @reader: a #GstBitReader instance + * + * Skips until the next byte. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ +gboolean +gst_bit_reader_skip_to_byte (GstBitReader * reader) +{ + g_return_val_if_fail (reader != NULL, FALSE); + + if (reader->byte > reader->size) + return FALSE; + + if (reader->bit) { + reader->bit = 0; + reader->byte++; + } + + return TRUE; +} + +/** + * gst_bit_reader_get_bits_uint8: + * @reader: a #GstBitReader instance + * @val: Pointer to a #guint8 to store the result + * @nbits: number of bits to read + * + * Read @nbits bits into @val and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_bit_reader_get_bits_uint16: + * @reader: a #GstBitReader instance + * @val: Pointer to a #guint16 to store the result + * @nbits: number of bits to read + * + * Read @nbits bits into @val and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_bit_reader_get_bits_uint32: + * @reader: a #GstBitReader instance + * @val: Pointer to a #guint32 to store the result + * @nbits: number of bits to read + * + * Read @nbits bits into @val and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_bit_reader_get_bits_uint64: + * @reader: a #GstBitReader instance + * @val: Pointer to a #guint64 to store the result + * @nbits: number of bits to read + * + * Read @nbits bits into @val and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_bit_reader_peek_bits_uint8: + * @reader: a #GstBitReader instance + * @val: Pointer to a #guint8 to store the result + * @nbits: number of bits to read + * + * Read @nbits bits into @val but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_bit_reader_peek_bits_uint16: + * @reader: a #GstBitReader instance + * @val: Pointer to a #guint16 to store the result + * @nbits: number of bits to read + * + * Read @nbits bits into @val but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_bit_reader_peek_bits_uint32: + * @reader: a #GstBitReader instance + * @val: Pointer to a #guint32 to store the result + * @nbits: number of bits to read + * + * Read @nbits bits into @val but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_bit_reader_peek_bits_uint64: + * @reader: a #GstBitReader instance + * @val: Pointer to a #guint64 to store the result + * @nbits: number of bits to read + * + * Read @nbits bits into @val but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +#define GST_BIT_READER_READ_BITS(bits) \ +gboolean \ +gst_bit_reader_get_bits_uint##bits (GstBitReader *reader, guint##bits *val, guint nbits) \ +{ \ + guint##bits ret = 0; \ + \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + g_return_val_if_fail (nbits <= bits, FALSE); \ + \ + if (reader->byte * 8 + reader->bit + nbits > reader->size * 8) \ + return FALSE; \ + \ + while (nbits > 0) { \ + guint toread = MIN (nbits, 8 - reader->bit); \ + \ + ret <<= toread; \ + ret |= (reader->data[reader->byte] & (0xff >> reader->bit)) >> (8 - toread - reader->bit); \ + \ + reader->bit += toread; \ + if (reader->bit >= 8) { \ + reader->byte++; \ + reader->bit = 0; \ + } \ + nbits -= toread; \ + } \ + \ + *val = ret; \ + return TRUE; \ +} \ +\ +gboolean \ +gst_bit_reader_peek_bits_uint##bits (const GstBitReader *reader, guint##bits *val, guint nbits) \ +{ \ + GstBitReader tmp; \ + \ + g_return_val_if_fail (reader != NULL, FALSE); \ + tmp = *reader; \ + return gst_bit_reader_get_bits_uint##bits (&tmp, val, nbits); \ +} + +GST_BIT_READER_READ_BITS (8); +GST_BIT_READER_READ_BITS (16); +GST_BIT_READER_READ_BITS (32); +GST_BIT_READER_READ_BITS (64); diff --git a/gst/flacparse/gstbitreader.h b/gst/flacparse/gstbitreader.h new file mode 100644 index 00000000..5dc16a30 --- /dev/null +++ b/gst/flacparse/gstbitreader.h @@ -0,0 +1,99 @@ +/* GStreamer + * + * 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. + */ + +#ifndef __GST_BIT_READER_H__ +#define __GST_BIT_READER_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +/** + * GstBitReader: + * @data: Data from which the bit reader will read + * @size: Size of @data in bytes + * @byte: Current byte position + * @bit: Bit position in the current byte + * + * A bit reader instance. + */ +typedef struct { + const guint8 *data; + guint size; + + guint byte; /* Byte position */ + guint bit; /* Bit position in the current byte */ +} GstBitReader; + +GstBitReader * gst_bit_reader_new (const guint8 *data, guint size); +GstBitReader * gst_bit_reader_new_from_buffer (const GstBuffer *buffer); +void gst_bit_reader_free (GstBitReader *reader); + +void gst_bit_reader_init (GstBitReader *reader, const guint8 *data, guint size); +void gst_bit_reader_init_from_buffer (GstBitReader *reader, const GstBuffer *buffer); + +gboolean gst_bit_reader_set_pos (GstBitReader *reader, guint pos); + +guint gst_bit_reader_get_pos (const GstBitReader *reader); +guint gst_bit_reader_get_remaining (const GstBitReader *reader); + +gboolean gst_bit_reader_skip (GstBitReader *reader, guint nbits); +gboolean gst_bit_reader_skip_to_byte (GstBitReader *reader); + +gboolean gst_bit_reader_get_bits_uint8 (GstBitReader *reader, guint8 *val, guint nbits); +gboolean gst_bit_reader_get_bits_uint16 (GstBitReader *reader, guint16 *val, guint nbits); +gboolean gst_bit_reader_get_bits_uint32 (GstBitReader *reader, guint32 *val, guint nbits); +gboolean gst_bit_reader_get_bits_uint64 (GstBitReader *reader, guint64 *val, guint nbits); + +gboolean gst_bit_reader_peek_bits_uint8 (const GstBitReader *reader, guint8 *val, guint nbits); +gboolean gst_bit_reader_peek_bits_uint16 (const GstBitReader *reader, guint16 *val, guint nbits); +gboolean gst_bit_reader_peek_bits_uint32 (const GstBitReader *reader, guint32 *val, guint nbits); +gboolean gst_bit_reader_peek_bits_uint64 (const GstBitReader *reader, guint64 *val, guint nbits); + +/** + * GST_BIT_READER_INIT: + * @data: Data from which the #GstBitReader should read + * @size: Size of @data in bytes + * + * A #GstBitReader must be initialized with this macro, before it can be + * used. This macro can used be to initialize a variable, but it cannot + * be assigned to a variable. In that case you have to use + * gst_bit_reader_init(). + * + * Since: 0.10.22 + */ +#define GST_BIT_READER_INIT(data, size) {data, size, 0, 0} + +/** + * GST_BIT_READER_INIT_FROM_BUFFER: + * @buffer: Buffer from which the #GstBitReader should read + * + * A #GstBitReader must be initialized with this macro, before it can be + * used. This macro can used be to initialize a variable, but it cannot + * be assigned to a variable. In that case you have to use + * gst_bit_reader_init(). + * + * Since: 0.10.22 + */ +#define GST_BIT_READER_INIT_FROM_BUFFER(buffer) {GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer), 0, 0} + +G_END_DECLS + +#endif /* __GST_BIT_READER_H__ */ diff --git a/gst/flacparse/gstbytereader.c b/gst/flacparse/gstbytereader.c new file mode 100644 index 00000000..cb91ea79 --- /dev/null +++ b/gst/flacparse/gstbytereader.c @@ -0,0 +1,1231 @@ +/* GStreamer + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstbytereader.h" + +#include <string.h> + +/** + * SECTION:gstbytereader + * @short_description: Reads different integer and floating point types from a memory buffer + * + * #GstByteReader provides a byte reader that can read different integer and + * floating point types from a memory buffer. It provides functions for reading + * signed/unsigned, little/big endian integers of 8, 16, 24, 32 and 64 bits + * and functions for reading little/big endian floating points numbers of + * 32 and 64 bits. + */ + + +/* Copied from gst/floatcast/floatcast.h as this is in gst-plugins-base */ + +inline static gdouble +GDOUBLE_SWAP_LE_BE (gdouble in) +{ + union + { + guint64 i; + gdouble d; + } u; + + u.d = in; + u.i = GUINT64_SWAP_LE_BE (u.i); + return u.d; +} + +inline static gfloat +GFLOAT_SWAP_LE_BE (gfloat in) +{ + union + { + guint32 i; + gfloat f; + } u; + + u.f = in; + u.i = GUINT32_SWAP_LE_BE (u.i); + return u.f; +} + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +#define GFLOAT_TO_LE(val) ((gfloat) (val)) +#define GFLOAT_TO_BE(val) (GFLOAT_SWAP_LE_BE (val)) +#define GDOUBLE_TO_LE(val) ((gdouble) (val)) +#define GDOUBLE_TO_BE(val) (GDOUBLE_SWAP_LE_BE (val)) + +#elif G_BYTE_ORDER == G_BIG_ENDIAN +#define GFLOAT_TO_LE(val) (GFLOAT_SWAP_LE_BE (val)) +#define GFLOAT_TO_BE(val) ((gfloat) (val)) +#define GDOUBLE_TO_LE(val) (GDOUBLE_SWAP_LE_BE (val)) +#define GDOUBLE_TO_BE(val) ((gdouble) (val)) + +#else /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */ +#error unknown ENDIAN type +#endif /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */ + +#define GFLOAT_FROM_LE(val) (GFLOAT_TO_LE (val)) +#define GFLOAT_FROM_BE(val) (GFLOAT_TO_BE (val)) +#define GDOUBLE_FROM_LE(val) (GDOUBLE_TO_LE (val)) +#define GDOUBLE_FROM_BE(val) (GDOUBLE_TO_BE (val)) + + +/** + * gst_byte_reader_new: + * @data: Data from which the #GstByteReader should read + * @size: Size of @data in bytes + * + * Create a new #GstByteReader instance, which will read from @data. + * + * Returns: a new #GstByteReader instance + * + * Since: 0.10.22 + */ +GstByteReader * +gst_byte_reader_new (const guint8 * data, guint size) +{ + GstByteReader *ret = g_slice_new0 (GstByteReader); + + ret->data = data; + ret->size = size; + + return ret; +} + +/** + * gst_byte_reader_new_from_buffer: + * @buffer: Buffer from which the #GstByteReader should read + * + * Create a new #GstByteReader instance, which will read from the + * #GstBuffer @buffer. + * + * Returns: a new #GstByteReader instance + * + * Since: 0.10.22 + */ +GstByteReader * +gst_byte_reader_new_from_buffer (const GstBuffer * buffer) +{ + g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL); + + return gst_byte_reader_new (GST_BUFFER_DATA (buffer), + GST_BUFFER_SIZE (buffer)); +} + +/** + * gst_byte_reader_free: + * @reader: a #GstByteReader instance + * + * Frees a #GstByteReader instance, which was previously allocated by + * gst_byte_reader_new() or gst_byte_reader_new_from_buffer(). + * + * Since: 0.10.22 + */ +void +gst_byte_reader_free (GstByteReader * reader) +{ + g_return_if_fail (reader != NULL); + + g_slice_free (GstByteReader, reader); +} + +/** + * gst_byte_reader_init: + * @reader: a #GstByteReader instance + * @data: Data from which the #GstByteReader should read + * @size: Size of @data in bytes + * + * Initializes a #GstByteReader instance to read from @data. This function + * can be called on already initialized instances. + * + * Since: 0.10.22 + */ +void +gst_byte_reader_init (GstByteReader * reader, const guint8 * data, guint size) +{ + g_return_if_fail (reader != NULL); + + reader->data = data; + reader->size = size; + reader->byte = 0; +} + +/** + * gst_byte_reader_init: + * @reader: a #GstByteReader instance + * @buffer: Buffer from which the #GstByteReader should read + * + * Initializes a #GstByteReader instance to read from @buffer. This function + * can be called on already initialized instances. + * + * Since: 0.10.22 + */ +void +gst_byte_reader_init_from_buffer (GstByteReader * reader, + const GstBuffer * buffer) +{ + g_return_if_fail (GST_IS_BUFFER (buffer)); + + gst_byte_reader_init (reader, GST_BUFFER_DATA (buffer), + GST_BUFFER_SIZE (buffer)); +} + +/** + * gst_byte_reader_set_pos: + * @reader: a #GstByteReader instance + * @pos: The new position in bytes + * + * Sets the new position of a #GstByteReader instance to @pos in bytes. + * + * Returns: %TRUE if the position could be set successfully, %FALSE + * otherwise. + * + * Since: 0.10.22 + */ +gboolean +gst_byte_reader_set_pos (GstByteReader * reader, guint pos) +{ + g_return_val_if_fail (reader != NULL, FALSE); + + if (pos > reader->size) + return FALSE; + + reader->byte = pos; + + return TRUE; +} + +/** + * gst_byte_reader_get_pos: + * @reader: a #GstByteReader instance + * + * Returns the current position of a #GstByteReader instance in bytes. + * + * Returns: The current position of @reader in bytes. + * + * Since: 0.10.22 + */ +guint +gst_byte_reader_get_pos (const GstByteReader * reader) +{ + g_return_val_if_fail (reader != NULL, 0); + + return reader->byte; +} + +/** + * gst_byte_reader_get_remaining: + * @reader: a #GstByteReader instance + * + * Returns the remaining number of bytes of a #GstByteReader instance. + * + * Returns: The remaining number of bytes of @reader instance. + * + * Since: 0.10.22 + */ +guint +gst_byte_reader_get_remaining (const GstByteReader * reader) +{ + g_return_val_if_fail (reader != NULL, 0); + + return reader->size - reader->byte; +} + +/** + * gst_byte_reader_skip: + * @reader: a #GstByteReader instance + * @nbytes: the number of bytes to skip + * + * Skips @nbytes bytes of the #GstByteReader instance. + * + * Returns: %TRUE if @nbytes bytes could be skipped, %FALSE otherwise. + * + * Since: 0.10.22 + */ +gboolean +gst_byte_reader_skip (GstByteReader * reader, guint nbytes) +{ + g_return_val_if_fail (reader != NULL, FALSE); + + if (gst_byte_reader_get_remaining (reader) < nbytes) + return FALSE; + + reader->byte += nbytes; + + return TRUE; +} + +/** + * gst_byte_reader_get_uint8: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint8 to store the result + * + * Read an unsigned 8 bit integer into @val and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_int8: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint8 to store the result + * + * Read a signed 8 bit integer into @val and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_uint8: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint8 to store the result + * + * Read a signed 8 bit integer into @val but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_int8: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint8 to store the result + * + * Read a signed 8 bit integer into @val but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_uint16_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint16 to store the result + * + * Read an unsigned 16 bit little endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_int16_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint16 to store the result + * + * Read a signed 16 bit little endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_uint16_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint16 to store the result + * + * Read a signed 16 bit little endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_int16_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint16 to store the result + * + * Read a signed 16 bit little endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_uint16_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint16 to store the result + * + * Read an unsigned 16 bit big endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_int16_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint16 to store the result + * + * Read a signed 16 bit big endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_uint16_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint16 to store the result + * + * Read a signed 16 bit big endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_int16_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint16 to store the result + * + * Read a signed 16 bit big endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_uint24_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint32 to store the result + * + * Read an unsigned 24 bit little endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_int24_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint32 to store the result + * + * Read a signed 24 bit little endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_uint24_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint32 to store the result + * + * Read a signed 24 bit little endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_int24_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint32 to store the result + * + * Read a signed 24 bit little endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_uint24_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint32 to store the result + * + * Read an unsigned 24 bit big endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_int24_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint32 to store the result + * + * Read a signed 24 bit big endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_uint24_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint32 to store the result + * + * Read a signed 24 bit big endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_int24_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint32 to store the result + * + * Read a signed 24 bit big endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + + +/** + * gst_byte_reader_get_uint32_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint32 to store the result + * + * Read an unsigned 32 bit little endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_int32_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint32 to store the result + * + * Read a signed 32 bit little endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_uint32_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint32 to store the result + * + * Read a signed 32 bit little endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_int32_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint32 to store the result + * + * Read a signed 32 bit little endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_uint32_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint32 to store the result + * + * Read an unsigned 32 bit big endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_int32_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint32 to store the result + * + * Read a signed 32 bit big endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_uint32_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint32 to store the result + * + * Read a signed 32 bit big endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_int32_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint32 to store the result + * + * Read a signed 32 bit big endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_uint64_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint64 to store the result + * + * Read an unsigned 64 bit little endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_int64_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint64 to store the result + * + * Read a signed 64 bit little endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_uint64_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint64 to store the result + * + * Read a signed 64 bit little endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_int64_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint64 to store the result + * + * Read a signed 64 bit little endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_uint64_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint64 to store the result + * + * Read an unsigned 64 bit big endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_int64_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint64 to store the result + * + * Read a signed 64 bit big endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_uint64_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #guint64 to store the result + * + * Read a signed 64 bit big endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_int64_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gint64 to store the result + * + * Read a signed 64 bit big endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +#define GST_BYTE_READER_READ_INTS(bits) \ +gboolean \ +gst_byte_reader_get_uint##bits##_le (GstByteReader *reader, guint##bits *val) \ +{ \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + \ + if (reader->byte + bits / 8 > reader->size) \ + return FALSE; \ + \ + *val = GST_READ_UINT##bits##_LE (&reader->data[reader->byte]); \ + reader->byte += bits / 8; \ + return TRUE; \ +} \ +\ +gboolean \ +gst_byte_reader_get_uint##bits##_be (GstByteReader *reader, guint##bits *val) \ +{ \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + \ + if (reader->byte + bits / 8 > reader->size) \ + return FALSE; \ + \ + *val = GST_READ_UINT##bits##_BE (&reader->data[reader->byte]); \ + reader->byte += bits / 8; \ + return TRUE; \ +} \ +\ +gboolean \ +gst_byte_reader_get_int##bits##_le (GstByteReader *reader, gint##bits *val) \ +{ \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + \ + if (reader->byte + bits / 8 > reader->size) \ + return FALSE; \ + \ + *val = GST_READ_UINT##bits##_LE (&reader->data[reader->byte]); \ + reader->byte += bits / 8; \ + return TRUE; \ +} \ +\ +gboolean \ +gst_byte_reader_get_int##bits##_be (GstByteReader *reader, gint##bits *val) \ +{ \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + \ + if (reader->byte + bits / 8 > reader->size) \ + return FALSE; \ + \ + *val = GST_READ_UINT##bits##_BE (&reader->data[reader->byte]); \ + reader->byte += bits / 8; \ + return TRUE; \ +} \ +gboolean \ +gst_byte_reader_peek_uint##bits##_le (GstByteReader *reader, guint##bits *val) \ +{ \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + \ + if (reader->byte + bits / 8 > reader->size) \ + return FALSE; \ + \ + *val = GST_READ_UINT##bits##_LE (&reader->data[reader->byte]); \ + return TRUE; \ +} \ +\ +gboolean \ +gst_byte_reader_peek_uint##bits##_be (GstByteReader *reader, guint##bits *val) \ +{ \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + \ + if (reader->byte + bits / 8 > reader->size) \ + return FALSE; \ + \ + *val = GST_READ_UINT##bits##_BE (&reader->data[reader->byte]); \ + return TRUE; \ +} \ +\ +gboolean \ +gst_byte_reader_peek_int##bits##_le (GstByteReader *reader, gint##bits *val) \ +{ \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + \ + if (reader->byte + bits / 8 > reader->size) \ + return FALSE; \ + \ + *val = GST_READ_UINT##bits##_LE (&reader->data[reader->byte]); \ + return TRUE; \ +} \ +\ +gboolean \ +gst_byte_reader_peek_int##bits##_be (GstByteReader *reader, gint##bits *val) \ +{ \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + \ + if (reader->byte + bits / 8 > reader->size) \ + return FALSE; \ + \ + *val = GST_READ_UINT##bits##_BE (&reader->data[reader->byte]); \ + return TRUE; \ +} + + +GST_BYTE_READER_READ_INTS (16); +GST_BYTE_READER_READ_INTS (32); +GST_BYTE_READER_READ_INTS (64); + +gboolean +gst_byte_reader_get_uint8 (GstByteReader * reader, guint8 * val) +{ + g_return_val_if_fail (reader != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (reader->byte + 1 > reader->size) + return FALSE; + + *val = GST_READ_UINT8 (&reader->data[reader->byte]); + reader->byte++; + return TRUE; +} + +gboolean +gst_byte_reader_get_int8 (GstByteReader * reader, gint8 * val) +{ + g_return_val_if_fail (reader != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (reader->byte + 1 > reader->size) + return FALSE; + + *val = GST_READ_UINT8 (&reader->data[reader->byte]); + reader->byte++; + return TRUE; +} + +gboolean +gst_byte_reader_peek_uint8 (GstByteReader * reader, guint8 * val) +{ + g_return_val_if_fail (reader != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (reader->byte + 1 > reader->size) + return FALSE; + + *val = GST_READ_UINT8 (&reader->data[reader->byte]); + return TRUE; +} + +gboolean +gst_byte_reader_peek_int8 (GstByteReader * reader, gint8 * val) +{ + g_return_val_if_fail (reader != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (reader->byte + 1 > reader->size) + return FALSE; + + *val = GST_READ_UINT8 (&reader->data[reader->byte]); + return TRUE; +} + +gboolean +gst_byte_reader_get_uint24_le (GstByteReader * reader, guint32 * val) +{ + g_return_val_if_fail (reader != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (reader->byte + 3 > reader->size) + return FALSE; + + *val = + (reader->data[reader->byte] | (reader->data[reader->byte + + 1] << 8) | (reader->data[reader->byte + 2] << 16)); + reader->byte += 3; + return TRUE; +} + +gboolean +gst_byte_reader_get_uint24_be (GstByteReader * reader, guint32 * val) +{ + g_return_val_if_fail (reader != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (reader->byte + 3 > reader->size) + return FALSE; + + *val = + (reader->data[reader->byte + 2] | (reader->data[reader->byte + + 1] << 8) | (reader->data[reader->byte] << 16)); + reader->byte += 3; + return TRUE; +} + +gboolean +gst_byte_reader_get_int24_le (GstByteReader * reader, gint32 * val) +{ + guint32 ret; + + g_return_val_if_fail (reader != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (reader->byte + 3 > reader->size) + return FALSE; + + ret = + (reader->data[reader->byte] | (reader->data[reader->byte + + 1] << 8) | (reader->data[reader->byte + 2] << 16)); + + if (ret & 0x00800000) + ret |= 0xff000000; + + reader->byte += 3; + + *val = ret; + return TRUE; +} + +gboolean +gst_byte_reader_get_int24_be (GstByteReader * reader, gint32 * val) +{ + guint32 ret; + + g_return_val_if_fail (reader != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (reader->byte + 3 > reader->size) + return FALSE; + + ret = + (reader->data[reader->byte + 2] | (reader->data[reader->byte + + 1] << 8) | (reader->data[reader->byte] << 16)); + if (ret & 0x00800000) + ret |= 0xff000000; + + reader->byte += 3; + + *val = ret; + return TRUE; +} + +gboolean +gst_byte_reader_peek_uint24_le (GstByteReader * reader, guint32 * val) +{ + g_return_val_if_fail (reader != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (reader->byte + 3 > reader->size) + return FALSE; + + *val = + (reader->data[reader->byte] | (reader->data[reader->byte + + 1] << 8) | (reader->data[reader->byte + 2] << 16)); + return TRUE; +} + +gboolean +gst_byte_reader_peek_uint24_be (GstByteReader * reader, guint32 * val) +{ + g_return_val_if_fail (reader != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (reader->byte + 3 > reader->size) + return FALSE; + + *val = + (reader->data[reader->byte + 2] | (reader->data[reader->byte + + 1] << 8) | (reader->data[reader->byte] << 16)); + return TRUE; +} + +gboolean +gst_byte_reader_peek_int24_le (GstByteReader * reader, gint32 * val) +{ + guint32 ret; + + g_return_val_if_fail (reader != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (reader->byte + 3 > reader->size) + return FALSE; + + ret = + (reader->data[reader->byte] | (reader->data[reader->byte + + 1] << 8) | (reader->data[reader->byte + 2] << 16)); + + if (ret & 0x00800000) + ret |= 0xff000000; + + *val = ret; + return TRUE; +} + +gboolean +gst_byte_reader_peek_int24_be (GstByteReader * reader, gint32 * val) +{ + guint32 ret; + + g_return_val_if_fail (reader != NULL, FALSE); + g_return_val_if_fail (val != NULL, FALSE); + + if (reader->byte + 3 > reader->size) + return FALSE; + + ret = + (reader->data[reader->byte + 2] | (reader->data[reader->byte + + 1] << 8) | (reader->data[reader->byte] << 16)); + if (ret & 0x00800000) + ret |= 0xff000000; + + *val = ret; + return TRUE; +} + +/** + * gst_byte_reader_get_float32_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gfloat to store the result + * + * Read a 32 bit little endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_float32_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gfloat to store the result + * + * Read a 32 bit little endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_float32_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gfloat to store the result + * + * Read a 32 bit big endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_float32_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gfloat to store the result + * + * Read a 32 bit big endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_float64_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gdouble to store the result + * + * Read a 64 bit little endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_float64_le: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gdouble to store the result + * + * Read a 64 bit little endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_get_float64_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gdouble to store the result + * + * Read a 64 bit big endian integer into @val + * and update the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +/** + * gst_byte_reader_peek_float64_be: + * @reader: a #GstByteReader instance + * @val: Pointer to a #gdouble to store the result + * + * Read a 64 bit big endian integer into @val + * but keep the current position. + * + * Returns: %TRUE if successful, %FALSE otherwise. + * + * Since: 0.10.22 + */ + +#define GST_BYTE_READER_READ_FLOATS(bits, type, TYPE) \ +gboolean \ +gst_byte_reader_get_float##bits##_le (GstByteReader *reader, g##type *val) \ +{ \ + g##type ret; \ + \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + \ + if (reader->byte + bits / 8 > reader->size) \ + return FALSE; \ + \ + memcpy (&ret, &reader->data[reader->byte], bits / 8); \ + *val = G##TYPE##_FROM_LE (ret); \ + reader->byte += bits / 8; \ + return TRUE; \ +} \ +gboolean \ +gst_byte_reader_get_float##bits##_be (GstByteReader *reader, g##type *val) \ +{ \ + g##type ret; \ + \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + \ + if (reader->byte + bits / 8 > reader->size) \ + return FALSE; \ + \ + memcpy (&ret, &reader->data[reader->byte], bits / 8); \ + *val = G##TYPE##_FROM_BE (ret); \ + reader->byte += bits / 8; \ + return TRUE; \ +} \ +gboolean \ +gst_byte_reader_peek_float##bits##_le (GstByteReader *reader, g##type *val) \ +{ \ + g##type ret; \ + \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + \ + if (reader->byte + bits / 8 > reader->size) \ + return FALSE; \ + \ + memcpy (&ret, &reader->data[reader->byte], bits / 8); \ + *val = G##TYPE##_FROM_LE (ret); \ + return TRUE; \ +} \ +gboolean \ +gst_byte_reader_peek_float##bits##_be (GstByteReader *reader, g##type *val) \ +{ \ + g##type ret; \ + \ + g_return_val_if_fail (reader != NULL, FALSE); \ + g_return_val_if_fail (val != NULL, FALSE); \ + \ + if (reader->byte + bits / 8 > reader->size) \ + return FALSE; \ + \ + memcpy (&ret, &reader->data[reader->byte], bits / 8); \ + *val = G##TYPE##_FROM_BE (ret); \ + return TRUE; \ +} + +GST_BYTE_READER_READ_FLOATS (32, float, FLOAT); +GST_BYTE_READER_READ_FLOATS (64, double, DOUBLE); diff --git a/gst/flacparse/gstbytereader.h b/gst/flacparse/gstbytereader.h new file mode 100644 index 00000000..28534c00 --- /dev/null +++ b/gst/flacparse/gstbytereader.h @@ -0,0 +1,134 @@ +/* GStreamer + * + * 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. + */ + +#ifndef __GST_BYTE_READER_H__ +#define __GST_BYTE_READER_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +/** + * GstByteReader: + * @data: Data from which the bit reader will read + * @size: Size of @data in bytes + * @byte: Current byte position + * + * A byte reader instance. + */ +typedef struct { + const guint8 *data; + guint size; + + guint byte; /* Byte position */ +} GstByteReader; + +GstByteReader * gst_byte_reader_new (const guint8 *data, guint size); +GstByteReader * gst_byte_reader_new_from_buffer (const GstBuffer *buffer); +void gst_byte_reader_free (GstByteReader *reader); + +void gst_byte_reader_init (GstByteReader *reader, const guint8 *data, guint size); +void gst_byte_reader_init_from_buffer (GstByteReader *reader, const GstBuffer *buffer); + +gboolean gst_byte_reader_set_pos (GstByteReader *reader, guint pos); + +guint gst_byte_reader_get_pos (const GstByteReader *reader); +guint gst_byte_reader_get_remaining (const GstByteReader *reader); + +gboolean gst_byte_reader_skip (GstByteReader *reader, guint nbytes); + +gboolean gst_byte_reader_get_uint8 (GstByteReader *reader, guint8 *val); +gboolean gst_byte_reader_get_int8 (GstByteReader *reader, gint8 *val); +gboolean gst_byte_reader_get_uint16_le (GstByteReader *reader, guint16 *val); +gboolean gst_byte_reader_get_int16_le (GstByteReader *reader, gint16 *val); +gboolean gst_byte_reader_get_uint16_be (GstByteReader *reader, guint16 *val); +gboolean gst_byte_reader_get_int16_be (GstByteReader *reader, gint16 *val); +gboolean gst_byte_reader_get_uint24_le (GstByteReader *reader, guint32 *val); +gboolean gst_byte_reader_get_int24_le (GstByteReader *reader, gint32 *val); +gboolean gst_byte_reader_get_uint24_be (GstByteReader *reader, guint32 *val); +gboolean gst_byte_reader_get_int24_be (GstByteReader *reader, gint32 *val); +gboolean gst_byte_reader_get_uint32_le (GstByteReader *reader, guint32 *val); +gboolean gst_byte_reader_get_int32_le (GstByteReader *reader, gint32 *val); +gboolean gst_byte_reader_get_uint32_be (GstByteReader *reader, guint32 *val); +gboolean gst_byte_reader_get_int32_be (GstByteReader *reader, gint32 *val); +gboolean gst_byte_reader_get_uint64_le (GstByteReader *reader, guint64 *val); +gboolean gst_byte_reader_get_int64_le (GstByteReader *reader, gint64 *val); +gboolean gst_byte_reader_get_uint64_be (GstByteReader *reader, guint64 *val); +gboolean gst_byte_reader_get_int64_be (GstByteReader *reader, gint64 *val); + +gboolean gst_byte_reader_peek_uint8 (GstByteReader *reader, guint8 *val); +gboolean gst_byte_reader_peek_int8 (GstByteReader *reader, gint8 *val); +gboolean gst_byte_reader_peek_uint16_le (GstByteReader *reader, guint16 *val); +gboolean gst_byte_reader_peek_int16_le (GstByteReader *reader, gint16 *val); +gboolean gst_byte_reader_peek_uint16_be (GstByteReader *reader, guint16 *val); +gboolean gst_byte_reader_peek_int16_be (GstByteReader *reader, gint16 *val); +gboolean gst_byte_reader_peek_uint24_le (GstByteReader *reader, guint32 *val); +gboolean gst_byte_reader_peek_int24_le (GstByteReader *reader, gint32 *val); +gboolean gst_byte_reader_peek_uint24_be (GstByteReader *reader, guint32 *val); +gboolean gst_byte_reader_peek_int24_be (GstByteReader *reader, gint32 *val); +gboolean gst_byte_reader_peek_uint32_le (GstByteReader *reader, guint32 *val); +gboolean gst_byte_reader_peek_int32_le (GstByteReader *reader, gint32 *val); +gboolean gst_byte_reader_peek_uint32_be (GstByteReader *reader, guint32 *val); +gboolean gst_byte_reader_peek_int32_be (GstByteReader *reader, gint32 *val); +gboolean gst_byte_reader_peek_uint64_le (GstByteReader *reader, guint64 *val); +gboolean gst_byte_reader_peek_int64_le (GstByteReader *reader, gint64 *val); +gboolean gst_byte_reader_peek_uint64_be (GstByteReader *reader, guint64 *val); +gboolean gst_byte_reader_peek_int64_be (GstByteReader *reader, gint64 *val); + +gboolean gst_byte_reader_get_float32_le (GstByteReader *reader, gfloat *val); +gboolean gst_byte_reader_get_float32_be (GstByteReader *reader, gfloat *val); +gboolean gst_byte_reader_get_float64_le (GstByteReader *reader, gdouble *val); +gboolean gst_byte_reader_get_float64_be (GstByteReader *reader, gdouble *val); + +gboolean gst_byte_reader_peek_float32_le (GstByteReader *reader, gfloat *val); +gboolean gst_byte_reader_peek_float32_be (GstByteReader *reader, gfloat *val); +gboolean gst_byte_reader_peek_float64_le (GstByteReader *reader, gdouble *val); +gboolean gst_byte_reader_peek_float64_be (GstByteReader *reader, gdouble *val); + +/** + * GST_BYTE_READER_INIT: + * @data: Data from which the #GstByteReader should read + * @size: Size of @data in bytes + * + * A #GstByteReader must be initialized with this macro, before it can be + * used. This macro can used be to initialize a variable, but it cannot + * be assigned to a variable. In that case you have to use + * gst_byte_reader_init(). + * + * Since: 0.10.22 + */ +#define GST_BYTE_READER_INIT(data, size) {data, size, 0} + +/** + * GST_BYTE_READER_INIT_FROM_BUFFER: + * @buffer: Buffer from which the #GstByteReader should read + * + * A #GstByteReader must be initialized with this macro, before it can be + * used. This macro can used be to initialize a variable, but it cannot + * be assigned to a variable. In that case you have to use + * gst_byte_reader_init(). + * + * Since: 0.10.22 + */ +#define GST_BYTE_READER_INIT_FROM_BUFFER(buffer) {GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer), 0} + +G_END_DECLS + +#endif /* __GST_BYTE_READER_H__ */ diff --git a/gst/flacparse/gstflac.c b/gst/flacparse/gstflac.c new file mode 100644 index 00000000..862426f5 --- /dev/null +++ b/gst/flacparse/gstflac.c @@ -0,0 +1,52 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstflacparse.h" + +#include <gst/tag/tag.h> +#include <gst/gst-i18n-plugin.h> + +static gboolean +plugin_init (GstPlugin * plugin) +{ +#if ENABLE_NLS + GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, + LOCALEDIR); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +#endif + + if (!gst_element_register (plugin, "flacparse", GST_RANK_NONE, + gst_flac_parse_get_type ())) + return FALSE; + + gst_tag_register_musicbrainz_tags (); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "flacparse", + "The FLAC Lossless compressor Codec parser", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/gst/flacparse/gstflacparse.c b/gst/flacparse/gstflacparse.c new file mode 100644 index 00000000..f25e34b9 --- /dev/null +++ b/gst/flacparse/gstflacparse.c @@ -0,0 +1,1233 @@ +/* GStreamer + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstflacparse.h" + +#include <string.h> +#include <gst/tag/tag.h> +#include <gst/audio/audio.h> + +#include "gstbitreader.h" +#include "gstbytereader.h" + +GST_DEBUG_CATEGORY_STATIC (flacparse_debug); +#define GST_CAT_DEFAULT flacparse_debug + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-flac, " + "channels = (int) [ 1, 8 ], " "rate = (int) [ 1, 655350 ]") + ); + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-flac") + ); + +static void gst_flac_parse_finalize (GObject * object); + +static gboolean gst_flac_parse_start (GstBaseParse * parse); +static gboolean gst_flac_parse_stop (GstBaseParse * parse); +static gboolean gst_flac_parse_check_valid_frame (GstBaseParse * parse, + GstBuffer * buffer, guint * framesize, gint * skipsize); +static GstFlowReturn gst_flac_parse_parse_frame (GstBaseParse * parse, + GstBuffer * buffer); + +GST_BOILERPLATE (GstFlacParse, gst_flac_parse, GstBaseParse, + GST_TYPE_BASE_PARSE); + +static void +gst_flac_parse_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + 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_simple (element_class, "FLAC audio parser", + "Codec/Parser/Audio", + "Parses audio with the FLAC lossless audio codec", + "Sebastian Dröge <sebastian.droege@collabora.co.uk>"); + + GST_DEBUG_CATEGORY_INIT (flacparse_debug, "flacparse", 0, + "Flac parser element"); +} + +static void +gst_flac_parse_class_init (GstFlacParseClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBaseParseClass *baseparse_class = GST_BASE_PARSE_CLASS (klass); + + gobject_class->finalize = gst_flac_parse_finalize; + + baseparse_class->start = GST_DEBUG_FUNCPTR (gst_flac_parse_start); + baseparse_class->stop = GST_DEBUG_FUNCPTR (gst_flac_parse_stop); + baseparse_class->check_valid_frame = + GST_DEBUG_FUNCPTR (gst_flac_parse_check_valid_frame); + baseparse_class->parse_frame = GST_DEBUG_FUNCPTR (gst_flac_parse_parse_frame); +} + +static void +gst_flac_parse_init (GstFlacParse * flacparse, GstFlacParseClass * klass) +{ +} + +static void +gst_flac_parse_finalize (GObject * object) +{ + GstFlacParse *flacparse = GST_FLAC_PARSE (object); + + if (flacparse->tags) { + gst_tag_list_free (flacparse->tags); + flacparse->tags = NULL; + } + + + g_list_foreach (flacparse->headers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (flacparse->headers); + flacparse->headers = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_flac_parse_start (GstBaseParse * parse) +{ + GstFlacParse *flacparse = GST_FLAC_PARSE (parse); + + flacparse->state = GST_FLAC_PARSE_STATE_INIT; + flacparse->min_blocksize = 0; + flacparse->max_blocksize = 0; + flacparse->min_framesize = 0; + flacparse->max_framesize = 0; + + flacparse->upstream_length = -1; + + flacparse->samplerate = 0; + flacparse->channels = 0; + flacparse->bps = 0; + flacparse->total_samples = 0; + + flacparse->requested_frame_size = 0; + flacparse->offset = GST_CLOCK_TIME_NONE; + flacparse->blocking_strategy = 0; + flacparse->block_size = 0; + flacparse->sample_number = 0; + + /* "fLaC" marker */ + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), 4); + + return TRUE; +} + +static gboolean +gst_flac_parse_stop (GstBaseParse * parse) +{ + GstFlacParse *flacparse = GST_FLAC_PARSE (parse); + + if (flacparse->tags) { + gst_tag_list_free (flacparse->tags); + flacparse->tags = NULL; + } + + g_list_foreach (flacparse->headers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (flacparse->headers); + flacparse->headers = NULL; + + return TRUE; +} + +static gint +gst_flac_parse_get_frame_size (GstFlacParse * flacparse, GstBuffer * buffer, + guint * framesize_ret) +{ + GstBitReader reader = GST_BIT_READER_INIT_FROM_BUFFER (buffer); + guint16 samplerate; + guint8 tmp; + gint i; + guint8 channel_assignment = 0; + + /* Skip 14 bit sync code */ + if (!gst_bit_reader_skip (&reader, 14)) + goto need_more_data; + + /* Must be 0 */ + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 1)) + goto need_more_data; + else if (tmp != 0) + goto error; + + /* 0 == fixed block size, 1 == variable block size */ + if (!gst_bit_reader_get_bits_uint8 (&reader, &flacparse->blocking_strategy, + 1)) + goto need_more_data; + + /* block size index, calculation of the real blocksize below */ + if (!gst_bit_reader_get_bits_uint16 (&reader, &flacparse->block_size, 4)) + goto need_more_data; + else if (flacparse->block_size == 0) + goto error; + + /* sample rate index, calculation of the real samplerate below */ + if (!gst_bit_reader_get_bits_uint16 (&reader, &samplerate, 4)) + goto need_more_data; + else if (samplerate == 0x0f) + goto error; + + /* channel assignment */ + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 4)) { + goto need_more_data; + } else if (tmp < 8) { + if (flacparse->channels && tmp + 1 != flacparse->channels) + goto error; + else + flacparse->channels = tmp + 1; + } else if (tmp <= 10 && flacparse->channels != 2) { + if (flacparse->channels && 2 != flacparse->channels) + goto error; + else + flacparse->channels = 2; + if (tmp == 8) + channel_assignment = 1; /* left-side */ + else if (tmp == 9) + channel_assignment = 2; /* right-side */ + else + channel_assignment = 3; /* mid-side */ + } else if (tmp > 10) { + goto error; + } + + /* bits per sample */ + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 3)) { + goto need_more_data; + } else if (tmp == 0x03 || tmp == 0x07) { + goto error; + } else if (tmp == 0 && flacparse->bps == 0) { + goto need_streaminfo; + } else if (tmp == 0x01 && flacparse->bps != 8) { + if (flacparse->bps && flacparse->bps != 8) + goto error; + else + flacparse->bps = 8; + } else if (tmp == 0x02 && flacparse->bps != 12) { + if (flacparse->bps && flacparse->bps != 12) + goto error; + else + flacparse->bps = 12; + } else if (tmp == 0x04 && flacparse->bps != 16) { + if (flacparse->bps && flacparse->bps != 16) + goto error; + else + flacparse->bps = 16; + } else if (tmp == 0x05 && flacparse->bps != 20) { + if (flacparse->bps && flacparse->bps != 20) + goto error; + else + flacparse->bps = 20; + } else if (tmp == 0x06 && flacparse->bps != 24) { + if (flacparse->bps && flacparse->bps != 24) + goto error; + else + flacparse->bps = 24; + } + + /* reserved, must be 0 */ + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 1)) + goto need_more_data; + else if (tmp != 0) + goto error; + + /* read "utf8" encoded sample/frame number */ + { + guint len = 0; + + tmp = 1; + while (tmp != 0) { + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 1)) + goto need_more_data; + else if (tmp == 1) + len++; + } + if (len == 1) + goto error; + + flacparse->sample_number = 0; + if (len == 0) { + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 7)) + goto need_more_data; + flacparse->sample_number = tmp; + } else if ((flacparse->blocking_strategy == 0 && len > 6) || + (flacparse->blocking_strategy == 1 && len > 7)) { + goto error; + } else { + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 8 - len - 1)) + goto need_more_data; + + flacparse->sample_number = tmp; + len -= 1; + + while (len > 0) { + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 2)) + goto need_more_data; + else if (tmp != 0x02) + goto error; + + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 6)) + goto need_more_data; + flacparse->sample_number <<= 6; + flacparse->sample_number |= tmp; + len--; + } + } + } + + /* calculate real blocksize from the blocksize index */ + if (flacparse->block_size == 1) + flacparse->block_size = 192; + else if (flacparse->block_size <= 5) + flacparse->block_size = 576 * (1 << (flacparse->block_size - 2)); + else if (flacparse->block_size <= 15) + flacparse->block_size = 256 * (1 << (flacparse->block_size - 8)); + else if (flacparse->block_size == 6) { + if (!gst_bit_reader_get_bits_uint16 (&reader, &flacparse->block_size, 8)) + goto need_more_data; + flacparse->block_size++; + } else if (flacparse->block_size == 7) { + if (!gst_bit_reader_get_bits_uint16 (&reader, &flacparse->block_size, 16)) + goto need_more_data; + flacparse->block_size++; + } + + /* calculate the real samplerate from the samplerate index */ + if (samplerate == 0 && flacparse->samplerate == 0) { + goto need_streaminfo; + } else if (samplerate == 1) { + if (flacparse->samplerate == 0) + flacparse->samplerate = 88200; + else if (flacparse->samplerate != 88200) + goto error; + } else if (samplerate == 2) { + if (flacparse->samplerate == 0) + flacparse->samplerate = 176400; + else if (flacparse->samplerate != 176400) + goto error; + } else if (samplerate == 3) { + if (flacparse->samplerate == 0) + flacparse->samplerate = 192000; + else if (flacparse->samplerate != 192000) + goto error; + } else if (samplerate == 4) { + if (flacparse->samplerate == 0) + flacparse->samplerate = 8000; + else if (flacparse->samplerate != 8000) + goto error; + } else if (samplerate == 5) { + if (flacparse->samplerate == 0) + flacparse->samplerate = 16000; + else if (flacparse->samplerate != 16000) + goto error; + } else if (samplerate == 6) { + if (flacparse->samplerate == 0) + flacparse->samplerate = 22050; + else if (flacparse->samplerate != 22050) + goto error; + } else if (samplerate == 7) { + if (flacparse->samplerate == 0) + flacparse->samplerate = 24000; + else if (flacparse->samplerate != 24000) + goto error; + } else if (samplerate == 8) { + if (flacparse->samplerate == 0) + flacparse->samplerate = 32000; + else if (flacparse->samplerate != 32000) + goto error; + } else if (samplerate == 9) { + if (flacparse->samplerate == 0) + flacparse->samplerate = 44100; + else if (flacparse->samplerate != 44100) + goto error; + } else if (samplerate == 10) { + if (flacparse->samplerate == 0) + flacparse->samplerate = 48000; + else if (flacparse->samplerate != 48000) + goto error; + } else if (samplerate == 11) { + if (flacparse->samplerate == 0) + flacparse->samplerate = 96000; + else if (flacparse->samplerate != 96000) + goto error; + } else if (samplerate == 12) { + if (!gst_bit_reader_get_bits_uint16 (&reader, &samplerate, 8)) + goto need_more_data; + samplerate *= 1000; + if (flacparse->samplerate == 0) + flacparse->samplerate = samplerate; + else if (flacparse->samplerate != samplerate) + goto error; + } else if (samplerate == 13) { + if (!gst_bit_reader_get_bits_uint16 (&reader, &samplerate, 16)) + goto need_more_data; + if (flacparse->samplerate == 0) + flacparse->samplerate = samplerate; + else if (flacparse->samplerate != samplerate) + goto error; + } else if (samplerate == 14) { + if (!gst_bit_reader_get_bits_uint16 (&reader, &samplerate, 16)) + goto need_more_data; + samplerate *= 10; + if (flacparse->samplerate == 0) + flacparse->samplerate = samplerate; + else if (flacparse->samplerate != samplerate) + goto error; + } + + /* skip crc-8 for the header */ + if (!gst_bit_reader_skip (&reader, 8)) + goto need_more_data; + + /* parse subframes, one subframe per channel */ + for (i = 0; i < flacparse->channels; i++) { + guint8 sf_type; + guint8 cur_bps; + + cur_bps = flacparse->bps; + + /* for mid/side, left/side, right/side the "difference" channel + * needs and additional bit */ + if (i == 0 && channel_assignment == 2) + cur_bps++; + else if (i == 1 && (channel_assignment == 1 || channel_assignment == 3)) + cur_bps++; + + /* must be 0 */ + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 1)) + goto need_more_data; + else if (tmp != 0) + goto error; + + /* sub frame type */ + if (!gst_bit_reader_get_bits_uint8 (&reader, &sf_type, 6)) + goto need_more_data; + else if (((sf_type & 0xfe) == 0x02) || + ((sf_type & 0xfc) == 0x04) || + ((sf_type & 0xf8) == 0x08 && (sf_type & 0x07) > 4) || + ((sf_type & 0xf0) == 0x10)) + goto error; + + /* wasted bits per sample, if 1 the value follows unary coded */ + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 1)) { + goto need_more_data; + } else if (tmp != 0) { + guint wasted = 1; + + tmp = 0; + while (tmp == 0) { + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 1)) + goto need_more_data; + else + wasted++; + } + cur_bps -= wasted; + } + + /* subframe type: constant */ + if (sf_type == 0x00) { + if (!gst_bit_reader_skip (&reader, cur_bps)) + goto need_more_data; + /* subframe type: verbatim */ + } else if (sf_type == 0x01) { + if (!gst_bit_reader_skip (&reader, cur_bps * flacparse->block_size)) + goto need_more_data; + /* subframe type: LPC or fixed */ + } else { + guint8 residual_type; + guint order = 0; + guint16 partition_order; + guint j; + + /* Skip warm-up samples for fixed subframe and calculate order */ + if ((sf_type & 0xf8) == 0x08) { + order = sf_type & 0x07; + + g_assert (order <= 4); + + if (!gst_bit_reader_skip (&reader, cur_bps * order)) + goto need_more_data; + /* Skip warm-up samples for LPC subframe, get parameters and calculate order */ + } else if ((sf_type & 0xe0) == 0x20) { + guint8 prec; + + order = (sf_type & 0x1f) + 1; + + /* warm-up samples */ + if (!gst_bit_reader_skip (&reader, cur_bps * order)) + goto need_more_data; + + /* LPC coefficient precision */ + if (!gst_bit_reader_get_bits_uint8 (&reader, &prec, 4)) + goto need_more_data; + else if (prec == 0x0f) + goto error; + prec++; + + /* LPC coefficient shift */ + if (!gst_bit_reader_skip (&reader, 5)) + goto need_more_data; + + /* LPC coefficients */ + if (!gst_bit_reader_skip (&reader, order * prec)) + goto need_more_data; + } else { + g_assert_not_reached (); + } + + /* residual type: 0 == rice, 1 == rice2 */ + if (!gst_bit_reader_get_bits_uint8 (&reader, &residual_type, 2)) + goto need_more_data; + + if (residual_type & 0x02) + goto error; + + /* partition order */ + if (!gst_bit_reader_get_bits_uint16 (&reader, &partition_order, 4)) + goto need_more_data; + + partition_order = 1 << partition_order; + + /* 2^partition_order partitions */ + for (j = 0; j < partition_order; j++) { + guint samples; + guint8 rice_parameter; + + /* calculate number of samples for the current partition */ + if (partition_order == 1) { + samples = flacparse->block_size - order; + } else if (j != 0) { + samples = flacparse->block_size / partition_order; + } else { + samples = flacparse->block_size / partition_order - order; + } + + /* rice parameter */ + if (!gst_bit_reader_get_bits_uint8 (&reader, &rice_parameter, + (residual_type == 0) ? 4 : 5)) + goto need_more_data; + + /* if rice parameter has all bits set the samples follow unencoded with the number of bits + * per sample in the following 5 bits */ + if ((residual_type == 0 && rice_parameter == 0x0f) + || (residual_type == 1 && rice_parameter == 0x1f)) { + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 5)) + goto need_more_data; + if (!gst_bit_reader_skip (&reader, tmp * samples)) + goto need_more_data; + } else { + guint k; + + /* read the rice encoded samples */ + for (k = 0; k < samples; k++) { + tmp = 0; + while (tmp == 0) + if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 1)) + goto need_more_data; + + if (!gst_bit_reader_skip (&reader, rice_parameter)) + goto need_more_data; + } + } + } + } + } + + /* zero padding to byte alignment */ + gst_bit_reader_skip_to_byte (&reader); + + /* Skip crc-16 for the complete frame */ + if (!gst_bit_reader_skip (&reader, 16)) + goto need_more_data; + + *framesize_ret = gst_bit_reader_get_pos (&reader) / 8; + + GST_DEBUG_OBJECT (flacparse, "Parsed frame at offset %" G_GUINT64_FORMAT ":\n" + "Frame size: %u\n" + "Block size: %u\n" + "Sample/Frame number: %" G_GUINT64_FORMAT, + flacparse->offset, *framesize_ret, + flacparse->block_size, flacparse->sample_number); + + return 0; + +need_more_data: + { + gint64 upstream_len = flacparse->upstream_length; + GstFormat fmt; + gboolean first_try = TRUE; + + GST_DEBUG_OBJECT (flacparse, "Need more data"); + + retry: + + fmt = GST_FORMAT_BYTES; + if (upstream_len != -1 || + (gst_pad_query_peer_duration (GST_BASE_PARSE_SINK_PAD (GST_BASE_PARSE + (flacparse)), &fmt, &upstream_len) + && fmt == GST_FORMAT_BYTES)) { + flacparse->upstream_length = upstream_len; + upstream_len -= GST_BUFFER_OFFSET (buffer); + + if (flacparse->max_framesize + && flacparse->max_framesize > flacparse->requested_frame_size + && flacparse->max_framesize <= upstream_len) { + flacparse->requested_frame_size = flacparse->max_framesize; + } else if (flacparse->requested_frame_size + 4096 <= upstream_len) { + flacparse->requested_frame_size += 4096; + } else if (flacparse->requested_frame_size <= upstream_len) { + flacparse->requested_frame_size = upstream_len; + } else { + if (first_try) { + upstream_len = -1; + first_try = FALSE; + goto retry; + } else { + goto eos; + } + } + GST_DEBUG_OBJECT (flacparse, "Requesting %u bytes", + flacparse->requested_frame_size); + return flacparse->requested_frame_size; + } else { + flacparse->requested_frame_size++; + GST_DEBUG_OBJECT (flacparse, "Requesting %u bytes", + flacparse->requested_frame_size); + return flacparse->requested_frame_size; + } + } + +need_streaminfo: + { + GST_ERROR_OBJECT (flacparse, "Need STREAMINFO"); + return -2; + } + +error: + { + GST_WARNING_OBJECT (flacparse, "Invalid frame"); + return -1; + } +eos: + { + GST_WARNING_OBJECT (flacparse, "EOS"); + return -1; + } +} + +static gboolean +gst_flac_parse_check_valid_frame (GstBaseParse * parse, GstBuffer * buffer, + guint * framesize, gint * skipsize) +{ + GstFlacParse *flacparse = GST_FLAC_PARSE (parse); + const guint8 *data = GST_BUFFER_DATA (buffer); + + if (flacparse->state == GST_FLAC_PARSE_STATE_INIT) { + if (memcmp (GST_BUFFER_DATA (buffer), "fLaC", 4) == 0) { + GST_DEBUG_OBJECT (flacparse, "fLaC marker found"); + *framesize = 4; + return TRUE; + } else if (data[0] == 0xff && (data[1] >> 2) == 0x3e) { + GST_DEBUG_OBJECT (flacparse, "Found headerless FLAC"); + /* Minimal size of a frame header */ + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), 16); + flacparse->requested_frame_size = 16; + flacparse->state = GST_FLAC_PARSE_STATE_GENERATE_HEADERS; + *skipsize = 0; + return FALSE; + } else { + GST_DEBUG_OBJECT (flacparse, "fLaC marker not found"); + return FALSE; + } + } else if (flacparse->state == GST_FLAC_PARSE_STATE_HEADERS) { + guint size = 4 + ((data[1] << 16) | (data[2] << 8) | (data[3])); + + GST_DEBUG_OBJECT (flacparse, "Found metadata block of size %u", size); + *framesize = size; + return TRUE; + } else { + if (data[0] == 0xff && (data[1] >> 2) == 0x3e) { + gint ret = 0; + + flacparse->offset = GST_BUFFER_OFFSET (buffer); + flacparse->blocking_strategy = 0; + flacparse->block_size = 0; + flacparse->sample_number = 0; + + GST_DEBUG_OBJECT (flacparse, "Found sync code"); + ret = gst_flac_parse_get_frame_size (flacparse, buffer, framesize); + if (ret == 0) { + return TRUE; + } else if (ret == -1) { + return FALSE; + } else if (ret == -2) { + GST_ELEMENT_ERROR (flacparse, STREAM, FORMAT, (NULL), + ("Need STREAMINFO for parsing")); + return FALSE; + } else if (ret > 0) { + *skipsize = 0; + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), ret); + flacparse->requested_frame_size = ret; + return FALSE; + } + } else { + GST_DEBUG_OBJECT (flacparse, "Sync code not found"); + return FALSE; + } + } + + return FALSE; +} + +static gboolean +gst_flac_parse_handle_streaminfo (GstFlacParse * flacparse, GstBuffer * buffer) +{ + GstBitReader reader = GST_BIT_READER_INIT_FROM_BUFFER (buffer); + + if (GST_BUFFER_SIZE (buffer) != 4 + 34) { + GST_ERROR_OBJECT (flacparse, "Invalid metablock size for STREAMINFO: %u", + GST_BUFFER_SIZE (buffer)); + return FALSE; + } + + /* Skip metadata block header */ + gst_bit_reader_skip (&reader, 32); + + if (!gst_bit_reader_get_bits_uint16 (&reader, &flacparse->min_blocksize, 16)) + goto error; + if (flacparse->min_blocksize < 16) { + GST_ERROR_OBJECT (flacparse, "Invalid minimum block size: %u", + flacparse->min_blocksize); + return FALSE; + } + + if (!gst_bit_reader_get_bits_uint16 (&reader, &flacparse->max_blocksize, 16)) + goto error; + if (flacparse->max_blocksize < 16) { + GST_ERROR_OBJECT (flacparse, "Invalid maximum block size: %u", + flacparse->max_blocksize); + return FALSE; + } + + if (!gst_bit_reader_get_bits_uint32 (&reader, &flacparse->min_framesize, 24)) + goto error; + if (!gst_bit_reader_get_bits_uint32 (&reader, &flacparse->max_framesize, 24)) + goto error; + + if (!gst_bit_reader_get_bits_uint32 (&reader, &flacparse->samplerate, 20)) + goto error; + if (flacparse->samplerate == 0) { + GST_ERROR_OBJECT (flacparse, "Invalid sample rate 0"); + return FALSE; + } + + if (!gst_bit_reader_get_bits_uint8 (&reader, &flacparse->channels, 3)) + goto error; + flacparse->channels++; + if (flacparse->channels > 8) { + GST_ERROR_OBJECT (flacparse, "Invalid number of channels %u", + flacparse->channels); + return FALSE; + } + + if (!gst_bit_reader_get_bits_uint8 (&reader, &flacparse->bps, 5)) + goto error; + flacparse->bps++; + + if (!gst_bit_reader_get_bits_uint64 (&reader, &flacparse->total_samples, 36)) + goto error; + if (flacparse->total_samples) + gst_base_parse_set_duration (GST_BASE_PARSE (flacparse), GST_FORMAT_TIME, + GST_FRAMES_TO_CLOCK_TIME (flacparse->total_samples, + flacparse->samplerate)); + + GST_DEBUG_OBJECT (flacparse, "STREAMINFO:\n" + "\tmin/max blocksize: %u/%u,\n" + "\tmin/max framesize: %u/%u,\n" + "\tsamplerate: %u,\n" + "\tchannels: %u,\n" + "\tbits per sample: %u,\n" + "\ttotal samples: %" G_GUINT64_FORMAT, + flacparse->min_blocksize, flacparse->max_blocksize, + flacparse->min_framesize, flacparse->max_framesize, + flacparse->samplerate, + flacparse->channels, flacparse->bps, flacparse->total_samples); + + return TRUE; + +error: + GST_ERROR_OBJECT (flacparse, "Failed to read data"); + return FALSE; +} + +static gboolean +gst_flac_parse_handle_vorbiscomment (GstFlacParse * flacparse, + GstBuffer * buffer) +{ + flacparse->tags = gst_tag_list_from_vorbiscomment_buffer (buffer, + GST_BUFFER_DATA (buffer), 4, NULL); + + if (flacparse->tags == NULL) { + GST_ERROR_OBJECT (flacparse, "Invalid vorbiscomment block"); + } else if (gst_tag_list_is_empty (flacparse->tags)) { + gst_tag_list_free (flacparse->tags); + flacparse->tags = NULL; + } + + return TRUE; +} + +static gboolean +gst_flac_parse_handle_picture (GstFlacParse * flacparse, GstBuffer * buffer) +{ + GstByteReader reader = GST_BYTE_READER_INIT_FROM_BUFFER (buffer); + const guint8 *data = GST_BUFFER_DATA (buffer); + guint32 img_len, img_type; + guint32 img_mimetype_len, img_description_len; + + if (!gst_byte_reader_get_uint32_be (&reader, &img_type)) + goto error; + + if (!gst_byte_reader_get_uint32_be (&reader, &img_mimetype_len)) + goto error; + if (!gst_byte_reader_skip (&reader, img_mimetype_len)) + goto error; + + if (!gst_byte_reader_get_uint32_be (&reader, &img_description_len)) + goto error; + if (!gst_byte_reader_skip (&reader, img_description_len)) + goto error; + + if (!gst_byte_reader_skip (&reader, 4 * 4)) + goto error; + + if (!gst_byte_reader_get_uint32_be (&reader, &img_len)) + goto error; + + if (!flacparse->tags) + flacparse->tags = gst_tag_list_new (); + + gst_tag_list_add_id3_image (flacparse->tags, + data + gst_byte_reader_get_pos (&reader), img_len, img_type); + + if (gst_tag_list_is_empty (flacparse->tags)) { + gst_tag_list_free (flacparse->tags); + flacparse->tags = NULL; + } + + return TRUE; + +error: + GST_ERROR_OBJECT (flacparse, "Error reading data"); + return FALSE; +} + +static void +_value_array_append_buffer (GValue * array_val, GstBuffer * buf) +{ + GValue value = { 0, }; + + g_value_init (&value, GST_TYPE_BUFFER); + /* copy buffer to avoid problems with circular refcounts */ + buf = gst_buffer_copy (buf); + /* again, for good measure */ + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS); + gst_value_set_buffer (&value, buf); + gst_buffer_unref (buf); + gst_value_array_append_value (array_val, &value); + g_value_unset (&value); +} + +static gboolean +gst_flac_parse_handle_headers (GstFlacParse * flacparse) +{ + GstBuffer *vorbiscomment = NULL; + GstBuffer *streaminfo = NULL; + GstBuffer *marker = NULL; + GValue array = { 0, }; + GstCaps *caps; + GList *l; + + caps = gst_caps_new_simple ("audio/x-flac", + "channels", G_TYPE_INT, flacparse->channels, + "rate", G_TYPE_INT, flacparse->samplerate, NULL); + + if (!flacparse->headers) + goto push_headers; + + for (l = flacparse->headers; l; l = l->next) { + GstBuffer *header = l->data; + const guint8 *data = GST_BUFFER_DATA (header); + guint size = GST_BUFFER_SIZE (header); + + GST_BUFFER_FLAG_SET (header, GST_BUFFER_FLAG_IN_CAPS); + + if (size == 4 && memcmp (data, "fLaC", 4) == 0) { + marker = header; + } else if (size > 1 && (data[0] & 0x7f) == 0) { + streaminfo = header; + } else if (size > 1 && (data[0] & 0x7f) == 4) { + vorbiscomment = header; + } + } + + if (marker == NULL || streaminfo == NULL || vorbiscomment == NULL) { + GST_WARNING_OBJECT (flacparse, + "missing header %p %p %p, muxing into container " + "formats may be broken", marker, streaminfo, vorbiscomment); + goto push_headers; + } + + g_value_init (&array, GST_TYPE_ARRAY); + + /* add marker including STREAMINFO header */ + { + GstBuffer *buf; + guint16 num; + + /* minus one for the marker that is merged with streaminfo here */ + num = g_list_length (flacparse->headers) - 1; + + buf = gst_buffer_new_and_alloc (13 + GST_BUFFER_SIZE (streaminfo)); + GST_BUFFER_DATA (buf)[0] = 0x7f; + memcpy (GST_BUFFER_DATA (buf) + 1, "FLAC", 4); + GST_BUFFER_DATA (buf)[5] = 0x01; /* mapping version major */ + GST_BUFFER_DATA (buf)[6] = 0x00; /* mapping version minor */ + GST_BUFFER_DATA (buf)[7] = (num & 0xFF00) >> 8; + GST_BUFFER_DATA (buf)[8] = (num & 0x00FF) >> 0; + memcpy (GST_BUFFER_DATA (buf) + 9, "fLaC", 4); + memcpy (GST_BUFFER_DATA (buf) + 13, GST_BUFFER_DATA (streaminfo), + GST_BUFFER_SIZE (streaminfo)); + _value_array_append_buffer (&array, buf); + gst_buffer_unref (buf); + } + + /* add VORBISCOMMENT header */ + _value_array_append_buffer (&array, vorbiscomment); + + /* add other headers, if there are any */ + for (l = flacparse->headers; l; l = l->next) { + if (GST_BUFFER_CAST (l->data) != marker && + GST_BUFFER_CAST (l->data) != streaminfo && + GST_BUFFER_CAST (l->data) != vorbiscomment) { + _value_array_append_buffer (&array, GST_BUFFER_CAST (l->data)); + } + } + + gst_structure_set_value (gst_caps_get_structure (caps, 0), + "streamheader", &array); + g_value_unset (&array); + +push_headers: + + gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (GST_BASE_PARSE (flacparse)), caps); + gst_caps_unref (caps); + + /* push header buffers; update caps, so when we push the first buffer the + * negotiated caps will change to caps that include the streamheader field */ + for (l = flacparse->headers; l != NULL; l = l->next) { + GstBuffer *buf = GST_BUFFER (l->data); + GstFlowReturn ret; + + l->data = NULL; + gst_buffer_set_caps (buf, + GST_PAD_CAPS (GST_BASE_PARSE_SRC_PAD (GST_BASE_PARSE (flacparse)))); + + ret = gst_base_parse_push_buffer (GST_BASE_PARSE (flacparse), buf); + if (ret != GST_FLOW_OK) + return FALSE; + } + g_list_free (flacparse->headers); + flacparse->headers = NULL; + + /* Push tags */ + if (flacparse->tags) + gst_element_found_tags (GST_ELEMENT (flacparse), + gst_tag_list_copy (flacparse->tags)); + + return TRUE; +} + +static gboolean +gst_flac_parse_generate_headers (GstFlacParse * flacparse) +{ + GstBuffer *marker, *streaminfo, *vorbiscomment; + guint8 *data; + + marker = gst_buffer_new_and_alloc (4); + memcpy (GST_BUFFER_DATA (marker), "fLaC", 4); + GST_BUFFER_TIMESTAMP (marker) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (marker) = GST_CLOCK_TIME_NONE; + GST_BUFFER_OFFSET (marker) = 0; + GST_BUFFER_OFFSET_END (marker) = 0; + flacparse->headers = g_list_append (flacparse->headers, marker); + + streaminfo = gst_buffer_new_and_alloc (4 + 34); + data = GST_BUFFER_DATA (streaminfo); + memset (data, 0, 4 + 34); + + /* metadata block header */ + data[0] = 0x00; /* is_last = 0; type = 0; */ + data[1] = 0x00; /* length = 34; */ + data[2] = 0x00; + data[3] = 0x22; + + /* streaminfo */ + + data[4] = (flacparse->block_size >> 8) & 0xff; /* min blocksize = blocksize; */ + data[5] = (flacparse->block_size) & 0xff; + data[6] = (flacparse->block_size >> 8) & 0xff; /* max blocksize = blocksize; */ + data[7] = (flacparse->block_size) & 0xff; + + data[8] = 0x00; /* min framesize = 0; */ + data[9] = 0x00; + data[10] = 0x00; + data[11] = 0x00; /* max framesize = 0; */ + data[12] = 0x00; + data[13] = 0x00; + + data[14] = (flacparse->samplerate >> 12) & 0xff; + data[15] = (flacparse->samplerate >> 4) & 0xff; + data[16] = (flacparse->samplerate >> 0) & 0xf0; + + data[16] |= (flacparse->channels - 1) << 1; + + data[16] |= ((flacparse->bps - 1) >> 4) & 0x01; + data[17] = (((flacparse->bps - 1)) & 0x0f) << 4; + + { + gint64 duration; + GstFormat fmt = GST_FORMAT_TIME; + + if (gst_pad_query_peer_duration (GST_BASE_PARSE_SINK_PAD (GST_BASE_PARSE + (flacparse)), &fmt, &duration) && fmt == GST_FORMAT_TIME) { + duration = GST_CLOCK_TIME_TO_FRAMES (duration, flacparse->samplerate); + + data[17] |= (duration >> 32) & 0xff; + data[18] |= (duration >> 24) & 0xff; + data[19] |= (duration >> 16) & 0xff; + data[20] |= (duration >> 8) & 0xff; + data[21] |= (duration >> 0) & 0xff; + } + } + /* MD5 = 0; */ + + GST_BUFFER_TIMESTAMP (streaminfo) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (streaminfo) = GST_CLOCK_TIME_NONE; + GST_BUFFER_OFFSET (streaminfo) = 0; + GST_BUFFER_OFFSET_END (streaminfo) = 0; + flacparse->headers = g_list_append (flacparse->headers, streaminfo); + + /* empty vorbiscomment */ + { + GstTagList *taglist = gst_tag_list_new (); + guchar header[4]; + guint size; + + header[0] = 0x84; /* is_last = 1; type = 4; */ + + vorbiscomment = + gst_tag_list_to_vorbiscomment_buffer (taglist, header, sizeof (header), + NULL); + gst_tag_list_free (taglist); + + /* Get rid of framing bit */ + if (GST_BUFFER_DATA (vorbiscomment)[GST_BUFFER_SIZE (vorbiscomment) - 1] == + 1) { + GstBuffer *sub; + + sub = + gst_buffer_create_sub (vorbiscomment, 0, + GST_BUFFER_SIZE (vorbiscomment) - 1); + gst_buffer_unref (vorbiscomment); + vorbiscomment = sub; + } + + size = GST_BUFFER_SIZE (vorbiscomment) - 4; + GST_BUFFER_DATA (vorbiscomment)[1] = ((size & 0xFF0000) >> 16); + GST_BUFFER_DATA (vorbiscomment)[2] = ((size & 0x00FF00) >> 8); + GST_BUFFER_DATA (vorbiscomment)[3] = (size & 0x0000FF); + + GST_BUFFER_TIMESTAMP (vorbiscomment) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (vorbiscomment) = GST_CLOCK_TIME_NONE; + GST_BUFFER_OFFSET (vorbiscomment) = 0; + GST_BUFFER_OFFSET_END (vorbiscomment) = 0; + flacparse->headers = g_list_append (flacparse->headers, vorbiscomment); + } + + return TRUE; +} + +static GstFlowReturn +gst_flac_parse_parse_frame (GstBaseParse * parse, GstBuffer * buffer) +{ + GstFlacParse *flacparse = GST_FLAC_PARSE (parse); + const guint8 *data = GST_BUFFER_DATA (buffer); + + if (flacparse->state == GST_FLAC_PARSE_STATE_INIT) { + GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_OFFSET (buffer) = 0; + GST_BUFFER_OFFSET_END (buffer) = 0; + + /* 32 bits metadata block */ + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), 4); + flacparse->state = GST_FLAC_PARSE_STATE_HEADERS; + + flacparse->headers = + g_list_append (flacparse->headers, gst_buffer_ref (buffer)); + + return GST_BASE_PARSE_FLOW_DROPPED; + } else if (flacparse->state == GST_FLAC_PARSE_STATE_HEADERS) { + gboolean is_last = ((data[0] & 0x80) == 0x80); + guint type = (data[0] & 0x7F); + + if (type == 127) { + GST_WARNING_OBJECT (flacparse, "Invalid metadata block type"); + return GST_BASE_PARSE_FLOW_DROPPED; + } + + GST_DEBUG_OBJECT (flacparse, "Handling metadata block of type %u", type); + + switch (type) { + case 0: /* STREAMINFO */ + if (!gst_flac_parse_handle_streaminfo (flacparse, buffer)) + return GST_FLOW_ERROR; + break; + case 3: /* SEEKTABLE */ + /* TODO: handle seektables */ + break; + case 4: /* VORBIS_COMMENT */ + if (!gst_flac_parse_handle_vorbiscomment (flacparse, buffer)) + return GST_FLOW_ERROR; + break; + case 6: /* PICTURE */ + if (!gst_flac_parse_handle_picture (flacparse, buffer)) + return GST_FLOW_ERROR; + break; + case 1: /* PADDING */ + case 2: /* APPLICATION */ + case 5: /* CUESHEET */ + default: /* RESERVED */ + break; + } + + GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_OFFSET (buffer) = 0; + GST_BUFFER_OFFSET_END (buffer) = 0; + + if (is_last) { + flacparse->headers = + g_list_append (flacparse->headers, gst_buffer_ref (buffer)); + + if (!gst_flac_parse_handle_headers (flacparse)) + return GST_FLOW_ERROR; + + /* Minimal size of a frame header */ + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), MAX (16, + flacparse->min_framesize)); + flacparse->requested_frame_size = MAX (16, flacparse->min_framesize); + flacparse->state = GST_FLAC_PARSE_STATE_DATA; + + /* DROPPED because we pushed all headers manually already */ + return GST_BASE_PARSE_FLOW_DROPPED; + } else { + flacparse->headers = + g_list_append (flacparse->headers, gst_buffer_ref (buffer)); + return GST_BASE_PARSE_FLOW_DROPPED; + } + } else { + if (flacparse->offset != GST_BUFFER_OFFSET (buffer)) { + gint ret; + guint framesize; + + flacparse->offset = GST_BUFFER_OFFSET (buffer); + ret = gst_flac_parse_get_frame_size (flacparse, buffer, &framesize); + if (ret != 0) { + GST_ERROR_OBJECT (flacparse, + "Baseclass didn't provide a complete frame"); + return GST_FLOW_ERROR; + } + } + + if (flacparse->block_size == 0) { + GST_ERROR_OBJECT (flacparse, "Unparsed frame"); + return GST_FLOW_ERROR; + } + + if (flacparse->state == GST_FLAC_PARSE_STATE_GENERATE_HEADERS) { + if (flacparse->blocking_strategy == 1) { + GST_WARNING_OBJECT (flacparse, + "Generating headers for variable blocksize streams not supported"); + + if (!gst_flac_parse_handle_headers (flacparse)) + return GST_FLOW_ERROR; + } else { + GST_DEBUG_OBJECT (flacparse, "Generating headers"); + + if (!gst_flac_parse_generate_headers (flacparse)) + return GST_FLOW_ERROR; + + if (!gst_flac_parse_handle_headers (flacparse)) + return GST_FLOW_ERROR; + } + flacparse->state = GST_FLAC_PARSE_STATE_DATA; + } + + if (flacparse->blocking_strategy == 0) { + GST_BUFFER_TIMESTAMP (buffer) = + gst_util_uint64_scale (flacparse->sample_number, + flacparse->block_size * GST_SECOND, flacparse->samplerate); + } else { + GST_BUFFER_TIMESTAMP (buffer) = + gst_util_uint64_scale (flacparse->sample_number, GST_SECOND, + flacparse->samplerate); + } + GST_BUFFER_DURATION (buffer) = + GST_FRAMES_TO_CLOCK_TIME (flacparse->block_size, flacparse->samplerate); + + /* Minimal size of a frame header */ + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), MAX (16, + flacparse->min_framesize)); + flacparse->requested_frame_size = MAX (16, flacparse->min_framesize); + + flacparse->offset = -1; + flacparse->blocking_strategy = 0; + flacparse->block_size = 0; + flacparse->sample_number = 0; + return GST_FLOW_OK; + } +} diff --git a/gst/flacparse/gstflacparse.h b/gst/flacparse/gstflacparse.h new file mode 100644 index 00000000..66171dcf --- /dev/null +++ b/gst/flacparse/gstflacparse.h @@ -0,0 +1,87 @@ +/* GStreamer + * + * 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. + */ + +#ifndef __GST_FLAC_PARSE_H__ +#define __GST_FLAC_PARSE_H__ + +#include <gst/gst.h> +#include "gstbaseparse.h" + +G_BEGIN_DECLS + +#define GST_TYPE_FLAC_PARSE (gst_base_parse_get_type()) +#define GST_FLAC_PARSE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FLAC_PARSE,GstFlacParse)) +#define GST_FLAC_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FLAC_PARSE,GstFlacParseClass)) +#define GST_FLAC_PARSE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_FLAC_PARSE,GstFlacParseClass)) +#define GST_IS_FLAC_PARSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FLAC_PARSE)) +#define GST_IS_FLAC_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_FLAC_PARSE)) +#define GST_FLAC_PARSE_CAST(obj) ((GstFlacParse *)(obj)) + +typedef struct _GstFlacParse GstFlacParse; +typedef struct _GstFlacParseClass GstFlacParseClass; + +typedef enum { + GST_FLAC_PARSE_STATE_INIT, + GST_FLAC_PARSE_STATE_HEADERS, + GST_FLAC_PARSE_STATE_GENERATE_HEADERS, + GST_FLAC_PARSE_STATE_DATA +} GstFlacParseState; + +typedef struct { + guint8 type; +} GstFlacParseSubFrame; + +struct _GstFlacParse { + GstBaseParse parent; + + GstFlacParseState state; + + gint64 upstream_length; + + /* STREAMINFO content */ + guint16 min_blocksize, max_blocksize; + guint32 min_framesize, max_framesize; + guint32 samplerate; + guint8 channels; + guint8 bps; + guint64 total_samples; + + guint requested_frame_size; + + /* Current frame */ + guint64 offset; + guint8 blocking_strategy; + guint16 block_size; + guint64 sample_number; + + GstTagList *tags; + + GList *headers; +}; + +struct _GstFlacParseClass { + GstBaseParseClass parent_class; +}; + +GType gst_flac_parse_get_type (void); + +G_END_DECLS + +#endif /* __GST_FLAC_PARSE_H__ */ |