X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=libs%2Fgst%2Fbase%2Fgstbaseparse.c;h=99283b8e85a5bfe639ab7e87063b95de459273f0;hb=91798e16cc09420163684d13779f13f374164ea2;hp=193bdf88b56030bdad2d1698bc65e7132aecac87;hpb=8641997630a9a63cc23f3e3f883496073ea61f34;p=platform%2Fupstream%2Fgstreamer.git diff --git a/libs/gst/base/gstbaseparse.c b/libs/gst/base/gstbaseparse.c index 193bdf8..99283b8 100644 --- a/libs/gst/base/gstbaseparse.c +++ b/libs/gst/base/gstbaseparse.c @@ -23,6 +23,7 @@ /** * SECTION:gstbaseparse + * @title: GstBaseParse * @short_description: Base class for stream parsers * @see_also: #GstBaseTransform * @@ -30,109 +31,88 @@ * into separate audio/video/whatever frames. * * It provides for: - * - * provides one sink pad and one source pad - * handles state changes - * can operate in pull mode or push mode - * handles seeking in both modes - * handles events (SEGMENT/EOS/FLUSH) - * - * handles queries (POSITION/DURATION/SEEKING/FORMAT/CONVERT) - * - * handles flushing - * + * + * * provides one sink pad and one source pad + * * handles state changes + * * can operate in pull mode or push mode + * * handles seeking in both modes + * * handles events (SEGMENT/EOS/FLUSH) + * * handles queries (POSITION/DURATION/SEEKING/FORMAT/CONVERT) + * * handles flushing * * The purpose of this base class is to provide the basic functionality of * a parser and share a lot of rather complex code. * - * Description of the parsing mechanism: - * - * - * Set-up phase - * - * #GstBaseParse calls @start to inform subclass that data processing is - * about to start now. - * - * - * #GstBaseParse class calls @set_sink_caps to inform the subclass about - * incoming sinkpad caps. Subclass could already set the srcpad caps - * accordingly, but this might be delayed until calling - * gst_base_parse_finish_frame() with a non-queued frame. - * - * - * At least at this point subclass needs to tell the #GstBaseParse class - * how big data chunks it wants to receive (min_frame_size). It can do - * this with gst_base_parse_set_min_frame_size(). - * - * - * #GstBaseParse class sets up appropriate data passing mode (pull/push) - * and starts to process the data. - * - * - * - * - * - * Parsing phase - * - * #GstBaseParse gathers at least min_frame_size bytes of data either - * by pulling it from upstream or collecting buffers in an internal - * #GstAdapter. - * - * - * A buffer of (at least) min_frame_size bytes is passed to subclass with - * @handle_frame. Subclass checks the contents and can optionally - * return GST_FLOW_OK along with an amount of data to be skipped to find - * a valid frame (which will result in a subsequent DISCONT). - * If, otherwise, the buffer does not hold a complete frame, - * @handle_frame can merely return and will be called again when additional - * data is available. In push mode this amounts to an - * additional input buffer (thus minimal additional latency), in pull mode - * this amounts to some arbitrary reasonable buffer size increase. - * Of course, gst_base_parse_set_min_frame_size() could also be used if a - * very specific known amount of additional data is required. - * If, however, the buffer holds a complete valid frame, it can pass - * the size of this frame to gst_base_parse_finish_frame(). - * If acting as a converter, it can also merely indicate consumed input data - * while simultaneously providing custom output data. - * Note that baseclass performs some processing (such as tracking - * overall consumed data rate versus duration) for each finished frame, - * but other state is only updated upon each call to @handle_frame - * (such as tracking upstream input timestamp). - * - * Subclass is also responsible for setting the buffer metadata - * (e.g. buffer timestamp and duration, or keyframe if applicable). - * (although the latter can also be done by #GstBaseParse if it is - * appropriately configured, see below). Frame is provided with - * timestamp derived from upstream (as much as generally possible), - * duration obtained from configuration (see below), and offset - * if meaningful (in pull mode). - * - * Note that @check_valid_frame might receive any small - * amount of input data when leftover data is being drained (e.g. at EOS). - * - * - * As part of finish frame processing, - * just prior to actually pushing the buffer in question, - * it is passed to @pre_push_frame which gives subclass yet one - * last chance to examine buffer metadata, or to send some custom (tag) - * events, or to perform custom (segment) filtering. - * - * - * During the parsing process #GstBaseParseClass will handle both srcpad - * and sinkpad events. They will be passed to subclass if @event or - * @src_event callbacks have been provided. - * - * - * - * - * Shutdown phase - * - * #GstBaseParse class calls @stop to inform the subclass that data - * parsing will be stopped. - * - * - * - * + * # Description of the parsing mechanism: + * + * ## Set-up phase + * + * * #GstBaseParse calls @start to inform subclass that data processing is + * about to start now. + * + * * #GstBaseParse class calls @set_sink_caps to inform the subclass about + * incoming sinkpad caps. Subclass could already set the srcpad caps + * accordingly, but this might be delayed until calling + * gst_base_parse_finish_frame() with a non-queued frame. + * + * * At least at this point subclass needs to tell the #GstBaseParse class + * how big data chunks it wants to receive (min_frame_size). It can do + * this with gst_base_parse_set_min_frame_size(). + * + * * #GstBaseParse class sets up appropriate data passing mode (pull/push) + * and starts to process the data. + * + * ## Parsing phase + * + * * #GstBaseParse gathers at least min_frame_size bytes of data either + * by pulling it from upstream or collecting buffers in an internal + * #GstAdapter. + * + * * A buffer of (at least) min_frame_size bytes is passed to subclass with + * @handle_frame. Subclass checks the contents and can optionally + * return GST_FLOW_OK along with an amount of data to be skipped to find + * a valid frame (which will result in a subsequent DISCONT). + * If, otherwise, the buffer does not hold a complete frame, + * @handle_frame can merely return and will be called again when additional + * data is available. In push mode this amounts to an + * additional input buffer (thus minimal additional latency), in pull mode + * this amounts to some arbitrary reasonable buffer size increase. + * Of course, gst_base_parse_set_min_frame_size() could also be used if a + * very specific known amount of additional data is required. + * If, however, the buffer holds a complete valid frame, it can pass + * the size of this frame to gst_base_parse_finish_frame(). + * If acting as a converter, it can also merely indicate consumed input data + * while simultaneously providing custom output data. + * Note that baseclass performs some processing (such as tracking + * overall consumed data rate versus duration) for each finished frame, + * but other state is only updated upon each call to @handle_frame + * (such as tracking upstream input timestamp). + * + * Subclass is also responsible for setting the buffer metadata + * (e.g. buffer timestamp and duration, or keyframe if applicable). + * (although the latter can also be done by #GstBaseParse if it is + * appropriately configured, see below). Frame is provided with + * timestamp derived from upstream (as much as generally possible), + * duration obtained from configuration (see below), and offset + * if meaningful (in pull mode). + * + * Note that @check_valid_frame might receive any small + * amount of input data when leftover data is being drained (e.g. at EOS). + * + * * As part of finish frame processing, + * just prior to actually pushing the buffer in question, + * it is passed to @pre_push_frame which gives subclass yet one + * last chance to examine buffer metadata, or to send some custom (tag) + * events, or to perform custom (segment) filtering. + * + * * During the parsing process #GstBaseParseClass will handle both srcpad + * and sinkpad events. They will be passed to subclass if @event or + * @src_event callbacks have been provided. + * + * ## Shutdown phase + * + * * #GstBaseParse class calls @stop to inform the subclass that data + * parsing will be stopped. * * Subclass is responsible for providing pad template caps for * source and sink pads. The pads need to be named "sink" and "src". It also @@ -151,44 +131,30 @@ * and end of data processing. * * Things that subclass need to take care of: - * - * Provide pad templates - * - * Fixate the source pad caps when appropriate - * - * - * Inform base class how big data chunks should be retrieved. This is - * done with gst_base_parse_set_min_frame_size() function. - * - * - * Examine data chunks passed to subclass with @handle_frame and pass - * proper frame(s) to gst_base_parse_finish_frame(), and setting src pad - * caps and timestamps on frame. - * - * Provide conversion functions - * - * Update the duration information with gst_base_parse_set_duration() - * - * - * Optionally passthrough using gst_base_parse_set_passthrough() - * - * - * Configure various baseparse parameters using - * gst_base_parse_set_average_bitrate(), gst_base_parse_set_syncable() - * and gst_base_parse_set_frame_rate(). - * - * - * In particular, if subclass is unable to determine a duration, but - * parsing (or specs) yields a frames per seconds rate, then this can be - * provided to #GstBaseParse to enable it to cater for - * buffer time metadata (which will be taken from upstream as much as - * possible). Internally keeping track of frame durations and respective - * sizes that have been pushed provides #GstBaseParse with an estimated - * bitrate. A default @convert (used if not overridden) will then use these - * rates to perform obvious conversions. These rates are also used to - * update (estimated) duration at regular frame intervals. - * - * + * + * * Provide pad templates + * * Fixate the source pad caps when appropriate + * * Inform base class how big data chunks should be retrieved. This is + * done with gst_base_parse_set_min_frame_size() function. + * * Examine data chunks passed to subclass with @handle_frame and pass + * proper frame(s) to gst_base_parse_finish_frame(), and setting src pad + * caps and timestamps on frame. + * * Provide conversion functions + * * Update the duration information with gst_base_parse_set_duration() + * * Optionally passthrough using gst_base_parse_set_passthrough() + * * Configure various baseparse parameters using + * gst_base_parse_set_average_bitrate(), gst_base_parse_set_syncable() + * and gst_base_parse_set_frame_rate(). + * + * * In particular, if subclass is unable to determine a duration, but + * parsing (or specs) yields a frames per seconds rate, then this can be + * provided to #GstBaseParse to enable it to cater for + * buffer time metadata (which will be taken from upstream as much as + * possible). Internally keeping track of frame durations and respective + * sizes that have been pushed provides #GstBaseParse with an estimated + * bitrate. A default @convert (used if not overridden) will then use these + * rates to perform obvious conversions. These rates are also used to + * update (estimated) duration at regular frame intervals. * */ @@ -219,6 +185,9 @@ #define MIN_FRAMES_TO_POST_BITRATE 10 #define TARGET_DIFFERENCE (20 * GST_SECOND) #define MAX_INDEX_ENTRIES 4096 +#define UPDATE_THRESHOLD 2 + +#define ABSDIFF(a,b) (((a) > (b)) ? ((a) - (b)) : ((b) - (a))) GST_DEBUG_CATEGORY_STATIC (gst_base_parse_debug); #define GST_CAT_DEFAULT gst_base_parse_debug @@ -270,6 +239,7 @@ struct _GstBaseParsePrivate GstClockTime next_dts; GstClockTime prev_pts; GstClockTime prev_dts; + gboolean prev_dts_from_pts; GstClockTime frame_duration; gboolean seen_keyframe; gboolean is_video; @@ -286,6 +256,7 @@ struct _GstBaseParsePrivate gboolean post_min_bitrate; gboolean post_avg_bitrate; gboolean post_max_bitrate; + guint min_bitrate; guint avg_bitrate; guint max_bitrate; @@ -359,6 +330,12 @@ struct _GstBaseParsePrivate /* When we need to skip more data than we have currently */ guint skip; + + /* Tag handling (stream tags only, global tags are passed through as-is) */ + GstTagList *upstream_tags; + GstTagList *parser_tags; + GstTagMergeMode parser_tags_merge_mode; + gboolean tags_changed; }; typedef struct _GstBaseParseSeek @@ -432,7 +409,8 @@ static gboolean gst_base_parse_sink_activate_mode (GstPad * pad, GstObject * parent, GstPadMode mode, gboolean active); static gboolean gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event); -static void gst_base_parse_handle_tag (GstBaseParse * parse, GstEvent * event); +static void gst_base_parse_set_upstream_tags (GstBaseParse * parse, + GstTagList * taglist); static void gst_base_parse_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); @@ -467,11 +445,6 @@ static gboolean gst_base_parse_sink_query_default (GstBaseParse * parse, static gboolean gst_base_parse_src_query_default (GstBaseParse * parse, GstQuery * query); -static void gst_base_parse_drain (GstBaseParse * parse); - -static void gst_base_parse_post_bitrates (GstBaseParse * parse, - gboolean post_min, gboolean post_avg, gboolean post_max); - static gint64 gst_base_parse_find_offset (GstBaseParse * parse, GstClockTime time, gboolean before, GstClockTime * _ts); static GstFlowReturn gst_base_parse_locate_time (GstBaseParse * parse, @@ -644,6 +617,10 @@ gst_base_parse_init (GstBaseParse * parse, GstBaseParseClass * bclass) GST_DEBUG_OBJECT (parse, "init ok"); GST_OBJECT_FLAG_SET (parse, GST_ELEMENT_FLAG_INDEXABLE); + + parse->priv->upstream_tags = NULL; + parse->priv->parser_tags = NULL; + parse->priv->parser_tags_merge_mode = GST_TAG_MERGE_APPEND; } static void @@ -678,7 +655,7 @@ gst_base_parse_get_property (GObject * object, guint prop_id, GValue * value, } } -static GstBaseParseFrame * +GstBaseParseFrame * gst_base_parse_frame_copy (GstBaseParseFrame * frame) { GstBaseParseFrame *copy; @@ -760,11 +737,8 @@ gst_base_parse_frame_new (GstBuffer * buffer, GstBaseParseFrameFlags flags, } static inline void -gst_base_parse_frame_update (GstBaseParse * parse, GstBaseParseFrame * frame, - GstBuffer * buf) +gst_base_parse_update_flags (GstBaseParse * parse) { - gst_buffer_replace (&frame->buffer, buf); - parse->flags = 0; /* set flags one by one for clarity */ @@ -776,6 +750,24 @@ gst_base_parse_frame_update (GstBaseParse * parse, GstBaseParseFrame * frame, parse->flags |= GST_BASE_PARSE_FLAG_LOST_SYNC; } +static inline void +gst_base_parse_update_frame (GstBaseParse * parse, GstBaseParseFrame * frame) +{ + if (G_UNLIKELY (parse->priv->discont)) { + GST_DEBUG_OBJECT (parse, "marking DISCONT"); + GST_BUFFER_FLAG_SET (frame->buffer, GST_BUFFER_FLAG_DISCONT); + } else { + GST_BUFFER_FLAG_UNSET (frame->buffer, GST_BUFFER_FLAG_DISCONT); + } + + if (parse->priv->prev_offset != parse->priv->offset || parse->priv->new_frame) { + GST_LOG_OBJECT (parse, "marking as new frame"); + frame->flags |= GST_BASE_PARSE_FRAME_FLAG_NEW_FRAME; + } + + frame->offset = parse->priv->prev_offset = parse->priv->offset; +} + static void gst_base_parse_reset (GstBaseParse * parse) { @@ -810,9 +802,6 @@ gst_base_parse_reset (GstBaseParse * parse) parse->priv->pts_interpolate = TRUE; parse->priv->infer_ts = TRUE; parse->priv->has_timing_info = FALSE; - parse->priv->post_min_bitrate = TRUE; - parse->priv->post_avg_bitrate = TRUE; - parse->priv->post_max_bitrate = TRUE; parse->priv->min_bitrate = G_MAXUINT; parse->priv->max_bitrate = 0; parse->priv->avg_bitrate = 0; @@ -854,6 +843,14 @@ gst_base_parse_reset (GstBaseParse * parse) if (parse->priv->adapter) gst_adapter_clear (parse->priv->adapter); + gst_base_parse_set_upstream_tags (parse, NULL); + + if (parse->priv->parser_tags) { + gst_tag_list_unref (parse->priv->parser_tags); + parse->priv->parser_tags = NULL; + } + parse->priv->parser_tags_merge_mode = GST_TAG_MERGE_APPEND; + parse->priv->new_frame = TRUE; parse->priv->first_buffer = TRUE; @@ -865,6 +862,86 @@ gst_base_parse_reset (GstBaseParse * parse) GST_OBJECT_UNLOCK (parse); } +static gboolean +gst_base_parse_check_bitrate_tag (GstBaseParse * parse, const gchar * tag) +{ + gboolean got_tag = FALSE; + guint n = 0; + + if (parse->priv->upstream_tags != NULL) + got_tag = gst_tag_list_get_uint (parse->priv->upstream_tags, tag, &n); + + if (!got_tag && parse->priv->parser_tags != NULL) + got_tag = gst_tag_list_get_uint (parse->priv->parser_tags, tag, &n); + + return got_tag; +} + +/* check if upstream or subclass tags contain bitrates already */ +static void +gst_base_parse_check_bitrate_tags (GstBaseParse * parse) +{ + parse->priv->post_min_bitrate = + !gst_base_parse_check_bitrate_tag (parse, GST_TAG_MINIMUM_BITRATE); + parse->priv->post_avg_bitrate = + !gst_base_parse_check_bitrate_tag (parse, GST_TAG_BITRATE); + parse->priv->post_max_bitrate = + !gst_base_parse_check_bitrate_tag (parse, GST_TAG_MAXIMUM_BITRATE); +} + +/* Queues new tag event with the current combined state of the stream tags + * (i.e. upstream tags merged with subclass tags and current baseparse tags) */ +static void +gst_base_parse_queue_tag_event_update (GstBaseParse * parse) +{ + GstTagList *merged_tags; + + GST_LOG_OBJECT (parse, "upstream : %" GST_PTR_FORMAT, + parse->priv->upstream_tags); + GST_LOG_OBJECT (parse, "parser : %" GST_PTR_FORMAT, + parse->priv->parser_tags); + GST_LOG_OBJECT (parse, "mode : %d", parse->priv->parser_tags_merge_mode); + + merged_tags = + gst_tag_list_merge (parse->priv->upstream_tags, parse->priv->parser_tags, + parse->priv->parser_tags_merge_mode); + + GST_DEBUG_OBJECT (parse, "merged : %" GST_PTR_FORMAT, merged_tags); + + if (merged_tags == NULL) + return; + + if (gst_tag_list_is_empty (merged_tags)) { + gst_tag_list_unref (merged_tags); + return; + } + + if (parse->priv->framecount >= MIN_FRAMES_TO_POST_BITRATE) { + /* only add bitrate tags to non-empty taglists for now, and only if neither + * upstream tags nor the subclass sets the bitrate tag in question already */ + if (parse->priv->min_bitrate != G_MAXUINT && parse->priv->post_min_bitrate) { + GST_LOG_OBJECT (parse, "adding min bitrate %u", parse->priv->min_bitrate); + gst_tag_list_add (merged_tags, GST_TAG_MERGE_KEEP, + GST_TAG_MINIMUM_BITRATE, parse->priv->min_bitrate, NULL); + } + if (parse->priv->max_bitrate != 0 && parse->priv->post_max_bitrate) { + GST_LOG_OBJECT (parse, "adding max bitrate %u", parse->priv->max_bitrate); + gst_tag_list_add (merged_tags, GST_TAG_MERGE_KEEP, + GST_TAG_MAXIMUM_BITRATE, parse->priv->max_bitrate, NULL); + } + if (parse->priv->avg_bitrate != 0 && parse->priv->post_avg_bitrate) { + parse->priv->posted_avg_bitrate = parse->priv->avg_bitrate; + GST_LOG_OBJECT (parse, "adding avg bitrate %u", parse->priv->avg_bitrate); + gst_tag_list_add (merged_tags, GST_TAG_MERGE_KEEP, + GST_TAG_BITRATE, parse->priv->avg_bitrate, NULL); + } + } + + parse->priv->pending_events = + g_list_prepend (parse->priv->pending_events, + gst_event_new_tag (merged_tags)); +} + /* gst_base_parse_parse_frame: * @parse: #GstBaseParse. * @buffer: #GstBuffer. @@ -945,6 +1022,84 @@ gst_base_parse_convert (GstBaseParse * parse, return ret; } +static gboolean +update_upstream_provided (GQuark field_id, const GValue * value, + gpointer user_data) +{ + GstCaps *default_caps = user_data; + gint i; + gint caps_size; + + caps_size = gst_caps_get_size (default_caps); + for (i = 0; i < caps_size; i++) { + GstStructure *structure = gst_caps_get_structure (default_caps, i); + if (gst_structure_id_has_field (structure, field_id)) + gst_structure_id_set_value (structure, field_id, value); + } + + return TRUE; +} + +static GstCaps * +gst_base_parse_negotiate_default_caps (GstBaseParse * parse) +{ + GstCaps *caps, *templcaps; + GstCaps *sinkcaps = NULL; + GstCaps *default_caps = NULL; + GstStructure *structure; + + templcaps = gst_pad_get_pad_template_caps (GST_BASE_PARSE_SRC_PAD (parse)); + caps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), templcaps); + if (caps) + gst_caps_unref (templcaps); + else + caps = templcaps; + templcaps = NULL; + + if (!caps || gst_caps_is_empty (caps) || gst_caps_is_any (caps)) { + goto caps_error; + } + + GST_LOG_OBJECT (parse, "peer caps %" GST_PTR_FORMAT, caps); + + /* before fixating, try to use whatever upstream provided */ + default_caps = gst_caps_copy (caps); + sinkcaps = gst_pad_get_current_caps (GST_BASE_PARSE_SINK_PAD (parse)); + + GST_LOG_OBJECT (parse, "current caps %" GST_PTR_FORMAT " for sinkpad", + sinkcaps); + + if (sinkcaps) { + structure = gst_caps_get_structure (sinkcaps, 0); + gst_structure_foreach (structure, update_upstream_provided, default_caps); + } + + default_caps = gst_caps_fixate (default_caps); + + if (!default_caps) { + GST_WARNING_OBJECT (parse, "Failed to create default caps !"); + goto caps_error; + } + + GST_INFO_OBJECT (parse, + "Chose default caps %" GST_PTR_FORMAT " for initial gap", default_caps); + + if (sinkcaps) + gst_caps_unref (sinkcaps); + gst_caps_unref (caps); + + return default_caps; + +caps_error: + { + if (caps) + gst_caps_unref (caps); + if (sinkcaps) + gst_caps_unref (sinkcaps); + return NULL; + } +} + /* gst_base_parse_sink_event: * @pad: #GstPad that received the event. * @event: #GstEvent to be handled. @@ -965,7 +1120,6 @@ gst_base_parse_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) return ret; } - /* gst_base_parse_sink_event_default: * @parse: #GstBaseParse. * @event: #GstEvent to be handled. @@ -1122,6 +1276,7 @@ gst_base_parse_sink_event_default (GstBaseParse * parse, GstEvent * event) parse->priv->last_dts = GST_CLOCK_TIME_NONE; parse->priv->prev_pts = GST_CLOCK_TIME_NONE; parse->priv->prev_dts = GST_CLOCK_TIME_NONE; + parse->priv->prev_dts_from_pts = FALSE; parse->priv->discont = TRUE; parse->priv->seen_keyframe = FALSE; parse->priv->skip = 0; @@ -1173,14 +1328,16 @@ gst_base_parse_sink_event_default (GstBaseParse * parse, GstEvent * event) GST_ELEMENT_ERROR (parse, STREAM, WRONG_TYPE, ("No valid frames found before end of stream"), (NULL)); } - /* newsegment and other serialized events before eos */ - gst_base_parse_push_pending_events (parse); if (!parse->priv->saw_gaps && parse->priv->framecount < MIN_FRAMES_TO_POST_BITRATE) { /* We've not posted bitrate tags yet - do so now */ - gst_base_parse_post_bitrates (parse, TRUE, TRUE, TRUE); + gst_base_parse_queue_tag_event_update (parse); } + + /* newsegment and other serialized events before eos */ + gst_base_parse_push_pending_events (parse); + forward_immediate = TRUE; break; case GST_EVENT_CUSTOM_DOWNSTREAM:{ @@ -1211,6 +1368,47 @@ gst_base_parse_sink_event_default (GstBaseParse * parse, GstEvent * event) { GST_DEBUG_OBJECT (parse, "draining current data due to gap event"); + /* Ensure we have caps before forwarding the event */ + if (!gst_pad_has_current_caps (GST_BASE_PARSE_SRC_PAD (parse))) { + GstCaps *default_caps = NULL; + if ((default_caps = gst_base_parse_negotiate_default_caps (parse))) { + GList *l; + GstEvent *caps_event = gst_event_new_caps (default_caps); + + GST_DEBUG_OBJECT (parse, + "Store caps event to pending list for initial pre-rolling"); + + /* Events are in decreasing order. Go down the list until we + * find the first pre-CAPS event and insert our CAPS event there. + * + * There should be a SEGMENT event already, which is > CAPS */ + for (l = parse->priv->pending_events; l; l = l->next) { + GstEvent *e = l->data; + + if (GST_EVENT_TYPE (e) < GST_EVENT_CAPS) { + parse->priv->pending_events = + g_list_insert_before (parse->priv->pending_events, l, + caps_event); + break; + } + } + /* No pending event that is < CAPS, so we have to add it at the very + * end of the list */ + if (!l) { + parse->priv->pending_events = + g_list_append (parse->priv->pending_events, caps_event); + } + gst_caps_unref (default_caps); + } else { + gst_event_unref (event); + event = NULL; + ret = FALSE; + GST_ELEMENT_ERROR (parse, STREAM, FORMAT, (NULL), + ("Parser output not negotiated before GAP event.")); + break; + } + } + gst_base_parse_push_pending_events (parse); if (parse->segment.rate > 0.0) @@ -1222,15 +1420,32 @@ gst_base_parse_sink_event_default (GstBaseParse * parse, GstEvent * event) break; } case GST_EVENT_TAG: - /* See if any bitrate tags were posted */ - gst_base_parse_handle_tag (parse, event); - break; + { + GstTagList *tags = NULL; + + gst_event_parse_tag (event, &tags); + + /* We only care about stream tags here, global tags we just forward */ + if (gst_tag_list_get_scope (tags) != GST_TAG_SCOPE_STREAM) + break; + gst_base_parse_set_upstream_tags (parse, tags); + gst_base_parse_queue_tag_event_update (parse); + parse->priv->tags_changed = FALSE; + gst_event_unref (event); + event = NULL; + ret = TRUE; + break; + } case GST_EVENT_STREAM_START: + { if (parse->priv->pad_mode != GST_PAD_MODE_PULL) forward_immediate = TRUE; - break; + gst_base_parse_set_upstream_tags (parse, NULL); + parse->priv->tags_changed = TRUE; + break; + } default: break; } @@ -1463,6 +1678,14 @@ gst_base_parse_convert_default (GstBaseParse * parse, return TRUE; } + if (parse->priv->upstream_format != GST_FORMAT_BYTES) { + /* don't do byte format conversions if we're not really parsing + * a raw elementary stream, since we don't really have BYTES + * position / duration info */ + if (src_format == GST_FORMAT_BYTES || dest_format == GST_FORMAT_BYTES) + goto no_slaved_conversions; + } + /* need at least some frames */ if (!parse->priv->framecount) goto no_framecount; @@ -1526,84 +1749,39 @@ no_duration_bytes: G_GUINT64_FORMAT, duration, bytes); return FALSE; } - -} - -static void -gst_base_parse_update_duration (GstBaseParse * baseparse) -{ - GstPad *peer; - GstBaseParse *parse; - - parse = GST_BASE_PARSE (baseparse); - - peer = gst_pad_get_peer (parse->sinkpad); - if (peer) { - gboolean qres = FALSE; - gint64 ptot, dest_value; - - qres = gst_pad_query_duration (peer, GST_FORMAT_BYTES, &ptot); - gst_object_unref (GST_OBJECT (peer)); - if (qres) { - if (gst_base_parse_convert (parse, GST_FORMAT_BYTES, ptot, - GST_FORMAT_TIME, &dest_value)) { - - /* inform if duration changed, but try to avoid spamming */ - parse->priv->estimated_drift += - dest_value - parse->priv->estimated_duration; - - parse->priv->estimated_duration = dest_value; - GST_LOG_OBJECT (parse, - "updated estimated duration to %" GST_TIME_FORMAT, - GST_TIME_ARGS (dest_value)); - - if (parse->priv->estimated_drift > GST_SECOND || - parse->priv->estimated_drift < -GST_SECOND) { - gst_element_post_message (GST_ELEMENT (parse), - gst_message_new_duration_changed (GST_OBJECT (parse))); - parse->priv->estimated_drift = 0; - } - } - } +no_slaved_conversions: + { + GST_DEBUG_OBJECT (parse, + "Can't do format conversions when upstream format is not BYTES"); + return FALSE; } } static void -gst_base_parse_post_bitrates (GstBaseParse * parse, gboolean post_min, - gboolean post_avg, gboolean post_max) +gst_base_parse_update_duration (GstBaseParse * parse) { - GstTagList *taglist = NULL; - - if (post_min && parse->priv->post_min_bitrate) { - taglist = gst_tag_list_new_empty (); - - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, - GST_TAG_MINIMUM_BITRATE, parse->priv->min_bitrate, NULL); - } - - if (post_avg && parse->priv->post_avg_bitrate) { - if (taglist == NULL) - taglist = gst_tag_list_new_empty (); + gint64 ptot, dest_value; - parse->priv->posted_avg_bitrate = parse->priv->avg_bitrate; - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, GST_TAG_BITRATE, - parse->priv->avg_bitrate, NULL); - } - - if (post_max && parse->priv->post_max_bitrate) { - if (taglist == NULL) - taglist = gst_tag_list_new_empty (); + if (!gst_pad_peer_query_duration (parse->sinkpad, GST_FORMAT_BYTES, &ptot)) + return; - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, - GST_TAG_MAXIMUM_BITRATE, parse->priv->max_bitrate, NULL); - } + if (!gst_base_parse_convert (parse, GST_FORMAT_BYTES, ptot, + GST_FORMAT_TIME, &dest_value)) + return; - GST_DEBUG_OBJECT (parse, "Updated bitrates. Min: %u, Avg: %u, Max: %u", - parse->priv->min_bitrate, parse->priv->avg_bitrate, - parse->priv->max_bitrate); + /* inform if duration changed, but try to avoid spamming */ + parse->priv->estimated_drift += dest_value - parse->priv->estimated_duration; - if (taglist != NULL) { - gst_pad_push_event (parse->srcpad, gst_event_new_tag (taglist)); + parse->priv->estimated_duration = dest_value; + GST_LOG_OBJECT (parse, + "updated estimated duration to %" GST_TIME_FORMAT, + GST_TIME_ARGS (dest_value)); + + if (parse->priv->estimated_drift > GST_SECOND || + parse->priv->estimated_drift < -GST_SECOND) { + gst_element_post_message (GST_ELEMENT (parse), + gst_message_new_duration_changed (GST_OBJECT (parse))); + parse->priv->estimated_drift = 0; } } @@ -1617,12 +1795,10 @@ gst_base_parse_post_bitrates (GstBaseParse * parse, gboolean post_min, static void gst_base_parse_update_bitrates (GstBaseParse * parse, GstBaseParseFrame * frame) { - /* Only update the tag on a 10 kbps delta */ - static const gint update_threshold = 10000; - guint64 data_len, frame_dur; - gint overhead, frame_bitrate, old_avg_bitrate; - gboolean update_min = FALSE, update_avg = FALSE, update_max = FALSE; + gint overhead; + guint frame_bitrate; + guint64 frame_bitrate64; GstBuffer *buffer = frame->buffer; overhead = frame->overhead; @@ -1635,11 +1811,17 @@ gst_base_parse_update_bitrates (GstBaseParse * parse, GstBaseParseFrame * frame) /* duration should be valid by now, * either set by subclass or maybe based on fps settings */ if (GST_BUFFER_DURATION_IS_VALID (buffer) && parse->priv->acc_duration != 0) { + guint64 avg_bitrate; + /* Calculate duration of a frame from buffer properties */ frame_dur = GST_BUFFER_DURATION (buffer); - parse->priv->avg_bitrate = (8 * parse->priv->data_bytecount * GST_SECOND) / - parse->priv->acc_duration; + avg_bitrate = gst_util_uint64_scale (GST_SECOND, + 8 * parse->priv->data_bytecount, parse->priv->acc_duration); + if (avg_bitrate > G_MAXUINT) + return; + + parse->priv->avg_bitrate = avg_bitrate; } else { /* No way to figure out frame duration (is this even possible?) */ return; @@ -1649,48 +1831,54 @@ gst_base_parse_update_bitrates (GstBaseParse * parse, GstBaseParseFrame * frame) if (parse->priv->bitrate) { parse->priv->avg_bitrate = parse->priv->bitrate; /* spread this (confirmed) info ASAP */ - if (parse->priv->posted_avg_bitrate != parse->priv->avg_bitrate) - gst_base_parse_post_bitrates (parse, FALSE, TRUE, FALSE); + if (parse->priv->post_avg_bitrate && + parse->priv->posted_avg_bitrate != parse->priv->avg_bitrate) + parse->priv->tags_changed = TRUE; } - if (frame_dur) - frame_bitrate = (8 * data_len * GST_SECOND) / frame_dur; - else + if (!frame_dur) + return; + + frame_bitrate64 = gst_util_uint64_scale (GST_SECOND, 8 * data_len, frame_dur); + + if (frame_bitrate64 > G_MAXUINT) return; + frame_bitrate = (guint) frame_bitrate64; + GST_LOG_OBJECT (parse, "frame bitrate %u, avg bitrate %u", frame_bitrate, parse->priv->avg_bitrate); - if (parse->priv->framecount < MIN_FRAMES_TO_POST_BITRATE) { - goto exit; - } else if (parse->priv->framecount == MIN_FRAMES_TO_POST_BITRATE) { - /* always post all at threshold time */ - update_min = update_max = update_avg = TRUE; - } + if (parse->priv->framecount < MIN_FRAMES_TO_POST_BITRATE) + return; + + if (parse->priv->framecount == MIN_FRAMES_TO_POST_BITRATE && + (parse->priv->post_min_bitrate || parse->priv->post_avg_bitrate + || parse->priv->post_max_bitrate)) + parse->priv->tags_changed = TRUE; if (G_LIKELY (parse->priv->framecount >= MIN_FRAMES_TO_POST_BITRATE)) { if (frame_bitrate < parse->priv->min_bitrate) { parse->priv->min_bitrate = frame_bitrate; - update_min = TRUE; + if (parse->priv->post_min_bitrate) + parse->priv->tags_changed = TRUE; } if (frame_bitrate > parse->priv->max_bitrate) { parse->priv->max_bitrate = frame_bitrate; - update_max = TRUE; + if (parse->priv->post_max_bitrate) + parse->priv->tags_changed = TRUE; } - old_avg_bitrate = parse->priv->posted_avg_bitrate; - if ((gint) (old_avg_bitrate - parse->priv->avg_bitrate) > update_threshold - || (gint) (parse->priv->avg_bitrate - old_avg_bitrate) > - update_threshold) - update_avg = TRUE; + /* Only update the tag on a 2% change */ + if (parse->priv->post_avg_bitrate && parse->priv->avg_bitrate) { + guint64 diffprev = gst_util_uint64_scale_int (100, + ABSDIFF (parse->priv->avg_bitrate, parse->priv->posted_avg_bitrate), + parse->priv->avg_bitrate); + if (diffprev >= UPDATE_THRESHOLD) + parse->priv->tags_changed = TRUE; + } } - - if ((update_min || update_avg || update_max)) - gst_base_parse_post_bitrates (parse, update_min, update_avg, update_max); - -exit: - return; } /** @@ -1920,27 +2108,17 @@ gst_base_parse_prepare_frame (GstBaseParse * parse, GstBuffer * buffer) GST_BUFFER_OFFSET (buffer), GST_BUFFER_OFFSET (buffer), gst_buffer_get_size (buffer)); - if (parse->priv->discont) { - GST_DEBUG_OBJECT (parse, "marking DISCONT"); - GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); - parse->priv->discont = FALSE; - } - GST_BUFFER_OFFSET (buffer) = parse->priv->offset; - frame = gst_base_parse_frame_new (buffer, 0, 0); + gst_base_parse_update_flags (parse); - /* also ensure to update state flags */ - gst_base_parse_frame_update (parse, frame, buffer); + frame = gst_base_parse_frame_new (buffer, 0, 0); gst_buffer_unref (buffer); + gst_base_parse_update_frame (parse, frame); - if (parse->priv->prev_offset != parse->priv->offset || parse->priv->new_frame) { - GST_LOG_OBJECT (parse, "marking as new frame"); - parse->priv->new_frame = FALSE; - frame->flags |= GST_BASE_PARSE_FRAME_FLAG_NEW_FRAME; - } - - frame->offset = parse->priv->prev_offset = parse->priv->offset; + /* clear flags for next frame */ + parse->priv->discont = FALSE; + parse->priv->new_frame = FALSE; /* use default handler to provide initial (upstream) metadata */ gst_base_parse_parse_frame (parse, frame); @@ -2039,6 +2217,12 @@ gst_base_parse_handle_buffer (GstBaseParse * parse, GstBuffer * buffer, gst_adapter_clear (parse->priv->adapter); } + if (*skip == 0 && *flushed == 0) { + /* Carry over discont if we need more data */ + if (GST_BUFFER_IS_DISCONT (frame->buffer)) + parse->priv->discont = TRUE; + } + gst_base_parse_frame_free (frame); return ret; @@ -2236,6 +2420,11 @@ gst_base_parse_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame) gst_base_parse_check_media (parse); } + if (parse->priv->tags_changed) { + gst_base_parse_queue_tag_event_update (parse); + parse->priv->tags_changed = FALSE; + } + /* Push pending events, including SEGMENT events */ gst_base_parse_push_pending_events (parse); @@ -2283,6 +2472,14 @@ gst_base_parse_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame) frame->flags |= GST_BASE_PARSE_FRAME_FLAG_CLIP; } + /* Push pending events, if there are any new ones + * like tags added by pre_push_frame */ + if (parse->priv->tags_changed) { + gst_base_parse_queue_tag_event_update (parse); + parse->priv->tags_changed = FALSE; + } + gst_base_parse_push_pending_events (parse); + /* take final ownership of frame buffer */ if (frame->out_buffer) { buffer = frame->out_buffer; @@ -2327,6 +2524,8 @@ gst_base_parse_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame) if (ret == GST_BASE_PARSE_FLOW_DROPPED) { GST_LOG_OBJECT (parse, "frame (%" G_GSIZE_FORMAT " bytes) dropped", size); + if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) + parse->priv->discont = TRUE; gst_buffer_unref (buffer); ret = GST_FLOW_OK; } else if (ret == GST_FLOW_OK) { @@ -2383,8 +2582,9 @@ gst_base_parse_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame) } /* Update current running segment position */ - if (ret == GST_FLOW_OK && last_stop != GST_CLOCK_TIME_NONE && - parse->segment.position < last_stop) + if ((ret == GST_FLOW_OK || ret == GST_FLOW_NOT_LINKED) + && last_stop != GST_CLOCK_TIME_NONE + && parse->segment.position < last_stop) parse->segment.position = last_stop; return ret; @@ -2496,13 +2696,17 @@ exit: return ret; } -/* gst_base_parse_drain: +/** + * gst_base_parse_drain: + * @parse: a #GstBaseParse * * Drains the adapter until it is empty. It decreases the min_frame_size to * match the current adapter size and calls chain method until the adapter * is emptied or chain returns with error. + * + * Since: 1.12 */ -static void +void gst_base_parse_drain (GstBaseParse * parse) { guint avail; @@ -2560,6 +2764,12 @@ gst_base_parse_send_buffers (GstBaseParse * parse) if (first) { GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); first = FALSE; + } else { + /* likewise, subsequent buffers should never have DISCONT + * according to the "reverse fragment protocol", or such would + * confuse a downstream decoder + * (could be DISCONT due to aggregating upstream fragments by parsing) */ + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); } /* iterate output queue an push downstream */ @@ -2597,6 +2807,7 @@ gst_base_parse_start_fragment (GstBaseParse * parse) parse->priv->prev_pts = GST_CLOCK_TIME_NONE; parse->priv->next_dts = GST_CLOCK_TIME_NONE; parse->priv->prev_dts = GST_CLOCK_TIME_NONE; + parse->priv->prev_dts_from_pts = FALSE; /* prevent it hanging around stop all the time */ parse->segment.position = GST_CLOCK_TIME_NONE; /* mark next run */ @@ -2810,7 +3021,6 @@ gst_base_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) GstBuffer *tmpbuf = NULL; guint fsize = 1; gint skip = -1; - const guint8 *data; guint min_size, av; GstClockTime pts, dts; @@ -2968,6 +3178,7 @@ gst_base_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) /* Stop either when adapter is empty or we are flushing */ while (!parse->priv->flushing) { gint flush = 0; + gboolean updated_prev_pts = FALSE; /* note: if subclass indicates MAX fsize, * this will not likely be available anyway ... */ @@ -2992,11 +3203,15 @@ gst_base_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) * but interpolate in between */ pts = gst_adapter_prev_pts (parse->priv->adapter, NULL); dts = gst_adapter_prev_dts (parse->priv->adapter, NULL); - if (GST_CLOCK_TIME_IS_VALID (pts) && (parse->priv->prev_pts != pts)) + if (GST_CLOCK_TIME_IS_VALID (pts) && (parse->priv->prev_pts != pts)) { parse->priv->prev_pts = parse->priv->next_pts = pts; + updated_prev_pts = TRUE; + } - if (GST_CLOCK_TIME_IS_VALID (dts) && (parse->priv->prev_dts != dts)) + if (GST_CLOCK_TIME_IS_VALID (dts) && (parse->priv->prev_dts != dts)) { parse->priv->prev_dts = parse->priv->next_dts = dts; + parse->priv->prev_dts_from_pts = FALSE; + } /* we can mess with, erm interpolate, timestamps, * and incoming stuff has PTS but no DTS seen so far, @@ -3004,31 +3219,29 @@ gst_base_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) if (parse->priv->infer_ts && parse->priv->pts_interpolate && !GST_CLOCK_TIME_IS_VALID (dts) && - !GST_CLOCK_TIME_IS_VALID (parse->priv->prev_dts) && - GST_CLOCK_TIME_IS_VALID (pts)) - parse->priv->next_dts = pts; + (!GST_CLOCK_TIME_IS_VALID (parse->priv->prev_dts) + || (parse->priv->prev_dts_from_pts && updated_prev_pts)) + && GST_CLOCK_TIME_IS_VALID (pts)) { + parse->priv->prev_dts = parse->priv->next_dts = pts; + parse->priv->prev_dts_from_pts = TRUE; + } /* always pass all available data */ - data = gst_adapter_map (parse->priv->adapter, av); - /* arrange for actual data to be copied if subclass tries to, - * since what is passed is tied to the adapter */ - tmpbuf = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY | - GST_MEMORY_FLAG_NO_SHARE, (gpointer) data, av, 0, av, NULL, NULL); + tmpbuf = gst_adapter_get_buffer (parse->priv->adapter, av); /* already inform subclass what timestamps we have planned, * at least if provided by time-based upstream */ if (parse->priv->upstream_format == GST_FORMAT_TIME) { + tmpbuf = gst_buffer_make_writable (tmpbuf); GST_BUFFER_PTS (tmpbuf) = parse->priv->next_pts; GST_BUFFER_DTS (tmpbuf) = parse->priv->next_dts; + GST_BUFFER_DURATION (tmpbuf) = GST_CLOCK_TIME_NONE; } /* keep the adapter mapped, so keep track of what has to be flushed */ ret = gst_base_parse_handle_buffer (parse, tmpbuf, &skip, &flush); tmpbuf = NULL; - /* probably already implicitly unmapped due to adapter operation, - * but for good measure ... */ - gst_adapter_unmap (parse->priv->adapter); if (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED) { goto done; } @@ -3039,7 +3252,8 @@ gst_base_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) ret = old_ret; goto done; } - old_ret = ret; + if (old_ret == GST_FLOW_OK) + old_ret = ret; } done: @@ -3327,8 +3541,6 @@ gst_base_parse_loop (GstPad * pad) } ret = gst_base_parse_scan_frame (parse, klass); - if (ret != GST_FLOW_OK) - goto done; /* eat expected eos signalling past segment in reverse playback */ if (parse->segment.rate < 0.0 && ret == GST_FLOW_EOS && @@ -3338,9 +3550,12 @@ gst_base_parse_loop (GstPad * pad) gst_base_parse_finish_fragment (parse, FALSE); /* force previous fragment */ parse->priv->offset = -1; - ret = GST_FLOW_OK; + goto eos; } + if (ret != GST_FLOW_OK) + goto done; + done: if (ret == GST_FLOW_EOS) goto eos; @@ -3393,11 +3608,13 @@ pause: /* for fatal errors we post an error message, wrong-state is * not fatal because it happens due to flushes and only means * that we should stop now. */ - GST_ELEMENT_ERROR (parse, STREAM, FAILED, (NULL), - ("streaming stopped, reason %s", gst_flow_get_name (ret))); + GST_ELEMENT_FLOW_ERROR (parse, ret); push_eos = TRUE; } if (push_eos) { + if (parse->priv->estimated_duration <= 0) { + gst_base_parse_update_duration (parse); + } /* Push pending events, including SEGMENT events */ gst_base_parse_push_pending_events (parse); @@ -3440,6 +3657,8 @@ gst_base_parse_sink_activate (GstPad * sinkpad, GstObject * parent) goto baseparse_push; parse->priv->push_stream_start = TRUE; + /* In pull mode, upstream is BYTES */ + parse->priv->upstream_format = GST_FORMAT_BYTES; return gst_pad_start_task (sinkpad, (GstTaskFunction) gst_base_parse_loop, sinkpad, NULL); @@ -3478,6 +3697,7 @@ gst_base_parse_activate (GstBaseParse * parse, gboolean active) result = klass->stop (parse); parse->priv->pad_mode = GST_PAD_MODE_NONE; + parse->priv->upstream_format = GST_FORMAT_UNDEFINED; } GST_DEBUG_OBJECT (parse, "activate return: %d", result); return result; @@ -3653,7 +3873,11 @@ gst_base_parse_set_frame_rate (GstBaseParse * parse, guint fps_num, gst_util_uint64_scale (GST_SECOND, fps_den * lead_out, fps_num); /* aim for about 1.5s to estimate duration */ if (parse->priv->update_interval < 0) { - parse->priv->update_interval = fps_num * 3 / (fps_den * 2); + guint64 interval = gst_util_uint64_scale (fps_num, 3, + G_GUINT64_CONSTANT (2) * fps_den); + + parse->priv->update_interval = MIN (interval, G_MAXINT); + GST_LOG_OBJECT (parse, "estimated update interval to %d frames", parse->priv->update_interval); } @@ -3832,7 +4056,10 @@ gst_base_parse_src_query_default (GstBaseParse * parse, GstQuery * query) if (!res) { /* Fall back on interpreting segment */ GST_OBJECT_LOCK (parse); - if (format == GST_FORMAT_BYTES) { + /* Only reply BYTES if upstream is in BYTES already, otherwise + * we're not in charge */ + if (format == GST_FORMAT_BYTES + && parse->priv->upstream_format == GST_FORMAT_BYTES) { dest_value = parse->priv->offset; res = TRUE; } else if (format == parse->segment.format && @@ -3842,9 +4069,10 @@ gst_base_parse_src_query_default (GstBaseParse * parse, GstQuery * query) res = TRUE; } GST_OBJECT_UNLOCK (parse); - if (!res) { + if (!res && parse->priv->upstream_format == GST_FORMAT_BYTES) { /* no precise result, upstream no idea either, then best estimate */ - /* priv->offset is updated in both PUSH/PULL modes */ + /* priv->offset is updated in both PUSH/PULL modes, *iff* we're + * in charge of things */ res = gst_base_parse_convert (parse, GST_FORMAT_BYTES, parse->priv->offset, format, &dest_value); } @@ -3894,8 +4122,8 @@ gst_base_parse_src_query_default (GstBaseParse * parse, GstQuery * query) if (!gst_base_parse_get_duration (parse, GST_FORMAT_TIME, &duration) || duration == -1) { /* seekable if we still have a chance to get duration later on */ - seekable = - parse->priv->upstream_seekable && parse->priv->update_interval; + seekable = parse->priv->upstream_seekable && + (parse->priv->update_interval > 0); } else { seekable = parse->priv->upstream_seekable; GST_LOG_OBJECT (parse, "already determine upstream seekabled: %d", @@ -4294,9 +4522,6 @@ gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event) if (rate < 0.0 && parse->priv->pad_mode == GST_PAD_MODE_PUSH) goto negative_rate; - if (rate < 0.0 && parse->priv->pad_mode == GST_PAD_MODE_PULL) - goto negative_rate_pull_mode; - if (start_type != GST_SEEK_TYPE_SET || (stop_type != GST_SEEK_TYPE_SET && stop_type != GST_SEEK_TYPE_NONE)) goto wrong_type; @@ -4322,20 +4547,36 @@ gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event) GST_DEBUG_OBJECT (parse, "accurate seek possible"); accurate = TRUE; } + if (accurate) { - GstClockTime startpos = seeksegment.position; + GstClockTime startpos; + if (rate >= 0) + startpos = seeksegment.position; + else + startpos = start; /* accurate requested, so ... seek a bit before target */ if (startpos < parse->priv->lead_in_ts) startpos = 0; else startpos -= parse->priv->lead_in_ts; + + if (seeksegment.stop == -1 && seeksegment.duration != -1) + seeksegment.stop = seeksegment.start + seeksegment.duration; + seekpos = gst_base_parse_find_offset (parse, startpos, TRUE, &start_ts); seekstop = gst_base_parse_find_offset (parse, seeksegment.stop, FALSE, NULL); } else { - start_ts = seeksegment.position; - if (!gst_base_parse_convert (parse, format, seeksegment.position, + if (rate >= 0) + start_ts = seeksegment.position; + else + start_ts = start; + + if (seeksegment.stop == -1 && seeksegment.duration != -1) + seeksegment.stop = seeksegment.start + seeksegment.duration; + + if (!gst_base_parse_convert (parse, format, start_ts, GST_FORMAT_BYTES, &seekpos)) goto convert_failed; if (!gst_base_parse_convert (parse, format, seeksegment.stop, @@ -4389,10 +4630,6 @@ gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event) gst_pad_push_event (parse->srcpad, gst_event_ref (fevent)); gst_pad_push_event (parse->sinkpad, fevent); gst_base_parse_clear_queues (parse); - } else { - /* keep track of our position */ - seeksegment.base = gst_segment_to_running_time (&seeksegment, - seeksegment.format, parse->segment.position); } memcpy (&parse->segment, &seeksegment, sizeof (GstSegment)); @@ -4499,12 +4736,6 @@ done: return res; /* ERRORS */ -negative_rate_pull_mode: - { - GST_FIXME_OBJECT (parse, "negative playback in pull mode needs fixing"); - res = FALSE; - goto done; - } negative_rate: { GST_DEBUG_OBJECT (parse, "negative playback rates delegated upstream."); @@ -4532,33 +4763,23 @@ convert_failed: } } -/* Checks if bitrates are available from upstream tags so that we don't - * override them later - */ static void -gst_base_parse_handle_tag (GstBaseParse * parse, GstEvent * event) +gst_base_parse_set_upstream_tags (GstBaseParse * parse, GstTagList * taglist) { - GstTagList *taglist = NULL; - guint tmp; - - gst_event_parse_tag (event, &taglist); - - /* We only care about stream tags here */ - if (gst_tag_list_get_scope (taglist) != GST_TAG_SCOPE_STREAM) + if (taglist == parse->priv->upstream_tags) return; - if (gst_tag_list_get_uint (taglist, GST_TAG_MINIMUM_BITRATE, &tmp)) { - GST_DEBUG_OBJECT (parse, "upstream min bitrate %d", tmp); - parse->priv->post_min_bitrate = FALSE; - } - if (gst_tag_list_get_uint (taglist, GST_TAG_BITRATE, &tmp)) { - GST_DEBUG_OBJECT (parse, "upstream avg bitrate %d", tmp); - parse->priv->post_avg_bitrate = FALSE; - } - if (gst_tag_list_get_uint (taglist, GST_TAG_MAXIMUM_BITRATE, &tmp)) { - GST_DEBUG_OBJECT (parse, "upstream max bitrate %d", tmp); - parse->priv->post_max_bitrate = FALSE; + if (parse->priv->upstream_tags) { + gst_tag_list_unref (parse->priv->upstream_tags); + parse->priv->upstream_tags = NULL; } + + GST_INFO_OBJECT (parse, "upstream tags: %" GST_PTR_FORMAT, taglist); + + if (taglist != NULL) + parse->priv->upstream_tags = gst_tag_list_ref (taglist); + + gst_base_parse_check_bitrate_tags (parse); } #if 0 @@ -4677,6 +4898,55 @@ gst_base_parse_set_ts_at_offset (GstBaseParse * parse, gsize offset) if (GST_CLOCK_TIME_IS_VALID (pts) && (parse->priv->prev_pts != pts)) parse->priv->prev_pts = parse->priv->next_pts = pts; - if (GST_CLOCK_TIME_IS_VALID (dts) && (parse->priv->prev_dts != dts)) + if (GST_CLOCK_TIME_IS_VALID (dts) && (parse->priv->prev_dts != dts)) { parse->priv->prev_dts = parse->priv->next_dts = dts; + parse->priv->prev_dts_from_pts = FALSE; + } +} + +/** + * gst_base_parse_merge_tags: + * @parse: a #GstBaseParse + * @tags: (allow-none): a #GstTagList to merge, or NULL to unset + * previously-set tags + * @mode: the #GstTagMergeMode to use, usually #GST_TAG_MERGE_REPLACE + * + * Sets the parser subclass's tags and how they should be merged with any + * upstream stream tags. This will override any tags previously-set + * with gst_base_parse_merge_tags(). + * + * Note that this is provided for convenience, and the subclass is + * not required to use this and can still do tag handling on its own. + * + * Since: 1.6 + */ +void +gst_base_parse_merge_tags (GstBaseParse * parse, GstTagList * tags, + GstTagMergeMode mode) +{ + g_return_if_fail (GST_IS_BASE_PARSE (parse)); + g_return_if_fail (tags == NULL || GST_IS_TAG_LIST (tags)); + g_return_if_fail (tags == NULL || mode != GST_TAG_MERGE_UNDEFINED); + + GST_OBJECT_LOCK (parse); + + if (tags != parse->priv->parser_tags) { + if (parse->priv->parser_tags) { + gst_tag_list_unref (parse->priv->parser_tags); + parse->priv->parser_tags = NULL; + parse->priv->parser_tags_merge_mode = GST_TAG_MERGE_APPEND; + } + if (tags) { + parse->priv->parser_tags = gst_tag_list_ref (tags); + parse->priv->parser_tags_merge_mode = mode; + } + + GST_DEBUG_OBJECT (parse, "setting parser tags to %" GST_PTR_FORMAT + " (mode %d)", tags, parse->priv->parser_tags_merge_mode); + + gst_base_parse_check_bitrate_tags (parse); + parse->priv->tags_changed = TRUE; + } + + GST_OBJECT_UNLOCK (parse); }