| /* GStreamer |
| * Copyright (C) 2008 Nokia Corporation. All rights reserved. |
| * Contact: Stefan Kost <stefan.kost@nokia.com> |
| * Copyright (C) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>. |
| * Copyright (C) 2011, Hewlett-Packard Development Company, L.P. |
| * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, Collabora Ltd. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| /** |
| * SECTION:gstbaseparse |
| * @short_description: Base class for stream parsers |
| * @see_also: #GstBaseTransform |
| * |
| * This base class is for parser elements that process data and splits it |
| * into separate audio/video/whatever frames. |
| * |
| * It provides for: |
| * <itemizedlist> |
| * <listitem><para>provides one sink pad and one source pad</para></listitem> |
| * <listitem><para>handles state changes</para></listitem> |
| * <listitem><para>can operate in pull mode or push mode</para></listitem> |
| * <listitem><para>handles seeking in both modes</para></listitem> |
| * <listitem><para>handles events (SEGMENT/EOS/FLUSH)</para></listitem> |
| * <listitem><para> |
| * handles queries (POSITION/DURATION/SEEKING/FORMAT/CONVERT) |
| * </para></listitem> |
| * <listitem><para>handles flushing</para></listitem> |
| * </itemizedlist> |
| * |
| * 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: |
| * <orderedlist> |
| * <listitem> |
| * <itemizedlist><title>Set-up phase</title> |
| * <listitem><para> |
| * #GstBaseParse calls @start to inform subclass that data processing is |
| * about to start now. |
| * </para></listitem> |
| * <listitem><para> |
| * #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. |
| * </para></listitem> |
| * <listitem><para> |
| * 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(). |
| * </para></listitem> |
| * <listitem><para> |
| * #GstBaseParse class sets up appropriate data passing mode (pull/push) |
| * and starts to process the data. |
| * </para></listitem> |
| * </itemizedlist> |
| * </listitem> |
| * <listitem> |
| * <itemizedlist> |
| * <title>Parsing phase</title> |
| * <listitem><para> |
| * #GstBaseParse gathers at least min_frame_size bytes of data either |
| * by pulling it from upstream or collecting buffers in an internal |
| * #GstAdapter. |
| * </para></listitem> |
| * <listitem><para> |
| * 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). |
| * </para><para> |
| * 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). |
| * </para><para> |
| * Note that @check_valid_frame might receive any small |
| * amount of input data when leftover data is being drained (e.g. at EOS). |
| * </para></listitem> |
| * <listitem><para> |
| * 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. |
| * </para></listitem> |
| * <listitem><para> |
| * 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. |
| * </para></listitem> |
| * </itemizedlist> |
| * </listitem> |
| * <listitem> |
| * <itemizedlist><title>Shutdown phase</title> |
| * <listitem><para> |
| * #GstBaseParse class calls @stop to inform the subclass that data |
| * parsing will be stopped. |
| * </para></listitem> |
| * </itemizedlist> |
| * </listitem> |
| * </orderedlist> |
| * |
| * Subclass is responsible for providing pad template caps for |
| * source and sink pads. The pads need to be named "sink" and "src". It also |
| * needs to set the fixed caps on srcpad, when the format is ensured (e.g. |
| * when base class calls subclass' @set_sink_caps function). |
| * |
| * This base class uses %GST_FORMAT_DEFAULT as a meaning of frames. So, |
| * subclass conversion routine needs to know that conversion from |
| * %GST_FORMAT_TIME to %GST_FORMAT_DEFAULT must return the |
| * frame number that can be found from the given byte position. |
| * |
| * #GstBaseParse uses subclasses conversion methods also for seeking (or |
| * otherwise uses its own default one, see also below). |
| * |
| * Subclass @start and @stop functions will be called to inform the beginning |
| * and end of data processing. |
| * |
| * Things that subclass need to take care of: |
| * <itemizedlist> |
| * <listitem><para>Provide pad templates</para></listitem> |
| * <listitem><para> |
| * Fixate the source pad caps when appropriate |
| * </para></listitem> |
| * <listitem><para> |
| * Inform base class how big data chunks should be retrieved. This is |
| * done with gst_base_parse_set_min_frame_size() function. |
| * </para></listitem> |
| * <listitem><para> |
| * 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. |
| * </para></listitem> |
| * <listitem><para>Provide conversion functions</para></listitem> |
| * <listitem><para> |
| * Update the duration information with gst_base_parse_set_duration() |
| * </para></listitem> |
| * <listitem><para> |
| * Optionally passthrough using gst_base_parse_set_passthrough() |
| * </para></listitem> |
| * <listitem><para> |
| * Configure various baseparse parameters using |
| * gst_base_parse_set_average_bitrate(), gst_base_parse_set_syncable() |
| * and gst_base_parse_set_frame_rate(). |
| * </para></listitem> |
| * <listitem><para> |
| * 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. |
| * </para></listitem> |
| * </itemizedlist> |
| * |
| */ |
| |
| /* TODO: |
| * - In push mode provide a queue of adapter-"queued" buffers for upstream |
| * buffer metadata |
| * - Queue buffers/events until caps are set |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <gst/base/gstadapter.h> |
| |
| #include "gstbaseparse.h" |
| |
| /* FIXME: get rid of old GstIndex code */ |
| #include "gstindex.h" |
| #include "gstindex.c" |
| #include "gstmemindex.c" |
| |
| #define GST_BASE_PARSE_FRAME_PRIVATE_FLAG_NOALLOC (1 << 0) |
| |
| #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 |
| |
| /* Supported formats */ |
| static const GstFormat fmtlist[] = { |
| GST_FORMAT_DEFAULT, |
| GST_FORMAT_BYTES, |
| GST_FORMAT_TIME, |
| GST_FORMAT_UNDEFINED |
| }; |
| |
| #define GST_BASE_PARSE_GET_PRIVATE(obj) \ |
| (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_BASE_PARSE, GstBaseParsePrivate)) |
| |
| struct _GstBaseParsePrivate |
| { |
| GstPadMode pad_mode; |
| |
| GstAdapter *adapter; |
| |
| gint64 duration; |
| GstFormat duration_fmt; |
| gint64 estimated_duration; |
| gint64 estimated_drift; |
| |
| guint min_frame_size; |
| gboolean disable_passthrough; |
| gboolean passthrough; |
| gboolean pts_interpolate; |
| gboolean infer_ts; |
| gboolean syncable; |
| gboolean has_timing_info; |
| guint fps_num, fps_den; |
| gint update_interval; |
| guint bitrate; |
| guint lead_in, lead_out; |
| GstClockTime lead_in_ts, lead_out_ts; |
| GstClockTime min_latency, max_latency; |
| |
| gboolean discont; |
| gboolean flushing; |
| gboolean drain; |
| gboolean saw_gaps; |
| |
| gint64 offset; |
| gint64 sync_offset; |
| GstClockTime next_pts; |
| GstClockTime next_dts; |
| GstClockTime prev_pts; |
| GstClockTime prev_dts; |
| GstClockTime frame_duration; |
| gboolean seen_keyframe; |
| gboolean is_video; |
| gint flushed; |
| |
| guint64 framecount; |
| guint64 bytecount; |
| guint64 data_bytecount; |
| guint64 acc_duration; |
| GstClockTime first_frame_pts; |
| GstClockTime first_frame_dts; |
| gint64 first_frame_offset; |
| |
| gboolean post_min_bitrate; |
| gboolean post_avg_bitrate; |
| gboolean post_max_bitrate; |
| |
| guint min_bitrate; |
| guint avg_bitrate; |
| guint max_bitrate; |
| guint posted_avg_bitrate; |
| |
| /* frames/buffers that are queued and ready to go on OK */ |
| GQueue queued_frames; |
| |
| GstBuffer *cache; |
| |
| /* index entry storage, either ours or provided */ |
| GstIndex *index; |
| gint index_id; |
| gboolean own_index; |
| GMutex index_lock; |
| |
| /* seek table entries only maintained if upstream is BYTE seekable */ |
| gboolean upstream_seekable; |
| gboolean upstream_has_duration; |
| gint64 upstream_size; |
| GstFormat upstream_format; |
| /* minimum distance between two index entries */ |
| GstClockTimeDiff idx_interval; |
| guint64 idx_byte_interval; |
| /* ts and offset of last entry added */ |
| GstClockTime index_last_ts; |
| gint64 index_last_offset; |
| gboolean index_last_valid; |
| |
| /* timestamps currently produced are accurate, e.g. started from 0 onwards */ |
| gboolean exact_position; |
| /* seek events are temporarily kept to match them with newsegments */ |
| GSList *pending_seeks; |
| |
| /* reverse playback */ |
| GSList *buffers_pending; |
| GSList *buffers_head; |
| GSList *buffers_queued; |
| GSList *buffers_send; |
| GstClockTime last_pts; |
| GstClockTime last_dts; |
| gint64 last_offset; |
| |
| /* Pending serialized events */ |
| GList *pending_events; |
| |
| /* If baseparse has checked the caps to identify if it is |
| * handling video or audio */ |
| gboolean checked_media; |
| |
| /* offset of last parsed frame/data */ |
| gint64 prev_offset; |
| /* force a new frame, regardless of offset */ |
| gboolean new_frame; |
| /* whether we are merely scanning for a frame */ |
| gboolean scanning; |
| /* ... and resulting frame, if any */ |
| GstBaseParseFrame *scanned_frame; |
| |
| /* TRUE if we're still detecting the format, i.e. |
| * if ::detect() is still called for future buffers */ |
| gboolean detecting; |
| GList *detect_buffers; |
| guint detect_buffers_size; |
| |
| /* True when no buffers have been received yet */ |
| gboolean first_buffer; |
| |
| /* if TRUE, a STREAM_START event needs to be pushed */ |
| gboolean push_stream_start; |
| |
| /* 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 |
| { |
| GstSegment segment; |
| gboolean accurate; |
| gint64 offset; |
| GstClockTime start_ts; |
| } GstBaseParseSeek; |
| |
| #define DEFAULT_DISABLE_PASSTHROUGH FALSE |
| |
| enum |
| { |
| PROP_0, |
| PROP_DISABLE_PASSTHROUGH, |
| PROP_LAST |
| }; |
| |
| #define GST_BASE_PARSE_INDEX_LOCK(parse) \ |
| g_mutex_lock (&parse->priv->index_lock); |
| #define GST_BASE_PARSE_INDEX_UNLOCK(parse) \ |
| g_mutex_unlock (&parse->priv->index_lock); |
| |
| static GstElementClass *parent_class = NULL; |
| |
| static void gst_base_parse_class_init (GstBaseParseClass * klass); |
| static void gst_base_parse_init (GstBaseParse * parse, |
| GstBaseParseClass * klass); |
| |
| GType |
| gst_base_parse_get_type (void) |
| { |
| static volatile gsize base_parse_type = 0; |
| |
| if (g_once_init_enter (&base_parse_type)) { |
| static const GTypeInfo base_parse_info = { |
| sizeof (GstBaseParseClass), |
| (GBaseInitFunc) NULL, |
| (GBaseFinalizeFunc) NULL, |
| (GClassInitFunc) gst_base_parse_class_init, |
| NULL, |
| NULL, |
| sizeof (GstBaseParse), |
| 0, |
| (GInstanceInitFunc) gst_base_parse_init, |
| }; |
| GType _type; |
| |
| _type = g_type_register_static (GST_TYPE_ELEMENT, |
| "GstBaseParse", &base_parse_info, G_TYPE_FLAG_ABSTRACT); |
| g_once_init_leave (&base_parse_type, _type); |
| } |
| return (GType) base_parse_type; |
| } |
| |
| static void gst_base_parse_finalize (GObject * object); |
| |
| static GstStateChangeReturn gst_base_parse_change_state (GstElement * element, |
| GstStateChange transition); |
| static void gst_base_parse_reset (GstBaseParse * parse); |
| |
| #if 0 |
| static void gst_base_parse_set_index (GstElement * element, GstIndex * index); |
| static GstIndex *gst_base_parse_get_index (GstElement * element); |
| #endif |
| |
| static gboolean gst_base_parse_sink_activate (GstPad * sinkpad, |
| GstObject * parent); |
| 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_set_upstream_tags (GstBaseParse * parse, |
| GstTagList * taglist); |
| |
| static void gst_base_parse_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_base_parse_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| |
| static gboolean gst_base_parse_src_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static gboolean gst_base_parse_src_query (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| |
| static gboolean gst_base_parse_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static gboolean gst_base_parse_sink_query (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| |
| static GstFlowReturn gst_base_parse_chain (GstPad * pad, GstObject * parent, |
| GstBuffer * buffer); |
| static void gst_base_parse_loop (GstPad * pad); |
| |
| static GstFlowReturn gst_base_parse_parse_frame (GstBaseParse * parse, |
| GstBaseParseFrame * frame); |
| |
| static gboolean gst_base_parse_sink_event_default (GstBaseParse * parse, |
| GstEvent * event); |
| |
| static gboolean gst_base_parse_src_event_default (GstBaseParse * parse, |
| GstEvent * event); |
| |
| static gboolean gst_base_parse_sink_query_default (GstBaseParse * parse, |
| GstQuery * query); |
| static gboolean gst_base_parse_src_query_default (GstBaseParse * parse, |
| GstQuery * query); |
| |
| static void gst_base_parse_drain (GstBaseParse * parse); |
| |
| static gint64 gst_base_parse_find_offset (GstBaseParse * parse, |
| GstClockTime time, gboolean before, GstClockTime * _ts); |
| static GstFlowReturn gst_base_parse_locate_time (GstBaseParse * parse, |
| GstClockTime * _time, gint64 * _offset); |
| |
| static GstFlowReturn gst_base_parse_start_fragment (GstBaseParse * parse); |
| static GstFlowReturn gst_base_parse_finish_fragment (GstBaseParse * parse, |
| gboolean prev_head); |
| static GstFlowReturn gst_base_parse_send_buffers (GstBaseParse * parse); |
| |
| static inline GstFlowReturn gst_base_parse_check_sync (GstBaseParse * parse); |
| |
| static gboolean gst_base_parse_is_seekable (GstBaseParse * parse); |
| |
| static void gst_base_parse_push_pending_events (GstBaseParse * parse); |
| |
| static void |
| gst_base_parse_clear_queues (GstBaseParse * parse) |
| { |
| g_slist_foreach (parse->priv->buffers_queued, (GFunc) gst_buffer_unref, NULL); |
| g_slist_free (parse->priv->buffers_queued); |
| parse->priv->buffers_queued = NULL; |
| g_slist_foreach (parse->priv->buffers_pending, (GFunc) gst_buffer_unref, |
| NULL); |
| g_slist_free (parse->priv->buffers_pending); |
| parse->priv->buffers_pending = NULL; |
| g_slist_foreach (parse->priv->buffers_head, (GFunc) gst_buffer_unref, NULL); |
| g_slist_free (parse->priv->buffers_head); |
| parse->priv->buffers_head = NULL; |
| g_slist_foreach (parse->priv->buffers_send, (GFunc) gst_buffer_unref, NULL); |
| g_slist_free (parse->priv->buffers_send); |
| parse->priv->buffers_send = NULL; |
| |
| g_list_foreach (parse->priv->detect_buffers, (GFunc) gst_buffer_unref, NULL); |
| g_list_free (parse->priv->detect_buffers); |
| parse->priv->detect_buffers = NULL; |
| parse->priv->detect_buffers_size = 0; |
| |
| g_queue_foreach (&parse->priv->queued_frames, |
| (GFunc) gst_base_parse_frame_free, NULL); |
| g_queue_clear (&parse->priv->queued_frames); |
| |
| gst_buffer_replace (&parse->priv->cache, NULL); |
| |
| g_list_foreach (parse->priv->pending_events, (GFunc) gst_event_unref, NULL); |
| g_list_free (parse->priv->pending_events); |
| parse->priv->pending_events = NULL; |
| |
| parse->priv->checked_media = FALSE; |
| } |
| |
| static void |
| gst_base_parse_finalize (GObject * object) |
| { |
| GstBaseParse *parse = GST_BASE_PARSE (object); |
| |
| g_object_unref (parse->priv->adapter); |
| |
| if (parse->priv->index) { |
| gst_object_unref (parse->priv->index); |
| parse->priv->index = NULL; |
| } |
| g_mutex_clear (&parse->priv->index_lock); |
| |
| gst_base_parse_clear_queues (parse); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_base_parse_class_init (GstBaseParseClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *gstelement_class; |
| |
| gobject_class = G_OBJECT_CLASS (klass); |
| g_type_class_add_private (klass, sizeof (GstBaseParsePrivate)); |
| parent_class = g_type_class_peek_parent (klass); |
| |
| gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_base_parse_finalize); |
| gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_base_parse_set_property); |
| gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_base_parse_get_property); |
| |
| /** |
| * GstBaseParse:disable-passthrough: |
| * |
| * If set to %TRUE, baseparse will unconditionally force parsing of the |
| * incoming data. This can be required in the rare cases where the incoming |
| * side-data (caps, pts, dts, ...) is not trusted by the user and wants to |
| * force validation and parsing of the incoming data. |
| * If set to %FALSE, decision of whether to parse the data or not is up to |
| * the implementation (standard behaviour). |
| */ |
| g_object_class_install_property (gobject_class, PROP_DISABLE_PASSTHROUGH, |
| g_param_spec_boolean ("disable-passthrough", "Disable passthrough", |
| "Force processing (disables passthrough)", |
| DEFAULT_DISABLE_PASSTHROUGH, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| gstelement_class = (GstElementClass *) klass; |
| gstelement_class->change_state = |
| GST_DEBUG_FUNCPTR (gst_base_parse_change_state); |
| |
| #if 0 |
| gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_base_parse_set_index); |
| gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_base_parse_get_index); |
| #endif |
| |
| /* Default handlers */ |
| klass->sink_event = gst_base_parse_sink_event_default; |
| klass->src_event = gst_base_parse_src_event_default; |
| klass->sink_query = gst_base_parse_sink_query_default; |
| klass->src_query = gst_base_parse_src_query_default; |
| klass->convert = gst_base_parse_convert_default; |
| |
| GST_DEBUG_CATEGORY_INIT (gst_base_parse_debug, "baseparse", 0, |
| "baseparse element"); |
| } |
| |
| static void |
| gst_base_parse_init (GstBaseParse * parse, GstBaseParseClass * bclass) |
| { |
| GstPadTemplate *pad_template; |
| |
| GST_DEBUG_OBJECT (parse, "gst_base_parse_init"); |
| |
| parse->priv = GST_BASE_PARSE_GET_PRIVATE (parse); |
| |
| pad_template = |
| gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "sink"); |
| g_return_if_fail (pad_template != NULL); |
| parse->sinkpad = gst_pad_new_from_template (pad_template, "sink"); |
| gst_pad_set_event_function (parse->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_base_parse_sink_event)); |
| gst_pad_set_query_function (parse->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_base_parse_sink_query)); |
| gst_pad_set_chain_function (parse->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_base_parse_chain)); |
| gst_pad_set_activate_function (parse->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_base_parse_sink_activate)); |
| gst_pad_set_activatemode_function (parse->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_base_parse_sink_activate_mode)); |
| GST_PAD_SET_PROXY_ALLOCATION (parse->sinkpad); |
| gst_element_add_pad (GST_ELEMENT (parse), parse->sinkpad); |
| |
| GST_DEBUG_OBJECT (parse, "sinkpad created"); |
| |
| pad_template = |
| gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "src"); |
| g_return_if_fail (pad_template != NULL); |
| parse->srcpad = gst_pad_new_from_template (pad_template, "src"); |
| gst_pad_set_event_function (parse->srcpad, |
| GST_DEBUG_FUNCPTR (gst_base_parse_src_event)); |
| gst_pad_set_query_function (parse->srcpad, |
| GST_DEBUG_FUNCPTR (gst_base_parse_src_query)); |
| gst_pad_use_fixed_caps (parse->srcpad); |
| gst_element_add_pad (GST_ELEMENT (parse), parse->srcpad); |
| GST_DEBUG_OBJECT (parse, "src created"); |
| |
| g_queue_init (&parse->priv->queued_frames); |
| |
| parse->priv->adapter = gst_adapter_new (); |
| |
| parse->priv->pad_mode = GST_PAD_MODE_NONE; |
| |
| g_mutex_init (&parse->priv->index_lock); |
| |
| /* init state */ |
| gst_base_parse_reset (parse); |
| 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 |
| gst_base_parse_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstBaseParse *parse = GST_BASE_PARSE (object); |
| |
| switch (prop_id) { |
| case PROP_DISABLE_PASSTHROUGH: |
| parse->priv->disable_passthrough = g_value_get_boolean (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_base_parse_get_property (GObject * object, guint prop_id, GValue * value, |
| GParamSpec * pspec) |
| { |
| GstBaseParse *parse = GST_BASE_PARSE (object); |
| |
| switch (prop_id) { |
| case PROP_DISABLE_PASSTHROUGH: |
| g_value_set_boolean (value, parse->priv->disable_passthrough); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static GstBaseParseFrame * |
| gst_base_parse_frame_copy (GstBaseParseFrame * frame) |
| { |
| GstBaseParseFrame *copy; |
| |
| copy = g_slice_dup (GstBaseParseFrame, frame); |
| copy->buffer = gst_buffer_ref (frame->buffer); |
| copy->_private_flags &= ~GST_BASE_PARSE_FRAME_PRIVATE_FLAG_NOALLOC; |
| |
| GST_TRACE ("copied frame %p -> %p", frame, copy); |
| |
| return copy; |
| } |
| |
| void |
| gst_base_parse_frame_free (GstBaseParseFrame * frame) |
| { |
| GST_TRACE ("freeing frame %p", frame); |
| |
| if (frame->buffer) { |
| gst_buffer_unref (frame->buffer); |
| frame->buffer = NULL; |
| } |
| |
| if (!(frame->_private_flags & GST_BASE_PARSE_FRAME_PRIVATE_FLAG_NOALLOC)) { |
| g_slice_free (GstBaseParseFrame, frame); |
| } else { |
| memset (frame, 0, sizeof (*frame)); |
| } |
| } |
| |
| G_DEFINE_BOXED_TYPE (GstBaseParseFrame, gst_base_parse_frame, |
| (GBoxedCopyFunc) gst_base_parse_frame_copy, |
| (GBoxedFreeFunc) gst_base_parse_frame_free); |
| |
| /** |
| * gst_base_parse_frame_init: |
| * @frame: #GstBaseParseFrame. |
| * |
| * Sets a #GstBaseParseFrame to initial state. Currently this means |
| * all public fields are zero-ed and a private flag is set to make |
| * sure gst_base_parse_frame_free() only frees the contents but not |
| * the actual frame. Use this function to initialise a #GstBaseParseFrame |
| * allocated on the stack. |
| */ |
| void |
| gst_base_parse_frame_init (GstBaseParseFrame * frame) |
| { |
| memset (frame, 0, sizeof (GstBaseParseFrame)); |
| frame->_private_flags = GST_BASE_PARSE_FRAME_PRIVATE_FLAG_NOALLOC; |
| GST_TRACE ("inited frame %p", frame); |
| } |
| |
| /** |
| * gst_base_parse_frame_new: |
| * @buffer: (transfer none): a #GstBuffer |
| * @flags: the flags |
| * @overhead: number of bytes in this frame which should be counted as |
| * metadata overhead, ie. not used to calculate the average bitrate. |
| * Set to -1 to mark the entire frame as metadata. If in doubt, set to 0. |
| * |
| * Allocates a new #GstBaseParseFrame. This function is mainly for bindings, |
| * elements written in C should usually allocate the frame on the stack and |
| * then use gst_base_parse_frame_init() to initialise it. |
| * |
| * Returns: a newly-allocated #GstBaseParseFrame. Free with |
| * gst_base_parse_frame_free() when no longer needed. |
| */ |
| GstBaseParseFrame * |
| gst_base_parse_frame_new (GstBuffer * buffer, GstBaseParseFrameFlags flags, |
| gint overhead) |
| { |
| GstBaseParseFrame *frame; |
| |
| frame = g_slice_new0 (GstBaseParseFrame); |
| frame->buffer = gst_buffer_ref (buffer); |
| |
| GST_TRACE ("created frame %p", frame); |
| return frame; |
| } |
| |
| static inline void |
| gst_base_parse_update_flags (GstBaseParse * parse) |
| { |
| parse->flags = 0; |
| |
| /* set flags one by one for clarity */ |
| if (G_UNLIKELY (parse->priv->drain)) |
| parse->flags |= GST_BASE_PARSE_FLAG_DRAINING; |
| |
| /* losing sync is pretty much a discont (and vice versa), no ? */ |
| if (G_UNLIKELY (parse->priv->discont)) |
| 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) |
| { |
| GST_OBJECT_LOCK (parse); |
| gst_segment_init (&parse->segment, GST_FORMAT_TIME); |
| parse->priv->duration = -1; |
| parse->priv->min_frame_size = 1; |
| parse->priv->discont = TRUE; |
| parse->priv->flushing = FALSE; |
| parse->priv->saw_gaps = FALSE; |
| parse->priv->offset = 0; |
| parse->priv->sync_offset = 0; |
| parse->priv->update_interval = -1; |
| parse->priv->fps_num = parse->priv->fps_den = 0; |
| parse->priv->frame_duration = GST_CLOCK_TIME_NONE; |
| parse->priv->lead_in = parse->priv->lead_out = 0; |
| parse->priv->lead_in_ts = parse->priv->lead_out_ts = 0; |
| parse->priv->bitrate = 0; |
| parse->priv->framecount = 0; |
| parse->priv->bytecount = 0; |
| parse->priv->acc_duration = 0; |
| parse->priv->first_frame_pts = GST_CLOCK_TIME_NONE; |
| parse->priv->first_frame_dts = GST_CLOCK_TIME_NONE; |
| parse->priv->first_frame_offset = -1; |
| parse->priv->estimated_duration = -1; |
| parse->priv->estimated_drift = 0; |
| parse->priv->next_pts = GST_CLOCK_TIME_NONE; |
| parse->priv->next_dts = 0; |
| parse->priv->syncable = TRUE; |
| parse->priv->disable_passthrough = DEFAULT_DISABLE_PASSTHROUGH; |
| parse->priv->passthrough = FALSE; |
| parse->priv->pts_interpolate = TRUE; |
| parse->priv->infer_ts = TRUE; |
| parse->priv->has_timing_info = FALSE; |
| parse->priv->min_bitrate = G_MAXUINT; |
| parse->priv->max_bitrate = 0; |
| parse->priv->avg_bitrate = 0; |
| parse->priv->posted_avg_bitrate = 0; |
| |
| parse->priv->index_last_ts = GST_CLOCK_TIME_NONE; |
| parse->priv->index_last_offset = -1; |
| parse->priv->index_last_valid = TRUE; |
| parse->priv->upstream_seekable = FALSE; |
| parse->priv->upstream_size = 0; |
| parse->priv->upstream_has_duration = FALSE; |
| parse->priv->upstream_format = GST_FORMAT_UNDEFINED; |
| parse->priv->idx_interval = 0; |
| parse->priv->idx_byte_interval = 0; |
| parse->priv->exact_position = TRUE; |
| parse->priv->seen_keyframe = FALSE; |
| parse->priv->checked_media = FALSE; |
| |
| parse->priv->last_dts = GST_CLOCK_TIME_NONE; |
| parse->priv->last_pts = GST_CLOCK_TIME_NONE; |
| parse->priv->last_offset = 0; |
| |
| parse->priv->skip = 0; |
| |
| g_list_foreach (parse->priv->pending_events, (GFunc) gst_mini_object_unref, |
| NULL); |
| g_list_free (parse->priv->pending_events); |
| parse->priv->pending_events = NULL; |
| |
| if (parse->priv->cache) { |
| gst_buffer_unref (parse->priv->cache); |
| parse->priv->cache = NULL; |
| } |
| |
| g_slist_foreach (parse->priv->pending_seeks, (GFunc) g_free, NULL); |
| g_slist_free (parse->priv->pending_seeks); |
| parse->priv->pending_seeks = NULL; |
| |
| 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; |
| |
| g_list_foreach (parse->priv->detect_buffers, (GFunc) gst_buffer_unref, NULL); |
| g_list_free (parse->priv->detect_buffers); |
| parse->priv->detect_buffers = NULL; |
| parse->priv->detect_buffers_size = 0; |
| 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; |
| } |
| |
| /* 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. |
| * |
| * Default callback for parse_frame. |
| */ |
| static GstFlowReturn |
| gst_base_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame) |
| { |
| GstBuffer *buffer = frame->buffer; |
| |
| if (!GST_BUFFER_PTS_IS_VALID (buffer) && |
| GST_CLOCK_TIME_IS_VALID (parse->priv->next_pts)) { |
| GST_BUFFER_PTS (buffer) = parse->priv->next_pts; |
| } |
| if (!GST_BUFFER_DTS_IS_VALID (buffer) && |
| GST_CLOCK_TIME_IS_VALID (parse->priv->next_dts)) { |
| GST_BUFFER_DTS (buffer) = parse->priv->next_dts; |
| } |
| if (!GST_BUFFER_DURATION_IS_VALID (buffer) && |
| GST_CLOCK_TIME_IS_VALID (parse->priv->frame_duration)) { |
| GST_BUFFER_DURATION (buffer) = parse->priv->frame_duration; |
| } |
| return GST_FLOW_OK; |
| } |
| |
| /* gst_base_parse_convert: |
| * @parse: #GstBaseParse. |
| * @src_format: #GstFormat describing the source format. |
| * @src_value: Source value to be converted. |
| * @dest_format: #GstFormat defining the converted format. |
| * @dest_value: Pointer where the conversion result will be put. |
| * |
| * Converts using configured "convert" vmethod in #GstBaseParse class. |
| * |
| * Returns: %TRUE if conversion was successful. |
| */ |
| static gboolean |
| gst_base_parse_convert (GstBaseParse * parse, |
| GstFormat src_format, |
| gint64 src_value, GstFormat dest_format, gint64 * dest_value) |
| { |
| GstBaseParseClass *klass = GST_BASE_PARSE_GET_CLASS (parse); |
| gboolean ret; |
| |
| g_return_val_if_fail (dest_value != NULL, FALSE); |
| |
| if (!klass->convert) |
| return FALSE; |
| |
| ret = klass->convert (parse, src_format, src_value, dest_format, dest_value); |
| |
| #ifndef GST_DISABLE_GST_DEBUG |
| { |
| if (ret) { |
| if (src_format == GST_FORMAT_TIME && dest_format == GST_FORMAT_BYTES) { |
| GST_LOG_OBJECT (parse, |
| "TIME -> BYTES: %" GST_TIME_FORMAT " -> %" G_GINT64_FORMAT, |
| GST_TIME_ARGS (src_value), *dest_value); |
| } else if (dest_format == GST_FORMAT_TIME && |
| src_format == GST_FORMAT_BYTES) { |
| GST_LOG_OBJECT (parse, |
| "BYTES -> TIME: %" G_GINT64_FORMAT " -> %" GST_TIME_FORMAT, |
| src_value, GST_TIME_ARGS (*dest_value)); |
| } else { |
| GST_LOG_OBJECT (parse, |
| "%s -> %s: %" G_GINT64_FORMAT " -> %" G_GINT64_FORMAT, |
| GST_STR_NULL (gst_format_get_name (src_format)), |
| GST_STR_NULL (gst_format_get_name (dest_format)), |
| src_value, *dest_value); |
| } |
| } else { |
| GST_DEBUG_OBJECT (parse, "conversion failed"); |
| } |
| } |
| #endif |
| |
| 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); |
| |
| 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. |
| * |
| * Handler for sink pad events. |
| * |
| * Returns: %TRUE if the event was handled. |
| */ |
| static gboolean |
| gst_base_parse_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| GstBaseParse *parse = GST_BASE_PARSE (parent); |
| GstBaseParseClass *bclass = GST_BASE_PARSE_GET_CLASS (parse); |
| gboolean ret; |
| |
| ret = bclass->sink_event (parse, event); |
| |
| return ret; |
| } |
| |
| /* gst_base_parse_sink_event_default: |
| * @parse: #GstBaseParse. |
| * @event: #GstEvent to be handled. |
| * |
| * Element-level event handler function. |
| * |
| * The event will be unreffed only if it has been handled and this |
| * function returns %TRUE |
| * |
| * Returns: %TRUE if the event was handled and not need forwarding. |
| */ |
| static gboolean |
| gst_base_parse_sink_event_default (GstBaseParse * parse, GstEvent * event) |
| { |
| GstBaseParseClass *klass = GST_BASE_PARSE_GET_CLASS (parse); |
| gboolean ret = FALSE; |
| gboolean forward_immediate = FALSE; |
| |
| GST_DEBUG_OBJECT (parse, "handling event %d, %s", GST_EVENT_TYPE (event), |
| GST_EVENT_TYPE_NAME (event)); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_CAPS: |
| { |
| GstCaps *caps; |
| |
| gst_event_parse_caps (event, &caps); |
| GST_DEBUG_OBJECT (parse, "caps: %" GST_PTR_FORMAT, caps); |
| |
| if (klass->set_sink_caps) |
| ret = klass->set_sink_caps (parse, caps); |
| else |
| ret = TRUE; |
| |
| /* will send our own caps downstream */ |
| gst_event_unref (event); |
| event = NULL; |
| break; |
| } |
| case GST_EVENT_SEGMENT: |
| { |
| const GstSegment *in_segment; |
| GstSegment out_segment; |
| gint64 offset = 0, next_dts; |
| guint32 seqnum = gst_event_get_seqnum (event); |
| |
| gst_event_parse_segment (event, &in_segment); |
| gst_segment_init (&out_segment, GST_FORMAT_TIME); |
| out_segment.rate = in_segment->rate; |
| out_segment.applied_rate = in_segment->applied_rate; |
| |
| GST_DEBUG_OBJECT (parse, "segment %" GST_SEGMENT_FORMAT, in_segment); |
| |
| parse->priv->upstream_format = in_segment->format; |
| if (in_segment->format == GST_FORMAT_BYTES) { |
| GstBaseParseSeek *seek = NULL; |
| GSList *node; |
| |
| /* stop time is allowed to be open-ended, but not start & pos */ |
| offset = in_segment->time; |
| |
| GST_OBJECT_LOCK (parse); |
| for (node = parse->priv->pending_seeks; node; node = node->next) { |
| GstBaseParseSeek *tmp = node->data; |
| |
| if (tmp->offset == offset) { |
| seek = tmp; |
| break; |
| } |
| } |
| parse->priv->pending_seeks = |
| g_slist_remove (parse->priv->pending_seeks, seek); |
| GST_OBJECT_UNLOCK (parse); |
| |
| if (seek) { |
| GST_DEBUG_OBJECT (parse, |
| "Matched newsegment to%s seek: %" GST_SEGMENT_FORMAT, |
| seek->accurate ? " accurate" : "", &seek->segment); |
| |
| out_segment.start = seek->segment.start; |
| out_segment.stop = seek->segment.stop; |
| out_segment.time = seek->segment.start; |
| |
| next_dts = seek->start_ts; |
| parse->priv->exact_position = seek->accurate; |
| g_free (seek); |
| } else { |
| /* best attempt convert */ |
| /* as these are only estimates, stop is kept open-ended to avoid |
| * premature cutting */ |
| gst_base_parse_convert (parse, GST_FORMAT_BYTES, in_segment->start, |
| GST_FORMAT_TIME, (gint64 *) & next_dts); |
| |
| out_segment.start = next_dts; |
| out_segment.stop = GST_CLOCK_TIME_NONE; |
| out_segment.time = next_dts; |
| |
| parse->priv->exact_position = (in_segment->start == 0); |
| } |
| |
| gst_event_unref (event); |
| |
| event = gst_event_new_segment (&out_segment); |
| gst_event_set_seqnum (event, seqnum); |
| |
| GST_DEBUG_OBJECT (parse, "Converted incoming segment to TIME. %" |
| GST_SEGMENT_FORMAT, in_segment); |
| |
| } else if (in_segment->format != GST_FORMAT_TIME) { |
| /* Unknown incoming segment format. Output a default open-ended |
| * TIME segment */ |
| gst_event_unref (event); |
| |
| out_segment.start = 0; |
| out_segment.stop = GST_CLOCK_TIME_NONE; |
| out_segment.time = 0; |
| |
| event = gst_event_new_segment (&out_segment); |
| gst_event_set_seqnum (event, seqnum); |
| |
| next_dts = 0; |
| } else { |
| /* not considered BYTE seekable if it is talking to us in TIME, |
| * whatever else it might claim */ |
| parse->priv->upstream_seekable = FALSE; |
| next_dts = in_segment->start; |
| gst_event_copy_segment (event, &out_segment); |
| } |
| |
| memcpy (&parse->segment, &out_segment, sizeof (GstSegment)); |
| |
| /* |
| gst_segment_set_newsegment (&parse->segment, update, rate, |
| applied_rate, format, start, stop, start); |
| */ |
| |
| ret = TRUE; |
| |
| /* save the segment for later, right before we push a new buffer so that |
| * the caps are fixed and the next linked element can receive |
| * the segment but finish the current segment */ |
| GST_DEBUG_OBJECT (parse, "draining current segment"); |
| if (in_segment->rate > 0.0) |
| gst_base_parse_drain (parse); |
| else |
| gst_base_parse_finish_fragment (parse, FALSE); |
| gst_adapter_clear (parse->priv->adapter); |
| |
| parse->priv->offset = offset; |
| parse->priv->sync_offset = offset; |
| parse->priv->next_dts = next_dts; |
| parse->priv->next_pts = GST_CLOCK_TIME_NONE; |
| parse->priv->last_pts = GST_CLOCK_TIME_NONE; |
| 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->discont = TRUE; |
| parse->priv->seen_keyframe = FALSE; |
| parse->priv->skip = 0; |
| break; |
| } |
| |
| case GST_EVENT_SEGMENT_DONE: |
| /* need to drain now, rather than upon a new segment, |
| * since that would have SEGMENT_DONE come before potential |
| * delayed last part of the current segment */ |
| GST_DEBUG_OBJECT (parse, "draining current segment"); |
| if (parse->segment.rate > 0.0) |
| gst_base_parse_drain (parse); |
| else |
| gst_base_parse_finish_fragment (parse, FALSE); |
| /* Also forward event immediately, there might be no new data |
| * coming afterwards that would allow us to forward it later */ |
| forward_immediate = TRUE; |
| break; |
| |
| case GST_EVENT_FLUSH_START: |
| GST_OBJECT_LOCK (parse); |
| parse->priv->flushing = TRUE; |
| GST_OBJECT_UNLOCK (parse); |
| break; |
| |
| case GST_EVENT_FLUSH_STOP: |
| gst_adapter_clear (parse->priv->adapter); |
| gst_base_parse_clear_queues (parse); |
| parse->priv->flushing = FALSE; |
| parse->priv->discont = TRUE; |
| parse->priv->last_pts = GST_CLOCK_TIME_NONE; |
| parse->priv->last_dts = GST_CLOCK_TIME_NONE; |
| parse->priv->new_frame = TRUE; |
| parse->priv->skip = 0; |
| |
| forward_immediate = TRUE; |
| break; |
| |
| case GST_EVENT_EOS: |
| if (parse->segment.rate > 0.0) |
| gst_base_parse_drain (parse); |
| else |
| gst_base_parse_finish_fragment (parse, TRUE); |
| |
| /* If we STILL have zero frames processed, fire an error */ |
| if (parse->priv->framecount == 0 && !parse->priv->saw_gaps && |
| !parse->priv->first_buffer) { |
| GST_ELEMENT_ERROR (parse, STREAM, WRONG_TYPE, |
| ("No valid frames found before end of stream"), (NULL)); |
| } |
| |
| 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_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:{ |
| /* FIXME: Code duplicated from libgstvideo because core can't depend on -base */ |
| #ifndef GST_VIDEO_EVENT_STILL_STATE_NAME |
| #define GST_VIDEO_EVENT_STILL_STATE_NAME "GstEventStillFrame" |
| #endif |
| |
| const GstStructure *s; |
| gboolean ev_still_state; |
| |
| s = gst_event_get_structure (event); |
| if (s != NULL && |
| gst_structure_has_name (s, GST_VIDEO_EVENT_STILL_STATE_NAME) && |
| gst_structure_get_boolean (s, "still-state", &ev_still_state)) { |
| if (ev_still_state) { |
| GST_DEBUG_OBJECT (parse, "draining current data for still-frame"); |
| if (parse->segment.rate > 0.0) |
| gst_base_parse_drain (parse); |
| else |
| gst_base_parse_finish_fragment (parse, TRUE); |
| } |
| forward_immediate = TRUE; |
| } |
| break; |
| } |
| case GST_EVENT_GAP: |
| { |
| 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))) { |
| GST_DEBUG_OBJECT (parse, |
| "Store caps event to pending list for initial pre-rolling"); |
| parse->priv->pending_events = |
| g_list_prepend (parse->priv->pending_events, |
| gst_event_new_caps (default_caps)); |
| 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) |
| gst_base_parse_drain (parse); |
| else |
| gst_base_parse_finish_fragment (parse, TRUE); |
| forward_immediate = TRUE; |
| parse->priv->saw_gaps = TRUE; |
| break; |
| } |
| case GST_EVENT_TAG: |
| { |
| 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; |
| |
| gst_base_parse_set_upstream_tags (parse, NULL); |
| parse->priv->tags_changed = TRUE; |
| break; |
| } |
| default: |
| break; |
| } |
| |
| /* Forward non-serialized events and EOS/FLUSH_STOP immediately. |
| * For EOS this is required because no buffer or serialized event |
| * will come after EOS and nothing could trigger another |
| * _finish_frame() call. * |
| * If the subclass handles sending of EOS manually it can return |
| * _DROPPED from ::finish() and all other subclasses should have |
| * decoded/flushed all remaining data before this |
| * |
| * For FLUSH_STOP this is required because it is expected |
| * to be forwarded immediately and no buffers are queued anyway. |
| */ |
| if (event) { |
| if (!GST_EVENT_IS_SERIALIZED (event) || forward_immediate) { |
| ret = gst_pad_push_event (parse->srcpad, event); |
| } else { |
| parse->priv->pending_events = |
| g_list_prepend (parse->priv->pending_events, event); |
| ret = TRUE; |
| } |
| } |
| |
| GST_DEBUG_OBJECT (parse, "event handled"); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_base_parse_sink_query_default (GstBaseParse * parse, GstQuery * query) |
| { |
| GstPad *pad; |
| gboolean res; |
| |
| pad = GST_BASE_PARSE_SINK_PAD (parse); |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_CAPS: |
| { |
| GstBaseParseClass *bclass; |
| |
| bclass = GST_BASE_PARSE_GET_CLASS (parse); |
| |
| if (bclass->get_sink_caps) { |
| GstCaps *caps, *filter; |
| |
| gst_query_parse_caps (query, &filter); |
| caps = bclass->get_sink_caps (parse, filter); |
| GST_LOG_OBJECT (parse, "sink getcaps returning caps %" GST_PTR_FORMAT, |
| caps); |
| gst_query_set_caps_result (query, caps); |
| gst_caps_unref (caps); |
| |
| res = TRUE; |
| } else { |
| GstCaps *caps, *template_caps, *filter; |
| |
| gst_query_parse_caps (query, &filter); |
| template_caps = gst_pad_get_pad_template_caps (pad); |
| if (filter != NULL) { |
| caps = |
| gst_caps_intersect_full (filter, template_caps, |
| GST_CAPS_INTERSECT_FIRST); |
| gst_caps_unref (template_caps); |
| } else { |
| caps = template_caps; |
| } |
| gst_query_set_caps_result (query, caps); |
| gst_caps_unref (caps); |
| |
| res = TRUE; |
| } |
| break; |
| } |
| default: |
| { |
| res = gst_pad_query_default (pad, GST_OBJECT_CAST (parse), query); |
| break; |
| } |
| } |
| |
| return res; |
| } |
| |
| static gboolean |
| gst_base_parse_sink_query (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| GstBaseParseClass *bclass; |
| GstBaseParse *parse; |
| gboolean ret; |
| |
| parse = GST_BASE_PARSE (parent); |
| bclass = GST_BASE_PARSE_GET_CLASS (parse); |
| |
| GST_DEBUG_OBJECT (parse, "%s query", GST_QUERY_TYPE_NAME (query)); |
| |
| if (bclass->sink_query) |
| ret = bclass->sink_query (parse, query); |
| else |
| ret = FALSE; |
| |
| GST_LOG_OBJECT (parse, "%s query result: %d %" GST_PTR_FORMAT, |
| GST_QUERY_TYPE_NAME (query), ret, query); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_base_parse_src_query (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| GstBaseParseClass *bclass; |
| GstBaseParse *parse; |
| gboolean ret; |
| |
| parse = GST_BASE_PARSE (parent); |
| bclass = GST_BASE_PARSE_GET_CLASS (parse); |
| |
| GST_DEBUG_OBJECT (parse, "%s query: %" GST_PTR_FORMAT, |
| GST_QUERY_TYPE_NAME (query), query); |
| |
| if (bclass->src_query) |
| ret = bclass->src_query (parse, query); |
| else |
| ret = FALSE; |
| |
| GST_LOG_OBJECT (parse, "%s query result: %d %" GST_PTR_FORMAT, |
| GST_QUERY_TYPE_NAME (query), ret, query); |
| |
| return ret; |
| } |
| |
| /* gst_base_parse_src_event: |
| * @pad: #GstPad that received the event. |
| * @event: #GstEvent that was received. |
| * |
| * Handler for source pad events. |
| * |
| * Returns: %TRUE if the event was handled. |
| */ |
| static gboolean |
| gst_base_parse_src_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| GstBaseParse *parse; |
| GstBaseParseClass *bclass; |
| gboolean ret = TRUE; |
| |
| parse = GST_BASE_PARSE (parent); |
| bclass = GST_BASE_PARSE_GET_CLASS (parse); |
| |
| GST_DEBUG_OBJECT (parse, "event %d, %s", GST_EVENT_TYPE (event), |
| GST_EVENT_TYPE_NAME (event)); |
| |
| if (bclass->src_event) |
| ret = bclass->src_event (parse, event); |
| else |
| gst_event_unref (event); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_base_parse_is_seekable (GstBaseParse * parse) |
| { |
| /* FIXME: could do more here, e.g. check index or just send data from 0 |
| * in pull mode and let decoder/sink clip */ |
| return parse->priv->syncable; |
| } |
| |
| /* gst_base_parse_src_event_default: |
| * @parse: #GstBaseParse. |
| * @event: #GstEvent that was received. |
| * |
| * Default srcpad event handler. |
| * |
| * Returns: %TRUE if the event was handled and can be dropped. |
| */ |
| static gboolean |
| gst_base_parse_src_event_default (GstBaseParse * parse, GstEvent * event) |
| { |
| gboolean res = FALSE; |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_SEEK: |
| if (gst_base_parse_is_seekable (parse)) |
| res = gst_base_parse_handle_seek (parse, event); |
| break; |
| default: |
| res = gst_pad_event_default (parse->srcpad, GST_OBJECT_CAST (parse), |
| event); |
| break; |
| } |
| return res; |
| } |
| |
| |
| /** |
| * gst_base_parse_convert_default: |
| * @parse: #GstBaseParse. |
| * @src_format: #GstFormat describing the source format. |
| * @src_value: Source value to be converted. |
| * @dest_format: #GstFormat defining the converted format. |
| * @dest_value: Pointer where the conversion result will be put. |
| * |
| * Default implementation of "convert" vmethod in #GstBaseParse class. |
| * |
| * Returns: %TRUE if conversion was successful. |
| */ |
| gboolean |
| gst_base_parse_convert_default (GstBaseParse * parse, |
| GstFormat src_format, |
| gint64 src_value, GstFormat dest_format, gint64 * dest_value) |
| { |
| gboolean ret = FALSE; |
| guint64 bytes, duration; |
| |
| if (G_UNLIKELY (src_format == dest_format)) { |
| *dest_value = src_value; |
| return TRUE; |
| } |
| |
| if (G_UNLIKELY (src_value == -1)) { |
| *dest_value = -1; |
| return TRUE; |
| } |
| |
| if (G_UNLIKELY (src_value == 0)) { |
| *dest_value = 0; |
| return TRUE; |
| } |
| |
| /* need at least some frames */ |
| if (!parse->priv->framecount) |
| goto no_framecount; |
| |
| duration = parse->priv->acc_duration / GST_MSECOND; |
| bytes = parse->priv->bytecount; |
| |
| if (G_UNLIKELY (!duration || !bytes)) |
| goto no_duration_bytes; |
| |
| if (src_format == GST_FORMAT_BYTES) { |
| if (dest_format == GST_FORMAT_TIME) { |
| /* BYTES -> TIME conversion */ |
| GST_DEBUG_OBJECT (parse, "converting bytes -> time"); |
| *dest_value = gst_util_uint64_scale (src_value, duration, bytes); |
| *dest_value *= GST_MSECOND; |
| GST_DEBUG_OBJECT (parse, "conversion result: %" G_GINT64_FORMAT " ms", |
| *dest_value / GST_MSECOND); |
| ret = TRUE; |
| } else { |
| GST_DEBUG_OBJECT (parse, "converting bytes -> other not implemented"); |
| } |
| } else if (src_format == GST_FORMAT_TIME) { |
| if (dest_format == GST_FORMAT_BYTES) { |
| GST_DEBUG_OBJECT (parse, "converting time -> bytes"); |
| *dest_value = gst_util_uint64_scale (src_value / GST_MSECOND, bytes, |
| duration); |
| GST_DEBUG_OBJECT (parse, |
| "time %" G_GINT64_FORMAT " ms in bytes = %" G_GINT64_FORMAT, |
| src_value / GST_MSECOND, *dest_value); |
| ret = TRUE; |
| } else { |
| GST_DEBUG_OBJECT (parse, "converting time -> other not implemented"); |
| } |
| } else if (src_format == GST_FORMAT_DEFAULT) { |
| /* DEFAULT == frame-based */ |
| if (dest_format == GST_FORMAT_TIME) { |
| GST_DEBUG_OBJECT (parse, "converting default -> time"); |
| if (parse->priv->fps_den) { |
| *dest_value = gst_util_uint64_scale (src_value, |
| GST_SECOND * parse->priv->fps_den, parse->priv->fps_num); |
| ret = TRUE; |
| } |
| } else { |
| GST_DEBUG_OBJECT (parse, "converting default -> other not implemented"); |
| } |
| } else { |
| GST_DEBUG_OBJECT (parse, "conversion not implemented"); |
| } |
| return ret; |
| |
| /* ERRORS */ |
| no_framecount: |
| { |
| GST_DEBUG_OBJECT (parse, "no framecount"); |
| return FALSE; |
| } |
| no_duration_bytes: |
| { |
| GST_DEBUG_OBJECT (parse, "no duration %" G_GUINT64_FORMAT ", bytes %" |
| G_GUINT64_FORMAT, duration, bytes); |
| return FALSE; |
| } |
| |
| } |
| |
| static void |
| gst_base_parse_update_duration (GstBaseParse * parse) |
| { |
| gint64 ptot, dest_value; |
| |
| if (!gst_pad_peer_query_duration (parse->sinkpad, GST_FORMAT_BYTES, &ptot)) |
| return; |
| |
| if (!gst_base_parse_convert (parse, GST_FORMAT_BYTES, ptot, |
| GST_FORMAT_TIME, &dest_value)) |
| return; |
| |
| /* 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; |
| } |
| } |
| |
| /* gst_base_parse_update_bitrates: |
| * @parse: #GstBaseParse. |
| * @buffer: Current frame as a #GstBuffer |
| * |
| * Keeps track of the minimum and maximum bitrates, and also maintains a |
| * running average bitrate of the stream so far. |
| */ |
| static void |
| gst_base_parse_update_bitrates (GstBaseParse * parse, GstBaseParseFrame * frame) |
| { |
| guint64 data_len, frame_dur; |
| gint overhead, frame_bitrate; |
| GstBuffer *buffer = frame->buffer; |
| |
| overhead = frame->overhead; |
| if (overhead == -1) |
| return; |
| |
| data_len = gst_buffer_get_size (buffer) - overhead; |
| parse->priv->data_bytecount += data_len; |
| |
| /* 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) { |
| /* 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; |
| |
| } else { |
| /* No way to figure out frame duration (is this even possible?) */ |
| return; |
| } |
| |
| /* override if subclass provided bitrate, e.g. metadata based */ |
| 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) |
| parse->priv->tags_changed = TRUE; |
| } |
| |
| if (frame_dur) |
| frame_bitrate = (8 * data_len * GST_SECOND) / frame_dur; |
| else |
| return; |
| |
| 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) |
| 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; |
| if (parse->priv->post_min_bitrate) |
| parse->priv->tags_changed = TRUE; |
| } |
| |
| if (frame_bitrate > parse->priv->max_bitrate) { |
| parse->priv->max_bitrate = frame_bitrate; |
| if (parse->priv->post_max_bitrate) |
| parse->priv->tags_changed = 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; |
| } |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /* FIXME need better helper data structure that handles these issues |
| * related to ongoing collecting of index entries */ |
| if (parse->priv->index_last_offset + parse->priv->idx_byte_interval >= |
| (gint64) offset) { |
| GST_LOG_OBJECT (parse, |
| "already have entries up to offset 0x%08" G_GINT64_MODIFIER "x", |
| parse->priv->index_last_offset + parse->priv->idx_byte_interval); |
| goto exit; |
| } |
| |
| if (GST_CLOCK_TIME_IS_VALID (parse->priv->index_last_ts) && |
| GST_CLOCK_DIFF (parse->priv->index_last_ts, ts) < |
| parse->priv->idx_interval) { |
| GST_LOG_OBJECT (parse, "entry too close to last time %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (parse->priv->index_last_ts)); |
| goto exit; |
| } |
| |
| /* if last is not really the last one */ |
| if (!parse->priv->index_last_valid) { |
| GstClockTime prev_ts; |
| |
| gst_base_parse_find_offset (parse, ts, TRUE, &prev_ts); |
| if (GST_CLOCK_DIFF (prev_ts, ts) < parse->priv->idx_interval) { |
| GST_LOG_OBJECT (parse, |
| "entry too close to existing entry %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (prev_ts)); |
| parse->priv->index_last_offset = offset; |
| parse->priv->index_last_ts = 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_BASE_PARSE_INDEX_LOCK (parse); |
| gst_index_add_associationv (parse->priv->index, parse->priv->index_id, |
| (key) ? GST_INDEX_ASSOCIATION_FLAG_KEY_UNIT : |
| GST_INDEX_ASSOCIATION_FLAG_DELTA_UNIT, 2, |
| (const GstIndexAssociation *) &associations); |
| GST_BASE_PARSE_INDEX_UNLOCK (parse); |
| |
| if (key) { |
| 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; |
| guint64 idx_byte_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) { |
| GST_DEBUG_OBJECT (parse, "doing duration query to fix up unset stop"); |
| gst_pad_peer_query_duration (parse->sinkpad, GST_FORMAT_BYTES, &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; |
| |
| /* ensure that even for large files (e.g. very long audio files), the index |
| * stays reasonably-size, with some arbitrary limit to the total number of |
| * index entries */ |
| idx_byte_interval = (stop - start) / MAX_INDEX_ENTRIES; |
| GST_DEBUG_OBJECT (parse, |
| "Limiting index entries to %d, indexing byte interval %" |
| G_GUINT64_FORMAT " bytes", MAX_INDEX_ENTRIES, idx_byte_interval); |
| } |
| |
| 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; |
| parse->priv->upstream_size = seekable ? stop : 0; |
| |
| GST_DEBUG_OBJECT (parse, "idx_interval: %ums", idx_interval); |
| parse->priv->idx_interval = idx_interval * GST_MSECOND; |
| parse->priv->idx_byte_interval = idx_byte_interval; |
| } |
| |
| /* some misc checks on upstream */ |
| static void |
| gst_base_parse_check_upstream (GstBaseParse * parse) |
| { |
| gint64 stop; |
| |
| if (gst_pad_peer_query_duration (parse->sinkpad, GST_FORMAT_TIME, &stop)) |
| if (GST_CLOCK_TIME_IS_VALID (stop) && stop) { |
| /* upstream has one, accept it also, and no further updates */ |
| gst_base_parse_set_duration (parse, GST_FORMAT_TIME, stop, 0); |
| parse->priv->upstream_has_duration = TRUE; |
| } |
| |
| GST_DEBUG_OBJECT (parse, "upstream_has_duration: %d", |
| parse->priv->upstream_has_duration); |
| } |
| |
| /* checks src caps to determine if dealing with audio or video */ |
| /* TODO maybe forego automagic stuff and let subclass configure it ? */ |
| static void |
| gst_base_parse_check_media (GstBaseParse * parse) |
| { |
| GstCaps *caps; |
| GstStructure *s; |
| |
| caps = gst_pad_get_current_caps (parse->srcpad); |
| if (G_LIKELY (caps) && (s = gst_caps_get_structure (caps, 0))) { |
| parse->priv->is_video = |
| g_str_has_prefix (gst_structure_get_name (s), "video"); |
| } else { |
| /* historical default */ |
| parse->priv->is_video = FALSE; |
| } |
| if (caps) |
| gst_caps_unref (caps); |
| |
| parse->priv->checked_media = TRUE; |
| GST_DEBUG_OBJECT (parse, "media is video: %d", parse->priv->is_video); |
| } |
| |
| /* takes ownership of frame */ |
| static void |
| gst_base_parse_queue_frame (GstBaseParse * parse, GstBaseParseFrame * frame) |
| { |
| if (!(frame->_private_flags & GST_BASE_PARSE_FRAME_PRIVATE_FLAG_NOALLOC)) { |
| /* frame allocated on the heap, we can just take ownership */ |
| g_queue_push_tail (&parse->priv->queued_frames, frame); |
| GST_TRACE ("queued frame %p", frame); |
| } else { |
| GstBaseParseFrame *copy; |
| |
| /* probably allocated on the stack, must make a proper copy */ |
| copy = gst_base_parse_frame_copy (frame); |
| g_queue_push_tail (&parse->priv->queued_frames, copy); |
| GST_TRACE ("queued frame %p (copy of %p)", copy, frame); |
| gst_base_parse_frame_free (frame); |
| } |
| } |
| |
| /* makes sure that @buf is properly prepared and decorated for passing |
| * to baseclass, and an equally setup frame is returned setup with @buf. |
| * Takes ownership of @buf. */ |
| static GstBaseParseFrame * |
| gst_base_parse_prepare_frame (GstBaseParse * parse, GstBuffer * buffer) |
| { |
| GstBaseParseFrame *frame = NULL; |
| |
| buffer = gst_buffer_make_writable (buffer); |
| |
| GST_LOG_OBJECT (parse, |
| "preparing frame at offset %" G_GUINT64_FORMAT |
| " (%#" G_GINT64_MODIFIER "x) of size %" G_GSIZE_FORMAT, |
| GST_BUFFER_OFFSET (buffer), GST_BUFFER_OFFSET (buffer), |
| gst_buffer_get_size (buffer)); |
| |
| GST_BUFFER_OFFSET (buffer) = parse->priv->offset; |
| |
| gst_base_parse_update_flags (parse); |
| |
| frame = gst_base_parse_frame_new (buffer, 0, 0); |
| gst_buffer_unref (buffer); |
| gst_base_parse_update_frame (parse, frame); |
| |
| /* 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); |
| |
| return frame; |
| } |
| |
| /* Wraps buffer in a frame and dispatches to subclass. |
| * Also manages data skipping and offset handling (including adapter flushing). |
| * Takes ownership of @buffer */ |
| static GstFlowReturn |
| gst_base_parse_handle_buffer (GstBaseParse * parse, GstBuffer * buffer, |
| gint * skip, gint * flushed) |
| { |
| GstBaseParseClass *klass = GST_BASE_PARSE_GET_CLASS (parse); |
| GstBaseParseFrame *frame; |
| GstFlowReturn ret; |
| |
| g_return_val_if_fail (skip != NULL || flushed != NULL, GST_FLOW_ERROR); |
| |
| GST_LOG_OBJECT (parse, |
| "handling buffer of size %" G_GSIZE_FORMAT " with dts %" GST_TIME_FORMAT |
| ", pts %" GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT, |
| gst_buffer_get_size (buffer), GST_TIME_ARGS (GST_BUFFER_DTS (buffer)), |
| GST_TIME_ARGS (GST_BUFFER_PTS (buffer)), |
| GST_TIME_ARGS (GST_BUFFER_DURATION (buffer))); |
| |
| /* track what is being flushed during this single round of frame processing */ |
| parse->priv->flushed = 0; |
| *skip = 0; |
| |
| /* make it easy for _finish_frame to pick up input data */ |
| if (parse->priv->pad_mode == GST_PAD_MODE_PULL) { |
| gst_buffer_ref (buffer); |
| gst_adapter_push (parse->priv->adapter, buffer); |
| } |
| |
| frame = gst_base_parse_prepare_frame (parse, buffer); |
| ret = klass->handle_frame (parse, frame, skip); |
| |
| *flushed = parse->priv->flushed; |
| |
| GST_LOG_OBJECT (parse, "handle_frame skipped %d, flushed %d", |
| *skip, *flushed); |
| |
| /* subclass can only do one of these, or semantics are too unclear */ |
| g_assert (*skip == 0 || *flushed == 0); |
| |
| /* track skipping */ |
| if (*skip > 0) { |
| GstClockTime pts, dts; |
| GstBuffer *outbuf; |
| |
| GST_LOG_OBJECT (parse, "finding sync, skipping %d bytes", *skip); |
| if (parse->segment.rate < 0.0 && !parse->priv->buffers_queued) { |
| /* reverse playback, and no frames found yet, so we are skipping |
| * the leading part of a fragment, which may form the tail of |
| * fragment coming later, hopefully subclass skips efficiently ... */ |
| pts = gst_adapter_prev_pts (parse->priv->adapter, NULL); |
| dts = gst_adapter_prev_dts (parse->priv->adapter, NULL); |
| outbuf = gst_adapter_take_buffer (parse->priv->adapter, *skip); |
| outbuf = gst_buffer_make_writable (outbuf); |
| GST_BUFFER_PTS (outbuf) = pts; |
| GST_BUFFER_DTS (outbuf) = dts; |
| parse->priv->buffers_head = |
| g_slist_prepend (parse->priv->buffers_head, outbuf); |
| outbuf = NULL; |
| } else { |
| /* If we're asked to skip more than is available in the adapter, |
| we need to remember what we need to skip for next iteration */ |
| gsize av = gst_adapter_available (parse->priv->adapter); |
| GST_DEBUG ("Asked to skip %u (%" G_GSIZE_FORMAT " available)", *skip, av); |
| if (av >= *skip) { |
| gst_adapter_flush (parse->priv->adapter, *skip); |
| } else { |
| GST_DEBUG |
| ("This is more than available, flushing %" G_GSIZE_FORMAT |
| ", storing %u to skip", av, (guint) (*skip - av)); |
| parse->priv->skip = *skip - av; |
| gst_adapter_flush (parse->priv->adapter, av); |
| *skip = av; |
| } |
| } |
| if (!parse->priv->discont) |
| parse->priv->sync_offset = parse->priv->offset; |
| parse->priv->offset += *skip; |
| parse->priv->discont = TRUE; |
| /* check for indefinite skipping */ |
| if (ret == GST_FLOW_OK) |
| ret = gst_base_parse_check_sync (parse); |
| } |
| |
| parse->priv->offset += *flushed; |
| |
| if (parse->priv->pad_mode == GST_PAD_MODE_PULL) { |
| gst_adapter_clear (parse->priv->adapter); |
| } |
| |
| gst_base_parse_frame_free (frame); |
| |
| return ret; |
| } |
| |
| /* gst_base_parse_push_pending_events: |
| * @parse: #GstBaseParse |
| * |
| * Pushes the pending events |
| */ |
| static void |
| gst_base_parse_push_pending_events (GstBaseParse * parse) |
| { |
| if (G_UNLIKELY (parse->priv->pending_events)) { |
| GList *r = g_list_reverse (parse->priv->pending_events); |
| GList *l; |
| |
| parse->priv->pending_events = NULL; |
| for (l = r; l != NULL; l = l->next) { |
| gst_pad_push_event (parse->srcpad, GST_EVENT_CAST (l->data)); |
| } |
| g_list_free (r); |
| } |
| } |
| |
| /* gst_base_parse_handle_and_push_frame: |
| * @parse: #GstBaseParse. |
| * @klass: #GstBaseParseClass. |
| * @frame: (transfer full): a #GstBaseParseFrame |
| * |
| * Parses the frame from given buffer and pushes it forward. Also performs |
| * timestamp handling and checks the segment limits. |
| * |
| * This is called with srcpad STREAM_LOCK held. |
| * |
| * Returns: #GstFlowReturn |
| */ |
| static GstFlowReturn |
| gst_base_parse_handle_and_push_frame (GstBaseParse * parse, |
| GstBaseParseFrame * frame) |
| { |
| gint64 offset; |
| GstBuffer *buffer; |
| |
| g_return_val_if_fail (frame != NULL, GST_FLOW_ERROR); |
| |
| buffer = frame->buffer; |
| offset = frame->offset; |
| |
| /* check if subclass/format can provide ts. |
| * If so, that allows and enables extra seek and duration determining options */ |
| if (G_UNLIKELY (parse->priv->first_frame_offset < 0)) { |
| if (GST_BUFFER_PTS_IS_VALID (buffer) && parse->priv->has_timing_info |
| && parse->priv->pad_mode == GST_PAD_MODE_PULL) { |
| parse->priv->first_frame_offset = offset; |
| parse->priv->first_frame_pts = GST_BUFFER_PTS (buffer); |
| parse->priv->first_frame_dts = GST_BUFFER_DTS (buffer); |
| GST_DEBUG_OBJECT (parse, "subclass provided dts %" GST_TIME_FORMAT |
| ", pts %" GST_TIME_FORMAT " for first frame at offset %" |
| G_GINT64_FORMAT, GST_TIME_ARGS (parse->priv->first_frame_dts), |
| GST_TIME_ARGS (parse->priv->first_frame_pts), |
| parse->priv->first_frame_offset); |
| if (!GST_CLOCK_TIME_IS_VALID (parse->priv->duration)) { |
| gint64 off; |
| GstClockTime last_ts = G_MAXINT64; |
| |
| GST_DEBUG_OBJECT (parse, "no duration; trying scan to determine"); |
| gst_base_parse_locate_time (parse, &last_ts, &off); |
| if (GST_CLOCK_TIME_IS_VALID (last_ts)) |
| gst_base_parse_set_duration (parse, GST_FORMAT_TIME, last_ts, 0); |
| } |
| } else { |
| /* disable further checks */ |
| parse->priv->first_frame_offset = 0; |
| } |
| } |
| |
| /* track upstream time if provided, not subclass' internal notion of it */ |
| if (parse->priv->upstream_format == GST_FORMAT_TIME) { |
| GST_BUFFER_PTS (frame->buffer) = GST_CLOCK_TIME_NONE; |
| GST_BUFFER_DTS (frame->buffer) = GST_CLOCK_TIME_NONE; |
| } |
| |
| /* interpolating and no valid pts yet, |
| * start with dts and carry on from there */ |
| if (parse->priv->infer_ts && parse->priv->pts_interpolate |
| && !GST_CLOCK_TIME_IS_VALID (parse->priv->next_pts)) |
| parse->priv->next_pts = parse->priv->next_dts; |
| |
| /* again use default handler to add missing metadata; |
| * we may have new information on frame properties */ |
| gst_base_parse_parse_frame (parse, frame); |
| |
| parse->priv->next_pts = GST_CLOCK_TIME_NONE; |
| if (GST_BUFFER_DTS_IS_VALID (buffer) && GST_BUFFER_DURATION_IS_VALID (buffer)) { |
| parse->priv->next_dts = |
| GST_BUFFER_DTS (buffer) + GST_BUFFER_DURATION (buffer); |
| if (parse->priv->pts_interpolate && GST_BUFFER_PTS_IS_VALID (buffer)) { |
| GstClockTime next_pts = |
| GST_BUFFER_PTS (buffer) + GST_BUFFER_DURATION (buffer); |
| if (next_pts >= parse->priv->next_dts) |
| parse->priv->next_pts = next_pts; |
| } |
| } else { |
| /* we lost track, do not produce bogus time next time around |
| * (probably means parser subclass has given up on parsing as well) */ |
| GST_DEBUG_OBJECT (parse, "no next fallback timestamp"); |
| parse->priv->next_dts = GST_CLOCK_TIME_NONE; |
| } |
| |
| if (parse->priv->upstream_seekable && parse->priv->exact_position && |
| GST_BUFFER_PTS_IS_VALID (buffer)) |
| gst_base_parse_add_index_entry (parse, offset, |
| GST_BUFFER_PTS (buffer), |
| !GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT), FALSE); |
| |
| /* All OK, push queued frames if there are any */ |
| if (G_UNLIKELY (!g_queue_is_empty (&parse->priv->queued_frames))) { |
| GstBaseParseFrame *queued_frame; |
| |
| while ((queued_frame = g_queue_pop_head (&parse->priv->queued_frames))) { |
| gst_base_parse_push_frame (parse, queued_frame); |
| gst_base_parse_frame_free (queued_frame); |
| } |
| } |
| |
| return gst_base_parse_push_frame (parse, frame); |
| } |
| |
| /** |
| * gst_base_parse_push_frame: |
| * @parse: #GstBaseParse. |
| * @frame: (transfer none): a #GstBaseParseFrame |
| * |
| * Pushes the frame's buffer downstream, sends any pending events and |
| * does some timestamp and segment handling. Takes ownership of |
| * frame's buffer, though caller retains ownership of @frame. |
| * |
| * This must be called with sinkpad STREAM_LOCK held. |
| * |
| * Returns: #GstFlowReturn |
| */ |
| GstFlowReturn |
| gst_base_parse_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| GstClockTime last_start = GST_CLOCK_TIME_NONE; |
| GstClockTime last_stop = GST_CLOCK_TIME_NONE; |
| GstBaseParseClass *klass = GST_BASE_PARSE_GET_CLASS (parse); |
| GstBuffer *buffer; |
| gsize size; |
| |
| g_return_val_if_fail (frame != NULL, GST_FLOW_ERROR); |
| g_return_val_if_fail (frame->buffer != NULL, GST_FLOW_ERROR); |
| |
| GST_TRACE_OBJECT (parse, "pushing frame %p", frame); |
| |
| buffer = frame->buffer; |
| |
| GST_LOG_OBJECT (parse, |
| "processing buffer of size %" G_GSIZE_FORMAT " with dts %" GST_TIME_FORMAT |
| ", pts %" GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT, |
| gst_buffer_get_size (buffer), |
| GST_TIME_ARGS (GST_BUFFER_DTS (buffer)), |
| GST_TIME_ARGS (GST_BUFFER_PTS (buffer)), |
| GST_TIME_ARGS (GST_BUFFER_DURATION (buffer))); |
| |
| /* update stats */ |
| parse->priv->bytecount += frame->size; |
| if (G_LIKELY (!(frame->flags & GST_BASE_PARSE_FRAME_FLAG_NO_FRAME))) { |
| parse->priv->framecount++; |
| if (GST_BUFFER_DURATION_IS_VALID (buffer)) { |
| parse->priv->acc_duration += GST_BUFFER_DURATION (buffer); |
| } |
| } |
| /* 0 means disabled */ |
| if (parse->priv->update_interval < 0) |
| parse->priv->update_interval = 50; |
| else if (parse->priv->update_interval > 0 && |
| (parse->priv->framecount % parse->priv->update_interval) == 0) |
| gst_base_parse_update_duration (parse); |
| |
| if (GST_BUFFER_PTS_IS_VALID (buffer)) |
| last_start = last_stop = GST_BUFFER_PTS (buffer); |
| if (last_start != GST_CLOCK_TIME_NONE |
| && GST_BUFFER_DURATION_IS_VALID (buffer)) |
| last_stop = last_start + GST_BUFFER_DURATION (buffer); |
| |
| /* should have caps by now */ |
| if (!gst_pad_has_current_caps (parse->srcpad)) |
| goto no_caps; |
| |
| if (G_UNLIKELY (!parse->priv->checked_media)) { |
| /* have caps; check identity */ |
| 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); |
| |
| /* segment adjustment magic; only if we are running the whole show */ |
| if (!parse->priv->passthrough && parse->segment.rate > 0.0 && |
| (parse->priv->pad_mode == GST_PAD_MODE_PULL || |
| parse->priv->upstream_seekable)) { |
| /* handle gaps */ |
| if (GST_CLOCK_TIME_IS_VALID (parse->segment.position) && |
| GST_CLOCK_TIME_IS_VALID (last_start)) { |
| GstClockTimeDiff diff; |
| |
| /* only send newsegments with increasing start times, |
| * otherwise if these go back and forth downstream (sinks) increase |
| * accumulated time and running_time */ |
| diff = GST_CLOCK_DIFF (parse->segment.position, last_start); |
| if (G_UNLIKELY (diff > 2 * GST_SECOND |
| && last_start > parse->segment.start |
| && (!GST_CLOCK_TIME_IS_VALID (parse->segment.stop) |
| || last_start < parse->segment.stop))) { |
| |
| GST_DEBUG_OBJECT (parse, |
| "Gap of %" G_GINT64_FORMAT " ns detected in stream " "(%" |
| GST_TIME_FORMAT " -> %" GST_TIME_FORMAT "). " |
| "Sending updated SEGMENT events", diff, |
| GST_TIME_ARGS (parse->segment.position), |
| GST_TIME_ARGS (last_start)); |
| |
| /* skip gap FIXME */ |
| gst_pad_push_event (parse->srcpad, |
| gst_event_new_segment (&parse->segment)); |
| |
| parse->segment.position = last_start; |
| } |
| } |
| } |
| |
| /* update bitrates and optionally post corresponding tags |
| * (following newsegment) */ |
| gst_base_parse_update_bitrates (parse, frame); |
| |
| if (klass->pre_push_frame) { |
| ret = klass->pre_push_frame (parse, frame); |
| } else { |
| 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; |
| frame->out_buffer = NULL; |
| gst_buffer_replace (&frame->buffer, NULL); |
| } else { |
| buffer = frame->buffer; |
| frame->buffer = NULL; |
| } |
| |
| /* subclass must play nice */ |
| g_return_val_if_fail (buffer != NULL, GST_FLOW_ERROR); |
| |
| size = gst_buffer_get_size (buffer); |
| |
| parse->priv->seen_keyframe |= parse->priv->is_video && |
| !GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); |
| |
| if (frame->flags & GST_BASE_PARSE_FRAME_FLAG_CLIP) { |
| if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) && |
| GST_CLOCK_TIME_IS_VALID (parse->segment.stop) && |
| GST_BUFFER_TIMESTAMP (buffer) > |
| parse->segment.stop + parse->priv->lead_out_ts) { |
| GST_LOG_OBJECT (parse, "Dropped frame, after segment"); |
| ret = GST_FLOW_EOS; |
| } else if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) && |
| GST_BUFFER_DURATION_IS_VALID (buffer) && |
| GST_CLOCK_TIME_IS_VALID (parse->segment.start) && |
| GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) + |
| parse->priv->lead_in_ts < parse->segment.start) { |
| if (parse->priv->seen_keyframe) { |
| GST_LOG_OBJECT (parse, "Frame before segment, after keyframe"); |
| ret = GST_FLOW_OK; |
| } else { |
| GST_LOG_OBJECT (parse, "Dropped frame, before segment"); |
| ret = GST_BASE_PARSE_FLOW_DROPPED; |
| } |
| } else { |
| ret = GST_FLOW_OK; |
| } |
| } |
| |
| if (ret == GST_BASE_PARSE_FLOW_DROPPED) { |
| GST_LOG_OBJECT (parse, "frame (%" G_GSIZE_FORMAT " bytes) dropped", size); |
| gst_buffer_unref (buffer); |
| ret = GST_FLOW_OK; |
| } else if (ret == GST_FLOW_OK) { |
| if (parse->segment.rate > 0.0) { |
| GST_LOG_OBJECT (parse, "pushing frame (%" G_GSIZE_FORMAT " bytes) now..", |
| size); |
| ret = gst_pad_push (parse->srcpad, buffer); |
| GST_LOG_OBJECT (parse, "frame pushed, flow %s", gst_flow_get_name (ret)); |
| } else if (!parse->priv->disable_passthrough && parse->priv->passthrough) { |
| |
| /* in backwards playback mode, if on passthrough we need to push buffers |
| * directly without accumulating them into the buffers_queued as baseparse |
| * will never check for a DISCONT while on passthrough and those buffers |
| * will never be pushed. |
| * |
| * also, as we are on reverse playback, it might be possible that |
| * passthrough might have just been enabled, so make sure to drain the |
| * buffers_queued list */ |
| if (G_UNLIKELY (parse->priv->buffers_queued != NULL)) { |
| gst_base_parse_finish_fragment (parse, TRUE); |
| ret = gst_base_parse_send_buffers (parse); |
| } |
| |
| if (ret == GST_FLOW_OK) { |
| GST_LOG_OBJECT (parse, |
| "pushing frame (%" G_GSIZE_FORMAT " bytes) now..", size); |
| ret = gst_pad_push (parse->srcpad, buffer); |
| GST_LOG_OBJECT (parse, "frame pushed, flow %s", |
| gst_flow_get_name (ret)); |
| } else { |
| GST_LOG_OBJECT (parse, |
| "frame (%" G_GSIZE_FORMAT " bytes) not pushed: %s", size, |
| gst_flow_get_name (ret)); |
| gst_buffer_unref (buffer); |
| } |
| |
| } else { |
| GST_LOG_OBJECT (parse, "frame (%" G_GSIZE_FORMAT " bytes) queued for now", |
| size); |
| parse->priv->buffers_queued = |
| g_slist_prepend (parse->priv->buffers_queued, buffer); |
| ret = GST_FLOW_OK; |
| } |
| } else { |
| GST_LOG_OBJECT (parse, "frame (%" G_GSIZE_FORMAT " bytes) not pushed: %s", |
| size, gst_flow_get_name (ret)); |
| gst_buffer_unref (buffer); |
| /* if we are not sufficiently in control, let upstream decide on EOS */ |
| if (ret == GST_FLOW_EOS && !parse->priv->disable_passthrough && |
| (parse->priv->passthrough || |
| (parse->priv->pad_mode == GST_PAD_MODE_PUSH && |
| !parse->priv->upstream_seekable))) |
| ret = GST_FLOW_OK; |
| } |
| |
| /* Update current running segment position */ |
| 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; |
| |
| /* ERRORS */ |
| no_caps: |
| { |
| if (GST_PAD_IS_FLUSHING (parse->srcpad)) |
| return GST_FLOW_FLUSHING; |
| |
| GST_ELEMENT_ERROR (parse, STREAM, DECODE, ("No caps set"), (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| } |
| |
| /** |
| * gst_base_parse_finish_frame: |
| * @parse: a #GstBaseParse |
| * @frame: a #GstBaseParseFrame |
| * @size: consumed input data represented by frame |
| * |
| * Collects parsed data and pushes this downstream. |
| * Source pad caps must be set when this is called. |
| * |
| * If @frame's out_buffer is set, that will be used as subsequent frame data. |
| * Otherwise, @size samples will be taken from the input and used for output, |
| * and the output's metadata (timestamps etc) will be taken as (optionally) |
| * set by the subclass on @frame's (input) buffer (which is otherwise |
| * ignored for any but the above purpose/information). |
| * |
| * Note that the latter buffer is invalidated by this call, whereas the |
| * caller retains ownership of @frame. |
| * |
| * Returns: a #GstFlowReturn that should be escalated to caller (of caller) |
| */ |
| GstFlowReturn |
| gst_base_parse_finish_frame (GstBaseParse * parse, GstBaseParseFrame * frame, |
| gint size) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| |
| g_return_val_if_fail (frame != NULL, GST_FLOW_ERROR); |
| g_return_val_if_fail (frame->buffer != NULL, GST_FLOW_ERROR); |
| g_return_val_if_fail (size > 0 || frame->out_buffer, GST_FLOW_ERROR); |
| g_return_val_if_fail (gst_adapter_available (parse->priv->adapter) >= size, |
| GST_FLOW_ERROR); |
| |
| GST_LOG_OBJECT (parse, "finished frame at offset %" G_GUINT64_FORMAT ", " |
| "flushing size %d", frame->offset, size); |
| |
| /* some one-time start-up */ |
| if (G_UNLIKELY (parse->priv->framecount == 0)) { |
| gst_base_parse_check_seekability (parse); |
| gst_base_parse_check_upstream (parse); |
| } |
| |
| parse->priv->flushed += size; |
| |
| if (parse->priv->scanning && frame->buffer) { |
| if (!parse->priv->scanned_frame) { |
| parse->priv->scanned_frame = gst_base_parse_frame_copy (frame); |
| } |
| goto exit; |
| } |
| |
| /* either PUSH or PULL mode arranges for adapter data */ |
| /* ensure output buffer */ |
| if (!frame->out_buffer) { |
| GstBuffer *src, *dest; |
| |
| frame->out_buffer = gst_adapter_take_buffer (parse->priv->adapter, size); |
| dest = frame->out_buffer; |
| src = frame->buffer; |
| GST_BUFFER_PTS (dest) = GST_BUFFER_PTS (src); |
| GST_BUFFER_DTS (dest) = GST_BUFFER_DTS (src); |
| GST_BUFFER_OFFSET (dest) = GST_BUFFER_OFFSET (src); |
| GST_BUFFER_DURATION (dest) = GST_BUFFER_DURATION (src); |
| GST_BUFFER_OFFSET_END (dest) = GST_BUFFER_OFFSET_END (src); |
| GST_MINI_OBJECT_FLAGS (dest) = GST_MINI_OBJECT_FLAGS (src); |
| } else { |
| gst_adapter_flush (parse->priv->adapter, size); |
| } |
| |
| /* use as input for subsequent processing */ |
| gst_buffer_replace (&frame->buffer, frame->out_buffer); |
| gst_buffer_unref (frame->out_buffer); |
| frame->out_buffer = NULL; |
| |
| /* mark input size consumed */ |
| frame->size = size; |
| |
| /* subclass might queue frames/data internally if it needs more |
| * frames to decide on the format, or might request us to queue here. */ |
| if (frame->flags & GST_BASE_PARSE_FRAME_FLAG_DROP) { |
| gst_buffer_replace (&frame->buffer, NULL); |
| goto exit; |
| } else if (frame->flags & GST_BASE_PARSE_FRAME_FLAG_QUEUE) { |
| GstBaseParseFrame *copy; |
| |
| copy = gst_base_parse_frame_copy (frame); |
| copy->flags &= ~GST_BASE_PARSE_FRAME_FLAG_QUEUE; |
| gst_base_parse_queue_frame (parse, copy); |
| goto exit; |
| } |
| |
| ret = gst_base_parse_handle_and_push_frame (parse, frame); |
| |
| exit: |
| return ret; |
| } |
| |
| /* gst_base_parse_drain: |
| * |
| * 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. |
| */ |
| static void |
| gst_base_parse_drain (GstBaseParse * parse) |
| { |
| guint avail; |
| |
| GST_DEBUG_OBJECT (parse, "draining"); |
| parse->priv->drain = TRUE; |
| |
| for (;;) { |
| avail = gst_adapter_available (parse->priv->adapter); |
| if (!avail) |
| break; |
| |
| if (gst_base_parse_chain (parse->sinkpad, GST_OBJECT_CAST (parse), |
| NULL) != GST_FLOW_OK) { |
| break; |
| } |
| |
| /* nothing changed, maybe due to truncated frame; break infinite loop */ |
| if (avail == gst_adapter_available (parse->priv->adapter)) { |
| GST_DEBUG_OBJECT (parse, "no change during draining; flushing"); |
| gst_adapter_clear (parse->priv->adapter); |
| } |
| } |
| |
| parse->priv->drain = FALSE; |
| } |
| |
| /* gst_base_parse_send_buffers |
| * |
| * Sends buffers collected in send_buffers downstream, and ensures that list |
| * is empty at the end (errors or not). |
| */ |
| static GstFlowReturn |
| gst_base_parse_send_buffers (GstBaseParse * parse) |
| { |
| GSList *send = NULL; |
| GstBuffer *buf; |
| GstFlowReturn ret = GST_FLOW_OK; |
| gboolean first = TRUE; |
| |
| send = parse->priv->buffers_send; |
| |
| /* send buffers */ |
| while (send) { |
| buf = GST_BUFFER_CAST (send->data); |
| GST_LOG_OBJECT (parse, "pushing buffer %p, dts %" |
| GST_TIME_FORMAT ", pts %" GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT |
| ", offset %" G_GINT64_FORMAT, buf, |
| GST_TIME_ARGS (GST_BUFFER_DTS (buf)), |
| GST_TIME_ARGS (GST_BUFFER_PTS (buf)), |
| GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), GST_BUFFER_OFFSET (buf)); |
| |
| /* Make sure the first buffer is always DISCONT. If we split |
| * GOPs inside the parser this is otherwise not guaranteed */ |
| if (first) { |
| GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); |
| first = FALSE; |
| } |
| |
| /* iterate output queue an push downstream */ |
| ret = gst_pad_push (parse->srcpad, buf); |
| send = g_slist_delete_link (send, send); |
| |
| /* clear any leftover if error */ |
| if (G_UNLIKELY (ret != GST_FLOW_OK)) { |
| while (send) { |
| buf = GST_BUFFER_CAST (send->data); |
| gst_buffer_unref (buf); |
| send = g_slist_delete_link (send, send); |
| } |
| } |
| } |
| |
| parse->priv->buffers_send = send; |
| |
| return ret; |
| } |
| |
| /* gst_base_parse_start_fragment: |
| * |
| * Prepares for processing a reverse playback (forward) fragment |
| * by (re)setting proper state variables. |
| */ |
| static GstFlowReturn |
| gst_base_parse_start_fragment (GstBaseParse * parse) |
| { |
| GST_LOG_OBJECT (parse, "starting fragment"); |
| |
| /* invalidate so no fall-back timestamping is performed; |
| * ok if taken from subclass or upstream */ |
| parse->priv->next_pts = GST_CLOCK_TIME_NONE; |
| parse->priv->prev_pts = GST_CLOCK_TIME_NONE; |
| parse->priv->next_dts = GST_CLOCK_TIME_NONE; |
| parse->priv->prev_dts = GST_CLOCK_TIME_NONE; |
| /* prevent it hanging around stop all the time */ |
| parse->segment.position = GST_CLOCK_TIME_NONE; |
| /* mark next run */ |
| parse->priv->discont = TRUE; |
| |
| /* head of previous fragment is now pending tail of current fragment */ |
| parse->priv->buffers_pending = parse->priv->buffers_head; |
| parse->priv->buffers_head = NULL; |
| |
| return GST_FLOW_OK; |
| } |
| |
| |
| /* gst_base_parse_finish_fragment: |
| * |
| * Processes a reverse playback (forward) fragment: |
| * - append head of last fragment that was skipped to current fragment data |
| * - drain the resulting current fragment data (i.e. repeated chain) |
| * - add time/duration (if needed) to frames queued by chain |
| * - push queued data |
| */ |
| static GstFlowReturn |
| gst_base_parse_finish_fragment (GstBaseParse * parse, gboolean prev_head) |
| { |
| GstBuffer *buf; |
| GstFlowReturn ret = GST_FLOW_OK; |
| gboolean seen_key = FALSE, seen_delta = FALSE; |
| |
| GST_LOG_OBJECT (parse, "finishing fragment"); |
| |
| /* restore order */ |
| parse->priv->buffers_pending = g_slist_reverse (parse->priv->buffers_pending); |
| while (parse->priv->buffers_pending) { |
| buf = GST_BUFFER_CAST (parse->priv->buffers_pending->data); |
| if (prev_head) { |
| GST_LOG_OBJECT (parse, "adding pending buffer (size %" G_GSIZE_FORMAT ")", |
| gst_buffer_get_size (buf)); |
| gst_adapter_push (parse->priv->adapter, buf); |
| } else { |
| GST_LOG_OBJECT (parse, "discarding head buffer"); |
| gst_buffer_unref (buf); |
| } |
| parse->priv->buffers_pending = |
| g_slist_delete_link (parse->priv->buffers_pending, |
| parse->priv->buffers_pending); |
| } |
| |
| /* chain looks for frames and queues resulting ones (in stead of pushing) */ |
| /* initial skipped data is added to buffers_pending */ |
| gst_base_parse_drain (parse); |
| |
| if (parse->priv->buffers_send) { |
| buf = GST_BUFFER_CAST (parse->priv->buffers_send->data); |
| seen_key |= !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); |
| } |
| |
| /* add metadata (if needed to queued buffers */ |
| GST_LOG_OBJECT (parse, "last timestamp: %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (parse->priv->last_pts)); |
| while (parse->priv->buffers_queued) { |
| buf = GST_BUFFER_CAST (parse->priv->buffers_queued->data); |
| |
| /* no touching if upstream or parsing provided time */ |
| if (GST_BUFFER_PTS_IS_VALID (buf)) { |
| GST_LOG_OBJECT (parse, "buffer has time %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (GST_BUFFER_PTS (buf))); |
| } else if (GST_BUFFER_DURATION_IS_VALID (buf)) { |
| if (GST_CLOCK_TIME_IS_VALID (parse->priv->last_pts)) { |
| if (G_LIKELY (GST_BUFFER_DURATION (buf) <= parse->priv->last_pts)) |
| parse->priv->last_pts -= GST_BUFFER_DURATION (buf); |
| else |
| parse->priv->last_pts = 0; |
| GST_BUFFER_PTS (buf) = parse->priv->last_pts; |
| GST_LOG_OBJECT (parse, "applied time %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (GST_BUFFER_PTS (buf))); |
| } |
| if (GST_CLOCK_TIME_IS_VALID (parse->priv->last_dts)) { |
| if (G_LIKELY (GST_BUFFER_DURATION (buf) <= parse->priv->last_dts)) |
| parse->priv->last_dts -= GST_BUFFER_DURATION (buf); |
| else |
| parse->priv->last_dts = 0; |
| GST_BUFFER_DTS (buf) = parse->priv->last_dts; |
| GST_LOG_OBJECT (parse, "applied dts %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (GST_BUFFER_DTS (buf))); |
| } |
| } else { |
| /* no idea, very bad */ |
| GST_WARNING_OBJECT (parse, "could not determine time for buffer"); |
| } |
| |
| parse->priv->last_pts = GST_BUFFER_PTS (buf); |
| parse->priv->last_dts = GST_BUFFER_DTS (buf); |
| |
| /* reverse order for ascending sending */ |
| /* send downstream at keyframe not preceded by a keyframe |
| * (e.g. that should identify start of collection of IDR nals) */ |
| if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) { |
| if (seen_key) { |
| ret = gst_base_parse_send_buffers (parse); |
| /* if a problem, throw all to sending */ |
| if (ret != GST_FLOW_OK) { |
| parse->priv->buffers_send = |
| g_slist_reverse (parse->priv->buffers_queued); |
| parse->priv->buffers_queued = NULL; |
| break; |
| } |
| seen_key = FALSE; |
| } |
| seen_delta = TRUE; |
| } else { |
| seen_key = TRUE; |
| } |
| |
| parse->priv->buffers_send = |
| g_slist_prepend (parse->priv->buffers_send, buf); |
| parse->priv->buffers_queued = |
| g_slist_delete_link (parse->priv->buffers_queued, |
| parse->priv->buffers_queued); |
| } |
| |
| /* audio may have all marked as keyframe, so arrange to send here. Also |
| * we might have ended the loop above on a keyframe, in which case we |
| * should */ |
| if (!seen_delta || seen_key) |
| ret = gst_base_parse_send_buffers (parse); |
| |
| /* any trailing unused no longer usable (ideally none) */ |
| if (G_UNLIKELY (gst_adapter_available (parse->priv->adapter))) { |
| GST_DEBUG_OBJECT (parse, "discarding %" G_GSIZE_FORMAT " trailing bytes", |
| gst_adapter_available (parse->priv->adapter)); |
| gst_adapter_clear (parse->priv->adapter); |
| } |
| |
| return ret; |
| } |
| |
| /* small helper that checks whether we have been trying to resync too long */ |
| static inline GstFlowReturn |
| gst_base_parse_check_sync (GstBaseParse * parse) |
| { |
| if (G_UNLIKELY (parse->priv->discont && |
| parse->priv->offset - parse->priv->sync_offset > 2 * 1024 * 1024)) { |
| GST_ELEMENT_ERROR (parse, STREAM, DECODE, |
| ("Failed to parse stream"), (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| |
| return GST_FLOW_OK; |
| } |
| |
| static GstFlowReturn |
| gst_base_parse_process_streamheader (GstBaseParse * parse) |
| { |
| GstCaps *caps; |
| GstStructure *str; |
| const GValue *value; |
| GstFlowReturn ret = GST_FLOW_OK; |
| |
| caps = gst_pad_get_current_caps (GST_BASE_PARSE_SINK_PAD (parse)); |
| if (caps == NULL) |
| goto notfound; |
| |
| str = gst_caps_get_structure (caps, 0); |
| value = gst_structure_get_value (str, "streamheader"); |
| if (value == NULL) |
| goto notfound; |
| |
| GST_DEBUG_OBJECT (parse, "Found streamheader field on input caps"); |
| |
| if (GST_VALUE_HOLDS_ARRAY (value)) { |
| gint i; |
| gsize len = gst_value_array_get_size (value); |
| |
| for (i = 0; i < len; i++) { |
| GstBuffer *buffer = |
| gst_value_get_buffer (gst_value_array_get_value (value, i)); |
| ret = |
| gst_base_parse_chain (GST_BASE_PARSE_SINK_PAD (parse), |
| GST_OBJECT_CAST (parse), gst_buffer_ref (buffer)); |
| } |
| |
| } else if (GST_VALUE_HOLDS_BUFFER (value)) { |
| GstBuffer *buffer = gst_value_get_buffer (value); |
| ret = |
| gst_base_parse_chain (GST_BASE_PARSE_SINK_PAD (parse), |
| GST_OBJECT_CAST (parse), gst_buffer_ref (buffer)); |
| } |
| |
| gst_caps_unref (caps); |
| |
| return ret; |
| |
| notfound: |
| { |
| if (caps) { |
| gst_caps_unref (caps); |
| } |
| |
| GST_DEBUG_OBJECT (parse, "No streamheader on caps"); |
| return GST_FLOW_OK; |
| } |
| } |
| |
| static GstFlowReturn |
| gst_base_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) |
| { |
| GstBaseParseClass *bclass; |
| GstBaseParse *parse; |
| GstFlowReturn ret = GST_FLOW_OK; |
| GstFlowReturn old_ret = GST_FLOW_OK; |
| GstBuffer *tmpbuf = NULL; |
| guint fsize = 1; |
| gint skip = -1; |
| guint min_size, av; |
| GstClockTime pts, dts; |
| |
| parse = GST_BASE_PARSE (parent); |
| bclass = GST_BASE_PARSE_GET_CLASS (parse); |
| GST_DEBUG_OBJECT (parent, "chain"); |
| |
| /* early out for speed, if we need to skip */ |
| if (buffer && GST_BUFFER_IS_DISCONT (buffer)) |
| parse->priv->skip = 0; |
| if (parse->priv->skip > 0) { |
| gsize bsize = gst_buffer_get_size (buffer); |
| GST_DEBUG ("Got %" G_GSIZE_FORMAT " buffer, need to skip %u", bsize, |
| parse->priv->skip); |
| if (parse->priv->skip >= bsize) { |
| parse->priv->skip -= bsize; |
| GST_DEBUG ("All the buffer is skipped"); |
| parse->priv->offset += bsize; |
| parse->priv->sync_offset = parse->priv->offset; |
| return GST_FLOW_OK; |
| } |
| buffer = gst_buffer_make_writable (buffer); |
| gst_buffer_resize (buffer, parse->priv->skip, bsize - parse->priv->skip); |
| parse->priv->offset += parse->priv->skip; |
| GST_DEBUG ("Done skipping, we have %u left on this buffer", |
| (unsigned) (bsize - parse->priv->skip)); |
| parse->priv->skip = 0; |
| parse->priv->discont = TRUE; |
| } |
| |
| if (G_UNLIKELY (parse->priv->first_buffer)) { |
| parse->priv->first_buffer = FALSE; |
| if (!GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_HEADER)) { |
| /* this stream has no header buffers, check if we just prepend the |
| * streamheader from caps to the stream */ |
| GST_DEBUG_OBJECT (parse, "Looking for streamheader field on caps to " |
| "prepend to the stream"); |
| gst_base_parse_process_streamheader (parse); |
| } else { |
| GST_DEBUG_OBJECT (parse, "Stream has header buffers, not prepending " |
| "streamheader from caps"); |
| } |
| } |
| |
| if (parse->priv->detecting) { |
| GstBuffer *detect_buf; |
| |
| if (parse->priv->detect_buffers_size == 0) { |
| detect_buf = gst_buffer_ref (buffer); |
| } else { |
| GList *l; |
| guint offset = 0; |
| |
| detect_buf = gst_buffer_new (); |
| |
| for (l = parse->priv->detect_buffers; l; l = l->next) { |
| gsize tmpsize = gst_buffer_get_size (l->data); |
| |
| gst_buffer_copy_into (detect_buf, GST_BUFFER_CAST (l->data), |
| GST_BUFFER_COPY_MEMORY, offset, tmpsize); |
| offset += tmpsize; |
| } |
| if (buffer) |
| gst_buffer_copy_into (detect_buf, buffer, GST_BUFFER_COPY_MEMORY, |
| offset, gst_buffer_get_size (buffer)); |
| } |
| |
| ret = bclass->detect (parse, detect_buf); |
| gst_buffer_unref (detect_buf); |
| |
| if (ret == GST_FLOW_OK) { |
| GList *l; |
| |
| /* Detected something */ |
| parse->priv->detecting = FALSE; |
| |
| for (l = parse->priv->detect_buffers; l; l = l->next) { |
| if (ret == GST_FLOW_OK && !parse->priv->flushing) |
| ret = |
| gst_base_parse_chain (GST_BASE_PARSE_SINK_PAD (parse), |
| parent, GST_BUFFER_CAST (l->data)); |
| else |
| gst_buffer_unref (GST_BUFFER_CAST (l->data)); |
| } |
| g_list_free (parse->priv->detect_buffers); |
| parse->priv->detect_buffers = NULL; |
| parse->priv->detect_buffers_size = 0; |
| |
| if (ret != GST_FLOW_OK) { |
| return ret; |
| } |
| |
| /* Handle the current buffer */ |
| } else if (ret == GST_FLOW_NOT_NEGOTIATED) { |
| /* Still detecting, append buffer or error out if draining */ |
| |
| if (parse->priv->drain) { |
| GST_DEBUG_OBJECT (parse, "Draining but did not detect format yet"); |
| return GST_FLOW_ERROR; |
| } else if (parse->priv->flushing) { |
| g_list_foreach (parse->priv->detect_buffers, (GFunc) gst_buffer_unref, |
| NULL); |
| g_list_free (parse->priv->detect_buffers); |
| parse->priv->detect_buffers = NULL; |
| parse->priv->detect_buffers_size = 0; |
| } else { |
| parse->priv->detect_buffers = |
| g_list_append (parse->priv->detect_buffers, buffer); |
| parse->priv->detect_buffers_size += gst_buffer_get_size (buffer); |
| return GST_FLOW_OK; |
| } |
| } else { |
| /* Something went wrong, subclass responsible for error reporting */ |
| return ret; |
| } |
| |
| /* And now handle the current buffer if detection worked */ |
| } |
| |
| if (G_LIKELY (buffer)) { |
| GST_LOG_OBJECT (parse, |
| "buffer size: %" G_GSIZE_FORMAT ", offset = %" G_GINT64_FORMAT |
| ", dts %" GST_TIME_FORMAT ", pts %" GST_TIME_FORMAT, |
| gst_buffer_get_size (buffer), GST_BUFFER_OFFSET (buffer), |
| GST_TIME_ARGS (GST_BUFFER_DTS (buffer)), |
| GST_TIME_ARGS (GST_BUFFER_PTS (buffer))); |
| |
| if (G_UNLIKELY (!parse->priv->disable_passthrough |
| && parse->priv->passthrough)) { |
| GstBaseParseFrame frame; |
| |
| gst_base_parse_frame_init (&frame); |
| frame.buffer = gst_buffer_make_writable (buffer); |
| ret = gst_base_parse_push_frame (parse, &frame); |
| gst_base_parse_frame_free (&frame); |
| return ret; |
| } |
| if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT))) { |
| /* upstream feeding us in reverse playback; |
| * finish previous fragment and start new upon DISCONT */ |
| if (parse->segment.rate < 0.0) { |
| GST_DEBUG_OBJECT (parse, "buffer starts new reverse playback fragment"); |
| ret = gst_base_parse_finish_fragment (parse, TRUE); |
| gst_base_parse_start_fragment (parse); |
| } else { |
| /* discont in the stream, drain and mark discont for next output */ |
| gst_base_parse_drain (parse); |
| parse->priv->discont = TRUE; |
| } |
| } |
| gst_adapter_push (parse->priv->adapter, buffer); |
| } |
| |
| /* Parse and push as many frames as possible */ |
| /* Stop either when adapter is empty or we are flushing */ |
| while (!parse->priv->flushing) { |
| gint flush = 0; |
| |
| /* note: if subclass indicates MAX fsize, |
| * this will not likely be available anyway ... */ |
| min_size = MAX (parse->priv->min_frame_size, fsize); |
| av = gst_adapter_available (parse->priv->adapter); |
| |
| if (G_UNLIKELY (parse->priv->drain)) { |
| min_size = av; |
| GST_DEBUG_OBJECT (parse, "draining, data left: %d", min_size); |
| if (G_UNLIKELY (!min_size)) { |
| goto done; |
| } |
| } |
| |
| /* Collect at least min_frame_size bytes */ |
| if (av < min_size) { |
| GST_DEBUG_OBJECT (parse, "not enough data available (only %d bytes)", av); |
| goto done; |
| } |
| |
| /* move along with upstream timestamp (if any), |
| * 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)) |
| parse->priv->prev_pts = parse->priv->next_pts = pts; |
| |
| if (GST_CLOCK_TIME_IS_VALID (dts) && (parse->priv->prev_dts != dts)) |
| parse->priv->prev_dts = parse->priv->next_dts = dts; |
| |
| /* we can mess with, erm interpolate, timestamps, |
| * and incoming stuff has PTS but no DTS seen so far, |
| * then pick up DTS from PTS and hope for the best ... */ |
| 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; |
| |
| /* always pass all available data */ |
| 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; |
| |
| if (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED) { |
| goto done; |
| } |
| if (skip == 0 && flush == 0) { |
| GST_LOG_OBJECT (parse, "nothing skipped and no frames finished, " |
| "breaking to get more data"); |
| /* ignore this return as it produced no data */ |
| ret = old_ret; |
| goto done; |
| } |
| old_ret = ret; |
| } |
| |
| done: |
| GST_LOG_OBJECT (parse, "chain leaving"); |
| return ret; |
| } |
| |
| /* pull @size bytes at current offset, |
| * i.e. at least try to and possibly return a shorter buffer if near the end */ |
| static GstFlowReturn |
| gst_base_parse_pull_range (GstBaseParse * parse, guint size, |
| GstBuffer ** buffer) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| |
| g_return_val_if_fail (buffer != NULL, GST_FLOW_ERROR); |
| |
| /* Caching here actually makes much less difference than one would expect. |
| * We do it mainly to avoid pulling buffers of 1 byte all the time */ |
| if (parse->priv->cache) { |
| gint64 cache_offset = GST_BUFFER_OFFSET (parse->priv->cache); |
| gint cache_size = gst_buffer_get_size (parse->priv->cache); |
| |
| if (cache_offset <= parse->priv->offset && |
| (parse->priv->offset + size) <= (cache_offset + cache_size)) { |
| *buffer = gst_buffer_copy_region (parse->priv->cache, GST_BUFFER_COPY_ALL, |
| parse->priv->offset - cache_offset, size); |
| GST_BUFFER_OFFSET (*buffer) = parse->priv->offset; |
| return GST_FLOW_OK; |
| } |
| /* not enough data in the cache, free cache and get a new one */ |
| gst_buffer_unref (parse->priv->cache); |
| parse->priv->cache = NULL; |
| } |
| |
| /* refill the cache */ |
| ret = |
| gst_pad_pull_range (parse->sinkpad, parse->priv->offset, MAX (size, |
| 64 * 1024), &parse->priv->cache); |
| if (ret != GST_FLOW_OK) { |
| parse->priv->cache = NULL; |
| return ret; |
| } |
| |
| if (gst_buffer_get_size (parse->priv->cache) >= size) { |
| *buffer = |
| gst_buffer_copy_region (parse->priv->cache, GST_BUFFER_COPY_ALL, 0, |
| size); |
| GST_BUFFER_OFFSET (*buffer) = parse->priv->offset; |
| return GST_FLOW_OK; |
| } |
| |
| /* Not possible to get enough data, try a last time with |
| * requesting exactly the size we need */ |
| gst_buffer_unref (parse->priv->cache); |
| parse->priv->cache = NULL; |
| |
| ret = gst_pad_pull_range (parse->sinkpad, parse->priv->offset, size, |
| &parse->priv->cache); |
| |
| if (ret != GST_FLOW_OK) { |
| GST_DEBUG_OBJECT (parse, "pull_range returned %d", ret); |
| *buffer = NULL; |
| return ret; |
| } |
| |
| if (gst_buffer_get_size (parse->priv->cache) < size) { |
| GST_DEBUG_OBJECT (parse, "Returning short buffer at offset %" |
| G_GUINT64_FORMAT ": wanted %u bytes, got %" G_GSIZE_FORMAT " bytes", |
| parse->priv->offset, size, gst_buffer_get_size (parse->priv->cache)); |
| |
| *buffer = parse->priv->cache; |
| parse->priv->cache = NULL; |
| |
| return GST_FLOW_OK; |
| } |
| |
| *buffer = |
| gst_buffer_copy_region (parse->priv->cache, GST_BUFFER_COPY_ALL, 0, size); |
| GST_BUFFER_OFFSET (*buffer) = parse->priv->offset; |
| |
| return GST_FLOW_OK; |
| } |
| |
| static GstFlowReturn |
| gst_base_parse_handle_previous_fragment (GstBaseParse * parse) |
| { |
| gint64 offset = 0; |
| GstClockTime ts = 0; |
| GstBuffer *buffer; |
| GstFlowReturn ret; |
| |
| GST_DEBUG_OBJECT (parse, "fragment ended; last_ts = %" GST_TIME_FORMAT |
| ", last_offset = %" G_GINT64_FORMAT, |
| GST_TIME_ARGS (parse->priv->last_pts), parse->priv->last_offset); |
| |
| if (!parse->priv->last_offset |
| || parse->priv->last_pts <= parse->segment.start) { |
| GST_DEBUG_OBJECT (parse, "past start of segment %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (parse->segment.start)); |
| ret = GST_FLOW_EOS; |
| goto exit; |
| } |
| |
| /* last fragment started at last_offset / last_ts; |
| * seek back 10s capped at 1MB */ |
| if (parse->priv->last_pts >= 10 * GST_SECOND) |
| ts = parse->priv->last_pts - 10 * GST_SECOND; |
| /* if we are exact now, we will be more so going backwards */ |
| if (parse->priv->exact_position) { |
| offset = gst_base_parse_find_offset (parse, ts, TRUE, NULL); |
| } else { |
| if (!gst_base_parse_convert (parse, GST_FORMAT_TIME, ts, |
| GST_FORMAT_BYTES, &offset)) { |
| GST_DEBUG_OBJECT (parse, "conversion failed, only BYTE based"); |
| } |
| } |
| offset = CLAMP (offset, parse->priv->last_offset - 1024 * 1024, |
| parse->priv->last_offset - 1024); |
| offset = MAX (0, offset); |
| |
| GST_DEBUG_OBJECT (parse, "next fragment from offset %" G_GINT64_FORMAT, |
| offset); |
| parse->priv->offset = offset; |
| |
| ret = gst_base_parse_pull_range (parse, parse->priv->last_offset - offset, |
| &buffer); |
| if (ret != GST_FLOW_OK) |
| goto exit; |
| |
| /* offset will increase again as fragment is processed/parsed */ |
| parse->priv->last_offset = offset; |
| |
| gst_base_parse_start_fragment (parse); |
| gst_adapter_push (parse->priv->adapter, buffer); |
| ret = gst_base_parse_finish_fragment (parse, TRUE); |
| if (ret != GST_FLOW_OK) |
| goto exit; |
| |
| /* force previous fragment */ |
| parse->priv->offset = -1; |
| |
| exit: |
| return ret; |
| } |
| |
| /* PULL mode: |
| * pull and scan for next frame starting from current offset |
| * ajusts sync, drain and offset going along */ |
| static GstFlowReturn |
| gst_base_parse_scan_frame (GstBaseParse * parse, GstBaseParseClass * klass) |
| { |
| GstBuffer *buffer; |
| GstFlowReturn ret = GST_FLOW_OK; |
| guint fsize, min_size; |
| gint flushed = 0; |
| gint skip = 0; |
| |
| GST_LOG_OBJECT (parse, "scanning for frame at offset %" G_GUINT64_FORMAT |
| " (%#" G_GINT64_MODIFIER "x)", parse->priv->offset, parse->priv->offset); |
| |
| /* let's make this efficient for all subclass once and for all; |
| * maybe it does not need this much, but in the latter case, we know we are |
| * in pull mode here and might as well try to read and supply more anyway |
| * (so does the buffer caching mechanism) */ |
| fsize = 64 * 1024; |
| |
| while (TRUE) { |
| min_size = MAX (parse->priv->min_frame_size, fsize); |
| |
| GST_LOG_OBJECT (parse, "reading buffer size %u", min_size); |
| |
| ret = gst_base_parse_pull_range (parse, min_size, &buffer); |
| if (ret != GST_FLOW_OK) |
| goto done; |
| |
| /* if we got a short read, inform subclass we are draining leftover |
| * and no more is to be expected */ |
| if (gst_buffer_get_size (buffer) < min_size) { |
| GST_LOG_OBJECT (parse, "... but did not get that; marked draining"); |
| parse->priv->drain = TRUE; |
| } |
| |
| if (parse->priv->detecting) { |
| ret = klass->detect (parse, buffer); |
| if (ret == GST_FLOW_NOT_NEGOTIATED) { |
| /* If draining we error out, otherwise request a buffer |
| * with 64kb more */ |
| if (parse->priv->drain) { |
| gst_buffer_unref (buffer); |
| GST_ERROR_OBJECT (parse, "Failed to detect format but draining"); |
| return GST_FLOW_ERROR; |
| } else { |
| fsize += 64 * 1024; |
| gst_buffer_unref (buffer); |
| continue; |
| } |
| } else if (ret != GST_FLOW_OK) { |
| gst_buffer_unref (buffer); |
| GST_ERROR_OBJECT (parse, "detect() returned %s", |
| gst_flow_get_name (ret)); |
| return ret; |
| } |
| |
| /* Else handle this buffer normally */ |
| } |
| |
| ret = gst_base_parse_handle_buffer (parse, buffer, &skip, &flushed); |
| if (ret != GST_FLOW_OK) |
| break; |
| |
| /* If a large amount of data was requested to be skipped, _handle_buffer |
| might have set the priv->skip flag to an extra amount on top of skip. |
| In pull mode, we can just pull from the new offset directly. */ |
| parse->priv->offset += parse->priv->skip; |
| parse->priv->skip = 0; |
| |
| /* something flushed means something happened, |
| * and we should bail out of this loop so as not to occupy |
| * the task thread indefinitely */ |
| if (flushed) { |
| GST_LOG_OBJECT (parse, "frame finished, breaking loop"); |
| break; |
| } |
| /* nothing flushed, no skip and draining, so nothing left to do */ |
| if (!skip && parse->priv->drain) { |
| GST_LOG_OBJECT (parse, "no activity or result when draining; " |
| "breaking loop and marking EOS"); |
| ret = GST_FLOW_EOS; |
| break; |
| } |
| /* otherwise, get some more data |
| * note that is checked this does not happen indefinitely */ |
| if (!skip) { |
| GST_LOG_OBJECT (parse, "getting some more data"); |
| fsize += 64 * 1024; |
| } |
| parse->priv->drain = FALSE; |
| } |
| |
| done: |
| return ret; |
| } |
| |
| /* Loop that is used in pull mode to retrieve data from upstream */ |
| static void |
| gst_base_parse_loop (GstPad * pad) |
| { |
| GstBaseParse *parse; |
| GstBaseParseClass *klass; |
| GstFlowReturn ret = GST_FLOW_OK; |
| |
| parse = GST_BASE_PARSE (gst_pad_get_parent (pad)); |
| klass = GST_BASE_PARSE_GET_CLASS (parse); |
| |
| GST_LOG_OBJECT (parse, "Entering parse loop"); |
| |
| if (G_UNLIKELY (parse->priv->push_stream_start)) { |
| gchar *stream_id; |
| GstEvent *event; |
| |
| stream_id = |
| gst_pad_create_stream_id (parse->srcpad, GST_ELEMENT_CAST (parse), |
| NULL); |
| |
| event = gst_event_new_stream_start (stream_id); |
| gst_event_set_group_id (event, gst_util_group_id_next ()); |
| |
| GST_DEBUG_OBJECT (parse, "Pushing STREAM_START"); |
| gst_pad_push_event (parse->srcpad, event); |
| parse->priv->push_stream_start = FALSE; |
| g_free (stream_id); |
| } |
| |
| /* reverse playback: |
| * first fragment (closest to stop time) is handled normally below, |
| * then we pull in fragments going backwards */ |
| if (parse->segment.rate < 0.0) { |
| /* check if we jumped back to a previous fragment, |
| * which is a post-first fragment */ |
| if (parse->priv->offset < 0) { |
| ret = gst_base_parse_handle_previous_fragment (parse); |
| goto done; |
| } |
| } |
| |
| ret = gst_base_parse_scan_frame (parse, klass); |
| |
| /* eat expected eos signalling past segment in reverse playback */ |
| if (parse->segment.rate < 0.0 && ret == GST_FLOW_EOS && |
| parse->segment.position >= parse->segment.stop) { |
| GST_DEBUG_OBJECT (parse, "downstream has reached end of segment"); |
| /* push what was accumulated during loop run */ |
| gst_base_parse_finish_fragment (parse, FALSE); |
| /* force previous fragment */ |
| parse->priv->offset = -1; |
| goto eos; |
| } |
| |
| if (ret != GST_FLOW_OK) |
| goto done; |
| |
| done: |
| if (ret == GST_FLOW_EOS) |
| goto eos; |
| else if (ret != GST_FLOW_OK) |
| goto pause; |
| |
| gst_object_unref (parse); |
| return; |
| |
| /* ERRORS */ |
| eos: |
| { |
| ret = GST_FLOW_EOS; |
| GST_DEBUG_OBJECT (parse, "eos"); |
| /* fall-through */ |
| } |
| pause: |
| { |
| gboolean push_eos = FALSE; |
| |
| GST_DEBUG_OBJECT (parse, "pausing task, reason %s", |
| gst_flow_get_name (ret)); |
| gst_pad_pause_task (parse->sinkpad); |
| |
| if (ret == GST_FLOW_EOS) { |
| /* handle end-of-stream/segment */ |
| if (parse->segment.flags & GST_SEGMENT_FLAG_SEGMENT) { |
| gint64 stop; |
| |
| if ((stop = parse->segment.stop) == -1) |
| stop = parse->segment.duration; |
| |
| GST_DEBUG_OBJECT (parse, "sending segment_done"); |
| |
| gst_element_post_message |
| (GST_ELEMENT_CAST (parse), |
| gst_message_new_segment_done (GST_OBJECT_CAST (parse), |
| GST_FORMAT_TIME, stop)); |
| gst_pad_push_event (parse->srcpad, |
| gst_event_new_segment_done (GST_FORMAT_TIME, stop)); |
| } else { |
| /* If we STILL have zero frames processed, fire an error */ |
| if (parse->priv->framecount == 0) { |
| GST_ELEMENT_ERROR (parse, STREAM, WRONG_TYPE, |
| ("No valid frames found before end of stream"), (NULL)); |
| } |
| push_eos = TRUE; |
| } |
| } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) { |
| /* 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))); |
| 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); |
| |
| gst_pad_push_event (parse->srcpad, gst_event_new_eos ()); |
| } |
| gst_object_unref (parse); |
| } |
| } |
| |
| static gboolean |
| gst_base_parse_sink_activate (GstPad * sinkpad, GstObject * parent) |
| { |
| GstSchedulingFlags sched_flags; |
| GstBaseParse *parse; |
| GstQuery *query; |
| gboolean pull_mode; |
| |
| parse = GST_BASE_PARSE (parent); |
| |
| GST_DEBUG_OBJECT (parse, "sink activate"); |
| |
| query = gst_query_new_scheduling (); |
| if (!gst_pad_peer_query (sinkpad, query)) { |
| gst_query_unref (query); |
| goto baseparse_push; |
| } |
| |
| gst_query_parse_scheduling (query, &sched_flags, NULL, NULL, NULL); |
| |
| pull_mode = gst_query_has_scheduling_mode (query, GST_PAD_MODE_PULL) |
| && ((sched_flags & GST_SCHEDULING_FLAG_SEEKABLE) != 0); |
| |
| gst_query_unref (query); |
| |
| if (!pull_mode) |
| goto baseparse_push; |
| |
| GST_DEBUG_OBJECT (parse, "trying to activate in pull mode"); |
| if (!gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE)) |
| goto baseparse_push; |
| |
| parse->priv->push_stream_start = TRUE; |
| |
| return gst_pad_start_task (sinkpad, (GstTaskFunction) gst_base_parse_loop, |
| sinkpad, NULL); |
| /* fallback */ |
| baseparse_push: |
| { |
| GST_DEBUG_OBJECT (parse, "trying to activate in push mode"); |
| return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE); |
| } |
| } |
| |
| static gboolean |
| gst_base_parse_activate (GstBaseParse * parse, gboolean active) |
| { |
| GstBaseParseClass *klass; |
| gboolean result = TRUE; |
| |
| GST_DEBUG_OBJECT (parse, "activate %d", active); |
| |
| klass = GST_BASE_PARSE_GET_CLASS (parse); |
| |
| if (active) { |
| if (parse->priv->pad_mode == GST_PAD_MODE_NONE && klass->start) |
| result = klass->start (parse); |
| |
| /* If the subclass implements ::detect we want to |
| * call it for the first buffers now */ |
| parse->priv->detecting = (klass->detect != NULL); |
| } else { |
| /* We must make sure streaming has finished before resetting things |
| * and calling the ::stop vfunc */ |
| GST_PAD_STREAM_LOCK (parse->sinkpad); |
| GST_PAD_STREAM_UNLOCK (parse->sinkpad); |
| |
| if (parse->priv->pad_mode != GST_PAD_MODE_NONE && klass->stop) |
| result = klass->stop (parse); |
| |
| parse->priv->pad_mode = GST_PAD_MODE_NONE; |
| } |
| GST_DEBUG_OBJECT (parse, "activate return: %d", result); |
| return result; |
| } |
| |
| static gboolean |
| gst_base_parse_sink_activate_mode (GstPad * pad, GstObject * parent, |
| GstPadMode mode, gboolean active) |
| { |
| gboolean result; |
| GstBaseParse *parse; |
| |
| parse = GST_BASE_PARSE (parent); |
| |
| GST_DEBUG_OBJECT (parse, "sink %sactivate in %s mode", |
| (active) ? "" : "de", gst_pad_mode_get_name (mode)); |
| |
| if (!gst_base_parse_activate (parse, active)) |
| goto activate_failed; |
| |
| switch (mode) { |
| case GST_PAD_MODE_PULL: |
| if (active) { |
| parse->priv->pending_events = |
| g_list_prepend (parse->priv->pending_events, |
| gst_event_new_segment (&parse->segment)); |
| result = TRUE; |
| } else { |
| result = gst_pad_stop_task (pad); |
| } |
| break; |
| default: |
| result = TRUE; |
| break; |
| } |
| if (result) |
| parse->priv->pad_mode = active ? mode : GST_PAD_MODE_NONE; |
| |
| GST_DEBUG_OBJECT (parse, "sink activate return: %d", result); |
| |
| return result; |
| |
| /* ERRORS */ |
| activate_failed: |
| { |
| GST_DEBUG_OBJECT (parse, "activate failed"); |
| return FALSE; |
| } |
| } |
| |
| /** |
| * gst_base_parse_set_duration: |
| * @parse: #GstBaseParse. |
| * @fmt: #GstFormat. |
| * @duration: duration value. |
| * @interval: how often to update the duration estimate based on bitrate, or 0. |
| * |
| * Sets the duration of the currently playing media. Subclass can use this |
| * when it is able to determine duration and/or notices a change in the media |
| * duration. Alternatively, if @interval is non-zero (default), then stream |
| * duration is determined based on estimated bitrate, and updated every @interval |
| * frames. |
| */ |
| void |
| gst_base_parse_set_duration (GstBaseParse * parse, |
| GstFormat fmt, gint64 duration, gint interval) |
| { |
| g_return_if_fail (parse != NULL); |
| |
| if (parse->priv->upstream_has_duration) { |
| GST_DEBUG_OBJECT (parse, "using upstream duration; discarding update"); |
| goto exit; |
| } |
| |
| if (duration != parse->priv->duration) { |
| GstMessage *m; |
| |
| m = gst_message_new_duration_changed (GST_OBJECT (parse)); |
| gst_element_post_message (GST_ELEMENT (parse), m); |
| |
| /* TODO: what about duration tag? */ |
| } |
| parse->priv->duration = duration; |
| parse->priv->duration_fmt = fmt; |
| GST_DEBUG_OBJECT (parse, "set duration: %" G_GINT64_FORMAT, duration); |
| if (fmt == GST_FORMAT_TIME && GST_CLOCK_TIME_IS_VALID (duration)) { |
| if (interval != 0) { |
| GST_DEBUG_OBJECT (parse, "valid duration provided, disabling estimate"); |
| interval = 0; |
| } |
| } |
| GST_DEBUG_OBJECT (parse, "set update interval: %d", interval); |
| parse->priv->update_interval = interval; |
| exit: |
| return; |
| } |
| |
| /** |
| * gst_base_parse_set_average_bitrate: |
| * @parse: #GstBaseParse. |
| * @bitrate: average bitrate in bits/second |
| * |
| * Optionally sets the average bitrate detected in media (if non-zero), |
| * e.g. based on metadata, as it will be posted to the application. |
| * |
| * By default, announced average bitrate is estimated. The average bitrate |
| * is used to estimate the total duration of the stream and to estimate |
| * a seek position, if there's no index and the format is syncable |
| * (see gst_base_parse_set_syncable()). |
| */ |
| void |
| gst_base_parse_set_average_bitrate (GstBaseParse * parse, guint bitrate) |
| { |
| parse->priv->bitrate = bitrate; |
| GST_DEBUG_OBJECT (parse, "bitrate %u", bitrate); |
| } |
| |
| /** |
| * gst_base_parse_set_min_frame_size: |
| * @parse: #GstBaseParse. |
| * @min_size: Minimum size of the data that this base class should give to |
| * subclass. |
| * |
| * Subclass can use this function to tell the base class that it needs to |
| * give at least #min_size buffers. |
| */ |
| void |
| gst_base_parse_set_min_frame_size (GstBaseParse * parse, guint min_size) |
| { |
| g_return_if_fail (parse != NULL); |
| |
| parse->priv->min_frame_size = min_size; |
| GST_LOG_OBJECT (parse, "set frame_min_size: %d", min_size); |
| } |
| |
| /** |
| * gst_base_parse_set_frame_rate: |
| * @parse: the #GstBaseParse to set |
| * @fps_num: frames per second (numerator). |
| * @fps_den: frames per second (denominator). |
| * @lead_in: frames needed before a segment for subsequent decode |
| * @lead_out: frames needed after a segment |
| * |
| * If frames per second is configured, parser can take care of buffer duration |
| * and timestamping. When performing segment clipping, or seeking to a specific |
| * location, a corresponding decoder might need an initial @lead_in and a |
| * following @lead_out number of frames to ensure the desired segment is |
| * entirely filled upon decoding. |
| */ |
| void |
| gst_base_parse_set_frame_rate (GstBaseParse * parse, guint fps_num, |
| guint fps_den, guint lead_in, guint lead_out) |
| { |
| g_return_if_fail (parse != NULL); |
| |
| parse->priv->fps_num = fps_num; |
| parse->priv->fps_den = fps_den; |
| if (!fps_num || !fps_den) { |
| GST_DEBUG_OBJECT (parse, "invalid fps (%d/%d), ignoring parameters", |
| fps_num, fps_den); |
| fps_num = fps_den = 0; |
| parse->priv->frame_duration = GST_CLOCK_TIME_NONE; |
| parse->priv->lead_in = parse->priv->lead_out = 0; |
| parse->priv->lead_in_ts = parse->priv->lead_out_ts = 0; |
| } else { |
| parse->priv->frame_duration = |
| gst_util_uint64_scale (GST_SECOND, fps_den, fps_num); |
| parse->priv->lead_in = lead_in; |
| parse->priv->lead_out = lead_out; |
| parse->priv->lead_in_ts = |
| gst_util_uint64_scale (GST_SECOND, fps_den * lead_in, fps_num); |
| parse->priv->lead_out_ts = |
| 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); |
| GST_LOG_OBJECT (parse, "estimated update interval to %d frames", |
| parse->priv->update_interval); |
| } |
| } |
| GST_LOG_OBJECT (parse, "set fps: %d/%d => duration: %" G_GINT64_FORMAT " ms", |
| fps_num, fps_den, parse->priv->frame_duration / GST_MSECOND); |
| GST_LOG_OBJECT (parse, "set lead in: %d frames = %" G_GUINT64_FORMAT " ms, " |
| "lead out: %d frames = %" G_GUINT64_FORMAT " ms", |
| lead_in, parse->priv->lead_in_ts / GST_MSECOND, |
| lead_out, parse->priv->lead_out_ts / GST_MSECOND); |
| } |
| |
| /** |
| * gst_base_parse_set_has_timing_info: |
| * @parse: a #GstBaseParse |
| * @has_timing: whether frames carry timing information |
| * |
| * Set if frames carry timing information which the subclass can (generally) |
| * parse and provide. In particular, intrinsic (rather than estimated) time |
| * can be obtained following a seek. |
| */ |
| void |
| gst_base_parse_set_has_timing_info (GstBaseParse * parse, gboolean has_timing) |
| { |
| parse->priv->has_timing_info = has_timing; |
| GST_INFO_OBJECT (parse, "has_timing: %s", (has_timing) ? "yes" : "no"); |
| } |
| |
| /** |
| * gst_base_parse_set_syncable: |
| * @parse: a #GstBaseParse |
| * @syncable: set if frame starts can be identified |
| * |
| * Set if frame starts can be identified. This is set by default and |
| * determines whether seeking based on bitrate averages |
| * is possible for a format/stream. |
| */ |
| void |
| gst_base_parse_set_syncable (GstBaseParse * parse, gboolean syncable) |
| { |
| parse->priv->syncable = syncable; |
| GST_INFO_OBJECT (parse, "syncable: %s", (syncable) ? "yes" : "no"); |
| } |
| |
| /** |
| * gst_base_parse_set_passthrough: |
| * @parse: a #GstBaseParse |
| * @passthrough: %TRUE if parser should run in passthrough mode |
| * |
| * Set if the nature of the format or configuration does not allow (much) |
| * parsing, and the parser should operate in passthrough mode (which only |
| * applies when operating in push mode). That is, incoming buffers are |
| * pushed through unmodified, i.e. no @check_valid_frame or @parse_frame |
| * callbacks will be invoked, but @pre_push_frame will still be invoked, |
| * so subclass can perform as much or as little is appropriate for |
| * passthrough semantics in @pre_push_frame. |
| */ |
| void |
| gst_base_parse_set_passthrough (GstBaseParse * parse, gboolean passthrough) |
| { |
| parse->priv->passthrough = passthrough; |
| GST_INFO_OBJECT (parse, "passthrough: %s", (passthrough) ? "yes" : "no"); |
| } |
| |
| /** |
| * gst_base_parse_set_pts_interpolation: |
| * @parse: a #GstBaseParse |
| * @pts_interpolate: %TRUE if parser should interpolate PTS timestamps |
| * |
| * By default, the base class will guess PTS timestamps using a simple |
| * interpolation (previous timestamp + duration), which is incorrect for |
| * data streams with reordering, where PTS can go backward. Sub-classes |
| * implementing such formats should disable PTS interpolation. |
| */ |
| void |
| gst_base_parse_set_pts_interpolation (GstBaseParse * parse, |
| gboolean pts_interpolate) |
| { |
| parse->priv->pts_interpolate = pts_interpolate; |
| GST_INFO_OBJECT (parse, "PTS interpolation: %s", |
| (pts_interpolate) ? "yes" : "no"); |
| } |
| |
| /** |
| * gst_base_parse_set_infer_ts: |
| * @parse: a #GstBaseParse |
| * @infer_ts: %TRUE if parser should infer DTS/PTS from each other |
| * |
| * By default, the base class might try to infer PTS from DTS and vice |
| * versa. While this is generally correct for audio data, it may not |
| * be otherwise. Sub-classes implementing such formats should disable |
| * timestamp inferring. |
| */ |
| void |
| gst_base_parse_set_infer_ts (GstBaseParse * parse, gboolean infer_ts) |
| { |
| parse->priv->infer_ts = infer_ts; |
| GST_INFO_OBJECT (parse, "TS inferring: %s", (infer_ts) ? "yes" : "no"); |
| } |
| |
| /** |
| * gst_base_parse_set_latency: |
| * @parse: a #GstBaseParse |
| * @min_latency: minimum parse latency |
| * @max_latency: maximum parse latency |
| * |
| * Sets the minimum and maximum (which may likely be equal) latency introduced |
| * by the parsing process. If there is such a latency, which depends on the |
| * particular parsing of the format, it typically corresponds to 1 frame duration. |
| */ |
| void |
| gst_base_parse_set_latency (GstBaseParse * parse, GstClockTime min_latency, |
| GstClockTime max_latency) |
| { |
| g_return_if_fail (min_latency != GST_CLOCK_TIME_NONE); |
| g_return_if_fail (min_latency <= max_latency); |
| |
| GST_OBJECT_LOCK (parse); |
| parse->priv->min_latency = min_latency; |
| parse->priv->max_latency = max_latency; |
| GST_OBJECT_UNLOCK (parse); |
| GST_INFO_OBJECT (parse, "min/max latency %" GST_TIME_FORMAT ", %" |
| GST_TIME_FORMAT, GST_TIME_ARGS (min_latency), |
| GST_TIME_ARGS (max_latency)); |
| } |
| |
| static gboolean |
| gst_base_parse_get_duration (GstBaseParse * parse, GstFormat format, |
| GstClockTime * duration) |
| { |
| gboolean res = FALSE; |
| |
| g_return_val_if_fail (duration != NULL, FALSE); |
| |
| *duration = GST_CLOCK_TIME_NONE; |
| if (parse->priv->duration != -1 && format == parse->priv->duration_fmt) { |
| GST_LOG_OBJECT (parse, "using provided duration"); |
| *duration = parse->priv->duration; |
| res = TRUE; |
| } else if (parse->priv->duration != -1) { |
| GST_LOG_OBJECT (parse, "converting provided duration"); |
| res = gst_base_parse_convert (parse, parse->priv->duration_fmt, |
| parse->priv->duration, format, (gint64 *) duration); |
| } else if (format == GST_FORMAT_TIME && parse->priv->estimated_duration != -1) { |
| GST_LOG_OBJECT (parse, "using estimated duration"); |
| *duration = parse->priv->estimated_duration; |
| res = TRUE; |
| } else { |
| GST_LOG_OBJECT (parse, "cannot estimate duration"); |
| } |
| |
| GST_LOG_OBJECT (parse, "res: %d, duration %" GST_TIME_FORMAT, res, |
| GST_TIME_ARGS (*duration)); |
| return res; |
| } |
| |
| static gboolean |
| gst_base_parse_src_query_default (GstBaseParse * parse, GstQuery * query) |
| { |
| gboolean res = FALSE; |
| GstPad *pad; |
| |
| pad = GST_BASE_PARSE_SRC_PAD (parse); |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_POSITION: |
| { |
| gint64 dest_value; |
| GstFormat format; |
| |
| GST_DEBUG_OBJECT (parse, "position query"); |
| gst_query_parse_position (query, &format, NULL); |
| |
| /* try upstream first */ |
| res = gst_pad_query_default (pad, GST_OBJECT_CAST (parse), query); |
| if (!res) { |
| /* Fall back on interpreting segment */ |
| GST_OBJECT_LOCK (parse); |
| if (format == GST_FORMAT_BYTES) { |
| dest_value = parse->priv->offset; |
| res = TRUE; |
| } else if (format == parse->segment.format && |
| GST_CLOCK_TIME_IS_VALID (parse->segment.position)) { |
| dest_value = gst_segment_to_stream_time (&parse->segment, |
| parse->segment.format, parse->segment.position); |
| res = TRUE; |
| } |
| GST_OBJECT_UNLOCK (parse); |
| if (!res) { |
| /* no precise result, upstream no idea either, then best estimate */ |
| /* priv->offset is updated in both PUSH/PULL modes */ |
| res = gst_base_parse_convert (parse, |
| GST_FORMAT_BYTES, parse->priv->offset, format, &dest_value); |
| } |
| if (res) |
| gst_query_set_position (query, format, dest_value); |
| } |
| break; |
| } |
| case GST_QUERY_DURATION: |
| { |
| GstFormat format; |
| GstClockTime duration; |
| |
| GST_DEBUG_OBJECT (parse, "duration query"); |
| gst_query_parse_duration (query, &format, NULL); |
| |
| /* consult upstream */ |
| res = gst_pad_query_default (pad, GST_OBJECT_CAST (parse), query); |
| |
| /* otherwise best estimate from us */ |
| if (!res) { |
| res = gst_base_parse_get_duration (parse, format, &duration); |
| if (res) |
| gst_query_set_duration (query, format, duration); |
| } |
| break; |
| } |
| case GST_QUERY_SEEKING: |
| { |
| GstFormat fmt; |
| GstClockTime duration = GST_CLOCK_TIME_NONE; |
| gboolean seekable = FALSE; |
| |
| GST_DEBUG_OBJECT (parse, "seeking query"); |
| gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); |
| |
| /* consult upstream */ |
| res = gst_pad_query_default (pad, GST_OBJECT_CAST (parse), query); |
| |
| /* we may be able to help if in TIME */ |
| if (fmt == GST_FORMAT_TIME && gst_base_parse_is_seekable (parse)) { |
| gst_query_parse_seeking (query, &fmt, &seekable, NULL, NULL); |
| /* already OK if upstream takes care */ |
| GST_LOG_OBJECT (parse, "upstream handled %d, seekable %d", |
| res, seekable); |
| if (!(res && seekable)) { |
| 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; |
| } else { |
| seekable = parse->priv->upstream_seekable; |
| GST_LOG_OBJECT (parse, "already determine upstream seekabled: %d", |
| seekable); |
| } |
| gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, duration); |
| res = TRUE; |
| } |
| } |
| break; |
| } |
| case GST_QUERY_FORMATS: |
| gst_query_set_formatsv (query, 3, fmtlist); |
| res = TRUE; |
| break; |
| case GST_QUERY_CONVERT: |
| { |
| GstFormat src_format, dest_format; |
| gint64 src_value, dest_value; |
| |
| gst_query_parse_convert (query, &src_format, &src_value, |
| &dest_format, &dest_value); |
| |
| res = gst_base_parse_convert (parse, src_format, src_value, |
| dest_format, &dest_value); |
| if (res) { |
| gst_query_set_convert (query, src_format, src_value, |
| dest_format, dest_value); |
| } |
| break; |
| } |
| case GST_QUERY_LATENCY: |
| { |
| if ((res = gst_pad_peer_query (parse->sinkpad, query))) { |
| gboolean live; |
| GstClockTime min_latency, max_latency; |
| |
| gst_query_parse_latency (query, &live, &min_latency, &max_latency); |
| GST_DEBUG_OBJECT (parse, "Peer latency: live %d, min %" |
| GST_TIME_FORMAT " max %" GST_TIME_FORMAT, live, |
| GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); |
| |
| GST_OBJECT_LOCK (parse); |
| /* add our latency */ |
| min_latency += parse->priv->min_latency; |
| if (max_latency == -1 || parse->priv->max_latency == -1) |
| max_latency = -1; |
| else |
| max_latency += parse->priv->max_latency; |
| GST_OBJECT_UNLOCK (parse); |
| |
| gst_query_set_latency (query, live, min_latency, max_latency); |
| } |
| break; |
| } |
| case GST_QUERY_SEGMENT: |
| { |
| GstFormat format; |
| gint64 start, stop; |
| |
| format = parse->segment.format; |
| |
| start = |
| gst_segment_to_stream_time (&parse->segment, format, |
| parse->segment.start); |
| if ((stop = parse->segment.stop) == -1) |
| stop = parse->segment.duration; |
| else |
| stop = gst_segment_to_stream_time (&parse->segment, format, stop); |
| |
| gst_query_set_segment (query, parse->segment.rate, format, start, stop); |
| res = TRUE; |
| break; |
| } |
| default: |
| res = gst_pad_query_default (pad, GST_OBJECT_CAST (parse), query); |
| break; |
| } |
| return res; |
| } |
| |
| /* scans for a cluster start from @pos, |
| * return GST_FLOW_OK and frame position/time in @pos/@time if found */ |
| static GstFlowReturn |
| gst_base_parse_find_frame (GstBaseParse * parse, gint64 * pos, |
| GstClockTime * time, GstClockTime * duration) |
| { |
| GstBaseParseClass *klass; |
| gint64 orig_offset; |
| gboolean orig_drain, orig_discont; |
| GstFlowReturn ret = GST_FLOW_OK; |
| GstBuffer *buf = NULL; |
| GstBaseParseFrame *sframe = NULL; |
| |
| g_return_val_if_fail (pos != NULL, GST_FLOW_ERROR); |
| g_return_val_if_fail (time != NULL, GST_FLOW_ERROR); |
| g_return_val_if_fail (duration != NULL, GST_FLOW_ERROR); |
| |
| klass = GST_BASE_PARSE_GET_CLASS (parse); |
| |
| *time = GST_CLOCK_TIME_NONE; |
| *duration = GST_CLOCK_TIME_NONE; |
| |
| /* save state */ |
| orig_offset = parse->priv->offset; |
| orig_discont = parse->priv->discont; |
| orig_drain = parse->priv->drain; |
| |
| GST_DEBUG_OBJECT (parse, "scanning for frame starting at %" G_GINT64_FORMAT |
| " (%#" G_GINT64_MODIFIER "x)", *pos, *pos); |
| |
| /* jump elsewhere and locate next frame */ |
| parse->priv->offset = *pos; |
| /* mark as scanning so frames don't get processed all the way */ |
| parse->priv->scanning = TRUE; |
| ret = gst_base_parse_scan_frame (parse, klass); |
| parse->priv->scanning = FALSE; |
| /* retrieve frame found during scan */ |
| sframe = parse->priv->scanned_frame; |
| parse->priv->scanned_frame = NULL; |
| |
| if (ret != GST_FLOW_OK || !sframe) |
| goto done; |
| |
| /* get offset first, subclass parsing might dump other stuff in there */ |
| *pos = sframe->offset; |
| buf = sframe->buffer; |
| g_assert (buf); |
| |
| /* but it should provide proper time */ |
| *time = GST_BUFFER_TIMESTAMP (buf); |
| *duration = GST_BUFFER_DURATION (buf); |
| |
| GST_LOG_OBJECT (parse, |
| "frame with time %" GST_TIME_FORMAT " at offset %" G_GINT64_FORMAT, |
| GST_TIME_ARGS (*time), *pos); |
| |
| done: |
| if (sframe) |
| gst_base_parse_frame_free (sframe); |
| |
| /* restore state */ |
| parse->priv->offset = orig_offset; |
| parse->priv->discont = orig_discont; |
| parse->priv->drain = orig_drain; |
| |
| return ret; |
| } |
| |
| /* bisect and scan through file for frame starting before @time, |
| * returns OK and @time/@offset if found, NONE and/or error otherwise |
| * If @time == G_MAXINT64, scan for duration ( == last frame) */ |
| static GstFlowReturn |
| gst_base_parse_locate_time (GstBaseParse * parse, GstClockTime * _time, |
| gint64 * _offset) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| gint64 lpos, hpos, newpos; |
| GstClockTime time, ltime, htime, newtime, dur; |
| gboolean cont = TRUE; |
| const GstClockTime tolerance = TARGET_DIFFERENCE; |
| const guint chunk = 4 * 1024; |
| |
| g_return_val_if_fail (_time != NULL, GST_FLOW_ERROR); |
| g_return_val_if_fail (_offset != NULL, GST_FLOW_ERROR); |
| |
| GST_DEBUG_OBJECT (parse, "Bisecting for time %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (*_time)); |
| |
| /* TODO also make keyframe aware if useful some day */ |
| |
| time = *_time; |
| |
| /* basic cases */ |
| if (time == 0) { |
| *_offset = 0; |
| return GST_FLOW_OK; |
| } |
| |
| if (time == -1) { |
| *_offset = -1; |
| return GST_FLOW_OK; |
| } |
| |
| /* do not know at first */ |
| *_offset = -1; |
| *_time = GST_CLOCK_TIME_NONE; |
| |
| /* need initial positions; start and end */ |
| lpos = parse->priv->first_frame_offset; |
| ltime = parse->priv->first_frame_pts; |
| /* try other one if no luck */ |
| if (!GST_CLOCK_TIME_IS_VALID (ltime)) |
| ltime = parse->priv->first_frame_dts; |
| if (!gst_base_parse_get_duration (parse, GST_FORMAT_TIME, &htime)) { |
| GST_DEBUG_OBJECT (parse, "Unknown time duration, cannot bisect"); |
| return GST_FLOW_ERROR; |
| } |
| hpos = parse->priv->upstream_size; |
| |
| GST_DEBUG_OBJECT (parse, |
| "Bisection initial bounds: bytes %" G_GINT64_FORMAT " %" G_GINT64_FORMAT |
| ", times %" GST_TIME_FORMAT " %" GST_TIME_FORMAT, lpos, hpos, |
| GST_TIME_ARGS (ltime), GST_TIME_ARGS (htime)); |
| |
| /* check preconditions are satisfied; |
| * start and end are needed, except for special case where we scan for |
| * last frame to determine duration */ |
| if (parse->priv->pad_mode != GST_PAD_MODE_PULL || !hpos || |
| !GST_CLOCK_TIME_IS_VALID (ltime) || |
| (!GST_CLOCK_TIME_IS_VALID (htime) && time != G_MAXINT64)) { |
| return GST_FLOW_OK; |
| } |
| |
| /* shortcut cases */ |
| if (time < ltime) { |
| goto exit; |
| } else if (time < ltime + tolerance) { |
| *_offset = lpos; |
| *_time = ltime; |
| goto exit; |
| } else if (time >= htime) { |
| *_offset = hpos; |
| *_time = htime; |
| goto exit; |
| } |
| |
| while (htime > ltime && cont) { |
| GST_LOG_OBJECT (parse, |
| "lpos: %" G_GUINT64_FORMAT ", ltime: %" GST_TIME_FORMAT, lpos, |
| GST_TIME_ARGS (ltime)); |
| GST_LOG_OBJECT (parse, |
| "hpos: %" G_GUINT64_FORMAT ", htime: %" GST_TIME_FORMAT, hpos, |
| GST_TIME_ARGS (htime)); |
| if (G_UNLIKELY (time == G_MAXINT64)) { |
| newpos = hpos; |
| } else if (G_LIKELY (hpos > lpos)) { |
| newpos = |
| gst_util_uint64_scale (hpos - lpos, time - ltime, htime - ltime) + |
| lpos - chunk; |
| } else { |
| /* should mean lpos == hpos, since lpos <= hpos is invariant */ |
| newpos = lpos; |
| /* we check this case once, but not forever, so break loop */ |
| cont = FALSE; |
| } |
| |
| /* ensure */ |
| newpos = CLAMP (newpos, lpos, hpos); |
| GST_LOG_OBJECT (parse, |
| "estimated _offset for %" GST_TIME_FORMAT ": %" G_GINT64_FORMAT, |
| GST_TIME_ARGS (time), newpos); |
| |
| ret = gst_base_parse_find_frame (parse, &newpos, &newtime, &dur); |
| if (ret == GST_FLOW_EOS) { |
| /* heuristic HACK */ |
| hpos = MAX (lpos, hpos - chunk); |
| continue; |
| } else if (ret != GST_FLOW_OK) { |
| goto exit; |
| } |
| |
| if (newtime == -1 || newpos == -1) { |
| GST_DEBUG_OBJECT (parse, "subclass did not provide metadata; aborting"); |
| break; |
| } |
| |
| if (G_UNLIKELY (time == G_MAXINT64)) { |
| *_offset = newpos; |
| *_time = newtime; |
| if (GST_CLOCK_TIME_IS_VALID (dur)) |
| *_time += dur; |
| break; |
| } else if (newtime > time) { |
| /* overshoot */ |
| hpos = (newpos >= hpos) ? MAX (lpos, hpos - chunk) : MAX (lpos, newpos); |
| htime = newtime; |
| } else if (newtime + tolerance > time) { |
| /* close enough undershoot */ |
| *_offset = newpos; |
| *_time = newtime; |
| break; |
| } else if (newtime < ltime) { |
| /* so a position beyond lpos resulted in earlier time than ltime ... */ |
| GST_DEBUG_OBJECT (parse, "non-ascending time; aborting"); |
| break; |
| } else { |
| /* undershoot too far */ |
| newpos += newpos == lpos ? chunk : 0; |
| lpos = CLAMP (newpos, lpos, hpos); |
| ltime = newtime; |
| } |
| } |
| |
| exit: |
| GST_LOG_OBJECT (parse, "return offset %" G_GINT64_FORMAT ", time %" |
| GST_TIME_FORMAT, *_offset, GST_TIME_ARGS (*_time)); |
| return ret; |
| } |
| |
| static gint64 |
| gst_base_parse_find_offset (GstBaseParse * parse, GstClockTime time, |
| gboolean before, GstClockTime * _ts) |
| { |
| gint64 bytes = 0, ts = 0; |
| GstIndexEntry *entry = NULL; |
| |
| if (time == GST_CLOCK_TIME_NONE) { |
| ts = time; |
| bytes = -1; |
| goto exit; |
| } |
| |
| GST_BASE_PARSE_INDEX_LOCK (parse); |
| if (parse->priv->index) { |
| /* Let's check if we have an index entry for that time */ |
| entry = gst_index_get_assoc_entry (parse->priv->index, |
| parse->priv->index_id, |
| before ? GST_INDEX_LOOKUP_BEFORE : GST_INDEX_LOOKUP_AFTER, |
| GST_INDEX_ASSOCIATION_FLAG_KEY_UNIT, GST_FORMAT_TIME, time); |
| } |
| |
| if (entry) { |
| gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &bytes); |
| gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, &ts); |
| |
| GST_DEBUG_OBJECT (parse, "found index entry for %" GST_TIME_FORMAT |
| " at %" GST_TIME_FORMAT ", offset %" G_GINT64_FORMAT, |
| GST_TIME_ARGS (time), GST_TIME_ARGS (ts), bytes); |
| } else { |
| GST_DEBUG_OBJECT (parse, "no index entry found for %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (time)); |
| if (!before) { |
| bytes = -1; |
| ts = GST_CLOCK_TIME_NONE; |
| } |
| } |
| GST_BASE_PARSE_INDEX_UNLOCK (parse); |
| |
| exit: |
| if (_ts) |
| *_ts = ts; |
| |
| return bytes; |
| } |
| |
| /* returns TRUE if seek succeeded */ |
| static gboolean |
| gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event) |
| { |
| gdouble rate; |
| GstFormat format; |
| GstSeekFlags flags; |
| GstSeekType start_type = GST_SEEK_TYPE_NONE, stop_type; |
| gboolean flush, update, res = TRUE, accurate; |
| gint64 start, stop, seekpos, seekstop; |
| GstSegment seeksegment = { 0, }; |
| GstClockTime start_ts; |
| guint32 seqnum; |
| GstEvent *segment_event; |
| |
| /* try upstream first, unless we're driving the streaming thread ourselves */ |
| if (parse->priv->pad_mode != GST_PAD_MODE_PULL) { |
| res = gst_pad_push_event (parse->sinkpad, gst_event_ref (event)); |
| if (res) |
| goto done; |
| } |
| |
| gst_event_parse_seek (event, &rate, &format, &flags, |
| &start_type, &start, &stop_type, &stop); |
| seqnum = gst_event_get_seqnum (event); |
| |
| GST_DEBUG_OBJECT (parse, "seek to format %s, rate %f, " |
| "start type %d at %" GST_TIME_FORMAT ", end type %d at %" |
| GST_TIME_FORMAT, gst_format_get_name (format), rate, |
| start_type, GST_TIME_ARGS (start), stop_type, GST_TIME_ARGS (stop)); |
| |
| /* we can only handle TIME, so check if subclass can convert |
| * to TIME format if it's some other format (such as DEFAULT) */ |
| if (format != GST_FORMAT_TIME) { |
| if (!gst_base_parse_convert (parse, format, start, GST_FORMAT_TIME, &start) |
| || !gst_base_parse_convert (parse, format, stop, GST_FORMAT_TIME, |
| &stop)) |
| goto no_convert_to_time; |
| |
| GST_INFO_OBJECT (parse, "converted %s format to start time " |
| "%" GST_TIME_FORMAT " and stop time %" GST_TIME_FORMAT, |
| gst_format_get_name (format), GST_TIME_ARGS (start), |
| GST_TIME_ARGS (stop)); |
| |
| format = GST_FORMAT_TIME; |
| } |
| |
| /* no negative rates in push mode (unless upstream takes care of that, but |
| * we've already tried upstream and it didn't handle the seek request) */ |
| if (rate < 0.0 && parse->priv->pad_mode == GST_PAD_MODE_PUSH) |
| goto negative_rate; |
| |
| if (start_type != GST_SEEK_TYPE_SET || |
| (stop_type != GST_SEEK_TYPE_SET && stop_type != GST_SEEK_TYPE_NONE)) |
| goto wrong_type; |
| |
| /* get flush flag */ |
| flush = flags & GST_SEEK_FLAG_FLUSH; |
| |
| /* copy segment, we need this because we still need the old |
| * segment when we close the current segment. */ |
| gst_segment_copy_into (&parse->segment, &seeksegment); |
| |
| GST_DEBUG_OBJECT (parse, "configuring seek"); |
| gst_segment_do_seek (&seeksegment, rate, format, flags, |
| start_type, start, stop_type, stop, &update); |
| |
| /* accurate seeking implies seek tables are used to obtain position, |
| * and the requested segment is maintained exactly, not adjusted any way */ |
| accurate = flags & GST_SEEK_FLAG_ACCURATE; |
| |
| /* maybe we can be accurate for (almost) free */ |
| gst_base_parse_find_offset (parse, seeksegment.position, TRUE, &start_ts); |
| if (seeksegment.position <= start_ts + TARGET_DIFFERENCE) { |
| GST_DEBUG_OBJECT (parse, "accurate seek possible"); |
| accurate = TRUE; |
| } |
| |
| if (accurate) { |
| 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 { |
| 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, |
| GST_FORMAT_BYTES, &seekstop)) |
| goto convert_failed; |
| } |
| |
| GST_DEBUG_OBJECT (parse, |
| "seek position %" G_GINT64_FORMAT " in bytes: %" G_GINT64_FORMAT, |
| start_ts, seekpos); |
| GST_DEBUG_OBJECT (parse, |
| "seek stop %" G_GINT64_FORMAT " in bytes: %" G_GINT64_FORMAT, |
| seeksegment.stop, seekstop); |
| |
| if (parse->priv->pad_mode == GST_PAD_MODE_PULL) { |
| gint64 last_stop; |
| |
| GST_DEBUG_OBJECT (parse, "seek in PULL mode"); |
| |
| if (flush) { |
| if (parse->srcpad) { |
| GstEvent *fevent = gst_event_new_flush_start (); |
| GST_DEBUG_OBJECT (parse, "sending flush start"); |
| |
| gst_event_set_seqnum (fevent, seqnum); |
| |
| gst_pad_push_event (parse->srcpad, gst_event_ref (fevent)); |
| /* unlock upstream pull_range */ |
| gst_pad_push_event (parse->sinkpad, fevent); |
| } |
| } else { |
| gst_pad_pause_task (parse->sinkpad); |
| } |
| |
| /* we should now be able to grab the streaming thread because we stopped it |
| * with the above flush/pause code */ |
| GST_PAD_STREAM_LOCK (parse->sinkpad); |
| |
| /* save current position */ |
| last_stop = parse->segment.position; |
| GST_DEBUG_OBJECT (parse, "stopped streaming at %" G_GINT64_FORMAT, |
| last_stop); |
| |
| /* now commit to new position */ |
| |
| /* prepare for streaming again */ |
| if (flush) { |
| GstEvent *fevent = gst_event_new_flush_stop (TRUE); |
| GST_DEBUG_OBJECT (parse, "sending flush stop"); |
| gst_event_set_seqnum (fevent, seqnum); |
| 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)); |
| |
| /* store the newsegment event so it can be sent from the streaming thread. */ |
| /* This will be sent later in _loop() */ |
| segment_event = gst_event_new_segment (&parse->segment); |
| gst_event_set_seqnum (segment_event, seqnum); |
| parse->priv->pending_events = |
| g_list_prepend (parse->priv->pending_events, segment_event); |
| |
| GST_DEBUG_OBJECT (parse, "Created newseg format %d, " |
| "start = %" GST_TIME_FORMAT ", stop = %" GST_TIME_FORMAT |
| ", time = %" GST_TIME_FORMAT, format, |
| GST_TIME_ARGS (parse->segment.start), |
| GST_TIME_ARGS (parse->segment.stop), |
| GST_TIME_ARGS (parse->segment.time)); |
| |
| /* one last chance in pull mode to stay accurate; |
| * maybe scan and subclass can find where to go */ |
| if (!accurate) { |
| gint64 scanpos; |
| GstClockTime ts = seeksegment.position; |
| |
| gst_base_parse_locate_time (parse, &ts, &scanpos); |
| if (scanpos >= 0) { |
| accurate = TRUE; |
| seekpos = scanpos; |
| /* running collected index now consists of several intervals, |
| * so optimized check no longer possible */ |
| parse->priv->index_last_valid = FALSE; |
| parse->priv->index_last_offset = 0; |
| parse->priv->index_last_ts = 0; |
| } |
| } |
| |
| /* mark discont if we are going to stream from another position. */ |
| if (seekpos != parse->priv->offset) { |
| GST_DEBUG_OBJECT (parse, |
| "mark DISCONT, we did a seek to another position"); |
| parse->priv->offset = seekpos; |
| parse->priv->last_offset = seekpos; |
| parse->priv->seen_keyframe = FALSE; |
| parse->priv->discont = TRUE; |
| parse->priv->next_dts = start_ts; |
| parse->priv->next_pts = GST_CLOCK_TIME_NONE; |
| parse->priv->last_dts = GST_CLOCK_TIME_NONE; |
| parse->priv->last_pts = GST_CLOCK_TIME_NONE; |
| parse->priv->sync_offset = seekpos; |
| parse->priv->exact_position = accurate; |
| } |
| |
| /* Start streaming thread if paused */ |
| gst_pad_start_task (parse->sinkpad, |
| (GstTaskFunction) gst_base_parse_loop, parse->sinkpad, NULL); |
| |
| GST_PAD_STREAM_UNLOCK (parse->sinkpad); |
| |
| /* handled seek */ |
| res = TRUE; |
| } else { |
| GstEvent *new_event; |
| GstBaseParseSeek *seek; |
| GstSeekFlags flags = (flush ? GST_SEEK_FLAG_FLUSH : GST_SEEK_FLAG_NONE); |
| |
| /* The only thing we need to do in PUSH-mode is to send the |
| seek event (in bytes) to upstream. Segment / flush handling happens |
| in corresponding src event handlers */ |
| GST_DEBUG_OBJECT (parse, "seek in PUSH mode"); |
| if (seekstop >= 0 && seekstop <= seekpos) |
| seekstop = seekpos; |
| new_event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, |
| GST_SEEK_TYPE_SET, seekpos, stop_type, seekstop); |
| gst_event_set_seqnum (new_event, seqnum); |
| |
| /* store segment info so its precise details can be reconstructed when |
| * receiving newsegment; |
| * this matters for all details when accurate seeking, |
| * is most useful to preserve NONE stop time otherwise */ |
| seek = g_new0 (GstBaseParseSeek, 1); |
| seek->segment = seeksegment; |
| seek->accurate = accurate; |
| seek->offset = seekpos; |
| seek->start_ts = start_ts; |
| GST_OBJECT_LOCK (parse); |
| /* less optimal, but preserves order */ |
| parse->priv->pending_seeks = |
| g_slist_append (parse->priv->pending_seeks, seek); |
| GST_OBJECT_UNLOCK (parse); |
| |
| res = gst_pad_push_event (parse->sinkpad, new_event); |
| |
| if (!res) { |
| GST_OBJECT_LOCK (parse); |
| parse->priv->pending_seeks = |
| g_slist_remove (parse->priv->pending_seeks, seek); |
| GST_OBJECT_UNLOCK (parse); |
| g_free (seek); |
| } |
| } |
| |
| done: |
| gst_event_unref (event); |
| return res; |
| |
| /* ERRORS */ |
| negative_rate: |
| { |
| GST_DEBUG_OBJECT (parse, "negative playback rates delegated upstream."); |
| res = FALSE; |
| goto done; |
| } |
| wrong_type: |
| { |
| GST_DEBUG_OBJECT (parse, "unsupported seek type."); |
| res = FALSE; |
| goto done; |
| } |
| no_convert_to_time: |
| { |
| GST_DEBUG_OBJECT (parse, "seek in %s format was requested, but subclass " |
| "couldn't convert that into TIME format", gst_format_get_name (format)); |
| res = FALSE; |
| goto done; |
| } |
| convert_failed: |
| { |
| GST_DEBUG_OBJECT (parse, "conversion TIME to BYTES failed."); |
| res = FALSE; |
| goto done; |
| } |
| } |
| |
| static void |
| gst_base_parse_set_upstream_tags (GstBaseParse * parse, GstTagList * taglist) |
| { |
| if (taglist == parse->priv->upstream_tags) |
| return; |
| |
| 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 |
| static void |
| gst_base_parse_set_index (GstElement * element, GstIndex * index) |
| { |
| GstBaseParse *parse = GST_BASE_PARSE (element); |
| |
| GST_BASE_PARSE_INDEX_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_CAST (element), |
| &parse->priv->index_id); |
| parse->priv->own_index = FALSE; |
| } else { |
| parse->priv->index = NULL; |
| } |
| GST_BASE_PARSE_INDEX_UNLOCK (parse); |
| } |
| |
| static GstIndex * |
| gst_base_parse_get_index (GstElement * element) |
| { |
| GstBaseParse *parse = GST_BASE_PARSE (element); |
| GstIndex *result = NULL; |
| |
| GST_BASE_PARSE_INDEX_LOCK (parse); |
| if (parse->priv->index) |
| result = gst_object_ref (parse->priv->index); |
| GST_BASE_PARSE_INDEX_UNLOCK (parse); |
| |
| return result; |
| } |
| #endif |
| |
| static GstStateChangeReturn |
| gst_base_parse_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstBaseParse *parse; |
| GstStateChangeReturn result; |
| |
| 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 */ |
| GST_BASE_PARSE_INDEX_LOCK (parse); |
| 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 = g_object_new (gst_mem_index_get_type (), NULL); |
| gst_index_get_writer_id (parse->priv->index, GST_OBJECT (parse), |
| &parse->priv->index_id); |
| parse->priv->own_index = TRUE; |
| } |
| GST_BASE_PARSE_INDEX_UNLOCK (parse); |
| break; |
| default: |
| break; |
| } |
| |
| result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| gst_base_parse_reset (parse); |
| break; |
| default: |
| break; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * gst_base_parse_set_ts_at_offset: |
| * @parse: a #GstBaseParse |
| * @offset: offset into current buffer |
| * |
| * This function should only be called from a @handle_frame implementation. |
| * |
| * #GstBaseParse creates initial timestamps for frames by using the last |
| * timestamp seen in the stream before the frame starts. In certain |
| * cases, the correct timestamps will occur in the stream after the |
| * start of the frame, but before the start of the actual picture data. |
| * This function can be used to set the timestamps based on the offset |
| * into the frame data that the picture starts. |
| * |
| * Since: 1.2 |
| */ |
| void |
| gst_base_parse_set_ts_at_offset (GstBaseParse * parse, gsize offset) |
| { |
| GstClockTime pts, dts; |
| |
| g_return_if_fail (GST_IS_BASE_PARSE (parse)); |
| |
| pts = gst_adapter_prev_pts_at_offset (parse->priv->adapter, offset, NULL); |
| dts = gst_adapter_prev_dts_at_offset (parse->priv->adapter, offset, NULL); |
| |
| if (!GST_CLOCK_TIME_IS_VALID (pts) || !GST_CLOCK_TIME_IS_VALID (dts)) { |
| GST_DEBUG_OBJECT (parse, |
| "offset adapter timestamps dts=%" GST_TIME_FORMAT " pts=%" |
| GST_TIME_FORMAT, GST_TIME_ARGS (dts), GST_TIME_ARGS (pts)); |
| } |
| 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)) |
| parse->priv->prev_dts = parse->priv->next_dts = dts; |
| } |
| |
| /** |
| * 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); |
| } |