Index: ext/ogg/gstoggdemux.c
===================================================================
RCS file: /cvs/gstreamer/gst-plugins-base/ext/ogg/gstoggdemux.c,v
retrieving revision 1.111
diff -u -w -p -r1.111 gstoggdemux.c
--- ext/ogg/gstoggdemux.c	31 Oct 2005 18:35:45 -0000	1.111
+++ ext/ogg/gstoggdemux.c	6 Nov 2005 21:57:27 -0000
@@ -111,6 +111,12 @@ struct _GstOggPad
 
   GList *headers;
 
+  gboolean is_skeleton;
+  gboolean have_fisbone;
+  gint64 granulerate_n;
+  gint64 granulerate_d;
+  guint granuleshift;
+  
   gint serialno;
   gint64 packetno;
   gint64 current_granule;
@@ -162,6 +168,10 @@ struct _GstOggDemux
   gint64 current_granule;
   GstClockTime current_time;
 
+  /* annodex stuff */
+  gboolean have_fishead;
+  gint64 basetime;
+  
   /* ogg stuff */
   ogg_sync_state sync;
 };
@@ -199,6 +209,15 @@ static gboolean gst_ogg_pad_src_query (G
 static gboolean gst_ogg_pad_event (GstPad * pad, GstEvent * event);
 static GstCaps *gst_ogg_pad_getcaps (GstPad * pad);
 static GstCaps *gst_ogg_type_find (ogg_packet * packet);
+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 GstPadClass *ogg_pad_parent_class = NULL;
 
@@ -557,6 +576,151 @@ compare_ranks (GstPluginFeature * f1, Gs
       gst_plugin_feature_get_name (f1));
 }
 
+/* function called when a skeleton fishead is found */
+static void
+gst_ogg_pad_found_fishead (GstPad * pad, GObject * fishead)
+{
+  gint64 basetime_n, basetime_d;
+  GstOggPad *oggpad = gst_pad_get_element_private (pad);
+  GstOggDemux *ogg = oggpad->ogg;
+  
+  ogg->have_fishead = TRUE;
+  oggpad->is_skeleton = TRUE;
+  oggpad->start_time = GST_CLOCK_TIME_NONE;
+  oggpad->first_granule = -1;
+  oggpad->first_time = GST_CLOCK_TIME_NONE;
+  
+  g_object_get (fishead, "base-time-numerator", &basetime_n,
+      "base-time-denominator", &basetime_d, NULL);;
+  ogg->basetime = GST_SECOND * basetime_n / basetime_d;
+}
+
+/* function called when a skeleton fisbone is found */
+static void
+gst_ogg_pad_found_fisbone (GstPad * pad, GObject * fisbone)
+{ 
+  guint32 serialno;
+  GstOggPad *cur;
+  GstOggPad *oggpad = gst_pad_get_element_private (pad);
+
+  g_object_get (fisbone, "serial-number", &serialno, NULL);
+
+  cur = gst_ogg_chain_get_stream (oggpad->chain, serialno);  
+
+  if (cur) {
+    gchar *content_type;
+    gint64 start_granule;
+    GstCaps *caps;
+    
+    if (cur->have_fisbone)
+      /* the pad is already configured */
+      return;
+
+    cur->have_fisbone = TRUE;
+
+    g_object_get (fisbone,
+        "granule-rate-numerator", &cur->granulerate_n,
+        "granule-rate-denominator", &cur->granulerate_d,
+        "granule-shift", &cur->granuleshift,
+        "granule-start", &start_granule,
+        "content-type", &content_type,
+        NULL);
+    
+    cur->start_time = gst_annodex_granule_to_time (start_granule, 
+        cur->granulerate_n, cur->granulerate_d,
+        cur->granuleshift);
+    
+    caps = gst_caps_from_string (content_type);
+    gst_pad_set_caps (GST_PAD (cur), caps);
+    
+    gst_caps_unref (caps);
+    g_free (content_type);
+  } else {
+    g_warning ("found fisbone for an unknown pad");
+  }
+ 
+}
+
+/* function called by the internal decoder element when it pushes an event. We
+ * use it to detect GST_TAG_SKELETON_FISHEAD and GST_TAG_SKELETON_FISBONE tags.
+ */
+static gboolean
+gst_ogg_pad_internal_event (GstPad * pad, GstEvent * event)
+{
+  GstOggPad *oggpad;
+  GstOggDemux *ogg;
+  gboolean res;
+
+  oggpad = gst_pad_get_element_private (pad);
+  ogg = GST_OGG_DEMUX (oggpad->ogg);
+  
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_TAG:
+    {
+      GstTagList *tags;
+      const GValue *tag_val;
+
+      gst_event_parse_tag (event, &tags);
+    
+      tag_val = gst_tag_list_get_value_index (tags,
+          GST_TAG_SKELETON_FISHEAD, 0); 
+      if (tag_val) {
+        gst_ogg_pad_found_fishead (pad, g_value_get_object (tag_val));
+      } else {
+        tag_val = gst_tag_list_get_value_index (tags,
+            GST_TAG_SKELETON_FISBONE, 0);
+        
+        if (tag_val)
+          gst_ogg_pad_found_fisbone (pad, g_value_get_object (tag_val));
+      }
+
+      res = gst_pad_event_default (pad, event);
+      break;
+    }
+    default:
+      res = gst_pad_event_default (pad, event);
+      break;
+  }
+
+  return res;
+}
+
+/* function called to convert a granuleops 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;
+  
+  g_return_val_if_fail (pad->have_fisbone || pad->elem_pad != NULL, FALSE);
+  
+  if (src_val == -1) {
+      *dest_val = -1;
+      return TRUE;
+  }
+
+  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 {
+        res = gst_pad_query_convert (pad->elem_pad, src_format, src_val,
+          dest_format, dest_val);
+      }
+
+      break;
+    default:
+      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 
  */
@@ -636,6 +800,7 @@ gst_ogg_pad_typefind (GstOggPad * pad, o
         pad->elem_out =
             gst_pad_new_from_template (gst_static_pad_template_get
             (&internaltemplate), "internal");
+        gst_pad_set_event_function (pad->elem_out, gst_ogg_pad_internal_event);
         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);
@@ -718,21 +883,6 @@ gst_ogg_demux_queue_data (GstOggPad * pa
   return GST_FLOW_OK;
 }
 
-static gboolean
-gst_ogg_pad_query_convert (GstOggPad * ogg_pad, GstFormat from_format,
-    gint64 from_value, GstFormat * to_format, gint64 * to_value)
-{
-  if (from_value == -1) {
-    *to_value = -1;
-    return TRUE;
-  }
-
-  g_return_val_if_fail (ogg_pad->elem_pad != NULL, FALSE);
-
-  return gst_pad_query_convert (ogg_pad->elem_pad,
-      from_format, from_value, to_format, to_value);
-}
-
 /* send packet to internal element */
 static GstFlowReturn
 gst_ogg_demux_chain_peer (GstOggPad * pad, ogg_packet * packet)
@@ -796,7 +946,7 @@ gst_ogg_pad_submit_packet (GstOggPad * p
   GST_DEBUG_OBJECT (ogg, "%p submit packet serial %08lx", pad, pad->serialno);
 
   /* first packet */
-  if (!pad->have_type) {
+  if (!ogg->have_fishead && !pad->have_type) {
     gst_ogg_pad_typefind (pad, packet);
     pad->have_type = TRUE;
   }
@@ -811,8 +961,8 @@ gst_ogg_pad_submit_packet (GstOggPad * p
   }
 
   /* no start time known, stream to internal plugin to
-   * get time */
-  if (pad->start_time == GST_CLOCK_TIME_NONE) {
+   * 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
@@ -837,6 +987,8 @@ gst_ogg_pad_submit_packet (GstOggPad * p
           segment_stop = chain->segment_stop;
         }
 
+        segment_start -= ogg->basetime;
+        
         /* create the discont event we are going to send out */
         event = gst_event_new_newsegment (FALSE, ogg->segment_rate,
             GST_FORMAT_TIME, segment_start, segment_stop, 0);
@@ -1043,7 +1195,7 @@ static GstStaticPadTemplate ogg_demux_si
 GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
     GST_PAD_ALWAYS,
-    GST_STATIC_CAPS ("application/ogg")
+    GST_STATIC_CAPS ("application/ogg; application/x-annodex")
     );
 
 static void gst_ogg_demux_finalize (GObject * object);
@@ -1126,6 +1278,9 @@ gst_ogg_demux_init (GstOggDemux * ogg, G
   ogg->segment_play = FALSE;
   ogg->total_time = GST_CLOCK_TIME_NONE;
 
+  ogg->have_fishead = FALSE;
+  ogg->basetime = 0;
+  
   ogg->running = FALSE;
 }
 
@@ -1567,7 +1722,7 @@ gst_ogg_demux_perform_seek (GstOggDemux 
           continue;
 
         pad = gst_ogg_chain_get_stream (chain, ogg_page_serialno (&og));
-        if (pad == NULL)
+        if (pad == NULL || pad->is_skeleton)
           continue;
 
         format = GST_FORMAT_TIME;
@@ -1577,6 +1732,9 @@ gst_ogg_demux_perform_seek (GstOggDemux 
           g_warning ("could not convert granulepos to time");
           granuletime = target;
         } else {
+          if (granuletime < pad->first_time)
+            continue;
+
           granuletime -= pad->first_time;
         }
 
@@ -1631,7 +1789,8 @@ gst_ogg_demux_perform_seek (GstOggDemux 
     /* create the discont event we are going to send out */
     event = gst_event_new_newsegment (FALSE, 1.0,
         GST_FORMAT_TIME,
-        start + chain->segment_start, stop + chain->segment_start, start);
+        start + chain->segment_start - ogg->basetime,
+        stop + chain->segment_start, start);
 
     if (chain != ogg->current_chain) {
       /* switch to different chain, send discont on new chain */
@@ -1820,7 +1979,7 @@ gst_ogg_demux_read_chain (GstOggDemux * 
       if (pad->serialno == serial) {
         known_serial = TRUE;
 
-        if (pad->start_time == -1 && ogg_page_eos (&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).
@@ -1855,6 +2014,9 @@ gst_ogg_demux_read_chain (GstOggDemux * 
     GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
     GstFormat target;
 
+    if (pad->is_skeleton)
+      continue;
+    
     target = GST_FORMAT_TIME;
     if (!gst_ogg_pad_query_convert (pad,
             GST_FORMAT_DEFAULT, pad->first_granule, &target,
@@ -1903,6 +2065,9 @@ gst_ogg_demux_read_end_chain (GstOggDemu
         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);
 
@@ -1987,6 +2152,9 @@ gst_ogg_demux_collect_chain_info (GstOgg
   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)
       goto no_start_time;
@@ -2435,6 +2603,26 @@ gst_ogg_demux_change_state (GstElement *
   return result;
 }
 
+static GstClockTime
+gst_annodex_granule_to_time (gint64 granulepos, gint64 granulerate_n, 
+    gint64 granulerate_d, guint8 granuleshift)
+{
+  gint64 keyindex, keyoffset;
+  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;
+  }
+
+  res = GST_SECOND * (granulepos * granulerate_d) / granulerate_n;
+  return res;
+}
+
 /*** typefinding **************************************************************/
 /* ogg supports its own typefinding because the ogg spec defines that the first
  * packet of an ogg stream must identify the stream. Therefore ogg can use a
