From 99944a5a81c22d0647164d322f5e8f1a8233e10a Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Mon, 6 Jun 2011 12:48:23 +0200 Subject: [PATCH] oggmux: determine granulepos metadata using stream mapper whenever possible ... which unfortunately is not the case for all types, but at least so for most common ones. --- ext/ogg/gstoggmux.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++- ext/ogg/gstoggmux.h | 4 ++ 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/ext/ogg/gstoggmux.c b/ext/ogg/gstoggmux.c index 58dd749..b8815a1 100644 --- a/ext/ogg/gstoggmux.c +++ b/ext/ogg/gstoggmux.c @@ -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; diff --git a/ext/ogg/gstoggmux.h b/ext/ogg/gstoggmux.h index 933226d..b0ba578 100644 --- a/ext/ogg/gstoggmux.h +++ b/ext/ogg/gstoggmux.h @@ -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 -- 2.7.4