gstreamer/plugins/elements/gstconcat.c
Sebastian Dröge 80c5dacfb3 concat: Reset last_stop on FLUSH_STOP too
Otherwise when seeking backwards we would keep the last_stop at the last
position we saw until playback passed the seek position again, and if
switching to the next pad happens in the meantime we would set the wrong
offset in the outgoing segment.
2019-08-08 13:12:19 +01:00

887 lines
26 KiB
C

/* GStreamer concat element
*
* Copyright (c) 2014 Sebastian Dröge <sebastian@centricular.com>
*
* 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:element-concat
* @title: concat
* @see_also: #GstFunnel
*
* Concatenates streams together to one continuous stream.
*
* All streams but the current one are blocked until the current one
* finished with %GST_EVENT_EOS. Then the next stream is enabled, while
* keeping the running time continuous for %GST_FORMAT_TIME segments or
* keeping the segment continuous for %GST_FORMAT_BYTES segments.
*
* Streams are switched in the order in which the sinkpads were requested.
*
* By default, the stream segment's base values are adjusted to ensure
* the segment transitions between streams are continuous. In some cases,
* it may be desirable to turn off these adjustments (for example, because
* another downstream element like a streamsynchronizer adjusts the base
* values on its own). The adjust-base property can be used for this purpose.
*
* ## Example launch line
* |[
* gst-launch-1.0 concat name=c ! xvimagesink videotestsrc num-buffers=100 ! c. videotestsrc num-buffers=100 pattern=ball ! c.
* ]| Plays two video streams one after another.
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstconcat.h"
GST_DEBUG_CATEGORY_STATIC (gst_concat_debug);
#define GST_CAT_DEFAULT gst_concat_debug
G_GNUC_INTERNAL GType gst_concat_pad_get_type (void);
#define GST_TYPE_CONCAT_PAD (gst_concat_pad_get_type())
#define GST_CONCAT_PAD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CONCAT_PAD, GstConcatPad))
#define GST_CONCAT_PAD_CAST(obj) ((GstConcatPad *)(obj))
#define GST_CONCAT_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CONCAT_PAD, GstConcatPadClass))
#define GST_IS_CONCAT_PAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CONCAT_PAD))
#define GST_IS_CONCAT_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CONCAT_PAD))
typedef struct _GstConcatPad GstConcatPad;
typedef struct _GstConcatPadClass GstConcatPadClass;
struct _GstConcatPad
{
GstPad parent;
GstSegment segment;
/* Protected by the concat lock */
gboolean flushing;
};
struct _GstConcatPadClass
{
GstPadClass parent;
};
G_DEFINE_TYPE (GstConcatPad, gst_concat_pad, GST_TYPE_PAD);
static void
gst_concat_pad_class_init (GstConcatPadClass * klass)
{
}
static void
gst_concat_pad_init (GstConcatPad * self)
{
gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
self->flushing = FALSE;
}
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink_%u",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
enum
{
PROP_0,
PROP_ACTIVE_PAD,
PROP_ADJUST_BASE
};
#define DEFAULT_ADJUST_BASE TRUE
#define _do_init \
GST_DEBUG_CATEGORY_INIT (gst_concat_debug, "concat", 0, "concat element");
#define gst_concat_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstConcat, gst_concat, GST_TYPE_ELEMENT, _do_init);
static void gst_concat_dispose (GObject * object);
static void gst_concat_finalize (GObject * object);
static void gst_concat_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static void gst_concat_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static GstStateChangeReturn gst_concat_change_state (GstElement * element,
GstStateChange transition);
static GstPad *gst_concat_request_new_pad (GstElement * element,
GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
static void gst_concat_release_pad (GstElement * element, GstPad * pad);
static GstFlowReturn gst_concat_sink_chain (GstPad * pad, GstObject * parent,
GstBuffer * buffer);
static gboolean gst_concat_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event);
static gboolean gst_concat_sink_query (GstPad * pad, GstObject * parent,
GstQuery * query);
static gboolean gst_concat_src_event (GstPad * pad, GstObject * parent,
GstEvent * event);
static gboolean gst_concat_src_query (GstPad * pad, GstObject * parent,
GstQuery * query);
static gboolean gst_concat_switch_pad (GstConcat * self);
static void gst_concat_notify_active_pad (GstConcat * self);
static GParamSpec *pspec_active_pad = NULL;
static void
gst_concat_class_init (GstConcatClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_concat_dispose);
gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_concat_finalize);
gobject_class->get_property = gst_concat_get_property;
gobject_class->set_property = gst_concat_set_property;
pspec_active_pad = g_param_spec_object ("active-pad", "Active pad",
"Currently active src pad", GST_TYPE_PAD, G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_property (gobject_class, PROP_ACTIVE_PAD,
pspec_active_pad);
g_object_class_install_property (gobject_class, PROP_ADJUST_BASE,
g_param_spec_boolean ("adjust-base", "Adjust segment base",
"Adjust the base value of segments to ensure they are adjacent",
DEFAULT_ADJUST_BASE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gst_element_class_set_static_metadata (gstelement_class,
"Concat", "Generic", "Concatenate multiple streams",
"Sebastian Dröge <sebastian@centricular.com>");
gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
gst_element_class_add_static_pad_template (gstelement_class, &src_template);
gstelement_class->request_new_pad =
GST_DEBUG_FUNCPTR (gst_concat_request_new_pad);
gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_concat_release_pad);
gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_concat_change_state);
}
static void
gst_concat_init (GstConcat * self)
{
g_mutex_init (&self->lock);
g_cond_init (&self->cond);
self->srcpad = gst_pad_new_from_static_template (&src_template, "src");
gst_pad_set_event_function (self->srcpad,
GST_DEBUG_FUNCPTR (gst_concat_src_event));
gst_pad_set_query_function (self->srcpad,
GST_DEBUG_FUNCPTR (gst_concat_src_query));
gst_pad_use_fixed_caps (self->srcpad);
gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
self->adjust_base = DEFAULT_ADJUST_BASE;
}
static void
gst_concat_dispose (GObject * object)
{
GstConcat *self = GST_CONCAT (object);
GList *item;
gst_object_replace ((GstObject **) & self->current_sinkpad, NULL);
restart:
for (item = GST_ELEMENT_PADS (object); item; item = g_list_next (item)) {
GstPad *pad = GST_PAD (item->data);
if (GST_PAD_IS_SINK (pad)) {
gst_element_release_request_pad (GST_ELEMENT (object), pad);
goto restart;
}
}
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_concat_finalize (GObject * object)
{
GstConcat *self = GST_CONCAT (object);
g_mutex_clear (&self->lock);
g_cond_clear (&self->cond);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_concat_get_property (GObject * object, guint prop_id, GValue * value,
GParamSpec * pspec)
{
GstConcat *self = GST_CONCAT (object);
switch (prop_id) {
case PROP_ACTIVE_PAD:{
g_mutex_lock (&self->lock);
g_value_set_object (value, self->current_sinkpad);
g_mutex_unlock (&self->lock);
break;
}
case PROP_ADJUST_BASE:{
g_mutex_lock (&self->lock);
g_value_set_boolean (value, self->adjust_base);
g_mutex_unlock (&self->lock);
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_concat_set_property (GObject * object, guint prop_id, const GValue * value,
GParamSpec * pspec)
{
GstConcat *self = GST_CONCAT (object);
switch (prop_id) {
case PROP_ADJUST_BASE:{
g_mutex_lock (&self->lock);
self->adjust_base = g_value_get_boolean (value);
g_mutex_unlock (&self->lock);
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstPad *
gst_concat_request_new_pad (GstElement * element, GstPadTemplate * templ,
const gchar * name, const GstCaps * caps)
{
GstConcat *self = GST_CONCAT (element);
GstPad *sinkpad;
gchar *pad_name;
gboolean do_notify = FALSE;
GST_DEBUG_OBJECT (element, "requesting pad");
g_mutex_lock (&self->lock);
pad_name = g_strdup_printf ("sink_%u", self->pad_count);
self->pad_count++;
g_mutex_unlock (&self->lock);
sinkpad = GST_PAD_CAST (g_object_new (GST_TYPE_CONCAT_PAD,
"name", pad_name, "direction", templ->direction, "template", templ,
NULL));
g_free (pad_name);
gst_pad_set_chain_function (sinkpad,
GST_DEBUG_FUNCPTR (gst_concat_sink_chain));
gst_pad_set_event_function (sinkpad,
GST_DEBUG_FUNCPTR (gst_concat_sink_event));
gst_pad_set_query_function (sinkpad,
GST_DEBUG_FUNCPTR (gst_concat_sink_query));
GST_OBJECT_FLAG_SET (sinkpad, GST_PAD_FLAG_PROXY_CAPS);
GST_OBJECT_FLAG_SET (sinkpad, GST_PAD_FLAG_PROXY_ALLOCATION);
gst_pad_set_active (sinkpad, TRUE);
g_mutex_lock (&self->lock);
self->sinkpads = g_list_prepend (self->sinkpads, gst_object_ref (sinkpad));
if (!self->current_sinkpad) {
do_notify = TRUE;
self->current_sinkpad = gst_object_ref (sinkpad);
}
g_mutex_unlock (&self->lock);
gst_element_add_pad (element, sinkpad);
if (do_notify)
gst_concat_notify_active_pad (self);
GST_DEBUG_OBJECT (sinkpad, "requested pad");
return sinkpad;
}
static void
gst_concat_release_pad (GstElement * element, GstPad * pad)
{
GstConcat *self = GST_CONCAT (element);
GstConcatPad *spad = GST_CONCAT_PAD_CAST (pad);
GList *l;
gboolean current_pad_removed = FALSE;
gboolean eos = FALSE;
gboolean do_notify = FALSE;
GST_DEBUG_OBJECT (pad, "releasing pad");
g_mutex_lock (&self->lock);
spad->flushing = TRUE;
g_cond_broadcast (&self->cond);
g_mutex_unlock (&self->lock);
gst_pad_set_active (pad, FALSE);
/* Now the pad is definitely not running anymore */
g_mutex_lock (&self->lock);
if (self->current_sinkpad == GST_PAD_CAST (spad)) {
eos = !gst_concat_switch_pad (self);
current_pad_removed = TRUE;
do_notify = TRUE;
}
for (l = self->sinkpads; l; l = l->next) {
if ((gpointer) spad == l->data) {
gst_object_unref (spad);
self->sinkpads = g_list_delete_link (self->sinkpads, l);
break;
}
}
g_mutex_unlock (&self->lock);
gst_element_remove_pad (GST_ELEMENT_CAST (self), pad);
if (do_notify)
gst_concat_notify_active_pad (self);
if (GST_STATE (self) > GST_STATE_READY) {
if (current_pad_removed && !eos)
gst_element_post_message (GST_ELEMENT_CAST (self),
gst_message_new_duration_changed (GST_OBJECT_CAST (self)));
/* FIXME: Sending EOS from application thread */
if (eos)
gst_pad_push_event (self->srcpad, gst_event_new_eos ());
}
}
/* Returns FALSE if flushing
* Must be called from the pad's streaming thread
*/
static gboolean
gst_concat_pad_wait (GstConcatPad * spad, GstConcat * self)
{
g_mutex_lock (&self->lock);
if (spad->flushing) {
g_mutex_unlock (&self->lock);
GST_DEBUG_OBJECT (spad, "Flushing");
return FALSE;
}
while (spad != GST_CONCAT_PAD_CAST (self->current_sinkpad)) {
GST_TRACE_OBJECT (spad, "Not the current sinkpad - waiting");
if (self->current_sinkpad == NULL && g_list_length (self->sinkpads) == 1) {
GST_LOG_OBJECT (spad, "Sole pad waiting, switching");
/* If we are the only sinkpad, take active pad ownership */
self->current_sinkpad = gst_object_ref (self->sinkpads->data);
break;
}
g_cond_wait (&self->cond, &self->lock);
if (spad->flushing) {
g_mutex_unlock (&self->lock);
GST_DEBUG_OBJECT (spad, "Flushing");
return FALSE;
}
}
/* This pad can only become not the current sinkpad from
* a) This streaming thread (we hold the stream lock)
* b) Releasing the pad (takes the stream lock, see above)
*
* Unlocking here is thus safe and we can safely push
* serialized data to our srcpad
*/
GST_DEBUG_OBJECT (spad, "Now the current sinkpad");
g_mutex_unlock (&self->lock);
return TRUE;
}
static GstFlowReturn
gst_concat_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
{
GstFlowReturn ret;
GstConcat *self = GST_CONCAT (parent);
GstConcatPad *spad = GST_CONCAT_PAD (pad);
GST_LOG_OBJECT (pad, "received buffer %" GST_PTR_FORMAT, buffer);
if (!gst_concat_pad_wait (spad, self))
return GST_FLOW_FLUSHING;
if (self->last_stop == GST_CLOCK_TIME_NONE)
self->last_stop = spad->segment.start;
if (self->format == GST_FORMAT_TIME) {
GstClockTime start_time = GST_BUFFER_TIMESTAMP (buffer);
GstClockTime end_time = GST_CLOCK_TIME_NONE;
if (start_time != GST_CLOCK_TIME_NONE)
end_time = start_time;
if (GST_BUFFER_DURATION_IS_VALID (buffer))
end_time += GST_BUFFER_DURATION (buffer);
if (end_time != GST_CLOCK_TIME_NONE && end_time > self->last_stop)
self->last_stop = end_time;
} else {
self->last_stop += gst_buffer_get_size (buffer);
}
ret = gst_pad_push (self->srcpad, buffer);
GST_LOG_OBJECT (pad, "handled buffer %s, last_stop %" GST_TIME_FORMAT,
gst_flow_get_name (ret), GST_TIME_ARGS (self->last_stop));
return ret;
}
/* Returns FALSE if no further pad, must be called with concat lock */
static gboolean
gst_concat_switch_pad (GstConcat * self)
{
GList *l;
gboolean next;
GstSegment segment;
gint64 last_stop;
segment = GST_CONCAT_PAD (self->current_sinkpad)->segment;
last_stop = self->last_stop;
if (last_stop == GST_CLOCK_TIME_NONE)
last_stop = segment.stop;
if (last_stop == GST_CLOCK_TIME_NONE)
last_stop = segment.start;
g_assert (last_stop != GST_CLOCK_TIME_NONE);
if (last_stop > segment.stop)
last_stop = segment.stop;
if (segment.format == GST_FORMAT_TIME)
last_stop =
gst_segment_to_running_time (&segment, segment.format, last_stop);
else
last_stop += segment.start;
self->current_start_offset += last_stop;
for (l = self->sinkpads; l; l = l->next) {
if ((gpointer) self->current_sinkpad == l->data) {
l = l->prev;
GST_DEBUG_OBJECT (self,
"Switching from pad %" GST_PTR_FORMAT " to %" GST_PTR_FORMAT,
self->current_sinkpad, l ? l->data : NULL);
gst_object_unref (self->current_sinkpad);
self->current_sinkpad = l ? gst_object_ref (l->data) : NULL;
g_cond_broadcast (&self->cond);
break;
}
}
next = self->current_sinkpad != NULL;
self->last_stop = GST_CLOCK_TIME_NONE;
return next;
}
static void
gst_concat_notify_active_pad (GstConcat * self)
{
g_object_notify_by_pspec ((GObject *) self, pspec_active_pad);
}
static gboolean
gst_concat_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstConcat *self = GST_CONCAT (parent);
GstConcatPad *spad = GST_CONCAT_PAD_CAST (pad);
gboolean ret = TRUE;
GST_LOG_OBJECT (pad, "received event %" GST_PTR_FORMAT, event);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_STREAM_START:{
if (!gst_concat_pad_wait (spad, self)) {
ret = FALSE;
gst_event_unref (event);
} else {
ret = gst_pad_event_default (pad, parent, event);
}
break;
}
case GST_EVENT_SEGMENT:{
gboolean adjust_base;
/* Drop segment event, we create our own one */
gst_event_copy_segment (event, &spad->segment);
gst_event_unref (event);
g_mutex_lock (&self->lock);
adjust_base = self->adjust_base;
if (self->format == GST_FORMAT_UNDEFINED) {
if (spad->segment.format != GST_FORMAT_TIME
&& spad->segment.format != GST_FORMAT_BYTES) {
g_mutex_unlock (&self->lock);
GST_ELEMENT_ERROR (self, CORE, FAILED, (NULL),
("Can only operate in TIME or BYTES format"));
ret = FALSE;
break;
}
self->format = spad->segment.format;
GST_DEBUG_OBJECT (self, "Operating in %s format",
gst_format_get_name (self->format));
g_mutex_unlock (&self->lock);
} else if (self->format != spad->segment.format) {
g_mutex_unlock (&self->lock);
GST_ELEMENT_ERROR (self, CORE, FAILED, (NULL),
("Operating in %s format but new pad has %s",
gst_format_get_name (self->format),
gst_format_get_name (spad->segment.format)));
ret = FALSE;
} else {
g_mutex_unlock (&self->lock);
}
if (!gst_concat_pad_wait (spad, self)) {
ret = FALSE;
} else {
GstSegment segment = spad->segment;
GstEvent *topush;
if (adjust_base) {
/* We know no duration */
segment.duration = -1;
/* Update segment values to be continuous with last stream */
if (self->format == GST_FORMAT_TIME) {
GST_DEBUG_OBJECT (self,
"Updating segment base %" GST_TIME_FORMAT " + %" GST_TIME_FORMAT
" = %" GST_TIME_FORMAT, GST_TIME_ARGS (segment.base),
GST_TIME_ARGS (self->current_start_offset),
GST_TIME_ARGS (segment.base + self->current_start_offset));
segment.base += self->current_start_offset;
} else {
/* Shift start/stop byte position */
GST_DEBUG_OBJECT (self,
"Updating segment start %" G_GUINT64_FORMAT " + %"
G_GUINT64_FORMAT " = %" G_GUINT64_FORMAT, segment.start,
self->current_start_offset,
segment.start + self->current_start_offset);
segment.start += self->current_start_offset;
if (segment.stop != -1) {
GST_DEBUG_OBJECT (self,
"Updating segment stop %" G_GUINT64_FORMAT " + %"
G_GUINT64_FORMAT " = %" G_GUINT64_FORMAT, segment.stop,
self->current_start_offset,
segment.stop + self->current_start_offset);
segment.stop += self->current_start_offset;
}
}
}
topush = gst_event_new_segment (&segment);
gst_event_set_seqnum (topush, gst_event_get_seqnum (event));
gst_pad_push_event (self->srcpad, topush);
}
break;
}
case GST_EVENT_EOS:{
gst_event_unref (event);
if (!gst_concat_pad_wait (spad, self)) {
ret = FALSE;
} else {
gboolean next;
g_mutex_lock (&self->lock);
next = gst_concat_switch_pad (self);
g_mutex_unlock (&self->lock);
ret = TRUE;
gst_concat_notify_active_pad (self);
if (!next) {
gst_pad_push_event (self->srcpad, gst_event_new_eos ());
} else {
gst_element_post_message (GST_ELEMENT_CAST (self),
gst_message_new_duration_changed (GST_OBJECT_CAST (self)));
}
}
break;
}
case GST_EVENT_FLUSH_START:{
gboolean forward;
g_mutex_lock (&self->lock);
spad->flushing = TRUE;
g_cond_broadcast (&self->cond);
forward = (self->current_sinkpad == GST_PAD_CAST (spad));
if (!forward && g_list_length (self->sinkpads) == 1)
forward = TRUE;
g_mutex_unlock (&self->lock);
if (forward)
ret = gst_pad_event_default (pad, parent, event);
else
gst_event_unref (event);
break;
}
case GST_EVENT_FLUSH_STOP:{
gboolean forward;
gst_segment_init (&spad->segment, GST_FORMAT_UNDEFINED);
spad->flushing = FALSE;
g_mutex_lock (&self->lock);
forward = (self->current_sinkpad == GST_PAD_CAST (spad));
if (!forward && g_list_length (self->sinkpads) == 1)
forward = TRUE;
g_mutex_unlock (&self->lock);
if (forward) {
gboolean reset_time;
gst_event_parse_flush_stop (event, &reset_time);
if (reset_time) {
GST_DEBUG_OBJECT (self,
"resetting start offset to 0 after flushing with reset_time = TRUE");
self->current_start_offset = 0;
self->last_stop = GST_CLOCK_TIME_NONE;
}
ret = gst_pad_event_default (pad, parent, event);
} else {
gst_event_unref (event);
}
break;
}
default:{
/* Wait for other serialized events before forwarding */
if (GST_EVENT_IS_SERIALIZED (event) && !gst_concat_pad_wait (spad, self)) {
gst_event_unref (event);
ret = FALSE;
} else {
ret = gst_pad_event_default (pad, parent, event);
}
break;
}
}
return ret;
}
static gboolean
gst_concat_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
GstConcat *self = GST_CONCAT (parent);
GstConcatPad *spad = GST_CONCAT_PAD_CAST (pad);
gboolean ret = TRUE;
GST_LOG_OBJECT (pad, "received query %" GST_PTR_FORMAT, query);
switch (GST_QUERY_TYPE (query)) {
default:
/* Wait for other serialized queries before forwarding */
if (GST_QUERY_IS_SERIALIZED (query) && !gst_concat_pad_wait (spad, self)) {
ret = FALSE;
} else {
ret = gst_pad_query_default (pad, parent, query);
}
break;
}
return ret;
}
static gboolean
gst_concat_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstConcat *self = GST_CONCAT (parent);
gboolean ret = TRUE;
GST_LOG_OBJECT (pad, "received event %" GST_PTR_FORMAT, event);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:{
GstPad *sinkpad = NULL;
g_mutex_lock (&self->lock);
if ((sinkpad = self->current_sinkpad))
gst_object_ref (sinkpad);
/* If no current active sinkpad but only one sinkpad, try reactivating that pad */
if (sinkpad == NULL && g_list_length (self->sinkpads) == 1) {
sinkpad = gst_object_ref (self->sinkpads->data);
}
g_mutex_unlock (&self->lock);
if (sinkpad) {
ret = gst_pad_push_event (sinkpad, event);
gst_object_unref (sinkpad);
} else {
gst_event_unref (event);
ret = FALSE;
}
break;
}
case GST_EVENT_QOS:{
GstQOSType type;
GstClockTimeDiff diff;
GstClockTime timestamp;
gdouble proportion;
GstPad *sinkpad = NULL;
g_mutex_lock (&self->lock);
if ((sinkpad = self->current_sinkpad))
gst_object_ref (sinkpad);
g_mutex_unlock (&self->lock);
if (sinkpad) {
gst_event_parse_qos (event, &type, &proportion, &diff, &timestamp);
gst_event_unref (event);
if (timestamp != GST_CLOCK_TIME_NONE
&& timestamp > self->current_start_offset) {
timestamp -= self->current_start_offset;
event = gst_event_new_qos (type, proportion, diff, timestamp);
ret = gst_pad_push_event (self->current_sinkpad, event);
} else {
ret = FALSE;
}
gst_object_unref (sinkpad);
} else {
gst_event_unref (event);
ret = FALSE;
}
break;
}
case GST_EVENT_FLUSH_STOP:{
gboolean reset_time;
gst_event_parse_flush_stop (event, &reset_time);
if (reset_time) {
GST_DEBUG_OBJECT (self,
"resetting start offset to 0 after flushing with reset_time = TRUE");
self->current_start_offset = 0;
}
ret = gst_pad_event_default (pad, parent, event);
break;
}
default:
ret = gst_pad_event_default (pad, parent, event);
break;
}
return ret;
}
static gboolean
gst_concat_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
gboolean ret = TRUE;
GST_LOG_OBJECT (pad, "received query %" GST_PTR_FORMAT, query);
switch (GST_QUERY_TYPE (query)) {
default:
ret = gst_pad_query_default (pad, parent, query);
break;
}
return ret;
}
static void
reset_pad (const GValue * data, gpointer user_data)
{
GstPad *pad = g_value_get_object (data);
GstConcatPad *spad = GST_CONCAT_PAD_CAST (pad);
gst_segment_init (&spad->segment, GST_FORMAT_UNDEFINED);
spad->flushing = FALSE;
}
static void
unblock_pad (const GValue * data, gpointer user_data)
{
GstPad *pad = g_value_get_object (data);
GstConcatPad *spad = GST_CONCAT_PAD_CAST (pad);
spad->flushing = TRUE;
}
static GstStateChangeReturn
gst_concat_change_state (GstElement * element, GstStateChange transition)
{
GstConcat *self = GST_CONCAT (element);
GstStateChangeReturn ret;
switch (transition) {
case GST_STATE_CHANGE_READY_TO_PAUSED:{
GstIterator *iter = gst_element_iterate_sink_pads (element);
GstIteratorResult res;
self->format = GST_FORMAT_UNDEFINED;
self->current_start_offset = 0;
self->last_stop = GST_CLOCK_TIME_NONE;
while ((res =
gst_iterator_foreach (iter, reset_pad,
NULL)) == GST_ITERATOR_RESYNC)
gst_iterator_resync (iter);
gst_iterator_free (iter);
if (res == GST_ITERATOR_ERROR)
return GST_STATE_CHANGE_FAILURE;
break;
}
case GST_STATE_CHANGE_PAUSED_TO_READY:{
GstIterator *iter = gst_element_iterate_sink_pads (element);
GstIteratorResult res;
g_mutex_lock (&self->lock);
while ((res =
gst_iterator_foreach (iter, unblock_pad,
NULL)) == GST_ITERATOR_RESYNC)
gst_iterator_resync (iter);
gst_iterator_free (iter);
g_cond_broadcast (&self->cond);
g_mutex_unlock (&self->lock);
if (res == GST_ITERATOR_ERROR)
return GST_STATE_CHANGE_FAILURE;
break;
}
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
return ret;
}