*
* 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., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
*/
/**
* SECTION:gstbasesink
+ * @title: GstBaseSink
* @short_description: Base class for sink elements
* @see_also: #GstBaseTransform, #GstBaseSrc
*
* #GstBaseSink provides support for exactly one sink pad, which should be
* named "sink". A sink implementation (subclass of #GstBaseSink) should
* install a pad template in its class_init function, like so:
- * |[
+ * |[<!-- language="C" -->
* static void
* my_element_class_init (GstMyElementClass *klass)
* {
* GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
*
* // sinktemplate should be a #GstStaticPadTemplate with direction
- * // #GST_PAD_SINK and name "sink"
- * gst_element_class_add_pad_template (gstelement_class,
- * gst_static_pad_template_get (&sinktemplate));
- * // see #GstElementDetails
- * gst_element_class_set_details (gstelement_class, &details);
+ * // %GST_PAD_SINK and name "sink"
+ * gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
+ *
+ * gst_element_class_set_static_metadata (gstelement_class,
+ * "Sink name",
+ * "Sink",
+ * "My Sink element",
+ * "The author <my.sink@my.email>");
* }
* ]|
*
* #GstBaseSink will handle the prerolling correctly. This means that it will
- * return #GST_STATE_CHANGE_ASYNC from a state change to PAUSED until the first
+ * return %GST_STATE_CHANGE_ASYNC from a state change to PAUSED until the first
* buffer arrives in this element. The base class will call the
* #GstBaseSinkClass.preroll() vmethod with this preroll buffer and will then
* commit the state change to the next asynchronously pending state.
*
* When the element is set to PLAYING, #GstBaseSink will synchronise on the
* clock using the times returned from #GstBaseSinkClass.get_times(). If this
- * function returns #GST_CLOCK_TIME_NONE for the start time, no synchronisation
+ * function returns %GST_CLOCK_TIME_NONE for the start time, no synchronisation
* will be done. Synchronisation can be disabled entirely by setting the object
* #GstBaseSink:sync property to %FALSE.
*
* element receives EOS in PAUSED, preroll completes, the event is queued and an
* EOS message is posted when going to PLAYING.
*
- * #GstBaseSink will internally use the #GST_EVENT_NEWSEGMENT events to schedule
+ * #GstBaseSink will internally use the %GST_EVENT_SEGMENT events to schedule
* synchronisation and clipping of buffers. Buffers that fall completely outside
* of the current segment are dropped. Buffers that fall partially in the
* segment are rendered (and prerolled). Subclasses should do any subbuffer
* clipping themselves when needed.
*
* #GstBaseSink will by default report the current playback position in
- * #GST_FORMAT_TIME based on the current clock time and segment information.
+ * %GST_FORMAT_TIME based on the current clock time and segment information.
* If no clock has been set on the element, the query will be forwarded
* upstream.
*
* should configure itself to process a specific media type.
*
* The #GstBaseSinkClass.start() and #GstBaseSinkClass.stop() virtual methods
- * will be called when resources should be allocated. Any
+ * will be called when resources should be allocated. Any
* #GstBaseSinkClass.preroll(), #GstBaseSinkClass.render() and
* #GstBaseSinkClass.set_caps() function will be called between the
* #GstBaseSinkClass.start() and #GstBaseSinkClass.stop() calls.
*
* The #GstBaseSinkClass.event() virtual method will be called when an event is
- * received by #GstBaseSink. Normally this method should only be overriden by
+ * received by #GstBaseSink. Normally this method should only be overridden by
* very specific elements (such as file sinks) which need to handle the
* newsegment event specially.
*
* The #GstBaseSink:async property can be used to instruct the sink to never
* perform an ASYNC state change. This feature is mostly usable when dealing
* with non-synchronized streams or sparse streams.
- *
- * Last reviewed on 2007-08-29 (0.10.15)
*/
#ifdef HAVE_CONFIG_H
GST_DEBUG_CATEGORY_STATIC (gst_base_sink_debug);
#define GST_CAT_DEFAULT gst_base_sink_debug
-#define GST_BASE_SINK_GET_PRIVATE(obj) \
- (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_BASE_SINK, GstBaseSinkPrivate))
-
#define GST_FLOW_STEP GST_FLOW_CUSTOM_ERROR
typedef struct
gboolean async_enabled;
GstClockTimeDiff ts_offset;
GstClockTime render_delay;
+ GstClockTime processing_deadline;
/* start, stop of current buffer, stream time, used to report position */
GstClockTime current_sstart;
GstClockTime last_left;
/* running averages go here these are done on running time */
- GstClockTime avg_pt;
- GstClockTime avg_duration;
- gdouble avg_rate;
- GstClockTime avg_in_diff;
-
- /* these are done on system time. avg_jitter and avg_render are
- * compared to eachother to see if the rendering time takes a
- * huge amount of the processing, If so we are flooded with
- * buffers. */
- GstClockTime last_left_systime;
- GstClockTime avg_jitter;
- GstClockTime start, stop;
- GstClockTime avg_render;
+ GstClockTime avg_pt, avg_in_diff;
+ gdouble avg_rate; /* average with infinite window */
/* number of rendered and dropped frames */
guint64 rendered;
/* latency stuff */
GstClockTime latency;
- /* if we already commited the state */
- gboolean commited;
+ /* if we already committed the state */
+ gboolean committed;
/* state change to playing ongoing */
gboolean to_playing;
gint enable_last_sample; /* atomic */
GstBuffer *last_buffer;
GstCaps *last_caps;
+ GstBufferList *last_buffer_list;
/* negotiated caps */
GstCaps *caps;
/* for throttling and QoS */
GstClockTime earliest_in_time;
GstClockTime throttle_time;
+
+ /* for rate control */
+ guint64 max_bitrate;
+ GstClockTime rc_time;
+ GstClockTime rc_next;
+ gsize rc_accumulated;
+
+ gboolean drop_out_of_segment;
};
#define DO_RUNNING_AVG(avg,val,size) (((val) + ((size)-1) * (avg)) / (size))
#define UPDATE_RUNNING_AVG(avg,val) DO_RUNNING_AVG(avg,val,8)
/* the windows for these running averages are experimentally obtained.
- * possitive values get averaged more while negative values use a small
+ * positive values get averaged more while negative values use a small
* window so we can react faster to badness. */
#define UPDATE_RUNNING_AVG_P(avg,val) DO_RUNNING_AVG(avg,val,16)
#define UPDATE_RUNNING_AVG_N(avg,val) DO_RUNNING_AVG(avg,val,4)
#define DEFAULT_RENDER_DELAY 0
#define DEFAULT_ENABLE_LAST_SAMPLE TRUE
#define DEFAULT_THROTTLE_TIME 0
+#define DEFAULT_MAX_BITRATE 0
+#define DEFAULT_DROP_OUT_OF_SEGMENT TRUE
+#define DEFAULT_PROCESSING_DEADLINE (20 * GST_MSECOND)
enum
{
PROP_BLOCKSIZE,
PROP_RENDER_DELAY,
PROP_THROTTLE_TIME,
+ PROP_MAX_BITRATE,
+ PROP_PROCESSING_DEADLINE,
PROP_LAST
};
static GstElementClass *parent_class = NULL;
+static gint private_offset = 0;
static void gst_base_sink_class_init (GstBaseSinkClass * klass);
static void gst_base_sink_init (GstBaseSink * trans, gpointer g_class);
_type = g_type_register_static (GST_TYPE_ELEMENT,
"GstBaseSink", &base_sink_info, G_TYPE_FLAG_ABSTRACT);
+
+ private_offset =
+ g_type_add_instance_private (_type, sizeof (GstBaseSinkPrivate));
+
g_once_init_leave (&base_sink_type, _type);
}
return base_sink_type;
}
+static inline GstBaseSinkPrivate *
+gst_base_sink_get_instance_private (GstBaseSink * self)
+{
+ return (G_STRUCT_MEMBER_P (self, private_offset));
+}
+
static void gst_base_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_base_sink_get_property (GObject * object, guint prop_id,
/* check if an object was too late */
static gboolean gst_base_sink_is_too_late (GstBaseSink * basesink,
GstMiniObject * obj, GstClockTime rstart, GstClockTime rstop,
- GstClockReturn status, GstClockTimeDiff jitter);
+ GstClockReturn status, GstClockTimeDiff jitter, gboolean render);
static void
gst_base_sink_class_init (GstBaseSinkClass * klass)
gobject_class = G_OBJECT_CLASS (klass);
gstelement_class = GST_ELEMENT_CLASS (klass);
+ if (private_offset != 0)
+ g_type_class_adjust_private_offset (klass, &private_offset);
+
GST_DEBUG_CATEGORY_INIT (gst_base_sink_debug, "basesink", 0,
"basesink element");
- g_type_class_add_private (klass, sizeof (GstBaseSinkPrivate));
-
parent_class = g_type_class_peek_parent (klass);
gobject_class->finalize = gst_base_sink_finalize;
/**
* GstBaseSink:async:
*
- * If set to #TRUE, the basesink will perform asynchronous state changes.
- * When set to #FALSE, the sink will not signal the parent when it prerolls.
+ * If set to %TRUE, the basesink will perform asynchronous state changes.
+ * When set to %FALSE, the sink will not signal the parent when it prerolls.
* Use this option when dealing with sparse streams or when synchronisation is
* not required.
*/
/**
* GstBaseSink:enable-last-sample:
*
- * Enable the last-sample property. If FALSE, basesink doesn't keep a
+ * Enable the last-sample property. If %FALSE, basesink doesn't keep a
* reference to the last buffer arrived and the last-sample property is always
- * set to NULL. This can be useful if you need buffers to be released as soon
+ * set to %NULL. This can be useful if you need buffers to be released as soon
* as possible, eg. if you're using a buffer pool.
*/
g_object_class_install_property (gobject_class, PROP_ENABLE_LAST_SAMPLE,
*
* The last buffer that arrived in the sink and was used for preroll or for
* rendering. This property can be used to generate thumbnails. This property
- * can be NULL when the sink has not yet received a bufer.
+ * can be %NULL when the sink has not yet received a buffer.
*/
g_object_class_install_property (gobject_class, PROP_LAST_SAMPLE,
g_param_spec_boxed ("last-sample", "Last Sample",
*
* The amount of bytes to pull when operating in pull mode.
*/
- /* FIXME 0.11: blocksize property should be int, otherwise min>max.. */
+ /* FIXME 2.0: blocksize property should be int, otherwise min>max.. */
g_object_class_install_property (gobject_class, PROP_BLOCKSIZE,
g_param_spec_uint ("blocksize", "Block size",
"Size in bytes to pull per buffer (0 = default)", 0, G_MAXUINT,
*/
g_object_class_install_property (gobject_class, PROP_THROTTLE_TIME,
g_param_spec_uint64 ("throttle-time", "Throttle time",
- "The time to keep between rendered buffers", 0, G_MAXUINT64,
- DEFAULT_THROTTLE_TIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ "The time to keep between rendered buffers (0 = disabled)", 0,
+ G_MAXUINT64, DEFAULT_THROTTLE_TIME,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstBaseSink:max-bitrate:
+ *
+ * Control the maximum amount of bits that will be rendered per second.
+ * Setting this property to a value bigger than 0 will make the sink delay
+ * rendering of the buffers when it would exceed to max-bitrate.
+ *
+ * Since: 1.2
+ */
+ g_object_class_install_property (gobject_class, PROP_MAX_BITRATE,
+ g_param_spec_uint64 ("max-bitrate", "Max Bitrate",
+ "The maximum bits per second to render (0 = disabled)", 0,
+ G_MAXUINT64, DEFAULT_MAX_BITRATE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstBaseSink:processing-deadline:
+ *
+ * Maximum amount of time (in nanoseconds) that the pipeline can take
+ * for processing the buffer. This is added to the latency of live
+ * pipelines.
+ *
+ * Since: 1.16
+ */
+ g_object_class_install_property (gobject_class, PROP_PROCESSING_DEADLINE,
+ g_param_spec_uint64 ("processing-deadline", "Processing deadline",
+ "Maximum processing deadline in nanoseconds", 0, G_MAXUINT64,
+ DEFAULT_PROCESSING_DEADLINE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_base_sink_change_state);
GstPadTemplate *pad_template;
GstBaseSinkPrivate *priv;
- basesink->priv = priv = GST_BASE_SINK_GET_PRIVATE (basesink);
+ basesink->priv = priv = gst_base_sink_get_instance_private (basesink);
pad_template =
gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "sink");
priv->async_enabled = DEFAULT_ASYNC;
priv->ts_offset = DEFAULT_TS_OFFSET;
priv->render_delay = DEFAULT_RENDER_DELAY;
+ priv->processing_deadline = DEFAULT_PROCESSING_DEADLINE;
priv->blocksize = DEFAULT_BLOCKSIZE;
priv->cached_clock_id = NULL;
g_atomic_int_set (&priv->enable_last_sample, DEFAULT_ENABLE_LAST_SAMPLE);
priv->throttle_time = DEFAULT_THROTTLE_TIME;
+ priv->max_bitrate = DEFAULT_MAX_BITRATE;
+
+ priv->drop_out_of_segment = DEFAULT_DROP_OUT_OF_SEGMENT;
GST_OBJECT_FLAG_SET (basesink, GST_ELEMENT_FLAG_SINK);
}
* @sync: the new sync value.
*
* Configures @sink to synchronize on the clock or not. When
- * @sync is FALSE, incoming samples will be played as fast as
- * possible. If @sync is TRUE, the timestamps of the incomming
+ * @sync is %FALSE, incoming samples will be played as fast as
+ * possible. If @sync is %TRUE, the timestamps of the incoming
* buffers will be used to schedule the exact render time of its
* contents.
*/
* Checks if @sink is currently configured to synchronize against the
* clock.
*
- * Returns: TRUE if the sink is configured to synchronize against the clock.
+ * Returns: %TRUE if the sink is configured to synchronize against the clock.
*/
gboolean
gst_base_sink_get_sync (GstBaseSink * sink)
}
/**
+ * gst_base_sink_set_drop_out_of_segment:
+ * @sink: the sink
+ * @drop_out_of_segment: drop buffers outside the segment
+ *
+ * Configure @sink to drop buffers which are outside the current segment
+ *
+ * Since: 1.12
+ */
+void
+gst_base_sink_set_drop_out_of_segment (GstBaseSink * sink,
+ gboolean drop_out_of_segment)
+{
+ g_return_if_fail (GST_IS_BASE_SINK (sink));
+
+ GST_OBJECT_LOCK (sink);
+ sink->priv->drop_out_of_segment = drop_out_of_segment;
+ GST_OBJECT_UNLOCK (sink);
+
+}
+
+/**
+ * gst_base_sink_get_drop_out_of_segment:
+ * @sink: the sink
+ *
+ * Checks if @sink is currently configured to drop buffers which are outside
+ * the current segment
+ *
+ * Returns: %TRUE if the sink is configured to drop buffers outside the
+ * current segment.
+ *
+ * Since: 1.12
+ */
+gboolean
+gst_base_sink_get_drop_out_of_segment (GstBaseSink * sink)
+{
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_BASE_SINK (sink), FALSE);
+
+ GST_OBJECT_LOCK (sink);
+ res = sink->priv->drop_out_of_segment;
+ GST_OBJECT_UNLOCK (sink);
+
+ return res;
+}
+
+/**
* gst_base_sink_set_max_lateness:
* @sink: the sink
* @max_lateness: the new max lateness value.
* gst_base_sink_get_max_lateness:
* @sink: the sink
*
- * Gets the max lateness value. See gst_base_sink_set_max_lateness for
+ * Gets the max lateness value. See gst_base_sink_set_max_lateness() for
* more details.
*
* Returns: The maximum time in nanoseconds that a buffer can be late
* Checks if @sink is currently configured to send Quality-of-Service events
* upstream.
*
- * Returns: TRUE if the sink is configured to perform Quality-of-Service.
+ * Returns: %TRUE if the sink is configured to perform Quality-of-Service.
*/
gboolean
gst_base_sink_is_qos_enabled (GstBaseSink * sink)
* @sink: the sink
* @enabled: the new async value.
*
- * Configures @sink to perform all state changes asynchronusly. When async is
+ * Configures @sink to perform all state changes asynchronously. When async is
* disabled, the sink will immediately go to PAUSED instead of waiting for a
* preroll buffer. This feature is useful if the sink does not synchronize
* against the clock or when it is dealing with sparse streams.
* Checks if @sink is currently configured to perform asynchronous state
* changes to PAUSED.
*
- * Returns: TRUE if the sink is configured to perform asynchronous state
+ * Returns: %TRUE if the sink is configured to perform asynchronous state
* changes.
*/
gboolean
*
* Free-function: gst_sample_unref
*
- * Returns: (transfer full): a #GstSample. gst_sample_unref() after usage.
- * This function returns NULL when no buffer has arrived in the sink yet
- * or when the sink is not in PAUSED or PLAYING.
+ * Returns: (transfer full) (nullable): a #GstSample. gst_sample_unref() after
+ * usage. This function returns %NULL when no buffer has arrived in the
+ * sink yet or when the sink is not in PAUSED or PLAYING.
*/
GstSample *
gst_base_sink_get_last_sample (GstBaseSink * sink)
g_return_val_if_fail (GST_IS_BASE_SINK (sink), NULL);
GST_OBJECT_LOCK (sink);
- if (sink->priv->last_buffer) {
+ if (sink->priv->last_buffer_list) {
+ GstBuffer *first_buffer = NULL;
+
+ /* Set the first buffer in the list to last sample's buffer */
+ first_buffer = gst_buffer_list_get (sink->priv->last_buffer_list, 0);
+ res =
+ gst_sample_new (first_buffer, sink->priv->last_caps, &sink->segment,
+ NULL);
+ gst_sample_set_buffer_list (res, sink->priv->last_buffer_list);
+ } else if (sink->priv->last_buffer) {
res = gst_sample_new (sink->priv->last_buffer,
sink->priv->last_caps, &sink->segment, NULL);
}
}
}
+/* with OBJECT_LOCK */
+static void
+gst_base_sink_set_last_buffer_list_unlocked (GstBaseSink * sink,
+ GstBufferList * buffer_list)
+{
+ GstBufferList *old;
+
+ old = sink->priv->last_buffer_list;
+ if (G_LIKELY (old != buffer_list)) {
+ GST_DEBUG_OBJECT (sink, "setting last buffer list to %p", buffer_list);
+ if (G_LIKELY (buffer_list))
+ gst_mini_object_ref (GST_MINI_OBJECT_CAST (buffer_list));
+ sink->priv->last_buffer_list = buffer_list;
+ } else {
+ old = NULL;
+ }
+
+ /* avoid unreffing with the lock because cleanup code might want to take the
+ * lock too */
+ if (G_LIKELY (old)) {
+ GST_OBJECT_UNLOCK (sink);
+ gst_mini_object_unref (GST_MINI_OBJECT_CAST (old));
+ GST_OBJECT_LOCK (sink);
+ }
+}
+
static void
gst_base_sink_set_last_buffer (GstBaseSink * sink, GstBuffer * buffer)
{
GST_OBJECT_UNLOCK (sink);
}
+static void
+gst_base_sink_set_last_buffer_list (GstBaseSink * sink,
+ GstBufferList * buffer_list)
+{
+ if (!g_atomic_int_get (&sink->priv->enable_last_sample))
+ return;
+
+ GST_OBJECT_LOCK (sink);
+ gst_base_sink_set_last_buffer_list_unlocked (sink, buffer_list);
+ GST_OBJECT_UNLOCK (sink);
+}
+
/**
* gst_base_sink_set_last_sample_enabled:
* @sink: the sink
!enabled, enabled) && !enabled) {
GST_OBJECT_LOCK (sink);
gst_base_sink_set_last_buffer_unlocked (sink, NULL);
+ gst_base_sink_set_last_buffer_list_unlocked (sink, NULL);
GST_OBJECT_UNLOCK (sink);
}
}
* Checks if @sink is currently configured to store the last received sample in
* the last-sample property.
*
- * Returns: TRUE if the sink is configured to store the last received sample.
+ * Returns: %TRUE if the sink is configured to store the last received sample.
*/
gboolean
gst_base_sink_is_last_sample_enabled (GstBaseSink * sink)
* @max_latency: (out) (allow-none): the max latency of the upstream elements
*
* Query the sink for the latency parameters. The latency will be queried from
- * the upstream elements. @live will be TRUE if @sink is configured to
- * synchronize against the clock. @upstream_live will be TRUE if an upstream
+ * the upstream elements. @live will be %TRUE if @sink is configured to
+ * synchronize against the clock. @upstream_live will be %TRUE if an upstream
* element is live.
*
- * If both @live and @upstream_live are TRUE, the sink will want to compensate
+ * If both @live and @upstream_live are %TRUE, the sink will want to compensate
* for the latency introduced by the upstream elements by setting the
- * @min_latency to a strictly possitive value.
+ * @min_latency to a strictly positive value.
*
* This function is mostly used by subclasses.
*
- * Returns: TRUE if the query succeeded.
+ * Returns: %TRUE if the query succeeded.
*/
gboolean
gst_base_sink_query_latency (GstBaseSink * sink, gboolean * live,
GstClockTime * max_latency)
{
gboolean l, us_live, res, have_latency;
- GstClockTime min, max, render_delay;
+ GstClockTime min, max, render_delay, processing_deadline;
GstQuery *query;
GstClockTime us_min, us_max;
l = sink->sync;
have_latency = sink->priv->have_latency;
render_delay = sink->priv->render_delay;
+ processing_deadline = sink->priv->processing_deadline;
GST_OBJECT_UNLOCK (sink);
/* assume no latency */
* values to create the complete latency. */
min = us_min;
max = us_max;
+
+ if (l) {
+ if (max == -1 || min + processing_deadline <= max)
+ min += processing_deadline;
+ else {
+ GST_ELEMENT_WARNING (sink, CORE, CLOCK,
+ (_("Pipeline construction is invalid, please add queues.")),
+ ("Not enough buffering available for "
+ " the processing deadline of %" GST_TIME_FORMAT
+ ", add enough queues to buffer %" GST_TIME_FORMAT
+ " additional data. Shortening processing latency to %"
+ GST_TIME_FORMAT ".",
+ GST_TIME_ARGS (processing_deadline),
+ GST_TIME_ARGS (min + processing_deadline - max),
+ GST_TIME_ARGS (max - min)));
+ min = max;
+ }
+ }
}
if (l) {
/* we need to add the render delay if we are live */
- if (min != -1)
- min += render_delay;
+ min += render_delay;
if (max != -1)
max += render_delay;
}
* Set the number of bytes that the sink will pull when it is operating in pull
* mode.
*/
-/* FIXME 0.11: blocksize property should be int, otherwise min>max.. */
+/* FIXME 2.0: blocksize property should be int, otherwise min>max.. */
void
gst_base_sink_set_blocksize (GstBaseSink * sink, guint blocksize)
{
*
* Returns: the number of bytes @sink will pull in pull mode.
*/
-/* FIXME 0.11: blocksize property should be int, otherwise min>max.. */
+/* FIXME 2.0: blocksize property should be int, otherwise min>max.. */
guint
gst_base_sink_get_blocksize (GstBaseSink * sink)
{
*
* Set the time that will be inserted between rendered buffers. This
* can be used to control the maximum buffers per second that the sink
- * will render.
+ * will render.
*/
void
gst_base_sink_set_throttle_time (GstBaseSink * sink, guint64 throttle)
* gst_base_sink_get_throttle_time:
* @sink: a #GstBaseSink
*
- * Get the time that will be inserted between frames to control the
+ * Get the time that will be inserted between frames to control the
* maximum buffers per second.
*
* Returns: the number of nanoseconds @sink will put between frames.
return res;
}
+/**
+ * gst_base_sink_set_max_bitrate:
+ * @sink: a #GstBaseSink
+ * @max_bitrate: the max_bitrate in bits per second
+ *
+ * Set the maximum amount of bits per second that the sink will render.
+ *
+ * Since: 1.2
+ */
+void
+gst_base_sink_set_max_bitrate (GstBaseSink * sink, guint64 max_bitrate)
+{
+ g_return_if_fail (GST_IS_BASE_SINK (sink));
+
+ GST_OBJECT_LOCK (sink);
+ sink->priv->max_bitrate = max_bitrate;
+ GST_LOG_OBJECT (sink, "set max_bitrate to %" G_GUINT64_FORMAT, max_bitrate);
+ GST_OBJECT_UNLOCK (sink);
+}
+
+/**
+ * gst_base_sink_get_max_bitrate:
+ * @sink: a #GstBaseSink
+ *
+ * Get the maximum amount of bits per second that the sink will render.
+ *
+ * Returns: the maximum number of bits per second @sink will render.
+ *
+ * Since: 1.2
+ */
+guint64
+gst_base_sink_get_max_bitrate (GstBaseSink * sink)
+{
+ guint64 res;
+
+ g_return_val_if_fail (GST_IS_BASE_SINK (sink), 0);
+
+ GST_OBJECT_LOCK (sink);
+ res = sink->priv->max_bitrate;
+ GST_OBJECT_UNLOCK (sink);
+
+ return res;
+}
+
+/**
+ * gst_base_sink_set_processing_deadline:
+ * @sink: a #GstBaseSink
+ * @processing_deadline: the new processing deadline in nanoseconds.
+ *
+ * Maximum amount of time (in nanoseconds) that the pipeline can take
+ * for processing the buffer. This is added to the latency of live
+ * pipelines.
+ *
+ * This function is usually called by subclasses.
+ *
+ * Since: 1.16
+ */
+void
+gst_base_sink_set_processing_deadline (GstBaseSink * sink,
+ GstClockTime processing_deadline)
+{
+ GstClockTime old_processing_deadline;
+
+ g_return_if_fail (GST_IS_BASE_SINK (sink));
+
+ GST_OBJECT_LOCK (sink);
+ old_processing_deadline = sink->priv->processing_deadline;
+ sink->priv->processing_deadline = processing_deadline;
+ GST_LOG_OBJECT (sink, "set render processing_deadline to %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (processing_deadline));
+ GST_OBJECT_UNLOCK (sink);
+
+ if (processing_deadline != old_processing_deadline) {
+ GST_DEBUG_OBJECT (sink, "posting latency changed");
+ gst_element_post_message (GST_ELEMENT_CAST (sink),
+ gst_message_new_latency (GST_OBJECT_CAST (sink)));
+ }
+}
+
+/**
+ * gst_base_sink_get_processing_deadline:
+ * @sink: a #GstBaseSink
+ *
+ * Get the processing deadline of @sink. see
+ * gst_base_sink_set_processing_deadline() for more information about
+ * the processing deadline.
+ *
+ * Returns: the processing deadline
+ *
+ * Since: 1.16
+ */
+GstClockTime
+gst_base_sink_get_processing_deadline (GstBaseSink * sink)
+{
+ GstClockTimeDiff res;
+
+ g_return_val_if_fail (GST_IS_BASE_SINK (sink), 0);
+
+ GST_OBJECT_LOCK (sink);
+ res = sink->priv->processing_deadline;
+ GST_OBJECT_UNLOCK (sink);
+
+ return res;
+}
+
static void
gst_base_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
case PROP_THROTTLE_TIME:
gst_base_sink_set_throttle_time (sink, g_value_get_uint64 (value));
break;
+ case PROP_MAX_BITRATE:
+ gst_base_sink_set_max_bitrate (sink, g_value_get_uint64 (value));
+ break;
+ case PROP_PROCESSING_DEADLINE:
+ gst_base_sink_set_processing_deadline (sink, g_value_get_uint64 (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
g_value_set_int64 (value, gst_base_sink_get_ts_offset (sink));
break;
case PROP_LAST_SAMPLE:
- gst_value_take_buffer (value, gst_base_sink_get_last_sample (sink));
+ gst_value_take_sample (value, gst_base_sink_get_last_sample (sink));
break;
case PROP_ENABLE_LAST_SAMPLE:
g_value_set_boolean (value, gst_base_sink_is_last_sample_enabled (sink));
case PROP_THROTTLE_TIME:
g_value_set_uint64 (value, gst_base_sink_get_throttle_time (sink));
break;
+ case PROP_MAX_BITRATE:
+ g_value_set_uint64 (value, gst_base_sink_get_max_bitrate (sink));
+ break;
+ case PROP_PROCESSING_DEADLINE:
+ g_value_set_uint64 (value, gst_base_sink_get_processing_deadline (sink));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
switch (pending) {
case GST_STATE_PLAYING:
{
- GST_DEBUG_OBJECT (basesink, "commiting state to PLAYING");
+ GST_DEBUG_OBJECT (basesink, "committing state to PLAYING");
basesink->need_preroll = FALSE;
post_async_done = TRUE;
- basesink->priv->commited = TRUE;
+ basesink->priv->committed = TRUE;
post_playing = TRUE;
/* post PAUSED too when we were READY */
if (current == GST_STATE_READY) {
break;
}
case GST_STATE_PAUSED:
- GST_DEBUG_OBJECT (basesink, "commiting state to PAUSED");
+ GST_DEBUG_OBJECT (basesink, "committing state to PAUSED");
post_paused = TRUE;
post_async_done = TRUE;
- basesink->priv->commited = TRUE;
+ basesink->priv->committed = TRUE;
post_pending = GST_STATE_VOID_PENDING;
break;
case GST_STATE_READY:
GST_CLOCK_TIME_NONE));
}
if (post_playing) {
+ if (post_paused) {
+ GstElementClass *klass;
+
+ klass = GST_ELEMENT_GET_CLASS (basesink);
+ basesink->have_preroll = TRUE;
+ /* after releasing this lock, the state change function
+ * can execute concurrently with this thread. There is nothing we do to
+ * prevent this for now. subclasses should be prepared to handle it. */
+ GST_BASE_SINK_PREROLL_UNLOCK (basesink);
+
+ if (klass->change_state)
+ klass->change_state (GST_ELEMENT_CAST (basesink),
+ GST_STATE_CHANGE_PAUSED_TO_PLAYING);
+
+ GST_BASE_SINK_PREROLL_LOCK (basesink);
+ /* state change function could have been executed and we could be
+ * flushing now */
+ if (G_UNLIKELY (basesink->flushing))
+ goto stopping_unlocked;
+ }
GST_DEBUG_OBJECT (basesink, "posting PLAYING state change message");
+ /* FIXME, we released the PREROLL lock above, it's possible that this
+ * message is not correct anymore when the element went back to PAUSED */
gst_element_post_message (GST_ELEMENT_CAST (basesink),
gst_message_new_state_changed (GST_OBJECT_CAST (basesink),
next, pending, GST_STATE_VOID_PENDING));
/* Depending on the state, set our vars. We get in this situation when the
* state change function got a change to update the state vars before the
* streaming thread did. This is fine but we need to make sure that we
- * update the need_preroll var since it was TRUE when we got here and might
- * become FALSE if we got to PLAYING. */
+ * update the need_preroll var since it was %TRUE when we got here and might
+ * become %FALSE if we got to PLAYING. */
GST_DEBUG_OBJECT (basesink, "nothing to commit, now in %s",
gst_element_state_get_name (current));
switch (current) {
GST_OBJECT_UNLOCK (basesink);
return TRUE;
}
+stopping_unlocked:
+ {
+ GST_OBJECT_LOCK (basesink);
+ goto stopping;
+ }
stopping:
{
/* app is going to READY */
/* update the segment clipping regions for non-flushing seeks */
if (segment->rate > 0.0) {
if (end != -1)
- position = gst_segment_to_position (segment, GST_FORMAT_TIME, end);
+ position =
+ gst_segment_position_from_running_time (segment, GST_FORMAT_TIME,
+ end);
else
position = segment->stop;
segment->position = position;
} else {
if (end != -1)
- position = gst_segment_to_position (segment, GST_FORMAT_TIME, end);
+ position =
+ gst_segment_position_from_running_time (segment, GST_FORMAT_TIME,
+ end);
else
position = segment->start;
step_end = TRUE;
if (segment->rate > 0.0) {
*rstart = end;
- *cstart = gst_segment_to_position (segment, GST_FORMAT_TIME, end);
+ *cstart =
+ gst_segment_position_from_running_time (segment, GST_FORMAT_TIME,
+ end);
} else {
*rstop = end;
- *cstop = gst_segment_to_position (segment, GST_FORMAT_TIME, end);
+ *cstop =
+ gst_segment_position_from_running_time (segment, GST_FORMAT_TIME,
+ end);
}
}
GST_DEBUG_OBJECT (sink,
/* with STREAM_LOCK, PREROLL_LOCK
*
- * Returns TRUE if the object needs synchronisation and takes therefore
+ * Returns %TRUE if the object needs synchronisation and takes therefore
* part in prerolling.
*
* rsstart/rsstop contain the start/stop in stream time.
static gboolean
gst_base_sink_get_sync_times (GstBaseSink * basesink, GstMiniObject * obj,
GstClockTime * rsstart, GstClockTime * rsstop,
- GstClockTime * rrstart, GstClockTime * rrstop, gboolean * do_sync,
- gboolean * stepped, GstStepInfo * step, gboolean * step_end)
+ GstClockTime * rrstart, GstClockTime * rrstop, GstClockTime * rrnext,
+ gboolean * do_sync, gboolean * stepped, GstStepInfo * step,
+ gboolean * step_end)
{
GstBaseSinkClass *bclass;
GstClockTime start, stop; /* raw start/stop timestamps */
guint64 cstart, cstop; /* clipped raw timestamps */
- guint64 rstart, rstop; /* clipped timestamps converted to running time */
+ guint64 rstart, rstop, rnext; /* clipped timestamps converted to running time */
GstClockTime sstart, sstop; /* clipped timestamps converted to stream time */
GstFormat format;
GstBaseSinkPrivate *priv;
}
}
- rstart = rstop = priv->eos_rtime;
- *do_sync = rstart != -1;
+ rstart = rstop = rnext = priv->eos_rtime;
+ *do_sync = GST_CLOCK_TIME_IS_VALID (rstart);
GST_DEBUG_OBJECT (basesink, "sync times for EOS %" GST_TIME_FORMAT,
GST_TIME_ARGS (rstart));
/* if we are stepping, we end now */
/* else do buffer sync code */
GstBuffer *buffer = GST_BUFFER_CAST (obj);
- /* just get the times to see if we need syncing, if the start returns -1 we
- * don't sync. */
+ /* just get the times to see if we need syncing, if the retuned start is -1
+ * we don't sync. */
if (bclass->get_times)
bclass->get_times (basesink, buffer, &start, &stop);
rstart = gst_segment_to_running_time (segment, format, cstart);
rstop = gst_segment_to_running_time (segment, format, cstop);
+ if (GST_CLOCK_TIME_IS_VALID (stop))
+ rnext = rstop;
+ else
+ rnext = rstart;
+
if (G_UNLIKELY (step->valid)) {
if (!(*step_end = handle_stepping (basesink, segment, step, &cstart, &cstop,
&rstart, &rstop))) {
*rsstop = sstop;
*rrstart = rstart;
*rrstop = rstop;
+ *rrnext = rnext;
/* buffers and EOS always need syncing and preroll */
return TRUE;
out_of_segment:
{
/* we usually clip in the chain function already but stepping could cause
- * the segment to be updated later. we return FALSE so that we don't try
+ * the segment to be updated later. we return %FALSE so that we don't try
* to sync on it. */
GST_LOG_OBJECT (basesink, "buffer skipped, not in segment");
return FALSE;
time += basesink->priv->latency;
- /* apply offset, be carefull for underflows */
+ /* apply offset, be careful for underflows */
ts_offset = basesink->priv->ts_offset;
if (ts_offset < 0) {
ts_offset = -ts_offset;
* gst_base_sink_wait_clock:
* @sink: the sink
* @time: the running_time to be reached
- * @jitter: (out) (allow-none): the jitter to be filled with time diff, or NULL
+ * @jitter: (out) (allow-none): the jitter to be filled with time diff, or %NULL
*
* This function will block until @time is reached. It is usually called by
* subclasses that use their own internal synchronisation.
*
- * If @time is not valid, no sycnhronisation is done and #GST_CLOCK_BADTIME is
+ * If @time is not valid, no synchronisation is done and %GST_CLOCK_BADTIME is
* returned. Likewise, if synchronisation is disabled in the element or there
- * is no clock, no synchronisation is done and #GST_CLOCK_BADTIME is returned.
+ * is no clock, no synchronisation is done and %GST_CLOCK_BADTIME is returned.
*
* This function should only be called with the PREROLL_LOCK held, like when
* receiving an EOS event in the #GstBaseSinkClass.event() vmethod or when
time += base_time;
/* Re-use existing clockid if available */
- /* FIXME: Casting to GstClockEntry only works because the types
- * are the same */
if (G_LIKELY (sink->priv->cached_clock_id != NULL
- && GST_CLOCK_ENTRY_CLOCK ((GstClockEntry *) sink->priv->
- cached_clock_id) == clock)) {
+ && gst_clock_id_uses_clock (sink->priv->cached_clock_id, clock))) {
if (!gst_clock_single_shot_id_reinit (clock, sink->priv->cached_clock_id,
time)) {
gst_clock_id_unref (sink->priv->cached_clock_id);
* against the clock it must unblock when going from PLAYING to the PAUSED state
* and call this method before continuing to render the remaining data.
*
+ * If the #GstBaseSinkClass.render() method can block on something else than
+ * the clock, it must also be ready to unblock immediately on
+ * the #GstBaseSinkClass.unlock() method and cause the
+ * #GstBaseSinkClass.render() method to immediately call this function.
+ * In this case, the subclass must be prepared to continue rendering where it
+ * left off if this function returns %GST_FLOW_OK.
+ *
* This function will block until a state change to PLAYING happens (in which
- * case this function returns #GST_FLOW_OK) or the processing must be stopped due
+ * case this function returns %GST_FLOW_OK) or the processing must be stopped due
* to a state change to READY or a FLUSH event (in which case this function
- * returns #GST_FLOW_FLUSHING).
+ * returns %GST_FLOW_FLUSHING).
*
* This function should only be called with the PREROLL_LOCK held, like in the
* render function.
*
- * Returns: #GST_FLOW_OK if the preroll completed and processing can
+ * Returns: %GST_FLOW_OK if the preroll completed and processing can
* continue. Any other return value should be returned from the render vmethod.
*/
GstFlowReturn
*
* This function should be called with the PREROLL_LOCK held.
*
- * Returns: #GST_FLOW_OK if the preroll completed and processing can
+ * Returns: %GST_FLOW_OK if the preroll completed and processing can
* continue. Any other return value should be returned from the render vmethod.
*/
GstFlowReturn
if (GST_IS_BUFFER_LIST (obj)) {
buf = gst_buffer_list_get (GST_BUFFER_LIST_CAST (obj), 0);
+ gst_base_sink_set_last_buffer (sink, buf);
+ gst_base_sink_set_last_buffer_list (sink, GST_BUFFER_LIST_CAST (obj));
g_assert (NULL != buf);
} else if (GST_IS_BUFFER (obj)) {
buf = GST_BUFFER_CAST (obj);
/* For buffer lists do not set last buffer for now */
gst_base_sink_set_last_buffer (sink, buf);
- } else
+ gst_base_sink_set_last_buffer_list (sink, NULL);
+ } else {
buf = NULL;
+ }
if (buf) {
GST_DEBUG_OBJECT (sink, "preroll buffer %" GST_TIME_FORMAT,
}
stopping:
{
- GST_DEBUG_OBJECT (sink, "stopping while commiting state");
+ GST_DEBUG_OBJECT (sink, "stopping while committing state");
return GST_FLOW_FLUSHING;
}
preroll_failed:
* gst_base_sink_wait:
* @sink: the sink
* @time: the running_time to be reached
- * @jitter: (out) (allow-none): the jitter to be filled with time diff, or NULL
+ * @jitter: (out) (allow-none): the jitter to be filled with time diff, or %NULL
*
* This function will wait for preroll to complete and will then block until @time
* is reached. It is usually called by subclasses that use their own internal
goto flushing;
/* retry if we got unscheduled, which means we did not reach the timeout
- * yet. if some other error occures, we continue. */
+ * yet. if some other error occurs, we continue. */
} while (status == GST_CLOCK_UNSCHEDULED);
GST_DEBUG_OBJECT (sink, "end of stream");
* immediately returns GST_FLOW_OK.
*
* for objects that arrive later than max-lateness to be synchronized to the
- * clock have the @late boolean set to TRUE.
+ * clock have the @late boolean set to %TRUE.
*
* This function keeps a running average of the jitter (the diff between the
* clock time and the requested sync time). The jitter is negative for
GstClockTimeDiff jitter = 0;
gboolean syncable;
GstClockReturn status = GST_CLOCK_OK;
- GstClockTime rstart, rstop, sstart, sstop, stime;
+ GstClockTime rstart, rstop, rnext, sstart, sstop, stime;
gboolean do_sync;
GstBaseSinkPrivate *priv;
GstFlowReturn ret;
priv = basesink->priv;
do_step:
- sstart = sstop = rstart = rstop = GST_CLOCK_TIME_NONE;
+ sstart = sstop = rstart = rstop = rnext = GST_CLOCK_TIME_NONE;
do_sync = TRUE;
stepped = FALSE;
/* get timing information for this object against the render segment */
syncable = gst_base_sink_get_sync_times (basesink, obj,
- &sstart, &sstop, &rstart, &rstop, &do_sync, &stepped, current, step_end);
+ &sstart, &sstop, &rstart, &rstop, &rnext, &do_sync, &stepped, current,
+ step_end);
if (G_UNLIKELY (stepped))
goto step_skipped;
priv->current_rstop = (GST_CLOCK_TIME_IS_VALID (rstop) ? rstop : rstart);
/* save sync time for eos when the previous object needed sync */
- priv->eos_rtime = (do_sync ? priv->current_rstop : GST_CLOCK_TIME_NONE);
+ priv->eos_rtime = (do_sync ? rnext : GST_CLOCK_TIME_NONE);
/* calculate inter frame spacing */
- if (G_UNLIKELY (priv->prev_rstart != -1 && priv->prev_rstart < rstart)) {
+ if (G_UNLIKELY (GST_CLOCK_TIME_IS_VALID (priv->prev_rstart) &&
+ priv->prev_rstart < rstart)) {
GstClockTime in_diff;
in_diff = rstart - priv->prev_rstart;
}
priv->prev_rstart = rstart;
- if (G_UNLIKELY (priv->earliest_in_time != -1
- && rstart < priv->earliest_in_time))
+ if (G_UNLIKELY (GST_CLOCK_TIME_IS_VALID (priv->earliest_in_time) &&
+ rstart < priv->earliest_in_time))
goto qos_dropped;
again:
/* adjust for latency */
stime = gst_base_sink_adjust_time (basesink, rstart);
+ /* adjust for rate control */
+ if (priv->rc_next == -1 || (stime != -1 && stime >= priv->rc_next)) {
+ GST_DEBUG_OBJECT (basesink, "reset rc_time to time %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (stime));
+ priv->rc_time = stime;
+ priv->rc_accumulated = 0;
+ } else {
+ GST_DEBUG_OBJECT (basesink, "rate control next %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (priv->rc_next));
+ stime = priv->rc_next;
+ }
+
/* preroll done, we can sync since we are in PLAYING now. */
GST_DEBUG_OBJECT (basesink, "possibly waiting for clock to reach %"
GST_TIME_FORMAT ", adjusted %" GST_TIME_FORMAT,
/* check if the object should be dropped */
*late = gst_base_sink_is_too_late (basesink, obj, rstart, rstop,
- status, jitter);
+ status, jitter, TRUE);
done:
return GST_FLOW_OK;
left = start + jitter;
}
- /* calculate duration of the buffer */
- if (GST_CLOCK_TIME_IS_VALID (stop) && stop != start)
- duration = stop - start;
- else
- duration = priv->avg_in_diff;
+ /* calculate duration of the buffer, only use buffer durations if not in
+ * trick mode or key-unit mode. Otherwise the buffer durations will be
+ * meaningless as frames are being dropped in-between without updating the
+ * durations. */
+ duration = priv->avg_in_diff;
/* if we have the time when the last buffer left us, calculate
* processing time */
GST_TIME_ARGS (entered), GST_TIME_ARGS (left), GST_TIME_ARGS (pt),
GST_TIME_ARGS (duration), jitter);
- GST_CAT_DEBUG_OBJECT (GST_CAT_QOS, sink, "avg_duration: %" GST_TIME_FORMAT
- ", avg_pt: %" GST_TIME_FORMAT ", avg_rate: %g",
- GST_TIME_ARGS (priv->avg_duration), GST_TIME_ARGS (priv->avg_pt),
- priv->avg_rate);
+ GST_CAT_DEBUG_OBJECT (GST_CAT_QOS, sink,
+ "avg_pt: %" GST_TIME_FORMAT ", avg_rate: %g",
+ GST_TIME_ARGS (priv->avg_pt), priv->avg_rate);
/* collect running averages. for first observations, we copy the
* values */
- if (!GST_CLOCK_TIME_IS_VALID (priv->avg_duration))
- priv->avg_duration = duration;
- else
- priv->avg_duration = UPDATE_RUNNING_AVG (priv->avg_duration, duration);
-
if (!GST_CLOCK_TIME_IS_VALID (priv->avg_pt))
priv->avg_pt = pt;
else
priv->avg_pt = UPDATE_RUNNING_AVG (priv->avg_pt, pt);
- if (priv->avg_duration != 0)
+ if (duration != -1 && duration != 0) {
rate =
gst_guint64_to_gdouble (priv->avg_pt) /
- gst_guint64_to_gdouble (priv->avg_duration);
- else
+ gst_guint64_to_gdouble (duration);
+ } else {
rate = 1.0;
+ }
if (GST_CLOCK_TIME_IS_VALID (priv->last_left)) {
if (dropped || priv->avg_rate < 0.0) {
}
GST_CAT_DEBUG_OBJECT (GST_CAT_QOS, sink,
- "updated: avg_duration: %" GST_TIME_FORMAT ", avg_pt: %" GST_TIME_FORMAT
- ", avg_rate: %g", GST_TIME_ARGS (priv->avg_duration),
- GST_TIME_ARGS (priv->avg_pt), priv->avg_rate);
+ "updated: avg_pt: %" GST_TIME_FORMAT
+ ", avg_rate: %g", GST_TIME_ARGS (priv->avg_pt), priv->avg_rate);
if (priv->avg_rate >= 0.0) {
priv->prev_rstart = GST_CLOCK_TIME_NONE;
priv->earliest_in_time = GST_CLOCK_TIME_NONE;
priv->last_left = GST_CLOCK_TIME_NONE;
- priv->avg_duration = GST_CLOCK_TIME_NONE;
priv->avg_pt = GST_CLOCK_TIME_NONE;
priv->avg_rate = -1.0;
- priv->avg_render = GST_CLOCK_TIME_NONE;
priv->avg_in_diff = GST_CLOCK_TIME_NONE;
priv->rendered = 0;
priv->dropped = 0;
*
* status and jitter contain the return values from the clock wait.
*
- * returns TRUE if the buffer was too late.
+ * returns %TRUE if the buffer was too late.
*/
static gboolean
gst_base_sink_is_too_late (GstBaseSink * basesink, GstMiniObject * obj,
GstClockTime rstart, GstClockTime rstop,
- GstClockReturn status, GstClockTimeDiff jitter)
+ GstClockReturn status, GstClockTimeDiff jitter, gboolean render)
{
gboolean late;
guint64 max_lateness;
}
done:
- if (!late || !GST_CLOCK_TIME_IS_VALID (priv->last_render_time)) {
+ if (render && (!late || !GST_CLOCK_TIME_IS_VALID (priv->last_render_time))) {
priv->last_render_time = rstart;
/* the next allowed input timestamp */
if (priv->throttle_time > 0)
}
}
-/* called before and after calling the render vmethod. It keeps track of how
- * much time was spent in the render method and is used to check if we are
- * flooded */
static void
-gst_base_sink_do_render_stats (GstBaseSink * basesink, gboolean start)
+gst_base_sink_update_start_time (GstBaseSink * basesink)
{
- GstBaseSinkPrivate *priv;
-
- priv = basesink->priv;
-
- if (start) {
- priv->start = gst_util_get_timestamp ();
- } else {
- GstClockTime elapsed;
+ GstClock *clock;
- priv->stop = gst_util_get_timestamp ();
+ GST_OBJECT_LOCK (basesink);
+ if (GST_STATE (basesink) == GST_STATE_PLAYING
+ && (clock = GST_ELEMENT_CLOCK (basesink))) {
+ GstClockTime now;
- elapsed = GST_CLOCK_DIFF (priv->start, priv->stop);
+ gst_object_ref (clock);
+ GST_OBJECT_UNLOCK (basesink);
- if (!GST_CLOCK_TIME_IS_VALID (priv->avg_render))
- priv->avg_render = elapsed;
- else
- priv->avg_render = UPDATE_RUNNING_AVG (priv->avg_render, elapsed);
+ /* calculate the time when we stopped */
+ now = gst_clock_get_time (clock);
+ gst_object_unref (clock);
- GST_CAT_DEBUG_OBJECT (GST_CAT_QOS, basesink,
- "avg_render: %" GST_TIME_FORMAT, GST_TIME_ARGS (priv->avg_render));
+ GST_OBJECT_LOCK (basesink);
+ /* store the current running time */
+ if (GST_ELEMENT_START_TIME (basesink) != GST_CLOCK_TIME_NONE) {
+ if (now != GST_CLOCK_TIME_NONE)
+ GST_ELEMENT_START_TIME (basesink) =
+ now - GST_ELEMENT_CAST (basesink)->base_time;
+ else
+ GST_WARNING_OBJECT (basesink,
+ "Clock %s returned invalid time, can't calculate "
+ "running_time when going to the PAUSED state",
+ GST_OBJECT_NAME (clock));
+ }
+ GST_DEBUG_OBJECT (basesink,
+ "start_time=%" GST_TIME_FORMAT ", now=%" GST_TIME_FORMAT
+ ", base_time %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (GST_ELEMENT_START_TIME (basesink)),
+ GST_TIME_ARGS (now),
+ GST_TIME_ARGS (GST_ELEMENT_CAST (basesink)->base_time));
}
+ GST_OBJECT_UNLOCK (basesink);
}
static void
* prerolled buffer */
basesink->playing_async = TRUE;
if (basesink->priv->async_enabled) {
+ gst_base_sink_update_start_time (basesink);
gst_element_lost_state (GST_ELEMENT_CAST (basesink));
} else {
/* start time reset in above case as well;
basesink->priv->have_latency = TRUE;
}
gst_base_sink_set_last_buffer (basesink, NULL);
+ gst_base_sink_set_last_buffer_list (basesink, NULL);
GST_PAD_STREAM_UNLOCK (pad);
}
basesink->have_newsegment = FALSE;
if (reset_time) {
gst_segment_init (&basesink->segment, GST_FORMAT_UNDEFINED);
+ GST_ELEMENT_START_TIME (basesink) = 0;
}
}
GST_OBJECT_UNLOCK (basesink);
gst_base_sink_default_wait_event (GstBaseSink * basesink, GstEvent * event)
{
GstFlowReturn ret;
- gboolean late, step_end;
+ gboolean late, step_end = FALSE;
ret = gst_base_sink_do_sync (basesink, GST_MINI_OBJECT_CAST (event),
&late, &step_end);
{
GstMessage *message;
guint32 seqnum;
+ guint group_id;
seqnum = gst_event_get_seqnum (event);
GST_DEBUG_OBJECT (basesink, "Now posting STREAM_START (seqnum:%d)",
seqnum);
message = gst_message_new_stream_start (GST_OBJECT_CAST (basesink));
+ if (gst_event_parse_group_id (event, &group_id)) {
+ gst_message_set_group_id (message, group_id);
+ } else {
+ GST_FIXME_OBJECT (basesink, "stream-start event without group-id. "
+ "Consider implementing group-id handling in the upstream "
+ "elements");
+ }
gst_message_set_seqnum (message, seqnum);
gst_element_post_message (GST_ELEMENT_CAST (basesink), message);
break;
}
case GST_EVENT_CAPS:
{
- GstCaps *caps;
+ GstCaps *caps, *current_caps;
GST_DEBUG_OBJECT (basesink, "caps %p", event);
gst_event_parse_caps (event, &caps);
- if (bclass->set_caps)
- result = bclass->set_caps (basesink, caps);
+ current_caps = gst_pad_get_current_caps (GST_BASE_SINK_PAD (basesink));
- if (result) {
- GST_OBJECT_LOCK (basesink);
- gst_caps_replace (&basesink->priv->caps, caps);
- GST_OBJECT_UNLOCK (basesink);
+ if (current_caps && gst_caps_is_equal (current_caps, caps)) {
+ GST_DEBUG_OBJECT (basesink,
+ "New caps equal to old ones: %" GST_PTR_FORMAT, caps);
+ } else {
+ if (bclass->set_caps)
+ result = bclass->set_caps (basesink, caps);
+
+ if (result) {
+ GST_OBJECT_LOCK (basesink);
+ gst_caps_replace (&basesink->priv->caps, caps);
+ GST_OBJECT_UNLOCK (basesink);
+ }
}
+ if (current_caps)
+ gst_caps_unref (current_caps);
break;
}
case GST_EVENT_SEGMENT:
/* the newsegment event is needed to bring the buffer timestamps to the
* stream time and to drop samples outside of the playback segment. */
gst_event_copy_segment (event, &basesink->segment);
- GST_DEBUG_OBJECT (basesink, "configured SEGMENT %" GST_SEGMENT_FORMAT,
+ GST_DEBUG_OBJECT (basesink, "configured segment %" GST_SEGMENT_FORMAT,
&basesink->segment);
basesink->have_newsegment = TRUE;
+ gst_base_sink_reset_qos (basesink);
GST_OBJECT_UNLOCK (basesink);
break;
case GST_EVENT_GAP:
{
GstBaseSinkClass *bclass;
GstBaseSinkPrivate *priv = basesink->priv;
- GstFlowReturn ret;
+ GstFlowReturn ret = GST_FLOW_OK;
GstClockTime start = GST_CLOCK_TIME_NONE, end = GST_CLOCK_TIME_NONE;
GstSegment *segment;
GstBuffer *sync_buf;
- gint do_qos;
- gboolean late, step_end;
+ gboolean late, step_end, prepared = FALSE;
if (G_UNLIKELY (basesink->flushing))
goto flushing;
goto was_eos;
if (is_list) {
- sync_buf = gst_buffer_list_get (GST_BUFFER_LIST_CAST (obj), 0);
+ GstBufferList *buffer_list = GST_BUFFER_LIST_CAST (obj);
+
+ if (gst_buffer_list_length (buffer_list) == 0)
+ goto empty_list;
+
+ sync_buf = gst_buffer_list_get (buffer_list, 0);
g_assert (NULL != sync_buf);
} else {
sync_buf = GST_BUFFER_CAST (obj);
GST_DEBUG_OBJECT (basesink, "got times start: %" GST_TIME_FORMAT
", end: %" GST_TIME_FORMAT, GST_TIME_ARGS (start), GST_TIME_ARGS (end));
- /* a dropped buffer does not participate in anything */
+ /* a dropped buffer does not participate in anything. Buffer can only be
+ * dropped if their PTS falls completely outside the segment, while we sync
+ * preferably on DTS */
if (GST_CLOCK_TIME_IS_VALID (start) && (segment->format == GST_FORMAT_TIME)) {
+ GstClockTime pts = GST_BUFFER_PTS (sync_buf);
+ GstClockTime pts_end = GST_CLOCK_TIME_NONE;
+
+ if (!GST_CLOCK_TIME_IS_VALID (pts))
+ pts = start;
+
+ if (GST_CLOCK_TIME_IS_VALID (end))
+ pts_end = pts + (end - start);
+
if (G_UNLIKELY (!gst_segment_clip (segment,
- GST_FORMAT_TIME, start, end, NULL, NULL)))
+ GST_FORMAT_TIME, pts, pts_end, NULL, NULL)
+ && priv->drop_out_of_segment))
goto out_of_segment;
}
- if (!is_list) {
- if (bclass->prepare) {
- ret = bclass->prepare (basesink, GST_BUFFER_CAST (obj));
- if (G_UNLIKELY (ret != GST_FLOW_OK))
- goto prepare_failed;
+ if (bclass->prepare || bclass->prepare_list) {
+ gboolean do_sync = TRUE, stepped = FALSE, syncable = TRUE;
+ GstClockTime sstart, sstop, rstart, rstop, rnext;
+ GstStepInfo *current;
+
+ late = FALSE;
+ step_end = FALSE;
+
+ current = &priv->current_step;
+ syncable =
+ gst_base_sink_get_sync_times (basesink, obj, &sstart, &sstop, &rstart,
+ &rstop, &rnext, &do_sync, &stepped, current, &step_end);
+
+ if (G_UNLIKELY (stepped))
+ goto dropped;
+
+ if (syncable && do_sync && gst_base_sink_get_sync (basesink)) {
+ GstClock *clock;
+
+ GST_OBJECT_LOCK (basesink);
+ clock = GST_ELEMENT_CLOCK (basesink);
+ if (clock && GST_STATE (basesink) == GST_STATE_PLAYING) {
+ GstClockTime base_time;
+ GstClockTime stime;
+ GstClockTime now;
+
+ base_time = GST_ELEMENT_CAST (basesink)->base_time;
+ stime = base_time + gst_base_sink_adjust_time (basesink, rstart);
+ now = gst_clock_get_time (clock);
+ GST_OBJECT_UNLOCK (basesink);
+
+ late =
+ gst_base_sink_is_too_late (basesink, obj, rstart, rstop,
+ GST_CLOCK_EARLY, GST_CLOCK_DIFF (stime, now), FALSE);
+ } else {
+ GST_OBJECT_UNLOCK (basesink);
+ }
}
- } else {
- if (bclass->prepare_list) {
- ret = bclass->prepare_list (basesink, GST_BUFFER_LIST_CAST (obj));
- if (G_UNLIKELY (ret != GST_FLOW_OK))
- goto prepare_failed;
+
+ /* We are about to prepare the first frame, make sure we have prerolled
+ * already. This prevent nesting prepare/render calls. */
+ ret = gst_base_sink_do_preroll (basesink, obj);
+ if (G_UNLIKELY (ret != GST_FLOW_OK))
+ goto preroll_failed;
+
+ if (G_UNLIKELY (late))
+ goto dropped;
+
+ if (!is_list) {
+ if (bclass->prepare) {
+ ret = bclass->prepare (basesink, GST_BUFFER_CAST (obj));
+ if (G_UNLIKELY (ret != GST_FLOW_OK))
+ goto prepare_failed;
+ }
+ } else {
+ if (bclass->prepare_list) {
+ ret = bclass->prepare_list (basesink, GST_BUFFER_LIST_CAST (obj));
+ if (G_UNLIKELY (ret != GST_FLOW_OK))
+ goto prepare_failed;
+ }
}
+
+ prepared = TRUE;
}
again:
if (G_UNLIKELY (ret != GST_FLOW_OK))
goto sync_failed;
+ /* Don't skip if prepare() was called on time */
+ late = late && !prepared;
+
/* drop late buffers unconditionally, let's hope it's unlikely */
if (G_UNLIKELY (late))
goto dropped;
- /* read once, to get same value before and after */
- do_qos = g_atomic_int_get (&priv->qos_enabled);
+ if (priv->max_bitrate) {
+ gsize size;
- GST_DEBUG_OBJECT (basesink, "rendering object %p", obj);
+ if (is_list)
+ size = gst_buffer_list_calculate_size (GST_BUFFER_LIST_CAST (obj));
+ else
+ size = gst_buffer_get_size (GST_BUFFER_CAST (obj));
+
+ priv->rc_accumulated += size;
+ priv->rc_next = priv->rc_time + gst_util_uint64_scale (priv->rc_accumulated,
+ 8 * GST_SECOND, priv->max_bitrate);
+ }
- /* record rendering time for QoS and stats */
- if (do_qos)
- gst_base_sink_do_render_stats (basesink, TRUE);
+ GST_DEBUG_OBJECT (basesink, "rendering object %p", obj);
if (!is_list) {
/* For buffer lists do not set last buffer for now. */
gst_base_sink_set_last_buffer (basesink, GST_BUFFER_CAST (obj));
+ gst_base_sink_set_last_buffer_list (basesink, NULL);
if (bclass->render)
ret = bclass->render (basesink, GST_BUFFER_CAST (obj));
} else {
+ GstBufferList *buffer_list = GST_BUFFER_LIST_CAST (obj);
+
if (bclass->render_list)
- ret = bclass->render_list (basesink, GST_BUFFER_LIST_CAST (obj));
- }
+ ret = bclass->render_list (basesink, buffer_list);
- if (do_qos)
- gst_base_sink_do_render_stats (basesink, FALSE);
+ /* Set the first buffer and buffer list to be included in last sample */
+ gst_base_sink_set_last_buffer (basesink, sync_buf);
+ gst_base_sink_set_last_buffer_list (basesink, buffer_list);
+ }
if (ret == GST_FLOW_STEP)
goto again;
gst_mini_object_unref (GST_MINI_OBJECT_CAST (obj));
return GST_FLOW_EOS;
}
+empty_list:
+ {
+ GST_DEBUG_OBJECT (basesink, "buffer list with no buffers");
+ gst_mini_object_unref (GST_MINI_OBJECT_CAST (obj));
+ return GST_FLOW_OK;
+ }
out_of_segment:
{
GST_DEBUG_OBJECT (basesink, "dropping buffer, out of clipping segment");
}
goto done;
}
+preroll_failed:
+ {
+ GST_DEBUG_OBJECT (basesink, "preroll failed: %s", gst_flow_get_name (ret));
+ gst_mini_object_unref (GST_MINI_OBJECT_CAST (obj));
+ return ret;
+ }
}
/* with STREAM_LOCK
guint i, len;
GstBuffer *buffer;
- GST_INFO_OBJECT (pad, "chaining each buffer in list");
+ GST_LOG_OBJECT (pad, "chaining each buffer in list");
len = gst_buffer_list_length (list);
res = gst_base_sink_default_do_seek (sink, &seeksegment);
}
-
if (flush) {
GST_DEBUG_OBJECT (sink, "stop flushing upstream");
gst_pad_push_event (pad, gst_event_new_flush_stop (TRUE));
res = FALSE;
}
+ GST_INFO_OBJECT (sink, "seeking done %d: %" GST_SEGMENT_FORMAT, res,
+ &seeksegment);
+
/* if successful seek, we update our real segment and push
* out the new segment. */
if (res) {
sink->playing_async = TRUE;
priv->pending_step.need_preroll = TRUE;
sink->need_preroll = FALSE;
+ gst_base_sink_update_start_time (sink);
gst_element_lost_state (GST_ELEMENT_CAST (sink));
} else {
sink->priv->have_latency = TRUE;
priv->eos_rtime = GST_CLOCK_TIME_NONE;
priv->call_preroll = TRUE;
gst_base_sink_set_last_buffer (sink, NULL);
+ gst_base_sink_set_last_buffer_list (sink, NULL);
gst_base_sink_reset_qos (sink);
if (sink->clock_id) {
}
} else if (result == GST_FLOW_NOT_LINKED || result <= GST_FLOW_EOS) {
/* for fatal errors we post an error message, post the error
- * first so the app knows about the error first.
+ * first so the app knows about the error first.
* wrong-state is not a fatal error because it happens due to
* flushing and posting an error message in that case is the
* wrong thing to do, e.g. when basesrc is doing a flushing
* seek. */
- GST_ELEMENT_ERROR (basesink, STREAM, FAILED,
- (_("Internal data stream error.")),
- ("stream stopped, reason %s", gst_flow_get_name (result)));
+ GST_ELEMENT_FLOW_ERROR (basesink, result);
gst_base_sink_event (pad, parent, gst_event_new_eos ());
}
return;
if (!gst_pad_peer_query (pad, query)) {
gst_query_unref (query);
- GST_DEBUG_OBJECT (basesink, "peer query faild, no pull mode");
+ GST_DEBUG_OBJECT (basesink, "peer query failed, no pull mode");
goto fallback;
}
result = FALSE;
/* this returns the intersection between our caps and the peer caps. If there
- * is no peer, it returns NULL and we can't operate in pull mode so we can
+ * is no peer, it returns %NULL and we can't operate in pull mode so we can
* fail the negotiation. */
caps = gst_pad_get_allowed_caps (GST_BASE_SINK_PAD (basesink));
if (caps == NULL || gst_caps_is_empty (caps))
}
if (forward) {
+ GST_DEBUG_OBJECT (basesink, "sending event %p %" GST_PTR_FORMAT, event,
+ event);
result = gst_pad_push_event (pad, event);
} else {
/* not forwarded, unref the event */
gst_object_unref (pad);
- GST_DEBUG_OBJECT (basesink, "handled event %p %" GST_PTR_FORMAT ": %d", event,
- event, result);
+ GST_DEBUG_OBJECT (basesink, "handled event: %d", result);
return result;
}
GstSegment *segment;
GstClockTime now, latency;
GstClockTimeDiff base_time;
- gint64 time, base, duration;
+ gint64 time, base, offset, duration;
gdouble rate;
gint64 last;
gboolean last_seen, with_clock, in_paused;
/* assume we will use the clock for getting the current position */
with_clock = TRUE;
- if (basesink->sync == FALSE)
+ if (!basesink->sync)
with_clock = FALSE;
/* and we need a clock */
else
time = 0;
+ if (GST_CLOCK_TIME_IS_VALID (segment->offset))
+ offset = segment->offset;
+ else
+ offset = 0;
+
if (GST_CLOCK_TIME_IS_VALID (segment->stop))
duration = segment->stop - segment->start;
else
rate = segment->rate * segment->applied_rate;
latency = basesink->priv->latency;
- if (oformat == GST_FORMAT_TIME) {
- gint64 start, stop;
-
- start = basesink->priv->current_sstart;
- stop = basesink->priv->current_sstop;
-
- if (in_paused) {
- /* in paused we use the last position as a lower bound */
- if (stop == -1 || segment->rate > 0.0)
- last = start;
- else
- last = stop;
- } else {
- /* in playing, use last stop time as upper bound */
- if (start == -1 || segment->rate > 0.0)
- last = stop;
- else
- last = start;
- }
- } else {
- /* convert last stop to stream time */
- last = gst_segment_to_stream_time (segment, oformat, segment->position);
- }
-
if (in_paused) {
/* in paused, use start_time */
base_time = GST_ELEMENT_START_TIME (basesink);
if (base_time == -1)
last_seen = TRUE;
+ if (oformat == GST_FORMAT_TIME) {
+ gint64 start, stop;
+
+ start = basesink->priv->current_sstart;
+ stop = basesink->priv->current_sstop;
+
+ if (last_seen) {
+ /* when we don't use the clock, we use the last position as a lower bound */
+ if (stop == -1 || segment->rate > 0.0)
+ last = start;
+ else
+ last = stop;
+
+ GST_DEBUG_OBJECT (basesink, "in PAUSED using last %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (last));
+ } else {
+ /* in playing and paused, use last stop time as upper bound */
+ if (start == -1 || segment->rate > 0.0)
+ last = stop;
+ else
+ last = start;
+
+ GST_DEBUG_OBJECT (basesink, "in PLAYING using last %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (last));
+ }
+ } else {
+ /* convert position to stream time */
+ last = gst_segment_to_stream_time (segment, oformat, segment->position);
+
+ GST_DEBUG_OBJECT (basesink, "in using last %" G_GINT64_FORMAT, last);
+ }
+
/* need to release the object lock before we can get the time,
* a clock might take the LOCK of the provider, which could be
* a basesink subclass. */
if (rate < 0.0)
time += duration;
- *cur = time + gst_guint64_to_gdouble (now - base_time) * rate;
+ *cur = time + offset + gst_guint64_to_gdouble (now - base_time) * rate;
- if (in_paused) {
- /* never report less than segment values in paused */
- if (last != -1)
- *cur = MAX (last, *cur);
- } else {
- /* never report more than last seen position in playing */
- if (last != -1)
+ /* never report more than last seen position */
+ if (last != -1) {
+ if (rate > 0.0)
*cur = MIN (last, *cur);
+ else
+ *cur = MAX (last, *cur);
}
GST_DEBUG_OBJECT (basesink,
case GST_QUERY_SEGMENT:
{
if (basesink->pad_mode == GST_PAD_MODE_PULL) {
- gst_query_set_segment (query, basesink->segment.rate,
- GST_FORMAT_TIME, basesink->segment.start, basesink->segment.stop);
+ GstFormat format;
+ gint64 start, stop;
+
+ format = basesink->segment.format;
+
+ start =
+ gst_segment_to_stream_time (&basesink->segment, format,
+ basesink->segment.start);
+ if ((stop = basesink->segment.stop) == -1)
+ stop = basesink->segment.duration;
+ else
+ stop = gst_segment_to_stream_time (&basesink->segment, format, stop);
+
+ gst_query_set_segment (query, basesink->segment.rate, format, start,
+ stop);
res = TRUE;
} else {
res = gst_pad_peer_query (basesink->sinkpad, query);
return res;
}
+static void
+gst_base_sink_drain (GstBaseSink * basesink)
+{
+ GstBuffer *old;
+ GstBufferList *old_list;
+
+ GST_OBJECT_LOCK (basesink);
+ if ((old = basesink->priv->last_buffer))
+ basesink->priv->last_buffer = gst_buffer_copy_deep (old);
+
+ if ((old_list = basesink->priv->last_buffer_list))
+ basesink->priv->last_buffer_list = gst_buffer_list_copy_deep (old_list);
+ GST_OBJECT_UNLOCK (basesink);
+
+ if (old)
+ gst_buffer_unref (old);
+ if (old_list)
+ gst_mini_object_unref (GST_MINI_OBJECT_CAST (old_list));
+}
static gboolean
gst_base_sink_default_query (GstBaseSink * basesink, GstQuery * query)
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_ALLOCATION:
{
+ gst_base_sink_drain (basesink);
if (bclass->propose_allocation)
res = bclass->propose_allocation (basesink, query);
else
gst_query_parse_accept_caps (query, &caps);
allowed = gst_base_sink_query_caps (basesink, basesink->sinkpad, NULL);
subset = gst_caps_is_subset (caps, allowed);
+ GST_DEBUG_OBJECT (basesink, "Checking if requested caps %" GST_PTR_FORMAT
+ " are a subset of pad caps %" GST_PTR_FORMAT " result %d", caps,
+ allowed, subset);
gst_caps_unref (allowed);
gst_query_set_accept_caps_result (query, subset);
res = TRUE;
break;
}
case GST_QUERY_DRAIN:
+ {
+ gst_base_sink_drain (basesink);
res = TRUE;
break;
+ }
+ case GST_QUERY_POSITION:
+ {
+ res = default_element_query (GST_ELEMENT (basesink), query);
+ break;
+ }
default:
res =
gst_pad_query_default (basesink->sinkpad, GST_OBJECT_CAST (basesink),
basesink->eos = FALSE;
priv->received_eos = FALSE;
gst_base_sink_reset_qos (basesink);
- priv->commited = FALSE;
+ priv->rc_next = -1;
+ priv->committed = FALSE;
priv->call_preroll = TRUE;
priv->current_step.valid = FALSE;
priv->pending_step.valid = FALSE;
basesink->need_preroll = TRUE;
basesink->playing_async = TRUE;
priv->call_preroll = TRUE;
- priv->commited = FALSE;
+ priv->committed = FALSE;
if (priv->async_enabled) {
GST_DEBUG_OBJECT (basesink, "doing async state change");
ret = GST_STATE_CHANGE_ASYNC;
if (bclass->unlock_stop)
bclass->unlock_stop (basesink);
- /* we need preroll again and we set the flag before unlocking the clockid
- * because if the clockid is unlocked before a current buffer expired, we
- * can use that buffer to preroll with */
- basesink->need_preroll = TRUE;
-
if (basesink->clock_id) {
GST_DEBUG_OBJECT (basesink, "unschedule clock");
gst_clock_id_unschedule (basesink->clock_id);
if (!gst_base_sink_needs_preroll (basesink)) {
GST_DEBUG_OBJECT (basesink, "PLAYING to PAUSED, we are prerolled");
basesink->playing_async = FALSE;
+ basesink->need_preroll = FALSE;
} else {
if (GST_STATE_TARGET (GST_ELEMENT (basesink)) <= GST_STATE_READY) {
GST_DEBUG_OBJECT (basesink, "element is <= READY");
GST_DEBUG_OBJECT (basesink,
"PLAYING to PAUSED, we are not prerolled");
basesink->playing_async = TRUE;
- priv->commited = FALSE;
+ basesink->need_preroll = TRUE;
+ priv->committed = FALSE;
priv->call_preroll = TRUE;
if (priv->async_enabled) {
GST_DEBUG_OBJECT (basesink, "doing async state change");
GST_OBJECT_UNLOCK (basesink);
gst_base_sink_set_last_buffer (basesink, NULL);
+ gst_base_sink_set_last_buffer_list (basesink, NULL);
priv->call_preroll = FALSE;
- if (!priv->commited) {
+ if (!priv->committed) {
if (priv->async_enabled) {
GST_DEBUG_OBJECT (basesink, "PAUSED to READY, posting async-done");
gst_message_new_async_done (GST_OBJECT_CAST (basesink),
GST_CLOCK_TIME_NONE));
}
- priv->commited = TRUE;
+ priv->committed = TRUE;
} else {
GST_DEBUG_OBJECT (basesink, "PAUSED to READY, don't need_preroll");
}
}
}
gst_base_sink_set_last_buffer (basesink, NULL);
+ gst_base_sink_set_last_buffer_list (basesink, NULL);
priv->call_preroll = FALSE;
break;
default:
start_failed:
{
GST_DEBUG_OBJECT (basesink, "failed to start");
+ /* subclass is supposed to post a message but we post one as a fallback
+ * just in case */
+ GST_ELEMENT_ERROR (basesink, CORE, STATE_CHANGE, (NULL),
+ ("Failed to start"));
return GST_STATE_CHANGE_FAILURE;
}
activate_failed: