diff -r 567bb019e3e3 -r 7e817e7e631c gst_plugins_base/ext/ogg/gstoggdemux.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gst_plugins_base/ext/ogg/gstoggdemux.c Wed Sep 01 12:16:41 2010 +0100 @@ -0,0 +1,3323 @@ +/* GStreamer + * Copyright (C) 2004 Wim Taymans + * + * gstoggdemux.c: ogg stream demuxer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-oggdemux + * @short_description: a demuxer for ogg files + * + * + * + * This element demuxes ogg files into their encoded audio and video components. + * + * Example pipelines + * + * + * gst-launch -v filesrc location=test.ogg ! oggdemux ! vorbisdec ! audioconvert ! alsasink + * + * Decodes the vorbis audio stored inside an ogg container. + * + * + * + * Last reviewed on 2006-12-30 (0.10.5) + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include + +#include "gstoggdemux.h" + +static const GstElementDetails gst_ogg_demux_details = +GST_ELEMENT_DETAILS ("Ogg demuxer", + "Codec/Demuxer", + "demux ogg streams (info about ogg: http://xiph.org)", + "Wim Taymans "); + +#define CHUNKSIZE (8500) /* this is out of vorbisfile */ +#define SKELETON_FISHEAD_SIZE 64 +#define SKELETON_FISBONE_MIN_SIZE 52 + +#define GST_FLOW_LIMIT GST_FLOW_CUSTOM_ERROR + +GST_DEBUG_CATEGORY_STATIC (gst_ogg_demux_debug); +GST_DEBUG_CATEGORY_STATIC (gst_ogg_demux_setup_debug); +#define GST_CAT_DEFAULT gst_ogg_demux_debug + +static ogg_page * +gst_ogg_page_copy (ogg_page * page) +{ + ogg_page *p = g_new0 (ogg_page, 1); + + /* make a copy of the page */ + p->header = g_memdup (page->header, page->header_len); + p->header_len = page->header_len; + p->body = g_memdup (page->body, page->body_len); + p->body_len = page->body_len; + + return p; +} + +static void +gst_ogg_page_free (ogg_page * page) +{ + g_free (page->header); + g_free (page->body); + g_free (page); +} + +static GstStaticPadTemplate internaltemplate = +GST_STATIC_PAD_TEMPLATE ("internal", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static gboolean gst_ogg_demux_collect_chain_info (GstOggDemux * ogg, + GstOggChain * chain); +static gboolean gst_ogg_demux_activate_chain (GstOggDemux * ogg, + GstOggChain * chain, GstEvent * event); +static void gst_ogg_chain_mark_discont (GstOggChain * chain); + +static gboolean gst_ogg_demux_perform_seek (GstOggDemux * ogg, + GstEvent * event); +static gboolean gst_ogg_demux_receive_event (GstElement * element, + GstEvent * event); + +static void gst_ogg_pad_class_init (GstOggPadClass * klass); +static void gst_ogg_pad_init (GstOggPad * pad); +static void gst_ogg_pad_dispose (GObject * object); +static void gst_ogg_pad_finalize (GObject * object); + +#if 0 +static const GstFormat *gst_ogg_pad_formats (GstPad * pad); +static const GstEventMask *gst_ogg_pad_event_masks (GstPad * pad); +#endif +static const GstQueryType *gst_ogg_pad_query_types (GstPad * pad); +static gboolean gst_ogg_pad_src_query (GstPad * pad, GstQuery * query); +static gboolean gst_ogg_pad_event (GstPad * pad, GstEvent * event); +static GstCaps *gst_ogg_pad_getcaps (GstPad * pad); +static GstOggPad *gst_ogg_chain_get_stream (GstOggChain * chain, + glong serialno); + +static gboolean gst_ogg_pad_query_convert (GstOggPad * pad, + GstFormat src_format, gint64 src_val, + GstFormat * dest_format, gint64 * dest_val); +static GstClockTime gst_annodex_granule_to_time (gint64 granulepos, + gint64 granulerate_n, gint64 granulerate_d, guint8 granuleshift); + +static GstFlowReturn gst_ogg_demux_combine_flows (GstOggDemux * ogg, + GstOggPad * pad, GstFlowReturn ret); + +static GstPadClass *ogg_pad_parent_class = NULL; + +static GType +gst_ogg_pad_get_type (void) +{ + static GType ogg_pad_type = 0; + + if (!ogg_pad_type) { + static const GTypeInfo ogg_pad_info = { + sizeof (GstOggPadClass), + NULL, + NULL, + (GClassInitFunc) gst_ogg_pad_class_init, + NULL, + NULL, + sizeof (GstOggPad), + 0, + (GInstanceInitFunc) gst_ogg_pad_init, + }; + + ogg_pad_type = + g_type_register_static (GST_TYPE_PAD, "GstOggPad", &ogg_pad_info, 0); + } + return ogg_pad_type; +} + +static void +gst_ogg_pad_class_init (GstOggPadClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + + ogg_pad_parent_class = g_type_class_peek_parent (klass); + + gobject_class->dispose = gst_ogg_pad_dispose; + gobject_class->finalize = gst_ogg_pad_finalize; +} + +static void +gst_ogg_pad_init (GstOggPad * pad) +{ + gst_pad_set_event_function (GST_PAD (pad), + GST_DEBUG_FUNCPTR (gst_ogg_pad_event)); + gst_pad_set_getcaps_function (GST_PAD (pad), + GST_DEBUG_FUNCPTR (gst_ogg_pad_getcaps)); + gst_pad_set_query_type_function (GST_PAD (pad), + GST_DEBUG_FUNCPTR (gst_ogg_pad_query_types)); + gst_pad_set_query_function (GST_PAD (pad), + GST_DEBUG_FUNCPTR (gst_ogg_pad_src_query)); + + pad->mode = GST_OGG_PAD_MODE_INIT; + + pad->first_granule = -1; + pad->current_granule = -1; + + pad->start_time = GST_CLOCK_TIME_NONE; + pad->first_time = GST_CLOCK_TIME_NONE; + + pad->have_type = FALSE; + pad->continued = NULL; + pad->headers = NULL; +} + +static void +gst_ogg_pad_dispose (GObject * object) +{ + GstOggPad *pad = GST_OGG_PAD (object); + GstPad **elem_pad_p; + GstElement **element_p; + GstPad **elem_out_p; + + if (pad->element) + gst_element_set_state (pad->element, GST_STATE_NULL); + + elem_pad_p = &pad->elem_pad; + element_p = &pad->element; + elem_out_p = &pad->elem_out; + gst_object_replace ((GstObject **) elem_pad_p, NULL); + gst_object_replace ((GstObject **) element_p, NULL); + gst_object_replace ((GstObject **) elem_out_p, NULL); + + pad->chain = NULL; + pad->ogg = NULL; + + g_list_foreach (pad->headers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (pad->headers); + pad->headers = NULL; + + /* clear continued pages */ + g_list_foreach (pad->continued, (GFunc) gst_ogg_page_free, NULL); + g_list_free (pad->continued); + pad->continued = NULL; + + ogg_stream_reset (&pad->stream); + + G_OBJECT_CLASS (ogg_pad_parent_class)->dispose (object); +} + +static void +gst_ogg_pad_finalize (GObject * object) +{ + GstOggPad *pad = GST_OGG_PAD (object); + + ogg_stream_clear (&pad->stream); + + G_OBJECT_CLASS (ogg_pad_parent_class)->finalize (object); +} + +#if 0 +static const GstFormat * +gst_ogg_pad_formats (GstPad * pad) +{ + static GstFormat src_formats[] = { + GST_FORMAT_DEFAULT, /* time */ + GST_FORMAT_TIME, /* granulepos */ + 0 + }; + static GstFormat sink_formats[] = { + GST_FORMAT_BYTES, + GST_FORMAT_DEFAULT, /* bytes */ + 0 + }; + + return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats); +} +#endif + +#if 0 +static const GstEventMask * +gst_ogg_pad_event_masks (GstPad * pad) +{ + static const GstEventMask src_event_masks[] = { + {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_FLUSH}, + {0,} + }; + + return src_event_masks; +} +#endif + +static const GstQueryType * +gst_ogg_pad_query_types (GstPad * pad) +{ + static const GstQueryType query_types[] = { + GST_QUERY_DURATION, + GST_QUERY_SEEKING, + 0 + }; + + return query_types; +} + +static GstCaps * +gst_ogg_pad_getcaps (GstPad * pad) +{ + return gst_caps_ref (GST_PAD_CAPS (pad)); +} + +static gboolean +gst_ogg_pad_src_query (GstPad * pad, GstQuery * query) +{ + gboolean res = TRUE; + GstOggDemux *ogg; + GstOggPad *cur; + + ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad)); + cur = GST_OGG_PAD (pad); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_DURATION: + { + GstFormat format; + + gst_query_parse_duration (query, &format, NULL); + /* can only get position in time */ + if (format != GST_FORMAT_TIME) + goto wrong_format; + + /* can only return the total time position */ + /* FIXME, return time for this specific stream */ + gst_query_set_duration (query, GST_FORMAT_TIME, ogg->total_time); + break; + } + case GST_QUERY_SEEKING: + { + GstFormat format; + + gst_query_parse_seeking (query, &format, NULL, NULL, NULL); + if (format == GST_FORMAT_TIME) { + gst_query_set_seeking (query, GST_FORMAT_TIME, ogg->seekable, + 0, ogg->total_time); + } else { + res = FALSE; + } + break; + } + + default: + res = gst_pad_query_default (pad, query); + break; + } +done: + gst_object_unref (ogg); + + return res; + + /* ERRORS */ +wrong_format: + { + GST_DEBUG_OBJECT (ogg, "only query duration on TIME is supported"); + res = FALSE; + goto done; + } +} + +static gboolean +gst_ogg_demux_receive_event (GstElement * element, GstEvent * event) +{ + gboolean res; + GstOggDemux *ogg; + + ogg = GST_OGG_DEMUX (element); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + /* can't seek if we are not seekable, FIXME could pass the + * seek query upstream after converting it to bytes using + * the average bitrate of the stream. */ + if (!ogg->seekable) { + GST_DEBUG_OBJECT (ogg, "seek on non seekable stream"); + goto error; + } + + /* now do the seek */ + res = gst_ogg_demux_perform_seek (ogg, event); + gst_event_unref (event); + break; + default: + GST_DEBUG_OBJECT (ogg, "We only handle seek events here"); + goto error; + } + + return res; + + /* ERRORS */ +error: + { + GST_DEBUG_OBJECT (ogg, "error handling event"); + gst_event_unref (event); + return FALSE; + } +} + +static gboolean +gst_ogg_pad_event (GstPad * pad, GstEvent * event) +{ + gboolean res; + GstOggDemux *ogg; + GstOggPad *cur; + + ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad)); + cur = GST_OGG_PAD (pad); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + /* can't seek if we are not seekable, FIXME could pass the + * seek query upstream after converting it to bytes using + * the average bitrate of the stream. */ + if (!ogg->seekable) { + GST_DEBUG_OBJECT (ogg, "seek on non seekable stream"); + goto error; + } + + /* now do the seek */ + res = gst_ogg_demux_perform_seek (ogg, event); + gst_event_unref (event); + break; + default: + res = gst_pad_event_default (pad, event); + break; + } +done: + gst_object_unref (ogg); + + return res; + + /* ERRORS */ +error: + { + GST_DEBUG_OBJECT (ogg, "error handling event"); + gst_event_unref (event); + res = FALSE; + goto done; + } +} + +static void +gst_ogg_pad_reset (GstOggPad * pad) +{ + ogg_stream_reset (&pad->stream); + + /* clear continued pages */ + g_list_foreach (pad->continued, (GFunc) gst_ogg_page_free, NULL); + g_list_free (pad->continued); + pad->continued = NULL; + + pad->last_ret = GST_FLOW_OK; +} + +/* the filter function for selecting the elements we can use in + * autoplugging */ +static gboolean +gst_ogg_demux_factory_filter (GstPluginFeature * feature, GstCaps * caps) +{ + guint rank; + const gchar *klass; + + /* we only care about element factories */ + if (!GST_IS_ELEMENT_FACTORY (feature)) + return FALSE; + + klass = gst_element_factory_get_klass (GST_ELEMENT_FACTORY (feature)); + /* only demuxers and decoders can play */ + if (strstr (klass, "Demux") == NULL && + strstr (klass, "Decoder") == NULL && strstr (klass, "Parse") == NULL) { + return FALSE; + } + + /* only select elements with autoplugging rank */ + rank = gst_plugin_feature_get_rank (feature); + if (rank < GST_RANK_MARGINAL) + return FALSE; + + GST_DEBUG ("checking factory %s", GST_PLUGIN_FEATURE_NAME (feature)); + /* now see if it is compatible with the caps */ + { + GstElementFactory *factory = GST_ELEMENT_FACTORY (feature); + const GList *templates; + GList *walk; + + /* get the templates from the element factory */ + templates = gst_element_factory_get_static_pad_templates (factory); + + for (walk = (GList *) templates; walk; walk = g_list_next (walk)) { + GstStaticPadTemplate *templ = walk->data; + + /* we only care about the sink templates */ + if (templ->direction == GST_PAD_SINK) { + GstCaps *intersect; + GstCaps *scaps; + gboolean empty; + + /* try to intersect the caps with the caps of the template */ + scaps = gst_static_caps_get (&templ->static_caps); + intersect = gst_caps_intersect (caps, scaps); + gst_caps_unref (scaps); + + empty = gst_caps_is_empty (intersect); + gst_caps_unref (intersect); + + /* check if the intersection is empty */ + if (!empty) { + /* non empty intersection, we can use this element */ + goto found; + } + } + } + } + return FALSE; + +found: + return TRUE; +} + +/* function used to sort element features */ +static gint +compare_ranks (GstPluginFeature * f1, GstPluginFeature * f2) +{ + gint diff; + + diff = gst_plugin_feature_get_rank (f2) - gst_plugin_feature_get_rank (f1); + if (diff != 0) + return diff; + return strcmp (gst_plugin_feature_get_name (f2), + gst_plugin_feature_get_name (f1)); +} + +/* called when the skeleton fishead is found. Caller ensures the packet is + * precisely the correct size; we don't re-check this here. */ +static void +gst_ogg_pad_parse_skeleton_fishead (GstOggPad * pad, ogg_packet * packet) +{ + GstOggDemux *ogg = pad->ogg; + guint8 *data = packet->packet; + guint16 major, minor; + gint64 prestime_n, prestime_d; + gint64 basetime_n, basetime_d; + + /* skip "fishead\0" */ + data += 8; + major = GST_READ_UINT16_LE (data); + data += 2; + minor = GST_READ_UINT16_LE (data); + data += 2; + prestime_n = (gint64) GST_READ_UINT64_LE (data); + data += 8; + prestime_d = (gint64) GST_READ_UINT64_LE (data); + data += 8; + basetime_n = (gint64) GST_READ_UINT64_LE (data); + data += 8; + basetime_d = (gint64) GST_READ_UINT64_LE (data); + data += 8; + + ogg->basetime = gst_util_uint64_scale (GST_SECOND, basetime_n, basetime_d); + ogg->have_fishead = TRUE; + pad->is_skeleton = TRUE; + pad->start_time = GST_CLOCK_TIME_NONE; + pad->first_granule = -1; + pad->first_time = GST_CLOCK_TIME_NONE; + GST_INFO_OBJECT (ogg, "skeleton fishead parsed (basetime: %" + GST_TIME_FORMAT ")", GST_TIME_ARGS (ogg->basetime)); +} + +/* function called when a skeleton fisbone is found. Caller ensures that + * the packet length is sufficient */ +static void +gst_ogg_pad_parse_skeleton_fisbone (GstOggPad * pad, ogg_packet * packet) +{ + GstOggPad *fisbone_pad; + gint64 start_granule; + guint32 serialno; + guint8 *data = packet->packet; + + /* skip "fisbone\0" */ + data += 8; + /* skip headers offset */ + data += 4; + serialno = GST_READ_UINT32_LE (data); + + fisbone_pad = gst_ogg_chain_get_stream (pad->chain, serialno); + if (fisbone_pad) { + if (fisbone_pad->have_fisbone) + /* already parsed */ + return; + + fisbone_pad->have_fisbone = TRUE; + + data += 4; + /* skip number of headers */ + data += 4; + fisbone_pad->granulerate_n = GST_READ_UINT64_LE (data); + data += 8; + fisbone_pad->granulerate_d = GST_READ_UINT64_LE (data); + data += 8; + start_granule = GST_READ_UINT64_LE (data); + data += 8; + fisbone_pad->preroll = GST_READ_UINT32_LE (data); + data += 4; + fisbone_pad->granuleshift = GST_READ_UINT8 (data); + data += 1; + /* padding */ + data += 3; + + fisbone_pad->start_time = gst_annodex_granule_to_time (start_granule, + fisbone_pad->granulerate_n, fisbone_pad->granulerate_d, + fisbone_pad->granuleshift); + + GST_INFO_OBJECT (pad->ogg, "skeleton fisbone parsed " + "(serialno: %08x start time: %" GST_TIME_FORMAT + " granulerate_n: %" G_GINT64_FORMAT " granulerate_d: %" G_GINT64_FORMAT + " preroll: %" G_GUINT32_FORMAT " granuleshift: %d)", + serialno, GST_TIME_ARGS (fisbone_pad->start_time), + fisbone_pad->granulerate_n, fisbone_pad->granulerate_d, + fisbone_pad->preroll, fisbone_pad->granuleshift); + } else { + GST_WARNING_OBJECT (pad->ogg, + "found skeleton fisbone for an unknown stream %" G_GUINT32_FORMAT, + serialno); + } +} + +/* function called to convert a granulepos to a timestamp */ +static gboolean +gst_ogg_pad_query_convert (GstOggPad * pad, GstFormat src_format, + gint64 src_val, GstFormat * dest_format, gint64 * dest_val) +{ + gboolean res; + + if (src_val == -1) { + *dest_val = -1; + return TRUE; + } + + if (!pad->have_fisbone && pad->elem_pad == NULL) + return FALSE; + + switch (src_format) { + case GST_FORMAT_DEFAULT: + if (pad->have_fisbone && *dest_format == GST_FORMAT_TIME) { + *dest_val = gst_annodex_granule_to_time (src_val, + pad->granulerate_n, pad->granulerate_d, pad->granuleshift); + + res = TRUE; + } else { + if (pad->elem_pad == NULL) + res = FALSE; + else + res = gst_pad_query_convert (pad->elem_pad, src_format, src_val, + dest_format, dest_val); + } + + break; + default: + if (pad->elem_pad == NULL) + res = FALSE; + else + res = gst_pad_query_convert (pad->elem_pad, src_format, src_val, + dest_format, dest_val); + } + + return res; +} + +/* function called by the internal decoder elements when it outputs + * a buffer. We use it to get the first timestamp of the stream + */ +static GstFlowReturn +gst_ogg_pad_internal_chain (GstPad * pad, GstBuffer * buffer) +{ + GstOggPad *oggpad; + GstOggDemux *ogg; + GstClockTime timestamp; + + oggpad = gst_pad_get_element_private (pad); + ogg = GST_OGG_DEMUX (oggpad->ogg); + + timestamp = GST_BUFFER_TIMESTAMP (buffer); + GST_DEBUG_OBJECT (oggpad, "received buffer from internal pad, TS=%" + GST_TIME_FORMAT "=%" G_GINT64_FORMAT, GST_TIME_ARGS (timestamp), + timestamp); + + if (oggpad->start_time == GST_CLOCK_TIME_NONE) { + oggpad->start_time = timestamp; + GST_DEBUG_OBJECT (oggpad, "new start time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (timestamp)); + } + + gst_buffer_unref (buffer); + + return GST_FLOW_OK; +} + +static void +internal_element_pad_added_cb (GstElement * element, GstPad * pad, + GstOggPad * oggpad) +{ + if (GST_PAD_DIRECTION (pad) == GST_PAD_SRC) { + if (!(gst_pad_link (pad, oggpad->elem_out) == GST_PAD_LINK_OK)) { + GST_ERROR ("Really couldn't find a valid pad"); + } + oggpad->dynamic = FALSE; + g_signal_handler_disconnect (element, oggpad->padaddedid); + oggpad->padaddedid = 0; + } +} + +/* runs typefind on the packet, which is assumed to be the first + * packet in the stream. + * + * Based on the type returned from the typefind function, an element + * is created to help in conversion between granulepos and timestamps + * so that we can do decent seeking. + */ +static gboolean +gst_ogg_pad_typefind (GstOggPad * pad, ogg_packet * packet) +{ + GstBuffer *buf; + GstCaps *caps; + GstElement *element = NULL; + +#ifndef GST_DISABLE_GST_DEBUG + GstOggDemux *ogg = pad->ogg; +#endif + + if (GST_PAD_CAPS (pad) != NULL) + return TRUE; + + /* The ogg spec defines that the first packet of an ogg stream must identify + * the stream. Therefore ogg can use a simplified approach to typefinding + * and only needs to check the first packet */ + buf = gst_buffer_new (); + GST_BUFFER_DATA (buf) = packet->packet; + GST_BUFFER_SIZE (buf) = packet->bytes; + GST_BUFFER_OFFSET (buf) = 0; + + caps = gst_type_find_helper_for_buffer (GST_OBJECT (pad), buf, NULL); + gst_buffer_unref (buf); + + if (caps == NULL) { + GST_WARNING_OBJECT (ogg, + "couldn't find caps for stream with serial %08x", pad->serialno); + caps = gst_caps_new_simple ("application/octet-stream", NULL); + } else { + /* ogg requires you to use a decoder element to define the + * meaning of granulepos etc so we make one. We also do this if + * we are in the streaming mode to calculate the first timestamp. */ + GList *factories; + + GST_LOG_OBJECT (ogg, "found caps: %" GST_PTR_FORMAT, caps); + + /* first filter out the interesting element factories */ + factories = gst_default_registry_feature_filter ( + (GstPluginFeatureFilter) gst_ogg_demux_factory_filter, FALSE, caps); + + /* sort them according to their ranks */ + factories = g_list_sort (factories, (GCompareFunc) compare_ranks); + + /* then pick the first factory to create an element */ + if (factories) { + element = + gst_element_factory_create (GST_ELEMENT_FACTORY (factories->data), + NULL); + if (element) { + GstPadTemplate *template; + + /* this is ours */ + gst_object_ref (element); + gst_object_sink (GST_OBJECT (element)); + + /* FIXME, it might not be named "sink" */ + pad->elem_pad = gst_element_get_pad (element, "sink"); + gst_element_set_state (element, GST_STATE_PAUSED); + template = gst_static_pad_template_get (&internaltemplate); + pad->elem_out = gst_pad_new_from_template (template, "internal"); + gst_pad_set_chain_function (pad->elem_out, gst_ogg_pad_internal_chain); + gst_pad_set_element_private (pad->elem_out, pad); + gst_pad_set_active (pad->elem_out, TRUE); + gst_object_unref (template); + + /* and this pad may not be named src.. */ + /* And it might also not exist at this time... */ + { + GstPad *p; + + p = gst_element_get_pad (element, "src"); + if (p) { + gst_pad_link (p, pad->elem_out); + gst_object_unref (p); + } else { + pad->dynamic = TRUE; + pad->padaddedid = g_signal_connect (G_OBJECT (element), + "pad-added", G_CALLBACK (internal_element_pad_added_cb), pad); + } + } + } + } + g_list_free (factories); + } + pad->element = element; + + gst_pad_set_caps (GST_PAD (pad), caps); + gst_caps_unref (caps); + + return TRUE; +} + +/* send packet to internal element */ +static GstFlowReturn +gst_ogg_demux_chain_elem_pad (GstOggPad * pad, ogg_packet * packet) +{ + GstBuffer *buf; + GstFlowReturn ret; + +#ifndef GST_DISABLE_GST_DEBUG + GstOggDemux *ogg = pad->ogg; +#endif + + /* initialize our internal decoder with packets */ + if (!pad->elem_pad) + goto no_decoder; + + GST_DEBUG_OBJECT (ogg, "%p init decoder serial %08x", pad, pad->serialno); + + buf = gst_buffer_new_and_alloc (packet->bytes); + memcpy (GST_BUFFER_DATA (buf), packet->packet, packet->bytes); + gst_buffer_set_caps (buf, GST_PAD_CAPS (pad)); + GST_BUFFER_OFFSET (buf) = -1; + GST_BUFFER_OFFSET_END (buf) = packet->granulepos; + + ret = gst_pad_chain (pad->elem_pad, buf); + if (GST_FLOW_IS_FATAL (ret)) + goto decoder_error; + + return ret; + +no_decoder: + { + GST_WARNING_OBJECT (ogg, + "pad %p does not have elem_pad, no decoder ?", pad); + return GST_FLOW_ERROR; + } +decoder_error: + { + GST_WARNING_OBJECT (ogg, "internal decoder error"); + return GST_FLOW_ERROR; + } +} + +/* queue data, basically takes the packet, puts it in a buffer and store the + * buffer in the headers list. + */ +static GstFlowReturn +gst_ogg_demux_queue_data (GstOggPad * pad, ogg_packet * packet) +{ + GstBuffer *buf; + +#ifndef GST_DISABLE_GST_DEBUG + GstOggDemux *ogg = pad->ogg; +#endif + + GST_DEBUG_OBJECT (ogg, "%p queueing data serial %08x", pad, pad->serialno); + + buf = gst_buffer_new_and_alloc (packet->bytes); + memcpy (buf->data, packet->packet, packet->bytes); + GST_BUFFER_OFFSET (buf) = -1; + GST_BUFFER_OFFSET_END (buf) = packet->granulepos; + pad->headers = g_list_append (pad->headers, buf); + + /* we are ok now */ + return GST_FLOW_OK; +} + +/* send packet to internal element */ +static GstFlowReturn +gst_ogg_demux_chain_peer (GstOggPad * pad, ogg_packet * packet) +{ + GstBuffer *buf; + GstFlowReturn ret, cret; + GstOggDemux *ogg = pad->ogg; + GstFormat format; + gint64 current_time; + GstOggChain *chain; + + GST_DEBUG_OBJECT (ogg, + "%p streaming to peer serial %08x", pad, pad->serialno); + + ret = + gst_pad_alloc_buffer_and_set_caps (GST_PAD_CAST (pad), + GST_BUFFER_OFFSET_NONE, packet->bytes, GST_PAD_CAPS (pad), &buf); + + /* combine flows */ + cret = gst_ogg_demux_combine_flows (ogg, pad, ret); + if (ret != GST_FLOW_OK) + goto no_buffer; + + /* copy packet in buffer */ + memcpy (buf->data, packet->packet, packet->bytes); + + GST_BUFFER_OFFSET (buf) = -1; + GST_BUFFER_OFFSET_END (buf) = packet->granulepos; + + /* Mark discont on the buffer */ + if (pad->discont) { + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + pad->discont = FALSE; + } + + ret = gst_pad_push (GST_PAD_CAST (pad), buf); + + /* combine flows */ + cret = gst_ogg_demux_combine_flows (ogg, pad, ret); + + /* we're done with skeleton stuff */ + if (pad->is_skeleton) + goto done; + + /* check if valid granulepos, then we can calculate the current + * position */ + if (packet->granulepos < 0) + goto done; + + /* store current granule pos */ + ogg->current_granule = packet->granulepos; + + /* convert to time */ + format = GST_FORMAT_TIME; + if (!gst_ogg_pad_query_convert (pad, + GST_FORMAT_DEFAULT, packet->granulepos, &format, + (gint64 *) & current_time)) + goto convert_failed; + + /* convert to stream time */ + if ((chain = pad->chain)) + current_time = current_time - chain->segment_start + chain->begin_time; + + /* and store as the current position */ + gst_segment_set_last_stop (&ogg->segment, GST_FORMAT_TIME, current_time); + + GST_DEBUG_OBJECT (ogg, "ogg current time %" GST_TIME_FORMAT, + GST_TIME_ARGS (current_time)); + +done: + /* return combined flow result */ + return cret; + + /* special cases */ +no_buffer: + { + GST_DEBUG_OBJECT (ogg, + "%p could not get buffer from peer %08x, %d (%s), combined %d (%s)", + pad, pad->serialno, ret, gst_flow_get_name (ret), + cret, gst_flow_get_name (cret)); + goto done; + } +convert_failed: + { + GST_WARNING_OBJECT (ogg, "could not convert granulepos to time"); + goto done; + } +} + +/* submit a packet to the oggpad, this function will run the + * typefind code for the pad if this is the first packet for this + * stream + */ +static GstFlowReturn +gst_ogg_pad_submit_packet (GstOggPad * pad, ogg_packet * packet) +{ + gint64 granule; + GstFlowReturn ret; + + GstOggDemux *ogg = pad->ogg; + + GST_DEBUG_OBJECT (ogg, "%p submit packet serial %08x", pad, pad->serialno); + + if (!pad->have_type) { + if (!ogg->have_fishead && packet->bytes == SKELETON_FISHEAD_SIZE && + !memcmp (packet->packet, "fishead\0", 8)) { + gst_ogg_pad_parse_skeleton_fishead (pad, packet); + } + gst_ogg_pad_typefind (pad, packet); + pad->have_type = TRUE; + } + + if (ogg->have_fishead && packet->bytes >= SKELETON_FISBONE_MIN_SIZE && + !memcmp (packet->packet, "fisbone\0", 8)) { + gst_ogg_pad_parse_skeleton_fisbone (pad, packet); + } + + granule = packet->granulepos; + if (granule != -1) { + GST_DEBUG_OBJECT (ogg, "%p has granulepos %" G_GINT64_FORMAT, pad, granule); + ogg->current_granule = granule; + pad->current_granule = granule; + /* granulepos 0 and -1 are considered header packets. + * Note that since theora is busted, it can create non-header + * packets with 0 granulepos. We will correct for this when our + * internal decoder produced a frame and we don't have a + * granulepos because in that case the granulpos must have been 0 */ + if (pad->first_granule == -1 && granule != 0) { + GST_DEBUG_OBJECT (ogg, "%p found first granulepos %" G_GINT64_FORMAT, pad, + granule); + pad->first_granule = granule; + } + } + + if (granule != -1 && memcmp (packet->packet, "KW-DIRAC", 8) == 0) { + return GST_FLOW_OK; + } + + /* no start time known, stream to internal plugin to + * get time. always stream to the skeleton decoder */ + if (pad->start_time == GST_CLOCK_TIME_NONE || pad->is_skeleton) { + ret = gst_ogg_demux_chain_elem_pad (pad, packet); + } + /* we know the start_time of the pad data, see if we + * can activate the complete chain if this is a dynamic + * chain. */ + if (pad->start_time != GST_CLOCK_TIME_NONE) { + GstOggChain *chain = pad->chain; + + /* correction for busted ogg, if the internal decoder outputed + * a timestamp but we did not get a granulepos, it must have + * been 0 and the time is therefore also 0 */ + if (pad->first_granule == -1) { + GST_DEBUG_OBJECT (ogg, "%p found start time without granulepos", pad); + pad->first_granule = 0; + pad->first_time = 0; + } + + /* check if complete chain has start time */ + if (chain == ogg->building_chain) { + + /* see if we have enough info to activate the chain, we have enough info + * when all streams have a valid start time. */ + if (gst_ogg_demux_collect_chain_info (ogg, chain)) { + GstEvent *event; + + GST_DEBUG_OBJECT (ogg, "segment_start: %" GST_TIME_FORMAT, + GST_TIME_ARGS (chain->segment_start)); + GST_DEBUG_OBJECT (ogg, "segment_stop: %" GST_TIME_FORMAT, + GST_TIME_ARGS (chain->segment_stop)); + GST_DEBUG_OBJECT (ogg, "segment_time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (chain->begin_time)); + + /* create the newsegment event we are going to send out */ + event = gst_event_new_new_segment (FALSE, ogg->segment.rate, + GST_FORMAT_TIME, chain->segment_start, chain->segment_stop, + chain->begin_time); + + gst_ogg_demux_activate_chain (ogg, chain, event); + + ogg->building_chain = NULL; + } + } + } + + /* if we are building a chain, store buffer for when we activate + * it. This path is taken if we operate in streaming mode. */ + if (ogg->building_chain) { + ret = gst_ogg_demux_queue_data (pad, packet); + } + /* else we are completely streaming to the peer */ + else { + ret = gst_ogg_demux_chain_peer (pad, packet); + } + return ret; +} + +/* flush at most @npackets from the stream layer. All packets if + * @npackets is 0; + */ +static GstFlowReturn +gst_ogg_pad_stream_out (GstOggPad * pad, gint npackets) +{ + GstFlowReturn result = GST_FLOW_OK; + gboolean done = FALSE; + GstOggDemux *ogg; + + ogg = pad->ogg; + + while (!done) { + int ret; + ogg_packet packet; + + ret = ogg_stream_packetout (&pad->stream, &packet); + switch (ret) { + case 0: + GST_LOG_OBJECT (ogg, "packetout done"); + done = TRUE; + break; + case -1: + GST_LOG_OBJECT (ogg, "packetout discont"); + gst_ogg_chain_mark_discont (pad->chain); + break; + case 1: + GST_LOG_OBJECT (ogg, "packetout gave packet of size %ld", packet.bytes); + result = gst_ogg_pad_submit_packet (pad, &packet); + if (GST_FLOW_IS_FATAL (result)) + goto could_not_submit; + break; + default: + GST_WARNING_OBJECT (ogg, + "invalid return value %d for ogg_stream_packetout, resetting stream", + ret); + gst_ogg_pad_reset (pad); + break; + } + if (npackets > 0) { + npackets--; + done = (npackets == 0); + } + } + return result; + + /* ERRORS */ +could_not_submit: + { + GST_WARNING_OBJECT (ogg, + "could not submit packet for stream %08x, error: %d", pad->serialno, + result); + gst_ogg_pad_reset (pad); + return result; + } +} + +/* submit a page to an oggpad, this function will then submit all + * the packets in the page. + */ +static GstFlowReturn +gst_ogg_pad_submit_page (GstOggPad * pad, ogg_page * page) +{ + GstFlowReturn result = GST_FLOW_OK; + GstOggDemux *ogg; + gboolean continued = FALSE; + + ogg = pad->ogg; + + /* for negative rates we read pages backwards and must therefore be carefull + * with continued pages */ + if (ogg->segment.rate < 0.0) { + gint npackets; + + continued = ogg_page_continued (page); + + /* number of completed packets in the page */ + npackets = ogg_page_packets (page); + if (!continued) { + /* page is not continued so it contains at least one packet start. It's + * possible that no packet ends on this page (npackets == 0). In that + * case, the next (continued) page(s) we kept contain the remainder of the + * packets. We mark npackets=1 to make us start decoding the pages in the + * remainder of the algorithm. */ + if (npackets == 0) + npackets = 1; + } + GST_LOG_OBJECT (ogg, "continued: %d, %d packets", continued, npackets); + + if (npackets == 0) { + GST_LOG_OBJECT (ogg, "no decodable packets, we need a previous page"); + goto done; + } + } + + if (ogg_stream_pagein (&pad->stream, page) != 0) + goto choked; + + /* flush all packets in the stream layer, this might not give a packet if + * the page had no packets finishing on the page (npackets == 0). */ + result = gst_ogg_pad_stream_out (pad, 0); + + if (pad->continued) { + ogg_packet packet; + + /* now send the continued pages to the stream layer */ + while (pad->continued) { + ogg_page *p = (ogg_page *) pad->continued->data; + + GST_LOG_OBJECT (ogg, "submitting continued page %p", p); + if (ogg_stream_pagein (&pad->stream, p) != 0) + goto choked; + + pad->continued = g_list_delete_link (pad->continued, pad->continued); + + /* free the page */ + gst_ogg_page_free (p); + } + + GST_LOG_OBJECT (ogg, "flushing last continued packet"); + /* flush 1 continued packet in the stream layer */ + result = gst_ogg_pad_stream_out (pad, 1); + + /* flush all remaining packets, we pushed them in the previous round. + * We don't use _reset() because we still want to get the discont when + * we submit a next page. */ + while (ogg_stream_packetout (&pad->stream, &packet) != 0); + } + +done: + /* keep continued pages (only in reverse mode) */ + if (continued) { + ogg_page *p = gst_ogg_page_copy (page); + + GST_LOG_OBJECT (ogg, "keeping continued page %p", p); + pad->continued = g_list_prepend (pad->continued, p); + } + + return result; + +choked: + { + GST_WARNING_OBJECT (ogg, + "ogg stream choked on page (serial %08x), resetting stream", + pad->serialno); + gst_ogg_pad_reset (pad); + /* we continue to recover */ + return GST_FLOW_OK; + } +} + + +static GstOggChain * +gst_ogg_chain_new (GstOggDemux * ogg) +{ + GstOggChain *chain = g_new0 (GstOggChain, 1); + + GST_DEBUG_OBJECT (ogg, "creating new chain %p", chain); + chain->ogg = ogg; + chain->offset = -1; + chain->bytes = -1; + chain->have_bos = FALSE; + chain->streams = g_array_new (FALSE, TRUE, sizeof (GstOggPad *)); + chain->begin_time = GST_CLOCK_TIME_NONE; + chain->segment_start = GST_CLOCK_TIME_NONE; + chain->segment_stop = GST_CLOCK_TIME_NONE; + chain->total_time = GST_CLOCK_TIME_NONE; + + return chain; +} + +static void +gst_ogg_chain_free (GstOggChain * chain) +{ + gint i; + + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + gst_object_unref (pad); + } + g_array_free (chain->streams, TRUE); + g_free (chain); +} + +static void +gst_ogg_chain_mark_discont (GstOggChain * chain) +{ + gint i; + + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + pad->discont = TRUE; + } +} + +static void +gst_ogg_chain_reset (GstOggChain * chain) +{ + gint i; + + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + gst_ogg_pad_reset (pad); + } +} + +static GstOggPad * +gst_ogg_chain_new_stream (GstOggChain * chain, glong serialno) +{ + GstOggPad *ret; + GstTagList *list; + gchar *name; + + GST_DEBUG_OBJECT (chain->ogg, "creating new stream %08lx in chain %p", + serialno, chain); + + ret = g_object_new (GST_TYPE_OGG_PAD, NULL); + /* we own this one */ + gst_object_ref (ret); + gst_object_sink (ret); + + GST_PAD_DIRECTION (ret) = GST_PAD_SRC; + ret->discont = TRUE; + + ret->chain = chain; + ret->ogg = chain->ogg; + + ret->serialno = serialno; + if (ogg_stream_init (&ret->stream, serialno) != 0) + goto init_failed; + + name = g_strdup_printf ("serial_%08lx", serialno); + gst_object_set_name (GST_OBJECT (ret), name); + g_free (name); + + /* FIXME: either do something with it or remove it */ + list = gst_tag_list_new (); + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_SERIAL, serialno, + NULL); + gst_tag_list_free (list); + + GST_DEBUG_OBJECT (chain->ogg, + "created new ogg src %p for stream with serial %08lx", ret, serialno); + + g_array_append_val (chain->streams, ret); + + return ret; + + /* ERRORS */ +init_failed: + { + GST_ERROR ("Could not initialize ogg_stream struct for serial %08lx.", + serialno); + gst_object_unref (ret); + return NULL; + } +} + +static GstOggPad * +gst_ogg_chain_get_stream (GstOggChain * chain, glong serialno) +{ + gint i; + + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + if (pad->serialno == serialno) + return pad; + } + return NULL; +} + +static gboolean +gst_ogg_chain_has_stream (GstOggChain * chain, glong serialno) +{ + return gst_ogg_chain_get_stream (chain, serialno) != NULL; +} + +#define CURRENT_CHAIN(ogg) (&g_array_index ((ogg)->chains, GstOggChain, (ogg)->current_chain)) + +/* signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + ARG_0 + /* FILL ME */ +}; + +static GstStaticPadTemplate ogg_demux_src_template_factory = +GST_STATIC_PAD_TEMPLATE ("src_%d", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate ogg_demux_sink_template_factory = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/ogg; application/x-annodex") + ); + +static void gst_ogg_demux_finalize (GObject * object); + +//static const GstEventMask *gst_ogg_demux_get_event_masks (GstPad * pad); +//static const GstQueryType *gst_ogg_demux_get_query_types (GstPad * pad); +static GstFlowReturn gst_ogg_demux_read_chain (GstOggDemux * ogg, + GstOggChain ** chain); +static GstFlowReturn gst_ogg_demux_read_end_chain (GstOggDemux * ogg, + GstOggChain * chain); + +static gboolean gst_ogg_demux_sink_event (GstPad * pad, GstEvent * event); +static void gst_ogg_demux_loop (GstOggPad * pad); +static GstFlowReturn gst_ogg_demux_chain (GstPad * pad, GstBuffer * buffer); +static gboolean gst_ogg_demux_sink_activate (GstPad * sinkpad); +static gboolean gst_ogg_demux_sink_activate_pull (GstPad * sinkpad, + gboolean active); +static gboolean gst_ogg_demux_sink_activate_push (GstPad * sinkpad, + gboolean active); +static GstStateChangeReturn gst_ogg_demux_change_state (GstElement * element, + GstStateChange transition); +static void gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event); + +static void gst_ogg_print (GstOggDemux * demux); + +GST_BOILERPLATE (GstOggDemux, gst_ogg_demux, GstElement, GST_TYPE_ELEMENT); + +static void +gst_ogg_demux_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details (element_class, &gst_ogg_demux_details); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&ogg_demux_sink_template_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&ogg_demux_src_template_factory)); +} +static void +gst_ogg_demux_class_init (GstOggDemuxClass * klass) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gstelement_class->change_state = gst_ogg_demux_change_state; + gstelement_class->send_event = gst_ogg_demux_receive_event; + + gobject_class->finalize = gst_ogg_demux_finalize; +} + +static void +gst_ogg_demux_init (GstOggDemux * ogg, GstOggDemuxClass * g_class) +{ + /* create the sink pad */ + ogg->sinkpad = + gst_pad_new_from_static_template (&ogg_demux_sink_template_factory, + "sink"); + + gst_pad_set_event_function (ogg->sinkpad, gst_ogg_demux_sink_event); + gst_pad_set_chain_function (ogg->sinkpad, gst_ogg_demux_chain); + gst_pad_set_activate_function (ogg->sinkpad, gst_ogg_demux_sink_activate); + gst_pad_set_activatepull_function (ogg->sinkpad, + gst_ogg_demux_sink_activate_pull); + gst_pad_set_activatepush_function (ogg->sinkpad, + gst_ogg_demux_sink_activate_push); + gst_element_add_pad (GST_ELEMENT (ogg), ogg->sinkpad); + + ogg->chain_lock = g_mutex_new (); + ogg->chains = g_array_new (FALSE, TRUE, sizeof (GstOggChain *)); + + ogg->newsegment = NULL; +} + +static void +gst_ogg_demux_finalize (GObject * object) +{ + GstOggDemux *ogg; + + ogg = GST_OGG_DEMUX (object); + + g_array_free (ogg->chains, TRUE); + g_mutex_free (ogg->chain_lock); + ogg_sync_clear (&ogg->sync); + + if (ogg->newsegment) + gst_event_unref (ogg->newsegment); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_ogg_demux_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean res; + GstOggDemux *ogg; + + ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + /* FIXME */ + GST_DEBUG_OBJECT (ogg, "got a new segment event"); + ogg_sync_reset (&ogg->sync); + gst_event_unref (event); + res = TRUE; + break; + case GST_EVENT_EOS: + default: + res = gst_pad_event_default (pad, event); + break; + } + gst_object_unref (ogg); + + return res; +} + +/* submit the given buffer to the ogg sync. + * + * Returns the number of bytes submited. + */ +static GstFlowReturn +gst_ogg_demux_submit_buffer (GstOggDemux * ogg, GstBuffer * buffer) +{ + gint size; + guint8 *data; + gchar *oggbuffer; + GstFlowReturn ret = GST_FLOW_OK; + + size = GST_BUFFER_SIZE (buffer); + data = GST_BUFFER_DATA (buffer); + + GST_DEBUG_OBJECT (ogg, "submitting %u bytes", size); + if (G_UNLIKELY (size == 0)) + goto done; + + oggbuffer = ogg_sync_buffer (&ogg->sync, size); + if (G_UNLIKELY (oggbuffer == NULL)) + goto no_buffer; + + memcpy (oggbuffer, data, size); + if (G_UNLIKELY (ogg_sync_wrote (&ogg->sync, size) < 0)) + goto write_failed; + +done: + gst_buffer_unref (buffer); + + return ret; + + /* ERRORS */ +no_buffer: + { + GST_ELEMENT_ERROR (ogg, STREAM, DECODE, + (NULL), ("failed to get ogg sync buffer")); + ret = GST_FLOW_ERROR; + goto done; + } +write_failed: + { + GST_ELEMENT_ERROR (ogg, STREAM, DECODE, + (NULL), ("failed to write %d bytes to the sync buffer", size)); + ret = GST_FLOW_ERROR; + goto done; + } +} + +/* in random access mode this code updates the current read position + * and resets the ogg sync buffer so that the next read will happen + * from this new location. + */ +static void +gst_ogg_demux_seek (GstOggDemux * ogg, gint64 offset) +{ + GST_LOG_OBJECT (ogg, "seeking to %" G_GINT64_FORMAT, offset); + + ogg->offset = offset; + ogg_sync_reset (&ogg->sync); +} + +/* read more data from the current offset and submit to + * the ogg sync layer. + */ +static GstFlowReturn +gst_ogg_demux_get_data (GstOggDemux * ogg) +{ + GstFlowReturn ret; + GstBuffer *buffer; + + GST_LOG_OBJECT (ogg, "get data %" G_GINT64_FORMAT " %" G_GINT64_FORMAT, + ogg->offset, ogg->length); + if (ogg->offset == ogg->length) + goto eos; + + ret = gst_pad_pull_range (ogg->sinkpad, ogg->offset, CHUNKSIZE, &buffer); + if (ret != GST_FLOW_OK) + goto error; + + ret = gst_ogg_demux_submit_buffer (ogg, buffer); + + return ret; + + /* ERROR */ +eos: + { + GST_LOG_OBJECT (ogg, "reached EOS"); + return GST_FLOW_UNEXPECTED; + } +error: + { + GST_WARNING_OBJECT (ogg, "got %d (%s) from pull range", ret, + gst_flow_get_name (ret)); + return ret; + } +} + +/* Read the next page from the current offset. + * boundary: number of bytes ahead we allow looking for; + * -1 if no boundary + * + * @offset will contain the offset the next page starts at when this function + * returns GST_FLOW_OK. + * + * GST_FLOW_UNEXPECTED is returned on EOS. + * + * GST_FLOW_LIMIT is returned when we did not find a page before the + * boundary. If @boundary is -1, this is never returned. + * + * Any other error returned while retrieving data from the peer is returned as + * is. + */ +static GstFlowReturn +gst_ogg_demux_get_next_page (GstOggDemux * ogg, ogg_page * og, gint64 boundary, + gint64 * offset) +{ + gint64 end_offset = 0; + GstFlowReturn ret; + + GST_LOG_OBJECT (ogg, + "get next page, current offset %" G_GINT64_FORMAT ", bytes boundary %" + G_GINT64_FORMAT, ogg->offset, boundary); + + if (boundary > 0) + end_offset = ogg->offset + boundary; + + while (TRUE) { + glong more; + + if (boundary > 0 && ogg->offset >= end_offset) + goto boundary_reached; + + more = ogg_sync_pageseek (&ogg->sync, og); + + GST_LOG_OBJECT (ogg, "pageseek gave %ld", more); + + if (more < 0) { + /* skipped n bytes */ + GST_LOG_OBJECT (ogg, "skipped %ld bytes", more); + ogg->offset -= more; + } else if (more == 0) { + /* we need more data */ + if (boundary == 0) + goto boundary_reached; + + GST_LOG_OBJECT (ogg, "need more data"); + ret = gst_ogg_demux_get_data (ogg); + if (ret != GST_FLOW_OK) + break; + } else { + gint64 res_offset = ogg->offset; + + /* got a page. Return the offset at the page beginning, + advance the internal offset past the page end */ + if (offset) + *offset = res_offset; + ret = GST_FLOW_OK; + + ogg->offset += more; + /* need to reset as we do not keep track of the bytes we + * sent to the sync layer */ + ogg_sync_reset (&ogg->sync); + + GST_LOG_OBJECT (ogg, + "got page at %" G_GINT64_FORMAT ", serial %08x, end at %" + G_GINT64_FORMAT ", granule %" G_GINT64_FORMAT, res_offset, + ogg_page_serialno (og), ogg->offset, ogg_page_granulepos (og)); + break; + } + } + GST_LOG_OBJECT (ogg, "returning %d", ret); + + return ret; + + /* ERRORS */ +boundary_reached: + { + GST_LOG_OBJECT (ogg, + "offset %" G_GINT64_FORMAT " >= end_offset %" G_GINT64_FORMAT, + ogg->offset, end_offset); + return GST_FLOW_LIMIT; + } +} + +/* from the current offset, find the previous page, seeking backwards + * until we find the page. + */ +static GstFlowReturn +gst_ogg_demux_get_prev_page (GstOggDemux * ogg, ogg_page * og, gint64 * offset) +{ + GstFlowReturn ret; + gint64 begin = ogg->offset; + gint64 end = begin; + gint64 cur_offset = -1; + + while (cur_offset == -1) { + begin -= CHUNKSIZE; + if (begin < 0) + begin = 0; + + /* seek CHUNKSIZE back */ + gst_ogg_demux_seek (ogg, begin); + + /* now continue reading until we run out of data, if we find a page + * start, we save it. It might not be the final page as there could be + * another page after this one. */ + while (ogg->offset < end) { + gint64 new_offset; + + ret = + gst_ogg_demux_get_next_page (ogg, og, end - ogg->offset, &new_offset); + /* we hit the upper limit, offset contains the last page start */ + if (ret == GST_FLOW_LIMIT) + break; + /* something went wrong */ + if (ret == GST_FLOW_UNEXPECTED) + new_offset = 0; + else if (ret != GST_FLOW_OK) + return ret; + + /* offset is next page start */ + cur_offset = new_offset; + } + } + + /* we have the offset. Actually snork and hold the page now */ + gst_ogg_demux_seek (ogg, cur_offset); + ret = gst_ogg_demux_get_next_page (ogg, og, -1, NULL); + if (ret != GST_FLOW_OK) + /* this shouldn't be possible */ + return ret; + + if (offset) + *offset = cur_offset; + + return ret; +} + +static gboolean +gst_ogg_demux_deactivate_current_chain (GstOggDemux * ogg) +{ + gint i; + GstOggChain *chain = ogg->current_chain; + + if (chain == NULL) + return TRUE; + + GST_DEBUG_OBJECT (ogg, "deactivating chain %p", chain); + + /* send EOS on all the pads */ + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + gst_pad_push_event (GST_PAD_CAST (pad), gst_event_new_eos ()); + + GST_DEBUG_OBJECT (ogg, "removing pad %" GST_PTR_FORMAT, pad); + + /* deactivate first */ + gst_pad_set_active (GST_PAD_CAST (pad), FALSE); + + gst_element_remove_pad (GST_ELEMENT (ogg), GST_PAD_CAST (pad)); + } + /* if we cannot seek back to the chain, we can destroy the chain + * completely */ + if (!ogg->seekable) { + gst_ogg_chain_free (chain); + } + ogg->current_chain = NULL; + + return TRUE; +} + +static gboolean +gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, + GstEvent * event) +{ + gint i; + + if (chain == ogg->current_chain) { + if (event) + gst_event_unref (event); + return TRUE; + } + + gst_ogg_demux_deactivate_current_chain (ogg); + + /* FIXME, should not be called with NULL */ + if (chain == NULL) { + ogg->current_chain = chain; + return TRUE; + } + + GST_DEBUG_OBJECT (ogg, "activating chain %p", chain); + + /* first add the pads */ + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad; + + pad = g_array_index (chain->streams, GstOggPad *, i); + GST_DEBUG_OBJECT (ogg, "adding pad %" GST_PTR_FORMAT, pad); + + /* mark discont */ + pad->discont = TRUE; + pad->last_ret = GST_FLOW_OK; + + /* activate first */ + gst_pad_set_active (GST_PAD_CAST (pad), TRUE); + + gst_element_add_pad (GST_ELEMENT (ogg), GST_PAD_CAST (pad)); + } + + gst_element_no_more_pads (GST_ELEMENT (ogg)); + ogg->current_chain = chain; + + /* FIXME, must be sent from the streaming thread */ + if (event) + gst_ogg_demux_send_event (ogg, event); + + GST_DEBUG_OBJECT (ogg, "starting chain"); + + /* then send out any queued buffers */ + for (i = 0; i < chain->streams->len; i++) { + GList *headers; + GstOggPad *pad; + + pad = g_array_index (chain->streams, GstOggPad *, i); + + for (headers = pad->headers; headers; headers = g_list_next (headers)) { + GstBuffer *buffer = GST_BUFFER (headers->data); + + if (pad->discont) { + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + pad->discont = FALSE; + } + + /* we don't care about the return value here */ + gst_pad_push (GST_PAD_CAST (pad), buffer); + } + /* and free the headers */ + g_list_free (pad->headers); + pad->headers = NULL; + } + return TRUE; +} + +/* + * do seek to time @position, return FALSE or chain and TRUE + */ +static gboolean +gst_ogg_demux_do_seek (GstOggDemux * ogg, gint64 position, gboolean accurate, + GstOggChain ** rchain) +{ + GstOggChain *chain = NULL; + gint64 begin, end; + gint64 begintime, endtime; + gint64 target; + gint64 best; + gint64 total; + gint64 result = 0; + GstFlowReturn ret; + gint i; + + /* first find the chain to search in */ + total = ogg->total_time; + if (ogg->chains->len == 0) + goto no_chains; + + for (i = ogg->chains->len - 1; i >= 0; i--) { + chain = g_array_index (ogg->chains, GstOggChain *, i); + total -= chain->total_time; + if (position >= total) + break; + } + + begin = chain->offset; + end = chain->end_offset; + begintime = chain->begin_time; + endtime = chain->begin_time + chain->total_time; + target = position - total + begintime; + if (accurate) { + /* FIXME, seek 4 seconds early to catch keyframes, better implement + * keyframe detection. */ + target = target - (gint64) 4 *GST_SECOND; + } + target = MAX (target, 0); + best = begin; + + GST_DEBUG_OBJECT (ogg, + "seeking to %" GST_TIME_FORMAT " in chain %p", + GST_TIME_ARGS (position), chain); + GST_DEBUG_OBJECT (ogg, + "chain offset %" G_GINT64_FORMAT ", end offset %" G_GINT64_FORMAT, begin, + end); + GST_DEBUG_OBJECT (ogg, + "chain begin time %" GST_TIME_FORMAT ", end time %" GST_TIME_FORMAT, + GST_TIME_ARGS (begintime), GST_TIME_ARGS (endtime)); + GST_DEBUG_OBJECT (ogg, "target %" GST_TIME_FORMAT, GST_TIME_ARGS (target)); + + /* perform the seek */ + while (begin < end) { + gint64 bisect; + + if ((end - begin < CHUNKSIZE) || (endtime == begintime)) { + bisect = begin; + } else { + /* take a (pretty decent) guess, avoiding overflow */ + gint64 rate = (end - begin) * GST_MSECOND / (endtime - begintime); + + bisect = (target - begintime) / GST_MSECOND * rate + begin - CHUNKSIZE; + + if (bisect <= begin) + bisect = begin; + GST_DEBUG_OBJECT (ogg, "Initial guess: %" G_GINT64_FORMAT, bisect); + } + gst_ogg_demux_seek (ogg, bisect); + + while (begin < end) { + ogg_page og; + + GST_DEBUG_OBJECT (ogg, + "after seek, bisect %" G_GINT64_FORMAT ", begin %" G_GINT64_FORMAT + ", end %" G_GINT64_FORMAT, bisect, begin, end); + + ret = gst_ogg_demux_get_next_page (ogg, &og, end - ogg->offset, &result); + GST_LOG_OBJECT (ogg, "looking for next page returned %" G_GINT64_FORMAT, + result); + + if (ret == GST_FLOW_LIMIT) { + /* we hit the upper limit, go back a bit */ + if (bisect <= begin + 1) { + end = begin; /* found it */ + } else { + if (bisect == 0) + goto seek_error; + + bisect -= CHUNKSIZE; + if (bisect <= begin) + bisect = begin + 1; + + gst_ogg_demux_seek (ogg, bisect); + } + } else if (ret == GST_FLOW_OK) { + /* found offset of next ogg page */ + gint64 granulepos; + GstClockTime granuletime; + GstFormat format; + GstOggPad *pad; + + GST_LOG_OBJECT (ogg, "found next ogg page at %" G_GINT64_FORMAT, + result); + granulepos = ogg_page_granulepos (&og); + if (granulepos == -1) { + GST_LOG_OBJECT (ogg, "granulepos of next page is -1"); + continue; + } + + pad = gst_ogg_chain_get_stream (chain, ogg_page_serialno (&og)); + if (pad == NULL || pad->is_skeleton) + continue; + + format = GST_FORMAT_TIME; + if (!gst_ogg_pad_query_convert (pad, + GST_FORMAT_DEFAULT, granulepos, &format, + (gint64 *) & granuletime)) { + GST_WARNING_OBJECT (ogg, "could not convert granulepos to time"); + granuletime = target; + } else { + if (granuletime < pad->start_time) + continue; + GST_LOG_OBJECT (ogg, "granulepos %" G_GINT64_FORMAT " maps to time %" + GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (granuletime)); + + granuletime -= pad->start_time; + } + + GST_DEBUG_OBJECT (ogg, + "found page with granule %" G_GINT64_FORMAT " and time %" + GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (granuletime)); + + if (granuletime < target) { + best = result; /* raw offset of packet with granulepos */ + begin = ogg->offset; /* raw offset of next page */ + begintime = granuletime; + + if (target - begintime > GST_SECOND) + break; + + bisect = begin; /* *not* begin + 1 */ + } else { + if (bisect <= begin + 1) { + end = begin; /* found it */ + } else { + if (end == ogg->offset) { /* we're pretty close - we'd be stuck in */ + end = result; + bisect -= CHUNKSIZE; /* an endless loop otherwise. */ + if (bisect <= begin) + bisect = begin + 1; + gst_ogg_demux_seek (ogg, bisect); + } else { + end = result; + endtime = granuletime; + break; + } + } + } + } else + goto seek_error; + } + } + + ogg->offset = best; + *rchain = chain; + + return TRUE; + +no_chains: + { + GST_DEBUG_OBJECT (ogg, "no chains"); + return FALSE; + } +seek_error: + { + GST_DEBUG_OBJECT (ogg, "got a seek error"); + return FALSE; + } +} + +/* does not take ownership of the event */ +static gboolean +gst_ogg_demux_perform_seek (GstOggDemux * ogg, GstEvent * event) +{ + GstOggChain *chain = NULL; + gboolean res; + gboolean flush, accurate; + GstFormat format; + gdouble rate; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + gint64 cur, stop; + gboolean update; + + if (event) { + GST_DEBUG_OBJECT (ogg, "seek with event"); + + gst_event_parse_seek (event, &rate, &format, &flags, + &cur_type, &cur, &stop_type, &stop); + + /* we can only seek on time */ + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (ogg, "can only seek on TIME"); + goto error; + } + } else { + GST_DEBUG_OBJECT (ogg, "seek without event"); + + flags = 0; + rate = 1.0; + } + + GST_DEBUG_OBJECT (ogg, "seek, rate %g", rate); + + flush = flags & GST_SEEK_FLAG_FLUSH; + accurate = flags & GST_SEEK_FLAG_ACCURATE; + + /* first step is to unlock the streaming thread if it is + * blocked in a chain call, we do this by starting the flush. because + * we cannot yet hold any streaming lock, we have to protect the chains + * with their own lock. */ + if (flush) { + gint i; + + gst_pad_push_event (ogg->sinkpad, gst_event_new_flush_start ()); + + GST_CHAIN_LOCK (ogg); + for (i = 0; i < ogg->chains->len; i++) { + GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i); + gint j; + + for (j = 0; j < chain->streams->len; j++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, j); + + gst_pad_push_event (GST_PAD (pad), gst_event_new_flush_start ()); + } + } + GST_CHAIN_UNLOCK (ogg); + } else { + gst_pad_pause_task (ogg->sinkpad); + } + + /* now grab the stream lock so that streaming cannot continue, for + * non flushing seeks when the element is in PAUSED this could block + * forever. */ + GST_PAD_STREAM_LOCK (ogg->sinkpad); + + if (ogg->segment_running && !flush) { + /* create the segment event to close the current segment */ + if ((chain = ogg->current_chain)) { + GstEvent *newseg; + + newseg = gst_event_new_new_segment (TRUE, ogg->segment.rate, + GST_FORMAT_TIME, ogg->segment.start + chain->segment_start, + ogg->segment.last_stop + chain->segment_start, ogg->segment.time); + + /* send segment on old chain, FIXME, must be sent from streaming thread. */ + gst_ogg_demux_send_event (ogg, newseg); + } + } + + if (event) { + gst_segment_set_seek (&ogg->segment, rate, format, flags, + cur_type, cur, stop_type, stop, &update); + } + + GST_DEBUG_OBJECT (ogg, "segment positions set to %" GST_TIME_FORMAT "-%" + GST_TIME_FORMAT, GST_TIME_ARGS (ogg->segment.start), + GST_TIME_ARGS (ogg->segment.stop)); + + /* we need to stop flushing on the srcpad as we're going to use it + * next. We can do this as we have the STREAM lock now. */ + gst_pad_push_event (ogg->sinkpad, gst_event_new_flush_stop ()); + + { + gint i; + + /* reset all ogg streams now, need to do this from within the lock to + * make sure the streaming thread is not messing with the stream */ + for (i = 0; i < ogg->chains->len; i++) { + GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i); + + gst_ogg_chain_reset (chain); + } + } + + res = gst_ogg_demux_do_seek (ogg, ogg->segment.last_stop, accurate, &chain); + + /* seek failed, make sure we continue the current chain */ + if (!res) { + chain = ogg->current_chain; + } + + if (!chain) + goto no_chain; + + /* now we have a new position, prepare for streaming again */ + { + GstEvent *event; + gint64 stop; + gint64 start; + gint64 last_stop, begin_time; + + /* we have to send the flush to the old chain, not the new one */ + if (flush) + gst_ogg_demux_send_event (ogg, gst_event_new_flush_stop ()); + + /* we need this to see how far inside the chain we need to start */ + if (chain->begin_time != GST_CLOCK_TIME_NONE) + begin_time = chain->begin_time; + else + begin_time = 0; + + /* segment.start gives the start over all chains, we calculate the amount + * of time into this chain we need to start */ + start = ogg->segment.start - begin_time; + if (chain->segment_start != GST_CLOCK_TIME_NONE) + start += chain->segment_start; + + if ((stop = ogg->segment.stop) == -1) + stop = ogg->segment.duration; + + /* segment.stop gives the stop time over all chains, calculate the amount of + * time we need to stop in this chain */ + if (stop != -1) { + if (stop > begin_time) + stop -= begin_time; + else + stop = 0; + stop += chain->segment_start; + /* we must stop when this chain ends and switch to the next chain to play + * the remainder of the segment. */ + stop = MIN (stop, chain->segment_stop); + } + + last_stop = ogg->segment.last_stop - begin_time; + if (chain->segment_start != GST_CLOCK_TIME_NONE) + last_stop += chain->segment_start; + + /* create the segment event we are going to send out */ + if (ogg->segment.rate >= 0.0) + event = gst_event_new_new_segment (FALSE, ogg->segment.rate, + ogg->segment.format, last_stop, stop, ogg->segment.time); + else + event = gst_event_new_new_segment (FALSE, ogg->segment.rate, + ogg->segment.format, start, last_stop, ogg->segment.time); + + if (chain != ogg->current_chain) { + /* switch to different chain, send segment on new chain */ + gst_ogg_demux_activate_chain (ogg, chain, event); + } else { + /* mark discont and send segment on current chain */ + gst_ogg_chain_mark_discont (chain); + /* This event should be sent from the streaming thread (sink pad task) */ + if (ogg->newsegment) + gst_event_unref (ogg->newsegment); + ogg->newsegment = event; + } + + /* notify start of new segment */ + if (ogg->segment.flags & GST_SEEK_FLAG_SEGMENT) { + gst_element_post_message (GST_ELEMENT (ogg), + gst_message_new_segment_start (GST_OBJECT (ogg), + GST_FORMAT_TIME, ogg->segment.last_stop)); + } + + ogg->segment_running = TRUE; + /* restart our task since it might have been stopped when we did the + * flush. */ + gst_pad_start_task (ogg->sinkpad, (GstTaskFunction) gst_ogg_demux_loop, + ogg->sinkpad); + } + + /* streaming can continue now */ + GST_PAD_STREAM_UNLOCK (ogg->sinkpad); + + return res; + +error: + { + GST_DEBUG_OBJECT (ogg, "seek failed"); + return FALSE; + } +no_chain: + { + GST_DEBUG_OBJECT (ogg, "no chain to seek in"); + GST_PAD_STREAM_UNLOCK (ogg->sinkpad); + return FALSE; + } +} + +/* finds each bitstream link one at a time using a bisection search + * (has to begin by knowing the offset of the lb's initial page). + * Recurses for each link so it can alloc the link storage after + * finding them all, then unroll and fill the cache at the same time + */ +static GstFlowReturn +gst_ogg_demux_bisect_forward_serialno (GstOggDemux * ogg, + gint64 begin, gint64 searched, gint64 end, GstOggChain * chain, glong m) +{ + gint64 endsearched = end; + gint64 next = end; + ogg_page og; + GstFlowReturn ret; + gint64 offset; + GstOggChain *nextchain; + + GST_LOG_OBJECT (ogg, + "bisect begin: %" G_GINT64_FORMAT ", searched: %" G_GINT64_FORMAT + ", end %" G_GINT64_FORMAT ", chain: %p", begin, searched, end, chain); + + /* the below guards against garbage seperating the last and + * first pages of two links. */ + while (searched < endsearched) { + gint64 bisect; + + if (endsearched - searched < CHUNKSIZE) { + bisect = searched; + } else { + bisect = (searched + endsearched) / 2; + } + + gst_ogg_demux_seek (ogg, bisect); + ret = gst_ogg_demux_get_next_page (ogg, &og, -1, &offset); + + if (ret == GST_FLOW_UNEXPECTED) { + endsearched = bisect; + } else if (ret == GST_FLOW_OK) { + glong serial = ogg_page_serialno (&og); + + if (!gst_ogg_chain_has_stream (chain, serial)) { + endsearched = bisect; + next = offset; + } else { + searched = offset + og.header_len + og.body_len; + } + } else + return ret; + } + + GST_LOG_OBJECT (ogg, "current chain ends at %" G_GINT64_FORMAT, searched); + + chain->end_offset = searched; + ret = gst_ogg_demux_read_end_chain (ogg, chain); + if (ret != GST_FLOW_OK) + return ret; + + GST_LOG_OBJECT (ogg, "found begin at %" G_GINT64_FORMAT, next); + + gst_ogg_demux_seek (ogg, next); + ret = gst_ogg_demux_read_chain (ogg, &nextchain); + if (ret == GST_FLOW_UNEXPECTED) { + nextchain = NULL; + ret = GST_FLOW_OK; + GST_LOG_OBJECT (ogg, "no next chain"); + } else if (ret != GST_FLOW_OK) + goto done; + + if (searched < end && nextchain != NULL) { + ret = gst_ogg_demux_bisect_forward_serialno (ogg, next, ogg->offset, + end, nextchain, m + 1); + if (ret != GST_FLOW_OK) + goto done; + } + GST_LOG_OBJECT (ogg, "adding chain %p", chain); + + g_array_insert_val (ogg->chains, 0, chain); + +done: + return ret; +} + +/* read a chain from the ogg file. This code will + * read all BOS pages and will create and return a GstOggChain + * structure with the results. + * + * This function will also read N pages from each stream in the + * chain and submit them to the decoders. When the decoder has + * decoded the first buffer, we know the timestamp of the first + * page in the chain. + */ +static GstFlowReturn +gst_ogg_demux_read_chain (GstOggDemux * ogg, GstOggChain ** res_chain) +{ + GstFlowReturn ret; + GstOggChain *chain = NULL; + gint64 offset = ogg->offset; + ogg_page op; + gboolean done; + gint i; + + GST_LOG_OBJECT (ogg, "reading chain at %" G_GINT64_FORMAT, offset); + + /* first read the BOS pages, do typefind on them, create + * the decoders, send data to the decoders. */ + while (TRUE) { + GstOggPad *pad; + glong serial; + + ret = gst_ogg_demux_get_next_page (ogg, &op, -1, NULL); + if (ret != GST_FLOW_OK) { + GST_WARNING_OBJECT (ogg, "problem reading BOS page: ret=%d", ret); + break; + } + if (!ogg_page_bos (&op)) { + GST_WARNING_OBJECT (ogg, "page is not BOS page"); + break; + } + + if (chain == NULL) { + chain = gst_ogg_chain_new (ogg); + chain->offset = offset; + } + + serial = ogg_page_serialno (&op); + if (gst_ogg_chain_get_stream (chain, serial) != NULL) { + GST_WARNING_OBJECT (ogg, "found serial %08lx BOS page twice, ignoring", + serial); + continue; + } + + pad = gst_ogg_chain_new_stream (chain, serial); + gst_ogg_pad_submit_page (pad, &op); + } + + if (ret != GST_FLOW_OK || chain == NULL) { + if (ret != GST_FLOW_UNEXPECTED) { + GST_WARNING_OBJECT (ogg, "failed to read chain"); + } else { + GST_DEBUG_OBJECT (ogg, "done reading chains"); + } + if (chain) { + gst_ogg_chain_free (chain); + } + if (res_chain) + *res_chain = NULL; + return ret; + } + + chain->have_bos = TRUE; + GST_LOG_OBJECT (ogg, "read bos pages, init decoder now"); + + /* now read pages until we receive a buffer from each of the + * stream decoders, this will tell us the timestamp of the + * first packet in the chain then */ + + /* save the offset to the first non bos page in the chain: if searching for + * pad->first_time we read past the end of the chain, we'll seek back to this + * position + */ + offset = ogg->offset; + + done = FALSE; + while (!done) { + glong serial; + gboolean known_serial = FALSE; + GstFlowReturn ret; + + serial = ogg_page_serialno (&op); + done = TRUE; + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + GST_LOG_OBJECT (ogg, "serial %08x time %" GST_TIME_FORMAT, pad->serialno, + GST_TIME_ARGS (pad->start_time)); + + if (pad->serialno == serial) { + known_serial = TRUE; + + /* submit the page now, this will fill in the start_time when the + * internal decoder finds it */ + gst_ogg_pad_submit_page (pad, &op); + + if (!pad->is_skeleton && pad->start_time == -1 && ogg_page_eos (&op)) { + /* got EOS on a pad before we could find its start_time. + * We have no chance of finding a start_time for every pad so + * stop searching for the other start_time(s). + */ + done = TRUE; + break; + } + } + /* the timestamp will be filled in when we submit the pages */ + if (!pad->is_skeleton) + done &= (pad->start_time != GST_CLOCK_TIME_NONE); + + GST_LOG_OBJECT (ogg, "done %08x now %d", pad->serialno, done); + } + + /* we read a page not belonging to the current chain: seek back to the + * beginning of the chain + */ + if (!known_serial) { + GST_LOG_OBJECT (ogg, "unknown serial %08lx", serial); + gst_ogg_demux_seek (ogg, offset); + break; + } + + if (!done) { + ret = gst_ogg_demux_get_next_page (ogg, &op, -1, NULL); + if (ret != GST_FLOW_OK) + break; + } + } + GST_LOG_OBJECT (ogg, "done reading chain"); + /* now we can fill in the missing info using queries */ + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + GstFormat target; + + if (pad->is_skeleton) + continue; + + GST_LOG_OBJECT (ogg, "convert first granule %" G_GUINT64_FORMAT " to time ", + pad->first_granule); + + target = GST_FORMAT_TIME; + if (!gst_ogg_pad_query_convert (pad, + GST_FORMAT_DEFAULT, pad->first_granule, &target, + (gint64 *) & pad->first_time)) { + GST_WARNING_OBJECT (ogg, "could not convert granulepos to time"); + } else { + GST_LOG_OBJECT (ogg, "converted to first time %" GST_TIME_FORMAT, + GST_TIME_ARGS (pad->first_time)); + } + + pad->mode = GST_OGG_PAD_MODE_STREAMING; + } + + if (res_chain) + *res_chain = chain; + + return GST_FLOW_OK; +} + +/* read the last pages from the ogg stream to get the final + * page end_offsets. + */ +static GstFlowReturn +gst_ogg_demux_read_end_chain (GstOggDemux * ogg, GstOggChain * chain) +{ + gint64 begin = chain->end_offset; + gint64 end = begin; + gint64 last_granule = -1; + GstOggPad *last_pad = NULL; + GstFormat target; + GstFlowReturn ret; + gboolean done = FALSE; + ogg_page og; + gint i; + + while (!done) { + begin -= CHUNKSIZE; + if (begin < 0) + begin = 0; + + gst_ogg_demux_seek (ogg, begin); + + /* now continue reading until we run out of data, if we find a page + * start, we save it. It might not be the final page as there could be + * another page after this one. */ + while (ogg->offset < end) { + ret = gst_ogg_demux_get_next_page (ogg, &og, end - ogg->offset, NULL); + + if (ret == GST_FLOW_LIMIT) + break; + if (ret != GST_FLOW_OK) + return ret; + + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + if (pad->is_skeleton) + continue; + + if (pad->serialno == ogg_page_serialno (&og)) { + gint64 granulepos = ogg_page_granulepos (&og); + + if (last_granule < granulepos) { + last_granule = granulepos; + last_pad = pad; + } + done = TRUE; + break; + } + } + } + } + + target = GST_FORMAT_TIME; + if (last_granule == -1 || !gst_ogg_pad_query_convert (last_pad, + GST_FORMAT_DEFAULT, last_granule, &target, + (gint64 *) & chain->segment_stop)) { + GST_WARNING_OBJECT (ogg, "could not convert granulepos to time"); + chain->segment_stop = GST_CLOCK_TIME_NONE; + } + + return GST_FLOW_OK; +} + +/* find a pad with a given serial number + */ +static GstOggPad * +gst_ogg_demux_find_pad (GstOggDemux * ogg, int serialno) +{ + GstOggPad *pad; + gint i; + + /* first look in building chain if any */ + if (ogg->building_chain) { + pad = gst_ogg_chain_get_stream (ogg->building_chain, serialno); + if (pad) + return pad; + } + + /* then look in current chain if any */ + if (ogg->current_chain) { + pad = gst_ogg_chain_get_stream (ogg->current_chain, serialno); + if (pad) + return pad; + } + + for (i = 0; i < ogg->chains->len; i++) { + GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i); + + pad = gst_ogg_chain_get_stream (chain, serialno); + if (pad) + return pad; + } + return NULL; +} + +/* find a chain with a given serial number + */ +static GstOggChain * +gst_ogg_demux_find_chain (GstOggDemux * ogg, int serialno) +{ + GstOggPad *pad; + + pad = gst_ogg_demux_find_pad (ogg, serialno); + if (pad) { + return pad->chain; + } + return NULL; +} + +/* returns TRUE if all streams have valid start time */ +static gboolean +gst_ogg_demux_collect_chain_info (GstOggDemux * ogg, GstOggChain * chain) +{ + gint i; + gboolean res = TRUE; + + chain->total_time = GST_CLOCK_TIME_NONE; + chain->segment_start = G_MAXUINT64; + + GST_DEBUG_OBJECT (ogg, "trying to collect chain info"); + + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + if (pad->is_skeleton) + continue; + + /* can do this if the pad start time is not defined */ + if (pad->start_time == GST_CLOCK_TIME_NONE) + res = FALSE; + else + chain->segment_start = MIN (chain->segment_start, pad->start_time); + } + + if (chain->segment_stop != GST_CLOCK_TIME_NONE + && chain->segment_start != G_MAXUINT64) + chain->total_time = chain->segment_stop - chain->segment_start; + + GST_DEBUG_OBJECT (ogg, "return %d", res); + + return res; +} + +static void +gst_ogg_demux_collect_info (GstOggDemux * ogg) +{ + gint i; + + /* collect all info */ + ogg->total_time = 0; + + for (i = 0; i < ogg->chains->len; i++) { + GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i); + + chain->begin_time = ogg->total_time; + + gst_ogg_demux_collect_chain_info (ogg, chain); + + ogg->total_time += chain->total_time; + } + gst_segment_set_duration (&ogg->segment, GST_FORMAT_TIME, ogg->total_time); +} + +/* find all the chains in the ogg file, this reads the first and + * last page of the ogg stream, if they match then the ogg file has + * just one chain, else we do a binary search for all chains. + */ +static GstFlowReturn +gst_ogg_demux_find_chains (GstOggDemux * ogg) +{ + ogg_page og; + GstPad *peer; + GstFormat format; + gboolean res; + gulong serialno; + GstOggChain *chain; + GstFlowReturn ret; + + /* get peer to figure out length */ + if ((peer = gst_pad_get_peer (ogg->sinkpad)) == NULL) + goto no_peer; + + /* find length to read last page, we store this for later use. */ + format = GST_FORMAT_BYTES; + res = gst_pad_query_duration (peer, &format, &ogg->length); + gst_object_unref (peer); + if (!res || ogg->length <= 0) + goto no_length; + + GST_DEBUG_OBJECT (ogg, "file length %" G_GINT64_FORMAT, ogg->length); + + /* read chain from offset 0, this is the first chain of the + * ogg file. */ + gst_ogg_demux_seek (ogg, 0); + ret = gst_ogg_demux_read_chain (ogg, &chain); + if (ret != GST_FLOW_OK) + goto no_first_chain; + + /* read page from end offset, we use this page to check if its serial + * number is contained in the first chain. If this is the case then + * this ogg is not a chained ogg and we can skip the scanning. */ + gst_ogg_demux_seek (ogg, ogg->length); + ret = gst_ogg_demux_get_prev_page (ogg, &og, NULL); + if (ret != GST_FLOW_OK) + goto no_last_page; + + serialno = ogg_page_serialno (&og); + + if (!gst_ogg_chain_has_stream (chain, serialno)) { + /* the last page is not in the first stream, this means we should + * find all the chains in this chained ogg. */ + ret = + gst_ogg_demux_bisect_forward_serialno (ogg, 0, 0, ogg->length, chain, + 0); + } else { + /* we still call this function here but with an empty range so that + * we can reuse the setup code in this routine. */ + ret = + gst_ogg_demux_bisect_forward_serialno (ogg, 0, ogg->length, ogg->length, + chain, 0); + } + if (ret != GST_FLOW_OK) + goto done; + + /* all fine, collect and print */ + gst_ogg_demux_collect_info (ogg); + + /* dump our chains and streams */ + gst_ogg_print (ogg); + +done: + return ret; + + /*** error cases ***/ +no_peer: + { + GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), ("we don't have a peer")); + return GST_FLOW_NOT_LINKED; + } +no_length: + { + GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), ("can't get file length")); + return GST_FLOW_NOT_SUPPORTED; + } +no_first_chain: + { + GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), ("can't get first chain")); + return GST_FLOW_ERROR; + } +no_last_page: + { + GST_DEBUG_OBJECT (ogg, "can't get last page"); + if (chain) + gst_ogg_chain_free (chain); + return ret; + } +} + +static GstFlowReturn +gst_ogg_demux_handle_page (GstOggDemux * ogg, ogg_page * page) +{ + GstOggPad *pad; + gint64 granule; + guint serialno; + GstFlowReturn result = GST_FLOW_OK; + + serialno = ogg_page_serialno (page); + granule = ogg_page_granulepos (page); + + GST_LOG_OBJECT (ogg, + "processing ogg page (serial %08x, pageno %ld, granulepos %" + G_GINT64_FORMAT ", bos %d)", + serialno, ogg_page_pageno (page), granule, ogg_page_bos (page)); + + if (ogg_page_bos (page)) { + GstOggChain *chain; + + /* first page */ + /* see if we know about the chain already */ + chain = gst_ogg_demux_find_chain (ogg, serialno); + if (chain) { + GstEvent *event; + + /* create the newsegment event we are going to send out */ + event = gst_event_new_new_segment (FALSE, ogg->segment.rate, + GST_FORMAT_TIME, chain->segment_start, chain->segment_stop, + chain->begin_time); + + GST_DEBUG_OBJECT (ogg, + "segment: start %" GST_TIME_FORMAT ", stop %" GST_TIME_FORMAT + ", time %" GST_TIME_FORMAT, GST_TIME_ARGS (chain->segment_start), + GST_TIME_ARGS (chain->segment_stop), + GST_TIME_ARGS (chain->begin_time)); + + /* activate it as it means we have a non-header */ + gst_ogg_demux_activate_chain (ogg, chain, event); + pad = gst_ogg_demux_find_pad (ogg, serialno); + } else { + GstClockTime chain_time; + GstOggChain *current_chain; + gint64 current_time; + + /* this can only happen in non-seekabe mode */ + if (ogg->seekable) + goto unknown_chain; + + current_chain = ogg->current_chain; + current_time = ogg->segment.last_stop; + + if (current_chain) { + /* remove existing pads */ + gst_ogg_demux_deactivate_current_chain (ogg); + } + /* time of new chain is current time */ + chain_time = current_time; + + if (ogg->building_chain == NULL) { + GstOggChain *newchain; + + newchain = gst_ogg_chain_new (ogg); + newchain->offset = 0; + /* set new chain begin time aligned with end time of old chain */ + newchain->begin_time = chain_time; + GST_DEBUG_OBJECT (ogg, "new chain, begin time %" GST_TIME_FORMAT, + GST_TIME_ARGS (chain_time)); + + /* and this is the one we are building now */ + ogg->building_chain = newchain; + } + pad = gst_ogg_chain_new_stream (ogg->building_chain, serialno); + } + } else { + pad = gst_ogg_demux_find_pad (ogg, serialno); + } + if (pad) { + result = gst_ogg_pad_submit_page (pad, page); + } else { + /* no pad, this is pretty fatal. This means an ogg page without bos + * has been seen for this serialno. could just ignore it too... */ + goto unknown_pad; + } + return result; + + /* ERRORS */ +unknown_chain: + { + GST_ELEMENT_ERROR (ogg, STREAM, DECODE, + (NULL), ("unknown ogg chain for serial %08x detected", serialno)); + return GST_FLOW_ERROR; + } +unknown_pad: + { + GST_ELEMENT_ERROR (ogg, STREAM, DECODE, + (NULL), ("unknown ogg pad for serial %08x detected", serialno)); + return GST_FLOW_ERROR; + } +} + +/* streaming mode, receive a buffer, parse it, create pads for + * the serialno, submit pages and packets to the oggpads + */ +static GstFlowReturn +gst_ogg_demux_chain (GstPad * pad, GstBuffer * buffer) +{ + GstOggDemux *ogg; + gint ret; + GstFlowReturn result = GST_FLOW_OK; + + ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (pad)); + + GST_DEBUG_OBJECT (ogg, "chain"); + result = gst_ogg_demux_submit_buffer (ogg, buffer); + + while (result == GST_FLOW_OK) { + ogg_page page; + + ret = ogg_sync_pageout (&ogg->sync, &page); + if (ret == 0) + /* need more data */ + break; + if (ret == -1) { + /* discontinuity in the pages */ + GST_DEBUG_OBJECT (ogg, "discont in page found, continuing"); + } else { + result = gst_ogg_demux_handle_page (ogg, &page); + } + } + return result; +} + +static void +gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event) +{ + GstOggChain *chain = ogg->current_chain; + + if (chain) { + gint i; + + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + gst_event_ref (event); + GST_DEBUG_OBJECT (ogg, "Pushing event on pad %p", pad); + gst_pad_push_event (GST_PAD (pad), event); + } + } + gst_event_unref (event); +} + +static GstFlowReturn +gst_ogg_demux_combine_flows (GstOggDemux * ogg, GstOggPad * pad, + GstFlowReturn ret) +{ + GstOggChain *chain; + + /* store the value */ + pad->last_ret = ret; + + /* any other error that is not-linked can be returned right + * away */ + if (ret != GST_FLOW_NOT_LINKED) + goto done; + + /* only return NOT_LINKED if all other pads returned NOT_LINKED */ + chain = ogg->current_chain; + if (chain) { + gint i; + + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *opad = g_array_index (chain->streams, GstOggPad *, i); + + ret = opad->last_ret; + /* some other return value (must be SUCCESS but we can return + * other values as well) */ + if (ret != GST_FLOW_NOT_LINKED) + goto done; + } + /* if we get here, all other pads were unlinked and we return + * NOT_LINKED then */ + } +done: + return ret; +} + +static GstFlowReturn +gst_ogg_demux_loop_forward (GstOggDemux * ogg) +{ + GstFlowReturn ret; + GstBuffer *buffer; + + if (ogg->offset == ogg->length) { + GST_LOG_OBJECT (ogg, "no more data to pull %" G_GINT64_FORMAT + " == %" G_GINT64_FORMAT, ogg->offset, ogg->length); + ret = GST_FLOW_UNEXPECTED; + goto done; + } + + GST_LOG_OBJECT (ogg, "pull data %" G_GINT64_FORMAT, ogg->offset); + ret = gst_pad_pull_range (ogg->sinkpad, ogg->offset, CHUNKSIZE, &buffer); + if (ret != GST_FLOW_OK) { + GST_LOG_OBJECT (ogg, "Failed pull_range"); + goto done; + } + + ogg->offset += GST_BUFFER_SIZE (buffer); + + if (G_UNLIKELY (ogg->newsegment)) { + gst_ogg_demux_send_event (ogg, ogg->newsegment); + ogg->newsegment = NULL; + } + + ret = gst_ogg_demux_chain (ogg->sinkpad, buffer); + if (ret != GST_FLOW_OK) { + GST_LOG_OBJECT (ogg, "Failed demux_chain"); + goto done; + } + + /* check for the end of the segment */ + if (ogg->segment.stop != -1 && ogg->segment.last_stop != -1) { + if (ogg->segment.last_stop > ogg->segment.stop) { + ret = GST_FLOW_UNEXPECTED; + goto done; + } + } +done: + return ret; +} + +/* reverse mode. + * + * We read the pages backwards and send the packets forwards. The first packet + * in the page will be pushed with the DISCONT flag set. + * + * Special care has to be taken for continued pages, which we can only decode + * when we have the previous page(s). + */ +static GstFlowReturn +gst_ogg_demux_loop_reverse (GstOggDemux * ogg) +{ + GstFlowReturn ret; + ogg_page page; + gint64 offset; + + if (ogg->offset == 0) { + GST_LOG_OBJECT (ogg, "no more data to pull %" G_GINT64_FORMAT + " == 0", ogg->offset); + ret = GST_FLOW_UNEXPECTED; + goto done; + } + + GST_LOG_OBJECT (ogg, "read page from %" G_GINT64_FORMAT, ogg->offset); + ret = gst_ogg_demux_get_prev_page (ogg, &page, &offset); + if (ret != GST_FLOW_OK) + goto done; + + ogg->offset = offset; + + if (G_UNLIKELY (ogg->newsegment)) { + gst_ogg_demux_send_event (ogg, ogg->newsegment); + ogg->newsegment = NULL; + } + + ret = gst_ogg_demux_handle_page (ogg, &page); + if (ret != GST_FLOW_OK) + goto done; + + /* check for the end of the segment */ + if (ogg->segment.start != -1 && ogg->segment.last_stop != -1) { + if (ogg->segment.last_stop <= ogg->segment.start) { + ret = GST_FLOW_UNEXPECTED; + goto done; + } + } +done: + return ret; +} + +/* random access code + * + * - first find all the chains and streams by scanning the file. + * - then get and chain buffers, just like the streaming case. + * - when seeking, we can use the chain info to perform the seek. + */ +static void +gst_ogg_demux_loop (GstOggPad * pad) +{ + GstOggDemux *ogg; + GstFlowReturn ret; + GstEvent *event; + + ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (pad)); + + if (ogg->need_chains) { + gboolean res; + + /* this is the only place where we write chains and thus need to lock. */ + GST_CHAIN_LOCK (ogg); + ret = gst_ogg_demux_find_chains (ogg); + GST_CHAIN_UNLOCK (ogg); + if (ret != GST_FLOW_OK) + goto chain_read_failed; + + ogg->need_chains = FALSE; + + GST_OBJECT_LOCK (ogg); + ogg->running = TRUE; + event = ogg->event; + ogg->event = NULL; + GST_OBJECT_UNLOCK (ogg); + + /* and seek to configured positions without FLUSH */ + res = gst_ogg_demux_perform_seek (ogg, event); + if (event) + gst_event_unref (event); + + if (!res) + goto seek_failed; + } + + if (ogg->segment.rate >= 0.0) + ret = gst_ogg_demux_loop_forward (ogg); + else + ret = gst_ogg_demux_loop_reverse (ogg); + + if (ret != GST_FLOW_OK) + goto pause; + + return; + + /* ERRORS */ +chain_read_failed: + { + /* error was posted */ + goto pause; + } +seek_failed: + { + GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), + ("failed to start demuxing ogg")); + ret = GST_FLOW_ERROR; + goto pause; + } +pause: + { + const gchar *reason = gst_flow_get_name (ret); + + GST_LOG_OBJECT (ogg, "pausing task, reason %s", reason); + ogg->segment_running = FALSE; + gst_pad_pause_task (ogg->sinkpad); + + if (GST_FLOW_IS_FATAL (ret) || ret == GST_FLOW_NOT_LINKED) { + if (ret == GST_FLOW_UNEXPECTED) { + /* perform EOS logic */ + if (ogg->segment.flags & GST_SEEK_FLAG_SEGMENT) { + gint64 stop; + + /* for segment playback we need to post when (in stream time) + * we stopped, this is either stop (when set) or the duration. */ + if ((stop = ogg->segment.stop) == -1) + stop = ogg->segment.duration; + + GST_LOG_OBJECT (ogg, "Sending segment done, at end of segment"); + gst_element_post_message (GST_ELEMENT (ogg), + gst_message_new_segment_done (GST_OBJECT (ogg), GST_FORMAT_TIME, + stop)); + } else { + /* normal playback, send EOS to all linked pads */ + GST_LOG_OBJECT (ogg, "Sending EOS, at end of stream"); + gst_ogg_demux_send_event (ogg, gst_event_new_eos ()); + } + } else { + GST_ELEMENT_ERROR (ogg, STREAM, FAILED, + (_("Internal data stream error.")), + ("stream stopped, reason %s", reason)); + gst_ogg_demux_send_event (ogg, gst_event_new_eos ()); + } + } + return; + } +} + +static void +gst_ogg_demux_clear_chains (GstOggDemux * ogg) +{ + gint i; + + gst_ogg_demux_deactivate_current_chain (ogg); + + GST_CHAIN_LOCK (ogg); + for (i = 0; i < ogg->chains->len; i++) { + GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i); + + gst_ogg_chain_free (chain); + } + ogg->chains = g_array_set_size (ogg->chains, 0); + GST_CHAIN_UNLOCK (ogg); +} + +/* this function is called when the pad is activated and should start + * processing data. + * + * We check if we can do random access to decide if we work push or + * pull based. + */ +static gboolean +gst_ogg_demux_sink_activate (GstPad * sinkpad) +{ + if (gst_pad_check_pull_range (sinkpad)) { + GST_DEBUG_OBJECT (sinkpad, "activating pull"); + return gst_pad_activate_pull (sinkpad, TRUE); + } else { + GST_DEBUG_OBJECT (sinkpad, "activating push"); + return gst_pad_activate_push (sinkpad, TRUE); + } +} + +/* this function gets called when we activate ourselves in push mode. + * We cannot seek (ourselves) in the stream */ +static gboolean +gst_ogg_demux_sink_activate_push (GstPad * sinkpad, gboolean active) +{ + GstOggDemux *ogg; + + ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (sinkpad)); + + ogg->seekable = FALSE; + + return TRUE; +} + +/* this function gets called when we activate ourselves in pull mode. + * We can perform random access to the resource and we start a task + * to start reading */ +static gboolean +gst_ogg_demux_sink_activate_pull (GstPad * sinkpad, gboolean active) +{ + GstOggDemux *ogg; + + ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (sinkpad)); + + if (active) { + ogg->need_chains = TRUE; + ogg->seekable = TRUE; + + return gst_pad_start_task (sinkpad, (GstTaskFunction) gst_ogg_demux_loop, + sinkpad); + } else { + return gst_pad_stop_task (sinkpad); + } +} + +static GstStateChangeReturn +gst_ogg_demux_change_state (GstElement * element, GstStateChange transition) +{ + GstOggDemux *ogg; + GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE; + + ogg = GST_OGG_DEMUX (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + ogg->basetime = 0; + ogg->have_fishead = FALSE; + ogg_sync_init (&ogg->sync); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + ogg_sync_reset (&ogg->sync); + ogg->current_granule = -1; + ogg->running = FALSE; + ogg->segment_running = FALSE; + gst_segment_init (&ogg->segment, GST_FORMAT_TIME); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + default: + break; + } + + result = parent_class->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_ogg_demux_clear_chains (ogg); + GST_OBJECT_LOCK (ogg); + ogg->running = FALSE; + ogg->segment_running = FALSE; + ogg->have_fishead = FALSE; + GST_OBJECT_UNLOCK (ogg); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + ogg_sync_clear (&ogg->sync); + break; + default: + break; + } + return result; +} + +static GstClockTime +gst_annodex_granule_to_time (gint64 granulepos, gint64 granulerate_n, + gint64 granulerate_d, guint8 granuleshift) +{ + gint64 keyindex, keyoffset; + gint64 granulerate; + GstClockTime res; + + if (granulepos == 0 || granulerate_n == 0 || granulerate_d == 0) + return 0; + + if (granuleshift != 0) { + keyindex = granulepos >> granuleshift; + keyoffset = granulepos - (keyindex << granuleshift); + granulepos = keyindex + keyoffset; + } + + /* GST_SECOND / (granulerate_n / granulerate_d) */ + granulerate = gst_util_uint64_scale (GST_SECOND, + granulerate_d, granulerate_n); + res = gst_util_uint64_scale (granulepos, granulerate, 1); + return res; +} + +gboolean +gst_ogg_demux_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_ogg_demux_debug, "oggdemux", 0, "ogg demuxer"); + GST_DEBUG_CATEGORY_INIT (gst_ogg_demux_setup_debug, "oggdemux_setup", 0, + "ogg demuxer setup stage when parsing pipeline"); + +#if ENABLE_NLS + GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, + LOCALEDIR); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); +#endif + + return gst_element_register (plugin, "oggdemux", GST_RANK_PRIMARY, + GST_TYPE_OGG_DEMUX); +} + +/* prints all info about the element */ +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT gst_ogg_demux_setup_debug + +#ifdef GST_DISABLE_GST_DEBUG + +static void +gst_ogg_print (GstOggDemux * ogg) +{ + /* NOP */ +} + +#else /* !GST_DISABLE_GST_DEBUG */ + +static void +gst_ogg_print (GstOggDemux * ogg) +{ + guint j, i; + + GST_INFO_OBJECT (ogg, "%u chains", ogg->chains->len); + GST_INFO_OBJECT (ogg, " total time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->total_time)); + + for (i = 0; i < ogg->chains->len; i++) { + GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i); + + GST_INFO_OBJECT (ogg, " chain %d (%u streams):", i, chain->streams->len); + GST_INFO_OBJECT (ogg, " offset: %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT, + chain->offset, chain->end_offset); + GST_INFO_OBJECT (ogg, " begin time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (chain->begin_time)); + GST_INFO_OBJECT (ogg, " total time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (chain->total_time)); + GST_INFO_OBJECT (ogg, " segment start: %" GST_TIME_FORMAT, + GST_TIME_ARGS (chain->segment_start)); + GST_INFO_OBJECT (ogg, " segment stop: %" GST_TIME_FORMAT, + GST_TIME_ARGS (chain->segment_stop)); + + for (j = 0; j < chain->streams->len; j++) { + GstOggPad *stream = g_array_index (chain->streams, GstOggPad *, j); + + GST_INFO_OBJECT (ogg, " stream %08x:", stream->serialno); + GST_INFO_OBJECT (ogg, " start time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (stream->start_time)); + GST_INFO_OBJECT (ogg, " first granulepos: %" G_GINT64_FORMAT, + stream->first_granule); + GST_INFO_OBJECT (ogg, " first time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (stream->first_time)); + } + } +} +#endif /* GST_DISABLE_GST_DEBUG */