From 4d20688af3376781944bddeb68a9e8b6106297c0 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Tue, 21 Sep 2010 10:57:04 +0200 Subject: [PATCH] baseparse: add index support --- gst/audioparsers/gstbaseparse.c | 212 ++++++++++++++++++++++++++++++++++++++++ gst/audioparsers/gstbaseparse.h | 3 + 2 files changed, 215 insertions(+) diff --git a/gst/audioparsers/gstbaseparse.c b/gst/audioparsers/gstbaseparse.c index 764711b..ca8fdb5 100644 --- a/gst/audioparsers/gstbaseparse.c +++ b/gst/audioparsers/gstbaseparse.c @@ -247,6 +247,18 @@ struct _GstBaseParsePrivate GList *pending_events; GstBuffer *cache; + + /* index entry storage, either ours or provided */ + GstIndex *index; + gint index_id; + gboolean own_index; + /* seek table entries only maintained if upstream is BYTE seekable */ + gboolean upstream_seekable; + /* minimum distance between two index entries */ + GstClockTimeDiff idx_interval; + /* ts and offset of last entry added */ + GstClockTime index_last_ts; + guint64 index_last_offset; }; static GstElementClass *parent_class = NULL; @@ -285,6 +297,9 @@ static GstStateChangeReturn gst_base_parse_change_state (GstElement * element, GstStateChange transition); static void gst_base_parse_reset (GstBaseParse * parse); +static void gst_base_parse_set_index (GstElement * element, GstIndex * index); +static GstIndex *gst_base_parse_get_index (GstElement * element); + static gboolean gst_base_parse_sink_activate (GstPad * sinkpad); static gboolean gst_base_parse_sink_activate_push (GstPad * pad, gboolean active); @@ -348,6 +363,11 @@ gst_base_parse_finalize (GObject * object) g_list_free (parse->priv->pending_events); parse->priv->pending_events = NULL; + if (parse->priv->index) { + gst_object_unref (parse->priv->index); + parse->priv->index = NULL; + } + G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -365,6 +385,8 @@ gst_base_parse_class_init (GstBaseParseClass * klass) gstelement_class = (GstElementClass *) klass; gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_base_parse_change_state); + gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_base_parse_set_index); + gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_base_parse_get_index); /* Default handlers */ klass->check_valid_frame = gst_base_parse_check_frame; @@ -459,6 +481,11 @@ gst_base_parse_reset (GstBaseParse * parse) parse->priv->avg_bitrate = 0; parse->priv->posted_avg_bitrate = 0; + parse->priv->index_last_ts = 0; + parse->priv->index_last_offset = 0; + parse->priv->upstream_seekable = FALSE; + parse->priv->idx_interval = 0; + if (parse->pending_segment) gst_event_unref (parse->pending_segment); @@ -687,6 +714,10 @@ gst_base_parse_sink_eventfunc (GstBaseParse * parse, GstEvent * event) gst_event_unref (event); event = gst_event_new_new_segment_full (update, rate, applied_rate, GST_FORMAT_TIME, 0, GST_CLOCK_TIME_NONE, 0); + } else { + /* not considered BYTE seekable if it is talking to us in TIME, + * whatever else it might claim */ + parse->priv->upstream_seekable = FALSE; } gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, @@ -1057,6 +1088,126 @@ gst_base_parse_update_bitrates (GstBaseParse * parse, GstBuffer * buffer) } /** + * gst_base_parse_add_index_entry: + * @parse: #GstBaseParse. + * @offset: offset of entry + * @ts: timestamp associated with offset + * @key: whether entry refers to keyframe + * @force: add entry disregarding sanity checks + * + * Adds an entry to the index associating @offset to @ts. It is recommended + * to only add keyframe entries. @force allows to bypass checks, such as + * whether the stream is (upstream) seekable, another entry is already "close" + * to the new entry, etc. + * + * Returns: #gboolean indicating whether entry was added + */ +gboolean +gst_base_parse_add_index_entry (GstBaseParse * parse, guint64 offset, + GstClockTime ts, gboolean key, gboolean force) +{ + gboolean ret = FALSE; + GstIndexAssociation associations[2]; + + GST_LOG_OBJECT (parse, "Adding key=%d index entry %" GST_TIME_FORMAT + " @ offset 0x%08" G_GINT64_MODIFIER "x", key, GST_TIME_ARGS (ts), offset); + + if (G_LIKELY (!force)) { + + if (!parse->priv->upstream_seekable) { + GST_DEBUG_OBJECT (parse, "upstream not seekable; discarding"); + goto exit; + } + + if (parse->priv->index_last_offset >= offset) { + GST_DEBUG_OBJECT (parse, "already have entries up to offset " + "0x%08" G_GINT64_MODIFIER "x", parse->priv->index_last_offset); + goto exit; + } + + if (GST_CLOCK_DIFF (parse->priv->index_last_ts, ts) < + parse->priv->idx_interval) { + GST_DEBUG_OBJECT (parse, "entry too close to last time %" GST_TIME_FORMAT, + GST_TIME_ARGS (parse->priv->index_last_ts)); + goto exit; + } + } + + associations[0].format = GST_FORMAT_TIME; + associations[0].value = ts; + associations[1].format = GST_FORMAT_BYTES; + associations[1].value = offset; + + /* index might change on-the-fly, although that would be nutty app ... */ + GST_OBJECT_LOCK (parse); + gst_index_add_associationv (parse->priv->index, parse->priv->index_id, + (key) ? GST_ASSOCIATION_FLAG_KEY_UNIT : GST_ASSOCIATION_FLAG_NONE, + 2, (const GstIndexAssociation *) &associations); + GST_OBJECT_UNLOCK (parse); + + parse->priv->index_last_offset = offset; + parse->priv->index_last_ts = ts; + + ret = TRUE; + +exit: + return ret; +} + +/* check for seekable upstream, above and beyond a mere query */ +static void +gst_base_parse_check_seekability (GstBaseParse * parse) +{ + GstQuery *query; + gboolean seekable = FALSE; + gint64 start = -1, stop = -1; + guint idx_interval = 0; + + query = gst_query_new_seeking (GST_FORMAT_BYTES); + if (!gst_pad_peer_query (parse->sinkpad, query)) { + GST_DEBUG_OBJECT (parse, "seeking query failed"); + goto done; + } + + gst_query_parse_seeking (query, NULL, &seekable, &start, &stop); + + /* try harder to query upstream size if we didn't get it the first time */ + if (seekable && stop == -1) { + GstFormat fmt = GST_FORMAT_BYTES; + + GST_DEBUG_OBJECT (parse, "doing duration query to fix up unset stop"); + gst_pad_query_peer_duration (parse->sinkpad, &fmt, &stop); + } + + /* if upstream doesn't know the size, it's likely that it's not seekable in + * practice even if it technically may be seekable */ + if (seekable && (start != 0 || stop <= start)) { + GST_DEBUG_OBJECT (parse, "seekable but unknown start/stop -> disable"); + seekable = FALSE; + } + + /* let's not put every single frame into our index */ + if (seekable) { + if (stop < 10 * 1024 * 1024) + idx_interval = 100; + else if (stop < 100 * 1024 * 1024) + idx_interval = 500; + else + idx_interval = 1000; + } + +done: + gst_query_unref (query); + + GST_DEBUG_OBJECT (parse, "seekable: %d (%" G_GUINT64_FORMAT " - %" + G_GUINT64_FORMAT ")", seekable, start, stop); + parse->priv->upstream_seekable = seekable; + + GST_DEBUG_OBJECT (parse, "idx_interval: %ums", idx_interval); + parse->priv->idx_interval = idx_interval * GST_MSECOND; +} + +/** * gst_base_parse_handle_and_push_buffer: * @parse: #GstBaseParse. * @klass: #GstBaseParseClass. @@ -1141,6 +1292,11 @@ gst_base_parse_push_buffer (GstBaseParse * parse, GstBuffer * buffer) GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), GST_TIME_ARGS (GST_BUFFER_DURATION (buffer))); + /* some one-time start-up */ + if (G_UNLIKELY (!parse->priv->framecount)) { + gst_base_parse_check_seekability (parse); + } + /* update stats */ parse->priv->bytecount += GST_BUFFER_SIZE (buffer); if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BASE_PARSE_BUFFER_FLAG_NO_FRAME)) { @@ -2423,6 +2579,38 @@ gst_base_parse_sink_setcaps (GstPad * pad, GstCaps * caps) return res && gst_pad_set_caps (pad, caps); } +static void +gst_base_parse_set_index (GstElement * element, GstIndex * index) +{ + GstBaseParse *parse = GST_BASE_PARSE (element); + + GST_OBJECT_LOCK (parse); + if (parse->priv->index) + gst_object_unref (parse->priv->index); + if (index) { + parse->priv->index = gst_object_ref (index); + gst_index_get_writer_id (index, GST_OBJECT (element), + &parse->priv->index_id); + parse->priv->own_index = FALSE; + } else + parse->priv->index = NULL; + GST_OBJECT_UNLOCK (parse); +} + +static GstIndex * +gst_base_parse_get_index (GstElement * element) +{ + GstBaseParse *parse = GST_BASE_PARSE (element); + GstIndex *result = NULL; + + GST_OBJECT_LOCK (parse); + if (parse->priv->index) + result = gst_object_ref (parse->priv->index); + GST_OBJECT_UNLOCK (parse); + + return result; +} + static GstStateChangeReturn gst_base_parse_change_state (GstElement * element, GstStateChange transition) { @@ -2431,6 +2619,30 @@ gst_base_parse_change_state (GstElement * element, GstStateChange transition) parse = GST_BASE_PARSE (element); + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + /* If this is our own index destroy it as the + * old entries might be wrong for the new stream */ + if (parse->priv->own_index) { + gst_object_unref (parse->priv->index); + parse->priv->index = NULL; + parse->priv->own_index = FALSE; + } + + /* If no index was created, generate one */ + if (G_UNLIKELY (!parse->priv->index)) { + GST_DEBUG_OBJECT (parse, "no index provided creating our own"); + + parse->priv->index = gst_index_factory_make ("memindex"); + gst_index_get_writer_id (parse->priv->index, GST_OBJECT (parse), + &parse->priv->index_id); + parse->priv->own_index = TRUE; + } + break; + default: + break; + } + result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); switch (transition) { diff --git a/gst/audioparsers/gstbaseparse.h b/gst/audioparsers/gstbaseparse.h index ccb761d..958e38c 100644 --- a/gst/audioparsers/gstbaseparse.h +++ b/gst/audioparsers/gstbaseparse.h @@ -305,6 +305,9 @@ gboolean gst_base_parse_convert_default (GstBaseParse * parse, GstFormat src_format, gint64 src_value, GstFormat dest_format, gint64 * dest_value); +gboolean gst_base_parse_add_index_entry (GstBaseParse * parse, guint64 offset, + GstClockTime ts, gboolean key, gboolean force); + G_END_DECLS #endif /* __GST_BASE_PARSE_H__ */ -- 2.7.4