oggmux: determine granulepos metadata using stream mapper whenever possible
authorMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Mon, 6 Jun 2011 10:48:23 +0000 (12:48 +0200)
committerMark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
Mon, 6 Jun 2011 11:02:49 +0000 (13:02 +0200)
... which unfortunately is not the case for all types, but at least so for
most common ones.

ext/ogg/gstoggmux.c
ext/ogg/gstoggmux.h

index 58dd749..b8815a1 100644 (file)
@@ -82,11 +82,13 @@ enum
 /* set to 0.5 seconds by default */
 #define DEFAULT_MAX_DELAY       G_GINT64_CONSTANT(500000000)
 #define DEFAULT_MAX_PAGE_DELAY  G_GINT64_CONSTANT(500000000)
+#define DEFAULT_MAX_TOLERANCE   G_GINT64_CONSTANT(40000000)
 enum
 {
   ARG_0,
   ARG_MAX_DELAY,
   ARG_MAX_PAGE_DELAY,
+  ARG_MAX_TOLERANCE
 };
 
 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
@@ -204,6 +206,11 @@ gst_ogg_mux_class_init (GstOggMuxClass * klass)
           "Maximum delay for sending out a page", 0, G_MAXUINT64,
           DEFAULT_MAX_PAGE_DELAY,
           (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, ARG_MAX_TOLERANCE,
+      g_param_spec_uint64 ("max-tolerance", "Max time tolerance",
+          "Maximum timestamp difference for maintaining perfect granules",
+          0, G_MAXUINT64, DEFAULT_MAX_TOLERANCE,
+          (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   gstelement_class->change_state = gst_ogg_mux_change_state;
 
@@ -257,6 +264,7 @@ gst_ogg_mux_init (GstOggMux * ogg_mux)
 
   ogg_mux->max_delay = DEFAULT_MAX_DELAY;
   ogg_mux->max_page_delay = DEFAULT_MAX_PAGE_DELAY;
+  ogg_mux->max_tolerance = DEFAULT_MAX_TOLERANCE;
 
   gst_ogg_mux_clear (ogg_mux);
 }
@@ -446,6 +454,8 @@ gst_ogg_mux_request_new_pad (GstElement * element,
       oggpad->pagebuffers = g_queue_new ();
       oggpad->map.headers = NULL;
       oggpad->map.queued = NULL;
+      oggpad->next_granule = 0;
+      oggpad->keyframe_granule = -1;
 
       gst_segment_init (&oggpad->segment, GST_FORMAT_TIME);
 
@@ -774,6 +784,13 @@ gst_ogg_mux_decorate_buffer (GstOggMux * ogg_mux, GstOggPadData * pad,
     GstBuffer * buf)
 {
   GstClockTime time;
+  gint64 duration, granule, limit;
+  GstClockTime next_time;
+  GstClockTimeDiff diff;
+  ogg_packet packet;
+
+  /* ensure messing with metadata is ok */
+  buf = gst_buffer_make_metadata_writable (buf);
 
   /* convert time to running time, so we need no longer bother about that */
   time = GST_BUFFER_TIMESTAMP (buf);
@@ -783,12 +800,90 @@ gst_ogg_mux_decorate_buffer (GstOggMux * ogg_mux, GstOggPadData * pad,
       gst_buffer_unref (buf);
       return NULL;
     } else {
-      buf = gst_buffer_make_metadata_writable (buf);
       GST_BUFFER_TIMESTAMP (buf) = time;
     }
   }
 
+  /* now come up with granulepos stuff corresponding to time */
+  if (!pad->have_type ||
+      pad->map.granulerate_n <= 0 || pad->map.granulerate_d <= 0)
+    goto no_granule;
+
+  packet.packet = GST_BUFFER_DATA (buf);
+  packet.bytes = GST_BUFFER_SIZE (buf);
+  duration = gst_ogg_stream_get_packet_duration (&pad->map, &packet);
+
+  /* give up if no duration can be determined, relying on upstream */
+  if (G_UNLIKELY (duration < 0)) {
+    /* well, if some day we really could handle sparse input ... */
+    if (pad->map.is_sparse) {
+      limit = 1;
+      diff = 2;
+      goto resync;
+    }
+    GST_WARNING_OBJECT (pad->collect.pad,
+        "failed to determine packet duration");
+    goto no_granule;
+  }
+
+  GST_LOG_OBJECT (pad->collect.pad, "buffer ts %" GST_TIME_FORMAT
+      ", duration %" GST_TIME_FORMAT ", granule duration %" G_GINT64_FORMAT,
+      GST_TIME_ARGS (time), GST_TIME_ARGS (GST_BUFFER_DURATION (buf)),
+      duration);
+
+  /* determine granule corresponding to time,
+   * using the inverse of oggdemux' granule -> time */
+
+  /* see if interpolated granule matches good enough */
+  granule = pad->next_granule;
+  next_time = gst_ogg_stream_granule_to_time (&pad->map, pad->next_granule);
+  diff = GST_CLOCK_DIFF (next_time, time);
+
+  /* we tolerate deviation up to configured or within granule granularity */
+  limit = gst_ogg_stream_granule_to_time (&pad->map, 1) / 2;
+  limit = MAX (limit, ogg_mux->max_tolerance);
+
+  GST_LOG_OBJECT (pad->collect.pad, "expected granule %" G_GINT64_FORMAT " == "
+      "time %" GST_TIME_FORMAT " --> ts diff %" GST_TIME_FORMAT
+      " < tolerance %" GST_TIME_FORMAT " (?)",
+      granule, GST_TIME_ARGS (next_time), GST_TIME_ARGS (ABS (diff)),
+      GST_TIME_ARGS (limit));
+
+resync:
+  /* if not good enough, determine granule based on time */
+  if (diff > limit || diff < -limit) {
+    granule = gst_util_uint64_scale_round (time, pad->map.granulerate_n,
+        GST_SECOND * pad->map.granulerate_d);
+    GST_DEBUG_OBJECT (pad->collect.pad,
+        "resyncing to determined granule %" G_GINT64_FORMAT, granule);
+  }
+
+  if (pad->map.is_ogm || pad->map.is_sparse) {
+    pad->next_granule = granule;
+  } else {
+    granule += duration;
+    pad->next_granule = granule;
+  }
+
+  /* track previous keyframe */
+  if (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT))
+    pad->keyframe_granule = granule;
+
+  /* determine corresponding time and granulepos */
+  GST_BUFFER_OFFSET (buf) = gst_ogg_stream_granule_to_time (&pad->map, granule);
+  GST_BUFFER_OFFSET_END (buf) =
+      gst_ogg_stream_granule_to_granulepos (&pad->map, granule,
+      pad->keyframe_granule);
+
   return buf;
+
+  /* ERRORS */
+no_granule:
+  {
+    GST_DEBUG_OBJECT (pad->collect.pad, "could not determine granulepos, "
+        "falling back to upstream provided metadata");
+    return buf;
+  }
 }
 
 
@@ -894,6 +989,15 @@ gst_ogg_mux_queue_pads (GstOggMux * ogg_mux)
                 "got data buffer in control state, switching to data mode");
             /* this is a data buffer so switch to data state */
             pad->state = GST_OGG_PAD_STATE_DATA;
+
+            /* check if this type of stream allows generating granulepos
+             * metadata here, if not, upstream will have to provide */
+            if (gst_ogg_stream_granule_to_granulepos (&pad->map, 1, 1) < 0) {
+              GST_WARNING_OBJECT (data->pad, "can not generate metadata; "
+                  "relying on upstream");
+              /* disable metadata code path, otherwise not used anyway */
+              pad->map.granulerate_n = 0;
+            }
           }
         }
 
@@ -1683,6 +1787,9 @@ gst_ogg_mux_get_property (GObject * object,
     case ARG_MAX_PAGE_DELAY:
       g_value_set_uint64 (value, ogg_mux->max_page_delay);
       break;
+    case ARG_MAX_TOLERANCE:
+      g_value_set_uint64 (value, ogg_mux->max_tolerance);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1704,6 +1811,9 @@ gst_ogg_mux_set_property (GObject * object,
     case ARG_MAX_PAGE_DELAY:
       ogg_mux->max_page_delay = g_value_get_uint64 (value);
       break;
+    case ARG_MAX_TOLERANCE:
+      ogg_mux->max_tolerance = g_value_get_uint64 (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
index 933226d..b0ba578 100644 (file)
@@ -82,6 +82,9 @@ typedef struct
   gboolean prev_delta;          /* was the previous buffer a delta frame */
   gboolean data_pushed;         /* whether we pushed data already */
 
+  gint64  next_granule;         /* expected granule of next buffer ts */
+  gint64  keyframe_granule;     /* granule of last preceding keyframe */
+
   GstPadEventFunction collect_event;
 
   gboolean always_flush_page;
@@ -123,6 +126,7 @@ struct _GstOggMux
 
   guint64 max_delay;
   guint64 max_page_delay;
+  guint64 max_tolerance;
 
   GstOggPadData *delta_pad;     /* when a delta frame is detected on a stream, we mark
                                    pages as delta frames up to the page that has the